Repository: T8RIN/ImageResizer Branch: master Commit: f34bd3109fe6 Files: 3013 Total size: 21.2 MB Directory structure: gitextract_9qydnqng/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── feature_request.yml │ ├── dependabot.yml │ └── workflows/ │ ├── android.yml │ ├── android_foss.yml │ ├── android_market.yml │ └── tb_release.yml ├── .gitignore ├── ARCHITECTURE.md ├── ARCHITECTURE_2 ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── app/ │ │ │ └── presentation/ │ │ │ ├── AppActivity.kt │ │ │ └── components/ │ │ │ ├── ImageToolboxApplication.kt │ │ │ ├── functions/ │ │ │ │ ├── AttachLogWriter.kt │ │ │ │ ├── InitCollages.kt │ │ │ │ ├── InitColorNames.kt │ │ │ │ ├── InitNeuralTool.kt │ │ │ │ ├── InitOpenCV.kt │ │ │ │ ├── InitPdfBox.kt │ │ │ │ ├── InitQrScanner.kt │ │ │ │ ├── InjectBaseComponent.kt │ │ │ │ ├── RegisterSecurityProviders.kt │ │ │ │ └── SetupFlags.kt │ │ │ └── utils/ │ │ │ └── GetProcessName.kt │ │ └── res/ │ │ └── resources.properties │ └── market/ │ ├── debug/ │ │ └── google-services.json │ └── release/ │ └── google-services.json ├── benchmark/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── t8rin/ │ └── imagetoolbox/ │ └── benchmark/ │ └── BaselineProfileGenerator.kt ├── build-logic/ │ ├── .gitignore │ ├── convention/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ └── kotlin/ │ │ ├── ImageToolboxApplicationPlugin.kt │ │ ├── ImageToolboxHiltPlugin.kt │ │ ├── ImageToolboxLibraryComposePlugin.kt │ │ ├── ImageToolboxLibraryFeaturePlugin.kt │ │ ├── ImageToolboxLibraryPlugin.kt │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ ├── ConfigureCompose.kt │ │ ├── ConfigureDetekt.kt │ │ ├── ConfigureKotlinAndroid.kt │ │ └── ProjectExtensions.kt │ └── settings.gradle.kts ├── build.gradle.kts ├── compose_compiler_config.conf ├── core/ │ ├── crash/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── foss/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── core/ │ │ │ └── crash/ │ │ │ └── data/ │ │ │ └── AnalyticsManagerImpl.kt │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── core/ │ │ │ └── crash/ │ │ │ ├── di/ │ │ │ │ └── CrashModule.kt │ │ │ └── presentation/ │ │ │ ├── CrashActivity.kt │ │ │ ├── components/ │ │ │ │ ├── CrashActionButtons.kt │ │ │ │ ├── CrashAttentionCard.kt │ │ │ │ ├── CrashBottomButtons.kt │ │ │ │ ├── CrashHandler.kt │ │ │ │ ├── CrashInfoCard.kt │ │ │ │ ├── CrashRootContent.kt │ │ │ │ └── GlobalExceptionHandler.kt │ │ │ └── screenLogic/ │ │ │ └── CrashComponent.kt │ │ └── market/ │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── core/ │ │ └── crash/ │ │ └── data/ │ │ └── AnalyticsManagerImpl.kt │ ├── data/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── core/ │ │ └── data/ │ │ ├── coil/ │ │ │ ├── Base64Fetcher.kt │ │ │ ├── CoilLogger.kt │ │ │ ├── MemoryCache.kt │ │ │ ├── PdfDecoder.kt │ │ │ ├── TiffDecoder.kt │ │ │ ├── TimeMeasureInterceptor.kt │ │ │ └── UpscaleSvgDecoder.kt │ │ ├── coroutines/ │ │ │ ├── AndroidDispatchersHolder.kt │ │ │ └── AppScopeImpl.kt │ │ ├── di/ │ │ │ ├── CoroutinesModule.kt │ │ │ ├── ImageLoaderModule.kt │ │ │ ├── ImageModule.kt │ │ │ ├── JsonModule.kt │ │ │ ├── LocalModule.kt │ │ │ ├── RemoteModule.kt │ │ │ ├── ResourcesModule.kt │ │ │ └── SavingModule.kt │ │ ├── image/ │ │ │ ├── AndroidImageCompressor.kt │ │ │ ├── AndroidImageGetter.kt │ │ │ ├── AndroidImagePreviewCreator.kt │ │ │ ├── AndroidImageScaler.kt │ │ │ ├── AndroidImageTransformer.kt │ │ │ ├── AndroidMetadata.kt │ │ │ ├── AndroidShareProvider.kt │ │ │ └── utils/ │ │ │ ├── BlendingModeExt.kt │ │ │ ├── CanvasUtils.kt │ │ │ ├── ColorUtils.kt │ │ │ ├── ImageCompressorBackend.kt │ │ │ ├── StaticOptions.kt │ │ │ └── compressor/ │ │ │ ├── AvifBackend.kt │ │ │ ├── BmpBackend.kt │ │ │ ├── HeicBackend.kt │ │ │ ├── IcoBackend.kt │ │ │ ├── Jpeg2000Backend.kt │ │ │ ├── JpegliBackend.kt │ │ │ ├── JpgBackend.kt │ │ │ ├── JxlBackend.kt │ │ │ ├── MozJpegBackend.kt │ │ │ ├── OxiPngBackend.kt │ │ │ ├── PngLosslessBackend.kt │ │ │ ├── PngLossyBackend.kt │ │ │ ├── QoiBackend.kt │ │ │ ├── StaticGifBackend.kt │ │ │ ├── TiffBackend.kt │ │ │ └── WebpBackend.kt │ │ ├── json/ │ │ │ └── MoshiParser.kt │ │ ├── remote/ │ │ │ ├── AndroidDownloadManager.kt │ │ │ └── AndroidRemoteResourcesStore.kt │ │ ├── resource/ │ │ │ └── AndroidResourceManager.kt │ │ ├── saving/ │ │ │ ├── AndroidFileController.kt │ │ │ ├── AndroidFilenameCreator.kt │ │ │ ├── AndroidKeepAliveService.kt │ │ │ ├── KeepAliveForegroundService.kt │ │ │ ├── SaveException.kt │ │ │ ├── SavingFolder.kt │ │ │ └── io/ │ │ │ ├── FileWriteable.kt │ │ │ ├── StreamWriteable.kt │ │ │ └── UriReadable.kt │ │ └── utils/ │ │ ├── BitmapUtils.kt │ │ ├── ChecksumUtils.kt │ │ ├── CoilUtils.kt │ │ ├── ContextUtils.kt │ │ ├── CoroutinesUtils.kt │ │ ├── FileUtils.kt │ │ ├── HttpClientUtils.kt │ │ └── WriteableUtils.kt │ ├── di/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── core/ │ │ └── di/ │ │ ├── EntryPointUtils.kt │ │ └── Qualifiers.kt │ ├── domain/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── kotlin/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── core/ │ │ └── domain/ │ │ ├── Constants.kt │ │ ├── coroutines/ │ │ │ ├── AppScope.kt │ │ │ └── DispatchersHolder.kt │ │ ├── image/ │ │ │ ├── ImageCompressor.kt │ │ │ ├── ImageGetter.kt │ │ │ ├── ImagePreviewCreator.kt │ │ │ ├── ImageScaler.kt │ │ │ ├── ImageShareProvider.kt │ │ │ ├── ImageTransformer.kt │ │ │ ├── Metadata.kt │ │ │ ├── ShareProvider.kt │ │ │ └── model/ │ │ │ ├── BlendingMode.kt │ │ │ ├── ImageData.kt │ │ │ ├── ImageFormat.kt │ │ │ ├── ImageFormatGroup.kt │ │ │ ├── ImageFrames.kt │ │ │ ├── ImageInfo.kt │ │ │ ├── ImageScaleMode.kt │ │ │ ├── ImageWithSize.kt │ │ │ ├── MetadataTag.kt │ │ │ ├── Preset.kt │ │ │ ├── Quality.kt │ │ │ ├── ResizeAnchor.kt │ │ │ ├── ResizeType.kt │ │ │ └── TiffCompressionScheme.kt │ │ ├── json/ │ │ │ └── JsonParser.kt │ │ ├── model/ │ │ │ ├── CipherType.kt │ │ │ ├── ColorModel.kt │ │ │ ├── DomainAspectRatio.kt │ │ │ ├── ExtraDataType.kt │ │ │ ├── FileModel.kt │ │ │ ├── FloatSize.kt │ │ │ ├── HashingType.kt │ │ │ ├── ImageModel.kt │ │ │ ├── IntegerSize.kt │ │ │ ├── MimeType.kt │ │ │ ├── OffsetModel.kt │ │ │ ├── Outline.kt │ │ │ ├── PerformanceClass.kt │ │ │ ├── Position.kt │ │ │ ├── Pt.kt │ │ │ ├── QrType.kt │ │ │ ├── RectModel.kt │ │ │ ├── SecureAlgorithmsMapping.kt │ │ │ ├── SortType.kt │ │ │ ├── SystemBarsVisibility.kt │ │ │ └── VisibilityOwner.kt │ │ ├── remote/ │ │ │ ├── AnalyticsManager.kt │ │ │ ├── Cache.kt │ │ │ ├── DownloadManager.kt │ │ │ ├── DownloadProgress.kt │ │ │ ├── RemoteResources.kt │ │ │ └── RemoteResourcesStore.kt │ │ ├── resource/ │ │ │ └── ResourceManager.kt │ │ ├── saving/ │ │ │ ├── FileController.kt │ │ │ ├── FilenameCreator.kt │ │ │ ├── KeepAliveService.kt │ │ │ ├── ObjectSaver.kt │ │ │ ├── RandomStringGenerator.kt │ │ │ ├── io/ │ │ │ │ ├── IoCloseable.kt │ │ │ │ ├── Readable.kt │ │ │ │ └── Writeable.kt │ │ │ └── model/ │ │ │ ├── FileSaveTarget.kt │ │ │ ├── FilenamePattern.kt │ │ │ ├── ImageSaveTarget.kt │ │ │ ├── SaveResult.kt │ │ │ └── SaveTarget.kt │ │ ├── transformation/ │ │ │ ├── ChainTransformation.kt │ │ │ ├── GenericTransformation.kt │ │ │ └── Transformation.kt │ │ └── utils/ │ │ ├── ByteUtils.kt │ │ ├── Delegates.kt │ │ ├── FileMode.kt │ │ ├── Flavor.kt │ │ ├── IntUtils.kt │ │ ├── KotlinUtils.kt │ │ ├── ListUtils.kt │ │ ├── ProgressInputStream.kt │ │ ├── Quad.kt │ │ ├── Rounding.kt │ │ ├── SmartJob.kt │ │ ├── StringUtils.kt │ │ └── TimeUtils.kt │ ├── filters/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── core/ │ │ └── filters/ │ │ ├── data/ │ │ │ └── SideFadePaint.kt │ │ ├── domain/ │ │ │ ├── FilterParamsInteractor.kt │ │ │ ├── FilterProvider.kt │ │ │ └── model/ │ │ │ ├── Filter.kt │ │ │ ├── FilterParam.kt │ │ │ ├── FilterUtils.kt │ │ │ ├── FilterValueWrapper.kt │ │ │ ├── TemplateFilter.kt │ │ │ ├── enums/ │ │ │ │ ├── BlurEdgeMode.kt │ │ │ │ ├── ColorMapType.kt │ │ │ │ ├── FadeSide.kt │ │ │ │ ├── MirrorSide.kt │ │ │ │ ├── PaletteTransferSpace.kt │ │ │ │ ├── PolarCoordinatesType.kt │ │ │ │ ├── PopArtBlendingMode.kt │ │ │ │ ├── SpotHealMode.kt │ │ │ │ └── TransferFunc.kt │ │ │ └── params/ │ │ │ ├── ArcParams.kt │ │ │ ├── AsciiParams.kt │ │ │ ├── BilaterialBlurParams.kt │ │ │ ├── BloomParams.kt │ │ │ ├── ChannelMixParams.kt │ │ │ ├── ClaheParams.kt │ │ │ ├── CropOrPerspectiveParams.kt │ │ │ ├── EnhancedZoomBlurParams.kt │ │ │ ├── GlitchParams.kt │ │ │ ├── KaleidoscopeParams.kt │ │ │ ├── LinearGaussianParams.kt │ │ │ ├── LinearTiltShiftParams.kt │ │ │ ├── PinchParams.kt │ │ │ ├── RadialTiltShiftParams.kt │ │ │ ├── RubberStampParams.kt │ │ │ ├── SideFadeParams.kt │ │ │ ├── SmearParams.kt │ │ │ ├── SparkleParams.kt │ │ │ ├── ToneCurvesParams.kt │ │ │ ├── VoronoiCrystallizeParams.kt │ │ │ └── WaterParams.kt │ │ └── presentation/ │ │ ├── model/ │ │ │ ├── UiAcesFilmicToneMappingFilter.kt │ │ │ ├── UiAcesHillToneMappingFilter.kt │ │ │ ├── UiAchromatomalyFilter.kt │ │ │ ├── UiAchromatopsiaFilter.kt │ │ │ ├── UiAldridgeFilter.kt │ │ │ ├── UiAmatorkaFilter.kt │ │ │ ├── UiAnaglyphFilter.kt │ │ │ ├── UiAnisotropicDiffusionFilter.kt │ │ │ ├── UiArcFilter.kt │ │ │ ├── UiAsciiFilter.kt │ │ │ ├── UiAtkinsonDitheringFilter.kt │ │ │ ├── UiAutoCropFilter.kt │ │ │ ├── UiAutoPerspectiveFilter.kt │ │ │ ├── UiAutoRemoveRedEyesFilter.kt │ │ │ ├── UiAutumnTonesFilter.kt │ │ │ ├── UiAverageDistanceFilter.kt │ │ │ ├── UiBayerEightDitheringFilter.kt │ │ │ ├── UiBayerFourDitheringFilter.kt │ │ │ ├── UiBayerThreeDitheringFilter.kt │ │ │ ├── UiBayerTwoDitheringFilter.kt │ │ │ ├── UiBilaterialBlurFilter.kt │ │ │ ├── UiBlackAndWhiteFilter.kt │ │ │ ├── UiBlackHatFilter.kt │ │ │ ├── UiBleachBypassFilter.kt │ │ │ ├── UiBlockGlitchFilter.kt │ │ │ ├── UiBloomFilter.kt │ │ │ ├── UiBokehFilter.kt │ │ │ ├── UiBorderFrameFilter.kt │ │ │ ├── UiBoxBlurFilter.kt │ │ │ ├── UiBrightnessFilter.kt │ │ │ ├── UiBrowniFilter.kt │ │ │ ├── UiBulgeDistortionFilter.kt │ │ │ ├── UiBurkesDitheringFilter.kt │ │ │ ├── UiCGAColorSpaceFilter.kt │ │ │ ├── UiCandlelightFilter.kt │ │ │ ├── UiCannyFilter.kt │ │ │ ├── UiCaramelDarknessFilter.kt │ │ │ ├── UiCelluloidFilter.kt │ │ │ ├── UiChannelMixFilter.kt │ │ │ ├── UiCircleBlurFilter.kt │ │ │ ├── UiCirclePixelationFilter.kt │ │ │ ├── UiClaheFilter.kt │ │ │ ├── UiClaheHSLFilter.kt │ │ │ ├── UiClaheHSVFilter.kt │ │ │ ├── UiClaheJzazbzFilter.kt │ │ │ ├── UiClaheLABFilter.kt │ │ │ ├── UiClaheLUVFilter.kt │ │ │ ├── UiClaheOklabFilter.kt │ │ │ ├── UiClaheOklchFilter.kt │ │ │ ├── UiClosingFilter.kt │ │ │ ├── UiClustered2x2DitheringFilter.kt │ │ │ ├── UiClustered4x4DitheringFilter.kt │ │ │ ├── UiClustered8x8DitheringFilter.kt │ │ │ ├── UiCodaChromeFilter.kt │ │ │ ├── UiCoffeeFilter.kt │ │ │ ├── UiColorAnomalyFilter.kt │ │ │ ├── UiColorBalanceFilter.kt │ │ │ ├── UiColorExplosionFilter.kt │ │ │ ├── UiColorHalftoneFilter.kt │ │ │ ├── UiColorMapFilter.kt │ │ │ ├── UiColorMatrix3x3Filter.kt │ │ │ ├── UiColorMatrix4x4Filter.kt │ │ │ ├── UiColorOverlayFilter.kt │ │ │ ├── UiColorPosterFilter.kt │ │ │ ├── UiColorfulSwirlFilter.kt │ │ │ ├── UiContourFilter.kt │ │ │ ├── UiContrastFilter.kt │ │ │ ├── UiConvexFilter.kt │ │ │ ├── UiConvolution3x3Filter.kt │ │ │ ├── UiCoolFilter.kt │ │ │ ├── UiCopyMoveDetectionFilter.kt │ │ │ ├── UiCropOrPerspectiveFilter.kt │ │ │ ├── UiCropToContentFilter.kt │ │ │ ├── UiCrossBlurFilter.kt │ │ │ ├── UiCrossPixelizationFilter.kt │ │ │ ├── UiCrosshatchFilter.kt │ │ │ ├── UiCrtCurvatureFilter.kt │ │ │ ├── UiCrystallizeFilter.kt │ │ │ ├── UiCubeLutFilter.kt │ │ │ ├── UiCyberpunkFilter.kt │ │ │ ├── UiDeepPurpleFilter.kt │ │ │ ├── UiDehazeFilter.kt │ │ │ ├── UiDeskewFilter.kt │ │ │ ├── UiDespeckleFilter.kt │ │ │ ├── UiDeutaromalyFilter.kt │ │ │ ├── UiDeutaronotopiaFilter.kt │ │ │ ├── UiDiamondPixelationFilter.kt │ │ │ ├── UiDiffuseFilter.kt │ │ │ ├── UiDigitalCodeFilter.kt │ │ │ ├── UiDilationFilter.kt │ │ │ ├── UiDoGFilter.kt │ │ │ ├── UiDragoFilter.kt │ │ │ ├── UiDropBluesFilter.kt │ │ │ ├── UiEdgyAmberFilter.kt │ │ │ ├── UiElectricGradientFilter.kt │ │ │ ├── UiEmbossFilter.kt │ │ │ ├── UiEnhancedCirclePixelationFilter.kt │ │ │ ├── UiEnhancedDiamondPixelationFilter.kt │ │ │ ├── UiEnhancedGlitchFilter.kt │ │ │ ├── UiEnhancedOilFilter.kt │ │ │ ├── UiEnhancedPixelationFilter.kt │ │ │ ├── UiEnhancedZoomBlurFilter.kt │ │ │ ├── UiEqualizeFilter.kt │ │ │ ├── UiEqualizeHistogramAdaptiveFilter.kt │ │ │ ├── UiEqualizeHistogramAdaptiveHSLFilter.kt │ │ │ ├── UiEqualizeHistogramAdaptiveHSVFilter.kt │ │ │ ├── UiEqualizeHistogramAdaptiveLABFilter.kt │ │ │ ├── UiEqualizeHistogramAdaptiveLUVFilter.kt │ │ │ ├── UiEqualizeHistogramFilter.kt │ │ │ ├── UiEqualizeHistogramHSVFilter.kt │ │ │ ├── UiEqualizeHistogramPixelationFilter.kt │ │ │ ├── UiErodeFilter.kt │ │ │ ├── UiErrorLevelAnalysisFilter.kt │ │ │ ├── UiExposureFilter.kt │ │ │ ├── UiFallColorsFilter.kt │ │ │ ├── UiFalseColorFilter.kt │ │ │ ├── UiFalseFloydSteinbergDitheringFilter.kt │ │ │ ├── UiFantasyLandscapeFilter.kt │ │ │ ├── UiFastBilaterialBlurFilter.kt │ │ │ ├── UiFastBlurFilter.kt │ │ │ ├── UiFastGaussianBlur2DFilter.kt │ │ │ ├── UiFastGaussianBlur3DFilter.kt │ │ │ ├── UiFastGaussianBlur4DFilter.kt │ │ │ ├── UiFilmStock50Filter.kt │ │ │ ├── UiFilter.kt │ │ │ ├── UiFloydSteinbergDitheringFilter.kt │ │ │ ├── UiFoggyNightFilter.kt │ │ │ ├── UiFractalGlassFilter.kt │ │ │ ├── UiFuturisticGradientFilter.kt │ │ │ ├── UiGammaFilter.kt │ │ │ ├── UiGaussianBlurFilter.kt │ │ │ ├── UiGaussianBoxBlurFilter.kt │ │ │ ├── UiGlassSphereRefractionFilter.kt │ │ │ ├── UiGlitchFilter.kt │ │ │ ├── UiGlitchVariantFilter.kt │ │ │ ├── UiGlowFilter.kt │ │ │ ├── UiGoldenForestFilter.kt │ │ │ ├── UiGoldenHourFilter.kt │ │ │ ├── UiGothamFilter.kt │ │ │ ├── UiGrainFilter.kt │ │ │ ├── UiGrayscaleFilter.kt │ │ │ ├── UiGreenSunFilter.kt │ │ │ ├── UiGreenishFilter.kt │ │ │ ├── UiHDRFilter.kt │ │ │ ├── UiHableFilmicToneMappingFilter.kt │ │ │ ├── UiHalftoneFilter.kt │ │ │ ├── UiHazeFilter.kt │ │ │ ├── UiHejlBurgessToneMappingFilter.kt │ │ │ ├── UiHighlightsAndShadowsFilter.kt │ │ │ ├── UiHorizontalWindStaggerFilter.kt │ │ │ ├── UiHotSummerFilter.kt │ │ │ ├── UiHueFilter.kt │ │ │ ├── UiJarvisJudiceNinkeDitheringFilter.kt │ │ │ ├── UiKaleidoscopeFilter.kt │ │ │ ├── UiKodakFilter.kt │ │ │ ├── UiKuwaharaFilter.kt │ │ │ ├── UiLUT512x512Filter.kt │ │ │ ├── UiLaplacianFilter.kt │ │ │ ├── UiLaplacianSimpleFilter.kt │ │ │ ├── UiLavenderDreamFilter.kt │ │ │ ├── UiLeftToRightDitheringFilter.kt │ │ │ ├── UiLemonadeLightFilter.kt │ │ │ ├── UiLensCorrectionFilter.kt │ │ │ ├── UiLinearBoxBlurFilter.kt │ │ │ ├── UiLinearFastGaussianBlurFilter.kt │ │ │ ├── UiLinearFastGaussianBlurNextFilter.kt │ │ │ ├── UiLinearGaussianBlurFilter.kt │ │ │ ├── UiLinearGaussianBoxBlurFilter.kt │ │ │ ├── UiLinearStackBlurFilter.kt │ │ │ ├── UiLinearTentBlurFilter.kt │ │ │ ├── UiLinearTiltShiftFilter.kt │ │ │ ├── UiLogarithmicToneMappingFilter.kt │ │ │ ├── UiLookupFilter.kt │ │ │ ├── UiLowPolyFilter.kt │ │ │ ├── UiLuminanceGradientFilter.kt │ │ │ ├── UiMarbleFilter.kt │ │ │ ├── UiMedianBlurFilter.kt │ │ │ ├── UiMicroMacroPixelizationFilter.kt │ │ │ ├── UiMirrorFilter.kt │ │ │ ├── UiMissEtikateFilter.kt │ │ │ ├── UiMobiusFilter.kt │ │ │ ├── UiMoireFilter.kt │ │ │ ├── UiMonochromeFilter.kt │ │ │ ├── UiMorphologicalGradientFilter.kt │ │ │ ├── UiMotionBlurFilter.kt │ │ │ ├── UiNativeStackBlurFilter.kt │ │ │ ├── UiNegativeFilter.kt │ │ │ ├── UiNeonFilter.kt │ │ │ ├── UiNightMagicFilter.kt │ │ │ ├── UiNightVisionFilter.kt │ │ │ ├── UiNoiseFilter.kt │ │ │ ├── UiNonMaximumSuppressionFilter.kt │ │ │ ├── UiNucleusPixelizationFilter.kt │ │ │ ├── UiOffsetFilter.kt │ │ │ ├── UiOilFilter.kt │ │ │ ├── UiOldTvFilter.kt │ │ │ ├── UiOpacityFilter.kt │ │ │ ├── UiOpeningFilter.kt │ │ │ ├── UiOrangeHazeFilter.kt │ │ │ ├── UiOrbitalPixelizationFilter.kt │ │ │ ├── UiPaletteTransferFilter.kt │ │ │ ├── UiPaletteTransferVariantFilter.kt │ │ │ ├── UiPastelFilter.kt │ │ │ ├── UiPerlinDistortionFilter.kt │ │ │ ├── UiPinchFilter.kt │ │ │ ├── UiPinkDreamFilter.kt │ │ │ ├── UiPixelMeltFilter.kt │ │ │ ├── UiPixelationFilter.kt │ │ │ ├── UiPointillizeFilter.kt │ │ │ ├── UiPoissonBlurFilter.kt │ │ │ ├── UiPolarCoordinatesFilter.kt │ │ │ ├── UiPolaroidFilter.kt │ │ │ ├── UiPolkaDotFilter.kt │ │ │ ├── UiPopArtFilter.kt │ │ │ ├── UiPosterizeFilter.kt │ │ │ ├── UiProtanopiaFilter.kt │ │ │ ├── UiProtonomalyFilter.kt │ │ │ ├── UiPulseGridPixelizationFilter.kt │ │ │ ├── UiPurpleMistFilter.kt │ │ │ ├── UiQuantizierFilter.kt │ │ │ ├── UiRGBFilter.kt │ │ │ ├── UiRadialTiltShiftFilter.kt │ │ │ ├── UiRadialWeavePixelizationFilter.kt │ │ │ ├── UiRainbowWorldFilter.kt │ │ │ ├── UiRandomDitheringFilter.kt │ │ │ ├── UiRedSwirlFilter.kt │ │ │ ├── UiReduceNoiseFilter.kt │ │ │ ├── UiRemoveColorFilter.kt │ │ │ ├── UiReplaceColorFilter.kt │ │ │ ├── UiRetroYellowFilter.kt │ │ │ ├── UiRingBlurFilter.kt │ │ │ ├── UiRubberStampFilter.kt │ │ │ ├── UiSandPaintingFilter.kt │ │ │ ├── UiSaturationFilter.kt │ │ │ ├── UiSeamCarvingFilter.kt │ │ │ ├── UiSepiaFilter.kt │ │ │ ├── UiSharpenFilter.kt │ │ │ ├── UiShuffleBlurFilter.kt │ │ │ ├── UiSideFadeFilter.kt │ │ │ ├── UiSierraDitheringFilter.kt │ │ │ ├── UiSierraLiteDitheringFilter.kt │ │ │ ├── UiSimpleOldTvFilter.kt │ │ │ ├── UiSimpleSketchFilter.kt │ │ │ ├── UiSimpleSolarizeFilter.kt │ │ │ ├── UiSimpleThresholdDitheringFilter.kt │ │ │ ├── UiSimpleWeavePixelationFilter.kt │ │ │ ├── UiSketchFilter.kt │ │ │ ├── UiSmearFilter.kt │ │ │ ├── UiSmoothToonFilter.kt │ │ │ ├── UiSobelEdgeDetectionFilter.kt │ │ │ ├── UiSobelSimpleFilter.kt │ │ │ ├── UiSoftEleganceFilter.kt │ │ │ ├── UiSoftEleganceVariantFilter.kt │ │ │ ├── UiSoftSpringLightFilter.kt │ │ │ ├── UiSolarizeFilter.kt │ │ │ ├── UiSpacePortalFilter.kt │ │ │ ├── UiSparkleFilter.kt │ │ │ ├── UiSpectralFireFilter.kt │ │ │ ├── UiSphereLensDistortionFilter.kt │ │ │ ├── UiSphereRefractionFilter.kt │ │ │ ├── UiStackBlurFilter.kt │ │ │ ├── UiStaggeredPixelizationFilter.kt │ │ │ ├── UiStarBlurFilter.kt │ │ │ ├── UiStrokePixelationFilter.kt │ │ │ ├── UiStuckiDitheringFilter.kt │ │ │ ├── UiSunriseFilter.kt │ │ │ ├── UiSwirlDistortionFilter.kt │ │ │ ├── UiTentBlurFilter.kt │ │ │ ├── UiThresholdFilter.kt │ │ │ ├── UiToneCurvesFilter.kt │ │ │ ├── UiToonFilter.kt │ │ │ ├── UiTopHatFilter.kt │ │ │ ├── UiTriToneFilter.kt │ │ │ ├── UiTritanopiaFilter.kt │ │ │ ├── UiTritonomalyFilter.kt │ │ │ ├── UiTwirlFilter.kt │ │ │ ├── UiTwoRowSierraDitheringFilter.kt │ │ │ ├── UiUchimuraFilter.kt │ │ │ ├── UiUnsharpFilter.kt │ │ │ ├── UiVHSFilter.kt │ │ │ ├── UiVibranceFilter.kt │ │ │ ├── UiVignetteFilter.kt │ │ │ ├── UiVintageFilter.kt │ │ │ ├── UiVoronoiCrystallizeFilter.kt │ │ │ ├── UiVortexPixelizationFilter.kt │ │ │ ├── UiWarmFilter.kt │ │ │ ├── UiWaterEffectFilter.kt │ │ │ ├── UiWeakPixelFilter.kt │ │ │ ├── UiWeaveFilter.kt │ │ │ ├── UiWhiteBalanceFilter.kt │ │ │ ├── UiYililomaDitheringFilter.kt │ │ │ └── UiZoomBlurFilter.kt │ │ ├── utils/ │ │ │ ├── CollectAsUiState.kt │ │ │ ├── LamaLoader.kt │ │ │ └── Mappings.kt │ │ └── widget/ │ │ ├── AddFilterButton.kt │ │ ├── CalculateBrightnessEstimate.kt │ │ ├── CubeLutDownloadDialog.kt │ │ ├── FilterItem.kt │ │ ├── FilterItemContent.kt │ │ ├── FilterPreviewSheet.kt │ │ ├── FilterReorderSheet.kt │ │ ├── FilterSelectionCubeLutBottomContent.kt │ │ ├── FilterSelectionItem.kt │ │ ├── FilterTemplateAddingGroup.kt │ │ ├── FilterTemplateCreationSheet.kt │ │ ├── FilterTemplateInfoSheet.kt │ │ ├── TemplateFilterSelectionItem.kt │ │ ├── addFilters/ │ │ │ ├── AddFiltersSheet.kt │ │ │ ├── AddFiltersSheetComponent.kt │ │ │ ├── FavoritesContent.kt │ │ │ ├── OtherContent.kt │ │ │ └── TemplatesContent.kt │ │ └── filterItem/ │ │ ├── ArcParamsItem.kt │ │ ├── AsciiParamsItem.kt │ │ ├── BilaterialBlurParamsItem.kt │ │ ├── BloomParamsItem.kt │ │ ├── BooleanItem.kt │ │ ├── ChannelMixParamsItem.kt │ │ ├── ClaheParamsItem.kt │ │ ├── CropOrPerspectiveParamsItem.kt │ │ ├── EdgeModeSelector.kt │ │ ├── EnhancedZoomBlurParamsItem.kt │ │ ├── FilterValueWrapperItem.kt │ │ ├── FloatArrayItem.kt │ │ ├── FloatItem.kt │ │ ├── GlitchParamsItem.kt │ │ ├── IntegerSizeParamsItem.kt │ │ ├── KaleidoscopeParamsItem.kt │ │ ├── LinearGaussianParamsItem.kt │ │ ├── LinearTiltShiftParamsItem.kt │ │ ├── MirrorSideSelector.kt │ │ ├── PairItem.kt │ │ ├── PinchParamsItem.kt │ │ ├── QuadItem.kt │ │ ├── RadialTiltShiftParamsItem.kt │ │ ├── RubberStampParamsItem.kt │ │ ├── SideFadeRelativeItem.kt │ │ ├── SmearParamsItem.kt │ │ ├── SparkleParamsItem.kt │ │ ├── ToneCurvesParamsItem.kt │ │ ├── TransferFuncSelector.kt │ │ ├── TripleItem.kt │ │ ├── VoronoiCrystallizeParamsItem.kt │ │ ├── WaterParamsItem.kt │ │ ├── pair_components/ │ │ │ ├── ColorModelPairItem.kt │ │ │ ├── FloatColorModelPairItem.kt │ │ │ ├── FloatFileModelPairItem.kt │ │ │ ├── FloatImageModelPairItem.kt │ │ │ ├── NumberBlurEdgeModePairItem.kt │ │ │ ├── NumberBooleanPairItem.kt │ │ │ ├── NumberMirrorSidePairItem.kt │ │ │ ├── NumberPairItem.kt │ │ │ └── NumberTransferFuncPairItem.kt │ │ ├── quad_components/ │ │ │ ├── NumberColorModelQuadItem.kt │ │ │ └── NumberQuadItem.kt │ │ └── triple_components/ │ │ ├── ColorModelTripleItem.kt │ │ ├── FloatPaletteImageModelTripleItem.kt │ │ ├── NumberColorModelColorModelTripleItem.kt │ │ ├── NumberColorModelPopArtTripleItem.kt │ │ ├── NumberNumberBlurEdgeModeTripleItem.kt │ │ ├── NumberNumberColorModelTripleItem.kt │ │ ├── NumberTransferFuncBlurEdgeModeTripleItem.kt │ │ └── NumberTripleItem.kt │ ├── ksp/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── core/ │ │ │ └── ksp/ │ │ │ ├── annotations/ │ │ │ │ ├── FilterInject.kt │ │ │ │ └── UiFilterInject.kt │ │ │ └── processor/ │ │ │ ├── FilterInjectProcessor.kt │ │ │ └── UiFilterInjectProcessor.kt │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider │ ├── resources/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── debug/ │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ ├── ic_logo_animated.xml │ │ │ │ └── ic_notification_icon.xml │ │ │ ├── drawable-v31/ │ │ │ │ └── ic_logo_animated.xml │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values/ │ │ │ │ ├── colors.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── values-night/ │ │ │ │ └── colors.xml │ │ │ ├── values-night-v31/ │ │ │ │ └── colors.xml │ │ │ └── values-v31/ │ │ │ └── colors.xml │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── core/ │ │ │ └── resources/ │ │ │ ├── emoji/ │ │ │ │ ├── Emoji.kt │ │ │ │ └── EmojiData.kt │ │ │ ├── icons/ │ │ │ │ ├── AddPhotoAlt.kt │ │ │ │ ├── AddSticky.kt │ │ │ │ ├── Analogous.kt │ │ │ │ ├── AnalogousComplementary.kt │ │ │ │ ├── Analytics.kt │ │ │ │ ├── Animation.kt │ │ │ │ ├── Apng.kt │ │ │ │ ├── ApngBox.kt │ │ │ │ ├── AppShortcut.kt │ │ │ │ ├── Apps.kt │ │ │ │ ├── Archive.kt │ │ │ │ ├── AreaChart.kt │ │ │ │ ├── ArtTrack.kt │ │ │ │ ├── Ascii.kt │ │ │ │ ├── AutoDelete.kt │ │ │ │ ├── BackgroundColor.kt │ │ │ │ ├── BarcodeScanner.kt │ │ │ │ ├── Base64.kt │ │ │ │ ├── BatchPrediction.kt │ │ │ │ ├── Beta.kt │ │ │ │ ├── Bitcoin.kt │ │ │ │ ├── Block.kt │ │ │ │ ├── BlurCircular.kt │ │ │ │ ├── BoldLine.kt │ │ │ │ ├── Bolt.kt │ │ │ │ ├── BookmarkOff.kt │ │ │ │ ├── BookmarkRemove.kt │ │ │ │ ├── Boosty.kt │ │ │ │ ├── BorderColor.kt │ │ │ │ ├── BrokenImageAlt.kt │ │ │ │ ├── BrushColor.kt │ │ │ │ ├── BubbleDelete.kt │ │ │ │ ├── Build.kt │ │ │ │ ├── CancelSmall.kt │ │ │ │ ├── ClipboardFile.kt │ │ │ │ ├── Collage.kt │ │ │ │ ├── Communication.kt │ │ │ │ ├── Compare.kt │ │ │ │ ├── CompareArrows.kt │ │ │ │ ├── Complementary.kt │ │ │ │ ├── ContractEdit.kt │ │ │ │ ├── ContractImage.kt │ │ │ │ ├── Cool.kt │ │ │ │ ├── Counter.kt │ │ │ │ ├── Crashlytics.kt │ │ │ │ ├── CropSmall.kt │ │ │ │ ├── Cube.kt │ │ │ │ ├── Curve.kt │ │ │ │ ├── DashedLine.kt │ │ │ │ ├── Database.kt │ │ │ │ ├── Delete.kt │ │ │ │ ├── DeleteSweep.kt │ │ │ │ ├── Deselect.kt │ │ │ │ ├── DesignServices.kt │ │ │ │ ├── DocumentScanner.kt │ │ │ │ ├── DotDashedLine.kt │ │ │ │ ├── Dots.kt │ │ │ │ ├── DownloadFile.kt │ │ │ │ ├── Draw.kt │ │ │ │ ├── EditAlt.kt │ │ │ │ ├── EmojiMultiple.kt │ │ │ │ ├── EmojiSticky.kt │ │ │ │ ├── Encrypted.kt │ │ │ │ ├── Eraser.kt │ │ │ │ ├── Exercise.kt │ │ │ │ ├── Exif.kt │ │ │ │ ├── ExifEdit.kt │ │ │ │ ├── Eyedropper.kt │ │ │ │ ├── FabCorner.kt │ │ │ │ ├── FileExport.kt │ │ │ │ ├── FileImage.kt │ │ │ │ ├── FileImport.kt │ │ │ │ ├── FileReplace.kt │ │ │ │ ├── FindInPage.kt │ │ │ │ ├── FingerprintOff.kt │ │ │ │ ├── Firebase.kt │ │ │ │ ├── Flip.kt │ │ │ │ ├── FlipVertical.kt │ │ │ │ ├── FloatingActionButton.kt │ │ │ │ ├── FloodFill.kt │ │ │ │ ├── FolderCompare.kt │ │ │ │ ├── FolderImage.kt │ │ │ │ ├── FolderImageAlt.kt │ │ │ │ ├── FolderMatch.kt │ │ │ │ ├── FolderOpened.kt │ │ │ │ ├── FontFamily.kt │ │ │ │ ├── FormatPaintVariant.kt │ │ │ │ ├── Forum.kt │ │ │ │ ├── FreeArrow.kt │ │ │ │ ├── FreeDoubleArrow.kt │ │ │ │ ├── FreeDraw.kt │ │ │ │ ├── Github.kt │ │ │ │ ├── Glyphs.kt │ │ │ │ ├── GooglePlay.kt │ │ │ │ ├── Gradient.kt │ │ │ │ ├── Group.kt │ │ │ │ ├── HandshakeAlt.kt │ │ │ │ ├── HardDrive.kt │ │ │ │ ├── HighRes.kt │ │ │ │ ├── Highlighter.kt │ │ │ │ ├── HistoryCreate.kt │ │ │ │ ├── HyperOS.kt │ │ │ │ ├── IOS.kt │ │ │ │ ├── ImageCombine.kt │ │ │ │ ├── ImageConvert.kt │ │ │ │ ├── ImageDownload.kt │ │ │ │ ├── ImageEdit.kt │ │ │ │ ├── ImageEmbedded.kt │ │ │ │ ├── ImageLimit.kt │ │ │ │ ├── ImageOverlay.kt │ │ │ │ ├── ImageReset.kt │ │ │ │ ├── ImageResize.kt │ │ │ │ ├── ImageSaw.kt │ │ │ │ ├── ImageSearch.kt │ │ │ │ ├── ImageSticky.kt │ │ │ │ ├── ImageSync.kt │ │ │ │ ├── ImageText.kt │ │ │ │ ├── ImageToText.kt │ │ │ │ ├── ImageToolboxBroken.kt │ │ │ │ ├── ImageTooltip.kt │ │ │ │ ├── ImageWeight.kt │ │ │ │ ├── ImagesMode.kt │ │ │ │ ├── ImagesearchRoller.kt │ │ │ │ ├── Interface.kt │ │ │ │ ├── Jpg.kt │ │ │ │ ├── Jxl.kt │ │ │ │ ├── KeyVariant.kt │ │ │ │ ├── KeyVertical.kt │ │ │ │ ├── LabelPercent.kt │ │ │ │ ├── Landscape.kt │ │ │ │ ├── Landscape2.kt │ │ │ │ ├── Lasso.kt │ │ │ │ ├── Latitude.kt │ │ │ │ ├── Layers.kt │ │ │ │ ├── LayersSearchOutline.kt │ │ │ │ ├── LetterO.kt │ │ │ │ ├── LetterS.kt │ │ │ │ ├── License.kt │ │ │ │ ├── Line.kt │ │ │ │ ├── LineArrow.kt │ │ │ │ ├── LineDoubleArrow.kt │ │ │ │ ├── Longitude.kt │ │ │ │ ├── Manga.kt │ │ │ │ ├── MaterialDesign.kt │ │ │ │ ├── MeshDownload.kt │ │ │ │ ├── MeshGradient.kt │ │ │ │ ├── MiniEdit.kt │ │ │ │ ├── MiniEditLarge.kt │ │ │ │ ├── Mobile.kt │ │ │ │ ├── MobileArrowDown.kt │ │ │ │ ├── MobileArrowUpRight.kt │ │ │ │ ├── MobileCast.kt │ │ │ │ ├── MobileLandscape.kt │ │ │ │ ├── MobileLayout.kt │ │ │ │ ├── MobileRotateLock.kt │ │ │ │ ├── MobileShare.kt │ │ │ │ ├── MobileVibrate.kt │ │ │ │ ├── Mop.kt │ │ │ │ ├── MultipleImageEdit.kt │ │ │ │ ├── MusicAdd.kt │ │ │ │ ├── NeonBrush.kt │ │ │ │ ├── Neurology.kt │ │ │ │ ├── NextPlan.kt │ │ │ │ ├── Noise.kt │ │ │ │ ├── NoiseAlt.kt │ │ │ │ ├── Numeric.kt │ │ │ │ ├── OverlayAbove.kt │ │ │ │ ├── PaletteBox.kt │ │ │ │ ├── PaletteSwatch.kt │ │ │ │ ├── Panorama.kt │ │ │ │ ├── Pdf.kt │ │ │ │ ├── Pen.kt │ │ │ │ ├── Perspective.kt │ │ │ │ ├── PhotoPicker.kt │ │ │ │ ├── PhotoPickerMobile.kt │ │ │ │ ├── PhotoPrints.kt │ │ │ │ ├── PhotoSizeSelectSmall.kt │ │ │ │ ├── PictureInPictureCenter.kt │ │ │ │ ├── Png.kt │ │ │ │ ├── Polygon.kt │ │ │ │ ├── Prefix.kt │ │ │ │ ├── Preview.kt │ │ │ │ ├── Print.kt │ │ │ │ ├── Psychology.kt │ │ │ │ ├── Puzzle.kt │ │ │ │ ├── QrCode.kt │ │ │ │ ├── QualityHigh.kt │ │ │ │ ├── QualityLow.kt │ │ │ │ ├── QualityMedium.kt │ │ │ │ ├── Rabbit.kt │ │ │ │ ├── Resize.kt │ │ │ │ ├── ResponsiveLayout.kt │ │ │ │ ├── Robot.kt │ │ │ │ ├── RobotExcited.kt │ │ │ │ ├── Rotate90Cw.kt │ │ │ │ ├── Routine.kt │ │ │ │ ├── SamsungLetter.kt │ │ │ │ ├── SaveConfirm.kt │ │ │ │ ├── ScaleUnbalanced.kt │ │ │ │ ├── Scanner.kt │ │ │ │ ├── Scissors.kt │ │ │ │ ├── ScissorsSmall.kt │ │ │ │ ├── SelectAll.kt │ │ │ │ ├── SelectInverse.kt │ │ │ │ ├── ServiceToolbox.kt │ │ │ │ ├── SettingsTimelapse.kt │ │ │ │ ├── Shadow.kt │ │ │ │ ├── ShareOff.kt │ │ │ │ ├── ShieldKey.kt │ │ │ │ ├── ShieldLock.kt │ │ │ │ ├── ShieldOpen.kt │ │ │ │ ├── ShineDiamond.kt │ │ │ │ ├── Signature.kt │ │ │ │ ├── SkewMore.kt │ │ │ │ ├── Slider.kt │ │ │ │ ├── Snail.kt │ │ │ │ ├── Snowflake.kt │ │ │ │ ├── Speed.kt │ │ │ │ ├── SplitAlt.kt │ │ │ │ ├── SplitComplementary.kt │ │ │ │ ├── Spray.kt │ │ │ │ ├── Square.kt │ │ │ │ ├── SquareEdit.kt │ │ │ │ ├── SquareFoot.kt │ │ │ │ ├── SquareHarmony.kt │ │ │ │ ├── Stack.kt │ │ │ │ ├── StackSticky.kt │ │ │ │ ├── StackStickyOff.kt │ │ │ │ ├── Stacks.kt │ │ │ │ ├── StampedLine.kt │ │ │ │ ├── StarSticky.kt │ │ │ │ ├── StickerEmoji.kt │ │ │ │ ├── Stylus.kt │ │ │ │ ├── Suffix.kt │ │ │ │ ├── Svg.kt │ │ │ │ ├── SwapVerticalCircle.kt │ │ │ │ ├── Swatch.kt │ │ │ │ ├── Symbol.kt │ │ │ │ ├── SyncArrowDown.kt │ │ │ │ ├── TableEye.kt │ │ │ │ ├── TagText.kt │ │ │ │ ├── Telegram.kt │ │ │ │ ├── TelevisionAmbientLight.kt │ │ │ │ ├── Tetradic.kt │ │ │ │ ├── TextFields.kt │ │ │ │ ├── TextSearch.kt │ │ │ │ ├── TextSticky.kt │ │ │ │ ├── Theme.kt │ │ │ │ ├── TimerEdit.kt │ │ │ │ ├── Titlecase.kt │ │ │ │ ├── Ton.kt │ │ │ │ ├── Tonality.kt │ │ │ │ ├── Toolbox.kt │ │ │ │ ├── TopLeft.kt │ │ │ │ ├── Tortoise.kt │ │ │ │ ├── Transparency.kt │ │ │ │ ├── Triadic.kt │ │ │ │ ├── Triangle.kt │ │ │ │ ├── USDT.kt │ │ │ │ ├── Unarchive.kt │ │ │ │ ├── Ungroup.kt │ │ │ │ ├── VectorPolyline.kt │ │ │ │ ├── VolunteerActivism.kt │ │ │ │ ├── WallpaperAlt.kt │ │ │ │ ├── WandShine.kt │ │ │ │ ├── WandStars.kt │ │ │ │ ├── Watermark.kt │ │ │ │ ├── Webp.kt │ │ │ │ ├── WebpBox.kt │ │ │ │ ├── Windows.kt │ │ │ │ └── ZigzagLine.kt │ │ │ └── shapes/ │ │ │ ├── ArrowShape.kt │ │ │ ├── BookmarkShape.kt │ │ │ ├── BurgerShape.kt │ │ │ ├── CloverShape.kt │ │ │ ├── DropletShape.kt │ │ │ ├── EggShape.kt │ │ │ ├── ExplosionShape.kt │ │ │ ├── HeartShape.kt │ │ │ ├── KotlinShape.kt │ │ │ ├── MapShape.kt │ │ │ ├── MaterialStarShape.kt │ │ │ ├── MorphShape.kt │ │ │ ├── OctagonShape.kt │ │ │ ├── OvalShape.kt │ │ │ ├── PathShape.kt │ │ │ ├── PentagonShape.kt │ │ │ ├── PillShape.kt │ │ │ ├── ShieldShape.kt │ │ │ ├── ShurikenShape.kt │ │ │ ├── SimpleHeartShape.kt │ │ │ ├── SmallMaterialStarShape.kt │ │ │ └── SquircleShape.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── app_registration_24px.xml │ │ │ ├── ic_24_barcode_scanner.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── ic_launcher_monochrome_24.xml │ │ │ ├── ic_logo_animated.xml │ │ │ ├── ic_notification_icon.xml │ │ │ ├── image_to_text_outlined.xml │ │ │ ├── mobile_screenshot.xml │ │ │ ├── multiple_image_edit.xml │ │ │ ├── outline_colorize_24.xml │ │ │ ├── outline_drag_handle_24.xml │ │ │ ├── palette_swatch_outlined.xml │ │ │ ├── rounded_document_scanner_24.xml │ │ │ ├── rounded_qr_code_scanner_24.xml │ │ │ └── shape_find_in_file.xml │ │ ├── drawable-v31/ │ │ │ └── ic_logo_animated.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── raw/ │ │ │ └── keep.xml │ │ ├── values/ │ │ │ ├── arrays.xml │ │ │ ├── bools.xml │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ ├── values-ar/ │ │ │ └── strings.xml │ │ ├── values-be/ │ │ │ └── strings.xml │ │ ├── values-bn/ │ │ │ └── strings.xml │ │ ├── values-ca/ │ │ │ └── strings.xml │ │ ├── values-cs/ │ │ │ └── strings.xml │ │ ├── values-da/ │ │ │ └── strings.xml │ │ ├── values-de/ │ │ │ └── strings.xml │ │ ├── values-es/ │ │ │ └── strings.xml │ │ ├── values-et/ │ │ │ └── strings.xml │ │ ├── values-eu/ │ │ │ └── strings.xml │ │ ├── values-fa/ │ │ │ └── strings.xml │ │ ├── values-fi/ │ │ │ └── strings.xml │ │ ├── values-fil/ │ │ │ └── strings.xml │ │ ├── values-fr/ │ │ │ └── strings.xml │ │ ├── values-hi/ │ │ │ └── strings.xml │ │ ├── values-hu/ │ │ │ └── strings.xml │ │ ├── values-in/ │ │ │ └── strings.xml │ │ ├── values-it/ │ │ │ └── strings.xml │ │ ├── values-iw/ │ │ │ └── strings.xml │ │ ├── values-ja/ │ │ │ └── strings.xml │ │ ├── values-kk/ │ │ │ └── strings.xml │ │ ├── values-ko/ │ │ │ └── strings.xml │ │ ├── values-lt/ │ │ │ └── strings.xml │ │ ├── values-mr/ │ │ │ └── strings.xml │ │ ├── values-night/ │ │ │ └── colors.xml │ │ ├── values-night-v31/ │ │ │ └── colors.xml │ │ ├── values-nl/ │ │ │ └── strings.xml │ │ ├── values-pa/ │ │ │ └── strings.xml │ │ ├── values-pl/ │ │ │ └── strings.xml │ │ ├── values-pt-rBR/ │ │ │ └── strings.xml │ │ ├── values-ro/ │ │ │ └── strings.xml │ │ ├── values-ru/ │ │ │ └── strings.xml │ │ ├── values-si/ │ │ │ └── strings.xml │ │ ├── values-sk/ │ │ │ └── strings.xml │ │ ├── values-sr/ │ │ │ └── strings.xml │ │ ├── values-sv/ │ │ │ └── strings.xml │ │ ├── values-ta/ │ │ │ └── strings.xml │ │ ├── values-te/ │ │ │ └── strings.xml │ │ ├── values-th/ │ │ │ └── strings.xml │ │ ├── values-tr/ │ │ │ └── strings.xml │ │ ├── values-ug/ │ │ │ └── strings.xml │ │ ├── values-uk/ │ │ │ └── strings.xml │ │ ├── values-v26/ │ │ │ └── bools.xml │ │ ├── values-v31/ │ │ │ └── colors.xml │ │ ├── values-vi/ │ │ │ └── strings.xml │ │ ├── values-zh-rCN/ │ │ │ └── strings.xml │ │ ├── values-zh-rTW/ │ │ │ └── strings.xml │ │ └── xml/ │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── file_paths.xml │ ├── settings/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── core/ │ │ └── settings/ │ │ ├── di/ │ │ │ └── SettingsStateEntryPoint.kt │ │ ├── domain/ │ │ │ ├── SettingsInteractor.kt │ │ │ ├── SettingsManager.kt │ │ │ ├── SettingsProvider.kt │ │ │ ├── SimpleSettingsInteractor.kt │ │ │ └── model/ │ │ │ ├── ColorHarmonizer.kt │ │ │ ├── CopyToClipboardMode.kt │ │ │ ├── DomainFontFamily.kt │ │ │ ├── FastSettingsSide.kt │ │ │ ├── FilenameBehavior.kt │ │ │ ├── FlingType.kt │ │ │ ├── NightMode.kt │ │ │ ├── OneTimeSaveLocation.kt │ │ │ ├── SettingsState.kt │ │ │ ├── ShapeType.kt │ │ │ ├── SliderType.kt │ │ │ ├── SnowfallMode.kt │ │ │ └── SwitchType.kt │ │ └── presentation/ │ │ ├── model/ │ │ │ ├── EditPresetsController.kt │ │ │ ├── IconShape.kt │ │ │ ├── PicturePickerMode.kt │ │ │ ├── Setting.kt │ │ │ ├── SettingsGroup.kt │ │ │ ├── UiFontFamily.kt │ │ │ └── UiSettingsState.kt │ │ ├── provider/ │ │ │ └── LocalSettingsState.kt │ │ └── utils/ │ │ └── RoundedPolygonUtils.kt │ ├── ui/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── foss/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── core/ │ │ │ └── ui/ │ │ │ ├── utils/ │ │ │ │ ├── content_pickers/ │ │ │ │ │ └── DocumentScannerImpl.kt │ │ │ │ └── helper/ │ │ │ │ └── ReviewHandlerImpl.kt │ │ │ └── widget/ │ │ │ └── sheets/ │ │ │ └── UpdateSheetImpl.kt │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── core/ │ │ │ └── ui/ │ │ │ ├── theme/ │ │ │ │ ├── Color.kt │ │ │ │ ├── Motion.kt │ │ │ │ ├── Theme.kt │ │ │ │ ├── ThemePreview.kt │ │ │ │ └── Type.kt │ │ │ ├── transformation/ │ │ │ │ └── ImageInfoTransformation.kt │ │ │ ├── utils/ │ │ │ │ ├── BaseComponent.kt │ │ │ │ ├── ComposeActivity.kt │ │ │ │ ├── ComposeApplication.kt │ │ │ │ ├── animation/ │ │ │ │ │ ├── Animate.kt │ │ │ │ │ ├── Animations.kt │ │ │ │ │ ├── CombinedMutableInteractionSource.kt │ │ │ │ │ └── Easing.kt │ │ │ │ ├── capturable/ │ │ │ │ │ ├── Capturable.kt │ │ │ │ │ ├── CaptureController.kt │ │ │ │ │ └── impl/ │ │ │ │ │ ├── CapturableNew.kt │ │ │ │ │ └── CapturableOld.kt │ │ │ │ ├── confetti/ │ │ │ │ │ ├── ConfettiHostState.kt │ │ │ │ │ └── Particles.kt │ │ │ │ ├── content_pickers/ │ │ │ │ │ ├── BarcodeScanner.kt │ │ │ │ │ ├── ContactPicker.kt │ │ │ │ │ ├── DocumentScanner.kt │ │ │ │ │ ├── FileMaker.kt │ │ │ │ │ ├── FilePicker.kt │ │ │ │ │ ├── FolderPicker.kt │ │ │ │ │ ├── ImagePicker.kt │ │ │ │ │ ├── ResultLauncher.kt │ │ │ │ │ └── Scanner.kt │ │ │ │ ├── helper/ │ │ │ │ │ ├── ActivityUtils.kt │ │ │ │ │ ├── AppToastHost.kt │ │ │ │ │ ├── BlendingModeExt.kt │ │ │ │ │ ├── Clipboard.kt │ │ │ │ │ ├── ClipboardUtils.kt │ │ │ │ │ ├── CoilUtils.kt │ │ │ │ │ ├── ColorUtils.kt │ │ │ │ │ ├── CompositionLocalUtils.kt │ │ │ │ │ ├── ContextUtils.kt │ │ │ │ │ ├── DensityUtils.kt │ │ │ │ │ ├── DeviceInfo.kt │ │ │ │ │ ├── DrawUtils.kt │ │ │ │ │ ├── HandleDeeplinks.kt │ │ │ │ │ ├── HandlerUtils.kt │ │ │ │ │ ├── ImageUtils.kt │ │ │ │ │ ├── IntentUtils.kt │ │ │ │ │ ├── LazyUtils.kt │ │ │ │ │ ├── LinkUtils.kt │ │ │ │ │ ├── LocalFilterPreviewModel.kt │ │ │ │ │ ├── LocaleConfigCompat.kt │ │ │ │ │ ├── PaddingUtils.kt │ │ │ │ │ ├── PredictiveBackObserver.kt │ │ │ │ │ ├── Preview.kt │ │ │ │ │ ├── Rect.kt │ │ │ │ │ ├── ReviewHandler.kt │ │ │ │ │ ├── Ripple.kt │ │ │ │ │ ├── SafeUriHandler.kt │ │ │ │ │ ├── SaveResultHandler.kt │ │ │ │ │ ├── ScanResult.kt │ │ │ │ │ └── image_vector/ │ │ │ │ │ ├── DrawCache.kt │ │ │ │ │ ├── GroupComponent.kt │ │ │ │ │ ├── ImageVectorUtils.kt │ │ │ │ │ ├── PathComponent.kt │ │ │ │ │ ├── VNode.kt │ │ │ │ │ ├── VectorComponent.kt │ │ │ │ │ └── VectorPainter.kt │ │ │ │ ├── navigation/ │ │ │ │ │ ├── Decompose.kt │ │ │ │ │ ├── Screen.kt │ │ │ │ │ └── ScreenUtils.kt │ │ │ │ ├── painter/ │ │ │ │ │ ├── CenterCropPainter.kt │ │ │ │ │ └── RoundCornersPainter.kt │ │ │ │ ├── permission/ │ │ │ │ │ ├── PermissionResult.kt │ │ │ │ │ ├── PermissionStatus.kt │ │ │ │ │ └── PermissionUtils.kt │ │ │ │ ├── provider/ │ │ │ │ │ ├── ImageToolboxCompositionLocals.kt │ │ │ │ │ ├── LocalComponentActivity.kt │ │ │ │ │ ├── LocalContainerShape.kt │ │ │ │ │ ├── LocalKeepAliveService.kt │ │ │ │ │ ├── LocalMetadataProvider.kt │ │ │ │ │ ├── LocalResourceManager.kt │ │ │ │ │ ├── LocalScreenSize.kt │ │ │ │ │ └── LocalWindowSizeClass.kt │ │ │ │ └── state/ │ │ │ │ ├── ObjectSaverDelegate.kt │ │ │ │ └── Update.kt │ │ │ └── widget/ │ │ │ ├── AdaptiveBottomScaffoldLayoutScreen.kt │ │ │ ├── AdaptiveLayoutScreen.kt │ │ │ ├── buttons/ │ │ │ │ ├── BottomButtonsBlock.kt │ │ │ │ ├── CompareButton.kt │ │ │ │ ├── EraseModeButton.kt │ │ │ │ ├── MediaCheckBox.kt │ │ │ │ ├── PagerScrollPanel.kt │ │ │ │ ├── PanModeButton.kt │ │ │ │ ├── ShareButton.kt │ │ │ │ ├── ShowOriginalButton.kt │ │ │ │ ├── SupportingButton.kt │ │ │ │ └── ZoomButton.kt │ │ │ ├── color_picker/ │ │ │ │ ├── AvailableColorTuplesSheet.kt │ │ │ │ ├── ColorInfo.kt │ │ │ │ ├── ColorPicker.kt │ │ │ │ ├── ColorPickerSheet.kt │ │ │ │ ├── ColorSelection.kt │ │ │ │ ├── ColorSelectionRow.kt │ │ │ │ ├── ColorTupleDefaults.kt │ │ │ │ ├── ColorTuplePicker.kt │ │ │ │ ├── ColorTuplePreview.kt │ │ │ │ └── RecentAndFavoriteColorsCard.kt │ │ │ ├── controls/ │ │ │ │ ├── FileReorderVerticalList.kt │ │ │ │ ├── FormatExifWarning.kt │ │ │ │ ├── IcoSizeWarning.kt │ │ │ │ ├── ImageReorderCarousel.kt │ │ │ │ ├── ImageTransformBar.kt │ │ │ │ ├── OOMWarning.kt │ │ │ │ ├── ResizeImageField.kt │ │ │ │ ├── SaveExifWidget.kt │ │ │ │ ├── ScaleSmallImagesToLargeToggle.kt │ │ │ │ ├── SortButton.kt │ │ │ │ ├── page/ │ │ │ │ │ ├── PageInputDialog.kt │ │ │ │ │ ├── PageInputField.kt │ │ │ │ │ ├── PageSelectionItem.kt │ │ │ │ │ └── PagesSelectionParser.kt │ │ │ │ ├── resize_group/ │ │ │ │ │ ├── ResizeTypeSelector.kt │ │ │ │ │ └── components/ │ │ │ │ │ ├── BlurRadiusSelector.kt │ │ │ │ │ └── UseBlurredBackgroundToggle.kt │ │ │ │ └── selection/ │ │ │ │ ├── AlphaSelector.kt │ │ │ │ ├── BlendingModeSelector.kt │ │ │ │ ├── ColorRowSelector.kt │ │ │ │ ├── DataSelector.kt │ │ │ │ ├── FontSelector.kt │ │ │ │ ├── HelperGridParamsSelector.kt │ │ │ │ ├── ImageFormatSelector.kt │ │ │ │ ├── ImageSelector.kt │ │ │ │ ├── MagnifierEnabledSelector.kt │ │ │ │ ├── PositionSelector.kt │ │ │ │ ├── PresetSelector.kt │ │ │ │ ├── QualitySelector.kt │ │ │ │ └── ScaleModeSelector.kt │ │ │ ├── dialogs/ │ │ │ │ ├── CalculatorDialog.kt │ │ │ │ ├── ExitWithoutSavingDialog.kt │ │ │ │ ├── LoadingDialog.kt │ │ │ │ ├── OneTimeImagePickingDialog.kt │ │ │ │ ├── OneTimeSaveLocationSelectionDialog.kt │ │ │ │ ├── PasswordRequestDialog.kt │ │ │ │ ├── ResetDialog.kt │ │ │ │ └── WantCancelLoadingDialog.kt │ │ │ ├── enhanced/ │ │ │ │ ├── EnhancedAlertDialog.kt │ │ │ │ ├── EnhancedBadge.kt │ │ │ │ ├── EnhancedButton.kt │ │ │ │ ├── EnhancedButtonGroup.kt │ │ │ │ ├── EnhancedCheckbox.kt │ │ │ │ ├── EnhancedChip.kt │ │ │ │ ├── EnhancedCircularProgressIndicator.kt │ │ │ │ ├── EnhancedDatePickerDialog.kt │ │ │ │ ├── EnhancedDropdownMenu.kt │ │ │ │ ├── EnhancedFlingBehavior.kt │ │ │ │ ├── EnhancedFloatingActionButton.kt │ │ │ │ ├── EnhancedHapticFeedback.kt │ │ │ │ ├── EnhancedIconButton.kt │ │ │ │ ├── EnhancedLoadingIndicator.kt │ │ │ │ ├── EnhancedModalBottomSheet.kt │ │ │ │ ├── EnhancedModalSheetDragHandle.kt │ │ │ │ ├── EnhancedNavigationBarItem.kt │ │ │ │ ├── EnhancedNavigationRailItem.kt │ │ │ │ ├── EnhancedRadioButton.kt │ │ │ │ ├── EnhancedRangeSliderItem.kt │ │ │ │ ├── EnhancedSlider.kt │ │ │ │ ├── EnhancedSliderItem.kt │ │ │ │ ├── EnhancedSwitch.kt │ │ │ │ ├── EnhancedToggleButton.kt │ │ │ │ ├── EnhancedTopAppBar.kt │ │ │ │ └── derivative/ │ │ │ │ └── OnlyAllowedSliderItem.kt │ │ │ ├── icon_shape/ │ │ │ │ └── IconShapeContainer.kt │ │ │ ├── image/ │ │ │ │ ├── AspectRatioSelector.kt │ │ │ │ ├── AspectRatios.kt │ │ │ │ ├── AutoFilePicker.kt │ │ │ │ ├── BadImageWidget.kt │ │ │ │ ├── HistogramChart.kt │ │ │ │ ├── ImageContainer.kt │ │ │ │ ├── ImageCounter.kt │ │ │ │ ├── ImageHeaderState.kt │ │ │ │ ├── ImageNotPickedWidget.kt │ │ │ │ ├── ImagePager.kt │ │ │ │ ├── ImagePreviewGrid.kt │ │ │ │ ├── ImageStickyHeader.kt │ │ │ │ ├── ImagesPreviewWithSelection.kt │ │ │ │ ├── MetadataPreviewButton.kt │ │ │ │ ├── Picture.kt │ │ │ │ ├── SimplePicture.kt │ │ │ │ ├── UrisCarousel.kt │ │ │ │ └── UrisPreview.kt │ │ │ ├── modifier/ │ │ │ │ ├── AdvancedShadow.kt │ │ │ │ ├── AlertDialogBorder.kt │ │ │ │ ├── AnimateContentSizeNoClip.kt │ │ │ │ ├── AutoCornersShape.kt │ │ │ │ ├── AutoElevatedBorder.kt │ │ │ │ ├── Blink.kt │ │ │ │ ├── Container.kt │ │ │ │ ├── ContiniousRoundedRectangle.kt │ │ │ │ ├── DetectSwipe.kt │ │ │ │ ├── DragHandler.kt │ │ │ │ ├── DrawHorizontalStroke.kt │ │ │ │ ├── FadingEdges.kt │ │ │ │ ├── HelperGrid.kt │ │ │ │ ├── LayoutCorners.kt │ │ │ │ ├── Line.kt │ │ │ │ ├── MaterialShadow.kt │ │ │ │ ├── MeshGradient.kt │ │ │ │ ├── NegativePadding.kt │ │ │ │ ├── ObservePointersCount.kt │ │ │ │ ├── Placholder.kt │ │ │ │ ├── PointerInput.kt │ │ │ │ ├── Pulsate.kt │ │ │ │ ├── RealisticSnowfall.kt │ │ │ │ ├── RotateAnimation.kt │ │ │ │ ├── ScaleOnTap.kt │ │ │ │ ├── ShapeDefaults.kt │ │ │ │ ├── Shimmer.kt │ │ │ │ ├── Tappable.kt │ │ │ │ ├── TransparencyChecker.kt │ │ │ │ └── WithModifier.kt │ │ │ ├── other/ │ │ │ │ ├── AnimatedBorder.kt │ │ │ │ ├── BoxAnimatedVisibility.kt │ │ │ │ ├── ColorWithNameItem.kt │ │ │ │ ├── DrawLockScreenOrientation.kt │ │ │ │ ├── EmojiItem.kt │ │ │ │ ├── ExpandableItem.kt │ │ │ │ ├── FeatureNotAvailableContent.kt │ │ │ │ ├── FontSelectionItem.kt │ │ │ │ ├── GradientEdge.kt │ │ │ │ ├── InfoContainer.kt │ │ │ │ ├── LinkPreviewCard.kt │ │ │ │ ├── LinkPreviewList.kt │ │ │ │ ├── QrPainter.kt │ │ │ │ ├── SearchBar.kt │ │ │ │ ├── SwipeToReveal.kt │ │ │ │ ├── ToastHost.kt │ │ │ │ └── TopAppBarEmoji.kt │ │ │ ├── palette_selection/ │ │ │ │ ├── PaletteMappings.kt │ │ │ │ ├── PaletteStyleSelection.kt │ │ │ │ └── PaletteStyleSelectionItem.kt │ │ │ ├── preferences/ │ │ │ │ ├── PreferenceItem.kt │ │ │ │ ├── PreferenceItemOverload.kt │ │ │ │ ├── PreferenceRow.kt │ │ │ │ ├── PreferenceRowSwitch.kt │ │ │ │ └── ScreenPreference.kt │ │ │ ├── saver/ │ │ │ │ ├── OneTimeEffect.kt │ │ │ │ └── Savers.kt │ │ │ ├── sheets/ │ │ │ │ ├── AddExifSheet.kt │ │ │ │ ├── DefaultUpdateSheet.kt │ │ │ │ ├── EditExifSheet.kt │ │ │ │ ├── EmojiSelectionSheet.kt │ │ │ │ ├── PickImageFromUrisSheet.kt │ │ │ │ ├── ProcessImagesPreferenceSheet.kt │ │ │ │ ├── UpdateSheet.kt │ │ │ │ └── ZoomModalSheet.kt │ │ │ ├── sliders/ │ │ │ │ ├── FancySlider.kt │ │ │ │ ├── HyperOSSlider.kt │ │ │ │ ├── M2Slider.kt │ │ │ │ ├── M3Slider.kt │ │ │ │ └── custom_slider/ │ │ │ │ ├── CustomRangeSlider.kt │ │ │ │ ├── CustomRangeSliderState.kt │ │ │ │ ├── CustomRangeSliderUtils.kt │ │ │ │ ├── CustomSlider.kt │ │ │ │ ├── CustomSliderColors.kt │ │ │ │ ├── CustomSliderDefaults.kt │ │ │ │ ├── CustomSliderRange.kt │ │ │ │ ├── CustomSliderState.kt │ │ │ │ └── CustomSliderUtils.kt │ │ │ ├── switches/ │ │ │ │ ├── CupertinoSwitch.kt │ │ │ │ ├── FluentSwitch.kt │ │ │ │ ├── HyperOSSwitch.kt │ │ │ │ ├── LiquidToggle.kt │ │ │ │ ├── M3Switch.kt │ │ │ │ ├── OneUISwitch.kt │ │ │ │ └── PixelSwitch.kt │ │ │ ├── text/ │ │ │ │ ├── AutoSizeText.kt │ │ │ │ ├── HtmlText.kt │ │ │ │ ├── IsKeyboardVisibleAsState.kt │ │ │ │ ├── Linkify.kt │ │ │ │ ├── Marquee.kt │ │ │ │ ├── OutlinedText.kt │ │ │ │ ├── PatternHighlightTransformation.kt │ │ │ │ ├── RoundedTextField.kt │ │ │ │ ├── TitleItem.kt │ │ │ │ └── TopAppBarTitle.kt │ │ │ ├── utils/ │ │ │ │ ├── AutoContentBasedColors.kt │ │ │ │ ├── AvailableHeight.kt │ │ │ │ ├── RememberRetainedLazyListState.kt │ │ │ │ └── ScreenList.kt │ │ │ └── value/ │ │ │ ├── ValueDialog.kt │ │ │ └── ValueText.kt │ │ └── market/ │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── core/ │ │ └── ui/ │ │ ├── utils/ │ │ │ ├── content_pickers/ │ │ │ │ └── DocumentScannerImpl.kt │ │ │ └── helper/ │ │ │ └── ReviewHandlerImpl.kt │ │ └── widget/ │ │ └── sheets/ │ │ └── UpdateSheetImpl.kt │ └── utils/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── t8rin/ │ └── imagetoolbox/ │ └── core/ │ └── utils/ │ ├── AppContext.kt │ ├── QrType.kt │ ├── Typeface.kt │ ├── Update.kt │ ├── UriUtils.kt │ └── Zip.kt ├── fastlane/ │ └── metadata/ │ └── android/ │ ├── en-US/ │ │ ├── changelogs/ │ │ │ ├── 13.txt │ │ │ └── 16.txt │ │ ├── full_description.txt │ │ └── short_description.txt │ └── ru/ │ ├── full_description.txt │ └── short_description.txt ├── feature/ │ ├── ai-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── ai_tools/ │ │ ├── data/ │ │ │ ├── AiProcessor.kt │ │ │ ├── AndroidAiToolsRepository.kt │ │ │ └── model/ │ │ │ ├── ChunkInfo.kt │ │ │ └── ModelInfo.kt │ │ ├── di/ │ │ │ └── AiToolsModule.kt │ │ ├── domain/ │ │ │ ├── AiProgressListener.kt │ │ │ ├── AiToolsRepository.kt │ │ │ └── model/ │ │ │ ├── NeuralConstants.kt │ │ │ ├── NeuralModel.kt │ │ │ └── NeuralParams.kt │ │ └── presentation/ │ │ ├── AiToolsContent.kt │ │ ├── components/ │ │ │ ├── AiToolsControls.kt │ │ │ ├── DeleteModelDialog.kt │ │ │ ├── FilteredModels.kt │ │ │ ├── NeuralModelFilterSheet.kt │ │ │ ├── NeuralModelSelectionSheet.kt │ │ │ ├── NeuralModelSelector.kt │ │ │ ├── NeuralModelTypeResources.kt │ │ │ ├── NeuralModelsColumn.kt │ │ │ ├── NeuralSaveProgress.kt │ │ │ ├── NeuralSaveProgressDialog.kt │ │ │ └── Savers.kt │ │ └── screenLogic/ │ │ └── AiToolsComponent.kt │ ├── apng-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── apng_tools/ │ │ ├── data/ │ │ │ └── AndroidApngConverter.kt │ │ ├── di/ │ │ │ └── ApngToolsModule.kt │ │ ├── domain/ │ │ │ ├── ApngConverter.kt │ │ │ └── ApngParams.kt │ │ └── presentation/ │ │ ├── ApngToolsContent.kt │ │ ├── components/ │ │ │ └── ApngParamsSelector.kt │ │ └── screenLogic/ │ │ └── ApngToolsComponent.kt │ ├── ascii-art/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── ascii_art/ │ │ ├── data/ │ │ │ └── AndroidAsciiConverter.kt │ │ ├── di/ │ │ │ └── AsciiArtModule.kt │ │ ├── domain/ │ │ │ └── AsciiConverter.kt │ │ └── presentation/ │ │ ├── AsciiArtContent.kt │ │ ├── components/ │ │ │ └── AsciiArtControls.kt │ │ └── screenLogic/ │ │ └── AsciiArtComponent.kt │ ├── audio-cover-extractor/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── audio_cover_extractor/ │ │ ├── data/ │ │ │ └── AndroidAudioCoverRetriever.kt │ │ ├── di/ │ │ │ └── AudioCoverExtractorModule.kt │ │ ├── domain/ │ │ │ ├── AudioCoverRetriever.kt │ │ │ └── model/ │ │ │ └── AudioCoverResult.kt │ │ └── ui/ │ │ ├── AudioCoverExtractorContent.kt │ │ ├── components/ │ │ │ └── AudioWithCover.kt │ │ └── screenLogic/ │ │ └── AudioCoverExtractorComponent.kt │ ├── base64-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── base64_tools/ │ │ ├── data/ │ │ │ └── AndroidBase64Converter.kt │ │ ├── di/ │ │ │ └── Base64ToolsModule.kt │ │ ├── domain/ │ │ │ └── Base64Converter.kt │ │ └── presentation/ │ │ ├── Base64ToolsContent.kt │ │ ├── components/ │ │ │ └── Base64ToolsTiles.kt │ │ └── screenLogic/ │ │ └── Base64ToolsComponent.kt │ ├── checksum-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── checksum_tools/ │ │ ├── data/ │ │ │ └── AndroidChecksumManager.kt │ │ ├── di/ │ │ │ └── ChecksumToolsModule.kt │ │ ├── domain/ │ │ │ ├── ChecksumManager.kt │ │ │ └── ChecksumSource.kt │ │ └── presentation/ │ │ ├── ChecksumToolsContent.kt │ │ ├── components/ │ │ │ ├── ChecksumEnterField.kt │ │ │ ├── ChecksumPage.kt │ │ │ ├── ChecksumPreviewField.kt │ │ │ ├── ChecksumResultCard.kt │ │ │ ├── ChecksumToolsTabs.kt │ │ │ ├── UriWithHashItem.kt │ │ │ └── pages/ │ │ │ ├── CalculateFromTextPage.kt │ │ │ ├── CalculateFromUriPage.kt │ │ │ ├── CompareWithUriPage.kt │ │ │ └── CompareWithUrisPage.kt │ │ └── screenLogic/ │ │ └── ChecksumToolsComponent.kt │ ├── cipher/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── cipher/ │ │ ├── data/ │ │ │ ├── AndroidCryptographyManager.kt │ │ │ └── AndroidRandomStringGenerator.kt │ │ ├── di/ │ │ │ └── CipherModule.kt │ │ ├── domain/ │ │ │ ├── CryptographyManager.kt │ │ │ └── WrongKeyException.kt │ │ └── presentation/ │ │ ├── CipherContent.kt │ │ ├── components/ │ │ │ ├── CipherControls.kt │ │ │ └── CipherTipSheet.kt │ │ └── screenLogic/ │ │ └── CipherComponent.kt │ ├── collage-maker/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── collage_maker/ │ │ └── presentation/ │ │ ├── CollageMakerContent.kt │ │ ├── components/ │ │ │ └── CollageParams.kt │ │ └── screenLogic/ │ │ └── CollageMakerComponent.kt │ ├── color-library/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── color_library/ │ │ └── presentation/ │ │ ├── ColorLibraryContent.kt │ │ ├── components/ │ │ │ └── FavoriteColors.kt │ │ └── screenLogic/ │ │ └── ColorLibraryComponent.kt │ ├── color-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── color_tools/ │ │ └── presentation/ │ │ ├── ColorToolsContent.kt │ │ ├── components/ │ │ │ ├── ColorHarmonies.kt │ │ │ ├── ColorHarmoniesUtils.kt │ │ │ ├── ColorHistogram.kt │ │ │ ├── ColorInfo.kt │ │ │ ├── ColorInfoDisplay.kt │ │ │ ├── ColorMixing.kt │ │ │ ├── ColorMixingUtils.kt │ │ │ └── ColorShading.kt │ │ └── screenLogic/ │ │ └── ColorToolsComponent.kt │ ├── compare/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── compare/ │ │ └── presentation/ │ │ ├── CompareContent.kt │ │ ├── components/ │ │ │ ├── CompareLabel.kt │ │ │ ├── CompareScreenContent.kt │ │ │ ├── CompareScreenContentImpl.kt │ │ │ ├── CompareScreenTopAppBar.kt │ │ │ ├── CompareSelectionButtons.kt │ │ │ ├── CompareShareSheet.kt │ │ │ ├── CompareSheet.kt │ │ │ ├── CompareType.kt │ │ │ ├── PixelByPixelCompareState.kt │ │ │ ├── beforeafter/ │ │ │ │ ├── BeforeAfterLayout.kt │ │ │ │ ├── BeforeAfterLayoutImpl.kt │ │ │ │ ├── ContentOrder.kt │ │ │ │ ├── DefaultOverlay.kt │ │ │ │ ├── DimensionSubcomposeLayout.kt │ │ │ │ ├── DimensionUtil.kt │ │ │ │ ├── Label.kt │ │ │ │ └── SlotsEnum.kt │ │ │ └── model/ │ │ │ └── CompareData.kt │ │ └── screenLogic/ │ │ └── CompareComponent.kt │ ├── crop/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── feature/ │ │ │ └── crop/ │ │ │ └── presentation/ │ │ │ ├── CropContent.kt │ │ │ ├── components/ │ │ │ │ ├── CoercePointsToImageBoundsToggle.kt │ │ │ │ ├── CropMaskSelection.kt │ │ │ │ ├── CropRotationSelector.kt │ │ │ │ ├── CropType.kt │ │ │ │ ├── Cropper.kt │ │ │ │ ├── DefaultOutlineProperties.kt │ │ │ │ └── FreeCornersCropToggle.kt │ │ │ └── screenLogic/ │ │ │ └── CropComponent.kt │ │ └── res/ │ │ └── values/ │ │ └── dimens.xml │ ├── delete-exif/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── delete_exif/ │ │ └── presentation/ │ │ ├── DeleteExifContent.kt │ │ └── screenLogic/ │ │ └── DeleteExifComponent.kt │ ├── document-scanner/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── document_scanner/ │ │ └── presentation/ │ │ ├── DocumentScannerContent.kt │ │ └── screenLogic/ │ │ └── DocumentScannerComponent.kt │ ├── draw/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── draw/ │ │ ├── data/ │ │ │ ├── AndroidImageDrawApplier.kt │ │ │ └── utils/ │ │ │ └── DrawRepeatedPath.kt │ │ ├── di/ │ │ │ └── DrawModule.kt │ │ ├── domain/ │ │ │ ├── DrawBehavior.kt │ │ │ ├── DrawLineStyle.kt │ │ │ ├── DrawMode.kt │ │ │ ├── DrawOnBackgroundParams.kt │ │ │ ├── DrawPathMode.kt │ │ │ ├── ImageDrawApplier.kt │ │ │ ├── PathPaint.kt │ │ │ └── Warp.kt │ │ └── presentation/ │ │ ├── DrawContent.kt │ │ ├── components/ │ │ │ ├── BitmapDrawer.kt │ │ │ ├── BrushSoftnessSelector.kt │ │ │ ├── DrawColorSelector.kt │ │ │ ├── DrawLineStyleSelector.kt │ │ │ ├── DrawModeSelector.kt │ │ │ ├── DrawPathModeSelector.kt │ │ │ ├── LineWidthSelector.kt │ │ │ ├── OpenColorPickerCard.kt │ │ │ ├── PixelSizeSelector.kt │ │ │ ├── UiPathPaint.kt │ │ │ ├── UiPathPaintCanvasAction.kt │ │ │ ├── controls/ │ │ │ │ ├── DrawContentControls.kt │ │ │ │ ├── DrawContentNoDataControls.kt │ │ │ │ └── DrawContentSecondaryControls.kt │ │ │ ├── element/ │ │ │ │ ├── ArrowParamsSelector.kt │ │ │ │ ├── CustomPathEffectParamsSelector.kt │ │ │ │ ├── DrawPathModeInfoSheet.kt │ │ │ │ ├── FloodFillParamsSelector.kt │ │ │ │ ├── ImageParamsSelector.kt │ │ │ │ ├── OutlinedFillColorSelector.kt │ │ │ │ ├── OvalParamsSelector.kt │ │ │ │ ├── PixelationParamsSelector.kt │ │ │ │ ├── PolygonParamsSelector.kt │ │ │ │ ├── PrivacyBlurParamsSelector.kt │ │ │ │ ├── RectParamsSelector.kt │ │ │ │ ├── SpotHealParamsSelector.kt │ │ │ │ ├── SprayParamsSelector.kt │ │ │ │ ├── StarParamsSelector.kt │ │ │ │ ├── TextParamsSelector.kt │ │ │ │ ├── TriangleParamsSelector.kt │ │ │ │ └── WarpParamsSelector.kt │ │ │ └── utils/ │ │ │ ├── BitmapDrawerPreview.kt │ │ │ ├── DrawPathEffectPreview.kt │ │ │ ├── DrawPathModeUtils.kt │ │ │ ├── DrawRepeatedPath.kt │ │ │ ├── DrawUtils.kt │ │ │ ├── FloodFill.kt │ │ │ ├── MotionEvent.kt │ │ │ ├── PathHelper.kt │ │ │ └── PointerDraw.kt │ │ └── screenLogic/ │ │ └── DrawComponent.kt │ ├── easter-egg/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── easter_egg/ │ │ └── presentation/ │ │ ├── EasterEggContent.kt │ │ └── screenLogic/ │ │ └── EasterEggComponent.kt │ ├── edit-exif/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── edit_exif/ │ │ └── presentation/ │ │ ├── EditExifContent.kt │ │ └── screenLogic/ │ │ └── EditExifComponent.kt │ ├── erase-background/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── foss/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── feature/ │ │ │ └── erase_background/ │ │ │ └── data/ │ │ │ └── backend/ │ │ │ └── MlKitBackgroundRemoverBackend.kt │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── imagetoolbox/ │ │ │ └── feature/ │ │ │ └── erase_background/ │ │ │ ├── data/ │ │ │ │ ├── AndroidAutoBackgroundRemover.kt │ │ │ │ ├── AndroidAutoBackgroundRemoverBackendFactory.kt │ │ │ │ └── backend/ │ │ │ │ └── GenericBackgroundRemoverBackend.kt │ │ │ ├── di/ │ │ │ │ └── EraseBackgroundModule.kt │ │ │ ├── domain/ │ │ │ │ ├── AutoBackgroundRemover.kt │ │ │ │ ├── AutoBackgroundRemoverBackend.kt │ │ │ │ ├── AutoBackgroundRemoverBackendFactory.kt │ │ │ │ └── model/ │ │ │ │ └── BgModelType.kt │ │ │ └── presentation/ │ │ │ ├── EraseBackgroundContent.kt │ │ │ ├── components/ │ │ │ │ ├── AutoEraseBackgroundCard.kt │ │ │ │ ├── BitmapEraser.kt │ │ │ │ ├── OriginalImagePreviewAlphaSelector.kt │ │ │ │ ├── RecoverModeCard.kt │ │ │ │ └── TrimImageToggle.kt │ │ │ └── screenLogic/ │ │ │ └── EraseBackgroundComponent.kt │ │ └── market/ │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── erase_background/ │ │ └── data/ │ │ └── backend/ │ │ └── MlKitBackgroundRemoverBackend.kt │ ├── filters/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── filters/ │ │ ├── data/ │ │ │ ├── AndroidFilterMaskApplier.kt │ │ │ ├── AndroidFilterParamsInteractor.kt │ │ │ ├── AndroidFilterProvider.kt │ │ │ ├── model/ │ │ │ │ ├── AcesFilmicToneMappingFilter.kt │ │ │ │ ├── AcesHillToneMappingFilter.kt │ │ │ │ ├── AchromatomalyFilter.kt │ │ │ │ ├── AchromatopsiaFilter.kt │ │ │ │ ├── AldridgeFilter.kt │ │ │ │ ├── AmatorkaFilter.kt │ │ │ │ ├── AnaglyphFilter.kt │ │ │ │ ├── AnisotropicDiffusionFilter.kt │ │ │ │ ├── ArcFilter.kt │ │ │ │ ├── AsciiFilter.kt │ │ │ │ ├── AtkinsonDitheringFilter.kt │ │ │ │ ├── AutoCropFilter.kt │ │ │ │ ├── AutoPerspectiveFilter.kt │ │ │ │ ├── AutoRemoveRedEyesFilter.kt │ │ │ │ ├── AutumnTonesFilter.kt │ │ │ │ ├── AverageDistanceFilter.kt │ │ │ │ ├── BayerEightDitheringFilter.kt │ │ │ │ ├── BayerFourDitheringFilter.kt │ │ │ │ ├── BayerThreeDitheringFilter.kt │ │ │ │ ├── BayerTwoDitheringFilter.kt │ │ │ │ ├── BilaterialBlurFilter.kt │ │ │ │ ├── BlackAndWhiteFilter.kt │ │ │ │ ├── BlackHatFilter.kt │ │ │ │ ├── BleachBypassFilter.kt │ │ │ │ ├── BlockGlitchFilter.kt │ │ │ │ ├── BloomFilter.kt │ │ │ │ ├── BokehFilter.kt │ │ │ │ ├── BorderFrameFilter.kt │ │ │ │ ├── BoxBlurFilter.kt │ │ │ │ ├── BrightnessFilter.kt │ │ │ │ ├── BrowniFilter.kt │ │ │ │ ├── BulgeDistortionFilter.kt │ │ │ │ ├── BurkesDitheringFilter.kt │ │ │ │ ├── CGAColorSpaceFilter.kt │ │ │ │ ├── CandlelightFilter.kt │ │ │ │ ├── CannyFilter.kt │ │ │ │ ├── CaramelDarknessFilter.kt │ │ │ │ ├── CelluloidFilter.kt │ │ │ │ ├── ChannelMixFilter.kt │ │ │ │ ├── CircleBlurFilter.kt │ │ │ │ ├── CirclePixelationFilter.kt │ │ │ │ ├── ClaheFilter.kt │ │ │ │ ├── ClaheHSLFilter.kt │ │ │ │ ├── ClaheHSVFilter.kt │ │ │ │ ├── ClaheJzazbzFilter.kt │ │ │ │ ├── ClaheLABFilter.kt │ │ │ │ ├── ClaheLUVFilter.kt │ │ │ │ ├── ClaheOklabFilter.kt │ │ │ │ ├── ClaheOklchFilter.kt │ │ │ │ ├── ClosingFilter.kt │ │ │ │ ├── Clustered2x2DitheringFilter.kt │ │ │ │ ├── Clustered4x4DitheringFilter.kt │ │ │ │ ├── Clustered8x8DitheringFilter.kt │ │ │ │ ├── CodaChromeFilter.kt │ │ │ │ ├── CoffeeFilter.kt │ │ │ │ ├── ColorAnomalyFilter.kt │ │ │ │ ├── ColorBalanceFilter.kt │ │ │ │ ├── ColorExplosionFilter.kt │ │ │ │ ├── ColorHalftoneFilter.kt │ │ │ │ ├── ColorMapFilter.kt │ │ │ │ ├── ColorMatrix3x3Filter.kt │ │ │ │ ├── ColorMatrix4x4Filter.kt │ │ │ │ ├── ColorOverlayFilter.kt │ │ │ │ ├── ColorPosterFilter.kt │ │ │ │ ├── ColorfulSwirlFilter.kt │ │ │ │ ├── ContourFilter.kt │ │ │ │ ├── ContrastFilter.kt │ │ │ │ ├── ConvexFilter.kt │ │ │ │ ├── Convolution3x3Filter.kt │ │ │ │ ├── CoolFilter.kt │ │ │ │ ├── CopyMoveDetectionFilter.kt │ │ │ │ ├── CropOrPerspectiveFilter.kt │ │ │ │ ├── CropToContentFilter.kt │ │ │ │ ├── CrossBlurFilter.kt │ │ │ │ ├── CrossPixelationFilter.kt │ │ │ │ ├── CrosshatchFilter.kt │ │ │ │ ├── CrtCurvatureFilter.kt │ │ │ │ ├── CrystallizeFilter.kt │ │ │ │ ├── CubeLutFilter.kt │ │ │ │ ├── CyberpunkFilter.kt │ │ │ │ ├── DeepPurpleFilter.kt │ │ │ │ ├── DehazeFilter.kt │ │ │ │ ├── DeskewFilter.kt │ │ │ │ ├── DespeckleFilter.kt │ │ │ │ ├── DeutaromalyFilter.kt │ │ │ │ ├── DeutaronotopiaFilter.kt │ │ │ │ ├── DiamondPixelationFilter.kt │ │ │ │ ├── DiffuseFilter.kt │ │ │ │ ├── DigitalCodeFilter.kt │ │ │ │ ├── DilationFilter.kt │ │ │ │ ├── DoGFilter.kt │ │ │ │ ├── DragoFilter.kt │ │ │ │ ├── DropBluesFilter.kt │ │ │ │ ├── EdgyAmberFilter.kt │ │ │ │ ├── ElectricGradientFilter.kt │ │ │ │ ├── EmbossFilter.kt │ │ │ │ ├── EnhancedCirclePixelationFilter.kt │ │ │ │ ├── EnhancedDiamondPixelationFilter.kt │ │ │ │ ├── EnhancedGlitchFilter.kt │ │ │ │ ├── EnhancedOilFilter.kt │ │ │ │ ├── EnhancedPixelationFilter.kt │ │ │ │ ├── EnhancedZoomBlurFilter.kt │ │ │ │ ├── EqualizeFilter.kt │ │ │ │ ├── EqualizeHistogramAdaptiveFilter.kt │ │ │ │ ├── EqualizeHistogramAdaptiveHSLFilter.kt │ │ │ │ ├── EqualizeHistogramAdaptiveHSVFilter.kt │ │ │ │ ├── EqualizeHistogramAdaptiveLABFilter.kt │ │ │ │ ├── EqualizeHistogramAdaptiveLUVFilter.kt │ │ │ │ ├── EqualizeHistogramFilter.kt │ │ │ │ ├── EqualizeHistogramHSVFilter.kt │ │ │ │ ├── EqualizeHistogramPixelationFilter.kt │ │ │ │ ├── ErodeFilter.kt │ │ │ │ ├── ErrorLevelAnalysisFilter.kt │ │ │ │ ├── ExposureFilter.kt │ │ │ │ ├── FallColorsFilter.kt │ │ │ │ ├── FalseColorFilter.kt │ │ │ │ ├── FalseFloydSteinbergDitheringFilter.kt │ │ │ │ ├── FantasyLandscapeFilter.kt │ │ │ │ ├── FastBilaterialBlurFilter.kt │ │ │ │ ├── FastBlurFilter.kt │ │ │ │ ├── FastGaussianBlur2DFilter.kt │ │ │ │ ├── FastGaussianBlur3DFilter.kt │ │ │ │ ├── FastGaussianBlur4DFilter.kt │ │ │ │ ├── FilmStock50Filter.kt │ │ │ │ ├── FloydSteinbergDitheringFilter.kt │ │ │ │ ├── FoggyNightFilter.kt │ │ │ │ ├── FractalGlassFilter.kt │ │ │ │ ├── FuturisticGradientFilter.kt │ │ │ │ ├── GammaFilter.kt │ │ │ │ ├── GaussianBlurFilter.kt │ │ │ │ ├── GaussianBoxBlurFilter.kt │ │ │ │ ├── GlassSphereRefractionFilter.kt │ │ │ │ ├── GlitchFilter.kt │ │ │ │ ├── GlitchVariantFilter.kt │ │ │ │ ├── GlowFilter.kt │ │ │ │ ├── GoldenForestFilter.kt │ │ │ │ ├── GoldenHourFilter.kt │ │ │ │ ├── GothamFilter.kt │ │ │ │ ├── GrainFilter.kt │ │ │ │ ├── GrayscaleFilter.kt │ │ │ │ ├── GreenSunFilter.kt │ │ │ │ ├── GreenishFilter.kt │ │ │ │ ├── HDRFilter.kt │ │ │ │ ├── HableFilmicToneMappingFilter.kt │ │ │ │ ├── HalftoneFilter.kt │ │ │ │ ├── HazeFilter.kt │ │ │ │ ├── HejlBurgessToneMappingFilter.kt │ │ │ │ ├── HighlightsAndShadowsFilter.kt │ │ │ │ ├── HorizontalWindStaggerFilter.kt │ │ │ │ ├── HotSummerFilter.kt │ │ │ │ ├── HueFilter.kt │ │ │ │ ├── JarvisJudiceNinkeDitheringFilter.kt │ │ │ │ ├── KaleidoscopeFilter.kt │ │ │ │ ├── KodakFilter.kt │ │ │ │ ├── KuwaharaFilter.kt │ │ │ │ ├── LUT512x512Filter.kt │ │ │ │ ├── LaplacianFilter.kt │ │ │ │ ├── LaplacianSimpleFilter.kt │ │ │ │ ├── LavenderDreamFilter.kt │ │ │ │ ├── LeftToRightDitheringFilter.kt │ │ │ │ ├── LemonadeLightFilter.kt │ │ │ │ ├── LensCorrectionFilter.kt │ │ │ │ ├── LinearBoxBlurFilter.kt │ │ │ │ ├── LinearFastGaussianBlurFilter.kt │ │ │ │ ├── LinearFastGaussianBlurNextFilter.kt │ │ │ │ ├── LinearGaussianBlurFilter.kt │ │ │ │ ├── LinearGaussianBoxBlurFilter.kt │ │ │ │ ├── LinearStackBlurFilter.kt │ │ │ │ ├── LinearTentBlurFilter.kt │ │ │ │ ├── LinearTiltShiftFilter.kt │ │ │ │ ├── LogarithmicToneMappingFilter.kt │ │ │ │ ├── LookupFilter.kt │ │ │ │ ├── LowPolyFilter.kt │ │ │ │ ├── LuminanceGradientFilter.kt │ │ │ │ ├── MarbleFilter.kt │ │ │ │ ├── MedianBlurFilter.kt │ │ │ │ ├── MicroMacroPixelationFilter.kt │ │ │ │ ├── MirrorFilter.kt │ │ │ │ ├── MissEtikateFilter.kt │ │ │ │ ├── MobiusFilter.kt │ │ │ │ ├── MoireFilter.kt │ │ │ │ ├── MonochromeFilter.kt │ │ │ │ ├── MorphologicalGradientFilter.kt │ │ │ │ ├── MotionBlurFilter.kt │ │ │ │ ├── NativeStackBlurFilter.kt │ │ │ │ ├── NegativeFilter.kt │ │ │ │ ├── NeonFilter.kt │ │ │ │ ├── NightMagicFilter.kt │ │ │ │ ├── NightVisionFilter.kt │ │ │ │ ├── NoiseFilter.kt │ │ │ │ ├── NonMaximumSuppressionFilter.kt │ │ │ │ ├── NucleusPixelationFilter.kt │ │ │ │ ├── OffsetFilter.kt │ │ │ │ ├── OilFilter.kt │ │ │ │ ├── OldTvFilter.kt │ │ │ │ ├── OpacityFilter.kt │ │ │ │ ├── OpeningFilter.kt │ │ │ │ ├── OrangeHazeFilter.kt │ │ │ │ ├── OrbitalPixelationFilter.kt │ │ │ │ ├── PaletteTransferFilter.kt │ │ │ │ ├── PaletteTransferVariantFilter.kt │ │ │ │ ├── PastelFilter.kt │ │ │ │ ├── PerlinDistortionFilter.kt │ │ │ │ ├── PinchFilter.kt │ │ │ │ ├── PinkDreamFilter.kt │ │ │ │ ├── PixelMeltFilter.kt │ │ │ │ ├── PixelationFilter.kt │ │ │ │ ├── PointillizeFilter.kt │ │ │ │ ├── PoissonBlurFilter.kt │ │ │ │ ├── PolarCoordinatesFilter.kt │ │ │ │ ├── PolaroidFilter.kt │ │ │ │ ├── PolkaDotFilter.kt │ │ │ │ ├── PopArtFilter.kt │ │ │ │ ├── PosterizeFilter.kt │ │ │ │ ├── ProtanopiaFilter.kt │ │ │ │ ├── ProtonomalyFilter.kt │ │ │ │ ├── PulseGridPixelationFilter.kt │ │ │ │ ├── PurpleMistFilter.kt │ │ │ │ ├── QuantizierFilter.kt │ │ │ │ ├── RGBFilter.kt │ │ │ │ ├── RadialTiltShiftFilter.kt │ │ │ │ ├── RadialWeavePixelationFilter.kt │ │ │ │ ├── RainbowWorldFilter.kt │ │ │ │ ├── RandomDitheringFilter.kt │ │ │ │ ├── RedSwirlFilter.kt │ │ │ │ ├── ReduceNoiseFilter.kt │ │ │ │ ├── RemoveColorFilter.kt │ │ │ │ ├── ReplaceColorFilter.kt │ │ │ │ ├── RetroYellowFilter.kt │ │ │ │ ├── RingBlurFilter.kt │ │ │ │ ├── RubberStampFilter.kt │ │ │ │ ├── SandPaintingFilter.kt │ │ │ │ ├── SaturationFilter.kt │ │ │ │ ├── SeamCarvingFilter.kt │ │ │ │ ├── SepiaFilter.kt │ │ │ │ ├── SharpenFilter.kt │ │ │ │ ├── ShuffleBlurFilter.kt │ │ │ │ ├── SideFadeFilter.kt │ │ │ │ ├── SierraDitheringFilter.kt │ │ │ │ ├── SierraLiteDitheringFilter.kt │ │ │ │ ├── SimpleOldTvFilter.kt │ │ │ │ ├── SimpleSketchFilter.kt │ │ │ │ ├── SimpleSolarizeFilter.kt │ │ │ │ ├── SimpleThresholdDitheringFilter.kt │ │ │ │ ├── SimpleWeavePixelationFilter.kt │ │ │ │ ├── SketchFilter.kt │ │ │ │ ├── SmearFilter.kt │ │ │ │ ├── SmoothToonFilter.kt │ │ │ │ ├── SobelEdgeDetectionFilter.kt │ │ │ │ ├── SobelSimpleFilter.kt │ │ │ │ ├── SoftEleganceFilter.kt │ │ │ │ ├── SoftEleganceVariantFilter.kt │ │ │ │ ├── SoftSpringLightFilter.kt │ │ │ │ ├── SolarizeFilter.kt │ │ │ │ ├── SpacePortalFilter.kt │ │ │ │ ├── SparkleFilter.kt │ │ │ │ ├── SpectralFireFilter.kt │ │ │ │ ├── SphereLensDistortionFilter.kt │ │ │ │ ├── SphereRefractionFilter.kt │ │ │ │ ├── SpotHealFilter.kt │ │ │ │ ├── StackBlurFilter.kt │ │ │ │ ├── StaggeredPixelationFilter.kt │ │ │ │ ├── StarBlurFilter.kt │ │ │ │ ├── StrokePixelationFilter.kt │ │ │ │ ├── StuckiDitheringFilter.kt │ │ │ │ ├── SunriseFilter.kt │ │ │ │ ├── SwirlDistortionFilter.kt │ │ │ │ ├── TentBlurFilter.kt │ │ │ │ ├── ThresholdFilter.kt │ │ │ │ ├── ToneCurvesFilter.kt │ │ │ │ ├── ToonFilter.kt │ │ │ │ ├── TopHatFilter.kt │ │ │ │ ├── TriToneFilter.kt │ │ │ │ ├── TritanopiaFilter.kt │ │ │ │ ├── TritonomalyFilter.kt │ │ │ │ ├── TwirlFilter.kt │ │ │ │ ├── TwoRowSierraDitheringFilter.kt │ │ │ │ ├── UchimuraFilter.kt │ │ │ │ ├── UnsharpFilter.kt │ │ │ │ ├── VHSFilter.kt │ │ │ │ ├── VibranceFilter.kt │ │ │ │ ├── VignetteFilter.kt │ │ │ │ ├── VintageFilter.kt │ │ │ │ ├── VoronoiCrystallizeFilter.kt │ │ │ │ ├── VortexPixelationFilter.kt │ │ │ │ ├── WarmFilter.kt │ │ │ │ ├── WaterEffectFilter.kt │ │ │ │ ├── WeakPixelFilter.kt │ │ │ │ ├── WeaveFilter.kt │ │ │ │ ├── WhiteBalanceFilter.kt │ │ │ │ ├── YililomaDitheringFilter.kt │ │ │ │ └── ZoomBlurFilter.kt │ │ │ ├── transformation/ │ │ │ │ ├── ColorMapTransformation.kt │ │ │ │ ├── GPUFilterTransformation.kt │ │ │ │ └── JhFilterTransformation.kt │ │ │ └── utils/ │ │ │ ├── EnumMappings.kt │ │ │ ├── TransformationUtils.kt │ │ │ ├── convolution/ │ │ │ │ └── AireConvolution.kt │ │ │ ├── glitch/ │ │ │ │ ├── GlitchTool.kt │ │ │ │ └── tools/ │ │ │ │ ├── Anaglyph.kt │ │ │ │ └── JpegGlitch.kt │ │ │ ├── gpu/ │ │ │ │ └── GPUImageHighlightShadowWideRangeFilter.kt │ │ │ ├── image/ │ │ │ │ └── ImageLoader.kt │ │ │ ├── pixelation/ │ │ │ │ ├── PixelationTool.kt │ │ │ │ └── tool/ │ │ │ │ ├── PixelationCommands.kt │ │ │ │ └── PixelationLayer.kt │ │ │ └── serialization/ │ │ │ ├── FilterSerializationUtils.kt │ │ │ └── Mappings.kt │ │ ├── di/ │ │ │ └── FilterModule.kt │ │ ├── domain/ │ │ │ ├── FilterMask.kt │ │ │ └── FilterMaskApplier.kt │ │ └── presentation/ │ │ ├── FiltersContent.kt │ │ ├── components/ │ │ │ ├── BasicFilterPreference.kt │ │ │ ├── BasicFilterState.kt │ │ │ ├── FiltersContentActionButtons.kt │ │ │ ├── FiltersContentControls.kt │ │ │ ├── FiltersContentNoData.kt │ │ │ ├── FiltersContentSheets.kt │ │ │ ├── FiltersContentTopAppBarActions.kt │ │ │ ├── MaskFilterPreference.kt │ │ │ ├── MaskItem.kt │ │ │ ├── MaskReorderSheet.kt │ │ │ ├── MaskingFilterState.kt │ │ │ ├── PathPaintPreview.kt │ │ │ ├── UiFilterMask.kt │ │ │ └── addEditMaskSheet/ │ │ │ ├── AddEditMaskSheet.kt │ │ │ ├── AddEditMaskSheetControls.kt │ │ │ ├── AddMaskSheetBitmapPreview.kt │ │ │ └── AddMaskSheetComponent.kt │ │ └── screenLogic/ │ │ └── FiltersComponent.kt │ ├── format-conversion/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── format_conversion/ │ │ └── presentation/ │ │ ├── FormatConversionContent.kt │ │ └── screenLogic/ │ │ └── FormatConversionComponent.kt │ ├── gif-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── gif_tools/ │ │ ├── data/ │ │ │ └── AndroidGifConverter.kt │ │ ├── di/ │ │ │ └── GifToolsModule.kt │ │ ├── domain/ │ │ │ ├── GifConverter.kt │ │ │ └── GifParams.kt │ │ └── presentation/ │ │ ├── GifToolsContent.kt │ │ ├── components/ │ │ │ ├── GifParamsSelector.kt │ │ │ ├── GifToolsControls.kt │ │ │ ├── GifToolsImagePreview.kt │ │ │ ├── GifToolsNoDataControls.kt │ │ │ └── GifToolsTopAppBarActions.kt │ │ └── screenLogic/ │ │ └── GifToolsComponent.kt │ ├── gradient-maker/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── gradient_maker/ │ │ ├── data/ │ │ │ └── AndroidGradientMaker.kt │ │ ├── di/ │ │ │ └── GradientMakerModule.kt │ │ ├── domain/ │ │ │ ├── GradientMaker.kt │ │ │ ├── GradientState.kt │ │ │ ├── GradientType.kt │ │ │ └── MeshGradientState.kt │ │ └── presentation/ │ │ ├── GradientMakerContent.kt │ │ ├── components/ │ │ │ ├── ColorStopSelection.kt │ │ │ ├── GradientMakerAppColorSchemeHandler.kt │ │ │ ├── GradientMakerBottomButtons.kt │ │ │ ├── GradientMakerCompareButton.kt │ │ │ ├── GradientMakerControls.kt │ │ │ ├── GradientMakerImagePreview.kt │ │ │ ├── GradientMakerNoDataControls.kt │ │ │ ├── GradientPreview.kt │ │ │ ├── GradientPropertiesSelector.kt │ │ │ ├── GradientSizeSelector.kt │ │ │ ├── GradientTypeSelector.kt │ │ │ ├── MeshGradientEditor.kt │ │ │ ├── MeshGradientPreview.kt │ │ │ ├── TileModeSelector.kt │ │ │ ├── UiGradientState.kt │ │ │ └── model/ │ │ │ └── GradientMakerType.kt │ │ └── screenLogic/ │ │ └── GradientMakerComponent.kt │ ├── image-cutting/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── image_cutting/ │ │ ├── data/ │ │ │ └── AndroidImageCutter.kt │ │ ├── di/ │ │ │ └── ImageCutterModule.kt │ │ ├── domain/ │ │ │ ├── CutParams.kt │ │ │ └── ImageCutter.kt │ │ └── presentation/ │ │ ├── ImageCutterContent.kt │ │ ├── components/ │ │ │ ├── CutParamsSelector.kt │ │ │ ├── CutPreview.kt │ │ │ └── Utils.kt │ │ └── screenLogic/ │ │ └── ImageCutterComponent.kt │ ├── image-preview/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── image_preview/ │ │ └── presentation/ │ │ ├── ImagePreviewContent.kt │ │ └── screenLogic/ │ │ └── ImagePreviewComponent.kt │ ├── image-splitting/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── image_splitting/ │ │ ├── data/ │ │ │ └── AndroidImageSplitter.kt │ │ ├── di/ │ │ │ └── ImageSplitterModule.kt │ │ ├── domain/ │ │ │ ├── ImageSplitter.kt │ │ │ └── SplitParams.kt │ │ └── presentation/ │ │ ├── ImageSplitterContent.kt │ │ ├── components/ │ │ │ └── SplitParamsSelector.kt │ │ └── screenLogic/ │ │ └── ImageSplitterComponent.kt │ ├── image-stacking/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── image_stacking/ │ │ ├── data/ │ │ │ └── AndroidImageStacker.kt │ │ ├── di/ │ │ │ └── ImageStackingModule.kt │ │ ├── domain/ │ │ │ ├── ImageStacker.kt │ │ │ ├── StackImage.kt │ │ │ └── StackingParams.kt │ │ └── presentation/ │ │ ├── ImageStackingContent.kt │ │ ├── components/ │ │ │ ├── StackImageItem.kt │ │ │ └── StackingParamsSelector.kt │ │ └── screenLogic/ │ │ └── ImageStackingComponent.kt │ ├── image-stitch/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── image_stitch/ │ │ ├── data/ │ │ │ ├── AndroidImageCombiner.kt │ │ │ └── CvStitchHelper.kt │ │ ├── di/ │ │ │ └── ImageStitchModule.kt │ │ ├── domain/ │ │ │ ├── CombiningParams.kt │ │ │ ├── ImageCombiner.kt │ │ │ ├── SavableCombiningParams.kt │ │ │ ├── StitchAlignment.kt │ │ │ ├── StitchFadeSide.kt │ │ │ └── StitchMode.kt │ │ └── presentation/ │ │ ├── ImageStitchingContent.kt │ │ ├── components/ │ │ │ ├── FadeStrengthSelector.kt │ │ │ ├── ImageFadingEdgesSelector.kt │ │ │ ├── ImageScaleSelector.kt │ │ │ ├── SpacingSelector.kt │ │ │ ├── StitchAlignmentSelector.kt │ │ │ └── StitchModeSelector.kt │ │ └── screenLogic/ │ │ └── ImageStitchingComponent.kt │ ├── jxl-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── jxl_tools/ │ │ ├── data/ │ │ │ └── AndroidJxlConverter.kt │ │ ├── di/ │ │ │ └── JxlToolsModule.kt │ │ ├── domain/ │ │ │ ├── AnimatedJxlParams.kt │ │ │ └── JxlConverter.kt │ │ └── presentation/ │ │ ├── JxlToolsContent.kt │ │ ├── components/ │ │ │ ├── AnimatedJxlParamsSelector.kt │ │ │ ├── JxlToolsBitmapPreview.kt │ │ │ ├── JxlToolsButtons.kt │ │ │ ├── JxlToolsControls.kt │ │ │ ├── JxlToolsNoDataControls.kt │ │ │ └── JxlToolsTopAppBarActions.kt │ │ └── screenLogic/ │ │ └── JxlToolsComponent.kt │ ├── libraries-info/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── libraries_info/ │ │ └── presentation/ │ │ ├── LibrariesInfoContent.kt │ │ ├── components/ │ │ │ ├── LibrariesContainer.kt │ │ │ └── LibraryLink.kt │ │ └── screenLogic/ │ │ └── LibrariesInfoComponent.kt │ ├── library-details/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── library_details/ │ │ └── presentation/ │ │ ├── LibraryDetailsContent.kt │ │ └── screenLogic/ │ │ └── LibraryDetailsComponent.kt │ ├── limits-resize/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── limits_resize/ │ │ ├── data/ │ │ │ └── AndroidLimitsImageScaler.kt │ │ ├── di/ │ │ │ └── LimitsResizeModule.kt │ │ ├── domain/ │ │ │ ├── LimitsImageScaler.kt │ │ │ └── LimitsResizeType.kt │ │ └── presentation/ │ │ ├── LimitsResizeContent.kt │ │ ├── components/ │ │ │ ├── AutoRotateLimitBoxToggle.kt │ │ │ └── LimitResizeGroup.kt │ │ └── screenLogic/ │ │ └── LimitsResizeComponent.kt │ ├── load-net-image/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── load_net_image/ │ │ ├── data/ │ │ │ └── AndroidHtmlImageParser.kt │ │ ├── di/ │ │ │ └── LoadNetImageModule.kt │ │ ├── domain/ │ │ │ └── HtmlImageParser.kt │ │ └── presentation/ │ │ ├── LoadNetImageContent.kt │ │ ├── components/ │ │ │ ├── LoadNetImageActionButtons.kt │ │ │ ├── LoadNetImageAdaptiveActions.kt │ │ │ ├── LoadNetImageTopAppBarActions.kt │ │ │ ├── LoadNetImageUrlTextField.kt │ │ │ ├── ParsedImagePreview.kt │ │ │ └── ParsedImagesSelection.kt │ │ └── screenLogic/ │ │ └── LoadNetImageComponent.kt │ ├── main/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── main/ │ │ └── presentation/ │ │ ├── MainContent.kt │ │ ├── components/ │ │ │ ├── FilteredScreenListFor.kt │ │ │ ├── LauncherScreenSelector.kt │ │ │ ├── MainContentImpl.kt │ │ │ ├── MainDrawerContent.kt │ │ │ ├── MainNavigationBar.kt │ │ │ ├── MainNavigationBarForFavorites.kt │ │ │ ├── MainNavigationRail.kt │ │ │ ├── MainNavigationRailForFavorites.kt │ │ │ ├── MainTopAppBar.kt │ │ │ ├── ScreenPreferenceSelection.kt │ │ │ └── SearchableBottomBar.kt │ │ └── screenLogic/ │ │ └── MainComponent.kt │ ├── markup-layers/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── markup_layers/ │ │ ├── data/ │ │ │ ├── AndroidMarkupLayersApplier.kt │ │ │ ├── project/ │ │ │ │ ├── AssetRegistry.kt │ │ │ │ ├── Mapping.kt │ │ │ │ ├── MarkupProjectConstants.kt │ │ │ │ ├── MarkupProjectExtensions.kt │ │ │ │ ├── MarkupProjectFile.kt │ │ │ │ └── ProjectFileLoadResult.kt │ │ │ └── utils/ │ │ │ ├── LayersRenderer.kt │ │ │ ├── PictureLayerShadowRenderer.kt │ │ │ ├── ShapeLayerRenderer.kt │ │ │ ├── TextLayerMetrics.kt │ │ │ └── TextLayerShadowRenderer.kt │ │ ├── di/ │ │ │ └── MarkupLayersModule.kt │ │ ├── domain/ │ │ │ ├── MarkupLayer.kt │ │ │ ├── MarkupLayersApplier.kt │ │ │ ├── MarkupProject.kt │ │ │ ├── MarkupProjectResult.kt │ │ │ ├── ProjectBackground.kt │ │ │ └── ShapeLayerModeExt.kt │ │ └── presentation/ │ │ ├── MarkupLayersContent.kt │ │ ├── components/ │ │ │ ├── ActiveLayerGestureModifier.kt │ │ │ ├── AddShapeLayerDialog.kt │ │ │ ├── AddTextLayerDialog.kt │ │ │ ├── ClickableTile.kt │ │ │ ├── DropShadowSection.kt │ │ │ ├── EditBox.kt │ │ │ ├── EditBoxState.kt │ │ │ ├── EditLayerSheet.kt │ │ │ ├── Layer.kt │ │ │ ├── LayerContent.kt │ │ │ ├── MarkupLayersActions.kt │ │ │ ├── MarkupLayersContextActions.kt │ │ │ ├── MarkupLayersNoDataControls.kt │ │ │ ├── MarkupLayersSideMenu.kt │ │ │ ├── MarkupLayersSideMenuColumn.kt │ │ │ ├── MarkupLayersTopAppBarActions.kt │ │ │ ├── ShapeLayerParamsSelector.kt │ │ │ └── model/ │ │ │ ├── BackgroundBehavior.kt │ │ │ ├── ShapeLayerModeUiExt.kt │ │ │ ├── UiMarkupLayer.kt │ │ │ └── UiMarkupLayerGrouping.kt │ │ └── screenLogic/ │ │ └── MarkupLayersComponent.kt │ ├── media-picker/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── media_picker/ │ │ ├── data/ │ │ │ ├── AndroidMediaRetriever.kt │ │ │ └── utils/ │ │ │ ├── DateExt.kt │ │ │ ├── MediaObserver.kt │ │ │ ├── MediaQuery.kt │ │ │ └── SupportedFiles.kt │ │ ├── di/ │ │ │ └── MediaPickerModule.kt │ │ ├── domain/ │ │ │ ├── MediaRetriever.kt │ │ │ └── model/ │ │ │ ├── Album.kt │ │ │ ├── AllowedMedia.kt │ │ │ ├── Media.kt │ │ │ ├── MediaItem.kt │ │ │ ├── MediaOrder.kt │ │ │ ├── MediaState.kt │ │ │ └── OrderType.kt │ │ └── presentation/ │ │ ├── MediaPickerActivity.kt │ │ ├── components/ │ │ │ ├── ManageExternalStorageWarning.kt │ │ │ ├── MediaExtensionHeader.kt │ │ │ ├── MediaImage.kt │ │ │ ├── MediaImagePager.kt │ │ │ ├── MediaPickerGrid.kt │ │ │ ├── MediaPickerGridWithOverlays.kt │ │ │ ├── MediaPickerHavePermissions.kt │ │ │ ├── MediaPickerRootContent.kt │ │ │ ├── MediaPickerRootContentEmbeddable.kt │ │ │ ├── MediaSizeFooter.kt │ │ │ ├── MediaStickyHeader.kt │ │ │ ├── MediaVideoDurationHeader.kt │ │ │ ├── ObserveColorSchemeExtra.kt │ │ │ └── SendMediaAsResult.kt │ │ └── screenLogic/ │ │ └── MediaPickerComponent.kt │ ├── mesh-gradients/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── mesh_gradients/ │ │ └── presentation/ │ │ ├── MeshGradientsContent.kt │ │ └── screenLogic/ │ │ └── MeshGradientsComponent.kt │ ├── noise-generation/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── noise_generation/ │ │ ├── data/ │ │ │ └── AndroidNoiseGenerator.kt │ │ ├── di/ │ │ │ └── NoiseGenerationModule.kt │ │ ├── domain/ │ │ │ ├── NoiseGenerator.kt │ │ │ └── model/ │ │ │ ├── CellularDistanceFunction.kt │ │ │ ├── CellularReturnType.kt │ │ │ ├── DomainWarpType.kt │ │ │ ├── FractalType.kt │ │ │ ├── NoiseParams.kt │ │ │ ├── NoiseType.kt │ │ │ └── RotationType3D.kt │ │ └── presentation/ │ │ ├── NoiseGenerationContent.kt │ │ ├── components/ │ │ │ └── NoiseParamsSelection.kt │ │ └── screenLogic/ │ │ └── NoiseGenerationComponent.kt │ ├── palette-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── palette_tools/ │ │ └── presentation/ │ │ ├── PaletteToolsContent.kt │ │ ├── components/ │ │ │ ├── DefaultPaletteControls.kt │ │ │ ├── EditPaletteControls.kt │ │ │ ├── ImageColorPalette.kt │ │ │ ├── MaterialYouPalette.kt │ │ │ ├── MaterialYouPaletteControls.kt │ │ │ ├── MaterialYouPaletteGroup.kt │ │ │ ├── MaterialYouPaletteItem.kt │ │ │ ├── PaletteColorNameField.kt │ │ │ ├── PaletteColorsCountSelector.kt │ │ │ ├── PaletteToolsScreenControls.kt │ │ │ ├── PaletteType.kt │ │ │ └── model/ │ │ │ ├── NamedColor.kt │ │ │ └── PaletteFormatHelper.kt │ │ └── screenLogic/ │ │ └── PaletteToolsComponent.kt │ ├── pdf-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── pdf_tools/ │ │ ├── data/ │ │ │ ├── AndroidPdfHelper.kt │ │ │ ├── AndroidPdfManager.kt │ │ │ └── utils/ │ │ │ ├── Hocr.kt │ │ │ ├── PdfContentStreamEditor.kt │ │ │ ├── PdfRenderer.kt │ │ │ └── PdfUtils.kt │ │ ├── di/ │ │ │ └── PdfToolsModule.kt │ │ ├── domain/ │ │ │ ├── PdfHelper.kt │ │ │ ├── PdfManager.kt │ │ │ └── model/ │ │ │ ├── ExtractPagesAction.kt │ │ │ ├── PageOrientation.kt │ │ │ ├── PageSize.kt │ │ │ ├── PdfAnnotationType.kt │ │ │ ├── PdfCheckResult.kt │ │ │ ├── PdfCreationParams.kt │ │ │ ├── PdfCropParams.kt │ │ │ ├── PdfExtractPagesParams.kt │ │ │ ├── PdfMetadata.kt │ │ │ ├── PdfPageNumbersParams.kt │ │ │ ├── PdfRemoveAnnotationParams.kt │ │ │ ├── PdfSignatureParams.kt │ │ │ ├── PdfWatermarkParams.kt │ │ │ ├── PrintPdfParams.kt │ │ │ └── SearchablePdfPage.kt │ │ └── presentation/ │ │ ├── common/ │ │ │ ├── BasePdfToolComponent.kt │ │ │ ├── BasePdfToolContent.kt │ │ │ ├── PageSwitcher.kt │ │ │ └── PdfPreviewItem.kt │ │ ├── compress/ │ │ │ ├── CompressPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── CompressPdfToolComponent.kt │ │ ├── crop/ │ │ │ ├── CropPdfToolContent.kt │ │ │ ├── components/ │ │ │ │ └── CropPreview.kt │ │ │ └── screenLogic/ │ │ │ └── CropPdfToolComponent.kt │ │ ├── extract_images/ │ │ │ ├── ExtractImagesPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── ExtractImagesPdfToolComponent.kt │ │ ├── extract_pages/ │ │ │ ├── ExtractPagesPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── ExtractPagesPdfToolComponent.kt │ │ ├── flatten/ │ │ │ ├── FlattenPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── FlattenPdfToolComponent.kt │ │ ├── grayscale/ │ │ │ ├── GrayscalePdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── GrayscalePdfToolComponent.kt │ │ ├── images_to_pdf/ │ │ │ ├── ImagesToPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── ImagesToPdfToolComponent.kt │ │ ├── merge/ │ │ │ ├── MergePdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── MergePdfToolComponent.kt │ │ ├── metadata/ │ │ │ ├── MetadataPdfToolContent.kt │ │ │ ├── components/ │ │ │ │ └── MetadataEditor.kt │ │ │ └── screenLogic/ │ │ │ └── MetadataPdfToolComponent.kt │ │ ├── ocr/ │ │ │ ├── OCRPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── OCRPdfToolComponent.kt │ │ ├── page_numbers/ │ │ │ ├── PageNumbersPdfToolContent.kt │ │ │ ├── components/ │ │ │ │ └── PageNumbersPreview.kt │ │ │ └── screenLogic/ │ │ │ └── PageNumbersPdfToolComponent.kt │ │ ├── preview/ │ │ │ ├── PreviewPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── PreviewPdfToolComponent.kt │ │ ├── print/ │ │ │ ├── PrintPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── PrintPdfToolComponent.kt │ │ ├── protect/ │ │ │ ├── ProtectPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── ProtectPdfToolComponent.kt │ │ ├── rearrange/ │ │ │ ├── RearrangePdfToolContent.kt │ │ │ ├── components/ │ │ │ │ └── PdfPagesRearrangeGrid.kt │ │ │ └── screenLogic/ │ │ │ └── RearrangePdfToolComponent.kt │ │ ├── remove_annotations/ │ │ │ ├── RemoveAnnotationsPdfToolContent.kt │ │ │ ├── components/ │ │ │ │ ├── PdfAnnotationTypeSelector.kt │ │ │ │ └── RemoveAnnotationsPreview.kt │ │ │ └── screenLogic/ │ │ │ └── RemoveAnnotationsPdfToolComponent.kt │ │ ├── remove_pages/ │ │ │ ├── RemovePagesPdfToolContent.kt │ │ │ ├── components/ │ │ │ │ └── PdfPagesRemoveGrid.kt │ │ │ └── screenLogic/ │ │ │ └── RemovePagesPdfToolComponent.kt │ │ ├── repair/ │ │ │ ├── RepairPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── RepairPdfToolComponent.kt │ │ ├── root/ │ │ │ ├── RootPdfToolsContent.kt │ │ │ ├── components/ │ │ │ │ ├── PdfViewer.kt │ │ │ │ └── viewer/ │ │ │ │ ├── LegacyPdfViewer.kt │ │ │ │ ├── ModernPdfViewer.kt │ │ │ │ └── ModernPdfViewerDelegate.kt │ │ │ └── screenLogic/ │ │ │ └── RootPdfToolsComponent.kt │ │ ├── rotate/ │ │ │ ├── RotatePdfToolContent.kt │ │ │ ├── components/ │ │ │ │ └── PdfPagesRotationGrid.kt │ │ │ └── screenLogic/ │ │ │ └── RotatePdfToolComponent.kt │ │ ├── signature/ │ │ │ ├── SignaturePdfToolContent.kt │ │ │ ├── components/ │ │ │ │ ├── SignatureDialog.kt │ │ │ │ ├── SignaturePreview.kt │ │ │ │ └── SignatureSelector.kt │ │ │ └── screenLogic/ │ │ │ └── SignaturePdfToolComponent.kt │ │ ├── split/ │ │ │ ├── SplitPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── SplitPdfToolComponent.kt │ │ ├── unlock/ │ │ │ ├── UnlockPdfToolContent.kt │ │ │ └── screenLogic/ │ │ │ └── UnlockPdfToolComponent.kt │ │ ├── watermark/ │ │ │ ├── WatermarkPdfToolContent.kt │ │ │ ├── components/ │ │ │ │ └── WatermarkPreview.kt │ │ │ └── screenLogic/ │ │ │ └── WatermarkPdfToolComponent.kt │ │ └── zip_convert/ │ │ ├── ZipConvertPdfToolContent.kt │ │ └── screenLogic/ │ │ └── ZipConvertPdfToolComponent.kt │ ├── pick-color/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── pick_color/ │ │ └── presentation/ │ │ ├── PickColorFromImageContent.kt │ │ ├── components/ │ │ │ ├── PickColorFromImageBottomAppBar.kt │ │ │ ├── PickColorFromImageContentImpl.kt │ │ │ ├── PickColorFromImageSheet.kt │ │ │ └── PickColorFromImageTopAppBar.kt │ │ └── screenLogic/ │ │ └── PickColorFromImageComponent.kt │ ├── quick-tiles/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── quick_tiles/ │ │ ├── screenshot/ │ │ │ ├── Contants.kt │ │ │ ├── ScreenshotLauncher.kt │ │ │ ├── ScreenshotMaker.kt │ │ │ └── ScreenshotService.kt │ │ └── tiles/ │ │ ├── QuickTile.kt │ │ ├── TileAction.kt │ │ └── Tiles.kt │ ├── recognize-text/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── recognize/ │ │ └── text/ │ │ ├── data/ │ │ │ └── AndroidImageTextReader.kt │ │ ├── di/ │ │ │ └── RecognizeTextModule.kt │ │ ├── domain/ │ │ │ ├── DownloadData.kt │ │ │ ├── ImageTextReader.kt │ │ │ ├── OCRLanguage.kt │ │ │ ├── OcrEngineMode.kt │ │ │ ├── RecognitionData.kt │ │ │ ├── RecognitionType.kt │ │ │ ├── SegmentationMode.kt │ │ │ ├── TessConstants.kt │ │ │ ├── TessParams.kt │ │ │ └── TextRecognitionResult.kt │ │ └── presentation/ │ │ ├── RecognizeTextContent.kt │ │ ├── components/ │ │ │ ├── DeleteLanguageDialog.kt │ │ │ ├── DownloadLanguageDialog.kt │ │ │ ├── DownloadedLanguageItem.kt │ │ │ ├── FillableButton.kt │ │ │ ├── FilterSelectionBar.kt │ │ │ ├── ModelTypeSelector.kt │ │ │ ├── OCRLanguageColumnForSearch.kt │ │ │ ├── OCRLanguagesColumn.kt │ │ │ ├── OCRTextPreviewItem.kt │ │ │ ├── OcrEngineModeSelector.kt │ │ │ ├── RecognitionTypeSelector.kt │ │ │ ├── RecognizeLanguageSelector.kt │ │ │ ├── RecognizeLanguageSelectorSheetContent.kt │ │ │ ├── RecognizeTextButtons.kt │ │ │ ├── RecognizeTextControls.kt │ │ │ ├── RecognizeTextDownloadDataDialog.kt │ │ │ ├── RecognizeTextNoDataControls.kt │ │ │ ├── TessParamsSelector.kt │ │ │ └── UiDownloadData.kt │ │ └── screenLogic/ │ │ └── RecognizeTextComponent.kt │ ├── resize-convert/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── resize_convert/ │ │ └── presentation/ │ │ ├── ResizeAndConvertContent.kt │ │ └── screenLogic/ │ │ └── ResizeAndConvertComponent.kt │ ├── root/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── root/ │ │ └── presentation/ │ │ ├── RootContent.kt │ │ ├── components/ │ │ │ ├── RootDialogs.kt │ │ │ ├── ScreenSelector.kt │ │ │ ├── SettingsBackdropWrapper.kt │ │ │ ├── SettingsOpenButton.kt │ │ │ ├── dialogs/ │ │ │ │ ├── AppExitDialog.kt │ │ │ │ ├── EditPresetsSheet.kt │ │ │ │ ├── FirstLaunchSetupDialog.kt │ │ │ │ ├── GithubReviewDialog.kt │ │ │ │ ├── PermissionDialog.kt │ │ │ │ └── TelegramGroupDialog.kt │ │ │ ├── navigation/ │ │ │ │ ├── ChildProvider.kt │ │ │ │ └── NavigationChild.kt │ │ │ └── utils/ │ │ │ ├── BackEventObserver.kt │ │ │ ├── ResetThemeOnGoBack.kt │ │ │ ├── ScreenBasedMaxBrightnessEnforcement.kt │ │ │ ├── SettingsUtils.kt │ │ │ └── SuccessRestoreBackupToastHandler.kt │ │ └── screenLogic/ │ │ └── RootComponent.kt │ ├── scan-qr-code/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── scan_qr_code/ │ │ ├── data/ │ │ │ └── AndroidImageBarcodeReader.kt │ │ ├── di/ │ │ │ └── ScanQrCodeModule.kt │ │ ├── domain/ │ │ │ └── ImageBarcodeReader.kt │ │ └── presentation/ │ │ ├── ScanQrCodeContent.kt │ │ ├── components/ │ │ │ ├── QrCodePreview.kt │ │ │ ├── QrInfo.kt │ │ │ ├── QrInfoBuilder.kt │ │ │ ├── QrParamsSelector.kt │ │ │ ├── QrPreviewParams.kt │ │ │ ├── QrTypeEditSheet.kt │ │ │ ├── QrTypeInfoItem.kt │ │ │ ├── QrTypeUtils.kt │ │ │ ├── ScanQrCodeControls.kt │ │ │ └── editor/ │ │ │ ├── QrCalendarEditField.kt │ │ │ ├── QrContactEditField.kt │ │ │ ├── QrEditField.kt │ │ │ ├── QrEmailEditField.kt │ │ │ ├── QrGeoEditField.kt │ │ │ ├── QrPhoneEditField.kt │ │ │ ├── QrSmsEditField.kt │ │ │ └── QrWifiEditField.kt │ │ └── screenLogic/ │ │ └── ScanQrCodeComponent.kt │ ├── settings/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── settings/ │ │ ├── data/ │ │ │ ├── AndroidSettingsManager.kt │ │ │ ├── ContextUtils.kt │ │ │ └── keys/ │ │ │ ├── MapToSettingsState.kt │ │ │ └── SettingKeys.kt │ │ ├── di/ │ │ │ └── SettingsModule.kt │ │ └── presentation/ │ │ ├── SettingsContent.kt │ │ ├── components/ │ │ │ ├── AddFileSizeSettingItem.kt │ │ │ ├── AddImageScaleModeToFilenameSettingItem.kt │ │ │ ├── AddOriginalFilenameSettingItem.kt │ │ │ ├── AddPresetToFilenameSettingItem.kt │ │ │ ├── AddTimestampToFilenameSettingItem.kt │ │ │ ├── AllowAutoClipboardPasteSettingItem.kt │ │ │ ├── AllowBetasSettingItem.kt │ │ │ ├── AllowImageMonetSettingItem.kt │ │ │ ├── AllowSkipIfLargerSettingItem.kt │ │ │ ├── AmoledModeSettingItem.kt │ │ │ ├── AnalyticsSettingItem.kt │ │ │ ├── AppBarShadowsSettingItem.kt │ │ │ ├── AuthorSettingItem.kt │ │ │ ├── AutoCacheClearSettingItem.kt │ │ │ ├── AutoCheckUpdatesSettingItem.kt │ │ │ ├── AutoPinClipboardOnlyClipSettingItem.kt │ │ │ ├── AutoPinClipboardSettingItem.kt │ │ │ ├── BackupSettingItem.kt │ │ │ ├── BorderThicknessSettingItem.kt │ │ │ ├── BrightnessEnforcementSettingItem.kt │ │ │ ├── ButtonShadowsSettingItem.kt │ │ │ ├── CanEnterPresetsByTextFieldSettingItem.kt │ │ │ ├── CenterAlignDialogButtonsSettingItem.kt │ │ │ ├── ChangeFontSettingItem.kt │ │ │ ├── ChangeLanguageSettingItem.kt │ │ │ ├── CheckUpdatesButtonSettingItem.kt │ │ │ ├── ChecksumAsFilenameSettingItem.kt │ │ │ ├── ClearCacheSettingItem.kt │ │ │ ├── ColorBlindSchemeSettingItem.kt │ │ │ ├── ColorSchemeSettingItem.kt │ │ │ ├── ConfettiHarmonizationColorSettingItem.kt │ │ │ ├── ConfettiHarmonizationLevelSettingItem.kt │ │ │ ├── ConfettiSettingItem.kt │ │ │ ├── ConfettiTypeSettingItem.kt │ │ │ ├── ContainerShadowsSettingItem.kt │ │ │ ├── CornersSizeSettingItem.kt │ │ │ ├── CrashlyticsSettingItem.kt │ │ │ ├── CurrentVersionCodeSettingItem.kt │ │ │ ├── DefaultColorSpaceSettingItem.kt │ │ │ ├── DefaultDrawColorSettingItem.kt │ │ │ ├── DefaultDrawLineWidthSettingItem.kt │ │ │ ├── DefaultDrawPathModeSettingItem.kt │ │ │ ├── DefaultImageFormatSettingItem.kt │ │ │ ├── DefaultQualitySettingItem.kt │ │ │ ├── DefaultResizeTypeSettingItem.kt │ │ │ ├── DefaultScaleModeSettingItem.kt │ │ │ ├── DonateSettingItem.kt │ │ │ ├── DragHandleWidthSettingItem.kt │ │ │ ├── DynamicColorsSettingItem.kt │ │ │ ├── EmojiSettingItem.kt │ │ │ ├── EmojisCountSettingItem.kt │ │ │ ├── EnableBackgroundColorForAlphaFormatsSettingItem.kt │ │ │ ├── EnableLauncherModeSettingItem.kt │ │ │ ├── EnableLinksPreviewSettingItem.kt │ │ │ ├── EnableToolExitConfirmationSettingItem.kt │ │ │ ├── ExifWidgetInitialStateSettingItem.kt │ │ │ ├── FabAlignmentSettingItem.kt │ │ │ ├── FabShadowsSettingItem.kt │ │ │ ├── FastSettingsSideSettingItem.kt │ │ │ ├── FilenamePatternSettingItem.kt │ │ │ ├── FilenamePrefixSettingItem.kt │ │ │ ├── FilenameSuffixSettingItem.kt │ │ │ ├── FlingTypeSettingItem.kt │ │ │ ├── FontScaleSettingItem.kt │ │ │ ├── FreeSoftwarePartnerSettingItem.kt │ │ │ ├── GeneratePreviewsSettingItem.kt │ │ │ ├── GroupOptionsSettingItem.kt │ │ │ ├── HelpTranslateSettingItem.kt │ │ │ ├── IconShapeSettingItem.kt │ │ │ ├── ImagePickerModeSettingItemGroup.kt │ │ │ ├── IssueTrackerSettingItem.kt │ │ │ ├── KeepDateTimeSettingItem.kt │ │ │ ├── LockDrawOrientationSettingItem.kt │ │ │ ├── MagnifierSettingItem.kt │ │ │ ├── MainScreenTitleSettingItem.kt │ │ │ ├── NightModeSettingItemGroup.kt │ │ │ ├── OneTimeSaveLocationSettingItem.kt │ │ │ ├── OpenEditInsteadOfPreviewSettingItem.kt │ │ │ ├── OpenSourceLicensesSettingItem.kt │ │ │ ├── OverwriteFilesSettingItem.kt │ │ │ ├── PresetsSettingItem.kt │ │ │ ├── RandomizeFilenameSettingItem.kt │ │ │ ├── ReplaceSequenceNumberSettingItem.kt │ │ │ ├── ResetSettingsSettingItem.kt │ │ │ ├── RestoreSettingItem.kt │ │ │ ├── SavingFolderSettingItemGroup.kt │ │ │ ├── ScreenOrderSettingItem.kt │ │ │ ├── ScreenSearchSettingItem.kt │ │ │ ├── SearchableSettingItem.kt │ │ │ ├── SecureModeSettingItem.kt │ │ │ ├── SendLogsSettingItem.kt │ │ │ ├── SettingGroupItem.kt │ │ │ ├── SettingItem.kt │ │ │ ├── ShapeTypeSettingItem.kt │ │ │ ├── ShowSettingsInLandscapeSettingItem.kt │ │ │ ├── ShowSystemBarsBySwipeSettingItem.kt │ │ │ ├── SkipImagePickingSettingItem.kt │ │ │ ├── SliderShadowsSettingItem.kt │ │ │ ├── SliderTypeSettingItem.kt │ │ │ ├── SnowfallModeSettingItem.kt │ │ │ ├── SourceCodeSettingItem.kt │ │ │ ├── SwitchShadowsSettingItem.kt │ │ │ ├── SwitchTypeSettingItem.kt │ │ │ ├── SystemBarsVisibilitySettingItem.kt │ │ │ ├── TelegramChannelSettingItem.kt │ │ │ ├── TelegramGroupSettingItem.kt │ │ │ ├── ToolsHiddenForShareSettingItem.kt │ │ │ ├── UseCompactSelectorsSettingItem.kt │ │ │ ├── UseFormattedFilenameTimestampSettingItem.kt │ │ │ ├── UseFullscreenSettingsSettingItem.kt │ │ │ ├── UseRandomEmojisSettingItem.kt │ │ │ ├── VibrationStrengthSettingItem.kt │ │ │ └── additional/ │ │ │ ├── AuthorLinksSheet.kt │ │ │ ├── DonateContainerContent.kt │ │ │ ├── DonateDialog.kt │ │ │ ├── DonateSheet.kt │ │ │ ├── FabPreview.kt │ │ │ ├── FontItem.kt │ │ │ └── PickFontFamilySheet.kt │ │ └── screenLogic/ │ │ └── SettingsComponent.kt │ ├── single-edit/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── single_edit/ │ │ └── presentation/ │ │ ├── SingleEditContent.kt │ │ ├── components/ │ │ │ ├── CropEditOption.kt │ │ │ ├── DrawEditOption.kt │ │ │ ├── EraseBackgroundEditOption.kt │ │ │ ├── FilterEditOption.kt │ │ │ ├── FullscreenEditOption.kt │ │ │ └── ToneCurvesEditOption.kt │ │ └── screenLogic/ │ │ └── SingleEditComponent.kt │ ├── svg-maker/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── svg_maker/ │ │ ├── data/ │ │ │ └── AndroidSvgManager.kt │ │ ├── di/ │ │ │ └── SvgMakerModule.kt │ │ ├── domain/ │ │ │ ├── SvgManager.kt │ │ │ └── SvgParams.kt │ │ └── presentation/ │ │ ├── SvgMakerContent.kt │ │ ├── components/ │ │ │ └── SvgParamsSelector.kt │ │ └── screenLogic/ │ │ └── SvgMakerComponent.kt │ ├── wallpapers-export/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── wallpapers_export/ │ │ ├── data/ │ │ │ └── AndroidWallpapersProvider.kt │ │ ├── di/ │ │ │ └── WallpapersExportModule.kt │ │ ├── domain/ │ │ │ ├── WallpapersProvider.kt │ │ │ └── model/ │ │ │ ├── Permission.kt │ │ │ ├── Wallpaper.kt │ │ │ └── WallpapersResult.kt │ │ └── presentation/ │ │ ├── WallpapersExportContent.kt │ │ ├── components/ │ │ │ ├── WallpapersActionButtons.kt │ │ │ ├── WallpapersControls.kt │ │ │ └── WallpapersPreview.kt │ │ └── screenLogic/ │ │ └── WallpapersExportComponent.kt │ ├── watermarking/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── watermarking/ │ │ ├── data/ │ │ │ └── AndroidWatermarkApplier.kt │ │ ├── di/ │ │ │ └── WatermarkingModule.kt │ │ ├── domain/ │ │ │ ├── HiddenWatermark.kt │ │ │ ├── WatermarkApplier.kt │ │ │ └── WatermarkParams.kt │ │ └── presentation/ │ │ ├── WatermarkingContent.kt │ │ ├── components/ │ │ │ ├── HiddenWatermarkInfo.kt │ │ │ ├── WatermarkDataSelector.kt │ │ │ ├── WatermarkParamsSelectionGroup.kt │ │ │ ├── WatermarkingTypeSelector.kt │ │ │ └── selectors/ │ │ │ ├── CommonParamsContent.kt │ │ │ ├── DigitalParamsContent.kt │ │ │ ├── ImageParamsContent.kt │ │ │ ├── StampParamsContent.kt │ │ │ └── TextParamsContent.kt │ │ └── screenLogic/ │ │ └── WatermarkingComponent.kt │ ├── webp-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── webp_tools/ │ │ ├── data/ │ │ │ └── AndroidWebpConverter.kt │ │ ├── di/ │ │ │ └── WebpToolsModule.kt │ │ ├── domain/ │ │ │ ├── WebpConverter.kt │ │ │ └── WebpParams.kt │ │ └── presentation/ │ │ ├── WebpToolsContent.kt │ │ ├── components/ │ │ │ └── WebpParamsSelector.kt │ │ └── screenLogic/ │ │ └── WebpToolsComponent.kt │ ├── weight-resize/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── imagetoolbox/ │ │ └── feature/ │ │ └── weight_resize/ │ │ ├── data/ │ │ │ └── AndroidWeightImageScaler.kt │ │ ├── di/ │ │ │ └── WeightResizeModule.kt │ │ ├── domain/ │ │ │ └── WeightImageScaler.kt │ │ └── presentation/ │ │ ├── WeightResizeContent.kt │ │ ├── components/ │ │ │ └── ImageFormatAlert.kt │ │ └── screenLogic/ │ │ └── WeightResizeComponent.kt │ └── zip/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── t8rin/ │ └── imagetoolbox/ │ └── feature/ │ └── zip/ │ ├── data/ │ │ └── AndroidZipManager.kt │ ├── di/ │ │ └── ZipModule.kt │ ├── domain/ │ │ └── ZipManager.kt │ └── presentation/ │ ├── ZipContent.kt │ ├── components/ │ │ └── ZipControls.kt │ └── screenLogic/ │ └── ZipComponent.kt ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── lib/ │ ├── ascii/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── ascii/ │ │ └── ASCIIConverter.kt │ ├── collages/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── collages/ │ │ ├── Collage.kt │ │ ├── CollageType.kt │ │ ├── CollageTypeSelection.kt │ │ ├── frames/ │ │ │ ├── EightFrameImage.kt │ │ │ ├── ExtendedFrameImage.kt │ │ │ ├── FiveFrameImage.kt │ │ │ ├── FourFrameImage.kt │ │ │ ├── NineFrameImage.kt │ │ │ ├── SevenFrameImage.kt │ │ │ ├── SixFrameImage.kt │ │ │ ├── TenFrameImage.kt │ │ │ ├── ThreeFrameImage.kt │ │ │ └── TwoFrameImage.kt │ │ ├── model/ │ │ │ └── CollageLayout.kt │ │ ├── public/ │ │ │ └── CollageConstants.kt │ │ ├── utils/ │ │ │ ├── CollageLayoutFactory.kt │ │ │ ├── GeometryUtils.kt │ │ │ ├── Handle.kt │ │ │ ├── ImageDecoder.kt │ │ │ ├── ParamsManager.kt │ │ │ ├── ParamsManagerBuilder.kt │ │ │ └── PreviewCollageGeneration.kt │ │ └── view/ │ │ ├── FrameImageView.kt │ │ ├── FramePhotoLayout.kt │ │ ├── MultiTouchHandler.kt │ │ └── PhotoItem.kt │ ├── colors/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ └── color_names.json │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── colors/ │ │ ├── ImageColorDetector.kt │ │ ├── ImageColorPaletteState.kt │ │ ├── model/ │ │ │ └── ColorData.kt │ │ ├── parser/ │ │ │ └── ColorNameParser.kt │ │ └── util/ │ │ ├── ColorUtil.kt │ │ ├── HexRegex.kt │ │ ├── HexUtil.kt │ │ ├── HexVisualTransformation.kt │ │ ├── RGBUtil.kt │ │ └── RoundngUtil.kt │ ├── cropper/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── cropper/ │ │ ├── CropModifier.kt │ │ ├── ImageCropper.kt │ │ ├── TouchRegion.kt │ │ ├── crop/ │ │ │ └── CropAgent.kt │ │ ├── draw/ │ │ │ ├── ImageDrawCanvas.kt │ │ │ └── Overlay.kt │ │ ├── image/ │ │ │ ├── ImageScope.kt │ │ │ └── ImageWithConstraints.kt │ │ ├── model/ │ │ │ ├── AspectRatios.kt │ │ │ ├── CropAspectRatio.kt │ │ │ ├── CropData.kt │ │ │ ├── CropOutline.kt │ │ │ ├── CropOutlineContainer.kt │ │ │ ├── CropOutlineProperties.kt │ │ │ └── OutlineType.kt │ │ ├── settings/ │ │ │ ├── CropDefaults.kt │ │ │ ├── CropType.kt │ │ │ └── Paths.kt │ │ ├── state/ │ │ │ ├── CropState.kt │ │ │ ├── CropStateImpl.kt │ │ │ ├── DynamicCropState.kt │ │ │ ├── StaticCropState.kt │ │ │ └── TransformState.kt │ │ ├── util/ │ │ │ ├── DimensionUtil.kt │ │ │ ├── DrawScopeUtils.kt │ │ │ ├── ImageContentScaleUtil.kt │ │ │ ├── OffsetUtil.kt │ │ │ ├── ShapeUtils.kt │ │ │ ├── ZoomLevel.kt │ │ │ └── ZoomUtil.kt │ │ └── widget/ │ │ ├── AspectRatioSlectionCard.kt │ │ ├── CropFrameDisplayCard.kt │ │ └── GridImageLayout.kt │ ├── curves/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── curves/ │ │ ├── ImageCurvesEditor.kt │ │ ├── ImageCurvesEditorColors.kt │ │ ├── ImageCurvesEditorDefaults.kt │ │ ├── ImageCurvesEditorState.kt │ │ ├── utils/ │ │ │ └── Utils.kt │ │ └── view/ │ │ ├── PhotoFilterCurvesControl.kt │ │ └── Rect.kt │ ├── documentscanner/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── websitebeaver/ │ │ │ └── documentscanner/ │ │ │ ├── DocumentScanner.kt │ │ │ ├── DocumentScannerActivity.kt │ │ │ ├── DocumentScannerFileProvider.kt │ │ │ ├── constants/ │ │ │ │ ├── DefaultSetting.kt │ │ │ │ ├── DocumentScannerExtra.kt │ │ │ │ └── ResponseType.kt │ │ │ ├── enums/ │ │ │ │ └── QuadCorner.kt │ │ │ ├── extensions/ │ │ │ │ ├── AppCompatActivity.kt │ │ │ │ ├── Bitmap.kt │ │ │ │ ├── Canvas.kt │ │ │ │ ├── ImageButton.kt │ │ │ │ └── Point.kt │ │ │ ├── models/ │ │ │ │ ├── Document.kt │ │ │ │ ├── Line.kt │ │ │ │ └── Quad.kt │ │ │ ├── ui/ │ │ │ │ ├── CircleButton.kt │ │ │ │ ├── DoneButton.kt │ │ │ │ └── ImageCropView.kt │ │ │ └── utils/ │ │ │ ├── FileUtil.kt │ │ │ └── ImageUtil.kt │ │ └── res/ │ │ ├── animator/ │ │ │ └── button_grow_animation.xml │ │ ├── drawable/ │ │ │ ├── ic_baseline_add_24.xml │ │ │ ├── ic_baseline_arrow_back_24.xml │ │ │ └── ic_baseline_check_24.xml │ │ ├── layout/ │ │ │ └── activity_image_crop.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── integers.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── xml/ │ │ └── file_paths.xml │ ├── dynamic-theme/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── dynamic/ │ │ └── theme/ │ │ ├── ColorBlindUtils.kt │ │ ├── DynamicTheme.kt │ │ ├── PaletteStyle.kt │ │ └── SystemUiController.kt │ ├── gesture/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── gesture/ │ │ ├── AwaitPointerMotionEvent.kt │ │ ├── PointerMotionModifier.kt │ │ └── TransformGesture.kt │ ├── image/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── image/ │ │ ├── ImageScope.kt │ │ ├── ImageWithConstraints.kt │ │ └── util/ │ │ └── ImageContentScaleUtil.kt │ ├── modalsheet/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── modalsheet/ │ │ ├── FullscreenPopup.kt │ │ ├── ModalSheet.kt │ │ ├── ModalSheetLayout.kt │ │ └── SwipeableV2.kt │ ├── neural-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ └── u2netp.onnx │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── neural_tools/ │ │ ├── DownloadProgress.kt │ │ ├── NeuralTool.kt │ │ ├── bgremover/ │ │ │ ├── BgRemover.kt │ │ │ ├── BiRefNetBackgroundRemover.kt │ │ │ ├── BiRefNetTinyBackgroundRemover.kt │ │ │ ├── GenericBackgroundRemover.kt │ │ │ ├── ISNetBackgroundRemover.kt │ │ │ ├── InSPyReNetBackgroundRemover.kt │ │ │ ├── RMBGBackgroundRemover.kt │ │ │ ├── U2NetFullBackgroundRemover.kt │ │ │ └── U2NetPortableBackgroundRemover.kt │ │ └── inpaint/ │ │ └── LaMaProcessor.kt │ ├── opencv-tools/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ └── haarcascade_eye.xml │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── opencv_tools/ │ │ ├── auto_straight/ │ │ │ ├── AutoStraighten.kt │ │ │ └── model/ │ │ │ ├── Corners.kt │ │ │ ├── PointD.kt │ │ │ └── StraightenMode.kt │ │ ├── autocrop/ │ │ │ ├── AutoCropper.kt │ │ │ └── model/ │ │ │ ├── CropEdges.kt │ │ │ ├── CropParameters.kt │ │ │ └── CropSensitivity.kt │ │ ├── color_map/ │ │ │ ├── ColorMap.kt │ │ │ └── model/ │ │ │ └── ColorMapType.kt │ │ ├── document_detector/ │ │ │ └── DocumentDetector.kt │ │ ├── forensics/ │ │ │ └── ImageForensics.kt │ │ ├── free_corners_crop/ │ │ │ ├── FreeCrop.kt │ │ │ ├── compose/ │ │ │ │ └── FreeCornersCropper.kt │ │ │ └── model/ │ │ │ └── Quad.kt │ │ ├── image_comparison/ │ │ │ ├── ImageDiffTool.kt │ │ │ └── model/ │ │ │ └── ComparisonType.kt │ │ ├── image_processing/ │ │ │ └── ImageProcessing.kt │ │ ├── lens_correction/ │ │ │ ├── LensCorrection.kt │ │ │ └── model/ │ │ │ ├── LCException.kt │ │ │ ├── LensProfile.kt │ │ │ └── Sample.kt │ │ ├── moire/ │ │ │ ├── Moire.kt │ │ │ └── model/ │ │ │ └── MoireType.kt │ │ ├── qr_prepare/ │ │ │ └── QrPrepareHelper.kt │ │ ├── red_eye/ │ │ │ └── RedEyeRemover.kt │ │ ├── seam_carving/ │ │ │ └── SeamCarver.kt │ │ ├── spot_heal/ │ │ │ ├── SpotHealer.kt │ │ │ └── model/ │ │ │ └── HealType.kt │ │ └── utils/ │ │ └── OpenCVUtils.kt │ ├── palette/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── palette/ │ │ │ ├── ColorByteFormat.kt │ │ │ ├── ColorGroup.kt │ │ │ ├── ColorSpace.kt │ │ │ ├── ColorType.kt │ │ │ ├── Palette.kt │ │ │ ├── PaletteCoder.kt │ │ │ ├── PaletteCoderException.kt │ │ │ ├── PaletteColor.kt │ │ │ ├── PaletteFormat.kt │ │ │ ├── coders/ │ │ │ │ ├── ACBPaletteCoder.kt │ │ │ │ ├── ACOPaletteCoder.kt │ │ │ │ ├── ACTPaletteCoder.kt │ │ │ │ ├── AFPaletteCoder.kt │ │ │ │ ├── ASEPaletteCoder.kt │ │ │ │ ├── AndroidColorsXMLCoder.kt │ │ │ │ ├── AutodeskColorBookCoder.kt │ │ │ │ ├── BasicXMLCoder.kt │ │ │ │ ├── CLFPaletteCoder.kt │ │ │ │ ├── CPLPaletteCoder.kt │ │ │ │ ├── CSVPaletteCoder.kt │ │ │ │ ├── CorelDraw3PaletteCoder.kt │ │ │ │ ├── CorelPainterCoder.kt │ │ │ │ ├── CorelXMLPaletteCoder.kt │ │ │ │ ├── DCPPaletteCoder.kt │ │ │ │ ├── GIMPPaletteCoder.kt │ │ │ │ ├── HEXPaletteCoder.kt │ │ │ │ ├── HPLPaletteCoder.kt │ │ │ │ ├── ImagePaletteCoder.kt │ │ │ │ ├── JCWPaletteCoder.kt │ │ │ │ ├── JSONPaletteCoder.kt │ │ │ │ ├── KOfficePaletteCoder.kt │ │ │ │ ├── KRITAPaletteCoder.kt │ │ │ │ ├── KotlinPaletteCoder.kt │ │ │ │ ├── OpenOfficePaletteCoder.kt │ │ │ │ ├── PaintNETPaletteCoder.kt │ │ │ │ ├── PaintShopProPaletteCoder.kt │ │ │ │ ├── PaletteFormatCoder.kt │ │ │ │ ├── ProcreateSwatchesCoder.kt │ │ │ │ ├── RGBAPaletteCoder.kt │ │ │ │ ├── RGBPaletteCoder.kt │ │ │ │ ├── RIFFPaletteCoder.kt │ │ │ │ ├── SKPPaletteCoder.kt │ │ │ │ ├── SVGPaletteCoder.kt │ │ │ │ ├── ScribusXMLPaletteCoder.kt │ │ │ │ ├── SimplePaletteCoder.kt │ │ │ │ ├── SkencilPaletteCoder.kt │ │ │ │ ├── SketchPaletteCoder.kt │ │ │ │ ├── SwatchbookerCoder.kt │ │ │ │ ├── SwiftPaletteCoder.kt │ │ │ │ ├── VGA18BitPaletteCoder.kt │ │ │ │ └── VGA24BitPaletteCoder.kt │ │ │ └── utils/ │ │ │ ├── BytesReader.kt │ │ │ ├── BytesWriter.kt │ │ │ ├── CSVParser.kt │ │ │ ├── HexUtils.kt │ │ │ └── Xml.kt │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── t8rin/ │ │ └── palette/ │ │ └── coders/ │ │ └── PaletteCoderCompatibilityTest.kt │ ├── qrose/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── io/ │ │ └── github/ │ │ └── alexzhirkevich/ │ │ └── qrose/ │ │ ├── CachedPainter.kt │ │ ├── Converters.kt │ │ ├── DelicateQRoseApi.kt │ │ ├── QrCodePainter.kt │ │ ├── QrData.kt │ │ ├── oned/ │ │ │ ├── BarcodeEncoder.kt │ │ │ ├── BarcodePainter.kt │ │ │ ├── BarcodeType.kt │ │ │ ├── CodabarEncoder.kt │ │ │ ├── Code128Encoder.kt │ │ │ ├── Code128Painter.kt │ │ │ ├── Code39Encoder.kt │ │ │ ├── Code93Encoder.kt │ │ │ ├── CodeEAN13Encoder.kt │ │ │ ├── CodeEAN8Encoder.kt │ │ │ ├── CodeITFEncoder.kt │ │ │ ├── CodeUPCAEncoder.kt │ │ │ ├── CodeUPCEEncoder.kt │ │ │ └── UpcEanUtils.kt │ │ ├── options/ │ │ │ ├── Neighbors.kt │ │ │ ├── QrBallShape.kt │ │ │ ├── QrBrush.kt │ │ │ ├── QrCodeMatrix.kt │ │ │ ├── QrCodeShape.kt │ │ │ ├── QrColors.kt │ │ │ ├── QrErrorCorrectionLevel.kt │ │ │ ├── QrFrameShape.kt │ │ │ ├── QrLogo.kt │ │ │ ├── QrLogoPadding.kt │ │ │ ├── QrLogoShape.kt │ │ │ ├── QrOptions.kt │ │ │ ├── QrPixelShape.kt │ │ │ ├── QrShapeModifier.kt │ │ │ ├── QrShapeModifiers.kt │ │ │ ├── QrShapes.kt │ │ │ ├── Util.kt │ │ │ └── dsl/ │ │ │ ├── InternalQrColorsBuilderScope.kt │ │ │ ├── InternalQrLogoBuilderScope.kt │ │ │ ├── InternalQrOptionsBuilderScope.kt │ │ │ ├── InternalQrShapesBuilderScope.kt │ │ │ ├── QrColorsBuilderScope.kt │ │ │ ├── QrLogoBuilderScope.kt │ │ │ ├── QrOptionsBuilderScope.kt │ │ │ └── QrShapesBuilderScope.kt │ │ └── qrcode/ │ │ ├── QRCode.kt │ │ ├── QRCodeEnums.kt │ │ └── internals/ │ │ ├── BitBuffer.kt │ │ ├── ErrorMessage.kt │ │ ├── Polynomial.kt │ │ ├── QRCodeSetup.kt │ │ ├── QRCodeSquare.kt │ │ ├── QRData.kt │ │ ├── QRMath.kt │ │ ├── QRUtil.kt │ │ └── RSBlock.kt │ ├── snowfall/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── t8rin/ │ │ │ └── snowfall/ │ │ │ ├── Constants.kt │ │ │ ├── SnowAnimState.kt │ │ │ ├── Snowfall.kt │ │ │ ├── Snowflake.kt │ │ │ ├── Utils.kt │ │ │ └── types/ │ │ │ ├── AnimType.kt │ │ │ └── FlakeType.kt │ │ └── res/ │ │ └── drawable/ │ │ ├── ic_flake_1.xml │ │ ├── ic_flake_10.xml │ │ ├── ic_flake_2.xml │ │ ├── ic_flake_3.xml │ │ ├── ic_flake_4.xml │ │ ├── ic_flake_5.xml │ │ ├── ic_flake_6.xml │ │ ├── ic_flake_7.xml │ │ ├── ic_flake_8.xml │ │ └── ic_flake_9.xml │ └── zoomable/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── net/ │ └── engawapg/ │ └── lib/ │ └── zoomable/ │ ├── ZoomState.kt │ ├── Zoomable.kt │ └── ZoomableDefaults.kt └── settings.gradle.kts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username buy_me_a_coffee: # Replace with a single Buy Me a Coffee username thanks_dev: # Replace with a single thanks.dev username custom: ['https://boosty.to/t8rin'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug Report description: Create a report to help us improve labels: ["bug"] body: - type: textarea id: description attributes: label: Describe the bug description: A clear and concise description of what the bug is. placeholder: Tell us what happened... validations: required: true - type: textarea id: reproduce attributes: label: Steps to reproduce description: Steps to reproduce the behavior placeholder: | 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error validations: required: true - type: textarea id: expected attributes: label: Expected behavior description: A clear and concise description of what you expected to happen. placeholder: Tell us what should have happened... validations: required: true - type: textarea id: screenshots attributes: label: Screenshots description: If applicable, add screenshots to help explain your problem. placeholder: You can drag and drop images here... - type: input id: device attributes: label: Device placeholder: e.g., Samsung Galaxy S23 validations: required: true - type: input id: os attributes: label: OS placeholder: e.g., Android 14 validations: required: true - type: input id: locale attributes: label: Locale placeholder: e.g., en-US, ru-RU - type: input id: version attributes: label: App Version placeholder: e.g., 1.2.3 validations: required: true - type: dropdown id: variant attributes: label: FOSS or Market description: Which variant of the app are you using? options: - FOSS - Market (Google Play) validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature Request description: Suggest an idea for this project labels: ["enhancement"] body: - type: textarea id: problem attributes: label: Is your feature request related to a problem? Please describe. placeholder: A clear and concise description of what the problem is... validations: required: false - type: textarea id: solution attributes: label: Describe the solution you'd like placeholder: A clear and concise description of what you want to happen... validations: required: true - type: textarea id: alternatives attributes: label: Describe alternatives you've considered placeholder: A clear and concise description of any alternative solutions or features you've considered... validations: required: false ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gradle" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" ================================================ FILE: .github/workflows/android.yml ================================================ name: Android CI on: workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - name: Set Swap Space uses: pierotofy/set-swap-space@master with: swap-size-gb: 10 - uses: actions/checkout@v4 - name: set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'adopt' java-package: 'jdk' cache: gradle - name: Set up Keystore run: | sudo apt update -y || true sudo apt install -y --no-install-recommends coreutils mkdir -p $RUNNER_TEMP/keystores echo "${{ secrets.SIGNING_KEY }}" | base64 --decode > $RUNNER_TEMP/keystores/keystore.jks - name: Build FOSS APKs run: bash ./gradlew assembleFossRelease - name: Sign FOSS APKs run: | ANDROID_SDK_PATH=$ANDROID_HOME/build-tools/35.0.0/apksigner for apk in app/build/outputs/apk/foss/release/*.apk; do $ANDROID_SDK_PATH sign \ --ks $RUNNER_TEMP/keystores/keystore.jks \ --ks-key-alias ${{ secrets.ALIAS }} \ --ks-pass pass:${{ secrets.KEY_STORE_PASS }} \ --key-pass pass:${{ secrets.KEY_STORE_PASS }} \ --out "$apk" \ "$apk" done - name: Upload FOSS APKs uses: actions/upload-artifact@v4 with: name: Signed FOSS APKs path: app/build/outputs/apk/foss/release/*.apk - name: Delete FOSS APKs run: rm -rf app/build/outputs/apk/foss - name: Build Market APKs run: bash ./gradlew assembleMarketRelease - name: Sign Market APKs run: | ANDROID_SDK_PATH=$ANDROID_HOME/build-tools/35.0.0/apksigner for apk in app/build/outputs/apk/market/release/*.apk; do $ANDROID_SDK_PATH sign \ --ks $RUNNER_TEMP/keystores/keystore.jks \ --ks-key-alias ${{ secrets.ALIAS }} \ --ks-pass pass:${{ secrets.KEY_STORE_PASS }} \ --key-pass pass:${{ secrets.KEY_STORE_PASS }} \ --out "$apk" \ "$apk" done - name: Upload Market APKs uses: actions/upload-artifact@v4 with: name: Signed Market APKs path: app/build/outputs/apk/market/release/*.apk - name: Delete Market APKs run: rm -rf app/build/outputs/apk/market ================================================ FILE: .github/workflows/android_foss.yml ================================================ name: Android CI FOSS on: workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - name: Set Swap Space uses: pierotofy/set-swap-space@master with: swap-size-gb: 10 - uses: actions/checkout@v4 - name: set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'adopt' java-package: 'jdk' cache: gradle - name: Set up Keystore run: | sudo apt update -y || true sudo apt install -y --no-install-recommends coreutils mkdir -p $RUNNER_TEMP/keystores echo "${{ secrets.SIGNING_KEY }}" | base64 --decode > $RUNNER_TEMP/keystores/keystore.jks - name: Build FOSS APKs run: bash ./gradlew assembleFossRelease - name: Sign FOSS APKs run: | ANDROID_SDK_PATH=$ANDROID_HOME/build-tools/35.0.0/apksigner for apk in app/build/outputs/apk/foss/release/*.apk; do $ANDROID_SDK_PATH sign \ --ks $RUNNER_TEMP/keystores/keystore.jks \ --ks-key-alias ${{ secrets.ALIAS }} \ --ks-pass pass:${{ secrets.KEY_STORE_PASS }} \ --key-pass pass:${{ secrets.KEY_STORE_PASS }} \ --out "$apk" \ "$apk" done - name: Upload FOSS APKs uses: actions/upload-artifact@v4 with: name: Signed FOSS APKs path: app/build/outputs/apk/foss/release/*.apk - name: Delete FOSS APKs run: rm -rf app/build/outputs/apk/foss ================================================ FILE: .github/workflows/android_market.yml ================================================ name: Android CI Market on: workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - name: Set Swap Space uses: pierotofy/set-swap-space@master with: swap-size-gb: 10 - uses: actions/checkout@v4 - name: set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'adopt' java-package: 'jdk' cache: gradle - name: Set up Keystore run: | sudo apt update -y || true sudo apt install -y --no-install-recommends coreutils mkdir -p $RUNNER_TEMP/keystores echo "${{ secrets.SIGNING_KEY }}" | base64 --decode > $RUNNER_TEMP/keystores/keystore.jks - name: Build Market APKs run: bash ./gradlew assembleMarketRelease - name: Sign Market APKs run: | ANDROID_SDK_PATH=$ANDROID_HOME/build-tools/35.0.0/apksigner for apk in app/build/outputs/apk/market/release/*.apk; do $ANDROID_SDK_PATH sign \ --ks $RUNNER_TEMP/keystores/keystore.jks \ --ks-key-alias ${{ secrets.ALIAS }} \ --ks-pass pass:${{ secrets.KEY_STORE_PASS }} \ --key-pass pass:${{ secrets.KEY_STORE_PASS }} \ --out "$apk" \ "$apk" done - name: Upload Market APKs uses: actions/upload-artifact@v4 with: name: Signed Market APKs path: app/build/outputs/apk/market/release/*.apk - name: Delete Market APKs run: rm -rf app/build/outputs/apk/market ================================================ FILE: .github/workflows/tb_release.yml ================================================ name: Create Release on: workflow_dispatch: #on: # push: # tags: # - '*' jobs: build_and_release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'adopt' cache: gradle - name: Set version variable run: echo "GITHUB_REF_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV - name: Assemble release env: VERSION_NAME: ${{ env.GITHUB_REF_NAME }} run: bash ./gradlew assembleRelease - uses: iota9star/sign-android-release@v1.0.5 name: Sign FOSS APK # ID used to access action output id: sign_app_foss with: releaseDirectory: app/build/outputs/apk/foss/release fileRex: .*apk signingKeyBase64: ${{ secrets.SIGNING_KEY }} alias: ${{ secrets.ALIAS }} keyStorePassword: ${{ secrets.KEY_STORE_PASS }} keyPassword: ${{ secrets.KEY_STORE_PASS }} env: BUILD_TOOLS_VERSION: "34.0.0" - name: Rename foss file stage run: | ls -la app/build/outputs/apk/market/release mv "app/build/outputs/apk/foss/release/image-toolbox-$GITHUB_REF_NAME-foss-arm64-v8a-release-unsigned-signed.apk" "image-toolbox-$GITHUB_REF_NAME-foss-arm64-v8a.apk" mv "app/build/outputs/apk/foss/release/image-toolbox-$GITHUB_REF_NAME-foss-universal-release-unsigned-signed.apk" "image-toolbox-$GITHUB_REF_NAME-foss-universal.apk" mv "app/build/outputs/apk/foss/release/image-toolbox-$GITHUB_REF_NAME-foss-armeabi-v7a-release-unsigned-signed.apk" "image-toolbox-$GITHUB_REF_NAME-foss-armeabi-v7a.apk" mv "app/build/outputs/apk/foss/release/image-toolbox-$GITHUB_REF_NAME-foss-x86_64-release-unsigned-signed.apk" "image-toolbox-$GITHUB_REF_NAME-foss-x86_64.apk" - uses: iota9star/sign-android-release@v1.0.5 name: Sign Market APK # ID used to access action output id: sign_app_market with: releaseDirectory: app/build/outputs/apk/market/release fileRex: .*apk signingKeyBase64: ${{ secrets.SIGNING_KEY }} alias: ${{ secrets.ALIAS }} keyStorePassword: ${{ secrets.KEY_STORE_PASS }} keyPassword: ${{ secrets.KEY_STORE_PASS }} env: BUILD_TOOLS_VERSION: "34.0.0" - name: Rename market file stage env: VERSION_NAME: ${{ env.GITHUB_REF_NAME }} run: | ls -la app/build/outputs/apk/market/release mv "app/build/outputs/apk/market/release/image-toolbox-$VERSION_NAME-market-arm64-v8a-release-unsigned-signed.apk" "image-toolbox-$VERSION_NAME-arm64-v8a.apk" mv "app/build/outputs/apk/market/release/image-toolbox-$VERSION_NAME-market-universal-release-unsigned-signed.apk" "image-toolbox-$VERSION_NAME-universal.apk" mv "app/build/outputs/apk/market/release/image-toolbox-$VERSION_NAME-market-armeabi-v7a-release-unsigned-signed.apk" "image-toolbox-$VERSION_NAME-armeabi-v7a.apk" mv "app/build/outputs/apk/market/release/image-toolbox-$VERSION_NAME-market-x86_64-release-unsigned-signed.apk" "image-toolbox-$VERSION_NAME-x86_64.apk" - uses: actions/upload-artifact@v4 id: signed-market-apk with: name: Signed apks Market path: "*.apk" create_release: runs-on: ubuntu-latest permissions: contents: write needs: - build_and_release steps: - name: Checkout code uses: actions/checkout@v4 - name: Download All Artifacts uses: actions/download-artifact@v4 with: merge-multiple: true - name: Display all downloaded files run: ls -la - name: Set version variable run: echo "GITHUB_REF_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV - name: Set Pre-release flag run: | if [[ "$GITHUB_REF_NAME" == *-* ]]; then # If GITHUB_REF_NAME contains a hyphen, set GITHUB_OTHER_ENV to true echo "PRE_RELEASE_FLAG=true" >> $GITHUB_ENV else # If GITHUB_REF_NAME does not contain a hyphen, set GITHUB_OTHER_ENV to false echo "PRE_RELEASE_FLAG=false" >> $GITHUB_ENV fi - uses: ncipollo/release-action@v1 with: artifacts: "*.apk" prerelease: ${{ env.PRE_RELEASE_FLAG }} ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties .DS_Store /build /app/release /captures .externalNativeBuild .cxx /.idea /libs/nQuant/build /.kotlin/errors/ /.kotlin ================================================ FILE: ARCHITECTURE.md ================================================ # 📐 Modules Graph ```mermaid %%{ init: { "theme": "base", "themeVariables": { "mainBkg": "#121418", "primaryColor": "#1b3a1b", "primaryTextColor": "#e0e3de", "primaryBorderColor": "#76c893", "nodeBorder": "#76c893", "lineColor": "#76c893", "secondaryColor": "#1f2721", "tertiaryColor": "#232c26", "clusterBkg": "#182018", "clusterBorder": "#4caf50", "nodeTextColor": "#e0e3de", "edgeLabelBackground": "#111316", "edgeLabelColor": "#cfe9de", "fontSize": "28px", "fontFamily": "JetBrains Mono, Inter, system-ui" } } }%% graph LR subgraph :core :core:data("data") :core:ui("ui") :core:domain("domain") :core:resources("resources") :core:settings("settings") :core:di("di") :core:crash("crash") :core:utils("utils") :core:filters("filters") :core:ksp("ksp") end subgraph :feature :feature:root("root") :feature:main("main") :feature:load-net-image("load-net-image") :feature:crop("crop") :feature:limits-resize("limits-resize") :feature:cipher("cipher") :feature:image-preview("image-preview") :feature:weight-resize("weight-resize") :feature:compare("compare") :feature:delete-exif("delete-exif") :feature:palette-tools("palette-tools") :feature:resize-convert("resize-convert") :feature:pdf-tools("pdf-tools") :feature:single-edit("single-edit") :feature:erase-background("erase-background") :feature:draw("draw") :feature:filters("filters") :feature:image-stitch("image-stitch") :feature:pick-color("pick-color") :feature:recognize-text("recognize-text") :feature:gradient-maker("gradient-maker") :feature:watermarking("watermarking") :feature:gif-tools("gif-tools") :feature:apng-tools("apng-tools") :feature:zip("zip") :feature:jxl-tools("jxl-tools") :feature:settings("settings") :feature:easter-egg("easter-egg") :feature:svg-maker("svg-maker") :feature:format-conversion("format-conversion") :feature:document-scanner("document-scanner") :feature:scan-qr-code("scan-qr-code") :feature:image-stacking("image-stacking") :feature:image-splitting("image-splitting") :feature:color-tools("color-tools") :feature:webp-tools("webp-tools") :feature:noise-generation("noise-generation") :feature:collage-maker("collage-maker") :feature:libraries-info("libraries-info") :feature:markup-layers("markup-layers") :feature:base64-tools("base64-tools") :feature:checksum-tools("checksum-tools") :feature:mesh-gradients("mesh-gradients") :feature:edit-exif("edit-exif") :feature:image-cutting("image-cutting") :feature:audio-cover-extractor("audio-cover-extractor") :feature:library-details("library-details") :feature:wallpapers-export("wallpapers-export") :feature:ascii-art("ascii-art") :feature:ai-tools("ai-tools") :feature:media-picker("media-picker") :feature:quick-tiles("quick-tiles") end :feature:root --> :core:data :feature:root --> :core:ui :feature:root --> :core:domain :feature:root --> :core:resources :feature:root --> :core:settings :feature:root --> :core:di :feature:root --> :core:crash :feature:root --> :feature:main :feature:root --> :feature:load-net-image :feature:root --> :feature:crop :feature:root --> :feature:limits-resize :feature:root --> :feature:cipher :feature:root --> :feature:image-preview :feature:root --> :feature:weight-resize :feature:root --> :feature:compare :feature:root --> :feature:delete-exif :feature:root --> :feature:palette-tools :feature:root --> :feature:resize-convert :feature:root --> :feature:pdf-tools :feature:root --> :feature:single-edit :feature:root --> :feature:erase-background :feature:root --> :feature:draw :feature:root --> :feature:filters :feature:root --> :feature:image-stitch :feature:root --> :feature:pick-color :feature:root --> :feature:recognize-text :feature:root --> :feature:gradient-maker :feature:root --> :feature:watermarking :feature:root --> :feature:gif-tools :feature:root --> :feature:apng-tools :feature:root --> :feature:zip :feature:root --> :feature:jxl-tools :feature:root --> :feature:settings :feature:root --> :feature:easter-egg :feature:root --> :feature:svg-maker :feature:root --> :feature:format-conversion :feature:root --> :feature:document-scanner :feature:root --> :feature:scan-qr-code :feature:root --> :feature:image-stacking :feature:root --> :feature:image-splitting :feature:root --> :feature:color-tools :feature:root --> :feature:webp-tools :feature:root --> :feature:noise-generation :feature:root --> :feature:collage-maker :feature:root --> :feature:libraries-info :feature:root --> :feature:markup-layers :feature:root --> :feature:base64-tools :feature:root --> :feature:checksum-tools :feature:root --> :feature:mesh-gradients :feature:root --> :feature:edit-exif :feature:root --> :feature:image-cutting :feature:root --> :feature:audio-cover-extractor :feature:root --> :feature:library-details :feature:root --> :feature:wallpapers-export :feature:root --> :feature:ascii-art :feature:root --> :feature:ai-tools :feature:erase-background --> :core:data :feature:erase-background --> :core:ui :feature:erase-background --> :core:domain :feature:erase-background --> :core:resources :feature:erase-background --> :core:settings :feature:erase-background --> :core:di :feature:erase-background --> :core:crash :feature:erase-background --> :feature:draw :feature:edit-exif --> :core:data :feature:edit-exif --> :core:ui :feature:edit-exif --> :core:domain :feature:edit-exif --> :core:resources :feature:edit-exif --> :core:settings :feature:edit-exif --> :core:di :feature:edit-exif --> :core:crash :feature:limits-resize --> :core:data :feature:limits-resize --> :core:ui :feature:limits-resize --> :core:domain :feature:limits-resize --> :core:resources :feature:limits-resize --> :core:settings :feature:limits-resize --> :core:di :feature:limits-resize --> :core:crash :feature:jxl-tools --> :core:data :feature:jxl-tools --> :core:ui :feature:jxl-tools --> :core:domain :feature:jxl-tools --> :core:resources :feature:jxl-tools --> :core:settings :feature:jxl-tools --> :core:di :feature:jxl-tools --> :core:crash :feature:libraries-info --> :core:data :feature:libraries-info --> :core:ui :feature:libraries-info --> :core:domain :feature:libraries-info --> :core:resources :feature:libraries-info --> :core:settings :feature:libraries-info --> :core:di :feature:libraries-info --> :core:crash :feature:gif-tools --> :core:data :feature:gif-tools --> :core:ui :feature:gif-tools --> :core:domain :feature:gif-tools --> :core:resources :feature:gif-tools --> :core:settings :feature:gif-tools --> :core:di :feature:gif-tools --> :core:crash :feature:library-details --> :core:data :feature:library-details --> :core:ui :feature:library-details --> :core:domain :feature:library-details --> :core:resources :feature:library-details --> :core:settings :feature:library-details --> :core:di :feature:library-details --> :core:crash :feature:pdf-tools --> :core:data :feature:pdf-tools --> :core:ui :feature:pdf-tools --> :core:domain :feature:pdf-tools --> :core:resources :feature:pdf-tools --> :core:settings :feature:pdf-tools --> :core:di :feature:pdf-tools --> :core:crash :feature:watermarking --> :core:data :feature:watermarking --> :core:ui :feature:watermarking --> :core:domain :feature:watermarking --> :core:resources :feature:watermarking --> :core:settings :feature:watermarking --> :core:di :feature:watermarking --> :core:crash :feature:watermarking --> :feature:compare :app --> :core:data :app --> :core:ui :app --> :core:domain :app --> :core:resources :app --> :core:settings :app --> :core:di :app --> :core:crash :app --> :core:utils :app --> :feature:root :app --> :feature:media-picker :app --> :feature:quick-tiles :feature:resize-convert --> :core:data :feature:resize-convert --> :core:ui :feature:resize-convert --> :core:domain :feature:resize-convert --> :core:resources :feature:resize-convert --> :core:settings :feature:resize-convert --> :core:di :feature:resize-convert --> :core:crash :feature:resize-convert --> :feature:compare :feature:easter-egg --> :core:data :feature:easter-egg --> :core:ui :feature:easter-egg --> :core:domain :feature:easter-egg --> :core:resources :feature:easter-egg --> :core:settings :feature:easter-egg --> :core:di :feature:easter-egg --> :core:crash :feature:webp-tools --> :core:data :feature:webp-tools --> :core:ui :feature:webp-tools --> :core:domain :feature:webp-tools --> :core:resources :feature:webp-tools --> :core:settings :feature:webp-tools --> :core:di :feature:webp-tools --> :core:crash :feature:markup-layers --> :core:data :feature:markup-layers --> :core:ui :feature:markup-layers --> :core:domain :feature:markup-layers --> :core:resources :feature:markup-layers --> :core:settings :feature:markup-layers --> :core:di :feature:markup-layers --> :core:crash :feature:media-picker --> :core:data :feature:media-picker --> :core:ui :feature:media-picker --> :core:domain :feature:media-picker --> :core:resources :feature:media-picker --> :core:settings :feature:media-picker --> :core:di :feature:media-picker --> :core:crash :feature:image-stitch --> :core:data :feature:image-stitch --> :core:ui :feature:image-stitch --> :core:domain :feature:image-stitch --> :core:resources :feature:image-stitch --> :core:settings :feature:image-stitch --> :core:di :feature:image-stitch --> :core:crash :feature:image-stitch --> :core:filters :core:ui --> :core:resources :core:ui --> :core:domain :core:ui --> :core:utils :core:ui --> :core:di :core:ui --> :core:settings :feature:noise-generation --> :core:data :feature:noise-generation --> :core:ui :feature:noise-generation --> :core:domain :feature:noise-generation --> :core:resources :feature:noise-generation --> :core:settings :feature:noise-generation --> :core:di :feature:noise-generation --> :core:crash :feature:wallpapers-export --> :core:data :feature:wallpapers-export --> :core:ui :feature:wallpapers-export --> :core:domain :feature:wallpapers-export --> :core:resources :feature:wallpapers-export --> :core:settings :feature:wallpapers-export --> :core:di :feature:wallpapers-export --> :core:crash :feature:document-scanner --> :core:data :feature:document-scanner --> :core:ui :feature:document-scanner --> :core:domain :feature:document-scanner --> :core:resources :feature:document-scanner --> :core:settings :feature:document-scanner --> :core:di :feature:document-scanner --> :core:crash :feature:document-scanner --> :feature:pdf-tools :feature:gradient-maker --> :core:data :feature:gradient-maker --> :core:ui :feature:gradient-maker --> :core:domain :feature:gradient-maker --> :core:resources :feature:gradient-maker --> :core:settings :feature:gradient-maker --> :core:di :feature:gradient-maker --> :core:crash :feature:gradient-maker --> :feature:compare :feature:zip --> :core:data :feature:zip --> :core:ui :feature:zip --> :core:domain :feature:zip --> :core:resources :feature:zip --> :core:settings :feature:zip --> :core:di :feature:zip --> :core:crash :core:utils --> :core:domain :core:utils --> :core:resources :core:utils --> :core:settings :feature:cipher --> :core:data :feature:cipher --> :core:ui :feature:cipher --> :core:domain :feature:cipher --> :core:resources :feature:cipher --> :core:settings :feature:cipher --> :core:di :feature:cipher --> :core:crash :feature:draw --> :core:data :feature:draw --> :core:ui :feature:draw --> :core:domain :feature:draw --> :core:resources :feature:draw --> :core:settings :feature:draw --> :core:di :feature:draw --> :core:crash :feature:draw --> :core:filters :feature:draw --> :feature:pick-color :feature:ai-tools --> :core:data :feature:ai-tools --> :core:ui :feature:ai-tools --> :core:domain :feature:ai-tools --> :core:resources :feature:ai-tools --> :core:settings :feature:ai-tools --> :core:di :feature:ai-tools --> :core:crash :feature:audio-cover-extractor --> :core:data :feature:audio-cover-extractor --> :core:ui :feature:audio-cover-extractor --> :core:domain :feature:audio-cover-extractor --> :core:resources :feature:audio-cover-extractor --> :core:settings :feature:audio-cover-extractor --> :core:di :feature:audio-cover-extractor --> :core:crash :core:crash --> :core:ui :core:crash --> :core:settings :feature:delete-exif --> :core:data :feature:delete-exif --> :core:ui :feature:delete-exif --> :core:domain :feature:delete-exif --> :core:resources :feature:delete-exif --> :core:settings :feature:delete-exif --> :core:di :feature:delete-exif --> :core:crash :feature:collage-maker --> :core:data :feature:collage-maker --> :core:ui :feature:collage-maker --> :core:domain :feature:collage-maker --> :core:resources :feature:collage-maker --> :core:settings :feature:collage-maker --> :core:di :feature:collage-maker --> :core:crash :feature:compare --> :core:data :feature:compare --> :core:ui :feature:compare --> :core:domain :feature:compare --> :core:resources :feature:compare --> :core:settings :feature:compare --> :core:di :feature:compare --> :core:crash :feature:mesh-gradients --> :core:data :feature:mesh-gradients --> :core:ui :feature:mesh-gradients --> :core:domain :feature:mesh-gradients --> :core:resources :feature:mesh-gradients --> :core:settings :feature:mesh-gradients --> :core:di :feature:mesh-gradients --> :core:crash :core:settings --> :core:domain :core:settings --> :core:resources :core:settings --> :core:di :feature:scan-qr-code --> :core:data :feature:scan-qr-code --> :core:ui :feature:scan-qr-code --> :core:domain :feature:scan-qr-code --> :core:resources :feature:scan-qr-code --> :core:settings :feature:scan-qr-code --> :core:di :feature:scan-qr-code --> :core:crash :feature:scan-qr-code --> :core:filters :feature:svg-maker --> :core:data :feature:svg-maker --> :core:ui :feature:svg-maker --> :core:domain :feature:svg-maker --> :core:resources :feature:svg-maker --> :core:settings :feature:svg-maker --> :core:di :feature:svg-maker --> :core:crash :feature:weight-resize --> :core:data :feature:weight-resize --> :core:ui :feature:weight-resize --> :core:domain :feature:weight-resize --> :core:resources :feature:weight-resize --> :core:settings :feature:weight-resize --> :core:di :feature:weight-resize --> :core:crash :feature:image-splitting --> :core:data :feature:image-splitting --> :core:ui :feature:image-splitting --> :core:domain :feature:image-splitting --> :core:resources :feature:image-splitting --> :core:settings :feature:image-splitting --> :core:di :feature:image-splitting --> :core:crash :benchmark --> :app :feature:checksum-tools --> :core:data :feature:checksum-tools --> :core:ui :feature:checksum-tools --> :core:domain :feature:checksum-tools --> :core:resources :feature:checksum-tools --> :core:settings :feature:checksum-tools --> :core:di :feature:checksum-tools --> :core:crash :feature:base64-tools --> :core:data :feature:base64-tools --> :core:ui :feature:base64-tools --> :core:domain :feature:base64-tools --> :core:resources :feature:base64-tools --> :core:settings :feature:base64-tools --> :core:di :feature:base64-tools --> :core:crash :feature:palette-tools --> :core:data :feature:palette-tools --> :core:ui :feature:palette-tools --> :core:domain :feature:palette-tools --> :core:resources :feature:palette-tools --> :core:settings :feature:palette-tools --> :core:di :feature:palette-tools --> :core:crash :feature:palette-tools --> :feature:pick-color :feature:settings --> :core:data :feature:settings --> :core:ui :feature:settings --> :core:domain :feature:settings --> :core:resources :feature:settings --> :core:settings :feature:settings --> :core:di :feature:settings --> :core:crash :core:data --> :core:utils :core:data --> :core:domain :core:data --> :core:resources :core:data --> :core:filters :core:data --> :core:settings :core:data --> :core:di :feature:pick-color --> :core:data :feature:pick-color --> :core:ui :feature:pick-color --> :core:domain :feature:pick-color --> :core:resources :feature:pick-color --> :core:settings :feature:pick-color --> :core:di :feature:pick-color --> :core:crash :feature:load-net-image --> :core:data :feature:load-net-image --> :core:ui :feature:load-net-image --> :core:domain :feature:load-net-image --> :core:resources :feature:load-net-image --> :core:settings :feature:load-net-image --> :core:di :feature:load-net-image --> :core:crash :feature:quick-tiles --> :core:data :feature:quick-tiles --> :core:ui :feature:quick-tiles --> :core:domain :feature:quick-tiles --> :core:resources :feature:quick-tiles --> :core:settings :feature:quick-tiles --> :core:di :feature:quick-tiles --> :core:crash :feature:quick-tiles --> :feature:erase-background :feature:recognize-text --> :core:data :feature:recognize-text --> :core:ui :feature:recognize-text --> :core:domain :feature:recognize-text --> :core:resources :feature:recognize-text --> :core:settings :feature:recognize-text --> :core:di :feature:recognize-text --> :core:crash :feature:recognize-text --> :core:filters :feature:recognize-text --> :feature:single-edit :core:domain --> :core:resources :feature:single-edit --> :core:data :feature:single-edit --> :core:ui :feature:single-edit --> :core:domain :feature:single-edit --> :core:resources :feature:single-edit --> :core:settings :feature:single-edit --> :core:di :feature:single-edit --> :core:crash :feature:single-edit --> :feature:crop :feature:single-edit --> :feature:erase-background :feature:single-edit --> :feature:draw :feature:single-edit --> :feature:filters :feature:single-edit --> :feature:pick-color :feature:single-edit --> :feature:compare :feature:image-cutting --> :core:data :feature:image-cutting --> :core:ui :feature:image-cutting --> :core:domain :feature:image-cutting --> :core:resources :feature:image-cutting --> :core:settings :feature:image-cutting --> :core:di :feature:image-cutting --> :core:crash :feature:image-cutting --> :feature:compare :feature:crop --> :core:data :feature:crop --> :core:ui :feature:crop --> :core:domain :feature:crop --> :core:resources :feature:crop --> :core:settings :feature:crop --> :core:di :feature:crop --> :core:crash :feature:color-tools --> :core:data :feature:color-tools --> :core:ui :feature:color-tools --> :core:domain :feature:color-tools --> :core:resources :feature:color-tools --> :core:settings :feature:color-tools --> :core:di :feature:color-tools --> :core:crash :feature:format-conversion --> :core:data :feature:format-conversion --> :core:ui :feature:format-conversion --> :core:domain :feature:format-conversion --> :core:resources :feature:format-conversion --> :core:settings :feature:format-conversion --> :core:di :feature:format-conversion --> :core:crash :feature:format-conversion --> :feature:compare :feature:ascii-art --> :core:data :feature:ascii-art --> :core:ui :feature:ascii-art --> :core:domain :feature:ascii-art --> :core:resources :feature:ascii-art --> :core:settings :feature:ascii-art --> :core:di :feature:ascii-art --> :core:crash :feature:ascii-art --> :feature:filters :core:filters --> :core:domain :core:filters --> :core:ui :core:filters --> :core:resources :core:filters --> :core:settings :core:filters --> :core:utils :feature:apng-tools --> :core:data :feature:apng-tools --> :core:ui :feature:apng-tools --> :core:domain :feature:apng-tools --> :core:resources :feature:apng-tools --> :core:settings :feature:apng-tools --> :core:di :feature:apng-tools --> :core:crash :feature:image-preview --> :core:data :feature:image-preview --> :core:ui :feature:image-preview --> :core:domain :feature:image-preview --> :core:resources :feature:image-preview --> :core:settings :feature:image-preview --> :core:di :feature:image-preview --> :core:crash :feature:filters --> :core:filters :feature:filters --> :core:data :feature:filters --> :core:ui :feature:filters --> :core:domain :feature:filters --> :core:resources :feature:filters --> :core:settings :feature:filters --> :core:di :feature:filters --> :core:crash :feature:filters --> :core:ksp :feature:filters --> :feature:draw :feature:filters --> :feature:pick-color :feature:filters --> :feature:compare :feature:image-stacking --> :core:data :feature:image-stacking --> :core:ui :feature:image-stacking --> :core:domain :feature:image-stacking --> :core:resources :feature:image-stacking --> :core:settings :feature:image-stacking --> :core:di :feature:image-stacking --> :core:crash :feature:main --> :core:data :feature:main --> :core:ui :feature:main --> :core:domain :feature:main --> :core:resources :feature:main --> :core:settings :feature:main --> :core:di :feature:main --> :core:crash :feature:main --> :feature:settings ``` ================================================ FILE: ARCHITECTURE_2 ================================================ %%{ init: { "theme": "base", "maxEdges": 1000, "themeVariables": { "mainBkg": "#121418", "primaryColor": "#1b3a1b", "primaryTextColor": "#e0e3de", "primaryBorderColor": "#76c893", "nodeBorder": "#76c893", "lineColor": "#76c893", "secondaryColor": "#1f2721", "tertiaryColor": "#232c26", "clusterBkg": "#182018", "clusterBorder": "#4caf50", "nodeTextColor": "#e0e3de", "edgeLabelBackground": "#111316", "edgeLabelColor": "#cfe9de", "fontSize": "28px", "fontFamily": "JetBrains Mono, Inter, system-ui" } } }%% graph LR subgraph :core :core:data("data") :core:ui("ui") :core:domain("domain") :core:resources("resources") :core:settings("settings") :core:di("di") :core:crash("crash") :core:utils("utils") :core:filters("filters") :core:ksp("ksp") end subgraph :feature :feature:root("root") :feature:main("main") :feature:load-net-image("load-net-image") :feature:crop("crop") :feature:limits-resize("limits-resize") :feature:cipher("cipher") :feature:image-preview("image-preview") :feature:weight-resize("weight-resize") :feature:compare("compare") :feature:delete-exif("delete-exif") :feature:palette-tools("palette-tools") :feature:resize-convert("resize-convert") :feature:pdf-tools("pdf-tools") :feature:single-edit("single-edit") :feature:erase-background("erase-background") :feature:draw("draw") :feature:filters("filters") :feature:image-stitch("image-stitch") :feature:pick-color("pick-color") :feature:recognize-text("recognize-text") :feature:gradient-maker("gradient-maker") :feature:watermarking("watermarking") :feature:gif-tools("gif-tools") :feature:apng-tools("apng-tools") :feature:zip("zip") :feature:jxl-tools("jxl-tools") :feature:settings("settings") :feature:easter-egg("easter-egg") :feature:svg-maker("svg-maker") :feature:format-conversion("format-conversion") :feature:document-scanner("document-scanner") :feature:scan-qr-code("scan-qr-code") :feature:image-stacking("image-stacking") :feature:image-splitting("image-splitting") :feature:color-tools("color-tools") :feature:webp-tools("webp-tools") :feature:noise-generation("noise-generation") :feature:collage-maker("collage-maker") :feature:libraries-info("libraries-info") :feature:markup-layers("markup-layers") :feature:base64-tools("base64-tools") :feature:checksum-tools("checksum-tools") :feature:mesh-gradients("mesh-gradients") :feature:edit-exif("edit-exif") :feature:image-cutting("image-cutting") :feature:audio-cover-extractor("audio-cover-extractor") :feature:library-details("library-details") :feature:wallpapers-export("wallpapers-export") :feature:ascii-art("ascii-art") :feature:ai-tools("ai-tools") :feature:color-library("color-library") :feature:media-picker("media-picker") :feature:quick-tiles("quick-tiles") end subgraph :lib :lib:neural-tools("neural-tools") :lib:opencv-tools("opencv-tools") :lib:dynamic-theme("dynamic-theme") :lib:snowfall("snowfall") :lib:documentscanner("documentscanner") :lib:collages("collages") :lib:palette("palette") :lib:curves("curves") :lib:ascii("ascii") end :feature:root --> :core:data :feature:root --> :core:ui :feature:root --> :core:domain :feature:root --> :core:resources :feature:root --> :core:settings :feature:root --> :core:di :feature:root --> :core:crash :feature:root --> :feature:main :feature:root --> :feature:load-net-image :feature:root --> :feature:crop :feature:root --> :feature:limits-resize :feature:root --> :feature:cipher :feature:root --> :feature:image-preview :feature:root --> :feature:weight-resize :feature:root --> :feature:compare :feature:root --> :feature:delete-exif :feature:root --> :feature:palette-tools :feature:root --> :feature:resize-convert :feature:root --> :feature:pdf-tools :feature:root --> :feature:single-edit :feature:root --> :feature:erase-background :feature:root --> :feature:draw :feature:root --> :feature:filters :feature:root --> :feature:image-stitch :feature:root --> :feature:pick-color :feature:root --> :feature:recognize-text :feature:root --> :feature:gradient-maker :feature:root --> :feature:watermarking :feature:root --> :feature:gif-tools :feature:root --> :feature:apng-tools :feature:root --> :feature:zip :feature:root --> :feature:jxl-tools :feature:root --> :feature:settings :feature:root --> :feature:easter-egg :feature:root --> :feature:svg-maker :feature:root --> :feature:format-conversion :feature:root --> :feature:document-scanner :feature:root --> :feature:scan-qr-code :feature:root --> :feature:image-stacking :feature:root --> :feature:image-splitting :feature:root --> :feature:color-tools :feature:root --> :feature:webp-tools :feature:root --> :feature:noise-generation :feature:root --> :feature:collage-maker :feature:root --> :feature:libraries-info :feature:root --> :feature:markup-layers :feature:root --> :feature:base64-tools :feature:root --> :feature:checksum-tools :feature:root --> :feature:mesh-gradients :feature:root --> :feature:edit-exif :feature:root --> :feature:image-cutting :feature:root --> :feature:audio-cover-extractor :feature:root --> :feature:library-details :feature:root --> :feature:wallpapers-export :feature:root --> :feature:ascii-art :feature:root --> :feature:ai-tools :feature:root --> :feature:color-library :feature:erase-background --> :core:data :feature:erase-background --> :core:ui :feature:erase-background --> :core:domain :feature:erase-background --> :core:resources :feature:erase-background --> :core:settings :feature:erase-background --> :core:di :feature:erase-background --> :core:crash :feature:erase-background --> :lib:neural-tools :feature:erase-background --> :feature:draw :feature:edit-exif --> :core:data :feature:edit-exif --> :core:ui :feature:edit-exif --> :core:domain :feature:edit-exif --> :core:resources :feature:edit-exif --> :core:settings :feature:edit-exif --> :core:di :feature:edit-exif --> :core:crash :feature:limits-resize --> :core:data :feature:limits-resize --> :core:ui :feature:limits-resize --> :core:domain :feature:limits-resize --> :core:resources :feature:limits-resize --> :core:settings :feature:limits-resize --> :core:di :feature:limits-resize --> :core:crash :feature:jxl-tools --> :core:data :feature:jxl-tools --> :core:ui :feature:jxl-tools --> :core:domain :feature:jxl-tools --> :core:resources :feature:jxl-tools --> :core:settings :feature:jxl-tools --> :core:di :feature:jxl-tools --> :core:crash :feature:libraries-info --> :core:data :feature:libraries-info --> :core:ui :feature:libraries-info --> :core:domain :feature:libraries-info --> :core:resources :feature:libraries-info --> :core:settings :feature:libraries-info --> :core:di :feature:libraries-info --> :core:crash :feature:gif-tools --> :core:data :feature:gif-tools --> :core:ui :feature:gif-tools --> :core:domain :feature:gif-tools --> :core:resources :feature:gif-tools --> :core:settings :feature:gif-tools --> :core:di :feature:gif-tools --> :core:crash :feature:library-details --> :core:data :feature:library-details --> :core:ui :feature:library-details --> :core:domain :feature:library-details --> :core:resources :feature:library-details --> :core:settings :feature:library-details --> :core:di :feature:library-details --> :core:crash :feature:pdf-tools --> :core:data :feature:pdf-tools --> :core:ui :feature:pdf-tools --> :core:domain :feature:pdf-tools --> :core:resources :feature:pdf-tools --> :core:settings :feature:pdf-tools --> :core:di :feature:pdf-tools --> :core:crash :feature:watermarking --> :core:data :feature:watermarking --> :core:ui :feature:watermarking --> :core:domain :feature:watermarking --> :core:resources :feature:watermarking --> :core:settings :feature:watermarking --> :core:di :feature:watermarking --> :core:crash :feature:watermarking --> :feature:compare :app --> :core:data :app --> :core:ui :app --> :core:domain :app --> :core:resources :app --> :core:settings :app --> :core:di :app --> :core:crash :app --> :core:utils :app --> :feature:root :app --> :feature:media-picker :app --> :feature:quick-tiles :app --> :lib:opencv-tools :app --> :lib:neural-tools :feature:resize-convert --> :core:data :feature:resize-convert --> :core:ui :feature:resize-convert --> :core:domain :feature:resize-convert --> :core:resources :feature:resize-convert --> :core:settings :feature:resize-convert --> :core:di :feature:resize-convert --> :core:crash :feature:resize-convert --> :feature:compare :feature:easter-egg --> :core:data :feature:easter-egg --> :core:ui :feature:easter-egg --> :core:domain :feature:easter-egg --> :core:resources :feature:easter-egg --> :core:settings :feature:easter-egg --> :core:di :feature:easter-egg --> :core:crash :feature:webp-tools --> :core:data :feature:webp-tools --> :core:ui :feature:webp-tools --> :core:domain :feature:webp-tools --> :core:resources :feature:webp-tools --> :core:settings :feature:webp-tools --> :core:di :feature:webp-tools --> :core:crash :feature:markup-layers --> :core:data :feature:markup-layers --> :core:ui :feature:markup-layers --> :core:domain :feature:markup-layers --> :core:resources :feature:markup-layers --> :core:settings :feature:markup-layers --> :core:di :feature:markup-layers --> :core:crash :feature:media-picker --> :core:data :feature:media-picker --> :core:ui :feature:media-picker --> :core:domain :feature:media-picker --> :core:resources :feature:media-picker --> :core:settings :feature:media-picker --> :core:di :feature:media-picker --> :core:crash :feature:image-stitch --> :core:data :feature:image-stitch --> :core:ui :feature:image-stitch --> :core:domain :feature:image-stitch --> :core:resources :feature:image-stitch --> :core:settings :feature:image-stitch --> :core:di :feature:image-stitch --> :core:crash :feature:image-stitch --> :core:filters :feature:image-stitch --> :lib:opencv-tools :core:ui --> :core:resources :core:ui --> :core:domain :core:ui --> :core:utils :core:ui --> :lib:dynamic-theme :core:ui --> :lib:snowfall :core:ui --> :core:di :core:ui --> :core:settings :core:ui --> :lib:documentscanner :feature:color-library --> :core:data :feature:color-library --> :core:ui :feature:color-library --> :core:domain :feature:color-library --> :core:resources :feature:color-library --> :core:settings :feature:color-library --> :core:di :feature:color-library --> :core:crash :feature:noise-generation --> :core:data :feature:noise-generation --> :core:ui :feature:noise-generation --> :core:domain :feature:noise-generation --> :core:resources :feature:noise-generation --> :core:settings :feature:noise-generation --> :core:di :feature:noise-generation --> :core:crash :feature:wallpapers-export --> :core:data :feature:wallpapers-export --> :core:ui :feature:wallpapers-export --> :core:domain :feature:wallpapers-export --> :core:resources :feature:wallpapers-export --> :core:settings :feature:wallpapers-export --> :core:di :feature:wallpapers-export --> :core:crash :feature:document-scanner --> :core:data :feature:document-scanner --> :core:ui :feature:document-scanner --> :core:domain :feature:document-scanner --> :core:resources :feature:document-scanner --> :core:settings :feature:document-scanner --> :core:di :feature:document-scanner --> :core:crash :feature:document-scanner --> :feature:pdf-tools :feature:gradient-maker --> :core:data :feature:gradient-maker --> :core:ui :feature:gradient-maker --> :core:domain :feature:gradient-maker --> :core:resources :feature:gradient-maker --> :core:settings :feature:gradient-maker --> :core:di :feature:gradient-maker --> :core:crash :feature:gradient-maker --> :feature:compare :feature:zip --> :core:data :feature:zip --> :core:ui :feature:zip --> :core:domain :feature:zip --> :core:resources :feature:zip --> :core:settings :feature:zip --> :core:di :feature:zip --> :core:crash :core:utils --> :core:domain :core:utils --> :core:resources :core:utils --> :core:settings :feature:cipher --> :core:data :feature:cipher --> :core:ui :feature:cipher --> :core:domain :feature:cipher --> :core:resources :feature:cipher --> :core:settings :feature:cipher --> :core:di :feature:cipher --> :core:crash :feature:draw --> :core:data :feature:draw --> :core:ui :feature:draw --> :core:domain :feature:draw --> :core:resources :feature:draw --> :core:settings :feature:draw --> :core:di :feature:draw --> :core:crash :feature:draw --> :core:filters :feature:draw --> :feature:pick-color :feature:ai-tools --> :core:data :feature:ai-tools --> :core:ui :feature:ai-tools --> :core:domain :feature:ai-tools --> :core:resources :feature:ai-tools --> :core:settings :feature:ai-tools --> :core:di :feature:ai-tools --> :core:crash :feature:ai-tools --> :lib:neural-tools :feature:audio-cover-extractor --> :core:data :feature:audio-cover-extractor --> :core:ui :feature:audio-cover-extractor --> :core:domain :feature:audio-cover-extractor --> :core:resources :feature:audio-cover-extractor --> :core:settings :feature:audio-cover-extractor --> :core:di :feature:audio-cover-extractor --> :core:crash :core:crash --> :core:ui :core:crash --> :core:settings :lib:documentscanner --> :lib:opencv-tools :feature:delete-exif --> :core:data :feature:delete-exif --> :core:ui :feature:delete-exif --> :core:domain :feature:delete-exif --> :core:resources :feature:delete-exif --> :core:settings :feature:delete-exif --> :core:di :feature:delete-exif --> :core:crash :feature:collage-maker --> :core:data :feature:collage-maker --> :core:ui :feature:collage-maker --> :core:domain :feature:collage-maker --> :core:resources :feature:collage-maker --> :core:settings :feature:collage-maker --> :core:di :feature:collage-maker --> :core:crash :feature:collage-maker --> :lib:collages :feature:compare --> :core:data :feature:compare --> :core:ui :feature:compare --> :core:domain :feature:compare --> :core:resources :feature:compare --> :core:settings :feature:compare --> :core:di :feature:compare --> :core:crash :feature:compare --> :lib:opencv-tools :feature:mesh-gradients --> :core:data :feature:mesh-gradients --> :core:ui :feature:mesh-gradients --> :core:domain :feature:mesh-gradients --> :core:resources :feature:mesh-gradients --> :core:settings :feature:mesh-gradients --> :core:di :feature:mesh-gradients --> :core:crash :core:settings --> :lib:dynamic-theme :core:settings --> :core:domain :core:settings --> :core:resources :core:settings --> :core:di :feature:scan-qr-code --> :core:data :feature:scan-qr-code --> :core:ui :feature:scan-qr-code --> :core:domain :feature:scan-qr-code --> :core:resources :feature:scan-qr-code --> :core:settings :feature:scan-qr-code --> :core:di :feature:scan-qr-code --> :core:crash :feature:scan-qr-code --> :core:filters :feature:svg-maker --> :core:data :feature:svg-maker --> :core:ui :feature:svg-maker --> :core:domain :feature:svg-maker --> :core:resources :feature:svg-maker --> :core:settings :feature:svg-maker --> :core:di :feature:svg-maker --> :core:crash :feature:weight-resize --> :core:data :feature:weight-resize --> :core:ui :feature:weight-resize --> :core:domain :feature:weight-resize --> :core:resources :feature:weight-resize --> :core:settings :feature:weight-resize --> :core:di :feature:weight-resize --> :core:crash :feature:image-splitting --> :core:data :feature:image-splitting --> :core:ui :feature:image-splitting --> :core:domain :feature:image-splitting --> :core:resources :feature:image-splitting --> :core:settings :feature:image-splitting --> :core:di :feature:image-splitting --> :core:crash :benchmark --> :app :feature:checksum-tools --> :core:data :feature:checksum-tools --> :core:ui :feature:checksum-tools --> :core:domain :feature:checksum-tools --> :core:resources :feature:checksum-tools --> :core:settings :feature:checksum-tools --> :core:di :feature:checksum-tools --> :core:crash :feature:base64-tools --> :core:data :feature:base64-tools --> :core:ui :feature:base64-tools --> :core:domain :feature:base64-tools --> :core:resources :feature:base64-tools --> :core:settings :feature:base64-tools --> :core:di :feature:base64-tools --> :core:crash :feature:palette-tools --> :core:data :feature:palette-tools --> :core:ui :feature:palette-tools --> :core:domain :feature:palette-tools --> :core:resources :feature:palette-tools --> :core:settings :feature:palette-tools --> :core:di :feature:palette-tools --> :core:crash :feature:palette-tools --> :feature:pick-color :feature:palette-tools --> :lib:palette :feature:settings --> :core:data :feature:settings --> :core:ui :feature:settings --> :core:domain :feature:settings --> :core:resources :feature:settings --> :core:settings :feature:settings --> :core:di :feature:settings --> :core:crash :core:data --> :core:utils :core:data --> :core:domain :core:data --> :core:resources :core:data --> :core:filters :core:data --> :core:settings :core:data --> :core:di :feature:pick-color --> :core:data :feature:pick-color --> :core:ui :feature:pick-color --> :core:domain :feature:pick-color --> :core:resources :feature:pick-color --> :core:settings :feature:pick-color --> :core:di :feature:pick-color --> :core:crash :feature:load-net-image --> :core:data :feature:load-net-image --> :core:ui :feature:load-net-image --> :core:domain :feature:load-net-image --> :core:resources :feature:load-net-image --> :core:settings :feature:load-net-image --> :core:di :feature:load-net-image --> :core:crash :feature:quick-tiles --> :core:data :feature:quick-tiles --> :core:ui :feature:quick-tiles --> :core:domain :feature:quick-tiles --> :core:resources :feature:quick-tiles --> :core:settings :feature:quick-tiles --> :core:di :feature:quick-tiles --> :core:crash :feature:quick-tiles --> :feature:erase-background :feature:recognize-text --> :core:data :feature:recognize-text --> :core:ui :feature:recognize-text --> :core:domain :feature:recognize-text --> :core:resources :feature:recognize-text --> :core:settings :feature:recognize-text --> :core:di :feature:recognize-text --> :core:crash :feature:recognize-text --> :core:filters :feature:recognize-text --> :feature:single-edit :core:domain --> :core:resources :feature:single-edit --> :core:data :feature:single-edit --> :core:ui :feature:single-edit --> :core:domain :feature:single-edit --> :core:resources :feature:single-edit --> :core:settings :feature:single-edit --> :core:di :feature:single-edit --> :core:crash :feature:single-edit --> :feature:crop :feature:single-edit --> :feature:erase-background :feature:single-edit --> :feature:draw :feature:single-edit --> :feature:filters :feature:single-edit --> :feature:pick-color :feature:single-edit --> :feature:compare :feature:single-edit --> :lib:curves :feature:image-cutting --> :core:data :feature:image-cutting --> :core:ui :feature:image-cutting --> :core:domain :feature:image-cutting --> :core:resources :feature:image-cutting --> :core:settings :feature:image-cutting --> :core:di :feature:image-cutting --> :core:crash :feature:image-cutting --> :feature:compare :feature:crop --> :core:data :feature:crop --> :core:ui :feature:crop --> :core:domain :feature:crop --> :core:resources :feature:crop --> :core:settings :feature:crop --> :core:di :feature:crop --> :core:crash :feature:crop --> :lib:opencv-tools :feature:color-tools --> :core:data :feature:color-tools --> :core:ui :feature:color-tools --> :core:domain :feature:color-tools --> :core:resources :feature:color-tools --> :core:settings :feature:color-tools --> :core:di :feature:color-tools --> :core:crash :feature:format-conversion --> :core:data :feature:format-conversion --> :core:ui :feature:format-conversion --> :core:domain :feature:format-conversion --> :core:resources :feature:format-conversion --> :core:settings :feature:format-conversion --> :core:di :feature:format-conversion --> :core:crash :feature:format-conversion --> :feature:compare :feature:ascii-art --> :core:data :feature:ascii-art --> :core:ui :feature:ascii-art --> :core:domain :feature:ascii-art --> :core:resources :feature:ascii-art --> :core:settings :feature:ascii-art --> :core:di :feature:ascii-art --> :core:crash :feature:ascii-art --> :feature:filters :feature:ascii-art --> :lib:ascii :core:filters --> :core:domain :core:filters --> :core:ui :core:filters --> :core:resources :core:filters --> :core:settings :core:filters --> :core:utils :core:filters --> :lib:curves :core:filters --> :lib:ascii :core:filters --> :lib:neural-tools :feature:apng-tools --> :core:data :feature:apng-tools --> :core:ui :feature:apng-tools --> :core:domain :feature:apng-tools --> :core:resources :feature:apng-tools --> :core:settings :feature:apng-tools --> :core:di :feature:apng-tools --> :core:crash :feature:image-preview --> :core:data :feature:image-preview --> :core:ui :feature:image-preview --> :core:domain :feature:image-preview --> :core:resources :feature:image-preview --> :core:settings :feature:image-preview --> :core:di :feature:image-preview --> :core:crash :feature:filters --> :core:filters :feature:filters --> :core:data :feature:filters --> :core:ui :feature:filters --> :core:domain :feature:filters --> :core:resources :feature:filters --> :core:settings :feature:filters --> :core:di :feature:filters --> :core:crash :feature:filters --> :core:ksp :feature:filters --> :feature:draw :feature:filters --> :feature:pick-color :feature:filters --> :feature:compare :feature:filters --> :lib:opencv-tools :feature:filters --> :lib:neural-tools :feature:filters --> :lib:curves :feature:filters --> :lib:ascii :feature:image-stacking --> :core:data :feature:image-stacking --> :core:ui :feature:image-stacking --> :core:domain :feature:image-stacking --> :core:resources :feature:image-stacking --> :core:settings :feature:image-stacking --> :core:di :feature:image-stacking --> :core:crash :feature:main --> :core:data :feature:main --> :core:ui :feature:main --> :core:domain :feature:main --> :core:resources :feature:main --> :core:settings :feature:main --> :core:di :feature:main --> :core:crash :feature:main --> :feature:settings ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guidelines This documentation contains set of guidelines to help you during the contribution process. # Submitting Contributions👨🏻‍💻 Below you will find the process and workflow used to review and merge your changes. ## 🌟 : Choose an issue/ Create an issue - Look for the existing issue or create your own issue. - Comment on the respective issue you would like to work before creating a Pull Request. - Wait for the issue to be assigned to you after which you can start working on it. ## 🌟 : Fork the repository - Fork this repository "ImageToolbox" by clicking on the "Fork" button. This will create a local copy of this respository on your GitHub profile. ## 🌟 : Clone the forked repository - Once the repository is forked you need to clone it to your local machine. - Click on the "Code" button in the repository page and copy the link provided in the dropdown menu. ```bash git clone https://github.com// ``` - Keep a reference to the original project in `upstream` remote. ```bash cd git remote add upstream https://github.com// git remote -v # To the check the remotes for this repository ``` - If the project is forked already, update the copy before working. ```bash git remote update git checkout git rebase upstream/ ``` ## 🌟 : Create a new branch - Always create a new branch and name it accordingly so as to identify the issue you are addressing. ```bash # It will create a new branch with name branch_name and switch to that branch git checkout -b branch_name ``` ## 🌟 : Work on the issue assigned - Work on the issue(s) assigned to you, make the necessary changes in the files/folders needed. - After making the changes add them to the branch you've created. ```bash # To add all new files to branch Branch_Name git add . # To add only a few files to Branch_Name git add ``` ## 🌟 : Commit the changes - Add your commits. - Along with the commit give a descriptive message that reflects your changes. ```bash git commit -m "message" ``` - Note : A Pull Request should always have only one commit. ## 🌟 : Push the changes - Push the committed changes in your branch to your remote repository. ```bash git push origin branch_name ``` ## 🌟 : Create a Pull Request - Go to your repository in the browser and click on compare and pull request. - Add a title and description to your pull request that best describes your contribution. - After which the pull request will be reviewed and the maintainer will provide the reviews required for the changes. If no changes are needed, this means that your Pull Request has been reviewed and will be merged to the original code base by the maintainer. Happy Hacking! ================================================ 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.md ================================================

# Image Toolbox

API Kotlin Jetpack Compose material








Featured|HelloGitHub

# 🗺️ Project Overview ImageToolbox is a versatile image editing tool designed for efficient photo manipulation. It allows users to crop, apply filters, edit EXIF data, erase backgrounds, and even enhance images with AI. Ideal for both photographers and developers, the tool offers a simple interface with powerful capabilities.

# 📔 Wiki Check out Image Toolbox [Wiki](https://github.com/T8RIN/ImageToolbox/wiki) for FAQ and useful info

# ✈️ Telegram Links
[![ImageToolbox Chat](https://img.shields.io/endpoint?&style=for-the-badge&colorA=246732&colorB=A2FFB0&logo=telegram&logoColor=A2FFB0&label=ImageToolbox%20Chat&url=https://tg.sumanjay.workers.dev/t8rin_imagetoolbox)](https://t.me/t8rin_imagetoolbox) [![CI Telegram](https://img.shields.io/endpoint?&style=for-the-badge&colorA=29626B&colorB=B5DFE8&logo=telegram&logoColor=B5DFE8&url=https://tg.sumanjay.workers.dev/t8rin_imagetoolbox_ci)](https://t.me/t8rin_imagetoolbox_ci)

Join our chat where you can discuss anything you want and also look into the CI channel where I post betas and announcements
# ☕ Buy me a coffee This application is completely free, but if you want to support the project development, you can send a donation to the crypto wallets below |
![Boosty](https://img.shields.io/badge/Boosty-F15F2C?style=for-the-badge&logo=Boosty&logoColor=white)

[https://boosty.to/t8rin](https://boosty.to/t8rin)

|
![Bitcoin](https://img.shields.io/badge/Bitcoin-EAB300?style=for-the-badge&logo=Bitcoin%20SV&logoColor=white)

`18QFWMREkjzQa4yetfYsN5Ua51UubKmJut`

|
![Tether](https://img.shields.io/badge/USDT%20(TRC20)-168363?style=for-the-badge&logo=tether&logoColor=white)

`TVdw6fP8dYsYA6HgQiSYNijBqPJ3k5BbYo`

| |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| # 📲 Download Go to the [Releases](https://github.com/t8rin/imageresizer/releases/latest) and the download latest apk or click one of the badges below.

Google Play F-Droid GitHub Obtainium Obtainium (Pre-release)

# 💻 Installation Instructions 1. Clone the repository: ```bash git clone https://github.com/yourusername/ImageToolbox.git ``` 2. Install dependencies using your preferred package manager (e.g., Gradle). 3. Build the project: bash ./gradlew build 4. Run the application: bash ./gradlew run # ⚔️ FOSS vs MARKET | **Feature** | **FOSS** | **Market** | |:-----------------------:|:------------------:|:------------------:| | QR Scanner | Zxing | MlKit | | Auto Background Remover | ONNX | MlKit | | Document Scanner | OpenCV | MlKit | | Analytics | :x: | :white_check_mark: | | Crashlytics | :x: | :white_check_mark: | | Other Google deps | :x: | :white_check_mark: | | All Other Features | :white_check_mark: | :white_check_mark: | # ✨ Features - Batch processing - Applying filter chains (More than 310 various filters)
Available filters
- [x] Saturation - [x] Contrast - [x] Brightness - [x] Exposure - [x] RGB - [x] Hue - [x] White Balance - [x] Monochrome - [x] Black and White - [x] False Color - [x] Sharpen - [x] Gamma - [x] Highlights and Shadows - [x] Haze - [x] Sepia Tone - [x] Color Inversion - [x] Solarize - [x] Vibrance - [x] Luminance Threshold - [x] Pixellate - [x] Halftone - [x] Crosshatch - [x] Sobel Edge Detection - [x] Sketch Filter - [x] Toon Filter - [x] SmoothToon Filter - [x] CGA Colorspace Filter - [x] Posterize - [x] Convolution 3x3 - [x] Emboss Filter - [x] Laplacian - [x] Kuwahara Filter - [x] Vignette - [x] Gaussian Blur - [x] Box Blur - [x] Stack Blur - [x] Fast Blur - [x] Bilaterial Blur - [x] Zoom Blur - [x] Median Blur - [x] Pixelation - [x] Enhanced Pixelation - [x] Stroke Pixelation - [x] Circle Pixelation - [x] Enhanced Circle Pixelation - [x] Diamond Pixelation - [x] Enhanced Diamond Pixelation - [x] Swirl Distortion - [x] Bulge Distortion - [x] Sphere Refraction - [x] Glass Sphere Refraction - [x] Dilation - [x] Non Maximum Suppression - [x] Opacity - [x] Weak Pixel Inclusion Filter - [x] Color Matrix 4x4 - [x] Lookup - [x] Color Replacement - [x] Color Removance - [x] Bayer Two Dithering - [x] Bayer Three Dithering - [x] Bayer Four Dithering - [x] Bayer Eight Dithering - [x] Floyd Steinberg Dithering - [x] Jarvis Judice Ninke Dithering - [x] Sierra Dithering - [x] Two Row Sierra Dithering - [x] Sierra Lite Dithering - [x] Atkinson Dithering - [x] Stucki Dithering - [x] Burkes Dithering - [x] False Floyd Steinberg Dithering - [x] Left To Right Dithering - [x] Random Dithering - [x] Simple Threshold Dithering - [x] Quantizier - [x] Glitch Effect - [x] Enhanced Glitch Effect - [x] Anaglyph - [x] Noise - [x] Tent Blur - [x] Side Fade - [x] Erode - [x] Anisotropic Diffusion - [x] Horizontal Wind Stagger - [x] Fast Bilaterial Blur - [x] Poisson Blur - [x] Logarithmic Tone Mapping - [x] Aces Filmic Tone Mapping - [x] Crystallize - [x] Fractal Glass - [x] Marble - [x] Oil - [x] Water Effect - [x] Hable Filmic Tone Mapping - [x] Aces Hill Tone Mapping - [x] Hejl Burgess Tone Mapping - [x] Perlin Distortion - [x] Grayscale - [x] Dehaze - [x] Color Matrix 3x3 - [x] Achromatomaly - [x] Achromatopsia - [x] Browni - [x] CodaChrome - [x] Cool - [x] Deutaromaly - [x] Deutaronotopia - [x] Night Vision - [x] Polaroid - [x] Protanopia - [x] Protonomaly - [x] Tritanopia - [x] Tritonomaly - [x] Vintage - [x] Warm - [x] Grain - [x] Unsharp - [x] Pastel - [x] Orange Haze - [x] Pink Dream - [x] Golden Hour - [x] Hot Summer - [x] Purple Mist - [x] Sunrise - [x] Colorful Swirl - [x] Soft Spring Light - [x] Autumn Tones - [x] Lavender Dream - [x] Cyberpunk - [x] Lemonade Light - [x] Spectral Fire - [x] Night Magic - [x] Fantasy Landscape - [x] Color Explosion - [x] Electric Gradient - [x] Caramel Darkness - [x] Futuristic Gradient - [x] Green Sun - [x] Rainbow World - [x] Deep Purple - [x] Space Portal - [x] Red Swirl - [x] Digital Code - [x] Bokeh - [x] Neon - [x] Old Tv - [x] Shuffle Blur - [x] Mobius - [x] Uchimura - [x] Aldridge - [x] Drago - [x] Color Anomaly - [x] Quantizier - [x] Ring Blur - [x] Cross Blur - [x] Circle Blur - [x] Star Blur - [x] Motion Blur - [x] Fast Gaussian Blur 2D - [x] Fast Gaussian Blur 3D - [x] Fast Gaussian Blur 4D - [x] Equalize Histogram - [x] Equalize Histogram HSV - [x] Equalize Histogram Pixelation - [x] Equalize Histogram Adaptive - [x] Equalize Histogram Adaptive LUV - [x] Equalize Histogram Adaptive LAB - [x] Equalize Histogram Adaptive HSV - [x] Equalize Histogram Adaptive HSL - [x] Clahe - [x] Clahe LUV - [x] Clahe LAB - [x] Clahe HSL - [x] Clahe HSV - [x] Crop To Content - [x] Linear Box Blur - [x] Linear Tent Blur - [x] Linear Gaussian Box Blur - [x] Linear Stack Blur - [x] Gaussian Box Blur - [x] Linear Fast Gaussian Next - [x] LinearFast Gaussian - [x] Linear Gaussian - [x] Low Poly - [x] Sand Painting - [x] Palette Transfer - [x] Enhanced Oil - [x] Simple Old TV - [x] HDR - [x] Simple Sketch - [x] Gotham - [x] Color Poster - [x] Tri Tone - [x] Clahe Oklch - [x] Clahe Jzazbz - [x] Clahe Oklab - [x] Yililoma Dithering - [x] Clustered 2x2 Dithering - [x] Clustered 4x4 Dithering - [x] Clustered8x8 Dithering - [x] Polka Dot - [x] LUT 512\*512 - [x] Amatorka - [x] Miss Etikate - [x] Soft Elegance - [x] Soft Elegance Variant - [x] Bleach Bypass - [x] Candlelight - [x] Drop Blues - [x] Edgy Amber - [x] Fall Colors - [x] Film Stock 50 - [x] Foggy Night - [x] Kodak - [x] Palette Transfer Variant - [x] 3D LUT (.cube / .CUBE) - [x] Pop Art - [x] Celluloid - [x] Coffee - [x] Golden Forest - [x] Greenish - [x] Retro Yellow - [x] Auto Crop - [x] Opening - [x] Closing - [x] Morphological Gradient - [x] Top Hat - [x] Black Hat - [x] Enhanced Zoom Blur - [x] Simple Sobel - [x] Simple Laplacian - [x] Auto Red Eyes remover - [x] Tone Curves - [x] Mirror - [x] Kaleidoscope - [x] Channel Mix - [x] Color Halftone - [x] Contour - [x] Voronoi Crystallize - [x] Despeckle - [x] Diffuse - [x] DoG - [x] Equalize - [x] Glow - [x] Offset - [x] Pinch - [x] Pointillize - [x] Polar Coordinates - [x] Reduce Noise - [x] Simple Solarize - [x] Weave - [x] Twirl - [x] Rubber Stamp - [x] Smear - [x] Sphere Lens Distortion - [x] Arc - [x] Sparkle - [x] ASCII - [x] Moire - [x] Autumn - [x] Bone - [x] Jet - [x] Winter - [x] Rainbow - [x] Ocean - [x] Summer - [x] Spring - [x] Cool Variant - [x] Hsv - [x] Pink - [x] Hot - [x] Parula - [x] Magma - [x] Inferno - [x] Plasma - [x] Viridis - [x] Cividis - [x] Twilight - [x] Twilight Shifted - [x] Deskew - [x] Auto Perspective - [x] Crop Or Perspective - [x] Turbo - [x] Deep Green - [x] Lens Correction - [x] Seam Carving - [x] Error Level Analysis - [x] Luminance Gradient - [x] Average Distance - [x] Copy Move Detection - [x] Simple Weave Pixelization - [x] Staggered Pixelization - [x] Cross Pixelization - [x] Micro Macro Pixelization - [x] Orbital Pixelization - [x] Vortex Pixelization - [x] Pulse Grid Pixelization - [x] Nucleus Pixelization - [x] Radial Weave Pixelization - [x] Border Frame - [x] Glitch Variant - [x] VHS - [x] Block Glitch - [x] Crt Curvature - [x] Pixel Melt - [x] Bloom
- Custom Filters Creation by Template filters - You can create filter from any filter chain - Share created filters by QR code - Scan filters from the app to get them on your device - Files encryption and decryption with 100+ different algorithms available - Adding Stickers and Text (Markup Layers Mode) - Extract Text From Images (OCR) - 120+ languages - 3 Type of data: Fast, Standard, Best - Segmentation Mode Selection - Engine Mode Selection - Custom Tesseract options entering - Multiple languages at the same time - Reading from batch of images to file - Placing in EXIF metadata of batch images - Creating searchable PDF with recognized text behind images - EXIF metadata editing/deleting - Loading images from internet - Image Stitching - Image Stacking - Image Splitting - Background Removal - By drawing - Automatically (MlKit, U2NetP, U2Net, RMBG, InSPyReNet, BiRefNet, ISNet) - Watermarking - Repeating Text - Image - Stamp - Timestamp - Digital (Steganography) - Drawing on Image/Background - Pen - Flood Fil - Spray - Neon - Highlighter - Warp (Move, Grow, Shrink, Swirl, Mix) - Privacy Blur - Pixelation Paint - Text - Image Brush - Filter Brush - Spot Healing (with ability to download AI model for generative inpainting) - Pointing Arrow - Line - Double Pointing Arrow - Line Pointing Arrow - Double Line Pointing Arrow - Outlined Rect - Outlined Oval - Outlined Triangle - Outlined Polygon - Outlined Star - Rect - Oval - Triangle - Polygon - Star - Lasso - Line Style - Dashed - Dot Dashed - Zigzag - Stamped - Image Resizing - Width changing - Height changing - Adaptive resize - Resize retaining aspect ratio - Resize by given limits - Center Crop with - Background color changing - Background blur drawing - Different Scaling Algorithms
Available methods
- Bilinear - Nearest Neighbour - Cubic - Mitchell-Netravalli - Catmull-Rom - Hermite - B-Spline - Hann - Bicubic - Hamming - Hanning - Blackman - Welch - Quadric - Gaussian - Sphinx - Bartlett - Robidoux - Robidoux Sharp - Spline 16 - Spline 36 - Spline 64 - Kaiser - Bartlett-Hann - Box - Bohman - Lanczos 2 - Lanczos 3 - Lanczos 4 - Lanczos 2 Jinc - Lanczos 3 Jinc - Lanczos 4 Jinc - Ewa Hanning - Ewa Robidoux - Ewa Blackman - Ewa Quadric - Ewa Robidoux Sharp - Ewa Lanczos 3 Jinc - Ginseng - Ginseng EWA - Lanczos Sharp EWA - Lanczos 4 Sharpest EWA - Lanczos Soft EWA - Haasn Soft - Lagrange 2 - Lagrange 3 - Lanczos 6 - Lanczos 6 Jinc
- Different Scale Color Spaces - Linear - sRGB - LAB - LUV - Sigmoidal - XYZ - F32 Gamma 2.2 - F32 Gamma 2.8 - F32 Rec.709 - F32 sRGB - LCH - Oklab sRGB - Oklab Rec.709 - Oklab Gamma 2.2 - Oklab Gamma 2.8 - Jzazbz sRGB - Jzazbz Rec.709 - Jzazbz Gamma 2.2 - Jzazbz Gamma 2.8 - GIF conversion - GIF to images - Images to GIF - GIF to WEBP - WEBP conversion - WEBP to images - Images to WEBP - APNG conversion - APNG to images - Images to APNG - JXL transcoding - JXL to JPEG - JPEG to JXL - Animated JXL conversion - Images to JXL - JXL to Images - APNG to JXL - GIF to JXL - PDF tools - PDF to images - Images to PDF - PDF previewing - Merge - Split - Rotate - Rearrange - Page Numbering - Watermark - Signature - Compress - Grayscale - Repair - Protect - Unlock - Metadata - Remove Pages - Crop - Flatten - Extract Images - Zip PDF - Print PDF - PDF to Text (OCR) - Document Scanning - AI tools (95+ ready to use models available) - Upscale - Remove BG - DeJPEG - DeNoise - Colorize - Artifacts - Enhance - Anime - Scans - Barcodes - Scanning - Creating & Parsing common types - Plain - Url - WiFi - Email - Geolocation - Phone - SMS - Contact (vCard) - Calendar event - Sharing as images - 13 formats available - QR CODE - AZTEC - CODABAR - CODE 39 - CODE 93 - CODE 128 - DATA MATRIX - EAN 8 - EAN 13 - ITF - PDF 417 - UPC A - UPC E - Collage Creation - From 1 to 20 images - More than 310 various collage layouts - Image Shrinking - Quality compressing - Preset shrinking - Reducing size by given weight (in KB) - Cropping - Regular crop - Free rotation crop - Free corners crop (can be used as Perspective Correction) - Crop by aspect ratio - Crop with shape mask
List of shapes
- Rounded Corners - Cut Corners - Oval - Squircle - Octagon - Rounded Pentagon - Clover - Material Star - Kotlin Logo - Small Material Star - Heart - Shuriken - Explosion - Bookmark - Pill - Burger - Shield - Droplet - Arrow - Egg - Map - Enhanced Heart - Star - Image Mask -
Additional Shapes
![image](./fastlane/metadata/android/en-US/images/banner/banner_shapes.png)
- Image Cutting (can be used as batch crop) - Tracing raster images to SVG - Format Conversion - HEIF - HEIC - AVIF - WEBP - JPEG - JPG - PNG Lossless - PNG Lossy - OxiPNG - MozJpeg - Jpegli - JXL - JP2 - J2K - TIFF - TIF - QOI - ICO - SVG, DNG, PSD, GIF to static raster images - Telegram sticker PNG format - Files to Zip - Comparing images - Slide - Toggle Tap - Transparency - Side By Side - Pixel By Pixel (7 Methods) - SSIM - AE - MAE - NCC - PSNR - RMSE - Color Utils - Palette generation - Material You Scheme - Simple Colors - Import/Export palette across 41 format - ACB - ACO - ACT - Android Xml - ASE - Basic Xml - Corel Painter - Corel Draw - Scribus Xml - Corel Palette - CSV - DCP - Gimp - Hex Rgba - Image - Json - Open Office - Paint Net - Paint Shop Pro - Rgba - Rgb - Riff - Sketch - SKP - SVG - Swift - Kotlin - Corel Draw V3 - CLF - Swatches - Autodesk Color Book - Simple Palette - Swatchbooker - Afpalette - Xara - Koffice - KPL - HPL - Skencil - Vga 24Bit - Vga 18Bit - Picking color from image - Gradient creation (Mesh gradients too) - Overlaying image with gradient - Mixing - Conversion - Harmonies - Shading - Tone Curves applying - Color Library with more than 33k different colors - Histograms - RGB - Brightness - Camera Like RGB - Image source selection - Additional Features - Base64 Decode/Encode - Rotating - Flipping - Perlin Noise Generation - Previewing SVG, DNG, PSD, DJVU and almost all types of images - Saving to any specific folder - Long press on save to choose one time output folder - Randomizing output filename - Using image cheksum as filename - Checksum Tools with ability to calculate and compare hashes - 64 different hashing algorithms - Audio files Album Cover export - Embedded media picker - Wallpapers Export - Ascii Art **And More!** # # 🌟 UI tweaks - Selecting Emoji for top app bar - Ability to use Pixel like switch instead of Material You - Secure Mode for app - Maximum brightness for selected screens - In app language changing - Enabling or Disabling confetti - Custom app color scheme - Different palette styles - Predefined schemes - Color inversion - Contrast adjusting - Controlling borders thickness - Enabling and disabling each existing shadow - Haptics controls - Light/Dark mode - AMOLED mode - Monet implementation (Dynamic colors) even for Android versions less than 12 by [Dynamic Theme](https://github.com/T8RIN/DynamicTheme) - Image based color scheme - Icons Background shape selection - Rounded Corners - Cut Corners - Oval - Squircle - Octagon - Rounded Pentagon - Clover - Material Star - Small Material Star - Heart - Enhanced Heart - Custom fonts
Preinstalled fonts
- Montserrat - Comfortaa - Caveat - Handjet - Jura - Podkova - Tektur - YsabeauSC - DejaVu - BadScript - RuslanDisplay - Catterdale - FRM32 - Tokeely Brookings - Nunito - Nothing - WOPR Tweaked - Alegreya Sans - Minecraft Gnu - Granite Fixed - Nokia Pixel - Ztivalia - Axotrel - Lcd Octagon - Lcd Moving - Unisource
- Ability to import any font (OTF/TTF) to further use - In app font scale changing - Changing between options list and grouped view - Confetti Type selection - Default - Festive - Explode - Rain - Side - Corners - ImageToolbox - Switch Type selection: - Material You - Compose - Pixel - Fluent - Cupertino - Liquid Glass - HyperOS - Slider Type Selection: - Fancy - Material You - Material - HyperOS - Shapes Type Selection with size adjustment: - Rounded - Cut - Squircle - Smooth - Main screen layout customization (Yes, the app supports dynamic coloring based on wallpapers for every android version) # 📚 Tech stack & Open-source libraries - Minimum SDK level 23 - [Kotlin](https://kotlinlang.org/) based - [Image Toolbox Libs](https://github.com/T8RIN/ImageToolboxLibs) - set of essential libraries for Image Toolbox. - [Dynamic Theme](https://github.com/T8RIN/DynamicTheme) - library, which allows you to easily implement custom color theming. - [Modal Sheet](https://github.com/T8RIN/ModalSheet) - modal bottom sheet that follows M3 guidelines. - [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) for asynchronous work. - [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/) to emit values from data layer reactively. - [Accompanist](https://github.com/google/accompanist) to expand jetpack compose opportunities. - [Decompose](https://github.com/arkivanov/Decompose) - KMP lifecycle-aware business logic components (aka BLoCs) with routing (navigation) and pluggable UI - [Hilt](https://dagger.dev/hilt/) for dependency injection. - [Coil](https://github.com/coil-kt/coil) for loading images. - [Konfetti](https://github.com/DanielMartinus/Konfetti) to establish beautiful particle system. - Jetpack - [Compose](https://developer.android.com/jetpack/compose) - Modern Declarative UI style framework based on composable functions. - [Material You Kit](https://developer.android.com/jetpack/androidx/releases/compose-material3) - Material 3 powerful UI components. - [Data Store](https://developer.android.com/jetpack/androidx/releases/datastore) - Store data asynchronously, consistently, and transactionally. - [Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) - Observe Android lifecycles and handle UI states upon the lifecycle changes. - [Exif Interface](https://developer.android.com/jetpack/androidx/releases/exifinterface) - Read and write image file EXIF tags. - [GPU Image](https://github.com/cats-oss/android-gpuimage) for creating and applying filters to the images. - [SmartToolFactory](https://github.com/SmartToolFactory) provides a bunch of helpful libraries. - [AVIF Coder](https://github.com/awxkee/avif-coder) and [JXL Coder](https://github.com/awxkee/jxl-coder) libraries which provide avif, heic, heif and jxl support. - [Aire](https://github.com/awxkee/aire) and [Trickle](https://github.com/T8RIN/Trickle) for creating and applying filters to the images on CPU using native cpp code. # 📐 App Architecture See Modules Graph at [ARCHITECTURE.md](https://github.com/T8RIN/ImageToolbox/blob/master/ARCHITECTURE.md)
# # 🌐 Translation You can help translate Image Toolbox into your language on [Hosted Weblate](https://hosted.weblate.org/engage/image-resizer/) [![Состояние перевода](https://hosted.weblate.org/widgets/image-resizer/-/horizontal-auto.svg)](https://hosted.weblate.org/engage/image-resizer/)
[![Translation status](https://hosted.weblate.org/widgets/image-resizer/-/image-resizer/287x66-black.png)](https://hosted.weblate.org/engage/image-resizer/) # ❤️ Find this repository useful? Support it by joining **[stargazers](https://github.com/t8rin/ImageToolbox/stargazers)** for this repository. :star:
And **[follow](https://github.com/t8rin)** me for my next creations! 🤩 # ⭐ Star History Star History Chart ![](https://repobeats.axiom.co/api/embed/c62092c6ec0d00e67496223d50e39f48a582c532.svg) # 📢 Contributors # 🔒 Signing Certificate Hashes SHA-256: `20d7689de0874f00015ea3e31fa067c15c03457d362d41d5e793db3a864fa534` SHA-1: `d69eacb30eeae804e8b72d2384c3c616b1906785` MD5: `db6f6b76c503d31099e4754e676353cf` For more info, see [wiki](https://github.com/T8RIN/ImageToolbox/wiki/FAQ#how-can-i-verify-my-download-of-imagetoolbox-is-legitimate) # ⚖️ License ```xml Designed and developed by 2023 T8RIN 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, softwaredistributed 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 andlimitations under the License. ``` ================================================ FILE: app/.gitignore ================================================ /build/ /release/ /foss/ /market/ /jxl/ /build /release /foss /market /jxl ================================================ FILE: app/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnstableApiUsage") plugins { alias(libs.plugins.image.toolbox.application) alias(libs.plugins.image.toolbox.hilt) } android { val supportedAbi = arrayOf("armeabi-v7a", "arm64-v8a", "x86_64") namespace = "com.t8rin.imagetoolbox" defaultConfig { vectorDrawables.useSupportLibrary = true //Maintained for compatibility with old version applicationId = "ru.tech.imageresizershrinker" versionCode = libs.versions.versionCode.get().toIntOrNull() versionName = System.getenv("VERSION_NAME") ?: libs.versions.versionName.get() ndk { abiFilters.clear() //noinspection ChromeOsAbiSupport abiFilters += supportedAbi.toSet() } } androidResources { generateLocaleConfig = true } flavorDimensions += "app" productFlavors { create("foss") { dimension = "app" versionNameSuffix = "-foss" extra.set("gmsEnabled", false) } create("market") { dimension = "app" extra.set("gmsEnabled", true) } } buildTypes { debug { applicationIdSuffix = ".debug" resValue("string", "app_launcher_name", "Image Toolbox DEBUG") resValue("string", "file_provider", "com.t8rin.imagetoolbox.fileprovider.debug") } release { isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) resValue("string", "app_launcher_name", "Image Toolbox") resValue("string", "file_provider", "com.t8rin.imagetoolbox.fileprovider") } create("benchmark") { initWith(buildTypes.getByName("release")) signingConfig = signingConfigs.getByName("debug") matchingFallbacks += listOf("release") isMinifyEnabled = false isShrinkResources = false } } splits { abi { // Detect app bundle and conditionally disable split abis // This is needed due to a "Sequence contains more than one matching element" error // present since AGP 8.9.0, for more info see: // https://issuetracker.google.com/issues/402800800 // AppBundle tasks usually contain "bundle" in their name //noinspection WrongGradleMethod val isBuildingBundle = gradle.startParameter.taskNames.any { it.lowercase().contains("bundle") } // Disable split abis when building appBundle isEnable = !isBuildingBundle reset() //noinspection ChromeOsAbiSupport include(*supportedAbi) isUniversalApk = true } } lint { disable += "Instantiatable" } packaging { jniLibs { keepDebugSymbols.add("**/*.so") pickFirsts.add("lib/*/libcoder.so") pickFirsts.add("**/libc++_shared.so") pickFirsts.add("**/libdatstore_shared_counter.so") useLegacyPackaging = true } resources { excludes += "META-INF/" excludes += "kotlin/" excludes += "org/" excludes += ".properties" excludes += ".bin" excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF" } } buildFeatures { resValues = true } } base { archivesName = "image-toolbox-${android.defaultConfig.versionName}" } aboutLibraries { export.excludeFields.addAll("generated") } dependencies { implementation(projects.feature.root) implementation(projects.feature.mediaPicker) implementation(projects.feature.quickTiles) implementation(projects.lib.opencvTools) implementation(projects.lib.neuralTools) implementation(projects.lib.collages) implementation(libs.bouncycastle.pkix) implementation(libs.bouncycastle.provider) implementation(libs.pdfbox) "marketImplementation"(libs.quickie.bundled) "fossImplementation"(libs.quickie.foss) } dependencySubstitution { substitute( dependency = "com.caverock:androidsvg-aar:1.4", using = "com.github.deckerst:androidsvg:cc9d59a88f" ) } afterEvaluate { android.productFlavors.forEach { flavor -> tasks.matching { task -> listOf("GoogleServices", "Crashlytics").any { task.name.contains(it) }.and( task.name.contains( flavor.name.replaceFirstChar(Char::uppercase) ) ) }.forEach { task -> task.enabled = flavor.extra.get("gmsEnabled") == true } } } fun Project.dependencySubstitution(action: DependencySubstitutions.() -> Unit) { allprojects { configurations.all { resolutionStrategy.dependencySubstitution(action) } } } fun DependencySubstitutions.substitute( dependency: String, using: String ) { substitute(module(dependency)).using(module(using)) } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keepclassmembers class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator CREATOR; } -keep class * implements com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter -keepclassmembers class * implements com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter { (...); } -keep class * implements com.t8rin.imagetoolbox.core.filters.domain.model.Filter -keepclassmembers class * implements com.t8rin.imagetoolbox.core.filters.domain.model.Filter { (...); } -keepclassmembers class com.t8rin.imagetoolbox.core.filters.** { (...); } -keep class com.t8rin.imagetoolbox.core.filters.** -keep class com.t8rin.imagetoolbox.core.filters.* # Please add these rules to your existing keep rules in order to suppress warnings. # This is generated automatically by the Android Gradle plugin. -dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.bouncycastle.jsse.BCSSLSocket -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider -dontwarn org.conscrypt.Conscrypt$Version -dontwarn org.conscrypt.Conscrypt -dontwarn org.conscrypt.ConscryptHostnameVerifier -dontwarn org.openjsse.javax.net.ssl.SSLParameters -dontwarn org.openjsse.javax.net.ssl.SSLSocket -dontwarn org.openjsse.net.ssl.OpenJSSE -keep class org.beyka.tiffbitmapfactory.**{ *; } -keep class org.bouncycastle.jcajce.provider.** { *; } -keep class org.bouncycastle.jce.provider.** { *; } -dontwarn com.google.re2j.Matcher -dontwarn com.google.re2j.Pattern -keepclassmembers class * { public java.lang.String name(); } -keep enum * { *; } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keepclassmembers class * extends java.lang.Enum { public java.lang.String name(); } -keep class ai.onnxruntime.** { *; } -keep class com.google.firebase.crashlytics.** { *; } -keep class com.google.firebase.analytics.** { *; } -keep class androidx.pdf.** { *; } -keepnames class androidx.pdf.** { *; } # Moshi reflective adapters need generic signatures in release. -keepattributes Signature -keepattributes *Annotation* -keep class kotlin.Metadata { *; } -keep class com.t8rin.imagetoolbox.feature.markup_layers.data.project.** { *; } -keep class com.t8rin.imagetoolbox.feature.markup_layers.data.project.** -keep class com.t8rin.imagetoolbox.feature.markup_layers.data.project.* ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/AppActivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation import android.content.Intent import androidx.compose.runtime.Composable import com.arkivanov.decompose.retainedComponent import com.t8rin.imagetoolbox.core.ui.utils.ComposeActivity import com.t8rin.imagetoolbox.feature.root.presentation.RootContent import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint class AppActivity : ComposeActivity() { @Inject lateinit var rootComponentFactory: RootComponent.Factory private val component: RootComponent by lazy { retainedComponent(factory = rootComponentFactory::invoke) } override fun handleIntent(intent: Intent) = component.handleDeeplinks(intent) @Composable override fun Content() = RootContent(component = component) } ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/ImageToolboxApplication.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components import com.t8rin.imagetoolbox.app.presentation.components.functions.attachLogWriter import com.t8rin.imagetoolbox.app.presentation.components.functions.initCollages import com.t8rin.imagetoolbox.app.presentation.components.functions.initColorNames import com.t8rin.imagetoolbox.app.presentation.components.functions.initNeuralTool import com.t8rin.imagetoolbox.app.presentation.components.functions.initOpenCV import com.t8rin.imagetoolbox.app.presentation.components.functions.initPdfBox import com.t8rin.imagetoolbox.app.presentation.components.functions.initQrScanner import com.t8rin.imagetoolbox.app.presentation.components.functions.injectBaseComponent import com.t8rin.imagetoolbox.app.presentation.components.functions.registerSecurityProviders import com.t8rin.imagetoolbox.app.presentation.components.functions.setupFlags import com.t8rin.imagetoolbox.app.presentation.components.utils.isMain import com.t8rin.imagetoolbox.core.crash.presentation.components.applyGlobalExceptionHandler import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.ui.utils.ComposeApplication import com.t8rin.imagetoolbox.core.utils.initAppContext import dagger.hilt.android.HiltAndroidApp import io.ktor.client.HttpClient import javax.inject.Inject @HiltAndroidApp class ImageToolboxApplication : ComposeApplication() { @Inject lateinit var keepAliveService: KeepAliveService @Inject lateinit var appScope: AppScope @Inject lateinit var httpClient: HttpClient private var isSetupCompleted: Boolean = false override fun onCreate() { super.onCreate() runSetup() } override fun runSetup() { if (isSetupCompleted) return if (isMain()) { setupFlags() initAppContext() initOpenCV() initNeuralTool() initColorNames() initQrScanner() attachLogWriter() applyGlobalExceptionHandler() registerSecurityProviders() initPdfBox() injectBaseComponent() initCollages() isSetupCompleted = true } } } ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/AttachLogWriter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import android.app.Application import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.DeviceInfo import com.t8rin.logger.Logger import com.t8rin.logger.attachLogWriter internal fun Application.attachLogWriter() { Logger.attachLogWriter( context = this@attachLogWriter, fileProvider = getString(R.string.file_provider), logsFilename = "image_toolbox_logs.txt", startupLog = Logger.Log( tag = "Device Info", message = "--${DeviceInfo.get()}--", level = Logger.Level.Info ), isSyncCreate = false ) } ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitCollages.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import com.t8rin.collages.public.CollageConstants import com.t8rin.imagetoolbox.core.data.image.utils.static fun initCollages() = CollageConstants.requestMapper { static() } ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitColorNames.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import com.t8rin.colors.parser.ColorNameParser import com.t8rin.imagetoolbox.app.presentation.components.ImageToolboxApplication import kotlinx.coroutines.launch internal fun ImageToolboxApplication.initColorNames() = appScope.launch { ColorNameParser.init(this@initColorNames) } ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitNeuralTool.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import com.t8rin.imagetoolbox.app.presentation.components.ImageToolboxApplication import com.t8rin.imagetoolbox.core.domain.HF_BASE_URL import com.t8rin.neural_tools.NeuralTool internal fun ImageToolboxApplication.initNeuralTool() = NeuralTool.init( context = this, httpClient = httpClient, baseUrl = HF_BASE_URL ) ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitOpenCV.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import android.app.Application import com.t8rin.opencv_tools.utils.OpenCV internal fun Application.initOpenCV() = OpenCV.init(this) ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitPdfBox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import android.app.Application import com.tom_roush.pdfbox.android.PDFBoxResourceLoader internal fun Application.initPdfBox() = PDFBoxResourceLoader.init(this) ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InitQrScanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.applyPadding import com.t8rin.opencv_tools.qr_prepare.QrPrepareHelper import io.github.g00fy2.quickie.extensions.QrProcessor internal fun initQrScanner() = QrProcessor.setProcessor(::prepareBitmap) private fun prepareBitmap(bitmap: Bitmap): Bitmap = QrPrepareHelper.prepareQrForDecode(bitmap.applyPadding(100)) ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/InjectBaseComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import com.t8rin.imagetoolbox.app.presentation.components.ImageToolboxApplication import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent internal fun ImageToolboxApplication.injectBaseComponent() = BaseComponent.inject(keepAliveService) ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/RegisterSecurityProviders.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import com.t8rin.imagetoolbox.core.domain.model.CipherType import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.logger.makeLog import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.operator.DefaultAlgorithmNameFinder import java.security.Provider import java.security.Security internal fun registerSecurityProviders() { initBouncyCastle() HashingType.registerSecurityMessageDigests( Security.getAlgorithms("MessageDigest").filterNotNull() ) val finder = DefaultAlgorithmNameFinder() CipherType.registerSecurityCiphers( Security.getAlgorithms("Cipher").filterNotNull().mapNotNull { cipher -> if (CipherType.BROKEN.any { cipher.contains(it, true) }) return@mapNotNull null val oid = cipher.removePrefix("OID.") if (oid.all { it.isDigit() || it.isWhitespace() || it == '.' }) { CipherType.getInstance( cipher = cipher, name = finder.getAlgorithmName( ASN1ObjectIdentifier(oid) ) ) } else { CipherType.getInstance( cipher = cipher ) }.also { val extraExclude = it.cipher == "DES" || it.name == "DES/CBC" || it.name == "THREEFISH-512" || it.name == "THREEFISH-1024" || it.name == "CCM" if (extraExclude) return@mapNotNull null } } ) } private fun initBouncyCastle() { if (Security.getProvider(WORKAROUND_NAME) != null) return try { logProviders("OLD") Security.addProvider(BouncyCastleWorkaroundProvider()) logProviders("NEW") } catch (e: Exception) { e.makeLog() "Failed to register BouncyCastleWorkaroundProvider".makeLog() } } private fun logProviders(tag: String): Int { val providers = Security.getProviders() providers.forEachIndexed { index, provider -> "$tag [$index]: ${provider.name} - ${provider.info}".makeLog("Providers") } return providers.size } private class BouncyCastleWorkaroundProvider( bouncyCastleProvider: Provider = BouncyCastleProvider() ) : Provider( WORKAROUND_NAME, bouncyCastleProvider.version, bouncyCastleProvider.info ) { init { for ((key, value) in bouncyCastleProvider.entries) { put(key.toString(), value.toString()) } } } private const val WORKAROUND_NAME = "BC_WORKAROUND" ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/functions/SetupFlags.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.functions import androidx.compose.foundation.ComposeFoundationFlags.isPausableCompositionInPrefetchEnabled import androidx.compose.material3.ComposeMaterial3Flags.isCheckboxStylingFixEnabled import com.arkivanov.decompose.DecomposeSettings internal fun setupFlags() { isCheckboxStylingFixEnabled = true DecomposeSettings.update { it.copy(duplicateConfigurationsEnabled = true) } isPausableCompositionInPrefetchEnabled = true } ================================================ FILE: app/src/main/java/com/t8rin/imagetoolbox/app/presentation/components/utils/GetProcessName.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.app.presentation.components.utils import android.annotation.SuppressLint import android.app.ActivityManager import android.app.Application import android.content.Context.ACTIVITY_SERVICE import android.os.Build.VERSION.SDK_INT import com.t8rin.logger.makeLog internal fun Application.isMain(): Boolean = getProcessName().makeLog("Current Process") == packageName.makeLog("Current packageName") @SuppressLint("PrivateApi") internal fun Application.getProcessName(): String? { if (SDK_INT >= 28) { return Application.getProcessName() } // Try using ActivityThread to determine the current process name. try { val activityThread = Class.forName( "android.app.ActivityThread", false, this::class.java.getClassLoader() ) val packageName: Any? val currentProcessName = activityThread.getDeclaredMethod("currentProcessName") currentProcessName.isAccessible = true packageName = currentProcessName.invoke(null) if (packageName is String) { return packageName } } catch (exception: Throwable) { exception.makeLog() } // Fallback to the most expensive way val pid: Int = android.os.Process.myPid() val am = getSystemService(ACTIVITY_SERVICE) as ActivityManager val processes = am.runningAppProcesses if (processes != null && processes.isNotEmpty()) { for (process in processes) { if (process.pid == pid) { return process.processName } } } return null } ================================================ FILE: app/src/main/res/resources.properties ================================================ # # ImageToolbox is an image editor for android # Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # # 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. # # You should have received a copy of the Apache License # along with this program. If not, see . # unqualifiedResLocale=en ================================================ FILE: app/src/market/debug/google-services.json ================================================ { "project_info": { "project_number": "698299287298", "project_id": "imagetoolbox-13e3c", "storage_bucket": "imagetoolbox-13e3c.appspot.com" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:698299287298:android:11af7edf43198644b6e41b", "android_client_info": { "package_name": "ru.tech.imageresizershrinker.debug" } }, "oauth_client": [ { "client_id": "698299287298-5ukroo831v2bn1vj8ff0oa7ch5h94mfg.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyBH5QgaBw6XAfuxhPVJzq-r9s8ZMFalXzU" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [ { "client_id": "698299287298-5ukroo831v2bn1vj8ff0oa7ch5h94mfg.apps.googleusercontent.com", "client_type": 3 } ] } } } ], "configuration_version": "1" } ================================================ FILE: app/src/market/release/google-services.json ================================================ { "project_info": { "project_number": "698299287298", "project_id": "imagetoolbox-13e3c", "storage_bucket": "imagetoolbox-13e3c.appspot.com" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:698299287298:android:11af7edf43198644b6e41b", "android_client_info": { "package_name": "ru.tech.imageresizershrinker" } }, "oauth_client": [ { "client_id": "698299287298-5ukroo831v2bn1vj8ff0oa7ch5h94mfg.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyBH5QgaBw6XAfuxhPVJzq-r9s8ZMFalXzU" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [ { "client_id": "698299287298-5ukroo831v2bn1vj8ff0oa7ch5h94mfg.apps.googleusercontent.com", "client_type": 3 } ] } } } ], "configuration_version": "1" } ================================================ FILE: benchmark/.gitignore ================================================ /build ================================================ FILE: benchmark/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnstableApiUsage") import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.test") id("androidx.baselineprofile") } android { namespace = "com.t8rin.imagetoolbox.benchmark" compileSdk = libs.versions.androidCompileSdk.get().toIntOrNull() defaultConfig { minSdk = 23 targetSdk = libs.versions.androidTargetSdk.get().toIntOrNull() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility = JavaVersion.toVersion(libs.versions.jvmTarget.get()) targetCompatibility = JavaVersion.toVersion(libs.versions.jvmTarget.get()) } kotlin { compilerOptions { jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get())) } } buildTypes { // This benchmark buildType is used for benchmarking, and should function like your // release build (for example, with minification on). It"s signed with a debug key // for easy local/CI testing. create("benchmark") { isDebuggable = true signingConfig = getByName("debug").signingConfig matchingFallbacks += listOf("release") } } flavorDimensions += listOf("app") productFlavors { create("foss") { dimension = "app" } create("market") { dimension = "app" } } targetProjectPath = ":app" experimentalProperties["android.experimental.self-instrumenting"] = true } dependencies { implementation(libs.androidx.test.ext.junit) implementation(libs.espresso) implementation(libs.uiautomator) implementation(libs.benchmark.macro.junit4) } androidComponents { beforeVariants(selector().all()) { it.enable = it.buildType == "benchmark" } } ================================================ FILE: benchmark/src/main/AndroidManifest.xml ================================================ ================================================ FILE: benchmark/src/main/java/com/t8rin/imagetoolbox/benchmark/BaselineProfileGenerator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.benchmark import androidx.annotation.RequiresApi import androidx.benchmark.macro.junit4.BaselineProfileRule import org.junit.Rule import org.junit.Test @RequiresApi(28) class BaselineProfileGenerator { @get:Rule val baselineProfileRule = BaselineProfileRule() @Test fun startup() = baselineProfileRule.collect( packageName = "com.t8rin.imagetoolbox", includeInStartupProfile = true, profileBlock = { startActivityAndWait() device.pressBack() } ) } ================================================ FILE: build-logic/.gitignore ================================================ *.iml .gradle /local.properties .DS_Store /build /app/release /captures .externalNativeBuild .cxx /.idea ================================================ FILE: build-logic/convention/.gitignore ================================================ /build ================================================ FILE: build-logic/convention/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { `kotlin-dsl` } group = "com.t8rin.imagetoolbox.buildlogic" // Configure the build-logic plugins to target JDK 17 // This matches the JDK used to build the project, and is not related to what is running on device. java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } tasks.withType().configureEach { compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } } dependencies { compileOnly(libs.agp.gradle) compileOnly(libs.kotlin.gradle) compileOnly(libs.detekt.gradle) compileOnly(libs.compose.compiler.gradle) compileOnly(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) } gradlePlugin { // register the convention plugin plugins { register("imageToolboxLibrary") { id = "image.toolbox.library" implementationClass = "ImageToolboxLibraryPlugin" } register("imageToolboxHiltPlugin") { id = "image.toolbox.hilt" implementationClass = "ImageToolboxHiltPlugin" } register("imageToolboxLibraryFeature") { id = "image.toolbox.feature" implementationClass = "ImageToolboxLibraryFeaturePlugin" } register("imageToolboxLibraryComposePlugin") { id = "image.toolbox.compose" implementationClass = "ImageToolboxLibraryComposePlugin" } register("imageToolboxApplicationPlugin") { id = "image.toolbox.application" implementationClass = "ImageToolboxApplicationPlugin" } } } ================================================ FILE: build-logic/convention/src/main/kotlin/ImageToolboxApplicationPlugin.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ import com.android.build.api.dsl.ApplicationExtension import com.t8rin.imagetoolbox.configureCompose import com.t8rin.imagetoolbox.configureDetekt import com.t8rin.imagetoolbox.configureKotlinAndroid import com.t8rin.imagetoolbox.core import com.t8rin.imagetoolbox.crash import com.t8rin.imagetoolbox.data import com.t8rin.imagetoolbox.di import com.t8rin.imagetoolbox.domain import com.t8rin.imagetoolbox.implementation import com.t8rin.imagetoolbox.libs import com.t8rin.imagetoolbox.projects import com.t8rin.imagetoolbox.resources import com.t8rin.imagetoolbox.settings import com.t8rin.imagetoolbox.ui import com.t8rin.imagetoolbox.utils import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType @Suppress("UNUSED") class ImageToolboxApplicationPlugin : Plugin { override fun apply(target: Project) { with(target) { apply(plugin = "com.android.application") apply(plugin = "kotlin-parcelize") apply(plugin = "com.google.gms.google-services") apply(plugin = "com.google.firebase.crashlytics") apply(plugin = "com.mikepenz.aboutlibraries.plugin.android") apply(plugin = "org.jetbrains.kotlin.plugin.compose") apply(plugin = "io.gitlab.arturbosch.detekt") configureDetekt(extensions.getByType()) extensions.configure { configureKotlinAndroid( commonExtension = this, createFlavors = false ) defaultConfig.targetSdk = libs.versions.androidTargetSdk.get().toIntOrNull() } dependencies { implementation(libs.androidxCore) implementation(projects.core.data) implementation(projects.core.ui) implementation(projects.core.domain) implementation(projects.core.resources) implementation(projects.core.settings) implementation(projects.core.di) implementation(projects.core.crash) implementation(projects.core.utils) } configureCompose(extensions.getByType()) } } } ================================================ FILE: build-logic/convention/src/main/kotlin/ImageToolboxHiltPlugin.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ import com.t8rin.imagetoolbox.implementation import com.t8rin.imagetoolbox.ksp import com.t8rin.imagetoolbox.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.dependencies @Suppress("UNUSED") class ImageToolboxHiltPlugin : Plugin { override fun apply(target: Project) { with(target) { apply(plugin = "dagger.hilt.android.plugin") apply(plugin = "com.google.devtools.ksp") dependencies { implementation(libs.dagger.hilt.android) ksp(libs.dagger.hilt.compiler) } } } } ================================================ FILE: build-logic/convention/src/main/kotlin/ImageToolboxLibraryComposePlugin.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ import com.android.build.api.dsl.LibraryExtension import com.t8rin.imagetoolbox.configureCompose import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.getByType @Suppress("UNUSED") class ImageToolboxLibraryComposePlugin : Plugin { override fun apply(target: Project) { with(target) { apply(plugin = "com.android.library") apply(plugin = "org.jetbrains.kotlin.plugin.compose") configureCompose(extensions.getByType()) } } } ================================================ FILE: build-logic/convention/src/main/kotlin/ImageToolboxLibraryFeaturePlugin.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ import com.t8rin.imagetoolbox.configureDetekt import com.t8rin.imagetoolbox.core import com.t8rin.imagetoolbox.crash import com.t8rin.imagetoolbox.data import com.t8rin.imagetoolbox.di import com.t8rin.imagetoolbox.domain import com.t8rin.imagetoolbox.implementation import com.t8rin.imagetoolbox.projects import com.t8rin.imagetoolbox.resources import com.t8rin.imagetoolbox.settings import com.t8rin.imagetoolbox.ui import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType @Suppress("UNUSED") class ImageToolboxLibraryFeaturePlugin : Plugin { override fun apply(target: Project) { with(target) { configureDetekt(extensions.getByType()) dependencies { implementation(projects.core.data) implementation(projects.core.ui) implementation(projects.core.domain) implementation(projects.core.resources) implementation(projects.core.settings) implementation(projects.core.di) implementation(projects.core.crash) } } } } ================================================ FILE: build-logic/convention/src/main/kotlin/ImageToolboxLibraryPlugin.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ import com.android.build.api.dsl.LibraryExtension import com.t8rin.imagetoolbox.configureDetekt import com.t8rin.imagetoolbox.configureKotlinAndroid import com.t8rin.imagetoolbox.implementation import com.t8rin.imagetoolbox.libs import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType @Suppress("UNUSED") class ImageToolboxLibraryPlugin : Plugin { override fun apply(target: Project) { with(target) { with(pluginManager) { apply("com.android.library") apply("kotlin-parcelize") apply("kotlinx-serialization") apply(libs.detekt.gradle.get().group) } configureDetekt(extensions.getByType()) extensions.configure { configureKotlinAndroid(this) defaultConfig.minSdk = libs.versions.androidMinSdk.get().toIntOrNull() } dependencies { implementation(libs.androidxCore) } } } } ================================================ FILE: build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureCompose.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox import com.android.build.api.dsl.CommonExtension import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension internal fun Project.configureCompose( commonExtension: CommonExtension ) { commonExtension.apply { buildFeatures.apply { compose = true } dependencies { implementation(libs.androidx.material3) implementation(libs.window.sizeclass) implementation(libs.androidx.material) implementation(libs.icons.extended) implementation(libs.compose.preview) } } extensions.configure { stabilityConfigurationFiles.addAll( rootProject.layout.projectDirectory.file("compose_compiler_config.conf") ) } } ================================================ FILE: build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureDetekt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox import io.gitlab.arturbosch.detekt.Detekt import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Project import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.named internal fun Project.configureDetekt(extension: DetektExtension) = extension.apply { tasks.named("detekt") { reports { xml.required.set(true) html.required.set(true) txt.required.set(true) sarif.required.set(true) md.required.set(true) } } dependencies { detektPlugins(libs.detekt.formatting) detektPlugins(libs.detekt.compose) } } ================================================ FILE: build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ConfigureKotlinAndroid.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox import com.android.build.api.dsl.CommonExtension import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile internal fun Project.configureKotlinAndroid( commonExtension: CommonExtension, createFlavors: Boolean = true ) { commonExtension.apply { compileSdk = libs.versions.androidCompileSdk.get().toIntOrNull() compileSdkExtension = libs.versions.androidCompileSdkExtension.get().toIntOrNull() defaultConfig.apply { minSdk = libs.versions.androidMinSdk.get().toIntOrNull() } if (createFlavors) { flavorDimensions += "app" productFlavors.apply { create("foss") { dimension = "app" } create("market") { dimension = "app" } } } compileOptions.apply { sourceCompatibility = javaVersion targetCompatibility = javaVersion isCoreLibraryDesugaringEnabled = true } buildFeatures.apply { compose = false aidl = false shaders = false buildConfig = false resValues = false } packaging.apply { resources { excludes.add("/META-INF/{AL2.0,LGPL2.1}") } } lint.apply { disable += "UsingMaterialAndMaterial3Libraries" disable += "ModifierParameter" } } configureKotlin() dependencies { coreLibraryDesugaring(libs.desugaring) } } val Project.javaVersion: JavaVersion get() = JavaVersion.toVersion( libs.versions.jvmTarget.get() ) /** * Configure base Kotlin options */ private inline fun Project.configureKotlin() = configure { val args = listOf( "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.compose.material3.ExperimentalMaterial3ExpressiveApi", "-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi", "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", "-opt-in=androidx.compose.ui.unit.ExperimentalUnitApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.FlowPreview", "-opt-in=androidx.compose.material.ExperimentalMaterialApi", "-opt-in=com.arkivanov.decompose.ExperimentalDecomposeApi", "-opt-in=coil3.annotation.ExperimentalCoilApi", "-opt-in=coil3.annotation.DelicateCoilApi", "-opt-in=kotlin.contracts.ExperimentalContracts", "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", "-opt-in=androidx.compose.ui.text.ExperimentalTextApi", "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi", "-Xannotation-default-target=param-property", "-XXLanguage:+PropertyParamAnnotationDefaultTargetMode" ) // Treat all Kotlin warnings as errors (disabled by default) // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties val warningsAsErrors: String? by project when (this) { is KotlinAndroidProjectExtension -> compilerOptions is KotlinJvmProjectExtension -> compilerOptions else -> error("Unsupported project extension $this ${T::class}") }.apply { jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) allWarningsAsErrors = warningsAsErrors.toBoolean() freeCompilerArgs.addAll(args) } tasks.withType().configureEach { compilerOptions.freeCompilerArgs.addAll(args) } } ================================================ FILE: build-logic/convention/src/main/kotlin/com/t8rin/imagetoolbox/ProjectExtensions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Project import org.gradle.api.artifacts.MinimalExternalModuleDependency import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.DependencyHandlerScope import org.gradle.kotlin.dsl.the val Project.libs get(): LibrariesForLibs = the() val Project.projects get(): Projects = object : Projects, ProjectHolder { override fun project(path: String): Project = this@projects.project(path) } val Projects.core get(): CoreProjects = object : CoreProjects, ProjectHolder by this {} val CoreProjects.data get(): ProjectLibrary = project(":core:data") val CoreProjects.ui get(): ProjectLibrary = project(":core:ui") val CoreProjects.domain get(): ProjectLibrary = project(":core:domain") val CoreProjects.resources get(): ProjectLibrary = project(":core:resources") val CoreProjects.settings get(): ProjectLibrary = project(":core:settings") val CoreProjects.di get(): ProjectLibrary = project(":core:di") val CoreProjects.crash get(): ProjectLibrary = project(":core:crash") val CoreProjects.utils get(): ProjectLibrary = project(":core:utils") fun DependencyHandlerScope.implementation( dependency: Library ) = add("implementation", dependency) fun DependencyHandlerScope.coreLibraryDesugaring( dependency: Library ) = add("coreLibraryDesugaring", dependency) fun DependencyHandlerScope.implementation( dependency: ProjectLibrary ) = add("implementation", dependency) fun DependencyHandlerScope.ksp( dependency: Library ) = add("ksp", dependency) fun DependencyHandlerScope.detektPlugins( dependency: Library ) = add("detektPlugins", dependency) typealias Library = Provider typealias ProjectLibrary = Project interface Projects : ProjectHolder interface CoreProjects : ProjectHolder interface ProjectHolder { fun project(path: String): Project } ================================================ FILE: build-logic/settings.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnstableApiUsage") dependencyResolutionManagement { repositories { google() mavenCentral() } versionCatalogs { create("libs") { // make sure the file rootProject/gradle/verison.toml exists! from(files("../gradle/libs.versions.toml")) } } } rootProject.name = "build-logic" include(":convention") ================================================ FILE: build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ buildscript { repositories { gradlePluginPortal() google() mavenCentral() maven { setUrl("https://jitpack.io") } } dependencies { classpath(libs.kotlinx.serialization.gradle) classpath(libs.ksp.gradle) classpath(libs.agp.gradle) classpath(libs.kotlin.gradle) classpath(libs.hilt.gradle) classpath(libs.gms.gradle) classpath(libs.firebase.crashlytics.gradle) classpath(libs.baselineprofile.gradle) classpath(libs.detekt.gradle) classpath(libs.aboutlibraries.gradle) classpath(libs.compose.compiler.gradle) } } tasks.register("clean", Delete::class) { delete(rootProject.layout.buildDirectory) } ================================================ FILE: compose_compiler_config.conf ================================================ java.time.ZoneId java.time.ZoneOffset java.text.DecimalFormat java.text.NumberFormat java.time.Instant java.time.Duration java.time.LocalDate java.time.LocalDateTime java.time.LocalTime java.time.ZonedDateTime java.util.Date java.util.Locale java.io.File kotlin.collections.* kotlin.time.Duration android.net.Uri android.graphics.Bitmap androidx.compose.ui.graphics.painter.Painter androidx.compose.ui.graphics.vector.ImageVector androidx.compose.ui.graphics.ImageBitmap com.t8rin.imagetoolbox.core.crash.* com.t8rin.imagetoolbox.core.filters.* com.t8rin.imagetoolbox.core.resources.* com.t8rin.imagetoolbox.core.data.* com.t8rin.imagetoolbox.core.domain.* com.t8rin.imagetoolbox.core.ui.* com.t8rin.imagetoolbox.core.settings.* com.t8rin.imagetoolbox.core.* com.t8rin.imagetoolbox.feature.* nl.dionsegijn.konfetti.core.* ================================================ FILE: core/crash/.gitignore ================================================ /build ================================================ FILE: core/crash/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.core.crash" dependencies { implementation(projects.core.ui) implementation(projects.core.settings) "marketImplementation"(platform(libs.firebase.bom)) "marketImplementation"(libs.firebase.crashlytics) "marketImplementation"(libs.firebase.analytics) } ================================================ FILE: core/crash/src/foss/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.data import com.t8rin.imagetoolbox.core.domain.remote.AnalyticsManager internal object AnalyticsManagerImpl : AnalyticsManager { override var allowCollectCrashlytics: Boolean = false override var allowCollectAnalytics: Boolean = false override fun updateAnalyticsCollectionEnabled(value: Boolean) = Unit override fun updateAllowCollectCrashlytics(value: Boolean) = Unit override fun sendReport(throwable: Throwable) = Unit override fun registerScreenOpen(screenName: String) = Unit } ================================================ FILE: core/crash/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/di/CrashModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.di import com.t8rin.imagetoolbox.core.crash.data.AnalyticsManagerImpl import com.t8rin.imagetoolbox.core.domain.remote.AnalyticsManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal object CrashModule { @Provides @Singleton fun analyticsManager(): AnalyticsManager = AnalyticsManagerImpl } ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/CrashActivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation import androidx.compose.runtime.Composable import com.arkivanov.decompose.retainedComponent import com.t8rin.imagetoolbox.core.crash.presentation.components.CrashHandler import com.t8rin.imagetoolbox.core.crash.presentation.components.CrashRootContent import com.t8rin.imagetoolbox.core.crash.presentation.screenLogic.CrashComponent import com.t8rin.imagetoolbox.core.ui.utils.ComposeActivity import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint class CrashActivity : ComposeActivity(), CrashHandler { @Inject lateinit var componentFactory: CrashComponent.Factory private val component: CrashComponent by lazy { retainedComponent { componentContext -> componentFactory( componentContext = componentContext, crashInfo = getCrashInfo() ) } } @Composable override fun Content() = CrashRootContent(component = component) } ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashActionButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.TELEGRAM_GROUP_LINK import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Github import com.t8rin.imagetoolbox.core.resources.icons.MobileShare import com.t8rin.imagetoolbox.core.resources.icons.Telegram import com.t8rin.imagetoolbox.core.ui.theme.Black import com.t8rin.imagetoolbox.core.ui.theme.Blue import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable internal fun CrashActionButtons( onCopyCrashInfo: () -> Unit, onShareLogs: () -> Unit, githubLink: String ) { BoxWithConstraints( modifier = Modifier.fillMaxWidth() ) { val containerWidth = maxWidth Column { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { val linkHandler = LocalUriHandler.current LargeEnhancedButton( onClick = { onCopyCrashInfo() linkHandler.openUri(TELEGRAM_GROUP_LINK) }, modifier = Modifier .weight(1f) .width(containerWidth / 2f) .padding(end = 8.dp), containerColor = Blue, contentColor = White, icon = Icons.Rounded.Telegram, text = stringResource(R.string.contact_me) ) LargeEnhancedButton( onClick = { onCopyCrashInfo() linkHandler.openUri(githubLink) }, modifier = Modifier .weight(1f) .width(containerWidth / 2f), containerColor = Black, contentColor = White, icon = Icons.Rounded.Github, text = stringResource(id = R.string.create_issue), ) } Spacer(modifier = Modifier.height(12.dp)) LargeEnhancedButton( onClick = { onCopyCrashInfo() onShareLogs() }, modifier = Modifier.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, icon = Icons.Rounded.MobileShare, text = stringResource(id = R.string.send_logs), ) } } } @Composable private fun LargeEnhancedButton( onClick: () -> Unit, containerColor: Color, contentColor: Color, icon: ImageVector, text: String, modifier: Modifier = Modifier, ) { EnhancedButton( onClick = onClick, modifier = modifier .height(48.dp), containerColor = containerColor, contentColor = contentColor, borderColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = containerColor ), contentPadding = ButtonDefaults.ButtonWithIconContentPadding ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Icon( imageVector = icon, contentDescription = text ) Spacer(modifier = Modifier.width(8.dp)) AutoSizeText( text = text, maxLines = 1 ) } } } ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashAttentionCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageToolboxBroken import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun CrashAttentionCard() { val isNightMode = LocalSettingsState.current.isNightMode Spacer(modifier = Modifier.height(16.dp)) Column( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.large, resultPadding = 16.dp, color = takeColorFromScheme { if (isNightMode) { errorContainer.blend(surfaceContainerLow, 0.75f) } else { errorContainer.blend(surfaceContainerLow, 0.65f) } } ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { val contentColor = takeColorFromScheme { if (isNightMode) { onError.blend(onSurface, 0.75f) } else { error.blend(onSurface, 0.6f) } } Icon( imageVector = Icons.Outlined.ImageToolboxBroken, contentDescription = null, modifier = Modifier .size(80.dp) .statusBarsPadding(), tint = contentColor ) Spacer(modifier = Modifier.height(16.dp)) Text( text = stringResource(R.string.crash_title), fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, fontSize = 22.sp, lineHeight = 26.sp, color = contentColor ) Spacer(modifier = Modifier.height(8.dp)) Text( text = stringResource(R.string.crash_subtitle), textAlign = TextAlign.Center, fontSize = 16.sp, lineHeight = 20.sp, color = contentColor ) } } ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashBottomButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.components import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material.icons.rounded.RestartAlt import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButtonType import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable internal fun CrashBottomButtons( modifier: Modifier, onCopy: () -> Unit, onRestartApp: () -> Unit ) { Row( modifier = modifier .padding(8.dp) .navigationBarsPadding() .displayCutoutPadding() ) { EnhancedFloatingActionButton( modifier = Modifier .weight(1f, false), onClick = onRestartApp, content = { Spacer(Modifier.width(16.dp)) Icon( imageVector = Icons.Rounded.RestartAlt, contentDescription = stringResource(R.string.restart_app) ) Spacer(Modifier.width(16.dp)) AutoSizeText( text = stringResource(R.string.restart_app), maxLines = 1 ) Spacer(Modifier.width(16.dp)) } ) Spacer(Modifier.width(8.dp)) EnhancedFloatingActionButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, onClick = onCopy, type = EnhancedFloatingActionButtonType.SecondaryHorizontal ) { Icon( imageVector = Icons.Rounded.ContentCopy, contentDescription = stringResource(R.string.copy) ) } } } ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.components import android.content.Intent import android.util.Log import com.t8rin.imagetoolbox.core.domain.ISSUE_TRACKER import com.t8rin.imagetoolbox.core.ui.utils.helper.DeviceInfo import com.t8rin.imagetoolbox.core.utils.encodeEscaped interface CrashHandler { fun getIntent(): Intent private val intent: Intent @JvmName("getIntentValue") get() = getIntent() private fun getCrashReason(): String = intent.getStringExtra(EXCEPTION_EXTRA) ?: "" fun getCrashInfo(): CrashInfo { val crashReason = getCrashReason() val splitData = crashReason.split(DELIMITER) val exceptionName = splitData.first().trim() val stackTrace = splitData.drop(1).joinToString(DELIMITER) val title = "[Bug] App Crash: $exceptionName" val deviceInfo = DeviceInfo.getAsString() val body = listOf(deviceInfo, stackTrace).joinToString(DELIMITER) return CrashInfo( title = title, body = body, exceptionName = exceptionName, stackTrace = stackTrace ) } companion object { internal const val EXCEPTION_EXTRA = "EXCEPTION_EXTRA" fun getCrashInfoAsExtra( throwable: Throwable ): String { val exceptionName = throwable::class.java.simpleName val stackTrace = Log.getStackTraceString(throwable) return listOf(exceptionName, stackTrace).joinToString(DELIMITER) } } } data class CrashInfo( val title: String, val body: String, val exceptionName: String, val stackTrace: String ) { val textToSend = listOf(title, body).joinToString(DELIMITER) val githubLink = "$ISSUE_TRACKER/new?title=${title.encodeEscaped()}&body=${body.encodeEscaped()}" } private const val DELIMITER = "\n\n" ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashInfoCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BugReport import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable internal fun CrashInfoCard(crashInfo: CrashInfo) { ExpandableItem( shape = ShapeDefaults.extraLarge, modifier = Modifier.fillMaxWidth(), visibleContent = { Icon( imageVector = Icons.Rounded.BugReport, contentDescription = "crash", modifier = Modifier.padding( start = 16.dp, top = 16.dp, bottom = 16.dp ) ) AutoSizeText( text = crashInfo.exceptionName, fontWeight = FontWeight.Bold, textAlign = TextAlign.Start, modifier = Modifier .padding(16.dp) .weight(1f) ) }, expandableContent = { AnimatedVisibility(visible = it) { SelectionContainer { Text( text = crashInfo.stackTrace, textAlign = TextAlign.Center, modifier = Modifier.padding(16.dp) ) } } } ) } ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/CrashRootContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.components import android.content.Intent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.crash.presentation.screenLogic.CrashComponent import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppActivityClass import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.provider.ImageToolboxCompositionLocals import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll @Composable internal fun CrashRootContent(component: CrashComponent) { val context = LocalContext.current val crashInfo = component.crashInfo ImageToolboxCompositionLocals( settingsState = component.settingsState.toUiState() ) { val copyCrashInfo: () -> Unit = { Clipboard.copy(crashInfo.textToSend) } Column( modifier = Modifier .align(Alignment.Center) .enhancedVerticalScroll(rememberScrollState()) .padding(horizontal = 16.dp) .padding(bottom = 80.dp) .navigationBarsPadding() .displayCutoutPadding(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { CrashAttentionCard() Spacer(modifier = Modifier.height(24.dp)) CrashActionButtons( onCopyCrashInfo = copyCrashInfo, onShareLogs = component::shareLogs, githubLink = crashInfo.githubLink ) Spacer(modifier = Modifier.height(24.dp)) CrashInfoCard(crashInfo = crashInfo) } CrashBottomButtons( modifier = Modifier.align(Alignment.BottomCenter), onCopy = copyCrashInfo, onRestartApp = { context.startActivity( Intent(context, AppActivityClass) ) } ) } } ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/components/GlobalExceptionHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.components import android.content.Context import android.content.Intent import com.t8rin.imagetoolbox.core.crash.di.CrashModule import com.t8rin.imagetoolbox.core.crash.presentation.CrashActivity import com.t8rin.imagetoolbox.core.domain.remote.AnalyticsManager import com.t8rin.logger.makeLog import kotlin.system.exitProcess private class GlobalExceptionHandler private constructor( private val applicationContext: Context, private val defaultHandler: Thread.UncaughtExceptionHandler?, private val activityToBeLaunched: Class ) : Thread.UncaughtExceptionHandler { override fun uncaughtException( p0: Thread, p1: Throwable ) { sendReport(p1) runCatching { p1.makeLog("FATAL_EXCEPTION") applicationContext.launchActivity(activityToBeLaunched, p1) exitProcess(0) }.getOrElse { defaultHandler?.uncaughtException(p0, p1) } } private fun Context.launchActivity( activity: Class<*>, throwable: Throwable ) = applicationContext.startActivity( Intent(applicationContext, activity).putExtra( CrashHandler.EXCEPTION_EXTRA, CrashHandler.getCrashInfoAsExtra(throwable) ).addFlags(defFlags) ) companion object : AnalyticsManager by CrashModule.analyticsManager() { fun initialize( applicationContext: Context, activityToBeLaunched: Class, ) = Thread.setDefaultUncaughtExceptionHandler( GlobalExceptionHandler( applicationContext = applicationContext, defaultHandler = Thread.getDefaultUncaughtExceptionHandler()!!, activityToBeLaunched = activityToBeLaunched ) ) } } private const val defFlags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK fun Context.applyGlobalExceptionHandler() = GlobalExceptionHandler.initialize( applicationContext = applicationContext, activityToBeLaunched = CrashActivity::class.java, ) ================================================ FILE: core/crash/src/main/java/com/t8rin/imagetoolbox/core/crash/presentation/screenLogic/CrashComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.presentation.screenLogic import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.crash.presentation.components.CrashInfo import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking class CrashComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val crashInfo: CrashInfo, private val settingsManager: SettingsManager, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _settingsState = mutableStateOf(SettingsState.Default) val settingsState: SettingsState by _settingsState init { runBlocking { _settingsState.value = settingsManager.getSettingsState() } settingsManager.settingsState.onEach { _settingsState.value = it }.launchIn(componentScope) } fun shareLogs() { componentScope.launch { shareProvider.shareUri( uri = settingsManager.createLogsExport(), onComplete = {} ) } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, crashInfo: CrashInfo ): CrashComponent } } ================================================ FILE: core/crash/src/market/java/com/t8rin/imagetoolbox/core/crash/data/AnalyticsManagerImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.crash.data import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics.Event import com.google.firebase.analytics.FirebaseAnalytics.Param import com.google.firebase.analytics.analytics import com.google.firebase.analytics.logEvent import com.google.firebase.crashlytics.crashlytics import com.t8rin.imagetoolbox.core.domain.remote.AnalyticsManager import com.t8rin.imagetoolbox.core.ui.utils.helper.DeviceInfo.Companion.get internal object AnalyticsManagerImpl : AnalyticsManager { override var allowCollectCrashlytics: Boolean = false override var allowCollectAnalytics: Boolean = false override fun updateAnalyticsCollectionEnabled(value: Boolean) { analytics.setAnalyticsCollectionEnabled(value) allowCollectAnalytics = value } override fun updateAllowCollectCrashlytics(value: Boolean) { crashlytics.isCrashlyticsCollectionEnabled = value allowCollectCrashlytics = value if (value) { crashlytics.sendUnsentReports() } } override fun sendReport(throwable: Throwable) { if (allowCollectCrashlytics) { crashlytics.apply { recordException(throwable) sendUnsentReports() } } } override fun registerScreenOpen(screenName: String) { if (allowCollectAnalytics) { analytics.apply { logEvent(Event.SELECT_CONTENT) { param(Param.CONTENT_TYPE, screenName) } logEvent(screenName) { param(Param.CONTENT, deviceInfo()) } } } } private fun deviceInfo(): String { val info = get() return listOf( "Device: ${info.device}", "App Version: ${info.appVersion}" ).joinToString(",") } private val analytics get() = Firebase.analytics private val crashlytics get() = Firebase.crashlytics } ================================================ FILE: core/data/.gitignore ================================================ /build ================================================ FILE: core/data/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.hilt) } android.namespace = "com.t8rin.imagetoolbox.core.data" dependencies { api(libs.coil) api(libs.coilNetwork) api(libs.ktor) api(libs.ktor.logging) implementation(libs.coilGif) implementation(libs.coilSvg) implementation(libs.trickle) implementation(libs.androidx.compose.ui.graphics) api(libs.datastore.preferences.android) api(libs.datastore.core.android) implementation(libs.avif.coder.coil) { exclude(module = "com.github.awxkee:avif-coder") } implementation(libs.avif.coder) implementation(libs.jxl.coder.coil) { exclude(module = "com.github.awxkee:jxl-coder") } implementation(libs.jxl.coder) implementation(libs.aire) implementation(libs.jpegli.coder) implementation(libs.moshi) implementation(libs.moshi.adapters) api(libs.androidx.documentfile) api(libs.logger) implementation(libs.toolbox.gifConverter) implementation(libs.toolbox.exif) implementation(libs.tiffdecoder) implementation(libs.toolbox.qoiCoder) implementation(libs.toolbox.jp2decoder) implementation(libs.toolbox.awebp) implementation(libs.toolbox.psd) implementation(libs.toolbox.apng) implementation(libs.toolbox.djvuCoder) implementation(libs.pdfbox) implementation(libs.trickle) implementation(projects.core.domain) implementation(projects.core.resources) implementation(projects.core.filters) implementation(projects.core.settings) implementation(projects.core.di) api(projects.core.utils) } ================================================ FILE: core/data/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/Base64Fetcher.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coil import android.util.Base64 import coil3.ImageLoader import coil3.Uri import coil3.decode.DataSource import coil3.decode.ImageSource import coil3.fetch.FetchResult import coil3.fetch.Fetcher import coil3.fetch.SourceFetchResult import coil3.request.Options import com.t8rin.imagetoolbox.core.domain.utils.isBase64 import com.t8rin.imagetoolbox.core.domain.utils.trimToBase64 import okio.Buffer internal class Base64Fetcher( private val options: Options, private val base64: String ) : Fetcher { override suspend fun fetch(): FetchResult? { val byteArray = runCatching { Base64.decode(base64, Base64.DEFAULT) }.getOrNull() ?: return null return SourceFetchResult( source = ImageSource( source = Buffer().apply { write(byteArray) }, fileSystem = options.fileSystem, ), mimeType = null, dataSource = DataSource.MEMORY, ) } class Factory : Fetcher.Factory { override fun create( data: Uri, options: Options, imageLoader: ImageLoader, ): Fetcher? { val stripped = data.toString().trimToBase64() return if (stripped.isBase64()) { Base64Fetcher( options = options, base64 = stripped ) } else null } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/CoilLogger.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coil import coil3.util.DebugLogger import coil3.util.Logger import com.t8rin.logger.makeLog import com.t8rin.logger.Logger as RealLogger internal class CoilLogger : Logger { private val delegate = DebugLogger() override var minLevel: Logger.Level get() = delegate.minLevel set(value) { delegate.minLevel = value } override fun log( tag: String, level: Logger.Level, message: String?, throwable: Throwable? ) { message?.takeIf { "NullRequestData" !in it && "PDF" !in it }?.makeLog(tag, level.toLogger()) throwable?.takeIf { "The request's data is null" !in it.message.orEmpty() && "PDF" !in it.message.orEmpty() }?.makeLog(tag) delegate.log( tag = tag, level = level, message = message, throwable = throwable ) } private fun Logger.Level.toLogger(): RealLogger.Level = when (this) { Logger.Level.Verbose -> RealLogger.Level.Verbose Logger.Level.Debug -> RealLogger.Level.Debug Logger.Level.Info -> RealLogger.Level.Info Logger.Level.Warn -> RealLogger.Level.Warn Logger.Level.Error -> RealLogger.Level.Error } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/MemoryCache.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coil import coil3.memory.MemoryCache fun MemoryCache.remove(key: String) = keys.filter { it.key == key }.ifEmpty { listOf(MemoryCache.Key(key)) }.all(::remove) ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/PdfDecoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName", "unused") package com.t8rin.imagetoolbox.core.data.coil import coil3.Extras import coil3.ImageLoader import coil3.asImage import coil3.decode.DecodeResult import coil3.decode.Decoder import coil3.decode.ImageSource import coil3.fetch.SourceFetchResult import coil3.getExtra import coil3.request.ImageRequest import coil3.request.Options import coil3.size.Size import coil3.size.pxOrElse import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.flexibleResize import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.logger.makeLog import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.rendering.PDFRenderer import okio.ByteString.Companion.toByteString import kotlin.math.roundToInt internal class PdfDecoder( private val source: ImageSource, private val options: Options, ) : Decoder { override suspend fun decode(): DecodeResult { val file = source.file().toFile() val image = PDDocument.load(file, options.password.orEmpty()).use { document -> val renderer = PDFRenderer(document) val pageIndex = options.pdfPage.coerceIn(0, document.numberOfPages - 1) val box = document.getPage(pageIndex).cropBox val originalWidth = box.width val originalHeight = box.height val targetSize = IntegerSize( width = originalWidth.roundToInt(), height = originalHeight.roundToInt() ).flexibleResize( w = options.size.width.pxOrElse { 0 }, h = options.size.height.pxOrElse { 0 } ) val scaleX = targetSize.width / originalWidth val scaleY = targetSize.height / originalHeight val scale = minOf(scaleX, scaleY) val bitmap = renderer.renderImage( pageIndex, scale.coerceAtMost(2f).makeLog("PdfDecoder, scale") ) bitmap.asImage() } return DecodeResult( image = image, isSampled = true ) } class Factory : Decoder.Factory { override fun create( result: SourceFetchResult, options: Options, imageLoader: ImageLoader ): Decoder? { return if (isPdf(result)) { PdfDecoder( source = result.source, options = options ) } else null } private fun isPdf(result: SourceFetchResult): Boolean { val pdfMagic = byteArrayOf(0x25, 0x50, 0x44, 0x46).toByteString() return result.source.source() .rangeEquals(0, pdfMagic) || result.mimeType == "application/pdf" } } } fun PdfImageRequest( data: Any?, pdfPage: Int = 0, password: String? = null, size: Size? = null ): ImageRequest = ImageRequest.Builder(appContext) .data(data) .pdfPage(pdfPage) .password(password) .memoryCacheKey(data.toString() + pdfPage) .diskCacheKey(data.toString() + pdfPage) .apply { size?.let(::size) } .build() fun ImageRequest.Builder.password(password: String?) = apply { extras[passwordKey] = password } fun ImageRequest.Builder.pdfPage(pdfPage: Int) = apply { extras[pdfPageKey] = pdfPage } val ImageRequest.password: String? get() = getExtra(passwordKey) val ImageRequest.pdfPage: Int get() = getExtra(pdfPageKey) val Options.password: String? get() = getExtra(passwordKey) val Options.pdfPage: Int get() = getExtra(pdfPageKey) val Extras.Key.Companion.password: Extras.Key get() = passwordKey val Extras.Key.Companion.pdfPage: Extras.Key get() = pdfPageKey private val pdfPageKey = Extras.Key(default = 0) private val passwordKey = Extras.Key(default = null) ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TiffDecoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coil import android.graphics.Bitmap import android.os.Build import coil3.ImageLoader import coil3.asImage import coil3.decode.DecodeResult import coil3.decode.Decoder import coil3.decode.ImageSource import coil3.fetch.SourceFetchResult import coil3.request.Options import coil3.request.bitmapConfig import coil3.size.Size import coil3.size.pxOrElse import okio.BufferedSource import okio.ByteString.Companion.toByteString import org.beyka.tiffbitmapfactory.TiffBitmapFactory import oupson.apng.utils.Utils.flexibleResize internal class TiffDecoder private constructor( private val source: ImageSource, private val options: Options ) : Decoder { @Suppress("DEPRECATION") override suspend fun decode(): DecodeResult? { val config = options.bitmapConfig.takeIf { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { it != Bitmap.Config.HARDWARE } else true } ?: Bitmap.Config.ARGB_8888 val decoded = TiffBitmapFactory.decodeFile( source.file().toFile() ) ?: return null val image = decoded .createScaledBitmap(options.size) .copy(config, false) .asImage() return DecodeResult( image = image, isSampled = options.size != Size.ORIGINAL ) } private fun Bitmap.createScaledBitmap( size: Size ): Bitmap { if (size == Size.ORIGINAL) return this return flexibleResize( maxOf( size.width.pxOrElse { 1 }, size.height.pxOrElse { 1 } ) ) } class Factory : Decoder.Factory { override fun create( result: SourceFetchResult, options: Options, imageLoader: ImageLoader ): Decoder? { return if (isTiff(result.source.source())) { TiffDecoder( source = result.source, options = options ) } else null } private fun isTiff(source: BufferedSource): Boolean { val magic1 = byteArrayOf(0x49, 0x49, 0x2a, 0x00) val magic2 = byteArrayOf(0x4d, 0x4d, 0x00, 0x2a) val cr2Magic = byteArrayOf(0x49, 0x49, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0x52) if (source.rangeEquals(0, cr2Magic.toByteString())) return false if (source.rangeEquals(0, magic1.toByteString())) return true return source.rangeEquals(0, magic2.toByteString()) } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/TimeMeasureInterceptor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coil import coil3.intercept.Interceptor import coil3.request.ImageResult import coil3.request.transformations import com.t8rin.logger.makeLog internal object TimeMeasureInterceptor : Interceptor { override suspend fun intercept( chain: Interceptor.Chain ): ImageResult { val time = System.currentTimeMillis() val result = chain.proceed() val endTime = System.currentTimeMillis() val delta = endTime - time val transformations = chain.request.transformations.joinToString(", ") { it.toString() } if (transformations.isNotEmpty()) { "Time $delta ms for transformations = $transformations, with ${result.request.sizeResolver.size()}".makeLog( "RealImageLoader" ) } "Time $delta ms for ${chain.size}".makeLog("RealImageLoader") return result } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coil/UpscaleSvgDecoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coil import coil3.ImageLoader import coil3.decode.DecodeResult import coil3.decode.DecodeUtils import coil3.decode.Decoder import coil3.decode.ImageSource import coil3.fetch.SourceFetchResult import coil3.request.Options import coil3.size.Size import coil3.size.pxOrElse import coil3.svg.SvgDecoder import coil3.svg.isSvg internal class UpscaleSvgDecoder( private val source: ImageSource, private val options: Options ) : Decoder { override suspend fun decode(): DecodeResult = SvgDecoder( source = source, options = options.copy( size = options.size.coerceAtLeast(2048) ) ).decode() private fun Size.coerceAtLeast(size: Int): Size = Size( width = width.pxOrElse { 0 }.coerceAtLeast(size), height = height.pxOrElse { 0 }.coerceAtLeast(size) ) class Factory : Decoder.Factory { override fun create( result: SourceFetchResult, options: Options, imageLoader: ImageLoader ): Decoder? { if (!isApplicable(result)) return null return UpscaleSvgDecoder( source = result.source, options = options ) } private fun isApplicable(result: SourceFetchResult): Boolean { return result.mimeType == "image/svg+xml" || DecodeUtils.isSvg(result.source.source()) } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AndroidDispatchersHolder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coroutines import com.t8rin.imagetoolbox.core.di.DecodingDispatcher import com.t8rin.imagetoolbox.core.di.DefaultDispatcher import com.t8rin.imagetoolbox.core.di.EncodingDispatcher import com.t8rin.imagetoolbox.core.di.IoDispatcher import com.t8rin.imagetoolbox.core.di.UiDispatcher import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import javax.inject.Inject import kotlin.coroutines.CoroutineContext internal data class AndroidDispatchersHolder @Inject constructor( @UiDispatcher override val uiDispatcher: CoroutineContext, @IoDispatcher override val ioDispatcher: CoroutineContext, @EncodingDispatcher override val encodingDispatcher: CoroutineContext, @DecodingDispatcher override val decodingDispatcher: CoroutineContext, @DefaultDispatcher override val defaultDispatcher: CoroutineContext ) : DispatchersHolder ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/coroutines/AppScopeImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.coroutines import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.logger.makeLog import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.SupervisorJob import javax.inject.Inject import kotlin.coroutines.CoroutineContext internal class AppScopeImpl @Inject constructor( dispatchersHolder: DispatchersHolder ) : AppScope, DispatchersHolder by dispatchersHolder { override val coroutineContext: CoroutineContext = defaultDispatcher + CoroutineExceptionHandler { _, e -> e.makeLog("AppScopeImpl") } + SupervisorJob() } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/CoroutinesModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import com.t8rin.imagetoolbox.core.data.coroutines.AndroidDispatchersHolder import com.t8rin.imagetoolbox.core.data.coroutines.AppScopeImpl import com.t8rin.imagetoolbox.core.data.utils.executorDispatcher import com.t8rin.imagetoolbox.core.di.DecodingDispatcher import com.t8rin.imagetoolbox.core.di.DefaultDispatcher import com.t8rin.imagetoolbox.core.di.EncodingDispatcher import com.t8rin.imagetoolbox.core.di.IoDispatcher import com.t8rin.imagetoolbox.core.di.UiDispatcher import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.Dispatchers import java.util.concurrent.Executors import javax.inject.Singleton import kotlin.coroutines.CoroutineContext @Module @InstallIn(SingletonComponent::class) internal interface CoroutinesModule { @Binds @Singleton fun dispatchersHolder( dispatchers: AndroidDispatchersHolder ): DispatchersHolder @Binds @Singleton fun appScope( impl: AppScopeImpl ): AppScope companion object { @DefaultDispatcher @Singleton @Provides fun defaultDispatcher(): CoroutineContext = executorDispatcher { Executors.newCachedThreadPool() } @DecodingDispatcher @Singleton @Provides fun decodingDispatcher(): CoroutineContext = executorDispatcher { Executors.newFixedThreadPool( 2 * Runtime.getRuntime().availableProcessors() + 1 ) } @EncodingDispatcher @Singleton @Provides fun encodingDispatcher(): CoroutineContext = executorDispatcher { Executors.newSingleThreadExecutor() } @IoDispatcher @Singleton @Provides fun ioDispatcher(): CoroutineContext = Dispatchers.IO @UiDispatcher @Singleton @Provides fun uiDispatcher(): CoroutineContext = Dispatchers.Main.immediate } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageLoaderModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import android.content.Context import android.os.Build import coil3.ComponentRegistry import coil3.ImageLoader import coil3.SingletonImageLoader import coil3.disk.DiskCache import coil3.disk.directory import coil3.gif.AnimatedImageDecoder import coil3.gif.GifDecoder import coil3.imageLoader import coil3.memory.MemoryCache import coil3.network.DeDupeConcurrentRequestStrategy import coil3.network.ktor3.KtorNetworkFetcherFactory import coil3.request.allowHardware import coil3.request.maxBitmapSize import coil3.size.Size import coil3.svg.SvgDecoder import coil3.util.Logger import com.awxkee.jxlcoder.coil.AnimatedJxlDecoder import com.gemalto.jp2.coil.Jpeg2000Decoder import com.github.awxkee.avifcoil.decoder.HeifDecoder import com.t8rin.awebp.coil.AnimatedWebPDecoder import com.t8rin.djvu_coder.coil.DjvuDecoder import com.t8rin.imagetoolbox.core.data.coil.Base64Fetcher import com.t8rin.imagetoolbox.core.data.coil.CoilLogger import com.t8rin.imagetoolbox.core.data.coil.PdfDecoder import com.t8rin.imagetoolbox.core.data.coil.TiffDecoder import com.t8rin.imagetoolbox.core.data.coil.TimeMeasureInterceptor import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.resources.BuildConfig import com.t8rin.psd.coil.PsdDecoder import com.t8rin.qoi_coder.coil.QoiDecoder import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import io.ktor.client.HttpClient import oupson.apng.coil.AnimatedPngDecoder import java.io.File import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal object ImageLoaderModule { @Provides @Singleton fun provideImageLoader( @ApplicationContext context: Context, logger: Logger?, componentRegistry: ComponentRegistry, dispatchersHolder: DispatchersHolder ): ImageLoader = context.imageLoader.newBuilder() .components(componentRegistry) .coroutineContext(dispatchersHolder.defaultDispatcher) .decoderCoroutineContext(dispatchersHolder.decodingDispatcher) .fetcherCoroutineContext(dispatchersHolder.ioDispatcher) .allowHardware(false) .maxBitmapSize(Size.ORIGINAL) .diskCache { DiskCache.Builder() .directory(File(context.cacheDir, "coil").apply(File::mkdirs)) .maxSizePercent(0.2) .cleanupCoroutineContext(dispatchersHolder.ioDispatcher) .build() } .memoryCache { MemoryCache.Builder() .maxSizePercent(context, 0.3) .build() } .logger(logger) .build() .also(SingletonImageLoader::setUnsafe) @Provides @Singleton fun provideCoilLogger(): Logger = CoilLogger() @Provides @Singleton fun provideComponentRegistry( client: HttpClient ): ComponentRegistry = ComponentRegistry.Builder() .apply { add( KtorNetworkFetcherFactory( httpClient = client, concurrentRequestStrategy = DeDupeConcurrentRequestStrategy() ) ) add(AnimatedPngDecoder.Factory()) if (Build.VERSION.SDK_INT >= 28) add(AnimatedImageDecoder.Factory()) else { add(GifDecoder.Factory()) add(AnimatedWebPDecoder.Factory()) } add(SvgDecoder.Factory()) if (Build.VERSION.SDK_INT >= 24) { add(HeifDecoder.Factory()) } add(AnimatedJxlDecoder.Factory()) add(Jpeg2000Decoder.Factory()) add(TiffDecoder.Factory()) add(QoiDecoder.Factory()) add(PsdDecoder.Factory()) add(DjvuDecoder.Factory()) add(Base64Fetcher.Factory()) add(PdfDecoder.Factory()) if (BuildConfig.DEBUG) add(TimeMeasureInterceptor) } .build() } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ImageModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.AndroidImageCompressor import com.t8rin.imagetoolbox.core.data.image.AndroidImageGetter import com.t8rin.imagetoolbox.core.data.image.AndroidImagePreviewCreator import com.t8rin.imagetoolbox.core.data.image.AndroidImageScaler import com.t8rin.imagetoolbox.core.data.image.AndroidImageTransformer import com.t8rin.imagetoolbox.core.data.image.AndroidShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ImageModule { @Singleton @Binds fun provideImageManager( transformer: AndroidImageTransformer ): ImageTransformer @Singleton @Binds fun provideImageScaler( scaler: AndroidImageScaler ): ImageScaler @Singleton @Binds fun provideImageCompressor( compressor: AndroidImageCompressor ): ImageCompressor @Singleton @Binds fun provideImageGetter( getter: AndroidImageGetter ): ImageGetter @Singleton @Binds fun provideImagePreviewCreator( creator: AndroidImagePreviewCreator ): ImagePreviewCreator @Singleton @Binds fun provideShareProvider( provider: AndroidShareProvider ): ShareProvider @Singleton @Binds fun provideImageShareProvider( provider: AndroidShareProvider ): ImageShareProvider } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/JsonModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import com.squareup.moshi.Moshi import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.t8rin.imagetoolbox.core.data.json.MoshiParser import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.json.JsonParser import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface JsonModule { @Binds @Singleton fun parser( impl: MoshiParser, ): JsonParser companion object { @Provides @Singleton fun moshi(): Moshi = Moshi.Builder() .add( PolymorphicJsonAdapterFactory.of(Quality::class.java, "quality_type") .withSubtype(Quality.Jxl::class.java, "jxl") .withSubtype(Quality.Avif::class.java, "avif") .withSubtype(Quality.PngLossy::class.java, "png") .withSubtype(Quality.Tiff::class.java, "tiff") .withSubtype(Quality.Base::class.java, "base") .withDefaultValue(Quality.Base()) ) .add( PolymorphicJsonAdapterFactory.of(ShapeType::class.java, "shape_type") .withSubtype(ShapeType.Rounded::class.java, "rounded") .withSubtype(ShapeType.Cut::class.java, "cut") .withSubtype(ShapeType.Squircle::class.java, "squircle") .withSubtype(ShapeType.Smooth::class.java, "smooth") .withDefaultValue(ShapeType.Rounded()) ) .add( PolymorphicJsonAdapterFactory.of(FilenameBehavior::class.java, "filename_type") .withSubtype(FilenameBehavior.None::class.java, "none") .withSubtype(FilenameBehavior.Overwrite::class.java, "overwrite") .withSubtype(FilenameBehavior.Checksum::class.java, "checksum") .withSubtype(FilenameBehavior.Random::class.java, "random") .withDefaultValue(FilenameBehavior.None()) ) .addLast(KotlinJsonAdapterFactory()) .build() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/LocalModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStoreFile import com.t8rin.imagetoolbox.core.domain.GLOBAL_STORAGE_NAME import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal object LocalModule { @Provides @Singleton fun dataStore( @ApplicationContext context: Context ): DataStore = PreferenceDataStoreFactory.create( produceFile = { context.preferencesDataStoreFile(GLOBAL_STORAGE_NAME) } ) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/RemoteModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import com.t8rin.imagetoolbox.core.data.remote.AndroidDownloadManager import com.t8rin.imagetoolbox.core.data.remote.AndroidRemoteResourcesStore import com.t8rin.imagetoolbox.core.domain.remote.DownloadManager import com.t8rin.imagetoolbox.core.domain.remote.RemoteResourcesStore import com.t8rin.logger.makeLog import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import javax.inject.Singleton import kotlin.time.Duration.Companion.minutes @Module @InstallIn(SingletonComponent::class) internal interface RemoteModule { @Binds @Singleton fun remoteResources( impl: AndroidRemoteResourcesStore ): RemoteResourcesStore @Binds @Singleton fun downloadManager( impl: AndroidDownloadManager ): DownloadManager companion object { @Provides @Singleton fun client(): HttpClient = HttpClient { install(HttpTimeout) { val timeout = 5.minutes.inWholeMilliseconds requestTimeoutMillis = timeout connectTimeoutMillis = timeout socketTimeoutMillis = timeout } install(Logging) { logger = object : Logger { override fun log(message: String) { message.makeLog("Ktor") } } level = LogLevel.HEADERS } } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/ResourcesModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import com.t8rin.imagetoolbox.core.data.resource.AndroidResourceManager import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ResourcesModule { @Binds @Singleton fun resManager( impl: AndroidResourceManager ): ResourceManager } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/di/SavingModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.di import com.t8rin.imagetoolbox.core.data.saving.AndroidFileController import com.t8rin.imagetoolbox.core.data.saving.AndroidFilenameCreator import com.t8rin.imagetoolbox.core.data.saving.AndroidKeepAliveService import com.t8rin.imagetoolbox.core.domain.image.MetadataProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FileController.Companion.toMetadataProvider import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface SavingModule { @Singleton @Binds fun provideFileController( impl: AndroidFileController ): FileController @Singleton @Binds fun filenameCreator( impl: AndroidFilenameCreator ): FilenameCreator @Singleton @Binds fun service( impl: AndroidKeepAliveService ): KeepAliveService companion object { @Singleton @Provides fun provideMetadata( impl: AndroidFileController ): MetadataProvider = impl.toMetadataProvider() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageCompressor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image import android.content.Context import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.data.utils.toSoftware import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.alphaContainedEntries import com.t8rin.imagetoolbox.core.domain.model.sizeTo import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.utils.fileSize import com.t8rin.trickle.Trickle import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import javax.inject.Inject internal class AndroidImageCompressor @Inject constructor( @ApplicationContext private val context: Context, private val imageTransformer: ImageTransformer, private val imageScaler: ImageScaler, private val imageGetter: ImageGetter, private val shareProvider: Lazy, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ImageCompressor { private val _settingsState = settingsProvider.settingsState private val settingsState get() = _settingsState.value override suspend fun compress( image: Bitmap, imageFormat: ImageFormat, quality: Quality ): ByteArray = withContext(encodingDispatcher) { val transformedImage = image.toSoftware().let { software -> val enableForAlpha = settingsState.enableBackgroundColorForAlphaFormats val isNonAlpha = imageFormat !in ImageFormat.alphaContainedEntries if (isNonAlpha || quality.isNonAlpha() || enableForAlpha) { withContext(defaultDispatcher) { if (isNonAlpha && settingsState.backgroundForNoAlphaImageFormats.colorInt == Color.Black.toArgb()) { software } else { Trickle.drawColorBehind( color = settingsState.backgroundForNoAlphaImageFormats.colorInt, input = software ) } } } else software } ImageCompressorBackend.Factory() .create( imageFormat = imageFormat, context = context, imageScaler = imageScaler ) .compress( image = transformedImage, quality = quality.coerceIn(imageFormat) ) } override suspend fun compressAndTransform( image: Bitmap, imageInfo: ImageInfo, onImageReadyToCompressInterceptor: suspend (Bitmap) -> Bitmap, applyImageTransformations: Boolean ): ByteArray = withContext(encodingDispatcher) { val currentImage: Bitmap if (applyImageTransformations) { val size = imageInfo.originalUri?.let { imageGetter.getImage( data = it, originalSize = true )?.run { width sizeTo height } } currentImage = imageScaler .scaleImage( image = imageTransformer.rotate( image = image.apply { setHasAlpha(true) }, degrees = imageInfo.rotationDegrees ), width = imageInfo.width, height = imageInfo.height, resizeType = imageInfo.resizeType.withOriginalSizeIfCrop(size), imageScaleMode = imageInfo.imageScaleMode ) .let { imageTransformer.flip( image = it, isFlipped = imageInfo.isFlipped ) } .let { onImageReadyToCompressInterceptor(it) } } else currentImage = onImageReadyToCompressInterceptor(image) val extension = imageInfo.originalUri?.let { imageGetter.getExtension(it) } val imageFormat = if (settingsState.filenameBehavior is FilenameBehavior.Overwrite && extension != null) { val target = ImageFormat[extension] if (imageInfo.imageFormat.extension == target.extension) { imageInfo.imageFormat } else { target } } else imageInfo.imageFormat yield() compress( image = currentImage, imageFormat = imageFormat, quality = imageInfo.quality ) } override suspend fun calculateImageSize( image: Bitmap, imageInfo: ImageInfo ): Long = withContext(encodingDispatcher) { val newInfo = imageInfo.let { if (it.width == 0 || it.height == 0) { it.copy( width = image.width, height = image.height ) } else it } compressAndTransform( image = image, imageInfo = newInfo ).let { shareProvider.get().cacheByteArray( byteArray = it, filename = "temp.${newInfo.imageFormat.extension}" )?.toUri() ?.fileSize() ?: it.size.toLong() } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageGetter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image import android.content.Context import android.graphics.Bitmap import androidx.core.net.toUri import coil3.ImageLoader import coil3.request.ImageRequest import coil3.request.transformations import coil3.size.Precision import coil3.size.Size import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.image.utils.static import com.t8rin.imagetoolbox.core.data.utils.toCoil import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.MetadataProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageData import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.core.utils.extension import com.t8rin.logger.makeLog import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidImageGetter @Inject constructor( @ApplicationContext private val context: Context, private val imageLoader: ImageLoader, private val appScope: AppScope, metadataProvider: Lazy, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder, ) : DispatchersHolder by dispatchersHolder, ImageGetter { private val _settingsState = settingsProvider.settingsState private val settingsState get() = _settingsState.value private val metadataProvider by lazy { metadataProvider.get() } override suspend fun getImage( uri: String, originalSize: Boolean, onFailure: (Throwable) -> Unit ): ImageData? = withContext(defaultDispatcher) { getImageImpl( data = uri, size = null, addSizeToRequest = originalSize, onFailure = onFailure )?.let { bitmap -> ImageData( image = bitmap, imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = settingsState.defaultImageFormat ?: ImageFormat[getExtension(uri)], originalUri = uri, resizeType = settingsState.defaultResizeType ), metadata = metadataProvider.readMetadata(uri) ) } } override suspend fun getImage( data: Any, originalSize: Boolean ): Bitmap? = getImageImpl( data = data, size = null, addSizeToRequest = originalSize ) override suspend fun getImage( data: Any, size: IntegerSize? ): Bitmap? = getImageImpl( data = data, size = size ) override suspend fun getImage( data: Any, size: Int? ): Bitmap? = getImageImpl( data = data, size = size?.let { IntegerSize( width = it, height = it ) }, precision = Precision.INEXACT ) override suspend fun getImageData( uri: String, size: Int?, onFailure: (Throwable) -> Unit ): ImageData? = withContext(defaultDispatcher) { getImageImpl( data = uri, size = size?.let { IntegerSize( width = it, height = it ) }, precision = Precision.INEXACT, onFailure = onFailure )?.let { bitmap -> ImageData( image = bitmap, imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = settingsState.defaultImageFormat ?: ImageFormat[getExtension(uri)], originalUri = uri, resizeType = settingsState.defaultResizeType ), metadata = metadataProvider.readMetadata(uri) ) } } override suspend fun getImageWithTransformations( uri: String, transformations: List>, originalSize: Boolean ): ImageData? = withContext(defaultDispatcher) { getImageImpl( data = uri, transformations = transformations, size = null, addSizeToRequest = originalSize )?.let { bitmap -> ImageData( image = bitmap, imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = ImageFormat[getExtension(uri)], originalUri = uri, resizeType = settingsState.defaultResizeType ), metadata = metadataProvider.readMetadata(uri) ) } } override suspend fun getImageWithTransformations( data: Any, transformations: List>, size: IntegerSize? ): Bitmap? = getImageImpl( data = data, transformations = transformations, size = size ) override fun getImageAsync( uri: String, originalSize: Boolean, onGetImage: (ImageData) -> Unit, onFailure: (Throwable) -> Unit ) { appScope.launch { var failureDelivered = false val imageData = getImage( uri = uri, originalSize = originalSize, onFailure = { failureDelivered = true onFailure(it) } ) if (imageData != null) { onGetImage(imageData) } else if (!failureDelivered) { onFailure( IllegalStateException(context.getString(R.string.failed_to_open)) ) } } } override fun getExtension(uri: String): String? = uri.toUri().extension(context) private suspend fun getImageImpl( data: Any, size: IntegerSize?, precision: Precision = Precision.EXACT, transformations: List> = emptyList(), onFailure: (Throwable) -> Unit = {}, addSizeToRequest: Boolean = true ): Bitmap? = withContext(defaultDispatcher) { if ((size == null || !addSizeToRequest) && data is Bitmap) return@withContext data val request = ImageRequest .Builder(context) .data(data) .static() .precision(precision) .transformations( transformations.map(Transformation::toCoil) ) .apply { if (addSizeToRequest) { size( size?.let { Size(size.width, size.height) } ?: Size.ORIGINAL ) } } .build() runSuspendCatching { imageLoader.execute(request).image?.toBitmap() }.onFailure { it.makeLog("ImageGetter") onFailure(it) }.getOrNull() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImagePreviewCreator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image import android.graphics.Bitmap import android.os.Build import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import javax.inject.Inject import kotlin.math.roundToInt internal class AndroidImagePreviewCreator @Inject constructor( private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val imageTransformer: ImageTransformer, private val imageScaler: ImageScaler, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ImagePreviewCreator { private val _settingsState = settingsProvider.settingsState private val settingsState get() = _settingsState.value override suspend fun createPreview( image: Bitmap, imageInfo: ImageInfo, transformations: List>, onGetByteCount: (Int) -> Unit ): Bitmap? = withContext(defaultDispatcher) { launch(encodingDispatcher) { onGetByteCount(0) yield() onGetByteCount( imageCompressor.calculateImageSize( image = image, imageInfo = imageInfo ).toInt() ) } if (!settingsState.generatePreviews) return@withContext null if (imageInfo.height == 0 || imageInfo.width == 0) return@withContext image val targetImage: Bitmap yield() val shouldTransform = transformations.isNotEmpty() || (imageInfo.width != image.width) || (imageInfo.height != image.height) || !imageInfo.quality.isDefault() || (imageInfo.rotationDegrees != 0f) || imageInfo.isFlipped if (shouldTransform) { var width = imageInfo.width var height = imageInfo.height var scaleFactor = 1f while (height * width * 4 > SMALL_SIZE) { height = (height * 0.85f).roundToInt() width = (width * 0.85f).roundToInt() scaleFactor *= 0.85f } yield() val bytes = imageCompressor.compressAndTransform( image = image, imageInfo = imageInfo.copy( width = width, height = height, resizeType = if (imageInfo.resizeType is ResizeType.CenterCrop) { (imageInfo.resizeType as ResizeType.CenterCrop).copy(scaleFactor = scaleFactor) } else imageInfo.resizeType ), onImageReadyToCompressInterceptor = { yield() imageTransformer.transform( image = it, transformations = transformations ) ?: it } ) targetImage = imageGetter.getImage(bytes) ?: image } else { targetImage = image } yield() imageScaler.scaleUntilCanShow(targetImage) } override fun canShow(image: Bitmap?): Boolean = image?.run { size() <= BIG_SIZE } ?: false private val Bitmap.configSize: Int get() = when (config) { Bitmap.Config.RGB_565 -> 2 else -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (config == Bitmap.Config.RGBA_F16) 8 else 4 } else 4 } } private fun Bitmap.size(): Int = width * height * configSize } private const val SMALL_SIZE = 1500 * 1500 * 3 private const val BIG_SIZE = 3096 * 3096 * 4 ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageScaler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image import android.graphics.Bitmap import android.graphics.Color import android.graphics.PorterDuff import androidx.core.graphics.BitmapCompat import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.scale import com.awxkee.aire.Aire import com.awxkee.aire.ResizeFunction import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.aspectRatio import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.data.utils.toSoftware import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.ResizeAnchor import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.image.model.ScaleColorSpace import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.createFilter import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.logger.makeLog import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt import com.awxkee.aire.ScaleColorSpace as AireScaleColorSpace internal class AndroidImageScaler @Inject constructor( settingsProvider: SettingsProvider, private val imageTransformer: ImageTransformer, private val filterProvider: FilterProvider, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ImageScaler { private val _settingsState = settingsProvider.settingsState private val settingsState get() = _settingsState.value override suspend fun scaleImage( image: Bitmap, width: Int, height: Int, resizeType: ResizeType, imageScaleMode: ImageScaleMode ): Bitmap = withContext(defaultDispatcher) { val widthInternal = width.takeIf { it > 0 } ?: image.width val heightInternal = height.takeIf { it > 0 } ?: image.height runSuspendCatching { when (resizeType) { ResizeType.Explicit -> { createScaledBitmap( image = image, width = widthInternal, height = heightInternal, imageScaleMode = imageScaleMode ) } is ResizeType.Flexible -> { flexibleResize( image = image, width = widthInternal, height = heightInternal, resizeAnchor = resizeType.resizeAnchor, imageScaleMode = imageScaleMode ) } is ResizeType.CenterCrop -> { resizeType.performCenterCrop( image = image, targetWidth = widthInternal, targetHeight = heightInternal, imageScaleMode = imageScaleMode ) } is ResizeType.Fit -> { resizeType.performFitResize( image = image, targetWidth = widthInternal, targetHeight = heightInternal, imageScaleMode = imageScaleMode ) } } }.onFailure { it.makeLog("AndroidImageScaler") }.getOrNull() ?: image } override suspend fun scaleUntilCanShow( image: Bitmap? ): Bitmap? = withContext(defaultDispatcher) { if (image == null) return@withContext null if (canShow(image.width * image.height * 4)) return@withContext image var (height, width) = image.run { height to width } var iterations = 0 while (!canShow(size = height * width * 4)) { height = (height * 0.85f).roundToInt() width = (width * 0.85f).roundToInt() iterations++ } if (iterations == 0) image else scaleImage( image = image, height = height, width = width, imageScaleMode = ImageScaleMode.Bicubic() ) } private fun canShow(size: Int): Boolean { return size < 3096 * 3096 * 3 } private suspend fun Bitmap.fitResize( targetWidth: Int, targetHeight: Int, imageScaleMode: ImageScaleMode ): Bitmap { val aspectRatio = width.toFloat() / height.toFloat() val targetAspectRatio = targetWidth.toFloat() / targetHeight.toFloat() val finalWidth: Int val finalHeight: Int if (aspectRatio > targetAspectRatio) { // Image is wider than the target aspect ratio finalWidth = targetWidth finalHeight = (targetWidth / aspectRatio).toInt() } else { // Image is taller than or equal to the target aspect ratio finalWidth = (targetHeight * aspectRatio).toInt() finalHeight = targetHeight } return createScaledBitmap( image = this, width = finalWidth, height = finalHeight, imageScaleMode = imageScaleMode ) } private suspend fun ResizeType.Fit.performFitResize( image: Bitmap, targetWidth: Int, targetHeight: Int, imageScaleMode: ImageScaleMode ): Bitmap = withContext(defaultDispatcher) { if (targetWidth == image.width && targetHeight == image.height) { return@withContext image } val originalWidth: Int val originalHeight: Int val aspect = image.aspectRatio val originalAspect = image.aspectRatio if (abs(aspect - originalAspect) > 0.001f) { originalWidth = image.height originalHeight = image.width } else { originalWidth = image.width originalHeight = image.height } val drawImage = image.fitResize( targetWidth = targetWidth, targetHeight = targetHeight, imageScaleMode = imageScaleMode ) val blurredBitmap = imageTransformer.transform( image = drawImage.let { bitmap -> val xScale: Float = targetWidth.toFloat() / originalWidth val yScale: Float = targetHeight.toFloat() / originalHeight val scale = xScale.coerceAtLeast(yScale) createScaledBitmap( image = bitmap, width = (scale * originalWidth).toInt(), height = (scale * originalHeight).toInt(), imageScaleMode = imageScaleMode ) }, transformations = listOf( filterProvider.filterToTransformation( createFilter( blurRadius.toFloat() / 1000 * max(targetWidth, targetHeight) ) ) ) ) createBitmap(targetWidth, targetHeight, drawImage.safeConfig).apply { setHasAlpha(true) } .applyCanvas { drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) canvasColor?.let { drawColor(it) } ?: blurredBitmap?.let { drawBitmap( bitmap = blurredBitmap, position = Position.Center ) } drawBitmap( bitmap = drawImage, position = position ) } } private suspend fun ResizeType.CenterCrop.performCenterCrop( image: Bitmap, targetWidth: Int, targetHeight: Int, imageScaleMode: ImageScaleMode ): Bitmap = withContext(defaultDispatcher) { val originalSize = if (!originalSize.isDefined()) { IntegerSize( width = image.width, height = image.height ) } else { originalSize } * scaleFactor if (targetWidth == originalSize.width && targetHeight == originalSize.height) { return@withContext image } val originalWidth: Int val originalHeight: Int val aspect = image.aspectRatio val originalAspect = originalSize.aspectRatio if (abs(aspect - originalAspect) > 0.001f) { originalWidth = originalSize.height originalHeight = originalSize.width } else { originalWidth = originalSize.width originalHeight = originalSize.height } val drawImage = createScaledBitmap( image = image, width = originalWidth, height = originalHeight, imageScaleMode = imageScaleMode ) val blurredBitmap = if (canvasColor == null) { imageTransformer.transform( image = drawImage.let { bitmap -> val xScale: Float = targetWidth.toFloat() / originalWidth val yScale: Float = targetHeight.toFloat() / originalHeight val scale = xScale.coerceAtLeast(yScale) createScaledBitmap( image = bitmap, width = (scale * originalWidth).toInt(), height = (scale * originalHeight).toInt(), imageScaleMode = imageScaleMode ) }, transformations = listOf( filterProvider.filterToTransformation( createFilter( blurRadius.toFloat() / 1000 * max(targetWidth, targetHeight) ) ) ) ) } else null createBitmap(targetWidth, targetHeight, drawImage.safeConfig).apply { setHasAlpha(true) } .applyCanvas { drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) canvasColor?.let { drawColor(it) } ?: blurredBitmap?.let { drawBitmap( bitmap = blurredBitmap, position = Position.Center ) } drawBitmap( bitmap = drawImage, position = position ) } } private suspend fun createScaledBitmap( image: Bitmap, width: Int, height: Int, imageScaleMode: ImageScaleMode ): Bitmap = withContext(defaultDispatcher) { if (width == image.width && height == image.height) return@withContext image val softwareImage = image.toSoftware() if (imageScaleMode is ImageScaleMode.Base) { return@withContext if (width < softwareImage.width && height < softwareImage.width) { BitmapCompat.createScaledBitmap(softwareImage, width, height, null, true) } else { softwareImage.scale(width, height) } } val mode = imageScaleMode.takeIf { it != ImageScaleMode.NotPresent && it.value >= 0 } ?: settingsState.defaultImageScaleMode Aire.scale( bitmap = softwareImage, dstWidth = width, dstHeight = height, scaleMode = mode.toResizeFunction(), colorSpace = mode.scaleColorSpace.toColorSpace() ) } private fun ImageScaleMode.toResizeFunction(): ResizeFunction = when (this) { ImageScaleMode.NotPresent, ImageScaleMode.Base -> ResizeFunction.Bilinear is ImageScaleMode.Bilinear -> ResizeFunction.Bilinear is ImageScaleMode.Nearest -> ResizeFunction.Nearest is ImageScaleMode.Cubic -> ResizeFunction.Cubic is ImageScaleMode.Mitchell -> ResizeFunction.MitchellNetravalli is ImageScaleMode.Catmull -> ResizeFunction.CatmullRom is ImageScaleMode.Hermite -> ResizeFunction.Hermite is ImageScaleMode.BSpline -> ResizeFunction.BSpline is ImageScaleMode.Hann -> ResizeFunction.Hann is ImageScaleMode.Bicubic -> ResizeFunction.Bicubic is ImageScaleMode.Hamming -> ResizeFunction.Hamming is ImageScaleMode.Hanning -> ResizeFunction.Hanning is ImageScaleMode.Blackman -> ResizeFunction.Blackman is ImageScaleMode.Welch -> ResizeFunction.Welch is ImageScaleMode.Quadric -> ResizeFunction.Quadric is ImageScaleMode.Gaussian -> ResizeFunction.Gaussian is ImageScaleMode.Sphinx -> ResizeFunction.Sphinx is ImageScaleMode.Bartlett -> ResizeFunction.Bartlett is ImageScaleMode.Robidoux -> ResizeFunction.Robidoux is ImageScaleMode.RobidouxSharp -> ResizeFunction.RobidouxSharp is ImageScaleMode.Spline16 -> ResizeFunction.Spline16 is ImageScaleMode.Spline36 -> ResizeFunction.Spline36 is ImageScaleMode.Spline64 -> ResizeFunction.Spline64 is ImageScaleMode.Kaiser -> ResizeFunction.Kaiser is ImageScaleMode.BartlettHann -> ResizeFunction.BartlettHann is ImageScaleMode.Box -> ResizeFunction.Box is ImageScaleMode.Bohman -> ResizeFunction.Bohman is ImageScaleMode.Lanczos2 -> ResizeFunction.Lanczos2 is ImageScaleMode.Lanczos3 -> ResizeFunction.Lanczos3 is ImageScaleMode.Lanczos4 -> ResizeFunction.Lanczos4 is ImageScaleMode.Lanczos2Jinc -> ResizeFunction.Lanczos2Jinc is ImageScaleMode.Lanczos3Jinc -> ResizeFunction.Lanczos3Jinc is ImageScaleMode.Lanczos4Jinc -> ResizeFunction.Lanczos4Jinc is ImageScaleMode.EwaHanning -> ResizeFunction.EwaHanning is ImageScaleMode.EwaRobidoux -> ResizeFunction.EwaRobidoux is ImageScaleMode.EwaBlackman -> ResizeFunction.EwaBlackman is ImageScaleMode.EwaQuadric -> ResizeFunction.EwaQuadric is ImageScaleMode.EwaRobidouxSharp -> ResizeFunction.EwaRobidouxSharp is ImageScaleMode.EwaLanczos3Jinc -> ResizeFunction.EwaLanczos3Jinc is ImageScaleMode.Ginseng -> ResizeFunction.Ginseng is ImageScaleMode.EwaGinseng -> ResizeFunction.EwaGinseng is ImageScaleMode.EwaLanczosSharp -> ResizeFunction.EwaLanczosSharp is ImageScaleMode.EwaLanczos4Sharpest -> ResizeFunction.EwaLanczos4Sharpest is ImageScaleMode.EwaLanczosSoft -> ResizeFunction.EwaLanczosSoft is ImageScaleMode.HaasnSoft -> ResizeFunction.HaasnSoft is ImageScaleMode.Lagrange2 -> ResizeFunction.Lagrange2 is ImageScaleMode.Lagrange3 -> ResizeFunction.Lagrange3 is ImageScaleMode.Lanczos6 -> ResizeFunction.Lanczos6 is ImageScaleMode.Lanczos6Jinc -> ResizeFunction.Lanczos6Jinc is ImageScaleMode.Area -> ResizeFunction.Area } private fun ScaleColorSpace.toColorSpace(): AireScaleColorSpace = when (this) { ScaleColorSpace.LAB -> AireScaleColorSpace.LAB ScaleColorSpace.Linear -> AireScaleColorSpace.LINEAR ScaleColorSpace.SRGB -> AireScaleColorSpace.SRGB ScaleColorSpace.LUV -> AireScaleColorSpace.LUV ScaleColorSpace.Sigmoidal -> AireScaleColorSpace.SIGMOIDAL ScaleColorSpace.XYZ -> AireScaleColorSpace.XYZ ScaleColorSpace.F32Gamma22 -> AireScaleColorSpace.LINEAR_F32_GAMMA_2_2 ScaleColorSpace.F32Gamma28 -> AireScaleColorSpace.LINEAR_F32_GAMMA_2_8 ScaleColorSpace.F32Rec709 -> AireScaleColorSpace.LINEAR_F32_REC709 ScaleColorSpace.F32sRGB -> AireScaleColorSpace.LINEAR_F32_SRGB ScaleColorSpace.LCH -> AireScaleColorSpace.LCH ScaleColorSpace.OklabGamma22 -> AireScaleColorSpace.OKLAB_GAMMA_2_2 ScaleColorSpace.OklabGamma28 -> AireScaleColorSpace.OKLAB_GAMMA_2_8 ScaleColorSpace.OklabRec709 -> AireScaleColorSpace.OKLAB_REC709 ScaleColorSpace.OklabSRGB -> AireScaleColorSpace.OKLAB_SRGB ScaleColorSpace.JzazbzGamma22 -> AireScaleColorSpace.JZAZBZ_GAMMA_2_2 ScaleColorSpace.JzazbzGamma28 -> AireScaleColorSpace.JZAZBZ_GAMMA_2_8 ScaleColorSpace.JzazbzRec709 -> AireScaleColorSpace.JZAZBZ_REC709 ScaleColorSpace.JzazbzSRGB -> AireScaleColorSpace.JZAZBZ_SRGB } private suspend fun flexibleResize( image: Bitmap, width: Int, height: Int, resizeAnchor: ResizeAnchor, imageScaleMode: ImageScaleMode ): Bitmap = withContext(defaultDispatcher) { val max = max(width, height) val scaleByWidth = suspend { val aspectRatio = image.aspectRatio createScaledBitmap( image = image, width = width, height = (width / aspectRatio).toInt(), imageScaleMode = imageScaleMode ) } val scaleByHeight = suspend { val aspectRatio = image.aspectRatio createScaledBitmap( image = image, width = (height * aspectRatio).toInt(), height = height, imageScaleMode = imageScaleMode ) } when (resizeAnchor) { ResizeAnchor.Max -> { if (width >= height) { scaleByWidth() } else scaleByHeight() } ResizeAnchor.Min -> { if (width >= height) { scaleByHeight() } else scaleByWidth() } ResizeAnchor.Width -> scaleByWidth() ResizeAnchor.Height -> scaleByHeight() ResizeAnchor.Default -> { runSuspendCatching { if (image.height >= image.width) { val aspectRatio = image.aspectRatio val targetWidth = (max * aspectRatio).toInt() createScaledBitmap(image, targetWidth, max, imageScaleMode) } else { val aspectRatio = 1f / image.aspectRatio val targetHeight = (max * aspectRatio).toInt() createScaledBitmap(image, max, targetHeight, imageScaleMode) } }.getOrNull() ?: image } } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidImageTransformer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image import android.content.Context import android.graphics.Bitmap import android.graphics.Matrix import coil3.ImageLoader import coil3.request.ImageRequest import coil3.request.transformations import coil3.size.Size import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.utils.toCoil import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.sizeTo import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.logger.makeLog import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.math.abs import kotlin.math.roundToInt internal class AndroidImageTransformer @Inject constructor( @ApplicationContext private val context: Context, private val imageLoader: ImageLoader, defaultDispatchersHolder: DispatchersHolder ) : DispatchersHolder by defaultDispatchersHolder, ImageTransformer { override suspend fun transform( image: Bitmap, transformations: List>, originalSize: Boolean ): Bitmap? = withContext(defaultDispatcher) { if (transformations.isEmpty()) return@withContext image val request = ImageRequest .Builder(context) .data(image) .transformations( transformations.map { it.toCoil() } ) .apply { if (originalSize) size(Size.ORIGINAL) } .build() return@withContext imageLoader.execute(request).image?.toBitmap() } override suspend fun transform( image: Bitmap, transformations: List>, size: IntegerSize ): Bitmap? = withContext(defaultDispatcher) { if (transformations.isEmpty()) return@withContext image val request = ImageRequest .Builder(context) .data(image) .transformations( transformations.map { it.toCoil() } ) .size(size.width, size.height) .build() return@withContext imageLoader.execute(request).image?.toBitmap() } override suspend fun applyPresetBy( image: Bitmap?, preset: Preset, currentInfo: ImageInfo ): ImageInfo = withContext(defaultDispatcher) { if (image == null || preset is Preset.None) return@withContext currentInfo val size = currentInfo.originalUri.makeLog("applyPresetBy originalUri")?.let { uri -> imageLoader.execute( ImageRequest.Builder(context) .data(uri) .size(Size.ORIGINAL) .build() ).image?.run { width sizeTo height }.makeLog("applyPresetBy using orig size") } ?: IntegerSize(image.width, image.height).makeLog("applyPresetBy using image size") val rotated = abs(currentInfo.rotationDegrees) % 180 != 0f fun calcWidth() = if (rotated) size.height else size.width fun calcHeight() = if (rotated) size.width else size.height fun Int.calc(cnt: Int): Int = (this * (cnt / 100f)).toInt() when (preset) { is Preset.Telegram -> { currentInfo.copy( width = 512, height = 512, imageFormat = ImageFormat.Png.Lossless, resizeType = ResizeType.Flexible, quality = Quality.Base(100) ) } is Preset.Percentage -> currentInfo.copy( width = calcWidth().calc(preset.value), height = calcHeight().calc(preset.value), ) is Preset.AspectRatio -> { val originalWidth = calcWidth().toFloat() val originalHeight = calcHeight().toFloat() val newWidth: Float val newHeight: Float val condition = if (preset.isFit) preset.ratio > originalWidth / originalHeight else preset.ratio < originalWidth / originalHeight if (condition) { newWidth = originalHeight * preset.ratio newHeight = originalHeight } else { newWidth = originalWidth newHeight = originalWidth / preset.ratio } currentInfo.copy( width = newWidth.roundToInt(), height = newHeight.roundToInt() ) } Preset.None -> currentInfo } } override suspend fun flip( image: Bitmap, isFlipped: Boolean ): Bitmap = withContext(defaultDispatcher) { if (isFlipped) { val matrix = Matrix().apply { postScale(-1f, 1f, image.width / 2f, image.height / 2f) } Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, true) } else image } override suspend fun rotate( image: Bitmap, degrees: Float ): Bitmap = withContext(defaultDispatcher) { if (degrees == 0f) return@withContext image if (degrees % 90 == 0f) { val matrix = Matrix().apply { postRotate(degrees) } Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, true) } else { val matrix = Matrix().apply { setRotate(degrees, image.width.toFloat() / 2, image.height.toFloat() / 2) } Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, true) } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidMetadata.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image import com.t8rin.exif.ExifInterface import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.image.toMap import java.io.FileDescriptor private data class ExifInterfaceMetadata( private val exifInterface: ExifInterface ) : Metadata { override fun saveAttributes(): Metadata = apply { exifInterface.saveAttributes() } override fun getAttribute( tag: MetadataTag ): String? = exifInterface.getAttribute(tag.key) override fun setAttribute( tag: MetadataTag, value: String? ): Metadata = apply { exifInterface.setAttribute(tag.key, value) } override fun toString(): String = "Android(${toMap()})" } internal fun ExifInterface.toMetadata(): Metadata = ExifInterfaceMetadata(this) internal fun FileDescriptor.toMetadata(): Metadata = ExifInterface(this).toMetadata() ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/AndroidShareProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.webkit.MimeTypeMap import androidx.core.content.FileProvider import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.saving.io.FileWriteable import com.t8rin.imagetoolbox.core.data.saving.io.UriReadable import com.t8rin.imagetoolbox.core.domain.PDF import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.model.toMimeType import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable import com.t8rin.imagetoolbox.core.domain.saving.io.use import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.logger.makeLog import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import java.io.File import javax.inject.Inject import kotlin.random.Random internal class AndroidShareProvider @Inject constructor( @ApplicationContext private val context: Context, private val imageGetter: ImageGetter, private val imageCompressor: ImageCompressor, private val filenameCreator: Lazy, resourceManager: ResourceManager, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager, ShareProvider, ImageShareProvider { override suspend fun shareImages( uris: List, imageLoader: suspend (String) -> Pair?, onProgressChange: (Int) -> Unit ) = withContext(ioDispatcher) { val cachedUris = uris.mapIndexedNotNull { index, uri -> imageLoader(uri)?.let { (image, imageInfo) -> cacheImage( image = image, imageInfo = imageInfo )?.also { onProgressChange(index + 1) } } } onProgressChange(-1) shareUris(cachedUris) } override suspend fun cacheImage( image: Bitmap, imageInfo: ImageInfo, filename: String? ): String? = withContext(ioDispatcher) { runSuspendCatching { val saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = imageInfo.originalUri ?: "share", sequenceNumber = null, data = byteArrayOf() ) val realFilename = filename ?: filenameCreator.get().constructImageFilename(saveTarget) val byteArray = imageCompressor.compressAndTransform(image, imageInfo) cacheByteArray( byteArray = byteArray, filename = realFilename ) }.getOrNull() } override suspend fun shareImage( imageInfo: ImageInfo, image: Bitmap, onComplete: () -> Unit ) = withContext(ioDispatcher) { cacheImage( image = image, imageInfo = imageInfo )?.let { shareUri( uri = it, type = imageInfo.imageFormat.mimeType, onComplete = {} ) } onComplete() } override suspend fun shareUri( uri: String, type: MimeType.Single?, onComplete: () -> Unit ) { withContext(defaultDispatcher) { runSuspendCatching { shareUriImpl( uri = uri, type = type ) onComplete() }.onFailure { val newUri = cacheData( writeData = { UriReadable( uri = uri.toUri(), context = context ).copyTo(it) }, filename = filenameCreator.get() .constructRandomFilename( extension = imageGetter.getExtension(uri) ?: "" ) ) shareUriImpl( uri = newUri ?: return@onFailure, type = type ) onComplete() } } } private fun shareUriImpl( uri: String, type: MimeType.Single? ) { val mimeType = type ?: MimeTypeMap.getSingleton() .getMimeTypeFromExtension( imageGetter.getExtension(uri) )?.toMimeType() ?: MimeType.All val sendIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri.toUri()) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) this.type = mimeType.entry } val shareIntent = Intent.createChooser(sendIntent, getString(R.string.share)) shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(shareIntent) } override suspend fun shareUris( uris: List ) = shareImageUris(uris.map { it.toUri() }) private suspend fun shareImageUris( uris: List ) = withContext(defaultDispatcher) { if (uris.isEmpty()) return@withContext val sendIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris)) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val mimeType = MimeTypeMap.getSingleton() .getMimeTypeFromExtension( imageGetter.getExtension(uris.first().toString()) ) ?: "*/*" type = mimeType.makeLog("shareImageUris") } val shareIntent = Intent.createChooser(sendIntent, getString(R.string.share)) shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(shareIntent) } override suspend fun cacheByteArray( byteArray: ByteArray, filename: String ): String? = withContext(ioDispatcher) { cacheData( writeData = { it.writeBytes(byteArray) }, filename = filename, ) } override suspend fun shareByteArray( byteArray: ByteArray, filename: String, onComplete: () -> Unit ) = withContext(ioDispatcher) { shareData( writeData = { it.writeBytes(byteArray) }, filename = filename, onComplete = {} ) onComplete() } override suspend fun cacheData( filename: String, writeData: suspend (Writeable) -> Unit ): String? = withContext(ioDispatcher) { runSuspendCatching { cacheDataOrThrow( filename = filename, writeData = writeData ) }.onFailure { it.makeLog("cacheData") }.getOrNull() } override suspend fun cacheDataOrThrow( filename: String, writeData: suspend (Writeable) -> Unit ): String = withContext(ioDispatcher) { val imagesFolder = if (filename.startsWith("temp.")) { File(context.cacheDir, "temp") } else if (filename.startsWith(PDF)) { File(context.cacheDir, "$PDF${Random.nextInt()}") } else { File(context.cacheDir, "cache/${Random.nextInt()}") } imagesFolder.mkdirs() val file = File(imagesFolder, filename.removePrefix(PDF)) FileWriteable(file).use { writeData(it) } FileProvider.getUriForFile( context, getString(R.string.file_provider), file ).also { uri -> runCatching { context.grantUriPermission( context.packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION ) } }.toString() } override suspend fun shareData( writeData: suspend (Writeable) -> Unit, filename: String, onComplete: () -> Unit ) = withContext(ioDispatcher) { cacheData( writeData = writeData, filename = filename )?.let { val mimeType = MimeTypeMap.getSingleton() .getMimeTypeFromExtension( imageGetter.getExtension(it) )?.toMimeType() ?: MimeType.All shareUri( uri = it, type = mimeType, onComplete = {} ) } onComplete() } override fun shareText( value: String, onComplete: () -> Unit ) { val sendIntent = Intent().apply { action = Intent.ACTION_SEND type = "text/plain" putExtra(Intent.EXTRA_TEXT, value) } val shareIntent = Intent.createChooser(sendIntent, getString(R.string.share)) shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(shareIntent) onComplete() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/BlendingModeExt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils import android.graphics.Paint import android.graphics.PorterDuffXfermode import android.os.Build import androidx.annotation.RequiresApi import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import android.graphics.BlendMode as AndroidBlendMode import android.graphics.PorterDuff.Mode as PorterDuffMode fun BlendingMode.toPorterDuffMode(): PorterDuffMode = when (this) { BlendingMode.Clear -> PorterDuffMode.CLEAR BlendingMode.Src -> PorterDuffMode.SRC BlendingMode.Dst -> PorterDuffMode.DST BlendingMode.SrcOver -> PorterDuffMode.SRC_OVER BlendingMode.DstOver -> PorterDuffMode.DST_OVER BlendingMode.SrcIn -> PorterDuffMode.SRC_IN BlendingMode.DstIn -> PorterDuffMode.DST_IN BlendingMode.SrcOut -> PorterDuffMode.SRC_OUT BlendingMode.DstOut -> PorterDuffMode.DST_OUT BlendingMode.SrcAtop -> PorterDuffMode.SRC_ATOP BlendingMode.DstAtop -> PorterDuffMode.DST_ATOP BlendingMode.Xor -> PorterDuffMode.XOR BlendingMode.Plus -> PorterDuffMode.ADD BlendingMode.Screen -> PorterDuffMode.SCREEN BlendingMode.Overlay -> PorterDuffMode.OVERLAY BlendingMode.Darken -> PorterDuffMode.DARKEN BlendingMode.Lighten -> PorterDuffMode.LIGHTEN BlendingMode.Modulate -> { // b/73224934 Android PorterDuff Multiply maps to Skia Modulate PorterDuffMode.MULTIPLY } // Always return SRC_OVER as the default if there is no valid alternative else -> PorterDuffMode.SRC_OVER } /** * Convert the domain [BlendingMode] to the underlying Android platform [AndroidBlendMode] */ @RequiresApi(Build.VERSION_CODES.Q) fun BlendingMode.toAndroidBlendMode(): AndroidBlendMode = when (this) { BlendingMode.Clear -> AndroidBlendMode.CLEAR BlendingMode.Src -> AndroidBlendMode.SRC BlendingMode.Dst -> AndroidBlendMode.DST BlendingMode.SrcOver -> AndroidBlendMode.SRC_OVER BlendingMode.DstOver -> AndroidBlendMode.DST_OVER BlendingMode.SrcIn -> AndroidBlendMode.SRC_IN BlendingMode.DstIn -> AndroidBlendMode.DST_IN BlendingMode.SrcOut -> AndroidBlendMode.SRC_OUT BlendingMode.DstOut -> AndroidBlendMode.DST_OUT BlendingMode.SrcAtop -> AndroidBlendMode.SRC_ATOP BlendingMode.DstAtop -> AndroidBlendMode.DST_ATOP BlendingMode.Xor -> AndroidBlendMode.XOR BlendingMode.Plus -> AndroidBlendMode.PLUS BlendingMode.Modulate -> AndroidBlendMode.MODULATE BlendingMode.Screen -> AndroidBlendMode.SCREEN BlendingMode.Overlay -> AndroidBlendMode.OVERLAY BlendingMode.Darken -> AndroidBlendMode.DARKEN BlendingMode.Lighten -> AndroidBlendMode.LIGHTEN BlendingMode.ColorDodge -> AndroidBlendMode.COLOR_DODGE BlendingMode.ColorBurn -> AndroidBlendMode.COLOR_BURN BlendingMode.Hardlight -> AndroidBlendMode.HARD_LIGHT BlendingMode.Softlight -> AndroidBlendMode.SOFT_LIGHT BlendingMode.Difference -> AndroidBlendMode.DIFFERENCE BlendingMode.Exclusion -> AndroidBlendMode.EXCLUSION BlendingMode.Multiply -> AndroidBlendMode.MULTIPLY BlendingMode.Hue -> AndroidBlendMode.HUE BlendingMode.Saturation -> AndroidBlendMode.SATURATION BlendingMode.Color -> AndroidBlendMode.COLOR BlendingMode.Luminosity -> AndroidBlendMode.LUMINOSITY // Always return SRC_OVER as the default if there is no valid alternative else -> AndroidBlendMode.SRC_OVER } fun BlendingMode.toPaint(): Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { blendMode = toAndroidBlendMode() } else { xfermode = PorterDuffXfermode(toPorterDuffMode()) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/CanvasUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF import androidx.core.graphics.get import androidx.core.graphics.set import com.t8rin.imagetoolbox.core.domain.model.Position import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive fun Canvas.drawBitmap( bitmap: Bitmap, position: Position, paint: Paint? = null, horizontalPadding: Float = 0f, verticalPadding: Float = 0f ) { val left = when (position) { Position.TopLeft, Position.CenterLeft, Position.BottomLeft -> horizontalPadding Position.TopCenter, Position.Center, Position.BottomCenter -> (width - bitmap.width) / 2f Position.TopRight, Position.CenterRight, Position.BottomRight -> (width - bitmap.width - horizontalPadding) } val top = when (position) { Position.TopLeft, Position.TopCenter, Position.TopRight -> verticalPadding Position.CenterLeft, Position.Center, Position.CenterRight -> (height - bitmap.height) / 2f Position.BottomLeft, Position.BottomCenter, Position.BottomRight -> (height - bitmap.height - verticalPadding) } drawBitmap( bitmap, null, RectF( left, top, bitmap.width + left, bitmap.height + top ), paint ) } fun Canvas.drawBitmap( bitmap: Bitmap, left: Float = 0f, top: Float = 0f ) = drawBitmap(bitmap, left, top, Paint(Paint.ANTI_ALIAS_FLAG)) fun Canvas.drawBitmap( bitmap: Bitmap, left: Float = 0f, top: Float = 0f, paint: Paint ) = drawBitmap(bitmap, left, top, paint) suspend fun Bitmap.healAlpha( original: Bitmap ): Bitmap = coroutineScope { val processed = this@healAlpha copy(Bitmap.Config.ARGB_8888, true).also { result -> for (y in 0 until original.height) { for (x in 0 until original.width) { ensureActive() val origPixel = original[x, y] val procPixel = processed[x, y] val origAlpha = origPixel ushr 24 if (origAlpha >= 255) continue val newPixel = (origAlpha shl 24) or (procPixel and 0x00FFFFFF) result[x, y] = newPixel } } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ColorUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.data.image.utils import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.model.ColorModel object ColorUtils { inline fun ColorModel.toColor() = Color(colorInt) inline fun Color.toModel() = ColorModel(toArgb()) inline val ColorModel.red: Float get() = toColor().red inline val ColorModel.green: Float get() = toColor().green inline val ColorModel.blue: Float get() = toColor().blue inline val ColorModel.alpha: Float get() = toColor().alpha inline fun Color.toAbgr() = run { Color( red = alpha, green = blue, blue = green, alpha = red, colorSpace = colorSpace ).toArgb() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/ImageCompressorBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils import android.content.Context import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.compressor.AvifBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.BmpBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.HeicBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.IcoBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.Jpeg2000Backend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.JpegliBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.JpgBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.JxlBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.MozJpegBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.OxiPngBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.PngLosslessBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.PngLossyBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.QoiBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.StaticGifBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.TiffBackend import com.t8rin.imagetoolbox.core.data.image.utils.compressor.WebpBackend import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.isLossless internal interface ImageCompressorBackend { suspend fun compress( image: Bitmap, quality: Quality ): ByteArray class Factory { fun create( imageFormat: ImageFormat, context: Context, imageScaler: ImageScaler ): ImageCompressorBackend = when (imageFormat) { ImageFormat.Jpeg, ImageFormat.Jpg -> JpgBackend ImageFormat.Jpegli -> JpegliBackend ImageFormat.MozJpeg -> MozJpegBackend ImageFormat.Png.Lossless -> PngLosslessBackend ImageFormat.Png.Lossy -> PngLossyBackend ImageFormat.Png.OxiPNG -> OxiPngBackend ImageFormat.Webp.Lossless, ImageFormat.Webp.Lossy -> WebpBackend(isLossless = imageFormat.isLossless) ImageFormat.Jxl.Lossless, ImageFormat.Jxl.Lossy -> JxlBackend(isLossless = imageFormat.isLossless) ImageFormat.Tif, ImageFormat.Tiff -> TiffBackend(context) ImageFormat.Heic.Lossless, ImageFormat.Heif.Lossless, ImageFormat.Heic.Lossy, ImageFormat.Heif.Lossy -> HeicBackend(isLossless = imageFormat.isLossless) ImageFormat.Avif.Lossless, ImageFormat.Avif.Lossy -> AvifBackend(isLossless = imageFormat.isLossless) ImageFormat.Jpeg2000.J2k -> Jpeg2000Backend(isJ2K = true) ImageFormat.Jpeg2000.Jp2 -> Jpeg2000Backend(isJ2K = false) ImageFormat.Gif -> StaticGifBackend ImageFormat.Bmp -> BmpBackend ImageFormat.Qoi -> QoiBackend ImageFormat.Ico -> IcoBackend(imageScaler) } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/StaticOptions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils import coil3.gif.repeatCount import coil3.request.ImageRequest import com.awxkee.jxlcoder.coil.enableJxlAnimation import com.github.awxkee.avifcoil.decoder.animation.enableAvifAnimation import com.t8rin.imagetoolbox.core.data.coil.UpscaleSvgDecoder fun ImageRequest.Builder.static() = repeatCount(0) .enableAvifAnimation(false) .enableJxlAnimation(false) .decoderFactory(UpscaleSvgDecoder.Factory()) ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/AvifBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.radzivon.bartoshyk.avif.coder.AvifSpeed import com.radzivon.bartoshyk.avif.coder.HeifCoder import com.radzivon.bartoshyk.avif.coder.PreciseMode import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data class AvifBackend( private val isLossless: Boolean ) : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray { val avifQuality = quality as? Quality.Avif ?: Quality.Avif() return HeifCoder().encodeAvif( bitmap = image, quality = avifQuality.qualityValue, preciseMode = if (isLossless) { PreciseMode.LOSSLESS } else { PreciseMode.LOSSY }, speed = AvifSpeed.entries.firstOrNull { it.ordinal == (10 - avifQuality.effort) } ?: AvifSpeed.TEN ) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/BmpBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.trickle.BmpCompressor internal data object BmpBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = runCatching { BmpCompressor.compress(image) }.getOrNull() ?: ByteArray(0) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/HeicBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.radzivon.bartoshyk.avif.coder.HeifCoder import com.radzivon.bartoshyk.avif.coder.HeifQualityArg import com.radzivon.bartoshyk.avif.coder.PreciseMode import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data class HeicBackend( private val isLossless: Boolean ) : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray { return HeifCoder().encodeHeic( bitmap = image, quality = HeifQualityArg.Quality(quality.qualityValue), preciseMode = if (isLossless) { PreciseMode.LOSSLESS } else { PreciseMode.LOSSY } ) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/IcoBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import androidx.core.graphics.get import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import kotlinx.coroutines.coroutineScope import java.io.ByteArrayOutputStream import java.nio.ByteBuffer import java.nio.ByteOrder internal data class IcoBackend( private val imageScaler: ImageScaler ) : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = coroutineScope { val bitmap = if (image.width > 256 || image.height > 256) { imageScaler.scaleImage( image = image, width = 256, height = 256, resizeType = ResizeType.Flexible ) } else image val width = bitmap.width val height = bitmap.height val outputStream = ByteArrayOutputStream() val header = ByteArray(6) val entry = ByteArray(16) val infoHeader = ByteArray(40) // ICO Header header[2] = 1 // Image type: Icon header[4] = 1 // Number of images outputStream.write(header) // Image entry entry[0] = if (width > 256) 0 else width.toByte() entry[1] = if (height > 256) 0 else height.toByte() entry[4] = 1 // Color planes entry[6] = 32 // Bits per pixel val andMaskSize = ((width + 31) / 32) * 4 * height val xorMaskSize = width * height * 4 val imageDataSize = infoHeader.size + xorMaskSize + andMaskSize ByteBuffer.wrap(entry, 8, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(imageDataSize) ByteBuffer.wrap(entry, 12, 4) .order(ByteOrder.LITTLE_ENDIAN) .putInt(header.size + entry.size) outputStream.write(entry) // BITMAP INFO HEADER ByteBuffer.wrap(infoHeader).order(ByteOrder.LITTLE_ENDIAN).apply { putInt(40) // Header size putInt(width) // Width putInt(height * 2) // Height (XOR + AND masks) putShort(1) // Color planes putShort(32) // Bits per pixel putInt(0) // Compression (BI_RGB) putInt(xorMaskSize + andMaskSize) // Image size } outputStream.write(infoHeader) // XOR mask (pixel data) for (y in height - 1 downTo 0) { for (x in 0 until width) { val pixel = bitmap[x, y] outputStream.write(pixel and 0xFF) // B outputStream.write((pixel shr 8) and 0xFF) // G outputStream.write((pixel shr 16) and 0xFF) // R outputStream.write((pixel shr 24) and 0xFF) // A } } // AND mask (all 0 for no transparency mask) outputStream.write(ByteArray(andMaskSize)) outputStream.toByteArray() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/Jpeg2000Backend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.gemalto.jp2.JP2Encoder import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data class Jpeg2000Backend( private val isJ2K: Boolean ) : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = JP2Encoder(image) .setOutputFormat( if (isJ2K) { JP2Encoder.FORMAT_J2K } else { JP2Encoder.FORMAT_JP2 } ) .setVisualQuality(quality.qualityValue.toFloat()) .encode() } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpegliBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality import io.github.awxkee.jpegli.coder.IccStrategy import io.github.awxkee.jpegli.coder.JpegliCoder import io.github.awxkee.jpegli.coder.Scalar import java.io.ByteArrayOutputStream internal data object JpegliBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = ByteArrayOutputStream().apply { use { out -> JpegliCoder.compress( bitmap = image, quality = quality.qualityValue, background = Scalar.ZERO, progressive = true, strategy = IccStrategy.DEFAULT, outputStream = out ) } }.toByteArray() } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JpgBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.data.utils.compress import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data object JpgBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = image.compress( format = Bitmap.CompressFormat.JPEG, quality = quality.qualityValue ) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/JxlBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.jxlcoder.JxlChannelsConfiguration import com.awxkee.jxlcoder.JxlCoder import com.awxkee.jxlcoder.JxlCompressionOption import com.awxkee.jxlcoder.JxlDecodingSpeed import com.awxkee.jxlcoder.JxlEffort import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data class JxlBackend( private val isLossless: Boolean ) : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray { val jxlQuality = quality as? Quality.Jxl ?: Quality.Jxl() return JxlCoder.encode( bitmap = if (jxlQuality.channels == Quality.Channels.Monochrome) { Aire.grayscale(image) } else image, channelsConfiguration = when (jxlQuality.channels) { Quality.Channels.RGBA -> JxlChannelsConfiguration.RGBA Quality.Channels.RGB -> JxlChannelsConfiguration.RGB Quality.Channels.Monochrome -> JxlChannelsConfiguration.MONOCHROME }, compressionOption = if (isLossless) { JxlCompressionOption.LOSSLESS } else { JxlCompressionOption.LOSSY }, quality = if (isLossless) 100 else jxlQuality.qualityValue, effort = JxlEffort.entries.first { it.ordinal == jxlQuality.effort - 1 }, decodingSpeed = JxlDecodingSpeed.entries.first { it.ordinal == jxlQuality.speed } ) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/MozJpegBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data object MozJpegBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = Aire.mozjpeg( bitmap = image, quality = quality.qualityValue ) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/OxiPngBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.trickle.Oxipng internal data object OxiPngBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = Oxipng.optimize( bitmap = image, options = Oxipng.SimpleOptions( level = quality.qualityValue.coerceIn(0..6) ) ) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLosslessBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.data.utils.compress import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data object PngLosslessBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = image.compress( format = Bitmap.CompressFormat.PNG, quality = quality.qualityValue ) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/PngLossyBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.AireColorMapper import com.awxkee.aire.AirePaletteDithering import com.awxkee.aire.AireQuantize import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data object PngLossyBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray { val pngLossyQuality = quality as? Quality.PngLossy ?: Quality.PngLossy() return Aire.toPNG( bitmap = image, maxColors = pngLossyQuality.maxColors, quantize = AireQuantize.XIAOLING_WU, dithering = AirePaletteDithering.JARVIS_JUDICE_NINKE, colorMapper = AireColorMapper.COVER_TREE, compressionLevel = pngLossyQuality.compressionLevel.coerceIn(0..9) ) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/QoiBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.qoi_coder.QOIEncoder internal data object QoiBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = QOIEncoder(image).encode() } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/StaticGifBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.gif_converter.GifEncoder import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality import java.io.ByteArrayOutputStream internal data object StaticGifBackend : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = ByteArrayOutputStream().use { out -> GifEncoder().apply { setSize( image.width, image.height ) setRepeat(1) setQuality( (100 - ((quality.qualityValue - 1) * (100 / 19f))).toInt() ) setTransparent(Color.Transparent.toArgb()) start(out) addFrame(image) finish() } out.toByteArray() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/TiffBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.content.Context import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.domain.image.model.Quality import org.beyka.tiffbitmapfactory.CompressionScheme import org.beyka.tiffbitmapfactory.Orientation import org.beyka.tiffbitmapfactory.TiffSaver import java.io.File internal data class TiffBackend( private val context: Context ) : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray { val tiffQuality = quality as? Quality.Tiff ?: Quality.Tiff() val file = File(context.cacheDir, "temp.tiff").apply { createNewFile() } val options = TiffSaver.SaveOptions().apply { compressionScheme = CompressionScheme.entries[tiffQuality.compressionScheme] orientation = Orientation.TOP_LEFT } TiffSaver.saveBitmap(file, image, options) return file.readBytes().also { file.delete() } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/image/utils/compressor/WebpBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.image.utils.compressor import android.graphics.Bitmap import android.os.Build import com.t8rin.imagetoolbox.core.data.image.utils.ImageCompressorBackend import com.t8rin.imagetoolbox.core.data.utils.compress import com.t8rin.imagetoolbox.core.domain.image.model.Quality internal data class WebpBackend( private val isLossless: Boolean ) : ImageCompressorBackend { override suspend fun compress( image: Bitmap, quality: Quality ): ByteArray = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { image.compress( format = if (isLossless) { Bitmap.CompressFormat.WEBP_LOSSLESS } else { Bitmap.CompressFormat.WEBP_LOSSY }, quality = quality.qualityValue ) } else { @Suppress("DEPRECATION") image.compress( format = Bitmap.CompressFormat.WEBP, quality = quality.qualityValue ) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/json/MoshiParser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.json import com.squareup.moshi.Moshi import com.squareup.moshi.rawType import com.t8rin.imagetoolbox.core.domain.json.JsonParser import com.t8rin.logger.makeLog import java.lang.reflect.Type import javax.inject.Inject internal class MoshiParser @Inject constructor( private val moshi: Moshi ) : JsonParser { override fun toJson( obj: T, type: Type, ): String? = runCatching { moshi.adapter(type).toJson(obj) }.onFailure { it.makeLog("MoshiParser toJson T = ${obj?.run { this::class }}") }.getOrNull() override fun fromJson( json: String, type: Type, ): T? = if (json.isBlank()) { "json is empty".makeLog("MoshiParser fromJson T = ${type.rawType.name}") null } else { runCatching { moshi.adapter(type).fromJson(json) }.onFailure { it.makeLog("MoshiParser fromJson T = ${type.rawType.name}") }.getOrNull() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidDownloadManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.remote import android.content.Context import com.t8rin.imagetoolbox.core.data.utils.getWithProgress import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.remote.DownloadManager import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.saving.track import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.throttleLatest import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.decodeEscaped import com.t8rin.logger.makeLog import dagger.hilt.android.qualifiers.ApplicationContext import io.ktor.client.HttpClient import io.ktor.utils.io.jvm.javaio.copyTo import io.ktor.utils.io.jvm.javaio.toInputStream import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import javax.inject.Inject import kotlin.math.roundToInt internal class AndroidDownloadManager @Inject constructor( @ApplicationContext private val context: Context, private val keepAliveService: KeepAliveService, private val client: HttpClient, resourceManager: ResourceManager, dispatchersHolder: DispatchersHolder ) : DownloadManager, ResourceManager by resourceManager, DispatchersHolder by dispatchersHolder { override suspend fun download( url: String, destinationPath: String, onStart: suspend () -> Unit, onProgress: suspend (DownloadProgress) -> Unit, onFinish: suspend (Throwable?) -> Unit ): Unit = withContext(defaultDispatcher) { keepAliveService.track( initial = { updateOrStart( title = getString(R.string.downloading) ) }, onFailure = onFinish ) { channelFlow { onStart() url.makeLog("Start Download") val tmp = File( File(context.cacheDir, "downloadCache").apply(File::mkdirs), "${url.substringAfterLast('/').substringBefore('?')}.tmp" ) client.getWithProgress( url = url, onFailure = onFinish, onProgress = { bytesSentTotal, contentLength -> val progress = contentLength?.let { bytesSentTotal.toFloat() / contentLength.toFloat() } ?: 0f onProgress( DownloadProgress( currentPercent = progress, currentTotalSize = contentLength ?: -1 ).also(::trySend) ) }, onOpen = { response -> tmp.outputStream().use { fos -> response.copyTo(fos) tmp.renameTo(File(destinationPath)) tmp.delete() onFinish(null) } } ) tmp.delete() close() }.throttleLatest(50).collect { updateProgress( title = getString(R.string.downloading), done = (it.currentPercent * 100).roundToInt(), total = 100 ) } } } override suspend fun downloadZip( url: String, destinationPath: String, onStart: suspend () -> Unit, onProgress: (DownloadProgress) -> Unit, downloadOnlyNewData: Boolean ): Unit = withContext(defaultDispatcher) { keepAliveService.track( initial = { updateOrStart( title = getString(R.string.downloading) ) } ) { channelFlow { client.getWithProgress( url = url, onProgress = { bytesSentTotal, contentLength -> val progress = contentLength?.let { bytesSentTotal.toFloat() / contentLength.toFloat() } ?: 0f onProgress( DownloadProgress( currentPercent = progress, currentTotalSize = contentLength ?: -1 ).also(::trySend) ) }, onOpen = { response -> ZipInputStream(response.toInputStream()).use { zipIn -> var entry: ZipEntry? while (zipIn.nextEntry.also { entry = it } != null) { entry?.let { zipEntry -> val filename = zipEntry.name if (filename.isNullOrBlank() || filename.startsWith("__")) return@let val outFile = File( destinationPath, filename.decodeEscaped() ).apply { delete() parentFile?.mkdirs() createNewFile() } if (downloadOnlyNewData) { val file = File(destinationPath).listFiles()?.find { it.name == filename && it.length() > 0L } if (file != null) return@let } FileOutputStream(outFile).use { fos -> zipIn.copyTo(fos) } zipIn.closeEntry() } } } } ) close() }.throttleLatest(50).collect { updateProgress( title = getString(R.string.downloading), done = (it.currentPercent * 100).roundToInt(), total = 100 ) } } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/remote/AndroidRemoteResourcesStore.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.remote import android.content.Context import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.RES_BASE_URL import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.remote.DownloadManager import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.remote.RemoteResource import com.t8rin.imagetoolbox.core.domain.remote.RemoteResources import com.t8rin.imagetoolbox.core.domain.remote.RemoteResourcesStore import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.utils.decodeEscaped import com.t8rin.logger.makeLog import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import java.io.File import javax.inject.Inject internal class AndroidRemoteResourcesStore @Inject constructor( @ApplicationContext private val context: Context, private val downloadManager: DownloadManager, resourceManager: ResourceManager, dispatchersHolder: DispatchersHolder ) : RemoteResourcesStore, DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { override suspend fun getResources( name: String, forceUpdate: Boolean, onDownloadRequest: suspend (name: String) -> RemoteResources? ): RemoteResources? = withContext(ioDispatcher) { val availableFiles = getSavingDir(name).listFiles() val shouldDownload = forceUpdate || availableFiles.isNullOrEmpty() val availableResources = if (!availableFiles.isNullOrEmpty()) { RemoteResources( name = name, list = availableFiles.mapNotNull { it.toUri().toString() }.map { uri -> RemoteResource( uri = uri, name = uri.takeLastWhile { it != '/' }.decodeEscaped() ) }.sortedBy { it.name } ) } else null if (shouldDownload) onDownloadRequest(name) ?: availableResources else availableResources } override suspend fun downloadResources( name: String, onProgress: (DownloadProgress) -> Unit, onFailure: (Throwable) -> Unit, downloadOnlyNewData: Boolean ): RemoteResources? = withContext(defaultDispatcher) { runSuspendCatching { val url = getResourcesLink(name) val savingDir = getSavingDir(name) downloadManager.downloadZip( url = url, destinationPath = savingDir.absolutePath, onProgress = onProgress, downloadOnlyNewData = downloadOnlyNewData ) name to url makeLog "downloadResources" val savedAlready = savingDir.listFiles()?.mapNotNull { it.toUri().toString() }?.map { uri -> RemoteResource( uri = uri, name = uri.takeLastWhile { it != '/' }.decodeEscaped() ) } ?: emptyList() RemoteResources( name = name, list = savedAlready.sortedBy { it.name } ) }.onFailure { it.makeLog() onFailure(it) }.getOrNull() } private fun getResourcesLink( dirName: String ): String = RES_BASE_URL.replace("*", dirName) private fun getSavingDir( dirName: String ): File = File(rootDir, dirName.substringBefore("/")).apply(File::mkdirs) private val rootDir = File(context.filesDir, "remoteResources").apply(File::mkdirs) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/resource/AndroidResourceManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.resource import android.content.Context import android.content.res.Configuration import androidx.annotation.StringRes import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import dagger.hilt.android.qualifiers.ApplicationContext import java.util.Locale import javax.inject.Inject internal class AndroidResourceManager @Inject constructor( @ApplicationContext private val context: Context ) : ResourceManager { override fun getString( resId: Int ): String = context.getString(resId) override fun getString( resId: Int, vararg formatArgs: Any ): String = context.getString(resId, *formatArgs) override fun getStringLocalized( resId: Int, language: String ): String = context.getStringLocalized( resId = resId, locale = Locale.forLanguageTag(language) ) override fun getStringLocalized( resId: Int, language: String, vararg formatArgs: Any ): String = context.getStringLocalized( resId = resId, locale = Locale.forLanguageTag(language), formatArgs = formatArgs ) private fun Context.getStringLocalized( @StringRes resId: Int, locale: Locale, ): String = createConfigurationContext( Configuration(resources.configuration).apply { setLocale(locale) } ).getText(resId).toString() private fun Context.getStringLocalized( @StringRes resId: Int, locale: Locale, vararg formatArgs: Any ): String = createConfigurationContext( Configuration(resources.configuration).apply { setLocale(locale) } ).getString(resId, *formatArgs) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFileController.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.net.Uri import androidx.core.content.getSystemService import androidx.core.net.toUri import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.documentfile.provider.DocumentFile import coil3.ImageLoader import com.t8rin.exif.ExifInterface import com.t8rin.imagetoolbox.core.data.coil.remove import com.t8rin.imagetoolbox.core.data.image.toMetadata import com.t8rin.imagetoolbox.core.data.saving.io.UriReadable import com.t8rin.imagetoolbox.core.data.saving.io.UriWriteable import com.t8rin.imagetoolbox.core.data.utils.cacheSize import com.t8rin.imagetoolbox.core.data.utils.clearCache import com.t8rin.imagetoolbox.core.data.utils.isExternalStorageWritable import com.t8rin.imagetoolbox.core.data.utils.openFileDescriptor import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.clearAllAttributes import com.t8rin.imagetoolbox.core.domain.image.copyTo import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.image.readOnly import com.t8rin.imagetoolbox.core.domain.json.JsonParser import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable import com.t8rin.imagetoolbox.core.domain.saving.io.use import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import com.t8rin.imagetoolbox.core.domain.utils.FileMode import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.domain.model.OneTimeSaveLocation import com.t8rin.imagetoolbox.core.utils.fileSize import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.core.utils.getPath import com.t8rin.imagetoolbox.core.utils.listFilesInDirectory import com.t8rin.imagetoolbox.core.utils.listFilesInDirectoryProgressive import com.t8rin.imagetoolbox.core.utils.tryExtractOriginal import com.t8rin.imagetoolbox.core.utils.uiPath import com.t8rin.logger.makeLog import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.FileOutputStream import javax.inject.Inject import kotlin.reflect.KClass internal class AndroidFileController @Inject constructor( @ApplicationContext private val context: Context, private val settingsManager: SettingsManager, private val shareProvider: ShareProvider, private val filenameCreator: FilenameCreator, private val jsonParser: JsonParser, private val appScope: AppScope, private val dataStore: DataStore, private val imageLoader: ImageLoader, dispatchersHolder: DispatchersHolder, resourceManager: ResourceManager, ) : DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager, FileController { private val _settingsState = settingsManager.settingsState private val settingsState get() = _settingsState.value override fun getSize(uri: String): Long? = uri.toUri().fileSize() override val defaultSavingPath: String get() = settingsState.saveFolderUri.getPath(context) override suspend fun save( saveTarget: SaveTarget, keepOriginalMetadata: Boolean, oneTimeSaveLocationUri: String?, ): SaveResult { val result = saveImpl( saveTarget = saveTarget, keepOriginalMetadata = keepOriginalMetadata, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) Triple( first = result, second = keepOriginalMetadata, third = oneTimeSaveLocationUri ).makeLog("File Controller save") return result } private suspend fun saveImpl( saveTarget: SaveTarget, keepOriginalMetadata: Boolean, oneTimeSaveLocationUri: String?, ): SaveResult = withContext(ioDispatcher) { if (!context.isExternalStorageWritable()) { return@withContext SaveResult.Error.MissingPermissions } val data = if (saveTarget is ImageSaveTarget && saveTarget.readFromUriInsteadOfData) { readBytes(saveTarget.originalUri) } else { saveTarget.data } val savingPath = oneTimeSaveLocationUri?.getPath(context) ?: defaultSavingPath runSuspendCatching { if (settingsState.copyToClipboardMode is CopyToClipboardMode.Enabled) { val clipboardManager = context.getSystemService() shareProvider.cacheByteArray( byteArray = data, filename = filenameCreator.constructRandomFilename(saveTarget.extension) )?.toUri()?.let { uri -> clipboardManager?.setPrimaryClip( ClipData.newUri( context.contentResolver, "IMAGE", uri ) ) } } if (settingsState.copyToClipboardMode is CopyToClipboardMode.Enabled.WithoutSaving) { return@withContext SaveResult.Success( message = getString(R.string.copied), savingPath = savingPath ) } val originalUri = saveTarget.originalUri.toUri() if (saveTarget is ImageSaveTarget && saveTarget.canSkipIfLarger && settingsState.allowSkipIfLarger) { val originalSize = originalUri.fileSize() val newSize = data.size if (originalSize != null && newSize > originalSize) { return@withContext SaveResult.Skipped } } if (settingsState.filenameBehavior is FilenameBehavior.Overwrite) { val providedMetadata = (saveTarget as? ImageSaveTarget)?.metadata val targetMetadata = if (keepOriginalMetadata) { readMetadata(originalUri.toString()) ?: providedMetadata } else { providedMetadata } runCatching { if (originalUri == Uri.EMPTY) throw IllegalStateException() context.openFileDescriptor( uri = originalUri, mode = FileMode.WriteTruncate ) }.getOrNull()?.use { parcel -> FileOutputStream(parcel.fileDescriptor).use { out -> out.write(data) copyMetadata( initialExif = targetMetadata, fileUri = originalUri, keepOriginalMetadata = keepOriginalMetadata, originalUri = originalUri ) } imageLoader.apply { memoryCache?.remove(originalUri.toString()) diskCache?.remove(originalUri.toString()) } return@withContext SaveResult.Success( message = getString( R.string.saved_to_original, originalUri.filename(context).toString() ), isOverwritten = true, savingPath = savingPath ) } } else { val documentFile: DocumentFile? val treeUri = (oneTimeSaveLocationUri ?: settingsState.saveFolderUri).takeIf { !it.isNullOrEmpty() } if (treeUri != null) { documentFile = runCatching { treeUri.toUri().let { if (DocumentFile.isDocumentUri(context, it)) { DocumentFile.fromSingleUri(context, it) } else DocumentFile.fromTreeUri(context, it) } }.getOrNull() if (documentFile == null || !documentFile.exists()) { if (oneTimeSaveLocationUri == null) { settingsManager.setSaveFolderUri(null) } else { settingsManager.setOneTimeSaveLocations( settingsState.oneTimeSaveLocations.let { locations -> (locations - locations.find { it.uri == oneTimeSaveLocationUri }).filterNotNull() } ) } return@withContext SaveResult.Error.Exception( Exception( getString( R.string.no_such_directory, treeUri.toUri().uiPath(treeUri) ) ) ) } } else { documentFile = null } var initialExif: Metadata? = null val newSaveTarget = if (saveTarget is ImageSaveTarget) { initialExif = saveTarget.metadata saveTarget.copy( filename = filenameCreator.constructImageFilename( saveTarget = saveTarget, forceNotAddSizeInFilename = saveTarget.imageInfo.height <= 0 || saveTarget.imageInfo.width <= 0 ) ) } else saveTarget val savingFolder = SavingFolder.getInstance( context = context, treeUri = treeUri?.toUri(), saveTarget = newSaveTarget ) ?: throw IllegalArgumentException(getString(R.string.error_while_saving)) savingFolder.use { it.writeBytes(data) } copyMetadata( initialExif = initialExif, fileUri = savingFolder.fileUri, keepOriginalMetadata = keepOriginalMetadata, originalUri = saveTarget.originalUri.toUri() ) val filename = newSaveTarget.filename ?: throw IllegalArgumentException(getString(R.string.filename_is_not_set)) oneTimeSaveLocationUri?.let { if (documentFile?.isDirectory == true) { val currentLocation = settingsState.oneTimeSaveLocations.find { it.uri == oneTimeSaveLocationUri } settingsManager.setOneTimeSaveLocations( currentLocation?.let { settingsState.oneTimeSaveLocations.toMutableList().apply { remove(currentLocation) add( currentLocation.copy( uri = oneTimeSaveLocationUri, date = System.currentTimeMillis(), count = currentLocation.count + 1 ) ) } } ?: settingsState.oneTimeSaveLocations.plus( OneTimeSaveLocation( uri = oneTimeSaveLocationUri, date = System.currentTimeMillis(), count = 1 ) ) ) } } return@withContext SaveResult.Success( message = if (savingPath.isNotEmpty()) { val isFile = (documentFile?.isDirectory != true && oneTimeSaveLocationUri != null) if (isFile) { getString(R.string.saved_to_custom) } else if (filename.isNotEmpty()) { getString( R.string.saved_to, savingPath, filename ) } else { getString( R.string.saved_to_without_filename, savingPath ) } } else null, savingPath = savingPath ) } }.onFailure { return@withContext SaveResult.Error.Exception(it) } SaveResult.Error.Exception( SaveException( message = getString(R.string.something_went_wrong) ) ) } override fun clearCache( onComplete: (Long) -> Unit, ) { appScope.launch { context.clearCache() onComplete(getCacheSize()) "cache cleared".makeLog("AndroidFileController") } } override fun getCacheSize(): Long = context.cacheSize() override suspend fun readBytes( uri: String, ): ByteArray = withContext(ioDispatcher) { runSuspendCatching { context.contentResolver.openInputStream(uri.toUri())?.use { it.buffered().readBytes() } }.onFailure { uri.makeLog("File Controller read") it.makeLog("File Controller read") }.getOrNull() ?: ByteArray(0) } override suspend fun writeBytes( uri: String, block: suspend (Writeable) -> Unit, ): SaveResult = withContext(ioDispatcher) { runSuspendCatching { block( UriWriteable( uri = uri.toUri(), context = context ) ) }.onSuccess { return@withContext SaveResult.Success( message = null, savingPath = "" ) }.onFailure { uri.makeLog("File Controller write") it.makeLog("File Controller write") return@withContext SaveResult.Error.Exception(it) } return@withContext SaveResult.Error.Exception(IllegalStateException()) } override suspend fun transferBytes( fromUri: String, toUri: String ): SaveResult = transferBytes( fromUri = fromUri, to = UriWriteable( uri = toUri.toUri(), context = context ) ) override suspend fun transferBytes( fromUri: String, to: Writeable ): SaveResult = withContext(ioDispatcher) { runSuspendCatching { UriReadable( uri = fromUri.toUri(), context = context ).copyTo(to) }.onSuccess { return@withContext SaveResult.Success( message = null, savingPath = "" ) }.onFailure { to.makeLog("File Controller write") it.makeLog("File Controller write") return@withContext SaveResult.Error.Exception(it) } return@withContext SaveResult.Error.Exception(IllegalStateException()) } override suspend fun saveObject( key: String, value: O, ): Boolean = withContext(ioDispatcher) { "saveObject value = $value".makeLog(key) runCatching { dataStore.edit { it[stringPreferencesKey("fast_$key")] = jsonParser.toJson(value, value::class.java)!! } }.onSuccess { "saveObject success".makeLog(key) return@withContext true }.onFailure { it.makeLog("saveObject $key") return@withContext false } return@withContext false } override suspend fun restoreObject( key: String, kClass: KClass, ): O? = withContext(ioDispatcher) { runCatching { "restoreObject".makeLog(key) jsonParser.fromJson( json = dataStore.data.first()[stringPreferencesKey("fast_$key")].orEmpty(), type = kClass.java ) }.onFailure { it.makeLog("restoreObject $key") }.onSuccess { "restoreObject success value = $it".makeLog(key) }.getOrNull() } override suspend fun writeMetadata( imageUri: String, metadata: Metadata? ) { copyMetadata( initialExif = metadata, fileUri = imageUri.toUri(), keepOriginalMetadata = false, originalUri = imageUri.toUri() ) } override suspend fun readMetadata( imageUri: String ): Metadata? = runSuspendCatching { context.contentResolver.openInputStream(imageUri.toUri().tryExtractOriginal())?.use { ExifInterface(it).toMetadata().readOnly().makeLog("readMetadata") } }.getOrNull() override suspend fun listFilesInDirectory( treeUri: String ): List = withContext(ioDispatcher) { treeUri.toUri().listFilesInDirectory().map { it.toString() } } override fun listFilesInDirectoryAsFlow( treeUri: String ): Flow = treeUri.toUri().listFilesInDirectoryProgressive().map { it.toString() }.flowOn(ioDispatcher) private suspend fun copyMetadata( initialExif: Metadata?, fileUri: Uri, keepOriginalMetadata: Boolean, originalUri: Uri ) = runSuspendCatching { if (initialExif != null) { openFileDescriptor(fileUri)?.use { initialExif.makeLog("initialMetadata") .copyTo(it.fileDescriptor.toMetadata().makeLog("dstMetadata")) } } else if (keepOriginalMetadata) { if (fileUri != originalUri) { openFileDescriptor(fileUri)?.use { readMetadata(originalUri.toString()).makeLog("srcMetadata")?.copyTo( it.fileDescriptor.toMetadata().makeLog("dstMetadata") ) } } else { "Nothing, copying from self to self is pointless".makeLog("copyMetadata") } } else { openFileDescriptor(fileUri)?.use { it.fileDescriptor.toMetadata().apply { clearAllAttributes() if (settingsState.keepDateTime) { readMetadata(originalUri.toString()).makeLog("srcMetadata") ?.copyTo( metadata = this, tags = MetadataTag.dateEntries ) } else { saveAttributes() } } }.makeLog("metadataCleared") } } private fun openFileDescriptor( imageUri: Uri ) = context.openFileDescriptor( uri = imageUri.tryExtractOriginal(), mode = FileMode.ReadWrite ) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidFilenameCreator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving import android.content.Context import android.net.Uri import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.utils.computeFromByteArray import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.title import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.RandomStringGenerator import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Date import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.DateUpper import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Extension import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.ExtensionUpper import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Height import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.OriginalName import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.OriginalNameUpper import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Prefix import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.PrefixUpper import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.PresetInfo import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.PresetInfoUpper import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Rand import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.ScaleMode import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.ScaleModeUpper import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Sequence import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Suffix import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.SuffixUpper import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Width import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.replace import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.logger.makeLog import dagger.hilt.android.qualifiers.ApplicationContext import java.util.Date import java.util.Locale import javax.inject.Inject import kotlin.random.Random internal class AndroidFilenameCreator @Inject constructor( private val randomStringGenerator: RandomStringGenerator, @ApplicationContext private val context: Context, settingsManager: SettingsManager, dispatchersHolder: DispatchersHolder, resourceManager: ResourceManager ) : FilenameCreator, DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { private val _settingsState = settingsManager.settingsState private val settingsState get() = _settingsState.value override fun constructImageFilename( saveTarget: ImageSaveTarget, oneTimePrefix: String?, forceNotAddSizeInFilename: Boolean, pattern: String? ): String { val extension = saveTarget.extension return when (val behavior = settingsState.filenameBehavior) { is FilenameBehavior.Checksum -> "${behavior.hashingType.computeFromByteArray(saveTarget.data)}.$extension" is FilenameBehavior.Random -> "${randomStringGenerator.generate(32)}.$extension" is FilenameBehavior.Overwrite, is FilenameBehavior.None -> constructImageFilename( saveTarget = saveTarget, oneTimePrefix = oneTimePrefix, pattern = (pattern ?: settingsState.filenamePattern).orEmpty().ifBlank { if (settingsState.addOriginalFilename) { FilenamePattern.ForOriginal } else { FilenamePattern.Default } } ) }.makeLog("Filename") } private fun constructImageFilename( saveTarget: ImageSaveTarget, oneTimePrefix: String?, pattern: String ): String { val random = Random(Date().time) val extension = saveTarget.extension val isOriginalEmpty = saveTarget.originalUri.toUri() == Uri.EMPTY val widthString = if (settingsState.addSizeInFilename) { if (isOriginalEmpty) "" else saveTarget.imageInfo.width.toString() } else "" val heightString = if (settingsState.addSizeInFilename) { if (isOriginalEmpty) "" else saveTarget.imageInfo.height.toString() } else "" val prefix = oneTimePrefix ?: settingsState.filenamePrefix val suffix = settingsState.filenameSuffix val originalName = if (settingsState.addOriginalFilename && !isOriginalEmpty) { saveTarget.originalUri.toUri().filename(context) ?.substringBeforeLast('.').orEmpty() } else "" val presetInfo = if (settingsState.addPresetInfoToFilename && saveTarget.presetInfo != null && saveTarget.presetInfo != Preset.None) { saveTarget.presetInfo?.asString().orEmpty() } else "" val scaleModeInfo = if (settingsState.addImageScaleModeInfoToFilename && saveTarget.imageInfo.imageScaleMode != ImageScaleMode.NotPresent) { getStringLocalized( resId = saveTarget.imageInfo.imageScaleMode.title, language = Locale.ENGLISH.language ).replace(" ", "-") } else "" val randomNumber: (count: Int) -> String = { count -> buildString { repeat(count.coerceAtMost(500)) { append(random.nextInt(0, 10)) } }.take(count) } val timestampString = if (settingsState.addTimestampToFilename) { if (settingsState.useFormattedFilenameTimestamp) { "${timestamp()}_${randomNumber(4)}" } else Date().time.toString() } else "" val sequenceNumber = saveTarget.sequenceNumber?.toString() ?: "" var result = pattern runCatching { result = result.replace("""\\d\{(.*?)\}""".toRegex()) { match -> if (settingsState.addTimestampToFilename) { timestamp( format = match.groupValues[1] ) } else { "" } } result = result.replace("""\\D\{(.*?)\}""".toRegex()) { match -> if (settingsState.addTimestampToFilename) { timestamp( format = match.groupValues[1] ).uppercase() } else { "" } } }.onFailure { it.makeLog("pattern date") } runCatching { result = result.replace("""\\r\{(\d+)\}""".toRegex()) { match -> randomNumber(match.groupValues[1].toIntOrNull() ?: 4) } }.onFailure { it.makeLog("pattern random") } return result .replace(Width, widthString) .replace(Height, heightString) .replace(Prefix, prefix) .replace(Suffix, suffix) .replace(OriginalName, originalName) .replace(Sequence, sequenceNumber) .replace(PresetInfo, presetInfo) .replace(ScaleMode, scaleModeInfo) .replace(Extension, extension) .replace(Rand, randomNumber(4)) .replace(Date, timestampString) .replace(PrefixUpper, prefix.uppercase()) .replace(SuffixUpper, suffix.uppercase()) .replace(OriginalNameUpper, originalName.uppercase()) .replace(PresetInfoUpper, presetInfo.uppercase()) .replace(ScaleModeUpper, scaleModeInfo.uppercase()) .replace(ExtensionUpper, extension.uppercase()) .replace(DateUpper, timestampString.uppercase()) .clean() .let { str -> if (str.split(".").filter { it.isNotBlank() }.size < 2) { "image${randomNumber(4)}.$extension" } else { str } } .clean() } private fun String.clean(): String = this .replace("[]", "") .replace("{}", "") .replace("()x()", "") .replace("()", "") .replace("___", "_") .replace("__", "_") .replace("_.", ".") .removePrefix("_") override fun constructRandomFilename( extension: String, length: Int ): String = "${randomStringGenerator.generate(length)}.${extension}" override fun getFilename(uri: String): String = uri.toUri().filename(context) ?: "" } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/AndroidKeepAliveService.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat import com.t8rin.imagetoolbox.core.data.saving.KeepAliveForegroundService.Companion.ACTION_STOP import com.t8rin.imagetoolbox.core.data.saving.KeepAliveForegroundService.Companion.ACTION_UPDATE import com.t8rin.imagetoolbox.core.data.saving.KeepAliveForegroundService.Companion.EXTRA_DESC import com.t8rin.imagetoolbox.core.data.saving.KeepAliveForegroundService.Companion.EXTRA_PROGRESS import com.t8rin.imagetoolbox.core.data.saving.KeepAliveForegroundService.Companion.EXTRA_REMOVE_NOTIFICATION import com.t8rin.imagetoolbox.core.data.saving.KeepAliveForegroundService.Companion.EXTRA_TITLE import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.utils.tryAll import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject internal class AndroidKeepAliveService @Inject constructor( @ApplicationContext private val context: Context ) : KeepAliveService { private val baseIntent: Intent get() = Intent(context, KeepAliveForegroundService::class.java) override fun updateOrStart( title: String, description: String, progress: Float ) { val intent = baseIntent .setAction(ACTION_UPDATE) .putExtra(EXTRA_TITLE, title) .putExtra(EXTRA_DESC, description) .putExtra(EXTRA_PROGRESS, progress) tryAll( { ContextCompat.startForegroundService(context, intent) }, { context.startService(intent) } ) } override fun stop(removeNotification: Boolean) { val intent = baseIntent .setAction(ACTION_STOP) .putExtra(EXTRA_REMOVE_NOTIFICATION, removeNotification) tryAll( { context.startService(intent) }, { context.stopService(intent) } ) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/KeepAliveForegroundService.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build import android.os.Handler import android.os.Looper import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat import androidx.core.content.getSystemService import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.logger.makeLog import kotlin.math.roundToInt internal class KeepAliveForegroundService : Service() { private val notificationManager: NotificationManager by lazy { getSystemService()!! } private var title = "" private var description = "" private var progress: Float = KeepAliveService.PROGRESS_NO_PROGRESS private var removeNotification: Boolean = true override fun onCreate() { super.onCreate() createChannel() startForeground() } override fun onDestroy() { super.onDestroy() stopForegroundSafe() } override fun onStartCommand( intent: Intent?, flags: Int, startId: Int ): Int { if (intent == null) { startForeground() Handler(Looper.getMainLooper()).postDelayed( ::stopForegroundSafe, 1000 ) return START_NOT_STICKY } handleIntent(intent) return START_NOT_STICKY } private fun handleIntent(intent: Intent) { when (intent.action.makeLog("KeepAliveForegroundService")) { ACTION_UPDATE -> { title = intent.getStringExtra(EXTRA_TITLE) ?: title description = intent.getStringExtra(EXTRA_DESC) ?: description progress = intent.getFloatExtra( EXTRA_PROGRESS, KeepAliveService.PROGRESS_NO_PROGRESS ) "UPDATE: title = $title, description = $description, progress = $progress".makeLog("KeepAliveForegroundService") startForeground() } ACTION_STOP -> { startForeground() removeNotification = intent.getBooleanExtra( EXTRA_REMOVE_NOTIFICATION, true ).makeLog("KeepAliveForegroundService") Handler(Looper.getMainLooper()).postDelayed( ::stopForegroundSafe, 1000 ) } else -> startForeground() } } private fun startForeground() { val action = { ServiceCompat.startForeground( this, NOTIFICATION_ID, buildNotification(), if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC } else { 0 } ) } runCatching { action() }.onFailure { action() it.makeLog() "startForeground failed".makeLog("KeepAliveForegroundService") } } private fun createChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, getString(R.string.processing_channel), NotificationManager.IMPORTANCE_LOW ).apply { setSound(null, null) enableVibration(false) } notificationManager.createNotificationChannel(channel) } } @Suppress("DEPRECATION") private fun stopForegroundSafe() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground( if (removeNotification) { STOP_FOREGROUND_REMOVE } else { STOP_FOREGROUND_DETACH } ) } else { stopForeground(removeNotification) } if (removeNotification) { notificationManager.cancel(NOTIFICATION_ID) } stopSelf() } private fun buildNotification(): Notification { val launchIntent = Intent( this, Class.forName( "com.t8rin.imagetoolbox.app.presentation.AppActivity" ) ).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP } val contentPendingIntent = PendingIntent.getActivity( this, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or (PendingIntent.FLAG_IMMUTABLE) ) return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(title.ifEmpty { getString(R.string.processing) }) .setContentText(description.takeIf { it.isNotEmpty() }) .setSmallIcon(R.drawable.ic_notification_icon) .run { when (progress) { KeepAliveService.PROGRESS_NO_PROGRESS -> this KeepAliveService.PROGRESS_INDETERMINATE -> setProgress(0, 0, true) else -> setProgress(100, (progress * 100).roundToInt(), false) } } .setOngoing(true) .setContentIntent(contentPendingIntent) .build() .also { notification -> runCatching { notificationManager.notify(NOTIFICATION_ID, notification) }.onFailure { it.makeLog("KeepAliveForegroundService") } } } override fun onBind(intent: Intent?) = null companion object { internal const val CHANNEL_ID = "keep_alive_channel" internal const val NOTIFICATION_ID = 1 internal const val ACTION_STOP = "ACTION_STOP" internal const val ACTION_UPDATE = "ACTION_UPDATE" internal const val EXTRA_TITLE = "EXTRA_TITLE" internal const val EXTRA_DESC = "EXTRA_DESC" internal const val EXTRA_PROGRESS = "EXTRA_PROGRESS" internal const val EXTRA_REMOVE_NOTIFICATION = "REMOVE_NOTIFICATION" } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SaveException.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving class SaveException(override val message: String?) : Throwable(message) ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/SavingFolder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving import android.content.ContentValues import android.content.Context import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import com.t8rin.imagetoolbox.core.data.saving.io.StreamWriteable import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import kotlinx.coroutines.coroutineScope import java.io.File import java.io.FileOutputStream import java.io.OutputStream @ConsistentCopyVisibility internal data class SavingFolder private constructor( val outputStream: OutputStream, val fileUri: Uri ) : StreamWriteable by StreamWriteable(outputStream) { companion object { suspend fun getInstance( context: Context, treeUri: Uri?, saveTarget: SaveTarget ): SavingFolder? = coroutineScope { if (treeUri == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val type = saveTarget.mimeType.entry val path = "${Environment.DIRECTORY_DOCUMENTS}/ImageToolbox" val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, saveTarget.filename) put( MediaStore.MediaColumns.MIME_TYPE, type ) put( MediaStore.MediaColumns.RELATIVE_PATH, path ) } val imageUri = context.contentResolver.insert( MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), contentValues ) ?: return@coroutineScope null SavingFolder( outputStream = context.contentResolver.openOutputStream(imageUri) ?: return@coroutineScope null, fileUri = imageUri ) } else { val imagesDir = File( Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOCUMENTS ), "ImageToolbox" ) if (!imagesDir.exists()) imagesDir.mkdir() val filename = saveTarget.filename ?: return@coroutineScope null SavingFolder( outputStream = FileOutputStream(File(imagesDir, filename)), fileUri = File(imagesDir, filename).toUri() ) } } else if (DocumentFile.isDocumentUri(context, treeUri)) { SavingFolder( outputStream = context.contentResolver.openOutputStream(treeUri) ?: return@coroutineScope null, fileUri = treeUri ) } else { val documentFile = DocumentFile.fromTreeUri(context, treeUri) if (documentFile == null || !documentFile.exists()) return@coroutineScope null val filename = saveTarget.filename ?: return@coroutineScope null val file = documentFile.createFile(saveTarget.mimeType.entry, filename) val imageUri = file?.uri ?: return@coroutineScope null SavingFolder( outputStream = context.contentResolver.openOutputStream(imageUri) ?: return@coroutineScope null, fileUri = imageUri ) } } } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/FileWriteable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving.io import java.io.File class FileWriteable( private val file: File ) : StreamWriteable by StreamWriteable(file.outputStream()) class FileReadable( private val file: File ) : StreamReadable by StreamReadable(file.inputStream()) ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/StreamWriteable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving.io import com.t8rin.imagetoolbox.core.domain.saving.io.Readable import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable import com.t8rin.logger.makeLog import java.io.InputStream import java.io.OutputStream private class StreamWriteableImpl( outputStream: OutputStream ) : StreamWriteable { override val stream = outputStream override fun writeBytes(byteArray: ByteArray) = stream.write(byteArray) override fun close() { stream.flush() stream.close() } } private class StreamReadableImpl( inputStream: InputStream ) : StreamReadable { override val stream = inputStream override fun readBytes(): ByteArray = stream.readBytes() override fun copyTo(writeable: Writeable) { if (writeable is StreamWriteable) { stream.copyTo(writeable.stream) } else { writeable.writeBytes(readBytes()) } } override fun close() = stream.close() } interface StreamReadable : Readable { val stream: InputStream companion object { operator fun invoke( inputStream: InputStream ): StreamReadable = StreamReadableImpl(inputStream) } } interface StreamWriteable : Writeable { val stream: OutputStream companion object { operator fun invoke( outputStream: OutputStream ): StreamWriteable = StreamWriteableImpl(outputStream) } } fun StreamWriteable.shielded(): StreamWriteable = CloseShieldWriteable(this) private class CloseShieldWriteable(wrapped: StreamWriteable) : StreamWriteable by wrapped { override fun close() { "can't be closed".makeLog("CloseShieldWriteable") } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/saving/io/UriReadable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.saving.io import android.content.Context import android.net.Uri import com.t8rin.imagetoolbox.core.data.utils.openWriteableStream import com.t8rin.logger.makeLog import io.ktor.utils.io.charsets.Charset import java.io.ByteArrayOutputStream class UriReadable( private val uri: Uri, private val context: Context ) : StreamReadable by StreamReadable( inputStream = context.contentResolver.openInputStream(uri) ?: ByteArray(0).inputStream() ) class UriWriteable( private val uri: Uri, private val context: Context ) : StreamWriteable by StreamWriteable( outputStream = context.openWriteableStream( uri = uri, onFailure = { uri.makeLog("UriWriteable write") it.makeLog("UriWriteable write") throw it } ) ?: ByteArrayOutputStream(0) ) class ByteArrayReadable( private val byteArray: ByteArray ) : StreamReadable by StreamReadable( inputStream = byteArray.inputStream() ) class StringReadable( private val string: String, private val charset: Charset = Charsets.UTF_8 ) : StreamReadable by ByteArrayReadable( byteArray = string.toByteArray(charset) ) ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/BitmapUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.os.Build import android.os.Build.VERSION.SDK_INT import androidx.core.graphics.drawable.toBitmap import coil3.Image import java.io.ByteArrayOutputStream private val possibleConfigs = mutableListOf().apply { if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { add(Bitmap.Config.RGBA_1010102) } if (SDK_INT >= Build.VERSION_CODES.O) { add(Bitmap.Config.RGBA_F16) } add(Bitmap.Config.ARGB_8888) add(Bitmap.Config.RGB_565) } fun getSuitableConfig( image: Bitmap? = null ): Bitmap.Config = image?.config?.takeIf { it in possibleConfigs } ?: Bitmap.Config.ARGB_8888 fun Bitmap.toSoftware(): Bitmap = copy(getSuitableConfig(this), false) ?: this val Bitmap.aspectRatio: Float get() = width / height.toFloat() val Image.aspectRatio: Float get() = width / height.toFloat() val Drawable.aspectRatio: Float get() = intrinsicWidth / intrinsicHeight.toFloat() val Bitmap.safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) val Bitmap.safeConfig: Bitmap.Config get() = config ?: getSuitableConfig(this) val Drawable.safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) fun Drawable.toBitmap(): Bitmap = toBitmap(config = getSuitableConfig()) fun Bitmap.compress( format: Bitmap.CompressFormat, quality: Int ): ByteArray = ByteArrayOutputStream().apply { use { out -> compress(format, quality, out) } }.toByteArray() val Bitmap.Config.isHardware: Boolean get() = SDK_INT >= 26 && this == Bitmap.Config.HARDWARE fun Bitmap.Config?.toSoftware(): Bitmap.Config { return if (this == null || isHardware) Bitmap.Config.ARGB_8888 else this } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ChecksumUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import com.t8rin.imagetoolbox.core.data.saving.io.StreamReadable import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.saving.io.Readable import java.io.InputStream import java.security.MessageDigest private const val STREAM_BUFFER_LENGTH = 1024 fun HashingType.computeBytesFromReadable( readable: Readable ): ByteArray = if (readable is StreamReadable) { computeBytesFromInputStream(readable.stream) } else { computeBytesFromByteArray(readable.readBytes()) } fun HashingType.computeFromReadable( readable: Readable ): String = if (readable is StreamReadable) { computeFromInputStream(readable.stream) } else { computeFromByteArray(readable.readBytes()) } internal fun HashingType.computeFromByteArray( byteArray: ByteArray ): String = computeFromInputStream(byteArray.inputStream()) internal fun HashingType.computeFromInputStream( inputStream: InputStream ): String = inputStream.buffered().use { val byteArray = updateDigest(it).digest() val hexCode = encodeHex(byteArray, true) return@use String(hexCode) } internal fun HashingType.computeBytesFromByteArray( byteArray: ByteArray ): ByteArray = computeBytesFromInputStream(byteArray.inputStream()) internal fun HashingType.computeBytesFromInputStream( inputStream: InputStream ): ByteArray = inputStream.buffered().use { return@use updateDigest(it).digest() } /** * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. * The returned array will be double the length of the passed array, as it takes two characters to represent any * given byte. * * @param data a byte[] to convert to Hex characters * @param toLowerCase `true` converts to lowercase, `false` to uppercase * @return A char[] containing hexadecimal characters in the selected case */ internal fun encodeHex( data: ByteArray, toLowerCase: Boolean ): CharArray = encodeHex( data = data, toDigits = if (toLowerCase) { DIGITS_LOWER } else { DIGITS_UPPER } ) /** * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. * The returned array will be double the length of the passed array, as it takes two characters to represent any * given byte. * * @param data a byte[] to convert to Hex characters * @param toDigits the output alphabet (must contain at least 16 chars) * @return A char[] containing the appropriate characters from the alphabet * For best results, this should be either upper- or lower-case hex. */ internal fun encodeHex( data: ByteArray, toDigits: CharArray ): CharArray { val l = data.size val out = CharArray(l shl 1) // two characters form the hex value. var i = 0 var j = 0 while (i < l) { out[j++] = toDigits[0xF0 and data[i].toInt() ushr 4] out[j++] = toDigits[0x0F and data[i].toInt()] i++ } return out } /** * Reads through an InputStream and updates the digest for the data * * @param HashingType The ChecksumType to use (e.g. MD5) * @param data Data to digest * @return the digest */ private fun HashingType.updateDigest( data: InputStream ): MessageDigest { val digest = toDigest() val buffer = ByteArray(STREAM_BUFFER_LENGTH) var read = data.read(buffer, 0, STREAM_BUFFER_LENGTH) while (read > -1) { digest.update(buffer, 0, read) read = data.read(buffer, 0, STREAM_BUFFER_LENGTH) } return digest } /** * Used to build output as Hex */ private val DIGITS_LOWER = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') /** * Used to build output as Hex */ private val DIGITS_UPPER = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F') private fun HashingType.toDigest(): MessageDigest = MessageDigest.getInstance(digest) ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoilUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import android.graphics.Bitmap import coil3.size.Size import coil3.size.pxOrElse import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import coil3.transform.Transformation as CoilTransformation fun Size.asDomain(): IntegerSize = if (this == Size.ORIGINAL) IntegerSize.Undefined else IntegerSize(width.pxOrElse { 1 }, height.pxOrElse { 1 }) fun IntegerSize.asCoil(): Size = if (this == IntegerSize.Undefined) Size.ORIGINAL else Size(width, height) fun Transformation.toCoil(): CoilTransformation = object : CoilTransformation() { override fun toString(): String = this@toCoil::class.simpleName.toString() override val cacheKey: String get() = this@toCoil.cacheKey override suspend fun transform( input: Bitmap, size: Size ): Bitmap = this@toCoil.transform(input, size.asDomain()) } fun CoilTransformation.asDomain(): Transformation = object : Transformation { override val cacheKey: String get() = this@asDomain.cacheKey override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = this@asDomain.transform(input, size.asCoil()) } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/ContextUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.net.Uri import android.os.Build import androidx.compose.ui.unit.Density import androidx.core.content.ContextCompat import com.t8rin.imagetoolbox.core.domain.utils.FileMode import kotlinx.coroutines.coroutineScope import java.io.OutputStream fun Context.isExternalStorageWritable(): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) true else ContextCompat.checkSelfPermission( this, Manifest.permission.WRITE_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED } fun Context.openWriteableStream( uri: Uri?, onFailure: (Throwable) -> Unit = {} ): OutputStream? = uri?.let { runCatching { openOutputStream( uri = uri, mode = FileMode.ReadWrite ) }.getOrElse { runCatching { openOutputStream( uri = uri, mode = FileMode.Write ) }.onFailure(onFailure).getOrNull() } } fun Context.openFileDescriptor(uri: Uri, mode: FileMode = FileMode.Read) = contentResolver.openFileDescriptor(uri, mode.mode) fun Context.openOutputStream(uri: Uri, mode: FileMode) = contentResolver.openOutputStream(uri, mode.mode) internal suspend fun Context.clearCache() = coroutineScope { runCatching { cacheDir?.deleteRecursively() codeCacheDir?.deleteRecursively() externalCacheDir?.deleteRecursively() externalCacheDirs?.forEach { it.deleteRecursively() } } } internal fun Context.cacheSize(): Long = runCatching { val cache = cacheDir?.walkTopDown()?.sumOf { if (it.isFile) it.length() else 0 } ?: 0 val code = codeCacheDir?.walkTopDown()?.sumOf { if (it.isFile) it.length() else 0 } ?: 0 var size = cache + code externalCacheDirs?.forEach { file -> size += file?.walkTopDown()?.sumOf { if (it.isFile) it.length() else 0 } ?: 0 } size }.getOrNull() ?: 0 fun Context.isInstalledFromPlayStore(): Boolean = verifyInstallerId( listOf( "com.android.vending", "com.google.android.feedback" ) ) private fun Context.verifyInstallerId( validInstallers: List, ): Boolean = validInstallers.contains(getInstallerPackageName(packageName)) private fun Context.getInstallerPackageName( packageName: String ): String? = runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { packageManager.getInstallSourceInfo(packageName).installingPackageName } else { @Suppress("DEPRECATION") packageManager.getInstallerPackageName(packageName) } }.getOrNull() val Context.density: Density get() = object : Density { override val density: Float get() = resources.displayMetrics.density override val fontScale: Float get() = resources.configuration.fontScale } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/CoroutinesUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import kotlinx.coroutines.asCoroutineDispatcher import java.util.concurrent.ExecutorService import kotlin.coroutines.CoroutineContext inline fun executorDispatcher( executor: () -> ExecutorService ): CoroutineContext = executor().asCoroutineDispatcher() ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/FileUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import android.os.Build import android.os.FileObserver import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import java.io.File @Suppress("DEPRECATION") fun File.observeHasChanges( flags: Int = FileObserver.ALL_EVENTS ): Flow = callbackFlow { val observer = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { object : FileObserver(this@observeHasChanges, flags) { override fun onEvent(event: Int, path: String?) { trySend(Unit) } } } else { object : FileObserver(absolutePath, flags) { override fun onEvent(event: Int, path: String?) { trySend(Unit) } } } send(Unit) observer.startWatching() awaitClose { observer.stopWatching() } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/HttpClientUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import io.ktor.client.HttpClient import io.ktor.client.plugins.onDownload import io.ktor.client.request.prepareGet import io.ktor.client.statement.bodyAsChannel import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.supervisorScope suspend fun HttpClient.getWithProgress( url: String, onFailure: suspend (Throwable) -> Unit = {}, onProgress: suspend (bytesSentTotal: Long, contentLength: Long?) -> Unit, onOpen: suspend (ByteReadChannel) -> Unit ) = supervisorScope { runSuspendCatching { prepareGet( urlString = url, block = { onDownload(onProgress) } ).execute { response -> onOpen(response.bodyAsChannel()) } }.onFailure { onFailure(it) } } ================================================ FILE: core/data/src/main/java/com/t8rin/imagetoolbox/core/data/utils/WriteableUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.data.utils import com.t8rin.imagetoolbox.core.data.saving.io.StreamWriteable import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable import java.io.OutputStream fun Writeable.outputStream(): OutputStream = if (this is StreamWriteable) { stream } else { object : OutputStream() { override fun write(b: Int) = writeBytes(byteArrayOf(b.toByte())) } } ================================================ FILE: core/di/.gitignore ================================================ /build ================================================ FILE: core/di/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.hilt) } android.namespace = "com.t8rin.imagetoolbox.core.di" ================================================ FILE: core/di/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/di/src/main/java/com/t8rin/imagetoolbox/core/di/EntryPointUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.di import android.content.Context import dagger.hilt.android.EntryPointAccessors inline fun Context.entryPoint( action: T.() -> Unit = {} ) = action( EntryPointAccessors.fromApplication( context = this, entryPoint = T::class.java ) ) ================================================ FILE: core/di/src/main/java/com/t8rin/imagetoolbox/core/di/Qualifiers.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.di import javax.inject.Qualifier @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DefaultDispatcher @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class EncodingDispatcher @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DecodingDispatcher @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class IoDispatcher @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UiDispatcher ================================================ FILE: core/domain/.gitignore ================================================ /build ================================================ FILE: core/domain/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.hilt) } android.namespace = "com.t8rin.imagetoolbox.core.domain" dependencies { implementation(projects.core.resources) } ================================================ FILE: core/domain/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/Constants.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain const val AUTHOR_NICK = "T8RIN" const val AUTHOR_TELEGRAM = "http://t.me/$AUTHOR_NICK" const val TELEGRAM_GROUP_LINK = "https://t.me/t8rin_imagetoolbox" const val TELEGRAM_CHANNEL_LINK = "https://t.me/t8rin_imagetoolbox_ci" const val AUTHOR_GITHUB = "https://github.com/$AUTHOR_NICK" const val APP_GITHUB_LINK = "$AUTHOR_GITHUB/ImageToolbox" const val ISSUE_TRACKER = "$APP_GITHUB_LINK/issues" const val APP_RELEASES = "$APP_GITHUB_LINK/releases" const val APP_CHANGELOG = "$APP_RELEASES.atom" const val RES_HOST = "$AUTHOR_GITHUB/ImageToolboxRemoteResources" const val LENS_PROFILES_LINK = "$RES_HOST/tree/main/lens_profile" const val RES_BASE_URL = "$RES_HOST/raw/refs/heads/main/*" const val HF_BASE_URL = "https://huggingface.co/T8RIN/imagetoolbox-models/resolve/main/*" const val GLOBAL_STORAGE_NAME = "image_resizer" const val BACKUP_FILE_EXT = "imtbx_backup" const val PDF = "pdf/" const val WEBLATE_LINK = "https://hosted.weblate.org/engage/image-resizer/" const val PARTNER_FREE_SOFTWARE = "https://t.me/FreeApkexe" const val JAVA_FORMAT_SPECIFICATION = "https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html" const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" const val BTC_WALLET = "18QFWMREkjzQa4yetfYsN5Ua51UubKmJut" const val USDT_WALLET = "TVdw6fP8dYsYA6HgQiSYNijBqPJ3k5BbYo" const val TON_SPACE_WALLET = "UQDMePBU-FaxwaIME8p7h2spRITeRxvtCPegtKefeV5v-sN1" const val TON_WALLET = "UQB8YI7eEJsFkr05juS-Ix1pRxhgRvCDF9S0g_aXayVJoGtg" const val BOOSTY_LINK = "https://boosty.to/t8rin" ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/AppScope.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.coroutines import kotlinx.coroutines.CoroutineScope interface AppScope : CoroutineScope ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/coroutines/DispatchersHolder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.coroutines import kotlin.coroutines.CoroutineContext interface DispatchersHolder { val uiDispatcher: CoroutineContext val ioDispatcher: CoroutineContext val encodingDispatcher: CoroutineContext val decodingDispatcher: CoroutineContext val defaultDispatcher: CoroutineContext } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageCompressor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality interface ImageCompressor { suspend fun compress( image: Image, imageFormat: ImageFormat, quality: Quality ): ByteArray suspend fun compressAndTransform( image: Image, imageInfo: ImageInfo, onImageReadyToCompressInterceptor: suspend (Bitmap) -> Bitmap = { it }, applyImageTransformations: Boolean = true ): ByteArray suspend fun calculateImageSize( image: Image, imageInfo: ImageInfo ): Long } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageGetter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image import com.t8rin.imagetoolbox.core.domain.image.model.ImageData import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation interface ImageGetter { suspend fun getImage( uri: String, originalSize: Boolean = true, onFailure: (Throwable) -> Unit = {} ): ImageData? fun getImageAsync( uri: String, originalSize: Boolean = true, onGetImage: (ImageData) -> Unit, onFailure: (Throwable) -> Unit ) suspend fun getImageWithTransformations( uri: String, transformations: List>, originalSize: Boolean = true ): ImageData? suspend fun getImageWithTransformations( data: Any, transformations: List>, size: IntegerSize? ): I? suspend fun getImage( data: Any, originalSize: Boolean = true ): I? suspend fun getImage( data: Any, size: IntegerSize? ): I? suspend fun getImage( data: Any, size: Int? ): I? suspend fun getImageData( uri: String, size: Int?, onFailure: (Throwable) -> Unit ): ImageData? fun getExtension( uri: String ): String? } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImagePreviewCreator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.transformation.Transformation interface ImagePreviewCreator { suspend fun createPreview( image: I, imageInfo: ImageInfo, transformations: List> = emptyList(), onGetByteCount: (Int) -> Unit ): I? fun canShow(image: I?): Boolean } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageScaler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType interface ImageScaler { suspend fun scaleImage( image: I, width: Int, height: Int, resizeType: ResizeType = ResizeType.Explicit, imageScaleMode: ImageScaleMode = ImageScaleMode.NotPresent ): I suspend fun scaleUntilCanShow( image: I? ): I? } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageShareProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo interface ImageShareProvider : ShareProvider { suspend fun shareImage( imageInfo: ImageInfo, image: I, onComplete: () -> Unit ) suspend fun cacheImage( image: I, imageInfo: ImageInfo, filename: String? = null ): String? suspend fun shareImages( uris: List, imageLoader: suspend (String) -> Pair?, onProgressChange: (Int) -> Unit ) } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ImageTransformer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation interface ImageTransformer { suspend fun transform( image: I, transformations: List>, originalSize: Boolean = true ): I? suspend fun transform( image: I, transformations: List>, size: IntegerSize ): I? suspend fun rotate( image: I, degrees: Float ): I suspend fun flip( image: I, isFlipped: Boolean ): I suspend fun applyPresetBy( image: I?, preset: Preset, currentInfo: ImageInfo ): ImageInfo } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/Metadata.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.domain.image import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag interface Metadata { fun saveAttributes(): Metadata fun getAttribute(tag: MetadataTag): String? fun setAttribute( tag: MetadataTag, value: String? ): Metadata } private class TagMapMetadata( initialTags: Map ) : Metadata { private val tags: MutableMap = initialTags.toMutableMap() override fun saveAttributes(): Metadata = this override fun getAttribute(tag: MetadataTag): String? = tags[tag] override fun setAttribute( tag: MetadataTag, value: String? ): Metadata = apply { if (value == null) { tags.remove(tag) } else { tags[tag] = value } } override fun toString(): String = "ReadOnly(${toMap()})" } fun Metadata.readOnly(): Metadata = TagMapMetadata(toMap()) inline operator fun Metadata.get( tag: MetadataTag ): String? = getAttribute(tag) inline operator fun Metadata.set( tag: MetadataTag, value: String? ): Metadata = setAttribute(tag, value) inline fun Metadata.clearAttribute( tag: MetadataTag ) = apply { setAttribute( tag = tag, value = null ) } inline fun Metadata.clearAttributes( attributes: List ): Metadata = apply { attributes.forEach(::clearAttribute) } inline fun Metadata.clearAllAttributes(): Metadata = clearAttributes(attributes = MetadataTag.entries) inline fun Metadata.toMap(): Map = mutableMapOf().apply { MetadataTag.entries.forEach { tag -> getAttribute(tag)?.let { put(tag, it) } } } inline fun Metadata.copyTo( metadata: Metadata, tags: List = MetadataTag.entries ): Metadata { tags.forEach { attr -> getAttribute(attr).let { metadata.setAttribute(attr, it) } } metadata.saveAttributes() return metadata } fun interface MetadataProvider { suspend fun readMetadata(imageUri: String): Metadata? } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/ShareProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable interface ShareProvider { suspend fun cacheByteArray( byteArray: ByteArray, filename: String ): String? suspend fun shareByteArray( byteArray: ByteArray, filename: String, onComplete: () -> Unit = {} ) suspend fun cacheData( filename: String, writeData: suspend (Writeable) -> Unit, ): String? suspend fun cacheDataOrThrow( filename: String, writeData: suspend (Writeable) -> Unit, ): String suspend fun shareData( writeData: suspend (Writeable) -> Unit, filename: String, onComplete: () -> Unit = {} ) suspend fun shareUri( uri: String, type: MimeType.Single? = null, onComplete: () -> Unit ) suspend fun shareUris( uris: List ) fun shareText( value: String, onComplete: () -> Unit ) } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/BlendingMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Color import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.ColorBurn import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.ColorDodge import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Difference import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.DstAtop import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.DstIn import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.DstOut import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.DstOver import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Exclusion import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Hardlight import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Hue import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Luminosity import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Modulate import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Multiply import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Overlay import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Saturation import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Screen import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.Softlight import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.SrcAtop import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.SrcIn import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.SrcOut import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode.Companion.SrcOver @JvmInline value class BlendingMode internal constructor(val value: Int) { companion object { /** * Drop both the source and destination images, leaving nothing. */ val Clear = BlendingMode(0) /** * Drop the destination image, only paint the source image. * * Conceptually, the destination is first cleared, then the source image is * painted. */ val Src = BlendingMode(1) /** * Drop the source image, only paint the destination image. * * Conceptually, the source image is discarded, leaving the destination * untouched. */ val Dst = BlendingMode(2) /** * Composite the source image over the destination image. * * This is the default value. It represents the most intuitive case, where * shapes are painted on top of what is below, with transparent areas showing * the destination layer. */ val SrcOver = BlendingMode(3) /** * Composite the source image under the destination image. * * This is the opposite of [SrcOver]. * * This is useful when the source image should have been painted before the * destination image, but could not be. */ val DstOver = BlendingMode(4) /** * Show the source image, but only where the two images overlap. The * destination image is not rendered, it is treated merely as a mask. The * color channels of the destination are ignored, only the opacity has an * effect. * * To show the destination image instead, consider [DstIn]. * * To reverse the semantic of the mask (only showing the source where the * destination is absent, rather than where it is present), consider * [SrcOut]. */ val SrcIn = BlendingMode(5) /** * Show the destination image, but only where the two images overlap. The * source image is not rendered, it is treated merely as a mask. The color * channels of the source are ignored, only the opacity has an effect. * * To show the source image instead, consider [SrcIn]. * * To reverse the semantic of the mask (only showing the source where the * destination is present, rather than where it is absent), consider [DstOut]. */ val DstIn = BlendingMode(6) /** * Show the source image, but only where the two images do not overlap. The * destination image is not rendered, it is treated merely as a mask. The color * channels of the destination are ignored, only the opacity has an effect. * * To show the destination image instead, consider [DstOut]. * * To reverse the semantic of the mask (only showing the source where the * destination is present, rather than where it is absent), consider [SrcIn]. * * This corresponds to the "Source out Destination" Porter-Duff operator. */ val SrcOut = BlendingMode(7) /** * Show the destination image, but only where the two images do not overlap. The * source image is not rendered, it is treated merely as a mask. The color * channels of the source are ignored, only the opacity has an effect. * * To show the source image instead, consider [SrcOut]. * * To reverse the semantic of the mask (only showing the destination where the * source is present, rather than where it is absent), consider [DstIn]. * * This corresponds to the "Destination out Source" Porter-Duff operator. */ val DstOut = BlendingMode(8) /** * Composite the source image over the destination image, but only where it * overlaps the destination. * * This is essentially the [SrcOver] operator, but with the output's opacity * channel being set to that of the destination image instead of being a * combination of both image's opacity channels. * * For a variant with the destination on top instead of the source, see * [DstAtop]. */ val SrcAtop = BlendingMode(9) /** * Composite the destination image over the source image, but only where it * overlaps the source. * * This is essentially the [DstOver] operator, but with the output's opacity * channel being set to that of the source image instead of being a * combination of both image's opacity channels. * * For a variant with the source on top instead of the destination, see * [SrcAtop]. */ val DstAtop = BlendingMode(10) /** * Apply a bitwise `xor` operator to the source and destination images. This * leaves transparency where they would overlap. */ val Xor = BlendingMode(11) /** * Sum the components of the source and destination images. * * Transparency in a pixel of one of the images reduces the contribution of * that image to the corresponding output pixel, as if the color of that * pixel in that image was darker. * */ val Plus = BlendingMode(12) /** * Multiply the color components of the source and destination images. * * This can only result in the same or darker colors (multiplying by white, * 1.0, results in no change; multiplying by black, 0.0, results in black). * * When compositing two opaque images, this has similar effect to overlapping * two transparencies on a projector. * * For a variant that also multiplies the alpha channel, consider [Multiply]. * * See also: * * * [Screen], which does a similar computation but inverted. * * [Overlay], which combines [Modulate] and [Screen] to favor the * destination image. * * [Hardlight], which combines [Modulate] and [Screen] to favor the * source image. */ val Modulate = BlendingMode(13) /** * Multiply the inverse of the components of the source and destination * images, and inverse the result. * * Inverting the components means that a fully saturated channel (opaque * white) is treated as the value 0.0, and values normally treated as 0.0 * (black, transparent) are treated as 1.0. * * This is essentially the same as [Modulate] blend mode, but with the values * of the colors inverted before the multiplication and the result being * inverted back before rendering. * * This can only result in the same or lighter colors (multiplying by black, * 1.0, results in no change; multiplying by white, 0.0, results in white). * Similarly, in the alpha channel, it can only result in more opaque colors. * * This has similar effect to two projectors displaying their images on the * same screen simultaneously. * * See also: * * * [Modulate], which does a similar computation but without inverting the * values. * * [Overlay], which combines [Modulate] and [Screen] to favor the * destination image. * * [Hardlight], which combines [Modulate] and [Screen] to favor the * source image. */ val Screen = BlendingMode(14) // The last coeff mode. /** * Multiply the components of the source and destination images after * adjusting them to favor the destination. * * Specifically, if the destination value is smaller, this multiplies it with * the source value, whereas is the source value is smaller, it multiplies * the inverse of the source value with the inverse of the destination value, * then inverts the result. * * Inverting the components means that a fully saturated channel (opaque * white) is treated as the value 0.0, and values normally treated as 0.0 * (black, transparent) are treated as 1.0. * * See also: * * * [Modulate], which always multiplies the values. * * [Screen], which always multiplies the inverses of the values. * * [Hardlight], which is similar to [Overlay] but favors the source image * instead of the destination image. */ val Overlay = BlendingMode(15) /** * Composite the source and destination image by choosing the lowest value * from each color channel. * * The opacity of the output image is computed in the same way as for * [SrcOver]. */ val Darken = BlendingMode(16) /** * Composite the source and destination image by choosing the highest value * from each color channel. * * The opacity of the output image is computed in the same way as for * [SrcOver]. */ val Lighten = BlendingMode(17) /** * Divide the destination by the inverse of the source. * * Inverting the components means that a fully saturated channel (opaque * white) is treated as the value 0.0, and values normally treated as 0.0 * (black, transparent) are treated as 1.0. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above */ val ColorDodge = BlendingMode(18) /** * Divide the inverse of the destination by the source, and inverse the result. * * Inverting the components means that a fully saturated channel (opaque * white) is treated as the value 0.0, and values normally treated as 0.0 * (black, transparent) are treated as 1.0. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above */ val ColorBurn = BlendingMode(19) /** * Multiply the components of the source and destination images after * adjusting them to favor the source. * * Specifically, if the source value is smaller, this multiplies it with the * destination value, whereas is the destination value is smaller, it * multiplies the inverse of the destination value with the inverse of the * source value, then inverts the result. * * Inverting the components means that a fully saturated channel (opaque * white) is treated as the value 0.0, and values normally treated as 0.0 * (black, transparent) are treated as 1.0. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above * * See also: * * * [Modulate], which always multiplies the values. * * [Screen], which always multiplies the inverses of the values. * * [Overlay], which is similar to [Hardlight] but favors the destination * image instead of the source image. */ val Hardlight = BlendingMode(20) /** * Use [ColorDodge] for source values below 0.5 and [ColorBurn] for source * values above 0.5. * * This results in a similar but softer effect than [Overlay]. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above * * See also: * * * [Color], which is a more subtle tinting effect. */ val Softlight = BlendingMode(21) /** * Subtract the smaller value from the bigger value for each channel. * * Compositing black has no effect; compositing white inverts the colors of * the other image. * * The opacity of the output image is computed in the same way as for * [SrcOver]. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above * * The effect is similar to [Exclusion] but harsher. */ val Difference = BlendingMode(22) /** * Subtract double the product of the two images from the sum of the two * images. * * Compositing black has no effect; compositing white inverts the colors of * the other image. * * The opacity of the output image is computed in the same way as for * [SrcOver]. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above * * The effect is similar to [Difference] but softer. */ val Exclusion = BlendingMode(23) /** * Multiply the components of the source and destination images, including * the alpha channel. * * This can only result in the same or darker colors (multiplying by white, * 1.0, results in no change; multiplying by black, 0.0, results in black). * * Since the alpha channel is also multiplied, a fully-transparent pixel * (opacity 0.0) in one image results in a fully transparent pixel in the * output. This is similar to [DstIn], but with the colors combined. * * For a variant that multiplies the colors but does not multiply the alpha * channel, consider [Modulate]. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above */ val Multiply = BlendingMode(24) // The last separable mode. /** * Take the hue of the source image, and the saturation and luminosity of the * destination image. * * The effect is to tint the destination image with the source image. * * The opacity of the output image is computed in the same way as for * [SrcOver]. Regions that are entirely transparent in the source image take * their hue from the destination. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above */ val Hue = BlendingMode(25) /** * Take the saturation of the source image, and the hue and luminosity of the * destination image. * * The opacity of the output image is computed in the same way as for * [SrcOver]. Regions that are entirely transparent in the source image take * their saturation from the destination. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above * * See also: * * * [Color], which also applies the hue of the source image. * * [Luminosity], which applies the luminosity of the source image to the * destination. */ val Saturation = BlendingMode(26) /** * Take the hue and saturation of the source image, and the luminosity of the * destination image. * * The effect is to tint the destination image with the source image. * * The opacity of the output image is computed in the same way as for * [SrcOver]. Regions that are entirely transparent in the source image take * their hue and saturation from the destination. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above * * See also: * * * [Hue], which is a similar but weaker effect. * * [Softlight], which is a similar tinting effect but also tints white. * * [Saturation], which only applies the saturation of the source image. */ val Color = BlendingMode(27) /** * Take the luminosity of the source image, and the hue and saturation of the * destination image. * * The opacity of the output image is computed in the same way as for * [SrcOver]. Regions that are entirely transparent in the source image take * their luminosity from the destination. * * **NOTE** This [BlendingMode] can only be used on Android API level 29 and above * * See also: * * * [Saturation], which applies the saturation of the source image to the * destination. */ val Luminosity = BlendingMode(28) val newEntries by lazy { listOf( Clear, Src, Dst, SrcOver, DstOver, SrcIn, DstIn, SrcOut, DstOut, SrcAtop, DstAtop, Xor, Plus, Modulate, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, Hardlight, Softlight, Difference, Exclusion, Multiply, Hue, Saturation, Color, Luminosity, ) } val oldEntries by lazy { listOf( Clear, Src, Dst, SrcOver, DstOver, SrcIn, DstIn, SrcOut, DstOut, SrcAtop, DstAtop, Xor, Plus, Modulate, Screen, Overlay, Darken, Lighten, ) } } override fun toString() = when (this) { Clear -> "Clear" Src -> "Src" Dst -> "Dst" SrcOver -> "SrcOver" DstOver -> "DstOver" SrcIn -> "SrcIn" DstIn -> "DstIn" SrcOut -> "SrcOut" DstOut -> "DstOut" SrcAtop -> "SrcAtop" DstAtop -> "DstAtop" Xor -> "Xor" Plus -> "Plus" Modulate -> "Modulate" Screen -> "Screen" Overlay -> "Overlay" Darken -> "Darken" Lighten -> "Lighten" ColorDodge -> "ColorDodge" ColorBurn -> "ColorBurn" Hardlight -> "HardLight" Softlight -> "Softlight" Difference -> "Difference" Exclusion -> "Exclusion" Multiply -> "Multiply" Hue -> "Hue" Saturation -> "Saturation" Color -> "Color" Luminosity -> "Luminosity" else -> "Unknown" // Should not get here since we have an internal constructor } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model import com.t8rin.imagetoolbox.core.domain.image.Metadata interface ImageData { val image: I val imageInfo: ImageInfo val metadata: Metadata? fun copy( image: I = this.image, imageInfo: ImageInfo = this.imageInfo, metadata: Metadata? = this.metadata, ): ImageData = ImageData(image, imageInfo, metadata) operator fun component1() = image operator fun component2() = imageInfo operator fun component3() = metadata companion object { operator fun invoke( image: I, imageInfo: ImageInfo, metadata: Metadata? = null, ): ImageData = ImageDataWrapper(image, imageInfo, metadata) } } private class ImageDataWrapper( override val image: I, override val imageInfo: ImageInfo, override val metadata: Metadata? = null, ) : ImageData ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormat.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model import com.t8rin.imagetoolbox.core.domain.model.MimeType sealed class ImageFormat( val title: String, val extension: String, val mimeType: MimeType.Single, val canChangeCompressionValue: Boolean, val canWriteExif: Boolean = false, val compressionTypes: List = listOf(CompressionType.Quality(0..100)) ) { sealed class Png( title: String, compressionTypes: List, canChangeCompressionValue: Boolean ) : ImageFormat( extension = "png", mimeType = MimeType.StaticPng, canChangeCompressionValue = canChangeCompressionValue, title = title, canWriteExif = true, compressionTypes = compressionTypes ) { data object Lossless : Png( title = "PNG Lossless", compressionTypes = emptyList(), canChangeCompressionValue = false ), LosslessMarker data object Lossy : Png( title = "PNG Lossy", compressionTypes = listOf( CompressionType.Effort(0..9) ), canChangeCompressionValue = true ) data object OxiPNG : Png( title = "OxiPNG", compressionTypes = listOf( CompressionType.Effort(0..6) ), canChangeCompressionValue = true ) } data object Jpg : ImageFormat( title = "JPG", extension = "jpg", mimeType = MimeType.Jpeg, canChangeCompressionValue = true, canWriteExif = true ) data object Jpeg : ImageFormat( title = "JPEG", extension = "jpeg", mimeType = MimeType.Jpeg, canChangeCompressionValue = true, canWriteExif = true ) data object MozJpeg : ImageFormat( title = "MozJPEG", extension = "jpg", mimeType = MimeType.Jpeg, canChangeCompressionValue = true, canWriteExif = true ) data object Jpegli : ImageFormat( title = "Jpegli", extension = "jpg", mimeType = MimeType.Jpeg, canChangeCompressionValue = true, canWriteExif = true ) sealed class Webp( title: String, compressionTypes: List ) : ImageFormat( extension = "webp", mimeType = MimeType.Webp, canChangeCompressionValue = true, title = title, canWriteExif = true, compressionTypes = compressionTypes ) { data object Lossless : Webp( title = "WEBP Lossless", compressionTypes = listOf(CompressionType.Effort(0..100)) ), LosslessMarker data object Lossy : Webp( title = "WEBP Lossy", compressionTypes = listOf(CompressionType.Quality(0..100)) ) } data object Bmp : ImageFormat( title = "BMP", extension = "bmp", mimeType = MimeType.Bmp, canChangeCompressionValue = false ) sealed class Avif( title: String, compressionTypes: List ) : ImageFormat( title = title, extension = "avif", mimeType = MimeType.Avif, canChangeCompressionValue = true, compressionTypes = compressionTypes ) { data object Lossless : Avif( title = "AVIF Lossless", compressionTypes = listOf( CompressionType.Effort(0..10) ) ), LosslessMarker data object Lossy : Avif( title = "AVIF Lossy", compressionTypes = listOf( CompressionType.Quality(1..100), CompressionType.Effort(0..10) ) ) } sealed class Heif( title: String, compressionTypes: List ) : ImageFormat( title = title, extension = "heif", mimeType = MimeType.Heif, compressionTypes = compressionTypes, canChangeCompressionValue = compressionTypes.isNotEmpty() ) { data object Lossless : Heif( title = "HEIF Lossless", compressionTypes = listOf() ), LosslessMarker data object Lossy : Heif( title = "HEIF Lossy", compressionTypes = listOf( CompressionType.Quality(0..100) ) ) } sealed class Heic( title: String, compressionTypes: List ) : ImageFormat( title = title, extension = "heic", mimeType = MimeType.Heic, compressionTypes = compressionTypes, canChangeCompressionValue = compressionTypes.isNotEmpty() ) { data object Lossless : Heic( title = "HEIC Lossless", compressionTypes = listOf() ), LosslessMarker data object Lossy : Heic( title = "HEIC Lossy", compressionTypes = listOf( CompressionType.Quality(0..100) ) ) } sealed class Jxl( title: String, compressionTypes: List ) : ImageFormat( extension = "jxl", mimeType = MimeType.Jxl, canChangeCompressionValue = true, title = title, compressionTypes = compressionTypes ) { data object Lossless : Jxl( title = "JXL Lossless", compressionTypes = listOf( CompressionType.Effort(1..10) ) ), LosslessMarker data object Lossy : Jxl( title = "JXL Lossy", compressionTypes = listOf( CompressionType.Quality(1..100), CompressionType.Effort(1..10) ) ) } sealed class Jpeg2000( title: String, extension: String ) : ImageFormat( title = title, extension = extension, mimeType = MimeType.Jp2, canChangeCompressionValue = true, compressionTypes = listOf( CompressionType.Quality(20..100) ) ) { data object Jp2 : Jpeg2000( title = "JP2", extension = "jp2" ) data object J2k : Jpeg2000( title = "J2K", extension = "j2k" ) } data object Tiff : ImageFormat( title = "TIFF", extension = "tiff", mimeType = MimeType.Tiff, canChangeCompressionValue = true, compressionTypes = emptyList() ) data object Tif : ImageFormat( title = "TIF", extension = "tif", mimeType = MimeType.Tiff, canChangeCompressionValue = true, compressionTypes = emptyList() ) data object Qoi : ImageFormat( title = "QOI", extension = "qoi", mimeType = MimeType.Qoi, canChangeCompressionValue = false ) data object Ico : ImageFormat( title = "ICO", extension = "ico", mimeType = MimeType.Ico, canChangeCompressionValue = false ) data object Gif : ImageFormat( title = "GIF", extension = "gif", mimeType = MimeType.Gif, canChangeCompressionValue = true ) interface LosslessMarker sealed class CompressionType( open val compressionRange: IntRange = 0..100 ) { data class Quality( override val compressionRange: IntRange = 0..100 ) : CompressionType(compressionRange) data class Effort( override val compressionRange: IntRange = 0..100 ) : CompressionType(compressionRange) } companion object { val Default: ImageFormat by lazy { Jpg } operator fun get(typeString: String?): ImageFormat = when { typeString == null -> Default typeString.contains("tiff") -> Tiff typeString.contains("tif") -> Tif typeString.contains("jp2") -> Jpeg2000.Jp2 typeString.contains("j2k") -> Jpeg2000.J2k typeString.contains("jxl") -> Jxl.Lossless typeString.contains("png") -> Png.Lossless typeString.contains("bmp") -> Bmp typeString.contains("jpeg") -> Jpeg typeString.contains("jpg") -> Jpg typeString.contains("webp") -> Webp.Lossless typeString.contains("avif") -> Avif.Lossless typeString.contains("heif") -> Heif.Lossless typeString.contains("heic") -> Heic.Lossless typeString.contains("qoi") -> Qoi typeString.contains("ico") -> Ico typeString.contains("svg") -> Png.Lossless typeString.contains("gif") -> Gif else -> Default } val highLevelFormats by lazy { listOf( Avif.Lossy, Avif.Lossless, Heic.Lossy, Heic.Lossless, Heif.Lossy, Heif.Lossless ) } val entries by lazy { listOf( Jpg, Jpeg, MozJpeg, Jpegli, Png.Lossless, Png.Lossy, Bmp, Webp.Lossless, Webp.Lossy, Avif.Lossless, Avif.Lossy, Heic.Lossless, Heic.Lossy, Heif.Lossless, Heif.Lossy, Jxl.Lossless, Jxl.Lossy, Tif, Tiff, Jpeg2000.Jp2, Jpeg2000.J2k, Qoi, Ico, Gif ) } } } val ImageFormat.isLossless get() = this is ImageFormat.LosslessMarker ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFormatGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model sealed class ImageFormatGroup( open val title: String, open val formats: List ) { data object Jpg : ImageFormatGroup( title = "JPG", formats = listOf( ImageFormat.Jpg, ImageFormat.Jpeg, ImageFormat.MozJpeg, ImageFormat.Jpegli ) ) data object Png : ImageFormatGroup( title = "PNG", formats = listOf( ImageFormat.Png.Lossless, ImageFormat.Png.Lossy, ImageFormat.Png.OxiPNG ) ) data object Bmp : ImageFormatGroup( title = "BMP", formats = listOf( ImageFormat.Bmp ) ) data object Webp : ImageFormatGroup( title = "WEBP", formats = listOf( ImageFormat.Webp.Lossless, ImageFormat.Webp.Lossy ) ) data object Avif : ImageFormatGroup( title = "AVIF", formats = listOf( ImageFormat.Avif.Lossless, ImageFormat.Avif.Lossy ) ) data object Heic : ImageFormatGroup( title = "HEIC", formats = listOf( ImageFormat.Heic.Lossless, ImageFormat.Heic.Lossy, ImageFormat.Heif.Lossless, ImageFormat.Heif.Lossy ) ) data object Jxl : ImageFormatGroup( title = "JXL", formats = listOf( ImageFormat.Jxl.Lossless, ImageFormat.Jxl.Lossy ) ) data object Jpeg2000 : ImageFormatGroup( title = "JPEG 2000", formats = listOf( ImageFormat.Jpeg2000.Jp2, ImageFormat.Jpeg2000.J2k ) ) data object Tiff : ImageFormatGroup( title = "TIFF", formats = listOf( ImageFormat.Tif, ImageFormat.Tiff ) ) data object Qoi : ImageFormatGroup( title = "QOI", formats = listOf( ImageFormat.Qoi ) ) data object Ico : ImageFormatGroup( title = "ICO", formats = listOf( ImageFormat.Ico ) ) data object Gif : ImageFormatGroup( title = "GIF", formats = listOf( ImageFormat.Gif ) ) data class Custom( override val title: String, override val formats: List ) : ImageFormatGroup(title, formats) companion object { val entries by lazy { listOf(Jpg, Png, Webp, Avif, Heic, Jxl, Bmp, Jpeg2000, Tiff, Qoi, Ico, Gif) } val alphaContainedEntries get() = listOf( Png, Webp, Avif, Heic, Jxl, Jpeg2000, Qoi, Ico, Gif ) val highLevelFormats by lazy { listOf( Avif, Heic ) } fun fromFormat( imageFormat: ImageFormat ): ImageFormatGroup = entries.first { imageFormat in it.formats } } } val ImageFormat.Companion.alphaContainedEntries: List by lazy { ImageFormatGroup.alphaContainedEntries.flatMap { it.formats } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageFrames.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model sealed interface ImageFrames { data object All : ImageFrames { override fun getFramePositions( frameCount: Int ): List = List(frameCount) { it + 1 } } data class ManualSelection( val framePositions: List ) : ImageFrames { override fun getFramePositions( frameCount: Int ): List = framePositions.filter { it - 1 < frameCount } } fun isEmpty(): Boolean = when (this) { All -> false is ManualSelection -> framePositions.isEmpty() } fun isNotEmpty(): Boolean = !isEmpty() fun getFramePositions( frameCount: Int ): List } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model data class ImageInfo( val width: Int = 0, val height: Int = 0, val quality: Quality = Quality.Base(), val imageFormat: ImageFormat = ImageFormat.Default, val resizeType: ResizeType = ResizeType.Explicit, val rotationDegrees: Float = 0f, val isFlipped: Boolean = false, val sizeInBytes: Int = 0, val imageScaleMode: ImageScaleMode = ImageScaleMode.Default, val originalUri: String? = null ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageScaleMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model import com.t8rin.imagetoolbox.core.resources.R sealed interface ScaleColorSpace { data object SRGB : ScaleColorSpace data object LAB : ScaleColorSpace data object LUV : ScaleColorSpace data object Linear : ScaleColorSpace data object Sigmoidal : ScaleColorSpace data object XYZ : ScaleColorSpace data object F32sRGB : ScaleColorSpace data object F32Rec709 : ScaleColorSpace data object F32Gamma22 : ScaleColorSpace data object F32Gamma28 : ScaleColorSpace data object LCH : ScaleColorSpace data object OklabSRGB : ScaleColorSpace data object OklabRec709 : ScaleColorSpace data object OklabGamma22 : ScaleColorSpace data object OklabGamma28 : ScaleColorSpace data object JzazbzSRGB : ScaleColorSpace data object JzazbzRec709 : ScaleColorSpace data object JzazbzGamma22 : ScaleColorSpace data object JzazbzGamma28 : ScaleColorSpace val ordinal: Int get() = entries.indexOf(this) companion object { val Default = Linear val entries by lazy { listOf( Linear, SRGB, F32sRGB, XYZ, LAB, LUV, Sigmoidal, F32Rec709, F32Gamma22, F32Gamma28, LCH, OklabSRGB, OklabRec709, OklabGamma22, OklabGamma28, JzazbzSRGB, JzazbzRec709, JzazbzGamma22, JzazbzGamma28 ) } fun fromOrdinal(ordinal: Int) = entries.getOrNull(ordinal) ?: Default } } sealed class ImageScaleMode(val value: Int) { abstract val scaleColorSpace: ScaleColorSpace abstract fun copy( scaleColorSpace: ScaleColorSpace = this.scaleColorSpace ): ImageScaleMode override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ImageScaleMode) return false if (value != other.value) return false if (scaleColorSpace != other.scaleColorSpace) return false return true } override fun hashCode(): Int { var result = value result = 31 * result + scaleColorSpace.hashCode() return result } object NotPresent : ImageScaleMode(-2) { override val scaleColorSpace: ScaleColorSpace get() = ScaleColorSpace.Default override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = NotPresent override fun toString(): String = "NotPresent" } object Base : ImageScaleMode(-3) { override val scaleColorSpace: ScaleColorSpace get() = ScaleColorSpace.Default override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Base override fun toString(): String = "Base" } class Bilinear( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(0) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Bilinear(scaleColorSpace) } class Nearest( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(1) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Nearest(scaleColorSpace) } class Cubic( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(2) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Cubic(scaleColorSpace) } class Mitchell( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(3) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Mitchell(scaleColorSpace) } class Catmull( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(4) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Catmull(scaleColorSpace) } class Hermite( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(5) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Hermite(scaleColorSpace) } class BSpline( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(6) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = BSpline(scaleColorSpace) } class Hann( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(7) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Hann(scaleColorSpace) } class Bicubic( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(8) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Bicubic(scaleColorSpace) } class Hamming( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(9) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Hamming(scaleColorSpace) } class Hanning( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(10) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Hanning(scaleColorSpace) } class Blackman( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(11) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Blackman(scaleColorSpace) } class Welch( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(12) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Welch(scaleColorSpace) } class Quadric( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(13) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Quadric(scaleColorSpace) } class Gaussian( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(14) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Gaussian(scaleColorSpace) } class Sphinx( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(15) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Sphinx(scaleColorSpace) } class Bartlett( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(16) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Bartlett(scaleColorSpace) } class Robidoux( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(17) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Robidoux(scaleColorSpace) } class RobidouxSharp( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(18) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = RobidouxSharp(scaleColorSpace) } class Spline16( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(19) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Spline16(scaleColorSpace) } class Spline36( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(20) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Spline36(scaleColorSpace) } class Spline64( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(21) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Spline64(scaleColorSpace) } class Kaiser( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(22) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Kaiser(scaleColorSpace) } class BartlettHann( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(23) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = BartlettHann(scaleColorSpace) } class Box( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(24) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Box(scaleColorSpace) } class Bohman( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(25) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Bohman(scaleColorSpace) } class Lanczos2( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(26) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos2(scaleColorSpace) } class Lanczos3( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(27) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos3(scaleColorSpace) } class Lanczos4( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(28) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos4(scaleColorSpace) } class Lanczos2Jinc( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(29) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos2Jinc(scaleColorSpace) } class Lanczos3Jinc( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(30) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos3Jinc(scaleColorSpace) } class Lanczos4Jinc( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(31) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos4Jinc(scaleColorSpace) } class EwaHanning( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(32) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaHanning(scaleColorSpace) } class EwaRobidoux( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(33) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaRobidoux(scaleColorSpace) } class EwaBlackman( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(34) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaBlackman(scaleColorSpace) } class EwaQuadric( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(35) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaQuadric(scaleColorSpace) } class EwaRobidouxSharp( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(36) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaRobidouxSharp(scaleColorSpace) } class EwaLanczos3Jinc( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(37) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaLanczos3Jinc(scaleColorSpace) } class Ginseng( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(38) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Ginseng(scaleColorSpace) } class EwaGinseng( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(39) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaGinseng(scaleColorSpace) } class EwaLanczosSharp( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(40) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaLanczosSharp(scaleColorSpace) } class EwaLanczos4Sharpest( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(41) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaLanczos4Sharpest(scaleColorSpace) } class EwaLanczosSoft( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(42) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = EwaLanczosSoft(scaleColorSpace) } class HaasnSoft( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(43) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = HaasnSoft(scaleColorSpace) } class Lagrange2( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(44) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lagrange2(scaleColorSpace) } class Lagrange3( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(45) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lagrange3(scaleColorSpace) } class Lanczos6( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(46) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos6(scaleColorSpace) } class Lanczos6Jinc( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(47) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Lanczos6Jinc(scaleColorSpace) } class Area( override val scaleColorSpace: ScaleColorSpace = ScaleColorSpace.Default ) : ImageScaleMode(48) { override fun copy( scaleColorSpace: ScaleColorSpace ): ImageScaleMode = Area(scaleColorSpace) } companion object { val Default = Bilinear() val entries by lazy { simpleEntries + complexEntries } val simpleEntries by lazy { listOf( EwaQuadric(), Quadric(), Bilinear(), Nearest(), Cubic(), Mitchell(), Catmull(), Hermite(), BSpline(), Bicubic(), Box(), Lanczos2(), Lanczos3(), Lanczos4(), Lanczos2Jinc(), Lanczos3Jinc(), Lanczos4Jinc(), Hamming(), Hanning(), EwaHanning(), EwaRobidoux(), Robidoux(), RobidouxSharp(), EwaLanczosSharp(), EwaLanczos4Sharpest(), EwaLanczosSoft(), EwaRobidouxSharp(), EwaLanczos3Jinc(), Lagrange2(), Lagrange3(), Lanczos6(), Lanczos6Jinc(), Area() ) } val complexEntries by lazy { listOf( Blackman(), Welch(), Gaussian(), Sphinx(), Bartlett(), Spline16(), Spline36(), Spline64(), Kaiser(), BartlettHann(), Bohman(), EwaBlackman(), Ginseng(), EwaGinseng(), HaasnSoft(), Hann(), ) } fun fromInt(value: Int): ImageScaleMode = when { value == -3 -> Base value >= 0 -> entries.associateBy { it.value }[value] ?: NotPresent else -> NotPresent } } } val ImageScaleMode.title: Int get() = when (this) { ImageScaleMode.Base, ImageScaleMode.NotPresent -> R.string.basic is ImageScaleMode.Bilinear -> R.string.bilinear is ImageScaleMode.Nearest -> R.string.nearest is ImageScaleMode.Cubic -> R.string.cubic is ImageScaleMode.Mitchell -> R.string.mitchell is ImageScaleMode.Catmull -> R.string.catmull is ImageScaleMode.Hermite -> R.string.hermite is ImageScaleMode.BSpline -> R.string.bspline is ImageScaleMode.Hann -> R.string.hann is ImageScaleMode.Bicubic -> R.string.bicubic is ImageScaleMode.Hamming -> R.string.hamming is ImageScaleMode.Hanning -> R.string.hanning is ImageScaleMode.Blackman -> R.string.blackman is ImageScaleMode.Welch -> R.string.welch is ImageScaleMode.Quadric -> R.string.quadric is ImageScaleMode.Gaussian -> R.string.gaussian is ImageScaleMode.Sphinx -> R.string.sphinx is ImageScaleMode.Bartlett -> R.string.bartlett is ImageScaleMode.Robidoux -> R.string.robidoux is ImageScaleMode.RobidouxSharp -> R.string.robidoux_sharp is ImageScaleMode.Spline16 -> R.string.spline16 is ImageScaleMode.Spline36 -> R.string.spline36 is ImageScaleMode.Spline64 -> R.string.spline64 is ImageScaleMode.Kaiser -> R.string.kaiser is ImageScaleMode.BartlettHann -> R.string.bartlett_hann is ImageScaleMode.Box -> R.string.box is ImageScaleMode.Bohman -> R.string.bohman is ImageScaleMode.Lanczos2 -> R.string.lanczos2 is ImageScaleMode.Lanczos3 -> R.string.lanczos3 is ImageScaleMode.Lanczos4 -> R.string.lanczos4 is ImageScaleMode.Lanczos2Jinc -> R.string.lanczos2_jinc is ImageScaleMode.Lanczos3Jinc -> R.string.lanczos3_jinc is ImageScaleMode.Lanczos4Jinc -> R.string.lanczos4_jinc is ImageScaleMode.EwaHanning -> R.string.ewa_hanning is ImageScaleMode.EwaRobidoux -> R.string.ewa_robidoux is ImageScaleMode.EwaBlackman -> R.string.ewa_blackman is ImageScaleMode.EwaQuadric -> R.string.ewa_quadric is ImageScaleMode.EwaRobidouxSharp -> R.string.ewa_robidoux_sharp is ImageScaleMode.EwaLanczos3Jinc -> R.string.ewa_lanczos3_jinc is ImageScaleMode.Ginseng -> R.string.ginseng is ImageScaleMode.EwaGinseng -> R.string.ewa_ginseng is ImageScaleMode.EwaLanczosSharp -> R.string.ewa_lanczos_sharp is ImageScaleMode.EwaLanczos4Sharpest -> R.string.ewa_lanczos_4_sharpest is ImageScaleMode.EwaLanczosSoft -> R.string.ewa_lanczos_soft is ImageScaleMode.HaasnSoft -> R.string.haasn_soft is ImageScaleMode.Lagrange2 -> R.string.lagrange_2 is ImageScaleMode.Lagrange3 -> R.string.lagrange_3 is ImageScaleMode.Lanczos6 -> R.string.lanczos_6 is ImageScaleMode.Lanczos6Jinc -> R.string.lanczos_6_jinc is ImageScaleMode.Area -> R.string.area } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ImageWithSize.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model import com.t8rin.imagetoolbox.core.domain.model.IntegerSize data class ImageWithSize( val image: I, val size: IntegerSize ) infix fun I.withSize( size: IntegerSize ): ImageWithSize = ImageWithSize( image = this, size = size ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/MetadataTag.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("SpellCheckingInspection") package com.t8rin.imagetoolbox.core.domain.image.model sealed class MetadataTag( val key: String ) : Comparable { object BitsPerSample : MetadataTag(TAG_BITS_PER_SAMPLE) object Compression : MetadataTag(TAG_COMPRESSION) object PhotometricInterpretation : MetadataTag(TAG_PHOTOMETRIC_INTERPRETATION) object SamplesPerPixel : MetadataTag(TAG_SAMPLES_PER_PIXEL) object PlanarConfiguration : MetadataTag(TAG_PLANAR_CONFIGURATION) object YCbCrSubSampling : MetadataTag(TAG_Y_CB_CR_SUB_SAMPLING) object YCbCrPositioning : MetadataTag(TAG_Y_CB_CR_POSITIONING) object XResolution : MetadataTag(TAG_X_RESOLUTION) object YResolution : MetadataTag(TAG_Y_RESOLUTION) object ResolutionUnit : MetadataTag(TAG_RESOLUTION_UNIT) object StripOffsets : MetadataTag(TAG_STRIP_OFFSETS) object RowsPerStrip : MetadataTag(TAG_ROWS_PER_STRIP) object StripByteCounts : MetadataTag(TAG_STRIP_BYTE_COUNTS) object JpegInterchangeFormat : MetadataTag(TAG_JPEG_INTERCHANGE_FORMAT) object JpegInterchangeFormatLength : MetadataTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) object TransferFunction : MetadataTag(TAG_TRANSFER_FUNCTION) object WhitePoint : MetadataTag(TAG_WHITE_POINT) object PrimaryChromaticities : MetadataTag(TAG_PRIMARY_CHROMATICITIES) object YCbCrCoefficients : MetadataTag(TAG_Y_CB_CR_COEFFICIENTS) object ReferenceBlackWhite : MetadataTag(TAG_REFERENCE_BLACK_WHITE) object Datetime : MetadataTag(TAG_DATETIME) object ImageDescription : MetadataTag(TAG_IMAGE_DESCRIPTION) object Make : MetadataTag(TAG_MAKE) object Model : MetadataTag(TAG_MODEL) object Software : MetadataTag(TAG_SOFTWARE) object Artist : MetadataTag(TAG_ARTIST) object Copyright : MetadataTag(TAG_COPYRIGHT) object ExifVersion : MetadataTag(TAG_EXIF_VERSION) object FlashpixVersion : MetadataTag(TAG_FLASHPIX_VERSION) object ColorSpace : MetadataTag(TAG_COLOR_SPACE) object Gamma : MetadataTag(TAG_GAMMA) object PixelXDimension : MetadataTag(TAG_PIXEL_X_DIMENSION) object PixelYDimension : MetadataTag(TAG_PIXEL_Y_DIMENSION) object CompressedBitsPerPixel : MetadataTag(TAG_COMPRESSED_BITS_PER_PIXEL) object MakerNote : MetadataTag(TAG_MAKER_NOTE) object UserComment : MetadataTag(TAG_USER_COMMENT) object RelatedSoundFile : MetadataTag(TAG_RELATED_SOUND_FILE) object DatetimeOriginal : MetadataTag(TAG_DATETIME_ORIGINAL) object DatetimeDigitized : MetadataTag(TAG_DATETIME_DIGITIZED) object OffsetTime : MetadataTag(TAG_OFFSET_TIME) object OffsetTimeOriginal : MetadataTag(TAG_OFFSET_TIME_ORIGINAL) object OffsetTimeDigitized : MetadataTag(TAG_OFFSET_TIME_DIGITIZED) object SubsecTime : MetadataTag(TAG_SUBSEC_TIME) object SubsecTimeOriginal : MetadataTag(TAG_SUBSEC_TIME_ORIGINAL) object SubsecTimeDigitized : MetadataTag(TAG_SUBSEC_TIME_DIGITIZED) object ExposureTime : MetadataTag(TAG_EXPOSURE_TIME) object FNumber : MetadataTag(TAG_F_NUMBER) object ExposureProgram : MetadataTag(TAG_EXPOSURE_PROGRAM) object SpectralSensitivity : MetadataTag(TAG_SPECTRAL_SENSITIVITY) object PhotographicSensitivity : MetadataTag(TAG_PHOTOGRAPHIC_SENSITIVITY) object Oecf : MetadataTag(TAG_OECF) object SensitivityType : MetadataTag(TAG_SENSITIVITY_TYPE) object StandardOutputSensitivity : MetadataTag(TAG_STANDARD_OUTPUT_SENSITIVITY) object RecommendedExposureIndex : MetadataTag(TAG_RECOMMENDED_EXPOSURE_INDEX) object IsoSpeed : MetadataTag(TAG_ISO_SPEED) object IsoSpeedLatitudeYyy : MetadataTag(TAG_ISO_SPEED_LATITUDE_YYY) object IsoSpeedLatitudeZzz : MetadataTag(TAG_ISO_SPEED_LATITUDE_ZZZ) object ShutterSpeedValue : MetadataTag(TAG_SHUTTER_SPEED_VALUE) object ApertureValue : MetadataTag(TAG_APERTURE_VALUE) object BrightnessValue : MetadataTag(TAG_BRIGHTNESS_VALUE) object ExposureBiasValue : MetadataTag(TAG_EXPOSURE_BIAS_VALUE) object MaxApertureValue : MetadataTag(TAG_MAX_APERTURE_VALUE) object SubjectDistance : MetadataTag(TAG_SUBJECT_DISTANCE) object MeteringMode : MetadataTag(TAG_METERING_MODE) object Flash : MetadataTag(TAG_FLASH) object SubjectArea : MetadataTag(TAG_SUBJECT_AREA) object FocalLength : MetadataTag(TAG_FOCAL_LENGTH) object FlashEnergy : MetadataTag(TAG_FLASH_ENERGY) object SpatialFrequencyResponse : MetadataTag(TAG_SPATIAL_FREQUENCY_RESPONSE) object FocalPlaneXResolution : MetadataTag(TAG_FOCAL_PLANE_X_RESOLUTION) object FocalPlaneYResolution : MetadataTag(TAG_FOCAL_PLANE_Y_RESOLUTION) object FocalPlaneResolutionUnit : MetadataTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT) object SubjectLocation : MetadataTag(TAG_SUBJECT_LOCATION) object ExposureIndex : MetadataTag(TAG_EXPOSURE_INDEX) object SensingMethod : MetadataTag(TAG_SENSING_METHOD) object FileSource : MetadataTag(TAG_FILE_SOURCE) object CfaPattern : MetadataTag(TAG_CFA_PATTERN) object CustomRendered : MetadataTag(TAG_CUSTOM_RENDERED) object ExposureMode : MetadataTag(TAG_EXPOSURE_MODE) object WhiteBalance : MetadataTag(TAG_WHITE_BALANCE) object DigitalZoomRatio : MetadataTag(TAG_DIGITAL_ZOOM_RATIO) object FocalLengthIn35mmFilm : MetadataTag(TAG_FOCAL_LENGTH_IN_35MM_FILM) object SceneCaptureType : MetadataTag(TAG_SCENE_CAPTURE_TYPE) object GainControl : MetadataTag(TAG_GAIN_CONTROL) object Contrast : MetadataTag(TAG_CONTRAST) object Saturation : MetadataTag(TAG_SATURATION) object Sharpness : MetadataTag(TAG_SHARPNESS) object DeviceSettingDescription : MetadataTag(TAG_DEVICE_SETTING_DESCRIPTION) object SubjectDistanceRange : MetadataTag(TAG_SUBJECT_DISTANCE_RANGE) object ImageUniqueId : MetadataTag(TAG_IMAGE_UNIQUE_ID) object CameraOwnerName : MetadataTag(TAG_CAMERA_OWNER_NAME) object BodySerialNumber : MetadataTag(TAG_BODY_SERIAL_NUMBER) object LensSpecification : MetadataTag(TAG_LENS_SPECIFICATION) object LensMake : MetadataTag(TAG_LENS_MAKE) object LensModel : MetadataTag(TAG_LENS_MODEL) object LensSerialNumber : MetadataTag(TAG_LENS_SERIAL_NUMBER) object GpsVersionId : MetadataTag(TAG_GPS_VERSION_ID) object GpsLatitudeRef : MetadataTag(TAG_GPS_LATITUDE_REF) object GpsLatitude : MetadataTag(TAG_GPS_LATITUDE) object GpsLongitudeRef : MetadataTag(TAG_GPS_LONGITUDE_REF) object GpsLongitude : MetadataTag(TAG_GPS_LONGITUDE) object GpsAltitudeRef : MetadataTag(TAG_GPS_ALTITUDE_REF) object GpsAltitude : MetadataTag(TAG_GPS_ALTITUDE) object GpsTimestamp : MetadataTag(TAG_GPS_TIMESTAMP) object GpsSatellites : MetadataTag(TAG_GPS_SATELLITES) object GpsStatus : MetadataTag(TAG_GPS_STATUS) object GpsMeasureMode : MetadataTag(TAG_GPS_MEASURE_MODE) object GpsDop : MetadataTag(TAG_GPS_DOP) object GpsSpeedRef : MetadataTag(TAG_GPS_SPEED_REF) object GpsSpeed : MetadataTag(TAG_GPS_SPEED) object GpsTrackRef : MetadataTag(TAG_GPS_TRACK_REF) object GpsTrack : MetadataTag(TAG_GPS_TRACK) object GpsImgDirectionRef : MetadataTag(TAG_GPS_IMG_DIRECTION_REF) object GpsImgDirection : MetadataTag(TAG_GPS_IMG_DIRECTION) object GpsMapDatum : MetadataTag(TAG_GPS_MAP_DATUM) object GpsDestLatitudeRef : MetadataTag(TAG_GPS_DEST_LATITUDE_REF) object GpsDestLatitude : MetadataTag(TAG_GPS_DEST_LATITUDE) object GpsDestLongitudeRef : MetadataTag(TAG_GPS_DEST_LONGITUDE_REF) object GpsDestLongitude : MetadataTag(TAG_GPS_DEST_LONGITUDE) object GpsDestBearingRef : MetadataTag(TAG_GPS_DEST_BEARING_REF) object GpsDestBearing : MetadataTag(TAG_GPS_DEST_BEARING) object GpsDestDistanceRef : MetadataTag(TAG_GPS_DEST_DISTANCE_REF) object GpsDestDistance : MetadataTag(TAG_GPS_DEST_DISTANCE) object GpsProcessingMethod : MetadataTag(TAG_GPS_PROCESSING_METHOD) object GpsAreaInformation : MetadataTag(TAG_GPS_AREA_INFORMATION) object GpsDatestamp : MetadataTag(TAG_GPS_DATESTAMP) object GpsDifferential : MetadataTag(TAG_GPS_DIFFERENTIAL) object GpsHPositioningError : MetadataTag(TAG_GPS_H_POSITIONING_ERROR) object InteroperabilityIndex : MetadataTag(TAG_INTEROPERABILITY_INDEX) object DngVersion : MetadataTag(TAG_DNG_VERSION) object DefaultCropSize : MetadataTag(TAG_DEFAULT_CROP_SIZE) object OrfPreviewImageStart : MetadataTag(TAG_ORF_PREVIEW_IMAGE_START) object OrfPreviewImageLength : MetadataTag(TAG_ORF_PREVIEW_IMAGE_LENGTH) object OrfAspectFrame : MetadataTag(TAG_ORF_ASPECT_FRAME) object Rw2SensorBottomBorder : MetadataTag(TAG_RW2_SENSOR_BOTTOM_BORDER) object Rw2SensorLeftBorder : MetadataTag(TAG_RW2_SENSOR_LEFT_BORDER) object Rw2SensorRightBorder : MetadataTag(TAG_RW2_SENSOR_RIGHT_BORDER) object Rw2SensorTopBorder : MetadataTag(TAG_RW2_SENSOR_TOP_BORDER) object Rw2Iso : MetadataTag(TAG_RW2_ISO) override fun compareTo(other: MetadataTag): Int = key.compareTo(other.key) override fun toString(): String = key override fun equals(other: Any?): Boolean { if (other !is MetadataTag) return false return other.key == key } override fun hashCode(): Int = key.hashCode() companion object { const val TAG_BITS_PER_SAMPLE: String = "BitsPerSample" const val TAG_COMPRESSION: String = "Compression" const val TAG_PHOTOMETRIC_INTERPRETATION: String = "PhotometricInterpretation" const val TAG_SAMPLES_PER_PIXEL: String = "SamplesPerPixel" const val TAG_PLANAR_CONFIGURATION: String = "PlanarConfiguration" const val TAG_Y_CB_CR_SUB_SAMPLING: String = "YCbCrSubSampling" const val TAG_Y_CB_CR_POSITIONING: String = "YCbCrPositioning" const val TAG_X_RESOLUTION: String = "XResolution" const val TAG_Y_RESOLUTION: String = "YResolution" const val TAG_RESOLUTION_UNIT: String = "ResolutionUnit" const val TAG_STRIP_OFFSETS: String = "StripOffsets" const val TAG_ROWS_PER_STRIP: String = "RowsPerStrip" const val TAG_STRIP_BYTE_COUNTS: String = "StripByteCounts" const val TAG_JPEG_INTERCHANGE_FORMAT: String = "JPEGInterchangeFormat" const val TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: String = "JPEGInterchangeFormatLength" const val TAG_TRANSFER_FUNCTION: String = "TransferFunction" const val TAG_WHITE_POINT: String = "WhitePoint" const val TAG_PRIMARY_CHROMATICITIES: String = "PrimaryChromaticities" const val TAG_Y_CB_CR_COEFFICIENTS: String = "YCbCrCoefficients" const val TAG_REFERENCE_BLACK_WHITE: String = "ReferenceBlackWhite" const val TAG_DATETIME: String = "DateTime" const val TAG_IMAGE_DESCRIPTION: String = "ImageDescription" const val TAG_MAKE: String = "Make" const val TAG_MODEL: String = "Model" const val TAG_SOFTWARE: String = "Software" const val TAG_ARTIST: String = "Artist" const val TAG_COPYRIGHT: String = "Copyright" const val TAG_EXIF_VERSION: String = "ExifVersion" const val TAG_FLASHPIX_VERSION: String = "FlashpixVersion" const val TAG_COLOR_SPACE: String = "ColorSpace" const val TAG_GAMMA: String = "Gamma" const val TAG_PIXEL_X_DIMENSION: String = "PixelXDimension" const val TAG_PIXEL_Y_DIMENSION: String = "PixelYDimension" const val TAG_COMPRESSED_BITS_PER_PIXEL: String = "CompressedBitsPerPixel" const val TAG_MAKER_NOTE: String = "MakerNote" const val TAG_USER_COMMENT: String = "UserComment" const val TAG_RELATED_SOUND_FILE: String = "RelatedSoundFile" const val TAG_DATETIME_ORIGINAL: String = "DateTimeOriginal" const val TAG_DATETIME_DIGITIZED: String = "DateTimeDigitized" const val TAG_OFFSET_TIME: String = "OffsetTime" const val TAG_OFFSET_TIME_ORIGINAL: String = "OffsetTimeOriginal" const val TAG_OFFSET_TIME_DIGITIZED: String = "OffsetTimeDigitized" const val TAG_SUBSEC_TIME: String = "SubSecTime" const val TAG_SUBSEC_TIME_ORIGINAL: String = "SubSecTimeOriginal" const val TAG_SUBSEC_TIME_DIGITIZED: String = "SubSecTimeDigitized" const val TAG_EXPOSURE_TIME: String = "ExposureTime" const val TAG_F_NUMBER: String = "FNumber" const val TAG_EXPOSURE_PROGRAM: String = "ExposureProgram" const val TAG_SPECTRAL_SENSITIVITY: String = "SpectralSensitivity" const val TAG_PHOTOGRAPHIC_SENSITIVITY: String = "PhotographicSensitivity" const val TAG_OECF: String = "OECF" const val TAG_SENSITIVITY_TYPE: String = "SensitivityType" const val TAG_STANDARD_OUTPUT_SENSITIVITY: String = "StandardOutputSensitivity" const val TAG_RECOMMENDED_EXPOSURE_INDEX: String = "RecommendedExposureIndex" const val TAG_ISO_SPEED: String = "ISOSpeed" const val TAG_ISO_SPEED_LATITUDE_YYY: String = "ISOSpeedLatitudeyyy" const val TAG_ISO_SPEED_LATITUDE_ZZZ: String = "ISOSpeedLatitudezzz" const val TAG_SHUTTER_SPEED_VALUE: String = "ShutterSpeedValue" const val TAG_APERTURE_VALUE: String = "ApertureValue" const val TAG_BRIGHTNESS_VALUE: String = "BrightnessValue" const val TAG_EXPOSURE_BIAS_VALUE: String = "ExposureBiasValue" const val TAG_MAX_APERTURE_VALUE: String = "MaxApertureValue" const val TAG_SUBJECT_DISTANCE: String = "SubjectDistance" const val TAG_METERING_MODE: String = "MeteringMode" const val TAG_FLASH: String = "Flash" const val TAG_SUBJECT_AREA: String = "SubjectArea" const val TAG_FOCAL_LENGTH: String = "FocalLength" const val TAG_FLASH_ENERGY: String = "FlashEnergy" const val TAG_SPATIAL_FREQUENCY_RESPONSE: String = "SpatialFrequencyResponse" const val TAG_FOCAL_PLANE_X_RESOLUTION: String = "FocalPlaneXResolution" const val TAG_FOCAL_PLANE_Y_RESOLUTION: String = "FocalPlaneYResolution" const val TAG_FOCAL_PLANE_RESOLUTION_UNIT: String = "FocalPlaneResolutionUnit" const val TAG_SUBJECT_LOCATION: String = "SubjectLocation" const val TAG_EXPOSURE_INDEX: String = "ExposureIndex" const val TAG_SENSING_METHOD: String = "SensingMethod" const val TAG_FILE_SOURCE: String = "FileSource" const val TAG_CFA_PATTERN: String = "CFAPattern" const val TAG_CUSTOM_RENDERED: String = "CustomRendered" const val TAG_EXPOSURE_MODE: String = "ExposureMode" const val TAG_WHITE_BALANCE: String = "WhiteBalance" const val TAG_DIGITAL_ZOOM_RATIO: String = "DigitalZoomRatio" const val TAG_FOCAL_LENGTH_IN_35MM_FILM: String = "FocalLengthIn35mmFilm" const val TAG_SCENE_CAPTURE_TYPE: String = "SceneCaptureType" const val TAG_GAIN_CONTROL: String = "GainControl" const val TAG_CONTRAST: String = "Contrast" const val TAG_SATURATION: String = "Saturation" const val TAG_SHARPNESS: String = "Sharpness" const val TAG_DEVICE_SETTING_DESCRIPTION: String = "DeviceSettingDescription" const val TAG_SUBJECT_DISTANCE_RANGE: String = "SubjectDistanceRange" const val TAG_IMAGE_UNIQUE_ID: String = "ImageUniqueID" const val TAG_CAMERA_OWNER_NAME: String = "CameraOwnerName" const val TAG_BODY_SERIAL_NUMBER: String = "BodySerialNumber" const val TAG_LENS_SPECIFICATION: String = "LensSpecification" const val TAG_LENS_MAKE: String = "LensMake" const val TAG_LENS_MODEL: String = "LensModel" const val TAG_LENS_SERIAL_NUMBER: String = "LensSerialNumber" const val TAG_GPS_VERSION_ID: String = "GPSVersionID" const val TAG_GPS_LATITUDE_REF: String = "GPSLatitudeRef" const val TAG_GPS_LATITUDE: String = "GPSLatitude" const val TAG_GPS_LONGITUDE_REF: String = "GPSLongitudeRef" const val TAG_GPS_LONGITUDE: String = "GPSLongitude" const val TAG_GPS_ALTITUDE_REF: String = "GPSAltitudeRef" const val TAG_GPS_ALTITUDE: String = "GPSAltitude" const val TAG_GPS_TIMESTAMP: String = "GPSTimeStamp" const val TAG_GPS_SATELLITES: String = "GPSSatellites" const val TAG_GPS_STATUS: String = "GPSStatus" const val TAG_GPS_MEASURE_MODE: String = "GPSMeasureMode" const val TAG_GPS_DOP: String = "GPSDOP" const val TAG_GPS_SPEED_REF: String = "GPSSpeedRef" const val TAG_GPS_SPEED: String = "GPSSpeed" const val TAG_GPS_TRACK_REF: String = "GPSTrackRef" const val TAG_GPS_TRACK: String = "GPSTrack" const val TAG_GPS_IMG_DIRECTION_REF: String = "GPSImgDirectionRef" const val TAG_GPS_IMG_DIRECTION: String = "GPSImgDirection" const val TAG_GPS_MAP_DATUM: String = "GPSMapDatum" const val TAG_GPS_DEST_LATITUDE_REF: String = "GPSDestLatitudeRef" const val TAG_GPS_DEST_LATITUDE: String = "GPSDestLatitude" const val TAG_GPS_DEST_LONGITUDE_REF: String = "GPSDestLongitudeRef" const val TAG_GPS_DEST_LONGITUDE: String = "GPSDestLongitude" const val TAG_GPS_DEST_BEARING_REF: String = "GPSDestBearingRef" const val TAG_GPS_DEST_BEARING: String = "GPSDestBearing" const val TAG_GPS_DEST_DISTANCE_REF: String = "GPSDestDistanceRef" const val TAG_GPS_DEST_DISTANCE: String = "GPSDestDistance" const val TAG_GPS_PROCESSING_METHOD: String = "GPSProcessingMethod" const val TAG_GPS_AREA_INFORMATION: String = "GPSAreaInformation" const val TAG_GPS_DATESTAMP: String = "GPSDateStamp" const val TAG_GPS_DIFFERENTIAL: String = "GPSDifferential" const val TAG_GPS_H_POSITIONING_ERROR: String = "GPSHPositioningError" const val TAG_INTEROPERABILITY_INDEX: String = "InteroperabilityIndex" const val TAG_DNG_VERSION: String = "DNGVersion" const val TAG_DEFAULT_CROP_SIZE: String = "DefaultCropSize" const val TAG_ORF_PREVIEW_IMAGE_START: String = "PreviewImageStart" const val TAG_ORF_PREVIEW_IMAGE_LENGTH: String = "PreviewImageLength" const val TAG_ORF_ASPECT_FRAME: String = "AspectFrame" const val TAG_RW2_SENSOR_BOTTOM_BORDER: String = "SensorBottomBorder" const val TAG_RW2_SENSOR_LEFT_BORDER: String = "SensorLeftBorder" const val TAG_RW2_SENSOR_RIGHT_BORDER: String = "SensorRightBorder" const val TAG_RW2_SENSOR_TOP_BORDER: String = "SensorTopBorder" const val TAG_RW2_ISO: String = "ISO" val entries by lazy { listOf( BitsPerSample, Compression, PhotometricInterpretation, SamplesPerPixel, PlanarConfiguration, YCbCrSubSampling, YCbCrPositioning, XResolution, YResolution, ResolutionUnit, StripOffsets, RowsPerStrip, StripByteCounts, JpegInterchangeFormat, JpegInterchangeFormatLength, TransferFunction, WhitePoint, PrimaryChromaticities, YCbCrCoefficients, ReferenceBlackWhite, Datetime, ImageDescription, Make, Model, Software, Artist, Copyright, ExifVersion, FlashpixVersion, ColorSpace, Gamma, PixelXDimension, PixelYDimension, CompressedBitsPerPixel, MakerNote, UserComment, RelatedSoundFile, DatetimeOriginal, DatetimeDigitized, OffsetTime, OffsetTimeOriginal, OffsetTimeDigitized, SubsecTime, SubsecTimeOriginal, SubsecTimeDigitized, ExposureTime, FNumber, ExposureProgram, SpectralSensitivity, PhotographicSensitivity, Oecf, SensitivityType, StandardOutputSensitivity, RecommendedExposureIndex, IsoSpeed, IsoSpeedLatitudeYyy, IsoSpeedLatitudeZzz, ShutterSpeedValue, ApertureValue, BrightnessValue, ExposureBiasValue, MaxApertureValue, SubjectDistance, MeteringMode, Flash, SubjectArea, FocalLength, FlashEnergy, SpatialFrequencyResponse, FocalPlaneXResolution, FocalPlaneYResolution, FocalPlaneResolutionUnit, SubjectLocation, ExposureIndex, SensingMethod, FileSource, CfaPattern, CustomRendered, ExposureMode, WhiteBalance, DigitalZoomRatio, FocalLengthIn35mmFilm, SceneCaptureType, GainControl, Contrast, Saturation, Sharpness, DeviceSettingDescription, SubjectDistanceRange, ImageUniqueId, CameraOwnerName, BodySerialNumber, LensSpecification, LensMake, LensModel, LensSerialNumber, GpsVersionId, GpsLatitudeRef, GpsLatitude, GpsLongitudeRef, GpsLongitude, GpsAltitudeRef, GpsAltitude, GpsTimestamp, GpsSatellites, GpsStatus, GpsMeasureMode, GpsDop, GpsSpeedRef, GpsSpeed, GpsTrackRef, GpsTrack, GpsImgDirectionRef, GpsImgDirection, GpsMapDatum, GpsDestLatitudeRef, GpsDestLatitude, GpsDestLongitudeRef, GpsDestLongitude, GpsDestBearingRef, GpsDestBearing, GpsDestDistanceRef, GpsDestDistance, GpsProcessingMethod, GpsAreaInformation, GpsDatestamp, GpsDifferential, GpsHPositioningError, InteroperabilityIndex, DngVersion, DefaultCropSize, OrfPreviewImageStart, OrfPreviewImageLength, OrfAspectFrame, Rw2SensorBottomBorder, Rw2SensorLeftBorder, Rw2SensorRightBorder, Rw2SensorTopBorder, Rw2Iso, ) } val dateEntries by lazy { listOf( Datetime, DatetimeOriginal, DatetimeDigitized, OffsetTime, OffsetTimeOriginal, OffsetTimeDigitized, SubsecTime, SubsecTimeOriginal, SubsecTimeDigitized, GpsTimestamp, GpsDatestamp ) } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Preset.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model sealed class Preset { data object Telegram : Preset() data object None : Preset() data class Percentage(val value: Int) : Preset() data class AspectRatio( val ratio: Float, val isFit: Boolean ) : Preset() fun isTelegram(): Boolean = this is Telegram fun value(): Int? = (this as? Percentage)?.value fun isEmpty(): Boolean = this is None fun isAspectRatio(): Boolean = this is AspectRatio fun asString() = when (this) { is AspectRatio -> "ratio($ratio)" None -> "" is Percentage -> "$value%" Telegram -> "telegram" } companion object { val Original by lazy { Percentage(100) } fun createListFromInts(presets: String?): List? { return presets?.split("*")?.map { it.toInt() }?.map { Percentage(it) } } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/Quality.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model import androidx.annotation.IntRange sealed interface Quality { val qualityValue: Int fun coerceIn( imageFormat: ImageFormat ): Quality { return when (imageFormat) { is ImageFormat.Jxl -> { val value = this as? Jxl ?: return Jxl() value.copy( qualityValue = qualityValue.coerceIn(1..100), effort = effort.coerceIn(1..10), speed = speed.coerceIn(0..4) ) } is ImageFormat.Png.Lossy -> { val value = this as? PngLossy ?: return PngLossy() value.copy( maxColors = value.maxColors.coerceIn(2..1024), compressionLevel = compressionLevel.coerceIn(0..9) ) } is ImageFormat.Avif -> { val value = this as? Avif ?: return Avif() value.copy( qualityValue = qualityValue.coerceIn(1..100), effort = effort.coerceIn(0..9) ) } is ImageFormat.Tif, is ImageFormat.Tiff -> { val value = this as? Tiff ?: return Tiff() value.copy( compressionScheme = value.compressionScheme.coerceIn(0..9) ) } is ImageFormat.Jpeg2000 -> Base(qualityValue.coerceIn(20..100)) else -> { Base(qualityValue.coerceIn(0..100)) } } } fun isNonAlpha(): Boolean = if (this is Jxl) { channels == Channels.RGB || channels == Channels.Monochrome } else false fun isDefault(): Boolean = when (this) { is Base -> this == Base() is Avif -> this == Avif() is Jxl -> this == Jxl() is PngLossy -> this == PngLossy() is Tiff -> this == Tiff() } data class Jxl( @IntRange(from = 1, to = 100) override val qualityValue: Int = 50, @IntRange(from = 1, to = 10) val effort: Int = 2, @IntRange(from = 0, to = 4) val speed: Int = 0, val channels: Channels = Channels.RGBA ) : Quality data class Avif( @IntRange(from = 1, to = 100) override val qualityValue: Int = 50, @IntRange(from = 0, to = 9) val effort: Int = 0 ) : Quality data class PngLossy( @IntRange(from = 2, to = 1024) val maxColors: Int = 512, @IntRange(from = 0, to = 9) val compressionLevel: Int = 7, ) : Quality { override val qualityValue: Int = compressionLevel } data class Tiff( val compressionScheme: Int = 5 ) : Quality { override val qualityValue: Int = compressionScheme } data class Base( override val qualityValue: Int = 100 ) : Quality enum class Channels { RGBA, RGB, Monochrome; companion object { fun fromInt(int: Int) = when (int) { 1 -> RGB 2 -> Monochrome else -> RGBA } } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeAnchor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model enum class ResizeAnchor { Default, Width, Height, Max, Min } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/ResizeType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Position sealed class ResizeType { data object Explicit : ResizeType() data class Flexible( val resizeAnchor: ResizeAnchor = ResizeAnchor.Default ) : ResizeType() data class CenterCrop( val canvasColor: Int? = 0, val blurRadius: Int = 35, val originalSize: IntegerSize = IntegerSize.Undefined, val scaleFactor: Float = 1f, val position: Position = Position.Center ) : ResizeType() data class Fit( val canvasColor: Int? = 0, val blurRadius: Int = 35, val position: Position = Position.Center ) : ResizeType() fun withOriginalSizeIfCrop( originalSize: IntegerSize? ): ResizeType = if (this is CenterCrop) { copy(originalSize = originalSize ?: IntegerSize.Undefined) } else this companion object { val Flexible = Flexible() val entries by lazy { listOf( Explicit, Flexible(), CenterCrop(), Fit() ) } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/image/model/TiffCompressionScheme.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.image.model enum class TiffCompressionScheme(val value: Int) { /** * No compression */ NONE(1), /** * CCITT modified Huffman RLE */ CCITTRLE(2), /** * CCITT Group 3 fax encoding */ CCITTFAX3(3), /** * CCITT Group 4 fax encoding */ CCITTFAX4(4), /** * LZW */ LZW(5), /** * JPEG ('new-style' JPEG) */ JPEG(7), PACKBITS(32773), DEFLATE(32946), ADOBE_DEFLATE(8), /** * All other compression schemes */ OTHER(0); companion object { val safeEntries by lazy { TiffCompressionScheme.entries - OTHER } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/json/JsonParser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.json import java.lang.reflect.Type interface JsonParser { /** * [type] is type of [obj]: [T], which is converted to json * * @return Json from given object */ fun toJson( obj: T, type: Type, ): String? /** * [type] is type of [T], which is will be parsed from json * * @return Object from given json */ fun fromJson( json: String, type: Type, ): T? } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/CipherType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model import com.t8rin.imagetoolbox.core.domain.model.CipherType.Companion.registerSecurityCiphers @ConsistentCopyVisibility /** * [CipherType] multiplatform domain wrapper for java Cipher, in order to add custom digests, you need to call [registerSecurityCiphers] when process created **/ data class CipherType private constructor( val cipher: String, val name: String = cipher ) { companion object { val AES_NO_PADDING = CipherType("AES/GCM/NoPadding") val BROKEN by lazy { listOf( "IES", "SM2", "ML-KEM", "1.2.840.113533.7.66.10", "CAST5", "ElGAMAL", "KW", "RC2WRAP", "1.2.840.113549.1.1.7", "RSA", "OID.2.5.8.1.1", "RC5-64", "NTRU", "1.2.804.2", "GRAINV1", "1.3.14.3.2.7", "DSTU7624", "1.2.840.113549.1.1.1", "ETSIKEMWITHSHA256", "2.5.8.1.1", "GOST3412-2015", "SEEDWRAP", "2.16.840.1.101.3.4.1", "WRAP", "ARIACCM", "AES_128/ECB/NOPADDING", "AES_128/ECB/PKCS5PADDING", "DESEDE/ECB/NOPADDING", "1.2.392.200011.61.1.1.3", "AES/CBC/NOPADDING", "AES/ECB/NOPADDING", "AES/GCM-SIV/NOPADDING", "AES_128/CBC/NOPADDING", "AES_128/GCM-SIV/NOPADDING", "AES_128/GCM/NOPADDING", "AES_256/CBC/NOPADDING", "AES_256/ECB/NOPADDING", "AES_256/GCM-SIV/NOPADDING", "AES_256/GCM/NOPADDING", "1.2.840.113549.1.9.16.3.6", "DESEDE/CBC/NOPADDING", "CHACHA20-POLY1305", "CHACHA20/POLY1305" ) } private var securityCiphers: List? = null fun registerSecurityCiphers(ciphers: List) { if (!securityCiphers.isNullOrEmpty()) { throw IllegalArgumentException("SecurityCiphers already registered") } securityCiphers = ciphers.distinctBy { it.cipher.replace("OID.", "").uppercase() } } val entries: List by lazy { val available = securityCiphers?.mapNotNull { cipher -> val oid = cipher.cipher fun checkForBadOid( oid: String ) = oid.isEmpty() || oid.contains("BROKEN", true) || oid.contains("OLD", true) if (checkForBadOid(oid)) null else { val strippedCipher = oid.replace("OID.", "") SecureAlgorithmsMapping.findMatch(strippedCipher)?.let { mapping -> if (checkForBadOid(oid + mapping.algorithm)) return@mapNotNull null CipherType( cipher = oid, name = mapping.algorithm ) } ?: cipher } }?.sortedBy { it.name } ?: emptyList() listOf( AES_NO_PADDING ).let { it + available }.distinctBy { it.name.replace("-", "") } } fun fromString( cipher: String? ): CipherType? = cipher?.let { entries.find { it.cipher == cipher } } fun getInstance( cipher: String, name: String = cipher ): CipherType = CipherType( cipher = cipher, name = name ) } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ColorModel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model @JvmInline value class ColorModel( val colorInt: Int ) fun Int.toColorModel() = ColorModel(this) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/DomainAspectRatio.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model sealed class DomainAspectRatio( open val widthProportion: Float, open val heightProportion: Float ) { val value: Float get() = widthProportion / heightProportion data class Numeric( override val widthProportion: Float, override val heightProportion: Float ) : DomainAspectRatio(widthProportion = widthProportion, heightProportion = heightProportion) data object Free : DomainAspectRatio(widthProportion = -2f, heightProportion = 1f) data object Original : DomainAspectRatio(widthProportion = -1f, heightProportion = 1f) data class Custom( override val widthProportion: Float = 1f, override val heightProportion: Float = 1f ) : DomainAspectRatio(widthProportion = widthProportion, heightProportion = heightProportion) companion object { val defaultList: List by lazy { listOf( Free, Original, Custom(), Numeric(widthProportion = 1f, heightProportion = 1f), Numeric(widthProportion = 10f, heightProportion = 16f), Numeric(widthProportion = 9f, heightProportion = 16f), Numeric(widthProportion = 9f, heightProportion = 18.5f), Numeric(widthProportion = 9f, heightProportion = 20f), Numeric(widthProportion = 9f, heightProportion = 21f), Numeric(widthProportion = 1f, heightProportion = 1.91f), Numeric(widthProportion = 2f, heightProportion = 3f), Numeric(widthProportion = 1f, heightProportion = 2f), Numeric(widthProportion = 5f, heightProportion = 3f), Numeric(widthProportion = 5f, heightProportion = 4f), Numeric(widthProportion = 4f, heightProportion = 3f), Numeric(widthProportion = 21f, heightProportion = 9f), Numeric(widthProportion = 20f, heightProportion = 9f), Numeric(widthProportion = 18.5f, heightProportion = 9f), Numeric(widthProportion = 16f, heightProportion = 9f), Numeric(widthProportion = 16f, heightProportion = 10f), Numeric(widthProportion = 1.91f, heightProportion = 1f), Numeric(widthProportion = 3f, heightProportion = 2f), Numeric(widthProportion = 3f, heightProportion = 4f), Numeric(widthProportion = 4f, heightProportion = 5f), Numeric(widthProportion = 3f, heightProportion = 5f), Numeric(widthProportion = 2f, heightProportion = 1f) ) } fun fromString(value: String): DomainAspectRatio? = when { value == Free::class.simpleName -> Free value == Original::class.simpleName -> Original value.contains(Custom::class.simpleName!!) -> { val (w, h) = value.split("|")[1].split(":").map { it.toFloat() } Custom(w, h) } value.contains(Numeric::class.simpleName!!) -> { val (w, h) = value.split("|")[1].split(":").map { it.toFloat() } Numeric(w, h) } else -> null } } fun asString(): String = when { this is Custom || this is Numeric -> "${this::class.simpleName}|${widthProportion}:${heightProportion}" else -> this::class.simpleName!! } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ExtraDataType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model sealed interface ExtraDataType { data object Gif : ExtraDataType data object Pdf : ExtraDataType data object File : ExtraDataType data object Audio : ExtraDataType data class Backup(val uri: String) : ExtraDataType data class Text(val text: String) : ExtraDataType } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FileModel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model @JvmInline value class FileModel( val uri: String ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/FloatSize.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model data class FloatSize( val width: Float, val height: Float ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/HashingType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model import com.t8rin.imagetoolbox.core.domain.model.HashingType.Companion.registerSecurityMessageDigests @ConsistentCopyVisibility /** * [HashingType] multiplatform domain wrapper for java MessageDigest, in order to add custom digests, you need to call [registerSecurityMessageDigests] when process created **/ data class HashingType private constructor( val digest: String, val name: String = digest ) { companion object { val MD5 = HashingType("MD5") val SHA_1 = HashingType("SHA-1") val SHA_224 = HashingType("SHA-224") val SHA_256 = HashingType("SHA-256") val SHA_384 = HashingType("SHA-384") val SHA_512 = HashingType("SHA-512") private var securityMessageDigests: List? = null fun registerSecurityMessageDigests(digests: List) { if (!securityMessageDigests.isNullOrEmpty()) { throw IllegalArgumentException("SecurityMessageDigests already registered") } securityMessageDigests = digests.distinctBy { it.replace("OID.", "").uppercase() } } val entries: List by lazy { val available = securityMessageDigests?.mapNotNull { messageDigest -> if (messageDigest.isEmpty()) null else { val digest = messageDigest.replace("OID.", "") SecureAlgorithmsMapping.findMatch(digest)?.let { mapping -> HashingType( digest = messageDigest, name = mapping.algorithm ) } ?: HashingType(digest = messageDigest) } }?.sortedBy { it.digest } ?: emptyList() listOf( MD5, SHA_1, SHA_224, SHA_256, SHA_384, SHA_512, ).let { it + available }.distinctBy { it.name.replace("-", "") } } fun fromString( digest: String? ): HashingType? = digest?.let { entries.find { it.digest == digest } } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/ImageModel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model @JvmInline value class ImageModel( val data: Any ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/IntegerSize.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("SameParameterValue") package com.t8rin.imagetoolbox.core.domain.model import kotlin.math.max data class IntegerSize( val width: Int, val height: Int ) { val aspectRatio: Float get() = runCatching { val value = width.toFloat() / height if (value.isNaN()) throw IllegalArgumentException() value }.getOrNull() ?: 1f val safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) operator fun times(i: Float): IntegerSize = IntegerSize( width = (width * i).toInt(), height = (height * i).toInt() ).coerceAtLeast(0, 0) private fun coerceAtLeast( minWidth: Int, minHeight: Int ): IntegerSize = IntegerSize( width = width.coerceAtLeast(minWidth), height = height.coerceAtLeast(minHeight) ) fun isZero(): Boolean = width == 0 || height == 0 fun isDefined(): Boolean = this != Undefined companion object { val Undefined by lazy { IntegerSize(-1, -1) } val Zero by lazy { IntegerSize(0, 0) } } } fun max(size: IntegerSize): Int = maxOf(size.width, size.height) infix fun Int.sizeTo(int: Int): IntegerSize = IntegerSize(this, int) fun IntegerSize.flexibleResize( w: Int, h: Int ): IntegerSize { val max = max(w, h) return runCatching { if (width > w) { val aspectRatio = width.toDouble() / height.toDouble() val targetHeight = w / aspectRatio return@runCatching IntegerSize(w, targetHeight.toInt()) } if (height >= width) { val aspectRatio = width.toDouble() / height.toDouble() val targetWidth = (max * aspectRatio).toInt() IntegerSize(targetWidth, max) } else { val aspectRatio = height.toDouble() / width.toDouble() val targetHeight = (max * aspectRatio).toInt() IntegerSize(max, targetHeight) } }.getOrNull() ?: this } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/MimeType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused", "MemberVisibilityCanBePrivate") package com.t8rin.imagetoolbox.core.domain.model import com.t8rin.imagetoolbox.core.domain.model.MimeType.Multiple sealed class MimeType( val entries: Set ) { constructor(type: String) : this(setOf(type)) data class Single( private val type: String ) : MimeType(type) { val entry = type } data class Multiple( private val types: Set ) : MimeType(types) companion object { val All = Single("*/*") val Txt = Single("text/plain") val Pdf = Single("application/pdf") val Zip = Single("application/zip") val Webp = Single("image/webp") val Gif = Single("image/gif") val Apng = Single("image/apng") val StaticPng = Single("image/png") val Png = Apng + StaticPng val Audio = Single("audio/*") val Jpg = Single("image/jpg") val Jpeg = Single("image/jpeg") val JpgAll = Jpg + Jpeg val Font = Multiple( setOf( "font/ttf", "application/x-font-ttf", "font/otf" ) ) val Bmp = Single("image/bmp") val Avif = Single("image/avif") val Heif = Single("image/heif") val Heic = Single("image/heic") val Jxl = Single("image/jxl") val Jp2 = Single("image/jp2") val Tiff = Single("image/tiff") val Qoi = Single("image/qoi") val Ico = Single("image/x-icon") val Svg = Single("image/svg+xml") val MarkupProject = Single("application/x-imagetoolbox-project") val MarkupProjectList = Multiple( setOf( MarkupProject.entry, Zip.entry, "application/octet-stream" ) ) } } fun mimeType( type: String ): MimeType.Single = MimeType.Single(type) fun String.toMimeType() = mimeType(this) fun mimeTypeOf( vararg types: String ): Multiple = Multiple(types.toSet()) fun mimeTypeOf( vararg types: MimeType ): Multiple = Multiple(types.flatMapTo(mutableSetOf()) { it.entries }) operator fun MimeType.plus( type: MimeType ): Multiple = Multiple(types = entries + type.entries) operator fun Multiple.minus( type: MimeType ): Multiple = Multiple(types = entries - type.entries) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/OffsetModel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model data class OffsetModel( val x: Float, val y: Float ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Outline.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model data class Outline( val color: Int, val width: Float ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/PerformanceClass.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model sealed interface PerformanceClass { data object Low : PerformanceClass data object Average : PerformanceClass data object High : PerformanceClass } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Position.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model enum class Position { Center, TopLeft, TopRight, BottomLeft, BottomRight, TopCenter, CenterRight, BottomCenter, CenterLeft } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/Pt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.domain.model import kotlin.math.min private const val NORMALIZATION_FACTOR = 500 @JvmInline value class Pt(val value: Float) { fun toPx( size: IntegerSize ): Float = min( size.width * (value / NORMALIZATION_FACTOR), size.height * (value / NORMALIZATION_FACTOR) ) /** * Add two [Pt]s together. */ inline operator fun plus(other: Pt) = Pt(this.value + other.value) /** * Subtract a Pt from another one. */ inline operator fun minus(other: Pt) = Pt(this.value - other.value) /** * This is the same as multiplying the Pt by -1.0. */ inline operator fun unaryMinus() = Pt(-value) /** * Divide a Pt by a scalar. */ inline operator fun div(other: Float): Pt = Pt(value / other) inline operator fun div(other: Int): Pt = Pt(value / other) /** * Divide by another Pt to get a scalar. */ inline operator fun div(other: Pt): Float = value / other.value /** * Multiply a Pt by a scalar. */ inline operator fun times(other: Float): Pt = Pt(value * other) inline operator fun times(other: Int): Pt = Pt(value * other) /** * Support comparing Dimensions with comparison operators. */ inline operator fun compareTo(other: Pt) = value.compareTo(other.value) companion object { val Zero = Pt(0f) } } inline val Float.pt: Pt get() = Pt(this) inline val Int.pt: Pt get() = Pt(this.toFloat()) inline fun Pt.coerceIn( min: Pt, max: Pt ) = Pt(value.coerceIn(min.value, max.value)) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/QrType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.core.domain.model import com.t8rin.imagetoolbox.core.domain.utils.cast import java.util.Date sealed interface QrType { val raw: String fun isEmpty(): Boolean data class Plain( override val raw: String ) : QrType { constructor() : this(raw = "") override fun isEmpty(): Boolean = raw.isBlank() } data class Url( override val raw: String, val title: String, val url: String ) : QrType { constructor() : this( raw = "", title = "", url = "" ) override fun isEmpty(): Boolean = title.isBlank() && url.isBlank() } sealed interface Complex : QrType data class Wifi( override val raw: String, val ssid: String, val password: String, val encryptionType: EncryptionType ) : Complex { constructor() : this( raw = "", ssid = "", password = "", encryptionType = EncryptionType.WPA ) enum class EncryptionType { OPEN, WPA, WEP } override fun isEmpty(): Boolean = ssid.isBlank() && password.isBlank() } data class Sms( override val raw: String, val message: String, val phoneNumber: String ) : Complex { constructor() : this( raw = "", message = "", phoneNumber = "" ) override fun isEmpty(): Boolean = message.isBlank() && phoneNumber.isBlank() } data class Geo( override val raw: String, val latitude: Double?, val longitude: Double? ) : Complex { constructor() : this( raw = "", latitude = null, longitude = null ) override fun isEmpty(): Boolean = latitude == null || longitude == null } data class Email( override val raw: String, val address: String, val body: String, val subject: String, val type: Int ) : Complex { constructor() : this( raw = "", address = "", body = "", subject = "", type = 0 ) override fun isEmpty(): Boolean = address.isBlank() && body.isBlank() && subject.isBlank() } data class Phone( override val raw: String, val number: String, val type: Int ) : Complex { constructor() : this( raw = "", number = "", type = 0 ) override fun isEmpty(): Boolean = number.isBlank() } data class Contact( override val raw: String, val addresses: List
, val emails: List, val name: PersonName, val organization: String, val phones: List, val title: String, val urls: List ) : Complex { constructor() : this( raw = "", addresses = emptyList(), emails = emptyList(), name = PersonName(), organization = "", phones = emptyList(), title = "", urls = emptyList() ) data class Address( val addressLines: List, val type: Int ) { constructor() : this( addressLines = emptyList(), type = 0 ) } data class PersonName( val first: String, val formattedName: String, val last: String, val middle: String, val prefix: String, val pronunciation: String, val suffix: String ) { constructor() : this( first = "", formattedName = "", last = "", middle = "", prefix = "", pronunciation = "", suffix = "" ) fun isEmpty() = first.isBlank() && formattedName.isBlank() && last.isBlank() && middle.isBlank() && prefix.isBlank() && pronunciation.isBlank() && suffix.isBlank() } override fun isEmpty(): Boolean = addresses.isEmpty() && emails.isEmpty() && name.isEmpty() && organization.isBlank() && phones.isEmpty() && title.isBlank() && urls.isEmpty() } data class Calendar( override val raw: String, val description: String, val end: Date?, val location: String, val organizer: String, val start: Date?, val status: String, val summary: String ) : Complex { constructor() : this( raw = "", description = "", end = null, location = "", organizer = "", start = null, status = "", summary = "", ) override fun isEmpty(): Boolean = description.isBlank() && end == null && location.isBlank() && organizer.isBlank() && start == null && status.isBlank() && summary.isBlank() } companion object { val Empty = Plain("") val complexEntries: List by lazy { listOf( Wifi(), Sms(), Geo(), Email(), Phone(), Contact(), Calendar() ) } } } inline fun T.copy(raw: String): T = when (this) { is QrType.Plain -> this.copy(raw = raw) is QrType.Wifi -> this.copy(raw = raw) is QrType.Url -> this.copy(raw = raw) is QrType.Sms -> this.copy(raw = raw) is QrType.Geo -> this.copy(raw = raw) is QrType.Email -> this.copy(raw = raw) is QrType.Phone -> this.copy(raw = raw) is QrType.Contact -> this.copy(raw = raw) is QrType.Calendar -> this.copy(raw = raw) }.cast() inline fun QrType.ifNotEmpty(action: () -> T): T? = if (!isEmpty()) action() else null ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/RectModel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model import kotlin.math.absoluteValue import kotlin.math.max import kotlin.math.min data class RectModel( /** The OffsetModel of the left edge of this rectangle from the x axis. */ val left: Float, /** The OffsetModel of the top edge of this rectangle from the y axis. */ val top: Float, /** The OffsetModel of the right edge of this rectangle from the x axis. */ val right: Float, /** The OffsetModel of the bottom edge of this rectangle from the y axis. */ val bottom: Float, ) { companion object { /** A rectangle with left, top, right, and bottom edges all at zero. */ val Zero: RectModel = RectModel(0.0f, 0.0f, 0.0f, 0.0f) } /** The distance between the left and right edges of this rectangle. */ inline val width: Float get() = right - left /** The distance between the top and bottom edges of this rectangle. */ inline val height: Float get() = bottom - top /** The distance between the upper-left corner and the lower-right corner of this rectangle. */ val size: FloatSize get() = FloatSize(width, height) /** Whether any of the coordinates of this rectangle are equal to positive infinity. */ // included for consistency with OffsetModel and Size val isInfinite: Boolean get() = (left == Float.POSITIVE_INFINITY) or (top == Float.POSITIVE_INFINITY) or (right == Float.POSITIVE_INFINITY) or (bottom == Float.POSITIVE_INFINITY) /** Whether all coordinates of this rectangle are finite. */ val isFinite: Boolean get() = ((left.toRawBits() and 0x7fffffff) < FloatInfinityBase) and ((top.toRawBits() and 0x7fffffff) < FloatInfinityBase) and ((right.toRawBits() and 0x7fffffff) < FloatInfinityBase) and ((bottom.toRawBits() and 0x7fffffff) < FloatInfinityBase) /** Whether this rectangle encloses a non-zero area. Negative areas are considered empty. */ val isEmpty: Boolean get() = (left >= right) or (top >= bottom) /** * Returns a new rectangle translated by the given OffsetModel. * * To translate a rectangle by separate x and y components rather than by an [offset], consider * [translate]. */ fun translate(offset: OffsetModel): RectModel { return RectModel( left + offset.x, top + offset.y, right + offset.x, bottom + offset.y ) } /** * Returns a new rectangle with translateX added to the x components and translateY added to the * y components. */ fun translate(translateX: Float, translateY: Float): RectModel { return RectModel( left + translateX, top + translateY, right + translateX, bottom + translateY ) } /** Returns a new rectangle with edges moved outwards by the given delta. */ fun inflate(delta: Float): RectModel { return RectModel(left - delta, top - delta, right + delta, bottom + delta) } /** Returns a new rectangle with edges moved inwards by the given delta. */ fun deflate(delta: Float): RectModel = inflate(-delta) /** * Returns a new rectangle that is the intersection of the given rectangle and this rectangle. * The two rectangles must overlap for this to be meaningful. If the two rectangles do not * overlap, then the resulting RectModel will have a negative width or height. */ fun intersect(other: RectModel): RectModel { return RectModel( max(left, other.left), max(top, other.top), min(right, other.right), min(bottom, other.bottom), ) } /** * Returns a new rectangle that is the intersection of the given rectangle and this rectangle. * The two rectangles must overlap for this to be meaningful. If the two rectangles do not * overlap, then the resulting RectModel will have a negative width or height. */ fun intersect( otherLeft: Float, otherTop: Float, otherRight: Float, otherBottom: Float ): RectModel { return RectModel( max(left, otherLeft), max(top, otherTop), min(right, otherRight), min(bottom, otherBottom), ) } /** Whether `other` has a nonzero area of overlap with this rectangle. */ fun overlaps(other: RectModel): Boolean { return (left < other.right) and (other.left < right) and (top < other.bottom) and (other.top < bottom) } /** The lesser of the magnitudes of the [width] and the [height] of this rectangle. */ val minDimension: Float get() = min(width.absoluteValue, height.absoluteValue) /** The greater of the magnitudes of the [width] and the [height] of this rectangle. */ val maxDimension: Float get() = max(width.absoluteValue, height.absoluteValue) /** The OffsetModel to the intersection of the top and left edges of this rectangle. */ val topLeft: OffsetModel get() = OffsetModel(left, top) /** The OffsetModel to the center of the top edge of this rectangle. */ val topCenter: OffsetModel get() = OffsetModel(left + width / 2.0f, top) /** The OffsetModel to the intersection of the top and right edges of this rectangle. */ val topRight: OffsetModel get() = OffsetModel(right, top) /** The OffsetModel to the center of the left edge of this rectangle. */ val centerLeft: OffsetModel get() = OffsetModel(left, top + height / 2.0f) /** * The OffsetModel to the point halfway between the left and right and the top and bottom edges of * this rectangle. * * See also [Size.center]. */ val center: OffsetModel get() = OffsetModel(left + width / 2.0f, top + height / 2.0f) /** The OffsetModel to the center of the right edge of this rectangle. */ val centerRight: OffsetModel get() = OffsetModel(right, top + height / 2.0f) /** The OffsetModel to the intersection of the bottom and left edges of this rectangle. */ val bottomLeft: OffsetModel get() = OffsetModel(left, bottom) /** The OffsetModel to the center of the bottom edge of this rectangle. */ val bottomCenter: OffsetModel get() { return OffsetModel(left + width / 2.0f, bottom) } /** The OffsetModel to the intersection of the bottom and right edges of this rectangle. */ val bottomRight: OffsetModel get() { return OffsetModel(right, bottom) } /** * Whether the point specified by the given OffsetModel (which is assumed to be relative to the * origin) lies between the left and right and the top and bottom edges of this rectangle. * * Rectangles include their top and left edges but exclude their bottom and right edges. */ operator fun contains(offset: OffsetModel): Boolean { val x = offset.x val y = offset.y return (x >= left) and (x < right) and (y >= top) and (y < bottom) } } internal const val FloatInfinityBase = 0x7f800000 ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SecureAlgorithmsMapping.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("EnumEntryName", "unused") package com.t8rin.imagetoolbox.core.domain.model /** * This utility class maps algorithm name to the corresponding oid strings. * NOTE: for 100% backward compatibility, the standard name for the enum * is determined by existing usage and may be in lowercase/uppercase in * order to match existing output. */ enum class SecureAlgorithmsMapping { // X.500 Attributes 2.5.4.* CommonName("2.5.4.3"), Surname("2.5.4.4"), SerialNumber("2.5.4.5"), CountryName("2.5.4.6"), LocalityName("2.5.4.7"), StateName("2.5.4.8"), StreetAddress("2.5.4.9"), OrgName("2.5.4.10"), OrgUnitName("2.5.4.11"), Title("2.5.4.12"), GivenName("2.5.4.42"), Initials("2.5.4.43"), GenerationQualifier("2.5.4.44"), DNQualifier("2.5.4.46"), // Certificate Extension 2.5.29.* SubjectDirectoryAttributes("2.5.29.9"), SubjectKeyID("2.5.29.14"), KeyUsage("2.5.29.15"), PrivateKeyUsage("2.5.29.16"), SubjectAlternativeName("2.5.29.17"), IssuerAlternativeName("2.5.29.18"), BasicConstraints("2.5.29.19"), CRLNumber("2.5.29.20"), ReasonCode("2.5.29.21"), HoldInstructionCode("2.5.29.23"), InvalidityDate("2.5.29.24"), DeltaCRLIndicator("2.5.29.27"), IssuingDistributionPoint("2.5.29.28"), CertificateIssuer("2.5.29.29"), NameConstraints("2.5.29.30"), CRLDistributionPoints("2.5.29.31"), CertificatePolicies("2.5.29.32"), CE_CERT_POLICIES_ANY("2.5.29.32.0"), PolicyMappings("2.5.29.33"), AuthorityKeyID("2.5.29.35"), PolicyConstraints("2.5.29.36"), extendedKeyUsage("2.5.29.37"), anyExtendedKeyUsage("2.5.29.37.0"), FreshestCRL("2.5.29.46"), InhibitAnyPolicy("2.5.29.54"), // PKIX 1.3.6.1.5.5.7. AuthInfoAccess("1.3.6.1.5.5.7.1.1"), SubjectInfoAccess("1.3.6.1.5.5.7.1.11"), // key usage purposes - PKIX.3.* serverAuth("1.3.6.1.5.5.7.3.1"), clientAuth("1.3.6.1.5.5.7.3.2"), codeSigning("1.3.6.1.5.5.7.3.3"), emailProtection("1.3.6.1.5.5.7.3.4"), ipsecEndSystem("1.3.6.1.5.5.7.3.5"), ipsecTunnel("1.3.6.1.5.5.7.3.6"), ipsecUser("1.3.6.1.5.5.7.3.7"), KP_TimeStamping("1.3.6.1.5.5.7.3.8", "timeStamping"), OCSPSigning("1.3.6.1.5.5.7.3.9"), // access descriptors - PKIX.48.* OCSP("1.3.6.1.5.5.7.48.1"), OCSPBasicResponse("1.3.6.1.5.5.7.48.1.1"), OCSPNonceExt("1.3.6.1.5.5.7.48.1.2"), OCSPNoCheck("1.3.6.1.5.5.7.48.1.5"), caIssuers("1.3.6.1.5.5.7.48.2"), AD_TimeStamping("1.3.6.1.5.5.7.48.3", "timeStamping"), caRepository("1.3.6.1.5.5.7.48.5", "caRepository"), // NIST -- // AES 2.16.840.1.101.3.4.1.* AES("2.16.840.1.101.3.4.1"), AES_128_ECB_NoPadding("2.16.840.1.101.3.4.1.1", "AES_128/ECB/NoPadding"), AES_128_CBC_NoPadding("2.16.840.1.101.3.4.1.2", "AES_128/CBC/NoPadding"), AES_128_OFB_NoPadding("2.16.840.1.101.3.4.1.3", "AES_128/OFB/NoPadding"), AES_128_CFB_NoPadding("2.16.840.1.101.3.4.1.4", "AES_128/CFB/NoPadding"), AES_128_KW_NoPadding( "2.16.840.1.101.3.4.1.5", "AES_128/KW/NoPadding", "AESWrap_128" ), AES_128_GCM_NoPadding("2.16.840.1.101.3.4.1.6", "AES_128/GCM/NoPadding"), AES_128_KWP_NoPadding( "2.16.840.1.101.3.4.1.8", "AES_128/KWP/NoPadding", "AESWrapPad_128" ), AES_192_ECB_NoPadding("2.16.840.1.101.3.4.1.21", "AES_192/ECB/NoPadding"), AES_192_CBC_NoPadding("2.16.840.1.101.3.4.1.22", "AES_192/CBC/NoPadding"), AES_192_OFB_NoPadding("2.16.840.1.101.3.4.1.23", "AES_192/OFB/NoPadding"), AES_192_CFB_NoPadding("2.16.840.1.101.3.4.1.24", "AES_192/CFB/NoPadding"), AES_192_KW_NoPadding( "2.16.840.1.101.3.4.1.25", "AES_192/KW/NoPadding", "AESWrap_192" ), AES_192_GCM_NoPadding("2.16.840.1.101.3.4.1.26", "AES_192/GCM/NoPadding"), AES_192_KWP_NoPadding( "2.16.840.1.101.3.4.1.28", "AES_192/KWP/NoPadding", "AESWrapPad_192" ), AES_256_ECB_NoPadding("2.16.840.1.101.3.4.1.41", "AES_256/ECB/NoPadding"), AES_256_CBC_NoPadding("2.16.840.1.101.3.4.1.42", "AES_256/CBC/NoPadding"), AES_256_OFB_NoPadding("2.16.840.1.101.3.4.1.43", "AES_256/OFB/NoPadding"), AES_256_CFB_NoPadding("2.16.840.1.101.3.4.1.44", "AES_256/CFB/NoPadding"), AES_256_KW_NoPadding( "2.16.840.1.101.3.4.1.45", "AES_256/KW/NoPadding", "AESWrap_256" ), AES_256_GCM_NoPadding("2.16.840.1.101.3.4.1.46", "AES_256/GCM/NoPadding"), AES_256_KWP_NoPadding( "2.16.840.1.101.3.4.1.48", "AES_256/KWP/NoPadding", "AESWrapPad_256" ), // hashAlgs 2.16.840.1.101.3.4.2.* SHA_256("2.16.840.1.101.3.4.2.1", "SHA-256", "SHA256"), SHA_384("2.16.840.1.101.3.4.2.2", "SHA-384", "SHA384"), SHA_512("2.16.840.1.101.3.4.2.3", "SHA-512", "SHA512"), SHA_224("2.16.840.1.101.3.4.2.4", "SHA-224", "SHA224"), SHA_512_224("2.16.840.1.101.3.4.2.5", "SHA-512/224", "SHA512/224"), SHA_512_256("2.16.840.1.101.3.4.2.6", "SHA-512/256", "SHA512/256"), SHA3_224("2.16.840.1.101.3.4.2.7", "SHA3-224"), SHA3_256("2.16.840.1.101.3.4.2.8", "SHA3-256"), SHA3_384("2.16.840.1.101.3.4.2.9", "SHA3-384"), SHA3_512("2.16.840.1.101.3.4.2.10", "SHA3-512"), SHAKE128("2.16.840.1.101.3.4.2.11"), SHAKE256("2.16.840.1.101.3.4.2.12"), HmacSHA3_224("2.16.840.1.101.3.4.2.13", "HmacSHA3-224"), HmacSHA3_256("2.16.840.1.101.3.4.2.14", "HmacSHA3-256"), HmacSHA3_384("2.16.840.1.101.3.4.2.15", "HmacSHA3-384"), HmacSHA3_512("2.16.840.1.101.3.4.2.16", "HmacSHA3-512"), SHAKE128_LEN("2.16.840.1.101.3.4.2.17", "SHAKE128-LEN"), SHAKE256_LEN("2.16.840.1.101.3.4.2.18", "SHAKE256-LEN"), SHA512_224("1.0.10118.3.0.55", "SHA-512/224"), SHA512_256("2.16.840.1.101.3.4.2.12", "SHA-512/256"), SHA384("2.16.840.1.101.3.4.2.11", "SHA-384"), SHA512("2.16.840.1.101.3.4.2.10", "SHA-512"), DSTU7564_256("1.2.804.2.1.1.1.1.2.2.3", "DSTU-7564-256"), DSTU7564_384("1.2.804.2.1.1.1.1.2.2.2", "DSTU-7564-384"), DSTU7564_512("1.2.804.2.1.1.1.1.2.2.1", "DSTU-7564-512"), SHA224("2.16.840.1.101.3.4.2.9", "SHA-224"), SHA256("2.16.840.1.101.3.4.2.8", "SHA-256"), SHA1("2.16.840.1.101.3.4.2.7", "SHA-1"), // sigAlgs 2.16.840.1.101.3.4.3.* SHA224withDSA("2.16.840.1.101.3.4.3.1"), SHA256withDSA("2.16.840.1.101.3.4.3.2"), SHA384withDSA("2.16.840.1.101.3.4.3.3"), SHA512withDSA("2.16.840.1.101.3.4.3.4"), SHA3_224withDSA("2.16.840.1.101.3.4.3.5", "SHA3-224withDSA"), SHA3_256withDSA("2.16.840.1.101.3.4.3.6", "SHA3-256withDSA"), SHA3_384withDSA("2.16.840.1.101.3.4.3.7", "SHA3-384withDSA"), SHA3_512withDSA("2.16.840.1.101.3.4.3.8", "SHA3-512withDSA"), SHA3_224withECDSA("2.16.840.1.101.3.4.3.9", "SHA3-224withECDSA"), SHA3_256withECDSA("2.16.840.1.101.3.4.3.10", "SHA3-256withECDSA"), SHA3_384withECDSA("2.16.840.1.101.3.4.3.11", "SHA3-384withECDSA"), SHA3_512withECDSA("2.16.840.1.101.3.4.3.12", "SHA3-512withECDSA"), SHA3_224withRSA("2.16.840.1.101.3.4.3.13", "SHA3-224withRSA"), SHA3_256withRSA("2.16.840.1.101.3.4.3.14", "SHA3-256withRSA"), SHA3_384withRSA("2.16.840.1.101.3.4.3.15", "SHA3-384withRSA"), SHA3_512withRSA("2.16.840.1.101.3.4.3.16", "SHA3-512withRSA"), ML_DSA_44("2.16.840.1.101.3.4.3.17", "ML-DSA-44"), ML_DSA_65("2.16.840.1.101.3.4.3.18", "ML-DSA-65"), ML_DSA_87("2.16.840.1.101.3.4.3.19", "ML-DSA-87"), // kems 2.16.840.1.101.3.4.4.* ML_KEM_512("2.16.840.1.101.3.4.4.1", "ML-KEM-512"), ML_KEM_768("2.16.840.1.101.3.4.4.2", "ML-KEM-768"), ML_KEM_1024("2.16.840.1.101.3.4.4.3", "ML-KEM-1024"), // RSASecurity // PKCS1 1.2.840.113549.1.1.* PKCS1("1.2.840.113549.1.1", "RSA"), RSA("1.2.840.113549.1.1.1"), // RSA encryption MD2withRSA("1.2.840.113549.1.1.2"), MD5withRSA("1.2.840.113549.1.1.4"), SHA1withRSA("1.2.840.113549.1.1.5"), OAEP("1.2.840.113549.1.1.7"), MGF1("1.2.840.113549.1.1.8"), PSpecified("1.2.840.113549.1.1.9"), RSASSA_PSS("1.2.840.113549.1.1.10", "RSASSA-PSS", "PSS"), SHA256withRSA("1.2.840.113549.1.1.11"), SHA384withRSA("1.2.840.113549.1.1.12"), SHA512withRSA("1.2.840.113549.1.1.13"), SHA224withRSA("1.2.840.113549.1.1.14"), SHA512_224withRSA("1.2.840.113549.1.1.15", "SHA512/224withRSA"), SHA512_256withRSA("1.2.840.113549.1.1.16", "SHA512/256withRSA"), // PKCS3 1.2.840.113549.1.3.* DiffieHellman("1.2.840.113549.1.3.1", "DiffieHellman", "DH"), // PKCS5 1.2.840.113549.1.5.* PBEWithMD5AndDES("1.2.840.113549.1.5.3"), PBEWithMD5AndRC2("1.2.840.113549.1.5.6"), PBEWithSHA1AndDES("1.2.840.113549.1.5.10"), PBEWithSHA1AndRC2("1.2.840.113549.1.5.11"), PBKDF2WithHmacSHA1("1.2.840.113549.1.5.12"), PBES2("1.2.840.113549.1.5.13"), // PKCS7 1.2.840.113549.1.7.* PKCS7("1.2.840.113549.1.7"), Data("1.2.840.113549.1.7.1"), SignedData("1.2.840.113549.1.7.2"), JDK_OLD_Data("1.2.840.1113549.1.7.1"), // extra 1 in 4th component JDK_OLD_SignedData("1.2.840.1113549.1.7.2"), EnvelopedData("1.2.840.113549.1.7.3"), SignedAndEnvelopedData("1.2.840.113549.1.7.4"), DigestedData("1.2.840.113549.1.7.5"), EncryptedData("1.2.840.113549.1.7.6"), // PKCS9 1.2.840.113549.1.9.* EmailAddress("1.2.840.113549.1.9.1"), UnstructuredName("1.2.840.113549.1.9.2"), ContentType("1.2.840.113549.1.9.3"), MessageDigest("1.2.840.113549.1.9.4"), SigningTime("1.2.840.113549.1.9.5"), CounterSignature("1.2.840.113549.1.9.6"), ChallengePassword("1.2.840.113549.1.9.7"), UnstructuredAddress("1.2.840.113549.1.9.8"), ExtendedCertificateAttributes("1.2.840.113549.1.9.9"), IssuerAndSerialNumber("1.2.840.113549.1.9.10"), ExtensionRequest("1.2.840.113549.1.9.14"), SMIMECapability("1.2.840.113549.1.9.15"), TimeStampTokenInfo("1.2.840.113549.1.9.16.1.4"), SigningCertificate("1.2.840.113549.1.9.16.2.12"), SignatureTimestampToken("1.2.840.113549.1.9.16.2.14"), HSSLMS("1.2.840.113549.1.9.16.3.17", "HSS/LMS"), CHACHA20_POLY1305("1.2.840.113549.1.9.16.3.18", "CHACHA20-POLY1305"), FriendlyName("1.2.840.113549.1.9.20"), LocalKeyID("1.2.840.113549.1.9.21"), CertTypeX509("1.2.840.113549.1.9.22.1"), CMSAlgorithmProtection("1.2.840.113549.1.9.52"), // PKCS12 1.2.840.113549.1.12.* PBEWithSHA1AndRC4_128("1.2.840.113549.1.12.1.1"), PBEWithSHA1AndRC4_40("1.2.840.113549.1.12.1.2"), PBEWithSHA1AndDESede("1.2.840.113549.1.12.1.3"), PBEWithSHA1AndRC2_128("1.2.840.113549.1.12.1.5"), PBEWithSHA1AndRC2_40("1.2.840.113549.1.12.1.6"), PKCS8ShroudedKeyBag("1.2.840.113549.1.12.10.1.2"), CertBag("1.2.840.113549.1.12.10.1.3"), SecretBag("1.2.840.113549.1.12.10.1.5"), // digestAlgs 1.2.840.113549.2.* MD2("1.2.840.113549.2.2"), MD5("1.2.840.113549.2.5"), HmacSHA1("1.2.840.113549.2.7"), HmacSHA224("1.2.840.113549.2.8"), HmacSHA256("1.2.840.113549.2.9"), HmacSHA384("1.2.840.113549.2.10"), HmacSHA512("1.2.840.113549.2.11"), HmacSHA512_224("1.2.840.113549.2.12", "HmacSHA512/224"), HmacSHA512_256("1.2.840.113549.2.13", "HmacSHA512/256"), // encryptionAlgs 1.2.840.113549.3.* RC2_CBC_PKCS5Padding("1.2.840.113549.3.2", "RC2/CBC/PKCS5Padding", "RC2"), ARCFOUR("1.2.840.113549.3.4", "ARCFOUR", "RC4"), DESede_CBC_NoPadding("1.2.840.113549.3.7", "DESede/CBC/NoPadding"), RC5_CBC_PKCS5Padding("1.2.840.113549.3.9", "RC5/CBC/PKCS5Padding"), // ANSI -- // X9 1.2.840.10040.4.* DSA("1.2.840.10040.4.1"), SHA1withDSA("1.2.840.10040.4.3", "SHA1withDSA", "DSS"), // X9.62 1.2.840.10045.* EC("1.2.840.10045.2.1"), c2pnb163v1("1.2.840.10045.3.0.1", "X9.62 c2pnb163v1"), c2pnb163v2("1.2.840.10045.3.0.2", "X9.62 c2pnb163v2"), c2pnb163v3("1.2.840.10045.3.0.3", "X9.62 c2pnb163v3"), c2pnb176w1("1.2.840.10045.3.0.4", "X9.62 c2pnb176w1"), c2tnb191v1("1.2.840.10045.3.0.5", "X9.62 c2tnb191v1"), c2tnb191v2("1.2.840.10045.3.0.6", "X9.62 c2tnb191v2"), c2tnb191v3("1.2.840.10045.3.0.7", "X9.62 c2tnb191v3"), c2pnb208w1("1.2.840.10045.3.0.10", "X9.62 c2pnb208w1"), c2tnb239v1("1.2.840.10045.3.0.11", "X9.62 c2tnb239v1"), c2tnb239v2("1.2.840.10045.3.0.12", "X9.62 c2tnb239v2"), c2tnb239v3("1.2.840.10045.3.0.13", "X9.62 c2tnb239v3"), c2pnb272w1("1.2.840.10045.3.0.16", "X9.62 c2pnb272w1"), c2pnb304w1("1.2.840.10045.3.0.17", "X9.62 c2pnb304w1"), c2tnb359v1("1.2.840.10045.3.0.18", "X9.62 c2tnb359v1"), c2pnb368w1("1.2.840.10045.3.0.19", "X9.62 c2pnb368w1"), c2tnb431r1("1.2.840.10045.3.0.20", "X9.62 c2tnb431r1"), secp192r1( "1.2.840.10045.3.1.1", "secp192r1", "NIST P-192", "X9.62 prime192v1" ), prime192v2("1.2.840.10045.3.1.2", "X9.62 prime192v2"), prime192v3("1.2.840.10045.3.1.3", "X9.62 prime192v3"), prime239v1("1.2.840.10045.3.1.4", "X9.62 prime239v1"), prime239v2("1.2.840.10045.3.1.5", "X9.62 prime239v2"), prime239v3("1.2.840.10045.3.1.6", "X9.62 prime239v3"), secp256r1( "1.2.840.10045.3.1.7", "secp256r1", "NIST P-256", "X9.62 prime256v1" ), SHA1withECDSA("1.2.840.10045.4.1"), SHA224withECDSA("1.2.840.10045.4.3.1"), SHA256withECDSA("1.2.840.10045.4.3.2"), SHA384withECDSA("1.2.840.10045.4.3.3"), SHA512withECDSA("1.2.840.10045.4.3.4"), SpecifiedSHA2withECDSA("1.2.840.10045.4.3"), // X9.42 1.2.840.10046.2.* X942_DH("1.2.840.10046.2.1", "DiffieHellman"), // Teletrust 1.3.36.* brainpoolP160r1("1.3.36.3.3.2.8.1.1.1"), brainpoolP192r1("1.3.36.3.3.2.8.1.1.3"), brainpoolP224r1("1.3.36.3.3.2.8.1.1.5"), brainpoolP256r1("1.3.36.3.3.2.8.1.1.7"), brainpoolP320r1("1.3.36.3.3.2.8.1.1.9"), brainpoolP384r1("1.3.36.3.3.2.8.1.1.11"), brainpoolP512r1("1.3.36.3.3.2.8.1.1.13"), // Certicom 1.3.132.* sect163k1("1.3.132.0.1", "sect163k1", "NIST K-163"), sect163r1("1.3.132.0.2"), sect239k1("1.3.132.0.3"), sect113r1("1.3.132.0.4"), sect113r2("1.3.132.0.5"), secp112r1("1.3.132.0.6"), secp112r2("1.3.132.0.7"), secp160r1("1.3.132.0.8"), secp160k1("1.3.132.0.9"), secp256k1("1.3.132.0.10"), sect163r2("1.3.132.0.15", "sect163r2", "NIST B-163"), sect283k1("1.3.132.0.16", "sect283k1", "NIST K-283"), sect283r1("1.3.132.0.17", "sect283r1", "NIST B-283"), sect131r1("1.3.132.0.22"), sect131r2("1.3.132.0.23"), sect193r1("1.3.132.0.24"), sect193r2("1.3.132.0.25"), sect233k1("1.3.132.0.26", "sect233k1", "NIST K-233"), sect233r1("1.3.132.0.27", "sect233r1", "NIST B-233"), secp128r1("1.3.132.0.28"), secp128r2("1.3.132.0.29"), secp160r2("1.3.132.0.30"), secp192k1("1.3.132.0.31"), secp224k1("1.3.132.0.32"), secp224r1("1.3.132.0.33", "secp224r1", "NIST P-224"), secp384r1("1.3.132.0.34", "secp384r1", "NIST P-384"), secp521r1("1.3.132.0.35", "secp521r1", "NIST P-521"), sect409k1("1.3.132.0.36", "sect409k1", "NIST K-409"), sect409r1("1.3.132.0.37", "sect409r1", "NIST B-409"), sect571k1("1.3.132.0.38", "sect571k1", "NIST K-571"), sect571r1("1.3.132.0.39", "sect571r1", "NIST B-571"), ECDH("1.3.132.1.12"), // OIW secsig 1.3.14.3.* OIW_DES_CBC("1.3.14.3.2.7", "DES/CBC", "DES"), OIW_DSA("1.3.14.3.2.12", "DSA"), OIW_JDK_SHA1withDSA("1.3.14.3.2.13", "SHA1withDSA"), OIW_SHA1withRSA_Odd("1.3.14.3.2.15", "SHA1withRSA"), DESede("1.3.14.3.2.17", "DESede"), SHA_1("1.3.14.3.2.26", "SHA-1", "SHA", "SHA1"), OIW_SHA1withDSA("1.3.14.3.2.27", "SHA1withDSA"), OIW_SHA1withRSA("1.3.14.3.2.29", "SHA1withRSA"), // Thawte 1.3.101.* X25519("1.3.101.110"), X448("1.3.101.111"), Ed25519("1.3.101.112"), Ed448("1.3.101.113"), // University College London (UCL) 0.9.2342.19200300.* UCL_UserID("0.9.2342.19200300.100.1.1"), UCL_DomainComponent("0.9.2342.19200300.100.1.25"), // Netscape 2.16.840.1.113730.* NETSCAPE_CertType("2.16.840.1.113730.1.1"), NETSCAPE_CertSequence("2.16.840.1.113730.2.5"), NETSCAPE_ExportApproved("2.16.840.1.113730.4.1"), // Oracle 2.16.840.1.113894.* ORACLE_TrustedKeyUsage("2.16.840.1.113894.746875.1.1"), // Miscellaneous oids below which are legacy, and not well known // Consider removing them in future releases when their usage // have died out ITUX509_RSA("2.5.8.1.1", "RSA"), SkipIPAddress("1.3.6.1.4.1.42.2.11.2.1"), JAVASOFT_JDKKeyProtector("1.3.6.1.4.1.42.2.17.1.1"), JAVASOFT_JCEKeyProtector("1.3.6.1.4.1.42.2.19.1"), MICROSOFT_ExportApproved("1.3.6.1.4.1.311.10.3.3"), Blowfish("1.3.6.1.4.1.3029.1.1.2"), asn1_module("1.2.410.200046.1.1.1", "ASN1-module"), aria256_ecb("1.2.410.200046.1.1.11", "ARIA256/ECB"), aria256_cbc("1.2.410.200046.1.1.12", "ARIA256/CBC"), aria256_cfb("1.2.410.200046.1.1.13", "ARIA256/CFB"), aria256_ofb("1.2.410.200046.1.1.14", "ARIA256/OFB"), aria128_cbc("1.2.410.200046.1.1.2", "ARIA128/CBC"), aria128_cfb("1.2.410.200046.1.1.3", "ARIA128/CFB"), aria128_ofb("1.2.410.200046.1.1.4", "ARIA128/OFB"), aria192_ecb("1.2.410.200046.1.1.6", "ARIA192/ECB"), aria192_cbc("1.2.410.200046.1.1.7", "ARIA192/CBC"), aria192_cfb("1.2.410.200046.1.1.8", "ARIA192/CFB"), aria192_ofb("1.2.410.200046.1.1.9", "ARIA192/OFB"), BROKEN_GOST_R28147_89("1.2.643.2.2.13.0"), BROKEN_CryptoPro("1.2.643.2.2.13.1"), GOST_28147_89("1.2.643.2.2.21", "GOST-28147-89"), dstu7624_ecb_128("1.2.804.2.1.1.1.1.1.3.1.1", "DSTU7624/ECB-128"), dstu7624_ecb_256("1.2.804.2.1.1.1.1.1.3.1.2", "DSTU7624/ECB-256"), dstu7624_ecb_512("1.2.804.2.1.1.1.1.1.3.1.3", "DSTU7624/ECB-512"), dstu7624_ctr_128("1.2.804.2.1.1.1.1.1.3.2.1", "DSTU7624/CTR-128"), dstu7624_ctr_256("1.2.804.2.1.1.1.1.1.3.2.2", "DSTU7624/CTR-256"), dstu7624_ctr_512("1.2.804.2.1.1.1.1.1.3.2.3", "DSTU7624/CTR-512"), dstu7624_cfb_128("1.2.804.2.1.1.1.1.1.3.3.1", "DSTU7624/CFB-128"), dstu7624_cfb_256("1.2.804.2.1.1.1.1.1.3.3.2", "DSTU7624/CFB-256"), dstu7624_cfb_512("1.2.804.2.1.1.1.1.1.3.3.3", "DSTU7624/CFB-512"), dstu7624_cbc_128("1.2.804.2.1.1.1.1.1.3.5.1", "DSTU7624/CBC-128"), dstu7624_cbc_256("1.2.804.2.1.1.1.1.1.3.5.2", "DSTU7624/CBC-256"), dstu7624_cbc_512("1.2.804.2.1.1.1.1.1.3.5.3", "DSTU7624/CBC-512"), dstu7624_ofb_128("1.2.804.2.1.1.1.1.1.3.6.1", "DSTU7624/OFB-128"), dstu7624_ofb_256("1.2.804.2.1.1.1.1.1.3.6.2", "DSTU7624/OFB-256"), dstu7624_ofb_512("1.2.804.2.1.1.1.1.1.3.6.3", "DSTU7624/OFB-512"), dstu7624_ccm_128("1.2.804.2.1.1.1.1.1.3.8.1", "DSTU7624/CCM-128"), dstu7624_ccm_256("1.2.804.2.1.1.1.1.1.3.8.2", "DSTU7624/CCM-256"), dstu7624_ccm_512("1.2.804.2.1.1.1.1.1.3.8.3", "DSTU7624/CCM-512"), CMS3DESwrap("1.2.840.113549.1.9.16.3.6", "CMS3DESwrap"), des_CBC("1.3.14.3.2.7", "DES/CBC"), Joint_RSA("2.5.8.1.1", "Joint-RSA"), camellia128_wrap("1.2.392.200011.61.1.1.3.2", "camellia128/wrap"), camellia192_wrap("1.2.392.200011.61.1.1.3.3", "camellia192/wrap"), camellia256_wrap("1.2.392.200011.61.1.1.3.4", "camellia256/wrap"), id_aes192_CCM("2.16.840.1.101.3.4.1.27", "AES192/CCM"), aes256_CCM("2.16.840.1.101.3.4.1.47", "AES256/CCM"), aes128_CCM("2.16.840.1.101.3.4.1.7", "AES128/CCM"); val algorithm: String val oid: String val aliases: Array constructor(oid: String) { this.oid = oid this.algorithm = name // defaults to enum name this.aliases = arrayOf(oid, name) } constructor( oid: String, algorithm: String, vararg aliases: String ) { this.oid = oid this.algorithm = algorithm this.aliases = aliases.toList().toTypedArray() } companion object { fun findMatch( oidOrName: String ): SecureAlgorithmsMapping? = SecureAlgorithmsMapping.entries.find { it.oid == oidOrName || it.algorithm == oidOrName || it.aliases.contains(oidOrName) } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SortType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model enum class SortType { DateModified, DateModifiedReversed, Name, NameReversed, Size, SizeReversed, MimeType, MimeTypeReversed, Extension, ExtensionReversed, DateAdded, DateAddedReversed } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/SystemBarsVisibility.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model sealed class SystemBarsVisibility( val ordinal: Int ) { data object Auto : SystemBarsVisibility(0) data object ShowAll : SystemBarsVisibility(1) data object HideAll : SystemBarsVisibility(2) data object HideNavigationBar : SystemBarsVisibility(3) data object HideStatusBar : SystemBarsVisibility(4) companion object { val entries: List by lazy { listOf( Auto, ShowAll, HideAll, HideNavigationBar, HideStatusBar ) } fun fromOrdinal(ordinal: Int?): SystemBarsVisibility? = ordinal?.let { entries[ordinal] } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/model/VisibilityOwner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.model interface VisibilityOwner { val isVisible: Boolean get() = true } inline fun T.ifVisible( action: T.() -> R ): R? = takeIf { it.isVisible }?.let(action) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/AnalyticsManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.remote interface AnalyticsManager { val allowCollectCrashlytics: Boolean val allowCollectAnalytics: Boolean fun updateAnalyticsCollectionEnabled(value: Boolean) fun updateAllowCollectCrashlytics(value: Boolean) fun sendReport(throwable: Throwable) fun registerScreenOpen(screenName: String) } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/Cache.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.remote import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap import kotlin.time.Clock import kotlin.time.Duration import kotlin.time.Instant private data class CacheItem( val value: V, val created: Instant, ) class Cache(private val maxAge: Duration) { private val map: ConcurrentMap> = ConcurrentHashMap() private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO) suspend fun call(key: K, dataGetter: suspend () -> V): V { val now = Clock.System.now() val prevItem = map[key]?.takeIf { it.created + maxAge > now } return if (prevItem != null) { prevItem.value } else { val data = dataGetter() val item = CacheItem( value = data, created = now ) map[key] = item scope.launch { delay(maxAge) map.remove(key, item) } data } } fun reset() { map.clear() } fun reset(key: K) { map.remove(key) } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.remote interface DownloadManager { suspend fun download( url: String, destinationPath: String, onStart: suspend () -> Unit = {}, onProgress: suspend (DownloadProgress) -> Unit, onFinish: suspend (Throwable?) -> Unit = {} ) suspend fun downloadZip( url: String, destinationPath: String, onStart: suspend () -> Unit = {}, onProgress: (DownloadProgress) -> Unit, downloadOnlyNewData: Boolean ) } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/DownloadProgress.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.remote data class DownloadProgress( val currentPercent: Float, val currentTotalSize: Long ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResources.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.remote data class RemoteResources( val name: String, val list: List ) { companion object { const val CUBE_LUT = "cubelut/luts.zip" const val MESH_GRADIENTS = "mesh_gradient/mesh_gradients.zip" val CubeLutDefault = RemoteResources( name = CUBE_LUT, list = emptyList() ) val MeshGradientsDefault = RemoteResources( name = MESH_GRADIENTS, list = emptyList() ) } } data class RemoteResource( val uri: String, val name: String ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/remote/RemoteResourcesStore.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.remote interface RemoteResourcesStore { /** * Get cached remote resources * * val resourcesStore: RemoteResourcesStore = ... * * resourcesStore.getResources( * name = RemoteResources.CUBE_LUT, * forceUpdate = true, * onDownloadRequest = { name -> * resourcesStore.downloadResources( * name = name, * onProgress = { progress -> * * }, * onFailure = { throwable -> * * } * ) * } * ) **/ suspend fun getResources( name: String, forceUpdate: Boolean, onDownloadRequest: suspend (name: String) -> RemoteResources? ): RemoteResources? /** * Download Resources from [ImageToolboxRemoteResources](https://github.com/T8RIN/ImageToolboxRemoteResources) * * val resourcesStore: RemoteResourcesStore = ... * * resourcesStore.downloadResources( * name = name, * onProgress = { progress -> * * }, * onFailure = { throwable -> * * } * ) */ suspend fun downloadResources( name: String, onProgress: (DownloadProgress) -> Unit, onFailure: (Throwable) -> Unit, downloadOnlyNewData: Boolean = false ): RemoteResources? } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/resource/ResourceManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.resource interface ResourceManager { fun getString(resId: Int): String fun getString( resId: Int, vararg formatArgs: Any ): String fun getStringLocalized( resId: Int, language: String ): String fun getStringLocalized( resId: Int, language: String, vararg formatArgs: Any ): String } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FileController.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.MetadataProvider import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import kotlinx.coroutines.flow.Flow interface FileController : ObjectSaver, MetadataProvider { val defaultSavingPath: String suspend fun save( saveTarget: SaveTarget, keepOriginalMetadata: Boolean, oneTimeSaveLocationUri: String? = null, ): SaveResult fun getSize(uri: String): Long? fun clearCache(onComplete: (Long) -> Unit = {}) fun getCacheSize(): Long suspend fun readBytes(uri: String): ByteArray suspend fun writeBytes( uri: String, block: suspend (Writeable) -> Unit, ): SaveResult suspend fun transferBytes( fromUri: String, toUri: String ): SaveResult suspend fun transferBytes( fromUri: String, to: Writeable ): SaveResult suspend fun writeMetadata( imageUri: String, metadata: Metadata? ) suspend fun listFilesInDirectory(treeUri: String): List fun listFilesInDirectoryAsFlow(treeUri: String): Flow companion object { fun FileController.toMetadataProvider(): MetadataProvider = object : MetadataProvider by this {} } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/FilenameCreator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget interface FilenameCreator { fun constructImageFilename( saveTarget: ImageSaveTarget, oneTimePrefix: String? = null, forceNotAddSizeInFilename: Boolean = false, pattern: String? = null ): String fun constructRandomFilename( extension: String, length: Int = 32 ): String fun getFilename(uri: String): String } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/KeepAliveService.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving import kotlinx.coroutines.CancellationException import kotlinx.coroutines.supervisorScope interface KeepAliveService { fun updateOrStart( title: String = "", description: String = "", progress: Float = PROGRESS_INDETERMINATE ) fun stop(removeNotification: Boolean = true) companion object { const val PROGRESS_NO_PROGRESS = -2f const val PROGRESS_INDETERMINATE = -1f } } fun KeepAliveService.updateProgress( title: String = "", done: Int, total: Int ) { updateOrStart( title = title, description = "$done / $total", progress = (done / total.toFloat()).takeIf { it > 0f } ?: KeepAliveService.PROGRESS_INDETERMINATE ) } suspend fun KeepAliveService.track( initial: KeepAliveService.() -> Unit = { updateOrStart() }, onCancel: () -> Unit = {}, onFailure: suspend (Throwable) -> Unit = {}, onComplete: KeepAliveService.(isSuccess: Boolean) -> Unit = { stop(true) }, action: suspend KeepAliveService.() -> R ): R? = supervisorScope { try { initial() action() } catch (e: CancellationException) { onComplete(false) onCancel() throw e } catch (e: Throwable) { onComplete(false) onFailure(e) null } finally { onComplete(true) } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/ObjectSaver.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving import kotlin.reflect.KClass interface ObjectSaver { suspend fun saveObject( key: String, value: O, ): Boolean suspend fun restoreObject( key: String, kClass: KClass, ): O? } suspend inline fun ObjectSaver.saveObject(value: O): Boolean = saveObject( key = O::class.simpleName.toString(), value = value ) suspend inline fun ObjectSaver.restoreObject(): O? = restoreObject( key = O::class.simpleName.toString(), kClass = O::class ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/RandomStringGenerator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving interface RandomStringGenerator { fun generate(length: Int): String } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/IoCloseable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving.io import kotlin.contracts.InvocationKind import kotlin.contracts.contract interface IoCloseable { fun close() } inline fun T.use(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } var exception: Throwable? = null try { return block(this) } catch (e: Throwable) { exception = e throw e } finally { closeFinally(exception) } } @SinceKotlin("1.1") @PublishedApi internal fun IoCloseable?.closeFinally(cause: Throwable?) = when { this == null -> {} cause == null -> close() else -> try { close() } catch (closeException: Throwable) { cause.addSuppressed(closeException) } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Readable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving.io interface Readable : IoCloseable { fun readBytes(): ByteArray fun copyTo(writeable: Writeable) } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/io/Writeable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving.io interface Writeable : IoCloseable { fun writeBytes(byteArray: ByteArray) } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FileSaveTarget.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving.model import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.model.MimeType data class FileSaveTarget( override val originalUri: String, override val filename: String, override val data: ByteArray, override val mimeType: MimeType.Single, override val extension: String, ) : SaveTarget { constructor( originalUri: String, filename: String, data: ByteArray, imageFormat: ImageFormat ) : this(originalUri, filename, data, imageFormat.mimeType, imageFormat.extension) override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as FileSaveTarget if (originalUri != other.originalUri) return false if (filename != other.filename) return false if (!data.contentEquals(other.data)) return false if (mimeType != other.mimeType) return false if (extension != other.extension) return false return true } override fun hashCode(): Int { var result = originalUri.hashCode() result = 31 * result + filename.hashCode() result = 31 * result + data.contentHashCode() result = 31 * result + mimeType.hashCode() result = 31 * result + extension.hashCode() return result } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/FilenamePattern.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("ConstPropertyName") package com.t8rin.imagetoolbox.core.domain.saving.model @JvmInline value class FilenamePattern( val value: String ) { override fun toString(): String = value fun upper() = FilenamePattern(value.uppercase()) fun hasUpper() = upperEntries.contains(this.upper()) companion object { val Prefix = FilenamePattern("\\p") val OriginalName = FilenamePattern("\\o") val Width = FilenamePattern("\\w") val Height = FilenamePattern("\\h") val Date = FilenamePattern("\\d") val Rand = FilenamePattern("\\r") val Sequence = FilenamePattern("\\c") val PresetInfo = FilenamePattern("\\i") val ScaleMode = FilenamePattern("\\m") val Suffix = FilenamePattern("\\s") val Extension = FilenamePattern("\\e") val PrefixUpper = FilenamePattern("\\p").upper() val OriginalNameUpper = FilenamePattern("\\o").upper() val DateUpper = FilenamePattern("\\d").upper() val PresetInfoUpper = FilenamePattern("\\i").upper() val ScaleModeUpper = FilenamePattern("\\m").upper() val SuffixUpper = FilenamePattern("\\s").upper() val ExtensionUpper = FilenamePattern("\\e").upper() val Default = "${Prefix}_$OriginalName($Width)x($Height)${Date}{yyyy-MM-dd_HH-mm-ss}_${Rand}{4}[${Sequence}]_${PresetInfo}_${ScaleMode}_${Suffix}.$Extension" val ForOriginal = "${Prefix}_$OriginalName($Width)x($Height)[${Sequence}]_${PresetInfo}_${ScaleMode}_${Suffix}.$Extension" fun String.replace( pattern: FilenamePattern, newValue: String, ignoreCase: Boolean = false ): String = replace( oldValue = pattern.value, newValue = newValue, ignoreCase = ignoreCase ) val entries by lazy { listOf( Prefix, OriginalName, Width, Height, Date, Rand, Sequence, PresetInfo, ScaleMode, Suffix, Extension ) } val upperEntries by lazy { listOf( PrefixUpper, OriginalNameUpper, DateUpper, PresetInfoUpper, ScaleModeUpper, SuffixUpper, ExtensionUpper ) } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/ImageSaveTarget.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving.model import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.model.MimeType data class ImageSaveTarget( val imageInfo: ImageInfo, override val originalUri: String, val sequenceNumber: Int?, val metadata: Metadata? = null, override val filename: String? = null, val imageFormat: ImageFormat = imageInfo.imageFormat, override val data: ByteArray, override val mimeType: MimeType.Single = imageFormat.mimeType, override val extension: String = imageFormat.extension, val readFromUriInsteadOfData: Boolean = false, val presetInfo: Preset? = null, val canSkipIfLarger: Boolean = false ) : SaveTarget { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as ImageSaveTarget if (imageInfo != other.imageInfo) return false if (originalUri != other.originalUri) return false if (sequenceNumber != other.sequenceNumber) return false if (metadata != other.metadata) return false if (filename != other.filename) return false if (imageFormat != other.imageFormat) return false if (!data.contentEquals(other.data)) return false if (mimeType != other.mimeType) return false if (extension != other.extension) return false return true } override fun hashCode(): Int { var result = imageInfo.hashCode() result = 31 * result + originalUri.hashCode() result = 31 * result + (sequenceNumber ?: 0) result = 31 * result + (metadata?.hashCode() ?: 0) result = 31 * result + (filename?.hashCode() ?: 0) result = 31 * result + imageFormat.hashCode() result = 31 * result + data.contentHashCode() result = 31 * result + mimeType.hashCode() result = 31 * result + extension.hashCode() return result } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving.model sealed class SaveResult( open val savingPath: String ) { data class Success( val message: String? = null, override val savingPath: String, val isOverwritten: Boolean = false, ) : SaveResult(savingPath) sealed class Error(open val throwable: Throwable) : SaveResult("") { data object MissingPermissions : Error(IllegalAccessException("MissingPermissions")) data class Exception( override val throwable: Throwable ) : Error(throwable) } data object Skipped : SaveResult("") fun onSuccess( action: () -> Unit ): SaveResult = apply { if (this is Success) action() } } fun List.onSuccess( action: () -> Unit ): List = apply { if (this.any { it is SaveResult.Success }) action() } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/saving/model/SaveTarget.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.saving.model import com.t8rin.imagetoolbox.core.domain.model.MimeType interface SaveTarget { val originalUri: String val data: ByteArray val filename: String? val mimeType: MimeType.Single val extension: String } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/ChainTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.transformation import com.t8rin.imagetoolbox.core.domain.model.IntegerSize interface ChainTransformation : Transformation { fun getTransformations(): List> override suspend fun transform( input: T, size: IntegerSize ): T = getTransformations().fold(input) { acc, filter -> filter.transform(acc, size) } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/GenericTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.transformation import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import kotlin.random.Random class GenericTransformation( val key: Any? = Random.nextInt(), val action: suspend (T, IntegerSize) -> T ) : Transformation { constructor( key: Any? = Random.nextInt(), action: suspend (T) -> T ) : this( key, { t, _ -> action(t) } ) override val cacheKey: String get() = (action to key).hashCode().toString() override suspend fun transform( input: T, size: IntegerSize ): T = action(input, size) } class EmptyTransformation : Transformation by GenericTransformation( key = Random.nextInt(), action = { i -> i } ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/transformation/Transformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.transformation import com.t8rin.imagetoolbox.core.domain.model.IntegerSize interface Transformation { /** * The unique cache key for this transformation. * * The key is added to the image request's memory cache key and should contain any params that * are part of this transformation (e.g. size, scale, color, radius, etc.). */ val cacheKey: String /** * Apply the transformation to [input] and return the transformed [T]. * * @param input The input [T] to transform. * Its config will always be ARGB_8888 or RGBA_F16. * @param size The size of the image request. * @return The transformed [T]. */ suspend fun transform( input: T, size: IntegerSize ): T } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ByteUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils import java.text.CharacterIterator import java.text.StringCharacterIterator import java.util.Locale fun humanFileSize( bytes: Long ): String { var tempBytes = bytes if (-1024 < tempBytes && tempBytes < 1024) { return "$tempBytes B" } val ci: CharacterIterator = StringCharacterIterator("kMGTPE") while (tempBytes <= -999950 || tempBytes >= 999950) { tempBytes /= 1024 ci.next() } return java.lang.String.format( Locale.getDefault(), "%.1f %cB", tempBytes / 1024.0, ci.current() ).replace(",0", "") } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Delegates.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils import kotlin.reflect.KProperty interface ReadWriteDelegate : ReadOnlyDelegate { fun set(value: V) operator fun setValue(thisRef: Any, property: KProperty<*>, value: V) = set(value) } interface ReadOnlyDelegate { fun get(): V operator fun getValue(thisRef: Any, property: KProperty<*>): V = get() } inline fun ReadWriteDelegate.update( transform: (T) -> T ): T = run { transform(get()).also(::set) } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/FileMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils @JvmInline value class FileMode(val mode: String) { operator fun plus(mode: FileMode) = FileMode(this.mode + mode.mode) override fun toString(): String = this.mode companion object { val Read = FileMode("r") val Write = FileMode("w") val Truncate = FileMode("t") val ReadWrite = Read + Write val WriteTruncate = Write + Truncate } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Flavor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("SimplifyBooleanWithConstants", "KotlinConstantConditions") package com.t8rin.imagetoolbox.core.domain.utils import com.t8rin.imagetoolbox.core.resources.BuildConfig object Flavor { fun isFoss() = BuildConfig.FLAVOR == "foss" } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/IntUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils fun Int.toBoolean() = this != 0 fun Boolean.toInt() = if (this) 1 else 0 ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/KotlinUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.domain.utils import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.transform import java.io.Closeable import kotlin.reflect.KClass inline fun T?.notNullAnd( predicate: (T) -> Boolean ): Boolean = if (this != null) predicate(this) else false fun CharSequence.isBase64() = isNotEmpty() && BASE64_PATTERN.matches(this) fun CharSequence.trimToBase64() = toString().filter { !it.isWhitespace() }.substringAfter(",") private val BASE64_PATTERN = Regex( "^(?=(.{4})*$)[A-Za-z0-9+/]*={0,2}$" ) inline fun Any.cast(): R = this as R inline fun T.autoCast(block: T.() -> Any): R = block() as R inline fun Any.safeCast(): R? = this as? R inline fun Any?.ifCasts(action: (R) -> Unit) = (this as? R)?.let(action) inline operator fun CharSequence.times( count: Int ): CharSequence = repeat(count.coerceAtLeast(0)) suspend inline fun T.runSuspendCatching(block: T.() -> R): Result { currentCoroutineContext().ensureActive() return runCatching(block).onFailure { println("ERROR: $it") currentCoroutineContext().ensureActive() } } inline fun KClass.simpleName() = simpleName!! inline fun Boolean.then(value: T): T? = if (this) value else null inline fun tryAll( vararg actions: () -> Unit ): Boolean { for (action in actions) { runCatching { action() }.onSuccess { return true } } return false } inline fun Result.onResult( action: (isSuccess: Boolean) -> Unit ): Result = apply { action(isSuccess) } fun Flow.throttleLatest(delayMillis: Long): Flow = this .conflate() .transform { emit(it) delay(delayMillis) } inline fun T.applyUse(block: T.() -> R): R = use(block) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ListUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.domain.utils import kotlin.reflect.KClass object ListUtils { fun List.nearestFor(item: T): T? { return if (isEmpty()) null else if (size == 1) first() else { val curIndex = indexOf(item) if (curIndex - 1 >= 0) { get(curIndex - 1) } else if (curIndex + 1 <= lastIndex) { get(curIndex + 1) } else { null } } } inline fun Iterable.filterIsNotInstance(): List { val destination = mutableListOf() for (element in this) if (element !is R) destination.add(element) return destination } inline fun Iterable.filterIsNotInstance(vararg types: KClass<*>): List { val destination = mutableListOf() for (element in this) if (types.all { !it.isInstance(element) }) destination.add(element) return destination } fun Iterable.toggle(item: T): List = run { if (item in this) this - item else this + item } fun Iterable.toggleByClass(item: T): List { val clazz = item::class val hasClass = any { clazz.isInstance(it) } return if (hasClass) filterNot { clazz.isInstance(it) } else this + item } inline fun Iterable.replaceAt(index: Int, transform: (T) -> T): List = toMutableList().apply { this[index] = transform(this[index]) } fun Set.toggle(item: T): Set = run { if (item in this) this - item else this + item } inline fun Iterable.firstOfType(): R? = firstOrNull { it is R } as? R operator fun List.component6(): E = get(5) operator fun List.component7(): E = get(6) operator fun List.component8(): E = get(7) operator fun List.component9(): E = get(8) operator fun List.component10(): E = get(9) inline fun List.leftFrom(index: Int): E = getOrNull(index - 1) ?: get(lastIndex) inline fun List.rightFrom(index: Int): E = getOrNull(index + 1) ?: get(0) inline fun > List.sortedByKey( descending: Boolean, crossinline selector: (E) -> T? ): List = if (descending) { sortedByDescending { selector(it) } } else { sortedBy { selector(it) } } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/ProgressInputStream.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils import java.io.InputStream fun InputStream.withProgress( total: Long, onProgress: (Float) -> Unit ): InputStream = ProgressInputStream( source = this, total = total, onProgress = onProgress ) private class ProgressInputStream( private val source: InputStream, private val total: Long, private val onProgress: (Float) -> Unit ) : InputStream() { private var readBytes = 0L override fun read(): Int { val r = source.read() if (r != -1) { readBytes++ report() } return r } override fun read(b: ByteArray, off: Int, len: Int): Int { val r = source.read(b, off, len) if (r > 0) { readBytes += r report() } return r } private fun report() { if (total > 0) { onProgress(readBytes.toFloat() / total) } } override fun close() = source.close() } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Quad.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.domain.utils data class Quad( val first: A, val second: B, val third: C, val fourth: D ) inline infix fun Pair.qto( other: Pair ) = Quad( first = first, second = second, third = other.first, fourth = other.second ) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/Rounding.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils import kotlin.math.pow import kotlin.math.roundToInt const val NEAREST_ODD_ROUNDING = -3 fun roundToNearestOdd( number: Float ): Float = number.roundToInt().let { if (it % 2 != 0) it else it + 1 }.toFloat() fun Float.roundTo( digits: Int ): Float = if (digits == NEAREST_ODD_ROUNDING) roundToNearestOdd(this) else (this * 10f.pow(digits)).roundToInt() / (10f.pow(digits)) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/SmartJob.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils import kotlinx.coroutines.Job import kotlinx.coroutines.cancelChildren /** * [Job] delegate which automatically cancels previous instance after setting new value, * @param onCancelled called when previous job is about to cancel **/ private class SmartJobImpl( private val onCancelled: (Job) -> Unit = {} ) : ReadWriteDelegate { private var job: Job? = null override fun get(): Job? = job override fun set(value: Job?) { job?.apply { onCancelled(this) cancelChildren() cancel() } job = value } } /** * [Job] delegate which automatically cancels previous instance after setting new value, * @param onCancelled called when previous job is about to cancel **/ fun smartJob( onCancelled: (Job) -> Unit = {} ): ReadWriteDelegate = SmartJobImpl(onCancelled) ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/StringUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.core.domain.utils fun String.trimTrailingZero(): String { val value = this return if (value.isNotEmpty()) { if (value.indexOf(".") < 0) { value } else { value.replace("0*$".toRegex(), "").replace("\\.$".toRegex(), "") } } else { value } } /** * Returns a minimal set of characters that have to be removed from (or added to) the respective * strings to make the strings equal. */ fun String.differenceFrom( other: String ): Pair = diffHelper( a = this, b = other, lookup = HashMap() ) /** * Recursively compute a minimal set of characters while remembering already computed substrings. * Runs in O(n^2). */ private fun diffHelper( a: String, b: String, lookup: MutableMap> ): Pair { val key = (a.length.toLong()) shl 32 or b.length.toLong() if (!lookup.containsKey(key)) { val value: Pair = if (a.isEmpty() || b.isEmpty()) { a to b } else if (a[0] == b[0]) { diffHelper( a = a.substring(1), b = b.substring(1), lookup = lookup ) } else { val aa = diffHelper(a.substring(1), b, lookup) val bb = diffHelper(a, b.substring(1), lookup) if (aa.first.length + aa.second.length < bb.first.length + bb.second.length) { (b[0].toString() + bb.second) to aa.second } else { bb.first to (b[0].toString() + bb.second) } } lookup[key] = value } return lookup.getOrElse(key) { "" to "" } } ================================================ FILE: core/domain/src/main/kotlin/com/t8rin/imagetoolbox/core/domain/utils/TimeUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.domain.utils import java.text.SimpleDateFormat import java.util.Date import java.util.Locale fun timestamp( format: String? = "yyyy-MM-dd_HH-mm-ss", date: Long? = null ): String = runCatching { val dateObject = date?.let(::Date) ?: Date() format?.let { SimpleDateFormat(format, Locale.getDefault()).format(dateObject) } ?: dateObject.time.toString() }.getOrNull() ?: Date().time.toString() ================================================ FILE: core/filters/.gitignore ================================================ /build ================================================ FILE: core/filters/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) alias(libs.plugins.image.toolbox.hilt) } android.namespace = "com.t8rin.imagetoolbox.core.filters" dependencies { implementation(projects.core.domain) implementation(projects.core.ui) implementation(projects.core.resources) implementation(projects.core.settings) implementation(projects.core.utils) implementation(projects.core.ksp) ksp(projects.core.ksp) implementation(projects.lib.curves) implementation(projects.lib.ascii) implementation(projects.lib.neuralTools) implementation(libs.trickle) } ================================================ FILE: core/filters/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/data/SideFadePaint.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.data import android.graphics.Bitmap import android.graphics.LinearGradient import android.graphics.Paint import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Shader import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide fun FadeSide.getPaint( bmp: Bitmap, length: Int, strength: Float ): Paint { val paint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) } val w = bmp.width.toFloat() val h = bmp.height.toFloat() val l = length.toFloat() val gradientStart = l * (1f - strength.coerceAtLeast(0.001f)) val (x1, y1, x2, y2) = when (this) { FadeSide.Start -> { arrayOf( gradientStart, h / 2, l, h / 2 ) } FadeSide.Top -> { arrayOf( w / 2, gradientStart, w / 2, l ) } FadeSide.End -> { arrayOf( w - gradientStart, h / 2, w - l, h / 2 ) } FadeSide.Bottom -> { arrayOf( w / 2, h - gradientStart, w / 2, h - l ) } } paint.shader = LinearGradient( x1, y1, x2, y2, intArrayOf( Color.Black.copy(alpha = 0f).toArgb(), Color.Black.toArgb() ), floatArrayOf( 0f, 1f ), Shader.TileMode.CLAMP ) return paint } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterParamsInteractor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import kotlinx.coroutines.flow.Flow interface FilterParamsInteractor { fun getFavoriteFilters(): Flow>> suspend fun toggleFavorite(filter: Filter<*>) suspend fun addTemplateFilter(templateFilter: TemplateFilter) fun getTemplateFilters(): Flow> suspend fun addTemplateFilterFromString( string: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) suspend fun convertTemplateFilterToString(templateFilter: TemplateFilter): String suspend fun removeTemplateFilter(templateFilter: TemplateFilter) suspend fun addTemplateFilterFromUri( uri: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) fun isValidTemplateFilter(string: String): Boolean suspend fun reorderFavoriteFilters(newOrder: List>) fun getFilterPreviewModel(): Flow fun getCanSetDynamicFilterPreview(): Flow suspend fun setCanSetDynamicFilterPreview(value: Boolean) suspend fun setFilterPreviewModel(uri: String) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/FilterProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter interface FilterProvider { fun filterToTransformation( filter: Filter<*> ): Transformation } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.VisibilityOwner import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PaletteTransferSpace import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PolarCoordinatesType import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.SpotHealMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.domain.model.params.ArcParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.AsciiParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.BilaterialBlurParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.BloomParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ChannelMixParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.CropOrPerspectiveParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.GlitchParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.KaleidoscopeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearGaussianParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearTiltShiftParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.PinchParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.RadialTiltShiftParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.RubberStampParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SideFadeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SmearParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SparkleParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ToneCurvesParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterParams interface Filter : VisibilityOwner { val value: Value interface BilaterialBlur : Filter interface BlackAndWhite : SimpleFilter interface BoxBlur : FloatFilter interface Brightness : FloatFilter interface BulgeDistortion : PairFloatFilter interface CGAColorSpace : SimpleFilter interface ColorBalance : FloatArrayFilter interface ColorOverlay : ColorValueFilter interface ColorMatrix4x4 : FloatArrayFilter interface Contrast : FloatFilter interface Convolution3x3 : FloatArrayFilter interface Crosshatch : PairFloatFilter interface Dilation : FloatBooleanFilter interface Emboss : FloatFilter interface Exposure : FloatFilter interface FalseColor : PairFilter interface FastBlur : PairFloatFilter interface Gamma : FloatFilter interface GaussianBlur : TripleFilter interface GlassSphereRefraction : PairFloatFilter interface Halftone : FloatFilter interface Haze : PairFloatFilter interface HighlightsAndShadows : FloatFilter interface Hue : FloatFilter interface Kuwahara : FloatFilter interface Laplacian : SimpleFilter interface Lookup : FloatFilter interface Monochrome : FloatColorModelFilter interface Negative : SimpleFilter interface NonMaximumSuppression : SimpleFilter interface Opacity : FloatFilter interface Posterize : FloatFilter interface RGB : ColorValueFilter interface Saturation : FloatBooleanFilter interface Sepia : SimpleFilter interface Sharpen : FloatFilter interface Sketch : FloatFilter interface SmoothToon : TripleFloatFilter interface SobelEdgeDetection : FloatFilter interface Solarize : FloatFilter interface SphereRefraction : PairFloatFilter interface StackBlur : PairFloatFilter interface SwirlDistortion : QuadFloatFilter interface Toon : PairFloatFilter interface Vibrance : FloatFilter interface Vignette : TripleFilter interface WeakPixel : SimpleFilter interface WhiteBalance : PairFloatFilter interface ZoomBlur : TripleFloatFilter interface Pixelation : FloatFilter interface EnhancedPixelation : FloatFilter interface StrokePixelation : FloatColorModelFilter interface CirclePixelation : FloatFilter interface DiamondPixelation : FloatFilter interface EnhancedCirclePixelation : FloatFilter interface EnhancedDiamondPixelation : FloatFilter interface ReplaceColor : TripleFilter interface RemoveColor : FloatColorModelFilter interface SideFade : Filter interface BayerTwoDithering : FloatBooleanFilter interface BayerThreeDithering : FloatBooleanFilter interface BayerFourDithering : FloatBooleanFilter interface BayerEightDithering : FloatBooleanFilter interface RandomDithering : FloatBooleanFilter interface FloydSteinbergDithering : FloatBooleanFilter interface JarvisJudiceNinkeDithering : FloatBooleanFilter interface SierraDithering : FloatBooleanFilter interface TwoRowSierraDithering : FloatBooleanFilter interface SierraLiteDithering : FloatBooleanFilter interface AtkinsonDithering : FloatBooleanFilter interface StuckiDithering : FloatBooleanFilter interface BurkesDithering : FloatBooleanFilter interface FalseFloydSteinbergDithering : FloatBooleanFilter interface LeftToRightDithering : FloatBooleanFilter interface SimpleThresholdDithering : FloatBooleanFilter interface MedianBlur : FloatFilter interface NativeStackBlur : FloatFilter interface RadialTiltShift : Filter interface Glitch : TripleFloatFilter interface Noise : FloatFilter interface Anaglyph : FloatFilter interface EnhancedGlitch : Filter interface TentBlur : FloatFilter interface Erode : FloatBooleanFilter interface AnisotropicDiffusion : TripleFloatFilter interface HorizontalWindStagger : TripleFilter interface FastBilaterialBlur : TripleFloatFilter interface PoissonBlur : FloatFilter interface LogarithmicToneMapping : FloatFilter interface AcesFilmicToneMapping : FloatFilter interface Crystallize : FloatColorModelFilter interface FractalGlass : PairFloatFilter interface Marble : TripleFloatFilter interface Oil : PairFloatFilter interface WaterEffect : Filter interface HableFilmicToneMapping : FloatFilter interface AcesHillToneMapping : FloatFilter interface HejlBurgessToneMapping : FloatFilter interface PerlinDistortion : TripleFloatFilter interface Grayscale : TripleFloatFilter interface Dehaze : PairFloatFilter interface Threshold : FloatFilter interface ColorMatrix3x3 : FloatArrayFilter interface Polaroid : SimpleFilter interface Cool : SimpleFilter interface Warm : SimpleFilter interface NightVision : SimpleFilter interface CodaChrome : SimpleFilter interface Browni : SimpleFilter interface Vintage : SimpleFilter interface Protonomaly : SimpleFilter interface Deutaromaly : SimpleFilter interface Tritonomaly : SimpleFilter interface Protanopia : SimpleFilter interface Deutaronotopia : SimpleFilter interface Tritanopia : SimpleFilter interface Achromatopsia : SimpleFilter interface Achromatomaly : SimpleFilter interface Grain : FloatFilter interface Unsharp : FloatFilter interface Pastel : SimpleFilter interface OrangeHaze : SimpleFilter interface PinkDream : SimpleFilter interface GoldenHour : SimpleFilter interface HotSummer : SimpleFilter interface PurpleMist : SimpleFilter interface Sunrise : SimpleFilter interface ColorfulSwirl : SimpleFilter interface SoftSpringLight : SimpleFilter interface AutumnTones : SimpleFilter interface LavenderDream : SimpleFilter interface Cyberpunk : SimpleFilter interface LemonadeLight : SimpleFilter interface SpectralFire : SimpleFilter interface NightMagic : SimpleFilter interface FantasyLandscape : SimpleFilter interface ColorExplosion : SimpleFilter interface ElectricGradient : SimpleFilter interface CaramelDarkness : SimpleFilter interface FuturisticGradient : SimpleFilter interface GreenSun : SimpleFilter interface RainbowWorld : SimpleFilter interface DeepPurple : SimpleFilter interface SpacePortal : SimpleFilter interface RedSwirl : SimpleFilter interface DigitalCode : SimpleFilter interface Bokeh : PairFloatFilter interface Neon : TripleFilter interface OldTv : SimpleFilter interface ShuffleBlur : PairFloatFilter interface Mobius : TripleFloatFilter interface Uchimura : FloatFilter interface Aldridge : PairFloatFilter interface Drago : PairFloatFilter interface ColorAnomaly : FloatFilter interface Quantizier : FloatFilter interface RingBlur : FloatFilter interface CrossBlur : FloatFilter interface CircleBlur : FloatFilter interface StarBlur : FloatFilter interface LinearTiltShift : Filter interface EnhancedZoomBlur : Filter interface Convex : FloatFilter interface FastGaussianBlur2D : PairFilter interface FastGaussianBlur3D : PairFilter interface FastGaussianBlur4D : FloatFilter interface EqualizeHistogram : SimpleFilter interface EqualizeHistogramHSV : FloatFilter interface EqualizeHistogramPixelation : PairFloatFilter interface EqualizeHistogramAdaptive : PairFloatFilter interface EqualizeHistogramAdaptiveLUV : TripleFloatFilter interface EqualizeHistogramAdaptiveLAB : TripleFloatFilter interface Clahe : TripleFloatFilter interface ClaheLUV : ClaheValueFilter interface ClaheLAB : ClaheValueFilter interface CropToContent : FloatColorModelFilter interface ClaheHSL : ClaheValueFilter interface ClaheHSV : ClaheValueFilter interface EqualizeHistogramAdaptiveHSV : TripleFloatFilter interface EqualizeHistogramAdaptiveHSL : TripleFloatFilter interface LinearBoxBlur : PairFilter interface LinearTentBlur : PairFilter interface LinearGaussianBoxBlur : PairFilter interface LinearStackBlur : PairFilter interface GaussianBoxBlur : FloatFilter interface LinearFastGaussianBlurNext : TripleFilter interface LinearFastGaussianBlur : TripleFilter interface LinearGaussianBlur : Filter interface LowPoly : FloatBooleanFilter interface SandPainting : TripleFilter interface PaletteTransfer : FileImageFilter interface EnhancedOil : FloatFilter interface SimpleOldTv : SimpleFilter interface HDR : SimpleFilter interface SimpleSketch : SimpleFilter interface Gotham : SimpleFilter interface ColorPoster : FloatColorModelFilter interface TriTone : TripleFilter interface ClaheOklch : ClaheValueFilter interface ClaheJzazbz : ClaheValueFilter interface ClaheOklab : ClaheValueFilter interface PolkaDot : TripleFilter interface Clustered2x2Dithering : BooleanFilter interface Clustered4x4Dithering : BooleanFilter interface Clustered8x8Dithering : BooleanFilter interface YililomaDithering : BooleanFilter interface LUT512x512 : FileImageFilter interface Amatorka : FloatFilter interface MissEtikate : FloatFilter interface SoftElegance : FloatFilter interface SoftEleganceVariant : FloatFilter interface PaletteTransferVariant : TripleFilter interface CubeLut : FileFilter interface BleachBypass : FloatFilter interface Candlelight : FloatFilter interface DropBlues : FloatFilter interface EdgyAmber : FloatFilter interface FallColors : FloatFilter interface FilmStock50 : FloatFilter interface FoggyNight : FloatFilter interface Kodak : FloatFilter interface PopArt : TripleFilter interface Celluloid : FloatFilter interface Coffee : FloatFilter interface GoldenForest : FloatFilter interface Greenish : FloatFilter interface RetroYellow : FloatFilter interface AutoCrop : FloatFilter interface SpotHeal : PairFilter interface Opening : FloatBooleanFilter interface Closing : FloatBooleanFilter interface MorphologicalGradient : FloatBooleanFilter interface TopHat : FloatBooleanFilter interface BlackHat : FloatBooleanFilter interface Canny : PairFloatFilter interface SobelSimple : SimpleFilter interface LaplacianSimple : SimpleFilter interface MotionBlur : TripleFilter interface AutoRemoveRedEyes : FloatFilter interface ToneCurves : Filter interface Mirror : PairFilter interface Kaleidoscope : Filter interface ChannelMix : Filter interface ColorHalftone : QuadFloatFilter interface Contour : QuadFilter interface VoronoiCrystallize : Filter interface Despeckle : SimpleFilter interface Diffuse : FloatFilter interface DoG : PairFloatFilter interface Equalize : SimpleFilter interface Glow : FloatFilter interface Offset : PairFloatFilter interface Pinch : Filter interface Pointillize : Filter interface PolarCoordinates : Filter interface ReduceNoise : SimpleFilter interface SimpleSolarize : SimpleFilter interface Weave : QuadFloatFilter interface Twirl : QuadFloatFilter interface RubberStamp : Filter interface Smear : Filter interface SphereLensDistortion : QuadFloatFilter interface Arc : Filter interface Sparkle : Filter interface Ascii : Filter interface Moire : SimpleFilter interface Autumn : SimpleFilter interface Bone : SimpleFilter interface Jet : SimpleFilter interface Winter : SimpleFilter interface Rainbow : SimpleFilter interface Ocean : SimpleFilter interface Summer : SimpleFilter interface Spring : SimpleFilter interface CoolVariant : SimpleFilter interface Hsv : SimpleFilter interface Pink : SimpleFilter interface Hot : SimpleFilter interface Parula : SimpleFilter interface Magma : SimpleFilter interface Inferno : SimpleFilter interface Plasma : SimpleFilter interface Viridis : SimpleFilter interface Cividis : SimpleFilter interface Twilight : SimpleFilter interface TwilightShifted : SimpleFilter interface AutoPerspective : SimpleFilter interface Deskew : FloatBooleanFilter interface CropOrPerspective : Filter interface Turbo : SimpleFilter interface DeepGreen : SimpleFilter interface LensCorrection : FileFilter interface SeamCarving : Filter interface ErrorLevelAnalysis : FloatFilter interface LuminanceGradient : SimpleFilter interface AverageDistance : SimpleFilter interface CopyMoveDetection : PairFloatFilter interface SimpleWeavePixelation : FloatFilter interface StaggeredPixelation : FloatFilter interface CrossPixelation : FloatFilter interface MicroMacroPixelation : FloatFilter interface OrbitalPixelation : FloatFilter interface VortexPixelation : FloatFilter interface PulseGridPixelation : FloatFilter interface NucleusPixelation : FloatFilter interface RadialWeavePixelation : FloatFilter interface BorderFrame : TripleFilter interface GlitchVariant : TripleFloatFilter interface VHS : PairFloatFilter interface BlockGlitch : PairFloatFilter interface CrtCurvature : TripleFloatFilter interface PixelMelt : PairFloatFilter interface Bloom : Filter } interface SimpleFilter : Filter interface PairFilter : Filter> interface TripleFilter : Filter> interface QuadFilter : Filter> interface FloatFilter : Filter interface PairFloatFilter : PairFilter interface TripleFloatFilter : TripleFilter interface QuadFloatFilter : QuadFilter interface FloatArrayFilter : Filter interface FileFilter : PairFilter interface FileImageFilter : PairFilter interface FloatBooleanFilter : PairFilter interface FloatColorModelFilter : PairFilter interface BooleanFilter : Filter interface ClaheValueFilter : Filter interface ColorValueFilter : WrapperFilter interface WrapperFilter : Filter> private typealias Image = ImageModel private typealias File = FileModel private typealias Color = ColorModel ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterParam.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model data class FilterParam( val title: Int? = null, val valueRange: ClosedFloatingPointRange, val roundTo: Int = 2 ) ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model import java.lang.reflect.Proxy inline fun > createFilter( value: V ): T = Proxy.newProxyInstance( T::class.java.classLoader, arrayOf(T::class.java) ) { _, method, _ -> when { method.name == "getValue" -> value method.name.contains("isVisible") -> true else -> null } } as T ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/FilterValueWrapper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model data class FilterValueWrapper( val wrapped: V ) fun T.wrap(): FilterValueWrapper = FilterValueWrapper(this) ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/TemplateFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model data class TemplateFilter( val name: String, val filters: List> ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is TemplateFilter) return false if (name != other.name) return false if (filters.size != other.filters.size) return false val filters1 = filters.sortedBy { it::class.simpleName } val filters2 = other.filters.sortedBy { it::class.simpleName } filters1.forEachIndexed { index, filter1 -> val filter2 = filters2[index] val filter1Name = filter1::class.simpleName val filter2Name = filter2::class.simpleName if (filter1Name != filter2Name) return false val v1 = filter1.value val v2 = filter2.value when { v1 is FloatArray && v2 is FloatArray -> { if (!v1.contentEquals(v2)) return false } v1 is IntArray && v2 is IntArray -> { if (!v1.contentEquals(v2)) return false } v1 is DoubleArray && v2 is DoubleArray -> { if (!v1.contentEquals(v2)) return false } else -> if (v1 != v2) return false } } return true } override fun hashCode(): Int { var result = name.hashCode() filters.forEach { filter -> result = 31 * result + filter::class.simpleName.hashCode() val v = filter.value result = 31 * result + when (v) { is FloatArray -> v.contentHashCode() is IntArray -> v.contentHashCode() is DoubleArray -> v.contentHashCode() else -> v.hashCode() } } return result } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/BlurEdgeMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class BlurEdgeMode { Clamp, Wrap, Reflect, Reflect101 } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/ColorMapType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class ColorMapType { AUTUMN, BONE, JET, WINTER, RAINBOW, OCEAN, SUMMER, SPRING, COOL, HSV, PINK, HOT, PARULA, MAGMA, INFERNO, PLASMA, VIRIDIS, CIVIDIS, TWILIGHT, TWILIGHT_SHIFTED, TURBO, DEEPGREEN } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/FadeSide.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class FadeSide { Start, End, Top, Bottom } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/MirrorSide.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class MirrorSide { LeftToRight, RightToLeft, TopToBottom, BottomToTop } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PaletteTransferSpace.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class PaletteTransferSpace { OKLAB, LAB, LUV, LALPHABETA } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PolarCoordinatesType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class PolarCoordinatesType { /** * Convert from rectangular to polar coordinates. */ RECT_TO_POLAR, /** * Convert from polar to rectangular coordinates. */ POLAR_TO_RECT, /** * Invert the image in a circle. */ INVERT_IN_CIRCLE; } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/PopArtBlendingMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class PopArtBlendingMode { MULTIPLY, COLOR_BURN, SOFT_LIGHT, HSL_COLOR, HSL_HUE, DIFFERENCE } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/SpotHealMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class SpotHealMode { OpenCV, LaMa } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/enums/TransferFunc.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.enums enum class TransferFunc { SRGB, REC709, GAMMA2P2, GAMMA2P8 } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ArcParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class ArcParams( val radius: Float, val height: Float, val angle: Float, val spreadAngle: Float, val centreX: Float, val centreY: Float, ) { companion object { val Default = ArcParams( radius = 0.2f, height = 0.3f, angle = 0f, spreadAngle = 360f, centreX = 0.5f, centreY = 0.5f ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/AsciiParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.ascii.Gradient import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.toColorModel import com.t8rin.imagetoolbox.core.settings.domain.model.FontType data class AsciiParams( val gradient: String, val fontSize: Float, val backgroundColor: ColorModel, val isGrayscale: Boolean, val font: FontType? ) { companion object { val Default = AsciiParams( gradient = Gradient.OLD.value, fontSize = 10f, backgroundColor = Color.Black.toArgb().toColorModel(), isGrayscale = false, font = null ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BilaterialBlurParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode data class BilaterialBlurParams( val radius: Int, val spatialSigma: Float, val rangeSigma: Float, val edgeMode: BlurEdgeMode, ) { companion object { val Default by lazy { BilaterialBlurParams( spatialSigma = 10f, rangeSigma = 3f, edgeMode = BlurEdgeMode.Reflect101, radius = 12, ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/BloomParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class BloomParams( val threshold: Float, val intensity: Float, val radius: Int, val softKnee: Float, val exposure: Float, val gamma: Float ) { companion object { val Default = BloomParams( threshold = 0.6f, intensity = 1.5f, radius = 25, softKnee = 0.5f, exposure = 0.02f, gamma = 1.1f ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ChannelMixParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class ChannelMixParams( val blueGreen: Int, val redBlue: Int, val greenRed: Int, val intoR: Int, val intoG: Int, val intoB: Int ) { companion object { val Default = ChannelMixParams( blueGreen = 255, redBlue = 255, greenRed = 255, intoR = 255, intoG = 255, intoB = 127 ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ClaheParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class ClaheParams( val threshold: Float, val gridSizeHorizontal: Int, val gridSizeVertical: Int, val binsCount: Int ) { companion object { val Default by lazy { ClaheParams( threshold = 0.5f, gridSizeHorizontal = 8, gridSizeVertical = 8, binsCount = 128 ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/CropOrPerspectiveParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class CropOrPerspectiveParams( val topLeft: FloatPair, val topRight: FloatPair, val bottomLeft: FloatPair, val bottomRight: FloatPair, val isAbsolute: Boolean = false ) { companion object { val Default by lazy { CropOrPerspectiveParams( topLeft = Pair(0.1f, 0.0f), topRight = Pair(1.0f, 0.0f), bottomRight = Pair(0.9f, 1.0f), bottomLeft = Pair(0.0f, 1.0f), isAbsolute = false ) } } } typealias FloatPair = Pair ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/EnhancedZoomBlurParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class EnhancedZoomBlurParams( val radius: Int, val sigma: Float, val centerX: Float, val centerY: Float, val strength: Float, val angle: Float, ) { companion object { val Default by lazy { EnhancedZoomBlurParams( radius = 25, sigma = 5f, centerX = 0.5f, centerY = 0.5f, strength = 1f, angle = 135f ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/GlitchParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class GlitchParams( val channelsShiftX: Float = -0.08f, val channelsShiftY: Float = -0.08f, val corruptionSize: Float = 0.01f, val corruptionCount: Int = 60, val corruptionShiftX: Float = -0.05f, val corruptionShiftY: Float = 0.0f, ) ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/KaleidoscopeParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class KaleidoscopeParams( val angle: Float, val angle2: Float, val centreX: Float, val centreY: Float, val sides: Int, val radius: Float, ) { companion object { val Default = KaleidoscopeParams( angle = 0f, angle2 = 0f, centreX = 0.5f, centreY = 0.5f, sides = 5, radius = 0f, ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearGaussianParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc data class LinearGaussianParams( val kernelSize: Int, val sigma: Float, val edgeMode: BlurEdgeMode, val transferFunction: TransferFunc ) { companion object { val Default by lazy { LinearGaussianParams( kernelSize = 25, sigma = 10f, edgeMode = BlurEdgeMode.Reflect101, transferFunction = TransferFunc.SRGB ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/LinearTiltShiftParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class LinearTiltShiftParams( val blurRadius: Float, val sigma: Float, val anchorX: Float, val anchorY: Float, val holeRadius: Float, val angle: Float ) { companion object { val Default by lazy { LinearTiltShiftParams( blurRadius = 25f, sigma = 10f, anchorX = 0.5f, anchorY = 0.5f, holeRadius = 0.2f, angle = 45f ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/PinchParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class PinchParams( val angle: Float, val centreX: Float, val centreY: Float, val radius: Float, val amount: Float ) { companion object { val Default = PinchParams( angle = 15f, centreX = 0.5f, centreY = 0.5f, radius = 1f, amount = 0.5f ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RadialTiltShiftParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class RadialTiltShiftParams( val blurRadius: Float, val sigma: Float, val anchorX: Float, val anchorY: Float, val holeRadius: Float ) { companion object { val Default by lazy { RadialTiltShiftParams( blurRadius = 25f, sigma = 10f, anchorX = 0.5f, anchorY = 0.5f, holeRadius = 0.2f ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/RubberStampParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.toColorModel data class RubberStampParams( val threshold: Float, val softness: Float, val radius: Float, val firstColor: ColorModel, val secondColor: ColorModel ) { companion object { val Default = RubberStampParams( threshold = 0.3f, softness = 0.1f, radius = 0.05f, firstColor = Color(0xff3FA08F).toArgb().toColorModel(), secondColor = Color(0xff003027).toArgb().toColorModel(), ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SideFadeParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import androidx.annotation.FloatRange import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide sealed class SideFadeParams( open val side: FadeSide ) { data class Relative( override val side: FadeSide, @FloatRange(0.0, 1.0) val scale: Float ) : SideFadeParams(side) data class Absolute( override val side: FadeSide, val size: Int, val strength: Float = 1f ) : SideFadeParams(side) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SmearParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class SmearParams( val angle: Float, val density: Float, val mix: Float, val distance: Int, val shape: Int ) { companion object { val Default = SmearParams( angle = 45f, density = 1f, mix = 1f, distance = 100, shape = 0 ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/SparkleParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.toColorModel data class SparkleParams( val amount: Int, val rays: Int, val radius: Float, val randomness: Int, val centreX: Float, val centreY: Float, val color: ColorModel ) { companion object { val Default = SparkleParams( amount = 50, rays = 50, radius = 0.3f, randomness = 25, centreX = 0.5f, centreY = 0.5f, color = Color.White.toArgb().toColorModel() ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/ToneCurvesParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import com.t8rin.curves.ImageCurvesEditorState data class ToneCurvesParams( val controlPoints: List> ) { companion object { val Default by lazy { ToneCurvesParams(ImageCurvesEditorState.Default.controlPoints) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/VoronoiCrystallizeParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel data class VoronoiCrystallizeParams( val borderThickness: Float, val scale: Float, val randomness: Float, val shape: Int, val turbulence: Float, val angle: Float, val stretch: Float, val amount: Float, val color: ColorModel, ) { companion object { val Default = VoronoiCrystallizeParams( borderThickness = 0.4f, color = Color(0xff000000).toModel(), scale = 32f, randomness = 5f, shape = 0, turbulence = 1f, angle = 0f, stretch = 1f, amount = 1f ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/domain/model/params/WaterParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.domain.model.params data class WaterParams( val fractionSize: Float = 0.2f, val frequencyX: Float = 2f, val frequencyY: Float = 2f, val amplitudeX: Float = 0.5f, val amplitudeY: Float = 0.5f, ) ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesFilmicToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiAcesFilmicToneMappingFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.aces_filmic_tone_mapping, value = value, valueRange = -4f..4f ), Filter.AcesFilmicToneMapping ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAcesHillToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiAcesHillToneMappingFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.aces_hill_tone_mapping, value = value, valueRange = -4f..4f ), Filter.AcesHillToneMapping ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiAchromatomalyFilter : UiFilter( title = R.string.achromatomaly, value = Unit ), Filter.Achromatomaly ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAchromatopsiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiAchromatopsiaFilter : UiFilter( title = R.string.achromatopsia, value = Unit ), Filter.Achromatopsia ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAldridgeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiAldridgeFilter( override val value: Pair = 1f to 0.025f ) : UiFilter>( title = R.string.aldridge, value = value, paramsInfo = listOf( FilterParam( title = R.string.exposure, valueRange = 0f..2f ), FilterParam( title = R.string.cutoff, valueRange = -1f..1f, roundTo = 3 ), ) ), Filter.Aldridge ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAmatorkaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiAmatorkaFilter( override val value: Float = 1f ) : UiFilter( title = R.string.amatorka, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.Amatorka ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnaglyphFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiAnaglyphFilter( override val value: Float = 20f ) : UiFilter( title = R.string.anaglyph, value = value, paramsInfo = listOf( FilterParam( valueRange = 0f..100f, roundTo = 0 ) ) ), Filter.Anaglyph ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAnisotropicDiffusionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiAnisotropicDiffusionFilter( override val value: Triple = Triple(20f, 0.6f, 0.5f) ) : UiFilter>( title = R.string.anisotropic_diffusion, value = value, paramsInfo = listOf( FilterParam( title = R.string.repeat_count, valueRange = 1f..100f, roundTo = 0 ), FilterParam( title = R.string.conduction, valueRange = 0.1f..1f, roundTo = 2 ), FilterParam( title = R.string.diffusion, valueRange = 0.01f..1f, roundTo = 2 ) ) ), Filter.AnisotropicDiffusion ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiArcFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ArcParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiArcFilter( override val value: ArcParams = ArcParams.Default ) : UiFilter( title = R.string.arc, paramsInfo = listOf( R.string.radius paramTo 0f..1f, R.string.just_size paramTo 0f..1f, R.string.angle paramTo 0f..360f, R.string.spread_angle paramTo 0f..360f, R.string.center_x paramTo 0f..1f, R.string.center_y paramTo 0f..1f ), value = value ), Filter.Arc ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAsciiFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.AsciiParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiAsciiFilter( override val value: AsciiParams = AsciiParams.Default ) : UiFilter( title = R.string.ascii, paramsInfo = listOf( FilterParam(R.string.gradient, 0f..0f), FilterParam(R.string.font_size, 1f..100f), FilterParam(R.string.font, 0f..0f), FilterParam(R.string.background_color, 0f..0f), FilterParam(R.string.gray_scale, 0f..0f) ), value = value ), Filter.Ascii ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAtkinsonDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiAtkinsonDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.atkinson_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.AtkinsonDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoCropFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiAutoCropFilter( override val value: Float = 5f ) : UiFilter( title = R.string.auto_crop, value = value, paramsInfo = listOf( FilterParam( title = R.string.tolerance, valueRange = 0f..10f, roundTo = 0 ) ) ), Filter.AutoCrop ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoPerspectiveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiAutoPerspectiveFilter( override val value: Unit = Unit ) : UiFilter( title = R.string.auto_perspective, value = value ), Filter.AutoPerspective ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutoRemoveRedEyesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiAutoRemoveRedEyesFilter( override val value: Float = 150f ) : UiFilter( title = R.string.auto_remove_red_eyes, value = value, paramsInfo = listOf( R.string.threshold paramTo 0f..255f ) ), Filter.AutoRemoveRedEyes ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAutumnTonesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiAutumnTonesFilter : UiFilter( title = R.string.autumn_tones, value = Unit ), Filter.AutumnTones ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiAverageDistanceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiAverageDistanceFilter : UiFilter( title = R.string.average_distance, value = Unit ), Filter.AverageDistance ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerEightDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiBayerEightDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.bayer_eight_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.BayerEightDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerFourDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiBayerFourDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.bayer_four_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.BayerFourDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerThreeDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiBayerThreeDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.bayer_three_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.BayerThreeDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBayerTwoDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiBayerTwoDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.bayer_two_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.BayerTwoDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBilaterialBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.BilaterialBlurParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiBilaterialBlurFilter( override val value: BilaterialBlurParams = BilaterialBlurParams.Default, ) : UiFilter( title = R.string.bilaterial_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..50f, roundTo = 0 ), FilterParam( title = R.string.sigma, valueRange = 1f..100f, roundTo = 1 ), FilterParam( title = R.string.spatial_sigma, valueRange = 1f..100f, roundTo = 1 ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f, roundTo = 0 ), ) ), Filter.BilaterialBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackAndWhiteFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiBlackAndWhiteFilter : UiFilter( title = R.string.black_and_white, value = Unit ), Filter.BlackAndWhite ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlackHatFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiBlackHatFilter( override val value: Pair = 25f to true ) : UiFilter>( title = R.string.black_hat, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.use_circle_kernel, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.BlackHat ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBleachBypassFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiBleachBypassFilter( override val value: Float = 1f ) : UiFilter( title = R.string.bleach_bypass, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.BleachBypass ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBlockGlitchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiBlockGlitchFilter( override val value: Pair = 0.02f to 0.5f, ) : UiFilter>( title = R.string.block_glitch, value = value, paramsInfo = listOf( FilterParam( title = R.string.block_size, valueRange = 0f..1f, roundTo = 3 ), FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 3 ), ) ), Filter.BlockGlitch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBloomFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.BloomParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiBloomFilter( override val value: BloomParams = BloomParams.Default ) : UiFilter( title = R.string.bloom, paramsInfo = listOf( FilterParam(R.string.threshold, 0f..1f), FilterParam(R.string.strength, 0f..3f), FilterParam(R.string.radius, 1f..100f, roundTo = 0), FilterParam(R.string.soft_knee, 0f..1f), FilterParam(R.string.exposure, 0f..1f), FilterParam(R.string.gamma, 0f..2f) ), value = value ), Filter.Bloom ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBokehFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiBokehFilter( override val value: Pair = 6f to 6f ) : UiFilter>( title = R.string.bokeh, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.amount, valueRange = 3f..40f, roundTo = 0 ) ) ), Filter.Bokeh ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBorderFrameFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiBorderFrameFilter( override val value: Triple = Triple(20f, 40f, Color.White.toModel()) ) : UiFilter>( title = R.string.border_frame, value = value, paramsInfo = listOf( FilterParam( title = R.string.horizontal_border_thickness, valueRange = 0f..500f, roundTo = 0 ), FilterParam( title = R.string.vertical_border_thickness, valueRange = 0f..500f, roundTo = 0 ), FilterParam( title = R.string.border_color, valueRange = 0f..0f ) ) ), Filter.BorderFrame ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiBoxBlurFilter( override val value: Float = 10f, ) : UiFilter( title = R.string.box_blur, value = value, paramsInfo = listOf( FilterParam( valueRange = 0f..100f, roundTo = 0 ) ) ), Filter.BoxBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrightnessFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiBrightnessFilter( override val value: Float = 0.5f, ) : UiFilter( title = R.string.brightness, value = value, valueRange = -1f..1f ), Filter.Brightness ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBrowniFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiBrowniFilter : UiFilter( title = R.string.browni, value = Unit ), Filter.Browni ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBulgeDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiBulgeDistortionFilter( override val value: Pair = 0.25f to 0.5f, ) : UiFilter>( title = R.string.bulge, value = value, paramsInfo = listOf( R.string.radius paramTo 0f..1f, R.string.scale paramTo -1f..1f ) ), Filter.BulgeDistortion ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiBurkesDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiBurkesDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.burkes_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.BurkesDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCGAColorSpaceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiCGAColorSpaceFilter : UiFilter( title = R.string.cga_colorspace, value = Unit ), Filter.CGAColorSpace ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCandlelightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiCandlelightFilter( override val value: Float = 1f ) : UiFilter( title = R.string.candlelight, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.Candlelight ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCannyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiCannyFilter( override val value: Pair = 100f to 200f ) : UiFilter>( title = R.string.canny, value = value, paramsInfo = listOf( R.string.threshold_one paramTo 0f..1000f, R.string.threshold_two paramTo 0f..1000f ) ), Filter.Canny ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCaramelDarknessFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiCaramelDarknessFilter : UiFilter( title = R.string.caramel_darkness, value = Unit ), Filter.CaramelDarkness ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCelluloidFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiCelluloidFilter( override val value: Float = 1f ) : UiFilter( title = R.string.celluloid, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.Celluloid ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiChannelMixFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ChannelMixParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiChannelMixFilter( override val value: ChannelMixParams = ChannelMixParams.Default ) : UiFilter( title = R.string.channel_mix, paramsInfo = listOf( FilterParam(R.string.blue_green, 0f..255f, 0), FilterParam(R.string.red_blue, 0f..255f, 0), FilterParam(R.string.green_red, 0f..255f, 0), FilterParam(R.string.into_red, 0f..255f, 0), FilterParam(R.string.into_green, 0f..255f, 0), FilterParam(R.string.into_blue, 0f..255f, 0), ), value = value ), Filter.ChannelMix ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCircleBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiCircleBlurFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.circle_blur, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 3f..200f, roundTo = NEAREST_ODD_ROUNDING ) ) ), Filter.CircleBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCirclePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiCirclePixelationFilter( override val value: Float = 24f, ) : UiFilter( title = R.string.circle_pixelation, value = value, valueRange = 5f..200f ), Filter.CirclePixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheFilter( override val value: Triple = Triple(0.5f, 8f, 8f) ) : UiFilter>( title = R.string.clahe, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0) ), value = value ), Filter.Clahe ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSLFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheHSLFilter( override val value: ClaheParams = ClaheParams.Default ) : UiFilter( title = R.string.clahe_hsl, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.ClaheHSL ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheHSVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheHSVFilter( override val value: ClaheParams = ClaheParams.Default ) : UiFilter( title = R.string.clahe_hsv, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.ClaheHSV ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheJzazbzFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheJzazbzFilter( override val value: ClaheParams = ClaheParams.Default ) : UiFilter( title = R.string.clahe_jzazbz, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.ClaheJzazbz ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLABFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheLABFilter( override val value: ClaheParams = ClaheParams.Default ) : UiFilter( title = R.string.clahe_lab, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.ClaheLAB ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheLUVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheLUVFilter( override val value: ClaheParams = ClaheParams.Default ) : UiFilter( title = R.string.clahe_luv, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.ClaheLUV ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklabFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheOklabFilter( override val value: ClaheParams = ClaheParams.Default ) : UiFilter( title = R.string.clahe_oklab, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.ClaheOklab ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClaheOklchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiClaheOklchFilter( override val value: ClaheParams = ClaheParams.Default ) : UiFilter( title = R.string.clahe_oklch, paramsInfo = listOf( FilterParam(R.string.threshold, -10f..10f, 2), FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.ClaheOklch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClosingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiClosingFilter( override val value: Pair = 25f to true ) : UiFilter>( title = R.string.closing, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.use_circle_kernel, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Closing ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered2x2DitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiClustered2x2DitheringFilter( override val value: Boolean = false, ) : UiFilter( title = R.string.clustered_2x2_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Clustered2x2Dithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered4x4DitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiClustered4x4DitheringFilter( override val value: Boolean = false, ) : UiFilter( title = R.string.clustered_4x4_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Clustered4x4Dithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiClustered8x8DitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiClustered8x8DitheringFilter( override val value: Boolean = false, ) : UiFilter( title = R.string.clustered_8x8_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Clustered8x8Dithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCodaChromeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiCodaChromeFilter : UiFilter( title = R.string.coda_chrome, value = Unit ), Filter.CodaChrome ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoffeeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiCoffeeFilter( override val value: Float = 1f ) : UiFilter( title = R.string.coffee, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.Coffee ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorAnomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiColorAnomalyFilter( override val value: Float = 0.56f ) : UiFilter( title = R.string.color_anomaly, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 0.56f..0.8f, roundTo = 3 ) ) ), Filter.ColorAnomaly ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorBalanceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiColorBalanceFilter( override val value: FloatArray = floatArrayOf( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ), ) : UiFilter( title = R.string.color_balance, value = value, valueRange = 3f..3f ), Filter.ColorBalance ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorExplosionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiColorExplosionFilter : UiFilter( title = R.string.color_explosion, value = Unit ), Filter.ColorExplosion ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorHalftoneFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiColorHalftoneFilter( override val value: Quad = Quad( first = 2f, second = 108f, third = 162f, fourth = 90f ) ) : UiFilter>( title = R.string.color_halftone, paramsInfo = listOf( FilterParam(R.string.radius, 0f..50f, 2), FilterParam(R.string.cyan, 0f..360f, 0), FilterParam(R.string.magenta, 0f..360f, 0), FilterParam(R.string.yellow, 0f..360f, 0), ), value = value ), Filter.ColorHalftone ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMapFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiAutumnFilter : UiFilter( title = R.string.autumn, value = Unit ), Filter.Autumn @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiBoneFilter : UiFilter( title = R.string.bone, value = Unit ), Filter.Bone @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiJetFilter : UiFilter( title = R.string.jet, value = Unit ), Filter.Jet @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiWinterFilter : UiFilter( title = R.string.winter, value = Unit ), Filter.Winter @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiRainbowFilter : UiFilter( title = R.string.rainbow, value = Unit ), Filter.Rainbow @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiOceanFilter : UiFilter( title = R.string.ocean, value = Unit ), Filter.Ocean @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSummerFilter : UiFilter( title = R.string.summer, value = Unit ), Filter.Summer @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSpringFilter : UiFilter( title = R.string.spring, value = Unit ), Filter.Spring @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiCoolVariantFilter : UiFilter( title = R.string.cool_variant, value = Unit ), Filter.CoolVariant @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiHsvFilter : UiFilter( title = R.string.hsv, value = Unit ), Filter.Hsv @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiPinkFilter : UiFilter( title = R.string.pink, value = Unit ), Filter.Pink @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiHotFilter : UiFilter( title = R.string.hot, value = Unit ), Filter.Hot @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiParulaFilter : UiFilter( title = R.string.parula, value = Unit ), Filter.Parula @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiMagmaFilter : UiFilter( title = R.string.magma, value = Unit ), Filter.Magma @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiInfernoFilter : UiFilter( title = R.string.inferno, value = Unit ), Filter.Inferno @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiPlasmaFilter : UiFilter( title = R.string.plasma, value = Unit ), Filter.Plasma @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiViridisFilter : UiFilter( title = R.string.viridis, value = Unit ), Filter.Viridis @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiCividisFilter : UiFilter( title = R.string.cividis, value = Unit ), Filter.Cividis @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiTwilightFilter : UiFilter( title = R.string.twilight, value = Unit ), Filter.Twilight @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiTwilightShiftedFilter : UiFilter( title = R.string.twilight_shifted, value = Unit ), Filter.TwilightShifted @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiTurboFilter : UiFilter( title = R.string.turbo, value = Unit ), Filter.Turbo @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiDeepGreenFilter : UiFilter( title = R.string.deep_green, value = Unit ), Filter.DeepGreen ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix3x3Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiColorMatrix3x3Filter( override val value: FloatArray = floatArrayOf( 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, ), ) : UiFilter( title = R.string.color_matrix_3x3, value = value, valueRange = 3f..3f ), Filter.ColorMatrix3x3 ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorMatrix4x4Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiColorMatrix4x4Filter( override val value: FloatArray = floatArrayOf( 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ), ) : UiFilter( title = R.string.color_matrix_4x4, value = value, valueRange = 4f..4f ), Filter.ColorMatrix4x4 ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorOverlayFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterValueWrapper import com.t8rin.imagetoolbox.core.filters.domain.model.wrap import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiColorOverlayFilter( override val value: FilterValueWrapper = Color.Yellow.copy(0.3f).toModel().wrap(), ) : UiFilter>( title = R.string.color_filter, value = value ), Filter.ColorOverlay ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorPosterFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiColorPosterFilter( override val value: Pair = 0.5f to Color(0xFF4DFFE4).toModel() ) : UiFilter>( title = R.string.color_poster, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f ), FilterParam( title = R.string.color, valueRange = 0f..0f ) ), value = value ), Filter.ColorPoster ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiColorfulSwirlFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiColorfulSwirlFilter : UiFilter( title = R.string.colorful_swirl, value = Unit ), Filter.ColorfulSwirl ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContourFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiContourFilter( override val value: Quad = Quad( first = 5f, second = 1f, third = 0f, fourth = Color(0xff000000).toModel() ) ) : UiFilter>( title = R.string.contour, paramsInfo = listOf( FilterParam(R.string.levels, 0f..50f, 2), FilterParam(R.string.scale, 0f..1f, 0), FilterParam(R.string.offset, 0f..255f, 0), FilterParam(R.string.color, 0f..0f, 0), ), value = value ), Filter.Contour ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiContrastFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiContrastFilter( override val value: Float = 2f, ) : UiFilter( title = R.string.contrast, value = value, valueRange = 0f..2f ), Filter.Contrast ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvexFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiConvexFilter( override val value: Float = 3f ) : UiFilter( title = R.string.spline, value = value, valueRange = 0f..30f ), Filter.Convex ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiConvolution3x3Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiConvolution3x3Filter( override val value: FloatArray = floatArrayOf( 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f ), ) : UiFilter( title = R.string.convolution3x3, value = value, valueRange = 3f..3f ), Filter.Convolution3x3 ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCoolFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiCoolFilter : UiFilter( title = R.string.cool, value = Unit ), Filter.Cool ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCopyMoveDetectionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiCopyMoveDetectionFilter( override val value: Pair = 4f to 0f ) : UiFilter>( title = R.string.copy_move_detection, paramsInfo = listOf( FilterParam(R.string.retain, 1f..40f), FilterParam(R.string.coefficent, 0f..1f), ), value = value ), Filter.CopyMoveDetection ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropOrPerspectiveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.CropOrPerspectiveParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiCropOrPerspectiveFilter( override val value: CropOrPerspectiveParams = CropOrPerspectiveParams.Default ) : UiFilter( title = R.string.crop_or_perspective, paramsInfo = listOf( FilterParam(R.string.top_left, 0f..0f), FilterParam(R.string.top_right, 0f..0f), FilterParam(R.string.bottom_left, 0f..0f), FilterParam(R.string.bottom_right, 0f..0f), FilterParam(R.string.absolute, 0f..0f), ), value = value ), Filter.CropOrPerspective ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCropToContentFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiCropToContentFilter( override val value: Pair = 0f to Color.Black.toModel() ) : UiFilter>( title = R.string.crop_to_content, paramsInfo = listOf( FilterParam( title = R.string.tolerance, valueRange = 0f..1f ), FilterParam( title = R.string.color_to_ignore, valueRange = 0f..0f ) ), value = value ), Filter.CropToContent ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiCrossBlurFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.cross_blur, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 3f..200f, roundTo = NEAREST_ODD_ROUNDING ) ) ), Filter.CrossBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrossPixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiCrossPixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.cross_pixelization, value = value, valueRange = 5f..200f ), Filter.CrossPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrosshatchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiCrosshatchFilter( override val value: Pair = 0.01f to 0.003f, ) : UiFilter>( title = R.string.crosshatch, value = value, paramsInfo = listOf( FilterParam(title = R.string.spacing, valueRange = 0.001f..0.05f, roundTo = 4), FilterParam(title = R.string.line_width, valueRange = 0.001f..0.02f, roundTo = 4) ) ), Filter.Crosshatch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrtCurvatureFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiCrtCurvatureFilter( override val value: Triple = Triple(0.25f, 0.65f, 0.015f) ) : UiFilter>( title = R.string.crt_curvature, value = value, paramsInfo = listOf( FilterParam( title = R.string.curvature, valueRange = -1f..1f, roundTo = 3 ), FilterParam( title = R.string.vignette, valueRange = 0f..1f, roundTo = 3 ), FilterParam( title = R.string.chroma, valueRange = 0f..1f, roundTo = 3 ), ) ), Filter.CrtCurvature ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCrystallizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiCrystallizeFilter( override val value: Pair = 1f to Color.Transparent.toModel() ) : UiFilter>( title = R.string.crystallize, value = value, paramsInfo = listOf( R.string.amount paramTo 0.01f..2f, R.string.stroke_color paramTo 0f..0f ) ), Filter.Crystallize ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCubeLutFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiCubeLutFilter( override val value: Pair = 1f to FileModel("") ) : UiFilter>( title = R.string.cube_lut, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ), FilterParam( title = R.string.target_cube_lut_file, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.CubeLut ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiCyberpunkFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiCyberpunkFilter : UiFilter( title = R.string.cyberpunk, value = Unit ), Filter.Cyberpunk ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeepPurpleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiDeepPurpleFilter : UiFilter( title = R.string.deep_purple, value = Unit ), Filter.DeepPurple ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDehazeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiDehazeFilter( override val value: Pair = 17f to 0.45f, ) : UiFilter>( title = R.string.dehaze, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..50f, roundTo = 0 ), R.string.omega paramTo 0f..1f ) ), Filter.Dehaze ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeskewFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiDeskewFilter( override val value: Pair = 15f to true ) : UiFilter>( title = R.string.deskew, paramsInfo = listOf( FilterParam(R.string.max, 0f..89f, 0), FilterParam(R.string.allow_crop, 0f..0f, 0) ), value = value ), Filter.Deskew ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDespeckleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiDespeckleFilter : UiFilter( title = R.string.despeckle, value = Unit ), Filter.Despeckle ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeutaromalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiDeutaromalyFilter : UiFilter( title = R.string.deutaromaly, value = Unit ), Filter.Deutaromaly ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDeutaronotopiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiDeutaronotopiaFilter : UiFilter( title = R.string.deutaronotopia, value = Unit ), Filter.Deutaronotopia ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiamondPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiDiamondPixelationFilter( override val value: Float = 24f, ) : UiFilter( title = R.string.diamond_pixelation, value = value, valueRange = 10f..200f ), Filter.DiamondPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDiffuseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiDiffuseFilter( override val value: Float = 50f ) : UiFilter( title = R.string.diffuse, value = value, paramsInfo = listOf( R.string.scale paramTo 0f..500f ) ), Filter.Diffuse ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDigitalCodeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiDigitalCodeFilter : UiFilter( title = R.string.digital_code, value = Unit ), Filter.DigitalCode ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDilationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiDilationFilter( override val value: Pair = 25f to true ) : UiFilter>( title = R.string.dilation, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.use_circle_kernel, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Dilation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDoGFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiDoGFilter( override val value: Pair = 1f to 2f ) : UiFilter>( title = R.string.dog, value = value, paramsInfo = listOf( R.string.radius paramTo 0f..100f, R.string.second_radius paramTo 0f..100f ) ), Filter.DoG ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDragoFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiDragoFilter( override val value: Pair = 1f to 250f ) : UiFilter>( title = R.string.drago, value = value, paramsInfo = listOf( FilterParam( title = R.string.exposure, valueRange = 0f..2f ), R.string.threshold paramTo 0f..500f ) ), Filter.Drago ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiDropBluesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiDropBluesFilter( override val value: Float = 1f ) : UiFilter( title = R.string.drop_blues, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.DropBlues ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEdgyAmberFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiEdgyAmberFilter( override val value: Float = 1f ) : UiFilter( title = R.string.edgy_amber, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.EdgyAmber ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiElectricGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiElectricGradientFilter : UiFilter( title = R.string.electric_gradient, value = Unit ), Filter.ElectricGradient ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEmbossFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiEmbossFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.emboss, value = value, valueRange = 0f..4f ), Filter.Emboss ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedCirclePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiEnhancedCirclePixelationFilter( override val value: Float = 32f, ) : UiFilter( title = R.string.enhanced_circle_pixelation, value = value, valueRange = 15f..200f ), Filter.EnhancedCirclePixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedDiamondPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiEnhancedDiamondPixelationFilter( override val value: Float = 48f, ) : UiFilter( title = R.string.enhanced_diamond_pixelation, value = value, valueRange = 20f..200f ), Filter.EnhancedDiamondPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedGlitchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.GlitchParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiEnhancedGlitchFilter( override val value: GlitchParams = GlitchParams() ) : UiFilter( title = R.string.enhanced_glitch, value = value, paramsInfo = listOf( FilterParam(R.string.channel_shift_x, -1f..1f, 2), FilterParam(R.string.channel_shift_y, -1f..1f, 2), FilterParam(R.string.corruption_size, 0f..1f, 2), FilterParam(R.string.amount, 1f..100f, 0), FilterParam(R.string.corruption_shift_x, -1f..1f, 2), FilterParam(R.string.corruption_shift_y, -1f..1f, 2) ) ), Filter.EnhancedGlitch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedOilFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiEnhancedOilFilter( override val value: Float = 10f ) : UiFilter( title = R.string.enhanced_oil, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 1f..25f, roundTo = 0 ) ) ), Filter.EnhancedOil ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiEnhancedPixelationFilter( override val value: Float = 48f, ) : UiFilter( title = R.string.enhanced_pixelation, value = value, valueRange = 10f..200f ), Filter.EnhancedPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEnhancedZoomBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiEnhancedZoomBlurFilter( override val value: EnhancedZoomBlurParams = EnhancedZoomBlurParams.Default, ) : UiFilter( title = R.string.enhanced_zoom_blur, value = value, paramsInfo = listOf( FilterParam(R.string.radius, 1f..100f, 2), FilterParam(R.string.sigma, 1f..100f, 2), FilterParam(R.string.blur_center_x, 0f..1f, 2), FilterParam(R.string.blur_center_y, 0f..1f, 2), FilterParam(R.string.strength, 0f..3f, 2), FilterParam(R.string.angle, 0f..360f, 0) ) ), Filter.EnhancedZoomBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiEqualizeFilter( override val value: Unit = Unit ) : UiFilter( title = R.string.equalize, value = value ), Filter.Equalize ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiEqualizeHistogramAdaptiveFilter( override val value: Pair = 3f to 3f ) : UiFilter>( title = R.string.equalize_histogram_adaptive, paramsInfo = listOf( FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0) ), value = value ), Filter.EqualizeHistogramAdaptive ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSLFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiEqualizeHistogramAdaptiveHSLFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : UiFilter>( title = R.string.equalize_histogram_adaptive_hsl, paramsInfo = listOf( FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.EqualizeHistogramAdaptiveHSL ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveHSVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiEqualizeHistogramAdaptiveHSVFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : UiFilter>( title = R.string.equalize_histogram_adaptive_hsv, paramsInfo = listOf( FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.EqualizeHistogramAdaptiveHSV ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLABFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiEqualizeHistogramAdaptiveLABFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : UiFilter>( title = R.string.equalize_histogram_adaptive_lab, paramsInfo = listOf( FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.EqualizeHistogramAdaptiveLAB ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramAdaptiveLUVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiEqualizeHistogramAdaptiveLUVFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : UiFilter>( title = R.string.equalize_histogram_adaptive_luv, paramsInfo = listOf( FilterParam(R.string.grid_size_x, 1f..100f, 0), FilterParam(R.string.grid_size_y, 1f..100f, 0), FilterParam(R.string.bins_count, 2f..256f, 0) ), value = value ), Filter.EqualizeHistogramAdaptiveLUV ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiEqualizeHistogramFilter : UiFilter( title = R.string.equalize_histogram, value = Unit ), Filter.EqualizeHistogram ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramHSVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiEqualizeHistogramHSVFilter( override val value: Float = 128f ) : UiFilter( title = R.string.equalize_histogram_hsv, value = value, paramsInfo = listOf( FilterParam(R.string.bins_count, 2f..256f, 0) ) ), Filter.EqualizeHistogramHSV ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiEqualizeHistogramPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiEqualizeHistogramPixelationFilter( override val value: Pair = 50f to 50f ) : UiFilter>( title = R.string.equalize_histogram_pixelation, paramsInfo = listOf( FilterParam(R.string.grid_size_x, 1f..200f, 0), FilterParam(R.string.grid_size_y, 1f..200f, 0) ), value = value ), Filter.EqualizeHistogramPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErodeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiErodeFilter( override val value: Pair = 25f to true ) : UiFilter>( title = R.string.erode, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.use_circle_kernel, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Erode ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiErrorLevelAnalysisFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiErrorLevelAnalysisFilter( override val value: Float = 90f ) : UiFilter( title = R.string.error_level_analysis, paramsInfo = listOf( FilterParam(R.string.quality, 0f..100f, roundTo = 0), ), value = value ), Filter.ErrorLevelAnalysis ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiExposureFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiExposureFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.exposure, value = value, valueRange = -4f..4f ), Filter.Exposure ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFallColorsFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiFallColorsFilter( override val value: Float = 1f ) : UiFilter( title = R.string.fall_colors, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.FallColors ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseColorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiFalseColorFilter( override val value: Pair = Color.Yellow.toModel() to Color.Magenta.toModel() ) : UiFilter>( title = R.string.false_color, value = value, ), Filter.FalseColor ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFalseFloydSteinbergDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiFalseFloydSteinbergDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.false_floyd_steinberg_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.FalseFloydSteinbergDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFantasyLandscapeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiFantasyLandscapeFilter : UiFilter( title = R.string.fantasy_landscape, value = Unit ), Filter.FantasyLandscape ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBilaterialBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiFastBilaterialBlurFilter( override val value: Triple = Triple(11f, 10f, 3f), ) : UiFilter>( title = R.string.fast_bilaterial_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..200f, roundTo = NEAREST_ODD_ROUNDING ), FilterParam( title = R.string.sigma, valueRange = 1f..100f, roundTo = 1 ), FilterParam( title = R.string.spatial_sigma, valueRange = 1f..100f, roundTo = 1 ) ) ), Filter.FastBilaterialBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiFastBlurFilter( override val value: Pair = 0.5f to 5f, ) : UiFilter>( title = R.string.fast_blur, value = value, paramsInfo = listOf( FilterParam(R.string.scale, 0.1f..1f, 2), FilterParam(R.string.radius, 0f..100f, 0) ) ), Filter.FastBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur2DFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiFastGaussianBlur2DFilter( override val value: Pair = 10f to BlurEdgeMode.Reflect101 ) : UiFilter>( title = R.string.fast_gaussian_blur_2d, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..300f, roundTo = 0 ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f ) ) ), Filter.FastGaussianBlur2D ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur3DFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiFastGaussianBlur3DFilter( override val value: Pair = 10f to BlurEdgeMode.Reflect101 ) : UiFilter>( title = R.string.fast_gaussian_blur_3d, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..500f, roundTo = 0 ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f ) ) ), Filter.FastGaussianBlur3D ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFastGaussianBlur4DFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiFastGaussianBlur4DFilter( override val value: Float = 10f ) : UiFilter( title = R.string.fast_gaussian_blur_4d, value = value, valueRange = 1f..100f ), Filter.FastGaussianBlur4D ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilmStock50Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiFilmStock50Filter( override val value: Float = 1f ) : UiFilter( title = R.string.film_stock_50, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.FilmStock50 ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.annotation.StringRes import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.material.icons.rounded.Extension import androidx.compose.material.icons.rounded.FilterHdr import androidx.compose.material.icons.rounded.Lightbulb import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.filterIsNotInstance import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.blurGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.colorGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.copyUiFilterInstance import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.distortionGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.ditheringGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.effectsGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.lightGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.lutGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.mapFilterToUiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.newUiFilterInstance import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.pixelationGroupFilters import com.t8rin.imagetoolbox.core.filters.presentation.model.generated.simpleGroupFilters import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Animation import com.t8rin.imagetoolbox.core.resources.icons.BlurCircular import com.t8rin.imagetoolbox.core.resources.icons.Cube import com.t8rin.imagetoolbox.core.resources.icons.FloodFill import com.t8rin.imagetoolbox.core.resources.icons.Gradient import com.t8rin.imagetoolbox.core.resources.icons.Speed import com.t8rin.imagetoolbox.core.resources.icons.TableEye import com.t8rin.imagetoolbox.core.utils.appContext sealed class UiFilter( @StringRes val title: Int, val paramsInfo: List = listOf(), override val value: T, ) : Filter { override var isVisible: Boolean by mutableStateOf(true) constructor( @StringRes title: Int, valueRange: ClosedFloatingPointRange, value: T, ) : this( title = title, paramsInfo = listOf( FilterParam(valueRange = valueRange) ), value = value ) fun copy( value: T ): UiFilter<*> = copyUiFilterInstance( filter = this, newValue = value ) fun newInstance(): UiFilter<*> = newUiFilterInstance(this) sealed class Group( val icon: ImageVector, val title: Int, data: List> ) { operator fun component1() = icon operator fun component2() = title internal val filters: List> by lazy { data.sortedBy { appContext.getString(it.title) } } private val filtersForTemplateCreation: List> by lazy { filters.filterIsNotInstance( Filter.PaletteTransfer::class, Filter.LUT512x512::class, Filter.PaletteTransferVariant::class, Filter.CubeLut::class, Filter.LensCorrection::class ) } fun filters(canAddTemplates: Boolean) = if (canAddTemplates) filters else filtersForTemplateCreation data object Template : Group( icon = Icons.Rounded.Extension, title = R.string.template, data = emptyList() ) class Favorite( data: List> ) : Group( icon = Icons.Rounded.Bookmark, title = R.string.favorite, data = data ) { override fun toString(): String = "Favorite" } data object Simple : Group( icon = Icons.Rounded.Speed, title = R.string.simple_effects, data = simpleGroupFilters() ) data object Color : Group( icon = Icons.Rounded.FloodFill, title = R.string.color, data = colorGroupFilters() ) data object LUT : Group( icon = Icons.Rounded.TableEye, title = R.string.lut, data = lutGroupFilters() ) data object Light : Group( icon = Icons.Rounded.Lightbulb, title = R.string.light_aka_illumination, data = lightGroupFilters() ) data object Effects : Group( icon = Icons.Rounded.FilterHdr, title = R.string.effect, data = effectsGroupFilters() ) data object Blur : Group( icon = Icons.Rounded.BlurCircular, title = R.string.blur, data = blurGroupFilters() ) data object Pixelation : Group( icon = Icons.Rounded.Cube, title = R.string.pixelation, data = pixelationGroupFilters() ) data object Distortion : Group( icon = Icons.Rounded.Animation, title = R.string.distortion, data = distortionGroupFilters() ) data object Dithering : Group( icon = Icons.Rounded.Gradient, title = R.string.dithering, data = ditheringGroupFilters() ) } companion object { val groups: List by lazy { listOf( Group.Simple, Group.Color, Group.LUT, Group.Light, Group.Effects, Group.Blur, Group.Pixelation, Group.Distortion, Group.Dithering ) } val count: Int by lazy { groups.sumOf { it.filters.size } } } } fun Filter<*>.toUiFilter( preserveVisibility: Boolean = true ): UiFilter<*> = mapFilterToUiFilter( filter = this, preserveVisibility = preserveVisibility ) infix fun Int.paramTo(valueRange: ClosedFloatingPointRange) = FilterParam( title = this, valueRange = valueRange ) ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFloydSteinbergDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiFloydSteinbergDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.floyd_steinberg_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.FloydSteinbergDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFoggyNightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiFoggyNightFilter( override val value: Float = 1f ) : UiFilter( title = R.string.foggy_night, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.FoggyNight ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFractalGlassFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiFractalGlassFilter( override val value: Pair = 0.02f to 0.02f ) : UiFilter>( title = R.string.fractal_glass, value = value, paramsInfo = listOf( R.string.strength paramTo 0f..1f, R.string.amplitude paramTo 0f..1f ) ), Filter.FractalGlass ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiFuturisticGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiFuturisticGradientFilter : UiFilter( title = R.string.futuristic_gradient, value = Unit ), Filter.FuturisticGradient ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGammaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiGammaFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.gamma, value = value, valueRange = 0f..2f ), Filter.Gamma ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiGaussianBlurFilter( override val value: Triple = Triple( 25f, 10f, BlurEdgeMode.Reflect101 ), ) : UiFilter>( title = R.string.gaussian_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 0f..100f, roundTo = 0 ), FilterParam( title = R.string.sigma, valueRange = 1f..100f ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f ) ) ), Filter.GaussianBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGaussianBoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiGaussianBoxBlurFilter( override val value: Float = 10f ) : UiFilter( title = R.string.gaussian_box_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.sigma, valueRange = 1f..300f, roundTo = 0 ) ) ), Filter.GaussianBoxBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlassSphereRefractionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiGlassSphereRefractionFilter( override val value: Pair = 0.25f to 0.71f, ) : UiFilter>( title = R.string.glass_sphere_refraction, value = value, paramsInfo = listOf( R.string.radius paramTo 0f..1f, R.string.refractive_index paramTo 0f..1f ) ), Filter.GlassSphereRefraction ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiGlitchFilter( override val value: Triple = Triple(20f, 15f, 9f) ) : UiFilter>( title = R.string.glitch, value = value, paramsInfo = listOf( FilterParam( title = R.string.amount, valueRange = 0f..100f, roundTo = 0 ), FilterParam( title = R.string.seed, valueRange = 0f..100f, roundTo = 0 ), FilterParam( title = R.string.repeat_count, valueRange = 0f..100f, roundTo = 0 ) ) ), Filter.Glitch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlitchVariantFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiGlitchVariantFilter( override val value: Triple = Triple(30f, 0.25f, 0.3f) ) : UiFilter>( title = R.string.glitch_variant, value = value, paramsInfo = listOf( FilterParam( title = R.string.repeat_count, valueRange = 0f..100f, roundTo = 0 ), FilterParam( title = R.string.max_offset, valueRange = 0f..1f, roundTo = 3 ), FilterParam( title = R.string.channel_shift, valueRange = 0f..1f, roundTo = 3 ), ) ), Filter.GlitchVariant ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGlowFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiGlowFilter( override val value: Float = 0.5f ) : UiFilter( title = R.string.glow, value = value, paramsInfo = listOf( R.string.amount paramTo 0f..1f ) ), Filter.Glow ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenForestFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiGoldenForestFilter( override val value: Float = 1f ) : UiFilter( title = R.string.golden_forest, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.GoldenForest ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGoldenHourFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiGoldenHourFilter : UiFilter( title = R.string.golden_hour, value = Unit ), Filter.GoldenHour ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGothamFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiGothamFilter : UiFilter( title = R.string.gotham, value = Unit ), Filter.Gotham ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrainFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiGrainFilter( override val value: Float = 0.75f, ) : UiFilter( title = R.string.grain, value = value, valueRange = 0f..2f ), Filter.Grain ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGrayscaleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiGrayscaleFilter( override val value: Triple = Triple(0.299f, 0.587f, 0.114f) ) : UiFilter>( title = R.string.gray_scale, value = value, paramsInfo = listOf( FilterParam( title = R.string.color_red, valueRange = 0f..1f ), FilterParam( title = R.string.color_green, valueRange = 0f..1f ), FilterParam( title = R.string.color_blue, valueRange = 0f..1f ) ) ), Filter.Grayscale ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenSunFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiGreenSunFilter : UiFilter( title = R.string.green_sun, value = Unit ), Filter.GreenSun ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiGreenishFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiGreenishFilter( override val value: Float = 1f ) : UiFilter( title = R.string.greenish, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.Greenish ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHDRFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiHDRFilter : UiFilter( title = R.string.hdr, value = Unit ), Filter.HDR ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHableFilmicToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiHableFilmicToneMappingFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.hable_filmic_tone_mapping, value = value, valueRange = -4f..4f ), Filter.HableFilmicToneMapping ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHalftoneFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiHalftoneFilter( override val value: Float = 0.005f, ) : UiFilter( title = R.string.halftone, value = value, paramsInfo = listOf( FilterParam(valueRange = 0.001f..0.02f, roundTo = 4) ) ), Filter.Halftone ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHazeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiHazeFilter( override val value: Pair = 0.2f to 0f, ) : UiFilter>( title = R.string.haze, value = value, paramsInfo = listOf( R.string.distance paramTo -0.3f..0.3f, R.string.slope paramTo -0.3f..0.3f ) ), Filter.Haze ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHejlBurgessToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiHejlBurgessToneMappingFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.heji_burgess_tone_mapping, value = value, valueRange = 0f..4f ), Filter.HejlBurgessToneMapping ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHighlightsAndShadowsFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiHighlightsAndShadowsFilter( override val value: Float = 0.25f ) : UiFilter( title = R.string.highlights_shadows, value = value, valueRange = 0f..2f ), Filter.HighlightsAndShadows ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHorizontalWindStaggerFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiHorizontalWindStaggerFilter( override val value: Triple = Triple( first = 0.2f, second = 90, third = Color.Transparent.toModel() ) ) : UiFilter>( title = R.string.horizontal_wind_stagger, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..100f, roundTo = 1 ), FilterParam( title = R.string.amount, valueRange = 10f..200f, roundTo = 0 ), FilterParam( title = R.string.color, valueRange = 0f..0f ) ) ), Filter.HorizontalWindStagger ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHotSummerFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiHotSummerFilter : UiFilter( title = R.string.hot_summer, value = Unit ), Filter.HotSummer ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiHueFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiHueFilter( override val value: Float = 90f, ) : UiFilter( title = R.string.hue, value = value, valueRange = 0f..255f ), Filter.Hue ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiJarvisJudiceNinkeDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiJarvisJudiceNinkeDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.jarvis_judice_ninke_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.JarvisJudiceNinkeDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKaleidoscopeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.KaleidoscopeParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiKaleidoscopeFilter( override val value: KaleidoscopeParams = KaleidoscopeParams.Default ) : UiFilter( title = R.string.kaleidoscope, paramsInfo = listOf( FilterParam(R.string.angle, 0f..360f, 0), FilterParam(R.string.secondary_angle, 0f..360f, 0), FilterParam(R.string.center_x, 0f..1f, 2), FilterParam(R.string.center_y, 0f..1f, 2), FilterParam(R.string.sides, 2f..12f, 0), FilterParam(R.string.radius, 0f..100f, 2), ), value = value ), Filter.Kaleidoscope ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKodakFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiKodakFilter( override val value: Float = 1f ) : UiFilter( title = R.string.kodak, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.Kodak ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiKuwaharaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiKuwaharaFilter( override val value: Float = 9f, ) : UiFilter( title = R.string.kuwahara, value = value, paramsInfo = listOf( FilterParam(null, 0f..10f, 0) ) ), Filter.Kuwahara ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLUT512x512Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiLUT512x512Filter( override val value: Pair = 1f to ImageModel(R.drawable.lookup) ) : UiFilter>( title = R.string.lut512x512, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ), FilterParam( title = R.string.target_lut_image, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LUT512x512 ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiLaplacianFilter : UiFilter( title = R.string.laplacian, value = Unit, valueRange = 0f..0f ), Filter.Laplacian ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLaplacianSimpleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiLaplacianSimpleFilter : UiFilter( title = R.string.laplacian_simple, value = Unit ), Filter.LaplacianSimple ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLavenderDreamFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiLavenderDreamFilter : UiFilter( title = R.string.lavender_dream, value = Unit ), Filter.LavenderDream ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLeftToRightDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiLeftToRightDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.left_to_right_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LeftToRightDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLemonadeLightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiLemonadeLightFilter : UiFilter( title = R.string.lemonade_light, value = Unit ), Filter.LemonadeLight ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLensCorrectionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiLensCorrectionFilter( override val value: Pair = 1f to FileModel("") ) : UiFilter>( title = R.string.lens_correction, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = -1f..3f, roundTo = 2 ), FilterParam( title = R.string.target_lens_profile, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LensCorrection ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearBoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearBoxBlurFilter( override val value: Pair = 10 to TransferFunc.SRGB ) : UiFilter>( title = R.string.linear_box_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..300f, roundTo = 0 ), FilterParam( title = R.string.tag_transfer_function, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LinearBoxBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearFastGaussianBlurFilter( override val value: Triple = Triple( first = 10, second = TransferFunc.SRGB, third = BlurEdgeMode.Reflect101 ) ) : UiFilter>( title = R.string.linear_fast_gaussian_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..300f, roundTo = 0 ), FilterParam( title = R.string.tag_transfer_function, valueRange = 0f..0f, roundTo = 0 ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LinearFastGaussianBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearFastGaussianBlurNextFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearFastGaussianBlurNextFilter( override val value: Triple = Triple( first = 10, second = TransferFunc.SRGB, third = BlurEdgeMode.Reflect101 ) ) : UiFilter>( title = R.string.linear_fast_gaussian_blur_next, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..300f, roundTo = 0 ), FilterParam( title = R.string.tag_transfer_function, valueRange = 0f..0f, roundTo = 0 ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LinearFastGaussianBlurNext ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearGaussianParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearGaussianBlurFilter( override val value: LinearGaussianParams = LinearGaussianParams.Default ) : UiFilter( title = R.string.linear_gaussian_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 3f..600f, roundTo = NEAREST_ODD_ROUNDING ), FilterParam( title = R.string.sigma, valueRange = 1f..50f, roundTo = 2 ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f, roundTo = 0 ), FilterParam( title = R.string.tag_transfer_function, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LinearGaussianBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearGaussianBoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearGaussianBoxBlurFilter( override val value: Pair = 10f to TransferFunc.SRGB ) : UiFilter>( title = R.string.linear_gaussian_box_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.sigma, valueRange = 1f..300f, roundTo = 0 ), FilterParam( title = R.string.tag_transfer_function, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LinearGaussianBoxBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearStackBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearStackBlurFilter( override val value: Pair = 10 to TransferFunc.SRGB ) : UiFilter>( title = R.string.linear_stack_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..300f, roundTo = 0 ), FilterParam( title = R.string.tag_transfer_function, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LinearStackBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTentBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearTentBlurFilter( override val value: Pair = 11f to TransferFunc.SRGB ) : UiFilter>( title = R.string.linear_tent_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.sigma, valueRange = 1f..300f, roundTo = NEAREST_ODD_ROUNDING ), FilterParam( title = R.string.tag_transfer_function, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LinearTentBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLinearTiltShiftFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearTiltShiftParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiLinearTiltShiftFilter( override val value: LinearTiltShiftParams = LinearTiltShiftParams.Default ) : UiFilter( title = R.string.linear_tilt_shift, value = value, paramsInfo = listOf( FilterParam(R.string.blur_radius, 1f..100f, 0), FilterParam(R.string.sigma, 1f..50f, 2), FilterParam(R.string.center_x, 0f..1f, 2), FilterParam(R.string.center_y, 0f..1f, 2), FilterParam(R.string.size, 0f..1f, 2), FilterParam(R.string.angle, 0f..360f, 0) ) ), Filter.LinearTiltShift ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLogarithmicToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiLogarithmicToneMappingFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.logarithmic_tone_mapping, value = value, valueRange = 0f..4f ), Filter.LogarithmicToneMapping ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLookupFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiLookupFilter( override val value: Float = -1f, ) : UiFilter( title = R.string.lookup, value = value, valueRange = -10f..10f ), Filter.Lookup ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLowPolyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiLowPolyFilter( override val value: Pair = 2000f to true ) : UiFilter>( title = R.string.low_poly, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 50f..30000f, roundTo = 0 ), FilterParam( title = R.string.fill, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.LowPoly ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiLuminanceGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiLuminanceGradientFilter : UiFilter( title = R.string.luminance_gradient, value = Unit ), Filter.LuminanceGradient ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMarbleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiMarbleFilter( override val value: Triple = Triple(0.02f, 1f, 1f) ) : UiFilter>( title = R.string.marble, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f ), FilterParam( title = R.string.turbulence, valueRange = 0f..1f ), FilterParam( title = R.string.amplitude, valueRange = 0f..1f ) ) ), Filter.Marble ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMedianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiMedianBlurFilter( override val value: Float = 10f ) : UiFilter( title = R.string.median_blur, value = value, paramsInfo = listOf( FilterParam(R.string.radius, 0f..100f, 0) ) ), Filter.MedianBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMicroMacroPixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiMicroMacroPixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.micro_macro_pixelization, value = value, valueRange = 5f..200f ), Filter.MicroMacroPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMirrorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiMirrorFilter( override val value: Pair = 0.5f to MirrorSide.LeftToRight, ) : UiFilter>( title = R.string.tile_mode_mirror, value = value, paramsInfo = listOf( FilterParam( title = R.string.center, valueRange = 0f..1f ), FilterParam( title = R.string.side, valueRange = 0f..0f ) ) ), Filter.Mirror ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMissEtikateFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiMissEtikateFilter( override val value: Float = 1f ) : UiFilter( title = R.string.miss_etikate, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.MissEtikate ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMobiusFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiMobiusFilter( override val value: Triple = Triple(1f, 0.9f, 1f) ) : UiFilter>( title = R.string.mobius, value = value, paramsInfo = listOf( FilterParam( title = R.string.exposure, valueRange = 0f..2f ), FilterParam( title = R.string.transition, valueRange = -2f..2f ), FilterParam( title = R.string.peak, valueRange = -2f..2f ) ) ), Filter.Mobius ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMoireFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiMoireFilter( override val value: Unit = Unit ) : UiFilter( title = R.string.moire, value = value ), Filter.Moire ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMonochromeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiMonochromeFilter( override val value: Pair = 1f to Color( red = 0.6f, green = 0.45f, blue = 0.3f, alpha = 1.0f ).toModel() ) : UiFilter>( title = R.string.monochrome, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ), FilterParam( title = R.string.color, valueRange = 0f..0f ) ) ), Filter.Monochrome ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMorphologicalGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiMorphologicalGradientFilter( override val value: Pair = 25f to true ) : UiFilter>( title = R.string.morphological_gradient, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.use_circle_kernel, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.MorphologicalGradient ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiMotionBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiMotionBlurFilter( override val value: Triple = Triple(25, 45f, BlurEdgeMode.Reflect101), ) : UiFilter>( title = R.string.motion_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 0f..201f, roundTo = NEAREST_ODD_ROUNDING ), FilterParam( title = R.string.angle, valueRange = 0f..360f ), FilterParam( title = R.string.edge_mode, valueRange = 0f..0f ) ) ), Filter.MotionBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNativeStackBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiNativeStackBlurFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.native_stack_blur, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 3f..250f, roundTo = 0 ) ) ), Filter.NativeStackBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNegativeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiNegativeFilter : UiFilter( title = R.string.negative, value = Unit ), Filter.Negative ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNeonFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiNeonFilter( override val value: Triple = Triple( first = 1f, second = 0.26f, third = Color.Magenta.toModel() ) ) : UiFilter>( title = R.string.neon, value = value, paramsInfo = listOf( FilterParam( title = R.string.amount, valueRange = 1f..25f, roundTo = 0 ), FilterParam( title = R.string.strength, valueRange = 0f..1f ), FilterParam( title = R.string.color, valueRange = 0f..0f ) ) ), Filter.Neon ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightMagicFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiNightMagicFilter : UiFilter( title = R.string.night_magic, value = Unit ), Filter.NightMagic ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNightVisionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiNightVisionFilter : UiFilter( title = R.string.night_vision, value = Unit ), Filter.NightVision ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNoiseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiNoiseFilter( override val value: Float = 128f ) : UiFilter( title = R.string.noise, value = value, paramsInfo = listOf( FilterParam( valueRange = 0f..255f, roundTo = 0 ) ) ), Filter.Noise ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNonMaximumSuppressionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiNonMaximumSuppressionFilter : UiFilter( title = R.string.non_maximum_suppression, value = Unit ), Filter.NonMaximumSuppression ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiNucleusPixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiNucleusPixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.nucleus_pixelization, value = value, valueRange = 5f..200f ), Filter.NucleusPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOffsetFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiOffsetFilter( override val value: Pair = 0.25f to 0.25f ) : UiFilter>( title = R.string.offset, value = value, paramsInfo = listOf( R.string.offset_x paramTo 0f..1f, R.string.offset_y paramTo 0f..1f ) ), Filter.Offset ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOilFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiOilFilter( override val value: Pair = 4f to 1f ) : UiFilter>( title = R.string.oil, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..20f, roundTo = 0 ), FilterParam( title = R.string.strength, valueRange = 0f..4f, roundTo = 1 ) ) ), Filter.Oil ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOldTvFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiOldTvFilter : UiFilter( title = R.string.old_tv, value = Unit ), Filter.OldTv ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpacityFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiOpacityFilter( override val value: Float = 0.5f, ) : UiFilter( title = R.string.opacity, value = value, valueRange = 0f..1f ), Filter.Opacity ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOpeningFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiOpeningFilter( override val value: Pair = 25f to true ) : UiFilter>( title = R.string.opening, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.use_circle_kernel, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Opening ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrangeHazeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiOrangeHazeFilter : UiFilter( title = R.string.orange_haze, value = Unit ), Filter.OrangeHaze ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiOrbitalPixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiOrbitalPixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.orbital_pixelization, value = value, valueRange = 5f..200f ), Filter.OrbitalPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiPaletteTransferFilter( override val value: Pair = 1f to ImageModel(R.drawable.filter_preview_source_2) ) : UiFilter>( title = R.string.palette_transfer, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ), FilterParam( title = R.string.target_image, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.PaletteTransfer ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPaletteTransferVariantFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PaletteTransferSpace import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiPaletteTransferVariantFilter( override val value: Triple = Triple( first = 1f, second = PaletteTransferSpace.OKLAB, third = ImageModel(R.drawable.filter_preview_source_2) ) ) : UiFilter>( title = R.string.palette_transfer_variant, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ), FilterParam( title = R.string.tag_color_space, valueRange = 0f..1f, roundTo = 0 ), FilterParam( title = R.string.target_image, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.PaletteTransferVariant ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPastelFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiPastelFilter : UiFilter( title = R.string.pastel, value = Unit ), Filter.Pastel ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPerlinDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiPerlinDistortionFilter( override val value: Triple = Triple(0.02f, 1f, 1f) ) : UiFilter>( title = R.string.perlin_distortion, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f ), FilterParam( title = R.string.turbulence, valueRange = 0f..1f ), FilterParam( title = R.string.amplitude, valueRange = 0f..1f ) ) ), Filter.PerlinDistortion ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.PinchParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiPinchFilter( override val value: PinchParams = PinchParams.Default ) : UiFilter( title = R.string.whirl_and_pinch, value = value, paramsInfo = listOf( FilterParam(R.string.angle, 0f..360f, 0), R.string.center_x paramTo 0f..1f, R.string.center_y paramTo 0f..1f, R.string.radius paramTo 0f..2f, R.string.amount paramTo -1f..1f ) ), Filter.Pinch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPinkDreamFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiPinkDreamFilter : UiFilter( title = R.string.pink_dream, value = Unit ), Filter.PinkDream ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelMeltFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiPixelMeltFilter( override val value: Pair = 20f to 0.5f, ) : UiFilter>( title = R.string.pixel_melt, value = value, paramsInfo = listOf( FilterParam( title = R.string.max_drop, valueRange = 0f..250f, roundTo = 0 ), FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 3 ), ) ), Filter.PixelMelt ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiPixelationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.pixelation, value = value, valueRange = 5f..200f ), Filter.Pixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPointillizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiPointillizeFilter( override val value: VoronoiCrystallizeParams = VoronoiCrystallizeParams.Default ) : UiFilter( title = R.string.pointillize, paramsInfo = listOf( FilterParam(R.string.border_thickness, 0f..5f, 2), FilterParam(R.string.scale, 1f..300f, 2), FilterParam(R.string.randomness, 0f..10f, 2), FilterParam(R.string.shape, 0f..4f, 0), FilterParam(R.string.turbulence, 0f..1f, 2), FilterParam(R.string.angle, 0f..360f, 0), FilterParam(R.string.stretch, 1f..6f, 2), FilterParam(R.string.amount, 0f..1f, 2), FilterParam(R.string.border_color, 0f..0f, 0), ), value = value ), Filter.Pointillize ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPoissonBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiPoissonBlurFilter( override val value: Float = 10f, ) : UiFilter( title = R.string.poisson_blur, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 3f..200f, roundTo = 0 ) ) ), Filter.PoissonBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolarCoordinatesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PolarCoordinatesType import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiPolarCoordinatesFilter( override val value: PolarCoordinatesType = PolarCoordinatesType.RECT_TO_POLAR ) : UiFilter( title = R.string.polar_coordinates, value = value ), Filter.PolarCoordinates ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolaroidFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiPolaroidFilter : UiFilter( title = R.string.polaroid, value = Unit ), Filter.Polaroid ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPolkaDotFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiPolkaDotFilter( override val value: Triple = Triple( first = 10, second = 8, third = Color.Black.toModel() ) ) : UiFilter>( title = R.string.polka_dot, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 1f..40f, roundTo = 0 ), FilterParam( title = R.string.spacing, valueRange = 1f..40f, roundTo = 0 ), FilterParam( title = R.string.background_color, valueRange = 0f..0f ) ) ), Filter.PolkaDot ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPopArtFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiPopArtFilter( override val value: Triple = Triple( first = 1f, second = Color.Red.toModel(), third = PopArtBlendingMode.MULTIPLY ) ) : UiFilter>( title = R.string.pop_art, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ), FilterParam( title = R.string.color, valueRange = 0f..1f, roundTo = 0 ), FilterParam( title = R.string.overlay_mode, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.PopArt ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPosterizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiPosterizeFilter( override val value: Float = 5f, ) : UiFilter( title = R.string.posterize, value = value, paramsInfo = listOf( FilterParam(null, 1f..40f, 0) ) ), Filter.Posterize ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtanopiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiProtanopiaFilter : UiFilter( title = R.string.protanopia, value = Unit ), Filter.Protanopia ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiProtonomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiProtonomalyFilter : UiFilter( title = R.string.protonomaly, value = Unit ), Filter.Protonomaly ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPulseGridPixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiPulseGridPixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.pulse_grid_pixelization, value = value, valueRange = 5f..200f ), Filter.PulseGridPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiPurpleMistFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiPurpleMistFilter : UiFilter( title = R.string.purple_mist, value = Unit ), Filter.PurpleMist ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiQuantizierFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiQuantizierFilter( override val value: Float = 256f ) : UiFilter( title = R.string.quantizier, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 2f..4096f, roundTo = 0 ) ) ), Filter.Quantizier ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRGBFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterValueWrapper import com.t8rin.imagetoolbox.core.filters.domain.model.wrap import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiRGBFilter( override val value: FilterValueWrapper = Color.Green.toModel().wrap(), ) : UiFilter>( title = R.string.rgb_filter, value = value, ), Filter.RGB ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialTiltShiftFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.RadialTiltShiftParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiRadialTiltShiftFilter( override val value: RadialTiltShiftParams = RadialTiltShiftParams.Default ) : UiFilter( title = R.string.tilt_shift, value = value, paramsInfo = listOf( FilterParam(R.string.blur_radius, 1f..100f, 0), FilterParam(R.string.sigma, 1f..50f, 2), FilterParam(R.string.center_x, 0f..1f, 2), FilterParam(R.string.center_y, 0f..1f, 2), FilterParam(R.string.radius, 0f..1f, 2) ) ), Filter.RadialTiltShift ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRadialWeavePixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiRadialWeavePixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.radial_weave_pixelization, value = value, valueRange = 5f..200f ), Filter.RadialWeavePixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRainbowWorldFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiRainbowWorldFilter : UiFilter( title = R.string.rainbow_world, value = Unit ), Filter.RainbowWorld ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRandomDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiRandomDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.random_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.RandomDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRedSwirlFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiRedSwirlFilter : UiFilter( title = R.string.red_swirl, value = Unit ), Filter.RedSwirl ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReduceNoiseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiReduceNoiseFilter( override val value: Unit = Unit ) : UiFilter( title = R.string.reduce_noise, value = value ), Filter.ReduceNoise ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRemoveColorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiRemoveColorFilter( override val value: Pair = 0f to Color(0xFF000000).toModel(), ) : UiFilter>( title = R.string.remove_color, value = value, paramsInfo = listOf( FilterParam( title = R.string.tolerance, valueRange = 0f..1f ), FilterParam( title = R.string.color_to_remove, valueRange = 0f..0f ) ) ), Filter.RemoveColor ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiReplaceColorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiReplaceColorFilter( override val value: Triple = Triple( first = 0f, second = Color(red = 0.0f, green = 0.0f, blue = 0.0f, alpha = 1.0f).toModel(), third = Color(red = 1.0f, green = 1.0f, blue = 1.0f, alpha = 1.0f).toModel() ), ) : UiFilter>( title = R.string.replace_color, value = value, paramsInfo = listOf( FilterParam( title = R.string.tolerance, valueRange = 0f..1f ), FilterParam( title = R.string.color_to_replace, valueRange = 0f..0f ), FilterParam( title = R.string.target_color, valueRange = 0f..0f ) ) ), Filter.ReplaceColor ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRetroYellowFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiRetroYellowFilter( override val value: Float = 1f ) : UiFilter( title = R.string.retro_yellow, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.RetroYellow ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRingBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiRingBlurFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.ring_blur, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 3f..200f, roundTo = NEAREST_ODD_ROUNDING ) ) ), Filter.RingBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiRubberStampFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.RubberStampParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiRubberStampFilter( override val value: RubberStampParams = RubberStampParams.Default ) : UiFilter( title = R.string.rubber_stmp, paramsInfo = listOf( R.string.threshold paramTo 0f..1f, R.string.brush_softness paramTo 0f..1f, R.string.radius paramTo 0f..2f, R.string.first_color paramTo 0f..0f, R.string.second_color paramTo 0f..0f ), value = value ), Filter.RubberStamp ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSandPaintingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiSandPaintingFilter( override val value: Triple = Triple(5000, 50, Color.Black.toModel()) ) : UiFilter>( title = R.string.sand_painting, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 50f..75000f, roundTo = 0 ), FilterParam( title = R.string.threshold, valueRange = 30f..90f, roundTo = 0 ), FilterParam( title = R.string.background_color, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.SandPainting ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSaturationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiSaturationFilter( override val value: Pair = 2f to true, ) : UiFilter>( title = R.string.saturation, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..2f, roundTo = 2 ), FilterParam( title = R.string.enable_tonemapping, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.Saturation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSeamCarvingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiSeamCarvingFilter( override val value: IntegerSize = IntegerSize.Zero ) : UiFilter( title = R.string.seam_carving, paramsInfo = listOf( FilterParam(R.string.width, 0f..0f), FilterParam(R.string.height, 0f..0f), ), value = value ), Filter.SeamCarving ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSepiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSepiaFilter : UiFilter( title = R.string.sepia, value = Unit ), Filter.Sepia ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSharpenFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiSharpenFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.sharpen, value = value, valueRange = -1f..1f ), Filter.Sharpen ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiShuffleBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiShuffleBlurFilter( override val value: Pair = 35f to 1f ) : UiFilter>( title = R.string.shuffle_blur, value = value, paramsInfo = listOf( FilterParam( title = R.string.radius, valueRange = 0f..70f, roundTo = 0 ), FilterParam( title = R.string.threshold, valueRange = -1f..1f, roundTo = 2 ), ) ), Filter.ShuffleBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSideFadeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide import com.t8rin.imagetoolbox.core.filters.domain.model.params.SideFadeParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiSideFadeFilter( override val value: SideFadeParams = SideFadeParams.Relative(FadeSide.End, 0.5f), ) : UiFilter( title = R.string.side_fade, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ), FilterParam( title = R.string.side, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.SideFade ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiSierraDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.sierra_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.SierraDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSierraLiteDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiSierraLiteDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.sierra_lite_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.SierraLiteDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleOldTvFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSimpleOldTvFilter : UiFilter( title = R.string.simple_old_tv, value = Unit ), Filter.SimpleOldTv ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSketchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSimpleSketchFilter : UiFilter( title = R.string.simple_sketch, value = Unit ), Filter.SimpleSketch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleSolarizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSimpleSolarizeFilter( override val value: Unit = Unit ) : UiFilter( title = R.string.simple_solarize, value = value ), Filter.SimpleSolarize ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleThresholdDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiSimpleThresholdDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.simple_threshold_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.SimpleThresholdDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSimpleWeavePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiSimpleWeavePixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.simple_weave_pixelization, value = value, valueRange = 5f..200f ), Filter.SimpleWeavePixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSketchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiSketchFilter( override val value: Float = 5f, ) : UiFilter( title = R.string.sketch, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 3f..9f, roundTo = 0 ) ) ), Filter.Sketch ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmearFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.SmearParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiSmearFilter( override val value: SmearParams = SmearParams.Default ) : UiFilter( title = R.string.smear, paramsInfo = listOf( FilterParam(R.string.angle, 0f..360f, 0), R.string.density paramTo 0f..1f, R.string.mix paramTo 0f..1f, FilterParam(R.string.distance, 0f..200f, 0), FilterParam(R.string.shape, 0f..3f, 0) ), value = value ), Filter.Smear ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSmoothToonFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiSmoothToonFilter( override val value: Triple = Triple(0.5f, 0.2f, 10f) ) : UiFilter>( title = R.string.smooth_toon, value = value, paramsInfo = listOf( R.string.blur_size paramTo 0f..100f, R.string.threshold paramTo 0f..5f, R.string.quantizationLevels paramTo 0f..100f ) ), Filter.SmoothToon ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelEdgeDetectionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiSobelEdgeDetectionFilter( override val value: Float = 1f, ) : UiFilter( title = R.string.sobel_edge, value = value, paramsInfo = listOf( FilterParam(title = R.string.line_width, valueRange = 1f..25f, roundTo = 0) ) ), Filter.SobelEdgeDetection ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSobelSimpleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSobelSimpleFilter : UiFilter( title = R.string.sobel_simple, value = Unit ), Filter.SobelSimple ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiSoftEleganceFilter( override val value: Float = 1f ) : UiFilter( title = R.string.soft_elegance, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.SoftElegance ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftEleganceVariantFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LUT) class UiSoftEleganceVariantFilter( override val value: Float = 1f ) : UiFilter( title = R.string.soft_elegance_variant, value = value, paramsInfo = listOf( FilterParam( title = R.string.strength, valueRange = 0f..1f, roundTo = 2 ) ) ), Filter.SoftEleganceVariant ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSoftSpringLightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSoftSpringLightFilter : UiFilter( title = R.string.soft_spring_light, value = Unit ), Filter.SoftSpringLight ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSolarizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiSolarizeFilter( override val value: Float = 0.5f, ) : UiFilter( title = R.string.solarize, value = value, valueRange = 0f..1f ), Filter.Solarize ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpacePortalFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSpacePortalFilter : UiFilter( title = R.string.space_portal, value = Unit ), Filter.SpacePortal ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSparkleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.SparkleParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiSparkleFilter( override val value: SparkleParams = SparkleParams.Default ) : UiFilter( title = R.string.sparkle, paramsInfo = listOf( FilterParam(R.string.amount, 0f..200f, 0), FilterParam(R.string.rays, 0f..200f, 0), FilterParam(R.string.radius, 0f..1f), FilterParam(R.string.randomness, 0f..100f, 0), FilterParam(R.string.center_x, 0f..1f), FilterParam(R.string.center_y, 0f..1f), FilterParam(R.string.color, 0f..0f) ), value = value ), Filter.Sparkle ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSpectralFireFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSpectralFireFilter : UiFilter( title = R.string.spectral_fire, value = Unit ), Filter.SpectralFire ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereLensDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiSphereLensDistortionFilter( override val value: Quad = 2.5f to 0.5f qto (0.5f to 0.5f) ) : UiFilter>( title = R.string.sphere_lensh_distortion, paramsInfo = listOf( R.string.refraction_index paramTo 1f..10f, R.string.radius paramTo 0f..1f, R.string.center_x paramTo 0f..1f, R.string.center_y paramTo 0f..1f ), value = value ), Filter.SphereLensDistortion ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSphereRefractionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiSphereRefractionFilter( override val value: Pair = 0.25f to 0.71f, ) : UiFilter>( title = R.string.sphere_refraction, value = value, paramsInfo = listOf( R.string.radius paramTo 0f..1f, R.string.refractive_index paramTo 0f..1f ) ), Filter.SphereRefraction ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStackBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiStackBlurFilter( override val value: Pair = 0.5f to 10f, ) : UiFilter>( title = R.string.stack_blur, value = value, paramsInfo = listOf( FilterParam(R.string.scale, 0.1f..1f, 2), FilterParam(R.string.radius, 0f..100f, 0) ) ), Filter.StackBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStaggeredPixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiStaggeredPixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.staggered_pixelization, value = value, valueRange = 5f..200f ), Filter.StaggeredPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStarBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiStarBlurFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.star_blur, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 3f..200f, roundTo = NEAREST_ODD_ROUNDING ) ) ), Filter.StarBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStrokePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiStrokePixelationFilter( override val value: Pair = 20f to Color.Black.toModel(), ) : UiFilter>( title = R.string.stroke_pixelation, value = value, paramsInfo = listOf( FilterParam( title = R.string.pixel_size, valueRange = 5f..200f ), FilterParam( title = R.string.background_color, valueRange = 0f..0f ) ) ), Filter.StrokePixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiStuckiDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiStuckiDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.stucki_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.StuckiDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSunriseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiSunriseFilter : UiFilter( title = R.string.sunrise, value = Unit ), Filter.Sunrise ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiSwirlDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiSwirlDistortionFilter( override val value: Quad = 0.5f to 1f qto (0.5f to 0.5f), ) : UiFilter>( title = R.string.swirl, value = value, paramsInfo = listOf( R.string.radius paramTo 0f..1f, R.string.angle paramTo -1f..1f, R.string.center_x paramTo 0.01f..1f, R.string.center_y paramTo 0.01f..1f ) ), Filter.SwirlDistortion ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTentBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiTentBlurFilter( override val value: Float = 10f, ) : UiFilter( title = R.string.tent_blur, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 1f..300f, roundTo = NEAREST_ODD_ROUNDING ) ) ), Filter.TentBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiThresholdFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiThresholdFilter( override val value: Float = 128f, ) : UiFilter( title = R.string.luminance_threshold, value = value, paramsInfo = listOf( FilterParam( title = null, valueRange = 0f..255f, roundTo = 0 ) ) ), Filter.Threshold ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToneCurvesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.ToneCurvesParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiToneCurvesFilter( override val value: ToneCurvesParams = ToneCurvesParams.Default ) : UiFilter( title = R.string.tone_curves, paramsInfo = listOf( FilterParam(R.string.values, 0f..0f) ), value = value ), Filter.ToneCurves ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiToonFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiToonFilter( override val value: Pair = 0.2f to 10f, ) : UiFilter>( title = R.string.toon, value = value, paramsInfo = listOf( R.string.threshold paramTo 0f..5f, R.string.quantizationLevels paramTo 0f..100f ) ), Filter.Toon ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTopHatFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiTopHatFilter( override val value: Pair = 25f to true ) : UiFilter>( title = R.string.top_hat, value = value, paramsInfo = listOf( FilterParam( title = R.string.just_size, valueRange = 1f..150f, roundTo = 0 ), FilterParam( title = R.string.use_circle_kernel, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.TopHat ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTriToneFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.COLOR) class UiTriToneFilter( override val value: Triple = Triple( first = Color(0xFFFF003B).toModel(), second = Color(0xFF831111).toModel(), third = Color(0xFFFF0099).toModel() ) ) : UiFilter>( title = R.string.tri_tone, value = value, ), Filter.TriTone ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritanopiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiTritanopiaFilter : UiFilter( title = R.string.tritanopia, value = Unit ), Filter.Tritanopia ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTritonomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiTritonomalyFilter : UiFilter( title = R.string.tritonomaly, value = Unit ), Filter.Tritonomaly ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwirlFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiTwirlFilter( override val value: Quad = 45f to 0.5f qto (0.5f to 0.5f) ) : UiFilter>( title = R.string.twirl, paramsInfo = listOf( R.string.angle paramTo -360f..360f, R.string.center_x paramTo 0f..1f, R.string.center_y paramTo 0f..1f, R.string.radius paramTo 0f..1f, ), value = value ), Filter.Twirl ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiTwoRowSierraDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiTwoRowSierraDitheringFilter( override val value: Pair = 200f to false, ) : UiFilter>( title = R.string.two_row_sierra_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.threshold, valueRange = 1f..255f, roundTo = 0 ), FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.TwoRowSierraDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUchimuraFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiUchimuraFilter( override val value: Float = 1f ) : UiFilter( title = R.string.uchimura, value = value, valueRange = 0f..2f ), Filter.Uchimura ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiUnsharpFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiUnsharpFilter( override val value: Float = 0.5f, ) : UiFilter( title = R.string.unsharp, value = value, valueRange = 0f..1f ), Filter.Unsharp ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVHSFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import kotlin.math.PI @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiVHSFilter( override val value: Pair = 2f to 3f, ) : UiFilter>( title = R.string.vhs, value = value, paramsInfo = listOf( FilterParam( title = R.string.seed, valueRange = 0f..PI.toFloat(), roundTo = 3 ), FilterParam( title = R.string.strength, valueRange = 0f..10f, roundTo = 3 ), ) ), Filter.VHS ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVibranceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiVibranceFilter( override val value: Float = 3f, ) : UiFilter( title = R.string.vibrance, value = value, valueRange = -5f..5f ), Filter.Vibrance ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVignetteFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel @UiFilterInject(group = UiFilterInject.Groups.EFFECTS) class UiVignetteFilter( override val value: Triple = Triple( first = 0.3f, second = 0.75f, third = Color.Black.toModel() ), ) : UiFilter>( title = R.string.vignette, value = value, paramsInfo = listOf( R.string.start paramTo -2f..2f, R.string.end paramTo -2f..2f, R.string.color paramTo 0f..0f ) ), Filter.Vignette ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVintageFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiVintageFilter : UiFilter( title = R.string.vintage, value = Unit ), Filter.Vintage ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVoronoiCrystallizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiVoronoiCrystallizeFilter( override val value: VoronoiCrystallizeParams = VoronoiCrystallizeParams.Default ) : UiFilter( title = R.string.voronoi_crystallize, paramsInfo = listOf( FilterParam(R.string.border_thickness, 0f..5f, 2), FilterParam(R.string.scale, 1f..300f, 2), FilterParam(R.string.randomness, 0f..10f, 2), FilterParam(R.string.shape, 0f..4f, 0), FilterParam(R.string.turbulence, 0f..1f, 2), FilterParam(R.string.angle, 0f..360f, 0), FilterParam(R.string.stretch, 1f..6f, 2), FilterParam(R.string.amount, 0f..1f, 2), FilterParam(R.string.border_color, 0f..0f, 0), ), value = value ), Filter.VoronoiCrystallize ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiVortexPixelizationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiVortexPixelizationFilter( override val value: Float = 25f, ) : UiFilter( title = R.string.vortex_pixelization, value = value, valueRange = 5f..200f ), Filter.VortexPixelation ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWarmFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiWarmFilter : UiFilter( title = R.string.warm, value = Unit ), Filter.Warm ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWaterEffectFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterParams import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DISTORTION) class UiWaterEffectFilter( override val value: WaterParams = WaterParams() ) : UiFilter( title = R.string.water_effect, value = value, paramsInfo = listOf( FilterParam(R.string.just_size, 0f..1f, 2), FilterParam(R.string.frequency_x, -4f..4f, 2), FilterParam(R.string.frequency_y, -4f..4f, 2), FilterParam(R.string.amplitude_x, -4f..4f, 2), FilterParam(R.string.amplitude_y, -4f..4f, 2) ) ), Filter.WaterEffect ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeakPixelFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.SIMPLE) class UiWeakPixelFilter : UiFilter( title = R.string.weak_pixel_inclusion, value = Unit ), Filter.WeakPixel ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWeaveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.PIXELATION) class UiWeaveFilter( override val value: Quad = 16f to 16f qto (6f to 6f) ) : UiFilter>( title = R.string.weave, paramsInfo = listOf( R.string.x_width paramTo 0f..100f, R.string.y_wdth paramTo 0f..100f, R.string.x_gap paramTo 0f..100f, R.string.y_gap paramTo 0f..100f, ), value = value ), Filter.Weave ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiWhiteBalanceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.LIGHT) class UiWhiteBalanceFilter( override val value: Pair = 7000.0f to 100f, ) : UiFilter>( title = R.string.white_balance, value = value, paramsInfo = listOf( FilterParam(R.string.temperature, 1000f..10000f, 0), FilterParam(R.string.tint, -100f..100f, 2) ) ), Filter.WhiteBalance ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiYililomaDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.DITHERING) class UiYililomaDitheringFilter( override val value: Boolean = false, ) : UiFilter( title = R.string.yililoma_dithering, value = value, paramsInfo = listOf( FilterParam( title = R.string.gray_scale, valueRange = 0f..0f, roundTo = 0 ) ) ), Filter.YililomaDithering ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/model/UiZoomBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterParam import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject import com.t8rin.imagetoolbox.core.resources.R @UiFilterInject(group = UiFilterInject.Groups.BLUR) class UiZoomBlurFilter( override val value: Triple = Triple(0.5f, 0.5f, 5f), ) : UiFilter>( title = R.string.zoom_blur, value = value, paramsInfo = listOf( FilterParam(R.string.blur_center_x, 0f..1f, 2), FilterParam(R.string.blur_center_y, 0f..1f, 2), FilterParam(R.string.blur_size, 0f..10f, 2) ) ), Filter.ZoomBlur ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/CollectAsUiState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.utils import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @JvmName("collectAsUiState1") @Composable fun Flow>>.collectAsUiState(): State>> = this .map { list -> list.map { it.toUiFilter() } } .collectAsState(emptyList()) @Composable fun Flow>.collectAsUiState(): State> = this .map { list -> list.sortedBy { it.name } } .collectAsState(emptyList()) ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/LamaLoader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.utils import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.neural_tools.inpaint.LaMaProcessor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach interface LamaLoader { val isDownloaded: Boolean fun download(): Flow companion object Companion : LamaLoader by LamaLoaderImpl } private object LamaLoaderImpl : LamaLoader { private val _isDownloaded: MutableState = mutableStateOf(LaMaProcessor.isDownloaded.value) override val isDownloaded: Boolean by _isDownloaded init { LaMaProcessor.isDownloaded.onEach { _isDownloaded.value = it }.launchIn(CoroutineScope(Dispatchers.IO)) } override fun download(): Flow = LaMaProcessor.startDownload().map { DownloadProgress( currentPercent = it.currentPercent, currentTotalSize = it.currentTotalSize ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/utils/Mappings.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.utils import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PaletteTransferSpace import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PolarCoordinatesType import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.resources.R internal val PopArtBlendingMode.translatedName: String @Composable get() = when (this) { PopArtBlendingMode.MULTIPLY -> "Multiply" PopArtBlendingMode.COLOR_BURN -> "Color Burn" PopArtBlendingMode.SOFT_LIGHT -> "Soft Light" PopArtBlendingMode.HSL_COLOR -> "HSL Color" PopArtBlendingMode.HSL_HUE -> "HSL Hue" PopArtBlendingMode.DIFFERENCE -> "Difference" } internal val PaletteTransferSpace.translatedName: String @Composable get() = when (this) { PaletteTransferSpace.LALPHABETA -> "Lαβ" PaletteTransferSpace.LAB -> "LAB" PaletteTransferSpace.OKLAB -> "OKLAB" PaletteTransferSpace.LUV -> "LUV" } internal val TransferFunc.translatedName: String @Composable get() = when (this) { TransferFunc.SRGB -> "sRGB" TransferFunc.REC709 -> "Rec.709" TransferFunc.GAMMA2P2 -> "${stringResource(R.string.gamma)} 2.2" TransferFunc.GAMMA2P8 -> "${stringResource(R.string.gamma)} 2.8" } internal val BlurEdgeMode.translatedName: String @Composable get() = when (this) { BlurEdgeMode.Clamp -> stringResource(R.string.tile_mode_clamp) BlurEdgeMode.Reflect101 -> stringResource(R.string.mirror_101) BlurEdgeMode.Wrap -> stringResource(R.string.wrap) BlurEdgeMode.Reflect -> stringResource(R.string.tile_mode_mirror) } internal val FadeSide.translatedName: String @Composable get() = when (this) { FadeSide.Start -> stringResource(R.string.start) FadeSide.End -> stringResource(R.string.end) FadeSide.Top -> stringResource(R.string.top) FadeSide.Bottom -> stringResource(R.string.bottom) } internal val MirrorSide.translatedName: String @Composable get() = when (this) { MirrorSide.LeftToRight -> stringResource(R.string.left_to_right) MirrorSide.RightToLeft -> stringResource(R.string.right_to_left) MirrorSide.TopToBottom -> stringResource(R.string.top_to_bottom) MirrorSide.BottomToTop -> stringResource(R.string.bottom_to_top) } internal val PolarCoordinatesType.translatedName: String @Composable get() = when (this) { PolarCoordinatesType.RECT_TO_POLAR -> stringResource(R.string.rect_to_polar) PolarCoordinatesType.POLAR_TO_RECT -> stringResource(R.string.polar_to_rect) PolarCoordinatesType.INVERT_IN_CIRCLE -> stringResource(R.string.invert_in_circle) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/AddFilterButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material.icons.rounded.Extension import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton @Composable fun AddFilterButton( modifier: Modifier = Modifier, containerColor: Color = MaterialTheme.colorScheme.mixedContainer, onClick: () -> Unit, onCreateTemplate: (() -> Unit)? = null ) { Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { onCreateTemplate?.let { EnhancedIconButton( onClick = onCreateTemplate, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Icon( imageVector = Icons.Rounded.Extension, contentDescription = "extension" ) } } EnhancedButton( containerColor = containerColor, onClick = onClick ) { Icon( imageVector = Icons.Rounded.AutoFixHigh, contentDescription = stringResource(R.string.add_filter) ) Spacer(Modifier.width(8.dp)) Text(stringResource(id = R.string.add_filter)) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/CalculateBrightnessEstimate.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import android.graphics.Bitmap import com.t8rin.trickle.TrickleUtils internal fun calculateBrightnessEstimate( bitmap: Bitmap, pixelSpacing: Int = 1 ): Int = TrickleUtils.calculateBrightness( bitmap = bitmap, pixelSpacing = pixelSpacing ) ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/CubeLutDownloadDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TableChart import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.BasicEnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator @Composable internal fun CubeLutDownloadDialog( visible: Boolean, onDismiss: () -> Unit, onDownload: () -> Unit, downloadOnlyNewData: Boolean, cubeLutDownloadProgress: DownloadProgress? ) { EnhancedAlertDialog( visible = visible, icon = { Icon( imageVector = Icons.Outlined.TableChart, contentDescription = "lut" ) }, title = { Text(stringResource(id = R.string.cube_lut)) }, text = { Text( stringResource( if (downloadOnlyNewData) R.string.lut_library_update_sub else R.string.lut_library_sub ) ) }, onDismissRequest = {}, confirmButton = { EnhancedButton( onClick = onDownload ) { Text(stringResource(R.string.download)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.close)) } } ) BasicEnhancedAlertDialog( onDismissRequest = {}, visible = cubeLutDownloadProgress != null, modifier = Modifier.fillMaxSize() ) { EnhancedLoadingIndicator( progress = (cubeLutDownloadProgress?.currentPercent ?: 0f), loaderSize = 72.dp ) { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() .padding(8.dp) ) { Text( text = rememberHumanFileSize(cubeLutDownloadProgress?.currentTotalSize ?: 0), maxLines = 1, textAlign = TextAlign.Center, fontSize = 10.sp, lineHeight = 10.sp ) } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.material.icons.rounded.Extension import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedDropdownMenu import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.value.ValueDialog import com.t8rin.imagetoolbox.core.ui.widget.value.ValueText @Composable fun FilterItem( filter: UiFilter, showDragHandle: Boolean, onRemove: () -> Unit, modifier: Modifier = Modifier, onLongPress: (() -> Unit)? = null, previewOnly: Boolean = false, onFilterChange: (value: Any) -> Unit, onCreateTemplate: (() -> Unit)?, backgroundColor: Color = Color.Unspecified, shape: Shape = MaterialTheme.shapes.extraLarge, canHide: Boolean = true ) { var isControlsExpanded by rememberSaveable { mutableStateOf(true) } val isVisible = filter.isVisible if (!canHide && !isVisible) { SideEffect { filter.isVisible = true } } Box( modifier = Modifier.then( onLongPress?.let { Modifier.pointerInput(Unit) { detectTapGestures( onLongPress = { it() } ) } } ?: Modifier ) ) { Row( modifier = modifier .container(color = backgroundColor, shape = shape) .animateContentSizeNoClip(), verticalAlignment = Alignment.CenterVertically ) { Column( modifier = Modifier .weight(1f) .alpha(if (previewOnly) 0.5f else 1f) .then( if (previewOnly) { Modifier .heightIn(max = 120.dp) .fadingEdges( scrollableState = null, isVertical = true, length = 12.dp ) .enhancedVerticalScroll(rememberScrollState()) } else Modifier ) ) { var sliderValue by remember(filter) { mutableFloatStateOf( ((filter.value as? Number)?.toFloat()) ?: 0f ) } Row(verticalAlignment = Alignment.CenterVertically) { if (!previewOnly) { if (canHide) { Box { var showPopup by remember { mutableStateOf(false) } EnhancedIconButton( onClick = { showPopup = true } ) { Icon( imageVector = Icons.Rounded.MoreVert, contentDescription = "more" ) } EnhancedDropdownMenu( expanded = showPopup, onDismissRequest = { showPopup = false }, shape = ShapeDefaults.large ) { Column( modifier = Modifier .width(IntrinsicSize.Max) .padding(horizontal = 8.dp) ) { EnhancedButton( modifier = Modifier.fillMaxWidth(), onClick = { onRemove() showPopup = false }, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.secondary ) { Icon( imageVector = Icons.Rounded.RemoveCircleOutline, contentDescription = stringResource(R.string.create_template) ) Spacer(Modifier.width(8.dp)) Text(stringResource(R.string.remove)) } Spacer(Modifier.height(4.dp)) EnhancedButton( modifier = Modifier.fillMaxWidth(), onClick = { filter.isVisible = !isVisible onFilterChange(filter.value as Any) showPopup = false }, shape = onCreateTemplate?.let { ShapeDefaults.center } ?: ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.primary ) { Icon( imageVector = if (isVisible) { Icons.Outlined.VisibilityOff } else { Icons.Rounded.Visibility }, contentDescription = stringResource(R.string.remove) ) Spacer(Modifier.width(8.dp)) Text( stringResource( if (isVisible) R.string.hide else R.string.show ) ) } onCreateTemplate?.let { Spacer(Modifier.height(4.dp)) EnhancedButton( modifier = Modifier.fillMaxWidth(), onClick = { onCreateTemplate() showPopup = false }, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.tertiary ) { Icon( imageVector = Icons.Rounded.Extension, contentDescription = stringResource(R.string.remove) ) Spacer(Modifier.width(8.dp)) Text(stringResource(R.string.create_template)) } } } } } } else { EnhancedIconButton( onClick = onRemove ) { Icon( imageVector = Icons.Rounded.RemoveCircleOutline, contentDescription = stringResource(R.string.remove) ) } } } Row( modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically ) { Text( text = stringResource(filter.title), fontWeight = FontWeight.SemiBold, modifier = Modifier .alpha( animateFloatAsState(if (isVisible) 1f else 0.5f).value ) .padding( top = 8.dp, end = 8.dp, start = 16.dp, bottom = 8.dp ) .fillMaxWidth(), textDecoration = if (!isVisible) { TextDecoration.LineThrough } else null ) } if (!filter.value.isSingle() && !previewOnly) { EnhancedIconButton( onClick = { isControlsExpanded = !isControlsExpanded } ) { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate( animateFloatAsState( if (isControlsExpanded) 180f else 0f ).value ) ) } } if (filter.value is Number) { var showValueDialog by remember { mutableStateOf(false) } ValueText( value = sliderValue, onClick = { showValueDialog = true } ) ValueDialog( roundTo = filter.paramsInfo[0].roundTo, valueRange = filter.paramsInfo[0].valueRange, valueState = sliderValue.toString(), expanded = showValueDialog && !previewOnly, onDismiss = { showValueDialog = false }, onValueUpdate = { sliderValue = it onFilterChange(it) } ) } } AnimatedVisibility( visible = isControlsExpanded || filter.value.isSingle() || previewOnly ) { FilterItemContent( filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } } if (showDragHandle) { Box( modifier = Modifier .height(if (filter.value is Unit) 32.dp else 64.dp) .width(1.dp) .background(MaterialTheme.colorScheme.outlineVariant()) .padding(start = 20.dp) ) Spacer(Modifier.width(8.dp)) Icon( imageVector = Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.drag_handle_width), modifier = Modifier .size(48.dp) .padding(12.dp) ) Spacer(Modifier.width(8.dp)) } } if (previewOnly) { Surface( color = Color.Transparent, modifier = modifier.matchParentSize() ) {} } } } private fun Any?.isSingle(): Boolean = this is Number || this is Unit ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterItemContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.compose.foundation.layout.Column import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.cast import com.t8rin.imagetoolbox.core.filters.domain.model.FilterValueWrapper import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PolarCoordinatesType import com.t8rin.imagetoolbox.core.filters.domain.model.params.ArcParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.AsciiParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.BilaterialBlurParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.BloomParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ChannelMixParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.CropOrPerspectiveParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.GlitchParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.KaleidoscopeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearGaussianParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearTiltShiftParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.PinchParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.RadialTiltShiftParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.RubberStampParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SideFadeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SmearParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SparkleParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ToneCurvesParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.translatedName import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.ArcParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.AsciiParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.BilaterialBlurParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.BloomParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.BooleanItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.ChannelMixParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.ClaheParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.CropOrPerspectiveParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.EnhancedZoomBlurParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.FilterValueWrapperItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.FloatArrayItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.FloatItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.GlitchParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.IntegerSizeParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.KaleidoscopeParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.LinearGaussianParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.LinearTiltShiftParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.PairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.PinchParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.QuadItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.RadialTiltShiftParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.RubberStampParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.SideFadeRelativeItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.SmearParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.SparkleParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.ToneCurvesParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.TripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.VoronoiCrystallizeParamsItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.WaterParamsItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup @Composable internal fun FilterItemContent( filter: UiFilter, onFilterChange: (value: Any) -> Unit, modifier: Modifier = Modifier, previewOnly: Boolean = false, ) { Column( modifier = modifier ) { when (val value = filter.value) { is FilterValueWrapper<*> -> { FilterValueWrapperItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is FloatArray -> { FloatArrayItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is Float -> { FloatItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is Boolean -> { BooleanItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is PolarCoordinatesType -> { EnhancedButtonGroup( inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, items = PolarCoordinatesType.entries.map { it.translatedName }, selectedIndex = PolarCoordinatesType.entries.indexOf(value), onIndexChange = { onFilterChange(PolarCoordinatesType.entries[it]) } ) } is Pair<*, *> -> { PairItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is Triple<*, *, *> -> { TripleItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is Quad<*, *, *, *> -> { QuadItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is RadialTiltShiftParams -> { RadialTiltShiftParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is LinearTiltShiftParams -> { LinearTiltShiftParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is GlitchParams -> { GlitchParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is SideFadeParams.Relative -> { SideFadeRelativeItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is WaterParams -> { WaterParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is EnhancedZoomBlurParams -> { EnhancedZoomBlurParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is ClaheParams -> { ClaheParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is LinearGaussianParams -> { LinearGaussianParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is ToneCurvesParams -> { ToneCurvesParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is BilaterialBlurParams -> { BilaterialBlurParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is KaleidoscopeParams -> { KaleidoscopeParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is ChannelMixParams -> { ChannelMixParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is VoronoiCrystallizeParams -> { VoronoiCrystallizeParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is PinchParams -> { PinchParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is RubberStampParams -> { RubberStampParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is SmearParams -> { SmearParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is ArcParams -> { ArcParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is SparkleParams -> { SparkleParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is AsciiParams -> { AsciiParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is CropOrPerspectiveParams -> { CropOrPerspectiveParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is IntegerSize -> { IntegerSizeParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } is BloomParams -> { BloomParamsItem( value = value, filter = filter.cast(), onFilterChange = onFilterChange, previewOnly = previewOnly ) } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterPreviewSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalSheetDragHandle import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.SimplePicture import com.t8rin.imagetoolbox.core.ui.widget.image.imageStickyHeader import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberAvailableHeight import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberImageState @Composable internal fun FilterPreviewSheet( component: AddFiltersSheetComponent, onFilterPickedWithParams: (UiFilter<*>) -> Unit, onVisibleChange: (Boolean) -> Unit, previewBitmap: Bitmap? ) { val previewSheetData = component.previewData var imageState by rememberImageState() LaunchedEffect(previewSheetData) { if (previewBitmap != null && previewSheetData != null) { if (previewSheetData.size == 1 && previewSheetData.firstOrNull()?.value is Unit) { imageState = imageState.copy(position = 2) } component.updatePreview(previewBitmap) } } EnhancedModalBottomSheet( dragHandle = { EnhancedModalSheetDragHandle( showDragHandle = false ) { EnhancedTopAppBar( type = EnhancedTopAppBarType.Center, drawHorizontalStroke = false, navigationIcon = { EnhancedIconButton( onClick = { component.setPreviewData(null) } ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } }, colors = EnhancedTopAppBarDefaults.colors( containerColor = EnhancedBottomSheetDefaults.barContainerColor ), actions = { EnhancedIconButton( onClick = { previewSheetData?.forEach { filter -> onFilterPickedWithParams( filter.copy(filter.value).also { it.isVisible = true } ) } component.setPreviewData(null) onVisibleChange(false) } ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } }, title = { Text( text = stringResource( id = previewSheetData?.let { if (it.size == 1) it.first().title else R.string.filter_preview } ?: R.string.filter_preview ), modifier = Modifier.marquee() ) } ) } }, sheetContent = { val backgroundColor = MaterialTheme.colorScheme.surfaceContainerLow DisposableEffect(Unit) { onDispose { imageState = imageState.copy(position = 2) } } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth() ) { val imageBlock = @Composable { AnimatedContent( targetState = component.previewBitmap == null, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { isNull -> if (isNull) { Box( modifier = if (component.previewBitmap == null) { Modifier .aspectRatio( previewBitmap?.safeAspectRatio ?: (1 / 2f) ) .clip(ShapeDefaults.mini) .shimmer(true) } else Modifier ) } else { SimplePicture( bitmap = component.previewBitmap, loading = component.isImageLoading, modifier = Modifier ) } } } val isPortrait by isPortraitOrientationAsState() Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, ) { val isUnit = previewSheetData?.size == 1 && previewSheetData.firstOrNull()?.value is Unit if (!isPortrait) { Box( modifier = Modifier .container(RectangleShape) .weight(1.2f) .padding(20.dp) ) { Box(Modifier.align(Alignment.Center)) { imageBlock() } } } val internalHeight = rememberAvailableHeight(imageState = imageState) LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .then( if (!isPortrait && !isUnit) Modifier.weight(1f) else Modifier ) .clipToBounds(), flingBehavior = enhancedFlingBehavior() ) { imageStickyHeader( visible = isPortrait, imageState = imageState, internalHeight = internalHeight, onStateChange = { imageState = it }, imageBlock = imageBlock, backgroundColor = backgroundColor ) item { previewSheetData?.takeIf { !isUnit }?.let { list -> list.forEachIndexed { index, filter -> FilterItem( backgroundColor = MaterialTheme .colorScheme .surface, modifier = Modifier.padding(horizontal = 16.dp), filter = filter, showDragHandle = false, onRemove = { if (list.size == 1) { component.setPreviewData(null) } else component.removeFilterAtIndex(index) }, onCreateTemplate = null, onFilterChange = { value -> component.updateFilter(value, index) } ) if (index != list.lastIndex) { Spacer(Modifier.height(8.dp)) } } Spacer(Modifier.height(16.dp)) } Spacer( Modifier.height( WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding() ) ) } } } } }, visible = previewSheetData != null, onDismiss = { if (!it) { component.setPreviewData(null) } } ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterReorderSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Reorder import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState @Composable fun FilterReorderSheet( filterList: List>, visible: Boolean, onDismiss: () -> Unit, onReorder: (List>) -> Unit ) { EnhancedModalBottomSheet( sheetContent = { if (filterList.size < 2) onDismiss() Box { val data = remember { mutableStateOf(filterList) } val listState = rememberLazyListState() val haptics = LocalHapticFeedback.current val state = rememberReorderableLazyListState( onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } }, lazyListState = listState ) LazyColumn( state = listState, contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data.value, key = { _, v -> v.hashCode() } ) { index, filter -> ReorderableItem( state = state, key = filter.hashCode() ) { isDragging -> FilterItem( filter = filter, onFilterChange = {}, modifier = Modifier .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { onReorder(data.value) } ) .scale( animateFloatAsState( if (isDragging) 1.05f else 1f ).value ), shape = ShapeDefaults.byIndex( index = index, size = data.value.size ), previewOnly = true, showDragHandle = filterList.size >= 2, onRemove = {}, onCreateTemplate = null ) } } } } }, visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.reorder), icon = Icons.Rounded.Reorder ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } }, ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionCubeLutBottomContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Download import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material.icons.rounded.TableChart import androidx.compose.material.icons.rounded.Update import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.request.ImageRequest import coil3.request.error import coil3.request.transformations import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.domain.remote.RemoteResources import com.t8rin.imagetoolbox.core.filters.presentation.model.UiCubeLutFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.appContext @Composable internal fun FilterSelectionCubeLutBottomContent( cubeLutRemoteResources: RemoteResources?, shape: Shape, onShowDownloadDialog: ( forceUpdate: Boolean, downloadOnlyNewData: Boolean ) -> Unit, onRequestFilterMapping: ((UiFilter<*>) -> Transformation), onClick: (UiCubeLutFilter) -> Unit ) { cubeLutRemoteResources?.let { resources -> val previewModel = LocalFilterPreviewModelProvider.current.preview var showSelection by rememberSaveable { mutableStateOf(false) } Row( modifier = Modifier .padding( start = 8.dp, end = 8.dp, bottom = 8.dp ) .container( color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp, shape = shape ) .hapticsClickable { if (resources.list.isEmpty()) { onShowDownloadDialog(false, false) } else { showSelection = true } } .padding(8.dp), verticalAlignment = Alignment.CenterVertically, ) { TitleItem( text = stringResource(R.string.lut_library), modifier = Modifier .padding(start = 8.dp) .weight(1f) ) Spacer(Modifier.width(8.dp)) EnhancedIconButton( onClick = { onShowDownloadDialog(true, false) }, containerColor = if (resources.list.isEmpty()) { MaterialTheme.colorScheme.secondary } else Color.Transparent ) { Icon( imageVector = Icons.Rounded.Download, contentDescription = null ) } if (resources.list.isNotEmpty()) { EnhancedIconButton( onClick = { onShowDownloadDialog(true, true) } ) { Icon( imageVector = Icons.Rounded.Update, contentDescription = null ) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.secondary, onClick = { showSelection = true } ) { Icon( imageVector = Icons.Rounded.Visibility, contentDescription = "View" ) } } } var isSearching by rememberSaveable { mutableStateOf(false) } var searchKeyword by rememberSaveable(isSearching) { mutableStateOf("") } EnhancedModalBottomSheet( visible = showSelection, onDismiss = { showSelection = it }, confirmButton = {}, enableBottomContentWeight = false, title = { AnimatedContent( targetState = isSearching ) { searching -> if (searching) { BackHandler { searchKeyword = "" isSearching = false } ProvideTextStyle(value = MaterialTheme.typography.bodyLarge) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = searchKeyword, onValueChange = { searchKeyword = it }, startIcon = { EnhancedIconButton( onClick = { searchKeyword = "" isSearching = false }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = MaterialTheme.colorScheme.onSurface ) } }, endIcon = { AnimatedVisibility( visible = searchKeyword.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = { searchKeyword = "" }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface ) } } }, shape = ShapeDefaults.circle ) } } else { Row( verticalAlignment = Alignment.CenterVertically ) { TitleItem( text = stringResource(R.string.lut_library), icon = Icons.Rounded.TableChart ) Spacer(modifier = Modifier.weight(1f)) EnhancedIconButton( onClick = { isSearching = true }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Icon( imageVector = Icons.Rounded.Search, contentDescription = stringResource(R.string.search_here) ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showSelection = false } ) { AutoSizeText(stringResource(R.string.close)) } Spacer(Modifier.width(8.dp)) } } } } ) { val data by remember(resources.list, searchKeyword) { derivedStateOf { if (searchKeyword.isEmpty()) resources.list else resources.list.filter { it.name.trim() .contains(searchKeyword.trim(), true) } } } AnimatedContent(data.isNotEmpty()) { haveData -> if (haveData) { LazyColumn( verticalArrangement = Arrangement.spacedBy(4.dp), contentPadding = PaddingValues(8.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data, key = { _, d -> d.name + d.uri } ) { index, (uri, name) -> PreferenceItemOverload( title = remember(name) { name.removeSuffix(".cube") .removeSuffix("_LUT") .replace("_", " ") }, drawStartIconContainer = false, startIcon = { Row( verticalAlignment = Alignment.CenterVertically ) { Picture( model = remember(uri, previewModel) { ImageRequest.Builder(appContext) .data(previewModel.data) .error(R.drawable.filter_preview_source) .transformations( onRequestFilterMapping( UiCubeLutFilter( 1f to FileModel(uri) ) ) ) .diskCacheKey(uri + previewModel.data.hashCode()) .memoryCacheKey(uri + previewModel.data.hashCode()) .size(300, 300) .build() }, shape = MaterialTheme.shapes.medium, contentScale = ContentScale.Crop, modifier = Modifier .size(48.dp) .scale(1.2f) ) Spacer(Modifier.width(16.dp)) Box( modifier = Modifier .height(36.dp) .width(1.dp) .background(MaterialTheme.colorScheme.outlineVariant()) ) } }, onClick = { showSelection = false onClick( UiCubeLutFilter(1f to FileModel(uri)) ) }, shape = ShapeDefaults.byIndex( index = index, size = data.size ), modifier = Modifier .fillMaxWidth() .animateItem() ) } } } else { Column( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.5f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterSelectionItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.SignalCellularConnectedNoInternet0Bar import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.material.icons.rounded.BookmarkBorder import androidx.compose.material.icons.rounded.Slideshow import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.request.ImageRequest import coil3.request.error import coil3.request.transformations import coil3.toBitmap import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.remote.RemoteResources import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove import com.t8rin.imagetoolbox.core.ui.theme.StrongBlack import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isNetworkAvailable import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString import kotlinx.coroutines.launch @Composable internal fun FilterSelectionItem( filter: UiFilter<*>, isFavoritePage: Boolean, canOpenPreview: Boolean, favoriteFilters: List>, onLongClick: (() -> Unit)?, onOpenPreview: () -> Unit, onClick: (UiFilter<*>?) -> Unit, onToggleFavorite: () -> Unit, onRequestFilterMapping: ((UiFilter<*>) -> Transformation), shape: Shape, modifier: Modifier, cubeLutRemoteResources: RemoteResources? = null, cubeLutDownloadProgress: DownloadProgress? = null, onCubeLutDownloadRequest: (forceUpdate: Boolean, downloadOnlyNewData: Boolean) -> Unit = { _, _ -> } ) { val previewModel = LocalFilterPreviewModelProvider.current.preview var isBitmapDark by remember { mutableStateOf(true) } var loading by remember { mutableStateOf(false) } var showDownloadDialog by rememberSaveable { mutableStateOf(false) } var downloadOnlyNewData by rememberSaveable { mutableStateOf(false) } var forceUpdate by rememberSaveable { mutableStateOf(false) } PreferenceItemOverload( title = stringResource(filter.title), startIcon = { val scope = rememberCoroutineScope() Row(verticalAlignment = Alignment.CenterVertically) { Box(contentAlignment = Alignment.Center) { Picture( model = remember(filter, previewModel) { ImageRequest.Builder(appContext) .data(previewModel.data) .error(R.drawable.filter_preview_source) .transformations(onRequestFilterMapping(filter)) .diskCacheKey(filter::class.simpleName + previewModel.data.hashCode()) .memoryCacheKey(filter::class.simpleName + previewModel.data.hashCode()) .size(300, 300) .build() }, contentScale = ContentScale.Crop, contentDescription = null, onLoading = { loading = true }, onSuccess = { loading = false scope.launch { isBitmapDark = calculateBrightnessEstimate(it.result.image.toBitmap()) < 110 } }, modifier = Modifier .size(48.dp) .scale(1.2f) .clip(MaterialTheme.shapes.medium) .transparencyChecker() .shimmer(loading) ) if (canOpenPreview) { Box( modifier = Modifier .size(36.dp) .clip(ShapeDefaults.circle) .hapticsClickable(onClick = onOpenPreview), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.Slideshow, contentDescription = stringResource(R.string.image_preview), tint = if (isBitmapDark) StrongBlack else White, modifier = Modifier.scale(1.2f) ) Icon( imageVector = Icons.Rounded.Slideshow, contentDescription = stringResource(R.string.image_preview), tint = if (isBitmapDark) White else StrongBlack ) } } } Spacer(Modifier.width(16.dp)) Box( modifier = Modifier .height(36.dp) .width(1.dp) .background(MaterialTheme.colorScheme.outlineVariant()) ) } }, endIcon = { EnhancedIconButton( onClick = onToggleFavorite, modifier = Modifier.offset(8.dp) ) { val inFavorite by remember(favoriteFilters, filter) { derivedStateOf { favoriteFilters.filterIsInstance(filter::class.java).isNotEmpty() } } AnimatedContent( targetState = inFavorite to isFavoritePage, transitionSpec = { (fadeIn() + scaleIn(initialScale = 0.85f)) .togetherWith(fadeOut() + scaleOut(targetScale = 0.85f)) } ) { (isInFavorite, isFavPage) -> val icon by remember(isInFavorite, isFavPage) { derivedStateOf { when { isFavPage && isInFavorite -> Icons.Rounded.BookmarkRemove isInFavorite -> Icons.Rounded.Bookmark else -> Icons.Rounded.BookmarkBorder } } } Icon( imageVector = icon, contentDescription = null ) } } }, modifier = modifier.fillMaxWidth(), shape = shape, onLongClick = onLongClick, onClick = { onClick(null) }, drawStartIconContainer = false, bottomContent = { FilterSelectionCubeLutBottomContent( cubeLutRemoteResources = cubeLutRemoteResources, shape = shape, onShowDownloadDialog = { forceUpdateP, downloadOnlyNewDataP -> showDownloadDialog = true forceUpdate = forceUpdateP downloadOnlyNewData = downloadOnlyNewDataP }, onRequestFilterMapping = onRequestFilterMapping, onClick = onClick ) } ) CubeLutDownloadDialog( visible = showDownloadDialog, onDismiss = { showDownloadDialog = false }, onDownload = { if (appContext.isNetworkAvailable()) { onCubeLutDownloadRequest( forceUpdate, downloadOnlyNewData ) showDownloadDialog = false } else { AppToastHost.showToast( message = getString(R.string.no_connection), icon = Icons.Outlined.SignalCellularConnectedNoInternet0Bar, duration = ToastDuration.Long ) } }, downloadOnlyNewData = downloadOnlyNewData, cubeLutDownloadProgress = cubeLutDownloadProgress ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateAddingGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import android.annotation.SuppressLint import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AutoFixHigh import androidx.compose.material.icons.outlined.FileOpen import androidx.compose.material.icons.rounded.QrCodeScanner import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberBarcodeScanner import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton @SuppressLint("StringFormatInvalid") @Composable internal fun FilterTemplateAddingGroup( component: FilterTemplateCreationSheetComponent, onAddTemplateFilterFromString: ( string: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) -> Unit, onAddTemplateFilterFromUri: ( uri: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) -> Unit ) { val context = LocalResourceManager.current fun addTemplateFilterFromString( string: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) = onAddTemplateFilterFromString(string, onSuccess, onFailure) fun addTemplateFilterFromUri( uri: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) = onAddTemplateFilterFromUri(uri, onSuccess, onFailure) val scanner = rememberBarcodeScanner { addTemplateFilterFromString( string = it.raw, onSuccess = { filterName, filtersCount -> AppToastHost.showToast( message = context.getString( R.string.added_filter_template, filterName, filtersCount ), icon = Icons.Outlined.AutoFixHigh ) }, onFailure = { AppToastHost.showToast( message = context.getString(R.string.scanned_qr_code_isnt_filter_template), icon = Icons.Rounded.QrCodeScanner ) } ) } val importFromFileLauncher = rememberFilePicker { uri: Uri -> addTemplateFilterFromUri( uri = uri.toString(), onSuccess = { filterName, filtersCount -> AppToastHost.showToast( message = context.getString( R.string.added_filter_template, filterName, filtersCount ), icon = Icons.Outlined.AutoFixHigh ) }, onFailure = { AppToastHost.showToast( message = context.getString(R.string.opened_file_have_no_filter_template), icon = Icons.Outlined.AutoFixHigh ) } ) } var showFilterTemplateCreationSheet by rememberSaveable { mutableStateOf(false) } Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier .padding(top = 16.dp) .fillMaxWidth() ) { EnhancedIconButton( onClick = scanner::scan, containerColor = MaterialTheme.colorScheme.tertiary ) { Icon( imageVector = Icons.Rounded.QrCodeScanner, contentDescription = "Scan QR" ) } EnhancedIconButton( onClick = importFromFileLauncher::pickFile, containerColor = MaterialTheme.colorScheme.secondary ) { Icon( imageVector = Icons.Outlined.FileOpen, contentDescription = "Import From File" ) } Spacer(modifier = Modifier.width(8.dp)) EnhancedButton( onClick = { showFilterTemplateCreationSheet = true }, containerColor = MaterialTheme.colorScheme.primary ) { Text(stringResource(R.string.create_new)) } } FilterTemplateCreationSheet( visible = showFilterTemplateCreationSheet, onDismiss = { showFilterTemplateCreationSheet = false }, component = component ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateCreationSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import android.graphics.Bitmap import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.outlined.Extension import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.childContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.filters.domain.FilterParamsInteractor import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.ImageHeaderState import com.t8rin.imagetoolbox.core.ui.widget.image.SimplePicture import com.t8rin.imagetoolbox.core.ui.widget.image.imageStickyHeader import com.t8rin.imagetoolbox.core.ui.widget.modifier.CornerSides import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.modifier.only import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberAvailableHeight import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @Composable fun FilterTemplateCreationSheet( component: FilterTemplateCreationSheetComponent, visible: Boolean, onDismiss: () -> Unit, initialTemplateFilter: TemplateFilter? = null ) { val previewModel = LocalFilterPreviewModelProvider.current.preview val isPortrait by isPortraitOrientationAsState() var showAddFilterSheet by rememberSaveable { mutableStateOf(false) } var showExitDialog by remember { mutableStateOf(false) } var showReorderSheet by rememberSaveable { mutableStateOf(false) } val canSave = component.filterList.isNotEmpty() EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!canSave) onDismiss() else showExitDialog = true }, cancelable = false, title = { TitleItem( text = stringResource(id = R.string.create_template), icon = Icons.Outlined.Extension ) }, confirmButton = { EnhancedButton( enabled = canSave && component.templateName.isNotEmpty(), containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { component.saveTemplate(initialTemplateFilter) onDismiss() } ) { Text(stringResource(id = R.string.save)) } }, dragHandle = { Column( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke(autoElevation = 3.dp) .zIndex(Float.MAX_VALUE) .background(EnhancedBottomSheetDefaults.barContainerColor) .padding(8.dp) ) { EnhancedIconButton( onClick = { if (!canSave) onDismiss() else showExitDialog = true } ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } } }, enableBackHandler = !canSave ) { component.AttachLifecycle() var imageState by remember { mutableStateOf(ImageHeaderState(2)) } var selectedUri by rememberSaveable { mutableStateOf(null) } LaunchedEffect(selectedUri) { component.setUri(selectedUri) } LaunchedEffect(initialTemplateFilter) { initialTemplateFilter?.let { component.setInitialTemplateFilter(it) } } if (visible) { BackHandler(enabled = canSave) { showExitDialog = true } } val preview: @Composable () -> Unit = { Box( modifier = Modifier .fillMaxSize() .clip( if (isPortrait) ShapeDefaults.extraLarge.only(CornerSides.Bottom) else RectangleShape ) .background( color = MaterialTheme.colorScheme .surfaceContainer .copy(0.8f) ) .shimmer(component.previewBitmap == null && component.isImageLoading), contentAlignment = Alignment.Center ) { SimplePicture( enableContainer = false, boxModifier = Modifier.padding(24.dp), bitmap = component.previewBitmap, loading = component.isImageLoading ) } } Row { val backgroundColor = MaterialTheme.colorScheme.surfaceContainerLow if (!isPortrait) { Box(modifier = Modifier.weight(1.3f)) { preview() } } val internalHeight = rememberAvailableHeight(imageState = imageState) LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f), flingBehavior = enhancedFlingBehavior() ) { imageStickyHeader( visible = isPortrait, internalHeight = internalHeight, imageState = imageState, onStateChange = { imageState = it }, padding = 0.dp, backgroundColor = backgroundColor, imageBlock = preview ) item { AnimatedContent( targetState = component.filterList.isNotEmpty(), transitionSpec = { fadeIn() + expandVertically() togetherWith fadeOut() + shrinkVertically() } ) { notEmpty -> Column( modifier = Modifier.padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(Modifier.height(16.dp)) ImageSelector( value = selectedUri ?: previewModel.data, onValueChange = { selectedUri = it }, subtitle = stringResource(id = R.string.select_template_preview), shape = ShapeDefaults.default, color = Color.Unspecified ) Spacer(Modifier.height(8.dp)) RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Text ), onValueChange = component::updateTemplateName, value = component.templateName, label = stringResource(R.string.template_name) ) if (notEmpty) { Spacer(Modifier.height(8.dp)) Column( modifier = Modifier .container(MaterialTheme.shapes.extraLarge) ) { TitleItem(text = stringResource(R.string.filters)) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(8.dp) ) { component.filterList.forEachIndexed { index, filter -> FilterItem( backgroundColor = MaterialTheme.colorScheme.surface, filter = filter, onFilterChange = { component.updateFilter( value = it, index = index, showError = AppToastHost::showFailureToast ) }, onLongPress = { showReorderSheet = true }, showDragHandle = false, onRemove = { component.removeFilterAtIndex( index ) }, onCreateTemplate = null ) } AddFilterButton( onClick = { showAddFilterSheet = true }, modifier = Modifier.padding( horizontal = 16.dp ) ) } } Spacer(Modifier.height(16.dp)) } else { Spacer(Modifier.height(8.dp)) AddFilterButton( onClick = { showAddFilterSheet = true } ) Spacer(Modifier.height(16.dp)) } } } } } } } AddFiltersSheet( component = component.addFiltersSheetComponent, visible = showAddFilterSheet, onVisibleChange = { showAddFilterSheet = it }, canAddTemplates = false, previewBitmap = component.previewBitmap, onFilterPicked = { component.addFilter(it.newInstance()) }, onFilterPickedWithParams = { component.addFilter(it) }, filterTemplateCreationSheetComponent = component ) FilterReorderSheet( filterList = component.filterList, visible = showReorderSheet, onDismiss = { showReorderSheet = false }, onReorder = component::updateFiltersOrder ) ExitWithoutSavingDialog( onExit = onDismiss, onDismiss = { showExitDialog = false }, visible = showExitDialog, placeAboveAll = true ) } class FilterTemplateCreationSheetComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, private val imageGetter: ImageGetter, private val filterParamsInteractor: FilterParamsInteractor, private val filterProvider: FilterProvider, dispatchersHolder: DispatchersHolder, addFiltersSheetComponentFactory: AddFiltersSheetComponent.Factory ) : BaseComponent(dispatchersHolder, componentContext) { val addFiltersSheetComponent: AddFiltersSheetComponent = addFiltersSheetComponentFactory( componentContext = componentContext.childContext( key = "addFiltersTemplate" ) ) private val _previewModel: MutableState = mutableStateOf(ImageModel("")) private val _filterList: MutableState>> = mutableStateOf(emptyList()) val filterList by _filterList private val _templateName: MutableState = mutableStateOf("") val templateName by _templateName private var bitmapUri: Uri? by mutableStateOf(null) private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap by _previewBitmap init { filterParamsInteractor .getFilterPreviewModel().onEach { data -> _previewModel.update { data } }.launchIn(componentScope) } fun updateTemplateName(newName: String) { _templateName.update { newName.filter { it.isLetter() || it.isWhitespace() }.trim() } } private fun updatePreview() { debouncedImageCalculation { _previewBitmap.update { imageGetter.getImageWithTransformations( data = bitmapUri ?: _previewModel.value.data, transformations = filterList.map { filterProvider.filterToTransformation(it) }, size = IntegerSize(1000, 1000) ) } } } fun removeFilterAtIndex(index: Int) { _filterList.update { it.toMutableList().apply { removeAt(index) } } updatePreview() } fun updateFilter( value: T, index: Int, showError: (Throwable) -> Unit ) { val list = _filterList.value.toMutableList() runCatching { list[index] = list[index].copy(value) _filterList.update { list } }.exceptionOrNull()?.let { throwable -> showError(throwable) list[index] = list[index].newInstance() _filterList.update { list } } updatePreview() } fun updateFiltersOrder(uiFilters: List>) { _filterList.update { uiFilters } updatePreview() } fun addFilter(filter: UiFilter<*>) { _filterList.update { it + filter } updatePreview() } fun saveTemplate(initialTemplateFilter: TemplateFilter?) { componentScope.launch { if (initialTemplateFilter != null) { filterParamsInteractor.removeTemplateFilter(initialTemplateFilter) } filterParamsInteractor.addTemplateFilter( TemplateFilter( name = templateName, filters = filterList ) ) } } fun setUri(selectedUri: Uri?) { bitmapUri = selectedUri updatePreview() } private var isInitialValueSetAlready: Boolean = false fun setInitialTemplateFilter(filter: TemplateFilter) { if (templateName.isEmpty() && filterList.isEmpty() && !isInitialValueSetAlready) { _templateName.update { filter.name } _filterList.update { filter.filters.map { it.toUiFilter() } } isInitialValueSetAlready = true } } override fun resetState() { _filterList.update { emptyList() } _templateName.update { "" } cancelImageLoading() _previewBitmap.update { null } bitmapUri = null isInitialValueSetAlready = false addFiltersSheetComponent.resetState() } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext ): FilterTemplateCreationSheetComponent } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/FilterTemplateInfoSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.filters.presentation.widget import android.graphics.Bitmap import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AutoFixHigh import androidx.compose.material.icons.outlined.Extension import androidx.compose.material.icons.rounded.FilePresent import androidx.compose.material.icons.rounded.QrCode import androidx.compose.material.icons.rounded.QrCode2 import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min import coil3.request.ImageRequest import coil3.request.error import coil3.request.transformations import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.EditAlt import com.t8rin.imagetoolbox.core.ui.utils.capturable.capturable import com.t8rin.imagetoolbox.core.ui.utils.capturable.rememberCaptureController import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.other.QrCode import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.appContext import kotlinx.coroutines.launch @Composable internal fun FilterTemplateInfoSheet( component: FilterTemplateCreationSheetComponent, visible: Boolean, onDismiss: (Boolean) -> Unit, templateFilter: TemplateFilter, onShareImage: (Bitmap) -> Unit, onSaveImage: (Bitmap) -> Unit, onSaveFile: (fileUri: Uri, content: String) -> Unit, onConvertTemplateFilterToString: suspend (TemplateFilter) -> String, onRemoveTemplateFilter: (TemplateFilter) -> Unit, onShareFile: (content: String) -> Unit, onRequestTemplateFilename: () -> String, onRequestFilterMapping: (UiFilter<*>) -> Transformation ) { EnhancedModalBottomSheet( visible = visible, onDismiss = onDismiss, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onDismiss(false) } ) { Text(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(id = R.string.template_filter), icon = Icons.Outlined.Extension ) } ) { var filterContent by rememberSaveable { mutableStateOf("") } LaunchedEffect(filterContent) { if (filterContent.isEmpty()) { filterContent = onConvertTemplateFilterToString(templateFilter) } } var showShareDialog by rememberSaveable { mutableStateOf(false) } var showDeleteDialog by rememberSaveable { mutableStateOf(false) } var showEditTemplateSheet by rememberSaveable { mutableStateOf(false) } val scope = rememberCoroutineScope() val captureController = rememberCaptureController() LazyColumn( modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.CenterHorizontally, flingBehavior = enhancedFlingBehavior() ) { item { Column(Modifier.capturable(captureController)) { Spacer(modifier = Modifier.height(32.dp)) BoxWithConstraints( modifier = Modifier .background( color = MaterialTheme.colorScheme.surfaceContainerLowest, shape = ShapeDefaults.default ) .padding(16.dp) ) { val targetSize = min(min(this.maxWidth, maxHeight), 300.dp) Column( horizontalAlignment = Alignment.CenterHorizontally ) { QrCode( content = filterContent, qrParams = QrCodeParams(), modifier = Modifier .padding(top = 36.dp, bottom = 16.dp) .size(targetSize) ) Text( text = templateFilter.name, style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center, modifier = Modifier.width(targetSize) ) } TemplateFilterPreviewItem( modifier = Modifier .align(Alignment.TopCenter) .offset(y = (-48).dp) .size(64.dp), templateFilter = templateFilter, onRequestFilterMapping = onRequestFilterMapping ) } } Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 8.dp) ) { EnhancedIconButton( onClick = { showDeleteDialog = true }, containerColor = MaterialTheme.colorScheme.error ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete) ) } EnhancedButton( onClick = { showShareDialog = true }, modifier = Modifier.fillMaxWidth(0.75f) ) { Text(stringResource(R.string.share)) } EnhancedIconButton( onClick = { showEditTemplateSheet = true }, containerColor = MaterialTheme.colorScheme.secondary ) { Icon( imageVector = Icons.Rounded.EditAlt, contentDescription = stringResource(R.string.edit) ) } } } } EnhancedAlertDialog( visible = showDeleteDialog, onDismissRequest = { showDeleteDialog = false }, confirmButton = { EnhancedButton( onClick = { showDeleteDialog = false } ) { Text(stringResource(R.string.cancel)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onRemoveTemplateFilter(templateFilter) onDismiss(false) showDeleteDialog = false } ) { Text(stringResource(R.string.delete)) } }, title = { Text(stringResource(R.string.delete_template)) }, icon = { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete) ) }, text = { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { TemplateFilterPreviewItem( modifier = Modifier .sizeIn( maxHeight = 80.dp, maxWidth = 80.dp ) .aspectRatio(1f), templateFilter = templateFilter, onRequestFilterMapping = onRequestFilterMapping ) Spacer(modifier = Modifier.height(16.dp)) Text(stringResource(R.string.delete_template_warn)) } } ) val saveLauncher = rememberFileCreator( onSuccess = { uri -> showShareDialog = false onSaveFile(uri, filterContent) } ) EnhancedAlertDialog( visible = showShareDialog, onDismissRequest = { showShareDialog = false }, confirmButton = { EnhancedButton( onClick = { showShareDialog = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.cancel)) } }, title = { Text(stringResource(R.string.share)) }, icon = { Icon( imageVector = Icons.Outlined.AutoFixHigh, contentDescription = null ) }, text = { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { PreferenceItem( title = stringResource(R.string.as_qr_code), shape = ShapeDefaults.top, startIcon = Icons.Rounded.QrCode, onClick = { showShareDialog = false scope.launch { captureController.captureAsync() .await() .asAndroidBitmap() .let(onShareImage) } }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) Spacer(Modifier.height(4.dp)) PreferenceItem( title = stringResource(R.string.as_file), shape = ShapeDefaults.center, startIcon = Icons.Rounded.FilePresent, onClick = { showShareDialog = false onShareFile(filterContent) }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) Spacer(Modifier.height(4.dp)) PreferenceItem( title = stringResource(R.string.save_as_qr_code_image), shape = ShapeDefaults.center, startIcon = Icons.Rounded.QrCode2, onClick = { showShareDialog = false scope.launch { captureController.captureAsync() .await() .asAndroidBitmap() .let(onSaveImage) } }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) Spacer(Modifier.height(4.dp)) PreferenceItem( title = stringResource(R.string.save_as_file), shape = ShapeDefaults.bottom, startIcon = Icons.Rounded.Save, onClick = { saveLauncher.make(onRequestTemplateFilename()) }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) } } ) FilterTemplateCreationSheet( visible = showEditTemplateSheet, onDismiss = { showEditTemplateSheet = false }, initialTemplateFilter = templateFilter, component = component ) } } @Composable internal fun TemplateFilterPreviewItem( modifier: Modifier, onRequestFilterMapping: (UiFilter<*>) -> Transformation, templateFilter: TemplateFilter ) { val previewModel = LocalFilterPreviewModelProvider.current.preview var loading by remember { mutableStateOf(false) } Picture( model = remember(templateFilter, previewModel) { ImageRequest.Builder(appContext) .data(previewModel.data) .error(R.drawable.filter_preview_source) .transformations(templateFilter.filters.map { onRequestFilterMapping(it.toUiFilter()) }) .diskCacheKey(templateFilter.toString() + previewModel.data.hashCode()) .memoryCacheKey(templateFilter.toString() + previewModel.data.hashCode()) .size(300, 300) .build() }, onLoading = { loading = true }, onSuccess = { loading = false }, contentScale = ContentScale.Crop, contentDescription = null, modifier = modifier .clip(MaterialTheme.shapes.medium) .transparencyChecker() .shimmer(loading) ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/TemplateFilterSelectionItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.rounded.Slideshow import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.request.ImageRequest import coil3.request.error import coil3.request.transformations import coil3.toBitmap import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.StrongBlack import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.utils.appContext import kotlinx.coroutines.launch @Composable internal fun TemplateFilterSelectionItem( templateFilter: TemplateFilter, onClick: () -> Unit, onLongClick: () -> Unit, onRequestFilterMapping: (UiFilter<*>) -> Transformation, onInfoClick: () -> Unit, shape: Shape, modifier: Modifier ) { val previewModel = LocalFilterPreviewModelProvider.current.preview var loading by remember { mutableStateOf(false) } var isBitmapDark by remember { mutableStateOf(true) } val scope = rememberCoroutineScope() PreferenceItemOverload( title = templateFilter.name, startIcon = { Row(verticalAlignment = Alignment.CenterVertically) { Box(contentAlignment = Alignment.Center) { Picture( model = remember(templateFilter, previewModel) { ImageRequest.Builder(appContext) .data(previewModel.data) .error(R.drawable.filter_preview_source) .transformations( templateFilter.filters.map { onRequestFilterMapping( it.toUiFilter() ) } ) .diskCacheKey(templateFilter.toString() + previewModel.data.hashCode()) .memoryCacheKey(templateFilter.toString() + previewModel.data.hashCode()) .size(300, 300) .build() }, onLoading = { loading = true }, onSuccess = { loading = false scope.launch { isBitmapDark = calculateBrightnessEstimate(it.result.image.toBitmap()) < 110 } }, contentScale = ContentScale.Crop, contentDescription = null, modifier = Modifier .size(48.dp) .scale(1.2f) .clip(MaterialTheme.shapes.medium) .transparencyChecker() .shimmer(loading) ) Box( modifier = Modifier .size(36.dp) .clip(ShapeDefaults.circle) .hapticsClickable(onClick = onLongClick), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.Slideshow, contentDescription = stringResource(R.string.image_preview), tint = if (isBitmapDark) StrongBlack else White, modifier = Modifier.scale(1.2f) ) Icon( imageVector = Icons.Rounded.Slideshow, contentDescription = stringResource(R.string.image_preview), tint = if (isBitmapDark) White else StrongBlack ) } } Spacer(Modifier.width(16.dp)) Box( modifier = Modifier .height(36.dp) .width(1.dp) .background(MaterialTheme.colorScheme.outlineVariant()) ) } }, endIcon = { EnhancedIconButton( onClick = onInfoClick, containerColor = MaterialTheme.colorScheme.secondaryContainer, modifier = Modifier.offset(8.dp) ) { Icon( imageVector = Icons.Outlined.Info, contentDescription = null ) } }, modifier = modifier.fillMaxWidth(), shape = shape, onClick = onClick, drawStartIconContainer = false ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters import android.graphics.Bitmap import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PrimaryScrollableTabRow import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Tab import androidx.compose.material3.TabRowDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.collectAsUiState import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterPreviewSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterSelectionItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalSheetDragHandle import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberRetainedLazyListState import com.t8rin.imagetoolbox.core.utils.getString import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.Locale @Composable fun AddFiltersSheet( component: AddFiltersSheetComponent, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent, visible: Boolean, onVisibleChange: (Boolean) -> Unit, previewBitmap: Bitmap?, onFilterPicked: (UiFilter<*>) -> Unit, onFilterPickedWithParams: (UiFilter<*>) -> Unit, canAddTemplates: Boolean = true ) { val favoriteFilters by component.favoritesFlow.collectAsUiState() val tabs: List by remember(canAddTemplates, favoriteFilters) { derivedStateOf { buildList { if (canAddTemplates) { add(UiFilter.Group.Template) } add(UiFilter.Group.Favorite(favoriteFilters)) addAll(UiFilter.groups) } } } val haptics = LocalHapticFeedback.current val pagerState = rememberPagerState( pageCount = { tabs.size }, initialPage = 2 ) val onRequestFilterMapping = component::filterToTransformation var isSearching by rememberSaveable { mutableStateOf(false) } var searchKeyword by rememberSaveable(isSearching) { mutableStateOf("") } val allFilters = remember { tabs.flatMap { group -> group.filters(canAddTemplates).sortedBy { getString(it.title) } } } var filtersForSearch by remember(allFilters) { mutableStateOf(allFilters) } val resourceManager = LocalResourceManager.current LaunchedEffect(searchKeyword) { withContext(Dispatchers.Default) { delay(400L) // Debounce calculations if (searchKeyword.isEmpty()) { filtersForSearch = allFilters return@withContext } filtersForSearch = allFilters.filter { resourceManager.getString(it.title).contains( other = searchKeyword, ignoreCase = true ) || resourceManager.getStringLocalized( resId = it.title, language = Locale.ENGLISH.language ).contains( other = searchKeyword, ignoreCase = true ) }.sortedBy { getString(it.title) } } } EnhancedModalBottomSheet( dragHandle = { EnhancedModalSheetDragHandle { AnimatedVisibility(visible = !isSearching) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { PrimaryScrollableTabRow( divider = {}, edgePadding = 16.dp, containerColor = EnhancedBottomSheetDefaults.barContainerColor, selectedTabIndex = pagerState.currentPage, indicator = { TabRowDefaults.PrimaryIndicator( modifier = Modifier.tabIndicatorOffset( selectedTabIndex = pagerState.currentPage, matchContentSize = true ), width = Dp.Unspecified, height = 4.dp, shape = AutoCornersShape( topStart = 100f, topEnd = 100f ) ) } ) { val scope = rememberCoroutineScope() tabs.forEachIndexed { index, (icon, title) -> val selected = pagerState.currentPage == index val color by animateColorAsState( if (selected) { MaterialTheme.colorScheme.primary } else MaterialTheme.colorScheme.onSurface ) val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = AutoCornersShape(42.dp), pressedShape = ShapeDefaults.default, interactionSource = interactionSource ) Tab( interactionSource = interactionSource, unselectedContentColor = MaterialTheme.colorScheme.onSurface, modifier = Modifier .padding(8.dp) .clip(shape), selected = selected, onClick = { haptics.longPress() scope.launch { pagerState.animateScrollToPage(index) } }, icon = { Icon( imageVector = icon, contentDescription = null, tint = color ) }, text = { Text( text = stringResource(title), color = color ) } ) } } } } } }, sheetContent = { component.AttachLifecycle() AnimatedContent( modifier = Modifier.weight(1f, false), targetState = isSearching ) { isSearching -> if (isSearching) { AnimatedContent( targetState = filtersForSearch.isNotEmpty() ) { isNotEmpty -> if (isNotEmpty) { LazyColumn( state = rememberRetainedLazyListState("sheet"), verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.animateContentSizeNoClip(), contentPadding = PaddingValues(16.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = filtersForSearch, key = { _, f -> f.hashCode() } ) { index, filter -> FilterSelectionItem( filter = filter, isFavoritePage = false, canOpenPreview = previewBitmap != null, favoriteFilters = favoriteFilters, onLongClick = { component.setPreviewData(filter) }, onOpenPreview = { component.setPreviewData(filter) }, onClick = { onVisibleChange(false) onFilterPicked(filter) }, onRequestFilterMapping = onRequestFilterMapping, shape = ShapeDefaults.byIndex( index = index, size = filtersForSearch.size ), onToggleFavorite = { component.toggleFavorite(filter) }, modifier = Modifier.animateItem() ) } } } else { Column( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.5f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } } } else { HorizontalPager( state = pagerState, beyondViewportPageCount = 2 ) { page -> when (val group = tabs[page]) { is UiFilter.Group.Template -> { TemplatesContent( component = component, filterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent, onVisibleChange = onVisibleChange, onFilterPickedWithParams = onFilterPickedWithParams ) } is UiFilter.Group.Favorite -> { FavoritesContent( component = component, onVisibleChange = onVisibleChange, onFilterPickedWithParams = onFilterPickedWithParams, onFilterPicked = onFilterPicked, previewBitmap = previewBitmap ) } else -> { val filters by remember(group, canAddTemplates) { derivedStateOf { group.filters(canAddTemplates) } } OtherContent( component = component, currentGroup = group, filters = filters, onVisibleChange = onVisibleChange, onFilterPickedWithParams = onFilterPickedWithParams, onFilterPicked = onFilterPicked, previewBitmap = previewBitmap ) } } } } } }, title = { AnimatedContent( targetState = isSearching ) { searching -> if (searching) { BackHandler { searchKeyword = "" isSearching = false } ProvideTextStyle(value = MaterialTheme.typography.bodyLarge) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = searchKeyword, onValueChange = { searchKeyword = it }, startIcon = { EnhancedIconButton( onClick = { searchKeyword = "" isSearching = false }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = MaterialTheme.colorScheme.onSurface ) } }, endIcon = { AnimatedVisibility( visible = searchKeyword.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = { searchKeyword = "" }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface ) } } }, shape = ShapeDefaults.circle ) } } else { Row( verticalAlignment = Alignment.CenterVertically ) { TitleItem( text = stringResource(R.string.filter), icon = Icons.Rounded.AutoFixHigh ) Spacer(modifier = Modifier.weight(1f)) EnhancedIconButton( onClick = { isSearching = true }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Icon( imageVector = Icons.Rounded.Search, contentDescription = stringResource(R.string.search_here) ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onVisibleChange(false) } ) { AutoSizeText(stringResource(R.string.close)) } Spacer(Modifier.width(8.dp)) } } } }, confirmButton = {}, enableBottomContentWeight = false, visible = visible, onDismiss = onVisibleChange ) FilterPreviewSheet( component = component, onFilterPickedWithParams = onFilterPickedWithParams, onVisibleChange = onVisibleChange, previewBitmap = previewBitmap ) } @Composable fun AddFiltersSheet( component: AddFiltersSheetComponent, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent, visible: Boolean, onDismiss: () -> Unit, previewBitmap: Bitmap?, onFilterPicked: (UiFilter<*>) -> Unit, onFilterPickedWithParams: (UiFilter<*>) -> Unit, canAddTemplates: Boolean = true ) { AddFiltersSheet( component = component, filterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent, visible = visible, onVisibleChange = { if (!it) onDismiss() }, previewBitmap = previewBitmap, onFilterPicked = onFilterPicked, onFilterPickedWithParams = onFilterPickedWithParams, canAddTemplates = canAddTemplates ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/AddFiltersSheetComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import coil3.transform.Transformation import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.remote.RemoteResources import com.t8rin.imagetoolbox.core.domain.remote.RemoteResourcesStore import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.filters.domain.FilterParamsInteractor import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.toCoil import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow class AddFiltersSheetComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, private val filterProvider: FilterProvider, private val imageTransformer: ImageTransformer, private val shareProvider: ImageShareProvider, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val favoriteInteractor: FilterParamsInteractor, private val imageGetter: ImageGetter, private val remoteResourcesStore: RemoteResourcesStore, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _previewData: MutableState>?> = mutableStateOf(null) val previewData by _previewData private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap by _previewBitmap private val _cubeLutRemoteResources: MutableState = mutableStateOf(RemoteResources.CubeLutDefault) val cubeLutRemoteResources by _cubeLutRemoteResources private val _cubeLutDownloadProgress: MutableState = mutableStateOf(null) val cubeLutDownloadProgress by _cubeLutDownloadProgress init { updateCubeLuts( startDownloadIfNeeded = false, forceUpdate = false, onFailure = {}, downloadOnlyNewData = false ) } fun setFilterPreviewModel(uri: String) { componentScope.launch { favoriteInteractor.setFilterPreviewModel(uri) favoriteInteractor.setCanSetDynamicFilterPreview(false) } } fun setCanSetDynamicFilterPreview(value: Boolean) { componentScope.launch { favoriteInteractor.setCanSetDynamicFilterPreview(value) } } fun updateCubeLuts( startDownloadIfNeeded: Boolean, forceUpdate: Boolean, onFailure: (Throwable) -> Unit, downloadOnlyNewData: Boolean = false ) { componentScope.launch { remoteResourcesStore.getResources( name = RemoteResources.CUBE_LUT, forceUpdate = forceUpdate, onDownloadRequest = { name -> if (startDownloadIfNeeded) { remoteResourcesStore.downloadResources( name = name, onProgress = { progress -> _cubeLutDownloadProgress.update { progress } }, onFailure = onFailure, downloadOnlyNewData = downloadOnlyNewData ) } else null } )?.let { data -> _cubeLutRemoteResources.update { data } } _cubeLutDownloadProgress.update { null } } } fun setPreviewData(data: UiFilter<*>?) { _previewData.update { data?.let { filter -> listOf( filter.copy(filter.value).apply { isVisible = true } ) } } } fun setPreviewData(data: List>) { _previewData.update { data.map { it.toUiFilter() } } } fun filterToTransformation( filter: UiFilter<*> ): Transformation = filterProvider.filterToTransformation(filter).toCoil() fun updatePreview(previewBitmap: Bitmap) { debouncedImageCalculation { _previewBitmap.update { imageTransformer.transform( image = previewBitmap, transformations = previewData?.map { filterProvider.filterToTransformation(it) } ?: emptyList(), size = IntegerSize(2000, 2000) ) } } } fun removeFilterAtIndex(index: Int) { _previewData.update { it?.toMutableList()?.apply { removeAt(index) } } } fun updateFilter( value: T, index: Int ) { val list = (previewData ?: emptyList()).toMutableList() runCatching { list[index] = list[index].copy(value) _previewData.update { list } }.onFailure { list[index] = list[index].newInstance() _previewData.update { list } } } fun shareImage( bitmap: Bitmap ) { componentScope.launch { shareProvider.shareImage( imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = ImageFormat.Png.Lossless ), image = bitmap, onComplete = AppToastHost::showConfetti ) } } fun saveImage( bitmap: Bitmap ) { componentScope.launch { val imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = ImageFormat.Png.Lossless ) parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = "", sequenceNumber = null, data = imageCompressor.compress( image = bitmap, imageFormat = imageInfo.imageFormat, quality = Quality.Base() ) ), keepOriginalMetadata = true ) ) } } fun saveContentTo( content: String, fileUri: Uri ) { componentScope.launch { fileController.writeBytes( uri = fileUri.toString(), block = { it.writeBytes(content.toByteArray()) } ).also(::parseFileSaveResult).onSuccess(::registerSave) } } fun shareContent( content: String, filename: String ) { componentScope.launch { shareProvider.shareData( writeData = { it.writeBytes(content.toByteArray()) }, filename = filename, onComplete = AppToastHost::showConfetti ) } } fun createTemplateFilename(templateFilter: TemplateFilter): String { return "template(${templateFilter.name})${timestamp()}.imtbx_template" } fun reorderFavoriteFilters(value: List>) { componentScope.launch { favoriteInteractor.reorderFavoriteFilters(value) } } val favoritesFlow: Flow>> get() = favoriteInteractor.getFavoriteFilters() val templatesFlow: Flow> get() = favoriteInteractor.getTemplateFilters() fun toggleFavorite(filter: UiFilter<*>) { componentScope.launch { favoriteInteractor.toggleFavorite(filter) } } fun removeTemplateFilter(templateFilter: TemplateFilter) { componentScope.launch { favoriteInteractor.removeTemplateFilter(templateFilter) } } suspend fun convertTemplateFilterToString( templateFilter: TemplateFilter ): String = favoriteInteractor.convertTemplateFilterToString(templateFilter) fun addTemplateFilterFromString( string: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) { componentScope.launch { favoriteInteractor.addTemplateFilterFromString( string = string, onSuccess = onSuccess, onFailure = onFailure ) } } fun addTemplateFilterFromUri( uri: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit ) { componentScope.launch { favoriteInteractor.addTemplateFilterFromUri( uri = uri, onSuccess = onSuccess, onFailure = onFailure ) } } fun cacheNeutralLut() { componentScope.launch { imageGetter.getImage(R.drawable.lookup)?.let { shareProvider.cacheImage( image = it, imageInfo = ImageInfo( width = 512, height = 512, imageFormat = ImageFormat.Png.Lossless ) )?.let { uri -> Clipboard.copy(uri.toUri()) } } } } fun shareNeutralLut() { componentScope.launch { imageGetter.getImage(R.drawable.lookup)?.let { shareProvider.shareImage( image = it, imageInfo = ImageInfo( width = 512, height = 512, imageFormat = ImageFormat.Png.Lossless ), onComplete = AppToastHost::showConfetti ) } } } fun saveNeutralLut( oneTimeSaveLocationUri: String? = null ) { componentScope.launch { imageGetter.getImage(R.drawable.lookup)?.let { bitmap -> val imageInfo = ImageInfo( width = 512, height = 512, imageFormat = ImageFormat.Png.Lossless ) parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = "", sequenceNumber = null, data = imageCompressor.compress( image = bitmap, imageFormat = imageInfo.imageFormat, quality = Quality.Base() ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } } } override fun resetState() { _previewData.update { null } _previewBitmap.update { null } cancelImageLoading() } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext ): AddFiltersSheetComponent } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/FavoritesContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiCubeLutFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.collectAsUiState import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterSelectionItem import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BookmarkOff import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState @Composable internal fun FavoritesContent( component: AddFiltersSheetComponent, onVisibleChange: (Boolean) -> Unit, onFilterPickedWithParams: (UiFilter<*>) -> Unit, onFilterPicked: (UiFilter<*>) -> Unit, previewBitmap: Bitmap? ) { val onRequestFilterMapping = component::filterToTransformation val favoriteFilters by component.favoritesFlow.collectAsUiState() AnimatedContent( targetState = favoriteFilters.isEmpty() ) { noFav -> if (noFav) { Column( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.5f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.no_favorite_filters), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Outlined.BookmarkOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } else { val data = remember { mutableStateOf(favoriteFilters) } val haptics = LocalHapticFeedback.current val listState = rememberLazyListState() val state = rememberReorderableLazyListState( lazyListState = listState, onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } } ) LaunchedEffect(favoriteFilters) { if (data.value.size != favoriteFilters.size) { data.value = favoriteFilters } } LazyColumn( state = listState, modifier = Modifier.fillMaxHeight(), verticalArrangement = Arrangement.spacedBy( space = 4.dp, alignment = Alignment.CenterVertically ), contentPadding = PaddingValues(16.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data.value, key = { _, f -> f.hashCode() } ) { index, filter -> ReorderableItem( state = state, key = filter.hashCode() ) { isDragging -> FilterSelectionItem( filter = filter, isFavoritePage = true, canOpenPreview = previewBitmap != null, favoriteFilters = favoriteFilters, onLongClick = null, onOpenPreview = { component.setPreviewData(filter) }, onClick = { custom -> onVisibleChange(false) if (custom != null) { onFilterPickedWithParams(custom) } else { onFilterPicked(filter) } }, onRequestFilterMapping = onRequestFilterMapping, shape = ShapeDefaults.byIndex( index = index, size = favoriteFilters.size ), onToggleFavorite = { component.toggleFavorite(filter) }, modifier = Modifier .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { component.reorderFavoriteFilters(data.value) } ) .scale( animateFloatAsState( if (isDragging) 1.05f else 1f ).value ), cubeLutRemoteResources = if (filter is UiCubeLutFilter) { component.cubeLutRemoteResources } else null, cubeLutDownloadProgress = if (filter is UiCubeLutFilter) { component.cubeLutDownloadProgress } else null, onCubeLutDownloadRequest = { forceUpdate, downloadOnlyNewData -> component.updateCubeLuts( startDownloadIfNeeded = true, forceUpdate = forceUpdate, onFailure = AppToastHost::showFailureToast, downloadOnlyNewData = downloadOnlyNewData ) } ) } } } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/OtherContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters import android.graphics.Bitmap import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ImageSearch import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiCubeLutFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.collectAsUiState import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterSelectionItem import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberRetainedLazyListState @Composable internal fun OtherContent( component: AddFiltersSheetComponent, currentGroup: UiFilter.Group, filters: List>, onVisibleChange: (Boolean) -> Unit, onFilterPickedWithParams: (UiFilter<*>) -> Unit, onFilterPicked: (UiFilter<*>) -> Unit, previewBitmap: Bitmap?, ) { val favoriteFilters by component.favoritesFlow.collectAsUiState() val onRequestFilterMapping = component::filterToTransformation LazyColumn( state = rememberRetainedLazyListState("sheet$currentGroup"), verticalArrangement = Arrangement.spacedBy(4.dp), contentPadding = PaddingValues(16.dp), flingBehavior = enhancedFlingBehavior() ) { if (currentGroup is UiFilter.Group.Simple) simpleAdditionalSection(component) if (currentGroup is UiFilter.Group.LUT) lutAdditionalSection(component) itemsIndexed( items = filters, key = { _, f -> f.hashCode() } ) { index, filter -> FilterSelectionItem( filter = filter, canOpenPreview = previewBitmap != null, favoriteFilters = favoriteFilters, onLongClick = { component.setPreviewData(filter) }, onOpenPreview = { component.setPreviewData(filter) }, onClick = { custom -> onVisibleChange(false) custom?.also(onFilterPickedWithParams) ?: onFilterPicked(filter) }, onRequestFilterMapping = onRequestFilterMapping, shape = ShapeDefaults.byIndex( index = index, size = filters.size ), onToggleFavorite = { component.toggleFavorite(filter) }, isFavoritePage = false, modifier = Modifier.animateItem(), cubeLutRemoteResources = component.cubeLutRemoteResources.takeIf { filter is UiCubeLutFilter }, cubeLutDownloadProgress = component.cubeLutDownloadProgress.takeIf { filter is UiCubeLutFilter }, onCubeLutDownloadRequest = { forceUpdate, downloadOnlyNewData -> component.updateCubeLuts( startDownloadIfNeeded = true, forceUpdate = forceUpdate, onFailure = AppToastHost::showFailureToast, downloadOnlyNewData = downloadOnlyNewData ) } ) } } } private fun LazyListScope.lutAdditionalSection( component: AddFiltersSheetComponent ) { item { PreferenceItemOverload( title = stringResource(R.string.save_empty_lut), subtitle = stringResource(R.string.save_empty_lut_sub), shape = ShapeDefaults.default, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp), endIcon = { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Picture( model = R.drawable.lookup, contentScale = ContentScale.Crop, modifier = Modifier .size(48.dp) .scale(1.1f) .clip(MaterialTheme.shapes.extraSmall), shape = MaterialTheme.shapes.extraSmall ) Spacer(modifier = Modifier.height(8.dp)) var showFolderSelection by rememberSaveable { mutableStateOf(false) } val saveNeutralLut: (String?) -> Unit = { component.saveNeutralLut( oneTimeSaveLocationUri = it ) } Row { ShareButton( onShare = component::shareNeutralLut, onCopy = component::cacheNeutralLut ) EnhancedIconButton( onClick = { saveNeutralLut(null) }, onLongClick = { showFolderSelection = true } ) { Icon( imageVector = Icons.Rounded.Save, contentDescription = stringResource(R.string.save) ) } OneTimeSaveLocationSelectionDialog( visible = showFolderSelection, onDismiss = { showFolderSelection = false }, onSaveRequest = saveNeutralLut ) } } } ) } } private fun LazyListScope.simpleAdditionalSection( component: AddFiltersSheetComponent ) { item { val previewProvider = LocalFilterPreviewModelProvider.current val previewModel = previewProvider.preview val canSetDynamicFilterPreview = previewProvider.canSetDynamicFilterPreview Row( modifier = Modifier .padding(bottom = 8.dp) .height(intrinsicSize = IntrinsicSize.Max), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { ImageSelector( value = previewModel.data, onValueChange = { component.setFilterPreviewModel(it.toString()) }, title = stringResource(R.string.filter_preview_image), subtitle = stringResource(R.string.filter_preview_image_sub), contentScale = ContentScale.Crop, color = Color.Unspecified, modifier = Modifier .weight(1f) .fillMaxHeight(), shape = ShapeDefaults.start ) val containerColor by animateColorAsState( if (canSetDynamicFilterPreview) { MaterialTheme.colorScheme.secondary } else { MaterialTheme.colorScheme.secondaryContainer } ) Box( modifier = Modifier .fillMaxHeight() .clip(ShapeDefaults.center) .hapticsClickable { component.setCanSetDynamicFilterPreview(true) } .container( color = containerColor, shape = ShapeDefaults.center, resultPadding = 0.dp ) .padding(horizontal = 8.dp), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Outlined.ImageSearch, contentDescription = null, tint = MaterialTheme.colorScheme.contentColorFor(containerColor) ) } Column( modifier = Modifier.fillMaxHeight(), verticalArrangement = Arrangement.spacedBy(4.dp) ) { repeat(2) { index -> val shape = if (index == 0) { ShapeDefaults.topEnd } else { ShapeDefaults.bottomEnd } val containerColor = takeColorFromScheme { when { canSetDynamicFilterPreview -> secondaryContainer previewModel.data == R.drawable.filter_preview_source && index == 0 -> secondary previewModel.data == R.drawable.filter_preview_source_3 && index == 1 -> secondary else -> secondaryContainer } } Box( modifier = Modifier .weight(1f) .clip(shape) .hapticsClickable { component.setFilterPreviewModel( index.toString() ) } .container( color = containerColor, shape = shape, resultPadding = 0.dp ) .padding(horizontal = 12.dp), contentAlignment = Alignment.Center ) { AutoSizeText( text = (index + 1).toString(), color = contentColorFor( containerColor ) ) } } } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/addFilters/TemplatesContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ExtensionOff import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.collectAsUiState import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateAddingGroup import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateInfoSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.TemplateFilterSelectionItem import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberRetainedLazyListState @Composable internal fun TemplatesContent( component: AddFiltersSheetComponent, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent, onVisibleChange: (Boolean) -> Unit, onFilterPickedWithParams: (UiFilter<*>) -> Unit, ) { val templateFilters by component.templatesFlow.collectAsUiState() val onRequestFilterMapping = component::filterToTransformation AnimatedContent( targetState = templateFilters.isEmpty() ) { noTemplates -> if (noTemplates) { Column( modifier = Modifier .fillMaxSize() .enhancedVerticalScroll(rememberScrollState()) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.no_template_filters), fontSize = 18.sp, textAlign = TextAlign.Center ) Spacer(Modifier.height(16.dp)) Icon( imageVector = Icons.Outlined.ExtensionOff, contentDescription = null, modifier = Modifier.size(128.dp) ) FilterTemplateAddingGroup( onAddTemplateFilterFromString = component::addTemplateFilterFromString, onAddTemplateFilterFromUri = component::addTemplateFilterFromUri, component = filterTemplateCreationSheetComponent ) Spacer(Modifier.weight(1f)) } } else { LazyColumn( state = rememberRetainedLazyListState("templates"), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp), contentPadding = PaddingValues(16.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = templateFilters, key = { _, f -> f.hashCode() } ) { index, templateFilter -> var showFilterTemplateInfoSheet by rememberSaveable { mutableStateOf(false) } TemplateFilterSelectionItem( templateFilter = templateFilter, onClick = { onVisibleChange(false) templateFilter.filters.forEach { onFilterPickedWithParams(it.toUiFilter()) } }, onLongClick = { component.setPreviewData(templateFilter.filters) }, onInfoClick = { showFilterTemplateInfoSheet = true }, onRequestFilterMapping = onRequestFilterMapping, shape = ShapeDefaults.byIndex( index = index, size = templateFilters.size ), modifier = Modifier.animateItem() ) FilterTemplateInfoSheet( visible = showFilterTemplateInfoSheet, onDismiss = { showFilterTemplateInfoSheet = it }, templateFilter = templateFilter, onRequestFilterMapping = onRequestFilterMapping, onShareImage = component::shareImage, onSaveImage = component::saveImage, onSaveFile = { fileUri, content -> component.saveContentTo( content = content, fileUri = fileUri ) }, onConvertTemplateFilterToString = component::convertTemplateFilterToString, onRemoveTemplateFilter = component::removeTemplateFilter, onRequestTemplateFilename = { component.createTemplateFilename(templateFilter) }, onShareFile = { content -> component.shareContent( content = content, filename = component.createTemplateFilename(templateFilter) ) }, component = filterTemplateCreationSheetComponent ) } item { FilterTemplateAddingGroup( onAddTemplateFilterFromString = component::addTemplateFilterFromString, onAddTemplateFilterFromUri = component::addTemplateFilterFromUri, component = filterTemplateCreationSheetComponent ) } } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ArcParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.ArcParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun ArcParamsItem( value: ArcParams, filter: UiFilter, onFilterChange: (value: ArcParams) -> Unit, previewOnly: Boolean ) { val radius: MutableState = remember(value) { mutableFloatStateOf(value.radius) } val height: MutableState = remember(value) { mutableFloatStateOf(value.height) } val angle: MutableState = remember(value) { mutableFloatStateOf(value.angle) } val spreadAngle: MutableState = remember(value) { mutableFloatStateOf(value.spreadAngle) } val centreX: MutableState = remember(value) { mutableFloatStateOf(value.centreX) } val centreY: MutableState = remember(value) { mutableFloatStateOf(value.centreY) } LaunchedEffect( radius.value, height.value, angle.value, spreadAngle.value, centreX.value, centreY.value ) { onFilterChange( ArcParams( radius = radius.value, height = height.value, angle = angle.value, spreadAngle = spreadAngle.value, centreX = centreX.value, centreY = centreY.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> radius 1 -> height 2 -> angle 3 -> spreadAngle 4 -> centreX else -> centreY } to filterParam } } } Column( modifier = Modifier.Companion.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, steps = if (valueRange == 0f..3f) 2 else 0, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/AsciiParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.ascii.Gradient import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.AsciiParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiAsciiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.settings.presentation.model.asUi import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FontSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import kotlinx.coroutines.launch @Composable internal fun AsciiParamsItem( value: AsciiParams, filter: UiFilter, onFilterChange: (value: AsciiParams) -> Unit, previewOnly: Boolean, itemShape: @Composable (Int) -> Shape = { ShapeDefaults.byIndex( index = it, size = 4 ) }, isForText: Boolean = false ) { val gradientState: MutableState = remember(value) { mutableStateOf(value.gradient) } val fontSize: MutableState = remember(value) { mutableFloatStateOf(value.fontSize) } val backgroundColor: MutableState = remember(value) { mutableStateOf(value.backgroundColor) } var isGrayscale by remember(value) { mutableStateOf(value.isGrayscale) } val settings = LocalSettingsState.current val currentFont = settings.font var font by remember(value) { mutableStateOf( value.font?.asUi() ?: currentFont ) } LaunchedEffect( gradientState.value, fontSize.value, backgroundColor.value, isGrayscale, font ) { onFilterChange( AsciiParams( gradient = gradientState.value, fontSize = fontSize.value, backgroundColor = backgroundColor.value, isGrayscale = isGrayscale, font = font.type ) ) } Column( modifier = Modifier.padding(if (isForText) 0.dp else 8.dp), verticalArrangement = Arrangement.spacedBy( if (isForText) 4.dp else 8.dp ) ) { filter.paramsInfo.take( if (isForText) 2 else 5 ).forEachIndexed { index, (title, valueRange, roundTo) -> when (index) { 0 -> { Column( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = if (isForText) { Modifier.container( color = MaterialTheme.colorScheme.surface, shape = itemShape(index) ) } else Modifier ) { val scope = rememberCoroutineScope() val interactor = LocalSimpleSettingsInteractor.current val defaultItems = remember { listOf( Gradient.NORMAL, Gradient.NORMAL2, Gradient.ARROWS, Gradient.OLD, Gradient.EXTENDED_HIGH, Gradient.MINIMAL, Gradient.MATH, Gradient.NUMERICAL ).map { it.value } } val customEntries = settings.customAsciiGradients - defaultItems val items = defaultItems + customEntries RoundedTextField( value = gradientState.value, onValueChange = { value -> gradientState.value = value.toList().distinct().filter { !it.isWhitespace() } .joinToString("") }, endIcon = { AnimatedContent( targetState = Triple( gradientState.value, defaultItems, customEntries ), transitionSpec = { slideInHorizontally { it / 2 } + fadeIn() togetherWith slideOutHorizontally { it / 2 } + fadeOut() }, contentKey = { (g, d, c) -> (g in d) to (g in c) }, ) { (gradient, default, custom) -> if (gradient.length > 1 && gradient !in default) { val saved = gradient in custom EnhancedIconButton( onClick = { scope.launch { interactor.toggleCustomAsciiGradient(gradient) } }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = if (saved) Icons.Outlined.Delete else Icons.Outlined.Save, contentDescription = if (saved) "delete" else "add" ) } } else { Spacer(Modifier.height(40.dp)) } } }, modifier = Modifier .fillMaxWidth() .then(if (isForText) Modifier.padding(top = 4.dp) else Modifier) .padding( horizontal = 4.dp ), label = stringResource(title!!) ) EnhancedButtonGroup( items = items, modifier = Modifier .fillMaxWidth() .padding( horizontal = 4.dp ), selectedIndex = items.indexOf(gradientState.value), onIndexChange = { gradientState.value = items[it] }, inactiveButtonColor = Color.Unspecified ) } } 1 -> { EnhancedSliderItem( enabled = !previewOnly, value = fontSize.value, title = stringResource(title!!), valueRange = valueRange, steps = if (valueRange == 0f..3f) 2 else 0, onValueChange = { fontSize.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = isForText, containerColor = MaterialTheme.colorScheme.surface, shape = itemShape(index) ) } 2 -> { FontSelector( value = font, onValueChange = { font = it }, behaveAsContainer = false, modifier = Modifier.fillMaxWidth() ) } 3 -> { ColorRowSelector( title = stringResource(title!!), value = backgroundColor.value.toColor(), onValueChange = { backgroundColor.value = it.toModel() }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 16.dp, ) } 4 -> { PreferenceRowSwitch( title = stringResource(id = title!!), checked = isGrayscale, onClick = { isGrayscale = it }, modifier = Modifier.padding( top = 8.dp, start = 4.dp, end = 4.dp ), applyHorizontalPadding = false, startContent = {}, resultModifier = Modifier.padding( horizontal = 16.dp, vertical = 8.dp ), enabled = !previewOnly ) } } } } } @Composable fun AsciiParamsSelector( value: AsciiParams, onValueChange: (value: AsciiParams) -> Unit, itemShapes: @Composable (Int) -> Shape = { ShapeDefaults.byIndex( index = it, size = 4 ) } ) { val filter by remember(value) { derivedStateOf { UiAsciiFilter(value) } } AsciiParamsItem( value = value, filter = filter, onFilterChange = onValueChange, previewOnly = false, isForText = true, itemShape = itemShapes ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BilaterialBlurParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.params.BilaterialBlurParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun BilaterialBlurParamsItem( value: BilaterialBlurParams, filter: UiFilter, onFilterChange: (value: BilaterialBlurParams) -> Unit, previewOnly: Boolean ) { val radius: MutableState = remember(value) { mutableFloatStateOf(value.radius.toFloat()) } val spatialSigma: MutableState = remember(value) { mutableFloatStateOf(value.spatialSigma) } val rangeSigma: MutableState = remember(value) { mutableFloatStateOf(value.rangeSigma) } val edgeMode: MutableState = remember(value) { mutableFloatStateOf(value.edgeMode.ordinal.toFloat()) } LaunchedEffect( radius.value, spatialSigma.value, rangeSigma.value, edgeMode.value ) { onFilterChange( BilaterialBlurParams( radius = radius.value.toInt(), spatialSigma = spatialSigma.value, rangeSigma = rangeSigma.value, edgeMode = BlurEdgeMode.entries[edgeMode.value.toInt()], ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> radius 1 -> spatialSigma 2 -> rangeSigma else -> edgeMode } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.take(3).forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } paramsInfo[3].let { (state, info) -> EdgeModeSelector( title = info.title!!, value = BlurEdgeMode.entries[state.value.toInt()], onValueChange = { state.value = it.ordinal.toFloat() } ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BloomParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.BloomParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import kotlin.math.roundToInt @Composable internal fun BloomParamsItem( value: BloomParams, filter: UiFilter, onFilterChange: (value: BloomParams) -> Unit, previewOnly: Boolean ) { val threshold: MutableState = remember(value) { mutableFloatStateOf(value.threshold) } val intensity: MutableState = remember(value) { mutableFloatStateOf(value.intensity) } val radius: MutableState = remember(value) { mutableFloatStateOf(value.radius.toFloat()) } val softKnee: MutableState = remember(value) { mutableFloatStateOf(value.softKnee) } val exposure: MutableState = remember(value) { mutableFloatStateOf(value.exposure) } val gamma: MutableState = remember(value) { mutableFloatStateOf(value.gamma) } LaunchedEffect( threshold.value, intensity.value, radius.value, softKnee.value, exposure.value, gamma.value ) { onFilterChange( BloomParams( threshold = threshold.value, intensity = intensity.value, radius = radius.value.roundToInt(), softKnee = softKnee.value, exposure = exposure.value, gamma = gamma.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> threshold 1 -> intensity 2 -> radius 3 -> softKnee 4 -> exposure else -> gamma } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/BooleanItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable internal fun BooleanItem( value: Boolean, filter: UiFilter, onFilterChange: (value: Boolean) -> Unit, previewOnly: Boolean ) { filter.paramsInfo[0].takeIf { it.title != null } ?.let { (title, _, _) -> PreferenceRowSwitch( title = stringResource(id = title!!), checked = value, onClick = { onFilterChange(value) }, modifier = Modifier.padding( top = 16.dp, start = 12.dp, end = 12.dp, bottom = 12.dp ), applyHorizontalPadding = false, startContent = {}, resultModifier = Modifier.padding( horizontal = 16.dp, vertical = 8.dp ), enabled = !previewOnly ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ChannelMixParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.ChannelMixParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import kotlin.math.roundToInt @Composable internal fun ChannelMixParamsItem( value: ChannelMixParams, filter: UiFilter, onFilterChange: (value: ChannelMixParams) -> Unit, previewOnly: Boolean ) { val blueGreen: MutableState = remember(value) { mutableFloatStateOf(value.blueGreen.toFloat()) } val redBlue: MutableState = remember(value) { mutableFloatStateOf(value.redBlue.toFloat()) } val greenRed: MutableState = remember(value) { mutableFloatStateOf(value.greenRed.toFloat()) } val intoR: MutableState = remember(value) { mutableFloatStateOf(value.intoR.toFloat()) } val intoG: MutableState = remember(value) { mutableFloatStateOf(value.intoG.toFloat()) } val intoB: MutableState = remember(value) { mutableFloatStateOf(value.intoB.toFloat()) } LaunchedEffect( blueGreen.value, redBlue.value, greenRed.value, intoR.value, intoG.value, intoB.value ) { onFilterChange( ChannelMixParams( blueGreen = blueGreen.value.roundToInt(), redBlue = redBlue.value.roundToInt(), greenRed = greenRed.value.roundToInt(), intoR = intoR.value.roundToInt(), intoG = intoG.value.roundToInt(), intoB = intoB.value.roundToInt(), ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> blueGreen 1 -> redBlue 2 -> greenRed 3 -> intoR 4 -> intoG else -> intoB } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ClaheParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun ClaheParamsItem( value: ClaheParams, filter: UiFilter, onFilterChange: (value: ClaheParams) -> Unit, previewOnly: Boolean ) { val threshold: MutableState = remember(value) { mutableFloatStateOf(value.threshold) } val gridSizeHorizontal: MutableState = remember(value) { mutableFloatStateOf(value.gridSizeHorizontal.toFloat()) } val gridSizeVertical: MutableState = remember(value) { mutableFloatStateOf(value.gridSizeVertical.toFloat()) } val binsCount: MutableState = remember(value) { mutableFloatStateOf(value.binsCount.toFloat()) } LaunchedEffect( threshold.value, gridSizeHorizontal.value, gridSizeVertical.value, binsCount.value ) { onFilterChange( ClaheParams( threshold = threshold.value, gridSizeHorizontal = gridSizeHorizontal.value.toInt(), gridSizeVertical = gridSizeVertical.value.toInt(), binsCount = binsCount.value.toInt() ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> threshold 1 -> gridSizeHorizontal 2 -> gridSizeVertical else -> binsCount } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/CropOrPerspectiveParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.filters.domain.model.params.CropOrPerspectiveParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.FloatPair import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun CropOrPerspectiveParamsItem( value: CropOrPerspectiveParams, filter: UiFilter, onFilterChange: (value: CropOrPerspectiveParams) -> Unit, previewOnly: Boolean ) { val topLeft: MutableState = remember(value) { mutableStateOf(value.topLeft) } val topRight: MutableState = remember(value) { mutableStateOf(value.topRight) } val bottomLeft: MutableState = remember(value) { mutableStateOf(value.bottomLeft) } val bottomRight: MutableState = remember(value) { mutableStateOf(value.bottomRight) } val isAbsolute: MutableState = remember(value) { mutableStateOf(value.isAbsolute) } LaunchedEffect( topLeft.value, topRight.value, bottomLeft.value, bottomRight.value, isAbsolute.value, ) { onFilterChange( CropOrPerspectiveParams( topLeft = topLeft.value, topRight = topRight.value, bottomLeft = bottomLeft.value, bottomRight = bottomRight.value, isAbsolute = isAbsolute.value ) ) } fun stateByIndex(index: Int) = when (index) { 0 -> topLeft 1 -> topRight 2 -> bottomLeft else -> bottomRight } Column( modifier = Modifier.padding(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { filter.paramsInfo.forEachIndexed { index, (title) -> when (index) { 0, 1, 2, 3 -> { val state = stateByIndex(index) var x by remember { mutableStateOf( state.value.first.toString().trimTrailingZero() ) } var y by remember { mutableStateOf( state.value.second.toString().trimTrailingZero() ) } LaunchedEffect(x, y) { state.update { it.copy( first = x.toFloatOrNull() ?: it.first, second = y.toFloatOrNull() ?: it.second, ) } } Column { TitleItem( text = stringResource(title!!), modifier = Modifier.padding( horizontal = 8.dp ) ) Spacer(Modifier.height(8.dp)) Row { RoundedTextField( value = x, onValueChange = { x = it }, shape = ShapeDefaults.smallStart, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = { Text("X") }, modifier = Modifier .weight(1f) .padding( start = 8.dp, top = 8.dp, bottom = 8.dp, end = 2.dp ) ) RoundedTextField( value = y, onValueChange = { y = it }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), shape = ShapeDefaults.smallEnd, label = { Text("Y") }, modifier = Modifier .weight(1f) .padding( start = 2.dp, top = 8.dp, bottom = 8.dp, end = 8.dp ), ) } } } 4 -> { PreferenceRowSwitch( title = stringResource(id = title!!), checked = isAbsolute.value, onClick = { isAbsolute.value = it }, modifier = Modifier.padding( top = 8.dp, start = 4.dp, end = 4.dp ), applyHorizontalPadding = false, startContent = {}, resultModifier = Modifier.padding( horizontal = 16.dp, vertical = 8.dp ), enabled = !previewOnly ) } } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EdgeModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.presentation.utils.translatedName import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup @Composable internal fun EdgeModeSelector( title: Int?, value: BlurEdgeMode, onValueChange: (BlurEdgeMode) -> Unit, ) { Text( text = stringResource(title!!), modifier = Modifier.padding( top = 8.dp, start = 12.dp, end = 12.dp, ) ) val entries = BlurEdgeMode.entries EnhancedButtonGroup( inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, items = entries.map { it.translatedName }, selectedIndex = entries.indexOf(value), onIndexChange = { onValueChange(entries[it]) } ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/EnhancedZoomBlurParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun EnhancedZoomBlurParamsItem( value: EnhancedZoomBlurParams, filter: UiFilter, onFilterChange: (value: EnhancedZoomBlurParams) -> Unit, previewOnly: Boolean ) { val radius: MutableState = remember(value) { mutableFloatStateOf((value.radius as Number).toFloat()) } val sigma: MutableState = remember(value) { mutableFloatStateOf((value.sigma as Number).toFloat()) } val anchorX: MutableState = remember(value) { mutableFloatStateOf((value.centerX as Number).toFloat()) } val anchorY: MutableState = remember(value) { mutableFloatStateOf((value.centerY as Number).toFloat()) } val strength: MutableState = remember(value) { mutableFloatStateOf((value.strength as Number).toFloat()) } val angle: MutableState = remember(value) { mutableFloatStateOf((value.angle as Number).toFloat()) } LaunchedEffect( radius.value, sigma.value, anchorX.value, anchorY.value, strength.value, angle.value ) { onFilterChange( EnhancedZoomBlurParams( radius = radius.value.toInt(), sigma = sigma.value, centerX = anchorX.value, centerY = anchorY.value, strength = strength.value, angle = angle.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> radius 1 -> sigma 2 -> anchorX 3 -> anchorY 4 -> strength else -> angle } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FilterValueWrapperItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.FilterValueWrapper import com.t8rin.imagetoolbox.core.filters.domain.model.wrap import com.t8rin.imagetoolbox.core.filters.presentation.model.UiColorOverlayFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiRGBFilter import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRow import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults @Composable internal fun FilterValueWrapperItem( value: FilterValueWrapper<*>, filter: UiFilter>, onFilterChange: (value: FilterValueWrapper<*>) -> Unit, previewOnly: Boolean ) { when (val wrapped = value.wrapped) { is ColorModel -> { Box( modifier = Modifier.padding( start = 16.dp, end = 16.dp ) ) { ColorSelectionRow( value = remember(wrapped) { wrapped.toColor() }, defaultColors = remember(filter) { derivedStateOf { ColorSelectionRowDefaults.colorList.map { if (filter is UiColorOverlayFilter) it.copy(0.5f) else it } } }.value, allowAlpha = filter !is UiRGBFilter, allowScroll = !previewOnly, onValueChange = { onFilterChange(it.toModel().wrap()) } ) } } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatArrayItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import kotlin.math.absoluteValue @Composable internal fun FloatArrayItem( value: FloatArray, filter: UiFilter, onFilterChange: (value: FloatArray) -> Unit, previewOnly: Boolean ) { val rows = filter.paramsInfo[0].valueRange.start.toInt().absoluteValue var text by rememberSaveable(value) { mutableStateOf( value.let { var string = "" it.forEachIndexed { index, float -> string += "$float, " if (index % rows == (rows - 1)) string += "\n" } string.dropLast(3) } ) } RoundedTextField( enabled = !previewOnly, modifier = Modifier.padding(16.dp), singleLine = false, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), onValueChange = { text = it }, onLoseFocusTransformation = { val matrix = filter.newInstance().value as FloatArray this.trim { it.isWhitespace() }.split(",").mapIndexed { index, num -> num.toFloatOrNull()?.let { matrix[index] = it } } onFilterChange(matrix) this }, endIcon = { EnhancedIconButton( onClick = { val matrix = filter.newInstance().value as FloatArray text.trim { it.isWhitespace() }.split(",") .mapIndexed { index, num -> num.toFloatOrNull()?.let { matrix[index] = it } } onFilterChange(matrix) } ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } }, value = text, label = { Text(stringResource(R.string.float_array_of)) } ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/FloatItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSlider @Composable internal fun FloatItem( value: Float, filter: UiFilter, onFilterChange: (value: Float) -> Unit, previewOnly: Boolean ) { EnhancedSlider( modifier = Modifier .padding(top = 16.dp, start = 12.dp, end = 12.dp, bottom = 8.dp) .offset(y = (-2).dp), enabled = !previewOnly, value = value, onValueChange = { onFilterChange(it.roundTo(filter.paramsInfo.first().roundTo)) }, valueRange = filter.paramsInfo.first().valueRange ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/GlitchParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.GlitchParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun GlitchParamsItem( value: GlitchParams, filter: UiFilter, onFilterChange: (value: GlitchParams) -> Unit, previewOnly: Boolean ) { val channelsShiftX: MutableState = remember(value) { mutableFloatStateOf((value.channelsShiftX as Number).toFloat()) } val channelsShiftY: MutableState = remember(value) { mutableFloatStateOf((value.channelsShiftY as Number).toFloat()) } val corruptionSize: MutableState = remember(value) { mutableFloatStateOf((value.corruptionSize as Number).toFloat()) } val corruptionCount: MutableState = remember(value) { mutableFloatStateOf((value.corruptionCount as Number).toFloat()) } val corruptionShiftX: MutableState = remember(value) { mutableFloatStateOf((value.corruptionShiftX as Number).toFloat()) } val corruptionShiftY: MutableState = remember(value) { mutableFloatStateOf((value.corruptionShiftY as Number).toFloat()) } LaunchedEffect( channelsShiftX.value, channelsShiftY.value, corruptionSize.value, corruptionCount.value, corruptionShiftX.value, corruptionShiftY.value ) { onFilterChange( GlitchParams( channelsShiftX = channelsShiftX.value, channelsShiftY = channelsShiftY.value, corruptionSize = corruptionSize.value, corruptionCount = corruptionCount.value.toInt(), corruptionShiftX = corruptionShiftX.value, corruptionShiftY = corruptionShiftY.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> channelsShiftX 1 -> channelsShiftY 2 -> corruptionSize 3 -> corruptionCount 4 -> corruptionShiftX else -> corruptionShiftY } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/IntegerSizeParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import kotlinx.coroutines.delay @Composable internal fun IntegerSizeParamsItem( value: IntegerSize, filter: UiFilter, onFilterChange: (value: IntegerSize) -> Unit, previewOnly: Boolean ) { var width by remember(value) { mutableIntStateOf(value.width) } var height by remember(value) { mutableIntStateOf(value.height) } val imageInfo by remember(width, height) { derivedStateOf { ImageInfo( width = width, height = height ) } } LaunchedEffect(imageInfo) { delay(500) onFilterChange( IntegerSize( width = imageInfo.width, height = imageInfo.height ) ) } ResizeImageField( modifier = Modifier.padding(8.dp), imageInfo = imageInfo, originalSize = null, onWidthChange = { width = it }, onHeightChange = { height = it }, enabled = !previewOnly ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/KaleidoscopeParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.KaleidoscopeParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import kotlin.math.roundToInt @Composable internal fun KaleidoscopeParamsItem( value: KaleidoscopeParams, filter: UiFilter, onFilterChange: (value: KaleidoscopeParams) -> Unit, previewOnly: Boolean ) { val angle: MutableState = remember(value) { mutableFloatStateOf(value.angle) } val angle2: MutableState = remember(value) { mutableFloatStateOf(value.angle2) } val centreX: MutableState = remember(value) { mutableFloatStateOf(value.centreX) } val centreY: MutableState = remember(value) { mutableFloatStateOf(value.centreY) } val sides: MutableState = remember(value) { mutableFloatStateOf(value.sides.toFloat()) } val radius: MutableState = remember(value) { mutableFloatStateOf(value.radius) } LaunchedEffect( angle.value, angle2.value, centreX.value, centreY.value, sides.value, radius.value ) { onFilterChange( KaleidoscopeParams( angle = angle.value, angle2 = angle2.value, centreX = centreX.value, centreY = centreY.value, sides = sides.value.roundToInt(), radius = radius.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> angle 1 -> angle2 2 -> centreX 3 -> centreY 4 -> sides else -> radius } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearGaussianParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearGaussianParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun LinearGaussianParamsItem( value: LinearGaussianParams, filter: UiFilter, onFilterChange: (value: LinearGaussianParams) -> Unit, previewOnly: Boolean ) { val kernelSize: MutableState = remember(value) { mutableFloatStateOf(value.kernelSize.toFloat()) } val sigma: MutableState = remember(value) { mutableFloatStateOf(value.sigma) } val edgeMode: MutableState = remember(value) { mutableFloatStateOf(value.edgeMode.ordinal.toFloat()) } val transferFunction: MutableState = remember(value) { mutableFloatStateOf(value.transferFunction.ordinal.toFloat()) } LaunchedEffect( kernelSize.value, sigma.value, edgeMode.value, transferFunction.value ) { onFilterChange( LinearGaussianParams( kernelSize = kernelSize.value.toInt(), sigma = sigma.value, edgeMode = BlurEdgeMode.entries[edgeMode.value.toInt()], transferFunction = TransferFunc.entries[transferFunction.value.toInt()] ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> kernelSize 1 -> sigma 2 -> edgeMode else -> transferFunction } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.take(2).forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } paramsInfo[2].let { (state, info) -> EdgeModeSelector( title = info.title!!, value = BlurEdgeMode.entries[state.value.toInt()], onValueChange = { state.value = it.ordinal.toFloat() } ) } paramsInfo[3].let { (state, info) -> TransferFuncSelector( title = info.title!!, value = TransferFunc.entries[state.value.toInt()], onValueChange = { state.value = it.ordinal.toFloat() } ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/LinearTiltShiftParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearTiltShiftParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun LinearTiltShiftParamsItem( value: LinearTiltShiftParams, filter: UiFilter, onFilterChange: (value: LinearTiltShiftParams) -> Unit, previewOnly: Boolean ) { val blurRadius: MutableState = remember(value) { mutableFloatStateOf((value.blurRadius as Number).toFloat()) } val sigma: MutableState = remember(value) { mutableFloatStateOf((value.sigma as Number).toFloat()) } val anchorX: MutableState = remember(value) { mutableFloatStateOf((value.anchorX as Number).toFloat()) } val anchorY: MutableState = remember(value) { mutableFloatStateOf((value.anchorY as Number).toFloat()) } val holeRadius: MutableState = remember(value) { mutableFloatStateOf((value.holeRadius as Number).toFloat()) } val angle: MutableState = remember(value) { mutableFloatStateOf((value.angle as Number).toFloat()) } LaunchedEffect( blurRadius.value, sigma.value, anchorX.value, anchorY.value, holeRadius.value, angle.value ) { onFilterChange( LinearTiltShiftParams( blurRadius = blurRadius.value, sigma = sigma.value, anchorX = anchorX.value, anchorY = anchorY.value, holeRadius = holeRadius.value, angle = angle.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> blurRadius 1 -> sigma 2 -> anchorX 3 -> anchorY 4 -> holeRadius else -> angle } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/MirrorSideSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.filters.presentation.utils.translatedName import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup @Composable internal fun MirrorSideSelector( title: Int, value: MirrorSide, onValueChange: (MirrorSide) -> Unit, ) { Text( text = stringResource(title), modifier = Modifier.padding( top = 8.dp, start = 12.dp, end = 12.dp, ) ) val entries = remember { MirrorSide.entries } EnhancedButtonGroup( inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, items = entries.map { it.translatedName }, selectedIndex = entries.indexOf(value), onIndexChange = { onValueChange(entries[it]) } ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.runtime.Composable import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.utils.cast import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.ColorModelPairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.FloatColorModelPairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.FloatFileModelPairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.FloatImageModelPairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.NumberBlurEdgeModePairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.NumberBooleanPairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.NumberMirrorSidePairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.NumberPairItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components.NumberTransferFuncPairItem @Composable internal fun PairItem( value: Pair<*, *>, filter: UiFilter>, onFilterChange: (value: Pair<*, *>) -> Unit, previewOnly: Boolean ) { when { value.first is Number && value.second is Number -> { NumberPairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is ColorModel && value.second is ColorModel -> { ColorModelPairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Float && value.second is ColorModel -> { FloatColorModelPairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Float && value.second is ImageModel -> { FloatImageModelPairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Float && value.second is FileModel -> { FloatFileModelPairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is Boolean -> { NumberBooleanPairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is BlurEdgeMode -> { NumberBlurEdgeModePairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is TransferFunc -> { NumberTransferFuncPairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is MirrorSide -> { NumberMirrorSidePairItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/PinchParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.PinchParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun PinchParamsItem( value: PinchParams, filter: UiFilter, onFilterChange: (value: PinchParams) -> Unit, previewOnly: Boolean ) { val angle: MutableState = remember(value) { mutableFloatStateOf(value.angle) } val centreX: MutableState = remember(value) { mutableFloatStateOf(value.centreX) } val centreY: MutableState = remember(value) { mutableFloatStateOf(value.centreY) } val radius: MutableState = remember(value) { mutableFloatStateOf(value.radius) } val amount: MutableState = remember(value) { mutableFloatStateOf(value.amount) } LaunchedEffect( angle.value, centreX.value, centreY.value, radius.value, amount.value, ) { onFilterChange( PinchParams( angle = angle.value, centreX = centreX.value, centreY = centreY.value, radius = radius.value, amount = amount.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> angle 1 -> centreX 2 -> centreY 3 -> radius else -> amount } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/QuadItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.runtime.Composable import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.cast import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.quad_components.NumberColorModelQuadItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.quad_components.NumberQuadItem @Composable internal fun QuadItem( value: Quad<*, *, *, *>, filter: UiFilter>, onFilterChange: (value: Quad<*, *, *, *>) -> Unit, previewOnly: Boolean ) { when { value.first is Number && value.second is Number && value.third is Number && value.fourth is Number -> { NumberQuadItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is Number && value.third is Number && value.fourth is ColorModel -> { NumberColorModelQuadItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RadialTiltShiftParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.RadialTiltShiftParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun RadialTiltShiftParamsItem( value: RadialTiltShiftParams, filter: UiFilter, onFilterChange: (value: RadialTiltShiftParams) -> Unit, previewOnly: Boolean ) { val blurRadius: MutableState = remember(value) { mutableFloatStateOf((value.blurRadius as Number).toFloat()) } val sigma: MutableState = remember(value) { mutableFloatStateOf((value.sigma as Number).toFloat()) } val anchorX: MutableState = remember(value) { mutableFloatStateOf((value.anchorX as Number).toFloat()) } val anchorY: MutableState = remember(value) { mutableFloatStateOf((value.anchorY as Number).toFloat()) } val holeRadius: MutableState = remember(value) { mutableFloatStateOf((value.holeRadius as Number).toFloat()) } LaunchedEffect( blurRadius.value, sigma.value, anchorX.value, anchorY.value, holeRadius.value ) { onFilterChange( RadialTiltShiftParams( blurRadius = blurRadius.value, sigma = sigma.value, anchorX = anchorX.value, anchorY = anchorY.value, holeRadius = holeRadius.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> blurRadius 1 -> sigma 2 -> anchorX 3 -> anchorY else -> holeRadius } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/RubberStampParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.toColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.RubberStampParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import kotlin.math.roundToInt @Composable internal fun RubberStampParamsItem( value: RubberStampParams, filter: UiFilter, onFilterChange: (value: RubberStampParams) -> Unit, previewOnly: Boolean ) { val threshold: MutableState = remember(value) { mutableFloatStateOf(value.threshold) } val softness: MutableState = remember(value) { mutableFloatStateOf(value.softness) } val radius: MutableState = remember(value) { mutableFloatStateOf(value.radius) } val firstColor: MutableState = remember(value) { mutableFloatStateOf(value.firstColor.colorInt.toFloat()) } val secondColor: MutableState = remember(value) { mutableFloatStateOf(value.secondColor.colorInt.toFloat()) } LaunchedEffect( threshold.value, softness.value, radius.value, firstColor.value, secondColor.value, ) { onFilterChange( RubberStampParams( threshold = threshold.value, softness = softness.value, radius = radius.value, firstColor = firstColor.value.toInt().toColorModel(), secondColor = secondColor.value.toInt().toColorModel(), ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> threshold 1 -> softness 2 -> radius 3 -> firstColor else -> secondColor } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.take(3).forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, steps = if (valueRange == 0f..4f) 3 else 0, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } paramsInfo[3].let { (state, info) -> ColorRowSelector( title = stringResource(info.title!!), value = state.value.roundToInt().toColor(), onValueChange = { state.value = it.toArgb().toFloat() }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 16.dp, modifier = Modifier.padding(start = 4.dp) ) } paramsInfo[4].let { (state, info) -> ColorRowSelector( title = stringResource(info.title!!), value = state.value.roundToInt().toColor(), onValueChange = { state.value = it.toArgb().toFloat() }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 16.dp, modifier = Modifier.padding(start = 4.dp) ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SideFadeRelativeItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide import com.t8rin.imagetoolbox.core.filters.domain.model.params.SideFadeParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.translatedName import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun SideFadeRelativeItem( value: SideFadeParams.Relative, filter: UiFilter, onFilterChange: (value: SideFadeParams.Relative) -> Unit, previewOnly: Boolean ) { var scale by remember(value) { mutableFloatStateOf(value.scale) } var sideFade by remember(value) { mutableStateOf(value.side) } LaunchedEffect(scale, sideFade) { onFilterChange( SideFadeParams.Relative( side = sideFade, scale = scale ) ) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = scale, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { scale = it }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) filter.paramsInfo[1].takeIf { it.title != null } ?.let { (title, _, _) -> Text( text = stringResource(title!!), modifier = Modifier.padding( top = 8.dp, start = 12.dp, end = 12.dp, ) ) EnhancedButtonGroup( inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, items = FadeSide.entries.map { it.translatedName }, selectedIndex = FadeSide.entries.indexOf(sideFade), onIndexChange = { sideFade = FadeSide.entries[it] } ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SmearParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.SmearParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun SmearParamsItem( value: SmearParams, filter: UiFilter, onFilterChange: (value: SmearParams) -> Unit, previewOnly: Boolean ) { val angle: MutableState = remember(value) { mutableFloatStateOf(value.angle) } val density: MutableState = remember(value) { mutableFloatStateOf(value.density) } val mix: MutableState = remember(value) { mutableFloatStateOf(value.mix) } val distance: MutableState = remember(value) { mutableFloatStateOf(value.distance.toFloat()) } val shape: MutableState = remember(value) { mutableFloatStateOf(value.shape.toFloat()) } LaunchedEffect( angle.value, density.value, mix.value, distance.value, shape.value ) { onFilterChange( SmearParams( angle = angle.value, density = density.value, mix = mix.value, distance = distance.value.toInt(), shape = shape.value.toInt() ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> angle 1 -> density 2 -> mix 3 -> distance else -> shape } to filterParam } } } Column( modifier = Modifier.Companion.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, steps = if (valueRange == 0f..3f) 2 else 0, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/SparkleParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.toColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.SparkleParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import kotlin.math.roundToInt @Composable internal fun SparkleParamsItem( value: SparkleParams, filter: UiFilter, onFilterChange: (value: SparkleParams) -> Unit, previewOnly: Boolean ) { val amount: MutableState = remember(value) { mutableFloatStateOf(value.amount.toFloat()) } val rays: MutableState = remember(value) { mutableFloatStateOf(value.rays.toFloat()) } val radius: MutableState = remember(value) { mutableFloatStateOf(value.radius) } val randomness: MutableState = remember(value) { mutableFloatStateOf(value.randomness.toFloat()) } val centreX: MutableState = remember(value) { mutableFloatStateOf(value.centreX) } val centreY: MutableState = remember(value) { mutableFloatStateOf(value.centreY) } val color: MutableState = remember(value) { mutableFloatStateOf(value.color.colorInt.toFloat()) } LaunchedEffect( amount.value, rays.value, radius.value, randomness.value, centreX.value, centreY.value, color.value ) { onFilterChange( SparkleParams( amount = amount.value.toInt(), rays = rays.value.toInt(), radius = radius.value, randomness = randomness.value.toInt(), centreX = centreX.value, centreY = centreY.value, color = color.value.roundToInt().toColorModel() ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> amount 1 -> rays 2 -> radius 3 -> randomness 4 -> centreX 5 -> centreY else -> color } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.take(6).forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } paramsInfo[6].let { (state, info) -> ColorRowSelector( title = stringResource(info.title!!), value = state.value.roundToInt().toColor(), onValueChange = { state.value = it.toArgb().toFloat() }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 16.dp, modifier = Modifier.padding(start = 4.dp) ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/ToneCurvesParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import android.graphics.Bitmap import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.imageLoader import coil3.request.ImageRequest import coil3.toBitmap import com.t8rin.curves.ImageCurvesEditor import com.t8rin.curves.ImageCurvesEditorState import com.t8rin.imagetoolbox.core.filters.domain.model.params.ToneCurvesParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.utils.appContext @Composable internal fun ToneCurvesParamsItem( value: ToneCurvesParams, filter: UiFilter, onFilterChange: (value: ToneCurvesParams) -> Unit, previewOnly: Boolean ) { val editorState: MutableState = remember { mutableStateOf(ImageCurvesEditorState(value.controlPoints)) } Box( modifier = Modifier.padding(8.dp) ) { var bitmap by remember { mutableStateOf(null) } val previewModel = LocalFilterPreviewModelProvider.current.preview LaunchedEffect(previewModel) { bitmap = appContext.imageLoader.execute( ImageRequest.Builder(appContext) .data(previewModel.data) .build() ).image?.toBitmap() } ImageCurvesEditor( bitmap = bitmap, state = editorState.value, curvesSelectionText = { Text( text = when (it) { 0 -> stringResource(R.string.all) 1 -> stringResource(R.string.color_red) 2 -> stringResource(R.string.color_green) 3 -> stringResource(R.string.color_blue) else -> "" }, style = MaterialTheme.typography.bodySmall ) }, imageObtainingTrigger = false, onImageObtained = { }, //shape = ShapeDefaults.extraSmall, containerModifier = Modifier.fillMaxWidth(), onStateChange = { onFilterChange( ToneCurvesParams( controlPoints = it.controlPoints ) ) } ) if (previewOnly) { Surface( modifier = Modifier.matchParentSize(), color = Color.Transparent, content = {} ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TransferFuncSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.presentation.utils.translatedName import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup @Composable internal fun TransferFuncSelector( title: Int, value: TransferFunc, onValueChange: (TransferFunc) -> Unit, ) { Text( text = stringResource(title), modifier = Modifier.padding( top = 8.dp, start = 12.dp, end = 12.dp, ) ) val entries = remember { TransferFunc.entries } EnhancedButtonGroup( inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, items = entries.map { it.translatedName }, selectedIndex = entries.indexOf(value), onIndexChange = { onValueChange(entries[it]) } ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/TripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.runtime.Composable import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.utils.cast import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PaletteTransferSpace import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.ColorModelTripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.FloatPaletteImageModelTripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.NumberColorModelColorModelTripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.NumberColorModelPopArtTripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.NumberNumberBlurEdgeModeTripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.NumberNumberColorModelTripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.NumberTransferFuncBlurEdgeModeTripleItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components.NumberTripleItem @Composable internal fun TripleItem( value: Triple<*, *, *>, filter: UiFilter>, onFilterChange: (value: Triple<*, *, *>) -> Unit, previewOnly: Boolean ) { when { value.first is Float && value.second is PaletteTransferSpace && value.third is ImageModel -> { FloatPaletteImageModelTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is Number && value.third is Number -> { NumberTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is Number && value.third is ColorModel -> { NumberNumberColorModelTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is ColorModel && value.third is ColorModel -> { NumberColorModelColorModelTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is Number && value.third is BlurEdgeMode -> { NumberNumberBlurEdgeModeTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is TransferFunc && value.third is BlurEdgeMode -> { NumberTransferFuncBlurEdgeModeTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is ColorModel && value.second is ColorModel && value.third is ColorModel -> { ColorModelTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } value.first is Number && value.second is ColorModel && value.third is PopArtBlendingMode -> { NumberColorModelPopArtTripleItem( value = value.cast(), filter = filter, onFilterChange = onFilterChange, previewOnly = previewOnly ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/VoronoiCrystallizeParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.toColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import kotlin.math.roundToInt @Composable internal fun VoronoiCrystallizeParamsItem( value: VoronoiCrystallizeParams, filter: UiFilter, onFilterChange: (value: VoronoiCrystallizeParams) -> Unit, previewOnly: Boolean ) { val borderThickness: MutableState = remember(value) { mutableFloatStateOf(value.borderThickness) } val scale: MutableState = remember(value) { mutableFloatStateOf(value.scale) } val randomness: MutableState = remember(value) { mutableFloatStateOf(value.randomness) } val shape: MutableState = remember(value) { mutableFloatStateOf(value.shape.toFloat()) } val turbulence: MutableState = remember(value) { mutableFloatStateOf(value.turbulence) } val angle: MutableState = remember(value) { mutableFloatStateOf(value.angle) } val stretch: MutableState = remember(value) { mutableFloatStateOf(value.stretch) } val amount: MutableState = remember(value) { mutableFloatStateOf(value.amount) } val color: MutableState = remember(value) { mutableFloatStateOf(value.color.colorInt.toFloat()) } LaunchedEffect( borderThickness.value, scale.value, randomness.value, shape.value, turbulence.value, angle.value, stretch.value, amount.value, color.value ) { onFilterChange( VoronoiCrystallizeParams( borderThickness = borderThickness.value, scale = scale.value, randomness = randomness.value, shape = shape.value.roundToInt(), turbulence = turbulence.value, angle = angle.value, stretch = stretch.value, amount = amount.value, color = color.value.roundToInt().toColorModel() ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> borderThickness 1 -> scale 2 -> randomness 3 -> shape 4 -> turbulence 5 -> angle 6 -> stretch 7 -> amount else -> color } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.take(8).forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, steps = if (valueRange == 0f..4f) 3 else 0, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } paramsInfo[8].let { (state, info) -> ColorRowSelector( title = stringResource(info.title!!), value = state.value.roundToInt().toColor(), onValueChange = { state.value = it.toArgb().toFloat() }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 16.dp, modifier = Modifier.padding(start = 4.dp) ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/WaterParamsItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun WaterParamsItem( value: WaterParams, filter: UiFilter, onFilterChange: (value: WaterParams) -> Unit, previewOnly: Boolean ) { val fractionSize: MutableState = remember(value) { mutableFloatStateOf(value.fractionSize) } val frequencyX: MutableState = remember(value) { mutableFloatStateOf(value.frequencyX) } val frequencyY: MutableState = remember(value) { mutableFloatStateOf(value.frequencyY) } val amplitudeX: MutableState = remember(value) { mutableFloatStateOf(value.amplitudeX) } val amplitudeY: MutableState = remember(value) { mutableFloatStateOf(value.amplitudeY) } LaunchedEffect( fractionSize.value, frequencyX.value, frequencyY.value, amplitudeX.value, amplitudeY.value ) { onFilterChange( WaterParams( fractionSize = fractionSize.value, frequencyX = frequencyX.value, frequencyY = frequencyY.value, amplitudeX = amplitudeX.value, amplitudeY = amplitudeY.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> fractionSize 1 -> frequencyX 2 -> frequencyY 3 -> amplitudeX else -> amplitudeY } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/ColorModelPairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector @Composable internal fun ColorModelPairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { Box( modifier = Modifier.padding( start = 16.dp, top = 16.dp, end = 16.dp ) ) { var color1 by remember(value) { mutableStateOf(value.first.toColor()) } var color2 by remember(value) { mutableStateOf(value.second.toColor()) } Column { ColorRowSelector( title = stringResource(R.string.first_color), value = color1, onValueChange = { color1 = it onFilterChange(color1.toModel() to color2.toModel()) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) Spacer(Modifier.height(8.dp)) ColorRowSelector( title = stringResource(R.string.second_color), value = color2, onValueChange = { color2 = it onFilterChange(color1.toModel() to color2.toModel()) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatColorModelPairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun FloatColorModelPairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember { mutableFloatStateOf(value.first) } var color1 by remember(value) { mutableStateOf(value.second.toColor()) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange(sliderState1 to color1.toModel()) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) Box( modifier = Modifier.padding( start = 16.dp, end = 16.dp ) ) { ColorRowSelector( title = stringResource(filter.paramsInfo[1].title!!), value = color1, onValueChange = { color1 = it onFilterChange(sliderState1 to color1.toModel()) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp, modifier = Modifier.padding(start = 4.dp) ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatFileModelPairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.LENS_PROFILES_LINK import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Github import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FileSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.utils.toFileModel @Composable internal fun FloatFileModelPairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember { mutableFloatStateOf(value.first) } var uri1 by remember(value) { mutableStateOf(value.second.uri) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange(sliderState1 to uri1.toFileModel()) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) FileSelector( modifier = Modifier.padding(16.dp), value = uri1, title = filter.paramsInfo[1].title?.let { stringResource(it) } ?: stringResource(R.string.pick_file), onValueChange = { uri1 = it.toString() onFilterChange(sliderState1 to uri1.toFileModel()) }, subtitle = null ) if (filter is Filter.LensCorrection) { val linkHandler = LocalUriHandler.current EnhancedButton( onClick = { linkHandler.openUri(LENS_PROFILES_LINK) }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) .padding(bottom = 16.dp) .height(44.dp), containerColor = MaterialTheme.colorScheme.secondary ) { Icon( imageVector = Icons.Rounded.Github, contentDescription = null ) Spacer(Modifier.width(8.dp)) Text( text = stringResource(R.string.download_ready_lens_profiles), modifier = Modifier.weight(1f, false) ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/FloatImageModelPairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.utils.toImageModel @Composable internal fun FloatImageModelPairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember { mutableFloatStateOf(value.first) } var uri1 by remember(value) { mutableStateOf(value.second.data) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange(sliderState1 to uri1.toImageModel()) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) ImageSelector( modifier = Modifier.padding(16.dp), value = uri1, title = filter.paramsInfo[1].title?.let { stringResource(it) } ?: stringResource(R.string.image), onValueChange = { uri1 = it.toString() onFilterChange(sliderState1 to uri1.toImageModel()) }, subtitle = null ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBlurEdgeModePairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.EdgeModeSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberBlurEdgeModePairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember(value) { mutableFloatStateOf(value.first.toFloat()) } var edgeMode by remember(value) { mutableStateOf(value.second) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange(sliderState1 to edgeMode) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) filter.paramsInfo[1].title?.let { title -> EdgeModeSelector( title = title, value = edgeMode, onValueChange = { edgeMode = it onFilterChange(sliderState1 to edgeMode) } ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberBooleanPairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable internal fun NumberBooleanPairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember(value) { mutableFloatStateOf(value.first.toFloat()) } var booleanState2 by remember(value) { mutableStateOf(value.second) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange(sliderState1 to booleanState2) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) filter.paramsInfo[1].takeIf { it.title != null } ?.let { (title, _, _) -> PreferenceRowSwitch( title = stringResource(id = title!!), checked = booleanState2, onClick = { booleanState2 = it onFilterChange(sliderState1 to it) }, modifier = Modifier.padding( top = 16.dp, start = 12.dp, end = 12.dp, bottom = 12.dp ), applyHorizontalPadding = false, startContent = {}, resultModifier = Modifier.padding( horizontal = 16.dp, vertical = 8.dp ) ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberMirrorSidePairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.MirrorSideSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberMirrorSidePairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember(value) { mutableFloatStateOf(value.first.toFloat()) } var mirrorSide by remember(value) { mutableStateOf(value.second) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange(sliderState1 to mirrorSide) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) filter.paramsInfo[1].title?.let { title -> MirrorSideSelector( title = title, value = mirrorSide, onValueChange = { mirrorSide = it onFilterChange(sliderState1 to mirrorSide) } ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberPairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberPairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { val sliderState1: MutableState = remember(value) { mutableFloatStateOf(value.first.toFloat()) } val sliderState2: MutableState = remember(value) { mutableFloatStateOf(value.second.toFloat()) } LaunchedEffect( sliderState1.value, sliderState2.value ) { onFilterChange( sliderState1.value to sliderState2.value ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> sliderState1 else -> sliderState2 } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/pair_components/NumberTransferFuncPairItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.pair_components import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.TransferFuncSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberTransferFuncPairItem( value: Pair, filter: UiFilter>, onFilterChange: (value: Pair) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember(value) { mutableFloatStateOf(value.first.toFloat()) } var transferFunction by remember(value) { mutableStateOf(value.second) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange(sliderState1 to transferFunction) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) filter.paramsInfo[1].title?.let { title -> TransferFuncSelector( title = title, value = transferFunction, onValueChange = { transferFunction = it onFilterChange(sliderState1 to transferFunction) } ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberColorModelQuadItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.quad_components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberColorModelQuadItem( value: Quad, filter: UiFilter>, onFilterChange: (Quad) -> Unit, previewOnly: Boolean ) { val sliderState1: MutableState = remember(value) { mutableFloatStateOf(value.first.toFloat()) } val sliderState2: MutableState = remember(value) { mutableFloatStateOf(value.second.toFloat()) } val sliderState3: MutableState = remember(value) { mutableFloatStateOf(value.third.toFloat()) } var color4 by remember(value) { mutableStateOf(value.fourth.toColor()) } LaunchedEffect( sliderState1.value, sliderState2.value, sliderState3.value, color4 ) { onFilterChange( Quad( first = sliderState1.value, second = sliderState2.value, third = sliderState3.value, fourth = color4.toModel() ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null || index > 2) return@mapIndexedNotNull null when (index) { 0 -> sliderState1 1 -> sliderState2 else -> sliderState3 } to filterParam } } } Column( modifier = Modifier.padding( top = 8.dp, start = 8.dp, end = 8.dp ) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } ColorRowSelector( title = stringResource(filter.paramsInfo[3].title!!), value = color4, onValueChange = { color4 = it }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 16.dp, modifier = Modifier.padding(start = 4.dp) ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/quad_components/NumberQuadItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.quad_components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberQuadItem( value: Quad, filter: UiFilter>, onFilterChange: (Quad) -> Unit, previewOnly: Boolean ) { val sliderState1: MutableState = remember(value) { mutableFloatStateOf(value.first.toFloat()) } val sliderState2: MutableState = remember(value) { mutableFloatStateOf(value.second.toFloat()) } val sliderState3: MutableState = remember(value) { mutableFloatStateOf(value.third.toFloat()) } val sliderState4: MutableState = remember(value) { mutableFloatStateOf(value.fourth.toFloat()) } LaunchedEffect( sliderState1.value, sliderState2.value, sliderState3.value, sliderState4.value ) { onFilterChange( Quad( sliderState1.value, sliderState2.value, sliderState3.value, sliderState4.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> sliderState1 1 -> sliderState2 2 -> sliderState3 else -> sliderState4 } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/ColorModelTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector @Composable internal fun ColorModelTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { Box( modifier = Modifier.padding( start = 16.dp, top = 16.dp, end = 16.dp ) ) { var color1 by remember(value) { mutableStateOf(value.first.toColor()) } var color2 by remember(value) { mutableStateOf(value.second.toColor()) } var color3 by remember(value) { mutableStateOf(value.third.toColor()) } Column { ColorRowSelector( title = stringResource(R.string.first_color), value = color1, onValueChange = { color1 = it onFilterChange( Triple( color1.toModel(), color2.toModel(), color3.toModel() ) ) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) Spacer(Modifier.height(8.dp)) ColorRowSelector( title = stringResource(R.string.second_color), value = color2, onValueChange = { color2 = it onFilterChange( Triple( color1.toModel(), color2.toModel(), color3.toModel() ) ) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) Spacer(Modifier.height(8.dp)) ColorRowSelector( title = stringResource(R.string.third_color), value = color3, onValueChange = { color3 = it onFilterChange( Triple( color1.toModel(), color2.toModel(), color3.toModel() ) ) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/FloatPaletteImageModelTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PaletteTransferSpace import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.translatedName import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.utils.toImageModel @Composable internal fun FloatPaletteImageModelTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember { mutableFloatStateOf(value.first) } var colorSpace1 by remember { mutableStateOf(value.second) } var uri1 by remember(value) { mutableStateOf(value.third.data) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange( Triple( sliderState1, colorSpace1, uri1.toImageModel() ) ) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) Spacer(Modifier.height(8.dp)) Column( modifier = Modifier .padding(horizontal = 16.dp) .container( shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surfaceContainerLow ) ) { Text( text = stringResource(filter.paramsInfo[1].title!!), modifier = Modifier.padding( top = 8.dp, start = 12.dp, end = 12.dp, ) ) val entries by remember(filter) { derivedStateOf { PaletteTransferSpace.entries } } EnhancedButtonGroup( inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, items = entries.map { it.translatedName }, selectedIndex = entries.indexOf(colorSpace1), onIndexChange = { colorSpace1 = entries[it] onFilterChange( Triple( sliderState1, colorSpace1, uri1.toImageModel() ) ) } ) } Spacer(Modifier.height(4.dp)) ImageSelector( modifier = Modifier.padding( horizontal = 16.dp ), value = uri1, title = filter.paramsInfo[2].title?.let { stringResource(it) } ?: stringResource(R.string.image), onValueChange = { uri1 = it.toString() onFilterChange( Triple( sliderState1, colorSpace1, uri1.toImageModel() ) ) }, subtitle = null, shape = ShapeDefaults.bottom ) Spacer(Modifier.height(16.dp)) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelColorModelTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberColorModelColorModelTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember { mutableFloatStateOf(value.first.toFloat()) } var color1 by remember(value) { mutableStateOf(value.second.toColor()) } var color2 by remember(value) { mutableStateOf(value.third.toColor()) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange( Triple( sliderState1, color1.toModel(), color2.toModel() ) ) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) Box( modifier = Modifier.padding( start = 16.dp, top = 16.dp, end = 16.dp ) ) { Column { ColorRowSelector( title = stringResource(filter.paramsInfo[1].title!!), value = color1, onValueChange = { color1 = it onFilterChange( Triple( sliderState1, color1.toModel(), color2.toModel() ) ) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) Spacer(Modifier.height(8.dp)) ColorRowSelector( title = stringResource(filter.paramsInfo[2].title!!), value = color2, onValueChange = { color2 = it onFilterChange( Triple( sliderState1, color1.toModel(), color2.toModel() ) ) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberColorModelPopArtTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.utils.translatedName import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberColorModelPopArtTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember { mutableFloatStateOf(value.first.toFloat()) } var color1 by remember(value) { mutableStateOf(value.second.toColor()) } var blendMode1 by remember(value) { mutableStateOf(value.third) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it onFilterChange( Triple( sliderState1, color1.toModel(), blendMode1 ) ) }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) Box( modifier = Modifier.padding( start = 16.dp, top = 16.dp, end = 16.dp ) ) { Column { ColorRowSelector( title = stringResource(filter.paramsInfo[1].title!!), value = color1, onValueChange = { color1 = it onFilterChange( Triple( sliderState1, color1.toModel(), blendMode1 ) ) }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 0.dp ) Text( text = stringResource(filter.paramsInfo[2].title!!), modifier = Modifier.padding( top = 8.dp, start = 12.dp, end = 12.dp, ) ) val entries by remember(filter) { derivedStateOf { PopArtBlendingMode.entries } } EnhancedButtonGroup( inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, items = entries.map { it.translatedName }, selectedIndex = entries.indexOf(blendMode1), onIndexChange = { blendMode1 = entries[it] onFilterChange( Triple( sliderState1, color1.toModel(), blendMode1 ) ) } ) } } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberBlurEdgeModeTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.EdgeModeSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberNumberBlurEdgeModeTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { val sliderState1: MutableState = remember(value) { mutableFloatStateOf(value.first.toFloat()) } val sliderState2: MutableState = remember(value) { mutableFloatStateOf(value.second.toFloat()) } var edgeMode by remember(value) { mutableStateOf(value.third) } LaunchedEffect( sliderState1.value, sliderState2.value, edgeMode ) { onFilterChange( Triple(sliderState1.value, sliderState2.value, edgeMode) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null || index > 1) return@mapIndexedNotNull null when (index) { 0 -> sliderState1 else -> sliderState2 } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } filter.paramsInfo[2].title?.let { title -> EdgeModeSelector( title = title, value = edgeMode, onValueChange = { edgeMode = it } ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberNumberColorModelTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberNumberColorModelTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { val sliderState1: MutableState = remember(value) { mutableFloatStateOf(value.first.toFloat()) } val sliderState2: MutableState = remember(value) { mutableFloatStateOf(value.second.toFloat()) } var color3 by remember(value) { mutableStateOf(value.third.toColor()) } LaunchedEffect( sliderState1.value, sliderState2.value, color3 ) { onFilterChange( Triple(sliderState1.value, sliderState2.value, color3.toModel()) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null || index > 1) return@mapIndexedNotNull null when (index) { 0 -> sliderState1 else -> sliderState2 } to filterParam } } } Column( modifier = Modifier.padding( top = 8.dp, start = 8.dp, end = 8.dp ) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } ColorRowSelector( title = stringResource(filter.paramsInfo[2].title!!), value = color3, onValueChange = { color3 = it }, allowScroll = !previewOnly, icon = null, defaultColors = ColorSelectionRowDefaults.colorList, contentHorizontalPadding = 16.dp, modifier = Modifier.padding(start = 4.dp) ) } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTransferFuncBlurEdgeModeTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.EdgeModeSelector import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.TransferFuncSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberTransferFuncBlurEdgeModeTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { var sliderState1 by remember(value) { mutableFloatStateOf(value.first.toFloat()) } var transferFunction by remember(value) { mutableStateOf(value.second) } var edgeMode by remember(value) { mutableStateOf(value.third) } LaunchedEffect( sliderState1, transferFunction, edgeMode ) { onFilterChange( Triple(sliderState1, transferFunction, edgeMode) ) } EnhancedSliderItem( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp ), enabled = !previewOnly, value = sliderState1, title = filter.paramsInfo[0].title?.let { stringResource(it) } ?: "", onValueChange = { sliderState1 = it }, internalStateTransformation = { it.roundTo(filter.paramsInfo[0].roundTo) }, valueRange = filter.paramsInfo[0].valueRange, behaveAsContainer = false ) filter.paramsInfo[1].title?.let { title -> TransferFuncSelector( title = title, value = transferFunction, onValueChange = { transferFunction = it } ) } filter.paramsInfo[2].title?.let { title -> EdgeModeSelector( title = title, value = edgeMode, onValueChange = { edgeMode = it } ) } } ================================================ FILE: core/filters/src/main/java/com/t8rin/imagetoolbox/core/filters/presentation/widget/filterItem/triple_components/NumberTripleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.triple_components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable internal fun NumberTripleItem( value: Triple, filter: UiFilter>, onFilterChange: (value: Triple) -> Unit, previewOnly: Boolean ) { val sliderState1: MutableState = remember(value) { mutableFloatStateOf(value.first.toFloat()) } val sliderState2: MutableState = remember(value) { mutableFloatStateOf(value.second.toFloat()) } val sliderState3: MutableState = remember(value) { mutableFloatStateOf(value.third.toFloat()) } LaunchedEffect( sliderState1.value, sliderState2.value, sliderState3.value ) { onFilterChange( Triple( sliderState1.value, sliderState2.value, sliderState3.value ) ) } val paramsInfo by remember(filter) { derivedStateOf { filter.paramsInfo.mapIndexedNotNull { index, filterParam -> if (filterParam.title == null) return@mapIndexedNotNull null when (index) { 0 -> sliderState1 1 -> sliderState2 else -> sliderState3 } to filterParam } } } Column( modifier = Modifier.padding(8.dp) ) { paramsInfo.forEach { (state, info) -> val (title, valueRange, roundTo) = info EnhancedSliderItem( enabled = !previewOnly, value = state.value, title = stringResource(title!!), valueRange = valueRange, onValueChange = { state.value = it }, internalStateTransformation = { it.roundTo(roundTo) }, behaveAsContainer = false ) } } } ================================================ FILE: core/ksp/.gitignore ================================================ /build ================================================ FILE: core/ksp/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { kotlin("jvm") id("com.google.devtools.ksp") } dependencies { implementation(kotlin("stdlib")) implementation(libs.symbol.processing.api) } ================================================ FILE: core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/FilterInject.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ksp.annotations @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class FilterInject ================================================ FILE: core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/annotations/UiFilterInject.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ksp.annotations @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class UiFilterInject( val group: String = Groups.UNSPECIFIED ) { object Groups { const val SIMPLE = "Simple" const val COLOR = "Color" const val LUT = "LUT" const val LIGHT = "Light" const val EFFECTS = "Effects" const val BLUR = "Blur" const val PIXELATION = "Pixelation" const val DISTORTION = "Distortion" const val DITHERING = "Dithering" const val UNSPECIFIED = "Unspecified" } } ================================================ FILE: core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/FilterInjectProcessor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.core.ksp.processor import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration internal class FilterInjectProcessor : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment ): SymbolProcessor = FilterInjectProcessorImpl(environment) } private class FilterInjectProcessorImpl( environment: SymbolProcessorEnvironment ) : SymbolProcessor { private val logger = environment.logger private val codegen = environment.codeGenerator override fun process(resolver: Resolver): List { logger.warn("START generation of filter map") val annotationFqn = "$PACKAGE.core.ksp.annotations.FilterInject" val annotated = resolver.getSymbolsWithAnnotation(annotationFqn) .filterIsInstance() .sortedBy { it.simpleName.asString() } .toList() if (annotated.isEmpty()) { logger.warn("No annotated classes found") return emptyList() } val fileName = "FilterMappings" val packageName = "$PACKAGE.generated" val files = annotated.mapNotNull { it.containingFile }.toTypedArray() val file = codegen.createNewFile( dependencies = Dependencies(false, *files), packageName = packageName, fileName = fileName ) file.bufferedWriter().use { writer -> writer.apply { appendLine("// Generated by KSP — do not edit") appendLine("package $PACKAGE.generated") appendLine() appendLine("import android.graphics.Bitmap") appendLine("import $PACKAGE.core.domain.transformation.*") appendLine("import $PACKAGE.feature.filters.data.model.*") appendLine("import $PACKAGE.core.filters.domain.model.*") appendLine() appendLine("internal fun mapFilter(filter: Filter<*>): Transformation = filter.run {") appendLine(" when (this) {") annotated.forEach { filter -> val filterName = filter.simpleName.asString() appendLine(" is Filter.${filterName.removeSuffix("Filter")} -> ${filterName}(value)") } appendLine() appendLine(" else -> throw IllegalArgumentException(\"No filter implementation for interface \${this::class.simpleName}\")") appendLine(" }") appendLine("}") } } logger.info("FilterMappings generated successfully") return emptyList() } companion object { private const val PACKAGE = "com.t8rin.imagetoolbox" } } ================================================ FILE: core/ksp/src/main/java/com/t8rin/imagetoolbox/core/ksp/processor/UiFilterInjectProcessor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.core.ksp.processor import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.Variance import com.t8rin.imagetoolbox.core.ksp.annotations.UiFilterInject internal class UiFilterInjectProcessor : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment ): SymbolProcessor = UiFilterInjectProcessorImpl(environment) } private class UiFilterInjectProcessorImpl( environment: SymbolProcessorEnvironment ) : SymbolProcessor { private val logger = environment.logger private val codegen = environment.codeGenerator private var generated = false override fun process(resolver: Resolver): List { if (generated) return emptyList() val annotationFqn = "$PACKAGE.core.ksp.annotations.UiFilterInject" val annotated = resolver.getSymbolsWithAnnotation(annotationFqn) .filterIsInstance() .sortedBy { it.simpleName.asString() } .toList() if (annotated.isEmpty()) return emptyList() val entries = annotated.mapNotNull { declaration -> declaration.toUiFilterEntry(annotationFqn, logger) } if (entries.isEmpty()) return emptyList() val duplicates = entries.groupBy { it.filterInterface } .filterValues { it.size > 1 } if (duplicates.isNotEmpty()) { duplicates.forEach { (filterInterface, declarations) -> logger.error( "Multiple UiFilters mapped to $filterInterface: " + declarations.joinToString { it.uiClassName } ) } return emptyList() } val file = codegen.createNewFile( dependencies = Dependencies( aggregating = false, *annotated.mapNotNull { it.containingFile }.toTypedArray() ), packageName = GENERATED_PACKAGE, fileName = GENERATED_FILE ) file.bufferedWriter().use { writer -> writer.apply { appendLine("// Generated by KSP — do not edit") appendLine("package $GENERATED_PACKAGE") appendLine() appendLine("import $PACKAGE.core.filters.domain.model.Filter") appendLine("import $PACKAGE.core.filters.presentation.model.*") appendLine() appendCopyFunction(entries) appendLine() appendNewInstanceFunction(entries) appendLine() appendToUiMapping(entries) appendLine() appendGroupFactories(entries) } } generated = true return emptyList() } private fun Appendable.appendCopyFunction(entries: List) { appendLine("@Suppress(\"UNCHECKED_CAST\")") appendLine("internal fun copyUiFilterInstance(") appendLine(" filter: UiFilter<*>,") appendLine(" newValue: Any") appendLine("): UiFilter<*> = when (filter) {") entries.forEach { entry -> val fallback = entry.defaultConstructorCall(receiverName = "filter") val line = if (entry.valueType != null) { " is ${entry.uiClassName} -> runCatching { ${entry.uiClassName}(newValue as ${entry.valueType}) }.getOrElse { $fallback }" } else { " is ${entry.uiClassName} -> $fallback" } appendLine(line) } appendLine(" else -> error(\"No copy mapping for \${filter::class.qualifiedName}\")") appendLine("}.also { copied ->") appendLine(" copied.isVisible = filter.isVisible") appendLine("}") } private fun Appendable.appendNewInstanceFunction(entries: List) { appendLine("internal fun newUiFilterInstance(filter: UiFilter<*>): UiFilter<*> = when (filter) {") entries.forEach { entry -> appendLine(" is ${entry.uiClassName} -> ${entry.defaultConstructorCall(receiverName = "filter")}") } appendLine(" else -> error(\"No newInstance mapping for \${filter::class.qualifiedName}\")") appendLine("}.also { instance ->") appendLine(" if (filter.value is Unit) {") appendLine(" instance.isVisible = filter.isVisible") appendLine(" }") appendLine("}") } private fun Appendable.appendToUiMapping(entries: List) { appendLine("internal fun mapFilterToUiFilter(") appendLine(" filter: Filter<*>,") appendLine(" preserveVisibility: Boolean = true") appendLine("): UiFilter<*> = when (filter) {") entries.forEach { entry -> val constructorCall = if (entry.valueType != null) { "${entry.uiClassName}(filter.value)" } else { "${entry.uiClassName}()" } appendLine(" is ${entry.filterInterface} -> $constructorCall") } appendLine(" else -> error(\"No UiFilter mapping for \${filter::class.qualifiedName}\")") appendLine("}.also { uiFilter ->") appendLine(" if (preserveVisibility) {") appendLine(" uiFilter.isVisible = filter.isVisible") appendLine(" }") appendLine("}") } private fun Appendable.appendGroupFactories(entries: List) { val grouped = entries.groupBy { it.group } GROUP_ORDER.forEach { group -> val functionName = groupFunctionName(group) val groupEntries = grouped[group].orEmpty().sortedBy { it.uiClassName } appendLine("internal fun $functionName(): List> = listOf(") if (groupEntries.isEmpty()) { appendLine(" // No filters for $group") } else { groupEntries.forEachIndexed { index, entry -> val constructorCall = entry.defaultConstructorCall(receiverName = "it") .replace("it.", "") val suffix = if (index == groupEntries.lastIndex) "" else "," appendLine(" $constructorCall$suffix") } } appendLine(")") appendLine() } } private fun KSClassDeclaration.toUiFilterEntry( annotationFqn: String, logger: KSPLogger ): UiFilterEntry? { val className = simpleName.asString() val filterInterface = superTypes .map { it.resolve().declaration as? KSClassDeclaration } .filterNotNull() .firstOrNull { it.qualifiedName?.asString() ?.startsWith("$PACKAGE.core.filters.domain.model.Filter.") == true } ?.qualifiedName ?.asString() ?.removePrefix("$PACKAGE.core.filters.domain.model.") if (filterInterface == null) { logger.error("$className is annotated with @UiFilterInject but does not implement Filter.*") return null } val constructorParameters = primaryConstructor?.parameters.orEmpty() if (constructorParameters.size > 1) { logger.error("$className should declare zero or one constructor parameter") return null } val valueParameter = constructorParameters.firstOrNull() val valueType = valueParameter?.type?.resolve()?.renderType() val hasDefaultValue = valueParameter?.hasDefault ?: false val group = annotations.firstOrNull { it.annotationType.resolve().declaration.qualifiedName?.asString() == annotationFqn }?.arguments?.firstOrNull { it.name?.asString() == "group" }?.value as? String ?: UiFilterInject.Groups.UNSPECIFIED if (group !in GROUP_ORDER && group != UiFilterInject.Groups.UNSPECIFIED) { logger.error("Unknown UiFilter group '$group' on $className") return null } return UiFilterEntry( uiClassName = className, filterInterface = filterInterface, valueType = valueType, valueParamHasDefault = hasDefaultValue, group = group ) } private fun KSType.renderType(): String { val declarationName = declaration.qualifiedName?.asString() ?: declaration.simpleName.asString() val argumentsRendered = arguments.takeIf { it.isNotEmpty() } ?.joinToString(", ") { argument -> val type = argument.type?.resolve() if (type == null || argument.variance == Variance.STAR) { "*" } else { val rendered = type.renderType() when (argument.variance) { Variance.COVARIANT -> "out $rendered" Variance.CONTRAVARIANT -> "in $rendered" else -> rendered } } } ?.let { "<$it>" } ?: "" val nullable = if (isMarkedNullable) "?" else "" return declarationName + argumentsRendered + nullable } private fun UiFilterEntry.defaultConstructorCall(receiverName: String): String = when { valueType == null -> "$uiClassName()" valueParamHasDefault -> "$uiClassName()" else -> "$uiClassName($receiverName.value as $valueType)" } private fun groupFunctionName(group: String): String = when (group) { UiFilterInject.Groups.SIMPLE -> "simpleGroupFilters" UiFilterInject.Groups.COLOR -> "colorGroupFilters" UiFilterInject.Groups.LUT -> "lutGroupFilters" UiFilterInject.Groups.LIGHT -> "lightGroupFilters" UiFilterInject.Groups.EFFECTS -> "effectsGroupFilters" UiFilterInject.Groups.BLUR -> "blurGroupFilters" UiFilterInject.Groups.PIXELATION -> "pixelationGroupFilters" UiFilterInject.Groups.DISTORTION -> "distortionGroupFilters" UiFilterInject.Groups.DITHERING -> "ditheringGroupFilters" else -> "unspecifiedGroupFilters" } private data class UiFilterEntry( val uiClassName: String, val filterInterface: String, val valueType: String?, val valueParamHasDefault: Boolean, val group: String ) private companion object { private const val PACKAGE = "com.t8rin.imagetoolbox" private const val GENERATED_PACKAGE = "com.t8rin.imagetoolbox.core.filters.presentation.model.generated" private const val GENERATED_FILE = "UiFilterMappings" private val GROUP_ORDER = listOf( UiFilterInject.Groups.SIMPLE, UiFilterInject.Groups.COLOR, UiFilterInject.Groups.LUT, UiFilterInject.Groups.LIGHT, UiFilterInject.Groups.EFFECTS, UiFilterInject.Groups.BLUR, UiFilterInject.Groups.PIXELATION, UiFilterInject.Groups.DISTORTION, UiFilterInject.Groups.DITHERING ) } } ================================================ FILE: core/ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider ================================================ # # ImageToolbox is an image editor for android # Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # # 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. # # You should have received a copy of the Apache License # along with this program. If not, see . # com.t8rin.imagetoolbox.core.ksp.processor.FilterInjectProcessor com.t8rin.imagetoolbox.core.ksp.processor.UiFilterInjectProcessor ================================================ FILE: core/resources/.gitignore ================================================ /build ================================================ FILE: core/resources/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android { namespace = "com.t8rin.imagetoolbox.core.resources" defaultConfig.vectorDrawables.useSupportLibrary = true buildFeatures { buildConfig = true } buildTypes { getByName("release") { buildConfigField("String", "VERSION_NAME", "\"${libs.versions.versionName.get()}\"") buildConfigField("int", "VERSION_CODE", libs.versions.versionCode.get()) } getByName("debug") { buildConfigField("String", "VERSION_NAME", "\"${libs.versions.versionName.get()}\"") buildConfigField("int", "VERSION_CODE", libs.versions.versionCode.get()) } } } dependencies { implementation(libs.material) implementation(libs.androidxCore) implementation(libs.appCompat) implementation(libs.splashScreen) implementation(libs.kotlinx.collections.immutable) } ================================================ FILE: core/resources/src/debug/res/drawable/ic_launcher_foreground.xml ================================================ ================================================ FILE: core/resources/src/debug/res/drawable/ic_logo_animated.xml ================================================ ================================================ FILE: core/resources/src/debug/res/drawable/ic_notification_icon.xml ================================================ ================================================ FILE: core/resources/src/debug/res/drawable-v31/ic_logo_animated.xml ================================================ ================================================ FILE: core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: core/resources/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: core/resources/src/debug/res/values/colors.xml ================================================ #00B1D3 #003131 ================================================ FILE: core/resources/src/debug/res/values/ic_launcher_background.xml ================================================ @color/primary ================================================ FILE: core/resources/src/debug/res/values-night/colors.xml ================================================ #003131 #00B1D3 ================================================ FILE: core/resources/src/debug/res/values-night-v31/colors.xml ================================================ @android:color/system_accent2_800 @android:color/system_accent1_100 ================================================ FILE: core/resources/src/debug/res/values-v31/colors.xml ================================================ @android:color/system_accent1_100 @android:color/system_accent1_700 ================================================ FILE: core/resources/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/Emoji.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.emoji import android.content.Context import android.content.res.Resources import android.net.Uri import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.EmojiEmotions import androidx.compose.material.icons.outlined.EmojiEvents import androidx.compose.material.icons.outlined.EmojiFoodBeverage import androidx.compose.material.icons.outlined.EmojiNature import androidx.compose.material.icons.outlined.EmojiObjects import androidx.compose.material.icons.outlined.EmojiSymbols import androidx.compose.material.icons.outlined.EmojiTransportation import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList object Emoji { private var Emotions: List? = null private var Food: List? = null private var Nature: List? = null private var Objects: List? = null private var Events: List? = null private var Transportation: List? = null private var Symbols: List? = null @Composable fun allIcons( context: Context = LocalContext.current ): ImmutableList = remember { derivedStateOf { initializeEmojis(context) (Emotions!! + Food!! + Nature!! + Objects!! + Events!! + Transportation!! + Symbols!!).toPersistentList() }.value } @Composable fun allIconsCategorized( context: Context = LocalContext.current, resources: Resources = LocalResources.current ): ImmutableList = remember { derivedStateOf { initializeEmojis(context) persistentListOf( EmojiData( title = resources.getString(R.string.emotions), icon = Icons.Outlined.EmojiEmotions, emojis = Emotions!! ), EmojiData( title = resources.getString(R.string.food_and_drink), icon = Icons.Outlined.EmojiFoodBeverage, emojis = Food!! ), EmojiData( title = resources.getString(R.string.nature_and_animals), icon = Icons.Outlined.EmojiNature, emojis = Nature!! ), EmojiData( title = resources.getString(R.string.objects), icon = Icons.Outlined.EmojiObjects, emojis = Objects!! ), EmojiData( title = resources.getString(R.string.activities), icon = Icons.Outlined.EmojiEvents, emojis = Events!! ), EmojiData( title = resources.getString(R.string.travels_and_places), icon = Icons.Outlined.EmojiTransportation, emojis = Transportation!! ), EmojiData( title = resources.getString(R.string.symbols), icon = Icons.Outlined.EmojiSymbols, emojis = Symbols!! ) ) }.value } private fun Context.listAssetFiles( path: String ): List = assets .list(path) ?.toMutableList() ?: emptyList() /** * Generates Uri of the assets path. */ private fun getFileFromAssets( cat: String, filename: String ): Uri = "file:///android_asset/svg/$cat/$filename".toUri() private fun initializeEmojis(context: Context) { if ( !listOf( Emotions, Food, Nature, Objects, Events, Transportation, Symbols ).all { it != null } ) { Emotions = context .listAssetFiles("svg/emotions") .sortedWith(String.CASE_INSENSITIVE_ORDER) .map { getFileFromAssets( cat = "emotions", filename = it ) } Food = context .listAssetFiles("svg/food") .sortedWith(String.CASE_INSENSITIVE_ORDER) .map { getFileFromAssets( cat = "food", filename = it ) } Nature = context .listAssetFiles("svg/nature") .sortedWith(String.CASE_INSENSITIVE_ORDER) .map { getFileFromAssets( cat = "nature", filename = it ) } Objects = context .listAssetFiles("svg/objects") .sortedWith(String.CASE_INSENSITIVE_ORDER) .map { getFileFromAssets( cat = "objects", filename = it ) } Events = context .listAssetFiles("svg/events") .sortedWith(String.CASE_INSENSITIVE_ORDER) .map { getFileFromAssets( cat = "events", filename = it ) } Transportation = context .listAssetFiles("svg/transportation") .sortedWith(String.CASE_INSENSITIVE_ORDER) .map { getFileFromAssets( cat = "transportation", filename = it ) } Symbols = context .listAssetFiles("svg/symbols") .sortedWith(String.CASE_INSENSITIVE_ORDER) .map { getFileFromAssets( cat = "symbols", filename = it ) } } } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/emoji/EmojiData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.emoji import android.net.Uri import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.vector.ImageVector @Stable @Immutable data class EmojiData( val title: String, val icon: ImageVector, val emojis: List ) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddPhotoAlt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.AddPhotoAlt: ImageVector by lazy { ImageVector.Builder( name = "Outlined.AddPhotoAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) close() moveTo(200f, 840f) quadTo(167f, 840f, 143.5f, 816.5f) quadTo(120f, 793f, 120f, 760f) lineTo(120f, 200f) quadTo(120f, 167f, 143.5f, 143.5f) quadTo(167f, 120f, 200f, 120f) lineTo(520f, 120f) quadTo(520f, 137f, 520f, 157f) quadTo(520f, 177f, 520f, 200f) lineTo(200f, 200f) quadTo(200f, 200f, 200f, 200f) quadTo(200f, 200f, 200f, 200f) lineTo(200f, 760f) quadTo(200f, 760f, 200f, 760f) quadTo(200f, 760f, 200f, 760f) lineTo(760f, 760f) quadTo(760f, 760f, 760f, 760f) quadTo(760f, 760f, 760f, 760f) lineTo(760f, 440f) quadTo(783f, 440f, 803f, 440f) quadTo(823f, 440f, 840f, 440f) lineTo(840f, 760f) quadTo(840f, 793f, 816.5f, 816.5f) quadTo(793f, 840f, 760f, 840f) lineTo(200f, 840f) close() moveTo(240f, 680f) lineTo(720f, 680f) lineTo(570f, 480f) lineTo(450f, 640f) lineTo(360f, 520f) lineTo(240f, 680f) close() moveTo(680f, 360f) lineTo(680f, 280f) lineTo(600f, 280f) lineTo(600f, 200f) lineTo(680f, 200f) lineTo(680f, 120f) lineTo(760f, 120f) lineTo(760f, 200f) lineTo(840f, 200f) lineTo(840f, 280f) lineTo(760f, 280f) lineTo(760f, 360f) lineTo(680f, 360f) close() } }.build() } val Icons.Rounded.AddPhotoAlt: ImageVector by lazy { ImageVector.Builder( name = "Rounded.AddPhotoAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(200f, 840f) quadTo(167f, 840f, 143.5f, 816.5f) quadTo(120f, 793f, 120f, 760f) lineTo(120f, 200f) quadTo(120f, 167f, 143.5f, 143.5f) quadTo(167f, 120f, 200f, 120f) lineTo(560f, 120f) quadTo(540f, 146f, 530f, 177f) quadTo(520f, 208f, 520f, 240f) quadTo(520f, 323f, 578.5f, 381.5f) quadTo(637f, 440f, 720f, 440f) quadTo(752f, 440f, 783f, 430f) quadTo(814f, 420f, 840f, 400f) lineTo(840f, 760f) quadTo(840f, 793f, 816.5f, 816.5f) quadTo(793f, 840f, 760f, 840f) lineTo(200f, 840f) close() moveTo(240f, 680f) lineTo(720f, 680f) lineTo(570f, 480f) lineTo(450f, 640f) lineTo(360f, 520f) lineTo(240f, 680f) close() moveTo(680f, 360f) lineTo(680f, 280f) lineTo(600f, 280f) lineTo(600f, 200f) lineTo(680f, 200f) lineTo(680f, 120f) lineTo(760f, 120f) lineTo(760f, 200f) lineTo(840f, 200f) lineTo(840f, 280f) lineTo(760f, 280f) lineTo(760f, 360f) lineTo(680f, 360f) close() } }.build() } val Icons.TwoTone.AddPhotoAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.AddPhotoAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12f, 12f) horizontalLineToRelative(0f) close() moveTo(5f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(5f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(8f) verticalLineToRelative(2f) horizontalLineTo(5f) verticalLineToRelative(14f) horizontalLineToRelative(14f) verticalLineToRelative(-8f) horizontalLineToRelative(2f) verticalLineToRelative(8f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(5f) close() moveTo(6f, 17f) horizontalLineToRelative(12f) lineToRelative(-3.75f, -5f) lineToRelative(-3f, 4f) lineToRelative(-2.25f, -3f) lineToRelative(-3f, 4f) close() moveTo(17f, 9f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } path(fill = SolidColor(Color.Black)) { moveTo(13.099f, 5.001f) curveToRelative(0.039f, -0.193f, 0.089f, -0.385f, 0.151f, -0.576f) curveToRelative(0.167f, -0.517f, 0.417f, -0.992f, 0.75f, -1.425f) horizontalLineToRelative(-1f) verticalLineToRelative(2f) lineToRelative(0.099f, 0.001f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(19f, 10.9f) curveToRelative(-0.33f, 0.066f, -0.664f, 0.1f, -1f, 0.1f) curveToRelative(-1.383f, 0f, -2.563f, -0.487f, -3.538f, -1.463f) curveToRelative(-0.975f, -0.975f, -1.462f, -2.154f, -1.462f, -3.537f) curveToRelative(0f, -0.336f, 0.033f, -0.669f, 0.099f, -0.999f) lineToRelative(-0.099f, -0.001f) horizontalLineToRelative(-8f) verticalLineToRelative(14f) horizontalLineToRelative(14f) verticalLineToRelative(-8f) lineToRelative(0f, -0.1f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19f, 11f) horizontalLineToRelative(2f) verticalLineToRelative(-1f) curveToRelative(-0.433f, 0.333f, -0.908f, 0.583f, -1.425f, 0.75f) curveToRelative(-0.19f, 0.061f, -0.382f, 0.111f, -0.575f, 0.15f) lineToRelative(-0f, 0.1f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6f, 17f) lineToRelative(3f, -4f) lineToRelative(2.25f, 3f) lineToRelative(3f, -4f) lineToRelative(3.75f, 5f) lineToRelative(-12f, 0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AddSticky.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.AddSticky: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.AddSticky", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11f, 13f) verticalLineToRelative(2f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.712f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-2f) verticalLineToRelative(-2f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) horizontalLineToRelative(2f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.412f, 3.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(5f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10.175f) curveToRelative(0.267f, 0f, 0.521f, -0.05f, 0.763f, -0.15f) curveToRelative(0.242f, -0.1f, 0.454f, -0.242f, 0.638f, -0.425f) lineToRelative(3.85f, -3.85f) curveToRelative(0.183f, -0.183f, 0.325f, -0.396f, 0.425f, -0.638f) curveToRelative(0.1f, -0.242f, 0.15f, -0.496f, 0.15f, -0.763f) verticalLineTo(5f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(19f, 15f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(2f) horizontalLineTo(5f) verticalLineTo(5f) horizontalLineToRelative(14f) verticalLineToRelative(10f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5f, 19f) verticalLineTo(5f) verticalLineToRelative(14f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analogous.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Analogous: ImageVector by lazy { Builder( name = "Analogous", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.0f, 8.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(16.9f, 8.0f, 18.0f, 8.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.0f, 8.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(4.9f, 8.0f, 6.0f, 8.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(10.9f, 4.0f, 12.0f, 4.0f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AnalogousComplementary.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.AnalogousComplementary: ImageVector by lazy { Builder( name = "AnalogousComplementary", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(10.9f, 16.0f, 12.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.0f, 8.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(16.9f, 8.0f, 18.0f, 8.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.0f, 8.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(4.9f, 8.0f, 6.0f, 8.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(10.9f, 4.0f, 12.0f, 4.0f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Analytics.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Analytics: ImageVector by lazy { Builder( name = "Analytics", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 192.0f, viewportHeight = 192.0f ).apply { path( fill = SolidColor(Color(0xFFF9AB00)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(130.0f, 29.0f) verticalLineToRelative(132.0f) curveToRelative(0.0f, 14.77f, 10.19f, 23.0f, 21.0f, 23.0f) curveToRelative(10.0f, 0.0f, 21.0f, -7.0f, 21.0f, -23.0f) verticalLineTo(30.0f) curveToRelative(0.0f, -13.54f, -10.0f, -22.0f, -21.0f, -22.0f) reflectiveCurveTo(130.0f, 17.33f, 130.0f, 29.0f) close() } path( fill = SolidColor(Color(0xFFE37400)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(75.0f, 96.0f) verticalLineToRelative(65.0f) curveToRelative(0.0f, 14.77f, 10.19f, 23.0f, 21.0f, 23.0f) curveToRelative(10.0f, 0.0f, 21.0f, -7.0f, 21.0f, -23.0f) verticalLineTo(97.0f) curveToRelative(0.0f, -13.54f, -10.0f, -22.0f, -21.0f, -22.0f) reflectiveCurveTo(75.0f, 84.33f, 75.0f, 96.0f) close() } path( fill = SolidColor(Color(0xFFE37400)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(41.0f, 163.0f) moveToRelative(-21.0f, 0.0f) arcToRelative( 21.0f, 21.0f, 0.0f, isMoreThanHalf = true, isPositiveArc = true, dx1 = 42.0f, dy1 = 0.0f ) arcToRelative( 21.0f, 21.0f, 0.0f, isMoreThanHalf = true, isPositiveArc = true, dx1 = -42.0f, dy1 = 0.0f ) } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Animation.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Animation: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Animation", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(360f, 880f) quadTo(302f, 880f, 251f, 858f) quadTo(200f, 836f, 162f, 798f) quadTo(124f, 760f, 102f, 709f) quadTo(80f, 658f, 80f, 600f) quadTo(80f, 519f, 122f, 452f) quadTo(164f, 385f, 232f, 350f) quadTo(252f, 311f, 281.5f, 281.5f) quadTo(311f, 252f, 350f, 232f) quadTo(383f, 164f, 451f, 122f) quadTo(519f, 80f, 600f, 80f) quadTo(658f, 80f, 709f, 102f) quadTo(760f, 124f, 798f, 162f) quadTo(836f, 200f, 858f, 251f) quadTo(880f, 302f, 880f, 360f) quadTo(880f, 445f, 838f, 510f) quadTo(796f, 575f, 728f, 610f) quadTo(708f, 649f, 678.5f, 678.5f) quadTo(649f, 708f, 610f, 728f) quadTo(575f, 796f, 508f, 838f) quadTo(441f, 880f, 360f, 880f) close() moveTo(360f, 800f) quadTo(393f, 800f, 423.5f, 790f) quadTo(454f, 780f, 480f, 760f) quadTo(422f, 760f, 371f, 738f) quadTo(320f, 716f, 282f, 678f) quadTo(244f, 640f, 222f, 589f) quadTo(200f, 538f, 200f, 480f) quadTo(180f, 506f, 170f, 536.5f) quadTo(160f, 567f, 160f, 600f) quadTo(160f, 642f, 176f, 678f) quadTo(192f, 714f, 219f, 741f) quadTo(246f, 768f, 282f, 784f) quadTo(318f, 800f, 360f, 800f) close() moveTo(480f, 680f) quadTo(513f, 680f, 544.5f, 670f) quadTo(576f, 660f, 602f, 640f) quadTo(543f, 640f, 492f, 617.5f) quadTo(441f, 595f, 403f, 557f) quadTo(365f, 519f, 342.5f, 468f) quadTo(320f, 417f, 320f, 358f) quadTo(300f, 384f, 290f, 415.5f) quadTo(280f, 447f, 280f, 480f) quadTo(280f, 522f, 295.5f, 558f) quadTo(311f, 594f, 339f, 621f) quadTo(366f, 649f, 402f, 664.5f) quadTo(438f, 680f, 480f, 680f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apng.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Apng: ImageVector by lazy { Builder( name = "Apng", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.0713f, 14.9742f) lineToRelative(-1.3831f, 0.0f) lineToRelative(-0.9221f, -2.4742f) lineToRelative(0.0f, 2.4742f) lineToRelative(-1.3831f, 0.0f) lineToRelative(0.0f, -5.938f) lineToRelative(1.3831f, 0.0f) lineToRelative(0.9221f, 2.4741f) lineToRelative(0.0f, -2.4741f) lineToRelative(1.3831f, 0.0f) lineToRelative(0.0f, 5.938f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.9922f, 10.5208f) horizontalLineTo(18.518f) verticalLineToRelative(2.969f) horizontalLineToRelative(0.9897f) verticalLineToRelative(-1.4845f) horizontalLineToRelative(1.4845f) verticalLineToRelative(1.6824f) curveToRelative(0.0f, 0.6928f, -0.4948f, 1.2866f, -1.2866f, 1.2866f) horizontalLineToRelative(-1.2866f) curveToRelative(-0.7917f, 0.0f, -1.2866f, -0.6928f, -1.2866f, -1.2866f) verticalLineTo(10.4218f) curveToRelative(-0.099f, -0.6928f, 0.3959f, -1.3855f, 1.1876f, -1.3855f) horizontalLineToRelative(1.2866f) curveToRelative(0.7917f, 0.0f, 1.2866f, 0.6928f, 1.2866f, 1.2866f) verticalLineToRelative(0.1979f) horizontalLineTo(20.9922f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(10.0217f, 9.0258f) horizontalLineTo(7.5476f) verticalLineToRelative(5.938f) horizontalLineToRelative(1.4845f) verticalLineToRelative(-1.9793f) horizontalLineToRelative(0.9897f) curveToRelative(0.7917f, 0.0f, 1.4845f, -0.6928f, 1.4845f, -1.4845f) verticalLineToRelative(-0.9897f) curveTo(11.5062f, 9.7185f, 10.8134f, 9.0258f, 10.0217f, 9.0258f) close() moveTo(10.0217f, 11.4999f) horizontalLineTo(9.0321f) verticalLineToRelative(-0.9897f) horizontalLineToRelative(0.9897f) verticalLineTo(11.4999f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(5.3746f, 9.0363f) horizontalLineTo(4.1912f) curveToRelative(-0.6536f, 0.0f, -1.1834f, 0.5299f, -1.1834f, 1.1834f) verticalLineToRelative(4.7335f) horizontalLineToRelative(1.1834f) verticalLineToRelative(-1.9688f) horizontalLineToRelative(1.1834f) verticalLineToRelative(1.9688f) horizontalLineToRelative(1.1834f) verticalLineTo(10.2197f) curveTo(6.5579f, 9.5661f, 6.0281f, 9.0363f, 5.3746f, 9.0363f) close() moveTo(4.288f, 10.5208f) horizontalLineToRelative(0.9897f) verticalLineToRelative(0.9897f) horizontalLineTo(4.288f) verticalLineTo(10.5208f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ApngBox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ApngBox: ImageVector by lazy { ImageVector.Builder( name = "Apng Box", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(19.0f, 3.0f) horizontalLineTo(5.0f) curveTo(3.89f, 3.0f, 3.0f, 3.89f, 3.0f, 5.0f) verticalLineToRelative(14.0f) curveToRelative(0.0f, 1.1046f, 0.8954f, 2.0f, 2.0f, 2.0f) horizontalLineToRelative(14.0f) curveToRelative(1.1046f, 0.0f, 2.0f, -0.8954f, 2.0f, -2.0f) verticalLineTo(5.0f) curveTo(21.0f, 3.89f, 20.1f, 3.0f, 19.0f, 3.0f) moveTo(19.0f, 5.0f) verticalLineToRelative(14.0f) horizontalLineTo(5.0f) verticalLineTo(5.0f) horizontalLineTo(19.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(17.9844f, 11.0121f) horizontalLineTo(16.3378f) verticalLineToRelative(1.9759f) horizontalLineToRelative(0.6586f) verticalLineToRelative(-0.9879f) horizontalLineToRelative(0.9879f) verticalLineToRelative(1.1197f) curveToRelative(0.0f, 0.461f, -0.3293f, 0.8562f, -0.8562f, 0.8562f) horizontalLineToRelative(-0.8562f) curveToRelative(-0.5269f, 0.0f, -0.8562f, -0.461f, -0.8562f, -0.8562f) verticalLineTo(10.9462f) curveToRelative(-0.0659f, -0.461f, 0.2635f, -0.9221f, 0.7904f, -0.9221f) horizontalLineToRelative(0.8562f) curveToRelative(0.5269f, 0.0f, 0.8562f, 0.461f, 0.8562f, 0.8562f) verticalLineToRelative(0.1317f) horizontalLineTo(17.9844f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(10.6834f, 10.0171f) horizontalLineTo(9.0369f) verticalLineToRelative(3.9518f) horizontalLineToRelative(0.9879f) verticalLineToRelative(-1.3173f) horizontalLineToRelative(0.6586f) curveToRelative(0.5269f, 0.0f, 0.9879f, -0.461f, 0.9879f, -0.9879f) verticalLineToRelative(-0.6586f) curveTo(11.6714f, 10.4782f, 11.2103f, 10.0171f, 10.6834f, 10.0171f) close() moveTo(10.6834f, 11.6637f) horizontalLineToRelative(-0.6586f) verticalLineToRelative(-0.6586f) horizontalLineToRelative(0.6586f) verticalLineTo(11.6637f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(7.5907f, 10.0241f) horizontalLineTo(6.8032f) curveToRelative(-0.435f, 0.0f, -0.7875f, 0.3526f, -0.7875f, 0.7876f) verticalLineToRelative(3.1502f) horizontalLineToRelative(0.7875f) verticalLineToRelative(-1.3103f) horizontalLineToRelative(0.7875f) verticalLineToRelative(1.3103f) horizontalLineToRelative(0.7875f) verticalLineTo(10.8117f) curveTo(8.3783f, 10.3767f, 8.0257f, 10.0241f, 7.5907f, 10.0241f) close() moveTo(6.8676f, 11.0121f) horizontalLineToRelative(0.6586f) verticalLineToRelative(0.6586f) horizontalLineTo(6.8676f) verticalLineTo(11.0121f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(14.7095f, 13.981f) lineToRelative(-0.9217f, 0.0f) lineToRelative(-0.6145f, -1.6487f) lineToRelative(0.0f, 1.6487f) lineToRelative(-0.9217f, 0.0f) lineToRelative(0.0f, -3.9569f) lineToRelative(0.9217f, 0.0f) lineToRelative(0.6145f, 1.6487f) lineToRelative(0.0f, -1.6487f) lineToRelative(0.9217f, 0.0f) lineToRelative(0.0f, 3.9569f) } }.build() } val Icons.TwoTone.ApngBox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ApngBox", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 5f) horizontalLineToRelative(14f) verticalLineToRelative(14f) horizontalLineToRelative(-14f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(19f, 3f) horizontalLineTo(5f) curveToRelative(-1.11f, 0f, -2f, 0.89f, -2f, 2f) verticalLineToRelative(14f) curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) horizontalLineToRelative(14f) curveToRelative(1.105f, 0f, 2f, -0.895f, 2f, -2f) verticalLineTo(5f) curveToRelative(0f, -1.11f, -0.9f, -2f, -2f, -2f) moveTo(19f, 5f) verticalLineToRelative(14f) horizontalLineTo(5f) verticalLineTo(5f) horizontalLineToRelative(14f) close() moveTo(17.984f, 11.012f) horizontalLineToRelative(-1.647f) verticalLineToRelative(1.976f) horizontalLineToRelative(0.659f) verticalLineToRelative(-0.988f) horizontalLineToRelative(0.988f) verticalLineToRelative(1.12f) curveToRelative(0f, 0.461f, -0.329f, 0.856f, -0.856f, 0.856f) horizontalLineToRelative(-0.856f) curveToRelative(-0.527f, 0f, -0.856f, -0.461f, -0.856f, -0.856f) verticalLineToRelative(-2.174f) curveToRelative(-0.066f, -0.461f, 0.264f, -0.922f, 0.79f, -0.922f) horizontalLineToRelative(0.856f) curveToRelative(0.527f, 0f, 0.856f, 0.461f, 0.856f, 0.856f) verticalLineToRelative(0.132f) horizontalLineToRelative(0.066f) moveTo(10.683f, 10.017f) horizontalLineToRelative(-1.647f) verticalLineToRelative(3.952f) horizontalLineToRelative(0.988f) verticalLineToRelative(-1.317f) horizontalLineToRelative(0.659f) curveToRelative(0.527f, 0f, 0.988f, -0.461f, 0.988f, -0.988f) verticalLineToRelative(-0.659f) curveToRelative(0f, -0.527f, -0.461f, -0.988f, -0.988f, -0.988f) close() moveTo(10.683f, 11.664f) horizontalLineToRelative(-0.659f) verticalLineToRelative(-0.659f) horizontalLineToRelative(0.659f) verticalLineToRelative(0.659f) close() moveTo(7.591f, 10.024f) horizontalLineToRelative(-0.788f) curveToRelative(-0.435f, 0f, -0.787f, 0.353f, -0.787f, 0.788f) verticalLineToRelative(3.15f) horizontalLineToRelative(0.787f) verticalLineToRelative(-1.31f) horizontalLineToRelative(0.787f) verticalLineToRelative(1.31f) horizontalLineToRelative(0.787f) verticalLineToRelative(-3.15f) curveToRelative(0f, -0.435f, -0.352f, -0.788f, -0.787f, -0.788f) close() moveTo(6.868f, 11.012f) horizontalLineToRelative(0.659f) verticalLineToRelative(0.659f) horizontalLineToRelative(-0.659f) verticalLineToRelative(-0.659f) close() moveTo(14.71f, 13.981f) horizontalLineToRelative(-0.922f) lineToRelative(-0.614f, -1.649f) verticalLineToRelative(1.649f) horizontalLineToRelative(-0.922f) verticalLineToRelative(-3.957f) horizontalLineToRelative(0.922f) lineToRelative(0.614f, 1.649f) verticalLineToRelative(-1.649f) horizontalLineToRelative(0.922f) verticalLineToRelative(3.957f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AppShortcut.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.AppShortcut: ImageVector by lazy { ImageVector.Builder( name = "AppShortcutOutlined", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(280f, 800f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) lineTo(680f, 840f) quadTo(680f, 840f, 680f, 840f) quadTo(680f, 840f, 680f, 840f) lineTo(680f, 800f) lineTo(280f, 800f) close() moveTo(280f, 160f) lineTo(680f, 160f) lineTo(680f, 120f) quadTo(680f, 120f, 680f, 120f) quadTo(680f, 120f, 680f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 160f) close() moveTo(280f, 160f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 160f) close() moveTo(280f, 800f) lineTo(280f, 800f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) lineTo(280f, 800f) close() moveTo(686f, 520f) lineTo(480f, 520f) quadTo(480f, 520f, 480f, 520f) quadTo(480f, 520f, 480f, 520f) lineTo(480f, 600f) quadTo(480f, 617f, 468.5f, 628.5f) quadTo(457f, 640f, 440f, 640f) quadTo(423f, 640f, 411.5f, 628.5f) quadTo(400f, 617f, 400f, 600f) lineTo(400f, 520f) quadTo(400f, 487f, 423.5f, 463.5f) quadTo(447f, 440f, 480f, 440f) lineTo(686f, 440f) lineTo(651f, 404f) quadTo(640f, 393f, 640f, 376.5f) quadTo(640f, 360f, 652f, 348f) quadTo(663f, 337f, 680f, 337f) quadTo(697f, 337f, 708f, 348f) lineTo(812f, 452f) quadTo(824f, 464f, 824f, 480f) quadTo(824f, 496f, 812f, 508f) lineTo(708f, 612f) quadTo(697f, 623f, 680.5f, 623.5f) quadTo(664f, 624f, 652f, 612f) quadTo(641f, 601f, 640.5f, 584.5f) quadTo(640f, 568f, 651f, 556f) lineTo(686f, 520f) close() moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 240f) quadTo(760f, 257f, 748.5f, 268.5f) quadTo(737f, 280f, 720f, 280f) quadTo(703f, 280f, 691.5f, 268.5f) quadTo(680f, 257f, 680f, 240f) lineTo(680f, 240f) lineTo(280f, 240f) lineTo(280f, 720f) lineTo(680f, 720f) lineTo(680f, 720f) quadTo(680f, 703f, 691.5f, 691.5f) quadTo(703f, 680f, 720f, 680f) quadTo(737f, 680f, 748.5f, 691.5f) quadTo(760f, 703f, 760f, 720f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() } }.build() } val Icons.Rounded.AppShortcut: ImageVector by lazy { ImageVector.Builder( name = "AppShortcut", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(686f, 520f) lineTo(480f, 520f) quadTo(480f, 520f, 480f, 520f) quadTo(480f, 520f, 480f, 520f) lineTo(480f, 600f) quadTo(480f, 617f, 468.5f, 628.5f) quadTo(457f, 640f, 440f, 640f) quadTo(423f, 640f, 411.5f, 628.5f) quadTo(400f, 617f, 400f, 600f) lineTo(400f, 520f) quadTo(400f, 487f, 423.5f, 463.5f) quadTo(447f, 440f, 480f, 440f) lineTo(686f, 440f) lineTo(651f, 404f) quadTo(640f, 393f, 640f, 376.5f) quadTo(640f, 360f, 652f, 348f) quadTo(663f, 337f, 680f, 337f) quadTo(697f, 337f, 708f, 348f) lineTo(812f, 452f) quadTo(824f, 464f, 824f, 480f) quadTo(824f, 496f, 812f, 508f) lineTo(708f, 612f) quadTo(697f, 623f, 680.5f, 623.5f) quadTo(664f, 624f, 652f, 612f) quadTo(641f, 601f, 640.5f, 584.5f) quadTo(640f, 568f, 651f, 556f) lineTo(686f, 520f) close() moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 240f) quadTo(760f, 257f, 748.5f, 268.5f) quadTo(737f, 280f, 720f, 280f) quadTo(703f, 280f, 691.5f, 268.5f) quadTo(680f, 257f, 680f, 240f) lineTo(680f, 240f) lineTo(280f, 240f) lineTo(280f, 720f) lineTo(680f, 720f) lineTo(680f, 720f) quadTo(680f, 703f, 691.5f, 691.5f) quadTo(703f, 680f, 720f, 680f) quadTo(737f, 680f, 748.5f, 691.5f) quadTo(760f, 703f, 760f, 720f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Apps.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Apps: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Apps", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(240f, 800f) quadTo(207f, 800f, 183.5f, 776.5f) quadTo(160f, 753f, 160f, 720f) quadTo(160f, 687f, 183.5f, 663.5f) quadTo(207f, 640f, 240f, 640f) quadTo(273f, 640f, 296.5f, 663.5f) quadTo(320f, 687f, 320f, 720f) quadTo(320f, 753f, 296.5f, 776.5f) quadTo(273f, 800f, 240f, 800f) close() moveTo(480f, 800f) quadTo(447f, 800f, 423.5f, 776.5f) quadTo(400f, 753f, 400f, 720f) quadTo(400f, 687f, 423.5f, 663.5f) quadTo(447f, 640f, 480f, 640f) quadTo(513f, 640f, 536.5f, 663.5f) quadTo(560f, 687f, 560f, 720f) quadTo(560f, 753f, 536.5f, 776.5f) quadTo(513f, 800f, 480f, 800f) close() moveTo(720f, 800f) quadTo(687f, 800f, 663.5f, 776.5f) quadTo(640f, 753f, 640f, 720f) quadTo(640f, 687f, 663.5f, 663.5f) quadTo(687f, 640f, 720f, 640f) quadTo(753f, 640f, 776.5f, 663.5f) quadTo(800f, 687f, 800f, 720f) quadTo(800f, 753f, 776.5f, 776.5f) quadTo(753f, 800f, 720f, 800f) close() moveTo(240f, 560f) quadTo(207f, 560f, 183.5f, 536.5f) quadTo(160f, 513f, 160f, 480f) quadTo(160f, 447f, 183.5f, 423.5f) quadTo(207f, 400f, 240f, 400f) quadTo(273f, 400f, 296.5f, 423.5f) quadTo(320f, 447f, 320f, 480f) quadTo(320f, 513f, 296.5f, 536.5f) quadTo(273f, 560f, 240f, 560f) close() moveTo(480f, 560f) quadTo(447f, 560f, 423.5f, 536.5f) quadTo(400f, 513f, 400f, 480f) quadTo(400f, 447f, 423.5f, 423.5f) quadTo(447f, 400f, 480f, 400f) quadTo(513f, 400f, 536.5f, 423.5f) quadTo(560f, 447f, 560f, 480f) quadTo(560f, 513f, 536.5f, 536.5f) quadTo(513f, 560f, 480f, 560f) close() moveTo(720f, 560f) quadTo(687f, 560f, 663.5f, 536.5f) quadTo(640f, 513f, 640f, 480f) quadTo(640f, 447f, 663.5f, 423.5f) quadTo(687f, 400f, 720f, 400f) quadTo(753f, 400f, 776.5f, 423.5f) quadTo(800f, 447f, 800f, 480f) quadTo(800f, 513f, 776.5f, 536.5f) quadTo(753f, 560f, 720f, 560f) close() moveTo(240f, 320f) quadTo(207f, 320f, 183.5f, 296.5f) quadTo(160f, 273f, 160f, 240f) quadTo(160f, 207f, 183.5f, 183.5f) quadTo(207f, 160f, 240f, 160f) quadTo(273f, 160f, 296.5f, 183.5f) quadTo(320f, 207f, 320f, 240f) quadTo(320f, 273f, 296.5f, 296.5f) quadTo(273f, 320f, 240f, 320f) close() moveTo(480f, 320f) quadTo(447f, 320f, 423.5f, 296.5f) quadTo(400f, 273f, 400f, 240f) quadTo(400f, 207f, 423.5f, 183.5f) quadTo(447f, 160f, 480f, 160f) quadTo(513f, 160f, 536.5f, 183.5f) quadTo(560f, 207f, 560f, 240f) quadTo(560f, 273f, 536.5f, 296.5f) quadTo(513f, 320f, 480f, 320f) close() moveTo(720f, 320f) quadTo(687f, 320f, 663.5f, 296.5f) quadTo(640f, 273f, 640f, 240f) quadTo(640f, 207f, 663.5f, 183.5f) quadTo(687f, 160f, 720f, 160f) quadTo(753f, 160f, 776.5f, 183.5f) quadTo(800f, 207f, 800f, 240f) quadTo(800f, 273f, 776.5f, 296.5f) quadTo(753f, 320f, 720f, 320f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Archive.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Archive: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Archive", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(451.5f, 411.5f) quadTo(440f, 423f, 440f, 440f) verticalLineToRelative(128f) lineToRelative(-36f, -36f) quadToRelative(-11f, -11f, -28f, -11f) reflectiveQuadToRelative(-28f, 11f) quadToRelative(-11f, 11f, -11f, 28f) reflectiveQuadToRelative(11f, 28f) lineToRelative(104f, 104f) quadToRelative(12f, 12f, 28f, 12f) reflectiveQuadToRelative(28f, -12f) lineToRelative(104f, -104f) quadToRelative(11f, -11f, 11f, -28f) reflectiveQuadToRelative(-11f, -28f) quadToRelative(-11f, -11f, -28f, -11f) reflectiveQuadToRelative(-28f, 11f) lineToRelative(-36f, 36f) verticalLineToRelative(-128f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(480f, 400f) quadToRelative(-17f, 0f, -28.5f, 11.5f) close() moveTo(200f, 320f) verticalLineToRelative(440f) horizontalLineToRelative(560f) verticalLineToRelative(-440f) lineTo(200f, 320f) close() moveTo(200f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(120f, 760f) verticalLineToRelative(-499f) quadToRelative(0f, -14f, 4.5f, -27f) reflectiveQuadToRelative(13.5f, -24f) lineToRelative(50f, -61f) quadToRelative(11f, -14f, 27.5f, -21.5f) reflectiveQuadTo(250f, 120f) horizontalLineToRelative(460f) quadToRelative(18f, 0f, 34.5f, 7.5f) reflectiveQuadTo(772f, 149f) lineToRelative(50f, 61f) quadToRelative(9f, 11f, 13.5f, 24f) reflectiveQuadToRelative(4.5f, 27f) verticalLineToRelative(499f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(760f, 840f) lineTo(200f, 840f) close() moveTo(216f, 240f) horizontalLineToRelative(528f) lineToRelative(-34f, -40f) lineTo(250f, 200f) lineToRelative(-34f, 40f) close() moveTo(480f, 540f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AreaChart.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.AreaChart: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.AreaChart", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(840f, 640f) lineTo(464f, 346f) lineTo(305f, 565f) lineTo(120f, 420f) lineTo(120f, 280f) lineTo(280f, 400f) lineTo(480f, 120f) lineTo(680f, 280f) lineTo(840f, 280f) lineTo(840f, 640f) close() moveTo(120f, 800f) lineTo(120f, 520f) lineTo(320f, 680f) lineTo(480f, 460f) lineTo(840f, 741f) lineTo(840f, 800f) lineTo(120f, 800f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ArtTrack.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ArtTrack: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Outlined", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14.412f, 5.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(3f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(10f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(7f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(13f, 17f) horizontalLineTo(3f) verticalLineTo(7f) horizontalLineToRelative(10f) verticalLineToRelative(10f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.5f, 14f) lineToRelative(-1f, -1.325f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.196f, -0.4f, -0.188f) reflectiveCurveToRelative(-0.3f, 0.079f, -0.4f, 0.213f) lineToRelative(-1.125f, 1.5f) curveToRelative(-0.133f, 0.167f, -0.146f, 0.342f, -0.038f, 0.525f) reflectiveCurveToRelative(0.262f, 0.275f, 0.463f, 0.275f) horizontalLineToRelative(6f) curveToRelative(0.2f, 0f, 0.35f, -0.092f, 0.45f, -0.275f) reflectiveCurveToRelative(0.083f, -0.358f, -0.05f, -0.525f) lineToRelative(-1.6f, -2.175f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-1.5f, 1.975f) close() } path(fill = SolidColor(Color.Black)) { moveTo(17.288f, 18.712f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) verticalLineTo(6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(12f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.288f, 18.712f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) verticalLineTo(6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(12f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(3f, 17f) verticalLineTo(7f) verticalLineToRelative(10f) close() } }.build() } val Icons.TwoTone.ArtTrack: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ArtTrack", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14.412f, 5.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(3f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(10f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(7f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(13f, 17f) horizontalLineTo(3f) verticalLineTo(7f) horizontalLineToRelative(10f) verticalLineToRelative(10f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.5f, 14f) lineToRelative(-1f, -1.325f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.196f, -0.4f, -0.188f) reflectiveCurveToRelative(-0.3f, 0.079f, -0.4f, 0.213f) lineToRelative(-1.125f, 1.5f) curveToRelative(-0.133f, 0.167f, -0.146f, 0.342f, -0.038f, 0.525f) reflectiveCurveToRelative(0.262f, 0.275f, 0.463f, 0.275f) horizontalLineToRelative(6f) curveToRelative(0.2f, 0f, 0.35f, -0.092f, 0.45f, -0.275f) reflectiveCurveToRelative(0.083f, -0.358f, -0.05f, -0.525f) lineToRelative(-1.6f, -2.175f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-1.5f, 1.975f) close() } path(fill = SolidColor(Color.Black)) { moveTo(17.288f, 18.712f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) verticalLineTo(6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(12f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.288f, 18.712f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) verticalLineTo(6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(12f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(3f, 17f) verticalLineTo(7f) verticalLineToRelative(10f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(2.989f, 7f) horizontalLineToRelative(10f) verticalLineToRelative(10f) horizontalLineToRelative(-10f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ascii.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Ascii: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Ascii", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.55f, 12.45f) verticalLineToRelative(1.71f) horizontalLineToRelative(-0.9f) verticalLineToRelative(5.13f) horizontalLineToRelative(0.9f) verticalLineToRelative(1.71f) horizontalLineToRelative(-3.6f) verticalLineToRelative(-1.71f) horizontalLineToRelative(0.9f) verticalLineToRelative(-5.13f) horizontalLineToRelative(-0.9f) verticalLineToRelative(-1.71f) horizontalLineToRelative(3.6f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4.8f, 3f) curveToRelative(-0.994f, 0f, -1.8f, 0.766f, -1.8f, 1.71f) verticalLineToRelative(6.84f) horizontalLineToRelative(1.8f) verticalLineToRelative(-3.42f) horizontalLineToRelative(1.8f) verticalLineToRelative(3.42f) horizontalLineToRelative(1.8f) verticalLineToRelative(-6.84f) curveToRelative(0f, -0.944f, -0.806f, -1.71f, -1.8f, -1.71f) horizontalLineToRelative(-1.8f) moveTo(4.8f, 4.71f) horizontalLineToRelative(1.8f) verticalLineToRelative(1.71f) horizontalLineToRelative(-1.8f) verticalLineToRelative(-1.71f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11.1f, 3f) curveToRelative(-0.994f, 0f, -1.8f, 0.766f, -1.8f, 1.71f) verticalLineToRelative(1.71f) curveToRelative(0f, 0.944f, 0.806f, 1.71f, 1.8f, 1.71f) horizontalLineToRelative(1.8f) verticalLineToRelative(1.71f) horizontalLineToRelative(-3.6f) verticalLineToRelative(1.71f) horizontalLineToRelative(3.6f) curveToRelative(0.994f, 0f, 1.8f, -0.766f, 1.8f, -1.71f) verticalLineToRelative(-1.71f) curveToRelative(0f, -0.944f, -0.806f, -1.71f, -1.8f, -1.71f) horizontalLineToRelative(-1.8f) verticalLineToRelative(-1.71f) horizontalLineToRelative(3.6f) verticalLineToRelative(-1.71f) horizontalLineToRelative(-3.6f) close() } path(fill = SolidColor(Color.Black)) { moveTo(17.4f, 3f) curveToRelative(-0.994f, 0f, -1.8f, 0.766f, -1.8f, 1.71f) verticalLineToRelative(5.13f) curveToRelative(0f, 0.944f, 0.806f, 1.71f, 1.8f, 1.71f) horizontalLineToRelative(1.8f) curveToRelative(0.994f, 0f, 1.8f, -0.766f, 1.8f, -1.71f) verticalLineToRelative(-0.855f) horizontalLineToRelative(-1.8f) verticalLineToRelative(0.855f) horizontalLineToRelative(-1.8f) verticalLineToRelative(-5.13f) horizontalLineToRelative(1.8f) verticalLineToRelative(0.855f) horizontalLineToRelative(1.8f) verticalLineToRelative(-0.855f) curveToRelative(0f, -0.944f, -0.806f, -1.71f, -1.8f, -1.71f) horizontalLineToRelative(-1.8f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.05f, 12.45f) verticalLineToRelative(1.71f) horizontalLineToRelative(-0.9f) verticalLineToRelative(5.13f) horizontalLineToRelative(0.9f) verticalLineToRelative(1.71f) horizontalLineToRelative(-3.6f) verticalLineToRelative(-1.71f) horizontalLineToRelative(0.9f) verticalLineToRelative(-5.13f) horizontalLineToRelative(-0.9f) verticalLineToRelative(-1.71f) horizontalLineToRelative(3.6f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/AutoDelete.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.AutoDelete: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.AutoDelete", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 240f) lineTo(280f, 240f) lineTo(280f, 400f) quadTo(280f, 400f, 280f, 481.5f) quadTo(280f, 563f, 280f, 680f) quadTo(280f, 701f, 280f, 721f) quadTo(280f, 741f, 280f, 760f) lineTo(280f, 760f) quadTo(280f, 760f, 280f, 760f) quadTo(280f, 760f, 280f, 760f) lineTo(280f, 240f) close() moveTo(450f, 840f) lineTo(280f, 840f) quadTo(247f, 840f, 223.5f, 816.5f) quadTo(200f, 793f, 200f, 760f) lineTo(200f, 240f) lineTo(160f, 240f) lineTo(160f, 160f) lineTo(360f, 160f) lineTo(360f, 120f) lineTo(600f, 120f) lineTo(600f, 160f) lineTo(800f, 160f) lineTo(800f, 240f) lineTo(760f, 240f) lineTo(760f, 412f) quadTo(743f, 407f, 720.5f, 403.5f) quadTo(698f, 400f, 680f, 400f) lineTo(680f, 240f) lineTo(280f, 240f) lineTo(280f, 760f) quadTo(280f, 760f, 280f, 760f) quadTo(280f, 760f, 280f, 760f) lineTo(412f, 760f) quadTo(418f, 781f, 428f, 801.5f) quadTo(438f, 822f, 450f, 840f) close() moveTo(360f, 680f) lineTo(400f, 680f) quadTo(400f, 617f, 420f, 576.5f) quadTo(440f, 536f, 440f, 536f) lineTo(440f, 320f) lineTo(360f, 320f) lineTo(360f, 680f) close() moveTo(520f, 450f) quadTo(537f, 439f, 558.5f, 428f) quadTo(580f, 417f, 600f, 412f) lineTo(600f, 320f) lineTo(520f, 320f) lineTo(520f, 450f) close() moveTo(680f, 880f) quadTo(597f, 880f, 538.5f, 821.5f) quadTo(480f, 763f, 480f, 680f) quadTo(480f, 597f, 538.5f, 538.5f) quadTo(597f, 480f, 680f, 480f) quadTo(763f, 480f, 821.5f, 538.5f) quadTo(880f, 597f, 880f, 680f) quadTo(880f, 763f, 821.5f, 821.5f) quadTo(763f, 880f, 680f, 880f) close() moveTo(746f, 774f) lineTo(774f, 746f) lineTo(700f, 672f) lineTo(700f, 560f) lineTo(660f, 560f) lineTo(660f, 688f) lineTo(746f, 774f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BackgroundColor.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.BackgroundColor: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.BackgroundColor", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(3.88f, 3f) curveToRelative(-0.5f, 0.06f, -0.88f, 0.5f, -0.88f, 1f) horizontalLineToRelative(0f) verticalLineToRelative(1f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) horizontalLineToRelative(-1.12f) } path(fill = SolidColor(Color.Black)) { moveTo(7f, 3f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(11f, 3f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(15f, 3f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(19f, 3f) verticalLineToRelative(2f) horizontalLineToRelative(2f) verticalLineToRelative(-1.12f) curveToRelative(-0.06f, -0.5f, -0.5f, -0.88f, -1f, -0.88f) horizontalLineToRelative(-1f) } path(fill = SolidColor(Color.Black)) { moveTo(3f, 7f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(19f, 7f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(3f, 11f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(3f, 15f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(3f, 19f) verticalLineToRelative(1.12f) curveToRelative(0.06f, 0.5f, 0.5f, 0.88f, 1f, 0.88f) horizontalLineToRelative(1f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) } path(fill = SolidColor(Color.Black)) { moveTo(7f, 19f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.462f, 21f) curveToRelative(-0.615f, 0f, -1.196f, -0.118f, -1.744f, -0.354f) reflectiveCurveToRelative(-1.026f, -0.559f, -1.434f, -0.967f) reflectiveCurveToRelative(-0.731f, -0.887f, -0.967f, -1.434f) reflectiveCurveToRelative(-0.354f, -1.129f, -0.354f, -1.744f) curveToRelative(0f, -0.622f, 0.122f, -1.207f, 0.366f, -1.755f) curveToRelative(0.244f, -0.548f, 0.574f, -1.024f, 0.99f, -1.429f) reflectiveCurveToRelative(0.902f, -0.726f, 1.457f, -0.962f) reflectiveCurveToRelative(1.148f, -0.354f, 1.778f, -0.354f) curveToRelative(0.6f, 0f, 1.166f, 0.103f, 1.699f, 0.309f) reflectiveCurveToRelative(0.999f, 0.491f, 1.401f, 0.855f) reflectiveCurveToRelative(0.72f, 0.795f, 0.956f, 1.294f) reflectiveCurveToRelative(0.354f, 1.037f, 0.354f, 1.614f) curveToRelative(0f, 0.862f, -0.262f, 1.524f, -0.788f, 1.986f) reflectiveCurveToRelative(-1.162f, 0.692f, -1.913f, 0.692f) horizontalLineToRelative(-0.832f) curveToRelative(-0.068f, 0f, -0.114f, 0.019f, -0.141f, 0.056f) reflectiveCurveToRelative(-0.039f, 0.079f, -0.039f, 0.124f) curveToRelative(0f, 0.09f, 0.056f, 0.219f, 0.169f, 0.388f) reflectiveCurveToRelative(0.169f, 0.362f, 0.169f, 0.579f) curveToRelative(0f, 0.375f, -0.103f, 0.652f, -0.309f, 0.832f) reflectiveCurveToRelative(-0.478f, 0.27f, -0.816f, 0.27f) close() moveTo(13.988f, 16.95f) curveToRelative(0.195f, 0f, 0.356f, -0.064f, 0.484f, -0.191f) reflectiveCurveToRelative(0.191f, -0.289f, 0.191f, -0.484f) curveToRelative(0f, -0.195f, -0.064f, -0.356f, -0.191f, -0.484f) reflectiveCurveToRelative(-0.289f, -0.191f, -0.484f, -0.191f) curveToRelative(-0.195f, 0f, -0.356f, 0.064f, -0.484f, 0.191f) reflectiveCurveToRelative(-0.191f, 0.289f, -0.191f, 0.484f) curveToRelative(0f, 0.195f, 0.064f, 0.356f, 0.191f, 0.484f) reflectiveCurveToRelative(0.289f, 0.191f, 0.484f, 0.191f) close() moveTo(15.338f, 15.15f) curveToRelative(0.195f, 0f, 0.356f, -0.064f, 0.484f, -0.191f) reflectiveCurveToRelative(0.191f, -0.289f, 0.191f, -0.484f) reflectiveCurveToRelative(-0.064f, -0.356f, -0.191f, -0.484f) reflectiveCurveToRelative(-0.289f, -0.191f, -0.484f, -0.191f) reflectiveCurveToRelative(-0.356f, 0.064f, -0.484f, 0.191f) reflectiveCurveToRelative(-0.191f, 0.289f, -0.191f, 0.484f) reflectiveCurveToRelative(0.064f, 0.356f, 0.191f, 0.484f) reflectiveCurveToRelative(0.289f, 0.191f, 0.484f, 0.191f) close() moveTo(17.587f, 15.15f) curveToRelative(0.195f, 0f, 0.356f, -0.064f, 0.484f, -0.191f) reflectiveCurveToRelative(0.191f, -0.289f, 0.191f, -0.484f) reflectiveCurveToRelative(-0.064f, -0.356f, -0.191f, -0.484f) reflectiveCurveToRelative(-0.289f, -0.191f, -0.484f, -0.191f) reflectiveCurveToRelative(-0.356f, 0.064f, -0.484f, 0.191f) reflectiveCurveToRelative(-0.191f, 0.289f, -0.191f, 0.484f) reflectiveCurveToRelative(0.064f, 0.356f, 0.191f, 0.484f) reflectiveCurveToRelative(0.289f, 0.191f, 0.484f, 0.191f) close() moveTo(18.938f, 16.95f) curveToRelative(0.195f, 0f, 0.356f, -0.064f, 0.484f, -0.191f) reflectiveCurveToRelative(0.191f, -0.289f, 0.191f, -0.484f) curveToRelative(0f, -0.195f, -0.064f, -0.356f, -0.191f, -0.484f) reflectiveCurveToRelative(-0.289f, -0.191f, -0.484f, -0.191f) reflectiveCurveToRelative(-0.356f, 0.064f, -0.484f, 0.191f) reflectiveCurveToRelative(-0.191f, 0.289f, -0.191f, 0.484f) curveToRelative(0f, 0.195f, 0.064f, 0.356f, 0.191f, 0.484f) reflectiveCurveToRelative(0.289f, 0.191f, 0.484f, 0.191f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BarcodeScanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.BarcodeScanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.BarcodeScanner", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(228.5f, 828.5f) quadTo(217f, 840f, 200f, 840f) lineTo(80f, 840f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(40f, 800f) verticalLineToRelative(-120f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(80f, 640f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(120f, 680f) verticalLineToRelative(80f) horizontalLineToRelative(80f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(240f, 800f) quadToRelative(0f, 17f, -11.5f, 28.5f) close() moveTo(908.5f, 651.5f) quadTo(920f, 663f, 920f, 680f) verticalLineToRelative(120f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(880f, 840f) lineTo(760f, 840f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(720f, 800f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(760f, 760f) horizontalLineToRelative(80f) verticalLineToRelative(-80f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(880f, 640f) quadToRelative(17f, 0f, 28.5f, 11.5f) close() moveTo(180f, 720f) quadToRelative(-8f, 0f, -14f, -6f) reflectiveQuadToRelative(-6f, -14f) verticalLineToRelative(-440f) quadToRelative(0f, -8f, 6f, -14f) reflectiveQuadToRelative(14f, -6f) horizontalLineToRelative(40f) quadToRelative(8f, 0f, 14f, 6f) reflectiveQuadToRelative(6f, 14f) verticalLineToRelative(440f) quadToRelative(0f, 8f, -6f, 14f) reflectiveQuadToRelative(-14f, 6f) horizontalLineToRelative(-40f) close() moveTo(286f, 714f) quadToRelative(-6f, -6f, -6f, -14f) verticalLineToRelative(-440f) quadToRelative(0f, -8f, 6f, -14f) reflectiveQuadToRelative(14f, -6f) quadToRelative(8f, 0f, 14f, 6f) reflectiveQuadToRelative(6f, 14f) verticalLineToRelative(440f) quadToRelative(0f, 8f, -6f, 14f) reflectiveQuadToRelative(-14f, 6f) quadToRelative(-8f, 0f, -14f, -6f) close() moveTo(420f, 720f) quadToRelative(-8f, 0f, -14f, -6f) reflectiveQuadToRelative(-6f, -14f) verticalLineToRelative(-440f) quadToRelative(0f, -8f, 6f, -14f) reflectiveQuadToRelative(14f, -6f) horizontalLineToRelative(40f) quadToRelative(8f, 0f, 14f, 6f) reflectiveQuadToRelative(6f, 14f) verticalLineToRelative(440f) quadToRelative(0f, 8f, -6f, 14f) reflectiveQuadToRelative(-14f, 6f) horizontalLineToRelative(-40f) close() moveTo(540f, 720f) quadToRelative(-8f, 0f, -14f, -6f) reflectiveQuadToRelative(-6f, -14f) verticalLineToRelative(-440f) quadToRelative(0f, -8f, 6f, -14f) reflectiveQuadToRelative(14f, -6f) horizontalLineToRelative(80f) quadToRelative(8f, 0f, 14f, 6f) reflectiveQuadToRelative(6f, 14f) verticalLineToRelative(440f) quadToRelative(0f, 8f, -6f, 14f) reflectiveQuadToRelative(-14f, 6f) horizontalLineToRelative(-80f) close() moveTo(686f, 714f) quadToRelative(-6f, -6f, -6f, -14f) verticalLineToRelative(-440f) quadToRelative(0f, -8f, 6f, -14f) reflectiveQuadToRelative(14f, -6f) quadToRelative(8f, 0f, 14f, 6f) reflectiveQuadToRelative(6f, 14f) verticalLineToRelative(440f) quadToRelative(0f, 8f, -6f, 14f) reflectiveQuadToRelative(-14f, 6f) quadToRelative(-8f, 0f, -14f, -6f) close() moveTo(766f, 714f) quadToRelative(-6f, -6f, -6f, -14f) verticalLineToRelative(-440f) quadToRelative(0f, -8f, 6f, -14f) reflectiveQuadToRelative(14f, -6f) quadToRelative(8f, 0f, 14f, 6f) reflectiveQuadToRelative(6f, 14f) verticalLineToRelative(440f) quadToRelative(0f, 8f, -6f, 14f) reflectiveQuadToRelative(-14f, 6f) quadToRelative(-8f, 0f, -14f, -6f) close() moveTo(228.5f, 188.5f) quadTo(217f, 200f, 200f, 200f) horizontalLineToRelative(-80f) verticalLineToRelative(80f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(80f, 320f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(40f, 280f) verticalLineToRelative(-120f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(80f, 120f) horizontalLineToRelative(120f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(240f, 160f) quadToRelative(0f, 17f, -11.5f, 28.5f) close() moveTo(731.5f, 131.5f) quadTo(743f, 120f, 760f, 120f) horizontalLineToRelative(120f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(920f, 160f) verticalLineToRelative(120f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(880f, 320f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(840f, 280f) verticalLineToRelative(-80f) horizontalLineToRelative(-80f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(720f, 160f) quadToRelative(0f, -17f, 11.5f, -28.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Base64.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Base64: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Base64", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(22f, 10.889f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) curveToRelative(0f, -1.227f, -0.995f, -2.222f, -2.222f, -2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) curveToRelative(-1.227f, 0f, -2.222f, 0.995f, -2.222f, 2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) curveToRelative(0f, 1.227f, 0.995f, 2.222f, 2.222f, 2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) curveToRelative(1.227f, 0f, 2.222f, -0.995f, 2.222f, -2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) close() moveTo(11.523f, 10.57f) horizontalLineToRelative(-2.86f) verticalLineToRelative(0.715f) horizontalLineToRelative(1.906f) curveToRelative(0.526f, 0f, 0.953f, 0.427f, 0.953f, 0.953f) verticalLineToRelative(1.668f) curveToRelative(0f, 0.526f, -0.427f, 0.953f, -0.953f, 0.953f) horizontalLineToRelative(-2.383f) curveToRelative(-0.526f, 0f, -0.953f, -0.427f, -0.953f, -0.953f) verticalLineToRelative(-3.813f) curveToRelative(0f, -0.526f, 0.427f, -0.953f, 0.953f, -0.953f) horizontalLineToRelative(3.336f) verticalLineToRelative(1.43f) close() moveTo(16.766f, 14.86f) horizontalLineToRelative(-1.43f) verticalLineToRelative(-2.383f) horizontalLineToRelative(-2.86f) verticalLineToRelative(-3.336f) horizontalLineToRelative(1.43f) verticalLineToRelative(1.906f) horizontalLineToRelative(1.43f) verticalLineToRelative(-1.906f) horizontalLineToRelative(1.43f) verticalLineToRelative(5.719f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(8.664f, 12.715f) horizontalLineToRelative(1.43f) verticalLineToRelative(0.715f) horizontalLineToRelative(-1.43f) close() } }.build() } val Icons.Outlined.Base64: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Base64", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(22f, 10.889f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) curveToRelative(0f, -1.227f, -0.995f, -2.222f, -2.222f, -2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) curveToRelative(-1.227f, 0f, -2.222f, 0.995f, -2.222f, 2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) curveToRelative(0f, 1.227f, 0.995f, 2.222f, 2.222f, 2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) curveToRelative(1.227f, 0f, 2.222f, -0.995f, 2.222f, -2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) close() moveTo(17.852f, 17.235f) curveToRelative(0f, 0.341f, -0.276f, 0.618f, -0.618f, 0.618f) horizontalLineTo(6.765f) curveToRelative(-0.341f, 0f, -0.618f, -0.276f, -0.618f, -0.618f) verticalLineTo(6.765f) curveToRelative(0f, -0.341f, 0.276f, -0.618f, 0.618f, -0.618f) horizontalLineToRelative(10.469f) curveToRelative(0.341f, 0f, 0.618f, 0.276f, 0.618f, 0.618f) verticalLineToRelative(10.469f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12.477f, 9.14f) lineToRelative(1.43f, 0f) lineToRelative(0f, 1.906f) lineToRelative(1.43f, 0f) lineToRelative(0f, -1.906f) lineToRelative(1.43f, 0f) lineToRelative(0f, 5.719f) lineToRelative(-1.43f, 0f) lineToRelative(0f, -2.383f) lineToRelative(-2.86f, 0f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(11.523f, 10.57f) verticalLineToRelative(-1.43f) horizontalLineToRelative(-3.336f) curveToRelative(-0.526f, 0f, -0.953f, 0.427f, -0.953f, 0.953f) verticalLineToRelative(3.813f) curveToRelative(0f, 0.526f, 0.427f, 0.953f, 0.953f, 0.953f) horizontalLineToRelative(2.383f) curveToRelative(0.526f, 0f, 0.953f, -0.427f, 0.953f, -0.953f) verticalLineToRelative(-1.668f) curveToRelative(0f, -0.526f, -0.427f, -0.953f, -0.953f, -0.953f) horizontalLineToRelative(-1.906f) verticalLineToRelative(-0.715f) horizontalLineToRelative(2.86f) close() moveTo(10.094f, 12.715f) verticalLineToRelative(0.715f) horizontalLineToRelative(-1.43f) verticalLineToRelative(-0.715f) horizontalLineToRelative(1.43f) close() } }.build() } val Icons.TwoTone.Base64: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Base64", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6.765f, 6.148f) lineTo(17.235f, 6.148f) arcTo(0.618f, 0.618f, 0f, isMoreThanHalf = false, isPositiveArc = true, 17.852f, 6.765f) lineTo(17.852f, 17.235f) arcTo( 0.618f, 0.618f, 0f, isMoreThanHalf = false, isPositiveArc = true, 17.235f, 17.852f ) lineTo(6.765f, 17.852f) arcTo(0.618f, 0.618f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6.148f, 17.235f) lineTo(6.148f, 6.765f) arcTo(0.618f, 0.618f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6.765f, 6.148f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12.477f, 9.14f) lineToRelative(1.43f, 0f) lineToRelative(0f, 1.906f) lineToRelative(1.43f, 0f) lineToRelative(0f, -1.906f) lineToRelative(1.43f, 0f) lineToRelative(0f, 5.719f) lineToRelative(-1.43f, 0f) lineToRelative(0f, -2.383f) lineToRelative(-2.86f, 0f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(11.523f, 10.57f) verticalLineToRelative(-1.43f) horizontalLineToRelative(-3.336f) curveToRelative(-0.526f, 0f, -0.953f, 0.427f, -0.953f, 0.953f) verticalLineToRelative(3.813f) curveToRelative(0f, 0.526f, 0.427f, 0.953f, 0.953f, 0.953f) horizontalLineToRelative(2.383f) curveToRelative(0.526f, 0f, 0.953f, -0.427f, 0.953f, -0.953f) verticalLineToRelative(-1.668f) curveToRelative(0f, -0.526f, -0.427f, -0.953f, -0.953f, -0.953f) horizontalLineToRelative(-1.906f) verticalLineToRelative(-0.715f) horizontalLineToRelative(2.86f) close() moveTo(10.094f, 12.715f) verticalLineToRelative(0.715f) horizontalLineToRelative(-1.43f) verticalLineToRelative(-0.715f) horizontalLineToRelative(1.43f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(22f, 10.889f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) curveToRelative(0f, -1.227f, -0.995f, -2.222f, -2.222f, -2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) curveToRelative(-1.227f, 0f, -2.222f, 0.995f, -2.222f, 2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) curveToRelative(0f, 1.227f, 0.995f, 2.222f, 2.222f, 2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) curveToRelative(1.227f, 0f, 2.222f, -0.995f, 2.222f, -2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(-2.222f) verticalLineToRelative(-2.222f) horizontalLineToRelative(2.222f) close() moveTo(17.852f, 17.235f) curveToRelative(0f, 0.341f, -0.276f, 0.618f, -0.618f, 0.618f) horizontalLineTo(6.765f) curveToRelative(-0.341f, 0f, -0.618f, -0.276f, -0.618f, -0.618f) verticalLineTo(6.765f) curveToRelative(0f, -0.341f, 0.276f, -0.618f, 0.618f, -0.618f) horizontalLineToRelative(10.469f) curveToRelative(0.341f, 0f, 0.618f, 0.276f, 0.618f, 0.618f) verticalLineToRelative(10.469f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BatchPrediction.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.BatchPrediction: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.BatchPrediction", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 880f) quadTo(247f, 880f, 223.5f, 856.5f) quadTo(200f, 833f, 200f, 800f) lineTo(200f, 400f) quadTo(200f, 367f, 223.5f, 343.5f) quadTo(247f, 320f, 280f, 320f) lineTo(680f, 320f) quadTo(713f, 320f, 736.5f, 343.5f) quadTo(760f, 367f, 760f, 400f) lineTo(760f, 800f) quadTo(760f, 833f, 736.5f, 856.5f) quadTo(713f, 880f, 680f, 880f) lineTo(280f, 880f) close() moveTo(280f, 800f) lineTo(680f, 800f) quadTo(680f, 800f, 680f, 800f) quadTo(680f, 800f, 680f, 800f) lineTo(680f, 400f) quadTo(680f, 400f, 680f, 400f) quadTo(680f, 400f, 680f, 400f) lineTo(280f, 400f) quadTo(280f, 400f, 280f, 400f) quadTo(280f, 400f, 280f, 400f) lineTo(280f, 800f) quadTo(280f, 800f, 280f, 800f) quadTo(280f, 800f, 280f, 800f) close() moveTo(440f, 760f) lineTo(520f, 760f) lineTo(520f, 720f) lineTo(440f, 720f) lineTo(440f, 760f) close() moveTo(440f, 680f) lineTo(520f, 680f) quadTo(520f, 661f, 529.5f, 645.5f) quadTo(539f, 630f, 550f, 615f) quadTo(562f, 598f, 571f, 578.5f) quadTo(580f, 559f, 580f, 538f) quadTo(580f, 497f, 551f, 468.5f) quadTo(522f, 440f, 480f, 440f) quadTo(438f, 440f, 409f, 468.5f) quadTo(380f, 497f, 380f, 538f) quadTo(380f, 559f, 389.5f, 578f) quadTo(399f, 597f, 410f, 614f) quadTo(421f, 630f, 430.5f, 645.5f) quadTo(440f, 661f, 440f, 680f) close() moveTo(240f, 260f) quadTo(240f, 235f, 257.5f, 217.5f) quadTo(275f, 200f, 300f, 200f) lineTo(660f, 200f) quadTo(685f, 200f, 702.5f, 217.5f) quadTo(720f, 235f, 720f, 260f) lineTo(240f, 260f) close() moveTo(280f, 140f) quadTo(280f, 115f, 297.5f, 97.5f) quadTo(315f, 80f, 340f, 80f) lineTo(620f, 80f) quadTo(645f, 80f, 662.5f, 97.5f) quadTo(680f, 115f, 680f, 140f) lineTo(280f, 140f) close() moveTo(480f, 600f) quadTo(480f, 600f, 480f, 600f) quadTo(480f, 600f, 480f, 600f) lineTo(480f, 600f) quadTo(480f, 600f, 480f, 600f) quadTo(480f, 600f, 480f, 600f) lineTo(480f, 600f) quadTo(480f, 600f, 480f, 600f) quadTo(480f, 600f, 480f, 600f) lineTo(480f, 600f) quadTo(480f, 600f, 480f, 600f) quadTo(480f, 600f, 480f, 600f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Beta.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Beta: ImageVector by lazy { Builder( name = "Beta", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(9.23f, 17.59f) verticalLineTo(23.12f) horizontalLineTo(6.88f) verticalLineTo(6.72f) curveTo(6.88f, 5.27f, 7.31f, 4.13f, 8.16f, 3.28f) curveTo(9.0f, 2.43f, 10.17f, 2.0f, 11.61f, 2.0f) curveTo(13.0f, 2.0f, 14.07f, 2.34f, 14.87f, 3.0f) curveTo(15.66f, 3.68f, 16.05f, 4.62f, 16.05f, 5.81f) curveTo(16.05f, 6.63f, 15.79f, 7.4f, 15.27f, 8.11f) curveTo(14.75f, 8.82f, 14.08f, 9.31f, 13.25f, 9.58f) verticalLineTo(9.62f) curveTo(14.5f, 9.82f, 15.47f, 10.27f, 16.13f, 11.0f) curveTo(16.79f, 11.71f, 17.12f, 12.62f, 17.12f, 13.74f) curveTo(17.12f, 15.06f, 16.66f, 16.14f, 15.75f, 16.97f) curveTo(14.83f, 17.8f, 13.63f, 18.21f, 12.13f, 18.21f) curveTo(11.07f, 18.21f, 10.1f, 18.0f, 9.23f, 17.59f) moveTo(10.72f, 10.75f) verticalLineTo(8.83f) curveTo(11.59f, 8.72f, 12.3f, 8.4f, 12.87f, 7.86f) curveTo(13.43f, 7.31f, 13.71f, 6.7f, 13.71f, 6.0f) curveTo(13.71f, 4.62f, 13.0f, 3.92f, 11.6f, 3.92f) curveTo(10.84f, 3.92f, 10.25f, 4.16f, 9.84f, 4.65f) curveTo(9.43f, 5.14f, 9.23f, 5.82f, 9.23f, 6.71f) verticalLineTo(15.5f) curveTo(10.14f, 16.03f, 11.03f, 16.29f, 11.89f, 16.29f) curveTo(12.73f, 16.29f, 13.39f, 16.07f, 13.86f, 15.64f) curveTo(14.33f, 15.2f, 14.56f, 14.58f, 14.56f, 13.79f) curveTo(14.56f, 12.0f, 13.28f, 11.0f, 10.72f, 10.75f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bitcoin.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Bitcoin: ImageVector by lazy { Builder( name = "Bitcoin", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(14.24f, 10.56f) curveTo(13.93f, 11.8f, 12.0f, 11.17f, 11.4f, 11.0f) lineTo(11.95f, 8.82f) curveTo(12.57f, 9.0f, 14.56f, 9.26f, 14.24f, 10.56f) moveTo(11.13f, 12.12f) lineTo(10.53f, 14.53f) curveTo(11.27f, 14.72f, 13.56f, 15.45f, 13.9f, 14.09f) curveTo(14.26f, 12.67f, 11.87f, 12.3f, 11.13f, 12.12f) moveTo(21.7f, 14.42f) curveTo(20.36f, 19.78f, 14.94f, 23.04f, 9.58f, 21.7f) curveTo(4.22f, 20.36f, 0.963f, 14.94f, 2.3f, 9.58f) curveTo(3.64f, 4.22f, 9.06f, 0.964f, 14.42f, 2.3f) curveTo(19.77f, 3.64f, 23.03f, 9.06f, 21.7f, 14.42f) moveTo(14.21f, 8.05f) lineTo(14.66f, 6.25f) lineTo(13.56f, 6.0f) lineTo(13.12f, 7.73f) curveTo(12.83f, 7.66f, 12.54f, 7.59f, 12.24f, 7.53f) lineTo(12.68f, 5.76f) lineTo(11.59f, 5.5f) lineTo(11.14f, 7.29f) curveTo(10.9f, 7.23f, 10.66f, 7.18f, 10.44f, 7.12f) lineTo(10.44f, 7.12f) lineTo(8.93f, 6.74f) lineTo(8.63f, 7.91f) curveTo(8.63f, 7.91f, 9.45f, 8.1f, 9.43f, 8.11f) curveTo(9.88f, 8.22f, 9.96f, 8.5f, 9.94f, 8.75f) lineTo(8.71f, 13.68f) curveTo(8.66f, 13.82f, 8.5f, 14.0f, 8.21f, 13.95f) curveTo(8.22f, 13.96f, 7.41f, 13.75f, 7.41f, 13.75f) lineTo(6.87f, 15.0f) lineTo(8.29f, 15.36f) curveTo(8.56f, 15.43f, 8.82f, 15.5f, 9.08f, 15.56f) lineTo(8.62f, 17.38f) lineTo(9.72f, 17.66f) lineTo(10.17f, 15.85f) curveTo(10.47f, 15.93f, 10.76f, 16.0f, 11.04f, 16.08f) lineTo(10.59f, 17.87f) lineTo(11.69f, 18.15f) lineTo(12.15f, 16.33f) curveTo(14.0f, 16.68f, 15.42f, 16.54f, 16.0f, 14.85f) curveTo(16.5f, 13.5f, 16.0f, 12.7f, 15.0f, 12.19f) curveTo(15.72f, 12.0f, 16.26f, 11.55f, 16.41f, 10.57f) curveTo(16.61f, 9.24f, 15.59f, 8.53f, 14.21f, 8.05f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Block.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.TwoTone.Block: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Block", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 2f) lineTo(12f, 2f) arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 22f, 12f) lineTo(22f, 12f) arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 22f) lineTo(12f, 22f) arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 2f, 12f) lineTo(2f, 12f) arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 2f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 22f) curveToRelative(-1.383f, 0f, -2.683f, -0.262f, -3.9f, -0.788f) reflectiveCurveToRelative(-2.275f, -1.237f, -3.175f, -2.138f) reflectiveCurveToRelative(-1.612f, -1.958f, -2.138f, -3.175f) reflectiveCurveToRelative(-0.788f, -2.517f, -0.788f, -3.9f) curveToRelative(0f, -1.383f, 0.262f, -2.683f, 0.788f, -3.9f) reflectiveCurveToRelative(1.237f, -2.275f, 2.138f, -3.175f) reflectiveCurveToRelative(1.958f, -1.612f, 3.175f, -2.138f) reflectiveCurveToRelative(2.517f, -0.788f, 3.9f, -0.788f) curveToRelative(1.383f, 0f, 2.683f, 0.262f, 3.9f, 0.788f) reflectiveCurveToRelative(2.275f, 1.237f, 3.175f, 2.138f) reflectiveCurveToRelative(1.612f, 1.958f, 2.138f, 3.175f) reflectiveCurveToRelative(0.788f, 2.517f, 0.788f, 3.9f) curveToRelative(0f, 1.383f, -0.262f, 2.683f, -0.788f, 3.9f) reflectiveCurveToRelative(-1.237f, 2.275f, -2.138f, 3.175f) reflectiveCurveToRelative(-1.958f, 1.612f, -3.175f, 2.138f) reflectiveCurveToRelative(-2.517f, 0.788f, -3.9f, 0.788f) close() moveTo(12f, 20f) curveToRelative(0.9f, 0f, 1.767f, -0.146f, 2.6f, -0.438f) reflectiveCurveToRelative(1.6f, -0.712f, 2.3f, -1.263f) lineTo(5.7f, 7.1f) curveToRelative(-0.55f, 0.7f, -0.971f, 1.467f, -1.263f, 2.3f) reflectiveCurveToRelative(-0.438f, 1.7f, -0.438f, 2.6f) curveToRelative(0f, 2.233f, 0.775f, 4.125f, 2.325f, 5.675f) reflectiveCurveToRelative(3.442f, 2.325f, 5.675f, 2.325f) close() moveTo(18.3f, 16.9f) curveToRelative(0.55f, -0.7f, 0.971f, -1.467f, 1.263f, -2.3f) reflectiveCurveToRelative(0.438f, -1.7f, 0.438f, -2.6f) curveToRelative(0f, -2.233f, -0.775f, -4.125f, -2.325f, -5.675f) reflectiveCurveToRelative(-3.442f, -2.325f, -5.675f, -2.325f) curveToRelative(-0.9f, 0f, -1.767f, 0.146f, -2.6f, 0.438f) reflectiveCurveToRelative(-1.6f, 0.712f, -2.3f, 1.263f) lineToRelative(11.2f, 11.2f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BlurCircular.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.BlurCircular: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.BlurCircular", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(400f, 440f) quadTo(417f, 440f, 428.5f, 428.5f) quadTo(440f, 417f, 440f, 400f) quadTo(440f, 383f, 428.5f, 371.5f) quadTo(417f, 360f, 400f, 360f) quadTo(383f, 360f, 371.5f, 371.5f) quadTo(360f, 383f, 360f, 400f) quadTo(360f, 417f, 371.5f, 428.5f) quadTo(383f, 440f, 400f, 440f) close() moveTo(400f, 600f) quadTo(417f, 600f, 428.5f, 588.5f) quadTo(440f, 577f, 440f, 560f) quadTo(440f, 543f, 428.5f, 531.5f) quadTo(417f, 520f, 400f, 520f) quadTo(383f, 520f, 371.5f, 531.5f) quadTo(360f, 543f, 360f, 560f) quadTo(360f, 577f, 371.5f, 588.5f) quadTo(383f, 600f, 400f, 600f) close() moveTo(280f, 420f) quadTo(288f, 420f, 294f, 414f) quadTo(300f, 408f, 300f, 400f) quadTo(300f, 392f, 294f, 386f) quadTo(288f, 380f, 280f, 380f) quadTo(272f, 380f, 266f, 386f) quadTo(260f, 392f, 260f, 400f) quadTo(260f, 408f, 266f, 414f) quadTo(272f, 420f, 280f, 420f) close() moveTo(400f, 700f) quadTo(408f, 700f, 414f, 694f) quadTo(420f, 688f, 420f, 680f) quadTo(420f, 672f, 414f, 666f) quadTo(408f, 660f, 400f, 660f) quadTo(392f, 660f, 386f, 666f) quadTo(380f, 672f, 380f, 680f) quadTo(380f, 688f, 386f, 694f) quadTo(392f, 700f, 400f, 700f) close() moveTo(280f, 580f) quadTo(288f, 580f, 294f, 574f) quadTo(300f, 568f, 300f, 560f) quadTo(300f, 552f, 294f, 546f) quadTo(288f, 540f, 280f, 540f) quadTo(272f, 540f, 266f, 546f) quadTo(260f, 552f, 260f, 560f) quadTo(260f, 568f, 266f, 574f) quadTo(272f, 580f, 280f, 580f) close() moveTo(400f, 300f) quadTo(408f, 300f, 414f, 294f) quadTo(420f, 288f, 420f, 280f) quadTo(420f, 272f, 414f, 266f) quadTo(408f, 260f, 400f, 260f) quadTo(392f, 260f, 386f, 266f) quadTo(380f, 272f, 380f, 280f) quadTo(380f, 288f, 386f, 294f) quadTo(392f, 300f, 400f, 300f) close() moveTo(560f, 440f) quadTo(577f, 440f, 588.5f, 428.5f) quadTo(600f, 417f, 600f, 400f) quadTo(600f, 383f, 588.5f, 371.5f) quadTo(577f, 360f, 560f, 360f) quadTo(543f, 360f, 531.5f, 371.5f) quadTo(520f, 383f, 520f, 400f) quadTo(520f, 417f, 531.5f, 428.5f) quadTo(543f, 440f, 560f, 440f) close() moveTo(560f, 300f) quadTo(568f, 300f, 574f, 294f) quadTo(580f, 288f, 580f, 280f) quadTo(580f, 272f, 574f, 266f) quadTo(568f, 260f, 560f, 260f) quadTo(552f, 260f, 546f, 266f) quadTo(540f, 272f, 540f, 280f) quadTo(540f, 288f, 546f, 294f) quadTo(552f, 300f, 560f, 300f) close() moveTo(680f, 580f) quadTo(688f, 580f, 694f, 574f) quadTo(700f, 568f, 700f, 560f) quadTo(700f, 552f, 694f, 546f) quadTo(688f, 540f, 680f, 540f) quadTo(672f, 540f, 666f, 546f) quadTo(660f, 552f, 660f, 560f) quadTo(660f, 568f, 666f, 574f) quadTo(672f, 580f, 680f, 580f) close() moveTo(680f, 420f) quadTo(688f, 420f, 694f, 414f) quadTo(700f, 408f, 700f, 400f) quadTo(700f, 392f, 694f, 386f) quadTo(688f, 380f, 680f, 380f) quadTo(672f, 380f, 666f, 386f) quadTo(660f, 392f, 660f, 400f) quadTo(660f, 408f, 666f, 414f) quadTo(672f, 420f, 680f, 420f) close() moveTo(480f, 880f) quadTo(397f, 880f, 324f, 848.5f) quadTo(251f, 817f, 197f, 763f) quadTo(143f, 709f, 111.5f, 636f) quadTo(80f, 563f, 80f, 480f) quadTo(80f, 397f, 111.5f, 324f) quadTo(143f, 251f, 197f, 197f) quadTo(251f, 143f, 324f, 111.5f) quadTo(397f, 80f, 480f, 80f) quadTo(563f, 80f, 636f, 111.5f) quadTo(709f, 143f, 763f, 197f) quadTo(817f, 251f, 848.5f, 324f) quadTo(880f, 397f, 880f, 480f) quadTo(880f, 563f, 848.5f, 636f) quadTo(817f, 709f, 763f, 763f) quadTo(709f, 817f, 636f, 848.5f) quadTo(563f, 880f, 480f, 880f) close() moveTo(560f, 700f) quadTo(568f, 700f, 574f, 694f) quadTo(580f, 688f, 580f, 680f) quadTo(580f, 672f, 574f, 666f) quadTo(568f, 660f, 560f, 660f) quadTo(552f, 660f, 546f, 666f) quadTo(540f, 672f, 540f, 680f) quadTo(540f, 688f, 546f, 694f) quadTo(552f, 700f, 560f, 700f) close() moveTo(560f, 600f) quadTo(577f, 600f, 588.5f, 588.5f) quadTo(600f, 577f, 600f, 560f) quadTo(600f, 543f, 588.5f, 531.5f) quadTo(577f, 520f, 560f, 520f) quadTo(543f, 520f, 531.5f, 531.5f) quadTo(520f, 543f, 520f, 560f) quadTo(520f, 577f, 531.5f, 588.5f) quadTo(543f, 600f, 560f, 600f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BoldLine.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.BoldLine: ImageVector by lazy { ImageVector.Builder( name = "BoldLine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(19.071f, 4.929f) curveToRelative(-0.585f, -0.585f, -1.534f, -0.585f, -2.12f, 0f) lineTo(12.709f, 9.172f) curveToRelative(-0f, 0f, -0f, 0f, -0f, 0f) lineToRelative(-3.537f, 3.537f) curveToRelative(-0f, 0f, -0f, 0f, -0f, 0f) lineToRelative(-4.242f, 4.242f) curveToRelative(-0.585f, 0.585f, -0.585f, 1.534f, 0f, 2.119f) curveToRelative(0.585f, 0.585f, 1.534f, 0.585f, 2.12f, 0f) lineToRelative(4.242f, -4.242f) curveToRelative(0f, -0f, 0f, -0f, 0f, -0f) lineToRelative(3.537f, -3.537f) lineToRelative(4.242f, -4.242f) curveTo(19.656f, 6.464f, 19.656f, 5.515f, 19.071f, 4.929f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Bolt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Bolt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Bolt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(10.55f, 18.2f) lineToRelative(5.175f, -6.2f) horizontalLineToRelative(-4f) lineToRelative(0.725f, -5.675f) lineToRelative(-4.625f, 6.675f) horizontalLineToRelative(3.475f) lineToRelative(-0.75f, 5.2f) close() moveTo(9f, 15f) horizontalLineToRelative(-3.1f) curveToRelative(-0.4f, 0f, -0.696f, -0.179f, -0.887f, -0.538f) reflectiveCurveToRelative(-0.171f, -0.704f, 0.063f, -1.038f) lineTo(12.55f, 2.675f) curveToRelative(0.167f, -0.233f, 0.383f, -0.396f, 0.65f, -0.488f) reflectiveCurveToRelative(0.542f, -0.087f, 0.825f, 0.013f) reflectiveCurveToRelative(0.492f, 0.275f, 0.625f, 0.525f) reflectiveCurveToRelative(0.183f, 0.517f, 0.15f, 0.8f) lineToRelative(-0.8f, 6.475f) horizontalLineToRelative(3.875f) curveToRelative(0.433f, 0f, 0.738f, 0.192f, 0.913f, 0.575f) reflectiveCurveToRelative(0.121f, 0.742f, -0.162f, 1.075f) lineToRelative(-8.225f, 9.85f) curveToRelative(-0.183f, 0.217f, -0.408f, 0.358f, -0.675f, 0.425f) reflectiveCurveToRelative(-0.525f, 0.042f, -0.775f, -0.075f) reflectiveCurveToRelative(-0.446f, -0.296f, -0.587f, -0.538f) reflectiveCurveToRelative(-0.196f, -0.504f, -0.162f, -0.788f) lineToRelative(0.8f, -5.525f) close() } }.build() } val Icons.TwoTone.Bolt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoToneBolt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(18.787f, 10.575f) curveToRelative(-0.175f, -0.383f, -0.479f, -0.575f, -0.912f, -0.575f) horizontalLineToRelative(-3.875f) lineToRelative(0.8f, -6.475f) curveToRelative(0.033f, -0.283f, -0.017f, -0.55f, -0.15f, -0.8f) curveToRelative(-0.133f, -0.25f, -0.342f, -0.425f, -0.625f, -0.525f) curveToRelative(-0.283f, -0.1f, -0.558f, -0.104f, -0.825f, -0.013f) reflectiveCurveToRelative(-0.483f, 0.254f, -0.65f, 0.487f) lineToRelative(-7.475f, 10.75f) curveToRelative(-0.233f, 0.333f, -0.254f, 0.679f, -0.063f, 1.038f) reflectiveCurveToRelative(0.487f, 0.537f, 0.888f, 0.537f) horizontalLineToRelative(3.1f) lineToRelative(-0.8f, 5.525f) curveToRelative(-0.033f, 0.283f, 0.021f, 0.546f, 0.162f, 0.787f) reflectiveCurveToRelative(0.338f, 0.421f, 0.588f, 0.537f) curveToRelative(0.25f, 0.117f, 0.508f, 0.142f, 0.775f, 0.075f) reflectiveCurveToRelative(0.492f, -0.208f, 0.675f, -0.425f) lineToRelative(8.225f, -9.85f) curveToRelative(0.283f, -0.333f, 0.338f, -0.692f, 0.162f, -1.075f) close() moveTo(10.55f, 18.2f) lineToRelative(0.75f, -5.2f) horizontalLineToRelative(-3.475f) lineToRelative(4.625f, -6.675f) lineToRelative(-0.725f, 5.675f) horizontalLineToRelative(4f) lineToRelative(-5.175f, 6.2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(10.55f, 18.2f) lineToRelative(5.175f, -6.2f) lineToRelative(-4f, 0f) lineToRelative(0.725f, -5.675f) lineToRelative(-4.625f, 6.675f) lineToRelative(3.475f, 0f) lineToRelative(-0.75f, 5.2f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkOff.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.BookmarkOff: ImageVector by lazy { Builder( name = "Bookmark Off", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(3.28f, 4.0f) lineTo(2.0f, 5.27f) lineTo(5.0f, 8.27f) verticalLineTo(21.0f) lineTo(12.0f, 18.0f) lineTo(16.78f, 20.05f) lineTo(18.73f, 22.0f) lineTo(20.0f, 20.72f) lineTo(3.28f, 4.0f) moveTo(7.0f, 18.0f) verticalLineTo(10.27f) lineTo(13.0f, 16.25f) lineTo(12.0f, 15.82f) lineTo(7.0f, 18.0f) moveTo(7.0f, 5.16f) lineTo(5.5f, 3.67f) curveTo(5.88f, 3.26f, 6.41f, 3.0f, 7.0f, 3.0f) horizontalLineTo(17.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 19.0f, y1 = 5.0f ) verticalLineTo(17.16f) lineTo(17.0f, 15.16f) verticalLineTo(5.0f) horizontalLineTo(7.0f) verticalLineTo(5.16f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BookmarkRemove.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.BookmarkRemove: ImageVector by lazy { Builder( name = "Bookmark Remove", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.0f, 3.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 19.0f, y1 = 5.0f ) verticalLineTo(21.0f) lineTo(12.0f, 18.0f) lineTo(5.0f, 21.0f) verticalLineTo(5.0f) curveTo(5.0f, 3.89f, 5.9f, 3.0f, 7.0f, 3.0f) horizontalLineTo(17.0f) moveTo(8.17f, 8.58f) lineTo(10.59f, 11.0f) lineTo(8.17f, 13.41f) lineTo(9.59f, 14.83f) lineTo(12.0f, 12.41f) lineTo(14.41f, 14.83f) lineTo(15.83f, 13.41f) lineTo(13.41f, 11.0f) lineTo(15.83f, 8.58f) lineTo(14.41f, 7.17f) lineTo(12.0f, 9.58f) lineTo(9.59f, 7.17f) lineTo(8.17f, 8.58f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Boosty.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Boosty: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Boosty", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(2.661f, 14.337f) lineTo(6.801f, 0f) horizontalLineToRelative(6.362f) lineTo(11.88f, 4.444f) lineToRelative(-0.038f, 0.077f) lineToRelative(-3.378f, 11.733f) horizontalLineToRelative(3.15f) curveToRelative(-1.321f, 3.289f, -2.35f, 5.867f, -3.086f, 7.733f) curveToRelative(-5.816f, -0.063f, -7.442f, -4.228f, -6.02f, -9.155f) moveTo(8.554f, 24f) lineToRelative(7.67f, -11.035f) horizontalLineToRelative(-3.25f) lineToRelative(2.83f, -7.073f) curveToRelative(4.852f, 0.508f, 7.137f, 4.33f, 5.791f, 8.952f) curveTo(20.16f, 19.81f, 14.344f, 24f, 8.68f, 24f) horizontalLineToRelative(-0.127f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BorderColor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.BorderColor: ImageVector by lazy { ImageVector.Builder( name = "BorderColor", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(160f, 960f) quadTo(127f, 960f, 103.5f, 936.5f) quadTo(80f, 913f, 80f, 880f) quadTo(80f, 847f, 103.5f, 823.5f) quadTo(127f, 800f, 160f, 800f) lineTo(800f, 800f) quadTo(833f, 800f, 856.5f, 823.5f) quadTo(880f, 847f, 880f, 880f) quadTo(880f, 913f, 856.5f, 936.5f) quadTo(833f, 960f, 800f, 960f) lineTo(160f, 960f) close() moveTo(240f, 640f) lineTo(296f, 640f) lineTo(608f, 329f) lineTo(579f, 300f) lineTo(551f, 272f) lineTo(240f, 584f) lineTo(240f, 640f) close() moveTo(160f, 680f) lineTo(160f, 567f) quadTo(160f, 559f, 163f, 551.5f) quadTo(166f, 544f, 172f, 538f) lineTo(608f, 103f) quadTo(619f, 92f, 633.5f, 86f) quadTo(648f, 80f, 664f, 80f) quadTo(680f, 80f, 695f, 86f) quadTo(710f, 92f, 722f, 104f) lineTo(777f, 160f) quadTo(789f, 171f, 794.5f, 186f) quadTo(800f, 201f, 800f, 217f) quadTo(800f, 232f, 794.5f, 246.5f) quadTo(789f, 261f, 777f, 273f) lineTo(342f, 708f) quadTo(336f, 714f, 328.5f, 717f) quadTo(321f, 720f, 313f, 720f) lineTo(200f, 720f) quadTo(183f, 720f, 171.5f, 708.5f) quadTo(160f, 697f, 160f, 680f) close() moveTo(720f, 216f) lineTo(720f, 216f) lineTo(664f, 160f) lineTo(664f, 160f) lineTo(720f, 216f) close() moveTo(608f, 329f) lineTo(579f, 300f) lineTo(551f, 272f) lineTo(551f, 272f) lineTo(608f, 329f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrokenImageAlt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.BrokenImageAlt: ImageVector by lazy { ImageVector.Builder( name = "BrokenImageAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(19f, 3f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 21f, 5f) verticalLineTo(11f) horizontalLineTo(19f) verticalLineTo(13f) horizontalLineTo(19f) lineTo(17f, 13f) verticalLineTo(15f) horizontalLineTo(15f) verticalLineTo(17f) horizontalLineTo(13f) verticalLineTo(19f) horizontalLineTo(11f) verticalLineTo(21f) horizontalLineTo(5f) curveTo(3.89f, 21f, 3f, 20.1f, 3f, 19f) verticalLineTo(5f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5f, 3f) horizontalLineTo(19f) moveTo(21f, 15f) verticalLineTo(19f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 19f, 21f) horizontalLineTo(19f) lineTo(15f, 21f) verticalLineTo(19f) horizontalLineTo(17f) verticalLineTo(17f) horizontalLineTo(19f) verticalLineTo(15f) horizontalLineTo(21f) moveTo(19f, 8.5f) arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = false, 18.5f, 8f) horizontalLineTo(5.5f) arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = false, 5f, 8.5f) verticalLineTo(15.5f) arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = false, 5.5f, 16f) horizontalLineTo(11f) verticalLineTo(15f) horizontalLineTo(13f) verticalLineTo(13f) horizontalLineTo(15f) verticalLineTo(11f) horizontalLineTo(17f) verticalLineTo(9f) horizontalLineTo(19f) verticalLineTo(8.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BrushColor.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.BrushColor: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.BrushColor", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(6.332f, 17.919f) curveToRelative(-0.625f, 0f, -1.243f, -0.153f, -1.853f, -0.458f) curveToRelative(-0.611f, -0.305f, -1.104f, -0.708f, -1.479f, -1.208f) curveToRelative(0.361f, 0f, 0.729f, -0.142f, 1.104f, -0.427f) reflectiveCurveToRelative(0.562f, -0.698f, 0.562f, -1.239f) curveToRelative(0f, -0.694f, 0.243f, -1.284f, 0.729f, -1.77f) reflectiveCurveToRelative(1.076f, -0.729f, 1.77f, -0.729f) reflectiveCurveToRelative(1.284f, 0.243f, 1.77f, 0.729f) reflectiveCurveToRelative(0.729f, 1.076f, 0.729f, 1.77f) curveToRelative(0f, 0.916f, -0.326f, 1.701f, -0.979f, 2.353f) reflectiveCurveToRelative(-1.437f, 0.979f, -2.353f, 0.979f) close() moveTo(6.332f, 16.253f) curveToRelative(0.458f, 0f, 0.85f, -0.163f, 1.177f, -0.489f) reflectiveCurveToRelative(0.489f, -0.718f, 0.489f, -1.177f) curveToRelative(0f, -0.236f, -0.08f, -0.434f, -0.239f, -0.594f) reflectiveCurveToRelative(-0.358f, -0.239f, -0.594f, -0.239f) reflectiveCurveToRelative(-0.434f, 0.08f, -0.594f, 0.239f) curveToRelative(-0.16f, 0.16f, -0.239f, 0.358f, -0.239f, 0.594f) curveToRelative(0f, 0.319f, -0.038f, 0.611f, -0.115f, 0.875f) reflectiveCurveToRelative(-0.177f, 0.514f, -0.302f, 0.75f) curveToRelative(0.069f, 0.028f, 0.139f, 0.042f, 0.208f, 0.042f) horizontalLineToRelative(0.208f) close() moveTo(11.122f, 12.92f) lineToRelative(-2.291f, -2.291f) lineToRelative(7.456f, -7.456f) curveToRelative(0.153f, -0.153f, 0.344f, -0.233f, 0.573f, -0.239f) reflectiveCurveToRelative(0.427f, 0.073f, 0.594f, 0.239f) lineToRelative(1.125f, 1.125f) curveToRelative(0.167f, 0.167f, 0.25f, 0.361f, 0.25f, 0.583f) reflectiveCurveToRelative(-0.083f, 0.417f, -0.25f, 0.583f) lineToRelative(-7.456f, 7.456f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.656f, 21.066f) curveToRelative(-0.594f, 0f, -1.155f, -0.114f, -1.683f, -0.342f) reflectiveCurveToRelative(-0.99f, -0.539f, -1.385f, -0.934f) reflectiveCurveToRelative(-0.706f, -0.856f, -0.934f, -1.385f) reflectiveCurveToRelative(-0.342f, -1.09f, -0.342f, -1.683f) curveToRelative(0f, -0.601f, 0.118f, -1.166f, 0.353f, -1.694f) curveToRelative(0.235f, -0.529f, 0.554f, -0.988f, 0.956f, -1.379f) reflectiveCurveToRelative(0.871f, -0.701f, 1.406f, -0.929f) reflectiveCurveToRelative(1.108f, -0.342f, 1.716f, -0.342f) curveToRelative(0.579f, 0f, 1.126f, 0.1f, 1.64f, 0.299f) reflectiveCurveToRelative(0.965f, 0.474f, 1.352f, 0.825f) reflectiveCurveToRelative(0.695f, 0.767f, 0.923f, 1.249f) reflectiveCurveToRelative(0.342f, 1.001f, 0.342f, 1.558f) curveToRelative(0f, 0.833f, -0.253f, 1.472f, -0.76f, 1.917f) reflectiveCurveToRelative(-1.122f, 0.668f, -1.846f, 0.668f) horizontalLineToRelative(-0.804f) curveToRelative(-0.065f, 0f, -0.11f, 0.018f, -0.136f, 0.054f) reflectiveCurveToRelative(-0.038f, 0.076f, -0.038f, 0.119f) curveToRelative(0f, 0.087f, 0.054f, 0.212f, 0.163f, 0.375f) reflectiveCurveToRelative(0.163f, 0.349f, 0.163f, 0.559f) curveToRelative(0f, 0.362f, -0.1f, 0.63f, -0.299f, 0.804f) reflectiveCurveToRelative(-0.462f, 0.261f, -0.787f, 0.261f) close() moveTo(14.266f, 17.156f) curveToRelative(0.188f, 0f, 0.344f, -0.062f, 0.467f, -0.185f) reflectiveCurveToRelative(0.185f, -0.279f, 0.185f, -0.467f) curveToRelative(0f, -0.188f, -0.062f, -0.344f, -0.185f, -0.467f) reflectiveCurveToRelative(-0.279f, -0.185f, -0.467f, -0.185f) curveToRelative(-0.188f, 0f, -0.344f, 0.062f, -0.467f, 0.185f) reflectiveCurveToRelative(-0.185f, 0.279f, -0.185f, 0.467f) curveToRelative(0f, 0.188f, 0.062f, 0.344f, 0.185f, 0.467f) reflectiveCurveToRelative(0.279f, 0.185f, 0.467f, 0.185f) close() moveTo(15.569f, 15.418f) curveToRelative(0.188f, 0f, 0.344f, -0.062f, 0.467f, -0.185f) reflectiveCurveToRelative(0.185f, -0.279f, 0.185f, -0.467f) reflectiveCurveToRelative(-0.062f, -0.344f, -0.185f, -0.467f) reflectiveCurveToRelative(-0.279f, -0.185f, -0.467f, -0.185f) reflectiveCurveToRelative(-0.344f, 0.062f, -0.467f, 0.185f) reflectiveCurveToRelative(-0.185f, 0.279f, -0.185f, 0.467f) reflectiveCurveToRelative(0.062f, 0.344f, 0.185f, 0.467f) reflectiveCurveToRelative(0.279f, 0.185f, 0.467f, 0.185f) close() moveTo(17.742f, 15.418f) curveToRelative(0.188f, 0f, 0.344f, -0.062f, 0.467f, -0.185f) reflectiveCurveToRelative(0.185f, -0.279f, 0.185f, -0.467f) reflectiveCurveToRelative(-0.062f, -0.344f, -0.185f, -0.467f) reflectiveCurveToRelative(-0.279f, -0.185f, -0.467f, -0.185f) reflectiveCurveToRelative(-0.344f, 0.062f, -0.467f, 0.185f) reflectiveCurveToRelative(-0.185f, 0.279f, -0.185f, 0.467f) reflectiveCurveToRelative(0.062f, 0.344f, 0.185f, 0.467f) reflectiveCurveToRelative(0.279f, 0.185f, 0.467f, 0.185f) close() moveTo(19.045f, 17.156f) curveToRelative(0.188f, 0f, 0.344f, -0.062f, 0.467f, -0.185f) reflectiveCurveToRelative(0.185f, -0.279f, 0.185f, -0.467f) curveToRelative(0f, -0.188f, -0.062f, -0.344f, -0.185f, -0.467f) reflectiveCurveToRelative(-0.279f, -0.185f, -0.467f, -0.185f) reflectiveCurveToRelative(-0.344f, 0.062f, -0.467f, 0.185f) reflectiveCurveToRelative(-0.185f, 0.279f, -0.185f, 0.467f) curveToRelative(0f, 0.188f, 0.062f, 0.344f, 0.185f, 0.467f) reflectiveCurveToRelative(0.279f, 0.185f, 0.467f, 0.185f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/BubbleDelete.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.BubbleDelete: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.BubbleDelete", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 3.285f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(15.575f) curveToRelative(0f, 0.45f, 0.204f, 0.763f, 0.612f, 0.938f) reflectiveCurveToRelative(0.771f, 0.104f, 1.088f, -0.213f) lineToRelative(2.3f, -2.3f) horizontalLineToRelative(14f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(4.697f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(20f, 16.697f) horizontalLineTo(5.15f) lineToRelative(-1.15f, 1.125f) verticalLineTo(4.697f) horizontalLineToRelative(16f) verticalLineToRelative(12f) close() } path(fill = SolidColor(Color.Black)) { moveTo(15.536f, 7.272f) horizontalLineToRelative(-2.341f) verticalLineToRelative(-0.687f) curveToRelative(0f, -0.276f, -0.224f, -0.5f, -0.5f, -0.5f) horizontalLineToRelative(-1.135f) curveToRelative(-0.276f, 0f, -0.5f, 0.224f, -0.5f, 0.5f) verticalLineToRelative(0.687f) horizontalLineToRelative(-2.153f) curveToRelative(-0.539f, 0f, -0.977f, 0.437f, -0.977f, 0.977f) curveToRelative(0f, 0.539f, 0.437f, 0.977f, 0.977f, 0.977f) horizontalLineToRelative(0.291f) verticalLineToRelative(3.906f) curveToRelative(0f, 1.079f, 0.874f, 1.953f, 1.953f, 1.953f) horizontalLineToRelative(1.93f) curveToRelative(0.073f, 0f, 0.137f, -0.027f, 0.205f, -0.041f) verticalLineToRelative(0.041f) curveToRelative(1.079f, 0f, 1.953f, -0.874f, 1.953f, -1.953f) verticalLineToRelative(-3.906f) horizontalLineToRelative(0.297f) curveToRelative(0.539f, 0f, 0.977f, -0.437f, 0.977f, -0.977f) curveToRelative(0f, -0.539f, -0.437f, -0.977f, -0.977f, -0.977f) close() moveTo(13.286f, 13.131f) horizontalLineToRelative(-2.135f) verticalLineToRelative(-3.906f) horizontalLineToRelative(2.135f) verticalLineToRelative(3.906f) close() } }.build() } val Icons.TwoTone.BubbleDelete: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.BubbleDelete", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 3.285f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(15.575f) curveToRelative(0f, 0.45f, 0.204f, 0.763f, 0.612f, 0.938f) reflectiveCurveToRelative(0.771f, 0.104f, 1.088f, -0.213f) lineToRelative(2.3f, -2.3f) horizontalLineToRelative(14f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(4.697f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(20f, 16.697f) horizontalLineTo(5.15f) lineToRelative(-1.15f, 1.125f) verticalLineTo(4.697f) horizontalLineToRelative(16f) verticalLineToRelative(12f) close() } path(fill = SolidColor(Color.Black)) { moveTo(15.536f, 7.272f) horizontalLineToRelative(-2.341f) verticalLineToRelative(-0.687f) curveToRelative(0f, -0.276f, -0.224f, -0.5f, -0.5f, -0.5f) horizontalLineToRelative(-1.135f) curveToRelative(-0.276f, 0f, -0.5f, 0.224f, -0.5f, 0.5f) verticalLineToRelative(0.687f) horizontalLineToRelative(-2.153f) curveToRelative(-0.539f, 0f, -0.977f, 0.437f, -0.977f, 0.977f) curveToRelative(0f, 0.539f, 0.437f, 0.977f, 0.977f, 0.977f) horizontalLineToRelative(0.291f) verticalLineToRelative(3.906f) curveToRelative(0f, 1.079f, 0.874f, 1.953f, 1.953f, 1.953f) horizontalLineToRelative(1.93f) curveToRelative(0.073f, 0f, 0.137f, -0.027f, 0.205f, -0.041f) verticalLineToRelative(0.041f) curveToRelative(1.079f, 0f, 1.953f, -0.874f, 1.953f, -1.953f) verticalLineToRelative(-3.906f) horizontalLineToRelative(0.297f) curveToRelative(0.539f, 0f, 0.977f, -0.437f, 0.977f, -0.977f) curveToRelative(0f, -0.539f, -0.437f, -0.977f, -0.977f, -0.977f) close() moveTo(13.286f, 13.131f) horizontalLineToRelative(-2.135f) verticalLineToRelative(-3.906f) horizontalLineToRelative(2.135f) verticalLineToRelative(3.906f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(4f, 4.697f) horizontalLineToRelative(16f) verticalLineToRelative(13.125f) horizontalLineToRelative(-16f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Build.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Build: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Build", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(9f, 15f) curveToRelative(-1.667f, 0f, -3.083f, -0.583f, -4.25f, -1.75f) reflectiveCurveToRelative(-1.75f, -2.583f, -1.75f, -4.25f) curveToRelative(0f, -0.333f, 0.025f, -0.667f, 0.075f, -1f) reflectiveCurveToRelative(0.142f, -0.65f, 0.275f, -0.95f) curveToRelative(0.083f, -0.167f, 0.188f, -0.292f, 0.313f, -0.375f) reflectiveCurveToRelative(0.262f, -0.142f, 0.412f, -0.175f) reflectiveCurveToRelative(0.304f, -0.029f, 0.463f, 0.013f) reflectiveCurveToRelative(0.304f, 0.129f, 0.438f, 0.262f) lineToRelative(2.625f, 2.625f) lineToRelative(1.8f, -1.8f) lineToRelative(-2.625f, -2.625f) curveToRelative(-0.133f, -0.133f, -0.221f, -0.279f, -0.262f, -0.438f) reflectiveCurveToRelative(-0.046f, -0.313f, -0.013f, -0.463f) reflectiveCurveToRelative(0.092f, -0.287f, 0.175f, -0.412f) reflectiveCurveToRelative(0.208f, -0.229f, 0.375f, -0.313f) curveToRelative(0.3f, -0.133f, 0.617f, -0.225f, 0.95f, -0.275f) reflectiveCurveToRelative(0.667f, -0.075f, 1f, -0.075f) curveToRelative(1.667f, 0f, 3.083f, 0.583f, 4.25f, 1.75f) curveToRelative(1.167f, 1.167f, 1.75f, 2.583f, 1.75f, 4.25f) curveToRelative(0f, 0.383f, -0.033f, 0.746f, -0.1f, 1.087f) reflectiveCurveToRelative(-0.167f, 0.679f, -0.3f, 1.013f) lineToRelative(5.05f, 5f) curveToRelative(0.483f, 0.483f, 0.725f, 1.075f, 0.725f, 1.775f) reflectiveCurveToRelative(-0.242f, 1.292f, -0.725f, 1.775f) reflectiveCurveToRelative(-1.075f, 0.725f, -1.775f, 0.725f) reflectiveCurveToRelative(-1.292f, -0.25f, -1.775f, -0.75f) lineToRelative(-5f, -5.025f) curveToRelative(-0.333f, 0.133f, -0.671f, 0.233f, -1.013f, 0.3f) reflectiveCurveToRelative(-0.704f, 0.1f, -1.087f, 0.1f) close() moveTo(9f, 13f) curveToRelative(0.433f, 0f, 0.867f, -0.067f, 1.3f, -0.2f) reflectiveCurveToRelative(0.825f, -0.342f, 1.175f, -0.625f) lineToRelative(6.075f, 6.075f) curveToRelative(0.083f, 0.083f, 0.196f, 0.121f, 0.338f, 0.112f) reflectiveCurveToRelative(0.254f, -0.054f, 0.338f, -0.138f) reflectiveCurveToRelative(0.125f, -0.196f, 0.125f, -0.338f) reflectiveCurveToRelative(-0.042f, -0.254f, -0.125f, -0.338f) lineToRelative(-6.075f, -6.05f) curveToRelative(0.3f, -0.333f, 0.517f, -0.721f, 0.65f, -1.163f) curveToRelative(0.133f, -0.442f, 0.2f, -0.887f, 0.2f, -1.337f) curveToRelative(0f, -1f, -0.321f, -1.871f, -0.962f, -2.612f) reflectiveCurveToRelative(-1.438f, -1.188f, -2.388f, -1.337f) lineToRelative(1.85f, 1.85f) curveToRelative(0.2f, 0.2f, 0.3f, 0.433f, 0.3f, 0.7f) reflectiveCurveToRelative(-0.1f, 0.5f, -0.3f, 0.7f) lineToRelative(-3.2f, 3.2f) curveToRelative(-0.2f, 0.2f, -0.433f, 0.3f, -0.7f, 0.3f) reflectiveCurveToRelative(-0.5f, -0.1f, -0.7f, -0.3f) lineToRelative(-1.85f, -1.85f) curveToRelative(0.15f, 0.95f, 0.596f, 1.746f, 1.337f, 2.388f) curveToRelative(0.742f, 0.642f, 1.612f, 0.962f, 2.612f, 0.962f) close() } }.build() } val Icons.TwoTone.Build: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoToneBuild", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19.65f, 16.1f) lineToRelative(-5.05f, -5f) curveToRelative(0.133f, -0.333f, 0.233f, -0.671f, 0.3f, -1.012f) curveToRelative(0.067f, -0.342f, 0.1f, -0.704f, 0.1f, -1.088f) curveToRelative(0f, -1.667f, -0.583f, -3.083f, -1.75f, -4.25f) reflectiveCurveToRelative(-2.583f, -1.75f, -4.25f, -1.75f) curveToRelative(-0.333f, 0f, -0.667f, 0.025f, -1f, 0.075f) reflectiveCurveToRelative(-0.65f, 0.142f, -0.95f, 0.275f) curveToRelative(-0.167f, 0.083f, -0.292f, 0.188f, -0.375f, 0.313f) curveToRelative(-0.083f, 0.125f, -0.142f, 0.263f, -0.175f, 0.413f) curveToRelative(-0.033f, 0.15f, -0.029f, 0.304f, 0.013f, 0.462f) curveToRelative(0.042f, 0.158f, 0.129f, 0.304f, 0.263f, 0.438f) lineToRelative(2.625f, 2.625f) lineToRelative(-1.8f, 1.8f) lineToRelative(-2.625f, -2.625f) curveToRelative(-0.133f, -0.133f, -0.279f, -0.221f, -0.438f, -0.263f) curveToRelative(-0.158f, -0.042f, -0.313f, -0.046f, -0.462f, -0.013f) curveToRelative(-0.15f, 0.033f, -0.288f, 0.092f, -0.413f, 0.175f) curveToRelative(-0.125f, 0.083f, -0.229f, 0.208f, -0.313f, 0.375f) curveToRelative(-0.133f, 0.3f, -0.225f, 0.617f, -0.275f, 0.95f) reflectiveCurveToRelative(-0.075f, 0.667f, -0.075f, 1f) curveToRelative(0f, 1.667f, 0.583f, 3.083f, 1.75f, 4.25f) reflectiveCurveToRelative(2.583f, 1.75f, 4.25f, 1.75f) curveToRelative(0.383f, 0f, 0.746f, -0.033f, 1.088f, -0.1f) curveToRelative(0.342f, -0.067f, 0.679f, -0.167f, 1.012f, -0.3f) lineToRelative(5f, 5.025f) curveToRelative(0.483f, 0.5f, 1.075f, 0.75f, 1.775f, 0.75f) reflectiveCurveToRelative(1.292f, -0.242f, 1.775f, -0.725f) reflectiveCurveToRelative(0.725f, -1.075f, 0.725f, -1.775f) reflectiveCurveToRelative(-0.242f, -1.292f, -0.725f, -1.775f) close() moveTo(18.225f, 18.225f) curveToRelative(-0.083f, 0.083f, -0.196f, 0.129f, -0.337f, 0.138f) curveToRelative(-0.142f, 0.008f, -0.254f, -0.029f, -0.338f, -0.112f) lineToRelative(-6.075f, -6.075f) curveToRelative(-0.35f, 0.283f, -0.742f, 0.492f, -1.175f, 0.625f) curveToRelative(-0.433f, 0.133f, -0.867f, 0.2f, -1.3f, 0.2f) curveToRelative(-1f, 0f, -1.871f, -0.321f, -2.612f, -0.963f) curveToRelative(-0.742f, -0.642f, -1.188f, -1.438f, -1.338f, -2.387f) lineToRelative(1.85f, 1.85f) curveToRelative(0.2f, 0.2f, 0.433f, 0.3f, 0.7f, 0.3f) reflectiveCurveToRelative(0.5f, -0.1f, 0.7f, -0.3f) lineToRelative(3.2f, -3.2f) curveToRelative(0.2f, -0.2f, 0.3f, -0.433f, 0.3f, -0.7f) reflectiveCurveToRelative(-0.1f, -0.5f, -0.3f, -0.7f) lineToRelative(-1.85f, -1.85f) curveToRelative(0.95f, 0.15f, 1.746f, 0.596f, 2.387f, 1.338f) curveToRelative(0.642f, 0.742f, 0.963f, 1.612f, 0.963f, 2.612f) curveToRelative(0f, 0.45f, -0.067f, 0.896f, -0.2f, 1.338f) curveToRelative(-0.133f, 0.442f, -0.35f, 0.829f, -0.65f, 1.162f) lineToRelative(6.075f, 6.05f) curveToRelative(0.083f, 0.083f, 0.125f, 0.196f, 0.125f, 0.338f) reflectiveCurveToRelative(-0.042f, 0.254f, -0.125f, 0.337f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(9f, 13f) curveToRelative(0.433f, 0f, 0.867f, -0.067f, 1.3f, -0.2f) reflectiveCurveToRelative(0.825f, -0.342f, 1.175f, -0.625f) lineToRelative(6.075f, 6.075f) curveToRelative(0.083f, 0.083f, 0.196f, 0.121f, 0.338f, 0.112f) reflectiveCurveToRelative(0.254f, -0.054f, 0.338f, -0.138f) reflectiveCurveToRelative(0.125f, -0.196f, 0.125f, -0.338f) reflectiveCurveToRelative(-0.042f, -0.254f, -0.125f, -0.338f) lineToRelative(-6.075f, -6.05f) curveToRelative(0.3f, -0.333f, 0.517f, -0.721f, 0.65f, -1.163f) curveToRelative(0.133f, -0.442f, 0.2f, -0.887f, 0.2f, -1.337f) curveToRelative(0f, -1f, -0.321f, -1.871f, -0.962f, -2.612f) reflectiveCurveToRelative(-1.438f, -1.188f, -2.388f, -1.337f) lineToRelative(1.85f, 1.85f) curveToRelative(0.2f, 0.2f, 0.3f, 0.433f, 0.3f, 0.7f) reflectiveCurveToRelative(-0.1f, 0.5f, -0.3f, 0.7f) lineToRelative(-3.2f, 3.2f) curveToRelative(-0.2f, 0.2f, -0.433f, 0.3f, -0.7f, 0.3f) reflectiveCurveToRelative(-0.5f, -0.1f, -0.7f, -0.3f) lineToRelative(-1.85f, -1.85f) curveToRelative(0.15f, 0.95f, 0.596f, 1.746f, 1.337f, 2.388f) curveToRelative(0.742f, 0.642f, 1.612f, 0.962f, 2.612f, 0.962f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CancelSmall.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.CancelSmall: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.CancelSmall", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveToRelative(480f, 536f) lineToRelative(116f, 116f) quadToRelative(11f, 11f, 28f, 11f) reflectiveQuadToRelative(28f, -11f) quadToRelative(11f, -11f, 11f, -28f) reflectiveQuadToRelative(-11f, -28f) lineTo(536f, 480f) lineToRelative(116f, -116f) quadToRelative(11f, -11f, 11f, -28f) reflectiveQuadToRelative(-11f, -28f) quadToRelative(-11f, -11f, -28f, -11f) reflectiveQuadToRelative(-28f, 11f) lineTo(480f, 424f) lineTo(364f, 308f) quadToRelative(-11f, -11f, -28f, -11f) reflectiveQuadToRelative(-28f, 11f) quadToRelative(-11f, 11f, -11f, 28f) reflectiveQuadToRelative(11f, 28f) lineToRelative(116f, 116f) lineToRelative(-116f, 116f) quadToRelative(-11f, 11f, -11f, 28f) reflectiveQuadToRelative(11f, 28f) quadToRelative(11f, 11f, 28f, 11f) reflectiveQuadToRelative(28f, -11f) lineToRelative(116f, -116f) close() moveTo(480f, 480f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ClipboardFile.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ClipboardFile: ImageVector by lazy { ImageVector.Builder( name = "ClipboardFile", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(19f, 3f) curveTo(20.1f, 3f, 21f, 3.9f, 21f, 5f) verticalLineTo(9.17f) lineTo(19.83f, 8f) horizontalLineTo(15f) curveTo(12.79f, 8f, 11f, 9.79f, 11f, 12f) verticalLineTo(21f) horizontalLineTo(5f) curveTo(3.9f, 21f, 3f, 20.1f, 3f, 19f) verticalLineTo(5f) curveTo(3f, 3.9f, 3.9f, 3f, 5f, 3f) horizontalLineTo(9.18f) curveTo(9.6f, 1.84f, 10.7f, 1f, 12f, 1f) curveTo(13.3f, 1f, 14.4f, 1.84f, 14.82f, 3f) horizontalLineTo(19f) moveTo(12f, 3f) curveTo(11.45f, 3f, 11f, 3.45f, 11f, 4f) curveTo(11f, 4.55f, 11.45f, 5f, 12f, 5f) curveTo(12.55f, 5f, 13f, 4.55f, 13f, 4f) curveTo(13f, 3.45f, 12.55f, 3f, 12f, 3f) moveTo(15f, 23f) curveTo(13.9f, 23f, 13f, 22.11f, 13f, 21f) verticalLineTo(12f) curveTo(13f, 10.9f, 13.9f, 10f, 15f, 10f) horizontalLineTo(19f) lineTo(23f, 14f) verticalLineTo(21f) curveTo(23f, 22.11f, 22.11f, 23f, 21f, 23f) horizontalLineTo(15f) moveTo(21f, 14.83f) lineTo(18.17f, 12f) horizontalLineTo(18f) verticalLineTo(15f) horizontalLineTo(21f) verticalLineTo(14.83f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Collage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Collage: ImageVector by lazy { ImageVector.Builder( name = "Outlined.Collage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(120f, 520f) lineTo(120f, 200f) quadTo(120f, 167f, 143.5f, 143.5f) quadTo(167f, 120f, 200f, 120f) lineTo(440f, 120f) lineTo(440f, 520f) lineTo(120f, 520f) close() moveTo(360f, 440f) lineTo(360f, 440f) lineTo(360f, 440f) lineTo(360f, 440f) lineTo(360f, 440f) quadTo(360f, 440f, 360f, 440f) quadTo(360f, 440f, 360f, 440f) close() moveTo(520f, 120f) lineTo(760f, 120f) quadTo(793f, 120f, 816.5f, 143.5f) quadTo(840f, 167f, 840f, 200f) lineTo(840f, 360f) lineTo(520f, 360f) lineTo(520f, 120f) close() moveTo(520f, 840f) lineTo(520f, 440f) lineTo(840f, 440f) lineTo(840f, 760f) quadTo(840f, 793f, 816.5f, 816.5f) quadTo(793f, 840f, 760f, 840f) lineTo(520f, 840f) close() moveTo(120f, 600f) lineTo(440f, 600f) lineTo(440f, 840f) lineTo(200f, 840f) quadTo(167f, 840f, 143.5f, 816.5f) quadTo(120f, 793f, 120f, 760f) lineTo(120f, 600f) close() moveTo(360f, 680f) lineTo(360f, 680f) lineTo(360f, 680f) lineTo(360f, 680f) lineTo(360f, 680f) quadTo(360f, 680f, 360f, 680f) quadTo(360f, 680f, 360f, 680f) close() moveTo(600f, 280f) lineTo(600f, 280f) lineTo(600f, 280f) lineTo(600f, 280f) lineTo(600f, 280f) quadTo(600f, 280f, 600f, 280f) quadTo(600f, 280f, 600f, 280f) close() moveTo(600f, 520f) quadTo(600f, 520f, 600f, 520f) quadTo(600f, 520f, 600f, 520f) lineTo(600f, 520f) lineTo(600f, 520f) lineTo(600f, 520f) close() moveTo(200f, 440f) lineTo(360f, 440f) lineTo(360f, 200f) lineTo(200f, 200f) quadTo(200f, 200f, 200f, 200f) quadTo(200f, 200f, 200f, 200f) lineTo(200f, 440f) close() moveTo(600f, 280f) lineTo(760f, 280f) lineTo(760f, 200f) quadTo(760f, 200f, 760f, 200f) quadTo(760f, 200f, 760f, 200f) lineTo(600f, 200f) lineTo(600f, 280f) close() moveTo(600f, 520f) lineTo(600f, 760f) lineTo(760f, 760f) quadTo(760f, 760f, 760f, 760f) quadTo(760f, 760f, 760f, 760f) lineTo(760f, 520f) lineTo(600f, 520f) close() moveTo(200f, 680f) lineTo(200f, 760f) quadTo(200f, 760f, 200f, 760f) quadTo(200f, 760f, 200f, 760f) lineTo(360f, 760f) lineTo(360f, 680f) lineTo(200f, 680f) close() } }.build() } val Icons.TwoTone.Collage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Collage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(9f, 11f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(9f, 17f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15f, 7f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15f, 13f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 11f) lineToRelative(4f, 0f) lineToRelative(0f, -6f) lineToRelative(-4f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 6f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(15f, 7f) lineToRelative(4f, 0f) lineToRelative(0f, -2f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(-4f, 0f) lineToRelative(0f, 2f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(15f, 13f) lineToRelative(0f, 6f) lineToRelative(4f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, -6f) lineToRelative(-4f, 0f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 17f) lineToRelative(0f, 2f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(4f, 0f) lineToRelative(0f, -2f) lineToRelative(-4f, 0f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(5f, 3f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.587f) reflectiveCurveToRelative(-0.588f, 0.863f, -0.588f, 1.413f) verticalLineToRelative(8f) horizontalLineToRelative(8f) verticalLineTo(3f) horizontalLineToRelative(-6f) close() moveTo(9f, 11f) horizontalLineToRelative(-4f) verticalLineToRelative(-6f) horizontalLineToRelative(4f) verticalLineToRelative(6f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(20.412f, 3.587f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.587f, -1.412f, -0.587f) horizontalLineToRelative(-6f) verticalLineToRelative(6f) horizontalLineToRelative(8f) verticalLineToRelative(-4f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.413f) close() moveTo(19f, 7f) horizontalLineToRelative(-4f) verticalLineToRelative(-2f) horizontalLineToRelative(4f) verticalLineToRelative(2f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(13f, 11f) verticalLineToRelative(10f) horizontalLineToRelative(6f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineToRelative(-8f) horizontalLineToRelative(-8f) close() moveTo(19f, 19f) horizontalLineToRelative(-4f) verticalLineToRelative(-6f) horizontalLineToRelative(4f) verticalLineToRelative(6f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(3f, 15f) verticalLineToRelative(4f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(6f) verticalLineToRelative(-6f) horizontalLineTo(3f) close() moveTo(9f, 19f) horizontalLineToRelative(-4f) verticalLineToRelative(-2f) horizontalLineToRelative(4f) verticalLineToRelative(2f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Communication.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Communication: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Communication", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(408f, 528f) lineTo(366f, 486f) quadTo(374f, 475f, 377f, 463.5f) quadTo(380f, 452f, 380f, 440f) quadTo(380f, 428f, 377f, 416.5f) quadTo(374f, 405f, 366f, 395f) lineTo(408f, 352f) quadTo(424f, 371f, 432f, 394f) quadTo(440f, 417f, 440f, 440f) quadTo(440f, 463f, 432f, 485.5f) quadTo(424f, 508f, 408f, 528f) close() moveTo(493f, 614f) lineTo(450f, 571f) quadTo(475f, 543f, 487.5f, 509f) quadTo(500f, 475f, 500f, 440f) quadTo(500f, 405f, 487.5f, 371.5f) quadTo(475f, 338f, 450f, 310f) lineTo(493f, 267f) quadTo(527f, 304f, 543.5f, 348.5f) quadTo(560f, 393f, 560f, 440f) quadTo(560f, 487f, 543.5f, 532f) quadTo(527f, 577f, 493f, 614f) close() moveTo(200f, 480f) quadTo(167f, 480f, 143.5f, 456.5f) quadTo(120f, 433f, 120f, 400f) quadTo(120f, 367f, 143.5f, 343.5f) quadTo(167f, 320f, 200f, 320f) quadTo(233f, 320f, 256.5f, 343.5f) quadTo(280f, 367f, 280f, 400f) quadTo(280f, 433f, 256.5f, 456.5f) quadTo(233f, 480f, 200f, 480f) close() moveTo(40f, 640f) lineTo(40f, 617f) quadTo(40f, 593f, 53f, 573f) quadTo(66f, 553f, 89f, 543f) quadTo(115f, 532f, 142.5f, 526f) quadTo(170f, 520f, 200f, 520f) quadTo(230f, 520f, 257.5f, 526f) quadTo(285f, 532f, 311f, 543f) quadTo(334f, 553f, 347f, 573f) quadTo(360f, 593f, 360f, 617f) lineTo(360f, 640f) lineTo(40f, 640f) close() moveTo(760f, 480f) quadTo(727f, 480f, 703.5f, 456.5f) quadTo(680f, 433f, 680f, 400f) quadTo(680f, 367f, 703.5f, 343.5f) quadTo(727f, 320f, 760f, 320f) quadTo(793f, 320f, 816.5f, 343.5f) quadTo(840f, 367f, 840f, 400f) quadTo(840f, 433f, 816.5f, 456.5f) quadTo(793f, 480f, 760f, 480f) close() moveTo(600f, 640f) lineTo(600f, 617f) quadTo(600f, 593f, 613f, 573f) quadTo(626f, 553f, 649f, 543f) quadTo(675f, 532f, 702.5f, 526f) quadTo(730f, 520f, 760f, 520f) quadTo(790f, 520f, 817.5f, 526f) quadTo(845f, 532f, 871f, 543f) quadTo(894f, 553f, 907f, 573f) quadTo(920f, 593f, 920f, 617f) lineTo(920f, 640f) lineTo(600f, 640f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Compare.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Compare: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Compare", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.713f, 1.287f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.713f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.713f) verticalLineToRelative(1f) horizontalLineToRelative(-5f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(5f) verticalLineToRelative(1f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.713f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) verticalLineTo(2f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) close() moveTo(10f, 18f) horizontalLineToRelative(-5f) lineToRelative(5f, -6f) verticalLineToRelative(6f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14f, 21f) verticalLineToRelative(-9f) lineToRelative(5f, 6f) verticalLineTo(5f) horizontalLineToRelative(-5f) verticalLineToRelative(-2f) horizontalLineToRelative(5f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-5f) close() } }.build() } val Icons.TwoTone.Compare: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Compare", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.713f, 1.287f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.713f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.713f) verticalLineToRelative(1f) horizontalLineToRelative(-5f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(5f) verticalLineToRelative(1f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.713f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) verticalLineTo(2f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) close() moveTo(10f, 18f) horizontalLineToRelative(-5f) lineToRelative(5f, -6f) verticalLineToRelative(6f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14f, 21f) verticalLineToRelative(-9f) lineToRelative(5f, 6f) verticalLineTo(5f) horizontalLineToRelative(-5f) verticalLineToRelative(-2f) horizontalLineToRelative(5f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-5f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 18f) lineToRelative(5f, 0f) lineToRelative(0f, -6f) lineToRelative(-5f, 6f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(14f, 5f) horizontalLineToRelative(5f) verticalLineToRelative(13f) horizontalLineToRelative(-5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CompareArrows.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.CompareArrows: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.CompareArrows", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(367f, 640f) lineTo(120f, 640f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(80f, 600f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(120f, 560f) horizontalLineToRelative(247f) lineToRelative(-75f, -75f) quadToRelative(-11f, -11f, -11f, -27.5f) reflectiveQuadToRelative(11f, -28.5f) quadToRelative(12f, -12f, 28.5f, -12f) reflectiveQuadToRelative(28.5f, 12f) lineToRelative(143f, 143f) quadToRelative(6f, 6f, 8.5f, 13f) reflectiveQuadToRelative(2.5f, 15f) quadToRelative(0f, 8f, -2.5f, 15f) reflectiveQuadToRelative(-8.5f, 13f) lineTo(348f, 772f) quadToRelative(-12f, 12f, -28f, 11.5f) reflectiveQuadTo(292f, 771f) quadToRelative(-11f, -12f, -11.5f, -28f) reflectiveQuadToRelative(11.5f, -28f) lineToRelative(75f, -75f) close() moveTo(593f, 400f) lineTo(668f, 475f) quadToRelative(11f, 11f, 11f, 27.5f) reflectiveQuadTo(668f, 531f) quadToRelative(-12f, 12f, -28.5f, 12f) reflectiveQuadTo(611f, 531f) lineTo(468f, 388f) quadToRelative(-6f, -6f, -8.5f, -13f) reflectiveQuadToRelative(-2.5f, -15f) quadToRelative(0f, -8f, 2.5f, -15f) reflectiveQuadToRelative(8.5f, -13f) lineToRelative(144f, -144f) quadToRelative(12f, -12f, 28f, -11.5f) reflectiveQuadToRelative(28f, 12.5f) quadToRelative(11f, 12f, 11.5f, 28f) reflectiveQuadTo(668f, 245f) lineToRelative(-75f, 75f) horizontalLineToRelative(247f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(880f, 360f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(840f, 400f) lineTo(593f, 400f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Complementary.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Complementary: ImageVector by lazy { Builder( name = "Complementary", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(10.9f, 16.0f, 12.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(10.9f, 4.0f, 12.0f, 4.0f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractEdit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ContractEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ContractEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(400f, 280f) horizontalLineToRelative(280f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(720f, 320f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(680f, 360f) lineTo(400f, 360f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(360f, 320f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(400f, 280f) close() moveTo(400f, 400f) horizontalLineToRelative(280f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(720f, 440f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(680f, 480f) lineTo(400f, 480f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(360f, 440f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(400f, 400f) close() moveTo(480f, 800f) lineTo(200f, 800f) horizontalLineToRelative(280f) close() moveTo(240f, 880f) quadToRelative(-50f, 0f, -85f, -35f) reflectiveQuadToRelative(-35f, -85f) verticalLineToRelative(-80f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(160f, 640f) horizontalLineToRelative(80f) verticalLineToRelative(-480f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(320f, 80f) horizontalLineToRelative(440f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(840f, 160f) verticalLineToRelative(240f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(800f, 440f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(760f, 400f) verticalLineToRelative(-240f) lineTo(320f, 160f) verticalLineToRelative(480f) horizontalLineToRelative(120f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(480f, 680f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(440f, 720f) lineTo(200f, 720f) verticalLineToRelative(40f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(240f, 800f) horizontalLineToRelative(200f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(480f, 840f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(440f, 880f) lineTo(240f, 880f) close() moveTo(560f, 840f) verticalLineToRelative(-66f) quadToRelative(0f, -8f, 3f, -15.5f) reflectiveQuadToRelative(9f, -13.5f) lineToRelative(209f, -208f) quadToRelative(9f, -9f, 20f, -13f) reflectiveQuadToRelative(22f, -4f) quadToRelative(12f, 0f, 23f, 4.5f) reflectiveQuadToRelative(20f, 13.5f) lineToRelative(37f, 37f) quadToRelative(8f, 9f, 12.5f, 20f) reflectiveQuadToRelative(4.5f, 22f) quadToRelative(0f, 11f, -4f, 22.5f) reflectiveQuadTo(903f, 660f) lineTo(695f, 868f) quadToRelative(-6f, 6f, -13.5f, 9f) reflectiveQuadTo(666f, 880f) horizontalLineToRelative(-66f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(560f, 840f) close() moveTo(860f, 617f) lineTo(823f, 580f) lineTo(860f, 617f) close() moveTo(620f, 820f) horizontalLineToRelative(38f) lineToRelative(121f, -122f) lineToRelative(-18f, -19f) lineToRelative(-19f, -18f) lineToRelative(-122f, 121f) verticalLineToRelative(38f) close() moveTo(761f, 679f) lineTo(742f, 661f) lineTo(779f, 698f) lineTo(761f, 679f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ContractImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ContractImage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ContractImage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.412f, 2.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-11f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) horizontalLineToRelative(-1f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(1f) curveToRelative(0f, 0.833f, 0.292f, 1.542f, 0.875f, 2.125f) reflectiveCurveToRelative(1.292f, 0.875f, 2.125f, 0.875f) horizontalLineToRelative(12f) curveToRelative(0.833f, 0f, 1.542f, -0.292f, 2.125f, -0.875f) reflectiveCurveToRelative(0.875f, -1.292f, 0.875f, -2.125f) verticalLineTo(4f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(15f, 20f) horizontalLineTo(6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.713f) verticalLineToRelative(-1f) horizontalLineToRelative(10f) verticalLineToRelative(2f) close() moveTo(19f, 19f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.713f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.713f) verticalLineToRelative(-1f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-7f) verticalLineTo(4f) horizontalLineToRelative(11f) verticalLineToRelative(15f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6f, 20f) lineToRelative(-1f, 0f) lineToRelative(10f, 0f) lineToRelative(-9f, 0f) close() } path(fill = SolidColor(Color.Black)) { moveTo(9.42f, 11.981f) horizontalLineToRelative(8.161f) curveToRelative(0.163f, 0f, 0.286f, -0.078f, 0.367f, -0.233f) reflectiveCurveToRelative(0.068f, -0.304f, -0.041f, -0.445f) lineToRelative(-2.244f, -3.113f) curveToRelative(-0.082f, -0.113f, -0.19f, -0.169f, -0.326f, -0.169f) reflectiveCurveToRelative(-0.245f, 0.056f, -0.326f, 0.169f) lineToRelative(-2.122f, 2.944f) lineToRelative(-1.51f, -2.097f) curveToRelative(-0.082f, -0.113f, -0.19f, -0.169f, -0.326f, -0.169f) reflectiveCurveToRelative(-0.245f, 0.056f, -0.326f, 0.169f) lineToRelative(-1.632f, 2.266f) curveToRelative(-0.109f, 0.141f, -0.122f, 0.289f, -0.041f, 0.445f) reflectiveCurveToRelative(0.204f, 0.233f, 0.367f, 0.233f) close() } }.build() } val Icons.TwoTone.ContractImage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ContractImage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.412f, 2.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-11f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) horizontalLineToRelative(-1f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(1f) curveToRelative(0f, 0.833f, 0.292f, 1.542f, 0.875f, 2.125f) reflectiveCurveToRelative(1.292f, 0.875f, 2.125f, 0.875f) horizontalLineToRelative(12f) curveToRelative(0.833f, 0f, 1.542f, -0.292f, 2.125f, -0.875f) reflectiveCurveToRelative(0.875f, -1.292f, 0.875f, -2.125f) verticalLineTo(4f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(15f, 20f) horizontalLineTo(6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.713f) verticalLineToRelative(-1f) horizontalLineToRelative(10f) verticalLineToRelative(2f) close() moveTo(19f, 19f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.713f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.713f) verticalLineToRelative(-1f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-7f) verticalLineTo(4f) horizontalLineToRelative(11f) verticalLineToRelative(15f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6f, 20f) lineToRelative(-1f, 0f) lineToRelative(10f, 0f) lineToRelative(-9f, 0f) close() } path(fill = SolidColor(Color.Black)) { moveTo(9.42f, 11.981f) horizontalLineToRelative(8.161f) curveToRelative(0.163f, 0f, 0.286f, -0.078f, 0.367f, -0.233f) reflectiveCurveToRelative(0.068f, -0.304f, -0.041f, -0.445f) lineToRelative(-2.244f, -3.113f) curveToRelative(-0.082f, -0.113f, -0.19f, -0.169f, -0.326f, -0.169f) reflectiveCurveToRelative(-0.245f, 0.056f, -0.326f, 0.169f) lineToRelative(-2.122f, 2.944f) lineToRelative(-1.51f, -2.097f) curveToRelative(-0.082f, -0.113f, -0.19f, -0.169f, -0.326f, -0.169f) reflectiveCurveToRelative(-0.245f, 0.056f, -0.326f, 0.169f) lineToRelative(-1.632f, 2.266f) curveToRelative(-0.109f, 0.141f, -0.122f, 0.289f, -0.041f, 0.445f) reflectiveCurveToRelative(0.204f, 0.233f, 0.367f, 0.233f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(15f, 20f) horizontalLineTo(6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.713f) verticalLineToRelative(-1f) horizontalLineToRelative(10f) verticalLineToRelative(2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(19f, 19f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.713f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.713f) verticalLineToRelative(-1f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-7f) verticalLineTo(4f) horizontalLineToRelative(11f) verticalLineToRelative(15f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cool.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Cool: ImageVector by lazy { Builder( name = "Outlined.Cool", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(19.0f, 10.0f) curveTo(19.0f, 11.38f, 16.88f, 12.5f, 15.5f, 12.5f) curveTo(14.12f, 12.5f, 12.75f, 11.38f, 12.75f, 10.0f) horizontalLineTo(11.25f) curveTo(11.25f, 11.38f, 9.88f, 12.5f, 8.5f, 12.5f) curveTo(7.12f, 12.5f, 5.0f, 11.38f, 5.0f, 10.0f) horizontalLineTo(4.25f) curveTo(4.09f, 10.64f, 4.0f, 11.31f, 4.0f, 12.0f) arcTo( 8.0f, 8.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 12.0f, y1 = 20.0f ) arcTo( 8.0f, 8.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 20.0f, y1 = 12.0f ) curveTo(20.0f, 11.31f, 19.91f, 10.64f, 19.75f, 10.0f) horizontalLineTo(19.0f) moveTo(12.0f, 4.0f) curveTo(9.04f, 4.0f, 6.45f, 5.61f, 5.07f, 8.0f) horizontalLineTo(18.93f) curveTo(17.55f, 5.61f, 14.96f, 4.0f, 12.0f, 4.0f) moveTo(22.0f, 12.0f) arcTo( 10.0f, 10.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 12.0f, y1 = 22.0f ) arcTo( 10.0f, 10.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 2.0f, y1 = 12.0f ) arcTo( 10.0f, 10.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 12.0f, y1 = 2.0f ) arcTo( 10.0f, 10.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 22.0f, y1 = 12.0f ) moveTo(12.0f, 17.23f) curveTo(10.25f, 17.23f, 8.71f, 16.5f, 7.81f, 15.42f) lineTo(9.23f, 14.0f) curveTo(9.68f, 14.72f, 10.75f, 15.23f, 12.0f, 15.23f) curveTo(13.25f, 15.23f, 14.32f, 14.72f, 14.77f, 14.0f) lineTo(16.19f, 15.42f) curveTo(15.29f, 16.5f, 13.75f, 17.23f, 12.0f, 17.23f) close() } }.build() } val Icons.Rounded.Cool: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "Rounded.Cool", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(3.22f, 7.22f) curveTo(4.91f, 4.11f, 8.21f, 2f, 12f, 2f) curveTo(15.79f, 2f, 19.09f, 4.11f, 20.78f, 7.22f) lineTo(20f, 8f) horizontalLineTo(4f) lineTo(3.22f, 7.22f) moveTo(21.4f, 8.6f) curveTo(21.78f, 9.67f, 22f, 10.81f, 22f, 12f) arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 22f) arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = true, 2f, 12f) curveTo(2f, 10.81f, 2.22f, 9.67f, 2.6f, 8.6f) lineTo(4f, 10f) horizontalLineTo(5f) curveTo(5f, 11.38f, 7.12f, 12.5f, 8.5f, 12.5f) curveTo(9.88f, 12.5f, 11.25f, 11.38f, 11.25f, 10f) horizontalLineTo(12.75f) curveTo(12.75f, 11.38f, 14.12f, 12.5f, 15.5f, 12.5f) curveTo(16.88f, 12.5f, 19f, 11.38f, 19f, 10f) horizontalLineTo(20f) lineTo(21.4f, 8.6f) moveTo(16.19f, 15.42f) lineTo(14.77f, 14f) curveTo(14.32f, 14.72f, 13.25f, 15.23f, 12f, 15.23f) curveTo(10.75f, 15.23f, 9.68f, 14.72f, 9.23f, 14f) lineTo(7.81f, 15.42f) curveTo(8.71f, 16.5f, 10.25f, 17.23f, 12f, 17.23f) curveTo(13.75f, 17.23f, 15.29f, 16.5f, 16.19f, 15.42f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Counter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Counter: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Counter", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(4f, 4f) horizontalLineTo(20f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 22f, 6f) verticalLineTo(18f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 20f, 20f) horizontalLineTo(4f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 2f, 18f) verticalLineTo(6f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 4f) moveTo(4f, 6f) verticalLineTo(18f) horizontalLineTo(11f) verticalLineTo(6f) horizontalLineTo(4f) moveTo(20f, 18f) verticalLineTo(6f) horizontalLineTo(18.76f) curveTo(19f, 6.54f, 18.95f, 7.07f, 18.95f, 7.13f) curveTo(18.88f, 7.8f, 18.41f, 8.5f, 18.24f, 8.75f) lineTo(15.91f, 11.3f) lineTo(19.23f, 11.28f) lineTo(19.24f, 12.5f) lineTo(14.04f, 12.47f) lineTo(14f, 11.47f) curveTo(14f, 11.47f, 17.05f, 8.24f, 17.2f, 7.95f) curveTo(17.34f, 7.67f, 17.91f, 6f, 16.5f, 6f) curveTo(15.27f, 6.05f, 15.41f, 7.3f, 15.41f, 7.3f) lineTo(13.87f, 7.31f) curveTo(13.87f, 7.31f, 13.88f, 6.65f, 14.25f, 6f) horizontalLineTo(13f) verticalLineTo(18f) horizontalLineTo(15.58f) lineTo(15.57f, 17.14f) lineTo(16.54f, 17.13f) curveTo(16.54f, 17.13f, 17.45f, 16.97f, 17.46f, 16.08f) curveTo(17.5f, 15.08f, 16.65f, 15.08f, 16.5f, 15.08f) curveTo(16.37f, 15.08f, 15.43f, 15.13f, 15.43f, 15.95f) horizontalLineTo(13.91f) curveTo(13.91f, 15.95f, 13.95f, 13.89f, 16.5f, 13.89f) curveTo(19.1f, 13.89f, 18.96f, 15.91f, 18.96f, 15.91f) curveTo(18.96f, 15.91f, 19f, 17.16f, 17.85f, 17.63f) lineTo(18.37f, 18f) horizontalLineTo(20f) moveTo(8.92f, 16f) horizontalLineTo(7.42f) verticalLineTo(10.2f) lineTo(5.62f, 10.76f) verticalLineTo(9.53f) lineTo(8.76f, 8.41f) horizontalLineTo(8.92f) verticalLineTo(16f) close() } }.build() } val Icons.TwoTone.Counter: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Counter", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20f, 4f) horizontalLineTo(4f) curveToRelative(-1.105f, 0f, -2f, 0.895f, -2f, 2f) verticalLineToRelative(12f) curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) horizontalLineToRelative(16f) curveToRelative(1.105f, 0f, 2f, -0.895f, 2f, -2f) verticalLineTo(6f) curveToRelative(0f, -1.105f, -0.895f, -2f, -2f, -2f) close() moveTo(11f, 18f) horizontalLineToRelative(-7f) verticalLineTo(6f) horizontalLineToRelative(7f) verticalLineToRelative(12f) close() moveTo(20f, 18f) horizontalLineToRelative(-1.63f) lineToRelative(-0.52f, -0.37f) curveToRelative(1.15f, -0.47f, 1.11f, -1.72f, 1.11f, -1.72f) curveToRelative(0f, 0f, 0.14f, -2.02f, -2.46f, -2.02f) curveToRelative(-2.55f, 0f, -2.59f, 2.06f, -2.59f, 2.06f) horizontalLineToRelative(1.52f) curveToRelative(0f, -0.82f, 0.94f, -0.87f, 1.07f, -0.87f) curveToRelative(0.15f, 0f, 1f, 0f, 0.96f, 1f) curveToRelative(-0.01f, 0.89f, -0.92f, 1.05f, -0.92f, 1.05f) lineToRelative(-0.97f, 0.01f) lineToRelative(0.01f, 0.86f) horizontalLineToRelative(-2.58f) verticalLineTo(6f) horizontalLineToRelative(1.25f) curveToRelative(-0.37f, 0.65f, -0.38f, 1.31f, -0.38f, 1.31f) lineToRelative(1.54f, -0.01f) reflectiveCurveToRelative(-0.14f, -1.25f, 1.09f, -1.3f) curveToRelative(1.41f, 0f, 0.84f, 1.67f, 0.7f, 1.95f) curveToRelative(-0.15f, 0.29f, -3.2f, 3.52f, -3.2f, 3.52f) lineToRelative(0.04f, 1f) lineToRelative(5.2f, 0.03f) lineToRelative(-0.01f, -1.22f) lineToRelative(-3.32f, 0.02f) lineToRelative(2.33f, -2.55f) curveToRelative(0.17f, -0.25f, 0.64f, -0.95f, 0.71f, -1.62f) curveToRelative(0f, -0.06f, 0.05f, -0.59f, -0.19f, -1.13f) horizontalLineToRelative(1.24f) verticalLineToRelative(12f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8.92f, 16f) lineToRelative(-1.5f, 0f) lineToRelative(0f, -5.8f) lineToRelative(-1.8f, 0.56f) lineToRelative(0f, -1.23f) lineToRelative(3.14f, -1.12f) lineToRelative(0.16f, 0f) lineToRelative(0f, 7.59f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(4f, 6f) lineToRelative(0f, 12f) lineToRelative(7f, 0f) lineToRelative(0f, -12f) lineToRelative(-7f, 0f) } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(20f, 18f) verticalLineTo(6f) horizontalLineToRelative(-1.24f) curveToRelative(0.24f, 0.54f, 0.19f, 1.07f, 0.19f, 1.13f) curveToRelative(-0.07f, 0.67f, -0.54f, 1.37f, -0.71f, 1.62f) lineToRelative(-2.33f, 2.55f) lineToRelative(3.32f, -0.02f) lineToRelative(0.01f, 1.22f) lineToRelative(-5.2f, -0.03f) lineToRelative(-0.04f, -1f) reflectiveCurveToRelative(3.05f, -3.23f, 3.2f, -3.52f) curveToRelative(0.14f, -0.28f, 0.71f, -1.95f, -0.7f, -1.95f) curveToRelative(-1.23f, 0.05f, -1.09f, 1.3f, -1.09f, 1.3f) lineToRelative(-1.54f, 0.01f) reflectiveCurveToRelative(0.01f, -0.66f, 0.38f, -1.31f) horizontalLineToRelative(-1.25f) verticalLineToRelative(12f) horizontalLineToRelative(2.58f) lineToRelative(-0.01f, -0.86f) lineToRelative(0.97f, -0.01f) reflectiveCurveToRelative(0.91f, -0.16f, 0.92f, -1.05f) curveToRelative(0.04f, -1f, -0.81f, -1f, -0.96f, -1f) curveToRelative(-0.13f, 0f, -1.07f, 0.05f, -1.07f, 0.87f) horizontalLineToRelative(-1.52f) reflectiveCurveToRelative(0.04f, -2.06f, 2.59f, -2.06f) curveToRelative(2.6f, 0f, 2.46f, 2.02f, 2.46f, 2.02f) curveToRelative(0f, 0f, 0.04f, 1.25f, -1.11f, 1.72f) lineToRelative(0.52f, 0.37f) horizontalLineToRelative(1.63f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Crashlytics.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Crashlytics: ImageVector by lazy { Builder( name = "Crashlytics", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 276.0f, viewportHeight = 276.0f ).apply { path( fill = SolidColor(Color(0xFF9B1B1D)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(115.637f, 54.539f) curveTo(115.637f, 54.539f, 115.636f, 29.056f, 115.706f, 18.677f) curveTo(115.783f, 7.471f, 122.854f, 0.407f, 133.949f, 0.374f) curveTo(144.848f, 0.341f, 155.747f, 0.342f, 166.646f, 0.374f) curveTo(177.628f, 0.405f, 184.719f, 7.384f, 184.745f, 18.306f) curveTo(184.795f, 39.585f, 184.763f, 60.864f, 184.752f, 82.143f) curveTo(184.751f, 83.264f, 184.626f, 84.384f, 184.531f, 85.969f) curveTo(164.491f, 74.365f, 144.042f, 72.644f, 123.252f, 82.272f) curveTo(107.71f, 89.469f, 96.94f, 101.464f, 91.214f, 117.64f) curveTo(79.796f, 149.899f, 97.261f, 185.336f, 129.91f, 196.39f) curveTo(163.792f, 207.86f, 202.118f, 189.008f, 211.618f, 150.299f) curveTo(212.59f, 151.109f, 244.73f, 182.672f, 259.843f, 197.651f) curveTo(268.055f, 205.79f, 268.042f, 215.538f, 259.865f, 223.673f) curveTo(252.415f, 231.086f, 244.941f, 238.476f, 237.449f, 245.846f) curveTo(229.122f, 254.036f, 219.251f, 254.069f, 210.983f, 245.944f) curveTo(203.487f, 238.578f, 184.663f, 220.176f, 184.663f, 220.176f) curveTo(184.663f, 220.176f, 184.815f, 246.647f, 184.743f, 257.416f) curveTo(184.673f, 267.888f, 177.571f, 274.994f, 167.134f, 275.051f) curveTo(155.976f, 275.113f, 144.817f, 275.104f, 133.659f, 275.054f) curveTo(123.009f, 275.008f, 115.847f, 267.956f, 115.719f, 257.243f) curveTo(115.596f, 246.865f, 115.69f, 220.393f, 115.69f, 220.393f) curveTo(115.69f, 220.393f, 97.538f, 238.619f, 90.13f, 245.89f) curveTo(81.808f, 254.058f, 71.98f, 254.059f, 63.664f, 245.904f) curveTo(56.253f, 238.636f, 48.868f, 231.342f, 41.498f, 224.032f) curveTo(32.917f, 215.52f, 32.948f, 205.998f, 41.368f, 197.458f) curveTo(49.551f, 189.158f, 66.505f, 171.787f, 66.505f, 171.787f) curveTo(66.505f, 171.787f, 39.662f, 171.832f, 28.374f, 171.774f) curveTo(16.755f, 171.715f, 10.067f, 164.983f, 10.029f, 153.365f) curveTo(9.994f, 142.855f, 9.986f, 132.345f, 10.032f, 121.835f) curveTo(10.081f, 110.499f, 16.825f, 103.738f, 28.222f, 103.662f) curveTo(39.25f, 103.589f, 66.644f, 103.644f, 66.644f, 103.644f) curveTo(66.644f, 103.644f, 48.143f, 85.149f, 40.516f, 77.547f) curveTo(32.688f, 69.744f, 32.622f, 59.87f, 40.378f, 52.11f) curveTo(48.175f, 44.31f, 56.007f, 36.545f, 63.91f, 28.852f) curveTo(71.35f, 21.611f, 81.62f, 21.541f, 88.948f, 28.901f) curveTo(97.447f, 37.436f, 115.637f, 54.539f, 115.637f, 54.539f) } path( fill = SolidColor(Color(0xFF9B1B1D)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(175.772f, 137.581f) curveTo(175.784f, 151.544f, 164.634f, 162.635f, 150.54f, 162.68f) curveTo(136.224f, 162.725f, 125.011f, 151.617f, 125.067f, 137.448f) curveTo(125.122f, 123.581f, 136.491f, 112.401f, 150.48f, 112.458f) curveTo(164.512f, 112.515f, 175.759f, 123.688f, 175.772f, 137.581f) } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/CropSmall.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.CropSmall: ImageVector by lazy { Builder( name = "Crop Small", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.4286f, 5.5714f) horizontalLineToRelative(-5.7143f) horizontalLineTo(9.2128f) verticalLineToRelative(2.0f) horizontalLineToRelative(1.5015f) horizontalLineToRelative(5.7143f) verticalLineToRelative(5.7143f) verticalLineToRelative(1.5015f) horizontalLineToRelative(2.0f) verticalLineToRelative(-1.5015f) verticalLineTo(7.5714f) curveTo(18.4286f, 6.4669f, 17.5331f, 5.5714f, 16.4286f, 5.5714f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.0f, 16.4282f) horizontalLineToRelative(-0.5015f) horizontalLineToRelative(-2.8389f) verticalLineToRelative(4.0E-4f) horizontalLineToRelative(-3.3743f) verticalLineToRelative(-0.005f) horizontalLineTo(7.5714f) verticalLineTo(5.5714f) horizontalLineTo(7.571f) verticalLineTo(4.4164f) verticalLineTo(3.9149f) curveToRelative(0.0f, -0.5523f, -0.4477f, -1.0f, -1.0f, -1.0f) reflectiveCurveToRelative(-1.0f, 0.4477f, -1.0f, 1.0f) verticalLineToRelative(0.5015f) verticalLineToRelative(1.155f) horizontalLineToRelative(-1.0695f) horizontalLineTo(4.0f) curveToRelative(-0.5523f, 0.0f, -1.0f, 0.4477f, -1.0f, 1.0f) reflectiveCurveToRelative(0.4477f, 1.0f, 1.0f, 1.0f) horizontalLineToRelative(0.5015f) horizontalLineToRelative(1.0699f) verticalLineToRelative(0.5715f) verticalLineToRelative(0.2162f) verticalLineToRelative(1.7838f) verticalLineToRelative(0.5664f) horizontalLineTo(5.571f) verticalLineToRelative(5.7143f) curveToRelative(0.0f, 1.1046f, 0.8954f, 2.0f, 2.0f, 2.0f) horizontalLineToRelative(0.5171f) curveToRelative(0.0188f, 4.0E-4f, 0.0358f, 0.0051f, 0.0548f, 0.0051f) horizontalLineToRelative(6.9582f) horizontalLineToRelative(0.756f) horizontalLineToRelative(0.5719f) verticalLineToRelative(0.6154f) verticalLineToRelative(0.3693f) verticalLineToRelative(0.1699f) verticalLineToRelative(0.0764f) verticalLineToRelative(0.2553f) curveToRelative(0.0f, 0.5523f, 0.4477f, 1.0f, 1.0f, 1.0f) reflectiveCurveToRelative(1.0f, -0.4477f, 1.0f, -1.0f) verticalLineToRelative(-0.2553f) verticalLineToRelative(-0.0764f) verticalLineToRelative(-0.1699f) verticalLineToRelative(-0.3693f) verticalLineToRelative(-0.6154f) horizontalLineToRelative(0.0497f) verticalLineToRelative(-4.0E-4f) horizontalLineToRelative(1.0198f) horizontalLineTo(20.0f) curveToRelative(0.5523f, 0.0f, 1.0f, -0.4477f, 1.0f, -1.0f) reflectiveCurveTo(20.5523f, 16.4282f, 20.0f, 16.4282f) close() } }.build() } val Icons.TwoTone.CropSmall: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.CropSmall", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(16.429f, 5.571f) horizontalLineToRelative(-7.216f) verticalLineToRelative(2f) horizontalLineToRelative(7.216f) verticalLineToRelative(7.216f) horizontalLineToRelative(2f) verticalLineToRelative(-7.216f) curveToRelative(0f, -1.105f, -0.896f, -2f, -2f, -2f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(20f, 16.428f) horizontalLineToRelative(-3.34f) verticalLineToRelative(0f) horizontalLineToRelative(-3.374f) verticalLineToRelative(-0.005f) horizontalLineToRelative(-5.714f) verticalLineTo(5.571f) horizontalLineToRelative(-0f) verticalLineToRelative(-1.657f) curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) reflectiveCurveToRelative(-1f, 0.448f, -1f, 1f) verticalLineToRelative(1.656f) horizontalLineToRelative(-1.571f) curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) reflectiveCurveToRelative(0.448f, 1f, 1f, 1f) horizontalLineToRelative(1.571f) verticalLineToRelative(3.138f) horizontalLineToRelative(-0f) verticalLineToRelative(5.714f) curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) horizontalLineToRelative(0.517f) curveToRelative(0.019f, 0f, 0.036f, 0.005f, 0.055f, 0.005f) horizontalLineToRelative(8.286f) verticalLineToRelative(1.486f) curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) reflectiveCurveToRelative(1f, -0.448f, 1f, -1f) verticalLineToRelative(-1.486f) horizontalLineToRelative(0.05f) verticalLineToRelative(-0f) horizontalLineToRelative(1.521f) curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) reflectiveCurveToRelative(-0.448f, -1f, -1f, -1f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(7.571f, 7.571f) horizontalLineToRelative(8.857f) verticalLineToRelative(8.852f) horizontalLineToRelative(-8.857f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Cube.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Cube: ImageVector by lazy { Builder( name = "Cube", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(21.0f, 16.5f) curveTo(21.0f, 16.88f, 20.79f, 17.21f, 20.47f, 17.38f) lineTo(12.57f, 21.82f) curveTo(12.41f, 21.94f, 12.21f, 22.0f, 12.0f, 22.0f) curveTo(11.79f, 22.0f, 11.59f, 21.94f, 11.43f, 21.82f) lineTo(3.53f, 17.38f) curveTo(3.21f, 17.21f, 3.0f, 16.88f, 3.0f, 16.5f) verticalLineTo(7.5f) curveTo(3.0f, 7.12f, 3.21f, 6.79f, 3.53f, 6.62f) lineTo(11.43f, 2.18f) curveTo(11.59f, 2.06f, 11.79f, 2.0f, 12.0f, 2.0f) curveTo(12.21f, 2.0f, 12.41f, 2.06f, 12.57f, 2.18f) lineTo(20.47f, 6.62f) curveTo(20.79f, 6.79f, 21.0f, 7.12f, 21.0f, 7.5f) verticalLineTo(16.5f) moveTo(12.0f, 4.15f) lineTo(6.04f, 7.5f) lineTo(12.0f, 10.85f) lineTo(17.96f, 7.5f) lineTo(12.0f, 4.15f) moveTo(5.0f, 15.91f) lineTo(11.0f, 19.29f) verticalLineTo(12.58f) lineTo(5.0f, 9.21f) verticalLineTo(15.91f) moveTo(19.0f, 15.91f) verticalLineTo(9.21f) lineTo(13.0f, 12.58f) verticalLineTo(19.29f) lineTo(19.0f, 15.91f) close() } } .build() } val Icons.Rounded.Cube: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "Rounded.DeployedCode", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(440f, 869f) lineTo(160f, 708f) quadTo(141f, 697f, 130.5f, 679f) quadTo(120f, 661f, 120f, 639f) lineTo(120f, 321f) quadTo(120f, 299f, 130.5f, 281f) quadTo(141f, 263f, 160f, 252f) lineTo(440f, 91f) quadTo(459f, 80f, 480f, 80f) quadTo(501f, 80f, 520f, 91f) lineTo(800f, 252f) quadTo(819f, 263f, 829.5f, 281f) quadTo(840f, 299f, 840f, 321f) lineTo(840f, 639f) quadTo(840f, 661f, 829.5f, 679f) quadTo(819f, 697f, 800f, 708f) lineTo(520f, 869f) quadTo(501f, 880f, 480f, 880f) quadTo(459f, 880f, 440f, 869f) close() moveTo(440f, 503f) lineTo(440f, 777f) lineTo(480f, 800f) quadTo(480f, 800f, 480f, 800f) quadTo(480f, 800f, 480f, 800f) lineTo(520f, 777f) lineTo(520f, 503f) lineTo(760f, 364f) lineTo(760f, 322f) quadTo(760f, 322f, 760f, 322f) quadTo(760f, 322f, 760f, 322f) lineTo(717f, 297f) lineTo(480f, 434f) lineTo(243f, 297f) lineTo(200f, 322f) quadTo(200f, 322f, 200f, 322f) quadTo(200f, 322f, 200f, 322f) lineTo(200f, 364f) lineTo(440f, 503f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Curve.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Curve: ImageVector by lazy { ImageVector.Builder( name = "Curve", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(9.96f, 11.31f) curveTo(10.82f, 8.1f, 11.5f, 6f, 13f, 6f) curveTo(14.5f, 6f, 15.18f, 8.1f, 16.04f, 11.31f) curveTo(17f, 14.92f, 18.1f, 19f, 22f, 19f) verticalLineTo(17f) curveTo(19.8f, 17f, 19f, 14.54f, 17.97f, 10.8f) curveTo(17.08f, 7.46f, 16.15f, 4f, 13f, 4f) curveTo(9.85f, 4f, 8.92f, 7.46f, 8.03f, 10.8f) curveTo(7.03f, 14.54f, 6.2f, 17f, 4f, 17f) verticalLineTo(2f) horizontalLineTo(2f) verticalLineTo(22f) horizontalLineTo(22f) verticalLineTo(20f) horizontalLineTo(4f) verticalLineTo(19f) curveTo(7.9f, 19f, 9f, 14.92f, 9.96f, 11.31f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DashedLine.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.DashedLine: ImageVector by lazy { ImageVector.Builder( name = "DashedLine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(11.291f, 14.828f) lineToRelative(-4.242f, 4.242f) curveToRelative(-0.585f, 0.585f, -1.534f, 0.585f, -2.119f, 0f) lineToRelative(-0f, -0f) curveToRelative(-0.585f, -0.585f, -0.585f, -1.534f, 0f, -2.119f) lineToRelative(4.242f, -4.242f) curveToRelative(0.585f, -0.585f, 1.534f, -0.585f, 2.119f, 0f) lineToRelative(0f, 0f) curveTo(11.877f, 13.294f, 11.877f, 14.243f, 11.291f, 14.828f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(19.071f, 7.049f) lineToRelative(-4.242f, 4.242f) curveToRelative(-0.585f, 0.585f, -1.534f, 0.585f, -2.119f, 0f) lineToRelative(-0f, -0f) curveToRelative(-0.585f, -0.585f, -0.585f, -1.534f, 0f, -2.119f) lineToRelative(4.242f, -4.242f) curveToRelative(0.585f, -0.585f, 1.534f, -0.585f, 2.119f, 0f) lineToRelative(0f, 0f) curveTo(19.656f, 5.515f, 19.656f, 6.464f, 19.071f, 7.049f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Database.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Database: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Database", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(480f, 440f) quadTo(630f, 440f, 735f, 393f) quadTo(840f, 346f, 840f, 280f) quadTo(840f, 214f, 735f, 167f) quadTo(630f, 120f, 480f, 120f) quadTo(330f, 120f, 225f, 167f) quadTo(120f, 214f, 120f, 280f) quadTo(120f, 346f, 225f, 393f) quadTo(330f, 440f, 480f, 440f) close() moveTo(480f, 540f) quadTo(521f, 540f, 582.5f, 531.5f) quadTo(644f, 523f, 701f, 504f) quadTo(758f, 485f, 799f, 454.5f) quadTo(840f, 424f, 840f, 380f) lineTo(840f, 480f) quadTo(840f, 524f, 799f, 554.5f) quadTo(758f, 585f, 701f, 604f) quadTo(644f, 623f, 582.5f, 631.5f) quadTo(521f, 640f, 480f, 640f) quadTo(439f, 640f, 377.5f, 631.5f) quadTo(316f, 623f, 259f, 604f) quadTo(202f, 585f, 161f, 554.5f) quadTo(120f, 524f, 120f, 480f) lineTo(120f, 380f) quadTo(120f, 424f, 161f, 454.5f) quadTo(202f, 485f, 259f, 504f) quadTo(316f, 523f, 377.5f, 531.5f) quadTo(439f, 540f, 480f, 540f) close() moveTo(480f, 740f) quadTo(521f, 740f, 582.5f, 731.5f) quadTo(644f, 723f, 701f, 704f) quadTo(758f, 685f, 799f, 654.5f) quadTo(840f, 624f, 840f, 580f) lineTo(840f, 680f) quadTo(840f, 724f, 799f, 754.5f) quadTo(758f, 785f, 701f, 804f) quadTo(644f, 823f, 582.5f, 831.5f) quadTo(521f, 840f, 480f, 840f) quadTo(439f, 840f, 377.5f, 831.5f) quadTo(316f, 823f, 259f, 804f) quadTo(202f, 785f, 161f, 754.5f) quadTo(120f, 724f, 120f, 680f) lineTo(120f, 580f) quadTo(120f, 624f, 161f, 654.5f) quadTo(202f, 685f, 259f, 704f) quadTo(316f, 723f, 377.5f, 731.5f) quadTo(439f, 740f, 480f, 740f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Delete.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Delete: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Delete", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(200f, 760f) verticalLineToRelative(-520f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(160f, 200f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(200f, 160f) horizontalLineToRelative(160f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(400f, 120f) horizontalLineToRelative(160f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(600f, 160f) horizontalLineToRelative(160f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(800f, 200f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(760f, 240f) verticalLineToRelative(520f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(680f, 840f) lineTo(280f, 840f) close() moveTo(428.5f, 668.5f) quadTo(440f, 657f, 440f, 640f) verticalLineToRelative(-280f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(400f, 320f) quadToRelative(-17f, 0f, -28.5f, 11.5f) reflectiveQuadTo(360f, 360f) verticalLineToRelative(280f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(400f, 680f) quadToRelative(17f, 0f, 28.5f, -11.5f) close() moveTo(588.5f, 668.5f) quadTo(600f, 657f, 600f, 640f) verticalLineToRelative(-280f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(560f, 320f) quadToRelative(-17f, 0f, -28.5f, 11.5f) reflectiveQuadTo(520f, 360f) verticalLineToRelative(280f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(560f, 680f) quadToRelative(17f, 0f, 28.5f, -11.5f) close() } }.build() } val Icons.Outlined.Delete: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.DeleteOutline", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(200f, 760f) verticalLineToRelative(-520f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(160f, 200f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(200f, 160f) horizontalLineToRelative(160f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(400f, 120f) horizontalLineToRelative(160f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(600f, 160f) horizontalLineToRelative(160f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(800f, 200f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(760f, 240f) verticalLineToRelative(520f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(680f, 840f) lineTo(280f, 840f) close() moveTo(680f, 240f) lineTo(280f, 240f) verticalLineToRelative(520f) horizontalLineToRelative(400f) verticalLineToRelative(-520f) close() moveTo(428.5f, 668.5f) quadTo(440f, 657f, 440f, 640f) verticalLineToRelative(-280f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(400f, 320f) quadToRelative(-17f, 0f, -28.5f, 11.5f) reflectiveQuadTo(360f, 360f) verticalLineToRelative(280f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(400f, 680f) quadToRelative(17f, 0f, 28.5f, -11.5f) close() moveTo(588.5f, 668.5f) quadTo(600f, 657f, 600f, 640f) verticalLineToRelative(-280f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(560f, 320f) quadToRelative(-17f, 0f, -28.5f, 11.5f) reflectiveQuadTo(520f, 360f) verticalLineToRelative(280f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(560f, 680f) quadToRelative(17f, 0f, 28.5f, -11.5f) close() moveTo(280f, 240f) verticalLineToRelative(520f) verticalLineToRelative(-520f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DeleteSweep.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.DeleteSweep: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.DeleteSweep", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(200f, 760f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(120f, 680f) verticalLineToRelative(-360f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(80f, 280f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(120f, 240f) horizontalLineToRelative(120f) verticalLineToRelative(-20f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(280f, 180f) horizontalLineToRelative(80f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(400f, 220f) verticalLineToRelative(20f) horizontalLineToRelative(120f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(560f, 280f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(520f, 320f) verticalLineToRelative(360f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(440f, 760f) lineTo(200f, 760f) close() moveTo(640f, 720f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(600f, 680f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(640f, 640f) horizontalLineToRelative(80f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(760f, 680f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(720f, 720f) horizontalLineToRelative(-80f) close() moveTo(640f, 560f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(600f, 520f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(640f, 480f) horizontalLineToRelative(160f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(840f, 520f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(800f, 560f) lineTo(640f, 560f) close() moveTo(640f, 400f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(600f, 360f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(640f, 320f) horizontalLineToRelative(200f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(880f, 360f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(840f, 400f) lineTo(640f, 400f) close() moveTo(200f, 320f) verticalLineToRelative(360f) horizontalLineToRelative(240f) verticalLineToRelative(-360f) lineTo(200f, 320f) close() } }.build() } val Icons.TwoTone.DeleteSweep: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.DeleteSweep", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(5f, 19f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(8f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(3f) verticalLineToRelative(-0.5f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(2f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(0.5f) horizontalLineToRelative(3f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) verticalLineToRelative(9f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-6f) close() moveTo(16f, 18f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(2f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-2f) close() moveTo(16f, 14f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-4f) close() moveTo(16f, 10f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(5f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-5f) close() moveTo(5f, 8f) verticalLineToRelative(9f) horizontalLineToRelative(6f) verticalLineTo(8f) horizontalLineToRelative(-6f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 8f) horizontalLineToRelative(6f) verticalLineToRelative(9f) horizontalLineToRelative(-6f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Deselect.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Deselect: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Deselect", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(763f, 876f) lineTo(567f, 680f) lineTo(360f, 680f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(280f, 600f) verticalLineToRelative(-207f) lineTo(84f, 197f) quadToRelative(-11f, -11f, -11f, -27.5f) reflectiveQuadTo(84f, 141f) quadToRelative(12f, -12f, 28.5f, -12f) reflectiveQuadToRelative(28.5f, 12f) lineToRelative(679f, 679f) quadToRelative(12f, 12f, 11.5f, 28f) reflectiveQuadTo(819f, 876f) quadToRelative(-12f, 11f, -28f, 11.5f) reflectiveQuadTo(763f, 876f) close() moveTo(487f, 600f) lineTo(360f, 473f) verticalLineToRelative(127f) horizontalLineToRelative(127f) close() moveTo(680f, 567f) lineTo(600f, 487f) verticalLineToRelative(-127f) lineTo(473f, 360f) lineToRelative(-80f, -80f) horizontalLineToRelative(207f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(680f, 360f) verticalLineToRelative(207f) close() moveTo(291.5f, 188.5f) quadTo(280f, 177f, 280f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(303f, 120f, 320f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(360f, 143f, 360f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(337f, 200f, 320f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(451.5f, 188.5f) quadTo(440f, 177f, 440f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(463f, 120f, 480f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(520f, 143f, 520f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(497f, 200f, 480f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(611.5f, 188.5f) quadTo(600f, 177f, 600f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(623f, 120f, 640f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(680f, 143f, 680f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(657f, 200f, 640f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 188.5f) quadTo(760f, 177f, 760f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 120f, 800f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 143f, 840f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 200f, 800f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 348.5f) quadTo(120f, 337f, 120f, 320f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 280f, 160f, 280f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 303f, 200f, 320f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 360f, 160f, 360f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 348.5f) quadTo(760f, 337f, 760f, 320f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 280f, 800f, 280f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 303f, 840f, 320f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 360f, 800f, 360f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 508.5f) quadTo(120f, 497f, 120f, 480f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 440f, 160f, 440f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 463f, 200f, 480f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 520f, 160f, 520f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 508.5f) quadTo(760f, 497f, 760f, 480f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 440f, 800f, 440f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 463f, 840f, 480f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 520f, 800f, 520f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 668.5f) quadTo(120f, 657f, 120f, 640f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 600f, 160f, 600f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 623f, 200f, 640f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 680f, 160f, 680f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 668.5f) quadTo(760f, 657f, 760f, 640f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 600f, 800f, 600f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 623f, 840f, 640f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 680f, 800f, 680f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 828.5f) quadTo(120f, 817f, 120f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 760f, 160f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 783f, 200f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 840f, 160f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(291.5f, 828.5f) quadTo(280f, 817f, 280f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(303f, 760f, 320f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(360f, 783f, 360f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(337f, 840f, 320f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(451.5f, 828.5f) quadTo(440f, 817f, 440f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(463f, 760f, 480f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(520f, 783f, 520f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(497f, 840f, 480f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(611.5f, 828.5f) quadTo(600f, 817f, 600f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(623f, 760f, 640f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(680f, 783f, 680f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(657f, 840f, 640f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DesignServices.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.DesignServices: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.DesignServices", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(352f, 438f) lineTo(438f, 351f) lineTo(382f, 294f) lineTo(338f, 338f) lineTo(282f, 282f) lineTo(325f, 238f) lineTo(280f, 193f) lineTo(193f, 280f) lineTo(352f, 438f) close() moveTo(680f, 767f) lineTo(767f, 680f) lineTo(722f, 635f) lineTo(678f, 678f) lineTo(622f, 622f) lineTo(665f, 578f) lineTo(608f, 522f) lineTo(522f, 608f) lineTo(680f, 767f) close() moveTo(649f, 257f) lineTo(705f, 313f) lineTo(761f, 257f) lineTo(704f, 200f) lineTo(649f, 257f) close() moveTo(290f, 840f) lineTo(120f, 840f) lineTo(120f, 670f) lineTo(295f, 495f) lineTo(80f, 280f) lineTo(280f, 80f) lineTo(496f, 296f) lineTo(647f, 144f) quadTo(659f, 132f, 674f, 126f) quadTo(689f, 120f, 705f, 120f) quadTo(721f, 120f, 736f, 126f) quadTo(751f, 132f, 763f, 144f) lineTo(816f, 198f) quadTo(828f, 210f, 834f, 225f) quadTo(840f, 240f, 840f, 256f) quadTo(840f, 272f, 834f, 286.5f) quadTo(828f, 301f, 816f, 313f) lineTo(665f, 465f) lineTo(880f, 680f) lineTo(680f, 880f) lineTo(465f, 665f) lineTo(290f, 840f) close() } }.build() } val Icons.TwoTone.DesignServices: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.DesignServices", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(17.6f, 5f) lineToRelative(1.425f, 1.425f) lineToRelative(-1.425f, -1.425f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.625f, 11.625f) lineToRelative(3.775f, -3.8f) curveToRelative(0.2f, -0.2f, 0.35f, -0.421f, 0.45f, -0.663f) curveToRelative(0.1f, -0.242f, 0.15f, -0.496f, 0.15f, -0.762f) reflectiveCurveToRelative(-0.05f, -0.525f, -0.15f, -0.775f) curveToRelative(-0.1f, -0.25f, -0.25f, -0.475f, -0.45f, -0.675f) lineToRelative(-1.325f, -1.35f) curveToRelative(-0.2f, -0.2f, -0.425f, -0.35f, -0.675f, -0.45f) curveToRelative(-0.25f, -0.1f, -0.508f, -0.15f, -0.775f, -0.15f) reflectiveCurveToRelative(-0.525f, 0.05f, -0.775f, 0.15f) curveToRelative(-0.25f, 0.1f, -0.475f, 0.25f, -0.675f, 0.45f) lineToRelative(-3.775f, 3.8f) lineTo(7f, 2f) lineTo(2f, 7f) lineToRelative(5.375f, 5.375f) lineToRelative(-4.375f, 4.375f) verticalLineToRelative(4.25f) horizontalLineToRelative(4.25f) lineToRelative(4.375f, -4.375f) lineToRelative(5.375f, 5.375f) lineToRelative(5f, -5f) lineToRelative(-5.375f, -5.375f) close() moveTo(4.825f, 7f) lineToRelative(2.175f, -2.175f) lineToRelative(1.125f, 1.125f) lineToRelative(-1.075f, 1.1f) lineToRelative(1.4f, 1.4f) lineToRelative(1.1f, -1.1f) lineToRelative(1.4f, 1.425f) lineToRelative(-2.15f, 2.175f) lineToRelative(-3.975f, -3.95f) close() moveTo(6.4f, 19f) horizontalLineToRelative(-1.4f) verticalLineToRelative(-1.4f) lineTo(14.775f, 7.8f) lineToRelative(1.425f, 1.425f) lineToRelative(-9.8f, 9.775f) close() moveTo(13.05f, 15.2f) lineToRelative(2.15f, -2.15f) lineToRelative(1.425f, 1.4f) lineToRelative(-1.075f, 1.1f) lineToRelative(1.4f, 1.4f) lineToRelative(1.1f, -1.075f) lineToRelative(1.125f, 1.125f) lineToRelative(-2.175f, 2.175f) lineToRelative(-3.95f, -3.975f) close() } path(fill = SolidColor(Color.Black)) { moveTo(15.5f, 8.525f) lineToRelative(-0.725f, -0.725f) lineToRelative(1.425f, 1.425f) lineToRelative(-0.7f, -0.7f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(8.8f, 10.95f) lineToRelative(2.15f, -2.175f) lineToRelative(-1.4f, -1.425f) lineToRelative(-1.1f, 1.1f) lineToRelative(-1.4f, -1.4f) lineToRelative(1.075f, -1.1f) lineToRelative(-1.125f, -1.125f) lineToRelative(-2.175f, 2.175f) lineToRelative(3.975f, 3.95f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(17f, 19.175f) lineToRelative(2.175f, -2.175f) lineToRelative(-1.125f, -1.125f) lineToRelative(-1.1f, 1.075f) lineToRelative(-1.4f, -1.4f) lineToRelative(1.075f, -1.1f) lineToRelative(-1.425f, -1.4f) lineToRelative(-2.15f, 2.15f) lineToRelative(3.95f, 3.975f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 19f) lineToRelative(1.4f, 0f) lineToRelative(9.8f, -9.775f) lineToRelative(-1.425f, -1.425f) lineToRelative(-9.775f, 9.8f) lineToRelative(0f, 1.4f) close() } }.build() } val Icons.Outlined.DesignServices: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.DesignServices", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(352f, 438f) lineTo(438f, 351f) lineTo(382f, 294f) lineTo(338f, 338f) lineTo(282f, 282f) lineTo(325f, 238f) lineTo(280f, 193f) lineTo(193f, 280f) lineTo(352f, 438f) close() moveTo(680f, 767f) lineTo(767f, 680f) lineTo(722f, 635f) lineTo(678f, 678f) lineTo(622f, 622f) lineTo(665f, 578f) lineTo(608f, 522f) lineTo(522f, 608f) lineTo(680f, 767f) close() moveTo(704f, 200f) lineTo(761f, 257f) lineTo(761f, 257f) lineTo(704f, 200f) close() moveTo(290f, 840f) lineTo(120f, 840f) lineTo(120f, 670f) lineTo(295f, 495f) lineTo(80f, 280f) lineTo(280f, 80f) lineTo(496f, 296f) lineTo(647f, 144f) quadTo(659f, 132f, 674f, 126f) quadTo(689f, 120f, 705f, 120f) quadTo(721f, 120f, 736f, 126f) quadTo(751f, 132f, 763f, 144f) lineTo(816f, 198f) quadTo(828f, 210f, 834f, 225f) quadTo(840f, 240f, 840f, 256f) quadTo(840f, 272f, 834f, 286.5f) quadTo(828f, 301f, 816f, 313f) lineTo(665f, 465f) lineTo(880f, 680f) lineTo(680f, 880f) lineTo(465f, 665f) lineTo(290f, 840f) close() moveTo(200f, 760f) lineTo(256f, 760f) lineTo(648f, 369f) lineTo(591f, 312f) lineTo(200f, 704f) lineTo(200f, 760f) close() moveTo(620f, 341f) lineTo(591f, 312f) lineTo(591f, 312f) lineTo(648f, 369f) lineTo(648f, 369f) lineTo(620f, 341f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DocumentScanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.DocumentScanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.DocumentScanner", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(2f, 5f) verticalLineToRelative(-2f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(2f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(20f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(2f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(2f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(2f, 21f) verticalLineToRelative(-2f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(2f) horizontalLineToRelative(2f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) close() moveTo(20f, 23f) horizontalLineToRelative(-2f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(2f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) close() moveTo(7f, 18f) horizontalLineToRelative(10f) verticalLineTo(6f) horizontalLineTo(7f) verticalLineToRelative(12f) close() moveTo(7f, 20f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(6f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(10f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(7f) close() moveTo(10f, 10f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-4f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(10f, 13f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-4f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(10f, 16f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-4f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(7f, 18f) verticalLineTo(6f) verticalLineToRelative(12f) close() } }.build() } val Icons.TwoTone.DocumentScanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.DocumentScanner", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(2f, 5f) verticalLineToRelative(-2f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(2f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(20f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(2f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(2f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(2f, 21f) verticalLineToRelative(-2f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(2f) horizontalLineToRelative(2f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) close() moveTo(20f, 23f) horizontalLineToRelative(-2f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(2f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) close() moveTo(7f, 18f) horizontalLineToRelative(10f) verticalLineTo(6f) horizontalLineTo(7f) verticalLineToRelative(12f) close() moveTo(7f, 20f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(6f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(10f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(7f) close() moveTo(10f, 10f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-4f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(10f, 13f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-4f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(10f, 16f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-4f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(7f, 18f) verticalLineTo(6f) verticalLineToRelative(12f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(7f, 6f) horizontalLineToRelative(10f) verticalLineToRelative(12f) horizontalLineToRelative(-10f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DotDashedLine.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.DotDashedLine: ImageVector by lazy { ImageVector.Builder( name = "DotDashedLine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(9.877f, 16.243f) lineToRelative(-2.828f, 2.828f) curveToRelative(-0.585f, 0.585f, -1.534f, 0.585f, -2.119f, 0f) lineToRelative(-0f, -0f) curveToRelative(-0.585f, -0.585f, -0.585f, -1.534f, -0f, -2.119f) lineToRelative(2.828f, -2.828f) curveToRelative(0.585f, -0.585f, 1.534f, -0.585f, 2.119f, 0f) lineToRelative(0f, 0f) curveTo(10.462f, 14.708f, 10.462f, 15.657f, 9.877f, 16.243f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(19.071f, 7.049f) lineToRelative(-2.83f, 2.83f) curveToRelative(-0.585f, 0.585f, -1.534f, 0.585f, -2.119f, 0f) lineToRelative(-0f, -0f) curveToRelative(-0.585f, -0.585f, -0.585f, -1.534f, 0f, -2.119f) lineToRelative(2.83f, -2.83f) curveToRelative(0.585f, -0.585f, 1.534f, -0.585f, 2.119f, 0f) lineToRelative(0f, 0f) curveTo(19.656f, 5.515f, 19.656f, 6.464f, 19.071f, 7.049f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(13.06f, 13.06f) lineToRelative(-0f, 0f) curveToRelative(-0.585f, 0.585f, -1.534f, 0.585f, -2.119f, 0f) lineToRelative(-0f, -0f) curveToRelative(-0.585f, -0.585f, -0.585f, -1.534f, 0f, -2.119f) lineToRelative(0f, -0f) curveToRelative(0.585f, -0.585f, 1.534f, -0.585f, 2.119f, 0f) lineToRelative(0f, 0f) curveTo(13.645f, 11.526f, 13.645f, 12.474f, 13.06f, 13.06f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Dots.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Dots: ImageVector by lazy { Builder( name = "Dots", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 19.0f) curveTo(13.1f, 19.0f, 14.0f, 19.9f, 14.0f, 21.0f) reflectiveCurveTo(13.1f, 23.0f, 12.0f, 23.0f) reflectiveCurveTo(10.0f, 22.1f, 10.0f, 21.0f) reflectiveCurveTo(10.9f, 19.0f, 12.0f, 19.0f) moveTo(12.0f, 1.0f) curveTo(13.1f, 1.0f, 14.0f, 1.9f, 14.0f, 3.0f) reflectiveCurveTo(13.1f, 5.0f, 12.0f, 5.0f) reflectiveCurveTo(10.0f, 4.1f, 10.0f, 3.0f) reflectiveCurveTo(10.9f, 1.0f, 12.0f, 1.0f) moveTo(6.0f, 16.0f) curveTo(7.1f, 16.0f, 8.0f, 16.9f, 8.0f, 18.0f) reflectiveCurveTo(7.1f, 20.0f, 6.0f, 20.0f) reflectiveCurveTo(4.0f, 19.1f, 4.0f, 18.0f) reflectiveCurveTo(4.9f, 16.0f, 6.0f, 16.0f) moveTo(3.0f, 10.0f) curveTo(4.1f, 10.0f, 5.0f, 10.9f, 5.0f, 12.0f) reflectiveCurveTo(4.1f, 14.0f, 3.0f, 14.0f) reflectiveCurveTo(1.0f, 13.1f, 1.0f, 12.0f) reflectiveCurveTo(1.9f, 10.0f, 3.0f, 10.0f) moveTo(6.0f, 4.0f) curveTo(7.1f, 4.0f, 8.0f, 4.9f, 8.0f, 6.0f) reflectiveCurveTo(7.1f, 8.0f, 6.0f, 8.0f) reflectiveCurveTo(4.0f, 7.1f, 4.0f, 6.0f) reflectiveCurveTo(4.9f, 4.0f, 6.0f, 4.0f) moveTo(18.0f, 16.0f) curveTo(19.1f, 16.0f, 20.0f, 16.9f, 20.0f, 18.0f) reflectiveCurveTo(19.1f, 20.0f, 18.0f, 20.0f) reflectiveCurveTo(16.0f, 19.1f, 16.0f, 18.0f) reflectiveCurveTo(16.9f, 16.0f, 18.0f, 16.0f) moveTo(21.0f, 10.0f) curveTo(22.1f, 10.0f, 23.0f, 10.9f, 23.0f, 12.0f) reflectiveCurveTo(22.1f, 14.0f, 21.0f, 14.0f) reflectiveCurveTo(19.0f, 13.1f, 19.0f, 12.0f) reflectiveCurveTo(19.9f, 10.0f, 21.0f, 10.0f) moveTo(18.0f, 4.0f) curveTo(19.1f, 4.0f, 20.0f, 4.9f, 20.0f, 6.0f) reflectiveCurveTo(19.1f, 8.0f, 18.0f, 8.0f) reflectiveCurveTo(16.0f, 7.1f, 16.0f, 6.0f) reflectiveCurveTo(16.9f, 4.0f, 18.0f, 4.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/DownloadFile.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.DownloadFile: ImageVector by lazy { Builder( name = "Download File", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(14.0f, 2.0f) horizontalLineTo(6.0f) curveTo(4.89f, 2.0f, 4.0f, 2.89f, 4.0f, 4.0f) verticalLineTo(20.0f) curveTo(4.0f, 21.11f, 4.89f, 22.0f, 6.0f, 22.0f) horizontalLineTo(18.0f) curveTo(19.11f, 22.0f, 20.0f, 21.11f, 20.0f, 20.0f) verticalLineTo(8.0f) lineTo(14.0f, 2.0f) moveTo(12.0f, 19.0f) lineTo(8.0f, 15.0f) horizontalLineTo(10.5f) verticalLineTo(12.0f) horizontalLineTo(13.5f) verticalLineTo(15.0f) horizontalLineTo(16.0f) lineTo(12.0f, 19.0f) moveTo(13.0f, 9.0f) verticalLineTo(3.5f) lineTo(18.5f, 9.0f) horizontalLineTo(13.0f) close() } }.build() } val Icons.Outlined.DownloadFile: ImageVector by lazy { Builder( name = "Download File Outline", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(14.0f, 2.0f) lineTo(20.0f, 8.0f) verticalLineTo(20.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 18.0f, y1 = 22.0f ) horizontalLineTo(6.0f) arcTo( horizontalEllipseRadius = 2.0f, verticalEllipseRadius = 2.0f, theta = 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 4.0f, y1 = 20.0f ) verticalLineTo(4.0f) arcTo( horizontalEllipseRadius = 2.0f, verticalEllipseRadius = 2.0f, theta = 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 6.0f, y1 = 2.0f ) horizontalLineTo(14.0f) moveTo(18.0f, 20.0f) verticalLineTo(9.0f) horizontalLineTo(13.0f) verticalLineTo(4.0f) horizontalLineTo(6.0f) verticalLineTo(20.0f) horizontalLineTo(18.0f) moveTo(12.0f, 19.0f) lineTo(8.0f, 15.0f) horizontalLineTo(10.5f) verticalLineTo(12.0f) horizontalLineTo(13.5f) verticalLineTo(15.0f) horizontalLineTo(16.0f) lineTo(12.0f, 19.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Draw.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Draw: ImageVector by lazy { ImageVector.Builder( name = "Draw", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(200f, 840f) quadTo(183f, 840f, 171.5f, 828.5f) quadTo(160f, 817f, 160f, 800f) lineTo(160f, 703f) quadTo(160f, 687f, 166f, 672.5f) quadTo(172f, 658f, 183f, 647f) lineTo(687f, 144f) quadTo(699f, 132f, 714f, 126f) quadTo(729f, 120f, 744f, 120f) quadTo(760f, 120f, 774.5f, 126f) quadTo(789f, 132f, 800f, 144f) lineTo(856f, 200f) quadTo(868f, 211f, 874f, 225.5f) quadTo(880f, 240f, 880f, 256f) quadTo(880f, 271f, 874f, 286f) quadTo(868f, 301f, 856f, 313f) lineTo(353f, 817f) quadTo(342f, 828f, 327.5f, 834f) quadTo(313f, 840f, 297f, 840f) lineTo(200f, 840f) close() moveTo(240f, 760f) lineTo(296f, 760f) lineTo(689f, 368f) lineTo(661f, 339f) lineTo(632f, 311f) lineTo(240f, 704f) lineTo(240f, 760f) close() moveTo(800f, 257f) lineTo(800f, 257f) lineTo(743f, 200f) lineTo(743f, 200f) lineTo(800f, 257f) close() moveTo(661f, 339f) lineTo(632f, 311f) lineTo(632f, 311f) lineTo(689f, 368f) lineTo(689f, 368f) lineTo(661f, 339f) close() moveTo(560f, 840f) quadTo(634f, 840f, 697f, 803f) quadTo(760f, 766f, 760f, 700f) quadTo(760f, 668f, 744f, 644.5f) quadTo(728f, 621f, 702f, 601f) quadTo(688f, 591f, 672f, 591f) quadTo(656f, 591f, 645f, 603f) quadTo(634f, 615f, 634f, 632.5f) quadTo(634f, 650f, 648f, 660f) quadTo(662f, 671f, 671f, 680f) quadTo(680f, 689f, 680f, 700f) quadTo(680f, 723f, 643.5f, 741.5f) quadTo(607f, 760f, 560f, 760f) quadTo(543f, 760f, 531.5f, 771.5f) quadTo(520f, 783f, 520f, 800f) quadTo(520f, 817f, 531.5f, 828.5f) quadTo(543f, 840f, 560f, 840f) close() moveTo(360f, 240f) quadTo(360f, 254f, 342.5f, 265.5f) quadTo(325f, 277f, 262f, 306f) quadTo(182f, 341f, 151f, 369.5f) quadTo(120f, 398f, 120f, 440f) quadTo(120f, 466f, 132f, 486f) quadTo(144f, 506f, 163f, 521f) quadTo(176f, 532f, 192f, 530.5f) quadTo(208f, 529f, 219f, 516f) quadTo(230f, 503f, 229f, 487f) quadTo(228f, 471f, 215f, 460f) quadTo(208f, 455f, 204f, 450f) quadTo(200f, 445f, 200f, 440f) quadTo(200f, 428f, 218f, 416f) quadTo(236f, 404f, 294f, 379f) quadTo(382f, 341f, 411f, 310f) quadTo(440f, 279f, 440f, 240f) quadTo(440f, 185f, 396f, 152.5f) quadTo(352f, 120f, 280f, 120f) quadTo(235f, 120f, 199.5f, 136f) quadTo(164f, 152f, 145f, 175f) quadTo(134f, 188f, 136f, 204f) quadTo(138f, 220f, 151f, 230f) quadTo(164f, 241f, 180f, 239f) quadTo(196f, 237f, 207f, 226f) quadTo(221f, 212f, 238f, 206f) quadTo(255f, 200f, 280f, 200f) quadTo(321f, 200f, 340.5f, 212f) quadTo(360f, 224f, 360f, 240f) close() } }.build() } val Icons.Rounded.Draw: ImageVector by lazy { ImageVector.Builder( name = "Draw", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(200f, 840f) quadTo(183f, 840f, 171.5f, 828.5f) quadTo(160f, 817f, 160f, 800f) lineTo(160f, 703f) quadTo(160f, 687f, 166f, 672.5f) quadTo(172f, 658f, 183f, 647f) lineTo(687f, 144f) quadTo(699f, 132f, 714f, 126f) quadTo(729f, 120f, 744f, 120f) quadTo(760f, 120f, 774.5f, 126f) quadTo(789f, 132f, 800f, 144f) lineTo(856f, 200f) quadTo(868f, 211f, 874f, 225.5f) quadTo(880f, 240f, 880f, 256f) quadTo(880f, 271f, 874f, 286f) quadTo(868f, 301f, 856f, 313f) lineTo(353f, 817f) quadTo(342f, 828f, 327.5f, 834f) quadTo(313f, 840f, 297f, 840f) lineTo(200f, 840f) close() moveTo(746f, 311f) lineTo(800f, 257f) lineTo(743f, 200f) lineTo(689f, 254f) lineTo(746f, 311f) close() moveTo(560f, 840f) quadTo(634f, 840f, 697f, 803f) quadTo(760f, 766f, 760f, 700f) quadTo(760f, 668f, 744f, 644.5f) quadTo(728f, 621f, 702f, 601f) quadTo(688f, 591f, 672f, 591f) quadTo(656f, 591f, 645f, 603f) quadTo(634f, 615f, 634f, 632.5f) quadTo(634f, 650f, 648f, 660f) quadTo(662f, 671f, 671f, 680f) quadTo(680f, 689f, 680f, 700f) quadTo(680f, 723f, 643.5f, 741.5f) quadTo(607f, 760f, 560f, 760f) quadTo(543f, 760f, 531.5f, 771.5f) quadTo(520f, 783f, 520f, 800f) quadTo(520f, 817f, 531.5f, 828.5f) quadTo(543f, 840f, 560f, 840f) close() moveTo(360f, 240f) quadTo(360f, 254f, 342.5f, 265.5f) quadTo(325f, 277f, 262f, 306f) quadTo(182f, 341f, 151f, 369.5f) quadTo(120f, 398f, 120f, 440f) quadTo(120f, 466f, 132f, 486f) quadTo(144f, 506f, 163f, 521f) quadTo(176f, 532f, 192f, 530.5f) quadTo(208f, 529f, 219f, 516f) quadTo(230f, 503f, 229f, 487f) quadTo(228f, 471f, 215f, 460f) quadTo(208f, 455f, 204f, 450f) quadTo(200f, 445f, 200f, 440f) quadTo(200f, 428f, 218f, 416f) quadTo(236f, 404f, 294f, 379f) quadTo(382f, 341f, 411f, 310f) quadTo(440f, 279f, 440f, 240f) quadTo(440f, 185f, 396f, 152.5f) quadTo(352f, 120f, 280f, 120f) quadTo(235f, 120f, 199.5f, 136f) quadTo(164f, 152f, 145f, 175f) quadTo(134f, 188f, 136f, 204f) quadTo(138f, 220f, 151f, 230f) quadTo(164f, 241f, 180f, 239f) quadTo(196f, 237f, 207f, 226f) quadTo(221f, 212f, 238f, 206f) quadTo(255f, 200f, 280f, 200f) quadTo(321f, 200f, 340.5f, 212f) quadTo(360f, 224f, 360f, 240f) close() } }.build() } val Icons.TwoTone.Draw: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Draw", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6f, 19f) lineToRelative(1.4f, 0f) lineToRelative(9.825f, -9.8f) lineToRelative(-0.7f, -0.725f) lineToRelative(-0.725f, -0.7f) lineToRelative(-9.8f, 9.825f) lineToRelative(0f, 1.4f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(16.525f, 8.475f) lineToRelative(-0.725f, -0.7f) lineToRelative(0f, 0f) lineToRelative(1.425f, 1.425f) lineToRelative(0f, 0f) lineToRelative(-0.7f, -0.725f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(14f, 21f) curveToRelative(1.233f, 0f, 2.375f, -0.308f, 3.425f, -0.925f) reflectiveCurveToRelative(1.575f, -1.475f, 1.575f, -2.575f) curveToRelative(0f, -0.533f, -0.133f, -0.996f, -0.4f, -1.388f) reflectiveCurveToRelative(-0.617f, -0.754f, -1.05f, -1.087f) curveToRelative(-0.233f, -0.167f, -0.483f, -0.25f, -0.75f, -0.25f) reflectiveCurveToRelative(-0.492f, 0.1f, -0.675f, 0.3f) reflectiveCurveToRelative(-0.275f, 0.446f, -0.275f, 0.738f) reflectiveCurveToRelative(0.117f, 0.521f, 0.35f, 0.688f) curveToRelative(0.233f, 0.183f, 0.425f, 0.35f, 0.575f, 0.5f) reflectiveCurveToRelative(0.225f, 0.317f, 0.225f, 0.5f) curveToRelative(0f, 0.383f, -0.304f, 0.729f, -0.913f, 1.038f) reflectiveCurveToRelative(-1.304f, 0.463f, -2.088f, 0.463f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(9f, 6f) curveToRelative(0f, 0.233f, -0.146f, 0.446f, -0.438f, 0.637f) reflectiveCurveToRelative(-0.962f, 0.529f, -2.013f, 1.013f) curveToRelative(-1.333f, 0.583f, -2.258f, 1.112f, -2.775f, 1.587f) reflectiveCurveToRelative(-0.775f, 1.063f, -0.775f, 1.763f) curveToRelative(0f, 0.433f, 0.1f, 0.817f, 0.3f, 1.15f) reflectiveCurveToRelative(0.458f, 0.625f, 0.775f, 0.875f) curveToRelative(0.217f, 0.183f, 0.458f, 0.262f, 0.725f, 0.237f) reflectiveCurveToRelative(0.492f, -0.146f, 0.675f, -0.363f) curveToRelative(0.183f, -0.217f, 0.267f, -0.458f, 0.25f, -0.725f) reflectiveCurveToRelative(-0.133f, -0.492f, -0.35f, -0.675f) curveToRelative(-0.117f, -0.083f, -0.208f, -0.167f, -0.275f, -0.25f) reflectiveCurveToRelative(-0.1f, -0.167f, -0.1f, -0.25f) curveToRelative(0f, -0.2f, 0.15f, -0.4f, 0.45f, -0.6f) reflectiveCurveToRelative(0.933f, -0.508f, 1.9f, -0.925f) curveToRelative(1.467f, -0.633f, 2.442f, -1.208f, 2.925f, -1.725f) reflectiveCurveToRelative(0.725f, -1.1f, 0.725f, -1.75f) curveToRelative(0f, -0.917f, -0.367f, -1.646f, -1.1f, -2.188f) reflectiveCurveToRelative(-1.7f, -0.813f, -2.9f, -0.813f) curveToRelative(-0.75f, 0f, -1.421f, 0.133f, -2.013f, 0.4f) curveToRelative(-0.592f, 0.267f, -1.046f, 0.592f, -1.362f, 0.975f) curveToRelative(-0.183f, 0.217f, -0.258f, 0.458f, -0.225f, 0.725f) reflectiveCurveToRelative(0.158f, 0.483f, 0.375f, 0.65f) curveToRelative(0.217f, 0.183f, 0.458f, 0.258f, 0.725f, 0.225f) reflectiveCurveToRelative(0.492f, -0.142f, 0.675f, -0.325f) curveToRelative(0.233f, -0.233f, 0.492f, -0.4f, 0.775f, -0.5f) reflectiveCurveToRelative(0.633f, -0.15f, 1.05f, -0.15f) curveToRelative(0.683f, 0f, 1.188f, 0.1f, 1.513f, 0.3f) reflectiveCurveToRelative(0.488f, 0.433f, 0.488f, 0.7f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(21.85f, 5.638f) curveToRelative(-0.1f, -0.242f, -0.25f, -0.454f, -0.45f, -0.638f) lineToRelative(-1.4f, -1.4f) curveToRelative(-0.183f, -0.2f, -0.396f, -0.35f, -0.638f, -0.45f) curveToRelative(-0.242f, -0.1f, -0.496f, -0.15f, -0.763f, -0.15f) curveToRelative(-0.25f, 0f, -0.5f, 0.05f, -0.75f, 0.15f) curveToRelative(-0.25f, 0.1f, -0.475f, 0.25f, -0.675f, 0.45f) lineToRelative(-12.6f, 12.575f) curveToRelative(-0.183f, 0.183f, -0.325f, 0.396f, -0.425f, 0.638f) curveToRelative(-0.1f, 0.242f, -0.15f, 0.496f, -0.15f, 0.763f) verticalLineToRelative(2.425f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) curveToRelative(0.192f, 0.192f, 0.429f, 0.288f, 0.713f, 0.288f) horizontalLineToRelative(2.425f) curveToRelative(0.267f, 0f, 0.521f, -0.05f, 0.763f, -0.15f) curveToRelative(0.242f, -0.1f, 0.454f, -0.242f, 0.638f, -0.425f) lineToRelative(12.575f, -12.6f) curveToRelative(0.2f, -0.2f, 0.35f, -0.425f, 0.45f, -0.675f) curveToRelative(0.1f, -0.25f, 0.15f, -0.5f, 0.15f, -0.75f) curveToRelative(0f, -0.267f, -0.05f, -0.521f, -0.15f, -0.762f) close() moveTo(7.4f, 19f) horizontalLineToRelative(-1.4f) verticalLineToRelative(-1.4f) lineTo(15.8f, 7.775f) lineToRelative(0.725f, 0.7f) lineToRelative(0.7f, 0.725f) lineToRelative(-9.825f, 9.8f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EditAlt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.EditAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.EditAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path( fill = SolidColor(Color(0xFF000000)) ) { moveTo(200f, 760f) horizontalLineToRelative(57f) lineToRelative(391f, -391f) lineToRelative(-57f, -57f) lineToRelative(-391f, 391f) verticalLineToRelative(57f) close() moveTo(160f, 840f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(120f, 800f) verticalLineToRelative(-97f) quadToRelative(0f, -16f, 6f, -30.5f) reflectiveQuadToRelative(17f, -25.5f) lineToRelative(505f, -504f) quadToRelative(12f, -11f, 26.5f, -17f) reflectiveQuadToRelative(30.5f, -6f) quadToRelative(16f, 0f, 31f, 6f) reflectiveQuadToRelative(26f, 18f) lineToRelative(55f, 56f) quadToRelative(12f, 11f, 17.5f, 26f) reflectiveQuadToRelative(5.5f, 30f) quadToRelative(0f, 16f, -5.5f, 30.5f) reflectiveQuadTo(817f, 313f) lineTo(313f, 817f) quadToRelative(-11f, 11f, -25.5f, 17f) reflectiveQuadToRelative(-30.5f, 6f) horizontalLineToRelative(-97f) close() moveTo(760f, 256f) lineTo(704f, 200f) lineTo(760f, 256f) close() moveTo(619f, 341f) lineTo(591f, 312f) lineTo(648f, 369f) lineTo(619f, 341f) close() } }.build() } val Icons.Filled.EditAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Filled.EditAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(160f, 840f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(120f, 800f) verticalLineToRelative(-97f) quadToRelative(0f, -16f, 6f, -30.5f) reflectiveQuadToRelative(17f, -25.5f) lineToRelative(505f, -504f) quadToRelative(12f, -11f, 26.5f, -17f) reflectiveQuadToRelative(30.5f, -6f) quadToRelative(16f, 0f, 31f, 6f) reflectiveQuadToRelative(26f, 18f) lineToRelative(55f, 56f) quadToRelative(12f, 11f, 17.5f, 26f) reflectiveQuadToRelative(5.5f, 30f) quadToRelative(0f, 16f, -5.5f, 30.5f) reflectiveQuadTo(817f, 313f) lineTo(313f, 817f) quadToRelative(-11f, 11f, -25.5f, 17f) reflectiveQuadToRelative(-30.5f, 6f) horizontalLineToRelative(-97f) close() moveTo(704f, 312f) lineTo(760f, 256f) lineTo(704f, 200f) lineTo(648f, 256f) lineTo(704f, 312f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiMultiple.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.EmojiMultiple: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.EmojiMultiple", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(13.747f, 6.657f) curveToRelative(-1.458f, -1.458f, -3.229f, -2.188f, -5.313f, -2.188f) reflectiveCurveToRelative(-3.854f, 0.729f, -5.313f, 2.188f) reflectiveCurveTo(0.935f, 9.886f, 0.935f, 11.969f) reflectiveCurveToRelative(0.729f, 3.854f, 2.188f, 5.313f) reflectiveCurveToRelative(3.229f, 2.188f, 5.313f, 2.188f) reflectiveCurveToRelative(3.854f, -0.729f, 5.313f, -2.188f) reflectiveCurveToRelative(2.188f, -3.229f, 2.188f, -5.313f) reflectiveCurveToRelative(-0.729f, -3.854f, -2.188f, -5.313f) close() moveTo(12.322f, 15.857f) curveToRelative(-1.075f, 1.075f, -2.371f, 1.612f, -3.888f, 1.612f) reflectiveCurveToRelative(-2.813f, -0.537f, -3.888f, -1.612f) reflectiveCurveToRelative(-1.612f, -2.371f, -1.612f, -3.888f) reflectiveCurveToRelative(0.537f, -2.813f, 1.612f, -3.888f) reflectiveCurveToRelative(2.371f, -1.612f, 3.888f, -1.612f) reflectiveCurveToRelative(2.813f, 0.537f, 3.888f, 1.612f) reflectiveCurveToRelative(1.612f, 2.371f, 1.612f, 3.888f) reflectiveCurveToRelative(-0.537f, 2.813f, -1.612f, 3.888f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5.935f, 11.469f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8.435f, 15.869f) curveToRelative(0.8f, 0f, 1.513f, -0.225f, 2.138f, -0.675f) reflectiveCurveToRelative(1.079f, -1.025f, 1.362f, -1.725f) horizontalLineToRelative(-7f) curveToRelative(0.283f, 0.7f, 0.738f, 1.275f, 1.362f, 1.725f) reflectiveCurveToRelative(1.337f, 0.675f, 2.138f, 0.675f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10.935f, 11.469f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.878f, 6.718f) curveToRelative(-1.458f, -1.458f, -3.229f, -2.188f, -5.313f, -2.188f) curveToRelative(-0.626f, 0f, -1.221f, 0.074f, -1.791f, 0.206f) curveToRelative(0.689f, 0.51f, 1.302f, 1.114f, 1.821f, 1.797f) curveToRelative(1.503f, 0.007f, 2.79f, 0.542f, 3.857f, 1.61f) curveToRelative(1.075f, 1.075f, 1.612f, 2.371f, 1.612f, 3.888f) reflectiveCurveToRelative(-0.537f, 2.813f, -1.612f, 3.888f) reflectiveCurveToRelative(-2.371f, 1.612f, -3.888f, 1.612f) curveToRelative(-0.021f, 0f, -0.039f, -0.005f, -0.06f, -0.005f) curveToRelative(-0.531f, 0.675f, -1.155f, 1.27f, -1.855f, 1.769f) curveToRelative(0.607f, 0.152f, 1.243f, 0.236f, 1.915f, 0.236f) curveToRelative(2.083f, 0f, 3.854f, -0.729f, 5.313f, -2.188f) reflectiveCurveToRelative(2.188f, -3.229f, 2.188f, -5.313f) reflectiveCurveToRelative(-0.729f, -3.854f, -2.188f, -5.313f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/EmojiSticky.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.EmojiSticky: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.EmojiSticky", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(460f, 600f) quadToRelative(64f, 0f, 113.5f, -39.5f) reflectiveQuadTo(637f, 460f) quadToRelative(2f, -6f, -3f, -10.5f) reflectiveQuadToRelative(-11f, -2.5f) lineToRelative(-282f, 79f) quadToRelative(-8f, 2f, -9.5f, 9.5f) reflectiveQuadTo(335f, 548f) quadToRelative(25f, 24f, 57f, 38f) reflectiveQuadToRelative(68f, 14f) close() moveTo(294f, 450f) lineToRelative(106f, -30f) quadToRelative(4f, -28f, -14f, -49f) reflectiveQuadToRelative(-46f, -21f) quadToRelative(-25f, 0f, -42.5f, 17.5f) reflectiveQuadTo(280f, 410f) quadToRelative(0f, 11f, 4f, 21f) reflectiveQuadToRelative(10f, 19f) close() moveTo(534f, 380f) lineTo(640f, 350f) quadToRelative(5f, -28f, -13.5f, -49f) reflectiveQuadTo(580f, 280f) quadToRelative(-25f, 0f, -42.5f, 17.5f) reflectiveQuadTo(520f, 340f) quadToRelative(0f, 11f, 4f, 21f) reflectiveQuadToRelative(10f, 19f) close() moveTo(200f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(120f, 760f) verticalLineToRelative(-560f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(200f, 120f) horizontalLineToRelative(560f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(840f, 200f) verticalLineToRelative(407f) quadToRelative(0f, 16f, -6f, 30.5f) reflectiveQuadTo(817f, 663f) lineTo(663f, 817f) quadToRelative(-11f, 11f, -25.5f, 17f) reflectiveQuadToRelative(-30.5f, 6f) lineTo(200f, 840f) close() moveTo(600f, 760f) verticalLineToRelative(-80f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(680f, 600f) horizontalLineToRelative(80f) verticalLineToRelative(-400f) lineTo(200f, 200f) verticalLineToRelative(560f) horizontalLineToRelative(400f) close() moveTo(600f, 760f) close() moveTo(200f, 760f) verticalLineToRelative(-560f) verticalLineToRelative(560f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Encrypted.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Encrypted: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Encrypted", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.1f, 15f) horizontalLineToRelative(1.8f) curveToRelative(0.15f, 0f, 0.279f, -0.063f, 0.387f, -0.188f) reflectiveCurveToRelative(0.146f, -0.262f, 0.112f, -0.412f) lineToRelative(-0.475f, -2.625f) curveToRelative(0.333f, -0.167f, 0.596f, -0.408f, 0.788f, -0.725f) curveToRelative(0.192f, -0.317f, 0.287f, -0.667f, 0.287f, -1.05f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.587f, -1.413f) reflectiveCurveToRelative(-0.863f, -0.587f, -1.413f, -0.587f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.413f, 0.587f) reflectiveCurveToRelative(-0.587f, 0.863f, -0.587f, 1.413f) curveToRelative(0f, 0.383f, 0.096f, 0.733f, 0.287f, 1.05f) curveToRelative(0.192f, 0.317f, 0.454f, 0.558f, 0.788f, 0.725f) lineToRelative(-0.475f, 2.625f) curveToRelative(-0.033f, 0.15f, 0.004f, 0.287f, 0.112f, 0.412f) reflectiveCurveToRelative(0.237f, 0.188f, 0.387f, 0.188f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.638f, 5.25f) curveToRelative(-0.242f, -0.333f, -0.554f, -0.575f, -0.938f, -0.725f) lineToRelative(-6f, -2.25f) curveToRelative(-0.233f, -0.083f, -0.467f, -0.125f, -0.7f, -0.125f) reflectiveCurveToRelative(-0.467f, 0.042f, -0.7f, 0.125f) lineToRelative(-6f, 2.25f) curveToRelative(-0.383f, 0.15f, -0.696f, 0.392f, -0.938f, 0.725f) curveToRelative(-0.242f, 0.333f, -0.362f, 0.708f, -0.362f, 1.125f) verticalLineToRelative(4.725f) curveToRelative(0f, 2.333f, 0.667f, 4.513f, 2f, 6.538f) curveToRelative(1.333f, 2.025f, 3.125f, 3.412f, 5.375f, 4.162f) curveToRelative(0.1f, 0.033f, 0.2f, 0.058f, 0.3f, 0.075f) curveToRelative(0.1f, 0.017f, 0.208f, 0.025f, 0.325f, 0.025f) reflectiveCurveToRelative(0.225f, -0.008f, 0.325f, -0.025f) curveToRelative(0.1f, -0.017f, 0.2f, -0.042f, 0.3f, -0.075f) curveToRelative(2.25f, -0.75f, 4.042f, -2.138f, 5.375f, -4.162f) curveToRelative(1.333f, -2.025f, 2f, -4.204f, 2f, -6.538f) verticalLineToRelative(-4.725f) curveToRelative(0f, -0.417f, -0.121f, -0.792f, -0.362f, -1.125f) close() moveTo(18f, 11.1f) curveToRelative(0f, 2.017f, -0.567f, 3.85f, -1.7f, 5.5f) curveToRelative(-1.133f, 1.65f, -2.567f, 2.75f, -4.3f, 3.3f) curveToRelative(-1.733f, -0.55f, -3.167f, -1.65f, -4.3f, -3.3f) curveToRelative(-1.133f, -1.65f, -1.7f, -3.483f, -1.7f, -5.5f) verticalLineToRelative(-4.725f) lineToRelative(6f, -2.25f) lineToRelative(6f, 2.25f) verticalLineToRelative(4.725f) close() } }.build() } val Icons.TwoTone.Encrypted: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Encrypted", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.1f, 15f) horizontalLineToRelative(1.8f) curveToRelative(0.15f, 0f, 0.279f, -0.063f, 0.387f, -0.188f) reflectiveCurveToRelative(0.146f, -0.262f, 0.112f, -0.412f) lineToRelative(-0.475f, -2.625f) curveToRelative(0.333f, -0.167f, 0.596f, -0.408f, 0.788f, -0.725f) curveToRelative(0.192f, -0.317f, 0.287f, -0.667f, 0.287f, -1.05f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.587f, -1.413f) reflectiveCurveToRelative(-0.863f, -0.587f, -1.413f, -0.587f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.413f, 0.587f) reflectiveCurveToRelative(-0.587f, 0.863f, -0.587f, 1.413f) curveToRelative(0f, 0.383f, 0.096f, 0.733f, 0.287f, 1.05f) curveToRelative(0.192f, 0.317f, 0.454f, 0.558f, 0.788f, 0.725f) lineToRelative(-0.475f, 2.625f) curveToRelative(-0.033f, 0.15f, 0.004f, 0.287f, 0.112f, 0.412f) reflectiveCurveToRelative(0.237f, 0.188f, 0.387f, 0.188f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.638f, 5.25f) curveToRelative(-0.242f, -0.333f, -0.554f, -0.575f, -0.938f, -0.725f) lineToRelative(-6f, -2.25f) curveToRelative(-0.233f, -0.083f, -0.467f, -0.125f, -0.7f, -0.125f) reflectiveCurveToRelative(-0.467f, 0.042f, -0.7f, 0.125f) lineToRelative(-6f, 2.25f) curveToRelative(-0.383f, 0.15f, -0.696f, 0.392f, -0.938f, 0.725f) curveToRelative(-0.242f, 0.333f, -0.362f, 0.708f, -0.362f, 1.125f) verticalLineToRelative(4.725f) curveToRelative(0f, 2.333f, 0.667f, 4.513f, 2f, 6.538f) curveToRelative(1.333f, 2.025f, 3.125f, 3.412f, 5.375f, 4.162f) curveToRelative(0.1f, 0.033f, 0.2f, 0.058f, 0.3f, 0.075f) curveToRelative(0.1f, 0.017f, 0.208f, 0.025f, 0.325f, 0.025f) reflectiveCurveToRelative(0.225f, -0.008f, 0.325f, -0.025f) curveToRelative(0.1f, -0.017f, 0.2f, -0.042f, 0.3f, -0.075f) curveToRelative(2.25f, -0.75f, 4.042f, -2.138f, 5.375f, -4.162f) curveToRelative(1.333f, -2.025f, 2f, -4.204f, 2f, -6.538f) verticalLineToRelative(-4.725f) curveToRelative(0f, -0.417f, -0.121f, -0.792f, -0.362f, -1.125f) close() moveTo(18f, 11.1f) curveToRelative(0f, 2.017f, -0.567f, 3.85f, -1.7f, 5.5f) curveToRelative(-1.133f, 1.65f, -2.567f, 2.75f, -4.3f, 3.3f) curveToRelative(-1.733f, -0.55f, -3.167f, -1.65f, -4.3f, -3.3f) curveToRelative(-1.133f, -1.65f, -1.7f, -3.483f, -1.7f, -5.5f) verticalLineToRelative(-4.725f) lineToRelative(6f, -2.25f) lineToRelative(6f, 2.25f) verticalLineToRelative(4.725f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 19.888f) curveToRelative(1.733f, -0.55f, 3.167f, -1.65f, 4.3f, -3.3f) reflectiveCurveToRelative(1.7f, -3.483f, 1.7f, -5.5f) verticalLineToRelative(-4.725f) lineToRelative(-6f, -2.25f) lineToRelative(-6f, 2.25f) verticalLineToRelative(4.725f) curveToRelative(0f, 2.017f, 0.567f, 3.85f, 1.7f, 5.5f) reflectiveCurveToRelative(2.567f, 2.75f, 4.3f, 3.3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eraser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Eraser: ImageVector by lazy { Builder( name = "Eraser", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.24f, 3.56f) lineTo(21.19f, 8.5f) curveTo(21.97f, 9.29f, 21.97f, 10.55f, 21.19f, 11.34f) lineTo(12.0f, 20.53f) curveTo(10.44f, 22.09f, 7.91f, 22.09f, 6.34f, 20.53f) lineTo(2.81f, 17.0f) curveTo(2.03f, 16.21f, 2.03f, 14.95f, 2.81f, 14.16f) lineTo(13.41f, 3.56f) curveTo(14.2f, 2.78f, 15.46f, 2.78f, 16.24f, 3.56f) moveTo(4.22f, 15.58f) lineTo(7.76f, 19.11f) curveTo(8.54f, 19.9f, 9.8f, 19.9f, 10.59f, 19.11f) lineTo(14.12f, 15.58f) lineTo(9.17f, 10.63f) lineTo(4.22f, 15.58f) close() } }.build() } val Icons.TwoTone.Eraser: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "TwoTone.Eraser", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(4.22f, 15.58f) lineToRelative(3.54f, 3.53f) curveToRelative(0.78f, 0.79f, 2.04f, 0.79f, 2.83f, 0f) lineToRelative(3.53f, -3.53f) lineToRelative(-4.95f, -4.95f) lineToRelative(-4.95f, 4.95f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(21.19f, 8.5f) lineToRelative(-4.95f, -4.94f) curveToRelative(-0.78f, -0.78f, -2.04f, -0.78f, -2.83f, 0f) lineTo(2.81f, 14.16f) curveToRelative(-0.78f, 0.79f, -0.78f, 2.05f, 0f, 2.84f) lineToRelative(3.53f, 3.53f) curveToRelative(1.57f, 1.56f, 4.1f, 1.56f, 5.66f, 0f) lineToRelative(9.19f, -9.19f) curveToRelative(0.78f, -0.79f, 0.78f, -2.05f, 0f, -2.84f) close() moveTo(10.59f, 19.11f) curveToRelative(-0.79f, 0.79f, -2.05f, 0.79f, -2.83f, 0f) lineToRelative(-3.54f, -3.53f) lineToRelative(4.95f, -4.95f) lineToRelative(4.95f, 4.95f) lineToRelative(-3.53f, 3.53f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exercise.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Exercise: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Exercise", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(826f, 375f) lineTo(770f, 319f) lineTo(800f, 288f) quadTo(800f, 288f, 800f, 288f) quadTo(800f, 288f, 800f, 288f) lineTo(672f, 160f) quadTo(672f, 160f, 672f, 160f) quadTo(672f, 160f, 672f, 160f) lineTo(641f, 190f) lineTo(584f, 133f) lineTo(614f, 102f) quadTo(637f, 79f, 671f, 79.5f) quadTo(705f, 80f, 728f, 103f) lineTo(857f, 232f) quadTo(880f, 255f, 880f, 288.5f) quadTo(880f, 322f, 857f, 345f) lineTo(826f, 375f) close() moveTo(346f, 856f) quadTo(323f, 879f, 289.5f, 879f) quadTo(256f, 879f, 233f, 856f) lineTo(104f, 727f) quadTo(81f, 704f, 81f, 670.5f) quadTo(81f, 637f, 104f, 614f) lineTo(134f, 584f) lineTo(191f, 641f) lineTo(160f, 671f) quadTo(160f, 671f, 160f, 671f) quadTo(160f, 671f, 160f, 671f) lineTo(289f, 800f) quadTo(289f, 800f, 289f, 800f) quadTo(289f, 800f, 289f, 800f) lineTo(319f, 769f) lineTo(376f, 826f) lineTo(346f, 856f) close() moveTo(743f, 520f) lineTo(800f, 463f) quadTo(800f, 463f, 800f, 463f) quadTo(800f, 463f, 800f, 463f) lineTo(497f, 160f) quadTo(497f, 160f, 497f, 160f) quadTo(497f, 160f, 497f, 160f) lineTo(440f, 217f) quadTo(440f, 217f, 440f, 217f) quadTo(440f, 217f, 440f, 217f) lineTo(743f, 520f) quadTo(743f, 520f, 743f, 520f) quadTo(743f, 520f, 743f, 520f) close() moveTo(463f, 800f) lineTo(520f, 742f) quadTo(520f, 742f, 520f, 742f) quadTo(520f, 742f, 520f, 742f) lineTo(218f, 440f) quadTo(218f, 440f, 218f, 440f) quadTo(218f, 440f, 218f, 440f) lineTo(160f, 497f) quadTo(160f, 497f, 160f, 497f) quadTo(160f, 497f, 160f, 497f) lineTo(463f, 800f) quadTo(463f, 800f, 463f, 800f) quadTo(463f, 800f, 463f, 800f) close() moveTo(457f, 566f) lineTo(567f, 457f) lineTo(503f, 393f) lineTo(394f, 503f) lineTo(457f, 566f) close() moveTo(520f, 856f) quadTo(497f, 879f, 463f, 879f) quadTo(429f, 879f, 406f, 856f) lineTo(104f, 554f) quadTo(81f, 531f, 81f, 497f) quadTo(81f, 463f, 104f, 440f) lineTo(161f, 383f) quadTo(184f, 360f, 217.5f, 360f) quadTo(251f, 360f, 274f, 383f) lineTo(337f, 446f) lineTo(447f, 336f) lineTo(384f, 274f) quadTo(361f, 251f, 361f, 217f) quadTo(361f, 183f, 384f, 160f) lineTo(441f, 103f) quadTo(464f, 80f, 497.5f, 80f) quadTo(531f, 80f, 554f, 103f) lineTo(857f, 406f) quadTo(880f, 429f, 880f, 462.5f) quadTo(880f, 496f, 857f, 519f) lineTo(800f, 576f) quadTo(777f, 599f, 743f, 599f) quadTo(709f, 599f, 686f, 576f) lineTo(624f, 513f) lineTo(514f, 623f) lineTo(577f, 686f) quadTo(600f, 709f, 600f, 742.5f) quadTo(600f, 776f, 577f, 799f) lineTo(520f, 856f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Exif.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Exif: ImageVector by lazy { Builder( name = "Exif", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(22.37f, 11.58f) lineToRelative(-9.45f, -9.45f) curveTo(12.5f, 1.71f, 11.975f, 1.5f, 11.45f, 1.5f) horizontalLineTo(4.1f) curveTo(2.945f, 1.5f, 2.0f, 2.445f, 2.0f, 3.6f) verticalLineToRelative(7.35f) curveTo(2.0f, 11.475f, 2.21f, 12.0f, 2.63f, 12.42f) lineToRelative(9.45f, 9.45f) curveTo(12.5f, 22.29f, 13.025f, 22.5f, 13.55f, 22.5f) curveToRelative(0.525f, 0.0f, 1.05f, -0.21f, 1.47f, -0.63f) lineToRelative(7.35f, -7.35f) curveTo(22.79f, 14.1f, 23.0f, 13.575f, 23.0f, 13.05f) curveTo(23.0f, 12.525f, 22.79f, 12.0f, 22.37f, 11.58f) close() moveTo(13.55f, 20.4f) lineToRelative(-9.45f, -9.45f) verticalLineTo(3.6f) horizontalLineToRelative(7.35f) lineToRelative(9.45f, 9.45f) lineTo(13.55f, 20.4f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.4701f, 4.65f) curveToRelative(0.704f, 0.0f, 1.3201f, 0.616f, 1.3201f, 1.3201f) reflectiveCurveTo(7.1741f, 7.2901f, 6.4701f, 7.2901f) reflectiveCurveTo(5.15f, 6.6741f, 5.15f, 5.9701f) reflectiveCurveTo(5.766f, 4.65f, 6.4701f, 4.65f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.233f, 14.8042f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(3.0977f, -3.0977f) lineToRelative(0.7744f, 0.7744f) lineToRelative(-3.0977f, 3.0977f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.8796f, 14.8042f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(-0.5163f, 0.5163f) lineToRelative(0.7744f, 0.7744f) lineToRelative(-0.7744f, 0.7744f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(-1.0326f, 1.0326f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(3.0977f, -3.0977f) lineToRelative(1.5489f, 1.5489f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(10.231f, 8.1556f) lineToRelative(0.7744f, 0.7744f) lineToRelative(0.7744f, -0.7744f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(-1.2159f, 1.2159f) lineToRelative(-0.666f, 0.666f) lineToRelative(-1.2159f, 1.2159f) lineToRelative(0.7744f, 0.7744f) lineToRelative(0.7744f, 0.7744f) lineToRelative(0.7744f, -0.7744f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(0.2581f, -0.2581f) lineToRelative(0.1833f, -0.1833f) lineToRelative(0.7744f, 0.7744f) lineToRelative(0.666f, -0.666f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(0.1833f, -0.1833f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.2426f, 8.5991f) lineToRelative(-0.9351f, 2.1819f) lineToRelative(-2.1819f, 0.9351f) lineToRelative(0.6234f, 0.6234f) lineToRelative(1.0909f, -0.4675f) lineToRelative(-0.4675f, 1.0909f) lineToRelative(0.6234f, 0.6234f) lineToRelative(0.9351f, -2.1819f) lineToRelative(2.1819f, -0.9351f) lineToRelative(-0.6234f, -0.6234f) lineToRelative(-1.0909f, 0.4675f) lineToRelative(0.4675f, -1.0909f) lineTo(12.2426f, 8.5991f) close() } }.build() } val Icons.Rounded.Exif: ImageVector by lazy { Builder( name = "Exif Rounded", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(22.3701f, 11.58f) lineToRelative(-9.45f, -9.45f) curveTo(12.5f, 1.71f, 11.975f, 1.5f, 11.45f, 1.5f) horizontalLineTo(4.1f) curveTo(2.945f, 1.5f, 2.0f, 2.445f, 2.0f, 3.6f) verticalLineToRelative(7.35f) curveTo(2.0f, 11.475f, 2.21f, 12.0f, 2.63f, 12.42f) lineToRelative(9.45f, 9.45f) curveTo(12.5f, 22.29f, 13.025f, 22.5f, 13.55f, 22.5f) curveToRelative(0.525f, 0.0f, 1.05f, -0.21f, 1.47f, -0.63f) lineToRelative(7.35f, -7.35f) curveTo(22.79f, 14.1f, 23.0f, 13.575f, 23.0f, 13.05f) curveTo(23.0f, 12.525f, 22.79f, 12.0f, 22.3701f, 11.58f) close() moveTo(5.15f, 5.9701f) curveToRelative(0.0f, -0.704f, 0.616f, -1.3201f, 1.3201f, -1.3201f) curveToRelative(0.704f, 0.0f, 1.3201f, 0.616f, 1.3201f, 1.3201f) curveToRelative(0.0f, 0.704f, -0.6161f, 1.3201f, -1.3201f, 1.3201f) curveTo(5.7661f, 7.2902f, 5.15f, 6.6741f, 5.15f, 5.9701f) close() moveTo(7.9077f, 10.4789f) lineTo(7.1332f, 9.7044f) lineToRelative(1.2159f, -1.2159f) lineToRelative(0.666f, -0.666f) lineToRelative(1.2159f, -1.2159f) lineToRelative(0.7744f, 0.7744f) lineToRelative(0.7745f, 0.7745f) lineToRelative(-0.7745f, 0.7744f) lineTo(10.231f, 8.1556f) lineTo(9.9728f, 8.4137f) lineTo(9.7895f, 8.597f) lineTo(10.564f, 9.3715f) lineToRelative(-0.666f, 0.666f) lineTo(9.1235f, 9.263f) lineTo(8.9402f, 9.4463f) lineTo(8.6821f, 9.7044f) lineToRelative(0.7745f, 0.7745f) lineToRelative(-0.7745f, 0.7744f) lineTo(7.9077f, 10.4789f) close() moveTo(10.3724f, 12.9628f) lineToRelative(0.4676f, -1.0909f) lineToRelative(-1.0909f, 0.4675f) lineToRelative(-0.6234f, -0.6234f) lineToRelative(2.1819f, -0.9351f) lineToRelative(0.9351f, -2.1819f) lineToRelative(0.6234f, 0.6234f) lineToRelative(-0.4675f, 1.0909f) lineToRelative(1.0909f, -0.4675f) lineToRelative(0.6234f, 0.6234f) lineToRelative(-2.1819f, 0.9351f) lineToRelative(-0.9351f, 2.1819f) lineTo(10.3724f, 12.9628f) close() moveTo(11.4585f, 14.0297f) lineToRelative(3.0978f, -3.0977f) lineToRelative(0.7744f, 0.7744f) lineToRelative(-3.0977f, 3.0977f) lineTo(11.4585f, 14.0297f) close() moveTo(16.8796f, 14.8041f) lineToRelative(-0.7744f, -0.7744f) lineToRelative(-0.5163f, 0.5163f) lineToRelative(0.7744f, 0.7744f) lineToRelative(-0.7744f, 0.7745f) lineToRelative(-0.7745f, -0.7745f) lineToRelative(-1.0325f, 1.0326f) lineToRelative(-0.7745f, -0.7744f) lineToRelative(3.0978f, -3.0978f) lineToRelative(1.5488f, 1.5489f) lineTo(16.8796f, 14.8041f) close() } }.build() } val Icons.TwoTone.Exif: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "TwoTone.Exif", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(22.37f, 11.58f) lineTo(12.92f, 2.13f) curveToRelative(-0.42f, -0.42f, -0.945f, -0.63f, -1.47f, -0.63f) horizontalLineToRelative(-7.35f) curveToRelative(-1.155f, 0f, -2.1f, 0.945f, -2.1f, 2.1f) verticalLineToRelative(7.35f) curveToRelative(0f, 0.525f, 0.21f, 1.05f, 0.63f, 1.47f) lineToRelative(9.45f, 9.45f) curveToRelative(0.42f, 0.42f, 0.945f, 0.63f, 1.47f, 0.63f) curveToRelative(0.525f, 0f, 1.05f, -0.21f, 1.47f, -0.63f) lineToRelative(7.35f, -7.35f) curveToRelative(0.42f, -0.42f, 0.63f, -0.945f, 0.63f, -1.47f) reflectiveCurveToRelative(-0.21f, -1.05f, -0.63f, -1.47f) close() moveTo(13.55f, 20.4f) lineTo(4.1f, 10.95f) verticalLineTo(3.6f) horizontalLineToRelative(7.35f) lineToRelative(9.45f, 9.45f) lineToRelative(-7.35f, 7.35f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(13.55f, 20.4f) lineToRelative(-9.45f, -9.45f) lineToRelative(0f, -7.35f) lineToRelative(7.35f, 0f) lineToRelative(9.45f, 9.45f) lineToRelative(-7.35f, 7.35f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(6.47f, 4.65f) curveToRelative(0.704f, 0f, 1.32f, 0.616f, 1.32f, 1.32f) reflectiveCurveToRelative(-0.616f, 1.32f, -1.32f, 1.32f) reflectiveCurveToRelative(-1.32f, -0.616f, -1.32f, -1.32f) reflectiveCurveToRelative(0.616f, -1.32f, 1.32f, -1.32f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12.233f, 14.804f) lineToRelative(-0.774f, -0.774f) lineToRelative(3.098f, -3.098f) lineToRelative(0.774f, 0.774f) lineToRelative(-3.098f, 3.098f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(16.88f, 14.804f) lineToRelative(-0.774f, -0.774f) lineToRelative(-0.516f, 0.516f) lineToRelative(0.774f, 0.774f) lineToRelative(-0.774f, 0.774f) lineToRelative(-0.774f, -0.774f) lineToRelative(-1.033f, 1.033f) lineToRelative(-0.774f, -0.774f) lineToRelative(3.098f, -3.098f) lineToRelative(1.549f, 1.549f) lineToRelative(-0.775f, 0.774f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(10.231f, 8.156f) lineToRelative(0.774f, 0.774f) lineToRelative(0.774f, -0.774f) lineToRelative(-0.774f, -0.774f) lineToRelative(-0.774f, -0.774f) lineToRelative(-1.216f, 1.216f) lineToRelative(-0.666f, 0.666f) lineToRelative(-1.216f, 1.216f) lineToRelative(0.774f, 0.774f) lineToRelative(0.774f, 0.774f) lineToRelative(0.774f, -0.774f) lineToRelative(-0.774f, -0.774f) lineToRelative(0.258f, -0.258f) lineToRelative(0.183f, -0.183f) lineToRelative(0.774f, 0.774f) lineToRelative(0.666f, -0.666f) lineToRelative(-0.774f, -0.774f) lineToRelative(0.183f, -0.183f) lineToRelative(0.258f, -0.258f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12.243f, 8.599f) lineToRelative(-0.935f, 2.182f) lineToRelative(-2.182f, 0.935f) lineToRelative(0.623f, 0.623f) lineToRelative(1.091f, -0.467f) lineToRelative(-0.467f, 1.091f) lineToRelative(0.623f, 0.623f) lineToRelative(0.935f, -2.182f) lineToRelative(2.182f, -0.935f) lineToRelative(-0.623f, -0.623f) lineToRelative(-1.091f, 0.467f) lineToRelative(0.467f, -1.091f) lineToRelative(-0.623f, -0.623f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ExifEdit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ExifEdit: ImageVector by lazy { ImageVector.Builder( name = "Outlined.ExifEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(17.138f, 9.874f) lineToRelative(-7.023f, -7.023f) curveTo(9.803f, 2.539f, 9.413f, 2.383f, 9.023f, 2.383f) horizontalLineTo(3.561f) curveTo(2.702f, 2.383f, 2f, 3.085f, 2f, 3.944f) verticalLineToRelative(5.462f) curveToRelative(0f, 0.39f, 0.156f, 0.78f, 0.468f, 1.092f) lineToRelative(7.023f, 7.023f) curveToRelative(0.312f, 0.312f, 0.702f, 0.468f, 1.092f, 0.468f) reflectiveCurveToRelative(0.78f, -0.156f, 1.092f, -0.468f) lineToRelative(5.462f, -5.462f) curveToRelative(0.312f, -0.312f, 0.468f, -0.702f, 0.468f, -1.092f) curveTo(17.606f, 10.576f, 17.45f, 10.186f, 17.138f, 9.874f) close() moveTo(10.584f, 16.429f) lineToRelative(-7.023f, -7.023f) verticalLineTo(3.944f) horizontalLineTo(9.023f) lineToRelative(7.023f, 7.023f) lineTo(10.584f, 16.429f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(5.322f, 4.724f) curveToRelative(0.523f, 0f, 0.981f, 0.458f, 0.981f, 0.981f) reflectiveCurveTo(5.845f, 6.686f, 5.322f, 6.686f) reflectiveCurveTo(4.341f, 6.228f, 4.341f, 5.705f) reflectiveCurveTo(4.799f, 4.724f, 5.322f, 4.724f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(9.605f, 12.27f) lineToRelative(-0.576f, -0.576f) lineToRelative(2.302f, -2.302f) lineToRelative(0.576f, 0.576f) lineToRelative(-2.302f, 2.302f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(13.058f, 12.27f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.384f, 0.384f) lineToRelative(0.576f, 0.576f) lineToRelative(-0.576f, 0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.767f, 0.767f) lineToRelative(-0.576f, -0.576f) lineToRelative(2.302f, -2.302f) lineToRelative(1.151f, 1.151f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(8.117f, 7.329f) lineToRelative(0.576f, 0.576f) lineToRelative(0.576f, -0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.904f, 0.904f) lineToRelative(-0.495f, 0.495f) lineToRelative(-0.904f, 0.904f) lineToRelative(0.576f, 0.576f) lineToRelative(0.576f, 0.576f) lineToRelative(0.576f, -0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(0.192f, -0.192f) lineToRelative(0.136f, -0.136f) lineToRelative(0.576f, 0.576f) lineToRelative(0.495f, -0.495f) lineToRelative(-0.576f, -0.576f) lineToRelative(0.136f, -0.136f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(9.612f, 7.659f) lineTo(8.917f, 9.28f) lineTo(7.295f, 9.975f) lineToRelative(0.463f, 0.463f) lineToRelative(0.811f, -0.347f) lineToRelative(-0.347f, 0.811f) lineToRelative(0.463f, 0.463f) lineToRelative(0.695f, -1.621f) lineToRelative(1.621f, -0.695f) lineToRelative(-0.463f, -0.463f) lineTo(9.728f, 8.933f) lineToRelative(0.347f, -0.811f) lineTo(9.612f, 7.659f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(21.886f, 14.399f) curveToRelative(-0.076f, -0.186f, -0.182f, -0.355f, -0.317f, -0.507f) lineToRelative(-0.937f, -0.937f) curveToRelative(-0.152f, -0.152f, -0.321f, -0.266f, -0.507f, -0.342f) curveTo(19.94f, 12.538f, 19.746f, 12.5f, 19.543f, 12.5f) curveToRelative(-0.186f, 0f, -0.371f, 0.034f, -0.557f, 0.101f) curveToRelative(-0.186f, 0.068f, -0.355f, 0.177f, -0.507f, 0.329f) lineToRelative(-5.293f, 5.268f) curveToRelative(-0.101f, 0.101f, -0.177f, 0.215f, -0.228f, 0.342f) reflectiveCurveToRelative(-0.076f, 0.258f, -0.076f, 0.393f) verticalLineToRelative(1.671f) curveToRelative(0f, 0.287f, 0.097f, 0.528f, 0.291f, 0.722f) curveToRelative(0.194f, 0.194f, 0.435f, 0.291f, 0.722f, 0.291f) horizontalLineToRelative(1.671f) curveToRelative(0.135f, 0f, 0.266f, -0.025f, 0.392f, -0.076f) curveToRelative(0.127f, -0.051f, 0.241f, -0.127f, 0.342f, -0.228f) lineToRelative(5.268f, -5.268f) curveToRelative(0.152f, -0.152f, 0.262f, -0.325f, 0.329f, -0.519f) curveTo(21.966f, 15.332f, 22f, 15.142f, 22f, 14.957f) reflectiveCurveTo(21.962f, 14.585f, 21.886f, 14.399f) close() moveTo(15.365f, 20.098f) horizontalLineTo(14.402f) verticalLineToRelative(-0.962f) lineToRelative(3.09f, -3.064f) lineToRelative(0.481f, 0.456f) lineToRelative(0.456f, 0.481f) lineTo(15.365f, 20.098f) close() } }.build() } val Icons.TwoTone.ExifEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ExifEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(10.584f, 16.429f) lineToRelative(-7.023f, -7.023f) lineToRelative(0f, -5.462f) lineToRelative(5.462f, 0f) lineToRelative(7.023f, 7.023f) lineToRelative(-5.462f, 5.462f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(5.322f, 4.724f) curveToRelative(0.523f, 0f, 0.981f, 0.458f, 0.981f, 0.981f) reflectiveCurveToRelative(-0.458f, 0.981f, -0.981f, 0.981f) reflectiveCurveToRelative(-0.981f, -0.458f, -0.981f, -0.981f) reflectiveCurveToRelative(0.458f, -0.981f, 0.981f, -0.981f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(9.605f, 12.27f) lineToRelative(-0.576f, -0.576f) lineToRelative(2.302f, -2.302f) lineToRelative(0.576f, 0.576f) lineToRelative(-2.302f, 2.302f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(13.058f, 12.27f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.384f, 0.384f) lineToRelative(0.576f, 0.576f) lineToRelative(-0.576f, 0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.767f, 0.767f) lineToRelative(-0.576f, -0.576f) lineToRelative(2.302f, -2.302f) lineToRelative(1.151f, 1.151f) lineToRelative(-0.574f, 0.576f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(8.117f, 7.329f) lineToRelative(0.576f, 0.576f) lineToRelative(0.576f, -0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(-0.904f, 0.904f) lineToRelative(-0.495f, 0.495f) lineToRelative(-0.904f, 0.904f) lineToRelative(0.576f, 0.576f) lineToRelative(0.576f, 0.576f) lineToRelative(0.576f, -0.576f) lineToRelative(-0.576f, -0.576f) lineToRelative(0.192f, -0.192f) lineToRelative(0.136f, -0.136f) lineToRelative(0.576f, 0.576f) lineToRelative(0.495f, -0.495f) lineToRelative(-0.576f, -0.576f) lineToRelative(0.136f, -0.136f) lineToRelative(0.192f, -0.192f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(9.612f, 7.659f) lineToRelative(-0.695f, 1.621f) lineToRelative(-1.622f, 0.695f) lineToRelative(0.463f, 0.463f) lineToRelative(0.811f, -0.347f) lineToRelative(-0.347f, 0.811f) lineToRelative(0.463f, 0.463f) lineToRelative(0.695f, -1.621f) lineToRelative(1.621f, -0.695f) lineToRelative(-0.463f, -0.463f) lineToRelative(-0.81f, 0.347f) lineToRelative(0.347f, -0.811f) lineToRelative(-0.463f, -0.463f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(15.365f, 20.098f) lineToRelative(-0.963f, 0f) lineToRelative(0f, -0.962f) lineToRelative(3.09f, -3.064f) lineToRelative(0.481f, 0.456f) lineToRelative(0.456f, 0.481f) lineToRelative(-3.064f, 3.089f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(21.886f, 14.399f) curveToRelative(-0.076f, -0.186f, -0.182f, -0.355f, -0.317f, -0.507f) lineToRelative(-0.937f, -0.937f) curveToRelative(-0.152f, -0.152f, -0.321f, -0.266f, -0.507f, -0.342f) curveToRelative(-0.185f, -0.075f, -0.379f, -0.113f, -0.582f, -0.113f) curveToRelative(-0.186f, 0f, -0.371f, 0.034f, -0.557f, 0.101f) curveToRelative(-0.186f, 0.068f, -0.355f, 0.177f, -0.507f, 0.329f) lineToRelative(-5.293f, 5.268f) curveToRelative(-0.101f, 0.101f, -0.177f, 0.215f, -0.228f, 0.342f) curveToRelative(-0.051f, 0.127f, -0.076f, 0.258f, -0.076f, 0.393f) verticalLineToRelative(1.671f) curveToRelative(0f, 0.287f, 0.097f, 0.528f, 0.291f, 0.722f) curveToRelative(0.194f, 0.194f, 0.435f, 0.291f, 0.722f, 0.291f) horizontalLineToRelative(1.671f) curveToRelative(0.135f, 0f, 0.266f, -0.025f, 0.392f, -0.076f) curveToRelative(0.127f, -0.051f, 0.241f, -0.127f, 0.342f, -0.228f) lineToRelative(5.268f, -5.268f) curveToRelative(0.152f, -0.152f, 0.262f, -0.325f, 0.329f, -0.519f) curveToRelative(0.069f, -0.194f, 0.103f, -0.384f, 0.103f, -0.569f) reflectiveCurveToRelative(-0.038f, -0.372f, -0.114f, -0.558f) close() moveTo(15.365f, 20.098f) horizontalLineToRelative(-0.963f) verticalLineToRelative(-0.962f) lineToRelative(3.09f, -3.064f) lineToRelative(0.481f, 0.456f) lineToRelative(0.456f, 0.481f) lineToRelative(-3.064f, 3.089f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(17.138f, 9.874f) lineToRelative(-7.023f, -7.023f) curveToRelative(-0.312f, -0.312f, -0.702f, -0.468f, -1.092f, -0.468f) horizontalLineTo(3.561f) curveToRelative(-0.859f, 0f, -1.561f, 0.702f, -1.561f, 1.561f) verticalLineToRelative(5.462f) curveToRelative(0f, 0.39f, 0.156f, 0.78f, 0.468f, 1.092f) lineToRelative(7.023f, 7.023f) curveToRelative(0.312f, 0.312f, 0.702f, 0.468f, 1.092f, 0.468f) reflectiveCurveToRelative(0.78f, -0.156f, 1.092f, -0.468f) lineToRelative(5.462f, -5.462f) curveToRelative(0.312f, -0.312f, 0.468f, -0.702f, 0.468f, -1.092f) curveToRelative(0.001f, -0.391f, -0.155f, -0.781f, -0.467f, -1.093f) close() moveTo(10.584f, 16.429f) lineToRelative(-7.023f, -7.023f) verticalLineTo(3.944f) horizontalLineToRelative(5.462f) lineToRelative(7.023f, 7.023f) lineToRelative(-5.462f, 5.462f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Eyedropper.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Eyedropper: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Eyedropper", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(19.35f, 11.72f) lineTo(17.22f, 13.85f) lineTo(15.81f, 12.43f) lineTo(8.1f, 20.14f) lineTo(3.5f, 22f) lineTo(2f, 20.5f) lineTo(3.86f, 15.9f) lineTo(11.57f, 8.19f) lineTo(10.15f, 6.78f) lineTo(12.28f, 4.65f) lineTo(19.35f, 11.72f) moveTo(16.76f, 3f) curveTo(17.93f, 1.83f, 19.83f, 1.83f, 21f, 3f) curveTo(22.17f, 4.17f, 22.17f, 6.07f, 21f, 7.24f) lineTo(19.08f, 9.16f) lineTo(14.84f, 4.92f) lineTo(16.76f, 3f) moveTo(5.56f, 17.03f) lineTo(4.5f, 19.5f) lineTo(6.97f, 18.44f) lineTo(14.4f, 11f) lineTo(13f, 9.6f) lineTo(5.56f, 17.03f) close() } }.build() } val Icons.TwoTone.Eyedropper: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Eyedropper", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(16.76f, 3f) curveToRelative(1.17f, -1.17f, 3.07f, -1.17f, 4.24f, 0f) curveToRelative(1.17f, 1.17f, 1.17f, 3.07f, 0f, 4.24f) lineToRelative(-1.92f, 1.92f) lineToRelative(-4.24f, -4.24f) lineToRelative(1.92f, -1.92f) } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5.56f, 17.03f) lineToRelative(-1.06f, 2.47f) lineToRelative(2.47f, -1.06f) lineToRelative(7.43f, -7.44f) lineToRelative(-1.4f, -1.4f) lineToRelative(-7.44f, 7.43f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12.28f, 4.65f) lineToRelative(-2.13f, 2.13f) lineToRelative(1.42f, 1.41f) lineToRelative(-7.71f, 7.71f) lineToRelative(-1.86f, 4.6f) lineToRelative(1.5f, 1.5f) lineToRelative(4.6f, -1.86f) lineToRelative(7.71f, -7.71f) lineToRelative(1.41f, 1.42f) lineToRelative(2.13f, -2.13f) lineToRelative(-7.07f, -7.07f) close() moveTo(6.97f, 18.44f) lineToRelative(-2.47f, 1.06f) lineToRelative(1.06f, -2.47f) lineToRelative(7.44f, -7.43f) lineToRelative(1.4f, 1.4f) lineToRelative(-7.43f, 7.44f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FabCorner.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.FabCorner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.FabCorner", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(2f, 20f) verticalLineToRelative(-2f) horizontalLineToRelative(18f) verticalLineTo(4f) horizontalLineToRelative(2f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(2f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10f, 8f) horizontalLineToRelative(8f) verticalLineToRelative(8f) horizontalLineToRelative(-8f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileExport.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FileExport: ImageVector by lazy { ImageVector.Builder( name = "Rounded.FileExport", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(6f, 2f) curveTo(4.89f, 2f, 4f, 2.9f, 4f, 4f) verticalLineTo(20f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 6f, 22f) horizontalLineTo(18f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 20f, 20f) verticalLineTo(8f) lineTo(14f, 2f) moveTo(13f, 3.5f) lineTo(18.5f, 9f) horizontalLineTo(13f) moveTo(8.93f, 12.22f) horizontalLineTo(16f) verticalLineTo(19.29f) lineTo(13.88f, 17.17f) lineTo(11.05f, 20f) lineTo(8.22f, 17.17f) lineTo(11.05f, 14.35f) } }.build() } val Icons.Outlined.FileExport: ImageVector by lazy { ImageVector.Builder( name = "Outlined.FileExport", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(14f, 2f) horizontalLineTo(6f) curveTo(4.9f, 2f, 4f, 2.9f, 4f, 4f) verticalLineTo(20f) curveTo(4f, 21.1f, 4.9f, 22f, 6f, 22f) horizontalLineTo(18f) curveTo(19.1f, 22f, 20f, 21.1f, 20f, 20f) verticalLineTo(8f) lineTo(14f, 2f) moveTo(18f, 20f) horizontalLineTo(6f) verticalLineTo(4f) horizontalLineTo(13f) verticalLineTo(9f) horizontalLineTo(18f) verticalLineTo(20f) moveTo(16f, 11f) verticalLineTo(18.1f) lineTo(13.9f, 16f) lineTo(11.1f, 18.8f) lineTo(8.3f, 16f) lineTo(11.1f, 13.2f) lineTo(8.9f, 11f) horizontalLineTo(16f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImage.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FileImage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.FileImage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(13f, 9f) horizontalLineTo(18.5f) lineTo(13f, 3.5f) verticalLineTo(9f) moveTo(6f, 2f) horizontalLineTo(14f) lineTo(20f, 8f) verticalLineTo(20f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 18f, 22f) horizontalLineTo(6f) curveTo(4.89f, 22f, 4f, 21.1f, 4f, 20f) verticalLineTo(4f) curveTo(4f, 2.89f, 4.89f, 2f, 6f, 2f) moveTo(6f, 20f) horizontalLineTo(15f) lineTo(18f, 20f) verticalLineTo(12f) lineTo(14f, 16f) lineTo(12f, 14f) lineTo(6f, 20f) moveTo(8f, 9f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 6f, 11f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 8f, 13f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 10f, 11f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 8f, 9f) close() } }.build() } val Icons.Outlined.FileImage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.FileImage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14f, 2f) lineTo(20f, 8f) verticalLineTo(20f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 18f, 22f) horizontalLineTo(6f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 20f) verticalLineTo(4f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6f, 2f) horizontalLineTo(14f) moveTo(18f, 20f) verticalLineTo(9f) horizontalLineTo(13f) verticalLineTo(4f) horizontalLineTo(6f) verticalLineTo(20f) horizontalLineTo(18f) moveTo(17f, 13f) verticalLineTo(19f) horizontalLineTo(7f) lineTo(12f, 14f) lineTo(14f, 16f) moveTo(10f, 10.5f) arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.5f, 12f) arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 7f, 10.5f) arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.5f, 9f) arcTo(1.5f, 1.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 10f, 10.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileImport.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FileImport: ImageVector by lazy { ImageVector.Builder( name = "Rounded.FileImport", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(6f, 2f) curveTo(4.89f, 2f, 4f, 2.9f, 4f, 4f) verticalLineTo(20f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 6f, 22f) horizontalLineTo(18f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 20f, 20f) verticalLineTo(8f) lineTo(14f, 2f) moveTo(13f, 3.5f) lineTo(18.5f, 9f) horizontalLineTo(13f) moveTo(10.05f, 11.22f) lineTo(12.88f, 14.05f) lineTo(15f, 11.93f) verticalLineTo(19f) horizontalLineTo(7.93f) lineTo(10.05f, 16.88f) lineTo(7.22f, 14.05f) } }.build() } val Icons.Outlined.FileImport: ImageVector by lazy { ImageVector.Builder( name = "Outlined.FileImport", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(14f, 2f) horizontalLineTo(6f) curveTo(4.89f, 2f, 4f, 2.9f, 4f, 4f) verticalLineTo(20f) curveTo(4f, 21.11f, 4.89f, 22f, 6f, 22f) horizontalLineTo(18f) curveTo(19.11f, 22f, 20f, 21.11f, 20f, 20f) verticalLineTo(8f) lineTo(14f, 2f) moveTo(18f, 20f) horizontalLineTo(6f) verticalLineTo(4f) horizontalLineTo(13f) verticalLineTo(9f) horizontalLineTo(18f) verticalLineTo(20f) moveTo(15f, 11.93f) verticalLineTo(19f) horizontalLineTo(7.93f) lineTo(10.05f, 16.88f) lineTo(7.22f, 14.05f) lineTo(10.05f, 11.22f) lineTo(12.88f, 14.05f) lineTo(15f, 11.93f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FileReplace.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.FileReplace: ImageVector by lazy { ImageVector.Builder( name = "FileReplace", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(14f, 3f) lineTo(12f, 1f) horizontalLineTo(4f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 2f, 3f) verticalLineTo(15f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = false, 4f, 17f) horizontalLineTo(11f) verticalLineTo(19f) lineTo(15f, 16f) lineTo(11f, 13f) verticalLineTo(15f) horizontalLineTo(4f) verticalLineTo(3f) horizontalLineTo(14f) moveTo(21f, 10f) verticalLineTo(21f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 19f, 23f) horizontalLineTo(8f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6f, 21f) verticalLineTo(19f) horizontalLineTo(8f) verticalLineTo(21f) horizontalLineTo(19f) verticalLineTo(12f) horizontalLineTo(14f) verticalLineTo(7f) horizontalLineTo(8f) verticalLineTo(13f) horizontalLineTo(6f) verticalLineTo(7f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 5f) horizontalLineTo(16f) lineTo(21f, 10f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FindInPage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.FindInPage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.FindInPage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14.75f, 20f) lineToRelative(2f, 2f) horizontalLineTo(6f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(4f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(9f) lineToRelative(5f, 6f) verticalLineToRelative(12f) curveToRelative(0f, 0.333f, -0.071f, 0.637f, -0.213f, 0.913f) reflectiveCurveToRelative(-0.338f, 0.504f, -0.587f, 0.688f) lineToRelative(-5.2f, -5.15f) curveToRelative(-0.283f, 0.183f, -0.592f, 0.321f, -0.925f, 0.412f) curveToRelative(-0.333f, 0.092f, -0.692f, 0.138f, -1.075f, 0.138f) curveToRelative(-1.1f, 0f, -2.042f, -0.392f, -2.825f, -1.175f) reflectiveCurveToRelative(-1.175f, -1.725f, -1.175f, -2.825f) reflectiveCurveToRelative(0.392f, -2.042f, 1.175f, -2.825f) reflectiveCurveToRelative(1.725f, -1.175f, 2.825f, -1.175f) reflectiveCurveToRelative(2.042f, 0.392f, 2.825f, 1.175f) reflectiveCurveToRelative(1.175f, 1.725f, 1.175f, 2.825f) curveToRelative(0f, 0.383f, -0.046f, 0.742f, -0.138f, 1.075f) reflectiveCurveToRelative(-0.229f, 0.642f, -0.412f, 0.925f) lineToRelative(2.55f, 2.6f) verticalLineToRelative(-8.9f) lineToRelative(-3.95f, -4.7f) horizontalLineTo(6f) verticalLineToRelative(16f) horizontalLineToRelative(8.75f) close() moveTo(13.413f, 14.412f) curveToRelative(0.392f, -0.392f, 0.587f, -0.863f, 0.587f, -1.413f) reflectiveCurveToRelative(-0.196f, -1.021f, -0.587f, -1.413f) curveToRelative(-0.392f, -0.392f, -0.863f, -0.587f, -1.413f, -0.587f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.413f, 0.587f) curveToRelative(-0.392f, 0.392f, -0.587f, 0.863f, -0.587f, 1.413f) reflectiveCurveToRelative(0.196f, 1.021f, 0.587f, 1.413f) reflectiveCurveToRelative(0.863f, 0.587f, 1.413f, 0.587f) reflectiveCurveToRelative(1.021f, -0.196f, 1.413f, -0.587f) close() } }.build() } val Icons.TwoTone.FindInPage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.FindInPage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(15f, 2f) horizontalLineTo(6f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(16f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10.75f) lineToRelative(-2f, -2f) horizontalLineTo(6f) verticalLineTo(4f) horizontalLineToRelative(8.05f) lineToRelative(3.95f, 4.7f) verticalLineToRelative(8.9f) lineToRelative(-2.55f, -2.6f) curveToRelative(0.183f, -0.283f, 0.321f, -0.592f, 0.412f, -0.925f) curveToRelative(0.092f, -0.333f, 0.138f, -0.692f, 0.138f, -1.075f) curveToRelative(0f, -1.1f, -0.392f, -2.042f, -1.175f, -2.825f) reflectiveCurveToRelative(-1.725f, -1.175f, -2.825f, -1.175f) reflectiveCurveToRelative(-2.042f, 0.392f, -2.825f, 1.175f) reflectiveCurveToRelative(-1.175f, 1.725f, -1.175f, 2.825f) reflectiveCurveToRelative(0.392f, 2.042f, 1.175f, 2.825f) reflectiveCurveToRelative(1.725f, 1.175f, 2.825f, 1.175f) curveToRelative(0.383f, 0f, 0.742f, -0.046f, 1.075f, -0.138f) curveToRelative(0.333f, -0.092f, 0.642f, -0.229f, 0.925f, -0.412f) lineToRelative(5.2f, 5.15f) curveToRelative(0.25f, -0.183f, 0.446f, -0.412f, 0.587f, -0.688f) curveToRelative(0.142f, -0.275f, 0.213f, -0.579f, 0.213f, -0.912f) verticalLineToRelative(-12f) lineToRelative(-5f, -6f) close() moveTo(13.412f, 14.412f) curveToRelative(-0.392f, 0.392f, -0.862f, 0.588f, -1.412f, 0.588f) reflectiveCurveToRelative(-1.021f, -0.196f, -1.412f, -0.588f) reflectiveCurveToRelative(-0.588f, -0.862f, -0.588f, -1.412f) reflectiveCurveToRelative(0.196f, -1.021f, 0.588f, -1.412f) reflectiveCurveToRelative(0.862f, -0.588f, 1.412f, -0.588f) reflectiveCurveToRelative(1.021f, 0.196f, 1.412f, 0.588f) reflectiveCurveToRelative(0.588f, 0.862f, 0.588f, 1.412f) reflectiveCurveToRelative(-0.196f, 1.021f, -0.588f, 1.412f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(18.003f, 8.623f) lineToRelative(-3.88f, -4.625f) horizontalLineTo(6.003f) verticalLineToRelative(16f) horizontalLineToRelative(12f) verticalLineToRelative(-11.375f) close() moveTo(13.415f, 14.411f) curveToRelative(-0.392f, 0.392f, -0.862f, 0.588f, -1.412f, 0.588f) reflectiveCurveToRelative(-1.021f, -0.196f, -1.412f, -0.588f) reflectiveCurveToRelative(-0.588f, -0.862f, -0.588f, -1.412f) reflectiveCurveToRelative(0.196f, -1.021f, 0.588f, -1.412f) reflectiveCurveToRelative(0.862f, -0.588f, 1.412f, -0.588f) reflectiveCurveToRelative(1.021f, 0.196f, 1.412f, 0.588f) reflectiveCurveToRelative(0.588f, 0.862f, 0.588f, 1.412f) reflectiveCurveToRelative(-0.196f, 1.021f, -0.588f, 1.412f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(18.003f, 19.999f) horizontalLineToRelative(-5.186f) verticalLineToRelative(2f) horizontalLineToRelative(5.186f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) horizontalLineToRelative(-2f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FingerprintOff.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FingerprintOff: ImageVector by lazy { Builder( name = "Fingerprint Off", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(1.5f, 4.77f) lineTo(2.78f, 3.5f) lineTo(20.5f, 21.22f) lineTo(19.23f, 22.5f) lineTo(16.67f, 19.94f) curveTo(15.58f, 19.9f, 14.62f, 19.6f, 13.82f, 19.05f) curveTo(12.34f, 18.05f, 11.46f, 16.43f, 11.44f, 14.71f) lineTo(10.27f, 13.53f) curveTo(10.03f, 13.85f, 9.89f, 14.23f, 9.89f, 14.65f) curveTo(9.89f, 16.36f, 10.55f, 17.96f, 11.76f, 19.16f) curveTo(12.71f, 20.1f, 13.62f, 20.62f, 15.03f, 21.0f) curveTo(15.3f, 21.08f, 15.45f, 21.36f, 15.38f, 21.62f) curveTo(15.33f, 21.85f, 15.12f, 22.0f, 14.91f, 22.0f) horizontalLineTo(14.78f) curveTo(13.19f, 21.54f, 12.15f, 20.95f, 11.06f, 19.88f) curveTo(9.66f, 18.5f, 8.89f, 16.64f, 8.89f, 14.66f) curveTo(8.89f, 13.97f, 9.14f, 13.33f, 9.56f, 12.83f) lineTo(8.5f, 11.77f) curveTo(7.78f, 12.54f, 7.34f, 13.55f, 7.34f, 14.66f) curveTo(7.34f, 16.1f, 7.66f, 17.43f, 8.27f, 18.5f) curveTo(8.91f, 19.66f, 9.35f, 20.15f, 10.12f, 20.93f) curveTo(10.31f, 21.13f, 10.31f, 21.44f, 10.12f, 21.64f) curveTo(10.0f, 21.74f, 9.88f, 21.79f, 9.75f, 21.79f) curveTo(9.62f, 21.79f, 9.5f, 21.74f, 9.4f, 21.64f) curveTo(8.53f, 20.77f, 8.06f, 20.21f, 7.39f, 19.0f) curveTo(6.7f, 17.77f, 6.34f, 16.27f, 6.34f, 14.66f) curveTo(6.34f, 13.28f, 6.89f, 12.0f, 7.79f, 11.06f) lineTo(6.7f, 9.97f) curveTo(6.15f, 10.5f, 5.69f, 11.15f, 5.35f, 11.86f) curveTo(4.96f, 12.67f, 4.76f, 13.62f, 4.76f, 14.66f) curveTo(4.76f, 15.44f, 4.83f, 16.67f, 5.43f, 18.27f) curveTo(5.53f, 18.53f, 5.4f, 18.82f, 5.14f, 18.91f) curveTo(4.88f, 19.0f, 4.59f, 18.87f, 4.5f, 18.62f) curveTo(4.0f, 17.31f, 3.77f, 16.0f, 3.77f, 14.66f) curveTo(3.77f, 13.46f, 4.0f, 12.37f, 4.45f, 11.42f) curveTo(4.84f, 10.61f, 5.36f, 9.88f, 6.0f, 9.26f) lineTo(4.97f, 8.24f) curveTo(4.58f, 8.63f, 4.22f, 9.05f, 3.89f, 9.5f) curveTo(3.81f, 9.65f, 3.66f, 9.72f, 3.5f, 9.72f) lineTo(3.21f, 9.63f) curveTo(3.0f, 9.47f, 2.93f, 9.16f, 3.09f, 8.93f) curveTo(3.45f, 8.43f, 3.84f, 7.96f, 4.27f, 7.53f) lineTo(1.5f, 4.77f) moveTo(17.81f, 4.47f) lineTo(17.58f, 4.41f) curveTo(15.66f, 3.42f, 14.0f, 3.0f, 12.0f, 3.0f) curveTo(10.03f, 3.0f, 8.15f, 3.47f, 6.44f, 4.41f) lineTo(6.29f, 4.46f) lineTo(5.71f, 3.89f) curveTo(5.73f, 3.74f, 5.82f, 3.61f, 5.96f, 3.53f) curveTo(7.82f, 2.5f, 9.86f, 2.0f, 12.0f, 2.0f) curveTo(14.14f, 2.0f, 16.0f, 2.47f, 18.04f, 3.5f) curveTo(18.29f, 3.65f, 18.38f, 3.95f, 18.25f, 4.19f) curveTo(18.16f, 4.37f, 18.0f, 4.47f, 17.81f, 4.47f) moveTo(17.15f, 5.65f) curveTo(18.65f, 6.42f, 19.91f, 7.5f, 20.9f, 8.9f) curveTo(21.06f, 9.12f, 21.0f, 9.44f, 20.78f, 9.6f) curveTo(20.55f, 9.76f, 20.24f, 9.71f, 20.08f, 9.5f) curveTo(19.18f, 8.22f, 18.04f, 7.23f, 16.69f, 6.54f) curveTo(14.06f, 5.19f, 10.76f, 5.08f, 8.03f, 6.21f) lineTo(7.27f, 5.45f) curveTo(10.34f, 4.04f, 14.14f, 4.1f, 17.15f, 5.65f) moveTo(12.0f, 9.27f) curveTo(15.12f, 9.27f, 17.66f, 11.69f, 17.66f, 14.66f) arcTo( 0.5f, 0.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 17.16f, y1 = 15.16f ) lineTo(16.93f, 15.11f) lineTo(16.72f, 14.89f) lineTo(16.66f, 14.66f) curveTo(16.66f, 12.27f, 14.62f, 10.32f, 12.09f, 10.27f) lineTo(11.15f, 9.33f) lineTo(12.0f, 9.27f) moveTo(14.38f, 18.22f) curveTo(14.71f, 18.45f, 15.07f, 18.62f, 15.47f, 18.73f) lineTo(12.63f, 15.9f) curveTo(12.92f, 16.82f, 13.53f, 17.65f, 14.38f, 18.22f) moveTo(19.21f, 14.66f) curveTo(19.21f, 10.89f, 15.96f, 7.83f, 11.96f, 7.83f) curveTo(11.26f, 7.83f, 10.58f, 7.93f, 9.93f, 8.11f) lineTo(9.12f, 7.3f) curveTo(10.0f, 7.0f, 10.97f, 6.82f, 11.96f, 6.82f) curveTo(16.5f, 6.82f, 20.21f, 10.33f, 20.21f, 14.65f) curveTo(20.21f, 15.65f, 19.69f, 16.53f, 18.89f, 17.06f) lineTo(18.17f, 16.34f) curveTo(18.79f, 16.0f, 19.21f, 15.38f, 19.21f, 14.66f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Firebase.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.TwoTone.Firebase: ImageVector by lazy { Builder( name = "Firebase", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFFFF9100)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(8.4f, 23.3f) curveToRelative(1.0f, 0.4f, 2.1f, 0.6f, 3.2f, 0.7f) curveToRelative(1.5f, 0.1f, 3.0f, -0.3f, 4.3f, -0.9f) curveToRelative(-1.6f, -0.6f, -3.0f, -1.5f, -4.2f, -2.7f) curveTo(11.0f, 21.7f, 9.8f, 22.7f, 8.4f, 23.3f) close() } path( fill = SolidColor(Color(0xFFFFC400)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(11.8f, 20.5f) curveTo(9.0f, 17.9f, 7.3f, 14.2f, 7.4f, 10.1f) curveToRelative(0.0f, -0.1f, 0.0f, -0.3f, 0.0f, -0.4f) curveTo(7.0f, 9.5f, 6.4f, 9.5f, 5.9f, 9.5f) curveToRelative(-0.8f, 0.0f, -1.5f, 0.1f, -2.2f, 0.3f) curveTo(3.0f, 11.0f, 2.5f, 12.5f, 2.5f, 14.1f) curveToRelative(-0.1f, 4.1f, 2.4f, 7.7f, 6.0f, 9.2f) curveTo(9.8f, 22.7f, 11.0f, 21.7f, 11.8f, 20.5f) close() } path( fill = SolidColor(Color(0xFFFF9100)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(11.8f, 20.5f) curveToRelative(0.6f, -1.0f, 1.0f, -2.3f, 1.1f, -3.6f) curveToRelative(0.1f, -3.4f, -2.2f, -6.4f, -5.4f, -7.2f) curveToRelative(0.0f, 0.1f, 0.0f, 0.3f, 0.0f, 0.4f) curveTo(7.3f, 14.2f, 9.0f, 17.9f, 11.8f, 20.5f) close() } path( fill = SolidColor(Color(0xFFDD2C00)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.5f, 0.0f) curveToRelative(-1.8f, 1.5f, -3.3f, 3.4f, -4.1f, 5.6f) curveTo(7.9f, 6.9f, 7.6f, 8.2f, 7.5f, 9.7f) curveToRelative(3.2f, 0.8f, 5.5f, 3.8f, 5.4f, 7.2f) curveToRelative(0.0f, 1.3f, -0.4f, 2.5f, -1.1f, 3.6f) curveToRelative(1.2f, 1.1f, 2.6f, 2.0f, 4.2f, 2.7f) curveToRelative(3.2f, -1.5f, 5.4f, -4.6f, 5.5f, -8.3f) curveToRelative(0.1f, -2.4f, -0.8f, -4.6f, -2.2f, -6.4f) curveTo(18.0f, 6.5f, 12.5f, 0.0f, 12.5f, 0.0f) close() } }.build() } val Icons.Outlined.Firebase: ImageVector by lazy { Builder( name = "Outlined.Firebase", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = SolidColor(Color(0xFF000000)), strokeLineWidth = 0.5f ) { moveTo(18.213f, 8.974f) curveToRelative(-0.449f, -0.623f, -1.482f, -1.904f, -3.068f, -3.808f) curveToRelative(-0.689f, -0.826f, -1.279f, -1.526f, -1.57f, -1.87f) curveToRelative(-0.16f, -0.19f, -0.298f, -0.352f, -0.406f, -0.481f) lineToRelative(-0.173f, -0.204f) lineToRelative(-0.094f, -0.111f) lineToRelative(-0.018f, -0.027f) lineToRelative(-0.008f, -0.004f) lineTo(12.475f, 2f) lineToRelative(-0.507f, 0.407f) curveToRelative(-1.295f, 1.038f, -2.356f, 2.375f, -3.067f, 3.866f) curveTo(8.464f, 7.16f, 8.18f, 8.027f, 8.03f, 8.92f) curveTo(7.991f, 9.121f, 7.958f, 9.328f, 7.93f, 9.535f) curveTo(7.756f, 9.52f, 7.579f, 9.511f, 7.403f, 9.507f) curveToRelative(-0.015f, -0.001f, -0.029f, -0.002f, -0.049f, -0.002f) curveToRelative(-0.643f, -0.022f, -1.282f, 0.054f, -1.9f, 0.228f) lineTo(5.19f, 9.808f) lineToRelative(-0.136f, 0.238f) curveToRelative(-0.637f, 1.118f, -0.998f, 2.39f, -1.043f, 3.68f) curveToRelative(-0.058f, 1.674f, 0.398f, 3.295f, 1.319f, 4.687f) curveToRelative(0.902f, 1.361f, 2.175f, 2.402f, 3.683f, 3.01f) lineToRelative(0.196f, 0.079f) lineToRelative(0.059f, 0.021f) lineToRelative(0.003f, -0.001f) curveToRelative(0.786f, 0.285f, 1.61f, 0.445f, 2.451f, 0.473f) curveTo(11.818f, 21.998f, 11.913f, 22f, 12.008f, 22f) curveToRelative(1.061f, 0f, 2.094f, -0.208f, 3.074f, -0.618f) lineToRelative(0.007f, 0.003f) lineToRelative(0.261f, -0.121f) curveToRelative(1.322f, -0.611f, 2.454f, -1.573f, 3.272f, -2.78f) curveToRelative(0.842f, -1.242f, 1.315f, -2.694f, 1.367f, -4.201f) curveToRelative(0.063f, -1.801f, -0.535f, -3.587f, -1.777f, -5.309f) lineTo(18.213f, 8.974f) close() moveTo(12.31f, 14.554f) curveToRelative(0.274f, 1.037f, 0.22f, 2.033f, -0.159f, 2.965f) curveToRelative(-0.946f, -0.934f, -1.64f, -1.96f, -2.063f, -3.054f) curveToRelative(-0.453f, -1.17f, -0.726f, -2.283f, -0.812f, -3.313f) curveToRelative(0.4f, 0.131f, 0.768f, 0.305f, 1.096f, 0.519f) curveToRelative(0.943f, 0.614f, 1.595f, 1.585f, 1.937f, 2.884f) verticalLineTo(14.554f) close() moveTo(12.483f, 19.572f) curveToRelative(0.402f, 0.306f, 0.825f, 0.593f, 1.26f, 0.857f) curveToRelative(-0.642f, 0.175f, -1.304f, 0.251f, -1.974f, 0.227f) curveToRelative(-0.103f, -0.004f, -0.207f, -0.01f, -0.311f, -0.018f) curveToRelative(0.382f, -0.329f, 0.725f, -0.686f, 1.024f, -1.066f) horizontalLineTo(12.483f) close() moveTo(13.605f, 14.212f) curveToRelative(-0.43f, -1.631f, -1.272f, -2.864f, -2.502f, -3.665f) curveToRelative(-0.539f, -0.351f, -1.154f, -0.618f, -1.829f, -0.792f) curveTo(9.284f, 9.644f, 9.296f, 9.532f, 9.311f, 9.423f) curveToRelative(0.012f, -0.094f, 0.025f, -0.18f, 0.039f, -0.261f) curveToRelative(0.111f, -0.574f, 0.277f, -1.142f, 0.491f, -1.687f) curveToRelative(0.082f, -0.209f, 0.172f, -0.417f, 0.267f, -0.617f) lineToRelative(0.004f, -0.007f) curveToRelative(0.147f, -0.298f, 0.313f, -0.599f, 0.508f, -0.92f) lineToRelative(0.077f, -0.126f) lineToRelative(-0.003f, -0.002f) curveToRelative(0.454f, -0.709f, 0.997f, -1.355f, 1.619f, -1.925f) lineToRelative(0.24f, 0.284f) curveToRelative(0.56f, 0.663f, 1.086f, 1.29f, 1.564f, 1.864f) curveToRelative(1.076f, 1.291f, 2.472f, 2.986f, 3.01f, 3.734f) curveToRelative(1.065f, 1.476f, 1.578f, 2.982f, 1.525f, 4.479f) curveToRelative(-0.041f, 1.162f, -0.385f, 2.296f, -0.996f, 3.277f) curveToRelative(-0.578f, 0.93f, -1.384f, 1.708f, -2.334f, 2.256f) curveToRelative(-0.53f, -0.264f, -1.299f, -0.699f, -2.116f, -1.332f) curveToRelative(0.659f, -1.313f, 0.793f, -2.734f, 0.399f, -4.227f) horizontalLineTo(13.605f) close() moveTo(11.459f, 18.71f) curveToRelative(-0.604f, 0.782f, -1.322f, 1.291f, -1.741f, 1.547f) curveToRelative(-0.068f, -0.025f, -0.136f, -0.051f, -0.203f, -0.078f) lineTo(9.461f, 20.158f) curveToRelative(-1.242f, -0.513f, -2.289f, -1.38f, -3.029f, -2.509f) curveToRelative(-0.756f, -1.153f, -1.131f, -2.494f, -1.082f, -3.877f) curveTo(5.384f, 12.78f, 5.631f, 11.833f, 6.085f, 10.955f) curveToRelative(0.263f, -0.058f, 0.532f, -0.095f, 0.8f, -0.109f) lineToRelative(0.069f, -0.002f) curveToRelative(0.136f, -0.003f, 0.27f, -0.003f, 0.399f, 0f) curveToRelative(0.189f, 0.009f, 0.378f, 0.028f, 0.564f, 0.058f) curveToRelative(0.061f, 1.26f, 0.37f, 2.62f, 0.921f, 4.043f) curveToRelative(0.53f, 1.37f, 1.411f, 2.635f, 2.62f, 3.763f) lineTo(11.459f, 18.71f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Flip.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Flip: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Flip", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(611.5f, 188.5f) quadTo(600f, 177f, 600f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(623f, 120f, 640f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(680f, 143f, 680f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(657f, 200f, 640f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 188.5f) quadTo(760f, 177f, 760f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 120f, 800f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 143f, 840f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 200f, 800f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 348.5f) quadTo(760f, 337f, 760f, 320f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 280f, 800f, 280f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 303f, 840f, 320f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 360f, 800f, 360f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 508.5f) quadTo(760f, 497f, 760f, 480f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 440f, 800f, 440f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 463f, 840f, 480f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 520f, 800f, 520f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 668.5f) quadTo(760f, 657f, 760f, 640f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 600f, 800f, 600f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 623f, 840f, 640f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 680f, 800f, 680f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(611.5f, 828.5f) quadTo(600f, 817f, 600f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(623f, 760f, 640f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(680f, 783f, 680f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(657f, 840f, 640f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 828.5f) quadTo(760f, 817f, 760f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 760f, 800f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 783f, 840f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 840f, 800f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(200f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(120f, 760f) verticalLineToRelative(-560f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(200f, 120f) horizontalLineToRelative(120f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(360f, 160f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(320f, 200f) lineTo(200f, 200f) verticalLineToRelative(560f) horizontalLineToRelative(120f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(360f, 800f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(320f, 840f) lineTo(200f, 840f) close() moveTo(440f, 880f) verticalLineToRelative(-800f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(480f, 40f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(520f, 80f) verticalLineToRelative(800f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(480f, 920f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(440f, 880f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FlipVertical.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.FlipVertical: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.FlipVertical", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19.288f, 15.287f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.712f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(19.288f, 19.287f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(15.288f, 19.287f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(11.288f, 19.288f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) curveToRelative(0.192f, 0.192f, 0.288f, 0.429f, 0.288f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.713f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.712f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(7.288f, 19.288f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.712f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(3.288f, 15.288f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.288f, -0.429f, -0.288f, -0.712f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(3.288f, 19.288f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.288f, -0.429f, -0.288f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(3f, 5f) curveToRelative(-0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.862f, -0.588f, 1.412f, -0.588f) lineToRelative(14f, -0f) curveToRelative(0.55f, -0f, 1.021f, 0.196f, 1.413f, 0.587f) curveToRelative(0.392f, 0.392f, 0.588f, 0.862f, 0.588f, 1.412f) lineToRelative(0f, 3f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.712f) lineToRelative(-0f, -3f) lineToRelative(-14f, 0f) lineToRelative(0f, 3f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.712f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.288f, -0.429f, -0.288f, -0.712f) lineToRelative(-0f, -3f) close() moveTo(2f, 11f) lineToRelative(20f, -0f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.713f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.288f, -0.712f, 0.288f) lineToRelative(-20f, 0f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.713f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.288f, -0.429f, -0.288f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.713f) reflectiveCurveToRelative(0.429f, -0.288f, 0.712f, -0.288f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FloatingActionButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FloatingActionButton: ImageVector by lazy { Builder( name = "Floating Action Button", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.9692f, 10.67f) lineToRelative(-3.0954f, 3.0955f) lineToRelative(0.0f, 0.4512f) lineToRelative(0.4512f, 0.0f) lineToRelative(3.0954f, -3.0954f) lineToRelative(-0.2296f, -0.2217f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.8593f, 3.1335f) horizontalLineTo(8.1407f) curveTo(5.3016f, 3.1335f, 3.0f, 5.4351f, 3.0f, 8.2742f) verticalLineToRelative(7.7186f) curveToRelative(0.0f, 2.8392f, 2.3016f, 5.1407f, 5.1407f, 5.1407f) horizontalLineToRelative(7.7186f) curveToRelative(2.8391f, 0.0f, 5.1407f, -2.3015f, 5.1407f, -5.1407f) verticalLineTo(8.2742f) curveTo(21.0f, 5.4351f, 18.6984f, 3.1335f, 15.8593f, 3.1335f) close() moveTo(14.7643f, 10.672f) curveToRelative(-0.001f, 0.001f, -0.0026f, 0.0013f, -0.0036f, 0.0024f) curveToRelative(-0.001f, 0.0011f, -0.0012f, 0.0024f, -0.0023f, 0.0035f) lineToRelative(-4.1721f, 4.1721f) horizontalLineTo(9.2404f) verticalLineToRelative(-1.3458f) lineToRelative(4.18f, -4.1721f) curveToRelative(4.0E-4f, -3.0E-4f, 8.0E-4f, -4.0E-4f, 0.0012f, -7.0E-4f) curveToRelative(4.0E-4f, -4.0E-4f, 4.0E-4f, -9.0E-4f, 8.0E-4f, -0.0012f) curveToRelative(0.2612f, -0.2612f, 0.6848f, -0.2612f, 0.946f, 0.0f) lineToRelative(0.3959f, 0.3959f) curveTo(15.0255f, 9.9872f, 15.0255f, 10.4108f, 14.7643f, 10.672f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FloodFill.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FloodFill: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.FloodFill", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(346f, 820f) lineTo(100f, 574f) quadTo(90f, 564f, 85f, 552f) quadTo(80f, 540f, 80f, 527f) quadTo(80f, 514f, 85f, 502f) quadTo(90f, 490f, 100f, 480f) lineTo(330f, 251f) lineTo(224f, 145f) lineTo(286f, 80f) lineTo(686f, 480f) quadTo(696f, 490f, 700.5f, 502f) quadTo(705f, 514f, 705f, 527f) quadTo(705f, 540f, 700.5f, 552f) quadTo(696f, 564f, 686f, 574f) lineTo(440f, 820f) quadTo(430f, 830f, 418f, 835f) quadTo(406f, 840f, 393f, 840f) quadTo(380f, 840f, 368f, 835f) quadTo(356f, 830f, 346f, 820f) close() moveTo(393f, 314f) lineTo(179f, 528f) quadTo(179f, 528f, 179f, 528f) quadTo(179f, 528f, 179f, 528f) lineTo(607f, 528f) quadTo(607f, 528f, 607f, 528f) quadTo(607f, 528f, 607f, 528f) lineTo(393f, 314f) close() moveTo(792f, 840f) quadTo(756f, 840f, 731f, 814.5f) quadTo(706f, 789f, 706f, 752f) quadTo(706f, 725f, 719.5f, 701f) quadTo(733f, 677f, 750f, 654f) lineTo(792f, 600f) lineTo(836f, 654f) quadTo(852f, 677f, 866f, 701f) quadTo(880f, 725f, 880f, 752f) quadTo(880f, 789f, 854f, 814.5f) quadTo(828f, 840f, 792f, 840f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderCompare.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FolderCompare: ImageVector by lazy { ImageVector.Builder( name = "FolderCompare", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(13f, 19f) curveTo(13f, 19.34f, 13.04f, 19.67f, 13.09f, 20f) horizontalLineTo(4f) curveTo(2.9f, 20f, 2f, 19.11f, 2f, 18f) verticalLineTo(6f) curveTo(2f, 4.89f, 2.89f, 4f, 4f, 4f) horizontalLineTo(10f) lineTo(12f, 6f) horizontalLineTo(20f) curveTo(21.1f, 6f, 22f, 6.89f, 22f, 8f) verticalLineTo(13.81f) curveTo(21.12f, 13.3f, 20.1f, 13f, 19f, 13f) curveTo(15.69f, 13f, 13f, 15.69f, 13f, 19f) moveTo(23f, 17f) lineTo(20f, 14.5f) verticalLineTo(16f) horizontalLineTo(16f) verticalLineTo(18f) horizontalLineTo(20f) verticalLineTo(19.5f) lineTo(23f, 17f) moveTo(18f, 18.5f) lineTo(15f, 21f) lineTo(18f, 23.5f) verticalLineTo(22f) horizontalLineTo(22f) verticalLineTo(20f) horizontalLineTo(18f) verticalLineTo(18.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.FolderImage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.FolderImage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(6.667f, 15.733f) lineToRelative(10.667f, 0f) lineToRelative(-3.68f, -4.8f) lineToRelative(-2.453f, 3.2f) lineToRelative(-1.653f, -2.133f) lineToRelative(-2.88f, 3.733f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.973f, 6.76f) curveToRelative(-0.418f, -0.418f, -0.92f, -0.627f, -1.507f, -0.627f) horizontalLineToRelative(-7.467f) lineToRelative(-2.133f, -2.133f) horizontalLineToRelative(-5.333f) curveToRelative(-0.587f, 0f, -1.089f, 0.209f, -1.507f, 0.627f) reflectiveCurveToRelative(-0.627f, 0.92f, -0.627f, 1.507f) verticalLineToRelative(11.733f) curveToRelative(0f, 0.587f, 0.209f, 1.089f, 0.627f, 1.507f) reflectiveCurveToRelative(0.92f, 0.627f, 1.507f, 0.627f) horizontalLineToRelative(14.933f) curveToRelative(0.587f, 0f, 1.089f, -0.209f, 1.507f, -0.627f) reflectiveCurveToRelative(0.627f, -0.92f, 0.627f, -1.507f) verticalLineToRelative(-9.6f) curveToRelative(0f, -0.587f, -0.209f, -1.089f, -0.627f, -1.507f) close() moveTo(19.467f, 17.867f) horizontalLineTo(4.533f) verticalLineTo(6.133f) horizontalLineToRelative(4.453f) lineToRelative(2.133f, 2.133f) horizontalLineToRelative(8.347f) verticalLineToRelative(9.6f) close() } }.build() } val Icons.Rounded.FolderImage: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.FolderImage", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.973f, 6.76f) curveToRelative(-0.418f, -0.418f, -0.92f, -0.627f, -1.507f, -0.627f) horizontalLineToRelative(-7.467f) lineToRelative(-2.133f, -2.133f) horizontalLineToRelative(-5.333f) curveToRelative(-0.587f, 0f, -1.089f, 0.209f, -1.507f, 0.627f) curveToRelative(-0.418f, 0.418f, -0.627f, 0.92f, -0.627f, 1.507f) verticalLineToRelative(11.733f) curveToRelative(0f, 0.587f, 0.209f, 1.089f, 0.627f, 1.507f) curveToRelative(0.418f, 0.418f, 0.92f, 0.627f, 1.507f, 0.627f) horizontalLineToRelative(14.933f) curveToRelative(0.587f, 0f, 1.089f, -0.209f, 1.507f, -0.627f) curveToRelative(0.418f, -0.418f, 0.627f, -0.92f, 0.627f, -1.507f) verticalLineToRelative(-9.6f) curveToRelative(0f, -0.587f, -0.209f, -1.089f, -0.627f, -1.507f) close() moveTo(6.667f, 15.733f) lineToRelative(2.88f, -3.733f) lineToRelative(1.653f, 2.133f) lineToRelative(2.453f, -3.2f) lineToRelative(3.68f, 4.8f) horizontalLineTo(6.667f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderImageAlt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FolderImageAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.FolderImageAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(10.509f, 20.464f) lineToRelative(2.457f, -3.272f) curveToRelative(0.061f, -0.082f, 0.136f, -0.143f, 0.223f, -0.184f) reflectiveCurveToRelative(0.177f, -0.061f, 0.269f, -0.061f) reflectiveCurveToRelative(0.182f, 0.02f, 0.269f, 0.061f) reflectiveCurveToRelative(0.161f, 0.102f, 0.223f, 0.184f) lineToRelative(2.089f, 2.78f) curveToRelative(0.061f, 0.082f, 0.133f, 0.143f, 0.215f, 0.184f) reflectiveCurveToRelative(0.174f, 0.061f, 0.276f, 0.061f) curveToRelative(0.256f, 0f, 0.44f, -0.115f, 0.553f, -0.346f) curveToRelative(0.113f, -0.23f, 0.092f, -0.448f, -0.061f, -0.653f) lineToRelative(-1.29f, -1.705f) curveToRelative(-0.082f, -0.113f, -0.123f, -0.236f, -0.123f, -0.369f) curveToRelative(0f, -0.133f, 0.041f, -0.256f, 0.123f, -0.369f) lineToRelative(1.536f, -2.043f) curveToRelative(0.061f, -0.082f, 0.136f, -0.143f, 0.223f, -0.184f) reflectiveCurveToRelative(0.177f, -0.061f, 0.269f, -0.061f) reflectiveCurveToRelative(0.182f, 0.02f, 0.269f, 0.061f) reflectiveCurveToRelative(0.161f, 0.102f, 0.223f, 0.184f) lineToRelative(4.301f, 5.729f) curveToRelative(0.154f, 0.205f, 0.174f, 0.42f, 0.061f, 0.645f) curveToRelative(-0.113f, 0.225f, -0.297f, 0.338f, -0.553f, 0.338f) horizontalLineToRelative(-11.059f) curveToRelative(-0.256f, 0f, -0.44f, -0.113f, -0.553f, -0.338f) curveToRelative(-0.113f, -0.225f, -0.092f, -0.44f, 0.061f, -0.645f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 20f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.412f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.588f, -0.863f, -0.588f, -1.413f) verticalLineTo(6f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.588f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.412f, -0.587f) horizontalLineToRelative(5.175f) curveToRelative(0.267f, 0f, 0.521f, 0.05f, 0.762f, 0.15f) curveToRelative(0.242f, 0.1f, 0.454f, 0.242f, 0.637f, 0.425f) lineToRelative(1.425f, 1.425f) horizontalLineToRelative(8f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) curveToRelative(0.392f, 0.392f, 0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(4f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.288f, 0.713f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-6f) curveToRelative(-1.667f, 0f, -3.083f, 0.583f, -4.25f, 1.75f) curveToRelative(-1.167f, 1.167f, -1.75f, 2.583f, -1.75f, 4.25f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.288f, -0.713f, 0.288f) horizontalLineToRelative(-4f) close() } }.build() } val Icons.Outlined.FolderImageAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.FolderImageAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(4f, 18f) lineToRelative(0f, -12f) lineToRelative(0f, 6.925f) lineToRelative(0f, -0.925f) lineToRelative(0f, 6f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10.617f, 20.464f) lineToRelative(2.457f, -3.272f) curveToRelative(0.061f, -0.082f, 0.136f, -0.143f, 0.223f, -0.184f) reflectiveCurveToRelative(0.177f, -0.061f, 0.269f, -0.061f) reflectiveCurveToRelative(0.182f, 0.02f, 0.269f, 0.061f) reflectiveCurveToRelative(0.161f, 0.102f, 0.223f, 0.184f) lineToRelative(2.089f, 2.78f) curveToRelative(0.061f, 0.082f, 0.133f, 0.143f, 0.215f, 0.184f) reflectiveCurveToRelative(0.174f, 0.061f, 0.276f, 0.061f) curveToRelative(0.256f, 0f, 0.44f, -0.115f, 0.553f, -0.346f) curveToRelative(0.113f, -0.23f, 0.092f, -0.448f, -0.061f, -0.653f) lineToRelative(-1.29f, -1.705f) curveToRelative(-0.082f, -0.113f, -0.123f, -0.236f, -0.123f, -0.369f) curveToRelative(0f, -0.133f, 0.041f, -0.256f, 0.123f, -0.369f) lineToRelative(1.536f, -2.043f) curveToRelative(0.061f, -0.082f, 0.136f, -0.143f, 0.223f, -0.184f) reflectiveCurveToRelative(0.177f, -0.061f, 0.269f, -0.061f) reflectiveCurveToRelative(0.182f, 0.02f, 0.269f, 0.061f) reflectiveCurveToRelative(0.161f, 0.102f, 0.223f, 0.184f) lineToRelative(4.301f, 5.729f) curveToRelative(0.154f, 0.205f, 0.174f, 0.42f, 0.061f, 0.645f) curveToRelative(-0.113f, 0.225f, -0.297f, 0.338f, -0.553f, 0.338f) horizontalLineToRelative(-11.059f) curveToRelative(-0.256f, 0f, -0.44f, -0.113f, -0.553f, -0.338f) curveToRelative(-0.113f, -0.225f, -0.092f, -0.44f, 0.061f, -0.645f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 6.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-8f) lineToRelative(-1.425f, -1.425f) curveToRelative(-0.183f, -0.183f, -0.396f, -0.325f, -0.638f, -0.425f) curveToRelative(-0.242f, -0.1f, -0.496f, -0.15f, -0.763f, -0.15f) horizontalLineToRelative(-5.175f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(4f) curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) reflectiveCurveToRelative(-0.448f, -1f, -1f, -1f) horizontalLineToRelative(-4f) verticalLineTo(6f) horizontalLineToRelative(5.175f) lineToRelative(1.425f, 1.425f) curveToRelative(0.183f, 0.183f, 0.396f, 0.325f, 0.638f, 0.425f) curveToRelative(0.242f, 0.1f, 0.496f, 0.15f, 0.763f, 0.15f) horizontalLineToRelative(8f) verticalLineToRelative(4f) curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) reflectiveCurveToRelative(1f, -0.448f, 1f, -1f) verticalLineToRelative(-4f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderMatch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FolderMatch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.FolderMatch", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(230f, 680f) quadToRelative(-20f, -24f, -33.5f, -51.5f) reflectiveQuadTo(174f, 571f) quadToRelative(-5f, -16f, 4f, -28.5f) reflectiveQuadToRelative(26f, -15.5f) quadToRelative(17f, -3f, 30.5f, 6f) reflectiveQuadToRelative(18.5f, 25f) quadToRelative(5f, 16f, 12.5f, 30.5f) reflectiveQuadTo(283f, 617f) quadToRelative(10f, 14f, 21f, 25.5f) reflectiveQuadToRelative(24f, 22.5f) quadToRelative(13f, 11f, 16.5f, 27f) reflectiveQuadToRelative(-4.5f, 30f) quadToRelative(-8f, 14f, -24f, 18.5f) reflectiveQuadToRelative(-29f, -5.5f) quadToRelative(-16f, -12f, -30.5f, -26f) reflectiveQuadTo(230f, 680f) close() moveTo(500f, 880f) quadToRelative(-25f, 0f, -42.5f, -17.5f) reflectiveQuadTo(440f, 820f) verticalLineToRelative(-240f) quadToRelative(0f, -25f, 17.5f, -42.5f) reflectiveQuadTo(500f, 520f) horizontalLineToRelative(88f) quadToRelative(15f, 0f, 28.5f, 7f) reflectiveQuadToRelative(21.5f, 20f) lineToRelative(22f, 33f) horizontalLineToRelative(160f) quadToRelative(25f, 0f, 42.5f, 17.5f) reflectiveQuadTo(880f, 640f) verticalLineToRelative(180f) quadToRelative(0f, 25f, -17.5f, 42.5f) reflectiveQuadTo(820f, 880f) lineTo(500f, 880f) close() moveTo(140f, 440f) quadToRelative(-25f, 0f, -42.5f, -17.5f) reflectiveQuadTo(80f, 380f) verticalLineToRelative(-240f) quadToRelative(0f, -25f, 17.5f, -42.5f) reflectiveQuadTo(140f, 80f) horizontalLineToRelative(88f) quadToRelative(15f, 0f, 28.5f, 7f) reflectiveQuadToRelative(21.5f, 20f) lineToRelative(22f, 33f) horizontalLineToRelative(160f) quadToRelative(25f, 0f, 42.5f, 17.5f) reflectiveQuadTo(520f, 200f) verticalLineToRelative(180f) quadToRelative(0f, 25f, -17.5f, 42.5f) reflectiveQuadTo(460f, 440f) lineTo(140f, 440f) close() moveTo(688f, 360f) quadToRelative(-11f, -19f, -25f, -35f) reflectiveQuadToRelative(-31f, -30f) quadToRelative(-13f, -11f, -16.5f, -27f) reflectiveQuadToRelative(4.5f, -30f) quadToRelative(8f, -14f, 24f, -18.5f) reflectiveQuadToRelative(29f, 5.5f) quadToRelative(21f, 16f, 39f, 35f) reflectiveQuadToRelative(33f, 41f) quadToRelative(21f, 31f, 34.5f, 66.5f) reflectiveQuadTo(798f, 441f) quadToRelative(2f, 16f, -9.5f, 27.5f) reflectiveQuadTo(760f, 480f) quadToRelative(-17f, 0f, -28.5f, -11f) reflectiveQuadTo(717f, 441f) quadToRelative(-4f, -22f, -11f, -42f) reflectiveQuadToRelative(-18f, -39f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FolderOpened.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FolderOpened: ImageVector by lazy { Builder( name = "Folder Opened", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(160.0f, 800.0f) quadToRelative(-33.0f, 0.0f, -56.5f, -23.5f) reflectiveQuadTo(80.0f, 720.0f) verticalLineToRelative(-480.0f) quadToRelative(0.0f, -33.0f, 23.5f, -56.5f) reflectiveQuadTo(160.0f, 160.0f) horizontalLineToRelative(240.0f) lineToRelative(80.0f, 80.0f) horizontalLineToRelative(320.0f) quadToRelative(33.0f, 0.0f, 56.5f, 23.5f) reflectiveQuadTo(880.0f, 320.0f) lineTo(160.0f, 320.0f) verticalLineToRelative(400.0f) lineToRelative(96.0f, -320.0f) horizontalLineToRelative(684.0f) lineTo(837.0f, 743.0f) quadToRelative(-8.0f, 26.0f, -29.5f, 41.5f) reflectiveQuadTo(760.0f, 800.0f) lineTo(160.0f, 800.0f) close() } } .build() } val Icons.Outlined.FolderOpened: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "Outlined.FolderOpened", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(160f, 800f) quadTo(127f, 800f, 103.5f, 776.5f) quadTo(80f, 753f, 80f, 720f) lineTo(80f, 240f) quadTo(80f, 207f, 103.5f, 183.5f) quadTo(127f, 160f, 160f, 160f) lineTo(400f, 160f) lineTo(480f, 240f) lineTo(800f, 240f) quadTo(833f, 240f, 856.5f, 263.5f) quadTo(880f, 287f, 880f, 320f) lineTo(447f, 320f) lineTo(367f, 240f) lineTo(160f, 240f) quadTo(160f, 240f, 160f, 240f) quadTo(160f, 240f, 160f, 240f) lineTo(160f, 720f) quadTo(160f, 720f, 160f, 720f) quadTo(160f, 720f, 160f, 720f) lineTo(256f, 400f) lineTo(940f, 400f) lineTo(837f, 743f) quadTo(829f, 769f, 807.5f, 784.5f) quadTo(786f, 800f, 760f, 800f) lineTo(160f, 800f) close() moveTo(244f, 720f) lineTo(760f, 720f) lineTo(832f, 480f) lineTo(316f, 480f) lineTo(244f, 720f) close() moveTo(244f, 720f) lineTo(316f, 480f) lineTo(316f, 480f) lineTo(244f, 720f) close() moveTo(160f, 320f) lineTo(160f, 240f) quadTo(160f, 240f, 160f, 240f) quadTo(160f, 240f, 160f, 240f) lineTo(160f, 240f) lineTo(160f, 320f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FontFamily.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FontFamily: ImageVector by lazy { Builder( name = "Font Family", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(186.0f, 880.0f) quadToRelative(-54.0f, 0.0f, -80.0f, -22.0f) reflectiveQuadToRelative(-26.0f, -66.0f) quadToRelative(0.0f, -58.0f, 49.0f, -74.0f) reflectiveQuadToRelative(116.0f, -16.0f) horizontalLineToRelative(21.0f) verticalLineToRelative(-56.0f) quadToRelative(0.0f, -34.0f, -1.0f, -55.5f) reflectiveQuadToRelative(-6.0f, -35.5f) quadToRelative(-5.0f, -14.0f, -11.5f, -19.5f) reflectiveQuadTo(230.0f, 530.0f) quadToRelative(-9.0f, 0.0f, -16.5f, 3.0f) reflectiveQuadToRelative(-12.5f, 8.0f) quadToRelative(-4.0f, 5.0f, -5.0f, 10.5f) reflectiveQuadToRelative(1.0f, 11.5f) quadToRelative(6.0f, 11.0f, 14.0f, 21.5f) reflectiveQuadToRelative(8.0f, 24.5f) quadToRelative(0.0f, 25.0f, -17.5f, 42.5f) reflectiveQuadTo(159.0f, 669.0f) quadToRelative(-25.0f, 0.0f, -42.5f, -17.5f) reflectiveQuadTo(99.0f, 609.0f) quadToRelative(0.0f, -27.0f, 12.0f, -44.0f) reflectiveQuadToRelative(32.5f, -27.0f) quadToRelative(20.5f, -10.0f, 47.5f, -14.0f) reflectiveQuadToRelative(58.0f, -4.0f) quadToRelative(85.0f, 0.0f, 118.0f, 30.5f) reflectiveQuadTo(400.0f, 658.0f) verticalLineToRelative(147.0f) quadToRelative(0.0f, 19.0f, 4.5f, 28.0f) reflectiveQuadToRelative(15.5f, 9.0f) quadToRelative(12.0f, 0.0f, 19.5f, -18.0f) reflectiveQuadToRelative(9.5f, -56.0f) horizontalLineToRelative(11.0f) quadToRelative(-3.0f, 62.0f, -23.5f, 87.0f) reflectiveQuadTo(368.0f, 880.0f) quadToRelative(-43.0f, 0.0f, -67.5f, -13.5f) reflectiveQuadTo(269.0f, 826.0f) quadToRelative(-10.0f, 29.0f, -29.5f, 41.5f) reflectiveQuadTo(186.0f, 880.0f) close() moveTo(559.0f, 880.0f) quadToRelative(-20.0f, 0.0f, -32.5f, -16.5f) reflectiveQuadTo(522.0f, 828.0f) lineToRelative(102.0f, -269.0f) quadToRelative(7.0f, -17.0f, 22.0f, -28.0f) reflectiveQuadToRelative(34.0f, -11.0f) quadToRelative(19.0f, 0.0f, 34.0f, 11.0f) reflectiveQuadToRelative(22.0f, 28.0f) lineToRelative(102.0f, 269.0f) quadToRelative(8.0f, 19.0f, -4.5f, 35.5f) reflectiveQuadTo(801.0f, 880.0f) quadToRelative(-12.0f, 0.0f, -22.0f, -7.0f) reflectiveQuadToRelative(-15.0f, -19.0f) lineToRelative(-20.0f, -58.0f) lineTo(616.0f, 796.0f) lineToRelative(-20.0f, 58.0f) quadToRelative(-4.0f, 11.0f, -14.0f, 18.5f) reflectiveQuadTo(559.0f, 880.0f) close() moveTo(235.0f, 851.0f) quadToRelative(13.0f, 0.0f, 22.0f, -20.5f) reflectiveQuadToRelative(9.0f, -49.5f) verticalLineToRelative(-67.0f) quadToRelative(-26.0f, 0.0f, -38.0f, 15.5f) reflectiveQuadTo(216.0f, 780.0f) verticalLineToRelative(11.0f) quadToRelative(0.0f, 36.0f, 4.0f, 48.0f) reflectiveQuadToRelative(15.0f, 12.0f) close() moveTo(642.0f, 726.0f) horizontalLineToRelative(77.0f) lineToRelative(-39.0f, -114.0f) lineToRelative(-38.0f, 114.0f) close() moveTo(605.0f, 441.0f) quadToRelative(-48.0f, 0.0f, -76.5f, -33.5f) reflectiveQuadTo(500.0f, 317.0f) quadToRelative(0.0f, -104.0f, 66.0f, -170.5f) reflectiveQuadTo(735.0f, 80.0f) quadToRelative(42.0f, 0.0f, 68.0f, 9.5f) reflectiveQuadToRelative(26.0f, 24.5f) quadToRelative(0.0f, 6.0f, -2.0f, 12.0f) reflectiveQuadToRelative(-7.0f, 11.0f) quadToRelative(-5.0f, 7.0f, -12.5f, 10.0f) reflectiveQuadToRelative(-15.5f, 1.0f) quadToRelative(-14.0f, -4.0f, -32.0f, -7.0f) reflectiveQuadToRelative(-33.0f, -3.0f) quadToRelative(-71.0f, 0.0f, -114.0f, 48.0f) reflectiveQuadToRelative(-43.0f, 127.0f) quadToRelative(0.0f, 22.0f, 8.0f, 46.0f) reflectiveQuadToRelative(36.0f, 24.0f) quadToRelative(11.0f, 0.0f, 21.5f, -5.0f) reflectiveQuadToRelative(18.5f, -14.0f) quadToRelative(17.0f, -18.0f, 31.5f, -60.0f) reflectiveQuadTo(712.0f, 202.0f) quadToRelative(2.0f, -13.0f, 10.5f, -18.5f) reflectiveQuadTo(746.0f, 178.0f) quadToRelative(18.0f, 0.0f, 27.5f, 9.5f) reflectiveQuadTo(779.0f, 211.0f) quadToRelative(-12.0f, 43.0f, -17.5f, 75.0f) reflectiveQuadToRelative(-5.5f, 58.0f) quadToRelative(0.0f, 20.0f, 5.5f, 29.0f) reflectiveQuadToRelative(16.5f, 9.0f) quadToRelative(11.0f, 0.0f, 21.5f, -8.0f) reflectiveQuadToRelative(29.5f, -30.0f) quadToRelative(2.0f, -3.0f, 15.0f, -7.0f) quadToRelative(8.0f, 0.0f, 12.0f, 6.0f) reflectiveQuadToRelative(4.0f, 17.0f) quadToRelative(0.0f, 28.0f, -32.0f, 54.0f) reflectiveQuadToRelative(-67.0f, 26.0f) quadToRelative(-26.0f, 0.0f, -44.5f, -14.0f) reflectiveQuadTo(691.0f, 386.0f) quadToRelative(-15.0f, 26.0f, -37.0f, 40.5f) reflectiveQuadTo(605.0f, 441.0f) close() moveTo(120.0f, 440.0f) verticalLineToRelative(-220.0f) quadToRelative(0.0f, -58.0f, 41.0f, -99.0f) reflectiveQuadToRelative(99.0f, -41.0f) quadToRelative(58.0f, 0.0f, 99.0f, 41.0f) reflectiveQuadToRelative(41.0f, 99.0f) verticalLineToRelative(220.0f) horizontalLineToRelative(-80.0f) verticalLineToRelative(-80.0f) lineTo(200.0f, 360.0f) verticalLineToRelative(80.0f) horizontalLineToRelative(-80.0f) close() moveTo(200.0f, 280.0f) horizontalLineToRelative(120.0f) verticalLineToRelative(-60.0f) quadToRelative(0.0f, -25.0f, -17.5f, -42.5f) reflectiveQuadTo(260.0f, 160.0f) quadToRelative(-25.0f, 0.0f, -42.5f, 17.5f) reflectiveQuadTo(200.0f, 220.0f) verticalLineToRelative(60.0f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FormatPaintVariant.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.FormatPaintVariant: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.FormatPaintVariant", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(440f, 880f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(360f, 800f) verticalLineToRelative(-160f) lineTo(240f, 640f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(160f, 560f) verticalLineToRelative(-280f) quadToRelative(0f, -66f, 47f, -113f) reflectiveQuadToRelative(113f, -47f) horizontalLineToRelative(480f) verticalLineToRelative(440f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(720f, 640f) lineTo(600f, 640f) verticalLineToRelative(160f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(520f, 880f) horizontalLineToRelative(-80f) close() moveTo(240f, 400f) horizontalLineToRelative(480f) verticalLineToRelative(-200f) horizontalLineToRelative(-40f) verticalLineToRelative(160f) horizontalLineToRelative(-80f) verticalLineToRelative(-160f) horizontalLineToRelative(-40f) verticalLineToRelative(80f) horizontalLineToRelative(-80f) verticalLineToRelative(-80f) lineTo(320f, 200f) quadToRelative(-33f, 0f, -56.5f, 23.5f) reflectiveQuadTo(240f, 280f) verticalLineToRelative(120f) close() moveTo(240f, 560f) horizontalLineToRelative(480f) verticalLineToRelative(-80f) lineTo(240f, 480f) verticalLineToRelative(80f) close() moveTo(240f, 560f) verticalLineToRelative(-80f) verticalLineToRelative(80f) close() } }.build() } val Icons.TwoTone.FormatPaintVariant: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.FormatPaintVariant", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(8f, 3f) curveToRelative(-1.1f, 0f, -2.042f, 0.392f, -2.825f, 1.175f) reflectiveCurveToRelative(-1.175f, 1.725f, -1.175f, 2.825f) verticalLineToRelative(7f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(3f) verticalLineToRelative(4f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(2f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineToRelative(-4f) horizontalLineToRelative(3f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(3f) horizontalLineToRelative(-12f) close() moveTo(18f, 14f) horizontalLineTo(6f) verticalLineToRelative(-2f) horizontalLineToRelative(12f) verticalLineToRelative(2f) close() moveTo(18f, 10f) horizontalLineTo(6f) verticalLineToRelative(-3f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.588f, -1.412f) reflectiveCurveToRelative(0.862f, -0.588f, 1.412f, -0.588f) horizontalLineToRelative(4f) verticalLineToRelative(2f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) horizontalLineToRelative(1f) verticalLineToRelative(4f) horizontalLineToRelative(2f) verticalLineToRelative(-4f) horizontalLineToRelative(1f) verticalLineToRelative(5f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6f, 14f) verticalLineToRelative(-2f) verticalLineToRelative(2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6f, 10f) horizontalLineToRelative(12f) verticalLineToRelative(-5f) horizontalLineToRelative(-1f) verticalLineToRelative(4f) horizontalLineToRelative(-2f) verticalLineToRelative(-4f) horizontalLineToRelative(-1f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) verticalLineToRelative(-2f) horizontalLineToRelative(-4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.413f, 0.587f) curveToRelative(-0.392f, 0.392f, -0.587f, 0.863f, -0.587f, 1.413f) verticalLineToRelative(3f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6f, 12f) horizontalLineToRelative(12f) verticalLineToRelative(2f) horizontalLineToRelative(-12f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Forum.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Forum: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Forum", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 720f) quadTo(263f, 720f, 251.5f, 708.5f) quadTo(240f, 697f, 240f, 680f) lineTo(240f, 600f) lineTo(760f, 600f) lineTo(760f, 600f) lineTo(760f, 240f) lineTo(840f, 240f) quadTo(857f, 240f, 868.5f, 251.5f) quadTo(880f, 263f, 880f, 280f) lineTo(880f, 880f) lineTo(720f, 720f) lineTo(280f, 720f) close() moveTo(80f, 680f) lineTo(80f, 120f) quadTo(80f, 103f, 91.5f, 91.5f) quadTo(103f, 80f, 120f, 80f) lineTo(640f, 80f) quadTo(657f, 80f, 668.5f, 91.5f) quadTo(680f, 103f, 680f, 120f) lineTo(680f, 480f) quadTo(680f, 497f, 668.5f, 508.5f) quadTo(657f, 520f, 640f, 520f) lineTo(240f, 520f) lineTo(80f, 680f) close() } }.build() } val Icons.Outlined.Forum: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Forum", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(880f, 880f) lineTo(720f, 720f) lineTo(320f, 720f) quadTo(287f, 720f, 263.5f, 696.5f) quadTo(240f, 673f, 240f, 640f) lineTo(240f, 600f) lineTo(680f, 600f) quadTo(713f, 600f, 736.5f, 576.5f) quadTo(760f, 553f, 760f, 520f) lineTo(760f, 240f) lineTo(800f, 240f) quadTo(833f, 240f, 856.5f, 263.5f) quadTo(880f, 287f, 880f, 320f) lineTo(880f, 880f) close() moveTo(160f, 487f) lineTo(207f, 440f) lineTo(600f, 440f) quadTo(600f, 440f, 600f, 440f) quadTo(600f, 440f, 600f, 440f) lineTo(600f, 160f) quadTo(600f, 160f, 600f, 160f) quadTo(600f, 160f, 600f, 160f) lineTo(160f, 160f) quadTo(160f, 160f, 160f, 160f) quadTo(160f, 160f, 160f, 160f) lineTo(160f, 487f) close() moveTo(80f, 680f) lineTo(80f, 160f) quadTo(80f, 127f, 103.5f, 103.5f) quadTo(127f, 80f, 160f, 80f) lineTo(600f, 80f) quadTo(633f, 80f, 656.5f, 103.5f) quadTo(680f, 127f, 680f, 160f) lineTo(680f, 440f) quadTo(680f, 473f, 656.5f, 496.5f) quadTo(633f, 520f, 600f, 520f) lineTo(240f, 520f) lineTo(80f, 680f) close() moveTo(160f, 440f) lineTo(160f, 160f) quadTo(160f, 160f, 160f, 160f) quadTo(160f, 160f, 160f, 160f) lineTo(160f, 160f) quadTo(160f, 160f, 160f, 160f) quadTo(160f, 160f, 160f, 160f) lineTo(160f, 440f) quadTo(160f, 440f, 160f, 440f) quadTo(160f, 440f, 160f, 440f) lineTo(160f, 440f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeArrow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FreeArrow: ImageVector by lazy { Builder( name = "FreeArrow", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(800.0f, 377.0f) lineTo(621.0f, 555.0f) quadToRelative(-35.0f, 35.0f, -85.0f, 35.0f) reflectiveQuadToRelative(-85.0f, -35.0f) lineToRelative(-47.0f, -47.0f) quadToRelative(-11.0f, -11.0f, -28.0f, -11.0f) reflectiveQuadToRelative(-28.0f, 11.0f) lineTo(164.0f, 692.0f) quadToRelative(-11.0f, 11.0f, -28.0f, 11.0f) reflectiveQuadToRelative(-28.0f, -11.0f) quadToRelative(-11.0f, -11.0f, -11.0f, -28.0f) reflectiveQuadToRelative(11.0f, -28.0f) lineToRelative(184.0f, -184.0f) quadToRelative(35.0f, -35.0f, 85.0f, -35.0f) reflectiveQuadToRelative(85.0f, 35.0f) lineToRelative(46.0f, 46.0f) quadToRelative(12.0f, 12.0f, 28.5f, 12.0f) reflectiveQuadToRelative(28.5f, -12.0f) lineToRelative(178.0f, -178.0f) horizontalLineToRelative(-63.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(640.0f, 280.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(680.0f, 240.0f) horizontalLineToRelative(160.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(880.0f, 280.0f) verticalLineToRelative(160.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(840.0f, 480.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(800.0f, 440.0f) verticalLineToRelative(-63.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDoubleArrow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FreeDoubleArrow: ImageVector by lazy { Builder( name = "FreeDoubleArrow", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(21.7125f, 6.2875f) curveTo(21.5208f, 6.0958f, 21.2833f, 6.0f, 21.0f, 6.0f) horizontalLineToRelative(-4.0f) curveToRelative(-0.2833f, 0.0f, -0.5208f, 0.0958f, -0.7125f, 0.2875f) curveTo(16.0958f, 6.4791f, 16.0f, 6.7167f, 16.0f, 7.0f) reflectiveCurveToRelative(0.0958f, 0.5208f, 0.2875f, 0.7125f) curveTo(16.4792f, 7.9042f, 16.7167f, 8.0f, 17.0f, 8.0f) horizontalLineToRelative(1.575f) lineTo(14.125f, 12.45f) curveTo(13.925f, 12.65f, 13.6875f, 12.75f, 13.4125f, 12.75f) curveToRelative(-0.275f, 0.0f, -0.5125f, -0.1f, -0.7125f, -0.3f) lineToRelative(-1.15f, -1.15f) curveToRelative(-0.5833f, -0.5833f, -1.2916f, -0.875f, -2.125f, -0.875f) curveToRelative(-0.8333f, 0.0f, -1.5416f, 0.2917f, -2.125f, 0.875f) lineTo(4.2344f, 14.3656f) lineToRelative(-0.2177f, 0.2165f) verticalLineToRelative(-1.5488f) curveToRelative(0.0f, -0.2786f, -0.0942f, -0.5122f, -0.2827f, -0.7007f) curveToRelative(-0.1885f, -0.1884f, -0.4221f, -0.2827f, -0.7007f, -0.2827f) curveToRelative(-0.2786f, 0.0f, -0.5121f, 0.0942f, -0.7006f, 0.2827f) curveToRelative(-0.1885f, 0.1885f, -0.2827f, 0.4221f, -0.2827f, 0.7007f) verticalLineToRelative(3.9333f) curveToRelative(0.0f, 0.2786f, 0.0942f, 0.5121f, 0.2827f, 0.7006f) curveToRelative(0.1885f, 0.1885f, 0.4221f, 0.2828f, 0.7006f, 0.2828f) horizontalLineToRelative(3.9333f) curveToRelative(0.2786f, 0.0f, 0.5121f, -0.0942f, 0.7006f, -0.2828f) curveToRelative(0.1885f, -0.1885f, 0.2827f, -0.422f, 0.2827f, -0.7006f) curveToRelative(0.0f, -0.2786f, -0.0942f, -0.5122f, -0.2827f, -0.7007f) curveToRelative(-0.1885f, -0.1884f, -0.422f, -0.2827f, -0.7006f, -0.2827f) horizontalLineTo(5.4179f) lineToRelative(1.4227f, -1.4227f) curveTo(6.8403f, 14.5605f, 6.8402f, 14.5602f, 6.84f, 14.5601f) lineToRelative(1.86f, -1.86f) curveToRelative(0.1833f, -0.1833f, 0.4166f, -0.275f, 0.7f, -0.275f) curveToRelative(0.2833f, 0.0f, 0.5167f, 0.0917f, 0.7f, 0.275f) lineTo(11.275f, 13.875f) curveToRelative(0.5833f, 0.5833f, 1.2916f, 0.875f, 2.125f, 0.875f) curveToRelative(0.8333f, 0.0f, 1.5416f, -0.2917f, 2.125f, -0.875f) lineTo(20.0f, 9.425f) verticalLineTo(11.0f) curveToRelative(0.0f, 0.2833f, 0.0958f, 0.5208f, 0.2875f, 0.7125f) curveTo(20.4792f, 11.9042f, 20.7167f, 12.0f, 21.0f, 12.0f) reflectiveCurveToRelative(0.5208f, -0.0958f, 0.7125f, -0.2875f) curveTo(21.9042f, 11.5208f, 22.0f, 11.2833f, 22.0f, 11.0f) verticalLineTo(7.0f) curveTo(22.0f, 6.7167f, 21.9042f, 6.4791f, 21.7125f, 6.2875f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/FreeDraw.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.FreeDraw: ImageVector by lazy { Builder( name = "FreeDraw", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(554.0f, 840.0f) quadToRelative(-54.0f, 0.0f, -91.0f, -37.0f) reflectiveQuadToRelative(-37.0f, -89.0f) quadToRelative(0.0f, -76.0f, 61.5f, -137.5f) reflectiveQuadTo(641.0f, 500.0f) quadToRelative(-3.0f, -36.0f, -18.0f, -54.5f) reflectiveQuadTo(582.0f, 427.0f) quadToRelative(-30.0f, 0.0f, -65.0f, 25.0f) reflectiveQuadToRelative(-83.0f, 82.0f) quadToRelative(-78.0f, 93.0f, -114.5f, 121.0f) reflectiveQuadTo(241.0f, 683.0f) quadToRelative(-51.0f, 0.0f, -86.0f, -38.0f) reflectiveQuadToRelative(-35.0f, -92.0f) quadToRelative(0.0f, -54.0f, 23.5f, -110.5f) reflectiveQuadTo(223.0f, 307.0f) quadToRelative(19.0f, -26.0f, 28.0f, -44.0f) reflectiveQuadToRelative(9.0f, -29.0f) quadToRelative(0.0f, -7.0f, -2.5f, -10.5f) reflectiveQuadTo(250.0f, 220.0f) quadToRelative(-10.0f, 0.0f, -25.0f, 12.5f) reflectiveQuadTo(190.0f, 271.0f) lineToRelative(-70.0f, -71.0f) quadToRelative(32.0f, -39.0f, 65.0f, -59.5f) reflectiveQuadToRelative(65.0f, -20.5f) quadToRelative(46.0f, 0.0f, 78.0f, 32.0f) reflectiveQuadToRelative(32.0f, 80.0f) quadToRelative(0.0f, 29.0f, -15.0f, 64.0f) reflectiveQuadToRelative(-50.0f, 84.0f) quadToRelative(-38.0f, 54.0f, -56.5f, 95.0f) reflectiveQuadTo(220.0f, 547.0f) quadToRelative(0.0f, 17.0f, 5.5f, 26.5f) reflectiveQuadTo(241.0f, 583.0f) quadToRelative(10.0f, 0.0f, 17.5f, -5.5f) reflectiveQuadTo(286.0f, 551.0f) quadToRelative(13.0f, -14.0f, 31.0f, -34.5f) reflectiveQuadToRelative(44.0f, -50.5f) quadToRelative(63.0f, -75.0f, 114.0f, -107.0f) reflectiveQuadToRelative(107.0f, -32.0f) quadToRelative(67.0f, 0.0f, 110.0f, 45.0f) reflectiveQuadToRelative(49.0f, 123.0f) horizontalLineToRelative(99.0f) verticalLineToRelative(100.0f) horizontalLineToRelative(-99.0f) quadToRelative(-8.0f, 112.0f, -58.5f, 178.5f) reflectiveQuadTo(554.0f, 840.0f) close() moveTo(556.0f, 740.0f) quadToRelative(32.0f, 0.0f, 54.0f, -36.5f) reflectiveQuadTo(640.0f, 602.0f) quadToRelative(-46.0f, 11.0f, -80.0f, 43.5f) reflectiveQuadTo(526.0f, 710.0f) quadToRelative(0.0f, 14.0f, 8.0f, 22.0f) reflectiveQuadToRelative(22.0f, 8.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Github.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Github: ImageVector by lazy { ImageVector.Builder( name = "Github", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(12.0f, 2.0f) arcTo( 10.0f, 10.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 2.0f, y1 = 12.0f ) curveTo(2.0f, 16.42f, 4.87f, 20.17f, 8.84f, 21.5f) curveTo(9.34f, 21.58f, 9.5f, 21.27f, 9.5f, 21.0f) curveTo(9.5f, 20.77f, 9.5f, 20.14f, 9.5f, 19.31f) curveTo(6.73f, 19.91f, 6.14f, 17.97f, 6.14f, 17.97f) curveTo(5.68f, 16.81f, 5.03f, 16.5f, 5.03f, 16.5f) curveTo(4.12f, 15.88f, 5.1f, 15.9f, 5.1f, 15.9f) curveTo(6.1f, 15.97f, 6.63f, 16.93f, 6.63f, 16.93f) curveTo(7.5f, 18.45f, 8.97f, 18.0f, 9.54f, 17.76f) curveTo(9.63f, 17.11f, 9.89f, 16.67f, 10.17f, 16.42f) curveTo(7.95f, 16.17f, 5.62f, 15.31f, 5.62f, 11.5f) curveTo(5.62f, 10.39f, 6.0f, 9.5f, 6.65f, 8.79f) curveTo(6.55f, 8.54f, 6.2f, 7.5f, 6.75f, 6.15f) curveTo(6.75f, 6.15f, 7.59f, 5.88f, 9.5f, 7.17f) curveTo(10.29f, 6.95f, 11.15f, 6.84f, 12.0f, 6.84f) curveTo(12.85f, 6.84f, 13.71f, 6.95f, 14.5f, 7.17f) curveTo(16.41f, 5.88f, 17.25f, 6.15f, 17.25f, 6.15f) curveTo(17.8f, 7.5f, 17.45f, 8.54f, 17.35f, 8.79f) curveTo(18.0f, 9.5f, 18.38f, 10.39f, 18.38f, 11.5f) curveTo(18.38f, 15.32f, 16.04f, 16.16f, 13.81f, 16.41f) curveTo(14.17f, 16.72f, 14.5f, 17.33f, 14.5f, 18.26f) curveTo(14.5f, 19.6f, 14.5f, 20.68f, 14.5f, 21.0f) curveTo(14.5f, 21.27f, 14.66f, 21.59f, 15.17f, 21.5f) curveTo(19.14f, 20.16f, 22.0f, 16.42f, 22.0f, 12.0f) arcTo( 10.0f, 10.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 12.0f, y1 = 2.0f ) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Glyphs.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Glyphs: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Glyphs", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(224f, 82f) lineTo(302f, 82f) lineTo(437f, 442f) lineTo(362f, 442f) lineTo(332f, 356f) lineTo(194f, 356f) lineTo(164f, 442f) lineTo(89f, 442f) lineTo(224f, 82f) close() moveTo(229f, 484f) quadTo(271f, 484f, 300f, 513f) quadTo(329f, 542f, 329f, 584f) quadTo(329f, 604f, 321.5f, 622.5f) quadTo(314f, 641f, 300f, 655f) lineTo(285f, 669f) lineTo(313f, 697f) lineTo(370f, 641f) lineTo(427f, 698f) lineTo(371f, 754f) lineTo(427f, 811f) lineTo(370f, 867f) lineTo(314f, 811f) lineTo(272f, 853f) quadTo(257f, 868f, 238f, 875.5f) quadTo(219f, 883f, 198f, 883f) quadTo(156f, 883f, 128f, 853.5f) quadTo(100f, 824f, 100f, 782f) quadTo(100f, 762f, 108f, 743.5f) quadTo(116f, 725f, 130f, 711f) lineTo(172f, 669f) lineTo(158f, 655f) quadTo(144f, 641f, 136f, 623f) quadTo(128f, 605f, 128f, 585f) quadTo(128f, 543f, 157.5f, 513.5f) quadTo(187f, 484f, 229f, 484f) close() moveTo(229f, 726f) lineTo(186f, 768f) quadTo(183f, 771f, 182f, 774.5f) quadTo(181f, 778f, 181f, 782f) quadTo(181f, 790f, 186.5f, 796f) quadTo(192f, 802f, 200f, 802f) quadTo(204f, 802f, 207.5f, 800.5f) quadTo(211f, 799f, 214f, 796f) lineTo(257f, 754f) lineTo(229f, 726f) close() moveTo(228f, 564f) quadTo(220f, 564f, 214.5f, 570f) quadTo(209f, 576f, 209f, 584f) quadTo(209f, 588f, 210f, 591.5f) quadTo(211f, 595f, 214f, 598f) lineTo(229f, 612f) lineTo(242f, 599f) quadTo(245f, 596f, 246.5f, 592.5f) quadTo(248f, 589f, 248f, 585f) quadTo(248f, 577f, 242f, 570.5f) quadTo(236f, 564f, 228f, 564f) close() moveTo(261f, 160f) lineTo(215f, 294f) lineTo(311f, 294f) lineTo(265f, 160f) lineTo(261f, 160f) close() moveTo(699f, 55f) quadTo(707f, 68f, 712.5f, 82f) quadTo(718f, 96f, 722f, 110f) lineTo(679f, 123f) lineTo(880f, 123f) lineTo(880f, 203f) lineTo(813f, 203f) quadTo(802f, 236f, 784.5f, 265.5f) quadTo(767f, 295f, 744f, 320f) quadTo(771f, 336f, 800f, 346.5f) quadTo(829f, 357f, 860f, 365f) lineTo(841f, 442f) quadTo(798f, 431f, 757.5f, 415f) quadTo(717f, 399f, 680f, 374f) quadTo(643f, 398f, 602.5f, 414.5f) quadTo(562f, 431f, 519f, 442f) lineTo(501f, 365f) quadTo(531f, 357f, 560f, 346.5f) quadTo(589f, 336f, 616f, 320f) quadTo(593f, 295f, 575.5f, 265.5f) quadTo(558f, 236f, 548f, 203f) lineTo(480f, 203f) lineTo(480f, 123f) lineTo(656f, 123f) quadTo(653f, 110f, 648f, 97.5f) quadTo(643f, 85f, 638f, 73f) lineTo(699f, 55f) close() moveTo(803f, 499f) lineTo(860f, 555f) lineTo(549f, 866f) lineTo(492f, 810f) lineTo(803f, 499f) close() moveTo(580f, 522f) quadTo(605f, 522f, 622.5f, 539.5f) quadTo(640f, 557f, 640f, 582f) quadTo(640f, 607f, 622.5f, 624.5f) quadTo(605f, 642f, 580f, 642f) quadTo(555f, 642f, 537.5f, 624.5f) quadTo(520f, 607f, 520f, 582f) quadTo(520f, 557f, 537.5f, 539.5f) quadTo(555f, 522f, 580f, 522f) close() moveTo(633f, 203f) quadTo(641f, 222f, 653f, 239f) quadTo(665f, 256f, 680f, 271f) quadTo(695f, 256f, 707f, 239f) quadTo(719f, 222f, 728f, 203f) lineTo(633f, 203f) close() moveTo(780f, 722f) quadTo(805f, 722f, 822.5f, 739.5f) quadTo(840f, 757f, 840f, 782f) quadTo(840f, 807f, 822.5f, 824.5f) quadTo(805f, 842f, 780f, 842f) quadTo(755f, 842f, 737.5f, 824.5f) quadTo(720f, 807f, 720f, 782f) quadTo(720f, 757f, 737.5f, 739.5f) quadTo(755f, 722f, 780f, 722f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/GooglePlay.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.GooglePlay: ImageVector by lazy { ImageVector.Builder( name = "Google Play", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(3.0f, 20.5f) verticalLineTo(3.5f) curveTo(3.0f, 2.91f, 3.34f, 2.39f, 3.84f, 2.15f) lineTo(13.69f, 12.0f) lineTo(3.84f, 21.85f) curveTo(3.34f, 21.6f, 3.0f, 21.09f, 3.0f, 20.5f) moveTo(16.81f, 15.12f) lineTo(6.05f, 21.34f) lineTo(14.54f, 12.85f) lineTo(16.81f, 15.12f) moveTo(20.16f, 10.81f) curveTo(20.5f, 11.08f, 20.75f, 11.5f, 20.75f, 12.0f) curveTo(20.75f, 12.5f, 20.53f, 12.9f, 20.18f, 13.18f) lineTo(17.89f, 14.5f) lineTo(15.39f, 12.0f) lineTo(17.89f, 9.5f) lineTo(20.16f, 10.81f) moveTo(6.05f, 2.66f) lineTo(16.81f, 8.88f) lineTo(14.54f, 11.15f) lineTo(6.05f, 2.66f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Gradient.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Gradient: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Gradient", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(200f, 840f) quadTo(167f, 840f, 143.5f, 816.5f) quadTo(120f, 793f, 120f, 760f) lineTo(120f, 200f) quadTo(120f, 167f, 143.5f, 143.5f) quadTo(167f, 120f, 200f, 120f) lineTo(760f, 120f) quadTo(793f, 120f, 816.5f, 143.5f) quadTo(840f, 167f, 840f, 200f) lineTo(840f, 760f) quadTo(840f, 793f, 816.5f, 816.5f) quadTo(793f, 840f, 760f, 840f) lineTo(200f, 840f) close() moveTo(440f, 440f) lineTo(440f, 520f) lineTo(520f, 520f) lineTo(520f, 440f) lineTo(440f, 440f) close() moveTo(280f, 440f) lineTo(280f, 520f) lineTo(360f, 520f) lineTo(360f, 440f) lineTo(280f, 440f) close() moveTo(360f, 520f) lineTo(360f, 600f) lineTo(440f, 600f) lineTo(440f, 520f) lineTo(360f, 520f) close() moveTo(520f, 520f) lineTo(520f, 600f) lineTo(600f, 600f) lineTo(600f, 520f) lineTo(520f, 520f) close() moveTo(200f, 520f) lineTo(200f, 600f) lineTo(280f, 600f) lineTo(280f, 520f) lineTo(200f, 520f) close() moveTo(600f, 440f) lineTo(600f, 520f) lineTo(680f, 520f) lineTo(680f, 600f) lineTo(760f, 600f) lineTo(760f, 520f) lineTo(680f, 520f) lineTo(680f, 440f) lineTo(600f, 440f) close() moveTo(280f, 600f) lineTo(280f, 680f) lineTo(200f, 680f) lineTo(200f, 760f) quadTo(200f, 760f, 200f, 760f) quadTo(200f, 760f, 200f, 760f) lineTo(280f, 760f) lineTo(280f, 680f) lineTo(360f, 680f) lineTo(360f, 760f) lineTo(440f, 760f) lineTo(440f, 680f) lineTo(520f, 680f) lineTo(520f, 760f) lineTo(600f, 760f) lineTo(600f, 680f) lineTo(680f, 680f) lineTo(680f, 760f) lineTo(760f, 760f) quadTo(760f, 760f, 760f, 760f) quadTo(760f, 760f, 760f, 760f) lineTo(760f, 680f) lineTo(680f, 680f) lineTo(680f, 600f) lineTo(600f, 600f) lineTo(600f, 680f) lineTo(520f, 680f) lineTo(520f, 600f) lineTo(440f, 600f) lineTo(440f, 680f) lineTo(360f, 680f) lineTo(360f, 600f) lineTo(280f, 600f) close() moveTo(760f, 440f) lineTo(760f, 440f) lineTo(760f, 520f) lineTo(760f, 520f) lineTo(760f, 440f) close() moveTo(760f, 600f) lineTo(760f, 680f) lineTo(760f, 680f) lineTo(760f, 600f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Group.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Group: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Group", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(1f, 1f) verticalLineTo(5f) horizontalLineTo(2f) verticalLineTo(19f) horizontalLineTo(1f) verticalLineTo(23f) horizontalLineTo(5f) verticalLineTo(22f) horizontalLineTo(19f) verticalLineTo(23f) horizontalLineTo(23f) verticalLineTo(19f) horizontalLineTo(22f) verticalLineTo(5f) horizontalLineTo(23f) verticalLineTo(1f) horizontalLineTo(19f) verticalLineTo(2f) horizontalLineTo(5f) verticalLineTo(1f) moveTo(5f, 4f) horizontalLineTo(19f) verticalLineTo(5f) horizontalLineTo(20f) verticalLineTo(19f) horizontalLineTo(19f) verticalLineTo(20f) horizontalLineTo(5f) verticalLineTo(19f) horizontalLineTo(4f) verticalLineTo(5f) horizontalLineTo(5f) moveTo(6f, 6f) verticalLineTo(14f) horizontalLineTo(9f) verticalLineTo(18f) horizontalLineTo(18f) verticalLineTo(9f) horizontalLineTo(14f) verticalLineTo(6f) moveTo(8f, 8f) horizontalLineTo(12f) verticalLineTo(12f) horizontalLineTo(8f) moveTo(14f, 11f) horizontalLineTo(16f) verticalLineTo(16f) horizontalLineTo(11f) verticalLineTo(14f) horizontalLineTo(14f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HandshakeAlt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.HandshakeAlt: ImageVector by lazy { ImageVector.Builder( name = "HandshakeAltOutline", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(21.71f, 8.71f) curveTo(22.96f, 7.46f, 22.39f, 6f, 21.71f, 5.29f) lineTo(18.71f, 2.29f) curveTo(17.45f, 1.04f, 16f, 1.61f, 15.29f, 2.29f) lineTo(13.59f, 4f) horizontalLineTo(11f) curveTo(9.1f, 4f, 8f, 5f, 7.44f, 6.15f) lineTo(3f, 10.59f) verticalLineTo(14.59f) lineTo(2.29f, 15.29f) curveTo(1.04f, 16.55f, 1.61f, 18f, 2.29f, 18.71f) lineTo(5.29f, 21.71f) curveTo(5.83f, 22.25f, 6.41f, 22.45f, 6.96f, 22.45f) curveTo(7.67f, 22.45f, 8.32f, 22.1f, 8.71f, 21.71f) lineTo(11.41f, 19f) horizontalLineTo(15f) curveTo(16.7f, 19f, 17.56f, 17.94f, 17.87f, 16.9f) curveTo(19f, 16.6f, 19.62f, 15.74f, 19.87f, 14.9f) curveTo(21.42f, 14.5f, 22f, 13.03f, 22f, 12f) verticalLineTo(9f) horizontalLineTo(21.41f) lineTo(21.71f, 8.71f) moveTo(20f, 12f) curveTo(20f, 12.45f, 19.81f, 13f, 19f, 13f) lineTo(18f, 13f) lineTo(18f, 14f) curveTo(18f, 14.45f, 17.81f, 15f, 17f, 15f) lineTo(16f, 15f) lineTo(16f, 16f) curveTo(16f, 16.45f, 15.81f, 17f, 15f, 17f) horizontalLineTo(10.59f) lineTo(7.31f, 20.28f) curveTo(7f, 20.57f, 6.82f, 20.4f, 6.71f, 20.29f) lineTo(3.72f, 17.31f) curveTo(3.43f, 17f, 3.6f, 16.82f, 3.71f, 16.71f) lineTo(5f, 15.41f) verticalLineTo(11.41f) lineTo(7f, 9.41f) verticalLineTo(11f) curveTo(7f, 12.21f, 7.8f, 14f, 10f, 14f) reflectiveCurveTo(13f, 12.21f, 13f, 11f) horizontalLineTo(20f) verticalLineTo(12f) moveTo(20.29f, 7.29f) lineTo(18.59f, 9f) horizontalLineTo(11f) verticalLineTo(11f) curveTo(11f, 11.45f, 10.81f, 12f, 10f, 12f) reflectiveCurveTo(9f, 11.45f, 9f, 11f) verticalLineTo(8f) curveTo(9f, 7.54f, 9.17f, 6f, 11f, 6f) horizontalLineTo(14.41f) lineTo(16.69f, 3.72f) curveTo(17f, 3.43f, 17.18f, 3.6f, 17.29f, 3.71f) lineTo(20.28f, 6.69f) curveTo(20.57f, 7f, 20.4f, 7.18f, 20.29f, 7.29f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HardDrive.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.HardDrive: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.HardDrive", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(680f, 640f) quadTo(705f, 640f, 722.5f, 623f) quadTo(740f, 606f, 740f, 580f) quadTo(740f, 555f, 722.5f, 537.5f) quadTo(705f, 520f, 680f, 520f) quadTo(654f, 520f, 637f, 537.5f) quadTo(620f, 555f, 620f, 580f) quadTo(620f, 606f, 637f, 623f) quadTo(654f, 640f, 680f, 640f) close() moveTo(80f, 360f) lineTo(216f, 224f) quadTo(227f, 213f, 241.5f, 206.5f) quadTo(256f, 200f, 273f, 200f) lineTo(686f, 200f) quadTo(703f, 200f, 717.5f, 206.5f) quadTo(732f, 213f, 743f, 224f) lineTo(880f, 360f) lineTo(80f, 360f) close() moveTo(160f, 760f) quadTo(126f, 760f, 103f, 737f) quadTo(80f, 714f, 80f, 680f) lineTo(80f, 440f) lineTo(880f, 440f) lineTo(880f, 680f) quadTo(880f, 714f, 856.5f, 737f) quadTo(833f, 760f, 800f, 760f) lineTo(160f, 760f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HighRes.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.HighRes: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.HighRes", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(180f, 740f) lineTo(240f, 740f) lineTo(240f, 660f) lineTo(264f, 660f) lineTo(300f, 740f) lineTo(360f, 740f) lineTo(324f, 656f) quadTo(339f, 647f, 349.5f, 632.5f) quadTo(360f, 618f, 360f, 600f) lineTo(360f, 560f) quadTo(360f, 535f, 342.5f, 517.5f) quadTo(325f, 500f, 300f, 500f) lineTo(180f, 500f) lineTo(180f, 740f) close() moveTo(400f, 740f) lineTo(560f, 740f) lineTo(560f, 680f) lineTo(460f, 680f) lineTo(460f, 650f) lineTo(540f, 650f) lineTo(540f, 590f) lineTo(460f, 590f) lineTo(460f, 560f) lineTo(560f, 560f) lineTo(560f, 500f) lineTo(400f, 500f) lineTo(400f, 740f) close() moveTo(600f, 740f) lineTo(740f, 740f) quadTo(757f, 740f, 768.5f, 728.5f) quadTo(780f, 717f, 780f, 700f) lineTo(780f, 640f) quadTo(780f, 623f, 768.5f, 611.5f) quadTo(757f, 600f, 740f, 600f) lineTo(660f, 600f) lineTo(660f, 560f) lineTo(780f, 560f) lineTo(780f, 500f) lineTo(640f, 500f) quadTo(623f, 500f, 611.5f, 511.5f) quadTo(600f, 523f, 600f, 540f) lineTo(600f, 600f) quadTo(600f, 617f, 611.5f, 628.5f) quadTo(623f, 640f, 640f, 640f) lineTo(720f, 640f) lineTo(720f, 680f) lineTo(600f, 680f) lineTo(600f, 740f) close() moveTo(240f, 600f) lineTo(240f, 560f) lineTo(300f, 560f) quadTo(300f, 560f, 300f, 560f) quadTo(300f, 560f, 300f, 560f) lineTo(300f, 600f) quadTo(300f, 600f, 300f, 600f) quadTo(300f, 600f, 300f, 600f) lineTo(240f, 600f) close() moveTo(300f, 460f) lineTo(360f, 460f) lineTo(360f, 370f) lineTo(420f, 370f) lineTo(420f, 460f) lineTo(480f, 460f) lineTo(480f, 220f) lineTo(420f, 220f) lineTo(420f, 310f) lineTo(360f, 310f) lineTo(360f, 220f) lineTo(300f, 220f) lineTo(300f, 460f) close() moveTo(580f, 460f) lineTo(640f, 460f) lineTo(640f, 220f) lineTo(580f, 220f) lineTo(580f, 460f) close() moveTo(120f, 840f) quadTo(87f, 840f, 63.5f, 816.5f) quadTo(40f, 793f, 40f, 760f) lineTo(40f, 200f) quadTo(40f, 167f, 63.5f, 143.5f) quadTo(87f, 120f, 120f, 120f) lineTo(840f, 120f) quadTo(873f, 120f, 896.5f, 143.5f) quadTo(920f, 167f, 920f, 200f) lineTo(920f, 760f) quadTo(920f, 793f, 896.5f, 816.5f) quadTo(873f, 840f, 840f, 840f) lineTo(120f, 840f) close() moveTo(120f, 760f) lineTo(840f, 760f) quadTo(840f, 760f, 840f, 760f) quadTo(840f, 760f, 840f, 760f) lineTo(840f, 200f) quadTo(840f, 200f, 840f, 200f) quadTo(840f, 200f, 840f, 200f) lineTo(120f, 200f) quadTo(120f, 200f, 120f, 200f) quadTo(120f, 200f, 120f, 200f) lineTo(120f, 760f) quadTo(120f, 760f, 120f, 760f) quadTo(120f, 760f, 120f, 760f) close() moveTo(120f, 760f) quadTo(120f, 760f, 120f, 760f) quadTo(120f, 760f, 120f, 760f) lineTo(120f, 200f) quadTo(120f, 200f, 120f, 200f) quadTo(120f, 200f, 120f, 200f) lineTo(120f, 200f) quadTo(120f, 200f, 120f, 200f) quadTo(120f, 200f, 120f, 200f) lineTo(120f, 760f) quadTo(120f, 760f, 120f, 760f) quadTo(120f, 760f, 120f, 760f) close() } }.build() } val Icons.Rounded.HighRes: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.HighRes", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(180f, 740f) lineTo(240f, 740f) lineTo(240f, 660f) lineTo(264f, 660f) lineTo(300f, 740f) lineTo(360f, 740f) lineTo(324f, 656f) quadTo(339f, 647f, 349.5f, 632.5f) quadTo(360f, 618f, 360f, 600f) lineTo(360f, 560f) quadTo(360f, 535f, 342.5f, 517.5f) quadTo(325f, 500f, 300f, 500f) lineTo(180f, 500f) lineTo(180f, 740f) close() moveTo(400f, 740f) lineTo(560f, 740f) lineTo(560f, 680f) lineTo(460f, 680f) lineTo(460f, 650f) lineTo(540f, 650f) lineTo(540f, 590f) lineTo(460f, 590f) lineTo(460f, 560f) lineTo(560f, 560f) lineTo(560f, 500f) lineTo(400f, 500f) lineTo(400f, 740f) close() moveTo(600f, 740f) lineTo(740f, 740f) quadTo(757f, 740f, 768.5f, 728.5f) quadTo(780f, 717f, 780f, 700f) lineTo(780f, 640f) quadTo(780f, 623f, 768.5f, 611.5f) quadTo(757f, 600f, 740f, 600f) lineTo(660f, 600f) lineTo(660f, 560f) lineTo(780f, 560f) lineTo(780f, 500f) lineTo(640f, 500f) quadTo(623f, 500f, 611.5f, 511.5f) quadTo(600f, 523f, 600f, 540f) lineTo(600f, 600f) quadTo(600f, 617f, 611.5f, 628.5f) quadTo(623f, 640f, 640f, 640f) lineTo(720f, 640f) lineTo(720f, 680f) lineTo(600f, 680f) lineTo(600f, 740f) close() moveTo(240f, 600f) lineTo(240f, 560f) lineTo(300f, 560f) quadTo(300f, 560f, 300f, 560f) quadTo(300f, 560f, 300f, 560f) lineTo(300f, 600f) quadTo(300f, 600f, 300f, 600f) quadTo(300f, 600f, 300f, 600f) lineTo(240f, 600f) close() moveTo(300f, 460f) lineTo(360f, 460f) lineTo(360f, 370f) lineTo(420f, 370f) lineTo(420f, 460f) lineTo(480f, 460f) lineTo(480f, 220f) lineTo(420f, 220f) lineTo(420f, 310f) lineTo(360f, 310f) lineTo(360f, 220f) lineTo(300f, 220f) lineTo(300f, 460f) close() moveTo(580f, 460f) lineTo(640f, 460f) lineTo(640f, 220f) lineTo(580f, 220f) lineTo(580f, 460f) close() moveTo(120f, 840f) quadTo(87f, 840f, 63.5f, 816.5f) quadTo(40f, 793f, 40f, 760f) lineTo(40f, 200f) quadTo(40f, 167f, 63.5f, 143.5f) quadTo(87f, 120f, 120f, 120f) lineTo(840f, 120f) quadTo(873f, 120f, 896.5f, 143.5f) quadTo(920f, 167f, 920f, 200f) lineTo(920f, 760f) quadTo(920f, 793f, 896.5f, 816.5f) quadTo(873f, 840f, 840f, 840f) lineTo(120f, 840f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Highlighter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Highlighter: ImageVector by lazy { Builder( name = "Highlighter", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(544f, 560f) lineTo(440f, 456f) lineTo(240f, 656f) quadTo(240f, 656f, 240f, 656f) quadTo(240f, 656f, 240f, 656f) lineTo(344f, 760f) quadTo(344f, 760f, 344f, 760f) quadTo(344f, 760f, 344f, 760f) lineTo(544f, 560f) close() moveTo(497f, 399f) lineTo(601f, 503f) lineTo(800f, 304f) quadTo(800f, 304f, 800f, 304f) quadTo(800f, 304f, 800f, 304f) lineTo(696f, 200f) quadTo(696f, 200f, 696f, 200f) quadTo(696f, 200f, 696f, 200f) lineTo(497f, 399f) close() moveTo(413f, 371f) lineTo(629f, 587f) lineTo(400f, 816f) quadTo(376f, 840f, 344f, 840f) quadTo(312f, 840f, 288f, 816f) lineTo(286f, 814f) lineTo(283f, 817f) quadTo(272f, 828f, 257.5f, 834f) quadTo(243f, 840f, 227f, 840f) lineTo(108f, 840f) quadTo(94f, 840f, 89f, 828f) quadTo(84f, 816f, 94f, 806f) lineTo(186f, 714f) lineTo(184f, 712f) quadTo(160f, 688f, 160f, 656f) quadTo(160f, 624f, 184f, 600f) lineTo(413f, 371f) close() moveTo(413f, 371f) lineTo(640f, 144f) quadTo(664f, 120f, 696f, 120f) quadTo(728f, 120f, 752f, 144f) lineTo(856f, 248f) quadTo(880f, 272f, 880f, 304f) quadTo(880f, 336f, 856f, 360f) lineTo(629f, 587f) lineTo(413f, 371f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HistoryCreate.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.HistoryCreate: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.HistoryEdu", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(320f, 800f) quadTo(287f, 800f, 263.5f, 776.5f) quadTo(240f, 753f, 240f, 720f) lineTo(240f, 600f) lineTo(360f, 600f) lineTo(360f, 510f) quadTo(325f, 508f, 293.5f, 494.5f) quadTo(262f, 481f, 236f, 454f) lineTo(236f, 410f) lineTo(190f, 410f) lineTo(60f, 280f) quadTo(96f, 234f, 149f, 215f) quadTo(202f, 196f, 256f, 196f) quadTo(283f, 196f, 308.5f, 200f) quadTo(334f, 204f, 360f, 215f) lineTo(360f, 160f) lineTo(840f, 160f) lineTo(840f, 680f) quadTo(840f, 730f, 805f, 765f) quadTo(770f, 800f, 720f, 800f) lineTo(320f, 800f) close() moveTo(440f, 600f) lineTo(680f, 600f) lineTo(680f, 680f) quadTo(680f, 697f, 691.5f, 708.5f) quadTo(703f, 720f, 720f, 720f) quadTo(737f, 720f, 748.5f, 708.5f) quadTo(760f, 697f, 760f, 680f) lineTo(760f, 240f) lineTo(440f, 240f) lineTo(440f, 264f) lineTo(680f, 504f) lineTo(680f, 560f) lineTo(624f, 560f) lineTo(510f, 446f) lineTo(502f, 454f) quadTo(488f, 468f, 472.5f, 479f) quadTo(457f, 490f, 440f, 496f) lineTo(440f, 600f) close() moveTo(224f, 330f) lineTo(316f, 330f) lineTo(316f, 416f) quadTo(328f, 424f, 341f, 427f) quadTo(354f, 430f, 368f, 430f) quadTo(391f, 430f, 409.5f, 423f) quadTo(428f, 416f, 446f, 398f) lineTo(454f, 390f) lineTo(398f, 334f) quadTo(369f, 305f, 333f, 290.5f) quadTo(297f, 276f, 256f, 276f) quadTo(236f, 276f, 218f, 279f) quadTo(200f, 282f, 182f, 288f) lineTo(224f, 330f) close() } }.build() } val Icons.Outlined.HistoryCreate: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.HistoryEdu", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(320f, 800f) quadTo(287f, 800f, 263.5f, 776.5f) quadTo(240f, 753f, 240f, 720f) lineTo(240f, 600f) lineTo(360f, 600f) lineTo(360f, 510f) quadTo(325f, 508f, 293.5f, 494.5f) quadTo(262f, 481f, 236f, 454f) lineTo(236f, 410f) lineTo(190f, 410f) lineTo(60f, 280f) quadTo(96f, 234f, 149f, 215f) quadTo(202f, 196f, 256f, 196f) quadTo(283f, 196f, 308.5f, 200f) quadTo(334f, 204f, 360f, 215f) lineTo(360f, 160f) lineTo(840f, 160f) lineTo(840f, 680f) quadTo(840f, 730f, 805f, 765f) quadTo(770f, 800f, 720f, 800f) lineTo(320f, 800f) close() moveTo(440f, 600f) lineTo(680f, 600f) lineTo(680f, 680f) quadTo(680f, 697f, 691.5f, 708.5f) quadTo(703f, 720f, 720f, 720f) quadTo(737f, 720f, 748.5f, 708.5f) quadTo(760f, 697f, 760f, 680f) lineTo(760f, 240f) lineTo(440f, 240f) lineTo(440f, 264f) lineTo(680f, 504f) lineTo(680f, 560f) lineTo(624f, 560f) lineTo(510f, 446f) lineTo(502f, 454f) quadTo(488f, 468f, 472.5f, 479f) quadTo(457f, 490f, 440f, 496f) lineTo(440f, 600f) close() moveTo(224f, 330f) lineTo(316f, 330f) lineTo(316f, 416f) quadTo(328f, 424f, 341f, 427f) quadTo(354f, 430f, 368f, 430f) quadTo(391f, 430f, 409.5f, 423f) quadTo(428f, 416f, 446f, 398f) lineTo(454f, 390f) lineTo(398f, 334f) quadTo(369f, 305f, 333f, 290.5f) quadTo(297f, 276f, 256f, 276f) quadTo(236f, 276f, 218f, 279f) quadTo(200f, 282f, 182f, 288f) lineTo(224f, 330f) close() moveTo(600f, 680f) lineTo(320f, 680f) lineTo(320f, 720f) quadTo(320f, 720f, 320f, 720f) quadTo(320f, 720f, 320f, 720f) lineTo(606f, 720f) quadTo(603f, 711f, 601.5f, 701f) quadTo(600f, 691f, 600f, 680f) close() moveTo(320f, 720f) quadTo(320f, 711f, 320f, 701f) quadTo(320f, 691f, 320f, 680f) lineTo(320f, 680f) quadTo(320f, 690f, 320f, 700f) quadTo(320f, 710f, 320f, 720f) lineTo(320f, 720f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/HyperOS.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.HyperOS: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.HyperOS", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF1D1D1B)), pathFillType = PathFillType.EvenOdd ) { moveTo(13.899f, 3.031f) curveToRelative(-0.089f, -0f, -0.187f, 0.022f, -0.295f, 0.051f) curveToRelative(-0.192f, 0.051f, -0.346f, 0.104f, -0.425f, 0.242f) curveToRelative(-0.08f, 0.138f, -0.049f, 0.297f, 0.002f, 0.489f) curveToRelative(0.051f, 0.191f, 0.105f, 0.345f, 0.243f, 0.425f) horizontalLineToRelative(0f) curveToRelative(0.138f, 0.079f, 0.298f, 0.049f, 0.49f, -0.002f) curveToRelative(0.192f, -0.051f, 0.345f, -0.106f, 0.425f, -0.243f) curveToRelative(0.08f, -0.138f, 0.049f, -0.297f, -0.002f, -0.488f) curveToRelative(-0.051f, -0.191f, -0.104f, -0.345f, -0.243f, -0.424f) curveToRelative(-0.06f, -0.035f, -0.125f, -0.048f, -0.195f, -0.048f) lineToRelative(0f, -0f) close() moveTo(9.322f, 3.238f) curveToRelative(-0.156f, -0f, -0.277f, 0.104f, -0.415f, 0.242f) curveToRelative(-0.138f, 0.138f, -0.242f, 0.258f, -0.242f, 0.414f) curveToRelative(0.001f, 0.156f, 0.104f, 0.277f, 0.242f, 0.414f) curveToRelative(0.138f, 0.138f, 0.259f, 0.241f, 0.416f, 0.241f) curveToRelative(0.156f, 0f, 0.277f, -0.104f, 0.414f, -0.242f) curveToRelative(0.138f, -0.137f, 0.242f, -0.258f, 0.242f, -0.414f) curveToRelative(0f, -0.156f, -0.105f, -0.277f, -0.243f, -0.414f) curveToRelative(-0.138f, -0.138f, -0.258f, -0.242f, -0.415f, -0.242f) horizontalLineToRelative(-0f) close() moveTo(11.974f, 3.796f) curveToRelative(-0.211f, -0f, -0.373f, 0.141f, -0.559f, 0.326f) curveToRelative(-0.186f, 0.185f, -0.327f, 0.348f, -0.326f, 0.559f) curveToRelative(0f, 0.21f, 0.14f, 0.374f, 0.326f, 0.559f) curveToRelative(0.186f, 0.185f, 0.349f, 0.325f, 0.56f, 0.325f) curveToRelative(0.211f, 0f, 0.373f, -0.141f, 0.559f, -0.326f) curveToRelative(0.185f, -0.185f, 0.327f, -0.347f, 0.326f, -0.558f) curveToRelative(0f, -0.211f, -0.141f, -0.373f, -0.327f, -0.558f) curveToRelative(-0.186f, -0.185f, -0.348f, -0.326f, -0.559f, -0.326f) close() moveTo(8.108f, 4.841f) curveToRelative(-0.091f, 0f, -0.176f, 0.018f, -0.255f, 0.064f) curveToRelative(-0.181f, 0.104f, -0.25f, 0.305f, -0.318f, 0.556f) curveToRelative(-0.067f, 0.25f, -0.108f, 0.459f, -0.003f, 0.64f) curveToRelative(0.105f, 0.181f, 0.306f, 0.251f, 0.557f, 0.319f) curveToRelative(0.251f, 0.067f, 0.46f, 0.106f, 0.641f, 0.002f) curveToRelative(0.181f, -0.104f, 0.251f, -0.305f, 0.318f, -0.556f) curveToRelative(0.067f, -0.251f, 0.108f, -0.459f, 0.003f, -0.64f) curveToRelative(-0.105f, -0.181f, -0.306f, -0.25f, -0.557f, -0.317f) curveToRelative(-0.141f, -0.038f, -0.269f, -0.067f, -0.386f, -0.067f) lineToRelative(0f, 0f) close() moveTo(15.834f, 4.841f) curveToRelative(-0.117f, -0f, -0.245f, 0.029f, -0.386f, 0.067f) curveToRelative(-0.251f, 0.067f, -0.453f, 0.137f, -0.557f, 0.317f) curveToRelative(-0.105f, 0.181f, -0.064f, 0.389f, 0.003f, 0.64f) curveToRelative(0.067f, 0.251f, 0.137f, 0.452f, 0.318f, 0.556f) curveToRelative(0.181f, 0.104f, 0.39f, 0.065f, 0.641f, -0.002f) curveToRelative(0.251f, -0.067f, 0.452f, -0.138f, 0.557f, -0.319f) curveToRelative(0.105f, -0.181f, 0.064f, -0.389f, -0.003f, -0.64f) curveToRelative(-0.067f, -0.25f, -0.137f, -0.452f, -0.318f, -0.556f) curveToRelative(-0.079f, -0.046f, -0.164f, -0.063f, -0.255f, -0.064f) verticalLineToRelative(-0f) close() moveTo(17.563f, 5.054f) curveToRelative(-0.07f, 0f, -0.134f, 0.014f, -0.195f, 0.048f) curveToRelative(-0.138f, 0.08f, -0.191f, 0.233f, -0.242f, 0.424f) curveToRelative(-0.051f, 0.191f, -0.082f, 0.351f, -0.002f, 0.488f) curveToRelative(0.08f, 0.138f, 0.233f, 0.192f, 0.425f, 0.243f) curveToRelative(0.191f, 0.051f, 0.351f, 0.081f, 0.49f, 0.002f) horizontalLineToRelative(0f) curveToRelative(0.138f, -0.079f, 0.192f, -0.233f, 0.243f, -0.425f) curveToRelative(0.051f, -0.191f, 0.082f, -0.351f, 0.002f, -0.489f) curveToRelative(-0.08f, -0.138f, -0.234f, -0.191f, -0.425f, -0.242f) curveToRelative(-0.108f, -0.029f, -0.206f, -0.051f, -0.295f, -0.051f) lineToRelative(0f, 0f) close() moveTo(10.321f, 5.359f) curveToRelative(-0.047f, 0.001f, -0.093f, 0.007f, -0.141f, 0.019f) curveToRelative(-0.252f, 0.067f, -0.4f, 0.287f, -0.562f, 0.567f) curveToRelative(-0.162f, 0.28f, -0.278f, 0.518f, -0.211f, 0.77f) curveToRelative(0.068f, 0.251f, 0.287f, 0.401f, 0.568f, 0.563f) curveToRelative(0.28f, 0.162f, 0.52f, 0.276f, 0.771f, 0.209f) curveToRelative(0.252f, -0.067f, 0.401f, -0.287f, 0.563f, -0.567f) curveToRelative(0.162f, -0.28f, 0.278f, -0.519f, 0.211f, -0.77f) curveToRelative(-0.068f, -0.251f, -0.288f, -0.4f, -0.569f, -0.562f) curveToRelative(-0.228f, -0.132f, -0.429f, -0.233f, -0.631f, -0.23f) lineToRelative(-0f, -0f) close() moveTo(13.618f, 5.359f) curveToRelative(-0.202f, -0.003f, -0.403f, 0.099f, -0.631f, 0.23f) curveToRelative(-0.281f, 0.162f, -0.501f, 0.31f, -0.569f, 0.562f) curveToRelative(-0.068f, 0.251f, 0.049f, 0.49f, 0.211f, 0.77f) curveToRelative(0.162f, 0.28f, 0.311f, 0.5f, 0.563f, 0.567f) curveToRelative(0.251f, 0.067f, 0.491f, -0.048f, 0.771f, -0.209f) curveToRelative(0.281f, -0.162f, 0.5f, -0.312f, 0.568f, -0.563f) curveToRelative(0.068f, -0.251f, -0.049f, -0.49f, -0.211f, -0.77f) curveToRelative(-0.162f, -0.28f, -0.311f, -0.5f, -0.562f, -0.567f) curveToRelative(-0.047f, -0.013f, -0.094f, -0.019f, -0.141f, -0.019f) verticalLineToRelative(0f) close() moveTo(5.459f, 5.681f) curveToRelative(-0.07f, 0f, -0.134f, 0.014f, -0.195f, 0.048f) curveToRelative(-0.138f, 0.08f, -0.191f, 0.233f, -0.242f, 0.424f) curveToRelative(-0.051f, 0.191f, -0.082f, 0.351f, -0.002f, 0.488f) curveToRelative(0.08f, 0.138f, 0.233f, 0.192f, 0.425f, 0.243f) curveToRelative(0.192f, 0.051f, 0.351f, 0.081f, 0.49f, 0.002f) curveToRelative(0.138f, -0.079f, 0.192f, -0.233f, 0.243f, -0.425f) curveToRelative(0.051f, -0.191f, 0.082f, -0.351f, 0.002f, -0.489f) curveToRelative(-0.08f, -0.138f, -0.234f, -0.191f, -0.425f, -0.242f) curveToRelative(-0.108f, -0.029f, -0.206f, -0.051f, -0.295f, -0.051f) lineToRelative(0f, 0f) close() moveTo(7.864f, 6.961f) curveToRelative(-0.323f, 0f, -0.587f, 0.019f, -0.771f, 0.202f) curveToRelative(-0.184f, 0.183f, -0.202f, 0.447f, -0.202f, 0.769f) curveToRelative(0f, 0.323f, 0.019f, 0.587f, 0.203f, 0.769f) curveToRelative(0.184f, 0.183f, 0.448f, 0.203f, 0.77f, 0.203f) curveToRelative(0.323f, -0f, 0.587f, -0.021f, 0.771f, -0.204f) curveToRelative(0.184f, -0.183f, 0.202f, -0.447f, 0.202f, -0.769f) curveToRelative(0f, -0.322f, -0.019f, -0.586f, -0.202f, -0.769f) curveToRelative(-0.183f, -0.183f, -0.448f, -0.202f, -0.771f, -0.202f) close() moveTo(16.082f, 6.961f) curveToRelative(-0.323f, 0f, -0.587f, 0.019f, -0.771f, 0.202f) curveToRelative(-0.184f, 0.183f, -0.202f, 0.447f, -0.202f, 0.769f) curveToRelative(0f, 0.323f, 0.019f, 0.587f, 0.203f, 0.769f) curveToRelative(0.184f, 0.183f, 0.448f, 0.203f, 0.77f, 0.203f) curveToRelative(0.323f, -0f, 0.587f, -0.021f, 0.771f, -0.204f) curveToRelative(0.184f, -0.183f, 0.202f, -0.447f, 0.202f, -0.769f) reflectiveCurveToRelative(-0.018f, -0.586f, -0.202f, -0.769f) curveToRelative(-0.183f, -0.183f, -0.448f, -0.202f, -0.771f, -0.202f) horizontalLineToRelative(0f) close() moveTo(5.78f, 7.536f) curveToRelative(-0.117f, -0f, -0.245f, 0.029f, -0.387f, 0.067f) curveToRelative(-0.251f, 0.067f, -0.453f, 0.137f, -0.557f, 0.317f) curveToRelative(-0.105f, 0.181f, -0.064f, 0.389f, 0.003f, 0.64f) curveToRelative(0.067f, 0.251f, 0.137f, 0.452f, 0.318f, 0.556f) curveToRelative(0.181f, 0.104f, 0.39f, 0.065f, 0.641f, -0.002f) curveToRelative(0.251f, -0.067f, 0.452f, -0.138f, 0.557f, -0.319f) curveToRelative(0.105f, -0.181f, 0.064f, -0.389f, -0.003f, -0.64f) curveToRelative(-0.067f, -0.251f, -0.137f, -0.452f, -0.318f, -0.556f) curveToRelative(-0.079f, -0.046f, -0.164f, -0.063f, -0.255f, -0.064f) lineToRelative(-0f, -0f) close() moveTo(18.159f, 7.537f) curveToRelative(-0.091f, 0f, -0.176f, 0.018f, -0.255f, 0.064f) curveToRelative(-0.181f, 0.104f, -0.251f, 0.305f, -0.318f, 0.556f) curveToRelative(-0.067f, 0.251f, -0.108f, 0.46f, -0.003f, 0.64f) curveToRelative(0.105f, 0.181f, 0.306f, 0.25f, 0.557f, 0.317f) curveToRelative(0.251f, 0.067f, 0.461f, 0.108f, 0.642f, 0.004f) curveToRelative(0.181f, -0.104f, 0.25f, -0.305f, 0.318f, -0.556f) curveToRelative(0.067f, -0.251f, 0.108f, -0.459f, 0.003f, -0.64f) curveToRelative(-0.105f, -0.181f, -0.306f, -0.252f, -0.557f, -0.319f) curveToRelative(-0.141f, -0.038f, -0.269f, -0.067f, -0.386f, -0.066f) horizontalLineToRelative(0f) close() moveTo(20.118f, 8.727f) curveToRelative(-0.156f, 0f, -0.278f, 0.104f, -0.415f, 0.241f) curveToRelative(-0.138f, 0.138f, -0.242f, 0.259f, -0.242f, 0.415f) curveToRelative(0f, 0.156f, 0.105f, 0.277f, 0.242f, 0.414f) curveToRelative(0.138f, 0.137f, 0.258f, 0.242f, 0.415f, 0.242f) curveToRelative(0.157f, -0f, 0.277f, -0.105f, 0.415f, -0.242f) curveToRelative(0.138f, -0.138f, 0.242f, -0.258f, 0.243f, -0.414f) curveToRelative(0f, -0.156f, -0.105f, -0.277f, -0.242f, -0.414f) curveToRelative(-0.138f, -0.138f, -0.259f, -0.242f, -0.415f, -0.242f) close() moveTo(6.497f, 9.449f) curveToRelative(-0.202f, -0.003f, -0.402f, 0.099f, -0.63f, 0.23f) curveToRelative(-0.28f, 0.162f, -0.5f, 0.31f, -0.568f, 0.561f) curveToRelative(-0.067f, 0.251f, 0.049f, 0.489f, 0.21f, 0.769f) curveToRelative(0.162f, 0.28f, 0.311f, 0.5f, 0.562f, 0.566f) curveToRelative(0.251f, 0.067f, 0.49f, -0.048f, 0.77f, -0.209f) curveToRelative(0.28f, -0.162f, 0.499f, -0.312f, 0.567f, -0.562f) curveToRelative(0.067f, -0.251f, -0.049f, -0.489f, -0.21f, -0.768f) curveToRelative(-0.162f, -0.28f, -0.31f, -0.499f, -0.561f, -0.566f) curveToRelative(-0.047f, -0.013f, -0.094f, -0.019f, -0.14f, -0.019f) verticalLineToRelative(-0f) close() moveTo(17.442f, 9.45f) curveToRelative(-0.047f, 0.001f, -0.093f, 0.007f, -0.14f, 0.019f) curveToRelative(-0.251f, 0.067f, -0.4f, 0.286f, -0.562f, 0.566f) curveToRelative(-0.162f, 0.28f, -0.278f, 0.518f, -0.211f, 0.769f) curveToRelative(0.067f, 0.251f, 0.287f, 0.399f, 0.568f, 0.561f) curveToRelative(0.28f, 0.162f, 0.519f, 0.278f, 0.77f, 0.211f) curveToRelative(0.251f, -0.067f, 0.4f, -0.287f, 0.561f, -0.566f) curveToRelative(0.162f, -0.28f, 0.278f, -0.518f, 0.21f, -0.768f) curveToRelative(-0.067f, -0.251f, -0.286f, -0.401f, -0.567f, -0.562f) curveToRelative(-0.227f, -0.131f, -0.428f, -0.231f, -0.63f, -0.228f) lineToRelative(0f, 0f) close() moveTo(3.724f, 9.636f) curveToRelative(-0.088f, -0f, -0.184f, 0.022f, -0.29f, 0.05f) curveToRelative(-0.188f, 0.05f, -0.339f, 0.102f, -0.418f, 0.238f) curveToRelative(-0.078f, 0.135f, -0.048f, 0.292f, 0.002f, 0.48f) curveToRelative(0.05f, 0.188f, 0.103f, 0.339f, 0.238f, 0.417f) horizontalLineToRelative(0f) curveToRelative(0.136f, 0.078f, 0.292f, 0.049f, 0.48f, -0.002f) curveToRelative(0.188f, -0.05f, 0.339f, -0.104f, 0.417f, -0.239f) curveToRelative(0.078f, -0.135f, 0.048f, -0.292f, -0.002f, -0.479f) curveToRelative(-0.05f, -0.188f, -0.102f, -0.338f, -0.238f, -0.416f) curveToRelative(-0.059f, -0.034f, -0.123f, -0.048f, -0.191f, -0.048f) lineToRelative(-0f, -0f) close() moveTo(19.333f, 11.133f) curveToRelative(-0.211f, -0f, -0.373f, 0.141f, -0.559f, 0.326f) curveToRelative(-0.186f, 0.185f, -0.327f, 0.348f, -0.326f, 0.559f) curveToRelative(0f, 0.21f, 0.14f, 0.374f, 0.326f, 0.559f) curveToRelative(0.186f, 0.185f, 0.349f, 0.325f, 0.56f, 0.325f) curveToRelative(0.211f, 0f, 0.373f, -0.141f, 0.559f, -0.326f) curveToRelative(0.185f, -0.185f, 0.327f, -0.347f, 0.326f, -0.558f) curveToRelative(0f, -0.211f, -0.141f, -0.373f, -0.327f, -0.558f) curveToRelative(-0.186f, -0.185f, -0.348f, -0.326f, -0.559f, -0.327f) close() moveTo(4.605f, 11.151f) curveToRelative(-0.211f, -0f, -0.373f, 0.141f, -0.559f, 0.326f) reflectiveCurveToRelative(-0.327f, 0.347f, -0.326f, 0.558f) curveToRelative(0f, 0.211f, 0.141f, 0.373f, 0.327f, 0.558f) curveToRelative(0.186f, 0.185f, 0.348f, 0.326f, 0.559f, 0.327f) curveToRelative(0.211f, 0f, 0.373f, -0.141f, 0.559f, -0.326f) curveToRelative(0.186f, -0.185f, 0.327f, -0.348f, 0.326f, -0.559f) curveToRelative(-0f, -0.21f, -0.14f, -0.374f, -0.326f, -0.559f) curveToRelative(-0.186f, -0.185f, -0.349f, -0.325f, -0.56f, -0.325f) verticalLineToRelative(-0f) close() moveTo(6.211f, 12.458f) curveToRelative(-0.047f, 0.001f, -0.093f, 0.007f, -0.14f, 0.019f) curveToRelative(-0.251f, 0.067f, -0.4f, 0.287f, -0.561f, 0.566f) reflectiveCurveToRelative(-0.278f, 0.518f, -0.21f, 0.768f) curveToRelative(0.067f, 0.251f, 0.286f, 0.401f, 0.567f, 0.562f) curveToRelative(0.28f, 0.161f, 0.519f, 0.276f, 0.77f, 0.209f) curveToRelative(0.251f, -0.067f, 0.4f, -0.286f, 0.562f, -0.566f) curveToRelative(0.162f, -0.28f, 0.278f, -0.518f, 0.21f, -0.769f) curveToRelative(-0.067f, -0.251f, -0.287f, -0.399f, -0.568f, -0.561f) curveToRelative(-0.228f, -0.131f, -0.428f, -0.233f, -0.63f, -0.23f) verticalLineToRelative(-0f) close() moveTo(17.726f, 12.459f) curveToRelative(-0.202f, -0.003f, -0.402f, 0.097f, -0.63f, 0.228f) curveToRelative(-0.28f, 0.162f, -0.499f, 0.312f, -0.567f, 0.562f) curveToRelative(-0.067f, 0.251f, 0.049f, 0.489f, 0.21f, 0.768f) reflectiveCurveToRelative(0.31f, 0.499f, 0.561f, 0.566f) curveToRelative(0.251f, 0.067f, 0.49f, -0.049f, 0.77f, -0.211f) curveToRelative(0.28f, -0.162f, 0.5f, -0.31f, 0.568f, -0.561f) curveToRelative(0.067f, -0.251f, -0.049f, -0.489f, -0.21f, -0.769f) curveToRelative(-0.162f, -0.28f, -0.311f, -0.5f, -0.562f, -0.566f) curveToRelative(-0.047f, -0.012f, -0.094f, -0.019f, -0.14f, -0.019f) verticalLineToRelative(0f) close() moveTo(20.489f, 13.185f) curveToRelative(-0.088f, -0f, -0.184f, 0.021f, -0.289f, 0.05f) curveToRelative(-0.188f, 0.05f, -0.339f, 0.104f, -0.417f, 0.239f) curveToRelative(-0.078f, 0.135f, -0.048f, 0.292f, 0.002f, 0.479f) curveToRelative(0.05f, 0.188f, 0.102f, 0.339f, 0.238f, 0.417f) curveToRelative(0.136f, 0.078f, 0.292f, 0.048f, 0.481f, -0.003f) curveToRelative(0.188f, -0.05f, 0.339f, -0.102f, 0.418f, -0.238f) curveToRelative(0.078f, -0.135f, 0.048f, -0.292f, -0.002f, -0.48f) curveToRelative(-0.05f, -0.188f, -0.103f, -0.339f, -0.238f, -0.417f) curveToRelative(-0.059f, -0.034f, -0.123f, -0.048f, -0.191f, -0.048f) verticalLineToRelative(0f) close() moveTo(3.821f, 14.013f) curveToRelative(-0.157f, 0f, -0.277f, 0.105f, -0.415f, 0.242f) curveToRelative(-0.138f, 0.138f, -0.243f, 0.258f, -0.243f, 0.414f) curveToRelative(-0f, 0.156f, 0.105f, 0.277f, 0.242f, 0.414f) curveToRelative(0.138f, 0.138f, 0.259f, 0.242f, 0.415f, 0.242f) curveToRelative(0.156f, -0f, 0.278f, -0.104f, 0.415f, -0.241f) curveToRelative(0.138f, -0.138f, 0.242f, -0.259f, 0.242f, -0.415f) curveToRelative(0f, -0.156f, -0.105f, -0.277f, -0.242f, -0.414f) curveToRelative(-0.138f, -0.137f, -0.258f, -0.242f, -0.415f, -0.242f) close() moveTo(5.412f, 14.872f) curveToRelative(-0.091f, 0f, -0.176f, 0.018f, -0.255f, 0.064f) curveToRelative(-0.181f, 0.104f, -0.25f, 0.306f, -0.318f, 0.556f) curveToRelative(-0.067f, 0.251f, -0.108f, 0.459f, -0.003f, 0.64f) curveToRelative(0.105f, 0.181f, 0.306f, 0.252f, 0.557f, 0.319f) curveToRelative(0.251f, 0.067f, 0.46f, 0.106f, 0.641f, 0.002f) curveToRelative(0.181f, -0.104f, 0.251f, -0.305f, 0.318f, -0.556f) curveToRelative(0.067f, -0.251f, 0.108f, -0.46f, 0.003f, -0.64f) curveToRelative(-0.105f, -0.181f, -0.306f, -0.25f, -0.557f, -0.317f) curveToRelative(-0.141f, -0.038f, -0.269f, -0.067f, -0.387f, -0.067f) lineToRelative(0f, 0f) close() moveTo(18.526f, 14.872f) curveToRelative(-0.117f, -0f, -0.245f, 0.028f, -0.386f, 0.066f) curveToRelative(-0.251f, 0.067f, -0.452f, 0.138f, -0.557f, 0.319f) curveToRelative(-0.105f, 0.181f, -0.064f, 0.389f, 0.003f, 0.64f) curveToRelative(0.067f, 0.251f, 0.137f, 0.452f, 0.318f, 0.556f) curveToRelative(0.181f, 0.104f, 0.39f, 0.064f, 0.641f, -0.004f) curveToRelative(0.251f, -0.067f, 0.453f, -0.137f, 0.557f, -0.317f) curveToRelative(0.105f, -0.181f, 0.064f, -0.389f, -0.003f, -0.64f) curveToRelative(-0.067f, -0.251f, -0.137f, -0.452f, -0.318f, -0.556f) curveToRelative(-0.079f, -0.045f, -0.164f, -0.063f, -0.255f, -0.064f) lineToRelative(0f, 0f) close() moveTo(7.859f, 15.158f) curveToRelative(-0.323f, 0f, -0.587f, 0.021f, -0.771f, 0.204f) curveToRelative(-0.184f, 0.183f, -0.202f, 0.447f, -0.202f, 0.769f) curveToRelative(-0f, 0.322f, 0.019f, 0.586f, 0.202f, 0.769f) curveToRelative(0.183f, 0.183f, 0.448f, 0.202f, 0.771f, 0.202f) curveToRelative(0.323f, -0f, 0.587f, -0.019f, 0.771f, -0.202f) curveToRelative(0.184f, -0.183f, 0.202f, -0.447f, 0.202f, -0.769f) curveToRelative(-0f, -0.323f, -0.019f, -0.587f, -0.203f, -0.769f) curveToRelative(-0.184f, -0.183f, -0.448f, -0.203f, -0.77f, -0.203f) close() moveTo(16.077f, 15.158f) curveToRelative(-0.323f, 0f, -0.587f, 0.021f, -0.771f, 0.204f) curveToRelative(-0.184f, 0.183f, -0.202f, 0.447f, -0.202f, 0.769f) curveToRelative(0f, 0.322f, 0.018f, 0.586f, 0.202f, 0.769f) curveToRelative(0.183f, 0.183f, 0.448f, 0.202f, 0.771f, 0.202f) curveToRelative(0.323f, -0f, 0.587f, -0.019f, 0.771f, -0.202f) curveToRelative(0.184f, -0.183f, 0.202f, -0.447f, 0.202f, -0.769f) curveToRelative(0f, -0.323f, -0.019f, -0.587f, -0.203f, -0.769f) curveToRelative(-0.184f, -0.183f, -0.448f, -0.203f, -0.77f, -0.203f) horizontalLineToRelative(-0f) close() moveTo(10.607f, 16.554f) curveToRelative(-0.202f, -0.003f, -0.403f, 0.097f, -0.631f, 0.229f) curveToRelative(-0.281f, 0.162f, -0.5f, 0.312f, -0.568f, 0.563f) curveToRelative(-0.068f, 0.251f, 0.049f, 0.49f, 0.211f, 0.77f) curveToRelative(0.162f, 0.28f, 0.311f, 0.5f, 0.562f, 0.567f) curveToRelative(0.252f, 0.067f, 0.491f, -0.049f, 0.771f, -0.211f) curveToRelative(0.281f, -0.162f, 0.501f, -0.31f, 0.569f, -0.562f) curveToRelative(0.068f, -0.251f, -0.049f, -0.49f, -0.211f, -0.77f) curveToRelative(-0.162f, -0.28f, -0.311f, -0.5f, -0.563f, -0.567f) horizontalLineToRelative(-0f) curveToRelative(-0.047f, -0.012f, -0.094f, -0.019f, -0.14f, -0.019f) horizontalLineToRelative(0f) close() moveTo(13.334f, 16.554f) curveToRelative(-0.047f, 0.001f, -0.093f, 0.007f, -0.14f, 0.019f) curveToRelative(-0.252f, 0.067f, -0.401f, 0.287f, -0.563f, 0.567f) curveToRelative(-0.162f, 0.28f, -0.278f, 0.519f, -0.211f, 0.77f) curveToRelative(0.068f, 0.251f, 0.288f, 0.4f, 0.569f, 0.562f) curveToRelative(0.281f, 0.162f, 0.52f, 0.278f, 0.771f, 0.211f) curveToRelative(0.252f, -0.067f, 0.4f, -0.287f, 0.562f, -0.567f) curveToRelative(0.162f, -0.28f, 0.278f, -0.518f, 0.211f, -0.77f) curveToRelative(-0.068f, -0.251f, -0.287f, -0.401f, -0.568f, -0.563f) curveToRelative(-0.228f, -0.131f, -0.429f, -0.231f, -0.631f, -0.229f) horizontalLineToRelative(0f) close() moveTo(18.201f, 17.126f) curveToRelative(-0.07f, 0f, -0.134f, 0.014f, -0.195f, 0.049f) curveToRelative(-0.138f, 0.079f, -0.192f, 0.233f, -0.243f, 0.425f) curveToRelative(-0.051f, 0.191f, -0.082f, 0.351f, -0.002f, 0.489f) curveToRelative(0.08f, 0.138f, 0.234f, 0.191f, 0.425f, 0.242f) curveToRelative(0.192f, 0.051f, 0.352f, 0.082f, 0.49f, 0.003f) curveToRelative(0.138f, -0.08f, 0.191f, -0.233f, 0.242f, -0.424f) curveToRelative(0.051f, -0.191f, 0.082f, -0.351f, 0.002f, -0.489f) curveToRelative(-0.08f, -0.138f, -0.233f, -0.192f, -0.425f, -0.243f) curveToRelative(-0.108f, -0.029f, -0.205f, -0.051f, -0.295f, -0.051f) verticalLineToRelative(-0f) close() moveTo(8.474f, 17.577f) curveToRelative(-0.117f, -0f, -0.245f, 0.028f, -0.386f, 0.066f) curveToRelative(-0.251f, 0.067f, -0.452f, 0.138f, -0.557f, 0.319f) curveToRelative(-0.105f, 0.181f, -0.064f, 0.389f, 0.003f, 0.64f) curveToRelative(0.067f, 0.25f, 0.137f, 0.452f, 0.318f, 0.556f) curveToRelative(0.181f, 0.104f, 0.39f, 0.064f, 0.641f, -0.004f) curveToRelative(0.251f, -0.067f, 0.453f, -0.137f, 0.557f, -0.317f) curveToRelative(0.105f, -0.181f, 0.064f, -0.389f, -0.003f, -0.64f) curveToRelative(-0.067f, -0.251f, -0.137f, -0.452f, -0.318f, -0.556f) curveToRelative(-0.079f, -0.045f, -0.164f, -0.064f, -0.255f, -0.064f) lineToRelative(-0f, 0f) close() moveTo(15.466f, 17.577f) curveToRelative(-0.091f, 0f, -0.176f, 0.019f, -0.255f, 0.064f) curveToRelative(-0.181f, 0.104f, -0.251f, 0.305f, -0.318f, 0.556f) curveToRelative(-0.067f, 0.251f, -0.108f, 0.459f, -0.003f, 0.64f) curveToRelative(0.105f, 0.181f, 0.306f, 0.25f, 0.557f, 0.317f) curveToRelative(0.251f, 0.067f, 0.46f, 0.108f, 0.641f, 0.004f) curveToRelative(0.181f, -0.104f, 0.25f, -0.305f, 0.317f, -0.556f) reflectiveCurveToRelative(0.108f, -0.459f, 0.003f, -0.64f) curveToRelative(-0.105f, -0.181f, -0.306f, -0.252f, -0.557f, -0.319f) curveToRelative(-0.141f, -0.038f, -0.269f, -0.067f, -0.386f, -0.066f) horizontalLineToRelative(0f) close() moveTo(6.097f, 17.752f) curveToRelative(-0.07f, 0f, -0.134f, 0.014f, -0.195f, 0.049f) curveToRelative(-0.138f, 0.079f, -0.192f, 0.233f, -0.243f, 0.425f) curveToRelative(-0.051f, 0.191f, -0.082f, 0.351f, -0.002f, 0.489f) curveToRelative(0.08f, 0.138f, 0.234f, 0.191f, 0.425f, 0.242f) curveToRelative(0.192f, 0.051f, 0.352f, 0.082f, 0.49f, 0.003f) curveToRelative(0.138f, -0.08f, 0.191f, -0.233f, 0.242f, -0.424f) reflectiveCurveToRelative(0.082f, -0.351f, 0.002f, -0.488f) curveToRelative(-0.08f, -0.138f, -0.233f, -0.192f, -0.425f, -0.243f) curveToRelative(-0.108f, -0.029f, -0.205f, -0.051f, -0.295f, -0.051f) verticalLineToRelative(0f) close() moveTo(11.966f, 18.498f) curveToRelative(-0.211f, -0f, -0.373f, 0.141f, -0.559f, 0.326f) reflectiveCurveToRelative(-0.327f, 0.347f, -0.326f, 0.558f) curveToRelative(0f, 0.211f, 0.141f, 0.373f, 0.327f, 0.558f) curveToRelative(0.186f, 0.185f, 0.348f, 0.326f, 0.559f, 0.326f) curveToRelative(0.211f, 0f, 0.373f, -0.141f, 0.559f, -0.326f) curveToRelative(0.186f, -0.185f, 0.327f, -0.348f, 0.326f, -0.559f) curveToRelative(-0f, -0.21f, -0.14f, -0.374f, -0.326f, -0.559f) curveToRelative(-0.186f, -0.185f, -0.349f, -0.325f, -0.56f, -0.325f) verticalLineToRelative(-0f) close() moveTo(14.618f, 19.512f) curveToRelative(-0.156f, -0f, -0.277f, 0.104f, -0.415f, 0.242f) curveToRelative(-0.138f, 0.137f, -0.242f, 0.258f, -0.242f, 0.414f) curveToRelative(0f, 0.156f, 0.105f, 0.277f, 0.242f, 0.414f) curveToRelative(0.138f, 0.138f, 0.258f, 0.242f, 0.415f, 0.242f) curveToRelative(0.156f, 0f, 0.277f, -0.104f, 0.415f, -0.242f) curveToRelative(0.138f, -0.138f, 0.242f, -0.258f, 0.242f, -0.414f) curveToRelative(-0.001f, -0.156f, -0.104f, -0.277f, -0.242f, -0.415f) curveToRelative(-0.138f, -0.138f, -0.259f, -0.241f, -0.416f, -0.241f) lineToRelative(0f, -0f) close() moveTo(10.322f, 19.776f) curveToRelative(-0.089f, -0f, -0.187f, 0.022f, -0.295f, 0.051f) curveToRelative(-0.192f, 0.051f, -0.345f, 0.105f, -0.425f, 0.243f) curveToRelative(-0.08f, 0.138f, -0.049f, 0.297f, 0.002f, 0.489f) reflectiveCurveToRelative(0.104f, 0.345f, 0.243f, 0.424f) curveToRelative(0.138f, 0.08f, 0.298f, 0.049f, 0.49f, -0.003f) curveToRelative(0.192f, -0.051f, 0.346f, -0.104f, 0.425f, -0.242f) curveToRelative(0.08f, -0.138f, 0.049f, -0.297f, -0.002f, -0.489f) curveToRelative(-0.051f, -0.191f, -0.105f, -0.345f, -0.243f, -0.425f) horizontalLineToRelative(-0f) curveToRelative(-0.06f, -0.035f, -0.125f, -0.048f, -0.195f, -0.049f) lineToRelative(0f, 0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/IOS.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.IOS: ImageVector by lazy { Builder( name = "IOS", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.71f, 19.5f) curveTo(17.88f, 20.74f, 17.0f, 21.95f, 15.66f, 21.97f) curveTo(14.32f, 22.0f, 13.89f, 21.18f, 12.37f, 21.18f) curveTo(10.84f, 21.18f, 10.37f, 21.95f, 9.1f, 22.0f) curveTo(7.79f, 22.05f, 6.8f, 20.68f, 5.96f, 19.47f) curveTo(4.25f, 17.0f, 2.94f, 12.45f, 4.7f, 9.39f) curveTo(5.57f, 7.87f, 7.13f, 6.91f, 8.82f, 6.88f) curveTo(10.1f, 6.86f, 11.32f, 7.75f, 12.11f, 7.75f) curveTo(12.89f, 7.75f, 14.37f, 6.68f, 15.92f, 6.84f) curveTo(16.57f, 6.87f, 18.39f, 7.1f, 19.56f, 8.82f) curveTo(19.47f, 8.88f, 17.39f, 10.1f, 17.41f, 12.63f) curveTo(17.44f, 15.65f, 20.06f, 16.66f, 20.09f, 16.67f) curveTo(20.06f, 16.74f, 19.67f, 18.11f, 18.71f, 19.5f) moveTo(13.0f, 3.5f) curveTo(13.73f, 2.67f, 14.94f, 2.04f, 15.94f, 2.0f) curveTo(16.07f, 3.17f, 15.6f, 4.35f, 14.9f, 5.19f) curveTo(14.21f, 6.04f, 13.07f, 6.7f, 11.95f, 6.61f) curveTo(11.8f, 5.46f, 12.36f, 4.26f, 13.0f, 3.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageCombine.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ImageCombine: ImageVector by lazy { ImageVector.Builder( name = "Image Combine", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(160.0f, 360.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(120.0f, 320.0f) verticalLineToRelative(-120.0f) quadToRelative(0.0f, -33.0f, 23.5f, -56.5f) reflectiveQuadTo(200.0f, 120.0f) horizontalLineToRelative(120.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(360.0f, 160.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(320.0f, 200.0f) lineTo(200.0f, 200.0f) verticalLineToRelative(120.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(160.0f, 360.0f) close() moveTo(800.0f, 360.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(760.0f, 320.0f) verticalLineToRelative(-120.0f) lineTo(640.0f, 200.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(600.0f, 160.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(640.0f, 120.0f) horizontalLineToRelative(120.0f) quadToRelative(33.0f, 0.0f, 56.5f, 23.5f) reflectiveQuadTo(840.0f, 200.0f) verticalLineToRelative(120.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(800.0f, 360.0f) close() moveTo(645.0f, 605.0f) lineToRelative(-97.0f, -97.0f) quadToRelative(-12.0f, -12.0f, -12.0f, -28.0f) reflectiveQuadToRelative(12.0f, -28.0f) lineToRelative(97.0f, -97.0f) quadToRelative(12.0f, -12.0f, 28.5f, -12.0f) reflectiveQuadToRelative(28.5f, 12.0f) quadToRelative(12.0f, 12.0f, 12.0f, 28.5f) reflectiveQuadTo(702.0f, 412.0f) lineToRelative(-29.0f, 28.0f) horizontalLineToRelative(167.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(880.0f, 480.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(840.0f, 520.0f) lineTo(673.0f, 520.0f) lineToRelative(29.0f, 28.0f) quadToRelative(12.0f, 12.0f, 12.0f, 28.5f) reflectiveQuadTo(702.0f, 605.0f) quadToRelative(-12.0f, 12.0f, -28.5f, 12.0f) reflectiveQuadTo(645.0f, 605.0f) close() moveTo(259.0f, 605.0f) quadToRelative(-12.0f, -12.0f, -12.5f, -28.5f) reflectiveQuadTo(258.0f, 548.0f) lineToRelative(29.0f, -28.0f) lineTo(120.0f, 520.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(80.0f, 480.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(120.0f, 440.0f) horizontalLineToRelative(167.0f) lineToRelative(-29.0f, -28.0f) quadToRelative(-12.0f, -12.0f, -11.5f, -28.5f) reflectiveQuadTo(259.0f, 355.0f) quadToRelative(12.0f, -12.0f, 28.0f, -12.0f) reflectiveQuadToRelative(28.0f, 12.0f) lineToRelative(97.0f, 97.0f) quadToRelative(12.0f, 12.0f, 12.0f, 28.0f) reflectiveQuadToRelative(-12.0f, 28.0f) lineToRelative(-97.0f, 97.0f) quadToRelative(-12.0f, 12.0f, -28.0f, 12.0f) reflectiveQuadToRelative(-28.0f, -12.0f) close() moveTo(200.0f, 840.0f) quadToRelative(-33.0f, 0.0f, -56.5f, -23.5f) reflectiveQuadTo(120.0f, 760.0f) verticalLineToRelative(-120.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(160.0f, 600.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(200.0f, 640.0f) verticalLineToRelative(120.0f) horizontalLineToRelative(120.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(360.0f, 800.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(320.0f, 840.0f) lineTo(200.0f, 840.0f) close() moveTo(640.0f, 840.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(600.0f, 800.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(640.0f, 760.0f) horizontalLineToRelative(120.0f) verticalLineToRelative(-120.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(800.0f, 600.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(840.0f, 640.0f) verticalLineToRelative(120.0f) quadToRelative(0.0f, 33.0f, -23.5f, 56.5f) reflectiveQuadTo(760.0f, 840.0f) lineTo(640.0f, 840.0f) close() } }.build() } val Icons.TwoTone.ImageCombine: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ImageCombine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5.075f, 3f) lineTo(18.925f, 3f) arcTo(2.075f, 2.075f, 0f, isMoreThanHalf = false, isPositiveArc = true, 21f, 5.075f) lineTo(21f, 18.925f) arcTo(2.075f, 2.075f, 0f, isMoreThanHalf = false, isPositiveArc = true, 18.925f, 21f) lineTo(5.075f, 21f) arcTo(2.075f, 2.075f, 0f, isMoreThanHalf = false, isPositiveArc = true, 3f, 18.925f) lineTo(3f, 5.075f) arcTo(2.075f, 2.075f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5.075f, 3f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(4f, 9f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) verticalLineToRelative(-3f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(3f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-3f) verticalLineToRelative(3f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() moveTo(20f, 9f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) verticalLineToRelative(-3f) horizontalLineToRelative(-3f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(3f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) curveToRelative(0.392f, 0.392f, 0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(3f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() moveTo(16.125f, 15.125f) lineToRelative(-2.425f, -2.425f) curveToRelative(-0.2f, -0.2f, -0.3f, -0.433f, -0.3f, -0.7f) reflectiveCurveToRelative(0.1f, -0.5f, 0.3f, -0.7f) lineToRelative(2.425f, -2.425f) curveToRelative(0.2f, -0.2f, 0.438f, -0.3f, 0.712f, -0.3f) reflectiveCurveToRelative(0.512f, 0.1f, 0.712f, 0.3f) reflectiveCurveToRelative(0.3f, 0.438f, 0.3f, 0.712f) reflectiveCurveToRelative(-0.1f, 0.512f, -0.3f, 0.712f) lineToRelative(-0.725f, 0.7f) horizontalLineToRelative(4.175f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-4.175f) lineToRelative(0.725f, 0.7f) curveToRelative(0.2f, 0.2f, 0.3f, 0.438f, 0.3f, 0.712f) reflectiveCurveToRelative(-0.1f, 0.512f, -0.3f, 0.712f) reflectiveCurveToRelative(-0.438f, 0.3f, -0.712f, 0.3f) reflectiveCurveToRelative(-0.512f, -0.1f, -0.712f, -0.3f) close() moveTo(6.475f, 15.125f) curveToRelative(-0.2f, -0.2f, -0.304f, -0.438f, -0.313f, -0.712f) reflectiveCurveToRelative(0.087f, -0.512f, 0.287f, -0.712f) lineToRelative(0.725f, -0.7f) horizontalLineTo(3f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(4.175f) lineToRelative(-0.725f, -0.7f) curveToRelative(-0.2f, -0.2f, -0.296f, -0.438f, -0.287f, -0.712f) reflectiveCurveToRelative(0.112f, -0.512f, 0.313f, -0.712f) reflectiveCurveToRelative(0.433f, -0.3f, 0.7f, -0.3f) reflectiveCurveToRelative(0.5f, 0.1f, 0.7f, 0.3f) lineToRelative(2.425f, 2.425f) curveToRelative(0.2f, 0.2f, 0.3f, 0.433f, 0.3f, 0.7f) reflectiveCurveToRelative(-0.1f, 0.5f, -0.3f, 0.7f) lineToRelative(-2.425f, 2.425f) curveToRelative(-0.2f, 0.2f, -0.433f, 0.3f, -0.7f, 0.3f) reflectiveCurveToRelative(-0.5f, -0.1f, -0.7f, -0.3f) close() moveTo(5f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineToRelative(-3f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(3f) horizontalLineToRelative(3f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-3f) close() moveTo(16f, 21f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(3f) verticalLineToRelative(-3f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(3f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageConvert.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageConvert: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImageConvert", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(15.6f, 8.9f) verticalLineToRelative(-1.9f) horizontalLineToRelative(2.4f) curveToRelative(-0.7f, -0.9f, -1.5f, -1.5f, -2.5f, -2f) reflectiveCurveToRelative(-2f, -0.7f, -3.2f, -0.7f) curveToRelative(-0.8f, 0f, -1.6f, 0.1f, -2.4f, 0.4f) curveToRelative(-0.7f, 0.3f, -1.4f, 0.6f, -2f, 1.1f) lineToRelative(-0.9f, -1.6f) curveToRelative(0.8f, -0.5f, 1.6f, -0.9f, 2.5f, -1.2f) reflectiveCurveToRelative(1.9f, -0.5f, 2.9f, -0.5f) curveToRelative(1.2f, 0f, 2.4f, 0.2f, 3.5f, 0.7f) reflectiveCurveToRelative(2.1f, 1.1f, 2.9f, 1.9f) verticalLineToRelative(-1.2f) horizontalLineToRelative(1.8f) verticalLineToRelative(5f) horizontalLineToRelative(-5f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(8.2f, 13.5f) horizontalLineToRelative(8f) lineToRelative(-2.6f, -3.5f) lineToRelative(-1.9f, 2.5f) lineToRelative(-1.3f, -1.8f) lineToRelative(-2.2f, 2.8f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(13.11f, 15.744f) lineToRelative(1.645f, 0.95f) lineToRelative(-1.2f, 2.078f) curveToRelative(1.129f, -0.156f, 2.049f, -0.549f, 2.982f, -1.165f) reflectiveCurveToRelative(1.606f, -1.382f, 2.206f, -2.421f) curveToRelative(0.4f, -0.693f, 0.713f, -1.436f, 0.854f, -2.278f) curveToRelative(0.09f, -0.756f, 0.18f, -1.512f, 0.047f, -2.282f) lineToRelative(1.836f, 0.021f) curveToRelative(0.033f, 0.943f, -0.021f, 1.836f, -0.211f, 2.765f) reflectiveCurveToRelative(-0.517f, 1.895f, -1.017f, 2.761f) curveToRelative(-0.6f, 1.039f, -1.373f, 1.978f, -2.356f, 2.681f) reflectiveCurveToRelative(-2.003f, 1.269f, -3.095f, 1.561f) lineToRelative(1.039f, 0.6f) lineToRelative(-0.9f, 1.559f) lineToRelative(-4.33f, -2.5f) lineToRelative(2.5f, -4.33f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(8.33f, 10.126f) lineToRelative(-1.634f, 0.969f) lineToRelative(-1.224f, -2.065f) curveToRelative(-0.417f, 1.061f, -0.525f, 2.055f, -0.446f, 3.17f) reflectiveCurveToRelative(0.418f, 2.077f, 1.03f, 3.11f) curveToRelative(0.408f, 0.688f, 0.902f, 1.325f, 1.568f, 1.861f) curveToRelative(0.615f, 0.449f, 1.23f, 0.898f, 1.966f, 1.16f) lineToRelative(-0.917f, 1.59f) curveToRelative(-0.838f, -0.433f, -1.59f, -0.917f, -2.307f, -1.539f) reflectiveCurveToRelative(-1.399f, -1.379f, -1.909f, -2.24f) curveToRelative(-0.612f, -1.032f, -1.052f, -2.167f, -1.183f, -3.368f) reflectiveCurveToRelative(-0.125f, -2.367f, 0.156f, -3.463f) lineToRelative(-1.032f, 0.612f) lineToRelative(-0.918f, -1.548f) lineToRelative(4.301f, -2.55f) lineToRelative(2.55f, 4.301f) close() } }.build() } val Icons.TwoTone.ImageConvert: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ImageConvert", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12.267f, 4.3f) lineTo(12.487f, 4.3f) arcTo( 7.264f, 7.264f, 0f, isMoreThanHalf = false, isPositiveArc = true, 19.751f, 11.564f ) lineTo(19.751f, 11.609f) arcTo( 7.264f, 7.264f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12.487f, 18.874f ) lineTo(12.267f, 18.874f) arcTo(7.264f, 7.264f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5.002f, 11.609f) lineTo(5.002f, 11.564f) arcTo(7.264f, 7.264f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12.267f, 4.3f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15.6f, 8.9f) verticalLineToRelative(-1.9f) horizontalLineToRelative(2.4f) curveToRelative(-0.7f, -0.9f, -1.5f, -1.5f, -2.5f, -2f) reflectiveCurveToRelative(-2f, -0.7f, -3.2f, -0.7f) curveToRelative(-0.8f, 0f, -1.6f, 0.1f, -2.4f, 0.4f) curveToRelative(-0.7f, 0.3f, -1.4f, 0.6f, -2f, 1.1f) lineToRelative(-0.9f, -1.6f) curveToRelative(0.8f, -0.5f, 1.6f, -0.9f, 2.5f, -1.2f) reflectiveCurveToRelative(1.9f, -0.5f, 2.9f, -0.5f) curveToRelative(1.2f, 0f, 2.4f, 0.2f, 3.5f, 0.7f) reflectiveCurveToRelative(2.1f, 1.1f, 2.9f, 1.9f) verticalLineToRelative(-1.2f) horizontalLineToRelative(1.8f) verticalLineToRelative(5f) horizontalLineToRelative(-5f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(8.2f, 13.5f) horizontalLineToRelative(8f) lineToRelative(-2.6f, -3.5f) lineToRelative(-1.9f, 2.5f) lineToRelative(-1.3f, -1.8f) lineToRelative(-2.2f, 2.8f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(13.11f, 15.744f) lineToRelative(1.645f, 0.95f) lineToRelative(-1.2f, 2.078f) curveToRelative(1.129f, -0.156f, 2.049f, -0.549f, 2.982f, -1.165f) reflectiveCurveToRelative(1.606f, -1.382f, 2.206f, -2.421f) curveToRelative(0.4f, -0.693f, 0.713f, -1.436f, 0.854f, -2.278f) curveToRelative(0.09f, -0.756f, 0.18f, -1.512f, 0.047f, -2.282f) lineToRelative(1.836f, 0.021f) curveToRelative(0.033f, 0.943f, -0.021f, 1.836f, -0.211f, 2.765f) reflectiveCurveToRelative(-0.517f, 1.895f, -1.017f, 2.761f) curveToRelative(-0.6f, 1.039f, -1.373f, 1.978f, -2.356f, 2.681f) reflectiveCurveToRelative(-2.003f, 1.269f, -3.095f, 1.561f) lineToRelative(1.039f, 0.6f) lineToRelative(-0.9f, 1.559f) lineToRelative(-4.33f, -2.5f) lineToRelative(2.5f, -4.33f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(8.33f, 10.126f) lineToRelative(-1.634f, 0.969f) lineToRelative(-1.224f, -2.065f) curveToRelative(-0.417f, 1.061f, -0.525f, 2.055f, -0.446f, 3.17f) reflectiveCurveToRelative(0.418f, 2.077f, 1.03f, 3.11f) curveToRelative(0.408f, 0.688f, 0.902f, 1.325f, 1.568f, 1.861f) curveToRelative(0.615f, 0.449f, 1.23f, 0.898f, 1.966f, 1.16f) lineToRelative(-0.917f, 1.59f) curveToRelative(-0.838f, -0.433f, -1.59f, -0.917f, -2.307f, -1.539f) reflectiveCurveToRelative(-1.399f, -1.379f, -1.909f, -2.24f) curveToRelative(-0.612f, -1.032f, -1.052f, -2.167f, -1.183f, -3.368f) reflectiveCurveToRelative(-0.125f, -2.367f, 0.156f, -3.463f) lineToRelative(-1.032f, 0.612f) lineToRelative(-0.918f, -1.548f) lineToRelative(4.301f, -2.55f) lineToRelative(2.55f, 4.301f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageDownload.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageDownload: ImageVector by lazy { ImageVector.Builder( name = "Outlined.ImageDownload", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(5f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(5f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(7f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-7f) verticalLineToRelative(14f) horizontalLineToRelative(14f) verticalLineToRelative(-6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(5f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.474f, 6.243f) lineToRelative(0.9f, -0.875f) curveToRelative(0.183f, -0.183f, 0.412f, -0.279f, 0.688f, -0.287f) reflectiveCurveToRelative(0.512f, 0.087f, 0.712f, 0.287f) curveToRelative(0.183f, 0.183f, 0.275f, 0.417f, 0.275f, 0.7f) reflectiveCurveToRelative(-0.092f, 0.517f, -0.275f, 0.7f) lineToRelative(-2.6f, 2.6f) curveToRelative(-0.1f, 0.1f, -0.208f, 0.175f, -0.325f, 0.225f) reflectiveCurveToRelative(-0.242f, 0.075f, -0.375f, 0.075f) reflectiveCurveToRelative(-0.258f, -0.025f, -0.375f, -0.075f) reflectiveCurveToRelative(-0.225f, -0.125f, -0.325f, -0.225f) lineToRelative(-2.6f, -2.6f) curveToRelative(-0.183f, -0.183f, -0.279f, -0.412f, -0.287f, -0.688f) reflectiveCurveToRelative(0.087f, -0.512f, 0.287f, -0.712f) curveToRelative(0.183f, -0.183f, 0.417f, -0.275f, 0.7f, -0.275f) reflectiveCurveToRelative(0.517f, 0.092f, 0.7f, 0.275f) lineToRelative(0.9f, 0.875f) verticalLineToRelative(-3.175f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(3.175f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7f, 17f) horizontalLineToRelative(10f) curveToRelative(0.2f, 0f, 0.35f, -0.092f, 0.45f, -0.275f) reflectiveCurveToRelative(0.083f, -0.358f, -0.05f, -0.525f) lineToRelative(-2.75f, -3.675f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2.6f, 3.475f) lineToRelative(-1.85f, -2.475f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2f, 2.675f) curveToRelative(-0.133f, 0.167f, -0.15f, 0.342f, -0.05f, 0.525f) reflectiveCurveToRelative(0.25f, 0.275f, 0.45f, 0.275f) close() } }.build() } val Icons.TwoTone.ImageDownload: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ImageDownload", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(5f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(5f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(7f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-7f) verticalLineToRelative(14f) horizontalLineToRelative(14f) verticalLineToRelative(-6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(5f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.474f, 6.243f) lineToRelative(0.9f, -0.875f) curveToRelative(0.183f, -0.183f, 0.412f, -0.279f, 0.688f, -0.287f) reflectiveCurveToRelative(0.512f, 0.087f, 0.712f, 0.287f) curveToRelative(0.183f, 0.183f, 0.275f, 0.417f, 0.275f, 0.7f) reflectiveCurveToRelative(-0.092f, 0.517f, -0.275f, 0.7f) lineToRelative(-2.6f, 2.6f) curveToRelative(-0.1f, 0.1f, -0.208f, 0.175f, -0.325f, 0.225f) reflectiveCurveToRelative(-0.242f, 0.075f, -0.375f, 0.075f) reflectiveCurveToRelative(-0.258f, -0.025f, -0.375f, -0.075f) reflectiveCurveToRelative(-0.225f, -0.125f, -0.325f, -0.225f) lineToRelative(-2.6f, -2.6f) curveToRelative(-0.183f, -0.183f, -0.279f, -0.412f, -0.287f, -0.688f) reflectiveCurveToRelative(0.087f, -0.512f, 0.287f, -0.712f) curveToRelative(0.183f, -0.183f, 0.417f, -0.275f, 0.7f, -0.275f) reflectiveCurveToRelative(0.517f, 0.092f, 0.7f, 0.275f) lineToRelative(0.9f, 0.875f) verticalLineToRelative(-3.175f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(3.175f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7f, 17f) horizontalLineToRelative(10f) curveToRelative(0.2f, 0f, 0.35f, -0.092f, 0.45f, -0.275f) reflectiveCurveToRelative(0.083f, -0.358f, -0.05f, -0.525f) lineToRelative(-2.75f, -3.675f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2.6f, 3.475f) lineToRelative(-1.85f, -2.475f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2f, 2.675f) curveToRelative(-0.133f, 0.167f, -0.15f, 0.342f, -0.05f, 0.525f) reflectiveCurveToRelative(0.25f, 0.275f, 0.45f, 0.275f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(18.271f, 12.599f) curveToRelative(-0.231f, -0.099f, -0.445f, -0.247f, -0.643f, -0.445f) lineToRelative(-5.141f, -5.141f) curveToRelative(-0.362f, -0.362f, -0.552f, -0.816f, -0.568f, -1.359f) curveToRelative(-0.007f, -0.233f, 0.032f, -0.449f, 0.1f, -0.654f) horizontalLineToRelative(-7.02f) verticalLineToRelative(14f) horizontalLineToRelative(14f) verticalLineToRelative(-6.254f) curveToRelative(-0.259f, -0.002f, -0.502f, -0.05f, -0.729f, -0.147f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEdit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "Outlined.ImageEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(22.459f, 13.864f) curveToRelative(-0.075f, -0.183f, -0.179f, -0.35f, -0.313f, -0.5f) lineToRelative(-0.925f, -0.925f) curveToRelative(-0.15f, -0.15f, -0.317f, -0.262f, -0.5f, -0.337f) reflectiveCurveToRelative(-0.375f, -0.113f, -0.575f, -0.113f) curveToRelative(-0.183f, 0f, -0.367f, 0.033f, -0.55f, 0.1f) reflectiveCurveToRelative(-0.35f, 0.175f, -0.5f, 0.325f) lineToRelative(-5.525f, 5.5f) verticalLineToRelative(3.075f) horizontalLineToRelative(3.075f) lineToRelative(5.5f, -5.5f) curveToRelative(0.15f, -0.15f, 0.258f, -0.321f, 0.325f, -0.512f) curveToRelative(0.067f, -0.192f, 0.1f, -0.379f, 0.1f, -0.563f) reflectiveCurveToRelative(-0.038f, -0.367f, -0.113f, -0.55f) close() moveTo(16.021f, 19.489f) horizontalLineToRelative(-0.95f) verticalLineToRelative(-0.95f) lineToRelative(3.05f, -3.025f) lineToRelative(0.475f, 0.45f) lineToRelative(0.45f, 0.475f) lineToRelative(-3.025f, 3.05f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11.018f, 18.608f) horizontalLineToRelative(-5.625f) verticalLineTo(5.468f) horizontalLineToRelative(13.214f) verticalLineToRelative(4.895f) horizontalLineToRelative(1.888f) verticalLineToRelative(-4.895f) curveToRelative(0f, -1.032f, -0.84f, -1.877f, -1.888f, -1.877f) horizontalLineTo(5.393f) curveToRelative(-1.038f, 0f, -1.888f, 0.845f, -1.888f, 1.877f) verticalLineToRelative(13.14f) curveToRelative(0f, 1.042f, 0.849f, 1.877f, 1.888f, 1.877f) horizontalLineToRelative(6.394f) verticalLineToRelative(-1.877f) horizontalLineToRelative(-0.769f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.047f, 12.914f) lineToRelative(-1.656f, -2.208f) lineToRelative(-2.475f, 3.3f) lineToRelative(-1.815f, -2.409f) lineToRelative(-2.805f, 3.729f) lineToRelative(6.339f, 0f) lineToRelative(2.412f, -2.412f) close() } }.build() } val Icons.TwoTone.ImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "TwoTone.ImageEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(22.459f, 13.864f) curveToRelative(-0.075f, -0.183f, -0.179f, -0.35f, -0.313f, -0.5f) lineToRelative(-0.925f, -0.925f) curveToRelative(-0.15f, -0.15f, -0.317f, -0.262f, -0.5f, -0.337f) reflectiveCurveToRelative(-0.375f, -0.113f, -0.575f, -0.113f) curveToRelative(-0.183f, 0f, -0.367f, 0.033f, -0.55f, 0.1f) reflectiveCurveToRelative(-0.35f, 0.175f, -0.5f, 0.325f) lineToRelative(-5.525f, 5.5f) verticalLineToRelative(3.075f) horizontalLineToRelative(3.075f) lineToRelative(5.5f, -5.5f) curveToRelative(0.15f, -0.15f, 0.258f, -0.321f, 0.325f, -0.512f) curveToRelative(0.067f, -0.192f, 0.1f, -0.379f, 0.1f, -0.563f) reflectiveCurveToRelative(-0.038f, -0.367f, -0.113f, -0.55f) close() moveTo(16.021f, 19.489f) horizontalLineToRelative(-0.95f) verticalLineToRelative(-0.95f) lineToRelative(3.05f, -3.025f) lineToRelative(0.475f, 0.45f) lineToRelative(0.45f, 0.475f) lineToRelative(-3.025f, 3.05f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11.018f, 18.608f) horizontalLineToRelative(-5.625f) verticalLineTo(5.468f) horizontalLineToRelative(13.214f) verticalLineToRelative(4.895f) horizontalLineToRelative(1.888f) verticalLineToRelative(-4.895f) curveToRelative(0f, -1.032f, -0.84f, -1.877f, -1.888f, -1.877f) horizontalLineTo(5.393f) curveToRelative(-1.038f, 0f, -1.888f, 0.845f, -1.888f, 1.877f) verticalLineToRelative(13.14f) curveToRelative(0f, 1.042f, 0.849f, 1.877f, 1.888f, 1.877f) horizontalLineToRelative(6.394f) verticalLineToRelative(-1.877f) horizontalLineToRelative(-0.769f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.047f, 12.914f) lineToRelative(-1.656f, -2.208f) lineToRelative(-2.475f, 3.3f) lineToRelative(-1.815f, -2.409f) lineToRelative(-2.805f, 3.729f) lineToRelative(6.339f, 0f) lineToRelative(2.412f, -2.412f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(11.787f, 5.468f) lineToRelative(-1.655f, 0f) lineToRelative(-4.739f, 0f) lineToRelative(0f, 13.14f) lineToRelative(4.739f, 0f) lineToRelative(1.651f, 0f) lineToRelative(0f, -1.431f) lineToRelative(0.001f, 0f) lineToRelative(6.822f, -6.822f) lineToRelative(0f, -4.886f) lineToRelative(-6.82f, 0f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(14.065f, 18.646f) lineToRelative(4.057f, -4.057f) lineToRelative(1.825f, 1.825f) lineToRelative(-4.057f, 4.057f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageEmbedded.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageEmbedded: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImageEmbedded", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(3f, 8f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.713f) verticalLineToRelative(-3f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.412f) reflectiveCurveToRelative(0.863f, -0.588f, 1.413f, -0.588f) horizontalLineToRelative(3f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.713f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.713f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.713f, 0.287f) horizontalLineToRelative(-3f) verticalLineToRelative(3f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.713f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 22f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.412f, -0.587f) reflectiveCurveToRelative(-0.588f, -0.862f, -0.588f, -1.413f) verticalLineToRelative(-3f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.288f, 0.713f, -0.288f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.713f, 0.288f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(3f) horizontalLineToRelative(3f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.713f, 0.288f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.288f, -0.713f, 0.288f) horizontalLineToRelative(-3f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 22f) horizontalLineToRelative(-3f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.288f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.288f, -0.712f) reflectiveCurveToRelative(0.429f, -0.288f, 0.712f, -0.288f) horizontalLineToRelative(3f) verticalLineToRelative(-3f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.288f, -0.712f) reflectiveCurveToRelative(0.429f, -0.288f, 0.712f, -0.288f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.288f) reflectiveCurveToRelative(0.288f, 0.429f, 0.288f, 0.712f) verticalLineToRelative(3f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.862f, 0.587f, -1.413f, 0.587f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 7f) verticalLineToRelative(-3f) horizontalLineToRelative(-3f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.713f) reflectiveCurveToRelative(0.096f, -0.521f, 0.288f, -0.713f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(3f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(3f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.288f, 0.713f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.288f, -0.429f, -0.288f, -0.713f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5.943f, 14.011f) lineToRelative(2.472f, -3.291f) curveToRelative(0.062f, -0.082f, 0.136f, -0.144f, 0.224f, -0.185f) reflectiveCurveToRelative(0.178f, -0.062f, 0.27f, -0.062f) reflectiveCurveToRelative(0.183f, 0.021f, 0.27f, 0.062f) reflectiveCurveToRelative(0.162f, 0.103f, 0.224f, 0.185f) lineToRelative(2.102f, 2.797f) curveToRelative(0.062f, 0.082f, 0.134f, 0.144f, 0.216f, 0.185f) reflectiveCurveToRelative(0.175f, 0.062f, 0.278f, 0.062f) curveToRelative(0.258f, 0f, 0.443f, -0.116f, 0.556f, -0.348f) curveToRelative(0.113f, -0.232f, 0.093f, -0.451f, -0.062f, -0.657f) lineToRelative(-1.298f, -1.715f) curveToRelative(-0.082f, -0.113f, -0.124f, -0.237f, -0.124f, -0.371f) curveToRelative(0f, -0.134f, 0.041f, -0.258f, 0.124f, -0.371f) lineToRelative(1.545f, -2.055f) curveToRelative(0.062f, -0.082f, 0.136f, -0.144f, 0.224f, -0.185f) reflectiveCurveToRelative(0.178f, -0.062f, 0.27f, -0.062f) reflectiveCurveToRelative(0.183f, 0.021f, 0.27f, 0.062f) reflectiveCurveToRelative(0.162f, 0.103f, 0.224f, 0.185f) lineToRelative(4.327f, 5.764f) curveToRelative(0.155f, 0.206f, 0.175f, 0.422f, 0.062f, 0.649f) curveToRelative(-0.113f, 0.227f, -0.299f, 0.34f, -0.556f, 0.34f) horizontalLineTo(6.437f) curveToRelative(-0.258f, 0f, -0.443f, -0.113f, -0.556f, -0.34f) reflectiveCurveToRelative(-0.093f, -0.443f, 0.062f, -0.649f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageLimit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageLimit: ImageVector by lazy { ImageVector.Builder( name = "Image Limit", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(7.6702f, 15.289f) lineToRelative(8.6596f, 0.0f) lineToRelative(-2.7061f, -3.6082f) lineToRelative(-2.1649f, 2.8865f) lineToRelative(-1.6237f, -2.1649f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 3.1136f) curveToRelative(-5.5228f, 0.0f, -10.0f, 4.4772f, -10.0f, 10.0f) curveToRelative(0.0f, 3.1172f, 1.4398f, 5.8854f, 3.6758f, 7.7189f) lineToRelative(2.6348f, -2.6348f) lineToRelative(-1.4087f, -1.4088f) lineToRelative(-1.2151f, 1.2151f) curveToRelative(-0.8453f, -1.0906f, -1.418f, -2.4037f, -1.6085f, -3.8388f) horizontalLineToRelative(1.7119f) verticalLineToRelative(-2.0f) horizontalLineTo(4.0599f) curveToRelative(0.1718f, -1.465f, 0.7408f, -2.8074f, 1.5935f, -3.9208f) lineToRelative(1.2126f, 1.2126f) lineToRelative(1.4142f, -1.4142f) lineTo(7.0601f, 6.8227f) curveTo(8.1728f, 5.9452f, 9.5237f, 5.3609f, 11.0f, 5.1748f) verticalLineToRelative(1.7205f) horizontalLineToRelative(2.0f) verticalLineTo(5.1747f) curveToRelative(1.4588f, 0.1837f, 2.7955f, 0.7556f, 3.9005f, 1.6158f) lineToRelative(-1.2166f, 1.2166f) lineToRelative(1.4142f, 1.4142f) lineToRelative(1.2186f, -1.2186f) curveToRelative(0.8701f, 1.1213f, 1.4495f, 2.4799f, 1.6234f, 3.9627f) horizontalLineToRelative(-1.7303f) verticalLineToRelative(2.0f) horizontalLineToRelative(1.7119f) curveToRelative(-0.1929f, 1.4529f, -0.776f, 2.7819f, -1.6387f, 3.8804f) lineToRelative(-1.2208f, -1.2208f) lineToRelative(-1.4142f, 1.4142f) lineToRelative(1.2112f, 1.2112f) lineToRelative(1.4319f, 1.4319f) horizontalLineToRelative(1.0E-4f) lineToRelative(0.0042f, 0.0042f) lineToRelative(0.0287f, -0.0287f) curveTo(20.567f, 19.0238f, 22.0f, 16.2367f, 22.0f, 13.1136f) curveTo(22.0f, 7.5908f, 17.5228f, 3.1136f, 12.0f, 3.1136f) close() } }.build() } val Icons.TwoTone.ImageLimit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ImageLimit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(7.67f, 15.289f) horizontalLineToRelative(8.66f) lineToRelative(-2.706f, -3.608f) lineToRelative(-2.165f, 2.886f) lineToRelative(-1.624f, -2.165f) lineToRelative(-2.165f, 2.887f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 3.114f) curveTo(6.477f, 3.114f, 2f, 7.591f, 2f, 13.114f) curveToRelative(0f, 3.117f, 1.44f, 5.885f, 3.676f, 7.719f) lineToRelative(2.635f, -2.635f) lineToRelative(-1.409f, -1.409f) lineToRelative(-1.215f, 1.215f) curveToRelative(-0.845f, -1.091f, -1.418f, -2.404f, -1.609f, -3.839f) horizontalLineToRelative(1.712f) verticalLineToRelative(-2f) horizontalLineToRelative(-1.73f) curveToRelative(0.172f, -1.465f, 0.741f, -2.807f, 1.594f, -3.921f) lineToRelative(1.213f, 1.213f) lineToRelative(1.414f, -1.414f) lineToRelative(-1.22f, -1.22f) curveToRelative(1.113f, -0.878f, 2.464f, -1.462f, 3.94f, -1.648f) verticalLineToRelative(1.72f) horizontalLineToRelative(2f) verticalLineToRelative(-1.721f) curveToRelative(1.459f, 0.184f, 2.795f, 0.756f, 3.9f, 1.616f) lineToRelative(-1.217f, 1.217f) lineToRelative(1.414f, 1.414f) lineToRelative(1.219f, -1.219f) curveToRelative(0.87f, 1.121f, 1.449f, 2.48f, 1.623f, 3.963f) horizontalLineToRelative(-1.73f) verticalLineToRelative(2f) horizontalLineToRelative(1.712f) curveToRelative(-0.193f, 1.453f, -0.776f, 2.782f, -1.639f, 3.88f) lineToRelative(-1.221f, -1.221f) lineToRelative(-1.414f, 1.414f) lineToRelative(1.211f, 1.211f) lineToRelative(1.432f, 1.432f) horizontalLineToRelative(0f) lineToRelative(0.004f, 0.004f) lineToRelative(0.029f, -0.029f) curveToRelative(2.243f, -1.834f, 3.676f, -4.621f, 3.676f, -7.744f) curveToRelative(0f, -5.523f, -4.477f, -10f, -10f, -10f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(20.554f, 18.274f) curveToRelative(0.912f, -1.508f, 1.446f, -3.27f, 1.446f, -5.16f) curveToRelative(0f, -5.523f, -4.477f, -10f, -10f, -10f) reflectiveCurveTo(2f, 7.591f, 2f, 13.114f) curveToRelative(0f, 1.841f, 0.506f, 3.559f, 1.373f, 5.04f) lineToRelative(17.18f, 0.12f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageOverlay.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageOverlay: ImageVector by lazy { ImageVector.Builder( name = "Outlined.ImageOverlay", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(3.5f, 1.5f) curveToRelative(-1.11f, 0f, -2f, 0.89f, -2f, 2f) verticalLineToRelative(11f) curveToRelative(0f, 1.11f, 0.89f, 2f, 2f, 2f) curveToRelative(3.67f, 0f, 7.33f, 0f, 11f, 0f) curveToRelative(1.11f, 0f, 2f, -0.89f, 2f, -2f) curveToRelative(0f, -3.67f, 0f, -7.33f, 0f, -11f) curveToRelative(0f, -1.11f, -0.89f, -2f, -2f, -2f) horizontalLineTo(3.5f) moveTo(3.5f, 3.5f) horizontalLineToRelative(11f) verticalLineToRelative(11f) horizontalLineToRelative(-11f) verticalLineTo(3.5f) moveTo(18.5f, 7.5f) verticalLineToRelative(2f) horizontalLineToRelative(2f) verticalLineToRelative(11f) horizontalLineToRelative(-11f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) curveToRelative(0f, 1.11f, 0.89f, 2f, 2f, 2f) horizontalLineToRelative(11f) curveToRelative(1.11f, 0f, 2f, -0.89f, 2f, -2f) verticalLineToRelative(-11f) curveToRelative(0f, -1.11f, -0.89f, -2f, -2f, -2f) horizontalLineTo(18.5f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(10.633f, 9.242f) lineToRelative(-2.292f, 2.95f) lineToRelative(-1.633f, -1.967f) lineToRelative(-2.292f, 2.942f) lineToRelative(9.167f, 0f) close() } }.build() } val Icons.TwoTone.ImageOverlay: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ImageOverlay", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(3.5f, 1.5f) curveToRelative(-1.11f, 0f, -2f, 0.89f, -2f, 2f) verticalLineToRelative(11f) curveToRelative(0f, 1.11f, 0.89f, 2f, 2f, 2f) horizontalLineToRelative(11f) curveToRelative(1.11f, 0f, 2f, -0.89f, 2f, -2f) verticalLineTo(3.5f) curveToRelative(0f, -1.11f, -0.89f, -2f, -2f, -2f) horizontalLineTo(3.5f) moveTo(3.5f, 3.5f) horizontalLineToRelative(11f) verticalLineToRelative(11f) horizontalLineTo(3.5f) verticalLineTo(3.5f) moveTo(18.5f, 7.5f) verticalLineToRelative(2f) horizontalLineToRelative(2f) verticalLineToRelative(11f) horizontalLineToRelative(-11f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) curveToRelative(0f, 1.11f, 0.89f, 2f, 2f, 2f) horizontalLineToRelative(11f) curveToRelative(1.11f, 0f, 2f, -0.89f, 2f, -2f) verticalLineToRelative(-11f) curveToRelative(0f, -1.11f, -0.89f, -2f, -2f, -2f) horizontalLineToRelative(-2f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(3.5f, 3.5f) horizontalLineToRelative(11f) verticalLineToRelative(11f) horizontalLineToRelative(-11f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(10.633f, 9.242f) lineToRelative(-2.292f, 2.95f) lineToRelative(-1.633f, -1.967f) lineToRelative(-2.292f, 2.942f) horizontalLineToRelative(9.167f) lineToRelative(-2.95f, -3.925f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageReset.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ImageReset: ImageVector by lazy { Builder( name = "ImageReset", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(120.0f, 360.0f) verticalLineToRelative(-240.0f) horizontalLineToRelative(80.0f) verticalLineToRelative(134.0f) quadToRelative(50.0f, -62.0f, 122.5f, -98.0f) reflectiveQuadTo(480.0f, 120.0f) quadToRelative(118.0f, 0.0f, 210.5f, 67.0f) reflectiveQuadTo(820.0f, 360.0f) horizontalLineToRelative(-87.0f) quadToRelative(-34.0f, -72.0f, -101.0f, -116.0f) reflectiveQuadToRelative(-152.0f, -44.0f) quadToRelative(-57.0f, 0.0f, -107.5f, 21.0f) reflectiveQuadTo(284.0f, 280.0f) horizontalLineToRelative(76.0f) verticalLineToRelative(80.0f) lineTo(120.0f, 360.0f) close() moveTo(240.0f, 720.0f) horizontalLineToRelative(480.0f) lineTo(570.0f, 520.0f) lineTo(450.0f, 680.0f) lineToRelative(-90.0f, -120.0f) lineToRelative(-120.0f, 160.0f) close() moveTo(200.0f, 880.0f) quadToRelative(-33.0f, 0.0f, -56.5f, -23.5f) reflectiveQuadTo(120.0f, 800.0f) verticalLineToRelative(-320.0f) horizontalLineToRelative(80.0f) verticalLineToRelative(320.0f) horizontalLineToRelative(560.0f) verticalLineToRelative(-320.0f) horizontalLineToRelative(80.0f) verticalLineToRelative(320.0f) quadToRelative(0.0f, 33.0f, -23.5f, 56.5f) reflectiveQuadTo(760.0f, 880.0f) lineTo(200.0f, 880.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageResize.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageResize: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImageResize", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19f, 9f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(-2f) horizontalLineToRelative(6f) verticalLineToRelative(6f) horizontalLineToRelative(-2f) close() moveTo(3f, 21f) verticalLineToRelative(-6f) horizontalLineToRelative(2f) verticalLineToRelative(4f) horizontalLineToRelative(4f) verticalLineToRelative(2f) horizontalLineTo(3f) close() moveTo(3f, 13f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(3f, 9f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(3f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(7f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(11f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(11f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(15f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 17f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 13f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6.5f, 16.792f) lineToRelative(11f, 0f) lineToRelative(-3.438f, -4.583f) lineToRelative(-2.75f, 3.667f) lineToRelative(-2.063f, -2.75f) lineToRelative(-2.75f, 3.667f) close() } }.build() } val Icons.TwoTone.ImageResize: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ImageResize", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19f, 9f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(-2f) horizontalLineToRelative(6f) verticalLineToRelative(6f) horizontalLineToRelative(-2f) close() moveTo(3f, 21f) verticalLineToRelative(-6f) horizontalLineToRelative(2f) verticalLineToRelative(4f) horizontalLineToRelative(4f) verticalLineToRelative(2f) horizontalLineTo(3f) close() moveTo(3f, 13f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(3f, 9f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(3f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(7f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(11f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(11f, 5f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(15f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 17f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 13f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 5f) horizontalLineToRelative(14f) verticalLineToRelative(14f) horizontalLineToRelative(-14f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6.5f, 16.792f) lineToRelative(11f, 0f) lineToRelative(-3.438f, -4.583f) lineToRelative(-2.75f, 3.667f) lineToRelative(-2.063f, -2.75f) lineToRelative(-2.75f, 3.667f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSaw.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageSaw: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImageSawTwoTone", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(7f, 14.143f) lineToRelative(10f, 0f) lineToRelative(-3.214f, -4.286f) lineToRelative(-2.5f, 3.214f) lineToRelative(-1.786f, -2.143f) lineToRelative(-2.5f, 3.214f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 15f) lineToRelative(2f, -2f) verticalLineToRelative(-4f) curveToRelative(-2.3f, 1.4f, -2.2f, -0.5f, -2.2f, -0.5f) verticalLineToRelative(-2.9f) lineToRelative(-2.8f, -2.8f) curveToRelative(-0.7f, 2.6f, -2f, 1.2f, -2f, 1.2f) lineToRelative(-2f, -2f) horizontalLineToRelative(-4f) curveToRelative(1.4f, 2.3f, -0.5f, 2.2f, -0.5f, 2.2f) horizontalLineToRelative(-2.9f) lineToRelative(-2.8f, 2.9f) curveToRelative(2.6f, 0.6f, 1.2f, 1.9f, 1.2f, 1.9f) lineToRelative(-2f, 2f) verticalLineToRelative(4f) curveToRelative(2.3f, -1.4f, 2.2f, 0.5f, 2.2f, 0.5f) verticalLineToRelative(2.8f) lineToRelative(2.8f, 2.8f) curveToRelative(0.7f, -2.5f, 2f, -1.1f, 2f, -1.1f) lineToRelative(2f, 2f) horizontalLineToRelative(4f) curveToRelative(-1.4f, -2.3f, 0.5f, -2.2f, 0.5f, -2.2f) horizontalLineToRelative(2.8f) lineToRelative(2.8f, -2.8f) curveToRelative(-2.5f, -0.7f, -1.1f, -2f, -1.1f, -2f) close() moveTo(12f, 19f) curveToRelative(-3.866f, 0f, -7f, -3.134f, -7f, -7f) reflectiveCurveToRelative(3.134f, -7f, 7f, -7f) reflectiveCurveToRelative(7f, 3.134f, 7f, 7f) reflectiveCurveToRelative(-3.134f, 7f, -7f, 7f) close() } }.build() } val Icons.TwoTone.ImageSaw: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ImageSawTwoTone", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 5f) lineTo(12f, 5f) arcTo(7f, 7f, 0f, isMoreThanHalf = false, isPositiveArc = true, 19f, 12f) lineTo(19f, 12f) arcTo(7f, 7f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 19f) lineTo(12f, 19f) arcTo(7f, 7f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5f, 12f) lineTo(5f, 12f) arcTo(7f, 7f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 5f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7f, 14.143f) lineToRelative(10f, 0f) lineToRelative(-3.214f, -4.286f) lineToRelative(-2.5f, 3.214f) lineToRelative(-1.786f, -2.143f) lineToRelative(-2.5f, 3.214f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 15f) lineToRelative(2f, -2f) verticalLineToRelative(-4f) curveToRelative(-2.3f, 1.4f, -2.2f, -0.5f, -2.2f, -0.5f) verticalLineToRelative(-2.9f) lineToRelative(-2.8f, -2.8f) curveToRelative(-0.7f, 2.6f, -2f, 1.2f, -2f, 1.2f) lineToRelative(-2f, -2f) horizontalLineToRelative(-4f) curveToRelative(1.4f, 2.3f, -0.5f, 2.2f, -0.5f, 2.2f) horizontalLineToRelative(-2.9f) lineToRelative(-2.8f, 2.9f) curveToRelative(2.6f, 0.6f, 1.2f, 1.9f, 1.2f, 1.9f) lineToRelative(-2f, 2f) verticalLineToRelative(4f) curveToRelative(2.3f, -1.4f, 2.2f, 0.5f, 2.2f, 0.5f) verticalLineToRelative(2.8f) lineToRelative(2.8f, 2.8f) curveToRelative(0.7f, -2.5f, 2f, -1.1f, 2f, -1.1f) lineToRelative(2f, 2f) horizontalLineToRelative(4f) curveToRelative(-1.4f, -2.3f, 0.5f, -2.2f, 0.5f, -2.2f) horizontalLineToRelative(2.8f) lineToRelative(2.8f, -2.8f) curveToRelative(-2.5f, -0.7f, -1.1f, -2f, -1.1f, -2f) close() moveTo(12f, 19f) curveToRelative(-3.866f, 0f, -7f, -3.134f, -7f, -7f) reflectiveCurveToRelative(3.134f, -7f, 7f, -7f) reflectiveCurveToRelative(7f, 3.134f, 7f, 7f) reflectiveCurveToRelative(-3.134f, 7f, -7f, 7f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSearch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ImageSearch: ImageVector by lazy { Builder( name = "ImageSearch", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.5f, 2.0f) curveTo(18.0f, 2.0f, 20.0f, 4.0f, 20.0f, 6.5f) curveTo(20.0f, 7.38f, 19.75f, 8.21f, 19.31f, 8.9f) lineTo(22.39f, 12.0f) lineTo(21.0f, 13.39f) lineTo(17.88f, 10.32f) curveTo(17.19f, 10.75f, 16.37f, 11.0f, 15.5f, 11.0f) curveTo(13.0f, 11.0f, 11.0f, 9.0f, 11.0f, 6.5f) curveTo(11.0f, 4.0f, 13.0f, 2.0f, 15.5f, 2.0f) moveTo(15.5f, 4.0f) arcTo( 2.5f, 2.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 13.0f, y1 = 6.5f ) arcTo( 2.5f, 2.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 15.5f, y1 = 9.0f ) arcTo( 2.5f, 2.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 18.0f, y1 = 6.5f ) arcTo( 2.5f, 2.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 15.5f, y1 = 4.0f ) moveTo(7.5f, 14.5f) lineTo(4.0f, 19.0f) horizontalLineTo(18.0f) lineTo(13.5f, 13.0f) lineTo(10.0f, 17.5f) lineTo(7.5f, 14.5f) moveTo(20.0f, 20.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 18.0f, y1 = 22.0f ) horizontalLineTo(4.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 2.0f, y1 = 20.0f ) verticalLineTo(6.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 4.0f, y1 = 4.0f ) horizontalLineTo(9.5f) curveTo(9.18f, 4.77f, 9.0f, 5.61f, 9.0f, 6.5f) arcTo( 6.5f, 6.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 15.5f, y1 = 13.0f ) curveTo(16.18f, 13.0f, 16.84f, 12.89f, 17.46f, 12.7f) lineTo(20.0f, 15.24f) verticalLineTo(20.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSticky.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageSticky: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImageSticky", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.412f, 3.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(5f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10.175f) curveToRelative(0.267f, 0f, 0.521f, -0.05f, 0.763f, -0.15f) curveToRelative(0.242f, -0.1f, 0.454f, -0.242f, 0.638f, -0.425f) lineToRelative(3.85f, -3.85f) curveToRelative(0.183f, -0.183f, 0.325f, -0.396f, 0.425f, -0.638f) curveToRelative(0.1f, -0.242f, 0.15f, -0.496f, 0.15f, -0.763f) verticalLineTo(5f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(19f, 15f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(2f) horizontalLineTo(5f) verticalLineTo(5f) horizontalLineToRelative(14f) verticalLineToRelative(10f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5f, 19f) verticalLineTo(5f) verticalLineToRelative(14f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8.281f, 13.968f) curveToRelative(-0.248f, 0f, -0.434f, -0.114f, -0.558f, -0.341f) reflectiveCurveToRelative(-0.103f, -0.444f, 0.062f, -0.651f) lineToRelative(1.395f, -1.86f) curveToRelative(0.124f, -0.165f, 0.289f, -0.248f, 0.496f, -0.248f) reflectiveCurveToRelative(0.372f, 0.083f, 0.496f, 0.248f) lineToRelative(1.209f, 1.612f) lineToRelative(1.829f, -2.449f) curveToRelative(0.124f, -0.165f, 0.289f, -0.248f, 0.496f, -0.248f) reflectiveCurveToRelative(0.372f, 0.083f, 0.496f, 0.248f) lineToRelative(2.015f, 2.697f) curveToRelative(0.165f, 0.207f, 0.186f, 0.424f, 0.062f, 0.651f) reflectiveCurveToRelative(-0.31f, 0.341f, -0.558f, 0.341f) horizontalLineToRelative(-7.439f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageSync.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ImageSync: ImageVector by lazy { Builder( name = "ImageSync", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(8.5f, 13.5f) lineTo(5.0f, 18.0f) horizontalLineTo(13.03f) curveTo(13.11f, 19.1f, 13.47f, 20.12f, 14.03f, 21.0f) horizontalLineTo(5.0f) curveTo(3.9f, 21.0f, 3.0f, 20.11f, 3.0f, 19.0f) verticalLineTo(5.0f) curveTo(3.0f, 3.9f, 3.9f, 3.0f, 5.0f, 3.0f) horizontalLineTo(19.0f) curveTo(20.1f, 3.0f, 21.0f, 3.89f, 21.0f, 5.0f) verticalLineTo(11.18f) curveTo(20.5f, 11.07f, 20.0f, 11.0f, 19.5f, 11.0f) curveTo(17.78f, 11.0f, 16.23f, 11.67f, 15.07f, 12.76f) lineTo(14.5f, 12.0f) lineTo(11.0f, 16.5f) lineTo(8.5f, 13.5f) moveTo(19.0f, 20.0f) curveTo(17.62f, 20.0f, 16.5f, 18.88f, 16.5f, 17.5f) curveTo(16.5f, 17.1f, 16.59f, 16.72f, 16.76f, 16.38f) lineTo(15.67f, 15.29f) curveTo(15.25f, 15.92f, 15.0f, 16.68f, 15.0f, 17.5f) curveTo(15.0f, 19.71f, 16.79f, 21.5f, 19.0f, 21.5f) verticalLineTo(23.0f) lineTo(21.25f, 20.75f) lineTo(19.0f, 18.5f) verticalLineTo(20.0f) moveTo(19.0f, 13.5f) verticalLineTo(12.0f) lineTo(16.75f, 14.25f) lineTo(19.0f, 16.5f) verticalLineTo(15.0f) curveTo(20.38f, 15.0f, 21.5f, 16.12f, 21.5f, 17.5f) curveTo(21.5f, 17.9f, 21.41f, 18.28f, 21.24f, 18.62f) lineTo(22.33f, 19.71f) curveTo(22.75f, 19.08f, 23.0f, 18.32f, 23.0f, 17.5f) curveTo(23.0f, 15.29f, 21.21f, 13.5f, 19.0f, 13.5f) close() } }.build() } val Icons.Outlined.ImageSync: ImageVector by lazy { Builder( name = "ImageSync Outline", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(13.18f, 19.0f) curveTo(13.35f, 19.72f, 13.64f, 20.39f, 14.03f, 21.0f) horizontalLineTo(5.0f) curveTo(3.9f, 21.0f, 3.0f, 20.11f, 3.0f, 19.0f) verticalLineTo(5.0f) curveTo(3.0f, 3.9f, 3.9f, 3.0f, 5.0f, 3.0f) horizontalLineTo(19.0f) curveTo(20.11f, 3.0f, 21.0f, 3.9f, 21.0f, 5.0f) verticalLineTo(11.18f) curveTo(20.5f, 11.07f, 20.0f, 11.0f, 19.5f, 11.0f) curveTo(19.33f, 11.0f, 19.17f, 11.0f, 19.0f, 11.03f) verticalLineTo(5.0f) horizontalLineTo(5.0f) verticalLineTo(19.0f) horizontalLineTo(13.18f) moveTo(11.21f, 15.83f) lineTo(9.25f, 13.47f) lineTo(6.5f, 17.0f) horizontalLineTo(13.03f) curveTo(13.14f, 15.54f, 13.73f, 14.22f, 14.64f, 13.19f) lineTo(13.96f, 12.29f) lineTo(11.21f, 15.83f) moveTo(19.0f, 13.5f) verticalLineTo(12.0f) lineTo(16.75f, 14.25f) lineTo(19.0f, 16.5f) verticalLineTo(15.0f) curveTo(20.38f, 15.0f, 21.5f, 16.12f, 21.5f, 17.5f) curveTo(21.5f, 17.9f, 21.41f, 18.28f, 21.24f, 18.62f) lineTo(22.33f, 19.71f) curveTo(22.75f, 19.08f, 23.0f, 18.32f, 23.0f, 17.5f) curveTo(23.0f, 15.29f, 21.21f, 13.5f, 19.0f, 13.5f) moveTo(19.0f, 20.0f) curveTo(17.62f, 20.0f, 16.5f, 18.88f, 16.5f, 17.5f) curveTo(16.5f, 17.1f, 16.59f, 16.72f, 16.76f, 16.38f) lineTo(15.67f, 15.29f) curveTo(15.25f, 15.92f, 15.0f, 16.68f, 15.0f, 17.5f) curveTo(15.0f, 19.71f, 16.79f, 21.5f, 19.0f, 21.5f) verticalLineTo(23.0f) lineTo(21.25f, 20.75f) lineTo(19.0f, 18.5f) verticalLineTo(20.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageText.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageText: ImageVector by lazy { Builder( name = "ImageText", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(14.4125f, 5.5875f) curveTo(14.0208f, 5.1959f, 13.55f, 5.0f, 13.0f, 5.0f) horizontalLineTo(3.0f) curveTo(2.45f, 5.0f, 1.9792f, 5.1959f, 1.5875f, 5.5875f) reflectiveCurveTo(1.0f, 6.45f, 1.0f, 7.0f) verticalLineToRelative(10.0f) curveToRelative(0.0f, 0.55f, 0.1959f, 1.0208f, 0.5875f, 1.4125f) reflectiveCurveTo(2.45f, 19.0f, 3.0f, 19.0f) horizontalLineToRelative(10.0f) curveToRelative(0.55f, 0.0f, 1.0208f, -0.1959f, 1.4125f, -0.5875f) reflectiveCurveTo(15.0f, 17.55f, 15.0f, 17.0f) verticalLineTo(7.0f) curveTo(15.0f, 6.45f, 14.8041f, 5.9792f, 14.4125f, 5.5875f) close() moveTo(13.0f, 17.0f) horizontalLineTo(3.0f) verticalLineTo(7.0f) horizontalLineToRelative(10.0f) verticalLineTo(17.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(4.0f, 15.0f) lineToRelative(8.0f, 0.0f) lineToRelative(-2.6f, -3.5f) lineToRelative(-1.9f, 2.5f) lineToRelative(-1.4f, -1.85f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(3.0f, 17.0f) verticalLineToRelative(-10.0f) verticalLineTo(17.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.0f, 5.1755f) horizontalLineToRelative(6.0f) verticalLineToRelative(3.0f) horizontalLineToRelative(-6.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.0f, 15.8245f) horizontalLineToRelative(6.0f) verticalLineToRelative(3.0f) horizontalLineToRelative(-6.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.8404f, 10.5f) horizontalLineToRelative(6.0f) verticalLineToRelative(3.0f) horizontalLineToRelative(-6.0f) close() } }.build() } val Icons.TwoTone.ImageText: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "TwoTone.ImageText", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(14.413f, 5.587f) curveToRelative(-0.392f, -0.392f, -0.863f, -0.588f, -1.413f, -0.588f) horizontalLineTo(3f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.413f, 0.588f) reflectiveCurveToRelative(-0.587f, 0.862f, -0.587f, 1.412f) verticalLineToRelative(10f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.587f, 1.413f) reflectiveCurveToRelative(0.863f, 0.587f, 1.413f, 0.587f) horizontalLineToRelative(10f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.587f) reflectiveCurveToRelative(0.587f, -0.863f, 0.587f, -1.413f) verticalLineTo(7f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.587f, -1.412f) close() moveTo(13f, 17f) horizontalLineTo(3f) verticalLineTo(7f) horizontalLineToRelative(10f) verticalLineToRelative(10f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(3f, 7f) horizontalLineToRelative(10f) verticalLineToRelative(10f) horizontalLineToRelative(-10f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(4f, 15f) horizontalLineToRelative(8f) lineToRelative(-2.6f, -3.5f) lineToRelative(-1.9f, 2.5f) lineToRelative(-1.4f, -1.85f) lineToRelative(-2.1f, 2.85f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(3f, 17f) verticalLineTo(7f) verticalLineToRelative(10f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(17f, 5.175f) horizontalLineToRelative(6f) verticalLineToRelative(3f) horizontalLineToRelative(-6f) verticalLineToRelative(-3f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(17f, 15.824f) horizontalLineToRelative(6f) verticalLineToRelative(3f) horizontalLineToRelative(-6f) verticalLineToRelative(-3f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(16.84f, 10.5f) horizontalLineToRelative(6f) verticalLineToRelative(3f) horizontalLineToRelative(-6f) verticalLineToRelative(-3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToText.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageToText: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImageToText", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(8.399f, 18f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.713f, -0.288f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.288f, 0.713f, -0.288f) horizontalLineToRelative(5f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.288f) curveToRelative(0.192f, 0.192f, 0.288f, 0.429f, 0.288f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.288f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.288f, -0.712f, 0.288f) curveToRelative(0f, 0f, -5f, 0f, -5f, 0f) close() } path(fill = SolidColor(Color.Black)) { moveTo(9.542f, 9.349f) lineToRelative(2.636f, -3.509f) curveToRelative(0.066f, -0.088f, 0.146f, -0.154f, 0.239f, -0.198f) reflectiveCurveToRelative(0.189f, -0.066f, 0.288f, -0.066f) reflectiveCurveToRelative(0.195f, 0.022f, 0.288f, 0.066f) reflectiveCurveToRelative(0.173f, 0.11f, 0.239f, 0.198f) lineToRelative(2.241f, 2.982f) curveToRelative(0.066f, 0.088f, 0.143f, 0.154f, 0.231f, 0.198f) reflectiveCurveToRelative(0.187f, 0.066f, 0.297f, 0.066f) curveToRelative(0.275f, 0f, 0.472f, -0.124f, 0.593f, -0.371f) reflectiveCurveToRelative(0.099f, -0.481f, -0.066f, -0.7f) lineToRelative(-1.384f, -1.829f) curveToRelative(-0.088f, -0.121f, -0.132f, -0.253f, -0.132f, -0.395f) curveToRelative(0f, -0.143f, 0.044f, -0.275f, 0.132f, -0.395f) lineToRelative(1.647f, -2.191f) curveToRelative(0.066f, -0.088f, 0.146f, -0.154f, 0.239f, -0.198f) reflectiveCurveToRelative(0.189f, -0.066f, 0.288f, -0.066f) reflectiveCurveToRelative(0.195f, 0.022f, 0.288f, 0.066f) reflectiveCurveToRelative(0.173f, 0.11f, 0.239f, 0.198f) lineToRelative(4.613f, 6.145f) curveToRelative(0.165f, 0.22f, 0.187f, 0.45f, 0.066f, 0.692f) reflectiveCurveToRelative(-0.319f, 0.362f, -0.593f, 0.362f) horizontalLineToRelative(-11.862f) curveToRelative(-0.275f, 0f, -0.472f, -0.121f, -0.593f, -0.362f) curveToRelative(-0.121f, -0.242f, -0.099f, -0.472f, 0.066f, -0.692f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8.399f, 15f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.713f, -0.288f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.288f, 0.713f, -0.288f) horizontalLineToRelative(5f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.288f) curveToRelative(0.192f, 0.192f, 0.288f, 0.429f, 0.288f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.288f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.288f, -0.712f, 0.288f) curveToRelative(0f, 0f, -5f, 0f, -5f, 0f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18.112f, 12.287f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.288f, 0.429f, -0.288f, 0.713f) verticalLineToRelative(5.822f) curveToRelative(0f, 0.032f, 0.014f, 0.058f, 0.016f, 0.089f) curveToRelative(-0.003f, 0.031f, -0.016f, 0.057f, -0.016f, 0.089f) verticalLineToRelative(1f) horizontalLineTo(5.399f) verticalLineTo(4f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.713f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(16f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(11f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.588f) curveToRelative(0.392f, -0.392f, 0.587f, -0.862f, 0.587f, -1.412f) verticalLineToRelative(-1f) curveToRelative(0f, -0.032f, -0.014f, -0.058f, -0.016f, -0.089f) curveToRelative(0.003f, -0.031f, 0.016f, -0.057f, 0.016f, -0.089f) verticalLineToRelative(-5.822f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageToolboxBroken.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageToolboxBroken: ImageVector by lazy { ImageVector.Builder( name = "Outlined.ImageToolboxBroken", defaultWidth = 1305.dp, defaultHeight = 1295.dp, viewportWidth = 1305f, viewportHeight = 1295f ).apply { path( fill = SolidColor(Color(0xFF000000)), pathFillType = PathFillType.EvenOdd ) { moveTo(794.4f, 452.5f) verticalLineToRelative(93.6f) curveToRelative(0f, 16.1f, 5.4f, 29.5f, 16.3f, 40.4f) curveToRelative(10.9f, 10.9f, 24.3f, 16.3f, 40.4f, 16.3f) horizontalLineToRelative(93.6f) curveToRelative(7.6f, 0f, 14.9f, -1.4f, 22f, -4.3f) curveToRelative(7.1f, -2.8f, 13.5f, -7.1f, 19.1f, -12.8f) lineToRelative(295f, -295f) curveToRelative(8.5f, -8.5f, 14.7f, -18.2f, 18.4f, -29.1f) curveToRelative(3.8f, -10.9f, 5.7f, -21.5f, 5.7f, -31.9f) curveToRelative(0f, -10.4f, -2.1f, -20.8f, -6.4f, -31.2f) curveToRelative(-4.3f, -10.4f, -10.2f, -19.9f, -17.7f, -28.4f) lineToRelative(-52.5f, -52.5f) curveToRelative(-8.5f, -8.5f, -18f, -14.9f, -28.4f, -19.1f) curveToRelative(-10.4f, -4.3f, -21.3f, -6.4f, -32.6f, -6.4f) curveToRelative(-10.4f, 0f, -20.8f, 1.9f, -31.2f, 5.7f) curveToRelative(-10.4f, 3.8f, -19.9f, 9.9f, -28.4f, 18.4f) lineToRelative(-296.4f, 295f) curveToRelative(-5.7f, 5.7f, -9.9f, 12.1f, -12.8f, 19.1f) curveTo(795.8f, 437.6f, 794.4f, 444.9f, 794.4f, 452.5f) close() moveTo(933.4f, 517.7f) horizontalLineToRelative(-53.9f) verticalLineToRelative(-53.9f) lineToRelative(173f, -171.6f) lineToRelative(52.5f, 52.5f) lineTo(933.4f, 517.7f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(657.8f, 301.6f) curveToRelative(69.4f, 0f, 125.2f, 0.3f, 171.5f, 2.2f) lineToRelative(94f, -94f) curveToRelative(-70.8f, -9.6f, -162.9f, -9.6f, -289.3f, -9.6f) curveToRelative(-5.1f, 0f, -10.1f, 0f, -15.1f, 0f) lineTo(657.8f, 301.6f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(1029f, 1268.8f) curveToRelative(12.8f, -6.2f, 24.6f, -13.4f, 35.8f, -21.7f) curveToRelative(23.7f, -17.6f, 44.7f, -38.6f, 62.3f, -62.3f) curveToRelative(60.4f, -81.1f, 60.4f, -197.7f, 60.4f, -430.9f) curveToRelative(0f, -115.3f, 0f, -202.2f, -7.3f, -270.3f) lineToRelative(-95.5f, 95.5f) curveToRelative(1.4f, 47.6f, 1.5f, 104.5f, 1.5f, 174.7f) curveToRelative(0f, 16.5f, 0f, 32.3f, 0f, 47.5f) curveToRelative(-49.3f, -41f, -87.3f, -60.7f, -144.1f, -37.7f) curveToRelative(-29.4f, 11.9f, -58.5f, 30.6f, -86.9f, 52.5f) lineTo(1029f, 1268.8f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(493.8f, 352.3f) curveToRelative(-6.3f, -14.6f, -14.9f, -27.4f, -25.7f, -38.2f) curveToRelative(-10.8f, -10.8f, -23.6f, -19.4f, -38.2f, -25.7f) curveToRelative(-14.6f, -6.3f, -30.3f, -9.5f, -46.9f, -9.5f) reflectiveCurveToRelative(-32.3f, 3.2f, -46.9f, 9.5f) reflectiveCurveToRelative(-27.4f, 14.9f, -38.2f, 25.7f) curveToRelative(-10.8f, 10.8f, -19.4f, 23.6f, -25.7f, 38.2f) curveToRelative(-6.3f, 14.6f, -9.5f, 30.3f, -9.5f, 46.9f) reflectiveCurveToRelative(3.2f, 32.3f, 9.5f, 46.9f) reflectiveCurveToRelative(14.9f, 27.4f, 25.7f, 38.2f) curveToRelative(10.8f, 10.8f, 23.6f, 19.4f, 38.2f, 25.7f) curveToRelative(14.6f, 6.3f, 30.3f, 9.5f, 46.9f, 9.5f) reflectiveCurveToRelative(32.3f, -3.2f, 46.9f, -9.5f) curveToRelative(14.6f, -6.3f, 27.4f, -14.9f, 38.2f, -25.7f) curveToRelative(10.8f, -10.8f, 19.4f, -23.6f, 25.7f, -38.2f) curveToRelative(6.3f, -14.6f, 9.5f, -30.3f, 9.5f, -46.9f) reflectiveCurveTo(500.1f, 367f, 493.8f, 352.3f) close() moveTo(392.2f, 460f) curveToRelative(-2.5f, 2.5f, -5.5f, 3.7f, -9.2f, 3.7f) reflectiveCurveToRelative(-6.7f, -1.2f, -9.2f, -3.7f) curveToRelative(-2.5f, -2.5f, -3.7f, -5.5f, -3.7f, -9.2f) reflectiveCurveToRelative(1.2f, -6.7f, 3.7f, -9.2f) curveToRelative(2.5f, -2.5f, 5.5f, -3.7f, 9.2f, -3.7f) reflectiveCurveToRelative(6.7f, 1.2f, 9.2f, 3.7f) curveToRelative(2.5f, 2.5f, 3.7f, 5.5f, 3.7f, 9.2f) reflectiveCurveTo(394.6f, 457.6f, 392.2f, 460f) close() moveTo(395.9f, 399.3f) curveToRelative(0f, 3.7f, -1.2f, 6.7f, -3.7f, 9.2f) curveToRelative(-2.5f, 2.5f, -5.5f, 3.7f, -9.2f, 3.7f) reflectiveCurveToRelative(-6.7f, -1.2f, -9.2f, -3.7f) reflectiveCurveToRelative(-3.7f, -5.5f, -3.7f, -9.2f) verticalLineToRelative(-51.6f) curveToRelative(0f, -3.7f, 1.2f, -6.7f, 3.7f, -9.2f) curveToRelative(2.5f, -2.5f, 5.5f, -3.7f, 9.2f, -3.7f) reflectiveCurveToRelative(6.7f, 1.2f, 9.2f, 3.7f) curveToRelative(2.5f, 2.5f, 3.7f, 5.5f, 3.7f, 9.2f) verticalLineTo(399.3f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(338.5f, 837.2f) lineToRelative(75.9f, 22.8f) lineToRelative(32.7f, -33.6f) lineToRelative(-64.6f, -39.7f) curveToRelative(-6.5f, -3.7f, -13.1f, -6.7f, -19.7f, -9.1f) lineTo(338.5f, 837.2f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(763.4f, 887.4f) lineToRelative(-24.2f, -69.5f) lineToRelative(-201f, 42.6f) lineToRelative(128f, 108.2f) lineTo(528.8f, 890f) lineToRelative(0f, 0.6f) lineToRelative(-3f, -2.3f) lineToRelative(-80.6f, -1.7f) lineToRelative(81.4f, 186.8f) horizontalLineToRelative(0f) lineToRelative(-78.3f, -115f) lineToRelative(-123.7f, 100.7f) lineToRelative(99.7f, -135.8f) lineToRelative(-25.4f, -37.4f) lineToRelative(-84.1f, -25.2f) lineToRelative(-15.3f, -4.6f) lineToRelative(6f, -14.9f) lineToRelative(28.3f, -69.5f) curveToRelative(-69.2f, -5f, -145.2f, 50.4f, -219.1f, 104.2f) lineToRelative(-1f, 0.7f) curveToRelative(-2f, -10.6f, -3.7f, -22.4f, -5.2f, -35.8f) curveToRelative(-6.8f, -61.6f, -6.9f, -142.5f, -6.9f, -261.4f) reflectiveCurveToRelative(0.1f, -199.8f, 6.9f, -261.4f) curveToRelative(6.6f, -59.6f, 18.4f, -88.8f, 33.4f, -109f) curveToRelative(11.8f, -15.8f, 25.8f, -29.8f, 41.6f, -41.6f) curveToRelative(20.2f, -15f, 49.3f, -26.8f, 109f, -33.4f) curveToRelative(46.2f, -5.1f, 103.3f, -6.5f, 179.2f, -6.8f) lineTo(432.8f, 26.2f) curveToRelative(-155.3f, 1.8f, -244.3f, 11.1f, -310f, 60f) curveToRelative(-23.7f, 17.6f, -44.7f, 38.6f, -62.3f, 62.3f) curveTo(0.1f, 229.6f, 0.1f, 346.2f, 0.1f, 579.4f) reflectiveCurveToRelative(0f, 349.8f, 60.4f, 430.9f) curveToRelative(17.6f, 23.7f, 38.6f, 44.7f, 62.3f, 62.3f) curveToRelative(81.1f, 60.4f, 197.7f, 60.4f, 430.9f, 60.4f) curveToRelative(132.7f, 0f, 227.6f, 0f, 299.7f, -11.1f) lineTo(763.4f, 887.4f) close() moveTo(299.4f, 1073.4f) lineTo(299.4f, 1073.4f) curveTo(299.4f, 1073.3f, 299.4f, 1073.3f, 299.4f, 1073.4f) lineTo(299.4f, 1073.4f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(723.5f, 783.5f) lineToRelative(-9f, -23.4f) lineToRelative(-17.5f, -45.5f) lineToRelative(-47.9f, 37.7f) curveToRelative(-14.6f, 13.1f, -29f, 25.5f, -43.1f, 36.7f) lineToRelative(-8.8f, 6.8f) curveToRelative(-0.2f, 0.2f, -0.4f, 0.3f, -0.6f, 0.5f) lineToRelative(0f, 0f) lineToRelative(-29.4f, 20.4f) lineTo(723.5f, 783.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageTooltip.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageTooltip: ImageVector by lazy { Builder( name = "Image Tooltip", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(4.0f, 2.0f) horizontalLineTo(20.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 22.0f, y1 = 4.0f ) verticalLineTo(16.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 20.0f, y1 = 18.0f ) horizontalLineTo(16.0f) lineTo(12.0f, 22.0f) lineTo(8.0f, 18.0f) horizontalLineTo(4.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 2.0f, y1 = 16.0f ) verticalLineTo(4.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 4.0f, y1 = 2.0f ) moveTo(4.0f, 4.0f) verticalLineTo(16.0f) horizontalLineTo(8.83f) lineTo(12.0f, 19.17f) lineTo(15.17f, 16.0f) horizontalLineTo(20.0f) verticalLineTo(4.0f) horizontalLineTo(4.0f) moveTo(7.5f, 6.0f) arcTo( 1.5f, 1.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 9.0f, y1 = 7.5f ) arcTo( 1.5f, 1.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 7.5f, y1 = 9.0f ) arcTo( 1.5f, 1.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 6.0f, y1 = 7.5f ) arcTo( 1.5f, 1.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 7.5f, y1 = 6.0f ) moveTo(6.0f, 14.0f) lineTo(11.0f, 9.0f) lineTo(13.0f, 11.0f) lineTo(18.0f, 6.0f) verticalLineTo(14.0f) horizontalLineTo(6.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImageWeight.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImageWeight: ImageVector by lazy { Builder( name = "Image Weight", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.0f, 19.0f) horizontalLineToRelative(12.0f) lineTo(16.575f, 9.0f) horizontalLineTo(7.425f) lineTo(6.0f, 19.0f) close() moveTo(12.0f, 7.0f) curveToRelative(0.2833f, 0.0f, 0.5208f, -0.0958f, 0.7125f, -0.2875f) reflectiveCurveTo(13.0f, 6.2833f, 13.0f, 6.0f) reflectiveCurveToRelative(-0.0958f, -0.5208f, -0.2875f, -0.7125f) curveTo(12.5208f, 5.0958f, 12.2833f, 5.0f, 12.0f, 5.0f) reflectiveCurveToRelative(-0.5208f, 0.0958f, -0.7125f, 0.2875f) curveTo(11.0958f, 5.4792f, 11.0f, 5.7167f, 11.0f, 6.0f) reflectiveCurveToRelative(0.0958f, 0.5208f, 0.2875f, 0.7125f) reflectiveCurveTo(11.7167f, 7.0f, 12.0f, 7.0f) close() moveTo(14.825f, 7.0f) horizontalLineToRelative(1.75f) curveToRelative(0.5f, 0.0f, 0.9333f, 0.1667f, 1.3f, 0.5f) curveToRelative(0.3667f, 0.3333f, 0.5917f, 0.7417f, 0.675f, 1.225f) lineToRelative(1.425f, 10.0f) curveToRelative(0.0833f, 0.6f, -0.0708f, 1.1292f, -0.4625f, 1.5875f) curveTo(19.1208f, 20.7708f, 18.6167f, 21.0f, 18.0f, 21.0f) horizontalLineTo(6.0f) curveToRelative(-0.6167f, 0.0f, -1.1208f, -0.2292f, -1.5125f, -0.6875f) reflectiveCurveToRelative(-0.5458f, -0.9875f, -0.4625f, -1.5875f) lineToRelative(1.425f, -10.0f) curveTo(5.5333f, 8.2417f, 5.7583f, 7.8333f, 6.125f, 7.5f) curveToRelative(0.3667f, -0.3333f, 0.8f, -0.5f, 1.3f, -0.5f) horizontalLineToRelative(1.75f) curveTo(9.125f, 6.8333f, 9.0833f, 6.6708f, 9.05f, 6.5125f) reflectiveCurveTo(9.0f, 6.1833f, 9.0f, 6.0f) curveToRelative(0.0f, -0.8333f, 0.2917f, -1.5417f, 0.875f, -2.125f) curveToRelative(0.5833f, -0.5833f, 1.2917f, -0.875f, 2.125f, -0.875f) reflectiveCurveToRelative(1.5417f, 0.2917f, 2.125f, 0.875f) reflectiveCurveTo(15.0f, 5.1667f, 15.0f, 6.0f) curveToRelative(0.0f, 0.1833f, -0.0167f, 0.3542f, -0.05f, 0.5125f) reflectiveCurveTo(14.875f, 6.8333f, 14.825f, 7.0f) close() moveTo(6.0f, 19.0f) horizontalLineToRelative(12.0f) horizontalLineTo(6.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.2442f, 15.1263f) curveToRelative(-0.1654f, -0.2197f, -0.4357f, -0.5795f, -0.6007f, -0.7995f) lineToRelative(-0.9704f, -1.2938f) curveToRelative(-0.165f, -0.22f, -0.435f, -0.22f, -0.6f, 0.0f) lineToRelative(-1.275f, 1.7f) curveToRelative(-0.165f, 0.22f, -0.4354f, 0.2203f, -0.6009f, 7.0E-4f) lineToRelative(-0.7733f, -1.0263f) curveToRelative(-0.1655f, -0.2196f, -0.4361f, -0.2195f, -0.6014f, 2.0E-4f) lineToRelative(-1.5239f, 2.0259f) curveToRelative(-0.1653f, 0.2198f, -0.0756f, 0.3996f, 0.1994f, 0.3996f) horizontalLineToRelative(0.9267f) curveToRelative(0.275f, 0.0f, 0.725f, 0.0f, 1.0f, 0.0f) horizontalLineToRelative(3.1464f) curveToRelative(0.275f, 0.0f, 0.725f, 0.0f, 1.0f, 0.0f) horizontalLineToRelative(0.9308f) curveToRelative(0.275f, 0.0f, 0.3647f, -0.1798f, 0.1993f, -0.3995f) lineTo(15.2442f, 15.1263f) close() } }.build() } val Icons.TwoTone.ImageWeight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "TwoTone.ImageWeight", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(19.975f, 18.725f) lineToRelative(-1.425f, -10f) curveToRelative(-0.083f, -0.483f, -0.308f, -0.892f, -0.675f, -1.225f) reflectiveCurveToRelative(-0.8f, -0.5f, -1.3f, -0.5f) horizontalLineToRelative(-1.75f) curveToRelative(0.05f, -0.167f, 0.092f, -0.329f, 0.125f, -0.487f) curveToRelative(0.033f, -0.158f, 0.05f, -0.329f, 0.05f, -0.513f) curveToRelative(0f, -0.833f, -0.292f, -1.542f, -0.875f, -2.125f) reflectiveCurveToRelative(-1.292f, -0.875f, -2.125f, -0.875f) reflectiveCurveToRelative(-1.542f, 0.292f, -2.125f, 0.875f) reflectiveCurveToRelative(-0.875f, 1.292f, -0.875f, 2.125f) curveToRelative(0f, 0.183f, 0.017f, 0.354f, 0.05f, 0.513f) curveToRelative(0.033f, 0.158f, 0.075f, 0.321f, 0.125f, 0.487f) horizontalLineToRelative(-1.75f) curveToRelative(-0.5f, 0f, -0.933f, 0.167f, -1.3f, 0.5f) reflectiveCurveToRelative(-0.592f, 0.742f, -0.675f, 1.225f) lineToRelative(-1.425f, 10f) curveToRelative(-0.083f, 0.6f, 0.071f, 1.129f, 0.462f, 1.588f) curveToRelative(0.392f, 0.458f, 0.896f, 0.688f, 1.513f, 0.688f) horizontalLineToRelative(12f) curveToRelative(0.617f, 0f, 1.121f, -0.229f, 1.513f, -0.688f) curveToRelative(0.392f, -0.458f, 0.546f, -0.987f, 0.462f, -1.588f) close() moveTo(11.287f, 5.287f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.713f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.713f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.288f, -0.713f, 0.288f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.713f, -0.288f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.713f) close() moveTo(6f, 19f) lineToRelative(1.425f, -10f) horizontalLineToRelative(9.15f) lineToRelative(1.425f, 10f) horizontalLineTo(6f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15.244f, 15.126f) curveToRelative(-0.165f, -0.22f, -0.436f, -0.58f, -0.601f, -0.8f) lineToRelative(-0.97f, -1.294f) curveToRelative(-0.165f, -0.22f, -0.435f, -0.22f, -0.6f, 0f) lineToRelative(-1.275f, 1.7f) curveToRelative(-0.165f, 0.22f, -0.435f, 0.22f, -0.601f, 0.001f) lineToRelative(-0.773f, -1.026f) curveToRelative(-0.165f, -0.22f, -0.436f, -0.219f, -0.601f, 0f) lineToRelative(-1.524f, 2.026f) curveToRelative(-0.165f, 0.22f, -0.076f, 0.4f, 0.199f, 0.4f) horizontalLineToRelative(7.004f) curveToRelative(0.275f, 0f, 0.365f, -0.18f, 0.199f, -0.399f) lineToRelative(-0.457f, -0.607f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6f, 19f) lineToRelative(12f, 0f) lineToRelative(-1.425f, -10f) lineToRelative(-9.15f, 0f) lineToRelative(-1.425f, 10f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 7f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.288f, -0.429f, 0.288f, -0.713f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.288f, -0.713f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.288f, 0.429f, -0.288f, 0.713f) reflectiveCurveToRelative(0.096f, 0.521f, 0.288f, 0.713f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(6f, 19f) horizontalLineToRelative(12f) horizontalLineTo(6f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ImagesMode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImagesMode", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(200f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(120f, 760f) verticalLineToRelative(-560f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(200f, 120f) horizontalLineToRelative(560f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(840f, 200f) verticalLineToRelative(560f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(760f, 840f) lineTo(200f, 840f) close() moveTo(200f, 760f) horizontalLineToRelative(560f) verticalLineToRelative(-560f) lineTo(200f, 200f) verticalLineToRelative(560f) close() moveTo(200f, 760f) verticalLineToRelative(-560f) verticalLineToRelative(560f) close() moveTo(280f, 680f) horizontalLineToRelative(400f) quadToRelative(12f, 0f, 18f, -11f) reflectiveQuadToRelative(-2f, -21f) lineTo(586f, 501f) quadToRelative(-6f, -8f, -16f, -8f) reflectiveQuadToRelative(-16f, 8f) lineTo(450f, 640f) lineToRelative(-74f, -99f) quadToRelative(-6f, -8f, -16f, -8f) reflectiveQuadToRelative(-16f, 8f) lineToRelative(-80f, 107f) quadToRelative(-8f, 10f, -2f, 21f) reflectiveQuadToRelative(18f, 11f) close() moveTo(382.5f, 382.5f) quadTo(400f, 365f, 400f, 340f) reflectiveQuadToRelative(-17.5f, -42.5f) quadTo(365f, 280f, 340f, 280f) reflectiveQuadToRelative(-42.5f, 17.5f) quadTo(280f, 315f, 280f, 340f) reflectiveQuadToRelative(17.5f, 42.5f) quadTo(315f, 400f, 340f, 400f) reflectiveQuadToRelative(42.5f, -17.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ImagesearchRoller.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ImagesearchRoller: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.ImagesearchRoller", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(600f, 920f) lineTo(440f, 920f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(400f, 880f) verticalLineToRelative(-240f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(440f, 600f) horizontalLineToRelative(40f) verticalLineToRelative(-120f) lineTo(160f, 480f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(80f, 400f) verticalLineToRelative(-160f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(160f, 160f) horizontalLineToRelative(80f) verticalLineToRelative(-40f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(280f, 80f) horizontalLineToRelative(480f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(800f, 120f) verticalLineToRelative(160f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(760f, 320f) lineTo(280f, 320f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(240f, 280f) verticalLineToRelative(-40f) horizontalLineToRelative(-80f) verticalLineToRelative(160f) horizontalLineToRelative(320f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(560f, 480f) verticalLineToRelative(120f) horizontalLineToRelative(40f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(640f, 640f) verticalLineToRelative(240f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(600f, 920f) close() } }.build() } val Icons.Outlined.ImagesearchRoller: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ImagesearchRoller", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(600f, 920f) lineTo(440f, 920f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(400f, 880f) verticalLineToRelative(-240f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(440f, 600f) horizontalLineToRelative(40f) verticalLineToRelative(-120f) lineTo(160f, 480f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(80f, 400f) verticalLineToRelative(-160f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(160f, 160f) horizontalLineToRelative(80f) verticalLineToRelative(-40f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(280f, 80f) horizontalLineToRelative(480f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(800f, 120f) verticalLineToRelative(160f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(760f, 320f) lineTo(280f, 320f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(240f, 280f) verticalLineToRelative(-40f) horizontalLineToRelative(-80f) verticalLineToRelative(160f) horizontalLineToRelative(320f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(560f, 480f) verticalLineToRelative(120f) horizontalLineToRelative(40f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(640f, 640f) verticalLineToRelative(240f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(600f, 920f) close() moveTo(480f, 840f) horizontalLineToRelative(80f) verticalLineToRelative(-160f) horizontalLineToRelative(-80f) verticalLineToRelative(160f) close() moveTo(320f, 240f) horizontalLineToRelative(400f) verticalLineToRelative(-80f) lineTo(320f, 160f) verticalLineToRelative(80f) close() moveTo(480f, 840f) horizontalLineToRelative(80f) horizontalLineToRelative(-80f) close() moveTo(320f, 240f) verticalLineToRelative(-80f) verticalLineToRelative(80f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Interface.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Interface: ImageVector by lazy { Builder( name = "Interface", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.0f, 9.0f) verticalLineTo(4.0f) horizontalLineTo(13.0f) verticalLineTo(9.0f) horizontalLineTo(23.0f) verticalLineTo(16.0f) horizontalLineTo(18.0f) verticalLineTo(21.0f) horizontalLineTo(11.0f) verticalLineTo(16.0f) horizontalLineTo(1.0f) verticalLineTo(9.0f) horizontalLineTo(6.0f) moveTo(16.0f, 16.0f) horizontalLineTo(13.0f) verticalLineTo(19.0f) horizontalLineTo(16.0f) verticalLineTo(16.0f) moveTo(8.0f, 9.0f) horizontalLineTo(11.0f) verticalLineTo(6.0f) horizontalLineTo(8.0f) verticalLineTo(9.0f) moveTo(6.0f, 14.0f) verticalLineTo(11.0f) horizontalLineTo(3.0f) verticalLineTo(14.0f) horizontalLineTo(6.0f) moveTo(18.0f, 11.0f) verticalLineTo(14.0f) horizontalLineTo(21.0f) verticalLineTo(11.0f) horizontalLineTo(18.0f) moveTo(13.0f, 11.0f) verticalLineTo(14.0f) horizontalLineTo(16.0f) verticalLineTo(11.0f) horizontalLineTo(13.0f) moveTo(8.0f, 11.0f) verticalLineTo(14.0f) horizontalLineTo(11.0f) verticalLineTo(11.0f) horizontalLineTo(8.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jpg.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Jpg: ImageVector by lazy { Builder( name = "Jpg", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(8.1429f, 13.9286f) curveToRelative(0.0f, 1.4143f, -1.1571f, 1.9286f, -2.5714f, 1.9286f) reflectiveCurveTo(3.0f, 15.3429f, 3.0f, 13.9286f) verticalLineTo(12.0f) horizontalLineToRelative(1.9286f) verticalLineToRelative(1.9286f) horizontalLineToRelative(1.2857f) verticalLineTo(8.1429f) horizontalLineToRelative(1.9286f) verticalLineTo(13.9286f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(21.0f, 10.0714f) horizontalLineToRelative(-3.2143f) verticalLineToRelative(3.8571f) horizontalLineToRelative(1.2857f) verticalLineTo(12.0f) horizontalLineTo(21.0f) verticalLineToRelative(2.1857f) curveToRelative(0.0f, 0.9f, -0.6429f, 1.6714f, -1.6714f, 1.6714f) horizontalLineToRelative(-1.6714f) curveToRelative(-1.0286f, 0.0f, -1.6714f, -0.9f, -1.6714f, -1.6714f) verticalLineToRelative(-4.2429f) curveTo(15.8571f, 9.0429f, 16.5f, 8.1429f, 17.5286f, 8.1429f) horizontalLineToRelative(1.6714f) curveToRelative(1.0286f, 0.0f, 1.6714f, 0.9f, 1.6714f, 1.6714f) verticalLineToRelative(0.2571f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.6429f, 8.1429f) horizontalLineTo(9.4286f) verticalLineToRelative(7.7142f) horizontalLineToRelative(1.9285f) verticalLineToRelative(-2.5714f) horizontalLineToRelative(1.2858f) curveToRelative(1.0286f, 0.0f, 1.9285f, -0.9f, 1.9285f, -1.9286f) verticalLineToRelative(-1.2857f) curveTo(14.5714f, 9.0428f, 13.6714f, 8.1429f, 12.6429f, 8.1429f) close() moveTo(12.6429f, 11.3571f) horizontalLineToRelative(-1.2858f) verticalLineToRelative(-1.2857f) horizontalLineToRelative(1.2858f) verticalLineTo(11.3571f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Jxl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Jxl: ImageVector by lazy { Builder( name = "Jxl", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(13.1206f, 12.0514f) curveToRelative(0.8368f, -1.7278f, 1.6713f, -3.4556f, 2.5059f, -5.1834f) horizontalLineToRelative(-2.3242f) curveToRelative(-0.5384f, 1.1136f, -1.0791f, 2.2297f, -1.6175f, 3.3433f) curveTo(10.8144f, 9.0976f, 9.9439f, 7.9816f, 9.0735f, 6.868f) horizontalLineTo(6.7493f) lineToRelative(4.0449f, 5.1834f) curveToRelative(-0.8188f, 1.692f, -1.6377f, 3.3863f, -2.4565f, 5.0783f) horizontalLineToRelative(2.3242f) curveToRelative(0.5227f, -1.0802f, 1.0454f, -2.1604f, 1.5659f, -3.2381f) curveToRelative(0.8435f, 1.0802f, 1.6848f, 2.1604f, 2.5283f, 3.2381f) horizontalLineToRelative(2.3242f) curveToRelative(-1.3169f, -1.692f, -2.6382f, -3.3839f, -3.9596f, -5.0783f) verticalLineTo(12.0514f) close() moveTo(5.4324f, 16.9504f) lineToRelative(0.0067f, 0.055f) lineToRelative(0.0157f, 0.0621f) curveToRelative(0.0852f, 0.3609f, 0.1997f, 1.4219f, -0.2692f, 2.0624f) curveToRelative(-0.1391f, 0.1912f, -0.332f, 0.3465f, -0.5743f, 0.4612f) lineTo(3.2092f, 22.0f) curveToRelative(0.8278f, 0.0f, 1.5569f, -0.1386f, 2.1649f, -0.4158f) curveToRelative(0.581f, -0.2629f, 1.0589f, -0.65f, 1.4178f, -1.1495f) curveToRelative(0.498f, -0.6906f, 0.7605f, -1.5916f, 0.7583f, -2.6072f) curveToRelative(0.0f, -0.5927f, -0.0897f, -1.0658f, -0.1279f, -1.2427f) lineToRelative(-0.9176f, -6.5312f) horizontalLineToRelative(0.0022f) verticalLineTo(7.898f) horizontalLineTo(2.0f) verticalLineToRelative(2.1556f) horizontalLineToRelative(2.4633f) lineTo(5.4324f, 16.9504f) close() moveTo(18.5676f, 7.0472f) lineToRelative(-0.0067f, -0.055f) lineToRelative(-0.0157f, -0.0621f) curveToRelative(-0.0852f, -0.3609f, -0.1997f, -1.4219f, 0.2692f, -2.0624f) curveToRelative(0.1391f, -0.1912f, 0.332f, -0.3465f, 0.5743f, -0.4612f) lineTo(20.7908f, 2.0f) curveToRelative(-0.8278f, 0.0f, -1.5569f, 0.1386f, -2.1649f, 0.4158f) curveToRelative(-0.581f, 0.2629f, -1.0589f, 0.65f, -1.4178f, 1.1495f) curveToRelative(-0.498f, 0.6906f, -0.7605f, 1.5916f, -0.7583f, 2.6072f) curveToRelative(0.0f, 0.5927f, 0.0897f, 1.0658f, 0.1279f, 1.2427f) lineToRelative(0.9176f, 6.5312f) horizontalLineToRelative(-0.0022f) verticalLineToRelative(2.1556f) horizontalLineTo(22.0f) verticalLineToRelative(-2.1556f) horizontalLineToRelative(-2.4633f) lineToRelative(-0.9692f, -6.8993f) verticalLineTo(7.0472f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(5.4324f, 16.9504f) lineToRelative(0.0067f, 0.055f) lineToRelative(0.0157f, 0.0621f) curveToRelative(0.0852f, 0.3609f, 0.1997f, 1.4219f, -0.2692f, 2.0624f) curveToRelative(-0.1391f, 0.1912f, -0.332f, 0.3465f, -0.5743f, 0.4612f) lineTo(3.2092f, 22.0f) curveToRelative(0.8278f, 0.0f, 1.5569f, -0.1386f, 2.1649f, -0.4158f) curveToRelative(0.581f, -0.2629f, 1.0589f, -0.65f, 1.4178f, -1.1495f) curveToRelative(0.498f, -0.6906f, 0.7605f, -1.5916f, 0.7583f, -2.6072f) curveToRelative(0.0f, -0.5927f, -0.0897f, -1.0658f, -0.1279f, -1.2427f) lineToRelative(-0.9176f, -6.5312f) horizontalLineToRelative(0.0022f) verticalLineTo(7.898f) horizontalLineTo(2.0f) verticalLineToRelative(2.1556f) horizontalLineToRelative(2.4633f) lineTo(5.4324f, 16.9504f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVariant.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.KeyVariant: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "KeyVariant", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(16.855f, 9.083f) curveToRelative(0f, -0.538f, -0.189f, -0.995f, -0.566f, -1.373f) reflectiveCurveToRelative(-0.835f, -0.566f, -1.373f, -0.566f) reflectiveCurveToRelative(-0.995f, 0.189f, -1.373f, 0.566f) reflectiveCurveToRelative(-0.566f, 0.835f, -0.566f, 1.373f) reflectiveCurveToRelative(0.189f, 0.995f, 0.566f, 1.373f) reflectiveCurveToRelative(0.835f, 0.566f, 1.373f, 0.566f) reflectiveCurveToRelative(0.995f, -0.189f, 1.373f, -0.566f) curveToRelative(0.377f, -0.377f, 0.566f, -0.835f, 0.566f, -1.373f) close() moveTo(19.034f, 13.201f) curveToRelative(1.144f, -1.144f, 1.716f, -2.516f, 1.716f, -4.118f) reflectiveCurveToRelative(-0.572f, -2.974f, -1.716f, -4.118f) reflectiveCurveToRelative(-2.516f, -1.716f, -4.118f, -1.716f) reflectiveCurveToRelative(-2.974f, 0.572f, -4.118f, 1.716f) curveToRelative(-0.766f, 0.766f, -1.272f, 1.65f, -1.518f, 2.651f) reflectiveCurveToRelative(-0.243f, 1.993f, 0.009f, 2.977f) lineToRelative(-5.748f, 5.748f) curveToRelative(-0.092f, 0.092f, -0.163f, 0.197f, -0.214f, 0.317f) reflectiveCurveToRelative(-0.077f, 0.249f, -0.077f, 0.386f) lineToRelative(-0f, 2.745f) curveToRelative(0f, 0.137f, 0.026f, 0.26f, 0.077f, 0.369f) curveToRelative(0.051f, 0.109f, 0.123f, 0.209f, 0.214f, 0.3f) reflectiveCurveToRelative(0.192f, 0.163f, 0.3f, 0.214f) reflectiveCurveToRelative(0.232f, 0.077f, 0.369f, 0.077f) horizontalLineToRelative(4.358f) curveToRelative(0.114f, 0f, 0.229f, -0.023f, 0.343f, -0.069f) reflectiveCurveToRelative(0.217f, -0.103f, 0.309f, -0.172f) reflectiveCurveToRelative(0.166f, -0.154f, 0.223f, -0.257f) reflectiveCurveToRelative(0.092f, -0.217f, 0.103f, -0.343f) lineToRelative(0.223f, -1.561f) lineToRelative(1.716f, -0.24f) curveToRelative(0.103f, -0.011f, 0.2f, -0.04f, 0.292f, -0.086f) reflectiveCurveToRelative(0.172f, -0.103f, 0.24f, -0.172f) reflectiveCurveToRelative(0.129f, -0.152f, 0.18f, -0.249f) reflectiveCurveToRelative(0.083f, -0.197f, 0.094f, -0.3f) lineToRelative(0.309f, -1.784f) lineToRelative(0.806f, -0.806f) curveToRelative(0.984f, 0.252f, 1.976f, 0.254f, 2.977f, 0.009f) reflectiveCurveToRelative(1.884f, -0.752f, 2.651f, -1.518f) close() moveTo(17.662f, 11.828f) curveToRelative(-0.641f, 0.641f, -1.398f, 1.009f, -2.273f, 1.107f) reflectiveCurveToRelative(-1.69f, -0.071f, -2.445f, -0.506f) lineToRelative(-2.145f, 2.145f) lineToRelative(-0.292f, 1.699f) lineToRelative(0.009f, 0.009f) lineToRelative(-0.009f, -0.009f) lineToRelative(-2.453f, 0.36f) lineToRelative(-0.275f, 2.162f) horizontalLineToRelative(-2.574f) lineToRelative(0.009f, -0.009f) lineToRelative(-0.009f, 0.009f) verticalLineToRelative(-1.373f) lineToRelative(-0.009f, -0.009f) lineToRelative(0.009f, 0.009f) lineToRelative(6.365f, -6.365f) curveToRelative(-0.435f, -0.755f, -0.603f, -1.57f, -0.506f, -2.445f) reflectiveCurveToRelative(0.466f, -1.633f, 1.107f, -2.273f) curveToRelative(0.755f, -0.755f, 1.67f, -1.132f, 2.745f, -1.132f) reflectiveCurveToRelative(1.99f, 0.377f, 2.745f, 1.132f) reflectiveCurveToRelative(1.132f, 1.67f, 1.132f, 2.745f) reflectiveCurveToRelative(-0.377f, 1.99f, -1.132f, 2.745f) close() } }.build() } val Icons.TwoTone.KeyVariant: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoToneKeyVariant", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(16.855f, 9.083f) curveToRelative(0f, -0.538f, -0.189f, -0.995f, -0.566f, -1.373f) reflectiveCurveToRelative(-0.835f, -0.566f, -1.373f, -0.566f) reflectiveCurveToRelative(-0.995f, 0.189f, -1.373f, 0.566f) reflectiveCurveToRelative(-0.566f, 0.835f, -0.566f, 1.373f) reflectiveCurveToRelative(0.189f, 0.995f, 0.566f, 1.373f) reflectiveCurveToRelative(0.835f, 0.566f, 1.373f, 0.566f) reflectiveCurveToRelative(0.995f, -0.189f, 1.373f, -0.566f) curveToRelative(0.377f, -0.377f, 0.566f, -0.835f, 0.566f, -1.373f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.034f, 4.966f) curveToRelative(-1.144f, -1.144f, -2.516f, -1.716f, -4.118f, -1.716f) reflectiveCurveToRelative(-2.974f, 0.572f, -4.118f, 1.716f) curveToRelative(-0.766f, 0.766f, -1.272f, 1.65f, -1.518f, 2.651f) curveToRelative(-0.246f, 1.001f, -0.243f, 1.993f, 0.009f, 2.977f) lineToRelative(-5.748f, 5.748f) curveToRelative(-0.092f, 0.091f, -0.163f, 0.197f, -0.214f, 0.317f) reflectiveCurveToRelative(-0.077f, 0.249f, -0.077f, 0.386f) verticalLineToRelative(2.745f) curveToRelative(0f, 0.137f, 0.026f, 0.26f, 0.077f, 0.369f) reflectiveCurveToRelative(0.123f, 0.209f, 0.214f, 0.3f) curveToRelative(0.091f, 0.091f, 0.192f, 0.163f, 0.3f, 0.214f) reflectiveCurveToRelative(0.232f, 0.077f, 0.369f, 0.077f) horizontalLineToRelative(4.358f) curveToRelative(0.114f, 0f, 0.229f, -0.023f, 0.343f, -0.069f) curveToRelative(0.114f, -0.046f, 0.217f, -0.103f, 0.309f, -0.172f) curveToRelative(0.091f, -0.069f, 0.166f, -0.154f, 0.223f, -0.257f) curveToRelative(0.057f, -0.103f, 0.092f, -0.217f, 0.103f, -0.343f) lineToRelative(0.223f, -1.561f) lineToRelative(1.716f, -0.24f) curveToRelative(0.103f, -0.011f, 0.2f, -0.04f, 0.292f, -0.086f) curveToRelative(0.091f, -0.046f, 0.172f, -0.103f, 0.24f, -0.172f) curveToRelative(0.069f, -0.069f, 0.129f, -0.152f, 0.18f, -0.249f) curveToRelative(0.051f, -0.097f, 0.083f, -0.197f, 0.094f, -0.3f) lineToRelative(0.309f, -1.784f) lineToRelative(0.806f, -0.806f) curveToRelative(0.984f, 0.252f, 1.976f, 0.255f, 2.977f, 0.009f) curveToRelative(1.001f, -0.246f, 1.884f, -0.752f, 2.651f, -1.518f) curveToRelative(1.144f, -1.144f, 1.716f, -2.516f, 1.716f, -4.118f) curveToRelative(0f, -1.601f, -0.572f, -2.974f, -1.716f, -4.118f) close() moveTo(17.666f, 11.828f) curveToRelative(-0.641f, 0.641f, -1.398f, 1.009f, -2.273f, 1.107f) reflectiveCurveToRelative(-1.69f, -0.071f, -2.445f, -0.506f) lineToRelative(-2.145f, 2.145f) lineToRelative(-0.292f, 1.698f) lineToRelative(-2.453f, 0.36f) lineToRelative(-0.275f, 2.162f) horizontalLineToRelative(-2.574f) verticalLineToRelative(-1.373f) lineToRelative(6.365f, -6.365f) curveToRelative(-0.435f, -0.755f, -0.603f, -1.57f, -0.506f, -2.445f) curveToRelative(0.097f, -0.875f, 0.466f, -1.633f, 1.107f, -2.273f) curveToRelative(0.755f, -0.755f, 1.67f, -1.132f, 2.745f, -1.132f) reflectiveCurveToRelative(1.99f, 0.377f, 2.745f, 1.132f) curveToRelative(0.755f, 0.755f, 1.132f, 1.67f, 1.132f, 2.745f) curveToRelative(0f, 1.075f, -0.377f, 1.99f, -1.132f, 2.745f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(17.666f, 11.828f) curveToRelative(-0.641f, 0.641f, -1.398f, 1.009f, -2.273f, 1.107f) reflectiveCurveToRelative(-1.69f, -0.071f, -2.445f, -0.506f) lineToRelative(-2.145f, 2.145f) lineToRelative(-0.292f, 1.699f) lineToRelative(0.009f, 0.009f) lineToRelative(-0.009f, -0.009f) lineToRelative(-2.453f, 0.36f) lineToRelative(-0.275f, 2.162f) horizontalLineToRelative(-2.574f) lineToRelative(0.009f, -0.009f) lineToRelative(-0.009f, 0.009f) verticalLineToRelative(-1.373f) lineToRelative(-0.009f, -0.009f) lineToRelative(0.009f, 0.009f) lineToRelative(6.365f, -6.365f) curveToRelative(-0.435f, -0.755f, -0.603f, -1.57f, -0.506f, -2.445f) reflectiveCurveToRelative(0.466f, -1.633f, 1.107f, -2.273f) curveToRelative(0.755f, -0.755f, 1.67f, -1.132f, 2.745f, -1.132f) reflectiveCurveToRelative(1.99f, 0.377f, 2.745f, 1.132f) reflectiveCurveToRelative(1.132f, 1.67f, 1.132f, 2.745f) reflectiveCurveToRelative(-0.377f, 1.99f, -1.132f, 2.745f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/KeyVertical.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.KeyVertical: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.KeyVertical", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(565f, 365f) quadToRelative(35f, -35f, 35f, -85f) reflectiveQuadToRelative(-35f, -85f) quadToRelative(-35f, -35f, -85f, -35f) reflectiveQuadToRelative(-85f, 35f) quadToRelative(-35f, 35f, -35f, 85f) reflectiveQuadToRelative(35f, 85f) quadToRelative(35f, 35f, 85f, 35f) reflectiveQuadToRelative(85f, -35f) close() moveTo(466f, 902.5f) quadToRelative(-7f, -2.5f, -13f, -7.5f) lineToRelative(-103f, -90f) quadToRelative(-6f, -5f, -9.5f, -11.5f) reflectiveQuadTo(336f, 779f) quadToRelative(-1f, -8f, 1.5f, -15.5f) reflectiveQuadTo(345f, 750f) lineToRelative(55f, -70f) lineToRelative(-52f, -52f) quadToRelative(-6f, -6f, -8.5f, -13f) reflectiveQuadToRelative(-2.5f, -15f) quadToRelative(0f, -8f, 2.5f, -15f) reflectiveQuadToRelative(8.5f, -13f) lineToRelative(52f, -52f) verticalLineToRelative(-14f) quadToRelative(-72f, -25f, -116f, -87f) reflectiveQuadToRelative(-44f, -139f) quadToRelative(0f, -100f, 70f, -170f) reflectiveQuadToRelative(170f, -70f) quadToRelative(100f, 0f, 170f, 70f) reflectiveQuadToRelative(70f, 170f) quadToRelative(0f, 81f, -46f, 141.5f) reflectiveQuadTo(560f, 506f) verticalLineToRelative(318f) quadToRelative(0f, 8f, -3f, 15.5f) reflectiveQuadToRelative(-9f, 13.5f) lineToRelative(-41f, 41f) quadToRelative(-5f, 5f, -11.5f, 8f) reflectiveQuadTo(481f, 905f) quadToRelative(-8f, 0f, -15f, -2.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LabelPercent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.LabelPercent: ImageVector by lazy { Builder( name = "Label Percent", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.0f, 17.0f) horizontalLineTo(5.0f) verticalLineTo(7.0f) horizontalLineTo(16.0f) lineTo(19.55f, 12.0f) moveTo(17.63f, 5.84f) curveTo(17.27f, 5.33f, 16.67f, 5.0f, 16.0f, 5.0f) horizontalLineTo(5.0f) curveTo(3.9f, 5.0f, 3.0f, 5.9f, 3.0f, 7.0f) verticalLineTo(17.0f) curveTo(3.0f, 18.11f, 3.9f, 19.0f, 5.0f, 19.0f) horizontalLineTo(16.0f) curveTo(16.67f, 19.0f, 17.27f, 18.66f, 17.63f, 18.15f) lineTo(22.0f, 12.0f) lineTo(17.63f, 5.84f) moveTo(13.8f, 8.0f) lineTo(15.0f, 9.2f) lineTo(8.2f, 16.0f) lineTo(7.0f, 14.8f) moveTo(8.45f, 8.03f) curveTo(9.23f, 8.03f, 9.87f, 8.67f, 9.87f, 9.45f) reflectiveCurveTo(9.23f, 10.87f, 8.45f, 10.87f) reflectiveCurveTo(7.03f, 10.23f, 7.03f, 9.45f) reflectiveCurveTo(7.67f, 8.03f, 8.45f, 8.03f) moveTo(13.55f, 13.13f) curveTo(14.33f, 13.13f, 14.97f, 13.77f, 14.97f, 14.55f) curveTo(14.97f, 15.33f, 14.33f, 15.97f, 13.55f, 15.97f) curveTo(12.77f, 15.97f, 12.13f, 15.33f, 12.13f, 14.55f) curveTo(12.13f, 13.77f, 12.77f, 13.13f, 13.55f, 13.13f) close() } }.build() } val Icons.Rounded.LabelPercent: ImageVector by lazy { Builder( name = "Label Percent", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.63f, 5.84f) curveTo(17.27f, 5.33f, 16.67f, 5.0f, 16.0f, 5.0f) horizontalLineTo(5.0f) curveTo(3.9f, 5.0f, 3.0f, 5.9f, 3.0f, 7.0f) verticalLineTo(17.0f) curveTo(3.0f, 18.11f, 3.9f, 19.0f, 5.0f, 19.0f) horizontalLineTo(16.0f) curveTo(16.67f, 19.0f, 17.27f, 18.66f, 17.63f, 18.15f) lineTo(22.0f, 12.0f) lineTo(17.63f, 5.84f) moveTo(8.45f, 8.03f) curveTo(9.23f, 8.03f, 9.87f, 8.67f, 9.87f, 9.45f) reflectiveCurveTo(9.23f, 10.87f, 8.45f, 10.87f) reflectiveCurveTo(7.03f, 10.23f, 7.03f, 9.45f) reflectiveCurveTo(7.67f, 8.03f, 8.45f, 8.03f) moveTo(13.55f, 15.97f) curveTo(12.77f, 15.97f, 12.13f, 15.33f, 12.13f, 14.55f) reflectiveCurveTo(12.77f, 13.13f, 13.55f, 13.13f) reflectiveCurveTo(14.97f, 13.77f, 14.97f, 14.55f) reflectiveCurveTo(14.33f, 15.97f, 13.55f, 15.97f) moveTo(8.2f, 16.0f) lineTo(7.0f, 14.8f) lineTo(13.8f, 8.0f) lineTo(15.0f, 9.2f) lineTo(8.2f, 16.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Landscape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Landscape: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Landscape", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(3f, 18f) curveToRelative(-0.417f, 0f, -0.717f, -0.183f, -0.9f, -0.55f) curveToRelative(-0.183f, -0.367f, -0.15f, -0.717f, 0.1f, -1.05f) lineToRelative(4f, -5.325f) curveToRelative(0.1f, -0.133f, 0.221f, -0.233f, 0.363f, -0.3f) reflectiveCurveToRelative(0.287f, -0.1f, 0.438f, -0.1f) reflectiveCurveToRelative(0.296f, 0.033f, 0.438f, 0.1f) reflectiveCurveToRelative(0.262f, 0.167f, 0.363f, 0.3f) lineToRelative(3.7f, 4.925f) horizontalLineToRelative(7.5f) lineToRelative(-5f, -6.65f) lineToRelative(-1.7f, 2.25f) curveToRelative(-0.2f, 0.267f, -0.433f, 0.404f, -0.7f, 0.412f) reflectiveCurveToRelative(-0.5f, -0.063f, -0.7f, -0.213f) reflectiveCurveToRelative(-0.333f, -0.354f, -0.4f, -0.613f) curveToRelative(-0.067f, -0.258f, 0f, -0.521f, 0.2f, -0.788f) lineToRelative(2.5f, -3.325f) curveToRelative(0.1f, -0.133f, 0.221f, -0.233f, 0.363f, -0.3f) reflectiveCurveToRelative(0.287f, -0.1f, 0.438f, -0.1f) reflectiveCurveToRelative(0.296f, 0.033f, 0.438f, 0.1f) reflectiveCurveToRelative(0.262f, 0.167f, 0.363f, 0.3f) lineToRelative(7f, 9.325f) curveToRelative(0.25f, 0.333f, 0.283f, 0.683f, 0.1f, 1.05f) curveToRelative(-0.183f, 0.367f, -0.483f, 0.55f, -0.9f, 0.55f) horizontalLineTo(3f) close() moveTo(11.5f, 16f) horizontalLineToRelative(7.5f) horizontalLineToRelative(-7.8f) horizontalLineToRelative(1.712f) horizontalLineToRelative(-1.413f) close() moveTo(5f, 16f) horizontalLineToRelative(4f) lineToRelative(-2f, -2.675f) lineToRelative(-2f, 2.675f) close() moveTo(5f, 16f) horizontalLineToRelative(4f) horizontalLineToRelative(-4f) close() } }.build() } val Icons.TwoTone.Landscape: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Landscape", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.5f, 16f) lineToRelative(7.5f, 0f) lineToRelative(-7.8f, 0f) lineToRelative(1.713f, 0f) lineToRelative(-1.413f, 0f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5f, 16f) horizontalLineToRelative(4f) horizontalLineToRelative(-4f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 16f) lineToRelative(4f, 0f) lineToRelative(-2f, -2.675f) lineToRelative(-2f, 2.675f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(10.111f, 16.915f) lineToRelative(10.845f, 0f) lineToRelative(0.477f, -0.886f) lineToRelative(-1.691f, -2.249f) lineToRelative(-0.845f, -1.125f) lineToRelative(-2.536f, -3.373f) lineToRelative(0f, -0.005f) lineToRelative(-2.315f, 0f) lineToRelative(-2.536f, 3.373f) lineToRelative(-0.846f, 1.125f) lineToRelative(-1.071f, 1.425f) lineToRelative(0.519f, 0f) lineToRelative(0f, 1.716f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.8f, 16.4f) lineToRelative(-7f, -9.325f) curveToRelative(-0.1f, -0.133f, -0.221f, -0.233f, -0.362f, -0.3f) curveToRelative(-0.142f, -0.067f, -0.287f, -0.1f, -0.438f, -0.1f) reflectiveCurveToRelative(-0.296f, 0.033f, -0.438f, 0.1f) curveToRelative(-0.142f, 0.067f, -0.263f, 0.167f, -0.362f, 0.3f) lineToRelative(-1.898f, 2.524f) lineToRelative(-0.002f, -0.001f) lineToRelative(-1.202f, 1.599f) lineToRelative(1.247f, 1.669f) lineToRelative(0.351f, -0.467f) lineToRelative(0.595f, -0.791f) curveToRelative(0.003f, -0.003f, 0.006f, -0.004f, 0.008f, -0.008f) lineToRelative(1.7f, -2.25f) lineToRelative(5f, 6.65f) horizontalLineToRelative(-7.5f) lineToRelative(-3.7f, -4.925f) curveToRelative(-0.1f, -0.133f, -0.221f, -0.233f, -0.362f, -0.3f) curveToRelative(-0.142f, -0.067f, -0.287f, -0.1f, -0.438f, -0.1f) reflectiveCurveToRelative(-0.296f, 0.033f, -0.438f, 0.1f) curveToRelative(-0.142f, 0.067f, -0.263f, 0.167f, -0.362f, 0.3f) lineToRelative(-4f, 5.325f) curveToRelative(-0.25f, 0.333f, -0.283f, 0.683f, -0.1f, 1.05f) curveToRelative(0.183f, 0.367f, 0.483f, 0.55f, 0.9f, 0.55f) horizontalLineToRelative(18f) curveToRelative(0.417f, 0f, 0.717f, -0.183f, 0.9f, -0.55f) curveToRelative(0.183f, -0.367f, 0.15f, -0.717f, -0.1f, -1.05f) close() moveTo(5f, 16f) lineToRelative(2f, -2.675f) lineToRelative(2f, 2.675f) horizontalLineToRelative(-4f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Landscape2.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Landscape2: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Landscape2", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(46f, 800f) lineTo(184f, 524f) quadTo(194f, 504f, 212.5f, 492f) quadTo(231f, 480f, 254f, 480f) quadTo(278f, 480f, 298f, 492.5f) quadTo(318f, 505f, 327f, 528f) lineTo(354f, 594f) quadTo(356f, 600f, 363f, 599.5f) quadTo(370f, 599f, 372f, 593f) lineTo(458f, 306f) quadTo(472f, 258f, 511.5f, 229f) quadTo(551f, 200f, 601f, 200f) quadTo(650f, 200f, 688.5f, 228.5f) quadTo(727f, 257f, 742f, 303f) lineTo(915f, 800f) lineTo(46f, 800f) close() moveTo(240f, 400f) quadTo(190f, 400f, 155f, 364.5f) quadTo(120f, 329f, 120f, 280f) quadTo(120f, 230f, 155f, 195f) quadTo(190f, 160f, 240f, 160f) quadTo(290f, 160f, 325f, 195f) quadTo(360f, 230f, 360f, 280f) quadTo(360f, 329f, 325f, 364.5f) quadTo(290f, 400f, 240f, 400f) close() } }.build() } val Icons.Outlined.Landscape2: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Landscape2", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(46f, 800f) lineTo(184f, 524f) quadTo(194f, 504f, 212.5f, 492f) quadTo(231f, 480f, 254f, 480f) quadTo(278f, 480f, 298f, 492.5f) quadTo(318f, 505f, 327f, 528f) lineTo(354f, 594f) quadTo(356f, 600f, 363f, 599.5f) quadTo(370f, 599f, 372f, 593f) lineTo(458f, 306f) quadTo(472f, 258f, 511.5f, 229f) quadTo(551f, 200f, 601f, 200f) quadTo(650f, 200f, 688.5f, 228.5f) quadTo(727f, 257f, 742f, 303f) lineTo(915f, 800f) lineTo(830f, 800f) lineTo(666f, 328f) quadTo(658f, 305f, 641f, 292.5f) quadTo(624f, 280f, 601f, 280f) quadTo(578f, 280f, 560.5f, 293f) quadTo(543f, 306f, 535f, 329f) lineTo(449f, 616f) quadTo(440f, 644f, 416.5f, 662f) quadTo(393f, 680f, 363f, 680f) quadTo(336f, 680f, 313f, 665.5f) quadTo(290f, 651f, 280f, 625f) lineTo(253f, 559f) quadTo(253f, 559f, 253f, 559f) quadTo(253f, 559f, 253f, 559f) lineTo(135f, 800f) lineTo(46f, 800f) close() moveTo(240f, 400f) quadTo(190f, 400f, 155f, 364.5f) quadTo(120f, 329f, 120f, 280f) quadTo(120f, 230f, 155f, 195f) quadTo(190f, 160f, 240f, 160f) quadTo(290f, 160f, 325f, 195f) quadTo(360f, 230f, 360f, 280f) quadTo(360f, 329f, 325f, 364.5f) quadTo(290f, 400f, 240f, 400f) close() moveTo(240f, 320f) quadTo(257f, 320f, 268.5f, 308.5f) quadTo(280f, 297f, 280f, 280f) quadTo(280f, 263f, 268.5f, 251.5f) quadTo(257f, 240f, 240f, 240f) quadTo(223f, 240f, 211.5f, 251.5f) quadTo(200f, 263f, 200f, 280f) quadTo(200f, 297f, 211.5f, 308.5f) quadTo(223f, 320f, 240f, 320f) close() moveTo(363f, 680f) lineTo(363f, 680f) quadTo(363f, 680f, 363f, 680f) quadTo(363f, 680f, 363f, 680f) lineTo(363f, 680f) quadTo(363f, 680f, 363f, 680f) quadTo(363f, 680f, 363f, 680f) quadTo(363f, 680f, 363f, 680f) quadTo(363f, 680f, 363f, 680f) lineTo(363f, 680f) quadTo(363f, 680f, 363f, 680f) quadTo(363f, 680f, 363f, 680f) quadTo(363f, 680f, 363f, 680f) quadTo(363f, 680f, 363f, 680f) lineTo(363f, 680f) close() moveTo(240f, 280f) quadTo(240f, 280f, 240f, 280f) quadTo(240f, 280f, 240f, 280f) quadTo(240f, 280f, 240f, 280f) quadTo(240f, 280f, 240f, 280f) quadTo(240f, 280f, 240f, 280f) quadTo(240f, 280f, 240f, 280f) quadTo(240f, 280f, 240f, 280f) quadTo(240f, 280f, 240f, 280f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Lasso.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Lasso: ImageVector by lazy { Builder( name = "Lasso", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 2.0f) curveTo(17.5f, 2.0f, 22.0f, 5.13f, 22.0f, 9.0f) curveTo(22.0f, 12.26f, 18.81f, 15.0f, 14.5f, 15.78f) lineTo(14.5f, 15.5f) curveTo(14.5f, 14.91f, 14.4f, 14.34f, 14.21f, 13.81f) curveTo(17.55f, 13.21f, 20.0f, 11.28f, 20.0f, 9.0f) curveTo(20.0f, 6.24f, 16.42f, 4.0f, 12.0f, 4.0f) curveTo(7.58f, 4.0f, 4.0f, 6.24f, 4.0f, 9.0f) curveTo(4.0f, 10.19f, 4.67f, 11.29f, 5.79f, 12.15f) curveTo(5.35f, 12.64f, 5.0f, 13.21f, 4.78f, 13.85f) curveTo(3.06f, 12.59f, 2.0f, 10.88f, 2.0f, 9.0f) curveTo(2.0f, 5.13f, 6.5f, 2.0f, 12.0f, 2.0f) moveTo(9.5f, 12.0f) curveTo(11.43f, 12.0f, 13.0f, 13.57f, 13.0f, 15.5f) curveTo(13.0f, 17.4f, 11.5f, 18.95f, 9.6f, 19.0f) curveTo(9.39f, 19.36f, 9.18f, 20.0f, 9.83f, 20.68f) curveTo(11.0f, 21.88f, 13.28f, 19.72f, 16.39f, 19.71f) curveTo(18.43f, 19.7f, 20.03f, 19.97f, 20.03f, 19.97f) curveTo(20.03f, 19.97f, 21.08f, 20.1f, 20.97f, 21.04f) curveTo(20.86f, 21.97f, 19.91f, 21.97f, 19.91f, 21.97f) curveTo(19.53f, 21.93f, 18.03f, 21.58f, 16.22f, 21.68f) curveTo(14.41f, 21.77f, 13.47f, 22.41f, 12.56f, 22.69f) curveTo(11.66f, 22.97f, 9.91f, 23.38f, 8.3f, 22.05f) curveTo(6.97f, 20.96f, 7.46f, 19.11f, 7.67f, 18.5f) curveTo(6.67f, 17.87f, 6.0f, 16.76f, 6.0f, 15.5f) curveTo(6.0f, 13.57f, 7.57f, 12.0f, 9.5f, 12.0f) moveTo(9.5f, 14.0f) curveTo(8.67f, 14.0f, 8.0f, 14.67f, 8.0f, 15.5f) curveTo(8.0f, 16.33f, 8.67f, 17.0f, 9.5f, 17.0f) curveTo(10.33f, 17.0f, 11.0f, 16.33f, 11.0f, 15.5f) curveTo(11.0f, 14.67f, 10.33f, 14.0f, 9.5f, 14.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Latitude.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Latitude: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Latitude", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12f, 2f) curveTo(6.5f, 2f, 2f, 6.5f, 2f, 12f) reflectiveCurveTo(6.5f, 22f, 12f, 22f) reflectiveCurveTo(22f, 17.5f, 22f, 12f) reflectiveCurveTo(17.5f, 2f, 12f, 2f) moveTo(12f, 4f) curveTo(15f, 4f, 17.5f, 5.6f, 18.9f, 8f) horizontalLineTo(5.1f) curveTo(6.5f, 5.6f, 9f, 4f, 12f, 4f) moveTo(12f, 20f) curveTo(9f, 20f, 6.5f, 18.4f, 5.1f, 16f) horizontalLineTo(18.9f) curveTo(17.5f, 18.4f, 15f, 20f, 12f, 20f) moveTo(4.3f, 14f) curveTo(4.1f, 13.4f, 4f, 12.7f, 4f, 12f) reflectiveCurveTo(4.1f, 10.6f, 4.3f, 10f) horizontalLineTo(19.8f) curveTo(20f, 10.6f, 20.1f, 11.3f, 20.1f, 12f) reflectiveCurveTo(20f, 13.4f, 19.8f, 14f) horizontalLineTo(4.3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Layers.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Layers: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Layers", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(161f, 594f) quadToRelative(-16f, -12f, -15.5f, -31.5f) reflectiveQuadTo(162f, 531f) quadToRelative(11f, -8f, 24f, -8f) reflectiveQuadToRelative(24f, 8f) lineToRelative(270f, 209f) lineToRelative(270f, -209f) quadToRelative(11f, -8f, 24f, -8f) reflectiveQuadToRelative(24f, 8f) quadToRelative(16f, 12f, 16.5f, 31.5f) reflectiveQuadTo(799f, 594f) lineTo(529f, 804f) quadToRelative(-22f, 17f, -49f, 17f) reflectiveQuadToRelative(-49f, -17f) lineTo(161f, 594f) close() moveTo(431f, 602f) lineTo(201f, 423f) quadToRelative(-31f, -24f, -31f, -63f) reflectiveQuadToRelative(31f, -63f) lineToRelative(230f, -179f) quadToRelative(22f, -17f, 49f, -17f) reflectiveQuadToRelative(49f, 17f) lineToRelative(230f, 179f) quadToRelative(31f, 24f, 31f, 63f) reflectiveQuadToRelative(-31f, 63f) lineTo(529f, 602f) quadToRelative(-22f, 17f, -49f, 17f) reflectiveQuadToRelative(-49f, -17f) close() moveTo(480f, 538f) lineTo(710f, 360f) lineTo(480f, 182f) lineTo(250f, 360f) lineTo(480f, 538f) close() moveTo(480f, 360f) close() } }.build() } val Icons.Rounded.Layers: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Layers", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(161f, 594f) quadToRelative(-16f, -12f, -15.5f, -31.5f) reflectiveQuadTo(162f, 531f) quadToRelative(11f, -8f, 24f, -8f) reflectiveQuadToRelative(24f, 8f) lineToRelative(270f, 209f) lineToRelative(270f, -209f) quadToRelative(11f, -8f, 24f, -8f) reflectiveQuadToRelative(24f, 8f) quadToRelative(16f, 12f, 16.5f, 31.5f) reflectiveQuadTo(799f, 594f) lineTo(529f, 804f) quadToRelative(-22f, 17f, -49f, 17f) reflectiveQuadToRelative(-49f, -17f) lineTo(161f, 594f) close() moveTo(431f, 602f) lineTo(201f, 423f) quadToRelative(-31f, -24f, -31f, -63f) reflectiveQuadToRelative(31f, -63f) lineToRelative(230f, -179f) quadToRelative(22f, -17f, 49f, -17f) reflectiveQuadToRelative(49f, 17f) lineToRelative(230f, 179f) quadToRelative(31f, 24f, 31f, 63f) reflectiveQuadToRelative(-31f, 63f) lineTo(529f, 602f) quadToRelative(-22f, 17f, -49f, 17f) reflectiveQuadToRelative(-49f, -17f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LayersSearchOutline.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.LayersSearchOutline: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.LayersSearchOutline", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19.31f, 18.9f) curveTo(19.75f, 18.21f, 20f, 17.38f, 20f, 16.5f) curveTo(20f, 14f, 18f, 12f, 15.5f, 12f) reflectiveCurveTo(11f, 14f, 11f, 16.5f) reflectiveCurveTo(13f, 21f, 15.5f, 21f) curveTo(16.37f, 21f, 17.19f, 20.75f, 17.88f, 20.32f) lineTo(21f, 23.39f) lineTo(22.39f, 22f) lineTo(19.31f, 18.9f) moveTo(15.5f, 19f) curveTo(14.12f, 19f, 13f, 17.88f, 13f, 16.5f) reflectiveCurveTo(14.12f, 14f, 15.5f, 14f) reflectiveCurveTo(18f, 15.12f, 18f, 16.5f) reflectiveCurveTo(16.88f, 19f, 15.5f, 19f) moveTo(9.59f, 19.2f) lineTo(3f, 14.07f) lineTo(4.62f, 12.81f) lineTo(9f, 16.22f) curveTo(9f, 16.32f, 9f, 16.41f, 9f, 16.5f) curveTo(9f, 17.46f, 9.22f, 18.38f, 9.59f, 19.2f) moveTo(9.5f, 14.04f) lineTo(3f, 9f) lineTo(12f, 2f) lineTo(21f, 9f) lineTo(18.66f, 10.82f) curveTo(17.96f, 10.44f, 17.19f, 10.18f, 16.37f, 10.07f) lineTo(17.74f, 9f) lineTo(12f, 4.53f) lineTo(6.26f, 9f) lineTo(10.53f, 12.32f) curveTo(10.1f, 12.84f, 9.74f, 13.42f, 9.5f, 14.04f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterO.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.LetterO: ImageVector by lazy { Builder( name = "Letter O", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(11.0f, 7.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 9.0f, y1 = 9.0f ) verticalLineTo(15.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 11.0f, y1 = 17.0f ) horizontalLineTo(13.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 15.0f, y1 = 15.0f ) verticalLineTo(9.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 13.0f, y1 = 7.0f ) horizontalLineTo(11.0f) moveTo(11.0f, 9.0f) horizontalLineTo(13.0f) verticalLineTo(15.0f) horizontalLineTo(11.0f) verticalLineTo(9.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LetterS.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.LetterS: ImageVector by lazy { Builder( name = "Letter S", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(11.0f, 7.0f) curveTo(9.9f, 7.0f, 9.0f, 7.9f, 9.0f, 9.0f) verticalLineTo(11.0f) curveTo(9.0f, 12.11f, 9.9f, 13.0f, 11.0f, 13.0f) horizontalLineTo(13.0f) verticalLineTo(15.0f) horizontalLineTo(9.0f) verticalLineTo(17.0f) horizontalLineTo(13.0f) curveTo(14.11f, 17.0f, 15.0f, 16.11f, 15.0f, 15.0f) verticalLineTo(13.0f) curveTo(15.0f, 11.9f, 14.11f, 11.0f, 13.0f, 11.0f) horizontalLineTo(11.0f) verticalLineTo(9.0f) horizontalLineTo(15.0f) verticalLineTo(7.0f) horizontalLineTo(11.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/License.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.License: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.License", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(480f, 520f) quadTo(430f, 520f, 395f, 485f) quadTo(360f, 450f, 360f, 400f) quadTo(360f, 350f, 395f, 315f) quadTo(430f, 280f, 480f, 280f) quadTo(530f, 280f, 565f, 315f) quadTo(600f, 350f, 600f, 400f) quadTo(600f, 450f, 565f, 485f) quadTo(530f, 520f, 480f, 520f) close() moveTo(240f, 920f) lineTo(240f, 611f) quadTo(202f, 569f, 181f, 515f) quadTo(160f, 461f, 160f, 400f) quadTo(160f, 266f, 253f, 173f) quadTo(346f, 80f, 480f, 80f) quadTo(614f, 80f, 707f, 173f) quadTo(800f, 266f, 800f, 400f) quadTo(800f, 461f, 779f, 515f) quadTo(758f, 569f, 720f, 611f) lineTo(720f, 920f) lineTo(480f, 840f) lineTo(240f, 920f) close() moveTo(480f, 640f) quadTo(580f, 640f, 650f, 570f) quadTo(720f, 500f, 720f, 400f) quadTo(720f, 300f, 650f, 230f) quadTo(580f, 160f, 480f, 160f) quadTo(380f, 160f, 310f, 230f) quadTo(240f, 300f, 240f, 400f) quadTo(240f, 500f, 310f, 570f) quadTo(380f, 640f, 480f, 640f) close() moveTo(320f, 801f) lineTo(480f, 760f) lineTo(640f, 801f) lineTo(640f, 677f) quadTo(605f, 697f, 564.5f, 708.5f) quadTo(524f, 720f, 480f, 720f) quadTo(436f, 720f, 395.5f, 708.5f) quadTo(355f, 697f, 320f, 677f) lineTo(320f, 801f) close() moveTo(480f, 739f) lineTo(480f, 739f) quadTo(480f, 739f, 480f, 739f) quadTo(480f, 739f, 480f, 739f) quadTo(480f, 739f, 480f, 739f) quadTo(480f, 739f, 480f, 739f) lineTo(480f, 739f) lineTo(480f, 739f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Line.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Line: ImageVector by lazy { Builder( name = "Line", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(212.0f, 748.0f) quadToRelative(-11.0f, -11.0f, -11.0f, -28.0f) reflectiveQuadToRelative(11.0f, -28.0f) lineToRelative(480.0f, -480.0f) quadToRelative(11.0f, -12.0f, 27.5f, -12.0f) reflectiveQuadToRelative(28.5f, 12.0f) quadToRelative(11.0f, 11.0f, 11.0f, 28.0f) reflectiveQuadToRelative(-11.0f, 28.0f) lineTo(268.0f, 748.0f) quadToRelative(-11.0f, 11.0f, -28.0f, 11.0f) reflectiveQuadToRelative(-28.0f, -11.0f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineArrow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.LineArrow: ImageVector by lazy { Builder( name = "LineArrow", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(680.0f, 336.0f) lineTo(244.0f, 772.0f) quadToRelative(-11.0f, 11.0f, -28.0f, 11.0f) reflectiveQuadToRelative(-28.0f, -11.0f) quadToRelative(-11.0f, -11.0f, -11.0f, -28.0f) reflectiveQuadToRelative(11.0f, -28.0f) lineToRelative(436.0f, -436.0f) lineTo(400.0f, 280.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(360.0f, 240.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(400.0f, 200.0f) horizontalLineToRelative(320.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(760.0f, 240.0f) verticalLineToRelative(320.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(720.0f, 600.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(680.0f, 560.0f) verticalLineToRelative(-224.0f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/LineDoubleArrow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.LineDoubleArrow: ImageVector by lazy { Builder( name = "LineDoubleArrow", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(160.0f, 840.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(120.0f, 800.0f) verticalLineToRelative(-240.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(160.0f, 520.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(200.0f, 560.0f) verticalLineToRelative(144.0f) lineToRelative(504.0f, -504.0f) lineTo(560.0f, 200.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(520.0f, 160.0f) quadToRelative(0.0f, -17.0f, 11.5f, -28.5f) reflectiveQuadTo(560.0f, 120.0f) horizontalLineToRelative(240.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(840.0f, 160.0f) verticalLineToRelative(240.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(800.0f, 440.0f) quadToRelative(-17.0f, 0.0f, -28.5f, -11.5f) reflectiveQuadTo(760.0f, 400.0f) verticalLineToRelative(-144.0f) lineTo(256.0f, 760.0f) horizontalLineToRelative(144.0f) quadToRelative(17.0f, 0.0f, 28.5f, 11.5f) reflectiveQuadTo(440.0f, 800.0f) quadToRelative(0.0f, 17.0f, -11.5f, 28.5f) reflectiveQuadTo(400.0f, 840.0f) lineTo(160.0f, 840.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Longitude.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Longitude: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Longitude", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12f, 2f) arcTo(10f, 10f, 0f, isMoreThanHalf = true, isPositiveArc = false, 22f, 12f) arcTo(10.03f, 10.03f, 0f, isMoreThanHalf = false, isPositiveArc = false, 12f, 2f) moveTo(9.4f, 19.6f) arcTo(8.05f, 8.05f, 0f, isMoreThanHalf = false, isPositiveArc = true, 9.4f, 4.4f) arcTo(16.45f, 16.45f, 0f, isMoreThanHalf = false, isPositiveArc = false, 7.5f, 12f) arcTo(16.45f, 16.45f, 0f, isMoreThanHalf = false, isPositiveArc = false, 9.4f, 19.6f) moveTo(12f, 20f) arcTo(13.81f, 13.81f, 0f, isMoreThanHalf = false, isPositiveArc = true, 9.5f, 12f) arcTo(13.81f, 13.81f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 4f) arcTo(13.81f, 13.81f, 0f, isMoreThanHalf = false, isPositiveArc = true, 14.5f, 12f) arcTo(13.81f, 13.81f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12f, 20f) moveTo(14.6f, 19.6f) arcTo(16.15f, 16.15f, 0f, isMoreThanHalf = false, isPositiveArc = false, 14.6f, 4.4f) arcTo(8.03f, 8.03f, 0f, isMoreThanHalf = false, isPositiveArc = true, 20f, 12f) arcTo(7.9f, 7.9f, 0f, isMoreThanHalf = false, isPositiveArc = true, 14.6f, 19.6f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Manga.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Manga: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Manga", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(160f, 800f) quadTo(127f, 800f, 103.5f, 776.5f) quadTo(80f, 753f, 80f, 720f) lineTo(80f, 240f) quadTo(80f, 207f, 103.5f, 183.5f) quadTo(127f, 160f, 160f, 160f) lineTo(800f, 160f) quadTo(833f, 160f, 856.5f, 183.5f) quadTo(880f, 207f, 880f, 240f) lineTo(880f, 720f) quadTo(880f, 753f, 856.5f, 776.5f) quadTo(833f, 800f, 800f, 800f) lineTo(160f, 800f) close() moveTo(423f, 720f) lineTo(800f, 720f) quadTo(800f, 720f, 800f, 720f) quadTo(800f, 720f, 800f, 720f) lineTo(800f, 411f) lineTo(773f, 374f) lineTo(680f, 404f) lineTo(588f, 374f) lineTo(530f, 453f) lineTo(437f, 483f) lineTo(437f, 581f) lineTo(380f, 660f) lineTo(423f, 720f) close() moveTo(324f, 720f) lineTo(281f, 660f) lineTo(357f, 555f) lineTo(357f, 425f) lineTo(480f, 385f) lineTo(557f, 280f) lineTo(680f, 320f) lineTo(800f, 281f) lineTo(800f, 240f) quadTo(800f, 240f, 800f, 240f) quadTo(800f, 240f, 800f, 240f) lineTo(160f, 240f) quadTo(160f, 240f, 160f, 240f) quadTo(160f, 240f, 160f, 240f) lineTo(160f, 720f) quadTo(160f, 720f, 160f, 720f) quadTo(160f, 720f, 160f, 720f) lineTo(324f, 720f) close() moveTo(437f, 483f) lineTo(437f, 483f) quadTo(437f, 483f, 437f, 483f) quadTo(437f, 483f, 437f, 483f) lineTo(437f, 483f) quadTo(437f, 483f, 437f, 483f) quadTo(437f, 483f, 437f, 483f) lineTo(437f, 483f) quadTo(437f, 483f, 437f, 483f) quadTo(437f, 483f, 437f, 483f) lineTo(437f, 483f) lineTo(437f, 483f) lineTo(437f, 483f) lineTo(437f, 483f) lineTo(437f, 483f) lineTo(437f, 483f) lineTo(437f, 483f) close() } }.build() } val Icons.Rounded.Manga: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Manga", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(160f, 800f) quadTo(127f, 800f, 103.5f, 776.5f) quadTo(80f, 753f, 80f, 720f) lineTo(80f, 240f) quadTo(80f, 207f, 103.5f, 183.5f) quadTo(127f, 160f, 160f, 160f) lineTo(800f, 160f) quadTo(833f, 160f, 856.5f, 183.5f) quadTo(880f, 207f, 880f, 240f) lineTo(880f, 720f) quadTo(880f, 753f, 856.5f, 776.5f) quadTo(833f, 800f, 800f, 800f) lineTo(160f, 800f) close() moveTo(324f, 720f) lineTo(800f, 720f) quadTo(800f, 720f, 800f, 720f) quadTo(800f, 720f, 800f, 720f) lineTo(800f, 281f) lineTo(800f, 281f) lineTo(680f, 320f) lineTo(557f, 280f) lineTo(480f, 385f) lineTo(357f, 425f) lineTo(357f, 555f) lineTo(281f, 660f) lineTo(324f, 720f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MaterialDesign.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MaterialDesign: ImageVector by lazy { ImageVector.Builder( name = "MaterialDesign", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(21f, 12f) curveTo(21f, 9.97f, 20.33f, 8.09f, 19f, 6.38f) verticalLineTo(17.63f) curveTo(20.33f, 15.97f, 21f, 14.09f, 21f, 12f) moveTo(17.63f, 19f) horizontalLineTo(6.38f) curveTo(7.06f, 19.55f, 7.95f, 20f, 9.05f, 20.41f) curveTo(10.14f, 20.8f, 11.13f, 21f, 12f, 21f) curveTo(12.88f, 21f, 13.86f, 20.8f, 14.95f, 20.41f) curveTo(16.05f, 20f, 16.94f, 19.55f, 17.63f, 19f) moveTo(11f, 17f) lineTo(7f, 9f) verticalLineTo(17f) horizontalLineTo(11f) moveTo(17f, 9f) lineTo(13f, 17f) horizontalLineTo(17f) verticalLineTo(9f) moveTo(12f, 14.53f) lineTo(15.75f, 7f) horizontalLineTo(8.25f) lineTo(12f, 14.53f) moveTo(17.63f, 5f) curveTo(15.97f, 3.67f, 14.09f, 3f, 12f, 3f) curveTo(9.91f, 3f, 8.03f, 3.67f, 6.38f, 5f) horizontalLineTo(17.63f) moveTo(5f, 17.63f) verticalLineTo(6.38f) curveTo(3.67f, 8.09f, 3f, 9.97f, 3f, 12f) curveTo(3f, 14.09f, 3.67f, 15.97f, 5f, 17.63f) moveTo(23f, 12f) curveTo(23f, 15.03f, 21.94f, 17.63f, 19.78f, 19.78f) curveTo(17.63f, 21.94f, 15.03f, 23f, 12f, 23f) curveTo(8.97f, 23f, 6.38f, 21.94f, 4.22f, 19.78f) curveTo(2.06f, 17.63f, 1f, 15.03f, 1f, 12f) curveTo(1f, 8.97f, 2.06f, 6.38f, 4.22f, 4.22f) curveTo(6.38f, 2.06f, 8.97f, 1f, 12f, 1f) curveTo(15.03f, 1f, 17.63f, 2.06f, 19.78f, 4.22f) curveTo(21.94f, 6.38f, 23f, 8.97f, 23f, 12f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshDownload.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MeshDownload: ImageVector by lazy { ImageVector.Builder( name = "MeshDownloadOutlined", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(15f, 20f) curveToRelative(0f, -0.552f, -0.448f, -1f, -1f, -1f) horizontalLineToRelative(-1f) verticalLineToRelative(-2f) horizontalLineToRelative(4f) curveToRelative(1.105f, 0f, 2f, -0.895f, 2f, -2f) verticalLineTo(5f) curveToRelative(0f, -1.105f, -0.895f, -2f, -2f, -2f) horizontalLineTo(7f) curveTo(5.895f, 3f, 5f, 3.895f, 5f, 5f) verticalLineToRelative(10f) curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) horizontalLineToRelative(4f) verticalLineToRelative(2f) horizontalLineToRelative(-1f) curveToRelative(-0.552f, 0f, -1f, 0.448f, -1f, 1f) horizontalLineTo(2f) verticalLineToRelative(2f) horizontalLineToRelative(7f) curveToRelative(0f, 0.552f, 0.448f, 1f, 1f, 1f) horizontalLineToRelative(4f) curveToRelative(0.552f, 0f, 1f, -0.448f, 1f, -1f) horizontalLineToRelative(7f) verticalLineToRelative(-2f) horizontalLineTo(15f) close() moveTo(17f, 15f) horizontalLineToRelative(-1.518f) curveToRelative(-0.017f, -0.055f, -0.533f, -1.283f, -0.562f, -1.348f) curveToRelative(0.703f, -0.081f, 1.37f, -0.296f, 1.994f, -0.631f) lineToRelative(0f, -0f) curveTo(16.934f, 13.01f, 16.967f, 12.996f, 17f, 12.982f) verticalLineTo(15f) close() moveTo(17f, 5f) verticalLineToRelative(1.59f) lineToRelative(-0.004f, 0.001f) curveTo(16.94f, 6.609f, 16.5f, 6.784f, 16.5f, 6.784f) curveToRelative(-0.138f, 0.055f, -0.275f, 0.098f, -0.414f, 0.17f) curveToRelative(-0.139f, 0.072f, -0.273f, 0.138f, -0.405f, 0.196f) curveToRelative(-0.087f, -0.745f, -0.324f, -1.445f, -0.674f, -2.095f) curveTo(15.001f, 5.044f, 14.992f, 5.02f, 14.984f, 5f) horizontalLineTo(17f) close() moveTo(14.982f, 10.612f) curveToRelative(0.224f, -0.445f, 0.454f, -0.928f, 0.585f, -1.478f) curveTo(16.104f, 9.003f, 16.569f, 8.783f, 17f, 8.574f) verticalLineToRelative(2.413f) lineToRelative(-0.169f, 0.06f) curveToRelative(-0.214f, 0.077f, -0.446f, 0.16f, -0.685f, 0.282f) curveToRelative(-0.406f, 0.208f, -0.776f, 0.379f, -1.146f, 0.442f) curveToRelative(-0.112f, 0.019f, -0.226f, 0.021f, -0.339f, 0.032f) curveTo(14.699f, 11.389f, 14.79f, 10.992f, 14.982f, 10.612f) close() moveTo(13.002f, 5f) lineToRelative(0.032f, 0.098f) curveToRelative(0.007f, 0.023f, 0.011f, 0.041f, 0.019f, 0.064f) lineTo(13.169f, 5.5f) lineToRelative(0.126f, 0.277f) lineToRelative(0.002f, -0.004f) curveToRelative(0.008f, 0.017f, 0.012f, 0.034f, 0.02f, 0.051f) lineToRelative(0f, 0f) curveToRelative(0.202f, 0.404f, 0.382f, 0.761f, 0.458f, 1.113f) curveToRelative(0.032f, 0.146f, 0.034f, 0.302f, 0.049f, 0.453f) curveToRelative(-0.421f, -0.039f, -0.825f, -0.136f, -1.215f, -0.335f) curveToRelative(-0.433f, -0.22f, -0.911f, -0.429f, -1.443f, -0.557f) curveTo(11.032f, 5.937f, 10.796f, 5.45f, 10.575f, 5f) horizontalLineTo(13.002f) close() moveTo(10.695f, 10.376f) curveToRelative(0.311f, -0.596f, 0.508f, -1.233f, 0.582f, -1.899f) curveToRelative(0.19f, 0.084f, 0.387f, 0.18f, 0.597f, 0.285f) curveToRelative(0.544f, 0.268f, 1.116f, 0.424f, 1.701f, 0.489f) curveToRelative(-0.134f, 0.29f, -0.276f, 0.583f, -0.412f, 0.89f) curveToRelative(-0.205f, 0.46f, -0.312f, 0.933f, -0.362f, 1.404f) curveToRelative(-0.22f, -0.098f, -0.448f, -0.209f, -0.702f, -0.33f) lineToRelative(0f, 0f) curveToRelative(-0.516f, -0.243f, -1.053f, -0.38f, -1.599f, -0.439f) curveTo(10.558f, 10.645f, 10.624f, 10.512f, 10.695f, 10.376f) close() moveTo(7f, 5f) horizontalLineToRelative(1.592f) lineToRelative(0.197f, 0.5f) curveToRelative(0.069f, 0.173f, 0.128f, 0.347f, 0.218f, 0.519f) curveToRelative(0.062f, 0.119f, 0.117f, 0.236f, 0.168f, 0.352f) curveTo(8.494f, 6.448f, 7.842f, 6.648f, 7.232f, 6.974f) horizontalLineToRelative(-0f) curveTo(7.168f, 7.008f, 7.079f, 7.043f, 7f, 7.076f) verticalLineTo(5f) close() moveTo(7f, 15f) verticalLineToRelative(-1.52f) lineToRelative(0.159f, -0.054f) curveToRelative(0.315f, -0.106f, 0.619f, -0.238f, 0.912f, -0.391f) horizontalLineToRelative(-0f) curveToRelative(0.11f, -0.057f, 0.22f, -0.089f, 0.329f, -0.137f) curveToRelative(0.078f, 0.674f, 0.279f, 1.32f, 0.601f, 1.924f) verticalLineToRelative(0f) curveTo(9.026f, 14.867f, 9.055f, 14.941f, 9.08f, 15f) horizontalLineTo(7f) close() moveTo(9.114f, 9.383f) curveToRelative(-0.225f, 0.452f, -0.465f, 0.944f, -0.6f, 1.509f) curveTo(7.943f, 11.03f, 7.453f, 11.273f, 7f, 11.492f) verticalLineTo(9.067f) lineToRelative(0.159f, -0.053f) curveToRelative(0.228f, -0.077f, 0.47f, -0.164f, 0.714f, -0.286f) lineToRelative(0f, -0f) curveToRelative(0.396f, -0.199f, 0.75f, -0.376f, 1.099f, -0.45f) horizontalLineToRelative(-0f) curveToRelative(0.147f, -0.032f, 0.299f, -0.033f, 0.449f, -0.047f) curveTo(9.384f, 8.631f, 9.297f, 9.015f, 9.114f, 9.383f) close() moveTo(11.018f, 14.835f) curveToRelative(-0.08f, -0.228f, -0.169f, -0.47f, -0.293f, -0.715f) lineToRelative(0f, 0f) curveToRelative(-0.183f, -0.36f, -0.341f, -0.688f, -0.414f, -1.013f) curveToRelative(-0.035f, -0.152f, -0.037f, -0.313f, -0.053f, -0.471f) curveToRelative(0.426f, 0.04f, 0.835f, 0.139f, 1.233f, 0.342f) horizontalLineToRelative(0f) curveToRelative(0.352f, 0.179f, 0.733f, 0.374f, 1.174f, 0.499f) curveToRelative(0.09f, 0.025f, 0.176f, 0.044f, 0.263f, 0.065f) curveToRelative(0.117f, 0.486f, 0.3f, 0.933f, 0.51f, 1.342f) verticalLineToRelative(0f) curveTo(13.454f, 14.91f, 13.474f, 14.964f, 13.49f, 15f) horizontalLineToRelative(-2.413f) lineTo(11.018f, 14.835f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MeshGradient.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MeshGradient: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.MeshGradient", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19f, 3f) horizontalLineTo(5f) curveToRelative(-1.11f, 0f, -2f, 0.89f, -2f, 2f) verticalLineToRelative(14f) curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) horizontalLineToRelative(14f) curveToRelative(1.105f, 0f, 2f, -0.895f, 2f, -2f) verticalLineTo(5f) curveToRelative(0f, -1.11f, -0.9f, -2f, -2f, -2f) close() moveTo(19f, 13.086f) curveToRelative(-1.067f, 0.87f, -2.173f, 1.365f, -3.294f, 1.453f) curveToRelative(-0.259f, 0.022f, -0.499f, 0.012f, -0.734f, -0.007f) curveToRelative(0.073f, -0.827f, 0.363f, -1.543f, 0.698f, -2.34f) curveToRelative(0.2f, -0.476f, 0.41f, -0.983f, 0.576f, -1.535f) curveToRelative(0.943f, -0.129f, 1.863f, -0.464f, 2.754f, -0.974f) verticalLineToRelative(3.403f) close() moveTo(5f, 11.005f) curveToRelative(1.239f, -1.126f, 2.488f, -1.75f, 3.717f, -1.851f) curveToRelative(-0.087f, 0.799f, -0.38f, 1.492f, -0.703f, 2.264f) curveToRelative(-0.226f, 0.538f, -0.46f, 1.118f, -0.633f, 1.757f) curveToRelative(-0.805f, 0.215f, -1.6f, 0.602f, -2.381f, 1.121f) verticalLineToRelative(-3.29f) close() moveTo(14.54f, 8.294f) curveToRelative(0.011f, 0.128f, -0.004f, 0.242f, -0.003f, 0.364f) curveToRelative(-0.67f, -0.121f, -1.286f, -0.364f, -1.954f, -0.644f) curveToRelative(-0.603f, -0.254f, -1.262f, -0.514f, -1.996f, -0.687f) curveToRelative(-0.159f, -0.795f, -0.468f, -1.571f, -0.9f, -2.327f) horizontalLineToRelative(3.4f) curveToRelative(0.87f, 1.067f, 1.365f, 2.173f, 1.453f, 3.294f) close() moveTo(14.144f, 10.634f) curveToRelative(-0.098f, 0.257f, -0.205f, 0.516f, -0.318f, 0.784f) curveToRelative(-0.32f, 0.762f, -0.665f, 1.599f, -0.808f, 2.586f) curveToRelative(-0.145f, -0.059f, -0.287f, -0.115f, -0.436f, -0.178f) curveToRelative(-0.875f, -0.368f, -1.855f, -0.756f, -3.043f, -0.844f) curveToRelative(0.098f, -0.258f, 0.205f, -0.519f, 0.319f, -0.789f) curveToRelative(0.34f, -0.807f, 0.71f, -1.696f, 0.838f, -2.762f) curveToRelative(0.365f, 0.123f, 0.729f, 0.266f, 1.112f, 0.427f) curveToRelative(0.698f, 0.294f, 1.455f, 0.613f, 2.337f, 0.776f) close() moveTo(9.145f, 15.232f) curveToRelative(-0.007f, -0.094f, 0.007f, -0.177f, 0.006f, -0.267f) curveToRelative(0.964f, 0.017f, 1.762f, 0.329f, 2.657f, 0.705f) curveToRelative(0.398f, 0.168f, 0.817f, 0.342f, 1.266f, 0.493f) curveToRelative(0.187f, 0.961f, 0.61f, 1.909f, 1.226f, 2.837f) horizontalLineToRelative(-3.294f) curveToRelative(-1.142f, -1.256f, -1.772f, -2.523f, -1.86f, -3.768f) close() moveTo(19f, 7.277f) curveToRelative(-0.798f, 0.65f, -1.618f, 1.091f, -2.45f, 1.306f) curveToRelative(-0.002f, -0.147f, -0.003f, -0.294f, -0.016f, -0.449f) curveToRelative(-0.086f, -1.077f, -0.445f, -2.124f, -1.031f, -3.135f) horizontalLineToRelative(3.497f) verticalLineToRelative(2.277f) close() moveTo(7.278f, 5f) curveToRelative(0.575f, 0.707f, 0.974f, 1.432f, 1.209f, 2.167f) curveToRelative(-1.183f, 0.109f, -2.35f, 0.55f, -3.487f, 1.304f) verticalLineToRelative(-3.472f) horizontalLineToRelative(2.278f) close() moveTo(5f, 16.815f) curveToRelative(0.716f, -0.65f, 1.435f, -1.116f, 2.152f, -1.427f) curveToRelative(0.089f, 1.226f, 0.536f, 2.435f, 1.318f, 3.612f) horizontalLineToRelative(-3.47f) verticalLineToRelative(-2.185f) close() moveTo(16.815f, 19f) curveToRelative(-0.739f, -0.813f, -1.242f, -1.631f, -1.542f, -2.445f) curveToRelative(0.192f, 0.002f, 0.389f, -0.004f, 0.592f, -0.021f) curveToRelative(1.077f, -0.086f, 2.124f, -0.445f, 3.135f, -1.031f) verticalLineToRelative(3.497f) horizontalLineToRelative(-2.185f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEdit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.MiniEdit: ImageVector by lazy { ImageVector.Builder( name = "Mini Edit", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(18.7912f, 7.9369f) curveToRelative(-0.1161f, -0.2838f, -0.2774f, -0.5418f, -0.4837f, -0.7739f) lineToRelative(-1.4317f, -1.4318f) curveToRelative(-0.2322f, -0.2321f, -0.4902f, -0.4062f, -0.7739f, -0.5223f) curveToRelative(-0.2838f, -0.1161f, -0.5804f, -0.1741f, -0.89f, -0.1741f) curveToRelative(-0.2838f, 0.0f, -0.5676f, 0.0516f, -0.8513f, 0.1547f) curveToRelative(-0.2838f, 0.1032f, -0.5418f, 0.2709f, -0.7739f, 0.5031f) lineToRelative(-8.5519f, 8.5132f) verticalLineToRelative(4.7596f) horizontalLineToRelative(4.7596f) lineToRelative(8.5132f, -8.5132f) curveToRelative(0.2321f, -0.2322f, 0.3998f, -0.4966f, 0.5031f, -0.7933f) curveToRelative(0.1032f, -0.2966f, 0.1547f, -0.5869f, 0.1547f, -0.8707f) curveTo(18.9653f, 8.5044f, 18.9073f, 8.2206f, 18.7912f, 7.9369f) close() moveTo(8.8269f, 16.6435f) horizontalLineTo(7.3565f) verticalLineToRelative(-1.4705f) lineToRelative(4.7209f, -4.6823f) lineToRelative(0.7353f, 0.6965f) lineToRelative(0.6965f, 0.7352f) lineTo(8.8269f, 16.6435f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MiniEditLarge.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.MiniEditLarge: ImageVector by lazy { ImageVector.Builder( name = "MiniEditLarge", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(20.775f, 6.75f) curveToRelative(-0.15f, -0.367f, -0.358f, -0.7f, -0.625f, -1f) lineToRelative(-1.85f, -1.85f) curveTo(18f, 3.6f, 17.667f, 3.375f, 17.3f, 3.225f) curveTo(16.933f, 3.075f, 16.55f, 3f, 16.15f, 3f) curveToRelative(-0.183f, 0f, -0.367f, 0.017f, -0.55f, 0.05f) curveToRelative(-0.183f, 0.033f, -0.367f, 0.083f, -0.55f, 0.15f) curveToRelative(-0.367f, 0.133f, -0.7f, 0.35f, -1f, 0.65f) lineToRelative(-0.461f, 0.458f) lineTo(3.6f, 14.25f) curveTo(3.4f, 14.45f, 3.25f, 14.675f, 3.15f, 14.925f) reflectiveCurveTo(3f, 15.433f, 3f, 15.7f) verticalLineTo(19f) curveToRelative(0f, 0.567f, 0.192f, 1.042f, 0.575f, 1.425f) curveTo(3.958f, 20.808f, 4.433f, 21f, 5f, 21f) horizontalLineToRelative(3.3f) curveToRelative(0.267f, 0f, 0.525f, -0.05f, 0.775f, -0.15f) reflectiveCurveToRelative(0.475f, -0.25f, 0.675f, -0.45f) lineToRelative(2.002f, -2.002f) lineTo(20.15f, 10f) curveToRelative(0.15f, -0.15f, 0.279f, -0.31f, 0.387f, -0.481f) reflectiveCurveToRelative(0.196f, -0.352f, 0.263f, -0.544f) curveTo(20.867f, 8.783f, 20.917f, 8.594f, 20.95f, 8.406f) reflectiveCurveTo(21f, 8.033f, 21f, 7.85f) curveTo(21f, 7.483f, 20.925f, 7.117f, 20.775f, 6.75f) close() moveTo(16.15f, 6f) lineTo(18f, 7.85f) lineToRelative(-1.85f, 1.95f) lineTo(14.25f, 7.9f) lineTo(16.15f, 6f) close() } }.build() } val Icons.Outlined.MiniEditLarge: ImageVector by lazy { ImageVector.Builder( name = "MiniEditLarge Outlined", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(20.775f, 6.75f) curveToRelative(-0.15f, -0.367f, -0.358f, -0.7f, -0.625f, -1f) lineToRelative(-1.85f, -1.85f) curveTo(18f, 3.6f, 17.667f, 3.375f, 17.3f, 3.225f) curveTo(16.933f, 3.075f, 16.55f, 3f, 16.15f, 3f) curveToRelative(-0.367f, 0f, -0.733f, 0.067f, -1.1f, 0.2f) curveToRelative(-0.367f, 0.133f, -0.7f, 0.35f, -1f, 0.65f) lineTo(3.6f, 14.25f) curveTo(3.4f, 14.45f, 3.25f, 14.675f, 3.15f, 14.925f) reflectiveCurveTo(3f, 15.433f, 3f, 15.7f) verticalLineTo(19f) curveToRelative(0f, 0.567f, 0.192f, 1.042f, 0.575f, 1.425f) curveTo(3.958f, 20.808f, 4.433f, 21f, 5f, 21f) horizontalLineToRelative(3.3f) curveToRelative(0.267f, 0f, 0.525f, -0.05f, 0.775f, -0.15f) reflectiveCurveToRelative(0.475f, -0.25f, 0.675f, -0.45f) lineTo(20.15f, 10f) curveToRelative(0.3f, -0.3f, 0.517f, -0.642f, 0.65f, -1.025f) curveTo(20.933f, 8.592f, 21f, 8.217f, 21f, 7.85f) reflectiveCurveTo(20.925f, 7.117f, 20.775f, 6.75f) close() moveTo(7.9f, 18f) horizontalLineTo(6f) verticalLineToRelative(-1.9f) lineToRelative(6.1f, -6.05f) lineToRelative(0.95f, 0.9f) lineToRelative(0.9f, 0.95f) lineTo(7.9f, 18f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mobile.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Mobile: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Mobile", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() moveTo(280f, 840f) lineTo(680f, 840f) quadTo(680f, 840f, 680f, 840f) quadTo(680f, 840f, 680f, 840f) lineTo(680f, 120f) quadTo(680f, 120f, 680f, 120f) quadTo(680f, 120f, 680f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() moveTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() moveTo(480f, 240f) quadTo(497f, 240f, 508.5f, 228.5f) quadTo(520f, 217f, 520f, 200f) quadTo(520f, 183f, 508.5f, 171.5f) quadTo(497f, 160f, 480f, 160f) quadTo(463f, 160f, 451.5f, 171.5f) quadTo(440f, 183f, 440f, 200f) quadTo(440f, 217f, 451.5f, 228.5f) quadTo(463f, 240f, 480f, 240f) close() } }.build() } val Icons.Rounded.Mobile: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Mobile", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() moveTo(480f, 240f) quadTo(497f, 240f, 508.5f, 228.5f) quadTo(520f, 217f, 520f, 200f) quadTo(520f, 183f, 508.5f, 171.5f) quadTo(497f, 160f, 480f, 160f) quadTo(463f, 160f, 451.5f, 171.5f) quadTo(440f, 183f, 440f, 200f) quadTo(440f, 217f, 451.5f, 228.5f) quadTo(463f, 240f, 480f, 240f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowDown.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MobileArrowDown: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.MobileArrowDown", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() moveTo(280f, 840f) lineTo(680f, 840f) quadTo(680f, 840f, 680f, 840f) quadTo(680f, 840f, 680f, 840f) lineTo(680f, 120f) quadTo(680f, 120f, 680f, 120f) quadTo(680f, 120f, 680f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() moveTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() moveTo(480f, 640f) lineTo(640f, 480f) lineTo(584f, 424f) lineTo(520f, 486f) lineTo(520f, 320f) lineTo(440f, 320f) lineTo(440f, 486f) lineTo(376f, 424f) lineTo(320f, 480f) lineTo(480f, 640f) close() } }.build() } val Icons.Rounded.MobileArrowDown: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.MobileArrowDown", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() moveTo(480f, 640f) lineTo(640f, 480f) lineTo(584f, 424f) lineTo(520f, 486f) lineTo(520f, 320f) lineTo(440f, 320f) lineTo(440f, 486f) lineTo(376f, 424f) lineTo(320f, 480f) lineTo(480f, 640f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileArrowUpRight.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MobileArrowUpRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.MobileArrowUpRight", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f, autoMirror = true ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() moveTo(280f, 840f) lineTo(680f, 840f) quadTo(680f, 840f, 680f, 840f) quadTo(680f, 840f, 680f, 840f) lineTo(680f, 120f) quadTo(680f, 120f, 680f, 120f) quadTo(680f, 120f, 680f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() moveTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() moveTo(396f, 620f) lineTo(520f, 496f) lineTo(520f, 600f) lineTo(600f, 600f) lineTo(600f, 360f) lineTo(360f, 360f) lineTo(360f, 440f) lineTo(464f, 440f) lineTo(340f, 564f) lineTo(396f, 620f) close() } }.build() } val Icons.Rounded.MobileArrowUpRight: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.MobileArrowUpRight", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f, autoMirror = true ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() moveTo(396f, 620f) lineTo(520f, 496f) lineTo(520f, 600f) lineTo(600f, 600f) lineTo(600f, 360f) lineTo(360f, 360f) lineTo(360f, 440f) lineTo(464f, 440f) lineTo(340f, 564f) lineTo(396f, 620f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileCast.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.MobileCast: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.MobileCast", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(200f, 920f) lineTo(200f, 840f) quadTo(233f, 840f, 256.5f, 863.5f) quadTo(280f, 887f, 280f, 920f) lineTo(200f, 920f) close() moveTo(360f, 920f) quadTo(360f, 854f, 313f, 807f) quadTo(266f, 760f, 200f, 760f) lineTo(200f, 680f) quadTo(300f, 680f, 370f, 750f) quadTo(440f, 820f, 440f, 920f) lineTo(360f, 920f) close() moveTo(520f, 920f) quadTo(520f, 786f, 427f, 693f) quadTo(334f, 600f, 200f, 600f) lineTo(200f, 520f) quadTo(283f, 520f, 356f, 551.5f) quadTo(429f, 583f, 483f, 637f) quadTo(537f, 691f, 568.5f, 764f) quadTo(600f, 837f, 600f, 920f) lineTo(520f, 920f) close() moveTo(480f, 240f) quadTo(497f, 240f, 508.5f, 228.5f) quadTo(520f, 217f, 520f, 200f) quadTo(520f, 183f, 508.5f, 171.5f) quadTo(497f, 160f, 480f, 160f) quadTo(463f, 160f, 451.5f, 171.5f) quadTo(440f, 183f, 440f, 200f) quadTo(440f, 217f, 451.5f, 228.5f) quadTo(463f, 240f, 480f, 240f) close() moveTo(680f, 920f) quadTo(680f, 820f, 642f, 732.5f) quadTo(604f, 645f, 538.5f, 580f) quadTo(473f, 515f, 386f, 477.5f) quadTo(299f, 440f, 200f, 440f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLandscape.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MobileLandscape: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.MobileLandscape", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(120f, 760f) quadTo(87f, 760f, 63.5f, 736.5f) quadTo(40f, 713f, 40f, 680f) lineTo(40f, 280f) quadTo(40f, 247f, 63.5f, 223.5f) quadTo(87f, 200f, 120f, 200f) lineTo(244f, 200f) quadTo(251f, 182f, 266f, 171f) quadTo(281f, 160f, 300f, 160f) lineTo(380f, 160f) quadTo(399f, 160f, 414f, 171f) quadTo(429f, 182f, 436f, 200f) lineTo(840f, 200f) quadTo(873f, 200f, 896.5f, 223.5f) quadTo(920f, 247f, 920f, 280f) lineTo(920f, 680f) quadTo(920f, 713f, 896.5f, 736.5f) quadTo(873f, 760f, 840f, 760f) lineTo(120f, 760f) close() moveTo(840f, 680f) lineTo(840f, 280f) quadTo(840f, 280f, 840f, 280f) quadTo(840f, 280f, 840f, 280f) lineTo(120f, 280f) quadTo(120f, 280f, 120f, 280f) quadTo(120f, 280f, 120f, 280f) lineTo(120f, 680f) quadTo(120f, 680f, 120f, 680f) quadTo(120f, 680f, 120f, 680f) lineTo(840f, 680f) quadTo(840f, 680f, 840f, 680f) quadTo(840f, 680f, 840f, 680f) close() moveTo(120f, 280f) lineTo(120f, 280f) quadTo(120f, 280f, 120f, 280f) quadTo(120f, 280f, 120f, 280f) lineTo(120f, 680f) quadTo(120f, 680f, 120f, 680f) quadTo(120f, 680f, 120f, 680f) lineTo(120f, 680f) quadTo(120f, 680f, 120f, 680f) quadTo(120f, 680f, 120f, 680f) lineTo(120f, 280f) quadTo(120f, 280f, 120f, 280f) quadTo(120f, 280f, 120f, 280f) close() moveTo(200f, 520f) quadTo(217f, 520f, 228.5f, 508.5f) quadTo(240f, 497f, 240f, 480f) quadTo(240f, 463f, 228.5f, 451.5f) quadTo(217f, 440f, 200f, 440f) quadTo(183f, 440f, 171.5f, 451.5f) quadTo(160f, 463f, 160f, 480f) quadTo(160f, 497f, 171.5f, 508.5f) quadTo(183f, 520f, 200f, 520f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileLayout.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.MobileLayout: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.MobileLayout", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(120f, 840f) quadTo(87f, 840f, 63.5f, 816.5f) quadTo(40f, 793f, 40f, 760f) lineTo(40f, 680f) quadTo(40f, 647f, 63.5f, 623.5f) quadTo(87f, 600f, 120f, 600f) lineTo(360f, 600f) quadTo(393f, 600f, 416.5f, 623.5f) quadTo(440f, 647f, 440f, 680f) lineTo(440f, 760f) quadTo(440f, 793f, 416.5f, 816.5f) quadTo(393f, 840f, 360f, 840f) lineTo(120f, 840f) close() moveTo(600f, 840f) quadTo(567f, 840f, 543.5f, 816.5f) quadTo(520f, 793f, 520f, 760f) lineTo(520f, 200f) quadTo(520f, 167f, 543.5f, 143.5f) quadTo(567f, 120f, 600f, 120f) lineTo(840f, 120f) quadTo(873f, 120f, 896.5f, 143.5f) quadTo(920f, 167f, 920f, 200f) lineTo(920f, 760f) quadTo(920f, 793f, 896.5f, 816.5f) quadTo(873f, 840f, 840f, 840f) lineTo(600f, 840f) close() moveTo(720f, 720f) quadTo(737f, 720f, 748.5f, 708.5f) quadTo(760f, 697f, 760f, 680f) quadTo(760f, 663f, 748.5f, 651.5f) quadTo(737f, 640f, 720f, 640f) quadTo(703f, 640f, 691.5f, 651.5f) quadTo(680f, 663f, 680f, 680f) quadTo(680f, 697f, 691.5f, 708.5f) quadTo(703f, 720f, 720f, 720f) close() moveTo(120f, 520f) quadTo(87f, 520f, 63.5f, 496.5f) quadTo(40f, 473f, 40f, 440f) lineTo(40f, 200f) quadTo(40f, 167f, 63.5f, 143.5f) quadTo(87f, 120f, 120f, 120f) lineTo(360f, 120f) quadTo(393f, 120f, 416.5f, 143.5f) quadTo(440f, 167f, 440f, 200f) lineTo(440f, 440f) quadTo(440f, 473f, 416.5f, 496.5f) quadTo(393f, 520f, 360f, 520f) lineTo(120f, 520f) close() moveTo(280f, 320f) quadTo(297f, 320f, 308.5f, 308.5f) quadTo(320f, 297f, 320f, 280f) quadTo(320f, 263f, 308.5f, 251.5f) quadTo(297f, 240f, 280f, 240f) quadTo(263f, 240f, 251.5f, 251.5f) quadTo(240f, 263f, 240f, 280f) quadTo(240f, 297f, 251.5f, 308.5f) quadTo(263f, 320f, 280f, 320f) close() moveTo(110f, 440f) lineTo(290f, 440f) lineTo(200f, 320f) lineTo(110f, 440f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileRotateLock.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MobileRotateLock: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.MobileRotateLock", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(714f, 360f) quadTo(700f, 360f, 690f, 350f) quadTo(680f, 340f, 680f, 326f) lineTo(680f, 194f) quadTo(680f, 180f, 690f, 170f) quadTo(700f, 160f, 714f, 160f) lineTo(720f, 160f) lineTo(720f, 120f) quadTo(720f, 87f, 743.5f, 63.5f) quadTo(767f, 40f, 800f, 40f) quadTo(833f, 40f, 856.5f, 63.5f) quadTo(880f, 87f, 880f, 120f) lineTo(880f, 160f) lineTo(886f, 160f) quadTo(900f, 160f, 910f, 170f) quadTo(920f, 180f, 920f, 194f) lineTo(920f, 326f) quadTo(920f, 340f, 910f, 350f) quadTo(900f, 360f, 886f, 360f) lineTo(714f, 360f) close() moveTo(760f, 160f) lineTo(840f, 160f) lineTo(840f, 120f) quadTo(840f, 103f, 828.5f, 91.5f) quadTo(817f, 80f, 800f, 80f) quadTo(783f, 80f, 771.5f, 91.5f) quadTo(760f, 103f, 760f, 120f) lineTo(760f, 160f) close() moveTo(496f, 778f) lineTo(183f, 464f) quadTo(172f, 453f, 166.5f, 438.5f) quadTo(161f, 424f, 161f, 410f) quadTo(161f, 395f, 166.5f, 381f) quadTo(172f, 367f, 183f, 356f) lineTo(356f, 183f) quadTo(367f, 172f, 381.5f, 166f) quadTo(396f, 160f, 410f, 160f) quadTo(425f, 160f, 439f, 166f) quadTo(453f, 172f, 464f, 183f) lineTo(777f, 496f) quadTo(788f, 507f, 794f, 521f) quadTo(800f, 535f, 800f, 550f) quadTo(800f, 564f, 794f, 578.5f) quadTo(788f, 593f, 777f, 604f) lineTo(604f, 778f) quadTo(593f, 789f, 579f, 794.5f) quadTo(565f, 800f, 550f, 800f) quadTo(536f, 800f, 521.5f, 794.5f) quadTo(507f, 789f, 496f, 778f) close() moveTo(550f, 720f) quadTo(550f, 720f, 550f, 720f) quadTo(550f, 720f, 550f, 720f) lineTo(720f, 550f) quadTo(720f, 550f, 720f, 550f) quadTo(720f, 550f, 720f, 550f) lineTo(410f, 240f) quadTo(410f, 240f, 410f, 240f) quadTo(410f, 240f, 410f, 240f) lineTo(240f, 410f) quadTo(240f, 410f, 240f, 410f) quadTo(240f, 410f, 240f, 410f) lineTo(550f, 720f) close() moveTo(480f, 960f) quadTo(381f, 960f, 293.5f, 922.5f) quadTo(206f, 885f, 140.5f, 819.5f) quadTo(75f, 754f, 37.5f, 666.5f) quadTo(0f, 579f, 0f, 480f) lineTo(80f, 480f) quadTo(80f, 551f, 104f, 616f) quadTo(128f, 681f, 170.5f, 733f) quadTo(213f, 785f, 272f, 821.5f) quadTo(331f, 858f, 401f, 873f) lineTo(296f, 768f) lineTo(352f, 712f) lineTo(588f, 948f) quadTo(562f, 954f, 534.5f, 957f) quadTo(507f, 960f, 480f, 960f) close() moveTo(480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) close() moveTo(373f, 404f) quadTo(386f, 404f, 394.5f, 395f) quadTo(403f, 386f, 403f, 374f) quadTo(403f, 361f, 394.5f, 352.5f) quadTo(386f, 344f, 373f, 344f) quadTo(361f, 344f, 352f, 352.5f) quadTo(343f, 361f, 343f, 374f) quadTo(343f, 386f, 352f, 395f) quadTo(361f, 404f, 373f, 404f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileShare.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MobileShare: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.MobileShare", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f, autoMirror = true ).apply { path(fill = SolidColor(Color.Black)) { moveTo(320f, 640f) lineTo(400f, 640f) lineTo(400f, 520f) quadTo(400f, 520f, 400f, 520f) quadTo(400f, 520f, 400f, 520f) lineTo(486f, 520f) lineTo(444f, 564f) lineTo(500f, 620f) lineTo(640f, 480f) lineTo(500f, 340f) lineTo(444f, 396f) lineTo(486f, 440f) lineTo(400f, 440f) quadTo(367f, 440f, 343.5f, 463.5f) quadTo(320f, 487f, 320f, 520f) lineTo(320f, 640f) close() moveTo(280f, 920f) quadTo(247f, 920f, 223.5f, 896.5f) quadTo(200f, 873f, 200f, 840f) lineTo(200f, 120f) quadTo(200f, 87f, 223.5f, 63.5f) quadTo(247f, 40f, 280f, 40f) lineTo(680f, 40f) quadTo(713f, 40f, 736.5f, 63.5f) quadTo(760f, 87f, 760f, 120f) lineTo(760f, 244f) quadTo(778f, 251f, 789f, 266f) quadTo(800f, 281f, 800f, 300f) lineTo(800f, 380f) quadTo(800f, 399f, 789f, 414f) quadTo(778f, 429f, 760f, 436f) lineTo(760f, 840f) quadTo(760f, 873f, 736.5f, 896.5f) quadTo(713f, 920f, 680f, 920f) lineTo(280f, 920f) close() moveTo(280f, 840f) lineTo(680f, 840f) quadTo(680f, 840f, 680f, 840f) quadTo(680f, 840f, 680f, 840f) lineTo(680f, 120f) quadTo(680f, 120f, 680f, 120f) quadTo(680f, 120f, 680f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() moveTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 120f) quadTo(280f, 120f, 280f, 120f) quadTo(280f, 120f, 280f, 120f) lineTo(280f, 840f) quadTo(280f, 840f, 280f, 840f) quadTo(280f, 840f, 280f, 840f) close() } }.build() } val Icons.Rounded.MobileShare: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.MobileShare", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(320f, 640f) horizontalLineToRelative(80f) verticalLineToRelative(-120f) horizontalLineToRelative(86f) lineToRelative(-42f, 44f) lineToRelative(56f, 56f) lineToRelative(140f, -140f) lineToRelative(-140f, -140f) lineToRelative(-56f, 56f) lineToRelative(42f, 44f) horizontalLineToRelative(-86f) quadToRelative(-33f, 0f, -56.5f, 23.5f) reflectiveQuadTo(320f, 520f) verticalLineToRelative(120f) close() moveTo(280f, 920f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(200f, 840f) verticalLineToRelative(-720f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(280f, 40f) horizontalLineToRelative(400f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(760f, 120f) verticalLineToRelative(124f) quadToRelative(18f, 7f, 29f, 22f) reflectiveQuadToRelative(11f, 34f) verticalLineToRelative(80f) quadToRelative(0f, 19f, -11f, 34f) reflectiveQuadToRelative(-29f, 22f) verticalLineToRelative(404f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(680f, 920f) lineTo(280f, 920f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MobileVibrate.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MobileVibrate: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.MobileVibrate", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(320f, 840f) quadTo(287f, 840f, 263.5f, 816.5f) quadTo(240f, 793f, 240f, 760f) lineTo(240f, 200f) quadTo(240f, 167f, 263.5f, 143.5f) quadTo(287f, 120f, 320f, 120f) lineTo(640f, 120f) quadTo(673f, 120f, 696.5f, 143.5f) quadTo(720f, 167f, 720f, 200f) lineTo(720f, 760f) quadTo(720f, 793f, 696.5f, 816.5f) quadTo(673f, 840f, 640f, 840f) lineTo(320f, 840f) close() moveTo(640f, 760f) lineTo(640f, 200f) quadTo(640f, 200f, 640f, 200f) quadTo(640f, 200f, 640f, 200f) lineTo(320f, 200f) quadTo(320f, 200f, 320f, 200f) quadTo(320f, 200f, 320f, 200f) lineTo(320f, 760f) quadTo(320f, 760f, 320f, 760f) quadTo(320f, 760f, 320f, 760f) lineTo(640f, 760f) quadTo(640f, 760f, 640f, 760f) quadTo(640f, 760f, 640f, 760f) close() moveTo(480f, 320f) quadTo(497f, 320f, 508.5f, 308.5f) quadTo(520f, 297f, 520f, 280f) quadTo(520f, 263f, 508.5f, 251.5f) quadTo(497f, 240f, 480f, 240f) quadTo(463f, 240f, 451.5f, 251.5f) quadTo(440f, 263f, 440f, 280f) quadTo(440f, 297f, 451.5f, 308.5f) quadTo(463f, 320f, 480f, 320f) close() moveTo(0f, 600f) lineTo(0f, 360f) lineTo(80f, 360f) lineTo(80f, 600f) lineTo(0f, 600f) close() moveTo(120f, 680f) lineTo(120f, 280f) lineTo(200f, 280f) lineTo(200f, 680f) lineTo(120f, 680f) close() moveTo(880f, 600f) lineTo(880f, 360f) lineTo(960f, 360f) lineTo(960f, 600f) lineTo(880f, 600f) close() moveTo(760f, 680f) lineTo(760f, 280f) lineTo(840f, 280f) lineTo(840f, 680f) lineTo(760f, 680f) close() moveTo(320f, 760f) quadTo(320f, 760f, 320f, 760f) quadTo(320f, 760f, 320f, 760f) lineTo(320f, 760f) quadTo(320f, 760f, 320f, 760f) quadTo(320f, 760f, 320f, 760f) lineTo(320f, 200f) quadTo(320f, 200f, 320f, 200f) quadTo(320f, 200f, 320f, 200f) lineTo(320f, 200f) quadTo(320f, 200f, 320f, 200f) quadTo(320f, 200f, 320f, 200f) lineTo(320f, 760f) close() } }.build() } val Icons.Rounded.MobileVibrate: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.MobileVibrate", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(320f, 840f) quadTo(287f, 840f, 263.5f, 816.5f) quadTo(240f, 793f, 240f, 760f) lineTo(240f, 200f) quadTo(240f, 167f, 263.5f, 143.5f) quadTo(287f, 120f, 320f, 120f) lineTo(640f, 120f) quadTo(673f, 120f, 696.5f, 143.5f) quadTo(720f, 167f, 720f, 200f) lineTo(720f, 760f) quadTo(720f, 793f, 696.5f, 816.5f) quadTo(673f, 840f, 640f, 840f) lineTo(320f, 840f) close() moveTo(480f, 320f) quadTo(497f, 320f, 508.5f, 308.5f) quadTo(520f, 297f, 520f, 280f) quadTo(520f, 263f, 508.5f, 251.5f) quadTo(497f, 240f, 480f, 240f) quadTo(463f, 240f, 451.5f, 251.5f) quadTo(440f, 263f, 440f, 280f) quadTo(440f, 297f, 451.5f, 308.5f) quadTo(463f, 320f, 480f, 320f) close() moveTo(0f, 600f) lineTo(0f, 360f) lineTo(80f, 360f) lineTo(80f, 600f) lineTo(0f, 600f) close() moveTo(120f, 680f) lineTo(120f, 280f) lineTo(200f, 280f) lineTo(200f, 680f) lineTo(120f, 680f) close() moveTo(880f, 600f) lineTo(880f, 360f) lineTo(960f, 360f) lineTo(960f, 600f) lineTo(880f, 600f) close() moveTo(760f, 680f) lineTo(760f, 280f) lineTo(840f, 280f) lineTo(840f, 680f) lineTo(760f, 680f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Mop.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Mop: ImageVector by lazy { ImageVector.Builder( name = "Mop", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(142f, 840f) lineTo(240f, 840f) lineTo(240f, 760f) quadTo(240f, 743f, 251.5f, 731.5f) quadTo(263f, 720f, 280f, 720f) quadTo(297f, 720f, 308.5f, 731.5f) quadTo(320f, 743f, 320f, 760f) lineTo(320f, 840f) lineTo(440f, 840f) lineTo(440f, 760f) quadTo(440f, 743f, 451.5f, 731.5f) quadTo(463f, 720f, 480f, 720f) quadTo(497f, 720f, 508.5f, 731.5f) quadTo(520f, 743f, 520f, 760f) lineTo(520f, 840f) lineTo(640f, 840f) lineTo(640f, 760f) quadTo(640f, 743f, 651.5f, 731.5f) quadTo(663f, 720f, 680f, 720f) quadTo(697f, 720f, 708.5f, 731.5f) quadTo(720f, 743f, 720f, 760f) lineTo(720f, 840f) lineTo(818f, 840f) quadTo(818f, 840f, 818f, 840f) quadTo(818f, 840f, 818f, 840f) lineTo(778f, 680f) lineTo(182f, 680f) lineTo(142f, 840f) quadTo(142f, 840f, 142f, 840f) quadTo(142f, 840f, 142f, 840f) close() moveTo(818f, 920f) lineTo(142f, 920f) quadTo(103f, 920f, 79f, 889f) quadTo(55f, 858f, 65f, 820f) lineTo(120f, 600f) lineTo(120f, 520f) quadTo(120f, 487f, 143.5f, 463.5f) quadTo(167f, 440f, 200f, 440f) lineTo(360f, 440f) lineTo(360f, 160f) quadTo(360f, 110f, 395f, 75f) quadTo(430f, 40f, 480f, 40f) quadTo(530f, 40f, 565f, 75f) quadTo(600f, 110f, 600f, 160f) lineTo(600f, 440f) lineTo(760f, 440f) quadTo(793f, 440f, 816.5f, 463.5f) quadTo(840f, 487f, 840f, 520f) lineTo(840f, 600f) lineTo(895f, 820f) quadTo(908f, 858f, 883.5f, 889f) quadTo(859f, 920f, 818f, 920f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MultipleImageEdit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.MultipleImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "Outlined.MultipleImageEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(6f, 6.5f) curveToRelative(0f, -0.417f, 0.146f, -0.771f, 0.438f, -1.063f) curveToRelative(0.292f, -0.292f, 0.646f, -0.438f, 1.063f, -0.438f) horizontalLineToRelative(9f) curveToRelative(0.417f, 0f, 0.771f, 0.146f, 1.063f, 0.438f) reflectiveCurveToRelative(0.438f, 0.646f, 0.438f, 1.063f) horizontalLineTo(6f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7f, 3.5f) curveToRelative(0f, -0.417f, 0.146f, -0.771f, 0.438f, -1.063f) reflectiveCurveToRelative(0.646f, -0.438f, 1.063f, -0.438f) horizontalLineToRelative(7f) curveToRelative(0.417f, 0f, 0.771f, 0.146f, 1.063f, 0.438f) reflectiveCurveToRelative(0.438f, 0.646f, 0.438f, 1.063f) horizontalLineTo(7f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.615f, 16.463f) curveToRelative(-0.062f, -0.151f, -0.148f, -0.288f, -0.257f, -0.412f) lineToRelative(-0.762f, -0.762f) curveToRelative(-0.124f, -0.124f, -0.261f, -0.216f, -0.412f, -0.278f) reflectiveCurveToRelative(-0.309f, -0.093f, -0.474f, -0.093f) curveToRelative(-0.151f, 0f, -0.302f, 0.027f, -0.453f, 0.082f) reflectiveCurveToRelative(-0.288f, 0.144f, -0.412f, 0.268f) lineToRelative(-4.552f, 4.531f) verticalLineToRelative(2.533f) horizontalLineToRelative(2.533f) lineToRelative(4.531f, -4.531f) curveToRelative(0.124f, -0.124f, 0.213f, -0.264f, 0.268f, -0.422f) curveToRelative(0.055f, -0.158f, 0.082f, -0.312f, 0.082f, -0.463f) reflectiveCurveToRelative(-0.031f, -0.302f, -0.093f, -0.453f) close() moveTo(15.311f, 21.097f) horizontalLineToRelative(-0.783f) verticalLineToRelative(-0.783f) lineToRelative(2.513f, -2.492f) lineToRelative(0.391f, 0.371f) lineToRelative(0.371f, 0.391f) lineToRelative(-2.492f, 2.513f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18.412f, 8.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(7f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(10f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(4.801f) verticalLineToRelative(-2f) horizontalLineToRelative(-4.801f) verticalLineToRelative(-10f) horizontalLineToRelative(10f) verticalLineToRelative(3.642f) horizontalLineToRelative(2f) verticalLineToRelative(-3.642f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14.93f, 15.73f) lineToRelative(-1.387f, -1.849f) lineToRelative(-1.875f, 2.5f) lineToRelative(-1.375f, -1.825f) lineToRelative(-2.125f, 2.825f) lineToRelative(5.111f, 0f) lineToRelative(1.651f, -1.651f) close() } }.build() } val Icons.TwoTone.MultipleImageEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "TwoTone.MultipleImageEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(6f, 6.5f) curveToRelative(0f, -0.417f, 0.146f, -0.771f, 0.438f, -1.063f) curveToRelative(0.292f, -0.292f, 0.646f, -0.438f, 1.063f, -0.438f) horizontalLineToRelative(9f) curveToRelative(0.417f, 0f, 0.771f, 0.146f, 1.063f, 0.438f) reflectiveCurveToRelative(0.438f, 0.646f, 0.438f, 1.063f) horizontalLineTo(6f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7f, 3.5f) curveToRelative(0f, -0.417f, 0.146f, -0.771f, 0.438f, -1.063f) reflectiveCurveToRelative(0.646f, -0.438f, 1.063f, -0.438f) horizontalLineToRelative(7f) curveToRelative(0.417f, 0f, 0.771f, 0.146f, 1.063f, 0.438f) reflectiveCurveToRelative(0.438f, 0.646f, 0.438f, 1.063f) horizontalLineTo(7f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.615f, 16.463f) curveToRelative(-0.062f, -0.151f, -0.148f, -0.288f, -0.257f, -0.412f) lineToRelative(-0.762f, -0.762f) curveToRelative(-0.124f, -0.124f, -0.261f, -0.216f, -0.412f, -0.278f) reflectiveCurveToRelative(-0.309f, -0.093f, -0.474f, -0.093f) curveToRelative(-0.151f, 0f, -0.302f, 0.027f, -0.453f, 0.082f) reflectiveCurveToRelative(-0.288f, 0.144f, -0.412f, 0.268f) lineToRelative(-4.552f, 4.531f) verticalLineToRelative(2.533f) horizontalLineToRelative(2.533f) lineToRelative(4.531f, -4.531f) curveToRelative(0.124f, -0.124f, 0.213f, -0.264f, 0.268f, -0.422f) curveToRelative(0.055f, -0.158f, 0.082f, -0.312f, 0.082f, -0.463f) reflectiveCurveToRelative(-0.031f, -0.302f, -0.093f, -0.453f) close() moveTo(15.311f, 21.097f) horizontalLineToRelative(-0.783f) verticalLineToRelative(-0.783f) lineToRelative(2.513f, -2.492f) lineToRelative(0.391f, 0.371f) lineToRelative(0.371f, 0.391f) lineToRelative(-2.492f, 2.513f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18.412f, 8.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(7f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(10f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(4.801f) verticalLineToRelative(-2f) horizontalLineToRelative(-4.801f) verticalLineToRelative(-10f) horizontalLineToRelative(10f) verticalLineToRelative(3.642f) horizontalLineToRelative(2f) verticalLineToRelative(-3.642f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14.93f, 15.73f) lineToRelative(-1.387f, -1.849f) lineToRelative(-1.875f, 2.5f) lineToRelative(-1.375f, -1.825f) lineToRelative(-2.125f, 2.825f) lineToRelative(5.111f, 0f) lineToRelative(1.651f, -1.651f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(11.801f, 18.862f) lineToRelative(0.007f, 0f) lineToRelative(-0.005f, -0.005f) lineToRelative(5.195f, -5.195f) lineToRelative(0.002f, 0.002f) lineToRelative(0f, -0.002f) lineToRelative(0f, -3.662f) lineToRelative(-10f, 0f) lineToRelative(0f, 10f) lineToRelative(4.801f, 0f) lineToRelative(0f, -1.138f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(13.628f, 20.412f) lineToRelative(4.057f, -4.057f) lineToRelative(1.49f, 1.49f) lineToRelative(-4.057f, 4.057f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/MusicAdd.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.MusicAdd: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.MusicAdd", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(17f, 9f) verticalLineTo(12f) horizontalLineTo(14f) verticalLineTo(14f) horizontalLineTo(17f) verticalLineTo(17f) horizontalLineTo(19f) verticalLineTo(14f) horizontalLineTo(22f) verticalLineTo(12f) horizontalLineTo(19f) verticalLineTo(9f) horizontalLineTo(17f) moveTo(9f, 3f) verticalLineTo(13.55f) curveTo(8.41f, 13.21f, 7.73f, 13f, 7f, 13f) curveTo(4.79f, 13f, 3f, 14.79f, 3f, 17f) reflectiveCurveTo(4.79f, 21f, 7f, 21f) reflectiveCurveTo(11f, 19.21f, 11f, 17f) verticalLineTo(7f) horizontalLineTo(15f) verticalLineTo(3f) horizontalLineTo(9f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NeonBrush.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.NeonBrush: ImageVector by lazy { Builder( name = "NeonBrush", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(19.443f, 6.414f) lineToRelative(-1.858f, -1.858f) curveToRelative(-0.354f, -0.354f, -0.774f, -0.531f, -1.261f, -0.531f) curveToRelative(-0.487f, 0f, -0.907f, 0.177f, -1.261f, 0.531f) lineTo(4.927f, 14.694f) lineToRelative(-0.876f, 4.22f) curveToRelative(-0.071f, 0.301f, 0.009f, 0.566f, 0.239f, 0.796f) curveToRelative(0.23f, 0.23f, 0.495f, 0.31f, 0.796f, 0.239f) lineToRelative(4.22f, -0.876f) lineTo(19.443f, 8.936f) curveToRelative(0.354f, -0.354f, 0.531f, -0.774f, 0.531f, -1.261f) curveTo(19.974f, 7.188f, 19.797f, 6.768f, 19.443f, 6.414f) close() moveTo(8.801f, 16.579f) lineToRelative(-1.38f, -1.38f) lineToRelative(8.89f, -8.89f) lineToRelative(1.38f, 1.38f) lineTo(8.801f, 16.579f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = SolidColor(Color(0xFF000000)), strokeAlpha = 0.3f, strokeLineWidth = 6f ) { moveTo(19.443f, 6.415f) lineToRelative(-1.858f, -1.858f) curveToRelative(-0.354f, -0.354f, -0.774f, -0.531f, -1.261f, -0.531f) reflectiveCurveToRelative(-0.907f, 0.177f, -1.261f, 0.531f) lineTo(5.04f, 14.581f) curveToRelative(-0.074f, 0.074f, -0.124f, 0.168f, -0.146f, 0.27f) lineToRelative(-0.843f, 4.063f) curveToRelative(-0.071f, 0.301f, 0.009f, 0.566f, 0.239f, 0.796f) curveToRelative(0.23f, 0.23f, 0.495f, 0.31f, 0.796f, 0.239f) lineToRelative(4.063f, -0.843f) curveToRelative(0.102f, -0.021f, 0.196f, -0.072f, 0.27f, -0.146f) lineTo(19.443f, 8.936f) curveToRelative(0.354f, -0.354f, 0.531f, -0.774f, 0.531f, -1.261f) curveTo(19.974f, 7.188f, 19.797f, 6.768f, 19.443f, 6.415f) close() moveTo(18.698f, 9.026f) lineToRelative(-8.888f, 8.888f) curveTo(9.178f, 18.546f, 8.153f, 18.546f, 7.521f, 17.914f) horizontalLineTo(7.521f) curveToRelative(-0.154f, -0.006f, -0.308f, -0.009f, -0.459f, -0.047f) curveToRelative(-0.285f, -0.072f, -0.569f, -0.226f, -0.777f, -0.435f) curveToRelative(-0.29f, -0.293f, -0.397f, -0.639f, -0.444f, -1.036f) curveToRelative(-0.007f, -0.055f, -0.001f, -0.11f, -0.004f, -0.165f) lineToRelative(-0.055f, -0.055f) curveToRelative(-0.423f, -0.423f, -0.423f, -1.109f, 0f, -1.532f) lineToRelative(9.267f, -9.267f) curveToRelative(0.632f, -0.632f, 1.657f, -0.632f, 2.289f, 0f) lineToRelative(1.36f, 1.36f) curveTo(19.33f, 7.369f, 19.33f, 8.394f, 18.698f, 9.026f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Neurology.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Neurology: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Neurology", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(390f, 840f) quadToRelative(-51f, 0f, -88f, -35.5f) reflectiveQuadTo(260f, 719f) quadToRelative(-60f, -8f, -100f, -53f) reflectiveQuadToRelative(-40f, -106f) quadToRelative(0f, -21f, 5.5f, -41.5f) reflectiveQuadTo(142f, 480f) quadToRelative(-11f, -18f, -16.5f, -38f) reflectiveQuadToRelative(-5.5f, -42f) quadToRelative(0f, -61f, 40f, -105.5f) reflectiveQuadToRelative(99f, -52.5f) quadToRelative(3f, -51f, 41f, -86.5f) reflectiveQuadToRelative(90f, -35.5f) quadToRelative(26f, 0f, 48.5f, 10f) reflectiveQuadToRelative(41.5f, 27f) quadToRelative(18f, -17f, 41f, -27f) reflectiveQuadToRelative(49f, -10f) quadToRelative(52f, 0f, 89.5f, 35f) reflectiveQuadToRelative(40.5f, 86f) quadToRelative(59f, 8f, 99.5f, 53f) reflectiveQuadTo(840f, 400f) quadToRelative(0f, 22f, -5.5f, 42f) reflectiveQuadTo(818f, 480f) quadToRelative(11f, 18f, 16.5f, 38.5f) reflectiveQuadTo(840f, 560f) quadToRelative(0f, 62f, -40.5f, 106.5f) reflectiveQuadTo(699f, 719f) quadToRelative(-5f, 50f, -41.5f, 85.5f) reflectiveQuadTo(570f, 840f) quadToRelative(-25f, 0f, -48.5f, -9.5f) reflectiveQuadTo(480f, 804f) quadToRelative(-19f, 17f, -42f, 26.5f) reflectiveQuadToRelative(-48f, 9.5f) close() moveTo(520f, 250f) verticalLineToRelative(460f) quadToRelative(0f, 21f, 14.5f, 35.5f) reflectiveQuadTo(570f, 760f) quadToRelative(20f, 0f, 34.5f, -16f) reflectiveQuadToRelative(15.5f, -36f) quadToRelative(-21f, -8f, -38.5f, -21.5f) reflectiveQuadTo(550f, 654f) quadToRelative(-10f, -14f, -7.5f, -30f) reflectiveQuadToRelative(16.5f, -26f) quadToRelative(14f, -10f, 30f, -7.5f) reflectiveQuadToRelative(26f, 16.5f) quadToRelative(11f, 16f, 28f, 24.5f) reflectiveQuadToRelative(37f, 8.5f) quadToRelative(33f, 0f, 56.5f, -23.5f) reflectiveQuadTo(760f, 560f) quadToRelative(0f, -5f, -0.5f, -10f) reflectiveQuadToRelative(-2.5f, -10f) quadToRelative(-17f, 10f, -36.5f, 15f) reflectiveQuadToRelative(-40.5f, 5f) quadToRelative(-17f, 0f, -28.5f, -11.5f) reflectiveQuadTo(640f, 520f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(680f, 480f) quadToRelative(33f, 0f, 56.5f, -23.5f) reflectiveQuadTo(760f, 400f) quadToRelative(0f, -33f, -23.5f, -56f) reflectiveQuadTo(680f, 320f) quadToRelative(-11f, 18f, -28.5f, 31.5f) reflectiveQuadTo(613f, 373f) quadToRelative(-16f, 6f, -31f, -1f) reflectiveQuadToRelative(-20f, -23f) quadToRelative(-5f, -16f, 1.5f, -31f) reflectiveQuadToRelative(22.5f, -20f) quadToRelative(15f, -5f, 24.5f, -18f) reflectiveQuadToRelative(9.5f, -30f) quadToRelative(0f, -21f, -14.5f, -35.5f) reflectiveQuadTo(570f, 200f) quadToRelative(-21f, 0f, -35.5f, 14.5f) reflectiveQuadTo(520f, 250f) close() moveTo(440f, 710f) verticalLineToRelative(-460f) quadToRelative(0f, -21f, -14.5f, -35.5f) reflectiveQuadTo(390f, 200f) quadToRelative(-21f, 0f, -35.5f, 14.5f) reflectiveQuadTo(340f, 250f) quadToRelative(0f, 16f, 9f, 29.5f) reflectiveQuadToRelative(24f, 18.5f) quadToRelative(16f, 5f, 23f, 20f) reflectiveQuadToRelative(2f, 31f) quadToRelative(-6f, 16f, -21f, 23f) reflectiveQuadToRelative(-31f, 1f) quadToRelative(-21f, -8f, -38.5f, -21.5f) reflectiveQuadTo(279f, 320f) quadToRelative(-32f, 1f, -55.5f, 24.5f) reflectiveQuadTo(200f, 400f) quadToRelative(0f, 33f, 23.5f, 56.5f) reflectiveQuadTo(280f, 480f) quadToRelative(17f, 0f, 28.5f, 11.5f) reflectiveQuadTo(320f, 520f) quadToRelative(0f, 17f, -11.5f, 28.5f) reflectiveQuadTo(280f, 560f) quadToRelative(-21f, 0f, -40.5f, -5f) reflectiveQuadTo(203f, 540f) quadToRelative(-2f, 5f, -2.5f, 10f) reflectiveQuadToRelative(-0.5f, 10f) quadToRelative(0f, 33f, 23.5f, 56.5f) reflectiveQuadTo(280f, 640f) quadToRelative(20f, 0f, 37f, -8.5f) reflectiveQuadToRelative(28f, -24.5f) quadToRelative(10f, -14f, 26f, -16.5f) reflectiveQuadToRelative(30f, 7.5f) quadToRelative(14f, 10f, 16.5f, 26f) reflectiveQuadToRelative(-7.5f, 30f) quadToRelative(-14f, 19f, -32f, 33f) reflectiveQuadToRelative(-39f, 22f) quadToRelative(1f, 20f, 16f, 35.5f) reflectiveQuadToRelative(35f, 15.5f) quadToRelative(21f, 0f, 35.5f, -14.5f) reflectiveQuadTo(440f, 710f) close() moveTo(480f, 480f) close() } }.build() } val Icons.TwoTone.Neurology: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Neurology", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(13f, 6.25f) verticalLineToRelative(11.5f) curveToRelative(0f, 0.35f, 0.121f, 0.646f, 0.363f, 0.887f) reflectiveCurveToRelative(0.538f, 0.363f, 0.887f, 0.363f) curveToRelative(0.333f, 0f, 0.621f, -0.133f, 0.863f, -0.4f) reflectiveCurveToRelative(0.371f, -0.567f, 0.387f, -0.9f) curveToRelative(-0.35f, -0.133f, -0.671f, -0.313f, -0.962f, -0.538f) reflectiveCurveToRelative(-0.554f, -0.496f, -0.788f, -0.813f) curveToRelative(-0.167f, -0.233f, -0.229f, -0.483f, -0.188f, -0.75f) reflectiveCurveToRelative(0.179f, -0.483f, 0.412f, -0.65f) reflectiveCurveToRelative(0.483f, -0.229f, 0.75f, -0.188f) reflectiveCurveToRelative(0.483f, 0.179f, 0.65f, 0.412f) curveToRelative(0.183f, 0.267f, 0.417f, 0.471f, 0.7f, 0.613f) reflectiveCurveToRelative(0.592f, 0.213f, 0.925f, 0.213f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.587f) reflectiveCurveToRelative(0.587f, -0.863f, 0.587f, -1.413f) curveToRelative(0f, -0.083f, -0.004f, -0.167f, -0.013f, -0.25f) reflectiveCurveToRelative(-0.029f, -0.167f, -0.063f, -0.25f) curveToRelative(-0.283f, 0.167f, -0.587f, 0.292f, -0.913f, 0.375f) reflectiveCurveToRelative(-0.663f, 0.125f, -1.013f, 0.125f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.587f) curveToRelative(0.392f, -0.392f, 0.587f, -0.863f, 0.587f, -1.413f) reflectiveCurveToRelative(-0.196f, -1.017f, -0.587f, -1.4f) reflectiveCurveToRelative(-0.863f, -0.583f, -1.413f, -0.6f) curveToRelative(-0.183f, 0.3f, -0.421f, 0.563f, -0.712f, 0.788f) reflectiveCurveToRelative(-0.613f, 0.404f, -0.962f, 0.538f) curveToRelative(-0.267f, 0.1f, -0.525f, 0.092f, -0.775f, -0.025f) reflectiveCurveToRelative(-0.417f, -0.308f, -0.5f, -0.575f) reflectiveCurveToRelative(-0.071f, -0.525f, 0.038f, -0.775f) reflectiveCurveToRelative(0.296f, -0.417f, 0.563f, -0.5f) curveToRelative(0.25f, -0.083f, 0.454f, -0.233f, 0.613f, -0.45f) reflectiveCurveToRelative(0.237f, -0.467f, 0.237f, -0.75f) curveToRelative(0f, -0.35f, -0.121f, -0.646f, -0.363f, -0.887f) reflectiveCurveToRelative(-0.538f, -0.363f, -0.887f, -0.363f) reflectiveCurveToRelative(-0.646f, 0.121f, -0.887f, 0.363f) reflectiveCurveToRelative(-0.363f, 0.538f, -0.363f, 0.887f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(11f, 17.75f) verticalLineTo(6.25f) curveToRelative(0f, -0.35f, -0.121f, -0.646f, -0.363f, -0.887f) reflectiveCurveToRelative(-0.538f, -0.363f, -0.887f, -0.363f) reflectiveCurveToRelative(-0.646f, 0.121f, -0.887f, 0.363f) reflectiveCurveToRelative(-0.363f, 0.538f, -0.363f, 0.887f) curveToRelative(0f, 0.267f, 0.075f, 0.512f, 0.225f, 0.738f) reflectiveCurveToRelative(0.35f, 0.379f, 0.6f, 0.463f) curveToRelative(0.267f, 0.083f, 0.458f, 0.25f, 0.575f, 0.5f) reflectiveCurveToRelative(0.133f, 0.508f, 0.05f, 0.775f) curveToRelative(-0.1f, 0.267f, -0.275f, 0.458f, -0.525f, 0.575f) reflectiveCurveToRelative(-0.508f, 0.125f, -0.775f, 0.025f) curveToRelative(-0.35f, -0.133f, -0.671f, -0.313f, -0.962f, -0.538f) reflectiveCurveToRelative(-0.529f, -0.488f, -0.712f, -0.788f) curveToRelative(-0.533f, 0.017f, -0.996f, 0.221f, -1.388f, 0.613f) reflectiveCurveToRelative(-0.587f, 0.854f, -0.587f, 1.388f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.587f, 1.413f) curveToRelative(0.392f, 0.392f, 0.863f, 0.587f, 1.413f, 0.587f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) curveToRelative(-0.35f, 0f, -0.688f, -0.042f, -1.013f, -0.125f) reflectiveCurveToRelative(-0.629f, -0.208f, -0.913f, -0.375f) curveToRelative(-0.033f, 0.083f, -0.054f, 0.167f, -0.063f, 0.25f) reflectiveCurveToRelative(-0.013f, 0.167f, -0.013f, 0.25f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.587f, 1.413f) reflectiveCurveToRelative(0.863f, 0.587f, 1.413f, 0.587f) curveToRelative(0.333f, 0f, 0.642f, -0.071f, 0.925f, -0.213f) reflectiveCurveToRelative(0.517f, -0.346f, 0.7f, -0.613f) curveToRelative(0.167f, -0.233f, 0.383f, -0.371f, 0.65f, -0.412f) reflectiveCurveToRelative(0.517f, 0.021f, 0.75f, 0.188f) reflectiveCurveToRelative(0.371f, 0.383f, 0.412f, 0.65f) reflectiveCurveToRelative(-0.021f, 0.517f, -0.188f, 0.75f) curveToRelative(-0.233f, 0.317f, -0.5f, 0.592f, -0.8f, 0.825f) reflectiveCurveToRelative(-0.625f, 0.417f, -0.975f, 0.55f) curveToRelative(0.017f, 0.333f, 0.15f, 0.629f, 0.4f, 0.887f) reflectiveCurveToRelative(0.542f, 0.387f, 0.875f, 0.387f) curveToRelative(0.35f, 0f, 0.646f, -0.121f, 0.887f, -0.363f) reflectiveCurveToRelative(0.363f, -0.538f, 0.363f, -0.887f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.45f, 12f) curveToRelative(0.183f, -0.3f, 0.321f, -0.617f, 0.412f, -0.95f) curveToRelative(0.092f, -0.333f, 0.138f, -0.683f, 0.138f, -1.05f) curveToRelative(0f, -1.017f, -0.338f, -1.9f, -1.013f, -2.65f) reflectiveCurveToRelative(-1.504f, -1.192f, -2.487f, -1.325f) curveToRelative(-0.05f, -0.85f, -0.388f, -1.567f, -1.013f, -2.15f) reflectiveCurveToRelative(-1.371f, -0.875f, -2.237f, -0.875f) curveToRelative(-0.433f, 0f, -0.842f, 0.083f, -1.225f, 0.25f) curveToRelative(-0.383f, 0.167f, -0.725f, 0.392f, -1.025f, 0.675f) curveToRelative(-0.317f, -0.283f, -0.662f, -0.508f, -1.037f, -0.675f) reflectiveCurveToRelative(-0.779f, -0.25f, -1.213f, -0.25f) curveToRelative(-0.867f, 0f, -1.617f, 0.296f, -2.25f, 0.888f) reflectiveCurveToRelative(-0.975f, 1.313f, -1.025f, 2.162f) curveToRelative(-0.983f, 0.133f, -1.808f, 0.571f, -2.475f, 1.313f) curveToRelative(-0.667f, 0.742f, -1f, 1.621f, -1f, 2.638f) curveToRelative(0f, 0.367f, 0.046f, 0.717f, 0.138f, 1.05f) curveToRelative(0.092f, 0.333f, 0.229f, 0.65f, 0.412f, 0.95f) curveToRelative(-0.183f, 0.3f, -0.321f, 0.621f, -0.412f, 0.963f) curveToRelative(-0.092f, 0.342f, -0.138f, 0.688f, -0.138f, 1.037f) curveToRelative(0f, 1.017f, 0.333f, 1.9f, 1f, 2.65f) reflectiveCurveToRelative(1.5f, 1.192f, 2.5f, 1.325f) curveToRelative(0.083f, 0.833f, 0.433f, 1.546f, 1.05f, 2.138f) curveToRelative(0.617f, 0.592f, 1.35f, 0.888f, 2.2f, 0.888f) curveToRelative(0.417f, 0f, 0.817f, -0.079f, 1.2f, -0.237f) curveToRelative(0.383f, -0.158f, 0.733f, -0.379f, 1.05f, -0.663f) curveToRelative(0.3f, 0.283f, 0.646f, 0.504f, 1.037f, 0.663f) reflectiveCurveToRelative(0.796f, 0.237f, 1.213f, 0.237f) curveToRelative(0.85f, 0f, 1.579f, -0.296f, 2.188f, -0.888f) reflectiveCurveToRelative(0.954f, -1.304f, 1.037f, -2.138f) curveToRelative(1f, -0.133f, 1.838f, -0.571f, 2.513f, -1.313f) curveToRelative(0.675f, -0.742f, 1.013f, -1.629f, 1.013f, -2.662f) curveToRelative(0f, -0.35f, -0.046f, -0.696f, -0.138f, -1.037f) curveToRelative(-0.092f, -0.342f, -0.229f, -0.663f, -0.412f, -0.963f) close() moveTo(11f, 17.75f) curveToRelative(0f, 0.35f, -0.121f, 0.646f, -0.362f, 0.888f) curveToRelative(-0.242f, 0.242f, -0.538f, 0.362f, -0.888f, 0.362f) curveToRelative(-0.333f, 0f, -0.625f, -0.129f, -0.875f, -0.388f) curveToRelative(-0.25f, -0.258f, -0.383f, -0.554f, -0.4f, -0.888f) curveToRelative(0.35f, -0.133f, 0.675f, -0.317f, 0.975f, -0.55f) reflectiveCurveToRelative(0.567f, -0.508f, 0.8f, -0.825f) curveToRelative(0.167f, -0.233f, 0.229f, -0.483f, 0.188f, -0.75f) reflectiveCurveToRelative(-0.179f, -0.483f, -0.412f, -0.65f) curveToRelative(-0.233f, -0.167f, -0.483f, -0.229f, -0.75f, -0.188f) curveToRelative(-0.267f, 0.042f, -0.483f, 0.179f, -0.65f, 0.412f) curveToRelative(-0.183f, 0.267f, -0.417f, 0.471f, -0.7f, 0.612f) reflectiveCurveToRelative(-0.592f, 0.213f, -0.925f, 0.213f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.412f, -0.588f) reflectiveCurveToRelative(-0.588f, -0.862f, -0.588f, -1.412f) curveToRelative(0f, -0.083f, 0.004f, -0.167f, 0.013f, -0.25f) curveToRelative(0.008f, -0.083f, 0.029f, -0.167f, 0.063f, -0.25f) curveToRelative(0.283f, 0.167f, 0.587f, 0.292f, 0.912f, 0.375f) reflectiveCurveToRelative(0.663f, 0.125f, 1.013f, 0.125f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.713f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.412f, -0.588f) reflectiveCurveToRelative(-0.588f, -0.862f, -0.588f, -1.412f) curveToRelative(0f, -0.533f, 0.196f, -0.996f, 0.588f, -1.388f) reflectiveCurveToRelative(0.854f, -0.596f, 1.387f, -0.612f) curveToRelative(0.183f, 0.3f, 0.421f, 0.563f, 0.713f, 0.787f) curveToRelative(0.292f, 0.225f, 0.612f, 0.404f, 0.963f, 0.538f) curveToRelative(0.267f, 0.1f, 0.525f, 0.092f, 0.775f, -0.025f) curveToRelative(0.25f, -0.117f, 0.425f, -0.308f, 0.525f, -0.575f) curveToRelative(0.083f, -0.267f, 0.067f, -0.525f, -0.05f, -0.775f) curveToRelative(-0.117f, -0.25f, -0.308f, -0.417f, -0.575f, -0.5f) curveToRelative(-0.25f, -0.083f, -0.45f, -0.237f, -0.6f, -0.463f) curveToRelative(-0.15f, -0.225f, -0.225f, -0.471f, -0.225f, -0.737f) curveToRelative(0f, -0.35f, 0.121f, -0.646f, 0.362f, -0.888f) curveToRelative(0.242f, -0.242f, 0.538f, -0.362f, 0.888f, -0.362f) reflectiveCurveToRelative(0.646f, 0.121f, 0.888f, 0.362f) curveToRelative(0.242f, 0.242f, 0.362f, 0.538f, 0.362f, 0.888f) verticalLineToRelative(11.5f) close() moveTo(16.287f, 13.713f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) curveToRelative(0.35f, 0f, 0.688f, -0.042f, 1.013f, -0.125f) reflectiveCurveToRelative(0.629f, -0.208f, 0.912f, -0.375f) curveToRelative(0.033f, 0.083f, 0.054f, 0.167f, 0.063f, 0.25f) curveToRelative(0.008f, 0.083f, 0.013f, 0.167f, 0.013f, 0.25f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.588f, 1.412f) reflectiveCurveToRelative(-0.862f, 0.588f, -1.412f, 0.588f) curveToRelative(-0.333f, 0f, -0.642f, -0.071f, -0.925f, -0.213f) reflectiveCurveToRelative(-0.517f, -0.346f, -0.7f, -0.612f) curveToRelative(-0.167f, -0.233f, -0.383f, -0.371f, -0.65f, -0.412f) curveToRelative(-0.267f, -0.042f, -0.517f, 0.021f, -0.75f, 0.188f) curveToRelative(-0.233f, 0.167f, -0.371f, 0.383f, -0.412f, 0.65f) reflectiveCurveToRelative(0.021f, 0.517f, 0.188f, 0.75f) curveToRelative(0.233f, 0.317f, 0.496f, 0.588f, 0.787f, 0.813f) curveToRelative(0.292f, 0.225f, 0.613f, 0.404f, 0.963f, 0.538f) curveToRelative(-0.017f, 0.333f, -0.146f, 0.633f, -0.388f, 0.9f) curveToRelative(-0.242f, 0.267f, -0.529f, 0.4f, -0.862f, 0.4f) curveToRelative(-0.35f, 0f, -0.646f, -0.121f, -0.888f, -0.362f) curveToRelative(-0.242f, -0.242f, -0.362f, -0.538f, -0.362f, -0.888f) verticalLineTo(6.25f) curveToRelative(0f, -0.35f, 0.121f, -0.646f, 0.362f, -0.888f) curveToRelative(0.242f, -0.242f, 0.538f, -0.362f, 0.888f, -0.362f) reflectiveCurveToRelative(0.646f, 0.121f, 0.888f, 0.362f) curveToRelative(0.242f, 0.242f, 0.362f, 0.538f, 0.362f, 0.888f) curveToRelative(0f, 0.283f, -0.079f, 0.533f, -0.237f, 0.75f) reflectiveCurveToRelative(-0.362f, 0.367f, -0.612f, 0.45f) curveToRelative(-0.267f, 0.083f, -0.454f, 0.25f, -0.563f, 0.5f) reflectiveCurveToRelative(-0.121f, 0.508f, -0.038f, 0.775f) curveToRelative(0.083f, 0.267f, 0.25f, 0.458f, 0.5f, 0.575f) curveToRelative(0.25f, 0.117f, 0.508f, 0.125f, 0.775f, 0.025f) curveToRelative(0.35f, -0.133f, 0.671f, -0.313f, 0.962f, -0.538f) curveToRelative(0.292f, -0.225f, 0.529f, -0.487f, 0.713f, -0.787f) curveToRelative(0.55f, 0.017f, 1.021f, 0.217f, 1.412f, 0.6f) curveToRelative(0.392f, 0.383f, 0.588f, 0.85f, 0.588f, 1.4f) reflectiveCurveToRelative(-0.196f, 1.021f, -0.588f, 1.412f) reflectiveCurveToRelative(-0.862f, 0.588f, -1.412f, 0.588f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.713f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.713f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.713f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NextPlan.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.NextPlan: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.NextPlan", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f, autoMirror = true ).apply { path(fill = SolidColor(Color.Black)) { moveTo(240f, 560f) lineTo(320f, 560f) quadTo(320f, 501f, 363f, 460.5f) quadTo(406f, 420f, 466f, 420f) quadTo(502f, 420f, 533f, 436.5f) quadTo(564f, 453f, 584f, 480f) lineTo(520f, 480f) lineTo(520f, 560f) lineTo(720f, 560f) lineTo(720f, 360f) lineTo(640f, 360f) lineTo(640f, 422f) quadTo(608f, 384f, 563.5f, 362f) quadTo(519f, 340f, 466f, 340f) quadTo(371f, 340f, 305.5f, 404f) quadTo(240f, 468f, 240f, 560f) close() moveTo(480f, 880f) quadTo(397f, 880f, 324f, 848.5f) quadTo(251f, 817f, 197f, 763f) quadTo(143f, 709f, 111.5f, 636f) quadTo(80f, 563f, 80f, 480f) quadTo(80f, 397f, 111.5f, 324f) quadTo(143f, 251f, 197f, 197f) quadTo(251f, 143f, 324f, 111.5f) quadTo(397f, 80f, 480f, 80f) quadTo(563f, 80f, 636f, 111.5f) quadTo(709f, 143f, 763f, 197f) quadTo(817f, 251f, 848.5f, 324f) quadTo(880f, 397f, 880f, 480f) quadTo(880f, 563f, 848.5f, 636f) quadTo(817f, 709f, 763f, 763f) quadTo(709f, 817f, 636f, 848.5f) quadTo(563f, 880f, 480f, 880f) close() moveTo(480f, 800f) quadTo(614f, 800f, 707f, 707f) quadTo(800f, 614f, 800f, 480f) quadTo(800f, 346f, 707f, 253f) quadTo(614f, 160f, 480f, 160f) quadTo(346f, 160f, 253f, 253f) quadTo(160f, 346f, 160f, 480f) quadTo(160f, 614f, 253f, 707f) quadTo(346f, 800f, 480f, 800f) close() moveTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Noise.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Noise: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Noise", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12f, 21f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 17f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 13f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 9f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 5f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14f, 19f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14f, 15f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14f, 11f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(14f, 7f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16f, 21f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16f, 17f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16f, 13f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16f, 9f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16f, 5f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18f, 19f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18f, 15f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18f, 11f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18f, 7f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 21f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 17f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 13f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 9f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20f, 5f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10f, 19f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10f, 15f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10f, 11f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10f, 7f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8f, 21f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8f, 17f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8f, 13f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8f, 9f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8f, 5f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6f, 19f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6f, 15f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6f, 11f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6f, 7f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 21f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 17f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 13f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 9f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 5f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/NoiseAlt.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.NoiseAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.NoiseAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(4.982f, 17.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(9.482f, 17.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(13.982f, 17.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18.482f, 17.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(2.982f, 13.011f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.482f, 13.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11.982f, 13.011f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.482f, 13.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.982f, 13.011f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4.982f, 9.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(9.482f, 9.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(13.982f, 9.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(18.482f, 9.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) reflectiveCurveToRelative(0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) reflectiveCurveToRelative(-0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.482f, 5.011f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11.982f, 5.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) curveToRelative(0.288f, 0.288f, 0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) curveToRelative(-0.287f, 0.288f, -0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.482f, 5.011f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.482f, 21.011f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11.982f, 21.511f) curveToRelative(-0.425f, 0f, -0.781f, -0.144f, -1.069f, -0.431f) reflectiveCurveToRelative(-0.431f, -0.644f, -0.431f, -1.069f) reflectiveCurveToRelative(0.144f, -0.781f, 0.431f, -1.069f) reflectiveCurveToRelative(0.644f, -0.431f, 1.069f, -0.431f) reflectiveCurveToRelative(0.781f, 0.144f, 1.069f, 0.431f) curveToRelative(0.288f, 0.287f, 0.431f, 0.644f, 0.431f, 1.069f) reflectiveCurveToRelative(-0.144f, 0.781f, -0.431f, 1.069f) curveToRelative(-0.287f, 0.287f, -0.644f, 0.431f, -1.069f, 0.431f) close() } path(fill = SolidColor(Color.Black)) { moveTo(16.482f, 21.011f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) reflectiveCurveToRelative(0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) reflectiveCurveToRelative(-0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Numeric.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Numeric: ImageVector by lazy { Builder( name = "Numeric", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(4.0f, 17.0f) verticalLineTo(9.0f) horizontalLineTo(2.0f) verticalLineTo(7.0f) horizontalLineTo(6.0f) verticalLineTo(17.0f) horizontalLineTo(4.0f) moveTo(22.0f, 15.0f) curveTo(22.0f, 16.11f, 21.1f, 17.0f, 20.0f, 17.0f) horizontalLineTo(16.0f) verticalLineTo(15.0f) horizontalLineTo(20.0f) verticalLineTo(13.0f) horizontalLineTo(18.0f) verticalLineTo(11.0f) horizontalLineTo(20.0f) verticalLineTo(9.0f) horizontalLineTo(16.0f) verticalLineTo(7.0f) horizontalLineTo(20.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 22.0f, y1 = 9.0f ) verticalLineTo(10.5f) arcTo( 1.5f, 1.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 20.5f, y1 = 12.0f ) arcTo( 1.5f, 1.5f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 22.0f, y1 = 13.5f ) verticalLineTo(15.0f) moveTo(14.0f, 15.0f) verticalLineTo(17.0f) horizontalLineTo(8.0f) verticalLineTo(13.0f) curveTo(8.0f, 11.89f, 8.9f, 11.0f, 10.0f, 11.0f) horizontalLineTo(12.0f) verticalLineTo(9.0f) horizontalLineTo(8.0f) verticalLineTo(7.0f) horizontalLineTo(12.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 14.0f, y1 = 9.0f ) verticalLineTo(11.0f) curveTo(14.0f, 12.11f, 13.1f, 13.0f, 12.0f, 13.0f) horizontalLineTo(10.0f) verticalLineTo(15.0f) horizontalLineTo(14.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/OverlayAbove.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.OverlayAbove: ImageVector by lazy { ImageVector.Builder( name = "OverlayAbove", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(3f, 1f) curveTo(1.89f, 1f, 1f, 1.89f, 1f, 3f) verticalLineTo(14f) curveTo(1f, 15.11f, 1.89f, 16f, 3f, 16f) curveTo(6.67f, 16f, 10.33f, 16f, 14f, 16f) curveTo(15.11f, 16f, 16f, 15.11f, 16f, 14f) curveTo(16f, 10.33f, 16f, 6.67f, 16f, 3f) curveTo(16f, 1.89f, 15.11f, 1f, 14f, 1f) horizontalLineTo(3f) moveTo(3f, 3f) horizontalLineTo(14f) verticalLineTo(14f) horizontalLineTo(3f) verticalLineTo(3f) moveTo(18f, 7f) verticalLineTo(9f) horizontalLineTo(20f) verticalLineTo(20f) horizontalLineTo(9f) verticalLineTo(18f) horizontalLineTo(7f) verticalLineTo(20f) curveTo(7f, 21.11f, 7.89f, 22f, 9f, 22f) horizontalLineTo(20f) curveTo(21.11f, 22f, 22f, 21.11f, 22f, 20f) verticalLineTo(9f) curveTo(22f, 7.89f, 21.11f, 7f, 20f, 7f) horizontalLineTo(18f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteBox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.PaletteBox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.PaletteBox", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(4f, 20f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(6f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(4f) close() moveTo(17.5f, 8.675f) horizontalLineToRelative(2.5f) verticalLineToRelative(-2.675f) horizontalLineToRelative(-2.5f) verticalLineToRelative(2.675f) close() moveTo(17.5f, 13.325f) horizontalLineToRelative(2.5f) verticalLineToRelative(-2.65f) horizontalLineToRelative(-2.5f) verticalLineToRelative(2.65f) close() moveTo(4f, 18f) horizontalLineToRelative(11.5f) verticalLineTo(6f) horizontalLineTo(4f) verticalLineToRelative(12f) close() moveTo(17.5f, 18f) horizontalLineToRelative(2.5f) verticalLineToRelative(-2.675f) horizontalLineToRelative(-2.5f) verticalLineToRelative(2.675f) close() } path(fill = SolidColor(Color.Black)) { moveTo(9.75f, 15f) curveToRelative(-0.7f, 0f, -1.296f, -0.242f, -1.788f, -0.726f) curveToRelative(-0.492f, -0.484f, -0.738f, -1.074f, -0.738f, -1.768f) curveToRelative(0f, -0.332f, 0.064f, -0.649f, 0.193f, -0.951f) curveToRelative(0.129f, -0.303f, 0.312f, -0.57f, 0.549f, -0.801f) lineToRelative(1.784f, -1.753f) lineToRelative(1.784f, 1.753f) curveToRelative(0.237f, 0.232f, 0.42f, 0.499f, 0.549f, 0.801f) curveToRelative(0.129f, 0.303f, 0.193f, 0.62f, 0.193f, 0.951f) curveToRelative(0f, 0.695f, -0.246f, 1.284f, -0.738f, 1.768f) reflectiveCurveToRelative(-1.088f, 0.726f, -1.788f, 0.726f) close() } }.build() } val Icons.Rounded.PaletteBox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.PaletteBox", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 4.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(6f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(11.538f, 14.274f) curveToRelative(-0.492f, 0.484f, -1.088f, 0.726f, -1.788f, 0.726f) reflectiveCurveToRelative(-1.296f, -0.242f, -1.788f, -0.726f) curveToRelative(-0.492f, -0.484f, -0.738f, -1.074f, -0.738f, -1.768f) curveToRelative(0f, -0.332f, 0.064f, -0.649f, 0.193f, -0.951f) reflectiveCurveToRelative(0.312f, -0.57f, 0.549f, -0.801f) lineToRelative(1.784f, -1.753f) lineToRelative(1.784f, 1.753f) curveToRelative(0.237f, 0.232f, 0.42f, 0.499f, 0.549f, 0.801f) reflectiveCurveToRelative(0.193f, 0.62f, 0.193f, 0.951f) curveToRelative(0f, 0.695f, -0.246f, 1.284f, -0.738f, 1.768f) close() moveTo(20f, 18f) horizontalLineToRelative(-2.5f) verticalLineToRelative(-2.675f) horizontalLineToRelative(2.5f) verticalLineToRelative(2.675f) close() moveTo(20f, 13.325f) horizontalLineToRelative(-2.5f) verticalLineToRelative(-2.65f) horizontalLineToRelative(2.5f) verticalLineToRelative(2.65f) close() moveTo(20f, 8.675f) horizontalLineToRelative(-2.5f) verticalLineToRelative(-2.675f) horizontalLineToRelative(2.5f) verticalLineToRelative(2.675f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PaletteSwatch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.PaletteSwatch: ImageVector by lazy { ImageVector.Builder( name = "Palette Swatch", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(2.53f, 19.65f) lineTo(3.87f, 20.21f) verticalLineTo(11.18f) lineTo(1.44f, 17.04f) curveTo(1.03f, 18.06f, 1.5f, 19.23f, 2.53f, 19.65f) moveTo(22.03f, 15.95f) lineTo(17.07f, 4.0f) curveTo(16.76f, 3.23f, 16.03f, 2.77f, 15.26f, 2.75f) curveTo(15.0f, 2.75f, 14.73f, 2.79f, 14.47f, 2.9f) lineTo(7.1f, 5.95f) curveTo(6.35f, 6.26f, 5.89f, 7.0f, 5.87f, 7.75f) curveTo(5.86f, 8.0f, 5.91f, 8.29f, 6.0f, 8.55f) lineTo(11.0f, 20.5f) curveTo(11.29f, 21.28f, 12.03f, 21.74f, 12.81f, 21.75f) curveTo(13.07f, 21.75f, 13.33f, 21.7f, 13.58f, 21.6f) lineTo(20.94f, 18.55f) curveTo(21.96f, 18.13f, 22.45f, 16.96f, 22.03f, 15.95f) moveTo(7.88f, 8.75f) arcTo( 1.0f, 1.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 6.88f, y1 = 7.75f ) arcTo( 1.0f, 1.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 7.88f, y1 = 6.75f ) curveTo(8.43f, 6.75f, 8.88f, 7.2f, 8.88f, 7.75f) curveTo(8.88f, 8.3f, 8.43f, 8.75f, 7.88f, 8.75f) moveTo(5.88f, 19.75f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 7.88f, y1 = 21.75f ) horizontalLineTo(9.33f) lineTo(5.88f, 13.41f) verticalLineTo(19.75f) close() } }.build() } val Icons.Outlined.PaletteSwatch: ImageVector by lazy { ImageVector.Builder( name = "Palette Swatch", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(2.5f, 19.6f) lineTo(3.8f, 20.2f) verticalLineTo(11.2f) lineTo(1.4f, 17.0f) curveTo(1.0f, 18.1f, 1.5f, 19.2f, 2.5f, 19.6f) moveTo(15.2f, 4.8f) lineTo(20.2f, 16.8f) lineTo(12.9f, 19.8f) lineTo(7.9f, 7.9f) verticalLineTo(7.8f) lineTo(15.2f, 4.8f) moveTo(15.3f, 2.8f) curveTo(15.0f, 2.8f, 14.8f, 2.8f, 14.5f, 2.9f) lineTo(7.1f, 6.0f) curveTo(6.4f, 6.3f, 5.9f, 7.0f, 5.9f, 7.8f) curveTo(5.9f, 8.0f, 5.9f, 8.3f, 6.0f, 8.6f) lineTo(11.0f, 20.5f) curveTo(11.3f, 21.3f, 12.0f, 21.7f, 12.8f, 21.7f) curveTo(13.1f, 21.7f, 13.3f, 21.7f, 13.6f, 21.6f) lineTo(21.0f, 18.5f) curveTo(22.0f, 18.1f, 22.5f, 16.9f, 22.1f, 15.9f) lineTo(17.1f, 4.0f) curveTo(16.8f, 3.2f, 16.0f, 2.8f, 15.3f, 2.8f) moveTo(10.5f, 9.9f) curveTo(9.9f, 9.9f, 9.5f, 9.5f, 9.5f, 8.9f) reflectiveCurveTo(9.9f, 7.9f, 10.5f, 7.9f) curveTo(11.1f, 7.9f, 11.5f, 8.4f, 11.5f, 8.9f) reflectiveCurveTo(11.1f, 9.9f, 10.5f, 9.9f) moveTo(5.9f, 19.8f) curveTo(5.9f, 20.9f, 6.8f, 21.8f, 7.9f, 21.8f) horizontalLineTo(9.3f) lineTo(5.9f, 13.5f) verticalLineTo(19.8f) close() } }.build() } val Icons.TwoTone.PaletteSwatch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.PaletteSwatch", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(2.5f, 19.6f) lineToRelative(1.3f, 0.6f) verticalLineToRelative(-9f) lineToRelative(-2.4f, 5.8f) curveToRelative(-0.4f, 1.1f, 0.1f, 2.2f, 1.1f, 2.6f) } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(15.2f, 4.8f) lineToRelative(5f, 12f) lineToRelative(-7.3f, 3f) lineToRelative(-5f, -11.9f) lineToRelative(0f, -0.1f) lineToRelative(7.3f, -3f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(10.5f, 9.9f) curveToRelative(-0.6f, 0f, -1f, -0.4f, -1f, -1f) reflectiveCurveToRelative(0.4f, -1f, 1f, -1f) reflectiveCurveToRelative(1f, 0.5f, 1f, 1f) reflectiveCurveToRelative(-0.4f, 1f, -1f, 1f) } path(fill = SolidColor(Color(0xFF000000))) { moveTo(5.9f, 19.8f) curveToRelative(0f, 1.1f, 0.9f, 2f, 2f, 2f) horizontalLineToRelative(1.4f) lineToRelative(-3.4f, -8.3f) verticalLineToRelative(6.3f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(22.1f, 15.9f) lineToRelative(-5f, -11.9f) curveToRelative(-0.3f, -0.8f, -1.1f, -1.2f, -1.8f, -1.2f) curveToRelative(-0.3f, 0f, -0.5f, 0f, -0.8f, 0.1f) lineToRelative(-7.4f, 3.1f) curveToRelative(-0.7f, 0.3f, -1.2f, 1f, -1.2f, 1.8f) curveToRelative(0f, 0.2f, 0f, 0.5f, 0.1f, 0.8f) lineToRelative(5f, 11.9f) curveToRelative(0.3f, 0.8f, 1f, 1.2f, 1.8f, 1.2f) curveToRelative(0.3f, 0f, 0.5f, 0f, 0.8f, -0.1f) lineToRelative(7.4f, -3.1f) curveToRelative(1f, -0.4f, 1.5f, -1.6f, 1.1f, -2.6f) close() moveTo(12.9f, 19.8f) lineTo(7.9f, 7.9f) verticalLineToRelative(-0.1f) lineToRelative(7.3f, -3f) lineToRelative(5f, 12f) lineToRelative(-7.3f, 3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Panorama.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Panorama: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Panorama", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(4f, 20f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(6f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(4f) close() moveTo(4f, 18f) horizontalLineToRelative(16f) verticalLineTo(6f) horizontalLineTo(4f) verticalLineToRelative(12f) close() moveTo(11.25f, 15f) lineToRelative(-1.85f, -2.475f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2f, 2.675f) curveToRelative(-0.133f, 0.167f, -0.15f, 0.342f, -0.05f, 0.525f) reflectiveCurveToRelative(0.25f, 0.275f, 0.45f, 0.275f) horizontalLineToRelative(10f) curveToRelative(0.2f, 0f, 0.35f, -0.092f, 0.45f, -0.275f) reflectiveCurveToRelative(0.083f, -0.358f, -0.05f, -0.525f) lineToRelative(-2.75f, -3.675f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2.6f, 3.475f) close() moveTo(4f, 18f) verticalLineTo(6f) verticalLineToRelative(12f) close() } }.build() } val Icons.TwoTone.Panorama: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Panorama", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(4f, 20f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(6f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(4f) close() moveTo(4f, 18f) horizontalLineToRelative(16f) verticalLineTo(6f) horizontalLineTo(4f) verticalLineToRelative(12f) close() moveTo(11.25f, 15f) lineToRelative(-1.85f, -2.475f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2f, 2.675f) curveToRelative(-0.133f, 0.167f, -0.15f, 0.342f, -0.05f, 0.525f) reflectiveCurveToRelative(0.25f, 0.275f, 0.45f, 0.275f) horizontalLineToRelative(10f) curveToRelative(0.2f, 0f, 0.35f, -0.092f, 0.45f, -0.275f) reflectiveCurveToRelative(0.083f, -0.358f, -0.05f, -0.525f) lineToRelative(-2.75f, -3.675f) curveToRelative(-0.1f, -0.133f, -0.233f, -0.2f, -0.4f, -0.2f) reflectiveCurveToRelative(-0.3f, 0.067f, -0.4f, 0.2f) lineToRelative(-2.6f, 3.475f) close() moveTo(4f, 18f) verticalLineTo(6f) verticalLineToRelative(12f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(4f, 6f) horizontalLineToRelative(16f) verticalLineToRelative(12f) horizontalLineToRelative(-16f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pdf.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Pdf: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Pdf", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(9.427f, 9.511f) curveToRelative(-0.216f, -0.216f, -0.483f, -0.323f, -0.802f, -0.323f) horizontalLineToRelative(-2.25f) verticalLineToRelative(5.625f) horizontalLineToRelative(1.125f) verticalLineToRelative(-2.25f) horizontalLineToRelative(1.125f) curveToRelative(0.319f, 0f, 0.586f, -0.108f, 0.802f, -0.323f) curveToRelative(0.216f, -0.216f, 0.323f, -0.483f, 0.323f, -0.802f) verticalLineToRelative(-1.125f) curveToRelative(0f, -0.319f, -0.108f, -0.586f, -0.323f, -0.802f) close() moveTo(8.625f, 11.438f) horizontalLineToRelative(-1.125f) verticalLineToRelative(-1.125f) horizontalLineToRelative(1.125f) verticalLineToRelative(1.125f) close() } path(fill = SolidColor(Color.Black)) { moveTo(13.927f, 9.511f) curveToRelative(-0.216f, -0.216f, -0.483f, -0.323f, -0.802f, -0.323f) horizontalLineToRelative(-2.25f) verticalLineToRelative(5.625f) horizontalLineToRelative(2.25f) curveToRelative(0.319f, 0f, 0.586f, -0.108f, 0.802f, -0.323f) curveToRelative(0.216f, -0.216f, 0.323f, -0.483f, 0.323f, -0.802f) verticalLineToRelative(-3.375f) curveToRelative(0f, -0.319f, -0.108f, -0.586f, -0.323f, -0.802f) close() moveTo(13.125f, 13.688f) horizontalLineToRelative(-1.125f) verticalLineToRelative(-3.375f) horizontalLineToRelative(1.125f) verticalLineToRelative(3.375f) close() } path(fill = SolidColor(Color.Black)) { moveTo(15.375f, 14.813f) lineToRelative(1.125f, 0f) lineToRelative(0f, -2.25f) lineToRelative(1.125f, 0f) lineToRelative(0f, -1.125f) lineToRelative(-1.125f, 0f) lineToRelative(0f, -1.125f) lineToRelative(1.125f, 0f) lineToRelative(0f, -1.125f) lineToRelative(-2.25f, 0f) lineToRelative(0f, 5.625f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.339f, 3.661f) curveToRelative(-0.441f, -0.441f, -0.97f, -0.661f, -1.589f, -0.661f) horizontalLineTo(5.25f) curveToRelative(-0.619f, 0f, -1.148f, 0.22f, -1.589f, 0.661f) reflectiveCurveToRelative(-0.661f, 0.97f, -0.661f, 1.589f) verticalLineToRelative(13.5f) curveToRelative(0f, 0.619f, 0.22f, 1.148f, 0.661f, 1.589f) reflectiveCurveToRelative(0.97f, 0.661f, 1.589f, 0.661f) horizontalLineToRelative(13.5f) curveToRelative(0.619f, 0f, 1.148f, -0.22f, 1.589f, -0.661f) reflectiveCurveToRelative(0.661f, -0.97f, 0.661f, -1.589f) verticalLineTo(5.25f) curveToRelative(0f, -0.619f, -0.22f, -1.148f, -0.661f, -1.589f) close() moveTo(18.75f, 18.75f) horizontalLineTo(5.25f) verticalLineTo(5.25f) horizontalLineToRelative(13.5f) verticalLineToRelative(13.5f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5.25f, 5.25f) verticalLineToRelative(13.5f) verticalLineTo(5.25f) close() } }.build() } val Icons.TwoTone.Pdf: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Pdf", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(9.427f, 9.511f) curveToRelative(-0.216f, -0.216f, -0.483f, -0.323f, -0.802f, -0.323f) horizontalLineToRelative(-2.25f) verticalLineToRelative(5.625f) horizontalLineToRelative(1.125f) verticalLineToRelative(-2.25f) horizontalLineToRelative(1.125f) curveToRelative(0.319f, 0f, 0.586f, -0.108f, 0.802f, -0.323f) curveToRelative(0.216f, -0.216f, 0.323f, -0.483f, 0.323f, -0.802f) verticalLineToRelative(-1.125f) curveToRelative(0f, -0.319f, -0.108f, -0.586f, -0.323f, -0.802f) close() moveTo(8.625f, 11.438f) horizontalLineToRelative(-1.125f) verticalLineToRelative(-1.125f) horizontalLineToRelative(1.125f) verticalLineToRelative(1.125f) close() } path(fill = SolidColor(Color.Black)) { moveTo(13.927f, 9.511f) curveToRelative(-0.216f, -0.216f, -0.483f, -0.323f, -0.802f, -0.323f) horizontalLineToRelative(-2.25f) verticalLineToRelative(5.625f) horizontalLineToRelative(2.25f) curveToRelative(0.319f, 0f, 0.586f, -0.108f, 0.802f, -0.323f) curveToRelative(0.216f, -0.216f, 0.323f, -0.483f, 0.323f, -0.802f) verticalLineToRelative(-3.375f) curveToRelative(0f, -0.319f, -0.108f, -0.586f, -0.323f, -0.802f) close() moveTo(13.125f, 13.688f) horizontalLineToRelative(-1.125f) verticalLineToRelative(-3.375f) horizontalLineToRelative(1.125f) verticalLineToRelative(3.375f) close() } path(fill = SolidColor(Color.Black)) { moveTo(15.375f, 14.813f) lineToRelative(1.125f, 0f) lineToRelative(0f, -2.25f) lineToRelative(1.125f, 0f) lineToRelative(0f, -1.125f) lineToRelative(-1.125f, 0f) lineToRelative(0f, -1.125f) lineToRelative(1.125f, 0f) lineToRelative(0f, -1.125f) lineToRelative(-2.25f, 0f) lineToRelative(0f, 5.625f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.339f, 3.661f) curveToRelative(-0.441f, -0.441f, -0.97f, -0.661f, -1.589f, -0.661f) horizontalLineTo(5.25f) curveToRelative(-0.619f, 0f, -1.148f, 0.22f, -1.589f, 0.661f) reflectiveCurveToRelative(-0.661f, 0.97f, -0.661f, 1.589f) verticalLineToRelative(13.5f) curveToRelative(0f, 0.619f, 0.22f, 1.148f, 0.661f, 1.589f) reflectiveCurveToRelative(0.97f, 0.661f, 1.589f, 0.661f) horizontalLineToRelative(13.5f) curveToRelative(0.619f, 0f, 1.148f, -0.22f, 1.589f, -0.661f) reflectiveCurveToRelative(0.661f, -0.97f, 0.661f, -1.589f) verticalLineTo(5.25f) curveToRelative(0f, -0.619f, -0.22f, -1.148f, -0.661f, -1.589f) close() moveTo(18.75f, 18.75f) horizontalLineTo(5.25f) verticalLineTo(5.25f) horizontalLineToRelative(13.5f) verticalLineToRelative(13.5f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5.25f, 5.25f) verticalLineToRelative(13.5f) verticalLineTo(5.25f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5.25f, 5.25f) horizontalLineToRelative(13.5f) verticalLineToRelative(13.5f) horizontalLineToRelative(-13.5f) close() } }.build() } val Icons.Rounded.Pdf: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Pdf", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12f, 10.313f) horizontalLineToRelative(1.125f) verticalLineToRelative(3.375f) horizontalLineToRelative(-1.125f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.5f, 10.313f) horizontalLineToRelative(1.125f) verticalLineToRelative(1.125f) horizontalLineToRelative(-1.125f) close() } path(fill = SolidColor(Color.Black)) { moveTo(20.339f, 3.661f) curveToRelative(-0.441f, -0.441f, -0.97f, -0.661f, -1.589f, -0.661f) horizontalLineTo(5.25f) curveToRelative(-0.619f, 0f, -1.148f, 0.22f, -1.589f, 0.661f) reflectiveCurveToRelative(-0.661f, 0.97f, -0.661f, 1.589f) verticalLineToRelative(13.5f) curveToRelative(0f, 0.619f, 0.22f, 1.148f, 0.661f, 1.589f) reflectiveCurveToRelative(0.97f, 0.661f, 1.589f, 0.661f) horizontalLineToRelative(13.5f) curveToRelative(0.619f, 0f, 1.148f, -0.22f, 1.589f, -0.661f) reflectiveCurveToRelative(0.661f, -0.97f, 0.661f, -1.589f) verticalLineTo(5.25f) curveToRelative(0f, -0.619f, -0.22f, -1.148f, -0.661f, -1.589f) close() moveTo(9.75f, 11.438f) curveToRelative(0f, 0.319f, -0.108f, 0.586f, -0.323f, 0.802f) reflectiveCurveToRelative(-0.483f, 0.323f, -0.802f, 0.323f) horizontalLineToRelative(-1.125f) verticalLineToRelative(2.25f) horizontalLineToRelative(-1.125f) verticalLineToRelative(-5.625f) horizontalLineToRelative(2.25f) curveToRelative(0.319f, 0f, 0.586f, 0.108f, 0.802f, 0.323f) reflectiveCurveToRelative(0.323f, 0.483f, 0.323f, 0.802f) verticalLineToRelative(1.125f) close() moveTo(14.25f, 13.688f) curveToRelative(0f, 0.319f, -0.108f, 0.586f, -0.323f, 0.802f) reflectiveCurveToRelative(-0.483f, 0.323f, -0.802f, 0.323f) horizontalLineToRelative(-2.25f) verticalLineToRelative(-5.625f) horizontalLineToRelative(2.25f) curveToRelative(0.319f, 0f, 0.586f, 0.108f, 0.802f, 0.323f) reflectiveCurveToRelative(0.323f, 0.483f, 0.323f, 0.802f) verticalLineToRelative(3.375f) close() moveTo(17.625f, 10.313f) horizontalLineToRelative(-1.125f) verticalLineToRelative(1.125f) horizontalLineToRelative(1.125f) verticalLineToRelative(1.125f) horizontalLineToRelative(-1.125f) verticalLineToRelative(2.25f) horizontalLineToRelative(-1.125f) verticalLineToRelative(-5.625f) horizontalLineToRelative(2.25f) verticalLineToRelative(1.125f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Pen.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Pen: ImageVector by lazy { ImageVector.Builder( name = "Pen", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(490f, 433f) lineTo(527f, 470f) lineTo(744f, 253f) lineTo(707f, 216f) lineTo(490f, 433f) close() moveTo(200f, 760f) lineTo(237f, 760f) lineTo(470f, 527f) lineTo(433f, 490f) lineTo(200f, 723f) lineTo(200f, 760f) close() moveTo(555f, 555f) lineTo(405f, 405f) lineTo(572f, 238f) lineTo(543f, 209f) quadTo(543f, 209f, 543f, 209f) quadTo(543f, 209f, 543f, 209f) lineTo(352f, 400f) quadTo(340f, 412f, 324f, 412f) quadTo(308f, 412f, 296f, 400f) quadTo(284f, 388f, 284f, 371.5f) quadTo(284f, 355f, 296f, 343f) lineTo(486f, 153f) quadTo(510f, 129f, 542.5f, 129f) quadTo(575f, 129f, 599f, 153f) lineTo(628f, 182f) lineTo(678f, 132f) quadTo(690f, 120f, 706.5f, 120f) quadTo(723f, 120f, 735f, 132f) lineTo(828f, 225f) quadTo(840f, 237f, 840f, 253.5f) quadTo(840f, 270f, 828f, 282f) lineTo(555f, 555f) close() moveTo(160f, 840f) quadTo(143f, 840f, 131.5f, 828.5f) quadTo(120f, 817f, 120f, 800f) lineTo(120f, 723f) quadTo(120f, 707f, 126f, 692.5f) quadTo(132f, 678f, 143f, 667f) lineTo(405f, 405f) lineTo(555f, 555f) lineTo(293f, 817f) quadTo(282f, 828f, 267.5f, 834f) quadTo(253f, 840f, 237f, 840f) lineTo(160f, 840f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Perspective.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Perspective: ImageVector by lazy { ImageVector.Builder( name = "Perspective", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( stroke = SolidColor(Color(0xFF000000)), strokeLineWidth = 1f ) { moveTo(5.892f, 7.771f) lineToRelative(12.216f, -1.879f) lineToRelative(0.94f, 12.216f) lineToRelative(-14.096f, -2.819f) lineTo(5.892f, 7.771f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = SolidColor(Color(0xFF000000)), strokeLineWidth = 1f ) { moveTo(4.482f, 7.771f) curveToRelative(0f, -0.778f, 0.631f, -1.41f, 1.41f, -1.41f) reflectiveCurveToRelative(1.41f, 0.631f, 1.41f, 1.41f) reflectiveCurveTo(6.67f, 9.181f, 5.892f, 9.181f) reflectiveCurveTo(4.482f, 8.55f, 4.482f, 7.771f) } path( fill = SolidColor(Color(0xFF000000)), stroke = SolidColor(Color(0xFF000000)), strokeLineWidth = 1f ) { moveTo(16.699f, 5.892f) curveToRelative(0f, -0.778f, 0.631f, -1.41f, 1.41f, -1.41f) reflectiveCurveToRelative(1.41f, 0.631f, 1.41f, 1.41f) reflectiveCurveToRelative(-0.631f, 1.41f, -1.41f, 1.41f) reflectiveCurveTo(16.699f, 6.67f, 16.699f, 5.892f) } path( fill = SolidColor(Color(0xFF000000)), stroke = SolidColor(Color(0xFF000000)), strokeLineWidth = 1f ) { moveTo(17.638f, 18.108f) curveToRelative(0f, -0.778f, 0.631f, -1.41f, 1.41f, -1.41f) reflectiveCurveToRelative(1.41f, 0.631f, 1.41f, 1.41f) reflectiveCurveToRelative(-0.631f, 1.41f, -1.41f, 1.41f) reflectiveCurveTo(17.638f, 18.887f, 17.638f, 18.108f) } path( fill = SolidColor(Color(0xFF000000)), stroke = SolidColor(Color(0xFF000000)), strokeLineWidth = 1f ) { moveTo(3.543f, 15.289f) curveToRelative(0f, -0.778f, 0.631f, -1.41f, 1.41f, -1.41f) reflectiveCurveToRelative(1.41f, 0.631f, 1.41f, 1.41f) reflectiveCurveToRelative(-0.631f, 1.41f, -1.41f, 1.41f) reflectiveCurveTo(3.543f, 16.067f, 3.543f, 15.289f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPicker.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.PhotoPicker: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.PhotoPicker", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.563f, 18.875f) lineToRelative(-1.312f, -1.749f) lineToRelative(-1.75f, 2.333f) lineToRelative(6.998f, 0f) lineToRelative(-2.187f, -2.916f) lineToRelative(-1.749f, 2.333f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.725f, 6.65f) curveToRelative(-0.183f, -0.25f, -0.425f, -0.433f, -0.725f, -0.55f) verticalLineToRelative(-3.1f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(7f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(18f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineToRelative(-10.1f) curveToRelative(0.3f, -0.117f, 0.542f, -0.3f, 0.725f, -0.55f) curveToRelative(0.183f, -0.25f, 0.275f, -0.533f, 0.275f, -0.85f) verticalLineToRelative(-2f) curveToRelative(0f, -0.317f, -0.092f, -0.6f, -0.275f, -0.85f) close() moveTo(7f, 3f) horizontalLineToRelative(10f) verticalLineToRelative(5.943f) curveToRelative(-0.297f, -0.181f, -0.614f, -0.318f, -0.951f, -0.409f) curveToRelative(-0.34f, -0.092f, -0.685f, -0.138f, -1.034f, -0.138f) horizontalLineToRelative(-6.015f) curveToRelative(-0.35f, 0f, -0.696f, 0.046f, -1.038f, 0.138f) reflectiveCurveToRelative(-0.662f, 0.229f, -0.962f, 0.412f) verticalLineTo(3f) close() moveTo(7f, 21f) verticalLineToRelative(-8.603f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.412f) reflectiveCurveToRelative(0.862f, -0.588f, 1.413f, -0.588f) horizontalLineToRelative(6.015f) curveToRelative(0.548f, 0f, 1.017f, 0.196f, 1.407f, 0.588f) curveToRelative(0.37f, 0.371f, 0.558f, 0.816f, 0.578f, 1.329f) verticalLineToRelative(8.687f) horizontalLineTo(7f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPickerMobile.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.PhotoPickerMobile: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.PhotoPickerMobile", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.563f, 18.875f) lineToRelative(-1.312f, -1.749f) lineToRelative(-1.75f, 2.333f) lineToRelative(6.998f, 0f) lineToRelative(-2.187f, -2.916f) lineToRelative(-1.749f, 2.333f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.725f, 6.65f) curveToRelative(-0.183f, -0.25f, -0.425f, -0.433f, -0.725f, -0.55f) verticalLineToRelative(-3.1f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(7f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(18f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineToRelative(-10.1f) curveToRelative(0.3f, -0.117f, 0.542f, -0.3f, 0.725f, -0.55f) curveToRelative(0.183f, -0.25f, 0.275f, -0.533f, 0.275f, -0.85f) verticalLineToRelative(-2f) curveToRelative(0f, -0.317f, -0.092f, -0.6f, -0.275f, -0.85f) close() moveTo(7f, 3f) horizontalLineToRelative(10f) verticalLineToRelative(5.943f) curveToRelative(-0.297f, -0.181f, -0.614f, -0.318f, -0.951f, -0.409f) curveToRelative(-0.34f, -0.092f, -0.685f, -0.138f, -1.034f, -0.138f) horizontalLineToRelative(-6.015f) curveToRelative(-0.35f, 0f, -0.696f, 0.046f, -1.038f, 0.138f) reflectiveCurveToRelative(-0.662f, 0.229f, -0.962f, 0.412f) verticalLineTo(3f) close() moveTo(7f, 21f) verticalLineToRelative(-8.603f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.412f) reflectiveCurveToRelative(0.862f, -0.588f, 1.413f, -0.588f) horizontalLineToRelative(6.015f) curveToRelative(0.548f, 0f, 1.017f, 0.196f, 1.407f, 0.588f) curveToRelative(0.37f, 0.371f, 0.558f, 0.816f, 0.578f, 1.329f) verticalLineToRelative(8.687f) horizontalLineTo(7f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 6f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoPrints.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.PhotoPrints: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.PhotoPrints", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(508f, 760f) lineTo(732f, 760f) quadTo(725f, 786f, 708f, 802f) quadTo(691f, 818f, 664f, 822f) lineTo(228f, 875f) quadTo(195f, 880f, 168.5f, 859.5f) quadTo(142f, 839f, 138f, 806f) lineTo(85f, 369f) quadTo(81f, 336f, 101f, 310f) quadTo(121f, 284f, 154f, 280f) lineTo(200f, 274f) lineTo(200f, 354f) lineTo(164f, 359f) quadTo(164f, 359f, 164f, 359f) quadTo(164f, 359f, 164f, 359f) lineTo(218f, 796f) quadTo(218f, 796f, 218f, 796f) quadTo(218f, 796f, 218f, 796f) lineTo(508f, 760f) close() moveTo(360f, 680f) quadTo(327f, 680f, 303.5f, 656.5f) quadTo(280f, 633f, 280f, 600f) lineTo(280f, 160f) quadTo(280f, 127f, 303.5f, 103.5f) quadTo(327f, 80f, 360f, 80f) lineTo(800f, 80f) quadTo(833f, 80f, 856.5f, 103.5f) quadTo(880f, 127f, 880f, 160f) lineTo(880f, 600f) quadTo(880f, 633f, 856.5f, 656.5f) quadTo(833f, 680f, 800f, 680f) lineTo(360f, 680f) close() moveTo(360f, 600f) lineTo(800f, 600f) quadTo(800f, 600f, 800f, 600f) quadTo(800f, 600f, 800f, 600f) lineTo(800f, 160f) quadTo(800f, 160f, 800f, 160f) quadTo(800f, 160f, 800f, 160f) lineTo(360f, 160f) quadTo(360f, 160f, 360f, 160f) quadTo(360f, 160f, 360f, 160f) lineTo(360f, 600f) quadTo(360f, 600f, 360f, 600f) quadTo(360f, 600f, 360f, 600f) close() moveTo(580f, 380f) quadTo(580f, 380f, 580f, 380f) quadTo(580f, 380f, 580f, 380f) lineTo(580f, 380f) quadTo(580f, 380f, 580f, 380f) quadTo(580f, 380f, 580f, 380f) lineTo(580f, 380f) quadTo(580f, 380f, 580f, 380f) quadTo(580f, 380f, 580f, 380f) lineTo(580f, 380f) quadTo(580f, 380f, 580f, 380f) quadTo(580f, 380f, 580f, 380f) close() moveTo(218f, 796f) lineTo(218f, 796f) lineTo(218f, 796f) lineTo(218f, 796f) lineTo(218f, 796f) quadTo(218f, 796f, 218f, 796f) quadTo(218f, 796f, 218f, 796f) close() moveTo(581f, 560f) quadTo(649f, 560f, 696.5f, 513f) quadTo(744f, 466f, 749f, 400f) quadTo(681f, 400f, 632.5f, 447f) quadTo(584f, 494f, 581f, 560f) close() moveTo(581f, 560f) quadTo(578f, 494f, 529.5f, 447f) quadTo(481f, 400f, 413f, 400f) quadTo(418f, 466f, 465.5f, 513f) quadTo(513f, 560f, 581f, 560f) close() moveTo(581f, 440f) quadTo(598f, 440f, 609.5f, 428.5f) quadTo(621f, 417f, 621f, 400f) lineTo(621f, 390f) lineTo(631f, 394f) quadTo(646f, 400f, 661.5f, 397f) quadTo(677f, 394f, 685f, 380f) quadTo(694f, 365f, 691f, 348f) quadTo(688f, 331f, 671f, 324f) lineTo(661f, 320f) lineTo(671f, 316f) quadTo(688f, 309f, 690.5f, 291.5f) quadTo(693f, 274f, 685f, 260f) quadTo(676f, 245f, 661f, 242.5f) quadTo(646f, 240f, 631f, 246f) lineTo(621f, 250f) lineTo(621f, 240f) quadTo(621f, 223f, 609.5f, 211.5f) quadTo(598f, 200f, 581f, 200f) quadTo(564f, 200f, 552.5f, 211.5f) quadTo(541f, 223f, 541f, 240f) lineTo(541f, 250f) lineTo(531f, 246f) quadTo(516f, 240f, 501f, 242.5f) quadTo(486f, 245f, 477f, 260f) quadTo(469f, 274f, 471.5f, 291.5f) quadTo(474f, 309f, 491f, 316f) lineTo(501f, 320f) lineTo(491f, 324f) quadTo(474f, 331f, 471f, 348f) quadTo(468f, 365f, 477f, 380f) quadTo(485f, 394f, 500.5f, 397f) quadTo(516f, 400f, 531f, 394f) lineTo(541f, 390f) lineTo(541f, 400f) quadTo(541f, 417f, 552.5f, 428.5f) quadTo(564f, 440f, 581f, 440f) close() moveTo(581f, 360f) quadTo(564f, 360f, 552.5f, 348.5f) quadTo(541f, 337f, 541f, 320f) quadTo(541f, 303f, 552.5f, 291.5f) quadTo(564f, 280f, 581f, 280f) quadTo(598f, 280f, 609.5f, 291.5f) quadTo(621f, 303f, 621f, 320f) quadTo(621f, 337f, 609.5f, 348.5f) quadTo(598f, 360f, 581f, 360f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PhotoSizeSelectSmall.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.PhotoSizeSelectSmall: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.PhotoSizeSelectSmall", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(520f, 840f) lineTo(200f, 840f) quadTo(167f, 840f, 143.5f, 816.5f) quadTo(120f, 793f, 120f, 760f) lineTo(120f, 440f) lineTo(520f, 440f) lineTo(520f, 840f) close() moveTo(160f, 760f) lineTo(480f, 760f) lineTo(376f, 620f) lineTo(300f, 720f) lineTo(244f, 646f) lineTo(160f, 760f) close() moveTo(200f, 200f) lineTo(120f, 200f) quadTo(120f, 167f, 143.5f, 143.5f) quadTo(167f, 120f, 200f, 120f) lineTo(200f, 200f) close() moveTo(280f, 200f) lineTo(280f, 120f) lineTo(360f, 120f) lineTo(360f, 200f) lineTo(280f, 200f) close() moveTo(440f, 200f) lineTo(440f, 120f) lineTo(520f, 120f) lineTo(520f, 200f) lineTo(440f, 200f) close() moveTo(600f, 200f) lineTo(600f, 120f) lineTo(680f, 120f) lineTo(680f, 200f) lineTo(600f, 200f) close() moveTo(600f, 840f) lineTo(600f, 760f) lineTo(680f, 760f) lineTo(680f, 840f) lineTo(600f, 840f) close() moveTo(760f, 200f) lineTo(760f, 120f) quadTo(793f, 120f, 816.5f, 143.5f) quadTo(840f, 167f, 840f, 200f) lineTo(760f, 200f) close() moveTo(120f, 360f) lineTo(120f, 280f) lineTo(200f, 280f) lineTo(200f, 360f) lineTo(120f, 360f) close() moveTo(760f, 760f) lineTo(840f, 760f) quadTo(840f, 793f, 816.5f, 816.5f) quadTo(793f, 840f, 760f, 840f) lineTo(760f, 760f) close() moveTo(760f, 680f) lineTo(760f, 600f) lineTo(840f, 600f) lineTo(840f, 680f) lineTo(760f, 680f) close() moveTo(760f, 520f) lineTo(760f, 440f) lineTo(840f, 440f) lineTo(840f, 520f) lineTo(760f, 520f) close() moveTo(760f, 360f) lineTo(760f, 280f) lineTo(840f, 280f) lineTo(840f, 360f) lineTo(760f, 360f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/PictureInPictureCenter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.PictureInPictureCenter: ImageVector by lazy { ImageVector.Builder( name = "PictureInPictureCenter", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(160f, 800f) quadTo(127f, 800f, 103.5f, 776.5f) quadTo(80f, 753f, 80f, 720f) lineTo(80f, 240f) quadTo(80f, 207f, 103.5f, 183.5f) quadTo(127f, 160f, 160f, 160f) lineTo(800f, 160f) quadTo(833f, 160f, 856.5f, 183.5f) quadTo(880f, 207f, 880f, 240f) lineTo(880f, 720f) quadTo(880f, 753f, 856.5f, 776.5f) quadTo(833f, 800f, 800f, 800f) lineTo(160f, 800f) close() moveTo(160f, 720f) lineTo(800f, 720f) quadTo(800f, 720f, 800f, 720f) quadTo(800f, 720f, 800f, 720f) lineTo(800f, 240f) quadTo(800f, 240f, 800f, 240f) quadTo(800f, 240f, 800f, 240f) lineTo(160f, 240f) quadTo(160f, 240f, 160f, 240f) quadTo(160f, 240f, 160f, 240f) lineTo(160f, 720f) quadTo(160f, 720f, 160f, 720f) quadTo(160f, 720f, 160f, 720f) close() moveTo(320f, 600f) lineTo(640f, 600f) lineTo(640f, 360f) lineTo(320f, 360f) lineTo(320f, 600f) close() moveTo(160f, 720f) quadTo(160f, 720f, 160f, 720f) quadTo(160f, 720f, 160f, 720f) lineTo(160f, 240f) quadTo(160f, 240f, 160f, 240f) quadTo(160f, 240f, 160f, 240f) lineTo(160f, 240f) quadTo(160f, 240f, 160f, 240f) quadTo(160f, 240f, 160f, 240f) lineTo(160f, 720f) quadTo(160f, 720f, 160f, 720f) quadTo(160f, 720f, 160f, 720f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Png.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Png: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Png", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(260f, 460f) verticalLineToRelative(-40f) horizontalLineToRelative(40f) verticalLineToRelative(40f) horizontalLineToRelative(-40f) close() moveTo(660f, 600f) horizontalLineToRelative(40f) quadToRelative(25f, 0f, 42.5f, -17.5f) reflectiveQuadTo(760f, 540f) verticalLineToRelative(-60f) horizontalLineToRelative(-60f) verticalLineToRelative(60f) horizontalLineToRelative(-40f) verticalLineToRelative(-120f) horizontalLineToRelative(100f) quadToRelative(0f, -25f, -17.5f, -42.5f) reflectiveQuadTo(700f, 360f) horizontalLineToRelative(-40f) quadToRelative(-25f, 0f, -42.5f, 17.5f) reflectiveQuadTo(600f, 420f) verticalLineToRelative(120f) quadToRelative(0f, 25f, 17.5f, 42.5f) reflectiveQuadTo(660f, 600f) close() moveTo(200f, 600f) horizontalLineToRelative(60f) verticalLineToRelative(-80f) horizontalLineToRelative(60f) quadToRelative(17f, 0f, 28.5f, -11.5f) reflectiveQuadTo(360f, 480f) verticalLineToRelative(-80f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(320f, 360f) lineTo(200f, 360f) verticalLineToRelative(240f) close() moveTo(400f, 600f) horizontalLineToRelative(60f) verticalLineToRelative(-96f) lineToRelative(40f, 96f) horizontalLineToRelative(60f) verticalLineToRelative(-240f) horizontalLineToRelative(-60f) verticalLineToRelative(94f) lineToRelative(-40f, -94f) horizontalLineToRelative(-60f) verticalLineToRelative(240f) close() moveTo(160f, 800f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(80f, 720f) verticalLineToRelative(-480f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(160f, 160f) horizontalLineToRelative(640f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(880f, 240f) verticalLineToRelative(480f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(800f, 800f) lineTo(160f, 800f) close() moveTo(160f, 720f) horizontalLineToRelative(640f) verticalLineToRelative(-480f) lineTo(160f, 240f) verticalLineToRelative(480f) close() moveTo(160f, 720f) verticalLineToRelative(-480f) verticalLineToRelative(480f) close() moveTo(160f, 720f) verticalLineToRelative(-480f) verticalLineToRelative(480f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Polygon.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Polygon: ImageVector by lazy { ImageVector.Builder( name = "Polygon", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(7.45f, 21.0f) curveTo(7.0167f, 21.0f, 6.625f, 20.875f, 6.275f, 20.625f) reflectiveCurveToRelative(-0.5917f, -0.5833f, -0.725f, -1.0f) lineToRelative(-3.075f, -9.2f) curveToRelative(-0.1333f, -0.4333f, -0.1333f, -0.8583f, 0.0f, -1.275f) curveToRelative(0.1333f, -0.4167f, 0.3833f, -0.75f, 0.75f, -1.0f) lineToRelative(7.625f, -5.35f) curveTo(11.2f, 2.5667f, 11.5833f, 2.45f, 12.0f, 2.45f) curveToRelative(0.4167f, 0.0f, 0.8f, 0.1167f, 1.15f, 0.35f) lineToRelative(7.625f, 5.35f) curveToRelative(0.3667f, 0.25f, 0.6167f, 0.5833f, 0.75f, 1.0f) curveToRelative(0.1333f, 0.4167f, 0.1333f, 0.8417f, 0.0f, 1.275f) lineTo(18.45f, 19.625f) curveToRelative(-0.1333f, 0.4167f, -0.375f, 0.75f, -0.725f, 1.0f) reflectiveCurveTo(16.9833f, 21.0f, 16.55f, 21.0f) horizontalLineTo(7.45f) close() } }.build() } val Icons.Outlined.Polygon: ImageVector by lazy { ImageVector.Builder( name = "OutlinedPolygon", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(298.0f, 760.0f) horizontalLineToRelative(364.0f) lineToRelative(123.0f, -369.0f) lineToRelative(-305.0f, -213.0f) lineToRelative(-305.0f, 213.0f) lineToRelative(123.0f, 369.0f) close() moveTo(298.0f, 840.0f) quadToRelative(-26.0f, 0.0f, -47.0f, -15.0f) reflectiveQuadToRelative(-29.0f, -40.0f) lineTo(99.0f, 417.0f) quadToRelative(-8.0f, -26.0f, 0.0f, -51.0f) reflectiveQuadToRelative(30.0f, -40.0f) lineToRelative(305.0f, -214.0f) quadToRelative(21.0f, -14.0f, 46.0f, -14.0f) reflectiveQuadToRelative(46.0f, 14.0f) lineToRelative(305.0f, 214.0f) quadToRelative(22.0f, 15.0f, 30.0f, 40.0f) reflectiveQuadToRelative(0.0f, 51.0f) lineTo(738.0f, 785.0f) quadToRelative(-8.0f, 25.0f, -29.0f, 40.0f) reflectiveQuadToRelative(-47.0f, 15.0f) lineTo(298.0f, 840.0f) close() moveTo(480.0f, 469.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Prefix.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Prefix: ImageVector by lazy { Builder( name = "Prefix", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(11.14f, 4.0f) lineTo(6.43f, 16.0f) horizontalLineTo(8.36f) lineTo(9.32f, 13.43f) horizontalLineTo(14.67f) lineTo(15.64f, 16.0f) horizontalLineTo(17.57f) lineTo(12.86f, 4.0f) moveTo(12.0f, 6.29f) lineTo(14.03f, 11.71f) horizontalLineTo(9.96f) moveTo(4.0f, 18.0f) verticalLineTo(15.0f) horizontalLineTo(2.0f) verticalLineTo(20.0f) horizontalLineTo(22.0f) verticalLineTo(18.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Preview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Preview: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Preview", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(200f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(120f, 760f) verticalLineToRelative(-560f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(200f, 120f) horizontalLineToRelative(560f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(840f, 200f) verticalLineToRelative(560f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(760f, 840f) lineTo(200f, 840f) close() moveTo(200f, 760f) horizontalLineToRelative(560f) verticalLineToRelative(-480f) lineTo(200f, 280f) verticalLineToRelative(480f) close() moveTo(333.5f, 635.5f) quadTo(269f, 591f, 240f, 520f) quadToRelative(29f, -71f, 93.5f, -115.5f) reflectiveQuadTo(480f, 360f) quadToRelative(82f, 0f, 146.5f, 44.5f) reflectiveQuadTo(720f, 520f) quadToRelative(-29f, 71f, -93.5f, 115.5f) reflectiveQuadTo(480f, 680f) quadToRelative(-82f, 0f, -146.5f, -44.5f) close() moveTo(582f, 593.5f) quadToRelative(46f, -26.5f, 72f, -73.5f) quadToRelative(-26f, -47f, -72f, -73.5f) reflectiveQuadTo(480f, 420f) quadToRelative(-56f, 0f, -102f, 26.5f) reflectiveQuadTo(306f, 520f) quadToRelative(26f, 47f, 72f, 73.5f) reflectiveQuadTo(480f, 620f) quadToRelative(56f, 0f, 102f, -26.5f) close() moveTo(480f, 520f) close() moveTo(522.5f, 562.5f) quadTo(540f, 545f, 540f, 520f) reflectiveQuadToRelative(-17.5f, -42.5f) quadTo(505f, 460f, 480f, 460f) reflectiveQuadToRelative(-42.5f, 17.5f) quadTo(420f, 495f, 420f, 520f) reflectiveQuadToRelative(17.5f, 42.5f) quadTo(455f, 580f, 480f, 580f) reflectiveQuadToRelative(42.5f, -17.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Print.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Print: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Print", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(8f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineToRelative(-4f) curveToRelative(0f, -0.85f, 0.292f, -1.563f, 0.875f, -2.138f) curveToRelative(0.583f, -0.575f, 1.292f, -0.863f, 2.125f, -0.863f) horizontalLineToRelative(14f) curveToRelative(0.85f, 0f, 1.563f, 0.287f, 2.138f, 0.863f) reflectiveCurveToRelative(0.863f, 1.288f, 0.863f, 2.138f) verticalLineToRelative(4f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-8f) close() moveTo(4f, 15f) horizontalLineToRelative(2f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(8f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) horizontalLineToRelative(2f) verticalLineToRelative(-4f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineTo(5f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(4f) close() moveTo(16f, 8f) verticalLineToRelative(-3f) horizontalLineToRelative(-8f) verticalLineToRelative(3f) horizontalLineToRelative(-2f) verticalLineToRelative(-3f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(8f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(3f) horizontalLineToRelative(-2f) close() moveTo(18f, 12.5f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(16f, 19f) verticalLineToRelative(-4f) horizontalLineToRelative(-8f) verticalLineToRelative(4f) horizontalLineToRelative(8f) close() moveTo(4f, 10f) horizontalLineToRelative(16f) horizontalLineTo(4f) close() } }.build() } val Icons.TwoTone.Print: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Print", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(8f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) verticalLineToRelative(-4f) curveToRelative(0f, -0.85f, 0.292f, -1.563f, 0.875f, -2.138f) curveToRelative(0.583f, -0.575f, 1.292f, -0.863f, 2.125f, -0.863f) horizontalLineToRelative(14f) curveToRelative(0.85f, 0f, 1.563f, 0.287f, 2.138f, 0.863f) reflectiveCurveToRelative(0.863f, 1.288f, 0.863f, 2.138f) verticalLineToRelative(4f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-8f) close() moveTo(4f, 15f) horizontalLineToRelative(2f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(8f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) horizontalLineToRelative(2f) verticalLineToRelative(-4f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineTo(5f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(4f) close() moveTo(16f, 8f) verticalLineToRelative(-3f) horizontalLineToRelative(-8f) verticalLineToRelative(3f) horizontalLineToRelative(-2f) verticalLineToRelative(-3f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(8f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(3f) horizontalLineToRelative(-2f) close() moveTo(18f, 12.5f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(16f, 19f) verticalLineToRelative(-4f) horizontalLineToRelative(-8f) verticalLineToRelative(4f) horizontalLineToRelative(8f) close() moveTo(4f, 10f) horizontalLineToRelative(16f) horizontalLineTo(4f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(8f, 5f) horizontalLineToRelative(8f) verticalLineToRelative(3f) horizontalLineToRelative(-8f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(4f, 10f) horizontalLineToRelative(16f) verticalLineToRelative(5f) horizontalLineToRelative(-16f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Psychology.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Psychology: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Psychology", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(434f, 550f) lineTo(438f, 582f) quadTo(439f, 590f, 444.5f, 595f) quadTo(450f, 600f, 458f, 600f) lineTo(502f, 600f) quadTo(510f, 600f, 515.5f, 595f) quadTo(521f, 590f, 522f, 582f) lineTo(526f, 550f) quadTo(534f, 547f, 540.5f, 543f) quadTo(547f, 539f, 552f, 534f) lineTo(582f, 547f) quadTo(589f, 550f, 596f, 548f) quadTo(603f, 546f, 607f, 539f) lineTo(629f, 501f) quadTo(633f, 494f, 631.5f, 487f) quadTo(630f, 480f, 624f, 475f) lineTo(598f, 456f) quadTo(600f, 448f, 600f, 440f) quadTo(600f, 432f, 598f, 424f) lineTo(624f, 405f) quadTo(630f, 400f, 631.5f, 393f) quadTo(633f, 386f, 629f, 379f) lineTo(607f, 341f) quadTo(603f, 334f, 596f, 332f) quadTo(589f, 330f, 582f, 333f) lineTo(552f, 346f) quadTo(547f, 341f, 540.5f, 337f) quadTo(534f, 333f, 526f, 330f) lineTo(522f, 298f) quadTo(521f, 290f, 515.5f, 285f) quadTo(510f, 280f, 502f, 280f) lineTo(458f, 280f) quadTo(450f, 280f, 444.5f, 285f) quadTo(439f, 290f, 438f, 298f) lineTo(434f, 330f) quadTo(426f, 333f, 419.5f, 337f) quadTo(413f, 341f, 408f, 346f) lineTo(378f, 333f) quadTo(371f, 330f, 364f, 332f) quadTo(357f, 334f, 353f, 341f) lineTo(331f, 379f) quadTo(327f, 386f, 328.5f, 393f) quadTo(330f, 400f, 336f, 405f) lineTo(362f, 424f) quadTo(360f, 432f, 360f, 440f) quadTo(360f, 448f, 362f, 456f) lineTo(336f, 475f) quadTo(330f, 480f, 328.5f, 487f) quadTo(327f, 494f, 331f, 501f) lineTo(353f, 539f) quadTo(357f, 546f, 364f, 548f) quadTo(371f, 550f, 378f, 547f) lineTo(408f, 534f) quadTo(413f, 539f, 419.5f, 543f) quadTo(426f, 547f, 434f, 550f) close() moveTo(480f, 500f) quadTo(455f, 500f, 437.5f, 482.5f) quadTo(420f, 465f, 420f, 440f) quadTo(420f, 415f, 437.5f, 397.5f) quadTo(455f, 380f, 480f, 380f) quadTo(505f, 380f, 522.5f, 397.5f) quadTo(540f, 415f, 540f, 440f) quadTo(540f, 465f, 522.5f, 482.5f) quadTo(505f, 500f, 480f, 500f) close() moveTo(280f, 880f) quadTo(263f, 880f, 251.5f, 868.5f) quadTo(240f, 857f, 240f, 840f) lineTo(240f, 708f) quadTo(183f, 656f, 151.5f, 586.5f) quadTo(120f, 517f, 120f, 440f) quadTo(120f, 290f, 225f, 185f) quadTo(330f, 80f, 480f, 80f) quadTo(605f, 80f, 701.5f, 153.5f) quadTo(798f, 227f, 827f, 345f) lineTo(879f, 550f) quadTo(884f, 569f, 872f, 584.5f) quadTo(860f, 600f, 840f, 600f) lineTo(760f, 600f) lineTo(760f, 720f) quadTo(760f, 753f, 736.5f, 776.5f) quadTo(713f, 800f, 680f, 800f) lineTo(600f, 800f) lineTo(600f, 840f) quadTo(600f, 857f, 588.5f, 868.5f) quadTo(577f, 880f, 560f, 880f) lineTo(280f, 880f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Puzzle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Puzzle: ImageVector by lazy { Builder( name = "Puzzle", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(349.0f, 859.0f) lineTo(174.0f, 859.0f) quadToRelative(-28.725f, 0.0f, -50.863f, -22.137f) quadTo(101.0f, 814.725f, 101.0f, 786.0f) verticalLineToRelative(-169.0f) quadToRelative(46.0f, -5.0f, 78.0f, -37.5f) reflectiveQuadToRelative(32.0f, -78.5f) quadToRelative(0.0f, -46.0f, -32.0f, -78.5f) reflectiveQuadTo(101.0f, 385.0f) verticalLineToRelative(-170.0f) quadToRelative(0.0f, -28.725f, 22.137f, -50.862f) quadTo(145.275f, 142.0f, 174.0f, 142.0f) horizontalLineToRelative(172.0f) quadToRelative(11.0f, -43.0f, 41.958f, -72.5f) quadToRelative(30.957f, -29.5f, 73.0f, -29.5f) quadTo(503.0f, 40.0f, 534.0f, 69.68f) reflectiveQuadTo(576.0f, 142.0f) horizontalLineToRelative(169.0f) quadToRelative(28.725f, 0.0f, 50.862f, 22.138f) quadTo(818.0f, 186.275f, 818.0f, 215.0f) verticalLineToRelative(169.0f) quadToRelative(43.0f, 11.0f, 71.0f, 43.958f) quadToRelative(28.0f, 32.957f, 28.0f, 75.0f) quadTo(917.0f, 545.0f, 888.82f, 574.5f) reflectiveQuadTo(818.0f, 615.0f) verticalLineToRelative(171.0f) quadToRelative(0.0f, 28.725f, -22.138f, 50.863f) quadTo(773.725f, 859.0f, 745.0f, 859.0f) lineTo(569.0f, 859.0f) quadToRelative(-2.0f, -53.0f, -33.75f, -86.5f) reflectiveQuadTo(459.0f, 739.0f) quadToRelative(-44.5f, 0.0f, -76.25f, 33.5f) reflectiveQuadTo(349.0f, 859.0f) close() moveTo(174.0f, 786.0f) horizontalLineToRelative(116.0f) quadToRelative(24.0f, -62.0f, 71.388f, -91.0f) quadToRelative(47.388f, -29.0f, 97.5f, -29.0f) reflectiveQuadTo(557.0f, 695.0f) quadToRelative(48.0f, 29.0f, 73.0f, 91.0f) horizontalLineToRelative(115.0f) verticalLineToRelative(-237.0f) horizontalLineToRelative(53.0f) quadToRelative(20.0f, 0.0f, 33.0f, -13.0f) reflectiveQuadToRelative(13.0f, -33.0f) quadToRelative(0.0f, -20.0f, -13.0f, -33.0f) reflectiveQuadToRelative(-33.0f, -13.0f) horizontalLineToRelative(-53.0f) verticalLineToRelative(-242.0f) lineTo(507.0f, 215.0f) verticalLineToRelative(-56.0f) quadToRelative(0.0f, -20.0f, -13.0f, -33.0f) reflectiveQuadToRelative(-33.0f, -13.0f) quadToRelative(-20.0f, 0.0f, -33.0f, 13.0f) reflectiveQuadToRelative(-13.0f, 33.0f) verticalLineToRelative(56.0f) lineTo(174.0f, 215.0f) verticalLineToRelative(115.0f) quadToRelative(49.15f, 24.817f, 79.575f, 70.186f) quadTo(284.0f, 445.555f, 284.0f, 501.223f) quadToRelative(0.0f, 54.929f, -30.5f, 100.353f) quadTo(223.0f, 647.0f, 174.0f, 671.0f) verticalLineToRelative(115.0f) close() moveTo(461.0f, 503.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QrCode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.QrCode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.QrCode", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(3f, 10f) verticalLineTo(4f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(5f, 9f) horizontalLineToRelative(4f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(4f) close() moveTo(3f, 20f) verticalLineToRelative(-6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(5f, 19f) horizontalLineToRelative(4f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(4f) close() moveTo(13f, 10f) verticalLineTo(4f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(15f, 9f) horizontalLineToRelative(4f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(4f) close() moveTo(19f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(13f, 15f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(15f, 17f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(13f, 19f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(15f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(17f, 19f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(17f, 15f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 17f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } }.build() } val Icons.TwoTone.QrCode: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.QrCode", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(3f, 10f) verticalLineTo(4f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(5f, 9f) horizontalLineToRelative(4f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(4f) close() moveTo(3f, 20f) verticalLineToRelative(-6f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) curveToRelative(-0.192f, -0.192f, -0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(5f, 19f) horizontalLineToRelative(4f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(4f) close() moveTo(13f, 10f) verticalLineTo(4f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) verticalLineToRelative(6f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) reflectiveCurveToRelative(-0.429f, 0.287f, -0.712f, 0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(15f, 9f) horizontalLineToRelative(4f) verticalLineToRelative(-4f) horizontalLineToRelative(-4f) verticalLineToRelative(4f) close() moveTo(19f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(13f, 15f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(15f, 17f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(13f, 19f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(15f, 21f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(17f, 19f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(17f, 15f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() moveTo(19f, 17f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 5f) horizontalLineToRelative(4f) verticalLineToRelative(4f) horizontalLineToRelative(-4f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 15f) horizontalLineToRelative(4f) verticalLineToRelative(4f) horizontalLineToRelative(-4f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(15f, 5f) horizontalLineToRelative(4f) verticalLineToRelative(4f) horizontalLineToRelative(-4f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityHigh.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.QualityHigh: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.QualityHigh", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(590f, 600f) verticalLineToRelative(30f) quadToRelative(0f, 13f, 8.5f, 21.5f) reflectiveQuadTo(620f, 660f) quadToRelative(13f, 0f, 21.5f, -8.5f) reflectiveQuadTo(650f, 630f) verticalLineToRelative(-30f) horizontalLineToRelative(30f) quadToRelative(17f, 0f, 28.5f, -11.5f) reflectiveQuadTo(720f, 560f) verticalLineToRelative(-160f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(680f, 360f) lineTo(560f, 360f) quadToRelative(-17f, 0f, -28.5f, 11.5f) reflectiveQuadTo(520f, 400f) verticalLineToRelative(160f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(560f, 600f) horizontalLineToRelative(30f) close() moveTo(300f, 520f) horizontalLineToRelative(80f) verticalLineToRelative(50f) quadToRelative(0f, 13f, 8.5f, 21.5f) reflectiveQuadTo(410f, 600f) quadToRelative(13f, 0f, 21.5f, -8.5f) reflectiveQuadTo(440f, 570f) verticalLineToRelative(-180f) quadToRelative(0f, -13f, -8.5f, -21.5f) reflectiveQuadTo(410f, 360f) quadToRelative(-13f, 0f, -21.5f, 8.5f) reflectiveQuadTo(380f, 390f) verticalLineToRelative(70f) horizontalLineToRelative(-80f) verticalLineToRelative(-70f) quadToRelative(0f, -13f, -8.5f, -21.5f) reflectiveQuadTo(270f, 360f) quadToRelative(-13f, 0f, -21.5f, 8.5f) reflectiveQuadTo(240f, 390f) verticalLineToRelative(180f) quadToRelative(0f, 13f, 8.5f, 21.5f) reflectiveQuadTo(270f, 600f) quadToRelative(13f, 0f, 21.5f, -8.5f) reflectiveQuadTo(300f, 570f) verticalLineToRelative(-50f) close() moveTo(580f, 540f) verticalLineToRelative(-120f) horizontalLineToRelative(80f) verticalLineToRelative(120f) horizontalLineToRelative(-80f) close() moveTo(160f, 800f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(80f, 720f) verticalLineToRelative(-480f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(160f, 160f) horizontalLineToRelative(640f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(880f, 240f) verticalLineToRelative(480f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(800f, 800f) lineTo(160f, 800f) close() moveTo(160f, 720f) horizontalLineToRelative(640f) verticalLineToRelative(-480f) lineTo(160f, 240f) verticalLineToRelative(480f) close() moveTo(160f, 720f) verticalLineToRelative(-480f) verticalLineToRelative(480f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityLow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.QualityLow: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.QualityLow", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(17.592f, 9.287f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) horizontalLineToRelative(-3f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.713f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.713f) verticalLineToRelative(4f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.713f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) horizontalLineToRelative(0.75f) verticalLineToRelative(0.75f) curveToRelative(0f, 0.217f, 0.071f, 0.396f, 0.213f, 0.537f) reflectiveCurveToRelative(0.321f, 0.213f, 0.537f, 0.213f) reflectiveCurveToRelative(0.396f, -0.071f, 0.537f, -0.213f) reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.537f) verticalLineToRelative(-0.75f) horizontalLineToRelative(0.75f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) verticalLineToRelative(-4f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) close() moveTo(16.38f, 13.5f) horizontalLineToRelative(-2f) verticalLineToRelative(-3f) horizontalLineToRelative(2f) verticalLineToRelative(3f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 4.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(6f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(20f, 18f) horizontalLineTo(4f) verticalLineTo(6f) horizontalLineToRelative(16f) verticalLineToRelative(12f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 18f) verticalLineTo(6f) verticalLineToRelative(12f) close() } path(fill = SolidColor(Color.Black)) { moveTo(10.62f, 13.5f) horizontalLineToRelative(-2.74f) verticalLineToRelative(-3.75f) curveToRelative(0f, -0.414f, -0.336f, -0.75f, -0.75f, -0.75f) reflectiveCurveToRelative(-0.75f, 0.336f, -0.75f, 0.75f) verticalLineToRelative(4.5f) curveToRelative(0f, 0.414f, 0.336f, 0.75f, 0.75f, 0.75f) horizontalLineToRelative(3.49f) curveToRelative(0.414f, 0f, 0.75f, -0.336f, 0.75f, -0.75f) reflectiveCurveToRelative(-0.336f, -0.75f, -0.75f, -0.75f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/QualityMedium.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.QualityMedium: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.QualityMedium", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(18.088f, 9.287f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) horizontalLineToRelative(-3f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.713f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.713f) verticalLineToRelative(4f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.713f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) horizontalLineToRelative(0.75f) verticalLineToRelative(0.75f) curveToRelative(0f, 0.217f, 0.071f, 0.396f, 0.213f, 0.537f) reflectiveCurveToRelative(0.321f, 0.213f, 0.537f, 0.213f) reflectiveCurveToRelative(0.396f, -0.071f, 0.537f, -0.213f) reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.537f) verticalLineToRelative(-0.75f) horizontalLineToRelative(0.75f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) verticalLineToRelative(-4f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) close() moveTo(16.875f, 13.5f) horizontalLineToRelative(-2f) verticalLineToRelative(-3f) horizontalLineToRelative(2f) verticalLineToRelative(3f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 4.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(6f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(20f, 18f) horizontalLineTo(4f) verticalLineTo(6f) horizontalLineToRelative(16f) verticalLineToRelative(12f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 18f) verticalLineTo(6f) verticalLineToRelative(12f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.125f, 10.5f) horizontalLineToRelative(1f) verticalLineToRelative(2.25f) curveToRelative(0f, 0.217f, 0.071f, 0.396f, 0.213f, 0.538f) reflectiveCurveToRelative(0.321f, 0.213f, 0.538f, 0.213f) reflectiveCurveToRelative(0.396f, -0.071f, 0.538f, -0.213f) reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) verticalLineToRelative(-2.25f) horizontalLineToRelative(1f) verticalLineToRelative(3.75f) curveToRelative(0f, 0.217f, 0.071f, 0.396f, 0.213f, 0.538f) reflectiveCurveToRelative(0.321f, 0.213f, 0.538f, 0.213f) reflectiveCurveToRelative(0.396f, -0.071f, 0.538f, -0.213f) reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) verticalLineToRelative(-4.25f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-4.5f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(4.25f) curveToRelative(0f, 0.217f, 0.071f, 0.396f, 0.213f, 0.538f) reflectiveCurveToRelative(0.321f, 0.213f, 0.538f, 0.213f) reflectiveCurveToRelative(0.396f, -0.071f, 0.538f, -0.213f) reflectiveCurveToRelative(0.213f, -0.321f, 0.213f, -0.538f) verticalLineToRelative(-3.75f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rabbit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Rabbit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Rabbit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(18.05f, 21f) lineTo(15.32f, 16.26f) curveTo(15.32f, 14.53f, 14.25f, 13.42f, 12.95f, 13.42f) curveTo(12.05f, 13.42f, 11.27f, 13.92f, 10.87f, 14.66f) curveTo(11.2f, 14.47f, 11.59f, 14.37f, 12f, 14.37f) curveTo(13.3f, 14.37f, 14.36f, 15.43f, 14.36f, 16.73f) curveTo(14.36f, 18.04f, 13.31f, 19.11f, 12f, 19.11f) horizontalLineTo(15.3f) verticalLineTo(21f) horizontalLineTo(6.79f) curveTo(6.55f, 21f, 6.3f, 20.91f, 6.12f, 20.72f) curveTo(5.75f, 20.35f, 5.75f, 19.75f, 6.12f, 19.38f) verticalLineTo(19.38f) lineTo(6.62f, 18.88f) curveTo(6.28f, 18.73f, 6f, 18.5f, 5.72f, 18.26f) curveTo(5.5f, 18.76f, 5f, 19.11f, 4.42f, 19.11f) curveTo(3.64f, 19.11f, 3f, 18.47f, 3f, 17.68f) curveTo(3f, 16.9f, 3.64f, 16.26f, 4.42f, 16.26f) lineTo(4.89f, 16.34f) verticalLineTo(14.37f) curveTo(4.89f, 11.75f, 7f, 9.63f, 9.63f, 9.63f) horizontalLineTo(9.65f) curveTo(11.77f, 9.64f, 13.42f, 10.47f, 13.42f, 9.16f) curveTo(13.42f, 8.23f, 13.62f, 7.86f, 13.96f, 7.34f) curveTo(13.23f, 7f, 12.4f, 6.79f, 11.53f, 6.79f) curveTo(11f, 6.79f, 10.58f, 6.37f, 10.58f, 5.84f) curveTo(10.58f, 5.41f, 10.86f, 5.05f, 11.25f, 4.93f) lineTo(10.58f, 4.89f) curveTo(10.06f, 4.89f, 9.63f, 4.47f, 9.63f, 3.95f) curveTo(9.63f, 3.42f, 10.06f, 3f, 10.58f, 3f) horizontalLineTo(11.53f) curveTo(13.63f, 3f, 15.47f, 4.15f, 16.46f, 5.85f) lineTo(16.74f, 5.84f) curveTo(17.45f, 5.84f, 18.11f, 6.07f, 18.65f, 6.45f) lineTo(19.1f, 6.83f) curveTo(21.27f, 8.78f, 21f, 10.1f, 21f, 10.11f) curveTo(21f, 11.39f, 19.94f, 12.44f, 18.65f, 12.44f) lineTo(18.16f, 12.39f) verticalLineTo(12.47f) curveTo(18.16f, 13.58f, 17.68f, 14.57f, 16.93f, 15.27f) lineTo(20.24f, 21f) horizontalLineTo(18.05f) moveTo(18.16f, 7.74f) curveTo(17.63f, 7.74f, 17.21f, 8.16f, 17.21f, 8.68f) curveTo(17.21f, 9.21f, 17.63f, 9.63f, 18.16f, 9.63f) curveTo(18.68f, 9.63f, 19.11f, 9.21f, 19.11f, 8.68f) curveTo(19.11f, 8.16f, 18.68f, 7.74f, 18.16f, 7.74f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Resize.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Resize: ImageVector by lazy { Builder( name = "Resize", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(9.0024f, 4.0f) horizontalLineTo(8.0f) verticalLineToRelative(1.0024f) curveTo(8.0f, 4.4488f, 8.4488f, 4.0f, 9.0024f, 4.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.9976f, 4.0f) curveTo(19.5512f, 4.0f, 20.0f, 4.4488f, 20.0f, 5.0024f) verticalLineTo(4.0f) horizontalLineTo(18.9976f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.9976f, 16.0f) horizontalLineTo(20.0f) verticalLineToRelative(-1.0024f) curveTo(20.0f, 15.5512f, 19.5512f, 16.0f, 18.9976f, 16.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.0f, 2.0f) horizontalLineTo(8.0f) curveTo(6.8954f, 2.0f, 6.0f, 2.8954f, 6.0f, 4.0f) verticalLineToRelative(8.0f) horizontalLineTo(4.0f) curveToRelative(-1.1046f, 0.0f, -2.0f, 0.8954f, -2.0f, 2.0f) verticalLineToRelative(6.0f) curveToRelative(0.0f, 1.1046f, 0.8954f, 2.0f, 2.0f, 2.0f) horizontalLineToRelative(6.0f) curveToRelative(1.1046f, 0.0f, 2.0f, -0.8954f, 2.0f, -2.0f) verticalLineToRelative(-2.0f) horizontalLineToRelative(8.0f) curveToRelative(1.1046f, 0.0f, 2.0f, -0.8954f, 2.0f, -2.0f) verticalLineTo(4.0f) curveTo(22.0f, 2.8954f, 21.1046f, 2.0f, 20.0f, 2.0f) close() moveTo(18.3137f, 13.5529f) curveToRelative(-0.2004f, 0.1885f, -0.442f, 0.2828f, -0.7248f, 0.2828f) curveToRelative(-0.2711f, -0.0118f, -0.5038f, -0.109f, -0.6982f, -0.2917f) curveToRelative(-0.1945f, -0.1827f, -0.2917f, -0.4154f, -0.2917f, -0.6982f) verticalLineTo(8.8152f) lineToRelative(-4.5139f, 4.5139f) verticalLineToRelative(-1.0E-4f) lineTo(10.0f, 15.4142f) verticalLineTo(16.0f) verticalLineToRelative(0.7035f) verticalLineToRelative(0.3228f) verticalLineTo(17.75f) verticalLineToRelative(1.2473f) curveTo(10.0f, 19.5511f, 9.5511f, 20.0f, 8.9974f, 20.0f) horizontalLineTo(5.0027f) curveTo(4.4489f, 20.0f, 4.0f, 19.5511f, 4.0f, 18.9973f) verticalLineTo(17.75f) verticalLineToRelative(-2.7474f) curveTo(4.0f, 14.4489f, 4.4489f, 14.0f, 5.0027f, 14.0f) horizontalLineToRelative(1.3095f) horizontalLineToRelative(0.741f) horizontalLineToRelative(0.9415f) horizontalLineTo(8.0f) horizontalLineToRelative(0.5858f) lineToRelative(2.1463f, -2.1463f) horizontalLineToRelative(-1.0E-4f) lineToRelative(4.4527f, -4.4527f) horizontalLineToRelative(-4.0305f) curveToRelative(-0.2828f, 0.0f, -0.5156f, -0.0972f, -0.6982f, -0.2917f) curveToRelative(-0.1827f, -0.1944f, -0.2799f, -0.4272f, -0.2917f, -0.6982f) curveToRelative(0.0f, -0.2828f, 0.0943f, -0.5244f, 0.2828f, -0.7248f) curveToRelative(0.1885f, -0.2003f, 0.4243f, -0.3005f, 0.7071f, -0.3005f) horizontalLineToRelative(6.47f) curveToRelative(0.1414f, 0.0f, 0.2681f, 0.0266f, 0.3801f, 0.0796f) curveToRelative(0.112f, 0.053f, 0.2151f, 0.1266f, 0.3094f, 0.2209f) curveToRelative(0.0942f, 0.0943f, 0.1679f, 0.1974f, 0.2209f, 0.3094f) curveToRelative(0.053f, 0.1119f, 0.0795f, 0.2386f, 0.0795f, 0.3801f) lineToRelative(1.0E-4f, 6.47f) curveTo(18.6143f, 13.1286f, 18.514f, 13.3643f, 18.3137f, 13.5529f) close() } }.build() } val Icons.Outlined.Resize: ImageVector by lazy { Builder( name = "Resize", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.0f, 2.0f) horizontalLineTo(8.0f) curveTo(6.8954f, 2.0f, 6.0f, 2.8954f, 6.0f, 4.0f) verticalLineToRelative(8.0f) horizontalLineTo(4.0f) curveToRelative(-1.1046f, 0.0f, -2.0f, 0.8954f, -2.0f, 2.0f) verticalLineToRelative(6.0f) curveToRelative(0.0f, 1.1046f, 0.8954f, 2.0f, 2.0f, 2.0f) horizontalLineToRelative(6.0f) curveToRelative(1.1046f, 0.0f, 2.0f, -0.8954f, 2.0f, -2.0f) verticalLineToRelative(-2.0f) horizontalLineToRelative(8.0f) curveToRelative(1.1046f, 0.0f, 2.0f, -0.8954f, 2.0f, -2.0f) verticalLineTo(4.0f) curveTo(22.0f, 2.8954f, 21.1046f, 2.0f, 20.0f, 2.0f) close() moveTo(10.0f, 15.4142f) verticalLineToRelative(1.6121f) verticalLineTo(17.75f) verticalLineToRelative(1.2473f) curveTo(10.0f, 19.5511f, 9.5511f, 20.0f, 8.9974f, 20.0f) horizontalLineTo(5.0027f) curveTo(4.4489f, 20.0f, 4.0f, 19.5511f, 4.0f, 18.9973f) verticalLineTo(17.75f) verticalLineToRelative(-2.7474f) curveTo(4.0f, 14.4489f, 4.4489f, 14.0f, 5.0027f, 14.0f) horizontalLineToRelative(2.0505f) horizontalLineToRelative(0.9415f) horizontalLineToRelative(0.5911f) horizontalLineToRelative(0.4116f) curveTo(9.5511f, 14.0f, 10.0f, 14.4489f, 10.0f, 15.0026f) verticalLineTo(15.4142f) close() moveTo(20.0f, 16.0f) horizontalLineToRelative(-8.0f) verticalLineToRelative(-0.3495f) verticalLineTo(14.0f) curveToRelative(0.0f, -0.1779f, -0.0307f, -0.3472f, -0.0743f, -0.5115f) lineToRelative(4.6733f, -4.6733f) verticalLineToRelative(4.0305f) curveToRelative(0.0f, 0.2828f, 0.0972f, 0.5156f, 0.2917f, 0.6982f) curveToRelative(0.1944f, 0.1827f, 0.4272f, 0.2799f, 0.6982f, 0.2917f) curveToRelative(0.2828f, 0.0f, 0.5244f, -0.0943f, 0.7248f, -0.2828f) curveToRelative(0.2003f, -0.1885f, 0.3005f, -0.4243f, 0.3005f, -0.7071f) lineToRelative(-1.0E-4f, -6.47f) curveToRelative(0.0f, -0.1414f, -0.0265f, -0.2681f, -0.0795f, -0.3801f) curveToRelative(-0.053f, -0.112f, -0.1267f, -0.2151f, -0.2209f, -0.3094f) curveTo(18.2194f, 5.592f, 18.1163f, 5.5184f, 18.0043f, 5.4653f) curveToRelative(-0.1119f, -0.053f, -0.2386f, -0.0796f, -0.3801f, -0.0796f) horizontalLineToRelative(-6.47f) curveToRelative(-0.2828f, 0.0f, -0.5186f, 0.1002f, -0.7071f, 0.3005f) curveToRelative(-0.1885f, 0.2004f, -0.2828f, 0.442f, -0.2828f, 0.7248f) curveToRelative(0.0118f, 0.2711f, 0.109f, 0.5038f, 0.2917f, 0.6982f) curveToRelative(0.1827f, 0.1945f, 0.4154f, 0.2917f, 0.6982f, 0.2917f) horizontalLineToRelative(4.0305f) lineToRelative(-4.6733f, 4.6733f) curveTo(10.3472f, 12.0307f, 10.1779f, 12.0f, 10.0f, 12.0f) horizontalLineTo(9.9947f) horizontalLineTo(8.2906f) horizontalLineTo(8.0f) verticalLineTo(4.0f) horizontalLineToRelative(12.0f) verticalLineTo(16.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(9.0024f, 4.0f) horizontalLineTo(8.0f) verticalLineToRelative(1.0024f) curveTo(8.0f, 4.4488f, 8.4488f, 4.0f, 9.0024f, 4.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.9976f, 4.0f) curveTo(19.5512f, 4.0f, 20.0f, 4.4488f, 20.0f, 5.0024f) verticalLineTo(4.0f) horizontalLineTo(18.9976f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.9976f, 16.0f) horizontalLineTo(20.0f) verticalLineToRelative(-1.0024f) curveTo(20.0f, 15.5512f, 19.5512f, 16.0f, 18.9976f, 16.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ResponsiveLayout.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ResponsiveLayout: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.ResponsiveLayout", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(680f, 840f) quadTo(663f, 840f, 651.5f, 828.5f) quadTo(640f, 817f, 640f, 800f) lineTo(640f, 400f) quadTo(640f, 367f, 616.5f, 343.5f) quadTo(593f, 320f, 560f, 320f) lineTo(420f, 320f) quadTo(403f, 320f, 391.5f, 308.5f) quadTo(380f, 297f, 380f, 280f) lineTo(380f, 160f) quadTo(380f, 143f, 391.5f, 131.5f) quadTo(403f, 120f, 420f, 120f) lineTo(800f, 120f) quadTo(817f, 120f, 828.5f, 131.5f) quadTo(840f, 143f, 840f, 160f) lineTo(840f, 800f) quadTo(840f, 817f, 828.5f, 828.5f) quadTo(817f, 840f, 800f, 840f) lineTo(680f, 840f) close() moveTo(420f, 840f) quadTo(403f, 840f, 391.5f, 828.5f) quadTo(380f, 817f, 380f, 800f) lineTo(380f, 440f) quadTo(380f, 423f, 391.5f, 411.5f) quadTo(403f, 400f, 420f, 400f) lineTo(520f, 400f) quadTo(537f, 400f, 548.5f, 411.5f) quadTo(560f, 423f, 560f, 440f) lineTo(560f, 800f) quadTo(560f, 817f, 548.5f, 828.5f) quadTo(537f, 840f, 520f, 840f) lineTo(420f, 840f) close() moveTo(160f, 840f) quadTo(143f, 840f, 131.5f, 828.5f) quadTo(120f, 817f, 120f, 800f) lineTo(120f, 440f) quadTo(120f, 423f, 131.5f, 411.5f) quadTo(143f, 400f, 160f, 400f) lineTo(260f, 400f) quadTo(277f, 400f, 288.5f, 411.5f) quadTo(300f, 423f, 300f, 440f) lineTo(300f, 800f) quadTo(300f, 817f, 288.5f, 828.5f) quadTo(277f, 840f, 260f, 840f) lineTo(160f, 840f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Robot.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Robot: ImageVector by lazy { Builder( name = "Robot", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.0f, 4.0f) horizontalLineTo(18.0f) verticalLineTo(3.0f) horizontalLineTo(20.5f) curveTo(20.78f, 3.0f, 21.0f, 3.22f, 21.0f, 3.5f) verticalLineTo(5.5f) curveTo(21.0f, 5.78f, 20.78f, 6.0f, 20.5f, 6.0f) horizontalLineTo(20.0f) verticalLineTo(7.0f) horizontalLineTo(19.0f) verticalLineTo(5.0f) horizontalLineTo(20.0f) verticalLineTo(4.0f) moveTo(19.0f, 9.0f) horizontalLineTo(20.0f) verticalLineTo(8.0f) horizontalLineTo(19.0f) verticalLineTo(9.0f) moveTo(17.0f, 3.0f) horizontalLineTo(16.0f) verticalLineTo(7.0f) horizontalLineTo(17.0f) verticalLineTo(3.0f) moveTo(23.0f, 15.0f) verticalLineTo(18.0f) curveTo(23.0f, 18.55f, 22.55f, 19.0f, 22.0f, 19.0f) horizontalLineTo(21.0f) verticalLineTo(20.0f) curveTo(21.0f, 21.11f, 20.11f, 22.0f, 19.0f, 22.0f) horizontalLineTo(5.0f) curveTo(3.9f, 22.0f, 3.0f, 21.11f, 3.0f, 20.0f) verticalLineTo(19.0f) horizontalLineTo(2.0f) curveTo(1.45f, 19.0f, 1.0f, 18.55f, 1.0f, 18.0f) verticalLineTo(15.0f) curveTo(1.0f, 14.45f, 1.45f, 14.0f, 2.0f, 14.0f) horizontalLineTo(3.0f) curveTo(3.0f, 10.13f, 6.13f, 7.0f, 10.0f, 7.0f) horizontalLineTo(11.0f) verticalLineTo(5.73f) curveTo(10.4f, 5.39f, 10.0f, 4.74f, 10.0f, 4.0f) curveTo(10.0f, 2.9f, 10.9f, 2.0f, 12.0f, 2.0f) reflectiveCurveTo(14.0f, 2.9f, 14.0f, 4.0f) curveTo(14.0f, 4.74f, 13.6f, 5.39f, 13.0f, 5.73f) verticalLineTo(7.0f) horizontalLineTo(14.0f) curveTo(14.34f, 7.0f, 14.67f, 7.03f, 15.0f, 7.08f) verticalLineTo(10.0f) horizontalLineTo(19.74f) curveTo(20.53f, 11.13f, 21.0f, 12.5f, 21.0f, 14.0f) horizontalLineTo(22.0f) curveTo(22.55f, 14.0f, 23.0f, 14.45f, 23.0f, 15.0f) moveTo(10.0f, 15.5f) curveTo(10.0f, 14.12f, 8.88f, 13.0f, 7.5f, 13.0f) reflectiveCurveTo(5.0f, 14.12f, 5.0f, 15.5f) reflectiveCurveTo(6.12f, 18.0f, 7.5f, 18.0f) reflectiveCurveTo(10.0f, 16.88f, 10.0f, 15.5f) moveTo(19.0f, 15.5f) curveTo(19.0f, 14.12f, 17.88f, 13.0f, 16.5f, 13.0f) reflectiveCurveTo(14.0f, 14.12f, 14.0f, 15.5f) reflectiveCurveTo(15.12f, 18.0f, 16.5f, 18.0f) reflectiveCurveTo(19.0f, 16.88f, 19.0f, 15.5f) moveTo(17.0f, 8.0f) horizontalLineTo(16.0f) verticalLineTo(9.0f) horizontalLineTo(17.0f) verticalLineTo(8.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/RobotExcited.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.RobotExcited: ImageVector by lazy { ImageVector.Builder( name = "Robot Excited", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(22.0f, 14.0f) horizontalLineTo(21.0f) curveTo(21.0f, 10.13f, 17.87f, 7.0f, 14.0f, 7.0f) horizontalLineTo(13.0f) verticalLineTo(5.73f) curveTo(13.6f, 5.39f, 14.0f, 4.74f, 14.0f, 4.0f) curveTo(14.0f, 2.9f, 13.11f, 2.0f, 12.0f, 2.0f) reflectiveCurveTo(10.0f, 2.9f, 10.0f, 4.0f) curveTo(10.0f, 4.74f, 10.4f, 5.39f, 11.0f, 5.73f) verticalLineTo(7.0f) horizontalLineTo(10.0f) curveTo(6.13f, 7.0f, 3.0f, 10.13f, 3.0f, 14.0f) horizontalLineTo(2.0f) curveTo(1.45f, 14.0f, 1.0f, 14.45f, 1.0f, 15.0f) verticalLineTo(18.0f) curveTo(1.0f, 18.55f, 1.45f, 19.0f, 2.0f, 19.0f) horizontalLineTo(3.0f) verticalLineTo(20.0f) curveTo(3.0f, 21.11f, 3.9f, 22.0f, 5.0f, 22.0f) horizontalLineTo(19.0f) curveTo(20.11f, 22.0f, 21.0f, 21.11f, 21.0f, 20.0f) verticalLineTo(19.0f) horizontalLineTo(22.0f) curveTo(22.55f, 19.0f, 23.0f, 18.55f, 23.0f, 18.0f) verticalLineTo(15.0f) curveTo(23.0f, 14.45f, 22.55f, 14.0f, 22.0f, 14.0f) moveTo(21.0f, 17.0f) horizontalLineTo(19.0f) verticalLineTo(20.0f) horizontalLineTo(5.0f) verticalLineTo(17.0f) horizontalLineTo(3.0f) verticalLineTo(16.0f) horizontalLineTo(5.0f) verticalLineTo(14.0f) curveTo(5.0f, 11.24f, 7.24f, 9.0f, 10.0f, 9.0f) horizontalLineTo(14.0f) curveTo(16.76f, 9.0f, 19.0f, 11.24f, 19.0f, 14.0f) verticalLineTo(16.0f) horizontalLineTo(21.0f) verticalLineTo(17.0f) moveTo(8.5f, 13.5f) lineTo(10.86f, 15.86f) lineTo(9.68f, 17.04f) lineTo(8.5f, 15.86f) lineTo(7.32f, 17.04f) lineTo(6.14f, 15.86f) lineTo(8.5f, 13.5f) moveTo(15.5f, 13.5f) lineTo(17.86f, 15.86f) lineTo(16.68f, 17.04f) lineTo(15.5f, 15.86f) lineTo(14.32f, 17.04f) lineTo(13.14f, 15.86f) lineTo(15.5f, 13.5f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Rotate90Cw.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Rotate90Cw: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rotate90Cw", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11f, 22f) curveToRelative(-2.5f, 0f, -4.625f, -0.875f, -6.375f, -2.625f) reflectiveCurveToRelative(-2.625f, -3.875f, -2.625f, -6.375f) reflectiveCurveToRelative(0.879f, -4.625f, 2.638f, -6.375f) curveToRelative(1.758f, -1.75f, 3.888f, -2.625f, 6.388f, -2.625f) horizontalLineToRelative(0.175f) lineToRelative(-0.9f, -0.9f) curveToRelative(-0.183f, -0.183f, -0.275f, -0.417f, -0.275f, -0.7f) reflectiveCurveToRelative(0.092f, -0.517f, 0.275f, -0.7f) reflectiveCurveToRelative(0.417f, -0.275f, 0.7f, -0.275f) reflectiveCurveToRelative(0.517f, 0.092f, 0.7f, 0.275f) lineToRelative(2.6f, 2.6f) curveToRelative(0.083f, 0.083f, 0.15f, 0.188f, 0.2f, 0.313f) reflectiveCurveToRelative(0.075f, 0.254f, 0.075f, 0.387f) reflectiveCurveToRelative(-0.025f, 0.262f, -0.075f, 0.387f) reflectiveCurveToRelative(-0.117f, 0.229f, -0.2f, 0.313f) lineToRelative(-2.6f, 2.6f) curveToRelative(-0.183f, 0.183f, -0.417f, 0.275f, -0.7f, 0.275f) reflectiveCurveToRelative(-0.517f, -0.092f, -0.7f, -0.275f) reflectiveCurveToRelative(-0.275f, -0.417f, -0.275f, -0.7f) reflectiveCurveToRelative(0.092f, -0.517f, 0.275f, -0.7f) lineToRelative(0.9f, -0.9f) horizontalLineToRelative(-0.175f) curveToRelative(-1.95f, 0f, -3.608f, 0.679f, -4.975f, 2.037f) curveToRelative(-1.367f, 1.358f, -2.05f, 3.013f, -2.05f, 4.963f) reflectiveCurveToRelative(0.679f, 3.604f, 2.037f, 4.963f) curveToRelative(1.358f, 1.358f, 3.013f, 2.037f, 4.963f, 2.037f) curveToRelative(0.483f, 0f, 0.954f, -0.046f, 1.413f, -0.138f) curveToRelative(0.458f, -0.092f, 0.904f, -0.229f, 1.337f, -0.412f) curveToRelative(0.25f, -0.117f, 0.5f, -0.121f, 0.75f, -0.013f) reflectiveCurveToRelative(0.425f, 0.287f, 0.525f, 0.538f) reflectiveCurveToRelative(0.108f, 0.504f, 0.025f, 0.762f) curveToRelative(-0.083f, 0.258f, -0.25f, 0.438f, -0.5f, 0.538f) curveToRelative(-0.567f, 0.233f, -1.146f, 0.412f, -1.737f, 0.538f) curveToRelative(-0.592f, 0.125f, -1.196f, 0.188f, -1.813f, 0.188f) close() moveTo(16.3f, 18.3f) lineToRelative(-4.6f, -4.6f) curveToRelative(-0.1f, -0.1f, -0.171f, -0.208f, -0.213f, -0.325f) reflectiveCurveToRelative(-0.063f, -0.242f, -0.063f, -0.375f) reflectiveCurveToRelative(0.021f, -0.258f, 0.063f, -0.375f) reflectiveCurveToRelative(0.112f, -0.225f, 0.213f, -0.325f) lineToRelative(4.6f, -4.6f) curveToRelative(0.1f, -0.1f, 0.208f, -0.171f, 0.325f, -0.213f) reflectiveCurveToRelative(0.242f, -0.063f, 0.375f, -0.063f) reflectiveCurveToRelative(0.258f, 0.021f, 0.375f, 0.063f) reflectiveCurveToRelative(0.225f, 0.112f, 0.325f, 0.213f) lineToRelative(4.6f, 4.6f) curveToRelative(0.1f, 0.1f, 0.171f, 0.208f, 0.213f, 0.325f) reflectiveCurveToRelative(0.063f, 0.242f, 0.063f, 0.375f) reflectiveCurveToRelative(-0.021f, 0.258f, -0.063f, 0.375f) reflectiveCurveToRelative(-0.112f, 0.225f, -0.213f, 0.325f) lineToRelative(-4.6f, 4.6f) curveToRelative(-0.1f, 0.1f, -0.208f, 0.171f, -0.325f, 0.213f) reflectiveCurveToRelative(-0.242f, 0.063f, -0.375f, 0.063f) reflectiveCurveToRelative(-0.258f, -0.021f, -0.375f, -0.063f) reflectiveCurveToRelative(-0.225f, -0.112f, -0.325f, -0.213f) close() moveTo(17f, 16.15f) lineToRelative(3.15f, -3.15f) lineToRelative(-3.15f, -3.15f) lineToRelative(-3.15f, 3.15f) lineToRelative(3.15f, 3.15f) close() } }.build() } val Icons.TwoTone.Rotate90Cw: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoToneRotate90Cw", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11f, 22f) curveToRelative(-2.5f, 0f, -4.625f, -0.875f, -6.375f, -2.625f) reflectiveCurveToRelative(-2.625f, -3.875f, -2.625f, -6.375f) reflectiveCurveToRelative(0.879f, -4.625f, 2.638f, -6.375f) curveToRelative(1.758f, -1.75f, 3.888f, -2.625f, 6.388f, -2.625f) horizontalLineToRelative(0.175f) lineToRelative(-0.9f, -0.9f) curveToRelative(-0.183f, -0.183f, -0.275f, -0.417f, -0.275f, -0.7f) reflectiveCurveToRelative(0.092f, -0.517f, 0.275f, -0.7f) reflectiveCurveToRelative(0.417f, -0.275f, 0.7f, -0.275f) reflectiveCurveToRelative(0.517f, 0.092f, 0.7f, 0.275f) lineToRelative(2.6f, 2.6f) curveToRelative(0.083f, 0.083f, 0.15f, 0.188f, 0.2f, 0.313f) reflectiveCurveToRelative(0.075f, 0.254f, 0.075f, 0.387f) reflectiveCurveToRelative(-0.025f, 0.262f, -0.075f, 0.387f) reflectiveCurveToRelative(-0.117f, 0.229f, -0.2f, 0.313f) lineToRelative(-2.6f, 2.6f) curveToRelative(-0.183f, 0.183f, -0.417f, 0.275f, -0.7f, 0.275f) reflectiveCurveToRelative(-0.517f, -0.092f, -0.7f, -0.275f) reflectiveCurveToRelative(-0.275f, -0.417f, -0.275f, -0.7f) reflectiveCurveToRelative(0.092f, -0.517f, 0.275f, -0.7f) lineToRelative(0.9f, -0.9f) horizontalLineToRelative(-0.175f) curveToRelative(-1.95f, 0f, -3.608f, 0.679f, -4.975f, 2.037f) curveToRelative(-1.367f, 1.358f, -2.05f, 3.013f, -2.05f, 4.963f) reflectiveCurveToRelative(0.679f, 3.604f, 2.037f, 4.963f) curveToRelative(1.358f, 1.358f, 3.013f, 2.037f, 4.963f, 2.037f) curveToRelative(0.483f, 0f, 0.954f, -0.046f, 1.413f, -0.138f) curveToRelative(0.458f, -0.092f, 0.904f, -0.229f, 1.337f, -0.412f) curveToRelative(0.25f, -0.117f, 0.5f, -0.121f, 0.75f, -0.013f) reflectiveCurveToRelative(0.425f, 0.287f, 0.525f, 0.538f) reflectiveCurveToRelative(0.108f, 0.504f, 0.025f, 0.762f) curveToRelative(-0.083f, 0.258f, -0.25f, 0.438f, -0.5f, 0.538f) curveToRelative(-0.567f, 0.233f, -1.146f, 0.412f, -1.737f, 0.538f) curveToRelative(-0.592f, 0.125f, -1.196f, 0.188f, -1.813f, 0.188f) close() } path(fill = SolidColor(Color.Black)) { moveTo(22.513f, 12.625f) curveToRelative(-0.042f, -0.117f, -0.112f, -0.225f, -0.213f, -0.325f) lineToRelative(-4.6f, -4.6f) curveToRelative(-0.1f, -0.1f, -0.208f, -0.171f, -0.325f, -0.213f) curveToRelative(-0.117f, -0.042f, -0.242f, -0.063f, -0.375f, -0.063f) reflectiveCurveToRelative(-0.258f, 0.021f, -0.375f, 0.063f) curveToRelative(-0.117f, 0.042f, -0.225f, 0.112f, -0.325f, 0.213f) lineToRelative(-4.6f, 4.6f) curveToRelative(-0.1f, 0.1f, -0.171f, 0.208f, -0.213f, 0.325f) curveToRelative(-0.042f, 0.117f, -0.063f, 0.242f, -0.063f, 0.375f) reflectiveCurveToRelative(0.021f, 0.258f, 0.063f, 0.375f) curveToRelative(0.042f, 0.117f, 0.112f, 0.225f, 0.213f, 0.325f) lineToRelative(4.6f, 4.6f) curveToRelative(0.1f, 0.1f, 0.208f, 0.171f, 0.325f, 0.213f) curveToRelative(0.117f, 0.042f, 0.242f, 0.063f, 0.375f, 0.063f) reflectiveCurveToRelative(0.258f, -0.021f, 0.375f, -0.063f) curveToRelative(0.117f, -0.042f, 0.225f, -0.112f, 0.325f, -0.213f) lineToRelative(4.6f, -4.6f) curveToRelative(0.1f, -0.1f, 0.171f, -0.208f, 0.213f, -0.325f) curveToRelative(0.042f, -0.117f, 0.063f, -0.242f, 0.063f, -0.375f) reflectiveCurveToRelative(-0.021f, -0.258f, -0.063f, -0.375f) close() moveTo(17f, 16.15f) lineToRelative(-3.15f, -3.15f) lineToRelative(3.15f, -3.15f) lineToRelative(3.15f, 3.15f) lineToRelative(-3.15f, 3.15f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(13.85f, 13f) lineToRelative(3.15f, -3.15f) lineToRelative(3.15f, 3.15f) lineToRelative(-3.15f, 3.15f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Routine.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Routine: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Routine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(453f, 508f) quadTo(504f, 559f, 567f, 595.5f) quadTo(630f, 632f, 702f, 652f) quadTo(662f, 703f, 604f, 731.5f) quadTo(546f, 760f, 481f, 760f) quadTo(364f, 760f, 282.5f, 678.5f) quadTo(201f, 597f, 201f, 480f) quadTo(201f, 415f, 229.5f, 357f) quadTo(258f, 299f, 309f, 259f) quadTo(329f, 331f, 365.5f, 394f) quadTo(402f, 457f, 453f, 508f) close() moveTo(743f, 580f) quadTo(723f, 575f, 703.5f, 569f) quadTo(684f, 563f, 665f, 555f) quadTo(673f, 537f, 676.5f, 518.5f) quadTo(680f, 500f, 680f, 480f) quadTo(680f, 397f, 621.5f, 338.5f) quadTo(563f, 280f, 480f, 280f) quadTo(460f, 280f, 441.5f, 283.5f) quadTo(423f, 287f, 405f, 295f) quadTo(397f, 276f, 391.5f, 257f) quadTo(386f, 238f, 381f, 218f) quadTo(405f, 209f, 430f, 204.5f) quadTo(455f, 200f, 481f, 200f) quadTo(598f, 200f, 679.5f, 281.5f) quadTo(761f, 363f, 761f, 480f) quadTo(761f, 506f, 756.5f, 531f) quadTo(752f, 556f, 743f, 580f) close() moveTo(440f, 120f) lineTo(440f, 0f) lineTo(520f, 0f) lineTo(520f, 120f) lineTo(440f, 120f) close() moveTo(440f, 960f) lineTo(440f, 840f) lineTo(520f, 840f) lineTo(520f, 960f) lineTo(440f, 960f) close() moveTo(763f, 254f) lineTo(706f, 197f) lineTo(791f, 113f) lineTo(848f, 169f) lineTo(763f, 254f) close() moveTo(169f, 847f) lineTo(112f, 791f) lineTo(197f, 706f) lineTo(254f, 763f) lineTo(169f, 847f) close() moveTo(840f, 520f) lineTo(840f, 440f) lineTo(960f, 440f) lineTo(960f, 520f) lineTo(840f, 520f) close() moveTo(0f, 520f) lineTo(0f, 440f) lineTo(120f, 440f) lineTo(120f, 520f) lineTo(0f, 520f) close() moveTo(791f, 848f) lineTo(706f, 763f) lineTo(763f, 706f) lineTo(847f, 791f) lineTo(791f, 848f) close() moveTo(197f, 254f) lineTo(113f, 169f) lineTo(169f, 112f) lineTo(254f, 197f) lineTo(197f, 254f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SamsungLetter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SamsungLetter: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.SamsungLetter", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF010101))) { moveTo(13.706f, 15.551f) curveToRelative(0.163f, 0.401f, 0.113f, 0.919f, 0.036f, 1.231f) curveToRelative(-0.139f, 0.551f, -0.514f, 1.115f, -1.616f, 1.115f) curveToRelative(-1.042f, 0f, -1.672f, -0.597f, -1.672f, -1.506f) verticalLineToRelative(-1.606f) horizontalLineToRelative(-4.452f) lineToRelative(-0.003f, 1.284f) curveToRelative(0f, 3.699f, 2.913f, 4.817f, 6.034f, 4.817f) curveToRelative(3.002f, 0f, 5.474f, -1.025f, 5.865f, -3.791f) curveToRelative(0.202f, -1.433f, 0.05f, -2.372f, -0.017f, -2.727f) curveToRelative(-0.7f, -3.473f, -6.999f, -4.511f, -7.467f, -6.452f) curveToRelative(-0.08f, -0.331f, -0.056f, -0.687f, -0.017f, -0.876f) curveToRelative(0.116f, -0.527f, 0.478f, -1.111f, 1.516f, -1.111f) curveToRelative(0.969f, 0f, 1.542f, 0.601f, 1.542f, 1.506f) verticalLineToRelative(1.025f) horizontalLineToRelative(4.136f) verticalLineToRelative(-1.164f) curveToRelative(0f, -3.616f, -3.244f, -4.18f, -5.593f, -4.18f) curveToRelative(-2.952f, 0f, -5.364f, 0.975f, -5.805f, 3.675f) curveToRelative(-0.119f, 0.746f, -0.136f, 1.41f, 0.036f, 2.243f) curveToRelative(0.726f, 3.387f, 6.618f, 4.369f, 7.474f, 6.518f) lineToRelative(0.001f, -0.001f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SaveConfirm.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SaveConfirm: ImageVector by lazy { ImageVector.Builder( name = "Outlined.SaveConfirm", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(14f, 12.8f) curveTo(13.5f, 12.31f, 12.78f, 12f, 12f, 12f) curveTo(10.34f, 12f, 9f, 13.34f, 9f, 15f) curveTo(9f, 16.31f, 9.84f, 17.41f, 11f, 17.82f) curveTo(11.07f, 15.67f, 12.27f, 13.8f, 14f, 12.8f) moveTo(11.09f, 19f) horizontalLineTo(5f) verticalLineTo(5f) horizontalLineTo(16.17f) lineTo(19f, 7.83f) verticalLineTo(12.35f) curveTo(19.75f, 12.61f, 20.42f, 13f, 21f, 13.54f) verticalLineTo(7f) lineTo(17f, 3f) horizontalLineTo(5f) curveTo(3.89f, 3f, 3f, 3.9f, 3f, 5f) verticalLineTo(19f) curveTo(3f, 20.1f, 3.89f, 21f, 5f, 21f) horizontalLineTo(11.81f) curveTo(11.46f, 20.39f, 11.21f, 19.72f, 11.09f, 19f) moveTo(6f, 10f) horizontalLineTo(15f) verticalLineTo(6f) horizontalLineTo(6f) verticalLineTo(10f) moveTo(15.75f, 21f) lineTo(13f, 18f) lineTo(14.16f, 16.84f) lineTo(15.75f, 18.43f) lineTo(19.34f, 14.84f) lineTo(20.5f, 16.25f) lineTo(15.75f, 21f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScaleUnbalanced.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ScaleUnbalanced: ImageVector by lazy { ImageVector.Builder( name = "ScaleUnbalanced", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(13f, 20f) verticalLineTo(8.8f) curveTo(13.5f, 8.6f, 14f, 8.3f, 14.3f, 7.9f) lineTo(17.8f, 9.2f) lineTo(14.9f, 16f) curveTo(14.4f, 18f, 15.9f, 19f, 18.4f, 19f) reflectiveCurveTo(22.5f, 18f, 21.9f, 16f) lineTo(19.3f, 9.7f) lineTo(20.2f, 10f) lineTo(20.9f, 8.1f) lineTo(15f, 6f) curveTo(15f, 4.8f, 14.3f, 3.6f, 13f, 3.1f) curveTo(11.8f, 2.6f, 10.5f, 3.1f, 9.7f, 4f) lineTo(3.9f, 2f) lineTo(3.2f, 3.8f) lineTo(4.8f, 4.4f) lineTo(2.1f, 11f) curveTo(1.6f, 13f, 3.1f, 14f, 5.6f, 14f) reflectiveCurveTo(9.7f, 13f, 9.1f, 11f) lineTo(6.6f, 5.1f) lineTo(9f, 6f) curveTo(9f, 7.2f, 9.7f, 8.4f, 11f, 8.9f) verticalLineTo(20f) horizontalLineTo(2f) verticalLineTo(22f) horizontalLineTo(22f) verticalLineTo(20f) horizontalLineTo(13f) moveTo(19.9f, 16f) horizontalLineTo(16.9f) lineTo(18.4f, 12.2f) lineTo(19.9f, 16f) moveTo(7.1f, 11f) horizontalLineTo(4.1f) lineTo(5.6f, 7.2f) lineTo(7.1f, 11f) moveTo(11.1f, 5.7f) curveTo(11.3f, 5.2f, 11.9f, 4.9f, 12.4f, 5.1f) reflectiveCurveTo(13.2f, 5.9f, 13f, 6.4f) reflectiveCurveTo(12.2f, 7.2f, 11.7f, 7f) reflectiveCurveTo(10.9f, 6.2f, 11.1f, 5.7f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Scanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Scanner", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(17.6f, 12f) lineTo(4.45f, 7.25f) curveToRelative(-0.267f, -0.1f, -0.458f, -0.275f, -0.575f, -0.525f) reflectiveCurveToRelative(-0.125f, -0.508f, -0.025f, -0.775f) reflectiveCurveToRelative(0.275f, -0.458f, 0.525f, -0.575f) reflectiveCurveToRelative(0.508f, -0.125f, 0.775f, -0.025f) lineToRelative(14.65f, 5.35f) curveToRelative(0.333f, 0.133f, 0.617f, 0.367f, 0.85f, 0.7f) curveToRelative(0.233f, 0.333f, 0.35f, 0.7f, 0.35f, 1.1f) verticalLineToRelative(5.5f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(5f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineToRelative(-4f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(12.6f) close() moveTo(19f, 18f) verticalLineToRelative(-4f) horizontalLineTo(5f) verticalLineToRelative(4f) horizontalLineToRelative(14f) close() moveTo(11f, 17f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() moveTo(7.713f, 16.712f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.712f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.712f, -0.287f) close() moveTo(5f, 18f) verticalLineToRelative(-4f) verticalLineToRelative(4f) close() } }.build() } val Icons.TwoTone.Scanner: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Scanner", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.65f, 11.4f) curveToRelative(-0.233f, -0.333f, -0.517f, -0.567f, -0.85f, -0.7f) lineTo(5.15f, 5.35f) curveToRelative(-0.267f, -0.1f, -0.525f, -0.092f, -0.775f, 0.025f) reflectiveCurveToRelative(-0.425f, 0.308f, -0.525f, 0.575f) curveToRelative(-0.1f, 0.267f, -0.092f, 0.525f, 0.025f, 0.775f) reflectiveCurveToRelative(0.308f, 0.425f, 0.575f, 0.525f) lineToRelative(13.15f, 4.75f) horizontalLineTo(5f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(4f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(14f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineToRelative(-5.5f) curveToRelative(0f, -0.4f, -0.117f, -0.767f, -0.35f, -1.1f) close() moveTo(19f, 18f) horizontalLineTo(5f) verticalLineToRelative(-4f) horizontalLineToRelative(14f) verticalLineToRelative(4f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11f, 17f) horizontalLineToRelative(6f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-6f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(7.713f, 16.712f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) reflectiveCurveToRelative(-0.096f, -0.521f, -0.287f, -0.712f) reflectiveCurveToRelative(-0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) reflectiveCurveToRelative(0.096f, 0.521f, 0.287f, 0.712f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.712f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.712f, -0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5f, 18f) verticalLineToRelative(-4f) verticalLineToRelative(4f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 14f) horizontalLineToRelative(14f) verticalLineToRelative(4f) horizontalLineToRelative(-14f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Scissors.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.TwoTone.Scissors: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Scissors", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 14f) lineToRelative(-2.35f, 2.35f) curveToRelative(0.133f, 0.25f, 0.225f, 0.517f, 0.275f, 0.8f) reflectiveCurveToRelative(0.075f, 0.567f, 0.075f, 0.85f) curveToRelative(0f, 1.1f, -0.392f, 2.042f, -1.175f, 2.825f) curveToRelative(-0.783f, 0.783f, -1.725f, 1.175f, -2.825f, 1.175f) reflectiveCurveToRelative(-2.042f, -0.392f, -2.825f, -1.175f) curveToRelative(-0.783f, -0.783f, -1.175f, -1.725f, -1.175f, -2.825f) reflectiveCurveToRelative(0.392f, -2.042f, 1.175f, -2.825f) curveToRelative(0.783f, -0.783f, 1.725f, -1.175f, 2.825f, -1.175f) curveToRelative(0.283f, 0f, 0.567f, 0.025f, 0.85f, 0.075f) reflectiveCurveToRelative(0.55f, 0.142f, 0.8f, 0.275f) lineToRelative(2.35f, -2.35f) lineToRelative(-2.35f, -2.35f) curveToRelative(-0.25f, 0.133f, -0.517f, 0.225f, -0.8f, 0.275f) reflectiveCurveToRelative(-0.567f, 0.075f, -0.85f, 0.075f) curveToRelative(-1.1f, 0f, -2.042f, -0.392f, -2.825f, -1.175f) curveToRelative(-0.783f, -0.783f, -1.175f, -1.725f, -1.175f, -2.825f) reflectiveCurveToRelative(0.392f, -2.042f, 1.175f, -2.825f) curveToRelative(0.783f, -0.783f, 1.725f, -1.175f, 2.825f, -1.175f) reflectiveCurveToRelative(2.042f, 0.392f, 2.825f, 1.175f) reflectiveCurveToRelative(1.175f, 1.725f, 1.175f, 2.825f) curveToRelative(0f, 0.283f, -0.025f, 0.567f, -0.075f, 0.85f) reflectiveCurveToRelative(-0.142f, 0.55f, -0.275f, 0.8f) lineToRelative(10.95f, 10.95f) curveToRelative(0.45f, 0.45f, 0.55f, 0.962f, 0.3f, 1.538f) reflectiveCurveToRelative(-0.692f, 0.863f, -1.325f, 0.863f) curveToRelative(-0.183f, 0f, -0.363f, -0.038f, -0.538f, -0.112f) reflectiveCurveToRelative(-0.329f, -0.179f, -0.463f, -0.313f) lineToRelative(-6.575f, -6.575f) close() moveTo(15f, 11f) lineToRelative(-2f, -2f) lineToRelative(5.575f, -5.575f) curveToRelative(0.133f, -0.133f, 0.287f, -0.237f, 0.463f, -0.313f) reflectiveCurveToRelative(0.354f, -0.112f, 0.538f, -0.112f) curveToRelative(0.633f, 0f, 1.071f, 0.292f, 1.313f, 0.875f) reflectiveCurveToRelative(0.138f, 1.1f, -0.313f, 1.55f) lineToRelative(-5.575f, 5.575f) close() moveTo(6f, 8f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.587f) reflectiveCurveToRelative(0.587f, -0.863f, 0.587f, -1.413f) reflectiveCurveToRelative(-0.196f, -1.021f, -0.587f, -1.413f) reflectiveCurveToRelative(-0.863f, -0.587f, -1.413f, -0.587f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.413f, 0.587f) reflectiveCurveToRelative(-0.587f, 0.863f, -0.587f, 1.413f) reflectiveCurveToRelative(0.196f, 1.021f, 0.587f, 1.413f) reflectiveCurveToRelative(0.863f, 0.587f, 1.413f, 0.587f) close() moveTo(12f, 12.5f) curveToRelative(0.133f, 0f, 0.25f, -0.05f, 0.35f, -0.15f) reflectiveCurveToRelative(0.15f, -0.217f, 0.15f, -0.35f) reflectiveCurveToRelative(-0.05f, -0.25f, -0.15f, -0.35f) reflectiveCurveToRelative(-0.217f, -0.15f, -0.35f, -0.15f) reflectiveCurveToRelative(-0.25f, 0.05f, -0.35f, 0.15f) reflectiveCurveToRelative(-0.15f, 0.217f, -0.15f, 0.35f) reflectiveCurveToRelative(0.05f, 0.25f, 0.15f, 0.35f) reflectiveCurveToRelative(0.217f, 0.15f, 0.35f, 0.15f) close() moveTo(6f, 20f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.587f) reflectiveCurveToRelative(0.587f, -0.863f, 0.587f, -1.413f) reflectiveCurveToRelative(-0.196f, -1.021f, -0.587f, -1.413f) reflectiveCurveToRelative(-0.863f, -0.587f, -1.413f, -0.587f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.413f, 0.587f) reflectiveCurveToRelative(-0.587f, 0.863f, -0.587f, 1.413f) reflectiveCurveToRelative(0.196f, 1.021f, 0.587f, 1.413f) reflectiveCurveToRelative(0.863f, 0.587f, 1.413f, 0.587f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6f, 4f) lineTo(6f, 4f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 6f) lineTo(8f, 6f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6f, 8f) lineTo(6f, 8f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 6f) lineTo(4f, 6f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6f, 4f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6f, 16f) lineTo(6f, 16f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8f, 18f) lineTo(8f, 18f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6f, 20f) lineTo(6f, 20f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 18f) lineTo(4f, 18f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 6f, 16f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12.008f, 11.5f) lineTo(12.008f, 11.5f) arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12.508f, 12f) lineTo(12.508f, 12f) arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12.008f, 12.5f) lineTo(12.008f, 12.5f) arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11.508f, 12f) lineTo(11.508f, 12f) arcTo(0.5f, 0.5f, 0f, isMoreThanHalf = false, isPositiveArc = true, 12.008f, 11.5f) close() } }.build() } val Icons.Rounded.Scissors: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Scissors", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 14f) lineToRelative(-2.35f, 2.35f) curveToRelative(0.133f, 0.25f, 0.225f, 0.517f, 0.275f, 0.8f) reflectiveCurveToRelative(0.075f, 0.567f, 0.075f, 0.85f) curveToRelative(0f, 1.1f, -0.392f, 2.042f, -1.175f, 2.825f) curveToRelative(-0.783f, 0.783f, -1.725f, 1.175f, -2.825f, 1.175f) reflectiveCurveToRelative(-2.042f, -0.392f, -2.825f, -1.175f) curveToRelative(-0.783f, -0.783f, -1.175f, -1.725f, -1.175f, -2.825f) reflectiveCurveToRelative(0.392f, -2.042f, 1.175f, -2.825f) curveToRelative(0.783f, -0.783f, 1.725f, -1.175f, 2.825f, -1.175f) curveToRelative(0.283f, 0f, 0.567f, 0.025f, 0.85f, 0.075f) reflectiveCurveToRelative(0.55f, 0.142f, 0.8f, 0.275f) lineToRelative(2.35f, -2.35f) lineToRelative(-2.35f, -2.35f) curveToRelative(-0.25f, 0.133f, -0.517f, 0.225f, -0.8f, 0.275f) reflectiveCurveToRelative(-0.567f, 0.075f, -0.85f, 0.075f) curveToRelative(-1.1f, 0f, -2.042f, -0.392f, -2.825f, -1.175f) curveToRelative(-0.783f, -0.783f, -1.175f, -1.725f, -1.175f, -2.825f) reflectiveCurveToRelative(0.392f, -2.042f, 1.175f, -2.825f) curveToRelative(0.783f, -0.783f, 1.725f, -1.175f, 2.825f, -1.175f) reflectiveCurveToRelative(2.042f, 0.392f, 2.825f, 1.175f) reflectiveCurveToRelative(1.175f, 1.725f, 1.175f, 2.825f) curveToRelative(0f, 0.283f, -0.025f, 0.567f, -0.075f, 0.85f) reflectiveCurveToRelative(-0.142f, 0.55f, -0.275f, 0.8f) lineToRelative(10.95f, 10.95f) curveToRelative(0.45f, 0.45f, 0.55f, 0.962f, 0.3f, 1.538f) reflectiveCurveToRelative(-0.692f, 0.863f, -1.325f, 0.863f) curveToRelative(-0.183f, 0f, -0.363f, -0.038f, -0.538f, -0.112f) reflectiveCurveToRelative(-0.329f, -0.179f, -0.463f, -0.313f) lineToRelative(-6.575f, -6.575f) close() moveTo(15f, 11f) lineToRelative(-2f, -2f) lineToRelative(5.575f, -5.575f) curveToRelative(0.133f, -0.133f, 0.287f, -0.237f, 0.463f, -0.313f) reflectiveCurveToRelative(0.354f, -0.112f, 0.538f, -0.112f) curveToRelative(0.633f, 0f, 1.071f, 0.292f, 1.313f, 0.875f) reflectiveCurveToRelative(0.138f, 1.1f, -0.313f, 1.55f) lineToRelative(-5.575f, 5.575f) close() moveTo(6f, 8f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.587f) reflectiveCurveToRelative(0.587f, -0.863f, 0.587f, -1.413f) reflectiveCurveToRelative(-0.196f, -1.021f, -0.587f, -1.413f) reflectiveCurveToRelative(-0.863f, -0.587f, -1.413f, -0.587f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.413f, 0.587f) reflectiveCurveToRelative(-0.587f, 0.863f, -0.587f, 1.413f) reflectiveCurveToRelative(0.196f, 1.021f, 0.587f, 1.413f) reflectiveCurveToRelative(0.863f, 0.587f, 1.413f, 0.587f) close() moveTo(12f, 12.5f) curveToRelative(0.133f, 0f, 0.25f, -0.05f, 0.35f, -0.15f) reflectiveCurveToRelative(0.15f, -0.217f, 0.15f, -0.35f) reflectiveCurveToRelative(-0.05f, -0.25f, -0.15f, -0.35f) reflectiveCurveToRelative(-0.217f, -0.15f, -0.35f, -0.15f) reflectiveCurveToRelative(-0.25f, 0.05f, -0.35f, 0.15f) reflectiveCurveToRelative(-0.15f, 0.217f, -0.15f, 0.35f) reflectiveCurveToRelative(0.05f, 0.25f, 0.15f, 0.35f) reflectiveCurveToRelative(0.217f, 0.15f, 0.35f, 0.15f) close() moveTo(6f, 20f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.413f, -0.587f) reflectiveCurveToRelative(0.587f, -0.863f, 0.587f, -1.413f) reflectiveCurveToRelative(-0.196f, -1.021f, -0.587f, -1.413f) reflectiveCurveToRelative(-0.863f, -0.587f, -1.413f, -0.587f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.413f, 0.587f) reflectiveCurveToRelative(-0.587f, 0.863f, -0.587f, 1.413f) reflectiveCurveToRelative(0.196f, 1.021f, 0.587f, 1.413f) reflectiveCurveToRelative(0.863f, 0.587f, 1.413f, 0.587f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ScissorsSmall.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ScissorsSmall: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ScissorsSmall", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(18.3f, 20.1f) lineToRelative(-6.3f, -6.3f) lineToRelative(-2.115f, 2.115f) curveToRelative(0.12f, 0.225f, 0.203f, 0.465f, 0.248f, 0.72f) reflectiveCurveToRelative(0.068f, 0.51f, 0.068f, 0.765f) curveToRelative(0f, 0.99f, -0.352f, 1.838f, -1.058f, 2.543f) curveToRelative(-0.705f, 0.705f, -1.553f, 1.058f, -2.543f, 1.058f) reflectiveCurveToRelative(-1.838f, -0.352f, -2.543f, -1.058f) curveToRelative(-0.705f, -0.705f, -1.058f, -1.553f, -1.058f, -2.543f) reflectiveCurveToRelative(0.352f, -1.837f, 1.058f, -2.543f) curveToRelative(0.705f, -0.705f, 1.553f, -1.058f, 2.543f, -1.058f) curveToRelative(0.255f, 0f, 0.51f, 0.023f, 0.765f, 0.068f) reflectiveCurveToRelative(0.495f, 0.127f, 0.72f, 0.248f) lineToRelative(2.115f, -2.115f) lineToRelative(-2.115f, -2.115f) curveToRelative(-0.225f, 0.12f, -0.465f, 0.203f, -0.72f, 0.248f) reflectiveCurveToRelative(-0.51f, 0.068f, -0.765f, 0.068f) curveToRelative(-0.99f, 0f, -1.838f, -0.353f, -2.543f, -1.058f) curveToRelative(-0.705f, -0.705f, -1.058f, -1.553f, -1.058f, -2.543f) reflectiveCurveToRelative(0.352f, -1.838f, 1.058f, -2.543f) curveToRelative(0.705f, -0.705f, 1.553f, -1.058f, 2.543f, -1.058f) reflectiveCurveToRelative(1.837f, 0.353f, 2.543f, 1.058f) reflectiveCurveToRelative(1.058f, 1.553f, 1.058f, 2.543f) curveToRelative(0f, 0.255f, -0.023f, 0.51f, -0.068f, 0.765f) reflectiveCurveToRelative(-0.127f, 0.495f, -0.248f, 0.72f) lineToRelative(11.115f, 11.115f) verticalLineToRelative(0.9f) horizontalLineToRelative(-2.7f) close() moveTo(14.7f, 11.1f) lineToRelative(-1.8f, -1.8f) lineToRelative(5.4f, -5.4f) horizontalLineToRelative(2.7f) verticalLineToRelative(0.9f) lineToRelative(-6.3f, 6.3f) close() moveTo(7.871f, 7.871f) curveToRelative(0.352f, -0.353f, 0.529f, -0.776f, 0.529f, -1.271f) reflectiveCurveToRelative(-0.176f, -0.919f, -0.529f, -1.271f) reflectiveCurveToRelative(-0.776f, -0.529f, -1.271f, -0.529f) reflectiveCurveToRelative(-0.919f, 0.176f, -1.271f, 0.529f) reflectiveCurveToRelative(-0.529f, 0.776f, -0.529f, 1.271f) reflectiveCurveToRelative(0.176f, 0.919f, 0.529f, 1.271f) reflectiveCurveToRelative(0.776f, 0.529f, 1.271f, 0.529f) reflectiveCurveToRelative(0.919f, -0.176f, 1.271f, -0.529f) close() moveTo(12.315f, 12.315f) curveToRelative(0.09f, -0.09f, 0.135f, -0.195f, 0.135f, -0.315f) reflectiveCurveToRelative(-0.045f, -0.225f, -0.135f, -0.315f) reflectiveCurveToRelative(-0.195f, -0.135f, -0.315f, -0.135f) reflectiveCurveToRelative(-0.225f, 0.045f, -0.315f, 0.135f) reflectiveCurveToRelative(-0.135f, 0.195f, -0.135f, 0.315f) reflectiveCurveToRelative(0.045f, 0.225f, 0.135f, 0.315f) reflectiveCurveToRelative(0.195f, 0.135f, 0.315f, 0.135f) reflectiveCurveToRelative(0.225f, -0.045f, 0.315f, -0.135f) close() moveTo(7.871f, 18.671f) curveToRelative(0.352f, -0.353f, 0.529f, -0.776f, 0.529f, -1.271f) reflectiveCurveToRelative(-0.176f, -0.919f, -0.529f, -1.271f) reflectiveCurveToRelative(-0.776f, -0.529f, -1.271f, -0.529f) reflectiveCurveToRelative(-0.919f, 0.176f, -1.271f, 0.529f) reflectiveCurveToRelative(-0.529f, 0.776f, -0.529f, 1.271f) reflectiveCurveToRelative(0.176f, 0.919f, 0.529f, 1.271f) reflectiveCurveToRelative(0.776f, 0.529f, 1.271f, 0.529f) reflectiveCurveToRelative(0.919f, -0.176f, 1.271f, -0.529f) close() } }.build() } val Icons.TwoTone.ScissorsSmall: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ScissorsSmall", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14.7f, 11.1f) lineToRelative(-1.8f, -1.8f) lineToRelative(5.4f, -5.4f) lineToRelative(2.7f, 0f) lineToRelative(0f, 0.9f) lineToRelative(-6.3f, 6.3f) close() } path(fill = SolidColor(Color.Black)) { moveTo(9.885f, 8.085f) curveToRelative(0.12f, -0.225f, 0.203f, -0.465f, 0.247f, -0.72f) reflectiveCurveToRelative(0.068f, -0.51f, 0.068f, -0.765f) curveToRelative(0f, -0.99f, -0.353f, -1.837f, -1.057f, -2.542f) curveToRelative(-0.705f, -0.705f, -1.552f, -1.057f, -2.543f, -1.057f) curveToRelative(-0.99f, 0f, -1.837f, 0.352f, -2.542f, 1.057f) reflectiveCurveToRelative(-1.057f, 1.552f, -1.057f, 2.542f) curveToRelative(0f, 0.99f, 0.352f, 1.838f, 1.057f, 2.543f) curveToRelative(0.705f, 0.705f, 1.552f, 1.057f, 2.542f, 1.057f) curveToRelative(0.255f, 0f, 0.51f, -0.023f, 0.765f, -0.068f) reflectiveCurveToRelative(0.495f, -0.128f, 0.72f, -0.247f) lineToRelative(2.115f, 2.115f) lineToRelative(-2.115f, 2.115f) curveToRelative(-0.225f, -0.12f, -0.465f, -0.203f, -0.72f, -0.247f) reflectiveCurveToRelative(-0.51f, -0.068f, -0.765f, -0.068f) curveToRelative(-0.99f, 0f, -1.837f, 0.353f, -2.542f, 1.057f) curveToRelative(-0.705f, 0.705f, -1.057f, 1.552f, -1.057f, 2.543f) curveToRelative(0f, 0.99f, 0.352f, 1.837f, 1.057f, 2.542f) reflectiveCurveToRelative(1.552f, 1.057f, 2.542f, 1.057f) curveToRelative(0.99f, 0f, 1.838f, -0.352f, 2.543f, -1.057f) curveToRelative(0.705f, -0.705f, 1.057f, -1.552f, 1.057f, -2.542f) curveToRelative(0f, -0.255f, -0.023f, -0.51f, -0.068f, -0.765f) reflectiveCurveToRelative(-0.128f, -0.495f, -0.247f, -0.72f) lineToRelative(2.115f, -2.115f) lineToRelative(6.3f, 6.3f) horizontalLineToRelative(2.7f) verticalLineToRelative(-0.9f) lineToRelative(-11.115f, -11.115f) close() moveTo(7.871f, 7.871f) curveToRelative(-0.353f, 0.352f, -0.776f, 0.529f, -1.271f, 0.529f) reflectiveCurveToRelative(-0.919f, -0.176f, -1.271f, -0.529f) curveToRelative(-0.352f, -0.353f, -0.529f, -0.776f, -0.529f, -1.271f) reflectiveCurveToRelative(0.176f, -0.919f, 0.529f, -1.271f) curveToRelative(0.353f, -0.352f, 0.776f, -0.529f, 1.271f, -0.529f) reflectiveCurveToRelative(0.919f, 0.176f, 1.271f, 0.529f) curveToRelative(0.352f, 0.353f, 0.529f, 0.776f, 0.529f, 1.271f) reflectiveCurveToRelative(-0.176f, 0.919f, -0.529f, 1.271f) close() moveTo(7.871f, 18.671f) curveToRelative(-0.353f, 0.352f, -0.776f, 0.529f, -1.271f, 0.529f) reflectiveCurveToRelative(-0.919f, -0.176f, -1.271f, -0.529f) curveToRelative(-0.352f, -0.353f, -0.529f, -0.776f, -0.529f, -1.271f) reflectiveCurveToRelative(0.176f, -0.919f, 0.529f, -1.271f) curveToRelative(0.353f, -0.352f, 0.776f, -0.529f, 1.271f, -0.529f) reflectiveCurveToRelative(0.919f, 0.176f, 1.271f, 0.529f) curveToRelative(0.352f, 0.353f, 0.529f, 0.776f, 0.529f, 1.271f) reflectiveCurveToRelative(-0.176f, 0.919f, -0.529f, 1.271f) close() moveTo(12.315f, 12.315f) curveToRelative(-0.09f, 0.09f, -0.195f, 0.135f, -0.315f, 0.135f) reflectiveCurveToRelative(-0.225f, -0.045f, -0.315f, -0.135f) reflectiveCurveToRelative(-0.135f, -0.195f, -0.135f, -0.315f) reflectiveCurveToRelative(0.045f, -0.225f, 0.135f, -0.315f) reflectiveCurveToRelative(0.195f, -0.135f, 0.315f, -0.135f) reflectiveCurveToRelative(0.225f, 0.045f, 0.315f, 0.135f) reflectiveCurveToRelative(0.135f, 0.195f, 0.135f, 0.315f) reflectiveCurveToRelative(-0.045f, 0.225f, -0.135f, 0.315f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(7.871f, 7.871f) curveToRelative(0.352f, -0.353f, 0.529f, -0.776f, 0.529f, -1.271f) reflectiveCurveToRelative(-0.176f, -0.919f, -0.529f, -1.271f) reflectiveCurveToRelative(-0.776f, -0.529f, -1.271f, -0.529f) reflectiveCurveToRelative(-0.919f, 0.176f, -1.271f, 0.529f) reflectiveCurveToRelative(-0.529f, 0.776f, -0.529f, 1.271f) reflectiveCurveToRelative(0.176f, 0.919f, 0.529f, 1.271f) reflectiveCurveToRelative(0.776f, 0.529f, 1.271f, 0.529f) reflectiveCurveToRelative(0.919f, -0.176f, 1.271f, -0.529f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12.315f, 12.315f) curveToRelative(0.09f, -0.09f, 0.135f, -0.195f, 0.135f, -0.315f) reflectiveCurveToRelative(-0.045f, -0.225f, -0.135f, -0.315f) reflectiveCurveToRelative(-0.195f, -0.135f, -0.315f, -0.135f) reflectiveCurveToRelative(-0.225f, 0.045f, -0.315f, 0.135f) reflectiveCurveToRelative(-0.135f, 0.195f, -0.135f, 0.315f) reflectiveCurveToRelative(0.045f, 0.225f, 0.135f, 0.315f) reflectiveCurveToRelative(0.195f, 0.135f, 0.315f, 0.135f) reflectiveCurveToRelative(0.225f, -0.045f, 0.315f, -0.135f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(7.871f, 18.671f) curveToRelative(0.352f, -0.353f, 0.529f, -0.776f, 0.529f, -1.271f) reflectiveCurveToRelative(-0.176f, -0.919f, -0.529f, -1.271f) reflectiveCurveToRelative(-0.776f, -0.529f, -1.271f, -0.529f) reflectiveCurveToRelative(-0.919f, 0.176f, -1.271f, 0.529f) reflectiveCurveToRelative(-0.529f, 0.776f, -0.529f, 1.271f) reflectiveCurveToRelative(0.176f, 0.919f, 0.529f, 1.271f) reflectiveCurveToRelative(0.776f, 0.529f, 1.271f, 0.529f) reflectiveCurveToRelative(0.919f, -0.176f, 1.271f, -0.529f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12.9f, 9.3f) lineToRelative(1.8f, 1.8f) lineToRelative(-0.9f, 0.9f) lineToRelative(-1.8f, -1.8f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectAll.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SelectAll: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.SelectAll", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(131.5f, 188.5f) quadTo(120f, 177f, 120f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 120f, 160f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 143f, 200f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 200f, 160f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(291.5f, 188.5f) quadTo(280f, 177f, 280f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(303f, 120f, 320f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(360f, 143f, 360f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(337f, 200f, 320f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(451.5f, 188.5f) quadTo(440f, 177f, 440f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(463f, 120f, 480f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(520f, 143f, 520f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(497f, 200f, 480f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(611.5f, 188.5f) quadTo(600f, 177f, 600f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(623f, 120f, 640f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(680f, 143f, 680f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(657f, 200f, 640f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 188.5f) quadTo(760f, 177f, 760f, 160f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 120f, 800f, 120f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 143f, 840f, 160f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 200f, 800f, 200f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 348.5f) quadTo(120f, 337f, 120f, 320f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 280f, 160f, 280f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 303f, 200f, 320f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 360f, 160f, 360f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 348.5f) quadTo(760f, 337f, 760f, 320f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 280f, 800f, 280f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 303f, 840f, 320f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 360f, 800f, 360f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 508.5f) quadTo(120f, 497f, 120f, 480f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 440f, 160f, 440f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 463f, 200f, 480f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 520f, 160f, 520f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 508.5f) quadTo(760f, 497f, 760f, 480f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 440f, 800f, 440f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 463f, 840f, 480f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 520f, 800f, 520f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 668.5f) quadTo(120f, 657f, 120f, 640f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 600f, 160f, 600f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 623f, 200f, 640f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 680f, 160f, 680f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 668.5f) quadTo(760f, 657f, 760f, 640f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 600f, 800f, 600f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 623f, 840f, 640f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 680f, 800f, 680f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(131.5f, 828.5f) quadTo(120f, 817f, 120f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 760f, 160f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 783f, 200f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 840f, 160f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(291.5f, 828.5f) quadTo(280f, 817f, 280f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(303f, 760f, 320f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(360f, 783f, 360f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(337f, 840f, 320f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(451.5f, 828.5f) quadTo(440f, 817f, 440f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(463f, 760f, 480f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(520f, 783f, 520f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(497f, 840f, 480f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(611.5f, 828.5f) quadTo(600f, 817f, 600f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(623f, 760f, 640f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(680f, 783f, 680f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(657f, 840f, 640f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 828.5f) quadTo(760f, 817f, 760f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 760f, 800f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 783f, 840f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 840f, 800f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(360f, 680f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(280f, 600f) verticalLineToRelative(-240f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(360f, 280f) horizontalLineToRelative(240f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(680f, 360f) verticalLineToRelative(240f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(600f, 680f) lineTo(360f, 680f) close() moveTo(360f, 600f) horizontalLineToRelative(240f) verticalLineToRelative(-240f) lineTo(360f, 360f) verticalLineToRelative(240f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SelectInverse.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.SelectInverse: ImageVector by lazy { ImageVector.Builder( name = "Rounded.SelectInverse", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(5f, 3f) horizontalLineTo(7f) verticalLineTo(5f) horizontalLineTo(9f) verticalLineTo(3f) horizontalLineTo(11f) verticalLineTo(5f) horizontalLineTo(13f) verticalLineTo(3f) horizontalLineTo(15f) verticalLineTo(5f) horizontalLineTo(17f) verticalLineTo(3f) horizontalLineTo(19f) verticalLineTo(5f) horizontalLineTo(21f) verticalLineTo(7f) horizontalLineTo(19f) verticalLineTo(9f) horizontalLineTo(21f) verticalLineTo(11f) horizontalLineTo(19f) verticalLineTo(13f) horizontalLineTo(21f) verticalLineTo(15f) horizontalLineTo(19f) verticalLineTo(17f) horizontalLineTo(21f) verticalLineTo(19f) horizontalLineTo(19f) verticalLineTo(21f) horizontalLineTo(17f) verticalLineTo(19f) horizontalLineTo(15f) verticalLineTo(21f) horizontalLineTo(13f) verticalLineTo(19f) horizontalLineTo(11f) verticalLineTo(21f) horizontalLineTo(9f) verticalLineTo(19f) horizontalLineTo(7f) verticalLineTo(21f) horizontalLineTo(5f) verticalLineTo(19f) horizontalLineTo(3f) verticalLineTo(17f) horizontalLineTo(5f) verticalLineTo(15f) horizontalLineTo(3f) verticalLineTo(13f) horizontalLineTo(5f) verticalLineTo(11f) horizontalLineTo(3f) verticalLineTo(9f) horizontalLineTo(5f) verticalLineTo(7f) horizontalLineTo(3f) verticalLineTo(5f) horizontalLineTo(5f) verticalLineTo(3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ServiceToolbox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ServiceToolbox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.ServiceToolbox", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(160f, 800f) quadTo(127f, 800f, 103.5f, 776.5f) quadTo(80f, 753f, 80f, 720f) lineTo(80f, 560f) lineTo(280f, 560f) lineTo(280f, 560f) quadTo(280f, 577f, 291.5f, 588.5f) quadTo(303f, 600f, 320f, 600f) quadTo(336f, 600f, 342.5f, 585.5f) quadTo(349f, 571f, 360f, 560f) lineTo(360f, 560f) lineTo(600f, 560f) lineTo(600f, 560f) quadTo(600f, 577f, 611.5f, 588.5f) quadTo(623f, 600f, 640f, 600f) quadTo(656f, 600f, 662.5f, 585.5f) quadTo(669f, 571f, 680f, 560f) lineTo(680f, 560f) lineTo(880f, 560f) lineTo(880f, 720f) quadTo(880f, 753f, 856.5f, 776.5f) quadTo(833f, 800f, 800f, 800f) lineTo(160f, 800f) close() moveTo(97f, 480f) lineTo(180f, 288f) quadTo(189f, 266f, 209f, 253f) quadTo(229f, 240f, 252f, 240f) lineTo(280f, 240f) lineTo(280f, 200f) quadTo(280f, 167f, 303.5f, 143.5f) quadTo(327f, 120f, 360f, 120f) lineTo(600f, 120f) quadTo(633f, 120f, 656.5f, 143.5f) quadTo(680f, 167f, 680f, 200f) lineTo(680f, 240f) lineTo(708f, 240f) quadTo(731f, 240f, 751f, 253f) quadTo(771f, 266f, 780f, 288f) lineTo(863f, 480f) lineTo(680f, 480f) lineTo(680f, 480f) quadTo(680f, 463f, 668.5f, 451.5f) quadTo(657f, 440f, 640f, 440f) quadTo(624f, 440f, 617.5f, 454.5f) quadTo(611f, 469f, 600f, 480f) lineTo(600f, 480f) lineTo(360f, 480f) lineTo(360f, 480f) quadTo(360f, 463f, 348.5f, 451.5f) quadTo(337f, 440f, 320f, 440f) quadTo(304f, 440f, 297.5f, 454.5f) quadTo(291f, 469f, 280f, 480f) lineTo(280f, 480f) lineTo(97f, 480f) close() moveTo(360f, 240f) lineTo(600f, 240f) lineTo(600f, 200f) lineTo(360f, 200f) lineTo(360f, 240f) close() } }.build() } val Icons.Outlined.ServiceToolbox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ServiceToolbox", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 240f) lineTo(280f, 200f) quadTo(280f, 167f, 303.5f, 143.5f) quadTo(327f, 120f, 360f, 120f) lineTo(600f, 120f) quadTo(633f, 120f, 656.5f, 143.5f) quadTo(680f, 167f, 680f, 200f) lineTo(680f, 240f) lineTo(708f, 240f) quadTo(731f, 240f, 751f, 253f) quadTo(771f, 266f, 780f, 288f) lineTo(874f, 504f) quadTo(877f, 512f, 878.5f, 520f) quadTo(880f, 528f, 880f, 536f) lineTo(880f, 720f) quadTo(880f, 753f, 856.5f, 776.5f) quadTo(833f, 800f, 800f, 800f) lineTo(160f, 800f) quadTo(127f, 800f, 103.5f, 776.5f) quadTo(80f, 753f, 80f, 720f) lineTo(80f, 536f) quadTo(80f, 528f, 81.5f, 520f) quadTo(83f, 512f, 86f, 504f) lineTo(180f, 288f) quadTo(189f, 266f, 209f, 253f) quadTo(229f, 240f, 252f, 240f) lineTo(280f, 240f) close() moveTo(360f, 240f) lineTo(600f, 240f) lineTo(600f, 200f) lineTo(360f, 200f) lineTo(360f, 240f) close() moveTo(280f, 480f) lineTo(280f, 479f) quadTo(280f, 462f, 291.5f, 450.5f) quadTo(303f, 439f, 320f, 439f) quadTo(337f, 439f, 348.5f, 450.5f) quadTo(360f, 462f, 360f, 479f) lineTo(360f, 480f) lineTo(600f, 480f) lineTo(600f, 479f) quadTo(600f, 462f, 611.5f, 450.5f) quadTo(623f, 439f, 640f, 439f) quadTo(657f, 439f, 668.5f, 450.5f) quadTo(680f, 462f, 680f, 479f) lineTo(680f, 480f) lineTo(776f, 480f) lineTo(708f, 320f) lineTo(252f, 320f) lineTo(184f, 480f) lineTo(280f, 480f) close() moveTo(280f, 560f) lineTo(160f, 560f) lineTo(160f, 720f) lineTo(800f, 720f) lineTo(800f, 560f) lineTo(680f, 560f) lineTo(680f, 561f) quadTo(680f, 578f, 668.5f, 589.5f) quadTo(657f, 601f, 640f, 601f) quadTo(623f, 601f, 611.5f, 589.5f) quadTo(600f, 578f, 600f, 561f) lineTo(600f, 560f) lineTo(360f, 560f) lineTo(360f, 561f) quadTo(360f, 578f, 348.5f, 589.5f) quadTo(337f, 601f, 320f, 601f) quadTo(303f, 601f, 291.5f, 589.5f) quadTo(280f, 578f, 280f, 561f) lineTo(280f, 560f) close() moveTo(480f, 520f) lineTo(480f, 520f) quadTo(480f, 520f, 480f, 520f) quadTo(480f, 520f, 480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) quadTo(480f, 520f, 480f, 520f) quadTo(480f, 520f, 480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) lineTo(480f, 520f) close() moveTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) close() moveTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) lineTo(480f, 560f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SettingsTimelapse.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SettingsTimelapse: ImageVector by lazy { ImageVector.Builder( name = "SettingsTimelapse", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(720f, 840f) lineTo(720f, 600f) lineTo(920f, 720f) lineTo(720f, 840f) close() moveTo(520f, 840f) lineTo(520f, 600f) lineTo(720f, 720f) lineTo(520f, 840f) close() moveTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) lineTo(480f, 480f) quadTo(480f, 480f, 480f, 480f) quadTo(480f, 480f, 480f, 480f) close() moveTo(370f, 880f) lineTo(354f, 752f) quadTo(341f, 747f, 329.5f, 740f) quadTo(318f, 733f, 307f, 725f) lineTo(188f, 775f) lineTo(78f, 585f) lineTo(181f, 507f) quadTo(180f, 500f, 180f, 493.5f) quadTo(180f, 487f, 180f, 480f) quadTo(180f, 473f, 180f, 466.5f) quadTo(180f, 460f, 181f, 453f) lineTo(78f, 375f) lineTo(188f, 185f) lineTo(307f, 235f) quadTo(318f, 227f, 330f, 220f) quadTo(342f, 213f, 354f, 208f) lineTo(370f, 80f) lineTo(590f, 80f) lineTo(606f, 208f) quadTo(619f, 213f, 630.5f, 220f) quadTo(642f, 227f, 653f, 235f) lineTo(772f, 185f) lineTo(882f, 375f) lineTo(779f, 453f) quadTo(780f, 460f, 780f, 466.5f) quadTo(780f, 473f, 780f, 480f) quadTo(780f, 490f, 780f, 500f) quadTo(780f, 510f, 778f, 520f) lineTo(696f, 520f) quadTo(698f, 510f, 699f, 500f) quadTo(700f, 490f, 700f, 480f) quadTo(699f, 461f, 697f, 446.5f) quadTo(695f, 432f, 691f, 419f) lineTo(777f, 354f) lineTo(738f, 286f) lineTo(639f, 328f) quadTo(617f, 305f, 590.5f, 289.5f) quadTo(564f, 274f, 533f, 266f) lineTo(520f, 160f) lineTo(441f, 160f) lineTo(427f, 266f) quadTo(396f, 274f, 369.5f, 289.5f) quadTo(343f, 305f, 321f, 327f) lineTo(222f, 286f) lineTo(183f, 354f) lineTo(269f, 418f) quadTo(264f, 433f, 262f, 448f) quadTo(260f, 463f, 260f, 480f) quadTo(260f, 496f, 262f, 511f) quadTo(264f, 526f, 269f, 541f) lineTo(183f, 606f) lineTo(222f, 674f) lineTo(321f, 632f) quadTo(345f, 657f, 375f, 674f) quadTo(405f, 691f, 440f, 696f) lineTo(440f, 880f) lineTo(370f, 880f) close() moveTo(440f, 614f) lineTo(440f, 523f) quadTo(432f, 515f, 427f, 504f) quadTo(422f, 493f, 422f, 480f) quadTo(422f, 455f, 439.5f, 437.5f) quadTo(457f, 420f, 482f, 420f) quadTo(507f, 420f, 524.5f, 437.5f) quadTo(542f, 455f, 542f, 480f) quadTo(542f, 491f, 538.5f, 501.5f) quadTo(535f, 512f, 527f, 520f) lineTo(616f, 520f) quadTo(619f, 510f, 620.5f, 500.5f) quadTo(622f, 491f, 622f, 480f) quadTo(622f, 422f, 581f, 381f) quadTo(540f, 340f, 482f, 340f) quadTo(423f, 340f, 382.5f, 381f) quadTo(342f, 422f, 342f, 480f) quadTo(342f, 528f, 369.5f, 564f) quadTo(397f, 600f, 440f, 614f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Shadow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Shadow: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Shadow", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(160f, 880f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(80f, 800f) verticalLineToRelative(-480f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(160f, 240f) horizontalLineToRelative(80f) verticalLineToRelative(-80f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(320f, 80f) horizontalLineToRelative(480f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(880f, 160f) verticalLineToRelative(480f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(800f, 720f) horizontalLineToRelative(-80f) verticalLineToRelative(80f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(640f, 880f) lineTo(160f, 880f) close() moveTo(320f, 640f) horizontalLineToRelative(480f) verticalLineToRelative(-480f) lineTo(320f, 160f) verticalLineToRelative(480f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShareOff.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ShareOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ShareOff", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(680f, 800f) quadTo(697f, 800f, 708.5f, 788.5f) quadTo(720f, 777f, 720f, 760f) quadTo(720f, 743f, 708.5f, 731.5f) quadTo(697f, 720f, 680f, 720f) quadTo(663f, 720f, 651.5f, 731.5f) quadTo(640f, 743f, 640f, 760f) quadTo(640f, 777f, 651.5f, 788.5f) quadTo(663f, 800f, 680f, 800f) close() moveTo(708.5f, 228.5f) quadTo(720f, 217f, 720f, 200f) quadTo(720f, 183f, 708.5f, 171.5f) quadTo(697f, 160f, 680f, 160f) quadTo(663f, 160f, 651.5f, 171.5f) quadTo(640f, 183f, 640f, 200f) quadTo(640f, 217f, 651.5f, 228.5f) quadTo(663f, 240f, 680f, 240f) quadTo(697f, 240f, 708.5f, 228.5f) close() moveTo(80f, 490f) quadTo(80f, 488f, 80f, 485f) quadTo(80f, 482f, 80f, 480f) quadTo(80f, 430f, 115f, 395f) quadTo(150f, 360f, 200f, 360f) quadTo(224f, 360f, 245f, 368.5f) quadTo(266f, 377f, 282f, 392f) lineTo(563f, 228f) quadTo(561f, 221f, 560.5f, 214.5f) quadTo(560f, 208f, 560f, 200f) quadTo(560f, 150f, 595f, 115f) quadTo(630f, 80f, 680f, 80f) quadTo(730f, 80f, 765f, 115f) quadTo(800f, 150f, 800f, 200f) quadTo(800f, 250f, 765f, 285f) quadTo(730f, 320f, 680f, 320f) quadTo(656f, 320f, 635f, 311.5f) quadTo(614f, 303f, 598f, 288f) lineTo(318f, 451f) quadTo(299f, 446f, 279.5f, 443f) quadTo(260f, 440f, 240f, 440f) quadTo(195f, 440f, 154.5f, 453f) quadTo(114f, 466f, 80f, 490f) close() moveTo(680f, 880f) quadTo(630f, 880f, 595f, 845f) quadTo(560f, 810f, 560f, 760f) quadTo(560f, 754f, 563f, 732f) lineTo(520f, 706f) quadTo(518f, 682f, 513f, 659.5f) quadTo(508f, 637f, 499f, 615f) lineTo(598f, 672f) quadTo(614f, 657f, 635f, 648.5f) quadTo(656f, 640f, 680f, 640f) quadTo(730f, 640f, 765f, 675f) quadTo(800f, 710f, 800f, 760f) quadTo(800f, 810f, 765f, 845f) quadTo(730f, 880f, 680f, 880f) close() moveTo(98.5f, 861.5f) quadTo(40f, 803f, 40f, 720f) quadTo(40f, 637f, 98.5f, 578.5f) quadTo(157f, 520f, 240f, 520f) quadTo(323f, 520f, 381.5f, 578.5f) quadTo(440f, 637f, 440f, 720f) quadTo(440f, 803f, 381.5f, 861.5f) quadTo(323f, 920f, 240f, 920f) quadTo(157f, 920f, 98.5f, 861.5f) close() moveTo(240f, 748f) lineTo(310f, 819f) lineTo(339f, 791f) lineTo(268f, 720f) lineTo(339f, 649f) lineTo(311f, 621f) lineTo(240f, 692f) lineTo(169f, 621f) lineTo(141f, 649f) lineTo(212f, 720f) lineTo(141f, 791f) lineTo(169f, 819f) lineTo(240f, 748f) close() moveTo(680f, 760f) quadTo(680f, 760f, 680f, 760f) quadTo(680f, 760f, 680f, 760f) quadTo(680f, 760f, 680f, 760f) quadTo(680f, 760f, 680f, 760f) quadTo(680f, 760f, 680f, 760f) quadTo(680f, 760f, 680f, 760f) quadTo(680f, 760f, 680f, 760f) quadTo(680f, 760f, 680f, 760f) close() moveTo(680f, 200f) quadTo(680f, 200f, 680f, 200f) quadTo(680f, 200f, 680f, 200f) quadTo(680f, 200f, 680f, 200f) quadTo(680f, 200f, 680f, 200f) quadTo(680f, 200f, 680f, 200f) quadTo(680f, 200f, 680f, 200f) quadTo(680f, 200f, 680f, 200f) quadTo(680f, 200f, 680f, 200f) close() } }.build() } val Icons.Rounded.ShareOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.ShareOff", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(80f, 490f) quadTo(80f, 488f, 80f, 485f) quadTo(80f, 482f, 80f, 480f) quadTo(80f, 430f, 115f, 395f) quadTo(150f, 360f, 200f, 360f) quadTo(224f, 360f, 245f, 368.5f) quadTo(266f, 377f, 282f, 392f) lineTo(563f, 228f) quadTo(561f, 221f, 560.5f, 214.5f) quadTo(560f, 208f, 560f, 200f) quadTo(560f, 150f, 595f, 115f) quadTo(630f, 80f, 680f, 80f) quadTo(730f, 80f, 765f, 115f) quadTo(800f, 150f, 800f, 200f) quadTo(800f, 250f, 765f, 285f) quadTo(730f, 320f, 680f, 320f) quadTo(656f, 320f, 635f, 311.5f) quadTo(614f, 303f, 598f, 288f) lineTo(318f, 451f) quadTo(299f, 446f, 279.5f, 443f) quadTo(260f, 440f, 240f, 440f) quadTo(195f, 440f, 154.5f, 453f) quadTo(114f, 466f, 80f, 490f) close() moveTo(680f, 880f) quadTo(630f, 880f, 595f, 845f) quadTo(560f, 810f, 560f, 760f) quadTo(560f, 754f, 563f, 732f) lineTo(520f, 706f) quadTo(518f, 682f, 513f, 659.5f) quadTo(508f, 637f, 499f, 615f) lineTo(598f, 672f) quadTo(614f, 657f, 635f, 648.5f) quadTo(656f, 640f, 680f, 640f) quadTo(730f, 640f, 765f, 675f) quadTo(800f, 710f, 800f, 760f) quadTo(800f, 810f, 765f, 845f) quadTo(730f, 880f, 680f, 880f) close() moveTo(98.5f, 861.5f) quadTo(40f, 803f, 40f, 720f) quadTo(40f, 637f, 98.5f, 578.5f) quadTo(157f, 520f, 240f, 520f) quadTo(323f, 520f, 381.5f, 578.5f) quadTo(440f, 637f, 440f, 720f) quadTo(440f, 803f, 381.5f, 861.5f) quadTo(323f, 920f, 240f, 920f) quadTo(157f, 920f, 98.5f, 861.5f) close() moveTo(240f, 748f) lineTo(310f, 819f) lineTo(339f, 791f) lineTo(268f, 720f) lineTo(339f, 649f) lineTo(311f, 621f) lineTo(240f, 692f) lineTo(169f, 621f) lineTo(141f, 649f) lineTo(212f, 720f) lineTo(141f, 791f) lineTo(169f, 819f) lineTo(240f, 748f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShieldKey.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ShieldKey: ImageVector by lazy { Builder( name = "Shield Key", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 8.0f) arcTo( 1.0f, 1.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 13.0f, y1 = 9.0f ) arcTo( 1.0f, 1.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 12.0f, y1 = 10.0f ) arcTo( 1.0f, 1.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 11.0f, y1 = 9.0f ) arcTo( 1.0f, 1.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 12.0f, y1 = 8.0f ) moveTo(21.0f, 11.0f) curveTo(21.0f, 16.55f, 17.16f, 21.74f, 12.0f, 23.0f) curveTo(6.84f, 21.74f, 3.0f, 16.55f, 3.0f, 11.0f) verticalLineTo(5.0f) lineTo(12.0f, 1.0f) lineTo(21.0f, 5.0f) verticalLineTo(11.0f) moveTo(12.0f, 6.0f) arcTo( 3.0f, 3.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 9.0f, y1 = 9.0f ) curveTo(9.0f, 10.31f, 9.83f, 11.42f, 11.0f, 11.83f) verticalLineTo(18.0f) horizontalLineTo(13.0f) verticalLineTo(16.0f) horizontalLineTo(15.0f) verticalLineTo(14.0f) horizontalLineTo(13.0f) verticalLineTo(11.83f) curveTo(14.17f, 11.42f, 15.0f, 10.31f, 15.0f, 9.0f) arcTo( 3.0f, 3.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 12.0f, y1 = 6.0f ) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShieldLock.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ShieldLock: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ShieldLock", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14.713f, 11.287f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) verticalLineToRelative(-1f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(1f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.713f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.713f) verticalLineToRelative(3f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.713f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) verticalLineToRelative(-3f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) close() moveTo(13f, 11f) horizontalLineToRelative(-2f) verticalLineToRelative(-1f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.713f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.713f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.713f) verticalLineToRelative(1f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.638f, 5.25f) curveToRelative(-0.242f, -0.333f, -0.554f, -0.575f, -0.938f, -0.725f) lineToRelative(-6f, -2.25f) curveToRelative(-0.233f, -0.083f, -0.467f, -0.125f, -0.7f, -0.125f) reflectiveCurveToRelative(-0.467f, 0.042f, -0.7f, 0.125f) lineToRelative(-6f, 2.25f) curveToRelative(-0.383f, 0.15f, -0.696f, 0.392f, -0.938f, 0.725f) curveToRelative(-0.242f, 0.333f, -0.362f, 0.708f, -0.362f, 1.125f) verticalLineToRelative(4.725f) curveToRelative(0f, 2.333f, 0.667f, 4.513f, 2f, 6.538f) curveToRelative(1.333f, 2.025f, 3.125f, 3.412f, 5.375f, 4.162f) curveToRelative(0.1f, 0.033f, 0.2f, 0.058f, 0.3f, 0.075f) curveToRelative(0.1f, 0.017f, 0.208f, 0.025f, 0.325f, 0.025f) reflectiveCurveToRelative(0.225f, -0.008f, 0.325f, -0.025f) curveToRelative(0.1f, -0.017f, 0.2f, -0.042f, 0.3f, -0.075f) curveToRelative(2.25f, -0.75f, 4.042f, -2.138f, 5.375f, -4.162f) curveToRelative(1.333f, -2.025f, 2f, -4.204f, 2f, -6.538f) verticalLineToRelative(-4.725f) curveToRelative(0f, -0.417f, -0.121f, -0.792f, -0.362f, -1.125f) close() moveTo(18f, 11.1f) curveToRelative(0f, 2.017f, -0.567f, 3.85f, -1.7f, 5.5f) curveToRelative(-1.133f, 1.65f, -2.567f, 2.75f, -4.3f, 3.3f) curveToRelative(-1.733f, -0.55f, -3.167f, -1.65f, -4.3f, -3.3f) curveToRelative(-1.133f, -1.65f, -1.7f, -3.483f, -1.7f, -5.5f) verticalLineToRelative(-4.725f) lineToRelative(6f, -2.25f) lineToRelative(6f, 2.25f) verticalLineToRelative(4.725f) close() } }.build() } val Icons.TwoTone.ShieldLock: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.ShieldLock", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14.713f, 11.287f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.713f, -0.287f) verticalLineToRelative(-1f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) reflectiveCurveToRelative(-1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(1f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.713f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.713f) verticalLineToRelative(3f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.713f) curveToRelative(0.192f, 0.192f, 0.429f, 0.287f, 0.713f, 0.287f) horizontalLineToRelative(4f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.713f, -0.287f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.713f) verticalLineToRelative(-3f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.713f) close() moveTo(13f, 11f) horizontalLineToRelative(-2f) verticalLineToRelative(-1f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.713f) curveToRelative(0.192f, -0.192f, 0.429f, -0.287f, 0.713f, -0.287f) reflectiveCurveToRelative(0.521f, 0.096f, 0.713f, 0.287f) curveToRelative(0.192f, 0.192f, 0.287f, 0.429f, 0.287f, 0.713f) verticalLineToRelative(1f) close() } path(fill = SolidColor(Color.Black)) { moveTo(19.638f, 5.25f) curveToRelative(-0.242f, -0.333f, -0.554f, -0.575f, -0.938f, -0.725f) lineToRelative(-6f, -2.25f) curveToRelative(-0.233f, -0.083f, -0.467f, -0.125f, -0.7f, -0.125f) reflectiveCurveToRelative(-0.467f, 0.042f, -0.7f, 0.125f) lineToRelative(-6f, 2.25f) curveToRelative(-0.383f, 0.15f, -0.696f, 0.392f, -0.938f, 0.725f) curveToRelative(-0.242f, 0.333f, -0.362f, 0.708f, -0.362f, 1.125f) verticalLineToRelative(4.725f) curveToRelative(0f, 2.333f, 0.667f, 4.513f, 2f, 6.538f) curveToRelative(1.333f, 2.025f, 3.125f, 3.412f, 5.375f, 4.162f) curveToRelative(0.1f, 0.033f, 0.2f, 0.058f, 0.3f, 0.075f) curveToRelative(0.1f, 0.017f, 0.208f, 0.025f, 0.325f, 0.025f) reflectiveCurveToRelative(0.225f, -0.008f, 0.325f, -0.025f) curveToRelative(0.1f, -0.017f, 0.2f, -0.042f, 0.3f, -0.075f) curveToRelative(2.25f, -0.75f, 4.042f, -2.138f, 5.375f, -4.162f) curveToRelative(1.333f, -2.025f, 2f, -4.204f, 2f, -6.538f) verticalLineToRelative(-4.725f) curveToRelative(0f, -0.417f, -0.121f, -0.792f, -0.362f, -1.125f) close() moveTo(18f, 11.1f) curveToRelative(0f, 2.017f, -0.567f, 3.85f, -1.7f, 5.5f) curveToRelative(-1.133f, 1.65f, -2.567f, 2.75f, -4.3f, 3.3f) curveToRelative(-1.733f, -0.55f, -3.167f, -1.65f, -4.3f, -3.3f) curveToRelative(-1.133f, -1.65f, -1.7f, -3.483f, -1.7f, -5.5f) verticalLineToRelative(-4.725f) lineToRelative(6f, -2.25f) lineToRelative(6f, 2.25f) verticalLineToRelative(4.725f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 19.904f) curveToRelative(1.733f, -0.55f, 3.167f, -1.65f, 4.3f, -3.3f) reflectiveCurveToRelative(1.7f, -3.483f, 1.7f, -5.5f) verticalLineToRelative(-4.725f) lineToRelative(-6f, -2.25f) lineToRelative(-6f, 2.25f) verticalLineToRelative(4.725f) curveToRelative(0f, 2.017f, 0.567f, 3.85f, 1.7f, 5.5f) reflectiveCurveToRelative(2.567f, 2.75f, 4.3f, 3.3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShieldOpen.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ShieldOpen: ImageVector by lazy { Builder( name = "Shield Open", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 1.0f) lineTo(3.0f, 5.0f) verticalLineTo(11.0f) curveTo(3.0f, 16.5f, 6.8f, 21.7f, 12.0f, 23.0f) curveTo(17.2f, 21.7f, 21.0f, 16.5f, 21.0f, 11.0f) verticalLineTo(5.0f) lineTo(12.0f, 1.0f) moveTo(16.0f, 15.8f) curveTo(16.0f, 16.4f, 15.4f, 17.0f, 14.7f, 17.0f) horizontalLineTo(9.2f) curveTo(8.6f, 17.0f, 8.0f, 16.4f, 8.0f, 15.7f) verticalLineTo(12.2f) curveTo(8.0f, 11.6f, 8.6f, 11.0f, 9.2f, 11.0f) verticalLineTo(8.5f) curveTo(9.2f, 7.1f, 10.6f, 6.0f, 12.0f, 6.0f) reflectiveCurveTo(14.8f, 7.1f, 14.8f, 8.5f) verticalLineTo(9.0f) horizontalLineTo(13.5f) verticalLineTo(8.5f) curveTo(13.5f, 7.7f, 12.8f, 7.2f, 12.0f, 7.2f) reflectiveCurveTo(10.5f, 7.7f, 10.5f, 8.5f) verticalLineTo(11.0f) horizontalLineTo(14.8f) curveTo(15.4f, 11.0f, 16.0f, 11.6f, 16.0f, 12.3f) verticalLineTo(15.8f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ShineDiamond.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.ShineDiamond: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.ShineDiamond", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(480f, 880f) lineTo(120f, 524f) lineToRelative(200f, -244f) horizontalLineToRelative(320f) lineToRelative(200f, 244f) lineTo(480f, 880f) close() moveTo(183f, 280f) lineToRelative(-85f, -85f) lineToRelative(57f, -56f) lineToRelative(85f, 85f) lineToRelative(-57f, 56f) close() moveTo(440f, 200f) verticalLineToRelative(-120f) horizontalLineToRelative(80f) verticalLineToRelative(120f) horizontalLineToRelative(-80f) close() moveTo(775f, 280f) lineTo(718f, 223f) lineTo(803f, 138f) lineTo(860f, 195f) lineTo(775f, 280f) close() moveTo(480f, 768f) lineToRelative(210f, -208f) lineTo(270f, 560f) lineToRelative(210f, 208f) close() moveTo(358f, 360f) lineToRelative(-99f, 120f) horizontalLineToRelative(442f) lineToRelative(-99f, -120f) lineTo(358f, 360f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Signature.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Signature: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Signature", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(563f, 469f) quadToRelative(73f, -54f, 114f, -118.5f) reflectiveQuadTo(718f, 222f) quadToRelative(0f, -32f, -10.5f, -47f) reflectiveQuadTo(679f, 160f) quadToRelative(-47f, 0f, -83f, 79.5f) reflectiveQuadTo(560f, 419f) quadToRelative(0f, 14f, 0.5f, 26.5f) reflectiveQuadTo(563f, 469f) close() moveTo(164f, 652f) quadToRelative(-11f, 11f, -28f, 11f) reflectiveQuadToRelative(-28f, -11f) quadToRelative(-11f, -11f, -11f, -28f) reflectiveQuadToRelative(11f, -28f) lineToRelative(36f, -36f) lineToRelative(-36f, -36f) quadToRelative(-11f, -11f, -11f, -28f) reflectiveQuadToRelative(11f, -28f) quadToRelative(11f, -11f, 28f, -11f) reflectiveQuadToRelative(28f, 11f) lineToRelative(36f, 36f) lineToRelative(36f, -36f) quadToRelative(11f, -11f, 28f, -11f) reflectiveQuadToRelative(28f, 11f) quadToRelative(11f, 11f, 11f, 28f) reflectiveQuadToRelative(-11f, 28f) lineToRelative(-36f, 36f) lineToRelative(36f, 36f) quadToRelative(11f, 11f, 11f, 28f) reflectiveQuadToRelative(-11f, 28f) quadToRelative(-11f, 11f, -28f, 11f) reflectiveQuadToRelative(-28f, -11f) lineToRelative(-36f, -36f) lineToRelative(-36f, 36f) close() moveTo(618f, 640f) quadToRelative(-30f, 0f, -55f, -11.5f) reflectiveQuadTo(520f, 591f) quadToRelative(-16f, 8f, -33f, 16f) lineToRelative(-34f, 16f) quadToRelative(-16f, 7f, -31.5f, 0.5f) reflectiveQuadTo(400f, 601f) quadToRelative(-6f, -16f, 1.5f, -31f) reflectiveQuadToRelative(23.5f, -22f) quadToRelative(17f, -8f, 33f, -15.5f) reflectiveQuadToRelative(31f, -15.5f) quadToRelative(-5f, -22f, -7.5f, -48f) reflectiveQuadToRelative(-2.5f, -56f) quadToRelative(0f, -144f, 57f, -238.5f) reflectiveQuadTo(679f, 80f) quadToRelative(52f, 0f, 85f, 38.5f) reflectiveQuadTo(797f, 226f) quadToRelative(0f, 86f, -54.5f, 170f) reflectiveQuadTo(591f, 547f) quadToRelative(7f, 7f, 14.5f, 10.5f) reflectiveQuadTo(621f, 561f) quadToRelative(21f, 0f, 49f, -23f) reflectiveQuadToRelative(54f, -62f) quadToRelative(10f, -14f, 25.5f, -19.5f) reflectiveQuadTo(780f, 458f) quadToRelative(15f, 8f, 22f, 23.5f) reflectiveQuadToRelative(4f, 32.5f) quadToRelative(-2f, 12f, -2f, 23f) reflectiveQuadToRelative(3f, 21f) quadToRelative(5f, -2f, 11.5f, -6.5f) reflectiveQuadTo(832f, 540f) quadToRelative(12f, -11f, 28.5f, -12.5f) reflectiveQuadTo(890f, 536f) quadToRelative(14f, 11f, 15f, 27f) reflectiveQuadToRelative(-10f, 27f) quadToRelative(-23f, 23f, -48.5f, 36.5f) reflectiveQuadTo(798f, 640f) quadToRelative(-21f, 0f, -37.5f, -12.5f) reflectiveQuadTo(733f, 589f) quadToRelative(-28f, 25f, -57f, 38f) reflectiveQuadToRelative(-58f, 13f) close() moveTo(131.5f, 828.5f) quadTo(120f, 817f, 120f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(143f, 760f, 160f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(200f, 783f, 200f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(177f, 840f, 160f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(291.5f, 828.5f) quadTo(280f, 817f, 280f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(303f, 760f, 320f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(360f, 783f, 360f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(337f, 840f, 320f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(451.5f, 828.5f) quadTo(440f, 817f, 440f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(463f, 760f, 480f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(520f, 783f, 520f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(497f, 840f, 480f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(611.5f, 828.5f) quadTo(600f, 817f, 600f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(623f, 760f, 640f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(680f, 783f, 680f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(657f, 840f, 640f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() moveTo(771.5f, 828.5f) quadTo(760f, 817f, 760f, 800f) reflectiveQuadToRelative(11.5f, -28.5f) quadTo(783f, 760f, 800f, 760f) reflectiveQuadToRelative(28.5f, 11.5f) quadTo(840f, 783f, 840f, 800f) reflectiveQuadToRelative(-11.5f, 28.5f) quadTo(817f, 840f, 800f, 840f) reflectiveQuadToRelative(-28.5f, -11.5f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SkewMore.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SkewMore: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.SkewMore", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12.5f, 11f) lineTo(10.41f, 20f) horizontalLineTo(5.5f) lineTo(7.59f, 11f) horizontalLineTo(12.5f) moveTo(15f, 9f) horizontalLineTo(6f) lineTo(3f, 22f) horizontalLineTo(12f) lineTo(15f, 9f) moveTo(21f, 6f) lineTo(17f, 2f) verticalLineTo(5f) horizontalLineTo(9f) verticalLineTo(7f) horizontalLineTo(17f) verticalLineTo(10f) lineTo(21f, 6f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Slider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Slider: ImageVector by lazy { Builder( name = "Slider", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, viewportHeight = 960.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(200.0f, 600.0f) quadToRelative(-50.0f, 0.0f, -85.0f, -35.0f) reflectiveQuadToRelative(-35.0f, -85.0f) quadToRelative(0.0f, -50.0f, 35.0f, -85.0f) reflectiveQuadToRelative(85.0f, -35.0f) horizontalLineToRelative(560.0f) quadToRelative(50.0f, 0.0f, 85.0f, 35.0f) reflectiveQuadToRelative(35.0f, 85.0f) quadToRelative(0.0f, 50.0f, -35.0f, 85.0f) reflectiveQuadToRelative(-85.0f, 35.0f) lineTo(200.0f, 600.0f) close() moveTo(560.0f, 520.0f) horizontalLineToRelative(200.0f) quadToRelative(17.0f, 0.0f, 28.5f, -11.5f) reflectiveQuadTo(800.0f, 480.0f) quadToRelative(0.0f, -17.0f, -11.5f, -28.5f) reflectiveQuadTo(760.0f, 440.0f) lineTo(560.0f, 440.0f) verticalLineToRelative(80.0f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snail.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Snail: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Snail", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.31f, 8.03f) lineTo(21.24f, 4.95f) curveTo(21.67f, 4.85f, 22f, 4.47f, 22f, 4f) curveTo(22f, 3.45f, 21.55f, 3f, 21f, 3f) reflectiveCurveTo(20f, 3.45f, 20f, 4f) curveTo(20f, 4.26f, 20.11f, 4.5f, 20.27f, 4.68f) lineTo(19.5f, 7.26f) lineTo(18.73f, 4.68f) curveTo(18.89f, 4.5f, 19f, 4.26f, 19f, 4f) curveTo(19f, 3.45f, 18.55f, 3f, 18f, 3f) reflectiveCurveTo(17f, 3.45f, 17f, 4f) curveTo(17f, 4.47f, 17.33f, 4.85f, 17.76f, 4.95f) lineTo(18.69f, 8.03f) curveTo(17.73f, 8.18f, 17f, 9f, 17f, 10f) verticalLineTo(12.25f) curveTo(15.65f, 9.16f, 12.63f, 7f, 9.11f, 7f) curveTo(5.19f, 7f, 2f, 10.26f, 2f, 14.26f) curveTo(2f, 16.1f, 2.82f, 17.75f, 4.1f, 18.85f) lineTo(2.88f, 19f) curveTo(2.38f, 19.06f, 2f, 19.5f, 2f, 20f) curveTo(2f, 20.55f, 2.45f, 21f, 3f, 21f) lineTo(19.12f, 21f) curveTo(20.16f, 21f, 21f, 20.16f, 21f, 19.12f) verticalLineTo(11.72f) curveTo(21.6f, 11.38f, 22f, 10.74f, 22f, 10f) curveTo(22f, 9f, 21.27f, 8.18f, 20.31f, 8.03f) moveTo(15.6f, 17.41f) lineTo(12.07f, 17.86f) curveTo(12.5f, 17.1f, 12.8f, 16.21f, 12.8f, 15.26f) curveTo(12.8f, 12.94f, 10.95f, 11.06f, 8.67f, 11.06f) curveTo(8.14f, 11.06f, 7.62f, 11.18f, 7.14f, 11.41f) curveTo(6.65f, 11.66f, 6.44f, 12.26f, 6.69f, 12.75f) curveTo(6.93f, 13.25f, 7.53f, 13.45f, 8.03f, 13.21f) curveTo(8.23f, 13.11f, 8.45f, 13.06f, 8.67f, 13.06f) curveTo(9.85f, 13.06f, 10.8f, 14.04f, 10.8f, 15.26f) curveTo(10.8f, 16.92f, 9.5f, 18.27f, 7.89f, 18.27f) curveTo(5.75f, 18.27f, 4f, 16.47f, 4f, 14.26f) curveTo(4f, 11.36f, 6.29f, 9f, 9.11f, 9f) curveTo(12.77f, 9f, 15.75f, 12.06f, 15.75f, 15.82f) curveTo(15.75f, 16.36f, 15.69f, 16.89f, 15.6f, 17.41f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Snowflake.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Snowflake: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Snowflake", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.79f, 13.95f) lineTo(18.46f, 14.57f) lineTo(16.46f, 13.44f) verticalLineTo(10.56f) lineTo(18.46f, 9.43f) lineTo(20.79f, 10.05f) lineTo(21.31f, 8.12f) lineTo(19.54f, 7.65f) lineTo(20f, 5.88f) lineTo(18.07f, 5.36f) lineTo(17.45f, 7.69f) lineTo(15.45f, 8.82f) lineTo(13f, 7.38f) verticalLineTo(5.12f) lineTo(14.71f, 3.41f) lineTo(13.29f, 2f) lineTo(12f, 3.29f) lineTo(10.71f, 2f) lineTo(9.29f, 3.41f) lineTo(11f, 5.12f) verticalLineTo(7.38f) lineTo(8.5f, 8.82f) lineTo(6.5f, 7.69f) lineTo(5.92f, 5.36f) lineTo(4f, 5.88f) lineTo(4.47f, 7.65f) lineTo(2.7f, 8.12f) lineTo(3.22f, 10.05f) lineTo(5.55f, 9.43f) lineTo(7.55f, 10.56f) verticalLineTo(13.45f) lineTo(5.55f, 14.58f) lineTo(3.22f, 13.96f) lineTo(2.7f, 15.89f) lineTo(4.47f, 16.36f) lineTo(4f, 18.12f) lineTo(5.93f, 18.64f) lineTo(6.55f, 16.31f) lineTo(8.55f, 15.18f) lineTo(11f, 16.62f) verticalLineTo(18.88f) lineTo(9.29f, 20.59f) lineTo(10.71f, 22f) lineTo(12f, 20.71f) lineTo(13.29f, 22f) lineTo(14.7f, 20.59f) lineTo(13f, 18.88f) verticalLineTo(16.62f) lineTo(15.5f, 15.17f) lineTo(17.5f, 16.3f) lineTo(18.12f, 18.63f) lineTo(20f, 18.12f) lineTo(19.53f, 16.35f) lineTo(21.3f, 15.88f) lineTo(20.79f, 13.95f) moveTo(9.5f, 10.56f) lineTo(12f, 9.11f) lineTo(14.5f, 10.56f) verticalLineTo(13.44f) lineTo(12f, 14.89f) lineTo(9.5f, 13.44f) verticalLineTo(10.56f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Speed.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Speed: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Speed", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(418f, 620f) quadTo(443f, 645f, 481f, 643.5f) quadTo(519f, 642f, 536f, 616f) lineTo(705f, 363f) quadTo(714f, 349f, 702.5f, 337.5f) quadTo(691f, 326f, 677f, 335f) lineTo(424f, 504f) quadTo(398f, 522f, 395.5f, 558.5f) quadTo(393f, 595f, 418f, 620f) close() moveTo(204f, 800f) quadTo(182f, 800f, 163.5f, 790.5f) quadTo(145f, 781f, 134f, 762f) quadTo(108f, 715f, 94f, 664.5f) quadTo(80f, 614f, 80f, 560f) quadTo(80f, 477f, 111.5f, 404f) quadTo(143f, 331f, 197f, 277f) quadTo(251f, 223f, 324f, 191.5f) quadTo(397f, 160f, 480f, 160f) quadTo(562f, 160f, 634f, 191f) quadTo(706f, 222f, 760f, 275.5f) quadTo(814f, 329f, 846f, 400.5f) quadTo(878f, 472f, 879f, 554f) quadTo(880f, 609f, 866.5f, 661.5f) quadTo(853f, 714f, 825f, 762f) quadTo(814f, 781f, 795.5f, 790.5f) quadTo(777f, 800f, 755f, 800f) lineTo(204f, 800f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitAlt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SplitAlt: ImageVector by lazy { ImageVector.Builder( name = "Outlined.SplitAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11f, 21f) verticalLineToRelative(-4f) curveToRelative(0f, -0.933f, -0.142f, -1.625f, -0.425f, -2.075f) reflectiveCurveToRelative(-0.658f, -0.892f, -1.125f, -1.325f) lineToRelative(1.425f, -1.425f) curveToRelative(0.2f, 0.183f, 0.392f, 0.379f, 0.575f, 0.587f) reflectiveCurveToRelative(0.367f, 0.429f, 0.55f, 0.663f) curveToRelative(0.233f, -0.317f, 0.471f, -0.596f, 0.712f, -0.837f) curveToRelative(0.242f, -0.242f, 0.488f, -0.479f, 0.738f, -0.712f) curveToRelative(0.633f, -0.583f, 1.208f, -1.258f, 1.725f, -2.025f) reflectiveCurveToRelative(0.792f, -2.108f, 0.825f, -4.025f) lineToRelative(-0.875f, 0.875f) curveToRelative(-0.183f, 0.183f, -0.412f, 0.275f, -0.688f, 0.275f) reflectiveCurveToRelative(-0.512f, -0.092f, -0.712f, -0.275f) curveToRelative(-0.2f, -0.2f, -0.3f, -0.438f, -0.3f, -0.712f) reflectiveCurveToRelative(0.1f, -0.512f, 0.3f, -0.712f) lineToRelative(2.575f, -2.575f) curveToRelative(0.1f, -0.1f, 0.208f, -0.171f, 0.325f, -0.213f) reflectiveCurveToRelative(0.242f, -0.063f, 0.375f, -0.063f) reflectiveCurveToRelative(0.258f, 0.021f, 0.375f, 0.063f) reflectiveCurveToRelative(0.225f, 0.112f, 0.325f, 0.213f) lineToRelative(2.6f, 2.6f) curveToRelative(0.183f, 0.183f, 0.279f, 0.412f, 0.287f, 0.688f) reflectiveCurveToRelative(-0.087f, 0.512f, -0.287f, 0.712f) curveToRelative(-0.183f, 0.183f, -0.417f, 0.275f, -0.7f, 0.275f) reflectiveCurveToRelative(-0.517f, -0.092f, -0.7f, -0.275f) lineToRelative(-0.9f, -0.875f) curveToRelative(-0.033f, 2.383f, -0.4f, 4.079f, -1.1f, 5.088f) curveToRelative(-0.7f, 1.008f, -1.4f, 1.829f, -2.1f, 2.463f) curveToRelative(-0.533f, 0.483f, -0.967f, 0.954f, -1.3f, 1.413f) reflectiveCurveToRelative(-0.5f, 1.196f, -0.5f, 2.213f) verticalLineToRelative(4f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(6.2f, 8.175f) curveToRelative(-0.067f, -0.333f, -0.112f, -0.7f, -0.138f, -1.1f) reflectiveCurveToRelative(-0.046f, -0.817f, -0.063f, -1.25f) lineToRelative(-0.9f, 0.9f) curveToRelative(-0.183f, 0.183f, -0.412f, 0.275f, -0.688f, 0.275f) reflectiveCurveToRelative(-0.512f, -0.1f, -0.712f, -0.3f) curveToRelative(-0.183f, -0.183f, -0.275f, -0.417f, -0.275f, -0.7f) reflectiveCurveToRelative(0.092f, -0.517f, 0.275f, -0.7f) lineToRelative(2.6f, -2.6f) curveToRelative(0.1f, -0.1f, 0.208f, -0.171f, 0.325f, -0.213f) reflectiveCurveToRelative(0.242f, -0.063f, 0.375f, -0.063f) reflectiveCurveToRelative(0.258f, 0.021f, 0.375f, 0.063f) reflectiveCurveToRelative(0.225f, 0.112f, 0.325f, 0.213f) lineToRelative(2.6f, 2.6f) curveToRelative(0.2f, 0.2f, 0.296f, 0.433f, 0.287f, 0.7f) reflectiveCurveToRelative(-0.112f, 0.5f, -0.313f, 0.7f) curveToRelative(-0.2f, 0.183f, -0.433f, 0.275f, -0.7f, 0.275f) reflectiveCurveToRelative(-0.5f, -0.092f, -0.7f, -0.275f) lineToRelative(-0.875f, -0.85f) curveToRelative(0f, 0.35f, 0.017f, 0.679f, 0.05f, 0.988f) reflectiveCurveToRelative(0.067f, 0.596f, 0.1f, 0.863f) lineToRelative(-1.95f, 0.475f) close() moveTo(8.35f, 12.575f) curveToRelative(-0.333f, -0.35f, -0.654f, -0.758f, -0.963f, -1.225f) curveToRelative(-0.308f, -0.467f, -0.579f, -1.042f, -0.813f, -1.725f) lineToRelative(1.925f, -0.475f) curveToRelative(0.167f, 0.45f, 0.358f, 0.833f, 0.575f, 1.15f) curveToRelative(0.217f, 0.317f, 0.45f, 0.6f, 0.7f, 0.85f) lineToRelative(-1.425f, 1.425f) close() } }.build() } val Icons.TwoTone.SplitAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.SplitAlt", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11f, 21f) verticalLineToRelative(-4f) curveToRelative(0f, -0.933f, -0.142f, -1.625f, -0.425f, -2.075f) reflectiveCurveToRelative(-0.658f, -0.892f, -1.125f, -1.325f) lineToRelative(1.425f, -1.425f) curveToRelative(0.2f, 0.183f, 0.392f, 0.379f, 0.575f, 0.587f) reflectiveCurveToRelative(0.367f, 0.429f, 0.55f, 0.663f) curveToRelative(0.233f, -0.317f, 0.471f, -0.596f, 0.712f, -0.837f) curveToRelative(0.242f, -0.242f, 0.488f, -0.479f, 0.738f, -0.712f) curveToRelative(0.633f, -0.583f, 1.208f, -1.258f, 1.725f, -2.025f) reflectiveCurveToRelative(0.792f, -2.108f, 0.825f, -4.025f) lineToRelative(-0.875f, 0.875f) curveToRelative(-0.183f, 0.183f, -0.412f, 0.275f, -0.688f, 0.275f) reflectiveCurveToRelative(-0.512f, -0.092f, -0.712f, -0.275f) curveToRelative(-0.2f, -0.2f, -0.3f, -0.438f, -0.3f, -0.712f) reflectiveCurveToRelative(0.1f, -0.512f, 0.3f, -0.712f) lineToRelative(2.575f, -2.575f) curveToRelative(0.1f, -0.1f, 0.208f, -0.171f, 0.325f, -0.213f) reflectiveCurveToRelative(0.242f, -0.063f, 0.375f, -0.063f) reflectiveCurveToRelative(0.258f, 0.021f, 0.375f, 0.063f) reflectiveCurveToRelative(0.225f, 0.112f, 0.325f, 0.213f) lineToRelative(2.6f, 2.6f) curveToRelative(0.183f, 0.183f, 0.279f, 0.412f, 0.287f, 0.688f) reflectiveCurveToRelative(-0.087f, 0.512f, -0.287f, 0.712f) curveToRelative(-0.183f, 0.183f, -0.417f, 0.275f, -0.7f, 0.275f) reflectiveCurveToRelative(-0.517f, -0.092f, -0.7f, -0.275f) lineToRelative(-0.9f, -0.875f) curveToRelative(-0.033f, 2.383f, -0.4f, 4.079f, -1.1f, 5.088f) curveToRelative(-0.7f, 1.008f, -1.4f, 1.829f, -2.1f, 2.463f) curveToRelative(-0.533f, 0.483f, -0.967f, 0.954f, -1.3f, 1.413f) reflectiveCurveToRelative(-0.5f, 1.196f, -0.5f, 2.213f) verticalLineToRelative(4f) curveToRelative(0f, 0.283f, -0.096f, 0.521f, -0.287f, 0.712f) curveToRelative(-0.192f, 0.192f, -0.429f, 0.287f, -0.712f, 0.287f) reflectiveCurveToRelative(-0.521f, -0.096f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.287f, -0.429f, -0.287f, -0.712f) close() moveTo(6.2f, 8.175f) curveToRelative(-0.067f, -0.333f, -0.112f, -0.7f, -0.138f, -1.1f) reflectiveCurveToRelative(-0.046f, -0.817f, -0.063f, -1.25f) lineToRelative(-0.9f, 0.9f) curveToRelative(-0.183f, 0.183f, -0.412f, 0.275f, -0.688f, 0.275f) reflectiveCurveToRelative(-0.512f, -0.1f, -0.712f, -0.3f) curveToRelative(-0.183f, -0.183f, -0.275f, -0.417f, -0.275f, -0.7f) reflectiveCurveToRelative(0.092f, -0.517f, 0.275f, -0.7f) lineToRelative(2.6f, -2.6f) curveToRelative(0.1f, -0.1f, 0.208f, -0.171f, 0.325f, -0.213f) reflectiveCurveToRelative(0.242f, -0.063f, 0.375f, -0.063f) reflectiveCurveToRelative(0.258f, 0.021f, 0.375f, 0.063f) reflectiveCurveToRelative(0.225f, 0.112f, 0.325f, 0.213f) lineToRelative(2.6f, 2.6f) curveToRelative(0.2f, 0.2f, 0.296f, 0.433f, 0.287f, 0.7f) reflectiveCurveToRelative(-0.112f, 0.5f, -0.313f, 0.7f) curveToRelative(-0.2f, 0.183f, -0.433f, 0.275f, -0.7f, 0.275f) reflectiveCurveToRelative(-0.5f, -0.092f, -0.7f, -0.275f) lineToRelative(-0.875f, -0.85f) curveToRelative(0f, 0.35f, 0.017f, 0.679f, 0.05f, 0.988f) reflectiveCurveToRelative(0.067f, 0.596f, 0.1f, 0.863f) lineToRelative(-1.95f, 0.475f) close() moveTo(8.35f, 12.575f) curveToRelative(-0.333f, -0.35f, -0.654f, -0.758f, -0.963f, -1.225f) curveToRelative(-0.308f, -0.467f, -0.579f, -1.042f, -0.813f, -1.725f) lineToRelative(1.925f, -0.475f) curveToRelative(0.167f, 0.45f, 0.358f, 0.833f, 0.575f, 1.15f) curveToRelative(0.217f, 0.317f, 0.45f, 0.6f, 0.7f, 0.85f) lineToRelative(-1.425f, 1.425f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(6.2f, 8.175f) curveToRelative(0.045f, 0.218f, 0.097f, 0.445f, 0.157f, 0.678f) curveToRelative(0.069f, 0.27f, 0.142f, 0.528f, 0.218f, 0.772f) curveToRelative(0.642f, -0.158f, 1.283f, -0.317f, 1.925f, -0.475f) curveToRelative(-0.073f, -0.234f, -0.142f, -0.485f, -0.206f, -0.752f) curveToRelative(-0.058f, -0.243f, -0.105f, -0.476f, -0.144f, -0.698f) curveToRelative(-0.65f, 0.158f, -1.3f, 0.317f, -1.95f, 0.475f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(8.35f, 12.575f) lineToRelative(1.1f, 1.025f) lineToRelative(1.425f, -1.425f) lineToRelative(-1.1f, -1.025f) lineToRelative(-1.425f, 1.425f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SplitComplementary.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.SplitComplementary: ImageVector by lazy { Builder( name = "SplitComplementary", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(8.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(6.9f, 16.0f, 8.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(10.9f, 4.0f, 12.0f, 4.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(14.9f, 16.0f, 16.0f, 16.0f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Spray.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Spray: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Spray", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(10f, 4f) horizontalLineTo(12f) verticalLineTo(6f) horizontalLineTo(10f) verticalLineTo(4f) moveTo(7f, 3f) horizontalLineTo(9f) verticalLineTo(5f) horizontalLineTo(7f) verticalLineTo(3f) moveTo(7f, 6f) horizontalLineTo(9f) verticalLineTo(8f) horizontalLineTo(7f) verticalLineTo(6f) moveTo(6f, 8f) verticalLineTo(10f) horizontalLineTo(4f) verticalLineTo(8f) horizontalLineTo(6f) moveTo(6f, 5f) verticalLineTo(7f) horizontalLineTo(4f) verticalLineTo(5f) horizontalLineTo(6f) moveTo(6f, 2f) verticalLineTo(4f) horizontalLineTo(4f) verticalLineTo(2f) horizontalLineTo(6f) moveTo(13f, 22f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11f, 20f) verticalLineTo(10f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 13f, 8f) verticalLineTo(7f) horizontalLineTo(14f) verticalLineTo(4f) horizontalLineTo(17f) verticalLineTo(7f) horizontalLineTo(18f) verticalLineTo(8f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 20f, 10f) verticalLineTo(20f) arcTo(2f, 2f, 0f, isMoreThanHalf = false, isPositiveArc = true, 18f, 22f) horizontalLineTo(13f) moveTo(13f, 10f) verticalLineTo(20f) horizontalLineTo(18f) verticalLineTo(10f) horizontalLineTo(13f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Square.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Square: ImageVector by lazy { Builder( name = "Square", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.4125f, 3.5875f) curveTo(20.0208f, 3.1959f, 19.55f, 3.0f, 19.0f, 3.0f) horizontalLineTo(5.0f) curveTo(4.45f, 3.0f, 3.9792f, 3.1959f, 3.5875f, 3.5875f) reflectiveCurveTo(3.0f, 4.45f, 3.0f, 5.0f) verticalLineToRelative(14.0f) curveToRelative(0.0f, 0.55f, 0.1959f, 1.0208f, 0.5875f, 1.4125f) reflectiveCurveTo(4.45f, 21.0f, 5.0f, 21.0f) horizontalLineToRelative(14.0f) curveToRelative(0.55f, 0.0f, 1.0208f, -0.1959f, 1.4125f, -0.5875f) reflectiveCurveTo(21.0f, 19.55f, 21.0f, 19.0f) verticalLineTo(5.0f) curveTo(21.0f, 4.45f, 20.8041f, 3.9792f, 20.4125f, 3.5875f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareEdit.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.SquareEdit: ImageVector by lazy { Builder( name = "Square Edit", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(5.0f, 3.0f) curveTo(3.89f, 3.0f, 3.0f, 3.89f, 3.0f, 5.0f) verticalLineTo(19.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 5.0f, y1 = 21.0f ) horizontalLineTo(19.0f) arcTo( 2.0f, 2.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 21.0f, y1 = 19.0f ) verticalLineTo(12.0f) horizontalLineTo(19.0f) verticalLineTo(19.0f) horizontalLineTo(5.0f) verticalLineTo(5.0f) horizontalLineTo(12.0f) verticalLineTo(3.0f) horizontalLineTo(5.0f) moveTo(17.78f, 4.0f) curveTo(17.61f, 4.0f, 17.43f, 4.07f, 17.3f, 4.2f) lineTo(16.08f, 5.41f) lineTo(18.58f, 7.91f) lineTo(19.8f, 6.7f) curveTo(20.06f, 6.44f, 20.06f, 6.0f, 19.8f, 5.75f) lineTo(18.25f, 4.2f) curveTo(18.12f, 4.07f, 17.95f, 4.0f, 17.78f, 4.0f) moveTo(15.37f, 6.12f) lineTo(8.0f, 13.5f) verticalLineTo(16.0f) horizontalLineTo(10.5f) lineTo(17.87f, 8.62f) lineTo(15.37f, 6.12f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareFoot.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.SquareFoot: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.SquareFoot", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(208f, 840f) quadTo(171f, 840f, 145.5f, 814.5f) quadTo(120f, 789f, 120f, 752f) lineTo(120f, 204f) quadTo(120f, 175f, 147f, 163.5f) quadTo(174f, 152f, 194f, 172f) lineTo(284f, 262f) lineTo(230f, 316f) lineTo(258f, 344f) lineTo(312f, 290f) lineTo(416f, 394f) lineTo(362f, 448f) lineTo(390f, 476f) lineTo(444f, 422f) lineTo(548f, 526f) lineTo(494f, 580f) lineTo(522f, 608f) lineTo(576f, 554f) lineTo(680f, 658f) lineTo(626f, 712f) lineTo(654f, 740f) lineTo(708f, 686f) lineTo(788f, 766f) quadTo(808f, 786f, 796.5f, 813f) quadTo(785f, 840f, 756f, 840f) lineTo(208f, 840f) close() moveTo(240f, 720f) lineTo(572f, 720f) lineTo(240f, 388f) lineTo(240f, 720f) quadTo(240f, 720f, 240f, 720f) quadTo(240f, 720f, 240f, 720f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SquareHarmony.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.SquareHarmony: ImageVector by lazy { Builder( name = "SquareHarmony", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(4.9f, 16.0f, 6.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(16.9f, 16.0f, 18.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveTo(7.1f, 8.0f, 6.0f, 8.0f) reflectiveCurveTo(4.0f, 7.1f, 4.0f, 6.0f) reflectiveCurveTo(4.9f, 4.0f, 6.0f, 4.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(16.9f, 4.0f, 18.0f, 4.0f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stack.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Stack: ImageVector by lazy { Builder( name = "Stack", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(480f, 560f) lineTo(40f, 320f) lineTo(480f, 80f) lineTo(920f, 320f) lineTo(480f, 560f) close() moveTo(480f, 720f) lineTo(63f, 493f) lineTo(147f, 447f) lineTo(480f, 629f) lineTo(813f, 447f) lineTo(897f, 493f) lineTo(480f, 720f) close() moveTo(480f, 880f) lineTo(63f, 653f) lineTo(147f, 607f) lineTo(480f, 789f) lineTo(813f, 607f) lineTo(897f, 653f) lineTo(480f, 880f) close() moveTo(480f, 469f) lineTo(753f, 320f) lineTo(480f, 171f) lineTo(207f, 320f) lineTo(480f, 469f) close() moveTo(480f, 320f) lineTo(480f, 320f) lineTo(480f, 320f) lineTo(480f, 320f) close() } }.build() } val Icons.TwoTone.Stack: ImageVector by lazy(LazyThreadSafetyMode.NONE) { Builder( name = "TwoTone.Stack", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 18f) lineToRelative(-10.425f, -5.675f) lineToRelative(2.1f, -1.15f) lineToRelative(8.325f, 4.55f) lineToRelative(8.325f, -4.55f) lineToRelative(2.1f, 1.15f) lineToRelative(-10.425f, 5.675f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 22f) lineToRelative(-10.425f, -5.675f) lineToRelative(2.1f, -1.15f) lineToRelative(8.325f, 4.55f) lineToRelative(8.325f, -4.55f) lineToRelative(2.1f, 1.15f) lineToRelative(-10.425f, 5.675f) close() } path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 11.725f) lineToRelative(6.825f, -3.725f) lineToRelative(-6.825f, -3.725f) lineToRelative(-6.825f, 3.725f) lineToRelative(6.825f, 3.725f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 8f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) lineToRelative(0f, 0f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 2f) lineTo(1f, 8f) lineToRelative(11f, 6f) lineToRelative(11f, -6f) lineTo(12f, 2f) close() moveTo(5.175f, 8f) lineToRelative(6.825f, -3.725f) lineToRelative(6.825f, 3.725f) lineToRelative(-6.825f, 3.725f) lineToRelative(-6.825f, -3.725f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackSticky.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.StackSticky: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.StackSticky", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(360f, 360f) verticalLineToRelative(440f) horizontalLineToRelative(280f) verticalLineToRelative(-120f) quadToRelative(0f, -17f, 11.5f, -28.5f) reflectiveQuadTo(680f, 640f) horizontalLineToRelative(120f) verticalLineToRelative(-280f) lineTo(360f, 360f) close() moveTo(580f, 580f) close() moveTo(280f, 800f) verticalLineToRelative(-441f) quadToRelative(0f, -33f, 24f, -56f) reflectiveQuadToRelative(57f, -23f) horizontalLineToRelative(439f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(880f, 360f) verticalLineToRelative(287f) quadToRelative(0f, 16f, -6f, 30.5f) reflectiveQuadTo(857f, 703f) lineTo(703f, 857f) quadToRelative(-11f, 11f, -25.5f, 17f) reflectiveQuadTo(647f, 880f) lineTo(360f, 880f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(280f, 800f) close() moveTo(81f, 250f) quadToRelative(-6f, -33f, 13f, -59.5f) reflectiveQuadToRelative(52f, -32.5f) lineToRelative(434f, -77f) quadToRelative(32f, -6f, 58f, 13.5f) reflectiveQuadToRelative(34f, 51.5f) lineToRelative(7f, 31f) quadToRelative(5f, 20f, -6f, 32f) reflectiveQuadToRelative(-26f, 14f) quadToRelative(-15f, 2f, -28.5f, -5.5f) reflectiveQuadTo(600f, 190f) lineToRelative(-7f, -30f) lineToRelative(-433f, 77f) lineToRelative(60f, 344f) quadToRelative(3f, 17f, -6f, 30.5f) reflectiveQuadTo(188f, 628f) quadToRelative(-17f, 3f, -30f, -6.5f) reflectiveQuadTo(142f, 595f) lineTo(81f, 250f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StackStickyOff.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.StackStickyOff: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.StackStickyOff", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(14.825f, 4f) lineToRelative(0.175f, 0.75f) curveToRelative(0.083f, 0.333f, 0.237f, 0.563f, 0.463f, 0.688f) curveToRelative(0.225f, 0.125f, 0.462f, 0.171f, 0.712f, 0.138f) reflectiveCurveToRelative(0.467f, -0.15f, 0.65f, -0.35f) curveToRelative(0.183f, -0.2f, 0.233f, -0.467f, 0.15f, -0.8f) lineToRelative(-0.175f, -0.775f) curveToRelative(-0.133f, -0.533f, -0.417f, -0.963f, -0.85f, -1.288f) reflectiveCurveToRelative(-0.917f, -0.438f, -1.45f, -0.337f) lineToRelative(-9.38f, 1.664f) lineToRelative(1.729f, 1.729f) lineToRelative(7.976f, -1.418f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 7.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-10.975f) curveToRelative(-0.183f, 0f, -0.355f, 0.029f, -0.522f, 0.072f) lineToRelative(1.928f, 1.928f) horizontalLineToRelative(9.569f) verticalLineToRelative(7f) horizontalLineToRelative(-2.569f) lineToRelative(2.785f, 2.785f) lineToRelative(1.21f, -1.21f) curveToRelative(0.183f, -0.183f, 0.325f, -0.396f, 0.425f, -0.638f) curveToRelative(0.1f, -0.242f, 0.15f, -0.496f, 0.15f, -0.763f) verticalLineToRelative(-7.175f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() } path(fill = SolidColor(Color.Black)) { moveTo(2.49f, 3.844f) curveToRelative(-0.395f, -0.395f, -1.036f, -0.395f, -1.431f, 0f) curveToRelative(-0.395f, 0.395f, -0.395f, 1.036f, 0f, 1.431f) lineToRelative(0.965f, 0.965f) curveToRelative(0.001f, 0.004f, 0f, 0.007f, 0.001f, 0.01f) lineToRelative(1.525f, 8.625f) curveToRelative(0.05f, 0.283f, 0.183f, 0.504f, 0.4f, 0.662f) curveToRelative(0.217f, 0.158f, 0.467f, 0.213f, 0.75f, 0.163f) reflectiveCurveToRelative(0.5f, -0.188f, 0.65f, -0.413f) curveToRelative(0.15f, -0.225f, 0.2f, -0.479f, 0.15f, -0.762f) lineToRelative(-1.016f, -5.826f) lineToRelative(2.516f, 2.516f) verticalLineToRelative(8.784f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(7.175f) curveToRelative(0.267f, 0f, 0.521f, -0.05f, 0.763f, -0.15f) curveToRelative(0.16f, -0.066f, 0.302f, -0.157f, 0.437f, -0.26f) lineToRelative(1.244f, 1.244f) curveToRelative(0.395f, 0.395f, 1.036f, 0.395f, 1.431f, 0f) reflectiveCurveToRelative(0.395f, -1.036f, 0f, -1.431f) lineTo(2.49f, 3.844f) close() moveTo(9f, 20f) verticalLineToRelative(-6.784f) lineToRelative(6.785f, 6.784f) horizontalLineToRelative(-6.785f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stacks.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Stacks: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Stacks", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11.512f, 13.662f) curveToRelative(-0.158f, -0.042f, -0.313f, -0.104f, -0.463f, -0.188f) lineTo(2.6f, 8.875f) curveToRelative(-0.183f, -0.1f, -0.313f, -0.225f, -0.387f, -0.375f) reflectiveCurveToRelative(-0.112f, -0.317f, -0.112f, -0.5f) reflectiveCurveToRelative(0.038f, -0.35f, 0.112f, -0.5f) reflectiveCurveToRelative(0.204f, -0.275f, 0.387f, -0.375f) lineTo(11.05f, 2.525f) curveToRelative(0.15f, -0.083f, 0.304f, -0.146f, 0.463f, -0.188f) reflectiveCurveToRelative(0.321f, -0.063f, 0.488f, -0.063f) reflectiveCurveToRelative(0.329f, 0.021f, 0.488f, 0.063f) reflectiveCurveToRelative(0.313f, 0.104f, 0.463f, 0.188f) lineToRelative(8.45f, 4.6f) curveToRelative(0.183f, 0.1f, 0.313f, 0.225f, 0.387f, 0.375f) reflectiveCurveToRelative(0.112f, 0.317f, 0.112f, 0.5f) reflectiveCurveToRelative(-0.038f, 0.35f, -0.112f, 0.5f) reflectiveCurveToRelative(-0.204f, 0.275f, -0.387f, 0.375f) lineToRelative(-8.45f, 4.6f) curveToRelative(-0.15f, 0.083f, -0.304f, 0.146f, -0.463f, 0.188f) reflectiveCurveToRelative(-0.321f, 0.063f, -0.488f, 0.063f) reflectiveCurveToRelative(-0.329f, -0.021f, -0.488f, -0.063f) close() moveTo(12f, 11.725f) lineToRelative(6.825f, -3.725f) lineToRelative(-6.825f, -3.725f) lineToRelative(-6.825f, 3.725f) lineToRelative(6.825f, 3.725f) close() moveTo(12f, 15.725f) lineToRelative(7.85f, -4.275f) curveToRelative(0.033f, -0.017f, 0.192f, -0.058f, 0.475f, -0.125f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) curveToRelative(0f, 0.183f, -0.042f, 0.35f, -0.125f, 0.5f) reflectiveCurveToRelative(-0.217f, 0.275f, -0.4f, 0.375f) lineToRelative(-7.85f, 4.275f) curveToRelative(-0.15f, 0.083f, -0.304f, 0.146f, -0.463f, 0.188f) reflectiveCurveToRelative(-0.321f, 0.063f, -0.488f, 0.063f) reflectiveCurveToRelative(-0.329f, -0.021f, -0.488f, -0.063f) reflectiveCurveToRelative(-0.313f, -0.104f, -0.463f, -0.188f) lineToRelative(-7.85f, -4.275f) curveToRelative(-0.183f, -0.1f, -0.317f, -0.225f, -0.4f, -0.375f) reflectiveCurveToRelative(-0.125f, -0.317f, -0.125f, -0.5f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.083f, 0f, 0.162f, 0.013f, 0.237f, 0.038f) reflectiveCurveToRelative(0.154f, 0.054f, 0.237f, 0.087f) lineToRelative(7.85f, 4.275f) close() moveTo(12f, 19.725f) lineToRelative(7.85f, -4.275f) curveToRelative(0.033f, -0.017f, 0.192f, -0.058f, 0.475f, -0.125f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) curveToRelative(0f, 0.183f, -0.042f, 0.35f, -0.125f, 0.5f) reflectiveCurveToRelative(-0.217f, 0.275f, -0.4f, 0.375f) lineToRelative(-7.85f, 4.275f) curveToRelative(-0.15f, 0.083f, -0.304f, 0.146f, -0.463f, 0.188f) reflectiveCurveToRelative(-0.321f, 0.063f, -0.488f, 0.063f) reflectiveCurveToRelative(-0.329f, -0.021f, -0.488f, -0.063f) reflectiveCurveToRelative(-0.313f, -0.104f, -0.463f, -0.188f) lineToRelative(-7.85f, -4.275f) curveToRelative(-0.183f, -0.1f, -0.317f, -0.225f, -0.4f, -0.375f) reflectiveCurveToRelative(-0.125f, -0.317f, -0.125f, -0.5f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.083f, 0f, 0.162f, 0.013f, 0.237f, 0.038f) reflectiveCurveToRelative(0.154f, 0.054f, 0.237f, 0.087f) lineToRelative(7.85f, 4.275f) close() } }.build() } val Icons.TwoTone.Stacks: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Stacks", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(21.787f, 7.5f) curveToRelative(-0.075f, -0.15f, -0.204f, -0.275f, -0.387f, -0.375f) lineTo(12.95f, 2.525f) curveToRelative(-0.15f, -0.083f, -0.304f, -0.146f, -0.463f, -0.188f) curveToRelative(-0.158f, -0.042f, -0.321f, -0.063f, -0.487f, -0.063f) reflectiveCurveToRelative(-0.329f, 0.021f, -0.487f, 0.063f) curveToRelative(-0.158f, 0.042f, -0.313f, 0.104f, -0.463f, 0.188f) lineTo(2.6f, 7.125f) curveToRelative(-0.183f, 0.1f, -0.313f, 0.225f, -0.387f, 0.375f) curveToRelative(-0.075f, 0.15f, -0.113f, 0.317f, -0.113f, 0.5f) reflectiveCurveToRelative(0.038f, 0.35f, 0.113f, 0.5f) curveToRelative(0.075f, 0.15f, 0.204f, 0.275f, 0.387f, 0.375f) lineToRelative(8.45f, 4.6f) curveToRelative(0.15f, 0.083f, 0.304f, 0.146f, 0.463f, 0.188f) curveToRelative(0.158f, 0.042f, 0.321f, 0.063f, 0.487f, 0.063f) reflectiveCurveToRelative(0.329f, -0.021f, 0.487f, -0.063f) curveToRelative(0.158f, -0.042f, 0.313f, -0.104f, 0.463f, -0.188f) lineToRelative(8.45f, -4.6f) curveToRelative(0.183f, -0.1f, 0.313f, -0.225f, 0.387f, -0.375f) curveToRelative(0.075f, -0.15f, 0.113f, -0.317f, 0.113f, -0.5f) reflectiveCurveToRelative(-0.038f, -0.35f, -0.113f, -0.5f) close() moveTo(12f, 11.725f) lineToRelative(-6.825f, -3.725f) lineToRelative(6.825f, -3.725f) lineToRelative(6.825f, 3.725f) lineToRelative(-6.825f, 3.725f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 15.725f) lineToRelative(7.85f, -4.275f) curveToRelative(0.033f, -0.017f, 0.192f, -0.058f, 0.475f, -0.125f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) curveToRelative(0f, 0.183f, -0.042f, 0.35f, -0.125f, 0.5f) reflectiveCurveToRelative(-0.217f, 0.275f, -0.4f, 0.375f) lineToRelative(-7.85f, 4.275f) curveToRelative(-0.15f, 0.083f, -0.304f, 0.146f, -0.463f, 0.188f) reflectiveCurveToRelative(-0.321f, 0.063f, -0.488f, 0.063f) reflectiveCurveToRelative(-0.329f, -0.021f, -0.488f, -0.063f) reflectiveCurveToRelative(-0.313f, -0.104f, -0.463f, -0.188f) lineToRelative(-7.85f, -4.275f) curveToRelative(-0.183f, -0.1f, -0.317f, -0.225f, -0.4f, -0.375f) reflectiveCurveToRelative(-0.125f, -0.317f, -0.125f, -0.5f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.083f, 0f, 0.162f, 0.013f, 0.237f, 0.038f) reflectiveCurveToRelative(0.154f, 0.054f, 0.237f, 0.087f) lineToRelative(7.85f, 4.275f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 19.725f) lineToRelative(7.85f, -4.275f) curveToRelative(0.033f, -0.017f, 0.192f, -0.058f, 0.475f, -0.125f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) curveToRelative(0f, 0.183f, -0.042f, 0.35f, -0.125f, 0.5f) reflectiveCurveToRelative(-0.217f, 0.275f, -0.4f, 0.375f) lineToRelative(-7.85f, 4.275f) curveToRelative(-0.15f, 0.083f, -0.304f, 0.146f, -0.463f, 0.188f) reflectiveCurveToRelative(-0.321f, 0.063f, -0.488f, 0.063f) reflectiveCurveToRelative(-0.329f, -0.021f, -0.488f, -0.063f) reflectiveCurveToRelative(-0.313f, -0.104f, -0.463f, -0.188f) lineToRelative(-7.85f, -4.275f) curveToRelative(-0.183f, -0.1f, -0.317f, -0.225f, -0.4f, -0.375f) reflectiveCurveToRelative(-0.125f, -0.317f, -0.125f, -0.5f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.083f, 0f, 0.162f, 0.013f, 0.237f, 0.038f) reflectiveCurveToRelative(0.154f, 0.054f, 0.237f, 0.087f) lineToRelative(7.85f, 4.275f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 11.725f) lineToRelative(6.825f, -3.725f) lineToRelative(-6.825f, -3.725f) lineToRelative(-6.825f, 3.725f) lineToRelative(6.825f, 3.725f) close() } }.build() } val Icons.Rounded.Stacks: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Stacks", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(21.787f, 7.5f) curveToRelative(-0.075f, -0.15f, -0.204f, -0.275f, -0.387f, -0.375f) lineTo(12.95f, 2.525f) curveToRelative(-0.15f, -0.083f, -0.304f, -0.146f, -0.463f, -0.188f) curveToRelative(-0.158f, -0.042f, -0.321f, -0.063f, -0.487f, -0.063f) reflectiveCurveToRelative(-0.329f, 0.021f, -0.487f, 0.063f) curveToRelative(-0.158f, 0.042f, -0.313f, 0.104f, -0.463f, 0.188f) lineTo(2.6f, 7.125f) curveToRelative(-0.183f, 0.1f, -0.313f, 0.225f, -0.387f, 0.375f) curveToRelative(-0.075f, 0.15f, -0.113f, 0.317f, -0.113f, 0.5f) reflectiveCurveToRelative(0.038f, 0.35f, 0.113f, 0.5f) curveToRelative(0.075f, 0.15f, 0.204f, 0.275f, 0.387f, 0.375f) lineToRelative(8.45f, 4.6f) curveToRelative(0.15f, 0.083f, 0.304f, 0.146f, 0.463f, 0.188f) curveToRelative(0.158f, 0.042f, 0.321f, 0.063f, 0.487f, 0.063f) reflectiveCurveToRelative(0.329f, -0.021f, 0.487f, -0.063f) curveToRelative(0.158f, -0.042f, 0.313f, -0.104f, 0.463f, -0.188f) lineToRelative(8.45f, -4.6f) curveToRelative(0.183f, -0.1f, 0.313f, -0.225f, 0.387f, -0.375f) curveToRelative(0.075f, -0.15f, 0.113f, -0.317f, 0.113f, -0.5f) reflectiveCurveToRelative(-0.038f, -0.35f, -0.113f, -0.5f) close() moveTo(12f, 11.725f) lineToRelative(-6.825f, -3.725f) lineToRelative(6.825f, -3.725f) lineToRelative(6.825f, 3.725f) lineToRelative(-6.825f, 3.725f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 15.725f) lineToRelative(7.85f, -4.275f) curveToRelative(0.033f, -0.017f, 0.192f, -0.058f, 0.475f, -0.125f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) curveToRelative(0f, 0.183f, -0.042f, 0.35f, -0.125f, 0.5f) reflectiveCurveToRelative(-0.217f, 0.275f, -0.4f, 0.375f) lineToRelative(-7.85f, 4.275f) curveToRelative(-0.15f, 0.083f, -0.304f, 0.146f, -0.463f, 0.188f) reflectiveCurveToRelative(-0.321f, 0.063f, -0.488f, 0.063f) reflectiveCurveToRelative(-0.329f, -0.021f, -0.488f, -0.063f) reflectiveCurveToRelative(-0.313f, -0.104f, -0.463f, -0.188f) lineToRelative(-7.85f, -4.275f) curveToRelative(-0.183f, -0.1f, -0.317f, -0.225f, -0.4f, -0.375f) reflectiveCurveToRelative(-0.125f, -0.317f, -0.125f, -0.5f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.083f, 0f, 0.162f, 0.013f, 0.237f, 0.038f) reflectiveCurveToRelative(0.154f, 0.054f, 0.237f, 0.087f) lineToRelative(7.85f, 4.275f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12f, 19.725f) lineToRelative(7.85f, -4.275f) curveToRelative(0.033f, -0.017f, 0.192f, -0.058f, 0.475f, -0.125f) curveToRelative(0.283f, 0f, 0.521f, 0.096f, 0.712f, 0.287f) reflectiveCurveToRelative(0.287f, 0.429f, 0.287f, 0.712f) curveToRelative(0f, 0.183f, -0.042f, 0.35f, -0.125f, 0.5f) reflectiveCurveToRelative(-0.217f, 0.275f, -0.4f, 0.375f) lineToRelative(-7.85f, 4.275f) curveToRelative(-0.15f, 0.083f, -0.304f, 0.146f, -0.463f, 0.188f) reflectiveCurveToRelative(-0.321f, 0.063f, -0.488f, 0.063f) reflectiveCurveToRelative(-0.329f, -0.021f, -0.488f, -0.063f) reflectiveCurveToRelative(-0.313f, -0.104f, -0.463f, -0.188f) lineToRelative(-7.85f, -4.275f) curveToRelative(-0.183f, -0.1f, -0.317f, -0.225f, -0.4f, -0.375f) reflectiveCurveToRelative(-0.125f, -0.317f, -0.125f, -0.5f) curveToRelative(0f, -0.283f, 0.096f, -0.521f, 0.287f, -0.712f) reflectiveCurveToRelative(0.429f, -0.287f, 0.712f, -0.287f) curveToRelative(0.083f, 0f, 0.162f, 0.013f, 0.237f, 0.038f) reflectiveCurveToRelative(0.154f, 0.054f, 0.237f, 0.087f) lineToRelative(7.85f, 4.275f) close() } path( fill = SolidColor(Color.Black) ) { moveTo(12f, 11.725f) lineToRelative(6.825f, -3.725f) lineToRelative(-6.825f, -3.725f) lineToRelative(-6.825f, 3.725f) lineToRelative(6.825f, 3.725f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StampedLine.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.StampedLine: ImageVector by lazy { ImageVector.Builder( name = "StampedLine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(7.427f, 17.754f) curveToRelative(0.116f, 0.124f, 0.118f, 0.317f, 0.003f, 0.443f) lineToRelative(0f, 0f) curveToRelative(-0.072f, 0.079f, -0.101f, 0.189f, -0.077f, 0.294f) lineToRelative(0f, 0f) curveToRelative(0.039f, 0.166f, -0.057f, 0.333f, -0.219f, 0.385f) lineToRelative(0f, 0f) curveToRelative(-0.102f, 0.033f, -0.182f, 0.113f, -0.213f, 0.216f) lineToRelative(0f, 0f) curveToRelative(-0.05f, 0.163f, -0.216f, 0.26f, -0.382f, 0.224f) lineToRelative(0f, 0f) curveToRelative(-0.105f, -0.023f, -0.214f, 0.007f, -0.293f, 0.081f) lineToRelative(0f, 0f) curveToRelative(-0.124f, 0.116f, -0.317f, 0.118f, -0.443f, 0.003f) lineToRelative(0f, 0f) curveToRelative(-0.079f, -0.072f, -0.189f, -0.101f, -0.294f, -0.077f) lineToRelative(0f, 0f) curveToRelative(-0.166f, 0.039f, -0.333f, -0.057f, -0.385f, -0.219f) lineToRelative(0f, 0f) curveToRelative(-0.033f, -0.102f, -0.113f, -0.182f, -0.216f, -0.213f) lineToRelative(0f, 0f) curveToRelative(-0.163f, -0.05f, -0.26f, -0.216f, -0.224f, -0.382f) lineToRelative(0f, 0f) curveToRelative(0.023f, -0.105f, -0.007f, -0.214f, -0.081f, -0.293f) lineToRelative(0f, 0f) curveToRelative(-0.116f, -0.124f, -0.118f, -0.317f, -0.003f, -0.443f) lineToRelative(0f, 0f) curveToRelative(0.072f, -0.079f, 0.101f, -0.189f, 0.077f, -0.294f) lineToRelative(0f, 0f) curveToRelative(-0.039f, -0.166f, 0.057f, -0.333f, 0.219f, -0.385f) lineToRelative(0f, 0f) curveToRelative(0.102f, -0.033f, 0.182f, -0.113f, 0.213f, -0.216f) lineToRelative(0f, 0f) curveToRelative(0.05f, -0.163f, 0.216f, -0.26f, 0.382f, -0.224f) lineToRelative(0f, 0f) curveToRelative(0.105f, 0.023f, 0.214f, -0.007f, 0.293f, -0.081f) lineToRelative(0f, 0f) curveToRelative(0.124f, -0.116f, 0.317f, -0.118f, 0.443f, -0.003f) lineToRelative(0f, 0f) curveToRelative(0.079f, 0.072f, 0.189f, 0.101f, 0.294f, 0.077f) lineToRelative(0f, 0f) curveToRelative(0.166f, -0.039f, 0.333f, 0.057f, 0.385f, 0.219f) lineToRelative(0f, 0f) curveToRelative(0.033f, 0.102f, 0.113f, 0.182f, 0.216f, 0.213f) lineToRelative(0f, 0f) curveToRelative(0.163f, 0.05f, 0.26f, 0.216f, 0.224f, 0.382f) lineToRelative(0f, 0f) curveTo(7.324f, 17.566f, 7.354f, 17.676f, 7.427f, 17.754f) lineTo(7.427f, 17.754f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(18.015f, 4.515f) curveToRelative(-0.022f, -0.011f, -0.048f, -0.011f, -0.07f, 0.002f) curveToRelative(-0.123f, 0.071f, -0.537f, 0.306f, -0.629f, 0.306f) curveToRelative(-0.076f, 0f, -0.432f, -0.158f, -0.642f, -0.254f) curveToRelative(-0.059f, -0.027f, -0.12f, 0.029f, -0.1f, 0.09f) curveToRelative(0.078f, 0.233f, 0.21f, 0.639f, 0.187f, 0.678f) curveToRelative(-0.029f, 0.047f, -0.214f, 0.527f, -0.264f, 0.654f) curveToRelative(-0.008f, 0.02f, -0.006f, 0.041f, 0.004f, 0.06f) curveToRelative(0.062f, 0.117f, 0.286f, 0.554f, 0.268f, 0.652f) curveToRelative(-0.015f, 0.08f, -0.15f, 0.435f, -0.228f, 0.638f) curveToRelative(-0.022f, 0.058f, 0.032f, 0.115f, 0.091f, 0.096f) curveToRelative(0.224f, -0.071f, 0.632f, -0.2f, 0.668f, -0.2f) curveToRelative(0.044f, 0f, 0.563f, 0.212f, 0.69f, 0.264f) curveToRelative(0.018f, 0.008f, 0.038f, 0.007f, 0.056f, 0f) curveToRelative(0.129f, -0.054f, 0.664f, -0.277f, 0.686f, -0.277f) curveToRelative(0.018f, 0f, 0.369f, 0.134f, 0.569f, 0.211f) curveToRelative(0.056f, 0.022f, 0.113f, -0.03f, 0.097f, -0.088f) curveToRelative(-0.058f, -0.209f, -0.16f, -0.584f, -0.157f, -0.628f) curveToRelative(0.004f, -0.055f, 0.198f, -0.573f, 0.248f, -0.704f) curveToRelative(0.007f, -0.019f, 0.006f, -0.04f, -0.003f, -0.059f) curveToRelative(-0.057f, -0.114f, -0.265f, -0.537f, -0.261f, -0.645f) curveToRelative(0.003f, -0.085f, 0.122f, -0.434f, 0.198f, -0.646f) curveToRelative(0.022f, -0.062f, -0.042f, -0.119f, -0.101f, -0.091f) curveToRelative(-0.218f, 0.104f, -0.583f, 0.277f, -0.615f, 0.277f) curveTo(18.667f, 4.852f, 18.155f, 4.588f, 18.015f, 4.515f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(7.424f, 15.775f) curveToRelative(0f, 0.399f, 0.321f, 0.722f, 0.717f, 0.722f) curveToRelative(0.108f, 0f, 0.21f, -0.024f, 0.302f, -0.067f) curveToRelative(0f, 0f, 0.001f, -0f, 0.002f, -0.001f) curveToRelative(0.009f, -0.004f, 0.017f, -0.008f, 0.026f, -0.013f) curveToRelative(0.085f, -0.041f, 0.346f, -0.165f, 0.468f, -0.174f) curveToRelative(0.146f, -0.011f, 0.403f, 0.068f, 0.403f, 0.068f) lineToRelative(0.216f, 0.104f) curveToRelative(0.014f, 0.007f, 0.028f, 0.014f, 0.043f, 0.021f) lineToRelative(0.001f, 0f) horizontalLineToRelative(0f) curveToRelative(0.061f, 0.027f, 0.125f, 0.045f, 0.193f, 0.054f) horizontalLineToRelative(0f) lineToRelative(0f, 0f) curveToRelative(0.018f, 0.002f, 0.036f, 0.004f, 0.055f, 0.005f) horizontalLineToRelative(0f) curveToRelative(0.013f, 0.001f, 0.026f, 0.001f, 0.039f, 0.001f) curveToRelative(0.396f, 0f, 0.717f, -0.324f, 0.717f, -0.722f) verticalLineTo(15.772f) curveToRelative(0f, -0.135f, -0.037f, -0.262f, -0.101f, -0.37f) lineTo(9.664f, 13.902f) curveToRelative(-0.118f, -0.237f, -0.361f, -0.4f, -0.642f, -0.4f) curveToRelative(-0.281f, 0f, -0.524f, 0.163f, -0.642f, 0.4f) lineToRelative(-0.839f, 1.473f) curveToRelative(-0.016f, 0.024f, -0.03f, 0.05f, -0.043f, 0.076f) lineTo(7.498f, 15.452f) curveToRelative(-0.047f, 0.096f, -0.074f, 0.205f, -0.074f, 0.32f) verticalLineToRelative(0.003f) horizontalLineTo(7.424f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(12f, 10.5f) curveToRelative(-0.828f, 0f, -1.5f, 0.672f, -1.5f, 1.5f) curveToRelative(0f, 0.014f, 0f, 0.029f, 0.001f, 0.043f) curveTo(10.5f, 12.05f, 10.5f, 12.057f, 10.5f, 12.064f) verticalLineToRelative(1.085f) curveTo(10.5f, 13.343f, 10.657f, 13.5f, 10.851f, 13.5f) horizontalLineToRelative(1.105f) curveToRelative(0.005f, 0f, 0.01f, -0f, 0.014f, -0f) curveTo(11.98f, 13.5f, 11.99f, 13.5f, 12f, 13.5f) curveToRelative(0.828f, 0f, 1.5f, -0.672f, 1.5f, -1.5f) reflectiveCurveTo(12.828f, 10.5f, 12f, 10.5f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(16.132f, 10.14f) curveToRelative(0f, 0f, -0.096f, -0.124f, 0f, -0.301f) curveToRelative(0.599f, -0.999f, 0.285f, -2.17f, 0.285f, -2.17f) lineToRelative(-0.285f, 0.204f) curveToRelative(0f, 0f, -0.131f, 0.088f, -0.293f, 0f) curveToRelative(-1.002f, -0.598f, -2.177f, -0.285f, -2.177f, -0.285f) lineToRelative(0.205f, 0.285f) curveToRelative(0f, 0f, 0.105f, 0.107f, 0f, 0.302f) curveToRelative(-0.004f, 0.006f, -0.006f, 0.012f, -0.01f, 0.017f) curveToRelative(-0.586f, 0.995f, -0.275f, 2.153f, -0.275f, 2.153f) lineToRelative(0.285f, -0.204f) curveToRelative(0f, 0f, 0.124f, -0.092f, 0.293f, 0f) curveToRelative(1.002f, 0.598f, 2.177f, 0.285f, 2.177f, 0.285f) lineTo(16.132f, 10.14f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StarSticky.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.StarSticky: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.StarSticky", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.412f, 3.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(5f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(14f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(10.175f) curveToRelative(0.267f, 0f, 0.521f, -0.05f, 0.763f, -0.15f) curveToRelative(0.242f, -0.1f, 0.454f, -0.242f, 0.638f, -0.425f) lineToRelative(3.85f, -3.85f) curveToRelative(0.183f, -0.183f, 0.325f, -0.396f, 0.425f, -0.638f) curveToRelative(0.1f, -0.242f, 0.15f, -0.496f, 0.15f, -0.763f) verticalLineTo(5f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(19f, 15f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(2f) horizontalLineTo(5f) verticalLineTo(5f) horizontalLineToRelative(14f) verticalLineToRelative(10f) close() } path(fill = SolidColor(Color.Black)) { moveTo(5f, 19f) verticalLineTo(5f) verticalLineToRelative(14f) close() } path(fill = SolidColor(Color.Black)) { moveTo(12.11f, 13.595f) lineToRelative(1.938f, 1.454f) curveToRelative(0.114f, 0.095f, 0.223f, 0.105f, 0.328f, 0.029f) reflectiveCurveToRelative(0.138f, -0.181f, 0.1f, -0.314f) lineToRelative(-0.713f, -2.423f) lineToRelative(1.995f, -1.596f) curveToRelative(0.095f, -0.095f, 0.124f, -0.204f, 0.086f, -0.328f) reflectiveCurveToRelative(-0.124f, -0.185f, -0.257f, -0.185f) horizontalLineToRelative(-2.451f) lineToRelative(-0.741f, -2.337f) curveToRelative(-0.038f, -0.133f, -0.133f, -0.2f, -0.285f, -0.2f) reflectiveCurveToRelative(-0.247f, 0.067f, -0.285f, 0.2f) lineToRelative(-0.741f, 2.337f) horizontalLineToRelative(-2.451f) curveToRelative(-0.133f, 0f, -0.219f, 0.062f, -0.257f, 0.185f) reflectiveCurveToRelative(-0.01f, 0.233f, 0.086f, 0.328f) lineToRelative(1.995f, 1.596f) lineToRelative(-0.713f, 2.423f) curveToRelative(-0.038f, 0.133f, -0.005f, 0.238f, 0.1f, 0.314f) reflectiveCurveToRelative(0.214f, 0.067f, 0.328f, -0.029f) lineToRelative(1.938f, -1.454f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/StickerEmoji.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.StickerEmoji: ImageVector by lazy { ImageVector.Builder( name = "Outlined.StickerEmoji", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(5.5f, 2f) curveTo(3.56f, 2f, 2f, 3.56f, 2f, 5.5f) verticalLineTo(18.5f) curveTo(2f, 20.44f, 3.56f, 22f, 5.5f, 22f) horizontalLineTo(16f) lineTo(22f, 16f) verticalLineTo(5.5f) curveTo(22f, 3.56f, 20.44f, 2f, 18.5f, 2f) horizontalLineTo(5.5f) moveTo(5.75f, 4f) horizontalLineTo(18.25f) arcTo(1.75f, 1.75f, 0f, isMoreThanHalf = false, isPositiveArc = true, 20f, 5.75f) verticalLineTo(15f) horizontalLineTo(18.5f) curveTo(16.56f, 15f, 15f, 16.56f, 15f, 18.5f) verticalLineTo(20f) horizontalLineTo(5.75f) arcTo(1.75f, 1.75f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 18.25f) verticalLineTo(5.75f) arcTo(1.75f, 1.75f, 0f, isMoreThanHalf = false, isPositiveArc = true, 5.75f, 4f) moveTo(14.44f, 6.77f) curveTo(14.28f, 6.77f, 14.12f, 6.79f, 13.97f, 6.83f) curveTo(13.03f, 7.09f, 12.5f, 8.05f, 12.74f, 9f) curveTo(12.79f, 9.15f, 12.86f, 9.3f, 12.95f, 9.44f) lineTo(16.18f, 8.56f) curveTo(16.18f, 8.39f, 16.16f, 8.22f, 16.12f, 8.05f) curveTo(15.91f, 7.3f, 15.22f, 6.77f, 14.44f, 6.77f) moveTo(8.17f, 8.5f) curveTo(8f, 8.5f, 7.85f, 8.5f, 7.7f, 8.55f) curveTo(6.77f, 8.81f, 6.22f, 9.77f, 6.47f, 10.7f) curveTo(6.5f, 10.86f, 6.59f, 11f, 6.68f, 11.16f) lineTo(9.91f, 10.28f) curveTo(9.91f, 10.11f, 9.89f, 9.94f, 9.85f, 9.78f) curveTo(9.64f, 9f, 8.95f, 8.5f, 8.17f, 8.5f) moveTo(16.72f, 11.26f) lineTo(7.59f, 13.77f) curveTo(8.91f, 15.3f, 11f, 15.94f, 12.95f, 15.41f) curveTo(14.9f, 14.87f, 16.36f, 13.25f, 16.72f, 11.26f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Stylus.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Stylus: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "StylusFountainPen", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(18.727f, 8.55f) curveToRelative(-0.044f, -0.177f, -0.137f, -0.336f, -0.279f, -0.478f) lineToRelative(-5.732f, -5.281f) curveToRelative(-0.195f, -0.195f, -0.433f, -0.292f, -0.716f, -0.292f) reflectiveCurveToRelative(-0.522f, 0.097f, -0.716f, 0.292f) lineToRelative(-5.732f, 5.281f) curveToRelative(-0.141f, 0.142f, -0.234f, 0.301f, -0.279f, 0.478f) curveToRelative(-0.044f, 0.177f, -0.049f, 0.363f, -0.013f, 0.557f) lineToRelative(1.937f, 8.12f) curveToRelative(0.053f, 0.248f, 0.177f, 0.447f, 0.372f, 0.597f) reflectiveCurveToRelative(0.416f, 0.226f, 0.663f, 0.226f) lineToRelative(7.536f, -0f) curveToRelative(0.248f, 0f, 0.469f, -0.075f, 0.663f, -0.226f) reflectiveCurveToRelative(0.318f, -0.349f, 0.372f, -0.597f) lineToRelative(1.937f, -8.12f) curveToRelative(0.035f, -0.195f, 0.031f, -0.38f, -0.013f, -0.557f) close() moveTo(14.919f, 15.927f) lineToRelative(-5.838f, 0f) lineToRelative(-1.619f, -6.714f) lineToRelative(3.476f, -3.211f) verticalLineToRelative(2.813f) curveToRelative(-0.248f, 0.177f, -0.442f, 0.398f, -0.584f, 0.663f) curveToRelative(-0.142f, 0.265f, -0.212f, 0.557f, -0.212f, 0.876f) curveToRelative(0f, 0.513f, 0.181f, 0.951f, 0.544f, 1.314f) curveToRelative(0.363f, 0.363f, 0.8f, 0.544f, 1.314f, 0.544f) reflectiveCurveToRelative(0.951f, -0.181f, 1.314f, -0.544f) curveToRelative(0.363f, -0.363f, 0.544f, -0.8f, 0.544f, -1.314f) curveToRelative(0f, -0.318f, -0.071f, -0.61f, -0.212f, -0.876f) curveToRelative(-0.141f, -0.265f, -0.336f, -0.486f, -0.584f, -0.663f) lineToRelative(-0f, -2.813f) lineToRelative(3.476f, 3.211f) lineToRelative(-1.619f, 6.714f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8.22f, 18.846f) lineTo(15.78f, 18.846f) arcTo( 0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 16.777f, 19.843f ) lineTo(16.777f, 20.503f) arcTo(0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 15.78f, 21.5f) lineTo(8.22f, 21.5f) arcTo(0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 7.223f, 20.503f) lineTo(7.223f, 19.843f) arcTo(0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.22f, 18.846f) close() } }.build() } val Icons.TwoTone.Stylus: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoToneStylusFountainPen", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(18.727f, 8.55f) curveToRelative(-0.044f, -0.177f, -0.137f, -0.336f, -0.279f, -0.478f) lineToRelative(-5.732f, -5.281f) curveToRelative(-0.195f, -0.195f, -0.433f, -0.292f, -0.716f, -0.292f) reflectiveCurveToRelative(-0.522f, 0.097f, -0.716f, 0.292f) lineToRelative(-5.732f, 5.281f) curveToRelative(-0.141f, 0.142f, -0.234f, 0.301f, -0.279f, 0.478f) curveToRelative(-0.044f, 0.177f, -0.049f, 0.363f, -0.013f, 0.557f) lineToRelative(1.937f, 8.12f) curveToRelative(0.053f, 0.248f, 0.177f, 0.447f, 0.372f, 0.597f) reflectiveCurveToRelative(0.416f, 0.226f, 0.663f, 0.226f) lineToRelative(7.536f, -0f) curveToRelative(0.248f, 0f, 0.469f, -0.075f, 0.663f, -0.226f) reflectiveCurveToRelative(0.318f, -0.349f, 0.372f, -0.597f) lineToRelative(1.937f, -8.12f) curveToRelative(0.035f, -0.195f, 0.031f, -0.38f, -0.013f, -0.557f) close() moveTo(14.919f, 15.927f) lineToRelative(-5.838f, 0f) lineToRelative(-1.619f, -6.714f) lineToRelative(3.476f, -3.211f) verticalLineToRelative(2.813f) curveToRelative(-0.248f, 0.177f, -0.442f, 0.398f, -0.584f, 0.663f) curveToRelative(-0.142f, 0.265f, -0.212f, 0.557f, -0.212f, 0.876f) curveToRelative(0f, 0.513f, 0.181f, 0.951f, 0.544f, 1.314f) curveToRelative(0.363f, 0.363f, 0.8f, 0.544f, 1.314f, 0.544f) reflectiveCurveToRelative(0.951f, -0.181f, 1.314f, -0.544f) curveToRelative(0.363f, -0.363f, 0.544f, -0.8f, 0.544f, -1.314f) curveToRelative(0f, -0.318f, -0.071f, -0.61f, -0.212f, -0.876f) curveToRelative(-0.141f, -0.265f, -0.336f, -0.486f, -0.584f, -0.663f) lineToRelative(-0f, -2.813f) lineToRelative(3.476f, 3.211f) lineToRelative(-1.619f, 6.714f) close() } path(fill = SolidColor(Color.Black)) { moveTo(8.22f, 18.846f) lineTo(15.78f, 18.846f) arcTo( 0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 16.777f, 19.843f ) lineTo(16.777f, 20.503f) arcTo(0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 15.78f, 21.5f) lineTo(8.22f, 21.5f) arcTo(0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 7.223f, 20.503f) lineTo(7.223f, 19.843f) arcTo(0.997f, 0.997f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.22f, 18.846f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(14.919f, 15.927f) lineToRelative(-5.838f, 0f) lineToRelative(-1.619f, -6.714f) lineToRelative(3.476f, -3.211f) lineToRelative(0f, 2.813f) curveToRelative(-0.248f, 0.177f, -0.442f, 0.398f, -0.584f, 0.663f) curveToRelative(-0.142f, 0.265f, -0.212f, 0.557f, -0.212f, 0.876f) curveToRelative(0f, 0.513f, 0.181f, 0.951f, 0.544f, 1.314f) reflectiveCurveToRelative(0.8f, 0.544f, 1.314f, 0.544f) reflectiveCurveToRelative(0.951f, -0.181f, 1.314f, -0.544f) reflectiveCurveToRelative(0.544f, -0.8f, 0.544f, -1.314f) curveToRelative(0f, -0.318f, -0.071f, -0.61f, -0.212f, -0.876f) curveToRelative(-0.141f, -0.265f, -0.336f, -0.486f, -0.584f, -0.663f) lineToRelative(-0f, -2.813f) lineToRelative(3.476f, 3.211f) lineToRelative(-1.619f, 6.714f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Suffix.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Suffix: ImageVector by lazy { Builder( name = "Suffix", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.8f, 4.6f) horizontalLineToRelative(-1.5f) lineToRelative(-4.8f, 11.0f) horizontalLineToRelative(2.1f) lineToRelative(0.9f, -2.2f) horizontalLineToRelative(5.0f) lineToRelative(0.9f, 2.2f) horizontalLineToRelative(2.1f) lineTo(12.8f, 4.6f) close() moveTo(10.1f, 11.6f) lineToRelative(1.9f, -5.0f) lineToRelative(1.9f, 5.0f) horizontalLineTo(10.1f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(19.0f, 14.7f) lineToRelative(0.0f, 2.5f) lineToRelative(-12.0f, 0.0f) lineToRelative(-2.3f, 0.0f) lineToRelative(-1.3f, 0.0f) lineToRelative(0.0f, 2.0f) lineToRelative(1.3f, 0.0f) lineToRelative(2.3f, 0.0f) lineToRelative(12.0f, 0.0f) lineToRelative(2.0f, 0.0f) lineToRelative(0.0f, -2.0f) lineToRelative(0.0f, -2.5f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Svg.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Svg: ImageVector by lazy { Builder( name = "Svg", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(5.13f, 10.71f) horizontalLineTo(8.87f) lineTo(6.22f, 8.06f) curveTo(5.21f, 8.06f, 4.39f, 7.24f, 4.39f, 6.22f) arcTo( 1.83f, 1.83f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 6.22f, y1 = 4.39f ) curveTo(7.24f, 4.39f, 8.06f, 5.21f, 8.06f, 6.22f) lineTo(10.71f, 8.87f) verticalLineTo(5.13f) curveTo(10.0f, 4.41f, 10.0f, 3.25f, 10.71f, 2.54f) curveTo(11.42f, 1.82f, 12.58f, 1.82f, 13.29f, 2.54f) curveTo(14.0f, 3.25f, 14.0f, 4.41f, 13.29f, 5.13f) verticalLineTo(8.87f) lineTo(15.95f, 6.22f) curveTo(15.95f, 5.21f, 16.76f, 4.39f, 17.78f, 4.39f) curveTo(18.79f, 4.39f, 19.61f, 5.21f, 19.61f, 6.22f) curveTo(19.61f, 7.24f, 18.79f, 8.06f, 17.78f, 8.06f) lineTo(15.13f, 10.71f) horizontalLineTo(18.87f) curveTo(19.59f, 10.0f, 20.75f, 10.0f, 21.46f, 10.71f) curveTo(22.18f, 11.42f, 22.18f, 12.58f, 21.46f, 13.29f) curveTo(20.75f, 14.0f, 19.59f, 14.0f, 18.87f, 13.29f) horizontalLineTo(15.13f) lineTo(17.78f, 15.95f) curveTo(18.79f, 15.95f, 19.61f, 16.76f, 19.61f, 17.78f) arcTo( 1.83f, 1.83f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, x1 = 17.78f, y1 = 19.61f ) curveTo(16.76f, 19.61f, 15.95f, 18.79f, 15.95f, 17.78f) lineTo(13.29f, 15.13f) verticalLineTo(18.87f) curveTo(14.0f, 19.59f, 14.0f, 20.75f, 13.29f, 21.46f) curveTo(12.58f, 22.18f, 11.42f, 22.18f, 10.71f, 21.46f) curveTo(10.0f, 20.75f, 10.0f, 19.59f, 10.71f, 18.87f) verticalLineTo(15.13f) lineTo(8.06f, 17.78f) curveTo(8.06f, 18.79f, 7.24f, 19.61f, 6.22f, 19.61f) curveTo(5.21f, 19.61f, 4.39f, 18.79f, 4.39f, 17.78f) curveTo(4.39f, 16.76f, 5.21f, 15.95f, 6.22f, 15.95f) lineTo(8.87f, 13.29f) horizontalLineTo(5.13f) curveTo(4.41f, 14.0f, 3.25f, 14.0f, 2.54f, 13.29f) curveTo(1.82f, 12.58f, 1.82f, 11.42f, 2.54f, 10.71f) curveTo(3.25f, 10.0f, 4.41f, 10.0f, 5.13f, 10.71f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SwapVerticalCircle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SwapVerticalCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "SwapVerticalCircle", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(9f, 8.85f) verticalLineToRelative(3.15f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) verticalLineToRelative(-3.15f) lineToRelative(0.9f, 0.875f) curveToRelative(0.183f, 0.183f, 0.412f, 0.275f, 0.688f, 0.275f) reflectiveCurveToRelative(0.512f, -0.1f, 0.712f, -0.3f) curveToRelative(0.183f, -0.183f, 0.275f, -0.417f, 0.275f, -0.7f) reflectiveCurveToRelative(-0.092f, -0.517f, -0.275f, -0.7f) lineToRelative(-2.575f, -2.575f) curveToRelative(-0.2f, -0.2f, -0.438f, -0.3f, -0.712f, -0.3f) reflectiveCurveToRelative(-0.512f, 0.1f, -0.712f, 0.3f) lineToRelative(-2.6f, 2.575f) curveToRelative(-0.183f, 0.183f, -0.279f, 0.412f, -0.287f, 0.688f) reflectiveCurveToRelative(0.087f, 0.512f, 0.287f, 0.712f) curveToRelative(0.183f, 0.183f, 0.412f, 0.279f, 0.688f, 0.287f) reflectiveCurveToRelative(0.512f, -0.079f, 0.712f, -0.262f) lineToRelative(0.9f, -0.875f) close() moveTo(13f, 15.15f) lineToRelative(-0.9f, -0.875f) curveToRelative(-0.183f, -0.183f, -0.412f, -0.275f, -0.688f, -0.275f) reflectiveCurveToRelative(-0.512f, 0.1f, -0.712f, 0.3f) curveToRelative(-0.183f, 0.183f, -0.275f, 0.417f, -0.275f, 0.7f) reflectiveCurveToRelative(0.092f, 0.517f, 0.275f, 0.7f) lineToRelative(2.6f, 2.6f) curveToRelative(0.2f, 0.2f, 0.438f, 0.3f, 0.712f, 0.3f) reflectiveCurveToRelative(0.512f, -0.1f, 0.712f, -0.3f) lineToRelative(2.575f, -2.6f) curveToRelative(0.183f, -0.183f, 0.279f, -0.412f, 0.287f, -0.688f) reflectiveCurveToRelative(-0.087f, -0.512f, -0.287f, -0.712f) curveToRelative(-0.183f, -0.183f, -0.412f, -0.279f, -0.688f, -0.287f) reflectiveCurveToRelative(-0.512f, 0.079f, -0.712f, 0.262f) lineToRelative(-0.9f, 0.875f) verticalLineToRelative(-3.15f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(3.15f) close() moveTo(12f, 22f) curveToRelative(-1.383f, 0f, -2.683f, -0.262f, -3.9f, -0.788f) reflectiveCurveToRelative(-2.275f, -1.237f, -3.175f, -2.138f) reflectiveCurveToRelative(-1.612f, -1.958f, -2.138f, -3.175f) reflectiveCurveToRelative(-0.788f, -2.517f, -0.788f, -3.9f) curveToRelative(0f, -1.383f, 0.262f, -2.683f, 0.788f, -3.9f) reflectiveCurveToRelative(1.237f, -2.275f, 2.138f, -3.175f) reflectiveCurveToRelative(1.958f, -1.612f, 3.175f, -2.138f) reflectiveCurveToRelative(2.517f, -0.788f, 3.9f, -0.788f) curveToRelative(1.383f, 0f, 2.683f, 0.262f, 3.9f, 0.788f) reflectiveCurveToRelative(2.275f, 1.237f, 3.175f, 2.138f) reflectiveCurveToRelative(1.612f, 1.958f, 2.138f, 3.175f) reflectiveCurveToRelative(0.788f, 2.517f, 0.788f, 3.9f) curveToRelative(0f, 1.383f, -0.262f, 2.683f, -0.788f, 3.9f) reflectiveCurveToRelative(-1.237f, 2.275f, -2.138f, 3.175f) reflectiveCurveToRelative(-1.958f, 1.612f, -3.175f, 2.138f) reflectiveCurveToRelative(-2.517f, 0.788f, -3.9f, 0.788f) close() moveTo(12f, 20f) curveToRelative(2.233f, 0f, 4.125f, -0.775f, 5.675f, -2.325f) reflectiveCurveToRelative(2.325f, -3.442f, 2.325f, -5.675f) reflectiveCurveToRelative(-0.775f, -4.125f, -2.325f, -5.675f) reflectiveCurveToRelative(-3.442f, -2.325f, -5.675f, -2.325f) reflectiveCurveToRelative(-4.125f, 0.775f, -5.675f, 2.325f) reflectiveCurveToRelative(-2.325f, 3.442f, -2.325f, 5.675f) reflectiveCurveToRelative(0.775f, 4.125f, 2.325f, 5.675f) reflectiveCurveToRelative(3.442f, 2.325f, 5.675f, 2.325f) close() } }.build() } val Icons.TwoTone.SwapVerticalCircle: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoToneSwapVerticalCircle", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(9f, 8.85f) verticalLineToRelative(3.15f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) verticalLineToRelative(-3.15f) lineToRelative(0.9f, 0.875f) curveToRelative(0.183f, 0.183f, 0.412f, 0.275f, 0.688f, 0.275f) reflectiveCurveToRelative(0.512f, -0.1f, 0.712f, -0.3f) curveToRelative(0.183f, -0.183f, 0.275f, -0.417f, 0.275f, -0.7f) reflectiveCurveToRelative(-0.092f, -0.517f, -0.275f, -0.7f) lineToRelative(-2.575f, -2.575f) curveToRelative(-0.2f, -0.2f, -0.438f, -0.3f, -0.712f, -0.3f) reflectiveCurveToRelative(-0.512f, 0.1f, -0.712f, 0.3f) lineToRelative(-2.6f, 2.575f) curveToRelative(-0.183f, 0.183f, -0.279f, 0.412f, -0.287f, 0.688f) reflectiveCurveToRelative(0.087f, 0.512f, 0.287f, 0.712f) curveToRelative(0.183f, 0.183f, 0.412f, 0.279f, 0.688f, 0.287f) reflectiveCurveToRelative(0.512f, -0.079f, 0.712f, -0.262f) lineToRelative(0.9f, -0.875f) close() } path(fill = SolidColor(Color.Black)) { moveTo(13f, 15.15f) lineToRelative(-0.9f, -0.875f) curveToRelative(-0.183f, -0.183f, -0.412f, -0.275f, -0.688f, -0.275f) reflectiveCurveToRelative(-0.512f, 0.1f, -0.712f, 0.3f) curveToRelative(-0.183f, 0.183f, -0.275f, 0.417f, -0.275f, 0.7f) reflectiveCurveToRelative(0.092f, 0.517f, 0.275f, 0.7f) lineToRelative(2.6f, 2.6f) curveToRelative(0.2f, 0.2f, 0.438f, 0.3f, 0.712f, 0.3f) reflectiveCurveToRelative(0.512f, -0.1f, 0.712f, -0.3f) lineToRelative(2.575f, -2.6f) curveToRelative(0.183f, -0.183f, 0.279f, -0.412f, 0.287f, -0.688f) reflectiveCurveToRelative(-0.087f, -0.512f, -0.287f, -0.712f) curveToRelative(-0.183f, -0.183f, -0.412f, -0.279f, -0.688f, -0.287f) reflectiveCurveToRelative(-0.512f, 0.079f, -0.712f, 0.262f) lineToRelative(-0.9f, 0.875f) verticalLineToRelative(-3.15f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) reflectiveCurveToRelative(-0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(3.15f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.213f, 8.1f) curveToRelative(-0.525f, -1.217f, -1.238f, -2.275f, -2.138f, -3.175f) curveToRelative(-0.9f, -0.9f, -1.958f, -1.612f, -3.175f, -2.138f) curveToRelative(-1.217f, -0.525f, -2.517f, -0.787f, -3.9f, -0.787f) reflectiveCurveToRelative(-2.683f, 0.263f, -3.9f, 0.787f) curveToRelative(-1.217f, 0.525f, -2.275f, 1.238f, -3.175f, 2.138f) curveToRelative(-0.9f, 0.9f, -1.612f, 1.958f, -2.138f, 3.175f) curveToRelative(-0.525f, 1.217f, -0.787f, 2.517f, -0.787f, 3.9f) reflectiveCurveToRelative(0.263f, 2.683f, 0.787f, 3.9f) curveToRelative(0.525f, 1.217f, 1.238f, 2.275f, 2.138f, 3.175f) curveToRelative(0.9f, 0.9f, 1.958f, 1.612f, 3.175f, 2.138f) curveToRelative(1.217f, 0.525f, 2.517f, 0.787f, 3.9f, 0.787f) reflectiveCurveToRelative(2.683f, -0.263f, 3.9f, -0.787f) curveToRelative(1.217f, -0.525f, 2.275f, -1.238f, 3.175f, -2.138f) curveToRelative(0.9f, -0.9f, 1.612f, -1.958f, 2.138f, -3.175f) curveToRelative(0.525f, -1.217f, 0.787f, -2.517f, 0.787f, -3.9f) reflectiveCurveToRelative(-0.263f, -2.683f, -0.787f, -3.9f) close() moveTo(17.675f, 17.675f) curveToRelative(-1.55f, 1.55f, -3.442f, 2.325f, -5.675f, 2.325f) reflectiveCurveToRelative(-4.125f, -0.775f, -5.675f, -2.325f) reflectiveCurveToRelative(-2.325f, -3.442f, -2.325f, -5.675f) reflectiveCurveToRelative(0.775f, -4.125f, 2.325f, -5.675f) reflectiveCurveToRelative(3.442f, -2.325f, 5.675f, -2.325f) reflectiveCurveToRelative(4.125f, 0.775f, 5.675f, 2.325f) reflectiveCurveToRelative(2.325f, 3.442f, 2.325f, 5.675f) reflectiveCurveToRelative(-0.775f, 4.125f, -2.325f, 5.675f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 20f) curveToRelative(2.233f, 0f, 4.125f, -0.775f, 5.675f, -2.325f) reflectiveCurveToRelative(2.325f, -3.442f, 2.325f, -5.675f) reflectiveCurveToRelative(-0.775f, -4.125f, -2.325f, -5.675f) reflectiveCurveToRelative(-3.442f, -2.325f, -5.675f, -2.325f) reflectiveCurveToRelative(-4.125f, 0.775f, -5.675f, 2.325f) reflectiveCurveToRelative(-2.325f, 3.442f, -2.325f, 5.675f) reflectiveCurveToRelative(0.775f, 4.125f, 2.325f, 5.675f) reflectiveCurveToRelative(3.442f, 2.325f, 5.675f, 2.325f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Swatch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Swatch: ImageVector by lazy { Builder( name = "Swatch", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.0f, 14.0f) horizontalLineTo(6.0f) curveTo(3.8f, 14.0f, 2.0f, 15.8f, 2.0f, 18.0f) reflectiveCurveTo(3.8f, 22.0f, 6.0f, 22.0f) horizontalLineTo(20.0f) curveTo(21.1f, 22.0f, 22.0f, 21.1f, 22.0f, 20.0f) verticalLineTo(16.0f) curveTo(22.0f, 14.9f, 21.1f, 14.0f, 20.0f, 14.0f) moveTo(6.0f, 20.0f) curveTo(4.9f, 20.0f, 4.0f, 19.1f, 4.0f, 18.0f) reflectiveCurveTo(4.9f, 16.0f, 6.0f, 16.0f) reflectiveCurveTo(8.0f, 16.9f, 8.0f, 18.0f) reflectiveCurveTo(7.1f, 20.0f, 6.0f, 20.0f) moveTo(6.3f, 12.0f) lineTo(13.0f, 5.3f) curveTo(13.8f, 4.5f, 15.0f, 4.5f, 15.8f, 5.3f) lineTo(18.6f, 8.1f) curveTo(19.4f, 8.9f, 19.4f, 10.1f, 18.6f, 10.9f) lineTo(17.7f, 12.0f) horizontalLineTo(6.3f) moveTo(2.0f, 13.5f) verticalLineTo(4.0f) curveTo(2.0f, 2.9f, 2.9f, 2.0f, 4.0f, 2.0f) horizontalLineTo(8.0f) curveTo(9.1f, 2.0f, 10.0f, 2.9f, 10.0f, 4.0f) verticalLineTo(5.5f) lineTo(2.0f, 13.5f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Symbol.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Symbol: ImageVector by lazy { Builder( name = "Symbol", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(2.0f, 7.0f) verticalLineTo(14.0f) horizontalLineTo(4.0f) verticalLineTo(7.0f) horizontalLineTo(2.0f) moveTo(6.0f, 7.0f) verticalLineTo(9.0f) horizontalLineTo(10.0f) verticalLineTo(11.0f) horizontalLineTo(8.0f) verticalLineTo(14.0f) horizontalLineTo(10.0f) verticalLineTo(13.0f) curveTo(11.11f, 13.0f, 12.0f, 12.11f, 12.0f, 11.0f) verticalLineTo(9.0f) curveTo(12.0f, 7.89f, 11.11f, 7.0f, 10.0f, 7.0f) horizontalLineTo(6.0f) moveTo(15.8f, 7.0f) lineTo(15.6f, 9.0f) horizontalLineTo(14.0f) verticalLineTo(11.0f) horizontalLineTo(15.4f) lineTo(15.2f, 13.0f) horizontalLineTo(14.0f) verticalLineTo(15.0f) horizontalLineTo(15.0f) lineTo(14.8f, 17.0f) horizontalLineTo(16.8f) lineTo(17.0f, 15.0f) horizontalLineTo(18.4f) lineTo(18.2f, 17.0f) horizontalLineTo(20.2f) lineTo(20.4f, 15.0f) horizontalLineTo(22.0f) verticalLineTo(13.0f) horizontalLineTo(20.6f) lineTo(20.8f, 11.0f) horizontalLineTo(22.0f) verticalLineTo(9.0f) horizontalLineTo(21.0f) lineTo(21.2f, 7.0f) horizontalLineTo(19.2f) lineTo(19.0f, 9.0f) horizontalLineTo(17.6f) lineTo(17.8f, 7.0f) horizontalLineTo(15.8f) moveTo(17.4f, 11.0f) horizontalLineTo(18.8f) lineTo(18.6f, 13.0f) horizontalLineTo(17.2f) lineTo(17.4f, 11.0f) moveTo(2.0f, 15.0f) verticalLineTo(17.0f) horizontalLineTo(4.0f) verticalLineTo(15.0f) horizontalLineTo(2.0f) moveTo(8.0f, 15.0f) verticalLineTo(17.0f) horizontalLineTo(10.0f) verticalLineTo(15.0f) horizontalLineTo(8.0f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/SyncArrowDown.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.SyncArrowDown: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.SyncArrowDown", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(80f, 780f) lineTo(80f, 700f) lineTo(137f, 700f) quadTo(90f, 658f, 65f, 600.5f) quadTo(40f, 543f, 40f, 480f) quadTo(40f, 373f, 107f, 290.5f) quadTo(174f, 208f, 280f, 186f) lineTo(280f, 268f) quadTo(208f, 288f, 164f, 346.5f) quadTo(120f, 405f, 120f, 479f) quadTo(120f, 529f, 141f, 572.5f) quadTo(162f, 616f, 200f, 648f) lineTo(200f, 580f) lineTo(280f, 580f) lineTo(280f, 780f) lineTo(80f, 780f) close() moveTo(400f, 774f) lineTo(400f, 692f) quadTo(472f, 672f, 516f, 613.5f) quadTo(560f, 555f, 560f, 481f) quadTo(560f, 431f, 539f, 387.5f) quadTo(518f, 344f, 480f, 312f) lineTo(480f, 380f) lineTo(400f, 380f) lineTo(400f, 180f) lineTo(600f, 180f) lineTo(600f, 260f) lineTo(543f, 260f) quadTo(590f, 302f, 615f, 359.5f) quadTo(640f, 417f, 640f, 480f) quadTo(640f, 587f, 573f, 669.5f) quadTo(506f, 752f, 400f, 774f) close() moveTo(780f, 800f) lineTo(640f, 660f) lineTo(697f, 604f) lineTo(740f, 647f) lineTo(740f, 160f) lineTo(820f, 160f) lineTo(820f, 648f) lineTo(864f, 604f) lineTo(920f, 660f) lineTo(780f, 800f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TableEye.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.TableEye: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.TableEye", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(120f, 640f) lineTo(328f, 640f) quadTo(318f, 654f, 309f, 668.5f) quadTo(300f, 683f, 292f, 700f) quadTo(282f, 719f, 282.5f, 740f) quadTo(283f, 761f, 292f, 780f) quadTo(300f, 797f, 309f, 811.5f) quadTo(318f, 826f, 328f, 840f) lineTo(200f, 840f) quadTo(167f, 840f, 143.5f, 816.5f) quadTo(120f, 793f, 120f, 760f) lineTo(120f, 640f) close() moveTo(120f, 560f) lineTo(120f, 360f) lineTo(440f, 360f) lineTo(440f, 533f) quadTo(430f, 539f, 420.5f, 545.5f) quadTo(411f, 552f, 402f, 560f) lineTo(120f, 560f) close() moveTo(520f, 360f) lineTo(840f, 360f) lineTo(840f, 533f) quadTo(796f, 507f, 746.5f, 493.5f) quadTo(697f, 480f, 640f, 480f) quadTo(608f, 480f, 578f, 484.5f) quadTo(548f, 489f, 520f, 497f) lineTo(520f, 360f) close() moveTo(120f, 280f) lineTo(120f, 200f) quadTo(120f, 167f, 143.5f, 143.5f) quadTo(167f, 120f, 200f, 120f) lineTo(760f, 120f) quadTo(793f, 120f, 816.5f, 143.5f) quadTo(840f, 167f, 840f, 200f) lineTo(840f, 280f) lineTo(120f, 280f) close() moveTo(640f, 920f) quadTo(561f, 920f, 492.5f, 884f) quadTo(424f, 848f, 382f, 782f) quadTo(376f, 773f, 373f, 762.5f) quadTo(370f, 752f, 370f, 741f) quadTo(370f, 730f, 373f, 719f) quadTo(376f, 708f, 382f, 698f) quadTo(424f, 632f, 492.5f, 596f) quadTo(561f, 560f, 640f, 560f) quadTo(719f, 560f, 787.5f, 596f) quadTo(856f, 632f, 898f, 698f) quadTo(904f, 708f, 907f, 718.5f) quadTo(910f, 729f, 910f, 740f) quadTo(910f, 751f, 907f, 761.5f) quadTo(904f, 772f, 898f, 782f) quadTo(856f, 848f, 787.5f, 884f) quadTo(719f, 920f, 640f, 920f) close() moveTo(640f, 840f) quadTo(682f, 840f, 711f, 811f) quadTo(740f, 782f, 740f, 740f) quadTo(740f, 698f, 711f, 669f) quadTo(682f, 640f, 640f, 640f) quadTo(598f, 640f, 569f, 669f) quadTo(540f, 698f, 540f, 740f) quadTo(540f, 782f, 569f, 811f) quadTo(598f, 840f, 640f, 840f) close() moveTo(640f, 800f) quadTo(615f, 800f, 597.5f, 782.5f) quadTo(580f, 765f, 580f, 740f) quadTo(580f, 715f, 597.5f, 697.5f) quadTo(615f, 680f, 640f, 680f) quadTo(665f, 680f, 682.5f, 697.5f) quadTo(700f, 715f, 700f, 740f) quadTo(700f, 765f, 682.5f, 782.5f) quadTo(665f, 800f, 640f, 800f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TagText.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.TagText: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.TagText", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(21.4f, 11.6f) lineTo(12.4f, 2.6f) curveToRelative(-0.4f, -0.4f, -0.9f, -0.6f, -1.4f, -0.6f) horizontalLineToRelative(-7f) curveToRelative(-1.1f, 0f, -2f, 0.9f, -2f, 2f) verticalLineToRelative(7f) curveToRelative(0f, 0.5f, 0.2f, 1f, 0.6f, 1.4f) lineToRelative(9f, 9f) curveToRelative(0.4f, 0.4f, 0.9f, 0.6f, 1.4f, 0.6f) reflectiveCurveToRelative(1f, -0.2f, 1.4f, -0.6f) lineToRelative(7f, -7f) curveToRelative(0.4f, -0.4f, 0.6f, -0.9f, 0.6f, -1.4f) reflectiveCurveToRelative(-0.2f, -1f, -0.6f, -1.4f) close() moveTo(13f, 20f) lineTo(4f, 11f) verticalLineToRelative(-7f) horizontalLineToRelative(7f) lineToRelative(9f, 9f) lineToRelative(-7f, 7f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6.5f, 5f) curveToRelative(0.8f, 0f, 1.5f, 0.7f, 1.5f, 1.5f) reflectiveCurveToRelative(-0.7f, 1.5f, -1.5f, 1.5f) reflectiveCurveToRelative(-1.5f, -0.7f, -1.5f, -1.5f) reflectiveCurveToRelative(0.7f, -1.5f, 1.5f, -1.5f) } path(fill = SolidColor(Color.Black)) { moveTo(8.3f, 10.7f) lineTo(8.3f, 10.7f) arcTo(0.99f, 0.99f, 90f, isMoreThanHalf = false, isPositiveArc = true, 9.7f, 10.7f) lineTo(12.3f, 13.3f) arcTo(0.99f, 0.99f, 90f, isMoreThanHalf = false, isPositiveArc = true, 12.3f, 14.7f) lineTo(12.3f, 14.7f) arcTo(0.99f, 0.99f, 0f, isMoreThanHalf = false, isPositiveArc = true, 10.9f, 14.7f) lineTo(8.3f, 12.1f) arcTo(0.99f, 0.99f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.3f, 10.7f) close() } path(fill = SolidColor(Color.Black)) { moveTo(15.609f, 13.988f) curveToRelative(0.25f, -0.002f, 0.5f, -0.097f, 0.691f, -0.288f) curveToRelative(0.387f, -0.387f, 0.387f, -1.013f, 0f, -1.4f) lineToRelative(-4.1f, -4.1f) curveToRelative(-0.191f, -0.191f, -0.441f, -0.286f, -0.691f, -0.288f) curveToRelative(-0.25f, 0.002f, -0.5f, 0.097f, -0.691f, 0.288f) curveToRelative(-0.387f, 0.387f, -0.387f, 1.013f, 0f, 1.4f) lineToRelative(4.1f, 4.1f) curveToRelative(0.191f, 0.191f, 0.441f, 0.286f, 0.691f, 0.288f) close() } }.build() } val Icons.TwoTone.TagText: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.TagText", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(13f, 20f) lineToRelative(-9f, -9f) lineToRelative(0f, -7f) lineToRelative(7f, 0f) lineToRelative(9f, 9f) } path(fill = SolidColor(Color.Black)) { moveTo(21.4f, 11.6f) lineTo(12.4f, 2.6f) curveToRelative(-0.4f, -0.4f, -0.9f, -0.6f, -1.4f, -0.6f) horizontalLineToRelative(-7f) curveToRelative(-1.1f, 0f, -2f, 0.9f, -2f, 2f) verticalLineToRelative(7f) curveToRelative(0f, 0.5f, 0.2f, 1f, 0.6f, 1.4f) lineToRelative(9f, 9f) curveToRelative(0.4f, 0.4f, 0.9f, 0.6f, 1.4f, 0.6f) reflectiveCurveToRelative(1f, -0.2f, 1.4f, -0.6f) lineToRelative(7f, -7f) curveToRelative(0.4f, -0.4f, 0.6f, -0.9f, 0.6f, -1.4f) reflectiveCurveToRelative(-0.2f, -1f, -0.6f, -1.4f) close() moveTo(13f, 20f) lineTo(4f, 11f) verticalLineToRelative(-7f) horizontalLineToRelative(7f) lineToRelative(9f, 9f) lineToRelative(-7f, 7f) close() } path(fill = SolidColor(Color.Black)) { moveTo(6.5f, 5f) curveToRelative(0.8f, 0f, 1.5f, 0.7f, 1.5f, 1.5f) reflectiveCurveToRelative(-0.7f, 1.5f, -1.5f, 1.5f) reflectiveCurveToRelative(-1.5f, -0.7f, -1.5f, -1.5f) reflectiveCurveToRelative(0.7f, -1.5f, 1.5f, -1.5f) } path(fill = SolidColor(Color.Black)) { moveTo(8.3f, 10.7f) lineTo(8.3f, 10.7f) arcTo(0.99f, 0.99f, 90f, isMoreThanHalf = false, isPositiveArc = true, 9.7f, 10.7f) lineTo(12.3f, 13.3f) arcTo(0.99f, 0.99f, 90f, isMoreThanHalf = false, isPositiveArc = true, 12.3f, 14.7f) lineTo(12.3f, 14.7f) arcTo(0.99f, 0.99f, 0f, isMoreThanHalf = false, isPositiveArc = true, 10.9f, 14.7f) lineTo(8.3f, 12.1f) arcTo(0.99f, 0.99f, 0f, isMoreThanHalf = false, isPositiveArc = true, 8.3f, 10.7f) close() } path(fill = SolidColor(Color.Black)) { moveTo(15.609f, 13.988f) curveToRelative(0.25f, -0.002f, 0.5f, -0.097f, 0.691f, -0.288f) curveToRelative(0.387f, -0.387f, 0.387f, -1.013f, 0f, -1.4f) lineToRelative(-4.1f, -4.1f) curveToRelative(-0.191f, -0.191f, -0.441f, -0.286f, -0.691f, -0.288f) curveToRelative(-0.25f, 0.002f, -0.5f, 0.097f, -0.691f, 0.288f) curveToRelative(-0.387f, 0.387f, -0.387f, 1.013f, 0f, 1.4f) lineToRelative(4.1f, 4.1f) curveToRelative(0.191f, 0.191f, 0.441f, 0.286f, 0.691f, 0.288f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Telegram.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Telegram: ImageVector by lazy { ImageVector.Builder( name = "Telegram", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(11.944f, 0.0f) arcTo( 12.0f, 12.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 0.0f, y1 = 12.0f ) arcToRelative( 12.0f, 12.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, dx1 = 12.0f, dy1 = 12.0f ) arcToRelative( 12.0f, 12.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, dx1 = 12.0f, dy1 = -12.0f ) arcTo( 12.0f, 12.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 12.0f, y1 = 0.0f ) arcToRelative( 12.0f, 12.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, dx1 = -0.056f, dy1 = 0.0f ) close() moveTo(16.906f, 7.224f) curveToRelative(0.1f, -0.002f, 0.321f, 0.023f, 0.465f, 0.14f) arcToRelative( 0.506f, 0.506f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = 0.171f, dy1 = 0.325f ) curveToRelative(0.016f, 0.093f, 0.036f, 0.306f, 0.02f, 0.472f) curveToRelative(-0.18f, 1.898f, -0.962f, 6.502f, -1.36f, 8.627f) curveToRelative(-0.168f, 0.9f, -0.499f, 1.201f, -0.82f, 1.23f) curveToRelative(-0.696f, 0.065f, -1.225f, -0.46f, -1.9f, -0.902f) curveToRelative(-1.056f, -0.693f, -1.653f, -1.124f, -2.678f, -1.8f) curveToRelative(-1.185f, -0.78f, -0.417f, -1.21f, 0.258f, -1.91f) curveToRelative(0.177f, -0.184f, 3.247f, -2.977f, 3.307f, -3.23f) curveToRelative(0.007f, -0.032f, 0.014f, -0.15f, -0.056f, -0.212f) reflectiveCurveToRelative(-0.174f, -0.041f, -0.249f, -0.024f) curveToRelative(-0.106f, 0.024f, -1.793f, 1.14f, -5.061f, 3.345f) curveToRelative(-0.48f, 0.33f, -0.913f, 0.49f, -1.302f, 0.48f) curveToRelative(-0.428f, -0.008f, -1.252f, -0.241f, -1.865f, -0.44f) curveToRelative(-0.752f, -0.245f, -1.349f, -0.374f, -1.297f, -0.789f) curveToRelative(0.027f, -0.216f, 0.325f, -0.437f, 0.893f, -0.663f) curveToRelative(3.498f, -1.524f, 5.83f, -2.529f, 6.998f, -3.014f) curveToRelative(3.332f, -1.386f, 4.025f, -1.627f, 4.476f, -1.635f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TelevisionAmbientLight.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.TelevisionAmbientLight: ImageVector by lazy { ImageVector.Builder( name = "TelevisionAmbientLight", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(3f, 11f) horizontalLineTo(0f) verticalLineTo(9f) horizontalLineTo(3f) verticalLineTo(11f) moveTo(3f, 14f) horizontalLineTo(0f) verticalLineTo(16f) horizontalLineTo(3f) verticalLineTo(14f) moveTo(5f, 5.12f) lineTo(2.88f, 3f) lineTo(1.46f, 4.41f) lineTo(3.59f, 6.54f) lineTo(5f, 5.12f) moveTo(10f, 5f) verticalLineTo(2f) horizontalLineTo(8f) verticalLineTo(5f) horizontalLineTo(10f) moveTo(24f, 9f) horizontalLineTo(21f) verticalLineTo(11f) horizontalLineTo(24f) verticalLineTo(9f) moveTo(16f, 5f) verticalLineTo(2f) horizontalLineTo(14f) verticalLineTo(5f) horizontalLineTo(16f) moveTo(20.41f, 6.54f) lineTo(22.54f, 4.42f) lineTo(21.12f, 3f) lineTo(19f, 5.12f) lineTo(20.41f, 6.54f) moveTo(24f, 14f) horizontalLineTo(21f) verticalLineTo(16f) horizontalLineTo(24f) verticalLineTo(14f) moveTo(19f, 9f) verticalLineTo(16f) curveTo(19f, 17.1f, 18.1f, 18f, 17f, 18f) horizontalLineTo(15f) verticalLineTo(20f) horizontalLineTo(9f) verticalLineTo(18f) horizontalLineTo(7f) curveTo(5.9f, 18f, 5f, 17.1f, 5f, 16f) verticalLineTo(9f) curveTo(5f, 7.9f, 5.9f, 7f, 7f, 7f) horizontalLineTo(17f) curveTo(18.1f, 7f, 19f, 7.9f, 19f, 9f) moveTo(17f, 9f) horizontalLineTo(7f) verticalLineTo(16f) horizontalLineTo(17f) verticalLineTo(9f) moveTo(19f, 19.88f) lineTo(21.12f, 22f) lineTo(22.54f, 20.59f) lineTo(20.41f, 18.47f) lineTo(19f, 19.88f) moveTo(3.59f, 18.46f) lineTo(1.47f, 20.59f) lineTo(2.88f, 22f) lineTo(5f, 19.88f) lineTo(3.59f, 18.46f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tetradic.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Tetradic: ImageVector by lazy { Builder( name = "Tetradic", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(8.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(6.9f, 16.0f, 8.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(14.9f, 16.0f, 16.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(8.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveTo(9.1f, 8.0f, 8.0f, 8.0f) reflectiveCurveTo(6.0f, 7.1f, 6.0f, 6.0f) reflectiveCurveTo(6.9f, 4.0f, 8.0f, 4.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(16.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(14.9f, 4.0f, 16.0f, 4.0f) } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextFields.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.TextFields: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.TextFields", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 800f) lineTo(280f, 280f) lineTo(80f, 280f) lineTo(80f, 160f) lineTo(600f, 160f) lineTo(600f, 280f) lineTo(400f, 280f) lineTo(400f, 800f) lineTo(280f, 800f) close() moveTo(640f, 800f) lineTo(640f, 480f) lineTo(520f, 480f) lineTo(520f, 360f) lineTo(880f, 360f) lineTo(880f, 480f) lineTo(760f, 480f) lineTo(760f, 800f) lineTo(640f, 800f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSearch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.TextSearch: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.TextSearch", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19.31f, 18.9f) lineTo(22.39f, 22f) lineTo(21f, 23.39f) lineTo(17.88f, 20.32f) curveTo(17.19f, 20.75f, 16.37f, 21f, 15.5f, 21f) curveTo(13f, 21f, 11f, 19f, 11f, 16.5f) curveTo(11f, 14f, 13f, 12f, 15.5f, 12f) curveTo(18f, 12f, 20f, 14f, 20f, 16.5f) curveTo(20f, 17.38f, 19.75f, 18.21f, 19.31f, 18.9f) moveTo(15.5f, 19f) curveTo(16.88f, 19f, 18f, 17.88f, 18f, 16.5f) curveTo(18f, 15.12f, 16.88f, 14f, 15.5f, 14f) curveTo(14.12f, 14f, 13f, 15.12f, 13f, 16.5f) curveTo(13f, 17.88f, 14.12f, 19f, 15.5f, 19f) moveTo(21f, 4f) verticalLineTo(6f) horizontalLineTo(3f) verticalLineTo(4f) horizontalLineTo(21f) moveTo(3f, 16f) verticalLineTo(14f) horizontalLineTo(9f) verticalLineTo(16f) horizontalLineTo(3f) moveTo(3f, 11f) verticalLineTo(9f) horizontalLineTo(21f) verticalLineTo(11f) horizontalLineTo(18.97f) curveTo(17.96f, 10.37f, 16.77f, 10f, 15.5f, 10f) curveTo(14.23f, 10f, 13.04f, 10.37f, 12.03f, 11f) horizontalLineTo(3f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TextSticky.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.TextSticky: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.TextSticky", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(200f, 840f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(120f, 760f) verticalLineToRelative(-560f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(200f, 120f) horizontalLineToRelative(560f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(840f, 200f) verticalLineToRelative(407f) quadToRelative(0f, 16f, -6f, 30.5f) reflectiveQuadTo(817f, 663f) lineTo(663f, 817f) quadToRelative(-11f, 11f, -25.5f, 17f) reflectiveQuadToRelative(-30.5f, 6f) lineTo(200f, 840f) close() moveTo(600f, 760f) verticalLineToRelative(-80f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(680f, 600f) horizontalLineToRelative(80f) verticalLineToRelative(-400f) lineTo(200f, 200f) verticalLineToRelative(560f) horizontalLineToRelative(400f) close() moveTo(440f, 400f) verticalLineToRelative(200f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(480f, 640f) quadToRelative(17f, 0f, 28.5f, -11.5f) reflectiveQuadTo(520f, 600f) verticalLineToRelative(-200f) horizontalLineToRelative(80f) quadToRelative(17f, 0f, 28.5f, -11.5f) reflectiveQuadTo(640f, 360f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(600f, 320f) lineTo(360f, 320f) quadToRelative(-17f, 0f, -28.5f, 11.5f) reflectiveQuadTo(320f, 360f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(360f, 400f) horizontalLineToRelative(80f) close() moveTo(600f, 760f) close() moveTo(200f, 760f) verticalLineToRelative(-560f) verticalLineToRelative(560f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Theme.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Theme: ImageVector by lazy { Builder( name = "Theme", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(22.0f, 22.0f) horizontalLineTo(10.0f) verticalLineTo(20.0f) horizontalLineTo(22.0f) verticalLineTo(22.0f) moveTo(2.0f, 22.0f) verticalLineTo(20.0f) horizontalLineTo(9.0f) verticalLineTo(22.0f) horizontalLineTo(2.0f) moveTo(18.0f, 18.0f) verticalLineTo(10.0f) horizontalLineTo(22.0f) verticalLineTo(18.0f) horizontalLineTo(18.0f) moveTo(18.0f, 3.0f) horizontalLineTo(22.0f) verticalLineTo(9.0f) horizontalLineTo(18.0f) verticalLineTo(3.0f) moveTo(2.0f, 18.0f) verticalLineTo(3.0f) horizontalLineTo(16.0f) verticalLineTo(18.0f) horizontalLineTo(2.0f) moveTo(9.0f, 14.56f) arcTo( 3.0f, 3.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 12.0f, y1 = 11.56f ) curveTo(12.0f, 9.56f, 9.0f, 6.19f, 9.0f, 6.19f) curveTo(9.0f, 6.19f, 6.0f, 9.56f, 6.0f, 11.56f) arcTo( 3.0f, 3.0f, 0.0f, isMoreThanHalf = false, isPositiveArc = false, x1 = 9.0f, y1 = 14.56f ) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TimerEdit.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.TimerEdit: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.TimerEdit", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(13f, 14f) horizontalLineTo(11f) verticalLineTo(8f) horizontalLineTo(13f) verticalLineTo(14f) moveTo(15f, 1f) horizontalLineTo(9f) verticalLineTo(3f) horizontalLineTo(15f) verticalLineTo(1f) moveTo(5f, 13f) curveTo(5f, 9.13f, 8.13f, 6f, 12f, 6f) curveTo(15.29f, 6f, 18.05f, 8.28f, 18.79f, 11.34f) lineTo(19.39f, 10.74f) curveTo(19.71f, 10.42f, 20.1f, 10.21f, 20.5f, 10.1f) curveTo(20.18f, 9.11f, 19.67f, 8.19f, 19.03f, 7.39f) lineTo(20.45f, 5.97f) curveTo(20f, 5.46f, 19.55f, 5f, 19.04f, 4.56f) lineTo(17.62f, 6f) curveTo(16.07f, 4.74f, 14.12f, 4f, 12f, 4f) curveTo(7.03f, 4f, 3f, 8.03f, 3f, 13f) curveTo(3f, 17.63f, 6.5f, 21.44f, 11f, 21.94f) verticalLineTo(19.92f) curveTo(7.61f, 19.43f, 5f, 16.53f, 5f, 13f) moveTo(13f, 19.96f) verticalLineTo(22f) horizontalLineTo(15.04f) lineTo(21.17f, 15.88f) lineTo(19.13f, 13.83f) lineTo(13f, 19.96f) moveTo(22.85f, 13.47f) lineTo(21.53f, 12.15f) curveTo(21.33f, 11.95f, 21f, 11.95f, 20.81f, 12.15f) lineTo(19.83f, 13.13f) lineTo(21.87f, 15.17f) lineTo(22.85f, 14.19f) curveTo(23.05f, 14f, 23.05f, 13.67f, 22.85f, 13.47f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Titlecase.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Titlecase: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Titlecase", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(344f, 710f) lineTo(344f, 344f) lineTo(224f, 344f) lineTo(224f, 280f) lineTo(532f, 280f) lineTo(532f, 344f) lineTo(412f, 344f) lineTo(412f, 710f) lineTo(344f, 710f) close() moveTo(688f, 720f) quadTo(644f, 720f, 619f, 694.5f) quadTo(594f, 669f, 594f, 624f) lineTo(594f, 462f) lineTo(540f, 462f) lineTo(540f, 404f) lineTo(594f, 404f) lineTo(594f, 317f) lineTo(660f, 317f) lineTo(660f, 404f) lineTo(734f, 404f) lineTo(734f, 462f) lineTo(660f, 462f) lineTo(660f, 610f) quadTo(660f, 633f, 670.5f, 646f) quadTo(681f, 659f, 699f, 659f) quadTo(708f, 659f, 717.5f, 655.5f) quadTo(727f, 652f, 736f, 646f) lineTo(736f, 711f) quadTo(726f, 716f, 714f, 718f) quadTo(702f, 720f, 688f, 720f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Ton: ImageVector by lazy { Builder( name = "Ton", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.0777f, 3.0f) horizontalLineTo(5.9216f) curveTo(3.6865f, 3.0f, 2.2699f, 5.4109f, 3.3943f, 7.3599f) lineToRelative(7.5023f, 13.0033f) curveToRelative(0.4896f, 0.8491f, 1.7165f, 0.8491f, 2.206f, 0.0f) lineToRelative(7.5038f, -13.0033f) curveTo(21.7294f, 5.414f, 20.3128f, 3.0f, 18.0792f, 3.0f) horizontalLineTo(18.0777f) close() moveTo(10.8905f, 16.4637f) lineToRelative(-1.6339f, -3.1621f) lineTo(5.3143f, 6.2508f) curveToRelative(-0.2601f, -0.4513f, 0.0612f, -1.0296f, 0.6058f, -1.0296f) horizontalLineToRelative(4.9689f) verticalLineToRelative(11.244f) lineTo(10.8905f, 16.4637f) close() moveTo(18.682f, 6.2493f) lineToRelative(-3.9409f, 7.0539f) lineToRelative(-1.6339f, 3.1605f) verticalLineTo(5.2197f) horizontalLineToRelative(4.9689f) curveTo(18.6208f, 5.2197f, 18.942f, 5.798f, 18.682f, 6.2493f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tonality.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Tonality: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Tonality", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(480f, 880f) quadTo(397f, 880f, 324f, 848.5f) quadTo(251f, 817f, 197f, 763f) quadTo(143f, 709f, 111.5f, 636f) quadTo(80f, 563f, 80f, 480f) quadTo(80f, 397f, 111.5f, 324f) quadTo(143f, 251f, 197f, 197f) quadTo(251f, 143f, 324f, 111.5f) quadTo(397f, 80f, 480f, 80f) quadTo(563f, 80f, 636f, 111.5f) quadTo(709f, 143f, 763f, 197f) quadTo(817f, 251f, 848.5f, 324f) quadTo(880f, 397f, 880f, 480f) quadTo(880f, 563f, 848.5f, 636f) quadTo(817f, 709f, 763f, 763f) quadTo(709f, 817f, 636f, 848.5f) quadTo(563f, 880f, 480f, 880f) close() moveTo(520f, 798f) quadTo(550f, 793f, 579f, 784.5f) quadTo(608f, 776f, 634f, 760f) lineTo(520f, 760f) lineTo(520f, 798f) close() moveTo(520f, 680f) lineTo(730f, 680f) quadTo(738f, 671f, 744f, 661f) quadTo(750f, 651f, 756f, 640f) lineTo(520f, 640f) lineTo(520f, 680f) close() moveTo(520f, 560f) lineTo(790f, 560f) quadTo(792f, 550f, 794f, 540f) quadTo(796f, 530f, 798f, 520f) lineTo(520f, 520f) lineTo(520f, 560f) close() moveTo(520f, 440f) lineTo(798f, 440f) quadTo(796f, 430f, 794f, 420f) quadTo(792f, 410f, 790f, 400f) lineTo(520f, 400f) lineTo(520f, 440f) close() moveTo(520f, 320f) lineTo(756f, 320f) quadTo(750f, 309f, 744f, 299f) quadTo(738f, 289f, 730f, 280f) lineTo(520f, 280f) lineTo(520f, 320f) close() moveTo(520f, 200f) lineTo(634f, 200f) quadTo(608f, 184f, 579f, 175.5f) quadTo(550f, 167f, 520f, 162f) lineTo(520f, 200f) close() } }.build() } val Icons.Outlined.Tonality: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Tonality", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(480f, 880f) quadTo(397f, 880f, 324f, 848.5f) quadTo(251f, 817f, 197f, 763f) quadTo(143f, 709f, 111.5f, 636f) quadTo(80f, 563f, 80f, 480f) quadTo(80f, 397f, 111.5f, 324f) quadTo(143f, 251f, 197f, 197f) quadTo(251f, 143f, 324f, 111.5f) quadTo(397f, 80f, 480f, 80f) quadTo(563f, 80f, 636f, 111.5f) quadTo(709f, 143f, 763f, 197f) quadTo(817f, 251f, 848.5f, 324f) quadTo(880f, 397f, 880f, 480f) quadTo(880f, 563f, 848.5f, 636f) quadTo(817f, 709f, 763f, 763f) quadTo(709f, 817f, 636f, 848.5f) quadTo(563f, 880f, 480f, 880f) close() moveTo(440f, 798f) lineTo(440f, 162f) quadTo(319f, 177f, 239.5f, 268f) quadTo(160f, 359f, 160f, 480f) quadTo(160f, 601f, 239.5f, 692f) quadTo(319f, 783f, 440f, 798f) close() moveTo(520f, 798f) quadTo(550f, 793f, 579f, 784.5f) quadTo(608f, 776f, 634f, 760f) lineTo(520f, 760f) lineTo(520f, 798f) close() moveTo(520f, 680f) lineTo(730f, 680f) quadTo(738f, 671f, 744f, 661f) quadTo(750f, 651f, 756f, 640f) lineTo(520f, 640f) lineTo(520f, 680f) close() moveTo(520f, 560f) lineTo(790f, 560f) quadTo(792f, 550f, 794f, 540f) quadTo(796f, 530f, 798f, 520f) lineTo(520f, 520f) lineTo(520f, 560f) close() moveTo(520f, 440f) lineTo(798f, 440f) quadTo(796f, 430f, 794f, 420f) quadTo(792f, 410f, 790f, 400f) lineTo(520f, 400f) lineTo(520f, 440f) close() moveTo(520f, 320f) lineTo(756f, 320f) quadTo(750f, 309f, 744f, 299f) quadTo(738f, 289f, 730f, 280f) lineTo(520f, 280f) lineTo(520f, 320f) close() moveTo(520f, 200f) lineTo(634f, 200f) quadTo(608f, 184f, 579f, 175.5f) quadTo(550f, 167f, 520f, 162f) lineTo(520f, 200f) close() moveTo(440f, 480f) quadTo(440f, 480f, 440f, 480f) quadTo(440f, 480f, 440f, 480f) quadTo(440f, 480f, 440f, 480f) quadTo(440f, 480f, 440f, 480f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Toolbox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Toolbox: ImageVector by lazy { Builder( name = "Toolbox", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.0f, 16.0f) horizontalLineTo(16.0f) verticalLineTo(15.0f) horizontalLineTo(8.0f) verticalLineTo(16.0f) horizontalLineTo(6.0f) verticalLineTo(15.0f) horizontalLineTo(2.0f) verticalLineTo(20.0f) horizontalLineTo(22.0f) verticalLineTo(15.0f) horizontalLineTo(18.0f) verticalLineTo(16.0f) moveTo(20.0f, 8.0f) horizontalLineTo(17.0f) verticalLineTo(6.0f) curveTo(17.0f, 4.9f, 16.1f, 4.0f, 15.0f, 4.0f) horizontalLineTo(9.0f) curveTo(7.9f, 4.0f, 7.0f, 4.9f, 7.0f, 6.0f) verticalLineTo(8.0f) horizontalLineTo(4.0f) curveTo(2.9f, 8.0f, 2.0f, 8.9f, 2.0f, 10.0f) verticalLineTo(14.0f) horizontalLineTo(6.0f) verticalLineTo(12.0f) horizontalLineTo(8.0f) verticalLineTo(14.0f) horizontalLineTo(16.0f) verticalLineTo(12.0f) horizontalLineTo(18.0f) verticalLineTo(14.0f) horizontalLineTo(22.0f) verticalLineTo(10.0f) curveTo(22.0f, 8.9f, 21.1f, 8.0f, 20.0f, 8.0f) moveTo(15.0f, 8.0f) horizontalLineTo(9.0f) verticalLineTo(6.0f) horizontalLineTo(15.0f) verticalLineTo(8.0f) close() } }.build() } val Icons.Outlined.Toolbox: ImageVector by lazy { Builder( name = "Toolbox Outline", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.0f, 8.0f) horizontalLineTo(17.0f) verticalLineTo(6.0f) curveTo(17.0f, 4.9f, 16.1f, 4.0f, 15.0f, 4.0f) horizontalLineTo(9.0f) curveTo(7.9f, 4.0f, 7.0f, 4.9f, 7.0f, 6.0f) verticalLineTo(8.0f) horizontalLineTo(4.0f) curveTo(2.9f, 8.0f, 2.0f, 8.9f, 2.0f, 10.0f) verticalLineTo(20.0f) horizontalLineTo(22.0f) verticalLineTo(10.0f) curveTo(22.0f, 8.9f, 21.1f, 8.0f, 20.0f, 8.0f) moveTo(9.0f, 6.0f) horizontalLineTo(15.0f) verticalLineTo(8.0f) horizontalLineTo(9.0f) verticalLineTo(6.0f) moveTo(20.0f, 18.0f) horizontalLineTo(4.0f) verticalLineTo(15.0f) horizontalLineTo(6.0f) verticalLineTo(16.0f) horizontalLineTo(8.0f) verticalLineTo(15.0f) horizontalLineTo(16.0f) verticalLineTo(16.0f) horizontalLineTo(18.0f) verticalLineTo(15.0f) horizontalLineTo(20.0f) verticalLineTo(18.0f) moveTo(18.0f, 13.0f) verticalLineTo(12.0f) horizontalLineTo(16.0f) verticalLineTo(13.0f) horizontalLineTo(8.0f) verticalLineTo(12.0f) horizontalLineTo(6.0f) verticalLineTo(13.0f) horizontalLineTo(4.0f) verticalLineTo(10.0f) horizontalLineTo(20.0f) verticalLineTo(13.0f) horizontalLineTo(18.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/TopLeft.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.TopLeft: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.TopLeft", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(5f, 15f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(5f, 11f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(19f, 9f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(19f, 5f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(5f, 7f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(19f, 17f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(19f, 13f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(17f, 3f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(13f, 3f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(5f, 3f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(13f, 11f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(9f, 3f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(19f, 21f) lineToRelative(2f, 0f) lineToRelative(0f, -2f) lineToRelative(-2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(17f, 19f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(13f, 19f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(5f, 19f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(9f, 19f) lineToRelative(-2f, 0f) lineToRelative(0f, 2f) lineToRelative(2f, 0f) } path(fill = SolidColor(Color.Black)) { moveTo(5f, 13f) lineToRelative(0f, -8f) lineToRelative(8f, 0f) lineToRelative(0f, -2f) lineToRelative(-8f, -0f) lineToRelative(-2f, -0f) lineToRelative(0f, 2f) lineToRelative(0f, 8f) lineToRelative(2f, 0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Tortoise.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Tortoise: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.Tortoise", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(19.31f, 5.6f) curveTo(18.09f, 5.56f, 16.88f, 6.5f, 16.5f, 8f) curveTo(16f, 10f, 16f, 10f, 15f, 11f) curveTo(13f, 13f, 10f, 14f, 4f, 15f) curveTo(3f, 15.16f, 2.5f, 15.5f, 2f, 16f) curveTo(4f, 16f, 6f, 16f, 4.5f, 17.5f) lineTo(3f, 19f) horizontalLineTo(6f) lineTo(8f, 17f) curveTo(10f, 18f, 11.33f, 18f, 13.33f, 17f) lineTo(14f, 19f) horizontalLineTo(17f) lineTo(16f, 16f) curveTo(16f, 16f, 17f, 12f, 18f, 11f) curveTo(19f, 10f, 19f, 11f, 20f, 11f) curveTo(21f, 11f, 22f, 10f, 22f, 8.5f) curveTo(22f, 8f, 22f, 7f, 20.5f, 6f) curveTo(20.15f, 5.76f, 19.74f, 5.62f, 19.31f, 5.6f) moveTo(9f, 6f) arcTo(6f, 6f, 0f, isMoreThanHalf = false, isPositiveArc = false, 3f, 12f) curveTo(3f, 12.6f, 3.13f, 13.08f, 3.23f, 13.6f) curveTo(9.15f, 12.62f, 12.29f, 11.59f, 13.93f, 9.94f) lineTo(14.43f, 9.44f) curveTo(13.44f, 7.34f, 11.32f, 6f, 9f, 6f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Transparency.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Transparency: ImageVector by lazy { Builder( name = "Transparency", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(19.0f, 11.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(19.0f, 7.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.0f, 15.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.0f, 19.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(21.0f, 19.0f) horizontalLineToRelative(-2.0f) verticalLineToRelative(2.0f) curveTo(20.1046f, 21.0f, 21.0f, 20.1046f, 21.0f, 19.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(19.0f, 15.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.0f, 3.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.0f, 17.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.0f, 5.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(19.0f, 3.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(2.0f) curveTo(21.0f, 3.8954f, 20.1046f, 3.0f, 19.0f, 3.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.0f, 9.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(13.0f, 17.0f) verticalLineToRelative(-2.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(-2.0f) horizontalLineToRelative(-2.0f) verticalLineToRelative(-2.0f) horizontalLineToRelative(2.0f) verticalLineTo(9.0f) horizontalLineToRelative(-2.0f) verticalLineTo(7.0f) horizontalLineToRelative(2.0f) verticalLineTo(5.0f) horizontalLineToRelative(-2.0f) verticalLineTo(3.0f) horizontalLineTo(5.0f) curveTo(3.8954f, 3.0f, 3.0f, 3.8954f, 3.0f, 5.0f) verticalLineToRelative(14.0f) curveToRelative(0.0f, 1.1046f, 0.8954f, 2.0f, 2.0f, 2.0f) horizontalLineToRelative(8.0f) verticalLineToRelative(-2.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(-2.0f) horizontalLineTo(13.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.0f, 7.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(15.0f, 11.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(17.0f, 13.0f) horizontalLineToRelative(2.0f) verticalLineToRelative(2.0f) horizontalLineToRelative(-2.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triadic.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.Triadic: ImageVector by lazy { Builder( name = "Triadic", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(6.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(4.9f, 16.0f, 6.0f, 16.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.0f, 4.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(10.9f, 4.0f, 12.0f, 4.0f) } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.0f, 16.0f) curveToRelative(1.1f, 0.0f, 2.0f, 0.9f, 2.0f, 2.0f) reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) reflectiveCurveTo(16.9f, 16.0f, 18.0f, 16.0f) } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Triangle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Triangle: ImageVector by lazy { ImageVector.Builder( name = "OutlinedTriangle", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(22.125f, 19.5f) lineToRelative(-9.25f, -16.0f) curveToRelative(-0.1f, -0.1667f, -0.2292f, -0.2917f, -0.3875f, -0.375f) reflectiveCurveTo(12.1667f, 3.0f, 12.0f, 3.0f) reflectiveCurveToRelative(-0.3292f, 0.0417f, -0.4875f, 0.125f) reflectiveCurveTo(11.225f, 3.3333f, 11.125f, 3.5f) lineToRelative(-9.25f, 16.0f) curveToRelative(-0.1f, 0.1667f, -0.1458f, 0.3375f, -0.1375f, 0.5125f) curveTo(1.7458f, 20.1875f, 1.7917f, 20.35f, 1.875f, 20.5f) reflectiveCurveToRelative(0.2f, 0.2708f, 0.35f, 0.3625f) curveTo(2.375f, 20.9542f, 2.5417f, 21.0f, 2.725f, 21.0f) horizontalLineToRelative(18.55f) curveToRelative(0.1833f, 0.0f, 0.35f, -0.0458f, 0.5f, -0.1375f) curveTo(21.925f, 20.7708f, 22.0417f, 20.65f, 22.125f, 20.5f) reflectiveCurveToRelative(0.1292f, -0.3125f, 0.1375f, -0.4875f) curveTo(22.2708f, 19.8375f, 22.225f, 19.6667f, 22.125f, 19.5f) close() moveTo(4.45f, 19.0f) lineTo(12.0f, 6.0f) lineToRelative(7.55f, 13.0f) horizontalLineTo(4.45f) close() } } .build() } val Icons.Rounded.Triangle: ImageVector by lazy { ImageVector.Builder( name = "Triangle", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(2.725f, 21.0f) curveToRelative(-0.1833f, 0.0f, -0.35f, -0.0458f, -0.5f, -0.1375f) curveTo(2.075f, 20.7708f, 1.9583f, 20.65f, 1.875f, 20.5f) reflectiveCurveToRelative(-0.1292f, -0.3125f, -0.1375f, -0.4875f) curveTo(1.7292f, 19.8375f, 1.775f, 19.6667f, 1.875f, 19.5f) lineToRelative(9.25f, -16.0f) curveToRelative(0.1f, -0.1667f, 0.2292f, -0.2917f, 0.3875f, -0.375f) curveToRelative(0.1583f, -0.0833f, 0.3208f, -0.125f, 0.4875f, -0.125f) curveToRelative(0.1667f, 0.0f, 0.3292f, 0.0417f, 0.4875f, 0.125f) curveToRelative(0.1583f, 0.0833f, 0.2875f, 0.2083f, 0.3875f, 0.375f) lineToRelative(9.25f, 16.0f) curveToRelative(0.1f, 0.1667f, 0.1458f, 0.3375f, 0.1375f, 0.5125f) curveTo(22.2542f, 20.1875f, 22.2083f, 20.35f, 22.125f, 20.5f) curveToRelative(-0.0833f, 0.15f, -0.2f, 0.2708f, -0.35f, 0.3625f) curveTo(21.625f, 20.9542f, 21.4583f, 21.0f, 21.275f, 21.0f) horizontalLineTo(2.725f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 4.0f, pathFillType = PathFillType.NonZero ) { moveTo(4.45f, 19.0f) lineToRelative(15.1f, 0.0f) lineToRelative(-7.55f, -13.0f) close() } } .build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/USDT.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Filled.USDT: ImageVector by lazy { Builder( name = "USDT", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(18.7538f, 10.5176f) curveToRelative(0.0f, 0.6251f, -2.2379f, 1.1483f, -5.2381f, 1.2812f) lineToRelative(0.0028f, 7.0E-4f) curveToRelative(-0.0848f, 0.0064f, -0.5233f, 0.0325f, -1.5012f, 0.0325f) curveToRelative(-0.7778f, 0.0f, -1.33f, -0.0233f, -1.5237f, -0.0325f) curveToRelative(-3.0059f, -0.1322f, -5.2495f, -0.6555f, -5.2495f, -1.2819f) reflectiveCurveToRelative(2.2436f, -1.149f, 5.2495f, -1.2834f) verticalLineToRelative(2.0442f) curveToRelative(0.1965f, 0.0142f, 0.7594f, 0.0474f, 1.5372f, 0.0474f) curveToRelative(0.9334f, 0.0f, 1.4008f, -0.0389f, 1.4849f, -0.0466f) lineTo(13.5157f, 9.2356f) curveToRelative(2.9994f, 0.1337f, 5.2381f, 0.657f, 5.2381f, 1.282f) close() moveTo(23.9438f, 11.0642f) lineTo(12.1248f, 22.389f) arcToRelative( 0.1803f, 0.1803f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = -0.2496f, dy1 = 0.0f ) lineTo(0.0562f, 11.0635f) arcToRelative( 0.1781f, 0.1781f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = -0.0382f, dy1 = -0.2079f ) lineToRelative(4.3762f, -9.1921f) arcToRelative( 0.1767f, 0.1767f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = 0.1626f, dy1 = -0.1026f ) horizontalLineToRelative(14.8878f) arcToRelative( 0.1768f, 0.1768f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = 0.1612f, dy1 = 0.1032f ) lineToRelative(4.3762f, 9.1922f) arcToRelative( 0.1782f, 0.1782f, 0.0f, isMoreThanHalf = false, isPositiveArc = true, dx1 = -0.0382f, dy1 = 0.2079f ) close() moveTo(19.4658f, 10.6604f) curveToRelative(0.0f, -0.8068f, -2.5515f, -1.4799f, -5.9473f, -1.6369f) lineTo(13.5185f, 7.195f) horizontalLineToRelative(4.186f) lineTo(17.7045f, 4.4055f) lineTo(6.3076f, 4.4055f) lineTo(6.3076f, 7.195f) horizontalLineToRelative(4.1852f) verticalLineToRelative(1.8286f) curveToRelative(-3.4018f, 0.1562f, -5.9601f, 0.83f, -5.9601f, 1.6376f) curveToRelative(0.0f, 0.8075f, 2.5583f, 1.4806f, 5.9601f, 1.6376f) verticalLineToRelative(5.8618f) horizontalLineToRelative(3.025f) verticalLineToRelative(-5.8639f) curveToRelative(3.394f, -0.1563f, 5.948f, -0.8295f, 5.948f, -1.6363f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Unarchive.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Unarchive: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Unarchive", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12.713f, 17.388f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) verticalLineToRelative(-3.2f) lineToRelative(0.9f, 0.9f) curveToRelative(0.183f, 0.183f, 0.417f, 0.275f, 0.7f, 0.275f) reflectiveCurveToRelative(0.517f, -0.092f, 0.7f, -0.275f) reflectiveCurveToRelative(0.275f, -0.417f, 0.275f, -0.7f) reflectiveCurveToRelative(-0.092f, -0.517f, -0.275f, -0.7f) lineToRelative(-2.6f, -2.575f) curveToRelative(-0.2f, -0.2f, -0.433f, -0.3f, -0.7f, -0.3f) reflectiveCurveToRelative(-0.5f, 0.1f, -0.7f, 0.3f) lineToRelative(-2.6f, 2.575f) curveToRelative(-0.183f, 0.183f, -0.275f, 0.417f, -0.275f, 0.7f) reflectiveCurveToRelative(0.092f, 0.517f, 0.275f, 0.7f) reflectiveCurveToRelative(0.417f, 0.275f, 0.7f, 0.275f) reflectiveCurveToRelative(0.517f, -0.092f, 0.7f, -0.275f) lineToRelative(0.9f, -0.9f) verticalLineToRelative(3.2f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.712f, -0.287f) close() moveTo(5f, 8f) verticalLineToRelative(11f) horizontalLineToRelative(14f) verticalLineTo(8f) horizontalLineTo(5f) close() moveTo(5f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(6.525f) curveToRelative(0f, -0.233f, 0.038f, -0.458f, 0.112f, -0.675f) reflectiveCurveToRelative(0.188f, -0.417f, 0.338f, -0.6f) lineToRelative(1.25f, -1.525f) curveToRelative(0.183f, -0.233f, 0.412f, -0.412f, 0.688f, -0.538f) reflectiveCurveToRelative(0.563f, -0.188f, 0.863f, -0.188f) horizontalLineToRelative(11.5f) curveToRelative(0.3f, 0f, 0.587f, 0.063f, 0.863f, 0.188f) reflectiveCurveToRelative(0.504f, 0.304f, 0.688f, 0.538f) lineToRelative(1.25f, 1.525f) curveToRelative(0.15f, 0.183f, 0.262f, 0.383f, 0.338f, 0.6f) reflectiveCurveToRelative(0.112f, 0.442f, 0.112f, 0.675f) verticalLineToRelative(12.475f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(5f) close() moveTo(5.4f, 6f) horizontalLineToRelative(13.2f) lineToRelative(-0.85f, -1f) horizontalLineTo(6.25f) lineToRelative(-0.85f, 1f) close() } }.build() } val Icons.TwoTone.Unarchive: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.Unarchive", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12.713f, 17.388f) curveToRelative(0.192f, -0.192f, 0.287f, -0.429f, 0.287f, -0.712f) verticalLineToRelative(-3.2f) lineToRelative(0.9f, 0.9f) curveToRelative(0.183f, 0.183f, 0.417f, 0.275f, 0.7f, 0.275f) reflectiveCurveToRelative(0.517f, -0.092f, 0.7f, -0.275f) reflectiveCurveToRelative(0.275f, -0.417f, 0.275f, -0.7f) reflectiveCurveToRelative(-0.092f, -0.517f, -0.275f, -0.7f) lineToRelative(-2.6f, -2.575f) curveToRelative(-0.2f, -0.2f, -0.433f, -0.3f, -0.7f, -0.3f) reflectiveCurveToRelative(-0.5f, 0.1f, -0.7f, 0.3f) lineToRelative(-2.6f, 2.575f) curveToRelative(-0.183f, 0.183f, -0.275f, 0.417f, -0.275f, 0.7f) reflectiveCurveToRelative(0.092f, 0.517f, 0.275f, 0.7f) reflectiveCurveToRelative(0.417f, 0.275f, 0.7f, 0.275f) reflectiveCurveToRelative(0.517f, -0.092f, 0.7f, -0.275f) lineToRelative(0.9f, -0.9f) verticalLineToRelative(3.2f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) reflectiveCurveToRelative(0.521f, -0.096f, 0.712f, -0.287f) close() moveTo(5f, 8f) verticalLineToRelative(11f) horizontalLineToRelative(14f) verticalLineTo(8f) horizontalLineTo(5f) close() moveTo(5f, 21f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineTo(6.525f) curveToRelative(0f, -0.233f, 0.038f, -0.458f, 0.112f, -0.675f) reflectiveCurveToRelative(0.188f, -0.417f, 0.338f, -0.6f) lineToRelative(1.25f, -1.525f) curveToRelative(0.183f, -0.233f, 0.412f, -0.412f, 0.688f, -0.538f) reflectiveCurveToRelative(0.563f, -0.188f, 0.863f, -0.188f) horizontalLineToRelative(11.5f) curveToRelative(0.3f, 0f, 0.587f, 0.063f, 0.863f, 0.188f) reflectiveCurveToRelative(0.504f, 0.304f, 0.688f, 0.538f) lineToRelative(1.25f, 1.525f) curveToRelative(0.15f, 0.183f, 0.262f, 0.383f, 0.338f, 0.6f) reflectiveCurveToRelative(0.112f, 0.442f, 0.112f, 0.675f) verticalLineToRelative(12.475f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) curveToRelative(-0.392f, 0.392f, -0.863f, 0.587f, -1.413f, 0.587f) horizontalLineTo(5f) close() moveTo(5.4f, 6f) horizontalLineToRelative(13.2f) lineToRelative(-0.85f, -1f) horizontalLineTo(6.25f) lineToRelative(-0.85f, 1f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 8f) horizontalLineToRelative(14f) verticalLineToRelative(11f) horizontalLineToRelative(-14f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Ungroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Ungroup: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Ungroup", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(2f, 2f) horizontalLineTo(6f) verticalLineTo(3f) horizontalLineTo(13f) verticalLineTo(2f) horizontalLineTo(17f) verticalLineTo(6f) horizontalLineTo(16f) verticalLineTo(9f) horizontalLineTo(18f) verticalLineTo(8f) horizontalLineTo(22f) verticalLineTo(12f) horizontalLineTo(21f) verticalLineTo(18f) horizontalLineTo(22f) verticalLineTo(22f) horizontalLineTo(18f) verticalLineTo(21f) horizontalLineTo(12f) verticalLineTo(22f) horizontalLineTo(8f) verticalLineTo(18f) horizontalLineTo(9f) verticalLineTo(16f) horizontalLineTo(6f) verticalLineTo(17f) horizontalLineTo(2f) verticalLineTo(13f) horizontalLineTo(3f) verticalLineTo(6f) horizontalLineTo(2f) verticalLineTo(2f) moveTo(18f, 12f) verticalLineTo(11f) horizontalLineTo(16f) verticalLineTo(13f) horizontalLineTo(17f) verticalLineTo(17f) horizontalLineTo(13f) verticalLineTo(16f) horizontalLineTo(11f) verticalLineTo(18f) horizontalLineTo(12f) verticalLineTo(19f) horizontalLineTo(18f) verticalLineTo(18f) horizontalLineTo(19f) verticalLineTo(12f) horizontalLineTo(18f) moveTo(13f, 6f) verticalLineTo(5f) horizontalLineTo(6f) verticalLineTo(6f) horizontalLineTo(5f) verticalLineTo(13f) horizontalLineTo(6f) verticalLineTo(14f) horizontalLineTo(9f) verticalLineTo(12f) horizontalLineTo(8f) verticalLineTo(8f) horizontalLineTo(12f) verticalLineTo(9f) horizontalLineTo(14f) verticalLineTo(6f) horizontalLineTo(13f) moveTo(12f, 12f) horizontalLineTo(11f) verticalLineTo(14f) horizontalLineTo(13f) verticalLineTo(13f) horizontalLineTo(14f) verticalLineTo(11f) horizontalLineTo(12f) verticalLineTo(12f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VectorPolyline.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.VectorPolyline: ImageVector by lazy { ImageVector.Builder( name = "Outlined.VectorPolyline", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(12f, 6f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) close() moveTo(5f, 14f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) close() moveTo(17f, 20f) horizontalLineToRelative(2f) verticalLineToRelative(-2f) horizontalLineToRelative(-2f) verticalLineToRelative(2f) close() moveTo(15f, 20f) verticalLineToRelative(-0.5f) lineToRelative(-7f, -3.5f) horizontalLineToRelative(-3f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) curveToRelative(-0.392f, -0.392f, -0.587f, -0.863f, -0.587f, -1.413f) verticalLineToRelative(-2f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) curveToRelative(0.392f, -0.392f, 0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(2.3f) lineToRelative(2.7f, -3.1f) verticalLineToRelative(-2.9f) curveToRelative(0f, -0.55f, 0.196f, -1.021f, 0.587f, -1.413f) reflectiveCurveToRelative(0.863f, -0.587f, 1.413f, -0.587f) horizontalLineToRelative(2f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(2f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-2.3f) lineToRelative(-2.7f, 3.1f) verticalLineToRelative(3.15f) lineToRelative(6.125f, 3.05f) curveToRelative(0.133f, -0.383f, 0.371f, -0.696f, 0.712f, -0.938f) reflectiveCurveToRelative(0.729f, -0.363f, 1.163f, -0.363f) horizontalLineToRelative(2f) curveToRelative(0.55f, 0f, 1.021f, 0.196f, 1.413f, 0.587f) reflectiveCurveToRelative(0.587f, 0.863f, 0.587f, 1.413f) verticalLineToRelative(2f) curveToRelative(0f, 0.55f, -0.196f, 1.021f, -0.587f, 1.413f) reflectiveCurveToRelative(-0.863f, 0.587f, -1.413f, 0.587f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, -0.196f, -1.413f, -0.587f) reflectiveCurveToRelative(-0.587f, -0.863f, -0.587f, -1.413f) close() } }.build() } val Icons.TwoTone.VectorPolyline: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.VectorPolyline", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(20.412f, 16.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-2f) curveToRelative(-0.433f, 0f, -0.821f, 0.121f, -1.162f, 0.362f) curveToRelative(-0.342f, 0.242f, -0.579f, 0.554f, -0.713f, 0.938f) lineToRelative(-6.125f, -3.05f) verticalLineToRelative(-3.15f) lineToRelative(2.7f, -3.1f) horizontalLineToRelative(2.3f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineToRelative(-2f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) reflectiveCurveToRelative(-0.862f, -0.588f, -1.412f, -0.588f) horizontalLineToRelative(-2f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(2.9f) lineToRelative(-2.7f, 3.1f) horizontalLineToRelative(-2.3f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(2f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(3f) lineToRelative(7f, 3.5f) verticalLineToRelative(0.5f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(2f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineToRelative(-2f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(12f, 4f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) verticalLineToRelative(-2f) close() moveTo(7f, 14f) horizontalLineToRelative(-2f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) close() moveTo(19f, 20f) horizontalLineToRelative(-2f) verticalLineToRelative(-2f) horizontalLineToRelative(2f) verticalLineToRelative(2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(12f, 4f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 12f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(17f, 18f) horizontalLineToRelative(2f) verticalLineToRelative(2f) horizontalLineToRelative(-2f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/VolunteerActivism.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.VolunteerActivism: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.VolunteerActivism", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(280f, 801f) lineTo(280f, 440f) lineTo(344f, 440f) quadTo(351f, 440f, 358f, 441.5f) quadTo(365f, 443f, 372f, 445f) lineTo(649f, 548f) quadTo(663f, 553f, 671.5f, 566f) quadTo(680f, 579f, 680f, 593f) quadTo(680f, 614f, 665.5f, 627f) quadTo(651f, 640f, 632f, 640f) lineTo(527f, 640f) quadTo(522f, 640f, 519.5f, 639.5f) quadTo(517f, 639f, 513f, 637f) lineTo(449f, 612f) lineTo(436f, 651f) lineTo(513f, 678f) quadTo(515f, 679f, 519f, 679.5f) quadTo(523f, 680f, 526f, 680f) lineTo(800f, 680f) quadTo(832f, 680f, 856f, 703f) quadTo(880f, 726f, 880f, 760f) lineTo(561f, 880f) lineTo(280f, 801f) close() moveTo(40f, 880f) lineTo(40f, 440f) lineTo(200f, 440f) lineTo(200f, 880f) lineTo(40f, 880f) close() moveTo(640f, 520f) lineTo(474f, 358f) quadTo(443f, 328f, 421.5f, 291.5f) quadTo(400f, 255f, 400f, 212f) quadTo(400f, 157f, 438.5f, 118.5f) quadTo(477f, 80f, 532f, 80f) quadTo(564f, 80f, 592f, 93.5f) quadTo(620f, 107f, 640f, 130f) quadTo(660f, 107f, 688f, 93.5f) quadTo(716f, 80f, 748f, 80f) quadTo(803f, 80f, 841.5f, 118.5f) quadTo(880f, 157f, 880f, 212f) quadTo(880f, 255f, 859f, 291.5f) quadTo(838f, 328f, 807f, 358f) lineTo(640f, 520f) close() } }.build() } val Icons.Outlined.VolunteerActivism: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.VolunteerActivism", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(640f, 520f) lineTo(474f, 358f) quadTo(443f, 328f, 421.5f, 291.5f) quadTo(400f, 255f, 400f, 212f) quadTo(400f, 157f, 438.5f, 118.5f) quadTo(477f, 80f, 532f, 80f) quadTo(564f, 80f, 592f, 93.5f) quadTo(620f, 107f, 640f, 130f) quadTo(660f, 107f, 688f, 93.5f) quadTo(716f, 80f, 748f, 80f) quadTo(803f, 80f, 841.5f, 118.5f) quadTo(880f, 157f, 880f, 212f) quadTo(880f, 255f, 859f, 291.5f) quadTo(838f, 328f, 807f, 358f) lineTo(640f, 520f) close() moveTo(640f, 408f) lineTo(749f, 301f) quadTo(768f, 282f, 784f, 260.5f) quadTo(800f, 239f, 800f, 212f) quadTo(800f, 190f, 785f, 175f) quadTo(770f, 160f, 748f, 160f) quadTo(734f, 160f, 721.5f, 165.5f) quadTo(709f, 171f, 700f, 182f) lineTo(640f, 254f) lineTo(580f, 182f) quadTo(571f, 171f, 558.5f, 165.5f) quadTo(546f, 160f, 532f, 160f) quadTo(510f, 160f, 495f, 175f) quadTo(480f, 190f, 480f, 212f) quadTo(480f, 239f, 496f, 260.5f) quadTo(512f, 282f, 531f, 301f) lineTo(640f, 408f) close() moveTo(280f, 740f) lineTo(558f, 816f) lineTo(796f, 742f) quadTo(791f, 733f, 781.5f, 726.5f) quadTo(772f, 720f, 760f, 720f) lineTo(558f, 720f) quadTo(531f, 720f, 515f, 718f) quadTo(499f, 716f, 482f, 710f) lineTo(389f, 679f) lineTo(411f, 601f) lineTo(492f, 628f) quadTo(509f, 633f, 532f, 636f) quadTo(555f, 639f, 600f, 640f) lineTo(600f, 640f) quadTo(600f, 640f, 600f, 640f) quadTo(600f, 640f, 600f, 640f) quadTo(600f, 629f, 593.5f, 619f) quadTo(587f, 609f, 578f, 606f) lineTo(344f, 520f) quadTo(344f, 520f, 344f, 520f) quadTo(344f, 520f, 344f, 520f) lineTo(280f, 520f) lineTo(280f, 740f) close() moveTo(40f, 880f) lineTo(40f, 440f) lineTo(344f, 440f) quadTo(351f, 440f, 358f, 441.5f) quadTo(365f, 443f, 371f, 445f) lineTo(606f, 532f) quadTo(639f, 544f, 659.5f, 574f) quadTo(680f, 604f, 680f, 640f) lineTo(760f, 640f) quadTo(810f, 640f, 845f, 673f) quadTo(880f, 706f, 880f, 760f) lineTo(880f, 800f) lineTo(560f, 900f) lineTo(280f, 822f) lineTo(280f, 822f) lineTo(280f, 880f) lineTo(40f, 880f) close() moveTo(120f, 800f) lineTo(200f, 800f) lineTo(200f, 520f) lineTo(120f, 520f) lineTo(120f, 800f) close() moveTo(640f, 254f) lineTo(640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) lineTo(640f, 254f) lineTo(640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) quadTo(640f, 254f, 640f, 254f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WallpaperAlt.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.WallpaperAlt: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Wallpaper", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(280f, 920f) quadToRelative(-33f, 0f, -56.5f, -23.5f) reflectiveQuadTo(200f, 840f) verticalLineToRelative(-720f) quadToRelative(0f, -33f, 23.5f, -56.5f) reflectiveQuadTo(280f, 40f) horizontalLineToRelative(400f) quadToRelative(33f, 0f, 56.5f, 23.5f) reflectiveQuadTo(760f, 120f) verticalLineToRelative(124f) quadToRelative(18f, 7f, 29f, 22f) reflectiveQuadToRelative(11f, 34f) verticalLineToRelative(80f) quadToRelative(0f, 19f, -11f, 34f) reflectiveQuadToRelative(-29f, 22f) verticalLineToRelative(404f) quadToRelative(0f, 33f, -23.5f, 56.5f) reflectiveQuadTo(680f, 920f) lineTo(280f, 920f) close() moveTo(280f, 840f) horizontalLineToRelative(400f) verticalLineToRelative(-720f) lineTo(280f, 120f) verticalLineToRelative(720f) close() moveTo(320f, 600f) horizontalLineToRelative(320f) lineTo(535f, 460f) lineToRelative(-75f, 100f) lineToRelative(-55f, -73f) lineToRelative(-85f, 113f) close() moveTo(600f, 400f) quadToRelative(17f, 0f, 28.5f, -11.5f) reflectiveQuadTo(640f, 360f) quadToRelative(0f, -17f, -11.5f, -28.5f) reflectiveQuadTo(600f, 320f) quadToRelative(-17f, 0f, -28.5f, 11.5f) reflectiveQuadTo(560f, 360f) quadToRelative(0f, 17f, 11.5f, 28.5f) reflectiveQuadTo(600f, 400f) close() moveTo(280f, 840f) verticalLineToRelative(-720f) verticalLineToRelative(720f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandShine.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.WandShine: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.WandShine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(331f, 309f) lineTo(211f, 189f) lineToRelative(57f, -57f) lineToRelative(120f, 120f) lineToRelative(-57f, 57f) close() moveTo(480f, 214f) verticalLineToRelative(-170f) horizontalLineToRelative(80f) verticalLineToRelative(170f) horizontalLineToRelative(-80f) close() moveTo(771f, 749f) lineTo(651f, 629f) lineToRelative(57f, -57f) lineToRelative(120f, 120f) lineToRelative(-57f, 57f) close() moveTo(708f, 309f) lineTo(651f, 252f) lineTo(771f, 132f) lineTo(828f, 189f) lineTo(708f, 309f) close() moveTo(746f, 480f) verticalLineToRelative(-80f) horizontalLineToRelative(170f) verticalLineToRelative(80f) lineTo(746f, 480f) close() moveTo(205f, 868f) lineTo(92f, 755f) quadToRelative(-12f, -12f, -12f, -28f) reflectiveQuadToRelative(12f, -28f) lineToRelative(363f, -364f) quadToRelative(35f, -35f, 85f, -35f) reflectiveQuadToRelative(85f, 35f) quadToRelative(35f, 35f, 35f, 85f) reflectiveQuadToRelative(-35f, 85f) lineTo(261f, 868f) quadToRelative(-12f, 12f, -28f, 12f) reflectiveQuadToRelative(-28f, -12f) close() moveTo(484f, 533f) lineTo(469.5f, 519f) lineTo(455f, 505f) lineTo(441f, 491f) lineTo(427f, 477f) lineTo(455f, 505f) lineTo(484f, 533f) close() moveTo(233f, 784f) lineToRelative(251f, -251f) lineToRelative(-57f, -56f) lineToRelative(-250f, 250f) lineToRelative(56f, 57f) close() } }.build() } val Icons.Rounded.WandShine: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.WandShine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(331f, 309f) lineTo(211f, 189f) lineToRelative(57f, -57f) lineToRelative(120f, 120f) lineToRelative(-57f, 57f) close() moveTo(480f, 214f) verticalLineToRelative(-170f) horizontalLineToRelative(80f) verticalLineToRelative(170f) horizontalLineToRelative(-80f) close() moveTo(771f, 749f) lineTo(651f, 629f) lineToRelative(57f, -57f) lineToRelative(120f, 120f) lineToRelative(-57f, 57f) close() moveTo(708f, 309f) lineTo(651f, 252f) lineTo(771f, 132f) lineTo(828f, 189f) lineTo(708f, 309f) close() moveTo(746f, 480f) verticalLineToRelative(-80f) horizontalLineToRelative(170f) verticalLineToRelative(80f) lineTo(746f, 480f) close() moveTo(205f, 868f) lineTo(92f, 755f) quadToRelative(-12f, -12f, -12f, -28f) reflectiveQuadToRelative(12f, -28f) lineToRelative(363f, -364f) quadToRelative(35f, -35f, 85f, -35f) reflectiveQuadToRelative(85f, 35f) quadToRelative(35f, 35f, 35f, 85f) reflectiveQuadToRelative(-35f, 85f) lineTo(261f, 868f) quadToRelative(-12f, 12f, -28f, 12f) reflectiveQuadToRelative(-28f, -12f) close() moveTo(484f, 533f) lineTo(568f, 448f) quadToRelative(12f, -12f, 12f, -28f) reflectiveQuadToRelative(-12f, -28f) quadToRelative(-12f, -12f, -28f, -12f) reflectiveQuadToRelative(-28f, 12f) lineToRelative(-85f, 85f) lineToRelative(57f, 56f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WandStars.kt ================================================ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.WandStars: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.WandStars", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(646f, 522f) lineTo(560f, 660f) quadTo(549f, 677f, 529.5f, 674f) quadTo(510f, 671f, 505f, 651f) lineTo(477f, 539f) lineTo(204f, 812f) quadTo(193f, 823f, 176.5f, 823.5f) quadTo(160f, 824f, 148f, 812f) quadTo(137f, 801f, 137f, 784f) quadTo(137f, 767f, 148f, 756f) lineTo(421f, 482f) lineTo(309f, 454f) quadTo(289f, 449f, 286f, 429.5f) quadTo(283f, 410f, 300f, 399f) lineTo(438f, 314f) lineTo(426f, 151f) quadTo(424f, 131f, 442f, 122f) quadTo(460f, 113f, 475f, 126f) lineTo(600f, 231f) lineTo(751f, 170f) quadTo(770f, 162f, 784f, 176f) quadTo(798f, 190f, 790f, 209f) lineTo(729f, 360f) lineTo(834f, 484f) quadTo(847f, 499f, 838f, 517f) quadTo(829f, 535f, 809f, 533f) lineTo(646f, 522f) close() moveTo(134f, 254f) quadTo(128f, 248f, 128f, 240f) quadTo(128f, 232f, 134f, 226f) lineTo(186f, 174f) quadTo(192f, 168f, 200f, 168f) quadTo(208f, 168f, 214f, 174f) lineTo(266f, 226f) quadTo(272f, 232f, 272f, 240f) quadTo(272f, 248f, 266f, 254f) lineTo(214f, 306f) quadTo(208f, 312f, 200f, 312f) quadTo(192f, 312f, 186f, 306f) lineTo(134f, 254f) close() moveTo(555f, 517f) lineTo(603f, 438f) lineTo(696f, 445f) lineTo(636f, 374f) lineTo(671f, 288f) lineTo(585f, 323f) lineTo(514f, 264f) lineTo(521f, 356f) lineTo(442f, 405f) lineTo(532f, 427f) lineTo(555f, 517f) close() moveTo(706f, 826f) lineTo(654f, 774f) quadTo(648f, 768f, 648f, 760f) quadTo(648f, 752f, 654f, 746f) lineTo(706f, 694f) quadTo(712f, 688f, 720f, 688f) quadTo(728f, 688f, 734f, 694f) lineTo(786f, 746f) quadTo(792f, 752f, 792f, 760f) quadTo(792f, 768f, 786f, 774f) lineTo(734f, 826f) quadTo(728f, 832f, 720f, 832f) quadTo(712f, 832f, 706f, 826f) close() moveTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) lineTo(569f, 390f) close() } }.build() } val Icons.Rounded.WandStars: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Rounded.WandStars", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 960f, viewportHeight = 960f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(646f, 522f) lineTo(560f, 660f) quadTo(549f, 677f, 529.5f, 674f) quadTo(510f, 671f, 505f, 651f) lineTo(477f, 539f) lineTo(204f, 812f) quadTo(193f, 823f, 176.5f, 823.5f) quadTo(160f, 824f, 148f, 812f) quadTo(137f, 801f, 137f, 784f) quadTo(137f, 767f, 148f, 756f) lineTo(421f, 482f) lineTo(309f, 454f) quadTo(289f, 449f, 286f, 429.5f) quadTo(283f, 410f, 300f, 399f) lineTo(438f, 314f) lineTo(426f, 151f) quadTo(424f, 131f, 442f, 122f) quadTo(460f, 113f, 475f, 126f) lineTo(600f, 231f) lineTo(751f, 170f) quadTo(770f, 162f, 784f, 176f) quadTo(798f, 190f, 790f, 209f) lineTo(729f, 360f) lineTo(834f, 484f) quadTo(847f, 499f, 838f, 517f) quadTo(829f, 535f, 809f, 533f) lineTo(646f, 522f) close() moveTo(134f, 254f) quadTo(128f, 248f, 128f, 240f) quadTo(128f, 232f, 134f, 226f) lineTo(186f, 174f) quadTo(192f, 168f, 200f, 168f) quadTo(208f, 168f, 214f, 174f) lineTo(266f, 226f) quadTo(272f, 232f, 272f, 240f) quadTo(272f, 248f, 266f, 254f) lineTo(214f, 306f) quadTo(208f, 312f, 200f, 312f) quadTo(192f, 312f, 186f, 306f) lineTo(134f, 254f) close() moveTo(706f, 826f) lineTo(654f, 774f) quadTo(648f, 768f, 648f, 760f) quadTo(648f, 752f, 654f, 746f) lineTo(706f, 694f) quadTo(712f, 688f, 720f, 688f) quadTo(728f, 688f, 734f, 694f) lineTo(786f, 746f) quadTo(792f, 752f, 792f, 760f) quadTo(792f, 768f, 786f, 774f) lineTo(734f, 826f) quadTo(728f, 832f, 720f, 832f) quadTo(712f, 832f, 706f, 826f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Watermark.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.Watermark: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Watermark", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(11f, 17f) horizontalLineToRelative(7f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) verticalLineToRelative(-4f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-7f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(4f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 4.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(6f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(20f, 17.398f) curveToRelative(0f, 0.333f, -0.27f, 0.602f, -0.602f, 0.602f) horizontalLineTo(4.602f) curveToRelative(-0.333f, 0f, -0.602f, -0.27f, -0.602f, -0.602f) verticalLineTo(6.602f) curveToRelative(0f, -0.333f, 0.27f, -0.602f, 0.602f, -0.602f) horizontalLineToRelative(14.795f) curveToRelative(0.333f, 0f, 0.602f, 0.27f, 0.602f, 0.602f) verticalLineToRelative(10.795f) close() } path(fill = SolidColor(Color.Black)) { moveTo(4f, 18f) verticalLineTo(6f) verticalLineToRelative(12f) close() } }.build() } val Icons.TwoTone.Watermark: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoToneWatermark", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color.Black)) { moveTo(21.412f, 4.588f) curveToRelative(-0.392f, -0.392f, -0.862f, -0.588f, -1.412f, -0.588f) horizontalLineTo(4f) curveToRelative(-0.55f, 0f, -1.021f, 0.196f, -1.412f, 0.588f) reflectiveCurveToRelative(-0.588f, 0.862f, -0.588f, 1.412f) verticalLineToRelative(12f) curveToRelative(0f, 0.55f, 0.196f, 1.021f, 0.588f, 1.412f) reflectiveCurveToRelative(0.862f, 0.588f, 1.412f, 0.588f) horizontalLineToRelative(16f) curveToRelative(0.55f, 0f, 1.021f, -0.196f, 1.412f, -0.588f) reflectiveCurveToRelative(0.588f, -0.862f, 0.588f, -1.412f) verticalLineTo(6f) curveToRelative(0f, -0.55f, -0.196f, -1.021f, -0.588f, -1.412f) close() moveTo(20f, 17.398f) curveToRelative(0f, 0.333f, -0.27f, 0.602f, -0.602f, 0.602f) horizontalLineTo(4.602f) curveToRelative(-0.333f, 0f, -0.602f, -0.27f, -0.602f, -0.602f) verticalLineTo(6.602f) curveToRelative(0f, -0.333f, 0.27f, -0.602f, 0.602f, -0.602f) horizontalLineToRelative(14.795f) curveToRelative(0.333f, 0f, 0.602f, 0.27f, 0.602f, 0.602f) verticalLineToRelative(10.795f) close() } path(fill = SolidColor(Color.Black)) { moveTo(11f, 17f) horizontalLineToRelative(7f) curveToRelative(0.283f, 0f, 0.521f, -0.096f, 0.712f, -0.287f) reflectiveCurveToRelative(0.287f, -0.429f, 0.287f, -0.712f) verticalLineToRelative(-4f) curveToRelative(0f, -0.283f, -0.096f, -0.521f, -0.287f, -0.712f) curveToRelative(-0.192f, -0.192f, -0.429f, -0.287f, -0.712f, -0.287f) horizontalLineToRelative(-7f) curveToRelative(-0.283f, 0f, -0.521f, 0.096f, -0.712f, 0.287f) curveToRelative(-0.192f, 0.192f, -0.287f, 0.429f, -0.287f, 0.712f) verticalLineToRelative(4f) curveToRelative(0f, 0.283f, 0.096f, 0.521f, 0.287f, 0.712f) reflectiveCurveToRelative(0.429f, 0.287f, 0.712f, 0.287f) close() } path( fill = SolidColor(Color.Black), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(4.602f, 6f) lineTo(19.398f, 6f) arcTo(0.602f, 0.602f, 0f, isMoreThanHalf = false, isPositiveArc = true, 20f, 6.602f) lineTo(20f, 17.398f) arcTo(0.602f, 0.602f, 0f, isMoreThanHalf = false, isPositiveArc = true, 19.398f, 18f) lineTo(4.602f, 18f) arcTo(0.602f, 0.602f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4f, 17.398f) lineTo(4f, 6.602f) arcTo(0.602f, 0.602f, 0f, isMoreThanHalf = false, isPositiveArc = true, 4.602f, 6f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Webp.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Webp: ImageVector by lazy { ImageVector.Builder( name = "Webp", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(4.115f, 14.781f) curveTo(3.499f, 14.781f, 3f, 14.282f, 3f, 13.666f) verticalLineTo(9.207f) horizontalLineToRelative(1.115f) verticalLineToRelative(4.459f) horizontalLineToRelative(1.115f) verticalLineTo(9.764f) horizontalLineToRelative(1.115f) verticalLineToRelative(3.902f) horizontalLineToRelative(1.115f) verticalLineTo(9.207f) horizontalLineToRelative(1.115f) verticalLineToRelative(4.459f) curveToRelative(0f, 0.616f, -0.499f, 1.115f, -1.115f, 1.115f) horizontalLineTo(4.115f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(9.372f, 9.202f) verticalLineToRelative(5.574f) horizontalLineToRelative(3.344f) verticalLineToRelative(-1.115f) horizontalLineToRelative(-2.23f) verticalLineToRelative(-1.115f) horizontalLineToRelative(2.23f) verticalLineToRelative(-1.115f) horizontalLineToRelative(-2.23f) verticalLineToRelative(-1.115f) horizontalLineToRelative(2.23f) verticalLineTo(9.202f) horizontalLineTo(9.372f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(16.858f, 11.158f) verticalLineToRelative(-0.836f) curveToRelative(0f, -0.616f, -0.499f, -1.115f, -1.115f, -1.115f) horizontalLineToRelative(-2.23f) verticalLineToRelative(5.574f) horizontalLineToRelative(2.23f) curveToRelative(0.616f, 0f, 1.115f, -0.499f, 1.115f, -1.115f) verticalLineToRelative(-0.836f) curveToRelative(0f, -0.446f, -0.39f, -0.836f, -0.836f, -0.836f) curveTo(16.468f, 11.994f, 16.858f, 11.604f, 16.858f, 11.158f) moveTo(15.743f, 13.666f) horizontalLineTo(14.628f) verticalLineToRelative(-1.115f) horizontalLineToRelative(1.115f) verticalLineTo(13.666f) moveTo(15.743f, 11.437f) horizontalLineTo(14.628f) verticalLineToRelative(-1.115f) horizontalLineToRelative(1.115f) verticalLineTo(11.437f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(17.656f, 9.207f) verticalLineToRelative(5.574f) horizontalLineToRelative(1.115f) verticalLineToRelative(-2.23f) horizontalLineToRelative(1.115f) curveTo(20.501f, 12.552f, 21f, 12.052f, 21f, 11.437f) verticalLineToRelative(-1.115f) curveToRelative(0f, -0.616f, -0.499f, -1.115f, -1.115f, -1.115f) horizontalLineTo(17.656f) moveTo(18.77f, 10.322f) horizontalLineToRelative(1.115f) verticalLineToRelative(1.115f) horizontalLineToRelative(-1.115f) verticalLineTo(10.322f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/WebpBox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Outlined.WebpBox: ImageVector by lazy { ImageVector.Builder( name = "WebpBox", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(6.757f, 13.855f) curveToRelative(-0.409f, 0f, -0.741f, -0.332f, -0.741f, -0.741f) verticalLineToRelative(-2.965f) horizontalLineToRelative(0.741f) verticalLineToRelative(2.965f) horizontalLineToRelative(0.741f) verticalLineToRelative(-2.594f) horizontalLineToRelative(0.741f) verticalLineToRelative(2.594f) horizontalLineToRelative(0.741f) verticalLineToRelative(-2.965f) horizontalLineToRelative(0.741f) verticalLineToRelative(2.965f) curveToRelative(0f, 0.409f, -0.332f, 0.741f, -0.741f, 0.741f) horizontalLineTo(6.757f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(10.252f, 10.145f) verticalLineToRelative(3.706f) horizontalLineToRelative(2.224f) verticalLineToRelative(-0.741f) horizontalLineToRelative(-1.483f) verticalLineToRelative(-0.741f) horizontalLineToRelative(1.483f) verticalLineToRelative(-0.741f) horizontalLineToRelative(-1.483f) verticalLineToRelative(-0.741f) horizontalLineToRelative(1.483f) verticalLineToRelative(-0.741f) horizontalLineTo(10.252f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15.23f, 11.446f) verticalLineToRelative(-0.556f) curveToRelative(0f, -0.409f, -0.332f, -0.741f, -0.741f, -0.741f) horizontalLineToRelative(-1.483f) verticalLineToRelative(3.706f) horizontalLineToRelative(1.483f) curveToRelative(0.409f, 0f, 0.741f, -0.332f, 0.741f, -0.741f) verticalLineToRelative(-0.556f) curveToRelative(0f, -0.297f, -0.259f, -0.556f, -0.556f, -0.556f) curveTo(14.971f, 12.002f, 15.23f, 11.742f, 15.23f, 11.446f) moveTo(14.489f, 13.114f) horizontalLineToRelative(-0.741f) verticalLineToRelative(-0.741f) horizontalLineToRelative(0.741f) verticalLineTo(13.114f) moveTo(14.489f, 11.631f) horizontalLineToRelative(-0.741f) verticalLineToRelative(-0.741f) horizontalLineToRelative(0.741f) verticalLineTo(11.631f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15.761f, 10.148f) verticalLineToRelative(3.706f) horizontalLineToRelative(0.741f) verticalLineToRelative(-1.483f) horizontalLineToRelative(0.741f) curveToRelative(0.409f, 0f, 0.741f, -0.332f, 0.741f, -0.741f) verticalLineToRelative(-0.741f) curveToRelative(0f, -0.409f, -0.332f, -0.741f, -0.741f, -0.741f) horizontalLineTo(15.761f) moveTo(16.502f, 10.89f) horizontalLineToRelative(0.741f) verticalLineToRelative(0.741f) horizontalLineToRelative(-0.741f) verticalLineTo(10.89f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(19f, 3f) horizontalLineTo(5f) curveTo(3.89f, 3f, 3f, 3.89f, 3f, 5f) verticalLineToRelative(14f) curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) horizontalLineToRelative(14f) curveToRelative(1.105f, 0f, 2f, -0.895f, 2f, -2f) verticalLineTo(5f) curveTo(21f, 3.89f, 20.1f, 3f, 19f, 3f) moveTo(19f, 5f) verticalLineToRelative(14f) horizontalLineTo(5f) verticalLineTo(5f) horizontalLineTo(19f) close() } }.build() } val Icons.TwoTone.WebpBox: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "TwoTone.WebpBox", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = SolidColor(Color(0xFF000000)), fillAlpha = 0.3f, strokeAlpha = 0.3f ) { moveTo(5f, 5f) horizontalLineToRelative(14f) verticalLineToRelative(14f) horizontalLineToRelative(-14f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(6.757f, 13.855f) curveToRelative(-0.409f, 0f, -0.741f, -0.332f, -0.741f, -0.741f) verticalLineToRelative(-2.965f) horizontalLineToRelative(0.741f) verticalLineToRelative(2.965f) horizontalLineToRelative(0.741f) verticalLineToRelative(-2.594f) horizontalLineToRelative(0.741f) verticalLineToRelative(2.594f) horizontalLineToRelative(0.741f) verticalLineToRelative(-2.965f) horizontalLineToRelative(0.741f) verticalLineToRelative(2.965f) curveToRelative(0f, 0.409f, -0.332f, 0.741f, -0.741f, 0.741f) horizontalLineToRelative(-2.223f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(10.252f, 10.145f) verticalLineToRelative(3.706f) horizontalLineToRelative(2.224f) verticalLineToRelative(-0.741f) horizontalLineToRelative(-1.483f) verticalLineToRelative(-0.741f) horizontalLineToRelative(1.483f) verticalLineToRelative(-0.741f) horizontalLineToRelative(-1.483f) verticalLineToRelative(-0.741f) horizontalLineToRelative(1.483f) verticalLineToRelative(-0.741f) horizontalLineToRelative(-2.224f) verticalLineToRelative(-0.001f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15.23f, 11.446f) verticalLineToRelative(-0.556f) curveToRelative(0f, -0.409f, -0.332f, -0.741f, -0.741f, -0.741f) horizontalLineToRelative(-1.483f) verticalLineToRelative(3.706f) horizontalLineToRelative(1.483f) curveToRelative(0.409f, 0f, 0.741f, -0.332f, 0.741f, -0.741f) verticalLineToRelative(-0.556f) curveToRelative(0f, -0.297f, -0.259f, -0.556f, -0.556f, -0.556f) curveToRelative(0.297f, -0f, 0.556f, -0.26f, 0.556f, -0.556f) moveTo(14.489f, 13.114f) horizontalLineToRelative(-0.741f) verticalLineToRelative(-0.741f) horizontalLineToRelative(0.741f) verticalLineToRelative(0.741f) moveTo(14.489f, 11.631f) horizontalLineToRelative(-0.741f) verticalLineToRelative(-0.741f) horizontalLineToRelative(0.741f) verticalLineToRelative(0.741f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(15.761f, 10.148f) verticalLineToRelative(3.706f) horizontalLineToRelative(0.741f) verticalLineToRelative(-1.483f) horizontalLineToRelative(0.741f) curveToRelative(0.409f, 0f, 0.741f, -0.332f, 0.741f, -0.741f) verticalLineToRelative(-0.741f) curveToRelative(0f, -0.409f, -0.332f, -0.741f, -0.741f, -0.741f) horizontalLineToRelative(-1.482f) moveTo(16.502f, 10.89f) horizontalLineToRelative(0.741f) verticalLineToRelative(0.741f) horizontalLineToRelative(-0.741f) verticalLineToRelative(-0.741f) close() } path(fill = SolidColor(Color(0xFF000000))) { moveTo(19f, 3f) horizontalLineTo(5f) curveToRelative(-1.11f, 0f, -2f, 0.89f, -2f, 2f) verticalLineToRelative(14f) curveToRelative(0f, 1.105f, 0.895f, 2f, 2f, 2f) horizontalLineToRelative(14f) curveToRelative(1.105f, 0f, 2f, -0.895f, 2f, -2f) verticalLineTo(5f) curveToRelative(0f, -1.11f, -0.9f, -2f, -2f, -2f) moveTo(19f, 5f) verticalLineToRelative(14f) horizontalLineTo(5f) verticalLineTo(5f) horizontalLineToRelative(14f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/Windows.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap.Companion.Butt import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.Windows: ImageVector by lazy { Builder( name = "Windows", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f ).apply { path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(2.0f, 4.0f) verticalLineToRelative(7.4769f) horizontalLineToRelative(9.481f) verticalLineTo(2.0f) horizontalLineTo(4.0f) curveTo(2.8954f, 2.0f, 2.0f, 2.8954f, 2.0f, 4.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(2.0f, 20.0f) curveToRelative(0.0f, 1.1046f, 0.8954f, 2.0f, 2.0f, 2.0f) horizontalLineToRelative(7.481f) verticalLineToRelative(-9.481f) horizontalLineTo(2.0f) verticalLineTo(20.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(20.0f, 2.0f) horizontalLineToRelative(-7.481f) verticalLineToRelative(9.4769f) horizontalLineTo(22.0f) verticalLineTo(4.0f) curveTo(22.0f, 2.8954f, 21.1046f, 2.0f, 20.0f, 2.0f) close() } path( fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, pathFillType = NonZero ) { moveTo(12.519f, 22.0f) horizontalLineTo(20.0f) curveToRelative(1.1046f, 0.0f, 2.0f, -0.8954f, 2.0f, -2.0f) verticalLineToRelative(-7.481f) horizontalLineToRelative(-9.481f) verticalLineTo(22.0f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/icons/ZigzagLine.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.icons import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Icons.Rounded.ZigzagLine: ImageVector by lazy { ImageVector.Builder( name = "ZigzagLine", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFF000000))) { moveTo(19.011f, 5.493f) horizontalLineToRelative(-6f) curveToRelative(-0.828f, 0f, -1.499f, 0.671f, -1.499f, 1.499f) curveToRelative(0f, 0.002f, 0.001f, 0.004f, 0.001f, 0.006f) reflectiveCurveToRelative(-0.001f, 0.004f, -0.001f, 0.006f) verticalLineToRelative(4.495f) horizontalLineTo(6.989f) curveToRelative(-0.828f, 0f, -1.499f, 0.671f, -1.499f, 1.499f) curveToRelative(0f, 0.002f, 0.001f, 0.004f, 0.001f, 0.006f) reflectiveCurveToRelative(-0.001f, 0.004f, -0.001f, 0.006f) verticalLineToRelative(6f) curveToRelative(0f, 0.828f, 0.671f, 1.499f, 1.499f, 1.499f) reflectiveCurveToRelative(1.499f, -0.671f, 1.499f, -1.499f) verticalLineToRelative(-4.512f) horizontalLineToRelative(4.467f) curveToRelative(0.019f, 0.001f, 0.037f, 0.006f, 0.056f, 0.006f) curveToRelative(0.828f, 0f, 1.499f, -0.671f, 1.499f, -1.499f) verticalLineToRelative(-4.512f) horizontalLineToRelative(4.501f) curveToRelative(0.828f, 0f, 1.499f, -0.671f, 1.499f, -1.499f) reflectiveCurveTo(19.839f, 5.493f, 19.011f, 5.493f) close() } }.build() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ArrowShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val ArrowPathData = "M0.00127574 36.4191C0.00127574 42.8145 5.14768 48 11.4949 48C13.2236 48 14.863 47.6147 16.3341 46.9262C16.3341 46.9262 16.343 46.9223 16.3583 46.9146C16.496 46.8491 16.6337 46.7823 16.7676 46.7117C18.1252 46.0476 22.3143 44.0656 24.2634 43.9205C26.6065 43.7458 30.7293 45.0136 30.7293 45.0136L34.1891 46.6885C34.4147 46.808 34.6455 46.9185 34.88 47.0225L34.8915 47.0289H34.8953C35.8655 47.4579 36.9057 47.7559 37.9944 47.9011H37.997L38.0033 47.9024C38.2914 47.9409 38.5834 47.9679 38.8778 47.9833H38.8855C39.0907 47.9949 39.2973 48 39.5063 48C45.8536 48 51 42.8145 51 36.4191V36.3703C51 34.1995 50.4059 32.1687 49.3746 30.4321L35.905 6.40702C34.0195 2.6088 30.1225 1.30422e-06 25.6198 1.30422e-06C21.1172 1.30422e-06 17.2214 2.6088 15.3347 6.40702L1.88672 30.0133C1.63175 30.4051 1.39846 30.8123 1.19067 31.2349L1.18557 31.2439C0.427059 32.7904 -3.11934e-07 34.5296 -3.11934e-07 36.3703V36.4191H0.00127574Z" val ArrowShape = PathShape(ArrowPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BookmarkShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val BookmarkPathData = "M10 0C4.47715 0 0 4.47715 0 10V44.3522C0 46.1924 0.832489 48.0327 4 47.9996C5.04582 47.9996 11.1957 46.5617 16.3492 45.3564C18.5872 44.833 20.6375 44.3535 22 44.0549C22.5203 43.9314 23 44.0549 23 44.0549C26.0208 44.6465 29.2917 45.4016 32.2846 46.0925C36.4967 47.0648 40.158 47.91 41.796 47.9935L42.0617 47.9925C45.2368 47.8668 46 46.0449 46 43.8424V10C46 4.47715 41.5228 0 36 0H10Z" val BookmarkShape = PathShape(BookmarkPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/BurgerShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val BurgerPathData = "M0.369565 7.44186C0.369565 3.33212 3.67833 0 7.76087 0H43.6087C47.6912 0 51 3.33212 51 7.44186V8.55814C51 12.6679 47.6912 16 43.6087 16C47.6912 16 51 19.3321 51 23.4419V24.5581C51 28.6679 47.6912 32 43.6087 32H43.2391C47.3217 32 50.6304 35.3321 50.6304 39.4419V40.5581C50.6304 44.6679 47.3217 48 43.2391 48H7.3913C3.30876 48 0 44.6679 0 40.5581V39.4419C0 35.3321 3.30876 32 7.3913 32H7.76087C3.67833 32 0.369565 28.6679 0.369565 24.5581V23.4419C0.369565 19.3321 3.67833 16 7.76087 16C3.67833 16 0.369565 12.6679 0.369565 8.55814V7.44186Z" val BurgerShape = PathShape(BurgerPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/CloverShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val CloverShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 200f val baseHeight = 200f val path = Path().apply { moveTo(12f, 100f) cubicTo(12f, 76f, 0f, 77.6142f, 0f, 50f) cubicTo(0f, 22.3858f, 22.3858f, 0f, 50f, 0f) cubicTo(77.6142f, 0f, 76f, 12f, 100f, 12f) cubicTo(124f, 12f, 122.3858f, 0f, 150f, 0f) cubicTo(177.6142f, 0f, 200f, 22.3858f, 200f, 50f) cubicTo(200f, 77.6142f, 188f, 76f, 188f, 100f) cubicTo(188f, 124f, 200f, 122.3858f, 200f, 150f) cubicTo(200f, 177.6142f, 177.6142f, 200f, 150f, 200f) cubicTo(122.3858f, 200f, 124f, 188f, 100f, 188f) cubicTo(76f, 188f, 77.6142f, 200f, 50f, 200f) cubicTo(22.3858f, 200f, 0f, 177.6142f, 0f, 150f) cubicTo(0f, 122.3858f, 12f, 124f, 12f, 100f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform(Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) }) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/DropletShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val DropletPathData = "M24 0C10.7452 0 0 10.7452 0 24C0 24.2317 0.00328295 24.4626 0.00980488 24.6927C0.00329738 24.8032 0 24.9144 0 25.0264V42.3896C0 45.4881 2.51186 48 5.61039 48H23.2847C23.3623 48 23.4396 47.9984 23.5167 47.9952C23.6774 47.9984 23.8385 48 24 48C37.2548 48 48 37.2548 48 24C48 10.7452 37.2548 0 24 0Z" val DropletShape = PathShape(DropletPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/EggShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val EggPathData = "M37 29.0542C36.8496 35.2815 34.6566 40.4326 29.781 44.0763C24.0677 48.3452 17.7493 49.1448 11.1127 46.4063C6.52734 44.5131 3.32181 41.1353 1.35268 36.5987C0.595451 34.8535 0.132022 32.9832 0.0428338 31.0619C-0.236972 25.0372 0.852523 19.2731 3.35679 13.7856C5.28745 9.55367 7.91064 5.81135 11.3033 2.65723C13.3004 0.799282 15.7172 0.0455345 18.4891 0.00150717C22.8523 -0.0689365 25.743 2.33848 28.263 5.43624C30.0888 7.67987 31.7082 10.0732 32.997 12.6972C35.0484 16.871 36.346 21.2403 36.7639 25.8755C36.8636 26.9849 36.93 28.0962 37 29.056V29.0542Z" val EggShape = PathShape(EggPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ExplosionShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val ExplosionPathData = "M24.3629 0.130582C24.0071 -0.0526879 23.5886 -0.0422153 23.2434 0.156763C21.2763 1.29304 14.6586 5.04745 13.1833 5.04745C11.9748 5.04745 6.27256 2.52356 2.91398 0.984095C1.97755 0.55472 0.988809 1.45012 1.31839 2.42407C2.57394 6.1523 4.68221 12.6505 4.30554 13.2684C3.84518 14.0224 0.873717 21.6936 0.0890009 23.7357C-0.0313222 24.0499 -0.0103964 24.3955 0.146547 24.6992C1.13529 26.579 4.72929 33.5695 4.44156 35.1299C4.20614 36.4023 2.04556 42.0941 0.790014 45.3406C0.434276 46.2622 1.3027 47.1785 2.23912 46.8801C5.82789 45.7386 12.3463 43.686 12.9217 43.686C13.6227 43.686 21.925 47.0791 23.9601 47.9116C24.2478 48.0321 24.5669 48.0268 24.8546 47.9116C26.9211 47.0476 35.4745 43.487 35.8302 43.487C36.1179 43.487 41.7417 45.6286 44.9381 46.8591C45.8379 47.2047 46.743 46.3774 46.4866 45.4506C45.5554 42.0994 43.9285 36.1143 43.9756 35.3969C44.0331 34.5225 47.1458 26.223 47.9358 24.1284C48.0508 23.8247 48.0352 23.4844 47.8887 23.1912C46.9784 21.3689 43.6512 14.5984 43.7088 12.8704C43.7558 11.5038 45.6653 5.93238 46.8738 2.53403C47.2243 1.54961 46.2094 0.633265 45.2625 1.08358C41.7731 2.75396 35.9348 5.51871 35.4221 5.51871C34.7891 5.51871 26.6072 1.29827 24.3629 0.130582Z" val ExplosionShape = PathShape(ExplosionPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/HeartShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val HeartShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val path = Path().apply { val height = size.height val width = size.width moveTo(0.5f * width, 0.25f * height) cubicTo( 0.5f * width, 0.225f * height, 0.45833334f * width, 0.125f * height, 0.29166666f * width, 0.125f * height ) cubicTo( 0.041666668f * width, 0.125f * height, 0.041666668f * width, 0.4f * height, 0.041666668f * width, 0.4f * height ) cubicTo( 0.041666668f * width, 0.5833333f * height, 0.20833333f * width, 0.76666665f * height, 0.5f * width, 0.9166667f * height ) cubicTo( 0.7916667f * width, 0.76666665f * height, 0.9583333f * width, 0.5833333f * height, 0.9583333f * width, 0.4f * height ) cubicTo( 0.9583333f * width, 0.4f * height, 0.9583333f * width, 0.125f * height, 0.7083333f * width, 0.125f * height ) cubicTo( 0.5833333f * width, 0.125f * height, 0.5f * width, 0.225f * height, 0.5f * width, 0.25f * height ) close() } return Outline.Generic(path) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/KotlinShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val KotlinShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 1000f val baseHeight = 1000f val path = Path().apply { moveTo(0f, 0f) lineTo(1000f, 0f) lineTo(473.5f, 500f) lineTo(1000f, 1000f) lineTo(0f, 1000f) lineTo(0f, 0f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform(Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) }) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MapShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val MapPathData = "M25.0702 4.38833C28.2859 2.26692 31.4412 0.185367 35.9294 0.00690002C41.3166 -0.204493 48 4.51158 48 4.51158L47.914 43.5389C41.8695 39.5777 37.3361 38.7019 34.1848 38.9888C29.4365 39.4182 26.0316 41.6001 22.7395 43.7097C19.4728 45.803 16.3173 47.8251 12.0706 47.9931C6.68339 48.2045 0 43.4884 0 43.4884V4.80351C5.84797 8.59348 10.4858 9.24276 13.8152 9.01123C18.563 8.68117 21.847 6.51473 25.0702 4.38833Z" val MapShape = PathShape(MapPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MaterialStarShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val MaterialStarShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 865.0807f val baseHeight = 865.0807f val path = Path().apply { moveTo(403.3913f, 8.7356f) cubicTo(421.0787f, -2.9119f, 444.002f, -2.9119f, 461.6894f, 8.7356f) lineTo(518.743f, 46.3066f) cubicTo(528.2839f, 52.5895f, 539.5995f, 55.6215f, 551.0036f, 54.9508f) lineTo(619.1989f, 50.9402f) cubicTo(640.3404f, 49.6968f, 660.1926f, 61.1585f, 669.6865f, 80.0892f) lineTo(700.3109f, 141.1534f) cubicTo(705.4321f, 151.365f, 713.7157f, 159.6486f, 723.9273f, 164.7699f) lineTo(784.9915f, 195.3942f) cubicTo(803.9222f, 204.8881f, 815.3839f, 224.7403f, 814.1406f, 245.8818f) lineTo(810.1299f, 314.0771f) cubicTo(809.4593f, 325.4812f, 812.4913f, 336.7969f, 818.7742f, 346.3378f) lineTo(856.3451f, 403.3913f) cubicTo(867.9926f, 421.0787f, 867.9927f, 444.002f, 856.3452f, 461.6894f) lineTo(818.7742f, 518.743f) cubicTo(812.4913f, 528.2839f, 809.4593f, 539.5995f, 810.1299f, 551.0036f) lineTo(814.1406f, 619.1989f) cubicTo(815.3839f, 640.3404f, 803.9223f, 660.1926f, 784.9916f, 669.6865f) lineTo(723.9274f, 700.3109f) cubicTo(713.7158f, 705.4321f, 705.4321f, 713.7157f, 700.3109f, 723.9273f) lineTo(669.6866f, 784.9915f) cubicTo(660.1926f, 803.9222f, 640.3404f, 815.3839f, 619.1989f, 814.1406f) lineTo(551.0036f, 810.1299f) cubicTo(539.5995f, 809.4593f, 528.2839f, 812.4913f, 518.743f, 818.7742f) lineTo(461.6894f, 856.3451f) cubicTo(444.0021f, 867.9926f, 421.0787f, 867.9927f, 403.3914f, 856.3452f) lineTo(346.3378f, 818.7742f) cubicTo(336.7969f, 812.4913f, 325.4812f, 809.4593f, 314.0771f, 810.1299f) lineTo(245.8818f, 814.1406f) cubicTo(224.7404f, 815.3839f, 204.8882f, 803.9223f, 195.3942f, 784.9916f) lineTo(164.7699f, 723.9274f) cubicTo(159.6486f, 713.7158f, 151.365f, 705.4321f, 141.1534f, 700.3109f) lineTo(80.0892f, 669.6866f) cubicTo(61.1585f, 660.1926f, 49.6968f, 640.3404f, 50.9402f, 619.199f) lineTo(54.9508f, 551.0036f) cubicTo(55.6215f, 539.5995f, 52.5895f, 528.2839f, 46.3066f, 518.743f) lineTo(8.7356f, 461.6894f) cubicTo(-2.9119f, 444.0021f, -2.9119f, 421.0787f, 8.7356f, 403.3914f) lineTo(46.3066f, 346.3378f) cubicTo(52.5895f, 336.7969f, 55.6215f, 325.4813f, 54.9508f, 314.0771f) lineTo(50.9402f, 245.8818f) cubicTo(49.6968f, 224.7404f, 61.1585f, 204.8882f, 80.0892f, 195.3942f) lineTo(141.1534f, 164.7699f) cubicTo(151.365f, 159.6486f, 159.6486f, 151.365f, 164.7699f, 141.1534f) lineTo(195.3942f, 80.0892f) cubicTo(204.8882f, 61.1585f, 224.7403f, 49.6968f, 245.8818f, 50.9402f) lineTo(314.0771f, 54.9508f) cubicTo(325.4813f, 55.6215f, 336.7969f, 52.5895f, 346.3378f, 46.3066f) lineTo(403.3913f, 8.7356f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform(Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) }) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/MorphShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import androidx.compose.material3.toPath import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import androidx.graphics.shapes.Morph class MorphShape( private val morph: Morph, private val percentage: () -> Float, private val rotation: Float = 0f, ) : Shape { private val matrix = Matrix() override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density, ): Outline { matrix.reset() matrix.scale(size.width, size.height) matrix.rotateZ(rotation) val path = morph.toPath(progress = percentage()) path.transform(matrix) return Outline.Generic(path) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OctagonShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val OctagonShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 1000f val baseHeight = 1000f val path = Path().apply { moveTo(500f, 0f) lineTo(853.5534f, 146.4466f) lineTo(1000f, 500f) lineTo(853.5534f, 853.5534f) lineTo(500f, 1000f) lineTo(146.4466f, 853.5534f) lineTo(0f, 500f) lineTo(146.4466f, 146.4466f) lineTo(500f, 0f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform(Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) }) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/OvalShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val OvalShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 1000f val baseHeight = 1000f val path = Path().apply { moveTo(1000f, 500f) cubicTo(1000f, 776.1424f, 776.1424f, 1000f, 500f, 1000f) cubicTo(223.8576f, 1000f, 0f, 776.1424f, 0f, 500f) cubicTo(0f, 223.8576f, 223.8576f, 0f, 500f, 0f) cubicTo(776.1424f, 0f, 1000f, 223.8576f, 1000f, 500f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform(Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) }) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PathShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import android.graphics.RectF import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.graphics.toAndroidRectF import androidx.compose.ui.graphics.vector.PathParser import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection internal fun PathShape(pathData: String): Shape = PathShapeImpl(pathData) private class PathShapeImpl(private val pathData: String) : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { return Outline.Generic(path = drawPath(size)) } private fun drawPath(size: Size): Path { return Path().apply { reset() addPath(pathData.toPath(size)) close() } } } private fun String.toPath( size: Size ): Path { if (isNotEmpty()) { val scaleMatrix = Matrix() val rectF = RectF() val path = PathParser().parsePathString(this).toPath() val rectPath = path.getBounds().toAndroidRectF() val scaleXFactor = size.width / rectPath.width() val scaleYFactor = size.height / rectPath.height() val androidPath = path.asAndroidPath() scaleMatrix.setScale(scaleXFactor, scaleYFactor, rectF.centerX(), rectF.centerY()) @Suppress("DEPRECATION") androidPath.computeBounds(rectF, true) androidPath.transform(scaleMatrix) return androidPath.asComposePath() } return Path() } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PentagonShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val PentagonShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 1224.1858f val baseHeight = 1137.3882f val path = Path().apply { moveTo(521.133f, 28.588f) cubicTo(575.7829f, -9.5293f, 648.4028f, -9.5293f, 703.0528f, 28.588f) lineTo(1156.1332f, 344.6029f) cubicTo(1214.148f, 385.0671f, 1238.4572f, 458.9902f, 1215.7808f, 525.9892f) lineTo(1045.41f, 1029.3625f) cubicTo(1023.5555f, 1093.9333f, 962.9716f, 1137.3882f, 894.8026f, 1137.3882f) lineTo(329.3832f, 1137.3882f) cubicTo(261.2142f, 1137.3882f, 200.6302f, 1093.9333f, 178.7757f, 1029.3625f) lineTo(8.405f, 525.9893f) cubicTo(-14.2714f, 458.9903f, 10.0377f, 385.0671f, 68.0526f, 344.6029f) lineTo(521.133f, 28.588f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform(Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) }) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/PillShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val PillPathData = "M48 20.4342C48 9.14772 38.8281 0 27.5193 0C21.8632 0 16.743 2.28863 13.036 5.98382L5.99744 13.1154C2.29384 16.814 0 21.9225 0 27.5658C0 38.8523 9.16854 48 20.4807 48C26.1368 48 31.257 45.7114 34.964 42.0162L42.0026 34.8846C45.7096 31.186 48 26.0775 48 20.4342Z" val PillShape = PathShape(PillPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShieldShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val ShieldPathData = "M22.8468 0.551384C24.8376 2.58461 34.1326 7.39938 41.7392 9.94215C42.1061 10.0645 42.4253 10.2994 42.6515 10.6135C42.8778 10.9276 42.9997 11.305 43 11.6923C43 32.5957 39.9697 42.7864 22.0137 47.9286C21.6828 48.0238 21.3319 48.0238 21.0011 47.9286C3.14341 42.8184 1.89533e-09 32.2757 1.89533e-09 12.0123C-1.82005e-05 11.6104 0.131069 11.2195 0.373309 10.8992C0.615549 10.5788 0.955673 10.3465 1.34191 10.2375C8.26315 8.39719 14.7081 5.08702 20.2392 0.531692C20.5968 0.202432 21.0611 0.0136179 21.5467 0C21.789 0.00155783 22.0286 0.051074 22.2517 0.145701C22.4748 0.240328 22.6771 0.378198 22.8468 0.551384Z" val ShieldShape = PathShape(ShieldPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/ShurikenShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes private const val ShurikenPathData = "M46.1183 46.1349C46.1183 46.1349 44.5771 44.149 46.1183 41.3152 55.7085 25.337 50.6851 6.5989 50.6851 6.5989L46.1183 9.8651C46.1183 9.8651 44.0233 11.2763 41.4317 9.8651 25.4025.2998 6.599 5.3129 6.599 5.3129L9.8756 9.8651C9.8756 9.8651 11.5539 11.5722 9.8756 14.6905 9.8185 14.7929 9.7729 14.8783 9.7215 14.9579.3483 30.8736 5.3146 49.4011 5.3146 49.4011L9.8813 46.1349C9.8813 46.1349 11.8621 44.6668 14.5622 46.1349 30.5971 55.7002 49.395 50.6871 49.395 50.6871L46.1183 46.1349Z" val ShurikenShape = PathShape(ShurikenPathData) ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SimpleHeartShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val SimpleHeartShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 24f val baseHeight = 24f val path = Path().apply { moveTo(12.0f, 21.35f) relativeLineTo(-1.45f, -1.32f) cubicTo(5.4f, 15.36f, 2.0f, 12.28f, 2.0f, 8.5f) cubicTo(2.0f, 5.42f, 4.42f, 3.0f, 7.5f, 3.0f) relativeCubicTo(1.74f, 0.0f, 3.41f, 0.81f, 4.5f, 2.09f) cubicTo(13.09f, 3.81f, 14.76f, 3.0f, 16.5f, 3.0f) cubicTo(19.58f, 3.0f, 22.0f, 5.42f, 22.0f, 8.5f) relativeCubicTo(0.0f, 3.78f, -3.4f, 6.86f, -8.55f, 11.54f) lineTo(12.0f, 21.35f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform( Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) } ) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SmallMaterialStarShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val SmallMaterialStarShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 2695.79f val baseHeight = 2680.65f val path = Path().apply { moveTo(2537.8f, 975.157f) cubicTo(2623.02f, 1101.61f, 2665.64f, 1164.84f, 2683.13f, 1233.15f) cubicTo(2699.69f, 1297.82f, 2700.13f, 1365.57f, 2684.42f, 1430.45f) cubicTo(2667.81f, 1498.98f, 2626.03f, 1562.76f, 2542.46f, 1690.32f) cubicTo(2517.24f, 1728.81f, 2504.64f, 1748.05f, 2494.33f, 1768.43f) cubicTo(2484.53f, 1787.8f, 2476.3f, 1807.93f, 2469.7f, 1828.61f) cubicTo(2462.77f, 1850.37f, 2458.26f, 1872.96f, 2449.25f, 1918.15f) cubicTo(2419.3f, 2068.37f, 2404.33f, 2143.48f, 2367.9f, 2204.19f) cubicTo(2334.18f, 2260.38f, 2287.43f, 2307.62f, 2231.59f, 2341.92f) cubicTo(2171.26f, 2378.98f, 2095.52f, 2394.9f, 1944.04f, 2426.74f) cubicTo(1897.48f, 2436.53f, 1874.19f, 2441.42f, 1852.04f, 2448.8f) cubicTo(1832.55f, 2455.29f, 1813.57f, 2463.25f, 1795.28f, 2472.6f) cubicTo(1774.49f, 2483.22f, 1754.88f, 2496.27f, 1715.65f, 2522.35f) cubicTo(1584.84f, 2609.34f, 1519.44f, 2652.83f, 1448.72f, 2669.7f) cubicTo(1388.23f, 2684.12f, 1325.25f, 2684.53f, 1264.59f, 2670.89f) cubicTo(1193.65f, 2654.95f, 1127.69f, 2612.31f, 995.759f, 2527.04f) cubicTo(956.196f, 2501.47f, 936.414f, 2488.68f, 915.487f, 2478.33f) cubicTo(897.073f, 2469.21f, 877.998f, 2461.51f, 858.423f, 2455.27f) cubicTo(836.176f, 2448.18f, 812.831f, 2443.59f, 766.142f, 2434.41f) cubicTo(614.258f, 2404.54f, 538.317f, 2389.61f, 477.51f, 2353.34f) cubicTo(421.231f, 2319.77f, 373.865f, 2273.14f, 339.421f, 2217.4f) cubicTo(302.205f, 2157.16f, 286.253f, 2082.26f, 254.349f, 1932.44f) cubicTo(244.752f, 1887.38f, 239.953f, 1864.84f, 232.735f, 1843.18f) cubicTo(225.871f, 1822.58f, 217.375f, 1802.56f, 207.326f, 1783.32f) cubicTo(196.758f, 1763.08f, 183.9f, 1744f, 158.185f, 1705.84f) cubicTo(72.9637f, 1579.38f, 30.3531f, 1516.16f, 12.8577f, 1447.85f) cubicTo(-3.7056f, 1383.18f, -4.1467f, 1315.43f, 11.5731f, 1250.55f) cubicTo(28.1775f, 1182.01f, 69.9612f, 1118.24f, 153.529f, 990.681f) cubicTo(178.745f, 952.192f, 191.353f, 932.947f, 201.657f, 912.571f) cubicTo(211.454f, 893.196f, 219.689f, 873.069f, 226.284f, 852.384f) cubicTo(233.219f, 830.629f, 237.724f, 808.034f, 246.734f, 762.845f) cubicTo(276.684f, 612.629f, 291.659f, 537.521f, 328.088f, 476.81f) cubicTo(361.804f, 420.62f, 408.558f, 373.377f, 464.395f, 339.08f) cubicTo(524.724f, 302.023f, 600.465f, 286.102f, 751.947f, 254.26f) cubicTo(798.513f, 244.472f, 821.796f, 239.578f, 843.949f, 232.199f) cubicTo(863.44f, 225.707f, 882.414f, 217.752f, 900.708f, 208.401f) cubicTo(921.498f, 197.775f, 941.112f, 184.732f, 980.339f, 158.647f) cubicTo(1111.14f, 71.6626f, 1176.55f, 28.1706f, 1247.27f, 11.3026f) cubicTo(1307.75f, -3.122f, 1370.73f, -3.5321f, 1431.4f, 10.1038f) cubicTo(1502.34f, 26.0493f, 1568.3f, 68.686f, 1700.23f, 153.959f) cubicTo(1739.79f, 179.532f, 1759.57f, 192.318f, 1780.5f, 202.673f) cubicTo(1798.91f, 211.784f, 1817.99f, 219.492f, 1837.57f, 225.73f) cubicTo(1859.81f, 232.819f, 1883.16f, 237.41f, 1929.85f, 246.591f) cubicTo(2081.73f, 276.457f, 2157.67f, 291.391f, 2218.48f, 327.659f) cubicTo(2274.76f, 361.227f, 2322.12f, 407.856f, 2356.57f, 463.603f) cubicTo(2393.78f, 523.834f, 2409.74f, 598.741f, 2441.64f, 748.554f) cubicTo(2451.24f, 793.622f, 2456.04f, 816.156f, 2463.25f, 837.819f) cubicTo(2470.12f, 858.417f, 2478.61f, 878.434f, 2488.66f, 897.68f) cubicTo(2499.23f, 917.921f, 2512.09f, 937f, 2537.8f, 975.157f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform( Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) } ) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/java/com/t8rin/imagetoolbox/core/resources/shapes/SquircleShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.resources.shapes import android.graphics.Matrix import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection val SquircleShape: Shape = object : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { val baseWidth = 1000f val baseHeight = 1000f val path = Path().apply { moveTo(0f, 500f) cubicTo(0f, 88.25f, 88.25f, 0f, 500f, 0f) cubicTo(911.75f, 0f, 1000f, 88.25f, 1000f, 500f) cubicTo(1000f, 911.75f, 911.75f, 1000f, 500f, 1000f) cubicTo(88.25f, 1000f, 0f, 911.75f, 0f, 500f) close() } return Outline.Generic( path .asAndroidPath() .apply { transform(Matrix().apply { setScale(size.width / baseWidth, size.height / baseHeight) }) } .asComposePath() ) } } ================================================ FILE: core/resources/src/main/res/drawable/app_registration_24px.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/ic_24_barcode_scanner.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/ic_launcher_foreground.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/ic_launcher_monochrome_24.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/ic_logo_animated.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/ic_notification_icon.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/image_to_text_outlined.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/mobile_screenshot.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/multiple_image_edit.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/outline_colorize_24.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/outline_drag_handle_24.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/palette_swatch_outlined.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/rounded_document_scanner_24.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/rounded_qr_code_scanner_24.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable/shape_find_in_file.xml ================================================ ================================================ FILE: core/resources/src/main/res/drawable-v31/ic_logo_animated.xml ================================================ ================================================ FILE: core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: core/resources/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: core/resources/src/main/res/raw/keep.xml ================================================ ================================================ FILE: core/resources/src/main/res/values/arrays.xml ================================================ afr amh ara asm aze bel ben bod bos bul cat ceb ces chi_sim chi_tra chr cos cym dan deu div dzo ell eng enm epo eqo est eus fao fas fil fin fra frk frm fry gla gle glg grc guj hat heb hin hrv hun hye iku ind isl ita jav jpn kan kat kaz khm kir kmr kor kur lao lat lav lit ltz mal mar mkd mlt mon mri msa mya nep nld nor oci ori osd pan pol por pus que ron rus san sin slk slv snd spa sqi srp sun swa swe syr tam tat tel tgk tgl tha tir ton tur uig ukr urd uzb vie yid yor ================================================ FILE: core/resources/src/main/res/values/bools.xml ================================================ false ================================================ FILE: core/resources/src/main/res/values/colors.xml ================================================ #ABE87C #394F29 ================================================ FILE: core/resources/src/main/res/values/ic_launcher_background.xml ================================================ @color/primary ================================================ FILE: core/resources/src/main/res/values/strings.xml ================================================ Image Toolbox Image Toolbox com.t8rin.imagetoolbox.fileprovider Something went wrong: %1$s Size %1$s Loading… Image is too large to preview, but saving will be attempted anyway Pick image to start Width %1$s Height %1$s Quality Extension Resize type Explicit Flexible Pick Image Are you sure you want to close the app? App closing Stay Close Reset image Image changes will roll back to initial values Values properly reset Reset Something went wrong Restart app Copied to clipboard Exception Edit EXIF OK No EXIF data found Add tag Save Clear Clear EXIF Cancel All image EXIF data will be erased. This action cannot be undone! Presets Crop Saving All unsaved changes will be lost, if you exit now Source code Get the latest updates, discuss issues and more Single Edit Modify, resize and edit one image Color Picker Pick color from image, copy or share Image Color Color copied Crop image to any bounds Version Keep EXIF Images: %d Change preview Remove Generate color palette swatch from given image Generate Palette Palette Update New version %1$s Unsupported type: %1$s Cannot generate palette for given image Original Output folder Documents/ImageToolbox Default Custom Unspecified Device storage Resize by Weight Max size in KB Resize an image following given size in KB Compare Compare two given images Pick two images to start Pick images Settings Night Mode Dark Light System Dynamic colors Customization Allow image monet If enabled, when you choose an image to edit, app colors will be adopted to this image Language Amoled mode If enabled surfaces color will be set to absolute dark in night mode Color scheme Red Green Blue Paste a valid aRGB color code Nothing to paste The app\'s color scheme cannot be changed while dynamic colors are turned on App theme will be based on selected color About app Malik Mukhametzyanov T8RIN No updates found Issue tracker Send bug reports and feature requests here Help translate Correct translation mistakes or localize project to another languages Nothing found by your query Search here If enabled, then app colors will be adopted to wallpaper colors Failed to save %d image(s) Telegram Email GitHub t8rin@bk.ru Primary Tertiary Secondary Border thickness Surface Values Add Permission Grant App needs access to your storage to save images to work, it is necessary. Please grant permission in the next dialog box. App needs this permission to work, please grant it manually External storage Monet colors This application is completely free, but if you want to support the project development, you can click here FAB alignment Check for updates If enabled, update dialog will be shown to you on app startup Image zoom Share Prefix Filename Emoji Select which emoji to display on the main screen Add file size If enabled, adds width and height of saved image to the name of output file Delete EXIF Delete EXIF metadata from any set of images Image Preview Preview any type of images: GIF, SVG, and so on Image source Photo picker Gallery File explorer Android modern photo picker which appears at the bottom of the screen, may work only on android 12+. Has issues receiving EXIF metadata Simple gallery image picker. It will work only if you have an app that provides media picking Use GetContent intent to pick image. Works everywhere, but is known to have issues receiving picked images on some devices. That\'s not my fault. Options arrangement Edit Order Determines order of the tools on the main screen Emojis count sequenceNum originalFilename Add original filename If enabled adds original filename in the name of the output image Replace sequence number If enabled replaces standard timestamp to the image sequence number if you use batch processing Adding original filename doesn\'t work if photo picker image source selected Web Image Loading Load any image from the internet to preview, zoom, edit and save it if you want. No image Image link Fill Fit Content scale Resizes images to the given height and width. The aspect ratio of the images may change. Resizes images with a long side to the given height or width. All size calculations will be done after saving. The aspect ratio of the images will be preserved. Brightness Contrast Hue Saturation Add filter Filter Apply filter chains to images Filters Light Color filter Alpha Exposure White balance Temperature Tint Monochrome Gamma Highlights and shadows Highlights Shadows Haze Effect Distance Slope Sharpen Sepia Negative Solarize Vibrance Black and White Crosshatch Spacing Line width Sobel edge Blur Halftone CGA colorspace Gaussian blur Box blur Bilaterial blur Emboss Laplacian Vignette Start End Kuwahara smoothing Stack blur Radius Scale Distortion Angle Swirl Bulge Dilation Sphere refraction Refractive index Glass sphere refraction Color matrix floatArrayOf(f, f, f) Opacity Resize by Limits Resize images to the given height and width while keeping the aspect ratio Sketch Threshold Quantization levels Smooth toon Toon Posterize Non maximum suppression Weak pixel inclusion Lookup Convolution 3x3 RGB filter False color First color Second color Reorder Fast blur Blur size Blur center x Blur center y Zoom blur Color balance Luminance threshold You disabled Files app, activate it to use this feature Draw Draw on image like in a sketchbook, or draw on the background itself Paint color Paint alpha Draw on image Pick an image and draw something on it Draw on background Pick background color and draw on top of it Background color Cipher Encrypt and Decrypt any file (not only the image) based on various available crypto algorithms Pick file Encrypt Decrypt Pick file to start Open project Continue editing a previously saved Image Toolbox project Unable to open Image Toolbox project Image Toolbox project is missing project data Image Toolbox project is corrupted Unsupported Image Toolbox project version: %1$d Decryption Encryption Key File processed Store this file on your device or use share action to put it wherever you want Features Implementation Compatibility Password-based encryption of files. Proceeded files can be stored in the selected directory or shared. Decrypted files can also be opened directly. AES-256, GCM mode, no padding, 12 bytes random IVs by default. You can select the needed algorithm. Keys are used as 256-bit SHA-3 hashes File size The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. Please note that compatibility to other file encryption software or services is not guaranteed. A slightly different key treatment or cipher configuration may cause incompatibility. Invalid password or chosen file is not encrypted Trying to save the image with the given width and height may cause an out of memory error. Do this at your own risk. Cache Cache size Found %1$s Auto cache clearing If enabled app cache will be cleared on app startup Create Tools Group options by type Groups options on the main screen by their type instead of a custom list arrangement Cannot change arrangement while option grouping is enabled ResizedImage Edit screenshot Secondary customization Screenshot Fallback option Skip Copy Saving in %1$s mode can be unstable, because it is a lossless format If you have selected preset 125, image will be saved as 125% size of the original image. If you choose preset 50, then image will be saved with 50% size Preset here determines % of output file, i.e. if you select preset 50 on 5 MB image then you will get a 2,5 MB image after saving Randomize filename If enabled output filename will be fully random Saved to %1$s folder with name %2$s Saved to %1$s folder Telegram chat Discuss the app and get feedback from other users. You can also get beta updates and insights there. Crop mask Aspect ratio Use this mask type to create mask from given image, notice that it SHOULD have alpha channel Backup and restore Backup Restore Backup your app settings to a file Restore app settings from previously generated file Corrupted file or not a backup Settings restored successfully Contact me This will roll back your settings to the default values. Notice that this can\'t be undone without a backup file mentioned above. Delete You are about to delete selected color scheme. This operation cannot be undone Delete scheme Font Text Font scale Default Using large font scales may cause UI glitches and problems, which won\'t be fixed. Use cautiously. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz 0123456789 !? Emotions Food and Drink Nature and Animals Objects Symbols Enable emoji Travels and Places Activities Background Remover Remove background from image by drawing or use Auto option Trim image Original image metadata will be kept Trasparent spaces around image will be trimmed Auto erase background Restore image Erase mode Erase background Restore background Blur radius Pipette Draw mode Create Issue Oops… Something went wrong. You can write to me using options below and I\'ll try to find solution Resize and Convert Change size of given images or convert them to other formats. EXIF metadata can also be edited here if picking a single image. Max color count Firebase Crashlytics This allows the app to collect crash reports automatically Analytics Allow collecting anonymous app usage statistics Currently, %1$s format only allows reading EXIF metadata on android. Output image won\'t have metadata at all, when saved. Effort A value of %1$s means a fast compression, resulting in a relatively large file size. %2$s means a slower compression, resulting in a smaller file. Wait Saving almost complete. Cancelling now will require saving again. Updates Allow betas Update checking will include beta app versions if enabled Draw Arrows If enabled drawing path will be represented as pointing arrow Brush softness Pictures will be center cropped to entered size. Canvas will be expanded with given background color if image is smaller than entered dimensions. Donation Bitcoin USDT Image Stitching Combine the given images to get one big one Pick at least 2 images Output image scale Image Orientation Horizontal Vertical Scale small images to large Small images will be scaled to the largest one in the sequence if enabled Images order Regular Blur edges Draws blurred edges under original image to fill spaces around it instead of single color if enabled Pixelation Enhanced Pixelation Stroke Pixelation Enhanced Diamond Pixelation Diamond Pixelation Circle Pixelation Enhanced Circle Pixelation Replace Color Tolerance Color to Replace Target Color Color to Remove Remove Color Recode Pixel Size Lock draw orientation If enabled in drawing mode, screen won\'t rotate Check for updates Palette style Tonal Spot Neutral Vibrant Expressive Rainbow Fruit Salad Fidelity Content Default palette style, it allows to customize all four colors, others allow you to set only the key color A style that\'s slightly more chromatic than monochrome A loud theme, colorfulness is maximum for Primary palette, increased for others A playful theme - the source color\'s hue does not appear in the theme A monochrome theme, colors are purely black / white / gray A scheme that places the source color in Scheme.primaryContainer A scheme that is very similar to the content scheme This update checker will connect to GitHub in reason of checking if there is a new update available Attention Fading Edges Disabled Both Invert Colors Replaces theme colors to negative ones if enabled Search Enables ability to search through all available tools on the main screen PDF Tools Operate with PDF files: Preview, Convert to batch of images or create one from given pictures Preview PDF PDF to Images Images to PDF Simple PDF preview Convert PDF to Images in given output format Pack given Images into output PDF file Mask Filter Apply filter chains on given masked areas, each mask area can determine its own set of filters Masks Add Mask Mask %d Mask Color TON Space TON Boosty Mask preview Drawn filter mask will be rendered to show you the approximate result Inverse Fill Type If enabled all of the non masked areas will be filtered instead of default behavior You are about to delete selected filter mask. This operation cannot be undone Delete mask Full Filter Apply any filter chains to given images or single image Start Center End Simple Variants Highlighter Neon Pen Privacy Blur Draw semi-transparent sharpened highlighter paths Add some glowing effect to your drawings Default one, simplest - just the color Blurs image under the drawn path to secure anything you want to hide Similar to privacy blur, but pixelates instead of bluring Containers Draw a shadow behind containers Sliders Switches FABs Buttons Draw a shadow behind sliders Draw a shadow behind switches Draw a shadow behind floating action buttons Draw a shadow behind buttons App bars Draw a shadow behind app bars Value in range %1$s - %2$s Auto Rotate Allows limit box to be adopted for image orientation Draw Path Mode Double Line Arrow Free Drawing Double Arrow Line Arrow Arrow Line Draws path as input value Draws path from start point to end point as a line Draws pointing arrow from start point to end point as a line Draws a pointing arrow from a given path Draws double pointing arrow from start point to end point as a line Draws double pointing arrow from a given path Outlined Oval Outlined Rect Oval Rect Draws rect from start point to end point Draws oval from start point to end point Draws outlined oval from start point to end point Draws outlined rect from start point to end point Lasso Draws closed filled path by given path Free Horizontal Grid Vertical Grid Stitch Mode Rows Count Columns Count No \"%1$s\" directory found, we switched it to default one, please save the file again Clipboard Auto pin Automatically adds saved image to clipboard if enabled Vibration Vibration Strength In order to overwrite files you need to use \"Explorer\" image source, try repick images, we\'ve changed image source to the needed one Overwrite Files Original file will be replaced with new one instead of saving in selected folder, this option need to image source be \"Explorer\" or GetContent, when toggling this, it will be set automatically Empty Suffix Scale mode Bilinear Catmull Bicubic Hann Hermite Lanczos Mitchell Nearest Spline Basic Default Value Linear (or bilinear, in two dimensions) interpolation is typically good for changing the size of an image, but causes some undesirable softening of details and can still be somewhat jagged Better scaling methods include Lanczos resampling and Mitchell-Netravali filters One of the simpler ways of increasing the size, replacing every pixel with a number of pixels of the same color Simplest android scaling mode that used in almost all apps Method for smoothly interpolating and resampling a set of control points, commonly used in computer graphics to create smooth curves Windowing function often applied in signal processing to minimize spectral leakage and improve the accuracy of frequency analysis by tapering the edges of a signal Mathematical interpolation technique that uses the values and derivatives at the endpoints of a curve segment to generate a smooth and continuous curve Resampling method that maintains high-quality interpolation by applying a weighted sinc function to the pixel values Resampling method that use a convolution filter with adjustable parameters to achieve a balance between sharpness and anti-aliasing in the scaled image Uses piecewise-defined polynomial functions to smoothly interpolate and approximate a curve or surface, providing flexible and continuous shape representation Only Clip Saving to storage will not be performed, and image will be tried to put into the clipboard only Brush will restore the background instead of erasing OCR (recognize text) Recognize text from given image, 120+ languages supported Picture has no text, or app didn\'t find it "Accuracy: %1$s" Recognition Type Fast Standard Best No Data For proper functioning of Tesseract OCR additional training data (%1$s) needs to be downloaded to your device.\nDo you want to download %2$s data? Download No connection, check it and try again in order to download train models Downloaded Languages Available Languages Segmentation Mode Use Pixel Switch Uses a Google Pixel-like switch Overwritten file with name %1$s at original destination Magnifier Enables magnifier at the top of finger in drawing modes for better accessibility Force initial value Forces exif widget to be checked initially EXIF Allow Multiple Languages Slide Side By Side Toggle Tap Transparency Rate App Rate This app is completely free, if you want it to become bigger, please star the project on Github 😄 Orientation & Script Detection only Auto Orientation & Script Detection Auto only Auto Single Column Single block vertical text Single block Single line Single word Circle word Single char Sparse text Sparse text Orientation & Script Detection Raw line Do you want to delete language \"%1$s\" OCR training data for all recognition types, or only for selected one (%2$s)? Current All Gradient Maker Create gradient of given output size with customized colors and appearance type Linear Radial Sweep Gradient Type Center X Center Y Tile Mode Repeated Mirror Clamp Decal Color Stops Add Color Properties Brightness Enforcement Screen Gradient Overlay Compose any gradient of top of given images Transformations Camera Take a picture with a camera. Note that it is possible to get only one image from this image source Watermarking Cover pictures with customizable text/image watermarks Repeat watermark Repeats watermark over image instead of single at given position Offset X Offset Y Watermark Type This image will be used as pattern for watermarking Text Color Overlay Mode GIF Tools Convert images to GIF picture or extract frames from given GIF image GIF to images Convert GIF file to batch of pictures Convert batch of images to GIF file Images to GIF Pick GIF image to start Use size of First frame Replace specified size with first frame dimensions Repeat Count Frame Delay millis FPS Use Lasso Uses Lasso like in drawing mode to perform erasing Original Image Preview Alpha Confetti Confetti will be shown on saving, sharing and other primary actions Secure Mode Hides app content in recent apps. It cannot be captured or recorded. Exit If you leave preview now, you\'ll need to add the images again Dithering Quantizier Gray Scale Bayer Two By Two Dithering Bayer Three By Three Dithering Bayer Four By Four Dithering Bayer Eight By Eight Dithering Floyd Steinberg Dithering Jarvis Judice Ninke Dithering Sierra Dithering Two Row Sierra Dithering Sierra Lite Dithering Atkinson Dithering Stucki Dithering Burkes Dithering False Floyd Steinberg Dithering Left To Right Dithering Random Dithering Simple Threshold Dithering Sigma Spatial Sigma Median Blur B Spline Utilizes piecewise-defined bicubic polynomial functions to smoothly interpolate and approximate a curve or surface, flexible and continuous shape representation Native Stack Blur Tilt Shift Glitch Amount Seed Anaglyph Noise Pixel Sort Shuffle Enhanced Glitch Channel Shift X Channel Shift Y Corruption Size Corruption Shift X Corruption Shift Y Tent Blur Side Fade Side Top Bottom Strength Erode Anisotropic Diffusion Diffusion Conduction Horizontal Wind Stagger Fast Bilaterial Blur Poisson Blur Logarithmic Tone Mapping ACES Filmic Tone Mapping Crystallize Stroke Color Fractal Glass Amplitude Marble Turbulence Oil Water Effect Size Frequency X Frequency Y Amplitude X Amplitude Y Perlin Distortion ACES Hill Tone Mapping Hable Filmic Tone Mapping Heji-Burgess Tone mapping Speed Dehaze Omega Color Matrix 4x4 Color Matrix 3x3 Simple Effects Polaroid Tritanomaly Deuteranomaly Protanomaly Vintage Browni Coda Chrome Night Vision Warm Cool Tritanopia Deutaronotopia Protanopia Achromatomaly Achromatopsia Grain Unsharp Pastel Orange Haze Pink Dream Golden Hour Hot Summer Purple Mist Sunrise Colorful Swirl Soft Spring Light Autumn Tones Lavender Dream Cyberpunk Lemonade Light Spectral Fire Night Magic Fantasy Landscape Color Explosion Electric Gradient Caramel Darkness Futuristic Gradient Green Sun Rainbow World Deep Purple Space Portal Red Swirl Digital Code Bokeh The app bar emoji will change randomly Random Emojis You cannot use random emojis while emojis are disabled You cannot select an emoji while random emojis are enabled Old Tv Shuffle Blur Favorite No favorite filters added yet Image Format Adds a container with the selected shape under the icons Icon Shape Drago Aldridge Cutoff Uchimura Mobius Transition Peak Color Anomaly Images overwritten at original destination Cannot change image format while overwrite files option enabled Emoji as Color Scheme Uses emoji primary color as app color scheme instead of manually defined one Material You Creates Material You palette from image Dark Colors Uses night mode color scheme instead of light variant Copy as Jetpack Compose code Ring Blur Cross Blur Circle Blur Star Blur Linear Tilt-Shift Tags To Remove APNG Tools Convert images to APNG picture or extract frames from given APNG image APNG to images Convert APNG file to batch of pictures Convert batch of images to APNG file Images to APNG Pick APNG image to start Motion Blur Zip Create Zip file from given files or images Drag Handle Width Confetti Type Festive Explode Rain Corners JXL Tools Perform JXL ~ JPEG transcoding with no quality loss, or convert GIF/APNG to JXL animation JXL to JPEG Perform lossless transcoding from JXL to JPEG Perform lossless transcoding from JPEG to JXL JPEG to JXL Pick JXL image to start Fast Gaussian Blur 2D Fast Gaussian Blur 3D Fast Gaussian Blur 4D Auto Paste Allows app to auto paste clipboard data, so it will appear on main screen and you will be able to process it Harmonization Color Harmonization Level Lanczos Bessel Resampling method that maintains high-quality interpolation by applying a Bessel (jinc) function to the pixel values GIF to JXL Convert GIF images to JXL animated pictures APNG to JXL Convert APNG images to JXL animated pictures JXL to Images Convert JXL animation to batch of pictures Images to JXL Convert batch of pictures to JXL animation Behavior Skip File Picking File picker will be shown immediately if this possible on chosen screen Generate Previews Enables preview generation, this may help to avoid crashes on some devices, this also disables some editing functionality within single edit option Lossy Compression Uses lossy compression to reduce file size instead of lossless Compression Type Controls resulting image decoding speed, this should help open resulting image faster, value of %1$s means slowest decoding, whereas %2$s - fastest, this setting may increase output image size Sorting Date Date (Reversed) Name Name (Reversed) Channels Configuration Today Yesterday Embedded Picker Image Toolbox\'s image picker No permissions Request Pick Multiple Media Pick Single Media Pick Try again Show Settings In Landscape If this disabled then in landscape mode settings will be opened on the button in top app bar as always, instead of permanent visible option Fullscreen Settings Enable it and settings page will be always opened as fullscreen instead of slideable drawer sheet Switch Type Compose A Jetpack Compose Material You switch A Material You switch Max Resize Anchor Pixel Fluent A switch based on the \"Fluent\" design system Cupertino A switch based on the \"Cupertino\" design system Images to SVG Trace given images to SVG images Use Sampled Palette Quantization palette will be sampled if this option enabled Path Omit Usage of this tool for tracing large images without downscaling are not recommended, it can cause crash and increase processing time Downscale image Image will be downscaled to lower dimensions before processing, this helps the tool to work faster and safer Minimum Color Ratio Lines Threshold Quadratic Threshold Coordinates Rounding Tolerance Path Scale Reset properties All properties will be set to default values, notice that this action cannot be undone Detailed Default Line Width Engine Mode Legacy LSTM network Legacy & LSTM Convert Convert image batches to given format Add New Folder Bits Per Sample Compression Photometric Interpretation Samples Per Pixel Planar Configuration Y Cb Cr Sub Sampling Y Cb Cr Positioning X Resolution Y Resolution Resolution Unit Strip Offsets Rows Per Strip Strip Byte Counts JPEG Interchange Format JPEG Interchange Format Length Transfer Function White Point Primary Chromaticities Y Cb Cr Coefficients Reference Black White Date Time Image Description Make Model Software Artist Copyright Exif Version Flashpix Version Color Space Gamma Pixel X Dimension Pixel Y Dimension Compressed Bits Per Pixel Maker Note User Comment Related Sound File Date Time Original Date Time Digitized Offset Time Offset Time Original Offset Time Digitized Sub Sec Time Sub Sec Time Original Sub Sec Time Digitized Exposure Time F Number Exposure Program Spectral Sensitivity Photographic Sensitivity OECF Sensitivity Type Standard Output Sensitivity Recommended Exposure Index ISO Speed ISO Speed Latitude yyy ISO Speed Latitude zzz Shutter Speed Value Aperture Value Brightness Value Exposure Bias Value Max Aperture Value Subject Distance Metering Mode Flash Subject Area Focal Length Flash Energy Spatial Frequency Response Focal Plane X Resolution Focal Plane Y Resolution Focal Plane Resolution Unit Subject Location Exposure Index Sensing Method File Source CFA Pattern Custom Rendered Exposure Mode White Balance Digital Zoom Ratio Focal Length In35mm Film Scene Capture Type Gain Control Contrast Saturation Sharpness Device Setting Description Subject Distance Range Image Unique ID Camera Owner Name Body Serial Number Lens Specification Lens Make Lens Model Lens Serial Number GPS Version ID GPS Latitude Ref GPS Latitude GPS Longitude Ref GPS Longitude GPS Altitude Ref GPS Altitude GPS Time Stamp GPS Satellites GPS Status GPS Measure Mode GPS DOP GPS Speed Ref GPS Speed GPS Track Ref GPS Track GPS Img Direction Ref GPS Img Direction GPS Map Datum GPS Dest Latitude Ref GPS Dest Latitude GPS Dest Longitude Ref GPS Dest Longitude GPS Dest Bearing Ref GPS Dest Bearing GPS Dest Distance Ref GPS Dest Distance GPS Processing Method GPS Area Information GPS Date Stamp GPS Differential GPS H Positioning Error Interoperability Index DNG Version Default Crop Size Preview Image Start Preview Image Length Aspect Frame Sensor Bottom Border Sensor Left Border Sensor Right Border Sensor Top Border ISO Draw Text on path with given font and color Font Size Watermark Size Repeat Text Current text will be repeated until the path end instead of one time drawing Dash Size Use selected image to draw it along given path This image will be used as repetitive entry of drawn path Draws outlined triangle from start point to end point Draws outlined triangle from start point to end point Outlined Triangle Triangle Draws polygon from start point to end point Polygon Outlined Polygon Draws outlined polygon from start point to end point Vertices Draw Regular Polygon Draw polygon which will be regular instead of free form Draws star from start point to end point Star Outlined Star Draws outlined star from start point to end point Inner Radius Ratio Draw Regular Star Draw star which will be regular instead of free form Antialias Enables antialiasing to prevent sharp edges Open Edit Instead Of Preview When you select image to open (preview) in ImageToolbox, edit selection sheet will be opened instead of previewing Document Scanner Scan documents and create PDF or separate images from them Click to start scanning Start Scanning Save As Pdf Share as PDF Options below is for saving images, not PDF Equalize Histogram HSV Equalize Histogram Enter Percentage Allow enter by Text Field Enables Text Field behind presets selection, to enter them on the fly Scale Color Space Linear Equalize Histogram Pixelation Grid Size X Grid Size Y Equalize Histogram Adaptive Equalize Histogram Adaptive LUV Equalize Histogram Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Crop to Content Frame Color Color To Ignore Template No template filters added Create New The scanned QR code is not a valid filter template Scan QR code Selected file have no filter template data Create Template Template Name This image will be used to preview this filter template Template Filter As QR code image As file Save as file Save project Store layers, background and edit history in an editable project file Save as QR code image Delete Template You are about to delete selected template filter. This operation cannot be undone Added filter template with name \"%1$s\" (%2$s) Filter Preview QR & Barcode Scan QR code and get its content or paste your string to generate new one Code Content Scan any barcode to replace content in field, or type something to generate new barcode with selected type QR Description Min Grant camera permission in settings to scan QR code Grant camera permission in settings to scan Document Scanner Cubic B-Spline Hamming Hanning Blackman Welch Quadric Gaussian Sphinx Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-Hann Box Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Cubic interpolation provides smoother scaling by considering the closest 16 pixels, giving better results than bilinear Utilizes piecewise-defined polynomial functions to smoothly interpolate and approximate a curve or surface, flexible and continuous shape representation A window function used to reduce spectral leakage by tapering the edges of a signal, useful in signal processing A variant of the Hann window, commonly used to reduce spectral leakage in signal processing applications A window function that provides good frequency resolution by minimizing spectral leakage, often used in signal processing A window function designed to give good frequency resolution with reduced spectral leakage, often used in signal processing applications A method that uses a quadratic function for interpolation, providing smooth and continuous results An interpolation method that applies a Gaussian function, useful for smoothing and reducing noise in images An advanced resampling method providing high-quality interpolation with minimal artifacts A triangular window function used in signal processing to reduce spectral leakage A high-quality interpolation method optimized for natural image resizing, balancing sharpness and smoothness A sharper variant of the Robidoux method, optimized for crisp image resizing A spline-based interpolation method that provides smooth results using a 16-tap filter A spline-based interpolation method that provides smooth results using a 36-tap filter A spline-based interpolation method that provides smooth results using a 64-tap filter An interpolation method that uses the Kaiser window, providing good control over the trade-off between main-lobe width and side-lobe level A hybrid window function combining the Bartlett and Hann windows, used to reduce spectral leakage in signal processing A simple resampling method that uses the average of the nearest pixel values, often resulting in a blocky appearance A window function used to reduce spectral leakage, providing good frequency resolution in signal processing applications A resampling method that uses a 2-lobe Lanczos filter for high-quality interpolation with minimal artifacts A resampling method that uses a 3-lobe Lanczos filter for high-quality interpolation with minimal artifacts A resampling method that uses a 4-lobe Lanczos filter for high-quality interpolation with minimal artifacts A variant of the Lanczos 2 filter that uses the jinc function, providing high-quality interpolation with minimal artifacts A variant of the Lanczos 3 filter that uses the jinc function, providing high-quality interpolation with minimal artifacts A variant of the Lanczos 4 filter that uses the jinc function, providing high-quality interpolation with minimal artifacts Hanning EWA Elliptical Weighted Average (EWA) variant of the Hanning filter for smooth interpolation and resampling Robidoux EWA Elliptical Weighted Average (EWA) variant of the Robidoux filter for high-quality resampling Blackman EWA Elliptical Weighted Average (EWA) variant of the Blackman filter for minimizing ringing artifacts Quadric EWA Elliptical Weighted Average (EWA) variant of the Quadric filter for smooth interpolation Robidoux Sharp EWA Elliptical Weighted Average (EWA) variant of the Robidoux Sharp filter for sharper results Lanczos 3 Jinc EWA Elliptical Weighted Average (EWA) variant of the Lanczos 3 Jinc filter for high-quality resampling with reduced aliasing Ginseng A resampling filter designed for high-quality image processing with a good balance of sharpness and smoothness Ginseng EWA Elliptical Weighted Average (EWA) variant of the Ginseng filter for enhanced image quality Lanczos Sharp EWA Elliptical Weighted Average (EWA) variant of the Lanczos Sharp filter for achieving sharp results with minimal artifacts Lanczos 4 Sharpest EWA Elliptical Weighted Average (EWA) variant of the Lanczos 4 Sharpest filter for extremely sharp image resampling Lanczos Soft EWA Elliptical Weighted Average (EWA) variant of the Lanczos Soft filter for smoother image resampling Haasn Soft A resampling filter designed by Haasn for smooth and artifact-free image scaling Format Conversion Convert batch of images from one format to another Dismiss Forever Image Stacking Stack images on top of each other with chosen blend modes Add Image Bins count Clahe HSL Clahe HSV Equalize Histogram Adaptive HSL Equalize Histogram Adaptive HSV Edge Mode Clip Wrap Color Blindness Select mode to adapt theme colors for the selected color blindness variant Difficulty distinguishing between red and green hues Difficulty distinguishing between green and red hues Difficulty distinguishing between blue and yellow hues Inability to perceive red hues Inability to perceive green hues Inability to perceive blue hues Reduced sensitivity to all colors Complete color blindness, seeing only shades of gray Do not use Color Blind scheme Colors will be exactly as set in the theme Sigmoidal Lagrange 2 A Lagrange interpolation filter of order 2, suitable for high-quality image scaling with smooth transitions Lagrange 3 A Lagrange interpolation filter of order 3, offering better accuracy and smoother results for image scaling Lanczos 6 A Lanczos resampling filter with a higher order of 6, providing sharper and more accurate image scaling Lanczos 6 Jinc A variant of the Lanczos 6 filter using a Jinc function for improved image resampling quality Linear Box Blur Linear Tent Blur Linear Gaussian Box Blur Linear Stack Blur Gaussian Box Blur Linear Fast Gaussian Blur Next Linear Fast Gaussian Blur Linear Gaussian Blur Choose one filter to use it as paint Replace Filter Pick filter below to use it as brush in your drawing TIFF compression scheme Low Poly Sand Painting Image Splitting Split single image by rows or columns Fit To Bounds Combine crop resize mode with this parameter to achieve desired behavior (Crop/Fit to aspect ratio) Languages imported successfully Backup OCR models Import Export Position Center Top Left Top Right Bottom Left Bottom Right Top Center Center Right Bottom Center Center Left Target Image Palette Transfer Enhanced Oil Simple Old TV HDR Gotham Simple Sketch Soft Glow Color Poster Tri Tone Third color Clahe Oklab Clahe Oklch Clahe Jzazbz Polka Dot Clustered 2x2 Dithering Clustered 4x4 Dithering Clustered 8x8 Dithering Yililoma Dithering No favorite options selected, add them in tools page Add Favorites Complementary Analogous Triadic Split Complementary Tetradic Square Analogous + Complementary Color Tools Mix, make tones, generate shades and more Color Harmonies Color Shading Variation Tints Tones Shades Color Mixing Color Info Selected Color Color To Mix Cannot use monet while dynamic colors are turned on 512x512 2D LUT Target LUT image Amatorka Miss Etikate Soft Elegance Soft Elegance Variant Palette Transfer Variant 3D LUT Target 3D LUT File (.cube / .CUBE) LUT Bleach Bypass Candlelight Drop Blues Edgy Amber Fall Colors Film Stock 50 Foggy Night Kodak Get Neutral LUT image First, use your favourite photo editing application to apply a filter to neutral LUT which you can obtain here. For this to work properly each pixel color must not depend on other pixels (e.g. blur will not work). Once ready, use your new LUT image as input for 512*512 LUT filter Pop Art Celluloid Coffee Golden Forest Greenish Retro Yellow Links Preview Enables link preview retrieving in places where you can obtain text (QRCode, OCR etc) Links ICO files can only be saved at the maximum size of 256 x 256 GIF to WEBP Convert GIF images to WEBP animated pictures WEBP Tools Convert images to WEBP animated picture or extract frames from given WEBP animation WEBP to images Convert WEBP file to batch of pictures Convert batch of images to WEBP file Images to WEBP Pick WEBP image to start No full access to files Allow all files access to see JXL, QOI and other images that are not recognized as images on Android. Without the permission Image Toolbox is unable to show those images Default Draw Color Default Draw Path Mode Add Timestamp Enables Timestamp adding to the output filename Formatted Timestamp Enable Timestamp formatting in output filename instead of basic millis Enable Timestamps to select their format One Time Save Location View and Edit one time save locations which you can use by long pressing the save button in mostly all options Recently Used CI channel Group Ungroup Image Toolbox in Telegram 🎉 Join our chat where you can discuss anything you want and also look into the CI channel where I post betas and announcements Get notified about new versions of app, and read announcements Fit an image to given dimensions and apply blur or color to background Tools Arrangement Group tools by type Groups tools on the main screen by their type instead of a custom list arrangement Default Values System Bars Visibility Show System Bars By Swipe Enables swiping to show system bars if they are hidden Auto Hide All Show All Hide Nav Bar Hide Status Bar Noise Generation Generate different noises like Perlin or other types Frequency Noise Type Rotation Type Fractal Type Octaves Lacunarity Gain Weighted Strength Ping Pong Strength Distance Function Return Type Jitter Domain Warp Alignment Custom Filename Select location and filename which are will be used to save current image Saved to folder with custom name Collage Maker Make collages from up to 20 images Collage Type Hold image to swap, move and zoom to adjust position Disable rotation Prevents rotating images with two-finger gestures Enable snapping to borders After moving or zooming, images will snap to fill frame edges Histogram RGB or Brightness image histogram to help you make adjustments This image will be used to generate RGB and Brightness histograms Tesseract Options Apply some input variables for tesseract engine Custom Options Options should be entered following this pattern: \"--{option_name} {value}\" Auto Crop Free Corners Crop image by polygon, this also corrects perspective Coerce Points To Image Bounds Points will not be limited by image bounds, this useful for more precise perspective correcting Mask Content aware fill under drawn path Heal Spot Use Circle Kernel Opening Closing Morphological Gradient Top Hat Black Hat Tone Curves Reset Curves Curves will be rolled back to default value Line Style Gap Size Dashed Dot Dashed Stamped Zigzag Draws dashed line along the drawn path with specified gap size Draws dot and dashed line along given path Just default straight lines Draws selected shapes along the path with specified spacing Draws wavy zigzag along the path Zigzag ratio Create Shortcut Choose tool to pin Tool will be added to home screen of your launcher as shortcut, use it combining with \"Skip file picking\" setting to achieve needed behavior Don\'t stack frames Enables disposing of previous frames, so they will not stack on each other Crossfade Frames will be crossfaded into each other Crossfade frames count Threshold One Threshold Two Canny Mirror 101 Enhanced Zoom Blur Laplacian Simple Sobel Simple Helper Grid Shows supporting grid above drawing area to help with precise manipulations Grid Color Cell Width Cell Height Compact Selectors Some selection controls will use a compact layout to take less space Grant camera permission in settings to capture image Layout Main Screen Title Constant Rate Factor (CRF) A value of %1$s means a slow compression, resulting in a relatively small file size. %2$s means a faster compression, resulting in a large file. Lut Library Download collection of LUTs, which you can apply after downloading Update collection of LUTs (only new ones will be queued), which you can apply after downloading Change default image preview for filters Preview Image Hide Show Slider Type Fancy Material 2 A fancy-looking slider. This is the default option A Material 2 slider A Material You slider Apply Center Dialog Buttons Buttons of dialogs will be positioned at center instead of left side if possible Open Source Licenses View licenses of open source libraries used in this app Area Resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire\'-free results. But when the image is zoomed, it is similar to the \"Nearest\" method. Enable Tonemapping Enter % Cannot access the site, try using VPN or check if the url correct Markup Layers Layers mode with ability to freely place images, text and more Edit layer Layers on image Use an image as a background and add different layers on top of it Layers on background The same as first option but with color instead of image Beta Fast Settings Side Add a floating strip at the selected side while editing images, which will open fast settings when clicked Clear selection Setting group \"%1$s\" will be collapsed by default Setting group \"%1$s\" will be expanded by default Base64 Tools Decode Base64 string to image, or encode image to Base64 format Base64 Provided value is not a valid Base64 string Cannot copy empty or invalid Base64 string Paste Base64 Copy Base64 Load image to copy or save Base64 string. If you have the string itself, you can paste it above to obtain image Save Base64 Share Base64 Options Actions Import Base64 Base64 Actions Add Outline Add outline around text with specified color and width Add Shadow Add blur shadow behind layer with configurable color and offsets Outline Color Outline Size Shadow Color Text Geometry Stretch or skew text for sharper stylization Scale X Skew X Rotation Checksum as Filename Output images will have name corresponding to their data checksum Free Software (Partner) More useful software in the partner channel of Android applications Algorithm Checksum Tools Compare checksums, calculate hashes or create hex strings from files using different hashing algorithms Calculate Text Hash Checksum Pick file to calculate its checksum based on selected algorithm Enter text to calculate its checksum based on selected algorithm Source Checksum Checksum To Compare Match! Difference Checksums are equal, it can be safe Checksums are not equal, file can be unsafe! Mesh Gradients Look at online collection of Mesh Gradients Only TTF and OTF fonts can be imported Import font (TTF/OTF) Export fonts Imported fonts Error while saving attempt, try to change output folder Filename is not set None Custom Pages Pages Selection Tool Exit Confirmation If you have unsaved changes while using particular tools and try to close it, then confirm dialog will be shown Edit EXIF Change metadata of single image without recompression Tap to edit available tags Change Sticker Fit Width Fit Height Batch Compare Pick file/files to calculate its checksum based on selected algorithm Pick Files Pick Directory Head Length Scale Stamp Timestamp Format Pattern Padding Image Cutting Cut image part and merge left ones (can be inverse) by vertical or horizontal lines Vertical Pivot Line Horizontal Pivot Line Inverse Selection Vertical cut part will be leaved, instead of merging parts around cut area Horizontal cut part will be leaved, instead of merging parts around cut area Collection of Mesh Gradients Create mesh gradient with custom amount of knots and resolution Mesh Gradient Overlay Compose mesh gradient of top of given images Points Customization Grid Size Resolution X Resolution Y Resolution Pixel By Pixel Highlight Color Pixel Comparison Type Scan barcode Height Ratio Barcode Type Enforce B/W Barcode Image will be fully black and white and not colored by app\'s theme Scan any Barcode (QR, EAN, AZTEC, …) and get its content or paste your text to generate new one No Barcode Found Generated Barcode Will Be Here Audio Covers Extract album cover images from audio files, most common formats are supported Pick audio to start Pick Audio No Covers Found Send Logs Click to share app logs file, this can help me to spot the problem and fix issues Oops… Something went wrong You can contact me using options below and I will try to find solution.\n(Do not forget to attach logs) Write To File Extract text from batch of images and store it in the one text file Write To Metadata Extract text from each image and place it in EXIF info of relative photos Write To Searchable PDF Recognize text from image batch and save searchable PDF with image and selectable text layer Invisible Mode Use steganography to create eye invisible watermarks inside bytes of your images Use LSB LSB (Less Significant Bit) steganography method will be used, FD (Frequency Domain) otherwise Auto Remove Red Eyes Password Lock Unlock PDF is protected Operation almost complete. Cancelling now will require restarting it Date Modified Date Modified (Reversed) Size Size (Reversed) MIME Type MIME Type (Reversed) Extension Extension (Reversed) Date Added Date Added (Reversed) Left to Right Right to Left Top to Bottom Bottom to Top Liquid Glass A switch based on recently announced IOS 26 and it\'s liquid glass design system Pick image or paste/import Base64 data below Type image link to start Paste link Kaleidoscope Secondary angle Sides Channel Mix Blue green Red blue Green red Into red Into green Into blue Cyan Magenta Yellow Color Halftone Contour Levels Offset Voronoi Crystallize Shape Stretch Randomness Despeckle Diffuse DoG Second radius Equalize Glow Whirl and Pinch Pointillize Border color Polar Coordinates Rect to polar Polar to rect Invert in circle Reduce Noise Simple Solarize Weave X Gap Y Gap X Width Y Width Twirl Rubber Stamp Smear Density Mix Sphere Lens Distortion Refraction index Arc Spread angle Sparkle Rays ASCII Gradient Moire Autumn Bone Jet Winter Ocean Summer Spring Cool Variant HSV Pink Hot Parula Magma Inferno Plasma Viridis Cividis Twilight Twilight Shifted Perspective Auto Deskew Allow crop Crop or Perspective Absolute Turbo Deep Green Lens Correction Target lens profile file in JSON format Download ready lens profiles Part percents Export as JSON Copy string with a palette data as json representation Seam Carving Home Screen Lock Screen Built-in Wallpapers Export Refresh Obtain current Home, Lock and Built-in wallpapers Allow access to all files, this is needed to retrieve wallpapers Manage external storage permission is not enough, you need to allow access to your images, make sure to select \"Allow all\" Add Preset To Filename Appends suffix with selected preset to image filename Add Image Scale Mode To Filename Appends suffix with selected image scale mode to image filename ASCII Art Convert picture to ASCII text which will look like image Params Applies negative filter to image for better result in some cases Processing screenshot Screenshot not captured, try again Saving skipped %1$s files skipped Allow Skip If Larger Some tools are will be allowed to skip saving images if the resulting file size would be larger than the original Calendar Event Contact Email Location Phone Text SMS URL Wi-Fi Open network N/A SSID Phone Message Address Subject Body Name Organization Title Phones Emails URLs Addresses Summary Description Location Organizer Start date End date Status Latitude Longitude Create Barcode Edit Barcode Wi-Fi configuration Security Pick contact Grant contacts permission in settings to autofill using selected contact Contact info First name Middle name Last name Pronunciation Add phone Add email Add address Website Add website Formatted name This image will be used to place above barcode Code customization This image will be used as logo in the center of QR code Logo Logo padding Logo size Logo corners Fourth eye Adds eye symmetry to qr code by adding fourth eye at the bottom end corner Pixel shape Frame shape Ball shape Error correction level Dark color Light color Hyper OS Xiaomi HyperOS like style Mask pattern This code may be not scannable, change appearance params to make it readable with all devices Not scannable Tools will look like home screen app launcher to be more compact Launcher Mode Fills an area with selected brush and style Flood Fill Spray Draws graffity styled path Square Particles Spray particles will be square shaped instead of circles Palette Tools Generate basic/material you palette from image, or import/export across different palette formats Edit Palette Export/import palette across various formats Color name Palette name Palette Format Export generated palette to different formats Adds new color to current palette %1$s format doesn\'t support providing palette name Due to Play Store policies, this feature cannot be included in the current build. To access this functionality, please download ImageToolbox from an alternative source. You can find the available builds on GitHub below. Open Github page %1$s color %1$s colors %1$s colors %1$s colors Original file will be replaced with new one instead of saving in selected folder Detected hidden watermark text Detected hidden watermark image This image was hidden Generative Inpainting Allows you to remove objects in an image using an AI model, without relying on OpenCV. To use this feature, app will download the required model (~200 MB) from GitHub Allows you to remove objects in an image using an AI model, without relying on OpenCV. This could be a long running operation Error Level Analysis Luminance Gradient Average Distance Copy Move Detection Retain Coefficent Clipboard data is too large Data is too large to copy Simple Weave Pixelization Staggered Pixelization Cross Pixelization Micro Macro Pixelization Orbital Pixelization Vortex Pixelization Pulse Grid Pixelization Nucleus Pixelization Radial Weave Pixelization Cannot open uri \"%1$s\" Snowfall Mode Enabled Border Frame Glitch Variant Channel Shift Max Offset VHS Block Glitch Block Size CRT curvature Curvature Chroma Pixel Melt Max Drop AI Tools Various tools to process images through ai models like artifact removing or denoising Compression, jagged lines Cartoons, broadcast compression General compression, general noise Colorless cartoon noise Fast, general compression, general noise, animation/comics/anime Book scanning Exposure correction Best at general compression, color images Best at general compression, grayscale images General compression, grayscale images, stronger General noise, color images General noise, color images, better details General noise, grayscale images General noise, grayscale images, stronger General noise, grayscale images, strongest General compression General compression Texturization, h264 compression VHS compression Non-standard compression (cinepak, msvideo1, roq) Bink compression, better on geometry Bink compression, stronger Bink compression, soft, retains detail Eliminating the stair-step effect, smoothing Scanned art/drawings, mild compression, moire Color banding Slow, removing halftones General colorizer for grayscale/bw images, for better results use DDColor Edge removal Removes oversharpening Slow, dithering Anti-aliasing, general artifacts, CGI KDM003 scans processing Lightweight image enhancement model Compression artifact removal Compression artifact removal Bandage removal with smooth results Halftone pattern processing Dither pattern removal V3 JPEG artifact removal V2 H.264 texture enhancement VHS sharpening and enhancement Merging Chunk Size Overlap Size Images over %1$s px will be sliced and processed in chunks, overlap blends these to prevent visible seams. Large sizes can cause instability with low-end devices Select one to start Do you want to delete %1$s model? You will need to download it again Confirm Models Downloaded Models Available Models Preparing Active model Failed to open session Only .onnx/.ort models can be imported Import model Import custom onnx model to further use, only onnx/ort models are accepted, supports almost all esrgan like variants Imported Models General noise, colored images General noise, colored images, stronger General noise, colored images, strongest Reduces dithering artifacts and color banding, improving smooth gradients and flat color areas. Enhances image brightness and contrast with balanced highlights while preserving natural colors. Brightens dark images while keeping details and avoiding overexposure. Removes excessive color toning and restores a more neutral and natural color balance. Applies Poisson-based noise toning with emphasis on preserving fine details and textures. Applies soft Poisson noise toning for smoother and less aggressive visual results. Uniform noise toning focused on detail preservation and image clarity. Gentle uniform noise toning for subtle texture and smooth appearance. Repairs damaged or uneven areas by repainting artifacts and improving image consistency. Lightweight debanding model that removes color banding with minimal performance cost. Optimizes images with very high compression artifacts (0-20% quality) for improved clarity. Enhances images with high compression artifacts (20-40% quality), restoring details and reducing noise. Improves images with moderate compression (40-60% quality), balancing sharpness and smoothness. Refines images with light compression (60-80% quality) to enhance subtle details and textures. Slightly enhances near-lossless images (80-100% quality) while preserving natural look and details. Simple and fast colorization, cartoons, not ideal Slightly reduces image blur, improving sharpness without introducing artifacts. Long running operations Processing image Processing Removes heavy JPEG compression artifacts in very low quality images (0-20%). Reduces strong JPEG artifacts in highly compressed images (20-40%). Cleans up moderate JPEG artifacts while preserving image details (40-60%). Refines light JPEG artifacts in fairly high quality images (60-80%). Subtly reduces minor JPEG artifacts in near-lossless images (80-100%). Enhances fine details and textures, improving perceived sharpness without heavy artifacts. Processing finished Processing failed Enhances skin textures and details while keeping a natural look, optimized for speed. Removes JPEG compression artifacts and restores image quality for compressed photos. Reduces ISO noise in photos taken in low-light conditions, preserving details. Corrects overexposed or “jumbo” highlights and restores better tonal balance. Lightweight and fast colorization model that adds natural colors to grayscale images. DeJPEG Denoise Colorize Artifacts Enhance Anime Scans Upscale X4 upscaler for general images; tiny model that uses less GPU and time, with moderate deblur and denoise. X2 upscaler for general images, preserving textures and natural details. X4 upscaler for general images with enhanced textures and realistic results. X4 upscaler optimized for anime images; 6 RRDB blocks for sharper lines and details. X4 upscaler with MSE loss, produces smoother results and reduced artifacts for general images. X4 Upscaler optimized for anime images; 4B32F variant with sharper details and smooth lines. X4 UltraSharp V2 model for general images; emphasizes sharpness and clarity. X4 UltraSharp V2 Lite; faster and smaller, preserves detail while using less GPU memory. Lightweight model for quick background removal. Balanced performance and accuracy. Works with portraits, objects, and scenes. Recommended for most use cases. Remove BG Horizontal Border Thickness Vertical Border Thickness Current model does not support chunking, image will be processed in original dimensions, this may cause high memory consumption and issues with low-end devices Chunking disabled, image will be processed in original dimensions, this may cause high memory consumption and issues with low-end devices but may give better results on inference Chunking High-accuracy image segmentation model for background removing Lightweight version of U2Net for faster background removing with smaller memory usage. Full DDColor model delivers high-quality colorization for general images with minimal artifacts. Best choice of all colorization models. DDColor Trained and private artistic datasets; produces diverse and artistic colorization results with fewer unrealistic color artifacts. Lightweight BiRefNet model based on Swin Transformer for accurate background removing. High-quality background removal with sharp edges and excellent detail preservation, especially on complex objects and tricky backgrounds. Background removal model that produces accurate masks with smooth edges, suitable for general objects and moderate detail preservation. Model already downloaded Model successfully imported Type Keyword Very Fast Normal Slow Very Slow Compute Percents Min value is %1$s Distort image by drawing with fingers Warp Hardness Warp Mode Move Grow Shrink Swirl CW Swirl CCW Fade Strength Top Drop Bottom Drop Start Drop End Drop Downloading Smooth Shapes Use superellipses instead of standard rounded rectangles for smoother, more natural shapes Shape Type Cut Rounded Smooth Sharp edges without rounding Classic rounded corners Shapes Type Corners Size Squircle Elegant rounded UI elements Filename Format Custom text placed at the very beginning of the filename, perfect for project names, brands, or personal tags. Uses the original file name without extension, helping you keep source identification intact. The image width in pixels, useful for tracking resolution changes or scaling results. The image height in pixels, helpful when working with aspect ratios or exports. A customizable timestamp that lets you define your own format by Java specification for perfect sorting. Generates random digits to guarantee unique filenames; add more digits for extra safety against duplicates. Auto-incrementing counter for batch exports, ideal when saving multiple images in one session. Inserts the applied preset name into the filename so you can easily remember how the image was processed. Displays the image scaling mode used during processing, helping distinguish resized, cropped, or fitted images. Custom text placed at the end of the filename, useful for versioning like _v2, _edited, or _final. The file extension (png, jpg, webp, etc.), automatically matching the actual saved format. Fling Type Android Native iOS Style Smooth Curve Quick Stop Bouncy Floaty Snappy Ultra Smooth Adaptive Accessibility Aware Reduced Motion Native Android scroll physics Balanced, smooth scrolling for general use Higher friction iOS-like scroll behavior Unique spline curve for distinct scroll feel Precise scrolling with quick stopping Playful, responsive bouncy scroll Long, gliding scrolls for content browsing Quick, responsive scrolling for interactive UIs Premium smooth scrolling with extended momentum Adjusts physics based on fling velocity Respects system accessibility settings Minimal motion for accessibility needs Primary Lines Adds thicker line every fifth line Fill Color Hidden Tools Tools Hidden For Share Color Library Browse a vast collection of colors Sharpens and removes blur from images while maintaining natural details, ideal for fixing out-of-focus photos. Intelligently restores images that have been previously resized, recovering lost details and textures. Optimized for live-action content, reduces compression artifacts and enhances fine details in movie/TV show frames. Converts VHS-quality footage to HD, removing tape noise and enhancing resolution while preserving vintage feel. Specialized for text-heavy images and screenshots, sharpens characters and improves readability. Advanced upscaling trained on diverse datasets, excellent for general-purpose photo enhancement. Optimized for web-compressed photos, removes JPEG artifacts and restores natural appearance. Improved version for web photos with better texture preservation and artifact reduction. 2x upscaling with Dual Aggregation Transformer technology, maintains sharpness and natural details. 3x upscaling using advanced transformer architecture, ideal for moderate enlargement needs. 4x high-quality upscaling with state-of-the-art transformer network, preserves fine details at larger scales. Removes blur/noise and shakes from photos. General purpose but best on photos. Restores low-quality images using Swin2SR transformer, optimized for BSRGAN degradation. Great for fixing heavy compression artifacts and enhancing details at 4x scale. 4x upscaling with SwinIR transformer trained on BSRGAN degradation. Uses GAN for sharper textures and more natural details in photos and complex scenes. Path Merge PDF Combine multiple PDF files into one document Files Order pp. Split PDF Extract specific pages from PDF document Rotate PDF Fix page orientation permanently Pages Rearrange PDF Drag and drop pages to reorder them Hold & Drag pages Page Numbers Add numbering to your documents automatically Label Format PDF to Text (OCR) Extract plain text from your PDF documents Overlay custom text for branding or security Signature Add your electronic signature to any document This will be used as signature Unlock PDF Remove passwords from your protected files Protect PDF Secure your documents with strong encryption Success PDF unlocked, you can save or share it Repair PDF Attempt to fix corrupted or unreadable documents Grayscale Convert all document embedded images to grayscale Compress PDF Optimize your document file size for easier sharing ImageToolbox rebuilds the internal cross-reference table and regenerates the file structure from scratch. This can restore access to many files that \"cannot be opened\" This tool converts all document images to grayscale. Best for printing and reducing file size Metadata Edit document properties for better privacy Tags Producer Author Keywords Creator Privacy Deep Clean Clear all available metadata for this document Page Deep OCR Extract text from document and store it in the one text file using Tesseract engine Can\'t remove all pages Remove PDF pages Remove specific pages from PDF document Tap To Remove Manually Crop PDF Crop document pages to any bounds Flatten PDF Make PDF unmodifiable by rastering document pages Could not start the camera. Please check permissions and make sure it\'s not being used by another app. Extract Images Extract images embedded in PDFs at their original resolution This PDF file doesn’t contain any embedded images This tool scans every page and recovers full-quality source images — perfect for saving originals from documents Draw Signature Pen Params Use own signature as image to be placed on documents Zip PDF Split document with given interval and pack new docuements into zip archive Interval Print PDF Prepare document for printing with custom page size Pages Per Sheet Orientation Page Size Margin Bloom Soft Knee Optimized for anime and cartoons. Fast upscaling with improved natural colors and fewer artifacts One UI Samsung One UI 7 like style Enter basic math symbols here to calculate the desired value (e.g. (5+5)*10) Math expression Pick up to %1$s images Keep Date Time Always preserve exif tags related to date and time, works independently of keep exif option Background Color For Alpha Formats Adds ability to set background color for every image format with alpha support, when disabled this available for non alpha ones only Failed to open Layer alpha Horizontal Flip Vertical Flip Remove annotations Remove selected annotation types such as links, comments, highlights, shapes, or form fields from the PDF pages Hyperlinks File Attachments Lines Popups Stamps Shapes Text Notes Text Markup Form Fields Markup Unknown Annotations ================================================ FILE: core/resources/src/main/res/values/themes.xml ================================================ ================================================ FILE: core/resources/src/main/res/values-ar/strings.xml ================================================ اللغة وضع Amoled إذا فُعّل لون الأسطح فسيتم ضبطه على الظلام المطلق في الوضع الليلي نظام الألوان أحمر أخضر أزرق ألصِق كود aRGB صالح لا شيئ للصقه لا يمكن تغيير مخطط ألوان التطبيق أثناء تشغيل الألوان الديناميكية سيعتمد سمة التطبيق على اللون الذي ستختاره حدث خطأ ما: %1$s الحجم %1$s تحميل… الصورة أكبر من تُعايَن، ولكن ستُحفَظ بأي حال. اخنر صورة للبدء العرض %1$s الارتفاع %1$s الجودة الصيغة نوع إعادة التحجيم واضح مرن اختر صورة أمتأكد برغبتك بإغلاق التطبيق؟ يُغلق التطبيق ابقِ أغلِق صفّر الصورة سيتم التراجع عن تغييرات الصورة لوضعها الاساسي صفّر القيم بشكل صحيح صفّر حدث خطأ ما أعد تشغيل التطبيق نُسخ إلى الحافظة إستثناء حرِّر البيانات الوصفية حسناً لم يتم العثور على بيانات وصفية أضف وسم احفظ امحُ امحُ البيانات الوصفية ألغِ سيتم مسح جميع البيانات الوصفية للصورة. لا يمكن التراجع عن هذا الإجراء! الإعدادات المسبقة اقتصاص جارِ الحفظ.. ستفقد جميع التغييرات غير المحفوظة، إذا غادرت الآن. شفرة المصدر احصل على آخر التحديثات وتناقش عن البرنامج من أعطال ومما يتعلق به تحرير واحد تعديل وتحجيم وتحرير صورة واحدة منتقي الألوان حدد لونًا من صورة، انسخ أو شارك صورة اللون نُسخ اللون اقتصاص الصورة بأي حدود النسخة حافظ على البيانات الوصفية الصور: %d غيّر المعاينة أزِل ولّد لوحة ألوان من صورة معينة ولّد لوحة ألوان لوحة ألوان تحديث نسخة جديدة %1$s نوع غير مدعوم: %1$s لا يمكن توليد لوحة ألوان للصورة المحدّدة الاصلية مجلد الحفظ الافتراضي مخصص غير محدد تخزين الجهاز تغيير حجم الصورة بناء على حجم الملف الحجم الأقصى بالكيلو بايت غيّر حجم الصورة باتباع الحجم المحدد بالكيلو بايت قارن قارن بين صورتين معينتين اختر صورتين للبدء اختر الصور الاعدادات الوضع الليلي مظلم مضيء نظام ألوان ديناميكية التخصيص وفقا لالوان الصورة المختارة في حال التفعيل، عند اختيار صورة لتحريرها، سيتم تكييف ألوان التطبيق لتناسب هذه الصورة. حول التطبيق متعقب المشاكل مساعدة في الترجمة لم يتم العثور على تحديثات أرسل تقرير بمشكلتك وطلباتك من هنا ابحث هنا تصحيح أخطاء الترجمة أو ترجمة المشروع إلى لغات أخرى لم يتم العثور على شيء من خلال الاستعلام الخاص بك إذا فعّلته، فسيتم تكييف ألوان التطبيق مع ألوان خلفية الشاشة فشل حفظ %d صورة (صور) سطح هذا التطبيق مجاني تمامًا، ولكن إذا كنت ترغب في دعم تطوير المشروع، يمكنك النقر هنا محاذاة FAB تحقق من وجود تحديثات في حال التفعيل، ستظهر لك نافذة التحديث عند تشغيل التطبيق القيم أضف أساسي ثالث ثانوي إذن منح يحتاج التطبيق إلى الوصول إلى وحدة التخزين الخاصة بك لحفظ الصور لكي يعمل، وهذا أمر ضروري. يُرجى منح الإذن في مربع الحوار التالي. يحتاج التطبيق إلى هذا الإذن للعمل، يُرجى منحه يدويًا سمك الحدود تخزين خارجي ألوان مونيه تكبير الصورة بادئة اسم الملف شارك تعبيري حدد الرموز التعبيرية التي سيتم عرضها على الشاشة الرئيسية أضف حجم الملف إذا فُعّل، يضيف عرض وارتفاع الصورة المحفوظة إلى اسم الملف الناتج احذف EXIF احذف البيانات الوصفية EXIF من أي مجموعة صور معاينة الصورة مصدر الصورة استخدم نية GetContent لاختيار صورة. تعمل في كل مكان، لكن من المعروف أن بها مشاكل في استقبال الصور المختارة على بعض الأجهزة. هذا ليس خطئي. ترتيب الخيارات رتب عدد الرموز التعبيرية تسلسل originalFilename أضف اسم الملف الأصلي إذا فُعّل، يضيف اسم الملف الأصلي في اسم صورة المخرجات معاينة أي نوع من الصور: GIF و SVG وما إلى ذلك منتقي الصور معرض الصور مستكشف الملفات قد يعمل منتقي الصور الحديث من Android الذي يظهر في الجزء السفلي من الشاشة فقط على adnroid 12+ ولديه أيضًا مشكلات في تلقي بيانات EXIF الوصفية منتقي صور المعرض البسيط، لن تعمل إلا إذا كان لديك تطبيق يوفر خاصية إنتقاء الوسائط حرِّر يحدد ترتيب الأدوات على الشاشة الرئيسية إضافة اسم الملف الأصلي لا تعمل إذا تم تحديد مصدر صورة منتقي الصور سطوع مقابلة مسحة بني داكن سلبي Crosshatch عرض الخط حافة سوبل نصف القطر حجم طمس بطيء رسم عتبة استبدل رقم التسلسل إذا فُعّل، يستبدل الطابع الزمني القياسي برقم تسلسل الصورة عند استخدام المعالجة الدفعية تحميل الصورة من الانترنت حمّل أي صورة من الإنترنت لمعاينتها وتقريبها/تبعيدها وتحريرها وحفظها إذا أردت. لا توجد صورة رابط الصورة يملأ ملائمة تغيير حجم الصور إلى العرض واﻹرتفاع المٌعطى. قد تتغير نسبة العرض إلى الارتفاع للصور. يغير حجم الصور ذات جانب طويل إلى العرض أو الارتفاع المٌعطى.سيتم إجراء جميع حسابات الحجم بعد الحفظ. سيتم الحفاظ على نسبة العرض إلى الارتفاع. التشبع أضف تصفية التصفية طبّق سلسلة تصفية على الصور التصفيات ضوء تصفية اللون ألفا التعرض توازن اللون الأبيض درجة حرارة لون أحادي اللون جاما يسلط الضوء والظلال يسلط الضوء الظلال ضباب تأثير مسافة ميل شحذ شمسي حيوية اسود و ابيض تباعد طمس نصفية مساحة ألوان GCA التمويه الضبابي مربع طمس طمس ثنائي الجوهر زخرف لابلاسيان المقالة القصيرة يبدأ نهاية تنعيم الكوهرة تشوه زاوية دوامة انتفاخ تمدد انكسار الكرة معامل الانكسار انكسار الكرة الزجاجية مصفوفة الألوان التعتيم تغيير الحجم بحدود الصورة غيّر حجم الصور المحدّدة لتتبع حدود العرض والارتفاع المحددة مع حفظ نسبة العرض إلى الارتفاع مستويات التكميم السلس تون تون تتالي مقياس المحتوى التفاف 3x3 تصفية RGB توازن الالوان عدم الحد الأقصى للقمع إدراج ضعيف للبكسل ابحث عن لون مزيف اللون الأول اللون الثاني إعادة ترتيب طمس سريع حجم التمويه مركز طمس x مركز طمس ذ زووم طمس عتبة النصوع الرسم لون الطلاء لقد عطلت تطبيق الملفات، فعّله لاستخدام هذه الميزة ارسم على صورة كما في دفتر الرسم، أو ارسم على الخلفية نفسها طلاء ألفا ارسم على صورة اختيار صورة وارسم شيئًا عليها ارسم على خلفية اختيار لون الخلفية وارسم فوقها لون الخلفية الشفرة تعمية وفك تعمية أي ملف (ليس فقط الصورة) بناءً على مختلف خوارزميات التعمية المتاحة اختر الملف تشفير فك تشفير اختر ملف للبدء فك التشفير التشفير مفتاح متابعة الملف خزّن هذا الملف على جهازك أو استخدم إجراء المشاركة لوضعه في أي مكان تريده سمات يرجى ملاحظة أن التوافق مع برامج أو خدمات تشفير الملفات الأخرى غير مضمون. قد يكون هناك اختلاف طفيف في معالجة المفتاح أو تكوين التشفير من أسباب عدم التوافق. تطبيق التوافق تشفير الملفات على أساس كلمة المرور. يمكن تخزين الملفات التي تمت متابعتها في دليل Crypto أو مشاركتها. يمكن أيضًا فتح الملفات التي تم فك تشفيرها مباشرة. AES-256، وضع GCM، بدون حشو، 12 بايت من المتجهات الأولية العشوائية افتراضيًا. يمكنك اختيار الخوارزمية المطلوبة. تُستخدم المفاتيح كقيم تجزئة SHA-3 بطول 256 بت. حجم الملف الحد الأقصى لحجم الملف مقيد بنظام التشغيل أندرويد والذاكرة المتاحة، والتي تعتمد بشكل واضح على جهازك. \nيُرجى ملاحظة: الذاكرة ليست تخزين. خيارات المجموعات على الشاشة الرئيسية من نوعها بدلاً من ترتيب القائمة المخصص أنشئ محاولة حفظ الصورة بالعرض والارتفاع المحددين قد تتسبب في حدوث خطأ في الذاكرة. قم بذلك على مسؤوليتك الخاصة. إذا فُعّل، سيتم مسح ذاكرة التخزين المؤقت للتطبيق عند بدء التشغيل أدوات لقطة شاشة خيارات المجموعة حسب النوع حجم ذاكرة التخزين المؤقت عُثر %1$s مسح ذاكرة التخزين المؤقت التلقائي لا يمكن تغيير الترتيب أثناء تمكين تجميع الخيارات حرِّر لقطة الشاشة التخصيص الثانوي الخيار الاحتياطي يتخطى ينسخ يمكن أن يكون الحفظ في وضع %1$s غير مستقر، لأنه تنسيق غير ضياع كلمة مرور غير صالحة أو الملف المختار غير مشفر مخبأ سيؤدي هذا إلى إعادة إعداداتك إلى القيم الافتراضية، لاحظ أنه لا يمكن التراجع عن هذا بدون ملف النسخ الاحتياطي من الخيار أعلاه محادثة تيليجرام قناع القص الطبيعة والحيوان الغذاء والشرب اتصل بي انسخ احتياطيًا إعدادات تطبيقك إلى الملف ملف تالف أو ليس نسخة احتياطية إذا حدّدت الإعداد المسبق 125، فسيتم حفظ الصورة بنسبة 125% من حجم الصورة الأصلية. أما إذا اخترت الإعداد المسبق 50، فسيتم حفظ الصورة بنسبة 50% من حجمها. مقياس الخط استعادة ناقش حول التطبيق واحصل على تعليقات من المستخدمين الآخرين، ويمكنك هنا أيضًا الحصول على تحديثات تجريبية ا أ إ آ ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي 0123456789 !؟ احذف نسبة الأبعاد أنت على وشك حذف نظام الألوان المحدد، لا يمكن التراجع عن هذه العملية حُفظ في المجلد %1$s اُستعيدت الإعدادات بنجاح النسخ الاحتياطي والاستعادة نسخ احتياطي المشاعر حُفظ في المجلد %1$s بالاسم %2$s استخدم هذا النوع من القناع لإنشاء قناع من صورة معينة، لاحظ أنه يجب أن يحتوي على قناة ألفا اسم عشوائي للملف النص الإفتراضي احذف المخطط الخط قد يؤدي استخدام مقاييس الخطوط الكبيرة إلى حدوث خلل في واجهة المستخدم ولن يتم إصلاحه، استخدمه إذا كنت تريد ذلك فقط إذا فُعّل اسم ملف الإخراج فسيكون عشوائيًا بشكل تام استعادة إعدادات التطبيق من الملف الذي ولّد مسبقًا يحدّد الإعداد المسبق هنا النسبة المئوية لملف الإخراج، أي إذا حددت 50 إعدادًا مسبقًا على صورة بحجم 5 ميجابايت، فستحصل على صورة بحجم 2.5 ميجابايت بعد الحفظ تعزيز البكسل بكسل الماس وضع الرسم سيتم قطع المساحات الشفافة حول الصورة مسح الخلفية تلقائيًا ماصة استعادة الخلفية سيتم الاحتفاظ بالبيانات الوصفية للصورة الأصلية التحديثات السماح بالإصدارات التجريبية الاتجاه & اكتشاف البرنامج النصي فقط التوجيه التلقائي & اكتشاف البرنامج النصي السيارات فقط آلي كتلة واحدة سطر واحد كلمة واحدة كلمة الدائرة حرف واحد اتجاه النص المتفرق & اكتشاف البرنامج النصي خط خام خلل كمية بذرة أنت على وشك حذف قناع التصفية المحددة. هذه العملية لا يمكن التراجع عنها احذف القناع لم يُعثر على دليل \"%1$s\"، لقد قمنا بتحويله إلى الدليل الافتراضي، يُرجى حفظ الملف مرة أخرى الحافظة لاستبدال الملفات، تحتاج إلى استخدام مصدر الصورة \"Explorer\"، حاول إعادة اختيار الصور، لقد قمنا بتغيير مصدر الصورة إلى المصدر المطلوب فارغ يبحث حر كُتب فوق الصور في الوجهة الأصلية أنشطة إذا فُعِّل مسار الرسم فسيتم تمثيله كسهم يشير سيتم اقتصاص الصور من المنتصف حسب الحجم الذي تم إدخاله. سيتم توسيع اللوحة القماشية بلون الخلفية المحدد إذا كانت الصورة أصغر من الأبعاد المدخلة. هبة أفقي رَأسِيّ سيتم تغيير حجم الصور الصغيرة إلى أكبر صورة في التسلسل إذا تم تمكينها رسم حواف غير واضحة أسفل الصورة الأصلية لملء الفراغات حولها بدلاً من لون واحد إذا تم تمكينه بكسل السكتة الدماغية تعزيز بكسل الماس تسامح اللون للاستبدال اللون المستهدف اللون المراد إزالته إذا فُعِّل في وضع الرسم، فلن تدور الشاشة نمط اللوحة بقعة نغمية حيادي نابض بالحياة معبرة قوس المطر سلطة فواكه الاخلاص محتوى نمط اللوحة الافتراضي، فهو يسمح بتخصيص جميع الألوان الأربعة، والبعض الآخر يسمح لك بتعيين اللون الرئيسي فقط نمط لوني أكثر قليلاً من اللون الأحادي سمة عالية، والألوان هي الحد الأقصى للوحة الأساسية، وزيادة للآخرين سمة مرحة - لا يظهر تدرج اللون المصدر في السمة سمة أحادية اللون، الألوان هي أسود / أبيض / رمادي بحت مخطط يضع اللون المصدر في Scheme.primaryContainer مخطط مشابه جدًا لنظام المحتوى عاجز كلاهما يستبدل ألوان السمة بالألوان السلبية إذا فُعّلت تمكن القدرة على البحث خلال جميع الأدوات المتاحة على الشاشة الرئيسية سيتم عرض قناع التصفية المرسوم ليظهر لك النتيجة التقريبية إذا فُعّل، فسيتم تصفية كافة المناطق غير المقنعة بدلاً من السلوك الافتراضي المتغيرات البسيطة قلم تمييز قلم طمس الخصوصية أضف بعض التأثير المتوهج إلى رسوماتك الافتراضي واحد، أبسط - فقط اللون يشبه تمويه الخصوصية، ولكنه يتقطّع بدلاً من التعتيم مفاتيح FABs أزرار رسم ظل خلف أشرطة التمرير رسم ظل خلف المفاتيح رسم ظل خلف أزرار الإجراءات العائمة رسم ظل خلف الأزرار أشرطة التطبيقات رسم ظل خلف أشرطة التطبيقات الدوران التلقائي يسمح باعتماد مربع الحد لتوجيه الصورة الرسم الحر سهم مزدوج رسم سهمًا مزدوجًا من نقطة البداية إلى نقطة النهاية كخط وضع الغرزة عدد الصفوف عدد الأعمدة دبوس تلقائي يضيف الصورة المحفوظة تلقائيًا إلى الحافظة إذا حُفظت اهتزاز قوة الاهتزاز الكتابة فوق الملفات سيتم استبدال الملف الأصلي بملف جديد بدلاً من حفظه في المجلد المحدد، ويجب أن يكون هذا الخيار هو مصدر الصورة \"Explorer\" أو GetContent، وعند تبديل هذا، سيتم تعيينه تلقائيًا لاحقة بيكوبيك خدد عادةً ما يكون الاستيفاء الخطي (أو ثنائي الخط، في بعدين) جيدًا لتغيير حجم الصورة، ولكنه يسبب بعض التخفيف غير المرغوب فيه للتفاصيل ويمكن أن يظل متعرجًا إلى حد ما تتضمن طرق القياس الأفضل إعادة أخذ العينات من Lanczos وتصفيات Mitchell-Netravali إحدى أبسط الطرق لزيادة الحجم هي استبدال كل بكسل بعدد من البكسلات من نفس اللون أبسط وضع للتحجيم على نظام Android يُستخدم في جميع التطبيقات تقريبًا طريقة لاستكمال وإعادة تشكيل مجموعة من نقاط التحكم بسلاسة، تُستخدم عادةً في رسوميات الحاسوب لإنشاء منحنيات ناعمة غالبًا ما يتم تطبيق وظيفة النوافذ في معالجة الإشارات لتقليل التسرب الطيفي وتحسين دقة تحليل التردد عن طريق تضييق حواف الإشارة تقنية الاستيفاء الرياضي التي تستخدم القيم والمشتقات عند نقاط نهاية مقطع المنحنى لتوليد منحنى سلس ومستمر طريقة إعادة التشكيل التي تحافظ على الاستيفاء عالي الجودة من خلال تطبيق دالة المزامنة الموزونة على قيم البكسل طريقة إعادة التشكيل التي تستخدم تصفية التفاف مع معلمات قابلة للتضبيط لتحقيق التوازن بين الحدة والصقل في الصورة ذات الحجم الكبير يستخدم الدوال متعددة الحدود المحددة لاستكمال وتقريب المنحنى أو السطح بسلاسة، وتمثيل الشكل المرن والمستمر لن يتم إجراء الحفظ في وحدة التخزين، وستتم محاولة وضع الصورة في الحافظة فقط لايوجد بيانات يستخدم مفتاحًا يشبه Google Pixel كُتب فوق الملف بالاسم %1$s في الوجهة الأصلية لتمكين المكبر الموجود أعلى الإصبع في أوضاع الرسم لتسهيل الوصول إليه قوة القيمة الأولية يفرض فحص عنصر واجهة المستخدم exif في البداية السماح بعدة لغات الانزلاق جنباألى جنب عمود فردي نص عمودي كتلة واحدة نص متفرق هل تريد حذف بيانات تدريب التعرف الضوئي على الحروف \"%1$s\" اللغوية لجميع أنواع التعرف، أم للنوع المحدد فقط (%2$s)؟ حاضِر الجميع أنشئ تدرج لحجم الإخراج المحدد بألوان مخصّصة ونوع المظهر المشبك صائق توقف اللون أضف اللون ملكيات قم بتغطية الصور بعلامات مائية نصية/صورة قابلة للتخصيص يكرر العلامة المائية على الصورة بدلاً من العلامة المائية في موضع معين الإزاحة X إزاحة Y نوع العلامة المائية سيتم استخدام هذه الصورة كنموذج للعلامة المائية لون الخط أدوات GIF تحويل الصور إلى صورة GIF أو استخراج الإطارات من صورة GIF معينة GIF للصور تحويل ملف GIF إلى مجموعة من الصور تحويل مجموعة من الصور إلى ملف GIF الصور إلى GIF اختر صورة GIF للبدء استخدم حجم الإطار الأول استبدل الحجم المحدد بأبعاد الإطار الأول تكرار العد تأخير الإطار ملي إطارا في الثانية قصاصات ورق ملون سيتم عرض قصاصات الورق عند الحفظ والمشاركة والإجراءات الأساسية الأخرى وضع آمن يخفي المحتوى عند الخروج، كما لا يمكن التقاط الشاشة أو تسجيلها باير ثلاثة بثلاثة التردد باير أربعة بأربعة التردد ثبات سييرا ذو صفين ثبات سييرا لايت أتكينسون تذبذب التردد الكاذب فلويد شتاينبرغ التردد من اليسار إلى اليمين ثبات عشوائي ثبات العتبة البسيطة يستخدم وظائف متعددة الحدود ثنائية التكعيبية لاستكمال وتقريب المنحنى أو السطح بسلاسة، وتمثيل الشكل المرن والمستمر طمس المكدس الأصلي النقش ضوضاء فرز البكسل خلط خلل معزز تحويل القناة X تحويل القناة Y حجم الفساد تحول الفساد X تحول الفساد Y طمس الخيمة تتلاشى الجانب جانب قمة قاع قوة السعة رخام تأثيرات بسيطة بولارويد شلل تريتونومي شلل ثنائي بروتونومالي كلاسيكي الرؤية الليلية دافيء رائع تريتانوبيا ديوتارونوتوبيا شلل لوني الأكروماتوبسيا الساعة الذهبية صيف حار نغمات الخريف المناظر الطبيعية الخيالية انفجار اللون التدرج الكهربائي كراميل الظلام التدرج المستقبلي الشمس الخضراء الكود الرقمي شكل الأيقونة دراجو الدريدج قطع اوتشيمورا موبيوس انتقال قمة شذوذ اللون لا يمكن تغيير تنسيق الصورة أثناء تمكين خيار الكتابة فوق الملفات الرموز التعبيرية كنظام ألوان يستخدم اللون الأساسي للرموز التعبيرية كنظام ألوان للتطبيق بدلاً من اللون المحدد يدويًا مزيل الخلفية تقليم الصورة وضع المسح بكسل الدائرة تعزيز بكسل الدائرة استبدال اللون أزل اللون إعادة ترميز تقلص انتشار متباين الخواص انتشار التوصيل الرياح الأفقية ترنح رسم خرائط النغمات السينمائية من ACES ACES هيل لهجة رسم الخرائط طمس ثنائي سريع بواسون طمس رسم خرائط النغمة اللوغاريتمية تبلور حلقه ملونه زجاج كسورية الاضطراب زيت تأثير الماء مقاس التردد X التردد Y السعة X السعة Y تشويه بيرلين رسم خرائط النغمات السينمائية رسم خرائط لهجة هيجل بورغيس تصفية كاملة يبدأ مركز نهاية طبّق أي سلاسل التصفية على الصور المُعطاة أو على صورة واحدة العمل مع ملفات PDF: معاينة، تحويل إلى مجموعة من الصور أو إنشاء واحدة من صور معينة معاينة PDF PDF إلى صور صور إلى PDF معاينة PDF بسيطة تحويل PDF إلى صور بتنسيق الإخراج المحدد حزم الصور المعطاة في ملف PDF الناتج صانع التدرج سرعة ديهيز أوميغا أدوات PDF قيّم التطبيق قيّم هذا التطبيق مجاني تمامًا، إذا كنت تريد أن يصبح أكبر، يُرجى تمييز المشروع بنجمة على Github 😄 مصفوفة الألوان 4x4 مصفوفة الألوان 3x3 براوني كودا كروم بروتانوبيا خطي شعاعي مسح نوع التدرج المركز العاشر مركز ي وضع البلاط معاد مرآة وضع رسم المسار سهم خط مزدوج سهم الخط رسم المسار من نقطة البداية إلى نقطة النهاية كخط سهم خط رسم المسار كقيمة إدخال رسم سهمًا يشير من نقطة البداية إلى نقطة النهاية كخط رسم سهم يشير من مسار معين رسم سهمًا مزدوجًا يشير من مسار معين البيضاوي المبين المستقيم المبين بيضاوي مستقيم رسم بشكل مستقيم من نقطة البداية إلى نقطة النهاية رسم شكلًا بيضاويًا من نقطة البداية إلى نقطة النهاية رسم شكل بيضاوي محدد من نقطة البداية إلى نقطة النهاية الرسم بشكل مستقيم من نقطة البداية إلى نقطة النهاية التردد كمية مقياس رمادي باير اثنين من اثنين التردد باير ثمانية بثمانية التردد فلويد شتاينبرغ التردد جارفيس جوديس نينكي التردد سييرا التردد ثبات ستوكي تذبذب بيركس يتيح ذلك للتطبيق جمع تقارير الأعطال يدويًا التحليلات السماح بجمع إحصائيات استخدام التطبيق بشكل مجهول لون القناع معاينة القناع وضع القياس خطين هان هيرميت لانكزوس ميتشل الأقرب أساسي القيمة الافتراضية القيمة في النطاق %1$s - %2$s سيجما سيجما المكانية طمس متوسط كاتمول كليب فقط يضيف حاوية بالشكل المحدد أسفل الأيقونات خياطة الصورة ادمج الصور المُعطاة للحصول على صورة واحدة كبيرة بريد إلكتروني إنفاذ السطوع شاشة تغطية بالتدرج اللوني إنشاء أي تدرج في أعلى الصورة المعطاة التحولات آلة تصوير يستخدم الكاميرا لالتقاط الصورة، لاحظ أنه من الممكن الحصول على صورة واحدة فقط من مصدر الصورة هذا اختر صورتين على الأقل مقياس صورة الإخراج اتجاه الصورة تحجيم الصور الصغيرة إلى كبيرة ترتيب الصور قمح ضباب برتقالي غير حاد باستيل الحلم الوردي الضباب الأرجواني شروق الشمس دوامة ملونة ضوء الربيع الناعم حلم الخزامى السايبربانك ضوء عصير الليمون النار الطيفية سحر الليل عالم قوس قزح ديب بيربل بوابة الفضاء دوامة حمراء العلامة المائية كرر العلامة المائية وضع التراكب حجم بكسل قفل اتجاه الرسم القيمة %1$s تعني ضغطًا سريعًا، مما يؤدي إلى حجم ملف كبير نسبيًا. %2$s يعني ضغطًا أبطأ، مما يؤدي إلى ملف أصغر. خوخه استخدم لاسو يستخدم Lasso كما هو الحال في وضع الرسم لإجراء المسح معاينة الصورة الأصلية ألفا تصفية القناع طبّق سلاسل التصفية على المناطق المقنعة المعطاة، حيث يمكن لكل منطقة قناع تحديد مجموعة التصفيات الخاصة بها. أضف قناع استعادة الصورة سيتم تغيير الرموز التعبيرية لشريط التطبيقات بشكل عشوائي بشكل مستمر بدلاً من استخدام الرمز المحدد الرموز التعبيرية العشوائية لا يمكن استخدام انتقاء عشوائي للرموز التعبيرية أثناء تعطيل الرموز التعبيرية لا يمكن تحديد رمز تعبيري أثناء تمكين اختيار رمز عشوائي تحقق من وجود تحديثات جهد انتظر أشياء حرف او رمز فعِّل الرموز التعبيرية الرحلات والأماكن أزِل الخلفية من الصورة عن طريق الرسم أو استخدام الخيار التلقائي محو الخلفية طمس نصف قطرها أنشئ علة عفوًا… حدث خطأ ما. يمكنك الكتابة إليَّ باستخدام الخيارات أدناه وسأحاول إيجاد حل تغيير الحجم والتحويل غيّر حجم الصور المعينة أو تحويلها إلى تنسيقات أخرى. يمكن أيضًا تحرير بيانات EXIF التعريفية هنا في حالة اختيار صورة واحدة. الحد الأقصى لعدد الألوان حاليًا، يسمح تنسيق %1$s بقراءة بيانات EXIF التعريفية فقط على نظام Android. لن تحتوي الصورة الناتجة على بيانات وصفية على الإطلاق عند حفظها. اكتمل الحفظ تقريبًا. سيتطلب الإلغاء الآن الحفظ مرة أخرى. سيتضمن التحقق من التحديث إصدارات التطبيق التجريبية إذا فُعّلت تلفزيون قديم خلط ورق اللعب طمس التعرف الضوئي على الحروف (التعرف على النص) التعرف على النص من صورة معينة، دعم أكثر من 120 لغة الصورة لا تحتوي على نص، أو أن التطبيق لم يعثر عليها Accuracy: %1$s نوع الاعتراف سريع معيار أفضل للحصول على أداء سليم لـ Tesseract OCR، يجب تنزيل بيانات التدريب الإضافية (%1$s) على جهازك. \nأتريد تنزيل %2$s بيانات؟ نزّل لا يوجد اتصال، تحقق منه وحاول مرة أخرى لتنزيل نماذج القطارات اللغات المُنزلة اللغات المتوفرة وضع التجزئة ستقوم الفرشاة باستعادة الخلفية بدلاً من مسحها الشبكة الأفقية الشبكة العمودية استخدم مفتاح البكسل تبديل اضغط الشفافية المكبر مفضل لم تتم إضافة أي تصفيات مفضلة حتى الآن ب سبلين التحول الميل عادي طمس الحواف البكسل نوع التعبئة العكسية ارسم السهام نعومة الفرشاة أقنعة قناع %d نيون ارسم مسارات تمييز حادة وشبه شفافة يطمس الصورة أسفل المسار المرسوم لتأمين أي شيء تريد إخفاءه حاويات رسم ظل خلف الحاويات المتزلجون سيتصل مدقق التحديث هذا بـ GitHub للتحقق من توفر تحديث جديد انتباه حواف باهتة عكس الألوان غادر إذا تركت المعاينة الآن، فسوف تحتاج إلى إضافة الصور مرة أخرى لاسو رسم مسارًا مغلقًا ومملوءًا بمسار معين تنسيق الصورة الألوان الداكنة يستخدم نظام ألوان الوضع الليلي بدلا من متغير الضوء نسخ كما جيتباك يؤلف التعليمات البرمجية ينشئ لوحة\"Materal You\" من الصورة خاتم الضباب دائرة الضباب نجمة الضباب ضباب متقاطع تحول الميل الخطي وسوم للإزالة ادوات APNG APNG إلى الصور تحويل ملف APNG إلى مجموعة من الصور الصور إلى APNG اختر صورة APNG للبدء ضبابية الحركة أنشئ ملف مضغوط من ملفات أو صور معينة تحويل الصور إلى صورة APNG أو استخراج الإطارات من صورة APNG معينة تحويل مجموعة من الصور إلى ملف APNG مضغوط عرض مقبض السحب نوع قصاصات الورق احتفالي الانفجار مطر الحواف قم بإجراء تحويل ترميز JXL ~ JPEG دون فقدان الجودة، أو قم بتحويل GIF/APNG إلى رسوم متحركة JXL من JXL إلى JPEG قم بإجراء تحويل بدون فقدان الترميز من JXL إلى JPEG أدوات جي إكس إل قم بإجراء تحويل بدون فقدان الترميز من JPEG الى JXL تمويه غاوسي سريع 2D طمس غاوسي سريع 3D تمويه غاوسي سريع 4D JPEG إلى JXL اختر صورة JXL للبدء اللصق التلقائي يسمح للتطبيق بلصق بيانات الحافظة تلقائيًا، بحيث تظهر على الشاشة الرئيسية وستتمكن من معالجتها وحدة تنسيق مستوى المواءمة لانكزوس بيسل طريقة إعادة التشكيل التي تحافظ على الاستيفاء عالي الجودة من خلال تطبيق دالة Bessel (jinc) على قيم البكسل GIF إلى JXL تحويل صور GIF إلى صور متحركة JXL APNG الى JXL تحويل صور APNG إلى صور متحركة JXL JXL إلى الصور تحويل الرسوم المتحركة JXL إلى مجموعة من الصور الصور إلى JXL تحويل مجموعة من الصور إلى الرسوم المتحركة JXL السلوك تخطي انتقاء الملف ولّد معاينات ضغط بيانات مع فقد (Lossy) يستخدم الضغط مع فقدان البيانات لتقليل حجم الملف بدلاً من عدم فقدانه نوع الضغط سيتم عرض منتقي الملفات على الفور إذا كان ذلك ممكنًا على الشاشة المختارة يفعّل إنشاء المعاينة، وقد يساعد هذا في تجنب الأعطال على بعض الأجهزة، كما يؤدي هذا أيضًا إلى تعطيل بعض وظائف التحرير ضمن خيار تحرير واحد يتحكم في سرعة فك تشفير الصورة الناتجة، وهذا من شأنه أن يساعد في فتح الصورة الناتجة بشكل أسرع، قيمة %1$s تعني أبطأ عملية فك تشفير، بينما %2$s - الأسرع، قد يؤدي هذا الإعداد إلى زيادة حجم الصورة الناتجة الفرز رتب حسب التاريخ رتب حسب التاريخ(معكوس) فرز حسب الاسم فرز حسب الاسم (معكوس) إعداد القنوات اليوم أمس المنتقى المضمن يستخدم منتقي الصور الخاص بـ Image Toolbox بدلاً من الصور المحددة مسبقًا في النظام لا أذونات الطلب اختر صور متعددة اختر اختر صورة واحدة عرض الإعدادات في الوضع الأفقي حاول مرة أخرى إذا تم تعطيل هذا الخيار، فسيتم فتح إعدادات الوضع الأفقي على الزر الموجود في شريط التطبيقات العلوي كما هو الحال دائمًا، بدلاً من الخيار المرئي الدائم إعدادات ملء الشاشة فعّله وسيتم فتح صفحة الإعدادات دائمًا بملء الشاشة بدلاً من ورقة الدرج القابلة للانزلاق تبديل النوع تركيب مفتاح Jetpack Compose Material You مفتاح Material You تغيير حجم الارتساء طلِق الأعلى بكسل يستخدم مفتاح Windows 11 المصمم على أساس نظام التصميم \"الطلاقة\". كوبرتينو مفتاح تبديل يعتمد على نظام التصميم \"كوبرتينو\" تتبع الصور المعطاة إلى صور SVG استخدم لوحة العينات إهمال المسار قلّص الصورة سيتم تقليص الصورة إلى أبعاد أقل قبل معالجتها، مما يساعد الأداة على العمل بشكل أسرع وأكثر أمانًا الحد الأدنى لنسبة اللون عتبة الخطوط العتبة التربيعية إحداثيات التقريب التسامح مقياس المسار صفّر الخصائص الصور إلى Svg سيتم أخذ عينات من لوحة القياس إذا فُعّل هذا الخيار لا يُنصح باستخدام هذه الأداة لتتبع الصور الكبيرة دون تقليصها، حيث يمكن أن تتسبب في حدوث إنهيار وزيادة وقت المعالجة سيصفّر كافة الخصائص إلى القيم الافتراضية، لاحظ أنه لا يمكن التراجع عن هذا الإجراء ‬مفصلة عرض الخط الافتراضي وضع المحرك قياسي شبكة LSTM قياسي & LSTM تحويل تحويل دفعات الصور إلى تنسيق معين أضف مجلد جديد الضغط التفسير الضوئي عينات لكل بكسل التكوين المستوي Y Cb Cr أخذ العينات الفرعية Y Cb Cr لتحديد المواقع القرار العاشر القرار ص بت لكل عينة التاريخ والوقت مصنّع وصف الصورة نموذج برمجية حقوق النشر فنان إصدار Flashpix إصدار Exif ارسم النص علي المسار بالخط المُعطى واللون وحدة الدقة نقطة بيضاء جاما تعليق المستخدم ملف الصوت المرتبط الوقت والتاريخ الأصليين حجم الخط حجم العلامة المائية تكرار النص إزالة الإزاحات الصفوف لكل قطاع تجريد عدد البايتات تنسيق التبادل JPEG طول تنسيق التبادل JPEG وظيفة النقل اللونيات الأولية معاملات Y Cb Cr مرجعية الاسود الابيض نطاق الألوان بكسل × البعد بكسل بُعد ص البت المضغوطة لكل بكسل ملاحظة الصانع الوقت والتاريخ المرقم وقت الإزاحة وقت الإزاحة الأصلي وقت الإزاحة المرقم الوقت الفرعي ثانية الوقت الفرعي للثانية الأصلي وقت فرعي رقمي وقت التعرض رقم ف برنامج التعرض الحساسية الطيفية حساسية التصوير الفوتوغرافي منظمة التعاون الاقتصادي نوع الحساسية حساسية الإخراج القياسية مؤشر التعرض الموصى به سرعة الأيزو خط عرض سرعة ISO yyy سرعة ISO خط العرض zzz قيمة سرعة الغالق قيمة الفتحة قيمة السطوع قيمة انحياز التعرض قيمة الفتحة القصوى مسافة الموضوع وضع القياس فلاش مجال الموضوع البعد البؤري طاقة الفلاش استجابة التردد المكاني المستوى البؤري X القرار المستوى البؤري Y القرار وحدة تحليل المستوى البؤري موقع الموضوع مؤشر التعرض طريقة الاستشعار مصدر الملف نمط CFA المقدمة المخصصة وضع التعريض توازن اللون الأبيض نسبة التكبير الرقمي البعد البؤري في فيلم 35 مم نوع التقاط المشهد السيطرة على المكاسب مقابلة التشبع حدة وصف إعدادات الجهاز نطاق مسافة الموضوع معرف الصورة الفريد اسم مالك الكاميرا الرقم التسلسلي للجسم مواصفات العدسة صنع العدسة نموذج العدسة الرقم التسلسلي للعدسة معرف إصدار نظام تحديد المواقع العالمي (GPS). مرجع خط العرض GPS خط العرض GPS المرجع خط الطول لنظام تحديد المواقع العالمي (GPS). خط الطول لنظام تحديد المواقع العالمي (GPS). مرجع ارتفاع نظام تحديد المواقع العالمي (GPS). ارتفاع نظام تحديد المواقع الطابع الزمني لنظام تحديد المواقع العالمي (GPS). الأقمار الصناعية لتحديد المواقع حالة نظام تحديد المواقع وضع قياس نظام تحديد المواقع نظام تحديد المواقع دوب مرجع سرعة GPS سرعة نظام تحديد المواقع مرجع مسار نظام تحديد المواقع العالمي (GPS). مسار نظام تحديد المواقع المرجع اتجاه صورة GPS اتجاه صورة GPS مسند خريطة GPS المرجع لخط العرض الوجهة لنظام تحديد المواقع العالمي (GPS). GPS الوجهة العرض GPS الوجهة خط الطول المرجع GPS خط الطول الوجهة المرجع المرجعي للوجهة بنظام تحديد المواقع العالمي (GPS). نظام تحديد المواقع العالمي (GPS) لتحديد الوجهة مرجع مسافة الوجهة GPS مسافة الوجهة بنظام تحديد المواقع العالمي (GPS). طريقة معالجة نظام تحديد المواقع معلومات منطقة GPS ختم تاريخ نظام تحديد المواقع نظام تحديد المواقع التفاضلي خطأ في تحديد موضع GPS H مؤشر التشغيل البيني نسخة دي إن جي حجم المحاصيل الافتراضي بداية معاينة الصورة معاينة طول الصورة إطار الجانب الحد السفلي لجهاز الاستشعار استشعار الحدود اليسرى استشعار الحدود اليمنى الحد العلوي لجهاز الاستشعار ISO سيتم تكرار النص الحالي حتى نهاية المسار بدلاً من الرسم مرة واحدة حجم داش استخدم الصورة المحددة لرسمها على طول المسار المحدد سيتم استخدام هذه الصورة كمدخل متكرر للمسار المرسوم رسم مثلث محدد من نقطة البداية إلى نقطة النهاية رسم مثلث محدد من نقطة البداية إلى نقطة النهاية المثلث المبين مثلث يرسم المضلع من نقطة البداية إلى نقطة النهاية مضلع المضلع المحدد يرسم المضلع المحدد من نقطة البداية إلى نقطة النهاية القمم ارسم مضلعًا منتظمًا ارسم المضلع الذي سيكون منتظمًا بدلًا من الشكل الحر يرسم النجمة من نقطة البداية إلى نقطة النهاية نجم النجمة المبينة رسم النجمة المحددة من نقطة البداية إلى نقطة النهاية نسبة نصف القطر الداخلي ارسم نجمة عادية ارسم النجمة التي ستكون منتظمة بدلاً من الشكل الحر الحواف يفعّل الحواف لمنع الحواف الحادة افتح تحرير بدلاً من المعاينة عند تحديد صورة لفتحها (معاينة) في ImageToolbox، سيتم فتح ورقة تحديد التحرير بدلاً من المعاينة ماسح ضوئي للمستندات امسح المستندات ضوئيًا وإنشاء ملف PDF أو افصل الصور عنها انقر لبدء المسح ابدأ المسح احفظ ك PDF شارك ك PDF الخيارات أدناه مخصصة لحفظ الصور، وليس PDF معادلة الرسم البياني HSV معادلة الرسم البياني أدخل النسبة المئوية السماح بالدخول عن طريق حقل النص يفعّل حقل النص خلف تحديد الإعدادات المسبقة، لإدخالها بسرعة مقياس مساحة اللون خطي معادلة بكسل الرسم البياني حجم الشبكة X حجم الشبكة Y معادلة الرسم البياني التكيفي معادلة الرسم البياني التكيفي LUV معادلة الرسم البياني التكيفي LAB كلاهي مختبر كلاهي كلاهي لوف الاقتصاص إلى المحتوى لون الإطار اللون الذي يجب تجاهله نموذج لم تتم إضافة تصفيات القالب أنشئ جديد رمز QR الممسوح ضوئيًا ليس قالب تصفية صالحًا امسح رمز QR الملف المحدد لا يحتوي على بيانات قالب التصفية أنشئ قالب اسم القالب سيتم استخدام هذه الصورة لمعاينة قالب التصفية هذا تصفية القالب كصورة رمز الاستجابة السريعة كملف احفظ كملف احفظ كصورة رمز QR احذف القالب أنت على وشك حذف عامل تصفية القالب المحدد. لا يمكن التراجع عن هذه العملية أُضيف قالب تصفية بالاسم \"%1$s\" (%2$s) معاينة التصفية QR والرمز الشريطي امسح رمز QR ضوئيًا واحصل على محتواه أو الصق سلسلتك لتوليد واحدة جديدة محتوى الكود امسح أي رمز شريطي لاستبدال المحتوى الموجود في الحقل، أو اكتب شيئًا لتوليد رمز شريطي جديد بالنوع المحدد وصف QR دقيقة امنح إذن الكاميرا في الإعدادات لمسح رمز QR امنح إذن الكاميرا في الإعدادات لمسح Document Scanner مكعب ب-الخط هامينج هانينج بلاكمان ولش رباعي غاوسي أبو الهول بارتليت روبيدوكس روبيدو شارب الخط 16 الخط 36 الخط 64 كايزر بارتليت-هي صندوق بوهمان لانكزوس 2 لانكزوس 3 لانكزوس 4 لانكزوس 2 جينك لانكزوس 3 جينك لانكزوس 4 جينك يوفر الاستيفاء المكعب تحجيمًا أكثر سلاسة من خلال النظر في أقرب 16 بكسل، مما يعطي نتائج أفضل من الخطين يستخدم الدوال متعددة الحدود المحددة لاستكمال وتقريب المنحنى أو السطح بسلاسة، وتمثيل الشكل المرن والمستمر وظيفة نافذة تستخدم لتقليل التسرب الطيفي عن طريق تضييق حواف الإشارة، وهي مفيدة في معالجة الإشارة أحد أشكال نافذة هان، يُستخدم عادةً لتقليل التسرب الطيفي في تطبيقات معالجة الإشارات وظيفة نافذة توفر دقة تردد جيدة عن طريق تقليل التسرب الطيفي، وغالبًا ما تستخدم في معالجة الإشارات وظيفة نافذة مصممة لتوفير دقة تردد جيدة مع تقليل التسرب الطيفي، وغالبًا ما تستخدم في تطبيقات معالجة الإشارات طريقة تستخدم الدالة التربيعية للاستيفاء، مما يوفر نتائج سلسة ومستمرة طريقة استيفاء تطبق دالة غاوسية، وهي مفيدة لتنعيم الصور وتقليل التشويش فيها طريقة متقدمة لإعادة التشكيل توفر استيفاءً عالي الجودة مع الحد الأدنى من القطع الأثرية وظيفة نافذة مثلثة تستخدم في معالجة الإشارات لتقليل التسرب الطيفي طريقة استيفاء عالية الجودة مُحسّنة لتغيير الحجم الطبيعي للصورة، وموازنة الحدة والنعومة نسخة أكثر وضوحًا من طريقة Robidoux، مُحسّنة لتغيير حجم الصورة بوضوح طريقة استيفاء قائمة على الشريحة توفر نتائج سلسة باستخدام تصفية ذو 16 نقرة طريقة استيفاء قائمة على الشريحة توفر نتائج سلسة باستخدام تصفية 36 نقرة طريقة استيفاء قائمة على الشريحة توفر نتائج سلسة باستخدام تصفية 64 نقرة طريقة استيفاء تستخدم نافذة كايزر، مما يوفر تحكمًا جيدًا في المفاضلة بين عرض الفص الرئيسي ومستوى الفص الجانبي وظيفة نافذة هجينة تجمع بين نوافذ بارتليت وهان، تستخدم لتقليل التسرب الطيفي في معالجة الإشارات طريقة بسيطة لإعادة التشكيل تستخدم متوسط قيم أقرب بكسل، مما يؤدي غالبًا إلى ظهور ممتلئ وظيفة نافذة تستخدم لتقليل التسرب الطيفي، مما يوفر دقة تردد جيدة في تطبيقات معالجة الإشارات طريقة إعادة أخذ العينات تستخدم تصفية Lanczos ثنائي الفص للحصول على استيفاء عالي الجودة مع الحد الأدنى من الشوائب طريقة إعادة أخذ العينات تستخدم تصفية Lanczos ثلاثي الفصوص للحصول على استيفاء عالي الجودة مع الحد الأدنى من الشوائب طريقة إعادة أخذ العينات تستخدم تصفية Lanczos رباعي الفصوص للحصول على استيفاء عالي الجودة مع الحد الأدنى من الشوائب نوع مختلف من تصفية Lanczos 2 الذي يستخدم وظيفة jinc، مما يوفر استيفاءً عالي الجودة مع الحد الأدنى من الشوائب نوع مختلف من تصفية Lanczos 3 الذي يستخدم وظيفة jinc، مما يوفر استيفاءً عالي الجودة مع الحد الأدنى من الشوائب نوع مختلف من تصفية Lanczos 4 الذي يستخدم وظيفة jinc، مما يوفر استيفاءً عالي الجودة مع الحد الأدنى من الشوائب هانينغ إيوا نوع المتوسط المرجح الإهليجي (EWA) من تصفية هانينغ للاستيفاء وإعادة التشكيل السلس روبيدو إيوا نوع المتوسط المرجح الإهليجي (EWA) من تصفية Robidoux لإعادة التشكيل عالية الجودة بلاكمان إيف نوع المتوسط المرجح البيضوي (EWA) من تصفية بلاكمان لتقليل التشويش الحلقي رباعية EWA نوع المتوسط المرجح الإهليجي (EWA) من مرشح الرباعي للاستيفاء السلس روبيدو شارب إيوا نوع المتوسط المرجح الإهليجي (EWA) من مرشح Robidoux Sharp للحصول على نتائج أكثر وضوحًا لانكزوس 3 جينك إيوا نوع المتوسط المرجح الإهليلجي (EWA) من مرشح جينك لانكوس 3 لإعادة التشكيل عالي الجودة مع تقليل التشويه الطيفي الجينسنغ مرشح إعادة التشكيل مصمم لمعالجة الصور عالية الجودة مع توازن جيد بين الحدة والنعومة الجينسنغ إيوا نظام التصفية Ginseng المحسّن باستخدام المتوسط المرجح الإهليجي (EWA) لتحسين جودة الصورة لانكزوس شارب إيوا النسخة المتوسطة المرجحة الإهليلجية (EWA) من مرشح لانكزوس شارب لتحقيق نتائج حادة بأقل قدر من التشوهات لانكزوس 4 شاربست إيوا الإصدار EWA (المتوسط المرجح الإهليجي) من مرشح Lanczos 4 الأكثر حدة لإعادة تشكيل الصورة بدقة فائقة لانكزوس سوفت إيوا نوع المتوسط المرجح الإهليلجي (EWA) من مرشح لانكزوس الناعم لإعادة تشكيل الصورة بشكل أكثر سلاسة حسن سوفت مرشح إعادة تشكيل صممه Haasn لتحجيم الصورة بشكل سلس وخالي من الشوائب تحويل التنسيق تحويل مجموعة من الصور من تنسيق إلى آخر أهمِل إلى الأبد تكديس الصور كدِّس الصور فوق بعضها البعض باستخدام أوضاع المزج المختارة أضف صورة عدد الصناديق كلاهي HSL كلاش HSV معادلة الرسم البياني التكيفي HSL معادلة الرسم البياني التكيفي HSV وضع الحافة مقطع طَوّق عمى الألوان حدّد السمة لتكييف ألوان السمة لمتغير عمى الألوان المحدّد صعوبة التمييز بين اللونين الأحمر والأخضر صعوبة التمييز بين اللونين الأخضر والأحمر صعوبة التمييز بين اللونين الأزرق والأصفر - عدم القدرة على إدراك الألوان الحمراء - عدم القدرة على إدراك الألوان الخضراء - عدم القدرة على إدراك الألوان الزرقاء انخفاض الحساسية لجميع الألوان عمى الألوان الكامل، حيث لا يرى سوى درجات اللون الرمادي لا تستخدم نظام Color Blind ستكون الألوان تمامًا كما هي محدّدة في السمة السيني لاغرانج 2 مرشح استيفاء لاغرانج من الرتبة 2، مناسب لقياس الصور عالي الجودة مع انتقالات سلسة لاغرانج 3 مرشح استيفاء لاغرانج من الدرجة 3، يوفر دقة أفضل ونتائج أكثر سلاسة لقياس الصورة لانكزوس 6 مرشح Lanczos لإعادة التشكيل بترتيب أعلى يبلغ 6، مما يوفر تحجيمًا أكثر وضوحًا ودقة للصورة لانكزوس 6 جينك متغير من مرشح Lanczos 6 يستخدم وظيفة Jinc لتحسين جودة إعادة تشكيل الصورة طمس المربع الخطي طمس الخيمة الخطية طمس مربع غاوسي الخطي طمس المكدس الخطي طمس مربع غاوسي تمويه غاوسي سريع خطي التالي طمس غاوسي خطي سريع طمس غاوسي الخطي اختر مرشحًا واحدًا لاستخدامه كطلاء استبدال الفلتر اختر الفلتر أدناه لاستخدامه كفرشاة في الرسم نظام ضغط TIFF بولي منخفض الرسم بالرمل تقسيم الصورة تقسيم صورة واحدة حسب الصفوف أو الأعمدة صالح للحدود ادمج وضع تغيير حجم الاقتصاص مع هذه المعلمة لتحقيق السلوك المطلوب (اقتصاص/ملاءمة نسبة العرض إلى الارتفاع) استوردت اللغات بنجاح النسخ الاحتياطي لنماذج التعرف الضوئي على الحروف استورد صدِّر موضع مركز أعلى اليسار أعلى اليمين أسفل اليسار أسفل اليمين أعلى المركز مركز اليمين مركز القاع وسط اليسار الصورة المستهدفة نقل لوحة النفط المعزز تلفزيون قديم بسيط تقرير التنمية البشرية جوثام رسم بسيط توهج ناعم ملصق ملون ثلاثي النغمة اللون الثالث كلاهي أوكلاب كلارا أولش كلاهي جازبز المنقط تذبذب متجمع 2x2 تذبذب سيارات الدفع الرباعي المتجمعة تذبذب متجمع 8 × 8 تذبذب ييليلوما لم يتم تحديد خيارات مفضلة، أضفهم في صفحة الأدوات أضف التفضيلات تكميلية مماثل ثلاثي سبليت التكميلية رباعي مربع مماثل + مكمل أدوات اللون يمكنك المزج وإنشاء النغمات وتوليد الظلال والمزيد تناغمات الألوان تظليل اللون تفاوت الصبغات نغمات ظلال خلط الألوان معلومات اللون اللون المحدد اللون للمزج لا يمكن استخدام monet أثناء تشغيل الألوان الديناميكية 512 × 512 جدول ثنائي الأبعاد صورة الهدف LUT أحد الهواة ملكة جمال الآداب الأناقة الناعمة البديل الناعم للأناقة متغير نقل اللوحة 3D لوط الهدف ملف LUT ثلاثي الأبعاد (.cube / .CUBE) طرفية المستعملين المحليين تجاوز التبييض ضوء الشموع إسقاط البلوز منفعل العنبر ألوان الخريف مخزون الفيلم 50 ليلة ضبابية كوداك احصل على صورة LUT محايدة أولاً، استخدم تطبيق تحرير الصور المفضل لديك لتطبيق مرشح على LUT المحايد والذي يمكنك الحصول عليه هنا. لكي يعمل هذا بشكل صحيح، يجب ألا يعتمد كل لون بكسل على وحدات بكسل أخرى (على سبيل المثال، لن يعمل التمويه). بمجرد أن تصبح جاهزًا، استخدم صورة LUT الجديدة كمدخل لمرشح LUT 512*512 فن البوب شريط سينمائي قهوة الغابة الذهبية مخضر الرجعية الأصفر معاينة الروابط تمكين استرداد معاينة الرابط في الأماكن التي يمكنك فيها الحصول على النص (QRCode، OCR، إلخ) روابط لا يمكن حفظ ملفات ICO إلا بالحجم الأقصى وهو 256 × 256 GIF إلى WEBP تحويل صور GIF إلى صور متحركة WEBP أدوات ويب تحويل الصور إلى صورة متحركة WEBP أو استخراج الإطارات من الرسوم المتحركة WEBP معينة WEBP للصور تحويل ملف WEBP إلى مجموعة من الصور تحويل مجموعة من الصور إلى ملف WEBP الصور إلى WEBP اختر صورة WEBP للبدء لا يوجد وصول كامل إلى الملفات السماح لجميع الملفات بالوصول لرؤية JXL وQOI والصور الأخرى التي لم يتم التعرف عليها كصور على Android. بدون إذن، يتعذر على Image Toolbox عرض تلك الصور لون الرسم الافتراضي وضع رسم المسار الافتراضي أضف الطابع الزمني يفعّل إضافة الطابع الزمني إلى اسم ملف الإخراج الطابع الزمني المنسق فعِّل تنسيق الطابع الزمني في اسم ملف الإخراج بدلاً من الميلي الأساسي فعّل الطوابع الزمنية لتحديد تنسيقها حفظ الموقع مرة واحدة اعرض وحرِّر مواقع الحفظ مرة واحدة والتي يمكنك استخدامها بالضغط لفترة طويلة على زر الحفظ في جميع الخيارات في الغالب المستخدمة مؤخرا قناة سي.اي مجموعة صندوق أدوات الصور في Telegram 🎉 انضم إلى محادثتنا حيث يمكنك مناقشة أي شيء تريده وكذلك الاطلاع على قناة CI حيث أنشر الإصدارات التجريبية والإعلانات احصل على إشعارات بشأن الإصدارات الجديدة من التطبيق، واقرأ الإعلانات لائم الصورة مع أبعاد معينة وطبّق التمويه أو اللون على الخلفية ترتيب الأدوات أدوات المجموعة حسب النوع تجميع الأدوات على الشاشة الرئيسية حسب نوعها بدلاً من ترتيب القائمة المخصصة القيم الافتراضية رؤية أشرطة النظام إظهار أشرطة النظام عن طريق السحب يفعّل الضرب لإظهار أشرطة النظام إذا كانت مخفية آلي إخفاء الكل إظهار الكل إخفاء شريط التنقل إخفاء شريط الحالة توليد الضوضاء ولّد أصوات مختلفة مثل بيرلين أو أنواع أخرى تكرار نوع الضوضاء نوع التناوب نوع كسورية أوكتافات نقص يكسب القوة المرجحة قوة بينج بونج وظيفة المسافة نوع الإرجاع غضب تشوه المجال تنسيق اسم الملف المخصص حدد الموقع واسم الملف الذي سيتم استخدامه لحفظ الصورة الحالية حُفظ في المجلد باسم مخصّص صانع الكولاج اصنع صور مجمّعة من ما يصل إلى 20 صور نوع الكولاج امسك الصورة للتبديل والتحريك والتكبير لضبط الموضع تعطيل التدوير يمنع تدوير الصور بإيماءات بإصبعين فعّل الانطباق على الحدود بعد النقل أو التكبير/التصغير، سيتم التقاط الصور لملء حواف الإطار الرسم البياني الرسم البياني للصورة RGB أو السطوع لمساعدتك في إجراء التضبيطات سيتم استخدام هذه الصورة لتوليد رسوم بيانية RGB والسطوع خيارات تسراكت تطبيق بعض متغيرات الإدخال لمحرك tesseract خيارات مخصصة يجب إدخال الخيارات باتباع هذا النمط: \"--{option_name} {value}\" الاقتصاص التلقائي زوايا حرة قص الصورة حسب المضلع، وهذا أيضًا يصحح المنظور نقاط الإكراه إلى حدود الصورة لن تكون النقاط محدودة بحدود الصورة، وهذا مفيد لتصحيح المنظور بشكل أكثر دقة قناع ملء علم المحتوى تحت المسار المرسوم شفاء بقعة استخدم نواة الدائرة يفتح إغلاق التدرج المورفولوجي القبعة العلوية القبعة السوداء منحنيات النغمة صفّر المنحنيات سيتم إرجاع المنحنيات إلى القيمة الافتراضية نمط الخط حجم الفجوة متقطع نقطة متقطعة مختوم متعرج يرسم خطًا متقطعًا على طول المسار المرسوم بحجم فجوة محدد رسم نقطة وخط متقطع على طول المسار المحدد مجرد خطوط مستقيمة افتراضية يرسم الأشكال المحددة على طول المسار بمسافة محددة يرسم خطًا متعرجًا متموجًا على طول المسار نسبة متعرجة أنشئ اختصار اختر أداة لتثبيتها ستتم إضافة الأداة إلى الشاشة الرئيسية للمشغل الخاص بك كاختصار، استخدمها مع إعداد \"تخطي انتقاء الملفات\" لتحقيق السلوك المطلوب لا تكدس الإطارات يتيح التخلص من الإطارات السابقة، بحيث لا تتكدس فوق بعضها البعض التلاشي المتقاطع سيتم تداخل الإطارات مع بعضها البعض عدد الإطارات المتداخلة العتبة الأولى العتبة الثانية حكيم مرآة 101 تعزيز طمس التكبير لابلاس بسيط سوبيل بسيط الشبكة المساعدة يُظهر الشبكة الداعمة أعلى منطقة الرسم للمساعدة في المعالجة الدقيقة لون الشبكة عرض الخلية ارتفاع الخلية محددات مدمجة ستستخدم بعض عناصر التحكم في التحديد تخطيطًا مضغوطًا لشغل مساحة أقل منح إذن الكاميرا في الإعدادات لالتقاط الصورة تَخطِيط عنوان الشاشة الرئيسية عامل المعدل الثابت (CRF) القيمة %1$s تعني ضغطًا بطيئًا، مما يؤدي إلى حجم ملف صغير نسبيًا. %2$s يعني ضغطًا أسرع، مما يؤدي إلى ملف كبير. مكتبة لوط نزّل مجموعة LUTs، والتي يمكنك تطبيقها بعد التنزيل حدِّث مجموعة جداول البحث (سيتم وضع العناصر الجديدة فقط في قائمة الانتظار)، والتي يمكنك تطبيقها بعد التنزيل غيّر معاينة الصورة الافتراضية للتصفيات معاينة الصورة يخفي يعرض نوع المنزلق باهِظ المادة 2 منزلق ذو مظهر فاخر. هذا هو الخيار الافتراضي شريط تمرير المادة 2 مادة أنت منزلق يتقدم أزرار الحوار المركزية سيتم وضع أزرار الحوار في المنتصف بدلاً من الجانب الأيسر إن أمكن تراخيص مفتوحة المصدر عرض تراخيص المكتبات مفتوحة المصدر المستخدمة في هذا التطبيق منطقة إعادة التشكيل باستخدام علاقة منطقة البكسل. قد تكون هذه هي الطريقة المفضلة لتدمير الصورة، لأنها تعطي نتائج خالية من التموج في النسيج. ولكن عندما يتم تكبير الصورة، فهي تشبه طريقة \"الأقرب\". فعّل تعيين النغمات يدخل ٪ لا يمكن الوصول إلى الموقع، حاول استخدام VPN أو تحقق مما إذا كان عنوان URL صحيحًا طبقات العلامات وضع الطبقات مع القدرة على وضع الصور والنصوص والمزيد بحرية حرِّر الطبقة طبقات على الصورة استخدم صورة كخلفية وأضف طبقات مختلفة فوقها طبقات على الخلفية نفس الخيار الأول ولكن مع اللون بدلاً من الصورة تجريبي جانب الإعدادات السريعة أضف شريط عائم على الجانب المحدد أثناء تحرير الصور، مما سيؤدي إلى فتح الإعدادات السريعة عند النقر عليها امحُ التحديد سيتم طي مجموعة الإعدادات \"%1$s\" بشكل افتراضي سيتم توسيع مجموعة الإعدادات \"%1$s\" بشكل افتراضي أدوات Base64 فك ترميز سلسلة Base64 إلى صورة، أو رمّز الصورة إلى تنسيق Base64 قاعدة64 القيمة المقدمة ليست سلسلة Base64 صالحة لا يمكن نسخ سلسلة Base64 فارغة أو غير صالحة لصق Base64 نسخ Base64 حمّل الصورة لنسخ أو حفظ سلسلة Base64. إذا كان لديك السلسلة نفسها، يمكنك لصقها أعلاه للحصول على الصورة احفظ Base64 شارك Base64 خيارات الإجراءات استورد Base64 إجراءات Base64 أضف مخطط تفصيلي أضف مخططًا تفصيليًا حول النص باللون والعرض المحددين لون المخطط التفصيلي حجم المخطط التفصيلي تناوب المجموع الاختباري كاسم الملف سيكون للصور الناتجة اسم يتوافق مع المجموع الاختباري لبياناتها البرمجيات الحرة (شريك) المزيد من البرامج المفيدة في القناة الشريكة لتطبيقات Android خوارزمية أدوات المجموع الاختباري قارن المجاميع الاختبارية أو احسب التجزئة أو أنشئ سلاسل سداسية عشرية من الملفات باستخدام خوارزميات تجزئة مختلفة احسب تجزئة النص المجموع الاختباري اختر الملف لحساب المجموع الاختباري الخاص به بناءً على الخوارزمية المحددة أدخل النص لحساب المجموع الاختباري الخاص به بناءً على الخوارزمية المحددة المجموع الاختباري للمصدر المجموع الاختباري للمقارنة مباراة! اختلاف المجاميع الاختبارية متساوية، ويمكن أن تكون آمنة المجاميع الاختبارية ليست متساوية، يمكن أن يكون الملف غير آمن! التدرجات شبكة انظر إلى مجموعة Mesh Gradients عبر الإنترنت يمكن استيراد خطوط TTF وOTF فقط استورد خط (TTF/OTF) صدِّر الخطوط الخطوط المستوردة حدث خطأ أثناء حفظ المحاولة، حاول تغيير مجلد الإخراج لم يتم تعيين اسم الملف لا أحد الصفحات المخصصة اختيار الصفحات تأكيد خروج الأداة إذا كانت لديك تغييرات غير محفوظة أثناء استخدام أدوات معينة وحاولت إغلاقها، فسيتم عرض مربع حوار التأكيد حرِّر EXIF غيّر البيانات الوصفية لصورة واحدة دون إعادة الضغط انقر لتحرير العلامات المتاحة غيّر الملصق عرض مناسب الارتفاع المناسب دفعة مقارنة اختر الملف/الملفات لحساب المجموع الاختباري الخاص بها بناءً على الخوارزمية المحددة اختر الملفات اختر الدليل مقياس طول الرأس ختم الطابع الزمني نمط التنسيق حشوة قطع الصور قص جزء من الصورة ودمج الأجزاء اليسرى (يمكن أن تكون معكوسة) بخطوط رأسية أو أفقية الخط المحوري العمودي الخط المحوري الأفقي الاختيار العكسي سيتم ترك الجزء المقطوع عموديًا، بدلاً من دمج الأجزاء حول منطقة القطع سيتم ترك الجزء المقطوع أفقيًا، بدلاً من دمج الأجزاء حول منطقة القطع مجموعة من التدرجات شبكة أنشئ تدرجًا شبكيًا بكمية مخصصة من العقد والدقة شبكة تراكب التدرج إنشاء تدرج شبكي أعلى الصور المعطاة تخصيص النقاط حجم الشبكة القرار العاشر القرار ص دقة بكسل بواسطة بكسل تسليط الضوء على اللون نوع مقارنة البكسل امسح الرمز الشريطي نسبة الارتفاع نوع الرمز الشريطي فرض B/W ستكون صورة الرمز الشريطي باللونين الأبيض والأسود بالكامل ولن يتم تلوينها حسب سمة التطبيق امسح أي رمز شريطي (QR، EAN، AZTEC، …) واحصل على محتواه أو الصق نصك لتوليد واحد جديد لم يُعثر على رمز الشريطي سيكون الرمز الشريطي المولّد هنا أغلفة الصوت استخرج صور غلاف الألبوم من الملفات الصوتية، ويدعم معظم التنسيقات الشائعة اختر الصوت للبدء اختر الصوت لم يتم العثور على أي أغطية إرسال السجلات انقر لمشاركة ملف سجلات التطبيق، حيث يمكن أن يساعدني هذا في اكتشاف المشاكل وإصلاحها عفوًا… حدث خطأ ما يمكنك الاتصال بي باستخدام الخيارات أدناه وسأحاول إيجاد حل.\n(لا تنس إرفاق السجلات) الكتابة إلى الملف استخرج النص من مجموعة الصور وخزنه في ملف نصي واحد الكتابة إلى بيانات التعريف استخرج النص من كل صورة وضعه في معلومات EXIF للصور ذات الصلة الوضع غير المرئي استخدم إخفاء المعلومات لإنشاء علامات مائية غير مرئية بالعين داخل وحدات البايت من صورك استخدم إل إس بي سيتم استخدام طريقة إخفاء المعلومات LSB (بت أقل أهمية)، وإلا FD (مجال التردد) أزل العيون الحمراء تلقائيًا كلمة المرور فتح PDF محمي اكتملت العملية تقريبًا. سيتطلب الإلغاء الآن إعادة تشغيله تاريخ التعديل تاريخ التعديل (معكوس) مقاس الحجم (معكوس) نوع MIME نوع MIME (معكوس) امتداد تمديد (عكس) تاريخ الإضافة تاريخ الإضافة (معكوس) من اليسار إلى اليمين من اليمين إلى اليسار من الأعلى إلى الأسفل من الأسفل إلى الأعلى الزجاج السائل مفتاح يعتمد على نظام التشغيل IOS 26 الذي تم الإعلان عنه مؤخرًا ونظام تصميم الزجاج السائل الخاص به اختر صورة أو ألصِق/استورد بيانات Base64 أدناه اكتب رابط الصورة للبدء لصق الرابط المشكال زاوية ثانوية الجانبين مزيج القناة أزرق أخضر أحمر أزرق أحمر أخضر إلى اللون الأحمر إلى اللون الأخضر إلى اللون الأزرق سماوي أرجواني أصفر اللون النصفي كفاف المستويات إزاحة تبلور فورونوي شكل تمتد العشوائية إزالة البقع منتشر كلب نصف القطر الثاني معادلة يشع دوامة وقرصة بوينتيليزي لون الحدود الإحداثيات القطبية المستقيم إلى القطبية القطبية لتصحيح عكس في الدائرة تقليل الضوضاء سولاريز بسيط نسج X الفجوة Y الفجوة عرض X عرض ص برم ختم مطاطي تشويه كثافة مزج تشويه عدسة المجال مؤشر الانكسار قوس زاوية الانتشار التألق أشعة أسكي التدرج ماري خريف عظم جيت شتاء محيط صيف ربيع البديل بارد HSV لون القرنفل حار كلمة الصهارة جحيم بلازما فيريديس المواطنين الشفق تحول الشفق منظور تلقائي منحرف السماح بالاقتصاص المحاصيل أو المنظور مطلق توربيني أخضر عميق تصحيح العدسة ملف تعريف العدسة المستهدفة بتنسيق JSON نزّل ملفات تعريف العدسة الجاهزة النسب المئوية للجزء صدِّر كـ JSON انسخ السلسلة باستخدام بيانات لوحة الألوان كتمثيل json نحت التماس الشاشة الرئيسية قفل الشاشة مدمج تصدير الخلفيات أنعِش احصل على خلفيات الصفحة الرئيسية والقفل والخلفيات المدمجة الحالية السماح بالوصول إلى جميع الملفات، وهذا ضروري لاسترداد الخلفيات إدارة إذن التخزين الخارجي ليست كافية، فأنت بحاجة إلى السماح بالوصول إلى صورك، وتأكد من تحديد \"السماح للجميع\" أضف الإعداد المسبق إلى اسم الملف إلحاق لاحقة بالإعداد المسبق المحدد لاسم ملف الصورة أضف وضع مقياس الصورة إلى اسم الملف إلحاق لاحقة مع وضع مقياس الصورة المحدد لاسم ملف الصورة فن أسكي (ASCII) تحويل الصورة إلى نص ASCII الذي سيبدو مثل الصورة بارامس يتم تطبيق مرشح سلبي على الصورة للحصول على نتيجة أفضل في بعض الحالات معالجة لقطة الشاشة لم يتم التقاط لقطة الشاشة، حاول مرة أخرى تم تخطي الحفظ تم تخطي %1$s الملفات السماح بالتخطي إذا كان أكبر سيتم السماح لبعض الأدوات بتخطي حفظ الصور إذا كان حجم الملف الناتج أكبر من الحجم الأصلي حدث التقويم اتصال بريد إلكتروني موقع هاتف نص رسالة قصيرة عنوان URL Wi-Fi شبكة مفتوحة لا يوجد SSID هاتف رسالة عنوان موضوع جسم اسم منظمة عنوان الهواتف رسائل البريد الإلكتروني عناوين URL العناوين ملخص وصف موقع منظم تاريخ البدء تاريخ الانتهاء حالة خط العرض خط الطول أنشئ رمز شريطي حرِّر الرمز شريطي تكوين واي فاي حماية اختر جهة الاتصال منح جهات الاتصال الإذن في الإعدادات للملء التلقائي باستخدام جهة الاتصال المحددة معلومات الاتصال الاسم الأول الاسم الأوسط اسم العائلة نطق أضف هاتف أضف البريد الإلكتروني أضف عنوانًا موقع إلكتروني أضف موقعًا إلكترونيًا اسم منسق سيتم استخدام هذه الصورة لوضعها فوق الرمز شريطي تخصيص الكود سيتم استخدام هذه الصورة كشعار في وسط رمز الاستجابة السريعة الشعار حشوة الشعار حجم الشعار زوايا الشعار العين الرابعة يضيف تماثل العين إلى رمز QR عن طريق إضافة العين الرابعة في الزاوية السفلية شكل بكسل شكل الإطار شكل الكرة مستوى تصحيح الخطأ اللون الداكن لون فاتح فرط نظام التشغيل Xiaomi HyperOS مثل الأسلوب نمط القناع قد لا يكون هذا الرمز قابلاً للمسح الضوئي، غيّر معلمات المظهر لجعله قابلاً للقراءة على جميع الأجهزة غير قابل للمسح الضوئي ستبدو الأدوات مثل مشغل تطبيقات الشاشة الرئيسية لتكون أكثر إحكاما وضع المشغل يملأ المنطقة بالفرشاة والأسلوب المحددين ملء الفيضان رذاذ يرسم مسارًا على شكل جرافيتي الجسيمات المربعة ستكون جزيئات الرش مربعة الشكل بدلاً من الدوائر أدوات اللوحة ولّد لوحة أساسية/material you من الصورة، أو استورد/صدِّر عبر تنسيقات لوحة مختلفة حرِّر اللوحة صدِّر/استورد لوحة عبر تنسيقات مختلفة اسم اللون اسم اللوحة تنسيق اللوحة صدِّر اللوحة التي المولّدة إلى تنسيقات مختلفة يضيف لونًا جديدًا إلى اللوحة الحالية تنسيق %1$s لا يدعم توفير اسم اللوحة نظرًا لسياسات متجر Play، لا يمكن تضمين هذه الميزة في الإصدار الحالي. للوصول إلى هذه الوظيفة، يُرجى تنزيل ImageToolbox من مصدر بديل. يمكنك العثور على الإصدارات المتاحة على GitHub أدناه. افتح صفحة Github سيتم استبدال الملف الأصلي بملف جديد بدلاً من حفظه في المجلد المحدد تم اكتشاف نص العلامة المائية المخفية تم الكشف عن صورة العلامة المائية المخفية أُخفت هذه الصورة الرسم التوليدي يسمح لك بإزالة الكائنات في الصورة باستخدام نموذج الذكاء الاصطناعي، دون الاعتماد على OpenCV. لاستخدام هذه الميزة، سيقوم التطبيق بتنزيل النموذج المطلوب (حوالي 200 ميجابايت) من GitHub يسمح لك بإزالة الكائنات في الصورة باستخدام نموذج الذكاء الاصطناعي، دون الاعتماد على OpenCV. قد تكون هذه عملية طويلة الأمد تحليل مستوى الخطأ التدرج النصوع متوسط المسافة كشف نقل النسخ يحتفظ معامل بيانات الحافظة كبيرة جدًا البيانات كبيرة جدًا بحيث لا يمكن نسخها نسج بسيط Pixelization بكسل متداخلة عبر البكسل بكسل مايكرو ماكرو البكسل المداري دوامة البكسل نبض شبكة البكسل بكسل النواة شعاعي نسج بكسل لا يمكن فتح عنوان uri \"%1$s\" وضع تساقط الثلوج ممكّن إطار الحدود البديل خلل تحول القناة ماكس إزاحة VHS كتلة خلل حجم الكتلة انحناء CRT انحناء صفاء بكسل تذوب ماكس قطرة أدوات الذكاء الاصطناعي أدوات مختلفة لمعالجة الصور من خلال نماذج الذكاء الاصطناعي مثل إزالة القطع الأثرية أو تقليل الضوضاء ضغط، خطوط خشنة الرسوم المتحركة، ضغط البث ضغط عام، ضوضاء عامة ضجيج الرسوم المتحركة عديم اللون سريع، ضغط عام، ضوضاء عامة، رسوم متحركة/كاريكاتير/أنيمي مسح الكتاب تصحيح التعرض الأفضل في الضغط العام والصور الملونة الأفضل في الضغط العام للصور ذات التدرج الرمادي الضغط العام، الصور ذات التدرج الرمادي، أقوى الضوضاء العامة والصور الملونة الضوضاء العامة والصور الملونة وتفاصيل أفضل الضوضاء العامة والصور ذات التدرج الرمادي الضوضاء العامة، الصور ذات التدرج الرمادي، أقوى الضوضاء العامة، الصور ذات التدرج الرمادي، الأقوى ضغط عام ضغط عام التركيب، ضغط h264 ضغط VHS الضغط غير القياسي (cinepak، msvideo1، roq) ضغط بينك، أفضل في الهندسة ضغط بينك، أقوى ضغط بينك، ناعم، يحتفظ بالتفاصيل القضاء على تأثير خطوة الدرج، والتنعيم الأعمال الفنية/الرسومات الممسوحة ضوئيًا، والضغط الخفيف، والتموج في النسيج النطاقات اللونية بطيئة، وإزالة الألوان النصفية أداة تلوين عامة للصور ذات التدرج الرمادي/الأبيض والأسود، للحصول على نتائج أفضل استخدم DDColor إزالة الحافة يزيل الحدة الزائدة بطيء، متردد تنعيم، التحف العامة، CGI معالجة المسح الضوئي KDM003 نموذج تحسين الصورة خفيف الوزن إزالة قطعة أثرية الضغط إزالة قطعة أثرية الضغط إزالة الضمادات مع نتائج سلسة معالجة نمط الألوان النصفية إزالة نمط التردد V3 إزالة آثار JPEG V2 تحسين نسيج H.264 شحذ وتعزيز VHS دمج حجم القطعة حجم التداخل سيتم تقطيع الصور التي يزيد حجمها عن %1$s بكسل إلى شرائح ومعالجتها إلى أجزاء، ويتم مزجها بشكل متداخل لمنع ظهور طبقات مرئية. يمكن أن تؤدي الأحجام الكبيرة إلى عدم الاستقرار مع الأجهزة المنخفضة الجودة اختر واحدة للبدء هل تريد حذف نموذج %1$s؟ سوف تحتاج إلى تنزيله مرة أخرى يتأكد نماذج النماذج المُنزلة النماذج المتاحة تحضير نموذج نشط فشل فتح الجلسة يمكن استيراد نماذج .onnx/.ort فقط استورد نموذج استورد نموذج onnx مخصص لمزيد من الاستخدام، يتم قبول نماذج onnx/ort فقط، ويدعم جميع المتغيرات المشابهة لـ esrgan تقريبًا نماذج مستوردة الضوضاء العامة والصور الملونة الضوضاء العامة، الصور الملونة، أقوى الضوضاء العامة، الصور الملونة، الأقوى يقلل من ثبات الألوان ونطاقات الألوان، مما يحسن التدرجات الناعمة ومناطق الألوان المسطحة. يعزز سطوع الصورة وتباينها من خلال الإبرازات المتوازنة مع الحفاظ على الألوان الطبيعية. يقوم بتفتيح الصور الداكنة مع الحفاظ على التفاصيل وتجنب التعرض المفرط. يزيل تنغيم اللون الزائد ويستعيد توازن اللون الأكثر حيادية وطبيعية. يطبق تنغيم الضوضاء المعتمد على Poisson مع التركيز على الحفاظ على التفاصيل الدقيقة والأنسجة. يطبق تنغيم ضوضاء Poisson الناعم للحصول على نتائج بصرية أكثر سلاسة وأقل عدوانية. يركز تنغيم الضوضاء الموحد على الحفاظ على التفاصيل ووضوح الصورة. نغمة ضوضاء موحدة لطيفة للحصول على ملمس رقيق ومظهر ناعم. إصلاح المناطق التالفة أو غير المستوية عن طريق إعادة طلاء القطع الأثرية وتحسين تناسق الصورة. نموذج debanding خفيف الوزن يزيل نطاقات الألوان بأقل تكلفة للأداء. يعمل على تحسين الصور باستخدام عناصر ضغط عالية جدًا (جودة 0-20%) لتحسين الوضوح. تحسين الصور بتأثيرات ضغط عالية (جودة 20-40%)، واستعادة التفاصيل وتقليل التشويش. يعمل على تحسين الصور بالضغط المعتدل (جودة 40-60%)، وموازنة الحدة والنعومة. ينقي الصور بضغط خفيف (جودة 60-80%) لتحسين التفاصيل والأنسجة الدقيقة. يعمل على تحسين الصور التي لا يمكن فقدانها تقريبًا (جودة 80-100%) مع الحفاظ على المظهر والتفاصيل الطبيعية. تلوين بسيط وسريع، رسوم متحركة، ليست مثالية يقلل من ضبابية الصورة قليلاً، ويحسن الحدة دون إدخال أي تشويش. عمليات تشغيل طويلة معالجة الصورة يعالج يزيل آثار ضغط JPEG الثقيلة في الصور ذات الجودة المنخفضة جدًا (0-20%). يقلل من آثار JPEG القوية في الصور المضغوطة للغاية (20-40%). ينظف عناصر JPEG المعتدلة مع الحفاظ على تفاصيل الصورة (40-60%). ينقي عناصر JPEG الخفيفة في صور ذات جودة عالية إلى حد ما (60-80%). يقلل بمهارة من آثار JPEG البسيطة في الصور التي لا تفقد البيانات تقريبًا (80-100%). يعزز التفاصيل الدقيقة والأنسجة، مما يحسن الحدة الملموسة دون المؤثرات الثقيلة. انتهت المعالجة فشلت المعالجة يعزز نسيج البشرة وتفاصيلها مع الحفاظ على المظهر الطبيعي، الأمثل للسرعة. يزيل آثار ضغط JPEG ويستعيد جودة الصورة للصور المضغوطة. يقلل من ضوضاء ISO في الصور الملتقطة في ظروف الإضاءة المنخفضة، مع الحفاظ على التفاصيل. يصحح التعريض الزائد أو التظليلات \"الضخمة\" ويستعيد توازنًا لونيًا أفضل. نموذج تلوين خفيف الوزن وسريع يضيف ألوانًا طبيعية إلى الصور ذات التدرج الرمادي. ديجبيج دينواز تلوين التحف يحسن أنيمي المسح الراقي جهاز ترقية X4 للصور العامة؛ نموذج صغير يستخدم معالج الرسوميات (GPU) ووقتًا أقل، مع تقليل التشويش وتقليل الضوضاء بشكل معتدل. جهاز ترقية X2 للصور العامة والحفاظ على الأنسجة والتفاصيل الطبيعية. أداة ترقية X4 للصور العامة مع مواد محسنة ونتائج واقعية. مُحسّن X4 لصور الأنيمي؛ 6 كتل RRDB لخطوط وتفاصيل أكثر وضوحًا. يؤدي جهاز ترقية X4 مع فقدان MSE إلى الحصول على نتائج أكثر سلاسة وتقليل الشوائب للصور العامة. X4 Upscaler مُحسّن لصور الأنيمي؛ متغير 4B32F بتفاصيل أكثر وضوحًا وخطوط ناعمة. طراز X4 UltraSharp V2 للصور العامة؛ يؤكد الحدة والوضوح. X4 ألتراشارب V2 لايت؛ أسرع وأصغر، ويحافظ على التفاصيل مع استخدام ذاكرة GPU أقل. نموذج خفيف الوزن لإزالة الخلفية بسرعة. الأداء المتوازن والدقة. يعمل مع الصور والأشياء والمشاهد. يوصى به لمعظم حالات الاستخدام. أزِل الخلفية سمك الحدود الأفقية سمك الحدود العمودية لا لون لون لونان %1$s لون %1$s لون %1$s لون النموذج الحالي لا يدعم التقطيع، ستتم معالجة الصورة بالأبعاد الأصلية، وقد يتسبب ذلك في استهلاك كبير للذاكرة ومشكلات مع الأجهزة المنخفضة الجودة عُطّل التقطيع، ستتم معالجة الصورة بالأبعاد الأصلية، وقد يتسبب ذلك في استهلاك كبير للذاكرة ومشكلات مع الأجهزة المنخفضة الجودة ولكنه قد يعطي نتائج أفضل عند الاستدلال التقطيع نموذج تجزئة الصور عالي الدقة لإزالة الخلفية إصدار خفيف الوزن من U2Net لإزالة الخلفية بشكل أسرع مع استخدام ذاكرة أصغر. يوفر نموذج DDColor الكامل تلوينًا عالي الجودة للصور العامة مع الحد الأدنى من الشوائب. أفضل خيار لجميع نماذج التلوين. مجموعات البيانات الفنية الخاصة والمدربة على DDColor؛ ينتج نتائج تلوين متنوعة وفنية مع عدد أقل من التحف اللونية غير الواقعية. نموذج BiRefNet خفيف الوزن يعتمد على Swin Transformer لإزالة الخلفية بدقة. إزالة خلفية عالية الجودة بحواف حادة والحفاظ على التفاصيل بشكل ممتاز، خاصة على الكائنات المعقدة والخلفيات الصعبة. نموذج إزالة الخلفية الذي ينتج أقنعة دقيقة ذات حواف ناعمة، مناسبة للأشياء العامة والحفاظ على التفاصيل بشكل معتدل. النموذج مُنزّل بالفعل استوردت النموذج بنجاح يكتب الكلمة الرئيسية سريع جدًا طبيعي بطيء بطيء جدًا احسب النسب المئوية القيمة الأدنى هي %1$s شوّه الصورة بالرسم بالأصابع طي الصلابة وضع الطي حرّك وسِّع قلِّص تدور في اتجاه عقارب الساعة تدور عكس عقارب الساعة قوة التلاشي أعلى قطرة إسقاط القاع ابدأ بالاسقاط نهاية السقوط يُنزّل الأشكال السلسة استخدم القطع الناقص الفائق بدلاً من المستطيلات المستديرة القياسية للحصول على أشكال أكثر سلاسة وطبيعية نوع الشكل يقطع مدور سلس حواف حادة بدون تقريب زوايا مستديرة كلاسيكية نوع الأشكال حجم الزوايا سكويركل عناصر واجهة المستخدم مدورة أنيقة تنسيق اسم الملف نص مخصص يتم وضعه في بداية اسم الملف، وهو مثالي لأسماء المشاريع أو العلامات التجارية أو العلامات الشخصية. يستخدم اسم الملف الأصلي بدون امتداد، مما يساعدك على الحفاظ على تعريف المصدر سليمًا. عرض الصورة بالبكسل، مفيد لتتبع تغييرات الدقة أو قياس النتائج. ارتفاع الصورة بالبكسل، وهو أمر مفيد عند التعامل مع نسب العرض إلى الارتفاع أو عمليات التصدير. يولّد أرقام عشوائية لضمان أسماء ملفات فريدة من نوعها؛ أضف المزيد من الأرقام لمزيد من الأمان ضد التكرارات. عداد متزايد تلقائيًا لصادرات الدُفعات، وهو مثالي عند حفظ صور متعدّدة في جلسة واحدة. يقوم بإدراج اسم الإعداد المسبق المطبق في اسم الملف حتى تتمكن من تذكر كيفية معالجة الصورة بسهولة. يعرض وضع تغيير حجم الصورة المستخدم أثناء المعالجة، مما يساعد على تمييز الصور التي تم تغيير حجمها أو اقتصاصها أو تركيبها. نص مخصص يتم وضعه في نهاية اسم الملف، وهو مفيد لإصدارات مثل _v2، أو _edited، أو _final. امتداد الملف (png، jpg، webp، وما إلى ذلك)، يطابق تلقائيًا التنسيق الفعلي المحفوظ. طابع زمني قابل للتخصيص يتيح لك تحديد التنسيق الخاص بك من خلال مواصفات جافا للفرز المثالي. نوع القذف الروبوت الأصلي نمط دائرة الرقابة الداخلية منحنى سلس توقف سريع نطاط عائم لاذع فائق النعومة التكيف علم إمكانية الوصول انخفاض الحركة فيزياء التمرير الأصلية لنظام Android لمقارنة خط الأساس التمرير المتوازن والسلس للاستخدام العام سلوك تمرير عالي الاحتكاك يشبه نظام iOS منحنى فريد من نوعه لإحساس التمرير المميز التمرير الدقيق مع التوقف السريع تمريرة نطاطة مرحة وسريعة الاستجابة مخطوطات طويلة ومزلقة لتصفح المحتوى تمرير سريع وسريع الاستجابة لواجهات المستخدم التفاعلية تمرير سلس ممتاز مع زخم ممتد يضبط الفيزياء على أساس سرعة الدفع يحترم إعدادات إمكانية الوصول إلى النظام الحد الأدنى من الحركة لتلبية احتياجات الوصول الخطوط الأولية يضيف خطًا أكثر سمكًا في كل سطر خامس لون التعبئة الأدوات المخفية أدوات مخفية للمشاركة مكتبة الألوان تصفح مجموعة واسعة من الألوان يعمل على زيادة حدة الصور وإزالة التشويش منها مع الحفاظ على التفاصيل الطبيعية، وهو مثالي لإصلاح الصور خارج نطاق التركيز. يستعيد الصور التي تم تغيير حجمها مسبقًا بذكاء، ويستعيد التفاصيل والأنسجة المفقودة. تم تحسينه لمحتوى الحركة الحية، ويقلل من تأثيرات الضغط ويعزز التفاصيل الدقيقة في إطارات الأفلام/البرامج التلفزيونية. يحول لقطات بجودة VHS إلى HD، مما يزيل ضوضاء الشريط ويعزز الدقة مع الحفاظ على المظهر القديم. مخصص للصور ذات النصوص الثقيلة ولقطات الشاشة، ويزيد من حدة الأحرف ويحسن إمكانية القراءة. تم تدريب الترقية المتقدمة على مجموعات بيانات متنوعة، وهي ممتازة لتحسين الصور للأغراض العامة. تم تحسينه للصور المضغوطة على الويب، ويزيل آثار JPEG ويستعيد المظهر الطبيعي. نسخة محسنة لصور الويب مع الحفاظ بشكل أفضل على الملمس وتقليل الشوائب. ترقية 2x باستخدام تقنية محول التجميع المزدوج، تحافظ على الوضوح والتفاصيل الطبيعية. ترقية 3x باستخدام بنية المحولات المتقدمة، مثالية لاحتياجات التوسيع المعتدلة. ترقية الجودة العالية 4x باستخدام شبكة محولات حديثة، تحافظ على التفاصيل الدقيقة بمقاييس أكبر. يزيل الضبابية/الضوضاء والاهتزازات من الصور. للأغراض العامة ولكن الأفضل في الصور. يستعيد الصور ذات الجودة المنخفضة باستخدام محول Swin2SR، الأمثل لتدهور BSRGAN. رائعة لإصلاح التحف المضغوطة الثقيلة وتحسين التفاصيل بمقياس 4x. ترقية 4x باستخدام محول SwinIR المدرب على تحلل BSRGAN. يستخدم GAN للحصول على مواد أكثر وضوحًا وتفاصيل أكثر طبيعية في الصور والمشاهد المعقدة. طريق دمج PDF دمج ملفات PDF متعدّدة في مستند واحد ترتيب الملفات ص. تقسيم PDF استخرج صفحات محدّدة من مستند PDF تدوير PDF إصلاح اتجاه الصفحة بشكل دائم الصفحات إعادة ترتيب PDF اسحب واسقط الصفحات لإعادة ترتيبها اضغط على الصفحات واسحبها أرقام الصفحات أضف الترقيم إلى مستنداتك تلقائيًا تنسيق التسمية PDF إلى نص (OCR) استخرج نص عادي من مستندات PDF الخاصة بك تراكب نص مخصص للعلامة التجارية أو الأمان إمضاء أضف توقيعك الإلكتروني إلى أي مستند سيتم استخدام هذا كتوقيع فتح ملف PDF أزِل كلمات السر من ملفاتك المحمية حماية PDF أمن مستنداتك بتعمية قوية نجاح PDF مقفل، يمكنك حفظه أو مشاركته إصلاح PDF محاولة إصلاح المستندات التالفة أو غير القابلة للقراءة تدرج الرمادي تحويل كافة الصور المضمنة في الوثيقة إلى التدرج الرمادي اضغط PDF حسّن حجم ملف مستندك لتسهيل المشاركة يقوم ImageToolbox بإعادة بناء جدول الإسناد الترافقي الداخلي ويعيد توليد بنية الملف من البداية. يمكن أن يؤدي هذا إلى استعادة الوصول إلى العديد من الملفات التي \"لا يمكن فتحها\" تقوم هذه الأداة بتحويل جميع صور المستندات إلى تدرج رمادي. الأفضل للطباعة وتقليل حجم الملف البيانات الوصفية حرِّر خصائص الوثيقة لتحسين الخصوصية العلامات منتج مؤلف الكلمات الرئيسية الخالق الخصوصية العميقة النظيفة امسح كافة البيانات التعريفية المتوفرة لهذا المستند صفحة التعرف الضوئي على الحروف العميق استخرج النص من المستند وخزنه في ملف نصي واحد باستخدام محرك Tesseract لا يمكن إزالة كل الصفحات أزِل صفحات PDF أزِل صفحات محدّدة من مستند PDF انقر لإزالة يدويا قص ملف PDF قص صفحات المستند إلى أي حدود سطح PDF اجعل ملف PDF غير قابل للتعديل عن طريق تنقيط صفحات المستند تعذر تشغيل الكاميرا. يرجى التحقق من الأذونات والتأكد من عدم استخدامها بواسطة تطبيق آخر. استخرج صور استخرج الصور المضمّنة في ملفات PDF بدقتها الأصلية لا يحتوي ملف PDF هذا على أي صور مضمنة تقوم هذه الأداة بمسح كل صفحة واستعادة الصور المصدرية ذات الجودة الكاملة، وهي مثالية لحفظ النسخ الأصلية من المستندات رسم التوقيع معلمات القلم استخدم التوقيع الخاص كصورة لوضعها على المستندات الرمز البريدي PDF قسّم المستند بفاصل زمني محدّد وحزم المستندات الجديدة في أرشيف مضغوط فاصلة اطبع PDF جهّز مستند للطباعة بحجم صفحة مخصّص صفحات لكل ورقة توجيه حجم الصفحة هامِش يزدهر الركبة الناعمة الأمثل للأنيمي والرسوم المتحركة. ترقية سريعة مع ألوان طبيعية محسنة وعدد أقل من القطع الأثرية سامسونج One UI 7 مثل الاسلوب أدخل رموز الرياضيات الأساسية هنا لحساب القيمة المطلوبة (على سبيل المثال (5+5)*10) التعبير الرياضي التقط ما يصل إلى %1$s من الصور احتفظ بالتاريخ والوقت احتفظ دائمًا بعلامات exif المتعلقة بالتاريخ والوقت، ويعمل بشكل مستقل عن خيار keep exif لون الخلفية لتنسيقات ألفا يضيف القدرة على تعيين لون الخلفية لكل تنسيق صورة مع دعم ألفا، عند تعطيله يكون هذا متاحًا للتنسيقات غير ألفا فقط مشروع مفتوح استمر في تحرير مشروع Image Toolbox المحفوظ مسبقًا غير قادر على فتح مشروع Image Toolbox يفتقد مشروع Image Toolbox بيانات المشروع مشروع Image Toolbox تالف إصدار مشروع Image Toolbox غير مدعوم: %1$d حفظ المشروع قم بتخزين الطبقات والخلفية وسجل التحرير في ملف مشروع قابل للتحرير فشل في الفتح الكتابة إلى ملف PDF قابل للبحث تعرف على النص من مجموعة الصور واحفظ ملف PDF القابل للبحث مع الصورة وطبقة النص القابلة للتحديد طبقة ألفا الوجه الأفقي الوجه العمودي قفل أضف الظل لون الظل هندسة النص قم بتمديد النص أو تحريفه للحصول على أسلوب أكثر وضوحًا مقياس X انحراف X إزالة التعليقات التوضيحية قم بإزالة أنواع التعليقات التوضيحية المحددة مثل الروابط أو التعليقات أو الإبرازات أو الأشكال أو حقول النموذج من صفحات PDF الارتباطات التشعبية مرفقات الملفات خطوط النوافذ المنبثقة الطوابع الأشكال ملاحظات نصية ترميز النص حقول النموذج العلامات مجهول الشروح فك التجميع أضف ظلًا ضبابيًا خلف الطبقة مع لون وإزاحات قابلة للتكوين ================================================ FILE: core/resources/src/main/res/values-be/strings.xml ================================================ Выберыце малюнак для пачатку Вышыня %1$s Спецыфічны Закрыццё праграмы Змены выявы вернуцца да пачатковых значэнняў Скід Скапіравана ў буфер абмену Выключэнне Ок Дадзеныя EXIF не знойдзены Дадаць тэг Нешта пайшло не так: %1$s Памер %1$s Загрузка… Відарыс занадта вялікі для папярэдняга прагляду, але ўсё роўна будзе зроблена спроба захаваць Шырыня %1$s Тып маштабавання Якасць Пашырэнне Гнуткі Выберыце малюнак Вы ўпэўнены, што хочаце закрыць праграму? Заставайся Закрыць Скінуць выяву Значэнне скінута правільна Нешта пайшло не так Перазапусціць праграму Рэдагаваць EXIF Усе незахаваныя змены будуць страчаны, калі вы выйдзеце зараз Захаваць Ў цяперашні час фармат %1$s на Android дазваляе толькі чытаць exif, але не змяняць/захоўваць іх, гэта азначае, што выходная выява наогул не будзе мець метададзеных. Ачысціць Ачысціць EXIF Захаванне Выберыце колер Выберыце колер з малюнка, скапіруйце або падзяліцеся ім Малюнак Прадустаноўкі Атрымлівайце апошнія абнаўленні, абмяркоўвайце праблемы і многае іншае Усе даныя EXIF выявы будуць выдалены. Гэта дзеянне нельга адмяніць! Адна праўка Колер скапіяваны Зыходны код Змяніць характарыстыкі аднаго дадзенага відарыса Адмена Выразаць Колер Палітра Выявы: %d Немагчыма стварыць палітру для дадзенай выявы Арыгінал Выключыць Другасная персаналізацыя Памер тэксту Абнаўленне Захаваць EXIF Тэчка вываду Светлы Версія Персаналізацыя Стварыце ўзор каляровай палітры з дадзенага малюнка Цёмны Абрэзаць выяву ў любых межах Па змаўчанні Стварыць палітру Калі ўключана, то колер паверхняў будзе ўсталяваны на абсалютна цёмны ў начным рэжыме. Шрыфт Выкарыстанне вялікага памеру шрыфтоў можа выклікаць збоі ў карыстальніцкім інтэрфейсе і праблемы, якія не будуць выпраўлены. Выкарыстоўвайце асцярожна. Як у сістэме Новая версія %1$s Змяніць папярэдні прагляд Выберыце, які эмодзі адлюстроўваць на галоўным экране Каляровы баланс Колер фону Ўключыць эмодзі Сіла вібрацыі Колер для замены Выдаліць колер Блікі і цені Блікі Групуе параметры на галоўным экране па іх тыпу замест карыстальніцкага спісу Стыль палітры па змаўчанні, ён дазваляе наладзіць усе чатыры колеры, іншыя дазваляюць усталяваць толькі асноўны колер Немагчыма змяніць каляровую схему праграмы, калі ўключаны дынамічныя колеры Калі гэта ўключана, то колеры праграмы будуць прымяняцца да колераў шпалер Калі ўключаны, то колеры праграмы будуць падладжвацца пад выбраны малюнак у рэжыме рэдагавання Колеры Monet Начны рэжым Дынамічныя колеры Зялёны Сіні Каляровая схема Ўстаўце правільны aRGB-код. Чырвоны Эмодзі Імя файла Колькасць эмодзі Колеравая прастора GCA Каляровая матрыца Фальшывы колер Захавана ў тэчку %1$s Вы збіраецеся выдаліць выбраную каляровую схему. Гэтую аперацыю нельга адмяніць Выдаліць схему Тэкст На малюнку няма тэксту або праграма не знайшла яго Вібрацыя Вы адключылі праграму \"Файлы\", актывуйце яе, каб выкарыстоўваць гэту функцыю Буфер абмену Аўтаматычна дадае захаваны малюнак у буфер абмену, калі ўключана Дадайце зыходнае імя файла Толькі націск Захаванне ў сховішча не будзе выканана, а малюнак будзе спрабаваць змясціць толькі ў буфер абмену Тэма праграмы будзе заснавана на абраным колеры Калі ўключана, імя вываднага файла будзе цалкам выпадковым Рэжым Amoled Няма чаго ўставіць зыходнае імя файла Светлы Каляровы фільтр Першы колер Другі колер Колер фарбы Рандамізацыя імя файла Максімальная колькасць колераў OCR (распазнаванне тэксту) Распазнаваць тэкст з дадзенай выявы, падтрымліваецца больш за 120 моў Замяніць колер Колер для выдалення Адкл. Пікселяцыя абводкі Алмазная пікселізацыя Ўключыць Monet Мова Выправіць памылкі перакладу або перакласці праект на іншыя мовы Слабае ўключэнне пікселяў Выкарыстоўваць перамыкач Pixel Будзе выкарыстоўвацца перамыкач, як на тэлефонах Pixel, замест стандартнага з Material You Дазволіць некалькі моў Палепшаная алмазная пікселізацыя Даступныя мовы Спампаваныя мовы Таўшчыня рамкі Пікселізацыя Палепшаная пікселізацыя Ўключыць адмалёўку ценяў пад панэллю дзеянняў Панэлі дзеянняў Калі ўключана, дыялогавае акно абнаўлення будзе паказвацца пры запуску праграмы Абнаўленні Вы збіраецеся выдаліць выбраную маску фільтра. Гэтую аперацыю нельга адмяніць Аб праграме Абнаўленні не знойдзены Праверка наяўнасці абнаўлення Праверыць абнаўлення Праверка абнаўленняў будзе ўключаць бэта-версіі праграмы, калі яна ўключана Гэта сродак праверкі абнаўленняў падключыцца да GitHub, каб праверыць наяўнасць новага абнаўлення Палепшаны глюк Зрух канала X Зрух канала Y Памер карупцыі Карупцыя Shift X Карупцыйны зрух Y Размыццё палаткі Бакавое выцвітанне Збоку Топ Фільтры Маляваць на фоне Выберыце колер фону і малюйце па-над ім Выберыце файл Немагчыма змяніць размяшчэнне, пакуль уключана групаванне опцый Скрыншот Звяжыцеся са мной Эмоцыі Арыентацыя & Толькі выяўленне сцэнарыя Аўтаматычная арыентацыя & Выяўленне сцэнарыя Адзін слупок Адзін блок вертыкальнага тэксту Адзінкавы блок Адзіны радок Разрэджаны тэкст Арыентацыя & Выяўленне сцэнарыя Сырая лінія Глюк Сума насенне Выдаліць маску Драго Олдрыдж Мёбіус Пераход Пікавая Каляровая анамалія Каталог \"%1$s\" не знойдзены, мы змянілі яго на стандартны, захавайце файл яшчэ раз Аўтаматычны штыфт Перазапісаць файлы Пусты Суфікс Пошук Бясплатна Замяніць парадкавы нумар Гэта дадатак цалкам бясплатнае, але калі вы хочаце падтрымаць развіццё праекта, вы можаце націснуць тут Просты выбар галерэі малюнкаў. Гэта будзе працаваць, толькі калі ў вас ёсць праграма, якая забяспечвае выбар мультымедыя Выкарыстоўвайце намер GetContent для выбару выявы. Працуе ўсюды, але, як вядома, узнікаюць праблемы з атрыманнем выбраных відарысаў на некаторых прыладах. Гэта не мая віна. Калі ўключана, стандартная пазнака часу замяняецца на парадкавы нумар выявы, калі вы выкарыстоўваеце пакетную апрацоўку Даданне зыходнай назвы файла не працуе, калі выбрана крыніца выявы ў сродку выбару фота Загрузіць малюнак з сеткі Запоўніць Прыстасаваны Змяняе памер малюнкаў у відарысы з доўгім бокам, зададзеным параметрам Шырыня або Вышыня, усе разлікі памеру будуць зроблены пасля захавання - захоўвае суадносіны бакоў Фільтраваць Прымяніце любую ланцужок фільтраў да зададзеных малюнкаў Альфа Экспазіцыя Баланс белага тэмпература Таніроўка Цені Дыстанцыя Схіл Вастрыць Сэпія Адмоўны Чорна-белы Штрыхоўка Інтэрвал Шырыня лініі Двухбаковае размыццё Выбіваць Лапласаўская Пачаць Канец Згладжванне Кувахара Размыццё стэка Радыус Маштаб Скажэнне Вугал Віхор Выпукласць Пашырэнне Праламленне сферы Паказчык праламлення Праламленне шкляной сферы Абмяжоўвае змяненне памеру Эскіз Узроўні квантавання Не максімальнае падаўленне Змяніць парадак Хуткае размыццё Памер размыцця Размыццё цэнтра x Цэнтр размыцця y Размыццё зуму Парог яркасці Шыфр Шыфраваць і дэшыфраваць любы файл (а не толькі малюнак) на аснове крыпта-алгарытму AES Зашыфраваць Расшыфраваць Захавайце гэты файл на сваёй прыладзе або выкарыстоўвайце дзеянне абагульвання, каб змясціць яго куды заўгодна Шыфраванне файлаў на аснове пароля. Атрыманыя файлы можна захоўваць у абраным каталогу або абагульваць. Расшыфраваныя файлы таксама можна адкрыць непасрэдна. AES-256, рэжым GCM, без запаўнення, 12 байтаў у выпадковым парадку IV. Ключы выкарыстоўваюцца як хэшы SHA-3 (256 біт). Захавана ў папку %1$s з назвай %2$s Выкарыстоўвайце гэты тып маскі для стварэння маскі з дадзенай выявы, заўважце, што яна ПАВІННА мець альфа-канал Налады паспяхова адноўлены Гэта верне вашы налады да значэнняў па змаўчанні. Звярніце ўвагу, што гэта нельга адмяніць без файла рэзервовай копіі, згаданага вышэй. Па змаўчанні Аа Бб Вв Гг Дд Ее Ёё Жж Зз Іі Йй Кк Лл Мм Нн Оо Пп Рр Сс Тт Уу Ўў Фф Хх Цц Чч Шш Ыы Ьь Ээ Юю Яя 0123456789 !? Прырода і жывёлы Аб\'екты Сімвалы Гэта дазваляе праграме збіраць справаздачы аб збоях уручную Дазволіць збор ананімнай статыстыкі выкарыстання праграмы Аналітыка Захаванне амаль завершана. Пры адмене зараз спатрэбіцца захаванне зноў. Намалюйце стрэлкі Маленькія выявы будуць маштабавацца да самых вялікіх у паслядоўнасці, калі гэта ўключана Палепшаная пікселізацыя кругоў Стыль палітры Танальная пляма Нейтральны Жывы Экспрэсіўны Вясёлка Фруктовы салата Вернасць Змест Гуллівая тэма - адценне зыходнага колеру не адлюстроўваецца ў тэме Манахромная тэма, колеры чыста чорны / белы / шэры Схема, якая змяшчае зыходны колер у Scheme.primaryContainer Дае магчымасць пошуку па ўсіх даступных опцыях на галоўным экране Пераўтварыце PDF у выявы ў зададзеным фармаце вываду Упакуйце дадзеныя выявы ў выходны файл PDF Фільтр маскі Прымяніць ланцужкі фільтраў да зададзеных замаскіраваных абласцей, кожная вобласць маскі можа вызначыць свой уласны набор фільтраў Маскі Дадаць маску Маска %d Папярэдні прагляд маскі Нарысаваная маска фільтра будзе візуалізавана, каб паказаць вам прыблізны вынік Поўны фільтр Пачаць Цэнтр Канец Простыя варыянты Хайлайтер Неон Пяро Размыццё прыватнасці Дадайце эфект ззяння сваім малюнкам Па змаўчанні адзін, самы просты - толькі колер Падобна да размыцця прыватнасці, але пікселізуе замест размыцця Аўтаматычны паварот Дазваляе выкарыстоўваць абмежавальнае поле для арыентацыі выявы Малюе шлях у якасці ўваходнага значэння Малюе шлях ад пачатковай да канчатковай кропкі ў выглядзе лініі Малюе стрэлку ад пачатковай да канчатковай кропкі ў выглядзе лініі Малюе паказальную стрэлку ад зададзенага шляху Малюе двайную стрэлку ад пачатковай да канчатковай кропкі ў выглядзе лініі Малюе двайную стрэлку ад зададзенага шляху Абведзены авал Акрэслены праст Авал Рэкт Малюе прамавухі ад пачатковай да канчатковай кропкі Малюе авал ад пачатковай да канчатковай кропкі Малюе контурны авал ад пачатковай да канчатковай кропкі Малюе акрэслены прастакут ад пачатковай да канчатковай кропкі Каб перазапісаць файлы, трэба выкарыстоўваць крыніцу відарысаў \"Правадыр\", паспрабуйце пераабраць відарысы, мы змянілі крыніцу відарысаў на патрэбны Арыгінальны файл будзе заменены на новы замест захавання ў абранай тэчцы, крыніцай выявы для гэтага параметра павінна быць \"Правадыр\" або GetContent, пры пераключэнні гэтага параметра ён будзе ўсталяваны аўтаматычна Адзін з самых простых спосабаў павелічэння памеру, замена кожнага пікселя на колькасць пікселяў таго ж колеру Метад плыўнай інтэрпаляцыі і паўторнай выбаркі набору кантрольных кропак, звычайна выкарыстоўваецца ў камп\'ютэрнай графіцы для стварэння гладкіх крывых Самы просты рэжым маштабавання Android, які выкарыстоўваецца амаль ва ўсіх праграмах Метад паўторнай выбаркі, які падтрымлівае высакаякасную інтэрпаляцыю шляхам прымянення ўзважанай функцыі sinc да значэнняў пікселяў Функцыя вокнаў часта прымяняецца пры апрацоўцы сігналаў для мінімізацыі спектральнай уцечкі і павышэння дакладнасці частотнага аналізу шляхам звужэння краёў сігналу Тэхніка матэматычнай інтэрпаляцыі, якая выкарыстоўвае значэнні і вытворныя ў канчатковых кропках сегмента крывой для стварэння гладкай і бесперапыннай крывой Для належнага функцыянавання Tesseract OCR дадатковыя навучальныя даныя (%1$s) неабходна загрузіць на вашу прыладу. \nВы хочаце спампаваць даныя %2$s? Метад паўторнай выбаркі, які выкарыстоўвае канвертацыйны фільтр з наладжвальнымі параметрамі для дасягнення балансу паміж рэзкасцю і згладжваннем у маштабаванай выяве Выкарыстоўвае шматчленныя функцыі для плыўнай інтэрпаляцыі і апраксімацыі крывой або паверхні, гнуткае і бесперапыннае прадстаўленне формы Даных няма Спампаваць Няма падключэння, праверце і паўтарыце спробу, каб спампаваць мадэлі цягнікоў Прымушае першапачатковую праверку віджэта exif Толькі аўто Аўто Адно слова Абвядзіце слова Адзін сімвал Разрэджаны тэкст Вы хочаце выдаліць даныя навучання OCR для мовы \"%1$s\" для ўсіх тыпаў распазнання ці толькі для выбранага (%2$s)? Дадаць колер Уласцівасці Вадзяныя знакі Малюнкі вокладкі з наладжвальнымі тэкставымі/малюнкавымі вадзянымі знакамі Паўтарыць вадзяны знак Паўтараецца вадзяны знак на малюнку замест аднаго ў дадзеным месцы Зрушэнне X Зрушэнне Y Тып вадзянога знака Квантызатар Шэрая шкала Bayer Eight By Eight Dithering Флойд Штайнберг Дызерынг False Floyd Steinberg Dithering Выкарыстоўвае кавалачна вызначаныя бікубічныя паліномныя функцыі для плыўнай інтэрпаляцыі і апраксімацыі крывой або паверхні, гнуткае і бесперапыннае прадстаўленне формы Зрух нахілу Знізу Сіла Фрактальнае шкло Памер Амплітуда X хуткасць Простыя эфекты Трытаномалія Дэўтарамалія Пратанамалія Вінтаж Coda Chrome Начное бачанне Цёплы Крута Трытанопія Ахраматамалія Ахроматопсия Нярэзкі Пастэльныя Аранжавая дымка Ружовая мара Залатая гадзіна Гарачае лета Фіялетавы туман Усход сонца Маляўнічы вір Мяккае вясновае святло Тоны восені Лавандавая мара Кіберпанк Лёгкі ліманад Прывідны агонь Начная магія Фантастычны пейзаж Каляровы выбух Электрычны градыент Касмічны партал Чырвоны вір Лічбавы код Форма значка Адрэзаць Учымура Выявы перазапісаны ў першапачатковым месцы прызначэння Немагчыма змяніць фармат выявы, калі ўключана опцыя перазапісу файлаў Эмодзі як каляровая схема Выкарыстоўвае асноўны колер эмодзі ў якасці каляровай схемы праграмы замест вызначанай уручную Рэзервовае капіраванне і аднаўленне Зрабіце рэзервовую копію налад праграмы ў файл Аднавіць налады праграмы з раней створанага файла Зыходныя метаданыя выявы будуць захаваны Парадак малюнкаў Круговая пікселізацыя Талерантнасць Колер мэты Перакадзіраваць Размываць Анізатропная дыфузія Дыфузія Правядзенне Гарызантальны вецер ACES Filmic Tone Mapping ACES Hill Tone Mapping Хуткае двухбаковае размыццё Размыццё Пуасона Лагарыфмічнае танальнае адлюстраванне Крышталізаваць Колер абводкі Амплітуда Мармуровы Турбулентнасць Алей Эфект вады Частата X Частата Y Амплітуда Y Скажэнне Перліна Hable Filmic Tone Mapping Hejl Burgess Tone Mapping Ток Усе Ужывайце любыя ланцужкі фільтраў да зададзеных відарысаў або аднаго відарыса PDF ў выявы Выявы ў pdf Просты папярэдні прагляд PDF Стваральнік градыентаў Стварыце градыент зададзенага выхаднога памеру з наладжанымі колерамі і тыпам выгляду Абезгазаваць Амега Інструменты PDF Ацаніць дадатак Стаўка Гэта дадатак цалкам бясплатнае, калі вы хочаце, каб яно стала большым, пазначце праект зоркай на Github 😄 Каляровая матрыца 4х4 Каляровая матрыца 3х3 Паляроід Браўні Дэўтаронатапія Пратанопія Лінейны Радыяльны Падмятаць Тып градыенту Цэнтр X Цэнтр Ю Рэжым пліткі Паўтараецца Люстэрка Заціск Дэкаль Каляровыя прыпынкі Электронная пошта Рэжым малявання шляху Падвойная стрэлка Бясплатнае маляванне Двайная стрэлка Лінія Стрэлка Стрэлка лінія Дызерінг Bayer Two By Two Dithering Баер тры на тры дызерінг Bayer Four By Four Dithering Джарвіс Джудіс Нінке Дытэрынг Сьера-Дытэрынг Two Row Sierra Dithering Sierra Lite Dithering Аткінсан Дзітэрынг Stucki Dithering Беркс Дытэрынг Размыванне злева направа Выпадковае змяненне Простае парогавае ваганне Тып не падтрымліваецца: %1$s Прыстасаваныя Невызначаны Захоўванне прылады Параўнайце Змяніць памер па вазе Максімальны памер у КБ Змяніць памер выявы ў адпаведнасці з зададзеным памерам у КБ Параўнайце два дадзеныя малюнкі Выберыце два відарысы для пачатку Падбярыце малюнкі Налады Адсочванне праблем Дасылайце справаздачы аб памылках і запыты аб функцыях сюды Дапамажыце перакласці Па вашым запыце нічога не знойдзена Шукайце тут Не ўдалося захаваць %d відарыс(ы) Другасны Дадаць Дазвол Грант Праграме неабходны доступ да вашага сховішча, каб захоўваць выявы для працы. Дайце дазвол у наступным дыялогавым акне. Знешняе сховішча Выраўноўванне FAB Маштаб выявы падзяліцца Прэфікс Дадайце памер файла Калі ўключана, дадае шырыню і вышыню захаванай выявы да назвы выходнага файла Выдаліць EXIF Выдаліць метададзеныя EXIF з любога набору малюнкаў Папярэдні прагляд выявы Папярэдні прагляд малюнкаў любога тыпу: GIF, SVG і гэтак далей Крыніца выявы Галерэя Правадыр файлаў Сучасны інструмент выбару фатаграфій Android, які з\'яўляецца ўнізе экрана, можа працаваць толькі на Android 12+. Ёсць праблемы з атрыманнем метаданых EXIF Размяшчэнне варыянтаў Рэдагаваць Парадак Вызначае парадак параметраў на галоўным экране Калі ўключана, дадае арыгінальнае імя файла ў назву выходнага відарыса Маштаб зместу Прымушае ператвараць кожны малюнак у відарыс, зададзены параметрамі Шырыня і Вышыня - можа змяніць суадносіны бакоў Яркасць Кантраст Адценне Насычанасць Дадаць фільтр Манахромны Гама Дымка Эфект Салярызаваць Жывасць Собельскі край Размыццё Паўтоны Гаўсава размыццё Размытасць скрынкі Віньетка Непразрыстасць Парог Гладкі мульцік Мультфільм Постэрызацыі Шукаць Згортка 3х3 Rgb фільтр Маляваць Малюйце на малюнку, як у альбоме для малявання, або малюйце на самім фоне Фарба альфа Маляваць на малюнку Выберыце малюнак і намалюйце што-небудзь на ім Выберыце файл для пачатку Расшыфроўка Шыфраванне ключ Файл апрацаваны Асаблівасці Рэалізацыя Сумяшчальнасць The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. Калі ласка, звярніце ўвагу, што сумяшчальнасць з іншымі праграмамі або службамі шыфравання файлаў не гарантуецца. Крыху іншая апрацоўка ключа або канфігурацыя шыфра могуць выклікаць несумяшчальнасць. Кэш Спроба захаваць малюнак з зададзенай шырынёй і вышынёй можа выклікаць памылку OOM. Рабіце гэта на свой страх і рызыку, і не кажыце, што я вас не папярэджваў! Памер кэша Знойдзена %1$s Аўтаматычная ачыстка кэша Калі ўключана, кэш праграмы будзе ачышчаны пры запуску праграмы Ствараць інструменты Рэдагаваць скрыншот Прапусціць Копія Захаванне ў рэжыме %1$s можа быць нестабільным, таму што гэта фармат без страт Калі вы абралі папярэднюю ўстаноўку 125, выява будзе захавана ў памеры 125% ад арыгінальнай выявы. Калі вы выбіраеце значэнне 50, то выява будзе захавана з памерам 50% Прадусталяванне тут вызначае % выхаднога файла, г. зн., калі вы выбіраеце заданне 50 для выявы памерам 5 МБ, пасля захавання вы атрымаеце выяву памерам 2,5 МБ Тэлеграм чат Кроп-маска Суадносіны бакоў Рэзервовае капіраванне Выдаліць Прадукты харчавання і напоі Абрэзаць малюнак Выдаліце фон з выявы, намаляваўшы або скарыстаўшыся опцыяй Аўта. Празрыстыя прасторы вакол выявы будуць абрэзаны Рэжым малявання Аднавіць фон Радыус размыцця Піпетка Колер маскі Рэжым маштабу Білінейны Ханн Эрміт Ланцош Мітчэл Бліжэйшы Сплайн Базавы Значэнне па змаўчанні Значэнне ў дыяпазоне %1$s - %2$s Сігма Прасторавая сігма Сярэдняе размыццё Кэтмул Бікубічны Лінейная (або білінейная, у двух вымярэннях) інтэрпаляцыя звычайна добрая для змены памеру выявы, але выклікае некаторае непажаданае змякчэнне дэталяў і ўсё яшчэ можа быць некалькі няроўнай Лепшыя метады маштабавання ўключаюць паўторную выбарку Ланцоша і фільтры Мітчэла-Нетравалі Дадае кантэйнер з выбранай формай пад галоўнымі значкамі карт Сшыванне малюнкаў Аб\'яднайце дадзеныя малюнкі, каб атрымаць адзін вялікі Максімальная яркасць Экран Градыентнае накладанне Складзіце любы градыент верхняй часткі дадзенага малюнка Пераўтварэнні Камера Выкарыстоўвае камеру для здымкі, звярніце ўвагу, што можна атрымаць толькі адну выяву з гэтай крыніцы выявы Выберыце як мінімум 2 выявы Збожжа Карамельная цемра Футурыстычны градыент Зялёнае сонца Вясёлкавы свет Deep Purple Гэта выява будзе выкарыстоўвацца ў якасці шаблону для вадзяных знакаў Колер тэксту Рэжым накладання Заблакіраваць арыентацыю малюнка Памер пікселя Калі ўключана ў рэжыме малявання, экран не будзе паварочвацца Змяняйце памер выбраных відарысаў у адпаведнасці з зададзенымі абмежаваннямі па шырыні і вышыні, захоўваючы суадносіны бакоў Боке Інструменты GIF Пераўтварыце выявы ў малюнак GIF або вылучыце рамкі з дадзенага малюнка GIF GIF да малюнкаў Пераўтварыце GIF-файл у пакет малюнкаў Пераўтварэнне пакета малюнкаў у файл GIF Выявы ў GIF Каб пачаць, абярыце GIF-малюнак Выкарыстоўвайце памер першага кадра Заменіце ўказаны памер першымі памерамі кадра Паўтарыце падлік Затрымка кадра міліс FPS Выкарыстоўвайце ласо Для выканання сцірання выкарыстоўваецца ласо, як у рэжыме малявання Арыгінальны папярэдні прагляд выявы Альфа Эмодзі на панэлі прыкладанняў будуць пастаянна змяняцца выпадковым чынам замест выкарыстання выбранага Выпадковыя эмодзі Немагчыма выкарыстоўваць выпадковы выбар эмодзі, калі эмодзі адключаны Немагчыма выбраць эмодзі, калі ўключаны выпадковы выбар Высілак Значэнне %1$s азначае хуткае сцісканне, што прыводзіць да адносна вялікага памеру файла. %2$s азначае больш павольнае сцісканне, што прыводзіць да меншага файла. Пачакай Першасны троесны Паверхня Каштоўнасці Прыкладанню патрэбны гэты дазвол для працы, дайце яго ўручную Выбар фота паслядоўнасцьNum Загрузіце любую выяву з Інтэрнэту для папярэдняга прагляду, маштабавання, рэдагавання і захавання, калі хочаце. Няма выявы Спасылка на малюнак Памер файла Няправільны пароль або выбраны файл не зашыфраваны Згрупуйце параметры па тыпу Запасны варыянт Абмяркуйце прыкладанне і атрымайце водгукі ад іншых карыстальнікаў. Вы таксама можаце атрымліваць абнаўленні бэта-версіі і інфармацыю тут. Аднавіць Пашкоджаны файл ці не рэзервовая копія Падарожжы і мясціны Мерапрыемствы Праграма для выдалення фону Аўтаматычнае выдаленне фону Аднавіць малюнак Рэжым сцірання Дазволіць бэта-версіі Сцерці фон Стварыць выпуск Ой… Нешта пайшло не так. Вы можаце напісаць мне, выкарыстоўваючы параметры ніжэй, і я паспрабую знайсці рашэнне Змяніць памер і канвертаваць Змяняйце памер дадзеных малюнкаў або канвертуйце іх у іншыя фарматы. Метададзеныя EXIF таксама можна рэдагаваць тут, выбраўшы адну выяву. Калі ўключана, шлях малявання будзе паказаны ў выглядзе стрэлкі Схема, якая вельмі падобная на схему кантэнту Працуйце з PDF-файламі: папярэдні прагляд, пераўтварэнне ў групу малюнкаў або стварэнне аднаго з дадзеных малюнкаў Стары тэлевізар Выпадковае размыццё Accuracy: %1$s Тып распазнавання Хуткі Стандартны Лепшы Рэжым сегментацыі Пэндзаль адновіць фон замест сцірання Гарызантальная сетка Вертыкальная сетка Рэжым вышыўкі Колькасць радкоў Граф слупкоў слайд Побач Пераключыць кран Празрыстасць Перазапісаны файл з назвай %1$s у першапачатковым месцы прызначэння Лупа Ўключае лупу ў верхняй частцы пальца ў рэжымах малявання для лепшай даступнасці Прымусовае пачатковае значэнне Любімая Яшчэ не дададзены любімыя фільтры Б Сплайн Уласнае размыццё стэка Рэгулярны Размыць краю Малюе размытыя краю пад зыходным відарысам, каб запоўніць прастору вакол яго замест аднаго колеру, калі ўключана Зваротны тып запаўнення Калі ўключана, усе незамаскіраваныя вобласці будуць адфільтраваны замест паводзін па змаўчанні Канфеці Канфеці будзе паказвацца пры захаванні, абагульванні і іншых асноўных дзеяннях Бяспечны рэжым Хавае змесціва пры выхадзе, таксама немагчыма захапіць або запісаць экран Мяккасць пэндзля Фотаздымкі будуць абрэзаны па цэнтры да ўведзенага памеру. Палатно будзе разгорнута з зададзеным колерам фону, калі малюнак меншы за ўведзеныя памеры. Ахвяраванне Выхадны маштаб малюнка Арыентацыя выявы Гарызантальны Вертыкальны Маштабуйце маленькія выявы да вялікіх Стыль крыху больш храматычны, чым манахромны Гучная тэма, маляўнічасць максімальная для першаснай палітры, павышаная для іншых Папярэдні прагляд PDF Намалюйце напаўпразрысты завостраны контур маркера Размывае малюнак пад намаляваным шляхам, каб засцерагчы ўсё, што вы хочаце схаваць Кантэйнеры Ўключае малюнак ценяў за кантэйнерамі Паўзункі Выключальнікі ФАБ Гузікі Ўключае малюнак ценяў за паўзункамі Ўключае малюнак ценяў за пераключальнікамі Ўключае малюнак ценяў за плаваючымі кнопкамі дзеянняў Ўключае малюнак ценяў за стандартнымі кнопкамі Увага Выцвітаючыя краю Абодва Інвертаваць колеры Замяняе колеры тэмы на адмоўныя, калі ўключана Выхад Калі вы выйдзеце з папярэдняга прагляду зараз, вам трэба будзе зноў дадаць выявы Ласо Малюе замкнуты заліты шлях па зададзеным шляху Фармат выявы Анагліф Шум Сартаванне пікселяў Ператасаваць Цёмныя колеру Выкарыстоўвае каляровую схему начнога рэжыму замест светлага варыянту Стварае палітру \"Material You\" з малюнка Скапіруйце як код \"Jetpack Compose\" Размыццё кольцы Перакрыжаванае размыццё Размыццё круга Размыццё зоркамі Лінейны зрух нахілу Тэгі для выдалення Інструменты APNG Пераўтварайце выявы ў малюнак APNG або выцягвайце кадры з дадзенага малюнка APNG Выявы ў APNG Каб пачаць, абярыце выяву APNG Размыццё ў руху APNG да малюнкаў Пераўтварыце файл APNG у пакет малюнкаў Zip Стварыце файл Zip з дадзеных файлаў або малюнкаў Пераўтварыце пакет малюнкаў у файл APNG Шырыня маркера перацягвання Тып канфеці Разрашыць праграме аўтаматычна ўставіць дадзеныя з буфера абмену, каб яны з\'явіліся на галоўным экране, і вы змаглі іх апрацаваць Колер гарманізацыі Узровень гарманізацыі Выкарыстоўваецца сціск са стратамі для памяншэння памеру файла замест сціску без страт Святочны Выбух Дождж Куткі Інструменты JXL JXL ў JPEG Выканайце перакадзіраванне без страт з JXL ў JPEG Выканайце перакадзіраванне без страт з JPEG ў JXL JPEG ў JXL Хуткае размыццё па Гаўсу 2D Хуткае размыццё па Гаўсу 4D Ўчора Выкарыстоўвае ўласны інструмент выбару малюнкаў замест наканаваных сістэмай Запыт Аўтаўстаўка Выканайце перакадзіраванне JXL ~ JPEG без страты якасці або пераўтварыце анімацыю GIF/APNG ў JXL Для пачатку абярыце відарыс JXL Хуткае размыццё па Гаўсу 3D Канфігурацыя каналаў Няма дазволаў Сёння Убудаваны інструмент выбару Ланцош Бесэль Метад паўторнай выбаркі, які падтрымлівае высакаякасную інтэрпаляцыю шляхам прымянення функцыі Беселя (jinc) да значэнняў пікселяў GIF ў JXL Канвертуйце выявы GIF ў аніміраваныя выявы JXL APNG ў JXL Канвертуйце выявы APNG ў аніміраваныя выявы JXL JXL ў Выявы Канвертуйце анімацыю JXL ў пакет малюнкаў Выявы ў JXL Канвертуйце партыю малюнкаў ў анімацыю JXL Паводзіны Прапусціць выбар файла Інструмент выбару файлаў будзе паказаны неадкладна, калі гэта магчыма, на выбраным экране Стварыць прэв\'ю Ўключае генерацыю папярэдняга прагляду, гэта можа дапамагчы пазбегнуць збояў на некаторых прыладах, гэта таксама адключае некаторыя функцыі рэдагавання ў рамках адной опцыі рэдагавання Сцісканне з стратамі Тып сціску Кантралюе хуткасць дэкадавання выніковага відарыса, гэта павінна дапамагчы адкрыць выніковы відарыс хутчэй, значэнне %1$s азначае самае павольнае дэкадаванне, ў той час як %2$s - самае хуткае, гэты параметр можа павялічыць памер выходнага відарыса Сартаванне Сартаваць па даце Сартаваць па даце (зваротна) Сартаваць па назве (зваротна) Сартаваць па назве Калі гэта адключана, то ў ландшафтным рэжыме налады будуць адчыняцца па кнопцы ў верхняй панэлі прыкладання, як заўсёды, замест сталай бачнай опцыі Ўключыце яго, і старонка настроек заўсёды будзе адкрывацца ў поўнаэкранным рэжыме, а не ў высоўнай скрыні Паспрабаваць зноў Паказаць налады ў альбомнай арыентацыі Выбраць Поўнаэкранныя налады Тып пераключальніка Множны выбар медыя Выбар медыя Памяншэнне выявы Квадратычны парог Допуск акруглення каардынат Дадаць новую тэчку Бітоў на сэмпл Кампрэсія Фотаметрычная інтэрпрэтацыя Сэмплаў на піксель Планарная канфігурацыя Кампоз Выкарыстоўвае перамыкач на аснове View, ён выглядае лепш за іншых і мае прыемную анімацыю Выкарыстоўвае перамыкач з Jetpack Compose, ён не так прыгожы, як на аснове View Таўшчыня лініі па змаўчанні Максімум Прывязка да памеру Піксель Флюент Выкарыстоўвае перамыкач у стылі Windows 11 на аснове сістэмы дызайну Fluent Куперціна Выкарыстоўвае iOS-падобны перамыкач на аснове сістэмы дызайну Куперціна Рэжым рухавіка Стары Сетка LSTM Стары & LSTM Канвертаваць Пераўтварэнне пакетаў выяваў у зададзены фармат Выявы ў SVG Трасіроўка дадзеных малюнкаў у выявы SVG Выкарыстоўваць выбарачную палітру Палітра квантавання будзе абрана, калі гэтая опцыя ўключана Пропуск шляху Выкарыстанне гэтай прылады для адсочвання вялікіх малюнкаў без памяншэння маштабу не рэкамендуецца, гэта можа прывесці да збою і павелічэнню часу апрацоўкі Перад апрацоўкай малюнак будзе паменшаны да меншых памераў, гэта дапаможа прыладзе працаваць хутчэй і бяспечней. Мінімальныя суадносіны колераў Парог ліній Маштаб шляху Скінуць уласцівасці Для ўсіх уласцівасцей будуць устаноўлены значэнні па змаўчанні. Звярніце ўвагу, што гэта дзеянне немагчыма адмяніць Падрабязны Y Cb Cr субвыбарка Y Cb Cr пазіцыянаванне Х Рэзалюцыя Дазвол Y Адзінка дазволу Зрушэнне паласы Радкоў на паласу Паласа падліку байтаў Фармат абмену JPEG Даўжыня фармату абмену JPEG Перадатная функцыя Уайт-Пойнт Першасныя каляровасці Каэфіцыенты Y Cb Cr Спасылка Чорны Белы Дата Час Апісанне выявы зрабіць мадэль праграмнае забеспячэнне Мастак Аўтарскае права Exif версія Flashpix версія Каляровая прастора Гама Pixel X Dimension Pixel Y Dimension Сціснутыя біты на піксель Заўвага вытворцы Каментар карыстальніка Звязаны гукавы файл Дата Час Арыгінал Дата Час Алічбаваны Час зрушэння Арыгінал часу зрушэння Час зрушэння алічбаваны Субсекундны час Арыгінал субсекунднага часу Падсекундны час алічбаваны Час уздзеяння Нумар F Праграму экспазіцыі Спектральная адчувальнасць Фатаграфічная адчувальнасць OECF Тып адчувальнасці Стандартная выхадная адчувальнасць Рэкамендаваны індэкс экспазіцыі Хуткасць ISO Шырата хуткасці ISO yyy Шырата хуткасці ISO zzz Значэнне вытрымкі Значэнне дыяфрагмы Значэнне яркасці Значэнне зрушэння экспазіцыі Максімальнае значэнне дыяфрагмы Прадметная дыстанцыя Рэжым вымярэння Успышка Прадметная вобласць Фокусная адлегласць Энергія ўспышкі Прасторавая частотная характарыстыка Разрозненне X у факальнай плоскасці Раздзяленне факальнай плоскасці Y Блок раздзялення ў факальнай плоскасці Размяшчэнне прадмета Індэкс экспазіцыі Метад зандзіравання Крыніца файла Шаблон CFA Атрымана на заказ Рэжым экспазіцыі Баланс белага Каэфіцыент лічбавага маштабавання Фокусная адлегласць у плёнцы 35 мм Тып захопу сцэны Кантроль узмацнення Кантраст Насычанасць Рэзкасць Апісанне налад прылады Дыяпазон адлегласці аб\'екта Унікальны ідэнтыфікатар выявы Імя ўладальніка камеры Серыйны нумар корпуса Спецыфікацыя аб\'ектыва Марка аб\'ектыва Мадэль аб\'ектыва Серыйны нумар аб\'ектыва Ідэнтыфікатар версіі GPS Шырата GPS Ref Шырата GPS GPS Даўгата Ref Даўгата GPS Вышыня па GPS GPS вышыня Пазнака часу GPS Спадарожнікі GPS Статус GPS Рэжым вымярэння GPS GPS DOP Спасылка на хуткасць GPS Хуткасць GPS GPS-трэк Ref GPS-трэк GPS Img Напрамак Ref Напрамак малюнка GPS Дата карты GPS GPS Dest Latitude Ref Шырата пункта прызначэння GPS GPS - даўгата пункта прызначэння Даўгата пункта прызначэння GPS Даз. пункт прызначэння GPS Пеленг GPS Спасылка на адлегласць да пункта прызначэння GPS Пункт прызначэння GPS Метад апрацоўкі GPS Інфармацыя пра вобласць GPS Штамп даты GPS Дыферэнцыял GPS Памылка пазіцыянавання GPS H Індэкс сумяшчальнасці Dng версія Памер кадравання па змаўчанні Пачатак папярэдняга прагляду выявы Даўжыня відарыса папярэдняга прагляду Рамка бакоў Ніжняя мяжа датчыка Левая мяжа датчыка Правая мяжа датчыка Верхняя мяжа датчыка ISO Намалюйце тэкст на шляху зададзеным шрыфтам і колерам Памер шрыфта Памер вадзянога знака Паўтарыць тэкст Бягучы тэкст будзе паўтарацца да канца шляху замест аднаразовага малявання Памер рыскі Выкарыстоўвайце абраны малюнак, каб намаляваць яго ўздоўж зададзенага шляху Гэты відарыс будзе выкарыстоўвацца як паўтаральны запіс намаляванага шляху Малюе акрэслены трохвугольнік ад пачатковай да канчатковай кропкі Малюе акрэслены трохвугольнік ад пачатковай да канчатковай кропкі Акрэслены трохкутнік Трохвугольнік Малюе шматкутнік ад пачатковай да канчатковай кропкі Шматкутнік Акрэслены шматкутнік Малюе акрэслены шматкутнік ад пачатковай да канчатковай кропкі Вершыні Намалюйце правільны шматкутнік Намалюйце шматкутнік, які будзе правільнай, а не свабоднай формы Малюе зорку ад пачатковай да канчатковай кропкі Зорка Акрэсленая зорка Малюе акрэсленую зорку ад пачатковай да канчатковай кропкі Суадносіны ўнутранага радыусу Намалюйце звычайную зорку Намалюйце зорку, якая будзе правільнай, а не свабоднай формы Антыаліас Уключае згладжванне для прадухілення вострых краёў Адкрыйце праўку замест папярэдняга прагляду Калі вы выбіраеце відарыс для адкрыцця (прагляду) у ImageToolbox, замест папярэдняга прагляду будзе адкрыты аркуш выбару рэдагавання Сканер дакументаў Сканіруйце дакументы і стварайце PDF або асобныя выявы з іх Націсніце, каб пачаць сканаванне Пачаць сканаванне Захаваць як pdf Падзяліцца як pdf Параметры ніжэй прызначаны для захавання малюнкаў, а не PDF Выраўнаваць гістаграму HSV Выраўнаваць гістаграму Увядзіце працэнт Дазволіць увод праз тэкставае поле Уключае тэкставае поле за выбарам перадустановак, каб уводзіць іх на лета Шкала Каляровая прастора Лінейны Выраўноўванне пікселізацыі гістаграмы Памер сеткі X Памер сеткі Y Адаптыўная гістаграма выраўноўвання Адаптыўны LUV для выраўноўвання гістаграмы Equalize Histogram Adaptive LAB КЛАЕ ЛАБАРАТЫЯ КЛАЭ КЛАЭ ЛУВ Абрэзаць да змесціва Колер рамкі Колер, які трэба ігнараваць Шаблон Ніякіх шаблонных фільтраў не дададзена Стварыць новы Адсканаваны QR-код не з\'яўляецца сапраўдным шаблонам фільтра Адсканіраваць QR-код Выбраны файл не мае даных шаблону фільтра Стварыць шаблон Імя шаблона Гэта выява будзе выкарыстоўвацца для папярэдняга прагляду гэтага шаблону фільтра Фільтр шаблонаў Як выява QR-кода Як файл Захаваць як файл Захаваць як відарыс QR-кода Выдаліць шаблон Вы збіраецеся выдаліць выбраны фільтр шаблона. Гэтую аперацыю нельга адмяніць Дададзены шаблон фільтра з назвай \"%1$s\" (%2$s) Папярэдні прагляд фільтра QR і штрых-код Адсканіруйце QR-код і атрымайце яго змесціва або ўстаўце свой радок, каб стварыць новы Змест кода Адсканіруйце любы штрых-код, каб замяніць змесціва ў полі, або ўвядзіце што-небудзь, каб стварыць новы штрых-код выбранага тыпу QR-апісанне Мін Дайце дазвол камеры ў наладах для сканіравання QR-кода Дайце дазвол камеры ў наладах, каб сканаваць сканер дакументаў Кубічны Б-сплайн Хэмінг Ханінг Чорны чалавек Уэлч Квадрычны Гаўсава Сфінкс Бартлет Рабіду Робіду Шарп Сплайн 16 Сплайн 36 Сплайн 64 Кайзер Бартлетт-Ён скрынка Боман Ланцош 2 Ланцош 3 Ланцош 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Кубічная інтэрпаляцыя забяспечвае больш плыўнае маштабаванне з улікам бліжэйшых 16 пікселяў, даючы лепшыя вынікі, чым білінейная Выкарыстоўвае шматчленныя функцыі для плыўнай інтэрпаляцыі і апраксімацыі крывой або паверхні, гнуткае і бесперапыннае прадстаўленне формы Аконная функцыя, якая выкарыстоўваецца для памяншэння спектральнай уцечкі шляхам звужэння краёў сігналу, карысная пры апрацоўцы сігналаў Варыянт акна Хана, які звычайна выкарыстоўваецца для памяншэння спектральнай уцечкі ў праграмах апрацоўкі сігналаў Аконная функцыя, якая забяспечвае добрае дазвол па частотах за кошт мінімізацыі спектральнай уцечкі, часта выкарыстоўваецца пры апрацоўцы сігналаў Аконная функцыя, прызначаная для забеспячэння добрага частотнага раздзялення з паменшанай спектральнай уцечкай, часта выкарыстоўваецца ў праграмах апрацоўкі сігналаў Метад, які выкарыстоўвае квадратычную функцыю для інтэрпаляцыі, забяспечваючы гладкія і бесперапынныя вынікі Метад інтэрпаляцыі, які прымяняе функцыю Гаўса, карысны для згладжвання і памяншэння шуму ў выявах Удасканалены метад паўторнай выбаркі, які забяспечвае высакаякасную інтэрпаляцыю з мінімальнымі артэфактамі Функцыя трохвугольнага акна, якая выкарыстоўваецца пры апрацоўцы сігналаў для памяншэння спектральнай уцечкі Высакаякасны метад інтэрпаляцыі, аптымізаваны для натуральнага змены памеру выявы, балансу выразнасці і плыўнасці Больш рэзкі варыянт метаду Робіду, аптымізаваны для выразнага змены памеру выявы Метад інтэрпаляцыі на аснове сплайнаў, які забяспечвае плыўныя вынікі з дапамогай фільтра з 16 націскамі Метад інтэрпаляцыі на аснове сплайнаў, які забяспечвае гладкія вынікі з выкарыстаннем фільтра з 36 націскамі Метад інтэрпаляцыі на аснове сплайнаў, які забяспечвае гладкія вынікі з выкарыстаннем фільтра з 64 націскамі Метад інтэрпаляцыі, які выкарыстоўвае акно Кайзера, забяспечваючы добры кантроль над кампрамісам паміж шырынёй галоўнага пялёстка і ўзроўнем бакавых пялёсткаў Функцыя гібрыднага акна, якая аб\'ядноўвае вокны Бартлета і Хана, выкарыстоўваецца для памяншэння спектральнай уцечкі пры апрацоўцы сігналаў Просты метад паўторнай выбаркі, які выкарыстоўвае сярэдняе значэнне бліжэйшых значэнняў пікселяў, што часта прыводзіць да з\'яўлення блокаў Аконная функцыя, якая выкарыстоўваецца для памяншэння спектральнай уцечкі, забяспечваючы добрае дазвол частоты ў праграмах апрацоўкі сігналаў Метад паўторнай выбаркі, які выкарыстоўвае 2-лепестковы фільтр Ланцоша для высакаякаснай інтэрпаляцыі з мінімальнымі артэфактамі Метад паўторнай выбаркі, які выкарыстоўвае 3-лепестковы фільтр Lanczos для высакаякаснай інтэрпаляцыі з мінімальнымі артэфактамі Метад паўторнай выбаркі, які выкарыстоўвае 4-лепестковы фільтр Lanczos для высакаякаснай інтэрпаляцыі з мінімальнымі артэфактамі Варыянт фільтра Lanczos 2, які выкарыстоўвае функцыю jinc, забяспечваючы высакаякасную інтэрпаляцыю з мінімальнымі артэфактамі Варыянт фільтра Lanczos 3, які выкарыстоўвае функцыю jinc, забяспечваючы высакаякасную інтэрпаляцыю з мінімальнымі артэфактамі Варыянт фільтра Lanczos 4, які выкарыстоўвае функцыю jinc, забяспечваючы высакаякасную інтэрпаляцыю з мінімальнымі артэфактамі Ханінг EWA Эліптычны сярэднеўзважаны (EWA) варыянт фільтра Ханінга для плыўнай інтэрпаляцыі і паўторнай выбаркі Рабіду ЭВА Эліптычны сярэднеўзважаны (EWA) варыянт фільтра Robidoux для высакаякаснай паўторнай выбаркі Blackman EVE Эліптычны сярэднеўзважаны (EWA) варыянт фільтра Блэкмана для мінімізацыі артэфактаў гуку Quadric EWA Эліптычны сярэднеўзважаны варыянт (EWA) фільтра Quadric для гладкай інтэрпаляцыі Robidoux Sharp EWA Эліптычны сярэднеўзважаны (EWA) варыянт фільтра Robidoux Sharp для больш выразных вынікаў Lanczos 3 Jinc EWA Варыянт фільтра Lanczos 3 Jinc з эліптычным сярэднеўзважаным значэннем (EWA) для высакаякаснай паўторнай дыскрэтызацыі з паменшаным згладжваннем Жэньшэнь Фільтр паўторнай выбаркі, прызначаны для высакаякаснай апрацоўкі малюнкаў з добрым балансам рэзкасці і плыўнасці Жэньшэнь EWA Эліптычны сярэднеўзважаны (EWA) варыянт фільтра Ginseng для паляпшэння якасці выявы Lanczos Sharp EWA Elliptical Weighted Average (EWA) варыянт фільтра Lanczos Sharp для дасягнення выразных вынікаў з мінімальнымі артэфактамі Lanczos 4 Sharpest EWA Варыянт Elliptical Weighted Average (EWA) фільтра Lanczos 4 Sharpest для надзвычай выразнай паўторнай выбаркі выявы Lanczos Soft EWA Эліптычны сярэднеўзважаны (EWA) варыянт мяккага фільтра Lanczos для больш плыўнай паўторнай выбаркі выявы Мяккі Haasn Фільтр паўторнай выбаркі, распрацаваны Haasn для плыўнага і без артэфактаў маштабавання выявы Пераўтварэнне фарматаў Пераўтварыце партыю малюнкаў з аднаго фармату ў іншы Звольніць назаўжды Стэкінг малюнкаў Складайце выявы адзін на аднаго з выбранымі рэжымамі змешвання Дадаць выяву Лічыць бункеры Clahe HSL Clahe HSV Адаптыўны HSL выраўноўвання гістаграмы Адаптыўная гістаграма Equalize HSV Рэжым Edge Кліп Абгарнуць Барваслепасць Выберыце рэжым, каб адаптаваць колеры тэмы для абранага варыянту дальтанізму Цяжка адрозніць чырвоны і зялёны адценні Цяжка адрозніць зялёны і чырвоны адценні Цяжка адрозніць сіні і жоўты адценні Няздольнасць ўспрымаць чырвоныя адценні Няздольнасць ўспрымаць зялёныя адценні Няздольнасць ўспрымаць блакітныя адценні Зніжэнне адчувальнасці да ўсіх колераў Поўная барваслепасць, бачыць толькі адценні шэрага Не выкарыстоўвайце схему дальтоніка Колеры будуць дакладна такімі, якія зададзены ў тэме Сігмападобная Лагранж 2 Інтэрпаляцыйны фільтр Лагранжа парадку 2, прыдатны для высакаякаснага маштабавання выявы з плыўнымі пераходамі Лагранж 3 Інтэрпаляцыйны фільтр Лагранжа парадку 3, які прапануе лепшую дакладнасць і больш плыўныя вынікі для маштабавання выявы Ланцош 6 Фільтр паўторнай выбаркі Lanczos з больш высокім парадкам 6, які забяспечвае больш выразнае і дакладнае маштабаванне выявы Lanczos 6 Jinc Варыянт фільтра Lanczos 6 з выкарыстаннем функцыі Jinc для паляпшэння якасці перадвыбаркі выявы Лінейнае размыццё поля Лінейнае размыццё палаткі Лінейнае размыццё скрынкі Гаўса Лінейнае размыццё стэка Размыццё скрынкі Гаўса Лінейнае хуткае размыццё па Гаўсу Далей Хуткае лінейнае размыццё па Гаўсу Лінейнае размыццё па Гаўсу Выберыце адзін фільтр, каб выкарыстоўваць яго ў якасці фарбы Замяніць фільтр Выберыце ніжэй фільтр, каб выкарыстоўваць яго ў якасці пэндзля ў сваім малюнку Схема сціску TIFF Нізкая полі Карціна пяском Раздзяленне выявы Разбіце адзін малюнак на радкі або слупкі Адпавядаць межам Аб\'яднайце рэжым змены памеру абрэзкі з гэтым параметрам, каб дасягнуць жаданага паводзін (абрэзка/падгонка да суадносін бакоў) Мовы паспяхова імпартаваны Рэзервовае капіраванне мадэляў OCR Імпарт Экспарт Пазіцыя Цэнтр Уверсе злева Уверсе справа Унізе злева Унізе справа Верхні цэнтр Правы цэнтр Ніжні цэнтр Левы цэнтр Мэтавы малюнак Перанос палітры Палепшанае алей Просты стары тэлевізар HDR Готэм Просты эскіз Мяккае ззянне Каляровы плакат Тры тоны Трэці колер Клахе Аклаб Клара Олх Clahe Jzazbz Гарошак Кластарны згладжванне 2x2 Кластарны 4x4 Dithering Кластарны дызерінг 8x8 Yililoma Dithering Любыя параметры не выбраны, дадайце іх на старонцы інструментаў Дадаць абранае Камплементарныя Аналаг Трыяда Спліт дадатковы Тэтрадычны квадратны Аналагічны + Дапаўняльны Інструменты колеру Змешвайце, стварайце тоны, стварайце адценні і многае іншае Каляровыя гармоніі Зацяненне колеру Варыяцыя Адценні Тоны Адценні Змешванне колераў Інфармацыя пра колер Абраны колер Колер для змешвання Немагчыма выкарыстоўваць Манэ, калі ўключаны дынамічныя колеры 512x512 2D LUT Мэтавая выява LUT Аматар Міс Этыкет Мяккая элегантнасць Варыянт Soft Elegance Варыянт перадачы палітры 3D LUT Мэтавы файл 3D LUT (.cube / .CUBE) LUT Абыход адбельвальніка Святло свечак Блюз Вострая Эмбер Колеры восені Фільм фонд 50 Туманная ноч Кодак Атрымаць выяву нейтральнага LUT Спачатку выкарыстайце сваё любімае прыкладанне для рэдагавання фатаграфій, каб прымяніць фільтр да нейтральнай LUT, які вы можаце атрымаць тут. Каб гэта працавала належным чынам, колер кожнага пікселя не павінен залежаць ад іншых пікселяў (напрыклад, размыццё не будзе працаваць). Пасля гатоўнасці выкарыстоўвайце свой новы малюнак LUT у якасці ўваходных дадзеных для фільтра LUT 512*512 Поп-арт Цэлюлоід Кава Залаты лес Зеленаватыя Рэтра жоўты Папярэдні прагляд спасылак Уключае папярэдні прагляд спасылак у месцах, дзе можна атрымаць тэкст (QRCode, OCR і г.д.) Спасылкі Файлы ICO можна захоўваць толькі ў максімальным памеры 256 x 256 GIF у WEBP Пераўтварыце выявы GIF у аніміраваныя выявы WEBP Інструменты WEBP Пераўтварайце выявы ў аніміраваныя выявы WEBP або выцягвайце кадры з дадзенай анімацыі WEBP WEBP да малюнкаў Пераўтварыце файл WEBP у пакет малюнкаў Пераўтварэнне пакета малюнкаў у файл WEBP Выявы ў WEBP Каб пачаць, абярыце малюнак WEBP Няма поўнага доступу да файлаў Дазволіць доступ да ўсіх файлаў, каб бачыць JXL, QOI і іншыя выявы, якія не распазнаюцца як выявы на Android. Без дазволу Image Toolbox не можа паказаць гэтыя выявы Колер малявання па змаўчанні Рэжым шляху малявання па змаўчанні Дадаць метку часу Дазваляе дадаваць метку часу да імя выходнага файла Адфарматаваная метка часу Уключыць фарматаванне меткі часу ў назве выходнага файла замест асноўных мілі Уключыце меткі часу, каб выбраць іх фармат Аднаразовае захаванне месцазнаходжання Праглядвайце і рэдагуйце месцы аднаразовага захавання, якімі вы можаце карыстацца, доўга націскаючы кнопку захавання ў большасці варыянтаў Нядаўна выкарыстаны Канал CI Група Панэль інструментаў малюнкаў у Telegram 🎉 Далучайцеся да нашага чата, дзе вы можаце абмяркоўваць усё, што заўгодна, а таксама зазірніце ў канал CI, дзе я публікую бэта-версіі і аб\'явы Атрымлівайце апавяшчэнні аб новых версіях праграмы і чытайце аб\'явы Адагнайце малюнак да зададзеных памераў і прымяніце размыццё або колер фону Размяшчэнне інструментаў Згрупуйце інструменты па тыпу Групуе інструменты на галоўным экране па іх тыпу замест карыстацкага спісу Значэнні па змаўчанні Бачнасць сістэмных палос Паказаць сістэмныя панэлі, правёўшы пальцам Уключае правядзенне пальцам, каб паказаць сістэмныя панэлі, калі яны схаваныя Аўто Схаваць усе Паказаць усе Схаваць панэль навігацыі Схаваць радок стану Генерацыя шуму Стварайце розныя шумы, такія як Perlin або іншыя тыпы Частата Тып шуму Тып кручэння Фрактальны тып Актавы Лакунарность Узмацненне Узважаная сіла Сіла пінг-понга Функцыя адлегласці Тып вяртання Дрыгаценне Дэфармацыя дамена Выраўноўванне Карыстальніцкае імя файла Выберыце месцазнаходжанне і імя файла, якія будуць выкарыстоўвацца для захавання бягучага малюнка Захавана ў тэчку з карыстальніцкай назвай Стваральнік калажаў Стварайце калажы з максімум 20 малюнкаў Тып калажа Утрымлівайце малюнак, каб памяняць месцамі, перамяшчаць і маштабаваць, каб адрэгуляваць становішча Адключыць паварот Прадухіляе паварот малюнкаў з дапамогай жэстаў двума пальцамі Уключыць прывязку да межаў Пасля перамяшчэння або павелічэння маштабу відарысы запаўняюць краю кадра Гістаграма Гістаграма выявы RGB або яркасці, якая дапаможа вам унесці карэктывы Гэта выява будзе выкарыстоўвацца для стварэння гістаграм RGB і яркасці Параметры тэсеракта Прымяніць некаторыя ўваходныя зменныя для рухавіка tesseract Карыстальніцкія параметры Варыянты трэба ўводзіць па наступным шаблоне: \"--{назва_опцыі} {значэнне}\" Аўтаматычнае абрэзка Свабодныя куткі Абрэзаць малюнак па шматкутніку, гэта таксама карэктуе ракурс Coerce паказвае на межы выявы Ачкі не будуць абмежаваныя межамі выявы, гэта карысна для больш дакладнай карэкцыі перспектывы Маска Запаўненне з улікам змесціва пад намаляваным шляхам Вылечыць пляму Выкарыстоўвайце Circle Kernel Адкрыццё Закрыццё Марфалагічны градыент Цыліндр Чорны Капялюш Крывыя тоны Скінуць крывыя Крывыя будуць адкачаны да значэння па змаўчанні Стыль лініі Памер зазору Пункцірная Пункцірная кропка Штампаваны Зігзаг Малюе пункцірную лінію ўздоўж намаляванага шляху з зададзеным памерам прабелу Малюе кропкавую і пункцірную лініі ўздоўж зададзенага шляху Проста прамыя лініі па змаўчанні Малюе выбраныя фігуры ўздоўж шляху з зададзеным інтэрвалам Малюе па сцяжынцы хвалісты зігзаг Зігзагападобныя суадносіны Стварыць ярлык Выберыце інструмент для замацавання Інструмент будзе дададзены на галоўны экран праграмы запуску ў якасці цэтліка, выкарыстоўвайце яго ў спалучэнні з наладай \"Прапусціць выбар файлаў\", каб дасягнуць неабходных паводзін Не складайце кадры Дазваляе ўтылізаваць папярэднія кадры, каб яны не накладваліся адзін на аднаго Перакрыжаваны плынь Кадры будуць пераходзіць адзін у аднаго Колькасць кадраў перакрыжаванага плыні Парог адзін Парог другі Кані Люстэрка 101 Палепшанае размыццё зуму Лапласа просты Собель Просты Дапаможная сетка Паказвае апорную сетку над вобласцю малявання, каб дапамагчы з дакладнымі маніпуляцыямі Колер сеткі Шырыня клеткі Вышыня клеткі Кампактныя селектары Некаторыя элементы кіравання выбарам будуць выкарыстоўваць кампактны макет, каб займаць менш месца Дайце камере дазвол у наладах, каб зрабіць відарыс Макет Назва галоўнага экрана Каэфіцыент пастаяннай хуткасці (CRF) Значэнне %1$s азначае павольнае сцісканне, што прыводзіць да адносна невялікага памеру файла. %2$s азначае больш хуткае сцісканне, што прыводзіць да вялікага файла. Луцкая бібліятэка Спампуйце калекцыю LUT, якую вы можаце прымяніць пасля загрузкі Абнавіць калекцыю LUT (толькі новыя будуць пастаўлены ў чаргу), якую можна прымяніць пасля спампоўкі Змяніць папярэдні прагляд выявы па змаўчанні для фільтраў Папярэдні прагляд выявы Схаваць Паказаць Тып паўзунка Вычварны Матэрыял 2 Прыгожы слайдэр. Гэта параметр па змаўчанні Слайдэр Material 2 Паўзунок A Material You Ужыць Цэнтральныя кнопкі дыялогу Кнопкі дыялогавых вокнаў будуць размешчаны ў цэнтры, а не злева, калі гэта магчыма Ліцэнзіі з адкрытым зыходным кодам Праглядзіце ліцэнзіі на бібліятэкі з адкрытым зыходным кодам, якія выкарыстоўваюцца ў гэтым дадатку Плошча Паўторная выбарка з выкарыстаннем адносіны плошчы пікселяў. Гэта можа быць пераважным метадам для дэцымацыі выявы, паколькі ён дае вынікі без муара. Але калі выява павялічана, гэта падобна на метад \"Бліжэйшы\". Уключыць Tonemapping Увядзіце % Немагчыма атрымаць доступ да сайта, паспрабуйце выкарыстоўваць VPN або праверце, ці правільны URL Пласты разметкі Рэжым слаёў з магчымасцю свабоднага размяшчэння малюнкаў, тэксту і іншага Рэдагаваць пласт Пласты на малюнку Выкарыстоўвайце малюнак у якасці фону і дадавайце па-над ім розныя пласты Пласты на фоне Тое самае, што і першы варыянт, але з колерам замест малюнка Бэта-версія Бок хуткіх налад Дадайце плаваючую паласу на абраным баку падчас рэдагавання малюнкаў, якая адкрые хуткія налады пры націску Ачысціць выбар Група налад \"%1$s\" будзе згорнута па змаўчанні Група налад \"%1$s\" будзе пашырана па змаўчанні Інструменты Base64 Дэкадзіраваць радок Base64 у відарыс або закадзіраваць відарыс у фармат Base64 База64 Уведзенае значэнне не з\'яўляецца сапраўдным радком Base64 Немагчыма скапіяваць пусты або несапраўдны радок Base64 Уставіць Base64 Скапіруйце Base64 Загрузіце малюнак, каб скапіяваць або захаваць радок Base64. Калі ў вас ёсць сам радок, вы можаце ўставіць яго вышэй, каб атрымаць малюнак Захаваць Base64 Падзяліцеся Base64 Параметры Дзеянні Імпарт Base64 Дзеянні Base64 Дадаць контур Дадайце контур вакол тэксту вызначаным колерам і шырынёй Колер контуру Памер контуру Кручэнне Кантрольная сума як імя файла Выхадныя выявы будуць мець назву, якая адпавядае іх кантрольнай суме дадзеных Бясплатнае праграмнае забеспячэнне (партнёр) Больш карыснага праграмнага забеспячэння ў партнёрскім канале прыкладанняў Android Алгарытм Інструменты кантрольнай сумы Параўноўвайце кантрольныя сумы, вылічвайце хэшы або стварайце шаснаццатковыя радкі з файлаў, выкарыстоўваючы розныя алгарытмы хэшавання Вылічыць Хэш тэксту Кантрольная сума Выберыце файл, каб вылічыць яго кантрольную суму на аснове абранага алгарытму Увядзіце тэкст для разліку яго кантрольнай сумы на аснове абранага алгарытму Зыходная кантрольная сума Кантрольная сума для параўнання Матч! Розніца Кантрольныя сумы роўныя, гэта можа быць бяспечна Кантрольныя сумы не роўныя, файл можа быць небяспечным! Градыенты сеткі Паглядзіце онлайн-калекцыю Mesh Gradients Можна імпартаваць толькі шрыфты TTF і OTF Імпартаваць шрыфт (TTF/OTF) Экспарт шрыфтоў Імпартаваныя шрыфты Памылка падчас спробы захавання, паспрабуйце змяніць тэчку вываду Імя файла не зададзена Няма Карыстальніцкія старонкі Выбар старонак Пацверджанне выхаду з інструмента Калі ў вас ёсць незахаваныя змены падчас выкарыстання пэўных інструментаў і вы спрабуеце закрыць яго, то будзе паказана дыялогавае акно пацверджання Рэдагаваць EXIF Змяніць метаданыя аднаго малюнка без паўторнага сціску Націсніце, каб змяніць даступныя тэгі Змяніць стыкер Адпавядаць шырыні Адпавядаць вышыні Параўнанне партый Выберыце файл/файлы для разліку яго кантрольнай сумы на аснове абранага алгарытму Выберыце файлы Выберыце каталог Шкала даўжыні галавы Марка Метка часу Шаблон фармату Падкладка Нарэзка малюнкаў Выражыце частку малюнка і аб\'яднайце левыя (можна наадварот) вертыкальнымі або гарызантальнымі лініямі Вертыкальная разводная лінія Гарызантальная апорная лінія Адваротны выбар Частка вертыкальнага разрэзу будзе пакінута, замест аб\'яднання частак вакол вобласці разрэзу Гарызантальная выразаная частка будзе пакінута замест аб\'яднання частак вакол выразанай вобласці Калекцыя сеткаватых градыентаў Стварыце градыент сеткі з індывідуальнай колькасцю вузлоў і дазволам Градыентнае накладанне сеткі Складзіце сеткаваты градыент верхняй частцы дадзеных відарысаў Настройка балаў Памер сеткі Рэзалюцыя X Рэзалюцыя Ю дазвол Піксель за пікселем Колер вылучэння Тып параўнання пікселяў Сканаваць штрых-код Каэфіцыент вышыні Тып штрых-кода Прымяніць Ч/Б Відарыс штрых-кода будзе цалкам чорна-белым і не будзе афарбаваны тэмай праграмы Адсканіруйце любы штрых-код (QR, EAN, AZTEC, …) і атрымайце яго змест або ўстаўце свой тэкст, каб стварыць новы Штрых-код не знойдзены Згенераваны штрых-код будзе тут Аўдыё вокладкі Выманне малюнкаў вокладкі альбома з аўдыяфайлаў, падтрымліваюцца найбольш распаўсюджаныя фарматы Выберыце аўдыя для пачатку Выберыце Аўдыё Вокладкі не знойдзены Адправіць часопісы Націсніце, каб падзяліцца файлам журналаў праграмы, гэта можа дапамагчы мне выявіць праблему і выправіць праблемы На жаль… Нешта пайшло не так Вы можаце звязацца са мной, выкарыстоўваючы варыянты ніжэй, і я паспрабую знайсці рашэнне.\n(Не забудзьцеся далучыць журналы) Запіс у файл Вылучыце тэкст з пакета малюнкаў і захавайце яго ў адным тэкставым файле Запіс у метададзеныя Вылучыце тэкст з кожнай выявы і змесціце яго ў інфармацыю EXIF ​​адпаведных фатаграфій Нябачны рэжым Выкарыстоўвайце стэганаграфію, каб ствараць нябачныя вадзяныя знакі ўнутры байтаў вашых малюнкаў Выкарыстоўвайце LSB Будзе выкарыстоўвацца метад стэганаграфіі LSB (менш важны біт), у адваротным выпадку FD (частотны дамен) Аўтаматычнае выдаленне чырвоных вачэй Пароль Разблакіраваць PDF абаронены Аперацыя амаль завершана. Каб скасаваць зараз, спатрэбіцца перазапусціць яго Дата змены Дата змены (адменена) Памер Памер (перавернуты) Тып MIME Тып MIME (перавернуты) Пашырэнне Пашырэнне (перавернута) Дата дадання Дата дадання (зваротная) Злева направа Справа налева Зверху ўніз Знізу ўверх Вадкае шкло Пераключальнік, заснаваны на нядаўна абвешчанай IOS 26 і яго сістэме дызайну з вадкага шкла Выберыце малюнак або ўстаўце/імпартуйце даныя Base64 ніжэй Каб пачаць, увядзіце спасылку на выяву Уставіць спасылку Калейдаскоп Другасны вугал Бакі Мікс канала Сіне-зялёны Чырвоны сіні Зялёны чырвоны У чырвоны У зялёны У блакіт Блакітны Пурпурны Жоўты Каляровы паўтон Контур Узроўні Зрушэнне Вараной Крышталізуйся Форма Расцяжка Выпадковасць Ачысціць плямы Дыфузны DoG Другі радыус Зраўнаваць Свяціцца Круціцца і шчыпаць Пуантылізаваць Колер мяжы Палярныя каардынаты Прамая да палярнай Палярны ў прамы Перавярнуць у круг Паменшыць шум Простая салярызацыя Ткаць X Gap Y Gap Шырыня X Шырыня Y Круціцца Гумовы штамп Мазок Шчыльнасць Змяшаць Скажэнне сферычнай лінзы Паказчык праламлення Арк Кут разгортвання Бліск Прамяні ASCII Градыент Мэры Восень Костка Рэактыўны зіма Акіян лета Вясна Класны варыянт ВПГ Ружовы Горача Слова Магма Пекла плазма Вірыдзіс Грамадзяне Змрок Змрок зрушаны Аўтаперспектыва Выправіць перакос Дазволіць абрэзку Абрэзка або перспектыва Абсалютны Турба Цёмна-зялёны Карэкцыя аб\'ектыва Файл профілю мэтавай лінзы ў фармаце JSON Спампаваць гатовыя профілі лінзаў Частка працэнтаў Экспарт у фармаце JSON Скапіруйце радок з дадзенымі палітры ў выглядзе прадстаўлення json Разьба швоў Галоўны экран Экран блакіроўкі Убудаваны Экспарт шпалер Абнавіць Атрымайце бягучыя шпалеры Home, Lock і Built-in Дазволіць доступ да ўсіх файлаў, гэта неабходна для атрымання шпалер Дазволу на кіраванне знешнім сховішчам недастаткова, вам трэба дазволіць доступ да вашых малюнкаў, не забудзьцеся выбраць \"Дазволіць усё\" Дадаць наладку да імя файла Дадае суфікс з выбранай перадусталёўкай да назвы файла выявы Дадайце рэжым маштабавання выявы ў імя файла Дадае суфікс з абраным рэжымам маштабу выявы да назвы файла выявы Ascii арт Пераўтварыце малюнак у тэкст ASCII, які будзе выглядаць як малюнак Параметры Прымяняе адмоўны фільтр да выявы для лепшага выніку ў некаторых выпадках Апрацоўка скрыншота Здымак экрана не зроблены, паўтарыце спробу Захаванне прапушчана %1$s файлаў прапушчана Дазволіць прапусціць, калі больш Некаторым інструментам будзе дазволена прапускаць захаванне малюнкаў, калі выніковы памер файла будзе большы за арыгінальны Каляндар падзей Кантакт Электронная пошта Размяшчэнне Тэлефон Тэкст SMS URL Wi-Fi Адкрытая сетка Н/Д SSID Тэлефон паведамленне Адрас Тэма Цела Імя Арганізацыя Назва Тэлефоны Электронныя лісты URL-адрасы Адрасы Рэзюмэ Апісанне Размяшчэнне Арганізатар Дата пачатку Дата заканчэння Статус Шырата Даўгата Стварыць штрых-код Рэдагаваць штрых-код Канфігурацыя Wi-Fi Бяспека Выберыце кантакт Дайце кантактам дазвол у наладах на аўтазапаўненне з дапамогай выбранага кантакту Кантактная інфармацыя імя Імя па бацьку Прозвішча Вымаўленне Дадаць тэлефон Дадаць адрас электроннай пошты Дадайце адрас Вэб-сайт Дадаць сайт Адфарматаванае імя Гэта выява будзе выкарыстоўвацца для размяшчэння над штрых-кодам Налада кода Гэта выява будзе выкарыстоўвацца ў якасці лагатыпа ў цэнтры QR-кода Лагатып Абіўка лагатыпа Памер лагатыпа Куты лагатыпа Чацвёртае вока Дадае сіметрыю вачэй у qr-код, дадаючы чацвёртае вока ў ніжнім кантавым куце Форма пікселя Форма каркаса Форма шара Узровень выпраўлення памылак Цёмны колер Светлы колер Гіпер АС Xiaomi HyperOS, як стыль Выкрайка маскі Гэты код можа быць не сканіраваны, змяніце параметры выгляду, каб зрабіць яго чытальным на ўсіх прыладах Не сканіруецца Інструменты будуць выглядаць як праграма запуску праграм на галоўным экране, каб быць больш кампактнымі Рэжым запуску Запаўняе вобласць абраным пэндзлем і стылем Заліванне паводкай Спрэй Малюе шлях у стылі графіці Квадратныя часціцы Часціцы спрэю будуць мець квадратную форму, а не кругі Інструменты палітры Стварыце асноўны/матэрыял, які вы палітруеце, з выявы або імпартуйце/экспартуйце ў розныя фарматы палітры Рэдагаваць палітру Экспарт/імпарт палітры ў розных фарматах Назва колеру Імя палітры Фармат палітры Экспарт згенераванай палітры ў розныя фарматы Дадае новы колер да бягучай палітры Фармат %1$s не падтрымлівае назву палітры З-за палітыкі Крамы Play гэтая функцыя не можа быць уключана ў бягучую зборку. Каб атрымаць доступ да гэтай функцыі, загрузіце ImageToolbox з альтэрнатыўнай крыніцы. Вы можаце знайсці даступныя зборкі на GitHub ніжэй. Адкрыць старонку Github Арыгінальны файл будзе заменены новым замест захавання ў выбранай папцы Выяўлены схаваны тэкст вадзянога знака Выяўлена схаваная выява вадзянога знака Гэта выява была схаваная Генератыўны жывапіс Дазваляе выдаляць аб\'екты на малюнку з дапамогай мадэлі штучнага інтэлекту, не абапіраючыся на OpenCV. Каб выкарыстоўваць гэту функцыю, праграма загрузіць неабходную мадэль (~200 МБ) з GitHub Дазваляе выдаляць аб\'екты на малюнку з дапамогай мадэлі штучнага інтэлекту, не абапіраючыся на OpenCV. Гэта можа быць працяглая аперацыя Аналіз ўзроўню памылак Градыент яркасці Сярэдняя адлегласць Выяўленне перамяшчэння капіявання Захаваць Каэфіцыент Даныя буфера абмену занадта вялікія Дадзеныя занадта вялікія для капіравання Простая пікселізацыя Weave Паступовая пікселізацыя Перакрыжаваная пікселізацыя Мікра Макра пікселізацыя Арбітальная пікселізацыя Віхравая пікселізацыя Піксэлізацыя імпульснай сеткі Пікселізацыю ядра Радыяльнае перапляценне пікселізацыі Немагчыма адкрыць uri \"%1$s\" Рэжым снегападу Уключаны Бардзюрная рамка Варыянт глюка Зрух канала Максімальнае зрушэнне VHS Блок Глюк Памер блока крывізна ЭПТ Скрыўленне каляровасць Pixel Melt Макс Дроп Інструменты штучнага інтэлекту Розныя інструменты для апрацоўкі відарысаў з дапамогай мадэляў штучнага інтэлекту, напрыклад, выдаленне артэфактаў або шумашумленне Сцісканне, няроўныя лініі Мультфільмы, сціск трансляцыі Агульная кампрэсія, агульны шум Бясколерны шум мультфільма Хуткі, агульнае сцісканне, агульны шум, анімацыя/коміксы/анімэ Сканаванне кнігі Карэкцыя экспазіцыі Найлепшыя пры агульным сціску, каляровыя выявы Лепш за ўсё пры агульным сціску, выявы ў адценнях шэрага Агульнае сцісканне, выявы ў адценнях шэрага мацней Агульны шум, каляровыя выявы Агульны шум, каляровыя малюнкі, лепшыя дэталі Агульны шум, выявы ў адценнях шэрага Агульны шум, малюнкі ў адценнях шэрага мацней Агульны шум, відарысы ў адценнях шэрага, самы моцны Агульная кампрэсія Агульная кампрэсія Тэкстурацыя, сціск h264 Кампрэсія VHS Нестандартнае сцісканне (cinepak, msvideo1, roq) Сціск Bink, лепш па геаметрыі Сціск Bink, мацней Сціск Bink, мяккі, захоўвае дэталі Ліквідацыю эфекту лесвіцы, згладжванне Адсканаваныя малюнкі/малюнкі, мяккае сцісканне, муар Каляровая паласа Павольна, выдаляючы паўтоны Агульны каларызатар для адценняў шэрага/ч.б. малюнкаў, для лепшых вынікаў выкарыстоўвайце DDColor Выдаленне краю Выдаляе празмерную завострыванне Павольна, дрыготка Згладжванне, агульныя артэфакты, CGI Апрацоўка сканаў KDM003 Лёгкая мадэль паляпшэння выявы Выдаленне артэфактаў сціску Выдаленне артэфактаў сціску Зняцце павязкі з гладкімі вынікамі Апрацоўка паўтонавых малюнкаў Выдаленне шаблону дызерінга V3 Выдаленне артэфактаў JPEG V2 Паляпшэнне тэкстуры H.264 VHS рэзкасць і паляпшэнне Зліццё Памер кавалка Памер перакрыцця Выявы памерам больш за %1$s пікселяў будуць нарэзаны і апрацаваны на кавалкі. Перакрываючы іх змешвае, каб прадухіліць бачныя швы. Вялікія памеры могуць выклікаць нестабільнасць прылад нізкага ўзроўню Выберыце адзін, каб пачаць Вы хочаце выдаліць мадэль %1$s? Вам трэба будзе спампаваць яго зноў Пацвердзіць Мадэлі Спампаваныя мадэлі Даступныя мадэлі Рыхтуецца Актыўная мадэль Не ўдалося адкрыць сеанс Можна імпартаваць толькі мадэлі .onnx/.ort Імпартная мадэль Імпартуйце карыстальніцкую мадэль onnx для далейшага выкарыстання, прымаюцца толькі мадэлі onnx/ort, падтрымлівае амаль усе варыянты, падобныя на esrgan Імпартныя мадэлі Агульны шум, каляровыя выявы Агульны шум, каляровыя малюнкі, мацней Агульны шум, каляровыя выявы, наймацнейшыя Памяншае артэфакты размывання і каляровыя палосы, паляпшаючы плыўныя градыенты і плоскія каляровыя вобласці. Павышае яркасць і кантраснасць выявы са збалансаванымі блікамі, захоўваючы натуральныя колеры. Асвятляе цёмныя выявы, захоўваючы дэталі і пазбягаючы пераэкспазіцыі. Выдаляе празмернае тоніраванне колеру і аднаўляе больш нейтральны і натуральны баланс колеру. Прымяняе таніраванне шуму на аснове Пуасона з акцэнтам на захаванне дробных дэталяў і тэкстур. Прымяняе мяккае таніраванне шуму Пуасона для больш гладкіх і менш агрэсіўных візуальных вынікаў. Раўнамернае тоніраванне шуму, арыентаванае на захаванне дэталяў і выразнасць выявы. Мяккае аднастайнае шумавое таніраванне для тонкай тэкстуры і гладкага выгляду. Аднаўляе пашкоджаныя або няроўныя ўчасткі, перафарбоўваючы артэфакты і паляпшаючы кансістэнцыю выявы. Лёгкая мадэль дэбандынгу, якая выдаляе каляровыя палосы з мінімальнымі выдаткамі. Аптымізуе выявы з вельмі высокімі артэфактамі сціску (якасць 0-20%) для павышэння выразнасці. Паляпшае выявы з высокімі артэфактамі сціску (якасць 20-40%), аднаўляючы дэталі і памяншаючы шум. Паляпшае выявы з умераным сцісканнем (якасць 40-60%), балансуючы рэзкасць і гладкасць. Удасканальвае выявы з лёгкім сцісканнем (якасць 60-80%) для паляпшэння тонкіх дэталяў і тэкстур. Нязначна паляпшае выявы амаль без страт (якасць 80-100%), захоўваючы натуральны выгляд і дэталі. Простая і хуткая размалёўка, мультфільмы, не ідэальна Злёгку памяншае размытасць выявы, паляпшаючы рэзкасць без увядзення артэфактаў. Працяглыя аперацыі Апрацоўка выявы Апрацоўка Выдаляе моцныя артэфакты сціску JPEG у малюнках вельмі нізкай якасці (0-20%). Памяншае моцныя артэфакты JPEG у моцна сціснутых выявах (20-40%). Ачышчае ўмераныя артэфакты JPEG, захоўваючы дэталі выявы (40-60%). Удасканальвае лёгкія артэфакты JPEG у малюнках даволі высокай якасці (60-80%). Тонка памяншае нязначныя артэфакты JPEG у выявах амаль без страт (80-100%). Паляпшае дробныя дэталі і тэкстуры, паляпшаючы адчувальную рэзкасць без моцных артэфактаў. Апрацоўка скончана Збой апрацоўкі Паляпшае тэкстуру скуры і дэталі, захоўваючы пры гэтым натуральны выгляд, аптымізаваны для хуткасці. Выдаляе артэфакты сціску JPEG і аднаўляе якасць выявы для сціснутых фатаграфій. Памяншае шум ISO на фотаздымках, зробленых ва ўмовах нізкай асветленасці, захоўваючы дэталі. Выпраўляе празмерна экспанаваныя або «гіганцкія» блікі і аднаўляе лепшы танальны баланс. Лёгкая і хуткая мадэль афарбоўкі, якая дадае натуральныя колеры да малюнкаў у адценнях шэрага. DEJPEG Знішчыць шум Размаляваць Артэфакты Палепшыць Анімэ Сканы Высакакласны X4 Upscaler для агульных малюнкаў; малюсенькая мадэль, якая выкарыстоўвае менш GPU і часу, з умераным выдаленнем размытасці і шуму. X2 Upscaler для агульных малюнкаў, захоўваючы тэкстуры і натуральныя дэталі. X4 Upscaler для агульных малюнкаў з палепшанымі тэкстурамі і рэалістычнымі вынікамі. X4 Upscaler аптымізаваны для малюнкаў анімэ; 6 блокаў RRDB для больш выразных ліній і дэталяў. X4 Upscaler са стратамі MSE, дае больш плыўныя вынікі і зніжае колькасць дэфектаў для агульных малюнкаў. X4 Upscaler аптымізаваны для малюнкаў анімэ; Варыянт 4B32F з больш выразнымі дэталямі і плыўнымі лініямі. мадэль X4 UltraSharp V2 для агульных малюнкаў; падкрэслівае выразнасць і выразнасць. X4 UltraSharp V2 Lite; больш хуткі і меншы, захоўвае дэталі, выкарыстоўваючы менш памяці GPU. Лёгкая мадэль для хуткага выдалення фону. Збалансаваная прадукцыйнасць і дакладнасць. Працуе з партрэтамі, аб\'ектамі і сцэнамі. Рэкамендуецца для большасці выпадкаў выкарыстання. Выдаліць BG Таўшчыня гарызантальнай мяжы Таўшчыня вертыкальнай мяжы %1$s колер %1$s колераў %1$s колераў %1$s колераў Бягучая мадэль не падтрымлівае разбіццё на кавалкі, выява будзе апрацоўвацца ў зыходных памерах, гэта можа выклікаць вялікае спажыванне памяці і праблемы з прыладамі нізкага ўзроўню Раздзяленне на кавалкі адключана, відарыс будзе апрацоўвацца ў зыходных памерах, гэта можа выклікаць вялікае спажыванне памяці і праблемы з прыладамі нізкага класа, але можа даць лепшыя вынікі высновы Чанкінг Высокадакладная мадэль сегментацыі выявы для выдалення фону Палегчаная версія U2Net для больш хуткага выдалення фону з меншым выкарыстаннем памяці. Поўная мадэль DDColor забяспечвае высакаякасную афарбоўку агульных малюнкаў з мінімальнымі артэфактамі. Лепшы выбар з усіх мадэляў афарбоўкі. DDColor Навучаныя і прыватныя наборы мастацкіх дадзеных; стварае разнастайныя і мастацкія вынікі афарбоўвання з меншай колькасцю нерэальных каляровых артэфактаў. Лёгкая мадэль BiRefNet на аснове Swin Transformer для дакладнага выдалення фону. Высакаякаснае выдаленне фону з рэзкімі краямі і выдатным захаваннем дэталяў, асабліва на складаных аб\'ектах і складаным фоне. Мадэль выдалення фону, якая стварае дакладныя маскі з гладкімі краямі, прыдатныя для агульных аб\'ектаў і ўмераным захаваннем дэталяў. Мадэль ужо спампавана Мадэль паспяхова імпартавана Тып Ключавое слова Вельмі хутка Нармальны павольна Вельмі павольна Вылічыць працэнты Мінімальнае значэнне %1$s Скажайце малюнак, малюючы пальцамі Дэфармацыя Цвёрдасць Рэжым дэфармацыі Рухайцеся Расці Скарачацца Круціцца CW Круціце супраць супрацьлеглай рукі Fade Strength Top Drop Ніжняя кропля Пачаць Drop End Drop Ідзе загрузка Гладкія фігуры Выкарыстоўвайце суперэліпсы замест стандартных круглявых прамавугольнікаў для больш гладкіх і натуральных формаў Тып формы Выразаць Закругленыя Гладкая Вострыя краю без закруглення Класічныя закругленыя куты Формы Тып Памер кутоў Squircle Элегантныя закругленыя элементы інтэрфейсу Фармат імя файла Нестандартны тэкст змяшчаецца ў самым пачатку назвы файла, ідэальна падыходзіць для назваў праектаў, брэндаў або асабістых тэгаў. Выкарыстоўвае зыходнае імя файла без пашырэння, што дапамагае захаваць ідэнтыфікацыю крыніцы. Шырыня выявы ў пікселях, карысная для адсочвання змяненняў раздзялення або маштабавання вынікаў. Вышыня выявы ў пікселях, карысная пры працы з суадносінамі бакоў або экспартам. Генеруе выпадковыя лічбы, каб гарантаваць унікальныя імёны файлаў; дадаць больш лічбаў для дадатковай бяспекі ад дублікатаў. Лічыльнік з аўтаматычным павелічэннем для пакетнага экспарту, ідэальны варыянт пры захаванні некалькіх малюнкаў за адзін сеанс. Устаўляе прымененае імя загадзя ў імя файла, каб вы маглі лёгка запомніць, як апрацоўваўся малюнак. Адлюстроўвае рэжым маштабавання выявы, які выкарыстоўваецца падчас апрацоўкі, дапамагаючы адрозніваць выявы змененага памеру, абрэзаныя або падагнаныя. Нестандартны тэкст, размешчаны ў канцы імя файла, карысны для кіравання версіямі, такімі як _v2, _edited або _final. Пашырэнне файла (png, jpg, webp і г.д.), якое аўтаматычна адпавядае фактычнаму захаванаму фармату. Наладжвальная пазнака часу, якая дазваляе вам вызначаць уласны фармат па спецыфікацыі Java для ідэальнага сартавання. Тып кідка Android Native Стыль iOS Плаўная крывая Хуткая прыпынак Бадзёры Плывучы Імклівы Ультрагладкая Адаптыўны Даступнасць Aware Зніжэнне руху Родная фізіка пракруткі Android Збалансаваная плыўная пракрутка для агульнага карыстання Паводзіны пракруткі, падобныя на iOS, з большым трэннем Унікальная сплайн-крывая для выразнага адчування пракруткі Дакладная пракрутка з хуткай прыпынкам Гуллівы, спагадны пругкі скрутак Доўгія слізгальныя скруткі для прагляду кантэнту Хуткая і адаптыўная пракрутка для інтэрактыўных інтэрфейсаў Прэміяльная плаўная пракрутка з пашыраным імпульсам Рэгулюе фізіку ў залежнасці ад хуткасці кідка Паважае налады даступнасці сістэмы Мінімальны рух для патрэб даступнасці Першасныя лініі Дадае больш тоўстую лінію кожны пяты радок Колер залівання Схаваныя інструменты Інструменты, схаваныя для абмену Бібліятэка колераў Праглядзіце шырокую калекцыю колераў Павялічвае рэзкасць і выдаляе размытасць малюнкаў, захоўваючы пры гэтым натуральныя дэталі, што ідэальна падыходзіць для выпраўлення расфакусаваных фатаграфій. Інтэлектуальна аднаўляе выявы, памер якіх раней быў зменены, аднаўляючы страчаныя дэталі і тэкстуры. Аптымізаваны для жывога кантэнту, памяншае артэфакты сціску і паляпшае дробныя дэталі ў кадрах фільмаў/тэлешоу. Пераўтварае відэаматэрыял VHS-якасці ў HD, выдаляючы шум стужкі і паляпшаючы разрозненне, захоўваючы старадаўняе адчуванне. Спецыялізуецца на выявах і скрыншотах з вялікай колькасцю тэксту, узмацняе выразнасць сімвалаў і паляпшае чытальнасць. Пашыранае павышэнне маштабу, навучанае на розных наборах даных, выдатна падыходзіць для паляпшэння фатаграфій агульнага прызначэння. Аптымізавана для фатаграфій, сціснутых у Інтэрнэце, выдаляе артэфакты JPEG і аднаўляе натуральны выгляд. Палепшаная версія для вэб-фатаграфій з лепшым захаваннем тэкстуры і памяншэннем артэфактаў. 2-кратнае павелічэнне маштабу з дапамогай тэхналогіі Dual Aggregation Transformer, захоўвае рэзкасць і натуральныя дэталі. 3-кратнае павелічэнне з выкарыстаннем перадавой архітэктуры трансфарматара, ідэальнае для ўмераных патрэб пашырэння. 4-кратнае высакаякаснае павышэнне маштабу з сучаснай сеткай трансфарматараў, захоўвае дробныя дэталі ў вялікіх маштабах. Выдаляе размытасць/шум і дрыгаценне фатаграфій. Агульнага прызначэння, але лепш за ўсё на фота. Аднаўляе выявы нізкай якасці з дапамогай трансфарматара Swin2SR, аптымізаванага для дэградацыі BSRGAN. Выдатна падыходзіць для выпраўлення моцных артэфактаў сціску і паляпшэння дэталяў у 4-кратным маштабе. 4-кратнае павелічэнне маштабу з дапамогай трансфарматара SwinIR, навучанага дэградацыі BSRGAN. Выкарыстоўвае GAN для больш выразных тэкстур і больш натуральных дэталяў на фотаздымках і складаных сцэнах. шлях Аб\'яднаць PDF Аб\'яднайце некалькі файлаў PDF у адзін дакумент Парадак файлаў с. Разбіць PDF Выняць пэўныя старонкі з дакумента PDF Павярнуць pdf Назаўжды выправіць арыентацыю старонкі старонкі Змяніць парадак pdf Перацягніце старонкі, каб змяніць іх парадак Утрымлівайце і перацягвайце старонкі Нумары старонак Аўтаматычна дадайце нумарацыю ў свае дакументы Фармат этыкеткі PDF у тэкст (OCR) Вылучыце звычайны тэкст з дакументаў PDF Накладанне ўласнага тэксту для брэндынгу або бяспекі Подпіс Дадайце свой электронны подпіс да любога дакумента Гэта будзе выкарыстоўвацца як подпіс Разблакіраваць PDF Выдаліце ​​​​паролі з вашых абароненых файлаў Абараніць PDF Абараніце свае дакументы з дапамогай моцнага шыфравання Поспех PDF разблакіраваны, вы можаце захаваць ці падзяліцца ім Рамонт pdf Спроба выправіць пашкоджаныя або нечытэльныя дакументы Адценні шэрага Пераўтварыце ўсе выявы, убудаваныя ў дакумент, у адценні шэрага Сціснуць PDF Аптымізуйце памер файла дакумента для палягчэння абмену ImageToolbox аднаўляе ўнутраную табліцу крыжаваных спасылак і аднаўляе структуру файла з нуля. Гэта можа аднавіць доступ да многіх файлаў, якія \\"немагчыма адкрыць\\" Гэты інструмент пераўтворыць усе выявы дакументаў у адценні шэрага. Лепшы для друку і памяншэння памеру файла Метададзеныя Рэдагуйце ўласцівасці дакумента для лепшай прыватнасці Тэгі Прадзюсер Аўтар Ключавыя словы Творца Прыватнасць Deep Clean Ачысціць усе даступныя метаданыя для гэтага дакумента старонка Глыбокае OCR Выняць тэкст з дакумента і захаваць яго ў адным тэкставым файле з дапамогай рухавіка Tesseract Немагчыма выдаліць усе старонкі Выдаліць старонкі PDF Выдаліць пэўныя старонкі з дакумента PDF Націсніце, каб выдаліць Ўручную Абрэзаць PDF Абрэжце старонкі дакумента да любых межаў Звесці PDF Зрабіце PDF-файл нязменным, растравіўшы старонкі дакумента Не атрымалася запусціць камеру. Калі ласка, праверце дазволы і пераканайцеся, што ён не выкарыстоўваецца іншай праграмай. Выманне малюнкаў Выманне малюнкаў, убудаваных у PDF-файлы, у іх зыходным раздзяленні Гэты PDF-файл не змяшчае ўбудаваных малюнкаў Гэты інструмент скануе кожную старонку і аднаўляе паўнавартасныя зыходныя выявы — ідэальна падыходзіць для захавання арыгіналаў з дакументаў Намалюйце подпіс Параметры ручкі Выкарыстоўвайце ўласны подпіс у якасці выявы для размяшчэння на дакументах Zip PDF Разбіце дакумент з зададзеным інтэрвалам і спакуйце новыя дакументы ў zip-архіў Інтэрвал Раздрукаваць PDF Падрыхтуйце дакумент да друку з нестандартным памерам старонкі Старонкі на аркушы Арыентацыя Памер старонкі Маржа красаваць Мяккае калена Аптымізаваны для анімэ і мультфільмаў. Хуткае павелічэнне маштабу з паляпшэннем натуральных колераў і меншай колькасцю артэфактаў Стыль, падобны на Samsung One UI 7 Увядзіце тут асноўныя матэматычныя сімвалы, каб вылічыць патрэбнае значэнне (напрыклад, (5+5)*10) Матэматычны выраз Выберыце да %1$s малюнкаў Захоўвайце дату і час Заўсёды захоўваць тэгі exif, звязаныя з датай і часам, працуе незалежна ад параметра keep exif Колер фону для альфа-фарматаў Дадае магчымасць усталёўваць колер фону для кожнага фармату выявы з падтрымкай альфа-версіі, калі адключана, гэта даступна толькі для неальфа-фармату Адкрыты праект Працягнуць рэдагаванне раней захаванага праекта Image Toolbox Немагчыма адкрыць праект Image Toolbox У праекце Image Toolbox адсутнічаюць даныя праекта Праект Image Toolbox пашкоджаны Непадтрымоўваная версія праекта Image Toolbox: %1$d Захаваць праект Захоўвайце слаі, фон і гісторыю рэдагавання ў файле праекта, які можна рэдагаваць Не ўдалося адкрыць Запіс у PDF з магчымасцю пошуку Распазнавайце тэкст з пакета малюнкаў і захоўвайце PDF з магчымасцю пошуку з выявай і тэкставым пластом, які можна выбраць Пласт альфа Гарызантальны фліп Вертыкальны фліп Замак Дадайце цень Колер цені Геаметрыя тэксту Расцягніце або перакосіце тэкст для больш выразнай стылізацыі Маштаб X Перакос X Выдаліць анатацыі Выдаліце ​​са старонак PDF выбраныя тыпы анатацый, такія як спасылкі, каментарыі, вылучэнні, фігуры або палі формы Гіперспасылкі Укладанні файлаў Радкі Усплывальныя вокны Маркі Формы Тэкставыя нататкі Разметка тэксту Палі формы Разметка Невядомы Анатацыі Разгрупаваць Дадайце цень размыцця за пласт з наладжвальнымі колерам і зрухамі ================================================ FILE: core/resources/src/main/res/values-bn/strings.xml ================================================ লোড হচ্ছে… ছবিটি প্রিভিউ করার জন্য খুব বড়, তবে সেভ করার চেষ্টা করা হবে। শুরু করার জন্য ছবি বেছে নিন প্রস্থ %1$s উচ্চতা %1$s গুণমান আকার পরিবর্তনের ধরণ স্পষ্ট নমনীয় কিছু ভুল হয়েছে: %1$s আকার %1$s ছবি বেছে নিন অ্যাপ বন্ধ করা হচ্ছে থাকা বন্ধ ছবি রিসেট করুন মানগুলি সঠিকভাবে রিসেট করা হয়েছে রিসেট কিছু ভুল হয়েছে অ্যাপটি পুনরায় চালু করুন ক্লিপবোর্ডে কপি করা হয়েছে EXIF সম্পাদনা করুন ঠিক আছে কোন EXIF তথ্য পাওয়া যায়নি সংরক্ষণ করুন EXIF সাফ করুন এক্সটেনশন পরিষ্কার আপনি কি অ্যাপটি বন্ধ করার বিষয়ে নিশ্চিত? ট্যাগ যোগ করুন ছবির পরিবর্তনগুলি প্রাথমিক মানগুলিতে ফিরে যাবে ব্যতিক্রম বাতিল করুন সব ইমেজ EXIF ডেটা মুছে ফেলা হবে। এই কাজের পর আগের অবস্থায় ফিরে আসা যাবে না! পূর্বনির্ধারণ সমূহ কেটে ছোট করুন সংরক্ষণ করুন সব অরক্ষিত পরিবর্তন হারিয়ে যাবে, যদি আপনি এখন বেরিয়ে যান সৌর্স কোড সর্বশেষ আপডেট পান, সমস্যাগুলো এবং আরো কিছু নিয়ে আলোচনা করুন একটি পরিবর্তন রং নির্ণায়ক ছবি থেকে রং নির্ণয়, কপি অথবা শেয়ার করুন ছবি রং রং কপি করা হয়েছে যেকোনো সীমায় ছবি কাটুন ভার্সন EXIF রাখুন খসড়া পরিবর্তন করুন মুছে ফেলুন প্রদত্ত ছবি থেকে কালার প্যালেট সোয়াচ তৈরি করুন প্যালেট তৈরি করুন প্যালেট আপডেট প্রদত্ত ছবি থেকে কালার প্যালেট তৈরি করা যাচ্ছে না আসল আউটপুট ফোল্ডার সাধারণ পছন্দমাফিক অনির্ধারিত ডিভাইস স্টোরেজ KB তে সর্বোচ্চ সাইজ একটি চিত্র পরিবর্তন, আকার পরিবর্তন এবং সম্পাদনা করুন ছবি: %d নতুন সংস্করণ %1$s অসমর্থিত প্রকার: %1$s ওজন দ্বারা মাপ KB তে প্রদত্ত আকার অনুসরণ করে একটি চিত্রের আকার পরিবর্তন করুন তুলনা করুন দুটি প্রদত্ত চিত্রের তুলনা করুন শুরু করতে দুটি ছবি বেছে নিন ছবি বাছাই করুন সেটিংস নাইট মোড অন্ধকার আলো সিস্টেম গতিশীল রং কাস্টমাইজেশন ইমেজ monet অনুমতি দিন যদি সক্ষম করা থাকে, আপনি যখন সম্পাদনা করার জন্য একটি ছবি চয়ন করেন, তখন অ্যাপের রঙগুলি এই ছবিতে গৃহীত হবে৷ ভাষা অ্যামোলেড মোড সক্ষম হলে পৃষ্ঠের রঙ রাতের মোডে পরম অন্ধকারে সেট করা হবে রঙের স্কিম লাল সবুজ নীল একটি বৈধ aRGB কালার কোড পেস্ট করুন পেস্ট করার মতো কিছুই নেই ডায়নামিক রং চালু থাকা অবস্থায় অ্যাপের রঙের স্কিম পরিবর্তন করা যাবে না অ্যাপ থিম নির্বাচিত রঙের উপর ভিত্তি করে হবে অ্যাপ সম্পর্কে কোন আপডেট পাওয়া যায়নি ইস্যু ট্র্যাকার এখানে বাগ রিপোর্ট এবং বৈশিষ্ট্য অনুরোধ পাঠান অনুবাদে সাহায্য করুন অনুবাদের ভুলগুলি সংশোধন করুন বা অন্য ভাষায় প্রকল্প স্থানীয়করণ করুন আপনার প্রশ্নের দ্বারা কিছুই পাওয়া যায়নি এখানে অনুসন্ধান করুন যদি সক্ষম করা থাকে, তাহলে অ্যাপের রং ওয়ালপেপারের রঙে গৃহীত হবে %d ছবি(গুলি) সংরক্ষণ করতে ব্যর্থ হয়েছে ইমেইল প্রাথমিক টারশিয়ারি মাধ্যমিক সীমানা বেধ সারফেস মূল্যবোধ যোগ করুন অনুমতি অনুদান অ্যাপ্লিকেশন কাজ করার জন্য ছবি সংরক্ষণ করতে আপনার স্টোরেজ অ্যাক্সেস প্রয়োজন, এটা প্রয়োজন. অনুগ্রহ করে পরবর্তী ডায়ালগ বক্সে অনুমতি দিন। অ্যাপটির কাজ করার জন্য এই অনুমতির প্রয়োজন, অনুগ্রহ করে এটি ম্যানুয়ালি মঞ্জুর করুন বাহ্যিক স্টোরেজ Monet রং এই অ্যাপ্লিকেশনটি সম্পূর্ণ বিনামূল্যে, তবে আপনি যদি প্রকল্পের উন্নয়নে সহায়তা করতে চান তবে আপনি এখানে ক্লিক করতে পারেন FAB প্রান্তিককরণ আপডেটের জন্য চেক করুন সক্রিয় থাকলে, অ্যাপ স্টার্টআপে আপনাকে আপডেট ডায়ালগ দেখানো হবে ছবি জুম শেয়ার করুন উপসর্গ ফাইলের নাম ইমোজি প্রধান স্ক্রিনে কোন ইমোজি প্রদর্শন করতে হবে তা নির্বাচন করুন ফাইলের আকার যোগ করুন যদি সক্রিয় থাকে, আউটপুট ফাইলের নামে সংরক্ষিত চিত্রের প্রস্থ এবং উচ্চতা যোগ করে EXIF মুছুন যেকোন ছবির সেট থেকে EXIF ​​মেটাডেটা মুছুন ছবির পূর্বরূপ যেকোন ধরনের ছবির পূর্বরূপ দেখুন: GIF, SVG, ইত্যাদি ইমেজ সোর্স ফটো পিকার গ্যালারি ফাইল এক্সপ্লোরার স্ক্রিনের নীচে প্রদর্শিত অ্যান্ড্রয়েড আধুনিক ফটো পিকার, শুধুমাত্র অ্যান্ড্রয়েড 12+ এ কাজ করতে পারে। EXIF মেটাডেটা প্রাপ্তিতে সমস্যা আছে সহজ গ্যালারি ইমেজ পিকার। মিডিয়া পিকিং প্রদান করে এমন একটি অ্যাপ থাকলেই এটি কাজ করবে ছবি বাছাই করতে GetContent উদ্দেশ্য ব্যবহার করুন। সব জায়গায় কাজ করে, কিন্তু কিছু ডিভাইসে বাছাই করা ছবি পেতে সমস্যা আছে বলে জানা যায়। এটা আমার দোষ না। বিকল্প ব্যবস্থা সম্পাদনা করুন অর্ডার প্রধান স্ক্রিনে টুলের ক্রম নির্ধারণ করে ইমোজি গণনা ক্রমসংখ্যা মূল ফাইলের নাম মূল ফাইলের নাম যোগ করুন সক্রিয় থাকলে আউটপুট চিত্রের নামে মূল ফাইলের নাম যোগ করে ক্রম নম্বর প্রতিস্থাপন করুন আপনি ব্যাচ প্রসেসিং ব্যবহার করলে ইমেজ সিকোয়েন্স নম্বরে স্ট্যান্ডার্ড টাইমস্ট্যাম্প প্রতিস্থাপন করা হলে ফটো পিকার ইমেজ সোর্স নির্বাচিত হলে আসল ফাইলের নাম যোগ করা কাজ করে না ওয়েব ইমেজ লোড হচ্ছে ইন্টারনেট থেকে যেকোনো ছবি লোড করুন প্রিভিউ, জুম, এডিট এবং আপনি চাইলে সেভ করতে। কোনো ছবি নেই ছবির লিঙ্ক ভরাট ফিট বিষয়বস্তু স্কেল প্রদত্ত উচ্চতা এবং প্রস্থে চিত্রগুলির আকার পরিবর্তন করে৷ চিত্রগুলির আকৃতির অনুপাত পরিবর্তিত হতে পারে। প্রদত্ত উচ্চতা বা প্রস্থে লম্বা পাশ সহ চিত্রগুলির আকার পরিবর্তন করে৷ সংরক্ষণের পরে সমস্ত আকারের হিসাব করা হবে। ছবির আকৃতির অনুপাত সংরক্ষণ করা হবে। উজ্জ্বলতা বৈপরীত্য হিউ স্যাচুরেশন ফিল্টার যোগ করুন ফিল্টার ছবিতে ফিল্টার চেইন প্রয়োগ করুন ফিল্টার আলো রঙ ফিল্টার আলফা প্রকাশ সাদা ভারসাম্য তাপমাত্রা আভা একরঙা গামা হাইলাইট এবং ছায়া হাইলাইট ছায়া কুয়াশা প্রভাব দূরত্ব ঢাল ধারালো সেপিয়া নেতিবাচক সোলারাইজ করুন ভাইব্রেন্স কালো এবং সাদা ক্রসশ্যাচ ব্যবধান লাইন প্রস্থ সোবেল প্রান্ত ঝাপসা হাফটোন CGA রঙের স্থান গাউসিয়ান ব্লার বক্স ঝাপসা দ্বিপাক্ষিক অস্পষ্টতা এমবস ল্যাপ্লাসিয়ান ভিগনেট শুরু করুন শেষ কুয়াহারা মসৃণ করা স্ট্যাক ব্লার ব্যাসার্ধ স্কেল বিকৃতি কোণ ঘূর্ণি স্ফীতি প্রসারণ গোলক প্রতিসরণ প্রতিসরণকারী সূচক কাচের গোলকের প্রতিসরণ কালার ম্যাট্রিক্স অস্বচ্ছতা সীমা অনুযায়ী আকার পরিবর্তন করুন আকৃতির অনুপাত বজায় রেখে প্রদত্ত উচ্চতা এবং প্রস্থে চিত্রগুলির আকার পরিবর্তন করুন স্কেচ থ্রেশহোল্ড কোয়ান্টাইজেশন লেভেল মসৃণ টুন টুন পোস্টারাইজ করুন অ সর্বোচ্চ দমন দুর্বল পিক্সেল অন্তর্ভুক্তি লুকআপ আবর্তন 3x3 আরজিবি ফিল্টার মিথ্যা রং প্রথম রঙ দ্বিতীয় রঙ পুনরায় সাজান দ্রুত ঝাপসা ব্লার সাইজ অস্পষ্ট কেন্দ্র x অস্পষ্ট কেন্দ্র y জুম ব্লার রঙের ভারসাম্য উজ্জ্বলতা থ্রেশহোল্ড আপনি ফাইল অ্যাপটি নিষ্ক্রিয় করেছেন, এই বৈশিষ্ট্যটি ব্যবহার করতে এটি সক্রিয় করুন৷ আঁকা একটি স্কেচবুকের মতো চিত্রে আঁকুন বা পটভূমিতে নিজেই আঁকুন পেইন্ট রং আলফা পেইন্ট করুন চিত্রে আঁকুন একটি ছবি বাছুন এবং এটিতে কিছু আঁকুন পটভূমিতে আঁকা পটভূমির রঙ চয়ন করুন এবং এটির উপরে আঁকুন পটভূমির রঙ সাইফার বিভিন্ন উপলব্ধ ক্রিপ্টো অ্যালগরিদমের উপর ভিত্তি করে যেকোনো ফাইল (শুধুমাত্র ছবি নয়) এনক্রিপ্ট এবং ডিক্রিপ্ট করুন ফাইল বাছাই করুন এনক্রিপ্ট করুন ডিক্রিপ্ট করুন শুরু করতে ফাইল বাছাই করুন ডিক্রিপশন এনক্রিপশন চাবি ফাইল প্রক্রিয়া করা হয়েছে এই ফাইলটি আপনার ডিভাইসে সঞ্চয় করুন অথবা আপনি যেখানে চান সেখানে রাখতে শেয়ার অ্যাকশন ব্যবহার করুন বৈশিষ্ট্য বাস্তবায়ন সামঞ্জস্য ফাইলগুলির পাসওয়ার্ড-ভিত্তিক এনক্রিপশন। অগ্রসর হওয়া ফাইলগুলি নির্বাচিত ডিরেক্টরিতে সংরক্ষণ করা বা ভাগ করা যেতে পারে। ডিক্রিপ্ট করা ফাইলগুলিও সরাসরি খোলা যায়। AES-256, GCM মোড, কোনো প্যাডিং নেই, ডিফল্টরূপে 12 বাইট র্যান্ডম IV। আপনি প্রয়োজনীয় অ্যালগরিদম নির্বাচন করতে পারেন। কীগুলি 256-বিট SHA-3 হ্যাশ হিসাবে ব্যবহৃত হয় ফাইলের আকার সর্বাধিক ফাইলের আকার Android OS এবং উপলব্ধ মেমরি দ্বারা সীমাবদ্ধ, যা ডিভাইস নির্ভর। \nঅনুগ্রহ করে মনে রাখবেন: মেমরি স্টোরেজ নয়। অনুগ্রহ করে মনে রাখবেন যে অন্যান্য ফাইল এনক্রিপশন সফ্টওয়্যার বা পরিষেবাগুলির সাথে সামঞ্জস্যপূর্ণতা নিশ্চিত নয়৷ একটি সামান্য ভিন্ন কী চিকিত্সা বা সাইফার কনফিগারেশন অসঙ্গতি সৃষ্টি করতে পারে। অবৈধ পাসওয়ার্ড বা নির্বাচিত ফাইল এনক্রিপ্ট করা হয় না প্রদত্ত প্রস্থ এবং উচ্চতা দিয়ে ছবিটি সংরক্ষণ করার চেষ্টা করলে মেমরির বাইরে ত্রুটি হতে পারে। এটি আপনার নিজের ঝুঁকিতে করুন। ক্যাশে ক্যাশে আকার %1$s পাওয়া গেছে স্বয়ংক্রিয় ক্যাশে ক্লিয়ারিং সক্ষম হলে অ্যাপ ক্যাশে অ্যাপ স্টার্টআপে সাফ হয়ে যাবে তৈরি করুন টুলস টাইপ দ্বারা গ্রুপ বিকল্প একটি কাস্টম তালিকা বিন্যাসের পরিবর্তে মূল স্ক্রিনে গোষ্ঠীর বিকল্পগুলি তাদের প্রকার অনুসারে বিকল্প গ্রুপিং সক্রিয় থাকা অবস্থায় বিন্যাস পরিবর্তন করা যাবে না স্ক্রিনশট সম্পাদনা করুন সেকেন্ডারি কাস্টমাইজেশন স্ক্রিনশট ফলব্যাক বিকল্প এড়িয়ে যান কপি %1$s মোডে সংরক্ষণ করা অস্থির হতে পারে, কারণ এটি একটি ক্ষতিহীন বিন্যাস আপনি যদি প্রিসেট 125 নির্বাচন করে থাকেন, তাহলে ছবিটি মূল ছবির 125% আকার হিসাবে সংরক্ষণ করা হবে। আপনি যদি প্রিসেট 50 নির্বাচন করেন, তাহলে ছবি 50% আকারের সাথে সংরক্ষণ করা হবে এখানে প্রিসেট আউটপুট ফাইলের % নির্ধারণ করে, অর্থাৎ আপনি যদি 5 এমবি ইমেজে প্রিসেট 50 নির্বাচন করেন তাহলে সেভ করার পর আপনি একটি 2,5 এমবি ইমেজ পাবেন। ফাইলের নাম র্যান্ডমাইজ করুন যদি সক্রিয় আউটপুট ফাইলের নাম সম্পূর্ণরূপে র্যান্ডম হবে %2$s নামের সাথে %1$s ফোল্ডারে সংরক্ষিত %1$s ফোল্ডারে সংরক্ষিত টেলিগ্রাম চ্যাট অ্যাপটি নিয়ে আলোচনা করুন এবং অন্যান্য ব্যবহারকারীদের কাছ থেকে প্রতিক্রিয়া পান। আপনি সেখানে বিটা আপডেট এবং অন্তর্দৃষ্টিও পেতে পারেন। ক্রপ মাস্ক আকৃতির অনুপাত প্রদত্ত চিত্র থেকে মুখোশ তৈরি করতে এই মাস্ক টাইপটি ব্যবহার করুন, লক্ষ্য করুন যে এটিতে আলফা চ্যানেল থাকা উচিত ব্যাকআপ এবং পুনরুদ্ধার ব্যাকআপ পুনরুদ্ধার করুন একটি ফাইলে আপনার অ্যাপ সেটিংস ব্যাকআপ করুন পূর্বে তৈরি করা ফাইল থেকে অ্যাপ সেটিংস পুনরুদ্ধার করুন দূষিত ফাইল বা ব্যাকআপ নয় সেটিংস সফলভাবে পুনরুদ্ধার করা হয়েছে৷ আমার সাথে যোগাযোগ করুন এটি আপনার সেটিংসকে ডিফল্ট মানগুলিতে ফিরিয়ে আনবে। লক্ষ্য করুন যে উপরে উল্লিখিত একটি ব্যাকআপ ফাইল ছাড়া এটি পূর্বাবস্থায় ফেরানো যাবে না৷ মুছুন আপনি নির্বাচিত রঙের স্কিম মুছে ফেলতে চলেছেন৷ এই অপারেশন পূর্বাবস্থায় ফেরানো যাবে না স্কিম মুছুন হরফ পাঠ্য ফন্ট স্কেল ডিফল্ট বড় ফন্ট স্কেল ব্যবহার করলে UI সমস্যা এবং সমস্যা হতে পারে, যা ঠিক করা হবে না। সাবধানে ব্যবহার করুন। অ আ ই ঈ উ ঊ ঋ এ ঐ ও ঔ ক খ গ ঘ ঙ চ ছ জ ঝ ঞ ট ঠ ড ঢ ণ ত থ দ ধ ন প ফ ব ভ ম য র ল শ ষ স হ 0123456789 !? আবেগ খাদ্য এবং পানীয় প্রকৃতি এবং প্রাণী বস্তু প্রতীক ইমোজি সক্ষম করুন ভ্রমণ এবং স্থান কার্যক্রম ব্যাকগ্রাউন্ড রিমুভার অঙ্কন করে বা অটো বিকল্প ব্যবহার করে ছবি থেকে পটভূমি সরান ছবি ট্রিম করুন অরিজিনাল ইমেজ মেটাডেটা রাখা হবে ছবির চারপাশে স্বচ্ছ স্থানগুলি ছাঁটাই করা হবে স্বয়ংক্রিয়ভাবে পটভূমি মুছে ফেলা ছবি পুনরুদ্ধার করুন মুছে ফেলার মোড পটভূমি মুছুন পটভূমি পুনরুদ্ধার করুন ব্লার ব্যাসার্ধ পিপেট আঁকা মোড ইস্যু তৈরি করুন ওহো… কিছু ভুল হয়েছে। আপনি নীচের বিকল্পগুলি ব্যবহার করে আমাকে লিখতে পারেন এবং আমি সমাধান খোঁজার চেষ্টা করব৷ আকার পরিবর্তন করুন এবং রূপান্তর করুন প্রদত্ত চিত্রগুলির আকার পরিবর্তন করুন বা অন্য বিন্যাসে রূপান্তর করুন। EXIF মেটাডেটাও এখানে সম্পাদনা করা যেতে পারে যদি একটি একক ছবি বাছাই করা হয়। সর্বাধিক রঙ গণনা এটি অ্যাপটিকে স্বয়ংক্রিয়ভাবে ক্র্যাশ রিপোর্ট সংগ্রহ করতে দেয় বিশ্লেষণ বেনামী অ্যাপ ব্যবহারের পরিসংখ্যান সংগ্রহ করার অনুমতি দিন বর্তমানে, %1$s ফরম্যাট শুধুমাত্র Android এ EXIF ​​মেটাডেটা পড়ার অনুমতি দেয়। আউটপুট ইমেজে মেটাডেটা থাকবে না, যখন সেভ করা হবে। প্রচেষ্টা %1$s এর মান মানে একটি দ্রুত কম্প্রেশন, যার ফলে ফাইলের আকার তুলনামূলকভাবে বড় হয়। %2$s মানে একটি ধীর সংকোচন, যার ফলে একটি ছোট ফাইল হয়। অপেক্ষা করুন সংরক্ষণ প্রায় সম্পূর্ণ। এখন বাতিল করার জন্য আবার সংরক্ষণের প্রয়োজন হবে। আপডেট বেটাকে অনুমতি দিন সক্রিয় থাকলে আপডেট চেকিং বিটা অ্যাপ সংস্করণ অন্তর্ভুক্ত করবে তীর আঁকা সক্ষম হলে অঙ্কন পথ নির্দেশক তীর হিসাবে উপস্থাপন করা হবে ব্রাশের কোমলতা প্রবেশ করা আকারে ছবি কেন্দ্রে কাটা হবে। প্রদত্ত পটভূমির রঙ দিয়ে ক্যানভাস প্রসারিত করা হবে যদি চিত্রটি প্রবেশ করা মাত্রার চেয়ে ছোট হয়। দান ছবি সেলাই একটি বড় একটি পেতে প্রদত্ত চিত্রগুলিকে একত্রিত করুন৷ কমপক্ষে 2টি ছবি বেছে নিন আউটপুট ইমেজ স্কেল ইমেজ ওরিয়েন্টেশন অনুভূমিক উল্লম্ব ছোট ছবিকে বড় করে স্কেল করুন সক্ষম করা থাকলে ছোট ছবিগুলিকে ক্রমানুসারে বৃহত্তম চিত্রে স্কেল করা হবে৷ ইমেজ অর্ডার নিয়মিত ঝাপসা প্রান্ত সক্রিয় থাকলে একক রঙের পরিবর্তে এটির চারপাশের স্থানগুলি পূরণ করতে আসল চিত্রের নীচে ঝাপসা প্রান্তগুলি আঁকে পিক্সেলেশন উন্নত Pixelation স্ট্রোক পিক্সেলেশন উন্নত ডায়মন্ড পিক্সেলেশন ডায়মন্ড পিক্সেলেশন সার্কেল পিক্সেলেশন উন্নত সার্কেল পিক্সেলেশন রঙ প্রতিস্থাপন করুন সহনশীলতা প্রতিস্থাপন করার জন্য রঙ টার্গেট কালার অপসারণ করার জন্য রঙ রঙ সরান রিকোড পিক্সেল সাইজ লক ড্র ওরিয়েন্টেশন অঙ্কন মোডে সক্রিয় থাকলে, স্ক্রিন ঘোরবে না আপডেটের জন্য চেক করুন প্যালেট শৈলী টোনাল স্পট নিরপেক্ষ প্রাণবন্ত অভিব্যক্তিপূর্ণ রংধনু ফলের সালাদ বিশ্বস্ততা বিষয়বস্তু ডিফল্ট প্যালেট শৈলী, এটি সমস্ত চারটি রঙ কাস্টমাইজ করার অনুমতি দেয়, অন্যরা আপনাকে শুধুমাত্র কী রঙ সেট করতে দেয় একটি শৈলী যা একরঙা থেকে একটু বেশি বর্ণময় একটি জোরে থিম, রঙিনতা প্রাথমিক প্যালেটের জন্য সর্বাধিক, অন্যদের জন্য বৃদ্ধি একটি কৌতুকপূর্ণ থিম - উত্স রঙের বর্ণ থিমে প্রদর্শিত হয় না৷ একটি একরঙা থিম, রঙগুলি সম্পূর্ণরূপে কালো/সাদা/ধূসর একটি স্কিম যা Scheme.primaryContainer-এ উৎসের রঙ রাখে একটি স্কিম যা বিষয়বস্তু স্কিমের অনুরূপ এই আপডেট পরীক্ষক একটি নতুন আপডেট উপলব্ধ আছে কিনা তা পরীক্ষা করার কারণে GitHub এর সাথে সংযুক্ত হবে মনোযোগ বিবর্ণ প্রান্ত অক্ষম উভয় উল্টানো রং সক্রিয় থাকলে থিমের রঙকে নেতিবাচক রঙে প্রতিস্থাপন করে অনুসন্ধান করুন মূল স্ক্রিনে সমস্ত উপলব্ধ সরঞ্জামগুলির মাধ্যমে অনুসন্ধান করার ক্ষমতা সক্ষম করে৷ পিডিএফ টুলস পিডিএফ ফাইলগুলির সাথে কাজ করুন: পূর্বরূপ, চিত্রের ব্যাচে রূপান্তর করুন বা প্রদত্ত ছবি থেকে একটি তৈরি করুন পিডিএফ প্রিভিউ করুন পিডিএফ টু ইমেজ পিডিএফে ছবি সহজ পিডিএফ প্রিভিউ প্রদত্ত আউটপুট বিন্যাসে পিডিএফকে চিত্রগুলিতে রূপান্তর করুন প্রদত্ত চিত্রগুলিকে আউটপুট পিডিএফ ফাইলে প্যাক করুন মাস্ক ফিল্টার প্রদত্ত মুখোশযুক্ত এলাকায় ফিল্টার চেইন প্রয়োগ করুন, প্রতিটি মুখোশ এলাকা তার নিজস্ব ফিল্টার সেট নির্ধারণ করতে পারে মুখোশ মাস্ক যোগ করুন মাস্ক %d মুখোশের রঙ মাস্ক প্রিভিউ আপনাকে আনুমানিক ফলাফল দেখানোর জন্য আঁকা ফিল্টার মাস্ক রেন্ডার করা হবে ইনভার্স ফিল টাইপ যদি সক্রিয় করা হয় তবে সমস্ত নন-মাস্কড এলাকাগুলি ডিফল্ট আচরণের পরিবর্তে ফিল্টার করা হবে আপনি নির্বাচিত ফিল্টার মাস্ক মুছে ফেলতে চলেছেন৷ এই অপারেশন পূর্বাবস্থায় ফেরানো যাবে না মাস্ক মুছে দিন সম্পূর্ণ ফিল্টার প্রদত্ত ইমেজ বা একক ছবিতে যেকোনো ফিল্টার চেইন প্রয়োগ করুন শুরু করুন কেন্দ্র শেষ সরল ভেরিয়েন্ট হাইলাইটার নিয়ন কলম গোপনীয়তা ঝাপসা আধা-স্বচ্ছ ধারালো হাইলাইটার পাথ আঁকুন আপনার আঁকা কিছু উজ্জ্বল প্রভাব যোগ করুন ডিফল্ট এক, সহজতম - শুধু রঙ আপনি যা কিছু লুকাতে চান তা সুরক্ষিত করতে আঁকা পথের নীচে ছবি ঝাপসা করে প্রাইভেসি ব্লারের মতো, কিন্তু পিক্সেলেট ব্লার করার পরিবর্তে পাত্রে পাত্রের পিছনে একটি ছায়া আঁকুন স্লাইডার সুইচ FABs বোতাম স্লাইডারের পিছনে একটি ছায়া আঁকুন সুইচের পিছনে একটি ছায়া আঁকুন ভাসমান অ্যাকশন বোতামের পিছনে একটি ছায়া আঁকুন বোতামের পিছনে একটি ছায়া আঁকুন অ্যাপ বার অ্যাপ বারের পিছনে একটি ছায়া আঁকুন পরিসরে মান %1$s - %2$s স্বয়ংক্রিয় ঘোরান ইমেজ ওরিয়েন্টেশনের জন্য সীমা বক্স গ্রহণ করার অনুমতি দেয় পাথ মোড আঁকুন ডাবল লাইন তীর বিনামূল্যে অঙ্কন ডাবল তীর লাইন তীর তীর লাইন ইনপুট মান হিসাবে পাথ আঁকে একটি রেখা হিসাবে শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত পথ আঁকে একটি রেখা হিসাবে শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত নির্দেশক তীর আঁকে একটি প্রদত্ত পথ থেকে একটি নির্দেশক তীর আঁকে একটি লাইন হিসাবে শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত ডবল নির্দেশক তীর আঁকে একটি প্রদত্ত পথ থেকে দ্বিগুণ নির্দেশক তীর আঁকে রূপরেখা ওভাল আউটলাইন রেক্ট ওভাল রেক্ট শুরুর বিন্দু থেকে শেষ বিন্দু পর্যন্ত আয়ত আঁকে শুরু বিন্দু থেকে শেষ বিন্দু ডিম্বাকৃতি আঁকা শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত রূপরেখা ডিম্বাকৃতি আঁকা প্রারম্ভ বিন্দু থেকে শেষ বিন্দু পর্যন্ত আউটলাইন রেক্ট আঁকে ল্যাসো প্রদত্ত পথ দ্বারা বদ্ধ ভরাট পথ আঁকে বিনামূল্যে অনুভূমিক গ্রিড উল্লম্ব গ্রিড স্টিচ মোড সারি গণনা কলাম সংখ্যা কোনো \"%1$s\" ডিরেক্টরি পাওয়া যায়নি, আমরা এটিকে ডিফল্টে স্যুইচ করেছি, অনুগ্রহ করে ফাইলটি আবার সংরক্ষণ করুন ক্লিপবোর্ড অটো পিন সক্রিয় থাকলে স্বয়ংক্রিয়ভাবে ক্লিপবোর্ডে সংরক্ষিত ছবি যোগ করে কম্পন কম্পন শক্তি ফাইলগুলিকে ওভাররাইট করার জন্য আপনাকে \"এক্সপ্লোরার\" ইমেজ সোর্স ব্যবহার করতে হবে, রিপিক ইমেজ চেষ্টা করুন, আমরা ইমেজ সোর্সকে প্রয়োজনীয় একটিতে পরিবর্তন করেছি ফাইল ওভাররাইট করুন আসল ফাইলটি নির্বাচিত ফোল্ডারে সংরক্ষণ করার পরিবর্তে নতুন ফাইলের সাথে প্রতিস্থাপিত হবে, এই বিকল্পটিকে \"এক্সপ্লোরার\" বা GetContent হতে ইমেজ সোর্স করতে হবে, এটি টগল করার সময়, এটি স্বয়ংক্রিয়ভাবে সেট হয়ে যাবে খালি প্রত্যয় স্কেল মোড বিলিনিয়ার ক্যাটমুল বিকিউবিক সে সন্ন্যাসী ল্যাঙ্কজোস মিচেল নিকটতম স্প্লাইন মৌলিক ডিফল্ট মান রৈখিক (বা বাইলিনিয়ার, দুই মাত্রায়) ইন্টারপোলেশন সাধারণত একটি চিত্রের আকার পরিবর্তনের জন্য ভাল, তবে বিশদ কিছু অবাঞ্ছিত নরম করে তোলে এবং এখনও কিছুটা জ্যাগড হতে পারে ভালো স্কেলিং পদ্ধতির মধ্যে রয়েছে ল্যাঙ্কজোস রিস্যাম্পলিং এবং মিচেল-নেত্রাভালি ফিল্টার আকার বাড়ানোর সহজ উপায়গুলির মধ্যে একটি, প্রতিটি পিক্সেলকে একই রঙের একাধিক পিক্সেল দিয়ে প্রতিস্থাপন করা সবচেয়ে সহজ অ্যান্ড্রয়েড স্কেলিং মোড যা প্রায় সব অ্যাপে ব্যবহৃত হয় মসৃণ বক্ররেখা তৈরি করতে সাধারণত কম্পিউটার গ্রাফিক্সে ব্যবহৃত নিয়ন্ত্রণ পয়েন্টগুলির একটি সেটকে মসৃণভাবে ইন্টারপোলেট করার এবং পুনরায় নমুনা করার পদ্ধতি বর্ণালী ফুটো কমাতে এবং সংকেতের প্রান্তগুলিকে ছোট করে ফ্রিকোয়েন্সি বিশ্লেষণের নির্ভুলতা উন্নত করতে সিগন্যাল প্রসেসিং-এ প্রায়ই উইন্ডো করার ফাংশন প্রয়োগ করা হয় গাণিতিক ইন্টারপোলেশন কৌশল যা একটি মসৃণ এবং অবিচ্ছিন্ন বক্ররেখা তৈরি করতে একটি বক্র অংশের শেষ বিন্দুতে মান এবং ডেরিভেটিভ ব্যবহার করে রিস্যাম্পলিং পদ্ধতি যা পিক্সেল মানগুলিতে একটি ওজনযুক্ত সিঙ্ক ফাংশন প্রয়োগ করে উচ্চ-মানের ইন্টারপোলেশন বজায় রাখে রিস্যাম্পলিং পদ্ধতি যা স্কেল করা ছবিতে তীক্ষ্ণতা এবং অ্যান্টি-আলিয়াসিংয়ের মধ্যে ভারসাম্য অর্জন করতে সামঞ্জস্যযোগ্য প্যারামিটার সহ একটি কনভোলিউশন ফিল্টার ব্যবহার করে নমনীয় এবং অবিচ্ছিন্ন আকৃতি উপস্থাপনা প্রদান করে, একটি বক্ররেখা বা পৃষ্ঠকে মসৃণভাবে প্রসারিত করতে এবং আনুমানিক করতে টুকরো টুকরো-সংজ্ঞায়িত বহুপদী ফাংশন ব্যবহার করে শুধুমাত্র ক্লিপ সঞ্চয়স্থানে সংরক্ষণ করা হবে না, এবং ছবি শুধুমাত্র ক্লিপবোর্ডে রাখার চেষ্টা করা হবে ব্রাশ মুছে ফেলার পরিবর্তে পটভূমি পুনরুদ্ধার করবে ওসিআর (পাঠ্য সনাক্ত করুন) প্রদত্ত চিত্র থেকে পাঠ্য সনাক্ত করুন, 120+ ভাষা সমর্থিত ছবির কোন টেক্সট নেই, বা অ্যাপ এটি খুঁজে পায়নি \"নির্ভুলতা: %1$s\" স্বীকৃতির ধরন দ্রুত স্ট্যান্ডার্ড সেরা কোন ডেটা নেই Tesseract OCR অতিরিক্ত প্রশিক্ষণ ডেটা (%1$s) সঠিকভাবে কাজ করার জন্য আপনার ডিভাইসে ডাউনলোড করতে হবে৷\nআপনি কি %2$s ডেটা ডাউনলোড করতে চান? ডাউনলোড করুন কোনও সংযোগ নেই, এটি পরীক্ষা করুন এবং ট্রেনের মডেলগুলি ডাউনলোড করার জন্য আবার চেষ্টা করুন৷ ডাউনলোড করা ভাষা উপলব্ধ ভাষা সেগমেন্টেশন মোড পিক্সেল সুইচ ব্যবহার করুন একটি Google Pixel-এর মতো সুইচ ব্যবহার করে মূল গন্তব্যে %1$s নামের সাথে ওভাররাইট করা ফাইল ম্যাগনিফায়ার আরও ভাল অ্যাক্সেসযোগ্যতার জন্য অঙ্কন মোডে আঙুলের শীর্ষে ম্যাগনিফায়ার সক্ষম করে৷ প্রাথমিক মান জোর করুন exif উইজেট প্রাথমিকভাবে চেক করতে বাধ্য করে একাধিক ভাষার অনুমতি দিন স্লাইড সাইড বাই সাইড টগল ট্যাপ করুন স্বচ্ছতা অ্যাপকে রেট দিন হার এই অ্যাপটি সম্পূর্ণ বিনামূল্যে, আপনি যদি এটিকে আরও বড় করতে চান, তাহলে অনুগ্রহ করে Github-এ প্রজেক্টটি স্টার করুন 😄 শুধুমাত্র ওরিয়েন্টেশন ও স্ক্রিপ্ট ডিটেকশন স্বয়ংক্রিয় অভিযোজন এবং স্ক্রিপ্ট সনাক্তকরণ শুধুমাত্র অটো অটো একক কলাম একক ব্লক উল্লম্ব পাঠ্য একক ব্লক একক লাইন একক শব্দ বৃত্ত শব্দ একক চর স্পার্স টেক্সট স্পার্স টেক্সট ওরিয়েন্টেশন এবং স্ক্রিপ্ট সনাক্তকরণ কাঁচা লাইন আপনি কি সমস্ত স্বীকৃতি প্রকারের জন্য ভাষা \"%1$s\" OCR প্রশিক্ষণ ডেটা মুছতে চান, নাকি শুধুমাত্র নির্বাচিত একটি (%2$s) এর জন্য? কারেন্ট সব গ্রেডিয়েন্ট মেকার কাস্টমাইজড রং এবং চেহারা টাইপ সহ প্রদত্ত আউটপুট আকারের গ্রেডিয়েন্ট তৈরি করুন রৈখিক রেডিয়াল ঝাড়ু গ্রেডিয়েন্ট টাইপ কেন্দ্র এক্স কেন্দ্র Y টাইল মোড বারবার আয়না বাতা ডেকাল রঙ স্টপ রঙ যোগ করুন বৈশিষ্ট্য উজ্জ্বলতা প্রয়োগ পর্দা গ্রেডিয়েন্ট ওভারলে প্রদত্ত চিত্রগুলির শীর্ষের যে কোনও গ্রেডিয়েন্ট রচনা করুন রূপান্তর ক্যামেরা ক্যামেরা দিয়ে ছবি তুলুন। উল্লেখ্য যে এই ইমেজ সোর্স থেকে শুধুমাত্র একটি ইমেজ পাওয়া সম্ভব ওয়াটারমার্কিং কাস্টমাইজযোগ্য টেক্সট/ইমেজ ওয়াটারমার্ক দিয়ে ছবি কভার করুন জলছাপ পুনরাবৃত্তি করুন প্রদত্ত অবস্থানে একক পরিবর্তে চিত্রের উপর জলছাপ পুনরাবৃত্তি করুন৷ অফসেট এক্স অফসেট Y ওয়াটারমার্ক টাইপ এই ছবিটি ওয়াটারমার্কিং এর প্যাটার্ন হিসাবে ব্যবহার করা হবে পাঠ্যের রঙ ওভারলে মোড GIF টুলস ছবিগুলিকে GIF ছবিতে রূপান্তর করুন বা প্রদত্ত GIF ছবি থেকে ফ্রেমগুলি বের করুন৷ ছবি থেকে GIF GIF ফাইলকে ছবির ব্যাচে রূপান্তর করুন ছবির ব্যাচকে GIF ফাইলে রূপান্তর করুন GIF-তে ছবি শুরু করতে GIF ছবি বাছুন প্রথম ফ্রেমের আকার ব্যবহার করুন প্রথম ফ্রেমের মাত্রা দিয়ে নির্দিষ্ট আকার প্রতিস্থাপন করুন পুনরাবৃত্তি গণনা ফ্রেম বিলম্ব মিলি FPS ল্যাসো ব্যবহার করুন মুছে ফেলার জন্য অঙ্কন মোডের মতো ল্যাসো ব্যবহার করে আসল ছবি প্রিভিউ আলফা কনফেটি কনফেটি সংরক্ষণ, ভাগ করে নেওয়া এবং অন্যান্য প্রাথমিক ক্রিয়াগুলিতে দেখানো হবে৷ নিরাপদ মোড সাম্প্রতিক অ্যাপে অ্যাপের বিষয়বস্তু লুকায়। এটা ক্যাপচার বা রেকর্ড করা যাবে না. প্রস্থান করুন আপনি এখন পূর্বরূপ ছেড়ে গেলে, আপনাকে আবার ছবি যোগ করতে হবে ডিথারিং কোয়ান্টাইজার গ্রে স্কেল বায়ার টু বাই টু ডিথারিং বায়ার থ্রি বাই থ্রি ডিথারিং বায়ার ফোর বাই ফোর ডিথারিং বায়ার এইট বাই এইট ডিথারিং ফ্লয়েড স্টেইনবার্গ ডিথারিং জার্ভিস বিচারক নিঙ্কে ডিথারিং সিয়েরা ডিথারিং দুই সারি সিয়েরা ডিথারিং সিয়েরা লাইট ডিথারিং অ্যাটকিনসন ডিথারিং স্টুকি ডিথারিং বার্কস ডিথারিং মিথ্যা ফ্লয়েড স্টেইনবার্গ ডিথারিং লেফট টু রাইট ডিথারিং র‍্যান্ডম ডিথারিং সরল থ্রেশহোল্ড ডিথারিং সিগমা স্থানিক সিগমা মাঝারি ব্লার বি স্প্লাইন একটি বক্ররেখা বা পৃষ্ঠ, নমনীয় এবং অবিচ্ছিন্ন আকৃতির উপস্থাপনাকে মসৃণভাবে প্রসারিত করতে এবং আনুমানিকভাবে আনুমানিকভাবে সংজ্ঞায়িত করতে পিসওয়াইজ-সংজ্ঞায়িত বাইকিউবিক বহুপদী ফাংশন ব্যবহার করে নেটিভ স্ট্যাক ব্লার টিল্ট শিফট গ্লিচ পরিমাণ বীজ অ্যানাগ্লিফ গোলমাল পিক্সেল সাজান এলোমেলো বর্ধিত ত্রুটি চ্যানেল শিফট এক্স চ্যানেল শিফট ওয়াই দুর্নীতির আকার দুর্নীতি শিফট এক্স দুর্নীতি শিফট Y তাঁবুর অস্পষ্টতা সাইড ফেইড পাশ শীর্ষ নীচে শক্তি ইরোড অ্যানিসোট্রপিক ডিফিউশন ডিফিউশন সঞ্চালন অনুভূমিক বায়ু স্তব্ধ দ্রুত দ্বিপাক্ষিক ঝাপসা পয়সন ব্লার লগারিদমিক টোন ম্যাপিং ACES ফিল্মিক টোন ম্যাপিং স্ফটিক করা স্ট্রোক রঙ ফ্র্যাক্টাল গ্লাস প্রশস্ততা মার্বেল অশান্তি তেল জলের প্রভাব আকার ফ্রিকোয়েন্সি এক্স ফ্রিকোয়েন্সি Y প্রশস্ততা এক্স প্রশস্ততা Y পার্লিন বিকৃতি ACES হিল টোন ম্যাপিং হ্যাবল ফিল্মিক টোন ম্যাপিং হেজি-বার্গেস টোন ম্যাপিং গতি দেহাজে ওমেগা কালার ম্যাট্রিক্স 4x4 কালার ম্যাট্রিক্স 3x3 সহজ প্রভাব পোলারয়েড ট্রাইটানোমালি Deuteranomaly প্রোটানোমালি ভিনটেজ ব্রাউন এর কোডা ক্রোম নাইট ভিশন উষ্ণ কুল ট্রাইটানোপিয়া ডিউটারোনোটোপিয়া প্রোটানোপিয়া আক্রোমাটোমালি অ্যাক্রোমাটোপসিয়া শস্য আনশার্প প্যাস্টেল কমলা কুয়াশা গোলাপী স্বপ্ন গোল্ডেন আওয়ার গরম গ্রীষ্ম বেগুনি কুয়াশা সূর্যোদয় রঙিন ঘূর্ণি নরম বসন্তের আলো শরতের টোন ল্যাভেন্ডার স্বপ্ন সাইবারপাঙ্ক লেমনেড লাইট স্পেকট্রাল ফায়ার নাইট ম্যাজিক ফ্যান্টাসি ল্যান্ডস্কেপ রঙের বিস্ফোরণ বৈদ্যুতিক গ্রেডিয়েন্ট ক্যারামেল ডার্কনেস ফিউচারিস্টিক গ্রেডিয়েন্ট সবুজ সূর্য রংধনু বিশ্ব গভীর বেগুনি স্পেস পোর্টাল লাল ঘূর্ণি ডিজিটাল কোড বোকেহ অ্যাপ বার ইমোজি এলোমেলোভাবে পরিবর্তন হবে এলোমেলো ইমোজিস ইমোজি অক্ষম থাকা অবস্থায় আপনি র্যান্ডম ইমোজি ব্যবহার করতে পারবেন না র্যান্ডম ইমোজি সক্রিয় থাকা অবস্থায় আপনি একটি ইমোজি নির্বাচন করতে পারবেন না পুরাতন টিভি এলোমেলো ঝাপসা প্রিয় এখনও কোন প্রিয় ফিল্টার যোগ করা হয়নি ইমেজ ফরম্যাট আইকনগুলির অধীনে নির্বাচিত আকৃতি সহ একটি ধারক যোগ করে আইকন আকৃতি ড্রাগো অ্যালড্রিজ কাটঅফ আপনি জেগে উঠুন মবিয়াস উত্তরণ পিক রঙের অসঙ্গতি মূল গন্তব্যে ছবিগুলি ওভাররাইট করা হয়েছে ওভাররাইট ফাইল অপশন সক্রিয় থাকা অবস্থায় ইমেজ ফরম্যাট পরিবর্তন করা যাবে না কালার স্কিম হিসেবে ইমোজি ম্যানুয়ালি সংজ্ঞায়িত একটির পরিবর্তে অ্যাপের রঙ স্কিম হিসাবে ইমোজি প্রাথমিক রঙ ব্যবহার করে ইমেজ থেকে ম্যাটেরিয়াল ইউ প্যালেট তৈরি করে গাঢ় রং হালকা বৈকল্পিক পরিবর্তে নাইট মোড রঙের স্কিম ব্যবহার করে জেটপ্যাক কম্পোজ কোড হিসাবে অনুলিপি করুন রিং ব্লার ক্রস ব্লার বৃত্ত ঝাপসা স্টার ব্লার লিনিয়ার টিল্ট-শিফট ট্যাগ অপসারণ APNG টুলস ছবিগুলিকে APNG ছবিতে রূপান্তর করুন বা প্রদত্ত APNG ছবি থেকে ফ্রেমগুলি বের করুন৷ ছবি APNG APNG ফাইলকে ছবির ব্যাচে রূপান্তর করুন ছবির ব্যাচকে APNG ফাইলে রূপান্তর করুন APNG-তে ছবি শুরু করতে APNG ছবি বেছে নিন মোশন ব্লার জিপ প্রদত্ত ফাইল বা ছবি থেকে Zip ফাইল তৈরি করুন হ্যান্ডেল প্রস্থ টেনে আনুন কনফেটি টাইপ উৎসব বিস্ফোরণ বৃষ্টি কোণ JXL টুলস কোন মানের ক্ষতি ছাড়াই JXL ~ JPEG ট্রান্সকোডিং সম্পাদন করুন, অথবা GIF/APNG কে JXL অ্যানিমেশনে রূপান্তর করুন JXL থেকে JPEG JXL থেকে JPEG তে ক্ষতিহীন ট্রান্সকোডিং করুন JPEG থেকে JXL পর্যন্ত ক্ষতিহীন ট্রান্সকোডিং সম্পাদন করুন JPEG থেকে JXL শুরু করতে JXL ছবি বাছুন দ্রুত গাউসিয়ান ব্লার 2D দ্রুত গাউসিয়ান ব্লার 3D দ্রুত গাউসিয়ান ব্লার 4D গাড়ী ইস্টার অ্যাপটিকে ক্লিপবোর্ড ডেটা স্বয়ংক্রিয়ভাবে আটকানোর অনুমতি দেয়, তাই এটি প্রধান স্ক্রিনে প্রদর্শিত হবে এবং আপনি এটি প্রক্রিয়া করতে সক্ষম হবেন৷ হারমোনাইজেশন রঙ হারমোনাইজেশন লেভেল ল্যাঙ্কজোস বেসেল রিস্যাম্পলিং পদ্ধতি যা পিক্সেল মানগুলিতে বেসেল (জিঙ্ক) ফাংশন প্রয়োগ করে উচ্চ-মানের ইন্টারপোলেশন বজায় রাখে JXL থেকে GIF GIF ছবিগুলিকে JXL অ্যানিমেটেড ছবিতে রূপান্তর করুন APNG থেকে JXL APNG ছবিকে JXL অ্যানিমেটেড ছবিতে রূপান্তর করুন ইমেজ থেকে JXL JXL অ্যানিমেশনকে ছবির ব্যাচে রূপান্তর করুন JXL-এ ছবি ছবির ব্যাচকে JXL অ্যানিমেশনে রূপান্তর করুন আচরণ ফাইল পিকিং এড়িয়ে যান নির্বাচিত স্ক্রিনে এটি সম্ভব হলে ফাইল পিকার অবিলম্বে দেখানো হবে প্রিভিউ তৈরি করুন পূর্বরূপ জেনারেশন সক্ষম করে, এটি কিছু ডিভাইসে ক্র্যাশ এড়াতে সাহায্য করতে পারে, এটি একক সম্পাদনা বিকল্পের মধ্যে কিছু সম্পাদনা কার্যকারিতা অক্ষম করে ক্ষতিকারক কম্প্রেশন লসলেস এর পরিবর্তে ফাইলের আকার কমাতে ক্ষতিকর কম্প্রেশন ব্যবহার করে কম্প্রেশন টাইপ ফলস্বরূপ ইমেজ ডিকোডিং গতি নিয়ন্ত্রণ করে, এটি ফলস্বরূপ চিত্রটি দ্রুত খুলতে সাহায্য করবে, %1$s এর মান হল সবচেয়ে ধীরগতির ডিকোডিং, যেখানে %2$s - দ্রুততম, এই সেটিংটি আউটপুট চিত্রের আকার বাড়াতে পারে বাছাই তারিখ তারিখ (বিপরীত) নাম নাম (বিপরীত) চ্যানেল কনফিগারেশন আজ গতকাল এমবেডেড পিকার ইমেজ টুলবক্সের ইমেজ পিকার কোন অনুমতি নেই অনুরোধ একাধিক মিডিয়া বাছুন একক মিডিয়া বাছুন বাছাই আবার চেষ্টা করুন ল্যান্ডস্কেপে সেটিংস দেখান যদি এটি অক্ষম করা হয় তবে স্থায়ী দৃশ্যমান বিকল্পের পরিবর্তে ল্যান্ডস্কেপ মোডে সেটিংস বরাবরের মতো উপরের অ্যাপ বারে বোতামে খোলা হবে ফুলস্ক্রিন সেটিংস এটি সক্ষম করুন এবং সেটিংস পৃষ্ঠা সর্বদা স্লাইডযোগ্য ড্রয়ার শীটের পরিবর্তে ফুলস্ক্রিন হিসাবে খোলা হবে সুইচ টাইপ রচনা করুন একটি Jetpack রচনা উপাদান আপনি সুইচ একটি উপাদান আপনি সুইচ সর্বোচ্চ অ্যাঙ্করের আকার পরিবর্তন করুন পিক্সেল সাবলীল \"ফ্লুয়েন্ট\" ডিজাইন সিস্টেমের উপর ভিত্তি করে একটি সুইচ কুপারটিনো \"Cupertino\" ডিজাইন সিস্টেমের উপর ভিত্তি করে একটি সুইচ SVG-তে ছবি SVG চিত্রগুলিতে প্রদত্ত চিত্রগুলি ট্রেস করুন৷ নমুনা প্যালেট ব্যবহার করুন এই বিকল্পটি সক্রিয় থাকলে কোয়ান্টাইজেশন প্যালেট নমুনা করা হবে পথ বাদ দাও ডাউনস্কেলিং ছাড়াই বড় ছবি ট্রেস করার জন্য এই টুলের ব্যবহার বাঞ্ছনীয় নয়, এটি ক্র্যাশ হতে পারে এবং প্রক্রিয়াকরণের সময় বাড়াতে পারে ডাউনস্কেল চিত্র প্রক্রিয়াকরণের আগে চিত্রটি নিম্ন মাত্রায় স্কেল করা হবে, এটি সরঞ্জামটিকে দ্রুত এবং নিরাপদে কাজ করতে সহায়তা করে ন্যূনতম রঙের অনুপাত লাইন থ্রেশহোল্ড চতুর্মুখী থ্রেশহোল্ড বৃত্তাকার সহনশীলতা স্থানাঙ্ক পাথ স্কেল বৈশিষ্ট্য রিসেট করুন সমস্ত বৈশিষ্ট্য ডিফল্ট মানগুলিতে সেট করা হবে, লক্ষ্য করুন যে এই ক্রিয়াটি পূর্বাবস্থায় ফেরানো যাবে না৷ বিস্তারিত ডিফল্ট লাইন প্রস্থ ইঞ্জিন মোড উত্তরাধিকার LSTM নেটওয়ার্ক উত্তরাধিকার এবং LSTM রূপান্তর করুন প্রদত্ত বিন্যাসে ইমেজ ব্যাচ রূপান্তর করুন নতুন ফোল্ডার যোগ করুন নমুনা প্রতি বিট কম্প্রেশন ফটোমেট্রিক ব্যাখ্যা পিক্সেল প্রতি নমুনা প্ল্যানার কনফিগারেশন Y Cb Cr সাব স্যাম্পলিং Y Cb Cr পজিশনিং এক্স রেজোলিউশন Y রেজোলিউশন রেজোলিউশন ইউনিট স্ট্রিপ অফসেট স্ট্রিপ প্রতি সারি স্ট্রিপ বাইট গণনা JPEG ইন্টারচেঞ্জ ফরম্যাট JPEG ইন্টারচেঞ্জ ফরম্যাটের দৈর্ঘ্য স্থানান্তর ফাংশন হোয়াইট পয়েন্ট প্রাথমিক ক্রোমাটিসিটিস Y Cb Cr সহগ রেফারেন্স কালো সাদা তারিখ সময় ছবির বর্ণনা তৈরি করুন মডেল সফটওয়্যার শিল্পী কপিরাইট এক্সিফ সংস্করণ ফ্ল্যাশপিক্স সংস্করণ রঙের স্থান গামা পিক্সেল এক্স ডাইমেনশন পিক্সেল ওয়াই ডাইমেনশন পিক্সেল প্রতি সংকুচিত বিট মেকার নোট ব্যবহারকারী মন্তব্য সম্পর্কিত শব্দ ফাইল তারিখ সময় মূল তারিখ সময় ডিজিটালাইজড অফসেট সময় অফসেট সময় আসল অফসেট সময় ডিজিটালাইজড সাব সেকেন্ড সময় সাব সেকেন্ড সময় মূল সাব সেকেন্ড টাইম ডিজিটালাইজড প্রকাশের সময় F নম্বর এক্সপোজার প্রোগ্রাম বর্ণালী সংবেদনশীলতা ফটোগ্রাফিক সংবেদনশীলতা ওইসিএফ সংবেদনশীলতার ধরন স্ট্যান্ডার্ড আউটপুট সংবেদনশীলতা প্রস্তাবিত এক্সপোজার সূচক আইএসও গতি ISO গতি অক্ষাংশ yyy ISO গতি অক্ষাংশ zzz শাটার গতির মান অ্যাপারচার মান উজ্জ্বলতার মান এক্সপোজার বায়াস মান সর্বোচ্চ অ্যাপারচার মান বিষয় দূরত্ব মিটারিং মোড ফ্ল্যাশ বিষয় এলাকা ফোকাল দৈর্ঘ্য ফ্ল্যাশ এনার্জি স্থানিক ফ্রিকোয়েন্সি প্রতিক্রিয়া ফোকাল প্লেন এক্স রেজোলিউশন ফোকাল প্লেন ওয়াই রেজোলিউশন ফোকাল প্লেন রেজোলিউশন ইউনিট বিষয় অবস্থান এক্সপোজার সূচক সেন্সিং পদ্ধতি ফাইল উৎস CFA প্যাটার্ন কাস্টম রেন্ডার করা এক্সপোজার মোড হোয়াইট ব্যালেন্স ডিজিটাল জুম অনুপাত ফোকাল দৈর্ঘ্য ইন 35 মিমি ফিল্ম দৃশ্য ক্যাপচার টাইপ নিয়ন্ত্রণ লাভ করুন বৈপরীত্য স্যাচুরেশন তীক্ষ্ণতা ডিভাইস সেটিং বর্ণনা বিষয় দূরত্ব পরিসীমা ইমেজ ইউনিক আইডি ক্যামেরার মালিকের নাম বডি সিরিয়াল নম্বর লেন্স স্পেসিফিকেশন লেন্স তৈরি করুন লেন্স মডেল লেন্স সিরিয়াল নম্বর জিপিএস সংস্করণ আইডি GPS অক্ষাংশ রেফ জিপিএস অক্ষাংশ GPS দ্রাঘিমাংশ রেফ GPS দ্রাঘিমাংশ জিপিএস উচ্চতা রেফ জিপিএস উচ্চতা জিপিএস টাইম স্ট্যাম্প জিপিএস স্যাটেলাইট জিপিএস স্ট্যাটাস GPS পরিমাপ মোড জিপিএস ডিওপি জিপিএস গতি রেফ জিপিএস গতি জিপিএস ট্র্যাক রেফ জিপিএস ট্র্যাক জিপিএস আইএমজি দিক নির্দেশনা জিপিএস আইএমজি দিকনির্দেশ GPS মানচিত্র তথ্য GPS Dest Latitude Ref GPS গন্তব্য অক্ষাংশ GPS গন্তব্য দ্রাঘিমাংশ রেফ GPS গন্তব্য দ্রাঘিমাংশ GPS ডেস্ট বিয়ারিং রেফ জিপিএস ডেস্ট বিয়ারিং GPS গন্তব্য দূরত্ব রেফ GPS গন্তব্য দূরত্ব জিপিএস প্রক্রিয়াকরণ পদ্ধতি জিপিএস এলাকার তথ্য GPS তারিখ স্ট্যাম্প জিপিএস ডিফারেনশিয়াল GPS H পজিশনিং ত্রুটি৷ ইন্টারঅপারেবিলিটি সূচক DNG সংস্করণ ডিফল্ট ক্রপ সাইজ পূর্বরূপ চিত্র শুরু পূর্বরূপ চিত্র দৈর্ঘ্য দৃষ্টিভঙ্গি ফ্রেম সেন্সর নিচের সীমানা সেন্সর বাম সীমানা সেন্সর ডান বর্ডার সেন্সর টপ বর্ডার আইএসও প্রদত্ত ফন্ট এবং রঙ দিয়ে পাথে পাঠ্য আঁকুন ফন্ট সাইজ ওয়াটারমার্ক সাইজ টেক্সট পুনরাবৃত্তি করুন এক সময় আঁকার পরিবর্তে পাথ শেষ না হওয়া পর্যন্ত বর্তমান পাঠ্য পুনরাবৃত্তি করা হবে ড্যাশ সাইজ প্রদত্ত পথ বরাবর এটি আঁকার জন্য নির্বাচিত ছবি ব্যবহার করুন এই চিত্রটি আঁকা পথের পুনরাবৃত্তিমূলক এন্ট্রি হিসাবে ব্যবহার করা হবে শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত রূপরেখাযুক্ত ত্রিভুজ আঁকে শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত রূপরেখাযুক্ত ত্রিভুজ আঁকে রূপরেখাযুক্ত ত্রিভুজ ত্রিভুজ শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত বহুভুজ আঁকে বহুভুজ আউটলাইন করা বহুভুজ প্রারম্ভ বিন্দু থেকে শেষ বিন্দু পর্যন্ত রূপরেখা বহুভুজ আঁকে শীর্ষবিন্দু নিয়মিত বহুভুজ আঁকুন বহুভুজ আঁকুন যা ফ্রি ফর্মের পরিবর্তে নিয়মিত হবে শুরু বিন্দু থেকে শেষ বিন্দু পর্যন্ত তারকা আঁকা তারা আউটলাইন স্টার সূচনা বিন্দু থেকে শেষ বিন্দু পর্যন্ত রূপরেখাযুক্ত তারা আঁকে অভ্যন্তরীণ ব্যাসার্ধ অনুপাত নিয়মিত তারা আঁকুন স্টার আঁকুন যা ফ্রি ফর্মের পরিবর্তে নিয়মিত হবে অ্যান্টিলিয়াস তীক্ষ্ণ প্রান্ত প্রতিরোধ করতে অ্যান্টিলিয়াসিং সক্ষম করে পূর্বরূপের পরিবর্তে সম্পাদনা খুলুন আপনি যখন ইমেজটুলবক্সে খোলার জন্য (প্রিভিউ) ছবি নির্বাচন করেন, তখন পূর্বরূপ দেখার পরিবর্তে সম্পাদনা নির্বাচন শীট খোলা হবে ডকুমেন্ট স্ক্যানার নথি স্ক্যান করুন এবং পিডিএফ তৈরি করুন বা তাদের থেকে আলাদা ছবি তৈরি করুন স্ক্যানিং শুরু করতে ক্লিক করুন স্ক্যান করা শুরু করুন পিডিএফ হিসাবে সংরক্ষণ করুন পিডিএফ হিসাবে শেয়ার করুন নীচের বিকল্পগুলি ছবি সংরক্ষণের জন্য, PDF নয় হিস্টোগ্রাম HSV সমান করুন হিস্টোগ্রামকে সমান করুন শতাংশ লিখুন পাঠ্য ক্ষেত্র দ্বারা প্রবেশের অনুমতি দিন প্রিসেট নির্বাচনের পিছনে পাঠ্য ক্ষেত্র সক্ষম করে, ফ্লাইতে সেগুলি প্রবেশ করতে স্কেল রঙের স্থান রৈখিক হিস্টোগ্রাম পিক্সেলেশন সমান করুন গ্রিড সাইজ এক্স গ্রিড সাইজ Y হিস্টোগ্রাম অভিযোজিত সমান করুন হিস্টোগ্রাম অভিযোজিত LUV সমান করুন হিস্টোগ্রাম অভিযোজিত LAB সমান করুন CLAHE CLAHE ল্যাব CLAHE LUV সামগ্রীতে ক্রপ করুন ফ্রেমের রঙ উপেক্ষা করার জন্য রঙ টেমপ্লেট কোন টেমপ্লেট ফিল্টার যোগ করা হয়নি নতুন তৈরি করুন স্ক্যান করা QR কোড একটি বৈধ ফিল্টার টেমপ্লেট নয় QR কোড স্ক্যান করুন নির্বাচিত ফাইলে কোনো ফিল্টার টেমপ্লেট ডেটা নেই টেমপ্লেট তৈরি করুন টেমপ্লেট নাম এই ছবিটি এই ফিল্টার টেমপ্লেটের পূর্বরূপ দেখতে ব্যবহার করা হবে টেমপ্লেট ফিল্টার QR কোড ইমেজ হিসাবে ফাইল হিসাবে ফাইল হিসাবে সংরক্ষণ করুন QR কোড ইমেজ হিসাবে সংরক্ষণ করুন টেমপ্লেট মুছুন আপনি নির্বাচিত টেমপ্লেট ফিল্টার মুছে ফেলতে চলেছেন৷ এই অপারেশন পূর্বাবস্থায় ফেরানো যাবে না \"%1$s\" (%2$s) নামের সাথে ফিল্টার টেমপ্লেট যোগ করা হয়েছে ফিল্টার প্রিভিউ QR এবং বারকোড QR কোড স্ক্যান করুন এবং এর বিষয়বস্তু পান বা নতুন একটি তৈরি করতে আপনার স্ট্রিং পেস্ট করুন কোড বিষয়বস্তু ক্ষেত্রের বিষয়বস্তু প্রতিস্থাপন করতে যেকোনো বারকোড স্ক্যান করুন, বা নির্বাচিত প্রকারের সাথে নতুন বারকোড তৈরি করতে কিছু টাইপ করুন QR বর্ণনা মিন QR কোড স্ক্যান করার জন্য সেটিংসে ক্যামেরার অনুমতি দিন ডকুমেন্ট স্ক্যানার স্ক্যান করতে সেটিংসে ক্যামেরার অনুমতি দিন ঘন বি-স্পলাইন হ্যামিং হ্যানিং ব্ল্যাকম্যান ওয়েলচ কোয়াড্রিক গাউসিয়ান স্ফিংক্স বার্টলেট রবিডক্স রবিডক্স শার্প স্প্লাইন 16 স্প্লাইন 36 স্প্লাইন 64 কায়সার বার্টলেট-হি বক্স বোহমান ল্যাঙ্কজোস 2 ল্যাঙ্কজোস ঘ ল্যাঙ্কজোস 4 ল্যাঙ্কজোস 2 জিঙ্ক ল্যাঙ্কজোস 3 জিঙ্ক ল্যাঙ্কজোস 4 জিঙ্ক কিউবিক ইন্টারপোলেশন নিকটতম 16 পিক্সেল বিবেচনা করে মসৃণ স্কেলিং প্রদান করে, বাইলিনিয়ারের চেয়ে ভাল ফলাফল দেয় একটি বক্ররেখা বা পৃষ্ঠ, নমনীয় এবং অবিচ্ছিন্ন আকৃতির উপস্থাপনাকে মসৃণভাবে ইন্টারপোলেট এবং আনুমানিক করতে টুকরো টুকরো-সংজ্ঞায়িত বহুপদী ফাংশন ব্যবহার করে একটি উইন্ডো ফাংশন সিগন্যালের প্রান্তগুলিকে টেপার করে বর্ণালী ফুটো কমাতে ব্যবহৃত হয়, সিগন্যাল প্রক্রিয়াকরণে দরকারী হ্যান উইন্ডোর একটি রূপ, সাধারণত সিগন্যাল প্রসেসিং অ্যাপ্লিকেশনগুলিতে বর্ণালী ফুটো কমাতে ব্যবহৃত হয় একটি উইন্ডো ফাংশন যা বর্ণালী ফুটো কমিয়ে ভাল ফ্রিকোয়েন্সি রেজোলিউশন প্রদান করে, প্রায়শই সংকেত প্রক্রিয়াকরণে ব্যবহৃত হয় কম বর্ণালী ফুটো সহ ভাল ফ্রিকোয়েন্সি রেজোলিউশন দেওয়ার জন্য ডিজাইন করা একটি উইন্ডো ফাংশন, প্রায়শই সংকেত প্রক্রিয়াকরণ অ্যাপ্লিকেশনগুলিতে ব্যবহৃত হয় একটি পদ্ধতি যা ইন্টারপোলেশনের জন্য একটি দ্বিঘাত ফাংশন ব্যবহার করে, মসৃণ এবং অবিচ্ছিন্ন ফলাফল প্রদান করে একটি ইন্টারপোলেশন পদ্ধতি যা একটি গাউসিয়ান ফাংশন প্রয়োগ করে, চিত্রগুলিকে মসৃণ এবং কমানোর জন্য দরকারী একটি উন্নত রিস্যাম্পলিং পদ্ধতি যা ন্যূনতম আর্টিফ্যাক্ট সহ উচ্চ-মানের ইন্টারপোলেশন প্রদান করে বর্ণালী ফুটো কমাতে সংকেত প্রক্রিয়াকরণে ব্যবহৃত একটি ত্রিভুজাকার উইন্ডো ফাংশন প্রাকৃতিক চিত্রের আকার পরিবর্তন, তীক্ষ্ণতা এবং মসৃণতা ভারসাম্যের জন্য অপ্টিমাইজ করা একটি উচ্চ-মানের ইন্টারপোলেশন পদ্ধতি রবিডক্স পদ্ধতির একটি তীক্ষ্ণ বৈকল্পিক, খাস্তা চিত্রের আকার পরিবর্তনের জন্য অপ্টিমাইজ করা হয়েছে একটি স্প্লাইন-ভিত্তিক ইন্টারপোলেশন পদ্ধতি যা একটি 16-ট্যাপ ফিল্টার ব্যবহার করে মসৃণ ফলাফল প্রদান করে একটি স্প্লাইন-ভিত্তিক ইন্টারপোলেশন পদ্ধতি যা একটি 36-ট্যাপ ফিল্টার ব্যবহার করে মসৃণ ফলাফল প্রদান করে একটি স্প্লাইন-ভিত্তিক ইন্টারপোলেশন পদ্ধতি যা একটি 64-ট্যাপ ফিল্টার ব্যবহার করে মসৃণ ফলাফল প্রদান করে একটি ইন্টারপোলেশন পদ্ধতি যা কায়সার উইন্ডো ব্যবহার করে, প্রধান-লোব প্রস্থ এবং পার্শ্ব-লোব স্তরের মধ্যে ট্রেড-অফের উপর ভাল নিয়ন্ত্রণ প্রদান করে বার্টলেট এবং হ্যান উইন্ডোর সমন্বয়ে একটি হাইব্রিড উইন্ডো ফাংশন, সিগন্যাল প্রক্রিয়াকরণে বর্ণালী ফুটো কমাতে ব্যবহৃত একটি সাধারণ রিস্যাম্পলিং পদ্ধতি যা নিকটতম পিক্সেল মানগুলির গড় ব্যবহার করে, প্রায়শই একটি ব্লকি চেহারার ফলে বর্ণালী ফুটো কমাতে ব্যবহৃত একটি উইন্ডো ফাংশন, সিগন্যাল প্রসেসিং অ্যাপ্লিকেশনগুলিতে ভাল ফ্রিকোয়েন্সি রেজোলিউশন প্রদান করে একটি রিস্যাম্পলিং পদ্ধতি যা 2-লোব ল্যাঙ্কজোস ফিল্টার ব্যবহার করে ন্যূনতম আর্টিফ্যাক্ট সহ উচ্চ-মানের ইন্টারপোলেশনের জন্য একটি রিস্যাম্পলিং পদ্ধতি যা ন্যূনতম নিদর্শন সহ উচ্চ-মানের ইন্টারপোলেশনের জন্য একটি 3-লোব ল্যাঙ্কজোস ফিল্টার ব্যবহার করে একটি রিস্যাম্পলিং পদ্ধতি যা 4-লোব ল্যাঙ্কজোস ফিল্টার ব্যবহার করে ন্যূনতম আর্টিফ্যাক্ট সহ উচ্চ-মানের ইন্টারপোলেশনের জন্য ল্যাঙ্কজোস 2 ফিল্টারের একটি বৈকল্পিক যা জিঙ্ক ফাংশন ব্যবহার করে, ন্যূনতম শিল্পকর্মের সাথে উচ্চ-মানের ইন্টারপোলেশন প্রদান করে ল্যাঙ্কজোস 3 ফিল্টারের একটি বৈকল্পিক যা জিঙ্ক ফাংশন ব্যবহার করে, ন্যূনতম শিল্পকর্মের সাথে উচ্চ-মানের ইন্টারপোলেশন প্রদান করে ল্যাঙ্কজোস 4 ফিল্টারের একটি বৈকল্পিক যা জিঙ্ক ফাংশন ব্যবহার করে, ন্যূনতম শিল্পকর্মের সাথে উচ্চ-মানের ইন্টারপোলেশন প্রদান করে হ্যানিং EWA মসৃণ ইন্টারপোলেশন এবং রিস্যাম্পলিং এর জন্য হ্যানিং ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ Robidoux EWA উচ্চ মানের পুনঃনমুনাকরণের জন্য রবিডক্স ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ ব্ল্যাকম্যান ইভ রিংিং আর্টিফ্যাক্টগুলি কমানোর জন্য ব্ল্যাকম্যান ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ কোয়াড্রিক EWA মসৃণ ইন্টারপোলেশনের জন্য কোয়াড্রিক ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ Robidoux শার্প EWA তীক্ষ্ণ ফলাফলের জন্য রবিডক্স শার্প ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ Lanczos 3 Jinc EWA ল্যাঙ্কজোস 3 জিঙ্ক ফিল্টারের উপবৃত্তাকার ওয়েটেড এভারেজ (EWA) ভেরিয়েন্ট কম অ্যালিয়াসিং সহ উচ্চ-মানের রিস্যাম্পলিং এর জন্য জিনসেং তীক্ষ্ণতা এবং মসৃণতার একটি ভাল ভারসাম্য সহ উচ্চ-মানের চিত্র প্রক্রিয়াকরণের জন্য ডিজাইন করা একটি রিস্যাম্পলিং ফিল্টার জিনসেং ইডব্লিউএ উন্নত চিত্রের গুণমানের জন্য জিনসেং ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ Lanczos শার্প EWA ন্যূনতম শিল্পকর্মের সাথে তীক্ষ্ণ ফলাফল অর্জনের জন্য ল্যাঙ্কজোস শার্প ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ Lanczos 4 শার্পেস্ট EWA অত্যন্ত তীক্ষ্ণ চিত্র পুনরায় নমুনা করার জন্য ল্যাঙ্কজোস 4 শার্পেস্ট ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ ল্যাঙ্কজোস সফট ইডব্লিউএ মসৃণ চিত্র পুনরায় নমুনা করার জন্য ল্যাঙ্কজোস সফট ফিল্টারের উপবৃত্তাকার ওজনযুক্ত গড় (EWA) রূপ হাসন নরম মসৃণ এবং আর্টিফ্যাক্ট-মুক্ত ইমেজ স্কেলিং এর জন্য হাসান দ্বারা ডিজাইন করা একটি রিস্যাম্পলিং ফিল্টার ফর্ম্যাট রূপান্তর এক বিন্যাস থেকে অন্য বিন্যাসে চিত্রের ব্যাচ রূপান্তর করুন চিরতরে বরখাস্ত করুন ইমেজ স্ট্যাকিং নির্বাচিত মিশ্রণ মোডগুলির সাথে একে অপরের উপরে ছবিগুলিকে স্ট্যাক করুন৷ ছবি যোগ করুন বিন গণনা Clahe HSL Clahe HSV হিস্টোগ্রাম অভিযোজিত HSL সমান করুন হিস্টোগ্রাম অভিযোজিত HSV সমান করুন এজ মোড ক্লিপ মোড়ানো বর্ণান্ধতা নির্বাচিত রঙ অন্ধত্ব বৈকল্পিক জন্য থিম রং মানিয়ে মোড নির্বাচন করুন লাল এবং সবুজ রঙের মধ্যে পার্থক্য করতে অসুবিধা সবুজ এবং লাল রঙের মধ্যে পার্থক্য করতে অসুবিধা নীল এবং হলুদ রঙের মধ্যে পার্থক্য করতে অসুবিধা লাল রং বোঝার অক্ষমতা সবুজ রং উপলব্ধি করতে অক্ষমতা নীল রং উপলব্ধি করতে অক্ষমতা সমস্ত রং সংবেদনশীলতা হ্রাস সম্পূর্ণ বর্ণান্ধতা, শুধুমাত্র ধূসর শেড দেখা কালার ব্লাইন্ড স্কিম ব্যবহার করবেন না রঙ ঠিক থিমে সেট করা হবে সিগময়েডাল Lagrange 2 অর্ডার 2 এর একটি Lagrange ইন্টারপোলেশন ফিল্টার, মসৃণ রূপান্তর সহ উচ্চ-মানের চিত্র স্কেলিং এর জন্য উপযুক্ত ল্যাগ্রেঞ্জ ঘ অর্ডার 3 এর একটি ল্যাগ্রেঞ্জ ইন্টারপোলেশন ফিল্টার, ইমেজ স্কেলিং এর জন্য আরও ভাল নির্ভুলতা এবং মসৃণ ফলাফল প্রদান করে ল্যাঙ্কজোস 6 একটি ল্যাঙ্কজোস রিস্যাম্পলিং ফিল্টার 6 এর উচ্চ ক্রম সহ, তীক্ষ্ণ এবং আরও সঠিক চিত্র স্কেলিং প্রদান করে ল্যাঙ্কজোস 6 জিঙ্ক উন্নত ইমেজ রিস্যাম্পলিং মানের জন্য জিঙ্ক ফাংশন ব্যবহার করে ল্যাঙ্কজোস 6 ফিল্টারের একটি বৈকল্পিক লিনিয়ার বক্স ব্লার লিনিয়ার টেন্ট ব্লার লিনিয়ার গাউসিয়ান বক্স ব্লার লিনিয়ার স্ট্যাক ব্লার গাউসিয়ান বক্স ব্লার লিনিয়ার ফাস্ট গাউসিয়ান ব্লার নেক্সট লিনিয়ার ফাস্ট গাউসিয়ান ব্লার লিনিয়ার গাউসিয়ান ব্লার পেইন্ট হিসাবে এটি ব্যবহার করার জন্য একটি ফিল্টার চয়ন করুন ফিল্টার প্রতিস্থাপন করুন আপনার অঙ্কনে ব্রাশ হিসাবে এটি ব্যবহার করতে নীচের ফিল্টারটি চয়ন করুন টিআইএফএফ কম্প্রেশন স্কিম নিম্ন পলি বালি পেইন্টিং ইমেজ স্প্লিটিং সারি বা কলাম দ্বারা একক ছবি বিভক্ত করুন ফিট টু বাউন্ডস পছন্দসই আচরণ অর্জন করতে এই প্যারামিটারের সাথে ক্রপ রিসাইজ মোড একত্রিত করুন (আসপেক্ট রেশিওতে ক্রপ/ফিট করুন) ভাষাগুলি সফলভাবে আমদানি করা হয়েছে৷ OCR মডেলের ব্যাকআপ আমদানি রপ্তানি অবস্থান কেন্দ্র উপরের বাম উপরে ডান নীচে বাম নীচে ডান শীর্ষ কেন্দ্র কেন্দ্র ডান নীচে কেন্দ্র কেন্দ্র বাম টার্গেট ইমেজ প্যালেট স্থানান্তর উন্নত তেল সাধারণ পুরানো টিভি এইচডিআর গোথাম সহজ স্কেচ নরম আভা রঙিন পোস্টার ট্রাই টোন তৃতীয় রঙ ক্লাহে ওকলাব ক্লারা ওলচ Clahe Jzazbz পোলকা ডট ক্লাস্টারড 2x2 ডিথারিং ক্লাস্টারড 4x4 ডিথারিং ক্লাস্টারড 8x8 ডিথারিং ইলিলোমা ডিথারিং কোন পছন্দসই বিকল্প নির্বাচন করা হয়নি, সেগুলিকে টুল পৃষ্ঠায় যোগ করুন ফেভারিট যোগ করুন পরিপূরক সাদৃশ্যপূর্ণ ট্রায়াডিক বিভক্ত পরিপূরক টেট্রাডিক বর্গক্ষেত্র সাদৃশ্য + পরিপূরক কালার টুলস মিশ্রিত করুন, টোন তৈরি করুন, শেড তৈরি করুন এবং আরও অনেক কিছু কালার হারমোনিস কালার শেডিং প্রকরণ টিন্টস টোন শেডস রঙের মিশ্রণ রঙের তথ্য নির্বাচিত রঙ মিশ্রিত রং গতিশীল রং চালু থাকা অবস্থায় monet ব্যবহার করা যাবে না 512x512 2D LUT লক্ষ্য LUT চিত্র একজন অপেশাদার মিস শিষ্টাচার নরম কমনীয়তা নরম কমনীয়তা বৈকল্পিক প্যালেট স্থানান্তর বৈকল্পিক 3D LUT টার্গেট 3D LUT ফাইল (.cube / .CUBE) LUT ব্লিচ বাইপাস মোমবাতির আলো ড্রপ ব্লুজ এজি অ্যাম্বার পতন রং ফিল্ম স্টক 50 কুয়াশাচ্ছন্ন রাত কোডাক নিরপেক্ষ LUT ইমেজ পান প্রথমে, নিরপেক্ষ LUT এ একটি ফিল্টার প্রয়োগ করতে আপনার প্রিয় ফটো এডিটিং অ্যাপ্লিকেশনটি ব্যবহার করুন যা আপনি এখানে পেতে পারেন। এটি সঠিকভাবে কাজ করার জন্য প্রতিটি পিক্সেল রঙ অবশ্যই অন্যান্য পিক্সেলের উপর নির্ভর করবে না (যেমন ব্লার কাজ করবে না)। একবার প্রস্তুত হয়ে গেলে, 512*512 LUT ফিল্টারের জন্য ইনপুট হিসাবে আপনার নতুন LUT চিত্রটি ব্যবহার করুন পপ আর্ট সেলুলয়েড কফি গোল্ডেন ফরেস্ট সবুজাভ রেট্রো হলুদ লিঙ্ক পূর্বরূপ যেখানে আপনি পাঠ্য (QRCode, OCR ইত্যাদি) পেতে পারেন সেখানে লিঙ্কের পূর্বরূপ পুনরুদ্ধার সক্ষম করে লিঙ্ক ICO ফাইলগুলি শুধুমাত্র সর্বাধিক 256 x 256 আকারে সংরক্ষণ করা যেতে পারে WEBP-এ GIF GIF ছবিগুলিকে WEBP অ্যানিমেটেড ছবিতে রূপান্তর করুন৷ WEBP টুলস ছবিগুলিকে WEBP অ্যানিমেটেড ছবিতে রূপান্তর করুন বা প্রদত্ত WEBP অ্যানিমেশন থেকে ফ্রেমগুলি বের করুন৷ ছবি WEBP WEBP ফাইলকে ছবির ব্যাচে রূপান্তর করুন WEBP ফাইলে চিত্রের ব্যাচ রূপান্তর করুন WEBP-এ ছবি শুরু করতে WEBP ছবি বাছুন ফাইলগুলিতে সম্পূর্ণ অ্যাক্সেস নেই সমস্ত ফাইলগুলিকে JXL, QOI এবং অন্যান্য ছবিগুলি দেখার অনুমতি দিন যেগুলি Android-এ ছবি হিসাবে স্বীকৃত নয়৷ অনুমতি ছাড়া ইমেজ টুলবক্স সেই ছবিগুলো দেখাতে অক্ষম ডিফল্ট আঁকা রঙ ডিফল্ট ড্র পাথ মোড টাইমস্ট্যাম্প যোগ করুন আউটপুট ফাইলনামে টাইমস্ট্যাম্প যোগ করা সক্ষম করে ফরম্যাট করা টাইমস্ট্যাম্প মৌলিক মিলের পরিবর্তে আউটপুট ফাইলনামে টাইমস্ট্যাম্প বিন্যাস সক্ষম করুন তাদের বিন্যাস নির্বাচন করতে টাইমস্ট্যাম্প সক্ষম করুন৷ ওয়ান টাইম সেভ লোকেশন একবার সংরক্ষণ করা অবস্থানগুলি দেখুন এবং সম্পাদনা করুন যা আপনি বেশিরভাগ বিকল্পে সংরক্ষণ বোতামটি দীর্ঘক্ষণ টিপে ব্যবহার করতে পারেন সম্প্রতি ব্যবহৃত সিআই চ্যানেল গ্রুপ টেলিগ্রামে ইমেজ টুলবক্স 🎉 আমাদের চ্যাটে যোগ দিন যেখানে আপনি যা চান তা নিয়ে আলোচনা করতে পারেন এবং CI চ্যানেলটিও দেখতে পারেন যেখানে আমি বেটা এবং ঘোষণা পোস্ট করি অ্যাপের নতুন সংস্করণ সম্পর্কে বিজ্ঞপ্তি পান এবং ঘোষণা পড়ুন প্রদত্ত মাত্রার সাথে একটি চিত্র ফিট করুন এবং পটভূমিতে অস্পষ্ট বা রঙ প্রয়োগ করুন টুলস ব্যবস্থা টাইপ অনুসারে গ্রুপ টুল একটি কাস্টম তালিকা বিন্যাসের পরিবর্তে প্রধান স্ক্রিনে সরঞ্জামগুলিকে তাদের প্রকার অনুসারে গোষ্ঠীবদ্ধ করে৷ ডিফল্ট মান সিস্টেম বার দৃশ্যমানতা সোয়াইপ করে সিস্টেম বার দেখান সিস্টেম বারগুলি লুকানো থাকলে তা দেখানোর জন্য সোয়াইপ করা সক্ষম করে৷ অটো সব লুকান সব দেখান নেভি বার লুকান স্ট্যাটাস বার লুকান নয়েজ জেনারেশন পার্লিন বা অন্যান্য ধরনের মত বিভিন্ন শব্দ তৈরি করুন ফ্রিকোয়েন্সি নয়েজ টাইপ ঘূর্ণন প্রকার ফ্র্যাক্টাল টাইপ অষ্টক স্বল্পতা লাভ ওজনযুক্ত শক্তি পিং পং শক্তি দূরত্ব ফাংশন রিটার্ন টাইপ জিটার ডোমেন ওয়ার্প প্রান্তিককরণ কাস্টম ফাইলের নাম অবস্থান এবং ফাইলের নাম নির্বাচন করুন যা বর্তমান চিত্র সংরক্ষণ করতে ব্যবহার করা হবে কাস্টম নামের সাথে ফোল্ডারে সংরক্ষিত কোলাজ মেকার 20টি পর্যন্ত ছবি থেকে কোলাজ তৈরি করুন৷ কোলাজ প্রকার অবস্থান সামঞ্জস্য করতে ছবি অদলবদল করতে, সরাতে এবং জুম করতে ধরে রাখুন ঘূর্ণন অক্ষম করুন দুই আঙুলের অঙ্গভঙ্গি দিয়ে ছবি ঘোরানো প্রতিরোধ করে সীমানায় স্ন্যাপিং সক্ষম করুন সরানো বা জুম করার পরে, ছবিগুলি ফ্রেমের প্রান্তগুলি পূরণ করতে স্ন্যাপ করবে৷ হিস্টোগ্রাম আপনাকে সামঞ্জস্য করতে সাহায্য করার জন্য RGB বা উজ্জ্বলতা ইমেজ হিস্টোগ্রাম এই চিত্রটি আরজিবি এবং উজ্জ্বলতা হিস্টোগ্রাম তৈরি করতে ব্যবহার করা হবে Tesseract অপশন টেসার্যাক্ট ইঞ্জিনের জন্য কিছু ইনপুট ভেরিয়েবল প্রয়োগ করুন কাস্টম বিকল্প এই প্যাটার্ন অনুসরণ করে বিকল্পগুলি লিখতে হবে: \"--{option_name} {value}\" অটো ক্রপ বিনামূল্যে কর্নার বহুভুজ দ্বারা চিত্র ক্রপ করুন, এটি দৃষ্টিকোণকেও সংশোধন করে ইমেজ বাউন্ডে জোর করে পয়েন্ট পয়েন্টগুলি চিত্র সীমার দ্বারা সীমাবদ্ধ থাকবে না, এটি আরও সুনির্দিষ্ট দৃষ্টিকোণ সংশোধনের জন্য দরকারী মুখোশ টানা পথের অধীনে বিষয়বস্তু সচেতন পূরণ করুন স্পট নিরাময় সার্কেল কার্নেল ব্যবহার করুন খোলা হচ্ছে বন্ধ হচ্ছে রূপগত গ্রেডিয়েন্ট শীর্ষ টুপি কালো টুপি টোন কার্ভস কার্ভ রিসেট করুন বক্ররেখাগুলিকে ডিফল্ট মানে ফিরিয়ে আনা হবে লাইন স্টাইল গ্যাপ সাইজ ড্যাশড ডট ড্যাশড মুদ্রাঙ্কিত জিগজ্যাগ নির্দিষ্ট ফাঁক আকারের সাথে আঁকা পথ বরাবর ড্যাশড রেখা আঁকে প্রদত্ত পথ বরাবর ডট এবং ড্যাশড লাইন আঁকে শুধু ডিফল্ট সোজা লাইন নির্দিষ্ট ব্যবধান সহ পথ বরাবর নির্বাচিত আকার আঁকে পথ বরাবর তরঙ্গায়িত জিগজ্যাগ আঁকে জিগজ্যাগ অনুপাত শর্টকাট তৈরি করুন পিন করার জন্য টুল বেছে নিন টুলটি আপনার লঞ্চারের হোম স্ক্রিনে শর্টকাট হিসাবে যোগ করা হবে, প্রয়োজনীয় আচরণ অর্জন করতে \"ফাইল পিকিং এড়িয়ে যান\" সেটিংসের সাথে এটি ব্যবহার করুন ফ্রেম স্ট্যাক করবেন না পূর্ববর্তী ফ্রেম নিষ্পত্তি সক্ষম করে, তাই তারা একে অপরের উপর স্ট্যাক করবে না ক্রসফেড ফ্রেম একে অপরের মধ্যে ক্রসফেড করা হবে ক্রসফেড ফ্রেম গণনা থ্রেশহোল্ড ওয়ান থ্রেশহোল্ড দুই ক্যানি মিরর 101 বর্ধিত জুম ব্লার ল্যাপ্লাসিয়ান সিম্পল সোবেল সিম্পল হেল্পার গ্রিড সুনির্দিষ্ট ম্যানিপুলেশনে সাহায্য করার জন্য অঙ্কন এলাকার উপরে সমর্থনকারী গ্রিড দেখায় গ্রিড রঙ কক্ষের প্রস্থ কোষের উচ্চতা কমপ্যাক্ট নির্বাচক কিছু নির্বাচন নিয়ন্ত্রণ কম জায়গা নিতে কমপ্যাক্ট লেআউট ব্যবহার করবে ছবি তোলার জন্য সেটিংসে ক্যামেরার অনুমতি দিন লেআউট প্রধান পর্দা শিরোনাম ধ্রুবক হার ফ্যাক্টর (CRF) %1$s এর মান মানে একটি ধীর সংকোচন, যার ফলে ফাইলের আকার অপেক্ষাকৃত ছোট হয়। %2$s মানে একটি দ্রুত কম্প্রেশন, যার ফলে একটি বড় ফাইল। লুট লাইব্রেরি LUTs এর সংগ্রহ ডাউনলোড করুন, যা আপনি ডাউনলোড করার পরে আবেদন করতে পারেন LUT-এর সংগ্রহ আপডেট করুন (শুধুমাত্র নতুনগুলি সারিবদ্ধ হবে), যা আপনি ডাউনলোড করার পরে আবেদন করতে পারেন ফিল্টারগুলির জন্য ডিফল্ট চিত্র পূর্বরূপ পরিবর্তন করুন পূর্বরূপ চিত্র লুকান দেখান স্লাইডার টাইপ অভিনব উপাদান 2 একটি অভিনব-সুদর্শন স্লাইডার. এটি ডিফল্ট বিকল্প একটি উপাদান 2 স্লাইডার একটি উপাদান আপনি স্লাইডার আবেদন করুন কেন্দ্র ডায়ালগ বোতাম ডায়ালগের বোতামগুলি সম্ভব হলে বাম দিকের পরিবর্তে কেন্দ্রে স্থাপন করা হবে ওপেন সোর্স লাইসেন্স এই অ্যাপে ব্যবহৃত ওপেন সোর্স লাইব্রেরির লাইসেন্স দেখুন এলাকা পিক্সেল এরিয়া রিলেশন ব্যবহার করে রিস্যাম্পলিং। এটি ইমেজ ডিসিমেশনের জন্য একটি পছন্দের পদ্ধতি হতে পারে, কারণ এটি ময়ার\'-মুক্ত ফলাফল দেয়। কিন্তু যখন ছবিটি জুম করা হয়, তখন এটি \"নিকটবর্তী\" পদ্ধতির অনুরূপ। টোনম্যাপিং সক্ষম করুন % লিখুন সাইটটি অ্যাক্সেস করতে পারবেন না, ভিপিএন ব্যবহার করার চেষ্টা করুন বা ইউআরএল সঠিক কিনা তা পরীক্ষা করুন মার্কআপ স্তর অবাধে ছবি, টেক্সট এবং আরও অনেক কিছু রাখার ক্ষমতা সহ লেয়ার মোড স্তর সম্পাদনা করুন ইমেজ উপর স্তর একটি পটভূমি হিসাবে একটি চিত্র ব্যবহার করুন এবং এটির উপরে বিভিন্ন স্তর যুক্ত করুন পটভূমিতে স্তর প্রথম বিকল্পের মতই কিন্তু ছবির পরিবর্তে রঙ সহ বেটা দ্রুত সেটিংস সাইড ছবি সম্পাদনা করার সময় নির্বাচিত পাশে একটি ভাসমান স্ট্রিপ যোগ করুন, যা ক্লিক করলে দ্রুত সেটিংস খুলবে নির্বাচন পরিষ্কার করুন সেট করা গোষ্ঠী \"%1$s\" ডিফল্টরূপে ভেঙে যাবে সেট করা গ্রুপ \"%1$s\" ডিফল্টরূপে প্রসারিত হবে বেস64 টুলস Base64 স্ট্রিংকে ইমেজ ডিকোড করুন, বা Base64 ফরম্যাটে ইমেজ এনকোড করুন বেস64 প্রদত্ত মান একটি বৈধ Base64 স্ট্রিং নয়৷ খালি বা অবৈধ Base64 স্ট্রিং কপি করা যাবে না পেস্ট বেস64 কপি Base64 Base64 স্ট্রিং কপি বা সংরক্ষণ করতে ছবি লোড করুন। আপনার যদি স্ট্রিংটি থাকে তবে আপনি ছবিটি পেতে উপরে পেস্ট করতে পারেন বেস 64 সংরক্ষণ করুন শেয়ার বেস64 অপশন কর্ম আমদানি বেস64 বেস64 অ্যাকশন আউটলাইন যোগ করুন নির্দিষ্ট রঙ এবং প্রস্থ সহ পাঠ্যের চারপাশে রূপরেখা যুক্ত করুন রূপরেখার রঙ রূপরেখার আকার ঘূর্ণন ফাইলের নাম হিসাবে চেকসাম আউটপুট ইমেজ তাদের ডেটা চেকসামের সাথে সম্পর্কিত নাম থাকবে ফ্রি সফটওয়্যার (অংশীদার) অ্যান্ড্রয়েড অ্যাপ্লিকেশনের অংশীদার চ্যানেলে আরও দরকারী সফ্টওয়্যার অ্যালগরিদম চেকসাম টুলস চেকসাম তুলনা করুন, হ্যাশ গণনা করুন বা বিভিন্ন হ্যাশিং অ্যালগরিদম ব্যবহার করে ফাইল থেকে হেক্স স্ট্রিং তৈরি করুন হিসাব করুন টেক্সট হ্যাশ চেকসাম নির্বাচিত অ্যালগরিদমের উপর ভিত্তি করে এর চেকসাম গণনা করতে ফাইলটি বেছে নিন নির্বাচিত অ্যালগরিদমের উপর ভিত্তি করে এর চেকসাম গণনা করতে পাঠ্য লিখুন উৎস চেকসাম তুলনা করার জন্য চেকসাম মিল ! পার্থক্য চেকসাম সমান, এটি নিরাপদ হতে পারে চেকসাম সমান নয়, ফাইল অনিরাপদ হতে পারে! মেশ গ্রেডিয়েন্ট মেশ গ্রেডিয়েন্টের অনলাইন সংগ্রহ দেখুন শুধুমাত্র TTF এবং OTF ফন্ট ইম্পোর্ট করা যাবে হরফ আমদানি করুন (TTF/OTF) ফন্ট রপ্তানি করুন আমদানিকৃত ফন্ট চেষ্টা সংরক্ষণ করার সময় ত্রুটি, আউটপুট ফোল্ডার পরিবর্তন করার চেষ্টা করুন ফাইলের নাম সেট করা নেই কোনোটিই নয় কাস্টম পেজ পৃষ্ঠা নির্বাচন টুল প্রস্থান নিশ্চিতকরণ নির্দিষ্ট টুল ব্যবহার করার সময় আপনার যদি অসংরক্ষিত পরিবর্তন থাকে এবং এটি বন্ধ করার চেষ্টা করে, তাহলে নিশ্চিত করুন ডায়ালগ দেখানো হবে EXIF সম্পাদনা করুন রিকম্প্রেশন ছাড়াই একক ছবির মেটাডেটা পরিবর্তন করুন উপলব্ধ ট্যাগ সম্পাদনা করতে আলতো চাপুন স্টিকার পরিবর্তন করুন ফিট প্রস্থ মানানসই উচ্চতা ব্যাচ তুলনা নির্বাচিত অ্যালগরিদমের উপর ভিত্তি করে এর চেকসাম গণনা করতে ফাইল/ফাইলগুলি বেছে নিন ফাইল বাছাই করুন ডিরেক্টরি বাছাই করুন মাথার দৈর্ঘ্য স্কেল স্ট্যাম্প টাইমস্ট্যাম্প বিন্যাস প্যাটার্ন প্যাডিং ইমেজ কাটিং চিত্রের অংশটি কেটে নিন এবং উল্লম্ব বা অনুভূমিক রেখা দ্বারা বাম অংশগুলিকে একত্রিত করুন (বিপরীত হতে পারে) উল্লম্ব পিভট লাইন অনুভূমিক পিভট লাইন বিপরীত নির্বাচন কাটা এলাকার চারপাশে অংশ একত্রিত করার পরিবর্তে উল্লম্ব কাটা অংশ ছেড়ে দেওয়া হবে কাটা এলাকার চারপাশে অংশ একত্রিত করার পরিবর্তে অনুভূমিক কাটা অংশ ছেড়ে দেওয়া হবে মেশ গ্রেডিয়েন্টের সংগ্রহ কাস্টম পরিমাণ নট এবং রেজোলিউশন সহ জাল গ্রেডিয়েন্ট তৈরি করুন মেশ গ্রেডিয়েন্ট ওভারলে প্রদত্ত চিত্রগুলির শীর্ষে জাল গ্রেডিয়েন্ট রচনা করুন পয়েন্ট কাস্টমাইজেশন গ্রিডের আকার রেজোলিউশন এক্স রেজোলিউশন Y রেজোলিউশন পিক্সেল বাই পিক্সেল হাইলাইট রঙ পিক্সেল তুলনা প্রকার বারকোড স্ক্যান করুন উচ্চতা অনুপাত বারকোড টাইপ B/W প্রয়োগ করুন বারকোড ইমেজ সম্পূর্ণ কালো এবং সাদা হবে এবং অ্যাপের থিম দ্বারা রঙিন হবে না যেকোনো বারকোড (QR, EAN, AZTEC, …) স্ক্যান করুন এবং এর বিষয়বস্তু পান বা নতুন তৈরি করতে আপনার পাঠ্য পেস্ট করুন কোন বারকোড পাওয়া যায়নি জেনারেটেড বারকোড এখানে থাকবে অডিও কভার অডিও ফাইল থেকে অ্যালবাম কভার ইমেজ নিষ্কাশন, সবচেয়ে সাধারণ বিন্যাস সমর্থিত হয় শুরু করতে অডিও বেছে নিন অডিও বাছাই করুন কোন কভার পাওয়া যায়নি লগ পাঠান অ্যাপ লগ ফাইল শেয়ার করতে ক্লিক করুন, এটি আমাকে সমস্যা খুঁজে পেতে এবং সমস্যার সমাধান করতে সাহায্য করতে পারে ওহো… কিছু ভুল হয়েছে আপনি নীচের বিকল্পগুলি ব্যবহার করে আমার সাথে যোগাযোগ করতে পারেন এবং আমি সমাধান খোঁজার চেষ্টা করব৷\n(লগগুলি সংযুক্ত করতে ভুলবেন না) ফাইলে লিখুন চিত্রের ব্যাচ থেকে পাঠ্য বের করুন এবং এটি একটি পাঠ্য ফাইলে সংরক্ষণ করুন মেটাডেটা লিখুন প্রতিটি ছবি থেকে টেক্সট বের করুন এবং আপেক্ষিক ফটোর EXIF ​​তথ্যে রাখুন অদৃশ্য মোড আপনার ছবির বাইটের ভিতরে চোখের অদৃশ্য ওয়াটারমার্ক তৈরি করতে স্টেগানোগ্রাফি ব্যবহার করুন LSB ব্যবহার করুন LSB (কম উল্লেখযোগ্য বিট) স্টেগানোগ্রাফি পদ্ধতি ব্যবহার করা হবে, অন্যথায় FD (ফ্রিকোয়েন্সি ডোমেন) স্বয়ং লাল চোখ সরান পাসওয়ার্ড আনলক পিডিএফ সুরক্ষিত অপারেশন প্রায় সম্পূর্ণ। এখন বাতিল করার জন্য এটি পুনরায় চালু করতে হবে তারিখ পরিবর্তিত তারিখ পরিবর্তিত (বিপরীত) আকার আকার (বিপরীত) MIME প্রকার MIME প্রকার (বিপরীত) এক্সটেনশন এক্সটেনশন (বিপরীত) তারিখ যোগ করা হয়েছে যোগ করার তারিখ (বিপরীত) বাম থেকে ডানে ডান থেকে বাম টপ টু বটম নিচ থেকে উপরে তরল গ্লাস সম্প্রতি ঘোষিত IOS 26 এবং এটির লিকুইড গ্লাস ডিজাইন সিস্টেমের উপর ভিত্তি করে একটি সুইচ চিত্র বাছাই করুন বা নীচে বেস64 ডেটা পেস্ট/আমদানি করুন শুরু করতে ছবির লিঙ্ক টাইপ করুন লিঙ্ক পেস্ট করুন ক্যালিডোস্কোপ মাধ্যমিক কোণ পক্ষসমূহ চ্যানেল মিক্স নীল সবুজ লাল নীল সবুজ লাল লাল হয়ে গেছে সবুজে নীলে সায়ান ম্যাজেন্টা হলুদ কালার হাফটোন কনট্যুর স্তর অফসেট Voronoi ক্রিস্টালাইজ আকৃতি প্রসারিত এলোমেলোতা Despeckle ছড়িয়ে পড়া DoG দ্বিতীয় ব্যাসার্ধ সমান করা দীপ্তি ঘূর্ণি এবং চিমটি পয়েন্টিলাইজ করুন বর্ডার রঙ পোলার স্থানাঙ্ক মেরু থেকে রেক্ট সংশোধন করতে পোলার বৃত্তে উল্টানো শব্দ কম করুন সরল সোলারাইজ বিণ এক্স গ্যাপ ওয়াই গ্যাপ এক্স প্রস্থ Y প্রস্থ ঘূর্ণায়মান রাবার স্ট্যাম্প স্মিয়ার ঘনত্ব মিক্স গোলক লেন্স বিকৃতি প্রতিসরণ সূচক অর্ক স্প্রেড কোণ ঝকঝকে রশ্মি ASCII গ্রেডিয়েন্ট মেরি শরৎ হাড় জেট শীতকাল মহাসাগর গ্রীষ্ম বসন্ত শীতল বৈকল্পিক এইচএসভি গোলাপী গরম শব্দ ম্যাগমা ইনফার্নো প্লাজমা ভিরিডিস নাগরিক গোধূলি গোধূলি স্থানান্তরিত দৃষ্টিকোণ অটো Deskew ক্রপ করার অনুমতি দিন ক্রপ বা দৃষ্টিকোণ পরম টার্বো গভীর সবুজ লেন্স সংশোধন টার্গেট লেন্স প্রোফাইল ফাইল JSON ফর্ম্যাটে প্রস্তুত লেন্স প্রোফাইল ডাউনলোড করুন অংশ শতাংশ JSON হিসাবে রপ্তানি করুন json উপস্থাপনা হিসাবে একটি প্যালেট ডেটা সহ স্ট্রিং অনুলিপি করুন সীম খোদাই হোম স্ক্রীন লক স্ক্রীন অন্তর্নির্মিত ওয়ালপেপার রপ্তানি রিফ্রেশ বর্তমান হোম, লক এবং অন্তর্নির্মিত ওয়ালপেপার পান সমস্ত ফাইল অ্যাক্সেস করার অনুমতি দিন, ওয়ালপেপার পুনরুদ্ধার করার জন্য এটি প্রয়োজন বাহ্যিক সঞ্চয়স্থানের অনুমতি পরিচালনা করা যথেষ্ট নয়, আপনাকে আপনার চিত্রগুলিতে অ্যাক্সেসের অনুমতি দিতে হবে, \"সমস্তকে অনুমতি দিন\" নির্বাচন করতে ভুলবেন না ফাইলনামে প্রিসেট যোগ করুন ইমেজ ফাইলনামে নির্বাচিত প্রিসেটের সাথে প্রত্যয় যুক্ত করে ফাইলনামে ইমেজ স্কেল মোড যোগ করুন ইমেজ ফাইলনামে নির্বাচিত ইমেজ স্কেল মোডের সাথে প্রত্যয় যুক্ত করে Ascii আর্ট ছবিকে ascii টেক্সটে রূপান্তর করুন যা চিত্রের মতো দেখাবে পরমস কিছু ক্ষেত্রে ভালো ফলাফলের জন্য ছবিতে নেতিবাচক ফিল্টার প্রয়োগ করে স্ক্রিনশট প্রক্রিয়া করা হচ্ছে স্ক্রিনশট ক্যাপচার করা হয়নি, আবার চেষ্টা করুন সেভ করা এড়িয়ে গেছে %1$s ফাইল এড়িয়ে গেছে বড় হলে এড়িয়ে যাওয়ার অনুমতি দিন ফলস্বরূপ ফাইলের আকার আসলটির চেয়ে বড় হলে কিছু সরঞ্জামকে ছবি সংরক্ষণ করা এড়ানোর অনুমতি দেওয়া হবে ক্যালেন্ডার ইভেন্ট যোগাযোগ ইমেইল অবস্থান ফোন পাঠ্য এসএমএস URL ওয়াই-ফাই নেটওয়ার্ক খুলুন N/A SSID ফোন বার্তা ঠিকানা বিষয় শরীর নাম সংগঠন শিরোনাম ফোন ইমেইল ইউআরএল ঠিকানা সারাংশ বর্ণনা অবস্থান সংগঠক শুরুর তারিখ শেষ তারিখ স্ট্যাটাস অক্ষাংশ দ্রাঘিমাংশ বারকোড তৈরি করুন বারকোড সম্পাদনা করুন Wi-Fi কনফিগারেশন নিরাপত্তা পরিচিতি বাছুন নির্বাচিত পরিচিতি ব্যবহার করে অটোফিল করার জন্য সেটিংসে পরিচিতিদের অনুমতি দিন যোগাযোগের তথ্য প্রথম নাম মধ্য নাম পদবি উচ্চারণ ফোন যোগ করুন ইমেল যোগ করুন ঠিকানা যোগ করুন ওয়েবসাইট ওয়েবসাইট যোগ করুন ফরম্যাট করা নাম এই ছবিটি বারকোডের উপরে রাখতে ব্যবহার করা হবে কোড কাস্টমাইজেশন এই ছবিটি QR কোডের কেন্দ্রে লোগো হিসাবে ব্যবহার করা হবে লোগো লোগো প্যাডিং লোগো আকার লোগো কোণ চতুর্থ চোখ নিচের প্রান্তের কোণায় চতুর্থ চোখ যোগ করে qr কোডে চোখের প্রতিসাম্য যোগ করে পিক্সেল আকৃতি ফ্রেমের আকৃতি বলের আকৃতি ত্রুটি সংশোধন স্তর গাঢ় রং হালকা রঙ হাইপার ওএস Xiaomi HyperOS এর মতো স্টাইল মাস্ক প্যাটার্ন এই কোডটি স্ক্যানযোগ্য নাও হতে পারে, এটিকে সমস্ত ডিভাইসের সাথে পঠনযোগ্য করতে চেহারা প্যারামগুলি পরিবর্তন করুন৷ স্ক্যানযোগ্য নয় সরঞ্জামগুলি আরও কমপ্যাক্ট হতে হোম স্ক্রীন অ্যাপ লঞ্চারের মতো দেখাবে লঞ্চার মোড নির্বাচিত ব্রাশ এবং শৈলী দিয়ে একটি এলাকা পূরণ করে বন্যা ভরাট স্প্রে গ্রাফিটি স্টাইলযুক্ত পথ আঁকে বর্গাকার কণা স্প্রে কণা বৃত্তের পরিবর্তে বর্গাকার আকৃতির হবে প্যালেট সরঞ্জাম ইমেজ থেকে আপনার প্যালেটের মৌলিক/উপাদান তৈরি করুন বা বিভিন্ন প্যালেট ফরম্যাট জুড়ে আমদানি/রপ্তানি করুন প্যালেট সম্পাদনা করুন বিভিন্ন ফরম্যাটে রপ্তানি/আমদানি প্যালেট রঙের নাম প্যালেট নাম প্যালেট বিন্যাস বিভিন্ন ফরম্যাটে জেনারেট করা প্যালেট রপ্তানি করুন বর্তমান প্যালেটে নতুন রঙ যোগ করে %1$s বিন্যাস প্যালেট নাম প্রদান সমর্থন করে না প্লে স্টোর নীতির কারণে, এই বৈশিষ্ট্যটি বর্তমান বিল্ডে অন্তর্ভুক্ত করা যাবে না। এই কার্যকারিতা অ্যাক্সেস করতে, একটি বিকল্প উৎস থেকে ImageToolbox ডাউনলোড করুন। আপনি নীচে গিটহাবে উপলব্ধ বিল্ডগুলি খুঁজে পেতে পারেন। Github পৃষ্ঠা খুলুন আসল ফাইলটি নির্বাচিত ফোল্ডারে সংরক্ষণ করার পরিবর্তে নতুন একটি দিয়ে প্রতিস্থাপিত হবে লুকানো ওয়াটারমার্ক টেক্সট সনাক্ত করা হয়েছে লুকানো জলছাপ চিত্র সনাক্ত করা হয়েছে এই ছবি লুকানো ছিল জেনারেটিভ ইনপেইন্টিং OpenCV-এর উপর নির্ভর না করেই আপনাকে AI মডেল ব্যবহার করে একটি ইমেজে অবজেক্ট অপসারণ করতে দেয়। এই বৈশিষ্ট্যটি ব্যবহার করতে, অ্যাপটি গিটহাব থেকে প্রয়োজনীয় মডেল (~200 MB) ডাউনলোড করবে OpenCV-এর উপর নির্ভর না করেই আপনাকে AI মডেল ব্যবহার করে একটি ইমেজে অবজেক্ট অপসারণ করতে দেয়। এটি একটি দীর্ঘ চলমান অপারেশন হতে পারে ত্রুটি স্তর বিশ্লেষণ লুমিনেন্স গ্রেডিয়েন্ট গড় দূরত্ব কপি মুভ ডিটেকশন ধরে রাখা সহগ ক্লিপবোর্ড ডেটা খুব বড়৷ কপি করার জন্য ডেটা খুব বড় সহজ বুনা Pixelization স্তব্ধ Pixelization ক্রস পিক্সেলাইজেশন মাইক্রো ম্যাক্রো পিক্সেলাইজেশন অরবিটাল পিক্সেলাইজেশন ঘূর্ণি পিক্সেলাইজেশন পালস গ্রিড পিক্সেলাইজেশন নিউক্লিয়াস পিক্সেলাইজেশন রেডিয়াল ওয়েভ পিক্সেলাইজেশন uri \"%1$s\" খুলতে পারে না তুষারপাত মোড সক্রিয় বর্ডার ফ্রেম গ্লিচ বৈকল্পিক চ্যানেল শিফট সর্বোচ্চ অফসেট ভিএইচএস ব্লক গ্লিচ ব্লক সাইজ CRT বক্রতা বক্রতা ক্রোমা পিক্সেল মেল্ট সর্বোচ্চ ড্রপ এআই টুলস এআই মডেলের মাধ্যমে ছবি প্রসেস করার জন্য বিভিন্ন টুল যেমন আর্টিফ্যাক্ট রিমুভিং বা ডিনোইসিং কম্প্রেশন, জ্যাগড লাইন কার্টুন, সম্প্রচার কম্প্রেশন সাধারণ সংকোচন, সাধারণ শব্দ বর্ণহীন কার্টুনের আওয়াজ দ্রুত, সাধারণ কম্প্রেশন, সাধারণ গোলমাল, অ্যানিমেশন/কমিক্স/অ্যানিম বই স্ক্যানিং এক্সপোজার সংশোধন সাধারণ কম্প্রেশনে সেরা, রঙিন ছবি সাধারণ কম্প্রেশনে সেরা, গ্রেস্কেল ছবি সাধারণ কম্প্রেশন, গ্রেস্কেল ইমেজ, শক্তিশালী সাধারণ গোলমাল, রঙিন ছবি সাধারণ গোলমাল, রঙিন ছবি, আরও ভালো বিবরণ সাধারণ গোলমাল, গ্রেস্কেল ছবি সাধারণ গোলমাল, গ্রেস্কেল ছবি, শক্তিশালী সাধারণ গোলমাল, গ্রেস্কেল ছবি, শক্তিশালী সাধারণ কম্প্রেশন সাধারণ কম্প্রেশন টেক্সচারাইজেশন, h264 কম্প্রেশন ভিএইচএস কম্প্রেশন অ-মানক কম্প্রেশন (cinepak, msvideo1, roq) বিঙ্ক কম্প্রেশন, জ্যামিতিতে ভাল বিঙ্ক কম্প্রেশন, শক্তিশালী বিঙ্ক কম্প্রেশন, নরম, বিস্তারিত ধরে রাখে সিঁড়ি-পদক্ষেপের প্রভাব দূর করা, মসৃণ করা স্ক্যান করা আর্ট/ড্রয়িং, হালকা কম্প্রেশন, মোয়ার কালার ব্যান্ডিং ধীর, হাফটোন অপসারণ গ্রেস্কেল/বিডব্লিউ ইমেজের জন্য সাধারণ কালারাইজার, ভালো ফলাফলের জন্য DDColor ব্যবহার করুন প্রান্ত অপসারণ ওভারশার্পেনিং দূর করে ধীর, বিভ্রান্তি অ্যান্টি-আলিয়াসিং, সাধারণ শিল্পকর্ম, সিজিআই KDM003 স্ক্যান প্রক্রিয়াকরণ লাইটওয়েট ইমেজ বর্ধিতকরণ মডেল কম্প্রেশন আর্টিফ্যাক্ট অপসারণ কম্প্রেশন আর্টিফ্যাক্ট অপসারণ মসৃণ ফলাফল সঙ্গে ব্যান্ডেজ অপসারণ হাফটোন প্যাটার্ন প্রক্রিয়াকরণ ডিথার প্যাটার্ন অপসারণ V3 JPEG আর্টিফ্যাক্ট অপসারণ V2 H.264 টেক্সচার বর্ধন VHS শার্পনিং এবং বর্ধিতকরণ মার্জিং খণ্ডের আকার ওভারল্যাপ সাইজ %1$s px-এর বেশি চিত্রগুলিকে টুকরো টুকরো করে কেটে প্রক্রিয়াজাত করা হবে, দৃশ্যমান সীমগুলি প্রতিরোধ করতে ওভারল্যাপ এগুলিকে মিশ্রিত করে৷ লো-এন্ড ডিভাইসের সাথে বড় আকার অস্থিরতা সৃষ্টি করতে পারে শুরু করতে একটি নির্বাচন করুন আপনি কি %1$s মডেল মুছতে চান? আপনাকে আবার এটি ডাউনলোড করতে হবে নিশ্চিত করুন মডেল ডাউনলোড করা মডেল উপলব্ধ মডেল প্রস্তুতি নিচ্ছে সক্রিয় মডেল সেশন খুলতে ব্যর্থ হয়েছে৷ শুধুমাত্র .onnx/.ort মডেল ইম্পোর্ট করা যাবে আমদানি মডেল আরও ব্যবহার করার জন্য কাস্টম onnx মডেল আমদানি করুন, শুধুমাত্র onnx/ort মডেলগুলি গ্রহণ করা হয়, প্রায় সমস্ত esrgan যেমন বৈকল্পিক সমর্থন করে আমদানিকৃত মডেল সাধারণ গোলমাল, রঙিন ছবি সাধারণ গোলমাল, রঙিন ছবি, শক্তিশালী সাধারণ গোলমাল, রঙিন ছবি, শক্তিশালী মসৃণ গ্রেডিয়েন্ট এবং সমতল রঙের ক্ষেত্রগুলিকে উন্নত করে, বিক্ষিপ্ত শিল্পকর্ম এবং রঙের ব্যান্ডিং হ্রাস করে। প্রাকৃতিক রং সংরক্ষণ করার সময় সুষম হাইলাইটের সাথে ছবির উজ্জ্বলতা এবং বৈসাদৃশ্য বাড়ায়। বিস্তারিত রাখা এবং অতিরিক্ত এক্সপোজার এড়ানোর সময় অন্ধকার ছবি উজ্জ্বল করে। অত্যধিক রঙের টোনিং দূর করে এবং আরও নিরপেক্ষ এবং প্রাকৃতিক রঙের ভারসাম্য পুনরুদ্ধার করে। সূক্ষ্ম বিবরণ এবং টেক্সচার সংরক্ষণের উপর জোর দিয়ে পয়সন-ভিত্তিক নয়েজ টোনিং প্রয়োগ করে। মসৃণ এবং কম আক্রমনাত্মক ভিজ্যুয়াল ফলাফলের জন্য নরম পয়সন নয়েজ টোনিং প্রয়োগ করে। ইউনিফর্ম নয়েজ টোনিং বিস্তারিত সংরক্ষণ এবং চিত্রের স্বচ্ছতার উপর দৃষ্টি নিবদ্ধ করে। সূক্ষ্ম টেক্সচার এবং মসৃণ চেহারা জন্য মৃদু অভিন্ন শব্দ টোনিং। আর্টিফ্যাক্ট পুনরায় রং করে এবং চিত্রের সামঞ্জস্য উন্নত করে ক্ষতিগ্রস্ত বা অসম এলাকা মেরামত করে। লাইটওয়েট ডিব্যান্ডিং মডেল যা ন্যূনতম পারফরম্যান্স খরচের সাথে রঙের ব্যান্ডিং সরিয়ে দেয়। উন্নত স্বচ্ছতার জন্য খুব উচ্চ কম্প্রেশন আর্টিফ্যাক্ট (0-20% গুণমান) সহ ছবিগুলিকে অপ্টিমাইজ করে৷ উচ্চ কম্প্রেশন আর্টিফ্যাক্ট (20-40% গুণমান) সহ চিত্রগুলিকে উন্নত করে, বিবরণ পুনরুদ্ধার করে এবং শব্দ কমায়। মাঝারি কম্প্রেশন (40-60% গুণমান), তীক্ষ্ণতা এবং মসৃণতা ভারসাম্য সহ চিত্রগুলিকে উন্নত করে। সূক্ষ্ম বিবরণ এবং টেক্সচার উন্নত করতে হালকা কম্প্রেশন (60-80% গুণমান) সহ চিত্রগুলিকে পরিমার্জিত করে। প্রাকৃতিক চেহারা এবং বিশদ বিবরণ সংরক্ষণ করার সময় কাছাকাছি-ক্ষতিহীন চিত্রগুলিকে (80-100% গুণমান) সামান্য উন্নত করে। সহজ এবং দ্রুত কালারাইজেশন, কার্টুন, আদর্শ নয় চিত্রের অস্পষ্টতাকে সামান্য কমিয়ে দেয়, শিল্পকর্মের পরিচয় না দিয়ে তীক্ষ্ণতা উন্নত করে। দীর্ঘ চলমান অপারেশন চিত্র প্রক্রিয়াকরণ প্রক্রিয়াকরণ খুব কম মানের ছবিতে (0-20%) ভারী JPEG কম্প্রেশন আর্টিফ্যাক্টগুলি সরিয়ে দেয়। অত্যন্ত সংকুচিত চিত্রগুলিতে শক্তিশালী JPEG আর্টিফ্যাক্টগুলি হ্রাস করে (20-40%)। ছবির বিশদ (40-60%) সংরক্ষণ করার সময় মাঝারি JPEG আর্টিফ্যাক্টগুলি পরিষ্কার করে। মোটামুটি উচ্চ মানের ছবিতে হালকা JPEG আর্টিফ্যাক্টগুলিকে পরিমার্জন করে (60-80%)। সূক্ষ্মভাবে প্রায় ক্ষতিহীন চিত্রগুলিতে (80-100%) ছোট JPEG শিল্পকর্মগুলিকে হ্রাস করে৷ সূক্ষ্ম বিবরণ এবং টেক্সচার উন্নত করে, ভারী শিল্পকর্ম ছাড়াই অনুভূত তীক্ষ্ণতা উন্নত করে। প্রক্রিয়াকরণ সমাপ্ত প্রক্রিয়াকরণ ব্যর্থ হয়েছে৷ গতির জন্য অপ্টিমাইজ করা একটি প্রাকৃতিক চেহারা রাখার সময় ত্বকের টেক্সচার এবং বিবরণ উন্নত করে। JPEG কম্প্রেশন আর্টিফ্যাক্টগুলি সরিয়ে দেয় এবং সংকুচিত ফটোগুলির জন্য ছবির গুণমান পুনরুদ্ধার করে। কম-আলোতে তোলা ফটোতে ISO নয়েজ কমায়, বিশদ সংরক্ষণ করে। ওভার এক্সপোজড বা \"জাম্বো\" হাইলাইটগুলিকে সংশোধন করে এবং আরও ভাল টোনাল ভারসাম্য পুনরুদ্ধার করে। লাইটওয়েট এবং দ্রুত কালারাইজেশন মডেল যা গ্রেস্কেল ছবিতে প্রাকৃতিক রং যোগ করে। DEJPEG Denoise রঙ করা শিল্পকর্ম উন্নত করুন এনিমে স্ক্যান আপস্কেল সাধারণ ছবির জন্য X4 আপস্কেলার; ছোট মডেল যা কম GPU এবং সময় ব্যবহার করে, মাঝারি deblur এবং denoise সহ। সাধারণ ছবি, টেক্সচার এবং প্রাকৃতিক বিবরণ সংরক্ষণের জন্য X2 আপস্কেলার। উন্নত টেক্সচার এবং বাস্তবসম্মত ফলাফল সহ সাধারণ চিত্রগুলির জন্য X4 আপস্কেলার। অ্যানিমে ছবিগুলির জন্য অপ্টিমাইজ করা X4 আপস্কেলার; ধারালো লাইন এবং বিশদ বিবরণের জন্য 6টি RRDB ব্লক। MSE ক্ষতি সহ X4 আপস্কেলার, সাধারণ চিত্রগুলির জন্য মসৃণ ফলাফল এবং কম শিল্পকর্ম তৈরি করে। অ্যানিমে ছবিগুলির জন্য অপ্টিমাইজ করা X4 আপস্ক্যালার; তীক্ষ্ণ বিবরণ এবং মসৃণ লাইন সহ 4B32F ভেরিয়েন্ট। সাধারণ ছবির জন্য X4 UltraSharp V2 মডেল; তীক্ষ্ণতা এবং স্বচ্ছতার উপর জোর দেয়। X4 UltraSharp V2 Lite; দ্রুত এবং ছোট, কম GPU মেমরি ব্যবহার করার সময় বিস্তারিত সংরক্ষণ করে। দ্রুত পটভূমি অপসারণের জন্য লাইটওয়েট মডেল। সুষম কর্মক্ষমতা এবং নির্ভুলতা। প্রতিকৃতি, বস্তু এবং দৃশ্য নিয়ে কাজ করে। বেশিরভাগ ব্যবহারের ক্ষেত্রে প্রস্তাবিত। বিজি সরান অনুভূমিক সীমানা পুরুত্ব উল্লম্ব সীমানা বেধ %1$s রঙ %1$s রং বর্তমান মডেল চঙ্কিং সমর্থন করে না, ছবিটি মূল মাত্রায় প্রসেস করা হবে, এর ফলে উচ্চ মেমরি খরচ হতে পারে এবং লো-এন্ড ডিভাইসে সমস্যা হতে পারে চঙ্কিং অক্ষম, ছবি মূল মাত্রায় প্রক্রিয়া করা হবে, এর ফলে উচ্চ মেমরি খরচ হতে পারে এবং লো-এন্ড ডিভাইসে সমস্যা হতে পারে তবে অনুমানে আরও ভাল ফলাফল দিতে পারে চঙ্কিং পটভূমি অপসারণের জন্য উচ্চ-নির্ভুলতা চিত্র বিভাজন মডেল ছোট মেমরি ব্যবহারের সাথে দ্রুত পটভূমি অপসারণের জন্য U2Net-এর হালকা সংস্করণ। সম্পূর্ণ DDCcolor মডেল ন্যূনতম শিল্পকর্ম সহ সাধারণ চিত্রগুলির জন্য উচ্চ-মানের রঙিনকরণ সরবরাহ করে। সমস্ত রঙিন মডেলের সেরা পছন্দ। DDColor প্রশিক্ষিত এবং ব্যক্তিগত শৈল্পিক ডেটাসেট; কম অবাস্তব রঙের শিল্পকর্মের সাথে বৈচিত্র্যময় এবং শৈল্পিক রঙিন ফলাফল তৈরি করে। সঠিক পটভূমি অপসারণের জন্য সুইন ট্রান্সফরমারের উপর ভিত্তি করে হালকা ওজনের BiRefNet মডেল। ধারালো প্রান্ত এবং চমৎকার বিশদ সংরক্ষণের সাথে উচ্চ-মানের পটভূমি অপসারণ, বিশেষ করে জটিল বস্তু এবং জটিল পটভূমিতে। পটভূমি অপসারণ মডেল যা মসৃণ প্রান্ত সহ সঠিক মুখোশ তৈরি করে, সাধারণ বস্তুর জন্য উপযুক্ত এবং মাঝারি বিস্তারিত সংরক্ষণ। মডেল ইতিমধ্যে ডাউনলোড করা হয়েছে মডেল সফলভাবে আমদানি করা হয়েছে৷ টাইপ কীওয়ার্ড খুব দ্রুত স্বাভাবিক ধীর খুব ধীর শতাংশ গণনা করুন সর্বনিম্ন মান হল %1$s আঙ্গুল দিয়ে আঁকা ছবি বিকৃত ওয়ার্প কঠোরতা ওয়ার্প মোড সরান বৃদ্ধি সঙ্কুচিত ঘূর্ণি CW ঘূর্ণায়মান CCW বিবর্ণ শক্তি শীর্ষ ড্রপ বটম ড্রপ স্টার্ট ড্রপ শেষ ড্রপ ডাউনলোড হচ্ছে মসৃণ আকার মসৃণ, আরও প্রাকৃতিক আকৃতির জন্য আদর্শ গোলাকার আয়তক্ষেত্রের পরিবর্তে সুপারেলিপ্স ব্যবহার করুন আকৃতির ধরন কাটা গোলাকার মসৃণ বৃত্তাকার ছাড়াই ধারালো প্রান্ত ক্লাসিক গোলাকার কোণ আকারের ধরন কোণার আকার কাঠবিড়ালি মার্জিত গোলাকার UI উপাদান ফাইলের নাম বিন্যাস কাস্টম টেক্সট ফাইলের নামের একেবারে শুরুতে, প্রকল্পের নাম, ব্র্যান্ড বা ব্যক্তিগত ট্যাগের জন্য উপযুক্ত। এক্সটেনশন ছাড়াই মূল ফাইলের নাম ব্যবহার করে, আপনাকে উৎস সনাক্তকরণ অক্ষত রাখতে সাহায্য করে। ছবির প্রস্থ পিক্সেলে, রেজোলিউশন পরিবর্তন বা স্কেলিং ফলাফল ট্র্যাক করার জন্য দরকারী। চিত্রের উচ্চতা পিক্সেলে, আকৃতির অনুপাত বা রপ্তানির সাথে কাজ করার সময় সহায়ক। অনন্য ফাইলের নাম নিশ্চিত করতে র্যান্ডম সংখ্যা তৈরি করে; ডুপ্লিকেটের বিরুদ্ধে অতিরিক্ত নিরাপত্তার জন্য আরও সংখ্যা যোগ করুন। ব্যাচ রপ্তানির জন্য স্বয়ংক্রিয়-বর্ধিত কাউন্টার, এক সেশনে একাধিক ছবি সংরক্ষণ করার সময় আদর্শ। ফাইলের নামের মধ্যে প্রয়োগকৃত প্রিসেট নাম সন্নিবেশ করান যাতে আপনি সহজেই মনে রাখতে পারেন কিভাবে চিত্রটি প্রক্রিয়া করা হয়েছিল। প্রসেসিং এর সময় ব্যবহৃত ইমেজ স্কেলিং মোড প্রদর্শন করে, রিসাইজ করা, ক্রপ করা বা ফিট করা ছবিগুলিকে আলাদা করতে সাহায্য করে। ফাইলের নামের শেষে কাস্টম টেক্সট রাখা, _v2, _edited বা _final-এর মত সংস্করণের জন্য উপযোগী। ফাইল এক্সটেনশন (png, jpg, webp, ইত্যাদি), স্বয়ংক্রিয়ভাবে প্রকৃত সংরক্ষিত বিন্যাসের সাথে মিলে যায়। একটি কাস্টমাইজযোগ্য টাইমস্ট্যাম্প যা আপনাকে নিখুঁত সাজানোর জন্য জাভা স্পেসিফিকেশন দ্বারা আপনার নিজস্ব বিন্যাস সংজ্ঞায়িত করতে দেয়। ফ্লিং টাইপ অ্যান্ড্রয়েড নেটিভ iOS স্টাইল মসৃণ বক্ররেখা কুইক স্টপ বাউন্সি ভাসমান চটপটি আল্ট্রা মসৃণ অভিযোজিত অ্যাক্সেসিবিলিটি সচেতন গতি কমানো বেসলাইন তুলনার জন্য নেটিভ অ্যান্ড্রয়েড স্ক্রোল ফিজিক্স সাধারণ ব্যবহারের জন্য সুষম, মসৃণ স্ক্রলিং উচ্চ ঘর্ষণ iOS-এর মতো স্ক্রোল আচরণ স্বতন্ত্র স্ক্রোল অনুভূতির জন্য অনন্য স্প্লাইন বক্ররেখা দ্রুত স্টপিংয়ের সাথে সুনির্দিষ্ট স্ক্রলিং কৌতুকপূর্ণ, প্রতিক্রিয়াশীল বাউন্সি স্ক্রোল কন্টেন্ট ব্রাউজিং এর জন্য লম্বা, গ্লাইডিং স্ক্রল ইন্টারেক্টিভ UI এর জন্য দ্রুত, প্রতিক্রিয়াশীল স্ক্রলিং বর্ধিত ভরবেগ সহ প্রিমিয়াম মসৃণ স্ক্রলিং ফ্লিং বেগের উপর ভিত্তি করে পদার্থবিদ্যা সামঞ্জস্য করে সিস্টেম অ্যাক্সেসিবিলিটি সেটিংসকে সম্মান করে অ্যাক্সেসযোগ্যতার প্রয়োজনের জন্য ন্যূনতম গতি প্রাথমিক লাইন প্রতি পঞ্চম লাইনে মোটা লাইন যোগ করে রঙ পূরণ করুন লুকানো টুলস শেয়ারের জন্য লুকানো টুল রঙিন গ্রন্থাগার রঙের একটি বিশাল সংগ্রহ ব্রাউজ করুন প্রাকৃতিক বিশদগুলি বজায় রেখে ছবিগুলিকে তীক্ষ্ণ করে এবং অস্পষ্টতা সরিয়ে দেয়, ফোকাসের বাইরের ফটোগুলি ঠিক করার জন্য আদর্শ৷ বুদ্ধিমত্তার সাথে ইমেজগুলি পুনরুদ্ধার করে যা পূর্বে পুনরায় আকার দেওয়া হয়েছে, হারানো বিবরণ এবং টেক্সচার পুনরুদ্ধার করে। লাইভ-অ্যাকশন কন্টেন্টের জন্য অপ্টিমাইজ করা, কম্প্রেশন আর্টিফ্যাক্ট কমায় এবং মুভি/টিভি শো ফ্রেমে সূক্ষ্ম বিবরণ বাড়ায়। ভিএইচএস-গুণমানের ফুটেজকে HD তে রূপান্তর করে, টেপের শব্দ অপসারণ করে এবং ভিনটেজ অনুভূতি সংরক্ষণ করার সময় রেজোলিউশন বাড়ায়। টেক্সট-ভারী ছবি এবং স্ক্রিনশটগুলির জন্য বিশেষ, অক্ষরগুলিকে তীক্ষ্ণ করে এবং পঠনযোগ্যতা উন্নত করে। বিভিন্ন ডেটাসেটে প্রশিক্ষিত উন্নত আপস্কেলিং, সাধারণ-উদ্দেশ্যের ফটো বর্ধনের জন্য চমৎকার। ওয়েব-সংকুচিত ফটোগুলির জন্য অপ্টিমাইজ করা, JPEG আর্টিফ্যাক্টগুলি সরিয়ে দেয় এবং প্রাকৃতিক চেহারা পুনরুদ্ধার করে। উন্নত টেক্সচার সংরক্ষণ এবং আর্টিফ্যাক্ট হ্রাস সহ ওয়েব ফটোগুলির জন্য উন্নত সংস্করণ। ডুয়াল অ্যাগ্রিগেশন ট্রান্সফরমার প্রযুক্তির সাথে 2x আপস্কেলিং, তীক্ষ্ণতা এবং প্রাকৃতিক বিবরণ বজায় রাখে। উন্নত ট্রান্সফরমার আর্কিটেকচার ব্যবহার করে 3x আপস্কেলিং, মাঝারি বৃদ্ধির প্রয়োজনের জন্য আদর্শ। অত্যাধুনিক ট্রান্সফরমার নেটওয়ার্কের সাথে 4x উচ্চ-মানের আপস্কেলিং, বড় স্কেলে সূক্ষ্ম বিবরণ সংরক্ষণ করে। ফটো থেকে ঝাপসা/গোলমাল এবং ঝাঁকুনি সরিয়ে দেয়। সাধারণ উদ্দেশ্য কিন্তু ফটোতে সেরা। Swin2SR ট্রান্সফরমার ব্যবহার করে নিম্ন-মানের ছবি পুনরুদ্ধার করে, BSRGAN অবক্ষয়ের জন্য অপ্টিমাইজ করা হয়েছে। ভারী কম্প্রেশন আর্টিফ্যাক্ট ঠিক করার জন্য এবং 4x স্কেলে বিশদ উন্নত করার জন্য দুর্দান্ত। BSRGAN অবক্ষয়ের উপর প্রশিক্ষিত SwinIR ট্রান্সফরমার সহ 4x upscaling। ফটো এবং জটিল দৃশ্যগুলিতে তীক্ষ্ণ টেক্সচার এবং আরও প্রাকৃতিক বিবরণের জন্য GAN ব্যবহার করে। পথ পিডিএফ মার্জ করুন একটি নথিতে একাধিক পিডিএফ ফাইল একত্রিত করুন ফাইল অর্ডার পিপি পিডিএফ বিভক্ত করুন পিডিএফ ডকুমেন্ট থেকে নির্দিষ্ট পৃষ্ঠাগুলি বের করুন পিডিএফ ঘোরান স্থায়ীভাবে পৃষ্ঠা অভিযোজন ঠিক করুন পাতা পিডিএফ পুনরায় সাজান পৃষ্ঠাগুলিকে পুনরায় সাজাতে টেনে আনুন এবং ড্রপ করুন৷ পৃষ্ঠাগুলি ধরে রাখুন এবং টেনে আনুন পৃষ্ঠা নম্বর স্বয়ংক্রিয়ভাবে আপনার নথিতে নম্বর যোগ করুন লেবেল বিন্যাস PDF থেকে পাঠ্য (OCR) আপনার পিডিএফ ডকুমেন্ট থেকে প্লেইন টেক্সট বের করুন ব্র্যান্ডিং বা নিরাপত্তার জন্য কাস্টম টেক্সট ওভারলে স্বাক্ষর যেকোনো নথিতে আপনার ইলেকট্রনিক স্বাক্ষর যোগ করুন এটি স্বাক্ষর হিসাবে ব্যবহার করা হবে পিডিএফ আনলক করুন আপনার সুরক্ষিত ফাইল থেকে পাসওয়ার্ড সরান পিডিএফ রক্ষা করুন শক্তিশালী এনক্রিপশন সহ আপনার নথিগুলি সুরক্ষিত করুন সফলতা PDF আনলক করা হয়েছে, আপনি এটি সংরক্ষণ বা ভাগ করতে পারেন পিডিএফ মেরামত করুন দূষিত বা অপঠনযোগ্য নথি ঠিক করার চেষ্টা করুন গ্রেস্কেল সমস্ত নথি এমবেড করা ছবিকে গ্রেস্কেলে রূপান্তর করুন পিডিএফ কম্প্রেস করুন সহজে ভাগ করার জন্য আপনার নথি ফাইলের আকার অপ্টিমাইজ করুন ImageToolbox অভ্যন্তরীণ ক্রস-রেফারেন্স টেবিল পুনর্নির্মাণ করে এবং স্ক্র্যাচ থেকে ফাইল গঠন পুনরুত্পাদন করে। এটি অনেক ফাইলের অ্যাক্সেস পুনরুদ্ধার করতে পারে যা \\"খোলা যাবে না\\" এই টুলটি সমস্ত নথির ছবিকে গ্রেস্কেলে রূপান্তর করে। ফাইলের আকার মুদ্রণ এবং হ্রাস করার জন্য সেরা মেটাডেটা ভাল গোপনীয়তার জন্য নথি বৈশিষ্ট্য সম্পাদনা করুন ট্যাগ প্রযোজক লেখক কীওয়ার্ড সৃষ্টিকর্তা গোপনীয়তা গভীর পরিষ্কার এই নথির জন্য সমস্ত উপলব্ধ মেটাডেটা সাফ করুন পাতা গভীর ওসিআর ডকুমেন্ট থেকে টেক্সট বের করুন এবং Tesseract ইঞ্জিন ব্যবহার করে একটি টেক্সট ফাইলে সংরক্ষণ করুন সব পৃষ্ঠা মুছে ফেলা যাবে না পিডিএফ পৃষ্ঠাগুলি সরান পিডিএফ ডকুমেন্ট থেকে নির্দিষ্ট পৃষ্ঠাগুলি সরান সরাতে আলতো চাপুন ম্যানুয়ালি পিডিএফ ক্রপ করুন দস্তাবেজ পৃষ্ঠাগুলিকে যে কোনও সীমা পর্যন্ত কাটুন পিডিএফ সমতল করুন ডকুমেন্ট পেজ রাস্টার করে পিডিএফকে পরিবর্তনযোগ্য করুন ক্যামেরা চালু করা যায়নি। অনুগ্রহ করে অনুমতি পরীক্ষা করুন এবং নিশ্চিত করুন যে এটি অন্য অ্যাপ ব্যবহার করছে না। ইমেজ এক্সট্র্যাক্ট পিডিএফ-এ এমবেড করা ছবিগুলো তাদের আসল রেজোলিউশনে বের করুন এই পিডিএফ ফাইলটিতে কোনো এমবেড করা ছবি নেই এই টুলটি প্রতিটি পৃষ্ঠা স্ক্যান করে এবং পূর্ণ মানের সোর্স ছবি পুনরুদ্ধার করে — নথি থেকে আসলগুলি সংরক্ষণ করার জন্য উপযুক্ত স্বাক্ষর আঁকুন কলম পরম নথিতে স্থাপন করার জন্য ছবি হিসাবে নিজের স্বাক্ষর ব্যবহার করুন জিপ পিডিএফ প্রদত্ত ব্যবধানে নথি ভাগ করুন এবং জিপ সংরক্ষণাগারে নতুন নথি প্যাক করুন ব্যবধান পিডিএফ প্রিন্ট করুন কাস্টম পৃষ্ঠা আকার সহ মুদ্রণের জন্য নথি প্রস্তুত করুন পত্র প্রতি পাতা ওরিয়েন্টেশন পৃষ্ঠার আকার মার্জিন পুষ্প নরম হাঁটু এনিমে এবং কার্টুন জন্য অপ্টিমাইজ করা. উন্নত প্রাকৃতিক রং এবং কম নিদর্শন সহ দ্রুত আপস্কেলিং Samsung One UI 7 এর মতো স্টাইল পছন্দসই মান গণনা করতে এখানে মৌলিক গণিত প্রতীক লিখুন (যেমন (5+5)*10) গণিত অভিব্যক্তি %1$s পর্যন্ত ছবি তুলে নিন তারিখ সময় রাখুন আলফা ফরম্যাটের জন্য পটভূমির রঙ আলফা সমর্থন সহ প্রতিটি ইমেজ ফরম্যাটের জন্য পটভূমির রঙ সেট করার ক্ষমতা যোগ করে, যখন এটি নিষ্ক্রিয় করা হয় শুধুমাত্র অ-আলফাগুলির জন্য উপলব্ধ সর্বদা তারিখ এবং সময়ের সাথে সম্পর্কিত exif ট্যাগ সংরক্ষণ করুন, exif বিকল্পটি স্বাধীনভাবে কাজ করে প্রকল্প খুলুন একটি পূর্বে সংরক্ষিত চিত্র টুলবক্স প্রকল্প সম্পাদনা চালিয়ে যান ইমেজ টুলবক্স প্রকল্প খুলতে অক্ষম ইমেজ টুলবক্স প্রোজেক্টে প্রোজেক্ট ডেটা নেই ইমেজ টুলবক্স প্রকল্প দূষিত হয়েছে অসমর্থিত চিত্র টুলবক্স প্রকল্প সংস্করণ: %1$d প্রকল্প সংরক্ষণ করুন একটি সম্পাদনাযোগ্য প্রকল্প ফাইলে স্তর, পটভূমি এবং সম্পাদনা ইতিহাস সংরক্ষণ করুন খুলতে ব্যর্থ হয়েছে অনুসন্ধানযোগ্য পিডিএফ-এ লিখুন চিত্র ব্যাচ থেকে পাঠ্য সনাক্ত করুন এবং চিত্র এবং নির্বাচনযোগ্য পাঠ্য স্তর সহ অনুসন্ধানযোগ্য PDF সংরক্ষণ করুন লেয়ার আলফা অনুভূমিক উল্টানো উল্লম্ব উল্টানো তালা ছায়া যোগ করুন ছায়া রঙ পাঠ্য জ্যামিতি তীক্ষ্ণ স্টাইলাইজেশনের জন্য টেক্সট প্রসারিত বা তির্যক করুন স্কেল এক্স স্কু এক্স টীকাগুলি সরান৷ পিডিএফ পৃষ্ঠাগুলি থেকে লিঙ্ক, মন্তব্য, হাইলাইট, আকার বা ফর্ম ক্ষেত্রগুলির মতো নির্বাচিত টীকা প্রকারগুলি সরান হাইপারলিঙ্ক ফাইল সংযুক্তি লাইন পপআপ স্ট্যাম্প আকৃতি টেক্সট নোট টেক্সট মার্কআপ ফর্ম ক্ষেত্র মার্কআপ অজানা টীকা আনগ্রুপ করুন কনফিগারযোগ্য রঙ এবং অফসেট সহ স্তরের পিছনে অস্পষ্ট ছায়া যোগ করুন ================================================ FILE: core/resources/src/main/res/values-ca/strings.xml ================================================ Alguna cosa ha anat malament: %1$s Mida %1$s Trieu la imatge per començar Amplada %1$s Alçada %1$s Qualitat Extensió Tipus de redimensionat Explícit Trieu una imatge Esteu segur que voleu tancar l\'aplicació? S\'està tancant l\'aplicació Flexible Valors reiniciats correctament Queda\'t Restableix la imatge Restableix Reinicia l\'aplicació S\'ha copiat al porta-retalls Excepció Edita l\'EXIF Alguna cosa ha anat malament D\'acord S\'esborraran totes les dades EXIF de la imatge. Aquesta acció no es pot desfer! Valors predefinits Desant No s\'han trobat dades EXIF Afegeix etiqueta Canvia les especificacions d\'una image Obteniu les últimes actualitzacions, discutiu temes i més Tria el color de la imatge, copia o comparteix Imatge Color Color copiat Manté l\'EXIF Imatges: %d Canvia la previsualització Genera una mostra de la paleta de colors a partir de la imatge donada Tipus no admès: %1$s Personalitzat Carpeta de sortida Canvia la mida d\'una imatge a partir de la mida indicada en KB Compara dues imatges donades Sense especificar Emmagatzematge del dispositiu Trieu dues imatges per a començar Tria imatges Configuració Idioma Permet Monet d\'imatge Colors dinàmics Personalització Verd Blau Esquema de color Vermell Seguiment de problemes No hi ha res a enganxar Quant a l\'aplicació No s\'ha trobat cap actualització El tema de l\'aplicació es basarà en el color seleccionat No s\'ha trobat res amb aquesta la consulta Si s\'habilita, els colors de l\'aplicació s\'adaptaran als colors del fons de pantalla No s\'ha pogut desar %d imatges Cerca aquí Correu electrònic Secundari Terciari Gruix de la vora L\'aplicació necessita aquest permís per funcionar, concediu-lo manualment Colors de Monet Valors Afegeix Emmagatzematge extern Permís Alineació FAB Comprova si hi ha actualitzacions Ampliació de la imatge Prefix Nom del fitxer Emoji Seleccioneu quin emoji es mostrarà a la pantalla principal Afegeix la mida del fitxer Si està habilitat, afegeix l\'amplada i l\'alçada de la imatge al nom del fitxer de sortida Suprimeix l\'EXIF Si està habilitat, es mostrarà el diàleg d\'actualització en iniciar l\'aplicació Comparteix Suprimeix les metadades EXIF de qualsevol conjunt d\'imatges Previsualització de la imatge Previsualitza qualsevol tipus d\'imatges: GIF, SVG, etc. Font de la imatge Selector de fotos Galeria Explorador de fitxers El selector de fotos modern d\'Android que apareix a la part inferior de la pantalla, pot funcionar només en Android 12 o superior. Té problemes en rebre les metadades EXIF Selector simple d\'imatges de galeria. Només funcionarà si teniu una aplicació que proporciona la selecció de fotos Utilitza GetContent per triar una imatge. Funciona a tot arreu, però se sap que té problemes per rebre imatges seleccionades en alguns dispositius. Arranjament de les opcions Afegeix el nom del fitxer original Ordre Nombre d\'emojis Reemplaça el nombre de seqüència L\'addició del nom del fitxer original no funciona si s\'ha seleccionat la font d\'imatge del selector de fotos Carregueu qualsevol imatge des d\'Internet per a previsualitzar-la, ampliar-la, editar-la i desar-la. Enllaç de la imatge Si està habilitat, substituirà la marca horària estàndard al número de seqüència de la imatge si utilitzeu el processament per lots Carrega la imatge des de la xarxa Sense imatge Escala del contingut To Alfa Exposició Temperatura Vivacitat Ombreig Relleu Inicia Esbós Posteritza Toon Difuminat ràpid Centre del difuminat y Equilibri de color Fitxer processat Emmagatzema aquest fitxer al dispositiu o utilitza l\'acció de compartició per posar-lo allà on vulgueu Compatibilitat AES-256, mode GCM, sense farciment, 12 bytes IVs aleatoris. Les claus s\'utilitzen com a sumes de verificació SHA-3 (256 bits). Mida del fitxer Omple Ajusta Canvia la mida de les imatges a les imatges amb un costat llarg donat pel paràmetre d\'amplada o alçada, tots els càlculs de la mida es faran després de desar - això manté la relació d\'aspecte Brillantor Afegeix un filtre Aplica qualsevol cadena de filtres a les imatges indicades Filtres Llum Balanç de blancs Monocrom Saturació Filtre Filtre de color Gamma Ressaltats i ombres Sèpia Negatiu Ressaltats Ombres Boira Distància Pendent Aguditza Efecte Solaritza Espaiat Vora Sobel Blanc i negre Amplada de la línia Difuminat Semi to Caixa de difuminat Vinyeta Espai de color CGA Difuminat gaussià Fi Difuminat bilateral Laplacià Suavització Kuwahara Difuminat de pila Radi Escala Mida del difuminat Distorsió Angle Remolí Protuberància Índex de refracció Dilatació Refracció de l\'esfera Opacitat Refracció de l\'esfera de vidre Matriu de color Límits del redimensionat Llindar Toon suau Supressió no màxima Cerca Centre del difuminat x Difuminat d\'ampliació Convolució 3x3 Filtre RGB Primer color Segon color Reordena Llindar de luminància Redimensiona les imatges seleccionades per a seguir els límits d\'amplada i alçada indicats mentre es desa la relació d\'aspecte Nivells de quantificació Inclusió de píxels febles Color fals Heu desactivat l\'aplicació Fitxers, activeu-la per utilitzar aquesta característica Dibuixa Dibuixa sobre la imatge com en un quadern de dibuix, o dibuixa sobre el fons mateix Color de pintura Pintura alfa Trieu una imatge i dibuixeu-hi alguna cosa Dibuixa a la imatge Dibuixa sobre el fons Tria el color de fons i dibuixeu sobre seu Tria un fitxer Xifra Color de fons Xifra Xifra i desxifra qualsevol fitxer (no només la imatge) basat en l\'algorisme criptogràfic AES Característiques Desxifratge Xifratge Implementació Desxifra Trieu el fitxer per iniciar Clau Xifratge de fitxers basat en contrasenya. Els fitxers processats es poden emmagatzemar al directori seleccionat o compartit. Els fitxers desxifrats també es poden obrir directament. e Tanca Desa Cancel·la Escapça Edició única Tria el color Actualitza Per defecte Escapça la imatge a qualsevol límit Suprimeix Mida màxima en KB Genera la paleta No s\'ha pogut generar la paleta per a la imatge donada Redimensiona per pes Fosc Mode nocturn Sistema Enganxa un codi aRGB vàlid. Aquesta aplicació és totalment gratuïta, però si voleu donar suport al desenvolupament del projecte feu clic aquí Edita Determina l\'ordre de les opcions a la pantalla principal Si està habilitat, afegeix el nom del fitxer original al nom de la imatge de sortida Esborra l\'EXIF Esborra Si sortiu ara es perdran tots els canvis no desats Codi font Versió Paleta Original Compara Clar Nova versió %1$s Si està habilitat, quan trieu una imatge per editar-la, els colors de l\'aplicació s\'adaptaran a ella Mode AMOLED Si està activat, en el mode nocturn el color de les superfícies s\'establirà a la foscor absoluta No es pot canviar l\'esquema de color de l\'aplicació mentre els colors dinàmics estan activats Envieu aquí els informes d\'error i les sol·licituds de funcionalitats Ajudeu a traduir Corregiu errors de traducció o localitzeu el projecte a altres idiomes Primari Superfície Atorga L\'aplicació necessita accés al vostre emmagatzematge per tal que es pugui desar imatges. Si us plau, concediu el permís en el quadre de diàleg que s\'obrirà ara. númSeqüència nomFitxerOriginal Força cada imatge pels paràmetres d\'amplada i alçada - pot canviar la relació d\'aspecte Contrast Tint S\'està carregant… La imatge és massa grossa per a previsualitzar-la, però es provarà de desar igualment Personalització secundària Captura de pantalla Opció de reserva Eliminador de fons Restaura la imatge Radi difuminat Analítica Permet recopilar estadístiques anònimes d\'ús d\'aplicacions Esforç Espera S\'ha desat gairebé completament. Si ho cancel·leu ara, caldrà tornar-ho a desar. Actualitzacions Dibuixa fletxes Si està activat, el camí de dibuix es representarà com a fletxa apuntant Ordre d\'imatges Pixelació del cercle millorada Buscar actualitzacions Arc de Sant Martí Un estil lleugerament més cromàtic que monocromàtic Atenció Imatges a PDF Afegeix una màscara Final Variants simples Ressaltador Contenidors FABs Barres d\'aplicacions Habilita el dibuix d\'ombres darrere de les barres d\'aplicacions Valor en l\'interval %1$s - %2$s Rotació automàtica Fletxa de doble línia Doble fletxa Mode de puntada Lanczos Mitchell Més proper Spline Bàsic Una de les maneres més senzilles d\'augmentar la mida, substituint cada píxel per un nombre de píxels del mateix color Accuracy: %1$s Idiomes descarregats Text vertical d\'un sol bloc Caràcter únic Text escàs Línia crua Transformacions Repetiu la marca d\'aigua Desplaçament X GIF a imatges Utilitza Lasso com en el mode de dibuix per esborrar Si deixeu la vista prèvia ara, haureu d\'afegir les imatges de nou Stucki Dithering Burkes Dithering B Spline Glitch Glitch millorat ACES Filmic Tone Mapping Turbulència Oli Amplitud Y Distorsió Perlin ACES Hill Tone Mapping Desembolica Omega Color Matrix 4x4 Efectes simples Protanomalia Vintage Tons de tardor Televisió antiga Difuminat aleatori The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. Tingueu en compte que la compatibilitat amb altres programes o serveis de xifratge de fitxers no està garantida. Un tractament de clau o una configuració de xifratge lleugerament diferent pot provocar incompatibilitats. La contrasenya no vàlida o el fitxer escollit no està xifrat Intentar desar la imatge amb una amplada i una alçada determinades pot provocar un error de MOO. Fes-ho sota el teu propi risc i no diguis que no t\'he advertit! Memòria cau Mida de la memòria cau S\'ha trobat %1$s Esborrada automàtica de la memòria cau Si està activada, la memòria cau de l\'aplicació s\'esborrarà a l\'inici de l\'aplicació Crear Eines Agrupa les opcions per tipus Edita la captura de pantalla Agrupa les opcions de la pantalla principal pel seu tipus en lloc d\'una disposició de llista personalitzada No es pot canviar la disposició mentre l\'agrupació d\'opcions està activada Omet Copia Desar en mode %1$s pot ser inestable, perquè és un format sense pèrdues Si heu seleccionat el valor predefinit 125, la imatge es desarà com a mida del 125% de la imatge original amb una qualitat del 100%. Si trieu el valor predefinit 50, la imatge es desarà amb un 50% de mida i un 50% de qualitat. El valor predefinit aquí determina el percentatge del fitxer de sortida, és a dir, si seleccioneu el valor predefinit 50 en una imatge de 5 MB, obtindreu una imatge de 2,5 MB després de desar-lo. Aleatoritzar el nom del fitxer Si està activat, el nom del fitxer de sortida serà totalment aleatori S\'ha desat a la carpeta %1$s amb el nom %2$s S\'ha desat a la carpeta %1$s Xat de Telegram Parleu de l\'aplicació i obteniu comentaris d\'altres usuaris. També podeu obtenir actualitzacions beta i estadístiques aquí. Màscara de cultiu Relació d\'aspecte Utilitzeu aquest tipus de màscara per crear una màscara a partir d\'una imatge donada, tingueu en compte que HA de tenir un canal alfa Còpia de seguretat i restauració Còpia de seguretat Restaurar Feu una còpia de seguretat de la configuració de l\'aplicació en un fitxer Restaura la configuració de l\'aplicació des del fitxer generat anteriorment Fitxer danyat o no és una còpia de seguretat La configuració s\'ha restaurat correctament Contacta amb mi Això tornarà a la vostra configuració als valors predeterminats. Tingueu en compte que això no es pot desfer sense un fitxer de còpia de seguretat esmentat anteriorment. Suprimeix Esteu a punt d\'eliminar l\'esquema de colors seleccionat. Aquesta operació no es pot desfer Suprimeix l\'esquema Font Text Escala de lletra Per defecte L\'ús de lletres grosses pot provocar errors i problemes a la interfície d\'usuari, i no se solucionaran. Feu-ho servir amb precaució. Aa Àà Bb Cc Çç Dd Ee Èè Éé Ff Gg Hh Ii Íí Ll L·l Mm Nn Oo Òò Óó Pp Qq Rr Ss Tt Uu Úú Vv Xx Yy Zz 0123456789 !? Emocions Menjar i beguda Natura i Animals Objectes Símbols Activa els emoji Viatges i Llocs Activitats Retalla la imatge Elimina el fons de la imatge dibuixant o utilitza l\'opció Automàtica Es conservaran les metadades de la imatge original Els espais transparents al voltant de la imatge es retallaran Esborra automàticament el fons Mode d\'esborrar Esborra el fons Restaura el fons Pipeta Mode dibuix Crea un problema Vaja… S\'ha produït un error. Podeu escriure\'m mitjançant les opcions següents i intentaré trobar una solució Canviar la mida i convertir Canvia la mida de les imatges donades o converteix-les a altres formats. Les metadades EXIF també es poden editar aquí si trieu una sola imatge. Recompte màxim de colors Això permet que l\'aplicació reculli informes d\'error manualment Actualment, el format %1$s només permet llegir metadades EXIF a Android. La imatge de sortida no tindrà metadades en absolut quan es desi. Un valor de %1$s significa una compressió ràpida, que resulta en una mida de fitxer relativament gran. %2$s significa una compressió més lenta, donant lloc a un fitxer més petit. Permet betas La comprovació d\'actualitzacions inclourà les versions beta de l\'aplicació si està activada suavitat del pinzell Les imatges es retallaran al centre a la mida introduïda. El llenç s\'ampliarà amb el color de fons donat si la imatge és més petita que les dimensions introduïdes. Donació Costura de la imatge Trieu almenys 2 imatges Combina les imatges donades per obtenir-ne una de gran Escala d\'imatge de sortida Orientació de la imatge Horitzontal Vertical Escala imatges petites a grans Les imatges petites s\'escalaran a la més gran de la seqüència si està activada Regular Difumina les vores Dibuixa vores borroses sota la imatge original per omplir espais al seu voltant en lloc d\'un sol color si està activat Pixelació Pixelació millorada Pixelació del traç Pixelació del diamant millorada Pixelació del diamant Pixelació del cercle Substitueix el color Tolerància Color per substituir Color objectiu Color per eliminar Elimina el color Recodificar Mida de píxels Bloqueja l\'orientació del dibuix Si està activat en mode de dibuix, la pantalla no girarà Estil de paleta Taca tonal Neutre Vibrant Expressiu Amanida de fruita Fidelitat Contingut Estil de paleta predeterminat, permet personalitzar els quatre colors, d\'altres permeten establir només el color clau Un tema fort, el colorit és màxim per a la paleta primària, augmentat per als altres Un tema lúdic: la tonalitat del color d\'origen no apareix al tema Un tema monocrom, els colors són purament negre / blanc / gris Un esquema que col·loca el color d\'origen a Scheme.primaryContainer Un esquema molt semblant a l\'esquema de contingut Aquest verificador d\'actualitzacions es connectarà a GitHub per comprovar si hi ha una nova actualització disponible Vores esvaïdes Inhabilitat Tots dos Invertir colors Substitueix els colors del tema per negatius si està activat Cerca Permet la possibilitat de cercar a través de totes les opcions disponibles a la pantalla principal Eines PDF Opera amb fitxers PDF: previsualitza, converteix en lots d\'imatges o crea\'n una a partir d\'imatges donades Vista prèvia del PDF PDF a Imatges Vista prèvia senzilla de PDF Converteix PDF a imatges en un format de sortida determinat Empaqueta les imatges donades al fitxer PDF de sortida Filtre de màscara Apliqueu cadenes de filtres a àrees emmascarades determinades, cada àrea de màscara pot determinar el seu propi conjunt de filtres Màscares Màscara %d Color de la màscara Vista prèvia de la màscara Es representarà la màscara de filtre dibuixada per mostrar-vos el resultat aproximat Tipus de farciment invers Si està activat, es filtraran totes les àrees no emmascarades en lloc del comportament predeterminat Esteu a punt d\'eliminar la màscara de filtre seleccionada. Aquesta operació no es pot desfer Suprimeix la màscara Filtre complet Neó Apliqueu qualsevol cadena de filtres a imatges donades o imatge única Bolígraf Començar Centre Difuminat de privadesa Dibuixa camins de ressaltat nítids semitransparents Afegeix un efecte brillant als teus dibuixos Difumina la imatge sota el camí dibuixat per protegir tot el que vulgueu amagar Un per defecte, el més senzill: només el color Similar al difuminat de privadesa, però es pixela en lloc de desenfocar Habilita el dibuix d\'ombres darrere dels contenidors Lliscants Interruptors Botons Activa el dibuix d\'ombres darrere dels controls lliscants Habilita el dibuix d\'ombres darrere dels interruptors Habilita el dibuix d\'ombres darrere dels botons d\'acció flotants Habilita el dibuix d\'ombres darrere dels botons predeterminats Permet adoptar un quadre de límit per a l\'orientació de la imatge Mode de dibuix del camí Dibuix lliure Fletxa de línia Fletxa Línia Dibuixa el camí com a valor d\'entrada Dibuixa el camí des del punt inicial fins al punt final com una línia Dibuixa la fletxa apuntant des del punt inicial fins al punt final com una línia Dibuixa una fletxa apuntant des d\'un camí determinat Dibuixa una fletxa apuntant doble des del punt inicial fins al punt final com una línia Dibuixa una fletxa apuntant doble des d\'un camí determinat Oval perfilat Esbossat Rect Oval Rect Dibuixa recte des del punt inicial fins al punt final Dibuixa un oval des del punt inicial fins al punt final Dibuixa un oval delineat des del punt inicial fins al punt final Dibuixa el recte delineat des del punt inicial fins al punt final Lazo Dibuixa un camí ple tancat per un camí donat Gratuït Quadrícula horitzontal Quadrícula vertical Recompte de files Recompte de columnes No s\'ha trobat cap directori \"%1$s\", l\'hem canviat a un per defecte. Torneu a desar el fitxer Porta-retalls Pin automàtic Afegeix automàticament la imatge desada al porta-retalls si està activat Vibració Força de vibració Per sobreescriure els fitxers, heu d\'utilitzar la font d\'imatge \"Explorer\", proveu de tornar a seleccionar les imatges, hem canviat la font de la imatge a la necessària. Sobreescriu els fitxers El fitxer original es substituirà per un de nou en comptes de desar-lo a la carpeta seleccionada, aquesta opció necessita que l\'origen de la imatge sigui \"Explorador\" o GetContent, en canviar-ho, s\'establirà automàticament Buit Sufix Mode d\'escala Bilineal Catmull Bicúbic Hann Ermita Valor per defecte La interpolació lineal (o bilineal, en dues dimensions) sol ser bona per canviar la mida d\'una imatge, però provoca una suavització indesitjable dels detalls i encara pot ser una mica irregular. Els millors mètodes d\'escalat inclouen el remostreig de Lanczos i els filtres Mitchell-Netravali. El mode d\'escalat d\'Android més senzill que s\'utilitza en gairebé totes les aplicacions Mètode per interpolar i tornar a mostrejar sense problemes un conjunt de punts de control, utilitzat habitualment en gràfics per ordinador per crear corbes suaus La funció de finestra s\'aplica sovint en el processament del senyal per minimitzar les fuites espectrals i millorar la precisió de l\'anàlisi de freqüència reduint les vores d\'un senyal. Tècnica d\'interpolació matemàtica que utilitza els valors i les derivades als extrems d\'un segment de corba per generar una corba suau i contínua Mètode de mostreig que manté una interpolació d\'alta qualitat aplicant una funció de sincronització ponderada als valors de píxels Mètode de remostreig que utilitza un filtre de convolució amb paràmetres ajustables per aconseguir un equilibri entre la nitidesa i l\'antialiàsing a la imatge escalada Utilitza funcions polinomials definides per trossos per interpolar i aproximar suaument una corba o una representació de forma flexible i contínua. Només Clip No es realitzarà l\'emmagatzematge i només s\'intentarà posar la imatge al porta-retalls El pinzell restaurarà el fons en lloc d\'esborrar-lo OCR (reconeix el text) Reconeix el text d\'una imatge donada, més de 120 idiomes compatibles La imatge no té text o l\'aplicació no l\'ha trobat Tipus de reconeixement Ràpid Estàndard descarregar El millor No hi ha dades Per al bon funcionament de Tesseract OCR, les dades d\'entrenament addicionals (%1$s) s\'han de baixar al vostre dispositiu. \nVoleu baixar %2$s dades? No hi ha connexió, comproveu-ho i torneu-ho a provar per descarregar models de tren Idiomes disponibles Mode de segmentació Utilitza Pixel Switch S\'utilitzarà un commutador semblant a un píxel en lloc del material de Google que has basat Fitxer sobreescrit amb el nom %1$s a la destinació original Lupa Activa la lupa a la part superior del dit en els modes de dibuix per a una millor accessibilitat Força el valor inicial Força el giny exif a comprovar inicialment Permet diversos idiomes Diapositiva Al costat de l\'altre Canvia Toc Transparència Valora l\'aplicació Taxa Aquesta aplicació és completament gratuïta, si voleu que sigui més gran, destaca el projecte a Github 😄 Orientació & Només detecció d\'scripts Orientació automàtica & Detecció de guió Només automàtic Automàtic Columna única Bloc únic Línia única Paraula única Encercla paraula Orientació de text escàs & Detecció d\'scripts Voleu suprimir les dades d\'entrenament OCR de l\'idioma \"%1$s\" per a tots els tipus de reconeixement o només per a un seleccionat (%2$s)? Actual Tots Creador de degradats Creeu un degradat d\'una mida de sortida determinada amb colors i tipus d\'aparença personalitzats Lineal Radial Escombra Tipus de degradat Centre X Centre Y Mode de mosaic Es repeteix Mirall Pinça Adhesiu Color Stops Afegeix color Propietats Aplicació de la brillantor Pantalla Superposició de degradat Compon qualsevol degradat de la part superior de la imatge donada Càmera Utilitza la càmera per fer fotos, tingueu en compte que només és possible obtenir una imatge d\'aquesta font d\'imatge Marca d\'aigua Cobrir imatges amb marques d\'aigua de text/imatge personalitzables Repeteix la marca d\'aigua sobre la imatge en lloc d\'una sola en una posició determinada Desplaçament Y Tipus de filigrana Aquesta imatge s\'utilitzarà com a patró per a la marca d\'aigua Color del text Mode de superposició Eines GIF Converteix imatges a imatge GIF o extreu marcs d\'una imatge GIF determinada Converteix el fitxer GIF en un lot d\'imatges Converteix un lot d\'imatges a un fitxer GIF Imatges a GIF Trieu la imatge GIF per començar Utilitzeu la mida del primer fotograma Substituïu la mida especificada per les dimensions del primer marc Repetiu el recompte Retard de fotograma milis FPS Utilitzeu Lasso Vista prèvia de la imatge original Alpha Confetti Es mostrarà confeti en desar, compartir i altres accions principals Mode segur Amaga el contingut en sortir, a més la pantalla no es pot capturar ni gravar Sortida Dithering Quantificador Escala de grisos Bayer Dos Per Dos Dithering Bayer Tres Per Tres Dithering Bayer Four By Four Dithering Bayer Eight By Eight Dithering Floyd Steinberg Dithering Jarvis Judice Ninke Dithering Sierra Dithering Dues fileres Sierra Dithering Dithering Sierra Lite Dithering d\'Atkinson Fals dithering de Floyd Steinberg Trama d\'esquerra a dreta Dithering aleatori Trama de llindar simple Sigma Sigma espacial Difuminat mitjà Utilitza funcions polinomials bicúbiques definides a trossos per interpolar i aproximar suaument una corba o una representació de forma flexible i contínua Difuminat de pila natiu Canvi de inclinació Import Llavor Anaglif Soroll Ordenació de píxels Barrejar Canvi de canal X Canvi de canal Y Mida de la corrupció Canvi de corrupció X Canvi de corrupció Y Difuminat de tenda Esvaïment lateral lateral Superior A baix Força Erosionar Difusió anisotròpica Difusió Conducció Escalador de vent horitzontal Difuminat bilateral ràpid Difuminat verinós Mapeig de to logarítmic Cristal·litza Color del traç Vidre fractal Amplitud Marbre Efecte Aigua Mida Freqüència X Freqüència Y Amplitud X Hable Filmic Tone Mapping Mapes de tons Hejl Burgess Velocitat Matriu de colors 3x3 Polaroid Tritonomia Deuteranomalia Browni Coda Chrome Visió nocturna càlid Collonut Tritanopia Deutaronotopia Protanopia Acromatomalia Acromatòpsia Gra Poc nítid Pastel Orange Haze Somni rosa Hora daurada Estiu calorós Boira Porpra Sortida del sol Remolino de colors Llum suau de primavera Somni de lavanda Cyberpunk Llimonada Llum Foc Espectral Màgia nocturna Paisatge de fantasia Explosió de colors Degradat elèctric Caramel foscor Degradat futurista Sol Verd El món de l\'arc de Sant Martí Lila fosc Portal Espacial Remolí vermell Codi digital Bokeh L\'emoji de la barra d\'aplicacions es canviarà contínuament de manera aleatòria en lloc d\'utilitzar-ne un seleccionat Emojis aleatoris No es pot utilitzar la selecció aleatòria d\'emojis mentre els emojis estan desactivats No es pot seleccionar un emoji mentre n\'escolliu un a l\'atzar activat Favorit Encara no s\'ha afegit cap filtre preferit Format d\'imatge Afegeix un contenidor amb la forma seleccionada sota les icones principals de les targetes Forma d\'icona Drago Aldridge Tallar Uchimura Mobius Transició Cim Anomalia del color Imatges sobreescrites a la destinació original No es pot canviar el format de la imatge mentre l\'opció de sobreescriure fitxers està activada Emoji com a esquema de colors Utilitza el color primari dels emoji com a esquema de colors de l\'aplicació en lloc d\'un de definit manualment Crea la paleta \"Material You\" a partir de la imatge Colors Foscos Utilitza el mode nocturn esquema de colors en lloc de la variant de llum Copia com a codi \"Jetpack Compose\" Difuminat d\'anell Difuminat creuat Difuminat de cercle Difuminat d\'estrelles Canvi d\'inclinació lineal Etiquetes A Eliminar Eines APNG Converteix imatges a imatge APNG o extreu marcs de la imatge APNG donada APNG a les imatges Converteix el fitxer APNG en un lot d\'imatges Converteix un lot d\'imatges a fitxer APNG Imatges a APNG Trieu la imatge APNG per començar Difuminat de moviment Zip Creeu un fitxer Zip a partir de fitxers o imatges donats Arrossegueu l\'amplada del mànec Difuminat gaussià ràpid en 2D Difuminat gaussià ràpid en 4D Difuminat gaussià ràpid en 3D Mètode de remostreig que manté la interpolació d\'alta qualitat aplicant una funció de Bessel (jinc) als valors dels píxels Tipus de confeti Festiu Explotar Pluja Cantonades Eines JXL Realitzeu una transcodificació JXL ~ JPEG sense pèrdua de qualitat o convertiu GIF/APNG a animació JXL JXL a JPEG Realitzeu una transcodificació sense pèrdues de JXL a JPEG Realitzeu una transcodificació sense pèrdues de JPEG a JXL JPEG a JXL Trieu la imatge JXL per començar Cotxe de Pasqua Permet que l\'aplicació enganxi automàticament les dades del porta-retalls, de manera que apareixeran a la pantalla principal i les podreu processar Color d\'harmonització Nivell d\'harmonització Lanczos Bessel GIF a JXL Converteix imatges GIF en imatges animades JXL APNG a JXL Converteix imatges APNG en imatges animades JXL JXL a Imatges Converteix l\'animació JXL en un lot d\'imatges Imatges a JXL Converteix el lot d\'imatges a animació JXL Comportament Omet la selecció de fitxers El selector de fitxers es mostrarà immediatament si això és possible a la pantalla escollida Genera visualitzacions prèvies Habilita la generació de visualitzacions prèvies, això pot ajudar a evitar bloquejos en alguns dispositius, això també desactiva algunes funcionalitats d\'edició dins de l\'opció d\'edició única Compressió amb pèrdues Utilitza la compressió amb pèrdues per reduir la mida del fitxer en lloc de la compressió sense pèrdues Tipus de compressió Controla la velocitat de descodificació de la imatge resultant, això hauria d\'ajudar a obrir la imatge resultant més ràpidament, el valor de %1$s significa la descodificació més lenta, mentre que %2$s - la més ràpida, aquesta configuració pot augmentar la mida de la imatge de sortida. Classificació Data Data (invertida) Nom Nom (invertit) Configuració de canals Avui Ahir Selector incrustat Selector d\'imatges de Image Toolbox Sense permisos Sol·licitud Trieu diversos mitjans Trieu un mitjà únic Tria Torna-ho a provar Mostra la configuració en paisatge Si està desactivat, la configuració del mode horitzontal s\'obrirà al botó de la barra superior d\'aplicacions com sempre, en lloc de l\'opció visible permanent. Configuració de pantalla completa Activeu-lo i la pàgina de configuració s\'obrirà sempre com a pantalla completa en lloc de full de calaix lliscant Tipus de canvi Composar Un Jetpack Compose Material que canvieu Un material que canvies Màx Canvia la mida de l\'àncora Píxel Fluït Un interruptor basat en el sistema de disseny \"Fluent\". Cupertino Un interruptor basat en el sistema de disseny \"Cupertino\". Imatges a SVG Traça imatges donades a imatges SVG Utilitzeu la paleta mostrada La paleta de quantització es mostrarà si aquesta opció està activada Omet el camí No es recomana l\'ús d\'aquesta eina per rastrejar imatges grans sense reduir l\'escala, pot provocar un bloqueig i augmentar el temps de processament Reducció de la imatge La imatge es reduirà a dimensions inferiors abans de processar-la, això ajuda a que l\'eina funcioni de manera més ràpida i segura Relació de color mínima Línies Llindar Llindar quadràtic Tolerància a l\'arrodoniment de les coordenades Escala del camí Restableix les propietats Totes les propietats s\'establiran als valors predeterminats, tingueu en compte que aquesta acció no es pot desfer Detallada Amplada de línia per defecte Mode motor Llegat Xarxa LSTM Llegat i LSTM Converteix Converteix lots d\'imatges al format donat Afegeix una carpeta nova Bits per mostra Compressió Interpretació fotomètrica Mostres per píxel Configuració plana Y Cb Cr Submostreig Y Cb Cr Posicionament X Resolució Resolució Y Unitat de Resolució Desplaçaments de franges Files per tira Recompte de bytes de banda Format d\'intercanvi JPEG Longitud del format d\'intercanvi JPEG Funció de transferència Punt Blanc Cromaticitats primàries Y Cb Cr Coeficients Referència Negre Blanc Data Hora Descripció de la imatge Fer Model Programari Artista Copyright Versió Exif Versió Flashpix Espai de color Gamma Dimensió Pixel X Dimensió Y de píxels Bits comprimits per píxel Nota del fabricant Comentari de l\'usuari Fitxer de so relacionat Data Hora Original Data Hora Digitalitzat Temps de compensació Temps de compensació original Temps de compensació digitalitzat Temps de subsec Subsec Temps Original Temps subsec digitalitzat Temps d\'exposició Número F Programa d\'exposició Sensibilitat espectral Sensibilitat fotogràfica Oecf Tipus de sensibilitat Sensibilitat de sortida estàndard Índex d\'exposició recomanat Velocitat ISO Velocitat ISO Latitud yyy Velocitat ISO Latitud zzz Valor de velocitat d\'obturació Valor d\'obertura Valor de brillantor Valor de biaix d\'exposició Valor màxim d\'obertura Distància del subjecte Mode de mesura Flash Àrea temàtica Distància focal Energia Flash Resposta de freqüència espacial Resolució del pla focal X Resolució del pla focal Y Unitat de resolució del pla focal Ubicació de l\'assignatura Índex d\'exposició Mètode de detecció Font del fitxer Patró CFA Representació personalitzada Mode d\'exposició Balanç de blancs Relació de zoom digital Distància focal en pel·lícula de 35 mm Tipus de captura d\'escena Obteniu el control Contrast Saturació Nitidez Descripció de la configuració del dispositiu Interval de distància del subjecte Identificador únic de la imatge Nom del propietari de la càmera Número de sèrie del cos Especificació de la lent Marca de lents Model de lent Número de sèrie de la lent ID de versió del GPS GPS Latitud Ref GPS Latitud GPS Longitud Ref Longitud GPS GPS Altitud Ref Altitud GPS Marca de temps GPS Satèl·lits GPS Estat GPS Mode de mesura GPS GPS DOP Velocitat GPS Ref Velocitat GPS Track GPS Ref Track GPS Direcció Img GPS Ref Direcció d\'imatge GPS Dades del mapa GPS GPS Dest Latitud Ref GPS Dest Latitude GPS Dest Longitud Ref Longitud destí GPS GPS Dest Bearing Ref GPS Dest Bearing Distància GPS Ref Distància de destí GPS Mètode de processament GPS Informació de l\'àrea GPS Segell de data GPS Diferencial GPS Error de posicionament GPS H Índex d\'interoperabilitat Versió DNG Mida de retall per defecte Inici de la previsualització de la imatge Previsualitza la longitud de la imatge Marc d\'aspecte Bord inferior del sensor Vora esquerra del sensor Bord dret del sensor Bord superior del sensor ISO Dibuixa el text al camí amb el tipus de lletra i el color donats Mida de la lletra Mida de la filigrana Repetiu el text El text actual es repetirà fins al final del camí en lloc de dibuixar una vegada Mida del guió Utilitzeu la imatge seleccionada per dibuixar-la al llarg del camí donat Aquesta imatge s\'utilitzarà com a entrada repetitiva del camí dibuixat Dibuixa un triangle delineat des del punt inicial fins al punt final Dibuixa un triangle delineat des del punt inicial fins al punt final Triangle esquematitzat Triangle Dibuixa polígon des del punt inicial fins al punt final Polígon Polígon esquematitzat Dibuixa un polígon delineat des del punt inicial fins al punt final Vèrtexs Dibuixa un polígon regular Dibuixa un polígon que serà regular en lloc de forma lliure Dibuixa estrella des del punt inicial fins al punt final Estrella Estrella perfilada Dibuixa l\'estrella delineada des del punt inicial fins al punt final Relació de radi interior Dibuixa una estrella regular Dibuixa una estrella que serà regular en lloc de lliure Antiàlies Habilita l\'antialiasing per evitar vores afilades Obriu Edita en lloc de previsualitzar Quan seleccioneu la imatge per obrir (visualització prèvia) a ImageToolbox, s\'obrirà el full de selecció d\'edició en lloc de previsualitzar Escàner de documents Escaneja documents i crea PDF o separa imatges d\'ells Feu clic per començar a escanejar Inicieu l\'escaneig Desa com a PDF Comparteix com a PDF Les opcions següents són per desar imatges, no PDF Igualar l\'histograma HSV Equalitzar l\'histograma Introduïu un percentatge Permet l\'entrada per camp de text Activa el camp de text darrere de la selecció de valors predefinits per introduir-los sobre la marxa Escala l\'espai de color Lineal Igualar la pixelació de l\'histograma Mida de la quadrícula X Mida de la quadrícula Y Equalitzar histograma adaptatiu Equalitzar histograma LUV adaptatiu Equalize Histogram Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Retalla al contingut Color del marc Color per ignorar Plantilla No s\'han afegit filtres de plantilla Crea nou El codi QR escanejat no és una plantilla de filtre vàlida Escaneja el codi QR El fitxer seleccionat no té dades de plantilla de filtre Crea plantilla Nom de la plantilla Aquesta imatge s\'utilitzarà per previsualitzar aquesta plantilla de filtre Filtre de plantilla Com a imatge de codi QR Com a fitxer Desa com a fitxer Desa com a imatge de codi QR Suprimeix la plantilla Esteu a punt d\'eliminar el filtre de plantilla seleccionat. Aquesta operació no es pot desfer S\'ha afegit una plantilla de filtre amb el nom \"%1$s\" (%2$s) Vista prèvia del filtre QR i codi de barres Escaneja el codi QR i obtén el seu contingut o enganxa la cadena per generar-ne una de nova Contingut del codi Escaneja qualsevol codi de barres per substituir el contingut del camp o escriviu alguna cosa per generar un codi de barres nou amb el tipus seleccionat Descripció QR Min Doneu permís a la càmera a la configuració per escanejar el codi QR Doneu permís a la càmera a la configuració per escanejar Document Scanner Cúbic B-Spline Hamming Hanning Blackman Welch Quadric Gaussià Esfinx Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-He Caixa Bohman Lanços 2 Lanços 3 Lanços 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc La interpolació cúbica proporciona una escala més suau tenint en compte els 16 píxels més propers, donant millors resultats que els bilineals Utilitza funcions polinomials definides per trossos per interpolar i aproximar suaument una corba o una representació de forma flexible i contínua. Una funció de finestra que s\'utilitza per reduir les fuites espectrals reduint les vores d\'un senyal, útil en el processament del senyal Una variant de la finestra de Hann, que s\'utilitza habitualment per reduir les fuites espectrals en aplicacions de processament de senyal Una funció de finestra que proporciona una bona resolució de freqüència minimitzant les fuites espectrals, que s\'utilitza sovint en el processament del senyal Una funció de finestra dissenyada per oferir una bona resolució de freqüència amb una fuga espectral reduïda, que s\'utilitza sovint en aplicacions de processament de senyal Un mètode que utilitza una funció quadràtica per a la interpolació, proporcionant resultats suaus i continus Un mètode d\'interpolació que aplica una funció gaussiana, útil per suavitzar i reduir el soroll a les imatges Un mètode avançat de mostreig que proporciona una interpolació d\'alta qualitat amb un mínim d\'artefactes Una funció de finestra triangular que s\'utilitza en el processament del senyal per reduir les fuites espectrals Un mètode d\'interpolació d\'alta qualitat optimitzat per redimensionar la imatge natural, equilibrant la nitidesa i la suavitat Una variant més nítida del mètode Robidoux, optimitzada per canviar la mida de la imatge nítida Un mètode d\'interpolació basat en spline que proporciona resultats suaus mitjançant un filtre de 16 tocs Un mètode d\'interpolació basat en spline que proporciona resultats suaus mitjançant un filtre de 36 tocs Un mètode d\'interpolació basat en spline que proporciona resultats suaus mitjançant un filtre de 64 tocs Un mètode d\'interpolació que utilitza la finestra Kaiser, proporcionant un bon control sobre la compensació entre l\'amplada del lòbul principal i el nivell del lòbul lateral Una funció de finestra híbrida que combina les finestres de Bartlett i Hann, utilitzada per reduir les fuites espectrals en el processament del senyal Un mètode de remuestreig senzill que utilitza la mitjana dels valors de píxels més propers, sovint donant lloc a una aparença de blocs Una funció de finestra utilitzada per reduir les fuites espectrals, proporcionant una bona resolució de freqüència en aplicacions de processament de senyal Un mètode de mostreig que utilitza un filtre Lanczos de 2 lòbuls per a una interpolació d\'alta qualitat amb artefactes mínims Un mètode de remuestreig que utilitza un filtre Lanczos de 3 lòbuls per a una interpolació d\'alta qualitat amb artefactes mínims Un mètode de mostreig que utilitza un filtre Lanczos de 4 lòbuls per a una interpolació d\'alta qualitat amb artefactes mínims Una variant del filtre Lanczos 2 que utilitza la funció jinc, proporcionant una interpolació d\'alta qualitat amb artefactes mínims Una variant del filtre Lanczos 3 que utilitza la funció jinc, proporcionant una interpolació d\'alta qualitat amb artefactes mínims Una variant del filtre Lanczos 4 que utilitza la funció jinc, proporcionant una interpolació d\'alta qualitat amb artefactes mínims Hanning EWA Variant de mitjana ponderada el·líptica (EWA) del filtre Hanning per a una interpolació i un remuestreig suaus Robidoux EWA Variant de mitjana ponderada el·líptica (EWA) del filtre Robidoux per a un remuestreig d\'alta qualitat Blackman EVE Variant de mitjana ponderada el·líptica (EWA) del filtre Blackman per minimitzar els artefactes de timbre Quadric EWA Variant de mitjana ponderada el·líptica (EWA) del filtre quàdric per a una interpolació suau Robidoux Sharp EWA Variant de mitjana ponderada el·líptica (EWA) del filtre Robidoux Sharp per obtenir resultats més nítids Lanczos 3 Jinc EWA Variant de mitjana ponderada el·líptica (EWA) del filtre Lanczos 3 Jinc per a un remuestreig d\'alta qualitat amb àlies reduïts Ginseng Un filtre de mostreig dissenyat per processar imatges d\'alta qualitat amb un bon equilibri de nitidesa i suavitat Ginseng EWA Mitjana ponderada el·líptica (EWA) variant del filtre Ginseng per millorar la qualitat d\'imatge Lanczos Sharp EWA Variant de mitjana ponderada el·líptica (EWA) del filtre Lanczos Sharp per aconseguir resultats nítids amb artefactes mínims Lanczos 4 EWA més nítid Variant de mitjana ponderada el·líptica (EWA) del filtre Lanczos 4 Sharpest per a un remuestreig d\'imatges extremadament nítid Lanczos Soft EWA Variant de mitjana ponderada el·líptica (EWA) del filtre Lanczos Soft per a un remuestreig més suau de la imatge Haasn suau Un filtre de mostreig dissenyat per Haasn per a una escala d\'imatges suau i sense artefactes Conversió de format Converteix lots d\'imatges d\'un format a un altre Descartar per sempre Apilament d\'imatges Apila imatges unes sobre les altres amb els modes de combinació escollits Afegeix una imatge Els contenidors compten Clahe HSL Clahe HSV Equalitzar histograma HSL adaptatiu Equalitzar histograma HSV adaptatiu Mode Edge Clip Embolicar Daltonisme Seleccioneu el mode per adaptar els colors del tema per a la variant de daltonisme seleccionada Dificultat per distingir entre tons vermells i verds Dificultat per distingir entre tons verds i vermells Dificultat per distingir els tons blaus i grocs Incapacitat per percebre tonalitats vermelles Incapacitat per percebre els tons verds Incapacitat per percebre tons blaus Sensibilitat reduïda a tots els colors Daltonisme total, veient només tons de gris No utilitzeu l\'esquema de daltònic Els colors seran exactament els establerts al tema Sigmoïdal Lagrange 2 Un filtre d\'interpolació de Lagrange d\'ordre 2, adequat per a l\'escala d\'imatges d\'alta qualitat amb transicions suaus Lagrange 3 Un filtre d\'interpolació de Lagrange d\'ordre 3, que ofereix una millor precisió i resultats més suaus per a l\'escala de la imatge Lanços 6 Un filtre de remuestreig de Lanczos amb un ordre superior de 6, que proporciona una escala d\'imatges més nítida i precisa Lanczos 6 Jinc Una variant del filtre Lanczos 6 que utilitza una funció Jinc per millorar la qualitat de mostreig de la imatge Desenfocament de caixa lineal Desenfocament de tenda lineal Desenfocament de caixa gaussiana lineal Desenfocament de pila lineal Gaussian Box Blur Desenfocament gaussià ràpid lineal A continuació Desenfocament gaussià ràpid lineal Desenfocament gaussià lineal Trieu un filtre per utilitzar-lo com a pintura Substituïu el filtre Trieu el filtre següent per utilitzar-lo com a pinzell al vostre dibuix Esquema de compressió TIFF Low Poly Pintura de sorra Divisió d\'imatges Dividiu una imatge única per files o columnes Ajust als límits Combina el mode de canvi de mida retallat amb aquest paràmetre per aconseguir el comportament desitjat (retalla/ajust a la relació d\'aspecte) Idiomes importats correctament Còpia de seguretat dels models OCR Importar Exporta Posició Centre Superior esquerra A dalt a la dreta A baix a l\'esquerra A baix a la dreta Centre superior Centre Dret Centre inferior Centre Esquerra Imatge objectiu Transferència de paleta Oli millorat Televisió antiga senzilla HDR Gotham Esbós simple Lluentor suau Cartell de colors Tri Ton Tercer color Clahe Oklab Clara Olch Clahe Jzazbz Punt de polca Dithering 2x2 agrupat Dithering 4x4 agrupat Dithering de 8 x 8 en clúster Dithering de Yililoma No s\'ha seleccionat cap opció preferida, afegiu-les a la pàgina d\'eines Afegeix Preferits Complementari Anàleg Triadic Complementària dividida Tetràdic Plaçada Anàleg + Complementari Eines de color Barrejar, crear tons, generar matisos i molt més Harmonies de colors Sombreat de colors Variació Tints Tons Ombres Barreja de colors Informació de color Color seleccionat Color per barrejar No es pot utilitzar monet mentre els colors dinàmics estan activats 512x512 2D LUT Imatge LUT objectiu Un aficionat Senyoreta Etiqueta Elegància suau Variant suau elegància Variant de transferència de paleta LUT 3D Fitxer LUT 3D de destinació (.cube / .CUBE) LUT Bypass de lleixiu Llum de les espelmes Drop Blues Amber nervioso Colors de tardor Film Stock 50 Nit de boira Kodak Obteniu una imatge LUT neutral Primer, utilitzeu la vostra aplicació d\'edició de fotos preferida per aplicar un filtre a LUT neutral que podeu obtenir aquí. Perquè això funcioni correctament, cada color de píxel no ha de dependre d\'altres píxels (per exemple, el desenfocament no funcionarà). Un cop estigui llest, utilitzeu la vostra nova imatge LUT com a entrada per al filtre 512*512 LUT Pop Art Cel·luloide Cafè Bosc daurat Verdós Groc retro Vista prèvia d\'enllaços Permet la recuperació de la vista prèvia d\'enllaços en llocs on podeu obtenir text (QRCode, OCR, etc.) Enllaços Els fitxers ICO només es poden desar amb una mida màxima de 256 x 256 GIF a WEBP Converteix imatges GIF en imatges animades WEBP Eines WEB Converteix imatges a imatges animades WEBP o extreu fotogrames d\'una animació WEBP determinada WEBP a imatges Converteix el fitxer WEBP en un lot d\'imatges Converteix un lot d\'imatges a un fitxer WEBP Imatges al WEBP Trieu la imatge WEBP per començar No hi ha accés complet als fitxers Permet l\'accés a tots els fitxers per veure JXL, QOI i altres imatges que no es reconeixen com a imatges a Android. Sense el permís Image Toolbox no pot mostrar aquestes imatges Color de dibuix per defecte Mode de camí de dibuix predeterminat Afegeix marca de temps Activa l\'addició de marca de temps al nom del fitxer de sortida Marca de temps amb format Activeu el format de marca de temps al nom del fitxer de sortida en comptes de mil·lisos bàsics Activeu les marques de temps per seleccionar-ne el format Ubicació d\'estalvi d\'una vegada Visualitzeu i editeu les ubicacions d\'emmagatzematge d\'una vegada que podeu utilitzar prement el botó desa en la majoria de les opcions Usat recentment Canal CI Grup Caixa d\'eines d\'imatge a Telegram 🎉 Uneix-te al nostre xat on podràs parlar del que vulguis i també mirar al canal CI on publico betas i anuncis Rebeu notificacions sobre les noves versions de l\'aplicació i llegiu els anuncis Ajusteu una imatge a les dimensions donades i apliqueu el desenfocament o el color al fons Disposició d\'eines Agrupa les eines per tipus Agrupa les eines de la pantalla principal pel seu tipus en lloc d\'una disposició de llista personalitzada Valors per defecte Visibilitat de les barres del sistema Mostra les barres del sistema fent lliscar el dit Permet lliscar per mostrar les barres del sistema si estan amagades Automàtic Amaga-ho tot Mostra-ho tot Amaga la barra de navegació Amaga la barra d\'estat Generació de soroll Genera diferents sorolls com Perlin o altres tipus Freqüència Tipus de soroll Tipus de rotació Tipus fractal Octaves Lacunaritat Guanyar Força ponderada Ping Pong Força Funció de distància Tipus de retorn Trastorn Deformació del domini Alineació Nom de fitxer personalitzat Seleccioneu la ubicació i el nom del fitxer que s\'utilitzaran per desar la imatge actual Desat a la carpeta amb un nom personalitzat Creador de collages Feu collages de fins a 20 imatges Tipus de collage Mantingueu premut la imatge per intercanviar, moure i fer zoom per ajustar la posició Desactiva la rotació Impedeix la rotació de les imatges amb gestos amb dos dits Activa l\'ajustament a les vores Després de moure\'s o fer zoom, les imatges s\'ajustaran per omplir les vores del marc Histograma Histograma d\'imatge RGB o brillantor per ajudar-vos a fer ajustaments Aquesta imatge s\'utilitzarà per generar histogrames RGB i Brillantor Opcions de Tesseract Apliqueu algunes variables d\'entrada per al motor tesseract Opcions personalitzades Les opcions s\'han d\'introduir seguint aquest patró: \"--{option_name} {value}\" Retall automàtic Racons lliures Retalla la imatge per polígon, això també corregeix la perspectiva Coaccionar els punts als límits de la imatge Els punts no estaran limitats pels límits de la imatge, això és útil per a una correcció de perspectiva més precisa Màscara Emplenament conscient del contingut sota el camí dibuixat Punt de curació Utilitzeu Circle Kernel Obertura Tancament Gradient Morfològic Barret de copa Barret negre Corbes de to Restableix les corbes Les corbes es tornaran al valor predeterminat Estil de línia Mida de la bretxa De punt Punt puntejat Estampat Ziga-zaga Dibuixa una línia discontínua al llarg del camí dibuixat amb la mida de buit especificada Dibuixa punts i línies discontínues al llarg del camí donat Només línies rectes per defecte Dibuixa les formes seleccionades al llarg del camí amb l\'espaiat especificat Dibuixa una ziga-zaga ondulada al llarg del camí Relació en zig-zag Crea una drecera Trieu l\'eina per fixar L\'eina s\'afegirà a la pantalla d\'inici del llançador com a drecera, utilitzeu-la combinant-la amb la configuració \"Omet la selecció de fitxers\" per aconseguir el comportament necessari No apileu marcs Permet eliminar els fotogrames anteriors, de manera que no s\'apilen els uns als altres Fundició creuada Els fotogrames s\'encreuaran entre si Els fotogrames de fundició creuada compten Llindar 1 Llindar dos Canny Mirall 101 Desenfocament del zoom millorat Laplacià simple Sobel Simple Graella d\'ajuda Mostra la quadrícula de suport a sobre de l\'àrea de dibuix per ajudar amb manipulacions precises Color de la quadrícula Amplada de la cel·la Alçada cel·lular Selectors compactes Alguns controls de selecció utilitzaran un disseny compacte per ocupar menys espai Doneu permís a la càmera a la configuració per capturar la imatge Disseny Títol de la pantalla principal Factor de taxa constant (CRF) Un valor de %1$s significa una compressió lenta, que resulta en una mida de fitxer relativament petita. %2$s significa una compressió més ràpida, donant lloc a un fitxer gran. Biblioteca Lut Baixeu la col·lecció de LUT, que podeu aplicar després de descarregar Actualitzeu la col·lecció de LUT (només es posaran a la cua les noves), que podeu aplicar després de descarregar Canvia la vista prèvia de la imatge per defecte per als filtres Imatge de previsualització Amaga Mostra Tipus de control lliscant Fantasia Material 2 Un control lliscant d\'aspecte elegant. Aquesta és l\'opció predeterminada Un control lliscant Material 2 Un control lliscant Material You Aplicar Botons de diàleg central Els botons dels diàlegs es col·locaran al centre en lloc del costat esquerre si és possible Llicències de codi obert Consulta les llicències de les biblioteques de codi obert utilitzades en aquesta aplicació Àrea Re-mostreig utilitzant la relació d\'àrea de píxels. Pot ser un mètode preferit per a la destrucció d\'imatges, ja que dóna resultats lliures de moiré. Però quan s\'amplia la imatge, és similar al mètode \"Més propera\". Activa el mapa de tons Introduïu % No es pot accedir al lloc, proveu d\'utilitzar VPN o comproveu si l\'URL és correcte Capes de marcatge Mode de capes amb la possibilitat de col·locar lliurement imatges, text i molt més Edita la capa Capes a la imatge Utilitzeu una imatge com a fons i afegiu-hi diferents capes a sobre Capes al fons Igual que la primera opció però amb color en comptes d\'imatge Beta Configuració ràpida lateral Afegiu una franja flotant al costat seleccionat mentre editeu les imatges, que obrirà la configuració ràpida quan feu clic Esborra la selecció El grup de configuració \"%1$s\" es replegarà de manera predeterminada El grup de configuració \"%1$s\" s\'ampliarà de manera predeterminada Eines Base64 Descodifiqueu la cadena Base64 a la imatge o codifiqueu la imatge al format Base64 Base 64 El valor proporcionat no és una cadena Base64 vàlida No es pot copiar la cadena Base64 buida o no vàlida Enganxa la base 64 Copia Base64 Carregueu la imatge per copiar o desar la cadena Base64. Si teniu la cadena en si, podeu enganxar-la a dalt per obtenir la imatge Desa Base64 Comparteix Base64 Opcions Accions Importa Base64 Accions Base64 Afegeix un esquema Afegiu un esquema al voltant del text amb el color i l\'amplada especificats Color del contorn Mida del contorn Rotació Suma de comprovació com a nom de fitxer Les imatges de sortida tindran un nom corresponent a la seva suma de comprovació de dades Programari lliure (partner) Programari més útil al canal de socis d\'aplicacions d\'Android Algorisme Eines de suma de comprovació Compareu sumes de comprovació, calculeu hash o creeu cadenes hexadecimales a partir de fitxers utilitzant diferents algorismes de hash Calcula Text Hash Suma de control Trieu el fitxer per calcular la seva suma de comprovació en funció de l\'algorisme seleccionat Introduïu text per calcular la seva suma de comprovació en funció de l\'algorisme seleccionat Suma de comprovació de la font Suma de comprovació per comparar Partit! Diferència Les sumes de control són iguals, pot ser segur Les sumes de control no són iguals, el fitxer pot ser perillós! Gradients de malla Mireu la col·lecció en línia de Mesh Gradients Només es poden importar tipus de lletra TTF i OTF Importa el tipus de lletra (TTF/OTF) Exportar tipus de lletra Tipus de lletra importats S\'ha produït un error en desar l\'intent, prova de canviar la carpeta de sortida El nom del fitxer no està definit Cap Pàgines personalitzades Selecció de pàgines Confirmació de sortida de l\'eina Si teniu canvis no desats mentre feu servir eines concretes i intenteu tancar-los, es mostrarà el diàleg de confirmació Edita EXIF Canvieu les metadades d\'una imatge única sense recompressió Toqueu per editar les etiquetes disponibles Canvia l\'adhesiu Amplada d\'ajust Alçada d\'ajust Comparació per lots Trieu fitxers/fitxers per calcular la seva suma de comprovació en funció de l\'algorisme seleccionat Trieu fitxers Trieu Directori Escala de longitud del cap Segell Marca de temps Patró de format Encoixinat Tall d\'imatges Retalla la part de la imatge i fusiona les de l\'esquerra (pot ser inversa) per línies verticals o horitzontals Línia de pivot vertical Línia de pivot horitzontal Selecció inversa Es deixarà la part tallada verticalment, en lloc de fusionar les parts al voltant de l\'àrea de tall Es deixarà la part tallada horitzontal, en lloc de fusionar les parts al voltant de l\'àrea de tall Col·lecció de degradats de malla Creeu un degradat de malla amb una quantitat personalitzada de nusos i resolució Superposició de degradat de malla Composa el degradat de malla de la part superior de les imatges donades Personalització de punts Mida de la graella Resolució X Resolució Y Resolució Píxel a Píxel Ressaltar el color Tipus de comparació de píxels Escaneja el codi de barres Relació d\'altura Tipus de codi de barres Aplicar en B/N La imatge del codi de barres serà completament en blanc i negre i no tindran color pel tema de l\'aplicació Escaneja qualsevol codi de barres (QR, EAN, AZTEC, …) i obtén el seu contingut o enganxa el teu text per generar-ne un de nou No s\'ha trobat cap codi de barres El codi de barres generat estarà aquí Portades d\'àudio Extraieu imatges de la portada d\'àlbum dels fitxers d\'àudio, els formats més habituals són compatibles Trieu l\'àudio per començar Trieu l\'àudio No s\'han trobat cobertes Envia registres Feu clic per compartir el fitxer de registres de l\'aplicació, això em pot ajudar a detectar el problema i solucionar-los Vaja… S\'ha produït un error Podeu contactar amb mi mitjançant les opcions següents i intentaré trobar una solució.\n(No oblideu adjuntar els registres) Escriure al fitxer Extraieu text del lot d\'imatges i emmagatzemeu-lo en un fitxer de text Escriure a les metadades Extraieu el text de cada imatge i col·loqueu-lo a la informació EXIF ​​de les fotos relatives Mode invisible Utilitzeu l\'esteganografia per crear filigranes invisibles als ulls dins dels bytes de les vostres imatges Utilitzeu LSB S\'utilitzarà el mètode d\'esteganografia LSB (Less Significant Bit), en cas contrari FD (Domini de freqüència). Elimina automàticament els ulls vermells Contrasenya Desbloqueja PDF està protegit Operació gairebé acabada. Si cancel·les ara caldrà reiniciar-lo Data de modificació Data de modificació (invertida) Mida Mida (invertida) Tipus MIME Tipus MIME (invertit) Extensió Extensió (invertida) Data d\'afegit Data afegida (invertida) D\'esquerra a dreta De dreta a esquerra De dalt a baix De baix a dalt Vidre líquid Un interruptor basat en IOS 26 anunciat recentment i el seu sistema de disseny de vidre líquid Trieu la imatge o enganxeu/importeu dades de Base64 a continuació Escriviu l\'enllaç de la imatge per començar Enganxa l\'enllaç Calidoscopi Angle secundari Els costats Mescla de canals Verd blau Blau vermell Verd vermell En vermell Al verd Al blau Cian Magenta Groc Color de mitges tintes Contorn Nivells Offset Voronoi cristal·litza Forma Estirar Aleatorietat Destaquejar Difús DoG Segon radi Igualar resplendor Girar i pessigar Puntilitzar Color de la vora Coordenades polars Recta a polar Polar a recte Inverteix en cercle Reduir el soroll Solarització senzilla Teixir X Gap Y Gap X Amplada Y Amplada Girar Segell de goma Untar Densitat Barrejar Distorsió de la lent esfèrica Índex de refracció Arc Angle de propagació Espurneig Raigs ASCII Gradient Maria Tardor Os Jet Hivern Oceà Estiu Primavera Variant genial HSV Rosa Calent Paraula Magma Infern Plasma Viridis Ciutadans Crepuscle Crepuscle canviat Perspectiva Automàtica Desviació Permetre retallar Retall o perspectiva Absoluta Turbo Verd profund Correcció de la lent Fitxer de perfil de la lent objectiu en format JSON Baixeu perfils de lents preparats Percentatges de part Exporta com a JSON Copieu la cadena amb dades de paleta com a representació JSON Talla de costures Pantalla d\'inici Pantalla de bloqueig Integrat Exportació de fons de pantalla Actualitza Obteniu fons de pantalla actuals de la llar, de bloqueig i integrats Permet l\'accés a tots els fitxers, això és necessari per recuperar fons de pantalla El permís de gestió d\'emmagatzematge extern no és suficient, heu de permetre l\'accés a les vostres imatges, assegureu-vos de seleccionar \"Permet-ho tot\" Afegeix un valor predefinit al nom del fitxer Afegeix el sufix amb el valor predefinit seleccionat al nom del fitxer d\'imatge Afegeix el mode d\'escala d\'imatge al nom del fitxer Afegeix el sufix amb el mode d\'escala d\'imatge seleccionat al nom del fitxer de la imatge Ascii Art Converteix la imatge en text ascii que semblarà una imatge Params Aplica un filtre negatiu a la imatge per obtenir un millor resultat en alguns casos S\'està processant la captura de pantalla La captura de pantalla no s\'ha capturat, torna-ho a provar S\'ha omès l\'estalvi S\'han omès %1$s fitxers Permet ometre si és més gran Algunes eines podran saltar-se desar imatges si la mida del fitxer resultant és més gran que l\'original Esdeveniment del calendari Contacte Correu electrònic Ubicació Telèfon Text SMS URL Wi-Fi Xarxa oberta N/A SSID Telèfon Missatge Adreça Assumpte Cos Nom Organització Títol Telèfons Correus electrònics URL Adreces Resum Descripció Ubicació Organitzador Data d\'inici Data de finalització Estat Latitud Longitud Crea codi de barres Edita el codi de barres Configuració Wi-Fi Seguretat Tria el contacte Concedeix permís als contactes a la configuració per emplenar automàticament amb el contacte seleccionat Informació de contacte Nom de pila segon nom Cognom Pronunciació Afegeix el telèfon Afegeix un correu electrònic Afegeix una adreça Lloc web Afegeix un lloc web Nom amb format Aquesta imatge s\'utilitzarà per col·locar-la a sobre del codi de barres Personalització del codi Aquesta imatge s\'utilitzarà com a logotip al centre del codi QR Logotip Encoixinat de logotip Mida del logotip Cantonades del logotip Quart ull Afegeix simetria ocular al codi qr afegint un quart ull a l\'extrem inferior Forma de píxel Forma de marc Forma de bola Nivell de correcció d\'errors Color fosc Color clar Hyper OS Estil semblant a Xiaomi HyperOS Patró de màscara És possible que aquest codi no es pugui escanejar, canvieu els paràmetres d\'aparença per fer-lo llegible amb tots els dispositius No escanejable Les eines semblaran el llançador d\'aplicacions de la pantalla d\'inici per ser més compactes Mode d\'inici Omple una àrea amb el pinzell i l\'estil seleccionats Farciment d\'inundació Spray Dibuixa un camí amb estil grafit Partícules quadrades Les partícules de polvorització tindran forma quadrada en lloc de cercles Eines de paleta Genereu material bàsic/de la paleta a partir d\'imatge, o importeu/exporteu a diferents formats de paleta Edita la paleta Exporta/importa la paleta en diversos formats Nom del color Nom de la paleta Format de paleta Exporta la paleta generada a diferents formats Afegeix un color nou a la paleta actual El format %1$s no admet proporcionar el nom de la paleta A causa de les polítiques de Play Store, aquesta funció no es pot incloure a la versió actual. Per accedir a aquesta funcionalitat, descarregueu ImageToolbox des d\'una font alternativa. Podeu trobar les versions disponibles a GitHub a continuació. Obriu la pàgina de Github El fitxer original es substituirà per un de nou en lloc de desar-lo a la carpeta seleccionada S\'ha detectat un text de marca d\'aigua amagat S\'ha detectat una imatge de marca d\'aigua oculta Aquesta imatge estava amagada Inpainting generatiu Us permet eliminar objectes d\'una imatge mitjançant un model d\'IA, sense dependre d\'OpenCV. Per utilitzar aquesta funció, l\'aplicació baixarà el model necessari (~200 MB) de GitHub Us permet eliminar objectes d\'una imatge mitjançant un model d\'IA, sense dependre d\'OpenCV. Aquesta pot ser una operació de llarga durada Anàlisi del nivell d\'error Gradient de lluminància Distància mitjana Detecció de moviment de còpia Retenir Coeficient Les dades del porta-retalls són massa grans Les dades són massa grans per copiar-les Pixelització de teixit simple Pixelització esglaonada Pixelització creuada Micro Macro Pixelització Pixelització orbital Pixelització de vòrtex Pixelització de quadrícula de pols Pixelització del nucli Pixelització de teixit radial No es pot obrir l\'uri \"%1$s\" Mode de nevada Habilitat Marc de vora Variant de Glitch Canvi de canal Desplaçament màxim VHS Block Glitch Mida del bloc curvatura CRT Curvatura Croma Pixel Melt Caiguda màxima Eines d\'IA Diverses eines per processar imatges mitjançant models d\'IA, com l\'eliminació d\'artefactes o la eliminació de sorolls Compressió, línies irregulars Dibuixos animats, compressió d\'emissió Compressió general, soroll general Soroll de dibuixos animats incolors Ràpid, compressió general, soroll general, animació/cómics/anime Escaneig de llibres Correcció de l\'exposició Millor en compressió general, imatges en color Millor en compressió general, imatges en escala de grisos Compressió general, imatges en escala de grisos, més forta Soroll general, imatges en color Soroll general, imatges en color, millors detalls Soroll general, imatges en escala de grisos Soroll general, imatges en escala de grisos, més fort Soroll general, imatges en escala de grisos, més fort Compressió general Compressió general Texturització, compressió h264 Compressió VHS Compressió no estàndard (cinepak, msvideo1, roq) Compressió Bink, millor en geometria Compressió Bink, més forta Compressió Bink, suau, conserva el detall Eliminació de l\'efecte de l\'escala, suavització Art/dibuixos escanejats, compressió suau, moiré Bandes de color Lenta, eliminant mitges tintes Coloritzador general per a imatges en escala de grisos/bw, per obtenir millors resultats utilitzeu DDColor Eliminació de vora Elimina el sobreafilat Lenta, distorsionada Anti-aliasing, artefactes generals, CGI Processament d\'exploracions KDM003 Model lleuger de millora de la imatge Eliminació d\'artefactes de compressió Eliminació d\'artefactes de compressió Eliminació de l\'embenat amb resultats suaus Processament de patrons de mitges tintes Eliminació del patró de tramado V3 Eliminació d\'artefactes JPEG V2 Millora de la textura H.264 Afilat i millora de VHS Fusió Mida del tros Mida de superposició Les imatges de més de %1$s px es tallaran i es processaran en trossos, la superposició les combina per evitar costures visibles. Les mides grans poden causar inestabilitat amb dispositius de gamma baixa Seleccioneu-ne un per començar Voleu suprimir el model %1$s? Haureu de descarregar-lo de nou Confirmeu Models Models descarregats Models disponibles Preparant Model actiu No s\'ha pogut obrir la sessió Només es poden importar models .onnx/.ort Model d\'importació Importeu el model onnx personalitzat per a un ús posterior, només s\'accepten models onnx/ort, admet gairebé totes les variants semblants a esrgan Models importats Soroll general, imatges de colors Soroll general, imatges de colors, més fort Soroll general, imatges de colors, més fort Redueix els artefactes de tramado i les bandes de color, millorant els degradats suaus i les zones de color planes. Millora la brillantor i el contrast de la imatge amb reflexos equilibrats alhora que preserva els colors naturals. Il·lumina les imatges fosques mantenint els detalls i evitant la sobreexposició. Elimina el to de color excessiu i restableix un equilibri de color més neutre i natural. Aplica tons de soroll basats en Poisson amb èmfasi en preservar els detalls i les textures. Aplica un suau to de soroll de Poisson per obtenir resultats visuals més suaus i menys agressius. Tonificació de soroll uniforme centrada en la preservació dels detalls i la claredat de la imatge. Tonificació de soroll uniforme suau per a una textura subtil i un aspecte suau. Repara les zones danyades o irregulars tornant a pintar els artefactes i millorar la consistència de la imatge. Model de desbandada lleuger que elimina les bandes de color amb un cost de rendiment mínim. Optimitza les imatges amb artefactes de compressió molt alts (0-20% de qualitat) per millorar la claredat. Millora les imatges amb artefactes de compressió alta (20-40% de qualitat), restaurant els detalls i reduint el soroll. Millora les imatges amb una compressió moderada (40-60% de qualitat), equilibrant la nitidesa i la suavitat. Perfecciona les imatges amb una compressió lleugera (60-80% de qualitat) per millorar els detalls i les textures subtils. Millora lleugerament les imatges gairebé sense pèrdues (qualitat del 80-100%) alhora que conserva l\'aspecte i els detalls naturals. Colorització senzilla i ràpida, dibuixos animats, no ideals Redueix lleugerament el desenfocament de la imatge, millorant la nitidesa sense introduir artefactes. Operacions de llarga durada Tractament de la imatge Tramitació Elimina els grans artefactes de compressió JPEG en imatges de molt baixa qualitat (0-20%). Redueix els forts artefactes JPEG en imatges altament comprimides (20-40%). Neteja els artefactes JPEG moderats alhora que conserva els detalls de la imatge (40-60%). Refina els artefactes JPEG lleugers en imatges d\'alta qualitat (60-80%). Redueix subtilment els artefactes JPEG menors en imatges gairebé sense pèrdues (80-100%). Millora els detalls i les textures fins, millorant la nitidesa percebuda sense artefactes pesats. Processament acabat Ha fallat el processament Millora les textures i els detalls de la pell mantenint un aspecte natural, optimitzat per a la velocitat. Elimina els artefactes de compressió JPEG i restaura la qualitat de la imatge de les fotos comprimides. Redueix el soroll ISO a les fotos fetes en condicions de poca llum, conservant els detalls. Corregeix els reflexos sobreexposats o \"jumbo\" i restableix un millor equilibri tonal. Model de coloració lleuger i ràpid que afegeix colors naturals a les imatges en escala de grisos. DEJPEG Denoise Acoloreix Artefactes Millora Anime Escaneigs De luxe X4 upscaler per a imatges generals; model petit que utilitza menys GPU i menys temps, amb un desenfocament i un soroll moderats. Escalador X2 per a imatges generals, conservant textures i detalls naturals. Escalador X4 per a imatges generals amb textures millorades i resultats realistes. X4 upscaler optimitzat per a imatges d\'anime; 6 blocs RRDB per a línies i detalls més nítids. L\'escalador X4 amb pèrdua de MSE, produeix resultats més suaus i artefactes reduïts per a imatges generals. X4 Upscaler optimitzat per a imatges d\'anime; Variant 4B32F amb detalls més nítids i línies suaus. Model X4 UltraSharp V2 per a imatges generals; emfatitza la nitidesa i la claredat. X4 UltraSharp V2 Lite; més ràpid i petit, conserva els detalls mentre utilitza menys memòria GPU. Model lleuger per eliminar ràpidament el fons. Rendiment i precisió equilibrats. Treballa amb retrats, objectes i escenes. Recomanat per a la majoria dels casos d\'ús. Eliminar BG Gruix de la vora horitzontal Gruix de la vora vertical %1$s color %1$s colors El model actual no admet la fragmentació, la imatge es processarà a les dimensions originals, això pot provocar un gran consum de memòria i problemes amb els dispositius de gamma baixa La fragmentació està desactivada, la imatge es processarà a les dimensions originals; això pot provocar un gran consum de memòria i problemes amb els dispositius de gamma baixa, però pot donar millors resultats en la inferència. En trossos Model de segmentació d\'imatges d\'alta precisió per eliminar fons Versió lleugera d\'U2Net per a una eliminació més ràpida de fons amb un ús de memòria més petit. El model DDColor complet ofereix una coloració d\'alta qualitat per a imatges generals amb un mínim d\'artefactes. La millor opció de tots els models de coloració. DDColor Conjunts de dades artístics entrenats i privats; produeix resultats de coloració diversos i artístics amb menys artefactes de color poc realistes. Model lleuger BiRefNet basat en Swin Transformer per eliminar amb precisió el fons. Eliminació de fons d\'alta qualitat amb vores nítides i excel·lent preservació dels detalls, especialment en objectes complexos i fons complicats. Model d\'eliminació de fons que produeix màscares precises amb vores llises, aptes per a objectes generals i conservació moderada dels detalls. Model ja descarregat El model s\'ha importat correctament Tipus Paraula clau Molt ràpid Normal Lenta Molt Lent Calcular percentatges El valor mínim és %1$s Distorsiona la imatge dibuixant amb els dits Deformació Duresa Mode Warp Mou-te Créixer Encongir-se Remolí CW Remolí cap a la dreta Força d\'esvaïment Top Drop Gota inferior Inicia Drop Finalitza la caiguda Descàrrega Formes llises Utilitzeu superel·lipses en lloc de rectangles arrodonits estàndard per obtenir formes més suaus i naturals Tipus de forma Tallar Arrodonit suau Vores afilades sense arrodoniments Clàssiques cantonades arrodonides Tipus de formes Mida de les cantonades Esquirol Elements d\'IU arrodonits elegants Format de nom de fitxer Text personalitzat situat al principi del nom del fitxer, perfecte per a noms de projectes, marques o etiquetes personals. Utilitza el nom del fitxer original sense extensió, ajudant-vos a mantenir intacta la identificació de la font. L\'amplada de la imatge en píxels, útil per fer un seguiment dels canvis de resolució o per escalar els resultats. L\'alçada de la imatge en píxels, útil quan es treballa amb relacions d\'aspecte o exportacions. Genera dígits aleatoris per garantir noms de fitxer únics; afegir més dígits per a més seguretat contra duplicats. Comptador d\'increment automàtic per a exportacions per lots, ideal per desar diverses imatges en una sessió. Insereix el nom predefinit aplicat al nom del fitxer perquè pugueu recordar fàcilment com es va processar la imatge. Mostra el mode d\'escala de la imatge utilitzat durant el processament, ajudant a distingir les imatges redimensionades, retallades o ajustades. Text personalitzat situat al final del nom del fitxer, útil per a versions com _v2, _edited o _final. L\'extensió del fitxer (png, jpg, webp, etc.), que coincideix automàticament amb el format desat real. Una marca de temps personalitzable que us permet definir el vostre propi format mitjançant l\'especificació de Java per a una ordenació perfecta. Tipus Fling Natiu d\'Android Estil iOS Corba suau Parada ràpida Rebot Flotant Snappy Ultra suau Adaptatiu Conscient de l\'accessibilitat Moviment reduït Física de desplaçament nativa d\'Android Desplaçament suau i equilibrat per a ús general Comportament de desplaçament semblant a iOS de major fricció Corba spline única per a una sensació de desplaçament diferent Desplaçament precís amb parada ràpida Desplaçament inflable lúdic i sensible Desplaçaments llargs i lliscants per a la navegació de contingut Desplaçament ràpid i sensible per a interfícies d\'usuari interactives Desplaçament suau premium amb impuls estès Ajusta la física en funció de la velocitat de llançament Respecta la configuració d\'accessibilitat del sistema Moviment mínim per a necessitats d\'accessibilitat Línies primàries Afegeix una línia més gruixuda cada cinquena línia Color de farciment Eines ocultes Eines amagades per compartir Biblioteca de colors Exploreu una àmplia col·lecció de colors Augmenta i elimina el desenfocament de les imatges mantenint els detalls naturals, ideal per arreglar fotos desenfocades. Restaura de manera intel·ligent les imatges que s\'han redimensionat prèviament, recuperant els detalls i les textures perduts. Optimitzat per a contingut d\'acció en directe, redueix els artefactes de compressió i millora els detalls detallats en fotogrames de pel·lícules o programes de televisió. Converteix metratges de qualitat VHS a HD, eliminant el soroll de la cinta i millorant la resolució alhora que conserva la sensació vintage. Especialitzat per a imatges i captures de pantalla amb un gran nombre de text, aguditza els caràcters i millora la llegibilitat. Escala avançada entrenada en diversos conjunts de dades, excel·lent per a la millora de fotografies d\'ús general. Optimitzat per a fotos comprimides a la web, elimina els artefactes JPEG i restaura l\'aspecte natural. Versió millorada per a fotos web amb una millor preservació de la textura i reducció d\'artefactes. Ampliació 2x amb tecnologia Dual Aggregation Transformer, manté la nitidesa i els detalls naturals. Ampliació 3x utilitzant una arquitectura de transformador avançada, ideal per a necessitats d\'ampliació moderades. L\'augment d\'alta qualitat 4x amb una xarxa de transformadors d\'última generació, conserva els detalls fins a escala més gran. Elimina el borrós/soroll i els tremolors de les fotos. Propòsit general però millor en fotos. Restaura imatges de baixa qualitat mitjançant el transformador Swin2SR, optimitzat per a la degradació de BSRGAN. Ideal per arreglar artefactes de compressió pesats i millorar els detalls a escala 4x. Ampliació 4x amb transformador SwinIR entrenat en degradació BSRGAN. Utilitza GAN per obtenir textures més nítides i detalls més naturals en fotos i escenes complexes. Camí Combina PDF Combina diversos fitxers PDF en un sol document Ordre de fitxers pp. PDF dividit Extreu pàgines específiques del document PDF Gira PDF Corregiu l\'orientació de la pàgina de manera permanent Pàgines Reorganitza el PDF Arrossegueu i deixeu anar les pàgines per reordenar-les Mantén premuda i arrossega les pàgines Números de pàgina Afegiu numeració als vostres documents automàticament Format de l\'etiqueta PDF a text (OCR) Extraieu text sense format dels vostres documents PDF Superposeu text personalitzat per a la marca o la seguretat Signatura Afegiu la vostra signatura electrònica a qualsevol document S\'utilitzarà com a signatura Desbloqueja PDF Elimineu les contrasenyes dels vostres fitxers protegits Protegeix PDF Protegiu els vostres documents amb un xifratge fort Èxit PDF desbloquejat, podeu desar-lo o compartir-lo Reparar PDF Intenteu arreglar documents danyats o il·legibles Escala de grisos Converteix totes les imatges incrustades del document a escala de grisos Comprimir PDF Optimitzeu la mida del fitxer del document per compartir-lo més fàcilment ImageToolbox reconstrueix la taula interna de referències creuades i regenera l\'estructura de fitxers des de zero. Això pot restaurar l\'accés a molts fitxers que \\"no es poden obrir\\" Aquesta eina converteix totes les imatges del document a escala de grisos. El millor per imprimir i reduir la mida del fitxer Metadades Editeu les propietats del document per a una millor privadesa Etiquetes Productor Autor Paraules clau Creador Neteja profunda de privadesa Esborra totes les metadades disponibles per a aquest document Pàgina OCR profund Extraieu text del document i emmagatzemeu-lo en un fitxer de text mitjançant el motor Tesseract No es poden eliminar totes les pàgines Elimina les pàgines PDF Elimina pàgines específiques del document PDF Toqueu Per eliminar Manualment Retalla PDF Retalla les pàgines del document fins a qualsevol límit Aplanar PDF Feu que els PDF no siguin modificables rasteritzant les pàgines del document No s\'ha pogut iniciar la càmera. Comproveu els permisos i assegureu-vos que no l\'utilitzi una altra aplicació. Extreu Imatges Extraieu imatges incrustades en PDF amb la seva resolució original Aquest fitxer PDF no conté cap imatge incrustada Aquesta eina escaneja totes les pàgines i recupera imatges d\'origen de qualitat completa, perfecte per desar originals dels documents Dibuixa la signatura Params de ploma Utilitzeu la pròpia signatura com a imatge per col·locar als documents Zip PDF Dividiu el document amb un interval donat i empaqueteu nous documents a l\'arxiu zip Interval Imprimeix PDF Prepareu el document per imprimir amb una mida de pàgina personalitzada Pàgines per full Orientació Mida de la pàgina Marge Floreix Genoll suau Optimitzat per a anime i dibuixos animats. Ampliació ràpida amb colors naturals millorats i menys artefactes Samsung One UI 7 estil semblant Introduïu aquí símbols matemàtics bàsics per calcular el valor desitjat (p. ex. (5+5)*10) Expressió matemàtica Recolliu fins a %1$s imatges Manteniu la data i l\'hora Preserveu sempre les etiquetes exif relacionades amb la data i l\'hora, funciona independentment de l\'opció de mantenir l\'exif Color de fons per a formats alfa Afegeix la possibilitat d\'establir el color de fons per a cada format d\'imatge amb suport alfa, quan està desactivat, només està disponible per als que no són alfa Projecte obert Continueu editant un projecte de Image Toolbox desat anteriorment No es pot obrir el projecte Image Toolbox Falten dades del projecte al projecte Image Toolbox El projecte Image Toolbox està malmès Versió del projecte Image Toolbox no compatible: %1$d Guarda el projecte Emmagatzema capes, fons i historial d\'edició en un fitxer de projecte editable No s\'ha pogut obrir Escriu en PDF cercable Reconeix el text del lot d\'imatges i desa PDF cercable amb imatge i capa de text seleccionable Capa alfa Flip horitzontal Flip vertical Bloqueig Afegeix ombra Color de l\'ombra Geometria del text Estira o inclina el text per a una estilització més nítida Escala X Esbiaix X Elimina les anotacions Elimina els tipus d\'anotacions seleccionats, com ara enllaços, comentaris, ressaltats, formes o camps de formulari de les pàgines PDF Hiperenllaços Fitxers adjunts Línies Popups Segells Formes Notes de text Marcatge de text Camps de formulari Marcatge Desconegut Anotacions Desagrupar Afegeix ombra borrosa darrere de la capa amb color i desplaçaments configurables ================================================ FILE: core/resources/src/main/res/values-cs/strings.xml ================================================ Předpona Něco se pokazilo: %1$s Velikost %1$s Načítání… Obrázek je pro náhled příliš velký, ale přesto se ho pokusíme uložit. Začněte výběrem obrázku Šířka %1$s Výška %1$s Kvalita Rozšíření Způsob změny velikosti Explicitní Flexibilní Vybrat obrázek Opravdu chcete aplikaci zavřít? Zavření aplikace Zůstat Zavřít Vrátit změny v obrázku Úpravy obrázku budou vráceny na počáteční hodnoty Hodnoty úspěšně obnoveny Vrátit změny Něco se pokazilo Restart aplikace Zkopírováno do schránky Výjimka Upravit EXIF Ok Nebyla nalezena žádná EXIF data Přidat štítek Uložit Vymazat Vymazat EXIF data Zrušit Všechna EXIF data obrázku budou vymazána, tuto akci nelze vrátit zpět! Předvolby Ořezat Ukládání Pokud nyní odejdete, všechny neuložené změny budou ztraceny. Zdrojový kód Získejte nejnovější aktualizace, diskutujte o problémech a další informace Jednotlivá změna velikosti Změna specifikací jednoho zadaného obrázku Vyberte barvu Výběr barvy z obrázku, kopírování nebo sdílení Obrázek Barva Barva zkopírována Oříznutí obrázku do libovolných mezí Verze Ponechat EXIF data Obrázky: %d Náhled úprav Odebrat Vytvořit paletu barev ze zadaného obrázku Vytvoření palety Paleta Obnovit Nová verze %1$s Nepodporovaný typ: %1$s Nelze vytvořit paletu pro daný obrázek Originál Výstupní složka Výchozí Vlastní Nespecifikováno Úložiště zařízení Upravit velikost Maximální velikost v KB Upravit velikost obrázku podle zadané velikosti v KB Porovnat Porovnání dvou zadaných obrázků Začněte výběrem dvou obrázků Vyberte obrázky Nastavení Noční režim Tmavý Světlý Systém Dynamické barvy Přizpůsobení Povolit zpeněžení obrázku Pokud je tato možnost povolena, při výběru obrázku k úpravě se barvy aplikace přizpůsobí tomuto obrázku. Jazyk Režim Amoled Pokud je povoleno, barva povrchů bude v nočním režimu nastavena na absolutně tmavou. Barevné schéma Červená Zelená Modrá Vložte platný kód aRGB. Nic k vložení Nelze změnit barevné schéma aplikace, když jsou zapnuté dynamické barvy Motiv aplikace bude založen na barvě, kterou si vyberete. O aplikaci Nebyly nalezeny žádné aktualizace Sledování problémů Pošlete sem hlášení o chybách a požadavky na funkce Nápověda k překladu Oprava chyb v překladu nebo lokalizace projektu do jiných jazyků Na základě vašeho dotazu nebylo nic nalezeno Hledejte zde Pokud je tato možnost povolena, barvy aplikace se přizpůsobí barvám tapety. Nepodařilo se uložit obrázek (obrázky) %d Primární Terciární Sekundární Tloušťka hranic Povrch Hodnoty Přidat Povolení Grant Aplikace potřebuje přístup k vašemu úložišti, aby mohla ukládat obrázky, je to nezbytné, bez toho nemůže fungovat, takže prosím udělte povolení v dalším dialogu. Aplikace potřebuje toto oprávnění ke své činnosti, udělte ji prosím ručně. Externí úložiště Monetovy barvy Tato aplikace je zcela zdarma, ale pokud chcete podpořit vývoj projektu, můžete kliknout zde. Vyrovnání FAB Zkontrolujte aktualizace Pokud je povoleno, zobrazí se po spuštění aplikace dialogové okno pro aktualizaci. Zvětšení obrazu Sdílet Název souboru Jas Sklon Ostřete Sépie Černý a bílý Vzdálenost Šířka čáry Hrana Sobel Konec Měřítko Poloměr Emoji Přidejte velikost souboru Načíst obrázek z internetu Vejít se Filtry Vystavení Vyvážení bílé Změna velikosti limitů Skica Práh Kvantizační úrovně Hladký toon Toon Ne maximální potlačení Slabé zahrnutí pixelů Vzhlédnout Práh svítivosti Vyberte, které emotikony se zobrazí na hlavní obrazovce Pokud je povoleno, přidá šířku a výšku uloženého obrázku k názvu výstupního souboru Odstraňte metadata EXIF z libovolného páru obrázků Náhled obrázku Průzkumník souborů Moderní nástroj pro výběr fotografií Android, který se zobrazuje ve spodní části obrazovky, může fungovat pouze na Androidu 12+ a má také problémy s přijímáním metadat EXIF Jednoduchý výběr obrázků galerie, bude fungovat, pouze pokud máte tuto aplikaci Použijte záměr GetContent k výběru obrázku, funguje všude, ale také může mít problémy s přijímáním vybraných obrázků na některých zařízeních, to není moje chyba Uspořádání možností Upravit Pořadí Určuje pořadí nástrojů na hlavní obrazovce Nahraďte pořadové číslo Pokud je povoleno, nahradí standardní časové razítko pořadovým číslem obrázku, pokud používáte dávkové zpracování Načtěte jakýkoli obrázek z internetu, zobrazte jeho náhled, přibližujte jej a také jej uložte nebo upravte, pokud chcete Bez obrázku Odkaz na obrázek Vyplnit Změní velikost obrázků na danou výšku a šířku. Poměr stran obrázků se může změnit. Změní velikost obrázků na obrázky s dlouhou stranou danou parametrem Width nebo Height, všechny výpočty velikosti budou provedeny po uložení - zachová poměr stran Kontrast Odstín Nasycení Přidat filtr Filtr Na dané obrázky použijte libovolný řetězec filtrů Světlo Barevný filtr Alfa Teplota Nádech Černobílý Gamma Světla a stíny Zvýraznění Stíny Opar Účinek Vzdálenost Negativní Solarizovat Vibrace Křížový šraf Rozmazat Půltón barevný prostor GCA Gaussovské rozostření Box rozostření Dvoustranné rozostření Vytepat laplacký Viněta Start Kuwahara vyhlazování Zkreslení Úhel Vířit se Boule Dilatace Lom koule Index lomu Lom skleněné koule Barevná matrice Neprůhlednost Změňte velikost daných obrázků tak, aby odpovídaly daným limitům šířky a výšky s uložením poměru stran Posterizovat Rozostření zásobníku Konvoluce 3x3 RGB filtr Falešná barva První barva Druhá barva Seřadit Rychlé rozmazání Velikost rozostření Rozmazat střed x Rozostření středu y Zoom rozostření Vyvážení barev Smazat EXIF Náhled libovolného typu obrázků: GIF, SVG a tak dále Zdroj obrázku Výběr fotografií Galerie Obsahové měřítko Počet emodži Pokud je povoleno, přidá původní název souboru do názvu výstupního obrazu sekvenceNum původní název souboru Přidejte původní název souboru Přidání původního názvu souboru nefunguje, pokud je vybrán zdroj obrázku pro výběr fotografií Zakázali jste aplikaci Soubory, aktivujte ji, abyste mohli tuto funkci používat Malování alfa Nakreslete na obrázek Kreslit Nakreslete na obrázek jako ve skicáku nebo nakreslete na samotné pozadí Vyberte si obrázek a něco na něj nakreslete Kreslit na pozadí Šifrování Klíč Uložte tento soubor ve svém zařízení nebo jej pomocí akce sdílení umístěte kamkoli chcete Implementace Kompatibilita Šifrování souborů na základě hesla. Provedené soubory mohou být uloženy ve vybraném adresáři nebo sdíleny. Dešifrované soubory lze také otevřít přímo. Maximální velikost souboru je omezena operačním systémem Android a dostupnou pamětí, což samozřejmě závisí na vašem zařízení. \nPoznámka: paměť není úložiště. Vytvořit Upravit snímek obrazovky Sekundární přizpůsobení Soubor zpracován Neplatné heslo nebo vybraný soubor není zašifrován Upozorňujeme, že kompatibilita s jiným softwarem nebo službami pro šifrování souborů není zaručena. Důvodem nekompatibility může být mírně odlišné zpracování klíče nebo konfigurace šifry. Snímek obrazovky Záložní možnost Přeskočit Dešifrování kopírovat Vyberte barvu pozadí a nakreslete na ni Barva pozadí Začněte výběrem souboru Funkce Ukládání v režimu %1$s může být nestabilní, protože se jedná o bezztrátový formát Barva laku Šifra Šifrujte a dešifrujte jakýkoli soubor (nejen obrázek) na základě šifrovacího algoritmu AES Vybrat soubor Šifrovat Dešifrovat AES-256, režim GCM, bez výplně, 12 bajtů náhodných IV. Klíče se používají jako hash SHA-3 (256 bitů). Velikost souboru Pokus o uložení obrázku s danou šířkou a výškou může způsobit chybu OOM, dělejte to na vlastní riziko a neříkejte, že jsem vás nevaroval! Mezipaměti Velikost mezipaměti Nalezeno %1$s Automatické vymazání mezipaměti Pokud je povoleno, mezipaměť aplikace se vymaže při spuštění aplikace Nástroje Seskupit možnosti podle typu Seskupí možnosti na hlavní obrazovce svého typu namísto vlastního uspořádání seznamu Nelze změnit uspořádání, když je povoleno seskupování možností Uloženo do složky %1$s Uloženo do složky %1$s pod názvem %2$s Vylepšená pixelace Plodinová maska Vylepšený Glitch Posun kanálu X Posun kanálu Y Velikost korupce Korupce Shift X Korupční posun Y Rozostření stanu Side Fade Boční Horní Dno Automatické vymazání pozadí Randomizujte název souboru Cesty a místa Povolit beta verze Orientace & Pouze detekce skriptu Automatická orientace & Detekce skriptu Pouze auto Auto Jediné slovo Zakroužkujte slovo Závada Množství Semínko Chystáte se smazat vybranou masku filtru. Tuto operaci nelze vrátit zpět Smazat masku Nebyl nalezen žádný adresář \"%1$s\", přepnuli jsme jej na výchozí, uložte prosím soubor znovu Schránka Automatický pin Automaticky přidá uložený obrázek do schránky, pokud je povoleno Vibrace Síla vibrací Přepsat soubory Prázdný Přípona Vyhledávání Volný, uvolnit Obrázky byly přepsány v původním umístění Pokud je povoleno, výstupní název souboru bude zcela náhodný Tento typ masky použijte k vytvoření masky z daného obrázku, všimněte si, že BY MĚL mít alfa kanál Obnovit Obnovte nastavení aplikace z dříve vygenerovaného souboru Poškozený soubor nebo není záloha Kontaktujte mě Tím se vrátí vaše nastavení na výchozí hodnoty. Všimněte si, že to nelze vrátit zpět bez výše uvedeného záložního souboru. Vymazat Chystáte se smazat vybrané barevné schéma. Tuto operaci nelze vrátit zpět Smazat schéma Písmo Text Měřítko písma Výchozí Použití velkých měřítek písem může způsobit závady a problémy uživatelského rozhraní, které nebudou opraveny. Používejte opatrně. Aa Áá Bb Cc Čč Dd Ďď Ee Éé Ěě Ff Gg Hh Ii Íí Jj Kk Ll Mm Nn Ňň Oo Óó Pp Qq Rr Řř Ss Šš Tt Ťť Uu Úú Ůů Vv Ww Xx Yy Zz Žž 0123456789 !? Emoce Jídlo a pití Příroda a zvířata Objekty Symboly Povolit emotikony Činnosti Odstraňovač pozadí Odstraňte pozadí z obrázku kreslením nebo použijte možnost Auto Oříznout obrázek Původní metadata obrázku budou zachována Obnovit pozadí Jejda… Něco se pokazilo. Můžete mi napsat pomocí níže uvedených možností a já se pokusím najít řešení Změňte velikost daných obrázků nebo je převeďte do jiných formátů. Metadata EXIF lze zde také upravit, pokud vyberete jeden obrázek. Obrázky budou oříznuty na střed na zadanou velikost. Pokud je obrázek menší než zadané rozměry, plátno se roztáhne o danou barvu pozadí. Pixelace tahu Barva k odstranění Odebrat barvu Paletový styl Tonální skvrna Neutrální Vibrující Expresivní Duha Ovocný salát Věrnost Obsah Výchozí styl palety, umožňuje přizpůsobit všechny čtyři barvy, ostatní umožňují nastavit pouze klíčovou barvu Styl, který je o něco více chromatičtější než monochromatický Hlasité téma, barevnost je maximální pro primární paletu, zvýšená pro ostatní Hravý motiv – v motivu se neobjevuje zdrojový barevný odstín Jednobarevné téma, barvy jsou čistě černá / bílá / šedá Schéma, které umístí zdrojovou barvu do Scheme.primaryContainer Schéma, které je velmi podobné schématu obsahu Umožňuje prohledávat všechny dostupné možnosti na hlavní obrazovce Náhled PDF Jednoduchý náhled PDF Převeďte PDF na obrázky v daném výstupním formátu Zabalte dané obrázky do výstupního souboru PDF Masky Přidejte masku Maska %d Barva masky Náhled masky Jednoduché varianty Zvýrazňovač Neon Pero Rozmazání soukromí Přidejte do svých kreseb nějaký zářící efekt Výchozí, nejjednodušší - jen barva Podobné rozostření soukromí, ale místo rozmazání pixeluje Automatické otáčení Umožňuje použití limitního rámečku pro orientaci obrázku Nakreslí cestu z počátečního bodu do koncového bodu jako čáru Čára Nakreslí cestu jako vstupní hodnotu Nakreslí směřující šipku z počátečního bodu do koncového bodu jako čáru Nakreslí ukazující šipku z dané cesty Nakreslí dvojitou šipku z počátečního bodu do koncového bodu jako čáru Nakreslí dvojitou šipku z dané cesty Nastínil Ovál Obrys Rect Nakreslí obrysový obdélník od počátečního bodu do koncového bodu Chcete-li přepsat soubory, musíte použít zdroj obrázků \"Explorer\", zkuste obrázky znovu vybrat, zdroj obrázků jsme změnili na potřebný Původní soubor bude nahrazen novým souborem namísto uložení do vybrané složky, tato možnost musí být zdrojem obrázku \"Explorer\" nebo GetContent, při přepnutí se nastaví automaticky Funkce okna se často používá při zpracování signálu, aby se minimalizoval spektrální únik a zlepšila přesnost frekvenční analýzy zúžením okrajů signálu Technika matematické interpolace, která využívá hodnoty a derivace na koncových bodech segmentu křivky k vytvoření hladké a spojité křivky Metoda převzorkování, která zachovává vysoce kvalitní interpolaci aplikací vážené funkce sinc na hodnoty pixelů Metoda převzorkování, která využívá konvoluční filtr s nastavitelnými parametry pro dosažení rovnováhy mezi ostrostí a vyhlazováním v zmenšeném obrázku Využívá po částech definované polynomiální funkce k hladké interpolaci a aproximaci křivky nebo povrchu, flexibilní a spojité reprezentace tvaru OCR (rozpoznat text) Rozpoznejte text z daného obrázku, podporováno více než 120 jazyků Obrázek neobsahuje žádný text nebo jej aplikace nenašla Přesnost: %1$s Typ rozpoznávání Rychle Dostupné jazyky Tato aplikace je zcela zdarma, pokud ji chcete zvětšit, označte prosím projekt hvězdičkou na Github 😄 Jeden sloupec Jednoblokový svislý text Jediný blok Jediný řádek Jediný znak Řídký text Orientace řídkého textu & Detekce skriptu Syrová čára Aktuální Všechno Vytvořte přechod dané výstupní velikosti s přizpůsobenými barvami a typem vzhledu Přidat barvu Vlastnosti Opakuje vodoznak přes obrázek místo jednoho na dané pozici Offset Y Typ vodoznaku Tento obrázek bude použit jako vzor pro vodoznak Barva textu Frame Delay milis FPS Nativní rozostření zásobníku Jarvis Judice Ninke Dithering Sierra Lite Dithering Dithering zleva doprava Náhodné dithering Jednoduché prahové dithering Změna sklonu Síla Rychlé dvoustranné rozmazání Olej Frekvence X Frekvence Y Amplituda X Color Matrix 3x3 Jednoduché efekty Polaroid Tritonomálie Deutaromálie Protonomálie Browni Coda Chrome Deutaronotopie Protanopie Achromatomálie Achromatopsie Barevná spirála Měkké jarní světlo Podzimní tóny Levandulový sen Cyberpunk Limonádové světlo Vesmírný portál Spektrální oheň Karamelová tma Futuristický přechod Deep Purple Červená spirála Digitální kód Drago Aldridge Odříznout Učimura Mobius Přechod Vrchol Barevná anomálie Nelze změnit formát obrázku, pokud je povolena možnost přepisování souborů Emoji jako barevné schéma Používá primární barvu emodži jako barevné schéma aplikace namísto ručně definovaného Telegramový chat Diskutujte o aplikaci a získejte zpětnou vazbu od ostatních uživatelů. Můžete zde také získat aktualizace beta verze a statistiky. Zálohování a obnovení Nastavení byla úspěšně obnovena Poloměr rozostření Režim kreslení Kreslit šipky Vylepšená diamantová pixelace Tolerance Překódovat Erodovat Anizotropní difúze Difúze Vedení Horizontální Wind Stagger ACES Filmic Tone Mapping Mapování horských tónů ACES Poissonovo rozostření Logaritmické mapování tónů Krystalizovat Barva tahu Fraktální sklo Amplituda Mramor Turbulence Vodní efekt Velikost Amplituda Y Perlinovo zkreslení Hable filmové mapování tónů Hejl Burgess Tone Mapping Chcete smazat jazyková \"%1$s\" trénovací data OCR pro všechny typy rozpoznávání, nebo pouze pro vybraný (%2$s)? Plný filtr Konec Centrum Aplikujte libovolné řetězy filtrů na dané obrázky nebo jeden obrázek Práce se soubory PDF: Náhled, převod na dávku obrázků nebo vytvoření jednoho z daných obrázků PDF do obrázků Obrázky do PDF Tvůrce přechodů Rychlost Dehaze Omega Nástroje PDF Ohodnoťte aplikaci Hodnotit Color Matrix 4x4 Vinobraní Noční vidění Teplý Chladný Tritanopie Lineární Radiální Zametat Typ přechodu Střed X Střed Y Režim dlaždic Opakované Zrcadlo Svorka Obtisk Barva se zastaví E-mailem Režim kreslení cesty Šipka dvojité čáry Kreslení zdarma Dvojitá šipka Šipka čáry Šipka Ovál Rect Kreslí obdélník od počátečního bodu do koncového bodu Kreslí ovál od počátečního bodu ke koncovému bodu Nakreslí obrysový ovál od počátečního bodu ke koncovému bodu Dithering Quantizier Stupnice šedé Bayer Two By Two Dithering Bayer Three By Three Dithering Bayer Four By Four Dithering Bayer Eight By Eight Dithering Floyd Steinberg Dithering Sierra Dithering Dvě řady Sierra Dithering Atkinsonův rozklad Stucki Dithering Burkes Dithering Falešný Floyd Steinberg Dithering Maximální počet barev To aplikaci umožňuje ručně shromažďovat zprávy o selhání Nakreslená maska filtru bude vykreslena, aby vám ukázala přibližný výsledek Režim měřítka Bilineární Hann Hermite Lanczos Mitchell Nejbližší Spline Základní Výchozí hodnota Hodnota v rozsahu %1$s – %2$s Sigma Prostorová Sigma Střední rozostření Catmull Bikubický Lineární (nebo bilineární, ve dvou rozměrech) interpolace je obvykle dobrá pro změnu velikosti obrázku, ale způsobuje určité nežádoucí změkčení detailů a může být stále poněkud zubatá. Mezi lepší metody škálování patří Lanczosovo převzorkování a Mitchell-Netravaliho filtry Jeden z jednodušších způsobů zvětšení velikosti, nahrazení každého pixelu určitým počtem pixelů stejné barvy Nejjednodušší režim škálování pro Android, který se používá téměř ve všech aplikacích Metoda pro hladkou interpolaci a převzorkování sady řídicích bodů, běžně používaná v počítačové grafice k vytvoření hladkých křivek Pouze klip Ukládání do úložiště se neprovede a obrázek se pokusí vložit pouze do schránky Přidá kontejner s vybraným tvarem pod úvodní ikony karet Tvar ikony Sešívání obrázku Spojením uvedených obrázků získáte jeden velký Vynucení jasu Obrazovka Překrytí přechodem Vytvořte libovolný přechod horní části daného obrázku Proměny Fotoaparát K pořízení snímku používá fotoaparát, všimněte si, že z tohoto zdroje obrázků je možné získat pouze jeden snímek Vyberte alespoň 2 obrázky Výstupní měřítko obrazu Orientace obrazu Horizontální Vertikální Měřítko malých obrázků na velké Pokud je tato možnost povolena, malé obrázky budou zmenšeny na největší v sekvenci Pořadí obrázků Obilí Neostrý Pastel Orange Haze Růžový sen Zlatá hodina Horké léto Fialová mlha svítání Noční magie Fantasy Krajina Barevná exploze Elektrický gradient Zelené slunce Duhový svět Vodoznak Zakryjte obrázky přizpůsobitelnými textovými/obrázkovými vodoznaky Opakujte vodoznak Odsazení X Režim překrytí Velikost pixelů Zamknout orientaci kreslení Pokud je povoleno v režimu kreslení, obrazovka se neotáčí bokeh Nástroje GIF Převeďte obrázky na obrázek GIF nebo extrahujte snímky z daného obrázku GIF GIF k obrázkům Převeďte soubor GIF na dávku obrázků Převeďte dávku obrázků do souboru GIF Obrázky do GIF Začněte výběrem obrázku GIF Použijte velikost prvního snímku Nahraďte určenou velikost rozměry prvního rámu Počet opakování Použijte laso K mazání používá laso jako v režimu kreslení Náhled původního obrázku Alpha Filtr masky Aplikujte řetězce filtrů na dané maskované oblasti, každá oblast masky si může určit vlastní sadu filtrů Emoji lišty aplikací se budou průběžně měnit náhodně namísto použití vybraného Náhodné emotikony Nelze použít náhodný výběr emodži, když jsou emodži vypnuté Nelze vybrat emotikon, když je aktivován náhodný výběr Kontrola aktualizací Počkejte Snaha Hodnota %1$s znamená rychlou kompresi, která má za následek relativně velkou velikost souboru. %2$s znamená pomalejší kompresi, výsledkem je menší soubor. Ukládání téměř dokončeno. Zrušení nyní bude vyžadovat opětovné uložení. Předvolba zde určuje % výstupního souboru, tj. pokud zvolíte předvolbu 50 na 5mb obrázku, získáte po uložení obrázek 2,5mb Pokud jste vybrali předvolbu 125, obrázek se uloží jako velikost 125 % původního obrázku se 100% kvalitou. Pokud zvolíte předvolbu 50, obrázek se uloží s 50% velikostí a 50% kvalitou. Poměr stran Záloha Zálohujte nastavení aplikace do souboru Průhledné prostory kolem obrázku budou oříznuty Obnovit obrázek Režim mazání Vymazat pozadí Pipeta Vytvořit vydání Změnit velikost a převést Analytics Povolit shromažďování anonymních statistik používání aplikací V současné době formát %1$s umožňuje pouze čtení metadat EXIF v systému Android. Výstupní obrázek po uložení nebude mít vůbec metadata. Aktualizace Kontrola aktualizací bude zahrnovat beta verze aplikace, pokud je povolena Pokud je povoleno, cesta výkresu bude znázorněna jako ukazující šipka Měkkost štětce Start Stará televize Náhodné rozmazání Standard Nejlepší Žádná data Pro správné fungování Tesseract OCR je třeba do vašeho zařízení stáhnout další tréninková data (%1$s). \nChcete stáhnout %2$s data? Stažení Žádné připojení, zkontrolujte jej a zkuste to znovu, abyste si mohli stáhnout modely vlaků Stažené jazyky Režim segmentace Štětec místo mazání obnoví pozadí Horizontální mřížka Vertikální mřížka Stitch Mode Počet řádků Počet sloupců Použijte Pixel Switch Pixel-like switch bude použit místo google materiálu, který jste založili Skluzavka Bok po boku Přepnout klepnutím Průhlednost Přepsaný soubor s názvem %1$s v původním umístění Lupa Povolí lupu v horní části prstu v režimech kreslení pro lepší přístupnost Vynutit počáteční hodnotu Vynutí počáteční kontrolu widgetu exif Povolit více jazyků Oblíbený Zatím nebyly přidány žádné oblíbené filtry B Spline Využívá po částech definované bikubické polynomické funkce k hladké interpolaci a aproximaci křivky nebo povrchu, flexibilní a spojité reprezentace tvaru Pravidelný Rozostření okrajů Nakreslí rozmazané okraje pod původní obrázek, aby se vyplnily mezery kolem něj místo jedné barvy, pokud je povoleno Pixelace Inverzní typ výplně Je-li povoleno, budou namísto výchozího chování filtrovány všechny nemaskované oblasti Konfety Konfety se zobrazí při ukládání, sdílení a dalších primárních akcích Zabezpečený režim Skryje obsah při ukončení, obrazovku také nelze zachytit nebo zaznamenat Dar Diamantová pixelace Kruhová pixelace Vylepšená kruhová pixelace Vyměnit barvu Barva k výměně Cílová barva Nakreslete poloprůhledné zaostřené dráhy zvýrazňovače Rozmaže obraz pod nakreslenou cestou, aby zajistil vše, co chcete skrýt Kontejnery Umožňuje kreslení stínů za kontejnery Posuvníky Přepínače FAB Tlačítka Umožňuje kreslení stínů za posuvníky Umožňuje kreslení stínů za přepínači Umožňuje kreslení stínů za plovoucími akčními tlačítky Povolí kreslení stínů za výchozími tlačítky Lišty aplikací Umožňuje kreslení stínů za lištami aplikace Tato kontrola aktualizací se připojí ke GitHubu z důvodu kontroly, zda je k dispozici nová aktualizace Pozornost Slábnoucí hrany Zakázáno Oba Invertovat barvy Nahradí barvy motivu negativními, pokud je povoleno Odejít Pokud nyní náhled opustíte, budete muset obrázky přidat znovu Laso Nakreslí uzavřenou vyplněnou cestu danou cestou Formát obrázku Anaglyf Hluk Pixel Sort Zamíchat Vytvoří paletu\" Material You \" z obrázku Tmavé Barvy Používá barevné schéma nočního režimu místo světelné varianty Kopírovat jako Jetpack Compose code Rozostření Kruhu Hvězdné Rozostření Lineární Posun Náklonu Rozostření Prstenu Křížové Rozostření 削除するタグ Nástroje APNG Převeďte obrázky na obrázek APNG nebo extrahujte snímky z daného obrázku APNG APNG k obrázkům Převeďte soubor APNG na dávku obrázků Převeďte dávku obrázků do souboru APNG Obrázky do APNG Začněte výběrem obrázku APNG Rozostření pohybu Zip Vytvořte soubor Zip z daných souborů nebo obrázků Šířka táhla Déšť Bezztrátový převod z formátu JXL do JPEG Proveď bezztrátový převod z formátu JPEG do JXL Začněte výběrem souboru JXL JXL na JPEG Nástroje JXL Rohy JPEG na JXL Obrázky na JXL Převod formátu obrázku JXL ~ JPEG bez ztráty kvality nebo převod obrázku GIF/APNG na animaci JXL Druh komprese Řazení Řadit podle data (pozpátku) Dnes Včera Automatické vložení Dovolí aplikaci automaticky vložit data, takže se zobrazí na hlavní obrazovce a bude je možno zpracovat GIF na JXL Převod obrázků GIF na animované obrázky JXL APNG na JXL Převod obrázků APNG na animované obrázky JXL JXL na obrázky Převod animace JXL na sadu obrázků Převod sady obrázků na animaci JXL Chování Přeskočit výběr obrázku Výběr souborů bude zobrazen okamžitě, pokud je to na vybrané obrazovce možné Vytvářet náhledy Povolí vytváření náhledů, toto může na některých zařízeních pomoci předejít pádům a také zakáže některé funkce úprav v rámci jednotlivé možnosti editace. Ztrátová komprese Používat ztrátovou kompresi ke snížení velikosti místo bezztrátové Řadit podle data Řadit podle názvu Řadit podle názvu (obráceně) Žádná oprávnění Požadavek Vybrat více médií Vybrat Zkusit znovu Zobrazit nastavení na šířku Pokud je toto vypnuto, pak budou v režimu na šířku nastavení jako obvykle otevíraná v tlačítku na horní liště aplikace místo trvale zobrazené volby Vykreslení vybraných obrázků do SVG Použití tohoto nástroje pro vykreslování velkých obrázku bez snížení velikosti není doporučeno, může způsobit pád a prodloužit čas zpracovávání Možnosti zobrazení na celou obrazovku Max Obrázky do SVG Vynechat cestu Snížit velikost obrázku Vybrat jeden mediální soubor Komprese Autor Vytvořit novou složku Rozlišení X Rozlišení Y Jednotka rozlišení Bílý bod Popis obrázku Model Software Autorská práva Verze Exif Verze Flashpix Uživatelský komentář Související zvukový soubor Pixel Konfigurace kanálů Vestavěný výběr Využívá vlastní výběr z aplikace Image Toolbox namísto předefinovaného ze systému. Starší Síť LSTM Starší a LSTM Harmonizační barva Úroveň harmonizace Konvertovat Určuje výslednou rychlost dekódování obrázku, tato funkce může napomoci k rychlejšímu otevření obrázku, hodnota %1$s znamená pomalejší dekódování, kdežto %2$s rychlejší, zároveň může zvětšit velikost výstupního obrázku. Obnovit výchozí hodnoty nastavení Všechna nastavení budou obnovena na výchozí hodnoty, uvědomte si, že tato akce nemůže být vrácena zpět Přizpůsobit hranicím Zálohovat OCR modely Importovat Exportovat Pozice Uprostřed Nahoře vlevo Nahoře vpravo Dole vlevo Dole vpravo Nahoře uprostřed Uprostřed vpravo Dole uprostřed Cílový obrázek Nástroje barev Míchejte, vytvářejte tóny, generujte odstíny a další Barevné harmonie Není dostupný plný přístup k souborům Přidat časové razítko Povolí vložení časového razítka do názvu výstupního souboru Výchozí barva kreslení Výchozí mód vykreslení cesty Formátované časové razítko Povolit časové razítko v názvu výrobního souboru místo základních milisekund Bitů na vzorek Fotometrická interpretace Vzorků na pixel Y Cb Cr pozicování Posledně použité Skupina Histogram Vytvořte různé koláže ze 20 obrázků Podržením obrázku jej můžete vyměnit, posunem a zvětšením upravit polohu Jako soubor Uložit jako soubor Převézt obrázky na animovaný obrázek WEBP nebo extrahovat snímky z dané animace WEBP WEBP na obrázky Varianty Tóny Nádech Skener dokumentů Klikněte pro spuštění skenování Skenovat Uložit jako PDF Sdílet jako PDF Volby níže jsou pro ukládání obrázků, nikoliv pro PDF Skenovat dokumenty a vytvořit PDF soubor nebo je rozdělit do obrázků Druh prepínače Výchozí šířka čáry Rozdělení obrázku Rozdělit jeden obrázek na části podle řádků nebo sloupců Rychlé 2D gaussovské rozostření Rychlé 3D gaussovské rozostření Rychlé 4D gaussovské rozostření Fluent Využívá přepínač ve stylu Windows 11 založený na systémovém vzhledu \"Fluent\" Cupertino Náhled odkazů Povolit náhled odkazů na místech, kde můžete získat text ( QR kód, OCR apod.) Převod formátu Dávkový převod obrázků z jednoho formátu do jiného Změna velikosti ukotvení Využívá přepínač ve stylu iOS založený na systémovém vzhledu Cupertino. Před spuštěním úprav bude sníženo rozlišení obrázku, aby nástroj mohl pracovat rychleji a bezpečněji. Podrobný Převod dávek obrázků do daného formátu Uspořádání nástrojů Seskupit nástroje podle typu Seskupit nástroje na hlavní obrazovce podle jejich typu namísto vlastního uspořádání seznamu Výchozí hodnoty Viditelnost systémových panelů Zobrazit systémové panely potažením Povolit potažení pro zobrazení systémových panelů, pokud jsou právě skryté. Automaticky Skrýt všechny Zobrazit všechny Skrýt navigační panel Skrýt stavový panel Generování šumu Generovat různé šumy jako Perlinův šum a jiné Frekvence Typ šumu Typ rotace Typ fraktálu Oktávy Lakunarita Vlastní název souboru Vyberte umístění a název pro uložení současného obrázku Uložena složka s vlastním názvem Druh koláže Stínování barev Odstíny Míchání barev Informace o barvě Vybraná barva Barva k míchání Povolte plný přístup k souborům pro zobrazení JXL, QOI a dalších souborů, které nejsou na Androidu rozpoznány jako obrázkové soubory, bez povolení oprávnění nebude možné tyto obrázky zobrazit. Uprostřed vlevo Minimální poměr barev Práh čar Kvadratický práh Měřítko cesty Nástroje pro WEBP Obrázky na WEBP Začněte výběrem WEBP obrázku Převést soubor WEBP na dávku obrázků Převést dávku obrázků na WEBP soubor Uložit jako obrázek s QR kódem Smazat šablonu Náhled filtru QR kód Načíst QR kód a získat jeho obsah nebo vložit vlastní řetězec pro generování nového kódu Obsah kódu Tvorba koláží Typ konfety Slavnostní Explodovat Lanczos Bessel Metoda převzorkování, která zachovává vysoce kvalitní interpolaci aplikací Besselovy (jinc) funkce na hodnoty pixelů Povolte ji a stránka nastavení se vždy otevře jako celá obrazovka, nikoli jako posuvná zásuvka Komponovat Materiál pro složení Jetpacku, který přepnete Materiál, který vyměníte Použít paletu vzorků Pokud je tato možnost povolena, bude vzorkována paleta kvantifikace Tolerance zaokrouhlení souřadnic Režim motoru Planární konfigurace Y Cb Cr Dílčí vzorkování Odsazení pásů Řádky na proužek Počty bajtů stripu Výměnný formát JPEG Délka výměnného formátu JPEG Přenosová funkce Primární barevnost Y Cb Cr koeficienty Referenční černá bílá Datum Čas Make Barevný prostor Gamma Pixel X rozměr Pixel rozměr Y Komprimované bity na pixel Poznámka výrobce Datum Čas Originál Datum Čas Digitalizace Čas offsetu Offset Time Original Čas posunu Digitalizováno Čas pod sekundu Čas pod sekundu Originál Čas pod sekundu Digitalizováno Doba vystavení Číslo F Program expozice Spektrální citlivost Fotografická citlivost Oecf Typ citlivosti Standardní výstupní citlivost Doporučený index expozice Rychlost ISO Rychlost ISO Latitude yyy Citlivost ISO Latitude zzz Hodnota rychlosti závěrky Hodnota clony Hodnota jasu Hodnota zkreslení expozice Maximální hodnota clony Vzdálenost předmětu Režim měření Blikat Předmětová oblast Ohnisková vzdálenost Energie blesku Prostorová frekvenční odezva Rozlišení ohniskové roviny X Rozlišení ohniskové roviny Y Jednotka rozlišení ohniskové roviny Umístění předmětu Index expozice Metoda snímání Zdroj souboru Vzor CFA Vlastní vykreslení Expoziční režim Vyvážení bílé Poměr digitálního zoomu Ohnisková vzdálenost v 35mm filmu Typ zachycení scény Gain Control Kontrast Nasycení Ostrost Popis nastavení zařízení Rozsah vzdálenosti předmětu Jedinečné ID obrázku Jméno vlastníka fotoaparátu Sériové číslo těla Specifikace objektivu Značka objektivu Model objektivu Sériové číslo objektivu ID verze GPS Ref. zeměpisná šířka GPS Zeměpisná šířka GPS GPS Zeměpisná délka Ref Zeměpisná délka GPS GPS Nadmořská výška Ref GPS nadmořská výška GPS časové razítko GPS satelity Stav GPS Režim měření GPS GPS DOP Rychlost GPS Ref Rychlost GPS GPS Track Ref GPS stopa GPS Směr obr. Ref Směr obrazu GPS Datum mapy GPS Ref. zeměpisná šířka cíle GPS Cílová zeměpisná šířka GPS Ref. zeměpisná délka cíle GPS Zeměpisná délka cíle GPS Cílové ložisko GPS Ref Cílové ložisko GPS Cílová vzdálenost GPS Ref Cílová vzdálenost GPS Metoda zpracování GPS Informace o oblasti GPS GPS datumovka Diferenciál GPS GPS H Positioning Error Index interoperability Verze DNG Výchozí velikost oříznutí Náhled obrázku Start Délka náhledového obrázku Aspect Frame Spodní okraj snímače Levý okraj snímače Pravý okraj snímače Horní okraj snímače ISO Nakreslete text na cestu s daným písmem a barvou Velikost písma Velikost vodoznaku Opakujte text Aktuální text se bude místo jednorázového kreslení opakovat až do konce cesty Velikost Dash Použijte vybraný obrázek k jeho nakreslení podél dané cesty Tento obrázek bude použit jako opakující se zadání nakreslené cesty Nakreslí obrysový trojúhelník od počátečního bodu do koncového bodu Nakreslí obrysový trojúhelník od počátečního bodu do koncového bodu Nastíněný trojúhelník Trojúhelník Kreslí mnohoúhelník z počátečního bodu do koncového bodu Polygon Obrysový mnohoúhelník Nakreslí obrysový mnohoúhelník od počátečního bodu ke koncovému bodu Vrcholy Nakreslete pravidelný mnohoúhelník Nakreslete mnohoúhelník, který bude pravidelný místo volného tvaru Kreslí hvězdu z počátečního bodu do koncového bodu Hvězda Nastíněná hvězda Kreslí obrysovou hvězdu od počátečního bodu do koncového bodu Poměr vnitřního poloměru Nakreslete pravidelnou hvězdu Nakreslete hvězdu, která bude pravidelná místo volné formy Antialias Umožňuje vyhlazování, aby se zabránilo ostrým hranám Otevřete Upravit místo náhledu Když v ImageToolbox vyberete obrázek k otevření (náhled), otevře se místo náhledu list výběru Vyrovnat histogram HSV Vyrovnat histogram Zadejte procento Povolit zadávání textovým polem Povolí textové pole za výběrem předvoleb, abyste je mohli zadávat za běhu Měřítko barevného prostoru Lineární Vyrovnat pixelaci histogramu Velikost mřížky X Velikost mřížky Y Equalize Histogram Adaptive Vyrovnat histogram Adaptivní LUV Equalize Histogram Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Oříznout na obsah Barva rámu Barva k ignorování Šablona Nebyly přidány žádné filtry šablon Vytvořit nový Naskenovaný QR kód není platnou šablonou filtru Naskenujte QR kód Vybraný soubor neobsahuje žádná data šablony filtru Vytvořit šablonu Název šablony Tento obrázek bude použit k náhledu této šablony filtru Filtr šablony Jako obrázek s QR kódem Chystáte se smazat vybraný filtr šablony. Tuto operaci nelze vrátit zpět Byla přidána šablona filtru s názvem \"%1$s \" (%2$s) Naskenujte libovolný čárový kód, abyste nahradili obsah v poli, nebo něco napište a vygenerujte nový čárový kód s vybraným typem Popis QR Min V nastavení udělte fotoaparátu oprávnění ke skenování QR kódu V nastavení udělte fotoaparátu oprávnění ke skenování skeneru dokumentů Krychlový B-Spline Hamming Hanning Blackman Welch Quadric Gaussův Sfinga Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-He Krabice Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kubická interpolace poskytuje plynulejší škálování tím, že bere v úvahu nejbližších 16 pixelů, což poskytuje lepší výsledky než bilineární Využívá po částech definované polynomiální funkce k hladké interpolaci a aproximaci křivky nebo povrchu, flexibilní a spojité reprezentace tvaru Funkce okna používaná ke snížení spektrálního úniku zúžením hran signálu, užitečná při zpracování signálu Varianta Hannova okna, běžně používaná ke snížení spektrálního úniku v aplikacích zpracování signálu Funkce okna, která poskytuje dobré frekvenční rozlišení minimalizací spektrálního úniku, často používaná při zpracování signálu Funkce okna navržená tak, aby poskytovala dobré frekvenční rozlišení se sníženým spektrálním únikem, často používaná v aplikacích zpracování signálu Metoda, která využívá kvadratickou funkci pro interpolaci a poskytuje hladké a spojité výsledky Interpolační metoda, která aplikuje Gaussovu funkci, užitečnou pro vyhlazení a redukci šumu v obrazech Pokročilá metoda převzorkování poskytující vysoce kvalitní interpolaci s minimem artefaktů Funkce trojúhelníkového okna používaná při zpracování signálu ke snížení spektrálního úniku Vysoce kvalitní metoda interpolace optimalizovaná pro přirozenou změnu velikosti obrazu, vyvážení ostrosti a plynulosti Ostřejší varianta metody Robidoux, optimalizovaná pro ostré změny velikosti obrazu Metoda interpolace založená na spline, která poskytuje hladké výsledky pomocí filtru s 16 klepnutími Metoda interpolace založená na spline, která poskytuje hladké výsledky pomocí filtru s 36 klepnutími Metoda interpolace založená na spline, která poskytuje hladké výsledky pomocí filtru s 64 klepnutími Interpolační metoda, která využívá okno Kaiser, poskytuje dobrou kontrolu nad kompromisem mezi šířkou hlavního laloku a úrovní postranního laloku Hybridní funkce okna kombinující okna Bartlett a Hann, která se používá ke snížení spektrálního úniku při zpracování signálu Jednoduchá metoda převzorkování, která používá průměr nejbližších hodnot pixelů, což často vede k hranatému vzhledu Funkce okna používaná ke snížení spektrálního úniku, poskytující dobré frekvenční rozlišení v aplikacích zpracování signálu Metoda převzorkování, která využívá 2-lalokový Lanczosův filtr pro vysoce kvalitní interpolaci s minimem artefaktů Metoda převzorkování, která využívá 3-lalokový Lanczosův filtr pro vysoce kvalitní interpolaci s minimem artefaktů Metoda převzorkování, která využívá 4-lalokový Lanczosův filtr pro vysoce kvalitní interpolaci s minimem artefaktů Varianta filtru Lanczos 2, která využívá funkci jinc a poskytuje vysoce kvalitní interpolaci s minimálními artefakty Varianta filtru Lanczos 3, která využívá funkci jinc a poskytuje vysoce kvalitní interpolaci s minimálními artefakty Varianta filtru Lanczos 4, která využívá funkci jinc a poskytuje vysoce kvalitní interpolaci s minimálními artefakty Hanning EWA Varianta eliptického váženého průměru (EWA) Hanningova filtru pro hladkou interpolaci a převzorkování Robidoux EWA Varianta eliptického váženého průměru (EWA) filtru Robidoux pro vysoce kvalitní převzorkování Blackman EVE Varianta eliptického váženého průměru (EWA) filtru Blackman pro minimalizaci artefaktů zvonění Quadric EWA Varianta eliptického váženého průměru (EWA) filtru Quadric pro hladkou interpolaci Robidoux Sharp EWA Varianta eliptického váženého průměru (EWA) filtru Robidoux Sharp pro ostřejší výsledky Lanczos 3 Jinc EWA Varianta eliptického váženého průměru (EWA) filtru Lanczos 3 Jinc pro vysoce kvalitní převzorkování s redukovaným aliasingem Ženšen Převzorkovací filtr určený pro vysoce kvalitní zpracování obrazu s dobrým vyvážením ostrosti a plynulosti Ženšen EWA Varianta eliptického váženého průměru (EWA) ženšenového filtru pro lepší kvalitu obrazu Lanczos Sharp EWA Varianta eliptického váženého průměru (EWA) Lanczos Sharp filtru pro dosažení ostrých výsledků s minimem artefaktů Lanczos 4 nejostřejší EWA Varianta eliptického váženého průměru (EWA) filtru Lanczos 4 Sharpest pro extrémně ostré převzorkování obrazu Lanczos Soft EWA Elliptical Weighted Average (EWA) varianta Lanczos Soft filtru pro plynulejší převzorkování obrazu Haasn Soft Převzorkovací filtr navržený Haasnem pro plynulé škálování obrazu bez artefaktů Odmítnout navždy Stohování obrázků Skládejte obrázky na sebe s vybranými režimy prolnutí Přidat obrázek Koše se počítají Clahe HSL Clahe HSV Equalize Histogram Adaptive HSL Equalize Histogram Adaptive HSV Režim okraje Klip Zabalit Barevná slepota Vyberte režim pro přizpůsobení barev motivu pro vybranou variantu barvosleposti Obtížné rozlišení mezi červenými a zelenými odstíny Obtížné rozlišení mezi zeleným a červeným odstínem Obtížné rozlišení mezi modrými a žlutými odstíny Neschopnost vnímat červené odstíny Neschopnost vnímat zelené odstíny Neschopnost vnímat modré odstíny Snížená citlivost na všechny barvy Úplná barvoslepost, vidět pouze odstíny šedi Nepoužívejte schéma Color Blind Barvy budou přesně takové, jaké jsou nastaveny v motivu Sigmoidální Lagrange 2 Lagrangeův interpolační filtr řádu 2, vhodný pro vysoce kvalitní škálování obrazu s plynulými přechody Lagrange 3 Lagrangeův interpolační filtr řádu 3, který nabízí lepší přesnost a hladší výsledky pro změnu měřítka obrazu Lanczos 6 Lanczosův převzorkovací filtr s vyšším řádem 6, který poskytuje ostřejší a přesnější měřítko obrazu Lanczos 6 Jinc Varianta filtru Lanczos 6 využívající funkci Jinc pro lepší kvalitu převzorkování obrazu Lineární rámeček rozostření Lineární rozostření stanu Lineární Gaussův Box Blur Lineární rozostření zásobníku Rozostření Gaussova pole Lineární rychlé Gaussovské rozostření Další Lineární rychlé Gaussovské rozostření Lineární gaussovské rozostření Vyberte jeden filtr, který chcete použít jako barvu Vyměňte filtr Vyberte filtr níže a použijte jej jako štětec ve výkresu Schéma komprese TIFF Low Poly Pískové malování Zkombinujte režim změny velikosti oříznutí s tímto parametrem, abyste dosáhli požadovaného chování (oříznutí/přizpůsobení poměru stran) Jazyky byly úspěšně importovány Přenos palety Vylepšený olej Jednoduchá stará televize HDR Gotham Jednoduchá skica Měkká záře Barevný plakát Tri Tone Třetí barva Clahe Oklab Klára Olchová Clahe Jzazbz Polka Dot Clustered 2x2 Dithering Clustered 4x4 Dithering Clustered 8x8 Dithering Yililoma Dithering Nejsou vybrány žádné oblíbené možnosti, přidejte je na stránce nástrojů Přidat oblíbené Komplementární Analogický Triadický Rozdělení komplementární Tetradický Náměstí Analogové + doplňkové Při zapnutých dynamických barvách nelze použít monet 512x512 2D LUT Cílový obrázek LUT Amatér Slečno Etiketa Měkká elegance Varianta Soft Elegance Paletová přenosová varianta 3D LUT Cílový soubor 3D LUT (.cube / .CUBE) LUT Bleach Bypass Světlo svíček Drop Blues Nervózní Amber Podzimní barvy Filmový sklad 50 Mlhavá noc Kodak Získejte neutrální obrázek LUT Nejprve pomocí své oblíbené aplikace pro úpravu fotografií aplikujte filtr na neutrální LUT, který můžete získat zde. Aby to fungovalo správně, barva každého pixelu nesmí záviset na jiných pixelech (např. nebude fungovat rozostření). Jakmile budete připraveni, použijte svůj nový obrázek LUT jako vstup pro filtr 512*512 LUT Pop Art Celuloid Káva Zlatý les Nazelenalý Retro žlutá Odkazy Soubory ICO lze uložit pouze v maximální velikosti 256 x 256 GIF na WEBP Převeďte obrázky GIF na animované obrázky WEBP Povolením časových razítek vyberte jejich formát Místo pro jednorázové uložení Zobrazit a upravit umístění jednorázového uložení, které můžete použít dlouhým stisknutím tlačítka uložit ve většině možností CI kanál Image Toolbox v Telegramu 🎉 Připojte se k našemu chatu, kde můžete diskutovat o čemkoli, co chcete, a také se podívat na kanál CI, kde zveřejňuji beta verze a oznámení Získejte upozornění na nové verze aplikace a přečtěte si oznámení Přizpůsobte obrázek daným rozměrům a použijte rozostření nebo barvu na pozadí Získat Vážená síla Síla ping pongu Funkce vzdálenosti Typ návratu Jitter Pokřivení domény Zarovnání Zakázat otáčení Zabraňuje otáčení obrázků pomocí gest dvěma prsty Povolit přichycení k okrajům Po přesunutí nebo přiblížení se obrázky přichytí k vyplnění okrajů rámečku Histogram obrazu RGB nebo jasu, který vám pomůže provést úpravy Tento obrázek bude použit ke generování histogramů RGB a jasu Možnosti Tesseractu Použijte některé vstupní proměnné pro engine tesseract Vlastní možnosti Možnosti je třeba zadat podle tohoto vzoru: \"--{název_možnosti} {hodnota} \" Automatické oříznutí Volné rohy Oříznout obrázek podle mnohoúhelníku, to také opraví perspektivu Vynucené body na hranice obrázku Body nebudou omezeny hranicemi obrazu, což je užitečné pro přesnější korekci perspektivy Maska Výplň pod nakreslenou cestou podle obsahu Heal Spot Použijte kruhové jádro Otevírací Zavírání Morfologický gradient Cylindr Černý klobouk Tónové křivky Resetovat křivky Křivky budou vráceny zpět na výchozí hodnotu Styl čáry Velikost mezery čárkovaná Přerušovaná tečka Vyraženo Cikcak Nakreslí přerušovanou čáru podél nakreslené cesty se zadanou velikostí mezery Nakreslí tečku a přerušovanou čáru podél dané cesty Pouze výchozí rovné čáry Nakreslí vybrané tvary podél cesty se zadaným rozestupem Kreslí vlnitá klikatá podél cesty Cikcak poměr Vytvořit zástupce Vyberte nástroj, který chcete připnout Nástroj bude přidán na domovskou obrazovku vašeho spouštěče jako zkratka, použijte jej v kombinaci s nastavením \"Přeskočit výběr souboru \", abyste dosáhli potřebného chování Neskládejte rámečky Umožňuje likvidaci předchozích snímků, takže se nebudou skládat na sebe Prolínání Rámy budou vzájemně prolínány Počet snímků prolínání Práh jedna Práh dva Mazaný Zrcadlo 101 Vylepšené rozostření přiblížením Laplacký jednoduchý Sobel Jednoduché Pomocná mřížka Zobrazuje podpůrnou mřížku nad oblastí kreslení, která pomáhá s přesnými manipulacemi Barva mřížky Šířka buňky Výška buňky Kompaktní selektory Některé ovládací prvky výběru budou používat kompaktní rozvržení, aby zabraly méně místa V nastavení udělte fotoaparátu oprávnění k pořízení snímku Rozložení Titulek hlavní obrazovky Konstantní rychlostní faktor (CRF) Hodnota %1$s znamená pomalou kompresi, což má za následek relativně malou velikost souboru. %2$s znamená rychlejší kompresi, výsledkem je velký soubor. Knihovna Lut Stáhněte si kolekci LUT, kterou můžete použít po stažení Aktualizujte kolekci LUT (pouze nové budou zařazeny do fronty), kterou můžete použít po stažení Změnit výchozí náhled obrázku pro filtry Náhledový obrázek Skrýt Show Typ posuvníku Fantazie Materiál 2 Efektně vypadající slider. Toto je výchozí možnost Posuvník Materiál 2 Posuvník Material You Použít Středová tlačítka dialogu Tlačítka dialogů budou pokud možno umístěna uprostřed místo na levé straně Open Source licence Zobrazit licence knihoven s otevřeným zdrojovým kódem používaných v této aplikaci Plocha Převzorkování pomocí vztahu plochy pixelů. Může to být preferovaná metoda pro decimaci obrazu, protože poskytuje výsledky bez moaré. Ale když je obrázek přiblížený, je to podobné jako u metody \"Nejbližší \". Povolit mapování tónů Zadejte % Nelze získat přístup k webu, zkuste použít VPN nebo zkontrolujte, zda je adresa URL správná Značkovací vrstvy Režim vrstev s možností libovolně umísťovat obrázky, text a další Upravit vrstvu Vrstvy na obrázku Použijte obrázek jako pozadí a přidejte na něj různé vrstvy Vrstvy na pozadí Stejné jako u první možnosti, ale s barvou místo obrázku Beta Strana rychlého nastavení Při úpravě obrázků přidejte na vybranou stranu plovoucí pruh, který po kliknutí otevře rychlé nastavení Jasný výběr Skupina nastavení \"%1$s \" bude ve výchozím nastavení sbalená Skupina nastavení \"%1$s \" bude ve výchozím nastavení rozbalena Nástroje Base64 Dekódujte řetězec Base64 na obrázek nebo zakódujte obrázek do formátu Base64 Základní 64 Zadaná hodnota není platný řetězec Base64 Nelze zkopírovat prázdný nebo neplatný řetězec Base64 Vložit Base64 Kopírovat Base64 Načtěte obrázek pro zkopírování nebo uložení řetězce Base64. Pokud máte samotný řetězec, můžete jej vložit výše a získat obrázek Uložit Base64 Share Base64 Možnosti Akce Import Base64 Akce Base64 Přidat obrys Přidejte obrys kolem textu se zadanou barvou a šířkou Barva obrysu Velikost obrysu Otáčení Kontrolní součet jako název souboru Výstupní obrázky budou mít název odpovídající jejich datovému kontrolnímu součtu Svobodný software (partner) Užitečnější software v partnerském kanálu aplikací pro Android Algoritmus Nástroje kontrolního součtu Porovnejte kontrolní součty, vypočítejte hash nebo vytvořte hex řetězce ze souborů pomocí různých hashovacích algoritmů Vypočítat Textový hash Kontrolní součet Vyberte soubor pro výpočet jeho kontrolního součtu na základě zvoleného algoritmu Zadejte text pro výpočet jeho kontrolního součtu na základě vybraného algoritmu Kontrolní součet zdroje Kontrolní součet k porovnání Zápas! Rozdíl Kontrolní součty jsou stejné, může to být bezpečné Kontrolní součty nejsou stejné, soubor může být nebezpečný! Síťové přechody Podívejte se na online kolekci síťových přechodů Importovat lze pouze písma TTF a OTF Importovat písmo (TTF/OTF) Exportujte písma Importovaná písma Chyba při pokusu o uložení, zkuste změnit výstupní složku Název souboru není nastaven Žádný Vlastní stránky Výběr stránek Potvrzení ukončení nástroje Pokud máte při používání určitých nástrojů neuložené změny a pokusíte se je zavřít, zobrazí se dialog pro potvrzení Upravit EXIF Změňte metadata jednoho obrázku bez rekomprese Klepnutím upravíte dostupné značky Změnit nálepku Přizpůsobit šířku Přizpůsobit výšku Dávkové porovnání Vyberte soubor/soubory pro výpočet jejich kontrolního součtu na základě zvoleného algoritmu Vyberte Soubory Vyberte adresář Měřítko délky hlavy Razítko Časové razítko Formát vzor Vycpávka Řezání obrazu Vyřízněte část obrázku a slučte levé (může být inverzní) svislými nebo vodorovnými čarami Vertikální otočná čára Horizontální otočná čára Inverzní výběr Svislá část řezu bude ponechána, místo sloučení částí kolem oblasti řezu Vodorovná část řezu bude ponechána namísto sloučení částí kolem oblasti řezu Kolekce síťových přechodů Vytvořte síťový gradient s vlastním množstvím uzlů a rozlišením Překrytí síťovým přechodem Vytvořte síťový gradient horní části daných obrázků Přizpůsobení bodů Velikost mřížky Rozlišení X Rozlišení Y Rezoluce Pixel za pixelem Barva zvýraznění Typ porovnání pixelů Naskenujte čárový kód Výškový poměr Typ čárového kódu Vynutit B/W Obrázek čárového kódu bude plně černobílý a nebude zbarven podle motivu aplikace Naskenujte libovolný čárový kód (QR, EAN, AZTEC, …) a získejte jeho obsah nebo vložte svůj text a vygenerujte nový Nebyl nalezen žádný čárový kód Generovaný čárový kód zde bude Audio kryty Extrahujte obrázky obalu alba ze zvukových souborů, většina běžných formátů je podporována Začněte výběrem zvuku Vyberte Zvuk Nebyly nalezeny žádné kryty Odeslat protokoly Kliknutím sdílejte soubor protokolů aplikace, může mi to pomoci odhalit problém a vyřešit problémy Jejda… Něco se pokazilo Můžete mě kontaktovat pomocí níže uvedených možností a já se pokusím najít řešení.\n(Nezapomeňte připojit protokoly) Zápis do souboru Extrahujte text z dávky obrázků a uložte jej do jednoho textového souboru Zápis do metadat Extrahujte text z každého obrázku a umístěte jej do EXIF ​​informací o relativních fotografiích Neviditelný režim Použijte steganografii k vytvoření okem neviditelných vodoznaků uvnitř bajtů vašich obrázků Použijte LSB Bude použita steganografická metoda LSB (Less Significant Bit), jinak FD (Frequency Domain). Automatické odstranění červených očí Heslo Odemknout PDF je chráněno Operace téměř dokončena. Zrušení nyní bude vyžadovat restartování Datum změny Datum změny (obráceno) Velikost Velikost (obrácená) Typ MIME Typ MIME (obrácený) Rozšíření Rozšíření (obráceno) Datum přidání Datum přidání (obráceno) Zleva doprava Zprava doleva Shora dolů Zespodu nahoru Tekuté sklo Přepínač založený na nedávno oznámeném IOS 26 a jeho designovém systému tekutého skla Níže vyberte obrázek nebo vložte/importujte data Base64 Začněte zadáním odkazu na obrázek Vložit odkaz Kaleidoskop Sekundární úhel Strany Mix kanálů Modrá zelená Červená modrá Zelená červená Do červena Do zelena Do modra azurová purpurová Žluť Barva Polotón Obrys úrovně Offset Voronoi krystalizuje Tvar Úsek Nahodilost Despeckle Šířit Pes Druhý poloměr Vyrovnat Záře Vířit a štípnout Pointillize Barva ohraničení Polární souřadnice Rect to polar Polární až obdélníkový Invertovat v kruhu Snížit hluk Jednoduchá Solarizace Vazba X mezera Y Gap X šířka Y šířka Točit Razítko Namazat Hustota Směs Sphere Lens Distortion Index lomu Oblouk Úhel rozpětí Jiskra Paprsky ASCII Gradient Marie Podzim Kost Proud Zima Oceán Letní Jaro Cool Varianta HSV Růžový Horký Slovo Magma Peklo Plazma Viridis Občané Soumrak Soumrak se posunul Perspektiva Auto Zkosení Povolit oříznutí Oříznutí nebo perspektiva Absolutní Turbo Tmavě zelená Korekce objektivu Soubor profilu cílového objektivu ve formátu JSON Stáhněte si hotové profily objektivů Část procent Exportovat jako JSON Zkopírujte řetězec s daty palety jako reprezentaci json Vyřezávání švů Domovská obrazovka Uzamknout obrazovku Vestavěný Export tapet Obnovit Získejte aktuální tapety Home, Lock a Built-in Povolit přístup ke všem souborům, to je potřeba k načtení tapet Oprávnění ke správě externího úložiště nestačí, musíte povolit přístup ke svým obrázkům, nezapomeňte vybrat možnost \"Povolit vše \" Přidat předvolbu k názvu souboru Připojí příponu s vybranou předvolbou k názvu souboru obrázku Přidat režim měřítka obrázku k názvu souboru Připojí příponu s vybraným režimem měřítka obrázku k souboru obrázku Ascii Art Převeďte obrázek na text ASCII, který bude vypadat jako obrázek Parametry Aplikuje negativní filtr na obrázek pro lepší výsledek v některých případech Zpracování snímku obrazovky Snímek obrazovky nebyl zachycen, zkuste to znovu Ukládání bylo přeskočeno %1$s souborů přeskočeno Povolit přeskočení, pokud je větší Některé nástroje budou moci přeskočit ukládání obrázků, pokud by výsledná velikost souboru byla větší než původní Kalendář událostí Kontakt E-mail Umístění Telefon Text SMS URL Wi-Fi Otevřená síť N/A SSID Telefon Zpráva Adresa Podrobit Tělo Jméno Organizace Titul Telefony e-maily URL Adresy Shrnutí Popis Umístění Organizátor Datum zahájení Datum ukončení Postavení Zeměpisná šířka Zeměpisná délka Vytvořte čárový kód Upravit čárový kód Konfigurace Wi-Fi Zabezpečení Vyberte kontakt Udělte kontaktům v nastavení oprávnění k automatickému vyplňování pomocí vybraného kontaktu Kontaktní údaje Křestní jméno Druhé jméno Příjmení Výslovnost Přidat telefon Přidat e-mail Přidat adresu webové stránky Přidat web Formátovaný název Tento obrázek bude použit k umístění nad čárový kód Přizpůsobení kódu Tento obrázek bude použit jako logo uprostřed QR kódu Logo Polstrování logem Velikost loga Rohy loga Čtvrté oko Přidá do qr kódu symetrii oka přidáním čtvrtého oka do spodního rohu Tvar pixelu Tvar rámu Tvar koule Úroveň opravy chyb Tmavá barva Světlá barva Hyper OS Styl podobný Xiaomi HyperOS Vzor masky Tento kód nemusí být skenovatelný, změňte parametry vzhledu, aby byl čitelný na všech zařízeních Nelze skenovat Nástroje budou vypadat jako spouštěč aplikací na domovské obrazovce, aby byly kompaktnější Režim spouštěče Vyplní oblast vybraným štětcem a stylem Povodňová výplň Sprej Kreslí cestu ve stylu graffity Čtvercové částice Částice spreje budou mít čtvercový tvar namísto kruhů Nástroje palety Vygenerujte základní/materiál z palety z obrázku nebo importujte/exportujte přes různé formáty palet Upravit paletu Export/import palety v různých formátech Název barvy Název palety Formát palety Export vygenerované palety do různých formátů Přidá novou barvu do aktuální palety Formát %1$s nepodporuje zadání názvu palety Kvůli zásadám Obchodu Play nelze tuto funkci zahrnout do aktuálního sestavení. Chcete-li získat přístup k této funkci, stáhněte si ImageToolbox z alternativního zdroje. Dostupné sestavení na GitHubu najdete níže. Otevřete stránku Github Původní soubor bude nahrazen novým souborem namísto uložení do vybrané složky Zjištěn skrytý text vodoznaku Byl zjištěn skrytý obrázek vodoznaku Tento obrázek byl skrytý Generativní malba Umožňuje odstranit objekty z obrázku pomocí modelu AI, aniž byste se spoléhali na OpenCV. K použití této funkce si aplikace stáhne požadovaný model (~200 MB) z GitHubu Umožňuje odstranit objekty z obrázku pomocí modelu AI, aniž byste se spoléhali na OpenCV. Může se jednat o dlouhodobou operaci Analýza úrovně chyb Gradient jasu Průměrná vzdálenost Kopírovat Detekce přesunu Zachovat Koeficient Data schránky jsou příliš velká Data jsou příliš velká na kopírování Jednoduchá Weave Pixelizace Postupná pixelizace Křížová pixelizace Mikro makro pixelizace Orbitální pixelizace Vortexová pixelizace Pixelizace pulzní mřížky Pixelizace jádra Radial Weave Pixelization Nelze otevřít uri \"%1$s \" Režim sněžení Povoleno Hraniční rám Závadová varianta Posun kanálu Maximální offset VHS Blokovat závadu Velikost bloku CRT zakřivení Zakřivení Chroma Pixel Melt Max Drop Nástroje AI Různé nástroje pro zpracování obrázků prostřednictvím modelů AI, jako je odstraňování artefaktů nebo odšumování Komprese, zubaté linie Karikatury, vysílací komprese Obecná komprese, obecný šum Bezbarvý kreslený hluk Rychlá, obecná komprese, obecný šum, animace/komiks/anime Skenování knih Korekce expozice Nejlepší při obecné kompresi, barevné obrázky Nejlepší při obecné kompresi, obrázky ve stupních šedi Obecná komprese, obrázky ve stupních šedi, silnější Obecný šum, barevné obrázky Obecný šum, barevné obrázky, lepší detaily Obecný šum, obrázky ve stupních šedi Obecný šum, obrázky ve stupních šedi, silnější Obecný šum, obrázky ve stupních šedi, nejsilnější Obecná komprese Obecná komprese Texturizace, komprese h264 Komprese VHS Nestandardní komprese (cinepak, msvideo1, roq) Bink komprese, lepší na geometrii Bink komprese, silnější Komprese Bink, měkká, zachovává detaily Eliminace schodišťového efektu, vyhlazování Naskenované umění/kresby, mírná komprese, moaré Barevné pruhování Pomalé, odstranění polotónů Obecný kolorizér pro obrázky ve stupních šedi/černobíle, pro lepší výsledky použijte DDColor Odstraňování okrajů Odstraňuje nadměrné ostření Pomalé, váhavé Anti-aliasing, obecné artefakty, CGI Zpracování skenů KDM003 Lehký model vylepšení obrazu Odstranění kompresního artefaktu Odstranění kompresního artefaktu Odstranění obvazu s hladkým výsledkem Zpracování polotónového vzoru Odstranění vzoru rozkladu V3 Odstranění artefaktů JPEG V2 Vylepšení textury H.264 Ostření a vylepšení VHS Sloučení Velikost kousku Velikost překrytí Obrázky větší než %1$s pixelů budou nakrájeny a zpracovány na kousky, které se překrývají, aby se zabránilo viditelným švům. Velké velikosti mohou způsobit nestabilitu u zařízení nižší třídy Začněte výběrem jednoho Chcete smazat %1$s model? Budete si jej muset stáhnout znovu Potvrdit Modelky Stažené modely Dostupné modely Příprava Aktivní model Otevření relace se nezdařilo Importovat lze pouze modely .onnx/.ort Importovat model Importujte vlastní model onnx pro další použití, jsou přijímány pouze modely onnx/ort, podporuje téměř všechny varianty podobné esrgan Importované modely Obecný šum, barevné obrázky Obecný šum, barevné obrázky, silnější Obecný šum, barevné obrázky, nejsilnější Snižuje artefakty rozkladu a barevné pruhy, zlepšuje hladké přechody a ploché barevné oblasti. Vylepšuje jas a kontrast obrazu s vyváženými světly při zachování přirozených barev. Zesvětluje tmavé snímky a zároveň zachovává detaily a zabraňuje přeexponování. Odstraňuje nadměrné barevné tónování a obnovuje neutrálnější a přirozenější vyvážení barev. Aplikuje tónování šumu na základě Poisson s důrazem na zachování jemných detailů a textur. Aplikuje jemné tónování Poissonova šumu pro hladší a méně agresivní vizuální výsledky. Jednotné tónování šumu zaměřené na zachování detailů a čistotu obrazu. Jemné jednotné tónování šumu pro jemnou texturu a hladký vzhled. Opravuje poškozené nebo nerovné oblasti překreslením artefaktů a zlepšením konzistence obrazu. Lehký model pro odstraňování pásů, který odstraňuje barevné pruhy s minimálními náklady na výkon. Optimalizuje obrazy s velmi vysokou kompresí artefaktů (0-20% kvalita) pro lepší jasnost. Vylepšuje obrázky pomocí artefaktů s vysokou kompresí (20-40% kvalita), obnovuje detaily a snižuje šum. Zlepšuje snímky s mírnou kompresí (40-60% kvalita), vyvážením ostrosti a plynulosti. Upřesňuje obrázky pomocí lehké komprese (60-80% kvalita) pro vylepšení jemných detailů a textur. Mírně vylepšuje téměř bezeztrátový obraz (80-100% kvalita) při zachování přirozeného vzhledu a detailů. Jednoduché a rychlé kolorování, kreslené, není ideální Mírně snižuje rozmazání obrazu a zlepšuje ostrost bez vnášení artefaktů. Dlouho běžící operace Zpracování obrázku Zpracování Odstraňuje těžké artefakty komprese JPEG v obrazech velmi nízké kvality (0-20 %). Snižuje silné JPEG artefakty ve vysoce komprimovaných obrázcích (20-40%). Vyčistí mírné JPEG artefakty při zachování detailů obrazu (40-60 %). Zjemňuje světlé JPEG artefakty v poměrně vysoké kvalitě obrázků (60-80%). Jemně redukuje drobné JPEG artefakty v téměř bezeztrátových obrázcích (80-100 %). Vylepšuje jemné detaily a textury a zlepšuje vnímanou ostrost bez těžkých artefaktů. Zpracování dokončeno Zpracování se nezdařilo Zlepšuje textury a detaily pleti a zároveň zachovává přirozený vzhled, optimalizovaný pro rychlost. Odstraňuje artefakty komprese JPEG a obnovuje kvalitu obrazu komprimovaných fotografií. Snižuje šum ISO na fotografiích pořízených za špatných světelných podmínek a zachovává detaily. Opravuje přeexponované nebo „jumbo“ zvýraznění a obnovuje lepší vyvážení tónů. Lehký a rychlý kolorizační model, který dodává obrázkům ve stupních šedi přirozené barvy. DEJPEG Odhlučnit Vybarvit Artefakty Zvýšit Anime Skenuje Upscale X4 upscaler pro obecné obrázky; malý model, který využívá méně GPU a času, s mírným rozmazáním a odšumováním. X2 upscaler pro obecné obrázky, zachování textur a přirozených detailů. X4 upscaler pro obecné obrázky s vylepšenými texturami a realistickými výsledky. X4 upscaler optimalizovaný pro obrázky anime; 6 bloků RRDB pro ostřejší linie a detaily. X4 upscaler se ztrátou MSE poskytuje hladší výsledky a snížené artefakty pro obecné obrázky. X4 Upscaler optimalizovaný pro obrázky anime; Varianta 4B32F s ostřejšími detaily a hladkými liniemi. Model X4 UltraSharp V2 pro obecné obrázky; zdůrazňuje ostrost a jasnost. X4 UltraSharp V2 Lite; rychlejší a menší, zachovává detaily při použití menší paměti GPU. Lehký model pro rychlé odstranění pozadí. Vyvážený výkon a přesnost. Pracuje s portréty, objekty a scénami. Doporučeno pro většinu případů použití. Odebrat BG Tloušťka horizontálního okraje Tloušťka vertikálního okraje %1$s barev %1$s barva %1$s barev %1$s barev Aktuální model nepodporuje chunking, obraz bude zpracován v původních rozměrech, což může způsobit vysokou spotřebu paměti a problémy se zařízeními nižší třídy Chunking zakázáno, obraz bude zpracován v původních rozměrech, což může způsobit vysokou spotřebu paměti a problémy se zařízeními nižší třídy, ale může poskytnout lepší výsledky při odvození Chunking Vysoce přesný model segmentace obrazu pro odstranění pozadí Odlehčená verze U2Net pro rychlejší odstraňování pozadí s menší spotřebou paměti. Plný model DDColor poskytuje vysoce kvalitní vybarvení pro obecné obrázky s minimálními artefakty. Nejlepší výběr ze všech barevných modelů. DDDColor Vyškolené a soukromé umělecké datové sady; vytváří rozmanité a umělecké výsledky zbarvení s menším počtem nerealistických barevných artefaktů. Lehký model BiRefNet založený na Swin Transformer pro přesné odstranění pozadí. Vysoce kvalitní odstranění pozadí s ostrými hranami a vynikajícím zachováním detailů, zejména na složitých objektech a složitých pozadích. Model odstranění pozadí, který vytváří přesné masky s hladkými okraji, vhodný pro běžné objekty a zachování mírného detailu. Model je již stažen Model byl úspěšně importován Typ Klíčové slovo Velmi rychlé Normální Pomalý Velmi pomalé Vypočítat procenta Minimální hodnota je %1$s Zkreslení obrazu kresbou prsty Warp Tvrdost Režim Warp Pohyb Růst Zmenšit Swirl CW Krouživým pohybem CCW Síla blednutí Top Drop Dolní Drop Spusťte aplikaci Drop End Drop Stahování Hladké tvary Použijte superelipsy místo standardních zaoblených obdélníků pro hladší a přirozenější tvary Typ tvaru Střih Zaoblený Hladký Ostré hrany bez zaoblení Klasické zaoblené rohy Tvary Typ Velikost rohů Veverka Elegantní zaoblené prvky uživatelského rozhraní Formát názvu souboru Vlastní text umístěný na samém začátku názvu souboru, ideální pro názvy projektů, značky nebo osobní značky. Používá původní název souboru bez přípony, což vám pomáhá zachovat identifikaci zdroje nedotčenou. Šířka obrázku v pixelech, užitečné pro sledování změn rozlišení nebo změny měřítka výsledků. Výška obrázku v pixelech, užitečná při práci s poměry stran nebo exportu. Generuje náhodné číslice pro zaručení jedinečných názvů souborů; přidejte další číslice pro větší bezpečnost proti duplikátům. Automatické počítadlo pro dávkové exporty, ideální při ukládání více obrázků v jedné relaci. Vloží použitý název předvolby do názvu souboru, abyste si snadno zapamatovali, jak byl obrázek zpracován. Zobrazuje režim změny velikosti obrazu použitý během zpracování a pomáhá rozlišovat obrazy se změněnou velikostí, oříznuté nebo přizpůsobené obrazy. Vlastní text umístěný na konci názvu souboru, užitečný pro verzování jako _v2, _edited nebo _final. Přípona souboru (png, jpg, webp atd.), automaticky odpovídající skutečně uloženému formátu. Přizpůsobitelné časové razítko, které vám umožní definovat svůj vlastní formát pomocí specifikace Java pro dokonalé třídění. Typ Fling Android Nativní Styl iOS Hladká křivka Rychlé zastavení Skákající Plovoucí Elegantní Ultra hladký Adaptivní Přístupnost Aware Snížený pohyb Nativní fyzika rolování pro Android Vyvážené, plynulé rolování pro všeobecné použití Chování posouvání jako u iOS s vyšším třením Unikátní spline křivka pro zřetelný pocit rolování Přesné rolování s rychlým zastavením Hravý, citlivý skákací svitek Dlouhé, klouzavé svitky pro procházení obsahu Rychlé a citlivé posouvání pro interaktivní uživatelská rozhraní Prémiové plynulé rolování s prodlouženou hybností Upravuje fyziku na základě rychlosti letu Respektuje nastavení přístupnosti systému Minimální pohyb pro potřeby dostupnosti Primární linky Přidá silnější čáru každý pátý řádek Barva výplně Skryté nástroje Nástroje skryté pro sdílení Knihovna barev Prohlédněte si rozsáhlou sbírku barev Zaostří a odstraní rozmazání snímků při zachování přirozených detailů, což je ideální pro opravu rozostřených fotografií. Inteligentně obnovuje obrázky, jejichž velikost byla dříve změněna, a obnovuje ztracené detaily a textury. Optimalizováno pro živě akční obsah, snižuje kompresní artefakty a vylepšuje jemné detaily ve snímcích filmu/TV pořadu. Převádí záznam v kvalitě VHS do HD, odstraňuje šum na pásce a zvyšuje rozlišení při zachování vintage stylu. Specializuje se na obrázky a snímky obrazovky s velkým množstvím textu, zaostřuje znaky a zlepšuje čitelnost. Pokročilé upscaling trénované na různých souborech dat, vynikající pro všeobecné vylepšení fotografií. Optimalizováno pro webově komprimované fotografie, odstraňuje JPEG artefakty a obnovuje přirozený vzhled. Vylepšená verze pro webové fotografie s lepším zachováním textury a redukcí artefaktů. 2x upscaling s technologií Dual Aggregation Transformer, zachovává ostrost a přirozené detaily. 3x upscaling pomocí pokročilé architektury transformátoru, ideální pro potřeby mírného zvětšení. 4x vysoce kvalitní upscaling s nejmodernější sítí transformátorů, zachovává jemné detaily ve větším měřítku. Odstraňuje rozmazání/šum a chvění z fotografií. Všeobecný účel, ale nejlépe na fotografiích. Obnovuje obrazy nízké kvality pomocí transformátoru Swin2SR, optimalizovaného pro degradaci BSRGAN. Skvělé pro opravu těžkých kompresních artefaktů a vylepšení detailů ve 4násobném měřítku. 4x upscaling s transformátorem SwinIR trénovaným na degradaci BSRGAN. Využívá GAN pro ostřejší textury a přirozenější detaily na fotografiích a složitých scénách. Cesta Sloučit PDF Spojte více souborů PDF do jednoho dokumentu Pořadí souborů pp. Rozdělit PDF Extrahujte konkrétní stránky z dokumentu PDF Otočit PDF Opravte orientaci stránky trvale Stránky Přeuspořádat PDF Přetažením stránek změníte jejich pořadí Podržte a přetáhněte stránky Čísla stránek Automaticky přidejte číslování do dokumentů Formát štítku PDF na text (OCR) Extrahujte prostý text z dokumentů PDF Překryvný vlastní text pro branding nebo zabezpečení Podpis Přidejte svůj elektronický podpis do jakéhokoli dokumentu Toto bude použito jako podpis Odemknout PDF Odstraňte hesla z chráněných souborů Chránit PDF Zabezpečte své dokumenty pomocí silného šifrování Úspěch PDF odemčené, můžete jej uložit nebo sdílet Oprava PDF Pokuste se opravit poškozené nebo nečitelné dokumenty Stupně šedi Převeďte všechny vložené obrázky dokumentu do stupňů šedi Komprimovat PDF Optimalizujte velikost souboru dokumentu pro snazší sdílení ImageToolbox znovu sestaví vnitřní tabulku křížových odkazů a regeneruje strukturu souborů od začátku. To může obnovit přístup k mnoha souborům, které \\"nelze otevřít\\" Tento nástroj převede všechny obrázky dokumentů do stupňů šedi. Nejlepší pro tisk a zmenšení velikosti souboru Metadata Upravte vlastnosti dokumentu pro lepší soukromí Tagy Výrobce Autor Klíčová slova Tvůrce Soukromí Deep Clean Vymažte všechna dostupná metadata pro tento dokument Strana Hluboké OCR Extrahujte text z dokumentu a uložte jej do jednoho textového souboru pomocí enginu Tesseract Nelze odstranit všechny stránky Odstraňte stránky PDF Odstraňte konkrétní stránky z dokumentu PDF Klepněte na Odebrat Ručně Oříznout PDF Ořízněte stránky dokumentu na libovolné hranice Vyrovnat PDF Udělejte PDF nemodifikovatelné rastrováním stránek dokumentu Fotoaparát nelze spustit. Zkontrolujte prosím oprávnění a ujistěte se, že je nepoužívá jiná aplikace. Extrahovat obrázky Extrahujte obrázky vložené do souborů PDF v původním rozlišení Tento soubor PDF neobsahuje žádné vložené obrázky Tento nástroj naskenuje každou stránku a obnoví zdrojové obrázky v plné kvalitě – ideální pro ukládání originálů z dokumentů Nakreslit podpis Parametry pera Použijte vlastní podpis jako obrázek pro umístění na dokumenty Zip PDF Rozdělte dokument s daným intervalem a zabalte nové dokumenty do zip archivu Interval Tisk PDF Připravte dokument pro tisk s vlastní velikostí stránky Stránky na list Orientace Velikost stránky Okraj Květ Měkké koleno Optimalizováno pro anime a kreslené filmy. Rychlé převzorkování s vylepšenými přirozenými barvami a menším počtem artefaktů Styl jako Samsung One UI 7 Zde zadejte základní matematické symboly pro výpočet požadované hodnoty (např. (5+5)*10) Matematický výraz Vyzvedněte až %1$s obrázků Udržujte datum a čas Vždy zachovejte exif značky související s datem a časem, funguje nezávisle na možnosti zachovat exif Barva Pozadí Pro Formáty Alfa Přidává možnost nastavit barvu pozadí pro každý formát obrázku s podporou alfa, pokud je tato možnost zakázána, je k dispozici pouze pro ty, které nejsou alfa Otevřete projekt Pokračujte v úpravách dříve uloženého projektu Image Toolbox Nelze otevřít projekt Image Toolbox V projektu Image Toolbox chybí data projektu Projekt Image Toolbox je poškozen Nepodporovaná verze projektu Image Toolbox: %1$d Uložit projekt Ukládejte vrstvy, pozadí a historii úprav do upravitelného souboru projektu Otevření se nezdařilo Zápis do prohledávatelného PDF Rozpoznejte text z obrázkové dávky a uložte prohledávatelné PDF s obrázkem a volitelnou textovou vrstvou Vrstva alfa Horizontální překlopení Vertikální překlopení Zámek Přidat stín Barva stínu Geometrie textu Roztažením nebo zkosením textu získáte ostřejší stylizaci Měřítko X Zkosit X Odebrat anotace Odstraňte vybrané typy poznámek, jako jsou odkazy, komentáře, zvýraznění, tvary nebo pole formulářů ze stránek PDF Hypertextové odkazy Přílohy souborů Čáry Vyskakovací okna Známky Tvary Textové poznámky Označení textu Pole formuláře Označení Neznámý Anotace Zrušit seskupení Přidejte stín rozostření za vrstvu s konfigurovatelnou barvou a posuny ================================================ FILE: core/resources/src/main/res/values-da/strings.xml ================================================ Fleksibel OK Beskær Kildekode Noget gik galt: %1$s Størrelse %1$s Indlæser… Billedet er for stort til at blive vist, men vil blive forsøgt gemt alligevel Vælg billede til at starte Bredde %1$s Højde %1$s Kvalitet Udvidelse Ændring af størrelse Eksplicit Vælg billede Er di sikker på, at du vil slukke appen? Lukning af app Bliv på Luk Nulstil billede Billedændringer vil blive rullet tilbage til startværdierne Værdierne nulstilles korrekt Nulstil Noget gik galt Genstart appen Kopieret til udklipsholderen Undtagelse Rediger EXIF Ingen EXIF-data fundet Tilføj tag Gem Klar Ryd EXIF Annuller Alle EXIF-data for billedet bliver slettet, og denne handling kan ikke fortrydes! Forudindstillinger Gemmer Alle ikke gemte ændringer vil gå tabt, hvis du forlader stedet nu Få de seneste opdateringer, diskutere spørgsmål og meget mere Enkelt ændring Ændre et enkelt billede Farvevælger Vælg farve fra billede, kopier eller del den Billede Farve Farve kopieret Beskær billedet Version Behold EXIF Billeder: %d Visning af ændringer Fjern Generer en farvepalet fra et givet billede Generere palette Palet Opdatering Ny version %1$s Ikke understøttet type: %1$s Kan ikke generere en palet for det givne billede Original Output-mappe Standard Tilpasset Uspecificeret Opbevaring af enheder Beskær i forhold til filstørrelse Maksimal størrelse i KB Ændre størrelsen på et billede efter en given størrelse i KB Sammenlign Sammenligne to givne billeder Vælg to billeder til at starte med Vælg billeder Indstillinger Nattilstand Mørk Lys System Dynamiske farver Tilpasning Tillad billedmonetering Hvis den er aktiveret, vil app-farverne blive overtaget til dette billede, når du vælger et billede til redigering Sprog Amoled-tilstand Hvis den er aktiveret, indstilles overfladenes farve til absolut mørk i nattilstand Farveskema Rød Grøn Blå Indsæt gyldig aRGB-kode. Intet at indsætte Kan ikke ændre appens farvepalette, mens dynamiske farver er slået til App-temaet vil være baseret på den farve, som du skal vælge Om appen Ingen opdateringer fundet Problemløser Send fejlrapporter og anmodninger om funktioner her Hjælp til at oversætte Korriger oversættelsesfejl eller lokalisere projektet til andre sprog Intet fundet ved din forespørgsel Søg her Hvis den er aktiveret, vil appens farver blive tilpasset tapetets farver Det lykkedes ikke at gemme %d billede(r) Primært Tertiær Sekundær Tykkelse af kant Overflade Værdier Tilføj Tilladelse Tilskud Appen skal have adgang til dit lager for at gemme billeder, det er nødvendigt, uden det kan den ikke fungere, så giv venligst tilladelse i den næste dialogboks Appen har brug for denne tilladelse for at fungere, giv den venligst manuelt Ekstern lagring Monet farver Denne applikation er helt gratis, men hvis du vil støtte udviklingen af projektet, kan du klikke her FAB-tilpasning Tjek for opdateringer Hvis den er aktiveret, vises opdateringsdialogboksen efter opstart af appen Billede zoom Del Præfiks Filenavn Anvend enhver filterkæde på givne billeder Filtre Lys Farvefilter Eksponering Skygger Dis Effekt Afstand Skærpe Sort og hvid Krydsskravering Mellemrum Halvtone Boks sløring Bilateral sløring Vignette Start Radius Bule Emoji Filudforsker Intet billede Farve Ændre størrelse ud fra øvre grænser Tilpas størrelsen for at følge givne bredde- og højdemål. Billedforhold bevares Kvantiseringsniveauer Ikke maksimal undertrykkelse Svag pixel inklusion stak sløring Genbestil Luminanstærskel Ændrer størrelsen på billeder til billeder med en lang side givet af parameteren Width eller Height, alle størrelsesberegninger vil blive udført efter lagring - bevarer billedformat Vælg hvilken emoji der skal vises på hovedskærmen Tilføj filstørrelse Hvis det er aktiveret, tilføjes bredden og højden af det gemte billede til navnet på outputfilen Slet EXIF Slet EXIF-metadata fra et par billeder Forhåndsvis enhver type billeder: GIF, SVG og så videre Billedkilde Fotovælger Galleri Android moderne fotovælger, som vises nederst på skærmen, fungerer muligvis kun på Android 12+ og har også problemer med at modtage EXIF-metadata Simpel galleribilledvælger, den fungerer kun, hvis du har den app Brug GetContent hensigt til at vælge billede, virker overalt, men kan også have problemer med at modtage udvalgte billeder på nogle enheder, det er ikke min skyld Options arrangement Redigere Bestille Skift rækkefølgen af værktøjer på hovedskærmen Billed link Fylde Passe Ændre billedstørrelse i forhold til brede og højde. Kan ændre proportioner Lysstyrke Kontrast Hue Mætning Tilføj filter Filter Alfa hvidbalance Temperatur Monokrom Gamma Højdepunkter og skygger Højdepunkter Hældning Sepia Negativ Solariser Livskraft Linjebredde Sobel kant Slør GCA farverum Gaussisk sløring Præg Laplacian Ende Kuwahara udjævning vægt Forvrængning Vinkel Hvirvel Dilatation Kuglebrydning Brydningsindeks Glaskuglebrydning Farve matrix Gennemsigtighed Skitse Grænseværdi Glat tone Toon Posterize Kig op Convolution 3x3 RGB filter Falsk farve Første farve Anden farve Hurtig sløring Sløringsstørrelse Sløring i midten x Sløring i midten y Zoom sløring Farvebalance Forhåndsvisning af billede Indlæs billede fra nettet Indlæs et hvilket som helst billede fra internettet, se et eksempel på det, zoom og gem eller rediger det, hvis du vil Indholdsskala Emojis tæller sekvensNum originalt filnavn Tilføj originalt filnavn Hvis det er aktiveret, tilføjes det originale filnavn i navnet på outputbilledet Tilføjelse af originalt filnavn virker ikke, hvis billedkilden til billedvælgeren er valgt Udskift sekvensnummer Hvis aktiveret, erstatter standardtidsstemplet til billedsekvensnummeret, hvis du bruger batchbehandling Cache størrelse Du deaktiverede Filer-appen, aktiver den for at bruge denne funktion Tegne Tegn på billede som i en skitsebog, eller tegn på selve baggrunden Maling farve Sekundær tilpasning Vælg et billede og tegn noget på det Krypter og dekrypter enhver fil (ikke kun billedet) baseret på forskellige algoritmer Vælg fil Krypter Dekryptering AES-256, GCM-tilstand, ingen polstring, 12 bytes tilfældige IV\'er. Nøgler bruges som SHA-3 hashes (256 bit). Hvis du forsøger at gemme billedet med de aktuelle højde/bredde-parametre, risikerer du at appen crasher og du taber dit arbejder. Du fortsætter derfor på EGEN RISIKO! Værktøjer Gruppér muligheder efter type Rediger skærmbillede Fallback mulighed Springe Kopi Lagring i tilstanden %1$s kan være ustabil, fordi det er et tabsfrit format Ugyldig adgangskode eller valgt fil er ikke krypteret Funktioner Cache Fundet %1$s Skærmbillede Male alfa Tegn på billede Tegn på baggrund Vælg baggrundsfarve og tegn oven på den Baggrundsfarve Chiffer Hvis aktiveret, vil app-cachen blive ryddet ved app-start skab Grupper muligheder på hovedskærmen af deres type i stedet for tilpasset listearrangement Kan ikke ændre arrangement, mens valgmulighedsgruppering er aktiveret Password-baseret kryptering af filer. Fortsatte filer kan gemmes i den valgte mappe eller deles. Dekrypterede filer kan også åbnes direkte. Automatisk cacherydning Dekrypter Vælg fil for at starte Kryptering Nøgle Gem denne fil på din enhed, eller brug delehandlingen til at placere den, hvor du vil Implementering Kompatibilitet Filstørrelse Den maksimale filstørrelse er begrænset af Android OS og den tilgængelige hukommelse, hvilket naturligvis afhænger af din enhed. \nBemærk venligst: Hukommelsen er ikke lager. Bemærk venligst, at kompatibilitet med anden filkrypteringssoftware eller -tjenester ikke er garanteret. En lidt anderledes nøglebehandling eller chifferkonfiguration kan være årsager til inkompatibilitet. Filen er behandlet Forbedret pixelering Slagpixelering Side Styrke Kontakt mig Skrift skala Gennemsigtige mellemrum omkring billedet vil blive beskåret Slet baggrund Slet Størrelsesforhold Gemt i mappen %1$s Symboler Brug denne masketype til at oprette maske fra et givet billede, bemærk at billedet SKAL have alfakanal (gennemsigtig baggrund) Aktiver emoji Tilfældig filnavn Gendan baggrund Gendan appindstillinger fra tidligere genereret fil En værdi på %1$s betyder en hurtig komprimering, hvilket resulterer i en relativt stor filstørrelse. %2$s betyder en langsommere komprimering, hvilket resulterer i en mindre fil. Enkelt kolonne Fejl Beløb Frø Du er ved at slette den valgte filtermaske. Denne handling kan ikke fortrydes Farveanomali Der blev ikke fundet nogen \"%1$s\"-mappe. Vi har ændret den til standard, gem venligst filen igen Udklipsholder Automatisk pin Overskriv filer Tom Suffiks Gratis Hvis du har valgt forudindstilling 125, vil billedet blive gemt som 125 % størrelse af det originale billede. Hvis du vælger forudindstilling 50, vil billedet blive gemt med 50 % størrelse Forudindstilling her bestemmer % af filstørrelsen. Hvis du vælger 50% på 5mb billede, vil du få 2,5mb billede efter lagring Hvis det er aktiveret, vil outputfilnavnet være helt tilfældigt Diskuter appen og få feedback fra andre brugere. Du kan også få betaopdateringer og indsigt her. Gendan Sikkerhedskopier dine appindstillinger til en fil Indstillinger blev gendannet Du er ved at slette det valgte farveskema. Denne handling kan ikke fortrydes Slet palette Tekst Standard Følelser Mad og drikke Natur og Dyr Objekter Rejser og steder Aktiviteter Baggrundsfjerner Fjern baggrunden fra billedet ved at tegne eller brug indstillingen Auto Trim billede Originale billedmetadata vil blive bevaret Slet automatisk baggrund Gendan billede Slet tilstand Sløringsradius Pipette Tegnetilstand Opret problem Ups… Noget gik galt. Du kan skrive til mig ved at bruge mulighederne nedenfor, og jeg vil prøve at finde en løsning Tilpas størrelse og konverter Skift størrelse på givne billeder eller konverter dem til andre formater. EXIF-metadata kan også redigeres her, hvis du vælger et enkelt billede. Analytics Tillad indsamling af anonym appbrugsstatistik I øjeblikket tillader formatet %1$s kun læsning af EXIF-metadata på Android. Outputbilledet vil slet ikke have metadata, når det er gemt. Vente Opdateringer Tillad betaversioner Opdateringskontrol inkluderer beta-appversioner, hvis den er aktiveret Tegn pile Hvis aktiveret, vil tegnestien blive repræsenteret som en pegepil Vandret Skaler små billeder til store Lodret Små billeder vil blive skaleret til det største i sekvensen, hvis det er aktiveret Billedrækkefølge Pixelering Forbedret diamantpixelering Diamantpixelering Cirkelpixelering Forbedret cirkelpixelering Udskift farve Farve der skal udskiftes Målfarve Farve der skal fjernes Fjern farve Faldende kanter handicappet Begge Søg Gør det muligt at søge gennem alle tilgængelige værktøjer på hovedskærmen Betjen med PDF-filer: Forhåndsvisning, Konverter til batch af billeder eller opret et fra givne billeder Forhåndsvisning af PDF PDF til billeder Billeder til PDF Enkel PDF-forhåndsvisning Konverter PDF til billeder i givet outputformat Pak givne billeder ind i output PDF-fil Tilføj maske Maske %d Tegnet filtermaske vil blive gengivet for at vise dig det omtrentlige resultat Slet maske Simple varianter Highlighter Neon Pen Sløring af privatliv Tegn semi-transparente, skærpede highlighter-baner Tilføj en glødende effekt til dine tegninger Standard en, enklest - kun farven Slører billedet under den tegnede sti for at sikre alt, hvad du vil skjule Svarende til sløring af privatlivets fred, men pixelerer i stedet for sløring Automatisk rotation Tillader, at begrænsningsboksen anvendes til billedorientering Tegnestitilstand Dobbelt linje pil Gratis tegning Dobbelt pil Linje pil Tegner en dobbeltpegende pil fra startpunkt til slutpunkt som en linje Tegner en dobbeltpegende pil fra en given sti Skitseret Oval Skitseret Rect Oval Tilføjer automatisk gemt billede til udklipsholder, hvis det er aktiveret Vibration Vibrationsstyrke For at overskrive filer skal du bruge \"Explorer\" billedkilde, prøv gentag billeder, vi har ændret billedkilde til den nødvendige Den originale fil vil blive erstattet med en ny i stedet for at gemme i den valgte mappe, denne mulighed skal være billedkilden \"Explorer\" eller GetContent, når du skifter dette, indstilles den automatisk Kattemuld Spline Grundlæggende Bedre skaleringsmetoder omfatter Lanczos-resampling og Mitchell-Netravali-filtre En af de nemmere måder at øge størrelsen på ved at erstatte hver pixel med et antal pixels af samme farve Den enkleste android-skaleringstilstand, der bruges i næsten alle apps Metode til jævn interpolering og resampling af et sæt kontrolpunkter, der almindeligvis bruges i computergrafik til at skabe jævne kurver Vinduesfunktion anvendes ofte i signalbehandling for at minimere spektral lækage og forbedre nøjagtigheden af frekvensanalyse ved at tilspidse kanterne af et signal Matematisk interpolationsteknik, der bruger værdierne og afledte ved endepunkterne af et kurvesegment til at generere en jævn og kontinuerlig kurve Resampling-metode, der opretholder interpolation af høj kvalitet ved at anvende en vægtet sinc-funktion på pixelværdierne Resampling-metode, der bruger et foldningsfilter med justerbare parametre for at opnå en balance mellem skarphed og anti-aliasing i det skalerede billede Hent Ingen forbindelse, tjek det og prøv igen for at downloade togmodeller Downloadede sprog Segmenteringstilstand Bedøm app Kun scriptregistrering & Auto Automatisk orientering & Scriptgenkendelse Kun auto Enkelt blok lodret tekst Enkelt blok Enkelt linje Enkelt ord Cirkel ord Enkelt forkullet Sparsom tekst Sparsom tekstorientering & Scriptdetektion Rå linje Vil du slette sprog \"%1$s\" OCR-træningsdata for alle genkendelsestyper, eller kun for den valgte (%2$s)? Nuværende Alle Lineær Radial Feje Gradient type Center X Center Y Flisetilstand Håndhævelse af lysstyrke Skærm Gradient Overlay Vandmærke Dæk billeder med brugerdefinerbare tekst/billede vandmærker Gentag vandmærke Gentager vandmærke over billede i stedet for enkelt på en given position Offset X Offset Y Dette billede vil blive brugt som mønster til vandmærkning Tekst farve GIF værktøjer Konverter billeder til GIF-billede eller udtræk rammer fra givet GIF-billede GIF til billeder Konverter GIF-fil til batch af billeder Konverter batch af billeder til GIF-fil Billeder til GIF Vælg GIF-billede for at starte Brug størrelsen af den første ramme Udskift den angivne størrelse med de første rammemål Gentag optælling Frame Delay millis FPS Dithering Quantizier Gråskala Bayer to og to dithering Bayer tre af tre dithering Bayer fire af fire dithering B Spline Anvender stykkevis definerede bikubiske polynomielle funktioner til jævnt at interpolere og tilnærme en kurve eller overflade, fleksibel og kontinuert formrepræsentation Indbygget stak sløring Forbedret fejl Kanalskift X Kanalskift Y Korruptionsstørrelse Korruption Shift X Korruptionsskift Y Telt sløring Side fade Top Bund Anisotropisk diffusion Diffusion Ledning Logaritmisk tonekortlægning Hable Filmic Tone Mapping Farve Matrix 4x4 Farvematrix 3x3 Simple effekter Polaroid Tritonomali Deutaromali Protonomali Coda Chrome Nattesyn Tritanopia Protanopia Akromatomali Achromatopsi Korn Uskarp Pastel Orange dis Lyserød drøm Gyldne time Varm sommer Lilla tåge Solopgang Lemonade lys Spektral ild Fantasy Landskab Elektrisk gradient Karamelmørke Rumportal Rød hvirvel Digital kode Ikon form Drago Aldridge Skære af Uchimura Mobius Overgang Spids Billeder overskrevet ved den oprindelige destination Kan ikke ændre billedformat, mens indstillingen for overskrivning af filer er aktiveret Emoji som farveskema Bruger emoji primærfarve som app-farveskema i stedet for manuelt defineret Telegram chat Beskær maske Brug af store skriftstørrelser kan forårsage fejl i brugergrænsefladen og problemer, som ikke bliver rettet. Brug forsigtigt. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz Ææ Øø Åå 0123456789 !? Billeder vil blive beskåret fra midten til den indtastede størrelse. Lærredet vil blive udvidet med en given baggrundsfarve, hvis billedet er mindre end de indtastede dimensioner. Tolerance Omkode Erodere Vandret vindsvingning ACES Filmic Tone Mapping ACES Hill Tone Mapping Hurtig bilateral sløring Poisson sløring Krystalliser Slagfarve Fraktal glas Amplitude Marmor Turbulens Olie Vand effekt Størrelse Frekvens X Frekvens Y Amplitude X Amplitude Y Perlin forvrængning Hejl Burgess Tone Mapping Fuldt filter Start Centrum Ende Anvend eventuelle filterkæder på givne billeder eller enkeltbilleder Gradient Maker Opret gradient af given outputstørrelse med tilpassede farver og udseendetype Fart Dehaze Omega PDF-værktøjer Sats Denne app er helt gratis, hvis du vil have den til at blive større, så stjerne projektet på Github 😄 Årgang Browni Varm Fedt nok Deutaronotopia Gentaget Spejl Klemme Decal Farve stopper Tilføj farve Ejendomme E-mail Pil Linje Tegner sti som inputværdi Tegner stien fra startpunkt til slutpunkt som en linje Tegner en pil fra startpunkt til slutpunkt som en linje Tegner en pil fra en given sti Rect Tegner oval fra startpunkt til slutpunkt Tegner ret fra startpunkt til slutpunkt Tegner ovalt skitseret fra startpunkt til slutpunkt Tegner skitseret rekt fra startpunkt til slutpunkt Bayer Eight By Eight Dithering Floyd Steinberg Dithering Jarvis Judice Ninke Dithering Sierra Dithering To Rækker Sierra Dithering Sierra Lite dithering Atkinson dithering Stucki Dithering Burkes Dithering Falsk Floyd Steinberg Dithering Venstre til højre dithering Tilfældig dithering Simpel Threshold Dithering Max farveantal Dette gør det muligt for appen at indsamle nedbrudsrapporter automatisk Maske farve Forhåndsvisning af maske Skaleringstilstand Bilineær Hann Hermite Lanczos Mitchell Nærmeste Standard værdi Værdi i området %1$s - %2$s Sigma Rumlig Sigma Median sløring Bikubisk Lineær (eller bilineær, i to dimensioner) interpolation er typisk god til at ændre størrelsen på et billede, men forårsager en uønsket blødgøring af detaljer og kan stadig være noget takket Anvender stykkevis definerede polynomielle funktioner til jævnt at interpolere og tilnærme en kurve eller overflade, fleksibel og kontinuerlig formrepræsentation Kun klip Gem til lager vil ikke blive udført, og billedet vil kun blive forsøgt at lægge i udklipsholderen Tilføjer beholder med valgt form under de førende ikoner for kort Billedsøm Kombiner de givne billeder for at få et stort Gemt i mappen %1$s med navnet %2$s Komponer en hvilken som helst gradient af toppen af et givet billede Transformationer Kamera Bruger kamera til at tage billede, bemærk at det kun er muligt at få ét billede fra denne billedkilde Vælg mindst 2 billeder Output billedskala Billedorientering Farverig hvirvel Blødt forårslys Efterårs toner Lavendel drøm Cyberpunk Nattemagi Farveeksplosion Futuristisk gradient Grøn Sol Rainbow World Mørke lilla Vandmærke Type Overlejringstilstand Pixel størrelse Lås tegneretningen Dette vil rulle dine indstillinger tilbage til standardværdierne. Bemærk, at dette ikke kan fortrydes uden en sikkerhedskopifil nævnt ovenfor. Hvis det er aktiveret i tegnetilstand, vil skærmen ikke rotere Bokeh Brug Lasso Bruger Lasso som i tegnetilstand til at udføre sletning Forhåndsvisning af originalt billede Alpha Maskefilter Anvend filterkæder på givne maskerede områder, hvert maskeområde kan have sit eget sæt af filtre Masker Appbar-emoji vil løbende blive ændret tilfældigt i stedet for at bruge den valgte Tilfældige emojis Kan ikke bruge tilfældig emojivalg, mens emojis er deaktiveret Kan ikke vælge en emoji, mens du vælger en tilfældig aktiveret Søg efter opdateringer Indsats Backup og genskab Backup Ødelagt fil eller ikke en sikkerhedskopi Skrifttype Lagring næsten fuldført. Hvis du annullerer nu, skal du gemme igen. Børstes blødhed Donation Gammelt tv Bland sløring OCR (genkend tekst) Genkend tekst fra et givet billede, 120+ sprog understøttet Billedet har ingen tekst, eller appen fandt det ikke Accuracy: %1$s Genkendelsestype Hurtig Standard Bedst Ingen data For at Tesseract OCR fungerer korrekt, skal yderligere træningsdata (%1$s) downloades til din enhed. \nVil du downloade %2$s-data? Tilgængelige sprog Pensel vil gendanne baggrunden i stedet for at slette Vandret gitter Lodret gitter Sømtilstand Rækker tæller Antal kolonner Brug Pixel Switch Pixel-lignende switch vil blive brugt i stedet for Googles materiale, du baserede en Glide Side om side Skift Tryk Gennemsigtighed Overskrevet fil med navn %1$s på den oprindelige destination Forstørrelsesglas Aktiverer forstørrelsesglas øverst på fingeren i tegnetilstande for bedre tilgængelighed Tving startværdi Tvinger exif-widget til at blive tjekket indledningsvis Tillad flere sprog Favorit Ingen favoritfiltre tilføjet endnu Tilt Shift Fast Slørede kanter Tegner slørede kanter under originalbilledet for at udfylde mellemrum omkring det i stedet for en enkelt farve, hvis det er aktiveret Omvendt fyldtype Hvis aktiveret, vil alle ikke-maskerede områder blive filtreret i stedet for standardadfærd Konfetti Konfetti vil blive vist ved lagring, deling og andre primære handlinger Sikker tilstand Skjuler indhold ved afslutning, og skærmen kan heller ikke optages eller optages Palette stil Tonalt sted Neutral Levende Udtryksfuldt Regnbue Frugtsalat Troskab Indhold Standard paletstil, det giver mulighed for at tilpasse alle fire farver, andre tillader dig kun at indstille nøglefarven En stil, der er lidt mere kromatisk end monokrom Et højt tema, farverighed er maksimal for Primær palet, øget for andre Et legende tema - kildefarvens nuance vises ikke i temaet Et monokromt tema, farverne er rent sort / hvid / grå Et skema, der placerer kildefarven i Scheme.primaryContainer En ordning, der minder meget om indholdsordningen Containere Tegn skygger bag beholdere Skydere Afbrydere FAB\'er Knapper Tegn skygge bag skydere Tegn skygge bag kontakter Aktiverer skyggetegning bag flydende handlingsknapper Aktiverer skyggetegning bag standardknapper App barer Aktiverer skyggetegning bag appbjælker Denne opdateringskontrol vil oprette forbindelse til GitHub for at kontrollere, om der er en ny opdatering tilgængelig Opmærksomhed Inverter farver Erstatter temafarver til negative, hvis aktiveret Afslut Hvis du forlader forhåndsvisningen nu, bliver du nødt til at tilføje billederne igen Lasso Tegner lukket udfyldt sti efter given sti Billedformat Anaglyph Støj Pixel Sort Bland Opretter \"Material You\" palette fra billede Mørke Farver Bruger nattilstand farveskema i stedet for lys variant Kopier som\" Jetpack Compose \" kode Ringsløring Kryds sløring Cirkel sløring Stjernesløring Lineær Tilt Shift Tags At Fjerne Konverter batch af billeder til APNG-fil Bevægelsessløring APNG-værktøjer Konverter billeder til APNG-billede eller udtræk rammer fra givet APNG-billede Konverter APNG-fil til batch af billeder Billeder til APNG Vælg APNG-billede for at starte APNG til billeder Lynlås Opret zip-fil fra givne filer eller billeder Trækhåndtagsbredde Standard beskæring Beskær til indhold af billedet Konfetti type Festligt Eksplodere Regn Hjørner JXL værktøj Udfør JXL ~ JPEG-omkodning uden kvalitetstab, eller konverter GIF/APNG til JXL-animation JXL til JPEG Udfør tabsfri omkodning fra JXL til JPEG Udfør tabsfri omkodning fra JPEG til JXL JPEG til JXL Vælg JXL-billede for at starte Hurtig Gaussisk sløring 2D Hurtig Gaussisk sløring 3D Hurtig Gaussisk sløring 4D Bil påske Tillader, at appen automatisk indsætter data fra udklipsholderen, så de vises på hovedskærmen, og du vil være i stand til at behandle dem Harmoniseringsfarve Harmoniseringsniveau Lanczos Bessel Resampling-metode, der opretholder interpolation af høj kvalitet ved at anvende en Bessel-funktion (jinc) på pixelværdierne GIF til JXL Konverter GIF-billeder til JXL-animerede billeder APNG til JXL Konverter APNG-billeder til JXL-animerede billeder JXL til billeder Konverter JXL-animation til batch af billeder Billeder til JXL Konverter batch af billeder til JXL-animation Opførsel Spring over filvalg Filvælger vil blive vist med det samme, hvis dette er muligt på den valgte skærm Generer forhåndsvisninger Aktiverer forhåndsvisning, dette kan hjælpe med at undgå nedbrud på nogle enheder, dette deaktiverer også nogle redigeringsfunktioner inden for en enkelt redigeringsmulighed Lossy kompression Bruger komprimering med tab til at reducere filstørrelsen i stedet for tabsfri Kompressionstype Styrer den resulterende billedafkodningshastighed, dette skulle hjælpe med at åbne det resulterende billede hurtigere, værdien %1$s betyder langsomste afkodning, hvorimod %2$s - hurtigst, denne indstilling kan øge outputbilledets størrelse Sortering Dato Dato (omvendt) Navn Navn (omvendt) Kanalkonfiguration I dag I går Embedded Picker Image Toolbox\'s billedvælger Ingen tilladelser Anmodning Vælg flere medier Vælg Single Media Plukke Prøv igen Vis indstillinger i liggende Hvis dette er deaktiveret, åbnes indstillingerne i landskabstilstand på knappen i den øverste applinje som altid, i stedet for permanent synlig mulighed Indstillinger for fuld skærm Aktiver det, og indstillingssiden vil altid blive åbnet som fuldskærm i stedet for forskydeligt skuffeark Switch Type Skriv En Jetpack Compose Materiale Du skifter Et materiale, du skifter Maks Ændr størrelse på anker Pixel Flydende En switch baseret på \"Fluent\" designsystemet Cupertino En switch baseret på \"Cupertino\" designsystemet Billeder til SVG Spor givne billeder til SVG-billeder Brug Sampled Palette Kvantiseringspaletten vil blive samplet, hvis denne indstilling er aktiveret Udelad sti Brug af dette værktøj til sporing af store billeder uden nedskalering anbefales ikke, det kan forårsage nedbrud og øge behandlingstiden Nedskaleret billede Billedet vil blive nedskaleret til lavere dimensioner før behandling, dette hjælper værktøjet til at arbejde hurtigere og mere sikkert Minimum farveforhold Linjer Tærskel Kvadratisk tærskel Koordinater afrundingstolerance Sti Skala Nulstil egenskaber Alle egenskaber vil blive sat til standardværdier, bemærk at denne handling ikke kan fortrydes Detaljeret Standard linjebredde Motortilstand Arv LSTM netværk Legacy & LSTM Konvertere Konverter billedbatches til givet format Tilføj ny mappe Bits pr. prøve Kompression Fotometrisk fortolkning Prøver pr. pixel Plan konfiguration Y Cb Cr Sub Sampling Y Cb Cr Positionering X opløsning Y Opløsning Opløsningsenhed Strip offsets Rækker pr. strimmel Strip Byte Counts JPEG-udvekslingsformat JPEG Interchange Format Længde Overførselsfunktion Hvidt punkt Primære kromaticiteter Y Cb Cr-koefficienter Reference Sort Hvid Dato Tid Billedbeskrivelse Lave Model Software Kunstner Copyright Exif version Flashpix version Farverum Gamma Pixel X Dimension Pixel Y-dimension Komprimerede bits pr. pixel Maker Note Brugerkommentar Relateret lydfil Dato Tid Original Dato Tid Digitaliseret Offset tid Offset Time Original Offset Tid Digitaliseret Undersek. tid Undersek. Tid Original Undersek. Tid digitaliseret Eksponeringstid F nummer Eksponeringsprogram Spektral følsomhed Fotografisk følsomhed Oecf Følsomhedstype Standard udgangsfølsomhed Anbefalet eksponeringsindeks ISO hastighed ISO Speed ​​Latitude yyy ISO Speed ​​Latitude zzz Lukkerhastighedsværdi Blændeværdi Lysstyrkeværdi Værdi for eksponeringsbias Max blændeværdi Emne afstand Måletilstand Blitz Fagområde Brændvidde Flash energi Rumlig frekvensrespons Focal Plane X-opløsning Brændplan Y-opløsning Focal Plane Resolution Unit Emnets placering Eksponeringsindeks Sensing metode Filkilde CFA mønster Brugerdefineret gengivet Eksponeringstilstand Hvidbalance Digitalt zoomforhold Brændvidde i 35 mm film Sceneoptagelsestype Få kontrol Kontrast Mætning Skarphed Beskrivelse af enhedsindstilling Emneafstand Billede unikt ID Navn på kameraejer Kroppens serienummer Objektivspecifikation Lens Make Objektiv model Objektivets serienummer GPS-versions-id GPS Latitude Ref GPS-breddegrad GPS Længdegrad Ref GPS Længdegrad GPS Højde Ref GPS højde GPS tidsstempel GPS satellitter GPS-status GPS måletilstand GPS DOP GPS-hastighed Ref GPS hastighed GPS Track Ref GPS spor GPS billede Ref GPS billedretning GPS-kort dato GPS Dest Latitude Ref GPS Dest Latitude GPS Dest Længdegrad Ref GPS Dest Længdegrad GPS Dest Leje Ref GPS Dest Bearing GPS Dest Distance Ref GPS-destinationsafstand GPS-behandlingsmetode GPS-områdeinformation GPS-datostempel GPS differentiale GPS H-positioneringsfejl Interoperabilitetsindeks DNG-version Preview Image Start Forhåndsvisning af billedlængde Aspektramme Sensor nederste kant Sensor venstre kant Sensor højre kant Sensor øverste kant ISO Tegn tekst på stien med givet skrifttype og farve Skriftstørrelse Vandmærke størrelse Gentag tekst Den aktuelle tekst vil blive gentaget, indtil stien slutter i stedet for at tegne én gang Dash størrelse Brug det valgte billede til at tegne det langs en given sti Dette billede vil blive brugt som gentagen indtastning af tegnet sti Tegner en skitseret trekant fra startpunkt til slutpunkt Tegner en skitseret trekant fra startpunkt til slutpunkt Skitseret trekant Trekant Tegner polygon fra startpunkt til slutpunkt Polygon Skitseret polygon Tegner en polygon med skitsering fra startpunkt til slutpunkt Toppunkter Tegn almindelig polygon Tegn polygon, som vil være regulær i stedet for fri form Tegner stjerne fra startpunkt til slutpunkt Stjerne Skitseret Stjerne Tegner skitseret stjerne fra startpunkt til slutpunkt Indre radiusforhold Tegn almindelig stjerne Tegn stjerne, som vil være almindelig i stedet for fri form Antialias Aktiverer antialiasing for at forhindre skarpe kanter Åbn Rediger i stedet for forhåndsvisning Når du vælger billede, der skal åbnes (forhåndsvisning) i ImageToolbox, åbnes redigeringsarket i stedet for forhåndsvisning Dokument scanner Scan dokumenter og opret PDF eller adskil billeder fra dem Klik for at starte scanningen Start scanning Gem som pdf Del som pdf Nedenstående muligheder er til at gemme billeder, ikke PDF Udlign Histogram HSV Udlign histogram Indtast procent Tillad indtastning efter tekstfelt Aktiverer tekstfelt bag forudindstillinger, for at indtaste dem med det samme Skala farverum Lineær Udlign histogrampixelering Gitterstørrelse X Gitterstørrelse Y Udlign Histogram Adaptive Udlign Histogram Adaptive LUV Udlign Histogram Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Ramme farve Farve at ignorere Skabelon Ingen skabelonfiltre tilføjet Opret ny Den scannede QR-kode er ikke en gyldig filterskabelon Scan QR-kode Den valgte fil har ingen filterskabelondata Opret skabelon Skabelonnavn Dette billede vil blive brugt til at få vist denne filterskabelon Skabelonfilter Som QR-kode billede Som fil Gem som fil Gem som QR-kodebillede Slet skabelon Du er ved at slette det valgte skabelonfilter. Denne handling kan ikke fortrydes Tilføjet filterskabelon med navnet \"%1$s\" (%2$s) Forhåndsvisning af filter QR & stregkode Scan QR-koden og få dens indhold, eller indsæt din streng for at generere en ny Kodeindhold Scan en hvilken som helst stregkode for at erstatte indhold i feltet, eller skriv noget for at generere ny stregkode med den valgte type QR beskrivelse Min Giv kameratilladelse i indstillingerne til at scanne QR-kode Giv kameratilladelse i indstillingerne til at scanne dokumentscanner Kubik B-Spline Hamming Hanning Blackman Welch Quadric Gaussisk Sphinx Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-He Boks Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kubisk interpolation giver jævnere skalering ved at overveje de nærmeste 16 pixels, hvilket giver bedre resultater end bilineær Anvender stykkevis definerede polynomielle funktioner til jævnt at interpolere og tilnærme en kurve eller overflade, fleksibel og kontinuerlig formrepræsentation En vinduesfunktion, der bruges til at reducere spektral lækage ved at tilspidse kanterne på et signal, hvilket er nyttigt i signalbehandling En variant af Hann-vinduet, der almindeligvis bruges til at reducere spektral lækage i signalbehandlingsapplikationer En vinduesfunktion, der giver god frekvensopløsning ved at minimere spektral lækage, ofte brugt i signalbehandling En vinduesfunktion designet til at give god frekvensopløsning med reduceret spektral lækage, der ofte bruges i signalbehandlingsapplikationer En metode, der bruger en kvadratisk funktion til interpolation, hvilket giver jævne og kontinuerlige resultater En interpolationsmetode, der anvender en Gauss-funktion, nyttig til at udjævne og reducere støj i billeder En avanceret resamplingmetode, der giver interpolation af høj kvalitet med minimale artefakter En trekantet vinduesfunktion, der bruges i signalbehandling for at reducere spektral lækage En interpolationsmetode af høj kvalitet, der er optimeret til naturlig billedstørrelse, balancering af skarphed og glathed En skarpere variant af Robidoux-metoden, optimeret til skarp billedstørrelse En spline-baseret interpolationsmetode, der giver jævne resultater ved hjælp af et 16-taps filter En spline-baseret interpolationsmetode, der giver jævne resultater ved hjælp af et 36-taps filter En spline-baseret interpolationsmetode, der giver jævne resultater ved hjælp af et 64-taps filter En interpolationsmetode, der bruger Kaiser-vinduet, der giver god kontrol over afvejningen mellem hovedlobens bredde og sidelobens niveau En hybrid vinduesfunktion, der kombinerer Bartlett og Hann vinduerne, bruges til at reducere spektral lækage i signalbehandling En simpel resamplingmetode, der bruger gennemsnittet af de nærmeste pixelværdier, hvilket ofte resulterer i et blokeret udseende En vinduesfunktion, der bruges til at reducere spektral lækage, hvilket giver god frekvensopløsning i signalbehandlingsapplikationer En resamplingmetode, der bruger et 2-lobs Lanczos-filter til interpolation af høj kvalitet med minimale artefakter En resamplingmetode, der bruger et 3-lobs Lanczos-filter til interpolation af høj kvalitet med minimale artefakter En resamplingmetode, der bruger et 4-lobs Lanczos-filter til interpolation af høj kvalitet med minimale artefakter En variant af Lanczos 2-filteret, der bruger jinc-funktionen, der giver interpolation af høj kvalitet med minimale artefakter En variant af Lanczos 3-filteret, der bruger jinc-funktionen, der giver interpolation af høj kvalitet med minimale artefakter En variant af Lanczos 4-filteret, der bruger jinc-funktionen, der giver interpolation af høj kvalitet med minimale artefakter Hanning EWA Elliptical Weighted Average (EWA) variant af Hanning-filteret til jævn interpolation og resampling Robidoux EWA Elliptical Weighted Average (EWA) variant af Robidoux-filteret til resampling af høj kvalitet Blackman EVE Elliptical Weighted Average (EWA) variant af Blackman-filteret for at minimere ringeartefakter Quadric EWA Elliptical Weighted Average (EWA) variant af Quadric-filteret for jævn interpolation Robidoux Sharp EWA Elliptical Weighted Average (EWA) variant af Robidoux Sharp-filteret for skarpere resultater Lanczos 3 Jinc EWA Elliptical Weighted Average (EWA) variant af Lanczos 3 Jinc-filteret til resampling af høj kvalitet med reduceret aliasing Ginseng Et resamplingfilter designet til billedbehandling af høj kvalitet med en god balance mellem skarphed og glathed Ginseng EWA Elliptical Weighted Average (EWA) variant af Ginseng-filteret for forbedret billedkvalitet Lanczos Sharp EWA Elliptical Weighted Average (EWA) variant af Lanczos Sharp-filteret for at opnå skarpe resultater med minimale artefakter Lanczos 4 Skarpeste EWA Elliptical Weighted Average (EWA)-variant af Lanczos 4 Sharpest-filteret til ekstremt skarp billedresampling Lanczos Soft EWA Elliptical Weighted Average (EWA) variant af Lanczos Soft-filteret for jævnere billedresampling Haasn blød Et resampling-filter designet af Haasn til jævn og artefaktfri billedskalering Formatkonvertering Konverter batch af billeder fra et format til et andet Afskedig for evigt Billedstabling Stak billeder oven på hinanden med de valgte blandingstilstande Tilføj billede Beholdere tæller Clahe HSL Clahe HSV Udlign Histogram Adaptive HSL Udlign Histogram Adaptive HSV Edge Mode Klip Indpakning Farveblindhed Vælg tilstand for at tilpasse temafarver til den valgte farveblindhedsvariant Svært ved at skelne mellem røde og grønne nuancer Svært ved at skelne mellem grønne og røde nuancer Svært ved at skelne mellem blå og gule nuancer Manglende evne til at opfatte røde nuancer Manglende evne til at opfatte grønne nuancer Manglende evne til at opfatte blå nuancer Reduceret følsomhed over for alle farver Fuldstændig farveblindhed, ser kun gråtoner Brug ikke farveblind-skemaet Farverne vil være nøjagtigt som angivet i temaet Sigmoidal Lagrange 2 Et Lagrange-interpolationsfilter af størrelsesorden 2, velegnet til billedskalering af høj kvalitet med jævne overgange Lagrange 3 Et Lagrange-interpolationsfilter af størrelsesorden 3, der giver bedre nøjagtighed og jævnere resultater til billedskalering Lanczos 6 Et Lanczos resampling-filter med en højere orden på 6, hvilket giver skarpere og mere nøjagtig billedskalering Lanczos 6 Jinc En variant af Lanczos 6-filteret, der bruger en Jinc-funktion for forbedret billedresampling-kvalitet Sløring af lineær boks Lineær teltsløring Lineær Gaussisk bokssløring Lineær stak sløring Gaussisk boks sløring Lineær Hurtig Gaussisk sløring Næste Lineær hurtig Gaussisk sløring Lineær Gaussisk sløring Vælg et filter for at bruge det som maling Udskift filter Vælg filter nedenfor for at bruge det som pensel i din tegning TIFF-komprimeringsskema Lav poly Sandmaleri Billedopdeling Opdel enkelt billede efter rækker eller kolonner Fit To Bounds Kombiner beskæringsændringstilstand med denne parameter for at opnå den ønskede adfærd (Beskær/tilpas til billedformat) Sprog blev importeret Backup OCR-modeller Importere Eksportere Position Centrum Øverst til venstre Øverst til højre Nederst til venstre Nederst til højre Top Center Center højre Nederst i midten Midt til venstre Målbillede Palette overførsel Forbedret olie Simpelt gammelt TV HDR Gotham Simpel skitse Blød glød Farve plakat Tri Tone Tredje farve Clahe Oklab Clara Olch Clahe Jzazbz Polka Dot Clustered 2x2 Dithering Clustered 4x4 dithering Clustered 8x8 dithering Yililoma dithering Ingen foretrukne valgmuligheder valgt, tilføj dem på siden med værktøjer Tilføj favoritter Komplementær Analog Triadisk Split komplementær tetradisk Firkant Analog + Komplementær Farveværktøjer Mix, lav toner, generer nuancer og mere Farveharmonier Farveskygge Variation Farver Toner Nuancer Farveblanding Farve info Valgt farve Farve at blande Kan ikke bruge monet, mens dynamiske farver er slået til 512x512 2D LUT Mål LUT-billede En amatør Frøken Etikette Blød elegance Soft Elegance Variant Palette Transfer Variant 3D LUT Mål 3D LUT-fil (.cube / .CUBE) LUT Bleach Bypass Stearinlys Drop Blues Edgy Amber Efterårsfarver Filmlager 50 Tåget nat Kodak Få et neutralt LUT-billede Brug først dit foretrukne fotoredigeringsprogram til at anvende et filter på neutral LUT, som du kan få her. For at dette skal fungere korrekt, må hver pixelfarve ikke afhænge af andre pixels (f.eks. virker sløring ikke). Når du er klar, skal du bruge dit nye LUT-billede som input til 512*512 LUT-filter Pop Art Celluloid Kaffe Gyldne Skov Grønlig Retro gul Forhåndsvisning af links Aktiverer hentning af linkforhåndsvisning på steder, hvor du kan hente tekst (QRCode, OCR osv.) Links ICO-filer kan kun gemmes i den maksimale størrelse på 256 x 256 GIF til WEBP Konverter GIF-billeder til WEBP-animerede billeder WEBP-værktøjer Konverter billeder til WEBP-animerede billeder eller udtræk rammer fra en given WEBP-animation WEBP til billeder Konverter WEBP-fil til batch af billeder Konverter batch af billeder til WEBP-fil Billeder til WEBP Vælg WEBP-billede for at starte Ingen fuld adgang til filer Tillad alle filer adgang til at se JXL, QOI og andre billeder, der ikke genkendes som billeder på Android. Uden tilladelsen er Image Toolbox ikke i stand til at vise disse billeder Standard tegnefarve Standard tegnestitilstand Tilføj tidsstempel Aktiverer tilføjelse af tidsstempel til outputfilnavnet Formateret tidsstempel Aktiver tidsstempelformatering i outputfilnavn i stedet for grundlæggende millis Aktiver tidsstempler for at vælge deres format En gang gem placering Se og rediger en gang gemte placeringer, som du kan bruge ved at trykke længe på gem-knappen i stort set alle muligheder Nyligt brugt CI kanal Gruppe Billedværktøjskasse i Telegram 🎉 Deltag i vores chat, hvor du kan diskutere alt, hvad du vil, og se også ind på CI-kanalen, hvor jeg poster betaer og annonceringer Få besked om nye versioner af appen, og læs meddelelser Tilpas et billede til givne dimensioner og anvend sløring eller farve på baggrunden Værktøjsarrangement Gruppér værktøjer efter type Grupperer værktøjer på hovedskærmen efter deres type i stedet for et brugerdefineret listearrangement Standardværdier Systembars synlighed Vis systembjælker ved at stryge Aktiverer strygning for at vise systembjælker, hvis de er skjulte Auto Skjul alle Vis alle Skjul Nav Bar Skjul statuslinje Støjgenerering Generer forskellige lyde som Perlin eller andre typer Frekvens Støjtype Rotationstype Fraktal type Oktaver Lakunaritet Gevinst Vægtet styrke Ping Pong Styrke Afstandsfunktion Returtype Jitter Domain Warp Justering Brugerdefineret filnavn Vælg placering og filnavn, som skal bruges til at gemme det aktuelle billede Gemt i mappe med brugerdefineret navn Collage maker Lav collager af op til 20 billeder Collage type Hold billedet for at bytte, flytte og zoome for at justere positionen Deaktiver rotation Forhindrer roterende billeder med to-fingerbevægelser Aktiver snapping til grænser Efter at have flyttet eller zoomet vil billederne snappes for at udfylde rammens kanter Histogram RGB- eller Brightness-billedhistogram for at hjælpe dig med at foretage justeringer Dette billede vil blive brugt til at generere RGB- og lysstyrkehistogrammer Tesseract muligheder Anvend nogle inputvariabler til tesseract-motoren Brugerdefinerede indstillinger Indstillinger skal indtastes efter dette mønster: \"--{option_name} {value}\" Automatisk beskæring Frie hjørner Beskær billede efter polygon, dette korrigerer også perspektivet Tvang peger på billedgrænser Points vil ikke være begrænset af billedgrænser, dette er nyttigt til mere præcis perspektivkorrektion Maske Indholdsbevidst fyld under tegnet sti Heal Spot Brug Circle Kernel Åbning Lukning Morfologisk gradient Top Hat Sort Hat Tonekurver Nulstil kurver Kurver vil blive rullet tilbage til standardværdien Linje stil Gab størrelse Stiplet Punkt stiplet Stemplet Zigzag Tegner stiplet linje langs den tegnede sti med specificeret mellemrumsstørrelse Tegner prik og stiplet linje langs den givne sti Bare standard lige linjer Tegner valgte figurer langs stien med specificeret mellemrum Tegner bølget zigzag langs stien Zigzag-forhold Opret genvej Vælg værktøj til at fastgøre Værktøjet vil blive tilføjet til startskærmen på din launcher som genvej, brug det i kombination med indstillingen \"Spring filvalg over\" for at opnå den nødvendige adfærd Stable ikke rammer Gør det muligt at bortskaffe tidligere rammer, så de ikke stables på hinanden Crossfade Rammer vil blive krydstonet ind i hinanden Crossfade-rammer tæller Tærskel 1 Tærskel to Canny Spejl 101 Forbedret zoomsløring Laplacian Simple Sobel Simpel Hjælpergitter Viser understøttende gitter over tegneområdet for at hjælpe med præcise manipulationer Gitter farve Cellebredde Cellehøjde Kompakte vælgere Nogle valgknapper vil bruge et kompakt layout for at tage mindre plads Giv kameratilladelse i indstillingerne til at tage billeder Layout Hovedskærmens titel Konstant Rate Factor (CRF) En værdi på %1$s betyder en langsom komprimering, hvilket resulterer i en relativt lille filstørrelse. %2$s betyder en hurtigere komprimering, hvilket resulterer i en stor fil. Lut Bibliotek Download samling af LUT\'er, som du kan anvende efter download Opdater samling af LUT\'er (kun nye vil stå i kø), som du kan anvende efter download Skift standard billedeksempel for filtre Eksempelbillede Skjule Vise Slider Type Fancy Materiale 2 En fancy skyder. Dette er standardindstillingen En Materiale 2-skyder Et materiale du skyder Anvende Midterdialogknapper Knapper af dialogbokse vil blive placeret i midten i stedet for venstre side, hvis det er muligt Open Source-licenser Se licenser til open source-biblioteker, der bruges i denne app Areal Resampling ved hjælp af pixelarealrelation. Det kan være en foretrukken metode til billeddecimering, da det giver moire\'-fri resultater. Men når billedet er zoomet ind, ligner det \"Nærmeste\"-metoden. Aktiver tonemapping Indtast % Kan ikke få adgang til webstedet, prøv at bruge VPN eller tjek om url\'en er korrekt Markup-lag Lagtilstand med mulighed for frit at placere billeder, tekst og mere Rediger lag Lag på billedet Brug et billede som baggrund og tilføj forskellige lag oven på det Lag på baggrund Det samme som første mulighed, men med farve i stedet for billede Beta Hurtige indstillinger side Tilføj en flydende strimmel på den valgte side, mens du redigerer billeder, som åbner hurtige indstillinger, når der klikkes på Ryd markering Indstillingsgruppen \"%1$s\" vil blive skjult som standard Indstillingsgruppen \"%1$s\" udvides som standard Base64 værktøjer Afkod Base64-streng til billede, eller indkod billede til Base64-format Base 64 Den angivne værdi er ikke en gyldig Base64-streng Kan ikke kopiere tom eller ugyldig Base64-streng Indsæt Base64 Kopiér Base64 Indlæs billede for at kopiere eller gemme Base64-streng. Hvis du har selve strengen, kan du indsætte den ovenfor for at få et billede Gem Base64 Aktiebase64 Valgmuligheder Handlinger Import Base64 Base64-handlinger Tilføj disposition Tilføj omrids omkring tekst med specificeret farve og bredde Konturfarve Omridsstørrelse Rotation Kontrolsum som filnavn Outputbilleder vil have et navn, der svarer til deres datakontrolsum Gratis software (partner) Mere nyttig software i partnerkanalen for Android-applikationer Algoritme Kontrolsum værktøjer Sammenlign kontrolsummer, beregn hashes eller opret hex-strenge fra filer ved hjælp af forskellige hashing-algoritmer Beregne Tekst Hash Kontrolsum Vælg fil for at beregne dens kontrolsum baseret på valgt algoritme Indtast tekst for at beregne dens kontrolsum baseret på den valgte algoritme Kildekontrolsum Kontrolsum til sammenligning Kamp! Forskel Kontrolsummer er lige store, det kan være sikkert Kontrolsummer er ikke ens, filen kan være usikker! Mesh gradienter Se på online samling af Mesh Gradienter Kun TTF- og OTF-skrifttyper kan importeres Importer skrifttype (TTF/OTF) Eksporter skrifttyper Importerede skrifttyper Fejl under lagring af forsøg. Prøv at ændre outputmappe Filnavn er ikke angivet Ingen Brugerdefinerede sider Udvalg af sider Bekræftelse af værktøjsafslutning Hvis du har ændringer, som ikke er gemt, mens du bruger bestemte værktøjer og prøver at lukke dem, vil bekræftelsesdialogen blive vist Rediger EXIF Skift metadata af enkelt billede uden rekomprimering Tryk for at redigere tilgængelige tags Skift klistermærke Tilpas bredde Tilpas højde Batch Sammenlign Vælg fil/filer for at beregne dens kontrolsum baseret på valgt algoritme Vælg filer Vælg bibliotek Skala for hovedlængde Stempel Tidsstempel Formater mønster Polstring Billedskæring Klip billeddelen og flet dem til venstre (kan være omvendt) med lodrette eller vandrette linjer Lodret drejelinje Vandret drejelinje Omvendt valg Lodret afskåret del vil blive forladt, i stedet for at flette dele rundt om det udskårne område Den vandrette afskårne del vil blive forladt, i stedet for at flette dele rundt om det udskårne område Samling af mesh gradienter Opret mesh-gradient med tilpasset mængde knob og opløsning Mesh Gradient Overlay Komponer mesh-gradient af toppen af ​​givne billeder Pointtilpasning Gitterstørrelse Opløsning X Opløsning Y Opløsning Pixel By Pixel Fremhæv farve Pixel sammenligningstype Scan stregkoden Højdeforhold Stregkode type Håndhæve S/H Stregkodebilledet vil være helt sort og hvidt og ikke farvet af appens tema Scan enhver stregkode (QR, EAN, AZTEC, …) og få dens indhold eller indsæt din tekst for at generere en ny Ingen stregkode fundet Genereret stregkode vil være her Audio Covers Udpak albumcoverbilleder fra lydfiler, de mest almindelige formater understøttes Vælg lyd for at starte Vælg lyd Ingen omslag fundet Send logs Klik for at dele app-logfil, dette kan hjælpe mig med at finde problemet og løse problemer Ups… Noget gik galt Du kan kontakte mig ved at bruge mulighederne nedenfor, og jeg vil prøve at finde en løsning.\n(Glem ikke at vedhæfte logfiler) Skriv til fil Udpak tekst fra en batch af billeder og gem den i den ene tekstfil Skriv til metadata Udtræk tekst fra hvert billede, og placer det i EXIF-info af relative billeder Usynlig tilstand Brug steganografi til at skabe øjensynlige vandmærker inde i bytes på dine billeder Brug LSB LSB (Less Significant Bit) steganografimetode vil blive brugt, ellers FD (Frequency Domain) Fjern automatisk røde øjne Adgangskode Lås op PDF er beskyttet Operation næsten afsluttet. Hvis du annullerer nu, skal du genstarte den Ændret dato Ændret dato (omvendt) Størrelse Størrelse (omvendt) MIME-type MIME-type (omvendt) Forlængelse Udvidelse (omvendt) Dato tilføjet Dato tilføjet (omvendt) Venstre mod højre Højre til venstre Top til bund Bund til top Flydende glas En switch baseret på nyligt annonceret IOS 26 og dets flydende glasdesignsystem Vælg billede eller indsæt/importér Base64-data nedenfor Indtast billedlink for at starte Indsæt link Kalejdoskop Sekundær vinkel Sider Kanalmix Blå grøn Rød blå Grøn rød Til rødt Ind i grønt Til blå Cyan Magenta Gul Farve Halvtone Kontur Niveauer Offset Voronoi krystallisere Form Strække Tilfældighed Afpletter Diffus Hund Anden radius Udlign Glød Hvirvel og knib Pointilliser Kantfarve Polære koordinater Ret til polar Polar til rekt Vend i cirkel Reducer støj Simpel solarisering Væve X Gap Y Gap X bredde Y Bredde Twirl Gummistempel Smøre Tæthed Blande Sphere Lens Distortion Brydningsindeks Bue Spredningsvinkel Glitrende Stråler ASCII Gradient Mary Efterår Knogle Stråle Vinter Ocean Sommer Forår Fed variant HSV Lyserød Varmt Ord Magma Inferno Plasma Viridis Borgere Tusmørke Twilight Shifted Perspektiv Auto Deskew Tillad beskæring Beskær eller perspektiv Absolut Turbo Dyb Grøn Linsekorrektion Målobjektivprofilfil i JSON-format Download klar linseprofiler Del procenter Eksporter som JSON Kopier streng med en paletdata som json-repræsentation Sømskæring Startskærm Lås skærm Indbygget Tapet eksport Opfriske Få aktuelle hjem, lås og indbyggede tapeter Tillad adgang til alle filer, dette er nødvendigt for at hente wallpapers Administrer ekstern lagringstilladelse er ikke nok, du skal give adgang til dine billeder, sørg for at vælge \"Tillad alle\" Tilføj forudindstilling til filnavn Tilføjer suffiks med valgt forudindstilling til billedfilnavn Tilføj billedskalatilstand til filnavn Tilføjer suffiks med valgt billedskaleringstilstand til billedfilnavnet Ascii Art Konverter billede til ascii-tekst, som vil ligne billede Params Anvender negativt filter på billedet for bedre resultat i nogle tilfælde Behandler skærmbillede Skærmbilledet blev ikke taget, prøv igen Lagring sprunget over %1$s filer sprunget over Tillad Spring over hvis større Nogle værktøjer får lov til at springe over at gemme billeder, hvis den resulterende filstørrelse ville være større end originalen Kalenderbegivenhed Kontakte E-mail Beliggenhed Telefon Tekst SMS URL Wi-Fi Åbent netværk N/A SSID Telefon Besked Adresse Emne Legeme Navn Organisation Titel Telefoner E-mails URL\'er adresser Oversigt Beskrivelse Beliggenhed Arrangør Startdato Slutdato Status Breddegrad Længde Opret stregkode Rediger stregkode Wi-Fi konfiguration Sikkerhed Vælg kontakt Giv kontaktpersoner tilladelse i indstillingerne til at autofylde ved hjælp af den valgte kontakt Kontakt info Fornavn Mellemnavn Efternavn Udtale Tilføj telefon Tilføj e-mail Tilføj adresse Hjemmeside Tilføj hjemmeside Formateret navn Dette billede vil blive brugt til at placere over stregkoden Kodetilpasning Dette billede vil blive brugt som logo i midten af ​​QR-koden Logo Logo polstring Logo størrelse Logo hjørner Fjerde øje Tilføjer øjensymmetri til qr-koden ved at tilføje det fjerde øje i det nederste endehjørne Pixel form Rammeform Kugleform Fejlkorrektionsniveau Mørk farve Lys farve Hyper OS Xiaomi HyperOS-lignende stil Maske mønster Denne kode kan muligvis ikke scannes, skift udseendeparametre for at gøre den læsbar med alle enheder Kan ikke scannes Værktøjer vil ligne startskærmens appstarter for at være mere kompakt Launcher-tilstand Fylder et område med udvalgt pensel og stil Flood Fyld Spray Tegner graffity-stilet sti Firkantede partikler Spraypartikler vil være firkantede i stedet for cirkler Paletværktøjer Generer basis-/materiale, du paletter fra billede, eller importer/eksportér på tværs af forskellige paletformater Rediger palet Eksporter/importer palet på tværs af forskellige formater Farvenavn Palette navn Palette format Eksporter genereret palet til forskellige formater Tilføjer ny farve til den nuværende palet Formatet %1$s understøtter ikke at angive paletnavn På grund af Play Butiks politikker kan denne funktion ikke inkluderes i den aktuelle build. For at få adgang til denne funktionalitet skal du downloade ImageToolbox fra en alternativ kilde. Du kan finde de tilgængelige builds på GitHub nedenfor. Åbn Github-siden Den originale fil vil blive erstattet med en ny i stedet for at gemme den i den valgte mappe Registreret skjult vandmærketekst Registreret skjult vandmærkebillede Dette billede blev skjult Generativ indmaling Giver dig mulighed for at fjerne objekter i et billede ved hjælp af en AI-model uden at være afhængig af OpenCV. For at bruge denne funktion vil appen downloade den påkrævede model (~200 MB) fra GitHub Giver dig mulighed for at fjerne objekter i et billede ved hjælp af en AI-model uden at være afhængig af OpenCV. Dette kan være en langvarig operation Analyse af fejlniveau Luminansgradient Gennemsnitlig afstand Detektion af kopibevægelse Beholde Koefficient Udklipsholderdata er for store Data er for store til at kopiere Simpel vævningspixelisering Forskudt pixelisering Krydspixelisering Mikromakropixelisering Orbital pixelisering Vortex pixelisering Puls Grid Pixelization Nucleus Pixelization Radial vævningspixelisering Kan ikke åbne uri \"%1$s\" Snefaldstilstand Aktiveret Kantramme Glitch-variant Kanalskift Max offset VHS Bloker fejl Blokstørrelse CRT-krumning Krumning Chroma Pixel Melt Max Drop AI værktøjer Forskellige værktøjer til at behandle billeder gennem ai-modeller som f.eks. fjernelse af artefakter eller denoising Kompression, takkede linjer Tegnefilm, udsendelseskomprimering Generel kompression, generel støj Farveløs tegneseriestøj Hurtig, generel komprimering, generel støj, animation/tegneserier/anime Bogscanning Eksponeringskorrektion Bedst til generel komprimering, farvebilleder Bedst til generel komprimering, gråtonebilleder Generel komprimering, gråtonebilleder, stærkere Generel støj, farvebilleder Generel støj, farvebilleder, bedre detaljer Generel støj, gråtonebilleder Generel støj, gråtonebilleder, stærkere Generel støj, gråtonebilleder, stærkest Generel kompression Generel kompression Teksturering, h264 komprimering VHS-komprimering Ikke-standard komprimering (cinepak, msvideo1, roq) Bink kompression, bedre på geometri Bink kompression, stærkere Bink kompression, blød, bevarer detaljer Eliminerer trappetrinseffekten, udjævner Scannet kunst/tegninger, mild kompression, moire Farvebånd Langsom, fjernende halvtoner Generel farvestof til gråtone-/bw-billeder, for bedre resultater brug DDColor Kantfjernelse Fjerner overslibning Langsomt, rystende Anti-aliasing, generelle artefakter, CGI KDM003 scanner behandling Letvægts billedforbedringsmodel Fjernelse af kompressionsartefakter Fjernelse af kompressionsartefakter Bandagefjernelse med glatte resultater Halvtonemønsterbehandling Dither mønster fjernelse V3 Fjernelse af JPEG-artefakter V2 H.264 teksturforbedring VHS skarphed og forbedring Sammenlægning Chunk størrelse Overlap størrelse Billeder over %1$s px vil blive skåret i skiver og behandlet i bidder, overlappende blander disse for at forhindre synlige sømme. Store størrelser kan forårsage ustabilitet med low-end enheder Vælg en for at starte Vil du slette %1$s model? Du skal downloade den igen Bekræfte Modeller Downloadede modeller Tilgængelige modeller Forbereder Aktiv model Sessionen kunne ikke åbnes Kun .onnx/.ort-modeller kan importeres Import model Importer tilpasset onnx-model til videre brug, kun onnx/ort-modeller accepteres, understøtter næsten alle esrgan-lignende varianter Importerede modeller Generel støj, farvede billeder Generel støj, farvede billeder, stærkere Generel støj, farvede billeder, stærkest Reducerer dithering-artefakter og farvestriber, hvilket forbedrer jævne gradienter og flade farveområder. Forbedrer billedets lysstyrke og kontrast med afbalancerede højlys, mens naturlige farver bevares. Gør mørke billeder lysere, samtidig med at detaljer bevares og overeksponering undgås. Fjerner overdreven farvetoning og genopretter en mere neutral og naturlig farvebalance. Anvender Poisson-baseret støjtoning med vægt på at bevare fine detaljer og teksturer. Anvender blød Poisson-støjtoning for jævnere og mindre aggressive visuelle resultater. Ensartet støjtoning med fokus på detaljebevarelse og billedklarhed. Blid ensartet støjtoning for subtil tekstur og glat udseende. Reparerer beskadigede eller ujævne områder ved at male artefakter igen og forbedre billedkonsistensen. Letvægts debanding-model, der fjerner farvestriber med minimal ydeevne. Optimerer billeder med meget høje kompressionsartefakter (0-20 % kvalitet) for forbedret klarhed. Forbedrer billeder med høje kompressionsartefakter (20-40 % kvalitet), genskaber detaljer og reducerer støj. Forbedrer billeder med moderat komprimering (40-60% kvalitet), balancerer skarphed og glathed. Forfiner billeder med let komprimering (60-80 % kvalitet) for at forbedre subtile detaljer og teksturer. Forbedrer næsten tabsfrie billeder en smule (80-100 % kvalitet), samtidig med at det naturlige udseende og detaljer bevares. Enkel og hurtig farvelægning, tegnefilm, ikke ideelt Reducerer billedsløring en smule og forbedrer skarpheden uden at introducere artefakter. Langvarige operationer Behandler billede Forarbejdning Fjerner tunge JPEG-komprimeringsartefakter i billeder af meget lav kvalitet (0-20%). Reducerer stærke JPEG-artefakter i stærkt komprimerede billeder (20-40%). Rydder op i moderate JPEG-artefakter, mens billeddetaljerne bevares (40-60%). Forfiner lette JPEG-artefakter i billeder af ret høj kvalitet (60-80%). Reducerer subtilt mindre JPEG-artefakter i næsten tabsfri billeder (80-100%). Forbedrer fine detaljer og teksturer, forbedrer den opfattede skarphed uden tunge artefakter. Behandling afsluttet Behandlingen mislykkedes Forbedrer hudens teksturer og detaljer, mens den bevarer et naturligt udseende, optimeret til hastighed. Fjerner JPEG-komprimeringsartefakter og gendanner billedkvaliteten for komprimerede fotos. Reducerer ISO-støj i billeder taget under svagt lys, og bevarer detaljer. Korrigerer overeksponerede eller \"jumbo\" highlights og genopretter en bedre tonal balance. Let og hurtig farvelægningsmodel, der tilføjer naturlige farver til gråtonebilleder. DEJPEG Denoise Farvelæg Artefakter Forbedre Anime Scanninger Fornemme X4 upscaler til generelle billeder; lille model, der bruger mindre GPU og tid, med moderat sløring og denoise. X2 upscaler til generelle billeder, bevarer teksturer og naturlige detaljer. X4 upscaler til generelle billeder med forbedrede teksturer og realistiske resultater. X4 upscaler optimeret til anime billeder; 6 RRDB blokke for skarpere linjer og detaljer. X4 opskalerer med MSE-tab, producerer jævnere resultater og reducerede artefakter til generelle billeder. X4 Upscaler optimeret til anime-billeder; 4B32F variant med skarpere detaljer og glatte linjer. X4 UltraSharp V2-model til generelle billeder; understreger skarphed og klarhed. X4 UltraSharp V2 Lite; hurtigere og mindre, bevarer detaljer, mens du bruger mindre GPU-hukommelse. Letvægtsmodel til hurtig fjernelse af baggrunden. Afbalanceret ydeevne og nøjagtighed. Arbejder med portrætter, objekter og scener. Anbefales til de fleste anvendelsestilfælde. Fjern BG Vandret kanttykkelse Lodret kanttykkelse %1$s farve %1$s farver Den nuværende model understøtter ikke chunking, billedet vil blive behandlet i originale dimensioner, dette kan forårsage højt hukommelsesforbrug og problemer med low-end enheder Chunking deaktiveret, billede vil blive behandlet i originale dimensioner, dette kan forårsage højt hukommelsesforbrug og problemer med low-end enheder, men kan give bedre resultater ved slutninger Chunking Høj nøjagtig billedsegmenteringsmodel til fjernelse af baggrund Letvægtsversion af U2Net til hurtigere fjernelse af baggrund med mindre hukommelsesforbrug. Fuld DDColor-model leverer farvelægning af høj kvalitet til generelle billeder med minimale artefakter. Bedste valg af alle farvelægningsmodeller. DDColor Trænede og private kunstneriske datasæt; producerer forskellige og kunstneriske farvelægningsresultater med færre urealistiske farveartefakter. Letvægts BiRefNet-model baseret på Swin Transformer til nøjagtig baggrundsfjernelse. Baggrundsfjernelse i høj kvalitet med skarpe kanter og fremragende detaljebevarelse, især på komplekse genstande og vanskelige baggrunde. Baggrundsfjernelsesmodel, der producerer nøjagtige masker med glatte kanter, velegnet til generelle genstande og moderat detaljebevarelse. Modellen er allerede downloadet Modellen blev importeret Type Søgeord Meget hurtig Normal Langsom Meget langsom Beregn procenter Min. værdi er %1$s Forvrænget billedet ved at tegne med fingrene Warp Hårdhed Warp-tilstand Flytte Dyrke Krympe Swirl CW Hvirvel mod venstre Fade Styrke Top Drop Nederste fald Start Drop Slut Drop Downloader Glatte former Brug superellipser i stedet for standard afrundede rektangler for glattere, mere naturlige former Form Type Skære Afrundet Glat Skarpe kanter uden afrunding Klassiske afrundede hjørner Former Type Hjørner Størrelse Squircle Elegante afrundede UI-elementer Filnavn format Brugerdefineret tekst placeret helt i begyndelsen af ​​filnavnet, perfekt til projektnavne, mærker eller personlige tags. Bruger det originale filnavn uden filtypenavn, hvilket hjælper dig med at holde kildeidentifikationen intakt. Billedbredden i pixels, nyttig til sporing af opløsningsændringer eller skalering af resultater. Billedhøjden i pixels, nyttigt, når du arbejder med billedformater eller eksporter. Genererer tilfældige cifre for at garantere unikke filnavne; tilføje flere cifre for ekstra sikkerhed mod dubletter. Auto-inkrementerende tæller til batch-eksport, ideel, når du gemmer flere billeder i én session. Indsætter det anvendte forudindstillede navn i filnavnet, så du nemt kan huske, hvordan billedet blev behandlet. Viser billedskaleringstilstanden, der bruges under behandling, og hjælper med at skelne mellem ændrede, beskårne eller tilpassede billeder. Brugerdefineret tekst placeret i slutningen af ​​filnavnet, nyttig til versionering som _v2, _edited eller _final. Filtypenavnet (png, jpg, webp osv.), der automatisk matcher det faktiske gemte format. Et tilpasseligt tidsstempel, der lader dig definere dit eget format efter java-specifikation for perfekt sortering. Fling Type Indbygget Android iOS stil Glat kurve Hurtigt stop hoppende Flydende Snappy Ultra Glat Adaptiv Tilgængelighedsbevidst Reduceret bevægelse Indbygget Android rullefysik Balanceret, jævn rulning til generel brug Højere friktion iOS-lignende rulleadfærd Unik splinekurve for tydelig rullefølelse Præcis rulning med hurtig stop Legesyg, responsiv hoppende rulle Lange, glidende ruller til gennemsyn af indhold Hurtig, responsiv rulning til interaktive brugergrænseflader Premium jævn rulning med udvidet momentum Justerer fysik baseret på slyngehastighed Respekterer systemtilgængelighedsindstillinger Minimal bevægelse for tilgængelighedsbehov Primære Linjer Tilføjer tykkere linje hver femte linje Fyld farve Skjulte værktøjer Værktøjer skjult til deling Farvebibliotek Gennemse en stor samling af farver Gør skarpere og fjerner sløring fra billeder, samtidig med at de naturlige detaljer bevares, ideel til at rette ufokuserede billeder. Intelligent gendanner billeder, der tidligere er blevet ændret, og genskaber mistede detaljer og teksturer. Optimeret til live-action-indhold, reducerer kompressionsartefakter og forbedrer fine detaljer i film-/tv-showrammer. Konverterer optagelser i VHS-kvalitet til HD, fjerner båndstøj og forbedrer opløsningen, samtidig med at vintage-følelsen bevares. Specialiseret til teksttunge billeder og skærmbilleder, skærper tegn og forbedrer læsbarheden. Avanceret opskalering trænet på forskellige datasæt, fremragende til generelle fotoforbedring. Optimeret til webkomprimerede billeder, fjerner JPEG-artefakter og genopretter det naturlige udseende. Forbedret version til webfotos med bedre teksturbevarelse og reduktion af artefakter. 2x opskalering med Dual Aggregation Transformer-teknologi, bevarer skarphed og naturlige detaljer. 3x opskalering ved hjælp af avanceret transformerarkitektur, ideel til moderate forstørrelsesbehov. 4x højkvalitets opskalering med state-of-the-art transformer netværk, bevarer fine detaljer i større skalaer. Fjerner sløring/støj og rystelser fra fotos. Generelt formål, men bedst på billeder. Gendanner billeder i lav kvalitet ved hjælp af Swin2SR-transformer, optimeret til BSRGAN-nedbrydning. Fantastisk til at fikse tunge kompressionsartefakter og forbedre detaljer i 4x skala. 4x opskalering med SwinIR-transformer trænet på BSRGAN-nedbrydning. Bruger GAN til skarpere teksturer og mere naturlige detaljer i fotos og komplekse scener. Sti Flet PDF Kombiner flere PDF-filer til ét dokument Filer rækkefølge pp. Opdel PDF Uddrag bestemte sider fra PDF-dokument Roter PDF Ret sideretning permanent Sider Omarranger PDF Træk og slip sider for at omarrangere dem Hold og træk sider Sidetal Tilføj automatisk nummerering til dine dokumenter Etiketformat PDF til tekst (OCR) Uddrag almindelig tekst fra dine PDF-dokumenter Overlay tilpasset tekst til branding eller sikkerhed Signatur Tilføj din elektroniske signatur til ethvert dokument Dette vil blive brugt som signatur Lås PDF op Fjern adgangskoder fra dine beskyttede filer Beskyt PDF Beskyt dine dokumenter med stærk kryptering Succes PDF ulåst, du kan gemme eller dele det Reparation af pdf Forsøg på at rette beskadigede eller ulæselige dokumenter Gråtoner Konverter alle dokumentindlejrede billeder til gråtoner Komprimer PDF Optimer din dokumentfilstørrelse for nemmere deling ImageToolbox genopbygger den interne krydsreferencetabel og regenererer filstrukturen fra bunden. Dette kan gendanne adgangen til mange filer, der \\"ikke kan åbnes\\" Dette værktøj konverterer alle dokumentbilleder til gråtoner. Bedst til udskrivning og reduktion af filstørrelse Metadata Rediger dokumentegenskaber for bedre privatliv Tags Producent Forfatter Nøgleord Skaber Privatliv Deep Clean Ryd alle tilgængelige metadata for dette dokument Side Dyb OCR Uddrag tekst fra dokument og gem den i den ene tekstfil ved hjælp af Tesseract-motoren Kan ikke fjerne alle sider Fjern PDF-sider Fjern bestemte sider fra PDF-dokument Tryk for at fjerne Manuelt Beskær PDF Beskær dokumentsider til enhver grænse Udglat PDF Gør PDF uændret ved at rastere dokumentsider Kunne ikke starte kameraet. Tjek venligst tilladelser og sørg for, at den ikke bliver brugt af en anden app. Uddrag billeder Uddrag billeder indlejret i PDF-filer i deres originale opløsning Denne PDF-fil indeholder ingen indlejrede billeder Dette værktøj scanner hver side og gendanner kildebilleder i fuld kvalitet - perfekt til at gemme originaler fra dokumenter Tegn signatur Pen Params Brug egen signatur som billede, der skal placeres på dokumenter Zip PDF Opdel dokument med givet interval og pak nye dokumenter i zip-arkiv Interval Udskriv PDF Forbered dokumentet til udskrivning med brugerdefineret sidestørrelse Sider pr. ark Orientering Sidestørrelse Margin Bloom Blødt knæ Optimeret til anime og tegnefilm. Hurtig opskalering med forbedrede naturlige farver og færre artefakter Samsung One UI 7-lignende stil Indtast grundlæggende matematiske symboler her for at beregne den ønskede værdi (f.eks. (5+5)*10) Matematisk udtryk Saml op til %1$s billeder Hold Dato Tid Bevar altid exif-tags relateret til dato og klokkeslæt, fungerer uafhængigt af keep exif-indstillingen Baggrundsfarve til alfaformater Tilføjer mulighed for at indstille baggrundsfarve for hvert billedformat med alfa-understøttelse, når den er deaktiveret, er dette kun tilgængeligt for ikke-alfa-one Åbent projekt Fortsæt med at redigere et tidligere gemt Image Toolbox-projekt Kan ikke åbne Image Toolbox-projektet Image Toolbox-projektet mangler projektdata Image Toolbox-projektet er beskadiget Ikke-understøttet Image Toolbox-projektversion: %1$d Gem projekt Gem lag, baggrund og redigeringshistorik i en redigerbar projektfil Kunne ikke åbne Skriv til søgbar PDF Genkend tekst fra billedbatch og gem søgbar PDF med billede og valgbart tekstlag Alfa lag Vandret vending Lodret flip Låse Tilføj skygge Skygge farve Tekstgeometri Stræk eller skæv tekst for skarpere stilisering Skala X Skæv X Fjern annoteringer Fjern valgte annoteringstyper såsom links, kommentarer, fremhævninger, former eller formularfelter fra PDF-siderne Hyperlinks Vedhæftede filer Linjer Popups Frimærker Former Tekstnoter Tekstmarkering Formularfelter Markup Ukendt Anmærkninger Ophæv grupperingen Tilføj sløret skygge bag lag med konfigurerbar farve og offsets ================================================ FILE: core/resources/src/main/res/values-de/strings.xml ================================================ Größe %1$s Höhe %1$s Qualität Methode Explizit Kann Farbpalette aus gewähltem Bild nicht erzeugen Ausgabeverzeichnis Standardeinstellung Benutzerdefiniert Ändern der Größe eines Bildes entsprechend der angegebenen Größe in KB Einstellungen Nach Systemvorgabe Nachtmodus Dunkles Design Helles Design Personalisierung Farbschema von Bild AMOLED Modus Alle dunklen Farben der Benutzeroberfläche werden im dunklen Design schwarz dargestellt Farbschema Dateityp Die Zwischenablage ist leer Das App Farbschema kann nicht geändert werden, wenn dynamische Farben aktiviert sind App-Farbschema basierend auf einer wählbaren Farbe ändern Über die App Kein Update gefunden Fehlermeldungen Sende Fehlermeldungen und Änderungswünsche hierhin Hilf beim Übersetzen Etwas ging schief: %1$s Bild ist zu groß für die Vorschau, es wird aber trotzdem versucht es zu speichern Lade… Zum Starten Bild wählen Breite %1$s Bild zurücksetzen Irgendetwas ging schief App neustarten In die Zwischenablage kopiert Bleiben Tag hinzufügen Flexibel Bild auswählen Schließe App Zurücksetzen Bist du sicher, dass du die App schließen willst? Schließen Änderungen am Bild werden zu den Ursprungswerten zurückgesetzt Werte erfolgreich zurückgesetzt OK Zuschneiden Modifiziere, skaliere und bearbeite ein Bild Ausnahme EXIF bearbeiten Keine EXIF Daten gefunden Abbrechen Voreinstellungen Speichern Leeren Alle EXIF-Daten des Bildes werden gelöscht. Dies kann nicht rückgängig gemacht werden! Speichern EXIF leeren Einzelne Bearbeitung Alle ungespeicherten Änderungen gehen verloren, wenn Du den Dialog jetzt schließt Farbwähler Aktualisierung Quellcode Bild EXIF behalten Aktuelle Meldungen, Problemmeldungen und mehr Farbe aus einem Bild wählen und diese kopieren oder teilen Farbe Farbe kopiert Bild beliebig zuschneiden Version Bilder: %d Vorschaubild ändern Entfernen Farbpalette aus einem angegebenen Bild erzeugen Farbpalette Neue Version %1$s Dateityp wird nicht unterstützt: %1$s Original Palette erzeugen Nicht spezifiziert Gerätespeicher Maximale Größe in KB Größe nach Gewicht ändern Vergleichen Wähle zwei Bilder zum Starten Wähle Bilder Vergleiche zwei Bilder Dynamisches Farbschema Ändert das Farbschema der App automatisch zum gewählten Bild Sprache Füge einen gültigen aRGB Farb Code ein Rot Grün Blau Hier suchen Verbessere Übersetzungsfehler oder übersetze die App in andere Sprachen Deine Suchanfrage hat nichts gefunden Wenn aktiviert, werden die Farben der Anwendung an die Farben des Hintergrundbildes angepasst. Fehler beim Speichern von %d-Bildern Oberfläche Werte Hinzufügen Tertiär Sekundär Primär Berechtigung Gewähren App benötigt Zugriff auf deinen Speicher, um Bilder zu speichern, es ist notwendig. Bitte erlaube deshalb im nächsten Dialog die Berechtigung. Rahmenstärke Die App benötigt diese Berechtigung, um zu funktionieren, bitte erteile diese manuell Externer Speicher „Monet“-Farben Diese Anwendung ist völlig kostenlos, aber wenn du die Projektentwicklung unterstützen möchtest, klicke hier. Auf Aktualisierungen prüfen Wenn aktiviert, wird der Aktualisierungsdialog nach dem Start der App angezeigt Bild-Zoom FAB-Ausrichtung Teilen Präfix Dateiname Emoji Wähle, welches Emoji auf der Hauptseite angezeigt wird Dateigröße hinzufügen Wenn aktiviert, werden Breite und Höhe des gespeicherten Bildes zum Namen der Ausgabedatei hinzugefügt EXIF löschen EXIF-Metadaten von einem beliebigen Bilderpaar löschen Bildvorschau Sieh dir jegliches Bildformat an: GIF, SVG usw. Bildquelle Galerie Bildauswahl Einfache Bildauswahl mittels einer Galerie-App. Funktioniert nur, wenn du eine solche App installiert hast. Reihenfolge Legt die Reihenfolge der Tools im Hauptfenster fest Nutzt GetContent-Intent für die Bildauswahl. Funktioniert überall, kann aber auf manchen Geräten Probleme mit dem Empfangen der Bildauswahl haben (nicht meine Schuld) Bearbeiten Anordnung der Optionen Ursprünglichen Namen hinzufügen Wenn aktiviert, fügt dies den ursprünglichen Dateinamen zum Namen des ausgegebenen Bildes hinzu UrsprName Dateimanager Androids aktuelle Fotoauswahl, die am unteren Rand des Bildschirms erscheint. Funktioniert möglicherweise nur auf Android 12+ und hat zudem Probleme mit dem Empfang von EXIF-Metadaten Bild-Link Laden von Web Bildern Lade jegliches Bild aus dem Internet, betrachte, zoome, bearbeite und speichere es wenn du willst Kein Bild Anzahl der Emojis Funktioniert nicht, wenn »Bildauswahl« als Bildquelle gewählt wurde Bildfolgenummer ersetzen Wenn aktiviert, ersetzt dies bei der Stapelverarbeitung den Zeitstempel durch die Bildfolgenummer Einpassen FolgeNum Füllen Inhaltsskalierung Ändert die Größe von Bildern mit einer langen Seite auf die angegebene Höhe oder Breite. Alle Größenberechnungen werden nach dem Speichern durchgeführt. Das Seitenverhältnis der Bilder wird beibehalten. Ändert die Größe von Bildern auf die angegebene Höhe und Breite. Das Seitenverhältnis der Bilder kann sich ändern. Helligkeit Kontrast Farbton Farbsättigung Filtern Filterketten auf Bilder anwenden Filter Licht Alpha Filter hinzufügen Farbfilter Weißabgleich Farbtemperatur Tönung Lichter Nebel Effekt Monochrom Lichter und Schatten Gamma Schatten Belichtung Steilheit Abstand Sepia Negativ Solarisieren Lebendigkeit Schwarzweiß Weichzeichner Sobel-Kanten Bilateraler Weichzeichner Schärfen Schraffieren Linienstärke Gaussscher Weichzeichner Halbton CGA-Farbraum Box-Weichzeichner Prägen Laplace-Kanten Vignettierung Anfang Ende Abstand der Linien Verwischen (langsam) Winkel Wirbel Schwellung Streckung Kugel-Brechung Intensität Farbmatrix Deckkraft Tonwerttrennung Cartoon Kuwahara-Glättung Radius Verzerrung Brechungsindex Glaskugel-Brechung Skizze Schwellenwert Stärke des Effekts Ändern der Größe von Bildern auf die angegebene Höhe und Breite unter Beibehaltung des Seitenverhältnisses Geglätteter Cartoon Größe nach Grenzen ändern Lookup Nicht maximale Unterdrückung Helle Pixel heller sog. Faltungsmatrix 3x3 RGB-Filter Falschfarben Erste Farbe Zweite Farbe Umsortieren Schnelle Unschärfe Größe der Unschärfe Bewegungsursprung x-Achse Farbbalance Bewegungsursprung y-Achse Bewegungsunschärfe Luminanzschwelle Zeichnen Zeichne auf einem Bild wie in einem Skizzenbuch oder zeichne auf dem Hintergrund selbst Deine Dateien-App ist deaktiviert. Aktiviere sie, um diese Funktion zu nutzen. Deckkraft Auf Bild zeichnen Wähle ein Bild und zeichne etwas darauf Zeichne auf Hintergrund Hintergrundfarbe Zeichenfarbe Wähle eine Hintergrundfarbe und zeichne darauf Datei auswählen Eigenschaften Kompatibilität Chiffrieren Ver- und Entschlüsselung beliebiger Dateien (nicht nur des Bildes) auf der Grundlage verschiedenen Verschlüsselungsalgorithmen Entschlüsseln Implementierung Verschlüsseln Datei zum Starten auswählen Verschlüsselung Entschlüsselung Schlüssel Dateigröße Optionen nach Typ gruppieren Ungültiges Passwort oder ausgewählte Datei ist nicht verschlüsselt %1$s gefunden Tools Gruppierung der Optionen auf dem Hauptbildschirm nach ihrem Typ anstelle einer benutzerdefinierten Listenanordnung Erstellen Speichere die Datei auf deinem Gerät oder benutze Teilen, um es wo immer du möchtest abzulegen AES-256, GCM-Modus, kein Padding, standardmäßig 12 Byte zufällige IVs. Du kannst den gewünschten Algorithmus auswählen. Schlüssel werden als 256-Bit SHA-3-Hashes benutzt Die maximale Dateigröße wird durch das Android-Betriebssystem und den verfügbaren Speicher begrenzt, was natürlich von Ihrem Gerät abhängt. \nBitte beachten Sie: Speicher ist nicht gleich Speicherplatz. Bitte beachte, dass die Kompatibilität zu anderen Dateiverschlüsselungsprogrammen oder -diensten nicht gewährleistet ist. Eine leicht abweichende Behandlung des Schlüssels oder eine andere Chiffrierkonfiguration können Gründe für eine Inkompatibilität sein. Datei bearbeitet Passwortbasierte Verschlüsselung von Dateien. Die verschlüsselten Dateien können im ausgewählten Verzeichnis gespeichert oder freigegeben werden. Entschlüsselte Dateien können auch unmittelbar geöffnet werden. Das Versuchen, das Bild mit der angegebenen Breite und Höhe zu speichern, kann zu einem Fehler führen, wenn der Speicher voll ist. Dies geschieht auf eigene Gefahr. Cache Cache-Größe Wenn aktiviert, wird der App-Cache beim Start der App geleert Automatische Cache-Leerung Die Anordnung kann nicht geändert werden, wenn die Optionen gruppiert sind Screenshot bearbeiten Screenshot Sekundäre Personalisierung Das Speichern im Modus %1$s kann instabil sein, da es sich um ein verlustfreies Format handelt Fallback-Option Überspringen Kopieren Wenn du die Voreinstellung 125 gewählt hast, wird das Bild in einer Größe von 125% des Originalbildes gespeichert. Wenn du Voreinstellung 50 wählst, wird das Bild mit 50 % Größe gespeichert Die Voreinstellung legt hier den % der Ausgabedatei fest, d. h. wenn du die Voreinstellung 50 für ein 5 MB Bild wählst, erhältst du nach dem Speichern ein 2,5 MB Bild. Zufälligen Dateinamen erzeugen Wenn aktiviert, wird der Dateiname völlig zufällig gewählt. Gespeichert im Ordner „%1$s“ als „%2$s“ Telegram-Chat Gespeichert im Ordner „%1$s“ Diskutiere über die App und erhalte Feedback von anderen Nutzern. Außerdem kannst du dort Beta-Updates und Einblicke erhalten. Verwende diesen Maskentyp, um eine Maskierung aus einem Bild zu erzeugen. Beachte, dass es einen Alphakanal haben sollte. Datensicherung und Wiederherstellung Datensicherung Wiederherstellung App-Einstellungen in Datei sichern Beschädigte Datei oder keine Backup-Datei Kontaktiere mich Beschnittmaske Seitenverhältnis Wiederherstellen von App-Einstellungen aus zuvor erstellter Datei Einstellungen wiederhergestellt Dadurch werden Deine Einstellungen auf die Standardwerte zurückgesetzt. Bitte beachte, dass dies nicht ohne die Sicherungsdatei der obigen Option rückgängig gemacht werden kann. Löschen Du bist dabei, das ausgewählte Farbschema zu löschen. Dieser Vorgang kann nicht rückgängig gemacht werden. Schema löschen Schriftgröße Schriftart Text Standardeinstellung Die Verwendung großer Schriftgrößen führt möglicherweise zu unschönen Nebeneffekten. Lösch-Modus Hintergrund löschen Emotionen Natur und Tiere Essen und Trinken Hintergrund wiederherstellen Objekte Symbole Emoji aktivieren Reisen und Orte Aktivitäten Unschärfe-Radius Pipette Zeichenmodus Aa Ää Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Öö Pp Qq Rr Ss ß Tt Uu Üü Vv Ww Xx Yy Zz 0123456789 !? Die Metadaten des Originalbilds bleiben erhalten Hintergrund entfernen Entferne den Hintergrund eines Bildes durch zeichnen oder verwende die Automatikfunktion Bildüberstand entfernen Reduziert die Bildmaße durch Entfernen transparenter Pixelbereiche Hintergrund automatisch löschen Bild wiederherstellen Fehlermeldung anlegen Ändere die Größe bestimmter Bilder oder konvertiere sie in andere Formate, außerdem kannst Du bei Auswahl eines einzelnen Bildes EXIF-Metadaten bearbeiten ­Ups… Irgendetwas ist schiefgelaufen. Du kannst mir mittels der nachfolgenden Optionen schreiben und ich werde versuchen, eine Lösung zu finden Größe ändern und konvertieren Höchstzahl an Farben Erlaubt der App, Absturzberichte automatisch zu sammeln Analyse Erlaube anonymisierte App-Nutzungsstatitiken zu erfassen Derzeit erlaubt das %1$s-Format auf Android nur das Lesen von Exif-Daten und nicht das Ändern/Speichern, d.h. das ausgegebene Bild wird keine Metadaten haben Aktualisierungen Warten Ein Wert von %1$s bedeutet, dass die Komprimierung schnell erfolgt, was zu einer relativ großen Datei führt. %2$s bedeutet, dass die Komprimierung mehr Zeit in Anspruch nimmt, was zu einer kleineren Datei führt Betas zulassen Speichervorgang ist fast abgeschlossen. Wenn du abbrichst, musst du es nochmal speichern. Aufwand Die Aktualisierungsprüfung schließt Beta-App-Versionen ein, wenn sie aktiviert ist Pinselweichheit Pfeile zeichnen Spende Falls aktiviert, wird der Zeichenpfad als Pfeil dargestellt Bilder werden mittig auf diese Größe zugeschnitten, wenn sie größer sind als die eingegebenen Maße. In anderen Fällen wird die Leinwand mit der angegebenen Hintergrundfarbe erweitert. Bild zusammenfügen Kombiniere bestimmte Bilder zu einem großen Horizontal Reihenfolge der Bilder Wähle min. zwei Bilder Wenn aktiviert, werden kleine Bilder maßstäblich dem Größten angepasst Bildausrichtung Vertikal Maßstab der ausgegebenen Bilder Kleine Bilder hochskalieren Ränder unscharf machen Verpixelung Wenn aktiviert, werden unscharfe Ränder unter das Originalbild gezeichnet, um den Raum um das Bild herum auszufüllen, anstatt nur eine Farbe zu verwenden Regulär Recodieren Toleranz Beide Ersetzt die Themenfarben durch negative, sofern aktiviert Regenbogen Ein verspieltes Thema – der Farbton der Quellfarbe erscheint nicht im Thema Verblassende Kanten Ein lautes Thema, die Farbigkeit ist für die primäre Palette maximal, für andere erhöht PDF Tools Ein Stil, der etwas chromatischer als monochrom ist Verbesserte Pixelierung Ziel-Farbe This update checker will connect to GitHub in reason of checking if there is a new update available Pixelgröße Ein Schema, das die Quellfarbe in Scheme.primaryContainer platziert Tonfleck Suchen Farbe ersetzen Diamand Pixelierung Farbe entfernen Bilder zu PDF Ein monochromes Thema, die Farben sind rein schwarz/weiß/grau Fruchtsalat Verbesserte Diamand Pixelierung Kreis Pixelierung PDF Vorschau Ein Schema, das dem Inhaltsschema sehr ähnlich ist Nach Updates suchen Ermöglicht die Suche durch alle verfügbaren Tools auf dem Hauptbildschirm Inhalt Deaktiviert PDF zu Bildern Neutral Zu entfernende Farbe Standard-Palettenstil, mit dem du alle vier Farben anpassen kannst, bei anderen kannst du nur die Schlüsselfarbe festlegen Zu ersetzende Farbe Führt Bilder in einem PDF zusammen Einfache PDF Vorschau Aktivieren um Bildschirmdrehung beim Zeichnen zu sperren. Paletten-Stil Farben invertieren Lebendig Pinsel Pixelierung PDF-Dateien verarbeiten: Vorschau, Konvertieren in einen Stapel von Bildern oder Erstellen eines neuen PDFs aus ausgewählten Bildern Verbesserte Kreis Pixelierung Achtung Konvrtiert PDF zu Bildern in angegebenem Format Du bist dabei, die ausgewählte Filtermaske zu löschen. Dieser Vorgang kann nicht rückgängig gemacht werden Maske löschen Freihand Mitte Ende Anfang Zeichnet den Pfad als Eingabewert Zeichnet einen Pfeil vom Anfangs- zum Endpunkt als Linie Malt einen gepunkteten Pfeil aus einem vorgegebenen Pfad Zeichnet einen Doppelpfeil aus einem vorgegebenen Pfad Konturiertes Oval Konturiertes Rechteck Oval Rechteck Zeichnet ein Rechteck vom Anfangs- zum Endpunkt Zeichnet ein Oval vom Anfangs- zum Endpunkt Zeichnet ein konturiertes Oval vom Anfangs- zum Endpunkt Maskenfarbe Die gezeichnete Filtermaske wird berechnet, um Ihnen das ungefähre Ergebnis zu zeigen Masken Vorschau Zeichenrichtung sperren Maskierungsfilter Masken Maske hinzufügen Maske %d Wende Filterketten auf vorgegebene maskierte Bereiche an, jeder Maskenbereich kann seinen eigenen Satz von Filtern bestimmen Freihand-Zeichnung Zeichnet einen Pfad vom Anfangs- zum Endpunkt als Linie Zeichnet einen Doppelpfeil vom Anfangs- zum Endpunkt als Linie Zeichnet ein konturiertes Rechteck vom Anfangs- zum Endpunkt Expressiv Wenn diese Option aktiviert ist, werden alle nicht maskierten Bereiche gefiltert, anstatt des Standardverfahrens Invertierter Fülltyp Genauigkeit Zeichnet einen geschlossenen, gefüllten Pfad anhand des vorgegebenen Pfades Lasso Schnell Beste Herunterladen Verfügbare Sprachen Malt einen Schatten hinter Schiebereglern Malt einen Schatten hinter Schaltern Wert im Bereich %1$s - %2$s Erkennungstyp Keine Daten Malt einen Schatten hinter Containern Heruntergeladene Sprachen Segmentierungsmodus Standard Einfache Varianten Textmarker Neon Stift Datenschutzunschärfe Zeichne halbtransparente, geschärfte Textmarkerpfade Leuchteffekte zu Ihren Zeichnungen hinzufügen Standardeinstellung, am einfachsten - nur die Farbe Automatisch drehen Container Schaltflächen Schieberegler Schalter Malt einen Schatten hinter Schaltflächen App Leisten Malt einen Schatten hinter App-Leisten Verbesserter Glitch Zeltunschärfe Automatische Ausrichtung & Skripterkennung Vertikaler Einzelblocktext Einzelner Block Kreiswort Ausrichtung von spärlichem Text & Skripterkennung Rohe Linie Email Panne Menge Kein \"%1$s\"-Verzeichnis gefunden, wir haben es auf das Standardverzeichnis umgestellt, bitte speichere die Datei erneut Zwischenablage Auto-Pin Fügt das gespeicherte Bild automatisch zur Zwischenablage hinzu, sofern aktiviert Vibration Dateien überschreiben Originaldatei wird durch eine neue ersetzt, anstatt sie im ausgewählten Ordner zu speichern. Diese Option muss als Bildquelle „Explorer“ oder „GetContent“ angegeben werden. Wenn du diese Option umschaltest, wird es automatisch festgelegt Leer Suffix Anzahl der Spalten Vibrationsstärke Um Dateien zu überschreiben, musst du die Bildquelle „Explorer“ verwenden. Versuche, die Bilder erneut auszuwählen. Wir haben die Bildquelle auf die benötigte geändert Die lineare (oder bilineare, in zwei Dimensionen) Interpolation eignet sich normalerweise gut zum Ändern der Größe eines Bildes, führt jedoch zu einer unerwünschten Weichzeichnung der Details und kann dennoch etwas gezackt wirken Zu besseren Skalierungsmethoden gehören Lanczos-Resampling und Mitchell-Netravali-Filter Eine der einfacheren Möglichkeiten, die Größe zu erhöhen, besteht darin, jedes Pixel durch eine Anzahl Pixel derselben Farbe zu ersetzen Einfachster Android-Skalierungsmodus, der in fast allen Apps verwendet wird Methode zum reibungslosen Interpolieren und Resampling einer Reihe von Kontrollpunkten, die häufig in der Computergrafik zum Erstellen glatter Kurven verwendet wird Fensterfunktion, die häufig in der Signalverarbeitung eingesetzt wird, um spektrale Verluste zu minimieren und die Genauigkeit der Frequenzanalyse durch Verjüngung der Signalflanken zu verbessern Mathematische Interpolationstechnik, die Werte und Ableitungen an den Endpunkten eines Kurvensegments verwendet, um eine glatte und kontinuierliche Kurve zu erzeugen Resampling-Methode, die eine qualitativ hochwertige Interpolation aufrechterhält, indem eine gewichtete Sinusfunktion auf die Pixelwerte angewendet wird Resampling-Methode, die einen Faltungsfilter mit einstellbaren Parametern verwendet, um ein Gleichgewicht zwischen Schärfe und Anti-Aliasing im skalierten Bild zu erreichen Verwendet stückweise definierte Polynomfunktionen zur sanften Interpolation und Annäherung an eine Kurve oder Fläche und bietet so eine flexible und kontinuierliche Formdarstellung Erkenne Text aus einem bestimmten Bild. Über 120 Sprachen werden unterstützt Das Bild enthält keinen Text oder die App hat ihn nicht gefunden Accuracy: %1$s Transparenz Rate Diese App ist völlig kostenlos. Wenn du möchtest, dass sie größer wird, dann gibt dem Projekt bitte einen Stern auf Github 😄 Ausrichtung & Nur Skripterkennung Nur Auto Auto Einzelne Spalte Einzelne Zeile Einzelnes Wort Einzelnes Zeichen Spärlicher Text Möchtest du die Sprach-OCR-Trainingsdaten „%1$s“ für alle Erkennungstypen oder nur für den ausgewählten Typ (%2$s) löschen? Linear Radial Fegen Farbverlaufstyp Mitte X Zentrum Y Kachelmodus Wiederholt Spiegel Klemme Abziehbild Farbstopps Farbe hinzufügen Eigenschaften Wasserzeichen Decke Bilder mit anpassbaren Text-/Bildwasserzeichen ab Wiederholt das Wasserzeichen über dem Bild statt einzeln an der angegebenen Position Versatz X Y-Versatz Wasserzeichentyp Dieses Bild wird als Muster für das Wasserzeichen verwendet Textfarbe Konvertiere Bilder in GIF-Bilder oder extrahiere Rahmen aus einem bestimmten GIF-Bild GIF zu Bildern Konvertiere eine GIF-Datei in einen Bilderstapel Konvertiere einen Stapel Bilder in eine GIF-Datei Wähle zum Starten ein GIF-Bild aus Verwende die Größe des ersten Rahmens Ersetze die angegebene Größe durch die ersten Rahmenabmessungen Zählen wiederholen Frame-Verzögerung Millis FPS Blendet den Inhalt der letzten Apps aus. Es kann nicht erfasst oder aufgezeichnet werden. Bayer Vier-gegen-Vier-Zögern Bayer-Acht-gegen-Acht-Zögern Zweireihiges Sierra-Dithering Einfaches Schwellenwert-Dithering Verwendet stückweise definierte bikubische Polynomfunktionen zur reibungslosen Interpolation und Approximation einer Kurve oder Oberfläche sowie einer flexiblen und kontinuierlichen Formdarstellung Tilt-Shift Samen Kanalverschiebung X Kanalverschiebung Y Korruptionsgröße Korruptionsverschiebung X Korruptionsverschiebung Y Seitliches Ausblenden Seite Spitze Unten Stärke Amplitude Marmor Wassereffekt Heji-Burgess Tone Mapping Geschwindigkeit Einfache Effekte Polaroid Tritanomalie Deuteranomalie Browni Cool Tritanopie Deuteranopie Protanopie Achromatomalie Achromatopsie Orangefarbener Dunst Rosa Traum Goldene Stunde Sanftes Frühlingslicht Herbsttöne Cyberpunk Limonade leicht Spektrales Feuer Nachtzauber Fantasielandschaft Farbexplosion Elektrischer Gradient Karamell-Dunkelheit Futuristischer Farbverlauf Regenbogenwelt Dunkellila Weltraumportal Digitaler Code Noch keine Lieblingsfilter hinzugefügt Drago Aldridge Abgeschnitten Uchimura Möbius Übergang Gipfel Farbanomalie Bilder werden am ursprünglichen Zielort überschrieben Das Bildformat kann nicht geändert werden, während die Option zum Überschreiben von Dateien aktiviert ist Emoji als Farbschema Verwendet die Emoji-Primärfarbe als App-Farbschema anstelle einer manuell definierten Erodieren Anisotrope Diffusion Diffusion Leitung Horizontaler Windstaffel ACES Filmic Tone Mapping ACES Hill Tone Mapping Schnelle bilaterale Unschärfe Poisson Unschärfe Logarithmische Tonzuordnung Kristallisieren Strichfarbe Fraktales Glas Turbulenz Öl Größe Frequenz X Frequenz Y Amplitude X Perlin-Verzerrung Amplitude Y Hable Filmic Tone Mapping Aktuell Alle Voller Filter Wende beliebige Filterketten auf bestimmte Bilder oder einzelne Bilder an Verlaufsgenerator Erstelle einen Farbverlauf in einer bestimmten Ausgabegröße mit benutzerdefinierten Farben und Darstellungstypen Entnebeln Omega Bewertungs App Farbmatrix 4x4 Farbmatrix 3x3 Protanomalie Jahrgang Coda Chrome Nachtsicht Warm Modus „Pfad zeichnen“. Doppellinienpfeil Doppelpfeil Linienpfeil Pfeil Linie Dithering Quantisierer Graustufen Bayer Two by Two Dithering Bayer-Drei-mal-Drei-Zögern Floyd Steinberg zittert Jarvis Judice Ninke Dithering Sierra Dithering Sierra Lite-Dithering Atkinson Dithering Stucki Dithering Burkes Dithering Falsches Floyd-Steinberg-Dithering Dithering von links nach rechts Zufälliges Dithering Skalierungsmodus Bilinear Hann Einsiedler Lanczos Mitchell Nächste Spline Basic Standardwert Sigma Räumliches Sigma Mediane Unschärfe Catmull Bikubisch Nur Clip Das Speichern im Speicher wird nicht durchgeführt und es wird versucht, das Bild nur in die Zwischenablage zu legen Fügt einen Container mit der ausgewählten Form unter den Symbolen hinzu Symbolform Durchsetzung der Helligkeit Bildschirm Verlaufsüberlagerung Erstelle einen beliebigen Farbverlauf am oberen Rand der angegebenen Bilder Transformationen Kamera Nimm ein Bild mit der Kamera auf. Beachte, dass es möglich ist, nur ein Bild aus dieser Bildquelle zu erhalten Getreide Unscharf Pastell Heißer Sommer Lila Nebel Sonnenaufgang Bunter Wirbel Lavendeltraum Grüne Sonne Roter Wirbel Wasserzeichen wiederholen Overlay-Modus Bokeh GIF Tools Bilder zu GIF Benutze Lasso Verwendet Lasso wie im Zeichenmodus zum Löschen Originalbildvorschau Alpha Das Emoji der App Leiste ändert sich zufällig Zufällige Emojis Du kannst keine zufälligen Emojis verwenden, wenn Emojis deaktiviert sind Du kannst kein Emoji auswählen, wenn zufällige Emojis aktiviert sind Alter Fernseher Mischunschärfe OCR (Text erkennen) Damit Tesseract OCR ordnungsgemäß funktioniert, müssen zusätzliche Trainingsdaten (%1$s) auf dein Gerät heruntergeladen werden. \nMöchtest du %2$s Daten herunterladen? Keine Verbindung. Überprüfen die Verbindung und versuche es erneut, um Eisenbahnmodelle herunterzuladen Der Pinsel stellt den Hintergrund wieder her, anstatt ihn auszuradieren Horizontales Gitter Vertikales Gitter Stichmodus Anzahl der Zeilen Verwende Pixel Switch Verwendet einen Google Pixel-ähnlichen Schalter Gleiten Seite an Seite Tippen umschalten Überschriebene Datei mit dem Namen %1$s am ursprünglichen Ziel Lupe Aktiviert die Lupe im Zeichenmodus für bessere Zugänglichkeit oben am Finger Anfangswert erzwingen Erzwingt die anfängliche Überprüfung des Exif-Widgets Mehrere Sprachen zulassen Favorit B-Spline Native Stapelunschärfe Konfetti Konfetti wird beim Speichern, Teilen und anderen primären Aktionen angezeigt Sicherer Modus Ähnlich wie Datenschutzunschärfe, aber pixelig statt unscharf Ermöglicht die Übernahme eines Begrenzungsrahmens für die Bildausrichtung Verwischt das Bild unter dem gezeichneten Pfad, um alles zu sichern, was du verbergen möchtest Fabs Malt einen Schatten hinter schwebenden Aktionsschaltflächen Verlassen Wenn du die Vorschau jetzt verlässt, musst du die Bilder erneut hinzufügen Bildformat Lärm Pixelsortierung Mischen Anaglyphe Erstellt die Palette \"Material You\" aus dem Bild Dunkle Farben Verwendet das Nachtmodus-Farbschema anstelle der Lichtvariante Als \"Jetpack Compose\" -Code kopieren Ringunschärfe Kreuzunschärfe Kreisunschärfe Sternenunschärfe Lineare Neigungsverschiebung Zu entfernende Tags Bilder zu APNG APNG Tools Konvertiere Bilder in APNG-Bilder oder extrahiere Rahmen aus einem bestimmten APNG-Bild APNG zu Bildern Wähle zum Starten das APNG-Bild aus Bewegungsunschärfe Konvertiere die APNG-Datei in einen Bilderstapel Konvertiere einen Stapel Bilder in eine APNG-Datei Zip Erstelle eine Zip-Datei aus bestimmten Dateien oder Bildern Griffbreite ziehen Regen Festlich Explodieren Ecken JXL Tools Führe eine JXL ~ JPEG Konvertierung ohne Qualitätsverlust durch, oder wandle GIF/APNG in JXL Animation um Verlustfreie Konvertierung von JXL zu JPEG Verlustfreie Konvertierung von JPEG zu JXL JPEG zu JXL Fast Gaussian Blur 3D Automatisches Einfügen Erlaubt der App automatisches Einfügen von der Zwischenablage, damit es auf dem Bildschirm erscheint und bearbeitet werden kann. Farben Vereinheitlichung Ebenen Vereinheitlichung JXL zu JPEG Wähle JXL Bild zum Starten Fast Gaussian Blur 2D Fast Gaussian Blur 4D Konfetti Typ GIF zu JXL Konvertiere GIF Bilder zu JXL animierte Bilder APNG zu JXL Konvertiere APNG Bilder zu JXL animierten Bilder JXL zu Bild BIlder zu JXL Vorschau generieren Lanczos Bessel Verhalten Dateiauswahl überspringen Aktiviert die Vorschauerstellung, was helfen könnte, Abstürze auf manchen Geräten zu verhindern, aber dadurch werden auch einige Funktionen der Einzelbildbearbeitung deaktiviert Wandelt eine JXL Animation in einen Bilderstapel um Wandelt einen Bilderstapel in eine JXL Animation um Wenn möglich wird die Dateiauswahl automatisch geöffnet Verlustbehaftete Kompression Verwendet verlustbehaftete Kompression (anstelle von verlustfreier) um die Dateigröße zu verringern Resampling-Methode, die durch Anwendung einer Bessel Funktion auf die Pixel-Werte eine hochqualitative Interpolation gewährleistet Komprimierungstyp Einstellungen für die Geschwindigkeit der Bild-Dekodierung, dies sollte helfen, das entsprechende Bild schneller zu öffnen, ein Wert von %1$s entspricht dem langsamsten Dekodieren, ein Wert von %2$s dem schnellsten, diese Einstellung kann die Größe der Bilddatei erhöhen Wähle mehrere Medien Wähle einzelnes Medium Wählen Heute Gestern Image Toolbox\'s Bildauswahl Keine Berechtigungen Kanal-Konfiguration Integrierte Auswahl Anfrage Sortierung Datum Datum (umgekehrt) Name Name (umgekehrt) Nochmal versuchen Zeige die Einstellungen im Querformat Wenn deaktiviert, werden die Einstellungen im Querformat über die Taste in der oberen App-Leiste aufgerufen, anstatt immer sichtbar zu sein Vollbild-Einstellungen Schalter Typ Compose Ein Material You Schalter Ein Jetpack Compose Material You Schalter Ein Schalter basierend auf dem \"Fluent\" Design Pixel Fluent Cupertino Ein Schalter basiert auf dem \"Cupertino\" Design Wenn aktiviert, werden die Einstellungen immer im Vollbild angezeigt, anstatt einer ausfahrbaren Seitenleiste Max Detailliert Bilder zu SVG Pfad auslassen Die Verwendung dieses Tools für die Umwandlung großer Bilder ohne Herunterskalierung wird nicht empfohlen, da dies zu Abstürzen führen und die Verarbeitungszeit verlängern kann. Bild herunterskalieren Standardlinienbreite Ankergröße ändern Vorgegebene Bilder in SVG-Bilder umwandeln Gesampelte Palette verwenden Die Quantisierungspalette wird gesampelt., wenn diese Option aktiviert ist. Das Bild wird vor der Verarbeitung auf eine geringere Größe herunterskaliert, damit das Tool schneller und sicherer arbeiten kann Minimales Farbverhältnis Schwellenwert für Linien Rundungstoleranz für Koordinaten Pfadskalierung Eigenschaften zurücksetzen Alle Eigenschaften werden auf deine Standardwerte zurückgesetzt. Beachte, dass dies nicht rückgängig gemacht werden kann. LSTM-Netzwerk Konvertieren Kompression X-Auflösung Y-Auflösung Auflösungseinheit Bildbeschreibung Modell Software Künstler Urheberrecht Belichtungsmodus Weißabgleich Digitalzoom-Verhältnis Kontrast GPS-Verarbeitungsmethode GPS-Gebietsinformationen GPS-Datumsstempel GPS-Differenzial Interoperabilitätsindex DNG-Version Text wiederholen Der aktuelle Text wird bis zum Ende des Pfades wiederholt, anstatt einmalig zu zeichnen Strichgröße Ausgewähltes Bild verwenden, um es entlang eines vorgegebenen Pfades zu zeichnen Als PDF teilen Die folgenden Optionen dienen zum Speichern von Bildern, nicht von PDF-Dateien Kubisch B-Spline Hamming Hanning Blackman Welch Gaußisch Sphinx Bartlett Robidoux Spline 16 Spline 36 Spline 64 Kaiser Bartlett-Hann Box Bohman Lanczos 4 Lanczos 3 Jinc Min. Zeichnet ein umrandetes Dreieck vom Startpunkt zum Endpunkt Umrandetes Dreieck Zeichnet ein umrandetes Dreieck vom Startpunkt zum Endpunkt Dreieck Zeichnet ein Polygon vom Startpunkt zum Endpunkt Polygon Umrandetes Polygon Zeichnet ein umrandetes Polygon vom Startpunkt zum Endpunkt Stern Umrandeter Stern Zeichnet einen umrandeten Stern vom Startpunkt zum Endpunkt Innenradius Verhältnis Komprimierte Bits pro Pixel Neuen Ordner hinzufügen Übertragungsfunktion Weißer Punkt Belichtungszeit Primäre Chromatizitäten Exif-Version Flashpix-Version Farbraum Gamma Zugehörige Tondatei Zeitverschiebung Helligkeitswert CFA-Muster Benutzer-Kommentar Fotografische Empfindlichkeit Empfindlichkeitstyp ISO-Geschwindigkeit ISO-Geschwindigkeit Breitengrad zzz Farbempfindlichkeit ISO-Geschwindigkeit Breitengrad yyy Verschlusszeitwert Max. Blendenwert Messmodus Blendenwert Brennweite Blitzlicht Blitzlicht-Energie Belichtungsindex Dateiquelle Name des Kamerabesitzers GPS-Breitengrad GPS-Messmodus Sättigung Schärfe Geräteeinstellung-Beschreibung Seriennummer des Gehäuses Spezifikation des Objektivs Objektiv-Modell Seriennummer des Objektivs GPS-Zeitstempel GPS-Längengrad GPS-Höhe GPS-Satelliten GPS-Status GPS-Bildrichtung GPS-Kartendatum GPS-Geschwindigkeit Schriftgröße Wasserzeichengröße ISO Text auf Pfad mit gegebener Schriftart und Farbe zeichnen Dieses Bild wird als wiederholter Eintrag des gezeichneten Pfades verwendet Als PDF speichern Regelmäßigen Stern zeichnen Eingabe per Textfeld zulassen Keine Vorlagenfilter hinzugefügt Der gescannte QR Code ist keine gültige Filtervorlage QR Code scannen Erlaube Kamera Berechtigung in den Einstellungen, um den QR-Code zu scannen Ausgewählte Datei hat keine Filtervorlagedaten Vorlage erstellen Quadratisch Lanczos 2 Lanczos 4 Jinc Lanczos 2 Jinc Lanczos 3 Linear Rastergröße X Rastergröße Y Kantenglättung Aktiviert die Kantenglättung, um scharfe Kanten zu vermeiden Zum Inhalt zuschneiden Rahmenfarbe Zu ignorierende Farbe Bearbeiten statt Vorschau öffnen Vorlage Neu erstellen Dieses Bild wird für die Vorschau dieser Filtervorlage verwendet Vorlagenfilter Vorlagenname Als QR-Code-Bild Als Datei Als Datei speichern Als QR-Code-Bild speichern Vorlage löschen Du bist dabei, den ausgewählten Vorlagenfilter zu löschen. Dieser Vorgang kann nicht rückgängig gemacht werden Filtervorschau QR & Barcode Code-Inhalt QR-Beschreibung Konvertieren von mehrere Bildern in ein bestimmtes Format Wähle einen Filter, um ihn als Farbe zu nutzen Bits pro Probe Photometrische Auswertung Abtastungen pro Pixel Planare Konfiguration Y Cb Cr Unterprobenahme Y Cb Cr Positionierung Streifenverschiebungen Zeilen pro Streifen Bild aufteilen Teile ein einzelnes Bild nach Zeilen oder Spalten auf Konvertieren von Bildstapeln von einem Format in ein anderes Stapelverarbeitung Motor Modus Legacy Bild stapeln Stapel Bilder mit ausgewählten Mischmodi übereinander Altbestand & LSTM Strip Byte-Zahlen JPEG Interchange Format Quadratischer Schwellenwert Scanne Dokumente und erstelle PDFs oder separate Bilder daraus Dokumentenscanner Favoriten hinzufügen Keine bevorzugten Optionen ausgewählt, füge sie auf der Seite Tools hinzu Wenn du ein Bild auswählst, um es in ImageToolbox zu öffnen (Vorschau), wird anstatt der Vorschau das Auswahlblatt \"Bearbeiten\" geöffnet Scanne den QR-Code und rufe seinen Inhalt ab oder füge deine Zeichenfolge ein, um einen neuen zu erstellen Scanne einen beliebigen Barcode, um den Inhalt des Feldes zu ersetzen, oder schreib etwas, um einen neuen Barcode mit dem ausgewählten Typ zu erzeugen JPEG Austauschformat Länge Y Cb Cr Koeffizienten Machen Pixel X Dimension Pixel Y Dimension Anmerkung des Herstellers Datum Uhrzeit Digitalisiert OECF Empfohlener Belichtungsindex Belichtung Verzerrungswert Thema Entfernung Räumlicher Frequenzgang Brennebene X Auflösung Brennebene Y Auflösung Einheit für die Auflösung der Brennebene Thema Standort Sensorik Methode Benutzerdefiniert gerendert Brennweite bei 35-mm-Film Szenenerfassungstyp Verstärkungsregelung Einzigartige Bild-ID Thema Entfernungsbereich Objektiv Marke GPS-Version ID GPS Breitengrad Referenz GPS Längengrad Referenz GPS Höhenangabe Referenz GPS DOP GPS-Geschwindigkeitsreferenz GPS-Track-Referenz GPS Track GPS-Bild Richtungsreferenz GPS Ziel Breitengrad Referenz GPS-Ziel Breitengrad GPS Ziel Längengrad Ref GPS Ziel Peilung Referenz GPS-Zielpeilung GPS-Zielentfernung GPS H-Ortungsfehler Standardausschnittgröße Bildvorschau Start Vorschaubild Länge Aspekt Rahmen Sensor unterer Rand Sensor Linker Rand Sensor Rechter Rand Sensor Oberer Rand Klicken, um das Scannen zu starten Scannen starten Verwendet stückweise definierte Polynomfunktionen, um eine Kurve oder Fläche sanft zu interpolieren und anzunähern, flexible und kontinuierliche Formdarstellung ‘Eine Variante des Hann-Fensters, die häufig zur Verringerung spektraler Leckagen bei der Signalverarbeitung verwendet wird Eine Fensterfunktion, die eine gute Frequenzauflösung bietet, indem sie spektrale Leckagen minimiert, und die häufig in der Signalverarbeitung verwendet wird Eine hybride Fensterfunktion, die das Bartlett- und das Hann-Fenster kombiniert und zur Verringerung spektraler Leckagen bei der Signalverarbeitung verwendet wird Eine Variante des Lanczos-4-Filters, welche die jinc-Funktion verwendet und eine hochwertige Interpolation mit minimalen Artefakten ermöglicht Regelmäßiges Polygon zeichnen Zeichne ein regelmäßiges Polygon anstelle einer freien Form Zeichnet den Stern vom Startpunkt zum Endpunkt Elliptical Weighted Average (EWA)-Variante des Lanczos-3-Jinc-Filters für qualitativ hochwertiges Resampling mit reduziertem Aliasing Elliptische gewichtete Mittelwertbildung (EWA) als Variante des Lanczos-Sharp-Filters zur Erzielung scharfer Ergebnisse mit minimalen Artefakten Zeichne einen Stern, der regelmäßig statt frei geformt sein wird Referenz Schwarz Weiß Datum Uhrzeit Original Eine Interpolationsmethode, die das Kaiser-Fenster verwendet und eine gute Kontrolle über den Kompromiss zwischen Hauptkeulenbreite und Nebenkeulenpegel bietet Eine Fensterfunktion, die für eine gute Frequenzauflösung mit reduziertem spektralen Leck entwickelt wurde und häufig in der Signalverarbeitung verwendet wird Eine einfache Resampling-Methode, bei welcher der Durchschnitt der nächstgelegenen Pixelwerte verwendet wird, was oft zu einem blockigen Aussehen führt Eine Variante des Lanczos-2-Filters, welche die jinc-Funktion verwendet und eine hochwertige Interpolation mit minimalen Artefakten ermöglicht Datum Uhrzeit Eine Fensterfunktion, die dazu dient, spektrale Leckagen zu reduzieren, indem die Ränder eines Signals verjüngt werden; nützlich bei der Signalverarbeitung Ein Lanczos-Resampling-Filter mit einer höheren Ordnung von 6, der eine schärfere und genauere Bildskalierung ermöglicht Versetzte Zeit Original Sub Sec Zeit Belichtungsprogramm GPS-Ziel Längengrad GPS Ziel Entfernung Referenz Eine Variante des Lanczos-3-Filters, welche die jinc-Funktion verwendet und eine hochwertige Interpolation mit minimalen Artefakten ermöglicht Die kubische Interpolation sorgt für eine glattere Skalierung, indem sie die nächstgelegenen 16 Pixel berücksichtigt und bessere Ergebnisse als die bilineare Interpolation liefert. Eine Interpolationsmethode, die eine Gauß-Funktion anwendet und zur Glättung und Rauschunterdrückung in Bildern dient Versetzte Zeit Digitalisiert Standard-Ausgangs-Empfindlichkeit Themenbereich Scheitelpunkte Sub Sec Zeit Original Sub Sec Zeit Digitalisiert F Nummer Einfacher alter Fernseher Zielbild Palette übertragen Verbessertes Öl Einfache Skizze HDR Gotham Farbiges Poster Kantenmodus Lineare Boxunschärfe Lineare Zelt Unschärfe Linearer Gaußscher Box Unschärfe Farbenblindheit Wähle den Modus, um die Themenfarben für die gewählte Variante der Farbenblindheit anzupassen Schwierigkeiten bei der Unterscheidung zwischen roten und grünen Farbtönen Schwierigkeiten bei der Unterscheidung zwischen grünen und roten Farbtönen Schwierigkeiten bei der Unterscheidung zwischen blauen und gelben Farbtönen Unfähigkeit, rote Farbtöne wahrzunehmen Unfähigkeit, grüne Farbtöne wahrzunehmen Unfähigkeit, blaue Farbtöne wahrzunehmen Verminderte Empfindlichkeit für alle Farben Vollständige Farbenblindheit, sieht nur Grautöne Kein Farbenblindheitschema verwenden Farben werden genau so sein, wie sie im Thema festgelegt sind Sigmoidal Lagrange 2 Ein Lagrange-Interpolationsfilter der Ordnung 2, geeignet für hochwertige Bildskalierung mit glatten Übergängen Ein Lagrange-Interpolationsfilter der Ordnung 3, der eine bessere Genauigkeit und glattere Ergebnisse bei der Bildskalierung bietet Lanczos 6 Lanczos 6 Jinc Linearer schneller Gaußscher Unschärfe Next Linearer Gaußscher Unschärfe Filter ersetzen Linearer schneller Gaußscher Unschärfe Wählen den Filter unten, um ihn als Pinsel in deiner Zeichnung zu nutzen Histogramm ausgleichen HSV Histogramm ausgleichen Robidoux Scharf Eine Methode, bei der eine quadratische Funktion zur Interpolation verwendet wird, die glatte und kontinuierliche Ergebnisse liefert Eine fortschrittliche Resampling-Methode, die eine hochwertige Interpolation mit minimalen Artefakten ermöglicht Eine schärfere Variante der Robidoux-Methode, optimiert für eine scharfe Bildgrößenänderung Eine splinebasierte Interpolationsmethode, die mit einem 16-Tap-Filter glatte Ergebnisse liefert Eine splinebasierte Interpolationsmethode, die mit einem 36-Tap-Filter glatte Ergebnisse liefert Eine splinebasierte Interpolationsmethode, die mit einem 64-Tap-Filter glatte Ergebnisse liefert Eine Fensterfunktion, die zur Verringerung spektraler Leckagen verwendet wird und eine gute Frequenzauflösung bei Signalverarbeitungsanwendungen ermöglicht Eine Resampling-Methode, die einen 2-lobe Lanczos-Filter für hochwertige Interpolation mit minimalen Artefakten verwendet Eine Resampling-Methode, die einen 4-lobe Lanczos-Filter für hochwertige Interpolation mit minimalen Artefakten verwendet Prozentsatz eingeben Ermöglicht ein Textfeld hinter der Auswahl der Voreinstellungen, um sie spontan einzugeben Clip Wickeln TIFF Komprimierungsschema Niedrig Poly Sandmalerei Dreifarbig Dritte Farbe Clahe Oklab Clahe Oklch Clahe Jzazbz Hanning EWA Elliptische gewichteter Durchschnitt (EWA) als Variante des Hanning-Filters für glatte Interpolation und Resampling Robidoux EWA Elliptische gewichteter Durchschnitt (EWA) als Variante des Robidoux-Filters für hochwertiges Resampling Blackman EWA Quadrische EWA Elliptische gewichteter Durchschnitt (EWA) als Variante des Robidoux Scharf-Filters für schärfere Ergebnisse Lanczos 3 Jinc EWA Ginseng Ein Resampling-Filter für hochwertige Bildverarbeitung mit einem ausgewogenen Verhältnis von Schärfe und Glätte Ginseng EWA Elliptische gewichteter Durchschnitt (EWA) als Variante des Ginseng-Filters zur Verbesserung der Bildqualität Lanczos Scharf EWA Lanczos 4 Schärfste EWA Elliptische gewichteter Durchschnitt (EWA) Variante des Lanczos 4 schärfste Filters für extrem scharfes Bild-Resampling Lanczos Weich EWA Elliptische gewichteter Durchschnitt (EWA) als Variante des Lanczos-Weich-Filters für eine glattere Bildumwandlung Haasn Weich Ein von Haasn entwickelter Resampling-Filter für eine glatte und artefaktfreie Bildskalierung Für immer verwerfen Skala Farbraum Histogramm-Pixelung ausgleichen Histogramm ausgleichen Adaptiv Histogramm ausgleichen Adaptive LUV Histogramm ausgleichen Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Bild hinzufügen Geclustertes 2x2 Dithering Polka Dot Geclustertes 4x4 Dithering Yililoma Dithering Anzahl der Behälter Histogramm ausgleichen Adaptive HSV Mischen, Töne erzeugen, Schattierungen erzeugen und mehr Analog + Komplementär Komplementär Analog Triadisch Geteilt Komplementär Tetradisch Viereckig Farb Tools Farbharmonien Farbschattierung Farbtöne Schattierungen Farbmischung Farbe Info Ausgewählte Farbe Tönep Monet kann nicht verwendet werden, wenn die dynamischen Farben aktiviert sind LUT-Zielbild Weiche Eleganz Variante 512x512 2D LUT Amatorka Miss Etikate Weiche Eleganz Elliptische gewichteter Durchschnitt (EWA) Variante des Blackman-Filters zur Minimierung von Ringing-Artefakten Robidoux Scharf EWA Elliptische gewichteter Durchschnitt (EWA) als Variante des Quadric-Filters für glatte Interpolation Clahe HSL Clahe HSV Histogramm ausgleichen Adaptive HSL Eine dreieckige Fensterfunktion, die in der Signalverarbeitung zur Verringerung spektraler Leckagen verwendet wird Ein hochwertiges Interpolationsverfahren, das für die natürliche Größenanpassung von Bildern optimiert ist und ein Gleichgewicht zwischen Schärfe und Glätte schafft Lagrange 3 Eine Variante des Lanczos-6-Filters unter Verwendung einer Jinc-Funktion zur Verbesserung der Qualität des Bild-Resamplings Eine Resampling-Methode, die einen 3-lobe Lanczos-Filter für hochwertige Interpolation mit minimalen Artefakten verwendet Lineare Stapelunschärfe Gaußscher Box Unschärfe Sprachen erfolgreich importiert OCR Modelle sichern Geclustertes 8x8 Dithering Farbe zum Mischen An Grenze halten Sanftes Glühen Kombiniere den Zuschneidemodus mit diesem Parameter, um das gewünschte Verhalten zu erreichen (Zuschneiden/Anpassen an das Seitenverhältnis) Variation Oben links Oben rechts Unten links Unten rechts Oben Mitte Mitte rechts Unten Mitte Mitte links Importieren Exportieren Position Mitte Filtervorlage mit Namen \"%1$s\" (%2$s) hinzugefügt LUT Bleich Bypass Kerzenlicht Tropfen Blues Edgy Amber Herbstfarben Filmstock 50 Neblige Nacht Kodak ­Neutrales LUT Bild Palette Transfer Variante 3D LUT Target 3D LUT File (.cube / .CUBE) Verwenden zunächst deine bevorzugte Fotobearbeitungsanwendung, um einen Filter auf die neutrale LUT anzuwenden, die du hier erhalten kannst. Damit dies richtig funktioniert, darf die Farbe jedes Pixels nicht von anderen Pixeln abhängen (z. B. funktioniert der Weichzeichner nicht). Sobald du fertig bist, verwendest du dein neues LUT-Bild als Eingabe für den 512*512 LUT-Filter Pop Art Erteile Kamera Erlaubnis in den Einstellungen, um den Dokumentenscanner zu scannen Goldener Wald Grünlich Retrogelb Kaffee Zelluloid Links Vorschau Aktiviert das Abrufen der Linkvorschau an Stellen, an denen du Text erhalten kannst (QR-Code, OCR usw.) Links ICO-Dateien können nur mit einer maximalen Größe von 256 x 256 gespeichert werden Standardfarbe zum Zeichnen Standard-Zeichenpfadmodus Zeitstempel hinzufügen Aktiviert das Hinzufügen eines Zeitstempels zum Dateinamen der Ausgabe Formatierter Zeitstempel Zeitstempelformatierung im Ausgabedateinamen anstelle von einfachen Millisekunden aktivieren Zeitstempel aktivieren, um ihr Format auszuwählen Kürzlich verwendet CI-Kanal Gruppe Lass dich über neue Versionen der App benachrichtigen und lies Ankündigungen Tritt unserem Chat bei, in dem du alles besprechen kannst, was du willst, und schau auch in den CI-Kanal, wo ich Betas und Ankündigungen poste Kein vollständiger Zugriff auf Dateien Erlaube allen Dateien den Zugriff, um JXL, QOI und andere Bilder zu sehen, die unter Android nicht als Bilder erkannt werden. Ohne diese Berechtigung kann Image Toolbox diese Bilder nicht anzeigen Einmaliger Speicherort Einmalige Speicherorte anzeigen und bearbeiten, die du durch langes Drücken der Speichertaste in fast allen Optionen verwenden kannst GIF zu WEBP GIF-Bilder in animierte WEBP-Bilder umwandeln WEBP zu Bildern ­­WEBP Tools Bilder in animierte WEBP-Bilder umwandeln oder Einzelbilder aus einer gegebenen WEBP-Animation extrahieren WEBP-Datei in einen Stapel an Bildern umwandeln Stapel an Bildern in eine WEBP-Datei umwandeln Bilder zu WEBP WEBP-Bild zum Starten auswählen Image Toolbox in Telegram 🎉 Eigene Optionen Optionen sollten nach folgendem Muster eingegeben werden: \"--{option_name} {value}\" Freie Ecken Maske Stelle heilen Punkte werden nicht durch Bildgrenzen gebunden, nützlich für genauere perspektivische Korrektur Inhaltsbewusste Füllung unter Zeichenpfad RGB- oder Helligkeits-Histogramm, um dir zu helfen, Anpassungen vorzunehmen Tesseract-Optionen Eingabevariablen für die Tesseract Engine anwenden Autom. Beschnitt Bild per Vieleck beschneiden. (Korrigiert auch die Perspektive.) Punkte in Bildgrenzen zwingen Passe ein Bild an vorgegebene Abmessungen an und wende Weichzeichner oder Farbe auf den Hintergrund an Anordnung der Tools Tools nach Typ gruppieren Tools im Hauptfenster nach Typ gruppieren anstatt untereinander als Liste Standardwerte Systemleisten Sichtbarkeit Systemleisten durch Wischen anzeigen Zeigt Systemleisten durch Wischen an, falls sie versteckt sind Auto Alle verbergen Alle anzeigen Navi-Leiste verbergen Statusleiste verbergen Rauscherzeugung Verschiedenes Rauschen erzeugen wie Perlin oder andere Frequenz Rauschtyp Rotationsart Fraktaltyp Oktaven Lückenhaftigkeit Zuwachs Gewichtete Stärke Flimmern Ping Pong Stärke Abstandsfunktion Wiedergabetyp Ausrichtung Eigener Dateiname Wähle Speicherort und Dateiname für das aktuelles Bild In Ordner mit eigenem Namen gespeichert Collagen erstellen Collagen-Art Bild halten zum Wechseln, bewegen und zoomen um Position anzupassen Histogramm Dieses Bild wird verwendet, um RGB und Helligkeits-Histogramme zu erzeugen Wird geöffnet Wird geschlossen Morphologischer Farbverlauf Schwarzer Hut Gestempelt Kurven zurücksetzen Linienstil Lückengröße Gestrichelt Strichpunktiert Zickzack Zeichnet eine gepunktete und gestrichelte Linie entlang eines angegebenen Pfades Zeichnet ein wellenförmiges Zickzack entlang des Pfades Zickzack-Verhältnis Schwellenwert Eins Schwellenwert Zwei Zeichnet eine gestrichelte Linie entlang des gezeichneten Pfades mit der angegebenen Lückengröße Nur standardmäßige gerade Linien Zeichnet ausgewählte Formen entlang des Pfades mit dem angegebenen Abstand Überblendung Zylinderhut Tonkurven Die Kurven werden auf den Standardwert zurückgesetzt Verknüpfung erstellen Tool zum Anheften auswählen Das Tool wird dem Startbildschirm deines Starters als Verknüpfung hinzugefügt, verwende es in Kombination mit der Einstellung „Dateiauswahl überspringen“, um das gewünschte Verhalten zu erreichen Erstelle Collagen aus bis zu 20 Bildern Spiegel 101 Verbesserte Zoom Unschärfe Rasterfarbe Hilfsraster Zeigt ein Hilfsraster über der Zeichenfläche an, um präzise Manipulationen zu ermöglichen Zellenbreite Zellenhöhe Kompakte Selektoren Einige Auswahlkontrollen verwenden ein kompaktes Layout, um weniger Platz zu benötigen Layout Titel des Hauptbildschirms Gewähre der Kamera in den Einstellungen die Berechtigung, ein Bild aufzunehmen Konstanter Ratenfaktor (KRF) Ein Wert von %1$s bedeutet eine langsame Komprimierung, was zu einer relativ kleinen Dateigröße führt. %2$s bedeutet eine schnellere Komprimierung, was zu einer großen Datei führt. Lut Bücherei Download einer Sammlung von LUTs, die du nach dem Herunterladen anwenden kannst Circle Kernel verwenden Aktualisiere LUT-Sammlung (nur neue werden in die Warteschlange gestellt), die du nach dem Herunterladen anwenden kannst Domain Warp Keine Rahmen stapeln Ermöglicht die Beseitigung vorheriger Einzelbilder, so dass sie nicht übereinander gestapelt werden Frames werden ineinander überblendet Anzahl der Überblendungsbilder Canny Laplacian Einfach Sobel Einfach Vorschaubild Standardbildvorschau für Filter ändern Ausblenden Anzeigen Schieberegler-Typ Ein Material You Schieberegler Ein schick aussehender Schieberegler. Das ist die Standardoption Material 2 Schick Ein Material 2 Schieberegler Anwenden Schaltflächen der Dialoge werden nach Möglichkeit in der Mitte anstatt links positioniert Markierungsebenen Ebenenmodus mit der Möglichkeit, Bilder, Text und mehr frei zu platzieren Benutze ein Bild als Hintergrund und füge verschiedene Ebenen darüber hinzu Ebenen auf dem Bild Dialogschaltflächen zentrieren Open Source Lizenzen Lizenzen der in dieser App verwendeten Open Source Bibliotheken anzeigen Eingabe % Auf Website kann nicht zugegriffen werden, versuche es mit VPN oder überprüfe, ob die URL korrekt ist Ebene bearbeiten Ebenen auf dem Hintergrund Das selbe wie bei der ersten Option, aber mit Farbe statt Bild Bereich Resampling unter Verwendung des Pixelflächenverhältnisses. Diese Methode kann bei der Bilddezimierung bevorzugt werden, da sie moirefreie Ergebnisse liefert. Wenn das Bild jedoch gezoomt wird, ähnelt sie der „Nähesten“-Methode. Tonemapping einschalten Schnelleinstellungen Seite Hinzufügen eines schwebenden Streifens an der ausgewählten Seite während der Bildbearbeitung, der beim Anklicken die Schnelleinstellungen öffnet Beta Auswahl löschen Base64 Tools Base64-String in Bild dekodieren, oder Bild in Base64-Format kodieren Base64 Bereitgestellter Wert ist keine gültige Base64-Zeichenkette Leere oder ungültige Base64-Zeichenfolge kann nicht kopiert werden Base64 einfügen Base64 kopieren Base64 speichern Base64 teilen Optionen Aktionen Base64 importieren Einstellungsgruppe \"%1$s\" wird standardmäßig eingeklappt Einstellungsgruppe \"%1$s\" wird standardmäßig erweitert Bild laden, um Base64-Zeichenfolge zu kopieren oder zu speichern. Wenn du die Zeichenfolge selbst hast, kannst du sie oben einfügen, um das Bild zu erhalten Base64 Aktionen Hinzufügen eines Umrisses um den Text mit bestimmter Farbe und Breite Umrissfarbe Umriss hinzufügen Umrissgröße Prüfsumme Übereinstimmung! Prüfsummen sind gleich, es kann sicher sein Die Namen der ausgegebenen Bilder entsprechen den Prüfsummen der Daten Prüfsummen vergleichen, Hashwerte berechnen, oder Hex Zeichenketten aus Dateien mit verschiedenen Hash-Algorithmen erstellen Berechnen Prüfsumme zum Vergleichen Unterschied Prüfsummen sind nicht gleich, Datei kann unsicher sein! Algorithmus Prüfsummen Tools Prüfsumme als Dateiname Drehung Freie Software (Partner) Mehr nützliche Software im Partnerkanal der Android-Anwendungen Text Hash Quelle Prüfsumme Meshverläufe Text eingeben, um seine Prüfsumme des gewählten Algorithmus zu berechnen Wählen die Datei aus, um ihre Prüfsumme des gewählten Algorithmus zu berechnen Online-Sammlung von Meshverläufen ansehen Importierte Schriftarten Schriftarten exportieren Nur TTF und OTF Schriften können importiert werden Schriftart importieren (TTF/OTF) Benutzerdefinierte Seiten Wenn du bei der Verwendung bestimmter Tools nicht gespeicherte Änderungen vorgenommen hast und versuchst, sie zu schließen, wird ein Bestätigungsdialog angezeigt Bestätigung der Beendigung des Tools Keine Fehler beim Speichern, versuche den Ausgabeordner zu ändern Dateiname ist nicht festgelegt Seitenauswahl EXIF bearbeiten Stapelvergleich Wähle Datei(en) aus, deren Prüfsumme auf der Grundlage des gewählten Algorithmus berechnet werden soll Verzeichnis auswählen Metadaten eines einzelnen Bildes ohne erneute Komprimierung ändern Tippen, um die verfügbaren Tags zu bearbeiten Sticker ändern Breite anpassen Höhe anpassen Dateien auswählen Kopf Länge Skala Polsterung Horizontale Pivotlinie Umgekehrte Auswahl Horizontal geschnittene Teile werden ausgelassen, anstatt die Teile um den Schnittbereich herum zusammenzuführen. Erstellen eines Maschenverlaufs mit benutzerdefinierter Anzahl von Knoten und Auflösung Zusammensetzen des Maschenverlaufs des oberen Teils der gegebenen Bilder Auflösung X Auflösung Y Auflösung Maschenverlaufsüberlagerung Punkteanpassung Zeitstempel Vertikaler Schnittteil wird ausgelassen, anstatt Teile um den Schnittbereich herum zusammenzuführen Rastergröße Stempel Formatmuster Bild schneiden Sammlung von Maschenverläufen Schneide einen Teil des Bildes aus und füge den linken Teil (kann auch umgekehrt sein) mit vertikalen oder horizontalen Linien zusammen Vertikale Pivotlinie Pixel für Pixel Hervorhebungsfarbe Pixel-Vergleichstyp In Datei schreiben Barcode scannen Höhenverhältnis Barcode-Typ Erzwinge Schwarz/Weiß Barcode-Bild wird vollständig schwarz-weiß und nicht durch das App-Thema eingefärbt Jeden Barcode (QR, EAN, AZTEC, …) scannen und seinen Inhalt abrufen oder Ihren Text einfügen, um einen neuen zu erstellen Kein Barcode gefunden Generierter Barcode wird hier sein Rechts nach links MIME Typ (umgekehrt) Extrahiere Albumcoverbilder aus Audiodateien, die meisten gängigen Formate werden unterstützt Audio Covers Audio zum Starten auswählen Audio auswählen Keine Cover gefunden Protokolle senden Du kannst mich über die unten stehenden Optionen kontaktieren und ich werde versuchen, eine Lösung zu finden.\n(Vergiss nicht, Protokolle anzuhängen) Text aus einem Stapel von Bildern extrahieren und in einer Textdatei speichern In Metadaten schreiben Extrahiere Text aus jedem Bild und füge ihn in die EXIF Informationen der entsprechenden Fotos ein LSB benutzen Die LSB (Less Significant Bit) Steganographie Methode wird verwendet, ansonsten FD (Frequency Domain) Rote Augen automatisch entfernen Passwort Entsperren PDF ist geschützt Vorgang fast abgeschlossen. Wenn jetzt abgebrochen wird, muss neugestartet werden Datum geändert (umgekehrt) MIME Typ Erweiterung Erweiterung (umgekehrt) Datum hinzugefügt Datum hinzugefügt (umgekehrt) Links nach rechts Oben nach unten Unsichtbarer Modus Datum geändert Ups… da ist etwas schief gelaufen Unten nach oben Klicken, um die Protokolldatei der App zu teilen. Dies kann mir helfen, das Problem zu erkennen und zu beheben. Verwende Steganografie, um unsichtbare Wasserzeichen in Bytes deiner Bilder zu erstellen Größe (umgekehrt) Größe Flüssiges Glas Ein Schalter, der auf dem kürzlich angekündigten IOS 26 und seinem Flüssigglas-Designsystem basiert Link einfügen Bild auswählen oder Base64-Daten einfügen/importieren Bildlink zum Starten eingeben Kaleidoskop Sekundärer Winkel Seiten Kanal Mix Blau grün Rot blau Grün rot Ins Rote Ins Grüne Ins Blaue Türkis Purpurrot Gelb Farbe Halbton Kontur Ebenen Versetzt Voronoi kristallisieren Form Dehnen Zufälligkeit Despeckle Diffus DoG Zweiter Radius Ausgleichen Glühen Wirbeln und Zwicken Punktuell Randfarbe Polarkoordinaten Rechteckig zu polar Polar zu rechteckig Im Kreis umkehren Geräuschreduzierung Einfaches Solarisieren Weben X Lücke Y Lücke X Breite Y Breite Wirbeln Gummistempel Verschmieren Dichte Mix Kugel Objektiv Verzerrung Lichtbrechungsindex Bogen Verschmierungswinkel Glitzern Strahlen ASCII Farbverlauf Magma Moire Herbst Knochen Jet Winter Ozean Sommer Frühling Coole Variante HSV Pink Heiß Parula Inferno Plasma Viridis Cividis Dämmerung Verschobene Dämmerung Auto Perspektive Deskew Zuschnitt zulassen Zuschnitt oder Perspektive Absolut Turbo Tiefgrün Linsenkorrektur Ziellinsenprofildatei im JSON-Format fertige Objektivprofile herunterladen Teilprozent Drehen deaktivieren Verhindert das Drehen von Bildern mit Zwei-Finger-Gesten Einrasten an Kanten aktivieren Nach dem Verschieben oder Zoomen werden Bilder so eingerastet, dass sie die Rahmenkanten ausfüllen Als JSON exportieren Zeichenfolge mit Palettendaten als JSON-Darstellung kopieren Hintergrundbilder Export Aktualisieren Rufe die aktuellen Hintergrundbilder für Start, Sperr und Built-in ab Zugriff auf alle Dateien zulassen, dies wird zum Abrufen von Hintergrundbildern benötigt Die Verwaltung externer Speicherberechtigungen reicht nicht aus. Du musst den Zugriff auf deine Bilder zulassen. Wähle unbedingt „Alle zulassen“ aus Nahtschnitzen Startbildschirm Sperrbildschirm Eingebaut Voreinstellung zum Dateinamen hinzufügen Hängt Suffix mit ausgewählter Voreinstellung an den Bilddateinamen an Bildskalierungsmodus zum Dateinamen hinzufügen Hängt Suffix mit ausgewähltem Bildskalierungsmodus an den Bilddateinamen an Ascii Kunst Konvertiere das Bild in ASCII-Text, der wie ein Bild aussieht Wendet in einigen Fällen einen negativen Filter auf das Bild an, um ein besseres Ergebnis zu erzielen Parameter Verarbeite Screenshot Screenshot wurde nicht erfasst, erneut versuchen Speichern übersprungen %1$s Dateien übersprungen Überspringen zulassen, wenn größer Einige Tools erlauben es, das Speichern von Bildern zu überspringen, wenn die resultierende Dateigröße größer als das Original wäre. Kalenderevent Kontakt Email Standort Telefon Text SMS URL WLAN Offenes Netzwerk N/A SSID Telefon Nachricht Adresse Thema Körper Name Organisation Titel Telefone Emails URLs Adressen Zusammenfassung Beschreibung Standort Organisierer Startdatum Enddatum Status Breitengrad Längengrad Barcode erstellen Barcode bearbeiten WLAN Konfiguration Sicherheit Kontakt auswählen Kontakte Berechtigung in den Einstellungn erteilen, um mit ausgewählten Kontakt automatisch Auszufüllen. zweiter Vorname Nachname Betonung Telefon hinzufügen Email hinzufügen Adresse hinzufügen Webseite Webseite hinzufügen Formatierter Name Kontaktinformationen Vorname Dieses Bild wird über dem Barcode platziert. Code-Anpassung Dieses Bild wird als Logo in der Mitte des QR-Codes verwendet Logo Logo-Ausgleich Logo-Größe Logo-Ecken Viertes Auge Fügt dem QR-Code Augensymmetrie hinzu, indem ein viertes Auge an der unteren Ecke hinzugefügt wird. Pixelform Schneefallmodus Aktiviert Rahmen Rahmenform Kugelform Fehlerkorrekturstufe Dunkle Farbe Helle Farbe Hyper OS Im Stil von Xiaomi HyperOS Maskenmuster Dieser Code ist möglicherweise nicht scanbar. Ändern Sie die Darstellungsparameter, damit er mit allen Geräten lesbar ist. Nicht scanbar Die Tools werden wie der App-Launcher auf dem Startbildschirm aussehen, um kompakter zu sein. Launcher Modus Füllt einen Bereich mit ausgewähltem Pinsel und Stil Überlappungsgröße Kantenglättung Farbstreifenbildung Kantenentfernung Kantenglättung, allgemeine Artefakte, CGI Entfernung von Kompressionsartefakten Entfernung von Kompressionsartefakten JPEG-Artefaktentfernung V2 H.264-Texturverbesserung VHS-Schärfung und -Verbesserung Zusammenführung Hochwasserfüllung Spray Zeichnet einen Pfad im Graffiti-Stil Quadratische Teilchen Die Sprühpartikel haben eine quadratische statt kreisförmige Form Palettenwerkzeuge Generieren Sie Basismaterial/Palettenmaterial aus einem Bild oder importieren/exportieren Sie es über verschiedene Palettenformate hinweg Palette bearbeiten Export-/Importpalette für verschiedene Formate Farbname Palettenname Palettenformat Exportieren Sie die generierte Palette in verschiedene Formate Fügt der aktuellen Palette eine neue Farbe hinzu Das Format %1$s unterstützt die Angabe des Palettennamens nicht Aufgrund der Play Store-Richtlinien kann diese Funktion nicht in den aktuellen Build aufgenommen werden. Um auf diese Funktionalität zuzugreifen, laden Sie ImageToolbox bitte von einer alternativen Quelle herunter. Die verfügbaren Builds finden Sie unten auf GitHub. Öffnen Sie die Github-Seite Die Originaldatei wird durch eine neue ersetzt, anstatt sie im ausgewählten Ordner zu speichern Versteckter Wasserzeichentext erkannt Verstecktes Wasserzeichenbild erkannt Dieses Bild wurde ausgeblendet Generatives Inpainting Ermöglicht das Entfernen von Objekten in einem Bild mithilfe eines KI-Modells, ohne auf OpenCV angewiesen zu sein. Um diese Funktion zu nutzen, lädt die App das erforderliche Modell (~200 MB) von GitHub herunter Ermöglicht das Entfernen von Objekten in einem Bild mithilfe eines KI-Modells, ohne auf OpenCV angewiesen zu sein. Dies könnte ein langwieriger Vorgang sein Fehlerebenenanalyse Luminanzgradient Durchschnittliche Entfernung Kopierbewegungserkennung Zurückbehalten Koeffizient Die Daten in der Zwischenablage sind zu groß Die Daten sind zu groß zum Kopieren Einfache Webpixelisierung Gestaffelte Pixelisierung Kreuzpixelisierung Mikro-Makro-Pixelisierung Orbitale Pixelisierung Vortex-Pixelisierung Pulsgitterpixelisierung Kernpixelisierung Radiale Webpixelisierung URI „%1$s“ kann nicht geöffnet werden Glitch-Variante Kanalverschiebung Maximaler Versatz VHS Glitch blockieren Blockgröße CRT-Krümmung Krümmung Chroma Pixelschmelze Max Drop KI-Tools Verschiedene Tools zur Verarbeitung von Bildern durch KI-Modelle wie Artefaktentfernung oder Rauschunterdrückung Komprimierung, gezackte Linien Cartoons, Broadcast-Komprimierung Allgemeine Komprimierung, allgemeines Rauschen Farbloses Cartoon-Geräusch Schnell, allgemeine Komprimierung, allgemeines Rauschen, Animation/Comics/Anime Scannen von Büchern Belichtungskorrektur Am besten bei allgemeiner Komprimierung und Farbbildern Am besten bei allgemeiner Komprimierung und Graustufenbildern Allgemeine Komprimierung, Graustufenbilder, stärker Allgemeines Rauschen, Farbbilder Allgemeines Rauschen, Farbbilder, bessere Details Allgemeines Rauschen, Graustufenbilder Allgemeines Rauschen, Graustufenbilder, stärker Allgemeines Rauschen, Graustufenbilder, am stärksten Allgemeine Komprimierung Allgemeine Komprimierung Texturierung, h264-Komprimierung VHS-Komprimierung Nicht standardmäßige Komprimierung (cinepak, msvideo1, roq) Bink-Komprimierung, bessere Geometrie Bink-Kompression, stärker Bink-Kompression, weich, behält Details bei Gescannte Kunstwerke/Zeichnungen, leichte Komprimierung, Moiré Langsam, Halbtöne entfernen Allgemeiner Kolorierer für Graustufen-/Schwarzweißbilder. Für bessere Ergebnisse verwenden Sie DDColor Entfernt Überschärfung Langsam, zögerlich KDM003 scannt die Verarbeitung Leichtes Bildverbesserungsmodell Verbandentfernung mit reibungslosem Ergebnis Verarbeitung von Halbtonmustern Entfernung von Dither-Mustern V3 Stückgröße Bilder über %1$s px werden in Stücke geschnitten und verarbeitet. Durch Überlappung werden diese zusammengefügt, um sichtbare Nähte zu vermeiden. Große Größen können bei Low-End-Geräten zu Instabilität führen Wählen Sie eine aus, um zu beginnen Möchten Sie das Modell %1$s löschen? Sie müssen es erneut herunterladen Bestätigen Modelle Heruntergeladene Modelle Verfügbare Modelle Vorbereiten Aktives Modell Sitzung konnte nicht geöffnet werden Es können nur .onnx/.ort-Modelle importiert werden Modell importieren Importieren Sie ein benutzerdefiniertes ONNX-Modell zur weiteren Verwendung. Es werden nur ONNX-/Ort-Modelle akzeptiert. Unterstützt fast alle Esrgan-ähnlichen Varianten Importierte Modelle Allgemeines Rauschen, farbige Bilder Allgemeines Rauschen, farbige Bilder, stärker Allgemeines Rauschen, farbige Bilder, am stärksten Reduziert Dithering-Artefakte und Farbstreifen und verbessert so sanfte Farbverläufe und flache Farbbereiche. Verbessert die Bildhelligkeit und den Kontrast mit ausgewogenen Glanzlichtern und behält gleichzeitig natürliche Farben bei. Hellt dunkle Bilder auf, behält dabei aber Details bei und vermeidet Überbelichtung. Entfernt übermäßige Farbtöne und stellt eine neutralere und natürlichere Farbbalance wieder her. Wendet eine Poisson-basierte Rauschtönung an, wobei der Schwerpunkt auf der Erhaltung feiner Details und Texturen liegt. Wendet eine sanfte Poisson-Rauschen-Tönung an, um weichere und weniger aggressive visuelle Ergebnisse zu erzielen. Die gleichmäßige Rauschtönung konzentrierte sich auf die Erhaltung von Details und Bildklarheit. Sanfte, gleichmäßige Rauschtönung für eine subtile Textur und ein glattes Erscheinungsbild. Repariert beschädigte oder unebene Bereiche durch Neulackierung von Artefakten und verbessert die Bildkonsistenz. Leichtes Debanding-Modell, das Farbstreifen mit minimalen Leistungseinbußen entfernt. Optimiert Bilder mit sehr hohen Komprimierungsartefakten (0–20 % Qualität) für verbesserte Klarheit. Verbessert Bilder mit hohen Komprimierungsartefakten (20–40 % Qualität), stellt Details wieder her und reduziert Rauschen. Verbessert Bilder mit mäßiger Komprimierung (40-60 % Qualität) und gleicht Schärfe und Glätte aus. Verfeinert Bilder mit leichter Komprimierung (60–80 % Qualität), um subtile Details und Texturen hervorzuheben. Verbessert nahezu verlustfreie Bilder leicht (80–100 % Qualität) und behält gleichzeitig das natürliche Aussehen und die Details bei. Einfache und schnelle Kolorierung, Cartoons, nicht ideal Reduziert die Bildunschärfe leicht und verbessert die Schärfe, ohne dass Artefakte entstehen. Lang andauernde Vorgänge Bild wird verarbeitet Verarbeitung Entfernt starke JPEG-Komprimierungsartefakte in Bildern mit sehr geringer Qualität (0–20 %). Reduziert starke JPEG-Artefakte in stark komprimierten Bildern (20–40 %). Bereinigt mäßige JPEG-Artefakte unter Beibehaltung der Bilddetails (40–60 %). Verfeinert leichte JPEG-Artefakte in Bildern mit relativ hoher Qualität (60–80 %). Reduziert geringfügige JPEG-Artefakte in nahezu verlustfreien Bildern (80–100 %). Verstärkt feine Details und Texturen und verbessert die wahrgenommene Schärfe ohne starke Artefakte. Verarbeitung abgeschlossen Die Verarbeitung ist fehlgeschlagen Verbessert Hauttexturen und -details und behält gleichzeitig ein natürliches Aussehen bei, optimiert für Geschwindigkeit. Entfernt JPEG-Komprimierungsartefakte und stellt die Bildqualität für komprimierte Fotos wieder her. Reduziert das ISO-Rauschen bei Fotos, die bei schlechten Lichtverhältnissen aufgenommen wurden, und bewahrt so Details. Korrigiert überbelichtete oder „riesige“ Glanzlichter und stellt eine bessere Tonbalance wieder her. Leichtes und schnelles Kolorierungsmodell, das Graustufenbildern natürliche Farben hinzufügt. DEJPEG Denoise Kolorieren Artefakte Erweitern Anime Scannt Gehoben X4-Upscaler für allgemeine Bilder; Winziges Modell, das weniger GPU und Zeit verbraucht, mit mäßiger Entunschärfe und Rauschunterdrückung. X2-Upscaler für allgemeine Bilder unter Beibehaltung von Texturen und natürlichen Details. X4-Upscaler für allgemeine Bilder mit verbesserten Texturen und realistischen Ergebnissen. X4-Upscaler, optimiert für Anime-Bilder; 6 RRDB-Blöcke für schärfere Linien und Details. X4-Upscaler mit MSE-Verlust sorgt für glattere Ergebnisse und weniger Artefakte für allgemeine Bilder. X4 Upscaler optimiert für Anime-Bilder; 4B32F-Variante mit schärferen Details und glatten Linien. X4 UltraSharp V2-Modell für allgemeine Bilder; betont Schärfe und Klarheit. X4 UltraSharp V2 Lite; schneller und kleiner, behält Details bei und verbraucht weniger GPU-Speicher. Leichtes Modell zur schnellen Hintergrundentfernung. Ausgewogene Leistung und Genauigkeit. Arbeitet mit Porträts, Objekten und Szenen. Empfohlen für die meisten Anwendungsfälle. BG entfernen Horizontale Randstärke Vertikale Randstärke %1$s Farbe %1$s Farben Das aktuelle Modell unterstützt kein Chunking. Das Bild wird in den Originalabmessungen verarbeitet. Dies kann zu hohem Speicherverbrauch und Problemen mit Low-End-Geräten führen Chunking ist deaktiviert, das Bild wird in den Originalabmessungen verarbeitet. Dies kann zu einem hohen Speicherverbrauch und Problemen mit Low-End-Geräten führen, kann jedoch zu besseren Ergebnissen bei der Inferenz führen Chunking Hochpräzises Bildsegmentierungsmodell zur Hintergrundentfernung Leichte Version von U2Net für schnellere Hintergrundentfernung bei geringerer Speichernutzung. Das vollständige DDColor-Modell liefert eine hochwertige Kolorierung für allgemeine Bilder mit minimalen Artefakten. Beste Wahl aller Kolorierungsmodelle. DDColor geschulte und private künstlerische Datensätze; Erzeugt vielfältige und künstlerische Kolorierungsergebnisse mit weniger unrealistischen Farbartefakten. Leichtes BiRefNet-Modell basierend auf Swin Transformer für präzise Hintergrundentfernung. Hochwertige Hintergrundentfernung mit scharfen Kanten und hervorragender Detailerhaltung, insbesondere bei komplexen Objekten und schwierigen Hintergründen. Hintergrundentfernungsmodell, das genaue Masken mit glatten Kanten erzeugt, geeignet für allgemeine Objekte und mäßige Detailerhaltung. Modell bereits heruntergeladen Modell erfolgreich importiert Typ Stichwort Sehr schnell Normal Langsam Sehr langsam Berechnen Sie Prozente Der Mindestwert ist %1$s Verzerren Sie das Bild, indem Sie mit den Fingern zeichnen Kette Härte Warp-Modus Bewegen Wachsen Schrumpfen Wirbel im Uhrzeigersinn Gegen den Uhrzeigersinn wirbeln Stärke verblassen Top-Drop Bottom Drop Starten Sie Drop End Drop Herunterladen Glatte Formen Verwenden Sie Superellipsen anstelle der standardmäßigen abgerundeten Rechtecke für glattere, natürlichere Formen Formtyp Schneiden Gerundet Glatt Scharfe Kanten ohne Rundung Klassische abgerundete Ecken Formentyp Eckengröße Squircle Elegante abgerundete UI-Elemente Dateinamenformat Benutzerdefinierter Text wird ganz am Anfang des Dateinamens platziert, ideal für Projektnamen, Marken oder persönliche Tags. Verwendet den ursprünglichen Dateinamen ohne Erweiterung, sodass die Quellenidentifizierung erhalten bleibt. Die Bildbreite in Pixel, nützlich zum Verfolgen von Auflösungsänderungen oder zum Skalieren von Ergebnissen. Die Bildhöhe in Pixel, hilfreich beim Arbeiten mit Seitenverhältnissen oder Exporten. Erzeugt zufällige Ziffern, um eindeutige Dateinamen zu gewährleisten; Fügen Sie weitere Ziffern hinzu, um die Sicherheit vor Duplikaten zu erhöhen. Automatisch inkrementierender Zähler für Batch-Exporte, ideal beim Speichern mehrerer Bilder in einer Sitzung. Fügt den Namen der angewendeten Voreinstellung in den Dateinamen ein, damit Sie sich leicht daran erinnern können, wie das Bild verarbeitet wurde. Zeigt den Bildskalierungsmodus an, der während der Verarbeitung verwendet wird, und hilft dabei, in der Größe veränderte, zugeschnittene oder angepasste Bilder zu unterscheiden. Benutzerdefinierter Text am Ende des Dateinamens, nützlich für die Versionierung, z. B. _v2, _edited oder _final. Die Dateierweiterung (png, jpg, webp usw.) passt automatisch zum tatsächlich gespeicherten Format. Ein anpassbarer Zeitstempel, mit dem Sie Ihr eigenes Format nach Java-Spezifikation für eine perfekte Sortierung definieren können. Fling-Typ Android nativ iOS-Stil Glatte Kurve Schneller Stopp Federnd Schwebend Bissig Ultra glatt Adaptiv Barrierefreiheit bewusst Reduzierte Bewegung Native Android-Scroll-Physik zum Basisvergleich Ausgewogenes, flüssiges Scrollen für den allgemeinen Gebrauch Höheres Reibungsverhalten beim iOS-ähnlichen Scrollverhalten Einzigartige Spline-Kurve für ein ausgeprägtes Scroll-Gefühl Präzises Scrollen mit schnellem Stoppen Verspielte, reaktionsschnelle, federnde Schriftrolle Lange, gleitende Scrolls zum Durchsuchen von Inhalten Schnelles, reaktionsschnelles Scrollen für interaktive Benutzeroberflächen Erstklassiges flüssiges Scrollen mit längerem Schwung Passt die Physik basierend auf der Wurfgeschwindigkeit an Berücksichtigt die Barrierefreiheitseinstellungen des Systems Minimale Bewegung für Zugänglichkeitsanforderungen Primäre Linien Fügt jede fünfte Zeile eine dickere Linie hinzu Füllfarbe Versteckte Werkzeuge Zum Teilen ausgeblendete Tools Farbbibliothek Stöbern Sie in einer riesigen Farbkollektion Schärft und entfernt Unschärfen aus Bildern und behält gleichzeitig natürliche Details bei, ideal zum Korrigieren unscharfer Fotos. Stellt Bilder, deren Größe zuvor geändert wurde, intelligent wieder her und stellt verlorene Details und Texturen wieder her. Optimiert für Live-Action-Inhalte, reduziert Komprimierungsartefakte und verbessert feine Details in Film-/TV-Show-Frames. Konvertiert Filmmaterial in VHS-Qualität in HD, entfernt Bandrauschen und verbessert die Auflösung, während das Vintage-Feeling erhalten bleibt. Spezialisiert auf textlastige Bilder und Screenshots, schärft Zeichen und verbessert die Lesbarkeit. Erweitertes Upscaling, trainiert auf verschiedenen Datensätzen, hervorragend für die allgemeine Fotoverbesserung. Optimiert für webkomprimierte Fotos, entfernt JPEG-Artefakte und stellt das natürliche Erscheinungsbild wieder her. Verbesserte Version für Webfotos mit besserer Texturerhaltung und Artefaktreduzierung. 2-fache Hochskalierung mit Dual Aggregation Transformer-Technologie, behält Schärfe und natürliche Details bei. 3-fache Hochskalierung mit fortschrittlicher Transformatorarchitektur, ideal für moderate Vergrößerungsanforderungen. 4-fache hochwertige Hochskalierung mit modernstem Transformatornetzwerk, bewahrt feine Details in größeren Maßstäben. Entfernt Unschärfe/Rauschen und Verwacklungen aus Fotos. Universell einsetzbar, aber am besten für Fotos geeignet. Stellt Bilder mit geringer Qualität mithilfe des Swin2SR-Transformators wieder her, der für die BSRGAN-Verschlechterung optimiert ist. Ideal zum Beheben starker Komprimierungsartefakte und zur Verbesserung von Details im 4-fachen Maßstab. 4-fache Hochskalierung mit SwinIR-Transformator, trainiert auf BSRGAN-Degradation. Verwendet GAN für schärfere Texturen und natürlichere Details in Fotos und komplexen Szenen. Weg PDF zusammenführen Kombinieren Sie mehrere PDF-Dateien in einem Dokument Dateireihenfolge S. PDF teilen Extrahieren Sie bestimmte Seiten aus einem PDF-Dokument PDF drehen Seitenausrichtung dauerhaft korrigieren Seiten PDF neu anordnen Ziehen Sie Seiten per Drag-and-Drop, um sie neu anzuordnen Halten und ziehen Sie Seiten Seitenzahlen Fügen Sie Ihren Dokumenten automatisch eine Nummerierung hinzu Etikettenformat PDF zu Text (OCR) Extrahieren Sie einfachen Text aus Ihren PDF-Dokumenten Überlagern Sie benutzerdefinierten Text für Branding oder Sicherheit Unterschrift Fügen Sie Ihre elektronische Signatur zu jedem Dokument hinzu Dies wird als Signatur verwendet PDF entsperren Entfernen Sie Passwörter aus Ihren geschützten Dateien PDF schützen Sichern Sie Ihre Dokumente mit starker Verschlüsselung Erfolg PDF entsperrt, Sie können es speichern oder teilen PDF reparieren Versuchen Sie, beschädigte oder unleserliche Dokumente zu reparieren Graustufen Konvertieren Sie alle in Dokumente eingebetteten Bilder in Graustufen PDF komprimieren Optimieren Sie die Dateigröße Ihres Dokuments für eine einfachere Weitergabe ImageToolbox erstellt die interne Querverweistabelle neu und generiert die Dateistruktur von Grund auf neu. Dadurch kann der Zugriff auf viele Dateien wiederhergestellt werden, die „nicht geöffnet werden können“. Dieses Tool konvertiert alle Dokumentbilder in Graustufen. Am besten zum Drucken und Reduzieren der Dateigröße geeignet Metadaten Bearbeiten Sie die Dokumenteigenschaften, um den Datenschutz zu verbessern Schlagworte Produzent Autor Schlüsselwörter Schöpfer Privatsphäre tief reinigen Löschen Sie alle verfügbaren Metadaten für dieses Dokument Seite Tiefe OCR Extrahieren Sie Text aus dem Dokument und speichern Sie ihn mithilfe der Tesseract-Engine in einer Textdatei Es können nicht alle Seiten entfernt werden PDF-Seiten entfernen Entfernen Sie bestimmte Seiten aus dem PDF-Dokument Tippen Sie auf „Entfernen“. Manuell PDF zuschneiden Beschneiden Sie Dokumentseiten auf beliebige Grenzen PDF reduzieren Machen Sie PDFs durch Rastern von Dokumentseiten unveränderbar Die Kamera konnte nicht gestartet werden. Bitte überprüfen Sie die Berechtigungen und stellen Sie sicher, dass sie nicht von einer anderen App verwendet wird. Bilder extrahieren Extrahieren Sie in PDFs eingebettete Bilder in ihrer Originalauflösung Diese PDF-Datei enthält keine eingebetteten Bilder Dieses Tool scannt jede Seite und stellt Quellbilder in voller Qualität wieder her – perfekt zum Speichern von Originalen aus Dokumenten Unterschrift zeichnen Stiftparameter Verwenden Sie Ihre eigene Signatur als Bild, das auf Dokumenten platziert werden soll PDF komprimieren Teilen Sie das Dokument in einem bestimmten Intervall auf und packen Sie neue Dokumente in ein ZIP-Archiv Intervall PDF drucken Bereiten Sie das Dokument zum Drucken mit benutzerdefinierter Seitengröße vor Seiten pro Blatt Orientierung Seitengröße Marge Blühen Weiches Knie Optimiert für Anime und Cartoons. Schnelles Hochskalieren mit verbesserten natürlichen Farben und weniger Artefakten Samsung One UI 7-ähnlicher Stil Geben Sie hier grundlegende mathematische Symbole ein, um den gewünschten Wert zu berechnen (z. B. (5+5)*10). Mathematischer Ausdruck Bis zu %1$s Bilder aufnehmen Datum und Uhrzeit beibehalten Behalten Sie immer Exif-Tags in Bezug auf Datum und Uhrzeit bei, funktioniert unabhängig von der Option „Exif behalten“. Hintergrundfarbe für Alpha-Formate Fügt die Möglichkeit hinzu, die Hintergrundfarbe für jedes Bildformat mit Alpha-Unterstützung festzulegen. Wenn diese Option deaktiviert ist, ist sie nur für Nicht-Alpha-Formate verfügbar Projekt öffnen Bearbeiten Sie weiterhin ein zuvor gespeichertes Image Toolbox-Projekt Das Image Toolbox-Projekt kann nicht geöffnet werden Im Image Toolbox-Projekt fehlen Projektdaten Das Image Toolbox-Projekt ist beschädigt Nicht unterstützte Image Toolbox-Projektversion: %1$d Projekt speichern Speichern Sie Ebenen, Hintergrund und Bearbeitungsverlauf in einer bearbeitbaren Projektdatei Öffnen fehlgeschlagen In durchsuchbares PDF schreiben Erkennen Sie Text aus dem Bildstapel und speichern Sie durchsuchbare PDFs mit Bild und auswählbarer Textebene Ebene Alpha Horizontaler Flip Vertikaler Flip Sperren Schatten hinzufügen Schattenfarbe Textgeometrie Dehnen oder neigen Sie Text für eine schärfere Stilisierung Maßstab X Skew X Anmerkungen entfernen Entfernen Sie ausgewählte Anmerkungstypen wie Links, Kommentare, Hervorhebungen, Formen oder Formularfelder von den PDF-Seiten Hyperlinks Dateianhänge Linien Popups Briefmarken Formen Textnotizen Textmarkierung Formularfelder Markup Unbekannt Anmerkungen Gruppierung aufheben Fügen Sie Unschärfeschatten hinter der Ebene mit konfigurierbaren Farben und Offsets hinzu ================================================ FILE: core/resources/src/main/res/values-es/strings.xml ================================================ Algo salió mal: %1$s Tamaño %1$s Cargando… La imagen es demasiado grande para previsualizarla, pero se intentará guardar de todas formas. Selecciona una imagen para iniciar Busca aquí Anchura %1$s Tipo de redimensión Explícito Flexible Elegir imagen Aplicación cerrándose Quedarse Cerrar Restablecer imagen Los cambios en la imagen se revertirán a los valores iniciales Valores restablecidos correctamente Restablecer Algo salió mal Reiniciar la aplicación Copiado al portapapeles Excepción Editar EXIF OK No se han encontrado datos EXIF Añadir etiqueta Guardar Borrar Borrar EXIF Cancelar Se borrarán todos los datos EXIF de la imagen, ¡esta acción no se puede deshacer! Preajustes Recortar Guardar Todos los cambios no guardados se perderán si sales ahora Código fuente Recibe las últimas actualizaciones, discute acerca de los problemas y más Edición única Modificar, redimensionar y editar una imagen Selector de color Elige el color de la imagen, cópiala o compártela Imagen Color Color copiado Recorta la imagen a cualquier límite Versión Conservar EXIF Imágenes: %d Cambiar vista previa Eliminar Generar una muestra de paleta de colores a partir de una imagen dada Generar Paleta Paleta Actualizar Nueva versión %1$s Tipo no admitido: %1$s No se puede generar la paleta para la imagen dada Original Carpeta de salida Por defecto Personalizado Sin especificar Almacenamiento del dispositivo Redimensionar por Peso Tamaño máximo en KB Redimensionar una imagen siguiendo un tamaño dado en KB Comparar Ajustes Modo nocturno Oscuro Claro Sistema Colores dinámicos Personalización Permitir monet de imagen Si está activado, cuando elijas una imagen para editar, los colores de la aplicación se adaptarán a esta imagen Idioma Modo Amoled Si se activa, el color de las superficies se establecerá como oscuro absoluto en modo nocturno Esquema de colores Rojo Verde Azul El código de color aRGB no es válido. Nada que pegar El esquema de colores de la app no se puede cambiar mientras los colores dinámicos están activados El tema de la aplicación se basará en el color que elijas Acerca de la aplicación No se han encontrado actualizaciones Seguimiento de problemas Envía aquí informes de errores y solicitudes de funciones Ayuda a traducir Corrige errores de traducción o localiza el proyecto a otros idiomas Altura %1$s Calidad Extensión ¿Estás seguro de querer cerrar la aplicación? Compara dos imágenes dadas Elige dos imágenes para empezar Elegir imágenes No se ha encontrado nada con tu consulta Si está habilitado, los colores de la aplicación se adaptarán a los colores del fondo de pantalla Error al guardar %d imagen(es) Primario Terciario Secundario Grosor del borde Superficie Valores Añadir Permiso Otorgar La app necesita acceso a tu almacenamiento para guardar imágenes, es necesario pues sin ello no funciona, así que por favor otorga el permiso en el siguiente diálogo. La aplicación necesita este permiso para funcionar, por favor, otorgalo manualmente Esta aplicación es totalmente gratuita, pero si quieres apoyar el desarrollo del proyecto, puedes hacer clic aquí Alineación FAB Buscar actualizaciones Si está activado, se mostrará un cuadro de diálogo de actualización al iniciar la aplicación Zoom de la imagen Prefijo Nombre de archivo Compartir Almacenamiento externo Colores Monet Redimensiona las imágenes a la altura y anchura dadas. La relación de aspecto puede cambiar. Brillo Contraste Saturación Añadir filtro Aplica la selección de filtros a las imágenes Filtros Luz Exposición Balance de blancos Temperatura Pendiente Negativo Afilar Sepia Sombreado Espaciado espacio de color GCA Suavizado Kuwahara Visualiza cualquier tipo de imagen: GIF, SVG, etc Orden Sin imagen Alfa Tinte En blanco y negro Opacidad Redimensionar por Límites Bosquejo Límite Niveles de cuantificación Toon suave Toon Supresión no máxima Inclusión de píxeles débiles Buscar Emoji Desenfoque de pila Convolución 3x3 filtro RGB Color falso Tamaño de desenfoque Centro de desenfoque x Centro de desenfoque y Desenfoque de zoom Balance de color Umbral de luminancia Agregar tamaño de archivo Si está habilitado, agrega la anchura y altura de la imagen guardada al nombre del archivo resultante Vista previa de la imagen Fuente de la imagen Selector de fotos Galería Explorador de archivos El selector de fotos moderno de Android que aparece en la parte inferior de la pantalla, puede que funcione solo en Android 12+ y además tiene problemas para recibir metadatos EXIF Selector simple de imágenes de la galería, solo funcionará si tienes alguna app que provea selección de fotos Determina el orden de las herramientas en la pantalla principal Si está habilitado, reemplaza la marca de tiempo estándar por el número de secuencia de imágenes si usas el procesamiento por lotes Carga cualquier imagen de Internet, obtén una vista previa, haz zoom y también guárdala o edítala si lo deseas. Enlace de imagen Llenar Adaptar Redimensiona las imágenes que no sean cuadradas a la altura o anchura introducida. Todos los cálculos de tamaño se realizarán después de guardar. Se mantendrá la relación de aspecto. Matiz Filtrar Filtro de color Monocromo Gama Luces y sombras Reflejos Sombras Bruma Efecto Distancia Solarizar Intensidad Ancho de línea Borde sobel Desenfocar Semitono Desenfoque gaussiano Caja de desenfoque Desenfoque bilateral Realzar Laplaciano Viñeta Comenzar Fin Radio Escala Distorsión Ángulo Remolino Bulto Dilatación Refracción de esfera Índice de refracción Refracción de esfera de vidrio Matriz de color Redimensionar imágenes a una altura y anchura dadas manteniendo la relación de aspecto. Posterizar Selecciona qué emoji se mostrará en la pantalla principal Primer color Segundo color Reordenar Desenfoque rápido Eliminar EXIF Eliminar metadatos EXIF de cualquier par de imágenes Cargar imagen en línea Acomodo de opciones Editar Escala de contenido Número de emojis número de secuencia Nombre de archivo original Agregar el nombre del archivo original Si está habilitado, agrega el nombre del archivo original en el nombre de la imagen resultante Agregar el nombre del archivo original no funciona si se seleccionó la fuente de imagen del selector de fotos Usa la intención GetContent para elegir una imagen, funciona en todas partes, pero también puede tener problemas para recibir imágenes seleccionadas en algunos dispositivos, eso no es mi culpa. Reemplazar número de secuencia Color de pintura Color de fondo Encriptar y desencriptar archivos (no solo imágenes) en base a diferentes algoritmos de encriptación disponibles. Escoger archivo Encriptar Escoge el archivo para empezar Descripción Cifrado Clave Compatibilidad Tamaño del archivo Dibujar en imagen Desencriptar Contraseña incorrecta o el archivo escogido no está encriptado Intentar guardar la imagen con esas dimensiones puede provocar un error de falta de memoria. Hazlo bajo tu responsabilidad. Caché Tamaño del caché Limpieza del caché automática Herramientas Implementación Escoge una imagen y dibuja algo en ella Guarda este archivo en tu dispositivo o usa la acción de compartir para colocarlo donde desees Características El tamaño máximo del archivo está restringido por el SO Android y la memoria disponible, lo cual obviamente dependerá de tu dispositivo. \nImportante: memoria no es almacenamiento. Editar captura de pantalla Dibujar Opciones de grupo por tipo Opciones de grupos en la pantalla principal de su tipo en lugar de disposición de lista personalizada Crear Personalización secundaria Captura de pantalla Opción alternativa Dibujar en el fondo Desactivó la aplicación Archivos, actívela para usar esta función Dibujar en la imagen como en un cuaderno de bocetos, o dibujar en el fondo mismo Tenga en cuenta que no se garantiza la compatibilidad con otros servicios o software de cifrado de archivos. Un tratamiento de claves o una configuración de cifrado ligeramente diferentes pueden ser motivos de incompatibilidad. Si la caché de la aplicación está habilitada, se borrará al iniciar la aplicación Copiar Guardar en modo %1$s puede ser inestable, porque es un formato sin pérdidas Encontrado %1$s Elija el color de fondo y dibuje encima Cifrado de archivos basado en contraseña. Los archivos procedidos pueden almacenarse en el directorio seleccionado o compartirse. Los archivos descifrados también se pueden abrir directamente. "AES-256, modo GCM, sin relleno, vector de Inicialización (IV) aleatorio de 12 bytes por defecto. Se puede seleccionar el algoritmo necesario. Las claves se utilizan como hashes SHA-3 de 256 bits." No se puede cambiar el arreglo mientras la agrupación de opciones está habilitada Saltar Pintura alfa Cifrar Archivo procesado Esto restablecerá la configuración a los valores predeterminados. Ten en cuenta que esto no se puede deshacer sin un archivo de respaldo mencionado anteriormente. Actualizaciones Ups… Algo salió mal, escríbeme utilizando las opciones debajo e intentaré encontrar una solución Chat de Telegram Recortar máscara Naturaleza y Animales Número de colores máximos Actividades Espera Actualmente el formato %1$s en android solamente permite leer el EXIF y no cambiarlo/guardarlo, lo que significa que la imagen resultante no tendrá metadatos en absoluto. Comida y Bebida Suavidad de la brocha Contáctame Un valor de %1$s significa comprimir rápidamente, resultando en un peso de archivo relativamente grande. %2$s significa tomar más tiempo comprimiendo, resultando en un peso de archivo más pequeño. Respalda tus configuraciones en la app a un archivo Archivo corrompido o no es un respaldo Si has seleccionado el preajuste 125, la imagen se guardará al 125% del tamaño de la imagen original. Si eliges el preajuste 50, entonces la imagen se guardará al 50% del tamaño original Permitir la recolección de estadísticas anónimas de uso de la app Tamaño de fuente Permitir betas Dibujar Flechas Modo de dibujo Restaurar Los espacios transparentes alrededor de la imagen serán recortados Habla sobre la aplicación y recibe comentarios de otros usuarios. Además, allí puedes conseguir actualizaciones en beta e información. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Ññ Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz 0123456789 !? Borrar fondo Restaurar imagen Si está habilitado, la dirección de dibujo será representada como una flecha apuntando Borrar Relación de aspecto Radio de desenfoque Eliminador de fondos Ajustar imagen Remueve el fondo de una imagen mediante el pincel o utiliza la opción automática Estás a punto de borrar el esquema de color seleccionado, esta acción no puede deshacerse Guardado en el folder %1$s Configuraciones restauradas con éxito Respaldar y restaurar Respaldar Remover fondo de manera automática Emociones Esto permite a la aplicación recolectar reportes de fallo automáticamente Guardado casi completado, si cancelas ahora entonces tendrás que empezar a guardar de nuevo. Crear Reporte de Problema Pipeta Guardado en el folder %1$s con nombre %2$s Símbolos Analíticos Usa este tipo de máscara para crear una máscara a partir de una imagen dada. Tenga en cuenta que DEBERÁ tener canal alfa Habilitar emoji Esfuerzo Reajustar Tamaño y Convertir Objetos Aleatorizar nombre del archivo Texto Predeterminado Borrar Esquema Restaurar fondo Fuente Las imágenes serán recortadas centralmente a este tamaño si es un tamaño más grande que las dimensiones establecidas, y el canvas será expandido con el color de fondo dado en otros casos. Cambia el tamaño de imágenes dadas o conviértelas a otros formatos, además puedes editar los metadatos EXIF si seleccionas una sola imagen. Usar tamaños de fuente grandes puede causar errores en la IU que no serán arreglados, úsalos solamente si lo deseas. Si se activa, el nombre del archivo resultante será completamente aleatorio Donación Se conservarán los metadatos de la imagen original Restaura las configuraciones de la app desde un archivo previamente generado El ajuste predeterminado aquí define el % de tamaño del archivo resultante. Es decir, seleccionando \"50\" para una imagen de 5 MB se guardará una imagen de 2.5 MB. Modo de borrado La búsqueda de actualizaciones incluirá versiones beta de la app si está habilitado Viajes y Lugares Correo electrónico Fallo mejorado Cambio de canal X Cambio de canal Y Tamaño de la corrupción Cambio de corrupción X Orientación & Solo detección de secuencias de comandos Orientación automática & Detección de secuencias de comandos Sólo automático Bloque único Línea sola Falla Cantidad Semilla Barajar Estás a punto de eliminar la máscara de filtro seleccionada. Esta operación no se puede deshacer Drago Aldridge Cortar Uchimura Móbius No se encontró el directorio \"%1$s\", lo cambiamos al predeterminado, guarde el archivo nuevamente Portapapeles Fijación automática Vibración Para sobrescribir archivos, necesita usar la fuente de imagen \"Explorer\", intente volver a seleccionar imágenes, hemos cambiado la fuente de imagen a la necesaria Sobrescribir archivos El archivo original se reemplazará por uno nuevo en lugar de guardarlo en la carpeta seleccionada. Esta opción debe tener como fuente de imagen \"Explorer\" o GetContent; al alternar esto, se configurará automáticamente Vacío Sufijo Buscar Permite buscar entre todas las herramientas disponibles en la pantalla principal. Gratis Imágenes sobrescritas en el destino original Bordes borrosos Dibuja bordes borrosos debajo de la imagen original para llenar los espacios a su alrededor en lugar de un solo color si está habilitado Pixelación Pixelación mejorada Pixelación de trazo Pixelación de diamantes Color para reemplazar Color objetivo Color para quitar Quitar color Recodificar Ensalada de frutas Fidelidad Contenido Estilo de paleta predeterminado, permite personalizar los cuatro colores, otros le permiten configurar solo el color clave Un estilo ligeramente más cromático que monocromático Un tema ruidoso, el colorido es máximo para la paleta principal, aumentado para otras Un tema divertido: el tono del color de origen no aparece en el tema Un tema monocromático, los colores son puramente negro/blanco/gris Un esquema que coloca el color de origen en Scheme.primaryContainer Un esquema que es muy similar al esquema de contenido Desactivado Vista previa de PDF PDF a imágenes Imágenes a PDF Vista previa sencilla de PDF Convertir PDF a imágenes en un formato de salida determinado Máscaras Agregar máscara Máscara %d Vista previa de máscara La máscara de filtro dibujada se representará para mostrarle el resultado aproximado Eliminar máscara Variantes simples Resaltador Neón Bolígrafo Desenfoque de privacidad Añade un efecto brillante a tus dibujos El predeterminado, el más simple: solo el color Similar al desenfoque de privacidad, pero pixelado en lugar de desenfocado Botones Dibuja una sombra detrás de los controles deslizantes Dibuja una sombra detrás de los interruptores Dibuja una sombra detrás de los botones de acción flotantes Dibuja una sombra detrás de los botones Barras de aplicaciones Dibuja una sombra detrás de las barras de la aplicación Valor en el rango %1$s - %2$s Dibuja una flecha de doble punta desde el punto inicial hasta el punto final como una línea Dibuja una flecha de doble punta desde un camino determinado. Rectángulo delineado Oval Recto Dibuja un óvalo delineado desde el punto inicial hasta el punto final Dibuja el rectángulo delineado desde el punto inicial hasta el punto final Agrega automáticamente la imagen guardada al portapapeles si está habilitado Fuerza de vibración Más cercano Ranura Método para interpolar y remuestrear suavemente un conjunto de puntos de control, comúnmente utilizado en gráficos por computadora para crear curvas suaves La función de ventana se aplica a menudo en el procesamiento de señales para minimizar la fuga espectral y mejorar la precisión del análisis de frecuencia al reducir los bordes de una señal Técnica de interpolación matemática que utiliza los valores y derivadas en los puntos finales de un segmento de curva para generar una curva suave y continua Método de remuestreo que mantiene una interpolación de alta calidad aplicando una función de sincronización ponderada a los valores de los píxeles Método de remuestreo que utiliza un filtro de convolución con parámetros ajustables para lograr un equilibrio entre nitidez y suavizado en la imagen escalada Usa funciones polinomiales definidas por partes para interpolar y aproximar suavemente una curva o superficie, proporcionando una representación de forma flexible y continua Reconoce texto de una imagen determinada, admite más de 120 idiomas La imagen no tiene texto o la aplicación no la encontró Accuracy: %1$s Tipo de reconocimiento Modo de segmentación Archivo sobrescrito con nombre %1$s en el destino original Habilita la lupa en la parte superior del dedo en los modos de dibujo para una mejor accesibilidad Forzar valor inicial Obliga a que el widget exif se compruebe inicialmente Auto Una sola columna Texto vertical de un solo bloque Una sola palabra Palabra circular Carácter único Texto escaso Orientación de texto disperso & Detección de secuencias de comandos Línea cruda ¿Desea eliminar los datos de entrenamiento de OCR de idioma \"%1$s\" para todos los tipos de reconocimiento o solo para uno seleccionado (%2$s)? Propiedades Utiliza la cámara para tomar fotografías; tenga en cuenta que solo es posible obtener una imagen de esta fuente de imágenes Repetir marca de agua Repite la marca de agua sobre la imagen en lugar de una sola en una posición determinada Desplazamiento X Compensación Y Tipo de marca de agua Esta imagen se utilizará como patrón para la marca de agua Color de texto Retraso de fotograma milis FPS Oculta el contenido al salir, además la pantalla no se puede capturar ni grabar Bayer tres por tres tramado Tramado de Sierra Lite Atkinson vacilante Stucki vacilante Falso Floyd Steinberg vacilante Tramado de izquierda a derecha Tramado aleatorio Tramado de umbral simple Utiliza funciones polinómicas bicúbicas definidas por partes para interpolar y aproximar suavemente una curva o superficie, representación de forma flexible y continua Cambio de corrupción Y Desenfoque de tienda Desvanecimiento lateral Lado Arriba Abajo Fortaleza Color del trazo Amplitud Turbulencia Efecto agua Tamaño Frecuencia Y Efectos simples Polaroid Tritanomalía Deuteranomalía Protanomalía Antiguo Brownie Coda Cromo Visión nocturna Cálido Fresco Tritanopía Deuteranopía Protanopía Acromatomalia Acromatopsia Pastel Neblina naranja Tonos de Otoño Sueño de lavanda Ciberpunk Fuego espectral Magia Nocturna Paisaje de fantasía Explosión de color Gradiente eléctrico Sol verde Mundo arcoíris Morado oscuro Portal espacial Remolino rojo Forma del icono Transición Cima Anomalía de color No se puede cambiar el formato de imagen mientras la opción de sobrescribir archivos está habilitada Emoji como combinación de colores Utiliza el color primario de emoji como combinación de colores de la aplicación en lugar de uno definido manualmente Vertical Orden de imágenes Pixelación de diamantes mejorada Erosionar Difusión anisotrópica Difusión Conducción Escalonamiento del viento horizontal Vidrio fractal Mármol Aceite Frecuencia X Amplitud X Amplitud Y Distorsión Perlin Mapeo de tonos fílmicos Hable Mapeo de tonos de Hejl Burgess Mapeo de tonos fílmicos de ACES Mapeo de tonos de colinas de ACES Desenfoque bilateral rápido Desenfoque venenoso Mapeo de tonos logarítmicos Cristalizar Actual Todo Filtro completo Comenzar Centro Fin Aplique cualquier cadena de filtros a imágenes determinadas o a una sola imagen Herramientas PDF Opere con archivos PDF: vista previa, convierta a un lote de imágenes o cree una a partir de imágenes determinadas Empaquetar las imágenes dadas en un archivo PDF de salida Creador de degradados Cree un degradado de un tamaño de salida determinado con colores y tipos de apariencia personalizados Velocidad Desempañar Omega Calificar aplicacion Tasa Esta aplicación es completamente gratuita, si quieres que crezca, estrella el proyecto en Github 😄 Matriz de colores 4x4 Matriz de colores 3x3 Lineal Radial Barrer Tipo de gradiente Centro X Centro Y Modo mosaico Repetido Espejo Abrazadera Calcomanía Paradas de color Agregar color Flecha de doble línea Modo dibujar ruta Dibujo Gratis Doble flecha Flecha de línea Flecha Línea Dibuja la ruta como valor de entrada Dibuja la ruta desde el punto inicial hasta el punto final como una línea Dibuja una flecha que apunta desde el punto inicial al punto final como una línea Dibuja una flecha que apunta desde una ruta determinada. Ovalado delineado Dibuja un rectángulo desde el punto inicial hasta el punto final Dibuja un óvalo desde el punto inicial hasta el punto final Tramado Cuantificador Escala de grises Bayer dos por dos tramado Bayer cuatro por cuatro tramado Bayer ocho por ocho tramado Floyd Steinberg vacilante Jarvis Judice Ninke vacilante Sierra vacilante Tramado Sierra de dos filas Burkes vacilante Color de máscara Modo de escala Bilineal Hann Ermita Lanczos Mitchell Básico Valor por defecto Sigma Sigma espacial Desenfoque mediano Catmull Bicúbico La interpolación lineal (o bilineal, en dos dimensiones) suele ser buena para cambiar el tamaño de una imagen, pero causa cierto suavizado no deseado de los detalles y aún puede ser algo irregular Los mejores métodos de escalado incluyen el remuestreo de Lanczos y los filtros Mitchell-Netravali Una de las formas más sencillas de aumentar el tamaño, sustituyendo cada píxel por varios píxeles del mismo color El modo de escalado de Android más simple que se usa en casi todas las aplicaciones Sólo vídeo No se guardará en el almacenamiento y se intentará colocar la imagen únicamente en el portapapeles Agrega un contenedor con la forma seleccionada debajo de los íconos principales de las tarjetas Unión de imágenes Combina las imágenes dadas para obtener una grande Aplicación del brillo Pantalla Gradiente de superposición Componga cualquier degradado en la parte superior de una imagen determinada Transformaciones Cámara Grano Desenfocar Sueño rosa Hora dorada Verano caluroso Niebla Púrpura Amanecer Remolino colorido Luz suave de primavera Luz de limonada Oscuridad del caramelo Degradado futurista Código digital Marca de agua Cubra imágenes con marcas de agua de texto/imagen personalizables Modo de superposición Tamaño de píxel Bloquear la orientación del dibujo Si está habilitado en el modo de dibujo, la pantalla no rotará Bokeh Herramientas GIF Convierta imágenes a imágenes GIF o extraiga fotogramas de una imagen GIF determinada GIF a imágenes Convertir un archivo GIF en un lote de imágenes Convertir lotes de imágenes a archivos GIF Imágenes a GIF Elija una imagen GIF para comenzar Usar el tamaño del primer fotograma Reemplace el tamaño especificado con las dimensiones del primer marco Repetir recuento Usar lazo Utiliza Lasso como en el modo de dibujo para realizar el borrado Vista previa de imagen original Alfa Filtro de máscara Aplique cadenas de filtros en áreas enmascaradas determinadas, cada área de máscara puede determinar su propio conjunto de filtros El emoji de la barra de aplicaciones se cambiará continuamente de forma aleatoria en lugar de usar uno seleccionado Emojis aleatorios No se puede utilizar la selección aleatoria de emojis mientras los emojis estén desactivados No puedo seleccionar un emoji mientras se selecciona uno aleatorio habilitado Buscar actualizaciones Televisor viejo Desenfoque aleatorio OCR (reconocer texto) Rápido Estándar Mejor Sin datos Para que Tesseract OCR funcione correctamente, es necesario descargar datos de entrenamiento adicionales (%1$s) en su dispositivo. \n¿Desea descargar %2$s datos? Descargar No hay conexión, compruébalo y vuelve a intentarlo para descargar los modelos de trenes Idiomas descargados Idiomas Disponibles El pincel restaurará el fondo en lugar de borrarlo Cuadrícula horizontal Cuadrícula vertical Modo de puntada Recuento de filas Recuento de columnas Usar interruptor de píxeles Utiliza un interruptor similar al de Google Pixel Deslizar Lado a lado Alternar toque Transparencia Lupa Permitir varios idiomas Favorito Aún no se han agregado filtros favoritos Estría B Desenfoque de pila nativo Cambio de inclinación Tipo de relleno inverso Si está habilitado, todas las áreas no enmascaradas se filtrarán en lugar del comportamiento predeterminado Confeti Se mostrará confeti al guardar, compartir y otras acciones principales Modo seguro Elige al menos 2 imágenes Escala de imagen de salida Orientación de la imagen Horizontal Escalar imágenes pequeñas a grandes Las imágenes pequeñas se escalarán a la más grande de la secuencia si está habilitada Regular Pixelación de círculos Pixelación de círculos mejorada Reemplazar color Tolerancia Auto rotar Permite adoptar un cuadro de límite para la orientación de la imagen Estilo de paleta Mancha tonal Neutral Vibrante Expresivo Arcoíris Dibuja trazados de resaltado afilados y semitransparentes Difumina la imagen debajo del camino dibujado para asegurar todo lo que quieras ocultar Contenedores Dibuja una sombra detrás de los contenedores Controles Deslizantes Interruptores FAB Este verificador de actualizaciones se conectará a GitHub para verificar si hay una nueva actualización disponible Atención Bordes desvanecidos Ambos Colores invertidos Reemplaza los colores del tema por negativos si está habilitado Salir Si sales de la vista previa ahora, tendrás que agregar las imágenes nuevamente Lazo Dibuja un camino cerrado y lleno por un camino dado Formato de imagen Ruido Ordenar píxeles Anáglifo Crea la paleta \"Material You\" a partir de la imagen Colores Oscuros Utiliza el esquema de color del modo nocturno en lugar de la variante de luz Copiar como código \"Jetpack Compose\" Desenfoque de anillo Desenfoque cruzado Desenfoque de círculo Desenfoque de estrella Cambio de inclinación lineal Etiquetas Para Eliminar Imágenes a APNG Desenfoque de movimiento Herramientas APNG Convierta imágenes a imágenes APNG o extraiga fotogramas de una imagen APNG determinada APNG a imágenes Convierta un archivo APNG en un lote de imágenes Convertir lotes de imágenes a archivos APNG Elija la imagen APNG para comenzar Comprimir Cree un archivo Zip a partir de archivos o imágenes determinados Ancho del mango de arrastre Festivo Lluvia Esquinas Herramientas JXL Realizar transcodificaciones JXL ~ JPEG sin pérdidas de calidad, o convertir animaciones GIF/APNG a JXL JXL a JPEG Realizar transcodificaciones sin pérdida de JXL a JPEG Realizar transcodificaciones sin pérdida de JPEG a JXL JPEG a JXL Elegir la imagen JXL para empezar Convertir a formato Ajustar a márgenes Idiomas importados con éxito Hacer copia de seguridad de los modelos OCR Exportar Position Centro Superior izquierda Centro izquierda Complementario Sombreado de color Tonos Tonos Mezcla de color Información de color Color seleccionado Color para mezclar Celuloide Café Verdoso Tipo confeti Sin acceso completo a los archivos Añadir marca de tiempo Esquema daltónico Elige un filtro para usarlo como pintura Reemplazar filtro Intentar de nuevo Añadir nueva carpeta Bits por muestra Muestras por píxel Fecha y hora Descripción de imagen Versión EXIF Artista Derecho de autor Espacio de color Gamma Comentario de usuario Tiempo de exposición Valor de velocidad de obturación Valor de apertura Valor de brillo Valor de apertura máximo Flash Distancia focal Archivo de origen Renderizado personalizado Modo de exposición Balance de blancos Modelo de lente Especificaciónd e lente Altitud GPS Estado GPS Número de serie de lente Latitud GPS Longitud GPS ISO Usados recientemente Canal CI Grupo ImageToolbox en Telegram 🎉 Tamaño de fuente Tamaño de marca de agua Opciones personalizadas Repetir texto Escanear documento Haz clic para empezar a escanear Empezar a escanear Cúbico Máscara Introducir porcentaje Elegir Ajustes de pantalla completa Compose Elegir múltiples archivos multimedia Elegir un único archivo multimedia Triángulo Polígono Dibujar polígono regular Estrella Píxel Vista previa de enlaces Enlaces Descartar para siempre Lineal Hoy Organización de herramientas Agrupar herramientas por tipo Valores predeterminados Ocultar todos Alineación Configuración de canales Ayer Convertir lotes de imágenes a un formato dado Contraste Saturación Vértices Guardar como PDF Compartir como PDF Nombre de plantilla Filtro de plantilla Como imagen de código QR Como archivo Guardar como archivo Guardar como imagen de código QR Eliminar plantilla Vista previa de filtro Código QR Descripción QR No utilices el esquema daltónico Importar Superior derecha Inferior izquierda Inferior derecha Centro superior Centro derecha Centro inferior HDR Añadir favoritos Cuadrado 512x512 2D LUT LUT Kodak Únete a nuestra conversación donde charlamos de todo lo que quieras y también échale un vistazo al canal CI donde publico versiones betas y comunicados Recibe notificaciones sobre nuevas versiones de la aplicación, y leer los comunicados Mostrar todos Ocultar barra de navegación Ocultar barra de estado Frecuencia Nombre de archivo personalizado Elige la ubicación y nombre de archivo que se usarán para guardar la imagen actual Guardar en una carpeta con un nombre personalizado Añadir imagen Convertir 3D LUT GIF a JXL APNG a JXL JXL a Imágenes Imágenes a JXL Generar vistas previas Compresión sin pérdidas Ordenar Ordenar por fecha Ordenar por fecha (Inversa) Ordenar por nombre Nombre (Inverso) Restablecer propiedades Detallado GIF WEBP Herramientas WEBP WEBP a imágenes Convertir lote de imágenes a archivo WEBP Imágenes a WEBP Plantilla No se han añadido filtros de plantilla Crear nuevo El código QR escaneado no es una plantilla de filtro válida Escanear código QR Crear plantilla Arte pop Permite añadir una marca de tiempo al nombre del archivo de salida Tamaño de recorte predeterminado Longitud de previsualización de imagen Marco de aspecto Borde inferior de sensor Borde izquierdo de sensor Borde derecho de sensor Borde superior de sensor El texto actual se repetirá hasta el final de la ruta en lugar de una única vez Tamaño de guion Escanear documentos y crear PDF or separar las imágenes de ellos La interpolación cúbica proporciona un escalado más suave teniendo en cuenta los 16 píxeles más cercanos, otrogando mejores resultados que la interpolación bilinear Un método que utiliza una función cuadrática para la interpolación, proporcionando unos resultados más fluidos y continuos Triángulo delineado Dibuja un triángulo delineado desde el punto inicial al punto final Dibuja un triángulo delineado desde el punto inicial al punto final Polígono delineado Dibuja una estrella desde el punto inicial al punto final Dibuja una estrella delineada desde el punto inicial al punto final Las siguientes opciones son para guardar imágenes, no PDF Permitir Intro en el campo de texto El archivo seleccionado no tiene datos de plantilla de filtro Se utilizará esta imagen para la vista previo de esta plantilla de filtro Escanear código QR y obtén su contenido o pega tu cadena de texto para generar uno nuevo Mín. Conceder el permiso de cámara desde ajustes para escanear el código QR Un método de interpolación que aplica una función gaussiana, útil para la fluidez y reducción de ruido en las imágenes Un método de interpolación de alta calidad optimizado para la redimensión natural de la imagen, con un equilibrio entre nitidez y suavidad Activa el antialiasing para evitar los bordes nítidos No se han seleccionado opciones favoritas, añádelas en la página de herramientas Utilizar la imagen seleccionada para dibujarla a lo largo de una ruta determinada Dibuja un polígono desde el punto inicial al punto final Dubija un polígono delineado desde el punto inicial al punto final Dibujar polígono que sea regular en lugar de forma libre Estrella delineada Proporción de radio interior Dibujar estrella regular Dibujar estrella que sea regular en lugar de forma libre Escala de espacio de color Tamaño de parrilla en eje X Tamaño de parrilla en eje Y Recortar a contenido Color de marco Color a ignorar Abrir Edición en lugar de Vista previa Estás a punto de eliminar la plantilla de filtro seleccionada. Esta operación no se puede deshacer Añadir plantilla de filtro con nombre \"%1$s\" (%2$s) Escanear código QR para reemplazar el contenido del campo, o escribe algo para generar un nuevo código QR Compresión Unidad de resolución Resolución X Resolución Y Función de transferencia Anchura de línea predeterminada Mostrar ajustes en vista horizontal Selector integrado Selector de imágenes de Image Toolbox Sin permisos Solicitar Pegado automático Permite que la aplicación pegue automáticamente los datos del portapapeles, para que aparezcan en la pantalla principal y puedas procesarlos Convertir imágenes GIF a fotografías animadas JXL Convertir imágenes APNG a fotografías animadas JXL Convertir animación JXL a un lote de fotografías Convertir lote de fotografías a animación JXL El selector de archivos aparecerá inmediatamente, si es posible, en la pantalla elegida Omitir selección de archivos Tipo de compresión Imágenes a SVG Trazar imágenes dadas a imágenes SVG Omitir ruta Reducir imagen Se reducirán las dimensiones de la imagen antes de procesarla, para permitir que la herramienta funcione de forma más rápida y segura Relación de color mínima Umbral de líneas Umbral cuadrático Se restablecerán los valores predeterminados para todas las propiedades. Ten en cuenta que esta acción no se puede deshacer Un filtro de remuestreo diseñado por Haasn para un escalado de imagen suave y sin artefactos Aceite mejorado Televisor viejo simple Boceto simple Complemento de división Herramientas de color Mezclar, hacer tonos, generar tonos y más Armonías de color Variación Imagen LUT objetivo Elegancia suave Colores de otoño Noche con niebla Obtener imagen LUT neutral Amarillo retro Imagen objetivo Transferencia de paleta Modo de borde Desenfoque linear de caja Dificultad para distinguir entre tonos rojos y verdes Dificultad para distinguir entre tonos verdes y rojos Ubicación de un solo guardado Índice de exposición Modo del motor Modo de medición Rango de distancia de sujeto Convertir lote de imágenes de un formato a otro Apilamiento de imágenes Posicionamiento Y Cb Cr Hacer Sensibilidad espectral Longitud del formato de intercambio JPEG Permite la generación de vistas previas, lo que puede ayudar a evitar bloqueos en algunos dispositivos; sin embargo, esto también desactiva algunas funcionalidades de edición dentro de la opción de edición única. Relación de zoom digital Usa compresión con pérdida para reducir el tamaño del archivo en lugar de comprensión sin pérdida. Modo de medición GPS Distancia de sujeto Un interruptor basado en el sistema de diseño \"Fluent\" Bits comprimidos por pixel Respuesta de frecuencia espacial Tamaño de Espacio Armonización de Color Paleta de cuantización será muestreada si está opción es activada Sensibilidad fotográfica Desenfoque Gaussiano Rápido 2D Modo predeterminado de ruta de dibujado Patrón CFA Selecciona imagen WEBP para comenzar Apilar imágenes una encima de otra con modos de fusión seleccionados Formato de intercambio JPEG Hora y fecha original División de imagen Dividir imagen única en filas y columnas Un interruptor basado en el sistema de diseño de \"Cupertino\" Si se desactiva, en modo horizontal los ajustes serán abiertos en el botón en la barra de aplicaciones de arriba como siempre, en lugar de la opción permanentemente visible. Velocidad de ISO Control de ganancia Máximo Ancla de redimensionamiento Tipo de captura de escena Sombrero negro Muestra barras del sistema al deslizar Aplica algunas variables de entrada para motor tesseract Daltonismo completo, viendo solo tonos de gris Los colores serán exactos a los establecidos en el tema Un filtro de remuestreo Lanczos con un orden más alto de 6, resultante en un escalado de imagen más preciso y nítido Una variante del filtro Lanczos 6 usando una función Jinc para una mejor calidad de remuestreo de imagen Desenfoque apilado linear Desenfoque Gaussiano linear rápido Elige filtro de abajo para usarlos como pincel en tu dibujo Tercer color Tintes Variante de elegancia suave Permitir permiso de cámara en ajustes para escanear Escaneador de documentos Convertir imágenes GIF a imágenes animadas WEBP Color de dibujado predeterminado Ver y editar ubicaciones de un solo guardado que puedes usar al mantener presionado el botón de guardado en la mayoría de las opciones Agrupar herramientas en la pantalla principal por su tipo en lugar de una lista de personalizada de orden Visibilidad de barras del sistema Generación de ruido Ganancia Fuerza medida Función de distancia Tipo de retorno Crea collages de hasta 20 imágenes Tipo de collage Mantiene presionada la imagen para intercambiar, mover y hacer zoom para ajustar posición Histograma de RGB o brillo de imagen para ayudarte a hacer ajustes Recorte automático Gradiente morfológica Explotar Desenfoque Gaussiano Rápido 3D Desenfoque Gaussiano Rápido 4D Armonización de Nivel Método de remuestreo que mantiene una interpolación de alta calidad al aplicar una función de Bessel (jinc) a los valores de los pixeles Al activar la página de ajustes será siempre abierta en pantalla completa en lugar de un panel deslizante. Tipo de interruptor Un interruptor basado en Material You Usa paleta de muestreo Tolerancia en el redondeo de las coordenadas Legado Red de LSTM Legado y LSTM Interpretación fotométrica Configuración planar Submuestreo Y Cb Cr Archivo de audio relacionado Tipo de sensibilidad Índice de exposiciones recomendadas Área de sujeto Energía de flash Resolución de plano focal X Resolución de plano focal Y Método de detección Plano focal en película de 35mm Nitidez ID único de imagen Nombre del propietario de cámara Velocidad de GPS Generar diferentes tipos de ruidos como Perlin u otros tipos Tipo de ruido Tipo de rotación Unidad de resolución de plano focal Esquema de comprensión TIFF Activa la recuperación de vista previa de enlaces en lugares donde puedes obtener texto (QRCode, OCR, etc) Esta imagen será utilizada para generar histogramas de RGB y brillo Curvas de tono Restablecer curvas Número serial de cuerpo Un filtro de remuestreo diseñado para procesamiento de imagen de alta calidad con un buen balance de nitidez y suavidad Selecciona modo para adaptar colores de tema según la variante de daltonismo dada Sensibilidad reducida a todos los colores Envolver Dificultad para distinguir entre tonos azules y amarillos Incapacidad para percibir tonos rojos Incapacidad para percibir tonos azules Brillo suave Pintura de arena Combina modo de redimensionamiento de recorte con este parámetro para obtener el comportamiento deseado(Recortar/Ajustar a la relación de aspecto) Luz de vela Habilita deslizar para mostrar barras de sistema si están ocultas Tipo de fractal Histograma Esquinas libres Las opciones deben ser ingresadas siguiendo este patrón: \"--{option_name} {valor}\" Los puntos no estarán limitados por los límites de imagen, es útil para correcciones más precisas de perspectivas Recorta imagen por polígono, esto también corrige la perspectiva Relleno según contenido bajo dibujado de ruta Estampado Abriendo Estilo de línea Variante de transferencia de paleta Punto Blanco Modelo Comportamiento Incapacidad para percibir tonos verdes Controla la velocidad de decodificación de la imagen resultante, esto debería ayudar a abrir la imagen resultante más rápido, un valor de %1$s significa en la decodificación mas lenta, mientras %2$s es la más rápida, esta configuración puede aumentar el tamaño de la imagen de salida. Primero, usa tu aplicación de edición de imágenes favorita para aplicar un filtro al LUT neutral el cual puedes obtener aquí. Para que esto funcione cada color de pixel no debe depender de otros pixeles (por ejemplo, el desenfoque no funcionará). Una vez listo, usa tu nueva imagen LUT como entrada para tu filtro LUT 512*512. Escala de ruta Desenfoque Gaussiano linear Ubicación de sujeto El uso de esta herramienta para trazar imágenes grandes sin reducir su escala no es recomendable, puede causar bloqueos y aumentar tiempo de procesamiento. Ajustar una imagen a las dimensiones dadas y aplicar desenfoque o colorear fondo Fluido Descripción de ajustes de dispositivo Las curvas serán reestablecidas al valor predeterminado Cuando seleccionas una imagen para abrir (vista previa) en ImageToolbox, la hoja de selección de edición se abrirá en vez de mostrar la vista previa Escanea cualquier código de barras (QR, EAN, AZTEC, …) y obtén su contenido o pega tu texto para generar uno nuevo Una variante del filtro Lanczos 2 que usa la función jinc, proveyendo interpolación de alta calidad con distorsiones mínimas Un interruptor Jetpack Compose Material You Lanczos Bessel Cupertino Liquid Glass Un interruptor basado en el recientemente anunciado IOS 26 y su sistema de diseño \"liquid glass\" Selecciona una imagen o pega/importa datos Base64 debajo Pegar link Caleidoscopio Escribe en enlace de la imagen para comenzar Ángulo secundario Lados Abajo a Arriba Arriba a Abajo Derecha a Izquierda Izquierda a Derecha Tamaño Tamaño (Inverso) Tipo MIME Tipo MIME (Inverso) Extensión Extensión (Inverso) Diseño Fecha de Adición Fecha de Adición (Inverso) Azul Verdoso Rojo Azul Verde Rojo En rojo Cian Magenta Amarillo Color Semitono Contorno Niveles Forma Estirar Aleatoriedad Destramar Difuminar Software Nota del creador Segundo radio Igualar Girar y Pellizcar Puntillizar Color del borde Coordenadas Polares Rect a Polar Polar a Rect Reducir Ruido Solarizar Simple Ancho X Ancho Y Molinete Emborronar Densidad Mix Distorsión de Lentes en esfera Índice de refracción Ángulo de dispersión Rayos ASCII Gradiente Oops… Algo salió mal Me puedes contactar usando las opciones de debajo e intentaré encontrar una solución.\n(No te olvides de adjuntar logs/registros) Escribir a archivo Contraseña Desbloquear El PDF está protegido Operación casi completada. Si cancelas ahora, será necesario reiniciarla Fecha de Modificación Fecha de Modificación (Inversa) Utiliza funciones polinómicas definidas por tramos para interpolar suavemente y aproximar una curva o superficie, lo que permite una representación flexible y continua de la forma Una función de ventana usada para reducir el manchado espectral mediante el estrechamiento de los bordes de una señal, útil en procesamiento de señales Una variante de la ventana de Hann, usada comúnmente para reducir el manchado espectral en aplicaciones de procesamiento de señales Una función ventana que provee buena resolución de frecuencia buena al minimizar el manchado espectral, utilizada a menudo en procesamiento de señales Una función ventana diseñada para dar buena resolución de frecuencia con un manchado espectral reducido, generalmente usada en aplicaciones de procesamiento de señales Un método avanzado de remuestreo que ofrece una interpolación de alta calidad con artefactos mínimos Una función triangular usada en procesamiento de señales para reducir el manchado espectral Una variante más nítida del método Robidoux, optimizada para el redimensionamiento de imágenes nítidas. Un método de interpolación basado en splines que proporciona resultados suaves utilizando un filtro 16-tap Un método de interpolación basado en splines que proporciona resultados suaves utilizando un filtro 36-tap Un método de interpolación basado en splines que proporciona resultados suaves utilizando un filtro 64-tap Un método de interpolación que utiliza la ventana (función) de Kaiser, proporcionando un buen control sobre la compensación entre la anchura del lóbulo principal y el nivel del lóbulo lateral Una función de ventana híbrida que combina las ventanas de Barlett y Hann, utilizada para reducir el manchado espectral en procesamiento de señales Un método simple de remuestreo que usa el promedio de los valores de los píxeles más cercanos, a menudo resulta en una apariencia pixelada Habilita las marcas de tiempo para seleccionar su formato Convierte un archivo WEBP a lotes de imágenes Bosque Dorado Convierte imágenes a imágenes WEBP animadas o extrae frames de un archivo WEBP animado dado Los archivos ICO sólo pueden ser guardados con tamaño máximo de 256 x 256 Permite acceso a todos los archivos para ver JXL, QOI y otras imágenes que no son reconocidas como imágenes en Android. Sin los permisos, Image Toolbox no podrá mostrar esas imágenes Marca de tiempo con formato Habilita el formateo de marcas de tiempo (Timestamps) en el nombre del archivo de salida, en lugar de milisegundos básicos Auto Octavas Lacunaridad Jitter Creador de Collage Opciones de Tesseract Warp de Dominio Limitar Puntos a Bordes de Imagen Cerrando Zigzag Dibuja una línea discontinua a lo largo de la ruta trazada, con el tamaño de separación especificado Rayado Punteado Dibuja una línea discontinua con puntos y rayas, a lo largo de una trayectoria dada Las líneas rectas por defecto Dibuja las formas seleccionadas a lo largo de la ruta, con un espaciado especificado Dibuja un zigzag ondulado a lo largo del recorrido Ratio del Zigzag Crear Atajo Elige que herramienta fijar La herramienta será añadida a la pantalla de inicio de tu launcher como un atajo. Utilízala junto con la configuración \"Omitir selección de archivos\" para conseguir el comportamiento deseado No apilar frames Habilita deshacerse de frames previos, de modo que no se amontonen unos sobre otros Crossfade Los frames se fundirán entre sí Recuento de frames fundidos/sobrepuestos Umbral Uno Umbral Dos Canny Espejo 101 Desenfoque de Zoom Mejorado Laplaciano Simple Sobel Simple Cuadrícula de Ayuda Muestra una cuadrícula de apoyo sobre el área de dibujo para facilitar manipulaciones precisas Color de la Cuadrícula Ancho de Celda Altura de Celda Selectores Compactos Algunos controles de selección usarán un diseño compacto para tomar menos espacio Conceda permiso a la cámara en ajustes para capturar la imagen Título de la Pantalla Principal Constant Rate Factor (CRF) Un valor de %1$s significa una compresión lenta, resultando en un tamaño de archivo relativamente pequeño. %2$s significa una compresión más rápida, resultando en un archivo grande/más pesado. Biblioteca LUT Descarga una colección de LUTs, que puedes aplicar tras la descarga Actualizar colección de LUTs (sólo los nuevos serán puestos en la cola), los cuales puedes aplicar tras la descarga Cambia la imagen de vista previa por defecto para filtros Imagen de Vista Previa Ocultar Mostrar Tipo de Slider Fancy Material 2 Un slider con aspecto elegante. Esta es la opción predeterminada Un slider Material 2 Un slider Material You Aplicar Centrar Botones de Diálogo Los botones de los cuadros de diálogo serán posicionados en el centro, en lugar de a la izquierda, si es posible Licencias Open Source Ver licencias de librerías open source usadas en esta app Área Remuestreo utilizando la relación del área de píxeles. Puede ser un método preferido para la reducción de imágenes, ya que ofrece resultados sin moiré. Sin embargo, cuando se amplía la imagen, es similar al método \"Más cercano\" Habilitar Mapeo de Tonos Introduzca % No se puede acceder al sitio, intenta usar una VPN o comprueba si la url es correcta Capas de Marcado Modo de capas con la habilidad de colocar libremente imágenes, texto y más Editar capa Capas en Imagen Usa una imagen como fondo y añade diferentes capas sobre ella Capas en el fondo Lo mismo que la primera opción pero con color en lugar de una imagen Beta Acceso a Configuración Lateral Añade una barra flotante en el lado seleccionado mientras editas imágenes, la cual abrirá los ajustes rápidos al clickear en ella Borrar selección Checksum como nombre de archivo El nombre de las imágenes de salida se corresponderá al checksum de datos de la imagen Herramientas de Checksum Compara checksums, calcular hashes o crear cadenas hexadecimales de archivos, usando diferentes algoritmos de hashing Checksum Elige el archivo para calcular su checksum basado en el algoritmo seleccionado Introduce un texto para calcular su checksum, basado en el algoritmo seleccionado Checksum de Origen Checksum a Comparar Los checksums son iguales, es seguro Los Checksums no son iguales, el archivo puede no ser seguro! Selecciona el/los archivo(s) para calcular su checksum basado en el algoritmo seleccionado Desplazamientos de tiras Filas por tira Recuentos de bytes de eliminación Cromaticidades primarias Coeficientes Y Cb Cr Referencia Negro Blanco Versión Flashpix Dimensión del píxel X Dimensión Y del píxel Fecha Hora Digitalizada Tiempo de compensación Hora de compensación original Hora de compensación digitalizada Tiempo subseg. Tiempo subseg. Original Hora subseg. digitalizada Número F Programa de exposición Oecf Sensibilidad de salida estándar Velocidad ISO Latitud yyy Velocidad ISO Latitud zzz Valor de sesgo de exposición Marca de lente ID de versión de GPS Referencia de latitud GPS Referencia de longitud GPS Referencia de altitud GPS Marca de tiempo GPS Satélites GPS GPS DOP Referencia de velocidad GPS Referencia de seguimiento GPS Seguimiento GPS Referencia de dirección de imagen GPS Dirección de imagen GPS Dato del mapa GPS GPS Dest Latitud Ref Latitud del destino del GPS GPS Dest Longitud Ref Longitud del destino del GPS Referencia de rumbo de destino GPS Rumbo de destino GPS Referencia de distancia de destino GPS Distancia de destino GPS Método de procesamiento GPS Información del área GPS Sello de fecha GPS Diferencial GPS Error de posicionamiento GPS H Índice de interoperabilidad Versión DNG Vista previa de imagen Inicio Dibuja texto en el camino con la fuente y el color dados Esta imagen se utilizará como entrada repetitiva del camino dibujado. Antialias Ecualizar histograma HSV Ecualizar histograma Habilita el campo de texto detrás de la selección de ajustes preestablecidos, para ingresarlos sobre la marcha Ecualizar pixelación de histograma Ecualizar histograma adaptativo Ecualizar histograma LUV adaptativo Ecualizar histograma adaptativo LAB CLAHE LABORATORIO CLAHE CLAHE LUV Contenido del código B-Spline hammam hanning hombre negro galés cuadrico gaussiano Esfinge bartlett Robidoux Robidoux afilado Línea 16 Estría 36 Estría 64 Emperador Bartlett-Él Caja bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Una función de ventana utilizada para reducir la fuga espectral, proporcionando una buena resolución de frecuencia en aplicaciones de procesamiento de señales. Un método de remuestreo que utiliza un filtro Lanczos de 2 lóbulos para una interpolación de alta calidad con artefactos mínimos Un método de remuestreo que utiliza un filtro Lanczos de 3 lóbulos para una interpolación de alta calidad con artefactos mínimos Un método de remuestreo que utiliza un filtro Lanczos de 4 lóbulos para una interpolación de alta calidad con artefactos mínimos Una variante del filtro Lanczos 3 que utiliza la función jinc, lo que proporciona una interpolación de alta calidad con artefactos mínimos. Una variante del filtro Lanczos 4 que utiliza la función jinc, lo que proporciona una interpolación de alta calidad con artefactos mínimos. Hanning EWA Variante de media ponderada elíptica (EWA) del filtro Hanning para una interpolación y un remuestreo suaves Robidoux EWA Variante de media ponderada elíptica (EWA) del filtro Robidoux para remuestreo de alta calidad Eva negra Variante de promedio ponderado elíptico (EWA) del filtro Blackman para minimizar los artefactos de timbre EWA cuádrico Variante de promedio ponderado elíptico (EWA) del filtro cuádrico para una interpolación suave Robidoux Sharp EWA Variante de promedio ponderado elíptico (EWA) del filtro Robidoux Sharp para obtener resultados más nítidos Lanczos 3 Jinc EWA Variante de media ponderada elíptica (EWA) del filtro Lanczos 3 Jinc para remuestreo de alta calidad con alias reducido Ginseng Ginseng EWA Variante de media ponderada elíptica (EWA) del filtro Ginseng para mejorar la calidad de imagen Lanczos Sharp EWA Variante de promedio ponderado elíptico (EWA) del filtro Lanczos Sharp para lograr resultados nítidos con artefactos mínimos Lanczos 4 EWA más nítidos Variante de promedio ponderado elíptico (EWA) del filtro Lanczos 4 Sharpest para un remuestreo de imágenes extremadamente nítido Lanczos Suave EWA Variante de media ponderada elíptica (EWA) del filtro Lanczos Soft para un remuestreo de imágenes más fluido Haasn suave Recuento de contenedores Clahe HSL Clahe HSV Ecualizar histograma HSL adaptativo Ecualizar histograma HSV adaptativo Acortar sigmoideo Lagrange 2 Un filtro de interpolación de Lagrange de orden 2, adecuado para escalado de imágenes de alta calidad con transiciones suaves Lagrange 3 Un filtro de interpolación de Lagrange de orden 3, que ofrece mayor precisión y resultados más fluidos para el escalado de imágenes. Lanczos 6 Lanczos 6 Jinc Desenfoque de tienda lineal Desenfoque de caja gaussiana lineal Desenfoque de caja gaussiano Desenfoque gaussiano rápido lineal Siguiente Poli baja ciudad gótica Color Póster Tritono Clahe Oklab Clara Olch Clahe Jzazbz Lunares Tramado 2x2 agrupado Tramado 4x4 agrupado Tramado agrupado de 8x8 Yililoma vacilante Análogo triádico Tetrádica Análogo + Complementario No se puede usar Monet mientras los colores dinámicos están activados un aficionado Señorita etiqueta Archivo LUT 3D de destino (.cube / .CUBE) Bypass de blanqueador soltar blues Ámbar nervioso Material de película 50 Fuerza del ping pong Desactivar rotación Evita la rotación de imágenes con gestos con dos dedos. Habilitar el ajuste a los bordes Después de moverlas o hacer zoom, las imágenes se ajustarán para llenar los bordes del marco. Punto de curación Usar núcleo circular Sombrero de copa El grupo de configuración \"%1$s\" se contraerá de forma predeterminada El grupo de configuración \"%1$s\" se expandirá de forma predeterminada Herramientas Base64 Decodificar cadena Base64 en imagen o codificar imagen en formato Base64 Base64 El valor proporcionado no es una cadena Base64 válida No se puede copiar una cadena Base64 vacía o no válida Pegar Base64 Copiar Base64 Cargue la imagen para copiar o guardar la cadena Base64. Si tiene la cadena en sí, puede pegarla arriba para obtener la imagen. Guardar Base64 Compartir Base64 Opciones Comportamiento Importar Base64 Acciones Base64 Agregar esquema Agregar contorno alrededor del texto con color y ancho especificados Color del contorno Tamaño del contorno Rotación Software gratuito (socio) Más software útil en el canal de socios de aplicaciones de Android Algoritmo Calcular Hash de texto ¡Fósforo! Diferencia Degradados de malla Mire la colección en línea de degradados de malla Sólo se pueden importar fuentes TTF y OTF Importar fuente (TTF/OTF) Exportar fuentes Fuentes importadas Error al guardar el intento, intente cambiar la carpeta de salida El nombre del archivo no está configurado Ninguno Páginas personalizadas Selección de páginas Confirmación de salida de herramienta Si tiene cambios no guardados mientras usa herramientas particulares e intenta cerrarlas, se mostrará el cuadro de diálogo de confirmación. Editar EXIF Cambiar metadatos de una sola imagen sin recomprimir Toque para editar las etiquetas disponibles Cambiar pegatina Ancho de ajuste Altura de ajuste Comparación por lotes Seleccionar archivos Seleccionar directorio Escala de longitud de la cabeza Estampilla Marca de tiempo Patrón de formato Relleno Corte de imagen Corte la parte de la imagen y combine las de la izquierda (puede ser al revés) mediante líneas verticales u horizontales. Línea de pivote vertical Línea de pivote horizontal Selección inversa La parte cortada vertical se dejará, en lugar de fusionar partes alrededor del área cortada. La parte cortada horizontal se dejará, en lugar de fusionar partes alrededor del área cortada. Colección de degradados de malla Cree un degradado de malla con una cantidad personalizada de nudos y resolución Superposición de degradado de malla Componer gradiente de malla de la parte superior de las imágenes dadas Personalización de puntos Tamaño de cuadrícula Resolución X Resolución Y Resolución Píxel por píxel Color de resaltado Tipo de comparación de píxeles escanear código de barras Relación de altura Tipo de código de barras Aplicar B/N La imagen del código de barras será completamente en blanco y negro y no estará coloreada por el tema de la aplicación. No se encontró ningún código de barras El código de barras generado estará aquí Cubiertas de audio Extraiga imágenes de portadas de álbumes de archivos de audio; se admiten los formatos más comunes Elige audio para comenzar Elige audio No se encontraron portadas Enviar registros Haga clic para compartir el archivo de registros de la aplicación. Esto puede ayudarme a detectar el problema y solucionarlo. Extraiga texto de un lote de imágenes y guárdelo en un archivo de texto Escribir en metadatos Extraer texto de cada imagen y añadirlo a sus datos EXIF Modo invisible Utilice la esteganografía para crear marcas de agua invisibles a los ojos dentro de bytes de sus imágenes Usar LSB Se utilizará el método de esteganografía LSB (bit menos significativo); en caso contrario, FD (dominio de frecuencia). Eliminación automática de ojos rojos Mezcla de canales En verde En azul Compensar Voronoi cristalizar Perro Brillo invertir en círculo Tejer X brecha Brecha Y Sello de goma Arco Brillar María Otoño Hueso Chorro Invierno Océano Verano Primavera Variante genial VHS Rosa Caliente Palabra Magma Infierno Plasma Viridis Ciudadanos Crepúsculo Crepúsculo cambiado Perspectiva automática alinear Permitir recorte Cultivo o perspectiva Absoluto Turbo verde intenso Corrección de lentes Archivo de perfil de lente de destino en formato JSON Descargar perfiles de lentes listos Porcentajes parciales Exportar como JSON Copie la cadena con datos de una paleta como representación json Tallado de costura Pantalla de inicio Pantalla de bloqueo Incorporado Exportación de fondos de pantalla Refrescar Obtenga fondos de pantalla actuales de Inicio, Bloqueo e Integrados Permitir el acceso a todos los archivos, esto es necesario para recuperar fondos de pantalla Administrar el permiso de almacenamiento externo no es suficiente, debe permitir el acceso a sus imágenes, asegúrese de seleccionar \"Permitir todo\" Agregar preset al nombre de archivo Agrega el sufijo con el ajuste preestablecido seleccionado al nombre del archivo de imagen Agregar modo de escala de imagen al nombre de archivo Agrega el sufijo con el modo de escala de imagen seleccionado al nombre del archivo de imagen Arte Ascii Convierta una imagen a texto ascii que se verá como una imagen parámetros Aplica un filtro negativo a la imagen para obtener mejores resultados en algunos casos. Procesando captura de pantalla Captura de pantalla no capturada, inténtalo de nuevo Guardado omitido %1$s archivos omitidos Permitir omitir si es más grande Algunas herramientas podrán omitir el guardado de imágenes si el tamaño del archivo resultante fuera mayor que el original. Evento del calendario Contacto Correo electrónico Ubicación Teléfono Texto SMS URL Wifi Red abierta N / A SSID Teléfono Mensaje DIRECCIÓN Sujeto Cuerpo Nombre Organización Título Teléfonos Correos electrónicos URL Direcciones Resumen Descripción Ubicación Organizador Fecha de inicio Fecha de finalización Estado Latitud Longitud Crear código de barras Editar código de barras configuración wifi Seguridad Escoger contacto Otorgar permiso a los contactos en la configuración para que se completen automáticamente usando el contacto seleccionado Información de contacto Nombre de pila Segundo nombre Apellido Pronunciación Agregar teléfono Agregar correo electrónico Agregar dirección Sitio web Agregar sitio web Nombre formateado Esta imagen se utilizará para colocar encima del código de barras. Personalización del código Esta imagen se utilizará como logotipo en el centro del código QR. Logo Acolchado con logotipo Tamaño del logotipo Esquinas del logotipo Cuarto ojo Agrega simetría de ojo al código qr agregando un cuarto ojo en la esquina inferior Forma de píxel Forma del marco forma de bola Nivel de corrección de errores color oscuro color claro Hipersistema operativo Xiaomi HyperOS le gusta el estilo Patrón de máscara Es posible que este código no se pueda escanear. Cambie los parámetros de apariencia para que sea legible en todos los dispositivos. No escaneable Las herramientas se parecerán al iniciador de aplicaciones de la pantalla de inicio y serán más compactos Modo lanzador Rellena un área con el pincel y el estilo seleccionados Relleno de inundación Pulverización Dibuja un camino con estilo graffiti. Partículas cuadradas Las partículas de pulverización tendrán forma cuadrada en lugar de círculos. Herramientas de paleta Genere su paleta básica/material a partir de una imagen, o importe/exporte a través de diferentes formatos de paleta Editar paleta Exportar/importar paleta en varios formatos Nombre del color Nombre de la paleta Formato de paleta Exportar paleta generada a diferentes formatos. Agrega un nuevo color a la paleta actual El formato %1$s no admite proporcionar el nombre de la paleta Debido a las políticas de Play Store, esta función no se puede incluir en la versión actual. Para acceder a esta funcionalidad, descargue ImageToolbox desde una fuente alternativa. Puede encontrar las compilaciones disponibles en GitHub a continuación. Abrir página de Github El archivo original será reemplazado por uno nuevo en lugar de guardarse en la carpeta seleccionada. Texto de marca de agua oculto detectado Imagen de marca de agua oculta detectada Esta imagen estaba oculta Pintura generativa Le permite eliminar objetos en una imagen utilizando un modelo de IA, sin depender de OpenCV. Para usar esta función, la aplicación descargará el modelo requerido (~200 MB) de GitHub Le permite eliminar objetos en una imagen utilizando un modelo de IA, sin depender de OpenCV. Esta podría ser una operación de larga duración. Análisis de nivel de error gradiente de luminancia Distancia promedio Detección de movimiento de copia Retener coeficiente Los datos del portapapeles son demasiado grandes Los datos son demasiado grandes para copiarlos. Pixelización de tejido simple Pixelización escalonada Pixelización cruzada Micropixelización macro Pixelización orbital Pixelización de vórtice Pixelización de cuadrícula de pulso Pixelización del núcleo Pixelización de tejido radial No se puede abrir la uri \"%1$s\" Modo nevadas Activado Marco de borde Variante de falla Cambio de canal Compensación máxima VHS Bloqueo de fallo Tamaño del bloque Curvatura CRT Curvatura croma Derretimiento de píxeles Caída máxima Herramientas de IA Varias herramientas para procesar imágenes a través de modelos de inteligencia artificial, como eliminación de artefactos o eliminación de ruido Compresión, líneas irregulares. Dibujos animados, compresión de transmisión. Compresión general, ruido general. Ruido de dibujos animados incoloro Rápido, compresión general, ruido general, animación/cómics/anime Escaneo de libros Corrección de exposición Lo mejor en compresión general e imágenes en color. Lo mejor en compresión general, imágenes en escala de grises Compresión general, imágenes en escala de grises, más fuertes. Ruido general, imágenes en color. Ruido general, imágenes en color, mejores detalles. Ruido general, imágenes en escala de grises. Ruido general, imágenes en escala de grises, más fuertes. Ruido general, imágenes en escala de grises, más fuertes. Compresión general Compresión general Texturización, compresión h264. compresión VHS Compresión no estándar (cinepak, msvideo1, roq) Compresión bink, mejor en geometría Compresión Bink, más fuerte. Compresión Bink, suave, conserva los detalles. Eliminando el efecto escalón, alisando Arte/dibujos escaneados, compresión suave, muaré bandas de color Lento, eliminando medios tonos. Colorizador general para imágenes en escala de grises/blanco y negro; para obtener mejores resultados utilice DDColor Eliminación de bordes Elimina el exceso de nitidez Lento, vacilante Antialiasing, artefactos generales, CGI Procesamiento de escaneos KDM003 Modelo ligero de mejora de imagen. Eliminación de artefactos de compresión Eliminación de artefactos de compresión Retiro del vendaje con resultados suaves. Procesamiento de patrones de semitonos Eliminación de patrones de tramado V3 Eliminación de artefactos JPEG V2 Mejora de textura H.264 Mejora y nitidez de VHS Fusionando Tamaño del fragmento Tamaño de superposición Las imágenes de más de %1$s px se cortarán y procesarán en trozos; la superposición las mezcla para evitar uniones visibles. Los tamaños grandes pueden causar inestabilidad con dispositivos de gama baja Seleccione uno para comenzar ¿Quieres eliminar el modelo %1$s? Tendrás que descargarlo nuevamente. Confirmar Modelos Modelos descargados Modelos disponibles Preparante modelo activo No se pudo abrir la sesión Sólo se pueden importar modelos .onnx/.ort Importar modelo Importe el modelo onnx personalizado para su uso posterior, solo se aceptan modelos onnx/ort, admite casi todas las variantes similares a esrgan Modelos importados Ruido general, imágenes en color. Ruido general, imágenes en color, más fuertes. Ruido general, imágenes en color, más fuerte. Reduce los artefactos de tramado y las bandas de color, mejorando los degradados suaves y las áreas de color planas. Mejora el brillo y el contraste de la imagen con reflejos equilibrados y al mismo tiempo conserva los colores naturales. Ilumina las imágenes oscuras manteniendo los detalles y evitando la sobreexposición. Elimina el tono excesivo del color y restaura un equilibrio de color más neutro y natural. Aplica tonificación de ruido basada en Poisson con énfasis en preservar texturas y detalles finos. Aplica una tonificación suave del ruido Poisson para obtener resultados visuales más suaves y menos agresivos. Tono de ruido uniforme centrado en la preservación de los detalles y la claridad de la imagen. Tono de ruido suave y uniforme para una textura sutil y una apariencia suave. Repara áreas dañadas o irregulares repintando artefactos y mejorando la consistencia de la imagen. Modelo de eliminación de bandas liviano que elimina las bandas de color con un costo de rendimiento mínimo. Optimiza imágenes con artefactos de compresión muy alta (calidad del 0 al 20 %) para mejorar la claridad. Mejora las imágenes con artefactos de alta compresión (20-40% de calidad), restaurando detalles y reduciendo el ruido. Mejora las imágenes con una compresión moderada (40-60% de calidad), equilibrando la nitidez y la suavidad. Refina las imágenes con una ligera compresión (60-80 % de calidad) para mejorar los detalles y texturas sutiles. Mejora ligeramente las imágenes casi sin pérdidas (80-100 % de calidad) y al mismo tiempo conserva el aspecto y los detalles naturales. Colorización simple y rápida, dibujos animados, no es ideal. Reduce ligeramente el desenfoque de la imagen, mejorando la nitidez sin introducir artefactos. Operaciones de larga duración Procesando imagen Tratamiento Elimina fuertes artefactos de compresión JPEG en imágenes de muy baja calidad (0-20%). Reduce fuertes artefactos JPEG en imágenes muy comprimidas (20-40%). Limpia artefactos JPEG moderados conservando los detalles de la imagen (40-60%). Refina los artefactos JPEG ligeros en imágenes de calidad bastante alta (60-80%). Reduce sutilmente pequeños artefactos JPEG en imágenes casi sin pérdidas (80-100%). Mejora los detalles finos y las texturas, mejorando la nitidez percibida sin artefactos pesados. Procesamiento terminado Error de procesamiento Mejora las texturas y los detalles de la piel mientras mantiene una apariencia natural, optimizada para la velocidad. Elimina los artefactos de compresión JPEG y restaura la calidad de la imagen de las fotografías comprimidas. Reduce el ruido ISO en fotografías tomadas en condiciones de poca luz, preservando los detalles. Corrige las luces sobreexpuestas o “jumbo” y restaura un mejor equilibrio tonal. Modelo de coloración ligero y rápido que agrega colores naturales a imágenes en escala de grises. DEJPEG eliminar ruido colorear Artefactos Mejorar animado Escaneos Exclusivo escalador X4 para imágenes generales; Modelo pequeño que utiliza menos GPU y tiempo, con desenfoque y ruido moderados. Escalador X2 para imágenes generales, preservando texturas y detalles naturales. Escalador X4 para imágenes generales con texturas mejoradas y resultados realistas. Escalador X4 optimizado para imágenes de anime; 6 bloques RRDB para líneas y detalles más nítidos. El escalador X4 con pérdida de MSE produce resultados más fluidos y artefactos reducidos para imágenes generales. X4 Upscaler optimizado para imágenes de anime; Variante 4B32F con detalles más nítidos y líneas suaves. modelo X4 UltraSharp V2 para imágenes generales; enfatiza la nitidez y la claridad. X4 UltraSharp V2 Lite; Más rápido y más pequeño, conserva los detalles mientras utiliza menos memoria GPU. Modelo liviano para una rápida eliminación del fondo. Rendimiento y precisión equilibrados. Trabaja con retratos, objetos y escenas. Recomendado para la mayoría de los casos de uso. Eliminar glucemia Grosor del borde horizontal Grosor del borde vertical %1$s color %1$s colores El modelo actual no admite fragmentación, la imagen se procesará en las dimensiones originales, lo que puede causar un alto consumo de memoria y problemas con dispositivos de gama baja. La fragmentación está deshabilitada, la imagen se procesará en las dimensiones originales, esto puede causar un alto consumo de memoria y problemas con dispositivos de gama baja, pero puede dar mejores resultados en la inferencia. fragmentación Modelo de segmentación de imágenes de alta precisión para eliminar el fondo Versión liviana de U2Net para una eliminación de fondo más rápida con un menor uso de memoria. El modelo DDColor completo ofrece coloración de alta calidad para imágenes generales con artefactos mínimos. La mejor elección de todos los modelos de coloración. DDColor Conjuntos de datos artísticos privados y capacitados; produce resultados de coloración diversos y artísticos con menos artefactos de color poco realistas. Modelo ligero BiRefNet basado en Swin Transformer para una eliminación precisa del fondo. Eliminación de fondos de alta calidad con bordes nítidos y excelente conservación de detalles, especialmente en objetos complejos y fondos complicados. Modelo de eliminación de fondo que produce máscaras precisas con bordes suaves, adecuado para objetos generales y preservación moderada de detalles. Modelo ya descargado Modelo importado exitosamente Tipo Palabra clave muy rápido Normal Lento muy lento Calcular porcentajes El valor mínimo es %1$s Distorsionar la imagen dibujando con los dedos. Urdimbre Dureza Modo de deformación Mover Crecer Encoger Remolino CW Remolino CCW Fuerza de desvanecimiento Caída superior Caída inferior Iniciar caída Caída final Descargando Formas suaves Utilice superelipses en lugar de rectángulos redondeados estándar para obtener formas más suaves y naturales Tipo de forma Cortar Redondeado Liso Bordes afilados sin redondear Esquinas redondeadas clásicas Tipo de formas Tamaño de las esquinas ardilla Elementos de interfaz de usuario redondeados y elegantes Formato de nombre de archivo Texto personalizado colocado al principio del nombre del archivo, perfecto para nombres de proyectos, marcas o etiquetas personales. Utiliza el nombre del archivo original sin extensión, lo que le ayuda a mantener intacta la identificación de la fuente. El ancho de la imagen en píxeles, útil para rastrear cambios de resolución o escalar resultados. La altura de la imagen en píxeles, útil cuando se trabaja con relaciones de aspecto o exportaciones. Genera dígitos aleatorios para garantizar nombres de archivos únicos; agregue más dígitos para mayor seguridad contra duplicados. Contador de incremento automático para exportaciones por lotes, ideal para guardar varias imágenes en una sesión. Inserta el nombre preestablecido aplicado en el nombre del archivo para que pueda recordar fácilmente cómo se procesó la imagen. Muestra el modo de escala de imagen utilizado durante el procesamiento, lo que ayuda a distinguir imágenes redimensionadas, recortadas o ajustadas. Texto personalizado colocado al final del nombre del archivo, útil para versiones como _v2, _edited o _final. La extensión del archivo (png, jpg, webp, etc.), que coincide automáticamente con el formato guardado real. Una marca de tiempo personalizable que le permite definir su propio formato según la especificación de Java para una clasificación perfecta. Tipo de aventura Nativo de Android Estilo iOS Curva suave Parada rápida Activo flotante Rápido Ultrasuave Adaptado Accesibilidad consciente Movimiento reducido Física de desplazamiento nativa de Android para comparación de referencia Desplazamiento equilibrado y fluido para uso general Comportamiento de desplazamiento similar al de iOS de mayor fricción Curva spline única para una sensación de desplazamiento distinta Desplazamiento preciso con parada rápida Desplazamiento lúdico y responsivo Desplazamientos largos y deslizantes para explorar contenidos Desplazamiento rápido y responsivo para interfaces de usuario interactivas Desplazamiento suave premium con impulso extendido Ajusta la física según la velocidad de lanzamiento. Respeta la configuración de accesibilidad del sistema Movimiento mínimo para necesidades de accesibilidad. Líneas primarias Agrega una línea más gruesa cada quinta línea Color de relleno Herramientas ocultas Herramientas ocultas para compartir Biblioteca de colores Explora una amplia colección de colores Mejora la nitidez y elimina las imágenes borrosas manteniendo los detalles naturales, ideal para corregir fotografías desenfocadas. Restaura de forma inteligente imágenes cuyo tamaño ha sido redimensionado previamente, recuperando detalles y texturas perdidas. Optimizado para contenido de acción en vivo, reduce los artefactos de compresión y mejora los detalles finos en fotogramas de películas o programas de televisión. Convierte metraje con calidad VHS a HD, eliminando el ruido de la cinta y mejorando la resolución al mismo tiempo que conserva la sensación vintage. Especializado para imágenes y capturas de pantalla con mucho texto, afina los caracteres y mejora la legibilidad. Ampliación avanzada entrenada en diversos conjuntos de datos, excelente para mejorar fotografías de uso general. Optimizado para fotografías comprimidas web, elimina artefactos JPEG y restaura la apariencia natural. Versión mejorada para fotografías web con mejor conservación de texturas y reducción de artefactos. La mejora 2x con la tecnología Dual Aggregation Transformer mantiene la nitidez y los detalles naturales. Ampliación 3x utilizando arquitectura de transformador avanzada, ideal para necesidades de ampliación moderadas. La ampliación de alta calidad 4x con una red de transformadores de última generación conserva los detalles finos a escalas más grandes. Elimina el desenfoque/ruido y las sacudidas de las fotos. Propósito general pero mejor en fotografías. Restaura imágenes de baja calidad utilizando el transformador Swin2SR, optimizado para la degradación de BSRGAN. Excelente para corregir artefactos de compresión intensa y mejorar detalles a escala 4x. Ampliación 4x con transformador SwinIR entrenado en degradación de BSRGAN. Utiliza GAN para obtener texturas más nítidas y detalles más naturales en fotografías y escenas complejas. Camino Fusionar PDF Combine varios archivos PDF en un solo documento Orden de archivos páginas. Dividir PDF Extraiga páginas específicas de un documento PDF Girar PDF Corregir la orientación de la página permanentemente paginas Reorganizar PDF Arrastra y suelta páginas para reordenarlas Sostener y arrastrar páginas Números de página Añade numeración a tus documentos automáticamente Formato de etiqueta PDF a texto (OCR) Extraiga texto sin formato de sus documentos PDF Superponer texto personalizado para marca o seguridad Firma Añade tu firma electrónica a cualquier documento Esto se utilizará como firma. Desbloquear PDF Elimina contraseñas de tus archivos protegidos Proteger PDF Proteja sus documentos con un cifrado seguro Éxito PDF desbloqueado, puedes guardarlo o compartirlo Reparar PDF Intente reparar documentos corruptos o ilegibles Escala de grises Convierta todas las imágenes incrustadas en documentos a escala de grises Comprimir PDF Optimice el tamaño del archivo de su documento para compartirlo más fácilmente ImageToolbox reconstruye la tabla de referencias cruzadas interna y regenera la estructura del archivo desde cero. Esto puede restaurar el acceso a muchos archivos que \\"no se pueden abrir\\" Esta herramienta convierte todas las imágenes de documentos a escala de grises. Lo mejor para imprimir y reducir el tamaño del archivo Metadatos Edite las propiedades del documento para una mayor privacidad Etiquetas Productor Autor Palabras clave Creador Limpieza profunda de privacidad Borrar todos los metadatos disponibles para este documento Página OCR profundo Extraiga texto del documento y guárdelo en un archivo de texto utilizando el motor Tesseract No se pueden eliminar todas las páginas Eliminar páginas PDF Eliminar páginas específicas del documento PDF Toque para eliminar A mano Recortar PDF Recortar páginas de documentos hasta cualquier límite Aplanar PDF Haga que el PDF no se pueda modificar rasterizando las páginas del documento No se pudo iniciar la cámara. Verifique los permisos y asegúrese de que otra aplicación no los esté utilizando. Extraer imágenes Extraiga imágenes incrustadas en archivos PDF en su resolución original Este archivo PDF no contiene ninguna imagen incrustada. Esta herramienta escanea cada página y recupera imágenes originales de alta calidad, perfecta para guardar originales de documentos. Dibujar firma Parámetros de pluma Utilice su propia firma como imagen para colocar en los documentos. Comprimir PDF Divida el documento con el intervalo determinado y empaquete documentos nuevos en un archivo zip Intervalo Imprimir PDF Prepare el documento para imprimir con un tamaño de página personalizado Páginas por hoja Orientación Tamaño de página Margen Floración Rodilla suave Optimizado para anime y dibujos animados. Ampliación rápida con colores naturales mejorados y menos artefactos Estilo Samsung One UI 7 Ingrese aquí símbolos matemáticos básicos para calcular el valor deseado (por ejemplo, (5+5)*10) expresión matemática Recoge hasta %1$s imágenes Mantener fecha y hora Conserve siempre las etiquetas exif relacionadas con la fecha y la hora, funciona independientemente de la opción mantener exif Color de fondo para formatos alfa Agrega la capacidad de establecer el color de fondo para cada formato de imagen con soporte alfa; cuando está deshabilitado, está disponible solo para los que no son alfa. Abrir proyecto Continuar editando un proyecto de Image Toolbox previamente guardado No se puede abrir el proyecto de Image Toolbox Al proyecto Image Toolbox le faltan datos del proyecto El proyecto Image Toolbox está dañado Versión del proyecto Image Toolbox no compatible: %1$d Guardar proyecto Almacene capas, fondos y edite el historial en un archivo de proyecto editable No se pudo abrir Escribir en PDF con capacidad de búsqueda Reconozca texto de un lote de imágenes y guarde PDF con capacidad de búsqueda con imagen y capa de texto seleccionable Capa alfa Voltear horizontalmente Voltear verticalmente Cerrar Agregar sombra Color de sombra Geometría del texto Estire o sesgue el texto para lograr un estilo más nítido Escala X Sesgar X Eliminar anotaciones Elimine los tipos de anotaciones seleccionados, como enlaces, comentarios, resaltados, formas o campos de formulario de las páginas PDF Hipervínculos Archivos adjuntos Pauta Ventanas emergentes Sellos formas Notas de texto Marcado de texto Campos de formulario Margen Desconocido Anotaciones Desagrupar Agregue sombra borrosa detrás de la capa con colores y compensaciones configurables ================================================ FILE: core/resources/src/main/res/values-et/strings.xml ================================================ Midagi läks valesti: %1$s Suurus %1$s Laadin… Pilt on eelvaate jaoks liiga suur, kuid üritame ikkagi salvestada Alustamiseks vali pilt Laius %1$s Kõrgus %1$s Kvaliteet Midagi läks valesti Käivita rakendus uuesti Kopeeritud lõikelauale Erandlik olukord Muuda EXIF-andmeid Sobib EXIF-andmeid ei leidunud Lisa silt Salvesta Eemalda Eemalda EXIF-andmed Katkesta Kõik EXIF-andmed kustutatakse. Seda tegevust ei saa tagasi pöörata! Eelseadistused Kadreeri Salvestan Kui väljud, siis kõik salvestamata muudatused lähevad kaotsi Lähtekood Lisamoodul Suuruse muutmise tüüp Ühe pildi muutmine Muuda ühe pildi suurust ja muid parameetreid Vali pilt Kas oled kindel, et soovid selle rakenduse sulgeda? Rakendus on sulgemisel Jää siia Sulge Pilt Värv Värv on kopeeritud Kadreeri pilti soovitud viisil Versioon Säilita EXIF-andmed Pildid: %d Muuda eelvaadet Eemalda Koosta antud pildi alusel värvipalett Paleti koostamine Värvipalett Siit leiad viimased uudised, saad osaleda arendusega seotud aruteludes ja palju muud Värvivalija Vali pildilt värv ning kopeeri või jaga seda Uuendus Uus versioon %1$s Mittetoetatud tüüp: %1$s Antud pildist ei saa paletti luua Konkreetne Paindlik Lähtesta pilt Pildi muudatused tühistuvad ja asenduvad vaikimisi väärtustega Väärtused on korrektselt lähtestatud Lähtesta Algne Väljundkaust Vaikimisi Kohandatud Määratlemata Seadme andmeruum Värvikombinatsioon Punane Roheline Sinine Aseta korrektne aRGB värvikood Pole mitte midagi asetada %d pildi salvestamine ei õnnestunud E-post Võrdle Seadistused Tume kujundus Hele kujundus Süsteemi kujundus Kohandamine Keel Esmane Tasapind Väärtused Lisa Õigused Anna õigused Jaga Eesliide Failinimi Emoji Galerii Muuda Järjestus Täida Sobita Eredus Kontrastsus Värvitoon Värviküllastus Filter Filtrid Suuruse muutmine kaalu järgi Maksimaalne suurus KB-des Muutke pildi suurust vastavalt etteantud suurusele KB-des Võrrelge kahte antud pilti Valige alustamiseks kaks pilti Valige pildid Öörežiim Dünaamilised värvid Luba pilt rahaks Kui see on lubatud, võetakse redigeeritava pildi valimisel sellele pildile rakenduse värvid Amoled režiim Kui see on lubatud, seatakse pindade värv öörežiimis absoluutselt tumedaks Kui dünaamilised värvid on sisse lülitatud, ei saa rakenduse värviskeemi muuta Rakenduse teema põhineb valitud värvil Teave rakenduse kohta Värskendusi ei leitud Probleemi jälgija Saatke siin veaaruandeid ja funktsioonitaotlusi Aidake tõlkida Parandage tõlkevead või lokaliseerige projekt teistesse keeltesse Teie päringuga ei leitud midagi Otsi siit Kui see on lubatud, võetakse rakenduse värvid taustapildi värvideks Tertsiaarne Sekundaarne Piiri paksus Rakendus vajab piltide tööks salvestamiseks juurdepääsu teie salvestusruumile, see on vajalik. Palun andke luba järgmises dialoogiboksis. Rakendus vajab töötamiseks seda luba, andke see käsitsi Väline salvestusruum Monet värvid See rakendus on täiesti tasuta, kuid kui soovite projekti arendamist toetada, klõpsake siin FAB joondus Kontrollige värskendusi Kui see on lubatud, kuvatakse teile rakenduse käivitamisel värskendamise dialoog Pildi suum Valige, millist emotikonit põhiekraanil kuvada Lisa faili suurus Kui see on lubatud, lisab salvestatud pildi laiuse ja kõrguse väljundfaili nimele Kustuta EXIF Kustutage EXIF-i metaandmed mis tahes pildikomplektist Pildi eelvaade Saate vaadata mis tahes tüüpi kujutiste eelvaateid: GIF, SVG ja nii edasi Pildi allikas Fotovalija Failiuurija Androidi kaasaegne fotovalija, mis kuvatakse ekraani allservas, võib töötada ainult Android 12+ puhul. EXIF-i metaandmete saamisel on probleeme Lihtne galerii pildivalija. See töötab ainult siis, kui teil on rakendus, mis pakub meediumivalikut Kasutage pildi valimiseks GetContenti kavatsust. Töötab kõikjal, kuid on teada, et mõnes seadmes on probleeme valitud piltide vastuvõtmisega. See pole minu süü. Optsioonide paigutus Määrab põhiekraanil olevate tööriistade järjestuse Emotikonid loevad järjestusNum originaalfailinimi Lisa algne failinimi Kui see on lubatud, lisab väljundpildi nimele algse failinime Asenda järjekorranumber Kui see on lubatud, asendab standardse ajatempli pildi järjekorranumbriga, kui kasutate paketttöötlust Algse failinime lisamine ei tööta, kui valitud on fotovalija pildiallikas Veebipildi laadimine Laadige Internetist mis tahes pilt, et seda soovi korral eelvaateks, suumida, redigeerida ja salvestada. Pilt puudub Pildi link Sisu skaala Muudab piltide suurust etteantud kõrgusele ja laiusele. Piltide kuvasuhe võib muutuda. Muudab pika küljega piltide suurust etteantud kõrgusele või laiusele. Kõik suuruse arvutused tehakse pärast salvestamist. Piltide kuvasuhe säilib. Lisa filter Kasutage piltidele filtrikette Valgus Värvifilter Alfa Kokkupuude Valge tasakaal Temperatuur Toon Ühevärviline Gamma Esiletõstetud ja varjud Esiletõstmised Varjud Hägusus Mõju Kaugus Kalle Teritama Seepia Negatiivne Solariseeruda Vibrance Must ja valge Ristviilu Vahekaugus Joone laius Sobel serv Hägusus Pooltoonid CGA värviruum Gaussi hägusus Kasti hägusus Kahepoolne hägusus Reljeef laplane Vinjett Alusta Lõpp Kuwahara silumine Virna hägusus Raadius Skaala Moonutused Nurk Keeris Mõhk Laienemine Sfääri murdumine Murdumisnäitaja Klaassfääri murdumine Värvimaatriks Läbipaistmatus Suuruse muutmine piirangutega Muutke piltide suurust etteantud kõrgusele ja laiusele, säilitades samal ajal kuvasuhte Sketš Lävi Kvantimistasemed Sujuv toon Toon Plakati Mitte maksimaalne summutus Nõrk pikslite kaasamine Otsimine Konvolutsioon 3x3 RGB filter Vale värv Esimene värv Teine värv Järjesta ümber Kiire hägustumine Hägususe suurus Keskelt hägustamine x Hägusus keskel y Suumi hägusus Värvitasakaalu Heleduse lävi Keelasite rakenduse Failid, aktiveerige see selle funktsiooni kasutamiseks Joonista Joonistage pildile nagu visandivihikule või joonistage taustale endale Värvi värv Alfa värvimine Joonista pildile Valige pilt ja joonistage sellele midagi Joonista taustale Valige taustavärv ja joonistage selle peale Taustavärv Šifr Krüptige ja dekrüptige kõik failid (mitte ainult pildid), mis põhinevad erinevatel saadaolevatel krüptoalgoritmidel Vali fail Krüptida Dekrüpteerida Valige alustamiseks fail Dekrüpteerimine Krüpteerimine Võti Fail töödeldud Salvestage see fail oma seadmesse või kasutage jagamistoimingut, et panna see kuhu iganes soovite Omadused Rakendamine Ühilduvus Paroolipõhine failide krüptimine. Jätkatud faile saab salvestada valitud kataloogi või jagada. Dekrüpteeritud faile saab avada ka otse. AES-256, GCM-režiim, polsterduseta, vaikimisi 12-baidised juhuslikud IV-d. Saate valida vajaliku algoritmi. Võtmeid kasutatakse 256-bitiste SHA-3 räsidena Faili suurus Maksimaalset failisuurust piiravad Android OS ja saadaolev mälu, mis sõltub seadmest. \nPange tähele: mälu ei ole salvestusruum. Pange tähele, et ühilduvus muude failide krüpteerimistarkvara või -teenustega ei ole garanteeritud. Veidi erinev võtmetöötlus või šifri konfiguratsioon võib põhjustada ühildumatust. Vale parool või valitud fail pole krüptitud Etteantud laiuse ja kõrgusega kujutise salvestamine võib põhjustada mälutõrke. Tehke seda omal riisikol. Vahemälu Vahemälu suurus Leitud %1$s Automaatne vahemälu tühjendamine Kui see on lubatud, tühjendatakse rakenduse käivitamisel rakenduse vahemälu Loo Tööriistad Rühmitage valikud tüübi järgi Rühmitab põhiekraanil olevad valikud kohandatud loendi paigutuse asemel nende tüübi järgi Paigutust ei saa muuta, kui valikute rühmitamine on lubatud Redigeeri ekraanipilti Sekundaarne kohandamine Ekraanipilt Varuvõimalus Jäta vahele Kopeeri Režiimis %1$s salvestamine võib olla ebastabiilne, kuna tegemist on kadudeta vorminguga Kui olete valinud eelseadistuse 125, salvestatakse pilt 125% suurusena originaalpildist. Kui valite eelseadistuse 50, salvestatakse pilt 50% suuruses Siinne eelseadistus määrab väljundfaili %, st kui valite 5 MB pildil eelseadistuse 50, saate pärast salvestamist 2,5 MB pildi Muutke failinimi juhuslikult Kui see on lubatud, on väljundfaili nimi täiesti juhuslik Salvestatud %1$s kausta nimega %2$s Salvestatud kausta %1$s Telegrami vestlus Arutage rakendust ja saage teistelt kasutajatelt tagasisidet. Sealt saate ka beetavärskendusi ja statistikat. Põllukultuuri mask Kuvasuhe Kasutage seda maski tüüpi maski loomiseks antud pildist, pange tähele, et sellel PEAKS olema alfakanal Varundamine ja taastamine Varundamine Taasta Varundage oma rakenduse seaded faili Rakenduse seadete taastamine varem loodud failist Rikutud fail või mitte varukoopia Seadete taastamine õnnestus Võta minuga ühendust See taastab teie seaded vaikeväärtustele. Pange tähele, et seda ei saa tagasi võtta ilma ülalmainitud varukoopiafailita. Kustuta Olete kustutamas valitud värviskeemi. Seda toimingut ei saa tagasi võtta Kustuta skeem Font Tekst Fondi skaala Vaikimisi Suurte fontide kasutamine võib põhjustada kasutajaliidese tõrkeid ja probleeme, mida ei saa parandada. Kasutage ettevaatlikult. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Šš Zz Žž Tt Uu Vv Ww Õõ Ää Öö Üü Xx Yy 0123456789 !? Emotsioonid Söök ja jook Loodus ja loomad Objektid Sümbolid Luba emotikonid Reisid ja kohad Tegevused Tausta eemaldaja Eemaldage pildilt taust joonistades või kasutage valikut Automaatne Kärbi pilti Kujutise algsed metaandmed säilitatakse Läbipaistvad ruumid pildi ümber kärbitakse Tausta automaatne kustutamine Taasta pilt Kustutusrežiim Kustuta taust Tausta taastamine Hägususe raadius Pipetti Joonistamisrežiim Loo probleem Oih… Midagi läks valesti. Võite mulle kirjutada, kasutades allolevaid valikuid ja ma püüan leida lahenduse Suuruse muutmine ja teisendamine Muutke antud piltide suurust või teisendage need muudesse vormingutesse. EXIF-i metaandmeid saab siin redigeerida ka ühe pildi valimisel. Maksimaalne värvide arv See võimaldab rakendusel krahhiaruandeid automaatselt koguda Analüütika Lubage koguda anonüümset rakenduste kasutusstatistikat Praegu võimaldab %1$s-vorming Androidis lugeda ainult EXIF-i metaandmeid. Väljundpildil pole salvestamisel metaandmeid. Pingutus Väärtus %1$s tähendab kiiret tihendamist, mille tulemuseks on suhteliselt suur failimaht. %2$s tähendab aeglasemat tihendamist, mille tulemuseks on väiksem fail. Oota Salvestamine on peaaegu lõppenud. Praeguse tühistamise korral tuleb uuesti salvestada. Värskendused Luba beetaversioonid Värskenduste kontrollimine hõlmab rakenduse beetaversioone, kui see on lubatud Joonista nooled Kui see on lubatud, kujutatakse joonistamise teed osutava noolena Pintsli pehmus Pildid kärbitakse keskelt sisestatud suuruseni. Lõuendit laiendatakse antud taustavärviga, kui pilt on sisestatud mõõtmetest väiksem. Annetus Piltide õmblemine Kombineerige antud pildid, et saada üks suur Valige vähemalt 2 pilti Väljundpildi skaala Pildi suund Horisontaalne Vertikaalne Skaalake väikesed pildid suureks Kui see on lubatud, skaleeritakse väikesed pildid järjestuse suurimaks Piltide järjekord Regulaarne Hägusad servad Kui see on lubatud, joonistab algkujutise alla udused servad, et täita selle ümber olevad ruumid ühe värvi asemel Pikselatsioon Täiustatud pikslistamine Stroke Pixelation Täiustatud teemantpikselatsioon Teemantpikselatsioon Ringi pikselatsioon Täiustatud ringikujuline pikselatsioon Asenda Värv Tolerantsus Asendatav värv Sihtvärv Eemaldatav värv Eemalda värv Ümberkodeerida Piksli suurus Lukusta joonistussuund Kui see on joonistusrežiimis lubatud, siis ekraan ei pöörle Kontrollige värskendusi Paleti stiil Tonaalne koht Neutraalne Elav Ekspressiivne Vikerkaar Puuvilja salat Truudus Sisu Vaikimisi paleti stiil, see võimaldab kohandada kõiki nelja värvi, teised võimaldavad teil määrata ainult võtmevärvi Stiil, mis on pisut kromaatilisem kui ühevärviline Kõva teema, esmase paleti jaoks on värvilisus maksimaalne, teiste jaoks suurenenud Mänguline teema – lähtevärvi toon ei ilmu teemasse Monokroomne teema, värvid on puhtalt must / valge / hall Skeem, mis asetab lähtevärvi kausta Scheme.primaryContainer Skeem, mis on sisuskeemiga väga sarnane See värskenduste kontrollija loob ühenduse GitHubiga, et kontrollida, kas uus värskendus on saadaval Tähelepanu Häälevad servad Keelatud Mõlemad Inverteeri värvid Kui see on lubatud, asendab teema värvid negatiivsetega Otsi Võimaldab otsida põhiekraanil kõiki saadaolevaid tööriistu PDF-tööriistad Töötage PDF-failidega: eelvaade, teisendage piltide partiiks või looge see antud piltidest PDF-i eelvaade PDF pildiks Pildid PDF-i Lihtne PDF-i eelvaade Teisendage PDF piltideks antud väljundvormingus Pakkige antud pildid väljundisse PDF-faili Maski filter Kasutage antud maskeeritud aladele filtrikette, iga maski piirkond saab määrata oma filtrite komplekti Maskid Lisa mask Mask %d Maski värv Maski eelvaade Joonistatud filtrimask renderdatakse, et näidata teile ligikaudset tulemust Pöördtäidise tüüp Kui see on lubatud, filtreeritakse vaikekäitumise asemel kõik maskeerimata alad Olete kustutamas valitud filtrimaski. Seda toimingut ei saa tagasi võtta Kustuta mask Täielik filter Kasutage antud piltidele või üksikule pildile mis tahes filtrikette Alusta Keskus Lõpp Lihtsad variandid Esiletõstja Neoon Pliiats Privaatsuse hägu Joonistage poolläbipaistvad teritatud markeriteed Lisage oma joonistele mõni helendav efekt Vaikimisi, kõige lihtsam – ainult värv Hägustab pildi joonistatud tee all, et kaitsta kõike, mida soovite peita Sarnaselt privaatsuse hägustamisele, kuid hägustamise asemel pikslib Konteinerid Joonista konteinerite taha vari Liugurid Lülitid FAB-id Nupud Joonistage liugurite taha vari Joonista lülitite taha vari Joonistage hõljuvate tegevusnuppude taha vari Joonistage nuppude taha vari Rakenduste ribad Joonistage vari rakenduse ribade taha Väärtus vahemikus %1$s - %2$s Automaatne pööramine Võimaldab pildi orientatsiooni jaoks kasutada piirkasti Teekonna joonistamise režiim Topeltjooneline nool Tasuta joonistamine Topeltnool Joone nool Nool Liin Joonistab sisendväärtusena tee Joonistab tee alguspunktist lõpp-punktini joonena Joonistab joonena osutava noole alguspunktist lõpp-punkti Joonistab etteantud rajalt osutava noole Joonistab topeltnoole alguspunktist lõpp-punkti joonena Joonistab etteantud rajalt topeltnoole Kontuuriga ovaalne Välja toodud õp Ovaalne Rect Joonistab otse alguspunktist lõpp-punkti Joonistab ovaali alguspunktist lõpp-punktini Joonistab ovaalse kontuuri alguspunktist lõpp-punktini Joonistab sirgjooneliselt alguspunktist lõpp-punktini Lasso Joonistab suletud täidetud tee antud tee järgi Tasuta Horisontaalne võrk Vertikaalne võrk Õmblusrežiim Ridade arv Veergude arv Kataloogi \"%1$s\" ei leitud, vahetasime selle vaikekataloogi, palun salvestage fail uuesti Lõikelaud Automaatne pin Kui see on lubatud, lisab salvestatud pildi automaatselt lõikelauale Vibratsioon Vibratsiooni tugevus Failide ülekirjutamiseks peate kasutama \"Exploreri\" pildiallikat, proovige pilte uuesti valida, oleme muutnud pildiallika vajalikuks Failide ülekirjutamine Algne fail asendatakse uuega, selle asemel, et salvestada valitud kausta Tühi Sufiks Skaalarežiim Bilineaarne Catmull Bicubic Tema Erak Lanczos Mitchell Lähim Spline Põhiline Vaikeväärtus Lineaarne (või bilineaarne, kahemõõtmeline) interpolatsioon on tavaliselt hea pildi suuruse muutmiseks, kuid põhjustab detailide ebasoovitavat pehmenemist ja võib siiski olla mõnevõrra sakiline Paremate skaleerimismeetodite hulka kuuluvad Lanczose resampling ja Mitchell-Netravali filtrid Üks lihtsamaid viise suuruse suurendamiseks, iga piksli asendamine sama värvi pikslitega Lihtsaim Androidi skaleerimisrežiim, mida kasutatakse peaaegu kõigis rakendustes Meetod kontrollpunktide komplekti sujuvaks interpoleerimiseks ja uuesti valimiseks, mida tavaliselt kasutatakse arvutigraafikas sujuvate kõverate loomiseks Akende funktsioon, mida sageli kasutatakse signaalitöötluses, et minimeerida spektraalset leket ja parandada sagedusanalüüsi täpsust, ahendades signaali servi Matemaatiline interpolatsioonitehnika, mis kasutab sujuva ja pideva kõvera loomiseks väärtusi ja tuletisi kõvera segmendi lõpp-punktides Resampling meetod, mis säilitab kvaliteetse interpolatsiooni, rakendades piksliväärtustele kaalutud funktsiooni sinc Resampling meetod, mis kasutab reguleeritavate parameetritega konvolutsioonifiltrit, et saavutada tasakaal skaleeritud pildi teravuse ja antialiasi vahel Kasutab kõvera või pinna sujuvaks interpoleerimiseks ja lähendamiseks tükkhaaval määratletud polünoomifunktsioone, pakkudes kuju paindlikku ja pidevat esitust Ainult klipp Salvestusruumi ei salvestata ja pilti proovitakse sisestada ainult lõikepuhvrisse Pintsel taastab kustutamise asemel tausta OCR (teksti tuvastamine) Tuvastage antud pildilt tekst, toetatud on üle 120 keele Pildil pole teksti või rakendus ei leidnud seda \"Täpsus: %1$s\" Tunnustamise tüüp Kiire Standardne Parim Andmeid pole Tesseracti OCR-i nõuetekohaseks toimimiseks tuleb teie seadmesse alla laadida täiendavad treeningandmed (%1$s).\nKas soovite %2$s andmeid alla laadida? Laadi alla Ühendust pole, kontrollige seda ja proovige uuesti rongimudelite allalaadimiseks Allalaaditud keeled Saadaolevad keeled Segmenteerimisrežiim Kasutage Pixel Switchi Kasutab Google Pixeli sarnast lülitit Algses sihtkohas on üle kirjutatud fail nimega %1$s Luup Lubab parema ligipääsetavuse tagamiseks joonistusrežiimides sõrme ülaosas oleva suurendi Jõu algväärtus Sunnib exif vidina esmalt kontrollima Luba mitu keelt Libistage Kõrvuti Lülita puudutage Läbipaistvus Hinda rakendust Hinda See rakendus on täiesti tasuta, kui soovite, et see muutuks suuremaks, tähistage projekti Githubis 😄 Ainult orientatsioon ja skripti tuvastamine Automaatne orientatsioon ja skripti tuvastamine Ainult auto Automaatne Üks veerg Ühe ploki vertikaalne tekst Üksik plokk Üksik rida Üksik sõna Sõna ringiga Üksik tähemärk Vähene tekst Hõreda teksti suund ja skripti tuvastamine Toores joon Kas soovite kustutada keele \"%1$s\" OCR treeningu andmed kõigi tuvastustüüpide või ainult valitud ühe (%2$s) jaoks? Praegune Kõik Gradiendi tegija Looge etteantud väljundsuurusega gradient kohandatud värvide ja välimuse tüübiga Lineaarne Radiaalne Pühkima Gradiendi tüüp Keskus X Keskus Y Plaatide režiim Korduv Peegel Klamber Kleebis Värv peatub Lisa värv Omadused Heleduse jõustamine Ekraan Gradiendi ülekate Koostage antud piltide ülaosast mis tahes gradient Transformatsioonid Kaamera Tehke kaameraga pilti. Pange tähele, et sellest pildiallikast on võimalik hankida ainult üks pilt Vesimärgid Katke pildid kohandatavate teksti/pildi vesimärkidega Korda vesimärki Korrab vesimärki antud asukohas üksiku asemel pildi kohal Nihe X Nihe Y Vesimärgi tüüp Seda pilti kasutatakse vesimärgi mustrina Teksti värv Ülekatterežiim GIF-tööriistad Teisendage pildid GIF-pildiks või eraldage antud GIF-pildist raamid GIF piltidele Teisendage GIF-fail piltide komplektiks Teisendage piltide partii GIF-failiks Pildid GIF-i Alustamiseks valige GIF-pilt Kasutage esimese kaadri suurust Asendage määratud suurus esimese raami mõõtmetega Korda loendust Kaadri viivitus millis FPS Kasutage Lassot Kasutab kustutamiseks Lassot nagu joonistusrežiimis Algse pildi eelvaade Alpha Konfettid Konfetti näidatakse salvestamise, jagamise ja muude esmaste toimingute puhul Turvarežiim Peidab viimaste rakenduste sisu. Seda ei saa jäädvustada ega salvestada. Välju Kui lahkute praegu eelvaatest, peate pildid uuesti lisama Dithering Kvantiseerija Hall skaala Bayer Two By Two Dithering Bayer Three By Three Dithering Bayer Four By Four dithering Bayer Eight By Eight Dithering Floyd Steinbergi segadus Jarvise kohtunik Ninke Dithering Sierra dithering Kaherealine Sierra dithering Sierra Lite Dithering Atkinsoni segadus Stucki dithering Burkes Dithering Vale Floyd Steinbergi segadus Vasakult paremale segamine Juhuslik dithering Lihtne läve närimine Sigma Ruumiline sigma Keskmine hägu B Spline Kasutab kõvera või pinna sujuvaks interpoleerimiseks ja ligikaudseks, paindlikuks ja pidevaks kujundi esituseks tükkhaaval määratletud kahekuubilised polünoomifunktsioonid Native Stack Blur Kallutamise vahetus Glitch Summa Seeme Anaglüüf Müra Pikslite sortimine Segamine Täiustatud tõrge Kanali nihe X Kanalivahetus Y Korruptsiooni suurus Korruptsioonivahetus X Korruptsioonivahetus Y Telgi hägu Külgmine tuhmumine Külg Üles Altpoolt Tugevus Erode Anisotroopne difusioon Difusioon Juhtimine Horisontaalne tuule astmeline Kiire kahepoolne hägu Poissoni hägu Logaritmiline toonide kaardistamine ACES Filmic Tone Mapping Kristalliseerida Löögi värv Fraktalklaas Amplituud Marmor Turbulents Õli Veeefekt Suurus Sagedus X Sagedus Y Amplituud X Amplituud Y Perlini moonutus ACES Hill Tone Mapping Hable Filmic Tone Mapping Heji-Burgessi toonide kaardistamine Kiirus Dehaze Omega Värvimaatriks 4x4 Värvimaatriks 3x3 Lihtsad efektid Polaroid Tritanomaalia Deuteranomaalia Protanomaalia Vintage Browni Coda Chrome Öine nägemine Soe Lahe Tritanopia Deutaronotoopia Protanopia Akromatoomia Akromatopsia Teravili Ebaterav Pastelne Oranž udu Roosa unistus Kuldne tund Kuum suvi Lilla udu Päikesetõus Värviline keeris Pehme kevadvalgusti Sügistoonid Lavendli unistus Küberpunk Limonaadi valgus Spektraalne tuli Öine maagia Fantaasia maastik Värviplahvatus Elektriline gradient Karamelli tumedus Futuristlik gradient Roheline päike Vikerkaare maailm Sügavlilla Kosmoseportaal Punane keeris Digitaalne kood Bokeh Rakenduse riba emotikon muutub juhuslikult Juhuslikud emotikonid Kui emotikonid on keelatud, ei saa te kasutada juhuslikke emotikone Kui juhuslikud emotikonid on lubatud, ei saa te emotikone valida Vana TV Juhusesine hägu Lemmik Lemmikfiltreid pole veel lisatud Pildi formaat Lisab ikoonide alla konteineri valitud kujuga Ikooni kuju Drago Aldridge Katkestus Sa ärkad üles Mobius Üleminek Tipp Värvi anomaalia Pildid on algses sihtkohas üle kirjutatud Pildivormingut ei saa muuta, kui failide ülekirjutamise valik on lubatud Emotikonid värviskeemina Kasutab rakenduse värviskeemina emotikonide põhivärvi, mitte käsitsi määratletud värvi Loob pildist Material You paleti Tumedad Värvid Kasutab valgusvariandi asemel öörežiimi värviskeemi Kopeerige Jetpack Compose koodina Rõngane hägu Risti hägu Ringi hägusus Tähehägu Lineaarne Tilt-Shift Eemaldatavad sildid APNG tööriistad Teisendage pildid APNG-pildiks või eraldage antud APNG-pildist kaadreid APNG piltidele Teisendage APNG-fail piltide partiiks Teisendage piltide partii APNG-failiks Pildid APNG-sse Alustamiseks valige APNG-pilt Liikumishägu Zip Looge antud failidest või piltidest ZIP-fail Lohista käepideme laius Konfeti tüüp Pidulik Plahvata Vihma Nurgad JXL tööriistad Tehke JXL ~ JPEG ümberkodeerimine ilma kvaliteedi kadumiseta või teisendage GIF/APNG JXL-animatsiooniks JXL kuni JPEG Tehke kadudeta ümberkodeerimine JXL-st JPEG-vormingusse Tehke kadudeta ümberkodeerimine JPEG-st JXL-i JPEG kuni JXL Alustamiseks valige JXL-pilt Kiire Gaussi hägu 2D Kiire Gaussi hägu 3D Kiire Gaussi hägu 4D Auto lihavõtted Võimaldab rakendusel lõikelaua andmeid automaatselt kleepida, nii et need ilmuvad põhiekraanile ja saate neid töödelda Ühtlustamise värv Ühtlustamise tase Lanczos Bessel Resampling meetod, mis säilitab kvaliteetse interpolatsiooni, rakendades piksliväärtustele Besseli (jinc) funktsiooni GIF JXL-i Teisendage GIF-pildid JXL-i animeeritud piltideks APNG kuni JXL Teisendage APNG-pildid JXL-animeeritud piltideks JXL piltidele Teisendage JXL-animatsioon piltide partiiks Pildid JXL-i Teisendage piltide partii JXL-animatsiooniks Käitumine Jätke faili valimine vahele Failivalija kuvatakse valitud ekraanil kohe, kui see on võimalik Loo eelvaateid Lubab eelvaate genereerimise, see võib aidata vältida mõne seadme kokkujooksmisi, samuti keelab see mõne redigeerimisfunktsiooni ühe redigeerimisvaliku raames Kadunud kompressioon Kasutab kadudeta pakkimist, et vähendada faili suurust, mitte kadudeta Kompressiooni tüüp Reguleerib saadud kujutise dekodeerimise kiirust, see peaks aitama tulemuseks oleva pildi kiiremini avada, väärtus %1$s tähendab kõige aeglasemat dekodeerimist, samas kui %2$s - kiireim, see säte võib suurendada väljundpildi suurust Sorteerimine Kuupäev Kuupäev (ümberpööratud) Nimi Nimi (tagurpidi) Kanalite konfiguratsioon Täna eile Manustatud valija Image Toolboxi pildivalija Lubasid pole Taotlus Valige mitu meediat Valige üks meedium Vali Proovi uuesti Kuva seaded maastikul Kui see on keelatud, avatakse rõhtrežiimis sätted ülemise rakenduse riba nupul nagu alati, mitte püsiva nähtava valiku asemel Täisekraani seaded Lubage see ja seadete leht avatakse alati täisekraanina, mitte libistatava sahtlilehena Lüliti tüüp Koosta Jetpack Compose materjal, mille vahetate Materjal, mille vahetate Max Ankru suuruse muutmine piksel Ladus Lüliti, mis põhineb disainisüsteemil \"Fluent\". Cupertino \"Cupertino\" disainisüsteemil põhinev lüliti Pildid SVG-sse Jälgige antud kujutised SVG-kujutisteks Kasutage proovide paletti Kui see suvand on lubatud, valitakse kvantimispalett Tee ära jätta Selle tööriista kasutamine suurte piltide jälgimiseks ilma skaleerimata ei ole soovitatav, see võib põhjustada krahhi ja pikendada töötlemisaega Madalam pilt Enne töötlemist vähendatakse kujutise mõõtmeid madalamaks, mis aitab tööriistal kiiremini ja ohutumalt töötada Minimaalne värvisuhe Liinide lävi Ruutkünnis Koordineerib ümardamise tolerantsi Tee skaala Lähtesta atribuudid Kõik atribuudid seatakse vaikeväärtustele, pange tähele, et seda toimingut ei saa tagasi võtta Üksikasjalik Vaikimisi rea laius Mootori režiim Pärand LSTM võrk Pärand ja LSTM Teisenda Teisendage pildipartiid antud vormingusse Lisa uus kaust Bitti proovi kohta Kokkusurumine Fotomeetriline tõlgendamine Näidised piksli kohta Tasapinnaline konfiguratsioon Y Cb Cr Subproovide võtmine Y Cb Cr positsioneerimine X resolutsioon Y Resolutsioon Lahutusvõime üksus Ribade nihked Rida riba kohta Riba baitide arv JPEG vahetusvorming JPEG vahetusvormingu pikkus Ülekandmise funktsioon Valge punkt Primaarsed kromaatsused Y Cb Cr koefitsiendid Viide Must Valge Kuupäev Kellaaeg Pildi kirjeldus Tee Mudel Tarkvara Kunstnik Autoriõigus Exif versioon Flashpix versioon Värviruum Gamma Pixel X dimensioon Pixel Y mõõde Tihendatud bitti piksli kohta Tootja märkus Kasutaja kommentaar Seotud helifail Kuupäev Kell Originaal Kuupäev Kell digiteeritud Offset Time Offset Time Original Offset Time Digitaliseeritud Alamsekundi aeg Sub Sec Time Original Alamsekundi aeg digiteeritud Kokkupuute aeg F Number Kokkupuute programm Spektri tundlikkus Fotograafiline tundlikkus Oecf Tundlikkuse tüüp Standardne väljundtundlikkus Soovitatav kokkupuuteindeks ISO kiirus ISO kiirus Latitude yyyy ISO kiirus Laiuskraad zzz Säriaja väärtus Ava väärtus Heleduse väärtus Särituse kallutatuse väärtus Maksimaalne ava väärtus Teema kaugus Mõõtmisrežiim Välklamp Teemavaldkond Fookuskaugus Välgu energia Ruumiline sagedusreaktsioon Fokaaltasandi X eraldusvõime Fokaaltasandi Y eraldusvõime Fokaaltasandi eraldusvõime üksus Teema asukoht Kokkupuute indeks Sensatsioonimeetod Faili allikas CFA muster Kohandatud renderdatud Särirežiim Valge tasakaal Digitaalne suumisuhe Fookuskaugus In35mm film Stseeni jäädvustamise tüüp Saavuta kontroll Kontrast Küllastus Teravus Seadme seadistuse kirjeldus Teema kauguse vahemik Pildi kordumatu ID Kaamera omaniku nimi Kere seerianumber Objektiivi spetsifikatsioon Objektiivi valmistamine Objektiivi mudel Objektiivi seerianumber GPS-i versiooni ID GPS Latitude Ref GPS Latitude GPS pikkuskraad Ref GPS pikkuskraad GPS-i kõrguse viide GPS kõrgus merepinnast GPS-i ajatempel GPS-satelliidid GPS-i olek GPS mõõtmise režiim GPS DOP GPS kiiruse viide GPS kiirus GPS Track Ref GPS jälg GPS Img suuna viide GPS Img suund GPS kaardi kuupäev GPS-i sihtlaiuskraadi viide GPS-i sihtlaiuskraad GPS sihtkoha pikkuskraad Ref GPS-i sihtpikkuskraad GPS Dest Bearing Ref GPS Dest Bearing GPS-i sihtkauguse viide GPS-i sihtkaugus GPS-i töötlemismeetod GPS-piirkonna teave GPS-i kuupäevatempel GPS diferentsiaal GPS H positsioneerimise viga Koostalitlusvõime indeks DNG versioon Kärpimise vaikesuurus Pildi eelvaate algus Pildi eelvaate pikkus Aspekti raam Anduri alumine piir Anduri vasak ääris Anduri parem ääris Anduri ülemine ääris ISO Joonistage tekst teele antud fondi ja värviga Fondi suurus Vesimärgi suurus Korda teksti Praegust teksti korratakse ühekordse joonistamise asemel kuni tee lõpuni Kriipsu suurus Kasutage valitud pilti, et joonistada see mööda etteantud teed Seda pilti kasutatakse joonistatud tee korduva sisestusena Joonistab kontuuriga kolmnurga alguspunktist lõpp-punkti Joonistab kontuuriga kolmnurga alguspunktist lõpp-punkti Kontuuriga kolmnurk Kolmnurk Joonistab hulknurga alguspunktist lõpp-punkti Hulknurk Piiratud hulknurk Joonistab piiritletud hulknurga alguspunktist lõpp-punktini Tipud Joonistage korrapärane hulknurk Joonistage hulknurk, mis on vaba vormi asemel korrapärane Joonistab tähe alguspunktist lõpp-punkti Täht Kontuuriga täht Joonistab kontuuriga tähe alguspunktist lõpp-punktini Sisemise raadiuse suhe Joonistage tavaline täht Joonistage täht, mis on vaba vormi asemel tavaline Antialias Võimaldab antialiasi, et vältida teravaid servi Ava eelvaate asemel Redigeerimine Kui valite ImageToolboxis avatava (eelvaate) pildi, avatakse eelvaate asemel redigeerimise valikuleht Dokumendi skanner Skannige dokumente ja looge PDF-faile või eraldage neist pilte Klõpsake skannimise alustamiseks Alustage skannimist Salvesta pdf-ina Jaga pdf-ina Allolevad valikud on mõeldud piltide, mitte PDF-i salvestamiseks HSV histogrammi võrdsustamine Histogrammi võrdsustamine Sisestage protsent Luba sisestada tekstivälja kaudu Lubab eelseadistuste valiku taga oleva tekstivälja, et need käigupealt sisestada Skaala värviruum Lineaarne Histogrammi pikslituse võrdsustamine Võre suurus X Võre suurus Y Histogrammi võrdsustamine adaptiivne Histogrammi kohanduva LUV-i võrdsustamine Ühtlustada histogrammi adaptiivne LAB CLAHE CLAHE LAB CLAHE LUV Kärbi sisu Raami värv Värv, mida ignoreerida Mall Mallfiltreid pole lisatud Loo uus Skannitud QR-kood ei ole kehtiv filtrimall Skaneeri QR-kood Valitud failil puuduvad filtrimalli andmed Loo mall Malli nimi Seda pilti kasutatakse selle filtrimalli eelvaateks Malli filter QR-koodi pildina Failina Salvesta failina Salvesta QR-koodi kujutisena Kustuta mall Olete kustutamas valitud mallifiltrit. Seda toimingut ei saa tagasi võtta Lisatud filtrimall nimega \"%1$s\" (%2$s) Filtri eelvaade QR ja vöötkood Skannige QR-kood ja hankige selle sisu või kleepige oma string uue koodi loomiseks Koodi sisu Skannige mis tahes vöötkoodi, et väljal sisu asendada, või sisestage midagi, et luua valitud tüüpi uus vöötkood QR-kirjeldus Min Andke seadetes kaamera luba QR-koodi skannimiseks Andke seadetes kaamerale luba dokumendiskanneri skannimiseks Kuubik B-spliin Hamming Hanning Blackman Welch Quadriric Gaussi Sfinks Bartlett Robidoux Robidoux Sharp Spliin 16 Spliin 36 Spliin 64 Keiser Bartlett-He Kast Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kuupinterpolatsioon tagab sujuvama skaleerimise, võttes arvesse lähimat 16 pikslit, andes paremaid tulemusi kui bilineaarne Kasutab kõvera või pinna sujuvaks interpoleerimiseks ja lähendamiseks tükikaupa määratletud polünoomifunktsioone, paindlikku ja pidevat kujundit Aknafunktsioon, mida kasutatakse spektraalse lekke vähendamiseks signaali servade kitsendamise teel, mis on kasulik signaalitöötluses Hanni akna variant, mida tavaliselt kasutatakse signaalitöötlusrakendustes spektraallekke vähendamiseks Aknafunktsioon, mis tagab hea sageduseraldusvõime, minimeerides spektraalset leket ja mida kasutatakse sageli signaalitöötluses Aknafunktsioon, mis on loodud hea sageduseraldusvõime tagamiseks koos vähendatud spektraallekkega, mida kasutatakse sageli signaalitöötlusrakendustes Meetod, mis kasutab interpoleerimiseks ruutfunktsiooni, pakkudes sujuvaid ja pidevaid tulemusi Interpolatsioonimeetod, mis rakendab Gaussi funktsiooni, mis on kasulik piltide müra tasandamiseks ja vähendamiseks Täiustatud resamplimise meetod, mis pakub kvaliteetset interpolatsiooni minimaalsete artefaktidega Kolmnurkse akna funktsioon, mida kasutatakse signaalitöötluses spektraalse lekke vähendamiseks Kvaliteetne interpolatsioonimeetod, mis on optimeeritud loomuliku pildi suuruse muutmiseks, tasakaalustades teravust ja sujuvust Robidouxi meetodi teravam variant, optimeeritud terava pildi suuruse muutmiseks Splainipõhine interpolatsioonimeetod, mis tagab sujuva tulemuse, kasutades 16-puudutusega filtrit Splainipõhine interpolatsioonimeetod, mis tagab sujuva tulemuse, kasutades 36-puudutusega filtrit Splainipõhine interpolatsioonimeetod, mis tagab sujuva tulemuse, kasutades 64-puudutusega filtrit Interpolatsioonimeetod, mis kasutab Kaiseri akent, pakkudes head kontrolli põhisagara laiuse ja külgsagara taseme vahelise kompromissi üle Hübriidakna funktsioon, mis ühendab Bartletti ja Hanni aknad, mida kasutatakse signaalitöötluse spektraallekke vähendamiseks Lihtne uuesti diskreetimismeetod, mis kasutab lähimate piksliväärtuste keskmist, mille tulemuseks on sageli blokeeritud välimus Aknafunktsioon, mida kasutatakse spektraalse lekke vähendamiseks, pakkudes signaalitöötlusrakendustes head sageduseraldusvõimet Resampling meetod, mis kasutab 2-sagaralist Lanczose filtrit kvaliteetse interpolatsiooni jaoks minimaalsete artefaktidega Resampling meetod, mis kasutab 3-sagaralist Lanczose filtrit kvaliteetse interpolatsiooni jaoks minimaalsete artefaktidega Resampling meetod, mis kasutab 4-sagaralist Lanczose filtrit kvaliteetse interpolatsiooni jaoks minimaalsete artefaktidega Lanczos 2 filtri variant, mis kasutab jinc-funktsiooni, pakkudes kvaliteetset interpolatsiooni minimaalsete artefaktidega Lanczos 3 filtri variant, mis kasutab jinc-funktsiooni, pakkudes kvaliteetset interpolatsiooni minimaalsete artefaktidega Lanczos 4 filtri variant, mis kasutab jinc-funktsiooni, pakkudes kvaliteetset interpolatsiooni minimaalsete artefaktidega Hanning EWA Hanningi filtri elliptiline kaalutud keskmine (EWA) variant sujuvaks interpoleerimiseks ja uuesti diskreetimiseks Robidoux EWA Robidouxi filtri elliptiline kaalutud keskmine (EWA) variant kvaliteetseks uuesti proovivõtuks Blackman EVE Blackmani filtri elliptiline kaalutud keskmine (EWA) variant helisevate artefaktide minimeerimiseks Kvadriline EWA Quadric filtri elliptiline kaalutud keskmine (EWA) variant sujuvaks interpoleerimiseks Robidoux Sharp EWA Robidoux Sharpi filtri elliptiline kaalutud keskmine (EWA) variant teravamate tulemuste saamiseks Lanczos 3 Jinc EWA Lanczos 3 Jinci filtri elliptiline kaalutud keskmine (EWA) variant kvaliteetseks uuesti proovivõtuks vähendatud aliasidega Ženšenn Resampling filter, mis on loodud kvaliteetseks pilditöötluseks koos hea teravuse ja sujuvuse tasakaaluga Ženšenn EWA Ženšenni filtri elliptiline kaalutud keskmine (EWA) variant parema pildikvaliteedi saavutamiseks Lanczos Sharp EWA Lanczos Sharpi filtri elliptiline kaalutud keskmine (EWA) variant teravate tulemuste saavutamiseks minimaalsete artefaktidega Lanczos 4 teravaim EWA Lanczos 4 Sharpest filtri elliptiline kaalutud keskmine (EWA) variant üliterava pildi uuesti proovivõtmiseks Lanczos Soft EWA Lanczose pehme filtri elliptiline kaalutud keskmine (EWA) variant sujuvamaks kujutiste uuesti valimiseks Haasn Pehme Haasni loodud resamplimisfilter sujuvaks ja artefaktivabaks pildi skaleerimiseks Vormingu teisendamine Teisendage piltide partii ühest vormingust teise Loobu igaveseks Piltide virnastamine Virnastada pilte valitud segamisrežiimidega üksteise peale Lisa pilt Prügikastid loevad Clahe HSL Clahe HSV Ühtlustada histogrammi adaptiivne HSL Ühtlustada histogrammi adaptiivne HSV Edge režiim Klipp Mähi Värvipimedus Valige režiim, et kohandada teemavärve valitud värvipimeduse variandiga Raskused eristada punaseid ja rohelisi toone Raskused roheliste ja punaste toonide eristamisel Raskused eristada sinist ja kollast tooni Võimetus tajuda punaseid toone Suutmatus tajuda rohelisi toone Suutmatus tajuda siniseid toone Vähendatud tundlikkus kõikide värvide suhtes Täielik värvipimedus, ainult halli varjundite nägemine Ärge kasutage värvipimeduse skeemi Värvid on täpselt sellised, nagu teemas määratud Sigmoidne Lagrange 2 Lagrange\'i interpolatsioonifilter järgus 2, mis sobib sujuvate üleminekutega kvaliteetseks kujutise skaleerimiseks Lagrange 3 Lagrange\'i interpolatsioonifilter järgus 3, mis pakub paremat täpsust ja sujuvamaid tulemusi pildi skaleerimisel Lanczos 6 Lanczose resampling filter kõrgema astmega 6, mis tagab teravama ja täpsema pildi skaleerimise Lanczos 6 Jinc Lanczos 6 filtri variant, mis kasutab Jinc-funktsiooni, et parandada kujutise uuesti proovivõtu kvaliteeti Lineaarne kasti hägu Lineaarne telgi hägu Lineaarne Gaussi kasti hägu Lineaarne virna hägu Gaussi kasti hägu Lineaarne kiire Gaussi hägusus Järgmine Lineaarne kiire Gaussi hägu Lineaarne Gaussi hägu Valige üks filter, et seda värvina kasutada Vahetage filter Valige allpool filter, et kasutada seda oma joonisel pintslina TIFF-i tihendamise skeem Madal polü Liivamaaling Pildi jagamine Jagage üks pilt ridade või veergude kaupa Sobib piiridesse Kombineerige kärpimise suuruse muutmise režiim selle parameetriga, et saavutada soovitud käitumine (kärpimine/sobita kuvasuhe) Keelte importimine õnnestus OCR-mudelite varundamine Import Ekspordi positsioon Keskus Üleval vasakul Üleval paremal All vasakul All paremal Ülemine keskus Keskel paremal Alumine keskus Keskel vasakul Sihtpilt Paleti ülekanne Täiustatud õli Lihtne vana teler HDR Gotham Lihtne sketš Pehme sära Värviline plakat Tri Tone Kolmas värv Clahe Oklab Clara Olch Clahe Jzazbz Polka Dot Kobaras 2x2 dithering Kobaras 4x4 dithering Kobaras 8x8 dithering Yililoma dithering Lemmikvalikuid pole valitud, lisage need tööriistade lehele Lisa lemmikud Täiendav Analoogne kolmik Tükeldatud Täiendav Tetradic Ruut Analoogne + täiendav Värvitööriistad Segage, looge toone, genereerige toone ja palju muud Värvide harmooniad Värvi varjutamine Variatsioon Tints Toonid Varjud Värvide segamine Värviinfo Valitud värv Värv segamiseks Kui dünaamilised värvid on sisse lülitatud, ei saa raha kasutada 512x512 2D LUT Siht-LUT-pilt Amatöör Preili etikett Pehme elegants Pehme elegantsi variant Paleti ülekande variant 3D LUT Siht-3D LUT-fail (.cube / .CUBE) LUT Bleach Bypass Küünlavalgus Drop Blues Äge Amber Sügisvärvid Filmivaru 50 Udune öö Kodak Hankige neutraalne LUT-pilt Esmalt kasutage oma lemmikfototöötlusrakendust, et rakendada neutraalsele LUT-ile filter, mille saate siit. Selle korrektseks toimimiseks ei tohi ükski pikslivärv sõltuda teistest pikslitest (nt hägusus ei tööta). Kui olete valmis, kasutage uut LUT-pilti 512*512 LUT-filtri sisendina Popkunst Tselluloid Kohv Kuldne mets Rohekas Retrokollane Linkide eelvaade Võimaldab linkide eelvaate allalaadimist kohtades, kust saate teksti (QRCode, OCR jne) Lingid ICO-faile saab salvestada ainult maksimaalses suuruses 256 x 256 GIF-i WEBP-sse Teisendage GIF-pildid WEBP-animeeritud piltideks WEBP tööriistad Teisendage pildid WEBP-animeeritud pildiks või eraldage antud WEBP-animatsioonist kaadreid WEBP piltidele Teisendage WEBP-fail piltide komplektiks Teisendage piltide partii WEBP-failiks Pildid WEBP-sse Alustamiseks valige WEBP-pilt Puudub täielik juurdepääs failidele Lubage kõigile failidele juurdepääs, et näha JXL-i, QOI-d ja muid pilte, mida Androidis piltidena ei tuvastata. Ilma loata ei saa Image Toolbox neid pilte näidata Joonistuse vaikevärv Vaikimisi joonistamise tee režiim Lisa ajatempel Lubab ajatempli lisamise väljundfaili nimele Vormindatud ajatempel Lubage ajatempli vormindamine väljundfaili nimes lihtsate millide asemel Lubage ajatemplid, et valida nende vorming Ühekordne salvestamiskoht Saate vaadata ja redigeerida ühekordseid salvestuskohti, mida saate kasutada, vajutades pikalt salvestamisnuppu enamasti kõigis valikutes Hiljuti kasutatud CI kanal Grupp Pildi tööriistakast Telegramis 🎉 Liituge meie vestlusega, kus saate arutada kõike, mida soovite, ja vaadata ka CI kanalit, kuhu postitan beetaversioone ja teadaandeid Hankige märguandeid rakenduse uute versioonide kohta ja lugege teadaandeid Sobitage pilt etteantud mõõtmetega ja rakendage taustale hägusust või värvi Tööriistade paigutus Grupeeri tööriistad tüübi järgi Rühmitab põhiekraanil olevad tööriistad kohandatud loendi paigutuse asemel nende tüübi järgi Vaikeväärtused Süsteemi ribade nähtavus Süsteemiribade kuvamine pühkides Lubab pühkimise, et kuvada süsteemiribad, kui need on peidetud Automaatne Peida kõik Näita kõiki Peida navigeerimisriba Peida olekuriba Müra tekitamine Looge erinevaid helisid, nagu Perlin või muud tüüpi helid Sagedus Müra tüüp Pöörlemise tüüp Fraktaali tüüp oktaavid Lakuuarsus Kasu Kaalutud tugevus Pingpongi tugevus Kauguse funktsioon Tagastamise tüüp Värisemine Domain Warp Joondamine Kohandatud failinimi Valige asukoht ja failinimi, mida kasutatakse praeguse pildi salvestamiseks Salvestatud kohandatud nimega kausta Kollaažide tegija Tehke kollaaže kuni 20 pildist Kollaaži tüüp Hoidke pilti, et vahetada, liigutada ja asendi reguleerimiseks suumida Keela pööramine Takistab piltide pööramist kahe sõrme liigutustega Luba ääriste külge kinnitamine Pärast liigutamist või suumimist klõpsavad pildid, et täita raami servad Histogramm RGB või Brightness kujutise histogramm, mis aitab teil kohandada Seda pilti kasutatakse RGB ja heleduse histogrammide loomiseks Tesseracti valikud Rakendage tesseract-mootori jaoks mõned sisendmuutujad Kohandatud valikud Valikud tuleb sisestada järgmise skeemi järgi: \"--{option_name} {value}\" Automaatne kärpimine Vabad nurgad Kärbi pilti hulknurga kaupa, see parandab ka perspektiivi Sundige punktid kujutise piiridele Punkte ei piira pildipiirid, see on kasulik perspektiivi täpsemaks korrigeerimiseks Mask Sisu teadlik täitmine joonistatud tee all Tervenemiskoht Kasutage Circle Kerneli Avamine Sulgemine Morfoloogiline gradient Top Müts Must müts Toonikõverad Lähtestage kõverad Kõverad veeretatakse tagasi vaikeväärtusele Joone stiil Vahe suurus Katkendlik Punkt katkendlik Tempel Siksak Joonistab määratud vahe suurusega katkendjoone piki tõmmatud rada Joonistab punkti ja katkendjoone mööda etteantud teed Lihtsalt vaikimisi sirgjooned Joonistab valitud kujundid mööda rada määratud vahedega Joonistab lainelise siksaki mööda teed Siksak-suhe Loo otsetee Valige kinnitamiseks tööriist Tööriist lisatakse teie käivitusprogrammi avakuvale otseteena. Vajaliku käitumise saavutamiseks kasutage seda koos sättega \"Jäta faili valimine vahele\". Ärge virna raame Võimaldab eelmiste raamide utiliseerimist, nii et need ei virna üksteise peale Crossfade Raamid liimitakse üksteise sisse Crossfade kaadrid loevad Lävi üks Lävi kaks Canny Peegel 101 Täiustatud suumi hägusus Laplacian Lihtne Sobel Lihtne Abistav võrk Täpse manipuleerimise hõlbustamiseks kuvatakse joonistusala kohal tugivõrk Võre värv Lahtri laius Lahtri kõrgus Kompaktsed valijad Mõned valiku juhtelemendid kasutavad vähem ruumi võtmiseks kompaktset paigutust Andke seadetes kaamera luba pildi jäädvustamiseks Paigutus Põhiekraani pealkiri Constant Rate Factor (CRF) Väärtus %1$s tähendab aeglast tihendamist, mille tulemuseks on suhteliselt väike failimaht. %2$s tähendab kiiremat tihendamist, mille tulemuseks on suur fail. Lut raamatukogu Laadige alla LUT-de kogu, mida saate pärast allalaadimist rakendada Värskendage LUT-de kogu (järjekorda pannakse ainult uued), mida saate pärast allalaadimist rakendada Muutke filtrite vaikepildi eelvaadet Pildi eelvaade Peida Näita Liuguri tüüp Uhke Materjal 2 Väljamõeldud liugur. See on vaikevalik Materjali 2 liugur Liugur Material You Rakenda Keskmised dialooginupud Dialoogide nupud paigutatakse võimaluse korral keskele, mitte vasakule küljele Avatud lähtekoodiga litsentsid Vaadake selles rakenduses kasutatavate avatud lähtekoodiga teekide litsentse Piirkond Resampling kasutades piksli pindala seost. See võib olla eelistatud meetod kujutiste detsimeerimiseks, kuna see annab muare-vabad tulemused. Kui aga pilti suumida, sarnaneb see \"Lähima\" meetodiga. Luba Tonemapping Sisesta % Saidile ei pääse juurde, proovige kasutada VPN-i või kontrollige, kas URL on õige Märgistuskihid Kihtide režiim, mis võimaldab vabalt paigutada pilte, teksti ja muud Redigeeri kihti Kihid pildil Kasutage pilti taustana ja lisage sellele erinevad kihid Kihid taustal Sama mis esimene valik, kuid pildi asemel on värv Beeta Kiirseadete külg Lisage piltide redigeerimise ajal valitud küljele ujuv riba, millel klõpsamisel avanevad kiired sätted Tühjenda valik Seadete rühm \"%1$s\" ahendatakse vaikimisi Seadete gruppi \"%1$s\" laiendatakse vaikimisi Base64 tööriistad Dekodeerige Base64 string pildiks või kodeerige pilt Base64 vormingusse Alus64 Esitatud väärtus ei ole kehtiv Base64 string Tühja või kehtetut Base64 stringi ei saa kopeerida Kleebi alus64 Kopeeri Base64 Laadige pilt Base64 stringi kopeerimiseks või salvestamiseks. Kui teil on string ise, saate selle pildi saamiseks ülal kleepida Salvesta Base64 Share Base64 Valikud Tegevused Impordibaas64 Base64 toimingud Lisa kontuur Lisage määratud värvi ja laiusega teksti ümber kontuur Kontuuri värv Kontuuri suurus Pöörlemine Kontrollsumma failinimena Väljundpiltidel on nende andmete kontrollsummale vastav nimi Tasuta tarkvara (partner) Rohkem kasulikku tarkvara Androidi rakenduste partnerkanalis Algoritm Kontrollsumma tööriistad Võrrelge kontrollsummasid, arvutage räsi või looge failidest kuueteistkümnendstringe, kasutades erinevaid räsimisalgoritme Arvutage Tekst Hash Kontrollsumma Valige fail, et arvutada selle kontrollsumma valitud algoritmi alusel Sisestage tekst, et arvutada selle kontrollsumma valitud algoritmi alusel Allika kontrollsumma Kontrollsumma võrdluseks Matš! Erinevus Kontrollsummad on võrdsed, see võib olla ohutu Kontrollsummad ei ole võrdsed, fail võib olla ohtlik! Võrgusilma gradiendid Vaadake võrgusilma gradientide kogumit Importida saab ainult TTF- ja OTF-fonte Importi font (TTF/OTF) Ekspordi fonte Imporditud fondid Viga katse salvestamisel, proovige muuta väljundkausta Failinimi pole määratud Mitte ühtegi Kohandatud lehed Lehtede valik Tööriistast väljumise kinnitus Kui teil on teatud tööriistade kasutamise ajal salvestamata muudatusi ja proovite seda sulgeda, kuvatakse kinnitusdialoog Redigeerige EXIF-i Muutke ühe pildi metaandmeid ilma uuesti tihendamiseta Puudutage saadaolevate siltide muutmiseks Muuda kleebist Sobiv laius Sobiv kõrgus Partii võrdlemine Valige fail/failid, et arvutada selle kontrollsumma valitud algoritmi alusel Valige failid Valige kataloog Pea pikkuse skaala Tempel Ajatempel Vorming muster Polsterdus Pildi lõikamine Lõika pildi osa ja ühenda vasakpoolsed (võib olla pöördvõrdeline) vertikaalsete või horisontaalsete joontega Vertikaalne pöördejoon Horisontaalne pöördejoon Pöördvalik Vertikaalne lõigatud osa jäetakse maha, selle asemel, et lõikeala ümber osi liita Horisontaalne lõigatud osa jäetakse maha, selle asemel, et lõikeala ümber osi liita Võrgusilma gradientide kogu Looge võrgusilma gradient kohandatud sõlmede arvu ja eraldusvõimega Võrgusilma gradiendi ülekate Koostage etteantud piltide ülaosa võrgusilma gradient Punktide kohandamine Võre suurus Resolutsioon X Resolutsioon Y Resolutsioon Pikslite kaupa Tõstke esile Värv Pikslite võrdluse tüüp Skanni vöötkoodi Kõrguse suhe Vöötkoodi tüüp Jõustada mustvalge Vöötkoodipilt on täielikult mustvalge ja seda ei värvita rakenduse teema järgi Skannige mis tahes vöötkoodi (QR, EAN, AZTEC jne) ja hankige selle sisu või kleepige oma tekst uue loomiseks Vöötkoodi ei leitud Loodud vöötkood on siin Helikaaned Ekstraktige helifailidest albumi kaanepildid, toetatakse enamikke levinumaid vorminguid Valige alustamiseks heli Valige heli Kaaneid ei leitud Logid saatmine Klõpsake rakenduse logifaili jagamiseks. See aitab mul probleemi tuvastada ja probleeme lahendada Oih… Midagi läks valesti Võite minuga ühendust võtta, kasutades allolevaid valikuid ja ma püüan leida lahenduse.\n(Ärge unustage logisid lisada) Kirjuta faili Ekstraktige piltide partiist tekst ja salvestage see ühte tekstifaili Kirjuta metaandmetesse Ekstraheerige igast pildist tekst ja asetage see suhteliste fotode EXIF-teabesse Nähtamatu režiim Kasutage steganograafiat, et luua oma piltide baitide sees silmale nähtamatud vesimärgid Kasutage LSB-d Kasutatakse LSB (Less Significant Bit) steganograafia meetodit, muul juhul FD (Frequency Domain) meetodit Punasilmsuse automaatne eemaldamine Parool Avage lukustus PDF on kaitstud Operatsioon peaaegu lõpetatud. Nüüd tühistamine nõuab selle taaskäivitamist Muutmise kuupäev Muutmise kuupäev (ümberpööratud) Suurus Suurus (tagurpidi) MIME tüüp MIME tüüp (ümberpööratud) Laiendus Laiendus (tagurpidi) Lisamise kuupäev Lisamise kuupäev (ümberpööratud) Vasakult paremale Paremalt vasakule Ülevalt alla Alt üles Vedel klaas Lüliti, mis põhineb hiljuti välja kuulutatud IOS 26-l ja selle vedelklaasi disainisüsteemil Valige allpool pilt või kleepige/importige Base64 andmed Alustamiseks tippige pildi link Kleebi link Kaleidoskoop Sekundaarne nurk Küljed Kanalite segu Sinine roheline Punane sinine Roheline punane Punaseks Roheliseks Sinise sisse Tsüaan Magenta Kollane Värv Pooltoon Kontuur Tasemed Offset Voronoi kristalliseerumine Kuju Venitada Juhuslikkus Eemaldage täpid Hajus DoG Teine raadius võrdsustada Sära Keerake ja näpistage Pointilliseerida Äärise värv Polaarkoordinaadid Otse polaarseks Polaarne otse Pöörake ringis ümber Vähenda müra Lihtne solariseerimine Kuduma X vahe Y vahe X Laius Y Laius Keerake Kummitempel Määri Tihedus Sega Keraobjektiivi moonutused Murdumisnäitaja Arc Levitamise nurk Säde Kiired ASCII Gradient Maarja Sügis Luu Jet Talv Ookean Suvi Kevad Lahe variant HSV Roosa Kuum Sõna Magma Inferno Plasma Viridis Kodanikud Hämar Hämarik nihkunud Perspektiiv Auto Deskew Luba kärpida Põllukultuur või perspektiiv Absoluutne Turbo Sügavroheline Objektiivi korrigeerimine Sihtobjektiivi profiilifail JSON-vormingus Laadige alla valmis objektiiviprofiilid Osa protsendid Ekspordi JSON-ina Kopeerige palettandmetega string JSON-esitusena Õmbluste nikerdamine Avakuva Lukustusekraan Sisseehitatud Taustapiltide eksport Värskenda Hankige praegused kodu-, luku- ja sisseehitatud taustapildid Luba juurdepääs kõikidele failidele, seda on vaja taustapiltide toomiseks Välise salvestusruumi haldamise loast ei piisa, peate lubama juurdepääsu oma piltidele, tehke kindlasti valik \"Luba kõik\". Lisa failinimele eelseade Lisab pildifaili nimele sufiks koos valitud eelseadistuse Lisage failinimele pildiskaala režiim Lisab pildifaili nimele sufiks valitud kujutise mõõtkava režiimiga Ascii art Teisendage pilt ascii tekstiks, mis näeb välja nagu pilt Parameetrid Mõnel juhul rakendab parema tulemuse saavutamiseks pildile negatiivse filtri Ekraanipildi töötlemine Ekraanipilti ei tehtud, proovige uuesti Salvestamine jäeti vahele %1$s faili jäeti vahele Luba vahelejätmine, kui suurem Mõnel tööriistal on lubatud piltide salvestamine vahele jätta, kui tulemuseks olev faili suurus on originaalist suurem Kalendri sündmus Võtke ühendust Meil Asukoht Telefon Tekst SMS URL Wi-Fi Avatud võrk Ei kehti SSID Telefon Sõnum Aadress Teema Keha Nimi Organisatsioon Pealkiri Telefonid Meilid URL-id Aadressid Kokkuvõte Kirjeldus Asukoht Korraldaja Alguskuupäev Lõppkuupäev Olek Laiuskraad Pikkuskraad Loo vöötkood Redigeeri vöötkoodi Wi-Fi konfiguratsioon Turvalisus Valige kontakt Andke seadetes kontaktidele luba valitud kontakti kasutades automaatseks täitmiseks Kontaktandmed Eesnimi Keskmine nimi Perekonnanimi Hääldus Lisa telefon Lisa email Lisa aadress Veebisait Lisa veebisait Vormindatud nimi Seda pilti kasutatakse vöötkoodi kohale paigutamiseks Koodi kohandamine Seda pilti kasutatakse QR-koodi keskel logona Logo Logo polsterdus Logo suurus Logo nurgad Neljas silm Lisab qr-koodile silmade sümmeetria, lisades alumisse otsanurka neljanda silma Piksli kuju Raami kuju Palli kuju Veaparanduse tase Tume värv Hele värv Hüper OS Xiaomi HyperOS-ile sarnane stiil Maski muster See kood ei pruugi olla skannitav. Muutke välimuse parameetreid, et see oleks kõigi seadmetega loetav Pole skannitav Tööriistad näevad välja nagu avaekraani rakenduste käivitaja, et need oleksid kompaktsemad Käivitusrežiim Täidab ala valitud pintsli ja stiiliga Üleujutuse täitmine Pihusta Joonistab graffity stiilis tee Ruudukujulised osakesed Pihustusosakesed on ringide asemel ruudukujulised Paleti tööriistad Looge pildilt palett põhi-/materjal või importige/eksportige erinevatesse paletivormingutesse Redigeeri paletti Ekspordi/impordi palett erinevates vormingutes Värvi nimi Paleti nimi Paleti formaat Ekspordi loodud palett erinevatesse vormingutesse Lisab praegusele paletile uue värvi %1$s vorming ei toeta paleti nime esitamist Play poe eeskirjade tõttu ei saa seda funktsiooni praegusesse järgmisse kaasata. Sellele funktsioonile juurdepääsemiseks laadige ImageToolbox alla alternatiivsest allikast. GitHubi saadaolevad versioonid leiate allpool. Avage Githubi leht Algne fail asendatakse valitud kausta salvestamise asemel uuega Tuvastati peidetud vesimärgi tekst Tuvastati peidetud vesimärgi kujutis See pilt oli peidetud Generatiivne maalimine Võimaldab eemaldada pildilt objekte AI-mudeli abil, ilma OpenCV-le tuginemata. Selle funktsiooni kasutamiseks laadib rakendus GitHubist alla vajaliku mudeli (~200 MB). Võimaldab eemaldada pildilt objekte AI-mudeli abil, ilma OpenCV-le tuginemata. See võib olla pikaajaline operatsioon Veataseme analüüs Heleduse gradient Keskmine vahemaa Kopeeri liikumise tuvastamine Säilitada Koefitsient Lõikelaua andmed on liiga suured Andmed on kopeerimiseks liiga suured Lihtne kudumise pikseliseerimine Ajastatud pikseliseerimine Ristpiksliseerimine Mikro-makropikseliseerimine Orbitaalne pikseliseerimine Vorteksi pikseliseerimine Impulssvõrgu pikseliseerimine Tuuma pikseliseerimine Radial Weave Pixelization Ei saa avada uri \"%1$s\" Lumesaju režiim Lubatud Piiriraam Glitchi variant Kanali nihe Maksimaalne nihe VHS Blokeeri tõrge Ploki suurus CRT kõverus Kumerus Chroma Piksli sulamine Max Drop AI tööriistad Erinevad tööriistad piltide töötlemiseks AI-mudelite kaudu, näiteks artefaktide eemaldamine või müra summutamine Kompressioon, sakilised jooned Multikad, saate tihendus Üldine kompressioon, üldine müra Värvitu koomiksimüra Kiire, üldine pakkimine, üldine müra, animatsioon/koomiksid/anime Raamatu skaneerimine Särituse korrigeerimine Parim üldise tihendamise, värviliste piltide osas Parimad üldise tihendamise, halltoonides kujutiste puhul Üldine tihendus, halltoonides pildid, tugevam Üldmüra, värvilised pildid Üldine müra, värvilised pildid, paremad detailid Üldine müra, halltoonides pildid Üldine müra, halltoonides pildid, tugevamad Üldine müra, halltoonides pildid, tugevaim Üldine kokkusurumine Üldine kokkusurumine Tekstuurimine, h264 tihendamine VHS tihendus Mittestandardne tihendus (cinepak, msvideo1, roq) Bink kokkusurumine, parem geomeetria Bink kompressioon, tugevam Bink-kompressioon, pehme, säilitab detailid Trepiastme efekti kõrvaldamine, silumine Skaneeritud kunst/joonised, kerge kokkusurumine, muaare Värviriba Aeglane, pooltoone eemaldav Üldine värvimisseade halltoonide/bw piltide jaoks, paremate tulemuste saamiseks kasutage DDColori Serva eemaldamine Eemaldab ületeritamise Aeglane, segane Antialiasing, üldised artefaktid, CGI KDM003 skannib töötlemist Kerge pildiparandusmudel Kompressiooniartefaktide eemaldamine Kompressiooniartefaktide eemaldamine Sideme eemaldamine sujuvate tulemustega Pooltoonmustri töötlemine Mustri eemaldamine V3 JPEG artefaktide eemaldamine V2 H.264 tekstuuri täiustamine VHS-i teravustamine ja täiustamine Ühinemine Tüki suurus Ülekatte suurus Üle %1$s piksli suurused pildid lõigatakse viiludeks ja töödeldakse tükkidena. Need kattuvad, et vältida nähtavaid õmblusi. Suured suurused võivad odavate seadmete puhul põhjustada ebastabiilsust Alustamiseks valige üks Kas soovite %1$s mudeli kustutada? Peate selle uuesti alla laadima Kinnita Mudelid Allalaaditud mudelid Saadaolevad mudelid Ettevalmistus Aktiivne mudel Seansi avamine ebaõnnestus Importida saab ainult .onnx/.ort mudeleid Impordi mudel Importige kohandatud onnxi mudel edasiseks kasutamiseks, aktsepteeritakse ainult onnx/ort mudeleid, toetab peaaegu kõiki esrgani sarnaseid variante Imporditud mudelid Üldine müra, värvilised pildid Üldine müra, värvilised pildid, tugevam Üldmüra, värvilised pildid, tugevaim Vähendab moonutavaid artefakte ja värviribasid, parandades sujuvaid gradiente ja tasaseid värvialasid. Suurendab pildi heledust ja kontrasti tasakaalustatud esiletõstetega, säilitades samal ajal loomulikud värvid. Muudab tumedaid pilte heledamaks, säilitades samal ajal üksikasjad ja vältides ülesäritust. Eemaldab liigse värvitoonuse ning taastab neutraalsema ja loomulikuma värvitasakaalu. Rakendab Poissoni-põhist müra toonimist, keskendudes peente detailide ja tekstuuride säilitamisele. Rakendab pehmet Poissoni müra toonimist sujuvamaks ja vähem agressiivseks visuaalseks tulemuseks. Ühtlane müra toonimine, mis keskendub detailide säilitamisele ja pildi selgusele. Õrn ühtlane müra toniseerimine peene tekstuuri ja sileda välimuse jaoks. Parandab kahjustatud või ebatasased alad, värvides uuesti esemeid ja parandades kujutise ühtlust. Kerge eraldusmudel, mis eemaldab värviribad minimaalse jõudluskuluga. Optimeerib väga suure tihendusartefaktidega pilte (0–20% kvaliteet), et parandada selgust. Täiustab pilte suure tihendusartefaktidega (20–40% kvaliteet), taastades detailid ja vähendades müra. Parandab mõõduka tihendusega pilte (40-60% kvaliteet), tasakaalustades teravust ja sujuvust. Täiustab pilte kerge tihendusega (60–80% kvaliteet), et täiustada peeneid detaile ja tekstuure. Parandab veidi peaaegu kadudeta pilte (80-100% kvaliteet), säilitades samal ajal loomuliku välimuse ja detailid. Lihtne ja kiire värvimine, multikad, pole ideaalne Vähendab veidi pildi hägusust, parandades teravust ilma artefakte lisamata. Pikaajalised operatsioonid Pildi töötlemine Töötlemine Eemaldab rasked JPEG-tihendusartefaktid väga madala kvaliteediga piltidelt (0–20%). Vähendab tugevaid JPEG-artefakte tugevalt tihendatud piltidel (20–40%). Puhastab mõõdukad JPEG-artefaktid, säilitades samal ajal pildi üksikasjad (40–60%). Viimistleb heledaid JPEG-artefakte üsna kõrge kvaliteediga piltides (60–80%). Vähendab peenelt väiksemaid JPEG-artefakte peaaegu kadudeta piltidel (80–100%). Täiustab peeneid detaile ja tekstuure, parandades tajutavat teravust ilma raskete artefaktideta. Töötlemine lõpetatud Töötlemine ebaõnnestus Parandab naha tekstuure ja detaile, säilitades samal ajal loomuliku välimuse, optimeeritud kiiruse jaoks. Eemaldab JPEG-tihendusartefaktid ja taastab tihendatud fotode pildikvaliteedi. Vähendab ISO-müra vähese valgusega fotodel, säilitades üksikasjad. Parandab ülevalgustatud või \"jumbo\" esiletõstmised ja taastab parema toonitasakaalu. Kerge ja kiire värvimismudel, mis lisab halltoonides piltidele loomulikke värve. DEJPEG Denoise Värvige Artefaktid Täiustage Anime Skaneerib Kallis X4 suurendaja üldiste piltide jaoks; väike mudel, mis kasutab vähem GPU-d ja aega, mõõduka hägususe ja müraga. X2 suurendaja üldiste piltide jaoks, säilitades tekstuurid ja loomulikud detailid. X4 suurendaja üldiste piltide jaoks täiustatud tekstuuride ja realistlike tulemustega. Animepiltide jaoks optimeeritud X4 suurendaja; 6 RRDB plokki teravamate joonte ja detailide jaoks. MSE kadudega X4 suurendaja annab sujuvamad tulemused ja vähendab üldiste piltide artefakte. Animepiltide jaoks optimeeritud X4 Upscaler; Teravamate detailide ja sujuvate joontega variant 4B32F. X4 UltraSharp V2 mudel üldiste piltide jaoks; rõhutab teravust ja selgust. X4 UltraSharp V2 Lite; kiirem ja väiksem, säilitab üksikasjad, kasutades vähem GPU mälu. Kerge mudel tausta kiireks eemaldamiseks. Tasakaalustatud jõudlus ja täpsus. Töötab portreede, objektide ja stseenidega. Soovitatav enamiku kasutusjuhtude jaoks. Eemalda BG Horisontaalne piiri paksus Vertikaalse piiri paksus %1$s värvi %1$s värvid Praegune mudel ei toeta tükeldamist, pilti töödeldakse originaalmõõtmetes, see võib põhjustada suurt mälutarbimist ja probleeme madala kvaliteediga seadmetega Tükeldamine on keelatud, pilti töödeldakse esialgsetes mõõtmetes, see võib põhjustada suurt mälutarbimist ja probleeme madala kvaliteediga seadmetega, kuid võib anda paremaid järeldusi Tükeldamine Suure täpsusega piltide segmenteerimise mudel tausta eemaldamiseks U2Neti kerge versioon tausta kiiremaks eemaldamiseks väiksema mälukasutusega. Full DDColor mudel pakub kvaliteetset värvimist üldiste piltide jaoks minimaalsete artefaktidega. Kõigi värvimismudelite parim valik. DDColor Koolitatud ja privaatsed kunstiandmed; annab mitmekesiseid ja kunstipäraseid värvimistulemusi vähemate ebarealistlike värviartefaktidega. Kerge BiRefNet mudel, mis põhineb Swin Transformeril tausta täpseks eemaldamiseks. Kvaliteetne teravate servade ja suurepärase detaili säilivusega taustaeemaldus, eriti keerukate objektide ja keerulise taustaga. Tausta eemaldamise mudel, mis toodab täpseid siledate servadega maske, mis sobivad üldistele objektidele ja mõõduka detaili säilitamiseks. Mudel on juba alla laaditud Mudel edukalt imporditud Tüüp Märksõna Väga kiire Tavaline Aeglane Väga aeglane Arvuta protsentuaalsed osakaalud Minimaalne väärtus on %1$s Pildi moonutamine sõrmedega joonistades lõime Kõvadus Warp režiim Liiguta Kasvama Kahanema Keerake CW Pöörake CCW tuhmumistugevus Ülemine tilk Alumine tilk Käivitage Drop Lõpeta kukkumine Allalaadimine Siledad kujundid Kasutage tavaliste ümarate ristkülikute asemel superellipsi, et saada sujuvamaid ja loomulikumaid kujundeid Kuju tüüp Lõika Ümardatud Sujuv Teravad servad ilma ümardamiseta Klassikalised ümarad nurgad Kujundite tüüp Nurkade suurus Squircle Elegantsed ümarad kasutajaliidese elemendid Failinime vorming Kohandatud tekst, mis asetatakse failinime algusesse, sobib ideaalselt projektinimede, kaubamärkide või isiklike siltide jaoks. Kasutab algset failinime ilma laiendita, mis aitab teil hoida allika identifitseerimist puutumata. Pildi laius pikslites, kasulik eraldusvõime muutuste jälgimiseks või tulemuste skaleerimiseks. Pildi kõrgus pikslites, abiks kuvasuhtega töötamisel või eksportimisel. Genereerib juhuslikud numbrid, et tagada unikaalsed failinimed; lisage rohkem numbreid, et kaitsta end duplikaatide eest. Automaatselt suurenev loendur partiide eksportimiseks, ideaalne mitme pildi salvestamiseks ühe seansi jooksul. Lisab rakendatud eelseadistuse nime failinimesse, et saaksite hõlpsasti meeles pidada, kuidas pilti töödeldakse. Kuvab töötlemise ajal kasutatava pildi skaleerimise režiimi, aidates eristada muudetud suurusega, kärbitud või kohandatud pilte. Failinime lõppu paigutatud kohandatud tekst, mis on kasulik versioonide loomiseks, nagu _v2, _edited või _final. Faililaiend (png, jpg, webp jne), mis vastab automaatselt tegelikule salvestatud vormingule. Kohandatav ajatempel, mis võimaldab teil täiusliku sortimise jaoks määratleda oma vormingu Java spetsifikatsioonide järgi. Paiskamise tüüp Androidi algseade iOS-i stiil Sujuv kõver Kiire peatus Kopsakas Ujuv Kihvt Ultra Smooth Kohanduv Juurdepääsetavus teadlik Vähendatud liikumine Androidi algse kerimisfüüsika võrdluseks Tasakaalustatud, sujuv kerimine üldiseks kasutamiseks Suurem hõõrdumine iOS-i sarnane kerimiskäitumine Ainulaadne splainikõver erilise kerimistunde jaoks Täpne kerimine koos kiire peatamisega Mänguline, tundlik hüppeline kerimisrull Pikad libisevad rullid sisu sirvimiseks Kiire ja tundlik kerimine interaktiivsete kasutajaliideste jaoks Esmaklassiline sujuv kerimine pikendatud hooga Reguleerib füüsikat paiskamiskiiruse alusel Austab süsteemi juurdepääsetavuse sätteid Minimaalne liikumine juurdepääsetavuse vajadustele Põhiliinid Lisab paksema joone igal viiendal real Täitevärv Peidetud tööriistad Jagamiseks peidetud tööriistad Värviteek Sirvige suurt värvikogu Teravustab ja eemaldab piltidelt hägususe, säilitades samal ajal loomulikud detailid, mis on ideaalne fookusest väljas olevate fotode parandamiseks. Taastab nutikalt pildid, mille suurust on varem muudetud, taastades kadunud detailid ja tekstuurid. Optimeeritud reaalajas toimuva sisu jaoks, vähendab tihendusartefakte ja täiustab filmi/telesaadete kaadrite peeneid detaile. Teisendab VHS-kvaliteediga video HD-vormingusse, eemaldades lindimüra ja parandades eraldusvõimet, säilitades samas vanaaegse tunde. Spetsialiseerunud tekstirohkete piltide ja ekraanipiltide jaoks, teravdab tähemärke ja parandab loetavust. Täiustatud ülesskaleerimine, mis on koolitatud erinevatele andmekogumitele, sobib suurepäraselt üldotstarbeliseks fotode täiustamiseks. Optimeeritud veebis kokkusurutud fotode jaoks, eemaldab JPEG-artefaktid ja taastab loomuliku välimuse. Täiustatud versioon veebifotode jaoks parema tekstuuri säilitamise ja artefaktide vähendamisega. 2x ülesskaleerimine Dual Aggregation Transformer tehnoloogiaga, säilitab teravuse ja loomulikud detailid. 3x ülesskaleerimine täiustatud trafoarhitektuuri abil, sobib ideaalselt mõõdukate laiendusvajaduste jaoks. 4x kvaliteetne ülesskaleerimine tipptasemel trafovõrguga, säilitab peened detailid suuremates mõõtkavades. Eemaldab fotodelt hägususe/müra ja värinad. Üldotstarbeline, kuid parim fotodel. Taastab madala kvaliteediga pildid, kasutades Swin2SR-i trafot, mis on optimeeritud BSRGAN-i halvenemise jaoks. Suurepärane raskete kokkusurutud artefaktide kinnitamiseks ja detailide täiustamiseks 4x skaalal. 4x ülesskaleerimine SwinIR-trafoga, mis on koolitatud BSRGANi halvenemise kohta. Kasutab GAN-i teravamate tekstuuride ja loomulikumate detailide saamiseks fotodel ja keerulistes stseenides. Tee Ühendage PDF Ühendage mitu PDF-faili üheks dokumendiks Failide järjestus lk. Poolita PDF Ekstraktige PDF-dokumendist konkreetsed lehed Pöörake PDF-i Parandage lehe orientatsioon jäädavalt Leheküljed PDF-i ümberkorraldamine Lehtede järjestuse muutmiseks pukseerige Hoia ja lohista lehti Lehekülje numbrid Lisage oma dokumentidele automaatselt nummerdamine Sildi vorming PDF tekstiks (OCR) Ekstraktige oma PDF-dokumentidest lihttekst Ülekate kohandatud tekst kaubamärgi või turvalisuse jaoks Allkiri Lisage oma elektrooniline allkiri mis tahes dokumendile Seda kasutatakse allkirjana Avage PDF Eemaldage kaitstud failidest paroolid Kaitske PDF-i Kaitske oma dokumente tugeva krüptimisega Edu PDF on lukustamata, saate seda salvestada või jagada Parandage PDF Proovige parandada rikutud või loetamatud dokumente Halltoonid Teisendage kõik dokumendi manustatud pildid halltoonides Tihendage PDF Lihtsamaks jagamiseks optimeerige oma dokumendi faili suurust ImageToolbox taastab sisemise ristviidetabeli ja taastab failistruktuuri nullist. See võib taastada juurdepääsu paljudele failidele, mida \\"ei saa avada\\" See tööriist teisendab kõik dokumendipildid halltoonides. Parim printimiseks ja faili suuruse vähendamiseks Metaandmed Parema privaatsuse tagamiseks muutke dokumendi atribuute Sildid Tootja Autor Märksõnad Looja Privaatsus sügavpuhastus Kustutage kõik selle dokumendi saadaolevad metaandmed Lehekülg Sügav OCR Ekstraktige dokumendist tekst ja salvestage see Tesseracti mootori abil ühte tekstifaili Kõiki lehti ei saa eemaldada Eemaldage PDF-lehed Eemaldage PDF-dokumendist konkreetsed lehed Puudutage Eemaldamiseks Käsitsi Kärbi PDF-i Kärbi dokumendi lehti suvaliste piirideni Lamendada PDF Muutke PDF-i muutmatuks, rasterdades dokumendi lehekülgi Kaamerat ei saanud käivitada. Kontrollige õigusi ja veenduge, et seda ei kasutaks mõni teine ​​rakendus. Ekstrakti pildid Eraldage PDF-failidesse manustatud pildid nende algse eraldusvõimega See PDF-fail ei sisalda manustatud pilte See tööriist skannib iga lehekülge ja taastab täiskvaliteediga lähtepildid – ideaalne originaalide salvestamiseks dokumentidest Joonista allkiri Pliiatsi parameetrid Kasutage dokumentidele lisatava pildina enda allkirja ZIP PDF Tükeldage dokument etteantud intervalliga ja pakkige uued dokumendid ZIP-arhiivi Intervall Printige PDF Valmistage dokument ette kohandatud leheformaadiga printimiseks Lehekülgi lehel Orienteerumine Lehekülje suurus Marginaali Õitsema Pehme põlv Optimeeritud anime ja koomiksite jaoks. Kiire skaleerimine täiustatud loomulike värvide ja vähemate esemetega Samsung One UI 7 sarnane stiil Soovitud väärtuse arvutamiseks sisestage siia põhilised matemaatilised sümbolid (nt (5+5)*10) Matemaatiline väljend Valige kuni %1$s pilti Hoidke kuupäev ja kellaaeg Säilitage alati kuupäeva ja kellaajaga seotud exif-sildid, töötab sõltumatult exif-i säilitamise võimalusest Taustavärv Alfa-vormingute jaoks Lisab võimaluse määrata taustavärvi igale alfatoega pildivormingule, kui see on keelatud, on see saadaval ainult mittealfavormingute jaoks Avatud projekt Jätkake varem salvestatud Image Toolboxi projekti redigeerimist Image Toolboxi projekti ei saa avada Pildi tööriistakasti projektil puuduvad projekti andmed Pildi tööriistakasti projekt on rikutud Toetamata pilditööriista projekti versioon: %1$d Salvesta projekt Salvestage kihid, taust ja redigeerimisajalugu redigeeritavas projektifailis Avamine ebaõnnestus Kirjutage otsitavasse PDF-i Tuvastage pildikomplektist tekst ja salvestage otsitav PDF koos pildi ja valitava tekstikihiga Alfa kiht Horisontaalne klapp Vertikaalne ümberpööramine Lukk Lisa varju Varju värv Teksti geomeetria Teravama stiliseerimise jaoks venitage või kallutage teksti Skaala X Viltus X Eemalda märkused Eemaldage PDF-lehtedelt valitud märkuste tüübid, nagu lingid, kommentaarid, esiletõstmised, kujundid või vormiväljad Hüperlingid Failide manused Jooned Hüpikaknad Margid Kujundid Tekst Märkused Teksti märgistus Vormi väljad Märgistus Tundmatu Märkused Lahutage rühmitamine Lisage konfigureeritava värvi ja nihkega kihi taha hägune vari ================================================ FILE: core/resources/src/main/res/values-eu/strings.xml ================================================ Tamaina %1$s Zerbait gaizki atera da: %1$s Kargatzen… Irudia handiegia da aurreikusteko, hala ere, gordetzen saiatuko da Aukeratu irudia hasteko Zabalera %1$s Altuera %1$s Kalitatea Luzapena Tamaina-aldaketaren mota Malgua Esplizitua Aukeratu Irudia Aplikazioa ixten Geratu Ziur aplikazioa itxi nahi duzula? Itxi Berrezarri irudia Berrezarri Arbelera kopiatuta Salbuespena Balioak ongi berrezarri dira Irudiaren aldaketak hasierako balioetara itzuliko dira Zerbait gaizki atera da Berrabiarazi aplikazioa Ados Editatu EXIF EXIF daturik ez da aurkitu Gehitu etiketa Gorde Ezabatu EXIF Utzi Irudiaren EXIF datu guztiak ezabatuko dira, ekintza hau ezin da leheneratu! Garbitu Gordetzen Gorde ez diren aldaketa guztiak galduko dira orain irteten bazara Jaso azken eguneraketak, eztabaidatu arazoak eta gehiago Tamaina-aldaketa bakarra Aurredoikuntzak Iturburu-kodea Moztu Aldatu emandako irudi bakarraren zehaztapenak Aukeratu kolorea Aukeratu kolorea iruditik, kopiatu edo partekatu Irudia Kolore Kolorea kopiatu da Moztu irudia edozein mugatara Bertsioa Aldatu aurrebista Eguneratu Ezin da paleta sortu emandako irudiarentzat Lehenetsia Pertsonalizatua Zehaztu gabe Gehienezko tamaina KBtan Aldatu irudi bat KB-tan emandako tamainari jarraituz Konparatu Konparatu emandako bi irudi Aukeratu bi irudi hasteko Aukeratu irudiak Ezarpenak Gaueko modua Iluna Argia Amoled modua Gorria Ezer itsatsi beharrik Ezin da aldatu aplikazioaren kolore-eskema kolore dinamikoak aktibatuta dauden bitartean Aplikazioaren gaia aukeratuko duzun kolorean oinarrituta egongo da Aplikazioari buruz Ez da eguneratzerik aurkitu Arazoen jarraipena Bidali hona akatsen txostenak eta eginbide eskaerak Lagundu itzultzen Zuzendu itzulpen-akatsak edo lokalizatu proiektua beste hizkuntza batera Hirugarren mailakoa Bigarren mailakoa Azalera Balioak Gehitu Baimena Baimena eman Mantendu EXIF Irudiak: %d Kendu Sortu kolore-paleta lagina emandako iruditik Sortu paleta Paleta Bertsio berria %1$s Onartu gabeko mota: %1$s Jatorrizkoa Irteera karpeta Gailuaren biltegiratzea Tamaina aldatu pisuaren arabera Sistema Kolore dinamikoak Pertsonalizazioa Baimendu irudien dirua Gaituta badago, editatzeko irudi bat aukeratzen duzunean, aplikazioaren koloreak hartuko dira irudi honetan Hizkuntza Gainazalen kolorea erabat ilunean ezarriko da gaueko moduan Kolore eskema Berdea Urdina Itsatsi baliozko aRGB-kode. Zure kontsultan ez da ezer aurkitu Bilatu hemen Gaituta badago, aplikazioaren koloreak hartuko dira horma-paperen koloreetarako Ezin izan dira gorde %d irudiak Lehen mailakoa Ertzaren lodiera Aplikazioak zure biltegiratze sarbidea behar du irudiak gordetzeko, beharrezkoa da, hori gabe ezin du funtzionatu, beraz, eman baimena hurrengo elkarrizketa-koadroan Aplikazioak funtziona dezan baimen hau behar du, mesedez, baimendu eskuz Aplikazio hau guztiz doakoa da, baina proiektuaren garapenean lagundu nahi baduzu, hemen klik egin dezakezu FAB lerrokatzea Egiaztatu eguneratzeak Gaituta badago, eguneratzeko elkarrizketa-koadroa erakutsiko zaizu aplikazioa abiarazi ondoren Irudia zooma Aurrizkia Fitxategi izena Partekatu Kanpoko biltegia Moneten koloreak Esposizio Monokromoa Gamma Crosshatch Tartea Lerroaren zabalera Laplazianoa Viñeta Erradioa Eskala Pantaila nagusiko aukeren ordena zehazten du Gehitu iragazkia Iragazkia Zurien balantzea Tinta Opakotasuna Tamaina aldatzeko mugak Zirriborroa Atalasea Toon Gehienezko ezabaketa Bilatu Emojia Pila lausotzea Lausoaren tamaina Lausotu zentroa x Luminantza atalasea Hautatu zein emoji bistaratuko den pantaila nagusian Gehitu fitxategiaren tamaina Gaituta badago, gordetako irudiaren zabalera eta altuera gehitzen zaizkio irteera-fitxategiaren izenari Ezabatu EXIF metadatuak edozein iruditatik Irudiaren aurrebista Galeriako irudi-hautatzaile sinplea, aplikazio hori baduzu bakarrik funtzionatuko du Erabili GetContent-en asmoa irudia hautatzeko, nonahi funtzionatzen du, baina gailu batzuetan aukeratutako irudiak jasotzeko arazoak ere izan ditzake, hori ez da nire errua Aukerak antolatzea Editatu Agindu Ordeztu sekuentzia-zenbakia Gaituta badago, denbora-zigilu estandarra irudiaren sekuentzia-zenbakiarekin ordezkatzen du sorta prozesatzea erabiltzen baduzu Kargatu irudia saretik Kargatu edozein irudi internetetik, ikusi aurrebista, zoomatu eta, halaber, gorde edo editatu nahi baduzu Ez dago irudirik Irudiaren esteka Bete Egokitu Irudi bakoitza Zabalera eta Altuera parametroak emandako irudi batera behartzen du; baliteke aspektu-erlazioa aldatzea Irudiak tamaina aldatzen du Zabalera edo Altuera parametroak emandako alde luzea duten irudietara, tamaina kalkulu guztiak gorde ondoren egingo dira - aspektu-erlazioa mantentzen du Distira Kontrastatu Hue Saturazioa Aplikatu edozein iragazki-katea emandako irudiei Iragazkiak Argia Kolore-iragazkia Alfa Tenperatura Nabarmenak eta itzalak Nabarmenak Itzalak Lainoa Eragina Distantzia Aldapa Zorroztu Sepia Negatiboa Eguzkiratu Bizitasuna Beltza eta zuria Sobel ertza Lausotzea Tonu erdia GCA kolore-espazioa Gauss lausotzea Kutxa lausotzea Aldebiko lausotzea Erliebea Hasi Amaiera Kuwahara leuntzea Distortsioa Angelua Zurrunbiloa Bultza Dilatazioa Esferaren errefrakzioa Errefrakzio-indizea Beirazko esferaren errefrakzioa Kolore-matrizea Aldatu tamaina emandako irudiak emandako zabalera eta altuera mugak aspektu-erlazioa gordetzeko Kuantizazio-mailak Toon leuna Posterizatu Pixelen inklusio ahula Bilaketa 3x3 RGB iragazkia Kolore faltsua Lehenengo kolorea Bigarren kolorea Berrantolatu Lausotze azkarra Lausotu zentroa y Zooma lausotzea Koloreen oreka Ezabatu EXIF Aurreikusi edozein motatako irudiak: GIF, SVG eta abar Irudiaren iturria Argazki-hautatzailea Galeria Fitxategien esploratzailea Pantailaren behealdean agertzen den Android argazki-hautatzaile modernoa, baliteke Android 12+etan bakarrik funtzionatzea eta arazoak ditu EXIF metadatuak jasotzeko. Edukien eskala Emoji kopurua sequenceNum originalFilename Gehitu jatorrizko fitxategi-izena Gaituta badago, jatorrizko fitxategi-izena gehitzen du irteerako irudiaren izenean Jatorrizko fitxategi-izena gehitzeak ez du funtzionatuko argazki-hautatzailearen irudi-iturria hautatuta badago Fitxategiak aplikazioa desgaitu duzu, aktibatu eginbide hau erabiltzeko Marraztu Margotu kolorea Margotu alfa Marraztu irudian Aukeratu irudi bat eta marraztu zerbait gainean Marraztu atzeko planoan Gakoa AES-256, GCM modua, betegarririk gabe, 12 byte ausazko IV. Gakoak SHA-3 hash gisa erabiltzen dira (256 bit). Fitxategiak aurrera egin du Pasahitz baliogabea edo aukeratutako fitxategia ez dago enkriptatuta Emandako zabalera eta altuera duen irudia gordetzen saiatzeak OOM errore bat sor dezake, egin hau zure ardurapean, eta ez esan abisatu ez dudala! Cachea Cachearen tamaina Aurkitu %1$s Cache garbiketa automatikoa Gaituta badago, aplikazioaren cachea garbituko da aplikazioa abiaraztean Tresnak Talde aukerak motaren arabera Taldeko aukerak bere motako pantaila nagusiko zerrenda pertsonalizatuaren ordez Ezin da antolamendua aldatu aukerak taldekatzea gaituta dagoen bitartean Sortu Marraztu irudian zirriborro batean bezala, edo marraztu hondoan bertan Aukeratu hondoko kolorea eta marraztu haren gainean Atzeko planoaren kolorea Zifratzea Zifratu eta deszifratu edozein fitxategi (ez soilik irudia) AES kripto algoritmoan oinarrituta Aukeratu fitxategia Enkriptatzea Deszifratu Hautatu fitxategia hasteko Deszifratzea Enkriptatzea Gorde fitxategi hau zure gailuan edo erabili partekatzeko ekintza nahi duzun lekuan jartzeko Ezaugarriak Ezarpena Bateragarritasuna Fitxategiaren tamaina Fitxategien gehienezko tamaina Android sistema eragileak eta eskuragarri dagoen memoriak mugatzen du, hau da, jakina, zure gailuaren araberakoa. \nKontuan izan: memoria ez da biltegiratzea. Kontuan izan fitxategiak enkriptatzeko beste software edo zerbitzu batzuekin bateragarritasuna ez dagoela bermatuta. Gako-tratamendu edo zifratze-konfigurazio apur bat desberdina bateraezintasunaren arrazoia izan daiteke. Fitxategien pasahitzetan oinarritutako enkriptatzea. Jarraitutako fitxategiak hautatutako direktorioan gorde edo partekatu daitezke. Deszifratutako fitxategiak ere zuzenean ireki daitezke. Atzerako aukera Bigarren mailako pertsonalizazioa %1$s moduan gordetzea ezegonkorra izan daiteke, galerarik gabeko formatua delako Pantaila-argazkia Saltatu Kopiatu Editatu pantaila-argazkia Moztu irudia Tamaina aldatu eta Bihurtu Ustelkeriaren Tamaina Indarra Eskuila leuntasuna Marrazketa modua Berreskuratu Orientazioa & Script-en hautematea soilik Orientazio automatikoa & Script-en hautematea Autoa soilik Bloke bakarreko testu bertikala Bloke bakarra Lerro bakarra Char bakarra Testu urria Testu eskasa Orientazioa & Gidoien detekzioa Lerro gordina Akatsa Zenbatekoa Hazia Anaglifoa Zarata Pixel sorta Nahastu Gailurra Kolore Anomalia Ez da aurkitu \"%1$s\" direktoriorik, lehenetsitako batera aldatu dugu, mesedez gorde fitxategia berriro Arbela Pin automatikoa Dardara Bibrazio-indarra Gainidatzi fitxategiak Jatorrizko fitxategia beste batekin ordezkatuko da hautatutako karpetan gorde beharrean, aukera honek irudiaren iturburua \"Explorer\" edo GetContent izan behar du, hau aldatzean, automatikoki ezarriko da. Hutsik Atzizkia Doan Posta elektronikoa Hemen aurrez ezarritakoak irteerako fitxategiaren % zehazten du, hau da, 5 MBko irudian 50 aurrez ezarritakoa hautatzen baduzu, 2,5 MBko irudia lortuko duzu gorde ondoren. Ausazko fitxategi-izena Gaituta badago irteerako fitxategi-izena guztiz ausazkoa izango da %1$s karpetan gorde da %2$s izenarekin %1$s karpetan gorde da Telegram txata Eztabaidatu aplikazioa eta jaso beste erabiltzaileen iritzia. Hemen ere lor ditzakezu beta eguneraketak eta estatistikak. Moztu maskara Aspektu-erlazioa Erabili maskara mota hau emandako iruditik maskara sortzeko, ohartu alfa kanala izan BEHAR DUela Babeskopia egin eta leheneratu Babeskopia Ezarpenak behar bezala leheneratu dira Berrezarri aplikazioaren ezarpenak aurrez sortutako fitxategitik Fitxategi hondatua edo babeskopia ez Jarri nirekin harremanetan Honek zure ezarpenak balio lehenetsietara itzuliko ditu. Kontuan izan hau ezin dela desegin goian aipatutako babeskopia fitxategirik gabe. Ezabatu Hautatutako kolore-eskema ezabatzera zoaz. Eragiketa hau ezin da desegin Ezabatu eskema Letra-tipoa Testua Letra-tipoen eskala Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Ññ Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz 0123456789 !? Lehenetsia Emozioak Letra-tipo-eskala handiak erabiltzeak UI akatsak eta arazoak sor ditzake, eta horiek ez dira konponduko. Erabili kontu handiz. Janaria eta edaria Natura eta Animaliak Jarduerak Atzeko planoa kentzeko Kendu atzeko planoa iruditik marraztuz edo erabili Auto aukera Objektuak Sinboloak Gaitu emojiak Bidaiak eta Lekuak Irudiaren inguruko espazio gardenak moztuko dira Ezabatu automatikoki atzeko planoa Berreskuratu irudia Ezabatu modua Ezabatu atzeko planoa Leheneratu atzeko planoa Lausotze erradioa Pipeta Sortu alea Aupa… Arazoren bat izan da. Beheko aukerak erabiliz idatz diezadazu eta irtenbidea bilatzen saiatuko naiz Emandako irudien tamaina aldatu edo beste formatu batzuetara bihurtu. EXIF metadatuak hemen ere edita daitezke irudi bakarra hautatuz gero. Horri esker, aplikazioak hutsegiteen txostenak eskuz bil ditzake Analitika Baimendu aplikazioen erabilera-estatistika anonimoak biltzea Une honetan, %1$s formatuak EXIF metadatuak irakurtzeko aukera ematen du Android-en. Irteerako irudiak ez du metadaturik izango gordetzean. %1$s balioak konpresio azkarra esan nahi du, eta horren ondorioz fitxategiaren tamaina handi samarra da. %2$s-k konpresio motelagoa dela esan nahi du, eta horren ondorioz fitxategi txikiagoa izango da. Itxaron Gorde ia amaituta. Orain bertan behera uzteko, berriro gorde beharko da. Eguneraketak Baimendu beta-ak Eguneratze-egiaztapenak aplikazioaren beta bertsioak barne hartuko ditu gaituta badago Gaituta badago marrazteko bidea gezi adierazgarri gisa irudikatuko da Irudiak erdian moztuko dira sartutako tamainara. Mihisea atzeko planoko kolorearekin zabalduko da irudia sartutako neurriak baino txikiagoa bada. Irudien Orientazioa Horizontala Bertikala Eskalatu irudi txikiak handietara Irudi txikiak sekuentziako handienera eskalatuko dira gaituta badago Irudien ordena Erregularra Lausotu ertzak Gaituta badago, jatorrizko irudiaren azpian ertz lausoak marrazten ditu bere inguruko espazioak betetzeko, kolore bakarreko ordez Pixelazioa Pixelazio hobetua Trazuaren pixelazioa Diamante pixelazio hobetua Diamante pixelazioa Zirkuluaren pixelazioa Zirkuluen pixelazio hobetua Ordeztu Kolorea Kendu beharreko kolorea Kendu kolorea Birkodetzea Paleta estiloa Tonal Spot Neutroa Bizia Adierazkorra Ortzadarra Fruitu entsalada Fideltasuna Edukia Paleta estilo lehenetsia, lau koloreak pertsonalizatzeko aukera ematen du, beste batzuek gako kolorea soilik ezartzeko aukera ematen dute Monokromoa baino apur bat kromatikoagoa den estiloa Gai ozena, koloretsutasuna maximoa da Lehen mailako paletarentzat, besteentzat areagotua Gai dibertigarria - iturriko kolorearen ñabardura ez da gaian agertzen Gai monokromoa, koloreak beltza / zuria / grisa dira Iturburu-kolorea Scheme.primaryContainer-en jartzen duen eskema Edukien eskemaren oso antzekoa den eskema Eguneratze-zuzentzaile hau GitHub-era konektatuko da eguneratze berririk eskuragarri dagoen egiaztatzeko Biak Bilatu Pantaila nagusian eskuragarri dauden aukera guztiak bilatzeko gaitasuna ematen du Aurreikusi PDFa PDF irudietara Irudiak PDFra Bihurtu PDF irudietara irteerako formatuan Pakeatu emandako irudiak irteerako PDF fitxategian Maskara-iragazkia Aplikatu iragazki-kateak estalitako eremu jakin batzuetan, maskara-eremu bakoitzak bere iragazki-multzoa zehaztu dezake Maskarak Gehitu maskara Maskara %d Maskararen kolorea Maskararen aurrebista Marraztutako iragazki-maskara errendatuko da gutxi gorabeherako emaitza erakusteko Hautatutako iragazki-maskara ezabatzera zoaz. Eragiketa hau ezin da desegin Ezabatu maskara Zentroa Amaiera Aldaera sinpleak Nabarmendutzailea Neoia Boligrafoa Pribatutasuna lausotzea Gehitu efektu distiratsu batzuk zure marrazkiei Lehenetsia, sinpleena - kolorea besterik ez Pribatutasun-lausotzearen antzekoa, baina pixelatu egiten da lausotu beharrean Botoiak %1$s - %2$s barrutiko balioa Biratu automatikoa Irudiaren orientaziorako muga-koadroa hartzeko aukera ematen du Bide jakin batetik gezi bikoitza marrazten du Azaldutako Rect Obalatua Zuzen Zuzena marrazten du hasierako puntutik amaierako puntura Automatikoki gehitzen du gordetako irudia arbelean gaituta badago Fitxategiak gainidazteko \"Explorer\" irudi-iturburua erabili behar duzu, saiatu irudiak berrikusten, irudi-iturburua behar den batera aldatu dugu. Hurbilena Spline Oinarrizkoa Kontrol-puntu multzo bat leunki interpolatzeko eta berriro lagintzeko metodoa, ordenagailu grafikoetan erabili ohi den kurba leunak sortzeko Leiho-funtzioa sarritan erabiltzen da seinaleen prozesamenduan, ihes espektrala minimizatzeko eta maiztasun-analisiaren zehaztasuna hobetzeko seinale baten ertzak txikituz. Eskuragarri dauden hizkuntzak Segmentazio modua Gainidatzitako fitxategia %1$s izena duen jatorrizko helmugan Lupa Irisgarritasun hobea lortzeko, hatzaren goiko aldean lupa gaitzen du marrazketa moduetan Indartu hasierako balioa Exif widget-a hasiera batean egiaztatzea behartzen du Aktibatu/Sakatu Aplikazio hau guztiz doakoa da, handiagoa izan nahi baduzu, izarra ezazu proiektua Github-en 😄 Autoa Zutabe bakarra Hitz bakarra Hitz biribila Hizkuntza \"%1$s\" OCR prestakuntza-datuak ezabatu nahi dituzu aintzatespen-mota guztietarako, edo hautatutako (%2$s) soilik? Oraingoa Gehitu kolorea Propietateak Ur-marka Estali argazkiak testu/irudi pertsonalizagarriekin Errepikatu ur-marka Ur-marka errepikatzen du irudiaren gainean, bakarren ordez, emandako posizioan Erabili Lehen fotogramaren tamaina Ordeztu zehaztutako tamaina lehen markoaren neurriekin Errepikatu zenbaketa Fotograma atzerapena milis FPS Lau Bider Lau Dithering Bi ilaratako Sierra dithering Sierra Lite dithering Atkinson dithering False Floyd Steinberg dithering Lausotze mediana B Spline Zatika definitutako polinomio bikubiko funtzioak erabiltzen ditu kurba edo gainazal bat, formaren irudikapen malgua eta jarraitua leunki interpolatzeko eta hurbiltzeko. Glitch hobetua Channel Shift X Ustelkeria txanda X Ustelkeria txanda Y Channel Shift Y Karpa Blur Side Fade Aldea Goiena Behean Beira fraktala X anplitudea Y anplitudea Perlin Distortsioa Kolore Matrizea 4x4 Kolore Matrizea 3x3 Efektu sinpleak Polaroid Tritonomalia Deutaromalia Protonomalia Vintagea Browni Coda Chrome Gaueko Ikusmena Epela Cool Tritanopia Alea Zorrotzgabea Pastel Laranja Lainoa Amets Arrosa Urrezko Ordua Uda beroa Laino morea Egunsentia Cyberpunk Limonada Argia Su espektrala Gau Magia Eguzki Berdea Ortzadarraren Mundua Drago Aldridge Moztu Uchimura Mobius Trantsizioa Jatorrizko helmugan gainidatzitako irudiak Ezin da irudi formatua aldatu fitxategiak gainidatzi aukera gaituta dagoen bitartean Emoji kolore eskema gisa Emoji kolore nagusia erabiltzen du aplikazioaren kolore-eskema gisa, eskuz definitutako baten ordez Egin zure aplikazioaren ezarpenen babeskopia fitxategi batean Esfortzua Marraztu Geziak Aukeratu gutxienez 2 irudi Tolerantzia Ordezkatzeko kolorea Helburu-kolorea Higatu Difusio anisotropikoa Zabalkundea Eroapena Haizearen mailakatu horizontala Aldebiko lausotze azkarra Poisson Blur Tonu logaritmikoak mapatzea Kristalizatu Trazuaren kolorea Anplitudea Marmola Turbulentzia Olioa Ur Efektua Tamaina X maiztasuna Y maiztasuna Hable Filmic Tone Mapping Hejl Burgess Tone Mapping ACES tonu filmikoaren mapak ACES Hill Tone Mapping Denak Iragazki osoa Hasi Aplikatu edozein iragazki-kateak emandako irudiei edo irudi bakarrei PDF tresnak Funtzionatu PDF fitxategiekin: Aurreikusi, Bihurtu irudi sorta batean edo sortu argazkien bat PDF aurrebista sinplea Gradient Maker Sortu irteera-tamainaren gradientea kolore eta itxura mota pertsonalizatuekin Abiadura Lainoa kendu Omega Tarifa aplikazioa Tarifa Deutaronotopia Protanopia Akromatomalia Akromatopsia Lineala Erradiala Ekorketa Gradiente mota X zentroa Y zentroa Fitxa modua Errepikatua Ispilua Pintza Decala Kolore Geldialdiak Marraztu bidea modua Lerro bikoitzeko gezia Marrazki Librea Gezi bikoitza Line Arrow Gezia Lerroa Bidea marrazten du sarrerako balio gisa Hasierako puntutik amaierarako bidea marrazten du lerro gisa Hasierako puntutik amaierako puntura marrazten duen gezia zuzena marrazten du Bide jakin batetik gezi zorrotzak marrazten ditu Hasierako puntutik amaierarako gezi bikoitza marrazten du lerro gisa Delineatutako Obalatua Hasiera-puntutik amaiera-puntura obalatua marrazten du Obalatua marrazten du hasierako puntutik amaieraraino Zuzen marrazten du hasierako puntutik amaierako puntura Dithering Quantizier Grisen Eskala Bayer bi teo Dithering Bayer Hiruz Hiru Dithering Bayer Eight By Eight Dithering Floyd Steinberg dithering Jarvis Judice Ninke Dithering Sierra Dithering Stucki Dithering Burkes Dithering Ezker-eskuin dithering Ausazko dithering Atalasearen dithering sinplea Eskala modua Bilineala Hann Ermita Lanczos Mitchell Balio lehenetsia Sigma Sigma espaziala Catmull Bikubikoa Interpolazio lineala (edo bilineala, bi dimentsiotan) normalean ona da irudi baten tamaina aldatzeko, baina xehetasunak nahiko ez diren leuntzea eragiten du eta, hala ere, apur bat apur bat izan daiteke. Eskalatze-metodo hobeak Lanczos birlaginketa eta Mitchell-Netravali iragazkiak dira Tamaina handitzeko modu errazenetako bat, pixel bakoitza kolore bereko pixel batzuekin ordezkatuz Ia aplikazio guztietan erabiltzen den Android eskalatze modurik sinpleena Kurba-segmentu baten amaierako puntuetan balioak eta deribatuak erabiltzen dituen interpolazio matematikoko teknika kurba leun eta jarraitua sortzeko Pixel balioei sinc funtzio haztatua aplikatuz kalitate handiko interpolazioa mantentzen duen birlaginketa metodoa Parametro doigarriekin konboluzio-iragazkia erabiltzen duen birlaginketa metodoa, eskalatutako irudian zorroztasunaren eta antialiasing-aren arteko oreka lortzeko. Zatika definitutako polinomio-funtzioak erabiltzen ditu kurba edo gainazal bat, forma-errepresentazio malgua eta jarraitua leunki interpolatzeko eta hurbiltzeko. Clip bakarrik Ez da biltegian gordeko, eta irudia arbelean bakarrik jartzen saiatuko da Hautatutako forma duen edukiontzia gehitzen du txartelen ikono nagusien azpian Ikonoaren forma Irudien jostura Konbinatu emandako irudiak handi bat lortzeko Distira betearaztea Pantaila Gradientearen gainjartzea Konposatu emandako irudiaren goialdeko edozein gradiente Eraldaketak Jatorrizko irudiaren metadatuak gordeko dira Kamera Kamera erabiltzen du argazkiak ateratzeko. Kontuan izan irudi-iturburu honetatik irudi bakarra atera daitekeela Zurrunbilo koloretsua Udaberriko Argia Udazkeneko Tonuak Izpiliku Ametsa Fantasiazko Paisaia Kolore leherketa Gradiente elektrikoa Caramel Iluntasuna Gradiente futurista More iluna Espazio Ataria Zurrunbilo Gorria Kode Digitala Desplazamendua X Desplazamendua Y Ur-marka mota Irudi hau ur-markak egiteko eredu gisa erabiliko da Testuaren kolorea Gainjartze modua Pixel Tamaina Blokeatu marrazkiaren orientazioa Kolore kopurua gehienez Marrazketa moduan gaituta badago, pantaila ez da biratuko Bokeh GIF tresnak Bihurtu irudiak GIF irudira edo atera fotogramak emandako GIF iruditik GIF irudietara Bihurtu GIF fitxategia argazki sorta batean Bihurtu irudi sorta GIF fitxategira Irudiak GIF-era Hautatu GIF irudia hasteko Erabili Lazoa Lassoa erabiltzen du marrazketa moduan bezala ezabatzeko Jatorrizko irudiaren aurrebista Alpha Aplikazio-barrako emojiak ausaz aldatuko dira etengabe hautatutakoa erabili beharrean Ausazko emojiak Ezin da erabili ausazko emojiak hautatzea emojiak desgaituta dauden bitartean Ezin da emojirik hautatu ausazko bat gaituta dagoen bitartean Egiaztatu eguneratzeak Aurrez ezarritako 125a aukeratu baduzu, irudia jatorrizko irudiaren % 125eko tamainan gordeko da, % 100eko kalitatearekin. Aurrez ezarritako 50 aukeratzen baduzu, irudia% 50eko tamainarekin eta% 50eko kalitatearekin gordeko da. Telebista Zaharra Nahastu Lausotzea OCR (testua ezagutu) Emandako irudiaren testua ezagutu, 120 hizkuntza baino gehiago onartzen dira Irudiak ez du testurik edo aplikazioak ez du aurkitu Accuracy: %1$s Aitorpen Mota Azkar Estandarra Onena Ez dago daturik Tesseract OCR prestakuntza-datu osagarriak (%1$s) behar bezala funtzionatzeko zure gailura deskargatu behar dira. \n%2$s datuak deskargatu nahi dituzu? Deskargatu Ez dago konexiorik, egiaztatu eta saiatu berriro tren-ereduak deskargatzeko Deskargatutako hizkuntzak Pintzelak atzeko planoa berreskuratuko du ezabatu beharrean Sare horizontala Sare bertikala Puntu modua Errenkadak zenbatzen Zutabeen zenbaketa Erabili Pixel Switch Pixel-itxurako etengailua erabiliko da zuk oinarritutako Google-ren materialaren ordez Diapositiba Alboz Albo Gardentasuna Onartu hainbat hizkuntza Gogokoena Ez dago gogoko iragazkirik gehitu oraindik Native Stack Blur Tilt Shift Alderantzizko betetze mota Gaituta badago, maskaratuta ez dauden eremu guztiak iragaziko dira portaera lehenetsiaren ordez Konfetiak Konfetiak gordetzeko, partekatzeko eta beste lehen ekintzetan erakutsiko dira Modu segurua Irteeran edukia ezkutatzen du; gainera, pantaila ezin da harrapatu edo grabatu Dohaintza Irteerako irudien eskala Desagertzen diren ertzak Desgaituta Marraztu erdi-gardenak zorroztutako argitzaile-bideak Marraztutako bidearen azpian irudia lausotzen du ezkutatu nahi duzun guztia ziurtatzeko Ontziak Edukiontzien atzean itzalen marrazketa gaitzen du Graduatzaileak Etengailuak FABak Irristagailuen atzean itzalen marrazketa gaitzen du Etengailuen atzean itzalen marrazketa gaitzen du Itzalen marrazketa gaitzen du ekintza-botoien atzean Itzalen marrazketa gaitzen du lehenetsitako botoien atzean Aplikazioen barrak Aplikazioen barren atzean itzalen marrazketa gaitzen du Arreta Alderantzikatu Koloreak Gaiaren koloreak negatiboekin ordezkatzen ditu gaituta badago Irten Aurrebista orain uzten baduzu, irudiak berriro gehitu beharko dituzu Lazoa Bide itxia marrazten du emandako bidearen arabera Irudi formatua Kolore Ilunak Gaueko moduaren kolore eskema erabiltzen du argiaren aldaeraren ordez Kopiatu Jetpack Compose kodea Material You paleta sortzen du iruditik Eraztunaren Lausotzea Gurutze Lausoa Zirkuluaren Lausotzea Izar Lausoa Aldaketa Lineala Etiketak Kentzeko APNG tresnak Irudiei APNG Mugimendu lausotzea Bihurtu irudiak APNG irudira edo atera fotogramak emandako APNG iruditik Bihurtu APNG fitxategia argazki sorta batean Bihurtu irudi sorta APNG fitxategira Irudiak APNGra Hautatu APNG irudia hasteko Zip Sortu Zip fitxategia emandako fitxategi edo irudietatik Arrastatu heldulekuaren zabalera Konfeti mota Jaia Lehertu Euria Txokoak JXL tresnak Egin JXL ~ JPEG transkodeketa kalitate-galerarik gabe edo bihurtu GIF/APNG JXL animaziora JXLtik JPEGra Egin galerarik gabeko transkodeketa JXLtik JPEGra Egin galerarik gabeko transkodeketa JPEGtik JXLra JPEGtik JXLra Hautatu JXL irudia hasteko Gauss lausotze azkarra 2D Gaussian Blur azkarra 3D Gaussian Blur azkarra 4D Autoa Aste Santua Arbeleko datuak automatikoki itsatsi ditzake aplikazioak, beraz, pantaila nagusian agertuko da eta prozesatu ahal izango dituzu Harmonizazio Kolorea Harmonizazio Maila Lanczos Bessel Pixel balioei Bessel (jinc) funtzioa aplikatuz kalitate handiko interpolazioa mantentzen duen birlaginketa metodoa GIFetik JXLra Bihurtu GIF irudiak JXL irudi animatuetara APNGtik JXLra Bihurtu APNG irudiak JXL irudi animatuetara JXL Irudietara Bihurtu JXL animazioa argazki sorta batean Irudiak JXLra Bihurtu argazki sorta JXL animaziora Portaera Saltatu fitxategien hautaketa Fitxategi-hautatzailea berehala erakutsiko da aukeratutako pantailan Sortu Aurrebistak Aurrebista sortzea gaitzen du; honek gailu batzuetan hutsegiterik ez izateko lagungarria izan daiteke; honek edizio-funtzio batzuk ere desgaitzen ditu edizio bakarreko aukeraren barruan. Konpresio galdua Konpresio galera erabiltzen du fitxategiaren tamaina murrizteko, galerarik gabekoa izan beharrean Konpresio Mota Ondorioz irudiak deskodetzeko abiadura kontrolatzen du. Honek ondoriozko irudia azkarrago irekitzen lagundu beharko luke, %1$s balioak deskodetze motelena esan nahi du, eta %2$s - azkarrena, ezarpen honek irteerako irudiaren tamaina handitu dezake. Sailkatzea Data Data (alderantziztuta) Izena Izena (alderantziztuta) Kanalen konfigurazioa Gaur Atzo Kapsulatutako hautatzailea Image Toolbox-en irudi-hautatzailea Ez dago baimenik Eskaera Aukeratu hainbat euskarri Aukeratu euskarri bakarra Aukeratu Saiatu berriro Erakutsi ezarpenak Paisaian Hau desgaituta badago, paisaia moduan ezarpenak beti bezala aplikazioaren goiko barrako botoian irekiko dira, betirako ikusgai dagoen aukeraren ordez. Pantaila osoko ezarpenak Gaitu eta ezarpenen orria pantaila osoko moduan irekiko da beti, tiradera-orri irristagarriaren ordez Aldatu mota Konposatu Jetpack Konposatzen duzun materiala Aldatzen duzun materiala Max Aingura tamaina aldatu Pixela Arina \"Fluent \" diseinu sisteman oinarritutako etengailua Cupertino \"Cupertino \" diseinu sisteman oinarritutako etengailua Irudiak SVGra Jarraitu emandako irudiak SVG irudietara Erabili Sampled Paleta Kuantizazio-paleta lagintuko da aukera hau gaituta badago Bidea Utzi Ez da gomendagarria irudi handiak trazatzeko tresna hau txikiagotu gabe erabiltzea, huts egin dezake eta prozesatzeko denbora handitu dezake. Irudia txikiagotu Irudia dimentsio txikiagoetara murriztuko da prozesatu aurretik, honek tresna azkarrago eta seguruago lan egiten laguntzen du Gutxieneko kolore-erlazioa Lerroen Atalasea Atalase koadratikoa Koordenatuak biribiltzeko tolerantzia Bide Eskala Berrezarri propietateak Propietate guztiak balio lehenetsiekin ezarriko dira, konturatu ekintza hau ezin dela desegin Xehetasuna Lerro-zabalera lehenetsia Motor modua Ondarea LSTM sarea Legacy & LSTM Bihurtu Bihurtu irudi sortak emandako formatura Gehitu Karpeta Berria Lagin bakoitzeko bits Konpresioa Interpretazio fotometrikoa Pixel bakoitzeko laginak Konfigurazio planoa Y Cb Cr Azpi-laginketa Y Cb Cr Posizionamendua X Ebazpena Y Ebazpena Ebazpen Unitatea Strip Offsets Tira bakoitzeko errenkadak Strip Byte-kopuruak JPEG truke formatua JPEG Truke formatuaren luzera Transferentzia Funtzioa Puntu Zuria Lehen mailako kromatikoak Y Cb Cr Koefizienteak Erreferentzia Zuri Beltza Data Ordua Irudiaren deskribapena Egin Eredua Softwarea Artista Copyright Exif bertsioa Flashpix bertsioa Kolore-espazioa Gamma Pixel X Dimentsioa Pixel Y Dimentsioa Pixel bakoitzeko bit konprimituak Maker Oharra Erabiltzaileen iruzkina Erlazionatutako Soinu Fitxategia Data Ordua Jatorrizkoa Data Ordua Digitalizatuta Desplazamendu-denbora Desplazamendu-denbora jatorrizkoa Desplazamendu-denbora digitalizatua Azpi Seg Denbora Sub Seg Denbora Jatorrizkoa Sub Seg Denbora digitalizatua Esposizio-denbora F Zenbakia Esposizio Programa Sentsibilitate Espektrala Argazki-sentsibilitatea Oecf Sentikortasun mota Irteera estandarraren sentikortasuna Gomendatutako Esposizio Indizea ISO abiadura ISO Abiadura Latitudea yyy ISO Abiadura Latitudea zzz Obturadorearen abiaduraren balioa Irekiduraren balioa Distira-balioa Esposizio-alborapenaren balioa Gehienezko irekiera-balioa Gaiaren distantzia Neurketa modua Flasha Gai Arloa Fokua Flash Energia Maiztasun Erantzun Espaziala Foku Plano X Ebazpena Foku Plano Y Ebazpena Foku Plano Ebazpen Unitatea Gaiaren kokapena Esposizio-indizea Sentsazio metodoa Fitxategiaren iturria CFA eredua Pertsonalizatutako errendazioa Esposizio modua Zurien Balantzea Zoom digitalaren ratioa Foku-luzera 35 mm-ko pelikulan Eszena Harrapaketa Mota Irabazi Kontrola Kontrastea Saturazioa Zorroztasuna Gailuaren ezarpenaren deskribapena Gaiaren Distantzia Barrutia Irudia ID bakarra Kameraren jabearen izena Gorputzaren serie zenbakia Lentearen zehaztapena Lens Make Lente eredua Lentearen serie zenbakia GPS bertsioaren IDa GPS Latitude Erref GPS Latitudea GPS Luzera Erref GPSaren luzera GPS Altuera Erref GPS Altuera GPS denbora-zigilua GPS sateliteak GPS egoera GPS neurketa modua GPS DOP GPS Abiadura Erref GPS Abiadura GPS Track Erref GPS Track GPS irudiaren norabidea Erref GPS irudiaren norabidea GPS maparen datuak GPS Dest Latitude Erref GPS Dest Latitude GPS Dest Luzera Erref GPS Dest Luzera GPS Dest Bearing Erref GPS Dest Bearing GPS Dest distantzia Erref GPS Dest Distantzia GPS prozesatzeko metodoa GPS eremuaren informazioa GPS data zigilua GPS diferentziala GPS H kokapen-errorea Elkarreragingarritasun Indizea DNG bertsioa Mozketaren tamaina lehenetsia Aurrebista Irudia Hasi Aurrebista irudiaren luzera Aspektu Markoa Sentsorearen beheko ertza Sentsorearen ezkerreko ertza Sentsorearen eskuineko ertza Sentsorearen goiko ertza ISO Marraztu testua bideko letra-tipoarekin eta kolorearekin Letra-tamaina Ur-markaren tamaina Errepikatu testua Uneko testua errepikatuko da bidea amaitu arte, behin marraztu beharrean Marratxoaren tamaina Erabili hautatutako irudia emandako bidetik marrazteko Irudi hau marraztutako bidearen sarrera errepikakor gisa erabiliko da Triangelu eskematua marrazten du hasierako puntutik amaierako puntura Triangelu eskematua marrazten du hasierako puntutik amaierako puntura Triangelu eskematua Triangelua Hasierako puntutik amaierako poligonoa marrazten du Poligonoa Deskribatutako poligonoa Aztertutako poligonoa marrazten du hasierako puntutik amaieraraino Erpinak Marraztu poligono erregularra Marraztu poligonoa forma askearen ordez erregularra izango dena Izarra hasierako puntutik amaierara marrazten du Izarra Izarra deskribatua Marraztutako izarra hasierako puntutik amaierako puntura marrazten du Barne Erradio Erlazioa Marraztu izar erregularra Marraztu forma askearen ordez erregularra izango den izarra Antialiak Antialiasing gaitzen du ertz zorrotzak saihesteko Ireki Editatu Aurrebistaren ordez Irudia irekitzeko (aurrebista) hautatzen duzunean ImageToolbox-en, editatu hautapen orria irekiko da aurrebistaren ordez Dokumentuen eskanerra Eskaneatu dokumentuak eta sortu PDF edo bereizi irudiak haietatik Egin klik eskaneatzen hasteko Hasi eskaneatzen Gorde Pdf gisa Partekatu PDF gisa Beheko aukerak irudiak gordetzeko dira, ez PDF Berdindu histograma HSV Histograma berdindu Sartu ehunekoa Baimendu testu-eremuaren bidez sartzeko Aurrez ezarritako hautapenaren atzean dagoen Testu-eremua gaitzen du, berehala sartzeko Eskala kolore-espazioa Lineala Berdindu histograma pixelazioa Sarearen tamaina X Sarearen tamaina Y Histograma Egokigarria berdindu Histograma Egokitzeko LUV berdindu Histograma berdindu LAB moldatzailea CLAHE CLAHE LAB CLAHE LUV Moztu edukira Markoaren Kolorea Ez ikusi egin beharreko kolorea Txantiloia Ez da txantiloi-iragazkirik gehitu Sortu Berria Eskaneatutako QR kodea ez da baliozko iragazki txantiloia Eskaneatu QR kodea Hautatutako fitxategiak ez du iragazki txantiloiaren daturik Sortu txantiloia Txantiloiaren izena Irudi hau iragazki txantiloi honen aurrebista egiteko erabiliko da Txantiloi-iragazkia QR kodearen irudi gisa Fitxategi gisa Gorde fitxategi gisa Gorde QR kodearen irudi gisa Ezabatu txantiloia Hautatutako txantiloi-iragazkia ezabatzera zoaz. Eragiketa hau ezin da desegin \"%1$s \" izena duen iragazki txantiloia gehitu da (%2$s) Iragaziaren aurrebista QR eta barra-kodea Eskaneatu QR kodea eta lortu bere edukia edo itsatsi zure katea berria sortzeko Kode Edukia Eskaneatu edozein barra-kode eremuko edukia ordezkatzeko, edo idatzi zerbait barra-kode berria sortzeko hautatutako motarekin QR deskribapena Min Eman kamerari baimena ezarpenetan QR kodea eskaneatzeko Eman kamerari baimena dokumentuen eskanerra eskaneatzeko ezarpenetan Kubikoa B-Spline Hamming Hanning Blackman Welch Kuadrikoa Gauss Esfingea Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-He Kutxa Bohman Lantxoak 2 Lantxoak 3 Lantxoak 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Interpolazio kubikoak eskala leunagoa eskaintzen du hurbilen dauden 16 pixelak kontuan hartuta, bilineala baino emaitza hobeak emanez. Zatika definitutako polinomio-funtzioak erabiltzen ditu kurba edo gainazal bat, forma-errepresentazio malgua eta jarraitua leunki interpolatzeko eta hurbiltzeko. Seinale baten ertzak txikituz ihes espektrala murrizteko erabiltzen den leiho-funtzioa, seinalea prozesatzeko erabilgarria. Hann leihoaren aldaera bat, seinaleak prozesatzeko aplikazioetan ihes espektrala murrizteko erabili ohi dena Maiztasun-bereizmen ona eskaintzen duen leiho-funtzioa, ihes espektrala gutxituz, sarritan seinalea prozesatzeko erabiltzen dena Maiztasun-bereizmen ona emateko diseinatutako leiho-funtzioa, ihes espektral murriztuarekin, sarritan seinalea prozesatzeko aplikazioetan erabilia Interpolaziorako funtzio koadratikoa erabiltzen duen metodoa, emaitza leun eta jarraituak ematen dituena Gauss funtzio bat aplikatzen duen interpolazio-metodoa, irudietan zarata leuntzeko eta murrizteko erabilgarria Birlaginketa metodo aurreratua kalitate handiko interpolazioa eskaintzen duen artefaktu minimoekin Seinalearen prozesamenduan erabiltzen den leiho triangeluar funtzioa ihes espektrala murrizteko Irudi naturalaren tamaina aldatzeko optimizatutako kalitate handiko interpolazio metodoa, zorroztasuna eta leuntasuna orekatuz Robidoux metodoaren aldaera zorrotzagoa, irudi kurruskaria aldatzeko optimizatua Splinen oinarritutako interpolazio-metodoa, 16 sakatze-iragazkia erabiliz emaitza leunak ematen dituena Splinen oinarritutako interpolazio-metodoa, 36 sakatze-iragazkia erabiliz emaitza leunak ematen dituena Splinen oinarritutako interpolazio-metodoa, 64 sakatze-iragazkia erabiliz emaitza leunak ematen dituena Kaiser leihoa erabiltzen duen interpolazio-metodoa, lobulu nagusiaren zabaleraren eta alboko lobuluen mailaren arteko trukearen kontrol ona eskaintzen duena. Bartlett eta Hann leihoak konbinatzen dituen leiho-funtzio hibridoa, seinaleen prozesamenduan ihes espektralak murrizteko erabiltzen dena. Hurbilen dauden pixelen balioen batez bestekoa erabiltzen duen birlaginketa-metodo sinplea, askotan bloke-itxura sortzen duena Ihes espektralak murrizteko erabiltzen den leiho-funtzioa, seinalea prozesatzeko aplikazioetan maiztasun-bereizmen ona eskaintzen duena 2 lobuludun Lanczos iragazkia erabiltzen duen birlaginketa metodoa kalitate handiko interpolaziorako artefaktu minimoekin 3 lobuludun Lanczos iragazkia erabiltzen duen birlaginketa metodoa kalitate handiko interpolaziorako artefaktu minimoekin Lanczos 4 lobuluko iragazkia erabiltzen duen birlaginketa metodoa kalitate handiko interpolaziorako artefaktu minimoekin Jinc funtzioa erabiltzen duen Lanczos 2 iragazkiaren aldaera bat, kalitate handiko interpolazioa eskaintzen du artefaktu minimoekin Jinc funtzioa erabiltzen duen Lanczos 3 iragazkiaren aldaera, kalitate handiko interpolazioa eskaintzen du artefaktu minimoekin Jinc funtzioa erabiltzen duen Lanczos 4 iragazkiaren aldaera, kalitate handiko interpolazioa eskaintzen du artefaktu minimoekin Hanning EWA Hanning iragazkiaren EWA (Eliptical Weighted Average) aldaera, interpolazio leun eta birlaginketa egiteko Robidoux EWA Eliptical Weighted Average (EWA) Robidoux iragazkiaren aldaera kalitate handiko birlaginketa egiteko Blackman EVE Eliptical Weighted Average (EWA) Blackman iragazkiaren aldaera, dei artefaktuak gutxitzeko EWA kuadrikoa Eliptical Weighted Average (EWA) iragazki kuadrikoaren aldaera interpolazio leunerako Robidoux Sharp EWA Robidoux Sharp iragazkiaren batez besteko haztatutako eliptikoa (EWA) aldaera emaitza zorrotzagoak lortzeko Lanczos 3 Jinc EWA Eliptical Weighted Average (EWA) Lanczos 3 Jinc iragazkiaren aldaera kalitate handiko birlaginketa aliasing murriztuarekin Ginseng Kalitate handiko irudiak prozesatzeko diseinatutako birlaginketa-iragazkia, zorroztasun eta leuntasun oreka onarekin Ginseng EWA Eliptical Weighted Average (EWA) Ginseng iragazkiaren aldaera irudiaren kalitatea hobetzeko Lanczos Sharp EWA Eliptical Weighted Average (EWA) Lanczos Sharp iragazkiaren aldaera, artefaktu minimoekin emaitza zorrotzak lortzeko Lanczos 4 EWA zorrotzena Eliptical Weighted Average (EWA) Lanczos 4 Sharpest iragazkiaren aldaera, irudiak oso zorrotzak birlagintzeko Lanczos Soft EWA Eliptical Weighted Average (EWA) Lanczos Soft iragazkiaren aldaera, irudien birlaginketa leunagoa izateko Haasn Soft Haasn-ek diseinatutako birlaginketa-iragazkia irudiak leun eta artefakturik gabeko eskalatzeko Formatu Bihurketa Bihurtu irudi sorta formatu batetik bestera Baztertu Betiko Irudien pilaketa Bildu irudiak bata bestearen gainean aukeratutako nahasketa moduekin Gehitu irudia Binak zenbatzen dira Clahe HSL Clahe HSV Histograma HSL egokitzailea berdindu Berdindu histograma Adaptive HSV Ertz modua Clip Itzulbiratu Daltonismoa Hautatu modua gaiaren koloreak egokitzeko hautatutako daltonismoaren aldaerarako Tonu gorria eta berdea bereizteko zailtasuna Tonu berdea eta gorria bereizteko zailtasuna Tonu urdina eta horia bereizteko zailtasuna Tonu gorriak hautemateko ezintasuna Tonu berdeak hautemateko ezintasuna Tonu urdinak hautemateko ezintasuna Kolore guztietarako sentikortasuna murriztu da Daltonismo osoa, gris tonuak bakarrik ikusiz Ez erabili daltonismoaren eskema Koloreak gaian ezarritakoaren araberakoak izango dira Sigmoidea Lagrange 2 2. ordenako Lagrange interpolazio-iragazkia, trantsizio leunekin kalitate handiko irudiak eskalatzeko egokia Lagrange 3 3. ordenako Lagrange interpolazio-iragazkia, zehaztasun hobea eta emaitza leunagoak eskaintzen ditu irudiak eskalatzeko Lantxoak 6 Lanczos-en birlaginketa-iragazkia 6 ordena handiagoarekin, irudien eskalatze zorrotzagoa eta zehatzagoa eskaintzen duena Lanczos 6 Jinc Lanczos 6 iragazkiaren aldaera Jinc funtzioa erabiliz irudien birlaginketa kalitatea hobetzeko Kutxa lineala lausotzea Karpa lausotu lineala Gauss Lineala Kutxa Lausotzea Pila lineala lausotzea Gaussiar Kutxa Lausotzea Lausotze Gaussiar Azkarra Lineala Hurrengoa Gaussiar Lausotasun Azkarra Lineala Gauss lausotze lineala Aukeratu iragazki bat pintura gisa erabiltzeko Ordeztu iragazkia Hautatu beheko iragazkia zure marrazkian pintzel gisa erabiltzeko TIFF konpresio eskema Low Poly Harea Pintura Irudien zatiketa Zatitu irudi bakarra errenkada edo zutabeen arabera Mugetara egokitu Konbinatu mozketaren tamaina aldatzeko modua parametro honekin nahi duzun portaera lortzeko (Moztu/Doitu aspektu-erlaziora) Inportatu dira hizkuntzak Egin babeskopiak OCR ereduak Inportatu Esportatu Posizioa Zentroa Goiko Ezkerrean Goian Eskuinekoa Beheko Ezkerrean Behean Eskuinean Goiko erdigunea Erdian Eskuin Beheko Erdian Erdiko Ezkerra Helburuko irudia Paleta transferentzia Olio hobetua Telebista zaharra sinplea HDR Gotham Krokis sinplea Distira leuna Koloretako kartela Hiru Tonua Hirugarren kolorea Clahe Oklab Clara Olch Clahe Jzazbz Polka Dot 2x2 dithering multzokatua 4x4 dithering multzokatua 8x8 dithering multzokatua Yililoma Dithering Ez dago gogoko aukerarik hautatu, gehitu tresnak orrian Gehitu gogokoak Osagarria Analogoa Triadikoa Zatiketa osagarria Tetradikoa Plaza Analogikoa + Osagarria Kolore Tresnak Nahastu, egin tonuak, sortu tonu eta gehiago Kolore Harmoniak Kolore Itzaltzea Aldakuntza Tinduak Tonuak Itzalak Kolore Nahasketa Koloreen informazioa Hautatutako kolorea Kolorea Nahasteko Ezin da dirua erabili kolore dinamikoak aktibatuta dauden bitartean 512x512 2D LUT Helburuko LUT irudia Afizionatua Etiketa andereñoa Dotorezia leuna Soft Elegance Aldaera Paleta transferentzia aldaera 3D LUT Helburuko 3D LUT fitxategia (.cube / .CUBE) LUT Lixiba Saihesbidea Kandelaren argia Jaregin Blues Amber zirraragarria Udazkeneko Koloreak Film Stock 50 Gau lainotsua Kodak Lortu LUT irudi neutroa Lehenik eta behin, erabili zure gogoko argazkiak editatzeko aplikazioa hemen lor dezakezun LUT neutralari iragazki bat aplikatzeko. Honek behar bezala funtziona dezan, pixel kolore bakoitzak ez du beste pixel batzuen menpe egon behar (adibidez, lausotzeak ez du funtzionatuko). Prest dagoenean, erabili zure LUT irudi berria 512*512 LUT iragazkirako sarrera gisa Pop Artea Zeluloidea Kafea Urrezko Basoa Berdexka Retro horia Estekak aurrebista Testua lor dezakezun lekuetan (QRCode, OCR eta abar) esteken aurrebista berreskuratzea gaitzen du. Estekak ICO fitxategiak 256 x 256 gehienezko tamainan soilik gorde daitezke GIF WEBPra Bihurtu GIF irudiak WEBP irudi animatuetara WEBP tresnak Bihurtu irudiak WEBP animaziozko irudietara edo atera fotogramak emandako WEBP animaziotik WEBP irudietara Bihurtu WEBP fitxategia argazki sorta batean Bihurtu irudi sorta WEBP fitxategira Irudiak WEBPra Hautatu WEBP irudia hasteko Ez dago fitxategietarako sarbide osorik Baimendu fitxategi guztiak sartzeko JXL, QOI eta Android-en irudi gisa ezagutzen ez diren beste irudi batzuk ikusteko. Baimenik gabe Image Toolbox ezin ditu irudi horiek erakutsi Marraztu kolore lehenetsia Marrazte-bide modu lehenetsia Gehitu denbora-zigilua Denbora-zigilua irteerako fitxategi-izenari gehitzea gaitzen du Formateatutako denbora-zigilua Gaitu Denbora-zigiluaren formatua irteerako fitxategi-izenean oinarrizko milis-en ordez Gaitu Denbora-zigiluak haien formatua hautatzeko Behin gordeko kokapena Ikusi eta editatu behin betiko gordetzeko kokapenak, gehienetan aukera guztietan gordetzeko botoia luze sakatuz erabil ditzakezun Duela gutxi erabilia CI kanala Taldea Irudi-tresnak Telegram-en 🎉 Sartu gure txatean, nahi duzun guztia eztabaidatu ahal izateko eta beta eta iragarkiak argitaratzen ditudan CI kanalean ere begiratu Jaso jakinarazpenak aplikazioaren bertsio berriei buruz eta irakurri iragarkiak Egokitu irudi bat emandako neurrietara eta aplikatu lausotasuna edo kolorea atzeko planoari Tresnen Antolaketa Motaren arabera taldekatu tresnak Pantaila nagusiko tresnak beren motaren arabera taldekatzen ditu, zerrenda pertsonalizatu baten ordez Balio lehenetsiak Sistema Barren Ikusgarritasuna Erakutsi sistema-barrak irristatuz Sistema-barrak ezkutatuta badaude erakusteko hatza egitea gaitzen du Autoa Ezkutatu guztiak Erakutsi guztiak Ezkutatu nabigazio-barra Ezkutatu egoera barra Zarata Sortzea Sortu Perlin edo beste mota batzuetako zarata desberdinak Maiztasuna Zarata Mota Errotazio Mota Fraktal Mota Oktabak Lakunartasuna Irabazi Indar haztatua Ping Pong Indarra Distantzia Funtzioa Itzuli mota Jitter Domeinuaren deformazioa Lerrokatzea Fitxategi-izen pertsonalizatua Hautatu uneko irudia gordetzeko erabiliko diren kokapena eta fitxategi-izena Izen pertsonalizatuarekin karpetan gorde da Collage Maker Egin collageak gehienez 20 iruditatik Collage mota Eduki sakatuta irudia trukatzeko, mugitzeko eta zooma posizioa doitzeko Desgaitu biraketa Bi hatzekin egindako keinuekin irudiak biratzea eragozten du Gaitu ertzetara atxikitzea Mugitu edo zooma egin ondoren, irudiak koadroaren ertzak beteko dira Histograma RGB edo Distira irudiaren histograma doikuntzak egiten laguntzeko Irudi hau RGB eta Distira histogramak sortzeko erabiliko da Tesseract Aukerak Aplikatu sarrerako aldagai batzuk tesseract motorrako Aukera pertsonalizatuak Aukerak eredu hau jarraituz sartu behar dira: \"--{option_name} {value} \" Mozketa automatikoa Doako Txokoak Moztu irudia poligonoz, honek perspektiba ere zuzentzen du Bortxatu Irudien Mugetara Puntuak ez dira irudien mugek mugatuko, hau da, perspektiba zehatzago zuzentzeko erabilgarria Maskara Edukia kontzienteki bete marraztutako bidearen azpian Sendatu Lekua Erabili Circle Kernel Irekiera Itxiera Gradiente morfologikoa Top Hat Kapela Beltza Tonu Kurbak Berrezarri Kurbak Kurbak balio lehenetsira itzuliko dira Lerro-estiloa Hutsunearen Tamaina Marrakatua Dot Marrakatua Zigilua Sigi-saga Lerro eten bat marrazten du marraztutako bidetik zehaztutako hutsunearen tamainarekin Emandako bidetik puntua eta marra etena marrazten ditu Lerro zuzen lehenetsiak besterik ez Aukeratutako formak marrazten ditu bidearen zehar zehaztutako tartearekin Bidean zehar sigi-saga uhintsuak marrazten ditu Sigi-saga-erlazioa Sortu lasterbidea Aukeratu ainguratzeko tresna Tresna abiarazlearen hasierako pantailan gehituko da lasterbide gisa, erabili \"Saltatu fitxategiak hautatzea \" ezarpenarekin konbinatuz, beharrezko portaera lortzeko. Ez pilatu markoak Aurreko fotogramak botatzeko aukera ematen du, beraz, ez dira elkarren gainean pilatuko Crossfade Fotogramak elkarren artean gurutzatuta egongo dira Crossfade fotogramak zenbatzen dira Atalase bat Bigarren atalasea Canny Ispilua 101 Zoom Lausodura hobetua Laplaziar sinplea Sobel Simple Laguntzailea Sarea Marrazki-eremuaren gainean euskarria erakusten du manipulazio zehatzak egiteko Sarearen kolorea Zelula-zabalera Zelula-Altuera Hautatzaile trinkoak Hautaketa-kontrol batzuek diseinu trinkoa erabiliko dute leku gutxiago hartzeko Eman argazkia ateratzeko kameraren baimena ezarpenetan Diseinua Pantaila nagusiaren izenburua Tasa Konstanteko Faktorea (CRF) %1$s balio batek konpresio motela esan nahi du, eta horren ondorioz fitxategiaren tamaina txiki samarra da. %2$s-k konpresio azkarragoa esan nahi du, fitxategi handi bat sortuz. Lut Liburutegia Deskargatu LUT bilduma, deskargatu ondoren aplika dezakezuna Eguneratu LUT bilduma (berriak bakarrik jarriko dira ilaran), deskargatu ondoren aplika dezakezuna Aldatu iragazkien irudien aurrebista lehenetsia Aurrebista irudia Ezkutatu Erakutsi Graduatzaile mota Fantasia Materiala 2 Itxura dotoreko graduatzailea. Hau da aukera lehenetsia Material 2 graduatzailea A Material You graduatzailea Aplikatu Erdiko elkarrizketa-botoiak Elkarrizketa-botoiak erdian kokatuko dira ezkerreko aldean beharrean Kode irekiko lizentziak Ikusi aplikazio honetan erabiltzen diren kode irekiko liburutegien lizentziak Eremua Birlaginketa pixelaren eremuaren erlazioa erabiliz. Irudiak dezimatzeko metodo hobetsia izan daiteke, moirerik gabeko emaitzak ematen baititu. Baina irudia handitzen denean, \"Gertuena \" metodoaren antzekoa da. Gaitu Tonemapping Sartu % Ezin da webgunera sartu, saiatu VPN erabiltzen edo egiaztatu url-a zuzena den Markatze geruzak Geruzak modua irudiak, testuak eta beste modu askean jartzeko gaitasunarekin Editatu geruza Geruzak irudian Erabili irudi bat atzeko plano gisa eta gehitu geruza desberdinak gainean Geruzak atzeko planoan Lehen aukeraren berdina baina irudiaren ordez kolorearekin Beta Ezarpen azkarrak aldean Gehitu zerrenda mugikor bat hautatutako aldean irudiak editatzen dituzun bitartean, eta horrek ezarpen azkarrak irekiko ditu klik egiten duzunean Garbitu hautaketa \"%1$s \" ezarpen taldea lehenespenez tolestuta egongo da \"%1$s \" ezarpen taldea lehenespenez zabalduko da Base64 tresnak Deskodetu Base64 katea irudira, edo kodetu irudia Base64 formatuan Oinarria64 Emandako balioa ez da baliozko Base64 kate bat Ezin da kopiatu Base64 kate hutsa edo baliogabea Itsatsi Oinarria64 Kopiatu Base64 Kargatu irudia Base64 katea kopiatzeko edo gordetzeko. Katea bera baduzu, goiko itsatsi dezakezu irudia lortzeko Gorde Base64 Partekatu Base64 Aukerak Ekintzak Inportatu Base64 Base64 Ekintzak Gehitu eskema Gehitu eskema kolore eta zabalera zehaztutako testuaren inguruan Eskema Kolorea Eskema Tamaina Errotazioa Checksum fitxategi-izen gisa Irteerako irudiek beren datuen kontrol-sumari dagokion izena izango dute Software librea (bazkidea) Software erabilgarriagoa Android aplikazioen bazkide kanalean Algoritmoa Checksum tresnak Konparatu kontrol batuketak, kalkulatu hashak edo sortu hashing-algoritmo desberdinak erabiliz fitxategietatik hex kateak Kalkulatu Testu Hash Checksum Aukeratu fitxategia hautatutako algoritmoan oinarrituta bere kontrol-sumoa kalkulatzeko Idatzi testua hautatutako algoritmoan oinarrituta kalkulatzeko Iturburua egiaztatzeko batura Konparatzeko checksum Partidu! Aldea Checksumak berdinak dira, segurua izan daiteke Checksumak ez dira berdinak, fitxategia segurua izan daiteke! Sarearen gradienteak Begiratu sareko Gradienteen sareko bilduma TTF eta OTF letra-tipoak soilik inporta daitezke Inportatu letra-tipoa (TTF/OTF) Esportatu letra-tipoak Inportatutako letra-tipoak Errore bat gertatu da saiakera gordetzean, saiatu irteera karpeta aldatzen Fitxategiaren izena ez dago ezarrita Bat ere ez Orriak pertsonalizatuak Orrialdeak hautatzea Erremintaren irteeraren berrespena Tresna jakinak erabiltzen dituzun bitartean gorde gabeko aldaketak badituzu eta ixten saiatzen bazara, berretsi elkarrizketa-koadroa agertuko da Editatu EXIF Aldatu irudi bakarreko metadatuak birkonpresiorik gabe Sakatu erabilgarri dauden etiketak editatzeko Aldatu eranskailua Egokitzeko Zabalera Fit Altuera Batch Konparazioa Aukeratu fitxategiak/fitxategiak hautatutako algoritmoan oinarrituta bere kontrol-bagadura kalkulatzeko Aukeratu Fitxategiak Aukeratu direktorioa Buruaren Luzera Eskala Zigilua Denbora-zigilua Formatu eredua Betegarria Irudiaren mozketa Moztu irudiaren zatia eta batu ezkerrekoak (alderantzizkoa izan daiteke) lerro bertikal edo horizontalen bidez Pibot-lerro bertikala Pibot-lerro horizontala Alderantzizko hautaketa Ebakitako zati bertikala utziko da, moztutako eremuaren inguruan zatiak batu beharrean Moztutako zati horizontala utziko da, moztutako eremuaren inguruan zatiak batu beharrean Sare-gradienteen bilduma Sortu sare-gradientea korapilo eta bereizmen kopuru pertsonalizatuarekin Sarearen gradientearen gainjartzea Konposatu emandako irudien goiko sareko gradientea Puntuen Pertsonalizazioa Sarearen tamaina X Ebazpena Y ebazpena Ebazpena Pixel By Pixel Nabarmendu Kolorea Pixel konparazio mota Eskaneatu barra-kodea Altuera ratioa Barra-kode mota B/N betearazi Barra-kodearen irudia zuri-beltzean izango da eta ez da aplikazioaren gaiaren arabera koloreztatu Eskaneatu edozein barra-kode (QR, EAN, AZTEC, …) eta lortu bere edukia edo itsatsi zure testua berri bat sortzeko Ez da barra-koderik aurkitu Sortutako barra-kodea hemen egongo da Audio Azalak Atera albumen azaleko irudiak audio fitxategietatik, formatu ohikoenak onartzen dira Hautatu audioa hasteko Aukeratu Audioa Ez da azala aurkitu Bidali erregistroak Egin klik aplikazioaren erregistro-fitxategia partekatzeko, honek arazoa antzematen eta arazoak konpontzen lagunduko dit Aupa… Arazoren bat izan da Nirekin harremanetan jar zaitezke beheko aukeren bidez eta irtenbidea bilatzen saiatuko naiz.\n(Ez ahaztu erregistroak eranstea) Idatzi fitxategira Atera testua irudi sortatik eta gorde testu fitxategi bakarrean Idatzi metadatuetara Atera testua irudi bakoitzetik eta jarri argazki erlatiboen EXIF ​​informazioan Modu Ikusezina Erabili esteganografia begi-markak ikusezinak sortzeko zure irudien byteen barruan Erabili LSB LSB (Less Significant Bit) esteganografia metodoa erabiliko da, FD (Frequency Domain) bestela Kendu begi gorriak automatikoki Pasahitza Desblokeatu PDF babestuta dago Eragiketa ia amaituta. Orain bertan behera uzteko, berrabiarazi beharko da Aldaketa data Aldaketa data (alderantziztua) Tamaina Tamaina (alderantzizkatua) MIME mota MIME mota (alderantzikatua) Luzapena Luzapena (alderantzikatua) Gehitutako data Gehitutako data (alderantzikatua) Ezkerretik Eskuinera Eskuinetik Ezkerrera Goitik Behetik Behetik gora Beira likidoa Duela gutxi iragarritako IOS 26an eta haren beira likidoaren diseinu sisteman oinarritutako etengailua Aukeratu irudia edo itsatsi/inportatu Base64 datuak behean Idatzi irudiaren esteka hasteko Itsatsi esteka Kaleidoskopioa Bigarren mailako angelua Aldeak Channel Mix Berde urdina Gorria urdina Berde gorria Gorri sartu Berdean sartu Urdinera Zian Magenta Horia Kolore-tonu erdia Ingerada Mailak Desplazamendua Voronoi Kristalizatu Forma Luzatu Ausazkotasuna Desitxuratu Zabaldua Txakurra Bigarren erradioa Berdindu Distira Zurrunbiloa eta Pintxa Puntillatu Ertzaren kolorea Koordenatu polarrak Zuzena polarrera Polarra zuzenera Inbertitu zirkuluan Murriztu Zarata Solarize sinplea Ehundu X Hutsunea Y Gap X Zabalera Y Zabalera Biraka Gomazko zigilua Lohitu Dentsitatea Nahastu Esfera lentearen distortsioa Errefrakzio-indizea Arkua Zabaltzeko angelua Distira Izpiak ASCII Gradientea Maria Udazkena Hezurra Jet Negua Ozeanoa Uda Udaberria Cool Aldaera HSV Arrosa Beroa Hitza Magma Infernua Plasma Viridis Herritarrak Ilunabarra Twilight Shifted Perspektiba Auto Okertu Moztu baimendu Mozketa edo Perspektiba Absolutua Turbo Berde sakona Lenteen zuzenketa Helburuko lentearen profil fitxategia JSON formatuan Deskargatu prest dauden lenteen profilak Zatiaren ehunekoak Esportatu JSON gisa Kopiatu katea paleta-datu batekin json irudikapen gisa Jostura Taila Hasierako pantaila Blokeatu pantaila Eraikituta Horma-irudiak esportatu Freskatu Lortu uneko hasierako, blokeoa eta horma-irudi integratuak Baimendu fitxategi guztietarako sarbidea, hau beharrezkoa da horma-paperak berreskuratzeko Kudeatu kanpoko biltegiratze-baimena ez da nahikoa, zure irudietarako sarbidea baimendu behar duzu, ziurtatu \"Baimendu guztiak \" hautatu duzula Gehitu aurrezarpena fitxategi-izenari Hautatutako aurrezartutako atzizkia eransten dio irudi-fitxategiaren izenari Gehitu irudien eskala modua fitxategi-izenari Hautatutako irudien eskala moduarekin atzizkia eransten dio irudi-fitxategiaren izenari Ascii art Bihurtu irudia irudiaren itxura izango duen ascii testura Parametroak Irudiari iragazki negatiboa aplikatzen dio emaitza hobeak lortzeko, kasu batzuetan Pantaila-argazkia prozesatzen Ez da pantaila-argazkia atera, saiatu berriro Saltatu da gordetzea %1$s fitxategi saltatu dira Baimendu Saltatu handiagoa bada Tresna batzuek irudiak gordetzeari uzteko baimena izango dute, ondoriozko fitxategiaren tamaina jatorrizkoa baino handiagoa bada Egutegiko Gertaera Harremanetan jarri Posta elektronikoa Kokapena Telefonoa Testua SMSak URLa Wi-Fi Sare irekia N/A SSID Telefonoa Mezua Helbidea Gaia Gorputza Izena Antolaketa Izenburua Telefonoak Posta elektronikoak URLak Helbideak Laburpena Deskribapena Kokapena Antolatzailea Hasiera data Amaiera data Egoera Latitudea Luzera Sortu barra-kodea Editatu barra-kodea Wi-Fi konfigurazioa Segurtasuna Aukeratu kontaktua Eman kontaktuei ezarpenetan aukeratutako kontaktua erabiliz automatikoki betetzeko baimena Harremanetarako informazioa Izena Erdiko izena Abizena Ahoskera Gehitu telefonoa Gehitu posta elektronikoa Gehitu helbidea Webgunea Gehitu webgunea Formateatutako izena Irudi hau barra-kodearen gainean jartzeko erabiliko da Kodeen pertsonalizazioa Irudi hau QR kodearen erdian logotipo gisa erabiliko da Logotipoa Logo betegarria Logotipoaren tamaina Logoaren txokoak Laugarren begia Begiaren simetria gehitzen dio qr kodeari laugarren begia gehituz beheko muturrean Pixel forma Markoaren forma Pilota forma Erroreak zuzentzeko maila Kolore iluna Kolore argia Hiper OS Xiaomi HyperOS bezalako estiloa Maskararen eredua Baliteke kode hau eskaneatu ezin izatea, aldatu itxura-parametroak gailu guztiekin irakurtzeko Ezin da eskaneatu Tresnek hasierako pantailako aplikazioen abiarazlearen itxura izango dute trinkoagoa izateko Abiarazle modua Aukeratutako pintzelarekin eta estiloarekin eremu bat betetzen du Uholde betetzea Spray Graffity estiloko bidea marrazten du Partikula karratuak Spray partikulak zirkuluen ordez karratu formakoak izango dira Paleta tresnak Sortu paleta duzun oinarrizko/materiala iruditik, edo inportatu/esportatu paleta formatu ezberdinetan Editatu paleta Esportatu/inportatu paleta hainbat formatutan Kolorearen izena Paletaren izena Paleta formatua Esportatu sortutako paleta formatu desberdinetara Kolore berria gehitzen dio uneko paletari %1$s formatuak ez du onartzen paleta izena ematea Play Store-ren gidalerroak direla eta, eginbide hau ezin da uneko bertsioan sartu. Funtzionalitate honetara sartzeko, deskargatu ImageToolbox iturri alternatibo batetik. GitHub-en dauden eraikuntzak aurki ditzakezu behean. Ireki Github orria Jatorrizko fitxategia beste batekin ordezkatuko da hautatutako karpetan gorde beharrean Ezkutuko ur-markaren testua detektatu da Ezkutuko ur-markaren irudia detektatu da Irudi hau ezkutatuta zegoen Inpainting generatiboa Irudi bateko objektuak AI eredu bat erabiliz kentzeko aukera ematen du, OpenCV-n fidatu gabe. Eginbide hau erabiltzeko, aplikazioak behar den eredua (~200 MB) deskargatuko du GitHub-etik Irudi bateko objektuak AI eredu bat erabiliz kentzeko aukera ematen du, OpenCV-n fidatu gabe. Eragiketa luzea izan daiteke Errore-mailaren analisia Luminantza Gradientea Batez besteko Distantzia Kopiatu Mugimendu detekzioa Atxiki Koefizientea Arbeleko datuak handiegiak dira Datuak handiegiak dira kopiatzeko Ehundura sinplearen pixelizazioa Pixelizazio mailakatua Pixelizazio gurutzatua Mikro makro pixelizazioa Pixelizazio orbitala Zurrunbiloen pixelizazioa Pultsu-sarearen pixelizazioa Nukleoaren pixelizazioa Ehun erradiala pixelizazioa Ezin da ireki uri \"%1$s \" Elurra modua Gaituta Ertzaren markoa Glitch aldaera Kanal-aldaketa Desplazamendu maximoa VHS Blokeatu Glitch Blokearen tamaina CRT kurbadura Kurbadura Kroma Pixel Melt Gehienezko tantoa AI tresnak Hainbat tresna irudiak prozesatzeko ai ereduen bidez, hala nola artefaktuak kentzea edo denoising Konpresioa, lerro bitxiak Marrazki bizidunak, emisio-konpresioa Konpresio orokorra, zarata orokorra Kolorerik gabeko marrazki bizidunen zarata Azkar, konpresio orokorra, zarata orokorra, animazioa/komikiak/anime Liburuen eskaneatzea Esposizioaren zuzenketa Konpresio orokorrean onena, koloretako irudietan Onena konpresio orokorrean, gris-eskalako irudietan Konpresio orokorra, gris-eskalako irudiak, indartsuagoak Zarata orokorra, koloretako irudiak Zarata orokorra, koloretako irudiak, xehetasun hobeak Zarata orokorra, gris-eskalako irudiak Zarata orokorra, gris-eskalako irudiak, indartsuagoak Zarata orokorra, gris-eskalako irudiak, indartsuena Konpresio orokorra Konpresio orokorra Testurizazioa, h264 konpresioa VHS konpresioa Konpresio ez estandarra (cinepak, msvideo1, roq) Bink konpresioa, geometrian hobea Bink konpresioa, indartsuagoa Bink konpresioa, biguna, xehetasunak mantentzen ditu Eskailera-urrats efektua kentzea, leuntzea Eskaneatutako artea/marrazkiak, konpresio leuna, moire Kolore-bandak Astiro, tonu erdiak kenduz Grisen eskala/bw irudietarako koloreztatzaile orokorra, emaitza hobeak lortzeko erabili DDColor Ertzak kentzea Gehiegizko zorroztasuna kentzen du Astiro, dithering Aliasaren aurkakoa, artefaktu orokorrak, CGI KDM003-k aztertzen du prozesatzea Irudia hobetzeko eredu arina Konpresioaren artefaktuak kentzea Konpresioaren artefaktuak kentzea Benda kentzea emaitza leunekin Tonu erdiko ereduen prozesamendua Dither eredua kentzea V3 JPEG artefaktuak kentzea V2 H.264 ehundura hobetzea VHS zorroztu eta hobetzea Batzea Zatiaren tamaina Gainjartze Tamaina %1$s px-tik gorako irudiak zatika zatikatu eta prozesatu egingo dira, gainjarriz nahasten dira josturak ikus daitezkeen saihesteko. Tamaina handiek ezegonkortasuna sor dezakete gama baxuko gailuekin Hautatu bat hasteko %1$s eredua ezabatu nahi duzu? Berriro deskargatu beharko duzu Berretsi Ereduak Deskargatutako ereduak Eskuragarri dauden ereduak Prestatzen Eredu aktiboa Ezin izan da saioa ireki .onnx/.ort ereduak soilik inporta daitezke Inportazio eredua Inportatu onnx eredu pertsonalizatua gehiago erabiltzeko, onnx/ort ereduak bakarrik onartzen dira, esrgan bezalako aldaera ia guztiak onartzen ditu Inportatutako ereduak Zarata orokorra, koloretako irudiak Zarata orokorra, koloretako irudiak, indartsuagoak Zarata orokorra, koloretako irudiak, indartsuena Dithering artefaktuak eta kolore-bandak murrizten ditu, gradiente leunak eta kolore-eremu lauak hobetuz. Irudiaren distira eta kontrastea hobetzen ditu distira orekatuekin, kolore naturalak mantenduz. Irudi ilunak argitzen ditu xehetasunak mantenduz eta gehiegizko esposizioa saihestuz. Gehiegizko kolore-tonua kentzen du eta kolore oreka neutralagoa eta naturalagoa berreskuratzen du. Poisson-en oinarritutako zarata-tonua aplikatzen du, xehetasun eta ehundura finak zaintzean arreta jarriz. Poisson zarata-tonu leuna aplikatzen du, ikusmen emaitza leunagoak eta ez hain oldarkorrak lortzeko. Zarata-tonu uniformea ​​xehetasunen kontserbazioan eta irudiaren argitasunean zentratua. Zarata tonu uniforme leuna, ehundura sotila eta itxura leuna lortzeko. Kaltetutako edo irregularrak diren eremuak konpontzen ditu artefaktuak berriro margotuz eta irudiaren koherentzia hobetuz. Debanding eredu arina, kolore-bandak kentzen dituena, errendimendu kostu minimoarekin. Konpresio artefaktu oso altuak dituzten irudiak optimizatzen ditu (% 0-20ko kalitatea) argitasuna hobetzeko. Irudiak hobetzen ditu konpresio handiko artefaktuekin (% 20-40ko kalitatea), xehetasunak leheneratzen eta zarata murriztuz. Irudiak hobetzen ditu konpresio moderatua (% 40-60 kalitatea), zorroztasuna eta leuntasuna orekatuz. Irudiak konpresio argiarekin (% 60-80 kalitatea) hobetzen ditu xehetasun eta ehundura sotilak hobetzeko. Ia galerarik gabeko irudiak apur bat hobetzen ditu (% 80-100eko kalitatea), itxura eta xehetasun naturalak mantenduz. Kolorizazio sinple eta azkarra, marrazki bizidunak, ez da ideala Irudiaren lausotasuna apur bat murrizten du, zorroztasuna hobetuz artefaktuak sartu gabe. Ibilbide luzeko eragiketak Irudia prozesatzea Tramitazioa JPEG konpresio-artefaktu handiak kentzen ditu kalitate baxuko irudietan (% 0-20). JPEG artefaktu indartsuak murrizten ditu oso konprimitutako irudietan (% 20-40). JPEG artefaktu moderatuak garbitzen ditu, irudiaren xehetasunak (% 40-60) gordetzen dituen bitartean. JPEG artefaktu argiak hobetzen ditu kalitate handiko irudietan (% 60-80). Ia galerarik gabeko irudietan JPEG artefaktu txikiak murrizten ditu (% 80-100). Xehetasun eta ehundura finak hobetzen ditu, artefaktu astunik gabe hautemandako zorroztasuna hobetuz. Prozesatzea amaitu da Ezin izan da prozesatu Azalaren ehundura eta xehetasunak hobetzen ditu, itxura naturala mantenduz, abiadurarako optimizatuta. JPEG konpresioaren artefaktuak kentzen ditu eta konprimitutako argazkien irudiaren kalitatea berrezartzen du. ISO zarata murrizten du argi gutxiko baldintzetan ateratako argazkietan, xehetasunak gordez. Gehiegizko esposizioak edo \"jumbo\" nabarmenak zuzentzen ditu eta tonu-oreka hobea berreskuratzen du. Kolore-eredu arina eta azkarra gris-eskalako irudiei kolore naturalak gehitzen dizkiena. DEJPEG Denoise Koloreztatu Artefaktuak Hobetu Animea Eskaneatzea Goi mailakoa X4 upscaler irudi orokorretarako; GPU eta denbora gutxiago erabiltzen dituen modelo txiki-txikia, lausotze eta desnoise moderatuak dituena. X2 upscaler irudi orokorretarako, testurak eta xehetasun naturalak gordez. X4 upscaler irudi orokorretarako testura hobetuekin eta emaitza errealistekin. X4 upscaler anime irudietarako optimizatua; 6 RRDB bloke lerro eta xehetasun zorrotzagoetarako. X4 upscaler MSE galerarekin, emaitza leunagoak eta artefaktu murriztuak sortzen ditu irudi orokorretarako. X4 Upscaler anime irudietarako optimizatua; 4B32F aldaera xehetasun zorrotzagoekin eta lerro leunekin. X4 UltraSharp V2 eredua irudi orokorretarako; zorroztasuna eta argitasuna azpimarratzen ditu. X4 UltraSharp V2 Lite; azkarrago eta txikiagoa, xehetasunak gordetzen ditu GPU memoria gutxiago erabiliz. Eredu arina atzeko planoa azkar kentzeko. Errendimendu orekatua eta zehaztasuna. Erretratu, objektu eta eszenekin lan egiten du. Erabilera gehienetarako gomendatua. Kendu BG Ertzaren lodiera horizontala Ertz Bertikala Lodiera %1$s kolorea %1$s koloreak Oraingo ereduak ez du zatiketa onartzen, irudia jatorrizko dimentsioetan prozesatu egingo da; horrek memoria-kontsumo handia eta maila baxuko gailuekin arazoak sor ditzake. Zatiketa desgaituta dago, irudia jatorrizko dimentsioetan prozesatuko da; horrek memoria-kontsumo handia eta arazoak sor ditzake gama baxuko gailuekin, baina emaitza hobeak eman ditzake ondorioetan. Zatiketa Zehaztasun handiko irudiak segmentatzeko eredua atzeko planoa kentzeko U2Net-en bertsio arina atzeko planoa bizkorrago kentzeko memoria-erabilera txikiagoarekin. DDColor eredu osoak kalitate handiko koloreztatzea eskaintzen du artefaktu minimoekin irudi orokorretarako. Kolorizazio eredu guztien aukerarik onena. DDColor Prestatutako eta datu artistiko pribatuak; Kolore-emaitzak anitz eta artistikoak sortzen ditu, kolore-artefaktu irrealista gutxiagorekin. Swin Transformer-en oinarritutako BiRefNet eredu arina atzeko planoa zehatz kentzeko. Kalitate handiko atzeko planoa kentzea ertz zorrotzekin eta xehetasunen kontserbazio bikainarekin, batez ere objektu konplexuetan eta atzeko plano delikatuenetan. Atzeko planoa kentzeko eredua, ertz leunekin maskara zehatzak sortzen dituena, objektu orokorretarako egokia eta xehetasun moderatua zaintzeko. Dagoeneko deskargatu da eredua Eredua behar bezala inportatu da Mota Gakoa Oso azkarra Normala Astiro Oso motela Ehuneko kalkulatu Gutxieneko balioa %1$s da Desitxuratu irudia hatzekin marraztuz Warp Gogortasuna Warp modua Mugitu Hazi Txikitu Swirl CW Zurrunbiloa CCW Desagertzeko indarra Top Drop Beheko Tanta Hasi Drop Amaitu Drop Deskargatzen Forma leunak Erabili superelipseak laukizuzen biribilduen ordez forma leunagoak eta naturalagoak lortzeko Forma mota Moztu Biribildua Leuna Ertz zorrotzak biribildu gabe Ertz biribildu klasikoak Formak Mota Txokoak Tamaina Squircle UI elementu biribildu dotoreak Fitxategi-izenen formatua Fitxategi-izenaren hasieran jarritako testu pertsonalizatua, ezin hobea proiektuen izenetarako, marketarako edo etiketa pertsonaletarako. Jatorrizko fitxategi-izena luzapenik gabe erabiltzen du, iturriaren identifikazioa osorik mantentzen lagunduko dizu. Irudiaren zabalera pixeletan, erabilgarria bereizmen-aldaketen jarraipena egiteko edo emaitzak eskalatzeko. Irudiaren altuera pixeletan, lagungarria aspektu-erlazioekin edo esportazioekin lan egiteko. Ausazko zifrak sortzen ditu fitxategi-izen bakarrak bermatzeko; Gehitu zifra gehiago bikoiztuen aurkako segurtasun gehigarrirako. Loteen esportazioetarako automatikoki gehitzen den kontagailua, saio batean hainbat irudi gordetzeko aproposa. Aplikatutako aurrez ezarritako izena fitxategi-izenean txertatzen du, irudia nola prozesatu zen erraz gogoratu ahal izateko. Prozesatzean erabilitako irudien eskalatze modua bistaratzen du, tamaina aldatu, moztutako edo egokitutako irudiak bereizten laguntzen du. Testu pertsonalizatua fitxategi-izenaren amaieran jartzen da, _v2, _edited edo _final bezalako bertsioak egiteko erabilgarria. Fitxategiaren luzapena (png, jpg, webp, etab.), automatikoki gordetako benetako formatuarekin bat datorrena. Denbora-zigilu pertsonalizagarria, zure formatua java zehaztapenen arabera definitzeko aukera ematen dizuna ordenatzeko. Fling mota Android natiboa iOS estiloa Kurba leuna Geldialdi azkarra Errebotea Flotagarria Snappy Ultra leuna Egokigarria Irisgarritasuna jakitun Mugimendu murriztua Native Android korritze fisika oinarrizko alderaketa egiteko Erabilera orokorrerako korritze orekatua eta leuna Marruskadura handiagoa iOS-en antzeko korritze portaera Spline kurba berezia korritze sentsazio desberdina lortzeko Mugimendu zehatza geldialdi bizkorrekin Sroll errebote dibertigarria eta sentikorra Edukiak arakatzeko korritu luze eta irristagarriak Mugimendu azkarra eta sentikorra interfaze interaktiboetarako Premium mugitze leuna, momentu hedatuarekin Fling abiaduran oinarritutako fisika doitzen du Sistemaren irisgarritasun-ezarpenak errespetatzen ditu Irisgarritasun-beharretarako mugimendu minimoa Lehen lerroak Lerro lodiagoa gehitzen du bosgarren lerroan behin Bete kolorea Ezkutuko Tresnak Partekatzeko ezkutatuta dauden tresnak Kolore Liburutegia Arakatu kolore bilduma zabala Irudiak zorrozten eta kentzen ditu xehetasun naturalak mantenduz, fokurik gabeko argazkiak konpontzeko aproposa. Aurrez tamaina aldatutako irudiak modu adimentsuan leheneratzen ditu, galdutako xehetasunak eta ehundurak berreskuratuz. Zuzeneko ekintzako edukietarako optimizatuta, konpresio-artefaktuak murrizten ditu eta xehetasun finak hobetzen ditu pelikula/telebistako saioen fotogrametan. VHS kalitateko metrajeak HD bihurtzen ditu, zintaren zarata kenduz eta bereizmena hobetuz, vintage kutsua mantenduz. Testu handiko irudi eta pantaila-argazkietarako espezializatua, karaktereak zorrozten ditu eta irakurgarritasuna hobetzen du. Datu-multzo ezberdinetan trebatutako igoera aurreratua, helburu orokorreko argazkiak hobetzeko bikaina. Web-konprimitutako argazkietarako optimizatua, JPEG artefaktuak kentzen ditu eta itxura naturala berreskuratzen du. Web-argazkietarako bertsio hobetua, testura hobeto kontserbatzeko eta artefaktuak murrizteko. Dual Aggregation Transformer teknologiarekin 2 aldiz eskalatzea, zorroztasuna eta xehetasun naturalak mantentzen ditu. 3 aldiz eskalatzea transformadoreen arkitektura aurreratua erabiliz, handitze neurrizko beharretarako aproposa. Kalitate handiko 4 aldiz eskalatzea punta-puntako transformadore-sarearekin, xehetasun finak gordetzen ditu eskala handiagoetan. Argazkietatik lausotasuna/zarata eta dardarak kentzen ditu. Helburu orokorra baina onena argazkietan. Kalitate baxuko irudiak leheneratzen ditu Swin2SR transformagailua erabiliz, BSRGAN degradaziorako optimizatuta. Konpresio-artefaktu astunak konpontzeko eta xehetasunak 4x eskalan hobetzeko bikaina. 4x igoera BSRGAN degradazioan trebatutako SwinIR transformadorearekin. GAN erabiltzen du testura zorrotzagoak eta xehetasun naturalagoak lortzeko argazkietan eta eszena konplexuetan. Bidea Batu PDFa Konbinatu hainbat PDF fitxategi dokumentu batean Fitxategien ordena orr. Zatitu PDFa Atera orri zehatzak PDF dokumentutik Biratu PDFa Konpondu orriaren orientazioa betirako Orriak Berrantolatu PDFa Arrastatu eta jaregin orriak ordenatzeko Eutsi eta arrastatu orriak Orrialde Zenbakiak Gehitu zenbakiak zure dokumentuei automatikoki Etiketa formatua PDF testura (OCR) Atera testu arrunta zure PDF dokumentuetatik Testu pertsonalizatua gainjarri marka edo segurtasunerako Sinadura Gehitu zure sinadura elektronikoa edozein dokumentutan Hau sinadura gisa erabiliko da Desblokeatu PDFa Kendu pasahitzak babestutako fitxategietatik Babestu PDFa Babestu zure dokumentuak enkriptazio sendoarekin Arrakasta PDF desblokeatua, gorde edo parteka dezakezu PDFa konpondu Saiatu hondatuta dauden edo irakurezinak diren dokumentuak konpontzen Grisen eskala Bihurtu dokumentu txertatutako irudi guztiak gris-eskalara Konprimitu PDFa Optimizatu zure dokumentu-fitxategiaren tamaina errazago partekatzeko ImageToolbox-ek barne-erreferentzia gurutzatuen taula berreraikitzen du eta fitxategi-egitura hutsetik birsortzen du. Honek \\"ireki ezin diren\\" fitxategi askotarako sarbidea berrezarri dezake Tresna honek dokumentuen irudi guztiak gris-eskala bihurtzen ditu. Fitxategien tamaina murrizteko eta inprimatzeko onena Metadatuak Editatu dokumentuaren propietateak pribatutasun hobea izateko Etiketak Ekoizlea Egilea Gako-hitzak Sortzailea Pribatutasuna Deep Clean Garbitu dokumentu honetarako erabilgarri dauden metadatu guztiak Orria OCR sakona Atera testua dokumentutik eta gorde testu fitxategi bakarrean Tesseract motorra erabiliz Ezin dira orri guztiak kendu Kendu PDF orriak Kendu orri zehatzak PDF dokumentutik Sakatu Kendu Eskuz Moztu PDFa Moztu dokumentuaren orriak edozein mugatara Laundu PDFa Egin PDF aldaezina dokumentu-orriak rasterizatuz Ezin izan da kamera abiarazi. Mesedez, egiaztatu baimenak eta ziurtatu beste aplikazio batek ez duela erabiltzen. Irudiak atera Atera PDFetan txertatutako irudiak jatorrizko bereizmenarekin PDF fitxategi honek ez du kapsulatutako irudirik Tresna honek orrialde guztiak eskaneatzen ditu eta kalitate osoko iturburu-irudiak berreskuratzen ditu - ezin hobea dokumentuetatik jatorrizkoak gordetzeko Marraztu Sinadura Pen Params Erabili sinadura propioa dokumentuetan jartzeko irudi gisa Zip PDF Zatitu dokumentua emandako tartearekin eta paketatu dokumentu berriak zip artxiboan Tartea PDF inprimatu Prestatu dokumentua orri-tamaina pertsonalizatuarekin inprimatzeko Orrialde bakoitzeko orrialdeak Orientazioa Orrialdearen Tamaina Marjina Loraldia Belauna biguna Anime eta marrazki bizidunetarako optimizatua. Azkar igotzea kolore natural hobetuekin eta artefaktu gutxiagorekin Samsung One UI 7 bezalako estiloa Sartu hemen oinarrizko matematika-sinboloak nahi duzun balioa kalkulatzeko (adibidez, (5+5)*10) Matematikako adierazpena Jaso %1$s irudi Atzeko planoko kolorea Alfa formatuetarako Alfa laguntzarekin atzeko planoko kolorea ezartzeko gaitasuna gehitzen du, desgaituta dagoenean, alfa ez direnentzat bakarrik eskuragarri. Mantendu Data Ordua Gorde beti data eta orduarekin lotutako exif etiketak, mantendu exif aukeratik independenteki funtzionatzen du Proiektu irekia Jarraitu aurrez gordetako Image Toolbox proiektu bat editatzen Ezin da Image Toolbox proiektua ireki Image Toolbox proiektuari proiektuaren datuak falta zaizkio Image Toolbox proiektua hondatuta dago Onartzen ez den Image Toolbox proiektuaren bertsioa: %1$d Gorde proiektua Gorde geruzak, atzeko planoa eta editatu historia proiektu editagarri batean Ezin izan da ireki Idatzi bila daitekeen PDF batean Ezagutu irudi sortako testua eta gorde PDF bilagarria irudiarekin eta hauta daitekeen testu geruzarekin Geruza alfa Flip horizontala Flip bertikala Blokea Gehitu Itzala Itzalen Kolorea Testuaren Geometria Luzatu edo okertu testua estilizazio zorrotzagoa lortzeko X eskala Okertu X Kendu oharrak Kendu hautatutako ohar motak, hala nola estekak, iruzkinak, nabarmenduak, formak edo inprimaki-eremuak PDF orrietatik Hiperestekak Fitxategi eranskinak Lerroak Popup-ak Zigiluak Formak Testu-oharrak Testuaren markaketa Inprimaki-eremuak Markatzea Ezezaguna Oharpenak Destaldekatu Gehitu lausotutako itzala geruzaren atzean, kolore eta desplazamendu konfiguragarriekin ================================================ FILE: core/resources/src/main/res/values-fa/strings.xml ================================================ اندازه تصویری برای شروع انتخاب کنید مشکلی رخ داد: %1$s در حال بارگذاری… تصویر برای پیش نمایش بسیار بزرگ است، اما به هر حال ذخیره خواهد شد عرض %1$s ارتفاع %1$s اصلی حافظه نهان برداشتن پس‌زمینه وفاداری OCR (تشخیص متن) بهترین امتیاز توقف‌های رنگ افزودن رنگ میلی‌ثانیه فریم در ثانیه استفاده از طناب حالت امن محو میانه دامنه مرمر ماتریس رنگ ۳×۳ جلوه‌های ساده کیفیت پسوند نوع تغییر اندازه صریح انعطاف‌پذیر تصویر را انتخاب کنید آیا مطمئن هستید که می خواهید برنامه را ببندید؟ در حال بستن برنامه ماندن بستن بازنشانی تصویر تغییرات تصویر به مقادیر اولیه بازخواهد گشت مقادیر به درستی بازنشانی شدند بازنشانی مشکلی پیش آمد باز راه‌اندازی برنامه در تخته‌گیره رونوشت شد استثنا ویرایش EXIF تأیید هیچ داده EXIF یافت نشد افزودن برچسب ذخیره پاک کردن پاک کردن EXIF رد کردن تمام داده‌های EXIF تصویر پاک خواهند شد. این عمل قابل لغو نیست! پیش‌تنظیم‌ها برش در حال ذخیره اگر اکنون خارج شوید، همه تغییرات ذخیره نشده از بین خواهند رفت کد منبع آخرین به‌روز رسانی‌ها را دریافت کنید، درباره مسائل و موارد دیگر بحث کنید ویرایش تکی تغییر اندازه و ویرایش یک تصویر انتخابگر رنگ رنگ را از تصویر انتخاب کنید، کپی کنید یا به اشتراک بگذارید تصویر رنگ رنگ رونوشت شد تصویر را به هر حدی برش دهید نگارش نگه‌داری EXIF Images: %d تغییر پیش‌نمایش پاک کردن نمونه پالت رنگی را از تصویر داده شده ایجاد کنید تولید تخته‌رنگ رنگی از تصویر داده‌شده تخته‌رنگ به‌روزرسانی نگارش جدید %1$s نوع پشتیبانی‌نشده: %1$s نمی توان پالت برای تصویر داده شده ایجاد کرد اصلی پوشه خروجی پیش فرض سفارشی نامشخص حافظه دستگاه تغییر اندازه بر اساس وزن حداکثر اندازه در کیلوبایت اندازه یک تصویر را با توجه به اندازه داده شده در کیلوبایت تغییر دهید مقایسه دو تصویر داده شده را مقایسه کنید دو تصویر برای شروع انتخاب کنید انتخاب تصاویر تنظیمات حالت شب تیره روشن سیستم رنگ‌های پویا سفارشی‌سازی اجازه هماهنگی رنگ تصویر اگر فعال باشد، وقتی تصویری را برای ویرایش انتخاب می‌کنید، رنگ‌های برنامه برای این تصویر انتخاب می‌شوند زبان حالت کاملا تیره در صورت فعال بودن رنگ سطوح در حالت شب روی تاریکی مطلق تنظیم می شود طرح رنگی قرمز سبز آبی یک کد رنگ aRGB معتبر را جایگذاری کنید چیزی برای چسباندن نیست در حالی که رنگ‌های پویا روشن هستند، نمی‌توان طرح رنگ برنامه را تغییر داد طرح زمینه برنامه بر اساس رنگ انتخاب شده خواهد بود درباره برنامه هیچ به روز رسانی پیدا نشد ردیاب مشکلات گزارش اشکال و درخواست ویژگی را اینجا ارسال کنید کمک به ترجمه اشتباهات ترجمه را تصحیح کنید یا پروژه را به زبان های دیگر بومی سازی کنید با درخواست شما چیزی پیدا نشد اینجا جستجو کنید اگر فعال باشد، رنگ‌های برنامه برای رنگ‌های کاغذدیواری استفاده می‌شوند ذخیره %d تصویر(ها) ناموفق بود رایانامه سومین فرعی ضخامت حاشیه سطح مقادیر افزودن دسترسی دادن برنامه نیاز به دسترسی به فضای ذخیره سازی شما برای ذخیره تصاویر برای کار دارد، لازم است. لطفاً در کادر محاوره ای بعدی مجوز بدهید. برنامه برای کار به این مجوز نیاز دارد، لطفاً آن را به صورت دستی اعطا کنید حافظه خارجی رنگ‌های پویا این برنامه کاملا رایگان است، اما اگر می خواهید از توسعه پروژه پشتیبانی کنید، می توانید اینجا کلیک کنید تراز دکمه شناور به روز رسانی را بررسی کنید در صورت فعال بودن، گفتگوی به‌روزرسانی هنگام راه‌اندازی برنامه به شما نشان داده می‌شود بزرگنمایی تصویر هم‌رسانی پیشوند نام پرونده شکلک انتخاب کنید کدام ایموجی در صفحه اصلی نمایش داده شود افزودن اندازه پرونده در صورت فعال بودن، عرض و ارتفاع تصویر ذخیره شده را به نام فایل خروجی اضافه می کند برداشتن EXIF ابرداده EXIF را از هر مجموعه ای از تصاویر حذف کنید پیش‌نمایش تصویر پیش نمایش هر نوع تصویر: GIF، SVG، و غیره منبع تصویر انتخاب‌گر عکس آلبوم عکس جستجوگر فایل انتخابگر عکس مدرن اندروید که در پایین صفحه ظاهر می شود، ممکن است فقط در اندروید 12+ کار کند. در دریافت فراداده EXIF مشکل دارد انتخابگر تصویر گالری ساده. فقط در صورتی کار می کند که برنامه ای داشته باشید که انتخاب رسانه را ارائه دهد از قصد GetContent برای انتخاب تصویر استفاده کنید. در همه جا کار می کند، اما مشخص است که در دریافت تصاویر انتخابی در برخی از دستگاه ها مشکلاتی دارد. این تقصیر من نیست چیدمان گزینه‌ها ویرایش ترتیب ترتیب ابزار ها را در صفحه اصلی تعیین می کند تعداد شکلک‌ها شماره ترتیب نام پرونده اصلی افزودن نام پرونده اصلی اگر فعال باشد، نام فایل اصلی را به نام تصویر خروجی اضافه می کند جایگزینی شماره ترتیب اگر فعال باشد، اگر از پردازش دسته‌ای استفاده می‌کنید، مهر زمانی استاندارد را به شماره توالی تصویر جایگزین می‌کند اگر منبع تصویر انتخابگر عکس انتخاب شود، افزودن نام فایل اصلی کار نمی‌کند بارگذاری تصویر از وب هر تصویری را از اینترنت برای پیش نمایش، زوم، ویرایش و ذخیره آن در صورت تمایل بارگیری کنید. بدون تصویر پیوند تصویر پر کردن تناسب مقیاس محتوا اندازه تصاویر را به ارتفاع و عرض داده شده تغییر دهید. نسبت ابعاد تصاویر ممکن است تغییر کند. تصاویر را با ضلع بلند به ارتفاع یا عرض داده شده تغییر اندازه می‌دهد. تمام محاسبات اندازه پس از ذخیره انجام می‌شود. نسبت ابعاد تصاویر حفظ خواهد شد. روشنایی تضاد رنگ‌مایه اشباع افزودن پالایه پالایه هر زنجیره فیلتر را روی تصاویر داده شده اعمال کنید پالایه‌ها سبک پالایه رنگ آلفا نوردهی تراز سفیدی دما ته‌رنگ تک‌رنگ گاما هایلایت و سایه ها هایلایت‌ها سایه‌ها مه جلوه فاصله شیب تیز کردن سپیا نفی خورشیدی‌سازی سرزندگی سیاه و سفید هاشور متقاطع فاصله‌گذاری عرض خط لبه سوبل محو نیم‌تون فضای رنگی CGA محو گاوسی محو جعبه‌ای تاری دو طرفه برجسته‌سازی لاپلاسین وینیت آغاز پایان کووهرا صاف کردن محو پشته‌ای شعاع مقیاس اعوجاج زاویه چرخش برآمدگی گشادسازی شکست کره‌ای شاخص شکست شکست کره شیشه‌ای ماتریس رنگ شفافیت تغییر اندازه با محدودیت اندازه تصاویر انتخاب شده را تغییر دهید تا از محدودیت های عرض و ارتفاع پیروی کنید و در عین حال نسبت ابعاد را ذخیره کنید طرح آستانه سطوح کوانتیزاسیون کارتونی نرم کارتونی پوسترسازی برداشتن غیرحداکثر گنجایش پیکسل ضعیف جستجو پیچیدگی 3x3 پالایه RGB رنگ کاذب رنگ اول رنگ دوم بازچینش محو سریع اندازه محو مرکز محو X مرکز محو Y محو بزرگ‌نمایی تعادل رنگ آستانه درخشندگی برنامه Files را غیرفعال کردید، برای استفاده از این ویژگی آن را فعال کنید نقاشی مانند یک کتاب طراحی روی تصویر بکشید یا روی خود پس زمینه بکشید رنگ قلم آلفای قلم نقاشی روی تصویر یک تصویر را انتخاب کنید و چیزی روی آن بکشید نقاشی روی پس‌زمینه رنگ پس زمینه را انتخاب کنید و بالای آن بکشید رنگ پس‌زمینه رمزنگاری هر فایل (نه تنها تصویر) را بر اساس الگوریتم‌های مختلف رمزنگاری موجود، رمزگذاری و رمزگشایی کنید انتخاب پرونده رمزنگاری رمزگشایی پرونده‌ای برای شروع انتخاب کنید رمزگشایی رمزنگاری کلید فایل پردازش شد این فایل را در دستگاه خود ذخیره کنید یا از اقدام اشتراک گذاری برای قرار دادن آن در هر کجا که می خواهید استفاده کنید ویژگی‌ها پیاده‌سازی سازگاری رمزگذاری فایل ها بر اساس رمز عبور فایل های ادامه یافته را می توان در فهرست انتخابی ذخیره کرد یا به اشتراک گذاشت. فایل های رمزگشایی شده نیز می توانند مستقیما باز شوند. AES-256 با حالت GCM، بدون padding، IV تصادفی ۱۲ بایتی (به صورت پیش‌فرض، اما می‌توانید الگوریتم مورد نظر را انتخاب کنید). کلیدها به صورت هش SHA-3 با طول ۲۵۶ بیت استفاده می‌شوند. اندازه پرونده The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. لطفاً توجه داشته باشید که سازگاری با سایر نرم افزارها یا خدمات رمزگذاری فایل تضمین نمی شود. یک درمان کلیدی یا پیکربندی رمز کمی متفاوت ممکن است باعث ناسازگاری شود. رمز عبور نامعتبر یا فایل انتخابی رمزگذاری نشده است تلاش برای ذخیره تصویر با عرض و ارتفاع معین ممکن است باعث خطای OOM شود. این کار را با مسئولیت خود انجام دهید و نگویید من به شما هشدار ندادم! اندازه حافظه نهان پیدا شد %1$s پاک‌سازی خودکار حافظه نهان در صورت فعال بودن حافظه پنهان برنامه هنگام راه اندازی برنامه پاک می شود ساختن ابزارها گروه‌بندی گزینه‌ها بر پایه نوع به جای ترتیب فهرست سفارشی، گزینه‌ها را بر اساس نوعشان در صفحه اصلی گروه‌بندی می‌کند وقتی گروه بندی گزینه ها فعال است، نمی توان ترتیب را تغییر داد ویرایش عکس از صفحه سفارشی‌سازی فرعی عکس از صفحه گزینه جایگزین رد کردن رونوشت ذخیره در حالت %1$s می تواند ناپایدار باشد، زیرا یک قالب بدون ضرر است اگر 125 از پیش تعیین شده را انتخاب کرده باشید، تصویر به اندازه 125% اندازه تصویر اصلی ذخیره می شود. اگر 50 از پیش تعیین شده را انتخاب کنید، تصویر با اندازه 50٪ ذخیره می شود پیش‌تنظیم در اینجا درصد فایل خروجی را تعیین می‌کند، یعنی اگر 50 از پیش تعیین شده را روی تصویر 5 مگابایتی انتخاب کنید، پس از ذخیره کردن تصویر 2.5 مگابایتی دریافت خواهید کرد. تصادفی‌سازی نام پرونده اگر فعال باشد نام فایل خروجی کاملا تصادفی خواهد بود در پوشه %1$s با نام %2$s ذخیره شد در پوشه %1$s ذخیره شد چت تلگرام در مورد برنامه بحث کنید و از سایر کاربران بازخورد دریافت کنید. همچنین می‌توانید به‌روزرسانی‌ها و اطلاعات آماری بتا را در اینجا دریافت کنید. ماسک برش نسبت ابعاد از این نوع ماسک برای ایجاد ماسک از تصویر داده شده استفاده کنید، توجه کنید که باید کانال آلفا داشته باشد پشتیبان‌گیری و بازیابی پشتیبان‌گیری بازیابی از تنظیمات برنامه خود در یک فایل نسخه پشتیبان تهیه کنید تنظیمات برنامه را از فایلی که قبلا ایجاد شده است بازیابی کنید فایل خراب است یا نسخه پشتیبان ندارد تنظیمات با موفقیت بازیابی شدند تماس با من با این کار تنظیمات شما به مقادیر پیش فرض برمی گردد. توجه داشته باشید که این کار بدون فایل پشتیبان ذکر شده در بالا قابل واگرد نیست. پاک کردن شما در حال حذف طرح رنگ انتخابی هستید. این عملیات قابل برگشت نیست حذف طرح قلم متن مقیاس قلم پیش فرض استفاده از مقیاس‌های فونت بزرگ ممکن است باعث اشکالات و مشکلات رابط کاربری شود که رفع نمی‌شوند. با احتیاط استفاده کنید. ا ب پ ت ث ج چ ح خ د ذ ر ز ژ س ش ص ض ط ظ ع غ ف ق ک گ ل م ن و ه ی 0123456789 !؟ احساسات غذا و نوشیدنی طبیعت و حیوانات اشیاء نمادها فعال‌سازی شکلک سفرها و مکان‌ها فعالیت‌ها با کشیدن پس زمینه از تصویر حذف کنید یا از گزینه Auto استفاده کنید کوتاه کردن تصویر ابرداده تصویر اصلی حفظ خواهد شد فضاهای شفاف اطراف تصویر کوتاه خواهند شد برداشتن خودکار پس‌زمینه بازیابی تصویر حالت پاک کردن پاک کردن پس‌زمینه بازیابی پس‌زمینه شعاع محو پیپت حالت نقاشی ایجاد مسئله اوه… مشکلی پیش آمد. می توانید با استفاده از گزینه های زیر برای من بنویسید و من سعی خواهم کرد راه حلی پیدا کنم تغییر اندازه و تبدیل اندازه تصاویر داده شده را تغییر دهید یا آنها را به فرمت های دیگر تبدیل کنید. در صورت انتخاب یک تصویر واحد، ابرداده EXIF را نیز می توان در اینجا ویرایش کرد. حداکثر تعداد رنگ این به برنامه اجازه می دهد تا گزارش های خرابی را به صورت دستی جمع آوری کند تحلیل جمع آوری آمار استفاده از برنامه ناشناس را مجاز کنید در حال حاضر، قالب %1$s فقط اجازه خواندن فراداده EXIF را در اندروید می دهد. تصویر خروجی در صورت ذخیره به هیچ وجه متادیتا نخواهد داشت. تلاش مقدار %1$s به معنای فشرده سازی سریع است که در نتیجه اندازه فایل نسبتاً بزرگی ایجاد می شود. %2$s به معنای فشرده سازی کندتر است که در نتیجه فایل کوچکتری ایجاد می شود. صبر کنید ذخیره تقریباً کامل شده است. لغو اکنون نیازمند ذخیره دوباره است. به‌روزرسانی‌ها اجازه نسخه‌های بتا در صورت فعال بودن، بررسی به‌روزرسانی شامل نسخه‌های برنامه بتا می‌شود نقاشی پیکان‌ها اگر فعال باشد مسیر ترسیم به صورت فلش اشاره گر نشان داده می شود نرمی قلم تصاویر به اندازه وارد شده در مرکز برش داده می شوند. اگر تصویر کوچکتر از ابعاد وارد شده باشد، بوم با رنگ پس زمینه داده شده بزرگ می شود. کمک مالی ترکیب تصاویر تصاویر داده شده را با هم ترکیب کنید تا یک تصویر بزرگ بدست آورید حداقل ۲ تصویر انتخاب کنید مقیاس تصویر خروجی جهت‌گیری تصویر افقی عمودی تصاویر کوچک را به بزرگ تبدیل کنید در صورت فعال بودن، تصاویر کوچک به بزرگ‌ترین آن‌ها در توالی مقیاس می‌شوند ترتیب تصاویر عادی محو کردن لبه‌ها لبه های تار را زیر تصویر اصلی می کشد تا در صورت فعال بودن، به جای تک رنگ، فضاهای اطراف آن را پر کند پیکسل‌سازی پیکسل‌سازی پیشرفته پیکسل‌سازی خطی پیکسل‌سازی الماسی پیشرفته پیکسل‌سازی الماسی پیکسل‌سازی دایره‌ای پیکسل‌سازی دایره‌ای پیشرفته جایگزینی رنگ تحمل رنگ برای جایگزینی رنگ هدف رنگ برای برداشتن برداشتن رنگ رمزنگاری مجدد اندازه پیکسل قفل جهت‌گیری نقاشی اگر در حالت طراحی فعال باشد، صفحه نمی‌چرخد بررسی به‌روزرسانی‌ها سبک تخته‌رنگ نقطه تونال خنثی پرجنب‌وجوش بیانگر رنگین‌کمان سالاد میوه محتوا سبک پالت پیش‌فرض، امکان سفارشی کردن هر چهار رنگ را فراهم می‌کند، بقیه به شما اجازه می‌دهند فقط رنگ کلید را تنظیم کنید سبکی که کمی رنگی تر از تک رنگ است تم بلند، رنگارنگی برای پالت اصلی حداکثر است، برای سایرین افزایش یافته است یک تم بازی - رنگ منبع رنگ در طرح زمینه ظاهر نمی شود یک تم تک ، رنگ ها کاملا سیاه / سفید / خاکستری هستند طرحی که رنگ منبع را در Scheme.primaryContainer قرار می دهد طرحی که شباهت زیادی به طرح محتوا دارد این بررسی کننده به روز رسانی به GitHub متصل می شود تا بررسی کند آیا به روز رسانی جدیدی در دسترس است یا خیر توجه لبه‌های محو هر دو غیرفعال معکوس کردن رنگ‌ها در صورت فعال بودن، رنگ های طرح زمینه را با رنگ های منفی جایگزین می کند جستجو کردن امکان جستجو در تمام ابزار های موجود در صفحه اصلی را فعال می کند ابزارهای PDF کار با فایل های PDF: پیش نمایش، تبدیل به دسته ای از تصاویر یا ایجاد یکی از تصاویر داده شده پیش‌نمایش PDF PDF به تصاویر تصاویر به PDF پیش نمایش PDF ساده تبدیل PDF به تصاویر در فرمت خروجی داده شده تصاویر داده شده را در فایل PDF خروجی بسته بندی کنید پالایه ماسک زنجیر فیلتر را روی نواحی پوشانده شده اعمال کنید، هر ناحیه ماسک می تواند مجموعه فیلترهای خود را تعیین کند ماسک‌ها افزودن ماسک ماسک %d رنگ ماسک پیش‌نمایش ماسک ماسک فیلتر کشیده شده برای نشان دادن نتیجه تقریبی به شما ارائه می شود نوع پر کردن معکوس اگر فعال باشد، به جای رفتار پیش‌فرض، تمام مناطق بدون ماسک فیلتر می‌شوند شما در حال حذف ماسک فیلتر انتخابی هستید. این عملیات قابل برگشت نیست حذف ماسک پالایه کامل هر زنجیره فیلتر را روی تصاویر داده شده یا تک تصویر اعمال کنید شروع کنید مرکز پایان انواع ساده هایلایتر نئون قلم محو حریم خصوصی مسیرهای هایلایتر تیز شده نیمه شفاف را رسم کنید جلوه ای درخشان به نقاشی های خود اضافه کنید پیش‌فرض، ساده‌ترین - فقط رنگ تصویر زیر مسیر ترسیم شده را محو می کند تا هر چیزی را که می خواهید پنهان کنید ایمن کنید شبیه محو کردن حریم خصوصی است، اما به جای محو کردن، پیکسل می‌شود ظروف طراحی سایه پشت کانتینرها را فعال می کند لغزنده سوئیچ ها FAB ها دکمه ها طراحی سایه پشت لغزنده را فعال می کند طراحی سایه پشت سوئیچ ها را فعال می کند طراحی سایه پشت دکمه‌های عمل شناور را فعال می‌کند طراحی سایه پشت دکمه های پیش فرض را فعال می کند نوارهای برنامه طراحی سایه در پشت نوارهای برنامه را فعال می کند مقدار در محدوده %1$s - %2$s چرخش خودکار اجازه می دهد تا جعبه محدودیت برای جهت گیری تصویر اتخاذ شود حالت مسیر نقاشی پیکان خط دو سویه نقاشی آزاد پیکان دو سویه پیکان خطی پیکان خط مسیر را به عنوان مقدار ورودی ترسیم می کند مسیر را از نقطه شروع به نقطه پایان به عنوان یک خط رسم می کند فلش اشاره گر را از نقطه شروع به نقطه پایان به عنوان یک خط رسم می کند فلش اشاره گر را از یک مسیر مشخص می کشد فلش های دوگانه را از نقطه شروع به نقطه پایان به عنوان یک خط رسم می کند فلش های دو نشان دهنده را از یک مسیر مشخص می کشد بیضی خطی مستطیل خطی بیضی مستطیل راست را از نقطه شروع به نقطه پایان رسم می کند بیضی شکل را از نقطه شروع تا نقطه پایان ترسیم می کند بیضی شکل را از نقطه شروع تا نقطه پایان ترسیم می کند رئوس مطالب را از نقطه شروع تا پایان رسم می کند طناب مسیر پر بسته را بر اساس مسیر داده شده ترسیم می کند آزاد شبکه افقی شبکه عمودی حالت ترکیب تعداد ردیف‌ها تعداد ستون‌ها دایرکتوری \"%1$s\" یافت نشد، ما آن را به یک فهرست پیش فرض تغییر دادیم، لطفاً فایل را دوباره ذخیره کنید بریده‌دان سنجاق خودکار در صورت فعال بودن، به طور خودکار تصویر ذخیره شده را به کلیپ بورد اضافه می کند لرزش قدرت لرزش برای بازنویسی فایل‌ها باید از منبع تصویر \"Explorer\" استفاده کنید، تصاویر را دوباره انتخاب کنید، ما منبع تصویر را به منبع مورد نیاز تغییر داده‌ایم بازنویسی پرونده‌ها فایل اصلی به جای ذخیره در پوشه انتخابی با فایل جدید جایگزین می شود، این گزینه باید منبع تصویر \"Explorer\" یا GetContent باشد، در صورت تغییر دادن این، به طور خودکار تنظیم می شود. خالی پسوند حالت مقیاس دوخطی کت‌مال دومکعبی هان هرمیت لانکزوس میچل نزدیک‌ترین منحنی پایه مقدار پیش‌فرض درون یابی خطی (یا دو خطی، در دو بعدی) معمولاً برای تغییر اندازه یک تصویر خوب است، اما باعث نرم شدن نامطلوب جزئیات می شود و هنوز هم می تواند تا حدودی ناهموار باشد. روش‌های مقیاس‌بندی بهتر شامل نمونه‌برداری مجدد Lanczos و فیلترهای Mitchell-Netravali است یکی از راه‌های ساده‌تر افزایش اندازه، جایگزینی هر پیکسل با تعدادی پیکسل همرنگ ساده‌ترین حالت مقیاس‌بندی اندروید که تقریباً در همه برنامه‌ها استفاده می‌شود روشی برای درونیابی هموار و نمونه برداری مجدد مجموعه ای از نقاط کنترل که معمولاً در گرافیک کامپیوتری برای ایجاد منحنی های صاف استفاده می شود. تابع پنجره اغلب در پردازش سیگنال برای به حداقل رساندن نشت طیفی و بهبود دقت تجزیه و تحلیل فرکانس با باریک کردن لبه‌های سیگنال استفاده می‌شود. تکنیک درون یابی ریاضی که از مقادیر و مشتقات در نقاط انتهایی یک بخش منحنی برای ایجاد یک منحنی صاف و پیوسته استفاده می کند. روش نمونه گیری مجدد که درون یابی با کیفیت بالا را با اعمال تابع sinc وزنی به مقادیر پیکسل حفظ می کند. روش نمونه برداری مجدد که از فیلتر پیچشی با پارامترهای قابل تنظیم برای دستیابی به تعادل بین وضوح و ضد aliasing در تصویر مقیاس شده استفاده می کند. از توابع چند جمله ای تعریف شده تکه ای برای درون یابی و تقریب هموار یک منحنی یا سطح، نمایش شکل انعطاف پذیر و پیوسته استفاده می کند. فقط برش ذخیره در فضای ذخیره سازی انجام نمی شود و سعی می شود تصویر فقط در کلیپ بورد قرار داده شود براش به جای پاک کردن پس زمینه را بازیابی می کند تشخیص متن از تصویر داده شده، بیش از 120 زبان پشتیبانی می شود تصویر متنی ندارد یا برنامه آن را پیدا نکرده است دقت: %1$s نوع تشخیص سریع استاندارد بدون داده برای عملکرد صحیح Tesseract OCR باید داده های آموزشی اضافی (%1$s) در دستگاه شما دانلود شود. \nآیا می خواهید داده های %2$s را دانلود کنید؟ بارگیری بدون اتصال، آن را بررسی کنید و دوباره برای بارگیری مدل‌های آموزشی تلاش کنید زبان‌های بارگیری‌شده زبان‌های در دسترس حالت تقسیم‌بندی استفاده از کلید شبیه پیکسل گوگل از یک سوئیچ شبیه به گوگل پیکسل استفاده می‌کند فایل بازنویسی شده با نام %1$s در مقصد اصلی ذره‌بین برای دسترسی بهتر، ذره بین را در بالای انگشت در حالت های طراحی فعال می کند اجباری مقدار اولیه ویجت exif را وادار می کند که در ابتدا بررسی شود اجازه چندین زبان لغزش کنار هم ضربه برای تغییر شفافیت امتیاز دادن به برنامه این برنامه کاملا رایگان است، اگر می خواهید بزرگتر شود، لطفا پروژه را در Github ستاره دار کنید 😄 جهت & فقط تشخیص اسکریپت جهت گیری خودکار & تشخیص اسکریپت فقط خودکار خودکار تک ستونی متن عمودی تک بلوکی تک بلوک تک خط تک کلمه دور کلمه کاراکتر تک متن پراکنده جهت متن پراکنده & تشخیص اسکریپت خط خام آیا می‌خواهید داده‌های آموزش OCR زبان \"%1$s\" را برای همه انواع تشخیص حذف کنید یا فقط برای یک انتخاب شده (%2$s)؟ جاری همه سازنده شیب شیب اندازه خروجی داده شده را با رنگ های سفارشی و نوع ظاهر ایجاد کنید خطی شعاعی جارو کردن نوع شیب مرکز X مرکز Y حالت کاشی تکرار شد آینه گیره برگردان ویژگی‌ها اجرای روشنایی صفحه همپوشانی گرادیان هر شیب بالای تصاویر داده شده را بنویسید تبدیل‌ها دوربین گرفتن یک عکس با استفاده از دوربین، توجه داشته باشید که دریافت تنها یک تصویر از این منبع تصویر امکان پذیر است علامت‌گذاری تصاویر را با واترمارک های متنی/تصویر قابل تنظیم بپوشانید تکرار علامت واترمارک را روی تصویر به جای تک در موقعیت مشخص تکرار می کند اختلاف X اختلاف Y نوع علامت این تصویر به عنوان الگو برای واترمارک استفاده خواهد شد رنگ متن حالت پوشش ابزارهای GIF تصاویر را به تصویر GIF تبدیل کنید یا فریم هایی را از تصویر GIF داده شده استخراج کنید GIF به تصاویر تبدیل فایل GIF به دسته ای از تصاویر تبدیل دسته ای از تصاویر به فایل GIF تصاویر به GIF برای شروع تصویر GIF را انتخاب کنید از اندازه فریم اول استفاده کنید اندازه مشخص شده را با ابعاد قاب اول جایگزین کنید تعداد تکرار تاخیر فریم از Lasso مانند در حالت ترسیم برای انجام پاک کردن استفاده می کند آلفای پیش‌نمایش تصویر اصلی کاغذ رنگی Confetti در ذخیره، اشتراک گذاری و سایر اقدامات اولیه نشان داده می شود محتوای برنامه را در برنامه‌های اخیر پنهان می‌کند. نمی‌توان آن را ضبط یا ثبت کرد. خروج اگر اکنون پیش‌نمایش را ترک کنید، باید دوباره تصاویر را اضافه کنید دیترینگ کوانتیزر مقیاس خاکستری بایر دو در دو دیترینگ بایر سه در سه دیترینگ بایر چهار با چهار دیترینگ Bayer Eight By Eight Dithering دیترینگ فلویید-اشتاینبرگ دیترینگ جارویس-جودیس-نینکه دیترینگ سیرا دیترینگ سیرا دو ردیفه دیترینگ سیرا لایت دیترینگ اتکینسون دیترینگ استوکی دیترینگ بورکس دیترینگ فلویید-اشتاینبرگ کاذب دیترینگ چپ به راست دیترینگ تصادفی دیترینگ آستانه ساده سیگما سیگمای فضایی منحنی-ب از توابع چند جمله ای دو مکعبی تعریف شده تکه ای برای درون یابی و تقریب هموار یک منحنی یا سطح، نمایش شکل انعطاف پذیر و پیوسته استفاده می کند. محو پشته‌ای بومی تغییر شیب گلیچ مقدار دانه آناگلیف نویز مرتب‌سازی پیکسل مخلوط کردن گلیچ پیشرفته جابه‌جایی کانال X جابه‌جایی کانال Y اندازه خرابی جابه‌جایی خرابی X جابه‌جایی خرابی Y محو چادری محو کناری کنار بالا پایین قدرت فرسایش انتشار ناهمسان انتشار هدایت لرزش باد افقی تاری دوطرفه سریع محو پواسون نقشه‌برداری تون لگاریتمی نقشه‌برداری تون فیلمک ACES متبلور کردن رنگ خط شیشه ناصاف آشوب روغن جلوه آب اندازه فرکانس X فرکانس Y دامنه X دامنه Y اعوجاج پرلین نقشه‌برداری تون هیل ACES نقشه‌برداری تون فیلمک هابل نقشه‌برداری تون هجی-بورگس سرعت مه‌زدایی امگا ماتریس رنگ ۴×۴ عکس فوری آبی‌دشواربینی سبزدشواربینی قرمز دشواربینی قدیمی شکلاتی کودا کروم دید در شب گرم خنک آبی‌ کوری سبزکوری سرخ کوری شبه کور رنگی کور رنگی دانه نا تیز پاستل مه نارنجی رؤیای صورتی ساعت طلایی تابستان گرم مه بنفش طلوع آفتاب چرخش رنگارنگ نور بهاری نرم تون‌های پاییزی رؤیای اسطوخودوس سایبرپانک نور لیموناد آتش طیفی جادوی شب چشم‌انداز فانتزی انفجار رنگ شیب الکتریکی تاریکی کارامل شیب آینده‌نگر خورشید سبز جهان رنگین‌کمان بنفش عمیق پورتال فضایی چرخش قرمز کد دیجیتال بوکه ایموجی نوار برنامه به صورت تصادفی تغییر خواهد کرد شکلک‌های تصادفی در حالی که ایموجی‌ها غیرفعال هستند، نمی‌توانید از ایموجی‌های تصادفی استفاده کنید نمیتوانید شکلک را انتخاب کنید در حالی که شکلک‌های تصادفی فعال هستند تلویزیون قدیمی محو شافل برگزیدن هنوز هیچ پالایه برگزیده‌ای اضافه نشده است قالب تصویر یک ظرف با شکل انتخاب شده در زیر آیکون‌ها اضافه می‌کند شکل نماد دراگو آلدریج کات‌آف اوچیمورا موبیوس انتقال پیک ناهنجاری رنگ تصاویر در مقصد اصلی بازنویسی شدند وقتی گزینه بازنویسی فایل ها فعال است، قالب تصویر را نمی توان تغییر داد شکلک به‌عنوان طرح رنگی از رنگ اصلی ایموجی به‌عنوان طرح رنگ برنامه به‌جای تعریف دستی استفاده می‌کند پالت \"Material You\" را از تصویر ایجاد می کند رنگ‌های تیره از طرح رنگ حالت شب به جای نوع نور استفاده می کند کپی به عنوان\" Jetpack Compose \" کد محو حلقه‌ای محو صلیبی محو دایره‌ای محو ستاره‌ای تغییر شیب خطی برچسب‌های برای حذف تصاویر را به تصویر APNG تبدیل کنید یا فریم هایی را از تصویر APNG داده شده استخراج کنید ابزارهای APNG APNG به تصاویر تبدیل فایل APNG به دسته ای از تصاویر تبدیل دسته ای از تصاویر به APNG تصاویر به APNG برای شروع تصاویر APNG را انتخاب کنید محو حرکتی زیپ ساخت فایل Zip از تصاویر یا فایل داده شده نوع کنفتی جشنواره‌ای انفجار باران دوباره تلاش کنید نمایش تنظیمات در حالت افقی اگر این غیرفعال باشد، تنظیمات حالت افقی مانند همیشه به جای گزینه قابل مشاهده دائم، روی دکمه در نوار بالای برنامه باز می شود. ابزارهای JXL JXL به JPEG رمزگذاری بدون اتلاف از JXL به JPEG رمزگذاری بدون اتلاف از JPEG به JXL JPEG به JXL برای شروع تصاویر JXL را انتخاب کنید همه ویژگی ها روی مقادیر پیش فرض تنظیم می شوند، توجه داشته باشید که این عمل قابل واگرد نیست انتخاب تنظیمات تمام‌صفحه آن را فعال کنید و صفحه تنظیمات همیشه به‌جای برگه کشویی به‌صورت تمام‌صفحه باز می‌شود نوع کلید کامپوز انتخاب چند رسانه انتخاب رسانه تکی گوشه‌ها رمزگذاری JXL ~ JPEG را بدون افت کیفیت انجام دهید یا انیمیشن GIF/APNG را به JXL تبدیل کنید محو گاوسی سریع سه‌بعدی سطح هماهنگ‌سازی تصاویر به JXL رد کردن انتخاب پرونده در صورت امکان، انتخابگر فایل بلافاصله در صفحه انتخاب شده نشان داده می شود محو گاوسی سریع چهاربعدی تولید پیش‌نمایش را فعال می‌کند، ممکن است به جلوگیری از خرابی در برخی دستگاه‌ها کمک کند، همچنین برخی از قابلیت‌های ویرایش را در گزینه ویرایش واحد غیرفعال می‌کند. فشرده‌سازی با افت از فشرده سازی با اتلاف برای کاهش حجم فایل به جای فشرده سازی بدون اتلاف استفاده می کند نوع فشرده‌سازی سرعت رمزگشایی تصویر حاصل را کنترل می‌کند، این باید به باز کردن سریع‌تر تصویر حاصل کمک کند، مقدار %1$s به معنای کندترین رمزگشایی است، در حالی که %2$s - سریع‌ترین، این تنظیم ممکن است اندازه تصویر خروجی را افزایش دهد. نوع فشرده‌سازی امروز دیروز تاریخ (معکوس) بدون دسترسی درخواست تبدیل تصاویر داده شده به تصاویر SVG استفاده از تخته‌رنگ نمونه‌برداری‌شده استفاده از این ابزار برای ردیابی تصاویر بزرگ بدون کاهش مقیاس توصیه نمی شود، می تواند باعث کرش و افزایش زمان پردازش شود. کلید Material You محو گاوسی سریع دوبعدی حداکثر لنگر تغییر اندازه پیکسل روان از سوییچ سبک ویندوز 11 بر اساس سیستم طراحی \"Fluent\" استفاده می کند از سوئیچ مانند iOS بر اساس سیستم طراحی کوپرتینو استفاده می کند پیکربندی کانال‌ها انتخاب‌گر تصویر جعبه‌ابزار تصویر انتخابگر تصویر در Image Toolbox جای‌گذاری خودکار به برنامه اجازه می‌دهد تا داده‌های کلیپ‌بورد را به‌طور خودکار جای‌گذاری کند، بنابراین در صفحه اصلی ظاهر می‌شود و می‌توانید آن‌ها را پردازش کنید. رنگ هماهنگ‌سازی روش نمونه‌گیری مجدد که درون یابی با کیفیت بالا را با اعمال تابع Bessel (jinc) به مقادیر پیکسل حفظ می‌کند. GIF به JXL تبدیل تصاویر GIF به تصاویر متحرک JXL APNG به JXL تبدیل تصاویر APNG به تصاویر متحرک JXL JXL به تصاویر تبدیل انمیشن JXL به دسته ای از تصاویر تبدیل دسته ای از تصاویر به انیمیشن JXL رفتار تولید پیش‌نمایش‌ها مرتب‌سازی تاریخ نام نام (معکوس) تصاویر به SVG اگر این گزینه فعال باشد از پالت Quantization نمونه برداری می شود برداشتن مسیر کاهش مقیاس تصویر تصویر قبل از پردازش به ابعاد پایین‌تر کاهش می‌یابد، این به ابزار کمک می‌کند تا سریع‌تر و ایمن‌تر کار کند حداقل نسبت رنگ آستانه خطوط آستانه درجه دوم تحمل گرد کردن مختصات مقیاس مسیر بازنشانی ویژگی‌ها افزودن پوشه جدید بیت ها در نمونه فشرده سازی نمونه ها در پیکسل عرض خط پیش‌فرض حالت موتور شبکه LSTM میراث میراث و LSTM تبدیل کمترین توضیحات QR تبدیل تصاویر به فرمت داده شده تفسیر فتومتریک رزولوشن X رزولوشن Y واحد رزولوشن تغییر فرمت JPEG تابع انتقال نقطه سفید مرجع سیاه سفید توضیحات تصویر ساختن مدل نرم افزار هنرمند نسخه Exif نسخه Flashpix گاما فضای رنگی ابعاد پیکسل X بیت های فشرده در هر پیکسل یادداشت ساز ابعاد پیکسل Y فایل صوتی مرتبط تاریخ زمان اصلی تاریخ زمان دیجیتالی شدن در تنظیمات برای اسکن کد QR به دوربین اجازه دهید پیش نمایش لینک ها بازیابی پیش‌نمایش پیوند را در مکان‌هایی که می‌توانید متن دریافت کنید (کد QR، OCR و غیره) را فعال می‌کند. تبدیل دسته ای از تصاویر از یک فرمت به فرمت دیگر تبدیل فرمت مقیاس فضای رنگ GIF به WEBP تبدیل تصاویر GIF به تصاویر متحرک WEBP WEBP به تصاویر تبدیل فایل WEBP به دسته ای از تصاویر دسته ای از تصاویر را به فایل WEBP تبدیل کنید تصاویر به WEBP تصویر WEBP را برای شروع انتخاب کنید کد QR اسکن شده یک الگوی فیلتر معتبر نیست اسکن QR کد QR کد به عنوان تصویر QR کد کد QR را برای جایگزینی محتوا در فیلد اسکن کنید، یا چیزی را برای تولید کد QR جدید تایپ کنید هیچ گزینه دلخواه انتخاب نشده است، آنها را در صفحه ابزار اضافه کنید ابزارهای WEBP ابزار رنگ مخلوط کنید، رنگ بسازید، سایه ها ایجاد کنید و موارد دیگر تصاویر را به تصویر متحرک WEBP تبدیل کنید یا فریم هایی را از انیمیشن WEBP داده شده استخراج کنید ذخیره QR کد به عنوان تصویر کد QR را اسکن کنید و محتوای آن را دریافت کنید یا رشته خود را برای ایجاد کد جدید جایگذاری کنید یک روش درون یابی که یک تابع گاوسی را اعمال می کند و برای صاف کردن و کاهش نویز در تصاویر مفید است. نظر کاربر زمان زیر ثانیه سرعت ISO مقدار سرعت شاتر فلش انرژی فلش ImageToolbox در تلگرام 🎉 تولید نویز نویز های مختلفی مانند پرلین یا انواع دیگر تولید کنید نوع نویز خودکار پنهان کردن همه نمایش همه پنهان کردن نوار وضعیت تراز عدد F وایت بالانس وضوح نام مالک دوربین شناسه نسخه GPS ماهواره های GPS وضعیت GPS حالت اندازه گیری GPS سرعت GPS داده زمان حق چاپ زمان افست افست زمان اصلی زمان دیجیتالی افست نوع حساسیت فاصله موضوع حالت اندازه گیری پیش نمایش تصویر مجموعه LUT ها را دانلود کنید که پس از دانلود می توانید آن را اعمال کنید پیش نمایش تصویر پیش فرض را برای فیلترها تغییر دهید فریم ها را روی هم قرار ندهید ابزار به عنوان میانبر به صفحه اصلی راه‌انداز شما اضافه می‌شود، از ترکیب آن با تنظیمات «پرش از انتخاب فایل» برای دستیابی به رفتار مورد نیاز استفاده کنید. ایجاد میانبر عنوان صفحه اصلی انتخابگرهای فشرده محل موضوع هانینگ استخراج کننده کاور صدا برای شروع صدا را انتخاب کنید اجازه ورود از طریق فیلد متنی را بدهید ایجاد قالب یکسان سازی هیستوگرام پیکسلی نام قالب هارمونی رنگ همینگ نوع بارکد بارکد تولید شده اینجا خواهد بود بارکدی پیدا نشد هیچ جلدی یافت نشد انتخاب صدا ISO اندازه فونت اندازه واترمارک تکرار متن اندازه خط تیره خطی فیلتر قالب سایه رنگ تنوع رنگ ها رنگ های پاییزی رزولیشن رزولیشن Y رزولیشن X اوه… مشکلی پیش آمد روی اشتراک‌گذاری فایل گزارش برنامه کلیک کنید، این می‌تواند به من کمک کند تا مشکل را پیدا کنم و مشکلات را برطرف کنم ارسال گزارش پیکسل به پیکسل LUT نور شمع یک روش درونیابی با کیفیت بالا که برای تغییر اندازه طبیعی تصاویر بهینه شده و بین وضوح و روانی تعادل برقرار می‌کند تابع پنجره‌ای برای کاهش نشت طیفی با کاهش دامنه لبه‌های سیگنال، کاربردی در پردازش سیگنال یک روش درونیابی مبتنی بر اسپلاین که با استفاده از فیلتر 36 تپی نتایج روانی ارائه می‌دهد یک تابع پنجره ترکیبی که پنجره‌های بارتلت و هان را ترکیب می‌کند و برای کاهش نشتی طیفی در پردازش سیگنال استفاده می‌شود یک تابع پنجره که برای کاهش نشتی طیفی استفاده می‌شود و وضوح فرکانس خوبی در برنامه‌های پردازش سیگنال ارائه می‌دهد مرجع طول جغرافیایی GPS یک روش درونیابی مبتنی بر اسپلاین که با استفاده از فیلتر 16 تپی نتایج روانی ارائه می‌دهد یک گونه از پنجره هان که معمولاً برای کاهش نشت طیفی در کاربردهای پردازش سیگنال استفاده می‌شود مرجع ارتفاع GPS از توابع چندجمله‌ای تعریف‌شده تکه‌ای برای درون‌یابی و تقریب هموار یک منحنی یا سطح استفاده می‌کند، نمایش شکل انعطاف‌پذیر و پیوسته یک روش درونیابی مبتنی بر اسپلاین که با استفاده از فیلتر 64 تپی نتایج روانی ارائه می‌دهد یک تابع پنجره طراحی‌شده برای ارائه وضوح فرکانس بالا با کاهش نشتی طیفی، که اغلب در برنامه‌های پردازش سیگنال استفاده می‌شود بایپس بلیچ این تصویر برای تولید هیستوگرام‌های RGB و روشنایی استفاده خواهد شد عرض دستگیره کشیدن مقایسه چکسام‌ها، محاسبه هش‌ها و ایجاد رشته‌های هگز از فایل‌ها با استفاده از الگوریتم‌های مختلف هشینگ طول فرمت تبادل JPEG منبع فایل تقسیم تصویر لانکزوس بسل کاپرتینو جزئی پیکربندی صفحه‌ای نمونه‌برداری فرعی Y Cb Cr موقعیت Y Cb Cr آفست‌های نواری سطرها در هر نوار تعداد بایت‌های نوار کروماتیسیته‌های اولیه ضرایب Y Cb Cr OECF ایندکس توصیه شده نوردهی زمان اصلی زیر ثانیه زمان دیجیتالی زیر ثانیه زمان نوردهی برنامه نوردهی حساسیت طیفی حساسیت عکاسی حساسیت خروجی استاندارد عرض سرعت ایزو yyy عرض سرعت ایزو zzz مقدار دیافراگم مقدار روشنایی مقدار تعصب نوردهی مقدار حداکثر دیافراگم منطقه موضوعی طول کانونی پاسخ فرکانس مکانی وضوح صفحه کانونی X طول جغرافیایی GPS ارتفاع GPS مهر زمان GPS داده‌های نقشه GPS مرجع عرض جغرافیایی GPS عرض جغرافیایی GPS مرجع عرض جغرافیایی مقصد GPS روش پردازش GPS اطلاعات منطقه GPS یک تابع پنجره‌ای که با به حداقل رساندن نشت طیفی، وضوح فرکانسی خوبی ارائه می‌دهد و اغلب در پردازش سیگنال استفاده می‌شود یک روش که از یک تابع درجه دوم برای درونیابی استفاده می‌کند و نتایج روان و پیوسته‌ای ارائه می‌دهد یک روش پیشرفته نمونه‌برداری مجدد که درونیابی با کیفیت بالا و حداقل مصنوع ارائه می‌دهد یک تابع پنجره مثلثی که در پردازش سیگنال برای کاهش نشتی طیفی استفاده می‌شود یک نوع تیزتر از روش روبیدوکس که برای تغییر اندازه واضح تصاویر بهینه شده است یک روش درونیابی که از پنجره کایزر استفاده می‌کند و کنترل خوبی بر توازن بین پهنای لوب اصلی و سطح لوب جانبی فراهم می‌کند طرح ساده پوستر رنگی سه رنگه رنگ سوم فایل هدف 3D LUT (.cube / .CUBE) قابلیت مشاهده نوارهای سیستم نمایش نوارهای سیستم با کشیدن نور ملایم شناسه یکتای تصویر وضوح محور Y در صفحه کانونی واحد وضوح صفحه کانونی شاخص نوردهی روش حسگر الگو CFA پردازش سفارشی شده حالت نوردهی نسبت بزرگ‌نمایی دیجیتال فاصله کانونی در فیلم ۳۵ میلی‌متری نوع ثبت صحنه کنتراست اشباع دستگاه تنظیم توضیحات محدوده فاصله سوژه شماره سریال بدنه مشخصات لنز سازنده لنز مدل لنز شماره سریال لنز دقت موقعیت مکانی مرجع سرعت موقعیت مکانی مرکز مهر تاریخ GPS دیفرانسیل GPS وارد کردن لینک ستاره ستاره طرح دار آنتی‌آلیاس‌ها اسکنر اسناد برای شروع اسکن کلیک کنید شروع اسکن ذخیره به صورت pdf اشتراک گذاری به صورت pdf اندازه شبکه X اندازه شبکه Y برش تصویر ویرایش EXIF تغییر متادیتای یک تصویر بدون فشرده‌سازی مجدد برای ویرایش برچسب‌های موجود، ضربه بزنید یک Jetpack Compose Material که شما جابجا می کنید کنترل را به دست آورید کد مسیر GPS مسیر GPS Ref. GPS img Direction GPS img جهت GPS Dest Latitude GPS Dest Longitude Ref طول جغرافیایی مقصد GPS کد بلبرینگ مقصد GPS بلبرینگ مقصد GPS مرجع فاصله مقصد GPS فاصله مقصد GPS خطای موقعیت یابی GPS H شاخص قابلیت همکاری نسخه DNG اندازه پیش‌فرض برش پیش نمایش شروع تصویر طول تصویر پیش نمایش قاب جنبه حاشیه پایین سنسور حاشیه سمت چپ سنسور حاشیه سمت راست سنسور حاشیه بالای سنسور با فونت و رنگ داده شده متن را روی مسیر بکشید متن فعلی به جای یک بار کشیدن تا پایان مسیر تکرار می شود از تصویر انتخاب شده برای کشیدن آن در مسیر مشخص شده استفاده کنید این تصویر به عنوان ورودی تکراری مسیر ترسیم شده استفاده خواهد شد مثلث مشخص شده را از نقطه شروع تا نقطه پایان رسم می کند مثلث مشخص شده را از نقطه شروع تا نقطه پایان رسم می کند مثلث مشخص شده مثلث چند ضلعی را از نقطه شروع به نقطه پایان رسم می کند چند ضلعی چند ضلعی مشخص شده چند ضلعی مشخص شده را از نقطه شروع تا نقطه پایان ترسیم می کند رئوس رسم چند ضلعی منتظم چند ضلعی رسم کنید که به جای فرم آزاد، منظم باشد ستاره را از نقطه شروع به نقطه پایان می کشد ستاره مشخص شده را از نقطه شروع تا نقطه پایان ترسیم می کند نسبت شعاع داخلی رسم ستاره منظم ستاره ای را بکشید که به جای شکل آزاد، منظم خواهد بود برای جلوگیری از لبه‌های تیز، آنتی‌الیاسینگ را فعال می‌کند ویرایش را به جای پیش نمایش باز کنید هنگامی که تصویر را برای باز کردن (پیش نمایش) در ImageToolbox انتخاب می کنید، برگه انتخاب ویرایش به جای پیش نمایش باز می شود اسناد را اسکن کنید و PDF یا تصاویر جدا از آنها ایجاد کنید گزینه های زیر برای ذخیره تصاویر است نه PDF هیستوگرام HSV را یکسان کنید یکسان سازی هیستوگرام درصد را وارد کنید فیلد متن را در پشت انتخاب از پیش تنظیم‌ها فعال می‌کند تا آن‌ها را در لحظه وارد کنید هیستوگرام تطبیقی ​​را برابر کنید LUV تطبیقی ​​هیستوگرام را یکسان کنید LAB تطبیقی ​​هیستوگرام را برابر کنید کلاه آزمایشگاه کلاه کلاه لوو برش به محتوا رنگ قاب رنگ برای نادیده گرفتن الگو هیچ فیلتر قالب اضافه نشده است ایجاد جدید فایل انتخابی داده‌های قالب فیلتر ندارد این تصویر برای پیش نمایش این الگوی فیلتر استفاده خواهد شد به عنوان فایل ذخیره به عنوان فایل حذف قالب شما در حال حذف فیلتر قالب انتخابی هستید. این عملیات قابل برگشت نیست الگوی فیلتر با نام \"%1$s\" (%2$s) اضافه شد پیش نمایش فیلتر محتوای کد اعطای مجوز دوربین در تنظیمات برای اسکن اسکنر اسناد مکعبی B-Spline بلکمن ولش چهارگانه گاوسی ابوالهول بارتلت روبیدوکس Robidoux Sharp اسپلاین 16 اسپلاین 36 اسپلاین 64 قیصر بارتلت-هی جعبه بومن لانچوس 2 لانچوس 3 لانچوس 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc درون یابی مکعبی با در نظر گرفتن نزدیکترین 16 پیکسل، مقیاس بندی نرم تری را فراهم می کند و نتایج بهتری نسبت به دوخطی دارد. یک روش نمونه‌گیری مجدد ساده که از میانگین نزدیک‌ترین مقادیر پیکسل استفاده می‌کند، که اغلب منجر به ظاهر بلوکی می‌شود. یک روش نمونه‌گیری مجدد که از فیلتر 2 لوب Lanczos برای درونیابی با کیفیت بالا با حداقل مصنوعات استفاده می‌کند. یک روش نمونه برداری مجدد که از یک فیلتر 3 لوب Lanczos برای درونیابی با کیفیت بالا با حداقل مصنوعات استفاده می کند. یک روش نمونه‌گیری مجدد که از فیلتر 4 لوب Lanczos برای درونیابی با کیفیت بالا با حداقل مصنوعات استفاده می‌کند. نوعی از فیلتر Lanczos 2 که از عملکرد jinc استفاده می کند و درون یابی با کیفیت بالا با حداقل مصنوعات را ارائه می دهد. نوعی از فیلتر Lanczos 3 که از عملکرد jinc استفاده می کند و درون یابی با کیفیت بالا با حداقل مصنوعات را ارائه می دهد. نوعی از فیلتر Lanczos 4 که از عملکرد jinc استفاده می کند و درون یابی با کیفیت بالا با حداقل مصنوعات را ارائه می دهد. هانینگ EWA نوع میانگین وزنی بیضوی (EWA) فیلتر هانینگ برای درونیابی صاف و نمونه برداری مجدد Robidoux EWA نوع میانگین وزنی بیضوی (EWA) فیلتر Robidoux برای نمونه برداری مجدد با کیفیت بالا بلکمن حوا نوع میانگین وزنی بیضوی (EWA) فیلتر Blackman برای به حداقل رساندن آرتیفکت های زنگ EWA چهارگانه نوع میانگین وزنی بیضوی (EWA) فیلتر Quadric برای درونیابی صاف Robidoux Sharp EWA نوع میانگین وزنی بیضوی (EWA) فیلتر Robidoux Sharp برای نتایج واضح تر Lanczos 3 Jinc EWA نوع میانگین وزنی بیضوی (EWA) فیلتر Lanczos 3 Jinc برای نمونه برداری مجدد با کیفیت بالا با کمترین الایاسینگ جینسینگ یک فیلتر نمونه برداری مجدد که برای پردازش تصویر با کیفیت بالا با تعادل خوبی از وضوح و صافی طراحی شده است جینسینگ EWA نوع میانگین وزنی بیضوی (EWA) فیلتر جینسنگ برای بهبود کیفیت تصویر Lanczos Sharp EWA نوع میانگین وزنی بیضوی (EWA) فیلتر Lanczos Sharp برای دستیابی به نتایج واضح با حداقل مصنوعات Lanczos 4 Sharpest EWA نوع میانگین وزنی بیضوی (EWA) فیلتر Lanczos 4 Sharpest برای نمونه برداری مجدد تصویر بسیار واضح Lanczos Soft EWA نوع میانگین وزنی بیضوی (EWA) فیلتر نرم Lanczos برای نمونه برداری مجدد صاف تر هاسن سافت یک فیلتر نمونه‌برداری مجدد که توسط Haasn برای مقیاس‌گذاری صاف و بدون مصنوعات تصویر طراحی شده است برای همیشه اخراج کنید انباشتن تصویر با حالت های ترکیبی انتخابی، تصاویر را روی هم قرار دهید اضافه کردن تصویر سطل ها به حساب می آیند کلاه HSL کلاه HSV HSL تطبیقی ​​هیستوگرام را برابر کنید یکسان سازی هیستوگرام تطبیقی ​​HSV حالت لبه کلیپ بسته بندی کنید کوررنگی حالت را برای تطبیق رنگ های تم برای نوع کوررنگی انتخاب شده انتخاب کنید مشکل در تشخیص رنگ قرمز و سبز مشکل در تشخیص رنگ سبز و قرمز مشکل در تشخیص رنگ آبی و زرد ناتوانی در درک رنگ قرمز ناتوانی در درک رنگ سبز ناتوانی در درک رنگ آبی کاهش حساسیت به تمام رنگ ها کوررنگی کامل، دیدن فقط سایه های خاکستری از طرح کور رنگی استفاده نکنید رنگ ها دقیقاً همانطور که در موضوع تنظیم شده است خواهند بود سیگموئیدی لاگرانژ 2 فیلتر درون یابی لاگرانژ درجه 2، مناسب برای مقیاس بندی تصویر با کیفیت بالا با انتقال صاف لاگرانژ 3 یک فیلتر درون یابی لاگرانژ درجه 3 که دقت بهتر و نتایج نرم تری را برای مقیاس بندی تصویر ارائه می دهد. لانچوس 6 فیلتر نمونه برداری مجدد Lanczos با مرتبه بالاتر از 6 که مقیاس تصویر واضح تر و دقیق تر را ارائه می دهد. Lanczos 6 Jinc نوعی از فیلتر Lanczos 6 با استفاده از عملکرد Jinc برای بهبود کیفیت نمونه‌برداری مجدد تصویر تاری جعبه خطی تاری چادر خطی تاری جعبه گاوسی خطی تاری پشته خطی تاری جعبه گاوسی تاری گاوسی سریع خطی بعدی تاری گاوسی سریع خطی تاری گاوسی خطی یک فیلتر را برای استفاده از آن به عنوان رنگ انتخاب کنید فیلتر را تعویض کنید فیلتر زیر را انتخاب کنید تا از آن به عنوان قلم مو در طراحی خود استفاده کنید طرح فشرده سازی TIFF کم پلی نقاشی شن و ماسه تک تصویر را بر اساس ردیف یا ستون تقسیم کنید متناسب با محدوده برای دستیابی به رفتار دلخواه، حالت تغییر اندازه برش را با این پارامتر ترکیب کنید (نسبت Crop/Fit to aspect) زبان ها با موفقیت وارد شدند پشتیبان گیری از مدل های OCR واردات صادرات موقعیت بالا سمت چپ بالا سمت راست پایین سمت چپ پایین سمت راست مرکز برتر مرکز راست مرکز پایین مرکز چپ تصویر هدف انتقال پالت روغن تقویت شده تلویزیون قدیمی ساده HDR گاتهام کلاه اوکلاب کلارا اولچ کلاه جزبز نقطه پولکا Dithering 2x2 خوشه ای Dithering 4x4 خوشه ای Dithering 8x8 خوشه ای ییلیلوما دیترینگ موارد دلخواه را اضافه کنید مکمل مشابه سه گانه تقسیم مکمل تترادیک مربع مشابه + مکمل تن سایه ها اختلاط رنگ اطلاعات رنگ رنگ انتخاب شده رنگ برای مخلوط کردن وقتی رنگ‌های پویا روشن هستند، نمی‌توان از monet استفاده کرد 512x512 2D LUT تصویر LUT را هدف بگیرید یک آماتور خانم آداب معاشرت ظرافت نرم نوع Soft Elegance نوع انتقال پالت LUT سه بعدی بلوز را رها کنید عنبر استوک فیلم 50 شب مه آلود کداک تصویر LUT خنثی را دریافت کنید ابتدا، از برنامه ویرایش عکس مورد علاقه خود برای اعمال فیلتر روی LUT خنثی استفاده کنید که می توانید از اینجا دریافت کنید. برای اینکه این کار به درستی کار کند، هر رنگ پیکسل نباید به پیکسل های دیگر بستگی داشته باشد (به عنوان مثال، تاری کار نخواهد کرد). پس از آماده شدن، از تصویر LUT جدید خود به عنوان ورودی فیلتر LUT 512*512 استفاده کنید هنر پاپ سلولوئید قهوه جنگل طلایی مایل به سبز یکپارچهسازی با سیستمعامل زرد پیوندها فایل‌های ICO را فقط می‌توان در حداکثر اندازه ۲۵۶×۲۵۶ ذخیره کرد عدم دسترسی کامل به فایل ها برای دیدن JXL، QOI و سایر تصاویری که در Android به‌عنوان تصویر شناخته نمی‌شوند، به همه فایل‌ها اجازه دسترسی دهید. بدون مجوز Image Toolbox قادر به نمایش آن تصاویر نیست رنگ ترسیم پیش فرض حالت پیش‌فرض ترسیم مسیر اضافه کردن زمان افزودن مهر زمانی به نام فایل خروجی را فعال می کند مُهر زمانی قالب بندی شده قالب‌بندی مهر زمانی را در نام فایل خروجی به جای میلی‌سی‌های اصلی فعال کنید Timestamps را برای انتخاب قالب خود فعال کنید یک بار ذخیره مکان مکان‌های ذخیره یک‌باره را مشاهده و ویرایش کنید که می‌توانید با فشار طولانی دکمه ذخیره در اکثر گزینه‌ها استفاده کنید اخیرا استفاده شده است کانال CI گروه به چت ما بپیوندید، جایی که می توانید در مورد هر چیزی که می خواهید بحث کنید و همچنین به کانال CI که در آن نسخه های بتا و اطلاعیه ها را پست می کنم نگاه کنید. در مورد نسخه های جدید برنامه مطلع شوید و اطلاعیه ها را بخوانید یک تصویر را در ابعاد داده شده قرار دهید و تاری یا رنگ را در پس زمینه اعمال کنید چیدمان ابزار ابزارها را بر اساس نوع گروه بندی کنید ابزارها را بر اساس نوع آنها در صفحه اصلی به جای ترتیب فهرست سفارشی گروه بندی می کند مقادیر پیش فرض کشیدن انگشت را برای نمایش نوارهای سیستم در صورت پنهان بودن فعال می کند نوار Nav را پنهان کنید فرکانس نوع چرخش نوع فراکتال اکتاو پوچی به دست آوردن قدرت وزنی قدرت پینگ پنگ تابع فاصله نوع برگشت عصبانیت Warp دامنه نام فایل سفارشی مکان و نام فایل را انتخاب کنید که برای ذخیره تصویر فعلی استفاده می شود در پوشه ای با نام سفارشی ذخیره شد کلاژ ساز از حداکثر 20 تصویر کلاژ بسازید نوع کلاژ تصویر را برای جابجایی، حرکت و بزرگنمایی برای تنظیم موقعیت نگه دارید چرخش را غیرفعال کنید با حرکات دو انگشتی از چرخش تصاویر جلوگیری می کند اتصال به مرزها را فعال کنید پس از جابجایی یا بزرگ‌نمایی، عکس‌ها می‌چپند تا لبه‌های قاب را پر کنند هیستوگرام هیستوگرام تصویر RGB یا Brightness برای کمک به شما در انجام تنظیمات گزینه های Tesseract برخی از متغیرهای ورودی را برای موتور تسراکت اعمال کنید گزینه های سفارشی گزینه‌ها باید با این الگو وارد شوند: \"--{option_name} {value}\" برش خودکار گوشه های رایگان برش تصویر با چند ضلعی، این نیز پرسپکتیو را تصحیح می کند اجبار به مرزهای تصویر اشاره می کند نقاط با محدودیت های تصویر محدود نمی شوند، این برای تصحیح پرسپکتیو دقیق تر مفید است ماسک محتوای آگاه در مسیر ترسیم شده پر می شود Heal Spot از Circle Kernel استفاده کنید باز شدن بسته شدن گرادیان مورفولوژیکی کلاه بالا کلاه سیاه منحنی های تن بازنشانی منحنی ها منحنی ها به مقدار پیش فرض برمی گردند سبک خط اندازه شکاف بریده بریده نقطه چین مهر شده زیگزاگ خط چین را در طول مسیر ترسیم شده با اندازه شکاف مشخص می‌کشد نقطه و خط چین را در مسیر داده شده رسم می کند فقط خطوط مستقیم پیش فرض اشکال انتخاب شده را در طول مسیر با فاصله مشخص ترسیم می کند زیگزاگ مواج را در طول مسیر ترسیم می کند نسبت زیگزاگ ابزاری را برای پین کردن انتخاب کنید حذف فریم‌های قبلی را فعال می‌کند، بنابراین آنها روی یکدیگر قرار نمی‌گیرند Crossfade فریم ها به یکدیگر متقاطع می شوند تعداد فریم های متقاطع آستانه یک آستانه دو کانی آینه 101 تاری زوم پیشرفته لاپلاسی ساده سوبل ساده شبکه کمکی شبکه پشتیبان را در بالای منطقه طراحی نشان می دهد تا به دستکاری های دقیق کمک کند رنگ شبکه عرض سلول ارتفاع سلول برخی از کنترل‌های انتخاب از یک چیدمان فشرده برای اشغال فضای کمتر استفاده می‌کنند اجازه دوربین را در تنظیمات برای گرفتن تصویر بدهید طرح بندی ضریب نرخ ثابت (CRF) مقدار %1$s به معنای فشرده سازی آهسته است که در نتیجه حجم فایل نسبتاً کم است. %2$s به معنای فشرده‌سازی سریع‌تر است که منجر به ایجاد یک فایل بزرگ می‌شود. کتابخانه لوت مجموعه LUT ها را به روز کنید (فقط موارد جدید در صف قرار می گیرند) که می توانید پس از دانلود آن را اعمال کنید پنهان کردن نمایش دهید نوع لغزنده فانتزی مواد 2 یک نوار لغزنده با ظاهر فانتزی. این گزینه پیش فرض است یک نوار لغزنده Material 2 نوار لغزنده مواد شما درخواست کنید دکمه های گفتگوی مرکزی دکمه های دیالوگ ها در صورت امکان به جای سمت چپ در مرکز قرار می گیرند مجوزهای منبع باز مجوزهای کتابخانه های منبع باز مورد استفاده در این برنامه را مشاهده کنید منطقه نمونه برداری مجدد با استفاده از رابطه مساحت پیکسل. ممکن است روشی ارجح برای از بین بردن تصویر باشد، زیرا نتایج بدون مویر می دهد. اما وقتی تصویر بزرگ‌نمایی می‌شود، شبیه به روش \"نزدیک‌ترین\" است. Tonemapping را فعال کنید % را وارد کنید نمی توانید به سایت دسترسی پیدا کنید، سعی کنید از VPN استفاده کنید یا بررسی کنید که آیا URL درست است لایه های نشانه گذاری حالت لایه ها با قابلیت قرار دادن آزادانه تصاویر، متن و موارد دیگر ویرایش لایه لایه ها روی تصویر از یک تصویر به عنوان پس زمینه استفاده کنید و لایه های مختلف را در بالای آن اضافه کنید لایه ها روی پس زمینه همان گزینه اول اما با رنگ به جای تصویر بتا سمت تنظیمات سریع هنگام ویرایش تصاویر، یک نوار شناور در سمت انتخاب شده اضافه کنید، که با کلیک کردن، تنظیمات سریع باز می شود پاک کردن انتخاب گروه تنظیم \"%1$s\" به طور پیش‌فرض جمع می‌شود گروه تنظیم \"%1$s\" به طور پیش فرض گسترش می یابد ابزارهای Base64 رشته Base64 را به تصویر رمزگشایی کنید یا تصویر را به فرمت Base64 رمزگذاری کنید پایه 64 مقدار ارائه شده یک رشته Base64 معتبر نیست نمی توان رشته Base64 خالی یا نامعتبر را کپی کرد Paste Base64 Copy Base64 تصویر را برای کپی یا ذخیره رشته Base64 بارگیری کنید. اگر خود رشته را دارید، می توانید آن را در بالا بچسبانید تا تصویر را به دست آورید ذخیره Base64 Base64 را به اشتراک بگذارید گزینه ها اقدامات Import Base64 Base64 Actions طرح کلی را اضافه کنید طرح کلی اطراف متن را با رنگ و عرض مشخص اضافه کنید رنگ طرح کلی اندازه طرح کلی چرخش Checksum به عنوان نام فایل تصاویر خروجی دارای نامی هستند که با جمع کنترلی داده هایشان مطابقت دارد نرم افزار رایگان (شریک) نرم افزارهای مفیدتر در کانال همکار اپلیکیشن های اندروید الگوریتم ابزارهای Checksum محاسبه کنید هش متن چک جمع فایلی را انتخاب کنید تا جمع چک آن بر اساس الگوریتم انتخاب شده محاسبه شود متنی را وارد کنید تا بر اساس الگوریتم انتخابی، جمع چک آن محاسبه شود منبع Checksum چک جمع برای مقایسه مسابقه! تفاوت مبلغ چک برابر است، می تواند ایمن باشد جمع های چک برابر نیستند، فایل می تواند ناامن باشد! گرادیان های مش به مجموعه آنلاین Mesh Gradients نگاه کنید فقط فونت های TTF و OTF را می توان وارد کرد وارد کردن فونت (TTF/OTF) صادرات فونت فونت های وارداتی خطا هنگام ذخیره تلاش، سعی کنید پوشه خروجی را تغییر دهید نام فایل تنظیم نشده است هیچ کدام صفحات سفارشی انتخاب صفحات تأیید خروج از ابزار اگر هنگام استفاده از ابزار خاصی تغییرات ذخیره نشده ای داشته باشید و سعی کنید آن را ببندید، گفتگوی تایید نمایش داده می شود تغییر استیکر متناسب با عرض ارتفاع مناسب مقایسه دسته ای فایل/فایل ها را انتخاب کنید تا جمع چک آن بر اساس الگوریتم انتخاب شده محاسبه شود فایل ها را انتخاب کنید دایرکتوری را انتخاب کنید ترازوی طول سر تمبر مهر زمان الگوی قالب بالشتک قسمت تصویر را برش دهید و قسمت های چپ (می تواند معکوس باشد) را با خطوط عمودی یا افقی ادغام کنید خط محوری عمودی خط محوری افقی انتخاب معکوس قسمت برش عمودی به جای ادغام قطعات در اطراف منطقه برش، برگ خواهد شد قسمت برش افقی به جای ادغام قطعات در اطراف منطقه برش، برگ خواهد شد مجموعه ای از گرادیان های مش شیب مش را با مقدار سفارشی گره و وضوح ایجاد کنید پوشش گرادیان مش شیب مش را از بالای تصاویر داده شده بنویسید سفارشی سازی امتیاز اندازه شبکه رنگ را برجسته کنید نوع مقایسه پیکسل اسکن بارکد نسبت ارتفاع اعمال B/W تصویر بارکد کاملا سیاه و سفید خواهد بود و با تم برنامه رنگی نمی شود هر بارکدی (QR، EAN، AZTEC، …) را اسکن کنید و محتوای آن را دریافت کنید یا متن خود را برای ایجاد بارکد جدید جایگذاری کنید. استخراج تصاویر جلد آلبوم از فایل های صوتی، رایج ترین فرمت ها پشتیبانی می شوند می‌توانید با استفاده از گزینه‌های زیر با من تماس بگیرید و من سعی می‌کنم راه‌حلی پیدا کنم.\n(فراموش نکنید که گزارش‌ها را پیوست کنید) نوشتن در فایل متن را از دسته ای از تصاویر استخراج کرده و در یک فایل متنی ذخیره کنید نوشتن در فراداده متن را از هر تصویر استخراج کنید و آن را در اطلاعات EXIF ​​عکس های نسبی قرار دهید حالت نامرئی از استگانوگرافی برای ایجاد واترمارک های نامرئی در داخل بایت های تصاویر خود استفاده کنید از LSB استفاده کنید از روش استگانوگرافی LSB (بیت کمتر با اهمیت) استفاده می شود، در غیر این صورت از FD (دامنه فرکانس) استفاده می شود. حذف خودکار قرمزی چشم رمز عبور باز کردن قفل PDF محافظت می شود عملیات تقریباً کامل شده است. لغو اکنون مستلزم راه اندازی مجدد آن است تاریخ اصلاح شد تاریخ اصلاح (معکوس) اندازه اندازه (معکوس) نوع MIME نوع MIME (معکوس) پسوند پسوند (معکوس) تاریخ اضافه شدن تاریخ اضافه شدن (معکوس) از چپ به راست از راست به چپ از بالا به پایین از پایین به بالا شیشه مایع سوئیچ مبتنی بر IOS 26 که اخیراً معرفی شده و سیستم طراحی شیشه مایع آن است تصویر را انتخاب کنید یا داده‌های Base64 را در زیر جای‌گذاری/وارد کنید برای شروع پیوند تصویر را تایپ کنید کلیدوسکوپ زاویه ثانویه طرفین میکس کانال سبز آبی قرمز آبی سبز قرمز به رنگ قرمز به رنگ سبز به رنگ آبی فیروزه ای سرخابی زرد رنگ نیم تنه کانتور سطوح افست کریستالیز ورونوی شکل کشش تصادفی بودن دسپکل پراکنده سگ شعاع دوم برابر کردن درخشش چرخش و خرج کردن Pointillize رنگ حاشیه مختصات قطبی راست به قطبی قطبی به راست در دایره معکوس کنید کاهش نویز خورشیدی ساده ببافید X Gap شکاف Y عرض X عرض Y چرخش مهر لاستیکی اسمیر تراکم مخلوط کنید اعوجاج لنز کره ضریب شکست قوس زاویه گسترش درخشش اشعه ها ASCII گرادیان مریم پاییز استخوان جت زمستان اقیانوس تابستان بهار نوع باحال HSV صورتی داغ کلمه ماگما دوزخ پلاسما ویریدیس شهروندان گرگ و میش گرگ و میش تغییر کرد پرسپکتیو خودکار رومیزی اجازه برش برش یا پرسپکتیو مطلق توربو سبز عمیق تصحیح لنز فایل پروفایل لنز هدف را با فرمت JSON دانلود پروفایل لنز آماده درصد درصد صادرات به عنوان JSON رشته را با داده های پالت به عنوان نمایش json کپی کنید کنده کاری درز صفحه اصلی صفحه قفل ساخته شده در صادرات تصاویر پس زمینه تازه کردن والپیپرهای خانه، قفل و داخلی فعلی را دریافت کنید اجازه دسترسی به همه فایل‌ها را بدهید، این برای بازیابی تصاویر پس زمینه لازم است مجوز مدیریت حافظه خارجی کافی نیست، شما باید اجازه دسترسی به تصاویر خود را بدهید، مطمئن شوید که \"Allow all\" را انتخاب کنید از پیش تنظیم به نام فایل اضافه کنید پسوندی را با از پیش تعیین شده انتخاب شده به نام فایل تصویر اضافه می کند حالت مقیاس تصویر را به نام فایل اضافه کنید پسوندی را با حالت مقیاس تصویر انتخاب شده به نام فایل تصویر اضافه می کند هنر آسکی تبدیل تصویر به متن ascii که شبیه تصویر است پارامترها فیلتر منفی را برای نتیجه بهتر در برخی موارد روی تصویر اعمال می کند در حال پردازش اسکرین شات اسکرین شات گرفته نشد، دوباره امتحان کنید ذخیره رد شد %1$s فایل رد شد Allow Skip If Larter اگر اندازه فایل حاصل بزرگتر از نسخه اصلی باشد، به برخی از ابزارها اجازه داده می شود از ذخیره تصاویر صرفنظر کنند. رویداد تقویم تماس بگیرید ایمیل مکان تلفن متن اس ام اس URL وای فای شبکه را باز کنید N/A SSID تلفن پیام آدرس موضوع بدن نام سازمان عنوان تلفن ها ایمیل ها URL ها آدرس ها خلاصه توضیحات مکان سازمان دهنده تاریخ شروع تاریخ پایان وضعیت عرض جغرافیایی طول جغرافیایی ایجاد بارکد ویرایش بارکد پیکربندی Wi-Fi امنیت مخاطب را انتخاب کنید به مخاطبین در تنظیمات اجازه دهید تا با استفاده از مخاطب انتخابی، به صورت خودکار تکمیل شوند اطلاعات تماس نام کوچک نام میانی نام خانوادگی تلفظ تلفن را اضافه کنید ایمیل اضافه کنید آدرس اضافه کنید وب سایت اضافه کردن وب سایت نام قالب بندی شده این تصویر برای قرار دادن بالای بارکد استفاده خواهد شد سفارشی سازی کد این تصویر به عنوان لوگو در مرکز کد QR استفاده خواهد شد لوگو بالشتک آرم اندازه لوگو گوشه های لوگو چشم چهارم با افزودن چشم چهارم در گوشه انتهایی پایین، تقارن چشم را به کد qr اضافه می کند شکل پیکسل شکل قاب شکل توپ سطح تصحیح خطا رنگ تیره رنگ روشن سیستم عامل هایپر شیائومی HyperOS سبک است الگوی ماسک ممکن است این کد قابل اسکن نباشد، پارامترهای ظاهری را تغییر دهید تا با همه دستگاه ها قابل خواندن باشد قابل اسکن نیست ابزارها مانند راه‌انداز برنامه صفحه اصلی به نظر می‌رسند تا فشرده‌تر باشند حالت لانچر یک منطقه را با قلم مو و سبک انتخاب شده پر می کند پر سیل اسپری کنید مسیر به سبک گرافیتی را ترسیم می کند ذرات مربع ذرات اسپری به جای دایره مربع شکل خواهند بود ابزار پالت پایه/مواد اولیه را که پالت می‌کنید از تصویر ایجاد کنید، یا در قالب‌های پالت مختلف وارد/صادرات کنید ویرایش پالت صادرات/وارد کردن پالت در قالب‌های مختلف نام رنگ نام پالت قالب پالت پالت تولید شده را به فرمت های مختلف صادر کنید رنگ جدیدی را به پالت فعلی اضافه می کند قالب %1$s از ارائه نام پالت پشتیبانی نمی کند به دلیل خط‌مشی‌های فروشگاه Play، این ویژگی نمی‌تواند در ساخت فعلی گنجانده شود. برای دسترسی به این قابلیت، لطفا ImageToolbox را از یک منبع جایگزین دانلود کنید. می توانید بیلدهای موجود در GitHub را در زیر بیابید. صفحه Github را باز کنید فایل اصلی به جای ذخیره در پوشه انتخابی با فایل جدید جایگزین می شود متن واترمارک پنهان شناسایی شد تصویر واترمارک مخفی شناسایی شد این تصویر مخفی بود رنگ آمیزی مولد به شما امکان می دهد اشیاء موجود در یک تصویر را با استفاده از مدل هوش مصنوعی، بدون تکیه بر OpenCV حذف کنید. برای استفاده از این ویژگی، برنامه مدل مورد نیاز (~200 مگابایت) را از GitHub دانلود می کند به شما امکان می دهد اشیاء موجود در یک تصویر را با استفاده از مدل هوش مصنوعی، بدون تکیه بر OpenCV حذف کنید. این می تواند یک عملیات طولانی مدت باشد تجزیه و تحلیل سطح خطا گرادیان درخشندگی فاصله متوسط تشخیص حرکت کپی حفظ کنید ضریب داده های کلیپ بورد خیلی بزرگ است داده ها برای کپی خیلی بزرگ هستند پیکسل سازی بافت ساده پیکسل‌سازی پلکانی Cross Pixelization پیکسل‌سازی میکرو ماکرو پیکسل سازی مداری پیکسل‌سازی گرداب Pixelization شبکه پالس پیکسل سازی هسته پیکسل سازی بافت شعاعی uri \"%1$s\" باز نمی شود حالت بارش برف فعال شد قاب حاشیه نوع اشکال کانال شیفت حداکثر افست VHS بلوک اشکال اندازه بلوک انحنای CRT انحنا کروما پیکسل ذوب ماکس دراپ ابزارهای هوش مصنوعی ابزارهای مختلف برای پردازش تصاویر از طریق مدل های ai مانند حذف مصنوع یا حذف نویز فشرده سازی، خطوط ناهموار کارتون، فشرده سازی پخش فشرده سازی عمومی، نویز عمومی صدای کارتونی بی رنگ سریع، فشرده سازی عمومی، نویز عمومی، انیمیشن/کمیک/انیمه اسکن کتاب تصحیح نوردهی بهترین در فشرده سازی عمومی، تصاویر رنگی بهترین در فشرده سازی عمومی، تصاویر در مقیاس خاکستری فشرده سازی عمومی، تصاویر خاکستری، قوی تر نویز عمومی، تصاویر رنگی نویز عمومی، تصاویر رنگی، جزئیات بهتر نویز عمومی، تصاویر خاکستری نویز عمومی، تصاویر خاکستری، قوی تر نویز عمومی، تصاویر خاکستری، قوی ترین فشرده سازی عمومی فشرده سازی عمومی بافت سازی، فشرده سازی h264 فشرده سازی VHS فشرده سازی غیر استاندارد (cinepak، msvideo1، roq) فشرده سازی مخزن، بهتر در هندسه فشرده سازی مخزن، قوی تر فشرده سازی مخزن، نرم، جزئیات را حفظ می کند از بین بردن اثر پله پله، صاف کردن هنر/طراحی های اسکن شده، فشرده سازی ملایم، مویر نواربندی رنگی آهسته، حذف نیم تن رنگ‌کننده عمومی برای تصاویر خاکستری/bw، برای نتایج بهتر از DDColor استفاده کنید حذف لبه تیز شدن بیش از حد را از بین می برد آهسته، پریشان Anti-aliasing، مصنوعات عمومی، CGI KDM003 پردازش را اسکن می کند مدل بهبود تصویر سبک حذف مصنوعات فشرده سازی حذف مصنوعات فشرده سازی برداشتن باند با نتایج صاف پردازش الگوی نیم‌تنی حذف الگوی Dither V3 حذف مصنوعات JPEG V2 بهبود بافت H.264 تیز کردن و تقویت VHS ادغام اندازه تکه اندازه همپوشانی تصاویر بیش از %1$s پیکسل برش داده می‌شوند و به صورت تکه‌ای پردازش می‌شوند، همپوشانی اینها را با هم ترکیب می‌کند تا از درزهای قابل مشاهده جلوگیری کند. اندازه های بزرگ می تواند باعث ناپایداری دستگاه های ارزان قیمت شود برای شروع یکی را انتخاب کنید آیا می خواهید مدل %1$s را حذف کنید؟ باید دوباره آن را دانلود کنید تایید کنید مدل ها مدل های دانلود شده مدل های موجود آماده سازی مدل فعال جلسه باز نشد فقط مدل‌های .onnx/.ort می‌توانند وارد شوند مدل وارداتی مدل سفارشی onnx را برای استفاده بیشتر وارد کنید، فقط مدل‌های onnx/ort پذیرفته می‌شوند، تقریباً از همه گونه‌های مشابه esrgan پشتیبانی می‌کند مدل های وارداتی نویز عمومی، تصاویر رنگی نویز عمومی، تصاویر رنگی، قوی تر نویز عمومی، تصاویر رنگی، قوی ترین مصنوعات متمایز و نوارهای رنگی را کاهش می دهد، شیب های صاف و مناطق رنگی صاف را بهبود می بخشد. روشنایی و کنتراست تصویر را با هایلایت های متعادل و در عین حال حفظ رنگ های طبیعی افزایش می دهد. تصاویر تاریک را با حفظ جزئیات و اجتناب از نوردهی بیش از حد روشن می کند. توناژ بیش از حد رنگ را از بین می برد و تعادل رنگ خنثی و طبیعی را بازیابی می کند. تونینگ نویز مبتنی بر پواسون را با تاکید بر حفظ جزئیات و بافت های ظریف اعمال می کند. از تونینگ نرم پواسون برای نتایج بصری نرمتر و کم تهاجمی استفاده می کند. نویز یکنواخت با تمرکز بر حفظ جزئیات و وضوح تصویر. نویز یکنواخت ملایم برای بافت ظریف و ظاهری صاف. نواحی آسیب دیده یا ناهموار را با رنگ آمیزی مجدد مصنوعات و بهبود ثبات تصویر ترمیم می کند. مدل بندکشی سبک که باندهای رنگی را با حداقل هزینه عملکرد حذف می کند. تصاویر را با فشرده سازی بسیار بالا (کیفیت 0-20٪) برای وضوح بهتر بهینه می کند. تصاویر را با آرتیفکت های فشرده سازی بالا (کیفیت 20-40٪)، بازیابی جزئیات و کاهش نویز بهبود می بخشد. تصاویر را با فشرده سازی متوسط ​​(کیفیت 40-60٪)، متعادل کردن وضوح و صافی بهبود می بخشد. تصاویر را با فشرده سازی نور (کیفیت 60-80٪) برای بهبود جزئیات و بافت های ظریف اصلاح می کند. تصاویر تقریباً بدون اتلاف (کیفیت 80 تا 100٪) را کمی بهبود می بخشد و در عین حال ظاهر و جزئیات طبیعی را حفظ می کند. رنگ آمیزی ساده و سریع، کارتون، ایده آل نیست کمی تاری تصویر را کاهش می دهد و وضوح را بدون معرفی مصنوعات بهبود می بخشد. عملیات طولانی مدت پردازش تصویر پردازش آرتیفکت های فشرده فشرده سازی JPEG را در تصاویر با کیفیت بسیار پایین (0-20٪) حذف می کند. آرتیفکت های JPEG قوی را در تصاویر بسیار فشرده (20-40٪) کاهش می دهد. مصنوعات JPEG متوسط ​​را با حفظ جزئیات تصویر (40-60٪) پاک می کند. مصنوعات سبک JPEG را در تصاویر با کیفیت نسبتاً بالا (60-80٪) اصلاح می کند. به طور نامحسوس مصنوعات JPEG جزئی را در تصاویر تقریباً بدون اتلاف (80-100٪) کاهش می دهد. جزئیات و بافت های ظریف را بهبود می بخشد و وضوح درک شده را بدون آثار سنگین بهبود می بخشد. پردازش به پایان رسید پردازش انجام نشد بافت ها و جزئیات پوست را بهبود می بخشد و در عین حال ظاهری طبیعی را حفظ می کند و برای سرعت بهینه شده است. آرتیفکت های فشرده سازی JPEG را حذف می کند و کیفیت تصویر را برای عکس های فشرده بازیابی می کند. نویز ISO را در عکس های گرفته شده در شرایط کم نور کاهش می دهد و جزئیات را حفظ می کند. هایلایت های بیش از حد نوردهی شده یا \"جامبو\" را تصحیح می کند و تعادل تونال بهتری را بازیابی می کند. مدل رنگی سبک و سریع که رنگ های طبیعی را به تصاویر خاکستری اضافه می کند. DEJPEG حذف نویز رنگ آمیزی کنید مصنوعات افزایش دهید انیمه اسکن می کند سطح بالا ارتقاء دهنده X4 برای تصاویر عمومی. مدل کوچکی که از GPU و زمان کمتری استفاده می‌کند، با تاری و حذف نویز متوسط. ارتقاء دهنده X2 برای تصاویر عمومی، حفظ بافت ها و جزئیات طبیعی. ارتقاء دهنده X4 برای تصاویر عمومی با بافت های پیشرفته و نتایج واقعی. ارتقاء دهنده X4 برای تصاویر انیمه بهینه شده است. 6 بلوک RRDB برای خطوط و جزئیات واضح تر. ارتقاء دهنده X4 با از دست دادن MSE، نتایج نرم تری ایجاد می کند و مصنوعات را برای تصاویر عمومی کاهش می دهد. X4 Upscaler بهینه شده برای تصاویر انیمه. نوع 4B32F با جزئیات واضح تر و خطوط صاف. X4 UltraSharp مدل V2 برای تصاویر عمومی; بر وضوح و وضوح تأکید می کند. X4 UltraSharp V2 Lite; سریعتر و کوچکتر، جزئیات را حفظ می کند و در عین حال از حافظه GPU کمتری استفاده می کند. مدل سبک وزن برای حذف سریع پس زمینه. عملکرد و دقت متعادل. با پرتره ها، اشیا و صحنه ها کار می کند. برای بیشتر موارد استفاده توصیه می شود. BG را حذف کنید ضخامت مرز افقی ضخامت حاشیه عمودی %1$s رنگ %1$s رنگ مدل فعلی از تکه‌شدن پشتیبانی نمی‌کند، تصویر در ابعاد اصلی پردازش می‌شود، این ممکن است باعث مصرف زیاد حافظه و مشکلات دستگاه‌های رده پایین شود. قطعه قطعه کردن غیرفعال است، تصویر در ابعاد اصلی پردازش می‌شود، این ممکن است باعث مصرف زیاد حافظه و مشکلات دستگاه‌های ارزان‌قیمت شود، اما ممکن است نتایج بهتری در استنتاج داشته باشد. تکه تکه شدن مدل تقسیم‌بندی تصویر با دقت بالا برای حذف پس‌زمینه نسخه سبک U2Net برای حذف سریع پس زمینه با استفاده از حافظه کمتر. مدل Full DDColor رنگ‌بندی با کیفیت بالا را برای تصاویر عمومی با حداقل مصنوعات ارائه می‌کند. بهترین انتخاب از همه مدل های رنگ بندی. مجموعه داده های هنری آموزش دیده و خصوصی DDColor. نتایج رنگ آمیزی متنوع و هنری را با مصنوعات رنگی غیر واقعی کمتر ایجاد می کند. مدل سبک BiRefNet بر اساس ترانسفورماتور Swin برای حذف دقیق پس زمینه. حذف پس‌زمینه با کیفیت بالا با لبه‌های تیز و حفظ جزئیات عالی، به‌ویژه در اشیاء پیچیده و پس‌زمینه‌های پیچیده. مدل حذف پس‌زمینه که ماسک‌هایی دقیق با لبه‌های صاف، مناسب برای اشیاء عمومی و حفظ جزئیات متوسط ​​تولید می‌کند. مدل قبلا دانلود شده است مدل با موفقیت وارد شد تایپ کنید کلمه کلیدی خیلی سریع عادی کند خیلی آهسته محاسبه درصد حداقل مقدار %1$s است با کشیدن انگشت، تصویر را تحریف کنید پیچ و تاب سختی حالت Warp حرکت کنید رشد کنید کوچک شدن چرخش CW چرخش CCW محو کردن قدرت رها کردن بالا افت پایین شروع رها کردن پایان رها کردن در حال دانلود اشکال صاف به جای مستطیل های گرد استاندارد از ابربیضی ها استفاده کنید تا شکل های صاف تر و طبیعی تر داشته باشید نوع شکل برش دهید گرد شده صاف لبه های تیز بدون گرد گوشه های گرد کلاسیک نوع اشکال اندازه گوشه ها سنجاب عناصر رابط کاربری گرد و زیبا فرمت نام فایل متن سفارشی قرار داده شده در همان ابتدای نام فایل، مناسب برای نام پروژه، مارک ها، یا برچسب های شخصی. از نام فایل اصلی بدون پسوند استفاده می کند و به شما کمک می کند تا شناسایی منبع را دست نخورده نگه دارید. عرض تصویر بر حسب پیکسل، برای ردیابی تغییرات وضوح یا مقیاس بندی نتایج مفید است. ارتفاع تصویر بر حسب پیکسل، هنگام کار با نسبت ابعاد یا صادرات مفید است. ارقام تصادفی را برای تضمین نام فایل های منحصر به فرد تولید می کند. برای ایمنی بیشتر در برابر موارد تکراری، ارقام بیشتری اضافه کنید. شمارنده افزایش خودکار برای صادرات دسته ای، ایده آل برای ذخیره چندین تصویر در یک جلسه. نام از پیش تعیین شده اعمال شده را در نام فایل درج می کند تا بتوانید به راحتی نحوه پردازش تصویر را به خاطر بسپارید. حالت مقیاس‌بندی تصویر را که در حین پردازش استفاده می‌شود، نمایش می‌دهد و به تشخیص تصاویر تغییر اندازه، برش‌خورده یا متناسب کمک می‌کند. متن سفارشی قرار داده شده در انتهای نام فایل، مفید برای نسخه سازی مانند _v2، _edited، یا _final. پسوند فایل (png، jpg، webp، و غیره)، به طور خودکار با فرمت ذخیره شده واقعی مطابقت دارد. یک مهر زمانی قابل تنظیم که به شما امکان می دهد قالب خود را با مشخصات جاوا برای مرتب سازی کامل تعریف کنید. نوع پرت کردن اندروید بومی سبک iOS منحنی صاف توقف سریع فنری شناور اسنپی فوق العاده صاف تطبیقی آگاهی از دسترسی حرکت کاهش یافته فیزیک اسکرول اندروید بومی برای مقایسه پایه پیمایش متعادل و روان برای استفاده عمومی رفتار پیمایش مانند iOS با اصطکاک بالاتر منحنی اسپلاین منحصر به فرد برای حس اسکرول متمایز پیمایش دقیق با توقف سریع اسکرول فنری بازیگوش و پاسخگو طومارهای بلند و سرخورده برای مرور محتوا پیمایش سریع و پاسخگو برای رابط های کاربری تعاملی پیمایش صاف ممتاز با حرکت گسترده فیزیک را بر اساس سرعت پرتاب تنظیم می کند به تنظیمات دسترسی سیستم احترام می گذارد حداقل حرکت برای نیازهای دسترسی خطوط اولیه هر خط پنجم خط ضخیم‌تری اضافه می‌کند رنگ را پر کنید ابزارهای پنهان ابزارهای پنهان برای اشتراک گذاری کتابخانه رنگ مجموعه گسترده ای از رنگ ها را مرور کنید با حفظ جزئیات طبیعی، تاری تصاویر را واضح می کند و از بین می برد که برای رفع عکس های خارج از فوکوس ایده آل است. به طور هوشمند تصاویری را که قبلاً تغییر اندازه داده اند، بازیابی می کند و جزئیات و بافت های از دست رفته را بازیابی می کند. برای محتوای لایو اکشن بهینه شده است، آثار فشرده سازی را کاهش می دهد و جزئیات دقیق را در قاب های فیلم/نمایش تلویزیونی بهبود می بخشد. فیلم‌های با کیفیت VHS را به HD تبدیل می‌کند، نویز نوار را حذف می‌کند و وضوح را افزایش می‌دهد و در عین حال احساس قدیمی را حفظ می‌کند. مخصوص تصاویر و اسکرین شات های سنگین متن، کاراکترها را واضح می کند و خوانایی را بهبود می بخشد. ارتقاء مقیاس پیشرفته آموزش دیده بر روی مجموعه داده های متنوع، عالی برای بهبود عکس های همه منظوره. برای عکس های فشرده شده تحت وب بهینه شده است، مصنوعات JPEG را حذف می کند و ظاهر طبیعی را بازیابی می کند. نسخه بهبود یافته برای عکس های وب با حفظ بافت بهتر و کاهش مصنوعات. ارتقاء 2 برابری با فناوری Dual Aggregation Transformer، وضوح و جزئیات طبیعی را حفظ می کند. ارتقاء 3 برابری با استفاده از معماری پیشرفته ترانسفورماتور، ایده آل برای نیازهای بزرگنمایی متوسط. ارتقاء 4 برابری با کیفیت بالا با شبکه ترانسفورماتور پیشرفته، جزئیات دقیق را در مقیاس های بزرگتر حفظ می کند. تاری/نویز و لرزش را از عکس ها حذف می کند. هدف کلی اما بهترین در عکس. تصاویر با کیفیت پایین را با استفاده از ترانسفورماتور Swin2SR که برای تخریب BSRGAN بهینه شده است، بازیابی می کند. برای تثبیت مصنوعات فشرده سازی سنگین و بهبود جزئیات در مقیاس 4 برابر عالی است. ارتقاء 4 برابری با ترانسفورماتور SwinIR آموزش دیده در مورد تخریب BSRGAN. از GAN برای بافت های واضح تر و جزئیات طبیعی تر در عکس ها و صحنه های پیچیده استفاده می کند. مسیر PDF را ادغام کنید چندین فایل PDF را در یک سند ترکیب کنید ترتیب فایل ها pp. تقسیم PDF صفحات خاصی را از سند PDF استخراج کنید PDF را بچرخانید جهت گیری صفحه را به طور دائم رفع کنید صفحات پی دی اف را دوباره تنظیم کنید صفحات را بکشید و رها کنید تا آنها را دوباره مرتب کنید صفحات را نگه و بکشید شماره صفحه شماره گذاری را به صورت خودکار به اسناد خود اضافه کنید قالب برچسب PDF به متن (OCR) متن ساده را از اسناد PDF خود استخراج کنید متن سفارشی را برای نام تجاری یا امنیت پوشش دهید امضا امضای الکترونیکی خود را به هر سندی اضافه کنید این به عنوان امضا استفاده خواهد شد قفل PDF را باز کنید رمزهای عبور را از فایل های محافظت شده خود حذف کنید محافظت از PDF اسناد خود را با رمزگذاری قوی ایمن کنید موفقیت PDF باز شد، می توانید آن را ذخیره یا به اشتراک بگذارید PDF را تعمیر کنید تلاش برای تعمیر اسناد خراب یا ناخوانا مقیاس خاکستری همه تصاویر جاسازی شده سند را به مقیاس خاکستری تبدیل کنید فشرده سازی PDF اندازه فایل سند خود را برای اشتراک گذاری آسان تر بهینه کنید ImageToolbox جدول مرجع متقابل داخلی را بازسازی می کند و ساختار فایل را از ابتدا بازسازی می کند. این می‌تواند دسترسی به بسیاری از فایل‌هایی را که \\"باز نمی‌شوند\\" بازیابی کند. این ابزار تمام تصاویر سند را به مقیاس خاکستری تبدیل می کند. بهترین برای چاپ و کاهش حجم فایل فراداده ویژگی های سند را برای حفظ حریم خصوصی بهتر ویرایش کنید برچسب ها تولید کننده نویسنده کلمات کلیدی خالق حفظ حریم خصوصی Deep Clean تمام ابرداده های موجود برای این سند را پاک کنید صفحه OCR عمیق متن را از سند استخراج کرده و با استفاده از موتور Tesseract در یک فایل متنی ذخیره کنید نمی توان همه صفحات را حذف کرد صفحات PDF را حذف کنید صفحات خاصی را از سند PDF حذف کنید برای حذف ضربه بزنید به صورت دستی برش PDF صفحات سند را به هر حدی برش دهید PDF را صاف کنید PDF را با شطرنجی کردن صفحات سند غیرقابل تغییر کنید نمی توان دوربین را راه اندازی کرد. لطفاً مجوزها را بررسی کنید و مطمئن شوید که توسط برنامه دیگری استفاده نمی‌شود. استخراج تصاویر تصاویر جاسازی شده در PDF را با وضوح اصلی خود استخراج کنید این فایل PDF حاوی هیچ تصویر تعبیه شده نیست این ابزار هر صفحه را اسکن می کند و تصاویر منبع با کیفیت کامل را بازیابی می کند - برای ذخیره نسخه های اصلی از اسناد عالی است رسم امضا Pen Params از امضای خود به عنوان تصویر برای قرار دادن روی اسناد استفاده کنید زیپ PDF سند را با فاصله زمانی مشخص تقسیم کنید و اسناد جدید را در بایگانی فشرده قرار دهید فاصله PDF چاپ کنید سند را برای چاپ با اندازه صفحه سفارشی آماده کنید صفحات در هر برگه جهت گیری اندازه صفحه حاشیه شکوفه دادن زانو نرم بهینه شده برای انیمیشن و کارتون. ارتقاء سریع با رنگ های طبیعی بهبود یافته و مصنوعات کمتر Samsung One UI 7 دارای سبکی است برای محاسبه مقدار مورد نظر، نمادهای ریاضی پایه را در اینجا وارد کنید (به عنوان مثال (5+5)*10) بیان ریاضی حداکثر %1$s عکس را انتخاب کنید زمان تاریخ را حفظ کنید همیشه برچسب های exif مربوط به تاریخ و زمان را حفظ کنید، مستقل از گزینه keep exif کار می کند رنگ پس زمینه برای فرمت های آلفا قابلیت تنظیم رنگ پس‌زمینه برای هر فرمت تصویر با پشتیبانی آلفا را اضافه می‌کند، در صورت غیرفعال بودن این امکان فقط برای قالب‌های غیر آلفا در دسترس است. پروژه را باز کنید به ویرایش پروژه جعبه ابزار تصویر ذخیره شده قبلی ادامه دهید پروژه جعبه ابزار تصویر باز نمی شود پروژه جعبه ابزار تصویر فاقد داده های پروژه است پروژه جعبه ابزار تصویر خراب است نسخه پروژه جعبه ابزار تصویر پشتیبانی نشده: %1$d ذخیره پروژه لایه ها، پس زمینه و تاریخچه ویرایش را در یک فایل پروژه قابل ویرایش ذخیره کنید باز نشد نوشتن در PDF قابل جستجو متن را از دسته تصویر تشخیص دهید و PDF قابل جستجو را با تصویر و لایه متن قابل انتخاب ذخیره کنید لایه آلفا تلنگر افقی چرخش عمودی قفل سایه اضافه کنید رنگ سایه هندسه متن برای سبک‌سازی واضح‌تر، متن را کشیده یا کج کنید مقیاس X کج X حاشیه نویسی ها را حذف کنید انواع حاشیه نویسی انتخاب شده مانند پیوندها، نظرات، هایلایت ها، اشکال یا فیلدهای فرم را از صفحات PDF حذف کنید. هایپرلینک ها فایل های پیوست خطوط پنجره های بازشو تمبر اشکال یادداشت های متنی نشانه گذاری متن فیلدهای فرم نشانه گذاری ناشناس حاشیه نویسی ها لغو گروه کردن سایه تاری پشت لایه را با رنگ قابل تنظیم و افست اضافه کنید ================================================ FILE: core/resources/src/main/res/values-fi/strings.xml ================================================ Leveys %1$s Korkeus %1$s Laatu Lisäosa Koon muunto tyyppi Selkeää Valitse kuva Pysy Jokin meni väärin: %1$s Koko %1$s "Kuva on liian iso esikatseltavaksi, mutta tallennusta yritetään joka tapauksessa" Valitse kuva aloittaaksesi Joustava Palauta kuva Oletko todella varma että haluat sulkea sovelluksen? Ohjelma sulkeutuu Sulje Kuvanmuutokset palautuvat alku tilanteeseen Arvot asianmukaisesti asetettu Nollaa Kopioitu leikepöydälle EXIF tietoja ei löytynyt Jotakin meni väärin Käynnistä sovellus uudelleen Muokkaa EXIF Ok Poikkeus Ladataan… Lisää tagi Tallenna Selvä Puhdas EXIF Peruuta Kaikki kuvan EXIF tiedot tullaan pyyhkimään. Tätä toimintoa ei voi perua! Esiasetukset Leikkaa Tallennus Kaikki tallentamattomat muutokset katoavat, jos poistut nyt Lähdekoodi Saa tuoreimmat uutiset, keskustele ongelmista ja paljon muuta Yksittäinen muokkaus Muunna, muuta kokoa ja muokkaa kuvaa Värin valitsin Ota väri kuvasta, kopioi tai jaa Kuva Väri Väri kopioitu Rajaa kuvaa miten vain Versio Pidä EXIF Kuvat: %d Muuta esikatselua Poista Luo väri paletti näyte annetusta kuvasta Luo paletti Paletti Päivitys Uusi versio %1$s Tukematon tyypi: %1$s Palettia ei voida luoda valitulle kuvalle Alkuperäinen Tulostekansio Oletus Muokattu Määrittelemätön Laitteen tallennustila Muuta kokoa korkeuden mukaan Suurin koko KB Muuta kuvan kokoa seuraamalla annettua kokoa kilobiteissä Vertaa Vertaa kahta määritettyä kuvaa Valitse kaksi kuvaa aloitaaksesi Valitse kuvat Asetukset Yötila Pimeä Valoisa Järjestelmä Dynaamiset värit Mukauttaminen Salli kuvan moneetti Jos käytössä, kun valitset kuvan muokkausta varten sovelluksen värit muuttuvat kuvaan sopiviksi Kieli AMOLED tila Jos käytössä liittymien värit asetetaan absoluuttisen pimeään yötilassa Värisuunnitelma Punainen Vihreä Sininen Liitä sopiva aRGB värikoodi Ei mitään liitettävää Sovelluksen värisuunitelmaa ei voida muuttaa kun dynaamisen värit ovat käytössä Sovelluksen teema pohjautuu valittuun väriin Lisätietoja sovelluksesta Päivityksiä ei löytynyt Ongelman jäljitin Lähetä virhe raportteja ja ominaisuus ehdotuksia tänne Auta kääntämään Korjaa käännös virheitä tai lokalisoi projekteja muille kielille Kyselysi ei tuottanut tulosta Etsi täältä Jos käytössä, sovelluksen värit mukautuvat taustakuvan väriin %d tallentaminen epäonnistui Sähköpostiosoite Ensisijainen Kolmannen asteen Toissijainen Kulmien paksuus Taso Arvot Lisää Käyttöoikeus Anna Sovellus tarvitsee käyttöoikeuden tallennustilaasi kuvien tallennusta varten, se on välttämätöntä. Anna oikeus seuraavaassa dialogi laatikossa Sovellus tarvitsee käyttöoikeuden toimiakseen, hyväksy se manuaalisesti Ulkoinen tallennustila Moneetti väri Tämä sovellus on täysin ilmainen, mutta jos haluat voit tukea sen kehitystä, voit painaa tästä FAB kohdistus Tarkista päivitykset Jos käytössä, päivitys dialogi näytetään sinulle kun sovellus käynnistyy Kuvan zoomaus Jaa Etuliite Tiedostonimi Emoji Valitse mikä emoji näytetään päänäytössä Lisää tiedosto koko Jos käytössä, lisää leveyden ja pituuden tallennettuihin kuviin tulostiedoston nimeen Poista EXIF Poistaa EXIF metatiedot mistä vain kuvasetistä Kuvan esikatselu Esikatsele minkä vain tyyppisiä kuvia: GIF, SVG, ja niin edelleen Kuvan lähde Kuvan valitsin Galleria Tiedosto etsijä Androidin moderni kuvan valitsin mikä ilmestyy näytön alareunaan, voi toimia vain Android 12+ . Siinä on ongelmia EXIF metadatan vastaanottamisessa Yksinkertainen galleria kuvan valitsin. Toimii vain jos sinulla on sovellus mikä tarjoaa median valintaa Käytä GetContent tarkoituksena valita kuva. Toimii missä vain, mutta siinä tiedetään olevan ongelmia kuvien valinnassa joillakin laitteilla. Se ei ole minun vikani. Vaihtoehtojen järjestely Muokkaa Järjestys Päättää työkalujen järjestyksen päänäytöllä Emojien lukumäärä SekvenssiNum AlkuperäinenTiedostonimi Lisää alkuperäinen tiedostonimi Jos käytössä, lisää alkuperäisen tiedostonimen tuloskuvan nimeen Korvaa sekvenssi numero Jos käytössä, korvaa standardin aikaleiman kuvan sekvenssi numerolla, jos käytät eräkäsittelyä Alkuperäisen tiedostonimen lisääminen ei toimi, jos kuvan valitsin lähde on valittuna Verkko Kuva Lataus Lataa joku kuva verkosta esikatseluun, zoomaa, editoi ja tallenna se jos haluat. Ei kuvaa Kuvan linkki Täytä Sovita Sisällön skaalaus Muuta kuvan kokoa annettuun leveyteen ja pituuteen. Kuvan kuvasuhde voi muuttua. Muokkaa leveiden kuvien kokoa annettuun leveyteen tai pituuteen. Kaikki koon laskennat tehdään tallennuksen jälkeen. Kuvien kuvasuhde säilytetään Kirkkaus Kontrasti Sävy Saturaatio Lisää suodatin Suodatin Lisää suodatin ketjuja kuviin Suodattimet Valoisa Väri suodatin Alpha Valotus Valkotasapaino Lämpötila Sävy Monokroominen Gamma Kohokohdat ja varjot Kohokohdat Varjot Usva Tehoste Etäisyys Kaltevuus Terävöitys Seepia Negatiivinen Solaarisointi Elävyys Musta Ja Valkoinen Ristiviivoitus Välistys Viivan paksuus Sobelin terä Sumennus Puolisävy CGA väriavaruus Gaussian sumennus Laatikkosumennus Kahdenvälinen sumennus Kohokuvio Laplacian Vignetti Aloitus Loppu Kuwahara pehmennys Päällekäis-sumennus Säde Skaalaus Vääristymä Kulma Pyöre Pullistuma Laajennus Ympyrätaittuminen taitekerroin Lasiympyrän taittuminen Värimatrix Läpinäkymättömyys Muokkaa rajoissa Muokkaa kuvia annettuun leveyteen ja pituuteen säilyttäen silti kuvasuhteen Luonnos Kynnys Kvantisointi tasot Sileä piirretty Piirrety Posteroi Ei maksimaalinen vaimennus Heikko pikselien säilytys Haku Konvoluutio 3x3 RGB filtteri Feikkiväri Ensimmäinen väri Toinen väri Uudelleenjärjestely Nopea sumennus Sumennuksen koko Sumennus keskus x Summenus keskus y Zoomaus sumennus Väri tasapaino luminanssikynnys Poistut käytöstä Tiedostot sovelluksen, aktivoi se käyttääksesi tätä ominaisuutta Piirrä Piirrä kuvaan, ihan kuin luonnoskirjaan, tai piirrä itse taustaan Maalin väri Alfamaali Piirrä kuvaan Valitse kuva ja piirrä siihen jotakin Piirrä taustaan Valitse taustan väri ja piirrä sen päälle Taustan väri Salainen Salaa ja pura minkä vain tiedoston (ei vain kuvien) salaus pohjattuna moniin olemassa oleviin salaus algoritmeihin Valitse tiedosto Salaa Pura salaus Valitse kansio aloitaaksesi Salauksen purku Salataan Avain Tiedosto käsitelty Tallenna tämä tiedosto laitteeseesi tai käytä jakotoimintoa laitaaksesi sen minne ikinä haluat Ominaisuudet Toteutus Yhteensopivuus Salasana pohjainen tiedostojen salaus. Valitut kansiot voidaan säilyttää valitussa hakemistossa tai jaettuna. Salaamattomat tiedostot voidaan avata suoraan AE-256, GCM tila, ei täytettä, 12 bittinen satunnainen IVs oletuksena. Voit valita vaaditun algoritmin. Avaimia käytetään 256-bit SHA-3 hasheina Tiedosto koko Suurin tiedosto koko on rajoitettu Android OS:än ja saatavilla olevan muistin mukaan, minkä laite on päättänyt\nHuomioi: Muisti ei ole tallennustila Huomioi, että yhteensopivuus muiden tiedoston salaus sovellusten tai palvelujen kanssa ei ole taattu. Hieman erilainen avaimen käsittely tai salaus voi johtaa yhteensopimattomuuteen Väärä salasana, tai valittu kansio ei ole salattu Yritys tallentaa kuva annetulla leveydellä ja korkeudella voi johtaa muisti virheeseen. Teet tämän omalla vastuullasi. Välimuisti Välimuistin koko Löydetty %1$s Automaattinen välimuistin tyhjennys Jos käytössä, välimuisti tyhjennetään kun sovellus käynnistetään Luo Työkalut Ryhmitä vaihtoehdot tyypin mukaan Ryhmittää vaihtoehdot päänäytöllä niiden tyypin mukaan, muokatun lista järjestelyn sijaan Listausta ei voida muuttaa kun ryhmitys vaihtoehto on käytössä Muokkaa kuvankaapausta Toissijainen muokkaus Kuvankaappaus Varavaihtoehto Ohita Kopioi Tallennetaan %1$s sisällä. Tila voi olla epävakaa, koska se on häviötön formaatti Jos olet valinnut esiasetuksen 125, kuva tallennetaan 125% koossa alkuperäisestä kuvasta. Jos valitset esiasetuksen 50, sitten kuva tallentuu 50% kossa Esiasetus täällä päättää tulostiedoston %, esim. jos valitset 50 5 megabitin kuvaan sitten siihen tulee 2,5 megabittiä lisää tallennuksen jälkeen Satunnaista tiedostonimi Jos käytössä tulostiedoston tiedostonimi tulee olemaan täysin satunnainen Tallennettu %1$s kansioon nimellä %2$s Tallennettu %1$s kansioon Telegram chat Keskustele sovelluksesta ja saa palautetta muilta käyttäjiltä. Voit myös saada beetapäivityksiä ja esikatseluita. Rajausmaski Kuvasuhde Käytä tätä maskityyppiä luodaksesi maskin annetusta kuvasta, huomioi että sen PITÄISI sisältää alfakanavan Varmuuskopioi ja palauta Varmuuskopioi Palauta Varmuuskopioi sovelluksesi asetukset tiedostoon Palauta sovelluksen asetukset aikaisemmin luodusta kansiosta Vaurioitunut tiedosto tai ei varmuuskopiota Asetukset palautettu onnistuneesti Ole yhteydessä Tämä palauttaa kaikki asetukset oletus arvoihin. Huomioi että tätä ei voi peruuttaa ilman varmuuskopio kansiota, kuten mainittu aikaisemmin Peruuta Aiot poistaa valitun värisuunnitelman. Tätä operaatiota ei voi peruuttaa Poista suunnittelma Fontti Teksti Fontin koko Oletus Suurten fontti kokojen käyttö voi aiheuttaa UI virheitä ja ongelmia, mitä ei voi korjata. Käytä varovaisuudella. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz Åå Ää Öö 0123456789 !? Tunteet Ruoka Ja Juoma Luonto Ja Eläimet Kohteet Symboolit Käytä emojia Matkat Ja Paikat Aktiviteetit Taustan Poistaja Poista tausta kuvasta piirtämällä tai käytä Auto vaihtoehtoa Leikkaa kuva Alkuperäisen kuvan metatiedot pidetään Läpinäkyvät alueet kuvan ympärillä leikataan Pyyhi automaattisesti tausta Palauta kuva Pyyhin tila Pyyhi tausta Palauta tausta Sumennuksen säde Pipetti Piirostila Luo julkaisu Hupsis… Jotain meni pieleen. Voit kirjoittaa minulle käyttäen vaihtoehtoja alhaalla ja yritän löytää ratkaisun Muokkaa Ja Muunna Muokkaa annettujen kuvien kokoa tai muunna ne muihin formaateihin. EXIF metatietoa voidaan myös muokata täällä jos valitaan yksittäinen kuva. Suurin väri määrä Tämä sallii sovelluksen kerätä kastumisraporteja automaattisesti Analytiikat Salli anonyymisen sovelluksen käyttötilastojen keräämiseen Tällä hetkellä, %1$s formaatti salli vain EXIF metatietojen luvun Androidilla. Tuloskuvassa ei tule olemaan yhtään tallennettua metatietoa, kun tallennettu. Ponnistus %1$s :n arvo tarkoittaa nopeaa tiivistystä, johtaen erittäin suureen tiedosto kokoon. %2$s tarkoittaa hitaampaa tiivistystä, johtaen pienempään tiedostoon. Odota Tallennus melkein valmis. Peruuttaminen nyt vaatii uudelleen tallennusta. Päivitykset Salli beetat Päivitysten tarkastus tulee sisältämään beetaversiot, jos kytketty päälle Piirrä nuolia Jos käytössä, polkujen piirtäminen tullaan näyttämään osoittavina nuolina Pensselin pehmeys Kuvat tulevat olemaan keskitettyjä määritettyyn kokoon. Kankaat laajennetaan annetulla taustavärillä jos kuva on pienempi kun annetut mitat. Lahjoitus Kuvan ompelu Yhdistä annetut kuvat saadaksesi yhden ison kuvan Valitse ainakin 2 kuvaa Tuloskuvan skaalaus Kuvan suunta Horisontaalinen Vertikaalinen Skaalaa pienet kuvat suuriksi Pienet kuvat skaalataan isoimmaksi kuvaksi sekvenssissä, jos päällä Kuvien järjestys Normaali Sumennetut reunat Pittää sumennetut reunat alkuperäisen kuvan alapuolelle täyttääkseen tilaa sen ympärillä yhden värin sijaan, jos kytketty päälle Pikselöinti Paranneltu pikselöinti Iskevä pikselöinti Paranneltu Timantti Pikselöinti Timantti Pikselöinti Ympyrä Pikselöinti Paranneltu Ympyrä Pikselöinti Korvaa Väri Toleranssi Väri Korvaukseen Kohdeväri Väri Poistoon Poista Väri Uudelleen Koodaa Pikseli Koko Lukitse piirin kulma Jos päällä piirostilassa, näyttö ei käänny Tarkista päivitykset Paletti tyyli Tonaalinen Piste Neutraali Eloisa Ilmeikäs Sateenkaari Hedelmä Salaatti Uskollisuus Kontentti Oletusarvoinen palettityyli, se salli kaikkien neljän värin muokkauksen, muut sallivat sinun vain asettaa avain värin Tyyli, mikä on hieman enemmän kromaattinen kuin monokroominen Äänekäs teema, värikkyys on täysillä Ensisijaiselle paketille, tehostettu muille Leikkisä teema, lähde värin sävy ei ilmesty teemassa Monokroominen teema, värit ovat täysin mustia/valkoisia/harmaita Suunnitelma, mikä asettaa lähde värin Scheme primaryContainer :ssa Suunnitelma, joka on samanlainen kontentti suunnitelman kanssa Tämä päivitystarkastus yhdistää GitHubiin tarkistaaksen onko siellä uusia päivityksiä saatavilla Huomio Katoavat Reunat Ei päällä Molemmat Käänteisvärit Korvaa teeman värit negatiivisilla, jos päällä Etsi Kytkee päälle kyvyn etsiä kaikkien päänäytöllä olevien työkalujen avulla PDF työkalut Operoi PDF tiedostojen kanssa: Esikatsele, Purista erä kuvia tai luo yksi annetuista kuvista Esikatsele PDF PDF:stä kuvaan Kuvista PDF:ään Yksinkertainen PDF esikatselu Purista PDF annetun ulostulo formaatin kuviksi Pakkaa annetut kuvat ulostulo PDF tiedostoksi Maski Suodatin Lisää suodatin ketjuja annettulle maskialuelle, jokainen maskialue voi päättää itse sen oman sarjan suodattimia Maskit Lisää Maski Maski %d Maskin Väri Maskin Esikatselu Piiretty suodatinmaski rendeöidään näyttääkseen sinulle summittaisen tuloksen Käänteinen täyttötyyppi Jos päällä jokainen ai maskeerattu alue tullaan suodattamaan, oletusarvoisen käyttäytymisen sijaan Olet poistamassa valittua suodatinmaskia. Tätä operaatiota ei voi peruuttaa Poista maski Täysi Suodatin Lisää minä vain suodatin ketju annettuihin kuviin tai yksittäiseen kuvaan Aloita Keskus Loppu Simppelit Variantit Kohokohdistaja Neon Kynä Yksityis Summenus Piirrä semi-läpinäkyviä tervöittetyjä kohokohdistaja polkuja Lisää hieman hohto tehostetta piirokseesi Oletusarvoinen, yksinkertainen - Vain väri Summena kuva piirrospolun alapuolelta turvataksesi mitä vain mitä haluat piilottaa Samanlainen Yksityis Sumennuksen kanssa, mutta pikselöi sumennuksen sijaan Säiliöt Piirrä varjo säiliöiden taakse Liukusäätimet Kytkimet FAB:t Painikkeet Piirrä varjo liukusäätimien taakse Piirrä varjo kytkimien taakse Piirrä varjo kelluvien toimintapainikkeiden taakse Piirrä varjo painikkeiden taakse Sovelluspalkit Piirrä varjo sovelluspalkkien taakse Arvo välillä %1$s - %2$s Automaattinen kierto Mahdollistaa rajaruudun käyttöönoton kuvan suunnassa Piirrä polkutila Kaksoisviivanuoli Ilmainen piirustus Kaksoisnuoli Viivanuoli Nuoli Linja Piirtää polun syöttöarvona Piirtää polun aloituspisteestä loppupisteeseen suorana Piirtää osoittavan nuolen aloituspisteestä loppupisteeseen viivana Piirtää osoittavan nuolen tietystä polusta Piirtää kaksoisnuolen aloituspisteestä loppupisteeseen viivana Piirtää kaksoisnuolen annetulta polulta Piirretty soikea Outlined Rect Soikea Rect Piirtää suoran aloituspisteestä loppupisteeseen Piirtää soikean aloituspisteestä loppupisteeseen Piirtää soikean ääriviivat aloituspisteestä loppupisteeseen Piirtää ääriviivat suoraan aloituspisteestä loppupisteeseen Lasso Piirtää suljetun täytetyn polun annetun polun mukaan Ilmainen Vaakasuora verkko Pystysuora verkko Ommeltila Rivien määrä Sarakkeiden määrä Ei löytynyt \"%1$s\" hakemistoa, vaihdoimme sen oletushakemistoon, tallenna tiedosto uudelleen Leikepöytä Automaattinen pin Lisää tallennetun kuvan automaattisesti leikepöydälle, jos se on käytössä Tärinä Värähtelyn voimakkuus Tiedostojen korvaamiseksi sinun on käytettävä \"Explorer\"-kuvalähdettä, kokeile kuvien uudelleenpoimimista, olemme vaihtaneet kuvalähteen tarvittavaan Korvaa tiedostot Alkuperäinen tiedosto korvataan uudella sen sijaan, että tallennettaisiin valittuun kansioon. Tämän vaihtoehdon kuvan lähteen on oltava \"Explorer\" tai GetContent, kun tätä vaihdetaan, se asetetaan automaattisesti Tyhjä Suffiksi Skaalaustila Bilineaarinen Catmull Bicubic Hän Erakko Lanczos Mitchell Lähin Spline Perus Oletusarvo Lineaarinen (tai bilineaarinen, kahdessa ulottuvuudessa) interpolointi on tyypillisesti hyvä kuvan koon muuttamiseen, mutta aiheuttaa ei-toivottua yksityiskohtien pehmenemistä ja voi silti olla hieman rosoinen. Parempia skaalausmenetelmiä ovat Lanczos-resampling ja Mitchell-Netravali-suodattimet Yksi yksinkertaisimmista tavoista suurentaa kokoa, korvaa jokainen pikseli useilla samanvärisillä pikseleillä Yksinkertaisin Android-skaalaustila, jota käytetään melkein kaikissa sovelluksissa Menetelmä ohjauspisteiden joukon sujuvaan interpoloimiseen ja uudelleennäytteenottoon, jota käytetään yleisesti tietokonegrafiikassa tasaisten käyrien luomiseen Ikkunatoiminto, jota käytetään usein signaalinkäsittelyssä spektrivuodon minimoimiseksi ja taajuusanalyysin tarkkuuden parantamiseksi kapenemalla signaalin reunoja Matemaattinen interpolointitekniikka, joka käyttää arvoja ja johdannaisia ​​käyräsegmentin päätepisteissä luomaan tasaisen ja jatkuvan käyrän Uudelleennäytteistysmenetelmä, joka ylläpitää korkealaatuista interpolaatiota käyttämällä painotettua sinc-funktiota pikseliarvoihin Uudelleennäytteenottomenetelmä, joka käyttää konvoluutiosuodatinta säädettävillä parametreilla saavuttaakseen tasapainon terävyyden ja anti-aliasoinnin välillä skaalatussa kuvassa Käyttää paloittain määriteltyjä polynomifunktioita käyrän tai pinnan sujuvaan interpoloimiseen ja approksimoimiseen, mikä tarjoaa joustavan ja jatkuvan muodon esityksen Vain klippi Tallennusta tallennustilaan ei suoriteta, vaan kuvaa yritetään laittaa vain leikepöydälle Sivellin palauttaa taustan poistamisen sijaan OCR (tunnista teksti) Tunnista teksti annetusta kuvasta, tuettu yli 120 kieltä Kuvassa ei ole tekstiä tai sovellus ei löytänyt sitä \"Tarkkuus: %1$s\" Tunnistustyyppi Nopeasti Vakio Parhaat Ei dataa Jotta Tesseract OCR toimisi oikein, laitteellesi on ladattava lisää harjoitustietoja (%1$s).\nHaluatko ladata %2$s-tiedot? Lataa Ei yhteyttä, tarkista se ja yritä uudelleen ladataksesi junamallit Ladatut kielet Saatavilla olevat kielet Segmentointitila Käytä Pixel Switchiä Käyttää Google Pixelin kaltaista kytkintä Ylikirjoitettu tiedosto nimellä %1$s alkuperäisessä kohteessa Suurennuslasi Ottaa käyttöön suurennuslasin sormen yläosassa piirustustiloissa parantaakseen käytettävyyttä Pakota alkuarvo Pakottaa exif-widgetin tarkistettavaksi aluksi Salli useita kieliä Liuku Rinnakkain Toggle Tap Läpinäkyvyys Arvioi sovellus Rate Tämä sovellus on täysin ilmainen, jos haluat sen kasvavan, tähdä projekti Githubissa 😄 Vain suunta ja komentosarjan tunnistus Automaattinen suuntaus ja komentosarjan tunnistus Vain auto Auto Yksi sarake Yhden lohkon pystysuuntainen teksti Yksi lohko Yksi rivi Yksi sana Ympyrä sana Yksittäinen merkki Vähän tekstiä Harva tekstin suuntaus ja komentosarjan tunnistus Raaka linja Haluatko poistaa kielen \"%1$s\" OCR-harjoitustiedot kaikille tunnistustyypeille vai vain valitulle (%2$s)? Nykyinen Kaikki Gradientin tekijä Luo tietyn tulostuskoon gradientti mukautetuilla väreillä ja ulkoasutyypillä Lineaarinen Säteittäinen Lakaista Gradientin tyyppi Keskusta X Keskusta Y Laattatila Toistettu Peili Puristin Tarra Väri pysähtyy Lisää väriä Ominaisuudet Kirkkauden tehostaminen Näyttö Gradienttipeitto Luo mikä tahansa kaltevuus annettujen kuvien yläreunasta Muutokset Kamera Ota kuva kameralla. Huomaa, että tästä kuvalähteestä on mahdollista saada vain yksi kuva Vesileima Peitä kuvat muokattavissa olevilla teksti-/kuvavesileimoilla Toista vesileima Toistaa vesileiman kuvan päällä yksittäisen sijasta tietyssä kohdassa Offset X Offset Y Vesileiman tyyppi Tätä kuvaa käytetään vesileiman mallina Tekstin väri Peittokuvatila GIF-työkalut Muunna kuvat GIF-kuvaksi tai poimi kehyksiä annetusta GIF-kuvasta GIF kuviin Muunna GIF-tiedosto kuvasarjaksi Muunna kuvaerä GIF-tiedostoksi Kuvat GIF-muotoon Aloita valitsemalla GIF-kuva Käytä ensimmäisen kehyksen kokoa Korvaa määritetty koko ensimmäisen kehyksen mitoilla Toista Count Kehyksen viive millis FPS Käytä Lassoa Käyttää Lassoa kuten piirustustilassa pyyhkimiseen Alkuperäisen kuvan esikatselu Alpha Konfetti Konfettia näytetään tallennuksen, jakamisen ja muiden ensisijaisten toimien yhteydessä Suojattu tila Piilottaa sovelluksen sisällön viimeaikaisissa sovelluksissa. Sitä ei voi tallentaa tai tallentaa. Poistu Jos poistut esikatselusta nyt, sinun on lisättävä kuvat uudelleen Dathering Kvantisoija Harmaa asteikko Bayer Two By Two Dithering Bayer Three By Three Dithering Bayer Four By Four Dathering Bayerin kahdeksaan kahdeksaan sekoitus Floyd Steinberg Dithering Jarvis-tuomari Ninke Dithering Sierra Dithering Kaksirivinen Sierra Dithering Sierra Lite Dithering Atkinson Dithering Stucki Dithering Burkes Dithering Väärä Floyd Steinbergin närästys Vasemmalta oikealle Dathering Random Dithering Yksinkertainen kynnysvärähtely Sigma Spatiaalinen Sigma Mediaani sumeus B Spline Käyttää paloittain määriteltyjä bikuutiopolynomifunktioita käyrän tai pinnan sujuvaan interpoloimiseen ja approksimoimiseen, joustavaan ja jatkuvaan muodon esitykseen Alkuperäinen pinon sumennus Tilt Shift Virhe Määrä Siemen Anaglyfi Melu Pikselilajittelu Sekoita Parannettu Glitch Kanavanvaihto X Kanavanvaihto Y Korruption koko Korruption vaihto X Korruptiovuoro Y Teltan hämärtyminen Side Fade Sivu Yläosa Pohja Vahvuus Erode Anisotrooppinen diffuusio Diffuusio Johtuminen Vaakasuuntainen tuuliporras Nopea kaksipuolinen sumennus Poisson Blur Logaritminen sävykartoitus ACES Filmic Tone Mapping Kiteytyä Viivan väri Fraktaalilasi Amplitudi Marmori Turbulenssi Öljy Vesi vaikutus Koko Taajuus X Taajuus Y Amplitudi X Amplitudi Y Perlin vääristymä ACES Hill Tone Mapping Hable Filmic Tone Mapping Heji-Burgess Tone -kartoitus Nopeus Dehaze Omega Värimatriisi 4x4 Värimatriisi 3x3 Yksinkertaiset tehosteet Polaroid Tritanomaly Deuteranomaalia Protanomaalia Vintage Brownin Coda Chrome Night Vision Lämmin Viileä Tritanopia Deutaronotopia Protanopia Akromatomia Achromatopsia Vilja Epäterävä Pastelli Oranssi Haze Vaaleanpunainen unelma Kultainen tunti Kuuma kesä Purppura sumu Auringonnousu Värikäs pyörre Pehmeä kevätvalo Syksyn sävyt Laventelin unelma Kyberpunk Limonadin valo Spektraalinen tuli Night Magic Fantasia maisema Väriräjähdys Sähköinen gradientti Karamellipimeys Futuristinen gradientti Vihreä aurinko Sateenkaaren maailma Syvä violetti Avaruusportaali Punainen Pyörre Digitaalinen koodi Bokeh Sovelluspalkin emoji muuttuu satunnaisesti Satunnaiset emojit Et voi käyttää satunnaisia ​​hymiöitä, kun emojit on poistettu käytöstä Et voi valita emojia, kun satunnaiset emojit ovat käytössä Vanha tv Sekoita sumennus Suosikki Suosikkisuodattimia ei ole vielä lisätty Kuvan muoto Lisää valitun muodon sisältävän säilön kuvakkeiden alle Ikonin muoto Drago Aldridge Katkaisu Heräät Mobius Siirtyminen Huippu Värien anomalia Kuvat on kirjoitettu alkuperäiseen kohteeseen Kuvamuotoa ei voi muuttaa, kun tiedostojen korvaaminen on käytössä Emoji värimaailmana Käyttää emojin pääväriä sovelluksen väriteemana manuaalisesti määritetyn väriteeman sijaan Luo Material You -paletin kuvasta Tummat Värit Käyttää yötilan värimaailmaa vaalean version sijaan Kopioi Jetpack Compose -koodina Renkaan sumennus Cross Blur Ympyrän sumennus Tähtien sumennus Lineaarinen Tilt-Shift Poistettavat tunnisteet APNG-työkalut Muunna kuvat APNG-kuvaksi tai poimi kehyksiä annetusta APNG-kuvasta APNG kuviin Muunna APNG-tiedosto kuvasarjaksi Muunna kuvaerä APNG-tiedostoksi Kuvat APNG:hen Aloita valitsemalla APNG-kuva Liikesumennus Postinumero Luo Zip-tiedosto annetuista tiedostoista tai kuvista Vedä kahvan leveys Konfetti tyyppi Juhlava Räjähtää Sade Kulmat JXL työkalut Suorita JXL ~ JPEG-transkoodaus ilman laadun heikkenemistä tai muunna GIF/APNG JXL-animaatioksi JXL - JPEG Suorita häviötön transkoodaus JXL:stä JPEG:ksi Suorita häviötön transkoodaus JPEG:stä JXL:ksi JPEG - JXL Aloita valitsemalla JXL-kuva Nopea Gaussian Blur 2D Nopea Gaussian Blur 3D Nopea Gaussian Blur 4D Auton pääsiäinen Sallii sovelluksen liittää leikepöydän tiedot automaattisesti, joten ne näkyvät päänäytössä ja voit käsitellä niitä Harmonisointiväri Harmonisointitaso Lanczos Bessel Uudelleennäytteenottomenetelmä, joka ylläpitää korkealaatuista interpolointia käyttämällä Bessel-funktiota (jinc) pikseliarvoihin GIF JXL:ään Muunna GIF-kuvat JXL-animoiduiksi kuviksi APNG - JXL Muunna APNG-kuvat JXL-animoiduiksi kuviksi JXL kuviin Muunna JXL-animaatio kuvasarjaksi Kuvat JXL:ään Muunna kuvasarja JXL-animaatioksi Käyttäytyminen Ohita tiedostojen poiminta Tiedostovalitsin näytetään välittömästi valitulla näytöllä, jos tämä on mahdollista Luo esikatselut Mahdollistaa esikatselun luomisen, tämä voi auttaa välttämään kaatumisia joissakin laitteissa, tämä myös poistaa käytöstä jotkin muokkaustoiminnot yksittäisessä muokkausvaihtoehdossa Häviöinen pakkaus Käyttää häviöllistä pakkausta pienentääkseen tiedostokokoa häviöttömän sijaan Pakkaustyyppi Säätää tuloksena olevan kuvan dekoodausnopeutta, tämän pitäisi auttaa avaamaan tuloksena olevaa kuvaa nopeammin, arvo %1$s tarkoittaa hitainta dekoodausta, kun taas %2$s - nopein, tämä asetus voi suurentaa ulostulokuvan kokoa Lajittelu Päivämäärä Päivämäärä (käännetty) Nimi Nimi (käänteinen) Kanavien asetukset Tänään Eilen Embedded Picker Image Toolboxin kuvanvalitsin Ei käyttöoikeuksia Pyytää Valitse useita mediatiedostoja Valitse yksittäinen media Valita Yritä uudelleen Näytä asetukset vaaka-asennossa Jos tämä ei ole käytössä, vaakatilassa asetukset avautuvat yläsovelluspalkin painikkeeseen kuten aina, pysyvän näkyvän vaihtoehdon sijaan Koko näytön asetukset Ota se käyttöön, niin asetussivu avautuu aina koko näytön kokoisena liukuvan vetolaatikon sijaan Kytkimen tyyppi Säveltää Vaihdettava Jetpack Compose -materiaali Vaihdettava materiaali Max Muuta ankkurin kokoa Pikseli Sujuva Kytkin, joka perustuu \"Fluent\"-suunnittelujärjestelmään Cupertino Kytkin perustuu \"Cupertino\"-suunnittelujärjestelmään Kuvat SVG:hen Jäljitä annetut kuvat SVG-kuviin Käytä näytepalettia Kvantisointipaletista otetaan näyte, jos tämä vaihtoehto on käytössä Polku jätetään pois Tämän työkalun käyttöä suurten kuvien jäljittämiseen ilman skaalausta ei suositella, se voi aiheuttaa kaatumisen ja pidentää käsittelyaikaa Alennettu kuva Kuva skaalataan pienempiin mittoihin ennen käsittelyä, mikä auttaa työkalua toimimaan nopeammin ja turvallisemmin Vähimmäisvärisuhde Linjojen kynnys Neliöllinen kynnys Koordinaattien pyöristystoleranssi Polun asteikko Palauta ominaisuudet Kaikki ominaisuudet asetetaan oletusarvoihin. Huomaa, että tätä toimintoa ei voi kumota Yksityiskohtainen Oletusviivan leveys Moottoritila Legacy LSTM verkko Legacy &amp; LSTM Muuntaa Muunna kuvaerät annettuun muotoon Lisää uusi kansio Bittiä näytettä kohti Puristus Fotometrinen tulkinta Näytteitä pikseliä kohden Tasokokoonpano Y Cb Cr Subnäytteenotto Y Cb Cr -paikannus X Resoluutio Y Resoluutio Resoluutioyksikkö Strip Offsets Rivit per nauha Strip Byte Counts JPEG-vaihtomuoto JPEG-vaihtomuodon pituus Siirtotoiminto Valkoinen piste Ensisijaiset kromaattisuudet Y Cb Cr kertoimet Viite musta valkoinen Päivämäärä Aika Kuvan kuvaus Tehdä Malli Ohjelmisto Taiteilija Tekijänoikeus Exif versio Flashpix versio Väriavaruus Gamma Pixel X Dimension Pixel Y -mitta Pakattu bittiä pikseliä kohden Tekijän huomautus Käyttäjän kommentti Aiheeseen liittyvä äänitiedosto Päivämäärä Aika Alkuperäinen Päivämäärä Aika digitoitu Siirtymäaika Offset Time Original Siirtymäaika digitoitu Sub Sec Time Sub Sec Time Alkuperäinen Sub Sec Time digitoitu Altistumisaika F Numero Altistusohjelma Spektriherkkyys Valokuvaherkkyys Oecf Herkkyystyyppi Normaali lähtöherkkyys Suositeltu altistusindeksi ISO-nopeus ISO-nopeus Latitude vyy ISO-nopeus Latitude zzz Suljinnopeuden arvo Aukon arvo Kirkkauden arvo Exposure Bias Value Suurin aukkoarvo Aiheetäisyys Mittaustila Flash Aihealue Polttoväli Flash Energy Spatial Frequency Response Polttotason X resoluutio Polttotason Y resoluutio Polttotason resoluutioyksikkö Aiheen sijainti Altistusindeksi Tunnistusmenetelmä Tiedoston lähde CFA-kuvio Mukautettu renderöity Valotustila Valkotasapaino Digitaalinen zoomaussuhde Polttoväli 35 mm filmi Scene Capture Type Hanki hallinta Kontrasti Kylläisyys Terävyys Laiteasetusten kuvaus Aiheen etäisyysalue Kuvan yksilöllinen tunnus Kameran omistajan nimi Rungon sarjanumero Objektiivin tekniset tiedot Linssin merkki Linssin malli Objektiivin sarjanumero GPS-version tunnus GPS Latitude Ref GPS Latitude GPS Pituusaste Ref GPS pituusaste GPS Korkeus Ref GPS korkeus GPS-aikaleima GPS-satelliitit GPS-tila GPS-mittaustila GPS DOP GPS Speed ​​Ref GPS nopeus GPS Track Ref GPS-jälki GPS Img Suuntaviite GPS Img suunta GPS-kartan päivämäärä GPS-kohteen leveysaste Ref GPS Dest Latitude GPS-kohteen pituusaste Ref GPS-kohteen pituusaste GPS Dest Bearing Ref GPS Dest Bearing GPS Dest Distance Ref GPS-kohdeetäisyys GPS-käsittelymenetelmä GPS-alueen tiedot GPS-päivämääräleima GPS-differentiaali GPS H -paikannusvirhe Yhteentoimivuusindeksi DNG versio Oletusrajauskoko Esikatselukuva Aloita Esikatselukuvan pituus Aspect Frame Anturin alareuna Anturin vasen reuna Anturin oikea reuna Anturin yläreuna ISO Piirrä tekstiä polulle annetulla fontilla ja värillä Fontin koko Vesileiman koko Toista teksti Nykyistä tekstiä toistetaan polun loppuun asti yhden kerran piirtämisen sijaan Viivan koko Käytä valittua kuvaa piirtämään se annettua polkua pitkin Tätä kuvaa käytetään piirretyn polun toistuvana syötönä Piirtää ääriviivat kolmion aloituspisteestä loppupisteeseen Piirtää ääriviivat kolmion aloituspisteestä loppupisteeseen Piirretty kolmio Kolmio Piirtää monikulmion aloituspisteestä loppupisteeseen Monikulmio Piirretty monikulmio Piirtää ääriviivat monikulmion aloituspisteestä loppupisteeseen Vertices Piirrä säännöllinen monikulmio Piirrä monikulmio, joka on säännöllinen vapaan muodon sijaan Piirtää tähden aloituspisteestä loppupisteeseen Tähti Piirretty tähti Piirtää rajatun tähden aloituspisteestä loppupisteeseen Sisäinen sädesuhde Piirrä tavallinen tähti Piirrä tähti, joka on tavallinen vapaamuotoisen sijaan Antialias Mahdollistaa antialiasoinnin terävien reunojen estämiseksi Avaa Muokkaa esikatselun sijaan Kun valitset avattavan (esikatselun) kuvan ImageToolboxissa, muokkausvalintasivu avautuu esikatselun sijaan Asiakirjan skanneri Skannaa asiakirjoja ja luo PDF-tiedostoja tai erota niistä kuvia Aloita skannaus napsauttamalla Aloita skannaus Tallenna pdf-muodossa Jaa pdf-muodossa Alla olevat vaihtoehdot ovat kuvien tallentamiseen, eivät PDF-tiedostoihin Tasaa histogrammi HSV Tasaa histogrammi Anna prosentti Salli kirjoittaminen tekstikenttään Ottaa käyttöön tekstikentän esiasetusten valinnan takana, jotta ne syötetään lennossa Skaalaa väriavaruutta Lineaarinen Tasaa histogrammin pikselaus Ruudukon koko X Ruudukon koko Y Tasaa histogrammin mukautuva Tasaa histogrammin mukautuva LUV Tasaa histogrammin mukautuva LAB CLAHE CLAHE LAB CLAHE LUV Rajaa sisältöön Kehyksen väri Väri ohitettava Malli Mallisuodattimia ei ole lisätty Luo uusi Skannattu QR-koodi ei ole kelvollinen suodatinmalli Skannaa QR-koodi Valitussa tiedostossa ei ole suodatinmallitietoja Luo malli Mallin nimi Tätä kuvaa käytetään tämän suodatinmallin esikatseluun Mallin suodatin QR-koodikuvana Tiedostona Tallenna tiedostona Tallenna QR-koodikuvana Poista malli Olet poistamassa valitun mallisuodattimen. Tätä toimintoa ei voi kumota Lisätty suodatinmalli, jonka nimi on \"%1$s\" (%2$s) Suodattimen esikatselu QR &amp; Viivakoodi Skannaa QR-koodi ja hae sen sisältö tai liitä merkkijonosi luodaksesi uuden koodin Koodin sisältö Skannaa mikä tahansa viivakoodi korvataksesi kentän sisällön tai kirjoita jotain luodaksesi uuden viivakoodin valitulla tyypillä QR Kuvaus Min Myönnä kameralle lupa skannata QR-koodi Myönnä kameralle lupa skannata asiakirjaskanneria Kuutio B-spline Hamming Hanning Blackman Welch Quadric Gaussin Sfinksi Bartlett Robidoux Robidoux Sharp Spliini 16 Spline 36 Spline 64 Kaiser Bartlett-He Laatikko Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kuutiointerpolointi mahdollistaa tasaisemman skaalan ottamalla huomioon lähimmät 16 pikseliä, mikä antaa parempia tuloksia kuin bilineaarinen Käyttää paloittain määriteltyjä polynomifunktioita käyrän tai pinnan sujuvaan interpoloimiseen ja approksimoimiseen, joustavaan ja jatkuvaan muodon esitykseen Ikkunatoiminto, jota käytetään vähentämään spektrivuotoa kapenemalla signaalin reunoja, hyödyllinen signaalinkäsittelyssä Hann-ikkunan muunnos, jota käytetään yleisesti vähentämään spektrivuotoa signaalinkäsittelysovelluksissa Ikkunatoiminto, joka tarjoaa hyvän taajuusresoluution minimoimalla spektrivuodon, jota käytetään usein signaalinkäsittelyssä Ikkunatoiminto, joka on suunniteltu antamaan hyvä taajuusresoluutio pienemmällä spektrivuodolla, jota käytetään usein signaalinkäsittelysovelluksissa Menetelmä, joka käyttää toisen asteen funktiota interpoloinnissa ja tarjoaa tasaisia ​​ja jatkuvia tuloksia Gaussin funktiota käyttävä interpolointimenetelmä, joka on hyödyllinen kuvien tasoittamiseen ja kohinan vähentämiseen Kehittynyt uudelleennäytteenottomenetelmä, joka tarjoaa korkealaatuisen interpoloinnin minimaalisilla artefakteilla Kolmioikkunatoiminto, jota käytetään signaalinkäsittelyssä spektrivuodon vähentämiseksi Laadukas interpolointimenetelmä, joka on optimoitu luonnollisen kuvan koon muuttamiseen, terävyyden ja sileyden tasapainottamiseen Robidoux-menetelmän terävämpi versio, joka on optimoitu terävän kuvan koon muuttamiseen Spline-pohjainen interpolointimenetelmä, joka tarjoaa tasaiset tulokset 16-napaisen suodattimen avulla Spline-pohjainen interpolointimenetelmä, joka tarjoaa tasaiset tulokset käyttämällä 36-napauksen suodatinta Spline-pohjainen interpolointimenetelmä, joka tarjoaa tasaiset tulokset käyttämällä 64-napauksen suodatinta Interpolointimenetelmä, joka käyttää Kaiser-ikkunaa ja tarjoaa hyvän hallinnan pääkeilan leveyden ja sivukeilan tason väliseen kompromissiin Bartlett- ja Hann-ikkunat yhdistävä hybridi-ikkunatoiminto, jota käytetään vähentämään spektrivuotoja signaalinkäsittelyssä Yksinkertainen uudelleennäytteenottomenetelmä, joka käyttää lähimpien pikseliarvojen keskiarvoa, mikä usein johtaa lohkoiseen ulkonäköön Ikkunatoiminto, jota käytetään vähentämään spektrivuotoa ja tarjoaa hyvän taajuusresoluution signaalinkäsittelysovelluksissa Uudelleennäytteenottomenetelmä, joka käyttää 2-keilaa Lanczos-suodatinta korkealaatuiseen interpolointiin minimaalisella artefaktilla Uudelleennäytteenottomenetelmä, joka käyttää 3-keilaa Lanczos-suodatinta korkealaatuiseen interpolointiin minimaalisella artefaktilla Uudelleennäytteenottomenetelmä, joka käyttää 4-keilaa Lanczos-suodatinta korkealaatuiseen interpolointiin minimaalisella artefaktilla Lanczos 2 -suodattimen muunnos, joka käyttää jinc-toimintoa ja tarjoaa korkealaatuisen interpoloinnin minimaalisilla artefakteilla Lanczos 3 -suodattimen muunnos, joka käyttää jinc-toimintoa ja tarjoaa korkealaatuisen interpoloinnin minimaalisilla artefakteilla Lanczos 4 -suodattimen muunnos, joka käyttää jinc-toimintoa ja tarjoaa korkealaatuisen interpoloinnin minimaalisilla artefakteilla Hanning EWA Hanning-suodattimen elliptinen painotettu keskiarvo (EWA) mahdollistaa sujuvan interpoloinnin ja uudelleennäytteenoton Robidoux EWA Robidoux-suodattimen elliptinen painotettu keskiarvo (EWA) -versio korkealaatuiseen uudelleennäytteenottoon Blackman EVE Blackman-suodattimen elliptinen painotettu keskiarvo (EWA) -muunnelma soivien artefaktien minimoimiseksi Neliöinen EWA Quadric-suodattimen elliptinen painotettu keskiarvo (EWA) -versio tasaiseen interpolointiin Robidoux Sharp EWA Elliptinen painotettu keskiarvo (EWA) -versio Robidoux Sharp -suodattimesta terävämpiin tuloksiin Lanczos 3 Jinc EWA Lanczos 3 Jinc -suodattimen elliptinen painotettu keskiarvo (EWA) -versio korkealaatuiseen uudelleennäytteenottoon pienemmällä aliasuksella Ginseng Uudelleennäytteenottosuodatin, joka on suunniteltu korkealaatuiseen kuvankäsittelyyn, jossa on hyvä tasapaino terävyyden ja sileyden välillä Ginseng EWA Ginseng-suodattimen elliptinen painotettu keskiarvo (EWA) -versio parantaa kuvanlaatua Lanczos Sharp EWA Lanczos Sharp -suodattimen elliptinen painotettu keskiarvo (EWA) -muunnelma terävien tulosten saavuttamiseksi minimaalisilla artefakteilla Lanczos 4 terävin EWA Lanczos 4 Sharpest -suodattimen elliptinen painotettu keskiarvo (EWA) -versio erittäin terävän kuvan uudelleennäytteenottoa varten Lanczos Soft EWA Lanczos Soft -suodattimen elliptinen painotettu keskiarvo (EWA) -versio tasaisempaan kuvan uudelleennäytteenottoon Haasn Soft Haasnin suunnittelema uudelleennäytteenottosuodatin tasaista ja artefaktitonta kuvan skaalausta varten Muodin muuntaminen Muunna kuvaerän muodosta toiseen Hylkää ikuisesti Kuvan pinoaminen Pinoa kuvat päällekkäin valituilla sekoitustiloilla Lisää kuva Säiliöt laskevat Clahe HSL Clahe HSV Tasaa histogrammin mukautuva HSL Tasaa histogrammin mukautuva HSV Reunatila Leike Kääri Värisokeus Valitse tila mukauttaaksesi teemavärit valitun värisokeuden muunnelman mukaan Punaisen ja vihreän sävyn erottaminen on vaikeaa Vihreän ja punaisen sävyn erottaminen on vaikeaa Vaikeus erottaa siniset ja keltaiset sävyt Kyvyttömyys havaita punaisia ​​sävyjä Kyvyttömyys havaita vihreitä sävyjä Kyvyttömyys havaita sinisiä sävyjä Vähentynyt herkkyys kaikille väreille Täydellinen värisokeus, näkee vain harmaan sävyjä Älä käytä Color Blind -mallia Värit ovat täsmälleen samat kuin teemassa Sigmoidinen Lagrange 2 Luokan 2 Lagrange-interpolaatiosuodatin, joka soveltuu korkealaatuiseen kuvan skaalaukseen tasaisilla siirtymillä Lagrange 3 Lagrange-interpolaatiosuodatin, luokka 3, joka tarjoaa paremman tarkkuuden ja tasaisemmat tulokset kuvan skaalautuksessa Lanczos 6 Lanczosin uudelleennäytteenottosuodatin korkeammalla 6:n asteikolla, joka tarjoaa terävämmän ja tarkemman kuvan skaalaus Lanczos 6 Jinc Lanczos 6 -suodattimen muunnelma, jossa käytetään Jinc-toimintoa kuvan uudelleennäytteenoton laadun parantamiseksi Lineaarinen laatikon sumennus Lineaarinen teltan hämärtyminen Lineaarinen Gaussian Box Blur Lineaarinen pinon sumennus Gaussian Box Blur Lineaarinen nopea Gaussin sumeus Seuraava Lineaarinen nopea Gaussin sumennus Lineaarinen Gaussin sumennus Valitse yksi suodatin käyttääksesi sitä maalina Vaihda suodatin Valitse alta suodatin käyttääksesi sitä siveltimenä piirustuksessasi TIFF-pakkausmalli Matala poly Hiekkamaalaus Kuvan jakaminen Jaa yksittäinen kuva riveillä tai sarakkeilla Sovita rajoihin Yhdistä rajauksen koonmuutostila tähän parametriin halutun toiminnan saavuttamiseksi (Rajaa/Sovita kuvasuhteeseen) Kielten tuonti onnistui Varmuuskopioi OCR-malleja Tuoda Viedä asema Keskusta Ylävasen Ylhäällä oikea Vasemmalla alhaalla Alhaalla oikealla Yläkeskus Keski oikealla Alakeskus Keskellä vasen Kohdekuva Paletin siirto Tehostettu öljy Yksinkertainen vanha TV HDR Gotham Yksinkertainen Sketch Pehmeä hehku Värillinen juliste Tri Tone Kolmas väri Clahe Oklab Clara Olch Clahe Jzazbz Pilkku Klusteri 2x2 Dithering Klusteroitu 4x4 dithering Klusteroitu 8x8 dithering Yililoma Dithering Suosikkivaihtoehtoja ei ole valittu, lisää ne työkalusivulle Lisää suosikkeja Täydentävä Analoginen Triadinen Jaettu täydentävä Tetradic Neliö Analoginen + täydentävä Värityökalut Sekoita, luo sävyjä, luo sävyjä ja paljon muuta Väriharmoniat Värivarjostus Variaatio Sävyt Äänet Varjostimet Värien sekoitus Väritiedot Valittu väri Väri Sekoita Rahaa ei voi käyttää, kun dynaamiset värit ovat käytössä 512x512 2D LUT Kohde LUT-kuva Amatööri Neiti Etiketti Pehmeä eleganssi Soft Elegance Variant Paletin siirtovaihtoehto 3D LUT Kohde 3D LUT -tiedosto (.cube / .CUBE) LUT Bleach Bypass Kynttilänvalo Drop Blues Herkkä Amber Syksyn värit Filmivarasto 50 Sumuinen yö Kodak Hanki neutraali LUT-kuva Käytä ensin suosikkikuvankäsittelyohjelmaasi suodattimen lisäämiseen neutraaliin LUT:iin, jonka saat täältä. Jotta tämä toimisi oikein, jokainen pikselin väri ei saa olla riippuvainen muista pikseleistä (esim. sumeus ei toimi). Kun olet valmis, käytä uutta LUT-kuvaasi tulona 512*512 LUT-suodattimelle Pop Art Selluloidi Kahvia Kultainen metsä Vihertävä Retro keltainen Linkkien esikatselu Mahdollistaa linkin esikatselun noutamisen paikoista, joista voit saada tekstiä (QRCode, OCR jne.) Linkit ICO-tiedostoja voidaan tallentaa vain enintään 256 x 256 kokoisina GIF WEBP:hen Muunna GIF-kuvat WEBP-animoiduiksi kuviksi WEBP-työkalut Muunna kuvat WEBP-animoiduiksi kuviksi tai poimi kehyksiä annetusta WEBP-animaatiosta WEBP kuviin Muunna WEBP-tiedosto kuvasarjaksi Muunna kuvaerä WEBP-tiedostoksi Kuvat WEBP:hen Aloita valitsemalla WEBP-kuva Ei täydellistä pääsyä tiedostoihin Salli kaikkien tiedostojen käyttöoikeus nähdä JXL, QOI ja muut kuvat, joita ei tunnisteta kuviksi Androidissa. Ilman lupaa Image Toolbox ei voi näyttää näitä kuvia Piirustuksen oletusväri Oletuspiirtopolkutila Lisää aikaleima Mahdollistaa aikaleiman lisäämisen tulosteen tiedostonimeen Muotoiltu aikaleima Ota aikaleiman muotoilu käyttöön tulostiedoston nimessä perusmilliksen sijaan Ota aikaleimat käyttöön valitaksesi niiden muodon Kerran tallennettava sijainti Tarkastele ja muokkaa kertatallennuspaikkoja, joita voit käyttää painamalla pitkään tallennuspainiketta useimmissa vaihtoehdoissa Äskettäin käytetty CI-kanava ryhmä Kuvatyökalut Telegramissa 🎉 Liity chattiin, jossa voit keskustella mistä haluat ja katsoa myös CI-kanavaa, jonne julkaisen betaversioita ja ilmoituksia Saat ilmoituksia sovelluksen uusista versioista ja lue ilmoituksia Sovita kuva annettuihin mittoihin ja lisää taustaan ​​sumennus tai väri Työkalujen järjestely Ryhmittele työkalut tyypin mukaan Ryhmittelee päänäytön työkalut niiden tyypin mukaan mukautetun luettelojärjestelyn sijaan Oletusarvot Järjestelmäpalkkien näkyvyys Näytä järjestelmäpalkit pyyhkäisemällä Ottaa käyttöön pyyhkäisemisen näyttääksesi järjestelmäpalkit, jos ne ovat piilossa Auto Piilota kaikki Näytä kaikki Piilota navigointipalkki Piilota tilapalkki Melun tuottaminen Luo erilaisia ​​ääniä, kuten Perlin tai muita ääniä Taajuus Melun tyyppi Kiertotyyppi Fraktaalityyppi Oktaavia Tyhjyys Saada Painotettu vahvuus Ping pong vahvuus Etäisyystoiminto Palautustyyppi Jitter Domain Warp Tasaus Mukautettu tiedostonimi Valitse sijainti ja tiedostonimi, joita käytetään nykyisen kuvan tallentamiseen Tallennettu kansioon mukautetulla nimellä Collage Maker Tee kollaaseja jopa 20 kuvasta Kollaasin tyyppi Pidä kuvaa painettuna vaihtaaksesi, siirrä ja zoomaa säätääksesi sijaintia Poista kierto käytöstä Estää kuvien kiertämisen kahden sormen eleillä Ota reunuksiin kiinnitys käyttöön Siirron tai zoomauksen jälkeen kuvat napsahtavat täyttämään kehyksen reunat Histogrammi RGB- tai Brightness-kuvan histogrammi, joka auttaa sinua tekemään säätöjä Tätä kuvaa käytetään RGB- ja Brightness-histogrammien luomiseen Tesseact-asetukset Käytä joitain syöttömuuttujia tesseract-moottorille Mukautetut asetukset Vaihtoehdot tulee syöttää seuraavan mallin mukaisesti: \"--{option_name} {value}\" Automaattinen rajaus Vapaat kulmat Rajaa kuvaa polygonin mukaan, tämä myös korjaa perspektiiviä Pakottaa pisteitä kuvan rajoihin Kuvan rajat eivät rajoita pisteitä, mikä on hyödyllistä tarkemman perspektiivin korjauksessa Naamio Sisältötietoinen täyttö piirretyn polun alla Parantumispiste Käytä Circle-ydintä Avaaminen Sulkeminen Morfologinen gradientti Silinteri Musta hattu Sävykäyrät Nollaa käyrät Käyrät palautetaan oletusarvoihin Viivan tyyli Välin koko Katkotettu Piste katkoviiva Leimattu Siksak Piirtää katkoviivan piirrettyä polkua pitkin määritetyllä aukon koolla Piirtää pisteen ja katkoviivan annettua polkua pitkin Vain oletussuorat viivat Piirtää valitut muodot polulle määritetyin välimatkoin Piirtää polkua pitkin aaltoilevaa siksakia Siksak-suhde Luo pikakuvake Valitse kiinnitettävä työkalu Työkalu lisätään käynnistysohjelman aloitusnäyttöön pikakuvakkeena. Käytä sitä yhdessä \"Ohita tiedostojen poiminta\" -asetuksen kanssa saavuttaaksesi tarvittavan toiminnan Älä pinoa kehyksiä Mahdollistaa aikaisempien kehysten hävittämisen, joten ne eivät pinoudu päällekkäin Crossfade Kehykset liitetään toisiinsa ristiin Crossfade-kehykset lasketaan Kynnys yksi Kynnys kaksi Ovela Peili 101 Parannettu zoomaussumennus Laplalainen yksinkertainen Sobel Simple Helper Grid Näyttää tukiruudukon piirtoalueen yläpuolella, mikä helpottaa tarkkoja käsittelyjä Ruudukon väri Solun leveys Solun korkeus Kompaktit valitsimet Jotkut valintaohjaimet käyttävät kompaktia asettelua, joka vie vähemmän tilaa Myönnä kameralle lupa asetuksista ottaa kuvia Layout Päänäytön otsikko Vakionopeustekijä (CRF) Arvo %1$s tarkoittaa hidasta pakkausta, joka johtaa suhteellisen pieneen tiedostokokoon. %2$s tarkoittaa nopeampaa pakkausta, joka johtaa suureen tiedostoon. Lut kirjasto Lataa LUT-kokoelma, jota voit käyttää lataamisen jälkeen Päivitä LUT-kokoelma (vain uudet ovat jonossa), joita voit käyttää lataamisen jälkeen Muuta suodattimien oletuskuvan esikatselua Esikatselukuva Piilottaa Show Liukusäätimen tyyppi Hieno Materiaali 2 Tyylikäs liukusäädin. Tämä on oletusasetus Materiaali 2 liukusäädin Material You -liukusäädin Käytä Keskusvalintapainikkeet Valintaikkunoiden painikkeet sijoitetaan keskelle vasemman reunan sijaan, jos mahdollista Avoimen lähdekoodin lisenssit Tarkastele tässä sovelluksessa käytettyjen avoimen lähdekoodin kirjastojen lisenssejä Alue Uudelleennäytteenotto pikselialuerelaation avulla. Se voi olla suositeltava menetelmä kuvan desimointiin, koska se antaa moire-vapaat tulokset. Mutta kun kuvaa zoomataan, se on samanlainen kuin \"Lähin\"-menetelmä. Ota Tonemapping käyttöön Anna % Sivustolle ei pääse, yritä käyttää VPN:ää tai tarkista, onko URL-osoite oikein Merkintätasot Tasot-tila, jossa on mahdollisuus sijoittaa vapaasti kuvia, tekstiä ja muuta Muokkaa tasoa Kerrokset kuvassa Käytä kuvaa taustana ja lisää sen päälle erilaisia ​​kerroksia Tasot taustalla Sama kuin ensimmäinen vaihtoehto, mutta värillä kuvan sijaan Beeta Pika-asetukset-puoli Lisää kelluva nauha valitulle puolelle kuvien muokkauksen aikana, joka avaa nopeat asetukset, kun sitä napsautetaan Tyhjennä valinta Asetusryhmä \"%1$s\" tiivistetään oletuksena Asetusryhmä \"%1$s\" laajennetaan oletuksena Base64 työkalut Pura Base64-merkkijono kuvaksi tai koodaa kuva Base64-muotoon Perus64 Annettu arvo ei ole kelvollinen Base64-merkkijono Tyhjää tai virheellistä Base64-merkkijonoa ei voi kopioida Liitä pohja64 Kopioi Base64 Lataa kuva kopioidaksesi tai tallentaaksesi Base64-merkkijonon. Jos sinulla on itse merkkijono, voit liittää sen yllä saadaksesi kuvan Tallenna Base64 Share Base64 Vaihtoehdot Toiminnot Tuo Base64 Base64-toiminnot Lisää ääriviivat Lisää tekstin ympärille ääriviiva määritetyllä värillä ja leveydellä Ääriviivan väri Ääriviivan koko Kierto Tarkistussumma tiedostonimenä Tulostetuilla kuvilla on niiden tietojen tarkistussummaa vastaava nimi Vapaa ohjelmisto (kumppani) Lisää hyödyllisiä ohjelmistoja Android-sovellusten kumppanikanavassa Algoritmi Tarkistussummatyökalut Vertaile tarkistussummia, laske tiivisteitä tai luo heksadesimaattisia merkkijonoja tiedostoista käyttämällä erilaisia ​​hajautusalgoritmeja Laskea Teksti Hash Tarkistussumma Valitse tiedosto laskeaksesi sen tarkistussumma valitun algoritmin perusteella Syötä tekstiä sen tarkistussumman laskemiseksi valitun algoritmin perusteella Lähteen tarkistussumma Vertailun tarkistussumma Ottelu! Ero Tarkistussummat ovat yhtä suuret, se voi olla turvallista Tarkistussummat eivät ole samat, tiedosto voi olla vaarallinen! Mesh gradientit Katso verkkogradienttikokoelmaa Vain TTF- ja OTF-fontteja voidaan tuoda Tuo fontti (TTF/OTF) Vie fontit Tuodut fontit Virhe tallennettaessa yritystä, yritä vaihtaa tulostuskansiota Tiedostonimeä ei ole asetettu Ei mitään Mukautetut sivut Sivujen valinta Työkalun poistumisen vahvistus Jos sinulla on tallentamattomia muutoksia käyttäessäsi tiettyjä työkaluja ja yrität sulkea sen, vahvistusikkuna tulee näkyviin Muokkaa EXIF-tiedostoa Muuta yksittäisen kuvan metatietoja ilman uudelleenpakkausta Napauta muokataksesi käytettävissä olevia tunnisteita Vaihda tarra Sovita leveys Sopiva korkeus Erävertailu Valitse tiedosto/tiedostot laskeaksesi sen tarkistussumman valitun algoritmin perusteella Valitse tiedostot Valitse hakemisto Pään pituusasteikko Leima Aikaleima Muotoile kuvio Pehmuste Kuvan leikkaaminen Leikkaa kuvan osa ja yhdistä vasemmanpuoleiset (voi olla käänteinen) pysty- tai vaakaviivoilla Pysty Pivot Line Vaakasuuntainen kääntöviiva Käänteinen valinta Pystysuora leikkausosa jätetään sen sijaan, että osia yhdistettäisiin leikatun alueen ympärillä Vaakasuora leikkausosa jätetään sen sijaan, että osia yhdistettäisiin leikatun alueen ympärillä Kokoelma verkkogradientteja Luo mesh-gradientti mukautetulla määrällä solmuja ja resoluutiota Mesh-gradienttipeitto Luo mesh-gradientti annettujen kuvien yläreunasta Pisteiden räätälöinti Ruudukon koko Päätös X Päätös Y Resoluutio Pixel By Pixel Korosta Väri Pikselivertailutyyppi Skannaa viivakoodi Korkeussuhde Viivakoodin tyyppi Täytä mustavalkoinen Viivakoodikuva on täysin mustavalkoinen, eikä sitä väritä sovelluksen teema Skannaa mikä tahansa viivakoodi (QR, EAN, AZTEC,…) ja hanki sen sisältö tai liitä tekstisi luodaksesi uuden Viivakoodia ei löydy Luotu viivakoodi on täällä Äänikotelot Pura albumin kansikuvat äänitiedostoista, yleisimpiä muotoja tuetaan Aloita valitsemalla ääni Valitse Ääni Kansia ei löytynyt Lähetä lokit Napsauta jakaaksesi sovelluksen lokitiedoston. Tämä voi auttaa minua havaitsemaan ongelman ja korjaamaan ongelmat Hups… Jotain meni pieleen Voit ottaa minuun yhteyttä alla olevilla vaihtoehdoilla, niin yritän löytää ratkaisun.\n(Älä unohda liittää lokit) Kirjoita tiedostoon Pura teksti kuvaerästä ja tallenna se yhteen tekstitiedostoon Kirjoita metatietoihin Poimi teksti jokaisesta kuvasta ja sijoita se suhteellisten valokuvien EXIF-tietoihin Näkymätön tila Käytä steganografiaa luodaksesi silmille näkymättömiä vesileimoja kuviesi tavujen sisään Käytä LSB:tä LSB (Less Significant Bit) steganografiamenetelmää käytetään, muuten FD (Frequency Domain) Automaattinen punasilmäisyyden poisto Salasana Avata PDF on suojattu Toiminta melkein valmis. Peruuttaminen nyt vaatii sen uudelleenkäynnistyksen Muutospäivämäärä Muokkauspäivä (käännetty) Koko Koko (käänteinen) MIME-tyyppi MIME-tyyppi (käänteinen) Laajennus Laajennus (käänteinen) Lisäyspäivä Lisäyspäivä (käännetty) Vasemmalta oikealle Oikealta vasemmalle Ylhäältä alas Alhaalta ylös Nestemäinen lasi Kytkin perustuu äskettäin julkistettuun IOS 26:een ja sen nestemäisen lasin suunnittelujärjestelmään Valitse kuva tai liitä/tuo Base64-tiedot alta Aloita kirjoittamalla kuvalinkki Liitä linkki Kaleidoskooppi Toissijainen kulma Sivut Kanavasekoitus Sinivihreä Punainen sininen Vihreä punainen Punaiseksi Vihreään Siniseen Syaani Magenta Keltainen Väri puolisävy Contour Tasot Offset Voronoi kiteytyy Muoto Venyttää Satunnaisuus Poistaa täplät Hajanainen Koira Toinen säde Tasaa Hehku Pyöritä ja purista Pointillisoida Reunuksen väri Napakoordinaatit Suoraan napaiseen Polaarinen suoraan Käännä ympyrässä Vähennä melua Yksinkertainen solarisointi Kutoa X aukko Y väli X Leveys Y Leveys Pyöritä Kumileimasin Smear Tiheys Sekoita Pallolinssin vääristymä Taitekerroin Arc Levityskulma Kimallus Säteet ASCII Kaltevuus Mary Syksy Luu Jet Talvi Valtameri Kesä Kevät Cool Variant HSV Vaaleanpunainen Kuuma Sana Magma Inferno Plasma Viridis Kansalaiset Iltahämärä Twilight Shifted Perspective Auto Deskew Salli rajaus Raja tai perspektiivi Ehdoton Turbo Syvän vihreä Linssin korjaus Kohdeobjektiivin profiilitiedosto JSON-muodossa Lataa valmiit linssiprofiilit Osaprosentit Vie JSON-muodossa Kopioi merkkijono, jossa on palettitiedot json-muodossa Sauman veistäminen Kotinäyttö Lukitusnäyttö Sisäänrakennettu Taustakuvien vienti Päivitä Hanki nykyiset koti-, lukko- ja sisäänrakennetut taustakuvat Salli pääsy kaikkiin tiedostoihin, tätä tarvitaan taustakuvien hakemiseen Hallinnoi ulkoista tallennustilaa ei riitä, sinun on sallittava kuviesi käyttö. Muista valita \"Salli kaikki\". Lisää esiasetus tiedostonimeen Lisää valitun esiasetuksen mukaisen päätteen kuvatiedoston nimeen Lisää kuvan skaalaustila tiedostonimeen Lisää valitun kuvan mittakaavatilan päätteen kuvatiedoston nimeen Ascii Art Muunna kuva ascii-tekstiksi, joka näyttää kuvalta Parametrit Käytä negatiivista suodatinta kuvaan paremman tuloksen saavuttamiseksi joissakin tapauksissa Käsitellään kuvakaappausta Kuvakaappausta ei otettu, yritä uudelleen Tallennus ohitettu %1$s tiedostoa ohitettiin Salli Ohita, jos suurempi Jotkut työkalut voivat ohittaa kuvien tallentamisen, jos tuloksena oleva tiedostokoko on suurempi kuin alkuperäinen Kalenteri Tapahtuma Ota yhteyttä Sähköposti Sijainti Puhelin Teksti SMS URL-osoite Wi-Fi Avoin verkko Ei käytössä SSID Puhelin Viesti Osoite Aihe Runko Nimi Organisaatio Otsikko Puhelimet Sähköpostit URL-osoitteet Osoitteet Yhteenveto Kuvaus Sijainti Järjestäjä Aloituspäivämäärä Päättymispäivä Status Leveysaste Pituusaste Luo viivakoodi Muokkaa viivakoodia Wi-Fi-määritys Turvallisuus Valitse yhteystieto Myönnä kontakteille asetuksissa lupa täyttää automaattisesti valitun yhteystiedon avulla Yhteystiedot Etunimi Toinen nimi Sukunimi Ääntäminen Lisää puhelin Lisää sähköpostiosoite Lisää osoite Verkkosivusto Lisää verkkosivusto Muotoiltu nimi Tätä kuvaa käytetään viivakoodin yläpuolelle sijoittamiseen Koodin mukauttaminen Tätä kuvaa käytetään logona QR-koodin keskellä Logo Logo pehmuste Logon koko Logon kulmat Neljäs silmä Lisää silmäsymmetriaa qr-koodiin lisäämällä neljännen silmän alareunaan Pikselin muoto Kehyksen muoto Pallon muoto Virheenkorjaustaso Tumma väri Vaalea väri Hyper käyttöjärjestelmä Xiaomi HyperOS:n kaltainen tyyli Maski kuvio Tätä koodia ei ehkä voi skannata. Muuta ulkoasuparametreja, jotta se on luettavissa kaikilla laitteilla Ei skannattavissa Työkalut näyttävät aloitusnäytön sovellusten käynnistysohjelmalta, jotta ne olisivat kompaktimpia Käynnistystila Täyttää alueen valitulla siveltimellä ja tyylillä Tulva täyttö Spray Piirtää graffitityylisen polun Neliömäiset hiukkaset Suihkehiukkaset ovat neliön muotoisia ympyröiden sijaan Palettityökalut Luo perus-/materiaali paletti kuvasta tai tuo/vie eri palettimuodoissa Muokkaa palettia Vie/tuo paletti eri muodoissa Värin nimi Paletin nimi Paletin muoto Vie luotu paletti eri muotoihin Lisää uutta väriä nykyiseen palettiin %1$s-muoto ei tue paletin nimen antamista Play Kaupan käytäntöjen vuoksi tätä ominaisuutta ei voi sisällyttää nykyiseen koontiversioon. Voit käyttää tätä toimintoa lataamalla ImageToolboxin vaihtoehtoisesta lähteestä. Löydät saatavilla olevat versiot GitHubista alta. Avaa Github-sivu Alkuperäinen tiedosto korvataan uudella sen sijaan, että se tallennettaisiin valittuun kansioon Havaittiin piilotettu vesileimateksti Havaittiin piilotettu vesileimakuva Tämä kuva oli piilotettu Generatiivinen maalaus Mahdollistaa objektien poistamisen kuvasta tekoälymallin avulla turvautumatta OpenCV:hen. Tämän ominaisuuden käyttämiseksi sovellus lataa vaaditun mallin (~200 Mt) GitHubista Mahdollistaa objektien poistamisen kuvasta tekoälymallin avulla turvautumatta OpenCV:hen. Tämä voi olla pitkäkestoinen operaatio Virhetason analyysi Luminanssigradientti Keskimääräinen etäisyys Kopioi siirtotunnistus Säilyttää Kerroin Leikepöydän tiedot ovat liian suuria Data on liian suuri kopioitavaksi Yksinkertainen Weave-pikselointi Porrastettu pikselisointi Ristipikselointi Mikro-makropikselointi Orbitaalinen pikselisointi Vortex-pikselointi Pulssiruudukon pikselointi Ytimen pikselisaatio Radial Weave -pikselointi Ei voi avata uria \"%1$s\" Lumisadetila Käytössä Reunuskehys Glitch Variant Kanavan vaihto Max Offset VHS Estä Glitch Lohkon koko CRT-kaarevuus Kaarevuus Chroma Pikselin sulaminen Max Drop AI-työkalut Erilaisia ​​työkaluja kuvien käsittelyyn AI-mallien avulla, kuten artefaktien poistaminen tai kohinan poistaminen Puristus, rosoiset linjat Sarjakuvat, lähetyspakkaus Yleinen puristus, yleinen melu Väritön sarjakuva melu Nopea, yleinen pakkaus, yleinen kohina, animaatio/sarjakuva/anime Kirjan skannaus Altistuksen korjaus Paras yleisessä pakkauksessa, värikuvissa Paras yleisessä pakkauksessa, harmaasävykuvissa Yleinen pakkaus, harmaasävykuvat, vahvempi Yleinen kohina, värikuvat Yleinen kohina, värikuvat, paremmat yksityiskohdat Yleinen kohina, harmaasävykuvat Yleinen kohina, harmaasävykuvat, vahvempi Yleinen kohina, harmaasävykuvat, voimakkain Yleinen pakkaus Yleinen pakkaus Teksturointi, h264-pakkaus VHS-pakkaus Epätyypillinen pakkaus (cinepak, msvideo1, roq) Bink-pakkaus, parempi geometriassa Bink-puristus, vahvempi Bink-pakkaus, pehmeä, säilyttää yksityiskohdat Poistaa portaikkovaikutuksen, tasoittaa Skannattu taide/piirustukset, lievä pakkaus, moire Värinauha Hidasta, puolisävyjä poistavaa Yleisväriaine harmaasävy-/bw-kuville, parempien tulosten saamiseksi käytä DDColoria Reunojen poisto Poistaa yliteroittumisen Hidasta, hämmentävää Anti-aliasing, yleiset artefaktit, CGI KDM003 skannaa käsittelyä Kevyt kuvanparannusmalli Puristusartefaktien poisto Puristusartefaktien poisto Siteen poisto tasaisin tuloksin Rasterikuvioiden käsittely Dther-kuvion poisto V3 JPEG-artefaktin poisto V2 H.264 tekstuurin parannus VHS-terästys ja parannus Yhdistäminen Palan koko Päällekkäisyyden koko Kuvat, joiden koko on yli %1$s px, leikataan ja käsitellään paloina, ja ne sekoitetaan päällekkäin, jotta vältetään näkyviä saumoja. Suuret koot voivat aiheuttaa epävakautta halvempien laitteiden kanssa Aloita valitsemalla yksi Haluatko poistaa mallin %1$s? Sinun on ladattava se uudelleen Vahvistaa Mallit Ladatut mallit Saatavilla olevat mallit Valmistellaan Aktiivinen malli Istunnon avaaminen epäonnistui Vain .onnx/.ort-malleja voidaan tuoda Tuo malli Tuo mukautettu onnx-malli myöhempää käyttöä varten, vain onnx/ort-mallit hyväksytään, tukee melkein kaikkia esrgan-tyyppisiä variantteja Tuodut mallit Yleinen kohina, värilliset kuvat Yleinen kohina, värilliset kuvat, vahvempi Yleinen kohina, värilliset kuvat, voimakkain Vähentää haalistuvia artefakteja ja värijuovia parantaen tasaisia ​​liukuvärejä ja tasaisia ​​värialueita. Parantaa kuvan kirkkautta ja kontrastia tasapainoisilla kohokohdilla säilyttäen samalla luonnolliset värit. Kirkastaa tummia kuvia säilyttäen samalla yksityiskohdat ja välttäen ylivalotusta. Poistaa liiallisen värisävytyksen ja palauttaa neutraalimman ja luonnollisemman väritasapainon. Käyttää Poisson-pohjaista kohinan sävytystä painottaen hienojen yksityiskohtien ja tekstuurien säilyttämistä. Käyttää pehmeää Poisson-kohinavärjäystä tasaisempien ja vähemmän aggressiivisten visuaalisten tulosten saamiseksi. Tasainen kohinan sävytys keskittyy yksityiskohtien säilyttämiseen ja kuvan selkeyteen. Hellävarainen tasainen kohinan sävytys hienovaraisen koostumuksen ja sileän ulkonäön saavuttamiseksi. Korjaa vaurioituneet tai epätasaiset alueet maalaamalla esineitä uudelleen ja parantamalla kuvan yhtenäisyyttä. Kevyt kaistanpoistomalli, joka poistaa värijuovat minimaalisilla suorituskustannuksilla. Optimoi kuvat, joissa on erittäin korkea pakkausvirhe (0-20 % laatu) selkeyden parantamiseksi. Parantaa kuvia korkean pakkauksen artefakteilla (20-40 % laatu), palauttaa yksityiskohtia ja vähentää kohinaa. Parantaa kuvia kohtuullisella pakkauksella (laatu 40-60 %) ja tasapainottaa terävyyttä ja tasaisuutta. Tarkoittaa kuvia kevyellä pakkauksella (60-80 % laatu) parantaakseen hienovaraisia ​​yksityiskohtia ja tekstuureja. Parantaa hieman lähes häviöttömiä kuvia (80-100 % laatu) säilyttäen samalla luonnollisen ilmeen ja yksityiskohdat. Yksinkertainen ja nopea väritys, sarjakuvia, ei ihanteellinen Vähentää hieman kuvan epäterävyyttä ja parantaa terävyyttä ilman artefakteja. Pitkät toiminnot Käsitellään kuvaa Käsittely Poistaa raskaat JPEG-pakkausartefaktit erittäin huonolaatuisista kuvista (0-20 %). Vähentää vahvoja JPEG-artefakteja erittäin pakatuissa kuvissa (20-40 %). Puhdistaa kohtalaiset JPEG-artefaktit säilyttäen samalla kuvan yksityiskohdat (40-60 %). Tarkoittaa kevyitä JPEG-artefakteja melko korkealaatuisissa kuvissa (60-80 %). Vähentää hienovaraisesti pieniä JPEG-virheitä lähes häviöttömissä kuvissa (80-100 %). Korostaa hienoja yksityiskohtia ja tekstuureja parantaen havaittua terävyyttä ilman raskaita esineitä. Käsittely valmis Käsittely epäonnistui Parantaa ihon tekstuuria ja yksityiskohtia säilyttäen luonnollisen ulkonäön, optimoituna nopeuteen. Poistaa JPEG-pakkausartefaktit ja palauttaa kuvanlaadun pakatuista valokuvista. Vähentää ISO-kohinaa valokuvissa, jotka on otettu heikossa valaistuksessa ja säilyttää yksityiskohdat. Korjaa ylivalottuneet tai \"jumbo\" kohokohdat ja palauttaa paremman sävytasapainon. Kevyt ja nopea väritysmalli, joka lisää luonnollisia värejä harmaasävykuviin. DEJPEG Denoise Väritä Artefaktit Parantaa Anime Skannaukset Hyväpalkkainen X4-skaalaus yleiskuville; pieni malli, joka käyttää vähemmän GPU:ta ja aikaa, kohtuullisella epätarkkuudella ja kohinalla. X2-skaalaus yleiskuviin, pintakuvioiden ja luonnollisten yksityiskohtien säilyttämiseen. X4-skaalaus yleisiin kuviin parannetuilla tekstuureilla ja realistisilla tuloksilla. X4-skaalaus optimoitu animekuville; 6 RRDB-lohkoa terävämpiin linjoihin ja yksityiskohtiin. X4-skaalaus, jossa on MSE-häviö, tuottaa tasaisempia tuloksia ja vähentää artefakteja yleisissä kuvissa. X4 Upscaler optimoitu animekuville; 4B32F-versio, jossa on terävämmät yksityiskohdat ja sileät linjat. X4 UltraSharp V2 -malli yleiskuviin; korostaa terävyyttä ja selkeyttä. X4 UltraSharp V2 Lite; nopeampi ja pienempi, säilyttää yksityiskohdat ja käyttää vähemmän GPU-muistia. Kevyt malli nopeaan taustan poistoon. Tasapainoinen suorituskyky ja tarkkuus. Toimii muotokuvien, esineiden ja kohtausten kanssa. Suositellaan useimpiin käyttötapauksiin. Poista BG Vaakasuora reunan paksuus Pystysuuntaisen reunan paksuus %1$s väri %1$s väriä Nykyinen malli ei tue paloittelua, kuva käsitellään alkuperäisissä mitoissa, mikä voi aiheuttaa suurta muistinkulutusta ja ongelmia halvempien laitteiden kanssa Sirpalointi poistettu käytöstä, kuva käsitellään alkuperäisissä mitoissa. Tämä voi aiheuttaa suurta muistinkulutusta ja ongelmia halvempien laitteiden kanssa, mutta voi antaa parempia tuloksia pääteltäessä Murskaavaa Erittäin tarkka kuvan segmentointimalli taustan poistamiseen U2Netin kevyt versio nopeampaan taustan poistamiseen pienemmällä muistinkäytöllä. Full DDColor -malli tarjoaa korkealaatuisen värityksen yleisille kuville minimaalisilla artefakteilla. Paras valinta kaikista väritysmalleista. DDColor Koulutetut ja yksityiset taiteelliset tietojoukot; tuottaa monipuolisia ja taiteellisia väritystuloksia harvemmilla epärealistisilla väriartefakteilla. Kevyt BiRefNet-malli, joka perustuu Swin Transformeriin tarkan taustan poistamiseen. Laadukas taustan poisto terävillä reunoilla ja erinomaisella yksityiskohdilla, erityisesti monimutkaisissa kohteissa ja hankalassa taustassa. Taustanpoistomalli, joka tuottaa tarkat maskit sileillä reunoilla, sopii yleisiin esineisiin ja kohtuulliseen yksityiskohtien säilytykseen. Malli on jo ladattu Mallin tuonti onnistui Tyyppi avainsana Erittäin nopea Normaali Hidas Erittäin hidas Laske prosentit Minimiarvo on %1$s Vääristä kuvaa piirtämällä sormilla Loimi Kovuus Warp-tila Liikkua Kasvaa Kutistua Pyöritä CW Pyöritä CCW Häivytysvoima Top Drop Pohjapudotus Käynnistä pudotus Lopeta pudotus Ladataan Sileät muodot Käytä superellipsiä tavallisten pyöristetyn suorakulmion sijaan saadaksesi pehmeämpiä ja luonnollisempia muotoja Muototyyppi Leikata Pyöristetty Sileä Terävät reunat ilman pyöristystä Klassiset pyöristetyt kulmat Muodot Tyyppi Kulmien koko Squircle Tyylikkäät pyöristetyt käyttöliittymäelementit Tiedostonimen muoto Muokattu teksti sijoitetaan aivan tiedostonimen alkuun, täydellinen projektinimille, tuotemerkeille tai henkilökohtaisille tunnisteille. Käyttää alkuperäistä tiedostonimeä ilman päätettä, mikä auttaa sinua pitämään lähteen tunnisteen ennallaan. Kuvan leveys pikseleinä, hyödyllinen tarkkuuden muutosten seurannassa tai tulosten skaalaus. Kuvan korkeus pikseleinä, hyödyllinen kuvasuhteita tai vientiä käytettäessä. Luo satunnaisia ​​numeroita ainutlaatuisten tiedostonimien takaamiseksi; lisää numeroita lisäturvaaksesi kaksoiskappaleita vastaan. Automaattisesti kasvava laskuri erävientiä varten, ihanteellinen, kun tallennetaan useita kuvia yhdessä istunnossa. Lisää käytetyn esiasetuksen nimen tiedostonimeen, jotta voit helposti muistaa, kuinka kuvaa käsiteltiin. Näyttää käsittelyn aikana käytetyn kuvan skaalaustilan, mikä auttaa erottamaan muutetut, rajatut tai sovitetut kuvat. Muokattu teksti, joka on sijoitettu tiedostonimen loppuun, hyödyllinen versioinnissa, kuten _v2, _edited tai _final. Tiedostotunniste (png, jpg, webp jne.), joka vastaa automaattisesti todellista tallennettua muotoa. Muokattava aikaleima, jonka avulla voit määrittää oman muotosi java-määrityksen avulla täydellisen lajittelun saavuttamiseksi. Fling tyyppi Android Native iOS-tyyli Tasainen käyrä Pikapysäytys Pirteä Kelluva Reipas Ultra Smooth Mukautuva Esteettömyystietoinen Alennettu liike Alkuperäinen Android-vieritysfysiikka vertailua varten Tasapainoinen, tasainen vieritys yleiseen käyttöön Suurempi kitka iOS:n kaltainen vierityskäyttäytyminen Ainutlaatuinen spline-käyrä antaa selkeän vieritystuntuman Tarkka vieritys nopealla pysäytyksellä Leikkisä, reagoiva pomppiva rulla Pitkät, liukuvat rullat sisällön selaamiseen Nopea, reagoiva vieritys interaktiivisille käyttöliittymille Ensiluokkainen sujuva vieritys pidennetyllä vauhdilla Säätää fysiikkaa heittonopeuden perusteella Kunnioita järjestelmän esteettömyysasetuksia Minimaalinen liike esteettömyystarpeisiin Ensisijaiset linjat Lisää paksumman viivan joka viides rivi Täyttöväri Piilotetut työkalut Työkalut Piilotettu jakaa varten Värikirjasto Selaa laajaa värivalikoimaa Terävöittää ja poistaa kuvista epäterävyyden säilyttäen luonnolliset yksityiskohdat, mikä sopii erinomaisesti epätarkkojen kuvien korjaamiseen. Palauttaa älykkäästi kuvat, joiden kokoa on muutettu, ja palauttaa kadonneet yksityiskohdat ja tekstuurit. Optimoitu live-action-sisällölle, vähentää pakkausartefakteja ja parantaa elokuvien/TV-ohjelmien kehysten hienoja yksityiskohtia. Muuntaa VHS-laatuisen materiaalin HD:ksi, poistaa nauhakohinaa ja parantaa resoluutiota säilyttäen samalla vintage-tunnelman. Erikoistunut paljon tekstiä sisältäviin kuviin ja kuvakaappauksiin, terävöittää merkkejä ja parantaa luettavuutta. Edistynyt skaalaus, joka on koulutettu erilaisiin tietokokonaisuuksiin, sopii erinomaisesti yleiskäyttöiseen valokuvien parantamiseen. Optimoitu web-pakattuille valokuville, poistaa JPEG-artefaktit ja palauttaa luonnollisen ulkonäön. Paranneltu versio verkkovalokuville paremmalla tekstuurin säilyttämisellä ja artefaktien vähentämisellä. Kaksinkertainen skaalaus Dual Aggregation Transformer -teknologialla, säilyttää terävyyden ja luonnolliset yksityiskohdat. 3x skaalaus edistyneellä muuntajaarkkitehtuurilla, ihanteellinen kohtalaisiin laajennustarpeisiin. 4x korkealaatuinen skaalaus huippuluokan muuntajaverkolla, säilyttää hienot yksityiskohdat suuremmissa mittakaavaissa. Poistaa kuvista epäterävyyttä/kohinaa ja tärinää. Yleiskäyttöinen, mutta paras kuvissa. Palauttaa huonolaatuiset kuvat käyttämällä Swin2SR-muuntajaa, joka on optimoitu BSRGANin heikkenemistä varten. Erinomainen raskaiden puristusartefaktien korjaamiseen ja yksityiskohtien parantamiseen 4x mittakaavassa. 4-kertainen skaalaus SwinIR-muuntajalla, joka on koulutettu BSRGANin heikkenemiseen. Käyttää GAN-tekniikkaa terävämpiin tekstuuriin ja luonnollisempiin yksityiskohtiin valokuvissa ja monimutkaisissa kohtauksissa. Polku Yhdistä PDF Yhdistä useita PDF-tiedostoja yhdeksi asiakirjaksi Tiedostojen järjestys s. Jaa PDF Pura tietyt sivut PDF-dokumentista Kierrä PDF Korjaa sivun suunta pysyvästi Sivut Järjestä PDF uudelleen Järjestä sivut uudelleen vetämällä ja pudottamalla Pidä ja vedä sivuja Sivunumerot Lisää numerointi asiakirjoihin automaattisesti Label Format PDF tekstiksi (OCR) Pura pelkkää tekstiä PDF-dokumenteistasi Peittoteksti brändäystä tai turvallisuutta varten Allekirjoitus Lisää sähköinen allekirjoituksesi mihin tahansa asiakirjaan Tätä käytetään allekirjoituksena Avaa PDF Poista salasanat suojatuista tiedostoistasi Suojaa PDF Suojaa asiakirjasi vahvalla salauksella Menestys PDF avattu, voit tallentaa tai jakaa sen Korjaa PDF Yritä korjata vioittuneet tai lukukelvottomat asiakirjat Harmaasävy Muunna kaikki asiakirjan upotetut kuvat harmaasävyiksi Pakkaa PDF Optimoi asiakirjatiedoston koko helpottamaan jakamista ImageToolbox rakentaa sisäisen ristiviittaustaulukon uudelleen ja luo tiedostorakenteen uudelleen tyhjästä. Tämä voi palauttaa pääsyn moniin tiedostoihin, joita \\"ei voi avata\\" Tämä työkalu muuntaa kaikki asiakirjan kuvat harmaasävyiksi. Paras tulostukseen ja tiedostokoon pienentämiseen Metatiedot Muokkaa asiakirjan ominaisuuksia parantaaksesi yksityisyyttä Tunnisteet Tuottaja Tekijä Avainsanat Luoja Yksityisyys syväpuhdistus Tyhjennä kaikki tämän asiakirjan saatavilla olevat metatiedot Sivu Syvä OCR Pura teksti asiakirjasta ja tallenna se yhteen tekstitiedostoon Tesseract-moottorilla Kaikkia sivuja ei voi poistaa Poista PDF-sivut Poista tietyt sivut PDF-dokumentista Poista napauttamalla Käsin Rajaa PDF Rajaa asiakirjan sivut mihin tahansa reunaan Litistä PDF Tee PDF-tiedostosta muokkaamaton rasteroimalla asiakirjan sivut Kameraa ei voitu käynnistää. Tarkista käyttöoikeudet ja varmista, että toinen sovellus ei käytä sitä. Pura kuvat Pura PDF-tiedostoihin upotetut kuvat alkuperäisellä tarkkuudellaan Tämä PDF-tiedosto ei sisällä upotettuja kuvia Tämä työkalu skannaa jokaisen sivun ja palauttaa täysilaatuiset lähdekuvat – täydellinen alkuperäisten tallentamiseen asiakirjoista Piirrä allekirjoitus Kynäparametrit Käytä omaa allekirjoitusta kuvana lisättäväksi asiakirjoihin Zip PDF Jaa asiakirja tietyllä aikavälillä ja pakkaa uudet asiakirjat zip-arkistoon Intervalli Tulosta PDF Valmistele asiakirja tulostusta varten mukautetulla sivukoolla Sivuja per arkki Suuntautuminen Sivun koko Marginaali Kukinta Pehmeä polvi Optimoitu animeille ja sarjakuville. Nopea skaalaus parannetuilla luonnollisilla väreillä ja vähemmillä esineillä Samsung One UI 7:n kaltainen tyyli Syötä tähän matemaattiset perussymbolit halutun arvon laskemiseksi (esim. (5+5)*10) Matemaattinen ilmaus Poimi jopa %1$s kuvaa Pidä päivämäärä ja aika Säilytä aina päivämäärään ja kellonaikaan liittyvät exif-tunnisteet, toimii riippumatta Keep exif -vaihtoehdosta Taustaväri Alfa-formaateille Lisää mahdollisuuden asettaa taustavärin jokaiselle kuvamuodolle alfa-tuella, kun tämä ei ole käytössä, tämä on käytettävissä vain muille kuin alfa-muodoille Avoin projekti Jatka aiemmin tallennetun Image Toolbox -projektin muokkaamista Image Toolbox -projektia ei voi avata Image Toolbox -projektista puuttuu projektitiedot Image Toolbox -projekti on vioittunut Ei tuettu Image Toolbox -projektin versio: %1$d Tallenna projekti Tallenna tasot, tausta ja muokkaushistoria muokattavaan projektitiedostoon Avaaminen epäonnistui Kirjoita haettavaan PDF-tiedostoon Tunnista teksti kuvaerästä ja tallenna haettavissa oleva PDF kuvan ja valittavan tekstikerroksen kanssa Kerros alfa Vaakasuora kääntö Pystysuora kääntö Lukko Lisää varjo Varjon väri Tekstin geometria Venytä tai vino tekstiä terävämmän tyylin saamiseksi Mittakaava X Vino X Poista merkinnät Poista valitut merkintätyypit, kuten linkit, kommentit, korostukset, muodot tai lomakekentät PDF-sivuilta Hyperlinkit Tiedoston liitteet Linjat Ponnahdusikkunat Postimerkit Muodot Teksti Huomautuksia Tekstin merkintä Lomakekentät Merkintä Tuntematon Huomautukset Pura ryhmittely Lisää sumea varjo tason taakse konfiguroitavilla väreillä ja poikkeamilla ================================================ FILE: core/resources/src/main/res/values-fil/strings.xml ================================================ Laki %1$s Ikinakarga… Pumili ng larawan para magsimula Lapad %1$s Taas %1$s Kalidad Ekstensyon Uri ng pag-resize Tahasan Flexible Pumili ng Larawan Manatili Pag-alis sa app Umalis I-reset ang larawan Ibabalik sa dati ang larawan at mawawala lahat ng mga pagbabago Matagumpay na na-reset ang mga value I-reset Nagkaproblema I-restart ang app Nakopya sa clipboard Pagbubukod Baguhin ang EXIF Sige Magdagdag ng tag Nagkaproblema: %1$s Masyadong malaki ang larawan para maipasilip, pero susubukan pa rin itong i-save Sigurado ka bang gusto mong umalis sa app? Walang nakitang EXIF na datos I-save Kanselahin Burahin Burahin ang EXIF Mawawala ang lahat ng EXIF na datos sa larawan, wala nang bawian! Mga Preset Putulan Pagse-save Mawawala ang lahat ng mga pagbabagong hindi pa nase-save kung aalis ka Source code Kunin ang pinakabagong update, pag-usapan ang mga isyu, atbp. Isahang pag-resize Baguhin ang laki ng isang larawan Pumili ng kulay Pumili ng kulay mula sa larawan, kopyahin o ibahagi Larawan Kulay Kinopya ang kulay Putulan ang larawan sa anumang laki Bersyon Panatilihin ang EXIF Mga larawan: %d Baguhin ang pasilip Tanggalin Gumawa ng palette ng kulay mula sa napiling larawan Gumawa ng palette Palette Update Bagong bersyon %1$s \'Di-suportadong uri: %1$s Hindi makagawa ng palette mula sa napiling larawan Orihinal Folder para sa mga output Default Pasadya Hindi naitakda Storage ng device I-resize ayon sa bigat Pinakamabigat sa KB Mag-resize ng larawan ayon sa ibinigay na bigat sa KB Magkumpara Pumili ng mga larawan Magkumpara ng dalawang larawan Pumili ng dalawang larawan para magsimula Mga Setting Mode panggabi Madilim Maliwanag Sistema Payagan ang monet sa larawan Wika Pagpapasadya Dynamikong kulay Kung bubuksan, aangkop ang mga kulay ng app sa larawang nakabukas Amoled mode Kung bubuksan, magiging napakadilim ang kulay ng mga surface sa mode panggabi Pula Berde Asul Mag-paste ng wastong aRGB-Code. Eskema ng kulay Walang maipe-paste Hindi mababago ang eskema ng kulay ng app habang gumagamit ng dynamikong kulay Ibabatay sa kulay na pipiliin mo ang tema ng app Tungkol sa app Walang nakitang update Tagasubaybay sa mga problema Magpadala rito ng mga ulat sa problema at kahilingan para sa mga feature Tumulong sa pagsasalin Maghanap dito Ayusin ang mga pagkakamali sa pagsasalin o isalin ang proyekto sa iba pang wika Walang nakitang tugma sa query mo Kung nakabukas, aangkop ang kulay ng app sa kulay ng wallpaper Nagkaproblema sa pag-save ng %d larawan Pangunahin Tersiyaryo Sekondaryo Kapal ng gilid Ibabaw Libre ang app na ito, pero pindutin ito kung gusto mong suportahan ang development ng proyekto Kailangan ng app ng access sa storage mo para mag-save ng mga larawan, kaya mangyaring ibigay ang pahintulot sa ipapakitang diyalogo Pagpapantay ng FAB Tingnan kung may update Kung nakabukas, magpapakita ng diyalogo sa update pagkabukas ng app Mga halaga Idagdag Pahintulot Grant Pag-zoom ng larawan Unlapi Pangalan ng file Kailangan ng app ang pahintulot na ito para gumana, kaya mangyaring manu-manong ibigay ang pahintulot na ito Ibahagi Eksternal na storage Mga kulay ng monet Emoji Piliin kung aling emoji ang ipapakita sa pangunahing screen Ilagay ang laki ng file Kung nakabukas, ilalagay sa pangalan ng output file ang lapad at taas ng na-save na larawan Magtanggal ng EXIF Magtanggal ng EXIF metadata sa anumang pares ng larawan Pasilip sa larawan I-preview ang anumang uri ng mga larawana: GIF, SVG, at iba pa Pinagmulan ng larawan Tagapili ng larawan Gallery Simpleng tagapili ng larawan ng gallery, gagana lamang ito kung mayroon kang app na iyon Gamitin ang layunin ng GetContent upang pumili ng larawan, gumagana sa lahat ng dako, ngunit maaari ding magkaroon ng mga isyu sa pagtanggap ng mga pciked na larawan sa ilang device, hindi ko iyon kasalanan Pag-aayos ng mga pagpipilian I-edit Umorder Bilang ng mga emoji sequenceNum orihinal na pangalan ng file Magdagdag ng orihinal na filename Kung pinagana ay nagdaragdag ng orihinal na filename sa pangalan ng output na imahe File explorer Android modernong photo picker na lumalabas sa ibaba ng screen, ay maaaring gumana lamang sa adnroid 12+ at mayroon ding mga isyu sa pagtanggap ng EXIF metadata Tinutukoy ang pagkakasunud-sunod ng mga opsyon sa pangunahing screen Hindi gagana ang pagdaragdag ng orihinal na filename kung napili ang source ng larawan ng photopicker Binabago ang laki ng mga larawan sa mga larawang may mahabang gilid na ibinigay ng Width o Height na parameter, lahat ng laki ng pagkalkula ay gagawin pagkatapos i-save - pinapanatili ang aspect ratio Ulap Epekto Distansya Slope Mag-solarize Vibrance Malabo Halftone Iskala Radius Umikot Palitan ang sequence number Liwanag Hue Magsimula Mga limitasyon sa pagbabago ng laki Baguhin ang laki ng mga ibinigay na larawan upang sundin ang ibinigay na mga limitasyon sa lapad at taas na may pag-save ng aspect ratio Sketch Threshold Hindi maximum na pagsugpo Stack blur Convolution 3x3 RGB filter Maling kulay Unang kulay Luminance threshold Kung pinagana, papalitan ang strandard timestamp sa numero ng pagkakasunud-sunod ng imahe kung gumagamit ka ng batch processing Mag-load ng larawan mula sa net Link ng larawan Punan Pinipilit ang bawat larawan sa isang imahe na ibinigay ng parameter na Lapad at Taas - maaaring magbago ng aspect ratio Contrast Saturation Magdagdag ng filter Salain Ilapat ang anumang chain ng filter sa mga ibinigay na larawan Mga filter Maliwanag Filter ng kulay Alpha Pagkalantad puting balanse Temperatura Tint Monochrome Gamma Mga highlight at anino Mga highlight Mga anino Patalasin Sepia Negatibo Itim at puti Crosshatch Spacing Lapad ng linya Sobel gilid colorspace ng GCA Gaussian blur Malabo ang kahon Bilateral blur Emboss Laplacian Vignette Tapusin Kuwahara smoothing pagbaluktot anggulo Umbok Pagluwang Sphere repraksyon Repraktibo index Glass sphere repraksyon Kulay matrix Opacity Mga antas ng quantization Smooth toon Toon Posterize Mahinang pagsasama ng pixel Paghahanap Pangalawang kulay Muling ayusin Mabilis na lumabo Laki ng blur Palabuin ang gitna x Palabuin ang gitna y Mag-zoom blur Balanse ng kulay Mag-load ng anumang larawan sa internet, i-preview ito, i-zoom, at i-save o i-edit din ito kung gusto mo Walang larawan Angkop Sukat ng nilalaman Laktawan Cache Laki ng cache Hindi maaaring baguhin ang kaayusan habang naka-enable ang pagpapangkat ng opsyon Lumikha I-edit ang screenshot Fallback na opsyon Hindi mo pinagana ang Files app, i-activate ito para magamit ang feature na ito Gumuhit sa larawan tulad ng sa isang sketchbook, o gumuhit sa background mismo Kulay ng pintura Gumuhit sa larawan Pumili ng isang imahe at gumuhit ng isang bagay dito Kulay ng background I-store ang file na ito sa iyong device o gumamit ng share action para ilagay ito kahit saan mo gusto Mga tampok Pakitandaan na ang pagiging tugma sa ibang file encryption software o mga serbisyo ay hindi ginagarantiyahan. Ang bahagyang naiibang key treatment o cipher configuration ay maaaring mga dahilan ng hindi pagkakatugma. Ang pag-save sa %1$s mode ay maaaring hindi matatag, dahil ito ay lossless na format Kulayan ang alpha Pangalawang pagpapasadya Pagkakatugma Screenshot Kopya Mga gamit Pagpapatupad Gumuhit Natagpuan %1$s Auto cache clearing Kung pinagana ang cache ng app ay iki-clear sa pagsisimula ng app Mga opsyon sa pangkat ayon sa uri Mga opsyon sa pangkat sa pangunahing screen ng kanilang uri sa halip na pasadyang pag-aayos ng listahan Gumuhit sa background Pumili ng kulay ng background at gumuhit sa ibabaw nito Pumili ng file I-encrypt I-decrypt Susi Ang maximum na laki ng file ay pinaghihigpitan ng Android OS at ang memorya na magagamit, na halatang nakadepende sa iyong device. \nMangyaring tandaan: ang memorya ay hindi imbakan. Cipher I-encrypt at I-decrypt ang anumang file (hindi lamang ang imahe) batay sa AES crypto algorithm Pumili ng file upang magsimula Pag-decryption Pag-encrypt AES-256, GCM mode, walang padding, 12 bytes na random na IVs. Ang mga susi ay ginagamit bilang SHA-3 hash (256 bits). Laki ng file Nakabatay sa password ang pag-encrypt ng mga file. Ang mga naituloy na file ay maaaring iimbak sa napiling direktoryo o ibinahagi. Ang mga decrypted na file ay maaari ding direktang mabuksan. Ang pagsisikap na i-save ang imahe na may ibinigay na lapad at taas ay maaaring magdulot ng isang error sa OOM, gawin ito sa iyong sariling peligro, at huwag sabihing hindi kita binalaan! Naproseso ang file Ang di-wastong password o napiling file ay hindi naka-encrypt Circle Pixelation Pinahusay na Circle Pixelation Palitan ang Kulay Pagpaparaya Email Telegram chat Pinahusay na Glitch Channel Shift X Nangunguna Ibaba Lakas Ibalik Talakayin ang app at makakuha ng feedback mula sa ibang mga user. Maaari ka ring makakuha ng mga beta update at insight dito. Burahin ang background Nai-save sa %1$s folder I-backup at i-restore Awtomatikong burahin ang background Mga emosyon Nai-save sa %1$s folder na may pangalang %2$s Default Pananatilihin ang orihinal na metadata ng larawan Donasyon Ibalik ang mga setting ng app mula sa dati nang nabuong file Oryentasyon & Script Detection lamang Isang linya Hilaw na linya Glitch Halaga Binhi Anaglyph ingay Pag-uuri ng Pixel Ide-delete mo na ang napiling filter mask. Hindi maa-undo ang operasyong ito Tanggalin ang Mask Drago Aldridge Putulin Uchimura Mobius Transisyon Tuktok Anomalya ng Kulay Walang nakitang direktoryo ng \"%1$s\", inilipat namin ito sa default, paki-save muli ang file Clipboard Auto pin Awtomatikong nagdaragdag ng naka-save na larawan sa clipboard kung pinagana Panginginig ng boses Lakas ng Vibration I-overwrite ang mga File Walang laman Suffix Libre Mga larawang na-overwrite sa orihinal na destinasyon Gumagamit ng pangunahing kulay ng emoji bilang scheme ng kulay ng app sa halip na manu-manong tinukoy Kung pinili mo ang preset na 125, ang imahe ay ise-save bilang 125% na laki ng orihinal na imahe na may 100% na kalidad. Kung pipiliin mo ang preset na 50, mase-save ang larawan na may 50% na laki at 50% na kalidad. Tinutukoy ng preset dito ang % ng output file, ibig sabihin, kung pipiliin mo ang preset na 50 sa 5mb na imahe pagkatapos ay makakakuha ka ng 2.5mb na imahe pagkatapos i-save Gamitin ang uri ng mask na ito upang lumikha ng mask mula sa ibinigay na larawan, pansinin na DAPAT itong magkaroon ng alpha channel Backup I-backup ang iyong mga setting ng app sa isang file Matagumpay na naibalik ang mga setting Tawagan mo ako Tanggalin ang Scheme Font Text Sukat ng font Ang paggamit ng malalaking font scale ay maaaring magdulot ng mga aberya at problema sa UI, na hindi maaayos. Gamitin nang maingat. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Ññ Ngng Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz 0123456789 !? Pagkain at Inumin Kalikasan at Hayop Mga bagay Mga simbolo Paganahin ang emoji Mga Paglalakbay at Lugar Mga aktibidad Pantanggal ng background Alisin ang background mula sa larawan sa pamamagitan ng pagguhit o paggamit ng Auto na opsyon I-trim ang larawan Ang mga trasparent na puwang sa paligid ng larawan ay pupugutan Ibalik ang imahe Erase mode Ibalik ang background Blur radius Draw mode Gumawa ng Isyu Ooops… Nagkaproblema. Maaari kang sumulat sa akin gamit ang mga opsyon sa ibaba at susubukan kong maghanap ng solusyon Baguhin ang laki at I-convert Baguhin ang laki ng mga ibinigay na larawan o i-convert ang mga ito sa iba pang mga format. Ang EXIF metadata ay maaari ding i-edit dito kung pumipili ng isang larawan. Payagan ang pagkolekta ng hindi kilalang istatistika ng paggamit ng app Sa kasalukuyan, pinapayagan lamang ng format na %1$s ang pagbabasa ng EXIF metadata sa android. Ang output na larawan ay hindi magkakaroon ng metadata, kapag na-save. Payagan ang mga beta Kasama sa pagsusuri sa update ang mga bersyon ng beta app kung naka-enable Gumuhit ng mga Arrow Ang mga larawan ay i-center crop sa inilagay na laki. Papalawakin ang canvas gamit ang ibinigay na kulay ng background kung mas maliit ang larawan kaysa sa mga inilagay na dimensyon. Pagsamahin ang mga ibinigay na larawan upang makakuha ng isang malaki Iskala ng imahe ng output Oryentasyon ng Larawan Patayo I-scale ang maliliit na larawan sa malaki Ang maliliit na larawan ay i-scale sa pinakamalaki sa pagkakasunud-sunod kung pinagana Pagkakasunod-sunod ng mga larawan Regular Pixelation Pinahusay na Pixelation Stroke Pixelation Kulay na Papalitan Kulay ng Target Kulay na Aalisin Alisin ang Kulay Kung naka-enable sa drawing mode, hindi iikot ang screen Tingnan ang mga update Isang istilong medyo mas chromatic kaysa sa monochrome Isang mapaglarong tema - hindi lumalabas sa tema ang kulay ng pinagmulang kulay Isang monochrome na tema, ang mga kulay ay puro itim / puti / kulay abo Isang scheme na naglalagay ng kulay ng pinagmulan sa Scheme.primaryContainer Isang scheme na halos kapareho sa scheme ng nilalaman Maghanap Pinapagana ang kakayahang maghanap sa lahat ng magagamit na opsyon sa pangunahing screen I-pack ang ibinigay na Mga Larawan sa output na PDF file Ire-render ang iginuhit na filter mask upang ipakita sa iyo ang tinatayang resulta Buong Filter Ilapat ang anumang mga chain ng filter sa mga ibinigay na larawan o isang larawan Magsimula Gitna Tapusin Mga Simpleng Variant Highlighter Neon Privacy Blur Gumuhit ng mga semi-transparent na sharpened highlighter path Magdagdag ng ilang kumikinang na epekto sa iyong mga guhit Default na isa, pinakasimple - ang kulay lang Katulad ng privacy blur, ngunit pixelates sa halip na malabo Pinapagana ang shadow drawing sa likod ng mga floating action button Pinapagana ang shadow drawing sa likod ng mga default na button Mga App Bar Pinapagana ang shadow drawing sa likod ng mga app bar Kusang pag-ikot Gumuhit ng double pointing arrow mula sa simula hanggang sa dulo bilang isang linya Nagbibigay-daan sa limit box na gamitin para sa oryentasyon ng imahe Gumuhit ng double pointing arrow mula sa isang ibinigay na landas Nakabalangkas na Oval Oval Rect Gumuhit nang patuwid mula sa simula hanggang sa wakas Gumuhit ng hugis-itlog mula sa simula hanggang sa wakas Gumuguhit ng nakabalangkas na hugis-itlog mula sa simula hanggang sa wakas Gumuhit ng nakabalangkas nang patuwid mula sa simula hanggang sa wakas Upang ma-overwrite ang mga file kailangan mong gumamit ng \"Explorer\" na pinagmumulan ng imahe, subukang muling pumili ng mga larawan, binago namin ang pinagmulan ng larawan sa kinakailangan. Ang orihinal na file ay papalitan ng bago sa halip na i-save sa napiling folder, ang pagpipiliang ito ay kailangang source ng larawan ay \"Explorer\" o GetContent, kapag ito ay i-toggling, ito ay awtomatikong itatakda Ang function ng windowing ay madalas na ginagamit sa pagpoproseso ng signal upang mabawasan ang spectral leakage at pagbutihin ang katumpakan ng frequency analysis sa pamamagitan ng pag-taping sa mga gilid ng isang signal Mathematical interpolation technique na gumagamit ng mga value at derivatives sa mga endpoint ng isang curve segment upang makabuo ng maayos at tuluy-tuloy na curve Paraan ng resampling na nagpapanatili ng mataas na kalidad na interpolation sa pamamagitan ng paglalapat ng weighted sinc function sa mga pixel value Paraan ng resampling na gumagamit ng convolution filter na may mga adjustable na parameter para magkaroon ng balanse sa pagitan ng sharpness at anti-aliasing sa naka-scale na imahe Gumagamit ng piecewise-defined polynomial functions upang maayos na i-interpolate at tantiyahin ang isang curve o surface, flexible at tuluy-tuloy na representasyon ng hugis Pinipilit ang exif widget na masuri sa simula Payagan ang Maramihang Wika Auto Orientation & Script Detection Auto lang Auto Isang Hanay Single block vertical text Isang bloke Isang salita Bilugan ang salita Single char Kalat-kalat na text Kalat-kalat na Oryentasyon ng teksto & Script Detection Salamin Clamp Gumagamit ng camera para kumuha ng larawan, tandaan na posibleng makakuha lamang ng isang larawan mula sa pinagmulan ng larawang ito Ulitin ang watermark Inuulit ang watermark sa larawan sa halip na solong sa ibinigay na posisyon Offset Y Uri ng Watermark Gagamitin ang larawang ito bilang pattern para sa watermarking Kulay ng teksto Palitan ang tinukoy na laki ng mga unang sukat ng frame Ulitin ang Bilang Pagkaantala ng Frame millis FPS Bayer Three By Three Dithering Bayer Four By Four Dithering Bayer Eight By Eight Dithering Floyd Steinberg Dithering Sierra Dithering Two Row Sierra Dithering Sierra Lite Dithering Stucki Dithering Burkes Dithering Maling Floyd Steinberg Dithering Native Stack Blur Ikiling Shift Balasahin Paglipat ng Channel Y Sukat ng Korapsyon Paglipat ng Korapsyon X Paglipat ng Korapsyon Y Tent Blur Side Fade Gilid Malawak Antigo Marmol Sukat ACES Hill Tone Mapping Hable Filmic Tone Mapping Polaroid Tritonomaly Deutaromaly Protonomaly Browni Coda Chrome Night Vision Mainit Malamig Tritanopia Deutaronotopia Pastel Orange Haze Rosas na Panaginip Gintong Oras Mainit na Tag-init Purple Mist pagsikat ng araw Makukulay na Swirl Malambot Spring Light Mga Tono ng Taglagas Pangarap ng Lavender Cyberpunk Liwanag ng limonada Spectral Fire Night Magic Hindi maaaring baguhin ang format ng imahe habang pinagana ang opsyon sa pag-overwrite ng mga file Emoji bilang Color Scheme Aspect ratio Sirang file o hindi isang backup Ibabalik nito ang iyong mga setting sa mga default na halaga. Pansinin na hindi ito maa-undo nang walang backup na file na binanggit sa itaas. Nagbibigay-daan ito sa app na manu-manong mangolekta ng mga ulat ng pag-crash Analytics Mga update Halaga sa hanay na %1$s - %2$s Palabuin ang mga gilid Gumuhit ng mga blur na gilid sa ilalim ng orihinal na larawan upang punan ang mga puwang sa paligid nito sa halip na isang kulay kung pinagana Pinahusay na Diamond Pixelation Diamond Pixelation I-recode Erode Anisotropic Diffusion Pagsasabog pagpapadaloy Pahalang na Wind Stagger Mabilis na Bilateral Blur Poisson Blur Logarithmic Tone Mapping Mag-kristal Kulay ng Stroke Fractal Glass Kaguluhan Langis Epekto ng Tubig Amplitude X Dalas X Dalas Y Amplitude Y Perlin Distortion Hejl Burgess Tone Mapping ACES Filmic Tone Mapping Gusto mo bang tanggalin ang data ng pagsasanay ng wikang \"%1$s\" OCR para sa lahat ng uri ng pagkilala, o para lamang sa napiling isa (%2$s)? Kasalukuyan Lahat Gumana gamit ang mga PDF file: I-preview, I-convert sa batch ng mga larawan o lumikha ng isa mula sa mga ibinigay na larawan I-preview ang PDF PDF sa Mga Larawan Mga larawan sa PDF Simpleng PDF preview I-convert ang PDF sa Mga Larawan sa ibinigay na format ng output Gradient Maker Lumikha ng gradient ng ibinigay na laki ng output na may naka-customize na mga kulay at uri ng hitsura Bilis Dehaze Omega Mga Tool sa PDF I-rate ang App Rate Ang app na ito ay libre, kung gusto mo itong lumaki, pakilagyan ng star ang proyekto sa Github 😄 Color Matrix 4x4 Color Matrix 3x3 Mga Simple Effect Protanopia Achromatomaly Achromatopsia Linear Radial Magwalis Uri ng Gradient Gitna X Sentro Y Tile Mode Paulit-ulit Decal Color Stops Magdagdag ng Kulay Ari-arian Gumuhit ng Path Mode Dobleng Linya na Arrow Libreng Pagguhit Dobleng Palaso Line Arrow Palaso Linya Gumuhit ng landas bilang halaga ng input Gumuhit ng landas mula sa simula hanggang sa dulo bilang isang linya Gumuguhit ng nakaturo na arrow mula sa simula hanggang sa dulo bilang isang linya Gumuguhit ng nakaturo na arrow mula sa isang ibinigay na landas Binalangkas Rect Dithering quantizier Gray na Scale Bayer Two By Two Dithering Jarvis Judice Ninke Dithering Atkinson Dithering Kaliwa Pakanan Dithering Random Dithering Simple Threshold Dithering Pipette Pagsisikap Teka Ang halaga ng %1$s ay nangangahulugang isang mabilis na pag-compress, na nagreresulta sa medyo malaking laki ng file. Ang %2$s ay nangangahulugang isang mas mabagal na compression, na nagreresulta sa isang mas maliit na file. Halos kumpleto ang pag-save. Ang pagkansela ngayon ay mangangailangan ng pag-save muli. Kulay ng Maskara Preview ng Mask Tanggalin Ide-delete mo na ang napiling color scheme. Hindi maa-undo ang operasyong ito Scale mode Bilinear Hann Hermite Lanczos Mitchell Pinakamalapit Spline Basic Default na Halaga Sigma Spatial Sigma Median Blur Catmull Bicubic Ang linear (o bilinear, sa dalawang dimensyon) na interpolation ay karaniwang mabuti para sa pagbabago ng laki ng isang imahe, ngunit nagiging sanhi ng ilang hindi kanais-nais na paglambot ng mga detalye at maaari pa ring maging medyo tulis-tulis. Kasama sa mas mahuhusay na paraan ng pag-scale ang Lanczos resampling at mga filter ng Mitchell-Netravali Isa sa mga mas simpleng paraan ng pagpapalaki ng laki, pinapalitan ang bawat pixel ng bilang ng mga pixel ng parehong kulay Pinakasimpleng android scaling mode na ginagamit sa halos lahat ng app Paraan para sa maayos na interpolating at resampling ng isang set ng mga control point, na karaniwang ginagamit sa computer graphics upang lumikha ng mga makinis na curve Clip lang Ang pag-save sa storage ay hindi isasagawa, at ang larawan ay susubukan na ilagay sa clipboard lamang Nagdaragdag ng lalagyan na may napiling hugis sa ilalim ng mga nangungunang icon ng mga card Hugis ng Icon Kung pinagana ang output filename ay magiging ganap na random I-randomize ang filename Pagpapatupad ng Liwanag Screen Gradient Overlay Bumuo ng anumang gradient ng tuktok ng ibinigay na larawan Mga pagbabago Camera Pumili ng hindi bababa sa 2 larawan Pahalang butil Unsharp Fantasy Landscape Pagsabog ng Kulay Electric Gradient Karamelo Kadiliman Futuristic Gradient Berdeng Araw Rainbow World Malalim na lila Space Portal Red Swirl Digital Code Watermarking Takpan ang mga larawan gamit ang nako-customize na text/image na mga watermark Offset X Overlay Mode Laki ng Pixel Lock draw orientation Max na bilang ng kulay Bokeh GIF Tools I-convert ang mga larawan sa GIF na larawan o i-extract ang mga frame mula sa ibinigay na GIF na larawan GIF sa mga larawan I-convert ang GIF file sa batch ng mga larawan I-convert ang batch ng mga larawan sa GIF file Mga larawan sa GIF Pumili ng GIF na imahe upang magsimula Gamitin ang laki ng Unang frame Gamitin ang Lasso Gumagamit ng Lasso tulad ng sa drawing mode para magsagawa ng pagbubura Orihinal na Preview ng Larawan Alpha Mask Filter Ilapat ang mga chain ng filter sa mga partikular na lugar na may maskara, maaaring matukoy ng bawat lugar ng maskara ang sarili nitong hanay ng mga filter Mga maskara Magdagdag ng Mask Mask %d Ang emoji ng app bar ay patuloy na babaguhin nang random sa halip na gumamit ng napili Mga Random na Emoji Hindi maaaring gumamit ng random na pagpili ng emoji habang naka-disable ang mga emoji Hindi makapili ng emoji habang naka-enable ang pagpili ng random crop mask Ang isang malakas na tema, ang pagiging makulay ay pinakamataas para sa Pangunahing palette, na nadagdagan para sa iba Lumang Tv Balasahin ang Blur OCR (Kilalanin ang Teksto) Kilalanin ang teksto mula sa ibinigay na larawan, suportado ng 120+ wika Walang text ang larawan, o hindi ito nakita ng app Accuracy: %1$s Uri ng Pagkilala Mabilis Pamantayan Pinakamahusay Walang Data Para sa wastong paggana ng Tesseract OCR, kailangang ma-download ang karagdagang data ng pagsasanay (%1$s) sa iyong device. \nGusto mo bang mag-download ng %2$s data? I-download Walang koneksyon, suriin ito at subukang muli upang mag-download ng mga modelo ng tren Mga Na-download na Wika Mga Magagamit na Wika Mode ng Segmentation Ibabalik ng brush ang background sa halip na burahin Pahalang na Grid Vertical Grid Stitch Mode Bilang ng mga hilera Bilang ng Mga Hanay Gamitin ang Pixel Switch Pixel-like switch ang gagamitin sa halip na ang materyal ng google na pinagbatayan mo Slide Magkatabi I-toggle ang Tapikin Aninaw Na-overwrite na file na may pangalang %1$s sa orihinal na destinasyon Magnifier Pinapagana ang magnifier sa tuktok ng daliri sa mga mode ng pagguhit para sa mas mahusay na accessibility Pilitin ang paunang halaga Paborito Wala pang naidagdag na mga paboritong filter B Spline Gumagamit ng piecewise-defined bicubic polynomial function upang maayos na i-interpolate at tantiyahin ang isang curve o surface, flexible at tuluy-tuloy na representasyon ng hugis Baliktad na Uri ng Punan Kung pinagana ang lahat ng hindi naka-mask na lugar ay sasalain sa halip na default na gawi Confetti Ipapakita ang confetti sa pag-save, pagbabahagi at iba pang pangunahing aksyon Secure Mode Itinatago ang nilalaman sa paglabas, pati na rin ang screen ay hindi maaaring makuha o i-record Kung pinagana ang landas sa pagguhit ay kakatawanin bilang nakaturo na arrow Ang lambot ng brush Pagtahi ng Larawan Panulat Estilo ng palette Tonal Spot Neutral Masigla Nagpapahayag bahaghari Fruit salad Katapatan Nilalaman Default na istilo ng palette, pinapayagan nitong i-customize ang lahat ng apat na kulay, pinapayagan ka ng iba na itakda lamang ang pangunahing kulay I-blurs ang larawan sa ilalim ng iginuhit na landas upang ma-secure ang anumang nais mong itago Mga lalagyan Pinapagana ang pagguhit ng anino sa likod ng mga lalagyan Mga slider Mga switch Mga FAB Mga Pindutan Pinapagana ang shadow drawing sa likod ng mga slider Pinapagana ang pagguhit ng anino sa likod ng mga switch Pansin Ang update checker na ito ay kumonekta sa GitHub sa dahilan ng pag-check kung may bagong update na available Pagkupas Mga Gilid Hindi pinagana pareho Baliktarin ang mga Kulay Pinapalitan ang mga kulay ng tema sa mga negatibo kung pinagana Lumabas Kung aalis ka sa preview ngayon, kakailanganin mong idagdag muli ang mga larawan Lasso Gumuguhit ng closed filled path ayon sa ibinigay na path Format ng Larawan Lumilikha ng \"Material You\" palette mula sa larawan Madidilim na kulay Gumagamit ng night mode color scheme sa halip na light variant Kopyahin bilang Jetpack Compose code Ring Blur Cross Blur Circle Blur Star Blur Linear Tilt Shift Mga Tag Upang Alisin Mga Tool ng APNG I-convert ang mga larawan sa APNG na larawan o kunin ang mga frame mula sa ibinigay na APNG na larawan APNG sa mga larawan I-convert ang APNG file sa batch ng mga larawan I-convert ang batch ng mga larawan sa APNG file Mga larawan sa APNG Pumili ng APNG na larawan upang magsimula Galaw Malabo Zip Lumikha ng Zip file mula sa mga ibinigay na file o larawan I-drag ang Lapad ng Handle Uri ng Confetti Masigasig Pasabog Ulan Kanto Mga Kagamitan para sa JXL JXL papuntang JPEG Mag-transcode sa pagitan ng JXL at JPEG nang walang pagkabawas sa kalidad, o i-convert ang GIF/APNG papunta sa animasyong JXL Magsagawa ng lossless transcoding mula JXL hanggang JPEG Magsagawa ng lossless transcoding mula JPEG hanggang JXL JPEG hanggang JXL Pumili ng JXL image para magsimula Mabilis na Gaussian Blur 2D Mabilis na Gaussian Blur 3D Mabilis na Gaussian Blur 4D Pasko ng Pagkabuhay ng Kotse Binibigyang-daan ang app na awtomatikong i-paste ang data ng clipboard, kaya lilitaw ito sa pangunahing screen at maproseso mo ito Kulay ng Harmonization Antas ng Harmonisasyon Lanczos Bessel Paraan ng resampling na nagpapanatili ng mataas na kalidad na interpolation sa pamamagitan ng paglalapat ng Bessel (jinc) function sa mga pixel value GIF sa JXL I-convert ang mga GIF na imahe sa JXL animated na mga larawan APNG hanggang JXL I-convert ang mga larawan ng APNG sa JXL animated na mga larawan JXL sa Mga Larawan I-convert ang JXL animation sa batch ng mga larawan Mga larawan sa JXL I-convert ang batch ng mga larawan sa JXL animation Pag-uugali Laktawan ang Pagpili ng File Ang tagapili ng file ay ipapakita kaagad kung posible ito sa napiling screen Bumuo ng mga Preview Pinapagana ang pagbuo ng preview, maaaring makatulong ito upang maiwasan ang mga pag-crash sa ilang device, hindi rin nito pinapagana ang ilang functionality sa pag-edit sa loob ng iisang opsyon sa pag-edit Lossy Compression Gumagamit ng lossy compression upang bawasan ang laki ng file sa halip na lossless Uri ng Compression Kinokontrol ang nagreresultang bilis ng pag-decode ng imahe, makakatulong ito sa pagbukas ng nagreresultang larawan nang mas mabilis, ang halaga ng %1$s ay nangangahulugang pinakamabagal na pag-decode, samantalang %2$s - pinakamabilis, maaaring pataasin ng setting na ito ang laki ng output ng imahe. Pag-uuri Petsa Petsa (Baliktad) Pangalan Pangalan (Baliktad) Configuration ng Mga Channel Ngayong araw Kahapon Naka-embed na Picker Tagapili ng imahe ng Image Toolbox Walang pahintulot Kahilingan Pumili ng Maramihang Media Pumili ng Single Media Pumili Subukan muli Ipakita ang Mga Setting Sa Landscape Kung ito ay hindi pinagana, sa mga setting ng landscape mode ay bubuksan sa button sa itaas na app bar gaya ng dati, sa halip na permanenteng nakikitang opsyon Mga Setting ng Fullscreen I-enable ito at palaging bubuksan ang page ng mga setting bilang fullscreen sa halip na slideable drawer sheet Uri ng Switch Mag-compose Isang Jetpack Compose Material Lilipat ka Isang Materyal na Papalitan mo Max Baguhin ang laki ng Anchor Pixel Matatas Isang switch batay sa \"Fluent\" na sistema ng disenyo Cupertino Isang switch batay sa sistema ng disenyo ng \"Cupertino\". Mga larawan sa SVG I-trace ang mga ibinigay na larawan sa mga SVG na larawan Gumamit ng Sampled Palette Ang quantization palette ay isa-sample kung ang opsyong ito ay pinagana Path Omit Ang paggamit ng tool na ito para sa pagsubaybay sa malalaking larawan nang walang downscaling ay hindi inirerekomenda, maaari itong magdulot ng pag-crash at dagdagan ang oras ng pagproseso Downscale na larawan Ang imahe ay ibababa sa mas mababang mga dimensyon bago iproseso, nakakatulong ito sa tool na gumana nang mas mabilis at mas ligtas Minimum na Ratio ng Kulay Threshold ng mga Linya Quadratic Threshold Coordinates Rounding Tolerance Scale ng Path I-reset ang mga katangian Itatakda ang lahat ng property sa mga default na value, pansinin na hindi na mababawi ang pagkilos na ito Detalyadong Default na Lapad ng Linya Mode ng Engine Legacy LSTM network Legacy at LSTM Magbalik-loob I-convert ang mga batch ng larawan sa ibinigay na format Magdagdag ng Bagong Folder Bits Bawat Sample Compression Photometric Interpretasyon Mga Sample Bawat Pixel Planar Configuration Y Cb Cr Sub Sampling Y Cb Cr Positioning X Resolution Y Resolusyon Yunit ng Resolusyon Mga Strip Offset Mga Hanay sa Bawat Strip Mga Bilang ng Strip Byte JPEG Interchange Format Haba ng JPEG Interchange Format Paglipat ng Function Puting Punto Pangunahing Chromaticities Y Cb Cr Coefficients Sanggunian Black White Petsa Oras Paglalarawan ng Larawan Gawin Modelo Software Artista Copyright Bersyon ng Exif Bersyon ng Flashpix Color Space Gamma Dimensyon ng Pixel X Dimensyon ng Pixel Y Mga Compressed Bits Bawat Pixel Tala ng Gumawa Komento ng User Kaugnay na Sound File Orihinal na Petsa ng Oras Petsa Oras na Digitize Oras ng Offset Orihinal na Oras ng Offset Offset Time Digitized Oras ng Sub Sec Orihinal na Oras ng Sub Sec Na-digitize ang Oras ng Sub Sec Tagal ng pagkalantad F Numero Exposure Program Spectral Sensitivity Photographic Sensitivity Oecf Uri ng Sensitivity Standard Output Sensitivity Inirerekomendang Exposure Index Bilis ng ISO ISO Speed ​​Latitude yyy Bilis ng ISO Latitude zzz Halaga ng Bilis ng Shutter Halaga ng Aperture Halaga ng Liwanag Halaga ng Exposure Bias Halaga ng Max Aperture Distansya ng Paksa Mode ng Pagsukat Flash Lugar ng Paksa Haba ng Focal Flash Energy Spatial Frequency Response Focal Plane X Resolution Focal Plane Y Resolution Focal Plane Resolution Unit Lokasyon ng Paksa Index ng Exposure Paraan ng Sensing Pinagmulan ng File Pattern ng CFA Custom na Na-render Exposure Mode White Balance Digital Zoom Ratio Focal Length Sa35mm Film Uri ng Pagkuha ng Eksena Makakuha ng Kontrol Contrast Saturation Ang talas Paglalarawan ng Setting ng Device Saklaw ng Distansya ng Paksa Natatanging ID ng Larawan Pangalan ng May-ari ng Camera Serial Number ng Katawan Pagtutukoy ng Lens Gumawa ng Lens Modelo ng Lens Serial Number ng Lens ID ng Bersyon ng GPS GPS Latitude Ref GPS Latitude GPS Longitude Ref GPS Longitude GPS Altitude Ref Altitude ng GPS GPS Time Stamp Mga GPS Satellite Katayuan ng GPS Mode ng Pagsukat ng GPS GPS DOP Bilis ng GPS Ref Bilis ng GPS GPS Track Ref GPS Track Direksyon ng GPS Img Ref Direksyon ng GPS Img Datum ng Mapa ng GPS GPS Dest Latitude Ref GPS Dest Latitude GPS Dest Longitude Ref GPS Dest Longitude GPS Dest Bearing Ref GPS Dest Bearing GPS Dest Distance Ref GPS Dest Distansya Paraan ng Pagproseso ng GPS Impormasyon sa Lugar ng GPS Stamp ng Petsa ng GPS GPS Differential GPS H Positioning Error Interoperability Index Bersyon ng DNG Default na Laki ng Pag-crop I-preview ang Simula ng Larawan I-preview ang Haba ng Imahe Aspect Frame Border sa Ibaba ng Sensor Kaliwang Border ng Sensor Kanang Border ng Sensor Nangungunang Border ng Sensor ISO Gumuhit ng Teksto sa landas na may ibinigay na font at kulay Laki ng Font Laki ng Watermark Ulitin ang Teksto Ang kasalukuyang text ay uulitin hanggang sa matapos ang path sa halip na isang beses na pagguhit Laki ng Dash Gamitin ang napiling larawan upang iguhit ito sa ibinigay na landas Gagamitin ang larawang ito bilang paulit-ulit na pagpasok ng iginuhit na landas Gumuguhit ng nakabalangkas na tatsulok mula sa simula hanggang sa wakas Gumuguhit ng nakabalangkas na tatsulok mula sa simula hanggang sa wakas Nakabalangkas na Triangle Tatsulok Gumuhit ng polygon mula sa simula hanggang sa wakas Polygon Nakabalangkas na Polygon Gumuguhit ng nakabalangkas na polygon mula sa simula hanggang sa wakas Vertices Gumuhit ng Regular na Polygon Gumuhit ng polygon na magiging regular sa halip na libreng anyo Gumuhit ng bituin mula sa simula hanggang sa wakas Bituin Nakabalangkas na Bituin Gumuhit ng nakabalangkas na bituin mula sa simula hanggang sa dulong punto Inner Radius Ratio Gumuhit ng Regular na Bituin Gumuhit ng bituin na magiging regular sa halip na libreng form Antialias Pinapagana ang antialiasing upang maiwasan ang matutulis na mga gilid Buksan ang Edit sa halip na Preview Kapag pinili mo ang larawan na bubuksan (preview) sa ImageToolbox, ang pag-edit ng selection sheet ay bubuksan sa halip na mag-preview Scanner ng Dokumento I-scan ang mga dokumento at gumawa ng PDF o hiwalay na mga larawan mula sa kanila I-click upang simulan ang pag-scan Simulan ang Pag-scan I-save Bilang PDF Ibahagi Bilang PDF Ang mga opsyon sa ibaba ay para sa pag-save ng mga larawan, hindi PDF I-equalize ang Histogram HSV I-equalize ang Histogram Ilagay ang Porsyento Payagan ang pagpasok sa pamamagitan ng Text Field Pinapagana ang Text Field sa likod ng pagpili ng mga preset, upang maipasok ang mga ito sa mabilisang Scale Color Space Linear I-equalize ang Histogram Pixelation Sukat ng Grid X Laki ng Grid Y I-equalize ang Histogram Adaptive I-equalize ang Histogram Adaptive LUV I-equalize ang Histogram Adaptive LAB CLAHE CLAHE LAB CLAHE LUV I-crop sa Nilalaman Kulay ng Frame Kulay Upang Huwag pansinin Template Walang naidagdag na mga filter ng template Lumikha ng Bago Ang na-scan na QR code ay hindi isang wastong template ng filter I-scan ang QR code Ang napiling file ay walang data ng template ng filter Lumikha ng Template Pangalan ng Template Gagamitin ang larawang ito upang i-preview ang template ng filter na ito Filter ng Template Bilang imahe ng QR code Bilang file I-save bilang file I-save bilang imahe ng QR code Tanggalin ang Template Ide-delete mo na ang napiling template filter. Hindi na maa-undo ang operasyong ito Idinagdag ang template ng filter na may pangalang \"%1$s\" (%2$s) I-filter ang Preview QR at Barcode I-scan ang QR code at kunin ang nilalaman nito o i-paste ang iyong string upang makabuo ng bago Nilalaman ng Code I-scan ang anumang barcode upang palitan ang nilalaman sa field, o mag-type ng isang bagay upang makabuo ng bagong barcode na may napiling uri Paglalarawan ng QR Min Magbigay ng pahintulot sa camera sa mga setting para i-scan ang QR code Magbigay ng pahintulot sa camera sa mga setting para i-scan ang Document Scanner Kubiko B-Spline Hamming Hanning Blackman Welch Quadric Gaussian Sphinx Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-Siya Kahon Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Nagbibigay ang cubic interpolation ng mas maayos na scaling sa pamamagitan ng pagsasaalang-alang sa pinakamalapit na 16 pixels, na nagbibigay ng mas magandang resulta kaysa bilinear Gumagamit ng piecewise-defined polynomial functions upang maayos na i-interpolate at tantiyahin ang isang curve o surface, flexible at tuluy-tuloy na representasyon ng hugis Isang window function na ginagamit upang bawasan ang spectral leakage sa pamamagitan ng pag-taping sa mga gilid ng isang signal, na kapaki-pakinabang sa pagpoproseso ng signal Isang variant ng Hann window, na karaniwang ginagamit upang mabawasan ang spectral leakage sa mga application sa pagpoproseso ng signal Isang window function na nagbibigay ng magandang frequency resolution sa pamamagitan ng pagliit ng spectral leakage, na kadalasang ginagamit sa pagpoproseso ng signal Isang window function na idinisenyo upang magbigay ng magandang frequency resolution na may pinababang spectral leakage, kadalasang ginagamit sa mga application sa pagpoproseso ng signal Isang paraan na gumagamit ng quadratic function para sa interpolation, na nagbibigay ng maayos at tuluy-tuloy na mga resulta Isang paraan ng interpolation na naglalapat ng Gaussian function, na kapaki-pakinabang para sa pagpapakinis at pagbabawas ng ingay sa mga larawan Isang advanced na paraan ng resampling na nagbibigay ng mataas na kalidad na interpolation na may kaunting artifact Isang triangular na window function na ginagamit sa pagpoproseso ng signal para mabawasan ang spectral leakage Isang mataas na kalidad na paraan ng interpolation na na-optimize para sa natural na pagbabago ng laki ng imahe, pagbabalanse ng sharpness at smoothness Isang mas matalas na variant ng Robidoux method, na na-optimize para sa malulutong na pagbabago ng laki ng larawan Isang spline-based na interpolation na paraan na nagbibigay ng maayos na resulta gamit ang 16-tap na filter Isang spline-based na interpolation na paraan na nagbibigay ng maayos na resulta gamit ang 36-tap na filter Isang spline-based na interpolation na paraan na nagbibigay ng maayos na resulta gamit ang 64-tap na filter Isang paraan ng interpolation na gumagamit ng Kaiser window, na nagbibigay ng mahusay na kontrol sa trade-off sa pagitan ng lapad ng main-lobe at side-lobe level Isang hybrid na function ng window na pinagsasama ang mga bintana ng Bartlett at Hann, na ginagamit upang bawasan ang spectral leakage sa pagpoproseso ng signal Isang simpleng paraan ng resampling na gumagamit ng average ng pinakamalapit na mga halaga ng pixel, na kadalasang nagreresulta sa isang blocky na hitsura Isang window function na ginagamit upang bawasan ang spectral leakage, na nagbibigay ng magandang frequency resolution sa mga application sa pagpoproseso ng signal Isang paraan ng resampling na gumagamit ng 2-lobe na Lanczos na filter para sa de-kalidad na interpolation na may kaunting artifact Isang paraan ng resampling na gumagamit ng 3-lobe na Lanczos na filter para sa mataas na kalidad na interpolation na may kaunting artifact Isang paraan ng resampling na gumagamit ng 4-lobe Lanczos na filter para sa mataas na kalidad na interpolation na may kaunting artifact Isang variant ng filter ng Lanczos 2 na gumagamit ng jinc function, na nagbibigay ng de-kalidad na interpolation na may kaunting artifact. Isang variant ng filter ng Lanczos 3 na gumagamit ng jinc function, na nagbibigay ng de-kalidad na interpolation na may kaunting artifact. Isang variant ng filter ng Lanczos 4 na gumagamit ng jinc function, na nagbibigay ng de-kalidad na interpolation na may kaunting artifact. Hanning EWA Elliptical Weighted Average (EWA) na variant ng Hanning filter para sa maayos na interpolation at resampling Robidoux EWA Elliptical Weighted Average (EWA) na variant ng Robidoux filter para sa mataas na kalidad na resampling Blackman EVE Elliptical Weighted Average (EWA) na variant ng Blackman filter para sa pagliit ng mga artifact ng ring Quadric EWA Elliptical Weighted Average (EWA) na variant ng Quadric na filter para sa maayos na interpolation Robidoux Sharp EWA Elliptical Weighted Average (EWA) na variant ng Robidoux Sharp filter para sa mas matalas na resulta Lanczos 3 Jinc EWA Elliptical Weighted Average (EWA) na variant ng Lanczos 3 Jinc filter para sa mataas na kalidad na resampling na may pinababang aliasing Ginseng Isang resampling filter na idinisenyo para sa mataas na kalidad na pagpoproseso ng imahe na may magandang balanse ng sharpness at smoothness Ginseng EWA Elliptical Weighted Average (EWA) na variant ng Ginseng filter para sa pinahusay na kalidad ng larawan Lanczos Sharp EWA Elliptical Weighted Average (EWA) na variant ng Lanczos Sharp filter para sa pagkamit ng matalim na resulta na may kaunting artifact Lanczos 4 Pinakamatalim na EWA Elliptical Weighted Average (EWA) na variant ng Lanczos 4 Sharpest na filter para sa sobrang matalas na resampling ng imahe Lanczos Soft EWA Elliptical Weighted Average (EWA) na variant ng Lanczos Soft filter para sa mas maayos na resampling ng imahe Haasn Soft Isang resampling filter na idinisenyo ni Haasn para sa makinis at walang artifact na pag-scale ng imahe Conversion ng Format I-convert ang batch ng mga larawan mula sa isang format patungo sa isa pa Iwaksi ang Magpakailanman Imahe Stacking I-stack ang mga larawan sa ibabaw ng bawat isa gamit ang mga piniling blend mode Magdagdag ng Larawan Bilang ng mga bin Clahe HSL Clahe HSV I-equalize ang Histogram Adaptive HSL I-equalize ang Histogram Adaptive HSV Edge Mode Clip Balutin Pagkabulag ng kulay Pumili ng mode para iakma ang mga kulay ng tema para sa napiling variant ng color blindness Kahirapan sa pagkilala sa pagitan ng pula at berdeng kulay Kahirapan sa pagkilala sa pagitan ng berde at pula na kulay Kahirapan sa pagkilala sa pagitan ng asul at dilaw na kulay Kawalan ng kakayahang makita ang mga pulang kulay Kawalan ng kakayahang makita ang mga berdeng kulay Kawalan ng kakayahang makita ang mga asul na kulay Nabawasan ang sensitivity sa lahat ng kulay Kumpletong color blindness, kulay abo lang ang nakikita Huwag gumamit ng Color Blind scheme Ang mga kulay ay magiging eksakto sa itinakda sa tema Sigmoidal Lagrange 2 Isang Lagrange interpolation filter ng order 2, na angkop para sa de-kalidad na pag-scale ng larawan na may maayos na mga transition Lagrange 3 Isang Lagrange interpolation filter ng order 3, na nag-aalok ng mas mahusay na katumpakan at mas malinaw na mga resulta para sa pag-scale ng imahe Lanczos 6 Isang Lanczos resampling filter na may mas mataas na pagkakasunud-sunod na 6, na nagbibigay ng mas matalas at mas tumpak na pag-scale ng imahe Lanczos 6 Jinc Isang variant ng filter ng Lanczos 6 gamit ang Jinc function para sa pinahusay na kalidad ng resampling ng imahe Linear Box Blur Linear Tent Blur Linear Gaussian Box Blur Linear Stack Blur Gaussian Box Blur Linear Fast Gaussian Blur Susunod Linear Fast Gaussian Blur Linear Gaussian Blur Pumili ng isang filter upang gamitin ito bilang pintura Palitan ang Filter Pumili ng filter sa ibaba para gamitin ito bilang brush sa iyong drawing TIFF compression scheme Mababang Poly Pagpipinta ng Buhangin Paghahati ng Larawan Hatiin ang iisang larawan ayon sa mga row o column Akma sa Hangganan Pagsamahin ang crop resize mode sa parameter na ito para makamit ang ninanais na gawi (Crop/Fit to aspect ratio) Matagumpay na na-import ang mga wika I-backup ang mga modelo ng OCR Mag-import I-export Posisyon Gitna Kaliwa sa itaas Kanan sa itaas Kaliwa sa ibaba Kanan sa ibaba Nangungunang Center Gitnang Kanan Ibaba Gitna Gitnang Kaliwa Target na Larawan Palette Transfer Pinahusay na Langis Simpleng Lumang TV HDR Gotham Simple Sketch Malambot na Glow Kulay ng Poster Tri Tone Pangatlong kulay Clahe Oklab Clara Olks Clahe Jzazbz Polka Dot Clustered 2x2 Dithering Clustered 4x4 Dithering Clustered 8x8 Dithering Yililoma Dithering Walang napiling mga paboritong opsyon, idagdag ang mga ito sa pahina ng mga tool Magdagdag ng Mga Paborito Komplementaryo Katulad Triadic Split Complementary Tetradic parisukat Analogous + Complementary Mga Tool sa Kulay Paghaluin, gumawa ng mga tono, bumuo ng mga shade at higit pa Mga Harmoni ng Kulay Color Shading pagkakaiba-iba Tints Mga tono Mga shade Paghahalo ng Kulay Impormasyon ng Kulay Napiling Kulay Kulay Upang Paghaluin Hindi magagamit ang monet habang naka-on ang mga dynamic na kulay 512x512 2D LUT I-target ang LUT na imahe Isang baguhan Miss Etiquette Malambot Elegance Soft Elegance na Variant Palette Transfer Variant 3D LUT Target na 3D LUT File (.cube / .CUBE) LUT Bleach Bypass Sindi ng kandila Drop Blues Nakakabaliw si Amber Mga Kulay ng Taglagas Stock ng Pelikula 50 Mahamog na Gabi Kodak Kumuha ng Neutral na LUT na imahe Una, gamitin ang iyong paboritong application sa pag-edit ng larawan upang maglapat ng filter sa neutral na LUT na maaari mong makuha dito. Para gumana ito ng maayos, hindi dapat nakadepende ang bawat kulay ng pixel sa iba pang mga pixel (hal. hindi gagana ang blur). Kapag handa na, gamitin ang iyong bagong LUT na imahe bilang input para sa 512*512 LUT filter Pop Art Celluloid kape Gintong Kagubatan Maberde Retro Yellow Preview ng Mga Link Pinapagana ang pagbawi ng preview ng link sa mga lugar kung saan makakakuha ka ng text (QRCode, OCR atbp) Mga link Ang mga ICO file ay maaari lamang i-save sa maximum na laki na 256 x 256 GIF sa WEBP I-convert ang mga larawang GIF sa mga animated na larawan ng WEBP Mga Tool sa WEBP I-convert ang mga larawan sa WEBP animated na larawan o kunin ang mga frame mula sa ibinigay na WEBP animation WEBP sa mga larawan I-convert ang WEBP file sa batch ng mga larawan I-convert ang batch ng mga larawan sa WEBP file Mga larawan sa WEBP Pumili ng WEBP image para magsimula Walang ganap na access sa mga file Payagan ang lahat ng mga file na ma-access upang makita ang JXL, QOI at iba pang mga larawan na hindi kinikilala bilang mga larawan sa Android. Kung walang pahintulot ang Image Toolbox ay hindi maipakita ang mga larawang iyon Default na Kulay ng Draw Default na Draw Path Mode Magdagdag ng Timestamp Pinapagana ang pagdaragdag ng Timestamp sa output filename Naka-format na Timestamp Paganahin ang pag-format ng Timestamp sa output filename sa halip na mga pangunahing millis Paganahin ang mga Timestamp upang piliin ang kanilang format Isang Oras na I-save ang Lokasyon Tingnan at I-edit ang isang beses na i-save ang mga lokasyon na maaari mong gamitin sa pamamagitan ng mahabang pagpindot sa save button sa halos lahat ng mga opsyon Kamakailang Ginamit CI channel Grupo Toolbox ng Larawan sa Telegram 🎉 Sumali sa aming chat kung saan maaari mong pag-usapan ang anumang gusto mo at tingnan din ang CI channel kung saan ako nagpo-post ng mga beta at anunsyo Maabisuhan tungkol sa mga bagong bersyon ng app, at magbasa ng mga anunsyo Pagkasyahin ang isang larawan sa mga ibinigay na dimensyon at ilapat ang blur o kulay sa background Pag-aayos ng mga Tool Pangkatin ang mga tool ayon sa uri Mga tool sa pangkat sa pangunahing screen ayon sa kanilang uri sa halip na isang pasadyang pag-aayos ng listahan Mga Default na Halaga Visibility ng System Bar Ipakita ang System Bars Sa pamamagitan ng Swipe Pinapagana ang pag-swipe para ipakita ang mga system bar kung nakatago ang mga ito Auto Itago Lahat Ipakita ang Lahat Itago ang Nav Bar Itago ang Status Bar Pagbuo ng Ingay Bumuo ng iba\'t ibang ingay tulad ng Perlin o iba pang uri Dalas Uri ng Ingay Uri ng Pag-ikot Uri ng Fractal Mga oktaba Kakulangan Makakuha Timbang Lakas Lakas ng Ping Pong Distansya Function Uri ng Pagbabalik Jitter Domain Warp Pag-align Custom na Filename Piliin ang lokasyon at filename na gagamitin para i-save ang kasalukuyang larawan Na-save sa folder na may custom na pangalan Collage Maker Gumawa ng mga collage mula sa hanggang 20 larawan Uri ng Collage I-hold ang imahe upang magpalit, ilipat at mag-zoom upang ayusin ang posisyon Huwag paganahin ang pag-ikot Pinipigilan ang pag-ikot ng mga larawan gamit ang dalawang daliri na mga galaw Paganahin ang pag-snap sa mga hangganan Pagkatapos gumalaw o mag-zoom, kukunin ang mga larawan upang punan ang mga gilid ng frame Histogram RGB o Brightness image histogram upang matulungan kang gumawa ng mga pagsasaayos Gagamitin ang larawang ito upang makabuo ng mga histogram ng RGB at Brightness Mga Pagpipilian sa Tesseract Ilapat ang ilang input variable para sa tesseract engine Mga Pasadyang Opsyon Dapat ipasok ang mga opsyon ayon sa pattern na ito: \"--{option_name} {value}\" Auto I-crop Libreng Corners I-crop ang imahe sa pamamagitan ng polygon, itinutuwid din nito ang pananaw Pilitin ang mga Punto Sa Mga Hangganan ng Imahe Ang mga puntos ay hindi lilimitahan ng mga hangganan ng larawan, ito ay kapaki-pakinabang para sa mas tumpak na pagwawasto ng pananaw maskara Punan ng kaalaman sa nilalaman sa ilalim ng iginuhit na landas Pagalingin ang Spot Gamitin ang Circle Kernel Pagbubukas Pagsasara Morphological Gradient Top Hat Itim na Sombrero Mga Kurba ng Tono I-reset ang Curves Ang mga curve ay ibabalik sa default na halaga Estilo ng Linya Laki ng Gap Dashed Dot Dashed Nakatatak Zigzag Gumuhit ng dashed line sa kahabaan ng iginuhit na landas na may tinukoy na laki ng gap Gumuguhit ng tuldok at putol-putol na linya sa ibinigay na landas Mga tuwid na linya lang ang default Gumuguhit ng mga napiling hugis sa landas na may tinukoy na espasyo Gumuguhit ng kulot na zigzag sa daan Zigzag ratio Lumikha ng Shortcut Pumili ng tool upang i-pin Idaragdag ang tool sa home screen ng iyong launcher bilang shortcut, gamitin ito kasama ng setting na \"Laktawan ang pagpili ng file\" upang makamit ang kinakailangang gawi Huwag mag-stack ng mga frame Ine-enable ang pagtatapon ng mga nakaraang frame, para hindi mag-stack ang mga ito sa isa\'t isa Crossfade Ang mga frame ay magiging crossfaded sa isa\'t isa Bilang ng mga crossfade na frame Unang Threshold Ikalawang Threshold Canny Salamin 101 Pinahusay na Zoom Blur Laplacian Simple Sobel Simple Helper Grid Nagpapakita ng pagsuporta sa grid sa itaas ng drawing area upang makatulong sa mga tumpak na manipulasyon Kulay ng Grid Lapad ng Cell Taas ng Cell Mga Compact Selector Ang ilang mga kontrol sa pagpili ay gagamit ng isang compact na layout upang kumuha ng mas kaunting espasyo Magbigay ng pahintulot sa camera sa mga setting para kumuha ng larawan Layout Pangunahing Pamagat ng Screen Constant Rate Factor (CRF) Ang halaga ng %1$s ay nangangahulugang isang mabagal na pag-compress, na nagreresulta sa isang medyo maliit na laki ng file. Ang ibig sabihin ng %2$s ay isang mas mabilis na compression, na nagreresulta sa isang malaking file. Lut Library I-download ang koleksyon ng mga LUT, na maaari mong ilapat pagkatapos mag-download I-update ang koleksyon ng mga LUT (mga bago lang ang ipi-queue), na maaari mong ilapat pagkatapos mag-download Baguhin ang default na preview ng larawan para sa mga filter I-preview ang Larawan Magtago Ipakita Uri ng Slider Fancy Materyal 2 Isang magarbong slider. Ito ang default na opsyon Isang Materyal 2 slider Isang Materyal Iyong slider Mag-apply Mga Pindutan ng Dialog sa Gitnang Ang mga pindutan ng mga dialog ay ipoposisyon sa gitna sa halip na sa kaliwang bahagi kung maaari Mga Lisensya sa Open Source Tingnan ang mga lisensya ng mga open source na library na ginagamit sa app na ito Lugar Resampling gamit ang pixel area relation. Ito ay maaaring isang ginustong paraan para sa pag-decimation ng imahe, dahil nagbibigay ito ng moire\'-free na mga resulta. Ngunit kapag ang imahe ay naka-zoom, ito ay katulad ng \"Nearest\" na paraan. Paganahin ang Tonemapping Ipasok ang % Hindi ma-access ang site, subukang gumamit ng VPN o tingnan kung tama ang url Mga Markup Layer Layers mode na may kakayahang malayang maglagay ng mga larawan, teksto at higit pa I-edit ang layer Mga layer sa larawan Gumamit ng isang imahe bilang isang background at magdagdag ng iba\'t ibang mga layer sa ibabaw nito Mga layer sa background Pareho sa unang opsyon ngunit may kulay sa halip na larawan Beta Gilid ng Mabilis na Mga Setting Magdagdag ng lumulutang na strip sa napiling gilid habang nag-e-edit ng mga larawan, na magbubukas ng mabilis na mga setting kapag na-click I-clear ang pagpili Ang pagtatakda ng pangkat na \"%1$s\" ay iko-collapse bilang default Ang pagtatakda ng pangkat na \"%1$s\" ay palalawakin bilang default Mga Tool sa Base64 I-decode ang Base64 string sa imahe, o i-encode ang imahe sa Base64 na format Base64 Ang ibinigay na halaga ay hindi wastong Base64 string Hindi makopya ang walang laman o di-wastong Base64 string Idikit ang Base64 Copy Base64 Mag-load ng larawan para kopyahin o i-save ang Base64 string. Kung mayroon kang mismong string, maaari mo itong i-paste sa itaas upang makakuha ng larawan I-save ang Base64 Ibahagi ang Base64 Mga pagpipilian Mga aksyon Import Base64 Base64 na Mga Aksyon Magdagdag ng Balangkas Magdagdag ng outline sa paligid ng teksto na may tinukoy na kulay at lapad Kulay ng Balangkas Laki ng Balangkas Pag-ikot Checksum bilang Filename Ang mga imahe ng output ay magkakaroon ng pangalang naaayon sa kanilang data checksum Libreng Software (Kasosyo) Mas kapaki-pakinabang na software sa partner channel ng mga Android application Algorithm Mga Tool sa Checksum Paghambingin ang mga checksum, kalkulahin ang mga hash o gumawa ng mga hex string mula sa mga file gamit ang iba\'t ibang algorithm ng hashing Kalkulahin I-text ang Hash Checksum Pumili ng file para kalkulahin ang checksum nito batay sa napiling algorithm Maglagay ng text para kalkulahin ang checksum nito batay sa napiling algorithm Pinagmulan Checksum Checksum Upang Paghambingin Match! Pagkakaiba Ang mga checksum ay pantay, maaari itong maging ligtas Ang mga checksum ay hindi pantay, ang file ay maaaring hindi ligtas! Mesh Gradients Tumingin sa online na koleksyon ng Mesh Gradients Tanging mga TTF at OTF na font ang maaaring ma-import Mag-import ng font (TTF/OTF) I-export ang mga font Mga na-import na font Error habang nagse-save ng pagtatangka, subukang baguhin ang folder ng output Hindi nakatakda ang filename wala Mga Custom na Pahina Pagpili ng Mga Pahina Kumpirmasyon sa Paglabas ng Tool Kung mayroon kang mga hindi na-save na pagbabago habang gumagamit ng mga partikular na tool at subukang isara ito, pagkatapos ay ipapakita ang dialog na kumpirmahin I-edit ang EXIF Baguhin ang metadata ng isang larawan nang walang recompression I-tap para i-edit ang mga available na tag Palitan ang Sticker Pagkasyahin ang Lapad Tamang-tama sa Taas Batch Compare Pumili ng file/file para kalkulahin ang checksum nito batay sa napiling algorithm Pumili ng Mga File Pumili ng Direktoryo Scale ng Haba ng Ulo selyo Timestamp Pattern ng Format Padding Pagputol ng Larawan Gupitin ang bahagi ng larawan at pagsamahin ang mga kaliwa (maaaring baligtad) sa pamamagitan ng patayo o pahalang na mga linya Vertical Pivot Line Pahalang na Pivot Line Baliktad na Pagpili Ang bahaging patayong hiwa ay iiwanan, sa halip na pagsamahin ang mga bahagi sa paligid ng lugar ng hiwa Iiwan ang pahalang na bahagi ng hiwa, sa halip na pagsamahin ang mga bahagi sa paligid ng hiwa Koleksyon ng Mesh Gradients Gumawa ng mesh gradient na may custom na dami ng mga buhol at resolution Mesh Gradient Overlay Gumawa ng mesh gradient ng tuktok ng mga ibinigay na larawan Pag-customize ng mga puntos Sukat ng Grid Resolusyon X Resolusyon Y Resolusyon Pixel Sa Pixel Kulay ng Highlight Uri ng Paghahambing ng Pixel I-scan ang barcode Ratio ng Taas Uri ng Barcode Ipatupad ang B/W Ang Larawan ng Barcode ay magiging ganap na itim at puti at hindi makulayan ng tema ng app I-scan ang anumang Barcode (QR, EAN, AZTEC, …) at kunin ang nilalaman nito o i-paste ang iyong teksto upang makabuo ng bago Walang Nakitang Barcode Naririto ang Binuo na Barcode Mga Audio Cover I-extract ang mga larawan sa pabalat ng album mula sa mga audio file, karamihan sa mga karaniwang format ay sinusuportahan Pumili ng audio para magsimula Pumili ng Audio Walang Nahanap na Cover Magpadala ng mga Log I-click upang ibahagi ang file ng mga log ng app, makakatulong ito sa akin na makita ang problema at ayusin ang mga isyu Oops… Nagkaproblema Maaari kang makipag-ugnayan sa akin gamit ang mga opsyon sa ibaba at susubukan kong maghanap ng solusyon.\n(Huwag kalimutang mag-attach ng mga log) Sumulat sa File I-extract ang text mula sa batch ng mga imahe at iimbak ito sa isang text file Sumulat sa Metadata I-extract ang text mula sa bawat larawan at ilagay ito sa EXIF ​​na impormasyon ng mga kamag-anak na larawan Invisible Mode Gumamit ng steganography upang lumikha ng mga hindi nakikitang watermark ng mata sa loob ng mga byte ng iyong mga larawan Gumamit ng LSB LSB (Less Significant Bit) steganography method ang gagamitin, FD (Frequency Domain) kung hindi. Awtomatikong Alisin ang Mga Pulang Mata Password I-unlock Pinoprotektahan ang PDF Halos kumpleto ang operasyon. Ang pagkansela ngayon ay mangangailangan ng pag-restart nito Petsa ng Binago Petsa ng Binago (Binaliktad) Sukat Sukat (Baliktad) Uri ng MIME Uri ng MIME (Baliktad) Extension Extension (Baliktad) Petsa ng Idinagdag Petsa ng Pagdagdag (Binaliktad) Kaliwa hanggang Kanan Kanan papuntang Kaliwa Itaas hanggang Ibaba Ibaba hanggang Itaas Liquid na Salamin Isang switch batay sa kamakailang inihayag na IOS 26 at ang sistema ng disenyo ng likidong salamin nito Pumili ng larawan o i-paste/i-import ang Base64 data sa ibaba I-type ang link ng larawan upang magsimula Idikit ang link Kaleidoscope Pangalawang anggulo Mga gilid Channel Mix Asul na berde Pulang asul Berdeng pula Sa pula Sa berde Sa asul Cyan Magenta Dilaw Kulay Halftone Contour Mga antas Offset Nag-kristal ang Voronoi Hugis Mag-stretch pagiging random Despeckle Nagkakalat DoG Pangalawang radius Magpantay kumikinang Whirl and Pinch Pointillize Kulay ng hangganan Mga Polar Coordinate Rect sa polar Polar sa rect Baliktarin sa bilog Bawasan ang Ingay Simpleng Solarize Paghahabi X Gap Y Gap X Lapad Y Lapad Umikot Rubber Stamp Pahid Densidad Paghaluin Distortion ng Sphere Lens Index ng repraksyon Arc Spread anggulo Kislap Sinag ASCII Gradient Mary taglagas buto Jet Taglamig Karagatan Tag-init tagsibol Cool na Variant HSV Rosas Mainit salita Magma Inferno Plasma Viridis Mga mamamayan takipsilim Twilight Shifted Pananaw Auto Deskew Payagan ang pag-crop I-crop o Pananaw Ganap Turbo Deep Green Pagwawasto ng Lens Target na file ng profile ng lens sa JSON format Mag-download ng mga ready lens profile Mga porsyento ng bahagi I-export bilang JSON Kopyahin ang string na may data ng palette bilang representasyon ng json Pag-ukit ng tahi Home Screen Lock Screen Naka-built-in Mga Wallpaper Export I-refresh Kumuha ng kasalukuyang Home, Lock at Built-in na wallpaper Payagan ang pag-access sa lahat ng mga file, ito ay kinakailangan upang makuha ang mga wallpaper Hindi sapat ang pahintulot na pamahalaan ang panlabas na storage, kailangan mong payagan ang pag-access sa iyong mga larawan, tiyaking piliin ang \"Pahintulutan ang lahat\" Magdagdag ng Preset Sa Filename Nagdaragdag ng suffix na may napiling preset sa image filename Magdagdag ng Image Scale Mode Sa Filename Nagdaragdag ng suffix na may napiling mode ng scale ng imahe sa filename ng imahe Ascii Art I-convert ang larawan sa ascii text na magmumukhang larawan Params Naglalapat ng negatibong filter sa larawan para sa mas magandang resulta sa ilang sitwasyon Pinoproseso ang screenshot Hindi nakuha ang screenshot, subukang muli Nilaktawan ang pag-save Nilaktawan ang %1$s file Payagan ang Laktawan Kung Mas Malaki Ang ilang mga tool ay papayagan na laktawan ang pag-save ng mga imahe kung ang resultang laki ng file ay mas malaki kaysa sa orihinal Kaganapan sa Kalendaryo Makipag-ugnayan Email Lokasyon Telepono Text SMS URL Wi-Fi Buksan ang network N/A SSID Telepono Mensahe Address Paksa Katawan Pangalan Organisasyon Pamagat Mga telepono Mga email Mga URL Mga address Buod Paglalarawan Lokasyon Organizer Petsa ng pagsisimula Petsa ng pagtatapos Katayuan Latitude Longitude Lumikha ng Barcode I-edit ang Barcode configuration ng Wi-Fi Seguridad Pumili ng contact Bigyan ang mga contact ng pahintulot sa mga setting na mag-autofill gamit ang napiling contact Impormasyon sa pakikipag-ugnayan Pangalan Gitnang pangalan apelyido Pagbigkas Magdagdag ng telepono Magdagdag ng email Magdagdag ng address Website Magdagdag ng website Naka-format na pangalan Ang larawang ito ay gagamitin upang ilagay sa itaas ng barcode Pag-customize ng code Gagamitin ang larawang ito bilang logo sa gitna ng QR code Logo Logo padding Laki ng logo Mga sulok ng logo Pang-apat na mata Nagdaragdag ng simetrya ng mata sa qr code sa pamamagitan ng pagdaragdag ng pang-apat na mata sa dulong sulok sa ibaba Hugis ng pixel Hugis ng frame Hugis ng bola Antas ng pagwawasto ng error Madilim na kulay Banayad na kulay Hyper OS Tulad ng estilo ng Xiaomi HyperOS Pattern ng maskara Maaaring hindi ma-scan ang code na ito, baguhin ang mga param ng hitsura upang gawin itong nababasa sa lahat ng device Hindi na-scan Ang mga tool ay magmumukhang home screen app launcher upang maging mas compact Launcher Mode Pinuno ang isang lugar na may napiling brush at istilo Punan ng Baha Mag-spray Gumuguhit ng graffyy styled path Square Particle Ang mga spray particle ay magiging parisukat sa halip na mga bilog Mga Tool sa Palette Bumuo ng pangunahing/materyal na iyong palette mula sa larawan, o mag-import/mag-export sa iba\'t ibang mga format ng palette I-edit ang Palette I-export/import ang palette sa iba\'t ibang mga format Pangalan ng kulay Pangalan ng palette Format ng Palette I-export ang nabuong palette sa iba\'t ibang mga format Nagdaragdag ng bagong kulay sa kasalukuyang palette Hindi sinusuportahan ng %1$s na format ang pagbibigay ng pangalan ng palette Dahil sa mga patakaran ng Play Store, hindi maaaring isama ang feature na ito sa kasalukuyang build. Upang ma-access ang functionality na ito, mangyaring i-download ang ImageToolbox mula sa isang alternatibong pinagmulan. Mahahanap mo ang mga available na build sa GitHub sa ibaba. Buksan ang pahina ng Github Ang orihinal na file ay papalitan ng bago sa halip na i-save sa napiling folder Nakakita ng nakatagong watermark na text Nakita ang nakatagong larawan ng watermark Nakatago ang larawang ito Generative Inpainting Binibigyang-daan kang mag-alis ng mga bagay sa isang imahe gamit ang isang modelo ng AI, nang hindi umaasa sa OpenCV. Para magamit ang feature na ito, ida-download ng app ang kinakailangang modelo (~200 MB) mula sa GitHub Binibigyang-daan kang mag-alis ng mga bagay sa isang imahe gamit ang isang modelo ng AI, nang hindi umaasa sa OpenCV. Ito ay maaaring isang matagal na operasyon Pagsusuri sa Antas ng Error Luminance Gradient Average na Distansya Kopyahin ang Move Detection Panatilihin Coefficient Masyadong malaki ang data ng clipboard Masyadong malaki ang data para makopya Simple Weave Pixelization Staggered Pixelization Cross Pixelization Micro Macro Pixelization Orbital Pixelization Vortex Pixelization Pixelization ng Pulse Grid Pixelization ng Nucleus Radial Weave Pixelization Hindi mabuksan ang uri \"%1$s\" Mode ng Snowfall Pinagana Border Frame Variant ng Glitch Paglipat ng Channel Max Offset VHS I-block ang Glitch Laki ng Block CRT curvature Curvature Chroma Pixel Melt Max Drop Mga Tool ng AI Iba\'t ibang mga tool upang iproseso ang mga larawan sa pamamagitan ng mga modelo tulad ng pag-alis o pag-denoise ng artifact Compression, tulis-tulis na linya Cartoons, broadcast compression Pangkalahatang compression, pangkalahatang ingay Walang kulay na ingay ng cartoon Mabilis, pangkalahatang compression, pangkalahatang ingay, animation/komiks/anime Pag-scan ng libro Pagwawasto ng exposure Pinakamahusay sa pangkalahatang compression, mga larawang may kulay Pinakamahusay sa pangkalahatang compression, grayscale na mga larawan Pangkalahatang compression, grayscale na mga imahe, mas malakas Pangkalahatang ingay, mga larawang may kulay Pangkalahatang ingay, mga larawang may kulay, mas magagandang detalye Pangkalahatang ingay, grayscale na mga larawan Pangkalahatang ingay, grayscale na mga imahe, mas malakas Pangkalahatang ingay, grayscale na mga imahe, pinakamalakas Pangkalahatang compression Pangkalahatang compression Texturization, h264 compression VHS compression Hindi karaniwang compression (cinepak, msvideo1, roq) Bink compression, mas mahusay sa geometry Bink compression, mas malakas Bink compression, malambot, pinapanatili ang detalye Inaalis ang epekto ng hagdan-hakbang, pagpapakinis Scanned art/drawings, mild compression, moire Banding ng kulay Mabagal, inaalis ang mga halftone Pangkalahatang colorizer para sa grayscale/bw na mga imahe, para sa mas magandang resulta gumamit ng DDColor Pag-alis ng gilid Nag-aalis ng labis na pagpapatalas Mabagal, nalilito Anti-aliasing, pangkalahatang artifact, CGI Pinoproseso ng KDM003 Magaan na modelo ng pagpapahusay ng imahe Pag-alis ng compression artifact Pag-alis ng compression artifact Pag-alis ng benda na may makinis na resulta Pagproseso ng pattern ng halftone Pag-alis ng pattern ng dither V3 JPEG artifact removal V2 H.264 pagpapahusay ng texture VHS hasa at pagpapahusay Pinagsasama Laki ng Tipak Laki ng Overlap Ang mga larawang higit sa %1$s px ay hihiwain at ipoproseso sa mga tipak, magkakapatong-patong ang mga ito upang maiwasan ang mga nakikitang tahi. Ang malalaking sukat ay maaaring magdulot ng kawalang-tatag sa mga low-end na device Pumili ng isa para magsimula Gusto mo bang tanggalin ang %1$s na modelo? Kakailanganin mong i-download itong muli Kumpirmahin Mga modelo Mga Na-download na Modelo Mga Magagamit na Modelo Naghahanda Aktibong modelo Nabigong buksan ang session Tanging mga .onnx/.ort na modelo ang maaaring ma-import Mag-import ng modelo Mag-import ng custom na onnx na modelo para magamit pa, mga onnx/ort model lang ang tinatanggap, sinusuportahan ang halos lahat ng esrgan na mga variant Mga na-import na Modelo Pangkalahatang ingay, mga larawang may kulay Pangkalahatang ingay, may kulay na mga imahe, mas malakas Pangkalahatang ingay, may kulay na mga imahe, pinakamalakas Binabawasan ang dithering artifact at color banding, pagpapabuti ng mga makinis na gradient at flat color na lugar. Pinapaganda ang liwanag at contrast ng imahe gamit ang mga balanseng highlight habang pinapanatili ang mga natural na kulay. Nagpapaliwanag ng mga madilim na larawan habang pinapanatili ang mga detalye at iniiwasan ang labis na pagkakalantad. Nag-aalis ng labis na kulay na toning at nagpapanumbalik ng mas neutral at natural na balanse ng kulay. Inilalapat ang Poisson-based na noise toning na may diin sa pagpapanatili ng mga magagandang detalye at texture. Naglalapat ng malambot na Poisson noise toning para sa mas makinis at hindi gaanong agresibong visual na mga resulta. Uniform noise toning na nakatuon sa pangangalaga ng detalye at kalinawan ng imahe. Magiliw na pare-parehong ingay toning para sa banayad na texture at makinis na hitsura. Nag-aayos ng mga nasira o hindi pantay na lugar sa pamamagitan ng muling pagpipinta ng mga artifact at pagpapabuti ng pagkakapare-pareho ng imahe. Lightweight debanding model na nag-aalis ng color banding na may kaunting gastos sa performance. Ino-optimize ang mga larawang may napakataas na compression artifact (0-20% na kalidad) para sa pinahusay na kalinawan. Pinapahusay ang mga larawang may mataas na compression artifact (20-40% na kalidad), pagpapanumbalik ng mga detalye at pagbabawas ng ingay. Pinapabuti ang mga larawang may katamtamang compression (40-60% na kalidad), binabalanse ang sharpness at smoothness. Pinipino ang mga larawan na may magaan na compression (60-80% na kalidad) para mapahusay ang mga banayad na detalye at texture. Bahagyang pinapaganda ang halos walang pagkawalang mga larawan (80-100% kalidad) habang pinapanatili ang natural na hitsura at mga detalye. Simple at mabilis na colorization, cartoons, hindi perpekto Bahagyang binabawasan ang blur ng imahe, pinapabuti ang sharpness nang hindi nagpapakilala ng mga artifact. Mahabang tumatakbong operasyon Pinoproseso ang imahe Pinoproseso Nag-aalis ng mabibigat na JPEG compression artifact sa napakababang kalidad ng mga larawan (0-20%). Binabawasan ang malalakas na JPEG artifact sa mataas na naka-compress na mga larawan (20-40%). Nililinis ang mga katamtamang JPEG artifact habang pinapanatili ang mga detalye ng larawan (40-60%). Pinipino ang mga magaan na JPEG artifact sa medyo mataas na kalidad na mga larawan (60-80%). Bahagyang binabawasan ang mga menor de edad na JPEG artifact sa halos walang pagkawala na mga larawan (80-100%). Pinapahusay ang mga pinong detalye at texture, pinapabuti ang nakikitang sharpness nang walang mabibigat na artifact. Tapos na ang pagproseso Nabigo ang pagproseso Pinapaganda ang mga texture at detalye ng balat habang pinapanatili ang natural na hitsura, na na-optimize para sa bilis. Inaalis ang mga artifact ng compression ng JPEG at ibinabalik ang kalidad ng imahe para sa mga naka-compress na larawan. Binabawasan ang ingay ng ISO sa mga larawang kinunan sa mga kondisyong mababa ang liwanag, na pinapanatili ang mga detalye. Itinatama ang overexposed o \"jumbo\" na mga highlight at ibinabalik ang mas magandang tonal balance. Magaan at mabilis na modelo ng colorization na nagdaragdag ng mga natural na kulay sa mga grayscale na larawan. DEJPEG Denoise Magkulay Mga artifact Pagandahin Anime Mga scan Upscale X4 upscaler para sa mga pangkalahatang larawan; maliit na modelo na gumagamit ng mas kaunting GPU at oras, na may katamtamang deblur at denoise. X2 upscaler para sa mga pangkalahatang larawan, pinapanatili ang mga texture at natural na mga detalye. X4 upscaler para sa mga pangkalahatang larawan na may pinahusay na mga texture at makatotohanang mga resulta. X4 upscaler na-optimize para sa mga imahe ng anime; 6 RRDB block para sa mas matalas na linya at detalye. X4 upscaler na may pagkawala ng MSE, gumagawa ng mas malinaw na mga resulta at pinababang artifact para sa mga pangkalahatang larawan. X4 Upscaler na-optimize para sa mga imahe ng anime; 4B32F variant na may mas matalas na detalye at makinis na linya. X4 UltraSharp V2 na modelo para sa mga pangkalahatang larawan; binibigyang-diin ang talas at kalinawan. X4 UltraSharp V2 Lite; mas mabilis at mas maliit, pinapanatili ang detalye habang gumagamit ng mas kaunting memorya ng GPU. Magaang modelo para sa mabilis na pag-alis ng background. Balanseng pagganap at katumpakan. Gumagana sa mga portrait, bagay, at eksena. Inirerekomenda para sa karamihan ng mga kaso ng paggamit. Alisin ang BG Pahalang na Kapal ng Border Vertical Border Thickness %1$s kulay %1$s kulay Hindi sinusuportahan ng kasalukuyang modelo ang chunking, ipoproseso ang imahe sa mga orihinal na dimensyon, maaari itong magdulot ng mataas na pagkonsumo ng memorya at mga isyu sa mga low-end na device Hindi pinagana ang pag-chun, ipoproseso ang larawan sa mga orihinal na dimensyon, maaari itong magdulot ng mataas na pagkonsumo ng memorya at mga isyu sa mga low-end na device ngunit maaaring magbigay ng mas magandang resulta sa hinuha Chunking Modelo ng pagse-segment ng larawan na may mataas na katumpakan para sa pag-alis ng background Magaan na bersyon ng U2Net para sa mas mabilis na pag-alis ng background na may mas maliit na paggamit ng memory. Ang buong modelo ng DDColor ay naghahatid ng mataas na kalidad na colorization para sa mga pangkalahatang larawan na may kaunting artifact. Pinakamahusay na pagpipilian ng lahat ng mga modelo ng colorization. DDColor Sinanay at pribadong artistikong dataset; gumagawa ng magkakaibang at masining na mga resulta ng colorization na may mas kaunting hindi makatotohanang mga artifact ng kulay. Magaang modelo ng BiRefNet batay sa Swin Transformer para sa tumpak na pag-alis ng background. De-kalidad na pag-alis ng background na may matatalim na gilid at mahusay na pangangalaga sa detalye, lalo na sa mga kumplikadong bagay at nakakalito na background. Modelo sa pag-alis ng background na gumagawa ng mga tumpak na maskara na may makinis na mga gilid, na angkop para sa mga pangkalahatang bagay at katamtamang pangangalaga sa detalye. Na-download na ang modelo Matagumpay na na-import ang modelo Uri Keyword Napakabilis Normal Mabagal Napakabagal Compute ng mga Porsyento Ang min value ay %1$s I-distort ang imahe sa pamamagitan ng pagguhit gamit ang mga daliri Warp Katigasan Warp Mode Ilipat Lumaki Paliitin Umikot CW Iikot ang CCW Lakas ng Fade Top Drop Bottom Drop Simulan ang Drop End Drop Nagda-download Makinis na Hugis Gumamit ng mga superellipse sa halip na mga karaniwang bilugan na parihaba para sa mas makinis, mas natural na mga hugis Uri ng Hugis Putulin Bilugan Makinis Matalim ang mga gilid nang walang pagbilog Mga klasikong bilugan na sulok Uri ng Hugis Sukat ng mga Sulok Squircle Mga eleganteng bilugan na elemento ng UI Format ng Filename Custom na text na inilagay sa pinakadulo simula ng filename, perpekto para sa mga pangalan ng proyekto, brand, o personal na tag. Gumagamit ng orihinal na pangalan ng file nang walang extension, na tumutulong sa iyong panatilihing buo ang pagkakakilanlan ng pinagmulan. Ang lapad ng imahe sa mga pixel, kapaki-pakinabang para sa pagsubaybay sa mga pagbabago sa resolution o pag-scale ng mga resulta. Ang taas ng larawan sa mga pixel, nakakatulong kapag nagtatrabaho sa mga aspect ratio o pag-export. Bumubuo ng mga random na digit upang magarantiya ang mga natatanging filename; magdagdag ng higit pang mga digit para sa karagdagang kaligtasan laban sa mga duplicate. Auto-incrementing counter para sa mga batch export, perpekto kapag nagse-save ng maraming larawan sa isang session. Inilalagay ang inilapat na preset na pangalan sa filename upang madali mong matandaan kung paano naproseso ang larawan. Ipinapakita ang mode ng pag-scale ng imahe na ginagamit sa pagpoproseso, na tumutulong na makilala ang binago, na-crop, o nilagyan ng mga larawan. Custom na text na inilagay sa dulo ng filename, kapaki-pakinabang para sa pag-bersyon tulad ng _v2, _edit, o _final. Ang extension ng file (png, jpg, webp, atbp.), ay awtomatikong tumutugma sa aktwal na naka-save na format. Isang nako-customize na timestamp na nagbibigay-daan sa iyong tukuyin ang sarili mong format ayon sa java specification para sa perpektong pag-uuri. Uri ng Fling Android Native Istilo ng iOS Makinis na Kurba Mabilis na Huminto Bouncy Lutang Snappy Napakakinis Adaptive Accessibility Aware Nabawasang Paggalaw Native Android scroll physics Balanse, makinis na pag-scroll para sa pangkalahatang paggamit Mas mataas na friction iOS-like scroll behavior Natatanging spline curve para sa kakaibang scroll feel Tumpak na pag-scroll na may mabilis na paghinto Mapaglaro, tumutugon bouncy scroll Mahahaba, gliding scroll para sa pag-browse ng content Mabilis, tumutugon na pag-scroll para sa mga interactive na UI Premium na makinis na pag-scroll na may pinahabang momentum Inaayos ang physics batay sa bilis ng fling Iginagalang ang mga setting ng accessibility ng system Minimal na paggalaw para sa mga pangangailangan sa accessibility Mga Pangunahing Linya Nagdaragdag ng mas makapal na linya sa bawat ikalimang linya Kulay ng Punan Mga Nakatagong Tool Mga Tool na Nakatago Para Ibahagi Library ng Kulay Mag-browse ng malawak na koleksyon ng mga kulay Pinatalas at inaalis ang blur sa mga larawan habang pinapanatili ang mga natural na detalye, perpekto para sa pag-aayos ng mga hindi naka-focus na larawan. Matalinong nire-restore ang mga larawang dati nang na-resize, binabawi ang mga nawawalang detalye at texture. Na-optimize para sa live-action na content, binabawasan ang mga artifact ng compression at pinapahusay ang magagandang detalye sa mga frame ng pelikula/palabas sa TV. Kino-convert ang VHS-kalidad na footage sa HD, inaalis ang ingay ng tape at pinapahusay ang resolution habang pinapanatili ang vintage na pakiramdam. Espesyalista para sa mga larawan at screenshot na mabigat sa teksto, nagpapatalas ng mga character at pinapahusay ang pagiging madaling mabasa. Advanced na upscaling na sinanay sa magkakaibang mga dataset, mahusay para sa pangkalahatang layunin na pagpapahusay ng larawan. Na-optimize para sa mga larawang naka-compress sa web, nag-aalis ng mga JPEG artifact at nagpapanumbalik ng natural na hitsura. Pinahusay na bersyon para sa mga larawan sa web na may mas mahusay na pangangalaga sa texture at pagbabawas ng artifact. 2x upscaling gamit ang Dual Aggregation Transformer na teknolohiya, nagpapanatili ng sharpness at natural na mga detalye. 3x upscaling gamit ang advanced na transformer architecture, perpekto para sa katamtamang mga pangangailangan sa pagpapalaki. 4x na mataas na kalidad na upscaling na may makabagong network ng transformer, pinapanatili ang magagandang detalye sa mas malalaking sukat. Nag-aalis ng blur/ingay at nanginginig sa mga larawan. Pangkalahatang layunin ngunit pinakamahusay sa mga larawan. Ibinabalik ang mababang kalidad na mga larawan gamit ang Swin2SR transformer, na na-optimize para sa BSRGAN degradation. Mahusay para sa pag-aayos ng mga heavy compression artifact at pagpapahusay ng mga detalye sa 4x scale. 4x na upscaling gamit ang SwinIR transformer na sinanay sa BSRGAN degradation. Gumagamit ng GAN para sa mas matalas na mga texture at mas natural na mga detalye sa mga larawan at kumplikadong mga eksena. Daan Pagsamahin ang PDF Pagsamahin ang maramihang mga PDF file sa isang dokumento Pagkakasunud-sunod ng mga File pp. Hatiin ang PDF I-extract ang mga partikular na pahina mula sa PDF na dokumento I-rotate ang PDF Permanenteng ayusin ang oryentasyon ng page Mga pahina Ayusin muli ang PDF I-drag at i-drop ang mga pahina upang muling ayusin ang mga ito Hawakan at I-drag ang mga pahina Mga Numero ng Pahina Awtomatikong magdagdag ng pagnunumero sa iyong mga dokumento Format ng Label PDF to Text (OCR) I-extract ang plain text mula sa iyong mga PDF na dokumento I-overlay ang custom na text para sa pagba-brand o seguridad Lagda Idagdag ang iyong electronic signature sa anumang dokumento Ito ay gagamitin bilang lagda I-unlock ang PDF Alisin ang mga password mula sa iyong mga protektadong file Protektahan ang PDF I-secure ang iyong mga dokumento gamit ang malakas na pag-encrypt Tagumpay Na-unlock ang PDF, maaari mong i-save o ibahagi ito Ayusin ang PDF Subukang ayusin ang mga sira o hindi nababasang mga dokumento Grayscale I-convert ang lahat ng mga larawang naka-embed na dokumento sa grayscale I-compress ang PDF I-optimize ang laki ng iyong file ng dokumento para sa mas madaling pagbabahagi Ang ImageToolbox ay muling itinatayo ang panloob na cross-reference na talahanayan at muling nabuo ang istraktura ng file mula sa simula. Maaari nitong ibalik ang access sa maraming file na \\"hindi mabubuksan\\" Kino-convert ng tool na ito ang lahat ng mga imahe ng dokumento sa grayscale. Pinakamahusay para sa pag-print at pagbabawas ng laki ng file Metadata I-edit ang mga katangian ng dokumento para sa mas mahusay na privacy Mga tag Producer May-akda Mga keyword Tagapaglikha Privacy Deep Clean I-clear ang lahat ng available na metadata para sa dokumentong ito Pahina Malalim na OCR I-extract ang text mula sa dokumento at iimbak ito sa isang text file gamit ang Tesseract engine Hindi maalis ang lahat ng pahina Alisin ang mga pahinang PDF Alisin ang mga partikular na pahina mula sa dokumentong PDF I-tap ang Upang Alisin Manu-manong I-crop ang PDF I-crop ang mga pahina ng dokumento sa anumang mga hangganan I-flatte ang PDF Gawing hindi nababago ang PDF sa pamamagitan ng pag-raster ng mga pahina ng dokumento Hindi masimulan ang camera. Pakisuri ang mga pahintulot at tiyaking hindi ito ginagamit ng ibang app. I-extract ang mga Larawan I-extract ang mga larawang naka-embed sa mga PDF sa orihinal na resolution ng mga ito Ang PDF file na ito ay hindi naglalaman ng anumang mga naka-embed na larawan Ini-scan ng tool na ito ang bawat pahina at binabawi ang buong kalidad na mga larawan ng pinagmulan — perpekto para sa pag-save ng mga orihinal mula sa mga dokumento Gumuhit ng Lagda Panulat Params Gumamit ng sariling pirma bilang imahe na ilalagay sa mga dokumento Zip PDF Hatiin ang dokumento sa ibinigay na pagitan at mag-pack ng mga bagong dokumento sa zip archive Pagitan Mag-print ng PDF Maghanda ng dokumento para sa pag-print na may custom na laki ng pahina Mga Pahina Bawat Sheet Oryentasyon Laki ng Pahina Margin Bloom Malambot na Tuhod Na-optimize para sa anime at cartoons. Mabilis na pag-upscale gamit ang pinahusay na natural na mga kulay at mas kaunting artifact Gaya ng istilo ng Samsung One UI 7 Maglagay ng mga pangunahing simbolo ng matematika dito upang kalkulahin ang nais na halaga (hal. (5+5)*10) Math expression Pumili ng hanggang %1$s na mga larawan Panatilihin ang Oras ng Petsa Palaging panatilihin ang mga exif tag na nauugnay sa petsa at oras, gumagana nang hiwalay sa opsyon na panatilihin ang exif Kulay ng Background Para sa Mga Alpha Format Nagdaragdag ng kakayahang magtakda ng kulay ng background para sa bawat format ng larawan na may suporta sa alpha, kapag hindi pinagana ito ay magagamit lamang para sa mga hindi alpha Buksan ang proyekto Ipagpatuloy ang pag-edit ng naunang na-save na proyekto ng Image Toolbox Hindi mabuksan ang proyekto ng Image Toolbox Ang proyekto ng Image Toolbox ay walang data ng proyekto Ang proyekto ng Image Toolbox ay sira Hindi sinusuportahang bersyon ng proyekto ng Image Toolbox: %1$d I-save ang proyekto Mag-imbak ng mga layer, background at kasaysayan ng pag-edit sa isang nae-edit na file ng proyekto Nabigong buksan Sumulat Sa Mahahanap na PDF Kilalanin ang teksto mula sa batch ng imahe at i-save ang nahahanap na PDF na may larawan at maaaring piliin na layer ng teksto Layer alpha Pahalang na Baligtad Vertical Flip Lock Magdagdag ng Shadow Kulay ng Anino Text Geometry I-stretch o i-skew ang text para sa mas matalas na stylization Scale X I-skew X Alisin ang mga anotasyon Alisin ang mga napiling uri ng anotasyon gaya ng mga link, komento, highlight, hugis, o mga field ng form mula sa mga pahinang PDF Mga hyperlink Mga Attachment ng File Mga linya Mga popup Mga selyo Mga hugis Mga Tala sa Teksto Markup ng Teksto Mga Patlang ng Form Markup Hindi alam Mga anotasyon Alisin sa pangkat Magdagdag ng blur shadow sa likod ng layer na may nako-configure na kulay at mga offset ================================================ FILE: core/resources/src/main/res/values-fr/strings.xml ================================================ Quelque chose s\'est mal passé: %1$s Dimension %1$s Chargement… L\'image est trop grande pour être prévisualisée, mais la sauvegarde sera tentée de toute façon Pour commencer, sélectionnez une image Largeur %1$s Hauteur %1$s Qualité Extension Type de redimensionnement Spécifique Flexible Choisir une image Êtes-vous sûr de vouloir fermer l\'application ? Fermeture de l\'application Rester Fermer Réinitialiser l\'image Les valeurs de l\'image seront restaurées aux valeurs initiales Les valeurs ont été correctement réinitialisées Réinitialiser Une erreur s\'est produite Redémarrer l\'application Copié dans le presse-papier Exception Modifier les données EXIFs OK Aucune donnée EXIF trouvée Ajouter une balise Sauvegarder Effacer Effacer les EXIF Annuler Toutes les données EXIF de l\'image seront effacées. Cette action ne peut pas être annulée ! Préconfigurations Rogner Enregistrement Toutes les modifications non enregistrées seront perdues si vous quittez maintenant Code source Recevez les dernières mises à jour, discutez des problèmes et plus encore Édition Unique Modifier, redimensionner et éditer une image Sélecteur de couleurs Sélectionnez une couleur dans une image, la copiez ou partagez Image Couleur Couleur copiée Rogner l\'image aux dimensions voulues Version Conserver EXIF Images : %d Modifier l\'aperçu Retirer Générer un échantillon de palette de couleurs à partir d\'une image donnée Générer des Palettes Palette Nouvelle version %1$s Mise à jour Type non pris en charge: %1$s Impossible de générer une palette pour l\'image donnée Original Dossier de sortie Defaut Personnalisé Non précisé Stockage de l\'appareil Redimensionner en fonction du Poids Taille maximale en ko Redimensionner une image suivant une taille donnée en KB Comparer Comparer deux images données Choisissez deux images pour commencer Choisir des images Paramètres Mode nuit Sombre Système Couleurs dynamiques Personnalisation Langue Permettre la monétisation des images Lumière Si cette option est activée, lorsque vous choisissez une image à modifier, les couleurs de l\'application seront adoptées pour cette image. Mode sombre Si la couleur des surfaces est activée, la couleur sera définie sur l’obscurité absolue en mode nuit Rouge Vert Bleu Couleurs Rien à coller Collez un code couleur aRGB valide Le jeu de couleurs de l\'application ne peut pas être modifié lorsque les couleurs dynamiques sont activées Le thème de l’application sera basé sur la couleur sélectionnée À propos de l\'application Aucune mise à jour trouvée Traqueur d\'incidents Envoyez les rapports de bugs et les demandes de fonctionnalités ici Aider à traduire Rechercher ici Corriger les erreurs de traduction ou localiser le projet dans une autre langue Rien n\'a été trouvé lors de votre recherche Si cette option est activée, les couleurs de l’application seront adoptées pour les couleurs du fond d’écran. Echec de l’enregistrement des images %d Surface Cette application est entièrement gratuite, mais si vous souhaitez soutenir le développement du projet, vous pouvez cliquer ici Primaire L\'application a besoin d\'accéder à votre espace de stockage pour enregistrer des images, cela est nécessaire. Veuillez accorder l\'autorisation dans la boîte de dialogue suivante. Stockage externe Alignement du bouton d\'action flottant Vérifier les mises à jour Si activé, la boîte de dialogue de mise à jour vous sera affichée au démarrage de l\'application Valeurs Ajouter Tertiaire Secondaire Autorisation Accorder Zoom sur les images L\'application a besoin de cette autorisation pour fonctionner, veuillez l\'accorder manuellement Épaisseur de la bordure Partager Couleurs de Monet Préfixe Nom du fichier Emoji Sélectionnez les émojis à afficher sur l\'écran principal Ajouter la taille du fichier Si activé, ajoute la largeur et la hauteur de l\'image enregistrée au nom du fichier de sortie Supprimer EXIF Supprimer les métadonnées EXIF de tout ensemble d\'images Aperçu de l\'Image Aperçu de tout type d’images : GIF, SVG, et ainsi de suite Sélecteur moderne de photos Android apparaissant en bas de l\'écran, ne fonctionne que sur Android 12+.A des problèmes de réception des métadonnées EXIF Utiliser l\'intent GetContent pour sélectionner l\'image. Fonctionne partout, mais celui-ci est connu pour avoir des difficultés à recevoir les images sélectionnées sur certains appareils. Cela n\'est pas de ma faute. Source de l\'image Sélecteur de photos Galerie Explorateur de fichiers Simple sélecteur d’image de galerie. Il ne fonctionnera que si vous avez une application qui permet la sélection d\'image Modifier Commander Disposition des options Détermine l’ordre des outils sur l’écran principal Nombre d’émojis Ajouter le nom de fichier original Si activé, ajoute le nom de fichier d\'origine au nom de l\'image de sortie Si cette option est activée, l’horodatage de Standard remplace le numéro de séquence d’image si vous utilisez le traitement par lots Numéro de séquence Fichier original Ajouter un numéro de séquence Ajouter le nom de fichier d\'origine ne fonctionne pas si la source d\'image du sélecteur de photo est sélectionnée. Charger une Image depuis Internet Charger n\'importe quelle image de l\'Internet pour la prévisualiser, zoomer, la modifier ou la sauvegarder si vous le souhaitez. Aucune image Lien de l\'image Ajuster Remplissage Échelle de contenu Redimensionne les images à la hauteur et à la largeur données. Le rapport hauteur/largeur des images peut changer. Redimensionne les images avec un côté long à la hauteur ou à la largeur donnée. Tous les calculs de taille seront effectués après l\'enregistrement. Le rapport hauteur/largeur des images sera préservé. Contraste Teinte Ajouter un filtre Filtre Filtres Lumière Filtre de couleur Luminosité Saturation Appliquer des filtres aux images Alpha Balance des blancs Température Teinte Monochrome Points saillants et ombres Exposition Gamma Ombres Flou Effet Distance Affiner Sepia Negative Solariser Vibe Points Saillants Pente Noir et Blanc Espacement Largeur de ligne Bord de sobel Flou Hachure Demi-teinte En relief Laplacien Vignette Commencer Fin Espace colorimétrique CGA Flou Gaussien Flou encadré Flou bilatéral Lissage de Kuwahara Rayon Échelle Flou lent Angle Tourbillon Distorsion Dilatation Réfraction de la sphère Réfraction de la sphère en verre Matrice de couleur Bosse indice de réfraction Opacité Redimensionner avec des limites Redimensionner les images à la hauteur et à la largeur données tout en conservant le rapport hauteur/largeur Esquisse Seuil Niveaux de quantification Lisse cartoon Dessin animé Postériser Consultation Inclusion de pixels faibles Suppression de non maximum Convolution 3x3 Couleur fausse Seconde couleur Filtre RGB Première couleur Réorganiser Flou rapide Taille floue Centre flou y Zoom flou Balance de couleur Seuil de luminance Flou centre x Dessiner Vous avez désactivé l\'application Fichiers, activez-la pour utiliser cette fonctionnalité Dessinez sur l\'image comme dans un carnet de croquis, ou dessinez sur l\'arrière-plan lui-même Peinture alpha Dessiner sur l’image Dessiner sur fond uni Choisissez la couleur de fond et dessinez dessus Couleur d\'arrière-plan Couleur de peinture Choisissez une image et dessinez quelque chose dessus Crypter Décrypter Choisir le fichier pour commencer Décryptage Cryptage Clé Le dossier a été traité Fonctionnalités Exécution Compatibilité AES-256, mode GCM, pas de rembourrage, IV aléatoires de 12 octets par défaut. Vous pouvez sélectionner l\'algorithme nécessaire. Les clés sont utilisées comme hachages SHA-3 de 256 bits. Taille du fichier Choisir un dossier Chiffrement Stockez ce fichier sur votre appareil ou utilisez l’action partage pour le mettre où vous voulez Chiffrer et déchiffrer n\'importe quel fichier (pas seulement l\'image) en fonction de divers algorithmes de chiffrement disponibles Chiffrement par mot de passe des fichiers. Les fichiers traités peuvent être stockés dans le répertoire sélectionné ou partagés. Les fichiers déchiffrés peuvent également être ouverts directement. Veuillez noter que la compatibilité avec d’autres logiciels ou services de chiffrement de fichiers n’est pas garantie. Un traitement de clé ou une configuration de chiffrement légèrement différente peuvent causer une incompatibilité. La taille maximale du fichier est limitée par le système d\'exploitation Android et la mémoire disponible, qui dépendent de votre appareil. \nAttention : la mémoire n\'est pas du stockage. Mot de passe invalide ou fichier choisi non chiffré Essayer d’enregistrer une image avec ces largeur et hauteur peut provoquer une erreur de mémoire insuffisante. Faites ceci à vos propres risques. Cache Taille du cache Trouvé %1$s Effacement automatique du cache Si activé, le cache de l\'application sera effacé au démarrage de l’application Créer Outils Grouper les options par type Regroupe les options sur l\'écran principal par type au lieu d\'une disposition de liste personnalisée Impossible de modifier la disposition lorsque le groupement d\'options est activé Éditer capture d\'écran Capture d\'écran Personnalisation secondaire Option de secours Copier Passer L’enregistrement en mode %1$s peut être instable, car c\'est un format sans perte Si vous avez sélectionné le préréglage 125, l\'image sera enregistrée avec une taille de 125% de l\'image originale. Si vous sélectionnez le préréglage 50, l\'image sera enregistrée avec une taille de 50% Le préréglage ici détermine le % du fichier de sortie, c\'est-à-dire que si vous sélectionnez le préréglage 50 sur une image de 5 Mo, vous obtiendrez une image de 2,5 Mo après l\'enregistrement Enregistré dans le dossier %1$s avec le nom %2$s Nom de fichier aléatoire Si activé, le nom du fichier de sortie sera entièrement aléatoire Enregistré dans le dossier %1$s Discussion Telegram Discutez de l\'application et obtenez les commentaires des autres utilisateurs. Vous pouvez également y obtenir des mises à jour bêta et des informations. Ratio d\'aspect Sauvegarde et restauration Sauvegarde Restaurer les paramètres de l\'application à partir du fichier généré précédemment Fichier corrompu ou pas de sauvegarde Paramètres restaurés avec succès Contactez moi Masque de recadrage Utilisez ce type de masque pour créer un masque à partir d’une image donnée, notez qu’il DEVRAIT avoir un canal alpha Restaurer Sauvegardez vos paramètres d\'application dans un fichier Cela ramènera vos paramètres aux valeurs par défaut. Notez que cela ne peut pas être annulé sans un fichier de sauvegarde mentionné ci-dessus. Supprimer Vous êtes sur le point de supprimer le jeu de couleurs sélectionné. Cette opération ne peut pas être annulée Supprimer le schéma Police Texte Taille de la Police Défaut L’utilisation de grandes échelles de police peut provoquer des erreurs d’interface utilisateur ainsi que d\'autres problèmes qui ne seront pas corrigés. A utiliser avec précaution. Aa Àà Ââ Ææ Bb Cc Çç Dd Ee Éé Èè Êê Ëë Ff Gg Hh Ii Îî Ïï Jj Kk Ll Mm Nn Oo Ôô Œœ Pp Qq Rr Ss Tt Uu Ùù Ûû Üü Vv Ww Xx Yy Zz 0123456789 !? Emotions Nourriture et Boisson Nature et animaux Objets Symboles Activer les emoji Voyages et lieux Activités Suppresseur d\'arrière-plan Supprimer l’arrière-plan de l’image par dessin ou utiliser l’option Auto Couper l’image Les métadonnées de l\'image originale seront conservées L\'espace transparent autour de l\'image sera coupé Effacement automatique de l\'arrière-plan Restaurer l\'image Mode Effacer Effacer l\'arrière-plan Rayon de flou Pipette Restaurer l\'arrière-plan Mode Dessin Oups… Quelque chose s\'est mal passé. Vous pouvez m\'écrire en utilisant les options ci-dessous et j\'essaierai de trouver une solution Modifiez la taille des images données ou convertissez-les dans d\'autres formats. Les métadonnées EXIF peuvent également être modifiées ici si une seule image est sélectionnée. Redimensionner et Convertir Créer un problème Nombre maximal de couleurs Permet à l\'application de collecter les rapports d\'erreur automatiquement Analyse Autoriser la collecte de données statistiques anonymes sur l\'utilisation de l\'application Actuellement, le format %1$s permet uniquement de lire les métadonnées EXIF sur Android. L\'image de sortie n\'aura aucune métadonnée, lorsque sauvegardée. Patientez Une valeur de %1$s signifie une compression rapide, ce qui entraîne une taille de fichier relativement importante. %2$s signifie une compression plus lente, ce qui donne un fichier plus petit. La sauvegarde est presque terminée. Annuler maintenant nécessitera de sauvegarder à nouveau. Travail Mises à jour Autoriser les versions bêta La vérification des mises à jour inclura les versions bêta de l\'application si activé Douceur du pinceau Dessiner des flèches Si activé, le chemin de dessin sera représenté par une flèche pointante Les images seront recadrées de façon centrée d\'après la taille saisie. Les bordures seront agrandies avec la couleur d\'arrière-plan choisie si l\'image est plus petite que les dimensions saisies. Don Assemblage d\'images Combinez les images choisies pour en obtenir une grande horizontale Ordre des images Choisissez au moins 2 images Les petites images seront mises à l\'échelle à la plus grande de la séquence si cette option est activée. Orientation des images Verticale Échelle de l\'image de sortie Agrandir les petites images Bords flous Pixelisation Dessine des bords flous sous l\'image d\'origine pour remplir les espaces autour d\'elle au lieu d\'une seule couleur si cette option est activée Regulié Tolérance Pixelation améliorée Couleur cible Remplacer la couleur Pixelation au diamant Supprimer la couleur Pixelation Diamond améliorée Pixelisation du cercle Couleur à supprimer Couleur à remplacer Pixellisation de la course Pixelisation de cercle améliorée Recoder Dimensions de Pixel Verrouiller l\'orientation du dessin Vérifier les mises à jour Si activé en mode dessin, l\'écran ne rotationnera pas Fidélité Arc-en-ciel Un thème ludique - la teinte de la couleur source n\'apparaît pas dans le thème Un thème fort, la couleur est maximale pour la palette primaire, augmentée pour les autres Un style légèrement plus chromatique que monochrome Un schéma qui place la couleur source dans Scheme.primaryContainer Accent tonique Un thème monochrome, les couleurs sont purement noir/blanc/gris Salade de fruits Un schéma très similaire au schéma de contenu Contenu Expression Neutre Style de palette par défaut, il permet de personnaliser les quatre couleurs, d\'autres vous permettent de définir uniquement la couleur clé Style de palette Vive Les deux Remplace les couleurs du thème par des couleurs négatives si elles sont activées Bords décolorés Outils PDF Ce vérificateur de mise à jour se connectera à GitHub pour vérifier si une nouvelle mise à jour est disponible Chercher Images en PDF Aperçu du PDF Permet de rechercher parmi toutes les outils disponibles sur l\'écran principal Désactiver PDF en images Emballer les images données dans le fichier PDF de sortie Aperçu PDF simple Inverser les couleurs Fonctionner avec des fichiers PDF : prévisualiser, convertir en lot d\'images ou en créer une à partir d\'images données Precison Convertir un PDF en images dans un format de sortie donné Filtre de masque Appliquez des chaînes de filtres sur des zones masquées données, chaque zone de masque peut déterminer son propre ensemble de filtres Aperçu du Masque Masques Couleur du masque Ajouter un masque Le masque de filtre dessiné sera rendu pour vous montrer le résultat approximatif Masque %d Néon Supprimer le masque Type de Remplissage Inverse Vous êtes sur le point de supprimer le masque filtre sélectionné. Cette opération est irréversible Application de toute chaîne de filtres aux images sélectionnées ou individuellement Si activé toutes les zones non-masquées seront filtrées au lieu du comportement par défaut Début Surligneur Variantes Simples Filtre Complet Fin Centre Nombre de lignes Flèche à double ligne Ajoutez un effet lumineux à vos dessins Mode point Dessiner une ombres derrière les commutateurs Dessine une flèche à double pointage du point de départ au point final sous forme de ligne FAB Dessine une flèche pointant à partir d\'un chemin donné Rotation automatique Dessin gratuit Vibration Grille horizontale Ovale décrit Afin d\'écraser les fichiers, vous devez utiliser la source d\'image \"Explorateur\", essayez de re-sélectionner les images, nous avons remplacé la source d\'image par celle nécessaire Stylo Doubler Ajoute automatiquement l\'image enregistrée au presse-papiers si activé Aucun répertoire \"%1$s\" trouvé, nous l\'avons remplacé par celui par défaut, veuillez enregistrer à nouveau le fichier Grille verticale Curseurs Dessiner une ombre derrière les conteneurs ovale Rectifier Nombre de colonnes Presse-papiers Dessine le rectangle décrit du point de départ au point final Permet d\'adopter une boîte de limite pour l\'orientation de l\'image Lasso L\'image est floue sous le chemin tracé pour sécuriser tout ce que vous souhaitez cacher Celui par défaut, le plus simple : juste la couleur Flèche de ligne Conteneurs Dessine le chemin du point de départ au point final sous forme de ligne Le fichier original sera remplacé par un nouveau au lieu d\'être enregistré dans le dossier sélectionné. Cette option doit être la source de l\'image \"Explorer\" ou GetContent. Lorsque vous activez cette option, elle sera définie automatiquement Résistance aux vibrations Dessine un ovale délimité du point de départ au point final Barres d\'Applications Dessinez des chemins de surligneur aiguisés semi-transparents Dessine un chemin rempli fermé par chemin donné Gratuite Double flèche Flèche Dessine le chemin comme valeur d\'entrée Boutons Écraser les fichiers Dessine le rectangle du point de départ au point final Dessiner une ombre derrière les curseurs Commutateurs Vide Dessiner une ombre derrière les barres d\'application Valeur comprise dans la plage %1$s - %2$s Dessine une flèche pointant du point de départ au point final sous forme de ligne Semblable au flou de confidentialité, mais pixelisé au lieu de flou Suffixe Dessine une double flèche pointée à partir d\'un chemin donné Rect décrit Flou de confidentialité Dessiner une ombre derrière les boutons d\'action flottants Dessine un ovale du point de départ au point final Dessiner une ombre derrière les boutons Épingle automatique Mode Dessiner un chemin Flou de tente Fondu latéral Côté Haut Bas Force Seule colonne Texte clairsemé Texte clairsemé Orientation & Détection de script Ligne brute Problème Montant Graine Anaglyphe Bruit Tri des pixels Mélanger Culminer Catmull Bicubique L\'interpolation linéaire (ou bilinéaire, en deux dimensions) est généralement efficace pour modifier la taille d\'une image, mais provoque un adoucissement indésirable des détails et peut encore être quelque peu irrégulière. Méthode de rééchantillonnage qui maintient une interpolation de haute qualité en appliquant une fonction sinc pondérée aux valeurs des pixels Méthode de rééchantillonnage utilisant un filtre de convolution avec des paramètres réglables pour obtenir un équilibre entre netteté et anticrénelage dans l\'image mise à l\'échelle Utilise des fonctions polynomiales définies par morceaux pour interpoler et approximer en douceur une courbe ou une surface, donnant une représentation de forme flexible et continue Utilise un bouton de type Google Pixel Loupe Active la loupe sur le dessus du doigt dans les modes de dessin pour une meilleure accessibilité Forcer la valeur initiale Force la vérification initiale du widget exif Autoriser plusieurs langues Appuyez sur Transparence Orientation & Détection de script uniquement Orientation automatique & Détection de script Automatique uniquement Auto Texte vertical à un seul bloc Bloc unique Une seule ligne Un seul mot Mot de cercle Caractère unique Souhaitez-vous supprimer les données de formation OCR de langue \"%1$s\" pour tous les types de reconnaissance, ou uniquement pour celui sélectionné (%2$s) ? Actuel Tous Prendre une photo avec l\'appareil photo. Notez qu\'il est possible d\'obtenir une seule image à partir de cette source d\'image. Filigrane Couvrir les images avec des filigranes de texte/image personnalisables Répéter le filigrane Répète le filigrane sur l\'image au lieu d\'un seul à une position donnée Type de filigrane Cette image sera utilisée comme modèle pour le filigrane Remplacer la taille spécifiée par les premières dimensions du cadre Retard de trame millis FPS Mode sécurisé Masque le contenu de l\'application dans les applications récentes. L\'écran ne peut pas non plus être capturé ou enregistré Bayer, quatre par quatre, tramage Floyd Steinberg Tramage Tramage Atkinson Tramage Sierra à deux rangées Faux Floyd Steinberg Dithering Tramage de gauche à droite Tramage aléatoire Utilise des fonctions polynomiales bicubiques définies par morceaux pour interpoler et approximer en douceur une courbe ou une surface, une représentation de forme flexible et continue Flou de pile natif Problème amélioré Changement de chaîne X Décalage de canal Y Taille de la corruption Changement de corruption X Changement de corruption Y Diffusion anisotrope La diffusion Conduction Cristalliser Couleur du trait Verre fractal Amplitude Marbre Fréquence Y AmplitudeX Matrice de couleurs 3x3 Polaroïd Tritanomalie Code Chrome Vision nocturne Chaud Cool Achromatomie Achromatopsie Heure d\'or Été chaud Brume violette Dégradé électrique Caramel Ténèbres Dégradé futuriste Violet foncé Portail spatial Tourbillon rouge Code numérique Forme d\'icône Drago Aldridge Couper Uchimura Mobius Transition Anomalie de couleur Images écrasées à la destination d\'origine Impossible de modifier le format de l\'image lorsque l\'option d\'écrasement des fichiers est activée Emoji comme palette de couleurs Utilise la couleur primaire des emoji comme jeu de couleurs de l\'application au lieu d\'un jeu de couleurs défini manuellement Éroder Décalage horizontal du vent Flou bilatéral rapide Flou de Poisson Cartographie des tons logarithmique Turbulence Huile Effet de l\'eau Taille Fréquence X Amplitude Y Distorsion Perlin Cartographie des tons filmiques Hable Cartographie des tons Heji-Burgess Cartographie des tons filmiques ACES Cartographie des tons ACES Hill Créateur de dégradés Créez un dégradé d\'une taille de sortie donnée avec des couleurs et un type d\'apparence personnalisés Vitesse Déhaze Oméga Noter l\'App Noter Cette application est entièrement gratuite, si vous souhaitez qu\'elle devienne plus grande, veuillez mettre le projet en vedette sur Github 😄 Matrice de couleurs 4x4 Effets simples Deuteranomalie Protanomalie Ancien Browni Tritanopie Deuteranotopie Protanopie Linéaire Radial Balayer Type de dégradé Centre X Centre Y Mode mosaïque Répété Miroir Serrer Décalcomanie Arrêts de couleur Ajouter de la couleur Propriétés Tramage Quantificateur Échelle de gris Bayer, tramage deux par deux Bayer, tramage trois par trois Bayer huit par huit tramage Jarvis Judice Ninke Dithering Tramage Sierra Tramage Sierra Lite Tramage bloqué Burkes tramage Dithering à seuil simple Mode échelle Bilinéaire Hann Hermite Lanczos Mitchell La plus proche Spline Basique Valeur par défaut Sigma Sigma spatial Flou médian E-mail Les meilleures méthodes de mise à l\'échelle incluent le rééchantillonnage de Lanczos et les filtres Mitchell-Netravali L\'un des moyens les plus simples d\'augmenter la taille, en remplaçant chaque pixel par un certain nombre de pixels de la même couleur. Mode de mise à l\'échelle Android le plus simple utilisé dans presque toutes les applications Méthode d\'interpolation et de rééchantillonnage en douceur d\'un ensemble de points de contrôle, couramment utilisée en infographie pour créer des courbes douces Fonction de fenêtrage souvent appliquée dans le traitement du signal pour minimiser les fuites spectrales et améliorer la précision de l\'analyse de fréquence en effilant les bords d\'un signal Technique d\'interpolation mathématique qui utilise les valeurs et les dérivées aux extrémités d\'un segment de courbe pour générer une courbe lisse et continue Seulement le clip L\'enregistrement dans le stockage ne sera pas effectué et l\'image sera tentée de être placée uniquement dans le presse-papiers. Ajoute un conteneur avec la forme sélectionnée sous les icônes Application de la luminosité Écran Incrustation en dégradé Composez n\'importe quel dégradé du haut des images données Transformations Caméra Grain Flou Pastel Brume orange Rêve rose Lever du soleil Tourbillon coloré Lumière de printemps douce Tons d\'automne Rêve de lavande Cyberpunk Limonade légère Feu spectral Magie nocturne Paysage fantastique Explosion de couleurs Soleil vert Monde arc-en-ciel Décalage X Décalage Y Couleur du texte Mode superposition Bokeh Outils GIF Convertir des images en image GIF ou extraire des images d\'une image GIF donnée GIF en images Convertir le fichier GIF en lot d\'images Convertir un lot d\'images en fichier GIF Images en GIF Choisissez une image GIF pour commencer Utiliser la taille de la première image Nombre de répétitions Utiliser le lasso Utilise le Lasso comme en mode dessin pour effectuer l\'effacement Aperçu de l\'image originale Alpha Les emojis de la barre d\'application changent aléatoirement Émojis aléatoires Impossible d\'utiliser la sélection aléatoire d\'emojis lorsque les emojis sont désactivés Impossible de sélectionner un emoji lorsque les emojis aléatoires sont activés Vieille télé Mélanger le flou OCR (reconnaître le texte) Reconnaître le texte d\'une image donnée, plus de 120 langues prises en charge L\'image ne contient pas de texte ou l\'application ne l\'a pas trouvé Accuracy: %1$s Type de reconnaissance Rapide Standard Meilleur Pas de données Pour que Tesseract OCR fonctionne correctement, des données d\'entraînement supplémentaires (%1$s) doivent être téléchargées sur votre appareil. \nVoulez-vous télécharger des données %2$s ? Télécharger Pas de connexion, vérifiez et réessayez afin de télécharger des modèles de train Langues téléchargées Langues disponibles Mode de segmentation Le pinceau restaurera l\'arrière-plan au lieu de l\'effacer Utiliser le commutateur Pixel Glisser Cote à cote Fichier écrasé portant le nom %1$s à la destination d\'origine Préféré Aucun filtre favori ajouté pour l\'instant Cannelure B Inclinaison Confettis Les confettis seront affichés lors de la sauvegarde, du partage et d\'autres actions principales Sortie Si vous quittez l\'aperçu maintenant, vous devrez à nouveau ajouter les images Format d\'image Crée la palette \"Material You\" à partir de l\'image Couleurs Sombres Utilise la palette de couleurs du mode nuit au lieu de la variante lumineuse Copier en tant que code \" Jetpack Compose” Flou d\'anneau Flou Croisé Flou Circulaire Flou d\'Étoile Tilt-Shift linéaire Tags à supprimer Convertir un lot d\'images en fichier APNG Images en APNG Outils APNG Convertir des images en image APNG ou extraire des images d\'une image APNG donnée APNG en images Convertir le fichier APNG en lot d\'images Flou de mouvement Choisissez l\'image APNG pour commencer Créer un fichier Zip à partir de fichiers ou d\'images donnés Zip Largeur de la poignée de déplacement Type de confetti Célébration Exploser Pluie Coins Outils JXL Effectuer le transcodage JXL ~ JPEG sans perte de qualité, ou convertir des GIF/APNG en animation JXL Effectuez un transcodage sans perte de JXL vers JPEG Effectuez un transcodage sans perte de JPEG vers JXL JPEG en JXL Choisissez l\'image JXL pour commencer Flou gaussien rapide 2D Flou gaussien rapide modèle 3D Flou gaussien rapide 4D JXL en JPEG Méthode de ré-échantillonnage qui conserve une haute qualité d\'interpolation en appliquant une fontion Bessel (jinc) aux valeurs de pixels GIF en JXL Converti les images APNG en images animées JXL Converti une animation JXL en un lot d\'images Converti un lot d\'images en animation JXL Comportement Si possible, le sélecteur de fichiers sera affiché immédiatement sur l\'écran choisi Générer les Aperçus Classement Largeur de ligne par défaut Aujourd\'hui Hier Converti les images GIF en images JXL animées APNG vers JXL JXL en Images Images en JXL Activer la génération d\'aperçu, ce qui peut permettre d\'éviter des crashs sur certains appareils. Cela désactive quelques fonctions d\'édition dans la fonction d\'édition simple Type de compression Date Date (inversé) Nom Nom (inversé) Harmonisation de la Couleur Configuration des Canaux Utilisez le sélecteur d\'Image Toolbox Sélectionner plusieurs médias Réessayer Afficher les Paramètres en Mode Paysage Paramètres du plein écran Sélectionner un média Sélectionner Max Ancre de redimensionnement Pixel Fluent Sélecteur intégré Pas de permissions Requête Réseau LSTM Collage automatique Permettez à l\'application de coller automatiquement les données depuis le presse-papiers, elles vont apparaître sur l\'écran principal et vous pourrez les éditer. Passer la Sélection du Fichier Compression avec Pertes Utilise la compression avec perte pour réduire la taille du fichier au lieu de la compression sans perte Images en SVG Trace les images fournies en images SVG Ratio colorimétrique minimum Échelle du chemin Réinitialiser les propriétés Toutes les propriétés vont être réinitialisés aux valeurs par défaut, notez que cette action est irréversible Détaillé.e L’utilisation de cet outil pour tracer de grandes images sans réduction d’échelle n’est pas recommandée, il peut provoquer un crash et augmenter le temps de traitement Utiliser la palette échantillonnée Activez-le et la page de paramètres sera toujours ouverte en plein écran au lieu de la fenêtre coulissante Type de bouton à bascule Composer Niveau d\'harmonisation Convertir L’image sera réduite à des dimensions inférieures avant le traitement, ce qui aide l’outil à travailler plus rapidement et en toute sécurité Image réduite Seuil des lignes Seuil quadratique Tolérance d’arrondissement des coordonnées Ajouter un Nouveau Dossier Compression Date Heure Description de l\'Image Artiste Droit d\'auteur Contrôle la vitesse de décodage de l’image résultante, cela devrait aider à ouvrir l’image résultante plus rapidement, la valeur de %1$s signifie le décodage le plus lent, tandis que %2$s - le plus rapide, ce paramètre peut augmenter la taille de l’image de sortie La palette de quantification sera échantillonnée si cette option est activée. Conversion de lots d\'images au format donné Temps Sub Sec original Réponse en fréquence spatiale Si cette option est désactivée, en mode paysage, les paramètres s\'ouvriront comme toujours sur le bouton de la barre d\'applications supérieure, au lieu de l\'option visible permanente. Bits par échantillon Interprétation photométrique Échantillons par pixel Configuration planaire Y Cb Cr Sous-échantillonage Y Cb Cr Positionnement Résolution X Résolution Y Unité de résolution Décalages de bande Lignes par bande Nombre d\'octets de la bande Format d\'échange JPEG Longueur du format d\'échange JPEG Fonction de transfert Point blanc Chromatismes primaires Y Cb Cr Coefficients Référence Noir Blanc Créer Modèle Logiciel Version Exif Version Flashpix Espace couleur Gamma Pixel X Dimension Pixel Y Dimension Bits compressés par pixel Note du fabricant Commentaire de l\'utilisateur Fichier son associé Date Heure Original Date Heure Numérisée Temps de décalage Temps de décalage Original Temps de décalage numérisé Temps Sub Sec Temps Sub Sec numérisé Temps d\'exposition Nombre F Programme d\'exposition Sensibilité spectrale Sensibilité photographique OECF Type de sensibilité Sensibilité de sortie standard Indice d\'exposition recommandé Vitesse ISO Vitesse ISO Latitude yyy Vitesse ISO Latitude zzz Vitesse d\'obturation Valeur d\'ouverture Valeur de luminosité Valeur du biais d\'exposition Valeur d\'ouverture maximale Distance du sujet Mode de mesure Flash Aire du sujet Longueur focale Énergie flash Plan focal X Résolution Plan focal Y Résolution Unité de résolution du plan focal Localisation du sujet Indice d\'exposition Méthode de détection Source du fichier Motif CFA Rendu personnalisé Mode d\'exposition Balance Blanche Digital Zoom Ratio Un Material que vous utilisez Le Jetpack Compose Material que vous utilisez Cupertino Utilise le systeme de design \"Fluent\" Utilise le système de design dit de \"Cupertino\" Légende Mode moteur Légende & LSTM Lanczos Bessel Chemin d\'accès Omit Longueur focale en équivalent 35 mm Type de capture de scène Contrôle de gain Contraste Saturation Netteté Description des paramètres de l\'appareil Plage de distance du sujet Affiche une grille de soutien au-dessus de la zone de dessin pour aider à des manipulations précises Flou de zoom amélioré Laplacien simple Sobel simple Grille d\'aide Couleur de la grille Largeur de cellule Hauteur de cellule Titre de l\'écran principal Sélecteurs compacts Certains contrôles de sélection utiliseront un agencement compact pour prendre moins de place Accordez la permission d\'accès à la caméra dans les paramètres pour capturer une image Mise en page Bibliothèque LUT Téléchargez une collection de LUTs que vous pouvez appliquer après le téléchargement Mettez à jour la collection de LUTs (seules les nouvelles seront mises en file d\'attente), que vous pouvez appliquer après le téléchargement Identifiant unique d\'image Nom du propriétaire de caméra Spécification de lentils Numéro de Série du Corps Modèle de lentille Créer un objectif Complémentaire Triangle Tons Ombrages LUT HDR Liens Étoile Linéaire Modèle Milieu Kodak Café Auto Octaves Gain Histogramme Masque ISO Cubique Fréquence Polygone Importer Exporter Position Numéro de Série de l\'Objectif ID de la version du GPS Latitude du GPS Découpage d\'images Découper une partie de l\'image et fusionner les parties restantes (peut être inversé) par des lignes verticales ou horizontales Bordure supérieure du capteur Bordure gauche du capteur Altitude GPS de référence Altitude GPS Vitesse GPS de référence Longitude GPS Latitude GPS de référence Longitude GPS de référence Horodatage GPS Satellites GPS Statut GPS Mode de mesure GPS Vitesse GPS Index d\'interopérabilité Version DNG Bordure inférieure du capteur Bordure droite du capteur Dessiner le texte sur le chemin avec la police et la couleur sélectionnés Taille de la police Taille du filigrane Taille de rognage par défaut Longueur de l\'image de prévisualisation GPS DOP GPS Track Ref GPS Track GPS Image Direction Reference GPS Image direction GPS date de la carte GPS Dest Latitude Référence GPS Dest Latitude GPS Dest Longitude Référence GPS Dest Longitude GPS Dest Bearing Référence GPS Dest Bearing GPS Dest Distance Référence GPS Dest Distance GPS Processing Methode GPS information de la zone Date du GPS Différentiel GPS GPS erreur de positionnement H Démarrage de la prévisualisation de l\'image Aspect du cadre Répétez le texte Le texte actuel sera répété jusqu\'à la fin du chemin au lieu d\'être dessiné une seule fois Ouvrir l\'édition au lieu de la prévisualisation Quand vous ouvrez une image avec ImageToolbox, le panneau d\'édition sera visible au lieu de celui de prévisualisation Numériseur de document Numérisez des documents et enregistrez-les en fichiers PDF ou en images Cliquez pour commencer la numérisation Commencer la numérisation Enregistrer en PDF Partager en PDF Les options ci-dessous sont utilisées lors d\'un enregistrement en image et non en PDF Couleur à ignorer Créer un nouveau Créer un modèle Nom du modèle En tant que fichier Enregistrer en tant que fichier Enregistrer en tant qu\'image QR code Supprimer le modèle Ajouter une image Empilement d\'images Empiler les images les unes au dessus des autres avec le fondu sélectionné En haut à gauche En haut à droite En bas à gauche En bas à droite En haut au milieu Au centre à droite En bas au centre Au centre à gauche Image cible Aucune option favorite sélectionnée, ajoutez-les dans la page d\'outils Ajouter des favoris Analogue Tintes Mélange des couleurs Information sur les couleurs Taille du tiret Utiliser l\'image sélectionnée pour la dessiner le long du chemin donné Cette image sera utilisée comme entrée répétitive du chemin tracé Dessine le triangle décrit du point de départ au point final Dessine le triangle décrit du point de départ au point final Triangle décrit Dessine un polygone du point de départ au point final Polygone décrit Dessine le polygone décrit du point de départ au point final Sommets Dessiner un polygone régulier Dessinez un polygone qui sera régulier au lieu de forme libre Dessine l\'étoile du point de départ au point final Étoile décrite Dessine l\'étoile décrite du point de départ au point final Rapport de rayon intérieur Dessiner une étoile régulière Dessinez une étoile qui sera régulière au lieu de forme libre Anticrénelage Permet l\'anticrénelage pour éviter les bords tranchants Égaliser l\'histogramme HSV Égaliser l\'histogramme Entrez le pourcentage Autoriser la saisie par champ de texte Active le champ de texte derrière la sélection des préréglages, pour les saisir à la volée Espace colorimétrique à l\'échelle Égaliser la pixellisation de l\'histogramme Taille de la grille X Taille de grille Y Égaliser l\'histogramme adaptatif Égaliser le LUV adaptatif de l\'histogramme Égaliser l\'histogramme LAB adaptatif CLAHÉ LABORATOIRE CLAHE CLAHÉ LUV Recadrer au contenu Couleur du cadre Aucun filtre de modèle ajouté Le code QR scanné n\'est pas un modèle de filtre valide Scanner le code QR Le fichier sélectionné ne contient aucune donnée de modèle de filtre Cette image sera utilisée pour prévisualiser ce modèle de filtre Filtre de modèle Comme image de code QR Vous êtes sur le point de supprimer le filtre de modèle sélectionné. Cette opération est irréversible Modèle de filtre ajouté avec le nom \"%1$s\" (%2$s) Aperçu du filtre QR et code-barres Scannez le code QR et obtenez son contenu ou collez votre chaîne pour en générer une nouvelle Contenu du code Scannez n\'importe quel code-barres pour remplacer le contenu dans le champ, ou tapez quelque chose pour générer un nouveau code-barres avec le type sélectionné QR descriptif Min. Accorder l\'autorisation à la caméra dans les paramètres pour scanner le code QR Accorder l\'autorisation à l\'appareil photo dans les paramètres pour numériser le scanner de documents B-Spline Hamming Hanning Homme noir Welch Quadrique Gaussien Sphinx Bartlett Robidoux Robidoux Sharp Cannelure 16 Cannelure 36 Cannelure 64 kaiser Bartlett-Il Boîte Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc L\'interpolation cubique permet une mise à l\'échelle plus fluide en prenant en compte les 16 pixels les plus proches, ce qui donne de meilleurs résultats que l\'interpolation bilinéaire. Utilise des fonctions polynomiales définies par morceaux pour interpoler et approximer en douceur une courbe ou une surface, une représentation de forme flexible et continue Une fonction de fenêtre utilisée pour réduire les fuites spectrales en effilant les bords d\'un signal, utile dans le traitement du signal Une variante de la fenêtre de Hann, couramment utilisée pour réduire les fuites spectrales dans les applications de traitement du signal Une fonction de fenêtre qui offre une bonne résolution en fréquence en minimisant les fuites spectrales, souvent utilisée dans le traitement du signal Une fonction de fenêtre conçue pour offrir une bonne résolution en fréquence avec une fuite spectrale réduite, souvent utilisée dans les applications de traitement du signal Une méthode qui utilise une fonction quadratique pour l\'interpolation, fournissant des résultats fluides et continus Une méthode d\'interpolation qui applique une fonction gaussienne, utile pour lisser et réduire le bruit dans les images Une méthode de rééchantillonnage avancée offrant une interpolation de haute qualité avec un minimum d\'artefacts Une fonction de fenêtre triangulaire utilisée dans le traitement du signal pour réduire les fuites spectrales Une méthode d\'interpolation de haute qualité optimisée pour le redimensionnement naturel de l\'image, équilibrant la netteté et la douceur Une variante plus nette de la méthode Robidoux, optimisée pour un redimensionnement d\'image net Une méthode d\'interpolation basée sur les splines qui fournit des résultats fluides à l\'aide d\'un filtre à 16 clics Une méthode d\'interpolation basée sur les splines qui fournit des résultats fluides à l\'aide d\'un filtre à 36 clics Une méthode d\'interpolation basée sur les splines qui fournit des résultats fluides à l\'aide d\'un filtre à 64 clics Une méthode d\'interpolation qui utilise la fenêtre Kaiser, offrant un bon contrôle sur le compromis entre la largeur du lobe principal et le niveau des lobes latéraux Une fonction de fenêtre hybride combinant les fenêtres Bartlett et Hann, utilisée pour réduire les fuites spectrales dans le traitement du signal Une méthode de rééchantillonnage simple qui utilise la moyenne des valeurs de pixels les plus proches, ce qui donne souvent une apparence en bloc Une fonction de fenêtre utilisée pour réduire les fuites spectrales, offrant une bonne résolution de fréquence dans les applications de traitement du signal Une méthode de rééchantillonnage qui utilise un filtre Lanczos à 2 lobes pour une interpolation de haute qualité avec un minimum d\'artefacts Une méthode de rééchantillonnage qui utilise un filtre Lanczos à 3 lobes pour une interpolation de haute qualité avec un minimum d\'artefacts Une méthode de rééchantillonnage qui utilise un filtre Lanczos à 4 lobes pour une interpolation de haute qualité avec un minimum d\'artefacts Une variante du filtre Lanczos 2 qui utilise la fonction jinc, offrant une interpolation de haute qualité avec un minimum d\'artefacts Une variante du filtre Lanczos 3 qui utilise la fonction jinc, offrant une interpolation de haute qualité avec un minimum d\'artefacts Une variante du filtre Lanczos 4 qui utilise la fonction jinc, offrant une interpolation de haute qualité avec un minimum d\'artefacts Hanning EWA Variante de moyenne pondérée elliptique (EWA) du filtre Hanning pour une interpolation et un rééchantillonnage en douceur Robidoux EWA Variante Elliptical Weighted Average (EWA) du filtre Robidoux pour un rééchantillonnage de haute qualité Homme noir EVE Variante Elliptical Weighted Average (EWA) du filtre Blackman pour minimiser les artefacts de sonnerie EWA quadrique Variante Elliptical Weighted Average (EWA) du filtre Quadric pour une interpolation fluide Robidoux Sharp EWA Variante Elliptical Weighted Average (EWA) du filtre Robidoux Sharp pour des résultats plus nets Lanczos 3 Jinc EWA Variante Elliptical Weighted Average (EWA) du filtre Lanczos 3 Jinc pour un rééchantillonnage de haute qualité avec un alias réduit Ginseng Un filtre de rééchantillonnage conçu pour un traitement d\'image de haute qualité avec un bon équilibre entre netteté et douceur Ginseng EWA Variante elliptique pondérée moyenne (EWA) du filtre Ginseng pour une qualité d\'image améliorée Lanczos Sharp EWA Variante Elliptical Weighted Average (EWA) du filtre Lanczos Sharp pour obtenir des résultats nets avec un minimum d\'artefacts Lanczos 4 EWA le plus pointu Variante Elliptical Weighted Average (EWA) du filtre Lanczos 4 Sharpest pour un rééchantillonnage d\'image extrêmement net Lanczos Soft EWA Variante Elliptical Weighted Average (EWA) du filtre Lanczos Soft pour un rééchantillonnage d\'image plus fluide Haasn Doux Un filtre de rééchantillonnage conçu par Haasn pour une mise à l\'échelle de l\'image fluide et sans artefacts Conversion de formats Convertir un lot d\'images d\'un format à un autre Rejeter pour toujours Nombre de bacs LGV Clahé Clahé HSV Égaliser l\'histogramme HSL adaptatif Égaliser l\'histogramme HSV adaptatif Mode bord Agrafe Envelopper Daltonisme Sélectionnez le mode pour adapter les couleurs du thème à la variante de daltonisme sélectionnée Difficulté à distinguer les teintes rouges et vertes Difficulté à distinguer les teintes vertes et rouges Difficulté à distinguer les teintes bleues et jaunes Incapacité à percevoir les teintes rouges Incapacité à percevoir les teintes vertes Incapacité à percevoir les teintes bleues Sensibilité réduite à toutes les couleurs Daltonisme complet, ne voyant que des nuances de gris N\'utilisez pas le système daltonien Les couleurs seront exactement telles que définies dans le thème Sigmoïde Lagrange2 Un filtre d\'interpolation Lagrange d\'ordre 2, adapté à une mise à l\'échelle d\'images de haute qualité avec des transitions douces Lagrange3 Un filtre d\'interpolation Lagrange d\'ordre 3, offrant une meilleure précision et des résultats plus fluides pour la mise à l\'échelle des images Lanczos 6 Un filtre de rééchantillonnage Lanczos avec un ordre supérieur de 6, offrant une mise à l\'échelle de l\'image plus nette et plus précise Lanczos 6 Jinc Une variante du filtre Lanczos 6 utilisant une fonction Jinc pour une qualité de rééchantillonnage d\'image améliorée Flou de boîte linéaire Flou de tente linéaire Flou de boîte gaussien linéaire Flou de pile linéaire Flou de boîte gaussien Flou gaussien rapide linéaire Suivant Flou gaussien rapide linéaire Flou gaussien linéaire Choisissez un filtre pour l\'utiliser comme peinture Remplacer le filtre Choisissez le filtre ci-dessous pour l\'utiliser comme pinceau dans votre dessin Schéma de compression TIFF Faible poly Peinture sur sable Fractionnement d\'image Diviser une seule image en lignes ou en colonnes Ajuster aux limites Combinez le mode de redimensionnement du recadrage avec ce paramètre pour obtenir le comportement souhaité (Recadrage/Ajustement au rapport hauteur/largeur) Langues importées avec succès Modèles OCR de sauvegarde Transfert de palettes Huile améliorée Vieille télé simple Gotham Croquis simple Lueur douce Affiche couleur Triton Troisième couleur Clahé Oklab Clara Olch Clahé Jzazbz Pois Dithering 2x2 en cluster Dithering 4x4 en cluster Dithering 8x8 en cluster Yililoma Tramage Triadique Split complémentaire Tétradique Carré Analogue + Complémentaire Outils de couleur Mélangez, créez des tons, générez des nuances et bien plus encore Harmonies de couleurs Ombrage des couleurs Variation Couleur sélectionnée Couleur à mélanger Impossible d\'utiliser Monet lorsque les couleurs dynamiques sont activées 512x512 LUT 2D Image LUT cible Un amateur Miss Étiquette Élégance douce Variante élégance douce Variante de transfert de palette LUT 3D Fichier LUT 3D cible (.cube / .CUBE) Contournement de l\'eau de Javel Aux chandelles Laisser tomber le blues Ambre énervé Couleurs d\'automne Pellicule 50 Nuit brumeuse Obtenir une image LUT neutre Tout d’abord, utilisez votre application de retouche photo préférée pour appliquer un filtre à la LUT neutre que vous pouvez obtenir ici. Pour que cela fonctionne correctement, la couleur de chaque pixel ne doit pas dépendre des autres pixels (par exemple, le flou ne fonctionnera pas). Une fois prêt, utilisez votre nouvelle image LUT comme entrée pour le filtre LUT 512*512 Pop-Art Celluloïd Forêt dorée Verdâtre Jaune rétro Aperçu des liens Permet la récupération de l\'aperçu des liens aux endroits où vous pouvez obtenir du texte (QRCode, OCR, etc.) Les fichiers ICO ne peuvent être enregistrés qu\'à la taille maximale de 256 x 256 GIF en WEBP Convertir des images GIF en images animées WEBP Outils WEBP Convertir des images en image animée WEBP ou extraire des images d\'une animation WEBP donnée WEBP en images Convertir le fichier WEBP en lot d\'images Convertir un lot d\'images en fichier WEBP Images vers WEBP Choisissez l\'image WEBP pour commencer Pas d\'accès complet aux fichiers Autorisez l\'accès à tous les fichiers pour voir JXL, QOI et autres images qui ne sont pas reconnues comme images sur Android. Sans l\'autorisation, Image Toolbox ne peut pas afficher ces images Couleur de dessin par défaut Mode de tracé par défaut Ajouter un horodatage Active l\'ajout d\'horodatage au nom du fichier de sortie Horodatage formaté Activer le formatage de l\'horodatage dans le nom du fichier de sortie au lieu des millis de base Activez les horodatages pour sélectionner leur format Emplacement unique Afficher et modifier les emplacements de sauvegarde uniques que vous pouvez utiliser en appuyant longuement sur le bouton Enregistrer dans la plupart des options Récemment utilisé canal CI Groupe Boîte à outils d\'images dans Telegram 🎉 Rejoignez notre chat où vous pourrez discuter de tout ce que vous voulez et également consulter la chaîne CI où je publie des versions bêta et des annonces. Soyez informé des nouvelles versions de l\'application et lisez les annonces Ajuster une image aux dimensions données et appliquer un flou ou une couleur à l\'arrière-plan Disposition des outils Regrouper les outils par type Regroupe les outils sur l\'écran principal par leur type au lieu d\'une disposition de liste personnalisée Valeurs par défaut Visibilité des barres système Afficher les barres système par balayage Permet de faire glisser pour afficher les barres système si elles sont masquées Masquer tout Afficher tout Masquer la barre de navigation Masquer la barre d\'état Génération de bruit Générez différents bruits comme Perlin ou d\'autres types Type de bruit Type de rotation Type fractal Lacunarité Force pondérée Force du ping-pong Fonction de distance Type de retour Gigue Déformation de domaine Alignement Nom de fichier personnalisé Sélectionnez l\'emplacement et le nom de fichier qui seront utilisés pour enregistrer l\'image actuelle Enregistré dans un dossier avec un nom personnalisé Créateur de collages Créez des collages à partir de 20 images maximum Type de collage Maintenez l\'image pour échanger, déplacer et zoomer pour ajuster la position Désactiver la rotation Empêche la rotation des images avec des gestes à deux doigts Activer l\'alignement sur les bordures Après un déplacement ou un zoom, les images s\'aligneront pour remplir les bords du cadre Histogramme d\'image RVB ou luminosité pour vous aider à effectuer des ajustements Cette image sera utilisée pour générer des histogrammes RVB et Luminosité Options de tesseract Appliquer quelques variables d\'entrée pour le moteur tesseract Options personnalisées Les options doivent être saisies en suivant ce modèle : \"--{option_name} {value}\" Recadrage automatique Coins Libres Recadrer l\'image par polygone, cela corrige également la perspective Contraindre les points aux limites de l\'image Les points ne seront pas limités par les limites de l\'image, ce qui est utile pour une correction de perspective plus précise Remplissage sensible au contenu sous le chemin tracé Point de guérison Utiliser le noyau circulaire Ouverture Clôture Dégradé Morphologique Chapeau haut de forme Chapeau noir Courbes de tonalité Réinitialiser les courbes Les courbes seront rétablies à leur valeur par défaut Style de ligne Taille de l\'écart En pointillés Pointillé Timbré Zigzag Dessine une ligne pointillée le long du chemin tracé avec la taille d\'espace spécifiée Dessine des points et des lignes pointillées le long d\'un chemin donné Juste des lignes droites par défaut Dessine les formes sélectionnées le long du chemin avec un espacement spécifié Dessine un zigzag ondulé le long du chemin Rapport de zigzag Créer un raccourci Choisissez l\'outil à épingler L\'outil sera ajouté à l\'écran d\'accueil de votre lanceur en tant que raccourci, utilisez-le en combinaison avec le paramètre « Ignorer la sélection de fichiers » pour obtenir le comportement souhaité. N\'empilez pas les cadres Permet de supprimer les images précédentes, afin qu\'elles ne s\'empilent pas les unes sur les autres Fondu enchaîné Les images seront fondues les unes dans les autres Nombre d’images de fondu enchaîné Seuil un Seuil deux Prudent Miroir 101 Facteur de taux constant (CRF) Une valeur de %1$s signifie une compression lente, ce qui entraîne une taille de fichier relativement petite. %2$s signifie une compression plus rapide, ce qui donne un fichier volumineux. Modifier l\'aperçu de l\'image par défaut pour les filtres Image d\'aperçu Cacher Montrer Type de curseur Fantaisie Matériel 2 Un curseur au look sophistiqué. C\'est l\'option par défaut Un curseur Matériau 2 Un curseur Matériau Vous Appliquer Boutons de la boîte de dialogue centrale Les boutons des boîtes de dialogue seront positionnés au centre plutôt qu\'à gauche si possible Licences Open Source Afficher les licences des bibliothèques open source utilisées dans cette application Zone Rééchantillonnage en utilisant la relation entre les zones de pixels. Il s\'agit peut-être d\'une méthode privilégiée pour la décimation d\'images, car elle donne des résultats sans moiré. Mais lorsque l\'image est agrandie, cela ressemble à la méthode \"Nearest\". Activer le mappage de tons Entrer % Impossible d\'accéder au site, essayez d\'utiliser un VPN ou vérifiez si l\'URL est correcte Calques Mode Calques avec possibilité de placer librement des images, du texte et plus encore Modifier le calque Calques sur l\'image Utilisez une image comme arrière-plan et ajoutez différents calques par-dessus Calques en arrière-plan Identique à la première option mais avec de la couleur au lieu de l\'image Bêta Côté réglages rapides Ajoutez une bande flottante sur le côté sélectionné lors de l\'édition des images, qui ouvrira les paramètres rapides lorsque vous cliquerez dessus Effacer la sélection Le groupe de paramètres \"%1$s\" sera réduit par défaut Le groupe de paramètres \"%1$s\" sera étendu par défaut Outils Base64 Décoder la chaîne Base64 en image ou encoder l\'image au format Base64 Base64 La valeur fournie n\'est pas une chaîne Base64 valide Impossible de copier une chaîne Base64 vide ou invalide Coller Base64 Copier Base64 Chargez l\'image pour copier ou enregistrer la chaîne Base64. Si vous avez la chaîne elle-même, vous pouvez la coller ci-dessus pour obtenir l\'image Enregistrer Base64 Partager Base64 Possibilités Actes Importer Base64 Actions Base64 Ajouter un contour Ajouter un contour autour du texte avec une couleur et une largeur spécifiées Couleur du contour Taille du contour Rotation Somme de contrôle comme nom de fichier Les images de sortie auront un nom correspondant à leur somme de contrôle de données Logiciel Libre (Partenaire) Logiciels plus utiles dans le canal partenaire des applications Android Algorithme Outils de somme de contrôle Comparez les sommes de contrôle, calculez les hachages ou créez des chaînes hexadécimales à partir de fichiers en utilisant différents algorithmes de hachage Calculer Hachage de texte Somme de contrôle Choisissez le fichier pour calculer sa somme de contrôle en fonction de l\'algorithme sélectionné Entrez du texte pour calculer sa somme de contrôle en fonction de l\'algorithme sélectionné Somme de contrôle source Somme de contrôle à comparer Correspondre! Différence Les sommes de contrôle sont égales, cela peut être sûr Les sommes de contrôle ne sont pas égales, le fichier peut être dangereux ! Dégradés de maillage Regardez la collection en ligne de dégradés de maillage Seules les polices TTF et OTF peuvent être importées Importer la police (TTF/OTF) Exporter des polices Polices importées Erreur lors de la tentative d\'enregistrement, essayez de modifier le dossier de sortie Le nom du fichier n\'est pas défini Aucun Pages personnalisées Sélection des pages Confirmation de sortie d\'outil Si vous avez des modifications non enregistrées lors de l\'utilisation d\'outils particuliers et essayez de le fermer, une boîte de dialogue de confirmation s\'affichera. Modifier EXIF Modifier les métadonnées d\'une seule image sans recompression Appuyez pour modifier les balises disponibles Changer l\'autocollant Ajuster la largeur Hauteur d\'ajustement Comparaison par lots Choisissez un ou plusieurs fichiers pour calculer sa somme de contrôle en fonction de l\'algorithme sélectionné Choisir des fichiers Choisir un répertoire Échelle de longueur de tête Timbre Horodatage Modèle de formatage Rembourrage Ligne de pivotement vertical Ligne pivot horizontale Sélection inverse La partie coupée verticalement sera conservée au lieu de fusionner les pièces autour de la zone coupée. La partie coupée horizontalement sera conservée au lieu de fusionner les pièces autour de la zone coupée. Collection de dégradés de maillage Créez un dégradé de maillage avec une quantité de nœuds et une résolution personnalisées Superposition de dégradé de maillage Composer un dégradé de maillage du haut des images données Personnalisation des points Taille de la grille Résolution X Résolution Y Résolution Pixel par pixel Couleur de surbrillance Type de comparaison de pixels Scanner le code-barres Rapport de hauteur Type de code-barres Appliquer le N/B L\'image du code-barres sera entièrement en noir et blanc et ne sera pas colorée par le thème de l\'application. Scannez n\'importe quel code-barres (QR, EAN, AZTEC, …) et récupérez son contenu ou collez votre texte pour en générer un nouveau Aucun code-barres trouvé Le code-barres généré sera ici Couvertures audio Extrayez les images de couverture d\'album à partir de fichiers audio, les formats les plus courants sont pris en charge Choisissez l\'audio pour commencer Choisissez l\'audio Aucune couverture trouvée Envoyer des journaux Cliquez pour partager le fichier des journaux d\'application, cela peut m\'aider à repérer le problème et à le résoudre Oups… Quelque chose s\'est mal passé Vous pouvez me contacter en utilisant les options ci-dessous et j\'essaierai de trouver une solution.\n(N\'oubliez pas de joindre les journaux) Écrire dans un fichier Extrayez le texte d\'un lot d\'images et stockez-le dans un seul fichier texte Écrire dans les métadonnées Extrayez le texte de chaque image et placez-le dans les informations EXIF des photos relatives Mode invisible Utilisez la stéganographie pour créer des filigranes invisibles à l\'œil nu dans les octets de vos images Utiliser LSB La méthode de stéganographie LSB (Less Significant Bit) sera utilisée, FD (Frequency Domain) sinon Supprimer automatiquement les yeux rouges Mot de passe Ouvrir Le PDF est protégé Opération presque terminée. Annuler maintenant nécessitera de le redémarrer Date de modification Date de modification (inversée) Taille Taille (inversée) Type MIME Type MIME (inversé) Extension Extension (inversée) Date d\'ajout Date d\'ajout (inversée) De gauche à droite De droite à gauche De haut en bas De bas en haut Verre liquide Un commutateur basé sur IOS 26 récemment annoncé et son système de conception en verre liquide Choisissez une image ou collez/importez des données Base64 ci-dessous Tapez le lien de l\'image pour commencer Coller le lien Kaléidoscope Angle secondaire Côtés Mixage des chaînes Bleu vert Bleu rouge Vert rouge En rouge En vert En bleu Cyan Magenta Jaune Couleur Demi-teinte Contour Niveaux Compenser Voronoï cristallise Forme Extensible Le hasard Détacher Diffuser Chien Deuxième rayon Égaliser Briller Tourbillonner et pincer Pointilliser Couleur de la bordure Coordonnées polaires Rectifier vers polaire Polaire à rectifier Inverser en cercle Réduire le bruit Solarisation simple Tisser Écart X Écart en Y Largeur X Largeur Y Tournoiement Timbre en caoutchouc Frottis Densité Mélanger Distorsion de la lentille sphérique Indice de réfraction Arc Angle de propagation Éclat Rayons ASCII Pente Marie Automne Os Jet Hiver Océan Été Printemps Variante cool HSV Rose Chaud Mot Magma Enfer Plasma Viridis Citoyens Crépuscule Crépuscule décalé Perspective automatique Redressement Autoriser le recadrage Recadrage ou perspective Absolu Turbo Vert profond Correction de l\'objectif Fichier de profil d\'objectif cible au format JSON Téléchargez les profils d\'objectifs prêts à l\'emploi Pourcentages partiels Exporter au format JSON Copier la chaîne avec les données d\'une palette sous forme de représentation json Sculpture de couture Écran d\'accueil Écran de verrouillage Intégré Exportation de fonds d’écran Rafraîchir Obtenez les fonds d\'écran d\'accueil, de verrouillage et intégrés actuels Autoriser l\'accès à tous les fichiers, cela est nécessaire pour récupérer les fonds d\'écran Gérer l\'autorisation de stockage externe ne suffit pas, vous devez autoriser l\'accès à vos images, assurez-vous de sélectionner \"Autoriser tout\" Ajouter un préréglage au nom de fichier Ajoute le suffixe avec le préréglage sélectionné au nom du fichier image Ajouter le mode d\'échelle d\'image au nom de fichier Ajoute le suffixe avec le mode d\'échelle d\'image sélectionné au nom du fichier image Art Ascii Convertir l\'image en texte ascii qui ressemblera à une image Paramètres Applique un filtre négatif à l\'image pour un meilleur résultat dans certains cas Capture d\'écran du traitement Capture d\'écran non capturée, réessayez Enregistrement ignoré %1$s fichiers ignorés Autoriser le saut si la taille est plus grande Certains outils seront autorisés à ignorer l\'enregistrement des images si la taille du fichier résultant est plus grande que l\'original. Événement du calendrier Contact E-mail Emplacement Téléphone Texte SMS URL Wi-Fi Réseau ouvert N / A SSID Téléphone Message Adresse Sujet Corps Nom Organisation Titre Téléphones E-mails URL Adresses Résumé Description Emplacement Organisateur Date de début Date de fin Statut Latitude Longitude Créer un code-barres Edit Barcode Configuration Wi-Fi Sécurité Choisir un contact Accorder aux contacts l\'autorisation dans les paramètres de remplir automatiquement à l\'aide du contact sélectionné Coordonnées Prénom Deuxième prénom Nom de famille Prononciation Ajouter un téléphone Ajouter un e-mail Ajouter une adresse Site web Ajouter un site Web Nom formaté Cette image sera utilisée pour être placée au-dessus du code-barres Personnalisation des codes Cette image sera utilisée comme logo au centre du code QR Logo Rembourrage du logo Taille du logo Coins logotés Quatrième œil Ajoute une symétrie oculaire au code QR en ajoutant un quatrième œil dans le coin inférieur Forme des pixels Forme du cadre Forme de boule Niveau de correction des erreurs Couleur foncée Couleur claire HyperOS Style similaire à Xiaomi HyperOS Modèle de masque Ce code peut ne pas être scannable, modifiez les paramètres d\'apparence pour le rendre lisible sur tous les appareils Non numérisable Les outils ressembleront au lanceur d\'applications sur l\'écran d\'accueil pour être plus compacts Mode lanceur Remplit une zone avec le pinceau et le style sélectionnés Remplissage d\'inondation Pulvérisation Dessine un chemin de style graffiti Particules carrées Les particules pulvérisées auront une forme carrée au lieu de cercles Outils de palettes Générez le matériau de base de votre palette à partir de l\'image, ou importez/exportez dans différents formats de palette Modifier la palette Palette d\'exportation/importation dans différents formats Nom de la couleur Nom de la palette Format des palettes Exporter la palette générée vers différents formats Ajoute une nouvelle couleur à la palette actuelle Le format %1$s ne prend pas en charge la fourniture du nom de la palette En raison des politiques du Play Store, cette fonctionnalité ne peut pas être incluse dans la version actuelle. Pour accéder à cette fonctionnalité, veuillez télécharger ImageToolbox à partir d\'une source alternative. Vous pouvez trouver les versions disponibles sur GitHub ci-dessous. Ouvrir la page Github Le fichier original sera remplacé par un nouveau au lieu d\'être enregistré dans le dossier sélectionné Texte de filigrane caché détecté Image de filigrane cachée détectée Cette image était cachée Inpainting génératif Vous permet de supprimer des objets dans une image à l\'aide d\'un modèle d\'IA, sans recourir à OpenCV. Pour utiliser cette fonctionnalité, l\'application téléchargera le modèle requis (~ 200 Mo) depuis GitHub. Vous permet de supprimer des objets dans une image à l\'aide d\'un modèle d\'IA, sans recourir à OpenCV. Cela pourrait être une opération de longue durée Analyse du niveau d\'erreur Dégradé de luminance Distance moyenne Détection de déplacement de copie Retenir Coefficient Les données du Presse-papiers sont trop volumineuses Les données sont trop volumineuses pour être copiées Pixelisation de tissage simple Pixelisation échelonnée Pixelisation croisée Pixelisation micro-macro Pixelisation orbitale Pixelisation Vortex Pixelisation de la grille d\'impulsions Pixelisation du noyau Pixelisation à tissage radial Impossible d\'ouvrir l\'uri \"%1$s\" Mode chute de neige Activé Cadre de bordure Variante de pépin Changement de chaîne Décalage maximum VHS Bloquer le problème Taille du bloc Courbure du tube cathodique Courbure Chroma Fusion de pixels Chute maximale Outils IA Divers outils pour traiter les images via des modèles IA comme la suppression ou le débruitage d\'artefacts Compression, lignes irrégulières Dessins animés, compression de diffusion Compression générale, bruit général Bruit de dessin animé incolore Rapide, compression générale, bruit général, animation/bande dessinée/anime Numérisation de livres Correction d\'exposition Meilleur en compression générale, images couleur Meilleur en compression générale, images en niveaux de gris Compression générale, images en niveaux de gris, plus forte Bruit général, images couleur Bruit général, images couleur, meilleurs détails Bruit général, images en niveaux de gris Bruit général, images en niveaux de gris, plus fort Bruit général, images en niveaux de gris, le plus fort Compression générale Compression générale Texturation, compression h264 Compression VHS Compression non standard (cinepak, msvideo1, roq) Compression Bink, meilleure sur la géométrie Compression Bink, plus forte Compression Bink, douce, conserve les détails Suppression de l\'effet marche d\'escalier, lissage Art/dessins numérisés, compression légère, moiré Bandes de couleur Lent, suppression des demi-teintes Coloriseur général pour les images en niveaux de gris/bw, pour de meilleurs résultats, utilisez DDColor Suppression des bords Supprime le suraffûtage Lent, tramage Anti-aliasing, artefacts généraux, CGI Traitement des analyses KDM003 Modèle léger d\'amélioration d\'image Suppression des artefacts de compression Suppression des artefacts de compression Retrait du pansement avec des résultats fluides Traitement des motifs en demi-teintes Suppression du motif de tramage V3 Suppression des artefacts JPEG V2 Amélioration de la texture H.264 Affûtage et amélioration VHS Fusion Taille du morceau Taille de chevauchement Les images de plus de %1$s px seront découpées et traitées en morceaux, superposés pour éviter les coutures visibles. Les grandes tailles peuvent provoquer une instabilité avec les appareils bas de gamme Sélectionnez-en un pour commencer Voulez-vous supprimer le modèle %1$s ? Vous devrez le télécharger à nouveau Confirmer Modèles Modèles téléchargés Modèles disponibles Préparation Modèle actif Échec de l\'ouverture de la session Seuls les modèles .onnx/.ort peuvent être importés Modèle d\'importation Importez un modèle onnx personnalisé pour une utilisation ultérieure, seuls les modèles onnx/ort sont acceptés, prend en charge presque toutes les variantes de type esrgan Modèles importés Bruit général, images colorées Bruit général, images colorées, plus fort Bruit général, images colorées, le plus fort Réduit les artefacts de tramage et les bandes de couleurs, améliorant ainsi les dégradés lisses et les zones de couleurs plates. Améliore la luminosité et le contraste de l\'image avec des reflets équilibrés tout en préservant les couleurs naturelles. Éclaircit les images sombres tout en conservant les détails et en évitant la surexposition. Élimine les tons excessifs de couleur et rétablit un équilibre des couleurs plus neutre et naturel. Applique une tonalité de bruit basée sur Poisson en mettant l\'accent sur la préservation des détails et des textures fins. Applique une tonalité douce du bruit de Poisson pour des résultats visuels plus fluides et moins agressifs. Tonalité de bruit uniforme axée sur la préservation des détails et la clarté de l’image. Tonification sonore douce et uniforme pour une texture subtile et un aspect lisse. Répare les zones endommagées ou inégales en repeignant les artefacts et en améliorant la cohérence de l\'image. Modèle de débandage léger qui supprime les bandes de couleur avec un coût de performance minimal. Optimise les images avec des artefacts de compression très élevés (qualité 0-20 %) pour une clarté améliorée. Améliore les images avec des artefacts de compression élevés (qualité de 20 à 40 %), en restaurant les détails et en réduisant le bruit. Améliore les images avec une compression modérée (qualité de 40 à 60 %), en équilibrant la netteté et la douceur. Affine les images avec une légère compression (qualité de 60 à 80 %) pour améliorer les détails et les textures subtiles. Améliore légèrement les images presque sans perte (qualité de 80 à 100 %) tout en préservant l\'aspect et les détails naturels. Colorisation simple et rapide, dessins animés, pas idéal Réduit légèrement le flou de l\'image, améliorant ainsi la netteté sans introduire d\'artefacts. Opérations de longue durée Traitement de l\'image Traitement Supprime les artefacts de compression JPEG importants dans les images de très faible qualité (0-20 %). Réduit les forts artefacts JPEG dans les images hautement compressées (20 à 40 %). Nettoie les artefacts JPEG modérés tout en préservant les détails de l\'image (40 à 60 %). Affine les artefacts JPEG légers dans des images d\'assez haute qualité (60-80%). Réduit subtilement les artefacts JPEG mineurs dans les images presque sans perte (80 à 100 %). Améliore les détails et les textures fins, améliorant ainsi la netteté perçue sans artefacts lourds. Traitement terminé Échec du traitement Améliore les textures et les détails de la peau tout en gardant un aspect naturel, optimisé pour la vitesse. Supprime les artefacts de compression JPEG et restaure la qualité d\'image des photos compressées. Réduit le bruit ISO sur les photos prises dans des conditions de faible luminosité, préservant ainsi les détails. Corrige les reflets surexposés ou « jumbo » et rétablit un meilleur équilibre tonal. Modèle de colorisation léger et rapide qui ajoute des couleurs naturelles aux images en niveaux de gris. DEJPEG Débruit Coloriser Artefacts Améliorer Anime Analyses Haut de gamme Upscaler X4 pour les images générales ; petit modèle qui utilise moins de GPU et de temps, avec un flou et un débruit modérés. Upscaler X2 pour les images générales, préservant les textures et les détails naturels. Upscaler X4 pour des images générales avec des textures améliorées et des résultats réalistes. Upscaler X4 optimisé pour les images animées ; 6 blocs RRDB pour des lignes et des détails plus nets. L\'upscaler X4 avec perte MSE produit des résultats plus fluides et des artefacts réduits pour les images générales. X4 Upscaler optimisé pour les images animées ; Variante 4B32F avec des détails plus nets et des lignes douces. Modèle X4 UltraSharp V2 pour les images générales ; met l\'accent sur la netteté et la clarté. X4 UltraSharp V2 Lite ; plus rapide et plus petit, préserve les détails tout en utilisant moins de mémoire GPU. Modèle léger pour une suppression rapide de l’arrière-plan. Performances et précision équilibrées. Fonctionne avec des portraits, des objets et des scènes. Recommandé pour la plupart des cas d\'utilisation. Supprimer la glycémie Épaisseur de la bordure horizontale Épaisseur de la bordure verticale %1$s couleur %1$s couleurs Le modèle actuel ne prend pas en charge le chunking, l\'image sera traitée dans ses dimensions d\'origine, cela peut entraîner une consommation de mémoire élevée et des problèmes avec les appareils bas de gamme. Blocage désactivé, l\'image sera traitée dans ses dimensions d\'origine, cela peut entraîner une consommation de mémoire élevée et des problèmes avec les appareils bas de gamme, mais peut donner de meilleurs résultats d\'inférence Morceau Modèle de segmentation d\'image de haute précision pour la suppression de l\'arrière-plan Version allégée de U2Net pour une suppression plus rapide de l\'arrière-plan avec une utilisation moindre de la mémoire. Le modèle complet DDColor offre une colorisation de haute qualité pour les images générales avec un minimum d\'artefacts. Meilleur choix de tous les modèles de colorisation. DDColor Ensembles de données artistiques formés et privés ; produit des résultats de colorisation diversifiés et artistiques avec moins d’artefacts de couleur irréalistes. Modèle BiRefNet léger basé sur Swin Transformer pour une suppression précise de l\'arrière-plan. Suppression d’arrière-plan de haute qualité avec des bords nets et une excellente préservation des détails, en particulier sur les objets complexes et les arrière-plans délicats. Modèle de suppression d\'arrière-plan qui produit des masques précis avec des bords lisses, adaptés aux objets généraux et à une préservation modérée des détails. Modèle déjà téléchargé Modèle importé avec succès Taper Mot clé Très rapide Normale Lent Très lent Calculer les pourcentages La valeur minimale est %1$s Déformer l\'image en dessinant avec les doigts Chaîne Dureté Mode déformation Se déplacer Grandir Rétrécir Tourbillon CW Tourbillon CCW Force de fondu Top Drop Chute inférieure Démarrer le dépôt Fin du dépôt Téléchargement Formes lisses Utilisez des superellipses au lieu des rectangles arrondis standard pour des formes plus douces et plus naturelles Type de forme Couper Arrondi Lisse Arêtes vives sans arrondi Coins arrondis classiques Type de formes Taille des coins Cercle Éléments d\'interface utilisateur arrondis et élégants Format du nom de fichier Texte personnalisé placé au tout début du nom de fichier, parfait pour les noms de projets, les marques ou les balises personnelles. Utilise le nom de fichier d\'origine sans extension, vous aidant ainsi à conserver l\'identification de la source intacte. La largeur de l\'image en pixels, utile pour suivre les changements de résolution ou la mise à l\'échelle des résultats. La hauteur de l\'image en pixels, utile lorsque vous travaillez avec des proportions ou des exportations. Génère des chiffres aléatoires pour garantir des noms de fichiers uniques ; ajoutez plus de chiffres pour plus de sécurité contre les doublons. Compteur à incrémentation automatique pour les exportations par lots, idéal lors de l\'enregistrement de plusieurs images en une seule session. Insère le nom du préréglage appliqué dans le nom de fichier afin que vous puissiez facilement vous rappeler comment l\'image a été traitée. Affiche le mode de mise à l\'échelle de l\'image utilisé pendant le traitement, permettant de distinguer les images redimensionnées, recadrées ou ajustées. Texte personnalisé placé à la fin du nom de fichier, utile pour le versioning comme _v2, _edited ou _final. L\'extension du fichier (png, jpg, webp, etc.), correspondant automatiquement au format réellement enregistré. Un horodatage personnalisable qui vous permet de définir votre propre format par spécification Java pour un tri parfait. Type d\'aventure Android natif Style iOS Courbe douce Arrêt rapide Gonflable Flottant Vif Ultra lisse Adaptatif Conscient de l\'accessibilité Mouvement réduit Physique de défilement Android native pour une comparaison de base Défilement équilibré et fluide pour un usage général Comportement de défilement de type iOS à friction plus élevée Courbe cannelée unique pour une sensation de défilement distincte Défilement précis avec arrêt rapide Défilement rebondissant ludique et réactif Défilements longs et glissants pour la navigation dans le contenu Défilement rapide et réactif pour les interfaces utilisateur interactives Défilement fluide de qualité supérieure avec un élan prolongé Ajuste la physique en fonction de la vitesse de lancement Respecte les paramètres d\'accessibilité du système Mouvement minimal pour les besoins d’accessibilité Lignes primaires Ajoute une ligne plus épaisse toutes les cinq lignes Couleur de remplissage Outils cachés Outils masqués pour le partage Bibliothèque de couleurs Parcourez une vaste collection de couleurs Affine et supprime le flou des images tout en conservant les détails naturels, idéal pour corriger les photos floues. Restaure intelligemment les images précédemment redimensionnées, en récupérant les détails et les textures perdus. Optimisé pour le contenu d\'action en direct, réduit les artefacts de compression et améliore les détails fins des images de films/émissions de télévision. Convertit les séquences de qualité VHS en HD, supprimant le bruit de la bande et améliorant la résolution tout en préservant l\'aspect vintage. Spécialisé pour les images et captures d\'écran contenant beaucoup de texte, il affine les caractères et améliore la lisibilité. Mise à l\'échelle avancée formée sur divers ensembles de données, excellente pour l\'amélioration de photos à usage général. Optimisé pour les photos compressées sur le Web, supprime les artefacts JPEG et restaure l\'apparence naturelle. Version améliorée pour les photos Web avec une meilleure préservation de la texture et une réduction des artefacts. La mise à l\'échelle 2x avec la technologie Dual Aggregation Transformer maintient la netteté et les détails naturels. Mise à l\'échelle 3x grâce à une architecture de transformateur avancée, idéale pour les besoins d\'agrandissement modérés. Mise à l\'échelle 4x de haute qualité avec un réseau de transformateurs de pointe, préserve les détails fins à plus grande échelle. Supprime le flou/bruit et les secousses des photos. Usage général mais meilleur sur les photos. Restaure les images de faible qualité à l\'aide du transformateur Swin2SR, optimisé pour la dégradation BSRGAN. Idéal pour corriger les artefacts de compression importants et améliorer les détails à une échelle 4x. Mise à l\'échelle 4x avec le transformateur SwinIR formé à la dégradation BSRGAN. Utilise GAN pour des textures plus nettes et des détails plus naturels dans les photos et les scènes complexes. Chemin Fusionner un PDF Combinez plusieurs fichiers PDF en un seul document Ordre des fichiers pp. Fractionner un PDF Extraire des pages spécifiques d\'un document PDF Faire pivoter le PDF Corriger l\'orientation de la page de manière permanente Pages Réorganiser le PDF Glissez et déposez les pages pour les réorganiser Maintenir et faire glisser les pages Numéros de pages Ajoutez automatiquement une numérotation à vos documents Format d\'étiquette PDF en texte (OCR) Extrayez le texte brut de vos documents PDF Superposer du texte personnalisé pour la marque ou la sécurité Signature Ajoutez votre signature électronique à n\'importe quel document Ceci servira de signature Déverrouiller le PDF Supprimez les mots de passe de vos fichiers protégés Protéger le PDF Sécurisez vos documents avec un cryptage fort Succès PDF déverrouillé, vous pouvez l\'enregistrer ou le partager Réparer le PDF Tentative de réparation de documents corrompus ou illisibles Niveaux de gris Convertir toutes les images intégrées au document en niveaux de gris Compresser un PDF Optimisez la taille de votre fichier de document pour un partage plus facile ImageToolbox reconstruit la table de références croisées interne et régénère la structure des fichiers à partir de zéro. Cela peut restaurer l\'accès à de nombreux fichiers qui \\"ne peuvent pas être ouverts\\" Cet outil convertit toutes les images du document en niveaux de gris. Idéal pour imprimer et réduire la taille des fichiers Métadonnées Modifier les propriétés du document pour une meilleure confidentialité Balises Producteur Auteur Mots-clés Créateur Nettoyage en profondeur de la confidentialité Effacer toutes les métadonnées disponibles pour ce document Page ROC profonde Extrayez le texte du document et stockez-le dans un fichier texte à l\'aide du moteur Tesseract Impossible de supprimer toutes les pages Supprimer des pages PDF Supprimer des pages spécifiques du document PDF Appuyez pour supprimer Manuellement Recadrer le PDF Recadrer les pages du document à n\'importe quelle limite Aplatir le PDF Rendre le PDF non modifiable en pixellisant les pages du document Impossible de démarrer la caméra. Veuillez vérifier les autorisations et vous assurer qu\'elle n\'est pas utilisée par une autre application. Extraire des images Extrayez les images intégrées dans des PDF à leur résolution d\'origine Ce fichier PDF ne contient aucune image intégrée Cet outil numérise chaque page et récupère les images source en pleine qualité – parfait pour enregistrer les originaux des documents Dessiner une signature Paramètres du stylo Utiliser votre propre signature comme image à placer sur les documents Zip PDF Divisez le document avec un intervalle donné et regroupez les nouveaux documents dans une archive zip Intervalle Imprimer le PDF Préparer le document pour l\'impression avec un format de page personnalisé Pages par feuille Orientation Taille des pages Marge Floraison Genou doux Optimisé pour les anime et les dessins animés. Mise à l\'échelle rapide avec des couleurs naturelles améliorées et moins d\'artefacts Style similaire à Samsung One UI 7 Entrez ici les symboles mathématiques de base pour calculer la valeur souhaitée (par exemple (5+5)*10) Expression mathématique Ramassez jusqu\'à %1$s images Conserver la date et l\'heure Conservez toujours les balises exif liées à la date et à l\'heure, fonctionne indépendamment de l\'option conserver exif Couleur d\'arrière-plan pour les formats alpha Ajoute la possibilité de définir la couleur d\'arrière-plan pour chaque format d\'image avec prise en charge alpha. Lorsqu\'elle est désactivée, cette option est disponible uniquement pour les formats non alpha. Projet ouvert Continuer à modifier un projet Image Toolbox précédemment enregistré Impossible d\'ouvrir le projet Image Toolbox Le projet Image Toolbox ne contient pas de données de projet Le projet Image Toolbox est corrompu Version du projet Image Toolbox non prise en charge : %1$d Enregistrer le projet Stockez les calques, l\'arrière-plan et l\'historique des modifications dans un fichier de projet modifiable Échec de l\'ouverture Écrire dans un PDF consultable Reconnaissez le texte d\'un lot d\'images et enregistrez un PDF consultable avec une image et un calque de texte sélectionnable Couche alpha Retournement horizontal Retournement vertical Verrouillage Ajouter une ombre Couleur de l\'ombre Géométrie du texte Étirez ou inclinez le texte pour une stylisation plus nette Échelle X Inclinaison X Supprimer les annotations Supprimez les types d\'annotations sélectionnés tels que les liens, les commentaires, les surlignages, les formes ou les champs de formulaire des pages PDF. Liens hypertextes Fichiers joints Lignes Fenêtres contextuelles Timbres Formes Notes de texte Balisage de texte Champs de formulaire Balisage Inconnu Annotations Dissocier Ajoutez une ombre floue derrière le calque avec des couleurs et des décalages configurables ================================================ FILE: core/resources/src/main/res/values-hi/strings.xml ================================================ ठहरें बंद करें छवि रीसेट करें छवि परिवर्तन प्रारंभिक मानों पर वापस आ जाएंगे मान ठीक से रीसेट हो गए रीसेट दी गई दो छवियों की तुलना करें आरंभ करने के लिए दो छवियां चुनें गतिशील रंग चालू होने पर ऐप रंग योजना नहीं बदली जा सकती ऐप थीम चयनित रंग पर आधारित होगी ऐप के बारे में कुछ ग़लत हुआ: %1$s आकार %1$s लोड हो रहा है… पूर्वावलोकन के लिए छवि बहुत बड़ी है, लेकिन फिर भी सहेजने का प्रयास किया जाएगा आरंभ करने के लिए छवि चुनें चौड़ाई %1$s ऊंचाई %1$s गुणवत्ता विस्तार पुनर्कार प्रकार सुस्पष्ट लचीला छवि चुनें क्या आप वाकई ऐप बंद करना चाहते हैं? ऐप बंद हो रहा है कुछ गलत हुआ ऐप पुनः प्रारंभ करें क्लिपबोर्ड पर कॉपी किया गया अपवाद EXIF बदलें ठीक है कोई EXIF डाटा नहीं मिला टैग जोड़ें सहेजें साफ करें EXIF साफ करें रद्द करें छवि का सारा EXIF डेटा मिटा दिया जाएगा। इस कार्रवाई को पूर्ववत नहीं किया जा सकता! प्रीसेट क्रॉप सहेजा जा रहा है यदि आप अभी बाहर निकलेंगे तो सभी सहेजे न गए परिवर्तन खो जाएंगे स्रोत कोड नवीनतम अपडेट्स प्राप्त करें,मुद्दों पर चर्चा करें और अधिक एकल संपादन एकल दी गई छवि का विवरण बदलें रंग चुनें छवि से रंग चुनें, कॉपी करें या साझा करें छवि रंग रंग कॉपी किया गया छवि को किसी भी सीमा तक क्रॉप करें संस्करण EXIF रखें छवियां: %d पूर्वावलोकन बदलें हटाएं दी गई छवि से रंग पैलेट नमूना बनाएं पैलेट उत्पन्न करें पैलेट अपडेट नया संस्करण %1$s असमर्थित प्रकार: %1$s दी गई छवि के लिए पैलेट उत्पन्न नहीं किया जा सकता मूल आउटपुट फोल्डर तयशुदा तदनुकूल अनिर्दिष्ट डिवाइस स्टोरेज वजन के आधार पर आकार बदलें अधिकतम आकार KB में KB में दिए गए आकार के अनुसार छवि का आकार बदलें तुलना करें छवियां चुनें सेटिंग्स रात्रि मोड गहरा हल्का सिस्टम गतिशील रंग अनुकूलीकरण छवि मोनेट की अनुमति दें यदि सक्षम है, तो जब आप संपादित करने के लिए एक छवि चुनते हैं, तो इस छवि के लिए ऐप रंग अपनाए जाएंगे भाषा Amoled मोड यदि सक्षम किया गया है तो रात्रि मोड में सतहों का रंग पूर्णतः गहरे रंग पर निर्धारित हो जाएगा रंग योजना लाल हरा नीला वैध aRGB-कोड पेस्ट करें। पेस्ट करने के लिए कुछ नहीं है कोई अपडेट नहीं मिला मुद्दा ट्रैकर बग रिपोर्ट और फीचर अनुरोध यहां भेजें अनुवाद करने में मदद करें अनुवाद संबंधी गलतियों को सुधारें या परियोजना को अन्य भाषाओं में स्थानीयकृत करें आपकी क्वेरी से कुछ नहीं मिला यहां खोजें यदि सक्षम किया गया है, तो ऐप रंग वॉलपेपर रंगों के अनुरूप हो जाएंगे %d छवि(यों) को सहेजने में विफल सतह अपडेट के लिए जांचें यदि सक्षम है, तो ऐप स्टार्टअप के बाद आपको अपडेट डायलॉग दिखाया जाएगा छवियों को सहेजने के लिए ऐप को आपके स्टोरेज तक पहुंच की आवश्यकता है, काम करने के लिए यह आवश्यक है। कृपया अगले संवाद बॉक्स में अनुमति प्रदान करें। यह एप्लिकेशन पूरी तरह से निःशुल्क है, लेकिन यदि आप परियोजना के विकास में सहायता करना चाहते हैं, तो आप यहां क्लिक कर सकते हैं FAB संरेखण मान जोड़ें प्राथमिक तृतीयक माध्यमिक अनुमति अनुदान ऐप को काम करने के लिए इस अनुमति की आवश्यकता है, कृपया इसे मैन्युअल रूप से प्रदान करें बॉर्डर की मोटाई बाहरी स्टोरेज मोनेट रंग छवि जूम उपसर्ग फाइल का नाम साझा करें इमोजी मुख्य स्क्रीन पर प्रदर्शित होने वाले इमोजी का चयन करें फाइल आकार जोड़ें यदि सक्षम किया गया है, तो आउटपुट फाइल के नाम पर सहेजी गई छवि की चौड़ाई और ऊंचाई जोड़ता है EXIF मिटाएं छवि पूर्वावलोकन छवि स्रोत फोटो चयनकर्ता गेलरी फाइल अन्वेषक सरल गैलरी छवि चयनकर्ता, यह तभी काम करेगा जब आपके पास वह ऐप होगा विकल्प व्यवस्था संपादित करें इमोजी की गिनती sequenceNum originalFilename छवियों के किसी भी समूह से EXIF मेटाडेटा मिटाएं किसी भी प्रकार की छवियों का पूर्वावलोकन करें: GIF, SVG, इत्यादि Android आधुनिक फोटो चयनकर्ता जो स्क्रीन के नीचे दिखाई देता है, केवल Android 12+ पर काम कर सकता हैै। इसमें EXIF मेटाडेटा प्राप्त करने में भी समस्याएं हैं छवि चुनने के लिए GetContent आशय का उपयोग करें। हर जगह काम करता है, लेकिन कुछ डिवाइसों पर चुनी गई छवियां प्राप्त करने में समस्याएं आती हैं। वह मेरी गलती नहीं है। क्रम मुख्य स्क्रीन पर विकल्पों का क्रम निर्धारित करता है मूल फाइल नाम जोड़ें सक्षम होने पर आउटपुट छवि के नाम पर मूल फाइल नाम जोड़ता है अनुक्रम संख्या जोड़ें यदि आप प्रचय संसाधन का उपयोग करते हैं तो सक्षम होने पर मानक समय-चिह्न को छवि अनुक्रम संख्या में बदल दिया जाता है यदि फोटो चयनकर्ता छवि स्रोत चयनित है तो मूल फाइल नाम जोड़ना काम नहीं करता है चमक पैना करें क्रॉसहैच अंतर रेखा की चौडाई CGA कलरस्पेस त्रिज्या पैमाना विरूपण कोण उभाड़ना नेट से छवि लोड करें सीमाओं का आकार बदलना स्टैक ब्लर किसी भी छवि को इंटरनेट से लोड करें, उसका पूर्वावलोकन करें, जूम करें, और यदि आप चाहें तो उसे सहेज या संपादित भी कर सकते हैं कोई छवि नहीं समुचित वैषम्य रंगत संतृप्ति फिल्टर जोड़ें फिल्टर दी गई छवियों पर कोई फिल्टर श्रृंखला लागू करें फिल्टर रोशनी रंग फिल्टर अल्फा एक्सपोज़र श्वेत संतुलन तापमान टिंट मोनोक्रोम गामा हाइलाइट्स और छायाएं हाइलाइट धुंध प्रभाव दूरी ढलान सीपिया नकारात्मक सौर्यकृत जीवंतता काला और सफेद सोबेल एज ब्लर हाफ़टोन गॉसियन ब्लर बॉक्स ब्लर द्विपक्षीय ब्लर उभार लैप्लासियन विनेट शुरू अंत कुवहारा चौरसाई भँवर फैलाव गोले का अपवर्तन अपवर्तक सूचकांक कांच के गोले का अपवर्तन रंग मैट्रिक्स अपारदर्शिता पक्षानुपात को सहेजते समय दी गई चौड़ाई और ऊंचाई की सीमाओं का पालन करने के लिए चयनित छवियों का आकार बदलें रेखाचित्र सीमा परिमाणीकरण स्तर चिकना तून तून पोस्टरीकरण चौड़ाई या ऊंचाई पैरामीटर द्वारा दिए गए लंबे पक्ष के साथ छवियों का आकार बदलता है, सहेजने के बाद सभी आकार की गणना की जाएगी - पहलू अनुपात रखता है छायाएं छवि लिंक भरें चौड़ाई और ऊंचाई पैरामीटर द्वारा दी गई छवि में हर तस्वीर को बल देता है - पहलू अनुपात बदल सकता है सामग्री का पैमाना गैर अधिकतम दमन कमजोर पिक्सल समावेशन कनवल्शन 3x3 ब्लर आकार ब्लर केंद्र x ब्लर केंद्र y जूम ब्लर रंग संतुलन चमक दहलीज ऊपर देखो RGB फिल्टर मिथ्या रंग पहला रंग दूसरा रंग पुन: व्यवस्थित करें तेज़ ब्लर आपने फाइलें ऐप अक्षम कर दिया है, इस सुविधा का उपयोग करने के लिए इसे सक्रिय करें चित्र बनाएं स्केचबुक की तरह छवि बनाएं, या पृष्ठभूमि पर ही चित्र बनाएं पृष्ठभूमि रंग चुनें और उसके ऊपर चित्र बनाएं पृष्ठभूमि का रंग फाइल चुनें कूटलेख आरंभ करने के लिए फाइल चुनें कूटलेखन कुंजी इस फाइल को अपने डिवाइस पर संग्रहीत करें या जहाँ चाहें इसे रखने के लिए साझा क्रिया का उपयोग करें विशेषताएं फाइल संसाधित AES-256, GCM मोड, कोई पैडिंग नहीं, 12 bytes यादृच्छिक IVs। कुंजियां SHA-3 हैश (256 bits) के रूप में उपयोग की जाती हैं। अधिकतम फाइल आकार Android OS और उपलब्ध मेमोरी द्वारा प्रतिबंधित है, जो स्पष्ट रूप से आपके डिवाइस पर निर्भर करता है। \nकृपया ध्यान दें: मेमोरी स्टोरेज नहीं है। कृपया ध्यान दें कि अन्य फाइल कूटलेखन सॉफ़्टवेयर या सेवाओं के साथ अनुकूलता की गारंटी नहीं है। थोड़ा अलग कुंजी उपचार या सिफर विन्यास असंगतता का कारण हो सकता है। फाइलों का पासवर्ड-आधारित कूटलेखन। आगे बढ़ी गई फाइलें चयनित निर्देशिका में संग्रहीत या साझा की जा सकती हैं। विकोडित फाइलें सीधे भी खोली जा सकती हैं। दी गई चौड़ाई और ऊंचाई के साथ छवि को सहेजने का प्रयास करने से OOM त्रुटि हो सकती है, इसे अपने जोखिम पर करें, और यह न कहें कि मैंने आपको चेतावनी नहीं दी थी! कैशे आकार %1$s मिला स्वचालित कैशे समाशोधन यदि सक्षम किया गया है तो ऐप स्टार्टअप पर ऐप कैशे साफ हो जाएगा औजार प्रकार के अनुसार विकल्पों को समूहित करें तदनुकूल सूची व्यवस्था के बजाय अपने प्रकार की मुख्य स्क्रीन पर समूह विकल्प विकल्प समूहन सक्षम होने पर व्यवस्था नहीं बदली जा सकती पेंट का रंग पेंट अल्फा छवि पर चित्र बनाएं एक छवि चुनें और उस पर कुछ बनाएं पृष्ठभूमि पर चित्र बनाएं सिफ़र AES क्रिप्टो एल्गोरिथम के आधार पर किसी भी फाइल (न केवल छवि) को कूटलेख और विकोड करें विकोड विकोडन कार्यान्वयन अनुकूलता फाइल का साइज़ अमान्य पासवर्ड या चुनी गई फाइल कूटलेखित नहीं है कैशे बनाएं स्क्रीनशॉट संपादित करें स्क्रीनशॉट फ़ॉलबैक विकल्प छोड़ें प्रतिलिपि द्वितीयक अनुकूलन %1$s मोड में सहेजना अस्थिर हो सकता है, क्योंकि यह दोषरहित प्रारूप है यह आपकी सेटिंग्स को तयशुदा मानों पर वापस ले आएगा। ध्यान दें कि इसे ऊपर उल्लिखित बैकअप फाइल के बिना पूर्ववत नहीं किया जा सकता है। अपडेट उफ़… कुछ ग़लत हो गया। आप नीचे दिए गए विकल्पों का उपयोग करके मुझे लिख सकते हैं और मैं समाधान खोजने का प्रयास करूंगा Telegram चैट मास्क क्रॉप करे प्रकृति और जानवर अधिकतम रंग गिनती गतिविधियां इंतजार करे वर्तमान में, %1$s प्रारूप केवल Android पर EXIF मेटाडेटा पढ़ने की अनुमति देता है। सहेजे जाने पर आउटपुट छवि में बिल्कुल भी मेटाडेटा नहीं होगा। खाद्य और पेय मुझसे बात करें %1$s के मान का अर्थ है तेज़ संपीड़न, जिसके परिणामस्वरूप फाइल का आकार अपेक्षाकृत बड़ा हो जाता है। %2$s का अर्थ है धीमा संपीड़न, जिसके परिणामस्वरूप छोटी फाइल बनती है। फाइल में अपनी ऐप सेटिंग का बैकअप लें दूषित फाइल या बैकअप नहीं यदि आपने प्रीसेट 125 चुना है, तो छवि मूल छवि के 125% आकार में सहेजी जाएगी। यदि आप प्रीसेट 50 चुनते हैं, तो छवि 50% आकार के साथ सहेजी जाएगी अनाम ऐप उपयोग आँकड़े एकत्र करने की अनुमति दें फ़ॉन्ट पैमाना बीटा को अनुमति दें ड्रॉ मोड पुनर्स्थापना छवि के चारों ओर पारदर्शी स्थान को ट्रिम कर दिया जाएगा ऐप पर चर्चा करें और अन्य उपयोगकर्ताओं से प्रतिक्रिया प्राप्त करें। आप यहां बीटा अपडेट और अंतर्दृष्टि भी प्राप्त कर सकते हैं। अ आ इ ई उ ऊ ऋ ए ऐ ओ औ क ख ग घ ङ च छ ज झ ञ ट ठ ड ढ ण त थ द ध न प फ ब भ म य र ल व श ष स ह 0123456789 !? पृष्ठभूमि मिटाएं छवि पुनर्स्थापित करें मिटाएं पहलू अनुपात ब्लर त्रिज्या पृष्ठभूमि हटानेवाला छवि ट्रिम करें आरेखण या स्वतः विकल्प का उपयोग करके छवि से पृष्ठभूमि हटाएं आप चयनित रंग योजना को मिटने वाले हैं। यह कार्रवाई पूर्ववत नहीं की जा सकती %1$s फोल्डर में सहेजा गया सेटिंग्स सफलतापूर्वक बहाल हो गई बैकअप और पुनर्स्थापना बैकअप स्वतः बैकग्राउंड मिटाएं भावनाएं यह ऐप को मैन्युअल रूप से क्रैश रिपोर्ट एकत्र करने की अनुमति देता है लगभग सहेजा गया। अब रद्द करने पर फिर से सहेजने की आवश्यकता होगी। मुद्दा बनाएं विंदुक %2$s नाम से %1$s फोल्डर में सहेजा गया प्रतीक विश्लेषिकी दी गई छवि से मास्क बनाने के लिए इस मास्क प्रकार का उपयोग करें, ध्यान दें कि इसमें अल्फा चैनल होना चाहिए इमोजी सक्षम करें कोशिश पुनर्कार और परिवर्तित करें वस्तुएं फाइलनाम को यादृच्छिक करें पाठ तयशुदा योजना मिटाएं पृष्ठभूमि पुनर्स्थापित करें फॉन्ट दी गई छवियों का आकार बदलें या उन्हें अन्य प्रारूपों में परिवर्तित करें, यदि आप एकल छवि चुनते हैं तो आप EXIF मेटाडेटा को भी संपादित कर सकते हैं बड़े फ़ॉन्ट पैमाने का उपयोग करने से UI गड़बड़ियां और समस्याएं हो सकती हैं, जिन्हें ठीक नहीं किया जा सकेगा। सावधानी से प्रयोग करें। सक्षम होने पर आउटपुट फाइल नाम पूरी तरह से यादृच्छिक होगा मूल छवि का मेटाडेटा रखा जाएगा पहले से बनाई गई फाइल से ऐप सेटिंग पुनर्स्थापित करें प्रीसेट यहाँ प्राप्त होने वाली फोटो का % निर्धारित करता है,जैसे कि अगर आपने प्रीसेट में 50 चुना हैं तो आपको 5mb की फोटो सेव होने के बाद 2.5mb की प्राप्त होगी मिटाएं मोड सक्षम होने पर अपडेट जाँच में बीटा ऐप संस्करण शामिल होंगे यात्राएं और स्थान ब्रश की कोमलता तीर खींचें यदि सक्षम किया गया है तो आरेखण पथ को इंगित तीर के रूप में दर्शाया जाएगा यदि चित्रों का आकार इनपुट आयामों से बड़ा है तो उन्हें बीच में इस आकार में क्रॉप किया जाएगा और अन्य मामलों में कैनवास को दिए गए पृष्ठभूमि रंग के साथ विस्तारित किया जाएगा दान क्षैतिज छवियों का क्रम छवि सिलाई कम से कम 2 छवियां चुनें सक्षम होने पर छोटी छवियों को अनुक्रम में सबसे बड़ी छवि में बदला जायेगा एक बड़ी छवि पाने के लिए दी गई छवियों को मिलाएं छवि अभिविन्यास लंबवत आउटपुट छवि पैमाना छोटी छवियों को बड़े आकार में बदलें नियमित ईमेल पिक्सलेशन सक्षम होने पर एक रंग की बजाय इसके चारों ओर स्थान भरने के लिए छवि के नीचे धुंधले किनारों को भरता है किनारों को धुंधला करें उन्नत पिक्सलेशन रंग बदलें सहिष्णुता बदलने लायक रंग लक्षित रंग हटाने के लिए रंग रंग हटाए बोकेह रैंडम इमोजी रैंडम इमोजी ऑन होने पे इमोजी चुन नहीं सकते इमोजी ऑफ होने पे रैंडम इमोजी इस्तेमाल नहीं कर सकते ऐप बार का इमोजी रैंडम इमोजी से लगातार बदला जाएगा पुराना टीवी डिजिटल कोड डीप पर्पल रेड स्वर्ल स्पेस पोर्टल उन्नत डायमंड पिक्सलेशन डायमंड पिक्सलेशन वृत्त पिक्सलेशन उन्नत वृत्त पिक्सलेशन रात्रि जादू हरा सूर्य पिक्सल साइज ड्रा अभिविन्यास लॉक करे तयशुदा पैलेट शैली, यह सभी चार रंगों को अनुकूलित करने की अनुमति देती है। अन्य आपको केवल मुख्य रंग तय करने की अनुमति देते हैं रंग विस्फोट एक शैली जो मोनोक्रोम की तुलना में थोड़ी अधिक रंगीन है सतरंगी दुनिया पुन:कोड अगर आरेखण मोड़ में सक्रिय रहा तो स्क्रीन नही घूमेगी अपडेट के लिये जांचें तटस्थ चमकीला अभिव्यंजक मेघधनुष फल सलाड सामग्री स्ट्रोक पिक्सलेशन कार्ड के प्रमुख आइकन के तहत चयनित आकार के साथ कंटेनर जोड़ता है एक चंचल थीम - स्रोत रंग का रंग थीम में प्रकट नहीं होता है एक योजना जो स्रोत रंग को Scheme.primaryContainer में रखती है PDF को छवियों में दिए गए आउटपुट प्रारूप में बदलें आउटपुट PDF फाइल में दी गई छवियों को पैक करें मुख्य स्क्रीन पर सभी उपलब्ध विकल्पों के माध्यम से खोज करने की क्षमता सक्षम करता है PDF उपकरण PDF फाइलों के साथ काम करें: पूर्वावलोकन करें, छवियों के बैच में परिवर्तित करें या दिए गए चित्रों में से एक बनाएं फिल्टर श्रृंखला दिए गए नकाबपोश क्षेत्रों पर लागू करें, प्रत्येक मास्क क्षेत्र फिल्टर का अपना समूह निर्धारित कर सकता है उलटा भरण प्रकार यदि सक्षम सभी गैर-नकाबपोश क्षेत्रों को तयशुदा व्यवहार के बजाय फिल्टर किया जाएगा पेन गोपनीयता ब्लर आप जो कुछ भी छिपाना चाहते हैं उसे सुरक्षित करने के लिए खींचे गए पथ के नीचे की छवि को ब्लर करता है कंटेनरों के पीछे छाया आरेखण सक्षम करता है फ़्लोटिंग एक्शन बटन के पीछे छाया आरेखण सक्षम करता है छवि अभिविन्यास के लिए सीमा बॉक्स को अपनाने की अनुमति देता है आप चयनित फिल्टर मास्क को हटाने वाले हैं । यह कार्रवाई पूर्ववत नहीं की जा सकती मास्क मिटायें खोजें पूर्ण फिल्टर प्रारंभ मध्य अंत किसी भी फिल्टर श्रृंखला को दी गई छवियों या एकल छवि पर लागू करें PDF पूर्वावलोकन PDF से छवियां छवियां से PDF सरल PDF पूर्वावलोकन पथ मोड ड्रा करें डबल लाइन तीर मुक्त आरेखण डबल तीर रेखा तीर तीर रेखा इनपुट मूल्य के रूप में पथ खींचता है एक रेखा के रूप में प्रारंभ बिंदु से अंत बिंदु तक पथ खींचता है एक रेखा के रूप में प्रारंभ बिंदु से अंत बिंदु तक इंगित तीर खींचता है किसी दिए गए पथ से तीर इंगित करता है एक पंक्ति के रूप में प्रारंभ बिंदु से अंत बिंदु तक डबल पॉइंटिंग तीर खींचता है किसी दिए गए पथ से डबल पॉइंटिंग तीर खींचता है उल्लिखित अंडाकार उल्लिखित रेक्ट अंडाकार रेक्ट प्रारंभ बिंदु से अंत बिंदु तक रेक्ट खींचता है प्रारंभ बिंदु से अंत बिंदु तक अंडाकार खींचता है प्रारंभ बिंदु से अंत बिंदु तक उल्लिखित अंडाकार खींचता है प्रारंभ बिंदु से अंत बिंदु तक उल्लिखित रेक्ट खींचता है मास्क रंग मास्क पूर्वावलोकन मान %1$s - %2$s श्रेणी में है चिह्न आकार एक ज़ोरदार थीम, रंगीनता प्राथमिक पैलेट के लिए अधिकतम है, दूसरों के लिए बढ़ी हुई है एक मोनोक्रोम थीम, रंग पूरी तरह से काले / सफेद / ग्रे हैं एक योजना जो सामग्री योजना के समान है सक्षम होने पर थीम रंगों को नकारात्मक रंगों में बदल देता है मास्क फिल्टर मास्क मास्क जोड़ें मास्क %d आपको अनुमानित परिणाम दिखाने के लिए खींचा गया फिल्टर मास्क प्रस्तुत किया जाएगा सरल प्रकार हाइलाइटर नियोन अर्ध-पारदर्शी तेज हाइलाइटर पथ बनाएं अपने चित्र के लिए कुछ चमक प्रभाव जोड़ें तयशुदा एक, सबसे सरल - केवल रंग गोपनीयता ब्लर के समान, लेकिन धुंधला होने के बजाय पिक्सलेट हो जाता है स्वतः घुमाएं पैलेट शैली टोनल स्पॉट फिडेलिटी कंटेनर स्लाइडर्स स्विचेस FABs बटन स्लाइडर्स के पीछे छाया आरेखण सक्षम करता है स्विचों के पीछे छाया आरेखण सक्षम करता है तयशुदा बटनों के पीछे छाया आरेखण सक्षम करता है ऐप बार ऐप बार के पीछे छाया आरेखण सक्षम करता है यदि कोई नया अपडेट उपलब्ध है तो यह जांचने के लिए यह अपडेट चेकर GitHub से जुड़ेगा ध्यान दें मिटते किनारे अक्षम दोनों रंगों को पलटें लैस्सो चैनल शिफ्ट X चैनल शिफ्ट Y भ्रष्टाचार शिफ्ट X भ्रष्टाचार शिफ्ट Y केवल ऑटो ऑटो एकल स्तंभ सिंगल ब्लॉक वर्टिकल टेक्स्ट एकल ब्लॉक सिंगल लाइन एकल शब्द वृत्त शब्द एकल वर्ण विरल पाठ कच्ची लाइन गड़बड़ राशि बीज शोर चोटी संक्रमण क्लिपबोर्ड स्वतः पिन सक्षम होने पर स्वचालित रूप से सहेजी गई छवि को क्लिपबोर्ड पर जोड़ता है कंपन कंपन शक्ति फाइलों को अधिलेखित करने के लिए आपको \"अन्वेषक\" छवि स्रोत का उपयोग करने की आवश्यकता है, छवियों को दोबारा चुनने का प्रयास करें, हमने छवि स्रोत को आवश्यक स्रोत में बदल दिया है फाइलें अधिलेखित करें खाली प्रत्यय कोई \"%1$s\" निर्देशिका नहीं मिली, हमने इसे तयशुदा निर्देशिका में बदल दिया है, कृपया फाइल को फिर से सहेजें मूल फाइल को चयनित फोल्डर में सहेजने के बजाय एक नई फाइल से बदल दिया जाएगा, इस विकल्प के लिए छवि स्रोत \"अन्वेषक\" या GetContent होना चाहिए, इसे टॉगल करने पर, यह स्वचालित रूप से निर्धारित हो जाएगा बाइक्यूबिक रैखिक (या द्विरेखीय, दो आयामों में) प्रक्षेप आम तौर पर छवि के आकार को बदलने के लिए अच्छा होता है, लेकिन विवरणों में कुछ अवांछनीय नरमी का कारण बनता है और फिर भी कुछ हद तक अनियमित हो सकता है आकार बढ़ाने के सरल तरीकों में से एक, प्रत्येक पिक्सल को एक ही रंग के कई पिक्सल के साथ बदलना सुचारू रूप से प्रक्षेप करने और नियंत्रण बिंदुओं के एक समूह को फिर से तैयार करने की विधि, आमतौर पर कंप्यूटर ग्राफिक्स में चिकनी वक्र बनाने के लिए उपयोग की जाती है वर्णक्रमीय रिसाव को कम करने और सिग्नल के किनारों को टैप करके आवृत्ति विश्लेषण की सटीकता में सुधार करने के लिए अक्सर सिग्नल प्रोसेसिंग में विंडोिंग फ़ंक्शन लागू किया जाता है स्केल की गई छवि में तीखेपन और एंटी-अलियासिंग के बीच संतुलन प्राप्त करने के लिए समायोज्य मापदंडों के साथ एक कनवल्शन फिल्टर का उपयोग करने वाली पुन: नमूनाकरण विधि एक वक्र या सतह, लचीले और निरंतर आकार प्रतिनिधित्व को सुचारू रूप से प्रक्षेपित और अनुमानित करने के लिए टुकड़े-टुकड़े परिभाषित बहुपद कार्यों का उपयोग करता है रंग विसंगति मूल गंतव्य पर %1$s नाम से अधिलेखित फाइल पिक्सल स्विच का प्रयोग करें गूगल के Material You आधारित स्विच की जगह पिक्सल जैसे स्विच का इस्तेमाल किया जाएगा कई भाषाओं की अनुमति दें अगल-बगल टैप टॉगल करें स्लाइड यह ऐप पूरी तरह से मुफ़्त है, यदि आप चाहते हैं कि यह बड़ा हो जाए, तो कृपया प्रोजेक्ट को Github 😄 पर तारांकित करें केवल अभिविन्यास और स्क्रिप्ट डिटेक्शन ऑटो अभिविन्यास और स्क्रिप्ट डिटेक्शन विरल पाठ अभिविन्यास और स्क्रिप्ट का पता लगाने वर्तमान अनुकूलित रंगों और उपस्थिति प्रकार के साथ दिए गए आउटपुट आकार का अनुपात बनाएं दी गई छवि के शीर्ष का कोई भी अनुपात बनाएं तस्वीर लेने के लिए कैमरे का उपयोग करता है, ध्यान दें कि इस छवि स्रोत से केवल एक छवि प्राप्त करना संभव है GIF उपकरण छवियों को GIF चित्र में बदलें या दिए गए GIF छवि से फ्रेम निकालें क्या आप सभी पहचान प्रकारों के लिए भाषा \"%1$s\" OCR प्रशिक्षण डेटा हटाना चाहते हैं, या केवल चयनित एक (%2$s) के लिए? छवियों के बैच को GIF फाइल में बदलें पहले फ्रेम के आकार का उपयोग करें कन्फ़ेटी को सहेजने, साझा करने और अन्य प्राथमिक कार्यों पर दिखाया जाएगा बाहर निकलने पर सामग्री छुपाता है, साथ ही स्क्रीन को कैप्चर या रिकॉर्ड नहीं किया जा सकता है बायर थ्री बाय थ्री डिथरिंग बायर फोर बाय फोर डिथरिंग फ्लोयड स्टाइनबर्ग डिथरिंग दो पंक्ति सिएरा डिथरिंग झूठी फ्लोयड स्टाइनबर्ग डिथरिंग एक वक्र या सतह, लचीले और निरंतर आकार के प्रतिनिधित्व को सुचारू रूप से प्रक्षेपित और अनुमानित करने के लिए टुकड़े-टुकड़े परिभाषित बाइबिक बहुपद कार्यों का उपयोग करता है एनाग्लिफ पिक्सल सॉर्ट शफल उन्नत गड़बड़ी भ्रष्टाचार आकार मुक्त सब कुछ अनुपात निर्माता ऐप को रेट करें रेट रैखिक रेडियल स्वीप अनुपात प्रकार केंद्र X केंद्र Y टाइल मोड दोहराया दर्पण क्लैंप डिकल रंग बंद हो जाता है रंग जोड़ें गुण दिए गए पथ से बंद भरा पथ खींचता है डिथरिंग क्वांटिज़ियर ग्रे स्केल बायर टू बाय टू डिथरिंग बायर आठ से आठ डिथरिंग जार्विस जुडिस निन्के डिथरिंग सिएरा डिथरिंग सिएरा लाइट डिथरिंग एटकिंसन डिथरिंग स्टकी डिथरिंग बर्क्स डिथरिंग बाएं से दाएं डिथरिंग यादृच्छिक डिथरिंग सरल थ्रेसहोल्ड डिथरिंग हर्माइट लैंक्ज़ोस स्केल मोड द्विरेखीय हन्न मिशेल निकटतम स्प्लाइन बेसिक तयशुदा मान सिग्मा स्थानिक सिग्मा मेडियन ब्लर कैटमुल बेहतर स्केलिंग विधियों में लैंक्ज़ोस रीसम्पलिंग और मिशेल-नेत्रावली फिल्टर शामिल हैं सरलतम Android स्केलिंग मोड जो लगभग सभी ऐप्स में उपयोग किया जाता है गणितीय प्रक्षेप तकनीक जो एक चिकनी और निरंतर वक्र उत्पन्न करने के लिए एक वक्र खंड के समापन बिंदुओं पर मूल्यों और डेरिवेटिव का उपयोग करती है पुन: नमूनाकरण विधि जो पिक्सल मानों के लिए भारित सिंक फ़ंक्शन को लागू करके उच्च गुणवत्ता वाले प्रक्षेप को बनाए रखती है केवल क्लिप स्टोरेज में सहेजा नहीं जाएगा, और छवि को केवल क्लिपबोर्ड में डालने का प्रयास किया जाएगा चमक प्रवर्तन स्क्रीन अनुपात उपरिशायी परिवर्तन कैमरा वॉटरमार्किंग अनुकूलन पाठ / छवि वॉटरमार्क के साथ चित्रों को कवर करें वॉटरमार्क दोहराएं दिए गए स्थान पर एकल के बजाय छवि पर वॉटरमार्क दोहराता है ऑफसेट X ऑफसेट Y वॉटरमार्क प्रकार इस छवि का उपयोग वॉटरमार्किंग के लिए पैटर्न के रूप में किया जाएगा पाठ का रंग ओवरले मोड GIF से छवियां GIF फाइल को चित्रों के बैच में बदलें छवियां से GIF आरंभ करने के लिए GIF छवि चुनें निर्दिष्ट आकार को पहले फ्रेम आयामों से बदलें गिनती दोहराएं फ्रेम देरी millis FPS लैस्सो का प्रयोग करें मिटाने के लिए आरेखण मोड में लैस्सो का उपयोग करता है मूल छवि पूर्वावलोकन अल्फा OCR (पाठ पहचानें) दी गई छवि से पाठ को पहचानें, 120 + भाषाओं का समर्थन किया चित्र में कोई पाठ नहीं है, या ऐप को यह नहीं मिला सटीकता: %1$s मान्यता प्रकार तेज मानक सबसे अच्छा कोई डेटा नहीं Tesseract OCR के समुचित कार्य के लिए अतिरिक्त प्रशिक्षण डेटा (%1$s) को आपके डिवाइस पर डाउनलोड करने की आवश्यकता है । \nक्या आप %2$s डेटा डाउनलोड करना चाहते हैं? डाउनलोड कोई कनेक्शन नहीं, इसे जांचें और ट्रेन मॉडल डाउनलोड करने के लिए फिर से प्रयास करें डाउनलोड की गई भाषाएं उपलब्ध भाषाएं विभाजन मोड ब्रश मिटाने के बजाय पृष्ठभूमि को पुनर्स्थापित करेगा क्षैतिज ग्रिड ऊर्ध्वाधर ग्रिड सिलाई मोड पंक्तियों की गिनती कॉलम गिनती पारदर्शिता आवर्धक बेहतर पहुंच के लिए आरेखण मोड में उंगली के शीर्ष पर आवर्धक सक्षम करता है आरंभिक मान को बाध्य करें Exif विजेट को प्रारंभ में जाँचने के लिए बाध्य करता है B स्प्लाइन मूल स्टैक ब्लर झुकाव शिफ्ट कन्फ़ेटी सुरक्षित मोड बाहर निकलें यदि आप अभी पूर्वावलोकन छोड़ते हैं, तो आपको छवियों को फिर से जोड़ना होगा टेंट ब्लर पार्श्व फीका पार्श्व शीर्ष नीचे ताकत एल्ड्रिज कटऑफ उचिमुरा मोबियस गुलाबी सपना रंगीन भंवर नींबू पानी प्रकाश लैवेंडर सपना साइबरपंक काल्पनिक परिदृश्य कारमेल अंधेरे ड्रैगो रंग मैट्रिक्स 4x4 रंग मैट्रिक्स 3x3 प्रोटोनोमाली कोडा क्रोम विंटेज रात्रि दृष्टि अक्रोमैटोमाली इरोड अनिसोट्रोपिक प्रसार प्रसार चालन क्षैतिज हवा डगमगाती तेज़ द्विपक्षीय ब्लर पॉइसन ब्लर लॉगरिदमिक टोन मैपिंग क्रिस्टलाइज स्ट्रोक रंग भग्न ग्लास आयाम संगमरमर अशांति तेल जल प्रभाव आकार आवृत्ति X आवृत्ति Y आयाम X आयाम Y पर्लिन विरूपण हैबल फिल्मी टोन मैपिंग हेजल बर्गेस टोन मैपिंग ACES फिल्मी टोन मैपिंग ACES हिल टोन मैपिंग गति डेहेज़ ओमेगा सरल प्रभाव पोलरॉइड ट्रिटोनोमाली ड्यूटारोमाली ब्राउनी गर्म कूल ट्रिटानोपिया ड्यूटारोनोटोपिया प्रोटानोपिया अक्रोमैटोप्सिया अनाज अनशर्प पेस्टल नारंगी धुंध सुनहरा घंटा गर्म गर्मी बैंगनी धुंध सूर्योदय शीतल वसंत प्रकाश शरद ऋतु टन वर्णक्रमीय आग विद्युत अनुपात भविष्यवादी अनुपात शफल ब्लर पसंदीदा अभी तक कोई पसंदीदा फिल्टर नहीं जोड़ा गया है छवि प्रारूप फाइलों को अधिलेखित करते समय छवि प्रारूप नहीं बदल सकता विकल्प सक्षम रंग योजना के रूप में इमोजी मैन्युअल रूप से परिभाषित एक के बजाय ऐप रंग योजना के रूप में इमोजी प्राथमिक रंग का उपयोग करता है मूल गंतव्य पर ओवरराइट की गई छवियां छवि से Material You पैलेट बनाता है गहरे रंग हल्के संस्करण के बजाय रात्रि मोड रंग योजना का उपयोग करता है Jetpack Compose कोड के रूप में कॉपी करें वृत्त ब्लर स्टार ब्लर रिंग ब्लर क्रॉस ब्लर रैखिक झुकाव शिफ्ट हटाने के लिए टैग छवियों को APNG चित्र में बदलें या दी गई APNG छवि से फ़्रेम निकालें APNG फाइल को चित्रों के बैच में बदलें छवियों के बैच को APNG फाइल में बदलें छवियां से APNG आरंभ करने के लिए APNG छवि चुनें APNG से छवियां APNG टूल्स मोशन ब्लर Zip दी गई फाइलों या छवियों से Zip फाइल बनाएं खींचें हैंडल की चौड़ाई विस्फोट बारिश कन्फ़ेटी प्रकार उत्सवपूर्ण कोने JXL टूल्स बिना गुणवत्ता हानि के JXL ~ JPEG ट्रांसकोडिंग करें, या GIF/APNG को JXL एनीमेशन में बदलें JXL से JPEG JPEG से JXL आरंभ करने के लिए JXL छवि चुनें JPEG से JXL तक दोषरहित ट्रांसकोडिंग करें JXL से JPEG तक दोषरहित ट्रांसकोडिंग करें तेज गॉसियन ब्लर 3D तेज गॉसियन ब्लर 4D तेज गॉसियन ब्लर 2D ऑटो पेस्ट ऐप को क्लिपबोर्ड डेटा को ऑटो पेस्ट करने की अनुमति देता है, जिससे यह मुख्य स्क्रीन पर दिखाई देगा और आप इसे संसाधित करने में सक्षम होंगे सामंजस्य रंग सामंजस्य स्तर लैंक्ज़ोस बेसेल GIF से JXL GIF छवियों को JXL एनिमेटेड चित्रों में बदलें APNG से JXL JXL एनिमेशन को चित्रों के बैच में बदलें JXL से छवियां छवियों से JXL चित्रों के बैच को JXL एनीमेशन में बदलें व्यवहार फाइल चयन छोड़ें यदि संभव हो तो चयनित स्क्रीन पर फाइल चयनकर्ता तुरंत दिखाया जाएगा पूर्वावलोकन उत्पन्न करें पूर्वावलोकन जनरेशन सक्षम करता है, इससे कुछ उपकरणों पर क्रैश से बचने में मदद मिल सकती है, यह एकल संपादन विकल्प के भीतर कुछ संपादन कार्यक्षमता को भी अक्षम कर देता है हानिपूर्ण संपीड़न फाइल आकार को दोषरहित के बजाय कम करने के लिए हानिपूर्ण संपीड़न का उपयोग करता है पुन: नमूनाकरण विधि जो पिक्सल मानों पर बेसेल (jinc) फ़ंक्शन लागू करके उच्च गुणवत्ता वाले इंटरपोलेशन को बनाए रखती है APNG छवियों को JXL एनिमेटेड चित्रों में बदलें संपीड़न प्रकार परिणामी छवि डिकोडिंग गति को नियंत्रित करता है, इससे परिणामी छवि को तेजी से खोलने में मदद मिलेगी, %1$s का मतलब सबसे धीमी डिकोडिंग, जबकि %2$s - सबसे तेज़, यह सेटिंग आउटपुट छवि आकार को बढ़ा सकती है नाम से छांटें नाम से छांटें (उलटा) छंटाई तिथि से छांटें (उलटा) तिथि से छांटें चैनल विन्यास आज अंतर्निहित चयनकर्ता कल सिस्टम द्वारा पूर्वनिर्धारित छवि चयनकर्ता के बजाय इमेज टूलबॉक्स के स्वयं के छवि चयनकर्ता का उपयोग करता है अनुरोध एकाधिक मीडिया चुनें एकल मीडिया चुनें चुनें कोई अनुमतियां नहीं पुनः प्रयास करें लैंडस्केप में सेटिंग्स दिखाएं अक्षम होने पर लैंडस्केप मोड में स्थायी दृश्यमान विकल्प के बजाय सेटिंग्स हमेशा की तरह शीर्ष ऐप बार में बटन पर खोली जाएंगी स्विच प्रकार लिखें फ़ुलस्क्रीन सेटिंग्स इसे सक्षम करें और सेटिंग पेज हमेशा स्लाइड करने योग्य ड्रॉअर शीट के बजाय फ़ुलस्क्रीन के रूप में खोला जाएगा Jetpack Compose Material You स्विच का उपयोग करें, यह दृश्य आधारित जितना सुंदर नहीं है दृश्य आधारित Material You स्विच का उपयोग करता है, यह दूसरों की तुलना में बेहतर दिखता है और इसमें अच्छे एनिमेशन हैं पिक्सल Cupertino डिज़ाइन प्रणाली पर आधारित iOS जैसे स्विच का उपयोग करता है अधि एंकर का आकार बदलें Fluent \"Fluent\" डिज़ाइन प्रणाली पर आधारित Windows 11 स्टाइल स्विच का उपयोग करता है Cupertino नमूनाकृत पैलेट का प्रयोग करें पथ छोड़ें छवि आकार घटाये प्रसंस्करण से पहले छवि को छोटे आयामों तक छोटा कर दिया जाएगा, इससे औजार को तेजी से और सुरक्षित रूप से काम करने में मदद मिलती है न्यूनतम रंग अनुपात रेखाएं दहलीज निर्देशांक गोलाई सहिष्णुता पथ पैमाना गुण रीसेट करें छवियां को SVG में दी गई छवियों को SVG छवियों में ट्रेस करें यदि यह विकल्प सक्षम है तो परिमाणीकरण पैलेट का नमूना लिया जाएगा इस औजार का उपयोग बड़ी छवियों का अनुरेखण बिना आकार घटाये करने के लिए अनुशंसित नहीं है, इससे क्रैश हो सकता है और प्रसंस्करण समय बढ़ सकता है द्विघात दहलीज सभी गुण को तयशुदा मानों पर सेट किया जाएगा, ध्यान दें कि इस क्रिया को पूर्ववत नहीं किया जा सकता है विस्तृत तयशुदा रेखा चौड़ाई इंजन मोड LSTM नेटवर्क विरासत विरासत और LSTM बदलें छवि बैचों को दिए गए प्रारूप में बदलें नया फोल्डर जोड़ें प्रति नमूना बिट्स संपीड़न फोटोमीट्रिक व्याख्या प्रति पिक्सल नमूने तलीय विन्यास Y Cb Cr उप नमूनाकरण Y Cb Cr पोजिशनिंग X रेजोल्यूशन Y रेजोल्यूशन रेजोल्यूशन इकाई स्ट्रिप ऑफसेट पंक्तियां प्रति पट्टी स्ट्रिप बाइट गणना JPEG इंटरचेंज प्रारूप JPEG इंटरचेंज प्रारूप की लंबाई स्थानांतरण प्रकार्य सफेद बिन्दु प्राथमिक वर्णिकताएं Y Cb Cr गुणांक निर्माता संदर्भ काला सफ़ेद तिथि समय छवि विवरण मॉडल सॉफ्टवेयर कलाकार कॉपीराइट EXIF संस्करण Flashpix संस्करण रंग स्थान गामा पिक्सल X आयाम पिक्सल Y आयाम प्रति पिक्सल संपीड़ित बिट्स निर्माता टिप्पणी उपयोक्ता टिप्पणी संबंधित ध्वनि फाइल दिनांक समय मूल दिनांक समय डिजिटलीकृत ऑफसेट समय ऑफसेट समय मूल ऑफसेट समय डिजिटलीकृत उप-सेकंड समय उप-सेकंड समय मूल उप-सेकंड समय डिजिटलीकृत एक्सपोज़र समय F संख्या एक्सपोज़र प्रोग्राम वर्णक्रमीय संवेदनशीलता फोटोग्राफिक संवेदनशीलता OECF संवेदनशीलता प्रकार मानक आउटपुट संवेदनशीलता अनुशंसित एक्सपोज़र इंडेक्स ISO गति ISO गति अक्षांश yyy ISO गति अक्षांश zzz शटर गति मान एपर्चर मान चमक मान एक्सपोजर पूर्वाग्रह मान अधि एपर्चर मान विषय दूरी पैमाइश प्रणाली फ़्लैश विषय क्षेत्र फोकल लंबाई फोकल प्लेन Y रेजोल्यूशन फ़्लैश ऊर्जा स्थानिक आवृत्ति प्रतिक्रिया फोकल प्लेन X रेजोल्यूशन फोकल प्लेन रेजोल्यूशन यूनिट विषय स्थान एक्सपोजर सूचकांक संवेदन विधि फाइल स्रोत CFA पैटर्न तदनुकूल प्रतिपादित एक्सपोज़र मोड श्वेत संतुलन डिजिटल जूम अनुपात फोकल लंबाई In35mm फिल्म दृश्य कैप्चर प्रकार गेन नियंत्रण वैषम्य संतृप्ति पैनापन डिवाइस सेटिंग विवरण विषय की दूरी सीमा छवि विशिष्ट ID कैमरा मालिक का नाम बॉडी क्रमांक लेंस विशिष्टता लेंस निर्माता लेंस मॉडल लेंस क्रमांक GPS संस्करण ID GPS अक्षांश संदर्भ GPS अक्षांश GPS देशांतर संदर्भ GPS देशांतर GPS ऊंचाई संदर्भ GPS ऊंचाई GPS समय-चिह्न GPS उपग्रह GPS स्थिति GPS माप मोड GPS DOP GPS गति संदर्भ GPS गति GPS ट्रैक संदर्भ GPS ट्रैक GPS छवि दिशा संदर्भ GPS छवि दिशा GPS मानचित्र आधार GPS गंतव्य अक्षांश संदर्भ GPS गंतव्य अक्षांश GPS गंतव्य देशांतर संदर्भ GPS गंतव्य देशांतर GPS गंतव्य बियरिंग संदर्भ GPS गंतव्य दिक्कोण GPS गंतव्य दूरी संदर्भ GPS गंतव्य दूरी GPS प्रसंस्करण विधि GPS क्षेत्र सूचना GPS दिनांक मोहर GPS विभेदक GPS H पोजिशनिंग त्रुटि अंतरसंचालनीयता सूचकांक DNG संस्करण तयशुदा क्रॉप आकार पूर्वावलोकन छवि प्रारंभ करें छवि लंबाई का पूर्वावलोकन करें पहलू ढांचा सेंसर निचला बॉर्डर सेंसर बायां बॉर्डर सेंसर दायां बॉर्डर सेंसर शीर्ष बॉर्डर ISO दिए गए फ़ॉन्ट और रंग के साथ पथ पर पाठ बनाएं फॉन्ट आकार वॉटरमार्क आकार चयनित छवि को दिए गए पथ पर खींचने के लिए उसका उपयोग करें पाठ दोहराएँ वर्तमान पाठ को एक बार की आरेखण के बजाय पथ समाप्त होने तक दोहराया जाएगा डैश का आकार इस छवि का उपयोग खींचे गए पथ की दोहरावदार प्रविष्टि के रूप में किया जाएगा आरंभ बिंदु से अंत बिंदु तक रेखांकित त्रिभुज बनाता है आरंभ बिंदु से अंत बिंदु तक रेखांकित त्रिभुज बनाता है रेखांकित त्रिभुज त्रिभुज शिखर बहुभुज प्रारंभ बिंदु से अंतिम बिंदु तक बहुभुज बनाता है रेखांकित बहुभुज प्रारंभ बिंदु से अंत बिंदु तक रेखांकित बहुभुज बनाता है बहुभुज बनाएं जो मुक्त रूप के बजाय नियमित होगा प्रारंभ बिंदु से अंतिम बिंदु तक तारा खींचता है तारा नियमित बहुभुज बनाएं रेखांकित तारा आंतरिक त्रिज्या अनुपात प्रारंभ बिंदु से अंतिम बिंदु तक रेखांकित तारा बनाता है नियमित तारा बनाएं तारा बनाएं जो मुक्त रूप के बजाय नियमित होगा एंटीएलियास तेज किनारों को रोकने के लिए एंटीएलियासिंग सक्षम करता है पूर्वावलोकन के बजाय संपादन खोलें जब आप ImageToolbox में खोलने (पूर्वावलोकन) के लिए छवि का चयन करते हैं, तो पूर्वावलोकन के बजाय संपादन चयन पत्रक खोला जायेगा दस्तावेज़ स्कैनर PDF के रूप में सहेजें दस्तावेज़ों को स्कैन करें और उनसे PDF बनाएं या चित्र अलग करें स्कैनिंग शुरू करने के लिए क्लिक करें स्कैनिंग शुरू करें PDF के रूप में साझा करें नीचे दिया गया विकल्प छवियों को सहेजने के लिए है, PDF के लिए नहीं हिस्टोग्राम को समान करें HSV हिस्टोग्राम को समान करें प्रतिशत दर्ज करें पाठ क्षेत्र द्वारा प्रवेश की अनुमति दें पूर्व निर्धारित चयन के पीछे पाठ क्षेत्र को तुरंत दर्ज करने के लिए सक्षम करता है कलर स्पेस को स्केल करें रेखीय हिस्टोग्राम पिक्सलेशन को समान करें ग्रिड आकार X ग्रिड आकार Y हिस्टोग्राम अनुकूली को समान करें हिस्टोग्राम अनुकूली को समान करें LUV हिस्टोग्राम अनुकूली को समान करें LAB क्लैहे क्लैहे LAB क्लैहे LUV सामग्री के अनुसार काटें फ्रेम का रंग नज़रअंदाज़ करने के लिए रंग QR कोड स्कैन करें चयनित फाइल में कोई फिल्टर खाका डेटा नहीं है खाका बनाएं खाका नाम इस फिल्टर खाके का पूर्वावलोकन करने के लिए इस छवि का उपयोग किया जाएगा स्कैन किया गया QR कोड मान्य फिल्टर खाका नहीं है खाका फिल्टर QR कोड छवि के रूप में फाइल के रूप में फाइल के रूप में सहेजें QR कोड छवि के रूप में सहेजें टेम्प्लेट मिटाएं \"%1$s\" नाम के साथ फिल्टर खाका जोड़ा गया (%2$s) फिल्टर पूर्वावलोकन QR कोड QR कोड को स्कैन करें और उसकी सामग्री प्राप्त करें या नया कोड बनाने के लिए अपनी स्ट्रिंग पेस्ट करें कोड सामग्री क्षेत्र में सामग्री को बदलने के लिए QR कोड को स्कैन करें, या नया QR कोड उत्पन्न करने के लिए कुछ टाइप करें QR विवरण खाका कोई खाका फिल्टर नहीं जोड़ा गया नया बनाएं आप चयनित खाका फिल्टर को मिटाने वाले हैं। यह कार्रवाई पूर्ववत नहीं की जा सकती न्यून QR कोड स्कैन करने के लिए सेटिंग्स में कैमरे की अनुमति दें कलर ब्लाइंड योजना दिए गए रंग अंधापन प्रकार के लिए थीम रंगों को अनुकूलित करने के लिए मोड का चयन करें लाल और हरे रंग के बीच अंतर करने में कठिनाई हरे और लाल रंग के बीच अंतर करने में कठिनाई हरे रंग को समझने में असमर्थता नीले रंग को समझने में असमर्थता रंग बिल्कुल वैसे ही होंगे जैसे थीम में सेट किए गए हैं सिग्मोयडल लैग्रेंज 2 क्रम 2 का एक लैग्रेंज इंटरपोलेशन फ़िल्टर, जो सुचारू संक्रमण के साथ उच्च-गुणवत्ता वाली छवि स्केलिंग के लिए उपयुक्त है लैग्रेंज 3 लांक्ज़ोस 6 लांक्ज़ोस 6 जिंक घन बी-स्पलाइन हैंनिंग EWA उच्च गुणवत्ता वाले पुनः नमूनाकरण के लिए रॉबिडौक्स फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण ब्लैकमैन EWA कम एलियासिंग के साथ उच्च गुणवत्ता वाले रीसैंपलिंग के लिए लैंक्ज़ोस 3 जिंक फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण जिनसेंग जिनसेंग EWA लैंक्ज़ोस शार्प EWA हसन सॉफ्ट हमेशा के लिए खारिज करें हैमिंग हैनिंग ब्लैकमैन वेल्च द्विघात गॉसियन स्फिंक्स बार्टलेट रॉबिडौक्स रॉबिडौक्स शार्प स्प्लाइन 16 स्प्लाइन 36 स्प्लाइन 64 केईसर बार्टलेट-हैन बॉक्स Bohman लांक्ज़ोस 2 लांक्ज़ोस 3 लांक्ज़ोस 4 लांक्ज़ोस 2 जिंक लांक्ज़ोस 3 जिंक लांक्ज़ोस 4 जिंक Utilizes piecewise-defined polynomial functions to smoothly interpolate and approximate a curve or surface, flexible and continuous shape representation क्यूबिक इंटरपोलेशन निकटतम 16 पिक्सल पर विचार करके चिकनी स्केलिंग प्रदान करता है, जो द्विरेखीय इंटरपोलेशन की तुलना में बेहतर परिणाम देता है। हैन विंडो का एक प्रकार, जिसका उपयोग आमतौर पर सिग्नल प्रोसेसिंग अनुप्रयोगों में स्पेक्ट्रल लीकेज को कम करने के लिए किया जाता है सिग्नल के किनारों को पतला करके स्पेक्ट्रल लीकेज को कम करने के लिए उपयोग किया जाने वाला एक विंडो फ़ंक्शन, सिग्नल प्रोसेसिंग में उपयोगी एक विंडो फ़ंक्शन जो स्पेक्ट्रल रिसाव को न्यूनतम करके अच्छा आवृत्ति रिज़ॉल्यूशन प्रदान करता है, अक्सर सिग्नल प्रोसेसिंग में उपयोग किया जाता है एक विधि जो प्रक्षेप के लिए द्विघात फ़ंक्शन का उपयोग करती है, जो सुचारू और निरंतर परिणाम प्रदान करती है एक इंटरपोलेशन विधि जो गॉसियन फ़ंक्शन को लागू करती है, जो छवियों में शोर को कम करने और कम करने के लिए उपयोगी है एक विंडो फ़ंक्शन जिसे कम स्पेक्ट्रल रिसाव के साथ अच्छा आवृत्ति रिज़ॉल्यूशन देने के लिए डिज़ाइन किया गया है, अक्सर सिग्नल प्रोसेसिंग अनुप्रयोगों में उपयोग किया जाता है एक उन्नत रीसैंपलिंग विधि जो न्यूनतम कलाकृतियों के साथ उच्च गुणवत्ता वाला इंटरपोलेशन प्रदान करती है स्पेक्ट्रल रिसाव को कम करने के लिए सिग्नल प्रोसेसिंग में प्रयुक्त त्रिकोणीय विंडो फ़ंक्शन एक उच्च गुणवत्ता वाली इंटरपोलेशन विधि जो प्राकृतिक छवि आकार बदलने, तीक्ष्णता और चिकनाई को संतुलित करने के लिए अनुकूलित है एक स्प्लाइन-आधारित इंटरपोलेशन विधि जो 64-टैप फ़िल्टर का उपयोग करके सुचारू परिणाम प्रदान करती है एक इंटरपोलेशन विधि जो कैसर विंडो का उपयोग करती है, मुख्य-लोब चौड़ाई और साइड-लोब स्तर के बीच व्यापार-बंद पर अच्छा नियंत्रण प्रदान करती है रॉबिडौक्स विधि का एक अधिक स्पष्ट संस्करण, जो स्पष्ट छवि आकार परिवर्तन के लिए अनुकूलित है एक स्प्लाइन-आधारित इंटरपोलेशन विधि जो 16-टैप फ़िल्टर का उपयोग करके सुचारू परिणाम प्रदान करती है एक स्प्लाइन-आधारित इंटरपोलेशन विधि जो 36-टैप फ़िल्टर का उपयोग करके सुचारू परिणाम प्रदान करती है बार्टलेट और हन विंडो को मिलाकर एक हाइब्रिड विंडो फ़ंक्शन, जिसका उपयोग सिग्नल प्रोसेसिंग में स्पेक्ट्रल लीकेज को कम करने के लिए किया जाता है एक सरल पुनः नमूनाकरण विधि जो निकटतम पिक्सल मानों के औसत का उपयोग करती है, जिसके परिणामस्वरूप अक्सर एक ब्लॉकनुमा उपस्थिति प्राप्त होती है एक विंडो फ़ंक्शन जिसका उपयोग स्पेक्ट्रल रिसाव को कम करने के लिए किया जाता है, जो सिग्नल प्रोसेसिंग अनुप्रयोगों में अच्छा आवृत्ति रिज़ॉल्यूशन प्रदान करता है एक पुनः नमूनाकरण विधि जो न्यूनतम कलाकृतियों के साथ उच्च गुणवत्ता वाले प्रक्षेप के लिए 2-लोब लैंक्ज़ोस फ़िल्टर का उपयोग करती है एक पुनः नमूनाकरण विधि जो न्यूनतम कलाकृतियों के साथ उच्च गुणवत्ता वाले प्रक्षेप के लिए 3-लोब लैंक्ज़ोस फ़िल्टर का उपयोग करती है एक पुनः नमूनाकरण विधि जो न्यूनतम कलाकृतियों के साथ उच्च गुणवत्ता वाले प्रक्षेप के लिए 4-लोब लैंक्ज़ोस फ़िल्टर का उपयोग करती है लैंक्ज़ोस 3 फ़िल्टर का एक प्रकार जो जिंक फ़ंक्शन का उपयोग करता है, न्यूनतम कलाकृतियों के साथ उच्च गुणवत्ता वाला इंटरपोलेशन प्रदान करता है लैंक्ज़ोस 2 फ़िल्टर का एक प्रकार जो जिंक फ़ंक्शन का उपयोग करता है, न्यूनतम कलाकृतियों के साथ उच्च गुणवत्ता वाला इंटरपोलेशन प्रदान करता है लैंक्ज़ोस 4 फ़िल्टर का एक प्रकार जो जिंक फ़ंक्शन का उपयोग करता है, न्यूनतम कलाकृतियों के साथ उच्च गुणवत्ता वाला इंटरपोलेशन प्रदान करता है रॉबिडौक्स EWA सुचारू अंतर्वेशन और पुनः नमूनाकरण के लिए हैनिंग फिल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण क्वाड्रिक EWA सुचारू अंतर्वेशन के लिए क्वाड्रिक फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण रिंगिंग आर्टिफैक्ट्स को न्यूनतम करने के लिए ब्लैकमैन फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण रॉबिडौक्स शार्प EWA अधिक स्पष्ट परिणामों के लिए रॉबिडौक्स शार्प फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण लांक्ज़ोस 3 जिंक EWA तीक्ष्णता और सहजता के अच्छे संतुलन के साथ उच्च गुणवत्ता वाली छवि प्रसंस्करण के लिए डिज़ाइन किया गया एक रीसैंपलिंग फ़िल्टर बेहतर छवि गुणवत्ता के लिए जिनसेंग फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण न्यूनतम कलाकृतियों के साथ तीक्ष्ण परिणाम प्राप्त करने के लिए लैंक्ज़ोस शार्प फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण अत्यंत तीक्ष्ण छवि पुनः नमूनाकरण के लिए लैंक्ज़ोस 4 शार्पेस्ट फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण लैंक्ज़ोस सॉफ्ट EWA चिकनी छवि पुनः नमूनाकरण के लिए लैंक्ज़ोस सॉफ्ट फ़िल्टर का दीर्घवृत्तीय भारित औसत (EWA) संस्करण लांक्ज़ोस 4 शार्पेस्ट EWA हासन द्वारा डिजाइन किया गया एक रीसैंपलिंग फिल्टर, जो सहज और आर्टिफैक्ट-मुक्त छवि स्केलिंग के लिए है प्रारूप रूपांतरण छवियों के बैच को एक प्रारूप से दूसरे प्रारूप में परिवर्तित करें छवि स्टैकिंग चुने हुए मिश्रण मोड के साथ छवियों को एक दूसरे के ऊपर रखें छवि जोड़ें इक्वलाइज़ हिस्टोग्राम एडेप्टिव HSL समान हिस्टोग्राम अनुकूली HSV एज मोड क्लिप लपेटें नीले और पीले रंग के बीच अंतर करने में कठिनाई लाल रंग को समझने में असमर्थता सभी रंगों के प्रति संवेदनशीलता कम हो गई पूर्ण रंग अंधापन, केवल ग्रे रंग ही देख पाना कलर ब्लाइंड योजना का उपयोग न करें 6 के उच्च क्रम वाला लैंक्ज़ोस रीसैंपलिंग फ़िल्टर, जो अधिक स्पष्ट और अधिक सटीक छवि स्केलिंग प्रदान करता है लैंक्ज़ोस 6 फ़िल्टर का एक प्रकार जो बेहतर छवि रीसैंपलिंग गुणवत्ता के लिए जिंक फ़ंक्शन का उपयोग करता है ऑर्डर 3 का एक लैग्रेंज इंटरपोलेशन फ़िल्टर, जो छवि स्केलिंग के लिए बेहतर सटीकता और सुचारू परिणाम प्रदान करता है डिब्बों की संख्या क्लेहे HSL क्लेहे HSV रैखिक बॉक्स धुंधलापन रैखिक तम्बू धुंधलापन रैखिक गाऊसी बॉक्स धुंधलापन रैखिक स्टैक धुंधलापन गॉसियन बॉक्स धुंधलापन अपने ड्राइंग में ब्रश के रूप में उपयोग करने के लिए नीचे फ़िल्टर चुनें रैखिक तेज़ गाऊसी धुंधलापन अगला रैखिक तेज़ गाऊसी धुंधलापन रैखिक गाऊसी धुंधलापन पेंट के रूप में उपयोग करने के लिए एक फ़िल्टर चुनें फ़िल्टर बदलें TIFF संपीड़न योजना निम्न पाली रेत चित्रकारी छवि विभाजन एकल छवि को पंक्तियों या स्तंभों द्वारा विभाजित करें वांछित व्यवहार (पहलू अनुपात के अनुसार क्रॉप/समुचित) प्राप्त करने के लिए इस पैरामीटर के साथ क्रॉप आकार मोड को संयोजित करें निर्यात स्थान मध्य शीर्ष बाएं शीर्ष दाएं नीचे बाएं नीचे दाएं मध्य दायां निचला केंद्र मध्य बायां सीमा के समुचित भाषाएं सफलतापूर्वक आयात की गई OCR मॉडल का बैकअप लें आयात शीर्ष केंद्र रंगीन पोस्टर लक्ष्य छवि पैलेट स्थानांतरण उन्नत तेल सरल पुराना टीवी एचडीआर गोथम सरल रेखाचित्र हल्की चमक ट्राई टोन तीसरा रंग क्लाहे ओकलैब क्लाहे ओकल्छ क्लाहे जज्बज़ क्लस्टर्ड 4x4 डिथरिंग क्लस्टर्ड 8x8 डिथरिंग पोल्का डॉट क्लस्टर्ड 2x2 डिथरिंग यिलिलोमा डिथरिंग डायनामिक रंग चालू होने पर मोनेट का उपयोग नहीं किया जा सकता 512x512 2D एलयूटी लक्ष्य एलयूटी छवि अमाटोरका मिस एटिकेट नरम लालित्य नरम लालित्य संस्करण पसंदीदा जोड़ें कोई पसंदीदा विकल्प चयनित नहीं है, उन्हें टूल पृष्ठ में जोड़ें पूरक अनुरूप त्रियादिक विभाजित पूरक टेट्राडिक चौकोर अनुरूप + पूरक रंग उपकरण मिश्रण करें, टोन बनाएं, शेड्स बनाएं और बहुत कुछ रंग सामंजस्य रंग छायांकन भिन्नता टिंट टोन छाया रंग मिश्रण रंग जानकारी चयनित रंग मिश्रण करने के लिए रंग मोमबत्ती की रौशनी ड्रॉप ब्लूज़ पैलेट ट्रांसफर वैरिएंट लक्ष्य 3D एलयूटी फाइल (.cube / .CUBE) 3डी एलयूटी एलयूटी ब्लीच बाईपास एजी एम्बर पतझड़ के रंग फिल्म स्टॉक 50 धूमिल रात कोडैक न्यूट्रल LUT छवि प्राप्त करें पॉप आर्ट सेल्यूलाइड कॉफी हरापन सबसे पहले, न्यूट्रल LUT पर फिल्टर लगाने के लिए अपने पसंदीदा फोटो संपादन एप्लिकेशन का उपयोग करें जिसे आप यहां प्राप्त कर सकते हैं। इसके ठीक से काम करने के लिए प्रत्येक पिक्सल का रंग अन्य पिक्सल पर निर्भर नहीं होना चाहिए (उदाहरण के लिए धुंधलापन काम नहीं करेगा)। एक बार तैयार हो जाने पर, अपनी नई LUT छवि को 512*512 LUT फिल्टर के लिए इनपुट के रूप में उपयोग करें सुनहरा जंगल रेट्रो पीला दस्तावेज़ स्कैनर को स्कैन करने के लिए सेटिंग में कैमरे की अनुमति प्रदान करें लिंक उन स्थानों पर लिंक पूर्वावलोकन पुनर्प्राप्ति सक्षम करता है जहां आप पाठ प्राप्त कर सकते हैं (क्यूआर कोड, ओसीआर आदि) लिंक पूर्वावलोकन ICO फ़ाइलें केवल 256 x 256 के अधिकतम आकार में ही सहेजी जा सकती हैं GIF से WEBP GIF छवियों को WEBP एनिमेटेड चित्रों में बदलें वेबपी उपकरण छवियों को WEBP एनिमेटेड चित्र में बदलें या दिए गए WEBP एनीमेशन से फ़्रेम निकालें छवियों के लिए WEBP WEBP फ़ाइल को चित्रों के बैच में कनवर्ट करें छवियों के बैच को WEBP फ़ाइल में बदलें WEBP के लिए छवियाँ आरंभ करने के लिए WEBP छवि चुनें फ़ाइलों तक पूर्ण पहुंच नहीं JXL, QOI और अन्य छवियों को देखने के लिए सभी फ़ाइलों तक पहुंच की अनुमति दें जिन्हें Android पर छवियों के रूप में मान्यता नहीं दी गई है। अनुमति के बिना इमेज टूलबॉक्स उन छवियों को दिखाने में असमर्थ है डिफ़ॉल्ट ड्रा रंग डिफ़ॉल्ट ड्रा पथ मोड टाइमस्टैम्प जोड़ें टाइमस्टैम्प को आउटपुट फ़ाइल नाम में जोड़ने में सक्षम बनाता है स्वरूपित टाइमस्टैम्प मूल मिलिस के बजाय आउटपुट फ़ाइल नाम में टाइमस्टैम्प फ़ॉर्मेटिंग सक्षम करें टाइमस्टैम्प को उनके प्रारूप का चयन करने के लिए सक्षम करें वन टाइम सेव लोकेशन एक बार सहेजे गए स्थानों को देखें और संपादित करें जिनका उपयोग आप अधिकतर सभी विकल्पों में सेव बटन को लंबे समय तक दबाकर कर सकते हैं हाल ही में प्रयुक्त सीआई चैनल समूह टेलीग्राम में इमेज टूलबॉक्स 🎉 हमारी चैट में शामिल हों जहां आप अपनी इच्छानुसार किसी भी चीज़ पर चर्चा कर सकते हैं और सीआई चैनल भी देख सकते हैं जहां मैं बीटा और घोषणाएं पोस्ट करता हूं ऐप के नए संस्करणों के बारे में सूचना प्राप्त करें और घोषणाएँ पढ़ें किसी छवि को दिए गए आयामों में फ़िट करें और पृष्ठभूमि पर धुंधलापन या रंग लागू करें उपकरण व्यवस्था प्रकार के आधार पर उपकरण समूहित करें कस्टम सूची व्यवस्था के बजाय मुख्य स्क्रीन पर टूल को उनके प्रकार के आधार पर समूहित करें डिफॉल्ट मान सिस्टम बार्स दृश्यता स्वाइप द्वारा सिस्टम बार दिखाएँ यदि सिस्टम बार छिपे हुए हैं तो उन्हें दिखाने के लिए स्वाइप करने में सक्षम बनाता है ऑटो सभी को छिपाएं सब दिखाएं नेव बार छिपाएँ स्थिति पट्टी छुपाएँ शोर उत्पन्न करना पेर्लिन या अन्य प्रकार जैसे विभिन्न शोर उत्पन्न करें आवृत्ति शोर का प्रकार घूर्णन प्रकार भग्न प्रकार अष्टक कमी पाना भारित ताकत पिंग पोंग ताकत दूरी समारोह वापसी प्रकार घबराना डोमेन ताना संरेखण कस्टम फ़ाइल नाम स्थान और फ़ाइल नाम का चयन करें जिसका उपयोग वर्तमान छवि को सहेजने के लिए किया जाएगा कस्टम नाम वाले फ़ोल्डर में सहेजा गया समुच्चित चित्रकला का निर्माता अधिकतम 20 छवियों से कोलाज बनाएं कोलाज प्रकार स्थिति को समायोजित करने के लिए छवि को स्वैप करने, स्थानांतरित करने और ज़ूम करने के लिए दबाए रखें रोटेशन अक्षम करें दो-उंगली के इशारों से छवियों को घूमने से रोकता है सीमाओं पर स्नैपिंग सक्षम करें मूव करने या ज़ूम करने के बाद, छवियाँ फ़्रेम के किनारों को भरने के लिए स्नैप हो जाएंगी हिस्टोग्राम समायोजन करने में आपकी सहायता के लिए आरजीबी या चमक छवि हिस्टोग्राम इस छवि का उपयोग आरजीबी और ब्राइटनेस हिस्टोग्राम उत्पन्न करने के लिए किया जाएगा टेसेरैक्ट विकल्प टेसेरैक्ट इंजन के लिए कुछ इनपुट वेरिएबल लागू करें कस्टम विकल्प विकल्प इस पैटर्न का अनुसरण करते हुए दर्ज किए जाने चाहिए: \"--{option_name} {value}\" ऑटो क्रॉप निःशुल्क कोने छवि को बहुभुज द्वारा काटें, इससे परिप्रेक्ष्य भी सही हो जाता है ज़बरदस्ती छवि सीमा की ओर इशारा करती है अंक छवि सीमाओं द्वारा सीमित नहीं होंगे, यह अधिक सटीक परिप्रेक्ष्य सुधार के लिए उपयोगी है नकाब सामग्री जागरूक तैयार पथ के अंतर्गत भरें ठीक स्थान सर्किल कर्नेल का प्रयोग करें प्रारंभिक समापन रूपात्मक ढाल लंबा टोप बुरा व्यक्ति स्वर वक्र वक्र रीसेट करें कर्व्स को डिफ़ॉल्ट मान पर वापस ले जाया जाएगा रेखा शैली अंतराल का आकार धराशायी डॉट धराशायी स्टाम्प वक्र निर्दिष्ट अंतराल आकार के साथ खींचे गए पथ पर धराशायी रेखा खींचता है दिए गए पथ पर बिंदु और धराशायी रेखा खींचता है बस डिफ़ॉल्ट सीधी रेखाएँ निर्दिष्ट रिक्त स्थान के साथ पथ के साथ चयनित आकृतियाँ बनाता है पथ पर लहरदार टेढ़े-मेढ़े निशान बनाता है ज़िगज़ैग अनुपात शॉर्टकट बनाएं पिन करने के लिए टूल चुनें टूल आपके लॉन्चर की होम स्क्रीन पर शॉर्टकट के रूप में जोड़ा जाएगा, आवश्यक व्यवहार प्राप्त करने के लिए इसे \"फ़ाइल चुनना छोड़ें\" सेटिंग के साथ संयोजन करके उपयोग करें फ़्रेमों का ढेर न लगाएं पिछले फ़्रेमों का निपटान सक्षम बनाता है, ताकि वे एक-दूसरे पर ढेर न हों क्रॉसफ़ेड फ़्रेम एक-दूसरे में क्रॉसफ़ेड किए जाएंगे क्रॉसफ़ेड फ़्रेम गिनती दहलीज एक दहलीज दो चालाक दर्पण 101 उन्नत ज़ूम ब्लर लाप्लासियन सरल सोबेल सिंपल सहायक ग्रिड सटीक जोड़-तोड़ में मदद के लिए ड्राइंग क्षेत्र के ऊपर सहायक ग्रिड दिखाता है ग्रिड का रंग सेल चौड़ाई सेल की ऊंचाई कॉम्पैक्ट चयनकर्ता कुछ चयन नियंत्रण कम जगह लेने के लिए कॉम्पैक्ट लेआउट का उपयोग करेंगे छवि कैप्चर करने के लिए सेटिंग्स में कैमरे की अनुमति दें लेआउट मुख्य स्क्रीन शीर्षक स्थिर दर कारक (सीआरएफ) %1$s का मान धीमी गति से संपीड़न का मतलब है, जिसके परिणामस्वरूप फ़ाइल का आकार अपेक्षाकृत छोटा होता है। %2$s का अर्थ है तेज़ संपीड़न, जिसके परिणामस्वरूप एक बड़ी फ़ाइल बनती है। लुट लाइब्रेरी LUTs का संग्रह डाउनलोड करें, जिसे डाउनलोड करने के बाद आप आवेदन कर सकते हैं LUTs का अद्यतन संग्रह (केवल नए कतारबद्ध होंगे), जिसे आप डाउनलोड करने के बाद लागू कर सकते हैं फ़िल्टर के लिए डिफ़ॉल्ट छवि पूर्वावलोकन बदलें छवि का पूर्वावलोकन करें छिपाना दिखाओ स्लाइडर प्रकार कल्पना सामग्री 2 एक आकर्षक दिखने वाला स्लाइडर. यह डिफॉल्ट विकल्प है एक सामग्री 2 स्लाइडर एक मटेरियल यू स्लाइडर आवेदन करना केंद्र संवाद बटन यदि संभव हो तो संवादों के बटन बाईं ओर के बजाय केंद्र में स्थित होंगे ओपन सोर्स लाइसेंस इस ऐप में प्रयुक्त ओपन सोर्स लाइब्रेरीज़ के लाइसेंस देखें क्षेत्र पिक्सेल क्षेत्र संबंध का उपयोग करके पुन: नमूनाकरण। यह छवि विच्छेदन के लिए एक पसंदीदा तरीका हो सकता है, क्योंकि यह मौयर-मुक्त परिणाम देता है। लेकिन जब छवि ज़ूम की जाती है, तो यह \"निकटतम\" विधि के समान होती है। टोनमैपिंग सक्षम करें प्रवेश करना % साइट तक नहीं पहुंच सकते, वीपीएन का उपयोग करने का प्रयास करें या जांचें कि यूआरएल सही है या नहीं मार्कअप परतें छवियों, पाठ और बहुत कुछ को स्वतंत्र रूप से रखने की क्षमता के साथ परत मोड परत संपादित करें छवि पर परतें पृष्ठभूमि के रूप में एक छवि का उपयोग करें और उसके ऊपर विभिन्न परतें जोड़ें पृष्ठभूमि पर परतें पहले विकल्प के समान लेकिन छवि के बजाय रंग के साथ बीटा फास्ट सेटिंग्स साइड छवियों को संपादित करते समय चयनित पक्ष में एक फ्लोटिंग स्ट्रिप जोड़ें, जिस पर क्लिक करने पर तेज़ सेटिंग्स खुल जाएंगी स्पष्ट चयन सेटिंग समूह \"%1$s\" डिफ़ॉल्ट रूप से संक्षिप्त हो जाएगा सेटिंग समूह \"%1$s\" को डिफ़ॉल्ट रूप से विस्तारित किया जाएगा बेस64 उपकरण बेस64 स्ट्रिंग को छवि में डीकोड करें, या छवि को बेस64 प्रारूप में एनकोड करें बेस 64 प्रदत्त मान वैध बेस64 स्ट्रिंग नहीं है खाली या अमान्य बेस64 स्ट्रिंग की प्रतिलिपि नहीं बनाई जा सकती बेस64 चिपकाएँ बेस64 कॉपी करें बेस64 स्ट्रिंग को कॉपी या सहेजने के लिए छवि लोड करें। यदि आपके पास स्ट्रिंग ही है, तो आप छवि प्राप्त करने के लिए इसे ऊपर चिपका सकते हैं बेस64 सहेजें बेस64 साझा करें विकल्प कार्रवाई बेस64 आयात करें बेस64 क्रियाएँ रूपरेखा जोड़ें निर्दिष्ट रंग और चौड़ाई के साथ पाठ के चारों ओर रूपरेखा जोड़ें रूपरेखा रंग रूपरेखा का आकार ROTATION फ़ाइलनाम के रूप में चेकसम आउटपुट छवियों का नाम उनके डेटा चेकसम के अनुरूप होगा मुफ़्त सॉफ़्टवेयर (साझेदार) एंड्रॉइड एप्लिकेशन के पार्टनर चैनल में अधिक उपयोगी सॉफ़्टवेयर एल्गोरिदम चेकसम उपकरण चेकसम की तुलना करें, हैश की गणना करें या विभिन्न हैशिंग एल्गोरिदम का उपयोग करके फ़ाइलों से हेक्स स्ट्रिंग बनाएं गणना टेक्स्ट हैश अंततः, चयनित एल्गोरिदम के आधार पर इसके चेकसम की गणना करने के लिए फ़ाइल चुनें चयनित एल्गोरिदम के आधार पर इसके चेकसम की गणना करने के लिए टेक्स्ट दर्ज करें स्रोत चेकसम तुलना करने के लिए चेकसम मिलान! अंतर चेकसम बराबर हैं, यह सुरक्षित हो सकता है चेकसम बराबर नहीं हैं, फ़ाइल असुरक्षित हो सकती है! मेष ग्रेडिएंट्स मेश ग्रेजुएट्स का ऑनलाइन संग्रह देखें केवल टीटीएफ और ओटीएफ फ़ॉन्ट ही आयात किए जा सकते हैं फ़ॉन्ट आयात करें (TTF/OTF) फ़ॉन्ट निर्यात करें आयातित फ़ॉन्ट प्रयास सहेजते समय त्रुटि, आउटपुट फ़ोल्डर बदलने का प्रयास करें फ़ाइल नाम सेट नहीं है कोई नहीं कस्टम पेज पेज चयन टूल निकास पुष्टिकरण यदि आपके पास विशेष टूल का उपयोग करते समय सहेजे नहीं गए परिवर्तन हैं और इसे बंद करने का प्रयास करते हैं, तो पुष्टि संवाद दिखाया जाएगा EXIF संपादित करें पुनर्संपीड़न के बिना एकल छवि का मेटाडेटा बदलें उपलब्ध टैग संपादित करने के लिए टैप करें स्टीकर बदलें फ़िट चौड़ाई फ़िट ऊंचाई बैच तुलना चयनित एल्गोरिदम के आधार पर इसके चेकसम की गणना करने के लिए फ़ाइल/फ़ाइलें चुनें फ़ाइलें चुनें निर्देशिका चुनें सिर की लंबाई का पैमाना टिकट समय-चिह्न प्रारूप पैटर्न पैडिंग छवि काटना छवि वाले भाग को काटें और बाएँ भाग को ऊर्ध्वाधर या क्षैतिज रेखाओं द्वारा मर्ज करें (उलटा भी हो सकता है)। लंबवत धुरी रेखा क्षैतिज धुरी रेखा उलटा चयन कटे हुए क्षेत्र के आसपास के हिस्सों को मिलाने के बजाय, लंबवत कटे हुए हिस्से को छोड़ दिया जाएगा कटे हुए क्षेत्र के चारों ओर के हिस्सों को मिलाने के बजाय, क्षैतिज कटे हुए हिस्से को छोड़ दिया जाएगा मेष ग्रेडिएंट्स का संग्रह गांठों और रिज़ॉल्यूशन की कस्टम मात्रा के साथ जाल ढाल बनाएं मेष ग्रेडिएंट ओवरले दी गई छवियों के शीर्ष की जालीदार ढाल लिखें अंक अनुकूलन ग्रिड का आकार संकल्प एक्स संकल्प वाई संकल्प पिक्सेल दर पिक्सेल रंग हाइलाइट करें पिक्सेल तुलना प्रकार बारकोड स्कैन करें ऊंचाई अनुपात बारकोड प्रकार बी/डब्ल्यू लागू करें बारकोड छवि पूरी तरह से काली और सफेद होगी और ऐप की थीम के अनुसार रंगीन नहीं होगी किसी भी बारकोड (QR, EAN, AZTEC,…) को स्कैन करें और उसकी सामग्री प्राप्त करें या नया उत्पन्न करने के लिए अपना टेक्स्ट पेस्ट करें कोई बारकोड नहीं मिला जनरेट किया गया बारकोड यहां होगा ऑडियो कवर ऑडियो फ़ाइलों से एल्बम कवर छवियां निकालें, अधिकांश सामान्य प्रारूप समर्थित हैं आरंभ करने के लिए ऑडियो चुनें ऑडियो चुनें कोई कवर नहीं मिला लॉग भेजें ऐप लॉग फ़ाइल साझा करने के लिए क्लिक करें, इससे मुझे समस्या का पता लगाने और समस्याओं को ठीक करने में मदद मिल सकती है ओह! कुछ गलत हो गया है आप नीचे दिए गए विकल्पों का उपयोग करके मुझसे संपर्क कर सकते हैं और मैं समाधान खोजने का प्रयास करूंगा।\n(लॉग संलग्न करना न भूलें) फाइल करने के लिए लिखें छवियों के बैच से टेक्स्ट निकालें और इसे एक टेक्स्ट फ़ाइल में संग्रहीत करें मेटाडेटा पर लिखें प्रत्येक छवि से टेक्स्ट निकालें और उसे संबंधित फ़ोटो की EXIF ​​जानकारी में रखें अदृश्य मोड अपनी छवियों के बाइट्स के अंदर आंखों के लिए अदृश्य वॉटरमार्क बनाने के लिए स्टेग्नोग्राफ़ी का उपयोग करें एलएसबी का प्रयोग करें एलएसबी (कम महत्वपूर्ण बिट) स्टेग्नोग्राफ़ी विधि का उपयोग किया जाएगा, एफडी (फ़्रीक्वेंसी डोमेन) अन्यथा लाल आँखें स्वतः हटाएँ पासवर्ड अनलॉक पीडीएफ सुरक्षित है ऑपरेशन लगभग पूरा हो गया है. अब रद्द करने पर इसे पुनः प्रारंभ करने की आवश्यकता होगी डेटा संशोधित संशोधित तिथि (उलट) आकार आकार (उलटा) माइम प्रकार माइम प्रकार (उलटा) विस्तार एक्सटेंशन (उलटा) तिथि जोड़ी जोड़ी गई तारीख (उलट) बाएं से दायां दाएं से बाएं नीचे से ऊपर नीचे से शीर्ष तक तरल ग्लास हाल ही में घोषित IOS 26 और इसके लिक्विड ग्लास डिज़ाइन सिस्टम पर आधारित एक स्विच छवि चुनें या नीचे बेस64 डेटा चिपकाएँ/आयात करें आरंभ करने के लिए छवि लिंक टाइप करें लिंक पेस्ट करो बहुरूपदर्शक द्वितीयक कोण पक्षों चैनल मिक्स नीले हरे लाल नीला हरी लाल लाल रंग में हरे रंग में नीले रंग में सियान मैजेंटा पीला आंशिक रंग समोच्च स्तरों ओफ़्सेट वोरोनोई क्रिस्टलाइज़ आकार खींचना अनियमितता डेस्पेकल बिखरा हुआ कुत्ता दूसरा दायरा बराबर चमकना चक्कर और चुटकी बिंदुवार सीमा रंग ध्रुवीय निर्देशांक ध्रुवीय की ओर सीधा ध्रुवीय से आयताकार वृत्त में उलटा करें शोर कम करें सरल सोलराइज़ बुनना एक्स गैप वाई गैप एक्स चौड़ाई वाई चौड़ाई घुमाव रबड़ की मोहर धब्बा घनत्व मिक्स क्षेत्र लेंस विरूपण अपवर्तन सूचकांक आर्क फैला हुआ कोण चमक किरणों एएससीआईआई ढाल मेरी शरद ऋतु हड्डी जेट सर्दी महासागर गर्मी वसंत कूल वेरिएंट एचएसवी गुलाबी गर्म शब्द मेग्मा नरक प्लाज्मा विरिडीस नागरिकों सांझ गोधूलि स्थानांतरित परिप्रेक्ष्य ऑटो डेस्क्यू फसल की अनुमति दें फसल या परिप्रेक्ष्य निरपेक्ष टर्बो गहरा हरा लेंस सुधार JSON प्रारूप में लक्ष्य लेंस प्रोफ़ाइल फ़ाइल तैयार लेंस प्रोफ़ाइल डाउनलोड करें भाग प्रतिशत JSON के रूप में निर्यात करें json प्रतिनिधित्व के रूप में पैलेट डेटा के साथ स्ट्रिंग की प्रतिलिपि बनाएँ सीवन नक्काशी होम स्क्रीन लॉक स्क्रीन में निर्मित वॉलपेपर निर्यात ताज़ा करना वर्तमान होम, लॉक और अंतर्निर्मित वॉलपेपर प्राप्त करें सभी फ़ाइलों तक पहुंच की अनुमति दें, वॉलपेपर पुनः प्राप्त करने के लिए यह आवश्यक है बाह्य संग्रहण प्रबंधित करने की अनुमति पर्याप्त नहीं है, आपको अपनी छवियों तक पहुंच की अनुमति देनी होगी, सुनिश्चित करें कि \"सभी को अनुमति दें\" का चयन करें फ़ाइल नाम में प्रीसेट जोड़ें छवि फ़ाइल नाम में चयनित प्रीसेट के साथ प्रत्यय जोड़ता है फ़ाइल नाम में छवि स्केल मोड जोड़ें छवि फ़ाइल नाम में चयनित छवि स्केल मोड के साथ प्रत्यय जोड़ता है आस्की कला चित्र को एएससीआई टेक्स्ट में बदलें जो छवि जैसा दिखेगा पैरामीटर कुछ मामलों में बेहतर परिणाम के लिए छवि पर नकारात्मक फ़िल्टर लागू करता है स्क्रीनशॉट संसाधित हो रहा है स्क्रीनशॉट कैप्चर नहीं हुआ, पुनः प्रयास करें सहेजना छोड़ दिया गया %1$s फ़ाइलें छोड़ दी गईं यदि बड़ा हो तो छोड़ें की अनुमति दें यदि परिणामी फ़ाइल का आकार मूल से बड़ा होगा तो कुछ टूल को छवियों को सहेजना छोड़ने की अनुमति दी जाएगी कैलेंडर इवेंट संपर्क ईमेल जगह फ़ोन मूलपाठ एसएमएस यूआरएल वाईफ़ाई नेटवर्क खोलें एन/ए एसएसआईडी फ़ोन संदेश पता विषय शरीर नाम संगठन शीर्षक फ़ोनों ईमेल यूआरएल पतों सारांश विवरण जगह व्यवस्था करनेवाला आरंभ करने की तिथि अंतिम तिथि स्थिति अक्षांश देशान्तर बारकोड बनाएं बारकोड संपादित करें वाई-फ़ाई कॉन्फ़िगरेशन सुरक्षा संपर्क चुनें चयनित संपर्क का उपयोग करके सेटिंग्स में संपर्कों को स्वतः भरण की अनुमति दें संपर्क सूचना पहला नाम मध्य नाम उपनाम उच्चारण फ़ोन जोड़ें ईमेल जोड़ें पता जोड़ें वेबसाइट वेबसाइट जोड़ें स्वरूपित नाम इस छवि का उपयोग बारकोड के ऊपर लगाने के लिए किया जाएगा कोड अनुकूलन इस छवि का उपयोग क्यूआर कोड के केंद्र में लोगो के रूप में किया जाएगा प्रतीक चिन्ह लोगो पैडिंग लोगो का आकार लोगो के कोने चौथी आँख निचले सिरे के कोने पर चौथी आँख जोड़कर क्यूआर कोड में नेत्र समरूपता जोड़ता है पिक्सेल आकार फ़्रेम का आकार गेंद का आकार त्रुटि सुधार स्तर गहरा रंग हल्के रंग हाइपर ओएस Xiaomi हाइपरओएस जैसा स्टाइल मुखौटा पैटर्न यह कोड स्कैन करने योग्य नहीं हो सकता है, इसे सभी उपकरणों के साथ पढ़ने योग्य बनाने के लिए स्वरूप पैरामीटर बदलें स्कैन करने योग्य नहीं टूल अधिक कॉम्पैक्ट होने के लिए होम स्क्रीन ऐप लॉन्चर की तरह दिखेंगे लॉन्चर मोड किसी क्षेत्र को चयनित ब्रश और शैली से भर देता है बाढ़ भरण फुहार भित्तिचित्र शैली वाला पथ बनाता है चौकोर कण स्प्रे कण वृत्तों के बजाय चौकोर आकार के होंगे पैलेट उपकरण छवि से अपने पैलेट में मूल/सामग्री उत्पन्न करें, या विभिन्न पैलेट प्रारूपों में आयात/निर्यात करें पैलेट संपादित करें विभिन्न प्रारूपों में निर्यात/आयात पैलेट रंग का नाम पैलेट नाम पैलेट प्रारूप उत्पन्न पैलेट को विभिन्न प्रारूपों में निर्यात करें मौजूदा पैलेट में नया रंग जोड़ता है %1$s प्रारूप पैलेट नाम प्रदान करने का समर्थन नहीं करता Play Store नीतियों के कारण, इस सुविधा को वर्तमान बिल्ड में शामिल नहीं किया जा सकता है। इस कार्यक्षमता तक पहुँचने के लिए, कृपया वैकल्पिक स्रोत से ImageToolbox डाउनलोड करें। आप नीचे GitHub पर उपलब्ध बिल्ड पा सकते हैं। जीथब पेज खोलें मूल फ़ाइल को चयनित फ़ोल्डर में सहेजने के बजाय नई फ़ाइल से बदल दिया जाएगा छिपे हुए वॉटरमार्क टेक्स्ट का पता लगाया गया छिपी हुई वॉटरमार्क छवि का पता लगाया गया यह छवि छिपाई गई थी जनरेटिव इनपेंटिंग आपको OpenCV पर भरोसा किए बिना, AI मॉडल का उपयोग करके किसी छवि में ऑब्जेक्ट को हटाने की अनुमति देता है। इस सुविधा का उपयोग करने के लिए, ऐप GitHub से आवश्यक मॉडल (~200 एमबी) डाउनलोड करेगा आपको OpenCV पर भरोसा किए बिना, AI मॉडल का उपयोग करके किसी छवि में ऑब्जेक्ट को हटाने की अनुमति देता है। यह लंबे समय तक चलने वाला ऑपरेशन हो सकता है त्रुटि स्तर विश्लेषण चमक प्रवणता औसत दूरी मूव डिटेक्शन कॉपी करें बनाए रखना गुणक क्लिपबोर्ड डेटा बहुत बड़ा है कॉपी करने के लिए डेटा बहुत बड़ा है सरल बुनाई पिक्सेलकरण क्रमबद्ध पिक्सेलकरण क्रॉस पिक्सेलाइज़ेशन माइक्रो मैक्रो पिक्सेलाइजेशन कक्षीय पिक्सेलकरण भंवर पिक्सेलकरण पल्स ग्रिड पिक्सेलाइजेशन न्यूक्लियस पिक्सेलाइजेशन रेडियल बुनाई पिक्सेलाइजेशन uri \"%1$s\" नहीं खुल सकता बर्फबारी मोड सक्रिय सीमा फ़्रेम गड़बड़ संस्करण चैनल शिफ्ट अधिकतम ऑफसेट वीएचएस ब्लॉक गड़बड़ी ब्लॉक का आकार सीआरटी वक्रता वक्रता क्रोमा पिक्सेल पिघल गया मैक्स ड्रॉप एआई उपकरण एआई मॉडल के माध्यम से छवियों को संसाधित करने के लिए विभिन्न उपकरण जैसे आर्टिफैक्ट हटाना या डीनोइज़िंग संपीड़न, दांतेदार रेखाएं कार्टून, प्रसारण संपीड़न सामान्य संपीड़न, सामान्य शोर बेरंग कार्टून शोर तेज़, सामान्य संपीड़न, सामान्य शोर, एनीमेशन/कॉमिक्स/एनीमे पुस्तक स्कैनिंग एक्सपोज़र सुधार सामान्य संपीड़न, रंगीन छवियों में सर्वश्रेष्ठ सामान्य संपीड़न, ग्रेस्केल छवियों में सर्वश्रेष्ठ सामान्य संपीड़न, ग्रेस्केल छवियां, मजबूत सामान्य शोर, रंगीन छवियाँ सामान्य शोर, रंगीन चित्र, बेहतर विवरण सामान्य शोर, ग्रेस्केल छवियां सामान्य शोर, ग्रेस्केल छवियां, अधिक मजबूत सामान्य शोर, ग्रेस्केल छवियां, सबसे मजबूत सामान्य संपीड़न सामान्य संपीड़न टेक्सचराइज़ेशन, h264 संपीड़न वीएचएस संपीड़न गैर-मानक संपीड़न (सिनेपैक, एमएसवीडियो1, आरओक्यू) बिंक संपीड़न, ज्यामिति पर बेहतर बिंक संपीड़न, मजबूत बिंक संपीड़न, नरम, विवरण बरकरार रखता है सीढ़ी-चरण प्रभाव को समाप्त करना, चौरसाई करना स्कैन की गई कला/चित्र, हल्का संपीड़न, मौयर रंग बैंडिंग धीरे-धीरे, हाफ़टोन हटाते हुए ग्रेस्केल/बीडब्ल्यू छवियों के लिए सामान्य कलराइज़र, बेहतर परिणामों के लिए DDColor का उपयोग करें किनारा हटाना अत्यधिक तीक्ष्णता को दूर करता है धीमा, डगमगाता हुआ एंटी-अलियासिंग, सामान्य कलाकृतियाँ, सीजीआई KDM003 स्कैन प्रोसेसिंग हल्के वजन वाली छवि वृद्धि मॉडल संपीड़न विरूपण साक्ष्य हटाना संपीड़न विरूपण साक्ष्य हटाना सहज परिणामों के साथ पट्टी हटाना हाफ़टोन पैटर्न प्रसंस्करण दिदर पैटर्न हटाना V3 JPEG विरूपण साक्ष्य निष्कासन V2 H.264 बनावट संवर्द्धन वीएचएस को तेज़ करना और बढ़ाना विलय खंड आकार ओवरलैप आकार %1$s px से अधिक की छवियों को टुकड़ों में काटा और संसाधित किया जाएगा, दृश्यमान सीमों को रोकने के लिए ओवरलैप इन्हें मिश्रित करता है। बड़े आकार निम्न-स्तरीय उपकरणों के साथ अस्थिरता पैदा कर सकते हैं आरंभ करने के लिए एक का चयन करें क्या आप %1$s मॉडल को हटाना चाहते हैं? आपको इसे दोबारा डाउनलोड करना होगा पुष्टि करना मॉडल डाउनलोड किए गए मॉडल उपलब्ध मॉडल तैयारी सक्रिय मॉडल सत्र खोलने में विफल केवल .onnx/.ort मॉडल आयात किए जा सकते हैं आयात मॉडल आगे उपयोग के लिए कस्टम ओएनएक्स मॉडल आयात करें, केवल ओएनएक्स/ओआरटी मॉडल स्वीकार किए जाते हैं, लगभग सभी एसर्गन जैसे वेरिएंट का समर्थन करता है आयातित मॉडल सामान्य शोर, रंगीन चित्र सामान्य शोर, रंगीन चित्र, अधिक मजबूत सामान्य शोर, रंगीन छवियाँ, सबसे मजबूत बिखरती कलाकृतियों और रंग बैंडिंग को कम करता है, चिकनी ग्रेडिएंट और सपाट रंग क्षेत्रों में सुधार करता है। प्राकृतिक रंगों को संरक्षित करते हुए संतुलित हाइलाइट्स के साथ छवि की चमक और कंट्रास्ट को बढ़ाता है। विवरण बनाए रखते हुए और ओवरएक्सपोज़र से बचते हुए गहरे रंग की छवियों को चमकाता है। अत्यधिक रंग टोनिंग को हटाता है और अधिक तटस्थ और प्राकृतिक रंग संतुलन बहाल करता है। बारीक विवरण और बनावट को संरक्षित करने पर जोर देने के साथ पॉइसन-आधारित शोर टोनिंग लागू करता है। सहज और कम आक्रामक दृश्य परिणामों के लिए नरम पॉइसन शोर टोनिंग लागू करता है। समान शोर टोनिंग विवरण संरक्षण और छवि स्पष्टता पर केंद्रित है। सूक्ष्म बनावट और चिकनी उपस्थिति के लिए सौम्य समान शोर टोनिंग। कलाकृतियों को दोबारा रंगकर और छवि स्थिरता में सुधार करके क्षतिग्रस्त या असमान क्षेत्रों की मरम्मत करता है। हल्के डिबैंडिंग मॉडल जो न्यूनतम प्रदर्शन लागत के साथ रंग बैंडिंग को हटा देता है। बेहतर स्पष्टता के लिए छवियों को बहुत उच्च संपीड़न कलाकृतियों (0-20% गुणवत्ता) के साथ अनुकूलित करता है। उच्च संपीड़न कलाकृतियों (20-40% गुणवत्ता) के साथ छवियों को बढ़ाता है, विवरण बहाल करता है और शोर को कम करता है। मध्यम संपीड़न (40-60% गुणवत्ता) के साथ छवियों को बेहतर बनाता है, तीक्ष्णता और चिकनाई को संतुलित करता है। सूक्ष्म विवरण और बनावट को बढ़ाने के लिए छवियों को हल्के संपीड़न (60-80% गुणवत्ता) के साथ परिष्कृत करता है। प्राकृतिक लुक और विवरण को संरक्षित करते हुए लगभग दोषरहित छवियों (80-100% गुणवत्ता) को थोड़ा बढ़ाता है। सरल और तेज़ रंगीकरण, कार्टून, आदर्श नहीं कलाकृतियों को पेश किए बिना छवि के धुंधलेपन को थोड़ा कम करता है, तीक्ष्णता में सुधार करता है। लंबे समय तक चलने वाले ऑपरेशन छवि प्रसंस्करण प्रसंस्करण बहुत कम गुणवत्ता वाली छवियों (0-20%) में भारी JPEG संपीड़न कलाकृतियों को हटा देता है। अत्यधिक संपीड़ित छवियों (20-40%) में मजबूत JPEG कलाकृतियों को कम करता है। छवि विवरण (40-60%) संरक्षित करते हुए मध्यम जेपीईजी कलाकृतियों को साफ करता है। प्रकाश JPEG कलाकृतियों को काफी उच्च गुणवत्ता वाली छवियों (60-80%) में परिष्कृत करता है। लगभग दोषरहित छवियों (80-100%) में मामूली जेपीईजी कलाकृतियों को सूक्ष्मता से कम कर देता है। बारीक विवरण और बनावट को बढ़ाता है, भारी कलाकृतियों के बिना कथित तीक्ष्णता में सुधार करता है। प्रसंस्करण समाप्त हो गया प्रसंस्करण विफल रहा गति के लिए अनुकूलित, प्राकृतिक लुक बनाए रखते हुए त्वचा की बनावट और विवरण को बढ़ाता है। JPEG संपीड़न कलाकृतियों को हटाता है और संपीड़ित फ़ोटो के लिए छवि गुणवत्ता पुनर्स्थापित करता है। कम रोशनी की स्थिति में ली गई तस्वीरों में आईएसओ शोर को कम करता है, विवरण संरक्षित करता है। ओवरएक्सपोज़्ड या \"जंबो\" हाइलाइट्स को ठीक करता है और बेहतर टोनल संतुलन बहाल करता है। हल्का और तेज़ रंगीकरण मॉडल जो ग्रेस्केल छवियों में प्राकृतिक रंग जोड़ता है। DEJPEG डेनोइज़ रंग दें कलाकृतियों बढ़ाना एनिमे स्कैन एक उच्च स्तरीय सामान्य छवियों के लिए X4 अपस्केलर; छोटा मॉडल जो कम जीपीयू और समय का उपयोग करता है, मध्यम डिब्लर और डीनोइज़ के साथ। सामान्य छवियों, बनावट और प्राकृतिक विवरणों को संरक्षित करने के लिए X2 अपस्केलर। उन्नत बनावट और यथार्थवादी परिणामों के साथ सामान्य छवियों के लिए X4 अपस्केलर। एनीमे छवियों के लिए अनुकूलित X4 अपस्केलर; स्पष्ट रेखाओं और विवरणों के लिए 6 आरआरडीबी ब्लॉक। MSE हानि के साथ X4 अपस्केलर, सामान्य छवियों के लिए बेहतर परिणाम और कम कलाकृतियाँ उत्पन्न करता है। X4 अपस्केलर एनीमे छवियों के लिए अनुकूलित; अधिक स्पष्ट विवरण और चिकनी रेखाओं वाला 4B32F संस्करण। सामान्य छवियों के लिए X4 UltraSharp V2 मॉडल; तीक्ष्णता और स्पष्टता पर जोर देता है। X4 अल्ट्राशार्प V2 लाइट; तेज़ और छोटा, कम GPU मेमोरी का उपयोग करते हुए विवरण को सुरक्षित रखता है। त्वरित पृष्ठभूमि हटाने के लिए हल्का मॉडल। संतुलित प्रदर्शन और सटीकता। पोर्ट्रेट, वस्तुओं और दृश्यों के साथ काम करता है। अधिकांश उपयोग के मामलों के लिए अनुशंसित. बीजी हटाएँ क्षैतिज सीमा मोटाई ऊर्ध्वाधर सीमा मोटाई %1$s रंग %1$s रंग वर्तमान मॉडल चंकिंग का समर्थन नहीं करता है, छवि को मूल आयामों में संसाधित किया जाएगा, इससे उच्च मेमोरी खपत और लो-एंड डिवाइस के साथ समस्याएं हो सकती हैं चंकिंग अक्षम, छवि को मूल आयामों में संसाधित किया जाएगा, इससे उच्च मेमोरी खपत और कम-अंत डिवाइसों के साथ समस्याएं हो सकती हैं लेकिन अनुमान लगाने पर बेहतर परिणाम मिल सकते हैं ठस पृष्ठभूमि हटाने के लिए उच्च सटीकता छवि विभाजन मॉडल कम मेमोरी उपयोग के साथ तेजी से पृष्ठभूमि हटाने के लिए U2Net का हल्का संस्करण। पूर्ण DDColor मॉडल न्यूनतम कलाकृतियों के साथ सामान्य छवियों के लिए उच्च गुणवत्ता वाला रंगीकरण प्रदान करता है। सभी रंगीकरण मॉडलों का सर्वोत्तम विकल्प। DDColor प्रशिक्षित और निजी कलात्मक डेटासेट; कम अवास्तविक रंग कलाकृतियों के साथ विविध और कलात्मक रंगीकरण परिणाम उत्पन्न करता है। सटीक पृष्ठभूमि हटाने के लिए स्विन ट्रांसफार्मर पर आधारित हल्के BiRefNet मॉडल। तेज किनारों और उत्कृष्ट विवरण संरक्षण के साथ उच्च गुणवत्ता वाली पृष्ठभूमि हटाना, विशेष रूप से जटिल वस्तुओं और पेचीदा पृष्ठभूमि पर। पृष्ठभूमि हटाने वाला मॉडल जो चिकने किनारों के साथ सटीक मास्क तैयार करता है, जो सामान्य वस्तुओं और मध्यम विवरण संरक्षण के लिए उपयुक्त है। मॉडल पहले ही डाउनलोड हो चुका है मॉडल सफलतापूर्वक आयात किया गया प्रकार कीवर्ड बहुत तेज सामान्य धीमा बहुत धीमी गति से प्रतिशत की गणना करें न्यूनतम मान %1$s है उंगलियों से चित्र बनाकर विकृत करें ताना कठोरता ताना मोड कदम बढ़ना सिकुड़ना भंवर सीडब्ल्यू भंवर CCW फीकी ताकत शीर्ष ड्रॉप नीचे की बूंद ड्रॉप प्रारंभ करें अंत ड्रॉप डाउनलोड चिकनी आकृतियाँ चिकनी, अधिक प्राकृतिक आकृतियों के लिए मानक गोल आयतों के बजाय सुपरलिप्स का उपयोग करें आकार प्रकार काटना गोल चिकना बिना गोलाई के तेज किनारे क्लासिक गोलाकार कोने आकृतियाँ प्रकार कोनों का आकार स्क्विर्कल सुंदर गोलाकार यूआई तत्व फ़ाइल नाम प्रारूप फ़ाइल नाम की शुरुआत में कस्टम टेक्स्ट रखा गया है, जो प्रोजेक्ट नाम, ब्रांड या व्यक्तिगत टैग के लिए बिल्कुल उपयुक्त है। बिना एक्सटेंशन के मूल फ़ाइल नाम का उपयोग करता है, जिससे आपको स्रोत पहचान बरकरार रखने में मदद मिलती है। पिक्सेल में छवि की चौड़ाई, रिज़ॉल्यूशन परिवर्तन या स्केलिंग परिणामों को ट्रैक करने के लिए उपयोगी है। पिक्सेल में छवि की ऊंचाई, पहलू अनुपात या निर्यात के साथ काम करते समय सहायक होती है। अद्वितीय फ़ाइल नामों की गारंटी के लिए यादृच्छिक अंक उत्पन्न करता है; डुप्लिकेट के विरुद्ध अतिरिक्त सुरक्षा के लिए अधिक अंक जोड़ें। बैच निर्यात के लिए ऑटो-इंक्रीमेंटिंग काउंटर, एक सत्र में एकाधिक छवियों को सहेजते समय आदर्श। लागू प्रीसेट नाम को फ़ाइल नाम में सम्मिलित करता है ताकि आप आसानी से याद रख सकें कि छवि कैसे संसाधित की गई थी। प्रसंस्करण के दौरान उपयोग किए गए छवि स्केलिंग मोड को प्रदर्शित करता है, जो आकार, क्रॉप या फिट की गई छवियों को अलग करने में मदद करता है। फ़ाइल नाम के अंत में कस्टम टेक्स्ट रखा गया है, जो _v2, _edited, या _final जैसे संस्करण के लिए उपयोगी है। फ़ाइल एक्सटेंशन (पीएनजी, जेपीजी, वेबपी, आदि), स्वचालित रूप से वास्तविक सहेजे गए प्रारूप से मेल खाता है। एक अनुकूलन योग्य टाइमस्टैम्प जो आपको सही सॉर्टिंग के लिए जावा विनिर्देश द्वारा अपना स्वयं का प्रारूप परिभाषित करने देता है। फ़्लिंग प्रकार एंड्रॉइड नेटिव आईओएस स्टाइल चिकना वक्र जल्दी बंद Bouncy हलका तेज़ अत्यंत चिकना अनुकूली अभिगम्यता जागरूक कम गति बेसलाइन तुलना के लिए मूल एंड्रॉइड स्क्रॉल भौतिकी सामान्य उपयोग के लिए संतुलित, सहज स्क्रॉलिंग उच्च घर्षण आईओएस जैसा स्क्रॉल व्यवहार विशिष्ट स्क्रॉल अनुभव के लिए अद्वितीय तख़्ता वक्र त्वरित रोक के साथ सटीक स्क्रॉलिंग चंचल, प्रतिक्रियाशील उछालभरी स्क्रॉल सामग्री ब्राउज़िंग के लिए लंबी, फिसलती हुई स्क्रॉल इंटरैक्टिव यूआई के लिए त्वरित, प्रतिक्रियाशील स्क्रॉलिंग विस्तारित गति के साथ प्रीमियम सहज स्क्रॉलिंग फ़्लिंग वेग के आधार पर भौतिकी को समायोजित करता है सिस्टम एक्सेसिबिलिटी सेटिंग्स का सम्मान करता है पहुंच आवश्यकताओं के लिए न्यूनतम गति प्राथमिक पंक्तियाँ प्रत्येक पाँचवीं पंक्ति में मोटी रेखा जोड़ता है रंग भरना छिपे हुए उपकरण साझा करने के लिए छिपे हुए उपकरण रंग पुस्तकालय रंगों का विशाल संग्रह ब्राउज़ करें प्राकृतिक विवरण बनाए रखते हुए छवियों से धुंधलापन को तेज और हटा देता है, जो फोकस से बाहर की तस्वीरों को ठीक करने के लिए आदर्श है। खोए हुए विवरण और बनावट को पुनर्प्राप्त करते हुए, पहले से आकार बदल दी गई छवियों को बुद्धिमानी से पुनर्स्थापित करता है। लाइव-एक्शन सामग्री के लिए अनुकूलित, संपीड़न कलाकृतियों को कम करता है और मूवी/टीवी शो फ्रेम में बारीक विवरण बढ़ाता है। वीएचएस-गुणवत्ता वाले फ़ुटेज को एचडी में परिवर्तित करता है, टेप के शोर को हटाता है और पुराने अनुभव को संरक्षित करते हुए रिज़ॉल्यूशन को बढ़ाता है। टेक्स्ट-भारी छवियों और स्क्रीनशॉट के लिए विशेषीकृत, अक्षरों को तेज़ करता है और पठनीयता में सुधार करता है। विविध डेटासेट पर प्रशिक्षित उन्नत अपस्केलिंग, सामान्य प्रयोजन फोटो एन्हांसमेंट के लिए उत्कृष्ट। वेब-संपीड़ित फ़ोटो के लिए अनुकूलित, JPEG कलाकृतियों को हटाता है और प्राकृतिक स्वरूप को पुनर्स्थापित करता है। बेहतर बनावट संरक्षण और कलाकृतियों में कमी के साथ वेब फ़ोटो के लिए उन्नत संस्करण। डुअल एग्रीगेशन ट्रांसफार्मर तकनीक के साथ 2x अपस्केलिंग, तीक्ष्णता और प्राकृतिक विवरण बनाए रखता है। उन्नत ट्रांसफॉर्मर आर्किटेक्चर का उपयोग करके 3x अपस्केलिंग, मध्यम विस्तार आवश्यकताओं के लिए आदर्श। अत्याधुनिक ट्रांसफार्मर नेटवर्क के साथ 4x उच्च-गुणवत्ता वाला अपस्केलिंग, बड़े पैमाने पर बारीक विवरणों को संरक्षित करता है। फ़ोटो से धुंधलापन/शोर और कंपन हटाता है। सामान्य प्रयोजन लेकिन तस्वीरों में सर्वोत्तम। BSRGAN गिरावट के लिए अनुकूलित Swin2SR ट्रांसफार्मर का उपयोग करके निम्न-गुणवत्ता वाली छवियों को पुनर्स्थापित करता है। भारी संपीड़न कलाकृतियों को ठीक करने और 4x पैमाने पर विवरण बढ़ाने के लिए बढ़िया। बीएसआरजीएएन गिरावट पर प्रशिक्षित स्विनआईआर ट्रांसफार्मर के साथ 4x अपस्केलिंग। फ़ोटो और जटिल दृश्यों में तेज़ बनावट और अधिक प्राकृतिक विवरण के लिए GAN का उपयोग करता है। पथ पीडीएफ मर्ज करें एकाधिक पीडीएफ फाइलों को एक दस्तावेज़ में संयोजित करें फ़ाइलें आदेश पीपी. पीडीएफ को विभाजित करें पीडीएफ दस्तावेज़ से विशिष्ट पृष्ठ निकालें पीडीएफ घुमाएँ पेज ओरिएंटेशन को स्थायी रूप से ठीक करें पृष्ठों पीडीएफ को पुनर्व्यवस्थित करें पृष्ठों को पुनः व्यवस्थित करने के लिए उन्हें खींचें और छोड़ें पेजों को पकड़ें और खींचें पेज नंबर अपने दस्तावेज़ों में स्वचालित रूप से क्रमांकन जोड़ें लेबल प्रारूप पीडीएफ से टेक्स्ट (ओसीआर) अपने पीडीएफ दस्तावेजों से सादा पाठ निकालें ब्रांडिंग या सुरक्षा के लिए कस्टम टेक्स्ट को ओवरले करें हस्ताक्षर किसी भी दस्तावेज़ में अपना इलेक्ट्रॉनिक हस्ताक्षर जोड़ें इसका उपयोग हस्ताक्षर के रूप में किया जाएगा पीडीएफ अनलॉक करें अपनी सुरक्षित फ़ाइलों से पासवर्ड हटाएँ पीडीएफ को सुरक्षित रखें अपने दस्तावेज़ों को मजबूत एन्क्रिप्शन के साथ सुरक्षित करें सफलता पीडीएफ अनलॉक हो गया है, आप इसे सहेज सकते हैं या साझा कर सकते हैं पीडीएफ की मरम्मत करें दूषित या अपठनीय दस्तावेज़ों को ठीक करने का प्रयास करें स्केल सभी दस्तावेज़ एम्बेडेड छवियों को ग्रेस्केल में बदलें पीडीएफ को कंप्रेस करें आसान साझाकरण के लिए अपने दस्तावेज़ फ़ाइल आकार को अनुकूलित करें ImageToolbox आंतरिक क्रॉस-रेफरेंस तालिका का पुनर्निर्माण करता है और फ़ाइल संरचना को स्क्रैच से पुनर्जीवित करता है। यह कई फ़ाइलों तक पहुंच बहाल कर सकता है जिन्हें \"खोला नहीं जा सकता\" यह टूल सभी दस्तावेज़ छवियों को ग्रेस्केल में परिवर्तित करता है। मुद्रण और फ़ाइल आकार को कम करने के लिए सर्वोत्तम मेटाडाटा बेहतर गोपनीयता के लिए दस्तावेज़ गुणों को संपादित करें टैग निर्माता लेखक कीवर्ड निर्माता प्राइवेसी डीप क्लीन इस दस्तावेज़ के लिए सभी उपलब्ध मेटाडेटा साफ़ करें पेज गहरा ओसीआर टेस्सेरैक्ट इंजन का उपयोग करके दस्तावेज़ से टेक्स्ट निकालें और उसे एक टेक्स्ट फ़ाइल में संग्रहीत करें सभी पेज नहीं निकाले जा सकते पीडीएफ पेज हटाएं पीडीएफ दस्तावेज़ से विशिष्ट पृष्ठ हटाएँ हटाने के लिए टैप करें मैन्युअल पीडीएफ को काटें दस्तावेज़ पृष्ठों को किसी भी सीमा तक काटें पीडीएफ को समतल करें दस्तावेज़ पृष्ठों को व्यवस्थित करके पीडीएफ को अपरिवर्तनीय बनाएं कैमरा प्रारंभ नहीं हो सका. कृपया अनुमतियाँ जांचें और सुनिश्चित करें कि इसका उपयोग किसी अन्य ऐप द्वारा नहीं किया जा रहा है। छवियाँ निकालें पीडीएफ़ में एम्बेड की गई छवियों को उनके मूल रिज़ॉल्यूशन पर निकालें इस पीडीएफ फाइल में कोई एम्बेडेड छवि नहीं है यह टूल प्रत्येक पृष्ठ को स्कैन करता है और पूर्ण-गुणवत्ता वाली स्रोत छवियां पुनर्प्राप्त करता है - दस्तावेज़ों से मूल को सहेजने के लिए बिल्कुल सही हस्ताक्षर बनाएं पेन पैराम्स दस्तावेज़ों पर लगाने के लिए छवि के रूप में स्वयं के हस्ताक्षर का उपयोग करें ज़िप पीडीएफ दिए गए अंतराल के साथ दस्तावेज़ को विभाजित करें और नए दस्तावेज़ों को ज़िप संग्रह में पैक करें अंतराल पीडीएफ प्रिंट करें कस्टम पेज आकार के साथ मुद्रण के लिए दस्तावेज़ तैयार करें पत्र के अनुसार पृष्ठों अभिविन्यास पृष्ठ का आकार अंतर खिलना नरम घुटना एनीमे और कार्टून के लिए अनुकूलित। बेहतर प्राकृतिक रंगों और कम कलाकृतियों के साथ तेजी से उन्नति सैमसंग वन यूआई 7 जैसा स्टाइल वांछित मान की गणना करने के लिए यहां बुनियादी गणित प्रतीक दर्ज करें (उदाहरण के लिए (5+5)*10) गणित अभिव्यक्ति %1$s तक छवियाँ उठाएँ दिनांक समय रखें दिनांक और समय से संबंधित एक्सिफ़ टैग को हमेशा सुरक्षित रखें, एक्सिफ़ विकल्प को बनाए रखने से स्वतंत्र रूप से काम करता है अल्फ़ा प्रारूपों के लिए पृष्ठभूमि रंग अल्फ़ा समर्थन के साथ प्रत्येक छवि प्रारूप के लिए पृष्ठभूमि रंग सेट करने की क्षमता जोड़ता है, अक्षम होने पर यह केवल गैर अल्फ़ा वाले के लिए उपलब्ध होता है ओपन प्रोजेक्ट पहले से सहेजे गए छवि टूलबॉक्स प्रोजेक्ट का संपादन जारी रखें छवि टूलबॉक्स प्रोजेक्ट खोलने में असमर्थ इमेज टूलबॉक्स प्रोजेक्ट में प्रोजेक्ट डेटा गुम है छवि टूलबॉक्स प्रोजेक्ट दूषित है असमर्थित छवि टूलबॉक्स प्रोजेक्ट संस्करण: %1$d प्रोजेक्ट सहेजें एक संपादन योग्य प्रोजेक्ट फ़ाइल में परतें, पृष्ठभूमि और संपादन इतिहास संग्रहीत करें खोलने में विफल खोजने योग्य पीडीएफ में लिखें छवि बैच से पाठ को पहचानें और छवि और चयन योग्य पाठ परत के साथ खोजने योग्य पीडीएफ को सहेजें परत अल्फा क्षैतिज फ़्लिप लंबवत फ्लिप ताला छाया जोड़ें छाया रंग पाठ ज्यामिति अधिक स्पष्ट शैलीकरण के लिए पाठ को फैलाएँ या तिरछा करें स्केल एक्स तिरछा एक्स एनोटेशन हटाएँ पीडीएफ पृष्ठों से चयनित एनोटेशन प्रकार जैसे लिंक, टिप्पणियाँ, हाइलाइट्स, आकार या फॉर्म फ़ील्ड हटा दें हाइपरलिंक फ़ाइल अनुलग्नक पंक्तियां पॉप अप टिकटों आकार पाठ नोट्स टेक्स्ट मार्कअप प्रपत्र फ़ील्ड मार्कअप अज्ञात एनोटेशन असमूहीकृत कॉन्फ़िगर करने योग्य रंग और ऑफसेट के साथ परत के पीछे धुंधली छाया जोड़ें ================================================ FILE: core/resources/src/main/res/values-hu/strings.xml ================================================ Élesítés Paletta Méret%1$s Ha most kilép, minden mentetlen változás el lesz veszve Az appról Féltónus Képek: %d Bezár Mentés Kék Távolság Nem tudunk palettát készíteni ehez a képhez Méretezés Web Kép Betöltése Szűrő Visszaállítás Keresés itt Efekt Összevetés Adjon hozzá eredeti fájlnevet Hőmérséklet Felszín Elmosás Tartalom méretezés Gaussian Elmosás Eredeti Átméretezi a képeket úgy, hogy a hosszabbik oldaluk megfeleljen a megadott magasságnak vagy szélességnek. Minden méretbeli számítás a mentés után történik. A képek képaránya megmarad. Nincs kép Eltávolítás Az App bezár Harmadlagos Ez az app teljesen ingyenes, de ha szeretné, támogathatja a fejlesztését, ha ide kattint Kép nagyítás Színárnyalat Szög Átlátszóság Kép kiválasztása Cserélje ki a sorszámot Kidudorodás Kép visszaállítása Meredek Ha engedélyezett, az app színei követni fogják a háttérkép színeit Negatív Magasság %1$s Szűrő hozzáadása Zöld Fekete-fehér Törlés Tényleg be szeretné zárni az appot? Megadás EXIF Törlése Hangulatjel Sugár Örvény Eszköztárhely App újraindítása Két kép kiválasztása a kezdéshez Ha engedélyezett, kicseréli a hagyományos időbélyeget a kép sorszámára, ha kötegelt feldolgozást használ Mentés… Hangulatjelek száma Szerkesztés Nem támogatott típus: %1$s Frissítés Színválasztó Galléria Kuwahara Lágyítás Használja a GetContent intent-et a képválasztáshoz. Mindenhol működik, de valamely eszközökön hibás. ez nem a mi hibánk, és nem tudunk mit tenni ellene. EXIF Megtartása Kimeneti mappa Egyszerű galléria képválasztó. Csak akkor működik, ha van egy app, amely tud képet megadni Megvilágítás Meghatározatlan Fájlnév Javítson a fordításban vagy fordítson más nyelvekre Világos Szín kinyerése képből, másolás vagy megosztás Kétoldali elmosódás Kép Előtag Előbeállítások Opciók elrendezése Tinta Szűrők Vége Elsődleges Vonal szélesség Kezdés EXIF Törlése Gamma FAB ilesztése Verzió Nincs mit beilleszteni Ok Sorolás Monokróm Másolva a vágólapra Segítsen a fordításban Valami nem sikerült Térköz Képek kiválasztása Másodlagos Doboz elmosás CGA Színtér Torzítás domborzat Testreszabás Címke hozzáadása Érvényes aRGB színkód beillesztése Dinamikus színek Képválasztó Kép forrása Kontraszt Színpaletta minta létrehozása adott képből Töltse le a legújabb frissítést, jelezzen hibákat, és egyéb Válasszon képet a kezdéshez Élénkség Hibajelentés és funkciókérés itt jelenthető Ha engedélyezett, képszerkesztéskor az app színei követik a kép színeit Az app működéséhez és képek szerkesztéséhez engedély kell a Tárhely eléréséhez. kérjük adja meg az engedélyt a következő ablakban. Az eredeti fájlnév nem adható hozzá, ha a képet képválasztóból választott képet Színszűrő Átméretezés Súly alapján A kép minden EXIF adata törölve lesz. Ez nem visszafordítható! Sobel-élek Egyedüli Szerkesztés Megéget Tágulás Bármijen kép előnézete: GIF, SVG, és így tovább… Szín másolva Rugalmas Kép Előnézet Szépia Értékek sikeresen visszaállítva Meghatározza az eszközök sorrendjét a főképernyőn Forráskód Egy kép módosítása, átméretezése és szerkesztése Sötét Szélesség %1$s Válassza ki, mely hangulatjel jelenjen meg a főképernyőn Mégsem Fájlkiterjesztés Nem található frissítés Marad Árnyékok Kép változásai pontos értékekre fognak visszaállni %d kép mentése sikertelen Amoled mód Gömbtörés Az alkalmazás színsémája nem módosítható, amíg a dinamikus színek be vannak kapcsolva Ha engedélyezett, hozzáadja a kép magasságát és szélességét a kép fájlnevéhez Semmi nem található a keresésre EXIF Adat törlése bármennyi képnél Határ vastagsága Nyelv Frissítések keresése Színséma Valami nem sikerült %1$s Fehéregyensúly Alap Paletta Generálása Ha engedélyezett, a felszíni színek teljesen feketék lesznek Éjszakai módban Fájlkezelő Hiba követő Ha engedélyezett, a frissítési ablak meg fog jelenni az app indításakor Minőség Telítettség Fény Engedély Beállítások Betöltés… Kép Linkje Kép monet engedélyezése Fájlméret megadása A működéshez szükség van erre az engedélyre. kérjük, adja meg manuálisan Kivétel Maximum méret KB-ban Átméretezi a képeket a megadott magasságra és szélességre. A képek képaránya megváltozhat. Nem található EXIF adat Éjszakai mód A kép túl nagy az előnézethez, a mentést így is megkisérlem Töltsön be képet az internetről, és szerkessze, nagyítsa, és mentse el ha akarja. Rendszer Fényesség Kép átméretezése adott KB-méretet követve Új verzió %1$s Megosztás Hozzáad Monet színek Sraffozás Piros Kitőltés Megvilágítás és árnyékok Körülvágás Előnézet megváltoztatása Saját Külső Tárhely EXIF Módosítása Szűrőláncok alkalmazása a képekre Szín Modern Android képválasztó, amely a képernyő alján jelenik meg, és Android 12 fölött is működik. Rosszul tudja az EXIF adatot magával hozni Értékek Átméretezés módja Ha engedélyezett, hozzáadja az eredeti fájlnevet a kész kép fájlnevéhez Két kép összevetése Az app témája a választott színen fog alapulni EredetiFájlnév köd Továbbfejlesztett Glitch Csatornaváltás X Csatornaváltás Y Korrupció mérete Sátor Blur Alsó Erő Tájolás & Csak szkriptészlelés Automatikus tájolás & Szkriptészlelés Egyetlen oszlop Kör szó Hiba Összeg Mag Anaglif Keverés Drago Aldridge A képek felülírva az eredeti célhelyen Nem lehet módosítani a képformátumot, ha a fájlok felülírása opció engedélyezve van Emoji színsémaként Törésmutató Üveggömb fénytörés Színes mátrix Átlátszatlanság Átméretezés Korlátok alapján A képek átméretezése a megadott magasságra és szélességre a képarány megtartása mellett Vázlat Küszöb Kvantálási szintek Sima ton Toon RGB szűrő Hamis szín Első szín Második szín Elmosódás középen x Elmosódás középen y Zoom elmosódás Színegyensúly Fénysűrűség küszöb Rajzolj a képre Válassz egy képet, és rajzolj rá valamit Rajzolj a háttérre Válassza ki a háttérszínt, és rajzoljon rá Háttérszín Bármilyen fájl titkosítása és visszafejtése (nem csak a kép) különféle elérhető titkosítási algoritmusok alapján Válasszon fájlt Titkosítás Dekódolás Az indításhoz válassza ki a fájlt Dekódolás Titkosítás Kulcs Végrehajtás AES-256, GCM mód, párnázás nélkül, alapértelmezettként 12 bájtos véletlenszerű IV-k. Igényelt algoritmus kiválasztható. A kulcsok 256 bites SHA-3 hash-ként vannak használva. Kompatibilitás Fájlok jelszó alapú titkosítása. A továbbhaladott fájlok a kiválasztott könyvtárban tárolhatók vagy megoszthatók. A visszafejtett fájlok közvetlenül is megnyithatók. Fájl méret The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. Érvénytelen jelszó vagy a kiválasztott fájl nincs titkosítva A kép megadott szélességgel és magassággal történő mentése memóriahiány hibát okozhat. Csak saját felelősségre. Automatikus gyorsítótár törlése Ha engedélyezve van, az alkalmazás gyorsítótára törlődik az alkalmazás indításakor Teremt Eszközök A főképernyőn megjelenő lehetőségeket típusuk szerint csoportosítja az egyéni listaelrendezés helyett Az elrendezés nem módosítható, ha az opciók csoportosítása engedélyezett Másodlagos testreszabás Képernyőkép Tartalék opció Kihagyás Másolat A %1$s módban történő mentés instabil lehet, mivel ez veszteségmentes formátum Véletlenszerű fájlnév Ha engedélyezve van, a kimeneti fájlnév teljesen véletlenszerű lesz Használja ezt a maszktípust a maszk létrehozásához az adott képből, figyelje meg, hogy alfa csatornával KELL rendelkeznie Alkalmazásbeállítások visszaállítása a korábban létrehozott fájlból A beállítások visszaállítása sikerült Keress meg Ezzel visszaállítja a beállításokat az alapértelmezett értékekre. Vegye figyelembe, hogy ez nem vonható vissza a feljebb említett biztonsági mentési fájl nélkül. Töröl A kiválasztott színséma törlésére készül. Ez a művelet nem vonható vissza Séma törlése Betűtípus Szöveg Betűméret Alapértelmezett A nagy betűméretek használata UI-hibákat és problémákat okozhat, amelyeket nem javítunk ki. Óvatosan használja. Aa Áá Bb Cc Cs cs Dd Dz dz Dzs dzs Ee Éé Ff Gg Gy gy Hh Ii Íí Jj Kk Ll Ly ly Mm Nn Ny ny Oo Óó Öö Őő Pp Qq Rr Ss Sz sz Tt Ty ty Uu Úú Üü Űű Vv Ww Xx Yy Zz Zs zs 0123456789 !? Érzelmek Tevékenységek Háttéreltávolító Távolítsa el a hátteret a képről rajzolással, vagy használja az Auto opciót Törlés mód Háttér törlése Háttér visszaállítása Elmosási sugár Pipetta Rajz mód Probléma létrehozása Hoppá… Hiba történt. Írhat nekem az alábbi lehetőségek segítségével, és megpróbálok megoldást találni Átméretezés és Konvertálás Ez lehetővé teszi az alkalmazás számára, hogy automatikusan gyűjtsön hibajelentéseket Jelenleg a %1$s formátum csak az EXIF-metaadatok olvasását teszi lehetővé Androidon. Mentéskor a kimeneti képnek egyáltalán nem lesznek metaadatai. A mentés majdnem kész. A mostani lemondáshoz ismét mentésre lesz szükség. Frissítések Nyilak rajzolása Ha engedélyezve van, a rajzi útvonal mutató nyílként jelenik meg Ecset puhaság A képek középen a megadott méretre lesznek levágva. Ha a kép kisebb a megadott méreteknél, a vászon a megadott háttérszínnel bővül. Adomány Képek Összeillesztése Kombinálja a megadott képeket, hogy egy nagyot kapjon Válasszon legalább 2 képet Kimeneti kép léptéke Vízszintes Függőleges A kis képeket nagyra méretezheti Ha engedélyezve van, a rendszer a kis képeket a sorozat legnagyobb méretére méretezi Képek sorrendje Szabályos Élek elmosása Elmosódott éleket rajzol az eredeti kép alá, hogy kitöltse a körülötte lévő tereket az egyetlen szín helyett, ha engedélyezve van Gyémánt pixelezés Kör pixeláció Továbbfejlesztett kör pixelezés Cserélje ki a színt Átkódolni Pixel méret A rajzolás irányának rögzítése Ha rajz módban engedélyezve van, a képernyő nem forog Frissítések keresése Paletta stílus Tonális folt Semleges Élénk Kifejező Szivárvány Gyümölcssaláta Hűség Tartalom Egy stílus, amely valamivel inkább kromatikus, mint a monokróm Hangos téma, a színesség maximum az elsődleges palettánál, a többinél fokozott Működtessen PDF fájlokkal: Előnézet, konvertálás képek köteggé, vagy hozzon létre egyet adott képekből PDF előnézete PDF-ből képekre Alapértelmezett palettastílus, lehetővé teszi mind a négy szín testreszabását, mások csak a kulcsszín beállítását teszik lehetővé Konvertálja a PDF-et képekké a megadott kimeneti formátumban Maszk szűrő Alkalmazzon szűrőláncokat adott maszkolt területeken, minden maszkterület meghatározhatja a saját szűrőkészletét Maszkok Maszk hozzáadása Maszk %d Maszk színe Maszk előnézete A megrajzolt szűrőmaszk a hozzávetőleges eredmény megjelenítéséhez Inverz kitöltési típus Ha engedélyezve van, az összes nem maszkolt terület szűrésre kerül az alapértelmezett viselkedés helyett A kiválasztott szűrőmaszk törlésére készül. Ez a művelet nem vonható vissza Teljes Szűrő Alkalmazzon bármilyen szűrőláncot adott képekre vagy egyetlen képre Rajt Központ Vége Egyszerű változatok Kiemelő Neon Toll Privacy Blur Rajzoljon félig átlátszó, éles kiemelő útvonalakat Adjon valami izzó hatást a rajzaihoz Alapértelmezett, legegyszerűbb – csak a szín Elhomályosítja a képet a megrajzolt útvonal alatt, így mindent el szeretne rejteni Gombok Árnyék rajzolása gombok mögé Alkalmazássávok Árnyék rajzolása alkalmazásrácsok mögé Érték a(z) %1$s–%2$s tartományban Automata forgatás Lehetővé teszi a határdoboz alkalmazását a képtájoláshoz Útvonal rajzolása mód Vonal nyíl Bemeneti értékként rajzolja meg az útvonalat Az útvonalat a kezdőponttól a végpontig vonalként rajzolja meg A mutató nyilat a kezdőponttól a végpontig vonalként rajzolja Mutató nyíl rajzolása egy adott útvonal mentén Dupla mutató nyilat rajzol a kezdőponttól a végpontig vonalként Vázolt Rect Ovális Rect A kiindulási ponttól a végpontig körvonalazott oválisan rajzol A kezdőponttól a végpontig egyenesen körvonalazva rajzol Zárt kitöltött útvonalat rajzol adott útvonalonként Sorok száma Oszlopok száma Nem található \"%1$s\" könyvtár, átváltottuk az alapértelmezettre. Kérjük, mentse újra a fájlt Ha engedélyezve van, automatikusan hozzáadja a mentett képet a vágólapra Rezgés erőssége A fájlok felülírásához \"Explorer\" képforrást kell használnia, próbálja meg újra kiválasztani a képeket, a képforrást megváltoztattuk a szükségesre Fájlok felülírása A tárhelyre mentés nem történik meg, a képet csak a vágólapra próbálja meg elhelyezni Fájl felülírva a következővel: %1$s az eredeti célhelyen Engedélyezi a nagyítót az ujj tetején rajzi módokban a jobb hozzáférhetőség érdekében Kezdeti érték kényszerítése Mérték Ez az alkalmazás teljesen ingyenes, ha azt szeretné, hogy nagyobb legyen, csillagozza meg a projektet a Githubon 😄 Csak automata Auto Egy blokkos függőleges szöveg Egyetlen blokk Egyvonalas Egyetlen szó Egyetlen karakter Ritka szöveg Ritka szöveg tájolása & Szkript észlelése Nyers vonal Törölni szeretné a \"%1$s\" nyelv OCR betanítási adatait az összes felismerési típusnál, vagy csak egy kiválasztottnál (%2$s)? Bayer Nyolc Dithering Floyd Steinberg Dithering Jarvis Judice Ninke Dithering Sierra Lite Dithering Atkinson dithering Stucki Dithering Burkes-féle ditherezés Balról jobbra Dithering Véletlenszerű dithering Egyszerű küszöb-dithering A darabonként definiált bikubikus polinom függvényeket használja a görbe vagy felület zökkenőmentes interpolálásához és közelítéséhez, rugalmas és folytonos alakábrázoláshoz Zaj Pixel rendezés Korrupciós műszak X Korrupciós műszak Y Side Fade Oldal Top Logaritmikus hangleképezés Olaj Víz hatás Méret Színes mátrix 4x4 Színes mátrix 3x3 Egyszerű effektusok Deuteranomália Browni Coda Chrome Éjszakai látás Meleg Menő Tritanopia Deutaronotopia Protanopia Achromatomaly Forró nyári Lila köd Napkelte Színes örvény Puha tavaszi fény Limonádé fény Éjszakai varázslat Elektromos gradiens Karamell sötétség Futurisztikus színátmenet Szivárványvilág Mély lila Ikon alakja Levág Uchimura Mobius Átmenet Csúcs Szín anomália Az emoji elsődleges színét használja alkalmazás színsémaként a kézzel definiált helyett Kifejezett Vágja le a képet bármilyen határig Email szekvenciaNum Elfér Kitettség laplaci Címke Stack blur Plakátolni Nem maximális elnyomás Gyenge pixelbefoglalás Nézz fel Konvolúció 3x3 Újrarendelés Gyors elmosódás Elmosódás mérete Letiltotta a Fájlok alkalmazást, aktiválja a funkció használatához Húz Rajzoljon a képre, mint egy vázlatfüzetben, vagy rajzoljon magára a háttérre Festék színe Alfa festék Rejtjel Fájl feldolgozva Tárolja ezt a fájlt az eszközén, vagy használja a megosztási műveletet, hogy bárhová elhelyezze Jellemzők Kérjük, vegye figyelembe, hogy a kompatibilitás más fájltitkosítási szoftverekkel vagy szolgáltatásokkal nem garantált. A kissé eltérő kulcskezelés vagy titkosítási konfiguráció összeférhetetlenséget okozhat. Gyorsítótár Gyorsítótár mérete Talált: %1$s Csoportosítsa a lehetőségeket típus szerint Képernyőkép szerkesztése Ha a 125-ös beállítást választotta, a kép az eredeti kép 125%-os méretében lesz mentve. Ha az 50-es beállítást választja, akkor a kép 50%-os méretben lesz mentve. A beállítás itt határozza meg a kimeneti fájl %-át, azaz ha az 50-es beállítást választja az 5 MB-os képen, akkor a mentés után 2,5 MB-os képet kap. Mentve %1$s mappába a következő névvel: %2$s Mentve a(z) %1$s mappába Telegram chat Beszélje meg az alkalmazást, és kérjen visszajelzést más felhasználóktól. Ott béta frissítéseket és betekintést is kaphat. Termés maszk Képarány Mentés és visszaállítás biztonsági mentés visszaállítás Készítsen biztonsági másolatot az alkalmazás beállításairól egy fájlba Sérült fájl vagy nem biztonsági másolat Étel és ital Természet és állatok Objektumok Szimbólumok Emoji engedélyezése Utazások és helyek Kép vágása A kép eredeti metaadatai megmaradnak A kép körüli átlátszó terek le lesznek vágva Háttér automatikus törlése Kép visszaállítása Módosítsa az adott képek méretét, vagy konvertálja őket más formátumba. Az EXIF metaadatok itt is szerkeszthetők, ha egyetlen képet választ. Max színszám Analitika Anonim alkalmazáshasználati statisztikák gyűjtésének engedélyezése Erőfeszítés A %1$s érték gyors tömörítést jelent, ami viszonylag nagy fájlméretet eredményez. Az %2$s lassabb tömörítést jelent, ami kisebb fájlt eredményez. Várjon Béták engedélyezése A frissítések ellenőrzése az alkalmazás bétaverzióit is magában foglalja, ha engedélyezve van Képtájolás Pixeláció Továbbfejlesztett pixelezés Stroke Pixelation Továbbfejlesztett gyémánt pixelezés Megértés Cserélni kívánt szín Célszín Eltávolítandó szín Szín eltávolítása Játékos téma – a forrásszín árnyalata nem jelenik meg a témában Monokróm téma, a színek tisztán fekete/fehér/szürke Egy séma, amely a forrásszínt a Scheme.primaryContainerben helyezi el A tartalomsémához nagyon hasonló séma Ez a frissítés-ellenőrző csatlakozik a GitHubhoz, hogy ellenőrizze, van-e elérhető új frissítés Figyelem Elhalványuló élek Tiltva Mindkét Színek megfordítása Ha engedélyezve van, a téma színeit negatívra cseréli Keresés Lehetővé teszi a keresést a főképernyőn elérhető összes eszköz között PDF-Eszközök Képek PDF-be Egyszerű PDF előnézet Csomagolja be a megadott képeket kimeneti PDF fájlba Maszk törlése Hasonló az adatvédelmi elmosódáshoz, de az elmosódás helyett pixeleket képez Konténerek Árnyék rajzolása tárolók mögé Csúszkák Kapcsolók FAB-ok Árnyék rajzolása csúszkák mögé Árnyék rajzolása kapcsolók mögé Árnyék rajzolása lebegő műveletgombok mögé Dupla vonalú nyíl Ingyenes rajz Dupla nyíl Nyíl Vonal Dupla mutató nyilat rajzol egy adott útvonalról Vázolt ovális Egyenesen rajzol a kezdőponttól a végpontig Oválisan rajzol a kezdőponttól a végpontig Lasszó Ingyenes Vízszintes rács Függőleges rács Stitch mód Vágólap Auto pin Rezgés Az eredeti fájl a kiválasztott mappába való mentés helyett újra cserélődik Üres Utótag Erode Anizotropikus Diffúzió Diffúzió Vezetés Vízszintes széllépcső Gyors kétoldali életlenítés Poisson-Elmosás Kristályosítsd Körvonal színe Fraktál üveg Amplitúdó Üveggolyó Légörvény X frekvencia Y frekvencia X amplitúdó Y amplitúdó Perlin-torzítás Hable Filmic Tone Mapping Heji-Burgess Tónusleképezés ACES Filmic Tone Mapping ACES Hill Tone Mapping Jelenlegi Minden Átmenetkészítő Adott kimeneti méretű színátmenet létrehozása testreszabott színekkel és megjelenéstípussal Sebesség Dehaze Omega Értékeld az alkalmazást polaroid Tritanomalía Protanomália Szüret Achromatopsia Lineáris Sugárirányú Söprés Gradiens típusa X központ Y központ Csempe mód Megismételt Tükör Szorító Matrica Szín megáll Szín hozzáadása Tulajdonságok Dithering Kvantifikátor Szürke skála Bayer Kettő Haladt Bayer Three By Three Dithering Bayer Four By Four Dithering Sierra Dithering Kétsoros Sierra dithering Hamis Floyd Steinberg Dhering Méretezési mód Bilineáris Hann Remete Lanczos Mitchell Legközelebb Spline Alapvető Alapértelmezett érték Sigma Térbeli Szigma Medián elmosódás Catmull Bicubic A lineáris (vagy bilineáris, kétdimenziós) interpoláció általában jó a kép méretének megváltoztatására, de a részletek nemkívánatos lágyulását okozza, és így is kissé szaggatott lehet. A jobb skálázási módszerek közé tartozik a Lanczos újramintavételezés és a Mitchell-Netravali szűrők A méretnövelés egyik egyszerűbb módja, minden képpont lecserélése azonos színű pixelekre A legegyszerűbb Android méretezési mód, amelyet szinte minden alkalmazásban használnak Módszer a vezérlőpontok halmazának zökkenőmentes interpolálására és újramintavételezésére, amelyet általában a számítógépes grafikában használnak sima görbék létrehozására A jelfeldolgozásban gyakran alkalmazott ablakos funkció a spektrális szivárgás minimalizálása és a frekvenciaelemzés pontosságának javítása a jel széleinek szűkítésével Matematikai interpolációs technika, amely a görbeszakaszok végpontjain lévő értékeket és deriváltokat használja sima és folyamatos görbe létrehozásához Újramintavételezési módszer, amely fenntartja a kiváló minőségű interpolációt súlyozott sinc függvény alkalmazásával a pixelértékekre Újramintavételezési módszer, amely állítható paraméterekkel rendelkező konvolúciós szűrőt használ az élesség és az élsimítás közötti egyensúly eléréséhez a méretezett képen A darabonként definiált polinom függvényeket használja a görbe vagy felület zökkenőmentes interpolálásához és közelítéséhez, rugalmas és folytonos alakábrázolást biztosítva Csak klip Hozzáad egy kiválasztott alakzatú tárolót az ikonok alá Fényerő Kényszerítése Képernyő Színátmenet Tetszőleges átmenetet készít a megadott képek tetejére Átváltozások Kamera Kép készítése kamerával. Ne feledje, hogy ebből a képforrásból lehet, hogy csak egy kép szerezhető Gabona Nem éles Pasztell Orange Haze Rózsaszín álom Arany óra Őszi hangok Levendula álom Cyberpunk Spektrális tűz Fantázia táj Színrobbanás Zöld Nap Űrportál Vörös Örvény Digitális kód Vízjelezés Fedje le a képeket testreszabható szöveges/képes vízjelekkel Ismételje meg a vízjelet Megismétli a vízjelet a kép felett, ahelyett, hogy egy adott pozícióban lenne X eltolás Eltolás Y Vízjel típusa Ezt a képet mintaként fogják használni a vízjelhez Szöveg szín Overlay mód Bokeh GIF Eszközök Konvertálja a képeket GIF-képpé, vagy bontsa ki a kereteket az adott GIF-képből GIF a képekhez Konvertálja a GIF fájlt képek kötegévé Kötegelt képek konvertálása GIF-fájlba Képek GIF-be A kezdéshez válassza ki a GIF-képet Használja az Első keret méretét Cserélje ki a megadott méretet az első keret méretére Ismételje meg a Számlálást Képkocka késleltetés millis FPS Használd a Lassót A Lasso-t használja, mint a rajz módban a törléshez Eredeti kép előnézete Alpha Az alkalmazás sávjának hangulatjele véletlenszerűen fog változni Véletlenszerű Hangulatjelek Véletlenszerű hangulatjelek nem használhatóak, ha a hangulatjelek ki vannak kapcsolva Nem lehet hangulatjelet kiválasztani, ha a véletlenszerű hangulatjelek be vannak kapcsolva Régi Tv Véletlenszerű elhomályosítás OCR (szövegfelismerés) Szöveg felismerése az adott képről, több mint 120 nyelv támogatott A képen nincs szöveg, vagy az alkalmazás nem találta meg Accuracy: %1$s Felismerés típusa Gyors Alapértelmezett Legjobb Nincs adat A Tesseract OCR megfelelő működéséhez további edzésadatokat (%1$s) kell letölteni eszközére. \nSzeretne letölteni %2$s adatokat? Letöltés Nincs kapcsolat, ellenőrizze, és próbálja újra a vonatmodellek letöltéséhez Letöltött nyelvek Elérhető nyelvek Szegmentációs mód Az ecset törlés helyett a hátteret állítja vissza Használja a Pixel Switchet Google Pixel-szerű kapcsolót használ Csúszik Egymás mellett Koppintás Váltó Átláthatóság Nagyító Az exif widgetet kezdetben ellenőrizni kényszeríti Több nyelv engedélyezése Kedvenc Még nincsenek kedvenc szűrők hozzáadva B Spline Natív Stack Blur Tilt Shift Konfetti A konfetti mentés, megosztás és egyéb elsődleges műveletek esetén megjelenik Biztonságos Mód Elrejti az alkalmazás tartalmát a legutóbbi alkalmazások listán. A tartalom nem rögzíthető és nem készíthető róla képernyőkép. Kijárat Ha most kilép az előnézetből, újra hozzá kell adnia a képeket Képformátum Sötét Színek Létrehoz\" Material You \" paletta kép Éjszakai mód színsémát használ a fényváltozat helyett Másolás \"Jetpack Compose\" kódként Gyűrű Elmosódás Körkörös Elmosódás Vezetés Elmosódás Kereszt Elmosódás Lineáris Dőléseltolás Az Eltávolítandó Címkék APNG Eszközök Konvertálja a képeket APNG-képpé, vagy bontsa ki a kereteket az adott APNG-képből APNG a képekhez Kötegelt képek konvertálása APNG-fájlba A kezdéshez válassza az APNG-képet Elmosódás APNG-fájl konvertálása képek kötegévé Képek APNG-be Postai irányítószám Zip fájl létrehozása adott fájlokból vagy képekből Húzza Fogantyú Szélesség Újrapróbálkozás Kapcsolótípus GIF képek átalakítása JXL animált képekké Nincsenek engedélyek Beágyazott Választó Kiválasztás Eső JXL → JPEG JPEG → JXL Átmintavételezési módszer, amely magas minőségű interpolációt biztosít a pixelértékekre alkalmazott Bessel (jinc) függvénnyel GIF → JXL APNG → JXL JXL → Képek JXL animáció átalakítása képek sorozatává Képek → JXL Képsorozat átalakítása JXL animációvá APNG képek átalakítása JXL animált képekké Viselkedés A fájlválasztó azonnal megjelenik, ha ez lehetséges a kiválasztott képernyőn Veszteséges tömörítést használ a fájl méretének csökkentésére veszteségmentes helyett Tömörítési típus Rendezés Dátum Dátum (fordított) Név Név (fordított) Csatornabeállítások Ma Tegnap Kérés Egyetlen média kiválasztása Teljes képernyős beállítások Ezt bekapcsolva a beállítások oldal mindig teljes képernyőn nyílik meg a húzható menü helyett Összeállítás Pixel Fluent Mintavételezett paletta használata Ha ez az opció be van kapcsolva, a kvantálási paletta mintavételezve lesz Útvonal kihagyása Minimális színarány Vonal küszöbérték Kvadratikus küszöbérték Útvonal méretezése Tulajdonságok visszaállítása Részletes Alapértelmezett vonalszélesség Konfetti Típus Robban JXL Eszközök Harmonizációs Szín Veszteséges Tömörítés Egy Material You kapcsoló Egy a \"Fluent\" dizájnrendszeren alapuló kapcsoló Egy a \"Cupertino\" dizájnrendszeren alapuló kapcsoló Képek → SVG A kép feldolgozás előtt kisebb méretre lesz lekicsinyítve, ami segíti az eszköz gyorsabb és biztonságosabb működését Átméretezés kiindulópontja Maximum Válasszon egy JXL képet a kezdéshez Ünnepi Végezzen JXL és JPEG közötti átkódolást minőségromlás nélkül, vagy konvertálja a GIF/APNG fájlokat JXL animációvá Automatikus beillesztés Koordináták kerekítési tűrése Sarkok Az Image Toolbox képkiválasztója Cupertino Kép lekicsinyítése Engedélyezi az alkalmazás számára, hogy automatikusan beillessze a vágólap tartalmát, így az megjelenik a főképernyőn, és feldolgozhatóvá válik Végezzen veszteségmentes átkódolást JXL-ről JPEG-re Végezzen veszteségmentes átkódolást JPEG-ről JXL-re Gyors 2D Gauss-elmosás Gyors 3D Gauss-elmosás Lanczos Bessel Minden tulajdonság vissza lesz állítva az alapértelmezett értékre. Figyelem, ez a művelet nem vonható vissza Ennek az eszköznek a használata nagy képek nyomkövetésére lekicsinyítés nélkül nem ajánlott, mert összeomláshoz vezethet és megnövelheti a feldolgozási időt Fájlválasztás kihagyása Több média kiválasztása Beállítások megjelenítése fekvő módban Előnézetek generálása Előnézet-generálás engedélyezése, ez segíthet elkerülni az összeomlásokat bizonyos eszközökön, viszont ezzel egyidejűleg az önálló szerkesztési mód használata közben néhány funkció nem lesz elérhető Harmonizációs Szint Megadott képek átrajzolása SVG képekké Ha ez ki van kapcsolva, akkor fekvő módban a beállítások a felső alkalmazássáv gombjából nyílnak meg, mint mindig, ahelyett, hogy folyamatosan láthatóak lennének Gyors 4D Gauss-elmosás Az eredményül kapott kép dekódolási sebességét szabályozza, ami segíthet a kész kép gyorsabb megnyitásában. A %1$s érték a leglassabb dekódolást jelenti, míg a %2$s a leggyorsabbat. Ez a beállítás megnövelheti a kimeneti kép méretét Egy Jetpack Compose Material You kapcsoló Motor Mód Örökölt LSTM hálózat Örökölt & LSTM Átalakítás Képkötegek átalakítása adott formátumra Új Mappa Létrehozása Bitek Mintánként Tömörítés Fotometriai értelmezés Minták Per Pixel Sík Beállítás Y Cb Cr Sub Mintázás Y Cb Cr Elhelyezés X Felbontás Y Felbontás Felbontás Mértékegység Strip Offsets Sorok csíkonként Strip Byte Counts JPEG csereformátum JPEG csereformátum hossza Átviteli funkció Fehér Pont Elsődleges kromatika Y Cb Cr együtthatók Referencia Fekete-fehér Dátum Idő Kép leírása Készíts Modell Szoftver Művész Szerzői jog Exif verzió Flashpix verzió Színtér Gamma Pixel X Dimension Pixel Y méret Tömörített bit/pixel Készítő megjegyzés Felhasználói megjegyzés Kapcsolódó hangfájl Dátum Idő Eredeti Dátum Idő digitalizálás Eltolási idő Eltolási idő eredeti Eltolási idő digitalizálva Sub Sec Time Sub Sec Time Eredeti Sub Sec Time Digitalizálva Kitettségi idő F Szám Expozíciós program Spektrális érzékenység Fényképérzékenység Oecf Érzékenység típusa Normál kimeneti érzékenység Ajánlott expozíciós index ISO Speed ISO Speed ​​Latitude yyyy ISO Speed ​​Latitude zzz Zársebesség értéke Rekesznyílás értéke Fényerő érték Expozíciós torzítás értéke Maximális rekeszérték Tárgy távolság Mérési mód Vaku Témakör Gyújtótávolság Flash energia Térbeli frekvenciaválasz X fókuszsík felbontás Y fókuszsík felbontása Fókuszsík-felbontási egység Tárgy helye Expozíciós index Érzékelési módszer Fájlforrás CFA minta Egyedi renderelt Expozíciós mód Fehéregyensúly Digitális zoom arány Fókusztávolság 35 mm-es filmben Jelenet rögzítési típusa Szerezze meg az irányítást Kontraszt Telítettség Élesség Eszközbeállítás leírása Tárgy távolsági tartomány Egyedi képazonosító A kamera tulajdonosának neve Test sorozatszáma Lencse specifikáció Lencse gyártmány Objektív modell Az objektív sorozatszáma GPS verzióazonosító GPS szélesség Ref GPS Latitude GPS hosszúság Ref GPS hosszúság GPS magasság Ref GPS magasság GPS időbélyegző GPS műholdak GPS állapot GPS mérési mód GPS DOP GPS Sebesség Ref GPS sebesség GPS nyomvonal Ref GPS nyomvonal GPS Img Irány Ref GPS Img Irány GPS térkép Datum GPS Cél szélesség Ref GPS célszélesség GPS cél hosszúsági ref GPS cél hosszúság GPS célcsapágy ref GPS Dest Bearing GPS cél távolság Ref GPS cél távolság GPS feldolgozási módszer GPS terület információ GPS dátumbélyegző GPS differenciálmű GPS H helymeghatározási hiba Interoperabilitási index DNG verzió Alapértelmezett vágási méret Preview Image Start Előnézet kép hossza Képarány keret Érzékelő alsó szegélye Érzékelő bal szegélye Érzékelő jobb szegélye Érzékelő felső szegély ISO Szöveg rajzolása az útvonalra adott betűtípussal és színnel Betűméret Vízjel mérete Szöveg ismétlése Az aktuális szöveg megismétlődik az elérési út végéig az egyszeri rajz helyett Dash Size A kiválasztott kép segítségével rajzolja meg a megadott útvonalon Ezt a képet a program a megrajzolt útvonal ismétlődő bejegyzéseként fogja használni Körvonalas háromszöget rajzol a kezdőponttól a végpontig Körvonalas háromszöget rajzol a kezdőponttól a végpontig Vázolt háromszög Háromszög Sokszöget rajzol a kezdőponttól a végpontig Poligon Vázolt sokszög Körvonalas sokszöget rajzol a kezdőponttól a végpontig Csúcsok Rajzolj szabályos sokszöget Rajzolj sokszöget, amely szabályos lesz a szabad forma helyett Csillagot rajzol a kezdőponttól a végpontig Csillag Vázolt csillag Körvonalas csillagot rajzol a kezdőponttól a végpontig Belső sugár arány Rajzolj szabályos csillagot Rajzolj csillagot, amely szabályos lesz a szabad forma helyett Antialias Lehetővé teszi az élsimítást az éles szélek elkerülése érdekében Nyissa meg a Szerkesztést az előnézet helyett Ha az ImageToolboxban kiválasztja a megnyitandó képet (előnézetet), a szerkesztési kijelölési lap megnyílik az előnézet helyett Dokumentum szkenner Szkenneljen be dokumentumokat, és készítsen PDF-fájlt vagy különítsen el belőlük képeket Kattintson a szkennelés elindításához Indítsa el a szkennelést Mentés Pdf-ként Megosztás Pdf-ként Az alábbi lehetőségek képek mentésére szolgálnak, nem PDF-re HSV hisztogram kiegyenlítése Hisztogram kiegyenlítése Írja be a százalékot Engedélyezze a bevitelt szöveges mezőben Engedélyezi a szövegmezőt az előre beállított értékek kijelölése mögött, hogy azokat menet közben beírja Skála színtér Lineáris Hisztogram pixeláció kiegyenlítése Rács mérete X Rácsméret Y A hisztogram kiegyenlítése adaptív A hisztogram adaptív LUV kiegyenlítése Hisztogram adaptív LAB kiegyenlítése CLAHE CLAHE LAB CLAHE LUV Vágás tartalomra Keret színe Figyelmen kívül hagyandó szín Sablon Nincsenek sablonszűrők hozzáadva Új létrehozása A beolvasott QR-kód nem érvényes szűrősablon QR-kód beolvasása A kiválasztott fájlban nincsenek szűrősablon adatok Sablon létrehozása Sablon neve Ezt a képet használjuk a szűrősablon előnézetéhez Sablonszűrő QR-kód képként Fájlként Mentés fájlként Mentés QR-kód képként Sablon törlése A kiválasztott sablonszűrő törlésére készül. Ez a művelet nem vonható vissza Szűrősablon hozzáadva a következő névvel: \"%1$s\" (%2$s) Szűrő előnézete QR és vonalkód Olvassa be a QR-kódot, és szerezze be a tartalmát, vagy illessze be a karakterláncot új kód létrehozásához Kódtartalom Olvasson be bármilyen vonalkódot a mező tartalmának lecseréléséhez, vagy írjon be valamit, hogy új vonalkódot generáljon a kiválasztott típussal QR leírás Min Adjon kameraengedélyt a beállításokban a QR-kód beolvasásához Adjon kameraengedélyt a beállításokban a dokumentumszkenner beolvasásához Kocka alakú B-Spline Hamming Hanning Blackman Rászed Quadric Gauss-féle Szfinksz Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 császár Bartlett-He Doboz Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc A köbös interpoláció simább skálázást biztosít a legközelebbi 16 képpont figyelembevételével, jobb eredményt adva, mint a bilineáris A darabonként definiált polinom függvényeket használja a görbe vagy felület zökkenőmentes interpolálásához és közelítéséhez, rugalmas és folyamatos alakábrázoláshoz Ablakfunkció, amely a jel széleinek szűkítésével csökkenti a spektrális szivárgást, hasznos a jelfeldolgozásban A Hann-ablak egy változata, amelyet általában a spektrális szivárgás csökkentésére használnak jelfeldolgozó alkalmazásokban A jelfeldolgozásban gyakran használt ablakfunkció, amely jó frekvenciafelbontást biztosít a spektrális szivárgás minimalizálásával A jelfeldolgozó alkalmazásokban gyakran használt ablakfunkció, amelyet arra terveztek, hogy jó frekvenciafelbontást biztosítson csökkentett spektrális szivárgással Olyan módszer, amely másodfokú függvényt használ az interpolációhoz, sima és folyamatos eredményeket biztosítva Gauss-függvényt alkalmazó interpolációs módszer, amely hasznos a képek simítására és zajcsökkentésére Speciális újramintavételezési módszer, amely kiváló minőségű interpolációt biztosít minimális műtermékekkel A jelfeldolgozásban használt háromszögablakos funkció a spektrális szivárgás csökkentésére Kiváló minőségű interpolációs módszer a kép természetes átméretezésére, az élesség és a simaság egyensúlyára A Robidoux-módszer élesebb változata, éles képméretezésre optimalizálva Spline alapú interpolációs módszer, amely sima eredményeket biztosít egy 16 koppintásos szűrővel Spline-alapú interpolációs módszer, amely sima eredményeket biztosít egy 36 koppintásos szűrő használatával Spline alapú interpolációs módszer, amely sima eredményeket biztosít egy 64 érintéssel járó szűrővel Egy interpolációs módszer, amely a Kaiser ablakot használja, jó szabályozást biztosítva a főlebeny szélessége és az oldallebeny szintje közötti kompromisszum felett A Bartlett és a Hann ablakokat kombináló hibrid ablak funkció, amely a jelfeldolgozásban a spektrális szivárgás csökkentésére szolgál. Egy egyszerű újramintavételi módszer, amely a legközelebbi pixelértékek átlagát használja, ami gyakran blokkos megjelenést eredményez A spektrális szivárgás csökkentésére használt ablak funkció, amely jó frekvenciafelbontást biztosít jelfeldolgozó alkalmazásokban Újramintavételi módszer, amely 2 lebenyű Lanczos szűrőt használ a kiváló minőségű interpoláció érdekében minimális műtermékekkel Újramintavételi módszer, amely 3 lebenyű Lanczos szűrőt használ a kiváló minőségű interpoláció érdekében minimális műtermékekkel Újramintavételi módszer, amely 4 lebenyű Lanczos szűrőt használ a kiváló minőségű interpoláció érdekében minimális műtermékekkel A Lanczos 2 szűrő egy változata, amely a jinc funkciót használja, kiváló minőségű interpolációt biztosít minimális műtermékekkel A Lanczos 3 szűrő egy változata, amely a jinc funkciót használja, kiváló minőségű interpolációt biztosít minimális műtermékekkel A Lanczos 4 szűrő egy változata, amely a jinc funkciót használja, kiváló minőségű interpolációt biztosít minimális műtermékekkel Hanning EWA A Hanning-szűrő elliptikus súlyozott átlag (EWA) változata a sima interpoláció és újramintavételezés érdekében Robidoux EWA A Robidoux szűrő elliptikus súlyozott átlag (EWA) változata a kiváló minőségű újramintavételezés érdekében Blackman EVE A Blackman-szűrő elliptikus súlyozott átlagú (EWA) változata a csengetési műtermékek minimalizálása érdekében Quadric EWA A Quadric szűrő elliptikus súlyozott átlag (EWA) változata a sima interpoláció érdekében Robidoux Sharp EWA A Robidoux Sharp szűrő elliptikus súlyozott átlag (EWA) változata az élesebb eredmények érdekében Lanczos 3 Jinc EWA A Lanczos 3 Jinc szűrő elliptikus súlyozott átlag (EWA) változata a kiváló minőségű újramintavételezéshez csökkentett álnevekkel Ginzeng Újramintavevő szűrő, amelyet kiváló minőségű képfeldolgozásra terveztek, az élesség és a simaság jó egyensúlyával Ginseng EWA A Ginseng szűrő elliptikus súlyozott átlag (EWA) változata a jobb képminőség érdekében Lanczos Sharp EWA A Lanczos Sharp szűrő elliptikus súlyozott átlag (EWA) változata az éles eredmények eléréséhez minimális műtermékekkel Lanczos 4 legélesebb EWA A Lanczos 4 Sharpest szűrő elliptikus súlyozott átlag (EWA) változata a rendkívül éles képek újramintavételezéséhez Lanczos Soft EWA A Lanczos Soft szűrő elliptikus súlyozott átlag (EWA) változata a simább kép-újramintavételezés érdekében Haasn Soft A Haasn által tervezett újramintavevő szűrő a sima és műtermékmentes képméretezés érdekében Formátum átalakítás Kötegelt képek konvertálása egyik formátumból a másikba Elvetés örökre Kép halmozás A kiválasztott keverési módokkal egymásra halmozhatja a képeket Kép hozzáadása elemre A kukák számítanak Clahe HSL Clahe HSV Hisztogram adaptív HSL kiegyenlítése Hisztogram adaptív HSV kiegyenlítése Edge mód Csipesz Betakar Színvakság Válassza ki a módot a téma színeinek a kiválasztott színvakság változathoz való igazításához Nehéz megkülönböztetni a vörös és a zöld árnyalatokat Nehéz megkülönböztetni a zöld és a vörös árnyalatokat Nehéz megkülönböztetni a kék és a sárga árnyalatokat Képtelenség érzékelni a vörös árnyalatokat Képtelenség érzékelni a zöld árnyalatokat Képtelenség érzékelni a kék árnyalatokat Csökkentett érzékenység minden színre Teljes színvakság, csak a szürke árnyalatait látja Ne használja a színvak sémát A színek pontosan olyanok lesznek, mint a témában Szigmoidális Lagrange 2 2-es rendű Lagrange interpolációs szűrő, amely kiváló minőségű képméretezésre alkalmas sima átmenetekkel Lagrange 3 A 3. rendű Lagrange interpolációs szűrő jobb pontosságot és egyenletesebb eredményeket kínál a képméretezéshez Lanczos 6 A Lanczos resampling szűrő magasabb, 6-os nagyságrenddel élesebb és pontosabb képméretezést biztosít Lanczos 6 Jinc A Lanczos 6 szűrő egy Jinc funkcióval rendelkező változata a jobb kép-újramintavételi minőség érdekében Lineáris Box Blur Lineáris sátorelmosás Lineáris Gauss-box elmosódás Lineáris Stack Blur Gaussian Box Blur Lineáris gyors Gauss-elmosás Következő Lineáris gyors Gauss-elmosás Lineáris Gauss-elmosás Válasszon egy szűrőt, hogy festékként használja Cserélje ki a szűrőt Válassza ki az alábbi szűrőt, ha ecsetként szeretné használni a rajzában TIFF tömörítési séma Alacsony poli Homokfestés Képfelosztás Egyetlen kép felosztása sorok vagy oszlopok szerint Fit to Bounds Kombinálja a kivágás átméretezési módot ezzel a paraméterrel a kívánt viselkedés eléréséhez (Körbevágás/Igazítás a képarányhoz) A nyelvek importálása sikeres volt Tartalék OCR modellek Importálás Export Pozíció Központ Bal felső Jobbra fent Balra lent Jobbra lent Felső központ Jobb középső Alsó központ Bal középen Célkép Paletta átvitel Fokozott olaj Egyszerű régi TV HDR Gotham Egyszerű vázlat Lágy Glow Színes poszter Tri Tone Harmadik szín Clahe Oklab Clara Olks Clahe Jzazbz Petty Csoportosított 2x2 Dithering Csoportosított 4x4 Dithering Csoportosított 8x8 dithering Yililoma dithering Nincsenek kiválasztva kedvenc opciók, adja hozzá őket az eszközök oldalon Kedvencek hozzáadása Kiegészítő Hasonló Triadikus Megosztott kiegészítő Tetradic Négyzet Analóg + Kiegészítő Színes eszközök Keverj, készíts tónusokat, generálj árnyalatokat és így tovább Színharmóniák Színes árnyékolás Variáció Tints Hangok Shades Színkeverés Színes információ Kiválasztott szín Szín Keverhető Nem használható pénz, ha a dinamikus színek be vannak kapcsolva 512x512 2D LUT Cél LUT-kép Egy amatőr Miss Etikett Puha elegancia Soft Elegance Variant Paletta átviteli változat 3D LUT Cél 3D LUT-fájl (.cube / .CUBE) LUT Bleach Bypass Gyertyafény Drop Blues Edgy Amber Őszi színek Filmkészlet 50 Ködös éjszaka Kodak Töltse le a semleges LUT-képet Először is használja kedvenc képszerkesztő alkalmazását, hogy alkalmazzon szűrőt a semleges LUT-ra, amelyet itt szerezhet be. Ahhoz, hogy ez megfelelően működjön, az egyes pixelszínek nem függhetnek a többi képponttól (például az elmosódás nem működik). Ha készen áll, használja az új LUT-képet bemenetként az 512*512-es LUT-szűrőhöz Pop Art Celluloid Kávé Arany Erdő Zöldes Retro sárga Linkek előnézete Lehetővé teszi a hivatkozás előnézetének lekérését olyan helyeken, ahol szöveget kaphat (QRCode, OCR stb.) Linkek Az ICO fájlok legfeljebb 256 x 256 méretben menthetők GIF a WEBP-re GIF-képek konvertálása WEBP-animált képekké WEBP Eszközök Konvertálja a képeket WEBP animált képpé, vagy bontsa ki a kereteket az adott WEBP animációból WEBP a képekhez A WEBP fájl konvertálása képek kötegévé Kötegelt képek konvertálása WEBP-fájllá Képek a WEBP-re A kezdéshez válassza ki a WEBP képet Nincs teljes hozzáférés a fájlokhoz Engedélyezze az összes fájlhoz való hozzáférést a JXL, QOI és más olyan képek megtekintéséhez, amelyeket nem ismer fel képként az Android. Engedély nélkül az Image Toolbox nem tudja megjeleníteni ezeket a képeket Alapértelmezett rajzszín Alapértelmezett rajzolási útvonal mód Időbélyeg hozzáadása Lehetővé teszi az időbélyeg hozzáadását a kimeneti fájlnévhez Formázott időbélyeg Engedélyezze az időbélyeg formázást a kimeneti fájlnévben az alap millis helyett A formátum kiválasztásához engedélyezze az Időbélyegeket Egyszeri mentési hely Tekintse meg és szerkessze az egyszeri mentési helyeket, amelyeket a mentés gomb hosszan lenyomásával használhat, többnyire minden lehetőségnél Nemrég használt CI csatorna Csoport Kép eszköztár a Telegramban 🎉 Csatlakozz a chatünkhöz, ahol megbeszélhetsz bármit, amit szeretnél, és nézz be a CI csatornára is, ahol bétákat és bejelentéseket teszek közzé Értesítést kaphat az alkalmazás új verzióiról, és olvassa el a közleményeket Illessze a képet a megadott méretekhez, és alkalmazzon elmosódást vagy színt a háttérre Eszközök elrendezése Csoportosítsa az eszközöket típus szerint Az egyéni listaelrendezés helyett típusuk szerint csoportosítja az eszközöket a főképernyőn Alapértelmezett értékek Rendszersávok láthatósága Rendszersávok megjelenítése ellopással Lehetővé teszi a csúsztatást a rendszersávok megjelenítéséhez, ha el vannak rejtve Auto Összes elrejtése Összes megjelenítése Navigációs sáv elrejtése Állapotsor elrejtése Zajgenerálás Különféle zajokat generál, mint például a Perlin vagy más típusú Frekvencia Zajtípus Forgatás típusa Fraktál típus Oktávok Lacunarity Nyereség Súlyozott erő Ping Pong Erő Távolság funkció Visszatérés típusa Jitter Domain Warp Igazítás Egyéni fájlnév Válassza ki azt a helyet és fájlnevet, amelyet az aktuális kép mentéséhez használni fog Mentve egy mappába egyéni névvel Kollázskészítő Kollázsokat készíthet akár 20 képből Kollázs típusa Tartsa lenyomva a képet a cseréhez, mozgatáshoz és nagyításhoz a pozíció beállításához Forgatás letiltása Megakadályozza a képek elforgatását kétujjas kézmozdulatokkal A szegélyekhez illesztés engedélyezése Mozgatás vagy nagyítás után a képek kattannak, hogy kitöltsék a keret széleit Hisztogram RGB vagy Brightness kép hisztogramja, amely segít a beállítások elvégzésében Ezt a képet RGB és Fényerő hisztogramok generálására használják fel Tesseact Options Alkalmazzon néhány bemeneti változót a tesseract motorhoz Egyéni beállítások A beállításokat a következő minta szerint kell megadni: \"--{opció_neve} {érték}\" Automatikus kivágás Szabad sarkok Vágja a képet sokszögenként, ezzel is korrigálja a perspektívát Pontok kényszerítése képhatárokra A pontokat nem korlátozzák a képhatárok, ez a perspektíva pontosabb korrekciójához hasznos Maszk Tartalomtudatos kitöltés a megrajzolt útvonal alatt Gyógyítóhely Használja a Circle Kernelt Nyílás Záró Morfológiai gradiens Top Hat Fekete kalap Hanggörbék Görbék visszaállítása A görbék visszaállnak az alapértelmezett értékre Vonalstílus Gap Size Szaggatott Pont szaggatott Bélyeges Cikcakk Szaggatott vonalat húz a megrajzolt útvonal mentén meghatározott hézagmérettel Pontot és szaggatott vonalat rajzol az adott útvonal mentén Csak alapértelmezett egyenes vonalak Kijelölt alakzatokat rajzol az útvonal mentén meghatározott térközzel Hullámos cikkcakkokat rajzol az ösvény mentén Cikcakk arány Parancsikon létrehozása Válassza ki a rögzíteni kívánt eszközt Az eszköz felkerül az indító kezdőképernyőjére parancsikonként, és használja a \"Fájlválasztás kihagyása\" beállítással kombinálva a kívánt viselkedés eléréséhez. Ne rakja egymásra a kereteket Lehetővé teszi a korábbi képkockák selejtezését, így azok nem fognak egymásra rakódni Crossfade A keretek egymásba kerülnek A Crossfade képkockák számítanak Egyes küszöb Kettes küszöb Ravasz Tükör 101 Továbbfejlesztett zoom elmosódás laplaci egyszerű Sobel Simple Segítő rács A rajzterület felett a támasztórácsot jeleníti meg a pontos manipulációk elősegítése érdekében Rács színe Cell Width Cell magasság Kompakt kiválasztók Egyes kiválasztási vezérlők kompakt elrendezést használnak, hogy kevesebb helyet foglaljanak Adja meg a kamera engedélyét a beállításokban a kép rögzítéséhez Elrendezés Főképernyő címe Állandó rátafaktor (CRF) A %1$s érték lassú tömörítést jelent, ami viszonylag kis fájlméretet eredményez. A %2$s gyorsabb tömörítést jelent, ami nagy fájlt eredményez. Lut Könyvtár Töltse le a LUT-gyűjteményt, amelyet a letöltés után alkalmazhat Frissítse a LUT-ok gyűjteményét (csak az újak kerülnek sorba), amelyeket a letöltés után alkalmazhat A szűrők alapértelmezett kép-előnézetének módosítása Kép előnézete Elrejt Megmutat Csúszka típusa Képzelet 2. anyag Egy díszes megjelenésű csúszka. Ez az alapértelmezett beállítás Egy Material 2 csúszka A Material You csúszka Alkalmazni Központi párbeszédpanel gombok A párbeszédpanelek gombjai lehetőleg a bal oldal helyett középen helyezkednek el Nyílt forráskódú licencek Tekintse meg az ebben az alkalmazásban használt nyílt forráskódú könyvtárak licenceit Terület Újramintavételezés pixel terület reláció segítségével. Előnyben részesített módszer lehet a képtizedelésre, mivel moire-mentes eredményeket ad. De a kép nagyítása hasonló a \"Legközelebbi\" módszerhez. Tonemapping engedélyezése Írja be a % Nem tud hozzáférni a webhelyhez, próbáljon meg VPN-t használni, vagy ellenőrizze, hogy az URL helyes-e Jelölőrétegek Rétegek mód képek, szövegek és egyebek szabad elhelyezésének lehetőségével Réteg szerkesztése Rétegek a képen Használjon egy képet háttérként, és adjon hozzá különböző rétegeket Rétegek a háttérben Ugyanaz, mint az első opció, de kép helyett színnel Beta Gyorsbeállítások oldal A képek szerkesztése közben adjon hozzá egy lebegő csíkot a kiválasztott oldalhoz, amelyre kattintva megnyílik a gyors beállítások Kiválasztás törlése A \"%1$s\" beállításcsoport alapértelmezés szerint össze lesz csukva A \"%1$s\" beállításcsoport alapértelmezés szerint ki lesz bontva Base64 eszközök Dekódolja a Base64 karakterláncot képpé, vagy kódolja a képet Base64 formátumba Base64 A megadott érték nem érvényes Base64 karakterlánc Üres vagy érvénytelen Base64 karakterlánc nem másolható Beillesztés Base64 Másolás Base64 Töltse be a képet a Base64 karakterlánc másolásához vagy mentéséhez. Ha megvan maga a karakterlánc, beillesztheti a fenti képhez Base64 mentése Share Base64 Opciók Akciók Import Base64 Base64 műveletek Vázlat hozzáadása Adjon hozzá körvonalat a szöveg körül meghatározott színnel és szélességgel Vázlat színe Vázlat mérete Forgás Ellenőrző összeg fájlnévként A kimeneti képek neve megfelel az adatok ellenőrző összegének Ingyenes szoftver (partner) További hasznos szoftverek az Android alkalmazások partnercsatornájában Algoritmus Ellenőrzőösszeg eszközök Ellenőrző összegek összehasonlítása, hash kiszámítása vagy hexadecimális karakterláncok létrehozása fájlokból különböző kivonatolási algoritmusok segítségével Számítsa ki Szöveg Hash Ellenőrző összeg Válassza ki a fájlt az ellenőrző összeg kiszámításához a kiválasztott algoritmus alapján Írjon be egy szöveget az ellenőrző összeg kiszámításához a kiválasztott algoritmus alapján Forrás ellenőrző összeg Összehasonlítandó ellenőrző összeg Mérkőzés! Különbség Az ellenőrző összegek egyenlőek, ez biztonságos lehet Az ellenőrző összegek nem egyenlőek, a fájl nem biztonságos! Mesh színátmenetek Tekintse meg a Mesh Gradients online gyűjteményét Csak TTF és OTF betűtípusok importálhatók Betűtípus importálása (TTF/OTF) Betűtípusok exportálása Importált betűtípusok Hiba történt a mentési kísérlet során, próbálja meg megváltoztatni a kimeneti mappát A fájlnév nincs beállítva Egyik sem Egyedi oldalak Oldalak kiválasztása Szerszám kilépés megerősítése Ha bizonyos eszközök használata közben nem mentett módosításokat, és megpróbálja bezárni, akkor megjelenik a megerősítés párbeszédpanel EXIF szerkesztése Egyetlen kép metaadatainak módosítása újratömörítés nélkül Érintse meg az elérhető címkék szerkesztéséhez Cserélje ki a matricát Fit Width Fit Height Kötegelt összehasonlítás Válassza ki a fájlt/fájlokat az ellenőrző összeg kiszámításához a kiválasztott algoritmus alapján Válassza ki a Fájlokat Válassza ki a Címtárat Fejhossz skála Bélyeg Időbélyeg Formátumminta Párnázás Képvágás Vágja ki a képrészt, és egyesítse a bal oldaliakat (lehet inverz) függőleges vagy vízszintes vonalakkal Függőleges forgási vonal Vízszintes forgásvonal Inverz kiválasztás A függőleges vágott rész kimarad, ahelyett, hogy a vágott terület körül egyesítené a részeket A vízszintes vágott rész meg lesz hagyva, ahelyett, hogy a vágott terület körül egyesítené a részeket Háló színátmenetek gyűjteménye Hozzon létre háló gradienst egyéni csomópontszámmal és felbontással Mesh gradiens fedvény Háló gradiens létrehozása adott képek tetején Pontok testreszabása Rács mérete X. határozat Y. határozat Felbontás Pixel by Pixel Jelölje ki a Színt Pixel-összehasonlítás típusa Vonalkód beolvasása Magasság aránya Vonalkód típusa B/W érvényesítése A vonalkód kép teljesen fekete-fehér lesz, és nem színezi az alkalmazás témája Szkenneljen be bármilyen vonalkódot (QR, EAN, AZTEC stb.), és szerezze be a tartalmát, vagy illessze be a szöveget új létrehozásához Nem található vonalkód A generált vonalkód itt lesz Audio borítók Az albumborítóképek kibontása hangfájlokból, a legtöbb általános formátum támogatott Az indításhoz válassza ki a hangot Válassza az Audio lehetőséget Nem található borító Naplók küldése Kattintson az alkalmazásnaplófájl megosztásához, ez segíthet a probléma észlelésében és a problémák megoldásában Hoppá… Hiba történt Az alábbi lehetőségek segítségével kapcsolatba léphet velem, és megpróbálok megoldást találni.\n(Ne felejtse el csatolni a naplókat) Írás fájlba Kivonja a szöveget a képek kötegéből, és tárolja egy szövegfájlban Írás a metaadatokba Vonja ki a szöveget az egyes képekből, és helyezze el a relatív fotók EXIF-információi közé Láthatatlan mód A szteganográfia segítségével szemmel láthatatlan vízjeleket hozhat létre a képek bájtjaiban Használj LSB-t LSB (Less Significant Bit) szteganográfiai módszer kerül alkalmazásra, egyébként FD (Frequency Domain) Vörös szemek automatikus eltávolítása Jelszó Kinyit A PDF védett A művelet majdnem kész. A mostani lemondáshoz újra kell indítani Módosítás dátuma Módosítás dátuma (megfordítva) Méret Méret (fordított) MIME típus MIME típus (fordított) Kiterjesztés Kiterjesztés (fordított) Hozzáadás dátuma Hozzáadás dátuma (megfordítva) Balról jobbra Jobbról balra Fentről lefelé Alulról felfelé Folyékony üveg A nemrég bejelentett IOS 26-on és annak folyékony üveg tervezési rendszerén alapuló kapcsoló Válassza ki a képet vagy illessze be/importálja a Base64 adatokat alább A kezdéshez írja be a kép hivatkozását Link beillesztése Kaleidoszkóp Másodlagos szög Oldalak Csatorna mix Kék zöld Piros kék Zöld piros Vörösbe Zöldbe Kékbe Cián Bíborvörös Sárga Színes Féltónus Körvonal Szintek Offset Voronoi kristályosodik Alak Nyújtsd Véletlenszerűség Folttalanítás Diffúz Kutya Második sugár Egyenlíteni Izzás Forgasd és csipkedj Pontozás Szegély színe Polárkoordináták Közvetlenül a polárisra Polárisról egyenesre Fordítsa meg a kört Zaj csökkentése Egyszerű Solarize Szövés X Gap Y Gap X szélesség Y Szélesség Forgat Gumibélyegző Kenet Sűrűség Keverék Gömblencse torzítása Törésmutató Ív Terítési szög Szikra Sugarak ASCII Gradiens Mary Őszi Csont Sugárhajtású Téli Óceán Nyári Tavaszi Cool Variant HSV Rózsaszín Forró Szó Magma Pokol Vérplazma Viridis Polgárok Szürkület Twilight Shifted Perspektíva Auto Deskew Vágás engedélyezése Termés vagy perspektíva Abszolút Turbó Mélyzöld Lencsekorrekció A céllencse profilfájlja JSON formátumban Töltse le a kész lencseprofilokat Rész százalékok Exportálás JSON-ként Karakterlánc másolása palettaadatokkal JSON-ábrázolásként Varrás faragás Kezdőképernyő Képernyőzár Beépített Háttérképek exportálása Frissítés Szerezze be az aktuális otthoni, zárolási és beépített háttérképeket Engedélyezze a hozzáférést az összes fájlhoz, ez szükséges a háttérképek letöltéséhez A külső tárhely engedélyének kezelése nem elegendő, engedélyeznie kell a képekhez való hozzáférést, és feltétlenül válassza az \"Minden engedélyezése\" lehetőséget. Előbeállítás hozzáadása a fájlnévhez A kiválasztott előre beállított utótagot hozzáfűzi a képfájl nevéhez Képméretezési mód hozzáadása a fájlnévhez A kiválasztott képméretezési móddal utótagot fűz a képfájl nevéhez Ascii Art Konvertálja a képet ascii szöveggé, amely képnek fog kinézni Params Egyes esetekben negatív szűrőt alkalmaz a képre a jobb eredmény érdekében Képernyőkép feldolgozása A képernyőkép nem készült, próbálkozzon újra A mentés kimaradt %1$s fájl kimaradt Engedélyezze az átugrást, ha nagyobb Egyes eszközök kihagyhatják a képek mentését, ha az eredményül kapott fájlméret nagyobb, mint az eredeti Eseménynaptár Érintkezés Email Elhelyezkedés Telefon Szöveg SMS URL Wi-Fi Nyitott hálózat N/A SSID Telefon Üzenet Cím Téma Test Név Szervezet Cím Telefonok E-mailek URL-ek Címek Összegzés Leírás Elhelyezkedés Szervező Kezdés dátuma Befejezés dátuma Állapot Szélesség Hosszúság Vonalkód létrehozása Vonalkód szerkesztése Wi-Fi konfiguráció Biztonság Válassza ki a kapcsolatot Adjon engedélyt a névjegyeknek a beállításokban, hogy automatikusan kitöltsék a kiválasztott névjegyet Elérhetőségi adatok Keresztnév Középső név Vezetéknév Kiejtés Telefon hozzáadása E-mail hozzáadása Adjon hozzá címet Weboldal Webhely hozzáadása Formázott név Ezt a képet a vonalkód fölé helyezzük el Kód testreszabása Ezt a képet logóként fogják használni a QR-kód közepén Logó Logó párnázás Logó mérete Logó sarkok Negyedik szem Szemszimmetriát ad a QR-kódhoz azáltal, hogy az alsó végsarokhoz hozzáad egy negyedik szemet Pixel alak Keret alakja Labda alakú Hibajavítási szint Sötét szín Világos szín Hyper OS Xiaomi HyperOS-szerű stílus Maszk minta Előfordulhat, hogy ez a kód nem szkennelhető, módosítsa a megjelenési paramétereket, hogy minden eszközzel olvasható legyen Nem szkennelhető Az eszközök úgy néznek ki, mint a kezdőképernyőn megjelenő alkalmazásindító, hogy kompaktabbak legyenek Indító mód Egy területet kitölt a kiválasztott ecsettel és stílussal Flood Fill Permet Graffity stílusú útvonalat rajzol Négyzet alakú részecskék A permetező részecskék körök helyett négyzet alakúak lesznek Paletta eszközök Létrehozhat alap/anyagot a palettán a képből, vagy importálhat/exportálhat különböző palettaformátumokba Paletta szerkesztése Exportálás/importálás paletta különböző formátumokba Színnév A paletta neve Paletta formátum A létrehozott paletta exportálása különböző formátumokba Új színt ad az aktuális palettához A %1$s formátum nem támogatja a palettanév megadását A Play Áruház irányelvei miatt ez a funkció nem illeszthető bele a jelenlegi buildbe. A funkció eléréséhez töltse le az ImageToolbox alkalmazást egy másik forrásból. A GitHubon elérhető buildeket alább találja. Nyissa meg a Github oldalt Az eredeti fájl a kiválasztott mappába való mentés helyett újra cserélődik Rejtett vízjelszöveg észlelve Rejtett vízjelképet észlelt Ez a kép el volt rejtve Generatív festészet Lehetővé teszi objektumok eltávolítását a képről mesterséges intelligencia modell segítségével, anélkül, hogy az OpenCV-re támaszkodna. A funkció használatához az alkalmazás letölti a szükséges modellt (~200 MB) a GitHubról Lehetővé teszi objektumok eltávolítását a képről mesterséges intelligencia modell segítségével, anélkül, hogy az OpenCV-re támaszkodna. Ez egy hosszú ideig tartó művelet lehet Hibaszint-elemzés Fényerő gradiens Átlagos távolság Mozgásérzékelés másolása Tartsa meg Együttható A vágólap adatai túl nagyok Az adatok túl nagyok a másoláshoz Egyszerű szövés pixelizálás Lépcsőzetes pixelizáció Keresztpixelizáció Mikro makró pixelizálás Orbitális pixelizáció Vortex pixelizálás Impulzus rács pixelizálás A mag pixelizációja Radial Weave pixelizáció Nem lehet megnyitni a következőt: \"%1$s\" Havazás mód Engedélyezve Szegély keret Glitch Variant Csatornaváltás Max Offset VHS Glitch blokkolása Blokkméret CRT görbület Görbület Chroma Pixel Melt Max Drop AI eszközök Különféle eszközök a képek feldolgozásához ai modelleken keresztül, például műtermékek eltávolítása vagy zajtalanítása Tömörítés, szaggatott vonalak Rajzfilmek, adás tömörítés Általános tömörítés, általános zaj Színtelen rajzfilm zaj Gyors, általános tömörítés, általános zaj, animáció/képregény/anime Könyv szkennelés Expozíció korrekció A legjobb általános tömörítésnél, színes képeknél A legjobb az általános tömörítésnél, szürkeárnyalatos képeknél Általános tömörítés, szürkeárnyalatos képek, erősebb Általános zaj, színes képek Általános zaj, színes képek, jobb részletek Általános zaj, szürkeárnyalatos képek Általános zaj, szürkeárnyalatos képek, erősebbek Általános zaj, szürkeárnyalatos képek, legerősebb Általános tömörítés Általános tömörítés Texturizálás, h264 tömörítés VHS tömörítés Nem szabványos tömörítés (cinepak, msvideo1, roq) Bink tömörítés, jobb a geometriában Bink tömörítés, erősebb Bink tömörítés, puha, megőrzi a részleteket Lépcső-lépés hatás megszüntetése, simítás Szkennelt műalkotások/rajzok, enyhe tömörítés, moire Színes sávozás Lassú, féltónusok eltávolítása Általános színező a szürkeárnyalatos/fekete képekhez, a jobb eredmény érdekében használja a DDColort Él eltávolítása Eltávolítja a túlélezést Lassú, háborgó Anti-aliasing, általános műtermékek, CGI A KDM003 vizsgálati feldolgozás Könnyű képjavító modell Tömörítési műtermékek eltávolítása Tömörítési műtermékek eltávolítása Kötszer eltávolítás sima eredménnyel Féltónus minta feldolgozás Dither minta eltávolítása V3 JPEG műtermék eltávolítása V2 H.264 textúra javítás VHS élesítés és javítás Összevonás Darab mérete Átfedési méret A %1$s px-nél nagyobb képeket a rendszer szeletelve és darabokban dolgozza fel, átfedésben keveri ezeket a látható varratok elkerülése érdekében. A nagy méretek instabilitást okozhatnak az alacsony kategóriás eszközöknél Válasszon egyet az indításhoz Törli a %1$s modellt? Újra le kell töltenie Erősítse meg Modellek Letöltött modellek Elérhető modellek Felkészülés Aktív modell Nem sikerült megnyitni a munkamenetet Csak .onnx/.ort modellek importálhatók Import modell Egyéni onnx modell importálása további használatra, csak az onnx/ort modellek fogadhatók el, szinte minden esrgan-szerű változatot támogat Importált modellek Általános zaj, színes képek Általános zaj, színes képek, erősebbek Általános zaj, színes képek, a legerősebb Csökkenti a dithering műtermékeket és a színsávosodást, javítja a sima színátmeneteket és az egyenletes színterületeket. Fokozza a kép fényerejét és kontrasztját a kiegyensúlyozott fénypontokkal, miközben megőrzi a természetes színeket. Világosabbá teszi a sötét képeket, miközben megtartja a részleteket és elkerüli a túlexponálást. Eltávolítja a túlzott színtónust, és visszaállítja a semlegesebb és természetesebb színegyensúlyt. Poisson-alapú zajtonizálást alkalmaz, hangsúlyt fektetve a finom részletek és textúrák megőrzésére. Lágy Poisson zajtónizálást alkalmaz a simább és kevésbé agresszív vizuális eredmény érdekében. Egységes zajtónus a részletek megőrzésére és a kép tisztaságára összpontosít. Gyengéd, egyenletes zajtónus a finom textúra és sima megjelenés érdekében. Javítja a sérült vagy egyenetlen területeket a műtermékek újrafestésével és a kép konzisztenciájának javításával. Könnyű sávbontású modell, amely minimális teljesítményköltséggel távolítja el a színsávokat. Optimalizálja a képeket nagyon nagy tömörítésű műtermékekkel (0-20%-os minőség) a jobb tisztaság érdekében. Javítja a képeket nagy tömörítésű műtermékekkel (20-40%-os minőség), visszaállítja a részleteket és csökkenti a zajt. Javítja a képeket mérsékelt tömörítéssel (40-60%-os minőség), egyensúlyban tartva az élességet és a simaságot. Finomítja a képeket enyhe tömörítéssel (60-80%-os minőség), hogy javítsa a finom részleteket és textúrákat. Kissé javítja a szinte veszteségmentes képeket (80-100%-os minőség), miközben megőrzi a természetes megjelenést és a részleteket. Egyszerű és gyors színezés, rajzfilmek, nem ideális Kissé csökkenti a kép elmosódását, javítja az élességet anélkül, hogy műtermékeket okozna. Hosszú távú műveletek Kép feldolgozása Feldolgozás Eltávolítja a nehéz JPEG tömörítési műtermékeket a nagyon gyenge minőségű képekről (0-20%). Csökkenti az erős JPEG műtermékeket az erősen tömörített képeken (20-40%). Megtisztítja a közepes JPEG műtermékeket, miközben megőrzi a kép részleteit (40-60%). Finomítja a könnyű JPEG műtermékeket meglehetősen jó minőségű képeken (60-80%). Finoman csökkenti a kisebb JPEG műtermékeket a szinte veszteségmentes képeken (80-100%). Javítja a finom részleteket és textúrákat, javítja az észlelt élességet súlyos műtermékek nélkül. A feldolgozás befejeződött A feldolgozás sikertelen Javítja a bőr textúráját és részleteit, miközben megőrzi a természetes megjelenést, a sebességre optimalizálva. Eltávolítja a JPEG tömörítési műtermékeket, és visszaállítja a tömörített fényképek képminőségét. Csökkenti az ISO-zajt a gyenge fényviszonyok mellett készült fényképeken, megőrzi a részleteket. Javítja a túlexponált vagy „jumbo” kiemeléseket, és helyreállítja a jobb tónusegyensúlyt. Könnyű és gyors színezésű modell, amely természetes színeket ad a szürkeárnyalatos képekhez. DEJPEG Denoise Színezd ki Műtárgyak Növelje Anime Szkennel Előkelő X4 felskálázó általános képekhez; apró modell, amely kevesebb GPU-t és időt használ, mérsékelt elmosódással és zajjal. X2-es felskálázó az általános képekhez, a textúrák és a természetes részletek megőrzéséhez. X4-es felskálázó általános képekhez továbbfejlesztett textúrákkal és valósághű eredményekkel. Anime képekhez optimalizált X4 felskálázó; 6 RRDB blokk az élesebb vonalak és részletek érdekében. X4 felskálázó MSE veszteséggel, egyenletesebb eredményeket és kevesebb műterméket biztosít az általános képekhez. Anime képekhez optimalizált X4 Upscaler; 4B32F változat élesebb részletekkel és sima vonalakkal. X4 UltraSharp V2 modell általános képekhez; kiemeli az élességet és a tisztaságot. X4 UltraSharp V2 Lite; gyorsabb és kisebb, megőrzi a részleteket, miközben kevesebb GPU-memóriát használ. Könnyű modell a háttér gyors eltávolításához. Kiegyensúlyozott teljesítmény és pontosság. Portrékkal, tárgyakkal és jelenetekkel működik. A legtöbb használati esetre ajánlott. Távolítsa el a BG-t Vízszintes határvastagság Függőleges határvastagság %1$s színek %1$s színek A jelenlegi modell nem támogatja a darabolást, a kép az eredeti méretekben kerül feldolgozásra, ami nagy memóriafogyasztást és problémákat okozhat az alsó kategóriás eszközökkel A darabolás le van tiltva, a kép az eredeti méretekben kerül feldolgozásra, ami nagy memóriafogyasztást és problémákat okozhat az alsó kategóriás eszközökkel, de jobb eredményeket ad a következtetéseknél Dobogó Nagy pontosságú képszegmentációs modell a háttér eltávolításához Az U2Net könnyű változata a háttér gyorsabb eltávolításához kisebb memóriahasználat mellett. A Full DDColor modell kiváló minőségű színezést biztosít az általános képekhez minimális műtermékekkel. A legjobb választás az összes színezési modell közül. DDColor Képzett és privát művészi adatkészletek; változatos és művészi színezési eredményeket hoz létre kevesebb irreális színművel. Swin Transformer alapú könnyű BiRefNet modell a pontos háttér eltávolításhoz. Kiváló minőségű háttéreltávolítás éles szélekkel és kiváló részletmegőrzéssel, különösen összetett tárgyakon és bonyolult háttereken. Háttéreltávolító modell, amely pontos, sima élű maszkokat készít, általános tárgyakhoz és mérsékelt részletmegőrzéshez alkalmas. A modell már letöltve A modell sikeresen importálva Írja be Kulcsszó Nagyon gyors Normál Lassú Nagyon lassú Számítsd ki a százalékokat A minimális érték %1$s Kép torzítása ujjakkal való rajzolással Warp Keménység Warp mód Mozog Összezsugorodik Swirl CW Örvény CCW Fade Strength Top Drop Alsó csepp Start Drop Vége csepp Letöltés Sima formák Használjon szuperellipszeket a szokásos lekerekített téglalapok helyett a simább, természetesebb formák érdekében Alaktípus Vágott Lekerekített Sima Éles élek lekerekítés nélkül Klasszikus lekerekített sarkok Formák típusa Sarkok mérete Squircle Elegáns, lekerekített felhasználói felület elemei Fájlnév formátum A fájlnév legelején elhelyezett egyéni szöveg, tökéletes projektnevekhez, márkákhoz vagy személyes címkékhez. Az eredeti fájlnevet használja kiterjesztés nélkül, így segít megőrizni a forrás azonosítását. A kép szélessége pixelben, hasznos a felbontás változásainak követéséhez vagy az eredmények méretezéséhez. A képmagasság pixelben, ami hasznos a képarányok vagy az exportálás során. Véletlen számjegyeket generál az egyedi fájlnevek garantálása érdekében; adjon hozzá több számjegyet az ismétlődések elleni fokozott biztonság érdekében. Automatikusan növekvő számláló kötegelt exportáláshoz, ideális több kép mentéséhez egy munkamenetben. Az alkalmazott előre beállított nevet beszúrja a fájlnévbe, így könnyen megjegyezheti, hogyan dolgozták fel a képet. Megjeleníti a feldolgozás során használt képméretezési módot, segítve az átméretezett, vágott vagy illesztett képek megkülönböztetését. A fájlnév végén elhelyezett egyéni szöveg, amely hasznos a verziószámításhoz, például a _v2, _edited vagy _final. A fájl kiterjesztése (png, jpg, webp stb.), amely automatikusan megfelel a tényleges mentett formátumnak. Egy testreszabható időbélyeg, amely lehetővé teszi, hogy saját formátumot határozzon meg Java specifikációval a tökéletes rendezés érdekében. Fling típus Android natív iOS stílus Sima görbe Gyors Stop Ugráló Úszó Lendületes Ultra Smooth Adaptív Kisegítő lehetőségek tudatában Csökkentett mozgás Natív Android görgetőfizika az alapvonal összehasonlításához Kiegyensúlyozott, sima görgetés általános használatra Nagyobb súrlódású iOS-szerű görgetési viselkedés Egyedülálló spline-görbe a határozott görgetéshez Precíz görgetés gyors leállítással Játékos, érzékeny pattogó tekercs Hosszú, csúszó tekercsek a tartalomböngészéshez Gyors, érzékeny görgetés az interaktív felhasználói felületekhez Prémium sima görgetés kiterjesztett lendülettel A fizikát a kirepülési sebesség alapján állítja be Tiszteletben tartja a rendszer akadálymentesítési beállításait Minimális mozgás a hozzáférhetőségi igényekhez Elsődleges vonalak Minden ötödik sor vastagabb vonalat ad hozzá Kitöltés színe Rejtett eszközök Megosztáshoz rejtett eszközök Színes könyvtár Böngésszen a színek hatalmas gyűjteményében Élesíti és eltávolítja a képek elmosódását, miközben megőrzi a természetes részleteket, ideális az életlen fényképek kijavításához. Intelligensen visszaállítja a korábban átméretezett képeket, helyreállítva az elveszett részleteket és textúrákat. Élőszereplős tartalomhoz optimalizálva, csökkenti a tömörítési műtermékeket, és javítja a film/TV-műsor képkockáinak finom részleteit. A VHS-minőségű felvételeket HD-vé alakítja, eltávolítja a szalagzajt és javítja a felbontást, miközben megőrzi a vintage hangulatot. A nehéz szöveget tartalmazó képekre és képernyőképekre specializálódott, élesíti a karaktereket és javítja az olvashatóságot. Különféle adatkészletekre kiképzett fejlett felskálázás, kiváló általános célú fényképjavításhoz. Interneten tömörített fényképekhez optimalizálva, eltávolítja a JPEG műtermékeket és visszaállítja a természetes megjelenést. Továbbfejlesztett változat webes fényképekhez jobb textúramegőrzéssel és műtermékcsökkentéssel. Kétszeres felskálázás a Dual Aggregation Transformer technológiával, megőrzi az élességet és a természetes részleteket. 3-szoros felskálázás fejlett transzformátor architektúrával, ideális közepes nagyítási igényekhez. 4x kiváló minőségű felskálázás a legmodernebb transzformátor hálózattal, megőrzi a finom részleteket nagyobb méretekben. Eltávolítja az elmosódást/zajt és a remegést a fényképekről. Általános célú, de legjobb fotókon. Az alacsony minőségű képeket visszaállítja a Swin2SR transzformátor segítségével, amely a BSRGAN leromlására van optimalizálva. Kiválóan alkalmas erős tömörítési műtermékek rögzítésére és 4-szeres léptékű részletek javítására. 4x-es felskálázás a BSRGAN degradációra kiképzett SwinIR transzformátorral. GAN-t használ az élesebb textúrák és a természetesebb részletek érdekében a fényképeken és az összetett jeleneteken. Útvonal PDF egyesítése Több PDF fájl egyesítése egyetlen dokumentumban Fájlok sorrendje pp. PDF felosztása Konkrét oldalak kibontása a PDF-dokumentumból PDF forgatása Állandóan javítsa az oldal tájolását Oldalak PDF átrendezése Az oldalak átrendezéséhez húzza át őket Tartsa és húzza az oldalakat Oldalszámok Automatikusan adja hozzá a számozást a dokumentumokhoz Címke formátum PDF szöveggé (OCR) Egyszerű szöveg kinyerése PDF-dokumentumaiból Egyéni fedvényszöveg márkaépítéshez vagy biztonsághoz Aláírás Adja hozzá elektronikus aláírását bármely dokumentumhoz Ezt aláírásként fogják használni PDF feloldása Távolítsa el a jelszavakat a védett fájljaiból PDF védelme Biztosítsa dokumentumait erős titkosítással Siker PDF feloldva, mentheti vagy megoszthatja PDF javítása Próbálja meg kijavítani a sérült vagy olvashatatlan dokumentumokat Szürkeárnyalatos Konvertálja az összes dokumentumba ágyazott képet szürkeárnyalatossá PDF tömörítése Optimalizálja a dokumentumfájl méretét a könnyebb megosztás érdekében Az ImageToolbox újraépíti a belső kereszthivatkozási táblát, és a semmiből regenerálja a fájlstruktúrát. Ezzel visszaállíthatja a hozzáférést számos olyan fájlhoz, amelyeket \\"nem lehet megnyitni\\" Ez az eszköz az összes dokumentumképet szürkeárnyalatossá alakítja. A legjobb a nyomtatáshoz és a fájlméret csökkentéséhez Metaadatok Szerkessze a dokumentum tulajdonságait a jobb adatvédelem érdekében Címkék Termelő Szerző Kulcsszavak Teremtő Adatvédelem Deep Clean Törölje a dokumentum összes elérhető metaadatát oldal Mély OCR Kivonja a szöveget a dokumentumból, és tárolja azt egyetlen szövegfájlban a Tesseract motor segítségével Nem lehet eltávolítani az összes oldalt PDF-oldalak eltávolítása Adott oldalak eltávolítása a PDF-dokumentumból Koppintson az Eltávolítás elemre Manuálisan PDF vágása A dokumentum oldalainak levágása tetszőleges határig PDF lapítása A PDF-et módosíthatatlanná teheti a dokumentumoldalak raszterezésével Nem sikerült elindítani a kamerát. Kérjük, ellenőrizze az engedélyeket, és győződjön meg arról, hogy más alkalmazás nem használja. Képek kibontása Kivonja a PDF-be ágyazott képeket eredeti felbontásukban Ez a PDF-fájl nem tartalmaz beágyazott képeket Ez az eszköz minden oldalt beolvas, és teljes minőségű forrásképeket állít vissza – tökéletes az eredeti dokumentumok dokumentumokból való mentéséhez Aláírás rajzolása Pen Params Használjon saját aláírást képként a dokumentumokon Zip PDF Ossza fel a dokumentumot adott időközönként, és csomagolja az új dokumentumokat zip archívumba Intervallum Nyomtatás PDF Készítse elő a dokumentumot egyéni oldalmérettel történő nyomtatáshoz Oldalak Laponként Tájolás Oldalméret Margó Virágzás Puha térd Animéhez és rajzfilmekhez optimalizálva. Gyors felskálázás továbbfejlesztett természetes színekkel és kevesebb műtermékkel A Samsung One UI 7 stílusa Írja be ide az alapvető matematikai szimbólumokat a kívánt érték kiszámításához (pl. (5+5)*10) Matematikai kifejezés Válasszon legfeljebb %1$s képet Tartsa a dátumot és az időt Mindig őrizze meg a dátumhoz és az időhöz kapcsolódó exif címkéket, az exif megtartása opciótól függetlenül működik Háttérszín Alfa formátumokhoz Lehetővé teszi a háttérszín beállítását minden alfa-támogatással rendelkező képformátumhoz, ha le van tiltva, ez csak a nem alfa-formátumok esetén érhető el Projekt megnyitása Folytassa a korábban elmentett Image Toolbox projekt szerkesztését Nem lehet megnyitni az Image Toolbox projektet Az Image Toolbox projektből hiányoznak a projektadatok Az Image Toolbox projekt sérült Nem támogatott Image Toolbox projektverzió: %1$d Projekt mentése Tárolja a rétegeket, a hátteret és a szerkesztési előzményeket egy szerkeszthető projektfájlban Nem sikerült megnyitni Írás kereshető PDF-be Szöveg felismerése képkötegből, és kereshető PDF mentése képpel és kiválasztható szövegréteggel Alfa réteg Vízszintes Flip Függőleges Flip Zár Árnyék hozzáadása Árnyék színe Szöveggeometria Nyújtsa vagy ferdítse a szöveget az élesebb stilizáció érdekében X skála Ferde X Távolítsa el a megjegyzéseket A kiválasztott megjegyzéstípusok, például hivatkozások, megjegyzések, kiemelések, alakzatok vagy űrlapmezők eltávolítása a PDF-oldalakról Hiperhivatkozások Fájlmellékletek Vonalak Előugró ablakok Bélyegek Alakzatok Szöveg Megjegyzések Szövegjelölés Űrlapmezők Jelölés Ismeretlen Annotációk Csoportbontás feloldása Adjon hozzá homályos árnyékot a réteg mögé konfigurálható színekkel és eltolásokkal ================================================ FILE: core/resources/src/main/res/values-in/strings.xml ================================================ Ada yang tidak beres: %1$s Ukuran %1$s Disalin ke papan klip Batal Pilih warna Pilih warna dari gambar, salin atau bagikan Simpan EXIF Gambar: %d Ubah pratinjau Menghapus Default Cahaya Sistem Warna-warna yang dinamis Kustomisasi Tentang aplikasi Tidak ada yang ditemukan oleh kueri Anda Menambahkan Memuat… Gambar terlalu besar untuk dipratinjau, tetapi akan dicoba untuk disimpan Pilih gambar untuk memulai Lebar %1$s Tinggi %1$s Kualitas Perluasan Mengubah ukuran jenis Eksplisit Fleksibel Pilih Gambar Apakah Anda ingin menutup aplikasi? Penutupan aplikasi Tetap di sini Tutup Atur ulang gambar Perubahan gambar akan dikembalikan ke semula Nilai diatur ulang dengan benar Atur ulang Ada yang tidak beres Mulai ulang aplikasi Pengecualian Edit EXIF OK Tidak ditemukan data EXIF Tambahkan tag Simpan Bersihkan Hapus EXIF Semua data EXIF gambar akan dihapus, tindakan ini tidak dapat dibatalkan! Preset Pangkas Menyimpan Semua perubahan yang belum disimpan akan hilang, jika Anda keluar sekarang Kode sumber Dapatkan informasi terbaru, diskusikan berbagai isu, dan lainnya Ubah ukuran tunggal Mengubah spesifikasi gambar tunggal yang diberikan Gambar Warna Warna disalin Pangkas gambar hingga batas apa pun Versi Menghasilkan contoh palet warna dari gambar yang diberikan Menghasilkan palet Palet Memperbarui Versi baru %1$s Jenis yang tidak didukung: %1$s Tidak dapat menghasilkan palet untuk gambar yang diberikan Asli Folder keluaran Kustom Tidak ditentukan Penyimpanan perangkat Ubah ukuran berdasarkan berat Ukuran maksimum dalam KB Mengubah ukuran gambar mengikuti ukuran yang diberikan dalam KB Bandingkan Bandingkan dua gambar yang diberikan Pilih dua gambar untuk memulai Pilih gambar Pengaturan Mode Malam Gelap Izinkan monetisasi gambar Jika diaktifkan, ketika Anda memilih gambar untuk diedit, warna aplikasi akan diadopsi ke gambar ini Bahasa Mode Amoled Jika diaktifkan, warna permukaan akan ditetapkan ke gelap mutlak dalam mode malam Skema warna Merah Hijau Biru Rekatkan Kode aRGB yang valid. Tidak ada yang bisa ditempelkan Tidak dapat mengubah skema warna aplikasi saat warna dinamis diaktifkan Tema aplikasi akan didasarkan pada warna yang dipilih Tidak ada pembaruan yang ditemukan Pelacak masalah Kirim laporan bug dan permintaan fitur di sini Bantu menerjemahkan Memperbaiki kesalahan terjemahan atau melokalkan proyek ke bahasa lain Cari di sini Jika diaktifkan, maka warna aplikasi akan diadopsi ke warna wallpaper Gagal menyimpan gambar %d Primer Tersier Sekunder Ketebalan perbatasan Permukaan Nilai-nilai Izin Hibah Aplikasi perlu akses ke penyimpanan Anda untuk menyimpan gambar untuk bekerja, Harap berikan izin di kotak dialog berikutnya. Aplikasi membutuhkan izin ini untuk bekerja, mohon berikan secara manual Penyimpanan eksternal Warna monet Aplikasi ini sepenuhnya gratis, tetapi jika Anda ingin mendukung pengembangan proyek, Anda dapat mengklik di sini Penyelarasan FAB Periksa pembaruan Jika diaktifkan, dialog pembaruan akan ditampilkan kepada Anda setelah aplikasi dimulai Zoom gambar Bagikan Awalan Nama file Emoji Pilih emoji mana yang akan ditampilkan di layar utama Tambah ukuran file Jika diaktifkan, menambahkan lebar dan tinggi gambar yang disimpan ke nama file output Hapus EXIF Hapus metadata EXIF dari beberapa gambar Pratinjau semua jenis gambar: GIF, SVG, dan sebagainya Pratinjau gambar Menentukan urutan alat di layar utama Pemilih foto modern Android yang muncul di bagian bawah layar, mungkin hanya berfungsi pada Android 12+ dan juga memiliki masalah dengan penerimaan metadata EXIF Sumber gambar Pemilih foto Penjelajah file Pemilih gambar galeri sederhana, hanya akan berfungsi jika Anda memiliki aplikasi tersebut Pengaturan opsi Ubah Urutan Jumlah emoji sequenceNum originalFilename Tambahkan nama file asli Jika diaktifkan, akan menambahkan nama file asli pada nama gambar output Gunakan GetContent untuk memilih gambar, berfungsi di mana saja, tetapi juga dapat mengalami masalah dengan menerima gambar yang dipilih pada beberapa perangkat, itu bukan kesalahan saya Ganti nomor urut Jika diaktifkan akan menggantikan cap waktu standar ke nomor urut gambar jika Anda menggunakan pemrosesan batch Menambahkan nama file asli tidak berfungsi jika sumber gambar pemilih foto dipilih Warna Satu warna Gamma Sorotan dan bayangan Negatif Crosshatch Jarak Lebar garis Radius Skala Tambahkan filter Batas mengubah ukuran Ubah ukuran gambar yang diberikan untuk mengikuti batas lebar dan tinggi yang diberikan dengan rasio aspek hemat Sketsa Ambang Penindasan tidak maksimal Lihatlah Tumpukan buram kabur cepat Muat gambar dari net Muat gambar apa pun dari internet, pratinjau, perbesar, dan juga simpan atau edit jika Anda mau Tautan gambar Mengisi Bugar Skala konten Memaksa setiap gambar menjadi gambar yang ditentukan oleh parameter Lebar dan Tinggi - dapat mengubah rasio aspek Mengubah ukuran gambar menjadi gambar dengan sisi panjang yang ditentukan oleh parameter Lebar atau Tinggi, semua penghitungan ukuran akan dilakukan setelah menyimpan - mempertahankan rasio aspek Kecerahan Kontras Warna Kejenuhan Saring Terapkan rantai filter apa pun ke gambar yang diberikan Filter Lampu Penyaring warna Alfa Paparan Keseimbangan putih Suhu Highlight Bayangan Kabut Memengaruhi Jarak Lereng Mengasah Warna coklat tua Solarisasi Getaran Hitam dan putih Tepi sobel Mengaburkan Setengah suara ruang warna GCA gaussian blur Kabur kotak Kabur bilateral Menatah Laplacian Skema Awal Akhir Kuwahara menghaluskan Distorsi Sudut Keramaian Tonjolan Pelebaran Refraksi bola Indeks bias Refraksi bola kaca Matriks warna Kegelapan Tingkat kuantisasi Toon halus Toon Buat poster Inklusi piksel lemah Konvolusi 3x3 penyaring RGB Warna palsu Warna pertama Warna kedua Susun ulang Ukuran buram Pusat buram x Pusat buram y Zoom buram Keseimbangan warna Ambang batas pencahayaan Galeri Tidak ada gambar Ukuran file Ukuran cache Gambarlah seperti di buku sketsa, atau gambarlah di latar belakang itu sendiri Warna cat Cat alfa Menggambar pada gambar Pilih gambar dan gambar sesuatu di atasnya Menggambar di latar belakang Simpan file ini di perangkat Anda atau gunakan tindakan berbagi untuk meletakkannya di mana pun Anda mau Melewati Menyimpan dalam mode %1$s bisa jadi tidak stabil, karena merupakan format lossless Anda menonaktifkan aplikasi File, aktifkan untuk menggunakan fitur ini Menggambar Warna latar belakang Enkripsi Dekripsi AES-256, mode GCM, tanpa padding, infus acak 12 byte. Kunci digunakan sebagai hash SHA-3 (256 bit). Kesesuaian Ditemukan %1$s Jika diaktifkan, cache aplikasi akan dihapus saat memulai aplikasi Peralatan Kelompokkan opsi berdasarkan jenis Opsi grup di layar utama jenisnya alih-alih pengaturan daftar kustom Tidak dapat mengubah pengaturan saat pengelompokan opsi diaktifkan Dekripsi Harap perhatikan bahwa kompatibilitas dengan perangkat lunak atau layanan enkripsi file lainnya tidak dijamin. Perawatan kunci atau konfigurasi sandi yang sedikit berbeda mungkin menjadi alasan ketidakcocokan. Kustomisasi sekunder Pilih warna latar belakang dan gambar di atasnya Pilih file untuk memulai Enkripsi Mencoba menyimpan gambar dengan lebar dan tinggi tertentu dapat menyebabkan kesalahan OOM, lakukan ini dengan risiko Anda sendiri, dan jangan bilang saya tidak memperingatkan Anda! Pembersihan cache otomatis Sunting tangkapan layar Tangkapan layar Opsi mundur Menyalin Sandi Enkripsi dan Dekripsi file apa pun (tidak hanya gambar) berdasarkan algoritme kripto AES Pilih berkas Kunci Fitur Penerapan Ukuran file maksimum dibatasi oleh OS Android dan memori yang tersedia, yang jelas bergantung pada perangkat Anda. \nHarap diperhatikan: memori bukanlah penyimpanan. Berkas diproses Kata sandi tidak valid atau file yang dipilih tidak dienkripsi Enkripsi file berbasis kata sandi. File yang diproses dapat disimpan di direktori yang dipilih atau dibagikan. File yang didekripsi juga bisa langsung dibuka. Cache Membuat Preset di sini menentukan % dari file output, misalnya jika Anda memilih preset 50 pada gambar 5mb maka Anda akan mendapatkan gambar 2,5mb setelah disimpan Jika Anda memilih preset 125, gambar akan disimpan dengan ukuran 125% dari gambar asli. Jika Anda memilih preset 50, gambar akan disimpan dengan ukuran 50% Jika diaktifkan, nama file keluaran akan sepenuhnya acak Acak nama file Simpan ke folder %1$s dengan nama %2$s Disimpan ke folder %1$s Obrolan Telegram Diskusikan aplikasi dan dapatkan tanggapan dari pengguna lain, di sini Anda bisa mendapatkan pembaruan dan wawasan beta Hitungan Kolom Pikselasi yang Ditingkatkan Pikselasi Goresan Pikselasi Berlian yang Ditingkatkan Pikselasi Berlian Simbol Modus menggambar Kesalahan yang Ditingkatkan Pergeseran Saluran X Pergeseran Korupsi Y Kegiatan Makanan dan minuman Hubungi saya Kelembutan sikat Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz 0123456789 !? Pulihkan gambar Hapus latar belakang Jika diaktifkan, jalur gambar akan direpresentasikan sebagai panah penunjuk Cadangan Pengaturan berhasil dipulihkan Emosi Hapus latar belakang secara otomatis Buat Masalah Pipet Teks Hapus Skema huruf Menggunakan skala font yang besar dapat menyebabkan gangguan dan masalah UI, yang tidak dapat diperbaiki. Gunakan dengan hati-hati. Pulihkan pengaturan aplikasi dari file yang dibuat sebelumnya Modus hapus Perjalanan dan Tempat Pembaruan Orientasi & Hanya Deteksi Skrip Orientasi Otomatis & Deteksi Skrip Hanya otomatis Mobil Garis tunggal Kata tunggal Lingkari kata Kesalahan Jumlah Benih Anaglif Kebisingan Sortir Piksel Acak Anda akan menghapus masker filter yang dipilih. Operasi ini tidak dapat dibatalkan Hapus Masker Naga Aldridge Memotong Uchimura Mobius Transisi Puncak Anomali Warna Direktori \"%1$s\" tidak ditemukan, kami mengalihkannya ke direktori default, harap simpan kembali file tersebut papan klip Pin otomatis Getaran Kekuatan Getaran Timpa File Kosong Akhiran Mencari Memungkinkan kemampuan untuk mencari melalui semua alat yang tersedia di layar utama Bebas Gambar ditimpa di tujuan semula Emoji sebagai Skema Warna Masker tanaman Rasio aspek Gunakan jenis topeng ini untuk membuat topeng dari gambar tertentu, perhatikan bahwa gambar tersebut HARUS memiliki saluran alfa Cadangkan dan pulihkan Ini akan mengembalikan pengaturan Anda ke nilai default. Perhatikan bahwa ini tidak dapat dibatalkan tanpa file cadangan yang disebutkan di atas. Anda akan menghapus skema warna yang dipilih. Operasi ini tidak dapat dibatalkan Skala font Bawaan Alam dan Hewan Objek Aktifkan emoji Penghapus latar belakang Hapus latar belakang dari gambar dengan menggambar atau menggunakan opsi Otomatis Pangkas gambar Metadata gambar asli akan disimpan Ruang transparan di sekitar gambar akan dipangkas Radius kabur Ups… Ada yang tidak beres. Anda dapat menulis kepada saya menggunakan opsi di bawah ini dan saya akan mencoba mencari solusinya Ubah ukuran gambar tertentu atau konversikan ke format lain. Metadata EXIF juga dapat diedit di sini jika memilih satu gambar. Hal ini memungkinkan aplikasi mengumpulkan laporan kerusakan secara manual Analisis Saat ini, format %1$s hanya mengizinkan pembacaan metadata EXIF di Android. Gambar keluaran tidak akan memiliki metadata sama sekali saat disimpan. Penghematan hampir selesai. Membatalkan sekarang memerlukan penyimpanan lagi. Izinkan beta Pemeriksaan pembaruan akan mencakup versi aplikasi beta jika diaktifkan Horisontal Skalakan gambar kecil ke besar Gambar kecil akan diperbesar ke gambar terbesar secara berurutan jika diaktifkan Menggambar tepi buram di bawah gambar asli untuk mengisi ruang di sekitarnya, bukan satu warna jika diaktifkan Pikselasi Pikselasi Lingkaran yang Ditingkatkan Toleransi Warna untuk Diganti Warna Sasaran Hapus Warna Kode ulang Sebuah gaya yang sedikit lebih berwarna daripada monokrom Tema yang keras, warna-warni maksimal untuk palet Primer, meningkat untuk palet lainnya Tema yang menyenangkan - rona warna sumber tidak muncul dalam tema Tema monokrom, warna murni hitam/putih/abu-abu Skema yang menempatkan warna sumber di Scheme.primaryContainer Skema yang sangat mirip dengan skema konten Dengan disabilitas Filter Masker Terapkan rantai filter pada area bertopeng tertentu, setiap area topeng dapat menentukan kumpulan filternya sendiri Tambahkan Masker Masker %d Masker filter yang digambar akan ditampilkan untuk menunjukkan kepada Anda hasil perkiraan Varian Sederhana Stabilo Privasi Kabur Gambarlah jalur stabilo runcing semi-transparan Tambahkan beberapa efek bercahaya pada gambar Anda Yang default, paling sederhana - hanya warnanya Mengaburkan gambar di bawah jalur yang digambar untuk mengamankan apa pun yang ingin Anda sembunyikan Mirip dengan keburaman privasi, tetapi berpiksel, bukan buram Bilah Aplikasi Mengaktifkan gambar bayangan di belakang bilah aplikasi Menggambar jalur sebagai nilai input Diuraikan Rek Bulat telur Benar Menarik garis lurus dari titik awal ke titik akhir Menggambar oval dari titik awal hingga titik akhir Secara otomatis menambahkan gambar yang disimpan ke clipboard jika diaktifkan Untuk menimpa file, Anda perlu menggunakan sumber gambar \"Explorer\", coba pilih ulang gambar, kami telah mengubah sumber gambar ke sumber yang diperlukan File asli akan diganti dengan yang baru alih-alih disimpan di folder yang dipilih, opsi ini harus berupa sumber gambar \"Explorer\" atau GetContent, saat mengaktifkannya, ini akan disetel secara otomatis Metode penskalaan yang lebih baik mencakup pengambilan sampel ulang Lanczos dan filter Mitchell-Netravali Salah satu cara sederhana untuk memperbesar ukuran adalah dengan mengganti setiap piksel dengan sejumlah piksel dengan warna yang sama Mode penskalaan Android paling sederhana yang digunakan di hampir semua aplikasi Metode untuk menginterpolasi dan mengambil sampel ulang sekumpulan titik kontrol dengan lancar, biasanya digunakan dalam grafik komputer untuk membuat kurva yang mulus Fungsi windowing sering diterapkan dalam pemrosesan sinyal untuk meminimalkan kebocoran spektral dan meningkatkan akurasi analisis frekuensi dengan memperkecil tepi sinyal Teknik interpolasi matematis yang menggunakan nilai dan turunan pada titik ujung suatu segmen kurva untuk menghasilkan kurva yang mulus dan kontinu Metode pengambilan sampel ulang yang mempertahankan interpolasi berkualitas tinggi dengan menerapkan fungsi sinc tertimbang pada nilai piksel Metode pengambilan sampel ulang yang menggunakan filter konvolusi dengan parameter yang dapat disesuaikan untuk mencapai keseimbangan antara ketajaman dan anti-aliasing pada gambar yang diskalakan Memanfaatkan fungsi polinomial yang ditentukan sedikit demi sedikit untuk menginterpolasi dan memperkirakan kurva atau permukaan dengan lancar, representasi bentuk yang fleksibel dan berkelanjutan Kolom tunggal Teks vertikal blok tunggal Blok tunggal karakter tunggal Teks jarang Orientasi teks jarang & Deteksi Skrip Garis mentah Apakah Anda ingin menghapus data pelatihan OCR bahasa \"%1$s\" untuk semua jenis pengenalan, atau hanya untuk jenis pengenalan tertentu (%2$s)? Saat ini Menggunakan kamera untuk mengambil gambar, perhatikan bahwa hanya mungkin mengambil satu gambar dari sumber gambar ini Ulangi tanda air Mengulangi tanda air pada gambar, bukan satu tanda air pada posisi tertentu Mengimbangi Y Jenis Tanda Air Bayer Dua Per Dua Dithering Bayer Tiga Per Tiga Dithering Bayer Empat Demi Empat Dithering Keragu-raguan Floyd Steinberg yang Palsu Pergeseran Saluran Y Ukuran Korupsi Pergeseran Korupsi X Tenda Kabur Sisi Memudar Samping Atas Dasar Kekuatan Marmer Pergolakan Minyak Frekuensi X Frekuensi Y Amplitudo X Amplitudo Y Distorsi Perlin Matriks Warna 3x3 Efek Sederhana Polaroid Tritonomali Ulangan protonomali browni Coda Chrome Penglihatan Malam Hangat Dingin Tritanopia Deutaronotopia Jam Emas Musim Panas yang Panas Kabut Ungu Matahari terbit Pusaran Warna-warni Cahaya Musim Semi yang Lembut Nada Musim Gugur Cahaya Limun Api Spektral Sihir Malam Pemandangan Fantasi Ledakan Warna Gradien Listrik Kegelapan Karamel Gradien Futuristik Portal Luar Angkasa pusaran merah Kode Digital Tidak dapat mengubah format gambar saat opsi timpa file diaktifkan Menggunakan warna utama emoji sebagai skema warna aplikasi, bukan skema warna yang ditentukan secara manual Pulihkan latar belakang Ubah ukuran dan Konversi Upaya Menggambar Panah Sumbangan Pikselasi Lingkaran Ganti Warna Warna untuk Dihapus Mengikis Difusi Anisotropik Difusi Konduksi Terhuyung-huyung Angin Horisontal Buram Bilateral Cepat racun kabur Pemetaan Nada Logaritmik Direalisasikan Warna Goresan Kaca Fraktal Amplitudo Efek Air Ukuran Pemetaan Nada Filmik Hable Pemetaan Nada Hejl Burgess Pemetaan Nada Film ACES Pemetaan Nada Bukit ACES Semua Penyaring Penuh Tengah Awal Akhir Terapkan rantai filter apa pun ke gambar tertentu atau gambar tunggal Beroperasi dengan file PDF: Pratinjau, Konversikan ke kumpulan gambar atau buat satu dari gambar tertentu Pratinjau PDF PDF ke Gambar Gambar ke PDF Pratinjau PDF sederhana Konversi PDF ke Gambar dalam format keluaran tertentu Kemas Gambar yang diberikan ke dalam file PDF keluaran Pembuat Gradien Buat gradien dengan ukuran keluaran tertentu dengan warna dan jenis tampilan yang disesuaikan Kecepatan Hilangkan kabut Akhir Alat PDF Nilai Aplikasi Kecepatan Aplikasi ini sepenuhnya gratis, jika Anda ingin menjadi lebih besar, silakan bintangi proyek ini di Github 😄 Matriks Warna 4x4 Antik Protanopia Akromatomali Akromatopsia Linier Radial Menyapu Tipe Gradien Pusat X Pusat Y Mode Ubin Ulang Cermin Penjepit Stiker Warna Berhenti Tambahkan Warna Properti Surel Laso Menggambar jalur tertutup yang diisi dengan jalur tertentu Mode Gambar Jalur Panah Garis Ganda Gambar Gratis Panah Ganda Panah Garis Anak panah Garis Menggambar jalur dari titik awal ke titik akhir sebagai sebuah garis Menggambar panah penunjuk dari titik awal ke titik akhir sebagai sebuah garis Menggambar panah penunjuk dari jalur tertentu Menggambar panah penunjuk ganda dari titik awal ke titik akhir sebagai sebuah garis Menggambar panah penunjuk ganda dari jalur tertentu Diuraikan Oval Menggambar garis oval dari titik awal hingga titik akhir Menggambar garis lurus dari titik awal ke titik akhir ragu-ragu Pengukur Skala Abu-abu Bayer Delapan Kali Delapan Dithering Floyd Steinberg ragu-ragu Jarvis Judice Ninke Dithering Sierra Dithering Dithering Dua Baris Sierra Sierra Lite Dithering Atkinson ragu-ragu Terjebak Dithering Burkes Dithering Dithering Kiri Ke Kanan Dithering Acak Dithering Ambang Batas Sederhana Jumlah warna maksimal Izinkan pengumpulan statistik penggunaan aplikasi anonim Tunggu Warna Masker Pratinjau Topeng Modus skala Bilinear Han pertapa Lanzos Mitchell Terdekat spline Dasar Nilai Bawaan Nilai dalam rentang %1$s - %2$s Sigma Sigma Spasial Kabur Median kucingmull Bikubik Interpolasi linier (atau bilinear, dalam dua dimensi) biasanya baik untuk mengubah ukuran gambar, namun menyebabkan beberapa pelunakan detail yang tidak diinginkan dan masih bisa agak bergerigi. Hanya Klip Penyimpanan ke penyimpanan tidak akan dilakukan, dan gambar akan dicoba dimasukkan ke clipboard saja Menambahkan wadah dengan bentuk yang dipilih di bawah ikon utama kartu Bentuk Ikon Jahitan Gambar Gabungkan gambar yang diberikan untuk mendapatkan satu gambar besar Penegakan Kecerahan Layar Hamparan Gradien Buatlah gradien apa pun di bagian atas gambar yang diberikan Transformasi Kamera Pilih setidaknya 2 gambar Skala gambar keluaran Orientasi Gambar Bulir Tidak tajam Pastel Kabut Oranye Mimpi Merah Muda Mimpi Lavender dunia maya Matahari Hijau Dunia Pelangi Ungu gelap Tanda air Sampul gambar dengan tanda air teks/gambar yang dapat disesuaikan Mengimbangi X Gambar ini akan digunakan sebagai pola untuk watermarking Warna teks Modus Hamparan Ukuran Piksel Kunci orientasi gambar Nilai %1$s berarti kompresi yang cepat sehingga menghasilkan ukuran file yang relatif besar. %2$s berarti kompresi lebih lambat, sehingga menghasilkan file lebih kecil. Jika diaktifkan dalam mode menggambar, layar tidak akan berputar Bokeh Alat GIF Konversikan gambar menjadi gambar GIF atau ekstrak bingkai dari gambar GIF yang diberikan GIF ke gambar Konversikan file GIF menjadi kumpulan gambar Konversikan kumpulan gambar ke file GIF Gambar ke GIF Pilih gambar GIF untuk memulai Gunakan ukuran bingkai Pertama Ganti ukuran yang ditentukan dengan dimensi bingkai pertama Ulangi Hitungan Penundaan Bingkai milis FPS Gunakan Laso Menggunakan Lasso seperti dalam mode menggambar untuk melakukan penghapusan Pratinjau Gambar Asli Alfa Masker Emoji Acak Emoji bilah aplikasi akan terus diubah secara acak alih-alih menggunakan emoji yang dipilih Tidak dapat menggunakan pemilihan emoji acak saat emoji dinonaktifkan Tidak dapat memilih emoji saat memilih emoji acak diaktifkan Periksa pembaruan Memulihkan Cadangkan pengaturan aplikasi Anda ke file File rusak atau tidak dicadangkan Menghapus TV Lama Acak Buram OCR (Kenali Teks) Kenali teks dari gambar yang diberikan, didukung 120+ bahasa Gambar tidak memiliki teks, atau aplikasi tidak menemukannya Accuracy: %1$s Jenis Pengakuan Cepat Standar Terbaik Tidak ada data Agar Tesseract OCR berfungsi dengan baik, data pelatihan tambahan (%1$s) perlu diunduh ke perangkat Anda. \nApakah Anda ingin mengunduh data %2$s? Unduh Tidak ada koneksi, periksa dan coba lagi untuk mendownload model kereta Bahasa yang Diunduh Bahasa yang Tersedia Modus Segmentasi Kuas akan memulihkan latar belakang alih-alih menghapus Kotak Horisontal Kotak Vertikal Mode Jahitan Jumlah Baris Gunakan Sakelar Piksel Sakelar seperti piksel akan digunakan sebagai pengganti materi Google yang Anda buat Menggeser Bersebelahan Alihkan Ketuk Transparansi File yang ditimpa dengan nama %1$s di tujuan aslinya Kaca pembesar Mengaktifkan kaca pembesar di bagian atas jari dalam mode menggambar untuk aksesibilitas yang lebih baik Paksa nilai awal Memaksa widget exic untuk diperiksa terlebih dahulu Izinkan Banyak Bahasa Favorit Belum ada filter favorit yang ditambahkan B Spline Memanfaatkan fungsi polinomial bikubik yang ditentukan sedikit demi sedikit untuk menginterpolasi dan memperkirakan kurva atau permukaan dengan lancar, representasi bentuk yang fleksibel dan berkelanjutan Keburaman Tumpukan Asli Pergeseran Kemiringan Reguler Mengaburkan tepinya Jenis Isian Terbalik Jika diaktifkan, semua area yang tidak disamarkan akan difilter, bukan perilaku default Konfeti Confetti akan ditampilkan saat menyimpan, berbagi, dan tindakan utama lainnya Modus Aman Menyembunyikan konten saat keluar, dan layar juga tidak dapat ditangkap atau direkam Gambar akan dipotong tengah sesuai ukuran yang dimasukkan. Kanvas akan diperluas dengan warna latar belakang tertentu jika gambar lebih kecil dari dimensi yang dimasukkan. Vertikal Urutan gambar Neon Pena Rotasi otomatis Memungkinkan kotak batas diadopsi untuk orientasi gambar Gaya palet Tempat Nada Netral Bersemangat Ekspresif Pelangi Salad buah Kesetiaan Isi Gaya palet default, memungkinkan untuk menyesuaikan keempat warna, yang lain memungkinkan Anda mengatur hanya warna utama Kontainer Mengaktifkan gambar bayangan di belakang wadah Penggeser Beralih FAB Tombol Mengaktifkan gambar bayangan di belakang bilah geser Mengaktifkan gambar bayangan di belakang sakelar Mengaktifkan gambar bayangan di belakang tombol aksi mengambang Mengaktifkan gambar bayangan di belakang tombol default Pemeriksa pembaruan ini akan terhubung ke GitHub untuk memeriksa apakah ada pembaruan baru yang tersedia Perhatian Tepi Memudar Keduanya Balikkan Warna Mengganti warna tema menjadi warna negatif jika diaktifkan Keluar Jika Anda keluar dari pratinjau sekarang, Anda perlu menambahkan gambar lagi Format Gambar Membuat palet “Material You” dari gambar Warna Gelap Salin sebagai kode\" Jetpack Compose\" Menggunakan skema warna mode malam alih-alih varian cahaya Cincin Kabur Pengaburan silang Lingkaran Kabur Bintang Kabur Pergeseran Kemiringan Linier Tag untuk dihapus Alat APNG Konversikan gambar ke gambar APNG atau ekstrak bingkai dari gambar APNG yang diberikan Pilih gambar APNG untuk memulai APNG ke gambar Konversikan file APNG menjadi kumpulan gambar Gambar ke APNG Gerakan Buram Konversikan kumpulan gambar ke file APNG Ritsleting Buat file Zip dari file atau gambar tertentu Seret Lebar Pegangan Meriah Meledak Hujan Sudut Jenis Konfeti Ubah gambar GIF menjadi gambar animasi JXL Metode pengambilan sampel ulang yang menjaga interpolasi berkualitas tinggi dengan menerapkan fungsi Bessel (jinc) pada nilai piksel Menyalakan pembuatan pratinjau, ini dapat membantu menghindari kerusakan pada beberapa perangkat, ini juga menonaktifkan beberapa fungsi pengeditan dalam opsi pengeditan tunggal Pemilih berkas akan segera ditampilkan jika memungkinkan pada layar yang dipilih Urutkan Berdasarkan Nama (Terbalik) Konfigurasi Saluran Kemarin Urutkan Berdasarkan Tanggal (Terbalik) Coba Lagi Tampilkan Pengaturan Dalam Lanskap Jika ini dimatikan maka dalam mode lanskap pengaturan akan dibuka pada tombol di bilah aplikasi atas seperti biasa, bukan opsi yang terlihat permanen Alat JXL JXL ke JPEG Lakukan transkode lossless dari JXL ke JPEG Lakukan JXL ~ JPEG transkode tanpa kehilangan kualitas, atau ubah GIF/APNG menjadi animasi JXL Lakukan transkode lossless dari JPEG ke JXL JPEG ke JXL Pilih gambar JXL untuk memulai Pengaturan Layar Penuh Nyalakan dan halaman pengaturan akan selalu dibuka sebagai layar penuh, bukan lembaran laci yang dapat digeser Jenis Tombol Menyusun Pilih Beberapa Media Pilih Satu Media Pilih Menggunakan basis tampilan material yang Anda pilih, yang ini terlihat lebih hebat dari yang lain dan memiliki animasi yang bagus Menggunakan material Jetpack Compose yang Anda pilih, tampilannya tidak seindah berbasis tampilan Gaussian Blur 2D cepat Gaussian Blur 3D cepat Gaussian Blur 4D cepat Maksimal Ubah Ukuran Jangkar Hari ini Pemilih Tersemat Menggunakan pemilih gambar bawaan Image Toolbox, bukan pemilih gambar yang telah ditentukan sistem Tidak Ada Izin Permintaan Tempel Otomatis Mengizinkan aplikasi menempelkan data papan klip secara otomatis, sehingga akan muncul di layar utama dan Anda dapat memprosesnya Harmonisasi Warna Tingkat Harmonisasi Lanczos Bessel GIF ke JXL APNG ke JXL Ubah gambar APNG menjadi gambar animasi JXL JXL ke Gambar Ubah animasi JXL menjadi kumpulan gambar Gambar ke JXL Ubah kumpulan gambar menjadi animasi JXL Perilaku Lewati Pengambilan Berkas Buat Pratinjau Kompresi Rugi Menggunakan kompresi lossy untuk mengurangi ukuran berkas daripada lossless Jenis Kompresi Mengontrol kecepatan decoding gambar yang dihasilkan, ini akan membantu membuka gambar yang dihasilkan lebih cepat, nilai %1$s berarti decoding paling lambat, sedangkan %2$s - tercepat, pengaturan ini dapat meningkatkan ukuran gambar keluaran Mengurutkan Urutkan Berdasarkan Tanggal Urutkan Berdasarkan Nama Cupertino Lama Gambar ke SVG Pixel Fluent Menggunakan gaya sakelar Windows 11 berdasarkan sistem desain \"Fluent\" Rasio Warna Minimum Lebar Garis Default Mode Mesin Jaringan LSTM Konversi Tambah Folder Baru Menggunakan gaya sakelar iOS berdasarkan sistem desain Cupertino Menjiplak gambar ke gambar SVG Gunakan Palet Sampel Palet kuantisasi akan diambil sampelnya jika opsi ini diaktifkan Jalur Hilang Penggunaan alat ini untuk menelusuri gambar besar tanpa penurunan skala tidak disarankan, karena dapat menyebabkan kerusakan dan menambah waktu pemrosesan Gambar yang diperkecil Gambar akan diturunkan skalanya ke dimensi yang lebih rendah sebelum diproses, hal ini membantu alat bekerja lebih cepat dan aman Ambang Batas Garis Ambang Batas Kuadrat Koordinat Pembulatan Toleransi Skala Jalur Setel ulang properti Semua properti akan disetel ke nilai default, perhatikan bahwa tindakan ini tidak dapat dibatalkan Berdetail Warisan &amp; LSTM Konversi kumpulan gambar ke format tertentu Bit Per Sampel Kompresi Interpretasi Fotometrik Sampel Per Piksel Konfigurasi Planar Sub Sampling Y Cb Cr Posisi Y Cb Cr X Resolusi Resolusi Y Satuan Resolusi Strip Offset Baris Per Strip Hapus Jumlah Byte Format Pertukaran JPEG Panjang Format Pertukaran JPEG Fungsi Pemindahan Titik Putih Kromatisitas Primer Koefisien Y Cb Cr Referensi Hitam Putih Tanggal Waktu Deskripsi Gambar Membuat Model Perangkat lunak Artis Hak cipta Versi Exif Versi Flashpix Ruang Warna Gamma Dimensi Piksel X Dimensi Piksel Y Bit Terkompresi Per Piksel Catatan Pembuat Komentar Pengguna File Suara Terkait Tanggal Waktu Asli Tanggal Waktu Didigitalkan Waktu Pengimbangan Waktu Offset Asli Waktu Offset Didigitalkan Waktu Sub Detik Sub Detik Waktu Asli Waktu Sub Detik Didigitalkan Waktu paparan Nomor F Program Paparan Sensitivitas Spektral Sensitivitas Fotografi Oecf Tipe Sensitivitas Sensitivitas Keluaran Standar Indeks Eksposur yang Direkomendasikan Kecepatan ISO Lintang Kecepatan ISO yyy Garis Lintang Kecepatan ISO zzz Nilai Kecepatan Rana Nilai Bukaan Nilai Kecerahan Nilai Bias Eksposur Nilai Apertur Maks Jarak Subjek Mode Pengukuran Kilatan Bidang Subyek Panjang Fokus Energi Kilatan Respon Frekuensi Spasial Resolusi Bidang Fokus X Resolusi Bidang Fokus Y Unit Resolusi Bidang Fokus Lokasi Subjek Indeks Paparan Metode Penginderaan Sumber Berkas Pola CFA Dirender Khusus Mode Eksposur Keseimbangan Putih Rasio Zoom Digital Film Panjang Fokus Dalam 35mm Jenis Pengambilan Pemandangan Dapatkan Kontrol Kontras Kejenuhan Ketajaman Deskripsi Pengaturan Perangkat Rentang Jarak Subjek ID Unik Gambar Nama Pemilik Kamera Nomor Seri Tubuh Spesifikasi Lensa Pembuatan Lensa Model Lensa Nomor Seri Lensa ID Versi GPS Referensi Garis Lintang GPS Garis Lintang GPS Referensi Bujur GPS Bujur GPS Referensi Ketinggian GPS Ketinggian GPS Stempel Waktu GPS Satelit GPS Status GPS Mode Pengukuran GPS GPS DOP Referensi Kecepatan GPS Kecepatan GPS Referensi Jalur GPS Jalur GPS Referensi Arah Gambar GPS Arah Gambar GPS Data Peta GPS Referensi Garis Lintang Tujuan GPS Lintang Tujuan GPS Referensi Bujur Tujuan GPS Bujur Tujuan GPS Referensi Bantalan Tujuan GPS Bantalan Tujuan GPS Ref. Jarak Tujuan GPS Jarak Tujuan GPS Metode Pemrosesan GPS Informasi Area GPS Stempel Tanggal GPS Diferensial GPS Kesalahan Penentuan Posisi GPS H Indeks Interoperabilitas Versi DNG Ukuran Pangkas Default Pratinjau Gambar Mulai Pratinjau Panjang Gambar Bingkai Aspek Batas Bawah Sensor Perbatasan Kiri Sensor Perbatasan Kanan Sensor Batas Atas Sensor ISO Gambar Teks di jalur dengan font dan warna tertentu Ukuran Huruf Ukuran Tanda Air Ulangi Teks Teks saat ini akan diulang hingga jalur berakhir, bukan satu kali menggambar Ukuran Garis Dasbor Gunakan gambar yang dipilih untuk menggambarnya di sepanjang jalur yang diberikan Gambar ini akan digunakan sebagai entri berulang dari jalur yang digambar Menggambar garis luar segitiga dari titik awal hingga titik akhir Menggambar garis luar segitiga dari titik awal hingga titik akhir Segitiga yang Diuraikan Segi tiga Menggambar poligon dari titik awal ke titik akhir Poligon Poligon yang Diuraikan Menggambar garis poligon dari titik awal hingga titik akhir simpul Menggambar Poligon Beraturan Gambarlah poligon yang beraturan, bukan bentuk bebas Menggambar bintang dari titik awal ke titik akhir Bintang Bintang yang Diuraikan Menggambar garis luar bintang dari titik awal hingga titik akhir Rasio Radius Dalam Gambar Bintang Biasa Gambarlah bintang yang berbentuk biasa, bukan berbentuk bebas Antialias Mengaktifkan antialiasing untuk mencegah tepi tajam Buka Edit, Bukan Pratinjau Saat Anda memilih gambar untuk dibuka (pratinjau) di ImageToolbox, lembar pilihan edit akan dibuka alih-alih dipratinjau Pemindai Dokumen Pindai dokumen dan buat PDF atau pisahkan gambar darinya Klik untuk mulai memindai Mulai Memindai Simpan Sebagai Pdf Bagikan Sebagai Pdf Opsi di bawah ini adalah untuk menyimpan gambar, bukan PDF Menyamakan Histogram HSV Menyamakan Histogram Masukkan Persentase Izinkan masuk dengan Bidang Teks Mengaktifkan Bidang Teks di belakang pilihan prasetel, untuk memasukkannya dengan cepat Skala Ruang Warna Linier Menyamakan Pikselasi Histogram Ukuran Kotak X Ukuran Kotak Y Menyamakan Histogram Adaptif Menyamakan Histogram Adaptif LUV Menyamakan LAB Adaptif Histogram CLAHE LAB CLAHE CLAHE LUV Pangkas ke Konten Warna Bingkai Warna Untuk Diabaikan Templat Tidak ada filter template yang ditambahkan Buat Baru Kode QR yang dipindai bukan template filter yang valid Pindai kode QR File yang dipilih tidak memiliki data template filter Buat Templat Nama Templat Gambar ini akan digunakan untuk melihat pratinjau template filter ini Filter Templat Sebagai gambar kode QR Sebagai file Simpan sebagai file Simpan sebagai gambar kode QR Hapus Templat Anda akan menghapus filter template yang dipilih. Operasi ini tidak dapat dibatalkan Menambahkan templat filter dengan nama \"%1$s\" (%2$s) Pratinjau Filter QR &amp; Kode Batang Pindai kode QR dan dapatkan kontennya atau tempel string Anda untuk menghasilkan yang baru Konten Kode Pindai kode batang apa pun untuk mengganti konten di bidang, atau ketik sesuatu untuk menghasilkan kode batang baru dengan jenis yang dipilih Deskripsi QR Minimal Berikan izin kamera di pengaturan untuk memindai kode QR Berikan izin kamera dalam pengaturan untuk memindai Pemindai Dokumen Kubik B-Spline Hamming Hanning orang kulit hitam Selamat Kuadrik Gaussian Sphinx Bartlett Robidoux Robidoux Tajam Spline 16 Spline 36 Spline 64 kaisar Bartlett-Dia Kotak Bohman Lanzos 2 Lanzos 3 Lanzos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Interpolasi kubik memberikan penskalaan yang lebih halus dengan mempertimbangkan 16 piksel terdekat, sehingga memberikan hasil yang lebih baik daripada bilinear Memanfaatkan fungsi polinomial yang ditentukan sedikit demi sedikit untuk menginterpolasi dan memperkirakan kurva atau permukaan dengan lancar, representasi bentuk yang fleksibel dan berkelanjutan Fungsi jendela yang digunakan untuk mengurangi kebocoran spektral dengan meruncingkan tepi sinyal, berguna dalam pemrosesan sinyal Varian dari jendela Hann, biasa digunakan untuk mengurangi kebocoran spektral dalam aplikasi pemrosesan sinyal Fungsi jendela yang memberikan resolusi frekuensi yang baik dengan meminimalkan kebocoran spektral, sering digunakan dalam pemrosesan sinyal Fungsi jendela yang dirancang untuk memberikan resolusi frekuensi yang baik dengan mengurangi kebocoran spektral, sering digunakan dalam aplikasi pemrosesan sinyal Sebuah metode yang menggunakan fungsi kuadrat untuk interpolasi, memberikan hasil yang halus dan berkelanjutan Metode interpolasi yang menerapkan fungsi Gaussian, berguna untuk menghaluskan dan mengurangi noise pada gambar Metode pengambilan sampel ulang tingkat lanjut yang memberikan interpolasi berkualitas tinggi dengan artefak minimal Fungsi jendela segitiga yang digunakan dalam pemrosesan sinyal untuk mengurangi kebocoran spektral Metode interpolasi berkualitas tinggi yang dioptimalkan untuk mengubah ukuran gambar secara alami, menyeimbangkan ketajaman dan kehalusan Varian yang lebih tajam dari metode Robidoux, dioptimalkan untuk pengubahan ukuran gambar yang tajam Metode interpolasi berbasis spline yang memberikan hasil halus menggunakan filter 16 ketukan Metode interpolasi berbasis spline yang memberikan hasil halus menggunakan filter 36 ketukan Metode interpolasi berbasis spline yang memberikan hasil halus menggunakan filter 64 ketukan Metode interpolasi yang menggunakan jendela Kaiser, memberikan kontrol yang baik atas trade-off antara lebar lobus utama dan tingkat lobus samping Fungsi jendela hibrid yang menggabungkan jendela Bartlett dan Hann, digunakan untuk mengurangi kebocoran spektral dalam pemrosesan sinyal Metode pengambilan sampel ulang sederhana yang menggunakan rata-rata nilai piksel terdekat, yang sering kali menghasilkan tampilan kotak-kotak Fungsi jendela yang digunakan untuk mengurangi kebocoran spektral, memberikan resolusi frekuensi yang baik dalam aplikasi pemrosesan sinyal Metode pengambilan sampel ulang yang menggunakan filter Lanczos 2 lobus untuk interpolasi berkualitas tinggi dengan artefak minimal Metode pengambilan sampel ulang yang menggunakan filter Lanczos 3 lobus untuk interpolasi berkualitas tinggi dengan artefak minimal Metode pengambilan sampel ulang yang menggunakan filter Lanczos 4 lobus untuk interpolasi berkualitas tinggi dengan artefak minimal Varian filter Lanczos 2 yang menggunakan fungsi jinc, memberikan interpolasi berkualitas tinggi dengan artefak minimal Varian filter Lanczos 3 yang menggunakan fungsi jinc, memberikan interpolasi berkualitas tinggi dengan artefak minimal Varian filter Lanczos 4 yang menggunakan fungsi jinc, memberikan interpolasi berkualitas tinggi dengan artefak minimal Hanning EWA Varian Elliptical Weighted Average (EWA) dari filter Hanning untuk interpolasi dan pengambilan sampel ulang yang lancar Robidoux EWA Varian Elliptical Weighted Average (EWA) dari filter Robidoux untuk pengambilan sampel ulang berkualitas tinggi Blackman EVE Varian Elliptical Weighted Average (EWA) dari filter Blackman untuk meminimalkan artefak dering EWA kuadrik Varian Elliptical Weighted Average (EWA) dari filter Quadric untuk interpolasi yang mulus Robidoux Tajam EWA Varian Elliptical Weighted Average (EWA) dari filter Robidoux Sharp untuk hasil yang lebih tajam Lanczos 3 Jinc EWA Varian Elliptical Weighted Average (EWA) dari filter Lanczos 3 Jinc untuk pengambilan sampel ulang berkualitas tinggi dengan pengurangan aliasing Ginseng Filter pengambilan sampel ulang yang dirancang untuk pemrosesan gambar berkualitas tinggi dengan keseimbangan ketajaman dan kehalusan yang baik Ginseng EWA Varian Elliptical Weighted Average (EWA) dari filter Ginseng untuk meningkatkan kualitas gambar Lanczos Tajam EWA Varian Elliptical Weighted Average (EWA) dari filter Lanczos Sharp untuk mencapai hasil yang tajam dengan artefak minimal Lanczos 4 EWA Paling Tajam Varian Elliptical Weighted Average (EWA) dari filter Lanczos 4 Sharpest untuk pengambilan sampel ulang gambar yang sangat tajam Lanczos Lembut EWA Varian Elliptical Weighted Average (EWA) dari filter Lanczos Soft untuk pengambilan sampel ulang gambar yang lebih halus Haasn Lembut Filter pengambilan sampel ulang yang dirancang oleh Haasn untuk penskalaan gambar yang mulus dan bebas artefak Konversi Format Konversi kumpulan gambar dari satu format ke format lainnya Singkirkan Selamanya Penumpukan Gambar Tumpuk gambar di atas satu sama lain dengan mode campuran yang dipilih Tambahkan Gambar Jumlah sampah Cheh HSL Clahe HSV Menyamakan Histogram Adaptif HSL Menyamakan Histogram Adaptif HSV Modus Tepi Klip Membungkus Buta Warna Pilih mode untuk mengadaptasi warna tema untuk varian buta warna yang dipilih Kesulitan membedakan warna merah dan hijau Kesulitan membedakan warna hijau dan merah Kesulitan membedakan warna biru dan kuning Ketidakmampuan untuk melihat warna merah Ketidakmampuan untuk melihat warna hijau Ketidakmampuan untuk melihat warna biru Mengurangi sensitivitas terhadap semua warna Buta warna total, hanya melihat gradasi warna abu-abu Jangan gunakan skema Buta Warna Warna akan persis seperti yang ditetapkan dalam tema Sigmoidal Keterlambatan 2 Filter interpolasi Lagrange orde 2, cocok untuk penskalaan gambar berkualitas tinggi dengan transisi yang mulus Keterlambatan 3 Filter interpolasi Lagrange urutan 3, menawarkan akurasi lebih baik dan hasil penskalaan gambar lebih halus Lanzos 6 Filter pengambilan sampel ulang Lanczos dengan urutan 6 lebih tinggi, memberikan penskalaan gambar yang lebih tajam dan akurat Lanczos 6 Jinc Varian filter Lanczos 6 yang menggunakan fungsi Jinc untuk meningkatkan kualitas pengambilan sampel ulang gambar Kabur Kotak Linier Kabur Tenda Linear Kabur Kotak Gaussian Linier Kekaburan Tumpukan Linier Kabur Kotak Gaussian Linear Fast Gaussian Blur Berikutnya Buram Gaussian Cepat Linier Kekaburan Gaussian Linier Pilih satu filter untuk digunakan sebagai cat Ganti Filter Pilih filter di bawah untuk menggunakannya sebagai kuas pada gambar Anda Skema kompresi TIFF Poli Rendah Lukisan Pasir Pemisahan Gambar Pisahkan satu gambar berdasarkan baris atau kolom Sesuai Batas Gabungkan mode pengubahan ukuran pangkas dengan parameter ini untuk mencapai perilaku yang diinginkan (Pangkas/Sesuaikan dengan rasio aspek) Bahasa berhasil diimpor Cadangkan model OCR Impor Ekspor Posisi Tengah Kiri Atas Kanan atas Kiri Bawah Kanan Bawah Pusat Atas Kanan Tengah Tengah Bawah Kiri Tengah Gambar Sasaran Pemindahan Palet Minyak yang Ditingkatkan TV Lama Sederhana HDR Gotham Sketsa Sederhana Cahaya Lembut Poster Berwarna Tri Nada Warna ketiga Clahe Oklab Clara Olch Clahe Jzazbz Polka dot Dithering 2x2 Berkelompok Dithering 4x4 Berkelompok Dithering 8x8 Berkelompok Yililoma Dithering Tidak ada opsi favorit yang dipilih, tambahkan di halaman alat Tambahkan Favorit Komplementer Sejalan Triadik Terpisah Komplementer Tetradik Persegi Analog + Komplementer Alat Warna Mencampur, membuat nada, menghasilkan corak, dan banyak lagi Harmoni Warna Bayangan Warna Variasi warna Nada Nuansa Pencampuran Warna Info Warna Warna yang Dipilih Warna Untuk Dicampur Tidak dapat menggunakan monet saat warna dinamis diaktifkan 512x512 2D LUT Targetkan gambar LUT Seorang amatir Nona Etiket Keanggunan Lembut Varian Keanggunan Lembut Varian Transfer Palet LUT 3D Targetkan File LUT 3D (.cube / .CUBE) LUT Bypass Pemutih Cahaya lilin Jatuhkan Blues Amber yang tegang Warna Musim Gugur Stok Film 50 Malam Berkabut Memotret dgn kodak Dapatkan gambar LUT Netral Pertama, gunakan aplikasi edit foto favorit Anda untuk menerapkan filter pada LUT netral yang bisa Anda dapatkan di sini. Agar ini berfungsi dengan baik, setiap warna piksel tidak boleh bergantung pada piksel lainnya (misalnya, keburaman tidak akan berfungsi). Setelah siap, gunakan gambar LUT baru Anda sebagai masukan untuk filter LUT 512*512 Seni Pop Seluloida Kopi Hutan Emas kehijauan Retro Kuning Pratinjau Tautan Mengaktifkan pengambilan pratinjau tautan di tempat Anda dapat memperoleh teks (QRCode, OCR, dll) Tautan File ICO hanya dapat disimpan dengan ukuran maksimal 256 x 256 GIF ke WEBP Konversi gambar GIF menjadi gambar animasi WEBP Alat WEBP Konversikan gambar menjadi gambar animasi WEBP atau ekstrak bingkai dari animasi WEBP yang diberikan WEBP ke gambar Konversikan file WEBP menjadi kumpulan gambar Konversikan kumpulan gambar ke file WEBP Gambar ke WEBP Pilih gambar WEBP untuk memulai Tidak ada akses penuh ke file Izinkan semua akses file untuk melihat JXL, QOI dan gambar lain yang tidak dikenali sebagai gambar di Android. Tanpa izin, Image Toolbox tidak dapat menampilkan gambar-gambar itu Warna Gambar Default Mode Jalur Gambar Default Tambahkan Stempel Waktu Mengaktifkan penambahan Stempel Waktu ke nama file keluaran Stempel Waktu yang Diformat Aktifkan pemformatan Stempel Waktu dalam nama file keluaran, bukan mili dasar Aktifkan Stempel Waktu untuk memilih formatnya Satu Kali Simpan Lokasi Lihat dan Edit lokasi penyimpanan satu kali yang dapat Anda gunakan dengan menekan lama tombol simpan di sebagian besar semua opsi Baru-baru ini Digunakan saluran CI Kelompok Kotak Alat Gambar di Telegram 🎉 Bergabunglah dengan obrolan kami di mana Anda dapat mendiskusikan apa pun yang Anda inginkan dan juga melihat saluran CI tempat saya memposting beta dan pengumuman Dapatkan pemberitahuan tentang versi aplikasi baru, dan baca pengumuman Sesuaikan gambar dengan dimensi tertentu dan terapkan buram atau warna pada latar belakang Penataan Alat Kelompokkan alat berdasarkan jenisnya Mengelompokkan alat di layar utama berdasarkan jenisnya, bukan berdasarkan susunan daftar khusus Nilai Bawaan Visibilitas Bilah Sistem Tampilkan Bilah Sistem Dengan Gesek Mengaktifkan gesekan untuk menampilkan bilah sistem jika disembunyikan Mobil Sembunyikan Semua Tampilkan Semua Sembunyikan Bilah Navigasi Sembunyikan Bilah Status Pembangkitan Kebisingan Hasilkan suara yang berbeda seperti Perlin atau jenis lainnya Frekuensi Jenis Kebisingan Tipe Rotasi Tipe Fraktal Oktaf kekurangan Memperoleh Kekuatan Tertimbang Kekuatan Ping Pong Fungsi Jarak Jenis Pengembalian Naik opelet Kelengkungan Domain Penyelarasan Nama File Khusus Pilih lokasi dan nama file yang akan digunakan untuk menyimpan gambar saat ini Disimpan ke folder dengan nama khusus Pembuat Kolase Buat kolase hingga 20 gambar Jenis Kolase Tahan gambar untuk menukar, memindahkan dan memperbesar untuk menyesuaikan posisi Nonaktifkan rotasi Mencegah memutar gambar dengan gerakan dua jari Aktifkan gertakan ke batas Setelah dipindahkan atau diperbesar, gambar akan diambil untuk memenuhi tepi bingkai Histogram Histogram gambar RGB atau Kecerahan untuk membantu Anda melakukan penyesuaian Gambar ini akan digunakan untuk menghasilkan histogram RGB dan Brightness Opsi Tesseract Terapkan beberapa variabel masukan untuk mesin tesseract Opsi Kustom Opsi harus dimasukkan mengikuti pola ini: \"--{option_name} {value}\" Pangkas Otomatis Sudut Bebas Pangkas gambar berdasarkan poligon, ini juga mengoreksi perspektif Paksaan Menunjuk Ke Batas Gambar Titik tidak akan dibatasi oleh batas gambar, hal ini berguna untuk koreksi perspektif yang lebih tepat Masker Isi sadar konten di bawah jalur yang digambar Tempat Sembuh Gunakan Kernel Lingkaran Pembukaan Penutupan Gradien Morfologi Topi Atas Topi Hitam Kurva Nada Atur Ulang Kurva Kurva akan dikembalikan ke nilai default Gaya Garis Ukuran Celah Putus-putus Titik putus-putus Dicap Zigzag Menarik garis putus-putus di sepanjang jalur yang ditarik dengan ukuran celah yang ditentukan Menggambar titik dan garis putus-putus di sepanjang jalur tertentu Hanya garis lurus default Menggambar bentuk yang dipilih di sepanjang jalur dengan jarak tertentu Menggambar zigzag bergelombang di sepanjang jalan Rasio zigzag Buat Pintasan Pilih alat untuk disematkan Alat akan ditambahkan ke layar beranda peluncur Anda sebagai pintasan, gunakan alat tersebut dengan menggabungkan pengaturan \"Lewati pengambilan file\" untuk mencapai perilaku yang diperlukan Jangan menumpuk bingkai Memungkinkan pembuangan bingkai sebelumnya, sehingga bingkai tersebut tidak akan saling bertumpuk memudar silang Bingkai akan saling memudar satu sama lain Bingkai crossfade dihitung Ambang Batas Satu Ambang Batas Dua Cerdik Cermin 101 Keburaman Zoom yang Ditingkatkan Laplacian Sederhana Sobel Sederhana Kotak Pembantu Menampilkan kisi pendukung di atas area gambar untuk membantu manipulasi yang tepat Warna Kotak Lebar Sel Tinggi Sel Penyeleksi Ringkas Beberapa kontrol pemilihan akan menggunakan tata letak yang ringkas untuk menghemat ruang Berikan izin kamera dalam pengaturan untuk mengambil gambar Tata Letak Judul Layar Utama Faktor Tingkat Konstan (CRF) Nilai %1$s berarti kompresi yang lambat sehingga menghasilkan ukuran file yang relatif kecil. %2$s berarti kompresi lebih cepat sehingga menghasilkan file besar. Perpustakaan Luth Download kumpulan LUT yang dapat Anda terapkan setelah mendownload Perbarui koleksi LUT (hanya yang baru yang akan dimasukkan dalam antrean), yang dapat Anda terapkan setelah mengunduh Ubah pratinjau gambar default untuk filter Pratinjau Gambar Bersembunyi Menunjukkan Tipe Penggeser Menyukai Bahan 2 Slider yang tampak mewah. Ini adalah opsi default Penggeser Material 2 Penggeser Materi Anda Menerapkan Tombol Dialog Tengah Tombol dialog akan diposisikan di tengah, bukan di kiri jika memungkinkan Lisensi Sumber Terbuka Lihat lisensi perpustakaan sumber terbuka yang digunakan dalam aplikasi ini Daerah Pengambilan sampel ulang menggunakan relasi area piksel. Ini mungkin merupakan metode yang lebih disukai untuk penipisan gambar, karena memberikan hasil bebas moire. Namun saat gambar diperbesar, serupa dengan metode \"Terdekat\". Aktifkan Pemetaan Nada Memasuki % Tidak dapat mengakses situs, coba gunakan VPN atau periksa apakah urlnya benar Lapisan Markup Mode lapisan dengan kemampuan untuk secara bebas menempatkan gambar, teks, dan lainnya Sunting lapisan Lapisan pada gambar Gunakan gambar sebagai latar belakang dan tambahkan lapisan berbeda di atasnya Lapisan di latar belakang Sama seperti opsi pertama tetapi dengan warna, bukan gambar Beta Sisi Pengaturan Cepat Tambahkan strip mengambang di sisi yang dipilih saat mengedit gambar, yang akan membuka pengaturan cepat saat diklik Hapus pilihan Grup pengaturan \"%1$s\" akan diciutkan secara default Grup pengaturan \"%1$s\" akan diperluas secara default Alat Base64 Dekode string Base64 menjadi gambar, atau enkode gambar ke format Base64 Basis64 Nilai yang diberikan bukan string Base64 yang valid Tidak dapat menyalin string Base64 yang kosong atau tidak valid Tempel Base64 Salin Base64 Muat gambar untuk menyalin atau menyimpan string Base64. Jika Anda memiliki stringnya sendiri, Anda dapat menempelkannya di atas untuk mendapatkan gambar Simpan Base64 Bagikan Base64 Pilihan Tindakan Impor Base64 Tindakan Base64 Tambahkan Garis Besar Tambahkan garis luar di sekitar teks dengan warna dan lebar tertentu Warna Garis Besar Ukuran Garis Besar Rotasi Checksum sebagai Nama File Gambar keluaran akan memiliki nama yang sesuai dengan checksum datanya Perangkat Lunak Gratis (Mitra) Perangkat lunak yang lebih berguna di saluran mitra aplikasi Android Algoritma Alat Checksum Bandingkan checksum, hitung hash, atau buat string hex dari file menggunakan algoritma hashing yang berbeda Menghitung Teks Hash Jumlah pemeriksaan Pilih file untuk menghitung checksumnya berdasarkan algoritma yang dipilih Masukkan teks untuk menghitung checksumnya berdasarkan algoritma yang dipilih Sumber Checksum Checksum Untuk Membandingkan Cocok! Perbedaan Checksumnya sama, bisa aman Checksum tidak sama, file bisa jadi tidak aman! Gradien Jala Lihatlah koleksi online Mesh Gradients Hanya font TTF dan OTF yang dapat diimpor Impor font (TTF/OTF) Ekspor font Font yang diimpor Terjadi kesalahan saat mencoba menyimpan, coba ubah folder keluaran Nama file tidak disetel Tidak ada Halaman Khusus Pemilihan Halaman Konfirmasi Keluar Alat Jika Anda memiliki perubahan yang belum disimpan saat menggunakan alat tertentu dan mencoba menutupnya, dialog konfirmasi akan ditampilkan Sunting EXIF Ubah metadata gambar tunggal tanpa kompresi ulang Ketuk untuk mengedit tag yang tersedia Ganti Stiker Lebar Pas Cocok Tinggi Bandingkan Batch Pilih file/file untuk menghitung checksumnya berdasarkan algoritma yang dipilih Pilih File Pilih Direktori Skala Panjang Kepala Perangko Stempel waktu Pola Format Lapisan Pemotongan Gambar Potong bagian gambar dan gabungkan bagian kiri (bisa terbalik) dengan garis vertikal atau horizontal Garis Pivot Vertikal Garis Pivot Horisontal Seleksi Terbalik Bagian yang dipotong secara vertikal akan dibiarkan, alih-alih menggabungkan bagian di sekitar area yang dipotong Bagian yang dipotong secara horizontal akan dibiarkan, bukannya menggabungkan bagian di sekitar area yang dipotong Koleksi Gradien Mesh Buat gradien mesh dengan jumlah simpul dan resolusi khusus Hamparan Gradien Jala Buat gradien mesh di bagian atas gambar yang diberikan Kustomisasi Poin Ukuran Kotak Resolusi X Resolusi Y Resolusi Piksel Demi Piksel Warna Sorotan Jenis Perbandingan Piksel Pindai kode batang Rasio Tinggi Badan Jenis Kode Batang Menerapkan Hitam dan Putih Gambar Barcode akan sepenuhnya hitam putih dan tidak diwarnai berdasarkan tema aplikasi Pindai Barcode apa pun (QR, EAN, AZTEC,…) dan dapatkan kontennya atau tempel teks Anda untuk menghasilkan yang baru Tidak Ditemukan Kode Batang Barcode yang Dihasilkan Akan Ada Di Sini Sampul Audio Ekstrak gambar sampul album dari file audio, format paling umum didukung Pilih audio untuk memulai Pilih Audio Tidak Ada Sampul yang Ditemukan Kirim Log Klik untuk membagikan file log aplikasi, ini dapat membantu saya menemukan masalah dan memperbaiki masalah Ups… Ada yang tidak beres Anda dapat menghubungi saya menggunakan opsi di bawah dan saya akan mencoba mencari solusinya.\n(Jangan lupa melampirkan log) Tulis Ke File Ekstrak teks dari kumpulan gambar dan simpan dalam satu file teks Tulis Ke Metadata Ekstrak teks dari setiap gambar dan letakkan di info EXIF ​​​​dari foto relatif Modus Tak Terlihat Gunakan steganografi untuk membuat tanda air yang tidak terlihat oleh mata di dalam byte gambar Anda Gunakan LSB Metode steganografi LSB (Less Significant Bit) akan digunakan, FD (Frequency Domain) sebaliknya Hapus Otomatis Mata Merah Kata sandi Membuka kunci PDF dilindungi Operasi hampir selesai. Membatalkan sekarang memerlukan memulai ulang Tanggal Dimodifikasi Tanggal Dimodifikasi (Terbalik) Ukuran Ukuran (Terbalik) Tipe MIME Jenis MIME (Terbalik) Perpanjangan Ekstensi (Terbalik) Tanggal Ditambahkan Tanggal Ditambahkan (Terbalik) Kiri ke Kanan Kanan ke Kiri Atas ke Bawah Bawah ke Atas Kaca Cair Peralihan berdasarkan IOS 26 yang baru diumumkan dan sistem desain kaca cairnya Pilih gambar atau tempel/impor data Base64 di bawah Ketik tautan gambar untuk memulai Tempel tautan Kaledoskop Sudut sekunder Sisi Campuran Saluran Biru hijau Merah biru Hijau merah Menjadi merah Menjadi hijau Menjadi biru Sian ungu Kuning Warna Halftone Kontur Tingkat Mengimbangi Voronoi Mengkristal Membentuk Menggeliat Keserampangan bintik Membaur Anjing Jari-jari kedua Menyamakan Binar Berputar dan Jepit Menunjuk Warna perbatasan Koordinat Kutub Searah ke kutub Kutub untuk meluruskan Balikkan dalam lingkaran Kurangi Kebisingan Solarisasi Sederhana Menenun X Kesenjangan kesenjangan Y X Lebar Y Lebar Berputar Stempel Karet Mengolesi Kepadatan Mencampur Distorsi Lensa Bola Indeks refraksi Busur Sudut penyebaran Berkilau sinar ASCII Gradien Maria Musim gugur Tulang Jet Musim dingin Laut Musim panas Musim semi Varian Keren HSV Berwarna merah muda Panas Kata magma Neraka Plasma Viridis Warga negara Senja Senja Bergeser Perspektif Otomatis meja tulis Izinkan pemangkasan Pangkas atau Perspektif Mutlak Turbo Hijau Tua Koreksi Lensa File profil lensa target dalam format JSON Unduh profil lensa siap pakai Bagian persen Ekspor sebagai JSON Salin string dengan data palet sebagai representasi json Ukiran Jahitan Layar Beranda Layar Kunci Bawaan Ekspor Wallpaper Menyegarkan Dapatkan wallpaper Beranda, Kunci, dan Bawaan terkini Izinkan akses ke semua file, ini diperlukan untuk mengambil wallpaper Izin mengelola penyimpanan eksternal saja tidak cukup, Anda perlu mengizinkan akses ke gambar Anda, pastikan untuk memilih \"Izinkan semua\" Tambahkan Preset Ke Nama File Menambahkan akhiran dengan preset yang dipilih ke nama file gambar Tambahkan Mode Skala Gambar Ke Nama File Menambahkan akhiran dengan mode skala gambar yang dipilih ke nama file gambar Seni Ascii Ubah gambar menjadi teks ascii yang akan terlihat seperti gambar Param Menerapkan filter negatif pada gambar untuk hasil yang lebih baik dalam beberapa kasus Memproses tangkapan layar Tangkapan layar tidak diambil, coba lagi Menyimpan dilewati %1$s file dilewati Izinkan Lewati Jika Lebih Besar Beberapa alat akan diizinkan untuk melewatkan penyimpanan gambar jika ukuran file yang dihasilkan lebih besar dari aslinya Acara Kalender Kontak E-mail Lokasi Telepon Teks SMS URL Wi-Fi Jaringan terbuka T/A SSID Telepon Pesan Alamat Subjek Tubuh Nama Organisasi Judul Telepon email URL Alamat Ringkasan Keterangan Lokasi Penyelenggara Tanggal mulai Tanggal akhir Status Lintang Garis bujur Buat Kode Batang Sunting Kode Batang Konfigurasi Wi-Fi Keamanan Pilih kontak Berikan izin pada kontak di pengaturan untuk mengisi otomatis menggunakan kontak yang dipilih Informasi kontak Nama depan Nama tengah Nama belakang Pengucapan Tambahkan telepon Tambahkan email Tambahkan alamat Situs web Tambahkan situs web Nama yang diformat Gambar ini akan digunakan untuk ditempatkan di atas barcode Kustomisasi kode Gambar ini akan digunakan sebagai logo di tengah kode QR logo Bantalan logo Ukuran logo Sudut logo Mata keempat Menambahkan simetri mata ke kode qr dengan menambahkan mata keempat di sudut ujung bawah Bentuk piksel Bentuk bingkai Bentuk bola Tingkat koreksi kesalahan Warna gelap Warna terang OS hiper Xiaomi HyperOS menyukai gaya Pola topeng Kode ini mungkin tidak dapat dipindai, ubah parameter tampilan agar dapat dibaca oleh semua perangkat Tidak dapat dipindai Alat akan terlihat seperti peluncur aplikasi layar beranda agar lebih ringkas Mode Peluncur Mengisi area dengan kuas dan gaya yang dipilih Isi Banjir Semprot Menggambar jalur bergaya grafiti Partikel Persegi Partikel semprotan akan berbentuk persegi, bukan lingkaran Alat Palet Hasilkan palet dasar/bahan Anda dari gambar, atau impor/ekspor ke berbagai format palet berbeda Sunting Palet Ekspor/impor palet dalam berbagai format Nama warna Nama palet Format Palet Ekspor palet yang dihasilkan ke format berbeda Menambahkan warna baru ke palet saat ini Format %1$s tidak mendukung pemberian nama palet Karena kebijakan Play Store, fitur ini tidak dapat disertakan dalam versi saat ini. Untuk mengakses fungsi ini, silakan unduh ImageToolbox dari sumber alternatif. Anda dapat menemukan build yang tersedia di GitHub di bawah. Buka halaman Github File asli akan diganti dengan yang baru alih-alih disimpan di folder yang dipilih Teks tanda air tersembunyi terdeteksi Terdeteksi gambar tanda air tersembunyi Gambar ini disembunyikan Lukisan Generatif Memungkinkan Anda menghapus objek dalam gambar menggunakan model AI, tanpa bergantung pada OpenCV. Untuk menggunakan fitur ini, aplikasi akan mengunduh model yang diperlukan (~200 MB) dari GitHub Memungkinkan Anda menghapus objek dalam gambar menggunakan model AI, tanpa bergantung pada OpenCV. Ini mungkin merupakan operasi yang berjalan lama Analisis Tingkat Kesalahan Gradien Pencahayaan Jarak Rata-rata Salin Deteksi Pemindahan Mempertahankan Koefisien Data papan klip terlalu besar Data terlalu besar untuk disalin Pikselisasi Tenun Sederhana Pikselisasi Terhuyung Pikselisasi Silang Pikselisasi Makro Mikro Pikselisasi Orbital Pikselisasi Pusaran Pikselisasi Jaringan Pulsa Pikselisasi Inti Pikselisasi Tenunan Radial Tidak dapat membuka uri \"%1$s\" Mode Hujan Salju Diaktifkan Bingkai Perbatasan Varian Kesalahan Pergeseran Saluran Offset Maks VHS Blokir Kesalahan Ukuran Blok kelengkungan CRT Lengkungan Kroma Pencairan Piksel Penurunan Maks Alat AI Berbagai alat untuk memproses gambar melalui model AI seperti penghilangan atau penghilangan artefak Kompresi, garis bergerigi Kartun, kompresi siaran Kompresi umum, kebisingan umum Suara kartun tak berwarna Cepat, kompresi umum, kebisingan umum, animasi/komik/anime Pemindaian buku Koreksi eksposur Terbaik dalam kompresi umum, gambar berwarna Terbaik dalam kompresi umum, gambar skala abu-abu Kompresi umum, gambar skala abu-abu, lebih kuat Kebisingan umum, gambar berwarna Kebisingan umum, gambar berwarna, detail lebih baik Kebisingan umum, gambar skala abu-abu Kebisingan umum, gambar skala abu-abu, lebih kuat Kebisingan umum, gambar skala abu-abu, paling kuat Kompresi umum Kompresi umum Teksturisasi, kompresi h264 Kompresi VHS Kompresi non-standar (cinepak, msvideo1, roq) Kompresi bink, lebih baik pada geometri Kompresi bink, lebih kuat Kompresi bink, lembut, mempertahankan detail Menghilangkan efek tangga, menghaluskan Seni/gambar yang dipindai, kompresi ringan, moire Garis warna Lambat, menghilangkan halftone Pewarna umum untuk gambar skala abu-abu/bw, untuk hasil lebih baik gunakan DDColor Penghapusan tepi Menghilangkan penajaman yang berlebihan Lambat, ragu-ragu Anti-aliasing, artefak umum, CGI Pemrosesan pemindaian KDM003 Model peningkatan gambar yang ringan Penghapusan artefak kompresi Penghapusan artefak kompresi Pelepasan perban dengan hasil yang halus Pemrosesan pola halftone Penghapusan pola gentar V3 Penghapusan artefak JPEG V2 Peningkatan tekstur H.264 Penajaman dan peningkatan VHS Penggabungan Ukuran Potongan Ukuran Tumpang Tindih Gambar di atas %1$s px akan diiris dan diproses dalam beberapa bagian, campuran ini tumpang tindih untuk mencegah jahitan terlihat. Ukuran yang besar dapat menyebabkan ketidakstabilan pada perangkat kelas bawah Pilih satu untuk memulai Apakah Anda ingin menghapus model %1$s? Anda perlu mengunduhnya lagi Mengonfirmasi Model Model yang Diunduh Model yang Tersedia Mempersiapkan Model aktif Gagal membuka sesi Hanya model .onnx/.ort yang dapat diimpor Model impor Impor model onnx khusus untuk digunakan lebih lanjut, hanya model onnx/ort yang diterima, mendukung hampir semua varian seperti esrgan Model yang Diimpor Kebisingan umum, gambar berwarna Kebisingan umum, gambar berwarna, lebih kuat Kebisingan umum, gambar berwarna, paling kuat Mengurangi artefak yang ragu-ragu dan garis warna, meningkatkan gradien halus dan area warna datar. Meningkatkan kecerahan dan kontras gambar dengan sorotan seimbang sekaligus mempertahankan warna alami. Mencerahkan gambar gelap sekaligus menjaga detail dan menghindari pencahayaan berlebih. Menghilangkan toning warna yang berlebihan dan mengembalikan keseimbangan warna yang lebih netral dan alami. Menerapkan noise toning berbasis Poisson dengan penekanan pada pelestarian detail dan tekstur halus. Menerapkan toning noise Poisson yang lembut untuk hasil visual yang lebih halus dan tidak terlalu agresif. Nada noise seragam berfokus pada pelestarian detail dan kejernihan gambar. Nada kebisingan seragam yang lembut untuk tekstur halus dan penampilan halus. Memperbaiki area yang rusak atau tidak rata dengan mengecat ulang artefak dan meningkatkan konsistensi gambar. Model pelepasan pita ringan yang menghilangkan pita warna dengan biaya kinerja minimal. Mengoptimalkan gambar dengan artefak kompresi sangat tinggi (kualitas 0-20%) untuk meningkatkan kejernihan. Meningkatkan gambar dengan artefak kompresi tinggi (kualitas 20-40%), memulihkan detail dan mengurangi noise. Meningkatkan gambar dengan kompresi sedang (kualitas 40-60%), menyeimbangkan ketajaman dan kehalusan. Memperbaiki gambar dengan kompresi ringan (kualitas 60-80%) untuk menyempurnakan detail dan tekstur halus. Sedikit menyempurnakan gambar yang nyaris lossless (kualitas 80-100%) dengan tetap menjaga tampilan dan detail alami. Pewarnaan sederhana dan cepat, kartun, tidak ideal Sedikit mengurangi keburaman gambar, meningkatkan ketajaman tanpa menimbulkan artefak. Operasi yang berjalan lama Memproses gambar Pengolahan Menghapus artefak kompresi JPEG yang berat pada gambar berkualitas sangat rendah (0-20%). Mengurangi artefak JPEG yang kuat pada gambar yang sangat terkompresi (20-40%). Membersihkan artefak JPEG moderat sambil mempertahankan detail gambar (40-60%). Memperbaiki artefak JPEG ringan dalam gambar berkualitas cukup tinggi (60-80%). Secara halus mengurangi artefak JPEG kecil pada gambar yang hampir tidak ada kerugian (80-100%). Meningkatkan detail dan tekstur halus, meningkatkan ketajaman yang dirasakan tanpa artefak berat. Pemrosesan selesai Pemrosesan gagal Meningkatkan tekstur dan detail kulit sekaligus menjaga tampilan alami, dioptimalkan untuk kecepatan. Menghapus artefak kompresi JPEG dan mengembalikan kualitas gambar untuk foto terkompresi. Mengurangi noise ISO pada foto yang diambil dalam kondisi cahaya redup, menjaga detailnya. Memperbaiki sorotan yang terlalu terang atau “jumbo” dan mengembalikan keseimbangan warna yang lebih baik. Model pewarnaan yang ringan dan cepat yang menambahkan warna alami pada gambar skala abu-abu. DEJPEG Tolak kebisingan Mewarnai Artefak Meningkatkan anime Pemindaian Kelas atas Peningkatan X4 untuk gambar umum; model kecil yang menggunakan lebih sedikit GPU dan waktu, dengan deblur dan denoise sedang. Peningkatan X2 untuk gambar umum, menjaga tekstur dan detail alami. Peningkatan X4 untuk gambar umum dengan tekstur yang ditingkatkan dan hasil yang realistis. Peningkatan X4 dioptimalkan untuk gambar anime; 6 blok RRDB untuk garis dan detail yang lebih tajam. Peningkatan X4 dengan kerugian MSE, memberikan hasil yang lebih halus dan mengurangi artefak untuk gambar umum. X4 Upscaler dioptimalkan untuk gambar anime; Varian 4B32F dengan detail lebih tajam dan garis halus. Model X4 UltraSharp V2 untuk gambar umum; menekankan ketajaman dan kejelasan. X4 UltraSharp V2 Lite; lebih cepat dan lebih kecil, menjaga detail sambil menggunakan lebih sedikit memori GPU. Model ringan untuk penghapusan latar belakang dengan cepat. Performa dan akurasi seimbang. Bekerja dengan potret, objek, dan pemandangan. Direkomendasikan untuk sebagian besar kasus penggunaan. Hapus BG Ketebalan Perbatasan Horisontal Ketebalan Batas Vertikal %1$s warna Model saat ini tidak mendukung chunking, gambar akan diproses dalam dimensi aslinya, hal ini dapat menyebabkan konsumsi memori yang tinggi dan masalah pada perangkat kelas bawah Chunking dinonaktifkan, gambar akan diproses dalam dimensi aslinya, hal ini dapat menyebabkan konsumsi memori yang tinggi dan masalah pada perangkat kelas bawah tetapi dapat memberikan hasil inferensi yang lebih baik Potongan Model segmentasi gambar dengan akurasi tinggi untuk penghapusan latar belakang Versi ringan U2Net untuk menghapus latar belakang lebih cepat dengan penggunaan memori lebih kecil. Model DDColor penuh menghadirkan pewarnaan berkualitas tinggi untuk gambar umum dengan artefak minimal. Pilihan terbaik dari semua model pewarnaan. Kumpulan data artistik pribadi dan terlatih DDColor; menghasilkan hasil pewarnaan yang beragam dan artistik dengan lebih sedikit artefak warna yang tidak realistis. Model BiRefNet ringan berdasarkan Swin Transformer untuk menghilangkan latar belakang secara akurat. Penghapusan latar belakang berkualitas tinggi dengan tepi tajam dan pelestarian detail yang sangat baik, terutama pada objek kompleks dan latar belakang rumit. Model penghapusan latar belakang yang menghasilkan topeng akurat dengan tepi halus, cocok untuk objek umum dan pelestarian detail moderat. Model sudah diunduh Model berhasil diimpor Jenis Kata kunci Sangat cepat Normal Lambat Sangat Lambat Hitung Persen Nilai minimumnya adalah %1$s Distorsi gambar dengan menggambar dengan jari Melengkung Kekerasan Mode Warp Bergerak Tumbuh Menyusut Putar CW Putar CCW Kekuatan Pudar Penurunan Teratas Penurunan Bawah Mulai Jatuhkan Akhir Penurunan Mengunduh Bentuk Halus Gunakan superelips daripada persegi panjang bulat standar untuk mendapatkan bentuk yang lebih halus dan alami Tipe Bentuk Memotong Bulat Mulus Tepi tajam tanpa pembulatan Sudut membulat klasik Tipe Bentuk Ukuran Sudut tupai Elemen UI bulat yang elegan Format Nama File Teks khusus ditempatkan di awal nama file, cocok untuk nama proyek, merek, atau tag pribadi. Menggunakan nama file asli tanpa ekstensi, membantu Anda menjaga identifikasi sumber tetap utuh. Lebar gambar dalam piksel, berguna untuk melacak perubahan resolusi atau menskalakan hasil. Tinggi gambar dalam piksel, berguna saat bekerja dengan rasio aspek atau ekspor. Menghasilkan digit acak untuk menjamin nama file unik; tambahkan lebih banyak digit untuk keamanan ekstra terhadap duplikat. Penghitung penambahan otomatis untuk ekspor batch, ideal saat menyimpan banyak gambar dalam satu sesi. Menyisipkan nama preset yang diterapkan ke dalam nama file sehingga Anda dapat dengan mudah mengingat bagaimana gambar diproses. Menampilkan mode penskalaan gambar yang digunakan selama pemrosesan, membantu membedakan gambar yang diubah ukurannya, dipotong, atau dipasang. Teks khusus ditempatkan di akhir nama file, berguna untuk pembuatan versi seperti _v2, _edited, atau _final. Ekstensi file (png, jpg, webp, dll.), secara otomatis cocok dengan format penyimpanan sebenarnya. Stempel waktu yang dapat disesuaikan yang memungkinkan Anda menentukan format Anda sendiri berdasarkan spesifikasi java untuk penyortiran yang sempurna. Tipe Lemparan Android Asli Gaya iOS Kurva Halus Berhenti Cepat Goyang Ringan Tajam Sangat Halus adaptif Sadar Aksesibilitas Gerakan Berkurang Fisika gulir Android asli Pengguliran yang seimbang dan mulus untuk penggunaan umum Perilaku gulir mirip iOS dengan gesekan lebih tinggi Kurva spline yang unik untuk nuansa gulir yang berbeda Pengguliran yang tepat dengan penghentian cepat Gulir goyang yang menyenangkan dan responsif Gulungan yang panjang dan meluncur untuk penelusuran konten Pengguliran cepat dan responsif untuk UI interaktif Pengguliran mulus premium dengan momentum yang diperpanjang Menyesuaikan fisika berdasarkan kecepatan lemparan Menghargai pengaturan aksesibilitas sistem Gerakan minimal untuk kebutuhan aksesibilitas Jalur Utama Menambahkan garis tebal setiap baris kelima Isi Warna Alat Tersembunyi Alat Tersembunyi Untuk Dibagikan Perpustakaan Warna Jelajahi banyak koleksi warna Mempertajam dan menghilangkan keburaman pada gambar sambil mempertahankan detail alami, ideal untuk memperbaiki foto yang tidak fokus. Secara cerdas mengembalikan gambar yang telah diubah ukurannya sebelumnya, memulihkan detail dan tekstur yang hilang. Dioptimalkan untuk konten aksi langsung, mengurangi artefak kompresi, dan menyempurnakan detail halus dalam bingkai film/acara TV. Mengonversi rekaman berkualitas VHS ke HD, menghilangkan noise rekaman dan meningkatkan resolusi sekaligus mempertahankan nuansa vintage. Khusus untuk gambar dan tangkapan layar yang banyak teks, mempertajam karakter dan meningkatkan keterbacaan. Peningkatan tingkat lanjut dilatih pada beragam kumpulan data, sangat baik untuk penyempurnaan foto tujuan umum. Dioptimalkan untuk foto terkompresi web, menghilangkan artefak JPEG dan mengembalikan tampilan alami. Versi yang ditingkatkan untuk foto web dengan pelestarian tekstur dan pengurangan artefak yang lebih baik. Peningkatan 2x dengan teknologi Dual Aggregation Transformer, menjaga ketajaman dan detail alami. Peningkatan 3x menggunakan arsitektur transformator canggih, ideal untuk kebutuhan pembesaran sedang. 4x peningkatan kualitas tinggi dengan jaringan transformator canggih, mempertahankan detail halus pada skala yang lebih besar. Menghilangkan keburaman/noise dan guncangan pada foto. Tujuan umum tetapi terbaik untuk foto. Mengembalikan gambar berkualitas rendah menggunakan transformator Swin2SR, dioptimalkan untuk degradasi BSRGAN. Sangat bagus untuk memperbaiki artefak kompresi berat dan meningkatkan detail pada skala 4x. Peningkatan 4x dengan transformator SwinIR yang dilatih tentang degradasi BSRGAN. Menggunakan GAN untuk tekstur yang lebih tajam dan detail yang lebih alami dalam foto dan pemandangan yang kompleks. Jalur Gabungkan PDF Gabungkan beberapa file PDF menjadi satu dokumen Urutan File hal. Pisahkan PDF Ekstrak halaman tertentu dari dokumen PDF Putar PDF Perbaiki orientasi halaman secara permanen Halaman Susun ulang PDF Seret dan lepas halaman untuk menyusun ulang Tahan &amp; Seret halaman Nomor Halaman Tambahkan penomoran ke dokumen Anda secara otomatis Format Label PDF ke Teks (OCR) Ekstrak teks biasa dari dokumen PDF Anda Hamparkan teks khusus untuk pencitraan merek atau keamanan Tanda tangan Tambahkan tanda tangan elektronik Anda ke dokumen apa pun Ini akan digunakan sebagai tanda tangan Buka kunci PDF Hapus kata sandi dari file Anda yang dilindungi Lindungi PDF Amankan dokumen Anda dengan enkripsi yang kuat Kesuksesan PDF tidak terkunci, Anda dapat menyimpan atau membagikannya Perbaiki PDF Mencoba memperbaiki dokumen yang rusak atau tidak dapat dibaca Skala abu-abu Ubah semua gambar yang disematkan pada dokumen menjadi skala abu-abu Kompres PDF Optimalkan ukuran file dokumen Anda agar lebih mudah dibagikan ImageToolbox membangun kembali tabel referensi silang internal dan membuat ulang struktur file dari awal. Ini dapat memulihkan akses ke banyak file yang \\"tidak dapat dibuka\\" Alat ini mengubah semua gambar dokumen menjadi skala abu-abu. Terbaik untuk mencetak dan mengurangi ukuran file Metadata Edit properti dokumen untuk privasi yang lebih baik Tag Produsen Pengarang Kata kunci Pencipta Privasi Sangat Bersih Hapus semua metadata yang tersedia untuk dokumen ini Halaman OCR yang dalam Ekstrak teks dari dokumen dan simpan dalam satu file teks menggunakan mesin Tesseract Tidak dapat menghapus semua halaman Hapus halaman PDF Hapus halaman tertentu dari dokumen PDF Ketuk Untuk Menghapus Secara manual Pangkas PDF Pangkas halaman dokumen hingga batas apa pun Ratakan PDF Jadikan PDF tidak dapat dimodifikasi dengan melakukan raster pada halaman dokumen Tidak dapat memulai kamera. Silakan periksa izin dan pastikan itu tidak digunakan oleh aplikasi lain. Ekstrak Gambar Ekstrak gambar yang tertanam dalam PDF pada resolusi aslinya File PDF ini tidak berisi gambar apa pun yang disematkan Alat ini memindai setiap halaman dan memulihkan gambar sumber berkualitas penuh — sempurna untuk menyimpan dokumen asli Gambar Tanda Tangan Param Pena Gunakan tanda tangan sendiri sebagai gambar untuk ditempatkan pada dokumen Zip PDF Pisahkan dokumen dengan interval tertentu dan kemas dokumen baru ke dalam arsip zip Selang Cetak PDF Siapkan dokumen untuk dicetak dengan ukuran halaman khusus Halaman Per Lembar Orientasi Ukuran Halaman Batas Bunga Lutut Lembut Dioptimalkan untuk anime dan kartun. Peningkatan cepat dengan peningkatan warna alami dan lebih sedikit artefak Samsung One UI 7 menyukai gaya Masukkan simbol matematika dasar di sini untuk menghitung nilai yang diinginkan (misalnya (5+5)*10) Ekspresi matematika Ambil hingga %1$s gambar Pertahankan Tanggal Waktu Selalu pertahankan tag Exif yang terkait dengan tanggal dan waktu, bekerja secara independen dari opsi Keep Exic Warna Latar Belakang Untuk Format Alfa Menambahkan kemampuan untuk mengatur warna latar belakang untuk setiap format gambar dengan dukungan alfa, jika dinonaktifkan, ini hanya tersedia untuk format non alfa Buka proyek Lanjutkan mengedit proyek Image Toolbox yang disimpan sebelumnya Tidak dapat membuka proyek Image Toolbox Proyek Image Toolbox tidak memiliki data proyek Proyek Image Toolbox rusak Versi proyek Image Toolbox yang tidak didukung: %1$d Simpan proyek Simpan lapisan, latar belakang, dan riwayat edit dalam file proyek yang dapat diedit Gagal dibuka Tulis Ke PDF yang Dapat Dicari Kenali teks dari kumpulan gambar dan simpan PDF yang dapat dicari dengan gambar dan lapisan teks yang dapat dipilih Lapisan alfa Balik Horisontal Balik Vertikal Kunci Tambahkan Bayangan Warna Bayangan Geometri Teks Regangkan atau miringkan teks untuk gaya yang lebih tajam Skala X miring X Hapus anotasi Hapus jenis anotasi yang dipilih seperti tautan, komentar, sorotan, bentuk, atau bidang formulir dari halaman PDF Hyperlink Lampiran Berkas Garis Popup Perangko Bentuk Catatan Teks Markup Teks Bidang Formulir Menandai Tidak dikenal Anotasi Pisahkan Tambahkan bayangan buram di belakang lapisan dengan warna dan offset yang dapat dikonfigurasi ================================================ FILE: core/resources/src/main/res/values-it/strings.xml ================================================ Qualcosa non ha funzionato: %1$s Dimensione %1$s Caricamento… L\'immagine è troppo grande per mostrarne un\'anteprima, ma verrà tentato comunque il salvataggio Scegli un\'immagine per iniziare Larghezza %1$s Altezza %1$s Qualità Estensione Tipo di ridimensionamento Specifico Flessibile Scegli un\'immagine Vuoi davvero chiudere l\'applicazione? Chiusura dell\'applicazione Rimani Chiudi Reimposta immagine Le modifiche all\'immagine verranno reimpostate ai valori iniziali. Valori correttamente reimpostati Reimposta Qualcosa non ha funzionato Riavvia app Copia negli appunti Eccezione Modifica EXIF OK Nessun EXIF trovato Aggiungi tag Salva Pulisci Cancella EXIF Cancella Tutti i dati EXIF dell\'immagine verranno rimossi. Questa azione è irreversibile! Preconfigurazioni Ritaglia Salvataggio Tutte le modifiche andranno perse se abbandoni adesso. Consulta il codice sorgente Ricevi gli ultimi aggiornamenti, discuti dei problemi e altro. Ridimensionamento Singolo Modifica e ridimensiona una singola immagine. Selettore colore Estrai, copia o condividi un colore da un\'immagine. Immagine Colore Colore copiato Ritaglia un\'immagine Versione Mantieni EXIF Immagini: %d Cambia anteprima Rimuovi Genera una palette di colori da un\'immagine. Genera Palette Palette Aggiorna Nuova versione %1$s Tipo non supportato: %1$s Impossibile generare una palette per l\'immagine specificata. Originale Cartella di output Predefinita Personalizzata Non specificata Memoria del telefono Ridimensiona per Peso Dimensione massima in KB Ridimensiona un\'immagine in base alla grandezza specificata in KB. Confrontare Confronto tra due immagini date Scegli un\'immagine Scegli un\'immagine per iniziare Impostazioni Modalità notturna Oscuro Leggero Sistema Consenti monet immagine Lingua Colori dinamici Personalizzazione Se abilitato, quando scegli un\'immagine da modificare, i colori dell\'app verranno adottati per questa immagine Modalità Amoled Se abilitato, il colore delle superfici verrà impostato su buio assoluto in modalità notturna Rosso Niente da incollare Schema di colore Blu Verde Incolla un codice aRGB valido Impossibile modificare la combinazione di colori dell\'app mentre i colori dinamici sono attivi Il tema dell\'app sarà basato sul colore selezionato Segnalazioni bug Aiuta a tradurre Nessun aggiornamento trovato Informazioni sull\'app Invia segnalazioni di bug e richieste di funzionalità qui Cerca qui Correggi gli errori di traduzione o localizza il progetto in un\'altra lingua Nessun risultato per questa ricerca Se abilitato, i colori dell\'app verranno adottati per i colori dello sfondo Impossibile salvare le immagini %d Superficie Secondario L\'app ha bisogno di accedere alla memoria per salvare le immagini. Per favore concedi l\'autorizzazione nella finestra di dialogo seguente Colori Monet Questa applicazione è completamente gratuita, ma se vuoi sostenere lo sviluppo del progetto, puoi cliccare qui Allineamento FAB Controlla gli aggiornamenti Se abilitata, la finestra di aggiornamento verrà mostrata all\'avvio dell\'app Valori Aggiungi Primario Terziario Permesso Consenti Zoom immagine Prefisso Nome del file L\'app ha bisogno di questa autorizzazione per funzionare, per favore concedila manualmente Spessore del bordo Condividi Archiviazione esterna Aggiungi dimensione file Se abilitato, aggiunge larghezza e altezza dell\'immagine al nome del file salvato Scegli quale emoji mostrare sulla schermata principale Emoji Gamma Luci e ombre Punti salienti Ombre Negativo Spaziatura Larghezza della linea Bordo di Sobel Raggio Collegamento immagine Alfa Vortice Limiti ridimensionamento Cercare Sfocatura dello stack Convoluzione 3x3 Centro sfocatura y Soglia di luminanza Elimina EXIF Elimina i metadati EXIF da qualsiasi set di immagini Anteprima Immagine Visualizza l\'anteprima di qualsiasi tipo di immagine: GIF, SVG e così via Fonte immagine Selettore di foto Galleria Esplora file Selettore di foto moderno per Android che appare nella parte inferiore dello schermo, potrebbe funzionare solo su android 12+. Presenta problemi con la ricezione dei metadati EXIF Semplice selettore di immagini della galleria. Funziona solo se hai un\'app per la selezione di file multimediali Usa l\'intento GetContent per scegliere l\'immagine. Funziona ovunque, ma su alcuni dispositivi può avere problemi con la ricezione di immagini selezionate. Non è colpa mia. Disposizione delle opzioni Modificare Ordine Modifica l\'ordine delle opzioni sulla schermata principale Contano gli emoji Sostituisci il numero di sequenza Se abilitato, sostituisce il timestamp standard al numero di sequenza dell\'immagine se si utilizza l\'elaborazione batch L\'aggiunta del nome file originale non funziona se è selezionata l\'origine dell\'immagine del selettore di foto Carica l\'immagine dalla rete Nessuna immagine Riempire Adatto Forza ogni immagine in un\'immagine data dal parametro Larghezza e Altezza - può cambiare le proporzioni Ridimensiona le immagini in immagini con un lato lungo dato dal parametro Larghezza o Altezza, tutti i calcoli delle dimensioni verranno eseguiti dopo il salvataggio - mantiene le proporzioni Luminosità Contrasto Tinta Saturazione Aggiungi filtro Filtro Applica qualsiasi catena di filtri a determinate immagini Filtri Leggero Filtro colore Esposizione bilanciamento del bianco Temperatura Tinta Monocromo Foschia Effetto Distanza Pendenza Affilare Seppia Solarizza Vividezza Bianco e nero Tratteggio incrociato Sfocatura Mezzitoni Spazio colore GCA sfocatura gaussiana Sfocatura della casella Sfocatura bilaterale Rilievo Laplaciano Vignetta Inizio FINE Levigatura Kuwahara Scala Distorsione Angolo Rigonfiamento Dilatazione Rifrazione della sfera Indice di rifrazione Rifrazione della sfera di vetro Matrice di colori Opacità Ridimensiona le immagini scelte in base a determinati limiti di larghezza e altezza rispettando le proporzioni Schizzo Soglia Livelli di quantizzazione Tono liscio Toon Posterizza Soppressione non massima Inclusione di pixel debole Filtro RGB Falso colore Primo colore Secondo colore Riordina Sfocatura veloce Dimensione sfocatura Centro sfocatura x Sfocatura dello zoom Bilanciamento del colore Carica qualsiasi immagine da internet, visualizzala in anteprima, ingrandiscila, salvala e modificala se lo desideri. Scala del contenuto sequenzaNum originaleNome file Aggiungi il nome del file originale Se abilitato aggiunge il nome file originale nel nome dell\'immagine di output Implementazione Scegli file Opzioni di gruppo per tipo Trovato %1$s Opzione di riserva Decrittare Crittografia dei file basata su password. I file ottenuti possono essere archiviati nella directory selezionata o condivisi. I file decifrati possono anche essere aperti direttamente. La dimensione massima del file è limitata dal sistema operativo Android e dalla memoria disponibile, che dipende dal dispositivo. \nNota: per memoria non si intende quella di archiviazione. Dimensione della cache Utensili Raggruppa le opzioni sulla schermata principale per tipologia invece di usare la disposizione personalizzata Personalizzazione secondaria Immagine dello schermo Crittografa e decrittografa qualsiasi file (non solo l\'immagine) in base a vari algoritmi crittografici Crittografare Disegno Disegna sull\'immagine come in uno sketchbook o disegna sullo sfondo stesso Dipingi alfa Scegli il colore di sfondo e disegnaci sopra Scegli il file per iniziare Archivia questo file sul tuo dispositivo o usa l\'azione di condivisione per metterlo dove vuoi Caratteristiche Saltare L\'app Files è disabilitata, attivala per utilizzare questa funzione Modifica schermata Password non valida o il file scelto non è crittografato Compatibilità Colore di sfondo File elaborato Impossibile modificare la disposizione mentre il raggruppamento delle opzioni è abilitato Cache Se abilitata, la cache dell\'app verrà cancellata all\'avvio dell\'app Creare Si prega di notare che la compatibilità con altri software o servizi di crittografia dei file non è garantita. Un trattamento della chiave o una configurazione di cifratura leggermente diversi possono causare incompatibilità. Il tentativo di salvare l\'immagine con una determinata larghezza e altezza potrebbe causare un errore OOM. Fallo a tuo rischio e non dire che non ti avevo avvertito! Cancellazione automatica della cache copia Il salvataggio in modalità %1$s può essere instabile, perché è un formato senza perdita di dati Colore della vernice Disegna sull\'immagine Scegli un\'immagine e disegna qualcosa su di essa Disegna sullo sfondo Cifra Decrittazione Crittografia Chiave AES-256, modalità GCM, nessuna spaziatura interna, IV casuali a 12 byte. (Per impostazione predefinita, ma è possibile selezionare l\'algoritmo desiderato) Le chiavi vengono utilizzate come hash SHA-3 (256 bit). Dimensione del file Se hai selezionato il preset 125, l\'immagine verrà salvata con il 125% della dimensione originale. Se hai scelto il preset 50, allora l\'immagine verrà salvata con il 50% della dimensione. In questo modo verranno ripristinate le impostazioni ai valori predefiniti. Si noti che questa operazione non può essere annullata senza un file di backup menzionato sopra. Oops… Qualcosa è andato storto. Puoi scrivermi utilizzando le opzioni di seguito e cercherò di trovare una soluzione Chat Telegram Natura e animali Attività Cibo e Bevande Contattami Fai il backup delle impostazioni dell\'app File corrotto o non un backup Permette di inviare dati anonimi sull\'utilizzo dell\'app Scala dei caratteri Modalità disegno Ripristina Gli spazi trasparenti intorno all\'immagine saranno sbarrati Discuti dell\'app e ricevi feedback da altri utenti. Puoi anche ottenere aggiornamenti e approfondimenti sulla versione beta. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Zz 0123456789 !? Cancella sfondo Ripristina l\'immagine Elimina Proporzioni Rimozione sfondo Ritaglia l\'immagine Rimuovi lo sfondo dall\'immagine disegnando o usando l\'opzione Automatica Stai per cancellare lo schema dei colori. Questa operazione non può essere annullata Salvato nella cartella %1$s Impostazioni ripristinate con successo Backup e ripristina Backup Cancella lo sfondo automaticamente Emoji Questo permette all\'app di collezzionare dati sui crash manualmente Crea segnalazione Salva in questa cartella %1$s, con questo nome %2$s Simboli Analisi Usa questo tipo di maschera per creare una maschera da una data immagine, nota che DOVREBBE avere un canale alfa Abilita emoji Ridimensiona e converti Oggetti Nome file casuale Testo Predefinito Cancella schema Ripristina sfondo Carattere Cambia la dimensione dell\'immagine e convertila in altri formati. I dati EXIF possono essere modificati se si seleziona una sola foto L\'uso di caratteri di grandi dimensioni può causare glitch e problemi dell\'interfaccia utente, che non verranno risolti. Usa con cautela. Se abilitato il nome del file sarà casuale I metadati dell\'immagine originale verranno conservati Ripristina le impostazioni dal backup precedente Il preset qui determina la % del file di output, ad esempio se si seleziona il preset 50 su un\'immagine da 5 MB, si otterrà un\'immagine da 2,5 mb dopo il salvataggio Modalità gomma Viaggi e Luoghi Aggiornamenti Orizzontale Maschera di ritaglio Numero max colori Attesa Al momento, il formato %1$s consente la sola lettura dei metadati EXIF su android. L\'immagine generata non avrà alcun metadato, quando verrà salvata. Ordine immagini Morbidezza pennello Arrotonda angoli In valore di %1$s indica una compressione veloce, che produrrà un file di dimensioni relativamente grandi. %2$s indica una compressione più lenta, che produrrà file di dimensioni più piccole. Cuci Immagini Consenti beta Disegna Frecce Scegli almeno 2 immagini Se abilitato, il percorso di disegno sarà rappresentato da una freccia a punta Raggio sfocatura Se abilitato, le immagini piccole saranno scalate sulla base delle dimensioni di quella più grande Combina le immagini scelte in una singola immagine grande Salvataggio quasi completato. Se si annulla ora bisognerà salvare di nuovo. Contagocce Orientamento Immagine Verticale Iterazioni Le immagini saranno ritagliate al centro in base alle dimensioni inserite. L\'area verrà espansa con il colore di sfondo indicato se l\'immagine è più piccola delle dimensioni inserite. Donazioni Scala immagine generata Regolare Se abilitato, il controllo aggiornamenti includerà le versioni beta dell\'app Scala le immagini piccole ingrandendole Pixelizzazione del cerchio migliorata Colore da rimuovere Rimuovi colore Superiore Forza Orientamento & Solo rilevamento script Carattere singolo Problema Quantità Seme Stai per eliminare la maschera filtro selezionata. Questa operazione non può essere annullata Nessuna directory \"%1$s\" trovata, l\'abbiamo impostata su quella predefinita, salva nuovamente il file Forza delle vibrazioni Sovrascrivi file Vuoto Suffisso Ricerca Libero Pixelizzazione migliorata Pixelizzazione del tratto Pixelazione del diamante migliorata Tolleranza Colore da sostituire Colore target Ricodificare Vibrante Espressivo Arcobaleno Macedonia Fedeltà Contenuto Stile palette predefinito, permette di personalizzare tutti e quattro i colori, altri permettono di impostare solo il colore chiave Uno stile leggermente più cromatico che monocromatico Un tema forte, la vivacità è massima per la tavolozza Primaria, aumentata per le altre Un tema giocoso: la tonalità del colore di origine non appare nel tema Un tema monocromatico, i colori sono puramente nero/bianco/grigio Uno schema che inserisce il colore di origine in Scheme.primaryContainer Uno schema che è molto simile allo schema dei contenuti Abilita la possibilità di effettuare ricerche tra tutte le opzioni disponibili nella schermata principale Opera con file PDF: visualizza l\'anteprima, converti in batch di immagini o creane uno da determinate immagini Anteprima del PDF PDF in immagini Immagini in PDF Anteprima PDF semplice Converti PDF in immagini nel formato di output specificato Comprimi le immagini fornite nel file PDF di output Filtro maschera Applica catene di filtri su determinate aree mascherate, ciascuna area mascherata può determinare il proprio set di filtri Maschere Aggiungi maschera Maschera %d Se abilitato, tutte le aree non mascherate verranno filtrate invece del comportamento predefinito Elimina maschera Varianti semplici Evidenziatore Neon Penna Sfocatura della privacy Aggiungi un effetto luminoso ai tuoi disegni Quello predefinito, il più semplice: solo il colore Sfoca l\'immagine sotto il percorso disegnato per proteggere tutto ciò che desideri nascondere Simile alla sfocatura della privacy, ma pixela invece di sfocare Abilita il disegno dell\'ombra dietro i cursori Abilita il disegno delle ombre dietro gli interruttori Abilita il disegno dell\'ombra dietro i pulsanti di azione mobili Abilita il disegno dell\'ombra dietro i pulsanti predefiniti Barre delle app Abilita il disegno dell\'ombra dietro le barre dell\'app Rotazione automatica Consente di adottare la casella limite per l\'orientamento dell\'immagine Disegna il percorso come valore di input Disegna il percorso dal punto iniziale al punto finale come una linea Disegna la freccia che punta dal punto iniziale al punto finale come una linea Disegna una freccia puntata da un determinato percorso Disegna una freccia a doppia punta dal punto iniziale al punto finale come una linea Disegna una freccia a doppia punta da un determinato percorso Disegna il rettangolo dal punto iniziale al punto finale Disegna l\'ovale dal punto iniziale al punto finale Disegna l\'ovale delineato dal punto iniziale al punto finale Disegna il rettangolo delineato dal punto iniziale al punto finale Appunti Perno automatico Se abilitato, aggiunge automaticamente l\'immagine salvata agli appunti Vibrazione Per sovrascrivere i file è necessario utilizzare l\'origine immagine \"Explorer\", prova a riselezionare le immagini, abbiamo cambiato l\'origine immagine con quella necessaria Il file originale verrà sostituito con uno nuovo invece di essere salvato nella cartella selezionata, questa opzione deve essere \"Explorer\" o GetContent, quando si attiva questa opzione, verrà impostata automaticamente Bicubico Di base L\'interpolazione lineare (o bilineare, in due dimensioni) è generalmente utile per modificare le dimensioni di un\'immagine, ma provoca un ammorbidimento indesiderato dei dettagli e può comunque risultare un po\' frastagliato Metodi di ridimensionamento migliori includono il ricampionamento di Lanczos e i filtri Mitchell-Netravali Uno dei modi più semplici per aumentare le dimensioni, sostituendo ogni pixel con un numero di pixel dello stesso colore La modalità di ridimensionamento Android più semplice utilizzata in quasi tutte le app Metodo per l\'interpolazione e il ricampionamento uniforme di una serie di punti di controllo, comunemente utilizzato nella grafica computerizzata per creare curve uniformi Funzione di finestra spesso applicata nell\'elaborazione del segnale per ridurre al minimo la perdita spettrale e migliorare la precisione dell\'analisi della frequenza assottigliando i bordi di un segnale Tecnica di interpolazione matematica che utilizza i valori e le derivate agli estremi di un segmento di curva per generare una curva uniforme e continua Metodo di ricampionamento che mantiene l\'interpolazione di alta qualità applicando una funzione di sincronizzazione ponderata ai valori dei pixel Metodo di ricampionamento che utilizza un filtro di convoluzione con parametri regolabili per ottenere un equilibrio tra nitidezza e anti-aliasing nell\'immagine ridimensionata Utilizza funzioni polinomiali definite a tratti per interpolare e approssimare in modo uniforme una curva o una superficie, rappresentazione di forme flessibili e continue Il salvataggio nella memoria non verrà eseguito e l\'immagine verrà tentata solo di essere inserita negli appunti Il pennello ripristinerà lo sfondo invece di cancellarlo Verrà utilizzato un interruttore simile a pixel al posto del materiale di Google basato su quello File sovrascritto con nome %1$s nella destinazione originale Lente d\'ingrandimento Abilita la lente d\'ingrandimento sulla parte superiore del dito nelle modalità di disegno per una migliore accessibilità Forza il valore iniziale Forza il controllo iniziale del widget EXIF Consenti più lingue Orientamento automatico & Rilevamento script Solo automatico Auto Colonna singola Testo verticale a blocco singolo Blocco unico Linea singola Singola parola Parola circolare Testo scarno Orientamento del testo sparse & Rilevamento script Linea cruda Vuoi eliminare i dati di addestramento OCR della lingua \"%1$s\" per tutti i tipi di riconoscimento o solo per quello selezionato (%2$s)? Tutto Crea un gradiente della dimensione di output specificata con colori e tipo di aspetto personalizzati Tipo di gradiente Centro X Centro Y Modalità tessera Ripetuto Specchio MORSETTO Decalcomania Il colore si ferma Aggiungi colore Proprietà Utilizza la fotocamera per scattare foto, tieni presente che è possibile ottenere solo un\'immagine da questa sorgente immagine Ripete la filigrana sull\'immagine anziché su una singola nella posizione specificata Scostamento X Scostamento Y Tipo di filigrana Modalità sovrapposizione Strumenti GIF Converti immagini in immagini GIF o estrai fotogrammi da una determinata immagine GIF GIF alle immagini Converti file GIF in batch di immagini Converti batch di immagini in file GIF Immagini in GIF Scegli l\'immagine GIF per iniziare Usa la dimensione del primo fotogramma Sostituisci la dimensione specificata con le prime dimensioni del telaio Ripeti conteggio Ritardo fotogramma millis FPS Nasconde il contenuto all\'uscita, inoltre lo schermo non può essere catturato o registrato Scala di grigi Bayer due a due dithering Bayer tre per tre tentennamenti Bayer quattro a quattro dithering Bayer Otto per Otto Dithering Floyd Steinberg Titubante Jarvis Judice Ninke esita Sierra dithering Dithering Sierra a due righe Sierra Lite Dithering Atkinson Dithering Stucki Dithering Burkes esita Dithering da sinistra a destra Utilizza funzioni polinomiali bicubiche definite a tratti per interpolare e approssimare in modo uniforme una curva o una superficie, rappresentazione di forme flessibili e continue Anaglifo Rumore Ordinamento pixel Mescola Glitch migliorato Spostamento canale X Cambio canale Y Dimensione della corruzione Spostamento della corruzione X Spostamento della corruzione Y Sfocatura della tenda Dissolvenza laterale Lato Metter il fondo a Cristallizzare Colore del tratto Vetro frattale Olio Effetto Acqua Misurare Frequenza X Frequenza Y Ampiezza X Matrice di colori 3x3 Effetti semplici Polaroid Tritanomalia Deuteranomalia Protanomalia Vintage ▾ Coda Cromata Visione notturna Caldo Freddo Tritanopia Deuteranopia Protanopia Acromatomalia Foschia arancione Sogno rosa Ora d\'oro Nebbia Viola Alba Vortice colorato Fuoco spettrale Vortice rosso Codice digitale Nessun filtro preferito ancora aggiunto Forma dell\'icona Drago Aldridge Tagliare Uchimura Mobius Transizione Picco Anomalia del colore Immagini sovrascritte nella destinazione originale Impossibile modificare il formato dell\'immagine mentre l\'opzione di sovrascrittura dei file è abilitata Emoji come combinazione di colori Utilizza il colore primario delle emoji come combinazione di colori dell\'app invece di quello definito manualmente Se abilitato, disegna bordi sfocati sotto l\'immagine originale per riempire gli spazi attorno ad essa anziché un singolo colore Pixelizzazione Pixelizzazione del diamante Pixelizzazione del cerchio Sostituisci colore Erodere Diffusione anisotropa Diffusione Conduzione Sfalsamento del vento orizzontale Sfocatura bilaterale veloce Sfocatura di Poisson Mappatura dei toni logaritmici Mappatura dei toni filmici di ACES Ampiezza Marmo Turbolenza Ampiezza Y Distorsione Perlin Mappatura dei toni filmici Hable Mappatura dei toni di Hejl Burgess Mappatura dei toni di collina ACES Attuale Filtro completo Inizio Centro FINE Applica eventuali catene di filtri a determinate immagini o a una singola immagine Creatore di gradienti Velocità Defoschia Omega Strumenti PDF Valuta l\'app Valutare Questa app è completamente gratuita, se vuoi che diventi più grande, avvia il progetto su Github 😄 Matrice di colori 4x4 Browni Acromatopsia Lineare Radiale Spazzare E-mail Lazo Disegna un percorso pieno chiuso in base al percorso specificato Freccia a doppia linea Modalità traccia percorso Disegno libero Doppia freccia Freccia di linea Freccia Linea Ovale delineato Rett. delineato Ovale Rett Dithering Quantizzatore Falso Floyd Steinberg Dithering Dithering casuale Dithering della soglia semplice Colore maschera Anteprima della maschera La maschera del filtro disegnata verrà renderizzata per mostrarti il risultato approssimativo Bilineare Modalità scala Hann Ermita Lanczos Mitchell Più vicino Spline Valore di default Valore nell\'intervallo %1$s - %2$s Sigma Sigma spaziale Sfocatura mediana Catmull Solo clip Aggiunge il contenitore con la forma selezionata sotto le icone principali delle carte Applicazione della luminosità Schermo Sovrapposizione gradiente Componi qualsiasi sfumatura della parte superiore dell\'immagine specificata Trasformazioni Telecamera Grano Non nitido Pastello Estate calda Luce soffusa primaverile Toni autunnali Sogno di lavanda Cyberpunk Limonata leggera Magia notturna Paesaggio di fantasia Esplosione di colori Gradiente elettrico Oscurità caramellata Gradiente futuristico Sole Verde Mondo Arcobaleno Viola profondo Portale spaziale Filigrana Coprire le immagini con filigrane di testo/immagine personalizzabili Ripeti filigrana Questa immagine verrà utilizzata come modello per la filigrana Colore del testo Dimensione pixel Blocca l\'orientamento del disegno Se abilitato in modalità disegno, lo schermo non ruoterà Bokeh Usa il lazo Utilizza Lazo come in modalità disegno per eseguire la cancellazione Anteprima dell\'immagine originale alfa L\'emoji della barra dell\'app verrà modificata continuamente in modo casuale invece di utilizzare quella selezionata Emoji casuali Non è possibile utilizzare la selezione casuale di emoji mentre gli emoji sono disabilitati Impossibile selezionare un emoji mentre se ne seleziona uno casuale abilitato Controlla gli aggiornamenti Vecchia televisione Sfocatura casuale OCR (riconoscimento del testo) Riconosci il testo da una determinata immagine, sono supportate oltre 120 lingue L\'immagine non contiene testo oppure l\'app non l\'ha trovata Accuracy: %1$s Tipo di riconoscimento Veloce Standard Migliore Nessun dato Per il corretto funzionamento di Tesseract OCR è necessario scaricare sul dispositivo dati di allenamento aggiuntivi (%1$s). \nVuoi scaricare i dati %2$s? Scaricamento Nessuna connessione, controlla e riprova per scaricare i modelli dei treni Lingue scaricate Lingue disponibili Modalità di segmentazione Griglia orizzontale Griglia verticale Modalità punto Conteggio delle righe Conteggio delle colonne Utilizza Pixel Switch Diapositiva Fianco a fianco Attiva/Disattiva tocco Trasparenza Preferito Spline B Sfocatura stack nativa Spostamento dell\'inclinazione Tipo di riempimento inverso Coriandoli I coriandoli verranno visualizzati durante il salvataggio, la condivisione e altre azioni primarie Modalità protetta Stile tavolozza Macchia tonale Neutro Disegna percorsi di evidenziatore più nitidi semitrasparenti Contenitori Abilita il disegno dell\'ombra dietro i contenitori Cursori Interruttori FAB Pulsanti Attenzione Questo controllo aggiornamenti si connetterà a GitHub per verificare se è disponibile un nuovo aggiornamento Bordi sbiaditi Disabilitato Entrambi Inverti i colori Sostituisce i colori del tema con quelli negativi, se abilitato Uscita Se lasci l\'anteprima ora, dovrai aggiungere nuovamente le immagini Formato immagine Colori scuri Utilizza modalità notte combinazione di colori invece di luce variante Copia come codice” Jetpack Compose\" Crea la tavolozza \"Material You\" dall\'immagine Sfocatura dell\'anello Sfocatura incrociata Sfocatura stellare Spostamento dell\'inclinazione lineare Sfocatura del cerchio Tag da rimuovere Strumenti APNG Converti immagini in immagini APNG o estrai fotogrammi da una determinata immagine APNG APNG alle immagini Converti batch di immagini in file APNG Immagini in APNG Scegli l\'immagine APNG per iniziare Sfocatura movimento Converti file APNG in batch di immagini Compressione Crea file zip da determinati file o immagini Trascina la larghezza della maniglia Tipo Coriandoli Da festa Esplosione Pioggia Angoli Strumenti JXL Esegui la transcodifica senza perdita da JXL a JPEG Eseguire la transcodifica senza perdita da JPEG a JXL Da JXL a JPEG Esegui la transcodifica JXL ~ JPEG senza perdita di qualità o converti le GIF/APNG in animazioni JXL. Da JPEG a JXL Livello Armonizzazione Abilita la generazione di anteprime, questo può aiutare a evitare crash su alcuni dispositivi, ma disabilita anche alcune funzionalità di modifica all\'interno dell\'opzione di modifica singola. Auto Incolla Consente all\'app di incollare automaticamente i dati degli appunti, in modo da farli apparire nella schermata principale e poterli elaborare. Colore Armonizzazione Lanczos Bessel Da GIF a JXL Da APNG a JXL Da JXL a Immagini Converti l\'animazione JXL in un gruppo di immagini Immagini in JXL Converti un gruppo di immagini in un\'animazione JXL Comportamento Salta Selezione File Il selezionatore di file verrà mostrato immediatamente, se possibile, nella schermata scelta Genera Anteprime Converti immagini GIF in animazioni JXL Converti immagini APNG in animazioni JXL Ordinamento Ordina Per Data Ordina Per Nome Ordina Per Nome (Invertito) Ordina Per Data (Invertito) Nessun Permesso Scegli l\'immagine JXL per iniziare Oggi Ieri Richiesta Immagini in Svg Riprova Metodo di ricampionamento che mantiene un\'interpolazione di alta qualità applicando una funzione di Bessel (jinc) ai valori dei pixel Utilizza il selezionatore di immagini di Image Toolbox invece di quelli predefiniti dal sistema. Mostra le impostazioni in orizzontale Impostazioni a schermo intero Abilita e la pagina delle impostazioni verrà sempre aperta a schermo intero invece che lateralmente Seleziona più media Seleziona un media Seleziona Sfocatura Gaussiana Veloce 2D Sfocatura Gaussiana Veloce 3D Sfocatura Gaussiana Veloce 4D Configurazione dei canali Selettore incorporato Compressione con perdita Tipo di compressione Se disattivata, le impostazioni in modalità panorama saranno aperte tramite il pulsante nella barra superiore invece di averle sempre visibili. Cambia Tipo Componi Controlla la velocità di decodifica del risultato per renderne l\'apertura più rapida. Un valore di %1$s indica la decodifica più lenta, mentre %2$s la più veloce. Quest\'impostazione può aumentare le dimensioni dell\'immagine finale. Usa una compressione con perdita di dati per ridurre le dimensioni del file invece di una senza perdita. Usa Material Your basato sulla vista, è molto meglio degli altri e ha belle animazioni Utilizza il Material You Jetpack Compose che cambi, non è così bello come quello basato sulla vista Max Ridimensiona Ancora Pixel Usa interruttori in stile Windows 11 basati sul sistema di design \"Fluent\" Fluent Trasferisci Funzione Risoluzione Y Nero Bianco di Riferimento Minimo Rapporto del Colore Descrizione dell\'Immagine Dettagliato/a Campioni Per Pixel Cupertino Riduci la risoluzione dell\'immagine Soglia delle linee Tolleranza dell\'Arrotondamento delle Coordinate Scala del percorso Larghezza Predefinita della Linea network LSTM Aggiungi una Nuova Cartella Bit Per Campione Compressione Interpretazione fotometrica Configurazione Planare Risoluzione X Unità di risoluzione Righe Per Striscia Punto Bianco Cromaticità Primarie L\'utilizzo di questo strumento per tracciare immagini larghe senza riduzione della dimensione non è consigliato, può causare crash e aumentare il tempo di elaborazione Ripristina proprietà Tutte le proprietà saranno impostate ai valori di default, nota che questa azione non può essere annullata Prima, usa la tua applicazione di editing di foto per applicare un filtro per LUT neutro che puoi ottenere qui. Per fare in modo che questo funzioni correttamente ogni colore di ogni pixel non deve dipendere da altri pixel (es. la sfocatura non funzionerà). Appena pronto, usa la tua nuova immagine LUT come input per un filtro LUT 512*512 Converti La risoluzione dell\'immagine verrà ridotta per ridurre le dimensioni prima di processare, questo aiuta lo strumento ad essere più veloce e sicuro Converti i batch di immagini nel formato specificato Traccia le immagini fornite in immagini SVG Utilizza uno switch simile ad iOS basato sul cupertino design system Gradiente ASCII Raggi Scintilla Angolo di Apertura Arco Indice di rifrazione Usa Palette Campionata La palette di quantificazione verrà campionata se questa opzione è abilitata Ometti Percorso Soglia Quadratica Modalità Motore Coefficienti Y Cb Cr Data Ora Crea Modello Artista Versione Exif Dimensione Pixel X Dimensione Pixel Y Nota Creatore Commento Utente Data Ora Originali Data Ora Digitalizzati Tempo di Esposizione Numero F Programma di Esposizione Sensibilità Spettrale Sensibilità Fotografica Tipo Sensibilità Sensibilità di Uscita Standard Velocità ISO Velocità ISO Latitudine yyy Velocità ISO Latitudine zzz Valore di Apertura Valore di Luminosità Valore di Apertura Massima Distanza Soggetto Area Soggetto Distanza Focale Energia Flash Risoluzione Piano Focale X Risoluzione Piano Focale Y Unità Risoluzione Piano Focale Posizione Soggetto Indice di Esposizione Sorgente File Modalità Esposizione Bilanciamento Bianco Saturazione Contrasto Nitidezza Descrizione Impostazioni Dispositivo ID Unico Immagine Nome Proprietario Fotocamera Numero di Serie Corpo Specifiche Lente Marca Lente Modello Lente Numero Di Serie Lente ID Versione GPS Riferimento Latitudine GPS Latitudine GPS Riferimento Longitudine GPS Longitudine GPS Riferimento Altitudine GPS Altitudine GPS Satelliti GPS Stato GPS Modalità Misurazione GPS Riferimento Velocità GPS Velocità GPS Riferimento Traccia GPS Traccia GPS Riferimento Direzione Immagine GPS Direzione Immagine GPS Dato Mappa GPS Riferimento Latitudine Dest GPS Latitudine Dest GPS Riferimento Longitudine Dest GPS Longitudine Dest GPS Dimensione Scritte Dimensione Filigrana Ripeti Testo Il testo corrente verrà ripetuto fino alla fine del percorso invece di un unica volta Vertici Poligono Triangolo Triangolo Delineato Disegna un poligono dal punto di partenza al punto finale Poligono Delineato Stella Stella Delineata Usa l\'immagine selezionata per disegnarla lungo il percorso dato Questa immagine sarà utilizzata come ripetizione nel percorso disegnato Disegna un triangolo delineato dal punto di partenza al punto finale Disegna un triangolo delineato dal punto di partenza al punto finale Disegna un poligono delineato dal punto di partenza al punto finale Disegna Poligono Regolare Disegnare poligono regolare invece che in forma libera Disegna una stella dal punto di partenza al punto finale Disegna una stella delineata dal punto di partenza al punto finale Rapporto Raggio Interno Disegna Stella Regolare Disegna una stella regolare invece che in forma libera Apri Modifica Invece Di Anteprima Quando selezioni l\'immagine da aprire (anteprima) in ImageToolbox, verrà aperto la selezione di modifica invece di visualizzare in anteprima Scanner Documenti Scansiona documenti e crea PDF o immagini separate Clicca per iniziare la scansione Inizia Scansione Salva Come PDF Condividi Come PDF Le opzioni di seguito sono per salvare immagini, non PDF Inserisci Percentuale Scansiona Codice QR Il file selezionato non ha dati sul modello di filtro Crea Modello Nome Modello Questa immagine sarà usata per visualizzare in anteprima questo modello di filtro Modello di Filtro Come Immagine QR Come file Salva come file Salva come Immagine QR Cancella Modello Stai per eliminare il modello di filtro selezionato. Questa operazione non può essere annullata Aggiunto modello di filtro con nome \"%1$s\" (%2$s) Anteprima Filtro Codice a barre & QR Scansiona il codice QR e ottieni il suo contenuto o incolla la tua stringa per generarne uno nuovo Contenuto Codice Scansiona qualsiasi codice a barre per sostituire il contenuto nel campo o digita qualcosa per generare un nuovo codice a barre con il tipo selezionato Descrizione QR Concedi l\'autorizzazione della fotocamera nelle impostazioni per la scansione di codici QR Concedi l\'autorizzazione della fotocamera nelle impostazioni per la scansione di documenti Cubica Quadratica Gaussiana Conversione Formato Converti una serie di immagini da un formato all\'altro Diritti d\'autore Versione Flashpix Spazio Colore Bit Compressi Per Pixel File Audio Associato Scostamenti Striscia Numero Byte Striscia Tempo Scostamento Tempo Scostamento Originale Tempo Scostamento Digitalizzato Scostamento Scostamento Massimo Tempo Sub Sec Tempo Sub Sec Originale Tempo Sub Sec Digitalizzato Indice Esposizione Consigliato Valore Velocità Otturatore Modalità Misurazione Risposta Frequenza Spaziale Metodo Rilevamento Rapporto Zoom Digitale Distanza Focale in pellicola da 35mm Tipo Cattura Scena Dimensione Ritaglio Predefinita Inizio Anteprima Immagine Lunghezza Anteprima Immagine Bordo Sinistro Sensore Bordo Destro Sensore Bordo Superiore Sensore Bordo Inferiore Sensore Colore bordo Lineare Scala Spazio Colore Dimensione Griglia X Dimensione Griglia Y Eredità Legacy e LSTM Y Cb Cr Sottocampionamento Posizionamento Y Cb Cr Formato di interscambio JPEG Lunghezza del formato di interscambio JPEG Software Gamma Oecf Valore di compensazione dell\'esposizione Flash Modello CFA Rendering personalizzato Ottieni il controllo Intervallo di distanza del soggetto Timbro orario GPS GPSDOP Rif. rilevamento destinazione GPS Rilevamento destinazione GPS Rif. distanza dest GPS Distanza di destinazione GPS Metodo di elaborazione GPS Informazioni sull\'area GPS Timbro data GPS Differenziale GPS Errore di posizionamento GPS H Indice di interoperabilità Versione DNG Cornice aspetto ISO Disegna il testo sul percorso con il carattere e il colore specificati Dimensione del trattino Antialias Abilita l\'antialiasing per evitare spigoli vivi Equalizza l\'istogramma HSV Equalizza l\'istogramma Consenti l\'immissione tramite campo di testo Abilita il campo di testo dietro la selezione delle preimpostazioni, per inserirle al volo Equalizza la pixelizzazione dell\'istogramma Equalizza istogramma adattivo Equalizza l\'istogramma LUV adattivo Equalizza istogramma LAB adattivo CLAHE LABORATORIO CLAHE CLAHE LUV Ritaglia al contenuto Colore della cornice Colore da ignorare Modello Nessun filtro modello aggiunto Crea nuovo Il codice QR scansionato non è un modello di filtro valido minimo B-Spline Hamming Hanning Uomo Nero Welch Sfinge Bartlett Robidoux Robidoux tagliente Splina 16 Splina 36 Splina 64 Kaiser Bartlett-He Scatola Bohmann Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc L\'interpolazione cubica fornisce un ridimensionamento più uniforme considerando i 16 pixel più vicini, fornendo risultati migliori rispetto a quello bilineare Utilizza funzioni polinomiali definite a tratti per interpolare e approssimare in modo uniforme una curva o una superficie, rappresentazione di forme flessibili e continue Una funzione finestra utilizzata per ridurre la perdita spettrale assottigliando i bordi di un segnale, utile nell\'elaborazione del segnale Una variante della finestra di Hann, comunemente utilizzata per ridurre la dispersione spettrale nelle applicazioni di elaborazione del segnale Una funzione finestra che fornisce una buona risoluzione di frequenza riducendo al minimo la perdita spettrale, spesso utilizzata nell\'elaborazione del segnale Una funzione finestra progettata per fornire una buona risoluzione di frequenza con una ridotta dispersione spettrale, spesso utilizzata nelle applicazioni di elaborazione del segnale Un metodo che utilizza una funzione quadratica per l\'interpolazione, fornendo risultati uniformi e continui Un metodo di interpolazione che applica una funzione gaussiana, utile per attenuare e ridurre il rumore nelle immagini Un metodo di ricampionamento avanzato che fornisce un\'interpolazione di alta qualità con artefatti minimi Una funzione di finestra triangolare utilizzata nell\'elaborazione del segnale per ridurre la dispersione spettrale Un metodo di interpolazione di alta qualità ottimizzato per il ridimensionamento naturale delle immagini, bilanciando nitidezza e uniformità Una variante più nitida del metodo Robidoux, ottimizzata per un ridimensionamento nitido delle immagini Un metodo di interpolazione basato su spline che fornisce risultati uniformi utilizzando un filtro a 16 tocchi Un metodo di interpolazione basato su spline che fornisce risultati uniformi utilizzando un filtro a 36 tocchi Un metodo di interpolazione basato su spline che fornisce risultati uniformi utilizzando un filtro a 64 tocchi Un metodo di interpolazione che utilizza la finestra Kaiser, fornendo un buon controllo sul compromesso tra larghezza del lobo principale e livello del lobo laterale Una funzione di finestra ibrida che combina le finestre Bartlett e Hann, utilizzata per ridurre la perdita spettrale nell\'elaborazione del segnale Un semplice metodo di ricampionamento che utilizza la media dei valori dei pixel più vicini, spesso risultando in un aspetto a blocchi Una funzione finestra utilizzata per ridurre la dispersione spettrale, fornendo una buona risoluzione di frequenza nelle applicazioni di elaborazione del segnale Un metodo di ricampionamento che utilizza un filtro Lanczos a 2 lobi per un\'interpolazione di alta qualità con artefatti minimi Un metodo di ricampionamento che utilizza un filtro Lanczos a 3 lobi per un\'interpolazione di alta qualità con artefatti minimi Un metodo di ricampionamento che utilizza un filtro Lanczos a 4 lobi per un\'interpolazione di alta qualità con artefatti minimi Una variante del filtro Lanczos 2 che utilizza la funzione jinc, fornendo un\'interpolazione di alta qualità con artefatti minimi Una variante del filtro Lanczos 3 che utilizza la funzione jinc, fornendo un\'interpolazione di alta qualità con artefatti minimi Una variante del filtro Lanczos 4 che utilizza la funzione jinc, fornendo un\'interpolazione di alta qualità con artefatti minimi Hanning EWA Variante ellittica ponderata media (EWA) del filtro Hanning per un\'interpolazione e un ricampionamento uniformi Robidoux EWA Variante ellittica ponderata media (EWA) del filtro Robidoux per un ricampionamento di alta qualità Blackman EVA Variante ellittica ponderata media (EWA) del filtro Blackman per ridurre al minimo gli artefatti di squillo Quadrica EWA Variante della media ponderata ellittica (EWA) del filtro Quadric per un\'interpolazione uniforme Robidoux Sharp EWA Variante ellittica ponderata media (EWA) del filtro Robidoux Sharp per risultati più nitidi Lanczos 3 Jinc EWA Variante Elliptical Weighted Average (EWA) del filtro Lanczos 3 Jinc per ricampionamento di alta qualità con aliasing ridotto Ginseng Un filtro di ricampionamento progettato per l\'elaborazione delle immagini di alta qualità con un buon equilibrio tra nitidezza e morbidezza Ginseng EWA Variante ellittica media ponderata (EWA) del filtro Ginseng per una migliore qualità dell\'immagine Lanczos Sharp EWA Variante ellittica ponderata media (EWA) del filtro Lanczos Sharp per ottenere risultati nitidi con artefatti minimi Lanczos 4 EWA più nitido Variante Elliptical Weighted Average (EWA) del filtro Lanczos 4 Sharpest per un ricampionamento delle immagini estremamente nitido Lanczos Soft EWA Variante EWA (Elliptical Weighted Average) del filtro Lanczos Soft per un ricampionamento delle immagini più fluido Haasn Soft Un filtro di ricampionamento progettato da Haasn per un ridimensionamento delle immagini fluido e privo di artefatti Licenzia per sempre Impilamento delle immagini Impila le immagini una sopra l\'altra con le modalità di fusione scelte Aggiungi immagine I contenitori contano Clahe HSL Clahe HSV Equalizza l\'HSL adattivo dell\'istogramma Equalizza l\'istogramma Adaptive HSV Modalità bordo Clip Avvolgere Daltonismo Seleziona la modalità per adattare i colori del tema alla variante daltonica selezionata Difficoltà a distinguere tra tonalità rosse e verdi Difficoltà a distinguere tra tonalità verdi e rosse Difficoltà a distinguere tra tonalità blu e gialle Incapacità di percepire le tonalità rosse Incapacità di percepire le tonalità verdi Incapacità di percepire le tonalità del blu Sensibilità ridotta a tutti i colori Daltonismo completo, vedendo solo sfumature di grigio Non utilizzare lo schema daltonico I colori saranno esattamente come impostati nel tema Sigmoidale Lagrangiano 2 Un filtro di interpolazione Lagrange di ordine 2, adatto per il ridimensionamento di immagini di alta qualità con transizioni uniformi Lagrangiano 3 Un filtro di interpolazione Lagrange di ordine 3, che offre migliore precisione e risultati più uniformi per il ridimensionamento delle immagini Lanczos 6 Un filtro di ricampionamento Lanczos con un ordine superiore a 6, che fornisce un ridimensionamento dell\'immagine più nitido e accurato Lanczos 6 Jinc Una variante del filtro Lanczos 6 che utilizza una funzione Jinc per una migliore qualità di ricampionamento dell\'immagine Sfocatura casella lineare Sfocatura tenda lineare Sfocatura lineare della scatola gaussiana Sfocatura stack lineare Sfocatura gaussiana della scatola Sfocatura gaussiana veloce lineare successiva Sfocatura gaussiana veloce lineare Sfocatura gaussiana lineare Scegli un filtro per usarlo come vernice Sostituisci il filtro Scegli il filtro qui sotto per usarlo come pennello nel tuo disegno Schema di compressione TIFF Basso poli Pittura con sabbia Divisione delle immagini Dividi una singola immagine per righe o colonne Adatta ai limiti Combina la modalità di ridimensionamento del ritaglio con questo parametro per ottenere il comportamento desiderato (Ritaglia/Adatta alle proporzioni) Lingue importate correttamente Backup dei modelli OCR Importare Esportare Posizione Centro In alto a sinistra In alto a destra In basso a sinistra In basso a destra Centro in alto Centrodestra In basso al centro Centrosinistra Immagine di destinazione Trasferimento tavolozza Olio potenziato Vecchia TV semplice HDR Gotham Schizzo semplice Bagliore morbido Manifesto a colori Tritono Terzo colore Clahe Oklab Clara Olch Clahe Jzazbz A pois Dithering 2x2 in cluster Dithering 4x4 in cluster Dithering 8x8 in cluster Yililoma Dithering Nessuna opzione preferita selezionata, aggiungila nella pagina degli strumenti Aggiungi preferiti Complementare Analogo Triadico Complementare diviso Tetradico Piazza Analogo + Complementare Strumenti colore Mescola, crea toni, genera sfumature e altro ancora Armonie di colori Sfumatura di colore Variazione Tinte Toni Sfumature Miscelazione dei colori Informazioni sul colore Colore selezionato Colore da mescolare Non è possibile utilizzare Monet mentre i colori dinamici sono attivi 512x512 2D LUT Immagine LUT di destinazione Un dilettante Signorina Etichetta Eleganza morbida Variante Eleganza Morbida Variante di trasferimento tavolozza LUT 3D File LUT 3D di destinazione (.cube / .CUBE) LUT Bypass della candeggina Lume di candela Abbandona il blues Ambra tagliente Colori autunnali Filmato 50 Notte nebbiosa Kodak Ottieni un\'immagine LUT neutra Pop art Celluloide Caffè Foresta d\'Oro Verdastro Giallo retrò Anteprima dei collegamenti Abilita il recupero dell\'anteprima del collegamento nei luoghi in cui è possibile ottenere testo (QRCode, OCR ecc.) Collegamenti I file ICO possono essere salvati solo alla dimensione massima di 256 x 256 GIF in WEBP Converti immagini GIF in immagini animate WEBP Strumenti WEBP Converti immagini in immagini animate WEBP o estrai fotogrammi da una determinata animazione WEBP WEBP alle immagini Converti file WEBP in batch di immagini Converti batch di immagini in file WEBP Immagini su WEBP Scegli l\'immagine WEBP per iniziare Nessun accesso completo ai file Consenti l\'accesso a tutti i file per vedere JXL, QOI e altre immagini che non sono riconosciute come immagini su Android. Senza l\'autorizzazione Image Toolbox non è in grado di mostrare tali immagini Colore di disegno predefinito Modalità di tracciamento predefinita Aggiungi timestamp Abilita l\'aggiunta del timestamp al nome del file di output Timestamp formattato Abilita la formattazione del timestamp nel nome del file di output anziché nei millis di base Abilita Timestamp per selezionarne il formato Salvataggio della posizione una volta Visualizza e modifica le posizioni di salvataggio una tantum che puoi utilizzare premendo a lungo il pulsante di salvataggio nella maggior parte delle opzioni Usato di recente CI channel Gruppo Casella degli strumenti per immagini in Telegram 🎉 Unisciti alla nostra chat dove puoi discutere di tutto ciò che desideri e guarda anche il canale CI dove pubblico beta e annunci Ricevi notifiche sulle nuove versioni dell\'app e leggi gli annunci Adatta un\'immagine alle dimensioni specificate e applica sfocatura o colore allo sfondo Disposizione degli strumenti Raggruppare gli strumenti per tipo Raggruppa gli strumenti nella schermata principale in base al tipo anziché a una disposizione di elenchi personalizzata Valori predefiniti Visibilità delle barre di sistema Mostra le barre di sistema tramite scorrimento Consente lo scorrimento per mostrare le barre di sistema se sono nascoste Auto Nascondi tutto Mostra tutto Nascondi barra di navigazione Nascondi barra di stato Generazione di rumore Genera rumori diversi come Perlin o altri tipi Frequenza Tipo di rumore Tipo di rotazione Tipo frattale Ottave Lacunarità Guadagno Forza ponderata Forza del ping pong Funzione Distanza Tipo di reso Jitter Deformazione del dominio Allineamento Nome file personalizzato Seleziona la posizione e il nome del file che verranno utilizzati per salvare l\'immagine corrente Salvato nella cartella con nome personalizzato Creatore di collage Realizza collage contenenti fino a 20 immagini Tipo di collage Tieni l\'immagine per scambiare, spostare e ingrandire per regolare la posizione Disabilita rotazione Impedisce la rotazione delle immagini con i gesti con due dita Abilita l\'aggancio ai bordi Dopo lo spostamento o lo zoom, le immagini verranno agganciate per riempire i bordi della cornice Istogramma Istogramma dell\'immagine RGB o luminosità per aiutarti a apportare modifiche Questa immagine verrà utilizzata per generare istogrammi RGB e luminosità Opzioni Tesseract Applica alcune variabili di input per il motore tesseract Opzioni personalizzate Le opzioni devono essere inserite seguendo questo schema: \"--{nome_opzione} {valore}\" Ritaglio automatico Angoli liberi Ritaglia l\'immagine per poligono, questo corregge anche la prospettiva Coercizione dei punti sui limiti dell\'immagine I punti non saranno limitati dai limiti dell\'immagine, il che è utile per una correzione prospettica più precisa Maschera Riempimento consapevole del contenuto sotto il percorso disegnato Punto di guarigione Usa Circle Kernel Apertura Chiusura Gradiente morfologico Cappello a cilindro Cappello Nero Curve di tono Reimposta curve Le curve verranno ripristinate al valore predefinito Stile linea Dimensione spazio Tratteggiato Punto tratteggiato Timbrato Zigzag Disegna una linea tratteggiata lungo il percorso disegnato con la dimensione dello spazio specificata Disegna una linea punto e tratteggiata lungo il percorso indicato Solo linee rette predefinite Disegna le forme selezionate lungo il percorso con la spaziatura specificata Disegna zigzag ondulati lungo il percorso Rapporto a zigzag Crea collegamento Scegli lo strumento da appuntare Lo strumento verrà aggiunto alla schermata iniziale del programma di avvio come scorciatoia, utilizzalo in combinazione con l\'impostazione \"Salta selezione file\" per ottenere il comportamento necessario Non impilare i fotogrammi Abilita l\'eliminazione dei fotogrammi precedenti, in modo che non si accumulino l\'uno sull\'altro Dissolvenza incrociata I fotogrammi verranno sfumati l\'uno nell\'altro Conteggio dei fotogrammi di dissolvenza incrociata Soglia Uno Soglia Due Astuto Specchio 101 Sfocatura zoom migliorata Laplaciano semplice Sobel Semplice Griglia di aiuto Mostra la griglia di supporto sopra l\'area di disegno per facilitare le manipolazioni precise Colore della griglia Larghezza della cella Altezza della cella Selettori compatti Alcuni controlli di selezione utilizzeranno un layout compatto per occupare meno spazio Concedi l\'autorizzazione alla fotocamera nelle impostazioni per acquisire l\'immagine Disposizione Titolo della schermata principale Fattore di tasso costante (CRF) Un valore di %1$s indica una compressione lenta, che risulta in una dimensione del file relativamente piccola. %2$s significa una compressione più veloce, che risulta in un file di grandi dimensioni. Biblioteca Lut Scarica la raccolta di LUT, che puoi applicare dopo il download Aggiorna la raccolta di LUT (solo quelle nuove verranno messe in coda), che puoi applicare dopo il download Modifica l\'anteprima dell\'immagine predefinita per i filtri Anteprima immagine Nascondere Spettacolo Tipo di cursore Fantasia Materiale 2 Uno slider dall\'aspetto fantasioso. Questa è l\'opzione predefinita Un cursore Materiale 2 Uno slider Materiale Tu Fare domanda a Pulsanti della finestra di dialogo centrale I pulsanti delle finestre di dialogo verranno posizionati al centro anziché a sinistra, se possibile Licenze Open Source Visualizza le licenze delle librerie open source utilizzate in questa app Zona Ricampionamento utilizzando la relazione dell\'area dei pixel. Potrebbe essere il metodo preferito per la decimazione delle immagini, poiché fornisce risultati privi di effetto moiré. Ma quando l\'immagine viene ingrandita, è simile al metodo \"Più vicino\". Abilita la mappatura dei toni Inserisci % Impossibile accedere al sito, provare a utilizzare la VPN o verificare se l\'URL è corretto Livelli di marcatura Modalità livelli con possibilità di posizionare liberamente immagini, testo e altro Modifica livello Strati sull\'immagine Usa un\'immagine come sfondo e aggiungi diversi livelli sopra di essa Strati sullo sfondo Come la prima opzione, ma con il colore anziché l\'immagine Beta Lato Impostazioni veloci Aggiungi una striscia mobile sul lato selezionato durante la modifica delle immagini, che aprirà le impostazioni rapide quando si fa clic Cancella selezione Il gruppo di impostazioni \"%1$s\" verrà compresso per impostazione predefinita Il gruppo di impostazioni \"%1$s\" verrà espanso per impostazione predefinita Strumenti Base64 Decodifica la stringa Base64 in immagine o codifica l\'immagine in formato Base64 Base64 Il valore fornito non è una stringa Base64 valida Impossibile copiare una stringa Base64 vuota o non valida Incolla Base64 Copia Base64 Carica l\'immagine per copiare o salvare la stringa Base64. Se hai la stringa stessa, puoi incollarla sopra per ottenere l\'immagine Salva Base64 Condividi Base64 Opzioni Azioni Importa Base64 Azioni Base64 Aggiungi contorno Aggiungi un contorno attorno al testo con il colore e la larghezza specificati Colore contorno Dimensione del contorno Rotazione Checksum come nome file Le immagini di output avranno un nome corrispondente al checksum dei dati Software gratuito (partner) Software più utile nel canale partner delle applicazioni Android Algoritmo Strumenti per il checksum Confronta checksum, calcola hash o crea stringhe esadecimali da file utilizzando diversi algoritmi di hashing Calcolare Hash di testo Somma di controllo Scegli il file per calcolarne il checksum in base all\'algoritmo selezionato Inserisci il testo per calcolare il checksum in base all\'algoritmo selezionato Checksum della fonte Checksum per confrontare Incontro! Differenza I checksum sono uguali, può essere sicuro I checksum non sono uguali, il file può essere pericoloso! Gradienti della maglia Guarda la raccolta online di gradienti mesh È possibile importare solo i caratteri TTF e OTF Importa carattere (TTF/OTF) Esporta caratteri Caratteri importati Errore durante il tentativo di salvataggio, prova a cambiare la cartella di output Il nome del file non è impostato Nessuno Pagine personalizzate Selezione delle pagine Conferma uscita strumento Se sono presenti modifiche non salvate durante l\'utilizzo di strumenti particolari e provi a chiuderlo, verrà visualizzata la finestra di dialogo di conferma Modifica EXIF Modifica i metadati di una singola immagine senza ricompressione Tocca per modificare i tag disponibili Cambia adesivo Adatta larghezza Adatta all\'altezza Confronto batch Scegli uno o più file per calcolarne il checksum in base all\'algoritmo selezionato Scegli file Scegli Directory Scala della lunghezza della testa Timbro Timestamp Modello di formato Imbottitura Taglio dell\'immagine Taglia la parte dell\'immagine e unisci quelle di sinistra (può essere inverso) tramite linee verticali o orizzontali Linea pivot verticale Linea pivot orizzontale Selezione inversa La parte tagliata verticale verrà lasciata, invece di unire le parti attorno all\'area tagliata La parte tagliata orizzontale verrà lasciata, invece di unire le parti attorno all\'area tagliata Raccolta di gradienti di mesh Crea gradiente mesh con quantità personalizzata di nodi e risoluzione Sovrapposizione sfumatura mesh Componi il gradiente mesh della parte superiore delle immagini specificate Personalizzazione dei punti Dimensione della griglia Risoluzione X Risoluzione Y Risoluzione Pixel per pixel Evidenzia colore Tipo di confronto pixel Scansiona il codice a barre Rapporto altezza Tipo di codice a barre Applica B/N L\'immagine del codice a barre sarà completamente in bianco e nero e non sarà colorata in base al tema dell\'app Scansiona qualsiasi codice a barre (QR, EAN, AZTEC, …) e ottieni il suo contenuto o incolla il testo per generarne uno nuovo Nessun codice a barre trovato Il codice a barre generato sarà qui Copertine audio Estrai le immagini delle copertine degli album dai file audio, sono supportati i formati più comuni Scegli l\'audio per iniziare Scegli Audio Nessuna copertina trovata Invia registri Fare clic per condividere il file di registro dell\'app, questo può aiutarmi a individuare il problema e risolverlo Spiacenti… Qualcosa è andato storto Puoi contattarmi utilizzando le opzioni di seguito e cercherò di trovare una soluzione.\n(Non dimenticare di allegare i log) Scrivi su file Estrai il testo da un batch di immagini e memorizzalo in un unico file di testo Scrivi nei metadati Estrai il testo da ciascuna immagine e inseriscilo nelle informazioni EXIF ​​​​delle foto relative Modalità invisibile Usa la steganografia per creare filigrane invisibili agli occhi all\'interno dei byte delle tue immagini Usa LSB Verrà utilizzato il metodo steganografia LSB (Less Significant Bit), altrimenti FD (Frequency Domain) Rimuovi automaticamente gli occhi rossi Password Sbloccare Il PDF è protetto Operazione quasi completata. L\'annullamento ora richiederà il riavvio Data di modifica Data di modifica (invertita) Misurare Dimensioni (invertite) Tipo MIME Tipo MIME (invertito) Estensione Estensione (invertita) Data aggiunta Data di aggiunta (invertita) Da sinistra a destra Da destra a sinistra Dall\'alto verso il basso Dal basso verso l\'alto Vetro liquido Uno switch basato sull\'IOS 26 recentemente annunciato e sul suo sistema di progettazione del vetro liquido Scegli l\'immagine o incolla/importa i dati Base64 di seguito Digita il collegamento all\'immagine per iniziare Incolla collegamento Caleidoscopio Angolo secondario Lati Miscelazione dei canali Verde blu Rosso blu Verde rosso Nel rosso Nel verde Nel blu Ciano Magenta Giallo Mezzitoni a colori Contorno Livelli Voronoi cristallizza Forma Stirata Casualità Smacchiare Diffondere Cane Secondo raggio Pareggiare Incandescenza Gira e pizzica Puntare Coordinate polari Rettangolo al polare Polare per rettificare Invertire in cerchio Riduci il rumore Solarizzazione semplice Tessere X divario Divario Y Larghezza X Larghezza Y Girare Timbro di gomma Spalmare Densità Mescolare Distorsione della lente sferica Maria Autunno Osso Getto Inverno Oceano Estate Primavera Variante interessante HSV Rosa Caldo Parola Magma Inferno Plasma Viridis Cittadini Crepuscolo Crepuscolo spostato Prospettiva automatica Allineamento Consenti ritaglio Ritaglia o Prospettiva Assoluto Turbo Verde intenso Correzione dell\'obiettivo File del profilo dell\'obiettivo target in formato JSON Scarica i profili obiettivo pronti Percentuali di parte Esporta come JSON Copia una stringa con i dati di una tavolozza come rappresentazione JSON Intaglio della cucitura Schermata iniziale Schermata di blocco Integrato Esportazione di sfondi Aggiorna Ottieni gli sfondi attuali per la casa, il blocco e gli sfondi integrati Consenti l\'accesso a tutti i file, è necessario per recuperare gli sfondi L\'autorizzazione per gestire l\'archiviazione esterna non è sufficiente, devi consentire l\'accesso alle tue immagini, assicurati di selezionare \"Consenti tutto\" Aggiungi preimpostazione al nome file Aggiunge il suffisso con la preimpostazione selezionata al nome del file immagine Aggiungi la modalità scala immagine al nome file Aggiunge il suffisso con la modalità di scala dell\'immagine selezionata al nome del file dell\'immagine Arte Ascii Converti l\'immagine in testo ASCII che assomiglierà all\'immagine Param Applica il filtro negativo all\'immagine per ottenere risultati migliori in alcuni casi Schermata di elaborazione Screenshot non catturato, riprova Salvataggio saltato %1$s file ignorati Consenti Salta se più grande Ad alcuni strumenti sarà consentito saltare il salvataggio delle immagini se la dimensione del file risultante è maggiore dell\'originale Evento del calendario Contatto E-mail Posizione Telefono Testo sms URL Wifi Rete aperta N / A SSID Telefono Messaggio Indirizzo Soggetto Corpo Nome Organizzazione Titolo Telefoni E-mail URL Indirizzi Riepilogo Descrizione Posizione Organizzatore Data di inizio Data di fine Stato Latitudine Longitudine Crea codice a barre Modifica codice a barre Configurazione Wi-Fi Sicurezza Scegli il contatto Concedi ai contatti l\'autorizzazione nelle impostazioni per il riempimento automatico utilizzando il contatto selezionato Informazioni di contatto Nome di battesimo Secondo nome Cognome Pronuncia Aggiungi telefono Aggiungi e-mail Aggiungi indirizzo Sito web Aggiungi sito web Nome formattato Questa immagine verrà utilizzata per posizionare sopra il codice a barre Personalizzazione del codice Questa immagine verrà utilizzata come logo al centro del codice QR Logo Imbottitura logata Dimensioni del logo Angoli del logo Quarto occhio Aggiunge la simmetria dell\'occhio al codice QR aggiungendo il quarto occhio nell\'angolo inferiore Forma pixelata Forma del telaio Forma a palla Livello di correzione degli errori Colore scuro Colore chiaro Sistema operativo iper Xiaomi HyperOS come lo stile Modello di maschera Questo codice potrebbe non essere scansionabile, modificare i parametri di aspetto per renderlo leggibile con tutti i dispositivi Non scansionabile Gli strumenti assomiglieranno all\'avvio delle app della schermata iniziale per essere più compatti Modalità di avvio Riempie un\'area con il pennello e lo stile selezionati Riempimento Spray Disegna un percorso in stile graffito Particelle quadrate Le particelle dello spray saranno di forma quadrata invece che circolare Strumenti tavolozza Genera base/materiale per la tavolozza dall\'immagine o importa/esporta tra diversi formati di tavolozza Modifica tavolozza Esporta/importa tavolozza in vari formati Nome del colore Nome della tavolozza Formato tavolozza Esporta la tavolozza generata in diversi formati Aggiunge un nuovo colore alla tavolozza corrente Il formato %1$s non supporta l\'indicazione del nome della tavolozza A causa delle politiche del Play Store, questa funzionalità non può essere inclusa nella build attuale. Per accedere a questa funzionalità, scarica ImageToolbox da una fonte alternativa. Di seguito puoi trovare le build disponibili su GitHub. Apri la pagina Github Il file originale verrà sostituito con uno nuovo invece di essere salvato nella cartella selezionata Rilevato testo di filigrana nascosto Rilevata immagine filigrana nascosta Questa immagine era nascosta Pittura generativa Ti consente di rimuovere oggetti in un\'immagine utilizzando un modello AI, senza fare affidamento su OpenCV. Per utilizzare questa funzione, l\'app scaricherà il modello richiesto (~200 MB) da GitHub Ti consente di rimuovere oggetti in un\'immagine utilizzando un modello AI, senza fare affidamento su OpenCV. Potrebbe trattarsi di un\'operazione a lungo termine Analisi del livello di errore Gradiente di luminanza Distanza media Rilevamento spostamento copia Conservare Coefficiente I dati degli appunti sono troppo grandi I dati sono troppo grandi per essere copiati Pixelizzazione della trama semplice Pixelizzazione sfalsata Pixelizzazione incrociata Micro macro pixelizzazione Pixelizzazione orbitale Pixelizzazione del vortice Pixelizzazione della griglia di impulsi Pixelizzazione del nucleo Pixelizzazione della trama radiale Impossibile aprire l\'URI \"%1$s\" Modalità nevicata Abilitato Cornice di confine Variante glitch Cambio di canale VHS Blocco problema tecnico Dimensione del blocco Curvatura del cinescopio Curvatura Croma Pixel Fusione Caduta massima Strumenti di intelligenza artificiale Vari strumenti per elaborare le immagini attraverso modelli di intelligenza artificiale come la rimozione degli artefatti o la riduzione del rumore Compressione, linee frastagliate Cartoni animati, compressione delle trasmissioni Compressione generale, rumore generale Rumore incolore dei cartoni animati Veloce, compressione generale, rumore generale, animazione/fumetti/anime Scansione di libri Correzione dell\'esposizione Migliore per compressione generale, immagini a colori Migliore per compressione generale, immagini in scala di grigi Compressione generale, immagini in scala di grigi, più forti Rumore generale, immagini a colori Rumore generale, immagini a colori, dettagli migliori Rumore generale, immagini in scala di grigi Rumore generale, immagini in scala di grigi, più forte Rumore generale, immagini in scala di grigi, più forte Compressione generale Compressione generale Texturizzazione, compressione h264 Compressione VHS Non-standard compression (cinepak, msvideo1, roq) Compressione Bink, migliore sulla geometria Compressione Bink, più forte Compressione Bink, morbida, mantiene i dettagli Elimina l\'effetto scalino, levigante Grafica/disegni scansionati, leggera compressione, effetto moiré Bande di colore Lento, rimuovendo i mezzitoni Coloratore generale per immagini in scala di grigi/bianco e nero, per risultati migliori utilizzare DDColor Rimozione dei bordi Rimuove l\'eccessiva nitidezza Lento, esitante Anti-aliasing, artefatti generali, CGI KDM003 esegue la scansione dell\'elaborazione Modello leggero di miglioramento delle immagini Rimozione degli artefatti da compressione Rimozione degli artefatti da compressione Rimozione della benda con risultati uniformi Elaborazione del motivo mezzitoni Rimozione del motivo dithering V3 Rimozione artefatti JPEG V2 Miglioramento della trama H.264 Affilatura e miglioramento VHS Fusione Dimensione del pezzo Dimensioni sovrapposte Le immagini superiori a %1$s px verranno tagliate ed elaborate in blocchi, la sovrapposizione le fonde per evitare cuciture visibili. Le grandi dimensioni possono causare instabilità con i dispositivi di fascia bassa Selezionane uno per iniziare Vuoi eliminare il modello %1$s? Dovrai scaricarlo di nuovo Confermare Modelli Modelli scaricati Modelli disponibili Preparazione Modello attivo Impossibile aprire la sessione È possibile importare solo modelli .onnx/.ort Importa modello Importa il modello onnx personalizzato per un ulteriore utilizzo, sono accettati solo i modelli onnx/ort, supporta quasi tutte le varianti simili a esrgan Modelli importati Rumore generale, immagini colorate Rumore generale, immagini colorate, più forti Rumore generale, immagini colorate, più forte Riduce gli artefatti dovuti al dithering e le bande di colore, migliorando le sfumature uniformi e le aree di colore piatte. Migliora la luminosità e il contrasto dell\'immagine con luci bilanciate preservando i colori naturali. Schiarisce le immagini scure mantenendo i dettagli ed evitando la sovraesposizione. Rimuove l\'eccessivo viraggio del colore e ripristina un equilibrio cromatico più neutro e naturale. Applica la tonalità del rumore basata su Poisson ponendo l\'accento sulla conservazione dei dettagli e delle texture più fini. Applica una tonalità morbida del rumore Poisson per risultati visivi più fluidi e meno aggressivi. Tonalità del rumore uniforme focalizzata sulla conservazione dei dettagli e sulla chiarezza dell\'immagine. Tonificazione delicata e uniforme del rumore per una consistenza sottile e un aspetto liscio. Ripara le aree danneggiate o irregolari ridipingendo gli artefatti e migliorando la coerenza dell\'immagine. Modello di debandatura leggero che rimuove le bande di colore con costi prestazionali minimi. Ottimizza le immagini con artefatti di compressione molto elevati (qualità 0-20%) per una maggiore chiarezza. Migliora le immagini con artefatti ad alta compressione (qualità 20-40%), ripristinando i dettagli e riducendo il rumore. Migliora le immagini con una compressione moderata (qualità 40-60%), bilanciando nitidezza e morbidezza. Perfeziona le immagini con una leggera compressione (qualità 60-80%) per migliorare i dettagli e le texture sottili. Migliora leggermente le immagini quasi senza perdita di dati (qualità 80-100%) preservando l\'aspetto e i dettagli naturali. Colorazione semplice e veloce, cartoni animati, non ideale Riduce leggermente la sfocatura dell\'immagine, migliorando la nitidezza senza introdurre artefatti. Operazioni di lunga durata Elaborazione dell\'immagine Elaborazione Rimuove i pesanti artefatti di compressione JPEG in immagini di qualità molto bassa (0-20%). Riduce i forti artefatti JPEG nelle immagini altamente compresse (20-40%). Ripulisce gli artefatti JPEG moderati preservando i dettagli dell\'immagine (40-60%). Perfeziona gli artefatti JPEG leggeri in immagini di qualità piuttosto elevata (60-80%). Riduce leggermente gli artefatti JPEG minori in immagini quasi senza perdita di dati (80-100%). Esalta i dettagli e le texture più fini, migliorando la nitidezza percepita senza artefatti pesanti. Elaborazione terminata Elaborazione non riuscita Migliora le texture e i dettagli della pelle mantenendo un aspetto naturale, ottimizzato per la velocità. Rimuove gli artefatti di compressione JPEG e ripristina la qualità dell\'immagine per le foto compresse. Riduce il rumore ISO nelle foto scattate in condizioni di scarsa illuminazione, preservando i dettagli. Corregge le luci sovraesposte o \"jumbo\" e ripristina un migliore equilibrio tonale. Modello di colorazione leggero e veloce che aggiunge colori naturali alle immagini in scala di grigi. DEJPEG Eliminazione del rumore Colora Artefatti Migliorare Anime Scansioni Di lusso upscaler X4 per immagini generali; modello minuscolo che utilizza meno GPU e tempo, con deblur e denoise moderati. Upscaler X2 per immagini generali, preservando trame e dettagli naturali. Upscaler X4 per immagini generali con texture migliorate e risultati realistici. Upscaler X4 ottimizzato per immagini anime; 6 blocchi RRDB per linee e dettagli più nitidi. L\'upscaler X4 con perdita MSE, produce risultati più uniformi e artefatti ridotti per immagini generali. X4 Upscaler ottimizzato per immagini anime; Variante 4B32F con dettagli più nitidi e linee morbide. modello X4 UltraSharp V2 per immagini generali; enfatizza la nitidezza e la chiarezza. X4 UltraSharp V2 Lite; più veloce e più piccolo, preserva i dettagli utilizzando meno memoria GPU. Modello leggero per una rapida rimozione dello sfondo. Prestazioni e precisione bilanciate. Funziona con ritratti, oggetti e scene. Consigliato per la maggior parte dei casi d\'uso. Rimuovi BG Spessore del bordo orizzontale Spessore del bordo verticale %1$s colore %1$s colori Il modello attuale non supporta il suddivisione in blocchi, l\'immagine verrà elaborata nelle dimensioni originali, ciò potrebbe causare un elevato consumo di memoria e problemi con i dispositivi di fascia bassa Chunking disabilitato, l\'immagine verrà elaborata nelle dimensioni originali, ciò potrebbe causare un elevato consumo di memoria e problemi con i dispositivi di fascia bassa ma potrebbe fornire risultati migliori nell\'inferenza Spezzatura Modello di segmentazione delle immagini ad alta precisione per la rimozione dello sfondo Versione leggera di U2Net per una rimozione dello sfondo più rapida con un utilizzo ridotto della memoria. Il modello DDColor completo offre una colorazione di alta qualità per immagini generiche con artefatti minimi. La scelta migliore tra tutti i modelli di colorazione. DDColor Set di dati artistici formati e privati; produce risultati di colorazione diversi e artistici con meno artefatti cromatici non realistici. Modello BiRefNet leggero basato su Swin Transformer per una rimozione accurata dello sfondo. Rimozione dello sfondo di alta qualità con bordi netti ed eccellente conservazione dei dettagli, soprattutto su oggetti complessi e sfondi difficili. Modello di rimozione dello sfondo che produce maschere accurate con bordi smussati, adatte per oggetti generici e conservazione moderata dei dettagli. Modello già scaricato Modello importato con successo Tipo Parola chiave Molto veloce Normale Lento Molto lento Calcola percentuali Il valore minimo è %1$s Distorcere l\'immagine disegnando con le dita Ordito Durezza Modalità di deformazione Mossa Crescere Restringersi Vortice in senso orario Girare in senso antiorario Forza della dissolvenza Goccia in alto Goccia dal basso Inizia a rilasciare Fine Goccia Scaricamento in corso Forme morbide Utilizza le superellissi invece dei rettangoli arrotondati standard per forme più morbide e naturali Tipo di forma Taglio Arrotondato Liscio Spigoli vivi senza arrotondamenti Angoli arrotondati classici Tipo di forme Dimensioni degli angoli Squircle Eleganti elementi dell\'interfaccia utente arrotondati Formato nome file Testo personalizzato posizionato all\'inizio del nome file, perfetto per nomi di progetti, marchi o tag personali. Utilizza il nome del file originale senza estensione, aiutandoti a mantenere intatta l\'identificazione della fonte. La larghezza dell\'immagine in pixel, utile per tenere traccia delle modifiche alla risoluzione o del ridimensionamento dei risultati. L\'altezza dell\'immagine in pixel, utile quando si lavora con proporzioni o esportazioni. Genera cifre casuali per garantire nomi di file univoci; aggiungi più cifre per una maggiore sicurezza contro i duplicati. Contatore con incremento automatico per esportazioni batch, ideale quando si salvano più immagini in un\'unica sessione. Inserisce il nome della preimpostazione applicata nel nome del file in modo da poter ricordare facilmente come è stata elaborata l\'immagine. Visualizza la modalità di ridimensionamento dell\'immagine utilizzata durante l\'elaborazione, aiutando a distinguere le immagini ridimensionate, ritagliate o adattate. Testo personalizzato posizionato alla fine del nome file, utile per il controllo delle versioni come _v2, _edited o _final. L\'estensione del file (png, jpg, webp, ecc.), che corrisponde automaticamente al formato effettivamente salvato. Un timestamp personalizzabile che ti consente di definire il tuo formato in base alle specifiche Java per un ordinamento perfetto. Tipo di lancio Android nativo Stile iOS Curva liscia Arresto rapido Rimbalzante Galleggiante Scattante Ultra liscio Adattivo Accessibilità consapevole Movimento ridotto Fisica di scorrimento nativa di Android Scorrimento bilanciato e fluido per uso generale Comportamento di scorrimento simile a iOS con maggiore attrito Curva spline unica per una sensazione di scorrimento distinta Scorrimento preciso con arresto rapido Scorrimento rimbalzante giocoso e reattivo Pergamene lunghe e scorrevoli per la navigazione dei contenuti Scorrimento rapido e reattivo per interfacce utente interattive Scorrimento fluido premium con slancio esteso Regola la fisica in base alla velocità di lancio Rispetta le impostazioni di accessibilità del sistema Movimento minimo per esigenze di accessibilità Linee primarie Aggiunge una linea più spessa ogni quinta linea Colore riempimento Strumenti nascosti Strumenti nascosti per la condivisione Libreria dei colori Sfoglia una vasta collezione di colori Rende più nitidi e rimuove la sfocatura dalle immagini mantenendo i dettagli naturali, ideale per correggere le foto sfocate. Ripristina in modo intelligente le immagini che sono state precedentemente ridimensionate, recuperando dettagli e texture perdute. Ottimizzato per contenuti live-action, riduce gli artefatti di compressione e migliora i dettagli più fini nei fotogrammi di film/programmi TV. Converte filmati di qualità VHS in HD, rimuovendo il rumore del nastro e migliorando la risoluzione preservando l\'atmosfera vintage. Specializzato per immagini e screenshot con molto testo, rende più nitidi i caratteri e migliora la leggibilità. Upscaling avanzato addestrato su diversi set di dati, eccellente per il miglioramento fotografico generico. Ottimizzato per foto compresse sul Web, rimuove gli artefatti JPEG e ripristina l\'aspetto naturale. Versione migliorata per le foto web con migliore conservazione della texture e riduzione degli artefatti. Upscaling 2x con tecnologia Dual Aggregation Transformer, mantiene nitidezza e dettagli naturali. Upscaling 3x utilizzando un\'architettura avanzata del trasformatore, ideale per esigenze di ingrandimento moderate. Upscaling 4x di alta qualità con rete di trasformatori all\'avanguardia, preserva i dettagli più fini su scale più grandi. Rimuove sfocature/rumore e vibrazioni dalle foto. Scopo generale ma migliore per le foto. Ripristina immagini di bassa qualità utilizzando il trasformatore Swin2SR, ottimizzato per il degrado BSRGAN. Ottimo per correggere artefatti da compressione pesante e migliorare i dettagli su scala 4x. Upscaling 4x con trasformatore SwinIR addestrato sulla degradazione BSRGAN. Utilizza GAN per texture più nitide e dettagli più naturali in foto e scene complesse. Sentiero Unisci PDF Combina più file PDF in un unico documento Ordine dei file pag. PDF diviso Estrai pagine specifiche dal documento PDF Ruota PDF Correggi l\'orientamento della pagina in modo permanente Pagine Riorganizzare il PDF Trascina e rilascia le pagine per riordinarle Tieni premuto e trascina le pagine Numeri di pagina Aggiungi automaticamente la numerazione ai tuoi documenti Formato etichetta Da PDF a testo (OCR) Estrai testo semplice dai tuoi documenti PDF Sovrapponi testo personalizzato per il branding o la sicurezza Firma Aggiungi la tua firma elettronica a qualsiasi documento Questo verrà utilizzato come firma Sblocca PDF Rimuovi le password dai tuoi file protetti Proteggi PDF Proteggi i tuoi documenti con una crittografia avanzata Successo PDF sbloccato, puoi salvarlo o condividerlo Ripara PDF Tentare di correggere documenti danneggiati o illeggibili Scala di grigi Converti tutte le immagini incorporate nel documento in scala di grigi Comprimi PDF Ottimizza le dimensioni del file del tuo documento per una condivisione più semplice ImageToolbox ricostruisce la tabella dei riferimenti incrociati interna e rigenera la struttura dei file da zero. Ciò può ripristinare l\'accesso a molti file che \\"non possono essere aperti\\" Questo strumento converte tutte le immagini del documento in scala di grigi. Ideale per stampare e ridurre le dimensioni del file Metadati Modifica le proprietà del documento per una migliore privacy Tag Produttore Autore Parole chiave Creatore Privacy Pulizia profonda Cancella tutti i metadati disponibili per questo documento Pagina OCR profondo Estrai il testo dal documento e memorizzalo in un unico file di testo utilizzando il motore Tesseract Impossibile rimuovere tutte le pagine Rimuovere le pagine PDF Rimuovi pagine specifiche dal documento PDF Tocca per rimuovere Manualmente Ritaglia PDF Ritaglia le pagine del documento fino a qualsiasi limite Appiattisci PDF Rendi il PDF immodificabile rasterizzando le pagine del documento Impossibile avviare la fotocamera. Controlla le autorizzazioni e assicurati che non sia utilizzata da un\'altra app. Estrai immagini Estrai le immagini incorporate nei PDF alla loro risoluzione originale Questo file PDF non contiene immagini incorporate Questo strumento esegue la scansione di ogni pagina e recupera immagini originali di qualità completa, perfette per salvare gli originali dai documenti Disegna firma Parametri penna Utilizza la propria firma come immagine da inserire sui documenti PDF zippato Dividi il documento con un determinato intervallo e inserisci i nuovi documenti nell\'archivio zip Intervallo Stampa PDF Preparare il documento per la stampa con dimensioni di pagina personalizzate Pagine per foglio Orientamento Dimensioni della pagina Margine Fioritura Ginocchio morbido Ottimizzato per anime e cartoni animati. Upscaling veloce con colori naturali migliorati e meno artefatti Stile simile a Samsung One UI 7 Inserisci qui i simboli matematici di base per calcolare il valore desiderato (ad esempio (5+5)*10) Espressione matematica Scegli fino a %1$s immagini Mantieni data e ora Conserva sempre i tag EXIF ​​relativi a data e ora, funziona indipendentemente dall\'opzione Mantieni EXIF Colore di sfondo per i formati Alpha Aggiunge la possibilità di impostare il colore di sfondo per ogni formato immagine con supporto alpha, quando disabilitato è disponibile solo per quelli non alpha Apri progetto Continua a modificare un progetto Image Toolbox salvato in precedenza Impossibile aprire il progetto Image Toolbox Nel progetto Image Toolbox mancano i dati del progetto Il progetto Image Toolbox è danneggiato Versione del progetto Image Toolbox non supportata: %1$d Salva progetto Memorizza livelli, sfondo e cronologia delle modifiche in un file di progetto modificabile Impossibile aprire Scrivi su PDF ricercabile Riconosci il testo da un batch di immagini e salva PDF ricercabili con immagine e livello di testo selezionabile Livello alfa Capovolgimento orizzontale Capovolgimento verticale Serratura Aggiungi ombra Colore dell\'ombra Geometria del testo Allunga o inclina il testo per una stilizzazione più nitida Scala X Inclina X Rimuovi le annotazioni Rimuovi i tipi di annotazioni selezionati come collegamenti, commenti, evidenziazioni, forme o campi modulo dalle pagine PDF Collegamenti ipertestuali Allegati file Linee Popup Francobolli Forme Note di testo Markup del testo Campi del modulo Markup Sconosciuto Annotazioni Separa Aggiungi ombra sfocata dietro il livello con colori e offset configurabili ================================================ FILE: core/resources/src/main/res/values-iw/strings.xml ================================================ חריג ערוך EXIF שמור בחר צבע מהתמונה, העתק או שתף תמונה לא ניתן לשנות את ערכת הצבעים של האפליקציה בזמן שצבעים דינמיים מופעלים עיצוב האפליקציה יתבסס על צבע, אותו תבחר אודות האפליקציה שתף משהו השתבש: %1$s גודל %1$s טוען… התמונה גדולה מדי לתצוגה מקדימה, אך אנסה לשמור אותה בכל זאת בחר תמונה כדי להתחיל רוחב %1$s האפליקציה נסגרת להישאר סגור גובה %1$s איכות סיומת סוג שיטת שינוי הגודל מפורש גמיש בחר תמונה האם אתה רוצה לסגור את האפליקציה? אפס תמונה שינויים בתמונה יוחזרו לערכים הראשונים ערכים מאופסים כראוי איפוס משהו השתבש הפעל את האפליקציה מחדש הועתק בסדר לא נמצאו נתוני EXIF הוסף תגית נקה נקה EXIF ביטול כל נתוני ה-EXIF של התמונה יימחקו, פעולה זו לא ניתנת לביטול! הגדרות קבועות מראש חיתוך שומר כל השינויים שלא נשמרו יאבדו אם תעזוב עכשיו קוד מקור קבל את העדכונים האחרונים, התחל לדון בבעיות ועוד עריכה בודדת התאם, שנה גודל וערוך תמונה בודדת בוחר צבעים צבע צבע הועתק חתוך תמונה לכל גבול גירסא שמור EXIF תמונות: %d שנה תצוגה מקדימה הסר צור דוגמית פלטת צבעים מתמונה נתונה צור פלטת צבעים לוח צבעים עדכון גרסא חדשה %1$s סוג לא נתמך: %1$s לא ניתן ליצור פלטת צבעים עבור תמונה נתונה מקורי תיקיית שמירה ברירת מחדל מותאם אישית לא מוגדר אחסון מכשיר שנה גודל לפי משקל גודל מקסימלי ב-KB שנה גודל תמונה בהתאם לגודל נתון ב-KB השוואה השווה בין שתי תמונות בחר שתי תמונות כדי להתחיל בחר תמונות הגדרות מצב לילה כהה בהיר מערכת צבעים דינמיים התאמה אישית אפשר תמונה של מונט אם אפשרות זו מופעלת, כאשר תבחר תמונה לעריכה, צבעי האפליקציה יאומצו לתמונה זו שפה מצב אמולד אם מופעל, צבע המשטחים יוגדר לשחור מוחלט במצב לילה סכמת צבעים אדום ירוק כחול הדבק קוד צבע aRGB חוקי. אין מה להדביק לא נמצאו עדכונים עוקב אחר בעיות שלח לכאן דוחות באגים ובקשות לתכונות חדשות שמירת התמונות %d נכשלה עזרה בתרגום תקן טעויות תרגום או התאם את הפרויקט לשפות אחרות שום דבר לא נמצא על ידי השאילתה שלך חפש כאן אם מופעל, אז צבעי האפליקציה יאומצו לצבעי טפט ראשון שלישי משני עובי גבול משטח ערכים הוסף הרשאה הענק האפליקציה צריכה גישה לאחסון שלך כדי לשמור תמונות, זה הכרחי, בלי זה היא לא יכולה לעבוד, נא הענק הרשאה בתיבת הדו-שיח הבאה האפליקציה זקוקה להרשאה זו כדי לעבוד, אנא הענק אותה באופן ידני אחסון חיצוני צבעי מונה אפליקציה זו חינמית לחלוטין, אך אם ברצונך לתמוך בפיתוח הפרויקט, תוכלו ללחוץ כאן יישור FAB חפש עדכונים אם מופעל, תיבת דו-שיח עדכון תוצג לך לאחר הפעלת האפליקציה זום תמונה קידומת שם קובץ אלפא לצבוע מונוכרום גמא בהירות והצללות עיקרי הדברים הצללות ערפל חום כהה הבלטה מחק EXIF אין תמונה סינון מסנן צבע חשיפה שלילי חיוניות הצלבה רוחב קו טישטוש התחלה מטריצת צבע שינוי גודל לפי גבולות שנה את גודל התמונות לפי הרוחב והגובה תוך שמירת היחס המקורי סקיצה מפתן רמות קוונטיזציה טון חלק טון דיכוי לא מקסימלי הכללת פיקסלים חלשה הבט מעלה טשטוש ערימה קונבולוציה 3x3 צבע ראשון צבע שני סדר מחדש טשטוש מהיר סף בהירות הוסף גודל קובץ אם מופעל מוסיף רוחב וגובה של התמונה השמורה לשם ה קובץ אימוג\'י בחר איזה אמוג\'י יוצג במסך הראשי מחק מטא נתונים של EXIF מכל סט תמונות תצוגה מקדימה של תמונה תצוגה מקדימה של כל סוג של תמונות: GIF, SVG וכן הלאה מקור תמונה בוחר תמונות גלריה סייר קבצים בוחר התמונות המודרני של אנדרואיד המופיע בתחתית המסך, עשוי לעבוד רק על אנדרואיד 12+ ויש לו גם בעיות בקבלת מטא נתונים של EXIF הסדר אפשרויות ערוך סדר ספירת אמוג\'ים טען תמונה מהרשת טען כל תמונה מהאינטרנט, הצג אותה בתצוגה מקדימה, הגדל אותה, וגם שמור או ערוך אותה אם תרצה קישור לתמונה למלא התאם משנה תמונות לפי ערכי הגובה והרוחב הנבחרים. עשוי לשנות את יחס הגובה והרוחב. משנה את גודל התמונות לתמונות עם צד ארוך כלשהו לפי פרמטר רוחב או גובה, כל חישובי הגודל יבוצעו לאחר השמירה - שומר על יחס רוחב-גובה בהירות ניגודיות צבע רוויה הוסף מסנן החל שרשרת פילטרים על תמונות מסננים מואר לבן מאוזן טמפרטורה אפקט מרחק מדרון חידוד סולריזציה שחור ולבן מרווחים קצה סובל חצי טון מרחב הצבעים של GCA טישטוש גאוסיאני תיבת טשטוש טשטוש דו צדדי לאפלסיאן וינייט סוף החלקת קוואהרה רדיוס קנה מידה עיוות זווית ערבוב בליטות הרחבה שבירה של כדור מקדם השבירה שבירה של כדור זכוכית אטימות פוסטר מסנן RGB צבע מזויף גודל טשטוש מרכז טשטוש x מרכז טשטוש y טשטוש זום איזון צבע בוחר תמונות פשוט של גלריה, זה יעבוד רק אם יש לך את האפליקציה הזו השתמש בכוונה של GetContent לבחירת תמונה, עובד בכל מקום, אבל יכול להיות גם בעיות בקבלת תמונות שנבחרו במכשירים מסוימים, זו לא אשמתי קבע את סדר הכלים במסך הראשי סולם תוכן sequenceNum שם הקובץ המקורי הוסף שם קובץ מקורי אם מופעל מוסיף שם קובץ מקורי בשם של התמונה שנערכה החלף את מספר הרצף אם מופעל מחליף חותמת זמן סטנדרטית למספר רצף התמונה אם אתה משתמש בעיבוד אצווה הוספת שם קובץ מקורי לא עובדת אם נבחר מקור תמונה בבורר התמונות אפשרות גיבוי הקובץ בעיבוד ערוך צילום מסך עותק דלג השבתת את אפליקציית הקבצים, הפעל אותה כדי להשתמש בתכונה זו צייר צייר על תמונה כמו בספר סקיצות, או צייר על הרקע עצמו צבע הצבע צבע אלפא צייר על תמונה בחר תמונה וצייר עליה משהו צייר על רקע בחר צבע רקע וצייר עליו תכונות יישום תאימות ‌ניסיון לשמור תמונה עם רוחב וגובה נתונים עלול לגרום לשגיאת זיכרון מלא, עשה זאת על אחריותך בלבד. מטמון נמצא %1$s אם מופעלת מטמון האפליקציה ינוקה בעת הפעלת האפליקציה כלים קבץ אפשרויות לפי סוג אפשרויות קבוצות במסך הראשי של סוגן במקום סידור רשימה מותאם אישית לא ניתן לשנות סידור בזמן שקיבוץ אפשרויות מופעל התאמה אישית משנית צילום מסך סיסמה לא חוקית או קובץ שנבחר אינו מוצפן הצפן הצפנה מבוססת סיסמה של קבצים. ניתן לאחסן קבצים שהמשיכו בספרייה הנבחרת או לשתף. ניתן גם לפתוח ישירות קבצים מפוענחים. גודל המטמון פענח גודל הקובץ שמירה במצב %1$s עלולה להיות לא יציבה, מכיוון שהיא פורמט ללא אובדן צבע רקע אחסן את הקובץ הזה במכשיר שלך או השתמש בכפתור השיתוף כדי לשתף אותו לכל מקום שתרצה גודל הקובץ המרבי מוגבל על ידי מערכת ההפעלה אנדרואיד והזיכרון הזמין, וזה כמובן תלוי במכשיר שלך. \nשימו לב: הזיכרון אינו אחסון. ניקוי מטמון אוטומטי הצפנה הצפנה ופענוח של כל קובץ (לא רק תמונה) בהתבסס על אלגוריתמי קריפטו שונים זמינים בחר קובץ בחר קובץ כדי להתחיל פענוח הצפנה מפתח AES-256, מצב GCM, ללא ריפוד, IVs אקראיים של 12 בתים. (כברירת מחדל, אך ניתן לבחור את האלגוריתם הרצוי) מפתחות משמשים כ-hashes של SHA-3 (256 סיביות). שים לב שתאימות לתוכנות או שירותים אחרים להצפנת קבצים אינה מובטחת. טיפול מפתח או תצורת צופן שונה במקצת עשויות להיות סיבות לאי התאמה. צור עדכונים צא\'ט טלגרם טבע וחיות המתן אוכל ושתייה צור קשר קובץ פגום או שאינו גיבוי בחר לפחות את 2 תמונות שחזור שחזור תמונה מחק נשמר בתיקייה %1$s ההגדרות שוחזרו בהצלחה גיבוי ושחזור גיבוי שמור ל קובץ %1$s ע\"פ שם %2$s אנליטיקס מאמץ חפצים צור שם קובץ אקראי טקסט ברירת מחדל גופן תרום פיקסלים משופרים פיקסלים של יהלום חיתוך מסכה שחזר את הגדרות האפליקציה מקובץ שנוצר בעבר חתוך תמונה Side Fade חלק עליון תחתית כוח רכות מברשת פעילויות א ב ג ד ה ו ז ח ט י כ ל מ נ ס ע פ צ ק ר ש ת 0123456789 !? פיפטה דיווח בעיה כיוון & זיהוי סקריפט בלבד זיהוי סקריפט & אוטומטי כיוון טקסט דליל & זיהוי סקריפט תקלה כמות זרע אנגליף רגע מיון פיקסל עירבוב אתה עומד למחוק את מסכת המסנן שנבחרה. לא ניתן לבטל פעולה זו מחק מסכה דראגו אולדריג\' לחתוך לא נמצאה ספריה \"%1$s\", החלפנו אותה לברירת מחדל, נא לשמור את הקובץ שוב לוח העתקה הצמדה אוטומטית רטט חוזק רטט החלף קבצים חיפוש מאפשר חיפוש בכל הכלים הזמינים במסך הראשי חינם תמונות שהוחלפו ביעד המקורי לא ניתן לשנות את פורמט התמונה כאשר אפשרות החלפת קבצים מופעלת אמוג\'י בתור ערכת צבעים אם בחרת בהגדרה 125, התמונה תישמר בגודל של 125% מהתמונה המקורית. אם תבחר בהגדרה 50, התמונה תישמר בגודל של 50%. הגדרה מראש כאן קובעת את % מקובץ הפלט, כלומר אם תבחר הגדרה מראש 50 בתמונה של 5MB אז תקבל תמונה של 2.5MB לאחר השמירה השתמש בסוג המסכה הזה כדי ליצור מסכה מהתמונה הנתונה, שימו לב שהיא אמורה להיות בעלת ערוץ אלפא גבה את הגדרות האפליקציה שלך לקובץ זה יחזיר את ההגדרות שלך לערכי ברירת המחדל. שימו לב שלא ניתן לבטל זאת ללא קובץ גיבוי שהוזכר לעיל. אתה עומד למחוק את ערכת הצבעים שנבחרה. לא ניתן לבטל פעולה זו סולם גופנים שימוש בקנה מידה גדול של גופנים עלול לגרום לתקלות ובעיות בממשק המשתמש, אשר לא יתוקנו. השתמש בזהירות. רגשות סמלים מסעות ומקומות מסיר הרקע הסר רקע מהתמונה על ידי ציור או השתמש באפשרות אוטומטי מטא נתונים של התמונה המקורית יישמרו רווחים שקופים סביב התמונה יחתכו מחיקה אוטומטית של רקע מצב מחיקה מחק רקע שחזר רקע רדיוס טשטוש מצב ציור אופס… משהו השתבש. אתה יכול לכתוב לי באמצעות האפשרויות למטה ואנסה למצוא פתרון. שנה גודל והמר שנה גודל של תמונות נתונות או המר אותן לפורמטים אחרים. ניתן לערוך כאן מטא נתונים של EXIF גם אם בוחרים תמונה בודדת. זה מאפשר לאפליקציה לאסוף דוחות קריסה באופן אוטומטי אפשר איסוף סטטיסטיקות שימוש אנונימיות באפליקציה נכון לעכשיו, פורמט %1$s מאפשר קריאת מטא נתונים של EXIF רק ב-Android. לתמונת פלט לא יהיו מטא נתונים כלל, כאשר היא נשמרת. ערך של %1$s פירושו דחיסה מהירה, וכתוצאה מכך גודל קובץ גדול יחסית. %2$s פירושו דחיסה איטית יותר, וכתוצאה מכך קובץ קטן יותר. אפשר בטא בדיקת העדכונים תכלול גרסאות של אפליקציות בטא אם מופעלת צייר חצים אם מופעל נתיב הציור יוצג כחץ מצביע פיקסלציה של מעגל סובלנות צבע להחלפה צבע יעד נאמנות תוכן סגנון פלטת ברירת המחדל, זה מאפשר להתאים אישית את כל ארבעת הצבעים, אחרים מאפשרים לך להגדיר רק את צבע המפתח סגנון קצת יותר כרומטי מאשר מונוכרום נושא רועש, הצבעוניות היא מקסימלית עבור פלטת ראשי, מוגברת עבור אחרים ערכת נושא שובבה - הגוון של צבע המקור אינו מופיע בערכת הנושא נושא מונוכרום, הצבעים הם אך ורק שחור/לבן/אפור סכימה שממקמת את צבע המקור ב- Scheme.primaryContainer סוג מילוי הפוך אם מופעל, כל האזורים הלא-מסוכים יסוננו במקום התנהגות ברירת המחדל סרגלי אפליקציה צייר צל מאחורי סרגלי אפליקציה מצייר נתיב כערך קלט מצייר נתיב מנקודת התחלה לנקודת סיום בתור קו מצייר חץ מצביע מנקודת התחלה לנקודת סיום בתור קו מצייר ישר מנקודת התחלה לנקודת סיום מצייר אליפסה מנקודת התחלה לנקודת סיום מצייר אליפסה עם קווי מתאר מנקודת ההתחלה לנקודת הסיום מצייר ישר מתואר מנקודת ההתחלה לנקודת הסיום מוסיף אוטומטית תמונה שנשמרה ללוח אם מופעל הקובץ המקורי יוחלף בחדש במקום לשמור בתיקייה שנבחרה, אפשרות זו צריכה להיות מקור התמונה \"Explorer\" או GetContent, כאשר מחליפים את זה, היא תוגדר אוטומטית כדי להחליף קבצים אתה צריך להשתמש במקור התמונה של \"סייר\", נסה לבחור תמונות מחדש, שינינו את מקור התמונה למקור הדרוש ריק סִיוֹמֶת אינטרפולציה ליניארית (או בילינארית, בשני מימדים) טובה בדרך כלל לשינוי גודל תמונה, אך גורמת לריכוך לא רצוי של פרטים ועדיין יכולה להיות מעט משוננים שיטות קנה מידה טובות יותר כוללות דגימה מחדש של Lanczos ומסנני Mitchell-Netravali אחת הדרכים הפשוטות יותר להגדיל את הגודל, החלפת כל פיקסל במספר פיקסלים מאותו צבע מצב קנה המידה הפשוט ביותר של אנדרואיד שהיה בשימוש כמעט בכל האפליקציות שיטה לאינטרפולציה חלקה ודגימה מחדש של קבוצה של נקודות בקרה, נפוץ בגרפיקה ממוחשבת ליצירת עקומות חלקות פונקציית חלונות מיושמת לעתים קרובות בעיבוד אותות כדי למזער דליפה ספקטרלית ולשפר את הדיוק של ניתוח תדרים על ידי הקטנת הקצוות של האות טכניקת אינטרפולציה מתמטית המשתמשת בערכים ובנגזרות בנקודות הקצה של קטע עקומה כדי ליצור עקומה חלקה ורציפה שיטת דגימה מחדש השומרת על אינטרפולציה איכותית על ידי החלת פונקציית sinc משוקללת על ערכי הפיקסלים שיטת דגימה חוזרת המשתמשת במסנן קונבולוציה עם פרמטרים מתכווננים כדי להשיג איזון בין חדות ל-anti-aliasing בתמונה המוקטנת אפשר מספר שפות אוטומטי בלבד אוטומטי טור יחיד טקסט אנכי בלוק בודד בלוק בודד שורה בודדת מילה בודדת עיגול מילה פח בודד טקסט דליל קו גולמי האם ברצונך למחוק נתוני אימון OCR בשפה \"%1$s\" עבור כל סוגי הזיהוי, או רק עבור אחד נבחר (%2$s)? כיסוי תמונות עם סימני מים ניתנים להתאמה אישית של טקסט/תמונה חוזר על סימן מים על פני תמונה במקום יחיד במיקום נתון היסט X קיזוז Y סוג סימן מים סיירה לייט דיטה אטקינסון דיטה Stucki Dithering משתמש בפונקציות פולינום דו-קוביות המוגדרות באופן חלקי כדי לבצע אינטרפולציה חלקה ולקירוב של עקומה או משטח, ייצוג צורה גמיש ורציף תקלה משופרת ערוץ Shift X משמרת ערוץ Y גודל שחיתות צד משמרת שחיתות X משמרת שחיתות Y טשטוש אוהל דאוטרנומליה פרוטנומליה בָּצִיר בראוני קודה כרום ראיית לילה נעים מגניב פרוטנופיה אכרומטומיה פסטל אובך כתום חלום ורוד שעת הזהב קיץ חם אור קפיץ רך צלילי סתיו לבנדר חלום סייברפאנק לימונדה אור אש ספקטרלית קסם לילה נוף פנטזיה עולם הקשת אוצ\'ימורה מוביוס מעבר שִׂיא אנומליה בצבע משתמש בצבע עיקרי של אמוג\'י כסכמת צבעי אפליקציה במקום אחד שהוגדר ידנית דון באפליקציה וקבל משוב ממשתמשים אחרים. אתה יכול גם לקבל עדכוני בטא ותובנות כאן. אפשר אמוג\'י פיקסלציה משופרת של יהלום פיקסלים עיגולים משופרים צבע להסרה הסר צבע קידוד מחדש לשחוק דיפוזיה אניזוטרופית ריכוך הולכה חשמלית מתנודד רוח אופקי טשטוש דו צדדי מהיר טשטוש פויסון מיפוי טון לוגריתמי לגבש צבע שבץ זכוכית פרקטל אמפליטודה שיש מערבולת שמן אפקט המים תדר X גודל תדר Y משרעת X משרעת Y פרלין דיסטורשן Hable Filmic Tone Mipping מיפוי טון הייל בורגס מיפוי טון סרטי ACES מיפוי הטון של ACES Hill נוֹכְחִי את כל אימייל מסנן מלא התחלה מרכז סוף החל כל שרשראות סינון על תמונות נתונות או תמונה בודדת פעל עם קבצי PDF: תצוגה מקדימה, המר לקבוצת תמונות או צור אחת מתמונות נתונות תצוגה מקדימה של PDF PDF לתמונות תמונות ל-PDF תצוגה מקדימה פשוטה של PDF המר PDF לתמונות בפורמט פלט נתון ארוז תמונות שניתנו לקובץ PDF פלט יוצר צבע צור שיפוע של גודל פלט נתון עם צבעים וסוג מראה מותאמים אישית מְהִירוּת מעורפל אוֹמֶגָה כלי PDF דרג אפליקציה ציון האפליקציה הזו חינמית לחלוטין, אם אתה רוצה שהיא תהפוך לגדולה יותר, נא לככב את הפרויקט ב- Github 😄 צבע מטריקס 4x4 מטריצת צבע 3x3 אפקטים פשוטים פולארויד טריטנומליה טריטנופיה דאוטרנופיה אכרומטופיה ליניארי רדיאלי לטאטא סוג שיפוע מרכז X מרכז Y מצב אריחים חוזר על עצמו מראה מהדק מדבקות עצירות צבע הוסף צבע נכסים Lasso מצייר נתיב מלא סגור לפי נתיב נתון מצב ציור נתיב חץ קו כפול ציור חינם חץ כפול חץ קו חץ קו מצייר חץ מצביע מנתיב נתון מצייר חץ מצביע כפול מנקודת התחלה לנקודת סיום בתור קו מצייר חץ מצביע כפול מנתיב נתון סגלגל מתואר מתואר רקט סגלגל רקט התרפקות קוונטיזיר סולם אפור באייר שניים באייר שלוש על שלוש מתנודדות באייר ארבע על ארבע דיבורים באייר שמונה על שמונה דיבורים פלויד סטיינברג דיטה ג\'רוויס ג\'ודיס נינקה דיטה סיירה דיטרינג שתי שורות סיירה דיטה Burkes Dithering שקר פלויד סטיינברג דיטה שיוף משמאל לימין חילוף אקראי הסרת סף פשוטה ספירת צבעים מקסימלית השמירה כמעט הושלמה. ביטול כעת יחייב שמירה שוב. צבע מסכה מצב קנה מידה ביליניארי האן הרמיט לנצ\'וס מיטשל הכי קרוב שֶׁגֶם בסיסי ערך ברירת מחדל ערך בטווח %1$s - %2$s סיגמא סיגמא מרחבית טשטוש חציוני Catmull Bicubic משתמש בפונקציות פולינום המוגדרות חלקית כדי לבצע אינטרפולציה חלקה ולהעריך עקומה או משטח, מאפשר ייצוג צורה גמיש ורציף רק קליפ שמירה לאחסון לא תתבצע, ותנסה להכניס תמונה ללוח בלבד מוסיף מיכל עם צורה נבחרת מתחת לסמלים המובילים של כרטיסים צורת סמל תפירת תמונה שלב את התמונות הנתונות כדי לקבל תמונה אחת גדולה אם מופעל שם קובץ הפלט יהיה אקראי לחלוטין אכיפת בהירות מסך שכבת שיפוע חבר כל גרדיאנט של החלק העליון של תמונות נתונות טרנספורמציות מצלמה צלם תמונה בעזרת המצלמה, שימו לב שאפשר לקבל רק תמונה אחת ממקור תמונה זה סולם תמונה פלט כיוון תמונה אופקי אנכי קנה מידה של תמונות קטנות לגדולות תמונות קטנות יותאמו לגדולה ברצף אם מופעלת סדר תמונות תְבוּאָה לא חד ערפל סגול זריחה מערבולת צבעונית פיצוץ צבע שיפוע חשמלי כהה קרמל שיפוע עתידני שמש ירוקה סגול עמוק פורטל החלל מערבולת אדומה קוד דיגיטלי סימון מים חזור על סימן מים תמונה זו תשמש כתבנית לסימון מים צבע טקסט מצב שכבת-על גודל פיקסל נעל את כיוון הציור אם מופעל במצב ציור, המסך לא יסתובב בוקה כלי GIF המר תמונות לתמונת GIF או חלץ מסגרות מתמונת GIF נתונה GIF לתמונות המרת קובץ GIF לקבוצת תמונות המר אצווה של תמונות לקובץ GIF תמונות ל-GIF בחר תמונת GIF כדי להתחיל השתמש בגודל של מסגרת ראשונה החלף את הגודל שצוין במידות המסגרת הראשונה חזור על ספירה השהיית מסגרת מילי FPS השתמש בלאסו משתמש ב-Lasso כמו במצב ציור כדי לבצע מחיקה תצוגה מקדימה של תמונה מקורית אלפא מסנן מסכה מסכות האימוג\'י של סרגל האפליקציה ישתנה באקראי אמוג\'י אקראיים לא ניתן להשתמש בבחירת אמוג\'י אקראית בזמן שהאימוג\'י מושבתים לא ניתן לבחור אמוג\'י בזמן שבחירת אימוג\'ים אקראית פעילה בדוק עדכונים יחס גובה-רוחב מחק סכמה הוסף מסכה טלוויזיה ישנה ערבוב טשטוש OCR (זיהוי טקסט) זיהוי טקסט מתמונה נתונה, 120+ שפות נתמכות לתמונה אין טקסט, או שהאפליקציה לא מצאה אותה Accuracy: %1$s סוג זיהוי מָהִיר תֶקֶן הטוב ביותר אין מידע לתפקוד תקין של Tesseract OCR יש להוריד נתוני אימון נוספים (%1$s) למכשיר שלך. \nהאם ברצונך להוריד נתוני %2$s? הורד אין חיבור, בדוק את זה ונסה שוב כדי להוריד דגמי רכבת שפות שהורדו שפות זמינות מצב פילוח המברשת תשחזר את הרקע במקום למחוק רשת אופקית רשת אנכית מצב תפירה ספירת שורות ספירת עמודות השתמש ב-Pixel Switch משתמש במתג דמוי פיקסל של Google שקופית זה לצד זה החלף הקש שְׁקִיפוּת קובץ שהוחלף עם השם %1$s ביעד המקורי זכוכית מגדלת מאפשר זכוכית מגדלת בחלק העליון של האצבע במצבי ציור עבור נגישות טובה יותר לכפות ערך התחלתי מאלץ את הווידג\'ט של ה-exif להיבדק תחילה אהוב עדיין לא נוספו מסננים מועדפים B Spline טשטוש מחסנית מקורי הטיה שיפט רגיל טשטוש קצוות מצייר קצוות מטושטשים מתחת לתמונה המקורית כדי למלא רווחים סביבה במקום צבע בודד אם מופעל פתיתי נייר ססגוניים קונפטי יוצג על שמירה, שיתוף ופעולות עיקריות אחרות מצב בטוח מסתיר תוכן במסך \'יישומים אחרונים\', לא יהיה ניתן ללכוד או להקליט. התמונות ייחתכו במרכז לגודל שהוזן. הקנבס יורחב עם צבע רקע נתון אם התמונה קטנה מהמידות שהוזנו. פיקסלים Pixelation שבץ החלף צבע סכימה שדומה מאוד לסכימת התוכן החלת שרשראות סינון על אזורים מסווים, כל אזור מסכה יכול לקבוע את קבוצת המסננים שלו מסכה %d תצוגה מקדימה של מסכה מסכת מסנן מצוירת תוצג כדי להראות לך את התוצאה המשוערת גרסאות פשוטות סימון ניאון עט טשטוש פרטיות הוסף אפקט זוהר לציורים שלך ברירת מחדל, הפשוטה ביותר - רק הצבע דומה לטשטוש הפרטיות, אבל מפיקסל במקום טשטוש סיבוב אוטומטי מאפשר לאמץ תיבת מגבלה עבור כיוון תמונה סגנון פלטה נקודה טונאלית ניטראלי תוסס אקספרסבי קשת בענן סלט פירות צייר נתיבי סימון מחודדים שקופים למחצה מטשטשת תמונה מתחת לנתיב המצויר כדי לאבטח כל מה שאתה רוצה להסתיר מיכלים צייר צל מאחורי קונטיינרים סליידרים מתגים FABs כפתורים צייר צל מאחורי המחוונים מאפשר ציור צל מאחורי מתגים מאפשר ציור צל מאחורי לחצני פעולה צפים צייר צל מאחורי כפתורים בודק העדכונים הזה יתחבר ל-GitHub בגלל בדיקה אם יש עדכון חדש זמין תשומת הלב קצוות דוהים השבת שניהם הפוך צבעים מחליף צבעי ערכת נושא לשליליים אם מופעל יציאה אם תעזוב את התצוגה המקדימה כעת, תצטרך להוסיף את התמונות שוב פורמט תמונה יוצר\"Material You\" צבעים מתמונה העתק כקוד \" Jetpack Compose\" צבעים כהים משתמש ערכת צבעי מצב הלילה במקום וריאנט אור טשטוש טבעת טשטוש צולב טשטוש מעגל טשטוש כוכבים שינוי הטיה ליניארי תגיות להסרה המר תמונות לתמונת APNG או חלץ מסגרות מתמונת APNG נתונה APNG לתמונות המרת קובץ APNG לקבוצת תמונות תמונות ל-APNG בחר תמונת APNG כדי להתחיל טשטוש תנועה צור קובץ Zip מקבצים או תמונות נתונים המר אצווה של תמונות לקובץ APNG רוכסן כלי APNG רוחב ידית גרור סוג קונפטי פינות כלי JXL בצע קידוד JXL ~ JPEG ללא אובדן איכות, או המרת GIF/APNG לאנימציית JXL JXL ל JPEG חגיגי מתפוצץ גֶשֶׁם בצע המרת קידוד ללא הפסדים מ-JXL ל-JPEG בצע המרת קידוד ללא הפסדים מ-JPEG ל-JXL JPEG עד JXL בחר תמונת JXL כדי להתחיל טשטוש גאוס מהיר 2D טשטוש גאוס מהיר תלת מימד טשטוש גאוס מהיר 4D פסחא לרכב מאפשר לאפליקציה להדביק אוטומטית נתוני לוח, כך שהם יופיעו במסך הראשי ותוכלו לעבד אותם צבע הרמוניזציה רמת הרמוניזציה לנצ\'וס בסל שיטת דגימה מחדש השומרת על אינטרפולציה איכותית על ידי החלת פונקציית Bessel (jinc) על ערכי הפיקסלים GIF ל-JXL המר תמונות GIF לתמונות מונפשות של JXL APNG ל-JXL המר תמונות APNG לתמונות מונפשות של JXL JXL לתמונות המר אנימציית JXL לקבוצת תמונות תמונות ל-JXL המר אצווה של תמונות לאנימציית JXL התנהגות דלג על בחירת קבצים בוחר הקבצים יוצג מיד אם זה אפשרי במסך שנבחר צור תצוגות מקדימות מאפשר יצירת תצוגה מקדימה, זה עשוי לעזור למנוע קריסות במכשירים מסוימים, זה גם משבית חלק מפונקציונליות העריכה בתוך אפשרות עריכה אחת דחיסה אבודה משתמש בדחיסה מאבדת כדי להקטין את גודל הקובץ במקום ללא אובדן סוג דחיסה שולט במהירות פענוח התמונה המתקבלת, זה אמור לעזור לפתוח את התמונה המתקבלת מהר יותר, הערך של %1$s פירושו הפענוח האיטי ביותר, ואילו %2$s - המהיר ביותר, הגדרה זו עשויה להגדיל את גודל תמונת הפלט מיון תאריך תאריך (הפוך) שם שם (הפוך) תצורת ערוצים היום אתמול בוחר מוטבע בוחר התמונות של ארגז הכלים של תמונות אין הרשאות בקשה בחירת מדיה מרובה בחר מדיה אחת בחירה נסה שוב הצג הגדרות בנוף אם זה מושבת, הגדרות מצב לרוחב ייפתחו על הכפתור בסרגל האפליקציה העליון כמו תמיד, במקום אפשרות גלויה קבועה הגדרות מסך מלא הפעל אותו ודף ההגדרות ייפתח תמיד כמסך מלא במקום גיליון מגירה הניתן להחלקה סוג מתג לחבר Jetpack Compose חומר שאתה מחליף חומר שאתה מחליף מקסימום שנה גודל עוגן פיקסל שׁוֹטֵף מתג המבוסס על מערכת העיצוב \"Fluent\". קופרטינו מתג המבוסס על מערכת העיצוב \"קופרטינו\". תמונות ל-SVG עקבו אחר תמונות שניתנו לתמונות SVG השתמש בלוח מדגם פלטת קוונטיזציה תידגם אם אפשרות זו מופעלת השמיטת נתיב השימוש בכלי זה למעקב אחר תמונות גדולות ללא הקטנת קנה מידה אינו מומלץ, הוא עלול לגרום לקריסה ולהגדיל את זמן העיבוד תמונה בקנה מידה נמוך התמונה תוקטן לממדים נמוכים יותר לפני העיבוד, זה עוזר לכלי לעבוד מהר ובטוח יותר יחס צבע מינימלי סף קווים סף ריבועי קואורדינטות עיגול סובלנות סולם נתיב אפס מאפיינים כל המאפיינים יוגדרו לערכי ברירת מחדל, שימו לב שלא ניתן לבטל פעולה זו מְפוֹרָט ברירת המחדל של רוחב קו מצב מנוע מוֹרֶשֶׁת רשת LSTM דור קודם ו-LSTM המרה המרת קבוצות תמונות לפורמט נתון הוסף תיקיה חדשה ביטים לדגימה דְחִיסָה פרשנות פוטומטרית דגימות לכל פיקסל תצורה מישורית דגימת משנה של Y Cb Cr מיקום Y Cb Cr X רזולוציה Y רזולוציה יחידת רזולוציה סטריפ קיזוז שורות לכל רצועה Strip Bytes Counts פורמט JPEG Interchange אורך פורמט JPEG Interchange פונקציית העברה נקודה לבנה צבעוניות ראשונית מקדמי Y Cb Cr הפניה שחור לבן תאריך שעה תיאור תמונה לַעֲשׂוֹת דֶגֶם תוֹכנָה אָמָן זְכוּת יְוֹצרִים גרסת ה-Exif גרסת פלאשפיקס מרחב צבע גמא Pixel X Dimension Pixel Y Dimension ביטים דחוסים לכל פיקסל הערה מייצרת הערת משתמש קובץ סאונד קשור תאריך שעה מקורי תאריך זמן דיגיטלי זמן קיזוז קיזוז זמן מקורי קיזוז זמן דיגיטלי זמן משנה זמן משנה משנה תת שניות זמן דיגיטלי זמן חשיפה מספר F תוכנית חשיפה רגישות ספקטרלית רגישות לצילום Oecf סוג רגישות רגישות פלט סטנדרטית מדד החשיפה המומלץ מהירות ISO מהירות ISO רוחב yyy ISO Speed ​​Latitude zzz ערך מהירות תריס ערך צמצם ערך בהירות ערך הטיית חשיפה ערך צמצם מרבי מרחק נושא מצב מדידה הֶבזֵק אזור נושא אורך מוקד אנרגיית פלאש תגובת תדר מרחבית רזולוציית מישור מוקד X רזולוציית מישור מוקד Y יחידת רזולוציה של מישור מוקד מיקום הנושא מדד החשיפה שיטת חישה מקור הקובץ דפוס CFA עיבוד מותאם אישית מצב חשיפה איזון לבן יחס זום דיגיטלי אורך מוקד בסרט 35 מ\"מ סוג לכידת סצנה השג שליטה לְהַשְׁווֹת רִוּוּי חַדוּת תיאור הגדרת ההתקן טווח מרחק נושא מזהה ייחודי לתמונה שם בעל המצלמה מספר סידורי גוף מפרט עדשה תוצרת עדשה דגם עדשה מספר סידורי של עדשה מזהה גרסת GPS GPS Ref GPS רוחב GPS קו אורך Ref GPS קו אורך GPS גובה רפ GPS גובה חותמת זמן GPS לווייני GPS מצב GPS מצב מדידת GPS GPS DOP מהירות GPS Ref מהירות GPS מסלול GPS Ref מסלול GPS GPS Img כיוון Ref GPS Img כיוון תאריך מפה של GPS GPS Dest Latitude Ref GPS Dest Latitude GPS יעד קו אורך Ref קו אורך יעד של GPS מיסב יעד GPS מיסב יעד GPS מרחק יעד GPS Ref מרחק יעד GPS שיטת עיבוד GPS מידע על אזור GPS חותמת תאריך של GPS הפרש GPS שגיאת מיקום GPS H אינדקס יכולת פעולה הדדית גרסת DNG גודל חיתוך ברירת מחדל תצוגה מקדימה של התחלת תמונה אורך תמונה בתצוגה מקדימה מסגרת היבט גבול תחתון של חיישן גבול שמאל של חיישן גבול ימין של חיישן גבול עליון חיישן ISO צייר טקסט על הנתיב עם גופן וצבע נתונים גודל גופן גודל סימן מים חזור על טקסט הטקסט הנוכחי יחזור על עצמו עד לסיום הנתיב במקום ציור חד פעמי גודל מקף השתמש בתמונה שנבחרה כדי לצייר אותה לאורך נתיב נתון תמונה זו תשמש ככניסה חוזרת ונשנית של הנתיב המצויר מצייר משולש מתאר מנקודת ההתחלה לנקודת הסיום מצייר משולש מתאר מנקודת ההתחלה לנקודת הסיום משולש מתואר מְשּוּלָשׁ מצייר מצולע מנקודת התחלה לנקודת סיום מְצוּלָע מצולע מתואר מצייר מצולע מתואר מנקודת התחלה לנקודת סיום קודקודים צייר מצולע רגיל צייר מצולע שיהיה רגיל במקום צורה חופשית מצייר כוכב מנקודת התחלה לנקודת סיום כּוֹכָב כוכב מתואר מצייר כוכב מסומן מנקודת ההתחלה לנקודת הסיום יחס רדיוס פנימי צייר כוכב רגיל צייר כוכב שיהיה רגיל במקום צורה חופשית Antialias מאפשר הדפסה נגדית כדי למנוע קצוות חדים פתח את עריכה במקום תצוגה מקדימה כאשר אתה בוחר תמונה לפתיחה (תצוגה מקדימה) ב-ImageToolbox, גיליון הבחירה של עריכה ייפתח במקום תצוגה מקדימה סורק מסמכים סרוק מסמכים וצור PDF או הפרד מהם תמונות לחץ כדי להתחיל בסריקה התחל בסריקה שמור כ-PDF שתף כ-Pdf האפשרויות להלן הן לשמירת תמונות, לא ל-PDF השווה היסטוגרמה HSV השווה את ההיסטוגרמה הזן אחוז אפשר להיכנס לפי שדה טקסט מאפשר שדה טקסט מאחורי בחירת הגדרות מוגדרות מראש, כדי להזין אותם תוך כדי תנועה קנה מידה של מרחב צבע ליניארי השווה פיקסלים היסטוגרמה גודל רשת X גודל רשת Y השוואת היסטוגרמה מסתגלת שווי היסטוגרמה אדפטיבית LUV Equalize Histogram Adaptive LAB קלה מעבדת CLAHE CLAHE LUV חתוך לתוכן צבע מסגרת צבע להתעלם תבנית לא נוספו מסנני תבנית צור חדש קוד ה-QR הסרוק אינו תבנית סינון חוקית סרוק קוד QR לקובץ שנבחר אין נתוני תבנית סינון צור תבנית שם התבנית תמונה זו תשמש לתצוגה מקדימה של תבנית סינון זו מסנן תבניות כתמונת קוד QR בתור קובץ שמור כקובץ שמור כתמונת קוד QR מחק תבנית אתה עומד למחוק את מסנן התבניות שנבחר. לא ניתן לבטל פעולה זו נוספה תבנית סינון בשם \"%1$s\" (%2$s) תצוגה מקדימה של מסנן QR וברקוד סרוק קוד QR וקבל את התוכן שלו או הדבק את המחרוזת שלך כדי ליצור מחרוזת חדשה תוכן קוד סרוק כל ברקוד כדי להחליף תוכן בשדה, או הקלד משהו כדי ליצור ברקוד חדש עם סוג נבחר תיאור QR מינימום הענק הרשאה למצלמה בהגדרות לסרוק קוד QR הענק הרשאה למצלמה בהגדרות כדי לסרוק את סורק המסמכים מְעוּקָב B-Spline האמינג האנינג בלקמן ולץ\' קוואדרי גאוס ספִינקס ברטלט רובידווקס רובידווקס שארפ ספליין 16 ספליין 36 ספליין 64 קֵיסָר בארטלט-היי קוּפסָה בוהמן לנצ\'וס 2 לנצ\'וס 3 לנצ\'וס 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc אינטרפולציה מעוקבת מספקת קנה מידה חלק יותר על ידי התחשבות ב-16 הפיקסלים הקרובים ביותר, ונותנת תוצאות טובות יותר מאשר בילינאריות משתמש בפונקציות פולינום המוגדרות חלקית כדי לבצע אינטרפולציה חלקה ולהעריך עקומה או משטח, ייצוג צורה גמיש ורציף פונקציית חלון המשמשת להפחתת דליפה ספקטרלית על ידי הקטנת קצוות האות, שימושית בעיבוד אותות גרסה של חלון Hann, המשמש בדרך כלל להפחתת דליפה ספקטרלית ביישומי עיבוד אותות פונקציית חלון המספקת רזולוציית תדר טובה על ידי מזעור דליפה ספקטרלית, המשמשת לעתים קרובות בעיבוד אותות פונקציית חלון שנועדה לתת רזולוציית תדר טובה עם דליפה ספקטרלית מופחתת, המשמשת לעתים קרובות ביישומי עיבוד אותות שיטה המשתמשת בפונקציה ריבועית לאינטרפולציה, המספקת תוצאות חלקות ורציפות שיטת אינטרפולציה המיישמת פונקציה גאוסית, שימושית להחלקה והפחתת רעש בתמונות שיטת דגימה מחדש מתקדמת המספקת אינטרפולציה באיכות גבוהה עם חפצים מינימליים פונקציית חלון משולש המשמשת בעיבוד אותות להפחתת דליפה ספקטרלית שיטת אינטרפולציה איכותית המותאמת לשינוי גודל תמונה טבעי, איזון חדות וחלקות גרסה חדה יותר של שיטת Robidoux, מותאמת לשינוי גודל תמונה חד שיטת אינטרפולציה מבוססת ספליין המספקת תוצאות חלקות באמצעות מסנן של 16 ברז שיטת אינטרפולציה מבוססת ספליין המספקת תוצאות חלקות באמצעות מסנן של 36 ברז שיטת אינטרפולציה מבוססת ספליין המספקת תוצאות חלקות באמצעות מסנן של 64 ברז שיטת אינטרפולציה המשתמשת בחלון הקייזר, המספקת שליטה טובה על ההחלפה בין רוחב האונה הראשית לרמת האונה הצדדית פונקציית חלון היברידית המשלבת את חלונות Bartlett ו-Hann, משמשת להפחתת דליפה ספקטרלית בעיבוד אותות שיטת דגימה מחודשת פשוטה המשתמשת בממוצע של ערכי הפיקסלים הקרובים ביותר, ולעתים קרובות גורמת למראה חסום פונקציית חלון המשמשת להפחתת דליפה ספקטרלית, המספקת רזולוציית תדר טובה ביישומי עיבוד אותות שיטת דגימה חוזרת המשתמשת במסנן Lanczos בעל 2 אונות לאינטרפולציה איכותית עם חפצים מינימליים שיטת דגימה חוזרת המשתמשת במסנן Lanczos בעל 3 אונות לאינטרפולציה איכותית עם חפצים מינימליים שיטת דגימה חוזרת המשתמשת במסנן Lanczos בעל 4 אונות לאינטרפולציה איכותית עם חפצים מינימליים גרסה של מסנן Lanczos 2 המשתמש בפונקציית jinc, המספקת אינטרפולציה באיכות גבוהה עם חפצים מינימליים גרסה של מסנן Lanczos 3 המשתמש בפונקציית jinc, המספקת אינטרפולציה באיכות גבוהה עם חפצים מינימליים גרסה של מסנן Lanczos 4 המשתמש בפונקציית jinc, המספקת אינטרפולציה איכותית עם חפצים מינימליים הנינג EWA גרסה אליפטית משוקללת (EWA) של מסנן האנינג לאינטרפולציה חלקה ודגימה מחדש Robidoux EWA גרסה אליפטית משוקללת (EWA) של מסנן Robidoux לדגימה מחדש באיכות גבוהה Blackman EVE גרסת ממוצע משוקלל אליפטי (EWA) של מסנן Blackman למזעור חפצי צלצול Quadric EWA גרסת ממוצע משוקלל אליפטי (EWA) של מסנן Quadric לאינטרפולציה חלקה Robidoux Sharp EWA גרסה אליפטית משוקללת (EWA) של מסנן Robidoux Sharp לתוצאות חדות יותר Lanczos 3 Jinc EWA גרסה אליפטית משוקללת (EWA) של מסנן Lanczos 3 Jinc לדגימה מחדש באיכות גבוהה עם כינוי מופחת ג\'ינסנג מסנן דגימה מחדש המיועד לעיבוד תמונה באיכות גבוהה עם איזון טוב של חדות וחלקות ג\'ינסנג EWA גרסה אליפטית משוקללת (EWA) של מסנן הג\'ינסנג לאיכות תמונה משופרת לנצ\'וס שארפ EWA גרסת ממוצע משוקלל אליפטי (EWA) של מסנן Lanczos Sharp להשגת תוצאות חדות עם חפצים מינימליים Lanczos 4 Sharpest EWA גרסת ממוצע משוקלל אליפטי (EWA) של מסנן Lanczos 4 Sharpest לדגימה מחדש של תמונה חדה במיוחד Lanczos Soft EWA גרסה אליפטית משוקללת (EWA) של מסנן Lanczos Soft לדגימה מחדש של תמונה חלקה יותר האסן סופט מסנן דגימה מחדש שעוצב על ידי Haasn לשינוי קנה מידה חלק וללא חפצים המרת פורמט המר אצווה של תמונות מפורמט אחד לאחר תפטר לנצח ערימת תמונה ערמו תמונות זו על גבי זו עם מצבי מיזוג נבחרים הוסף תמונה ספירת פחים Clahe HSL Clahe HSV השוואת היסטוגרמה אדפטיבית HSL השוואת היסטוגרמה HSV אדפטיבית מצב קצה לְקַצֵץ לַעֲטוֹף עיוורון צבעים בחר מצב כדי להתאים את צבעי הנושא לגרסה של עיוורון צבעים שנבחרה קושי להבחין בין גוונים אדומים לירוקים קושי להבחין בין גוונים ירוקים לאדומים קושי להבחין בין גוונים כחולים לצהובים חוסר יכולת לתפוס גוונים אדומים חוסר יכולת לתפוס גוונים ירוקים חוסר יכולת לתפוס גוונים כחולים רגישות מופחתת לכל הצבעים עיוורון צבעים מוחלט, רואה רק גוונים של אפור אל תשתמש בערכת עיוור צבעים הצבעים יהיו בדיוק כפי שנקבעו בערכת הנושא סיגמואידי לגראנג\' 2 מסנן אינטרפולציה של Lagrange בסדר 2, מתאים לשינוי קנה מידה באיכות גבוהה עם מעברים חלקים לגראנז\' 3 מסנן אינטרפולציה של Lagrange בסדר 3, המציע דיוק טוב יותר ותוצאות חלקות יותר עבור קנה מידה של תמונה לנצ\'וס 6 מסנן דגימה מחדש של Lanczos בסדר גבוה יותר של 6, המספק קנה מידה חד ומדויק יותר של תמונה Lanczos 6 Jinc גרסה של מסנן Lanczos 6 באמצעות פונקציית Jinc לשיפור איכות דגימת התמונה מחדש טשטוש תיבה לינארית טשטוש אוהל ליניארי טשטוש תיבת גאוס ליניארי טשטוש מחסנית ליניארי טשטוש תיבת גאוס Linear Fast Gaussian Blur Next טשטוש גאוסי מהיר ליניארי טשטוש גאוס ליניארי בחר מסנן אחד כדי להשתמש בו כצבע החלף מסנן בחר מסנן למטה כדי להשתמש בו בתור מברשת בציור שלך ערכת דחיסת TIFF פולי נמוך ציור חול פיצול תמונה פיצול תמונה אחת לפי שורות או עמודות התאמה לגבולות שלב מצב שינוי גודל חיתוך עם פרמטר זה כדי להשיג התנהגות רצויה (חיתוך/התאמה ליחס רוחב-גובה) שפות יובאו בהצלחה גיבוי דגמי OCR יְבוּא יְצוּא מַצָב מֶרְכָּז שמאל למעלה למעלה מימין שמאל למטה ימין למטה מרכז העליון מרכז ימין מרכז תחתון מרכז שמאל תמונת יעד העברת פלטות שמן משופר טלוויזיה ישנה פשוטה HDR Gotham סקיצה פשוטה זוהר רך פוסטר צבעוני טרי טון צבע שלישי קלה אוקלאב קלרה אולך קלה ג\'זבז נקודה מקבץ 2x2 ניתוק מקבץ 4x4 דיטה מקבץ 8x8 ניתוק שיוף ילילומה לא נבחרו אפשרויות מועדפות, הוסף אותן בדף הכלים הוסף מועדפים מַשׁלִים מַקְבִּיל טריאדי פיצול משלים טטראדי מְרוּבָּע אנלוגי + משלים כלי צבע לערבב, ליצור גוונים, ליצור גוונים ועוד הרמוניות צבע הצללת צבע וָרִיאַצִיָה גוונים צלילים מִשְׁקפֵי שֶׁמֶשׁ ערבוב צבעים מידע על צבע צבע נבחר צבע לערבב לא ניתן להשתמש ב-Monte בזמן שצבעים דינמיים מופעלים 512x512 2D LUT תמונת LUT יעד חובבן מיס נימוס אלגנטיות רכה וריאנט אלגנטיות רכה וריאנט העברת צבעים 3D LUT יעד קובץ LUT 3D (.cube / .CUBE) LUT מעקף אקונומיקה אור נרות זרוק בלוז אמבר עצבנית צבעי סתיו מלאי סרטים 50 לילה ערפילי קודאק קבל תמונת LUT ניטראלית ראשית, השתמש באפליקציית עריכת התמונות המועדפת עליך כדי להחיל מסנן על LUT ניטרלי שתוכל להשיג כאן. כדי שזה יעבוד כמו שצריך, אסור שכל צבע פיקסל יהיה תלוי בפיקסלים אחרים (למשל, טשטוש לא יעבוד). ברגע שאתה מוכן, השתמש בתמונת LUT החדשה שלך כקלט עבור מסנן 512*512 LUT פופ ארט צִיבִית קָפֶה יער הזהב יְרַקרַק רטרו צהוב תצוגה מקדימה של קישורים מאפשר אחזור תצוגה מקדימה של קישורים במקומות שבהם אתה יכול להשיג טקסט (QRCode, OCR וכו\') קישורים ניתן לשמור קבצי ICO רק בגודל המרבי של 256 x 256 GIF ל-WEBP המר תמונות GIF לתמונות מונפשות WEBP כלי WEBP המר תמונות לתמונה מונפשת של WEBP או חלץ מסגרות מהנפשת WEBP נתונה WEBP לתמונות המרת קובץ WEBP לקבוצת תמונות המר אצווה של תמונות לקובץ WEBP תמונות ל-WEBP בחר תמונת WEBP כדי להתחיל אין גישה מלאה לקבצים אפשר לכל הקבצים גישה לראות JXL, QOI ותמונות אחרות שאינן מזוהות כתמונות באנדרואיד. ללא הרשאה Image Toolbox אינו יכול להציג את התמונות הללו צבע ציור ברירת מחדל מצב ציור ברירת מחדל הוסף חותמת זמן מאפשר הוספה של חותמת זמן לשם קובץ הפלט חותמת זמן מעוצבת אפשר עיצוב חותמת זמן בשם קובץ הפלט במקום מיליס בסיסי אפשר חותמות זמן כדי לבחור את הפורמט שלהן מיקום חד פעמי של שמירה הצג וערוך מיקומי שמירה חד-פעמיים שבהם תוכל להשתמש בלחיצה ארוכה על כפתור השמירה ברוב האפשרויות בשימוש לאחרונה ערוץ CI קְבוּצָה ארגז כלים לתמונה בטלגרם 🎉 הצטרף לצ\'אט שלנו שבו אתה יכול לדון בכל מה שאתה רוצה וגם להסתכל בערוץ CI שבו אני מפרסם בטא והודעות קבל הודעה על גרסאות חדשות של האפליקציה וקרא הודעות התאם תמונה למידות נתונות והחל טשטוש או צבע על הרקע סידור כלים קבץ כלים לפי סוג מקבץ כלים במסך הראשי לפי סוגם במקום סידור רשימה מותאם אישית ערכי ברירת מחדל נראות סרגלי מערכת הצג את סרגלי המערכת באמצעות החלקה מאפשר החלקה כדי להציג סרגלי מערכת אם הם מוסתרים אוטומטי הסתר הכל הצג הכל הסתר Nav Bar הסתר את שורת המצב יצירת רעש צור רעשים שונים כמו פרלין או סוגים אחרים תֶדֶר סוג רעש סוג סיבוב סוג פרקטל אוקטבות לאקונריות לְהַשִׂיג כוח משוקלל חוזק פינג פונג פונקציית מרחק סוג החזרה לְהִתְעַצְבֵּן עיוות דומיין מַעֲרָך שם קובץ מותאם אישית בחר מיקום ושם קובץ אשר ישמשו לשמירת התמונה הנוכחית נשמר בתיקייה עם שם מותאם אישית קולאז\' יוצר צור קולאז\'ים מ-20 תמונות לכל היותר סוג קולאז\' החזק את התמונה כדי להחליף, להזיז ולהתקרב כדי להתאים את המיקום השבת סיבוב מונע סיבוב תמונות באמצעות תנועות בשתי אצבעות אפשר הצמדה לגבולות לאחר הזזה או התקרבות, התמונות יוצמדו כדי למלא את קצוות המסגרת היסטוגרמה היסטוגרמת תמונת RGB או Brightness כדי לעזור לך לבצע התאמות תמונה זו תשמש ליצירת היסטוגרמות RGB ובהירות אפשרויות Tesseract החל כמה משתני קלט עבור מנוע tesseract אפשרויות מותאמות אישית יש להזין אפשרויות לפי הדפוס הזה: \"--{option_name} {value}\" חיתוך אוטומטי פינות חופשיות חתוך תמונה לפי מצולע, זה גם מתקן פרספקטיבה כפייה מצביע על גבולות תמונה נקודות לא יוגבלו על ידי גבולות התמונה, זה שימושי לתיקון פרספקטיבה מדויק יותר מַסֵכָה מילוי מודע לתוכן תחת נתיב מצויר נקודת ריפוי השתמש ב- Circle Kernel פְּתִיחָה סְגִירָה שיפוע מורפולוגי כּוֹבַע צִילִינדר כובע שחור עקומות טון אפס עקומות עקומות יוחזרו לערך ברירת המחדל סגנון קו גודל פער מקווקו דוט מקווקו חָתוּם לְזַגזֵג מצייר קו מקווקו לאורך הנתיב המצויר עם גודל הרווח שצוין מצייר נקודות וקו מקווקו לאורך הנתיב הנתון רק ברירת מחדל קווים ישרים מצייר צורות נבחרות לאורך הנתיב עם מרווח שצוין מצייר זיגזג גלי לאורך השביל יחס זיגזג צור קיצור דרך בחר כלי להצמדה הכלי יתווסף למסך הבית של המשגר ​​שלך כקיצור דרך, השתמש בו בשילוב עם הגדרת \"דלג על בחירת קבצים\" כדי להשיג התנהגות נדרשת אל תערמו מסגרות מאפשר סילוק מסגרות קודמות, כך שהן לא ייערמו אחת על השנייה Crossfade מסגרות יהיו מוצלבות זו לזו מסגרות Crossfade נחשבות סף ראשון סף שני עַרמוּמִי מראה 101 טשטוש זום משופר Laplacian Simple סובל סימפל רשת עוזרת מציג רשת תומכת מעל אזור הציור כדי לעזור במניפולציות מדויקות צבע רשת רוחב תא גובה תא בוררים קומפקטיים חלק מפקדי הבחירה ישתמשו בפריסה קומפקטית כדי לקחת פחות מקום הענק הרשאה למצלמה בהגדרות לצילום תמונה מַעֲרָך כותרת המסך הראשית פקטור קצב קבוע (CRF) ערך של %1$s פירושו דחיסה איטית, וכתוצאה מכך גודל קובץ קטן יחסית. %2$s פירושו דחיסה מהירה יותר, וכתוצאה מכך קובץ גדול. ספריית לוט הורד אוסף של LUTs, שתוכל ליישם לאחר ההורדה עדכון אוסף של LUTs (רק חדשים יעמדו בתור), שתוכל להחיל לאחר ההורדה שנה את ברירת המחדל של תצוגה מקדימה של תמונה עבור מסננים תצוגה מקדימה של תמונה לְהַסתִיר לְהַצִיג סוג המחוון לְחַבֵּב חומר 2 סליידר בעל מראה מהודר. זוהי אפשרות ברירת המחדל מחוון חומר 2 מחוון חומר אתה לִפְנוֹת לחצני דיאלוג מרכזי לחצנים של דיאלוגים ימוקמו במרכז במקום בצד שמאל במידת האפשר רישיונות קוד פתוח הצג רישיונות של ספריות קוד פתוח המשמשות באפליקציה זו אֵזוֹר דגימה מחדש באמצעות יחס שטח פיקסלים. זו עשויה להיות שיטה מועדפת להפחתת תמונה, מכיוון שהיא נותנת תוצאות נטולות מואר. אבל כשהתמונה מוגדלת, היא דומה לשיטת ה\"קרוב\". הפעל מיפוי גוונים הזן % לא מצליח לגשת לאתר, נסה להשתמש ב-VPN או בדוק אם כתובת האתר נכונה סימון שכבות מצב שכבות עם יכולת למקם בחופשיות תמונות, טקסט ועוד ערוך שכבה שכבות על התמונה השתמש בתמונה כרקע והוסף שכבות שונות מעליה שכבות על רקע זהה לאפשרות הראשונה אבל עם צבע במקום תמונה בטא צד הגדרות מהיר הוסף רצועה צפה בצד הנבחר בעת עריכת תמונות, אשר תפתח הגדרות מהירות בלחיצה נקה בחירה קבוצת ההגדרה \"%1$s\" תכווץ כברירת מחדל קבוצת ההגדרה \"%1$s\" תורחב כברירת מחדל כלים של Base64 פענח מחרוזת Base64 לתמונה, או קידד תמונה לפורמט Base64 בסיס 64 הערך שסופק אינו מחרוזת Base64 חוקית לא ניתן להעתיק מחרוזת Base64 ריקה או לא חוקית הדבק את Base64 העתק את Base64 טען תמונה כדי להעתיק או לשמור מחרוזת Base64. אם יש לך את המחרוזת עצמה, אתה יכול להדביק אותה למעלה כדי לקבל תמונה שמור את Base64 שתף את Base64 אפשרויות פעולות ייבוא ​​Base64 פעולות Base64 הוסף מתאר הוסף קווי מתאר סביב טקסט עם צבע ורוחב שצוינו צבע מתאר גודל מתאר רוֹטַציָה בדיקת סכום כשם קובץ לתמונות הפלט יהיה שם המתאים לסכום הבדיקה שלהן תוכנה חופשית (שותף) תוכנה שימושית יותר בערוץ השותפים של אפליקציות אנדרואיד אַלגוֹרִיתְם כלי סכום ביקורת השווה סכומי בדיקה, חישוב גיבוב או צור מחרוזות hex מקבצים באמצעות אלגוריתמי גיבוב שונים לְחַשֵׁב טקסט Hash סכום בדיקה בחר קובץ כדי לחשב את סכום הבדיקה שלו בהתבסס על האלגוריתם שנבחר הזן טקסט כדי לחשב את סכום הבדיקה שלו בהתבסס על האלגוריתם שנבחר בדיקת סכום מקור Checksum להשוואה לְהַתְאִים! הֶבדֵל סכומי המחאה שווים, זה יכול להיות בטוח סכומי המחאה אינם שווים, הקובץ יכול להיות לא בטוח! מעברי רשת תסתכל על אוסף מקוון של Mesh Gradients ניתן לייבא רק גופני TTF ו-OTF ייבוא ​​גופן (TTF/OTF) ייצוא גופנים גופנים מיובאים שגיאה בעת שמירת הניסיון, נסה לשנות את תיקיית הפלט שם הקובץ לא מוגדר אַף לֹא אֶחָד דפים מותאמים אישית בחירת דפים אישור יציאת הכלי אם יש לך שינויים שלא נשמרו תוך כדי שימוש בכלים מסוימים ותנסה לסגור אותם, תוצג תיבת הדו-שיח לאישור ערוך EXIF שנה מטא נתונים של תמונה בודדת ללא דחיסה מחדש הקש כדי לערוך תגים זמינים שנה מדבקה התאמה לרוחב גובה מתאים השוואת אצווה בחר קובץ/קבצים כדי לחשב את סכום הבדיקה שלו בהתבסס על האלגוריתם שנבחר בחר קבצים בחר ספרייה סולם אורך ראש חוֹתֶמֶת חותמת זמן עיצוב דפוס ריפוד חיתוך תמונה חותכים חלק של התמונה וממזג את החלקים השמאליים (יכולים להיות הפוכים) על ידי קווים אנכיים או אופקיים קו ציר אנכי קו ציר אופקי בחירה הפוכה חלק חתוך אנכי ישאיר, במקום מיזוג חלקים סביב אזור החתך חלק חתוך אופקי ישאיר, במקום למזג חלקים סביב אזור החתך אוסף של מעברי רשת צור שיפוע רשת עם כמות מותאמת אישית של קשרים ורזולוציה שכבת שיפוע רשת צור שיפוע רשת של החלק העליון של תמונות נתונות התאמה אישית של נקודות גודל רשת רזולוציה X החלטה Y הַחְלָטָה Pixel By Pixel הדגש צבע סוג השוואת פיקסלים סרוק ברקוד יחס גובה סוג ברקוד לאכוף שחור/לבן תמונת ברקוד תהיה בשחור-לבן לחלוטין ולא צבועה לפי נושא האפליקציה סרוק כל ברקוד (QR, EAN, AZTEC, …) וקבל את התוכן שלו או הדבק את הטקסט שלך כדי ליצור אחד חדש לא נמצא ברקוד ברקוד שנוצר יהיה כאן עטיפות אודיו חלץ תמונות עטיפת אלבום מקובצי אודיו, רוב הפורמטים הנפוצים נתמכים בחר אודיו כדי להתחיל בחר אודיו לא נמצאו עטיפות שלח יומנים לחץ כדי לשתף קובץ יומני אפליקציה, זה יכול לעזור לי לזהות את הבעיה ולתקן בעיות אופס… משהו השתבש אתה יכול ליצור איתי קשר באמצעות האפשרויות למטה ואני אנסה למצוא פתרון.\n(אל תשכח לצרף יומנים) כתוב לקובץ חלץ טקסט מקבוצת תמונות ואחסן אותו בקובץ הטקסט האחד כתוב למטא נתונים חלץ טקסט מכל תמונה והצב אותו ב-EXIF info של תמונות יחסית מצב בלתי נראה השתמש בסטגנוגרפיה כדי ליצור סימני מים בלתי נראים בעיניים בתוך בתים של התמונות שלך השתמש ב-LSB ייעשה שימוש בשיטת סטגנוגרפיה LSB (Less Significant Bit), אחרת FD (דומיין תדר) הסרה אוטומטית של עיניים אדומות סִיסמָה לִפְתוֹחַ PDF מוגן המבצע כמעט הושלם. ביטול כעת יחייב הפעלה מחדש תאריך שינוי תאריך שינוי (הפוך) גוֹדֶל גודל (הפוך) סוג MIME סוג MIME (הפוך) הַרחָבָה הרחבה (הפוכה) תאריך נוסף תאריך הוספה (הפוך) משמאל לימין מימין לשמאל מלמעלה למטה מלמטה למעלה זכוכית נוזלית מתג המבוסס על IOS 26 שהוכרז לאחרונה ומערכת עיצוב הזכוכית הנוזלית שלו בחר תמונה או הדבק/ייבא נתוני Base64 למטה הקלד קישור לתמונה כדי להתחיל הדבק קישור קָלֵידוֹסקוֹפּ זווית משנית צדדים מיקס ערוץ כחול ירוק אדום כחול ירוק אדום לתוך אדום לתוך ירוק לתוך כחול ציאן מַגֶנטָה צָהוֹב צבע חצי טון קוֹנטוּר רמות לְקַזֵז Voronoi להתגבש צוּרָה לִמְתוֹחַ אקראיות משחרר כתמים מְפוּזָר כֶּלֶב רדיוס שני לְהַשְׁווֹת לַהַט מערבלים וצבטים פוינטיליזציה צבע גבול קואורדינטות קוטב ישר לקוטב קוטבי לתקן הפוך במעגל הפחת רעש Solarize פשוט לֶאֱרוֹג X Gap Y Gap X רוחב רוחב Y לְסוֹבֵב חותמת גומי לִמְרוֹחַ צְפִיפוּת לְעַרְבֵּב עיוות עדשת כדור מדד השבירה קֶשֶׁת זווית התפשטות נִצנוּץ קרניים ASCII מִדרוֹן מרי סתָיו עֶצֶם סִילוֹן חוֹרֶף יָם קַיִץ אָבִיב וריאנט מגניב HSV וָרוֹד חַם מִלָה מִקפָּה תוֹפֶת פְּלַסמָה וירידיס אזרחים דִמדוּם דמדומים הוסט פרספקטיבה אוטומטית הטיה אפשר חיתוך חיתוך או פרספקטיבה מוּחלָט טורבו ירוק עמוק תיקון עדשה קובץ פרופיל העדשה היעד בפורמט JSON הורד פרופילי עדשות מוכנים אחוזי חלק ייצא כ-JSON העתק מחרוזת עם נתוני לוח כייצוג של json גילוף תפר מסך הבית מסך נעילה מובנה ייצוא טפטים לְרַעֲנֵן השג טפטים עדכניים של בית, מנעול וטפטים מובנים אפשר גישה לכל הקבצים, זה נחוץ כדי לאחזר טפטים ניהול הרשאת אחסון חיצוני אינו מספיק, אתה צריך לאפשר גישה לתמונות שלך, הקפד לבחור \"אפשר הכל\" הוסף מוגדר מראש לשם הקובץ מוסיף סיומת עם הגדרה מראש שנבחרה לשם קובץ התמונה הוסף מצב סולם תמונה לשם הקובץ מוסיף סיומת עם מצב קנה המידה של התמונה שנבחר לשם קובץ התמונה Ascii Art המר תמונה לטקסט ascii שייראה כמו תמונה פרמס מחיל מסנן שלילי על התמונה לקבלת תוצאה טובה יותר במקרים מסוימים מעבד צילום מסך צילום מסך לא צולם, נסה שוב השמירה דילגה %1$s קבצים דילגו אפשר לדלג אם גדול יותר חלק מהכלים יורשו לדלג על שמירת תמונות אם גודל הקובץ המתקבל יהיה גדול מהמקור אירוע לוח שנה מַגָע אֶלֶקטרוֹנִי מִקוּם טֵלֵפוֹן טֶקסט SMS כתובת אתר Wi-Fi רשת פתוחה לא SSID טֵלֵפוֹן הוֹדָעָה כְּתוֹבֶת נוֹשֵׂא גוּף שֵׁם אִרגוּן כּוֹתֶרֶת טלפונים אימיילים כתובות אתרים כתובות תַקצִיר תֵאוּר מִקוּם מְאַרגֵן תאריך התחלה תאריך סיום סטָטוּס רוֹחַב קו אורך צור ברקוד ערוך ברקוד תצורת Wi-Fi בִּטָחוֹן בחר איש קשר הענק לאנשי קשר הרשאה בהגדרות למילוי אוטומטי באמצעות איש קשר נבחר פרטי יצירת קשר שֵׁם פְּרַטִי שם אמצעי שֵׁם מִשׁפָּחָה מִבטָא הוסף טלפון הוסף אימייל הוסף כתובת אֲתַר אִינטֶרנֶט הוסף אתר שם מעוצב תמונה זו תשמש למיקום מעל ברקוד התאמה אישית של קוד תמונה זו תשמש כלוגו במרכז קוד QR סֵמֶל ריפוד לוגו גודל לוגו פינות לוגו עין רביעית מוסיף סימטרית עין לקוד qr על ידי הוספת עין רביעית בפינה התחתונה צורת פיקסל צורת מסגרת צורת כדור רמת תיקון השגיאה צבע כהה צבע בהיר Hyper OS סגנון כמו Xiaomi HyperOS דפוס מסכה ייתכן שקוד זה אינו ניתן לסריקה, שנה פרמטרים של מראה כדי שיהיה קריא עם כל המכשירים לא ניתן לסריקה הכלים ייראו כמו משגר אפליקציות מסך הבית כדי להיות קומפקטי יותר מצב משגר ממלא אזור במברשת ובסגנון נבחרים מילוי הצפה תַרסִיס מצייר נתיב בסגנון גרפיטי חלקיקים מרובעים חלקיקי הריסוס יהיו בצורת ריבוע במקום עיגולים כלי לוח צור חומר בסיסי/חומר שאתה לוח מתמונה, או ייבא/ייצא על פני פורמטים שונים של לוח צבעים ערוך לוח פלטת ייצוא/ייבוא ​​בפורמטים שונים שם צבע שם פלטה פורמט פלטה ייצוא פלטה שנוצרה לפורמטים שונים מוסיף צבע חדש ללוח הנוכחי פורמט %1$s אינו תומך במתן שם לוח עקב מדיניות חנות Play, לא ניתן לכלול תכונה זו במבנה הנוכחי. כדי לגשת לפונקציונליות זו, אנא הורד את ImageToolbox ממקור חלופי. אתה יכול למצוא את ה-builds הזמינים ב-GitHub למטה. פתח את דף Github הקובץ המקורי יוחלף בקובץ חדש במקום לשמור בתיקייה שנבחרה זוהה טקסט של סימן מים מוסתר זוהתה תמונת סימן מים נסתרת התמונה הזו הוסתרה ציור גנרטיבי מאפשר להסיר אובייקטים בתמונה באמצעות מודל AI, מבלי להסתמך על OpenCV. כדי להשתמש בתכונה זו, האפליקציה תוריד את הדגם הנדרש (~200 MB) מ-GitHub מאפשר להסיר אובייקטים בתמונה באמצעות מודל AI, מבלי להסתמך על OpenCV. זה יכול להיות פעולה ארוכה ניתוח רמת שגיאה שיפוע בהירות מרחק ממוצע העתק זיהוי תזוזה לִשְׁמוֹר מקדם נתוני הלוח גדולים מדי הנתונים גדולים מדי להעתקה פיקסליזציה מארג פשוטה פיקסליזציה מדורגת פיקסליזציה צולבת פיקסלים של מיקרו מאקרו פיקסליזציה של מסלול פיקסליזציה של וורטקס Pixelization של רשת הדופק פיקסליזציה של גרעין Pixelization של אריגה רדיאלית לא ניתן לפתוח את uri \"%1$s\" מצב שלג מופעל מסגרת גבול וריאנט תקלות שינוי ערוץ מקסימום היסט VHS בלוק תקלה גודל בלוק עקמומיות CRT עַקמוּמִיוּת Chroma Pixel Melt מקסימום דרופ כלי AI כלים שונים לעיבוד תמונות באמצעות מודלים של AI כמו הסרת חפצים או דה-נוז דחיסה, קווים משוננים קריקטורות, דחיסת שידור דחיסה כללית, רעש כללי רעש מצויר חסר צבע מהיר, דחיסה כללית, רעש כללי, אנימציה/קומיקס/אנימה סריקת ספרים תיקון חשיפה הכי טוב בדחיסה כללית, תמונות צבעוניות הטוב ביותר בדחיסה כללית, תמונות בגווני אפור דחיסה כללית, תמונות בגווני אפור, חזקות יותר רעש כללי, תמונות צבעוניות רעש כללי, תמונות צבעוניות, פרטים טובים יותר רעש כללי, תמונות בגווני אפור רעש כללי, תמונות בגווני אפור, חזק יותר רעש כללי, תמונות בגווני אפור, החזק ביותר דחיסה כללית דחיסה כללית טקסטוריזציה, דחיסה של h264 דחיסת VHS דחיסה לא סטנדרטית (cinepak, msvideo1, roq) דחיסת Bink, טוב יותר בגיאומטריה דחיסת Bink, חזקה יותר דחיסת בינק, רכה, שומרת על פרטים ביטול אפקט מדרגות המדרגות, החלקה אמנות/רישומים סרוקים, דחיסה מתונה, מואר פסי צבע איטי, מסיר חצאי גוונים צבעוני כללי לתמונות בגווני אפור/לבלב, לתוצאות טובות יותר השתמש ב-DDColor הסרת קצוות מסיר חידוד יתר לאט, מתערער אנטי-aliasing, חפצים כלליים, CGI KDM003 סורק עיבוד דגם קל משקל לשיפור תמונה הסרת חפצי דחיסה הסרת חפצי דחיסה הסרת תחבושת עם תוצאות חלקות עיבוד דפוסי חצי גוון הסרת דפוסי זוהר V3 הסרת חפצי JPEG V2 שיפור מרקם H.264 חידוד ושיפור VHS מיזוג גודל נתח גודל חפיפה תמונות מעל %1$s פיקסלים יפורסו ויעובדו בחתיכות, חופפות מיזוג אלה כדי למנוע תפרים גלויים. גדלים גדולים עלולים לגרום לחוסר יציבות במכשירים מתקדמים בחר אחד כדי להתחיל האם ברצונך למחוק מודל %1$s? תצטרך להוריד אותו שוב לְאַשֵׁר דגמים מודלים שהורדו דגמים זמינים עֲרִיכָה דגם פעיל פתיחת ההפעלה נכשלה ניתן לייבא רק דגמי .onnx/.ort דגם ייבוא יבא מודל onnx מותאם אישית לשימוש נוסף, רק דגמי onnx/ort מתקבלים, תומך כמעט בכל הגרסאות הדומות ל-esrgan דגמים מיובאים רעש כללי, תמונות צבעוניות רעש כללי, תמונות צבעוניות, חזק יותר רעש כללי, תמונות צבעוניות, החזק ביותר מפחית חפצים מטלטלים ורצועות צבע, משפר שיפועים חלקים ואזורי צבע שטוחים. משפר את הבהירות והניגודיות של התמונה עם הבהרה מאוזנת תוך שמירה על צבעים טבעיים. מבהיר תמונות כהות תוך שמירה על פרטים והימנעות מחשיפת יתר. מסיר גוון צבע מוגזם ומשחזר איזון צבע ניטרלי וטבעי יותר. מחיל גוון רעש מבוסס Poisson עם דגש על שימור פרטים ומרקמים עדינים. מחיל גוון רעש רך של Poisson לתוצאות חזותיות חלקות ופחות אגרסיביות. גוון רעש אחיד המתמקד בשימור פרטים ובהירות התמונה. גוון רעש עדין אחיד למרקם עדין ומראה חלק. מתקן אזורים פגומים או לא אחידים על ידי צביעה מחדש של חפצים ושיפור עקביות התמונה. דגם קל משקל המסיר פסי צבע בעלות ביצועים מינימלית. מייעל תמונות עם חפצי דחיסה גבוהים מאוד (איכות 0-20%) לשיפור הבהירות. משפר תמונות עם חפצי דחיסה גבוהים (איכות 20-40%), שחזור פרטים והפחתת רעש. משפר תמונות עם דחיסה מתונה (איכות 40-60%), מאזן חדות וחלקות. מחדד תמונות עם דחיסה קלה (איכות 60-80%) כדי לשפר פרטים ומרקמים עדינים. משפר מעט תמונות כמעט ללא אובדן (איכות 80-100%) תוך שמירה על מראה ופרטים טבעיים. צביעה פשוטה ומהירה, קריקטורות, לא אידיאלי מפחית מעט את טשטוש התמונה, משפר את החדות מבלי להכניס חפצים. פעולות ארוכות טווח מעבד תמונה עיבוד מסיר חפצי דחיסה כבדים של JPEG בתמונות באיכות נמוכה מאוד (0-20%). מפחית חפצי JPEG חזקים בתמונות דחוסות מאוד (20-40%). מנקה חפצי JPEG מתונים תוך שמירה על פרטי התמונה (40-60%). מחדד חפצי JPEG קלים בתמונות באיכות גבוהה למדי (60-80%). מפחית בעדינות חפצי JPEG קלים בתמונות כמעט ללא אובדן (80-100%). משפר פרטים ומרקמים עדינים, משפר את החדות הנתפסת ללא חפצים כבדים. העיבוד הסתיים העיבוד נכשל משפר את מרקמי העור ואת הפרטים תוך שמירה על מראה טבעי, מותאם למהירות. מסיר חפצי דחיסת JPEG ומשחזר את איכות התמונה עבור תמונות דחוסות. מפחית את רעשי ה-ISO בתמונות שצולמו בתנאי תאורה חלשים, תוך שמירה על פרטים. מתקן הדגשות חשופות יתר או \"ג\'מבו\" ומשחזר איזון טונאלי טוב יותר. דגם צביעה קל משקל ומהיר המוסיף צבעים טבעיים לתמונות בגווני אפור. DEJPEG דנואיז צבע חפצים לְהַגבִּיר אנימה סריקות יוקרתי X4 upscaler לתמונות כלליות; דגם קטנטן שמשתמש בפחות GPU וזמן, עם טשטוש ו-denoise מתונים. X2 upscaler לתמונות כלליות, שמירה על טקסטורות ופרטים טבעיים. X4 יוקרתי לתמונות כלליות עם טקסטורות משופרות ותוצאות מציאותיות. X4 upscaler מותאם לתמונות אנימה; 6 בלוקים RRDB לקווים ופרטים חדים יותר. X4 upscaler עם אובדן MSE, מייצר תוצאות חלקות יותר וחפצים מופחתים עבור תמונות כלליות. X4 Upscaler מותאם לתמונות אנימה; גרסת 4B32F עם פרטים חדים יותר וקווים חלקים. דגם X4 UltraSharp V2 לתמונות כלליות; מדגיש חדות ובהירות. X4 UltraSharp V2 Lite; מהיר יותר וקטן יותר, שומר על פרטים תוך שימוש בפחות זיכרון GPU. דגם קל משקל להסרת רקע מהירה. ביצועים ודיוק מאוזנים. עובד עם פורטרטים, אובייקטים וסצנות. מומלץ לרוב מקרי השימוש. הסר את BG עובי גבול אופקי עובי גבול אנכי %1$s צבעים %1$s צבעים %1$s צבעים %1$s צבעים הדגם הנוכחי אינו תומך ב-chunking, התמונה תעובד במידות מקוריות, הדבר עלול לגרום לצריכת זיכרון גבוהה ולבעיות במכשירים מתקדמים Chunking מושבת, התמונה תעובד במידות מקוריות, זה עלול לגרום לצריכת זיכרון גבוהה ולבעיות במכשירים מתקדמים אך עשוי לתת תוצאות טובות יותר בהסקת מסקנות צ\'אנקינג מודל פילוח תמונה ברמת דיוק גבוהה להסרת רקע גרסה קלת משקל של U2Net להסרת רקע מהירה יותר עם שימוש קטן יותר בזיכרון. דגם DDColor מלא מספק צביעה באיכות גבוהה לתמונות כלליות עם חפצים מינימליים. הבחירה הטובה ביותר מכל דגמי הצביעה. מערכי נתונים אומנותיים פרטיים מאומנים ב-DDColor; מייצר תוצאות צביעה מגוונות ואמנותיות עם פחות חפצי צבע לא מציאותיים. דגם BiRefNet קל משקל המבוסס על Swin Transformer להסרת רקע מדויקת. הסרת רקע איכותית עם קצוות חדים ושימור פרטים מעולה, במיוחד על אובייקטים מורכבים ורקעים מסובכים. דגם הסרת רקע המייצר מסכות מדויקות עם קצוות חלקים, מתאים לחפצים כלליים ולשימור מתון לפרטים. הדגם כבר הורד הדגם יובא בהצלחה סוּג מילת מפתח מהיר מאוד נוֹרמָלִי לְהַאֵט מאוד איטי חישוב אחוזים הערך המינימלי הוא %1$s עיוות תמונה על ידי ציור עם אצבעות לְעַקֵם קַשִׁיוּת מצב עיוות מַהֲלָך לִגדוֹל לְצַמֵק מערבולת CW סיבוב CCW חוזק דעיכה Top Drop ירידה למטה התחל ירידה סוף ירידה מוריד צורות חלקות השתמש בסופראליפסות במקום במלבנים מעוגלים סטנדרטיים לקבלת צורות חלקות וטבעיות יותר סוג צורה גְזִירָה מְעוּגָל לְהַחלִיק קצוות חדים ללא עיגול פינות מעוגלות קלאסיות סוג צורות גודל פינות סקוורקל רכיבי ממשק משתמש מעוגלים ואלגנטיים פורמט שם קובץ טקסט מותאם אישית ממוקם ממש בתחילת שם הקובץ, מושלם עבור שמות פרויקטים, מותגים או תגים אישיים. משתמש בשם הקובץ המקורי ללא סיומת, ועוזר לך לשמור על זיהוי המקור ללא פגע. רוחב התמונה בפיקסלים, שימושי למעקב אחר שינויים ברזולוציה או שינוי קנה מידה של תוצאות. גובה התמונה בפיקסלים, מועיל בעבודה עם יחסי גובה-רוחב או ייצוא. יוצר ספרות אקראיות כדי להבטיח שמות קבצים ייחודיים; הוסף ספרות נוספות לבטיחות נוספת מפני כפילויות. מונה הגדלה אוטומטית ליצוא אצווה, אידיאלי בעת שמירת תמונות מרובות בסשן אחד. מכניס את השם המוגדר מראש שהוחל לשם הקובץ כך שתוכל לזכור בקלות כיצד עבדה התמונה. מציג את מצב קנה המידה של התמונה המשמש במהלך העיבוד, ועוזר להבחין בין תמונות שגודלו, חתכו או הותאמו. טקסט מותאם אישית ממוקם בסוף שם הקובץ, שימושי לניהול גרסאות כמו _v2, _edited או _final. סיומת הקובץ (png, jpg, webp וכו\'), תואמת אוטומטית לפורמט השמור בפועל. חותמת זמן הניתנת להתאמה אישית המאפשרת לך להגדיר פורמט משלך לפי מפרט Java למיון מושלם. סוג השלכה Android Native סגנון iOS עקומה חלקה עצירה מהירה קופצני צף נִמרָץ אולטרה חלק הסתגלות מודע לנגישות תנועה מופחתת פיזיקת גלילה מקורית של אנדרואיד להשוואה בסיסית גלילה מאוזנת וחלקה לשימוש כללי חיכוך גבוה יותר התנהגות גלילה דמוית iOS עקומת ספליין ייחודית לתחושת גלילה ברורה גלילה מדויקת עם עצירה מהירה גלילה קופצנית שובבה ומגיבה מגילות ארוכות וגולשות לגלישה בתוכן גלילה מהירה ומגיבה עבור ממשקי משתמש אינטראקטיביים גלילה חלקה מובחרת עם מומנטום מורחב מתאים את הפיזיקה על סמך מהירות הטיפה מכבד את הגדרות נגישות המערכת תנועה מינימלית לצרכי נגישות קווים ראשיים מוסיף קו עבה יותר בכל שורה חמישית צבע מילוי כלים נסתרים כלים מוסתרים לשיתוף ספריית צבע עיין באוסף עצום של צבעים מחדד ומסיר טשטוש מתמונות תוך שמירה על פרטים טבעיים, אידיאלי לתיקון תמונות לא ממוקדות. משחזר באופן אינטליגנטי תמונות ששונו בעבר, משחזר פרטים ומרקמים שאבדו. מותאם לתוכן חי, מפחית חפצי דחיסה ומשפר פרטים עדינים במסגרות של סרטים/תוכניות טלוויזיה. ממיר קטעים באיכות VHS ל-HD, מסיר רעשי קלטת ושיפור הרזולוציה תוך שמירה על תחושת וינטג\'. מתמחה לתמונות וצילומי מסך עתירי טקסט, מחדד תווים ומשפר את הקריאה. שיפור קנה מידה מתקדם מאומן על מערכי נתונים מגוונים, מצוין לשיפור צילום למטרות כלליות. מותאם לתמונות דחוסות באינטרנט, מסיר חפצי JPEG ומשחזר מראה טבעי. גרסה משופרת לתמונות אינטרנט עם שימור מרקם טוב יותר והפחתת חפצים. העלאת קנה מידה פי 2 עם טכנולוגיית Dual Aggregation Transformer, שומרת על חדות ופרטים טבעיים. הגדלה פי 3 באמצעות ארכיטקטורת שנאים מתקדמת, אידיאלית לצרכי הגדלה מתונים. שיפוץ קנה מידה איכותי פי 4 עם רשת שנאים מתקדמת, משמרת פרטים עדינים בקנה מידה גדול יותר. מסיר טשטוש/רעש ורעידות מתמונות. מטרה כללית אבל הכי טובה בתמונות. משחזר תמונות באיכות נמוכה באמצעות שנאי Swin2SR, מותאם לפירוק BSRGAN. נהדר לתיקון חפצי דחיסה כבדים ושיפור פרטים בקנה מידה פי 4. העלאת קנה מידה פי 4 עם שנאי SwinIR מאומן על השפלה של BSRGAN. משתמש ב-GAN למרקמים חדים יותר ופרטים טבעיים יותר בתמונות ובסצנות מורכבות. נָתִיב מיזוג PDF שלב קובצי PDF מרובים למסמך אחד סדר קבצים עמ. פיצול PDF חלץ דפים ספציפיים ממסמך PDF סובב PDF תקן את כיוון העמוד לצמיתות דפים סידור מחדש של PDF גרור ושחרר דפים כדי לסדר אותם מחדש החזק וגרור דפים מספרי עמודים הוסף מספור למסמכים שלך באופן אוטומטי פורמט תווית PDF לטקסט (OCR) חלץ טקסט רגיל ממסמכי ה-PDF שלך כיסוי טקסט מותאם אישית למיתוג או אבטחה חֲתִימָה הוסף את החתימה האלקטרונית שלך לכל מסמך זה ישמש כחתימה ביטול נעילת PDF הסר סיסמאות מהקבצים המוגנים שלך הגן על PDF אבטח את המסמכים שלך עם הצפנה חזקה הַצלָחָה PDF לא נעול, אתה יכול לשמור או לשתף אותו תיקון PDF ניסיון לתקן מסמכים פגומים או בלתי קריאים גווני אפור המר את כל התמונות המוטבעות במסמכים לגווני אפור דחוס PDF בצע אופטימיזציה של גודל קובץ המסמך שלך לשיתוף קל יותר ImageToolbox בונה מחדש את טבלת ההפניות הפנימית ומחדשת את מבנה הקובץ מאפס. זה יכול לשחזר גישה לקבצים רבים ש\\"לא ניתן לפתוח\\" כלי זה ממיר את כל תמונות המסמכים לגווני אפור. הטוב ביותר להדפסה ולהקטנת גודל הקובץ מטא נתונים ערוך מאפייני מסמך לפרטיות טובה יותר תגים יַצרָן מְחַבֵּר מילות מפתח יוֹצֵר פרטיות ניקוי עמוק נקה את כל המטא נתונים הזמינים עבור מסמך זה עַמוּד OCR עמוק חלץ טקסט מהמסמך ואחסן אותו בקובץ הטקסט האחד באמצעות מנוע Tesseract לא ניתן להסיר את כל הדפים הסר דפי PDF הסר דפים ספציפיים ממסמך PDF הקש כדי להסיר באופן ידני חיתוך PDF חתוך דפי מסמכים לכל גבול שטח PDF הפוך את PDF לבלתי ניתן לשינוי על ידי רסטר דפי מסמכים לא ניתן להפעיל את המצלמה. אנא בדוק הרשאות וודא שהיא לא בשימוש על ידי אפליקציה אחרת. חלץ תמונות חלץ תמונות המוטבעות בקובצי PDF ברזולוציה המקורית שלהן קובץ PDF זה אינו מכיל תמונות מוטבעות כלי זה סורק כל עמוד ומשחזר תמונות מקור באיכות מלאה - מושלם לשמירת מסמכי מקור ממסמכים צייר חתימה עט פרמס השתמש בחתימה משלך כתמונה שתוצב על מסמכים Zip PDF פצל מסמך עם מרווח נתון וארוז מסמכים חדשים לארכיון zip הַפסָקָה הדפס PDF הכן מסמך להדפסה עם גודל עמוד מותאם אישית דפים לגיליון הִתמַצְאוּת גודל עמוד מֶתַח לִפְרוֹחַ ברך רכה מותאם לאנימה וסרטים מצוירים. שיפור קנה מידה מהיר עם צבעים טבעיים משופרים ופחות חפצים סגנון כמו Samsung One UI 7 הזן כאן סמלים מתמטיים בסיסיים כדי לחשב את הערך הרצוי (למשל (5+5)*10) ביטוי מתמטי אסוף עד %1$s תמונות שמור על תאריך שעון שמור תמיד תגיות exif הקשורות לתאריך ושעה, עובד ללא תלות באפשרות Keep exif צבע רקע עבור פורמטי אלפא מוסיף יכולת להגדיר צבע רקע עבור כל פורמט תמונה עם תמיכה באלפא, כאשר מושבת זה זמין עבור לא אלפא בלבד פרויקט פתוח המשך לערוך פרויקט תמונה כלים שנשמר בעבר לא ניתן לפתוח את פרויקט Image Toolbox בפרויקט Image Toolbox חסרים נתוני פרויקט פרויקט Image Toolbox פגום גרסת פרויקט Image Toolbox לא נתמכת: %1$d שמור פרויקט אחסן שכבות, רקע והיסטוריית עריכה בקובץ פרויקט הניתן לעריכה הפתיחה נכשלה כתוב ל-PDF הניתן לחיפוש זיהוי טקסט מאצוות תמונה ושמור PDF שניתן לחיפוש עם תמונה ושכבת טקסט לבחירה שכבת אלפא היפוך אופקי Flip אנכי לִנְעוֹל הוסף צל צבע צל גיאומטריית טקסט למתוח או להטות טקסט לסטייליזציה חדה יותר סולם X עיוות X הסר הערות הסר סוגי הערות שנבחרו כגון קישורים, הערות, הדגשות, צורות או שדות טופס מדפי PDF היפר-קישורים קבצים מצורפים קווים חלונות קופצים חותמות צורות הערות טקסט סימון טקסט שדות טופס סימון לֹא יְדוּעַ הערות בטל קבוצה הוסף צל טשטוש מאחורי השכבה עם צבע והיסטים הניתנים להגדרה ================================================ FILE: core/resources/src/main/res/values-ja/strings.xml ================================================ 問題が発生しました: %1$s 最新のアップデートを入手し、問題を議論するなど アプリについて 検索クエリに一致するものが見つかりませんでした サイズ %1$s 読み込み中… 画像はプレビューするには大きすぎますが、保存は試みます 画像を選択して開始 幅 %1$s 高さ %1$s 品質 拡張子 リサイズタイプ 絶対指定 相対指定 画像を選択 アプリを終了してもよろしいですか? アプリ終了 残る 閉じる 画像をリセット 画像の変更が初期値に戻ります 値が正しくリセットされました リセット 問題が発生しました アプリを再起動 クリップボードにコピーしました 例外 EXIF編集 OK EXIF データが見つかりませんでした タグを追加 保存 クリア EXIFをクリア キャンセル 画像のすべてのEXIFデータが消去されます。この操作は元に戻せません! プリセット 切り抜き 保存中 今終了すると、保存されていない変更はすべて失われます ソースコード 単一編集 指定された単一の画像の仕様を変更 色を抽出 画像から色を選んで、コピーまたは共有 画像 色をコピーしました 画像: %d 画像を任意の範囲でトリミング バージョン EXIFを保持 プレビューを変更 削除 指定された画像からカラーパレットスウォッチを生成 パレットを生成 パレット 更新 新バージョン %1$s サポートされていないタイプ: %1$s この画像のパレットを生成できません オリジナル 出力フォルダ デフォルト カスタム 未指定 デバイスストレージ サイズ指定でリサイズ KBでの最大サイズ KB単位で指定したサイズに画像をリサイズ 比較 指定された2つの画像を比較 開始するには2つの画像を選択してください 画像を選択 設定 ナイトモード ダーク ライト システム ダイナミックカラー カスタマイズ 画像モネを許可 有効にすると、編集する画像を選択したとき、アプリの色がその画像に合わせて調整されます 言語 AMOLED モード 有効にすると、ナイトモードでサーフェスカラーが完全な黒に設定されます カラースキーム 有効なaRGBコードを貼り付けてください 貼り付けるものがありません ダイナミックカラーがオンの間はアプリの配色を変更できません アプリテーマは選択した色に基づいて設定されます アップデートが見つかりません 問題追跡 バグレポートや機能リクエストはこちらへ 翻訳を手伝う 翻訳の間違いを修正したり、プロジェクトを他の言語にローカライズしたりする ここで検索 有効にすると、アプリの色が壁紙の色に合わせて調整されます %d個の画像の保存に失敗しました 追加 権限 枠線の太さ サーフェス このアプリケーションは完全に無料ですが、プロジェクト開発をサポートしたい場合はここをクリックしてください 外部ストレージ FABの配置 アップデートの確認 有効にすると、アプリ起動時にアップデートダイアログが表示されます プライマリ 第三 セカンダリ 許可 アプリが機能するには、画像を保存するためにストレージへのアクセス権が必要です。次のダイアログボックスで権限を許可してください。 画像ズーム 接頭辞 ファイル名 アプリが機能するにはこの権限が必要です。手動で許可してください 共有 Monetカラー 絵文字 メイン画面に表示する絵文字を選択 ファイルサイズを追加 有効にすると、出力ファイルの名前に保存済み画像の幅と高さが追加されます EXIFを削除 任意の画像セットからEXIFメタデータを削除 画像プレビュー GIF、SVGなど、あらゆるタイプの画像をプレビュー 画面下部に表示されるAndroidのモダンなフォトピッカーで、Android 12以上でのみ動作する可能性があります。EXIFメタデータの取得に問題がある場合があります 画像ソース フォトピッカー 画像を選択するためにGetContentインテントを使用します。どこでも動作しますが、一部のデバイスでは選択した画像の取得に問題があることが知られています。これは私のせいではありません。 オプションの配置 絵文字の数 連番 元のファイル名 元のファイル名を追加 有効にすると、出力画像の名前に元のファイル名が追加されます 連番を置き換え 有効にすると、バッチ処理を使用する場合、標準のタイムスタンプが画像の連番に置き換えられます フォトピッカー画像ソースを選択している場合、元のファイル名の追加は機能しません ギャラリー ファイルエクスプローラー シンプルなギャラリー画像ピッカー。メディアピッキングを提供するアプリがある場合のみ機能します 編集 順序 メイン画面上のツールの順序を決定します ハイライト シャドウ シャープ クロスハッチ 間隔 線の幅 画像リンク 塗りつぶし フィルター追加 フィルター モノクロ 渦巻き 膨張 拡張 球面屈折 カラーマトリックス 制限付きサイズ変更 非最大抑制 スタックぼかし 輝度しきい値 第二色 Webから画像を読み込む インターネットから任意の画像を読み込んでプレビュー、ズーム、編集し、必要に応じて保存できます。 フィット 指定された高さと幅に画像のサイズを変更します。画像のアスペクト比が変わる可能性があります。 長辺を指定した幅または高さに合わせて画像をリサイズします。サイズ計算はすべて保存後に行われ、アスペクト比は維持されます。 明るさ コントラスト 色相 彩度 画像に一連のフィルターを適用する フィルター ライト カラーフィルター アルファ 露出 ホワイトバランス 色温度 色合い ガンマ ハイライトとシャドウ ヘイズ エフェクト 距離 傾き セピア ネガティブ ソラリゼーション バイブランス 白黒 ソーベルエッジ ぼかし ハーフトーン CGAカラースペース ガウスぼかし ボックスぼかし バイラテラルぼかし エンボス ラプラシアン ビネット 開始 終了 桑原スムージング 半径 スケール 歪み 角度 屈折率 ガラス球体屈折 不透明度 アスペクト比を維持しながら、指定された高さと幅に画像のサイズを変更します スケッチ しきい値 量子化レベル スムーズトゥーン トゥーン ポスタライズ 弱ピクセル包含 ルックアップ 畳み込み 3x3 RGBフィルター 偽色 第一色 並べ替え 高速ぼかし ぼかしサイズ ぼかし中心X ぼかし中心Y ズームぼかし カラーバランス 画像がありません コンテンツスケール スクリーンショット スキップ パスワードが無効か、選択したファイルが暗号化されていません 自動キャッシュクリア 背景に描画 ファイルを選択 このファイルをデバイスに保存するか、共有アクションを使用して好きな場所に配置してください 機能 キャッシュ セカンダリカスタマイズ フォールバックオプション 復号化 開始するにはファイルを選択してください 復号化 互換性 AES-256をGCMモードで動作させ、パディングは行いません。デフォルトでは12バイトのランダムIVを使用します。必要に応じてアルゴリズムを選択でき、鍵には256ビット長のSHA-3ハッシュを用います。 ファイルサイズ ファイル処理完了 最大ファイルサイズはAndroid OSと利用可能なメモリによって制限されており、これはデバイスに依存します。 \n注意:メモリはストレージではありません。 他のファイル暗号化ソフトウェアやサービスとの互換性は保証されないことにご注意ください。キーの扱いや暗号の設定がわずかに異なると問題が発生する可能性があります 指定した幅と高さで画像を保存しようとすると、メモリ不足のエラーが発生することがあります。 自己責任で行ってください。 キャッシュサイズ 有効にするとアプリ起動時にアプリキャッシュがクリアされます ツール タイプ別にオプションをグループ化 メイン画面上のオプションをカスタムリスト配置ではなく、タイプ別にグループ化します 画像を選択してその上に何かを描画します 暗号化 実装 %1$sが見つかりました オプショングループ化が有効な間は配置を変更できません スクリーンショットを編集 コピー %1$sモードでの保存は、可逆フォーマットであるため不安定な場合があります ファイルアプリを無効にしました。この機能を使用するには有効にしてください 描画 スケッチブックのように画像に描画したり、背景自体に描画したりします ペイントの色 ペイントの透明度 画像に描画 背景色を選択してその上に描画します 背景色 暗号化 様々な暗号化アルゴリズムを基にファイル(画像だけでなく)を暗号化/復号化します 暗号化 キー パスワードベースのファイル暗号化。処理されたファイルは選択したディレクトリに保存するか共有することができます。復号化されたファイルも直接開くことができます。 作成 拡張ピクセル化 ストロークピクセル化 ダイヤモンドピクセル化 Telegramチャット 有効にすると出力ファイル名が完全にランダムになります 食べ物と飲み物 メール 復元 背景除去 描画または自動オプションを使用して画像から背景を削除します 画像のトリミング バックアップと復元 指定した画像からマスクを作成するにはこのマスクタイプを使用します。アルファチャンネルが必要なことに注意してください ファイル名をランダム化 スキームを削除 テキスト デフォルト フォント ここでの設定値は出力ファイルの容量の%を決定します。つまり5MBの画像で50を選択すると、2.5MBの画像として保存されます。 消去モード 更新 自動方向とスクリプト検出 単一ブロック垂直テキスト グリッチ シード 選択したフィルターマスクを削除しようとしています。この操作は元に戻せません \"%1$s\"ディレクトリが見つかりません。デフォルトに切り替えましたので、ファイルを再度保存してください クリップボード 自動ピン ファイルを上書きするには「エクスプローラー」画像ソースを使用する必要があります。画像を再選択してください。必要な画像ソースに変更しました ファイルを上書き 接尾辞 自由 アプリについて議論し、他のユーザーからフィードバックを得ることができます。ベータ版のアップデートや洞察もここで得られます。 切り抜きマスク アスペクト比 バックアップ アプリの設定をファイルにバックアップします 以前に生成されたファイルからアプリの設定を復元します 連絡先 これにより設定がデフォルト値に戻ります。上記のバックアップファイルがなければ元に戻せないことに注意してください 破損したファイルまたはバックアップではありません 設定が正常に復元されました 削除 フォントスケール 大きなフォントスケールを使用すると、UIの不具合や問題が発生する可能性があり、それらは修正されません。慎重に使用してください。 あ い う え お か き く け こ さ し す せ そ た ち つ て と な に ぬ ね の は ひ ふ へ ほ ま み む め も や ゆ よ ら り る れ ろ わ を ん 0123456789 !? 感情 物体 記号 絵文字を有効化 旅行と場所 活動 画像周りの透明なスペースがトリミングされます 背景の自動消去 画像を復元 背景を消去 背景を復元 ぼかし半径 描画モード おっと…何か問題が発生しました。以下のオプションを使用して私に連絡してください。解決策を見つけようとします 指定された画像のサイズを変更したり、他の形式に変換したりします。単一の画像を選択する場合はEXIFメタデータもここで編集できます。 現在、%1$s形式ではAndroidでのEXIFメタデータの読み取りのみが可能です。保存されると出力画像にはメタデータがまったく含まれません。 有効にすると、アップデートチェックにはベータ版のアプリバージョンも含まれます 矢印を描画 有効にすると描画パスが指す矢印として表示されます ブラシの柔らかさ 寄付 少なくとも2つの画像を選択してください 画像の向き 水平 垂直 小さな画像を大きくする 有効にすると、小さい画像はシーケンス内の最大の画像にスケーリングされます 画像の順序 有効にすると、元の画像の下にぼかしたエッジを描画して、単色の代わりに周囲のスペースを埋めます 拡張ダイヤモンドピクセル化 円形ピクセル化 ターゲット色 削除する色 色を削除 再コード化 描画モードで有効にすると、画面が回転しなくなります アップデートを確認 パレットスタイル トーナルスポット ニュートラル ビブラント エクスプレッシブ フルーツサラダ フィデリティ コンテンツ デフォルトのパレットスタイルで、4色すべてをカスタマイズできます。他のスタイルではキーカラーのみを設定できます モノクロよりもわずかに彩度の高いスタイル 派手なテーマ、プライマリパレットの彩度が最大で、他のパレットでも増加します プレイフルなテーマ - 元の色相がテーマに表示されません モノクロのテーマ、色は純粋な黒/白/グレーです ソースカラーをScheme.primaryContainerに配置するスキーム コンテンツスキームに非常に似たスキーム このアップデートチェッカーは、新しいアップデートがあるかどうかを確認するためにGitHubに接続します 注意 フェードエッジ 無効 両方 有効にするとテーマの色を反転した色に置き換えます 検索 メインスクリーンで利用可能なすべてのツールを検索する機能を有効にします PDFツール PDFを指定した出力形式の画像に変換 指定した画像を出力PDFファイルにパッケージ化 マスクを追加 マスク %d マスクプレビュー 描画されたフィルターマスクがレンダリングされ、おおよその結果が表示されます 有効にすると、デフォルトの動作ではなく、マスクされていないすべての領域がフィルターされます マスクを削除 指定された画像または単一画像にフィルターチェーンを適用します シンプルバリアント ハイライター ペン プライバシーぼかし 半透明で先鋭化されたハイライターパスを描画 描画に光る効果を追加 デフォルト、最もシンプル - 色だけ 隠したいものを保護するために描画されたパスの下の画像をぼかします プライバシーぼかしに似ていますが、ぼかす代わりにピクセル化します FAB ボタン スライダーの背後に影を描画する スイッチの背後に影を描画する フローティングアクションボタンの背後に影を描画する ボタンの背後に影を描画する アプリバー アプリバーの背後に影を描画する 線矢印 矢印 入力値としてパスを描画 開始点から終了点までのパスを線として描画 開始点から終了点まで線として指し示す矢印を描画 指定されたパスから指し示す矢印を描画 矩形 開始点から終了点まで矩形を描画 結合モード 行数 列数 有効にすると保存した画像を自動的にクリップボードに追加します 振動 振動の強さ 元のファイルは選択したフォルダに保存する代わりに新しいものに置き換えられます。このオプションには画像ソースが「エクスプローラー」またはGetContentである必要があります 線形(または二次元ではバイリニア)補間は、画像のサイズ変更には一般的に適していますが、望ましくない詳細のぼかしを引き起こします より優れたスケーリング方法には、ランチョスリサンプリングやミッチェル-ネトラバリフィルターがあります サイズを大きくする最も単純な方法の1つで、各ピクセルを同じ色のピクセル数に置き換えます ほとんどすべてのアプリで使用されている最もシンプルなAndroidスケーリングモード 滑らかな曲線を作成するためにコンピュータグラフィックスでよく使用される、制御点セットを滑らかに補間およびリサンプリングする方法 信号のエッジをテーパリングすることでスペクトルリークを最小限に抑え、周波数分析の精度を向上させるために信号処理でよく適用されるウィンドウ関数 曲線セグメントのエンドポイントでの値と導関数を使用して滑らかで連続的な曲線を生成する数学的補間技術 ピクセル値に重み付けされたsinc関数を適用することで高品質の補間を維持するリサンプリング方法 スケーリングされた画像の鮮明さとアンチエイリアシングのバランスを取るために調整可能なパラメータを持つ畳み込みフィルターを使用するリサンプリング方法 区分的に定義された多項式関数を使用して、曲線または面を滑らかに補間および近似し、柔軟で連続的な形状表現を提供します ストレージへの保存は行われず、クリップボードにのみ画像を配置しようとします ブラシは消去する代わりに背景を復元します 初期値を強制 EXIFウィジェットが最初にチェックされるよう強制します 複数言語を許可 スライド 並べて表示 タップで切り替え 透明度 このアプリは完全に無料です。もっと大きくしたい場合は、Githubでプロジェクトにスターをつけてください 😄 方向とスクリプト検出のみ 自動のみ 自動 単一列 単一ブロック 単一行 単一単語 円形の単語 単一文字 まばらなテキスト まばらなテキストの方向とスクリプト検出 生の行 言語「%1$s」のOCRトレーニングデータをすべての認識タイプで削除しますか、それとも選択したタイプ(%2$s)のみで削除しますか? すべて グラデーション作成 カスタマイズされた色と外観タイプで指定された出力サイズのグラデーションを作成 中心Y タイルモード 繰り返し ミラー クランプ デカール カラーストップ 色を追加 プロパティ カメラ 写真を撮るためにカメラを使用します。この画像ソースからは1つの画像しか取得できないことに注意してください カスタマイズ可能なテキスト/画像ウォーターマークで写真をカバー この画像はウォーターマークのパターンとして使用されます 繰り返し回数 フレーム遅延 ミリ秒 FPS 最近使用したアプリのアプリコンテンツを非表示にします。キャプチャや録画はできません。 量子化器 グレースケール ベイヤー2×2ディザリング ベイヤー4×4ディザリング ベイヤー8×8ディザリング フロイドスタインバーグディザリング ジャービス・ジュディス・ニンケディザリング シエラライトディザリング アトキンソンディザリング メディアンぼかし 曲線や表面を滑らかに補間および近似するために区分的に定義された双三次多項式関数を利用し、柔軟で連続的な形状表現を提供します ネイティブスタックぼかし チルトシフト アナグリフ ノイズ ピクセルソート シャッフル 拡張グリッチ チャンネルシフトX チャンネルシフトY 破損サイズ 破損シフトX 破損シフトY テントぼかし サイドフェード 側面 上部 下部 強度 フラクタルガラス ACESヒルトーンマッピング カラーマトリックス3x3 シンプルエフェクト ポラロイド トリタノマリー プロタノマリー ヴィンテージ ブラウニ コダクローム ナイトビジョン 暖色 寒色 トリタノピア デューテラノピア プロタノピア アクロマトマリー アクロマトプシア ピンクドリーム ゴールデンアワー ホットサマー パープルミスト 日の出 カラフルスワール ソフトスプリングライト 秋のトーン ラベンダードリーム サイバーパンク レモネードライト スペクトラルファイア ナイトマジック ファンタジーランドスケープ カラーエクスプロージョン エレクトリックグラデーション キャラメルダークネス ディープパープル シャッフルぼかし お気に入りフィルターはまだ追加されていません カードの先頭アイコンの下に選択された形状のコンテナを追加します アイコンの形状 ドラゴ アルドリッジ カットオフ ウチムラ メビウス トランジション ピーク 色異常 画像は元の場所で上書きされました ファイル上書きオプションが有効な間は画像フォーマットを変更できません 絵文字をカラースキームとして使用 手動で定義されたものの代わりに絵文字のプライマリカラーをアプリのカラースキームとして使用 %1$sフォルダに%2$sという名前で保存されました %1$sフォルダに保存されました 選択したカラースキームを削除しようとしています。この操作は元に戻せません 自然と動物 元の画像のメタデータが保持されます スポイト 問題を作成 サイズ変更と変換 保存がほぼ完了しました。今キャンセルすると再度保存する必要があります。 ベータ版を許可 画像は入力されたサイズに中央でトリミングされます。画像が入力された寸法より小さい場合、キャンバスは指定された背景色で拡張されます。 拡張円形ピクセル化 色の置換 許容値 置換する色 侵食 異方性拡散 拡散 伝導 水平風ずらし 高速バイラテラルぼかし ポアソンぼかし 対数トーンマッピング 結晶化 ストロークの色 振幅 マーブル 乱流 油彩 水の効果 サイズ 周波数X 周波数Y パーリン歪み 振幅X 振幅Y ハーブルフィルムトーンマッピング ヘイジルバージェストーンマッピング ACESフィルムトーンマッピング 現在 フルフィルター 開始 中央 終了 PDFファイルを操作:プレビュー、画像バッチへの変換、または指定した写真からの作成 PDFプレビュー PDFから画像へ 画像からPDFへ シンプルなPDFプレビュー 速度 デヘイズ オメガ アプリを評価 評価 カラーマトリックス4x4 デューテラノマリー 線形 放射状 スイープ グラデーションタイプ 中心X 投げ縄 指定されたパスで閉じた塗りつぶしパスを描画 描画パスモード 二重線矢印 自由描画 二重矢印 開始点から終了点まで線として二重の指し示す矢印を描画 指定されたパスから二重の指し示す矢印を描画 アウトライン楕円 アウトライン矩形 楕円 開始点から終了点まで楕円を描画 開始点から終了点までアウトラインのある楕円を描画 開始点から終了点までアウトラインのある矩形を描画 ディザリング ベイヤー3×3ディザリング シエラディザリング 2行シエラディザリング スタッキディザリング バークスディザリング 疑似フロイドスタインバーグディザリング 左から右へのディザリング ランダムディザリング シンプルしきい値ディザリング 最大色数 アプリがクラッシュレポートを自動的に収集できるようにします 分析 匿名のアプリ使用統計情報の収集を許可する エフォート %1$sの値は高速圧縮を意味し、比較的大きなファイルサイズになります。%2$sはより遅い圧縮を意味し、より小さなファイルになります。 お待ちください マスクの色 スケールモード バイリニア ランチョス ミッチェル ハン エルミート ニアレスト スプライン ベーシック デフォルト値 プリセット125を選択した場合、画像は元のサイズの125%のサイズで保存されます。プリセット50を選択した場合、画像は元のサイズの50%で保存されます 範囲内の値 %1$s - %2$s シグマ 空間シグマ キャトムル バイキュービック クリップのみ 画像結合 指定された画像を組み合わせて1つの大きな画像を作成します 明るさの強制 スクリーン グラデーションオーバーレイ 指定された画像の上に任意のグラデーションを構成 変形 出力画像のスケール グレイン アンシャープ パステル オレンジヘイズ 未来的グラデーション グリーンサン レインボーワールド スペースポータル レッドスワール デジタルコード ウォーターマーク ウォーターマークを繰り返す 指定された位置に単一ではなく、画像全体にウォーターマークを繰り返します オフセットX オフセットY ウォーターマークタイプ テキストの色 オーバーレイモード ピクセルサイズ 描画方向をロック ボケ GIFツール 画像をGIF画像に変換したり、指定されたGIF画像からフレームを抽出したりします GIFから画像へ GIFファイルを画像のバッチに変換 画像のバッチをGIFファイルに変換 画像からGIFへ 開始するにはGIF画像を選択 最初のフレームのサイズを使用 指定されたサイズを最初のフレームの寸法に置き換えます 投げ縄を使用 描画モードのように投げ縄を使用して消去を実行します 元画像プレビューの透明度 マスクフィルター 指定されたマスク領域にフィルターチェーンを適用します。各マスク領域は独自のフィルターセットを決定できます マスク 選択した絵文字を使用する代わりに、アプリバーの絵文字がランダムに連続して変更されます ランダム絵文字 絵文字が無効化されている間はランダム絵文字選択を使用できません ランダム絵文字選択が有効になっている間は絵文字を選択できません 古いテレビ OCR(テキスト認識) 指定された画像からテキストを認識、120以上の言語がサポートされています 画像にはテキストがないか、アプリが見つけられませんでした 精度:%1$s 認識タイプ 高速 標準 最高 データなし Tesseract OCRが正常に機能するには、追加のトレーニングデータ(%1$s)をデバイスにダウンロードする必要があります。\n%2$sデータをダウンロードしますか? ダウンロード 接続がありません。確認してトレーニングモデルをダウンロードするために再試行してください ダウンロード済み言語 利用可能な言語 セグメンテーションモード 水平グリッド 垂直グリッド ピクセルスイッチを使用 Pixelのようなスイッチを使用 元の場所に%1$s名で上書きされました 拡大鏡 アクセシビリティ向上のため、描画モードで指先の上に拡大鏡を有効にします お気に入り Bスプライン 通常 エッジぼかし ピクセル化 反転塗りつぶしタイプ 紙吹雪 保存、共有、その他の主要アクションで紙吹雪が表示されます セキュアモード ネオン 自動回転 リミットボックスが画像の向きに適応することを許可します コンテナ コンテナの背後に影を描画する スライダー スイッチ 色を反転 終了 今プレビューを終了すると、画像を再度追加する必要があります 画像フォーマット 画像からMaterial Youパレットを作成 ダークカラー ライトバリアントの代わりにナイトモードのカラースキームを使用 Jetpack Composeコードとしてコピー リングぼかし クロスぼかし 円形ぼかし 星型ぼかし 直線的な傾斜移動 削除するタグ APNGツール 画像をAPNG画像に変換したり、指定されたAPNG画像からフレームを抽出したりします APNGから画像へ モーションぼかし APNGファイルを画像のバッチに変換 画像のバッチをAPNGファイルに変換 画像からAPNGへ 開始するにはAPNG画像を選択 Zip 指定されたファイルや画像からZipファイルを作成 ドラッグハンドル幅 紙吹雪のタイプ お祝い 爆発 コーナー JXLツール 品質損失なしでJXL~JPEG変換を実行、またはGIF/APNGをJXLアニメーションに変換 JXLからJPEGへ JXLからJPEGへの可逆変換を実行 JPEGからJXLへの可逆変換を実行 JPEGからJXLへ 開始するにはJXL画像を選択 高速ガウスぼかし2D 高速ガウスぼかし3D 高速ガウスぼかし4D 自動貼り付け GIFからJXLへ GIF画像をJXLアニメーション画像に変換 APNGからJXLへ APNG画像をJXLアニメーション画像に変換 JXLから画像へ JXLアニメーションを画像のバッチに変換 画像からJXLへ 画像のバッチをJXLアニメーションに変換 パス省略 画像は処理前に低い寸法にダウンスケールされます。これによりツールがより速く安全に動作するようになります クパチーノデザインシステムに基づいたiOSライクなスイッチを使用します ランチョスベッセル ピクセル値にベッセル(jinc)関数を適用することで高品質の補間を維持するリサンプリング方法 プレビューを生成 プレビュー生成を有効にします。これは一部のデバイスでのクラッシュを回避するのに役立ちますが、単一編集オプション内の一部の編集機能も無効になります 非可逆圧縮 可逆圧縮の代わりに非可逆圧縮を使用してファイルサイズを削減 結果画像のデコード速度を制御します。これにより結果画像の開く速度を速くできます。%1$sの値は最も遅いデコードを意味し、%2$sは最速です。この設定はファイルサイズを増加させる可能性があります 並び替え 日付 日付(逆順) このツールをダウンスケールなしで大きな画像のトレースに使用することはお勧めできません。クラッシュを引き起こし処理時間を増加させる可能性があります 名前 名前(逆順) 今日 埋め込みピッカー 指定された画像をSVG画像にトレース チャンネル構成 昨日 アプリがクリップボードデータを自動で貼り付けることを許可し、メイン画面に表示して処理できるようにします 調和色 調和レベル 動作 ファイル選択をスキップ 選択した画面でこれが可能な場合、ファイルピッカーが直ちに表示されます 圧縮タイプ 画像からSVGへ サンプリングされたパレットを使用 このオプションが有効になっている場合、量子化パレットはサンプリングされます 画像をダウンスケール 最小色比率 線のしきい値 二次しきい値 座標丸め許容値 パススケール プロパティをリセット 再試行 横向きで設定を表示 これが無効になっている場合、横向きモードでは常時表示オプションではなく、トップアプリバーのボタンで設定が開かれます 全画面設定 有効にすると、設定ページは常にスライド可能なドロワーシートではなく全画面で開かれます 複数メディアの選択 単一メディアの選択 選択 システム定義のものの代わりにImage Toolbox独自の画像ピッカーを使用します 権限なし リクエスト GPS目的地緯度 フォントサイズ ウォーターマークサイズ ストリップあたりの行数 CFAパターン Y解像度 コンポーズ 参照黒白 すべてのプロパティがデフォルト値に設定されます。この操作は元に戻せないことに注意してください レンズシリアル番号 ISO感度緯度zzz ガンマ LSTMネットワーク ピクセルあたりのサンプル数 原色色度 露出プログラム GPS目的地距離 変換 色空間 Flashpixバージョン ピクセルX寸法 ピクセルY寸法 圧縮ピクセルあたりビット数 メーカーノート ユーザーコメント 関連サウンドファイル サブ秒時間 元のサブ秒時間 デジタイズされたサブ秒時間 露出時間 F値 分光感度 写真感度 OECF 感度タイプ ISO感度緯度yyy 明るさ値 露出補正値 カスタムレンダリング 露出モード ホワイトバランス 35mmフィルムでの焦点距離 シーンキャプチャタイプ ゲイン制御 彩度 シャープネス 画像固有ID カメラ所有者名 レンズ仕様 GPS速度 GPSトラック GPS画像方向 GPS処理方法 GPS地域情報 GPS日付 GPS差動 GPS水平位置誤差 DNGバージョン デフォルト切り抜きサイズ プレビュー画像長 センサー下端境界 センサー右端境界 ダッシュサイズ この画像は描画パスの反復要素として使用されます ピクセル フルーエント 「フルーエント」デザインシステムに基づいたWindows 11スタイルのスイッチを使用します クパチーノ スイッチタイプ Jetpack Compose マテリアル You スイッチを使用します。ビューベースよりも美しくありません ビューベースのマテリアル You スイッチを使用します。これは他のものよりも見栄えが良く、素敵なアニメーション効果があります 最大 リサイズアンカー 詳細 デフォルトの線幅 エンジンモード レガシー レガシーとLSTM 画像バッチを指定された形式に変換 新しいフォルダを追加 サンプルあたりのビット数 圧縮 測光解釈 平面構成 YCbCrサブサンプリング YCbCr位置決め X解像度 解像度単位 ストリップオフセット ストリップバイト数 JPEG交換形式 JPEG交換形式の長さ 転送関数 ホワイトポイント YCbCr係数 日時 画像説明 メーカー モデル ソフトウェア アーティスト 著作権 Exifバージョン 元の日時 デジタイズされた日時 オフセット時間 元のオフセット時間 デジタイズされたオフセット時間 標準出力感度 指定されたフォントと色でパスにテキストを描画 推奨露出指数 ISO感度 センシング方法 レンズメーカー GPSバージョンID GPS緯度参照 GPSタイムスタンプ GPSステータス GPS測定モード GPS DOP GPS速度参照 センサー上端境界 カイザー 信号のエッジをテーパリングすることでスペクトルリークを低減する窓関数で、信号処理に有用です ガウス関数を適用する補間方法で、画像のスムージングやノイズ低減に有用です jinc関数を使用するランチョス3フィルターの変種で、最小限のアーティファクトで高品質な補間を提供します 画像を追加 CLAHE HSV 境界に合わせる シンプルスケッチ ソフトグロー カラーポスター 正方形 計算 テキストハッシュ チェックサム 選択したアルゴリズムに基づいてチェックサムを計算するテキストを入力 ソースチェックサム 比較するチェックサム 一致! 相違 チェックサムが等しく、安全かもしれません チェックサムが等しくありません。ファイルは安全でない可能性があります! エッジモード 線形ボックスぼかし メタデータに書き込む 色覚異常スキーム 赤色の色相を知覚できない ランチョス6 Jinc GPS目的地緯度参照 GPS目的地経度参照 バッチ比較 選択したアルゴリズムに基づいてチェックサムを計算するファイル/ファイルを選択 ドキュメントをスキャンしてPDFや個別の画像を作成します バートレット・ハン ボックス ボーマン メッシュグラデーション カスタム数のノットと解像度でメッシュグラデーションを作成 クリップ 保存試行中にエラーが発生しました。出力フォルダを変更してみてください ファイル名が設定されていません ローポリ サンドペインティング 開始点から終了点まで多角形を描画します フリーソフトウェア(パートナー) アルゴリズム 水玉模様 ティント バートレット窓とハン窓を組み合わせたハイブリッド窓関数で、信号処理でスペクトルリークを低減するために使用されます コンテンツに合わせて切り抜き ほとんどのオプションで保存ボタンを長押しすることで使用できる一度だけの保存場所を表示および編集します よりスムーズな画像リサンプリングのためのランチョスソフトフィルターの楕円加重平均(EWA)変種 16タップフィルターを使用して滑らかな結果を提供するスプライン基準の補間方法 指定されたパスに沿って選択した画像を描画します QRコード画像として保存 コラージュメーカー 中央左 Base64文字列をコピーまたは保存するには画像をロードしてください。文字列自体がある場合は、上に貼り付けて画像を取得できます アウトライン三角形 カスタムオプション 色情報 測光モード WEBPファイルを画像のバッチに変換 三色 ゴールデンフォレスト 指定されたパスに沿って点線と破線を描画 類似色 高品質リサンプリングのためのロビドゥーフィルターの楕円加重平均(EWA)変種 アニメーション付きのカスタムファンシースタイルスライダー。これがこのアプリのデフォルトです 調整に役立つRGBまたは明るさの画像ヒストグラム テンプレート作成 第三色 Bスプライン 3次のラグランジュ補間フィルターで、画像スケーリングにより高い精度とより滑らかな結果を提供します テキストを繰り返す マスク %1$sの値は遅い圧縮を意味し、比較的小さなファイルサイズになります。%2$sはより速い圧縮を意味し、大きなファイルになります。 多角形で画像を切り抜き、パースペクティブも修正します 右上 QRコード ロビドゥーEWA アマトルカ 画像分割 画像スタッキング 選択したテンプレートフィルターを削除しようとしています。この操作は元に戻せません ファイルを選択 ディレクトリを選択 ファイルとして保存 選択した色 アプリの新バージョンについて通知を受け、お知らせを読む ドロップブルース マークアップレイヤー トーン 自動 スポット修復 ファイルソース 512x512 2D LUT ハニング チェックサムをファイル名として使用 ナビゲーションバーを非表示 設定グループ「%1$s」はデフォルトで折りたたまれます EXIF編集 単一画像のメタデータを再圧縮なしで変更 ステッカーを変更 領域 アウトライン多角形 焦点面Y解像度 バーコードをスキャン 高さ比 バーコードタイプ 白黒を強制 バーコード画像はアプリのテーマで色付けされず、完全に白黒になります CLAHE シャッタースピード値 絞り値 最大絞り値 被写体距離 フラッシュ 被写体エリア 焦点距離 露出指数 被写体位置 デジタルズーム比 コントラスト デバイス設定の説明 被写体距離範囲 本体シリアル番号 レンズモデル GPS緯度 GPS経度参照 GPS経度 GPS高度参照 GPS高度 GPS衛星 GPSトラック参照 GPS画像方向参照 GPS地図基準 GPS目的地経度 GPS目的地方位参照 GPS目的地方位 GPS目的地距離参照 相互運用性インデックス プレビュー画像開始 アスペクトフレーム センサー左端境界 ISO 三角形 現在のテキストは1回だけでなくパスの終わりまで繰り返されます 開始点から終了点までアウトラインのある三角形を描画します 開始点から終了点まで三角形を描画します 開始点から終了点までアウトラインのある多角形を描画します 正多角形を描画 自由形式ではなく正多角形を描画します 開始点から終了点まで星を描画します アウトライン星 内部半径比 正星形を描画 開始点から終了点までアウトラインのある星を描画します アンチエイリアス 自由形式ではなく正星形を描画します 鋭いエッジを防止するためにアンチエイリアスを有効にします プレビューの代わりに編集を開く ImageToolboxで画像を開く(プレビュー)を選択すると、プレビューの代わりに編集選択シートが開きます ドキュメントスキャナー クリックしてスキャンを開始 スキャン開始 PDFとして保存 PDFとして共有 以下のオプションはPDFではなく画像の保存用です ヒストグラム平均化HSV ヒストグラム平均化 スケールカラースペース 線形 テキストフィールドでの入力を許可 ヒストグラム平均化ピクセル化 グリッドサイズX プリセット選択の背後にテキストフィールドを有効にして、その場で入力できるようにします グリッドサイズY 適応的ヒストグラム平均化 適応的ヒストグラム平均化LUV 適応的ヒストグラム平均化LAB CLAHE LAB CLAHE LUV フレームの色 無視する色 テンプレートフィルターが追加されていません 新規作成 スキャンしたQRコードは有効なフィルターテンプレートではありません QRコードをスキャン テンプレート 選択したファイルにはフィルターテンプレートデータがありません テンプレート名 この画像はこのフィルターテンプレートのプレビューとして使用されます テンプレートフィルター QRコード画像として 名前「%1$s」(%2$s)のフィルターテンプレートを追加しました フィルタープレビュー コード内容 QRコードをスキャンして内容を取得するか、文字列を貼り付けて新しいQRコードを生成します QRコードをスキャンしてフィールドの内容を置き換えるか、テキストを入力して新しいQRコードを生成します QR説明 最小 キュービック ロビドゥー ロビドゥーシャープ スプライン16 スプライン36 スプライン64 補間に二次関数を使用する方法で、滑らかで連続的な結果を提供します 最も近いピクセル値の平均を使用する単純なリサンプリング方法で、しばしばブロック状の外観になります 最小限のアーティファクトで高品質な補間のために2ローブのランチョスフィルターを使用するリサンプリング方法 最小限のアーティファクトで高品質な補間のために3ローブのランチョスフィルターを使用するリサンプリング方法 最小限のアーティファクトで高品質な補間のために4ローブのランチョスフィルターを使用するリサンプリング方法 リンギングアーティファクトを最小限に抑えるためのブラックマンフィルターの楕円加重平均(EWA)変種 クアドリックEWA エイリアシングを低減した高品質リサンプリングのためのランチョス3 Jincフィルターの楕円加重平均(EWA)変種 ジンセン 鮮明さとスムーズさのバランスが良好な高品質画像処理用に設計されたリサンプリングフィルター ジンセンEWA 画質向上のためのジンセンフィルターの楕円加重平均(EWA)変種 ランチョスシャープEWA ランチョス4最シャープEWA ランチョスソフトEWA ハースンソフト スムーズでアーティファクトのない画像スケーリングのためにハースンによって設計されたリサンプリングフィルター フォーマット変換 指定された色覚異常タイプに合わせてテーマカラーを適応させるモードを選択します すべての色に対する感度が低下 完全な色覚異常で、グレーの階調のみ見える 色覚異常スキームを使用しない 青色の色相を知覚できない テーマで設定されたとおりに色が表示されます シグモイド ラグランジュ3 ランチョス6 より高次の6を持つランチョスリサンプリングフィルターで、より鮮明で正確な画像スケーリングを提供します 線形スタックぼかし ガウシアンボックスぼかし 線形高速ガウシアンぼかしネクスト 描画のブラシとして使用するフィルターを以下から選択してください TIFF圧縮スキーム 単一画像を行または列で分割 希望する動作(切り抜き/アスペクト比に合わせる)を実現するために、このパラメータを切り抜きリサイズモードと組み合わせます 言語のインポートに成功しました OCRモデルのバックアップ インポート エクスポート 位置 右下 中央右 下中央 ゴッサム トリトーン CLAHE Jzazbz CLAHE Oklab CLAHE Oklch クラスタ化8x8ディザリング 四色 バリエーション シェード 色の混合 混合する色 ダイナミックカラーが有効になっている間はモネを使用できません ミスエティケイト ソフトエレガンス パレット転送バリアント 3D LUT ブリーチバイパス キャンドルライト エッジーアンバー 秋色 ポップアート セルロイド GIFからWEBPへ GIF画像をWEBPアニメーション画像に変換 WEBPツール 画像をWEBPアニメーション画像に変換したり、指定されたWEBPアニメーションからフレームを抽出したりします 画像のバッチをWEBPファイルに変換 画像からWEBPへ 開始するにはWEBP画像を選択 JXL、QOIおよびAndroidで画像ファイルとして認識されない他の形式の画像を見るには、全ファイルへのアクセスを許可してください デフォルトの描画パスモード ファイルへの完全アクセスがありません タイムスタンプを追加 一度だけの保存場所 最近使用した CIチャンネル グループ ImageToolbox in Telegram 🎉 何でも議論できるチャットに参加してください。また、ベータ版や発表を投稿するCIチャンネルもチェックできます 画像を指定した寸法に合わせ、背景にぼかしや色を適用します タイプ別にツールをグループ化 ツール配置 メイン画面のツールをカスタムリスト配置ではなく、タイプ別にグループ化します デフォルト値 システムバーの表示 非表示の場合にスワイプでシステムバーを表示できるようにします すべて非表示 すべて表示 ノイズ生成 ステータスバーを非表示 パーリンやその他のタイプのノイズを生成します 周波数 ノイズタイプ 回転タイプ ピンポン強度 距離関数 戻り値タイプ ジッター 破線 点線と破線 スタンプ ジグザグ 指定されたギャップサイズで描画されたパスに沿って破線を描画 単なるデフォルトの直線 ショートカットを作成 固定するツールを選択 前のフレームを破棄するようにして、フレームが互いにスタックしないようにします フレームは互いにクロスフェードされます クロスフェードフレーム数 しきい値1 しきい値2 ミラー101 拡張ズームぼかし シンプルラプラシアン シンプルソーベル ヘルパーグリッド 正確な操作に役立つよう描画領域の上にサポートグリッドを表示 グリッドの色 セル幅 セル高 コンパクトセレクター 一部の選択コントロールはスペースを取らないようにコンパクトなレイアウトを使用します レイアウト LUTライブラリ ダウンロード後に適用できるLUTのコレクションをダウンロード LUTのコレクションを更新します(新しいもののみがキューに追加されます)。ダウンロード後に適用できます フィルターのデフォルトプレビュー画像を変更 プレビュー画像 非表示 表示 スライダータイプ ファンシー モダンなマテリアルYouスライダー。未来的でフレッシュ、アクセシブルです 適用 ダイアログボタンを中央揃え 可能であればダイアログのボタンは左側ではなく中央に配置されます オープンソースライセンス このアプリで使用されているオープンソースライブラリのライセンスを表示 ピクセル面積関係を使用したリサンプリング。モアレのない結果が得られるため、画像縮小に適した方法かもしれません。ただし、画像が拡大されると、最近傍法に似ています トーンマッピングを有効化 %を入力 サイトにアクセスできません。VPNを使用するか、URLが正しいか確認してください 画像、テキストなどを自由に配置できるレイヤーモード レイヤーを編集 画像上のレイヤー ベータ Base64 空または無効なBase64文字列はコピーできません Base64を貼り付け Base64をコピー 提供された値は有効なBase64文字列ではありません Base64を保存 Base64を共有 オプション アクション Base64をインポート Base64アクション アウトラインを追加 指定した色と幅でテキストの周りにアウトラインを追加 アウトラインの色 アウトラインのサイズ 回転 フラクタルタイプ オクターブ ラキュナリティ ゲイン 重み付け強度 頭部長さスケール バーコードが見つかりません 生成されたバーコードがここに表示されます 音声ファイルのカバー画像抽出 オーディオファイルからアルバムカバー画像を抽出。最も一般的なフォーマットがサポートされています スタンプ 開始するにはオーディオを選択 オーディオを選択 幅に合わせる 高さに合わせる なし カスタムページ ページ選択 画像切り取り 垂直または水平線で画像の一部を切り取り、残りの部分を結合(反転も可能) 垂直ピボットライン フィルムストック50 霧の夜 コダック 上中央 ツール終了確認 特定のツールを使用中に保存されていない変更がある場合に閉じようとすると、確認ダイアログが表示されます ドキュメントスキャナーを使用するには設定でカメラ権限を許可してください フラッシュエネルギー ハミング 空間周波数応答 ブラックマン 焦点面X解像度 ウェルチ クアドリック ガウシアン スフィンクス バートレット ランチョス2 ランチョス3 ランチョス4 ランチョス2 Jinc 焦点面解像度単位 多角形 頂点 パーセンテージを入力 ファイルとして テンプレートを削除 ランチョス3 Jinc ランチョス4 Jinc QRコードをスキャンするには設定でカメラ権限を許可してください ハン窓の変形で、信号処理アプリケーションでスペクトルリークを低減するためによく使用されます カイザー窓を使用する補間方法で、メインローブの幅とサイドローブのレベルのトレードオフを適切に制御できます jinc関数を使用するランチョス2フィルターの変種で、最小限のアーティファクトで高品質な補間を提供します 緑色の色相を知覚できない Tesseractエンジンの入力変数を適用 最初のオプションと同じですが、画像の代わりに色を使用 赤と緑の色相の区別が困難 クリックしてアプリログファイルを共有。これは問題を特定して解決するのに役立ちます チェックサムの比較、ハッシュの計算、様々なハッシュアルゴリズムを使用したファイルからのHEX文字列の作成が可能です 最小限のアーティファクトで高品質な補間を提供する高度なリサンプリング方法 信号処理でスペクトルリークを低減するために使用される三角窓関数 36タップフィルターを使用して滑らかな結果を提供するスプライン基準の補間方法 64タップフィルターを使用して滑らかな結果を提供するスプライン基準の補間方法 ハニングEWA ブラックマンEWA スペクトルリークを低減するために使用される窓関数で、信号処理アプリケーションで優れた周波数分解能を提供します jinc関数を使用するランチョス4フィルターの変種で、最小限のアーティファクトで高品質な補間を提供します 滑らかな補間とリサンプリングのためのハニングフィルターの楕円加重平均(EWA)変種 ロビドゥーシャープEWA ランチョス3 Jinc EWA 最小限のアーティファクトでシャープな結果を得るためのランチョスシャープフィルターの楕円加重平均(EWA)変種 永久に非表示 ビン数 CLAHE HSL 適応的ヒストグラム平均化HSL 適応的ヒストグラム平均化HSV ラップ ラグランジュ2 2次のラグランジュ補間フィルターで、滑らかな遷移による高品質な画像スケーリングに適しています 画像リサンプリング品質を向上させるためにJinc関数を使用するランチョス6フィルターの変種 線形テントぼかし 線形ガウシアンボックスぼかし 線形高速ガウシアンぼかし 中央 左上 左下 ターゲット画像 線形ガウシアンぼかし フィルターを置換 パレット転送 拡張油彩 シンプル古いテレビ HDR クラスタ化2x2ディザリング クラスタ化4x4ディザリング ユリローマディザリング お気に入りオプションが選択されていません。ツールページで追加してください お気に入りを追加 補色 分裂補色 類似色と補色 カラーツール コーヒー 緑っぽい カラーハーモニー カラーシェーディング ターゲットLUT画像 ソフトエレガンスバリアント ターゲット3D LUTファイル(.cube / .CUBE) LUT ニュートラルLUT画像を取得 レトロイエロー リンクプレビュー リンク Icoファイルは256×256の最大サイズでのみ保存できます。より大きいサイズは結果画像で自動的に調整されます デフォルトの描画色 フォーマット済みタイムスタンプ フォーマットするためにタイムスタンプを有効にする スワイプでシステムバーを表示 ドメインワープ 配置 カスタムファイル名 カスタム名でフォルダに保存されました コラージュタイプ 画像を長押しして入れ替え、移動とズームで位置を調整 ヒストグラム この画像はRGBと明るさのヒストグラム生成に使用されます Tesseractオプション オプションは次のパターンに従って入力する必要があります:「--{option_name} {value}」 自動切り抜き 自由コーナー 点を画像境界に強制 円形カーネルを使用 オープニング クロージング モルフォロジカルグラデーション トップハット トーンカーブ ブラックハット カーブをリセット 線のスタイル ギャップサイズ ジグザグ比率 ツールはランチャーのホーム画面にショートカットとして追加されます。必要な動作を実現するために「ファイル選択をスキップ」設定と組み合わせて使用してください フレームをスタックしない 指定された間隔でパスに沿って選択した形状を描画 クロスフェード キャニー 画像をキャプチャするには設定でカメラ権限を許可してください メイン画面タイトル 固定レート係数(CRF) マテリアル2 背景上のレイヤー クイック設定側面 選択をクリア 設定グループ「%1$s」はデフォルトで展開されます Base64ツール チェックサムツール インポートできるのはTTFフォントのみです フォントをインポート(TTF/OTF) フォントをエクスポート インポートしたフォント タップして利用可能なタグを編集 タイムスタンプ パディング フォーマットパターン 水平ピボットライン 選択を反転 ポイントのカスタマイズ グリッドサイズ 解像度X カバーが見つかりません おっと… 何か問題が発生しました 以下のオプションを使用して連絡していただければ、解決策を見つけるよう努めます。\n(ログの添付をお忘れなく) ファイルに書き込む 不可視モード 画像のバッチからテキストを抽出し、1つのテキストファイルに保存 LSBを使用 赤目を自動削除 ロビドゥー方法のより鮮明なバリアントで、くっきりとした画像リサイズに最適化されています 区分的に定義された多項式関数を利用して曲線や表面を滑らかに補間および近似し、柔軟で連続的な形状表現を提供します 青と黄色の色相の区別が困難 各画像からテキストを抽出し、関連する写真のEXIF情報に配置 現在の画像の保存に使用される場所とファイル名を選択 テキストを取得できる場所(QRコード、OCRなど)でリンクプレビューの取得を有効にします 画像を背景として使用し、その上に異なるレイヤーを追加できます カーブはデフォルト値に戻されます キュービック補間は最も近い16ピクセルを考慮してよりスムーズなスケーリングを提供し、バイリニアよりも優れた結果を得られます 出力ファイル名に基本的なミリ秒の代わりにフォーマット済みタイムスタンプを有効にします スペクトルリークを最小限に抑えることで優れた周波数分解能を提供する窓関数で、信号処理でよく使用されます よりシャープな結果を得るためのロビドゥーシャープフィルターの楕円加重平均(EWA)変種 スペクトルリークを低減しながら優れた周波数分解能を提供するように設計された窓関数で、信号処理アプリケーションでよく使用されます ステガノグラフィーを使用して、画像のバイト内に目に見えないウォーターマークを作成 滑らかな補間のためのクアドリックフィルターの楕円加重平均(EWA)変種 自然画像のリサイズに最適化された高品質補間方法で、鮮明さとスムーズさのバランスを取ります パスに沿って波状のジグザグを描画 切り取り領域の周りの部分を結合する代わりに、垂直切り取り部分が残されます Base64文字列を画像にデコードするか、画像をBase64形式にエンコードします 画像編集中に選択した側面にフローティングストリップを追加し、クリックするとクイック設定が開きます 非常にシャープな画像リサンプリングのためのランチョス4最シャープフィルターの楕円加重平均(EWA)変種 緑と赤の色相の区別が困難 ペイントとして使用するフィルターを1つ選択 色の混合、トーン作成、シェード生成など 出力画像はそのデータチェックサムに対応する名前になります 画像のバッチを一つのフォーマットから別のフォーマットに変換 選択したブレンドモードで画像を重ね合わせる まず、ここで取得できるニュートラルLUTにお気に入りの写真編集アプリケーションでフィルターを適用します。これが正しく機能するためには、各ピクセルの色が変更されていないことが必要です 出力ファイル名へのタイムスタンプ追加を有効にします 20枚の画像からさまざまなコラージュを作成します Androidアプリケーションのパートナーチャンネルで、より多くの役立つソフトウェア 点は画像境界に制限されません。これはより正確なパースペクティブ修正に役立ちます 描画されたパスの下でコンテンツアウェアフィル マテリアル2ベースのスライダー。モダンではなくシンプルで直感的です 切り取り領域の周りの部分を結合する代わりに、水平切り取り部分が残されます LSB(最下位ビット)ステガノグラフィー方式が使用されます。そうでない場合はFD(周波数領域) 任意のバーコード(QR、EAN、AZTECなど)をスキャンしてその内容を取得するか、テキストを貼り付けて新しいものを生成します WEBPから画像へ メッシュグラデーションのオンラインコレクションを閲覧 メッシュグラデーションのコレクション メッシュグラデーションオーバーレイ 指定された画像の上にメッシュグラデーションを合成 解像度Y 解像度 選択したアルゴリズムに基づいてチェックサムを計算するファイルを選択 ログを送信 ピクセルごと ハイライトの色 ピクセル比較タイプ 上から下 PDFは保護されています サイズ(逆順) MIMEタイプ サイズ ロック解除 パスワード 操作はほぼ完了しています。今キャンセルすると、再開するには最初からやり直す必要があります。 更新日 更新日(逆順) MIMEタイプ(逆順) 拡張子 拡張子(逆順) 追加日 追加日(逆順) 左から右 右から左 下から上 赤色化 緑色化 青色化 シアン マゼンタ イエロー マゼンタ系 イエロー系 シアン系 チャンネルミックス 回転を無効化 画像の回転機能を無効にします 枠線への吸着を有効化 編集時に要素が枠線に自動的に吸着します リキッドグラス 流動的なガラス効果を画像に適用します 画像を選択またはBase64データを入力 画像のリンクを入力 リンクを貼り付け 万華鏡 副角度 辺の数 カラーハーフトーン 輪郭 レベル オフセット ボロノイ結晶化 形状 ストレッチ ランダム性 ノイズ除去 拡散 DoG フィルター 第2半径 均等化 グロー 渦と引き寄せ 点描 枠線の色 極座標 矩形から極座標へ 極座標から矩形へ 円内反転 ノイズ軽減 シンプルソラライズ 織物 X間隔 Y間隔 X幅 Y幅 ツイスト ゴム印 スミア 密度 ミックス 球面レンズ歪み 屈折率 アーク 広がり角度 スパークル 光線 ASCII グラデーション モアレ ジェット クール HSV ピンク ホット パルーラ マグマ インフェルノ プラズマ ビリディス シヴィディス トワイライト トワイライトシフト 自動透視変換 自動傾き補正 切り抜きを許可 切り抜きまたは透視変換を選択 絶対 ターボ ディープグリーン レンズ補正 対象レンズプロファイル 利用可能なレンズプロファイルをダウンロード パーセンテージ JSONとしてエクスポート 編集内容をJSON形式でエクスポートして、後で読み込み直すことができます シームカービング ホーム画面 ロック画面 ビルトイン 壁紙をエクスポート 更新 編集済みの画像を壁紙として保存します 壁紙設定のため全ファイルへのアクセスを許可 壁紙設定のためメディア画像の読み取りを許可 プリセット名をファイル名に追加 保存時にプリセット名を自動的にファイル名に含めます スケールモードをファイル名に追加 保存時にスケールモードを自動的にファイル名に含めます ASCIIアート 画像をASCII文字アートに変換します パラメーター ASCIIアートの色を反転します スクリーンショットを処理中です スクリーンショットの取得に失敗しました。もう一度お試しください 保存をスキップしました %1$sファイルをスキップしました ファイルサイズが大きい場合は保存をスキップ 元のファイルより大きくなる場合は自動的に保存をスキップします カレンダーイベント 連絡先情報 メール 地理的位置 電話番号 プレーンテキスト SMS URL Wi-Fi オープンネットワーク 指定されていません SSID 電話番号 メッセージ 住所 件名 本文 名前 組織 タイトル 電話番号 メールアドレス URL 住所 概要 説明 場所 主催者 開始日 終了日 ステータス 緯度 経度 バーコードを作成 バーコードを編集 Wi-Fi設定 セキュリティ 連絡先を選択 連絡先へのアクセス許可を付与 連絡先情報 ミドルネーム 発音 電話番号を追加 メールアドレスを追加 住所を追加 ウェブサイト ウェブサイトを追加 表示名 QRコード上部の画像 コードカスタマイズ QRロゴ画像 ロゴ ロゴパディング ロゴサイズ ロゴコーナー 第4の目 QRコード内に追加の位置検出パターンを配置します ピクセル形状 フレーム形状 ボール形状 誤り訂正レベル 暗い色 明るい色 HyperOS HyperOSデザインシステムに基づいたUIを使用します マスクパターン このコードはスキャンできない可能性があります。すべてのデバイスで読み取れるように外観パラメータを変更してください スキャン不可 ツールはホーム画面のアプリランチャーのようになり、よりコンパクトになります ランチャーモード 選択したブラシとスタイルで領域を塗りつぶします 塗りつぶし スプレー グラフィティスタイルのパスを描画します 四角い粒子 スプレー粒子は円ではなく正方形になります パレットツール 画像からパレットに使用するベーシック/マテリアルを生成、またはさまざまなパレット形式でインポート/エクスポート パレットの編集 さまざまな形式でパレットをエクスポート/インポート 色の名前 パレット名 パレットフォーマット 生成されたパレットをさまざまな形式でエクスポートする 現在のパレットに新しい色を追加します %1$s 形式はパレット名の指定をサポートしていません Play ストアのポリシーにより、この機能は現在のビルドに含めることができません。この機能にアクセスするには、別のソースから ImageToolbox をダウンロードしてください。利用可能なビルドは以下の GitHub で見つけることができます。 Githubページを開く 選択したフォルダーに保存されるのではなく、元のファイルが新しいファイルに置き換えられます 検出された隠し透かしテキスト 検出された隠し透かし画像 この画像は非表示にされました ジェネレーティブ・インペインティング OpenCV に依存せずに、AI モデルを使用して画像内のオブジェクトを削除できます。この機能を使用するには、アプリは必要なモデル (約 200 MB) を GitHub からダウンロードします。 OpenCV に依存せずに、AI モデルを使用して画像内のオブジェクトを削除できます。これは長時間実行される操作になる可能性があります エラーレベル分析 輝度勾配 平均距離 コピー移動検出 保持 係数 クリップボードのデータが大きすぎます データが大きすぎてコピーできない シンプルな織りのピクセル化 千鳥状ピクセル化 クロスピクセル化 マイクロマクロピクセル化 軌道ピクセル化 渦ピクセル化 パルスグリッドピクセル化 核のピクセル化 放射状織りピクセル化 URI「%1$s」を開けません 降雪モード 有効 ボーダーフレーム グリッチのバリアント チャンネルシフト 最大オフセット VHS ブロックグリッチ ブロックサイズ CRTの曲率 曲率 彩度 ピクセルメルト 最大ドロップ AIツール アーティファクトの除去やノイズ除去など、AI モデルを通じて画像を処理するためのさまざまなツール 圧縮、ギザギザの線 漫画、放送圧縮 一般的な圧縮、一般的なノイズ 無色の漫画のノイズ 高速、一般的な圧縮、一般的なノイズ、アニメーション/コミック/アニメ 本のスキャン 露出補正 一般的な圧縮、カラー画像に最適 一般的な圧縮、グレースケール画像に最適 一般圧縮、グレースケール画像、強力 一般的なノイズ、カラー画像 一般的なノイズ、カラー画像、より詳細なディテール 一般的なノイズ、グレースケール画像 一般的なノイズ、グレースケール画像、強め 一般的なノイズ、グレースケール画像、最も強い 一般的な圧縮 一般的な圧縮 テクスチャライゼーション、h264 圧縮 VHS圧縮 非標準圧縮 (cinepak、msvideo1、roq) ビンク圧縮、ジオメトリでの改善 ビンク圧縮、強化 ビンク圧縮、ソフト、ディテール保持 階段状の効果を除去し、滑らかにする スキャンしたアート/図面、軽度の圧縮、モアレ カラーバンディング ゆっくりとハーフトーンを除去します グレースケール/白黒画像用の一般的なカラーライザー。より良い結果を得るには、DDColor を使用します。 エッジ除去 過度の研ぎを除去します 遅い、ディザリング アンチエイリアシング、一般的なアーティファクト、CGI KDM003 スキャン処理 軽量画質向上モデル 圧縮アーティファクトの除去 圧縮アーティファクトの除去 スムーズな結果による包帯除去 ハーフトーンパターン処理 ディザパターン除去V3 JPEGアーチファクト除去V2 H.264 テクスチャの強化 VHS のシャープ化と強化 結合 チャンクサイズ オーバーラップサイズ %1$s ピクセルを超える画像はスライスされてチャンクに処理され、継ぎ目が見えないようにこれらをオーバーラップ ブレンドします。 サイズが大きいと、ローエンド デバイスで不安定になる可能性があります 開始するには 1 つを選択してください %1$s モデルを削除しますか?再度ダウンロードする必要があります 確認する モデル ダウンロードしたモデル 利用可能なモデル 準備中 アクティブモデル セッションを開けませんでした .onnx/.ort モデルのみインポート可能 インポートモデル さらに使用するためにカスタム onnx モデルをインポートします。onnx/ort モデルのみが受け入れられ、ほぼすべての esrgan のようなバリアントをサポートします。 輸入モデル 一般的なノイズ、カラー画像 一般的なノイズ、カラー画像、強め 一般的なノイズ、カラー画像、最強 ディザリングアーティファクトとカラーバンディングを軽減し、滑らかなグラデーションと平坦なカラー領域を改善します。 自然な色を維持しながら、バランスの取れたハイライトで画像の明るさとコントラストを強化します。 暗い画像を細部を維持し、露出オーバーを避けながら明るくします。 過度な色調を除去し、よりニュートラルで自然なカラーバランスを取り戻します。 細かいディテールとテクスチャの維持に重点を置いて、ポアソン ベースのノイズ トーンを適用します。 ソフトなポアソン ノイズ トーンを適用して、よりスムーズで攻撃性の低い視覚的な結果を実現します。 均一なノイズ調色は、ディテールの維持と画像の鮮明さに重点を置いています。 穏やかで均一なノイズ調色により、繊細な質感と滑らかな外観を実現します。 アーティファクトを再描画し、画像の一貫性を向上させることで、損傷した領域や不均一な領域を修復します。 最小限のパフォーマンスコストでカラーバンディングを除去する軽量デバンディングモデル。 非常に高圧縮アーティファクト (品質 0 ~ 20%) を含む画像を最適化し、鮮明さを向上させます。 高圧縮アーティファクト (品質 20 ~ 40%) で画像を強化し、詳細を復元し、ノイズを低減します。 適度な圧縮 (品質 40 ~ 60%) で画像を改善し、鮮明さと滑らかさのバランスをとります。 軽い圧縮 (60 ~ 80% の品質) で画像を洗練し、微妙なディテールやテクスチャを強調します。 自然な外観と詳細を維持しながら、ほぼ損失のない画像 (80 ~ 100% の品質) をわずかに向上させます。 シンプルで素早いカラー化、漫画、理想的ではない 画像のぼやけをわずかに軽減し、アーティファクトを発生させることなくシャープネスを向上させます。 長時間実行される操作 画像処理中 処理 非常に低品質の画像 (0 ~ 20%) に含まれる重い JPEG 圧縮アーティファクトを除去します。 高圧縮画像内の強力な JPEG アーティファクトを軽減します (20 ~ 40%)。 画像の詳細 (40 ~ 60%) を維持しながら、中程度の JPEG アーティファクトをクリーンアップします。 軽い JPEG アーティファクトをかなり高品質の画像 (60 ~ 80%) に調整します。 ほぼロスレス画像の小さな JPEG アーティファクトを微妙に軽減します (80 ~ 100%)。 細かいディテールとテクスチャを強化し、大きなアーティファクトを発生させることなく知覚されるシャープネスを向上させます。 処理が完了しました 処理に失敗しました 自然な外観を維持しながら肌のテクスチャとディテールを強化し、速度を最適化します。 JPEG 圧縮アーティファクトを除去し、圧縮された写真の画質を復元します。 暗い場所で撮影した写真の ISO ノイズを低減し、ディテールを維持します。 露出過度または「ジャンボ」ハイライトを修正し、より良い色調バランスを復元します。 グレースケール画像に自然な色を追加する、軽量かつ高速なカラー化モデル。 デジェペグ ノイズ除去 色付け アーティファクト 強化する アニメ スキャン 高級感のある 一般画像用の X4 アップスケーラ。 GPU の使用量と時間が少なく、適度なブラー除去とノイズ除去を備えた小型モデル。 テクスチャと自然なディテールを維持する、一般的な画像用の X2 アップスケーラー。 強化されたテクスチャとリアルな結果を備えた一般画像用の X4 アップスケーラー。 アニメ画像に最適化された X4 アップスケーラー。 6 つの RRDB ブロックにより、より鮮明なラインとディテールが実現します。 MSE 損失を備えた X4 アップスケーラーは、よりスムーズな結果を生成し、一般的な画像のアーティファクトを軽減します。 アニメ画像に最適化された X4 アップスケーラー。よりシャープなディテールと滑らかなラインを備えた 4B32F バリアント。 一般画像用の X4 UltraSharp V2 モデル。シャープさと明瞭さを強調します。 X4デジタルハイエンドシリーズV2ライト。高速かつ小型で、GPU メモリの使用量を減らしながらディテールを維持します。 素早い背景除去が可能な軽量モデル。バランスのとれたパフォーマンスと精度。ポートレート、オブジェクト、シーンに使用します。ほとんどの使用例に推奨されます。 BGを削除する 水平方向の境界線の太さ 垂直方向の境界線の太さ %1$s色 現在のモデルはチャンキングをサポートしていません。画像は元のサイズで処理されます。これにより、メモリ消費量が多くなり、ローエンド デバイスで問題が発生する可能性があります。 チャンキングが無効になっていると、画像は元のサイズで処理されます。これにより、メモリ消費量が多くなり、ローエンド デバイスで問題が発生する可能性がありますが、推論ではより良い結果が得られる可能性があります。 チャンク化 背景除去のための高精度画像セグメンテーションモデル 少ないメモリ使用量で高速にバックグラウンドを削除できる U2Net の軽量バージョン。 完全な DDColor モデルは、アーティファクトを最小限に抑えながら、一般的な画像に対して高品質のカラー化を実現します。すべてのカラー化モデルの中で最良の選択。 DDColor トレーニング済みのプライベート芸術データセット。非現実的な色のアーティファクトが少なく、多様で芸術的な色付け結果が得られます。 正確な背景除去のための Swin Transformer に基づく軽量 BiRefNet モデル。 特に複雑なオブジェクトや扱いにくい背景において、シャープなエッジと優れたディテール保持による高品質の背景除去。 滑らかなエッジを持つ正確なマスクを生成する背景除去モデル。一般的なオブジェクトと適度なディテールの保持に適しています。 モデルはすでにダウンロードされています モデルは正常にインポートされました タイプ キーワード 非常に速い 普通 遅い 非常に遅い パーセントを計算する 最小値は %1$s です 指で描いて画像を歪ませる ワープ 硬度 ワープモード 動く 育つ 縮む CWに旋回 反時計回りに旋回 フェード強度 トップドロップ ボトムドロップ スタートドロップ エンドドロップ ダウンロード中 滑らかな形状 より滑らかで自然な形状を得るには、標準の角丸長方形の代わりに超楕円を使用します。 形状タイプ カット 丸い スムーズ 丸みのないシャープなエッジ クラシックな丸い角 形状の種類 コーナーサイズ スクワクル エレガントな丸みを帯びた UI 要素 ファイル名の形式 ファイル名の先頭に配置されるカスタム テキスト。プロジェクト名、ブランド、個人タグに最適です。 拡張子なしの元のファイル名を使用するため、ソースの識別をそのまま維持できます。 ピクセル単位の画像幅。解像度の変更の追跡や結果のスケーリングに役立ちます。 画像の高さ (ピクセル単位)。アスペクト比やエクスポートを操作するときに役立ちます。 ランダムな数字を生成して、一意のファイル名を保証します。重複に対する安全性を高めるために、さらに桁を追加します。 バッチエクスポート用の自動インクリメントカウンター。1 つのセッションで複数の画像を保存する場合に最適です。 適用されたプリセット名をファイル名に挿入すると、画像がどのように処理されたかを簡単に思い出すことができます。 処理中に使用される画像スケーリング モードを表示し、サイズ変更、トリミング、またはフィットした画像を区別するのに役立ちます。 ファイル名の末尾に配置されるカスタム テキスト。_v2、_edited、_final などのバージョン管理に役立ちます。 ファイル拡張子 (png、jpg、webp など) は、実際に保存された形式に自動的に一致します。 カスタマイズ可能なタイムスタンプを使用すると、完璧な並べ替えのために Java 仕様に従って独自の形式を定義できます。 フリングタイプ Androidネイティブ iOSスタイル 滑らかな曲線 クイックストップ 弾む ふわふわ キビキビ ウルトラスムーズ アダプティブ アクセシビリティを意識した モーションの軽減 Android のネイティブ スクロール物理学 一般的な用途向けのバランスの取れたスムーズなスクロール 摩擦が大きい iOS のようなスクロール動作 独特のスプライン曲線で独特のスクロール感を実現 正確なスクロールと素早い停止 遊び心のある、応答性の高い弾むスクロール コンテンツ閲覧のための長くて滑らかなスクロール インタラクティブ UI の迅速で応答性の高いスクロール 勢いを増したプレミアムでスムーズなスクロール 飛行速度に基づいて物理を調整します システムのアクセシビリティ設定を尊重します アクセシビリティのニーズに合わせた最小限の動き プライマリライン 5行ごとに太い線を追加します 塗りつぶしの色 隠しツール 共有用に非表示になっているツール カラーライブラリ 膨大な色のコレクションを閲覧する 自然なディテールを維持しながら、画像のぼやけを鮮明にして除去します。焦点の合っていない写真を修正するのに最適です。 以前にサイズ変更された画像をインテリジェントに復元し、失われたディテールやテクスチャを復元します。 実写コンテンツ用に最適化されており、圧縮アーチファクトが軽減され、映画/テレビ番組のフレームの細部が強調されます。 VHS 品質の映像を HD に変換し、テープノイズを除去し、ヴィンテージ感を維持しながら解像度を高めます。 テキストの多い画像やスクリーンショットに特化しており、文字を鮮明にして読みやすさを向上させます。 多様なデータセットでトレーニングされた高度なアップスケーリングで、汎用の写真補正に優れています。 Web 圧縮された写真用に最適化され、JPEG アーティファクトを除去し、自然な外観を復元します。 テクスチャの保存とアーティファクトの削減が向上した、Web 写真用の改良版。 Dual Aggregation Transformer テクノロジーによる 2 倍のアップスケーリングにより、鮮明さと自然なディテールが維持されます。 高度なトランス アーキテクチャを使用した 3 倍のアップスケーリングは、中程度の拡大ニーズに最適です。 最先端のトランスネットワークによる 4 倍の高品質アップスケーリングにより、大規模なスケールでも微細なディテールを維持します。 写真のブレやノイズ、手ぶれを除去します。汎用ですが写真に最適です。 BSRGAN の劣化に対して最適化された Swin2SR トランスフォーマーを使用して低品質の画像を復元します。重度の圧縮アーティファクトを修正し、4 倍スケールで細部を強調するのに最適です。 BSRGAN 劣化でトレーニングされた SwinIR トランスフォーマーによる 4 倍のアップスケーリング。 GAN を使用して、写真や複雑なシーンのより鮮明なテクスチャとより自然なディテールを実現します。 パス PDFを結合 複数の PDF ファイルを 1 つのドキュメントに結合する ファイルの順序 pp. PDFの分割 PDFドキュメントから特定のページを抽出 PDFを回転する ページの向きを永続的に修正する ページ PDF を並べ替える ページをドラッグ アンド ドロップして順序を変更します ページを押したままドラッグ ページ番号 文書に番号を自動的に追加する ラベルフォーマット PDF からテキストへ (OCR) PDF ドキュメントからプレーンテキストを抽出する ブランドまたはセキュリティのためのオーバーレイカスタムテキスト サイン あらゆる文書に電子署名を追加します これは署名として使用されます PDFのロックを解除する 保護されたファイルからパスワードを削除する PDFを保護する 強力な暗号化でドキュメントを保護する 成功 PDF のロックが解除され、保存または共有できます PDFを修復する 破損した文書または判読不能な文書を修復してみます グレースケール すべてのドキュメントに埋め込まれた画像をグレースケールに変換します PDFを圧縮する ドキュメントのファイル サイズを最適化して共有しやすくします ImageToolbox は内部相互参照テーブルを再構築し、ファイル構造を最初から再生成します。これにより、「開けない」多くのファイルへのアクセスを復元できます。 このツールはすべての文書画像をグレースケールに変換します。印刷やファイルサイズの縮小に最適 メタデータ プライバシーを向上させるためにドキュメントのプロパティを編集する タグ プロデューサー 著者 キーワード クリエイター プライバシーのディープクリーン このドキュメントの利用可能なメタデータをすべてクリアします ページ ディープ OCR Tesseract エンジンを使用してドキュメントからテキストを抽出し、1 つのテキスト ファイルに保存します すべてのページを削除できません PDF ページを削除する PDF ドキュメントから特定のページを削除する タップして削除 手動で PDFのトリミング ドキュメントページを任意の境界にトリミング PDF を平坦化する 文書ページをラスター化して PDF を変更不可能にする カメラを起動できませんでした。権限を確認し、別のアプリによって使用されていないことを確認してください。 画像の抽出 PDFに埋め込まれた画像を元の解像度で抽出します この PDF ファイルには埋め込み画像が含まれていません このツールはすべてのページをスキャンし、フル品質のソース画像を復元します。ドキュメントからオリジナルを保存するのに最適です。 署名を描く ペンパラメータ 自分の署名を文書に配置する画像として使用する PDF を圧縮 指定された間隔でドキュメントを分割し、新しいドキュメントを zip アーカイブに圧縮します 間隔 PDFを印刷する カスタム ページ サイズで印刷するドキュメントを準備する シートあたりのページ数 向き ページサイズ マージン 咲く ソフトニー アニメや漫画向けに最適化されています。改善された自然な色と少ないアーティファクトによる高速アップスケーリング Samsung One UI 7 のようなスタイル ここに基本的な数学記号を入力して、目的の値を計算します (例: (5+5)*10) 数学式 最大 %1$s 枚の画像を選択します 日付時刻を保持する 日付と時刻に関連する exif タグを常に保持し、exif を保持するオプションとは独立して機能します。 アルファ形式の背景色 アルファサポートを使用してすべての画像フォーマットの背景色を設定する機能を追加します。無効にすると、アルファ以外のフォーマットでのみ使用できます。 プロジェクトを開く 以前に保存した Image Toolbox プロジェクトの編集を続行する Image Toolbox プロジェクトを開けません Image Toolbox プロジェクトにプロジェクト データがありません Image Toolbox プロジェクトが破損しています サポートされていない Image Toolbox プロジェクト バージョン: %1$d プロジェクトの保存 レイヤー、背景、編集履歴を編集可能なプロジェクト ファイルに保存 開けませんでした 検索可能な PDF に書き込む 画像バッチからテキストを認識し、画像と選択可能なテキストレイヤーを含む検索可能な PDF を保存します レイヤーアルファ 水平反転 垂直反転 ロック 影を追加する 影の色 テキストジオメトリ テキストを引き伸ばしたり傾けたりして、よりシャープなスタイルを実現します スケールX スキューX 注釈を削除する リンク、コメント、ハイライト、図形、フォームフィールドなどの選択した注釈タイプを PDF ページから削除します ハイパーリンク 添付ファイル ライン ポップアップ スタンプ 形状 テキストメモ テキストのマークアップ フォームフィールド マークアップ 未知 注釈 グループを解除する 構成可能な色とオフセットを使用してレイヤーの後ろにぼかしシャドウを追加します ================================================ FILE: core/resources/src/main/res/values-kk/strings.xml ================================================ Жаңартулар Палитра Қазір шықсаңыз, барлық сақталмаған өңдеме жоғалады Қолданба туралы Бастау үшін файл таңдаңыз Суреттер: %d Шығу Сақтау Көк Арылту Салыстыру Түпнұсқа файл атауын қосу Табиғат пен жануар Түпнұсқа Күте тұрыңыз Тағам мен сусын Сурет жоқ Қолданбадан шығу Кәш Кемпірқосақ Сурет үлкейту Бұрыш Сурет таңдау Суреттерді біріктіру PDF құралдары Қаріп өлшемі Биіктігі %1$s Жасыл Ақ-қара Тазарту Қолданбадан шығасыз ба? EXIF тазарту Скриншот Радиус Қолданбаны қайта қосу Бастау үшін екі сурет таңдаңыз Сақтауда Өңдеу Көшіру Жаңарту Түс таңдау EXIF сақтау Аа Әә Бб Вв Гг Ғғ Дд Ее Ёё Жж Зз Ии Йй Кк Ққ Лл Мм Нн Ңң Оо Өө Пп Рр Сс Тт Уу Ұұ Үү Фф Хх Һһ Цц Чч Шш Щщ Ъъ Ыы Іі Ьь Ээ Юю Яя 0123456789 !? Іздеу Файл атауы Түсті ауыстыру Сурет Үлгілер Жою EXIF жою Гамма Нұсқа ОК Алмасу буферіне көшірілді Аударуға көмектесу RGB сүзгісі Өткізу Тег қосу Сақтық көшірме Кереғарлық Кәш өлшемі Бастау үшін сурет таңдаңыз Маскалар Түс көшірілді Алдын ала қарау Сепия Бастапқы коды PDF алдын ала қарау Ені %1$s Болдырмау Жаңартуларды тексеру Тамызғыш Қалу %d сурет сақталмады Amoled режімі Сөндірулі Тіл Таңба Құралдар Файл өлшемі Әдепкі Эмоджи қосу Кілт Қанықтық Рұқсат Баптау Зат Кездейсоқ файл атауы Жүктелуде… Сурет сілтемесі Мәтін Әдепкі Қаріп Айрықшалық Скриншот өңдеу EXIF деректері табылмады Түнгі режім Палитра стилі Жарықтық Шифрлау Жаңа нұсқа %1$s Бөлісу Өшіру режімі Қызыл Толтыру Қиып алу EXIF өңдеу Түс Өлшем %1$s Кеңейтім Бірдеңе дұрыс болмады: %1$s Сапасы Кескін алдын ала қарау үшін тым үлкен, бірақ оны бәрібір сақтауға тырысады Берілген кескін үшін палитраны жасау мүмкін емес Осы жерден іздеңіз Беткей Жою Үшіншілік Бұл қолданба толығымен тегін, бірақ жобаны әзірлеуге қолдау көрсеткіңіз келсе, осы жерді басуға болады Айқын Кескінді қалпына келтіру %1$s режимінде сақтау тұрақсыз болуы мүмкін, себебі ол жоғалтпайтын пішім Қосылған болса, қолданба түстері тұсқағаз түстеріне қабылданады Грант Құрылғыны сақтау Қолдау көрсетілмейтін түрі: %1$s Шығару қалтасы Анықталмаған Аударма қателерін түзетіңіз немесе жобаны басқа тілдерге локализациялаңыз Жарық Суреттен түсті таңдаңыз, көшіріңіз немесе бөлісіңіз Префикс Негізгі FAB туралау Қоюға ештеңе жоқ Бірдеңе дұрыс болмады %1$s қалтасына сақталды Суреттерді таңдаңыз Екінші Баптау Жарамды aRGB кодын қойыңыз. Динамикалық түстер Берілген кескіннен түстер палитрасының үлгісін жасаңыз Соңғы жаңартуларды алыңыз, мәселелерді талқылаңыз және т.б Қосылған болса, өңдеу үшін кескінді таңдағанда, қолданба түстері осы кескінге қабылданады Салмақ бойынша өлшемін өзгерту Икемді Мәндер дұрыс қалпына келтірілді Берілген бір кескіннің сипаттамаларын өзгерту Қараңғы Жаңартулар табылмады Динамикалық түстер қосулы кезде қолданбаның түс схемасын өзгерту мүмкін емес Сұрауыңыз бойынша ештеңе табылмады Жиектің қалыңдығы Жаңартуларды тексеру Түс схемасы Кескінді кез келген шекке дейін қиыңыз Палитра жасау Қосылған жағдайда беттердің түсі түнгі режимде абсолютті қараңғыға орнатылады Мәселе бақылаушысы Қосылған болса, қолданба іске қосылғаннан кейін жаңарту диалогы көрсетіледі Жарық Монет кескініне рұқсат ету Қолданба жұмыс істеуі үшін бұл рұқсат қажет, оны қолмен беріңіз Ең үлкен өлшем КБ Жүйе Кескіннің өлшемін КБ-де берілген өлшемнен кейін өзгертіңіз қосу Монет түстері Алдын ала қарауды өзгерту Арнаулы Сыртқы жад Құндылықтар Өлшем түрін өзгерту Берілген екі суретті салыстырыңыз Жетілдірілген ақау Channel Shift X Арнаны ауыстыру Y Сыбайлас жемқорлық мөлшері Сыбайлас жемқорлықтың ауысуы X Сыбайлас жемқорлықтың ауысуы Y Шатыр бұлыңғыр Бүйірлік әлсіреу Бүйір Жоғарғы Төменгі Күш Бағдар & Тек сценарийді анықтау Автоматты бағдарлау & сценарийді анықтау Тек авто Сирек мәтін бағыты & сценарийді анықтау Шикі сызық Ақаулық Сома Тұқым Анаглиф Шу Пиксельді сұрыптау Араластыру Сіз таңдалған сүзгі маскасын жойғалы жатырсыз. Бұл әрекетті қайтару мүмкін емес Масканы жою Драго Олдридж Кесіп алу Учимура Мобиус Өту шың Түс аномалиясы Алмасу буфері Діріл Діріл күші Файлдарды қайта жазу үшін \"Explorer\" кескін көзін пайдалану керек, суреттерді қайталап көріңіз, біз сурет көзін қажеттіге өзгерттік Файлдарды қайта жазу Тегін Бастапқы тағайындалған жерде қайта жазылған кескіндер Кескінді таңдау үшін GetContent мақсатын пайдаланыңыз. Барлық жерде жұмыс істейді, бірақ кейбір құрылғыларда таңдалған кескіндерді қабылдауда мәселелер бар екені белгілі. Бұл менің кінәм емес. Опцияларды реттеу Тапсырыс Негізгі экрандағы опциялардың ретін анықтайды Эмодзилер саны sequenceNum бастапқы файл аты Қосылған болса, шығыс кескінінің атына бастапқы файл атауын қосады Реттік нөмірді ауыстырыңыз Фотосурет таңдау құралының кескін көзі таңдалған болса, бастапқы файл атауын қосу жұмыс істемейді Қосылған болса, пакеттік өңдеуді пайдалансаңыз, стандартты уақыт белгісін кескін реттік нөміріне ауыстырады Кескінді желіден жүктеңіз Қаласаңыз, алдын ала қарау, масштабтау, өңдеу және сақтау үшін интернеттен кез келген суретті жүктеңіз. Сәйкес Мазмұн масштабы Әрбір суретті Width және Height параметрі арқылы берілген кескінге мәжбүрлейді - арақатынасын өзгертуі мүмкін Суреттердің өлшемін Width немесе Height параметрі арқылы берілген ұзын жағы бар кескіндерге өзгертеді, барлық өлшемді есептеулер сақталғаннан кейін орындалады - арақатынасын сақтайды Берілген кескіндерге кез келген сүзгі тізбегін қолданыңыз Сүзгілер Түс сүзгісі Альфа Экспозиция Ақ баланс Температура Реңк Монохромды Бөлек нүктелер мен көлеңкелер CGA түс кеңістігі Гаусс бұлдыры Қораптың бұлыңғырлығы Екі жақты бұлыңғырлық Бедер Лаплациялық Виньетка Бастау Соңы Кувахара тегістеу Стек бұлыңғыр Масштаб Жалған түс Бірінші түс Масштабты бұлыңғырлау Түс балансы Жарықтық шегі Сурет салу Сіз Files қолданбасын өшірдіңіз, осы мүмкіндікті пайдалану үшін оны белсендіріңіз Суретке эскиз кітапшасындағыдай сурет салыңыз немесе фонның өзінде сурет салыңыз Бояу түсі Бояу альфа Суретке салу Суретті таңдап, оған бірдеңе салыңыз Фон түсі Шифр AES криптографиялық алгоритміне негізделген кез келген файлды (тек суретті ғана емес) шифрлаңыз және шифрын шешіңіз Файлды таңдаңыз Шифрын шешу Шифрды шешу Шифрлау Файл өңделді Бұл файлды құрылғыда сақтаңыз немесе оны қалаған жерге қою үшін бөлісу әрекетін пайдаланыңыз AES-256, GCM режимі, толтыру жоқ, 12 байт кездейсоқ IV. Кілттер SHA-3 хэштері (256 бит) ретінде пайдаланылады. The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. Басқа файлды шифрлау бағдарламалық құралымен немесе қызметтерімен үйлесімділікке кепілдік берілмейтінін ескеріңіз. Сәл басқаша кілт өңдеуі немесе шифр конфигурациясы сәйкессіздікті тудыруы мүмкін. Қосылған жағдайда шығыс файл атауы толығымен кездейсоқ болады %2$s атымен %1$s қалтасына сақталды Бұл параметрлерді әдепкі мәндерге қайтарады. Жоғарыда аталған сақтық көшірме файлынсыз мұны қайтару мүмкін емес екенін ескеріңіз. %1$s мәні салыстырмалы түрде үлкен файл өлшеміне әкелетін жылдам қысуды білдіреді. %2$s кішірек файлға әкелетін баяу қысуды білдіреді. Сақтау аяқталды. Қазір бас тарту үшін қайта сақтау қажет болады. Қосылған болса, жаңартуды тексеру қолданбаның бета нұсқаларын қамтиды Көрсеткілерді салыңыз Қылқаламның жұмсақтығы Жиектерді бұлдырату Қосылған болса, бір түстің орнына айналасындағы бос орындарды толтыру үшін түпнұсқа кескіннің астындағы бұлыңғыр жиектерді салады Пикселдеу Жетілдірілген пиксельдеу Инсульт пикселизациясы Жетілдірілген гауһар пикселизациясы Алмаз пиксельдеу Шеңбер пикселизациясы Жетілдірілген шеңбер пиксельденуі Ауыстыру үшін түс Мақсатты түс Жою керек түс Жеміс салаты Адалдық Мазмұны Әдепкі палитра стилі, ол барлық төрт түсті теңшеуге мүмкіндік береді, басқалары тек негізгі түсті орнатуға мүмкіндік береді Монохромдыдан сәл хроматикалық стиль Бастапқы палитра үшін қатты тақырып, түстілік максимум, басқалары үшін артады Көңілді тақырып - тақырыпта бастапқы түстің реңктері көрсетілмейді Монохромды тақырып, түстер таза қара / ақ / сұр Бастапқы түсті Scheme.primaryContainer ішіне орналастыратын схема Бұл жаңарту тексерушісі жаңа жаңартудың бар-жоғын тексеру үшін GitHub қызметіне қосылады Мазмұндық схемаға өте ұқсас схема Назар аударыңыз Өңделетін жиектер Екеуі де Түстерді инверттеу Қосылған болса, тақырып түстерін теріс түстерге ауыстырады Негізгі экрандағы барлық қолжетімді опциялар арқылы іздеу мүмкіндігін қосады PDF - кескіндерге Суреттер PDF форматына Қарапайым PDF алдын ала қарау Берілген шығыс пішімінде PDF форматын кескіндерге түрлендіру Берілген кескіндерді шығыс PDF файлына жинаңыз Маска сүзгісі Қосылған болса, әдепкі әрекеттің орнына маскаланбаған аумақтардың барлығы сүзіледі Қарапайым нұсқалар Бөлектеу құралы Қалам Құпиялылық бұлыңғыр Жартылай мөлдір өткірленген бөлектеуіш жолдарын сызыңыз Сызбаларыңызға жарқыраған әсер қосыңыз Әдепкі, ең қарапайым - тек түс Жасырғыңыз келетін кез келген нәрсені қорғау үшін сызылған жолдың астындағы кескінді бұлдыратады Құпиялылық бұлыңғырлығына ұқсас, бірақ бұлыңғырлаудың орнына пиксельдейді Қос меңзегіш көрсеткіні бастапқы нүктеден соңғы нүктеге дейін сызық ретінде салады Берілген жолдан қос көрсеткіні салады Белгіленген Рект Сопақ Рект Бастапқы нүктеден соңғы нүктеге дейін түзу салады Бастапқы нүктеден соңғы нүктеге дейін сопақ сызады Бастапқы нүктеден соңғы нүктеге дейін сызылған сопақ сызады \"%1$s\" каталогы табылмады, біз оны әдепкіге ауыстырдық, файлды қайта сақтаңыз Автоматты бекіту Қосылған болса, сақталған кескінді алмасу буферіне автоматты түрде қосады Түпнұсқа файл таңдалған қалтада сақтаудың орнына жаңасымен ауыстырылады, бұл опция сурет көзі \"Explorer\" немесе GetContent болуы керек, оны ауыстырған кезде ол автоматты түрде орнатылады. Бос Суффикс Масштабталған кескіндегі айқындық пен антиалиазинг арасындағы тепе-теңдікке қол жеткізу үшін реттелетін параметрлері бар конволюция сүзгісін қолданатын қайта үлгілеу әдісі Қисық сызықты немесе бетті, икемді және үздіксіз пішінді бейнелеуді біркелкі интерполяциялау және жақындату үшін бөліктермен анықталған көпмүшелік функцияларды пайдаланады. Автоматты Бір баған Бір блокты тік мәтін Бір блок Бір жол Бір сөз Шеңбер сөз Жалғыз таңба Сирек мәтін Барлық тану түрлері үшін немесе тек таңдалған біреуіне (%2$s) арналған \"%1$s\" OCR оқу деректерін жойғыңыз келе ме? Барлық Сұр шкала Байер екіден екіге дитеринг Байер Үштен Үшке Дитеринг Sierra Lite Dithering Стукки Дитеринг Беркс Дитеринг Жалған Флойд Стейнберг Дитеринг Солдан оңға қарай дитеринг Мұнай Протономалия Винтаж Брауни Coda Chrome Керемет Тританопия Дейтаронотопия Ахроматопсия Қызғылт сары тұман Қызғылт арман Алтын сағат Ыстық жаз Күлгін тұман Күннің шығуы Түрлі-түсті бұрылыс Жұмсақ көктемгі жарық Күз тондары Лаванда арманы Киберпанк Лимонадты жарық Спектрлік өрт Түнгі сиқыр Фантастикалық пейзаж Түс жарылысы Электр градиенті Кемпірқосақ әлемі Қою күлгін Сандық код Ғарыш порталы Қызыл бұрылыс Файлдарды қайта жазу опциясы қосылған кезде кескін пішімін өзгерту мүмкін емес Эмодзи түстер схемасы ретінде Қолмен анықталғанның орнына қолданбаның түс схемасы ретінде негізгі эмодзи түсін пайдаланады Эрозия Анизотропты диффузия Диффузия Өткізгіштік Көлденең жел тепкіш Жылдам екі жақты бұлыңғыр Пуассон бұлыңғырлығы Логарифмдік тонды кескіндеу Кристалдану Сызық түсі Фракталды шыны Амплитудасы Мәрмәр Турбуленттілік Су әсері Өлшем X жиілігі Жиілік Y Амплитудасы X Амплитудасы Y Перлиннің бұрмалануы Hable Filmic Tone Mapping Hejl Burgess Tone Mapping ACES фильмдік тонды салыстыру ACES Hill Tone Mapping Ағымдағы Толық сүзгі Бастау Орталық Соңы Берілген кескіндерге немесе бір кескінге кез келген сүзгі тізбегін қолданыңыз Градиент жасаушы Реттелетін түстермен және сыртқы түрімен берілген шығыс өлшемінің градиентін жасаңыз Жылдамдық Дехазе Омега Қолданбаны бағалаңыз Бағалау Бұл қолданба толығымен тегін, егер сіз оның үлкенірек болғанын қаласаңыз, жобаны Github-да жұлдызшамен белгілеңіз 😄 Түс матрицасы 4x4 Түс матрицасы 3x3 Қарапайым әсерлер Полароид Тритономия Деутаромалы Түнгі көру Жылы Протанопия Ахроматомалия Сызықтық Радиалды Тазарту Градиент түрі Орталық X Орталық Y Тақта режимі Қайталанды Айна Қысқыш Жапсырма Түс тоқтайды Түс қосу Қасиеттер Ласо Берілген жол бойынша жабық толтырылған жолды салады Сурет салу режимі Қос сызықты көрсеткі Еркін сурет салу Қос көрсеткі Сызық көрсеткі Жебе Түзу Жолды кіріс мәні ретінде салады Жолды бастапқы нүктеден соңғы нүктеге дейін сызық ретінде салады Меңзегіш көрсеткіні бастапқы нүктеден соңғы нүктеге дейін сызық ретінде салады Берілген жолдан меңзейтін көрсеткіні салады Контурланған сопақ Бастапқы нүктеден соңғы нүктеге дейін сызылған түзу салады Дитеринг Квантизатор Байер төрттен төртке Дитеринг Байер сегіз сегіздік дитеринг Флойд Стейнберг Дитеринг Джарвис Джудис Нинке Дитеринг Сьерра Дитеринг Екі қатарлы Сьерра Дитеринг Аткинсон Дитеринг Кездейсоқ дитеринг Қарапайым шекті дитеринг Масштаб режимі Билинарлық Ханн Эрмит Ланчос Митчелл Ең жақын Сплайн Негізгі Әдепкі мән Сигма Кеңістіктік сигма Медиандық бұлыңғыр Catmull Бикуб Сызықтық (немесе екі өлшемді, екі өлшемді) интерполяция әдетте кескіннің өлшемін өзгерту үшін жақсы, бірақ бөлшектердің кейбір жағымсыз жұмсартуын тудырады және әлі де біраз қисық болуы мүмкін. Жақсырақ масштабтау әдістеріне Lanczos қайта үлгілеу және Митчелл-Нетравали сүзгілері кіреді Өлшемді үлкейтудің қарапайым тәсілдерінің бірі, әр пикселді бірдей түсті пикселдер санымен ауыстыру Барлық дерлік қолданбаларда қолданылатын қарапайым Android масштабтау режимі Бірқалыпты қисықтарды жасау үшін компьютерлік графикада әдетте қолданылатын басқару нүктелерінің жиынын біркелкі интерполяциялау және қайта үлгілеу әдісі Терезе функциясы жиі спектрлік ағып кетуді азайту және сигналдың жиектерін тарылту арқылы жиілікті талдаудың дәлдігін жақсарту үшін сигналды өңдеуде қолданылады. Тегіс және үздіксіз қисық генерациялау үшін қисық сегменттің соңғы нүктелеріндегі мәндер мен туындыларды пайдаланатын математикалық интерполяция әдісі Пиксель мәндеріне өлшенген sinc функциясын қолдану арқылы жоғары сапалы интерполяцияны сақтайтын қайта үлгілеу әдісі Тек клип Жадқа сақтау орындалмайды және кескін тек алмасу буферіне қойылады Таңдалған пішіні бар контейнерді карталардың жетекші белгішелерінің астына қосады Белгіше пішіні Жарықтықты қамтамасыз ету Экран Градиент қабаттасуы Берілген кескіннің жоғарғы жағының кез келген градиентін жасаңыз Трансформациялар Камера Суретке түсіру үшін камераны пайдаланады, осы сурет көзінен бір ғана суретті алуға болатынын ескеріңіз Астық Өшіру Пастел Карамель қараңғылығы Футуристік градиент Жасыл күн Су таңбалау Суреттерді реттелетін мәтін/сурет су белгілерімен жабыңыз Су таңбасын қайталаңыз Берілген орындағы жалғыздың орнына су таңбасын кескіннің үстіне қайталайды Офсет X Офсет Y Су таңбасы түрі Бұл сурет су таңбасы үшін үлгі ретінде пайдаланылады Мәтін түсі Қабаттасу режимі Боке GIF құралдары Суреттерді GIF суретіне түрлендіру немесе берілген GIF кескінінен жақтауларды шығарып алу суреттерге GIF GIF файлын суреттер топтамасына түрлендіру Суреттер партиясын GIF файлына түрлендіру GIF форматындағы суреттер Бастау үшін GIF кескінін таңдаңыз Бірінші кадрдың өлшемін пайдаланыңыз Көрсетілген өлшемді бірінші жақтау өлшемдерімен ауыстырыңыз Сананы қайталау Кадр кідірісі миллис FPS Lasso пайдаланыңыз Өшіруді орындау үшін сурет режимінде Lasso сияқты пайдаланады Түпнұсқа кескінді алдын ала қарау альфасы Қолданбалар тақтасының эмодзилері таңдалғанды пайдаланудың орнына кездейсоқ түрде үздіксіз өзгертіледі Кездейсоқ эмодзилер Эмодзилер өшірілген кезде кездейсоқ эмодзилерді таңдау мүмкін емес Кездейсоқ таңдалған кезде эмодзиді таңдау мүмкін емес Маңызды жерлер Көлеңкелер Тұман Әсер Қашықтық Еңіс Қайрау Теріс Соляризация Діріл Кроссшеч Аралық Сызық ені Собель жиегі Бұлыңғыр Жартылай реңк Ескі теледидар Бұлыңғырлықты араластыру OCR (мәтінді тану) Берілген кескіндегі мәтінді тану, 120+ тілге қолдау көрсетіледі Суретте мәтін жоқ немесе қолданба оны таппады Accuracy: %1$s Тану түрі Жылдам Стандартты Ең жақсы Деректер жоқ Tesseract OCR дұрыс жұмыс істеуі үшін құрылғыңызға қосымша жаттығу деректерін (%1$s) жүктеп алу қажет. \n%2$s деректерін жүктеп алғыңыз келе ме? Жүктеп алу Байланыс жоқ, пойыз үлгілерін жүктеп алу үшін оны тексеріп, әрекетті қайталаңыз Жүктелген тілдер Қолжетімді тілдер Сегменттеу режимі Қылқалам өшірудің орнына фонды қалпына келтіреді Көлденең тор Тік тор Тігіс режимі Жолдар саны Бағандар саны Pixel Switch пайдаланыңыз Сіз негізделген google материалының орнына пикселге ұқсас қосқыш пайдаланылады Слайд Қатар Түртуді ауыстырып қосу Мөлдірлік Бастапқы тағайындалған жерде %1$s атымен қайта жазылған файл Үлкейткіш Жақсырақ қол жетімділік үшін сурет салу режимдерінде саусақтың жоғарғы жағындағы ұлғайтқышты қосады Бастапқы мәнді мәжбүрлеу Бастапқыда exif виджетін тексеруге мәжбүрлейді Бірнеше тілге рұқсат ету Таңдаулы Таңдаулы сүзгілер әлі қосылмаған B Сплайн Қисық сызықты немесе бетті, икемді және үздіксіз пішінді бейнелеуді біркелкі интерполяциялау және жақындату үшін бөліктермен анықталған екі кубтық көпмүшелік функцияларды пайдаланады Native Stack Blur Еңкейту жылжыту Кескін өзгертулері бастапқы мәндерге оралады Барлық кескін EXIF деректері жойылады. Бұл әрекетті қайтару мүмкін емес! Жалғыз өңдеу Қолданба тақырыбы таңдалған түске негізделеді Қате туралы есептерді және мүмкіндік сұрауларын осында жіберіңіз Электрондық пошта Жұмыс істеу үшін кескіндерді сақтау үшін қолданбаға жадқа кіру рұқсаты қажет. Келесі диалогтық терезеде рұқсат беріңіз. Эмодзи Негізгі экранда қандай эмодзи көрсетілетінін таңдаңыз Файл өлшемін қосыңыз Қосылған болса, шығыс файлының атына сақталған кескіннің енін және биіктігін қосады Кез келген кескіндер жинағынан EXIF метадеректерін жойыңыз Кескіндердің кез келген түрін алдын ала қараңыз: GIF, SVG және т.б Сурет көзі Фото таңдаушы Галерея Файл зерттеушісі Экранның төменгі жағында пайда болатын Android заманауи фото таңдау құралы тек Android 12+ жүйесінде жұмыс істей алады. EXIF метадеректерін қабылдауда мәселелер бар Қарапайым галерея кескінін таңдау құралы. Ол медиа таңдауды қамтамасыз ететін қолданба болған жағдайда ғана жұмыс істейді Реңк Сүзгіні қосыңыз Сүзгі Бұрмалау Айналмалы Дөңес Кеңейту Шардың сынуы Сыну көрсеткіші Шыны шардың сынуы Түс матрицасы Мөлдірлік Өлшемді өзгертуге шектеулер Таңдалған кескіндердің өлшемін өзгертіп, арақатынасын сақтай отырып, берілген ені мен биіктігі шектеулерін орындаңыз Эскиз Табалдырық Кванттау деңгейлері Тегіс тон Тон Постеризация Максималды емес басу Әлсіз пиксельді қосу Іздеу Конволюция 3x3 Екінші түс Қайта реттеу Жылдам бұлыңғыр Бұлыңғыр өлшем Бұлыңғырлық орталығы x Бұлыңғырлық орталығы y Фонға сурет салу Фон түсін таңдап, оның үстіне сызыңыз Ерекше өзгешеліктері Іске асыру Үйлесімділік Құпия сөз негізінде файлдарды шифрлау. Жалғастырылған файлдарды таңдалған каталогта сақтауға немесе ортақ пайдалануға болады. Шифры шешілген файлдарды да тікелей ашуға болады. Жарамсыз құпия сөз немесе таңдалған файл шифрланбаған Кескінді берілген ені мен биіктігімен сақтауға әрекет OOM қатесін тудыруы мүмкін. Мұны өз тәуекеліңізге байланысты жасаңыз және мен сізге ескертпедім деп айтпаңыз! %1$s табылды Автоматты кэшті тазалау Қосылған болса, қолданбаның кэші қолданбаны іске қосқан кезде тазаланады Жасау Опцияларды түрі бойынша топтаңыз Негізгі экрандағы опцияларды реттелетін тізім реттеуінің орнына түрі бойынша топтайды Опцияларды топтау қосулы кезде реттеуді өзгерту мүмкін емес Қосымша теңшеу Кері опция Алдын ала орнатылған 125 параметрін таңдасаңыз, сурет 100% сапасымен түпнұсқа кескіннің 125% өлшемі ретінде сақталады. Алдын ала орнатылған 50 параметрін таңдасаңыз, сурет 50% өлшеммен және 50% сапамен сақталады. Мұнда алдын ала орнату шығыс файлының % анықтайды, яғни 5 мб суретте алдын ала орнатылған 50 параметрін таңдасаңыз, сақталғаннан кейін 2,5 мб кескінді аласыз. Telegram чаты Қолданбаны талқылаңыз және басқа пайдаланушылардан пікір алыңыз. Сондай-ақ, бета жаңартулары мен түсініктерін осы жерден ала аласыз. Кептірілген маска Аспект арақатынасы Берілген кескіннен маска жасау үшін осы маска түрін пайдаланыңыз, оның альфа арнасы болуы КЕРЕК екенін ескеріңіз Сақтық көшірме жасау және қалпына келтіру Қалпына келтіру Қолданба параметрлерінің сақтық көшірмесін файлға жасаңыз Бұрын жасалған файлдан қолданба параметрлерін қалпына келтіріңіз Зақымдалған файл немесе сақтық көшірме емес Параметрлер сәтті қалпына келтірілді Менімен хабарлас Сіз таңдалған түс схемасын жойғалы жатырсыз. Бұл әрекетті қайтару мүмкін емес Схеманы жою Үлкен қаріп масштабтарын пайдалану UI ақаулары мен ақауларын тудыруы мүмкін, олар түзетілмейді. Абайлаңыз. Эмоциялар Саяхат және орындар Іс-шаралар Фонды кетіргіш Суреттен фондық суретті алып тастаңыз немесе Авто опциясын пайдаланыңыз Кескінді кесу Түпнұсқа кескін метадеректері сақталады Кескіннің айналасындағы мөлдір кеңістіктер кесіледі Фонды автоматты түрде өшіру Кескінді қалпына келтіру Фонды өшіру Фонды қалпына келтіру Бұлыңғырлық радиусы Сурет салу режимі Мәселе жасау Ой… Бірдеңе дұрыс болмады. Төмендегі опцияларды пайдаланып маған жаза аласыз, мен шешім табуға тырысамын Өлшемін өзгерту және түрлендіру Берілген кескіндердің өлшемін өзгертіңіз немесе оларды басқа пішімдерге түрлендіріңіз. Бір суретті таңдасаңыз, EXIF метадеректерін де осы жерде өңдеуге болады. Максималды түс саны Бұл қолданбаға бұзылу есептерін қолмен жинауға мүмкіндік береді Аналитика Анонимді қолданбаны пайдалану статистикасын жинауға рұқсат ету Қазіргі уақытта %1$s пішімі тек Android құрылғысында EXIF метадеректерін оқуға мүмкіндік береді. Шығарылған кескінде сақталған кезде метадеректер мүлдем болмайды. Күш Бета нұсқасына рұқсат беріңіз Суреттер тәртібі Толеранттылық Түсті жою Қайта кодтау Пиксель өлшемі Сызба бағдарын құлыптау Сурет салу режимінде қосылса, экран айналмайды Тоналды нүкте Бейтарап Жанды Экспрессивті Берілген бетперделенген аймақтарға сүзгі тізбегін қолданыңыз, әрбір маска аймағы өзінің сүзгілер жинағын анықтай алады Маска қосыңыз Маска %d Маска түсі Масканы алдын ала қарау Сізге шамамен нәтиже көрсету үшін сызылған сүзгі маскасы көрсетіледі Кері толтыру түрі Конфетти Конфетти сақтау, бөлісу және басқа негізгі әрекеттерде көрсетіледі Қауіпсіз режим Шығу кезінде мазмұнды жасырады, сонымен қатар экранды түсіру немесе жазу мүмкін емес Қосылған болса, сызба жолы меңзегіш көрсеткі ретінде көрсетіледі Суреттер енгізілген өлшемге дейін ортасынан қиылады. Кескін енгізілген өлшемдерден кішірек болса, кенеп берілген өң түсімен кеңейтіледі. Қайырымдылық Бір үлкен кескін алу үшін берілген кескіндерді біріктіріңіз Кем дегенде 2 суретті таңдаңыз Шығарылатын кескін масштабы Кескінді бағдарлау Көлденең Вертикалды Кішкентай кескіндерді үлкенге дейін масштабтаңыз Қосылған болса, шағын кескіндер реттіліктегі ең үлкеніне дейін масштабталады Тұрақты PDF файлдарымен жұмыс істеу: алдын ала қарау, кескіндер топтамасына түрлендіру немесе берілген суреттерден біреуін жасау Неон %1$s - %2$s ауқымындағы мән Автоматты айналдыру Кескінді бағдарлау үшін шектеу жолағын қабылдауға мүмкіндік береді Контейнерлер Контейнерлердің артындағы көлеңкелі суретті қосады Жүгірткілер Коммутаторлар FABs Түймелер Жүгірткілердің артындағы көлеңкелі суретті қосады Коммутаторлардың артындағы көлеңке сызбасын қосады Қалқымалы әрекет түймелерінің артында көлеңкелі сурет салуды қосады Әдепкі түймелердің артындағы көлеңке сызбасын қосады Қолданба жолақтары Қолданба жолақтарының артындағы көлеңкелі суретті қосады Шығу Алдын ала қараудан қазір шықсаңыз, суреттерді қайта қосуыңыз керек Сурет пішімі Кескіннен \"Material You \" палитрасын жасайды Қою Түстер Жарық нұсқасының орнына түнгі режимнің түс схемасын қолданады \"Jetpack Compose\" коды ретінде көшіріңіз Сызықтық көлбеу жылжу Сақинаның Бұлыңғырлығы Көлденең Бұлыңғырлық Шеңбер Бұлыңғыр Жұлдызды Бұлыңғырлық Жою Үшін Тегтер Суреттерге APNG APNG файлын суреттер топтамасына түрлендіру APNG құралдары Суреттерді APNG суретіне түрлендіру немесе берілген APNG кескінінен жақтауларды шығарып алу Суреттер партиясын APNG файлына түрлендіру Суреттер APNG Бастау үшін APNG кескінін таңдаңыз Размытие движением Zip Берілген файлдардан немесе кескіндерден Zip файлын жасаңыз Тұтқаны ені сүйреңіз Конфетти түрі Мерекелік Жарылу Жаңбыр Бұрыштар JXL құралдары JXL ~ JPEG кодтауын сапа жоғалтпай орындаңыз немесе GIF/APNG форматын JXL анимациясына түрлендіріңіз. JXL - JPEG JXL-ден JPEG-ге жоғалтпай қайта кодтауды орындаңыз JPEG-ден JXL-ге жоғалтпай қайта кодтауды орындаңыз JPEG - JXL Бастау үшін JXL кескінін таңдаңыз Жылдам Gaussian Blur 2D Жылдам Gaussian Blur 3D Жылдам Gaussian Blur 4D Көлік Пасха Қолданбаға алмасу буферінің деректерін автоматты қоюға рұқсат береді, сондықтан ол негізгі экранда пайда болады және сіз оны өңдей аласыз Гармонизация түсі Гармонизация деңгейі Ланчос Бессель Бессель (jinc) функциясын пиксель мәндеріне қолдану арқылы жоғары сапалы интерполяцияны сақтайтын қайта үлгілеу әдісі JXL үшін GIF GIF кескіндерін JXL анимациялық суреттеріне түрлендіру APNG – JXL APNG кескіндерін JXL анимациялық суреттеріне түрлендіру JXL-ден Суреттерге JXL анимациясын суреттер топтамасына түрлендіру Суреттер JXL Суреттер партиясын JXL анимациясына түрлендіру Мінез-құлық Файлды таңдауды өткізіп жіберу Мүмкін болса, таңдалған экранда файл таңдау құралы бірден көрсетіледі Алдын ала қарауларды жасау Алдын ала қарау генерациясын қосады, бұл кейбір құрылғыларда бұзылуларды болдырмауға көмектесуі мүмкін, сонымен қатар бір өңдеу опциясындағы кейбір өңдеу функцияларын өшіреді Жоғалған қысу Файл өлшемін жоғалтпайтын орнына азайту үшін жоғалтатын қысуды пайдаланады Қысу түрі Нәтижедегі кескінді декодтау жылдамдығын басқарады, бұл алынған кескінді тезірек ашуға көмектеседі, %1$s мәні ең баяу декодтауды білдіреді, ал %2$s - ең жылдам, бұл параметр шығыс кескін өлшемін арттыруы мүмкін Сұрыптау Күн Күні (кері) Аты Аты (кері) Арналар конфигурациясы Бүгін Кеше Енгізілген таңдау құралы Кескін құралдар жинағының кескін таңдау құралы Рұқсат жоқ Сұраныс Бірнеше медианы таңдаңыз Бір медианы таңдаңыз Таңдау Қайтадан байқап көріңіз Параметрлерді ландшафтта көрсету Егер бұл өшірілсе, ландшафт режимінде параметрлер тұрақты көрінетін опцияның орнына әдеттегідей жоғарғы қолданбалар жолағындағы түймеде ашылады. Толық экран параметрлері Оны қосыңыз және параметрлер беті жылжымалы тартпа парағының орнына әрқашан толық экран ретінде ашылады Ауыстыру түрі Құрастыру Jetpack құрастыру материалы Сіз ауыстырасыз Сіз ауыстыратын материал Макс Анкордың өлшемін өзгерту пиксел Еркін \"Fluent\" дизайн жүйесіне негізделген қосқыш Купертино \"Купертино\" дизайн жүйесіне негізделген қосқыш Суреттер SVG SVG кескіндеріне берілген кескіндерді қадағалаңыз Үлгі палитрасын пайдаланыңыз Бұл опция қосылған болса, кванттау палитрасы таңдалады Жолды қалдыру Бұл құралды масштабты кішірейтусіз үлкен кескіндерді қадағалау үшін пайдалану ұсынылмайды, ол бұзылуға және өңдеу уақытын арттыруға әкелуі мүмкін. Кескінді кішірейту Кескін өңдеу алдында кіші өлшемдерге дейін кішірейтіледі, бұл құралдың жылдамырақ және қауіпсіз жұмыс істеуіне көмектеседі Минималды түс қатынасы Жолдардың шегі Квадраттық шек Дөңгелектеу төзімділігін үйлестіреді Жол масштабы Сипаттарды қалпына келтіру Барлық сипаттар әдепкі мәндерге орнатылады, бұл әрекетті қайтару мүмкін емес екенін ескеріңіз Егжей-тегжейлі Әдепкі сызық ені Қозғалтқыш режимі Мұра LSTM желісі Legacy &amp; LSTM Түрлендіру Кескін топтамаларын берілген пішімге түрлендіру Жаңа қалта қосу Бір үлгідегі бит Қысу Фотометриялық интерпретация Бір пиксельге арналған үлгілер Жазық конфигурация Y Cb Cr ішкі сынама алу Y Cb Cr Орналастыру X ажыратымдылығы Y Ажыратымдылығы Ажыратымдылық бірлігі Жолақтардың ығысулары Жолақтағы жолдар Жолақ байт санаулары JPEG алмасу пішімі JPEG алмасу пішімінің ұзындығы Тасымалдау функциясы Ақ нүкте Бастапқы хроматиктер Y Cb Cr коэффициенттері Анықтама қара ақ Күн уақыты Сурет сипаттамасы Жасаңыз Үлгі Бағдарламалық қамтамасыз ету Суретші Авторлық құқық Exif нұсқасы Flashpix нұсқасы Түс кеңістігі Гамма Pixel X өлшемі Pixel Y өлшемі Бір пиксельге қысылған бит Жасаушы жазбасы Пайдаланушы пікірі Қатысты дыбыс файлы Күні Уақыт Түпнұсқа Цифрланған күн уақыты Ауыстыру уақыты Офсет уақыты түпнұсқасы Офсет уақыты цифрланған Қосымша секунд уақыты Қосымша секунд уақыты Түпнұсқа Қосалқы сек уақыты Цифрланған Экспозиция уақыты F саны Экспозиция бағдарламасы Спектрлік сезімталдық Фотосезімталдық Oecf Сезімталдық түрі Стандартты шығыс сезімталдығы Ұсынылатын экспозиция индексі ISO жылдамдығы ISO жылдамдығы ендік жж ISO жылдамдығы ендік zzz Ысырма жылдамдығының мәні Диафрагма мәні Жарықтық мәні Экспозицияның ауытқу мәні Максималды диафрагма мәні Тақырып қашықтығы Есептеу режимі Жарқыл Пән аймағы Фокус ұзындығы Жарқыл энергиясы Кеңістіктік жиілікке жауап беру Фокус жазықтығы X ажыратымдылығы Фокус жазықтығы Y ажыратымдылығы Фокус жазықтығы ажыратымдылық бірлігі Тақырып орны Экспозиция индексі Сезімдеу әдісі Файл көзі CFA үлгісі Арнайы көрсетілген Экспозиция режимі Ақ баланс Сандық масштабтау коэффициенті Фокус ұзындығы 35 мм пленка Көрініс түсіру түрі Бақылауды алу Контраст Қанықтылық Айқындық Құрылғы параметрінің сипаттамасы Тақырып қашықтығы Кескіннің бірегей идентификаторы Камера иесінің аты Дененің сериялық нөмірі Линзаның сипаттамасы Объектив жасау Объектив үлгісі Объективтің сериялық нөмірі GPS нұсқасының идентификаторы GPS ендік сілтемесі GPS ендігі GPS бойлық сілтемесі GPS бойлығы GPS биіктігі сілтемесі GPS биіктігі GPS уақыт белгісі GPS спутниктері GPS күйі GPS өлшеу режимі GPS DOP GPS жылдамдығы сілтемесі GPS жылдамдығы GPS Track Ref GPS Track GPS Img бағыты сілтемесі GPS Img бағыты GPS картасының деректері GPS Dest Latitude Ref GPS Dest Latitude GPS мақсатты бойлық сілтемесі GPS мақсатты бойлық GPS Dest Bearing Ref GPS тіреуіш GPS мақсатты қашықтығы сілтемесі GPS мақсатты қашықтығы GPS өңдеу әдісі GPS аймағы туралы ақпарат GPS күн белгісі GPS дифференциалы GPS H орналасу қатесі Өзара жұмыс істеу индексі DNG нұсқасы Әдепкі қию өлшемі Кескінді алдын ала қарауды бастау Кескін ұзындығын алдын ала қарау Аспект жақтауы Датчиктің төменгі жиегі Датчиктің сол жақ шекарасы Датчиктің оң жақ жиегі Сенсордың жоғарғы жиегі ISO Берілген қаріп пен түспен жолға мәтін салыңыз Қаріп өлшемі Су таңбасының өлшемі Мәтінді қайталау Ағымдағы мәтін бір рет салудың орнына жолдың соңына дейін қайталанады Сызық өлшемі Таңдалған кескінді берілген жол бойымен салу үшін пайдаланыңыз Бұл сурет сызылған жолдың қайталанатын жазбасы ретінде пайдаланылады Сызылған үшбұрышты бастапқы нүктеден соңғы нүктеге дейін салады Сызылған үшбұрышты бастапқы нүктеден соңғы нүктеге дейін салады Сызылған үшбұрыш Үшбұрыш Бастапқы нүктеден соңғы нүктеге дейін көпбұрышты салады Көпбұрыш Құрылған көпбұрыш Бастапқы нүктеден соңғы нүктеге дейін сызылған көпбұрышты салады Шыңдар Тұрақты көпбұрышты сызу Еркін пішіннің орнына тұрақты болатын көпбұрышты сызыңыз Жұлдызшаны бастапқы нүктеден соңғы нүктеге дейін салады Жұлдыз Белгіленген жұлдыз Сызылған жұлдызды бастапқы нүктеден соңғы нүктеге дейін салады Ішкі радиус қатынасы Тұрақты жұлдызды сал Еркін пішіннің орнына тұрақты болатын жұлдызды сызыңыз Антиалиас Өткір жиектерді болдырмау үшін антиалиазингті қосады Алдын ала қараудың орнына Өңдеуді ашыңыз ImageToolbox қолданбасында ашу (алдын ала қарау) үшін кескінді таңдаған кезде, таңдау парағы алдын ала қараудың орнына ашылады. Құжат сканері Құжаттарды сканерлеңіз және олардан PDF немесе бөлек кескіндер жасаңыз Сканерлеуді бастау үшін басыңыз Сканерлеуді бастаңыз Pdf ретінде сақтау Pdf ретінде бөлісіңіз Төмендегі опциялар PDF емес, кескіндерді сақтауға арналған HSV гистограммасын теңестіру Гистограмманы теңестіру Процентті енгізіңіз Мәтін өрісі арқылы енгізуге рұқсат етіңіз Оларды жылдам енгізу үшін алдын ала орнату таңдауының артындағы мәтін өрісін қосады Түс кеңістігінің масштабы Сызықтық Гистограмма пикселін теңестіру Тор өлшемі X Тор өлшемі Y Gistogram Adaptive теңестіру Адаптивті LUV гистограммасын теңестіру Гистограмманы теңестіру Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Мазмұнға қию Жақтау түсі Елемеу үшін түс Үлгі Үлгі сүзгілері қосылмаған Жаңа жасау Сканерленген QR коды жарамды сүзгі үлгісі емес QR кодын сканерлеңіз Таңдалған файлда сүзгі үлгі деректері жоқ Үлгі жасау Үлгі атауы Бұл сурет осы сүзгі үлгісін алдын ала қарау үшін пайдаланылады Үлгі сүзгісі QR код суреті ретінде Файл ретінде Файл ретінде сақтау QR код суреті ретінде сақтаңыз Үлгіні жою Таңдалған үлгі сүзгісін жойғалы жатырсыз. Бұл әрекетті қайтару мүмкін емес \"%1$s\" (%2$s) атты сүзгі үлгісі қосылды Сүзгі алдын ала қарау QR және штрих-код QR кодын сканерлеңіз және оның мазмұнын алыңыз немесе жаңасын жасау үшін жолды қойыңыз Код мазмұны Өрістегі мазмұнды ауыстыру үшін кез келген штрих-кодты сканерлеңіз немесе таңдалған түрі бар жаңа штрих-код жасау үшін бірдеңені теріңіз QR сипаттамасы Мин QR кодын сканерлеу үшін параметрлерде камераға рұқсат беріңіз Құжат сканерін сканерлеу үшін параметрлерде камераға рұқсат беріңіз Текше B-Сплайн Хэминг Ханнинг Блэкман Уэлч Квадрат Гаусс Сфинкс Барлетт Робиду Робиду Шарп Сплайн 16 Сплайн 36 Сплайн 64 Кайзер Бартлет-О Қорап Боман Ланчос 2 Ланчос 3 Ланчос 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Кубтық интерполяция ең жақын 16 пиксельді ескере отырып, біркелкі масштабтауды қамтамасыз етеді, екі сызықтыға қарағанда жақсы нәтиже береді. Қисық сызықты немесе бетті, икемді және үздіксіз пішінді бейнелеуді біркелкі интерполяциялау және жақындату үшін бөліктермен анықталған көпмүшелік функцияларды пайдаланады. Сигналдың жиектерін кішірейту арқылы спектрлік ағып кетуді азайту үшін қолданылатын терезе функциясы, сигналды өңдеуде пайдалы Сигналды өңдеу қолданбаларында спектрлік ағып кетуді азайту үшін әдетте қолданылатын Hann терезесінің нұсқасы Сигналдарды өңдеуде жиі қолданылатын спектрлік ағып кетуді азайту арқылы жақсы жиілікті ажыратымдылықты қамтамасыз ететін терезе функциясы Сигналдарды өңдеу қолданбаларында жиі қолданылатын спектрлік ағып кетуді азайту арқылы жақсы жиілік ажыратымдылығын беруге арналған терезе функциясы Біркелкі және үздіксіз нәтижелерді қамтамасыз ететін интерполяция үшін квадраттық функцияны қолданатын әдіс Гаусс функциясын қолданатын интерполяция әдісі, кескіндердегі шуды тегістеу және азайту үшін пайдалы Ең аз артефактілермен жоғары сапалы интерполяцияны қамтамасыз ететін кеңейтілген қайта үлгілеу әдісі Спектрлік ағып кетуді азайту үшін сигналды өңдеуде қолданылатын үшбұрышты терезе функциясы Суреттің табиғи өлшемін өзгерту, айқындық пен тегістікті теңестіру үшін оңтайландырылған жоғары сапалы интерполяция әдісі Кескін өлшемін анық өзгерту үшін оңтайландырылған Robidoux әдісінің айқынырақ нұсқасы 16 түрту сүзгісі арқылы біркелкі нәтижелерді қамтамасыз ететін сплайн негізіндегі интерполяция әдісі 36 түрту сүзгісі арқылы біркелкі нәтижелерді қамтамасыз ететін сплайн негізіндегі интерполяция әдісі 64 түрту сүзгісі арқылы біркелкі нәтижелерді қамтамасыз ететін сплайн негізіндегі интерполяция әдісі Кайзер терезесін пайдаланатын интерполяция әдісі, негізгі лоб ені мен бүйірлік лоб деңгейі арасындағы айырбасты жақсы бақылауды қамтамасыз етеді. Сигналды өңдеу кезінде спектрлік ағып кетуді азайту үшін қолданылатын Бартлетт пен Ханн терезелерін біріктіретін гибридті терезе функциясы Ең жақын пиксель мәндерінің орташа мәнін пайдаланатын қарапайым қайта іріктеу әдісі, көбінесе бұғатталған көрініске әкеледі. Сигналды өңдеу қолданбаларында жақсы жиілікті ажыратымдылықты қамтамасыз ететін спектрлік ағып кетуді азайту үшін пайдаланылатын терезе функциясы Ең аз артефактілермен жоғары сапалы интерполяция үшін 2 лобты Lanczos сүзгісін қолданатын қайта үлгілеу әдісі Ең аз артефактілермен жоғары сапалы интерполяция үшін 3 лобты Lanczos сүзгісін қолданатын қайта үлгілеу әдісі Ең аз артефактілермен жоғары сапалы интерполяция үшін 4 лобты Lanczos сүзгісін қолданатын қайта үлгілеу әдісі Ең аз артефактілермен жоғары сапалы интерполяцияны қамтамасыз ететін jinc функциясын пайдаланатын Lanczos 2 сүзгісінің нұсқасы Ең аз артефактілермен жоғары сапалы интерполяцияны қамтамасыз ететін jinc функциясын пайдаланатын Lanczos 3 сүзгісінің нұсқасы Ең аз артефактілермен жоғары сапалы интерполяцияны қамтамасыз ететін jinc функциясын пайдаланатын Lanczos 4 сүзгісінің нұсқасы Hanning EWA Тегіс интерполяция және қайта үлгілеу үшін Hanning сүзгісінің эллиптикалық орташа (EWA) нұсқасы Robidoux EWA Жоғары сапалы қайта үлгілеуге арналған Robidoux сүзгісінің эллиптикалық орташа (EWA) нұсқасы Блэкмен ЭВЕ Қоңырау артефактілерін азайтуға арналған Блэкман сүзгісінің эллиптикалық орташа (EWA) нұсқасы Quadric EWA Тегіс интерполяцияға арналған квадрикалық сүзгінің Эллиптикалық салмақты орташа (EWA) нұсқасы Robidoux Sharp EWA Анық нәтижелер үшін Robidoux Sharp сүзгісінің эллиптикалық орташа (EWA) нұсқасы Lanczos 3 Jinc EWA Азайтылған бүркеншік атпен жоғары сапалы қайта үлгілеуге арналған Lanczos 3 Jinc сүзгісінің эллиптикалық орташа (EWA) нұсқасы женьшень Айқындық пен тегістіктің жақсы тепе-теңдігі бар жоғары сапалы кескінді өңдеуге арналған қайта үлгілеу сүзгісі Женьшень EWA Кескін сапасын жақсартуға арналған женьшень сүзгісінің эллиптикалық орташа (EWA) нұсқасы Lanczos Sharp EWA Ең аз артефактілермен айқын нәтижелерге қол жеткізу үшін Lanczos Sharp сүзгісінің эллиптикалық орташа (EWA) нұсқасы Lanczos 4 Ең өткір EWA Өте анық кескінді қайта үлгілеуге арналған Lanczos 4 Sharpest сүзгісінің эллиптикалық орташа (EWA) нұсқасы Lanczos Soft EWA Кескінді қайта үлгілеуге арналған Lanczos Soft сүзгісінің эллиптикалық орташа (EWA) нұсқасы Haasn Soft Кескінді тегіс және артефактсыз масштабтау үшін Haasn жасаған қайта үлгілеу сүзгісі Форматты түрлендіру Суреттер партиясын бір пішімнен екіншісіне түрлендіру Мәңгілікке шығару Кескінді жинақтау Таңдалған араластыру режимдерімен кескіндерді бірінің үстіне бірін жинаңыз Кескін қосу Қоқыс жәшіктері Clahe HSL Clahe HSV Гистограмманы теңестіру Adaptive HSL Гистограмманы теңестіру адаптивті HSV Жиек режимі Клип Орау Түс соқырлығы Таңдалған түс соқырлығы нұсқасына тақырып түстерін бейімдеу үшін режимді таңдаңыз Қызыл және жасыл реңктерді ажырату қиын Жасыл және қызыл реңктерді ажырату қиын Көк және сары реңктерді ажырату қиын Қызыл реңктерді қабылдай алмау Жасыл реңктерді қабылдай алмау Көк реңктерді қабылдай алмау Барлық түстерге сезімталдықтың төмендеуі Толық түсті соқырлық, тек сұр реңктерді көру Color Blind схемасын пайдаланбаңыз Түстер тақырыпта көрсетілгендей болады Сигмоидальды Лагранж 2 2 ретті Лагранж интерполяциялық сүзгісі, біркелкі ауысулары бар жоғары сапалы кескінді масштабтау үшін жарамды Лагранж 3 Кескінді масштабтау үшін жақсырақ дәлдік пен тегіс нәтижелерді ұсынатын 3 ретті Лагранж интерполяциялық сүзгісі Ланчос 6 Кескінді нақтырақ және дәлірек масштабтауды қамтамасыз ететін, жоғарырақ реті 6 болатын Lanczos қайта үлгілеу сүзгісі. Lanczos 6 Jinc Кескінді қайта үлгілеу сапасын жақсарту үшін Jinc функциясын пайдаланатын Lanczos 6 сүзгісінің нұсқасы Сызықтық қорап бұлыңғыр Сызықтық шатыр бұлдыры Сызықтық гаусс қорапшасының бұлыңғырлығы Сызықтық стек бұлыңғыр Gaussian Box Blur Сызықтық жылдам гаусс бұлыңғырлығы Келесі Сызықтық жылдам гаусс бұлыңғырлығы Сызықтық Гаусс бұлыңғырлығы Бояу ретінде пайдалану үшін бір сүзгіні таңдаңыз Сүзгіні ауыстыру Оны суретте қылқалам ретінде пайдалану үшін төмендегі сүзгіні таңдаңыз TIFF қысу схемасы Төмен поли Құммен сурет салу Кескінді бөлу Бір кескінді жолдар немесе бағандар бойынша бөлу Шектерге сәйкес Қажетті әрекетке қол жеткізу үшін кесу өлшемін өзгерту режимін осы параметрмен біріктіріңіз (Пікірлер арақатынасына қию/Сәйкестендіру) Тілдер сәтті импортталды OCR үлгілерінің сақтық көшірмесін жасау Импорттау Экспорттау Позиция Орталық Жоғарғы сол Жоғарғы оң Төменгі сол Төменгі оң жақ Жоғарғы орталық Орталық оң Төменгі орталық Орталық сол Мақсатты кескін Палитраны тасымалдау Жетілдірілген май Қарапайым ескі теледидар HDR Готам Қарапайым эскиз Жұмсақ жарқырау Түсті плакат Үш тон Үшінші түс Клахе Оклаб Клара Олкс Клахе Джазбз Полка нүкте Кластерленген 2x2 Дитеринг Кластерленген 4x4 дитеринг Кластерленген 8x8 Дитеринг Йилиома Дитеринг Таңдаулы опциялар таңдалмаған, оларды құралдар бетіне қосыңыз Таңдаулыларды қосу Қосымша Аналогты Үштік Бөлу қосымшасы Тетрадик Шаршы Аналогтық + Толықтауыш Түс құралдары Араластырыңыз, тондар жасаңыз, реңктер жасаңыз және т.б Түс гармониясы Түс көлеңкесі Вариация Реңктер Тондар Көлеңкелер Түсті араластыру Түс туралы ақпарат Таңдалған түс Араластыратын түс Динамикалық түстер қосулы кезде monet пайдалану мүмкін емес 512x512 2D LUT Мақсатты LUT кескіні Әуесқой Этикет ханым Жұмсақ талғампаздық Жұмсақ талғампаздық нұсқасы Палитраны тасымалдау нұсқасы 3D LUT Мақсатты 3D LUT файлы (.cube / .CUBE) LUT Ағартқышты айналып өту Шам жарығы Блюзді тастаңыз Қатты амбер Күз түстері Фильм қоры 50 Тұманды түн Kodak Бейтарап LUT кескінін алыңыз Алдымен бейтарап LUT-ге сүзгіні қолдану үшін таңдаулы фотосуреттерді өңдеу қолданбасын пайдаланыңыз, оны осы жерден алуға болады. Бұл дұрыс жұмыс істеуі үшін әрбір пиксель түсі басқа пикселдерге тәуелді болмауы керек (мысалы, бұлыңғырлық жұмыс істемейді). Дайын болғаннан кейін, жаңа LUT кескінін 512*512 LUT сүзгісі үшін кіріс ретінде пайдаланыңыз Поп-арт Целлулоид Кофе Алтын орман Жасыл түсті Ретро сары Сілтемелерді алдын ала қарау Мәтін алуға болатын жерлерде (QRCode, OCR т.б.) сілтемені алдын ала қарауды қосады. Сілтемелер ICO файлдарын тек 256 x 256 максималды өлшемінде сақтауға болады GIF-тен WEBP GIF кескіндерін WEBP анимациялық суреттеріне түрлендіру WEBP құралдары Суреттерді WEBP анимациялық суретіне түрлендіру немесе берілген WEBP анимациясынан кадрларды шығарып алу Суреттерге WEBP WEBP файлын суреттер топтамасына түрлендіру Суреттер партиясын WEBP файлына түрлендіру Суреттер WEBP Бастау үшін WEBP кескінін таңдаңыз Файлдарға толық рұқсат жоқ JXL, QOI және Android жүйесінде кескін ретінде танылмаған басқа кескіндерді көру үшін барлық файлдарға рұқсат беріңіз. Рұқсатсыз Image Toolbox бұл кескіндерді көрсете алмайды Әдепкі сызу түсі Әдепкі сызу жолы режимі Уақыт белгісін қосыңыз Уақыт белгісін шығыс файл атауына қосуды қосады Пішімделген уақыт белгісі Негізгі миллис орнына шығыс файл атауында уақыт белгісін пішімдеуді қосыңыз Уақыт белгілерін олардың пішімін таңдау үшін қосыңыз Орынды бір рет сақтау Негізінен барлық опцияларда сақтау түймесін ұзақ басу арқылы пайдалануға болатын бір рет сақтау орындарын қараңыз және өңдеңіз Жақында пайдаланылған CI арнасы Топ Telegram-дағы кескін құралдар жинағы 🎉 Біздің чатқа қосылыңыз, онда сіз қалаған нәрсені талқылай аласыз, сонымен қатар мен бета нұсқалары мен хабарландыруларды жариялайтын CI арнасын қараңыз. Қолданбаның жаңа нұсқалары туралы хабарландыру алыңыз және хабарландыруларды оқыңыз Кескінді берілген өлшемдерге сәйкестендіру және фонға бұлыңғырлау немесе түс қолдану Құралдарды реттеу Құралдарды түрлеріне қарай топтастыру Негізгі экрандағы құралдарды реттелетін тізім реттеуінің орнына түрі бойынша топтайды Әдепкі мәндер Жүйе жолақтарының көрінуі Жүйе жолақтарын сырғыту арқылы көрсету Жүйе жолақтары жасырылған болса, оларды көрсету үшін сырғыту мүмкіндігін қосады Авто Барлығын жасыру Барлығын көрсету Навигация жолағын жасыру Күй жолағын жасыру Шудың пайда болуы Perlin немесе басқа түрлер сияқты әртүрлі шуды жасаңыз Жиілік Шу түрі Айналу түрі Фракталды түрі Октавалар Лакунарлылық Табыс Салмақты күш Пинг-понг күші Қашықтық функциясы Қайтару түрі Дірілдеу Доменнің бұзылуы Туралау Пайдаланушы файл аты Ағымдағы кескінді сақтау үшін пайдаланылатын орын мен файл атауын таңдаңыз Пайдаланушы аты бар қалтаға сақталды Коллаж жасаушы 20 суретке дейін коллаждар жасаңыз Коллаж түрі Орынды реттеу үшін ауыстыру, жылжыту және масштабтау үшін кескінді басып тұрыңыз Айналуды өшіру Екі саусақ қимылымен кескіндердің айналуын болдырмайды Жиектерге түсіруді қосыңыз Жылжытқаннан немесе масштабтағаннан кейін кескіндер жақтау жиектерін толтыру үшін түсіріледі Гистограмма RGB немесе Жарықтық кескінінің гистограммасы түзетулер енгізуге көмектеседі Бұл кескін RGB және Brightness гистограммаларын жасау үшін пайдаланылады Tesseract опциялары Тессеракт қозғалтқышы үшін кейбір кіріс айнымалыларын қолданыңыз Теңшелетін опциялар Параметрлерді мына үлгі бойынша енгізу керек: \"--{option_name} {value}\" Автоматты қию Еркін бұрыштар Кескінді көпбұрыш бойынша қию, бұл да перспективаны түзетеді Кескін шекараларына мәжбүрлеу нүктелері Ұпайлар кескін шекараларымен шектелмейді, бұл перспективаны дәлірек түзету үшін пайдалы Маска Сызылған жолдың астындағы мазмұнды толтыру Емдеу нүктесі Шеңбер ядросын пайдаланыңыз Ашылу Жабу Морфологиялық градиент Жоғарғы қалпақ Қара қалпақ Тондық қисықтар Қисықтарды қалпына келтіру Қисықтар әдепкі мәнге оралады Сызық стилі Саңылау өлшемі Сызық Нүкте сызықша Мөр басылған Зигзаг Белгіленген саңылау өлшемімен сызылған жол бойымен үзік сызық сызады Берілген жол бойымен нүкте және үзік сызық сызады Тек әдепкі түзу сызықтар Белгіленген аралықпен жол бойымен таңдалған кескіндерді салады Жол бойымен толқынды ирек сызбаларды салады Зигзаг қатынасы Таңбаша жасау Бекіту үшін құралды таңдаңыз Құрал іске қосу құралының негізгі экранына таңбаша ретінде қосылады, қажетті әрекетке жету үшін оны \"Файлды таңдауды өткізіп жіберу\" параметрімен біріктіріп пайдаланыңыз. Жақтауларды жинақтамаңыз Алдыңғы кадрларды жоюға мүмкіндік береді, сондықтан олар бір-біріне жиналмайды Кроссфад Жақтаулар бір-біріне қиылысады Кроссфад кадрлары есептеледі Бір табалдырық Екінші шек Canny Айна 101 Жетілдірілген масштабтауды бұлыңғырлау Қарапайым лаплас Sobel Simple Көмекші торы Нақты манипуляцияларға көмектесу үшін сызба аймағының үстіндегі тірек торын көрсетеді Тор түсі Ұяшық ені Ұяшықтың биіктігі Шағын селекторлар Кейбір таңдауды басқару элементтері аз орын алу үшін ықшам орналасуды пайдаланады Суретке түсіру үшін параметрлерде камераға рұқсат беріңіз Орналасу Негізгі экран тақырыбы Тұрақты мөлшерлеме коэффициенті (CRF) %1$s мәні салыстырмалы түрде шағын файл өлшеміне әкелетін баяу қысуды білдіреді. %2$s жылдамырақ қысуды білдіреді, нәтижесінде үлкен файл пайда болады. Лут кітапханасы Жүктеп алғаннан кейін қолдануға болатын LUT жинағын жүктеп алыңыз Жүктеп алғаннан кейін қолдануға болатын LUT жиынтығын жаңарту (тек жаңалары кезекке қойылады). Сүзгілер үшін әдепкі кескінді алдын ала қарауды өзгертіңіз Кескінді алдын ала қарау Жасыру Көрсету Слайдер түрі Керемет Материал 2 Сәнді көрінетін сырғытпа. Бұл әдепкі опция Материал 2 сырғытпасы Сіз сырғытатын материал Қолдану Орталық диалогтық түймелер Диалогтардың түймелері мүмкіндігінше сол жақтың орнына орталықта орналасады Ашық бастапқы лицензиялар Осы қолданбада пайдаланылатын ашық бастапқы кітапханалардың лицензияларын қараңыз Аудан Пиксель аймағы қатынасын пайдаланып қайта үлгілеу. Ол муарсыз нәтижелер беретіндіктен кескінді жоюдың таңдаулы әдісі болуы мүмкін. Бірақ кескінді үлкейткенде, ол \"Ең жақын\" әдісіне ұқсайды. Tonemapping қосу % енгізіңіз Сайтқа кіру мүмкін емес, VPN пайдаланып көріңіз немесе URL мекенжайының дұрыстығын тексеріңіз Белгілеу қабаттары Суреттерді, мәтінді және т.б. еркін орналастыру мүмкіндігі бар қабаттар режимі Қабатты өңдеу Суреттегі қабаттар Кескінді фон ретінде пайдаланыңыз және оның үстіне әртүрлі қабаттарды қосыңыз Фондағы қабаттар Бірінші нұсқа сияқты, бірақ суреттің орнына түсті Бета Жылдам параметрлер жағы Суреттерді өңдеу кезінде таңдалған жағына қалқымалы жолақты қосыңыз, ол басқан кезде жылдам параметрлерді ашады Таңдауды тазалау \"%1$s\" параметр тобы әдепкі бойынша жиырылады \"%1$s\" параметр тобы әдепкі бойынша кеңейтіледі Base64 құралдары Base64 жолын кескінге декодтаңыз немесе кескінді Base64 пішіміне кодтаңыз База 64 Берілген мән жарамды Base64 жолы емес Бос немесе жарамсыз Base64 жолын көшіру мүмкін емес 64 негізін қойыңыз Base64 көшіру Base64 жолын көшіру немесе сақтау үшін суретті жүктеңіз. Егер сізде жолдың өзі болса, суретті алу үшін оны жоғарыға қоюға болады Base64 сақтау 64 базасын бөлісу Параметрлер Әрекеттер Импорттық база 64 Base64 әрекеттері Құрылымды қосу Белгіленген түсі мен ені бар мәтіннің айналасына контур қосыңыз Контур түсі Контур өлшемі Айналу Файл аты ретінде бақылау сомасы Шығарылатын кескіндердің деректердің бақылау сомасына сәйкес атауы болады Тегін бағдарламалық қамтамасыз ету (серіктес) Android қолданбаларының серіктес арнасындағы пайдалырақ бағдарламалық құрал Алгоритм Бақылау сомасы құралдары Бақылау сомасын салыстырыңыз, хэштерді есептеңіз немесе әртүрлі хэштеу алгоритмдерін пайдаланып файлдардан он алтылық жолдарды жасаңыз Есептеу Мәтін хэші Бақылау сомасы Таңдалған алгоритм негізінде бақылау сомасын есептеу үшін файлды таңдаңыз Таңдалған алгоритм негізінде бақылау сомасын есептеу үшін мәтінді енгізіңіз Бастапқы бақылау сомасы Салыстыру үшін бақылау сомасы Сәйкес! Айырмашылық Бақылау сомасы тең, ол қауіпсіз болуы мүмкін Бақылау сомасы бірдей емес, файл қауіпті болуы мүмкін! Тор градиенттері Mesh Gradients онлайн жинағын қараңыз Тек TTF және OTF қаріптерін импорттауға болады Импорттау қаріпі (TTF/OTF) Қаріптерді экспорттау Импортталған қаріптер Әрекетті сақтау кезінде қате орын алды, шығыс қалтасын өзгертіп көріңіз Файл атауы орнатылмаған Жоқ Теңшелетін беттер Беттерді таңдау Құралдан шығуды растау Белгілі бір құралдарды пайдалану кезінде сақталмаған өзгерістер болса және оны жабуға әрекеттенсеңіз, растау диалогы көрсетіледі EXIF файлын өңдеу Бір кескіннің метадеректерін қайта қыспай өзгертіңіз Қол жетімді тегтерді өңдеу үшін түртіңіз Стикерді өзгерту Сәйкес ені Сәйкес биіктік Пакеттік салыстыру Таңдалған алгоритм негізінде бақылау сомасын есептеу үшін файлды/файлдарды таңдаңыз Файлдарды таңдаңыз Каталогты таңдаңыз Бас ұзындығы шкаласы Мөр Уақыт белгісі Формат үлгісі Толтырғыш Кескінді кесу Кескін бөлігін кесіп, сол жақтарын (кері болуы мүмкін) тік немесе көлденең сызықтармен біріктіріңіз Тік айналмалы сызық Көлденең айналмалы сызық Кері таңдау Кесілген аумақтың айналасындағы бөліктерді біріктірудің орнына тік кесілген бөлік қалдырылады Кесілген аумақтың айналасындағы бөліктерді біріктірудің орнына көлденең кесілген бөлік қалдырылады Тор градиенттерінің жинағы Түйіндердің реттелетін саны мен ажыратымдылығы бар торлы градиент жасаңыз Торлы градиент қабаттасуы Берілген кескіндердің жоғарғы жағындағы тор градиентін құрастырыңыз Ұпайларды теңшеу Тор өлшемі X ажыратымдылығы Y шешімі Ажыратымдылық Pixel by Pixel Түсті бөлектеу Пиксельді салыстыру түрі Штрих-кодты сканерлеу Биіктік қатынасы Штрихкод түрі B/W режимін орындау Штрих-код кескіні толығымен ақ-қара болады және қолданбаның тақырыбы бойынша боялмайды Кез келген штрих-кодты сканерлеңіз (QR, EAN, AZTEC, …) және оның мазмұнын алыңыз немесе жаңасын жасау үшін мәтініңізді қойыңыз. Штрихкод табылмады Жасалған штрих-код осында болады Аудио мұқабалар Аудио файлдардан альбом мұқабасының кескіндерін шығарып алыңыз, ең көп таралған пішімдерге қолдау көрсетіледі Бастау үшін дыбысты таңдаңыз Аудио таңдаңыз Ешқандай мұқаба табылмады Журналдарды жіберу Қолданба журналдары файлын ортақ пайдалану үшін басыңыз, бұл мәселені анықтауға және мәселелерді шешуге көмектеседі Ой… Бірдеңе дұрыс болмады Төмендегі опцияларды пайдаланып маған хабарласуыңызға болады, мен шешімді табуға тырысамын.\n(Журналдарды тіркеуді ұмытпаңыз) Файлға жазу Суреттер топтамасынан мәтінді шығарып, оны бір мәтіндік файлда сақтаңыз Метадеректерге жазу Әр суреттен мәтінді шығарып, оны салыстырмалы фотосуреттердің EXIF ​​ақпаратына орналастырыңыз Көрінбейтін режим Суреттеріңіздің байттары ішінде көзге көрінбейтін су белгілерін жасау үшін стеганографияны пайдаланыңыз LSB пайдаланыңыз LSB (аз маңызды бит) стеганография әдісі пайдаланылады, әйтпесе FD (жиілік домені) Қызыл көзді автоматты түрде жою Құпия сөз Құлыпты ашу PDF қорғалған Операция аяқталуға жақын. Қазір бас тарту үшін оны қайта іске қосу қажет болады Өзгертілген күні Өзгертілген күні (керісінше) Өлшем Өлшем (кері) MIME түрі MIME түрі (кері) Кеңейтім Кеңейтім (кері) Қосылған күні Қосылған күні (кері) Солдан оңға Оңнан солға Жоғарыдан төменге Төменнен жоғарыға Сұйық шыны Жақында жарияланған IOS 26 және оның сұйық шыны дизайн жүйесіне негізделген қосқыш Төмендегі суретті таңдаңыз немесе Base64 деректерін қойыңыз/импорттаңыз Бастау үшін сурет сілтемесін теріңіз Сілтемені қою Калейдоскоп Қосымша бұрыш Тараптар Арна қоспасы Көк жасыл Қызыл көк Жасыл қызыл Қызылға Жасыл түске Көк түске Көгілдір Қызыл қызыл Сары Түс жарты реңк Контур Деңгейлер Офсет Воронойдың кристалдануы Пішін Созылу Кездейсоқтық Деспекл Диффузиялық DoG Екінші радиус Теңестіру Жарқырау Айналаңыз және қысыңыз Пунтилизация Жиек түсі Полярлық координаттар Полярға тік Полярдан тікке дейін Шеңберге айналдыру Шуды азайту Қарапайым Solarize Тоқу X аралығы Y Gap X Ені Y Ені Бұралу Резеңке мөртабан Жағынды Тығыздығы Араластырыңыз Сфералық линзаның бұрмалануы Сыну көрсеткіші Арк Таралу бұрышы Жарқырау Сәулелер ASCII Градиент Мэри Күз Сүйек Реактивті Қыс Мұхит Жаз Көктем Салқын нұсқа HSV Қызғылт Ыстық Сөз Магма Тозақ Плазма Виридис Азаматтар Ымырт Ымырт ауысты Перспективалық авто Дескрипт Кесуге рұқсат етіңіз Кесу немесе перспектива Абсолютті Турбо Қою жасыл Линзаны түзету JSON пішіміндегі мақсатты линза профилі файлы Дайын объектив профильдерін жүктеп алыңыз Бөлшек пайыздар JSON ретінде экспорттау Json көрінісі ретінде палитра деректерімен жолды көшіріңіз Тігіс оюы Негізгі экран Экранды құлыптау Кірістірілген Түсқағаздар экспорты Жаңарту Ағымдағы үй, құлып және кірістірілген тұсқағаздарды алыңыз Барлық файлдарға кіруге рұқсат беріңіз, бұл тұсқағаздарды алу үшін қажет Сыртқы жадты басқару рұқсаты жеткіліксіз, суреттерге кіруге рұқсат беру керек, \"Барлығына рұқсат беру\" опциясын таңдауды ұмытпаңыз. Файл атауына алдын ала орнатуды қосыңыз Кескін файлының атына таңдалған алдын ала орнатылған жұрнақ қосады Файл атына кескін масштабы режимін қосыңыз Кескін файлының атына таңдалған кескін масштабы режимі бар жұрнақ қосады Ascii Art Суретті кескінге ұқсайтын ascii мәтініне түрлендіру Парам Кейбір жағдайларда жақсы нәтиже алу үшін кескінге теріс сүзгіні қолданады Скриншот өңделуде Скриншот түсірілмеді, әрекетті қайталаңыз Сақтау өткізіп жіберілді %1$s файлдар өткізіп жіберілді Үлкенірек болса, өткізіп жіберуге рұқсат етіңіз Нәтижедегі файл өлшемі түпнұсқадан үлкенірек болса, кейбір құралдарға кескіндерді сақтауды өткізіп жіберуге рұқсат етіледі Күнтізбе оқиғасы Байланыс Электрондық пошта Орналасқан жері Телефон Мәтін қысқаша хабар қызметі URL Сымсыз дәлдiк Ашық желі Жоқ SSID Телефон Хабарлама Мекенжай Тақырып Дене Аты Ұйымдастыру Тақырып Телефондар Электрондық пошталар URL мекенжайлары Мекенжайлар Түйіндеме Сипаттама Орналасқан жері Ұйымдастырушы Басталу күні Аяқталу күні Күй Ендік Бойлық Штрих-код жасау Штрихкодты өңдеу Wi-Fi конфигурациясы Қауіпсіздік Контакт таңдау Параметрлерде контактілерге таңдалған контактіні пайдаланып автотолтыруға рұқсат беріңіз Байланыс ақпараты Аты Екінші аты Фамилия Айтылуы Телефон қосыңыз Электрондық поштаны қосыңыз Мекенжай қосыңыз Веб-сайт Веб-сайтты қосыңыз Пішімделген атау Бұл сурет штрих-кодтың үстіне қою үшін пайдаланылады Кодты теңшеу Бұл сурет QR кодының ортасында логотип ретінде пайдаланылады Логотип Логотипті толтыру Логотип өлшемі Логотип бұрыштары Төртінші көз Төменгі шеткі бұрышқа төртінші көзді қосу арқылы qr кодына көз симметриясын қосады Пиксель пішіні Жақтау пішіні Шар пішіні Қатені түзету деңгейі Қою түсті Ашық түс Гипер ОЖ Xiaomi HyperOS стилі ұнайды Маска үлгісі Бұл код сканерленбеуі мүмкін, оны барлық құрылғылармен оқуға болатындай ету үшін сыртқы көрініс параметрлерін өзгертіңіз Сканерлеу мүмкін емес Құралдар ықшам болу үшін негізгі экран қолданбасын іске қосу құралы сияқты болады Іске қосу режимі Таңдалған щеткамен және стильмен аумақты толтырады Су тасқыны Бүріккіш Граффити стиліндегі жолды салады Шаршы бөлшектер Бүріккіш бөлшектер шеңбердің орнына шаршы пішінді болады Палитра құралдары Кескіннен негізгі/материалды бояғышты жасаңыз немесе әртүрлі палитра пішімдері бойынша импорттау/экспорттау Палитраны өңдеу Түрлі пішімдер бойынша палитраны экспорттау/импорттау Түс атауы Палитра атауы Палитра пішімі Жасалған палитраны әртүрлі пішімдерге экспорттау Ағымдағы палитраға жаңа түс қосады %1$s пішімі палитра атауын беруді қолдамайды Play Store саясаттарына байланысты бұл мүмкіндікті ағымдағы құрастыруға қосу мүмкін емес. Бұл функцияға қол жеткізу үшін ImageToolbox қолданбасын балама көзден жүктеп алыңыз. Төменде GitHub сайтында қолжетімді құрылымдарды таба аласыз. Github бетін ашыңыз Түпнұсқа файл таңдалған қалтада сақтаудың орнына жаңасымен ауыстырылады Жасырын су таңбасының мәтіні анықталды Жасырын су таңбасының кескіні анықталды Бұл сурет жасырылды Генеративті кескіндеме OpenCV-ге сенбей, AI үлгісін пайдаланып кескіндегі нысандарды жоюға мүмкіндік береді. Бұл мүмкіндікті пайдалану үшін қолданба GitHub сайтынан қажетті үлгіні (~200 МБ) жүктеп алады OpenCV-ге сенбей, AI үлгісін пайдаланып кескіндегі нысандарды жоюға мүмкіндік береді. Бұл ұзаққа созылатын операция болуы мүмкін Қате деңгейін талдау Жарықтық градиенті Орташа қашықтық Көшіру жылжытуды анықтау Сақтау Коэффицент Алмасу буферінің деректері тым үлкен Деректер көшіру үшін тым үлкен Қарапайым тоқыма пикселизациясы Кезеңдік пикселдеу Айқас пикселизация Микро макропикселизация Орбиталық пикселизация Құйынның пикселизациясы Импульстік тордың пикселизациясы Ядроның пикселизациясы Радиалды тоқыма пикселизациясы \"%1$s\" uri ашылмады Қар жауу режимі Қосылған Жиек жақтауы Ақаулық нұсқасы Арнаны ауыстыру Максималды ығысу VHS Glitch блоктау Блок өлшемі CRT қисықтығы Қисықтық Chroma Pixel Melt Максималды құлдырау AI құралдары Артефакттарды жою немесе жою сияқты AI үлгілері арқылы кескіндерді өңдеуге арналған әртүрлі құралдар Сығымдау, кесілген сызықтар Мультфильмдер, хабар тарату Жалпы қысу, жалпы шу Түссіз мультфильм шуы Жылдам, жалпы қысу, жалпы шу, анимация/комикс/аниме Кітапты сканерлеу Экспозицияны түзету Жалпы қысу, түрлі-түсті кескіндер бойынша ең жақсы Ең жақсы жалпы қысу, сұр түсті кескіндер Жалпы қысу, сұр түсті кескіндер, күштірек Жалпы шу, түрлі-түсті бейнелер Жалпы шу, түрлі-түсті кескіндер, жақсырақ мәліметтер Жалпы шу, сұр түсті кескіндер Жалпы шу, сұр түсті кескіндер, күштірек Жалпы шу, сұр түсті кескіндер, ең күшті Жалпы қысу Жалпы қысу Текстуризация, h264 қысу VHS қысу Стандартты емес қысу (cinepak, msvideo1, roq) Бинк қысу, геометрияда жақсырақ Бинк қысу, күштірек Бинк қысу, жұмсақ, бөлшектерді сақтайды Баспалдақ әсерін жою, тегістеу Сканерленген өнер/сызбалар, жұмсақ сығымдау, муар Түсті ленталау Баяу, жартылай реңктерді жою Сұр реңкті/bw кескіндерге арналған жалпы бояғыш, жақсы нәтижелер алу үшін DDColor пайдаланыңыз Жиекті жою Артық қайрауды жояды Баяу, дірілдеу Антиалиазинг, жалпы артефактілер, CGI KDM003 сканерлеуді өңдеу Жеңіл кескінді жақсарту үлгісі Компрессиялық артефактты жою Компрессиялық артефактты жою Тегіс нәтиже беретін таңғышты алып тастау Жартылай тон үлгісін өңдеу Дитер үлгісін жою V3 JPEG артефактілерін жою V2 H.264 текстурасын жақсарту VHS нақтылау және жақсарту Біріктіру Бөлшек өлшемі Қабаттасу өлшемі %1$s пиксельден асатын кескіндер кесектерге кесіледі және өңделеді, көрінетін тігістердің алдын алу үшін қабаттасу араласады. Үлкен өлшемдер төмен деңгейлі құрылғылармен тұрақсыздықты тудыруы мүмкін Бастау үшін біреуін таңдаңыз %1$s үлгісін жойғыңыз келе ме? Сіз оны қайтадан жүктеп алуыңыз керек Растау Модельдер Жүктелген үлгілер Қолжетімді үлгілер Дайындалуда Белсенді модель Сеанс ашылмады Тек .onnx/.ort үлгілерін импорттауға болады Импорт үлгісі Әрі қарай пайдалану үшін пайдаланушы onnx моделін импорттаңыз, тек onnx/ort үлгілері қабылданады, барлық дерлік esrgan сияқты нұсқаларды қолдайды Импортталған үлгілер Жалпы шу, түрлі-түсті суреттер Жалпы шу, түрлі-түсті кескіндер, күштірек Жалпы шу, түрлі-түсті суреттер, ең күшті Тегіс градиенттер мен тегіс түс аумақтарын жақсартып, артефактілер мен түс жолағын азайтады. Табиғи түстерді сақтай отырып, теңдестірілген жарықтандыру арқылы кескіннің жарықтығы мен контрастын жақсартады. Мәліметтерді сақтай отырып және шамадан тыс экспозицияны болдырмай, күңгірт кескіндерді ағартады. Түстердің шамадан тыс тонусын жояды және бейтарап және табиғи түс балансын қалпына келтіреді. Нәзік бөлшектер мен текстураларды сақтауға баса назар аудара отырып, Пуассон негізіндегі шуды сергітеді. Тегіс және аз агрессивті визуалды нәтижелер үшін жұмсақ Пуассон шуыл тонусын қолданады. Бөлшектерді сақтауға және кескіннің анықтығына бағытталған біркелкі шуды тондау. Нәзік текстура мен тегіс көрініс үшін жұмсақ біркелкі шуды сергітеді. Артефактілерді қайта бояу және кескіннің үйлесімділігін жақсарту арқылы зақымдалған немесе тегіс емес жерлерді жөндейді. Ең аз өнімділік құнымен түс жолағын жоятын жеңіл жолақты жою үлгісі. Жақсартылған айқындық үшін өте жоғары қысу артефактілері (0-20% сапа) бар кескіндерді оңтайландырады. Суреттерді жоғары қысу артефактілерімен жақсартады (20-40% сапа), мәліметтерді қалпына келтіреді және шуды азайтады. Орташа қысумен (40-60% сапа) кескіндерді жақсартады, айқындық пен тегістікті теңестіреді. Нәзік бөлшектер мен текстураларды жақсарту үшін жеңіл қысумен (60-80% сапа) кескіндерді нақтылайды. Табиғи көрініс пен бөлшектерді сақтай отырып, жоғалмайтын кескіндерді (80-100% сапа) аздап жақсартады. Қарапайым және жылдам бояу, мультфильмдер, идеалды емес Кескіннің бұлыңғырлығын аздап азайтады, артефактілерді енгізбестен айқындықты жақсартады. Ұзақ мерзімді операциялар Кескінді өңдеу Өңдеу Өте төмен сапалы кескіндердегі ауыр JPEG қысу артефактілерін жояды (0-20%). Жоғары қысылған кескіндердегі күшті JPEG артефактілерін азайтады (20-40%). Кескін мәліметтерін сақтай отырып, қалыпты JPEG артефактілерін тазартады (40-60%). Жеткілікті жоғары сапалы кескіндердегі жеңіл JPEG артефактілерін нақтылайды (60-80%). Жоғалмайтын кескіндердегі кішігірім JPEG артефактілерін (80-100%) азайтады. Ауыр артефактілерсіз қабылданатын айқындықты жақсарта отырып, ұсақ бөлшектер мен текстураларды жақсартады. Өңдеу аяқталды Өңдеу сәтсіз аяқталды Жылдамдық үшін оңтайландырылған табиғи көріністі сақтай отырып, тері құрылымы мен бөлшектерін жақсартады. JPEG қысу артефактілерін жояды және қысылған фотосуреттер үшін кескін сапасын қалпына келтіреді. Жарық аз жағдайда түсірілген фотосуреттердегі ISO шуылын азайтып, бөлшектерді сақтайды. Шамадан тыс экспозиция немесе «джумбо» бөлектеулерді түзетеді және жақсырақ тоналды тепе-теңдікті қалпына келтіреді. Сұр реңктегі кескіндерге табиғи түстер қосатын жеңіл және жылдам түс беру үлгісі. DEJPEG Denoise Түстендіріңіз Артефактілер Жақсарту Аниме Сканерлеу Жоғары деңгейлі Жалпы кескіндер үшін X4 кеңейткіші; графикалық процессорды және уақытты аз пайдаланатын, орташа ақауы бар және денозы бар шағын модель. Текстуралар мен табиғи бөлшектерді сақтай отырып, жалпы кескіндерге арналған X2 кеңейткіш. Жетілдірілген текстурасы мен шынайы нәтижелері бар жалпы кескіндерге арналған X4 кеңейткіш. Аниме кескіндері үшін оңтайландырылған X4 кеңейткіші; Өткір сызықтар мен бөлшектер үшін 6 RRDB блоктары. MSE жоғалтуы бар X4 кеңейткіші тегіс нәтижелер береді және жалпы кескіндер үшін артефактілерді азайтады. X4 Upscaler аниме кескіндері үшін оңтайландырылған; Өткір бөлшектері мен тегіс сызықтары бар 4B32F нұсқасы. Жалпы кескіндерге арналған X4 UltraSharp V2 үлгісі; айқындық пен айқындылыққа баса назар аударады. X4 UltraSharp V2 Lite; жылдамырақ және кішірек, GPU жадын аз пайдалану кезінде мәліметтерді сақтайды. Фонды жылдам жоюға арналған жеңіл модель. Теңдестірілген өнімділік пен дәлдік. Портреттермен, заттармен және көріністермен жұмыс істейді. Көптеген пайдалану жағдайлары үшін ұсынылады. BG алып тастаңыз Көлденең шекараның қалыңдығы Тік жиек қалыңдығы %1$s түстер %1$s түстер Ағымдағы үлгі бөлшектеуге қолдау көрсетпейді, кескін бастапқы өлшемдерде өңделеді, бұл жадты көп тұтынуды және төмен деңгейлі құрылғылармен ақауларды тудыруы мүмкін. Бөлшектеу өшірілген, кескін бастапқы өлшемдерде өңделеді, бұл жадты жоғары тұтынуды және төмен деңгейлі құрылғылармен ақауларды тудыруы мүмкін, бірақ қорытынды жасауда жақсы нәтижелер беруі мүмкін. Бөлшектеу Фонды жоюға арналған жоғары дәлдіктегі кескінді сегменттеу үлгісі Жадты азырақ пайдалану арқылы фонды жылдам жоюға арналған U2Net жеңіл нұсқасы. Толық DDColor үлгісі ең аз артефактілермен жалпы кескіндер үшін жоғары сапалы түс беруді қамтамасыз етеді. Барлық бояу үлгілерінің ең жақсы таңдауы. DDColor Оқытылған және жеке көркем деректер жиыны; азырақ шынайы емес түсті артефактілермен әртүрлі және көркем бояу нәтижелерін береді. Фонды дәл жою үшін Swin Transformer негізіндегі жеңіл BiRefNet моделі. Өткір жиектері бар жоғары сапалы фонды жою және егжей-тегжейлерді тамаша сақтау, әсіресе күрделі нысандар мен күрделі фондар. Жалпы нысандарға және орташа бөлшектерді сақтауға жарамды, тегіс жиектері бар дәл маскаларды шығаратын фонды жою үлгісі. Үлгі жүктеп алынған Модель сәтті импортталды Түр Негізгі сөз Өте жылдам Қалыпты Баяу Өте баяу Проценттерді есептеу Минималды мән – %1$s Саусақтармен сурет салу арқылы кескінді бұрмалау Соғыс Қаттылық Бұрмалау режимі Жылжыту Өсу Кішірейту Swirl CW Айналмалы CCW Өшіру күші Жоғарғы тамшы Төменгі тамшы Drop бастау Аяқтау Жүктеп алынуда Тегіс пішіндер Тегіс, табиғи пішіндер үшін стандартты дөңгелектелген төртбұрыштардың орнына суперэллипстерді пайдаланыңыз Пішін түрі Кесу Дөңгеленген Тегіс Дөңгелектеусіз өткір жиектер Классикалық дөңгелек бұрыштар Фигуралар түрі Бұрыштардың өлшемі Айналма Керемет дөңгелектенген UI элементтері Файл атауы пішімі Жоба атаулары, брендтер немесе жеке тегтер үшін тамаша файл атауының ең басында орналастырылған теңшелетін мәтін. Бастапқы файл атауын кеңейтусіз пайдаланады, бұл бастапқы идентификацияны сақтауыңызға көмектеседі. Ажыратымдылық өзгерістерін немесе масштабтау нәтижелерін бақылау үшін пайдалы пиксельдегі кескін ені. Пиксельдегі кескін биіктігі, пропорциялармен немесе экспорттармен жұмыс істегенде пайдалы. Бірегей файл атауларына кепілдік беру үшін кездейсоқ сандарды жасайды; көшірмелерден қосымша қауіпсіздік үшін қосымша сандарды қосыңыз. Пакеттік экспортқа арналған автоматты ұлғайту есептегіші, бір сеанста бірнеше кескінді сақтау үшін өте қолайлы. Кескіннің қалай өңделгенін оңай есте сақтау үшін қолданылатын алдын ала орнатылған атауды файл атауына енгізеді. Өлшемі өзгертілген, қиылған немесе орнатылған кескіндерді ажыратуға көмектесетін өңдеу кезінде пайдаланылатын кескін масштабтау режимін көрсетеді. Файл атауының соңында орналастырылған теңшелетін мәтін, _v2, _edited немесе _final сияқты нұсқалар үшін пайдалы. Файл кеңейтімі (png, jpg, webp, т.б.), нақты сақталған пішімге автоматты түрде сәйкес келеді. Керемет сұрыптау үшін java спецификациясы бойынша өз пішіміңізді анықтауға мүмкіндік беретін теңшелетін уақыт белгісі. Ұшу түрі Android Native iOS стилі Тегіс қисық Жылдам тоқтату Боунси Қалқымалы Жылдам Ультра тегіс Бейімделу Қол жетімділікті біледі Қысқартылған қозғалыс Жергілікті Android айналдыру физикасы Жалпы пайдалану үшін теңдестірілген, тегіс айналдыру Жоғары үйкеліс iOS тәрізді айналдыру әрекеті Айналдыру сезімі үшін ерекше сплайн қисығы Жылдам тоқтату арқылы дәл айналдыру Ойын, жауап беретін серпінді шиыршық Мазмұнды шолу үшін ұзын, жылжымалы айналдырулар Интерактивті UI үшін жылдам, жауапты айналдыру Ұзартылған серпінмен премиум тегіс айналдыру Ұшу жылдамдығына негізделген физиканы реттейді Жүйенің қол жетімділік параметрлерін құрметтейді Қол жетімділік қажеттіліктері үшін минималды қозғалыс Бастапқы сызықтар Әрбір бесінші жолға қалыңырақ жолды қосады Түсті толтыру Жасырын құралдар Бөлісу үшін жасырылған құралдар Түстер кітапханасы Түстердің кең жиынтығын шолыңыз Табиғи мәліметтерді сақтай отырып, кескіндерді айқындайды және бұлыңғырлықты жояды, бұл фокустан тыс фотосуреттерді бекіту үшін өте қолайлы. Жоғалған мәліметтер мен текстураларды қалпына келтіре отырып, бұрын өлшемі өзгертілген кескіндерді ақылды түрде қалпына келтіреді. Тікелей эфир мазмұны үшін оңтайландырылған, қысу артефактілерін азайтады және фильмдер/теледидар шоу кадрларындағы ұсақ бөлшектерді жақсартады. VHS-сапалы бейнелерді HD форматына түрлендіреді, таспа шуын жояды және винтаждық сезімді сақтай отырып, ажыратымдылықты арттырады. Мәтінді көп қажет ететін кескіндер мен скриншоттар үшін мамандандырылған, кейіпкерлерді айқындайды және оқылуды жақсартады. Жетілдірілген масштабтау әртүрлі деректер жиынында оқытылады, жалпы мақсаттағы фотосуреттерді жақсарту үшін тамаша. Веб-қысылған фотосуреттер үшін оңтайландырылған, JPEG артефактілерін жояды және табиғи көріністі қалпына келтіреді. Текстураны жақсырақ сақтау және артефакті азайту арқылы веб-фотосуреттерге арналған жетілдірілген нұсқасы. Қос біріктіру трансформаторы технологиясымен 2 есе үлкейту, анықтық пен табиғи бөлшектерді сақтайды. Трансформатордың жетілдірілген архитектурасын пайдаланып масштабты 3 есе арттыру, қалыпты үлкейту қажеттіліктері үшін өте қолайлы. Соңғы үлгідегі трансформатор желісімен 4 есе жоғары сапалы масштабтау, үлкенірек масштабта ұсақ бөлшектерді сақтайды. Фотосуреттердегі бұлыңғырлықты/ шуды және дірілдерді жояды. Жалпы мақсат, бірақ фотосуреттерде жақсы. BSRGAN деградациясы үшін оңтайландырылған Swin2SR трансформаторын пайдаланып сапасыз кескіндерді қалпына келтіреді. Ауыр қысу артефактілерін бекіту және 4x масштабындағы мәліметтерді жақсарту үшін тамаша. BSRGAN деградациясына үйретілген SwinIR трансформаторымен 4 есе кеңейту. Фотосуреттер мен күрделі көріністердегі анық текстуралар мен табиғи бөлшектер үшін GAN пайдаланады. Жол PDF біріктіру Бірнеше PDF файлдарын бір құжатқа біріктіріңіз Файлдар тәртібі бет. PDF бөлу PDF құжатынан арнайы беттерді шығарып алыңыз PDF файлын бұру Бет бағытын біржола түзетіңіз Беттер PDF файлын қайта реттеңіз Беттерді ретін өзгерту үшін сүйреп апарыңыз Беттерді ұстап тұрыңыз және сүйреңіз Бет нөмірлері Құжаттарыңызға нөмірлеуді автоматты түрде қосыңыз Белгі пішімі PDF-мәтінге (OCR) PDF құжаттарынан кәдімгі мәтінді шығарып алыңыз Брендинг немесе қауіпсіздік үшін реттелетін мәтінді қабаттастырыңыз Қол қою Кез келген құжатқа электрондық қолтаңбаңызды қосыңыз Бұл қолтаңба ретінде пайдаланылады PDF құлпын ашу Құпия сөздерді қорғалған файлдардан жойыңыз PDF файлын қорғаңыз Құжаттарды күшті шифрлау арқылы қорғаңыз Сәттілік PDF құлпы ашылды, оны сақтауға немесе бөлісуге болады PDF жөндеу Бүлінген немесе оқылмайтын құжаттарды түзету әрекеті Сұр реңк Барлық ендірілген құжат кескіндерін сұр реңкке түрлендіріңіз PDF файлын қысыңыз Бөлісуді жеңілдету үшін құжат файлының өлшемін оңтайландырыңыз ImageToolbox ішкі айқас сілтеме кестесін қайта құрады және файл құрылымын нөлден бастап қайта жасайды. Бұл \\"ашу мүмкін емес\\" көптеген файлдарға кіру рұқсатын қалпына келтіре алады. Бұл құрал барлық құжат кескіндерін сұр реңкке түрлендіреді. Басып шығару және файл өлшемін азайту үшін ең жақсы Метадеректер Құпиялықты жақсарту үшін құжат сипаттарын өңдеңіз Тегтер Продюсер Автор Негізгі сөздер Жаратушы Құпиялылық Deep Clean Осы құжат үшін барлық қолжетімді метадеректерді өшіріңіз Бет Терең OCR Құжаттан мәтінді шығарып, оны Tesseract қозғалтқышының көмегімен бір мәтіндік файлға сақтаңыз Барлық беттерді жою мүмкін емес PDF беттерін жою PDF құжатынан арнайы беттерді алып тастаңыз Жою үшін түртіңіз Қолмен PDF қию Құжат беттерін кез келген шекке қию PDF файлын тегістеңіз Құжат беттерін растирлеу арқылы PDF файлын өзгертілмейтін етіңіз Камераны іске қосу мүмкін болмады. Рұқсаттарды тексеріп, оны басқа қолданба пайдаланбайтынына көз жеткізіңіз. Суреттерді шығару PDF файлдарына ендірілген кескіндерді бастапқы ажыратымдылығымен шығарып алыңыз Бұл PDF файлында ендірілген кескіндер жоқ Бұл құрал әрбір бетті сканерлейді және толық сапалы бастапқы кескіндерді қалпына келтіреді — құжаттардан түпнұсқаларды сақтауға өте ыңғайлы Қолтаңбаны салу Қалам параметрлері Құжаттарға орналастыру үшін сурет ретінде өз қолтаңбасын пайдаланыңыз ZIP PDF Берілген аралықпен құжатты бөліңіз және жаңа құжаттарды zip мұрағатына салыңыз Аралық PDF басып шығару Құжатты бет өлшемімен басып шығаруға дайындаңыз Парақтағы беттер Бағдарлау Бет өлшемі Маржа Блум Жұмсақ тізе Аниме және мультфильмдер үшін оңтайландырылған. Жақсартылған табиғи түстермен және аз артефактілермен жылдам үлкейту Samsung One UI 7 стиліне ұқсас Қажетті мәнді есептеу үшін осы жерге негізгі математикалық белгілерді енгізіңіз (мысалы, (5+5)*10) Математикалық өрнек %1$s суретке дейін таңдаңыз Күн уақытын сақтаңыз Әрқашан күн мен уақытқа қатысты exif тегтерін сақтаңыз, exif сақтау опциясынан тәуелсіз жұмыс істейді Альфа пішімдері үшін фон түсі Альфа қолдауы бар әрбір кескін пішімі үшін фон түсін орнату мүмкіндігін қосады, өшірілген кезде бұл тек альфа еместер үшін қолжетімді. Ашық жоба Бұрын сақталған Image Toolbox жобасын өңдеуді жалғастырыңыз Image Toolbox жобасын ашу мүмкін емес Image Toolbox жобасында жоба деректері жоқ Image Toolbox жобасы бүлінген Қолдау көрсетілмейтін Image Toolbox жобасының нұсқасы: %1$d Жобаны сақтау Қабаттарды, фондық және өңдеу журналын өңделетін жоба файлында сақтаңыз Ашылмады Іздеуге болатын PDF файлына жазыңыз Кескін топтамасынан мәтінді танып, сурет пен таңдалатын мәтін қабатымен ізделетін PDF файлын сақтаңыз Альфа қабаты Көлденең айналдыру Тік бұру Құлыптау Көлеңке қосу Көлеңке түсі Мәтін геометриясы Анық стильдеу үшін мәтінді созыңыз немесе қисайтыңыз X шкаласы Скью X Аннотацияларды жою PDF беттерінен сілтемелер, түсініктемелер, ерекшеліктер, пішіндер немесе пішін өрістері сияқты таңдалған аннотация түрлерін алып тастаңыз Гиперсілтемелер Файл қосымшалары Сызықтар Қалқымалы терезелер Маркалар Пішіндер Мәтіндік жазбалар Мәтінді белгілеу Пішін өрістері Белгілеу Белгісіз Аннотациялар Топтан шығару Конфигурацияланатын түс пен ығысу арқылы қабаттың артына бұлыңғыр көлеңке қосыңыз ================================================ FILE: core/resources/src/main/res/values-ko/strings.xml ================================================ 이미지 선택 너비 %1$s 높이 %1$s 품질 크기 조정 유형 파일크기 %1$s 이미지 선택 앱 종료 머물기 이미지 변경 사항이 시작값으로 변경됩니다 이미지 초기화 초기화 닫기 설정값이 초기화 되었습니다 문제가 발생했습니다 앱 재시작 문제 발생: %1$s 명시적 유연성 예외 저장 지우기 OK 소스 코드 저장중 최신 업데이트 받기, 문제 논의 등 색상 이미지 색상이 복사되었습니다 단일 크기 조정 하나의 이미지 수정, 크기 조정 및 편집 삭제 지정된 이미지에서 색상 팔레트 견본 생성 팔레트 생성 팔레트 새로운 버전 %1$s 지원되지 않는 유형: %1$s 지정된 이미지에 대한 팔레트를 생성할 수 없습니다 원본 기본값 장치 저장 공간 용량으로 크기 조정 최대 크기 KB 미지정 비교 지정된 두 이미지를 비교 출력 폴더 사용자 지정 이미지 선택 설정 야간 모드 어둡게 밝게 시스템 동적 색상 사용자 지정 이미지 모네 허용 이 기능을 활성화하면 편집할 이미지를 선택하면 이 이미지에 앱 색상이 적용됩니다. 빨간색 초록색 아몰레드 모드 언어 색 구성표 aRGB 코드 붙여넣기 붙여넣을 항목 없음 앱 정보 앱 테마는 선택한 색상을 기반으로 합니다. 번역을 도와주세요 번역 실수를 수정하거나 프로젝트를 다른 언어로 현지화 검색어에서 아무것도 찾지 못했습니다. 여기에서 검색 %d 이미지 저장 실패 기본 제삼기 보조 테두리 두께 추가 권한 승인 앱이 작동하려면 이 권한이 필요하므로 수동으로 권한을 부여하세요. 외부 저장소 모네 색상 불러오는중… 확장자 앱을 정말 종료하시겠습니까? 클립보드에 복사되었습니다 EXIF 수정 EXIF 데이터를 찾을 수 없습니다 tag 추가 취소 이미지의 모든 EXIF 데이터가 지워집니다. 이 작업은 되돌릴 수 없습니다! EXIF 지우기 사전설정 버전 자르기 색상 선택 원하는 범위로 이미지 자르기 이미지에서 색상을 선택, 복사 또는 공유 EXIF 유지 지금 나가면 저장되지 않은 모든 변경사항이 손실됩니다 이미지: %d 변경 미리보기 주어진 크기 KB 에 따라 이미지 크기 조정 이미지 두 개를 선택하세요 파란색 여기에 버그 보고서 및 기능 요청을 보내주십시오. 업데이트 활성화된 경우 야간 모드에서 표면 색상이 완전히 어둡게 설정됩니다. 동적 색상이 켜져 있는 동안에는 앱 색 구성표를 변경할 수 없습니다. 업데이트가 없습니다. 이슈 트래커 활성화하면 앱 색상이 배경 화면 색상에 적용됩니다. 표면 앱이 이미지를 저장하려면 저장소에 액세스해야하며, 그렇지 않으면 작동 할 수 없으므로 다음 대화 상자에서 권한을 부여하세요. 이 애플리케이션은 완전 무료이지만, 프로젝트 개발을 지원하고 싶다면 여기를 클릭하세요. 이미지가 너무 커서 미리 볼 수 없지만, 일단 저장을 시도합니다 FAB 정렬 업데이트 확인 활성화하면 앱 시작 후 업데이트 대화 상자가 표시됩니다. 이미지 확대/축소 접두사 파일 이름 공유 이모티콘 활성화하면 저장된 이미지의 너비와 높이가 출력 파일의 이름에 추가됩니다. 파일 크기 추가 EXIF 삭제 이미지 출처 사진 선택기 화면 하단에 표시되는 Android 최신 사진 선택기는 Android 12 이상에서만 작동할 수 있으며 EXIF 메타데이터 수신에 문제가 있습니다. 갤러리 파일 탐색기 간단한 갤러리 이미지 선택기, 해당 앱이 있는 경우에만 작동합니다. 옵션 배열 편집하기 주문하다 이모티콘 수 시퀀스 번호 원본 파일 이름 원본 파일 이름 추가 활성화되면 출력 이미지의 이름에 원래 파일 이름을 추가합니다. GetContent 의도를 사용하여 이미지를 선택하고 모든 곳에서 작동하지만 일부 장치에서 선택한 이미지를 수신하는 데 문제가 있을 수 있습니다. 제 잘못이 아닙니다 시퀀스 번호 바꾸기 메인 화면에 표시할 이모티콘 선택 모든 이미지에서 EXIF 메타데이터 삭제 이미지 미리보기 모든 유형의 이미지 미리보기: GIF, SVG 등 메인 화면에서 도구의 순서를 결정합니다. 활성화된 경우 일괄 처리를 사용하는 경우 표준 타임스탬프를 이미지 시퀀스 번호로 바꿉니다. 사진 선택기 이미지 소스를 선택한 경우 원본 파일 이름 추가가 작동하지 않음 하이라이트 그림자 생동감 크로스해칭 간격 선의 폭 소벨 엣지 필터 노출 색조 굴절률 불투명 크기 조정 제한 가로 세로 비율을 유지하면서 지정된 높이와 너비에 맞게 이미지 크기 조정하기 최대가 아닌 억제 스택 블러 재 주문 웹에서 이미지 불러오기 인터넷에서 이미지 형식을 로드하고, 미리 보고, 확대하고, 원하는 경우 저장하거나 편집할 수도 있습니다. 이미지 없음 이미지 링크 채우다 맞다 지정된 높이와 너비에 맞게 이미지 크기를 조정합니다. - 이미지의 가로 세로 비율이 변경될 수 있습니다. 측면이 긴 이미지의 크기를 지정된 높이 또는 너비에 맞게 조정합니다. 모든 크기 계산은 저장 후 수행됩니다. 이미지의 가로 세로 비율은 그대로 유지됩니다. 명도 차이 색조 포화 필터 추가 필터 주어진 이미지에 필터 체인 적용 컬러 필터 알파 화이트 밸런스 온도 단색화 감마 하이라이트와 그림자 안개 효과 거리 경사 갈다 세피아 부정적인 솔라라이즈 검정색과 흰색 흐림 반음 GCA 색상 공간 가우스 흐림 상자 흐림 양자 흐림 양각 라플라시안 삽화 시작 쿠와하라 스무딩 반지름 규모 왜곡 각도 소용돌이 부풀다 팽창 구 굴절 유리 구 굴절 컬러 매트릭스 스케치 한계점 양자화 수준 부드러운 툰 인도 마호가니 포스터라이즈 약한 픽셀 포함 조회 컨볼루션 3x3 RGB 필터 거짓 색상 첫 번째 색상 두 번째 색상 빠른 흐림 블러 크기 블러 센터 x 블러 센터 y 줌 블러 색의 균형 휘도 임계값 콘텐츠 규모 파일 처리됨 AES 암호화 알고리즘을 기반으로 모든 파일 (이미지뿐만 아니라) 암호화 및 해독 호환성 이미지를 선택하고 그 위에 무언가를 그립니다. 배경에 그리기 배경색을 선택하고 그 위에 그림을 그립니다. 파일 앱을 비활성화했습니다. 이 기능을 사용하려면 활성화하세요. 배경색 암호 열쇠 이 파일을 기기에 저장하거나 공유 작업을 사용하여 원하는 위치에 배치하세요. 복호화 특징 구현 은닉처 캐시 크기 발견 %1$s 자동 캐시 지우기 사용자 지정 목록 정렬 대신 해당 유형의 기본 화면에 있는 그룹 옵션 활성화된 경우 앱 시작 시 앱 캐시가 지워집니다. 도구 보조 사용자화 비밀번호가 잘못되었거나 선택한 파일이 암호화되지 않았습니다. 옵션 그룹화가 활성화된 상태에서는 배열을 변경할 수 없습니다. 스크린샷 수정 폴백 옵션 건너뛰다 복사 %1$s 모드는 무손실 형식이므로 저장이 불안정할 수 있습니다. 그리다 파일 선택 암호화 최대 파일 크기는 Android OS 및 사용 가능한 메모리에 의해 제한되며 이는 분명히 장치에 따라 다릅니다. \n참고: 메모리는 스토리지가 아닙니다. 다른 파일 암호화 소프트웨어 또는 서비스와의 호환성은 보장되지 않습니다. 약간 다른 키 처리 또는 암호 구성이 비호환성의 원인이 될 수 있습니다. 지정된 너비와 높이로 이미지를 저장하려고 하면 메모리 부족 오류가 발생할 수 있습니다. 이 작업에서 발생한 문제는 사용자 책임입니다. 스크린샷 유형별로 그룹화 옵션 스케치북처럼 이미지 위에 그리거나, 배경 그 자체에 그립니다. 페인트 색상 페인트 알파 이미지에 그리기 암호화 복호화 시작할 파일 선택 암호 기반 파일 암호화. 진행된 파일은 선택한 디렉토리에 저장하거나 공유할 수 있습니다. 해독된 파일을 직접 열 수도 있습니다. AES-256, GCM 모드, 패딩 없음, 12바이트 임의 IV. 키는 SHA-3 해시(256비트)로 사용됩니다. 파일 크기 만들기 향상된 픽셀화 뇌졸중 픽셀화 향상된 다이아몬드 픽셀화 다이아몬드 픽셀화 원 픽셀화 향상된 원 픽셀화 색상 바꾸기 용인 교체할 색상 이메일 자르기 마스크 앱 설정을 파일로 백업하세요 저에게 연락하세요 향상된 글리치 부패 전환 X 맨 아래 복원하다 방향 & 스크립트 감지 전용 단일 블록 세로 텍스트 동그라미 단어 단일 문자 희소 텍스트 결함 선택한 필터 마스크를 삭제하려고 합니다. 이 작업은 취소할 수 없습니다. 마스크 삭제 드라고 \"%1$s\" 디렉터리를 찾을 수 없습니다. 기본 디렉터리로 전환했습니다. 파일을 다시 저장하세요. 선택한 폴더에 저장하는 대신 원본 파일이 새 파일로 대체됩니다. 이 옵션은 이미지 소스가 \"Explorer\" 또는 GetContent여야 하며, 이 옵션을 전환하면 자동으로 설정됩니다. 비어 있는 무료 사전 설정에서 125를 선택한 경우 이미지가 원본 이미지의 125% 크기로 저장됩니다. 사전 설정에서 50을 선택하면 이미지가 50% 크기로 저장됩니다. 여기 사전 설정은 출력 파일의 %를 결정합니다. 즉, 5MB 이미지에서 사전 설정 50을 선택하면 저장 후 2.5MB 이미지를 얻게 됩니다. 활성화하면 출력 파일 이름이 완전히 무작위가 됩니다. %1$s 폴더에 저장됨 텔레그램 채팅 앱에 대해 토론하고 다른 사용자로부터 피드백을 받으세요. 여기에서 베타 업데이트와 통찰력을 얻을 수도 있습니다. 종횡비 주어진 이미지에서 마스크를 생성하려면 이 마스크 유형을 사용하세요. 알파 채널이 있어야 합니다. 백업 및 복원 그러면 설정이 기본값으로 롤백됩니다. 위에서 언급한 백업 파일 없이는 이 작업을 취소할 수 없습니다. 구성표 삭제 글꼴 크기 큰 글꼴 크기를 사용하면 UI 결함 및 문제가 발생할 수 있으며 이는 해결되지 않습니다. 주의해서 사용하세요. 가 나 다 라 마 바 사 아 자 차 카 타 파 하 0123456789 !? 감정 음식과 음료 자연과 동물 사물 기호 이모티콘 활성화 여행과 장소 활동 배경 제거기 그림을 그리거나 자동 옵션을 사용하여 이미지에서 배경을 제거합니다. 이미지 자르기 원본 이미지 메타데이터는 유지됩니다. 이미지 주변의 투명한 공간이 잘립니다. 배경 자동 삭제 배경 지우기 이런… 문제가 발생했습니다. 아래 옵션을 사용하여 저에게 메일을 보내주시면 제가 해결 방법을 찾아보겠습니다. 크기 조정 및 변환 현재 %1$s 형식은 Android에서 EXIF 메타데이터 읽기만 허용합니다. 출력 이미지에는 저장 시 메타데이터가 전혀 포함되지 않습니다. 업데이트 베타 허용 활성화된 경우 업데이트 확인에 베타 앱 버전이 포함됩니다. 화살표 그리기 활성화된 경우 그리기 경로는 가리키는 화살표로 표시됩니다. 브러시 부드러움 기부 수평의 수직의 작은 이미지를 크게 확대 활성화된 경우 작은 이미지는 시퀀스에서 가장 큰 이미지로 크기가 조정됩니다. 이미지 순서 대상 색상 제거할 색상 색상 제거 녹음 그리기 모드에서 활성화하면 화면이 회전하지 않습니다. 토널 스팟 중립적 떠는 과일 샐러드 충실도 콘텐츠 기본 팔레트 스타일로 4가지 색상을 모두 사용자 정의할 수 있으며 다른 색상은 주요 색상만 설정할 수 있습니다. 모노크롬보다 살짝 더 유채색인 스타일 시끄러운 테마, 기본 팔레트에서는 다채로움이 최대이고 다른 팔레트에서는 증가합니다. 재미있는 테마 - 원본 색상의 색조가 테마에 표시되지 않습니다. 단색 테마, 색상은 순수 검정/흰색/회색입니다. Scheme.primaryContainer에 소스 색상을 배치하는 구성표 콘텐츠 구성표와 매우 유사한 구성표 둘 다 활성화된 경우 테마 색상을 부정적인 색상으로 바꿉니다. 찾다 메인 화면에서 사용 가능한 모든 도구를 검색할 수 있습니다. 주어진 출력 형식의 PDF를 이미지로 변환 주어진 이미지를 출력 PDF 파일로 압축 마스크 %d 단순 변형 형광펜 네온 개인 정보 보호 흐림 그림에 빛나는 효과를 추가하세요 기본 하나, 가장 간단함 - 색상만 있음 숨기고 싶은 것을 보호하기 위해 그려진 경로 아래의 이미지를 흐리게 합니다. 프라이버시 블러와 유사하지만 블러링 대신 픽셀화됩니다. 슬라이더 뒤에 그림자 그리기를 활성화합니다. 스위치 뒤에 그림자 그리기를 활성화합니다. 플로팅 액션 버튼 뒤에 그림자 그리기를 활성화합니다. 기본 버튼 뒤에 그림자 그리기를 활성화합니다. 앱 바 뒤에 그림자 그리기를 활성화합니다. 자동 회전 이미지 방향에 제한 상자를 채택할 수 있습니다. 시작점에서 끝점까지의 경로를 선으로 그립니다. 시작점에서 끝점까지 가리키는 화살표를 선으로 그립니다. 지정된 경로에서 가리키는 화살표를 그립니다. 시작점에서 끝점까지 이중 화살표를 선으로 그립니다. 지정된 경로에서 이중 가리키는 화살표를 그립니다. 시작점에서 끝점까지 직사각형을 그립니다. 시작점에서 끝점까지 타원을 그립니다. 시작점에서 끝점까지 윤곽선이 있는 타원을 그립니다. 시작점에서 끝점까지 윤곽선이 있는 직사각형을 그립니다. 수직 그리드 스티치 모드 행 수 열 개수 클립보드 자동 핀 활성화된 경우 저장된 이미지를 클립보드에 자동으로 추가합니다. 진동 진동 강도 파일을 덮어쓰려면 \"탐색기\" 이미지 소스를 사용해야 합니다. 이미지를 다시 선택해 보세요. 이미지 소스를 필요한 이미지 소스로 변경했습니다. 파일 덮어쓰기 접미사 쌍입방 기초적인 선형(또는 2차원의 이중선형) 보간은 일반적으로 이미지 크기를 변경하는 데 적합하지만 세부 사항이 바람직하지 않게 부드러워지고 여전히 들쭉날쭉해질 수 있습니다. 더 나은 스케일링 방법에는 Lanczos 리샘플링 및 Mitchell-Netravali 필터가 포함됩니다. 크기를 늘리는 간단한 방법 중 하나입니다. 모든 픽셀을 동일한 색상의 여러 픽셀로 바꾸는 것입니다. 거의 모든 앱에서 사용되는 가장 간단한 안드로이드 스케일링 모드 부드러운 곡선을 만들기 위해 컴퓨터 그래픽에서 일반적으로 사용되는 일련의 제어점을 부드럽게 보간하고 리샘플링하는 방법 스펙트럼 누출을 최소화하고 신호의 가장자리를 테이퍼링하여 주파수 분석의 정확성을 향상시키기 위해 신호 처리에 자주 적용되는 윈도우 기능 부드럽고 연속적인 곡선을 생성하기 위해 곡선 세그먼트의 끝점에서 값과 도함수를 사용하는 수학적 보간 기술 픽셀값에 가중치를 부여한 sinc 함수를 적용하여 고품질의 보간을 유지하는 리샘플링 방식 조정 가능한 매개변수가 있는 컨볼루션 필터를 사용하여 크기 조정된 이미지의 선명도와 앤티앨리어싱 간의 균형을 달성하는 리샘플링 방법 조각별로 정의된 다항식 함수를 사용하여 커브 또는 표면을 부드럽게 보간하고 근사치를 구하여 유연하고 연속적인 모양 표현을 제공합니다.. 저장소에 저장되지 않으며, 클립보드에만 이미지를 넣으려고 합니다. 구글 픽셀과 같은 스위치를 사용합니다. 원래 대상에 이름이 %1$s인 파일을 덮어썼습니다. 돋보기 강제 초기값 더 나은 접근성을 위해 그리기 모드에서 손가락 상단에 돋보기를 활성화합니다. EXIF 위젯을 처음에 강제로 확인합니다. 다중 언어 허용 탭 전환 이 앱은 완전 무료입니다. 앱이 더 커지길 원한다면 Github에서 프로젝트에 별표를 표시해 주세요 😄 자동 방향 지정 & 스크립트 감지 자동만 자동 단일 열 단일 블록 하나의 선 한 단어 희소 텍스트 방향 & 스크립트 감지 원시 라인 모든 인식 유형에 대한 언어 \"%1$s\" OCR 훈련 데이터를 삭제하시겠습니까, 아니면 선택한 인식 유형(%2$s)에 대해서만 삭제하시겠습니까? 속성 주어진 이미지 상단의 그라데이션을 구성합니다. 카메라 사용자 정의 가능한 텍스트/이미지 워터마크로 사진 표지 오프셋 Y 워터마크 유형 이 이미지는 워터마킹의 패턴으로 사용됩니다. GIF를 이미지로 GIF 파일을 사진 묶음으로 변환 일괄 이미지를 GIF 파일로 변환 이미지를 GIF로 시작하려면 GIF 이미지를 선택하세요. 첫 번째 프레임 크기 사용 지정된 크기를 첫 번째 프레임 크기로 바꾸기 반복 횟수 프레임 지연 밀리초 FPS 색종이 조각 저장, 공유 및 기타 기본 작업에 색종이가 표시됩니다. 보안 모드 종료 시 콘텐츠를 숨깁니다. 또한 화면을 캡처하거나 녹화할 수 없습니다. 그레이 스케일 바이엘 투 바이 투 디더링 바이엘 3x3 디더링 바이엘 포 바이 포 디더링 플로이드 스타인버그 디더링 자비스 주디스 닌케 디더링 시에라 디더링 두 행 시에라 디더링 시에라 라이트 디더링 앳킨슨 디더링 스투키 디더링 버크스 디더링 랜덤 디더링 단순 임계값 디더링 경사 변화 조각별로 정의된 쌍삼차 다항식 함수를 활용하여 곡선이나 표면을 부드럽게 보간하고 근사화하며 유연하고 연속적인 모양 표현을 제공합니다. 씨앗 애너글리프 소음 픽셀 정렬 혼합 채널 이동 X 채널 이동 Y 손상 크기 부패 변화 Y 텐트 블러 사이드 페이드 맨 위 물 효과 컬러 매트릭스 3x3 간단한 효과 폴라로이드 청색약 녹색약 적색약 포도 수확 코다 크롬 나이트 비전 시원한 삼변맹 듀타로노토피아 백색맹 색종종 색맹 보라색 안개 해돋이 다채로운 소용돌이 부드러운 봄빛 가을 톤 라벤더 드림 레모네이드 라이트 스펙트럼 파이어 나이트 매직 환상의 풍경 컬러 폭발 전기 그래디언트 카라멜 다크니스 미래 지향적인 그라데이션 그린 썬 레인보우 월드 딥 퍼플 우주 포털 붉은 소용돌이 디지털 코드 아이콘 모양 알드리지 끊다 우치무라 뫼비우스 이행 정점 색상 이상 원래 대상에서 덮어쓴 이미지 파일 덮어쓰기 옵션이 활성화된 동안에는 이미지 형식을 변경할 수 없습니다. 색 구성표로서의 이모티콘 수동으로 정의한 색상 대신 이모티콘 기본 색상을 앱 색상 구성표로 사용합니다. 파일이 손상되었거나 백업이 아님 기본 배경 복원 주어진 이미지의 크기를 변경하거나 다른 형식으로 변환하세요. 단일 이미지를 선택하는 경우 여기에서 EXIF 메타데이터를 편집할 수도 있습니다. 사진은 입력한 크기에 맞게 가운데가 잘립니다. 이미지가 입력한 크기보다 작을 경우 캔버스가 지정된 배경색으로 확장됩니다. 좀먹다 이방성 확산 확산 전도 수평풍 비틀거림 빠른 양측 흐림 포아송 블러 로그 톤 매핑 결정화하다 획 색상 프랙탈 유리 진폭 대리석 난기류 기름 크기 빈도 X 빈도 Y 진폭 X 진폭 Y 펄린 왜곡 Hable 필름 톤 매핑 Hejl Burgess 톤 매핑 ACES 영화 톤 매핑 ACES 힐 톤 매핑 현재의 모두 전체 필터 시작 센터 특정 이미지 또는 단일 이미지에 필터 체인 적용 PDF 파일로 작업: 미리보기, 이미지 배치로 변환 또는 주어진 사진에서 하나 생성 PDF 미리보기 PDF를 이미지로 이미지를 PDF로 간단한 PDF 미리보기 그라디언트 메이커 사용자 정의된 색상 및 모양 유형으로 지정된 출력 크기의 그라데이션 생성 속도 디헤이즈 오메가 PDF 도구 앱 평가 비율 컬러 매트릭스 4x4 브라우니 따뜻한 선의 방사형 스위프 그라데이션 유형 센터 X 센터 Y 타일 모드 반복됨 거울 집게 데칼 컬러 정지 색상 추가 올가미 주어진 경로로 닫힌 채워진 경로를 그립니다. 경로 그리기 모드 이중선 화살표 무료 드로잉 이중 화살표 선 화살표 화살 입력 값으로 경로를 그립니다. 윤곽선이 있는 타원형 윤곽선이 있는 직사각형 타원형 직사각형 디더링 양자화기 바이엘 에이트 바이 에이트 디더링 거짓 플로이드 스타인버그 디더링 왼쪽에서 오른쪽으로 디더링 최대 색상 수 이를 통해 앱이 충돌 보고서를 자동으로 수집할 수 있습니다. 노력 %1$s 값은 빠른 압축을 의미하므로 파일 크기가 상대적으로 커집니다. %2$s는 압축 속도가 느려져 파일 크기가 작아짐을 의미합니다. 기다리다 저장이 거의 완료되었습니다. 지금 취소하면 다시 저장해야 합니다. 마스크 색상 마스크 미리보기 그려진 필터 마스크가 렌더링되어 대략적인 결과를 보여줍니다. 스케일 모드 이중선형 에르미트 란초스 미첼 가장 가까운 운형자 기본값 %1$s - %2$s 범위의 값 시그마 공간 시그마 중앙값 흐림 캣멀 클립만 카드의 주요 아이콘 아래에 선택한 모양의 컨테이너를 추가합니다. 이미지 스티칭 주어진 이미지를 결합하여 하나의 큰 이미지를 얻으세요 밝기 강화 화면 그라데이션 오버레이 변환 카메라를 사용하여 사진을 찍습니다. 이 이미지 소스에서는 단 하나의 이미지만 가져올 수 있습니다. 이미지를 2개 이상 선택하세요. 출력 이미지 크기 이미지 방향 곡물 언샵 파스텔 오렌지 헤이즈 핑크 드림 골든아워 더운 여름 사이버펑크 워터마킹 워터마크 반복 주어진 위치에서 단일 워터마크 대신 이미지 전체에 워터마크를 반복합니다. 오프셋 X 텍스트 색상 오버레이 모드 픽셀 크기 그리기 방향 잠금 보케 GIF 도구 이미지를 GIF 그림으로 변환하거나 주어진 GIF 이미지에서 프레임을 추출합니다. 올가미 사용 그리기 모드에서와 같이 올가미를 사용하여 지우기를 수행합니다. 원본 이미지 미리보기 알파 마스크 필터 지정된 마스크 영역에 필터 체인을 적용합니다. 각 마스크 영역은 자체 필터 세트를 결정할 수 있습니다. 마스크 마스크 추가 앱바 이모지가 무작위로 변경됩니다. 무작위 이모티콘 이모티콘이 비활성화된 동안에는 무작위 이모티콘 선택을 사용할 수 없습니다. 무작위로 이모티콘을 선택하는 동안에는 이모티콘을 선택할 수 없습니다. 업데이트 확인 파일 이름 무작위화 이름이 %2$s인 %1$s 폴더에 저장되었습니다. 지원 이전에 생성된 파일에서 앱 설정 복원 설정이 성공적으로 복원되었습니다. 삭제 선택한 색 구성표를 삭제하려고 합니다. 이 작업은 취소할 수 없습니다. 폰트 텍스트 이미지 복원 지우기 모드 흐림 반경 피펫 그리기 모드 이슈 생성 해석학 익명의 앱 사용 통계 수집 허용 오래된 TV 셔플 블러 OCR(텍스트 인식) 주어진 이미지에서 텍스트를 인식합니다. 120개 이상의 언어가 지원됩니다. 사진에 텍스트가 없거나 앱에서 텍스트를 찾지 못했습니다. Accuracy: %1$s 인식 유형 빠른 기준 최상의 데이터 없음 Tesseract OCR이 제대로 작동하려면 추가 학습 데이터(%1$s)를 기기에 다운로드해야 합니다. \n%2$s 데이터를 다운로드하시겠습니까? 다운로드 연결되지 않았습니다. 기차 모델을 다운로드하려면 확인하고 다시 시도하세요. 다운로드된 언어 사용 가능한 언어 분할 모드 브러시는 배경을 지우는 대신 복원합니다. 수평 그리드 픽셀 스위치 사용 미끄러지 다 나란히 투명도 가장 좋아하는 아직 즐겨찾기 필터가 추가되지 않았습니다. B 스플라인 네이티브 스택 블러 가장자리를 흐리게 처리 활성화된 경우 단일 색상 대신 원본 이미지 아래에 흐린 가장자리를 그려 주변 공간을 채웁니다. 정기적인 픽셀화 역 채우기 유형 활성화하면 마스크되지 않은 모든 영역이 기본 동작 대신 필터링됩니다. 팔레트 스타일 나타내는 무지개 반투명의 날카로운 형광펜 경로 그리기 컨테이너 컨테이너 뒤에 그림자 그리기를 활성화합니다. 슬라이더 스위치 FAB 버튼 앱바 이 업데이트 검사기는 사용 가능한 새 업데이트가 있는지 확인하기 위해 GitHub에 연결됩니다. 주목 페이딩 엣지 장애가 있는 색상 반전 출구 지금 미리보기를 종료하면 이미지를 다시 추가해야 합니다. 이미지 형식 이미지에서\"Material You \"팔레트를 만듭니다 어두운 색상 라이트 변형 대신 야간 모드 색 구성표 사용 \"Jetpack Compose\"코드로 복사 링 블러 크로스 블러 원 흐림 스타 블러 선형 경사 교대 제거할 태그 이미지를 APNG로 APNG 파일을 사진 배치로 변환 배치 이미지를 APNG 파일로 변환 APNG 도구 이미지를 APNG 그림으로 변환하거나 주어진 APNG 이미지에서 프레임을 추출합니다 APNG를 이미지로 시작하려면 APNG 이미지를 선택하세요 모션 블러 지퍼 주어진 파일이나 이미지로 Zip 파일 만들기 드래그 핸들 너비 색종이 종류 축제 폭발 모서리 JXL 도구 품질 손실 없이 JXL ~ JPEG 트랜스코딩을 수행하거나 GIF/APNG를 JXL 애니메이션으로 변환합니다. JXL 에서 JPEG로 JXL에서 JPEG로 무손실 트랜스코딩을 수행합니다. JPEG에서 JXL로 무손실 트랜스코딩을 수행합니다. JPEG에서 JXL로 JXL 이미지를 선택하여 시작하세요. 빠른 정규분포 블러 2D 빠른 정규분포 블러 3D 빠른 정규분포 블러 4D 자동 붙여넣기 앱이 클립보드 데이터를 자동으로 붙여넣을 수 있도록 허용하여 메인 화면에 표시되고 처리할 수 있습니다. 색상 조화 레벨 조화 란코스 베셀 픽셀 값에 베셀(jinc) 함수를 적용하여 고품질 보간을 유지하는 리샘플링 방법 GIF에서 JXL로 GIF 이미지를 JXL 애니메이션 사진으로 변환합니다. APNG에서 JXL로 APNG 이미지를 JXL 애니메이션 사진으로 변환합니다. JXL에서 사진으로 JXL 애니메이션을 사진 배치로 변환합니다. 사진에서 JXL으로 사진 배치를 JXL 애니메이션으로 변환합니다. 행동 파일 선택 건너뛰기 선택한 화면에서 가능한 경우 파일 선택기가 즉시 표시됩니다. 미리보기 생성 미리보기 생성을 활성화하면 일부 장치에서 충돌을 방지하는 데 도움이 될 수 있으며, 단일 편집 옵션 내에서 일부 편집 기능을 비활성화할 수도 있습니다. 손실 압축 무손실 압축을 사용하여 무손실 대신 파일 크기를 줄입니다. 압축 타입 결과 이미지 디코딩 속도를 제어하여 결과 이미지를 더 빠르게 열 수 있으며, %1$s 값은 가장 느린 디코딩을 의미하고 %2$s는 가장 빠른 디코딩을 의미하며, 이 설정은 출력 이미지 크기를 증가시킬 수 있습니다. 분류 날짜 날짜 (역순) 이름 이름 (역순) 채널 구성 오늘 어제 내장 선택기 Image Toolbox의 이미지 선택기 권한이 없습니다 요청 여러 미디어 선택 미디어 하나 선택 선택 다시 시도하기 가로에서 설정 표시 이 설정을 비활성화하면 가로 모드 설정은 영구적으로 표시되는 옵션 대신 항상 앱 상단 표시줄의 버튼에서 열립니다. 전체화면 설정 이 기능을 활성화하면 설정 페이지가 슬라이드 가능한 서랍 시트 대신 항상 전체 화면으로 열립니다. 스위치 타입 구성 제트팩 구성 재료를 교체하세요. A 머티리얼 전환 맥스 앵커 크기 조정 픽셀 유창한 \"Fluent\" 디자인 시스템을 기반으로 한 스위치 쿠퍼티노 \"Cupertino\" 디자인 시스템을 기반으로 한 스위치 이미지를 SVG로 주어진 이미지를 SVG 이미지로 추적 샘플링된 팔레트 사용 이 옵션을 활성화하면 양자화 팔레트가 샘플링됩니다. 경로 생략 축소 없이 큰 이미지를 추적하기 위해 이 도구를 사용하는 것은 권장되지 않습니다. 충돌이 발생하고 처리 시간이 늘어날 수 있습니다. 이미지 축소 처리하기 전에 이미지 크기가 더 낮은 크기로 축소됩니다. 이는 도구가 더 빠르고 안전하게 작동하는 데 도움이 됩니다. 최소 색상 비율 라인 임계값 2차 임계값 좌표 반올림 공차 경로 규모 속성 재설정 모든 속성이 기본값으로 설정됩니다. 이 작업은 취소할 수 없습니다. 상세한 기본 줄 너비 엔진 모드 유산 LSTM 네트워크 레거시 및 LSTM 전환하다 이미지 배치를 지정된 형식으로 변환 새 폴더 추가 샘플당 비트 압축 광도 해석 픽셀당 샘플 평면 구성 Y Cb Cr 하위 샘플링 Y Cb Cr 포지셔닝 X 해상도 Y 해상도 분해능 단위 스트립 오프셋 스트립당 행 스트립 바이트 수 JPEG 교환 형식 JPEG 교환 형식 길이 전달 함수 화이트 포인트 기본 색도 Y Cb Cr 계수 참조 블랙 화이트 날짜 시간 이미지 설명 만들다 모델 소프트웨어 아티스트 저작권 Exif 버전 플래시픽스 버전 색 공간 감마 픽셀 X 차원 픽셀 Y 차원 픽셀당 압축된 비트 메이커노트 사용자 코멘트 관련 사운드 파일 날짜 시간 원본 디지털화된 날짜 시간 오프셋 시간 오프셋 시간 원본 오프셋 시간이 디지털화됨 하위 초 시간 하위 초 시간 원본 1초 미만의 시간이 디지털화됨 노출 시간 F 번호 노출 프로그램 스펙트럼 감도 사진 감도 OECF 감도 유형 표준 출력 감도 권장 노출 지수 ISO 속도 ISO 속도 위도 yyy ISO 속도 위도 zzz 셔터 속도 값 조리개 값 밝기 값 노출 바이어스 값 최대 조리개 값 피사체 거리 측광 모드 플래시 주제 영역 초점 거리 플래시 에너지 공간 주파수 응답 초점면 X 해상도 초점면 Y 해상도 초점면 해상도 단위 주제 위치 노출지수 감지방식 파일 소스 CFA 패턴 맞춤 렌더링됨 노출 모드 화이트 밸런스 디지털 줌 비율 초점 거리 In35mm 필름 장면 캡처 유형 이득 제어 차이 포화 날카로움 장치 설정 설명 피사체 거리 범위 이미지 고유 ID 카메라 소유자 이름 본체 일련번호 렌즈 사양 렌즈 제조사 렌즈 모델 렌즈 일련번호 GPS 버전 ID GPS 위도 참조 GPS 위도 GPS 경도 참조 GPS 경도 GPS 고도 참조 GPS 고도 GPS 타임 스탬프 GPS 위성 GPS 상태 GPS 측정 모드 GPS DOP GPS 속도 참조 GPS 속도 GPS 트랙 참조 GPS 트랙 GPS 이미지 방향 참조 GPS 이미지 방향 GPS 지도 데이텀 GPS 목적지 위도 참조 GPS 목적지 위도 GPS 목적지 경도 참조 GPS 목적지 경도 GPS 목적지 베어링 참조 GPS 대상 베어링 GPS 목적지 거리 참조 GPS 목적지 거리 GPS 처리 방식 GPS 지역 정보 GPS 날짜 스탬프 GPS 차동 GPS H 포지셔닝 오류 상호 운용성 지수 DNG 버전 기본 자르기 크기 미리보기 이미지 시작 미리보기 이미지 길이 화면 프레임 센서 하단 테두리 센서 왼쪽 테두리 센서 오른쪽 테두리 센서 상단 테두리 ISO 주어진 글꼴과 색상으로 경로에 텍스트 그리기 글꼴 크기 워터마크 크기 텍스트 반복 한 번만 그리는 대신 경로 끝까지 현재 텍스트가 반복됩니다. 대시 크기 선택한 이미지를 사용하여 주어진 경로를 따라 그립니다. 이 이미지는 그려진 경로의 반복 항목으로 사용됩니다. 시작점에서 끝점까지 윤곽선이 있는 삼각형을 그립니다. 시작점에서 끝점까지 윤곽선이 있는 삼각형을 그립니다. 윤곽이 있는 삼각형 삼각형 시작점에서 끝점까지 다각형을 그립니다. 다각형 윤곽이 있는 다각형 시작점에서 끝점까지 윤곽이 있는 다각형을 그립니다. 정점 정다각형 그리기 자유 형식이 아닌 규칙적인 다각형을 그립니다. 시작점부터 끝점까지 별을 그립니다. 윤곽선 별 시작점에서 끝점까지 윤곽선 별을 그립니다. 내부 반경 비율 일반 별 그리기 자유형이 아닌 규칙적인 별을 그려보세요 앤티앨리어스 날카로운 모서리를 방지하기 위해 앤티앨리어싱을 활성화합니다. 미리보기 대신 편집 열기 ImageToolbox에서 열려는(미리보기) 이미지를 선택하면 미리보기 대신 편집 선택 시트가 열립니다. 문서 스캐너 문서를 스캔하고 PDF를 생성하거나 문서에서 이미지를 분리하세요 스캔을 시작하려면 클릭하세요 스캔 시작 PDF로 저장 PDF로 공유 아래 옵션은 PDF가 아닌 이미지 저장을 위한 것입니다. 히스토그램 HSV 균등화 히스토그램 균등화 백분율 입력 텍스트 필드로 입력 허용 사전 설정 선택 뒤에 텍스트 필드를 활성화하여 즉시 입력할 수 있습니다. 스케일 색 공간 선의 히스토그램 픽셀화 균등화 그리드 크기 X 그리드 크기 Y 히스토그램 적응형 균등화 히스토그램 적응형 LUV 균등화 히스토그램 적응형 LAB 균등화 클라헤 클라헤 연구소 클라헤 루브 내용에 맞춰 자르기 프레임 색상 무시할 색상 주형 추가된 템플릿 필터가 없습니다. 새로 만들기 스캔한 QR 코드는 유효한 필터 템플릿이 아닙니다. QR 코드 스캔 선택한 파일에는 필터 템플릿 데이터가 없습니다. 템플릿 생성 템플릿 이름 이 이미지는 이 필터 템플릿을 미리 보는 데 사용됩니다. 템플릿 필터 QR코드 이미지로 파일로 파일로 저장 QR 코드 이미지로 저장 템플릿 삭제 선택한 템플릿 필터를 삭제하려고 합니다. 이 작업은 취소할 수 없습니다. 이름이 \"%1$s\"(%2$s)인 필터 템플릿이 추가되었습니다. 필터 미리보기 QR 및 바코드 QR 코드를 스캔하여 내용을 가져오거나 문자열을 붙여넣어 새 내용을 생성하세요. 코드 내용 바코드를 스캔하여 필드의 콘텐츠를 바꾸거나 무언가를 입력하여 선택한 유형의 새 바코드를 생성하세요. QR 설명 최소 QR 코드를 스캔하려면 설정에서 카메라 권한을 부여하세요. 문서 스캐너를 스캔하려면 설정에서 카메라 권한을 부여하세요. 큐빅 B-스플라인 해밍 해닝 블랙맨 웨일스 말 2차 가우스 스핑크스 바틀렛 로비두 로비두 샤프 스플라인 16 스플라인 36 스플라인 64 황제 바틀렛-헤 상자 보만 란초스 2 란초스 3 란초스 4 란초스 2 징크 란초스 3 징크 란초스 4 징크 큐빅 보간은 가장 가까운 16픽셀을 고려하여 더 부드러운 스케일링을 제공하여 바이리니어 보간보다 더 나은 결과를 제공합니다. 조각별로 정의된 다항식 함수를 활용하여 곡선이나 표면을 부드럽게 보간하고 근사화하며 유연하고 연속적인 모양 표현을 제공합니다. 신호 처리에 유용한 신호 가장자리를 테이퍼링하여 스펙트럼 누출을 줄이는 데 사용되는 창 기능 신호 처리 응용 분야에서 스펙트럼 누출을 줄이기 위해 일반적으로 사용되는 Hann 창의 변형입니다. 신호 처리에 자주 사용되는 스펙트럼 누출을 최소화하여 우수한 주파수 분해능을 제공하는 윈도우 함수 신호 처리 응용 분야에서 자주 사용되는 스펙트럼 누출을 줄이면서 우수한 주파수 분해능을 제공하도록 설계된 창 기능 보간을 위해 2차 함수를 사용하는 방법으로 부드럽고 연속적인 결과를 제공합니다. 이미지의 노이즈를 부드럽게 하고 줄이는 데 유용한 가우스 함수를 적용하는 보간 방법 아티팩트를 최소화하면서 고품질 보간을 제공하는 고급 리샘플링 방법 스펙트럼 누출을 줄이기 위해 신호 처리에 사용되는 삼각형 창 기능 자연스러운 이미지 크기 조정, 선명도와 부드러움의 균형을 맞추는 데 최적화된 고품질 보간 방법 선명한 이미지 크기 조정에 최적화된 Robidoux 방법의 보다 선명한 변형 16탭 필터를 사용하여 부드러운 결과를 제공하는 스플라인 기반 보간 방법 36탭 필터를 사용하여 부드러운 결과를 제공하는 스플라인 기반 보간법 64탭 필터를 사용하여 부드러운 결과를 제공하는 스플라인 기반 보간 방법 카이저 창을 사용하는 보간 방법으로 메인 로브 너비와 사이드 로브 수준 간의 균형을 효과적으로 제어할 수 있습니다. 신호 처리에서 스펙트럼 누출을 줄이는 데 사용되는 Bartlett 창과 Hann 창을 결합한 하이브리드 창 기능 가장 가까운 픽셀 값의 평균을 사용하는 간단한 리샘플링 방법으로 종종 덩어리진 모양이 발생합니다. 스펙트럼 누출을 줄이는 데 사용되는 창 기능으로 신호 처리 응용 분야에서 우수한 주파수 분해능을 제공합니다. 아티팩트를 최소화한 고품질 보간을 위해 2-엽 Lanczos 필터를 사용하는 리샘플링 방법 아티팩트를 최소화한 고품질 보간을 위해 3-엽 Lanczos 필터를 사용하는 리샘플링 방법 아티팩트를 최소화한 고품질 보간을 위해 4-엽 Lanczos 필터를 사용하는 리샘플링 방법 jinc 함수를 사용하여 아티팩트를 최소화하면서 고품질 보간을 제공하는 Lanczos 2 필터의 변형입니다. jinc 기능을 사용하여 아티팩트를 최소화하면서 고품질 보간을 제공하는 Lanczos 3 필터의 변형입니다. jinc 기능을 사용하여 아티팩트를 최소화하면서 고품질 보간을 제공하는 Lanczos 4 필터의 변형입니다. 해닝 EWA 원활한 보간 및 리샘플링을 위한 해닝 필터의 EWA(Elliptical Weighted Average) 변형 로비두 EWA 고품질 리샘플링을 위한 Robidoux 필터의 EWA(Elliptical Weighted Average) 변형 블랙맨 이브 링잉 아티팩트를 최소화하기 위한 Blackman 필터의 EWA(Elliptical Weighted Average) 변형 2차 EWA 원활한 보간을 위한 Quadric 필터의 EWA(Elliptical Weighted Average) 변형 로비두 샤프 EWA 보다 선명한 결과를 위한 Robidoux Sharp 필터의 EWA(Elliptical Weighted Average) 변형 Lanczos 3 진크 EWA 감소된 앨리어싱으로 고품질 리샘플링을 위한 Lanczos 3 Jinc 필터의 EWA(Elliptical Weighted Average) 변형 인삼 선명도와 부드러움의 균형이 잘 잡힌 고품질 이미지 처리를 위해 설계된 리샘플링 필터 인삼 EWA 향상된 이미지 품질을 위한 Ginseng 필터의 EWA(Elliptical Weighted Average) 변형 란초스 샤프 EWA 아티팩트를 최소화하면서 선명한 결과를 얻기 위한 Lanczos Sharp 필터의 EWA(Elliptical Weighted Average) 변형 Lanczos 4 샤프스트 EWA 매우 선명한 이미지 리샘플링을 위한 Lanczos 4 Sharpest 필터의 EWA(Elliptical Weighted Average) 변형 란초스 소프트 EWA 더욱 부드러운 이미지 리샘플링을 위한 Lanczos Soft 필터의 EWA(Elliptical Weighted Average) 변형 하슨소프트 부드럽고 아티팩트 없는 이미지 스케일링을 위해 Haasn이 설계한 리샘플링 필터 형식 변환 이미지 배치를 한 형식에서 다른 형식으로 변환 영원히 해고하다 이미지 스태킹 선택한 블렌드 모드를 사용하여 이미지를 서로 쌓습니다. 이미지 추가 빈 개수 클라헤 HSL 클라헤 HSV 히스토그램 적응형 HSL 균등화 히스토그램 적응형 HSV 균등화 엣지 모드 클립 포장하다 색맹 선택한 색맹 변형에 테마 색상을 적용하려면 모드를 선택하세요. 빨간색과 녹색 색상을 구별하기가 어렵습니다. 녹색과 빨간색 색상을 구별하기가 어렵습니다. 파란색과 노란색 색상을 구별하기가 어렵습니다. 붉은색을 인식할 수 없음 녹색 색상을 인식할 수 없음 푸른 색조를 인식할 수 없음 모든 색상에 대한 민감도 감소 완전한 색맹, 회색 음영만 보는 색맹 색맹 구성표를 사용하지 마십시오 색상은 테마에 설정된 대로 정확하게 적용됩니다. S자형 라그랑주 2 부드러운 전환이 가능한 고품질 이미지 스케일링에 적합한 2차 라그랑주 보간 필터 라그랑주 3 이미지 스케일링에 대해 더 나은 정확도와 더 부드러운 결과를 제공하는 3차 라그랑주 보간 필터 란초스 6 더 선명하고 정확한 이미지 스케일링을 제공하는 더 높은 차수 6의 Lanczos 리샘플링 필터 란초스 6 징크 향상된 이미지 리샘플링 품질을 위해 Jinc 기능을 사용하는 Lanczos 6 필터의 변형 선형 상자 흐림 선형 텐트 흐림 선형 가우스 상자 흐림 선형 스택 흐림 가우시안 박스 블러 선형 고속 가우스 흐림 다음 선형 고속 가우스 흐림 선형 가우시안 블러 하나의 필터를 선택하여 페인트로 사용 필터 교체 그림에서 브러시로 사용하려면 아래 필터를 선택하세요. TIFF 압축 방식 낮은 폴리 모래 그림 이미지 분할 단일 이미지를 행 또는 열로 분할 경계에 맞추기 원하는 동작을 달성하려면 자르기 크기 조정 모드를 이 매개변수와 결합하세요(종횡비에 맞게 자르기/맞춤). 성공적으로 가져온 언어 백업 OCR 모델 수입 내보내다 위치 센터 왼쪽 위 오른쪽 상단 왼쪽 하단 오른쪽 하단 상단 중앙 중앙 오른쪽 하단 중앙 중앙 왼쪽 대상 이미지 팔레트 이송 강화 오일 단순한 오래된 TV HDR 고담 간단한 스케치 부드러운 빛 컬러 포스터 트라이톤 세 번째 색상 클라헤 오크랩 클라라 올치 클라헤 자즈브즈 폴카 도트 클러스터링된 2x2 디더링 클러스터링된 4x4 디더링 클러스터링된 8x8 디더링 Yililoma 디더링 즐겨찾는 옵션이 선택되지 않았습니다. 도구 페이지에 추가하세요. 즐겨찾기 추가 보완적인 유사한 삼극체 분할 보완 테트라딕 정사각형 유사 + 보완 컬러 도구 믹스, 톤 만들기, 셰이드 생성 등 색상 조화 색상 음영 변화 색조 그늘 색상 혼합 색상 정보 선택한 색상 혼합할 색상 동적 색상이 켜져 있는 동안에는 모네를 사용할 수 없습니다. 512x512 2D LUT 대상 LUT 이미지 아마추어 미스 에티켓 부드러운 우아함 소프트 엘레강스 변형 팔레트 전송 변형 3D LUT 대상 3D LUT 파일(.cube / .CUBE) LUT 표백제 우회 촛불 드롭 블루스 엣지있는 앰버 가을 색 필름 스톡 50 안개가 자욱한 밤 코닥 중립 LUT 이미지 가져오기 먼저, 즐겨 사용하는 사진 편집 애플리케이션을 사용하여 여기에서 얻을 수 있는 중립 LUT에 필터를 적용하세요. 이것이 제대로 작동하려면 각 픽셀 색상이 다른 픽셀에 종속되어서는 안 됩니다(예: 흐림 효과는 작동하지 않습니다). 준비가 되면 새 LUT 이미지를 512*512 LUT 필터의 입력으로 사용합니다. 팝아트 셀룰로이드 커피 황금의 숲 녹색을 띠는 레트로 옐로우 링크 미리보기 텍스트(QRCode, OCR 등)를 얻을 수 있는 위치에서 링크 미리보기 검색을 활성화합니다. 모래밭 ICO 파일은 최대 256 x 256 크기로만 저장할 수 있습니다. WEBP로 GIF GIF 이미지를 WEBP 애니메이션 사진으로 변환 WEBP 도구 이미지를 WEBP 애니메이션 그림으로 변환하거나 특정 WEBP 애니메이션에서 프레임을 추출합니다. WEBP를 이미지로 WEBP 파일을 사진 배치로 변환 이미지 배치를 WEBP 파일로 변환 WEBP에 대한 이미지 시작하려면 WEBP 이미지를 선택하세요. 파일에 대한 전체 액세스 권한이 없습니다. 안드로이드에서 이미지로 인식되지 않는 JXL, QOI 등의 이미지를 보려면 모든 파일 접근을 허용하세요. 권한이 없으면 Image Toolbox가 해당 이미지를 표시할 수 없습니다. 기본 그리기 색상 기본 그리기 경로 모드 타임스탬프 추가 출력 파일 이름에 타임스탬프 추가를 활성화합니다. 형식화된 타임스탬프 기본 밀리초 대신 출력 파일 이름에 타임스탬프 형식을 활성화합니다. 타임스탬프를 활성화하여 형식을 선택하세요 일회용 저장 위치 대부분의 모든 옵션에서 저장 버튼을 길게 눌러 사용할 수 있는 일회용 저장 위치를 ​​보고 편집합니다. 최근 사용됨 CI 채널 그룹 텔레그램의 이미지 도구 상자 🎉 원하는 무엇이든 토론할 수 있는 채팅에 참여하고 베타 및 공지사항을 게시하는 CI 채널도 살펴보세요. 앱의 새 버전에 대한 알림을 받고 공지사항을 읽어보세요. 주어진 크기에 이미지를 맞추고 배경에 흐림이나 색상을 적용합니다. 도구 배열 유형별로 도구 그룹화 사용자 정의 목록 정렬 대신 유형별로 기본 화면의 도구를 그룹화합니다. 기본값 시스템 표시줄 가시성 스와이프하여 시스템 표시줄 표시 숨겨진 경우 스와이프하여 시스템 표시줄을 표시할 수 있습니다. 자동 모두 숨기기 모두 표시 탐색 모음 숨기기 상태 표시줄 숨기기 소음 발생 Perlin이나 다른 유형과 같은 다양한 노이즈 생성 빈도 소음 유형 회전 유형 프랙탈 유형 옥타브 빈약함 얻다 가중 강도 탁구의 힘 거리 함수 반환 유형 지터 도메인 워프 조정 사용자 정의 파일 이름 현재 이미지를 저장하는 데 사용될 위치와 파일 이름을 선택하십시오. 맞춤 이름으로 폴더에 저장됨 콜라주 메이커 최대 20개의 이미지로 콜라주 만들기 콜라주 유형 이미지를 잡고 위치를 조정하고 이동하고 확대/축소하세요. 회전 비활성화 두 손가락 동작으로 이미지 회전 방지 테두리에 맞추기 활성화 이동하거나 확대/축소한 후에는 이미지가 프레임 가장자리를 채우도록 스냅됩니다. 히스토그램 조정에 도움이 되는 RGB 또는 밝기 이미지 히스토그램 이 이미지는 RGB 및 밝기 히스토그램을 생성하는 데 사용됩니다. 테서랙트 옵션 Tesseract 엔진에 일부 입력 변수 적용 맞춤 옵션 옵션은 다음 패턴에 따라 입력해야 합니다: \"--{option_name} {value}\" 자동 자르기 무료 코너 다각형으로 이미지 자르기, 원근감도 수정됩니다. 포인트를 이미지 경계로 강제 변환 포인트는 이미지 경계에 의해 제한되지 않으며 이는 보다 정확한 원근 교정에 유용합니다. 마스크 그려진 경로 아래 내용 인식 채우기 힐 스팟 서클 커널 사용 열기 폐쇄 형태학적 구배 모자 검은 모자 톤 곡선 곡선 재설정 곡선이 기본값으로 롤백됩니다. 선 스타일 간격 크기 점선 점선 스탬프가 찍힌 지그재그 지정된 간격 크기로 그려진 경로를 따라 점선을 그립니다. 주어진 경로를 따라 점선과 점선을 그립니다. 그냥 기본 직선 지정된 간격으로 경로를 따라 선택한 모양을 그립니다. 경로를 따라 물결 모양의 지그재그를 그립니다. 지그재그 비율 바로가기 만들기 고정할 도구 선택 도구는 실행기의 홈 화면에 바로가기로 추가됩니다. 필요한 동작을 달성하려면 \"파일 선택 건너뛰기\" 설정과 결합하여 사용하세요. 프레임을 쌓지 마세요 이전 프레임을 폐기하여 서로 쌓이지 않도록 합니다. 크로스페이드 프레임이 서로 크로스페이드됩니다. 크로스페이드 프레임 수 임계값 1 임계값 2 영리한 거울 101 향상된 줌 블러 라플라시안 단순 소벨 심플 도우미 그리드 정확한 조작을 돕기 위해 도면 영역 위에 지지 그리드를 표시합니다. 그리드 색상 셀 너비 셀 높이 컴팩트 셀렉터 일부 선택 컨트롤은 공간을 덜 차지하기 위해 컴팩트한 레이아웃을 사용합니다. 이미지를 캡처하려면 설정에서 카메라 권한을 부여하세요. 공들여 나열한 것 메인 화면 제목 고정율 인자(CRF) %1$s 값은 압축 속도가 느려 파일 크기가 상대적으로 작아짐을 의미합니다. %2$s은 압축 속도가 빨라져 파일 크기가 커진다는 의미입니다. 루트 도서관 다운로드 후 적용할 수 있는 LUT 컬렉션 다운로드 다운로드 후 적용할 수 있는 LUT 컬렉션 업데이트(새 LUT만 대기열에 추가됨) 필터의 기본 이미지 미리보기 변경 미리보기 이미지 숨다 보여주다 슬라이더 유형 팬시한 자료 2 고급스러워 보이는 슬라이더. 기본 옵션입니다 머티리얼 2 슬라이더 재료 당신 슬라이더 적용하다 중앙 대화 상자 버튼 가능한 경우 대화 상자의 버튼은 왼쪽 대신 중앙에 배치됩니다. 오픈 소스 라이선스 이 앱에 사용된 오픈 소스 라이브러리의 라이선스 보기 영역 픽셀 영역 관계를 사용한 리샘플링. 모아레 없는 결과를 제공하므로 이미지 데시메이션에 선호되는 방법일 수 있습니다. 하지만 이미지를 확대하면 \"Nearest\" 방법과 유사합니다. 톤 매핑 활성화 입력하다 % 사이트에 액세스할 수 없습니다. VPN을 사용해 보거나 URL이 올바른지 확인하세요. 마크업 레이어 이미지, 텍스트 등을 자유롭게 배치할 수 있는 레이어 모드 레이어 편집 이미지의 레이어 이미지를 배경으로 사용하고 그 위에 다양한 레이어를 추가하세요. 배경 레이어 첫 번째 옵션과 동일하지만 이미지 대신 색상이 사용됨 베타 빠른 설정 측면 이미지를 편집하는 동안 선택한 면에 플로팅 스트립을 추가하면 클릭 시 빠른 설정이 열립니다. 선택 취소 \"%1$s\" 설정 그룹은 기본적으로 축소됩니다. 기본적으로 \"%1$s\" 설정 그룹이 확장됩니다. Base64 도구 Base64 문자열을 이미지로 디코딩하거나 이미지를 Base64 형식으로 인코딩합니다. Base64 제공된 값은 유효한 Base64 문자열이 아닙니다. 비어 있거나 유효하지 않은 Base64 문자열을 복사할 수 없습니다. Base64 붙여넣기 Base64 복사 Base64 문자열을 복사하거나 저장하려면 이미지를 로드하세요. 문자열 자체가 있는 경우 위에 붙여넣어 이미지를 얻을 수 있습니다. Base64 저장 Base64 공유 옵션 행위 Base64 가져오기 Base64 작업 개요 추가 지정된 색상과 너비로 텍스트 주위에 윤곽선 추가 외곽선 색상 외곽선 크기 회전 파일 이름으로 체크섬 출력 이미지에는 데이터 체크섬에 해당하는 이름이 있습니다. 자유 소프트웨어(파트너) Android 애플리케이션 파트너 채널의 더 유용한 소프트웨어 연산 체크섬 도구 다양한 해싱 알고리즘을 사용하여 체크섬 비교, 해시 계산 또는 파일에서 16진수 문자열 생성 믿다 텍스트 해시 체크섬 선택한 알고리즘을 기반으로 체크섬을 계산할 파일을 선택하세요. 선택한 알고리즘을 기반으로 체크섬을 계산하려면 텍스트를 입력하세요. 소스 체크섬 비교할 체크섬 성냥! 차이점 체크섬이 동일하므로 안전할 수 있습니다. 체크섬이 동일하지 않아 파일이 안전하지 않을 수 있습니다! 메쉬 그라디언트 메쉬 그라디언트의 온라인 컬렉션을 살펴보세요 TTF 및 OTF 글꼴만 가져올 수 있습니다. 글꼴 가져오기(TTF/OTF) 글꼴 내보내기 가져온 글꼴 시도를 저장하는 중 오류가 발생했습니다. 출력 폴더를 변경해 보세요. 파일 이름이 설정되지 않았습니다. 없음 사용자 정의 페이지 페이지 선택 도구 종료 확인 특정 도구를 사용하는 동안 저장하지 않은 변경 사항이 있는 경우 이를 닫으려고 하면 확인 대화 상자가 표시됩니다. EXIF 편집 재압축 없이 단일 이미지의 메타데이터 변경 사용 가능한 태그를 수정하려면 탭하세요. 스티커 변경 너비에 맞게 맞는 높이 일괄 비교 선택한 알고리즘을 기반으로 체크섬을 계산할 파일을 선택하세요. 파일 선택 디렉토리 선택 머리 길이 척도 우표 타임스탬프 형식 패턴 이미지 커팅 이미지 부분을 자르고 왼쪽 부분을 수직 또는 수평선으로 병합합니다(반전 가능). 수직 피벗선 수평 피벗선 역선택 절단 영역 주위의 부분을 병합하는 대신 수직 절단 부분이 남습니다. 절단 영역 주위의 부품을 병합하는 대신 수평 절단 부분이 남습니다. 메쉬 그라디언트 컬렉션 사용자 정의된 매듭 수와 해상도로 메쉬 그라디언트 생성 메쉬 그라디언트 오버레이 주어진 이미지 상단의 메쉬 그래디언트 구성 포인트 맞춤화 그리드 크기 해상도 X 해상도 Y 해결 픽셀 단위 하이라이트 색상 픽셀 비교 유형 바코드 스캔 높이 비율 바코드 유형 흑백 적용 바코드 이미지는 완전히 흑백이며 앱 테마에 따라 색상이 지정되지 않습니다. 바코드(QR, EAN, AZTEC 등)를 스캔하고 내용을 가져오거나 텍스트를 붙여넣어 새 바코드를 생성하세요. 바코드를 찾을 수 없습니다 생성된 바코드가 여기에 있습니다 오디오 커버 오디오 파일에서 앨범 표지 이미지를 추출합니다. 가장 일반적인 형식이 지원됩니다. 시작할 오디오 선택 오디오 선택 표지를 찾을 수 없습니다 로그 보내기 앱 로그 파일을 공유하려면 클릭하세요. 그러면 문제를 파악하고 해결하는 데 도움이 될 수 있습니다. 이런… 문제가 발생했습니다. 아래 옵션을 사용하여 저에게 연락하시면 해결 방법을 찾아보겠습니다.\n(로그를 첨부하는 것을 잊지 마세요) 파일에 쓰기 이미지 배치에서 텍스트를 추출하여 하나의 텍스트 파일에 저장합니다. 메타데이터에 쓰기 각 이미지에서 텍스트를 추출하여 관련 사진의 EXIF ​​정보에 배치합니다. 보이지 않는 모드 스테가노그래피를 사용하여 이미지 바이트 안에 눈에 보이지 않는 워터마크 만들기 LSB 사용 LSB(Less Significant Bit) 스테가노그래피 방법이 사용되며, 그렇지 않으면 FD(Frequency Domain)가 사용됩니다. 적목 현상 자동 제거 비밀번호 터놓다 PDF가 보호됩니다 작업이 거의 완료되었습니다. 지금 취소하면 다시 시작해야 합니다. 수정된 날짜 수정 날짜(역순) 크기 크기(역순) MIME 유형 MIME 유형(역방향) 확대 확장(역방향) 추가된 날짜 추가된 날짜(역순) 왼쪽에서 오른쪽으로 오른쪽에서 왼쪽으로 위에서 아래로 아래에서 위로 액체 유리 최근 발표된 IOS 26 및 액체 유리 설계 시스템을 기반으로 한 스위치 이미지를 선택하거나 아래에서 Base64 데이터를 붙여넣거나 가져오세요. 시작하려면 이미지 링크를 입력하세요. 링크 붙여넣기 만화경 보조 각도 측면 채널 믹스 청록색 빨간색 파란색 녹색 빨간색 빨간색으로 녹색으로 파란색으로 청록색 마젠타 노란색 색상 하프톤 윤곽 레벨 오프셋 보로노이 크리스탈라이즈 모양 뻗기 무작위성 얼룩 제거 퍼지다 두 번째 반경 같게 하다 불타는 듯한 빛깔 소용돌이와 핀치 점묘화 테두리 색상 극좌표 극좌표에서 직사각형으로 극좌표에서 직사각형으로 원으로 반전 소음 감소 단순 솔라라이즈 짜다 X 간격 Y 간격 X 폭 Y 폭 회전 고무 도장 도말 표본 밀도 혼합 구형 렌즈 왜곡 굴절률 확산 각도 불꽃 광선 아스키 구배 메리 가을 제트기 겨울 대양 여름 멋진 변형 HSV 분홍색 더운 단어 연한 덩어리 지옥 혈장 비리디스 시민 어스름 황혼의 변화 원근 자동 왜곡 보정 자르기 허용 자르기 또는 원근감 순수한 터보 딥 그린 렌즈 교정 JSON 형식의 대상 렌즈 프로필 파일 준비된 렌즈 프로필 다운로드 부분 퍼센트 JSON으로 내보내기 팔레트 데이터가 포함된 문자열을 JSON 표현으로 복사 솔기 조각 홈 화면 잠금 화면 내장 배경화면 내보내기 새로 고치다 현재 홈, 자물쇠 및 내장 배경화면을 받으세요. 모든 파일에 대한 액세스를 허용합니다. 배경화면을 검색하는 데 필요합니다. 외부 저장소 권한 관리만으로는 충분하지 않습니다. 이미지에 대한 액세스를 허용해야 합니다. \"모두 허용\"을 선택하세요. 파일 이름에 사전 설정 추가 이미지 파일 이름에 선택한 사전 설정이 포함된 접미사를 추가합니다. 파일 이름에 이미지 크기 모드 추가 선택한 이미지 크기 모드가 포함된 접미사를 이미지 파일 이름에 추가합니다. 아스키 아트 그림을 이미지처럼 보이는 ASCII 텍스트로 변환 매개변수 경우에 따라 더 나은 결과를 얻기 위해 이미지에 네거티브 필터를 적용합니다. 스크린샷 처리 중 스크린샷이 캡처되지 않았습니다. 다시 시도해 주세요. 저장을 건너뛰었습니다. %1$s 파일을 건너뛰었습니다. 더 큰 경우 건너뛰기 허용 결과 파일 크기가 원본보다 클 경우 일부 도구에서는 이미지 저장을 건너뛸 수 있습니다. 캘린더 이벤트 연락하다 이메일 위치 핸드폰 텍스트 SMS URL Wi-Fi 개방형 네트워크 해당 없음 SSID 핸드폰 메시지 주소 주제 이름 조직 제목 전화기 이메일 URL 구애 요약 설명 위치 조직자 시작일 종료일 상태 위도 경도 바코드 생성 바코드 편집 Wi-Fi 구성 보안 연락처 선택 선택한 연락처를 사용하여 자동 완성하려면 설정에서 연락처 권한을 부여하세요. 연락처 정보 이름 중간 이름 발음 전화 추가 이메일 추가 주소 추가 웹사이트 웹사이트 추가 형식화된 이름 이 이미지는 바코드 위에 배치하는 데 사용됩니다. 코드 사용자 정의 QR코드 중앙에 로고로 사용될 이미지입니다. 심벌 마크 로고 패딩 로고 크기 로고 코너 네 번째 눈 하단 모서리에 네 번째 눈을 추가하여 qr 코드에 눈 대칭을 추가합니다. 픽셀 모양 프레임 모양 공 모양 오류 수정 수준 어두운 색 연한 색 하이퍼 OS Xiaomi HyperOS와 같은 스타일 마스크 패턴 이 코드는 스캔이 불가능할 수 있습니다. 모든 장치에서 읽을 수 있도록 모양 매개변수를 변경하세요. 스캔할 수 없음 도구는 더 컴팩트해지기 위해 홈 화면 앱 실행기처럼 보일 것입니다. 런처 모드 선택한 브러시와 스타일로 영역을 채웁니다. 홍수 채우기 스프레이 낙서 스타일의 경로를 그립니다. 정사각형 입자 스프레이 입자는 원형이 아닌 정사각형 모양이 됩니다. 팔레트 도구 이미지에서 기본/재료 팔레트를 생성하거나 다양한 팔레트 형식으로 가져오기/내보내기 팔레트 편집 다양한 형식으로 팔레트 내보내기/가져오기 색상명 팔레트 이름 팔레트 형식 생성된 팔레트를 다른 형식으로 내보내기 현재 팔레트에 새로운 색상을 추가합니다. %1$s 형식은 팔레트 이름 제공을 지원하지 않습니다. Play 스토어 정책으로 인해 이 기능은 현재 빌드에 포함될 수 없습니다. 이 기능에 액세스하려면 대체 소스에서 ImageToolbox를 다운로드하십시오. 아래 GitHub에서 사용 가능한 빌드를 찾을 수 있습니다. Github 페이지 열기 선택한 폴더에 저장하는 대신 원본 파일이 새 파일로 대체됩니다. 숨겨진 워터마크 텍스트가 감지되었습니다. 숨겨진 워터마크 이미지가 감지되었습니다. 이 이미지는 숨겨져 있습니다 생성적 인페인팅 OpenCV에 의존하지 않고 AI 모델을 사용하여 이미지의 개체를 제거할 수 있습니다. 이 기능을 사용하려면 앱이 GitHub에서 필요한 모델(~200MB)을 다운로드합니다. OpenCV에 의존하지 않고 AI 모델을 사용하여 이미지의 개체를 제거할 수 있습니다. 이는 장기간 실행되는 작업일 수 있습니다. 오류 수준 분석 휘도 변화도 평균 거리 복사 이동 감지 유지하다 계수 클립보드 데이터가 너무 큽니다. 데이터가 너무 커서 복사할 수 없습니다. 단순 직조 픽셀화 시차적 픽셀화 교차 픽셀화 마이크로 매크로 픽셀화 궤도 픽셀화 소용돌이 픽셀화 펄스 그리드 픽셀화 핵 픽셀화 방사형 직조 픽셀화 URI \"%1$s\"을(를) 열 수 없습니다. 강설 모드 활성화됨 테두리 프레임 글리치 변형 채널 이동 최대 오프셋 VHS 블록 글리치 블록 크기 CRT 곡률 곡률 크로마 픽셀 용해 최대 드롭 AI 도구 아티팩트 제거 또는 노이즈 제거와 같은 AI 모델을 통해 이미지를 처리하는 다양한 도구 압축, 들쭉날쭉한 선 만화, 방송 압축 일반 압축, 일반 소음 무색 만화 소음 빠른 일반 압축, 일반 노이즈, 애니메이션/만화/애니메이션 도서 스캔 노출 보정 일반 압축, 컬러 이미지에 가장 적합 일반 압축, 회색조 이미지에 가장 적합 일반 압축, 회색조 이미지, 더 강력함 일반 노이즈, 컬러 이미지 일반 노이즈, 컬러 이미지, 더 나은 디테일 일반 노이즈, 회색조 이미지 일반 노이즈, 회색조 이미지, 더 강함 일반 노이즈, 회색조 이미지, 가장 강함 일반 압축 일반 압축 텍스처화, h264 압축 VHS 압축 비표준 압축(cinepak, msvideo1, roq) Bink 압축, 기하학에 더 좋음 Bink 압축, 더 강력해짐 Bink 압축, 부드러움, 디테일 유지 계단현상 제거, 스무딩 스캔한 아트/도면, 약한 압축, 모아레 컬러 밴딩 천천히, 중간색 제거 회색조/bw 이미지용 일반 컬러라이저, 더 나은 결과를 얻으려면 DDColor를 사용하세요. 가장자리 제거 과도한 선명도 제거 느리고 디더링됨 앤티앨리어싱, 일반 아티팩트, CGI KDM003 스캔 처리 중 경량 이미지 향상 모델 압축 아티팩트 제거 압축 아티팩트 제거 원활한 결과로 붕대 제거 하프톤 패턴 처리 디더 패턴 제거 V3 JPEG 아티팩트 제거 V2 H.264 텍스처 향상 VHS 선명화 및 향상 병합 청크 크기 중복 크기 %1$s픽셀을 초과하는 이미지는 분할되어 청크로 처리되며, 이음새가 보이지 않도록 겹쳐서 혼합됩니다. 크기가 크면 저가형 장치가 불안정해질 수 있습니다. 시작하려면 하나를 선택하세요. %1$s 모델을 삭제하시겠습니까? 다시 다운로드해야 합니다. 확인하다 모델 다운로드한 모델 사용 가능한 모델 준비 중 활성 모델 세션을 열지 못했습니다. .onnx/.ort 모델만 가져올 수 있습니다. 수입모델 추가 사용을 위해 사용자 정의 onnx 모델 가져오기, onnx/ort 모델만 허용, 변형과 같은 거의 모든 esrgan 지원 가져온 모델 일반 노이즈, 컬러 이미지 일반 노이즈, 컬러 이미지, 더 강함 일반 노이즈, 컬러 이미지, 가장 강함 디더링 아티팩트와 색상 밴딩을 줄여 부드러운 그라데이션과 단조로운 색상 영역을 개선합니다. 자연스러운 색상을 유지하면서 균형 잡힌 하이라이트로 이미지 밝기와 대비를 향상시킵니다. 디테일을 유지하고 과다 노출을 방지하면서 어두운 이미지를 밝게 합니다. 과도한 색상 톤을 제거하고 보다 중립적이고 자연스러운 색상 균형을 복원합니다. 미세한 디테일과 질감을 유지하는 데 중점을 두고 포아송 기반 노이즈 토닝을 적용합니다. 보다 부드럽고 덜 공격적인 시각적 결과를 위해 부드러운 포아송 노이즈 토닝을 적용합니다. 디테일 보존과 이미지 선명도에 초점을 맞춘 균일한 노이즈 토닝. 미묘한 질감과 부드러운 외관을 위한 부드럽고 균일한 노이즈 토닝. 아티팩트를 다시 칠하고 이미지 일관성을 개선하여 손상되거나 고르지 않은 영역을 복구합니다. 최소한의 성능 비용으로 컬러 밴딩을 제거하는 경량 디밴딩 모델입니다. 선명도 향상을 위해 매우 높은 압축 아티팩트(0-20% 품질)로 이미지를 최적화합니다. 고압축 아티팩트(20-40% 품질)로 이미지를 향상하여 세부 사항을 복원하고 노이즈를 줄입니다. 선명도와 부드러움의 균형을 유지하면서 적당한 압축(40-60% 품질)으로 이미지를 개선합니다. 가벼운 압축(60-80% 품질)으로 이미지를 개선하여 미묘한 디테일과 질감을 향상합니다. 자연스러운 모양과 디테일을 유지하면서 거의 무손실 이미지(80-100% 품질)를 약간 향상시킵니다. 간단하고 빠른 채색, 만화, 이상적이지 않음 이미지 흐림을 약간 줄여 아티팩트 없이 선명도를 향상시킵니다. 장기 실행 작업 이미지 처리 중 처리 매우 낮은 품질의 이미지(0-20%)에서 심한 JPEG 압축 아티팩트를 제거합니다. 압축률이 높은 이미지에서 강한 JPEG 아티팩트를 줄입니다(20-40%). 이미지 세부 정보(40-60%)를 유지하면서 적당한 JPEG 아티팩트를 정리합니다. 상당히 높은 품질의 이미지(60-80%)에서 가벼운 JPEG 아티팩트를 개선합니다. 거의 무손실 이미지(80-100%)에서 사소한 JPEG 아티팩트를 미묘하게 줄입니다. 미세한 디테일과 질감을 향상시켜 큰 아티팩트 없이 인지된 선명도를 향상시킵니다. 처리 완료 처리 실패 속도에 최적화되어 자연스러운 모습을 유지하면서 피부 질감과 디테일을 향상시킵니다. JPEG 압축 아티팩트를 제거하고 압축된 사진의 이미지 품질을 복원합니다. 저조도 조건에서 촬영한 사진의 ISO 노이즈를 줄여 디테일을 보존합니다. 과다 노출 또는 \"점보\" 하이라이트를 수정하고 더 나은 색조 균형을 복원합니다. 회색조 이미지에 자연스러운 색상을 추가하는 가볍고 빠른 색상화 모델입니다. DEJPEG 노이즈 제거 색상화 유물 향상시키다 일본 만화 영화 스캔 고급 일반 이미지용 X4 업스케일러; GPU와 시간을 덜 사용하고 중간 정도의 디블러와 노이즈 제거 기능을 갖춘 작은 모델입니다. 일반 이미지를 위한 X2 업스케일러로 질감과 자연스러운 디테일을 보존합니다. 향상된 질감과 사실적인 결과를 제공하는 일반 이미지용 X4 업스케일러입니다. 애니메이션 이미지에 최적화된 X4 업스케일러; 더욱 선명한 라인과 디테일을 위한 6개의 RRDB 블록. MSE 손실이 포함된 X4 업스케일러는 일반 이미지에 대해 더 부드러운 결과를 생성하고 아티팩트를 줄입니다. 애니메이션 이미지에 최적화된 X4 업스케일러; 더 선명한 디테일과 부드러운 라인을 갖춘 4B32F 변형입니다. 일반 이미지용 X4 UltraSharp V2 모델; 선명함과 선명함을 강조합니다. X4 UltraSharp V2 라이트; 더 빠르고 더 작아지며, GPU 메모리를 덜 사용하면서 디테일을 보존합니다. 빠른 배경 제거를 위한 경량 모델입니다. 균형 잡힌 성능과 정확성. 인물 사진, 사물, 장면과 함께 작동합니다. 대부분의 사용 사례에 권장됩니다. BG 제거 가로 테두리 두께 세로 테두리 두께 %1$s 색상 현재 모델은 청킹을 지원하지 않습니다. 이미지는 원래 크기로 처리되므로 메모리 소모가 많아 저사양 장치에서 문제가 발생할 수 있습니다. 청킹이 비활성화되었습니다. 이미지는 원래 크기로 처리됩니다. 이로 인해 메모리 소비가 많아지고 저사양 장치에 문제가 발생할 수 있지만 추론에서는 더 나은 결과를 얻을 수 있습니다. 청킹 배경 제거를 위한 고정밀 이미지 분할 모델 더 적은 메모리 사용량으로 더 빠른 배경 제거를 위한 U2Net의 경량 버전입니다. 전체 DDColor 모델은 아티팩트를 최소화하면서 일반 이미지에 대한 고품질 색상화를 제공합니다. 모든 색상화 모델 중 최고의 선택입니다. DDColor 훈련된 개인 예술 데이터 세트 비현실적인 색상 아티팩트를 줄여 다양하고 예술적인 색상화 결과를 만들어냅니다. 정확한 배경 제거를 위해 Swin Transformer를 기반으로 한 경량 BiRefNet 모델입니다. 특히 복잡한 물체와 까다로운 배경에서 날카로운 모서리와 뛰어난 세부 묘사 보존 기능을 갖춘 고품질 배경 제거 기능을 제공합니다. 가장자리가 부드러운 정확한 마스크를 생성하는 배경 제거 모델로 일반 객체에 적합하며 적당한 디테일 보존이 가능합니다. 모델이 이미 다운로드됨 모델을 성공적으로 가져왔습니다. 유형 예어 매우 빠름 정상 느린 매우 느림 백분율 계산 최소값은 %1$s입니다. 손가락으로 그려 이미지 왜곡 경사 경도 워프 모드 이동하다 자라다 수축 소용돌이 CW 소용돌이 CCW 페이드 강도 탑 드롭 하단 드롭 드롭 시작 엔드 드롭 다운로드 중 부드러운 모양 더 부드럽고 자연스러운 모양을 위해 표준 둥근 직사각형 대신 초타원을 사용하세요. 모양 유형 자르다 둥근 매끄러운 둥글게 뭉치지 않고 날카로운 모서리 클래식한 둥근 모서리 모양 유형 모서리 크기 다람쥐 우아한 둥근 UI 요소 파일 이름 형식 파일 이름 맨 앞에 배치되는 사용자 정의 텍스트로 프로젝트 이름, 브랜드 또는 개인 태그에 적합합니다. 확장자 없이 원본 파일 이름을 사용하므로 소스 식별을 그대로 유지하는 데 도움이 됩니다. 해상도 변경을 추적하거나 결과를 조정하는 데 유용한 이미지 너비(픽셀)입니다. 픽셀 단위의 이미지 높이. 종횡비 작업이나 내보내기 작업 시 유용합니다. 고유한 파일 이름을 보장하기 위해 임의의 숫자를 생성합니다. 중복에 대한 추가 안전을 위해 더 많은 숫자를 추가하십시오. 일괄 내보내기를 위한 자동 증가 카운터로, 한 세션에서 여러 이미지를 저장할 때 이상적입니다. 적용된 프리셋 이름을 파일 이름에 삽입하여 이미지 처리 방법을 쉽게 기억할 수 있습니다. 처리 중에 사용되는 이미지 크기 조정 모드를 표시하여 크기가 조정되거나 잘리거나 맞는 이미지를 구별하는 데 도움이 됩니다. 파일 이름 끝에 배치된 사용자 정의 텍스트는 _v2, _edited 또는 _final과 같은 버전 관리에 유용합니다. 파일 확장자(png, jpg, webp 등)는 실제 저장된 형식과 자동으로 일치합니다. 완벽한 정렬을 위해 Java 사양에 따라 고유한 형식을 정의할 수 있는 사용자 정의 가능한 타임스탬프입니다. 플링타입 안드로이드 네이티브 iOS 스타일 부드러운 곡선 급정지 탄력 뜨는 팔팔한 울트라 스무스 적응형 접근성 인식 움직임 감소 기준선 비교를 위한 기본 Android 스크롤 물리학 일반적인 사용을 위한 균형 있고 부드러운 스크롤 마찰이 더 높은 iOS와 유사한 스크롤 동작 독특한 스크롤 느낌을 위한 독특한 스플라인 곡선 빠른 정지로 정확한 스크롤 재미있고 반응성이 뛰어난 탄력 있는 스크롤 콘텐츠 탐색을 위한 길고 미끄러지는 스크롤 대화형 UI를 위한 빠르고 반응성이 뛰어난 스크롤 확장된 추진력을 갖춘 프리미엄 부드러운 스크롤링 플링 속도에 따라 물리학을 조정합니다. 시스템 접근성 설정을 존중합니다. 접근성 요구 사항에 맞는 최소한의 동작 기본 라인 다섯 번째 줄마다 더 두꺼운 줄을 추가합니다. 채우기 색상 숨겨진 도구 공유를 위해 숨겨진 도구 컬러 라이브러리 다양한 색상 컬렉션을 살펴보세요 자연스러운 디테일을 유지하면서 이미지의 흐림을 선명하게 하고 제거하여 초점이 맞지 않는 사진을 수정하는 데 이상적입니다. 이전에 크기가 조정된 이미지를 지능적으로 복원하여 손실된 세부 정보와 질감을 복구합니다. 실사 콘텐츠에 최적화되어 압축 아티팩트를 줄이고 영화/TV 쇼 프레임의 미세한 디테일을 향상시킵니다. VHS 품질의 영상을 HD로 변환하여 빈티지 느낌을 유지하면서 테이프 노이즈를 제거하고 해상도를 향상시킵니다. 텍스트가 많은 이미지와 스크린샷에 특화되어 문자를 선명하게 하고 가독성을 높입니다. 다양한 데이터 세트에 대해 훈련된 고급 업스케일링으로 범용 사진 향상에 탁월합니다. 웹 압축 사진에 최적화되어 JPEG 아티팩트를 제거하고 자연스러운 모습을 복원합니다. 더 나은 질감 보존 및 아티팩트 감소 기능을 갖춘 웹 사진용 개선 버전입니다. Dual Aggregation Transformer 기술로 2배 업스케일링하여 선명도와 자연스러운 디테일을 유지합니다. 고급 트랜스포머 아키텍처를 사용한 3배 업스케일링으로 적당한 확장 요구 사항에 이상적입니다. 최첨단 변압기 네트워크를 통한 4배 고품질 업스케일링으로 더 큰 규모에서도 미세한 디테일을 보존합니다. 사진에서 흐림/노이즈 및 흔들림을 제거합니다. 일반적인 용도이지만 사진에 가장 적합합니다. BSRGAN 저하에 최적화된 Swin2SR 변환기를 사용하여 저품질 이미지를 복원합니다. 과도한 압축 아티팩트를 수정하고 4배율로 디테일을 향상시키는 데 적합합니다. BSRGAN 저하에 대해 훈련된 SwinIR 변환기를 사용한 4배 업스케일링. 사진과 복잡한 장면에서 더 선명한 질감과 더 자연스러운 디테일을 위해 GAN을 사용합니다. PDF 병합 여러 PDF 파일을 하나의 문서로 결합 파일 순서 pp. PDF 분할 PDF 문서에서 특정 페이지 추출 PDF 회전 페이지 방향을 영구적으로 수정 페이지 PDF 재정렬 페이지를 드래그 앤 드롭하여 재정렬하세요. 페이지 유지 및 드래그 페이지 번호 문서에 자동으로 번호 매기기 추가 라벨 형식 PDF를 텍스트로(OCR) PDF 문서에서 일반 텍스트 추출 브랜딩 또는 보안을 위한 오버레이 사용자 정의 텍스트 서명 모든 문서에 전자 서명을 추가하세요 서명으로 사용됩니다. PDF 잠금해제 보호된 파일에서 비밀번호를 제거하세요 PDF 보호 강력한 암호화로 문서를 보호하세요 성공 PDF가 잠금 해제되었습니다. 저장하거나 공유할 수 있습니다. PDF 복구 손상되었거나 읽을 수 없는 문서를 수정하려고 시도합니다. 그레이스케일 문서에 포함된 모든 이미지를 회색조로 변환 PDF 압축 더 쉽게 공유할 수 있도록 문서 파일 크기를 최적화하세요. ImageToolbox는 내부 상호 참조 테이블을 다시 작성하고 파일 구조를 처음부터 다시 생성합니다. 이렇게 하면 \\"열 수 없는\\" 많은 파일에 대한 액세스를 복원할 수 있습니다. 이 도구는 모든 문서 이미지를 회색조로 변환합니다. 인쇄 및 파일 크기 축소에 가장 적합 메타데이터 더 나은 개인 정보 보호를 위해 문서 속성 편집 태그 생산자 작가 키워드 창조자 개인 정보 보호 딥 클린 이 문서에 사용 가능한 모든 메타데이터 지우기 페이지 깊은 OCR Tesseract 엔진을 사용하여 문서에서 텍스트를 추출하고 하나의 텍스트 파일에 저장합니다. 모든 페이지를 제거할 수 없습니다 PDF 페이지 제거 PDF 문서에서 특정 페이지 제거 삭제하려면 탭하세요. 수동으로 PDF 자르기 문서 페이지를 원하는 대로 자르기 PDF를 병합 문서 페이지를 래스터링하여 PDF를 수정할 수 없게 만듭니다. 카메라를 시작할 수 없습니다. 권한을 확인하고 다른 앱에서 사용하고 있지 않은지 확인하세요. 이미지 추출 PDF에 포함된 이미지를 원래 해상도로 추출 이 PDF 파일에는 삽입된 이미지가 없습니다. 이 도구는 모든 페이지를 스캔하고 최고 품질의 소스 이미지를 복구합니다. 문서의 원본을 저장하는 데 적합합니다. 서명 그리기 펜 매개변수 자신의 서명을 이미지로 사용하여 문서에 배치 PDF 압축 주어진 간격으로 문서를 분할하고 새 문서를 zip 아카이브로 압축합니다. 간격 PDF 인쇄 사용자 정의 페이지 크기로 인쇄할 문서 준비 시트당 페이지 수 정위 페이지 크기 여유 소프트니 애니메이션과 만화에 최적화되어 있습니다. 자연스러운 색상이 향상되고 아티팩트가 적어 빠른 확장이 가능합니다. Samsung One UI 7 같은 스타일 여기에 기본 수학 기호를 입력하여 원하는 값을 계산하세요(예: (5+5)*10) 수학 표현 최대 %1$s 이미지 선택 알파 형식의 배경색 알파 지원이 포함된 모든 이미지 형식에 배경색을 설정하는 기능을 추가합니다. 이 기능을 비활성화하면 알파가 아닌 형식에만 사용할 수 있습니다. 날짜 시간 유지 날짜 및 시간과 관련된 EXIF ​​태그를 항상 유지하며, EXIF ​​유지 옵션과 독립적으로 작동합니다. 프로젝트 열기 이전에 저장한 Image Toolbox 프로젝트 계속 편집 이미지 도구 상자 프로젝트를 열 수 없습니다 Image Toolbox 프로젝트에 프로젝트 데이터가 없습니다. 이미지 도구 상자 프로젝트가 손상되었습니다 지원되지 않는 Image Toolbox 프로젝트 버전: %1$d 프로젝트 저장 편집 가능한 프로젝트 파일에 레이어, 배경 및 편집 기록 저장 열지 못했습니다. 검색 가능한 PDF에 쓰기 이미지 일괄에서 텍스트를 인식하고 이미지 및 선택 가능한 텍스트 레이어와 함께 검색 가능한 PDF를 저장합니다. 레이어 알파 수평 뒤집기 수직 뒤집기 잠그다 그림자 추가 그림자 색상 텍스트 기하학 더 선명한 스타일을 위해 텍스트를 늘리거나 기울입니다. 스케일 X 기울이기 X 주석 제거 PDF 페이지에서 링크, 주석, 강조 표시, 모양 또는 양식 필드와 같은 선택한 주석 유형을 제거합니다. 하이퍼링크 파일 첨부 윤곽 팝업 우표 모양 텍스트 참고 텍스트 마크업 양식 필드 마크업 알려지지 않은 주석 그룹 해제 구성 가능한 색상과 오프셋을 사용하여 레이어 뒤에 흐림 그림자 추가 ================================================ FILE: core/resources/src/main/res/values-lt/strings.xml ================================================ Vienas redagavimas Pasirinkite įrankį, kurį prisegti Kažkas nutiko ne taip: %1$s. %1$s dydis Įkeliama… Vaizdas per didelis, kad būtų galima peržiūrėti, bet vis tiek bus bandoma išsaugoti. Pasirinkite vaizdą, kad pradėtumėte %1$s plotis %1$s aukštis Kokybė Plėtinys Modifikuokite, keiskite dydį ir redaguokite vieną vaizdą. Atnaujinti Tikrinti naujinimus Jei naudodami tam tikrus įrankius turite neišsaugotų pakeitimų ir bandote juos užverti, bus rodomas patvirtinimo dialogas. Įrankio išėjimo patvirtinimas Puslapių pasirinkimas Pasirinktiniai puslapiai Gaukite naujausius naujinimus, aptarkite problemas ir daugiau. Naujinimų nerasta Naujinimai Ieškokite čia Jei įjungta, naujinimo dialogo langas bus rodomas paleidžiant programėlę. Atnaujinti LUT kolekciją (į eilę bus įtraukti tik nauji), kurią galėsite taikyti atsisiuntę. Aptarkite programėlę ir gaukite kitų naudotojų atsiliepimų. Čia taip pat galite gauti beta versijos naujinimų ir įžvalgų. Tikrinti, ar yra naujinimų Jei įjungta, į naujinimų tikrinimą bus įtrauktos beta programėlės versijos. Šis naujinimų tikrintuvas prisijungs prie „GitHub“, kad patikrintų, ar yra naujas naujinimas. Nieko nerasta pagal jūsų užklausą Pakeiskite vaizdo dydį pagal nurodytą dydį vienetu KB. Keisti dydžio tipą Keisti dydį pagal svorį Didžiausias dydis vienetu KB Ši programa yra visiškai nemokama, bet jei norite paremti projekto kūrimą, galite spustelėti čia. Susisiekite su manimi Aukojimas Paieška Suteikia galimybę ieškoti visų pagrindiniame ekrane esančių įrankių. Junkitės prie mūsų pokalbio, kuriame galite aptarti viską, ką norite, ir taip pat pažvelkite į CI kanalą, kuriame skelbiu beta versijas ir skelbimus. Tai sugrąžins jūsų nustatymus į numatytąsias reikšmes. Atkreipkite dėmesį, kad to negalima anuliuoti be pirmiau minėto atsarginės kopijos failo. Siųsti žurnalus Spustelėkite norint bendrinti programėlės žurnalų failą. Tai gali padėti man pastebėti ir išspręsti problemas. Užverti Nustatyti iš naujo Šaltinio kodas Apie programėlę „Telegram“ pokalbis Atkurti Atkurkite programėlės nustatymus iš anksčiau sugeneruoto failo. Leisti beta versijų CI kanalas Grupė „Image Toolbox“ programėlėje „Telegram“ 🎉 Gaukite pranešimų apie naujas programėlės versijas ir skaitykite skelbimus. Galite susisiekti su manimi naudojant žemiau nurodytas parinktis ir aš bandysiu surasti sprendimą.\n\n(Nepamirškite pridėti žurnalus). Aiškus Lankstus Pasirinkite vaizdą Ar tikrai norite užverti programą? Programa užveriama Likti Nustatyti iš naujo vaizdą Vaizdo pakeitimai bus grąžinti į pradines reikšmes. Reikšmės tinkamai iš naujo nustatytos. Kažkas nutiko Paleisti programą iš naujo Nukopijuota į iškarpinę. Išimtis Redaguoti EXIF Gerai EXIF duomenų nerasta. Pridėti žymę Išsaugoti Valyti Valyti EXIF Atšaukti Visi vaizdo EXIF duomenys bus ištrinti. Šio veiksmo anuliuoti negalima. Išankstiniai nustatymai Apkirpti Išsaugoma Jei dabar išeisite, visi neišsaugoti pakeitimai bus prarasti. Spalvų parinkiklis Pasirinkite spalvą iš vaizdo, nukopijuokite arba bendrinkite. Vaizdas Spalva Spalva nukopijuota Apkirpkite vaizdą iki bet kokių ribų. Versija Išlaikyti EXIF Vaizdai: %d Keisti peržiūrą Šalinti Generuokite spalvų paletės pavyzdį iš nurodyto vaizdo. Generuoti paletę Paletė Nauja versija %1$s Nepalaikomas tipas: %1$s Nepavyko sugeneruoti paletės nurodytam vaizdui. Originalus Išvesties aplankas Numatytoji Pasirinktinis Nenurodyta Įrenginio saugykla Palyginti Palyginkite du pateiktus vaizdus Norėdami pradėti, pasirinkite du vaizdus Pasirinkite vaizdus Nustatymai Naktinis režimas Tamsus Šviesa Sistema Dinaminės spalvos Tinkinimas Leisti vaizdo pinigų Jei įjungta, kai pasirenkate redaguotiną vaizdą, programos spalvos bus pritaikytos šiam vaizdui Kalba Amoled režimas Jei įjungta, nakties režimu paviršių spalva bus visiškai tamsi Spalvų schema Raudona Žalia Mėlyna Įklijuokite tinkamą aRGB spalvos kodą Nėra ką įklijuoti Programos spalvų schemos pakeisti negalima, kol įjungtos dinaminės spalvos Programos tema bus pagrįsta pasirinkta spalva Problemų stebėjimo priemonė Siųskite klaidų ataskaitas ir funkcijų užklausas čia Padėkite išversti Ištaisykite vertimo klaidas arba lokalizuokite projektą kitomis kalbomis Jei įjungta, programos spalvos bus pritaikytos ekrano fono spalvoms Nepavyko išsaugoti %d vaizdo (-ų) El. paštas Pirminis Tretinis Antrinis Krašto storis Paviršius Vertybės Pridėti Leidimas Suteikti Programai reikia prieigos prie jūsų saugyklos, kad galėtų išsaugoti vaizdus, ​​​​kad veiktų, tai būtina. Suteikite leidimą kitame dialogo lange. Programai reikalingas šis leidimas, kad veiktų, suteikite jį rankiniu būdu Išorinė saugykla Monet spalvos FAB lygiavimas Vaizdo priartinimas Dalintis Priešdėlis Failo pavadinimas Jaustukai Pasirinkite jaustukus, kuriuos norite rodyti pagrindiniame ekrane Pridėti failo dydį Jei įjungta, prie išvesties failo pavadinimo pridedamas išsaugoto vaizdo plotis ir aukštis Ištrinkite EXIF Ištrinkite EXIF ​​metaduomenis iš bet kurio vaizdų rinkinio Vaizdo peržiūra Peržiūrėkite bet kokio tipo vaizdus: GIF, SVG ir pan Vaizdo šaltinis Nuotraukų rinkėjas Galerija Failų naršyklė „Android“ modernus nuotraukų rinkiklis, kuris rodomas ekrano apačioje, gali veikti tik 12 ir naujesnėse versijose „Android“. Iškilo problemų gaunant EXIF ​​metaduomenis Paprastas galerijos vaizdų rinkiklis. Tai veiks tik tuo atveju, jei turite programą, kuri teikia medijos rinkimą Norėdami pasirinkti vaizdą, naudokite „GetContent“ tikslą. Veikia visur, tačiau žinoma, kad kai kuriuose įrenginiuose kyla problemų gaunant pasirinktus vaizdus. Tai ne mano kaltė. Pasirinkimų išdėstymas Redaguoti Užsakyti Nustato įrankių tvarką pagrindiniame ekrane Jaustukų skaičius sekaNum originalus failo pavadinimas Pridėkite originalų failo pavadinimą Jei įjungta, į išvesties vaizdo pavadinimą įtraukiamas originalus failo pavadinimas Pakeiskite eilės numerį Jei įjungta, standartinė laiko žyma pakeičiama vaizdo sekos numeriu, jei naudojate paketinį apdorojimą Pradinio failo pavadinimo pridėjimas neveiks, jei pasirinktas nuotraukų rinkiklio vaizdo šaltinis Interneto vaizdo įkėlimas Jei norite, įkelkite bet kurį vaizdą iš interneto, kad galėtumėte peržiūrėti, keisti mastelį, redaguoti ir išsaugoti. Nėra vaizdo Vaizdo nuoroda Užpildykite Tinka Turinio skalė Pakeičia vaizdų dydį iki nurodyto aukščio ir pločio. Vaizdų formato santykis gali keistis. Pakeičia ilgos kraštinės vaizdų dydį iki nurodyto aukščio arba pločio. Visi dydžio skaičiavimai bus atlikti po išsaugojimo. Vaizdų formato santykis bus išsaugotas. Ryškumas Kontrastas Atspalvis Sodrumas Pridėti filtrą Filtruoti Taikyti filtrų grandines vaizdams Filtrai Šviesa Spalvų filtras Alfa Poveikis Baltos spalvos balansas Temperatūra Atspalvis Vienspalvis Gama Akcentai ir šešėliai Akcentai Šešėliai Migla Efektas Atstumas Šlaitas Galąsti Sepija Neigiamas Pasideginti Vibrance Juoda ir balta Crosshatch Tarpai Linijos plotis Sobelio kraštas Suliejimas Pustonis CGA spalvų erdvė Gauso neryškumas Dėžutės suliejimas Dvipusis suliejimas Reljefinis laplakietis Vinjetė Pradėti Pabaiga Kuwahara lyginimas Stack suliejimas Spindulys Skalė Iškraipymas Kampas Sūkurys Išsipūtimas Išsiplėtimas Sferos refrakcija Lūžio rodiklis Stiklo sferos refrakcija Spalvų matrica Neskaidrumas Keisti dydį pagal ribas Pakeiskite vaizdų dydį iki nurodyto aukščio ir pločio, išlaikydami formato santykį Eskizas Slenkstis Kvantavimo lygiai Lygus tonas Toonas Plakatas Ne maksimalus slopinimas Silpnas pikselių įtraukimas Ieškoti Konvoliucija 3x3 RGB filtras Klaidinga spalva Pirmoji spalva Antra spalva Pertvarkyti Greitas suliejimas Suliejimo dydis Sulieti centrą x Suliejimo centras y Mastelio suliejimas Spalvų balansas Skaisčio slenkstis Išjungėte programą „Failai“, suaktyvinkite ją, kad galėtumėte naudotis šia funkcija Lygiosios Pieškite ant paveikslėlio kaip eskizų knygelėje arba pieškite pačiame fone Dažų spalva Dažai alfa Pieškite ant paveikslėlio Pasirinkite paveikslėlį ir nupieškite ką nors ant jo Pieškite fone Pasirinkite fono spalvą ir pieškite ant jos Fono spalva Šifravimas Šifruokite ir iššifruokite bet kurį failą (ne tik vaizdą), pagrįstą įvairiais prieinamais šifravimo algoritmais Pasirinkite failą Šifruoti Iššifruoti Norėdami pradėti, pasirinkite failą Iššifravimas Šifravimas Raktas Failas apdorotas Išsaugokite šį failą savo įrenginyje arba naudokite bendrinimo veiksmą, kad įdėtumėte jį kur norite Savybės Įgyvendinimas Suderinamumas Failų šifravimas slaptažodžiu. Tęsti failai gali būti saugomi pasirinktame kataloge arba bendrinami. Iššifruotus failus taip pat galima atidaryti tiesiogiai. AES-256, GCM režimas, be užpildymo, 12 baitų atsitiktiniai IV pagal numatytuosius nustatymus. Galite pasirinkti reikiamą algoritmą. Raktai naudojami kaip 256 bitų SHA-3 maišos Failo dydis Didžiausią failo dydį riboja „Android“ OS ir turima atmintis, kuri priklauso nuo įrenginio. \nAtkreipkite dėmesį: atmintis nėra saugykla. Atminkite, kad suderinamumas su kita failų šifravimo programine įranga ar paslaugomis negarantuojamas. Šiek tiek kitoks raktų apdorojimas arba šifro konfigūracija gali sukelti nesuderinamumą. Neteisingas slaptažodis arba pasirinktas failas nėra užšifruotas Bandant išsaugoti nurodyto pločio ir aukščio vaizdą, gali atsirasti atminties trūkumo klaida. Darykite tai savo rizika. Talpykla Talpyklos dydis Rasta %1$s Automatinis talpyklos išvalymas Jei įjungta, programos talpykla bus išvalyta paleidžiant programą Sukurti Įrankiai Grupuokite parinktis pagal tipą Grupuoja pagrindinio ekrano parinktis pagal jų tipą, o ne tinkintą sąrašo išdėstymą Negalima pakeisti išdėstymo, kai įjungtas parinkčių grupavimas Redaguoti ekrano kopiją Antrinis pritaikymas Ekrano kopija Atsarginis variantas Praleisti Kopijuoti Įrašymas %1$s režimu gali būti nestabilus, nes tai yra be nuostolių formatas Jei pasirinkote išankstinį nustatymą 125, vaizdas bus išsaugotas kaip 125% pradinio vaizdo dydžio. Jei pasirinksite iš anksto nustatytą 50, vaizdas bus išsaugotas 50% dydžiu Iš anksto nustatytas čia nustato išvesties failo %, t. y. jei pasirinksite iš anksto nustatytą 50 5 MB vaizde, tada išsaugoję gausite 2,5 MB vaizdą Atsitiktinai nustatyti failo pavadinimą Jei įjungta, išvesties failo pavadinimas bus visiškai atsitiktinis Išsaugota %1$s aplanke pavadinimu %2$s Išsaugota %1$s aplanke Pasėlių kaukė Kraštinių santykis Naudokite šį kaukės tipą, kad sukurtumėte kaukę iš pateikto vaizdo, atkreipkite dėmesį, kad jis TURI turėti alfa kanalą Atsarginė kopija ir atkūrimas Atsarginė kopija Kurkite atsarginę programos nustatymų kopiją į failą Sugadintas failas arba ne atsarginė kopija Nustatymai sėkmingai atkurti Ištrinti Ketinate ištrinti pasirinktą spalvų schemą. Šios operacijos anuliuoti negalima Ištrinti schemą Šriftas Tekstas Šrifto mastelis Numatytoji Naudojant didelius šrifto mastelius, gali kilti vartotojo sąsajos trikdžių ir problemų, kurios nebus ištaisytos. Naudokite atsargiai. Aa Ąą Bb Cc Čč Dd Ee Ęę Ėė Ff Gg Hh Ii Įį Yy Jj Kk Ll Mm Nn Oo Pp Rr Ss Šš Tt Uu Ųų Ūū Vv Zz Žž 0123456789 !? Emocijos Maistas ir gėrimai Gamta ir gyvūnai Objektai Simboliai Įgalinti jaustukus Kelionės ir vietos Veikla Fono valiklis Pašalinkite foną iš vaizdo piešdami arba naudokite parinktį Automatinis Apkarpyti vaizdą Originalūs vaizdo metaduomenys bus saugomi Permatomos erdvės aplink vaizdą bus apkarpytos Automatiškai ištrinti foną Atkurti vaizdą Ištrynimo režimas Ištrinti foną Atkurti foną Suliejimo spindulys Pipete Piešimo režimas Sukurti problemą Oi… Kažkas ne taip. Galite parašyti man naudodami toliau pateiktas parinktis ir aš pabandysiu rasti sprendimą Keisti dydį ir konvertuoti Pakeiskite pateiktų vaizdų dydį arba konvertuokite juos į kitus formatus. Čia taip pat galima redaguoti EXIF ​​metaduomenis, jei pasirenkamas vienas vaizdas. Maksimalus spalvų skaičius Tai leidžia programai automatiškai rinkti gedimų ataskaitas Analizė Leisti rinkti anoniminę programos naudojimo statistiką Šiuo metu %1$s formatas leidžia skaityti tik EXIF ​​metaduomenis „Android“. Išsaugotas vaizdas iš viso neturės metaduomenų. Pastangos %1$s reikšmė reiškia greitą suspaudimą, dėl kurio failas yra gana didelis. %2$s reiškia lėtesnį glaudinimą, todėl failas bus mažesnis. Palauk Išsaugojimas beveik baigtas. Atšaukus dabar, reikės dar kartą išsaugoti. Nubrėžkite rodykles Jei įjungta, piešimo kelias bus rodomas kaip rodyklė Šepetėlio minkštumas Nuotraukos bus apkarpytos centre iki įvesto dydžio. Drobė bus išplėsta naudojant nurodytą fono spalvą, jei vaizdas bus mažesnis nei įvesti matmenys. Vaizdo susiuvimas Sujunkite pateiktus vaizdus, ​​​​kad gautumėte vieną didelį Pasirinkite bent 2 vaizdus Išvesties vaizdo skalė Vaizdo orientacija Horizontaliai Vertikalus Pakeiskite mažus vaizdus į didelius Jei įjungta, mažų vaizdų mastelis bus padidintas iki didžiausio Vaizdų tvarka Reguliarus Sulieti kraštus Nupiešia neryškius kraštus po pradiniu vaizdu, kad užpildytų tarpus aplink jį, o ne viena spalva, jei įjungta Pikseliacija Patobulintas pikseliavimas Brūkšnio pikseliavimas Patobulintas deimantinis pikseliavimas Deimantinis pikseliavimas Apskritimo pikseliavimas Patobulintas apskritimo pikseliavimas Pakeisti spalvą Tolerancija Spalva, kurią reikia pakeisti Tikslinė spalva Pašalinti spalva Pašalinti spalvą Perkoduoti Pikselių dydis Užrakinti piešimo orientaciją Jei įjungta piešimo režimu, ekranas nesisuks Paletės stilius Toninė dėmė Neutralus Gyvybingas Išraiškingas Vaivorykštė Vaisių salotos Ištikimybė Turinys Numatytasis paletės stilius, leidžia pritaikyti visas keturias spalvas, kitos leidžia nustatyti tik rakto spalvą Stilius, kuris yra šiek tiek chromatiškesnis nei vienspalvis Garsi tema, spalvingumas pirminei paletei maksimalus, kitiems padidintas Žaisminga tema – šaltinio spalvos atspalvis temoje nerodomas Vienspalvė tema, spalvos yra grynai juoda / balta / pilka Schema, kuri įdeda šaltinio spalvą į Scheme.primaryContainer Schema, kuri labai panaši į turinio schemą Dėmesio Išblukę kraštai Išjungta Abu Invertuoti spalvas Jei įjungta, temos spalvas pakeičia neigiamomis PDF įrankiai Dirbkite su PDF failais: Peržiūrėkite, konvertuokite į vaizdų paketą arba sukurkite vieną iš pateiktų paveikslėlių Peržiūrėti PDF PDF į vaizdus Vaizdai į pdf Paprasta PDF peržiūra Konvertuokite PDF į vaizdus nurodytu išvesties formatu Supakuokite pateiktus vaizdus į išvesties PDF failą Kaukės filtras Taikykite filtrų grandines nurodytose užmaskuotose srityse, kiekviena kaukės sritis gali nustatyti savo filtrų rinkinį Kaukės Pridėti kaukę Kaukė %d Kaukės spalva Kaukės peržiūra Nupiešta filtro kaukė bus atvaizduota, kad būtų rodomas apytikslis rezultatas Atvirkštinis užpildymo tipas Jei įjungta, visos neužmaskuotos sritys bus filtruojamos vietoj numatytosios elgsenos Ketinate ištrinti pasirinktą filtro kaukę. Šios operacijos anuliuoti negalima Ištrinti kaukę Pilnas filtras Taikyti bet kokias filtrų grandines pateiktiems vaizdams arba vienam vaizdui Pradėti centras Pabaiga Paprasti variantai Paryškintuvas Neoninis Rašiklis Privatumo suliejimas Nubrėžkite pusiau permatomus paryškintus žymeklio kelius Pridėkite šiek tiek švytinčio efekto savo piešiniams Numatytasis, paprasčiausias – tik spalva Sulieja vaizdą po nupieštu keliu, kad apsaugotų viską, ką norite paslėpti Panašus į privatumo suliejimą, bet vietoj suliejimo sukuria pikselius Konteineriai Nubrėžkite šešėlį už konteinerių Slankikliai Jungikliai FAB Mygtukai Nubrėžkite šešėlį už slankiklių Nubrėžkite šešėlį už jungiklių Nubrėžkite šešėlį už slankiųjų veiksmų mygtukų Nubrėžkite šešėlį už mygtukų Programų juostos Nubrėžkite šešėlį už programos juostų Vertė diapazone %1$s - %2$s Automatinis pasukimas Leidžia naudoti ribinį langelį vaizdo orientacijai Nubrėžti kelią Dvigubos linijos rodyklė Nemokamas piešimas Dviguba rodyklė Linijinė rodyklė Rodyklė Linija Nubrėžia kelią kaip įvesties reikšmę Nubrėžia kelią nuo pradžios taško iki pabaigos taško kaip liniją Nubrėžia rodyklę nuo pradžios taško iki pabaigos taško kaip liniją Nubrėžia rodyklę iš nurodyto kelio Nubrėžia dvigubą rodyklę nuo pradžios taško iki pabaigos taško kaip liniją Nubrėžia dvigubą rodyklę iš nurodyto kelio Kontūrinis ovalas Nurodyta rekt Ovalus Rekt Brėžia tiesiai nuo pradžios taško iki pabaigos taško Piešia ovalą nuo pradžios iki pabaigos taško Nubrėžia ovalą nuo pradžios iki pabaigos taško Nubrėžia tiesią kontūrą nuo pradžios taško iki pabaigos taško Lasso Nubrėžia uždarą užpildytą kelią pagal nurodytą kelią Nemokama Horizontalus tinklelis Vertikalus tinklelis Dygsnio režimas Eilučių skaičius Stulpelių skaičius Nerastas \"%1$s\" katalogas, perjungėme jį į numatytąjį, išsaugokite failą dar kartą Iškarpinė Automatinis kaištis Automatiškai prideda išsaugotą vaizdą į mainų sritį, jei įjungta Vibracija Vibracijos stiprumas Norėdami perrašyti failus, turite naudoti \"Explorer\" vaizdo šaltinį, pabandykite perrinkti vaizdus, ​​mes pakeitėme vaizdo šaltinį į reikiamą Perrašyti failus Originalus failas bus pakeistas nauju, o ne išsaugoti pasirinktame aplanke, šios parinkties vaizdo šaltinis turi būti \"Explorer\" arba GetContent, perjungus tai bus nustatyta automatiškai Tuščia Priesaga Mastelio režimas Bilinear Catmull Bikubinis Jis Atsiskyrėlis Lanczos Mitchell Artimiausias Spline Pagrindinis Numatytoji reikšmė Linijinė (arba bilinijinė, dviejų matmenų) interpoliacija paprastai tinka vaizdo dydžiui keisti, tačiau sukelia tam tikrą nepageidaujamą detalių sušvelninimą ir vis tiek gali būti šiek tiek dantyta. Geresni mastelio keitimo metodai apima Lanczos resampling ir Mitchell-Netravali filtrus Vienas iš paprastesnių būdų padidinti dydį, pakeičiant kiekvieną pikselį tos pačios spalvos pikselių skaičiumi Paprasčiausias „Android“ mastelio keitimo režimas, naudojamas beveik visose programose Valdymo taškų rinkinio sklandaus interpoliavimo ir atrinkimo metodas, dažniausiai naudojamas kompiuterinėje grafikoje, kad būtų sukurtos lygios kreivės Langų funkcija, dažnai taikoma apdorojant signalą, siekiant sumažinti spektrinį nutekėjimą ir pagerinti dažnių analizės tikslumą siaurinant signalo kraštus. Matematinės interpoliacijos metodas, kuris naudoja vertes ir išvestines kreivės segmento galiniuose taškuose, kad būtų sukurta lygi ir ištisinė kreivė Pakartotinio atrankos metodas, užtikrinantis aukštos kokybės interpoliaciją, pikselių reikšmėms taikant svertinę sinc funkciją Pakartotinio atrankos metodas, kai naudojamas konvoliucijos filtras su reguliuojamais parametrais, kad būtų pasiekta pusiausvyra tarp ryškumo ir pakeitimo mastelio vaizde. Naudoja dalimis apibrėžtas daugianario funkcijas, kad sklandžiai interpoliuotų ir aproksimuotų kreivę arba paviršių, užtikrinant lankstų ir nuolatinį formos vaizdavimą Tik klipas Išsaugojimas saugykloje nebus vykdomas, o vaizdas bus bandomas įdėti tik į mainų sritį Teptukas atkurs foną, o ne ištrins OCR (teksto atpažinimas) Atpažinti tekstą iš pateikto vaizdo, palaikoma daugiau nei 120 kalbų Nuotraukoje nėra teksto arba programa jo nerado \"Tikslumas: %1$s\" Atpažinimo tipas Greitai Standartinis Geriausia Nėra duomenų Kad Tesseract OCR tinkamai veiktų, į įrenginį reikia atsisiųsti papildomus mokymo duomenis (%1$s).\nAr norite atsisiųsti %2$s duomenis? Atsisiųsti Nėra ryšio, patikrinkite ir bandykite dar kartą, kad atsisiųstumėte traukinių modelius Atsisiųstos kalbos Galimos kalbos Segmentavimo režimas Naudokite Pixel Switch Naudojamas į Google Pixel panašus jungiklis Perrašytas failas pavadinimu %1$s pradinėje paskirties vietoje Didintuvas Piešimo režimais įgalina didintuvą piršto viršuje, kad būtų lengviau pasiekti Priversti pradinę vertę Priverčia iš pradžių patikrinti exif valdiklį Leisti kelias kalbas Skaidrė Side By Side Perjungti bakstelėjimą Skaidrumas Įvertinkite programą Įvertink Ši programa yra visiškai nemokama, jei norite, kad ji taptų didesnė, pažymėkite projektą „Github“ 😄 Tik orientacija ir scenarijaus aptikimas Automatinė orientacija ir scenarijaus aptikimas Tik auto Auto Viena kolona Vieno bloko vertikalus tekstas Vienas blokas Viena linija Vienas žodis Apskritimo žodis Vienas simbolis Retas tekstas Retai teksto orientacija ir scenarijaus aptikimas Neapdorota linija Ar norite ištrinti kalbos \"%1$s\" OCR mokymo duomenis visiems atpažinimo tipams, ar tik pasirinktam vienam (%2$s)? Dabartinė Visi Gradiento kūrėjas Sukurkite nurodyto išvesties dydžio gradientą naudodami tinkintas spalvas ir išvaizdos tipą Linijinis Radialinis Šluoti Gradiento tipas Centras X Centras Y Plytelių režimas Pasikartojo Veidrodis Spaustuvas Lipdukas Spalvos sustojimai Pridėti spalvą Savybės Ryškumo užtikrinimas Ekranas Gradiento perdanga Sukurkite bet kokį pateiktų vaizdų viršaus gradientą Transformacijos Fotoaparatas Fotografuokite fotoaparatu. Atminkite, kad iš šio vaizdo šaltinio galima gauti tik vieną vaizdą Vandens ženklai Uždenkite paveikslėlius tinkinamais teksto / vaizdo vandens ženklais Pakartokite vandens ženklą Tam tikroje padėtyje pakartoja vandens ženklą virš vaizdo, o ne vieną Poslinkis X Poslinkis Y Vandens ženklo tipas Šis paveikslėlis bus naudojamas kaip vandens ženklų piešinys Teksto spalva Perdangos režimas GIF įrankiai Konvertuokite vaizdus į GIF paveikslėlį arba ištraukite rėmelius iš nurodyto GIF vaizdo GIF į vaizdus Konvertuokite GIF failą į nuotraukų paketą Konvertuoti vaizdų paketą į GIF failą Vaizdai į GIF Norėdami pradėti, pasirinkite GIF vaizdą Naudokite pirmojo kadro dydį Nurodytą dydį pakeiskite pirmojo rėmo matmenimis Pakartokite skaičių Kadro delsa mln FPS Naudokite Lasso Naudoja Lasso kaip piešimo režimu, kad atliktų trynimą Originalaus vaizdo peržiūra Alpha Konfeti Konfeti bus rodomi taupant, dalijantis ir atliekant kitus pagrindinius veiksmus Saugus režimas Paslepia programų turinį naujausiose programose. Jo negalima užfiksuoti ar įrašyti. Išeiti Jei dabar paliksite peržiūrą, turėsite dar kartą pridėti vaizdų Dingimas Kvantifikatorius Pilka skalė „Bayer Two By Two Dithering“. „Bayer Three By Three Dathering“. Bayer Four By Four dithering Bayer Eight By Eight dithering Floydas Steinbergas Jarviso teisėja Ninke Dithering Sierra Dithering Dviejų eilių Sierra dithering Sierra Lite Dithering Atkinsono diteringas Stucki Dithering Burkes Dithering False Floyd Steinberg dithering Skirstymas iš kairės į dešinę Atsitiktinis suskaidymas Paprastas slenkstis Sigma Erdvinė sigma Vidutinis suliejimas B Spline Naudoja dalimis apibrėžtas dvikubines daugianario funkcijas, kad sklandžiai interpoliuotų ir aproksimuotų kreivę arba paviršių, lankstų ir ištisinį formos vaizdavimą Native Stack Blur Tilt Shift Gedimas Suma Sėkla Anaglifas Triukšmas Pikselių rūšiavimas Maišyti Patobulintas Glitch Kanalo poslinkis X Kanalo poslinkis Y Korupcijos dydis Korupcijos pamaina X Korupcijos pamaina Y Tent Blur Šoninis išnykimas Šoninė Į viršų Apačia Jėga Erode Anizotropinė difuzija Difuzija Laidumas Horizontalus vėjo stabdys Greitas dvišalis suliejimas Poisson Blur Logaritminis tonų atvaizdavimas ACES Filmic Tone Mapping Iškristalizuokite Potėpio spalva Fraktalinis stiklas Amplitudė Marmuras Turbulencija Aliejus Vandens efektas Dydis X dažnis Dažnis Y Amplitudė X Amplitudė Y Perlino iškraipymas ACES Hill Tone Mapping Hable Filmic Tone Mapping Heji-Burgess Tonų žemėlapis Greitis Dehaze Omega Spalvų matrica 4x4 Spalvų matrica 3x3 Paprasti efektai Polaroidas Tritanomalija Deuteranomalija Protanomalija Vintažinis Browno „Coda Chrome“. Naktinis matymas Šiltas Kietas Tritanopija Deutaronotopija Protanopija Achromatomalija Achromatopsija Grūdai Neryškus Pastelinė Oranžinė migla Rožinė svajonė Auksinė valanda Karšta vasara Violetinė migla Saulėtekis Spalvingas sūkurys Minkšta pavasario lemputė Rudens tonai Levandų svajonė Kiberpankas Šviesus limonadas Spektrinė ugnis Naktinė magija Fantastinis peizažas Spalvų sprogimas Elektrinis gradientas Karamelinė tamsa Futuristinis gradientas Žalia saulė Vaivorykštės pasaulis Giliai violetinė Kosmoso portalas Raudonasis sūkurys Skaitmeninis kodas Bokeh Programų juostos jaustukai keisis atsitiktinai Atsitiktinės emocijos Negalite naudoti atsitiktinių jaustukų, kai jaustukai išjungti Negalite pasirinkti jaustukų, kai įjungti atsitiktiniai jaustukai Senas tv Maišyti suliejimą Mėgstamiausias Mėgstamiausių filtrų dar nepridėta Vaizdo formatas Prideda konteinerį su pasirinkta forma po piktogramomis Piktogramos forma Drago Aldridžas Atkarpa Tu pabundi Mobiusas Perėjimas Peak Spalvos anomalija Vaizdai perrašyti pradinėje paskirties vietoje Negalima pakeisti vaizdo formato, kai įjungta failų perrašymo parinktis Jaustukai kaip spalvų schema Naudoja jaustukų pagrindinę spalvą kaip programos spalvų schemą, o ne rankiniu būdu apibrėžtą Sukuria Material You paletę iš vaizdo Tamsios Spalvos Naudoja naktinio režimo spalvų schemą, o ne šviesų variantą Nukopijuokite kaip „Jetpack Compose“ kodą Žiedo suliejimas Kryžminis suliejimas Apskritimo suliejimas Žvaigždžių suliejimas Linijinis „Tilt-Shift“. Žymos, kurias reikia pašalinti APNG įrankiai Konvertuokite vaizdus į APNG paveikslėlį arba ištraukite rėmelius iš nurodyto APNG vaizdo APNG vaizdams Konvertuokite APNG failą į nuotraukų paketą Konvertuoti vaizdų paketą į APNG failą Vaizdai į APNG Norėdami pradėti, pasirinkite APNG vaizdą Judesio suliejimas Zip Sukurkite ZIP failą iš pateiktų failų ar vaizdų Vilkimo rankenos plotis Konfeti tipas Šventinis Sprogti Lietus Kampai JXL įrankiai Atlikite JXL ~ JPEG perkodavimą neprarandant kokybės arba konvertuokite GIF / APNG į JXL animaciją JXL į JPEG Atlikite be nuostolių perkodavimą iš JXL į JPEG Atlikite be nuostolių perkodavimą iš JPEG į JXL JPEG į JXL Norėdami pradėti, pasirinkite JXL vaizdą Greitas Gauso suliejimas 2D Greitas Gauso suliejimas 3D Greitas Gauso suliejimas 4D Automobilių Velykos Leidžia programai automatiškai įklijuoti mainų srities duomenis, kad jie būtų rodomi pagrindiniame ekrane ir galėsite juos apdoroti Harmonizavimo spalva Harmonizavimo lygis Lanczos Bessel Pakartotinis atrankos metodas, užtikrinantis aukštos kokybės interpoliaciją pikselių reikšmėms taikant Beselio (jinc) funkciją GIF į JXL Konvertuokite GIF vaizdus į JXL animacinius paveikslėlius APNG į JXL Konvertuokite APNG vaizdus į JXL animacinius paveikslėlius JXL į vaizdus Konvertuokite JXL animaciją į nuotraukų paketą Vaizdai į JXL Konvertuokite paveikslėlių paketą į JXL animaciją Elgesys Praleisti failų pasirinkimą Failų rinkiklis bus iškart parodytas pasirinktame ekrane, jei tai įmanoma Generuokite peržiūras Įgalina peržiūros generavimą, tai gali padėti išvengti kai kurių įrenginių gedimų, taip pat išjungia kai kurias redagavimo funkcijas naudojant vieną redagavimo parinktį Prarastos kompresijos Naudoja nuostolingą glaudinimą, kad sumažintų failo dydį, o ne be nuostolių Suspaudimo tipas Valdo vaizdo dekodavimo greitį, tai turėtų padėti greičiau atidaryti gautą vaizdą, %1$s reikšmė reiškia lėčiausią dekodavimą, o %2$s - greičiausią, šis nustatymas gali padidinti išvesties vaizdo dydį Rūšiavimas Data Data (atvirkščiai) Vardas Vardas (atvirkščias) Kanalų konfigūracija Šiandien vakar Įterptasis rinkiklis Vaizdo įrankių rinkinio vaizdų rinkiklis Jokių leidimų Prašymas Pasirinkite kelias laikmenas Pasirinkite vieną laikmeną Pasirinkti Bandykite dar kartą Rodyti nustatymus kraštovaizdyje Jei tai išjungta, gulsčiojo režimo nustatymai bus atidaryti viršutinėje programos juostoje esančiame mygtuke, kaip visada, vietoj nuolatinės matomos parinkties Viso ekrano nustatymai Įjunkite jį ir nustatymų puslapis visada bus atidarytas kaip viso ekrano režimas, o ne slankiojantis stalčiaus lapas Jungiklio tipas Sukurti „Jetpack Compose“ medžiaga, kurią perjungiate Medžiaga, kurią perjungiate Maks Inkaro dydžio keitimas Pikselis Sklandžiai Jungiklis, pagrįstas \"Fluent\" dizaino sistema Cupertino Jungiklis, pagrįstas \"Cupertino\" dizaino sistema Vaizdai į SVG Atsekti pateiktus vaizdus į SVG vaizdus Naudokite pavyzdinę paletę Kvantifikavimo paletė bus atrinkta, jei ši parinktis įjungta Kelias praleisti Nerekomenduojama naudoti šio įrankio dideliems vaizdams sekti be mastelio sumažinimo, nes tai gali sukelti strigtį ir pailginti apdorojimo laiką Sumažintas vaizdas Prieš apdorojimą vaizdas bus sumažintas iki mažesnių matmenų, todėl įrankis veiks greičiau ir saugiau Minimalus spalvų santykis Linijų slenkstis Kvadratinis slenkstis Koordinatės apvalinimo tolerancija Kelio skalė Iš naujo nustatyti savybes Visoms ypatybėms bus nustatytos numatytosios vertės, atkreipkite dėmesį, kad šio veiksmo anuliuoti negalima Išsamus Numatytasis linijos plotis Variklio režimas Palikimas LSTM tinklas Legacy &amp; LSTM Konvertuoti Konvertuoti vaizdų paketus į nurodytą formatą Pridėti naują aplanką Bitai vienam mėginiui Suspaudimas Fotometrinis aiškinimas Pavyzdžiai pikseliui Plokštuminė konfigūracija Y Cb Cr sub mėginių ėmimas Y Cb Cr padėties nustatymas X rezoliucija Y rezoliucija Rezoliucijos vienetas Juostelių poslinkiai Eilutės per juostelę Juostelės baitų skaičius JPEG mainų formatas JPEG mainų formato ilgis Perdavimo funkcija Baltasis taškas Pirminiai chromatai Y Cb Cr koeficientai Nuoroda Black White Data Laikas Vaizdo aprašymas Padaryti Modelis Programinė įranga Menininkas Autorių teisės Exif versija Flashpix versija Spalvų erdvė Gama Pixel X Dimension Pixel Y matmuo Suspausti bitai pikselyje Kūrėjo pastaba Vartotojo komentaras Susijęs garso failas Data Laikas Originalas Data Laikas Suskaitmenintas Poslinkio laikas Offset Time Original Poslinkio laikas suskaitmenintas Subsekundės laikas Sub Sec Time Original Subsekundės laikas suskaitmenintas Kontakto trukmė F Skaičius Ekspozicijos programa Spektrinis jautrumas Fotografinis jautrumas Oecf Jautrumo tipas Standartinis išvesties jautrumas Rekomenduojamas ekspozicijos indeksas ISO greitis ISO greitis Platuma yyyy ISO greitis Platuma zzz Užrakto greičio vertė Diafragmos vertė Ryškumo vertė Ekspozicijos šališkumo vertė Maksimali diafragmos vertė Dalyko atstumas Matavimo režimas Blykstė Dalyko sritis Židinio nuotolis Blykstės energija Erdvinio dažnio atsakas X židinio plokštumos skiriamoji geba Židinio plokštumos Y skiriamoji geba Židinio plokštumos skyros vienetas Dalyko vieta Ekspozicijos indeksas Jutimo metodas Failo šaltinis CFA modelis Pateikta pagal užsakymą Ekspozicijos režimas Baltos spalvos balansas Skaitmeninis priartinimo koeficientas 35 mm židinio nuotolio juosta Scenos fiksavimo tipas Įgykite kontrolę Kontrastas Sodrumas Ryškumas Įrenginio nustatymų aprašymas Dalyko atstumo diapazonas Vaizdo unikalus ID Kameros savininko vardas Korpuso serijos numeris Objektyvo specifikacija Objektyvo gaminys Objektyvo modelis Objektyvo serijos numeris GPS versijos ID GPS platumos nuorod GPS platuma GPS ilguma nuorod GPS ilguma GPS aukštis nuorod GPS aukštis GPS laiko žyma GPS palydovai GPS būsena GPS matavimo režimas GPS DOP GPS greičio nuorod GPS greitis GPS sekimo nuoroda GPS sekimas GPS Img kryptis nuorod GPS Img kryptis GPS žemėlapio data GPS paskirties platumos nuorod GPS paskirties platuma GPS paskirties ilgumos nuorod GPS paskirties ilguma GPS tiksl. guolio Nr GPS tikslus guolis GPS paskirties atstumo nuoroda GPS paskirties atstumas GPS apdorojimo metodas GPS srities informacija GPS datos antspaudas GPS diferencialas GPS H padėties nustatymo klaida Sąveikos indeksas DNG versija Numatytasis apkarpymo dydis Vaizdo peržiūros pradžia Peržiūrėti vaizdo ilgį Aspekto rėmelis Jutiklio apatinė kraštinė Jutiklio kairioji sienelė Jutiklio dešinė kraštinė Jutiklio viršutinė sienelė ISO Nubrėžkite tekstą kelyje su nurodytu šriftu ir spalva Šrifto dydis Vandens ženklo dydis Pakartokite tekstą Dabartinis tekstas bus kartojamas iki kelio pabaigos, o ne vieną kartą Brūkšnelio dydis Naudokite pasirinktą vaizdą, kad nubrėžtumėte jį nurodytu keliu Šis vaizdas bus naudojamas kaip pasikartojantis nubrėžto kelio įvedimas Nubrėžia nubrėžtą trikampį nuo pradžios iki pabaigos taško Nubrėžia nubrėžtą trikampį nuo pradžios iki pabaigos taško Nubrėžtas trikampis Trikampis Brėžia daugiakampį nuo pradžios taško iki pabaigos taško Daugiakampis Kontūrinis daugiakampis Nubrėžia daugiakampį nuo pradžios iki pabaigos taško Viršūnės Nubrėžkite reguliarųjį daugiakampį Nubrėžkite daugiakampį, kuris bus taisyklingas, o ne laisvos formos Nubrėžia žvaigždę nuo pradžios iki pabaigos taško Žvaigždė Nubrėžta žvaigždė Nubrėžia žvaigždę nuo pradžios iki pabaigos taško Vidinio spindulio santykis Nupieškite įprastą žvaigždę Nubrėžkite žvaigždę, kuri bus įprasta, o ne laisvos formos Antialias Įgalina antialiasing, kad būtų išvengta aštrių kraštų Atidarykite Redaguoti, o ne peržiūrą Pasirinkus vaizdą, kurį norite atidaryti (peržiūrėti) „ImageToolbox“, bus atidarytas redagavimo pasirinkimo lapas, o ne peržiūra Dokumentų skaitytuvas Nuskaitykite dokumentus ir kurkite PDF arba atskirkite iš jų vaizdus Spustelėkite norėdami pradėti nuskaitymą Pradėti nuskaitymą Išsaugoti kaip pdf Bendrinti kaip pdf Toliau pateiktos parinktys skirtos vaizdams išsaugoti, o ne PDF Išlyginkite HSV histogramą Išlyginti histogramą Įveskite procentą Leisti įvesti teksto lauke Įgalina teksto lauką už išankstinių nustatymų pasirinkimo, kad juos būtų galima įvesti iškart Spalvų erdvės skalė Linijinis Išlyginkite histogramos pikseliavimą Tinklelio dydis X Tinklelio dydis Y Išlyginti histogramą prisitaikanti Išlyginkite histogramos adaptyvųjį LUV Išlyginti histogramos adaptyviąją LAB CLAHE CLAHE LAB CLAHE LUV Apkarpyti iki turinio Rėmo spalva Spalva Ignoruoti Šablonas Nepridėta jokių šablonų filtrų Sukurti naują Nuskaitytas QR kodas nėra tinkamas filtro šablonas Nuskaitykite QR kodą Pasirinktame faile nėra filtro šablono duomenų Sukurti šabloną Šablono pavadinimas Šis vaizdas bus naudojamas šiam filtro šablonui peržiūrėti Šablonų filtras Kaip QR kodo vaizdas Kaip failas Išsaugoti kaip failą Išsaugoti kaip QR kodo vaizdą Ištrinti šabloną Ketinate ištrinti pasirinktą šablono filtrą. Šios operacijos anuliuoti negalima Pridėtas filtro šablonas pavadinimu \"%1$s\" (%2$s) Filtro peržiūra QR ir brūkšninis kodas Nuskaitykite QR kodą ir gaukite jo turinį arba įklijuokite eilutę, kad sukurtumėte naują Kodo turinys Nuskaitykite bet kokį brūkšninį kodą, kad pakeistumėte turinį lauke, arba įveskite ką nors, kad sugeneruotumėte naują pasirinkto tipo brūkšninį kodą QR aprašymas Min Nustatymuose suteikite fotoaparatui leidimą nuskaityti QR kodą Nustatymuose suteikite fotoaparato leidimą nuskaityti dokumentų skaitytuvą Kubinis B-Spline Hammingas Hanningas Blackman Welch Keturkampis Gauso Sfinksas Bartlettas Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaizeris Bartlettas-Jis Dėžutė Bohmanas Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kubinė interpoliacija užtikrina sklandesnį mastelio keitimą, atsižvelgiant į artimiausius 16 pikselių, o tai suteikia geresnių rezultatų nei dvilinijinis Naudoja dalimis apibrėžtas daugianario funkcijas, kad sklandžiai interpoliuotų ir aproksimuotų kreivę arba paviršių, lankstų ir ištisinį formos vaizdavimą Lango funkcija, naudojama spektriniam nutekėjimui sumažinti siaurinant signalo kraštus, naudinga apdorojant signalą Hann lango variantas, dažniausiai naudojamas spektriniam nutekėjimui sumažinti signalų apdorojimo programose Lango funkcija, užtikrinanti gerą dažnio skiriamąją gebą sumažindama spektrinį nuotėkį, dažnai naudojama signalų apdorojimui Lango funkcija, skirta užtikrinti gerą dažnio skiriamąją gebą su mažesniu spektro nuotėkiu, dažnai naudojama signalų apdorojimo programose Metodas, kuriame interpoliacijai naudojama kvadratinė funkcija, užtikrinanti sklandžius ir nuolatinius rezultatus Interpoliacijos metodas, kuriame taikoma Gauso funkcija, naudinga norint išlyginti ir sumažinti vaizdų triukšmą Pažangus pakartotinio atrankos metodas, užtikrinantis aukštos kokybės interpoliaciją su minimaliais artefaktais Trikampio lango funkcija, naudojama apdorojant signalą, siekiant sumažinti spektrinį nuotėkį Aukštos kokybės interpoliacijos metodas, optimizuotas natūraliam vaizdo dydžio keitimui, ryškumui ir glotnumui subalansuoti Ryškesnis Robidoux metodo variantas, optimizuotas aiškaus vaizdo dydžio keitimui Spline pagrįstas interpoliacijos metodas, užtikrinantis sklandžius rezultatus naudojant 16 bakstelėjimų filtrą Spline pagrįstas interpoliacijos metodas, užtikrinantis sklandžius rezultatus naudojant 36 bakstelėjimų filtrą Spline pagrįstas interpoliacijos metodas, užtikrinantis sklandžius rezultatus naudojant 64 bakstelėjimų filtrą Interpoliacijos metodas, kuriame naudojamas Kaiser langas, leidžiantis gerai valdyti pagrindinės skilties pločio ir šoninės skilties lygio kompromisą Hibridinė lango funkcija, jungianti Bartlett ir Hann langus, naudojama signalų apdorojimo spektriniam nutekėjimui sumažinti Paprastas pakartotinio atrankos metodas, kuris naudoja artimiausių pikselių reikšmių vidurkį, todėl dažnai atrodo blokuotas Lango funkcija, naudojama spektriniam nutekėjimui sumažinti, užtikrinanti gerą dažnio skiriamąją gebą signalų apdorojimo programose Pakartotinio mėginių ėmimo metodas, kuriame naudojamas 2 skilčių Lanczos filtras aukštos kokybės interpoliacijai su minimaliais artefaktais Pakartotinio mėginių ėmimo metodas, kuriame naudojamas 3 skilčių Lanczos filtras aukštos kokybės interpoliacijai su minimaliais artefaktais Pakartotinio mėginių ėmimo metodas, kuriame naudojamas 4 skilčių Lanczos filtras aukštos kokybės interpoliacijai su minimaliais artefaktais Lanczos 2 filtro variantas, kuriame naudojama jinc funkcija, užtikrinanti aukštos kokybės interpoliaciją su minimaliais artefaktais Lanczos 3 filtro variantas, kuriame naudojama jinc funkcija, užtikrinanti aukštos kokybės interpoliaciją su minimaliais artefaktais Lanczos 4 filtro variantas, kuriame naudojama jinc funkcija, užtikrinanti aukštos kokybės interpoliaciją su minimaliais artefaktais Hanningas EWA Hanningo filtro elipsinio svertinio vidurkio (EWA) variantas sklandžiam interpoliavimui ir pakartotiniam mėginių ėmimui Robidoux EWA Elipsinis svertinis vidurkis (EWA) Robidoux filtro variantas, skirtas aukštos kokybės pakartotiniam mėginių ėmimui Blackman EVE Elipsinis svertinis vidurkis (EWA) Blackman filtro variantas, skirtas sumažinti skambėjimo artefaktus Keturkampis EWA Kvadrinio filtro elipsinio svertinio vidurkio (EWA) variantas sklandžiam interpoliavimui Robidoux Sharp EWA Elipsinis svertinis vidurkis (EWA) Robidoux Sharp filtro variantas, kad rezultatai būtų ryškesni Lanczos 3 Jinc EWA Lanczos 3 Jinc filtro elipsinio svertinio vidurkio (EWA) variantas, skirtas aukštos kokybės pakartotiniam atrankai su sumažintu slapyvardžiu Ženšenis Resampling filtras, sukurtas aukštos kokybės vaizdo apdorojimui su geru ryškumo ir lygumo balansu Ženšenis EWA Ženšenio filtro elipsinio svertinio vidurkio (EWA) variantas pagerina vaizdo kokybę Lanczos Sharp EWA Lanczos Sharp filtro elipsinio svertinio vidurkio (EWA) variantas, kad būtų pasiekti ryškūs rezultatai su minimaliais artefaktais Lanczos 4 aštriausias EWA Lanczos 4 Sharpest filtro elipsinio svertinio vidurkio (EWA) variantas itin ryškiems vaizdams pakartotinai atrinkti Lanczos Soft EWA Lanczos Soft filtro elipsinio svertinio vidurkio (EWA) variantas, skirtas sklandžiau atrinkti vaizdą Haasn Soft Haasn sukurtas pakartotinio atrankos filtras sklandžiam ir be artefaktų vaizdo mastelio keitimui Formato konvertavimas Konvertuokite vaizdų paketą iš vieno formato į kitą Atsisakyti amžinai Vaizdų krovimas Sudėkite vaizdus vieną ant kito naudodami pasirinktus maišymo režimus Pridėti vaizdą Dėžės skaičiuojamos Clahe HSL Clahe HSV Išlyginkite histogramos adaptyvųjį HSL Išlyginkite histogramos adaptyvųjį HSV Krašto režimas Klipas Apvyniokite Spalvų aklumas Pasirinkite režimą, kad pritaikytumėte temos spalvas pasirinktam daltonizmo variantui Sunku atskirti raudonus ir žalius atspalvius Sunku atskirti žalius ir raudonus atspalvius Sunku atskirti mėlynus ir geltonus atspalvius Nesugebėjimas suvokti raudonų atspalvių Nesugebėjimas suvokti žalių atspalvių Nesugebėjimas suvokti mėlynų atspalvių Sumažintas jautrumas visoms spalvoms Visiškas daltonizmas, matantis tik pilkus atspalvius Nenaudokite Color Blind schemos Spalvos bus tiksliai tokios, kaip nustatytos temoje Sigmoidinis Lagranžas 2 2 eilės Lagrange interpoliacijos filtras, tinkantis aukštos kokybės vaizdo mastelio keitimui su sklandžiais perėjimais Lagranžas 3 3 eilės Lagrange interpoliacijos filtras, užtikrinantis didesnį tikslumą ir sklandesnius vaizdo mastelio keitimo rezultatus Lanczos 6 Lanczos resampling filtras su didesne eile 6, užtikrinantis ryškesnį ir tikslesnį vaizdo mastelį Lanczos 6 Jinc „Lanczos 6“ filtro variantas, naudojant „Jinc“ funkciją, kad pagerintų vaizdo atrankos kokybę Linijinis langelio suliejimas Linijinis palapinės suliejimas Linijinis Gauso langelio suliejimas Linijinis dėklo suliejimas Gauso langelio suliejimas Linijinis greitas Gauso suliejimas Kitas Linijinis greitas Gauso suliejimas Linijinis Gauso suliejimas Pasirinkite vieną filtrą, kad naudotumėte jį kaip dažus Pakeiskite filtrą Toliau pasirinkite filtrą, kad galėtumėte naudoti jį kaip teptuką piešinyje TIFF suspaudimo schema Žemas poli Smėlio tapyba Vaizdo padalijimas Padalinkite vieną vaizdą į eilutes arba stulpelius Tinka riboms Sujunkite apkarpymo dydžio keitimo režimą su šiuo parametru, kad pasiektumėte pageidaujamą elgseną (apkarpyti / pritaikyti formatui) Kalbos sėkmingai importuotos Atsarginiai OCR modeliai Importuoti Eksportuoti Padėtis centras Viršuje kairėje Viršuje dešinėje Apačioje kairėje Apačioje dešinėje Viršutinis centras Centras dešinėje Apatinis centras Centras kairėje Tikslinis vaizdas Paletės perkėlimas Patobulintas aliejus Paprastas senas televizorius HDR Gotemas Paprastas eskizas Minkštas švytėjimas Spalvotas plakatas Tri tonas Trečia spalva Clahe Oklab Klara Olch Clahe Jzazbz Polka Dot Sugrupuotas 2x2 dithering Sugrupuotas 4x4 dithering Klasterizuotas 8x8 Dithering Yililoma dithering Mėgstamiausių parinkčių nepasirinkta, pridėkite jas įrankių puslapyje Pridėti parankinius Papildomas Analogiškas Triadinis Suskaidytas papildomas Tetradicinis Kvadratas Analogiškas + papildomas Spalvų įrankiai Maišykite, sukurkite tonus, generuokite atspalvius ir dar daugiau Spalvų harmonijos Spalvų šešėliavimas Variacija Atspalviai Tonai Atspalviai Spalvų maišymas Spalvos informacija Pasirinkta spalva Spalva Maišyti Negalima naudoti pinigų, kai įjungtos dinaminės spalvos 512x512 2D LUT Tikslinis LUT vaizdas Mėgėjas Ponia etiketas Minkšta elegancija Minkštos elegancijos variantas Paletės perkėlimo variantas 3D LUT Tikslinis 3D LUT failas (.cube / .CUBE) LUT Baliklio aplinkkelis Žvakių šviesa Drop Blues Švelnus Gintaras Rudens spalvos Filmo atsargos 50 Miglota naktis Kodak Gaukite neutralų LUT vaizdą Pirmiausia naudokite savo mėgstamą nuotraukų redagavimo programą ir pritaikykite filtrą neutraliam LUT, kurį galite gauti čia. Kad tai veiktų tinkamai, kiekviena pikselio spalva neturi priklausyti nuo kitų pikselių (pvz., suliejimas neveiks). Kai būsite pasiruošę, naudokite naują LUT vaizdą kaip 512*512 LUT filtro įvestį Pop menas Celiuliozė Kava Auksinis miškas Žalsvos spalvos Retro geltona Nuorodų peržiūra Įgalina nuorodų peržiūrą tose vietose, kur galite gauti tekstą (QRCcode, OCR ir kt.) Nuorodos ICO failus galima išsaugoti tik maksimaliu 256 x 256 dydžiu GIF į WEBP Konvertuokite GIF vaizdus į WEBP animuotus paveikslėlius WEBP įrankiai Konvertuokite vaizdus į WEBP animuotą paveikslėlį arba ištraukite kadrus iš pateiktos WEBP animacijos WEBP į vaizdus Konvertuoti WEBP failą į nuotraukų paketą Konvertuoti vaizdų paketą į WEBP failą Vaizdai į WEBP Norėdami pradėti, pasirinkite WEBP vaizdą Nėra visiškos prieigos prie failų Leiskite visiems failams pasiekti JXL, QOI ir kitus vaizdus, ​​kurie „Android“ neatpažįstami kaip vaizdai. Be leidimo „Image Toolbox“ negali rodyti tų vaizdų Numatytoji piešimo spalva Numatytasis piešimo kelio režimas Pridėti laiko žymą Įgalina laiko žymos pridėjimą prie išvesties failo pavadinimo Suformatuota laiko žyma Įgalinkite laiko žymos formatavimą išvesties failo pavadinime, o ne pagrindiniame milis Įgalinkite laiko žymes, kad pasirinktumėte jų formatą Vienkartinė išsaugojimo vieta Peržiūrėkite ir redaguokite vienkartines išsaugojimo vietas, kurias galite naudoti ilgai paspaudę išsaugojimo mygtuką dažniausiai visose parinktyse Neseniai naudotas Pritaikykite vaizdą pagal nurodytus matmenis ir pritaikykite fono suliejimą arba spalvą Įrankių išdėstymas Grupuokite įrankius pagal tipą Grupuoja įrankius pagrindiniame ekrane pagal jų tipą, o ne tinkintą sąrašo išdėstymą Numatytosios reikšmės Sistemos juostų matomumas Rodyti sistemos juostas perbraukiant Įgalinamas perbraukimas, kad būtų rodomos sistemos juostos, jei jos paslėptos Auto Slėpti viską Rodyti viską Slėpti navigacijos juostą Slėpti būsenos juostą Triukšmo generavimas Sukurkite įvairius garsus, pvz., Perlin ar kitų tipų garsus Dažnis Triukšmo tipas Sukimosi tipas Fraktalų tipas oktavos Lacuariškumas Pelnas Pasverta jėga Ping Pong Jėga Atstumo funkcija Grąžinimo tipas Drebulys Domeno deformacija Lygiavimas Pasirinktinis failo pavadinimas Pasirinkite vietą ir failo pavadinimą, kurie bus naudojami dabartiniam vaizdui išsaugoti Išsaugota aplanke pasirinktu pavadinimu Koliažų kūrėjas Kurkite koliažus iš iki 20 vaizdų Koliažo tipas Laikykite vaizdą, kad pakeistumėte, perkeltumėte ir priartintumėte, kad sureguliuotumėte padėtį Išjungti sukimąsi Neleidžia pasukti vaizdų dviem pirštų gestais Įgalinti pririšimą prie kraštinių Perkėlus arba padidinus mastelį, vaizdai užsifiksuos, kad užpildytų rėmelio kraštus Histograma RGB arba Brightness vaizdo histograma, padėsianti koreguoti Šis vaizdas bus naudojamas RGB ir šviesumo histogramoms generuoti Tesseact parinktys Taikykite kai kuriuos tesseract variklio įvesties kintamuosius Pasirinktinės parinktys Parinktys turi būti įvedamos pagal šį šabloną: \"--{parinkties_pavadinimas} {value}\" Automatinis apkarpymas Nemokami kampai Apkarpykite vaizdą pagal daugiakampį, tai taip pat pataiso perspektyvą Priversti taškus į vaizdo ribas Taškai nebus ribojami vaizdo ribomis, tai naudinga norint tiksliau koreguoti perspektyvą Kaukė Užpildykite turinį pagal nubrėžtą kelią Gydymo vieta Naudokite Circle Kernel Atidarymas Uždarymas Morfologinis gradientas Top Hat Juoda skrybėlė Tonų kreivės Iš naujo nustatyti kreives Kreivės bus grąžintos į numatytąją vertę Linijos stilius Tarpo dydis Brūkšniuotas Brūkšninis taškas Antspauduotas Zigzagas Nubrėžia punktyrinę liniją palei nubrėžtą kelią su nurodytu tarpo dydžiu Nubrėžia tašką ir punktyrinę liniją palei nurodytą kelią Tiesiog numatytosios tiesios linijos Nubrėžia pasirinktas figūras išilgai kelio su nurodytais tarpais Nubrėžia banguotu zigzagu palei taką Zigzago santykis Sukurti nuorodą Įrankis bus pridėtas prie paleidimo priemonės pagrindinio ekrano kaip spartusis klavišas, naudokite jį kartu su nustatymu \"Praleisti failų pasirinkimą\", kad pasiektumėte reikiamą elgesį Nekraukite rėmelių Leidžia išmesti ankstesnius rėmelius, todėl jie nebus sukrauti vienas ant kito Crossfade Rėmeliai bus perbraukti vienas į kitą Crossfade kadrų skaičius Slenkstis vienas Antras slenkstis Canny Veidrodis 101 Patobulintas priartinimo suliejimas Paprastas laplasietis Sobel Simple Pagalbinis tinklelis Virš piešimo srities rodomas atraminis tinklelis, kad būtų lengviau atlikti tikslias manipuliacijas Tinklelio spalva Ląstelės plotis Ląstelės aukštis Kompaktiški selektoriai Kai kurie pasirinkimo valdikliai naudos kompaktišką išdėstymą, kad užimtų mažiau vietos Nustatymuose suteikite fotoaparato leidimą fotografuoti Išdėstymas Pagrindinio ekrano pavadinimas Pastovios normos koeficientas (CRF) %1$s reikšmė reiškia lėtą glaudinimą, dėl kurio failo dydis yra palyginti mažas. %2$s reiškia greitesnį suspaudimą, todėl failas yra didelis. Lut biblioteka Atsisiųskite LUT kolekciją, kurią galėsite pritaikyti atsisiuntę Pakeiskite numatytąją filtrų vaizdo peržiūrą Vaizdo peržiūra Slėpti Rodyti Slankiklio tipas Išgalvotas 2 medžiaga Prabangiai atrodantis slankiklis. Tai numatytoji parinktis 2 medžiagos slankiklis Slankiklis „Material You“. Taikyti Centriniai dialogo mygtukai Jei įmanoma, dialogo langų mygtukai bus išdėstyti centre, o ne kairėje Atvirojo kodo licencijos Peržiūrėkite šioje programoje naudojamų atvirojo kodo bibliotekų licencijas Plotas Atranka naudojant pikselių ploto santykį. Tai gali būti tinkamiausias vaizdų naikinimo metodas, nes jis duoda rezultatus be muaro. Bet kai vaizdas padidinamas, jis panašus į \"Arčiausiai\" metodą. Įgalinti Tonemapping Įveskite % Negalite pasiekti svetainės, pabandykite naudoti VPN arba patikrinkite, ar teisingas URL Žymėjimo sluoksniai Sluoksnių režimas su galimybe laisvai dėti vaizdus, ​​tekstą ir kt Redaguoti sluoksnį Sluoksniai ant vaizdo Naudokite vaizdą kaip foną ir pridėkite skirtingus sluoksnius ant jo Sluoksniai fone Tas pats, kaip ir pirmasis variantas, bet su spalva vietoj paveikslėlio Beta Greitųjų nustatymų pusė Redaguodami vaizdus pasirinktoje pusėje pridėkite slankiąją juostelę, kurią spustelėjus atsidarys greiti nustatymai Išvalyti pasirinkimą Nustatymų grupė \"%1$s\" bus sutraukta pagal numatytuosius nustatymus Nustatymų grupė \"%1$s\" bus išplėsta pagal numatytuosius nustatymus „Base64“ įrankiai Iššifruokite „Base64“ eilutę į vaizdą arba užkoduokite vaizdą į „Base64“ formatą Bazė64 Pateikta vertė nėra tinkama Base64 eilutė Negalima nukopijuoti tuščios arba netinkamos Base64 eilutės Įklijuoti pagrindą64 Kopijuoti bazę64 Įkelkite vaizdą, kad nukopijuotumėte arba išsaugotumėte Base64 eilutę. Jei turite pačią eilutę, galite ją įklijuoti aukščiau, kad gautumėte vaizdą Išsaugoti bazę64 Bendrinti bazę64 Parinktys Veiksmai Importavimo bazė64 „Base64“ veiksmai Pridėti kontūrą Pridėkite kontūrą aplink tekstą su nurodyta spalva ir pločiu Kontūro spalva Kontūro dydis Rotacija Kontrolinė suma kaip failo pavadinimas Išvesties vaizdai turės pavadinimą, atitinkantį jų duomenų kontrolinę sumą Nemokama programinė įranga (partneris) Daugiau naudingos programinės įrangos Android programų partnerių kanale Algoritmas Kontrolinės sumos įrankiai Palyginkite kontrolines sumas, apskaičiuokite maišą arba kurkite šešioliktaines eilutes iš failų naudodami skirtingus maišos algoritmus Apskaičiuokite Teksto maiša Kontrolinė suma Pasirinkite failą, kad apskaičiuotumėte jo kontrolinę sumą pagal pasirinktą algoritmą Įveskite tekstą, kad apskaičiuotumėte jo kontrolinę sumą pagal pasirinktą algoritmą Šaltinio kontrolinė suma Kontrolinė suma Palyginti Rungtynės! Skirtumas Kontrolinės sumos yra lygios, tai gali būti saugu Kontrolinės sumos nėra lygios, failas gali būti nesaugus! Tinklelio gradientai Peržiūrėkite internetinę tinklelio gradientų kolekciją Galima importuoti tik TTF ir OTF šriftus Importuoti šriftą (TTF / OTF) Eksportuoti šriftus Importuoti šriftai Išsaugant bandymą įvyko klaida, pabandykite pakeisti išvesties aplanką Failo pavadinimas nenustatytas Nėra Redaguoti EXIF Pakeiskite vieno vaizdo metaduomenis be pakartotinio suspaudimo Palieskite, jei norite redaguoti galimas žymas Keisti lipduką Pritaikyti plotį Tinkamo aukščio Palyginti partiją Pasirinkite failą / failus, kad apskaičiuotumėte jo kontrolinę sumą pagal pasirinktą algoritmą Pasirinkite failus Pasirinkite katalogą Galvos ilgio skalė Antspaudas Laiko žyma Formato šablonas Paminkštinimas Vaizdo pjovimas Iškirpti vaizdo dalį ir sujungti kairę (gali būti atvirkštinė) vertikaliomis arba horizontaliomis linijomis Vertikali sukimosi linija Horizontali sukimosi linija Atvirkštinis pasirinkimas Vertikali nupjauta dalis bus palikta, o ne sujungti dalis aplink nupjautą vietą Horizontali nupjauta dalis bus palikta, o ne sujungti dalis aplink nupjautą vietą Tinklelio gradientų kolekcija Sukurkite tinklelio gradientą naudodami pasirinktinį mazgų kiekį ir skiriamąją gebą Tinklelio gradiento perdanga Sukurkite pateiktų vaizdų viršaus tinklinį gradientą Taškų pritaikymas Tinklelio dydis X rezoliucija Rezoliucija Y Rezoliucija Pixel By Pixel Paryškinkite spalvą Pikselių palyginimo tipas Nuskaityti brūkšninį kodą Aukščio santykis Brūkšninio kodo tipas Įgyvendinti nespalvotą Brūkšninio kodo vaizdas bus visiškai nespalvotas ir nenuspalvintas pagal programos temą Nuskaitykite bet kokį brūkšninį kodą (QR, EAN, AZTEC ir kt.) ir gaukite jo turinį arba įklijuokite tekstą, kad sukurtumėte naują Brūkšninio kodo nerasta Sugeneruotas brūkšninis kodas bus čia Garso viršeliai Iš garso failų ištraukite albumo viršelio vaizdus, ​​palaikomi dažniausiai naudojami formatai Norėdami pradėti, pasirinkite garso įrašą Pasirinkite garso įrašą Viršelių nerasta Oi… Kažkas ne taip Rašyti į failą Ištraukite tekstą iš vaizdų paketo ir išsaugokite jį viename tekstiniame faile Rašyti į metaduomenis Ištraukite tekstą iš kiekvieno vaizdo ir įdėkite jį į atitinkamų nuotraukų EXIF ​​informaciją Nematomas režimas Naudokite steganografiją, kad sukurtumėte akims nematomus vandens ženklus savo vaizdų baitų viduje Naudokite LSB Bus naudojamas LSB (Less Significant Bit) steganografijos metodas, kitu atveju FD (Frequency Domain) Automatinis raudonų akių pašalinimas Slaptažodis Atrakinti PDF yra apsaugotas Operacija beveik baigta. Norint atšaukti dabar, reikės iš naujo paleisti Pakeitimo data Pakeitimo data (atvirkščiai) Dydis Dydis (atvirkštinis) MIME tipas MIME tipas (atvirkštinis) Pratęsimas Plėtinys (atvirkštinis) Įtraukimo data Pridėjimo data (atvirkščiai) Iš kairės į dešinę Iš dešinės į kairę Iš viršaus į apačią Iš apačios į viršų Skystas stiklas Jungiklis, pagrįstas neseniai paskelbta IOS 26 ir jos skysto stiklo dizaino sistema Toliau pasirinkite vaizdą arba įklijuokite / importuokite „Base64“ duomenis Norėdami pradėti, įveskite paveikslėlio nuorodą Įklijuoti nuorodą Kaleidoskopas Antrinis kampas Šonai Kanalų mišinys Mėlyna žalia Raudona mėlyna Žalia raudona Į raudoną Į žalią Į mėlyną Žydra spalva Magenta Geltona Spalva Pustonis Kontūras Lygiai Užskaita Voronojaus kristalizacija Forma Ištempti Atsitiktinumas Išblukinti Difuzinis DoG Antrasis spindulys Išlyginti Švytėjimas Sūkurys ir žiupsnelis Pointilizuoti Krašto spalva Poliarinės koordinatės Tiesiai į poliarinį Poliarinis į tiesiąją Apverskite ratu Sumažinti Triukšmą Paprastas soliarizavimas Pynimas X tarpas Y tarpas X plotis Y Plotis Sukti Guminis antspaudas Ištepti Tankis Sumaišykite Sferinis objektyvo iškraipymas Refrakcijos rodiklis Arc Skleidimo kampas Sparkle Spinduliai ASCII Gradientas Marija Ruduo Kaulas Jet Žiema Vandenynas Vasara Pavasaris Šaunus variantas HSV Rožinė Karšta Žodis Magma Inferno Plazma Viridis Piliečiai Prieblanda Twilight Shifted Perspektyva Auto Deskew Leisti apkarpyti Pasėlis arba perspektyva Absoliutus Turbo Giliai žalia Objektyvo korekcija Tikslinio objektyvo profilio failas JSON formatu Atsisiųskite paruoštus objektyvo profilius Dalies procentai Eksportuoti kaip JSON Nukopijuokite eilutę su paletės duomenimis kaip JSON atvaizdą Siūlių drožyba Pagrindinis ekranas Užrakinimo ekranas Įmontuotas Užsklandos eksportas Atnaujinti Gaukite dabartinius namų, užrakto ir įmontuotus fono paveikslėlius Suteikite prieigą prie visų failų, to reikia norint gauti fono paveikslėlius Nepakanka tvarkyti išorinės saugyklos leidimo, turite leisti prieigą prie vaizdų, būtinai pasirinkite \"Leisti viską\" Pridėti išankstinį nustatymą prie failo pavadinimo Prie vaizdo failo pavadinimo pridedamas priesaga su pasirinktu išankstiniu nustatymu Pridėti vaizdo mastelio režimą prie failo pavadinimo Prie vaizdo failo pavadinimo prideda priesagą su pasirinktu vaizdo mastelio režimu Ascii str Konvertuokite paveikslėlį į ASCII tekstą, kuris atrodys kaip vaizdas Paramos Tam, kad kai kuriais atvejais būtų geresnis rezultatas, vaizdui taikomas neigiamas filtras Apdorojama ekrano kopija Ekrano kopija neužfiksuota, bandykite dar kartą Išsaugojimas praleistas %1$s failai praleisti Leisti praleisti, jei didesnis Kai kuriems įrankiams bus leidžiama praleisti vaizdų įrašymą, jei gautas failo dydis būtų didesnis nei originalas Kalendoriaus įvykis Susisiekite El. paštas Vieta Telefonas Tekstas SMS URL Wi-Fi Atviras tinklas N/A SSID Telefonas Pranešimas Adresas Tema Kūnas Vardas Organizacija Pavadinimas Telefonai Laiškai URL Adresai Santrauka Aprašymas Vieta Organizatorius Pradžios data Pabaigos data Būsena Platuma Ilguma Sukurti brūkšninį kodą Redaguoti brūkšninį kodą Wi-Fi konfigūracija Saugumas Pasirinkite kontaktą Nustatymuose suteikite kontaktams leidimą automatiškai pildyti naudojant pasirinktą kontaktą Kontaktinė informacija Vardas Vidurinis vardas Pavardė Tarimas Pridėti telefoną Pridėti el Pridėti adresą Svetainė Pridėti svetainę Suformatuotas pavadinimas Šis vaizdas bus naudojamas virš brūkšninio kodo įdėti Kodo pritaikymas Šis vaizdas bus naudojamas kaip logotipas QR kodo centre Logotipas Logotipo pamušalas Logotipo dydis Logotipo kampai Ketvirtoji akis Prideda akių simetriją prie qr kodo pridedant ketvirtą akį apatiniame galo kampe Pikselio forma Rėmo forma Rutulio forma Klaidų taisymo lygis Tamsi spalva Šviesios spalvos Hiper OS Xiaomi HyperOS stilius Kaukės raštas Šio kodo gali nepavykti nuskaityti, pakeiskite išvaizdos parametrus, kad jį būtų galima nuskaityti visuose įrenginiuose Nenuskaitoma Įrankiai atrodys kaip pradinio ekrano programų paleidimo priemonė, kad būtų kompaktiškesni Paleidimo režimas Užpildo sritį pasirinktu teptuku ir stiliumi Potvynių užpildymas Purkšti Piešia graffito stiliaus kelią Kvadratinės dalelės Purškimo dalelės bus kvadrato formos, o ne apskritimų Paletės įrankiai Sukurkite pagrindinę / medžiagą savo paletę iš vaizdo arba importuokite / eksportuokite į skirtingus paletės formatus Redaguoti paletę Eksportuoti / importuoti paletę įvairiais formatais Spalvos pavadinimas Paletės pavadinimas Paletės formatas Eksportuokite sugeneruotą paletę į skirtingus formatus Prideda naują spalvą į dabartinę paletę %1$s formatas nepalaiko paletės pavadinimo Dėl „Play“ parduotuvės politikos šios funkcijos negalima įtraukti į dabartinę versiją. Norėdami pasiekti šią funkciją, atsisiųskite ImageToolbox iš alternatyvaus šaltinio. Galimas versijas „GitHub“ rasite toliau. Atidarykite „Github“ puslapį Originalus failas bus pakeistas nauju, o ne išsaugoti pasirinktame aplanke Aptiktas paslėptas vandens ženklo tekstas Aptiktas paslėptas vandens ženklo vaizdas Šis vaizdas buvo paslėptas Generatyvus tapymas Leidžia pašalinti objektus vaizde naudojant AI modelį, nepasikliaujant OpenCV. Norėdami naudotis šia funkcija, programa atsisiųs reikiamą modelį (~200 MB) iš GitHub Leidžia pašalinti objektus vaizde naudojant AI modelį, nepasikliaujant OpenCV. Tai gali būti ilgai trunkanti operacija Klaidos lygio analizė Šviesumo gradientas Vidutinis atstumas Kopijuoti judėjimo aptikimą Išlaikyti Koeficientas Iškarpinės duomenys per dideli Duomenys per dideli, kad juos būtų galima kopijuoti Paprastas pynimo pikseliavimas Laipsniškas pikseliavimas Kryžminis pikselis Mikro makro pikseliavimas Orbitos pikseliavimas Sūkurio pikseliavimas Impulsinio tinklelio pikseliavimas Branduolio pikseliavimas Radialinio pynimo pikseliavimas Negalima atidaryti uri \"%1$s\" Sniego režimas Įjungta Kraštinis rėmelis Glitch variantas Kanalo poslinkis Maksimalus poslinkis VHS Blokuoti Glitch Bloko dydis CRT kreivumas Kreivumas Chroma Pikselių tirpimas Maksimalus lašas AI įrankiai Įvairūs įrankiai vaizdams apdoroti naudojant AI modelius, pvz., artefaktų pašalinimą arba triukšmo mažinimą Suspaudimas, dantytos linijos Animaciniai filmai, transliacijų suspaudimas Bendras suspaudimas, bendras triukšmas Bespalvis animacinis triukšmas Greitas, bendras suspaudimas, bendras triukšmas, animacija/komiksai/anime Knygų skenavimas Ekspozicijos korekcija Geriausias bendras suspaudimas, spalvoti vaizdai Geriausiai tinka bendram suspaudimui, pilkos spalvos atvaizdams Bendras suspaudimas, pilkos spalvos vaizdai, stipresni Bendras triukšmas, spalvoti vaizdai Bendras triukšmas, spalvoti vaizdai, geresnės detalės Bendras triukšmas, pilkos spalvos vaizdai Bendras triukšmas, pilkų atspalvių vaizdai, stipresni Bendras triukšmas, pilkos spalvos vaizdai, stipriausi Bendras suspaudimas Bendras suspaudimas Tekstūravimas, h264 suspaudimas VHS suspaudimas Nestandartinis glaudinimas (cinepak, msvideo1, roq) Bink suspaudimas, geriau geometrijoje Bink suspaudimas, stipresnis Bink suspaudimas, minkštas, išlaiko detales Laiptų efekto pašalinimas, išlyginimas Nuskaityti meno kūriniai/piešiniai, švelnus suspaudimas, muare Spalvų juostos Lėtas, pašalinantis pustonius Bendras pilkos spalvos / juodos spalvos vaizdų spalvinimas, geresniems rezultatams naudokite DDColor Kraštų pašalinimas Pašalina perdėtą galandimą Lėtas, niūrus Anti-aliasing, bendrieji artefaktai, CGI KDM003 nuskaito apdorojimas Lengvas vaizdo pagerinimo modelis Suspaudimo artefaktų pašalinimas Suspaudimo artefaktų pašalinimas Tvarsčio pašalinimas su sklandžiais rezultatais Pustonių raštų apdorojimas Dydžio rašto pašalinimas V3 JPEG artefaktų pašalinimas V2 H.264 tekstūros pagerinimas VHS ryškinimas ir tobulinimas Sujungimas Dalies dydis Persidengimo dydis Didesni nei %1$s px vaizdai bus supjaustomi ir apdorojami gabalais, juos perdengiant, kad nebūtų matomų siūlių. Dideli dydžiai gali sukelti nestabilumą naudojant žemos klasės įrenginius Pasirinkite vieną, kad pradėtumėte Ar norite ištrinti %1$s modelį? Turėsite jį atsisiųsti dar kartą Patvirtinti Modeliai Parsisiųsti modeliai Galimi modeliai Ruošiamasi Aktyvus modelis Nepavyko atidaryti seanso Galima importuoti tik .onnx/.ort modelius Importo modelis Importuokite pasirinktinį onnx modelį tolesniam naudojimui, priimami tik onnx / ort modeliai, palaiko beveik visus esrgan tipo variantus Importuoti modeliai Bendras triukšmas, spalvoti vaizdai Bendras triukšmas, spalvoti vaizdai, stipresni Bendras triukšmas, spalvoti vaizdai, stipriausi Sumažina artefaktus ir spalvų juostas, pagerina lygius gradientus ir lygias spalvų sritis. Padidina vaizdo ryškumą ir kontrastą subalansuotais akcentais, išsaugant natūralias spalvas. Paryškina tamsius vaizdus, ​​išsaugant detales ir išvengiant per didelio eksponavimo. Pašalina pernelyg didelį spalvų atspalvį ir atkuria neutralesnį bei natūralesnį spalvų balansą. Taiko Puasono pagrindu sukurtą triukšmo tonizavimą, pabrėžiant smulkių detalių ir tekstūrų išsaugojimą. Taiko švelnų Puasono triukšmo atspalvį, kad vaizdo rezultatai būtų lygesni ir ne tokie agresyvūs. Vienodas triukšmo atspalvis, skirtas detalių išsaugojimui ir vaizdo aiškumui. Švelnus vienodas triukšmo tonizavimas subtiliai tekstūrai ir lygiai išvaizdai. Taiso pažeistas ar nelygias vietas perdažydamas artefaktus ir pagerindamas vaizdo nuoseklumą. Lengvas juostų pašalinimo modelis, kuris pašalina spalvų juostas su minimaliomis eksploatacinėmis sąnaudomis. Optimizuoja vaizdus su labai dideliu suspaudimo artefaktu (0–20 % kokybė), kad būtų geresnis aiškumas. Patobulina vaizdus su didelio suspaudimo artefaktais (20–40 % kokybė), atkuria detales ir sumažina triukšmą. Pagerina vaizdus su vidutiniu suspaudimu (40–60 % kokybė), subalansuojant ryškumą ir lygumą. Patobulina vaizdus su lengvu suspaudimu (60–80 % kokybė), kad paryškintų subtilias detales ir tekstūras. Šiek tiek pagerina beveik neprarandančius vaizdus (80–100 % kokybė), išsaugant natūralią išvaizdą ir detales. Paprastas ir greitas spalvinimas, animaciniai filmukai, ne idealu Šiek tiek sumažina vaizdo susiliejimą, pagerina ryškumą, neįvedant artefaktų. Ilgos operacijos Apdorojamas vaizdas Apdorojimas Pašalina sunkius JPEG glaudinimo artefaktus iš labai žemos kokybės vaizdų (0–20 %). Sumažina stiprius JPEG artefaktus labai suspaustuose vaizduose (20–40 %). Išvalo vidutinio sunkumo JPEG artefaktus, išsaugant vaizdo detales (40–60 %). Patobulina šviesius JPEG artefaktus gana aukštos kokybės vaizduose (60–80 %). Subtiliai sumažina smulkius JPEG artefaktus beveik neprarandant vaizdus (80–100 %). Paryškina smulkias detales ir tekstūras, pagerina suvokiamą ryškumą be sunkių artefaktų. Apdorojimas baigtas Apdoroti nepavyko Pagerina odos tekstūrą ir detales išlaikant natūralią išvaizdą, optimizuotą greitumui. Pašalina JPEG glaudinimo artefaktus ir atkuria suglaudintų nuotraukų vaizdo kokybę. Sumažina ISO triukšmą nuotraukose, darytose prasto apšvietimo sąlygomis, išsaugodamas detales. Pataiso per daug eksponuotus arba „jumbo“ paryškinimus ir atkuria geresnę tonų pusiausvyrą. Lengvas ir greitas spalvinimo modelis, kuris pilkų tonų vaizdams suteikia natūralių spalvų. DEJPEG Denoise Nuspalvinti Artefaktai Padidinti Anime Nuskaito Prabangus X4 padidinimas bendriems vaizdams; mažas modelis, kuris naudoja mažiau GPU ir laiko, su nedideliu išblukimu ir triukšmu. X2 padidinimo priemonė bendriems vaizdams, tekstūrų ir natūralių detalių išsaugojimui. X4 padidinimo priemonė bendriems vaizdams su patobulintomis tekstūromis ir tikroviškais rezultatais. X4 padidinimas, optimizuotas anime vaizdams; 6 RRDB blokai ryškesnėms linijoms ir detalėms. X4 padidinimas su MSE praradimu, sukuria sklandesnius rezultatus ir sumažina bendrųjų vaizdų artefaktus. X4 Upscaler optimizuotas anime vaizdams; 4B32F variantas su ryškesnėmis detalėmis ir lygiomis linijomis. X4 UltraSharp V2 modelis bendriems vaizdams; pabrėžia ryškumą ir aiškumą. X4 UltraSharp V2 Lite; greitesnis ir mažesnis, išsaugo detales ir naudoja mažiau GPU atminties. Lengvas modelis greitam fono pašalinimui. Subalansuotas našumas ir tikslumas. Dirba su portretais, objektais ir scenomis. Rekomenduojama daugeliui naudojimo atvejų. Pašalinti BG Horizontalios kraštinės storis Vertikalios kraštinės storis %1$s spalvos %1$s spalva %1$s spalvos %1$s spalvos Dabartinis modelis nepalaiko smulkinimo, vaizdas bus apdorojamas originaliais matmenimis, todėl gali sunaudoti daug atminties ir kilti problemų su žemos klasės įrenginiais Dalijimas išjungtas, vaizdas bus apdorojamas originalių matmenų, todėl gali sunaudoti daug atminties ir gali kilti problemų su žemos klasės įrenginiais, bet gali duoti geresnių išvadų rezultatų Susmulkinti Didelio tikslumo vaizdo segmentavimo modelis, skirtas fono pašalinimui Lengva U2Net versija, skirta greičiau pašalinti foną naudojant mažiau atminties. Visas DDColor modelis užtikrina aukštos kokybės bendrų vaizdų spalvinimą su minimaliais artefaktais. Geriausias pasirinkimas iš visų spalvinimo modelių. DDColor Išmokyti ir privatūs meniniai duomenų rinkiniai; sukuria įvairius ir meniškus spalvinimo rezultatus su mažiau nerealių spalvų artefaktų. Lengvas BiRefNet modelis, pagrįstas Swin Transformer, kad būtų galima tiksliai pašalinti foną. Aukštos kokybės fono pašalinimas su aštriais kraštais ir puikiu detalių išsaugojimu, ypač sudėtinguose objektuose ir sudėtingame fone. Fono pašalinimo modelis, gaminantis tikslias kaukes su lygiais kraštais, tinkamas bendriems objektams ir saikingam detalių išsaugojimui. Modelis jau atsisiųstas Modelis sėkmingai importuotas Tipas raktinis žodis Labai greitai Normalus Lėtas Labai lėtas Apskaičiuokite procentus Minimali vertė yra %1$s Iškraipykite vaizdą piešdami pirštais Metmenys Kietumas Metimo režimas Judėti Augti Susitraukti Sūkurys CW Sūkurys CCW Išblukimo stiprumas Viršutinis lašas Apatinis lašas Pradėti Drop Pabaigos kritimas Atsisiunčiama Lygios formos Naudokite superelipses, o ne standartinius suapvalintus stačiakampius, kad gautumėte lygesnes, natūralesnes formas Formos tipas Iškirpti Suapvalinti Sklandžiai Aštrūs kraštai be apvalinimo Klasikiniai užapvalinti kampai Formos tipas Kampų dydis Squircle Elegantiški suapvalinti vartotojo sąsajos elementai Failo vardo formatas Pasirinktinis tekstas, patalpintas pačioje failo pavadinimo pradžioje, puikiai tinka projektų pavadinimams, prekių ženklams ar asmeninėms žymoms. Naudoja originalų failo pavadinimą be plėtinio, padedantį išlaikyti šaltinio identifikavimo duomenis. Vaizdo plotis pikseliais, naudingas stebint skiriamosios gebos pokyčius arba keičiant mastelio rezultatus. Vaizdo aukštis pikseliais, naudinga dirbant su formato koeficientu arba eksportuojant. Generuoja atsitiktinius skaitmenis, kad garantuotų unikalius failų pavadinimus; pridėkite daugiau skaitmenų, kad išvengtumėte dublikatų. Automatiškai didėjantis paketinio eksporto skaitiklis, idealiai tinka išsaugant kelis vaizdus per vieną sesiją. Į failo pavadinimą įterpia pritaikytą išankstinio nustatymo pavadinimą, kad galėtumėte lengvai prisiminti, kaip buvo apdorotas vaizdas. Rodomas vaizdo mastelio keitimo režimas, naudojamas apdorojimo metu, padedantis atskirti pakeisto dydžio, apkarpytus ar pritaikytus vaizdus. Pasirinktinis tekstas, dedamas failo pavadinimo pabaigoje, naudingas kuriant versijas, pvz., _v2, _edited arba _final. Failo plėtinys (png, jpg, webp ir kt.), automatiškai atitinkantis tikrąjį išsaugotą formatą. Pritaikoma laiko žyma, leidžianti nustatyti savo formatą pagal „Java“ specifikaciją, kad būtų galima tobulai rūšiuoti. Išmetimo tipas „Android Native“. iOS stilius Lygi kreivė Greitas sustojimas Bouncy Plūduriuojantis Šmaikštus Ultra Smooth Prisitaikantis Prieinamumas Aware Sumažintas judesys „Android“ slinkties fizika, skirta pradiniam palyginimui Subalansuotas, sklandus slinkimas bendram naudojimui Didesnė trintis, panaši į „iOS“ slinkimo elgsena Unikali spline kreivė, leidžianti aiškiai slinkti Tikslus slinkimas su greitu stabdymu Žaismingas, reaguojantis šokinėjantis slinktis Ilgi, slenkantys slinktys, skirti naršyti turinį Greitas, reaguojantis interaktyvių vartotojo sąsajų slinkimas Aukščiausios kokybės sklandus slinkimas su padidintu impulsu Koreguoja fiziką pagal svaidymo greitį Gerbia sistemos prieinamumo nustatymus Minimalus judėjimas pasiekiamumo poreikiams Pirminės linijos Kas penktą eilutę prideda storesnė linija Užpildymo spalva Paslėpti įrankiai Įrankiai, paslėpti bendrinimui Spalvų biblioteka Naršykite didelę spalvų kolekciją Paryškina ir pašalina vaizdų susiliejimą išlaikant natūralias detales, idealiai tinka taisyti nesufokusuotas nuotraukas. Protingai atkuria vaizdus, ​​kurių dydis anksčiau buvo pakeistas, atkuriant prarastas detales ir tekstūras. Optimizuotas tiesioginio veiksmo turiniui, sumažina suspaudimo artefaktus ir pagerina smulkias detales filmų / TV laidų kadruose. Konvertuoja VHS kokybės filmuotą medžiagą į HD, pašalina juostos triukšmą ir padidina skiriamąją gebą, išsaugant senovinį pojūtį. Specializuotas vaizdams ir ekrano kopijoms, kuriuose yra daug teksto, paryškina simbolius ir pagerina skaitomumą. Išplėstinis padidinimas, parengtas naudojant įvairius duomenų rinkinius, puikiai tinka bendrosios paskirties nuotraukų patobulinimui. Optimizuotas žiniatinklyje suspaustoms nuotraukoms, pašalina JPEG artefaktus ir atkuria natūralią išvaizdą. Patobulinta žiniatinklio nuotraukų versija su geresniu tekstūros išsaugojimu ir artefaktų mažinimu. 2x padidinimas naudojant Dual Aggregation Transformer technologiją, išlaiko ryškumą ir natūralias detales. 3x padidinimas naudojant pažangią transformatoriaus architektūrą, idealiai tinka vidutinio dydžio padidinimo poreikiams. 4x aukštos kokybės padidinimas naudojant moderniausią transformatorių tinklą, išsaugo smulkias detales esant didesniam masteliui. Pašalina nuotraukų susiliejimą/triukšmą ir drebėjimą. Bendra paskirtis, bet geriausia nuotraukose. Atkuria žemos kokybės vaizdus naudojant Swin2SR transformatorių, optimizuotą BSRGAN degradacijai. Puikiai tinka tvirtinti stiprius suspaudimo artefaktus ir patobulinti detales 4 kartus. 4x padidinimas naudojant SwinIR transformatorių, apmokytą BSRGAN degradacijai. Naudoja GAN, kad nuotraukose ir sudėtingose scenose būtų ryškesnės tekstūros ir natūralesnės detalės. Kelias Sujungti PDF Sujunkite kelis PDF failus į vieną dokumentą Failų tvarka p. Padalinti PDF Ištraukite konkrečius puslapius iš PDF dokumento Pasukti PDF Pataisykite puslapio orientaciją visam laikui Puslapiai Pertvarkyti PDF Nuvilkite puslapius, kad juos pakeistumėte Laikykite ir vilkite puslapius Puslapių numeriai Automatiškai pridėkite numeraciją prie savo dokumentų Etiketės formatas PDF į tekstą (OCR) Ištraukite paprastą tekstą iš savo PDF dokumentų Perdenkite tinkintą tekstą, skirtą prekės ženklui arba saugai Parašas Pridėkite savo elektroninį parašą prie bet kurio dokumento Tai bus naudojama kaip parašas Atrakinti PDF Pašalinkite slaptažodžius iš apsaugotų failų Apsaugoti PDF Apsaugokite dokumentus naudodami tvirtą šifravimą Sėkmės PDF atrakintas, galite jį išsaugoti arba bendrinti Pataisyti PDF Bandykite taisyti sugadintus arba neįskaitomus dokumentus Pilkos spalvos Konvertuoti visus dokumento įterptus vaizdus į pilkos spalvos tonus Suspausti PDF Optimizuokite dokumento failo dydį, kad būtų lengviau bendrinti „ImageToolbox“ atkuria vidinę kryžminių nuorodų lentelę ir atkuria failo struktūrą nuo nulio. Tai gali atkurti prieigą prie daugelio failų, kurių \\"negalima atidaryti\\" Šis įrankis konvertuoja visus dokumentų vaizdus į pilkos spalvos tonus. Geriausiai tinka spausdinti ir sumažinti failo dydį Metaduomenys Redaguokite dokumento ypatybes, kad užtikrintumėte didesnį privatumą Žymos Gamintojas Autorius Raktažodžiai Kūrėjas Privatumas Deep Clean Išvalyti visus galimus šio dokumento metaduomenis Puslapis Gilus OCR Ištraukite tekstą iš dokumento ir išsaugokite jį viename tekstiniame faile naudodami Tesseract variklį Negalima pašalinti visų puslapių Pašalinti PDF puslapius Pašalinkite konkrečius puslapius iš PDF dokumento Bakstelėkite Norėdami pašalinti Rankiniu būdu Apkarpyti PDF Apkarpykite dokumento puslapius iki bet kokių ribų Išlyginti PDF Padarykite PDF nekeičiamą rastruodami dokumento puslapius Nepavyko paleisti fotoaparato. Patikrinkite leidimus ir įsitikinkite, kad jo nenaudoja kita programa. Ištraukite vaizdus Ištraukite į PDF failus įterptus vaizdus pradine raiška Šiame PDF faile nėra įterptų vaizdų Šis įrankis nuskaito kiekvieną puslapį ir atkuria visos kokybės šaltinio vaizdus – puikiai tinka išsaugoti originalus iš dokumentų Piešti parašą Pen Params Naudokite savo parašą kaip vaizdą, kuris bus dedamas ant dokumentų ZIP PDF Padalinkite dokumentą nurodytu intervalu ir supakuokite naujus dokumentus į ZIP archyvą Intervalas Spausdinti PDF Paruoškite dokumentą spausdinti pagal pasirinktinį puslapio dydį Puslapiai lape Orientacija Puslapio dydis Marža Bloom Minkštas kelias Optimizuotas anime ir animaciniams filmams. Greitas mastelio padidinimas su patobulintomis natūraliomis spalvomis ir mažiau artefaktų „Samsung One UI 7“ panašus stilius Norėdami apskaičiuoti norimą reikšmę, įveskite čia pagrindinius matematinius simbolius (pvz., (5+5)*10) Matematinė išraiška Paimkite iki %1$s vaizdų Išsaugokite datos laiką Visada išsaugokite exif žymas, susijusias su data ir laiku, veikia nepriklausomai nuo parinkties išlaikyti exif Fono spalva Alfa formatams Pridedama galimybė nustatyti fono spalvą kiekvienam vaizdo formatui su alfa palaikymu, kai išjungta, tai galima tik ne alfa formatams Atviras projektas Tęskite anksčiau išsaugoto vaizdo įrankių dėžės projekto redagavimą Nepavyko atidaryti „Image Toolbox“ projekto Vaizdo įrankių dėžutės projekte trūksta projekto duomenų Vaizdo įrankių dėžutės projektas sugadintas Nepalaikoma „Image Toolbox“ projekto versija: %1$d Išsaugoti projektą Saugokite sluoksnius, foną ir redaguokite istoriją redaguojamame projekto faile Nepavyko atidaryti Rašyti į ieškomą PDF Atpažinkite tekstą iš vaizdų paketo ir išsaugokite ieškomą PDF su vaizdu ir pasirenkamu teksto sluoksniu Alfa sluoksnis Horizontalus apvertimas Vertikalus apvertimas Užraktas Pridėti šešėlį Šešėlių spalva Teksto geometrija Ištempkite arba pasukite tekstą, kad stilizacija būtų ryškesnė X skalė Iškreiptas X Pašalinti komentarus Pašalinkite pasirinktus komentarų tipus, pvz., nuorodas, komentarus, paryškinimus, figūras ar formos laukus iš PDF puslapių Hipersaitai Failų priedai Linijos Iššokantys langai Antspaudai Formos Teksto pastabos Teksto žymėjimas Formos laukai Žymėjimas Nežinoma Anotacijos Išgrupuoti Už sluoksnio pridėkite neryškų šešėlį su konfigūruojama spalva ir poslinkiais ================================================ FILE: core/resources/src/main/res/values-mr/strings.xml ================================================ काहीतरी चूक झाली: %1$s आकार %1$s लोड होत आहे… प्रतिदर्शनासाठी प्रतिमा खूप मोठी आहे, परंतु तरीही ती सेव्ह करण्याचा प्रयत्न केला जाईल. सुरू करण्यासाठी प्रतिमा निवडा रुंदी %1$s उंची %1$s गुणवत्ता विस्तार आकाराचा प्रकार बदला स्पष्ट लवचिक प्रतिमा निवडा तुम्हाला खात्री आहे की तुम्हाला ॲप बंद करायचे आहे? ॲप बंद होत आहे थांबा बंद करा प्रतिमा रीसेट करा प्रतिमांमध्ये केलेले बदल त्यांच्या सुरुवातीच्या मूल्यांवर परत येतील. मूल्ये योग्यरित्या रीसेट केली गेली. रीसेट करा काहीतरी चूक झाली. ॲप रीस्टार्ट करा क्लिपबोर्डवर कॉपी केले अपवाद EXIF संपादित करा ठीक आहे सर्वात जवळचे आकार अरेरे… काहीतरी चूक झाली आहे. तुम्ही खाली दिलेल्या पर्यायांचा वापर करून मला लिहू शकता आणि मी त्यावर उपाय शोधण्याचा प्रयत्न करेन. दिलेल्या ठिकाणी एकदाच वॉटरमार्क लावण्याऐवजी, तो प्रतिमेवर पुन्हा लावतो. ऑफसेट एक्स स्कॅन करण्यायोग्य नाही लॅन्झोस ६ जिंक लॅन्झोस कमाल आकार केबी मध्ये दिलेल्या केबी (KB) आकारानुसार प्रतिमेचा आकार बदला. तुलना करा दिलेल्या दोन प्रतिमांची तुलना करा. सुरुवात करण्यासाठी दोन प्रतिमा निवडा. प्रतिमा निवडा सेटिंग्ज रात्री मोड गडद प्रकाश डीफॉल्ट सानुकूल अनिर्दिष्ट डिव्हाइस स्टोरेज काढा दिलेल्या प्रतिमेवरून कलर पॅलेट स्वॉच तयार करा पॅलेट तयार करा पॅलेट अद्यतन नवीन आवृत्ती %1$s असमर्थित प्रकार: %1$s दिलेल्या प्रतिमेसाठी पॅलेट तयार करता येत नाही. मूळ आउटपुट फोल्डर वजनानुसार आकार बदला प्रणाली गतिशील रंग सानुकूलन प्रतिमा कमाईला परवानगी द्या जर हे वैशिष्ट्य सक्षम केले असेल, तर जेव्हा तुम्ही संपादनासाठी एखादी प्रतिमा निवडाल, तेव्हा ॲपचे रंग त्या प्रतिमेनुसार बदलले जातील. भाषा अमोलेड मोड सक्षम केल्यास, नाईट मोडमध्ये पृष्ठभागांचा रंग पूर्णपणे गडद होईल. रंगसंगती लाल हिरवा निळा एक वैध aRGB कलर कोड पेस्ट करा. पेस्ट करण्यासाठी काहीही नाही डायनॅमिक रंग चालू असताना ॲपची रंगसंगती बदलता येत नाही. ॲपची थीम निवडलेल्या रंगावर आधारित असेल. अ‍ॅपबद्दल कोणतेही अपडेट्स आढळले नाहीत. समस्या ट्रॅकर बग रिपोर्ट आणि वैशिष्ट्यांच्या विनंत्या येथे पाठवा. भाषांतर करण्यास मदत करा भाषांतरातील चुका दुरुस्त करा किंवा प्रकल्प इतर भाषांमध्ये स्थानिकृत करा. तुमच्या शोधानुसार काहीही सापडले नाही. येथे शोधा जर हे सक्षम केले असेल, तर ॲपचे रंग वॉलपेपरच्या रंगांनुसार बदलतील. %d प्रतिमा जतन करण्यात अयशस्वी. ईमेल प्राथमिक माध्यमिक तृतीयक बॉर्डरची जाडी पृष्ठभाग मुल्ये जोडा परवानगी अनुदान ॲपला काम करण्यासाठी आणि प्रतिमा सेव्ह करण्यासाठी तुमच्या स्टोरेजमध्ये प्रवेशाची आवश्यकता आहे, हे आवश्यक आहे. कृपया पुढील संवाद बॉक्समध्ये परवानगी द्या. ॲपला काम करण्यासाठी या परवानगीची आवश्यकता आहे, कृपया ती स्वतःहून मंजूर करा. बाह्य संचयन मोनेट रंग हा ॲप्लिकेशन पूर्णपणे विनामूल्य आहे, परंतु जर तुम्हाला प्रकल्पाच्या विकासाला पाठिंबा द्यायचा असेल, तर तुम्ही येथे क्लिक करू शकता. एफएबी संरेखन अपडेट्स तपासा सक्षम केल्यास, ॲप सुरू झाल्यावर तुम्हाला अपडेटचा संवाद दिसेल. प्रतिमा झूम सामायिक करा उपसर्ग फाइलचे नाव इमोजी मुख्य स्क्रीनवर कोणता इमोजी प्रदर्शित करायचा ते निवडा. फाइलचा आकार जोडा सक्षम केल्यास, सेव्ह केलेल्या प्रतिमेची रुंदी आणि उंची आउटपुट फाइलच्या नावात जोडली जाते. EXIF हटवा कोणत्याही प्रतिमांच्या संचातून EXIF मेटाडेटा हटवा. प्रतिमा पूर्वावलोकन कोणत्याही प्रकारच्या प्रतिमांचे पूर्वावलोकन करा: GIF, SVG, आणि इतर. प्रतिमा स्रोत फोटो निवडक गॅलरी फाइल एक्सप्लोरर स्क्रीनच्या तळाशी दिसणारा अँड्रॉइडचा आधुनिक फोटो पिकर, कदाचित फक्त अँड्रॉइड १२+ वरच काम करेल. त्याला EXIF मेटाडेटा मिळवण्यात समस्या येतात. एक साधे गॅलरी इमेज पिकर. हे फक्त तेव्हाच काम करेल जेव्हा तुमच्याकडे मीडिया निवडण्याची सुविधा देणारे ॲप असेल. प्रतिमा निवडण्यासाठी GetContent इंटेंट वापरा. हे सर्वत्र काम करते, परंतु काही उपकरणांवर निवडलेल्या प्रतिमा प्राप्त करताना समस्या येत असल्याचे दिसून आले आहे. यात माझा दोष नाही. पर्यायांची मांडणी संपादित करा ऑर्डर मुख्य स्क्रीनवरील साधनांचा क्रम निश्चित करतो. इमोजींची संख्या अनुक्रम क्रमांक मूळ फाइलनाव मूळ फाइलचे नाव जोडा सक्षम केल्यास, आउटपुट प्रतिमेच्या नावात मूळ फाइलचे नाव जोडले जाते. अनुक्रमांक बदला सक्षम केल्यास, बॅच प्रोसेसिंग वापरताना, हे स्टँडर्ड टाइमस्टॅम्पऐवजी इमेजचा अनुक्रमांक वापरेल. फोटो पिकर इमेज स्रोत निवडल्यास मूळ फाइलचे नाव जोडणे काम करत नाही. वेब प्रतिमा लोडिंग पूर्वावलोकन करण्यासाठी, झूम करण्यासाठी, संपादित करण्यासाठी आणि इच्छित असल्यास सेव्ह करण्यासाठी इंटरनेटवरून कोणतीही प्रतिमा लोड करा. कोणतीही प्रतिमा नाही प्रतिमा दुवा भरा फिट सामग्री प्रमाण प्रतिमेचा आकार दिलेल्या उंची आणि रुंदीनुसार बदला. प्रतिमांचे गुणोत्तर बदलू शकते. प्रतिमेच्या सर्वात लांब बाजूला दिलेल्या उंची किंवा रुंदीनुसार आकार बदला. सर्व आकारांची गणना सेव्ह केल्यानंतर केली जाईल. प्रतिमांचे गुणोत्तर प्रमाण कायम राखले जाईल. चमक फरक रंगछटा संतृप्ति फिल्टर जोडा फिल्टर प्रतिमांवर फिल्टर चेन लागू करा फिल्टर प्रकाश रंग फिल्टर अल्फा उद्भासन पांढरा शिल्लक तापमान रंगछटा मोनोक्रोम गामा हायलाइट्स आणि सावल्या ठळक मुद्दे सावल्या धुके परिणाम अंतर उतार धार लावा सेपिया नकारात्मक सोलराइझ करा कंपन काळा आणि पांढरा कोणताही EXIF ​​डेटा आढळला नाही टॅग जोडा जतन करा साफ EXIF साफ करा रद्द करा सर्व प्रतिमा EXIF ​​डेटा मिटविला जाईल. ही क्रिया पूर्ववत केली जाऊ शकत नाही! प्रीसेट पीक बचत करत आहे तुम्ही आता बाहेर पडल्यास सर्व जतन न केलेले बदल गमावले जातील स्त्रोत कोड नवीनतम अद्यतने मिळवा, समस्यांवर चर्चा करा आणि बरेच काही एकल संपादन एक प्रतिमा सुधारा, आकार बदला आणि संपादित करा रंग निवडक प्रतिमेतून रंग निवडा, कॉपी करा किंवा शेअर करा प्रतिमा रंग रंग कॉपी केला प्रतिमा कोणत्याही मर्यादेपर्यंत क्रॉप करा आवृत्ती EXIF ठेवा प्रतिमा: %d पूर्वावलोकन बदला क्रॉसशॅच अंतर ओळीची रुंदी सोबेल काठ अस्पष्ट हाफटोन CGA कलरस्पेस गॉसियन अस्पष्टता बॉक्स ब्लर द्विपक्षीय अस्पष्टता एम्बॉस लॅपलाशियन विग्नेट सुरू करा शेवट कुवाहरा गुळगुळीत स्टॅक ब्लर त्रिज्या स्केल विकृती कोन फिरणे फुगवटा फैलाव गोलाकार अपवर्तन अपवर्तक निर्देशांक काचेच्या गोलाचे अपवर्तन रंग मॅट्रिक्स अपारदर्शकता मर्यादेनुसार आकार बदला आस्पेक्ट रेशो ठेवताना दिलेल्या उंची आणि रुंदीनुसार प्रतिमांचा आकार बदला स्केच उंबरठा परिमाणीकरण पातळी गुळगुळीत टून टून पोस्टराइझ करा कमाल दडपशाही नाही कमकुवत पिक्सेल समावेश पहा कंव्होल्युशन 3x3 आरजीबी फिल्टर खोटा रंग पहिला रंग दुसरा रंग पुनर्क्रमित करा जलद अस्पष्टता अस्पष्ट आकार अस्पष्ट केंद्र x अस्पष्ट केंद्र y झूम ब्लर रंग शिल्लक ल्युमिनेन्स थ्रेशोल्ड तुम्ही Files ॲप अक्षम केले आहे, हे वैशिष्ट्य वापरण्यासाठी ते सक्रिय करा काढा स्केचबुक प्रमाणे प्रतिमेवर काढा किंवा पार्श्वभूमीवरच काढा पेंट रंग अल्फा पेंट करा प्रतिमेवर काढा एक प्रतिमा निवडा आणि त्यावर काहीतरी काढा पार्श्वभूमीवर काढा पार्श्वभूमी रंग निवडा आणि त्याच्या वर काढा पार्श्वभूमी रंग सायफर विविध उपलब्ध क्रिप्टो अल्गोरिदमवर आधारित कोणतीही फाईल (केवळ प्रतिमाच नाही) कूटबद्ध आणि डिक्रिप्ट करा फाइल निवडा एनक्रिप्ट करा डिक्रिप्ट करा सुरू करण्यासाठी फाइल निवडा डिक्रिप्शन एनक्रिप्शन की फाइल प्रक्रिया केली ही फाईल तुमच्या डिव्हाइसवर साठवा किंवा तुम्हाला पाहिजे तेथे ठेवण्यासाठी शेअर कृती वापरा वैशिष्ट्ये अंमलबजावणी सुसंगतता फाइल्सचे पासवर्ड-आधारित एनक्रिप्शन. पुढे केलेल्या फायली निवडलेल्या निर्देशिकेत संग्रहित केल्या जाऊ शकतात किंवा सामायिक केल्या जाऊ शकतात. डिक्रिप्ट केलेल्या फायली थेट उघडल्या जाऊ शकतात. AES-256, GCM मोड, कोणतेही पॅडिंग नाही, 12 बाइट यादृच्छिक IV डीफॉल्टनुसार. आपण आवश्यक अल्गोरिदम निवडू शकता. की 256-बिट SHA-3 हॅश म्हणून वापरल्या जातात फाइल आकार जास्तीत जास्त फाइल आकार Android OS आणि उपलब्ध मेमरीद्वारे प्रतिबंधित आहे, जी डिव्हाइसवर अवलंबून आहे. \nकृपया लक्षात ठेवा: मेमरी ही स्टोरेज नाही. कृपया लक्षात घ्या की इतर फाइल एन्क्रिप्शन सॉफ्टवेअर किंवा सेवांच्या सुसंगततेची हमी दिलेली नाही. किंचित भिन्न की उपचार किंवा सायफर कॉन्फिगरेशनमुळे विसंगतता येऊ शकते. अवैध पासवर्ड किंवा निवडलेली फाइल एनक्रिप्ट केलेली नाही दिलेल्या रुंदी आणि उंचीसह प्रतिमा जतन करण्याचा प्रयत्न केल्याने मेमरी त्रुटी होऊ शकते. हे तुमच्या स्वतःच्या जोखमीवर करा. कॅशे कॅशे आकार %1$s सापडले ऑटो कॅशे क्लिअरिंग सक्षम असल्यास ॲप कॅशे ॲप स्टार्टअपवर साफ केला जाईल तयार करा साधने प्रकारानुसार गट पर्याय सानुकूल सूची व्यवस्थेऐवजी मुख्य स्क्रीनवरील पर्याय त्यांच्या प्रकारानुसार गटबद्ध करा पर्याय गटिंग सक्षम असताना व्यवस्था बदलू शकत नाही स्क्रीनशॉट संपादित करा दुय्यम सानुकूलन स्क्रीनशॉट फॉलबॅक पर्याय वगळा कॉपी करा %1$s मोडमध्ये जतन करणे अस्थिर असू शकते, कारण ते नुकसानरहित स्वरूप आहे तुम्ही प्रीसेट 125 निवडले असल्यास, इमेज मूळ प्रतिमेच्या 125% आकारात सेव्ह केली जाईल. आपण प्रीसेट 50 निवडल्यास, प्रतिमा 50% आकारासह जतन केली जाईल येथे प्रीसेट आउटपुट फाइलचे % ठरवते, म्हणजे तुम्ही 5 MB प्रतिमेवर प्रीसेट 50 निवडल्यास, सेव्ह केल्यानंतर तुम्हाला 2,5 MB प्रतिमा मिळेल. फाइलनाव यादृच्छिक करा सक्षम केल्यास आउटपुट फाइलनाव पूर्णपणे यादृच्छिक असेल %2$s नावाने %1$s फोल्डरमध्ये जतन केले %1$s फोल्डरमध्ये जतन केले टेलीग्राम गप्पा ॲपवर चर्चा करा आणि इतर वापरकर्त्यांकडून फीडबॅक मिळवा. तुम्ही तेथे बीटा अपडेट्स आणि अंतर्दृष्टी देखील मिळवू शकता. क्रॉप मास्क आस्पेक्ट रेशो दिलेल्या प्रतिमेतून मुखवटा तयार करण्यासाठी हा मुखवटा प्रकार वापरा, लक्षात घ्या की त्यात अल्फा चॅनेल असणे आवश्यक आहे बॅकअप आणि पुनर्संचयित करा बॅकअप पुनर्संचयित करा तुमच्या ॲप सेटिंग्जचा एका फाईलमध्ये बॅकअप घ्या पूर्वी व्युत्पन्न केलेल्या फाइलमधून ॲप सेटिंग्ज पुनर्संचयित करा दूषित फाइल किंवा बॅकअप नाही सेटिंग्ज यशस्वीरित्या पुनर्संचयित केल्या माझ्याशी संपर्क साधा हे तुमची सेटिंग्ज डीफॉल्ट मूल्यांवर परत आणेल. लक्षात घ्या की वर नमूद केलेल्या बॅकअप फाइलशिवाय हे पूर्ववत केले जाऊ शकत नाही. हटवा तुम्ही निवडलेली रंगसंगती हटवणार आहात. हे ऑपरेशन पूर्ववत केले जाऊ शकत नाही योजना हटवा फॉन्ट मजकूर फॉन्ट स्केल डीफॉल्ट मोठ्या फॉन्ट स्केल वापरल्याने UI त्रुटी आणि समस्या उद्भवू शकतात, ज्याचे निराकरण केले जाणार नाही. सावधपणे वापरा. अ आ इ ई उ ऊ ऋ ए ऐ ओ औ क ख ग घ ङ च छ ज झ ञ ट ठ ड ढ ण त थ द ध न प फ ब भ म य र ल व श ष स ह 0123456789 !? भावना अन्न आणि पेय निसर्ग आणि प्राणी वस्तू चिन्हे इमोजी सक्षम करा प्रवास आणि ठिकाणे उपक्रम पार्श्वभूमी रिमूव्हर चित्रातून पार्श्वभूमी काढा किंवा ऑटो पर्याय वापरा प्रतिमा ट्रिम करा मूळ इमेज मेटाडेटा ठेवला जाईल प्रतिमेभोवतीची पारदर्शक जागा ट्रिम केली जाईल पार्श्वभूमी स्वयं पुसून टाका प्रतिमा पुनर्संचयित करा मिटवा मोड पार्श्वभूमी पुसून टाका पार्श्वभूमी पुनर्संचयित करा अस्पष्ट त्रिज्या पिपेट रेखाचित्र मोड समस्या तयार करा आकार बदला आणि रूपांतरित करा दिलेल्या प्रतिमांचा आकार बदला किंवा त्यांना इतर स्वरूपांमध्ये रूपांतरित करा. एकल प्रतिमा निवडल्यास EXIF ​​मेटाडेटा देखील येथे संपादित केला जाऊ शकतो. कमाल रंग संख्या हे ॲपला आपोआप क्रॅश अहवाल संकलित करण्यास अनुमती देते विश्लेषण अनामित ॲप वापर आकडेवारी गोळा करण्यास अनुमती द्या सध्या, %1$s फॉरमॅट केवळ Android वर EXIF ​​मेटाडेटा वाचण्याची अनुमती देते. सेव्ह केल्यावर आउटपुट इमेजमध्ये मेटाडेटा अजिबात नसेल. प्रयत्न %1$s चे मूल्य म्हणजे जलद कॉम्प्रेशन, परिणामी फाइल आकारमानाने मोठा होतो. %2$s म्हणजे धीमे कॉम्प्रेशन, परिणामी एक लहान फाईल. थांबा जतन करणे जवळजवळ पूर्ण झाले. आता रद्द करण्यासाठी पुन्हा बचत करणे आवश्यक आहे. अपडेट्स बीटास परवानगी द्या सक्षम केले असल्यास अद्यतन तपासणीमध्ये बीटा ॲप आवृत्त्यांचा समावेश असेल बाण काढा सक्षम असल्यास रेखाचित्र मार्ग पॉइंटिंग ॲरो म्हणून दर्शविला जाईल ब्रश मऊपणा एंटर केलेल्या आकारानुसार चित्रे मध्यभागी क्रॉप केली जातील. इमेज एंटर केलेल्या आयामांपेक्षा लहान असल्यास कॅनव्हास दिलेल्या पार्श्वभूमी रंगाने विस्तारित केला जाईल. दान प्रतिमा स्टिचिंग एक मोठी प्रतिमा मिळविण्यासाठी दिलेल्या प्रतिमा एकत्र करा किमान 2 प्रतिमा निवडा आउटपुट प्रतिमा स्केल प्रतिमा अभिमुखता क्षैतिज उभ्या लहान प्रतिमा मोठ्या करा सक्षम केल्यास लहान प्रतिमा अनुक्रमातील सर्वात मोठ्या प्रतिमांवर मोजल्या जातील प्रतिमा क्रम नियमित अस्पष्ट कडा सक्षम असल्यास एकल रंगाऐवजी त्याच्या सभोवतालची जागा भरण्यासाठी मूळ प्रतिमेखाली अस्पष्ट कडा काढते पिक्सेलेशन वर्धित पिक्सेलेशन स्ट्रोक पिक्सेलेशन वर्धित डायमंड पिक्सेलेशन डायमंड पिक्सेलेशन सर्कल पिक्सेलेशन वर्धित सर्कल पिक्सेलेशन रंग बदला सहिष्णुता बदलण्यासाठी रंग लक्ष्य रंग काढण्यासाठी रंग रंग काढा रीकोड करा पिक्सेल आकार लॉक ड्रॉ ओरिएंटेशन ड्रॉइंग मोडमध्ये सक्षम केल्यास, स्क्रीन फिरणार नाही अद्यतनांसाठी तपासा पॅलेट शैली टोनल स्पॉट तटस्थ दोलायमान अभिव्यक्त इंद्रधनुष्य फळ कोशिंबीर निष्ठा सामग्री डीफॉल्ट पॅलेट शैली, हे सर्व चार रंग सानुकूलित करण्याची परवानगी देते, इतर तुम्हाला फक्त मुख्य रंग सेट करण्याची परवानगी देतात एक शैली जी मोनोक्रोमपेक्षा थोडी अधिक रंगीत आहे एक मोठा थीम, प्राथमिक पॅलेटसाठी रंगीतपणा जास्तीत जास्त आहे, इतरांसाठी वाढला आहे एक खेळकर थीम - स्रोत रंगाची छटा थीममध्ये दिसत नाही एक मोनोक्रोम थीम, रंग पूर्णपणे काळा / पांढरा / राखाडी आहेत एक योजना जी Scheme.primaryContainer मध्ये स्त्रोत रंग ठेवते एक योजना जी सामग्री योजनेसारखीच आहे नवीन अपडेट उपलब्ध आहे की नाही हे तपासण्याच्या कारणास्तव हा अपडेट तपासक GitHub शी कनेक्ट होईल लक्ष द्या धूसर कडा अक्षम दोन्ही उलटे रंग सक्षम असल्यास थीमचे रंग नकारात्मक रंगांमध्ये बदलते शोधा मुख्य स्क्रीनवरील सर्व उपलब्ध साधनांमधून शोधण्याची क्षमता सक्षम करते PDF साधने PDF फाइल्ससह ऑपरेट करा: पूर्वावलोकन करा, प्रतिमांच्या बॅचमध्ये रूपांतरित करा किंवा दिलेल्या चित्रांमधून एक तयार करा पीडीएफचे पूर्वावलोकन करा PDF ते प्रतिमा PDF मध्ये प्रतिमा साधे पीडीएफ पूर्वावलोकन दिलेल्या आउटपुट फॉरमॅटमध्ये पीडीएफला प्रतिमांमध्ये रूपांतरित करा दिलेल्या प्रतिमा आउटपुट PDF फाईलमध्ये पॅक करा मास्क फिल्टर दिलेल्या मास्क केलेल्या भागांवर फिल्टर चेन लावा, प्रत्येक मास्क क्षेत्र स्वतःचे फिल्टरचे संच ठरवू शकते मुखवटे मास्क जोडा मुखवटा %d मुखवटा रंग मुखवटा पूर्वावलोकन तुम्हाला अंदाजे परिणाम दर्शविण्यासाठी काढलेला फिल्टर मास्क रेंडर केला जाईल व्यस्त भरण प्रकार सक्षम केल्यास सर्व मुखवटा नसलेले क्षेत्र डीफॉल्ट वर्तनाऐवजी फिल्टर केले जातील तुम्ही निवडलेला फिल्टर मास्क हटवणार आहात. हे ऑपरेशन पूर्ववत केले जाऊ शकत नाही मास्क हटवा पूर्ण फिल्टर दिलेल्या प्रतिमा किंवा एकल प्रतिमेवर कोणतेही फिल्टर चेन लागू करा सुरू करा केंद्र शेवट साधी रूपे हायलाइटर निऑन पेन गोपनीयता अस्पष्टता अर्ध-पारदर्शक तीक्ष्ण हायलाइटर मार्ग काढा तुमच्या रेखाचित्रांमध्ये काही चमकणारा प्रभाव जोडा डीफॉल्ट एक, सर्वात सोपा - फक्त रंग आपण लपवू इच्छित असलेली कोणतीही गोष्ट सुरक्षित करण्यासाठी काढलेल्या मार्गाखाली प्रतिमा अस्पष्ट करते प्रायव्हसी ब्लर प्रमाणेच, परंतु अस्पष्ट करण्याऐवजी पिक्सेलेट कंटेनर कंटेनरच्या मागे सावली काढा स्लाइडर स्विचेस FABs बटणे स्लाइडरच्या मागे सावली काढा स्विचच्या मागे सावली काढा फ्लोटिंग ॲक्शन बटणांच्या मागे सावली काढा बटणांच्या मागे सावली काढा ॲप बार ॲप बारच्या मागे सावली काढा श्रेणीतील मूल्य %1$s - %2$s स्वयं फिरवा प्रतिमा अभिमुखतेसाठी मर्यादा बॉक्स स्वीकारण्याची अनुमती देते पथ मोड काढा दुहेरी रेषा बाण विनामूल्य रेखाचित्र दुहेरी बाण रेषा बाण बाण ओळ इनपुट मूल्य म्हणून मार्ग काढतो रेषा म्हणून प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंतचा मार्ग काढतो सुरुवातीच्या बिंदूपासून शेवटच्या बिंदूपर्यंत एक रेषा म्हणून सूचक बाण काढतो दिलेल्या मार्गावरून सूचक बाण काढतो प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत एक रेषा म्हणून दुहेरी पॉइंटिंग बाण काढतो दिलेल्या मार्गावरून दुहेरी निर्देश करणारा बाण काढतो रेखांकित ओव्हल रेखांकित रेक्ट ओव्हल रेक्ट प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत आयत काढतो प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत अंडाकृती काढतो प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत बाह्यरेखित अंडाकृती काढतो प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत बाह्यरेखा काढतो लॅसो दिलेल्या मार्गाने बंद भरलेला मार्ग काढतो मोफत क्षैतिज ग्रिड अनुलंब ग्रिड स्टिच मोड पंक्तींची संख्या स्तंभांची संख्या कोणतीही \"%1$s\" निर्देशिका आढळली नाही, आम्ही ती डीफॉल्टवर स्विच केली, कृपया फाइल पुन्हा सेव्ह करा क्लिपबोर्ड ऑटो पिन सक्षम असल्यास क्लिपबोर्डवर जतन केलेली प्रतिमा स्वयंचलितपणे जोडते कंपन कंपन शक्ती फाइल्स ओव्हरराइट करण्यासाठी तुम्हाला \"एक्सप्लोरर\" इमेज सोर्स वापरण्याची गरज आहे, इमेज रिपिक करून पहा, आम्ही इमेज सोर्स आवश्यकतेनुसार बदलला आहे. फायली अधिलिखित करा मूळ फाइल निवडलेल्या फोल्डरमध्ये सेव्ह करण्याऐवजी नवीन फाइलने बदलली जाईल, या पर्यायाला इमेज सोर्स \"एक्सप्लोरर\" किंवा GetContent असणे आवश्यक आहे, हे टॉगल करताना, ते स्वयंचलितपणे सेट केले जाईल. रिकामे प्रत्यय स्केल मोड द्विरेखीय कॅटमुल बायक्यूबिक तो संन्यासी मिशेल पट्टी बेसिक डीफॉल्ट मूल्य प्रतिमेचा आकार बदलण्यासाठी रेखीय (किंवा द्विरेखीय, दोन आयामांमध्ये) इंटरपोलेशन सामान्यत: चांगले असते, परंतु तपशिलांना काही अवांछित मऊ बनवते आणि तरीही काहीसे दातेरी असू शकते. उत्तम स्केलिंग पद्धतींमध्ये लँकझोस रीसॅम्पलिंग आणि मिशेल-नेत्रावली फिल्टर समाविष्ट आहेत आकार वाढवण्याचा एक सोपा मार्ग, प्रत्येक पिक्सेलला एकाच रंगाच्या अनेक पिक्सेलसह बदलणे सर्वात सोपा Android स्केलिंग मोड जो जवळजवळ सर्व ॲप्समध्ये वापरला जातो गुळगुळीत वक्र तयार करण्यासाठी सामान्यतः संगणक ग्राफिक्समध्ये वापरल्या जाणाऱ्या कंट्रोल पॉइंट्सच्या संचाचे सहजतेने इंटरपोलेटिंग आणि रीसेम्पलिंग करण्याची पद्धत स्पेक्ट्रल गळती कमी करण्यासाठी आणि सिग्नलच्या कडा कमी करून वारंवारता विश्लेषणाची अचूकता सुधारण्यासाठी सिग्नल प्रक्रियेमध्ये विंडोिंग फंक्शन अनेकदा लागू केले जाते. गणितीय इंटरपोलेशन तंत्र जे वक्र विभागाच्या शेवटच्या बिंदूंवर एक गुळगुळीत आणि सतत वक्र तयार करण्यासाठी मूल्ये आणि व्युत्पन्न वापरते पिक्सेल मूल्यांवर भारित sinc फंक्शन लागू करून उच्च-गुणवत्तेचे इंटरपोलेशन राखणारी रीसॅम्पलिंग पद्धत रीसॅम्पलिंग पद्धत जी मोजमाप केलेल्या प्रतिमेमध्ये तीक्ष्णता आणि अँटी-अलायझिंग दरम्यान संतुलन साधण्यासाठी समायोज्य पॅरामीटर्ससह कॉन्व्होल्यूशन फिल्टर वापरते लवचिक आणि सतत आकाराचे प्रतिनिधित्व प्रदान करून, वक्र किंवा पृष्ठभागावर सहजतेने इंटरपोलेट करण्यासाठी आणि अंदाजे करण्यासाठी तुकड्यानुसार-परिभाषित बहुपदीय कार्ये वापरते फक्त क्लिप स्टोरेजमध्ये सेव्हिंग केले जाणार नाही आणि इमेज फक्त क्लिपबोर्डमध्ये ठेवण्याचा प्रयत्न केला जाईल ब्रश मिटवण्याऐवजी पार्श्वभूमी पुनर्संचयित करेल OCR (मजकूर ओळखा) दिलेल्या प्रतिमेतून मजकूर ओळखा, 120+ भाषा समर्थित चित्रात मजकूर नाही किंवा ॲपला तो सापडला नाही \"अचूकता: %1$s\" ओळख प्रकार जलद मानक सर्वोत्तम डेटा नाही Tesseract OCR च्या योग्य कार्यासाठी अतिरिक्त प्रशिक्षण डेटा (%1$s) आपल्या डिव्हाइसवर डाउनलोड करणे आवश्यक आहे.\nतुम्हाला %2$s डेटा डाउनलोड करायचा आहे का? डाउनलोड करा कोणतेही कनेक्शन नाही, ते तपासा आणि ट्रेन मॉडेल डाउनलोड करण्यासाठी पुन्हा प्रयत्न करा डाउनलोड केलेल्या भाषा उपलब्ध भाषा सेगमेंटेशन मोड पिक्सेल स्विच वापरा Google Pixel सारखे स्विच वापरते मूळ गंतव्यस्थानावर %1$s नावासह अधिलिखित फाइल भिंग उत्तम प्रवेशयोग्यतेसाठी रेखाचित्र मोडमध्ये बोटाच्या शीर्षस्थानी भिंग सक्षम करते प्रारंभिक मूल्य सक्ती करा सुरुवातीला exif विजेट तपासण्याची सक्ती करते एकाधिक भाषांना परवानगी द्या स्लाइड करा शेजारी शेजारी टॅप टॉगल करा पारदर्शकता ॲपला रेट करा रेट करा हे ॲप पूर्णपणे विनामूल्य आहे, जर तुम्हाला ते मोठे व्हायचे असेल तर कृपया Github वर प्रोजेक्ट स्टार करा 😄 केवळ अभिमुखता आणि स्क्रिप्ट शोध ऑटो ओरिएंटेशन आणि स्क्रिप्ट शोध फक्त ऑटो ऑटो सिंगल कॉलम एकल ब्लॉक अनुलंब मजकूर सिंगल ब्लॉक एकच ओळ एकच शब्द वर्तुळ शब्द एकच चारी विरळ मजकूर विरळ मजकूर अभिमुखता आणि स्क्रिप्ट शोध कच्ची ओळ तुम्ही सर्व ओळख प्रकारांसाठी भाषा \"%1$s\" OCR प्रशिक्षण डेटा हटवू इच्छिता की फक्त निवडलेल्या (%2$s) साठी? चालू सर्व ग्रेडियंट मेकर सानुकूलित रंग आणि देखावा प्रकारासह दिलेल्या आउटपुट आकाराचा ग्रेडियंट तयार करा रेखीय रेडियल स्वीप करा ग्रेडियंट प्रकार केंद्र एक्स केंद्र वाय टाइल मोड वारंवार आरसा पकडीत घट्ट करणे Decal रंग थांबतो रंग जोडा गुणधर्म ब्राइटनेस अंमलबजावणी पडदा ग्रेडियंट आच्छादन दिलेल्या प्रतिमांच्या शीर्षस्थानी कोणताही ग्रेडियंट तयार करा परिवर्तने कॅमेरा कॅमेऱ्याने फोटो काढा. लक्षात ठेवा की या प्रतिमा स्त्रोतावरून फक्त एक प्रतिमा मिळविणे शक्य आहे वॉटरमार्किंग सानुकूल करण्यायोग्य मजकूर/प्रतिमा वॉटरमार्कसह चित्रे कव्हर करा वॉटरमार्कची पुनरावृत्ती करा ऑफसेट Y वॉटरमार्क प्रकार ही प्रतिमा वॉटरमार्किंगसाठी नमुना म्हणून वापरली जाईल मजकूर रंग आच्छादन मोड GIF साधने प्रतिमांना GIF चित्रात रूपांतरित करा किंवा दिलेल्या GIF प्रतिमेमधून फ्रेम्स काढा प्रतिमांना GIF GIF फाइल चित्रांच्या बॅचमध्ये रूपांतरित करा प्रतिमांचा बॅच GIF फाइलमध्ये रूपांतरित करा GIF मध्ये प्रतिमा प्रारंभ करण्यासाठी GIF प्रतिमा निवडा प्रथम फ्रेमचा आकार वापरा प्रथम फ्रेम परिमाणांसह निर्दिष्ट आकार पुनर्स्थित करा पुनरावृत्ती गणना फ्रेम विलंब मिलिस FPS लॅसो वापरा मिटवण्यासाठी ड्रॉईंग मोडमध्ये Lasso चा वापर करते मूळ प्रतिमा पूर्वावलोकन अल्फा कॉन्फेटी सेव्हिंग, शेअरिंग आणि इतर प्राथमिक क्रियांवर कॉन्फेटी दाखवली जाईल सुरक्षित मोड अलीकडील ॲप्समधील ॲप सामग्री लपवते. ते कॅप्चर किंवा रेकॉर्ड केले जाऊ शकत नाही. बाहेर पडा तुम्ही आता पूर्वावलोकन सोडल्यास, तुम्हाला पुन्हा प्रतिमा जोडण्याची आवश्यकता असेल डिथरिंग क्वांटायझर ग्रे स्केल बायर टू बाय टू डिथरिंग बायर थ्री बाय थ्री डिथरिंग बायर फोर बाय फोर डिथरिंग बायर आठ बाय आठ डिथरिंग फ्लॉइड स्टीनबर्ग डिथरिंग जार्विस न्यायाधीश निन्के डिथरिंग सिएरा डिथरिंग दोन पंक्ती सिएरा डिथरिंग सिएरा लाइट डिथरिंग ॲटकिन्सन डिथरिंग Stucki Dithering बर्क्स डिथरिंग खोटे फ्लॉइड स्टीनबर्ग डिथरिंग डावीकडून उजवीकडे डिथरिंग यादृच्छिक डिथरिंग साधे थ्रेशोल्ड डिथरिंग सिग्मा अवकाशीय सिग्मा मध्यम अस्पष्टता बी स्प्लाइन वक्र किंवा पृष्ठभाग, लवचिक आणि सतत आकाराचे प्रतिनिधित्व सहजतेने इंटरपोलेट करण्यासाठी आणि अंदाजे करण्यासाठी तुकडावार-परिभाषित बायक्यूबिक बहुपदी कार्ये वापरते नेटिव्ह स्टॅक ब्लर टिल्ट शिफ्ट गडबड रक्कम बी ॲनाग्लिफ गोंगाट पिक्सेल क्रमवारी शफल वर्धित ग्लिच चॅनल शिफ्ट एक्स चॅनल शिफ्ट वाई भ्रष्टाचाराचा आकार भ्रष्टाचार शिफ्ट एक्स भ्रष्टाचार शिफ्ट वाई तंबू अंधुक बाजूला फिकट बाजू वर तळ ताकद इरोड ॲनिसोट्रॉपिक प्रसार प्रसार वहन क्षैतिज वारा स्टॅगर जलद द्विपक्षीय अस्पष्टता पॉयसन ब्लर लॉगरिदमिक टोन मॅपिंग ACES फिल्मिक टोन मॅपिंग स्फटिक करणे स्ट्रोक रंग फ्रॅक्टल ग्लास मोठेपणा संगमरवरी अशांतता तेल पाण्याचा प्रभाव आकार वारंवारता X वारंवारता Y मोठेपणा X मोठेपणा Y पर्लिन विरूपण ACES हिल टोन मॅपिंग हेबल फिल्मिक टोन मॅपिंग हेजी-बर्गेस टोन मॅपिंग गती देहाळे ओमेगा कलर मॅट्रिक्स 4x4 कलर मॅट्रिक्स 3x3 साधे प्रभाव पोलरॉइड ट्रायटॅनोमली Deuteranomaly प्रोटोनोमली विंटेज तपकिरी च्या कोडा क्रोम नाईट व्हिजन उबदार मस्त ट्रायटॅनोपिया ड्युटारोनोटोपिया प्रोटानोपिया अक्रोमॅटोमॅली ऍक्रोमॅटोप्सिया धान्य अनशार्प पेस्टल नारिंगी धुके गुलाबी स्वप्न गोल्डन अवर गरम उन्हाळा जांभळा धुके सूर्योदय रंगीबेरंगी चक्कर मऊ स्प्रिंग लाइट शरद ऋतूतील टोन लॅव्हेंडर स्वप्न सायबरपंक लिंबूपाणी प्रकाश स्पेक्ट्रल फायर रात्रीची जादू कल्पनारम्य लँडस्केप रंग स्फोट इलेक्ट्रिक ग्रेडियंट कारमेल अंधार फ्युचरिस्टिक ग्रेडियंट हिरवा सूर्य इंद्रधनुष्य जग खोल जांभळा स्पेस पोर्टल लाल चक्कर डिजिटल कोड बोकेह ॲप बार इमोजी यादृच्छिकपणे बदलेल यादृच्छिक इमोजी इमोजी अक्षम असताना तुम्ही यादृच्छिक इमोजी वापरू शकत नाही यादृच्छिक इमोजी सक्षम असताना तुम्ही इमोजी निवडू शकत नाही जुना टीव्ही अस्पष्टता शफल करा आवडते अद्याप कोणतेही आवडते फिल्टर जोडलेले नाहीत प्रतिमा स्वरूप चिन्हांखाली निवडलेल्या आकारासह कंटेनर जोडते चिन्ह आकार ड्रॅगो अल्ड्रिज कटऑफ तुम्ही जागे व्हा मोबियस संक्रमण शिखर रंग विसंगती मूळ गंतव्यस्थानावर ओव्हरराईट केलेल्या प्रतिमा फाइल्स ओव्हरराइट पर्याय सक्षम असताना इमेज फॉरमॅट बदलू शकत नाही रंग योजना म्हणून इमोजी मॅन्युअली परिभाषित रंगाऐवजी इमोजी प्राथमिक रंग ॲप कलर स्कीम म्हणून वापरते इमेजमधून मटेरियल यू पॅलेट तयार करते गडद रंग लाइट वेरिएंटऐवजी नाईट मोड कलर स्कीम वापरते Jetpack कंपोझ कोड म्हणून कॉपी करा रिंग ब्लर क्रॉस ब्लर वर्तुळ अस्पष्ट तारा अस्पष्ट रेखीय टिल्ट-शिफ्ट टॅग्ज काढण्यासाठी APNG साधने प्रतिमा APNG चित्रात रूपांतरित करा किंवा दिलेल्या APNG प्रतिमेमधून फ्रेम काढा प्रतिमांना APNG APNG फाइल चित्रांच्या बॅचमध्ये रूपांतरित करा प्रतिमांचा बॅच APNG फाईलमध्ये रूपांतरित करा APNG वर प्रतिमा सुरू करण्यासाठी APNG प्रतिमा निवडा मोशन ब्लर जि.प दिलेल्या फाईल्स किंवा इमेजेसमधून Zip फाइल तयार करा हँडल रुंदी ड्रॅग करा कॉन्फेटी प्रकार सण स्फोट पाऊस कोपरे JXL साधने गुणवत्ता कमी न करता JXL ~ JPEG ट्रान्सकोडिंग करा किंवा GIF/APNG ते JXL ॲनिमेशनमध्ये रूपांतरित करा JXL ते JPEG JXL ते JPEG ला लॉसलेस ट्रान्सकोडिंग करा JPEG ते JXL पर्यंत लॉसलेस ट्रान्सकोडिंग करा JPEG ते JXL सुरू करण्यासाठी JXL प्रतिमा निवडा जलद गॉसियन ब्लर 2D जलद गॉसियन ब्लर 3D जलद गॉसियन ब्लर 4D कार इस्टर ॲपला क्लिपबोर्ड डेटा स्वयं पेस्ट करण्याची अनुमती देते, त्यामुळे तो मुख्य स्क्रीनवर दिसेल आणि तुम्ही त्यावर प्रक्रिया करू शकाल सुसंवाद रंग सामंजस्य पातळी Lanczos Bessel पिक्सेल मूल्यांवर बेसल (जिंक) फंक्शन लागू करून उच्च-गुणवत्तेचे इंटरपोलेशन राखणारी रीसॅम्पलिंग पद्धत GIF ते JXL GIF प्रतिमांना JXL ॲनिमेटेड चित्रांमध्ये रूपांतरित करा APNG ते JXL APNG प्रतिमांना JXL ॲनिमेटेड चित्रांमध्ये रूपांतरित करा JXL ते प्रतिमा JXL ॲनिमेशनला चित्रांच्या बॅचमध्ये रूपांतरित करा JXL साठी प्रतिमा चित्रांच्या बॅचचे JXL ॲनिमेशनमध्ये रूपांतर करा वागणूक फाइल निवडणे वगळा निवडलेल्या स्क्रीनवर हे शक्य असल्यास फाइल पिकर त्वरित दर्शविला जाईल पूर्वावलोकने व्युत्पन्न करा पूर्वावलोकन निर्मिती सक्षम करते, हे काही उपकरणांवर क्रॅश टाळण्यास मदत करू शकते, हे एकल संपादन पर्यायामध्ये काही संपादन कार्यक्षमता देखील अक्षम करते हानीकारक कॉम्प्रेशन लॉसलेस ऐवजी फाईलचा आकार कमी करण्यासाठी हानीकारक कॉम्प्रेशन वापरते कॉम्प्रेशन प्रकार परिणामी प्रतिमा डीकोडिंग गती नियंत्रित करते, यामुळे परिणामी प्रतिमा जलद उघडण्यास मदत होईल, %1$s चे मूल्य म्हणजे सर्वात धीमे डीकोडिंग, तर %2$s - सर्वात वेगवान, ही सेटिंग आउटपुट प्रतिमा आकार वाढवू शकते वर्गीकरण तारीख तारीख (उलट) नाव नाव (उलट) चॅनेल कॉन्फिगरेशन आज काल एम्बेडेड पिकर इमेज टूलबॉक्सचा इमेज पिकर परवानग्या नाहीत विनंती एकाधिक मीडिया निवडा सिंगल मीडिया निवडा निवडा पुन्हा प्रयत्न करा लँडस्केपमध्ये सेटिंग्ज दर्शवा हे अक्षम केल्यास, कायमस्वरूपी दृश्यमान पर्यायाऐवजी, नेहमीप्रमाणे वरच्या ॲप बारमधील बटणावर लँडस्केप मोड सेटिंग्ज उघडतील. पूर्णस्क्रीन सेटिंग्ज ते सक्षम करा आणि सेटिंग्ज पृष्ठ नेहमी स्लाइड करण्यायोग्य ड्रॉवर शीटऐवजी फुलस्क्रीन म्हणून उघडले जाईल स्विच प्रकार रचना करा तुम्ही स्विच करता ते जेटपॅक कंपोझ मटेरियल एक मटेरिअल तुम्ही स्विच करता कमाल अँकरचा आकार बदला पिक्सेल अस्खलित \"फ्लुएंट\" डिझाइन प्रणालीवर आधारित स्विच क्युपर्टिनो \"क्युपर्टिनो\" डिझाइन प्रणालीवर आधारित एक स्विच SVG साठी प्रतिमा SVG प्रतिमांना दिलेल्या प्रतिमा ट्रेस करा नमुना पॅलेट वापरा हा पर्याय सक्षम केल्यास क्वांटायझेशन पॅलेट नमुना केला जाईल मार्ग सोडून द्या डाउनस्केलिंगशिवाय मोठ्या प्रतिमा ट्रेस करण्यासाठी या साधनाचा वापर करण्याची शिफारस केलेली नाही, यामुळे क्रॅश होऊ शकतो आणि प्रक्रियेचा वेळ वाढू शकतो. डाउनस्केल प्रतिमा प्रक्रिया करण्यापूर्वी प्रतिमा कमी आकारात कमी केली जाईल, हे साधन जलद आणि सुरक्षित कार्य करण्यास मदत करते किमान रंग गुणोत्तर लाईन्स थ्रेशोल्ड चतुर्भुज थ्रेशोल्ड गोलाकार सहिष्णुता समन्वयित करते पथ स्केल गुणधर्म रीसेट करा सर्व गुणधर्म डीफॉल्ट मूल्यांवर सेट केले जातील, लक्षात घ्या की ही क्रिया पूर्ववत केली जाऊ शकत नाही तपशीलवार डीफॉल्ट लाइन रुंदी इंजिन मोड वारसा LSTM नेटवर्क वारसा आणि LSTM रूपांतर करा इमेज बॅचेस दिलेल्या फॉरमॅटमध्ये रूपांतरित करा नवीन फोल्डर जोडा प्रति नमुना बिट्स संक्षेप फोटोमेट्रिक व्याख्या प्रति पिक्सेल नमुने प्लॅनर कॉन्फिगरेशन Y Cb Cr सब सॅम्पलिंग Y Cb Cr पोझिशनिंग एक्स रिझोल्यूशन Y ठराव रिझोल्यूशन युनिट स्ट्रिप ऑफसेट्स पंक्ती प्रति पट्टी स्ट्रिप बाइट संख्या JPEG इंटरचेंज स्वरूप JPEG इंटरचेंज फॉरमॅटची लांबी हस्तांतरण कार्य पांढरा बिंदू प्राथमिक रंगसंगती Y Cb Cr गुणांक संदर्भ काळा पांढरा तारीख वेळ प्रतिमा वर्णन बनवा मॉडेल सॉफ्टवेअर कलाकार कॉपीराइट Exif आवृत्ती फ्लॅशपिक्स आवृत्ती रंगीत जागा गामा पिक्सेल एक्स परिमाण पिक्सेल वाई परिमाण संकुचित बिट्स प्रति पिक्सेल मेकर नोट वापरकर्ता टिप्पणी संबंधित ध्वनी फाइल तारीख वेळ मूळ तारीख वेळ डिजिटाइझ केली ऑफसेट वेळ ऑफसेट वेळ मूळ ऑफसेट टाईम डिजिटायझ्ड उप सेकंद वेळ उप सेकंद वेळ मूळ सब सेक टाइम डिजीटल उद्भासन वेळ F क्रमांक एक्सपोजर कार्यक्रम वर्णक्रमीय संवेदनशीलता फोटोग्राफिक संवेदनशीलता ओईसीएफ संवेदनशीलता प्रकार मानक आउटपुट संवेदनशीलता शिफारस केलेले एक्सपोजर इंडेक्स ISO गती ISO गती अक्षांश yyy ISO स्पीड अक्षांश zzz शटर गती मूल्य छिद्र मूल्य ब्राइटनेस मूल्य एक्सपोजर बायस मूल्य कमाल छिद्र मूल्य विषय अंतर मीटरिंग मोड फ्लॅश विषय क्षेत्र फोकल लांबी फ्लॅश एनर्जी अवकाशीय वारंवारता प्रतिसाद फोकल प्लेन एक्स रिझोल्यूशन फोकल प्लेन वाई रिझोल्यूशन फोकल प्लेन रिझोल्यूशन युनिट विषय स्थान एक्सपोजर इंडेक्स सेन्सिंग पद्धत फाइल स्रोत CFA नमुना सानुकूल प्रस्तुत एक्सपोजर मोड पांढरा शिल्लक डिजिटल झूम प्रमाण फोकल लांबी In35mm फिल्म दृश्य कॅप्चर प्रकार नियंत्रण मिळवा कॉन्ट्रास्ट संपृक्तता तीक्ष्णपणा डिव्हाइस सेटिंग वर्णन विषय अंतर श्रेणी प्रतिमा अद्वितीय आयडी कॅमेरा मालकाचे नाव मुख्य भाग अनुक्रमांक लेन्स तपशील लेन्स बनवा लेन्स मॉडेल लेन्स अनुक्रमांक GPS आवृत्ती आयडी GPS अक्षांश संदर्भ GPS अक्षांश GPS रेखांश संदर्भ GPS रेखांश GPS Altitude Ref जीपीएस उंची जीपीएस टाइम स्टॅम्प GPS उपग्रह जीपीएस स्थिती GPS मापन मोड GPS DOP GPS गती संदर्भ GPS गती GPS ट्रॅक संदर्भ जीपीएस ट्रॅक GPS Img दिशा रेफ GPS Img दिशा जीपीएस नकाशा डेटाम GPS Dest Latitude Ref GPS डेस्ट अक्षांश GPS डेस्ट रेखांश संदर्भ GPS गंतव्य रेखांश GPS डेस्ट बेअरिंग रेफ GPS डेस्ट बेअरिंग GPS Dest Distance Ref GPS गंतव्य अंतर जीपीएस प्रक्रिया पद्धत जीपीएस क्षेत्र माहिती जीपीएस तारीख स्टॅम्प जीपीएस भिन्नता GPS H पोझिशनिंग एरर इंटरऑपरेबिलिटी इंडेक्स DNG आवृत्ती डीफॉल्ट क्रॉप आकार पूर्वावलोकन प्रतिमा प्रारंभ पूर्वावलोकन प्रतिमा लांबी आस्पेक्ट फ्रेम सेन्सर तळाची सीमा सेन्सर डावी सीमा सेन्सर उजवी सीमा सेन्सर शीर्ष सीमा आयएसओ दिलेल्या फॉन्ट आणि रंगाने पथावर मजकूर काढा फॉन्ट आकार वॉटरमार्क आकार मजकूर पुन्हा करा एक वेळ काढण्याऐवजी पाथ संपेपर्यंत वर्तमान मजकूराची पुनरावृत्ती केली जाईल डॅश आकार दिलेल्या मार्गावर ती काढण्यासाठी निवडलेली प्रतिमा वापरा ही प्रतिमा काढलेल्या मार्गाची पुनरावृत्ती एंट्री म्हणून वापरली जाईल प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत बाह्यरेखित त्रिकोण काढतो प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत बाह्यरेखित त्रिकोण काढतो बाह्यरेखित त्रिकोण त्रिकोण प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत बहुभुज काढतो बहुभुज बाह्यरेखित बहुभुज प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत बाह्यरेखित बहुभुज काढतो शिरोबिंदू नियमित बहुभुज काढा बहुभुज काढा जो फ्री फॉर्म ऐवजी नियमित असेल प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत तारा काढतो तारा आराखडा तारा प्रारंभ बिंदूपासून शेवटच्या बिंदूपर्यंत बाह्यरेखित तारा काढतो आतील त्रिज्या प्रमाण नियमित तारा काढा तारा काढा जो फ्री फॉर्म ऐवजी नियमित असेल अँटिलियास तीक्ष्ण कडा रोखण्यासाठी अँटिलायझिंग सक्षम करते पूर्वावलोकनाऐवजी संपादन उघडा जेव्हा तुम्ही ImageToolbox मध्ये उघडण्यासाठी (पूर्वावलोकन) प्रतिमा निवडता, तेव्हा पूर्वावलोकन करण्याऐवजी संपादन निवड पत्रक उघडले जाईल दस्तऐवज स्कॅनर दस्तऐवज स्कॅन करा आणि त्यांच्यापासून PDF किंवा वेगळ्या प्रतिमा तयार करा स्कॅनिंग सुरू करण्यासाठी क्लिक करा स्कॅनिंग सुरू करा पीडीएफ म्हणून सेव्ह करा पीडीएफ म्हणून शेअर करा खालील पर्याय प्रतिमा जतन करण्यासाठी आहेत, PDF नाही हिस्टोग्राम HSV समान करा हिस्टोग्राम समान करा टक्केवारी प्रविष्ट करा मजकूर फील्डद्वारे प्रविष्ट करण्यास अनुमती द्या प्रीसेट निवडीच्या मागे मजकूर फील्ड सक्षम करते, त्यांना फ्लायवर प्रविष्ट करण्यासाठी स्केल कलर स्पेस रेखीय हिस्टोग्राम पिक्सेलेशन समान करा ग्रिड आकार X ग्रिड आकार Y हिस्टोग्राम अनुकूली समान करा हिस्टोग्राम अडॅप्टिव्ह LUV समान करा हिस्टोग्राम ॲडॉप्टिव्ह LAB समान करा CLAHE CLAHE लॅब CLAHE LUV सामग्रीसाठी क्रॉप करा फ्रेम रंग दुर्लक्ष करण्यासाठी रंग साचा कोणतेही टेम्पलेट फिल्टर जोडलेले नाहीत नवीन तयार करा स्कॅन केलेला QR कोड वैध फिल्टर टेम्पलेट नाही QR कोड स्कॅन करा निवडलेल्या फाइलमध्ये फिल्टर टेम्पलेट डेटा नाही टेम्पलेट तयार करा टेम्पलेट नाव ही प्रतिमा या फिल्टर टेम्पलेटचे पूर्वावलोकन करण्यासाठी वापरली जाईल टेम्पलेट फिल्टर QR कोड प्रतिमा म्हणून फाइल म्हणून फाइल म्हणून सेव्ह करा QR कोड प्रतिमा म्हणून जतन करा टेम्पलेट हटवा तुम्ही निवडलेले टेम्पलेट फिल्टर हटवणार आहात. हे ऑपरेशन पूर्ववत केले जाऊ शकत नाही \"%1$s\" (%2$s) नावासह फिल्टर टेम्पलेट जोडले फिल्टर पूर्वावलोकन QR आणि बारकोड QR कोड स्कॅन करा आणि त्याची सामग्री मिळवा किंवा नवीन तयार करण्यासाठी तुमची स्ट्रिंग पेस्ट करा कोड सामग्री फील्डमधील सामग्री बदलण्यासाठी कोणताही बारकोड स्कॅन करा किंवा निवडलेल्या प्रकारासह नवीन बारकोड तयार करण्यासाठी काहीतरी टाइप करा QR वर्णन मि QR कोड स्कॅन करण्यासाठी सेटिंग्जमध्ये कॅमेरा परवानगी द्या दस्तऐवज स्कॅनर स्कॅन करण्यासाठी सेटिंग्जमध्ये कॅमेरा परवानगी द्या घन बी-स्प्लाइन हॅमिंग हॅनिंग ब्लॅकमन वेल्च चौकोन गॉसियन स्फिंक्स बार्टलेट रॉबिडॉक्स रॉबिडॉक्स शार्प स्प्लाइन 16 स्प्लाइन 36 स्प्लाइन 64 कैसर बार्टलेट-हे पेटी बोहमन लॅन्झोस २ Lanczos 3 लॅन्झोस ४ Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc क्यूबिक इंटरपोलेशन सर्वात जवळच्या 16 पिक्सेलचा विचार करून नितळ स्केलिंग प्रदान करते, द्विरेखीय पेक्षा चांगले परिणाम देते वक्र किंवा पृष्ठभाग, लवचिक आणि सतत आकाराचे प्रतिनिधित्व सहजतेने इंटरपोलेट करण्यासाठी आणि अंदाजे करण्यासाठी तुकडावार-परिभाषित बहुपदीय कार्ये वापरते सिग्नलच्या कडांना निमुळता करून वर्णक्रमीय गळती कमी करण्यासाठी वापरलेले विंडो फंक्शन, सिग्नल प्रक्रियेत उपयुक्त हॅन विंडोचा एक प्रकार, सामान्यतः सिग्नल प्रोसेसिंग ऍप्लिकेशन्समध्ये वर्णक्रमीय गळती कमी करण्यासाठी वापरला जातो एक विंडो फंक्शन जे स्पेक्ट्रल गळती कमी करून चांगले वारंवारता रिझोल्यूशन प्रदान करते, बहुतेकदा सिग्नल प्रक्रियेत वापरले जाते कमी स्पेक्ट्रल गळतीसह चांगले वारंवारता रिझोल्यूशन देण्यासाठी डिझाइन केलेले विंडो फंक्शन, अनेकदा सिग्नल प्रोसेसिंग ऍप्लिकेशनमध्ये वापरले जाते गुळगुळीत आणि सतत परिणाम प्रदान करून प्रक्षेपणासाठी चतुर्भुज कार्य वापरणारी पद्धत एक इंटरपोलेशन पद्धत जी गॉसियन फंक्शन लागू करते, प्रतिमा गुळगुळीत करण्यासाठी आणि आवाज कमी करण्यासाठी उपयुक्त कमीत कमी कलाकृतींसह उच्च-गुणवत्तेचे इंटरपोलेशन प्रदान करणारी प्रगत पुनर्नमुने करण्याची पद्धत स्पेक्ट्रल गळती कमी करण्यासाठी सिग्नल प्रक्रियेमध्ये वापरलेले त्रिकोणी विंडो कार्य नैसर्गिक प्रतिमेचा आकार बदलण्यासाठी, तीक्ष्णता आणि गुळगुळीतपणा संतुलित करण्यासाठी ऑप्टिमाइझ केलेली उच्च-गुणवत्तेची इंटरपोलेशन पद्धत रॉबिडॉक्स पद्धतीचा एक तीक्ष्ण प्रकार, कुरकुरीत प्रतिमा आकार बदलण्यासाठी अनुकूल एक स्प्लाइन-आधारित इंटरपोलेशन पद्धत जी 16-टॅप फिल्टर वापरून गुळगुळीत परिणाम प्रदान करते एक स्प्लाइन-आधारित इंटरपोलेशन पद्धत जी 36-टॅप फिल्टर वापरून गुळगुळीत परिणाम प्रदान करते एक स्प्लाइन-आधारित इंटरपोलेशन पद्धत जी 64-टॅप फिल्टर वापरून गुळगुळीत परिणाम प्रदान करते एक इंटरपोलेशन पद्धत जी कैसर विंडो वापरते, मुख्य-लोब रुंदी आणि साइड-लोब लेव्हलमधील ट्रेड-ऑफवर चांगले नियंत्रण प्रदान करते. बार्टलेट आणि हॅन विंडो एकत्र करणारे एक संकरित विंडो फंक्शन, सिग्नल प्रक्रियेत वर्णक्रमीय गळती कमी करण्यासाठी वापरले जाते नजीकच्या पिक्सेल मूल्यांची सरासरी वापरणारी एक साधी पुनर्नमुना पद्धत, अनेकदा ब्लॉकी दिसणे स्पेक्ट्रल गळती कमी करण्यासाठी वापरलेले विंडो फंक्शन, सिग्नल प्रोसेसिंग ऍप्लिकेशन्समध्ये चांगले वारंवारता रिझोल्यूशन प्रदान करते किमान कलाकृतींसह उच्च-गुणवत्तेच्या इंटरपोलेशनसाठी 2-लोब लँकझोस फिल्टर वापरणारी पुनर्नमुना पद्धत किमान कलाकृतींसह उच्च-गुणवत्तेच्या इंटरपोलेशनसाठी 3-लोब लॅन्झोस फिल्टर वापरणारी पुनर्नमुना पद्धत किमान कलाकृतींसह उच्च-गुणवत्तेच्या इंटरपोलेशनसाठी 4-लोब लँकझोस फिल्टर वापरणारी पुनर्नमुना पद्धत Lanczos 2 फिल्टरचा एक प्रकार जो जिंक फंक्शन वापरतो, कमीत कमी कलाकृतींसह उच्च-गुणवत्तेचा इंटरपोलेशन प्रदान करतो Lanczos 3 फिल्टरचा एक प्रकार जो जिंक फंक्शन वापरतो, कमीत कमी कलाकृतींसह उच्च-गुणवत्तेचा इंटरपोलेशन प्रदान करतो Lanczos 4 फिल्टरचा एक प्रकार जो झिंक फंक्शन वापरतो, कमीत कमी कलाकृतींसह उच्च-गुणवत्तेचा इंटरपोलेशन प्रदान करतो हॅनिंग EWA गुळगुळीत इंटरपोलेशन आणि रीसॅम्पलिंगसाठी हॅनिंग फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार Robidoux EWA उच्च-गुणवत्तेच्या पुनर्नमुनाकरणासाठी रॉबिडॉक्स फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार ब्लॅकमन इव्ह रिंगिंग आर्टिफॅक्ट्स कमी करण्यासाठी ब्लॅकमॅन फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार क्वाड्रिक EWA गुळगुळीत इंटरपोलेशनसाठी क्वाड्रिक फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार Robidoux शार्प EWA तीव्र परिणामांसाठी रॉबिडॉक्स शार्प फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार Lanczos 3 Jinc EWA कमी अलियासिंगसह उच्च-गुणवत्तेच्या रिसॅम्पलिंगसाठी Lanczos 3 Jinc फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार जिनसेंग तीक्ष्णता आणि गुळगुळीतपणाच्या चांगल्या संतुलनासह उच्च-गुणवत्तेच्या प्रतिमा प्रक्रियेसाठी डिझाइन केलेले पुनर्नमुना फिल्टर जिनसेंग EWA वर्धित प्रतिमेच्या गुणवत्तेसाठी जिनसेंग फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार Lanczos शार्प EWA कमीत कमी कलाकृतींसह तीव्र परिणाम प्राप्त करण्यासाठी लँकझोस शार्प फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार Lanczos 4 शार्पेस्ट EWA अत्यंत तीक्ष्ण इमेज रिसॅम्पलिंगसाठी लँकझोस 4 शार्पेस्ट फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार Lanczos सॉफ्ट EWA स्मूथ इमेज रिसॅम्पलिंगसाठी लँकझोस सॉफ्ट फिल्टरचे लंबवर्तुळ भारित सरासरी (EWA) प्रकार हसन सॉफ्ट गुळगुळीत आणि आर्टिफॅक्ट-मुक्त प्रतिमा स्केलिंगसाठी हसनने डिझाइन केलेले पुनर्नमुना फिल्टर स्वरूप रूपांतरण प्रतिमांचा बॅच एका फॉरमॅटमधून दुसऱ्या फॉरमॅटमध्ये रूपांतरित करा कायमचे डिसमिस करा प्रतिमा स्टॅकिंग निवडलेल्या मिश्रण मोडसह प्रतिमा एकमेकांच्या वर स्टॅक करा प्रतिमा जोडा डब्यांची संख्या क्ले एचएसएल क्ले एचएसव्ही हिस्टोग्राम अडॅप्टिव्ह HSL समान करा हिस्टोग्राम अनुकूली HSV समान करा एज मोड क्लिप गुंडाळणे रंग अंधत्व निवडलेल्या रंग अंधत्व प्रकारासाठी थीम रंग जुळवून घेण्यासाठी मोड निवडा लाल आणि हिरव्या रंगांमध्ये फरक करण्यात अडचण हिरव्या आणि लाल रंगांमध्ये फरक करण्यात अडचण निळ्या आणि पिवळ्या रंगांमध्ये फरक करण्यात अडचण लाल रंगाची छटा समजण्यास असमर्थता हिरव्या रंगाची छटा ओळखण्यास असमर्थता निळ्या रंगाची छटा समजण्यास असमर्थता सर्व रंगांसाठी कमी संवेदनशीलता पूर्ण रंग अंधत्व, फक्त राखाडी छटा पाहणे कलर ब्लाइंड स्कीम वापरू नका थीममध्ये सेट केल्याप्रमाणे रंग अचूक असतील सिग्मॉइडल Lagrange 2 ऑर्डर 2 चा लॅग्रेंज इंटरपोलेशन फिल्टर, गुळगुळीत संक्रमणांसह उच्च-गुणवत्तेच्या प्रतिमा स्केलिंगसाठी योग्य Lagrange 3 ऑर्डर 3 चा लॅग्रेंज इंटरपोलेशन फिल्टर, इमेज स्केलिंगसाठी अधिक अचूकता आणि नितळ परिणाम देते Lanczos 6 6 च्या उच्च ऑर्डरसह एक Lanczos रीसॅम्पलिंग फिल्टर, तीक्ष्ण आणि अधिक अचूक प्रतिमा स्केलिंग प्रदान करते सुधारित इमेज रिसॅम्पलिंग गुणवत्तेसाठी जिन फंक्शन वापरून लॅन्झोस 6 फिल्टरचा एक प्रकार लिनियर बॉक्स ब्लर रेखीय तंबू अस्पष्ट रेखीय गॉसियन बॉक्स ब्लर रेखीय स्टॅक ब्लर गॉसियन बॉक्स ब्लर लीनियर फास्ट गॉसियन ब्लर पुढे लीनियर फास्ट गॉसियन ब्लर रेखीय गॉसियन ब्लर पेंट म्हणून वापरण्यासाठी एक फिल्टर निवडा फिल्टर बदला तुमच्या ड्रॉईंगमध्ये ब्रश म्हणून वापरण्यासाठी खालील फिल्टर निवडा TIFF कॉम्प्रेशन योजना कमी पॉली वाळू चित्रकला प्रतिमा विभाजन पंक्ती किंवा स्तंभांद्वारे एकल प्रतिमा विभाजित करा सीमेवर फिट इच्छित वर्तन साध्य करण्यासाठी या पॅरामीटरसह क्रॉप रिसाइज मोड एकत्र करा (क्रॉप/फिट टू ॲस्पेक्ट रेशो) भाषा यशस्वीरित्या आयात केल्या OCR मॉडेल्सचा बॅकअप घ्या आयात करा निर्यात करा स्थिती केंद्र वर डावीकडे वर उजवीकडे तळ डावीकडे तळ उजवीकडे शीर्ष केंद्र मध्यभागी उजवीकडे तळ केंद्र मध्यभागी डावीकडे लक्ष्य प्रतिमा पॅलेट हस्तांतरण वर्धित तेल साधा जुना टीव्ही HDR गोथम साधे स्केच मऊ चमक रंगीत पोस्टर ट्राय टोन तिसरा रंग क्ले ओकलाब क्लारा ओल्च क्ले ज्जाज्ब्ज पोल्का डॉट क्लस्टर केलेले 2x2 डिथरिंग क्लस्टर केलेले 4x4 डिथरिंग क्लस्टर केलेले 8x8 डिथरिंग यिलीलोमा डिथरिंग कोणतेही आवडते पर्याय निवडलेले नाहीत, त्यांना टूल पेजमध्ये जोडा आवडी जोडा पूरक समानार्थी ट्रायडिक स्प्लिट पूरक टेट्राडिक चौरस समान + पूरक रंग साधने मिक्स करा, टोन बनवा, शेड्स तयार करा आणि बरेच काही रंगसंगती कलर शेडिंग तफावत टिंट्स स्वर छटा रंग मिक्सिंग रंग माहिती निवडलेला रंग मिक्स करण्यासाठी रंग डायनॅमिक रंग चालू असताना मोनेट वापरू शकत नाही 512x512 2D LUT लक्ष्य LUT प्रतिमा एक हौशी मिस शिष्टाचार मऊ अभिजात सॉफ्ट एलिगन्स व्हेरिएंट पॅलेट हस्तांतरण प्रकार 3D LUT लक्ष्य 3D LUT फाइल (.cube / .CUBE) LUT ब्लीच बायपास मेणबत्ती ब्लूज ड्रॉप करा कडक अंबर फॉल कलर्स फिल्म स्टॉक 50 धुक्याची रात्र कोडॅक तटस्थ LUT प्रतिमा मिळवा प्रथम, तटस्थ LUT वर फिल्टर लागू करण्यासाठी तुमचा आवडता फोटो संपादन अनुप्रयोग वापरा जो तुम्ही येथे मिळवू शकता. हे योग्यरित्या कार्य करण्यासाठी प्रत्येक पिक्सेल रंग इतर पिक्सेलवर अवलंबून नसावा (उदा. अस्पष्ट काम करणार नाही). एकदा तयार झाल्यावर, 512*512 LUT फिल्टरसाठी इनपुट म्हणून तुमची नवीन LUT प्रतिमा वापरा पॉप आर्ट सेल्युलॉइड कॉफी गोल्डन फॉरेस्ट हिरवट रेट्रो पिवळा दुवे पूर्वावलोकन तुम्ही मजकूर मिळवू शकता अशा ठिकाणी लिंक पूर्वावलोकन पुनर्प्राप्त करणे सक्षम करते (QRCode, OCR इ.) दुवे ICO फायली केवळ 256 x 256 च्या कमाल आकारात जतन केल्या जाऊ शकतात WEBP वर GIF GIF प्रतिमा WEBP ॲनिमेटेड चित्रांमध्ये रूपांतरित करा WEBP साधने प्रतिमांना WEBP ॲनिमेटेड चित्रात रूपांतरित करा किंवा दिलेल्या WEBP ॲनिमेशनमधून फ्रेम्स काढा प्रतिमांसाठी WEBP WEBP फाइल चित्रांच्या बॅचमध्ये रूपांतरित करा प्रतिमांचा बॅच WEBP फाइलमध्ये रूपांतरित करा WEBP वर प्रतिमा सुरू करण्यासाठी WEBP प्रतिमा निवडा फायलींमध्ये पूर्ण प्रवेश नाही Android वर प्रतिमा म्हणून ओळखल्या जात नसलेल्या JXL, QOI आणि इतर प्रतिमा पाहण्यासाठी सर्व फायलींना प्रवेश द्या. परवानगीशिवाय इमेज टूलबॉक्स त्या प्रतिमा दाखवू शकत नाही डीफॉल्ट ड्रॉ रंग डीफॉल्ट ड्रॉ पथ मोड टाइमस्टॅम्प जोडा आउटपुट फाइलनावामध्ये टाइमस्टॅम्प जोडणे सक्षम करते स्वरूपित टाइमस्टॅम्प मूलभूत मिलिसऐवजी आउटपुट फाइलनावामध्ये टाइमस्टॅम्प स्वरूपन सक्षम करा टाइमस्टॅम्प त्यांचे स्वरूप निवडण्यासाठी सक्षम करा वन टाइम सेव्ह लोकेशन एक वेळ सेव्ह स्थाने पहा आणि संपादित करा जी तुम्ही बहुतेक सर्व पर्यायांमध्ये सेव्ह बटण जास्त वेळ दाबून वापरू शकता अलीकडे वापरले सीआय चॅनेल गट टेलीग्राम मधील इमेज टूलबॉक्स 🎉 आमच्या चॅटमध्ये सामील व्हा जिथे तुम्ही तुम्हाला पाहिजे असलेल्या कोणत्याही गोष्टीवर चर्चा करू शकता आणि मी बीटा आणि घोषणा पोस्ट करत असलेल्या CI चॅनेलमध्ये देखील पाहू शकता ॲपच्या नवीन आवृत्त्यांबद्दल सूचना मिळवा आणि घोषणा वाचा दिलेल्या परिमाणांमध्ये प्रतिमा फिट करा आणि पार्श्वभूमीला अस्पष्ट किंवा रंग लागू करा साधने व्यवस्था प्रकारानुसार गट साधने सानुकूल सूची व्यवस्थेऐवजी मुख्य स्क्रीनवर साधने त्यांच्या प्रकारानुसार गटबद्ध करा डीफॉल्ट मूल्ये सिस्टम बार दृश्यमानता स्वाइप करून सिस्टम बार दर्शवा सिस्टम बार लपविल्या असल्यास ते दर्शविण्यासाठी स्वाइप करणे सक्षम करते ऑटो सर्व लपवा सर्व दाखवा नव बार लपवा स्टेटस बार लपवा आवाज निर्मिती पर्लिन किंवा इतर प्रकारचे वेगवेगळे आवाज निर्माण करा वारंवारता आवाज प्रकार रोटेशन प्रकार फ्रॅक्टल प्रकार सप्तक अक्षता मिळवणे भारित सामर्थ्य पिंग पाँग सामर्थ्य अंतराचे कार्य परतीचा प्रकार जिटर डोमेन वार्प संरेखन सानुकूल फाइलनाव स्थान आणि फाइल नाव निवडा जे वर्तमान प्रतिमा जतन करण्यासाठी वापरले जाईल सानुकूल नावासह फोल्डरमध्ये जतन केले कोलाज मेकर 20 प्रतिमांपासून कोलाज बनवा कोलाज प्रकार बदलण्यासाठी प्रतिमा धरून ठेवा, हलवा आणि स्थिती समायोजित करण्यासाठी झूम करा रोटेशन अक्षम करा दोन-बोटांच्या जेश्चरने प्रतिमा फिरवण्यास प्रतिबंधित करते सीमांवर स्नॅपिंग सक्षम करा हलवल्यानंतर किंवा झूम केल्यानंतर, फ्रेमच्या कडा भरण्यासाठी प्रतिमा स्नॅप होतील हिस्टोग्राम तुम्हाला समायोजन करण्यात मदत करण्यासाठी RGB किंवा ब्राइटनेस इमेज हिस्टोग्राम ही प्रतिमा RGB आणि ब्राइटनेस हिस्टोग्राम तयार करण्यासाठी वापरली जाईल टेसरॅक्ट पर्याय टेसरॅक्ट इंजिनसाठी काही इनपुट व्हेरिएबल्स लागू करा सानुकूल पर्याय या पॅटर्ननुसार पर्याय प्रविष्ट केले पाहिजेत: \"--{option_name} {value}\" ऑटो क्रॉप मोफत कोपरे बहुभुजानुसार प्रतिमा क्रॉप करा, हे दृष्टीकोन देखील सुधारते इमेज बाऊंड्सवर जबरदस्ती पॉइंट्स पॉइंट्स प्रतिमेच्या सीमांद्वारे मर्यादित नसतील, हे अधिक अचूक दृष्टीकोन सुधारण्यासाठी उपयुक्त आहे मुखवटा काढलेल्या मार्गाखाली सामग्री जागरूक भरा बरे स्पॉट सर्कल कर्नल वापरा उघडत आहे बंद होत आहे मॉर्फोलॉजिकल ग्रेडियंट टॉप हॅट काळी टोपी टोन वक्र वक्र रीसेट करा वक्र डीफॉल्ट मूल्यावर परत आणले जातील रेखा शैली अंतर आकार डॅश डॉट डॅश शिक्का मारला झिगझॅग निर्दिष्ट अंतर आकारासह काढलेल्या मार्गावर डॅश रेषा काढते दिलेल्या मार्गावर बिंदू आणि डॅश रेषा काढतो फक्त डीफॉल्ट सरळ रेषा निर्दिष्ट अंतरासह मार्गावर निवडलेले आकार काढतो मार्गावर लहरी झिगझॅग काढतो झिगझॅग प्रमाण शॉर्टकट तयार करा पिन करण्यासाठी साधन निवडा शॉर्टकट म्हणून तुमच्या लाँचरच्या होम स्क्रीनवर टूल जोडले जाईल, आवश्यक वर्तन साध्य करण्यासाठी ते \"फाइल निवडणे वगळा\" सेटिंगसह एकत्र वापरा फ्रेम स्टॅक करू नका मागील फ्रेम्सची विल्हेवाट लावणे सक्षम करते, जेणेकरून ते एकमेकांवर स्टॅक करणार नाहीत क्रॉसफेड फ्रेम्स एकमेकांमध्ये क्रॉसफेड ​​केल्या जातील क्रॉसफेड ​​फ्रेम्स मोजतात थ्रेशोल्ड वन थ्रेशोल्ड दोन कॅनी मिरर 101 वर्धित झूम ब्लर लॅपलाशियन साधे सोबेल साधे हेल्पर ग्रिड अचूक हेरफेर करण्यात मदत करण्यासाठी रेखांकन क्षेत्राच्या वर सहाय्यक ग्रिड दर्शविते ग्रिड रंग सेल रुंदी सेलची उंची कॉम्पॅक्ट सिलेक्टर काही निवड नियंत्रणे कमी जागा घेण्यासाठी कॉम्पॅक्ट लेआउट वापरतील प्रतिमा कॅप्चर करण्यासाठी सेटिंग्जमध्ये कॅमेरा परवानगी द्या मांडणी मुख्य स्क्रीन शीर्षक स्थिर दर घटक (CRF) %1$s चे मूल्य म्हणजे मंद कॉम्प्रेशन, परिणामी तुलनेने लहान फाइल आकार. %2$s म्हणजे जलद कॉम्प्रेशन, परिणामी फाइल मोठी होते. लुट लायब्ररी LUTs चा संग्रह डाउनलोड करा, जो तुम्ही डाउनलोड केल्यानंतर अर्ज करू शकता LUT चे संकलन अद्यतनित करा (फक्त नवीन रांगेत असतील), जे तुम्ही डाउनलोड केल्यानंतर अर्ज करू शकता फिल्टरसाठी डीफॉल्ट प्रतिमा पूर्वावलोकन बदला पूर्वावलोकन प्रतिमा लपवा दाखवा स्लाइडर प्रकार फॅन्सी साहित्य २ फॅन्सी दिसणारा स्लाइडर. हा डीफॉल्ट पर्याय आहे एक साहित्य 2 स्लाइडर एक साहित्य आपण स्लाइडर अर्ज करा मध्यभागी संवाद बटणे शक्य असल्यास डायलॉग्सची बटणे डाव्या बाजूला ऐवजी मध्यभागी ठेवली जातील मुक्त स्रोत परवाने या ॲपमध्ये वापरलेल्या मुक्त स्रोत लायब्ररींचे परवाने पहा क्षेत्रफळ पिक्सेल एरिया रिलेशन वापरून रिसॅम्पलिंग. प्रतिमा नष्ट करण्यासाठी ही एक पसंतीची पद्धत असू शकते, कारण ती मोअर\'-मुक्त परिणाम देते. परंतु जेव्हा प्रतिमा झूम केली जाते, तेव्हा ती \"जवळपास\" पद्धतीसारखी असते. टोनमॅपिंग सक्षम करा % प्रविष्ट करा साइटवर प्रवेश करू शकत नाही, VPN वापरून पहा किंवा url बरोबर आहे का ते तपासा मार्कअप स्तर मुक्तपणे प्रतिमा, मजकूर आणि बरेच काही ठेवण्याच्या क्षमतेसह स्तर मोड स्तर संपादित करा प्रतिमेवरील स्तर पार्श्वभूमी म्हणून प्रतिमा वापरा आणि तिच्या वर विविध स्तर जोडा पार्श्वभूमीवरील स्तर पहिल्या पर्यायाप्रमाणेच परंतु प्रतिमेऐवजी रंगासह बीटा जलद सेटिंग्ज बाजूला प्रतिमा संपादित करताना निवडलेल्या बाजूला फ्लोटिंग स्ट्रिप जोडा, जे क्लिक केल्यावर जलद सेटिंग्ज उघडेल निवड साफ करा सेट करणे गट \"%1$s\" डीफॉल्टनुसार संकुचित केले जाईल सेटिंग गट \"%1$s\" डीफॉल्टनुसार विस्तारित केला जाईल बेस64 साधने Base64 स्ट्रिंगला इमेज डीकोड करा किंवा Base64 फॉरमॅटमध्ये इमेज एन्कोड करा बेस64 प्रदान केलेले मूल्य वैध Base64 स्ट्रिंग नाही रिक्त किंवा अवैध Base64 स्ट्रिंग कॉपी करू शकत नाही बेस64 पेस्ट करा बेस64 कॉपी करा Base64 स्ट्रिंग कॉपी किंवा सेव्ह करण्यासाठी इमेज लोड करा. जर तुमच्याकडे स्ट्रिंग असेल, तर तुम्ही इमेज मिळवण्यासाठी वर पेस्ट करू शकता बेस64 जतन करा शेअर बेस64 पर्याय क्रिया बेस64 आयात करा बेस64 क्रिया बाह्यरेखा जोडा निर्दिष्ट रंग आणि रुंदीसह मजकुराभोवती बाह्यरेखा जोडा बाह्यरेखा रंग बाह्यरेखा आकार रोटेशन फाइलनाव म्हणून चेकसम आउटपुट प्रतिमांना त्यांच्या डेटा चेकसमशी संबंधित नाव असेल मोफत सॉफ्टवेअर (भागीदार) Android अनुप्रयोगांच्या भागीदार चॅनेलमध्ये अधिक उपयुक्त सॉफ्टवेअर अल्गोरिदम चेकसम साधने चेकसमची तुलना करा, हॅशची गणना करा किंवा भिन्न हॅशिंग अल्गोरिदम वापरून फाइल्समधून हेक्स स्ट्रिंग तयार करा गणना करा मजकूर हॅश चेकसम निवडलेल्या अल्गोरिदमवर आधारित त्याच्या चेकसमची गणना करण्यासाठी फाइल निवडा निवडलेल्या अल्गोरिदमवर आधारित त्याच्या चेकसमची गणना करण्यासाठी मजकूर प्रविष्ट करा स्त्रोत चेकसम तुलना करण्यासाठी चेकसम जुळवा! फरक चेकसम समान आहेत, ते सुरक्षित असू शकतात चेकसम समान नाहीत, फाइल असुरक्षित असू शकते! मेष ग्रेडियंट्स मेश ग्रेडियंटचे ऑनलाइन संग्रह पहा फक्त TTF आणि OTF फॉन्ट आयात केले जाऊ शकतात फॉन्ट आयात करा (TTF/OTF) फॉन्ट निर्यात करा आयात केलेले फॉन्ट प्रयत्न जतन करताना त्रुटी, आउटपुट फोल्डर बदलण्याचा प्रयत्न करा फाइलनाव सेट केलेले नाही काहीही नाही सानुकूल पृष्ठे पृष्ठे निवड साधन निर्गमन पुष्टीकरण तुमच्याकडे विशिष्ट टूल्स वापरताना सेव्ह न केलेले बदल असल्यास आणि ते बंद करण्याचा प्रयत्न केल्यास, पुष्टी करा संवाद दर्शविला जाईल EXIF संपादित करा रीकंप्रेशनशिवाय सिंगल इमेजचा मेटाडेटा बदला उपलब्ध टॅग संपादित करण्यासाठी टॅप करा स्टिकर बदला फिट रुंदी फिट उंची बॅचची तुलना करा निवडलेल्या अल्गोरिदमवर आधारित त्याच्या चेकसमची गणना करण्यासाठी फाइल/फाईल्स निवडा फाइल्स निवडा निर्देशिका निवडा डोके लांबी स्केल मुद्रांक टाईमस्टॅम्प स्वरूप नमुना पॅडिंग प्रतिमा कटिंग प्रतिमेचा भाग कट करा आणि डाव्या भागांना उभ्या किंवा क्षैतिज रेषांनी एकत्र करा (विलोम असू शकते). अनुलंब पिव्होट लाइन क्षैतिज पिव्होट लाइन व्यस्त निवड कट क्षेत्राभोवती भाग विलीन करण्याऐवजी अनुलंब कट भाग सोडला जाईल कट क्षेत्राभोवती भाग विलीन करण्याऐवजी, क्षैतिज कट भाग सोडला जाईल मेष ग्रेडियंट्सचे संकलन सानुकूल नॉट्स आणि रिझोल्यूशनसह जाळी ग्रेडियंट तयार करा मेष ग्रेडियंट आच्छादन दिलेल्या प्रतिमांच्या शीर्षस्थानी मेश ग्रेडियंट तयार करा पॉइंट्स कस्टमायझेशन ग्रिड आकार ठराव X ठराव Y ठराव पिक्सेल बाय पिक्सेल रंग हायलाइट करा पिक्सेल तुलना प्रकार बारकोड स्कॅन करा उंचीचे प्रमाण बारकोड प्रकार B/W ला लागू करा बारकोड प्रतिमा पूर्णपणे काळा आणि पांढरी असेल आणि ॲपच्या थीमनुसार रंगीत नसेल कोणताही बारकोड स्कॅन करा (QR, EAN, AZTEC, …) आणि त्याची सामग्री मिळवा किंवा नवीन तयार करण्यासाठी तुमचा मजकूर पेस्ट करा बारकोड सापडला नाही जनरेट केलेला बारकोड येथे असेल ऑडिओ कव्हर्स ऑडिओ फायलींमधून अल्बम कव्हर प्रतिमा काढा, सर्वात सामान्य स्वरूप समर्थित आहेत सुरू करण्यासाठी ऑडिओ निवडा ऑडिओ निवडा कोणतेही कव्हर्स आढळले नाहीत नोंदी पाठवा ॲप लॉग फाइल शेअर करण्यासाठी क्लिक करा, हे मला समस्या शोधण्यात आणि समस्यांचे निराकरण करण्यात मदत करू शकते अरेरे… काहीतरी चूक झाली तुम्ही खालील पर्याय वापरून माझ्याशी संपर्क साधू शकता आणि मी उपाय शोधण्याचा प्रयत्न करेन.\n(लॉग जोडण्यास विसरू नका) फाइलवर लिहा प्रतिमांच्या बॅचमधून मजकूर काढा आणि एका मजकूर फाइलमध्ये संग्रहित करा मेटाडेटा वर लिहा प्रत्येक प्रतिमेतून मजकूर काढा आणि संबंधित फोटोंच्या EXIF ​​माहितीमध्ये ठेवा अदृश्य मोड तुमच्या इमेजच्या बाइट्समध्ये डोळा अदृश्य वॉटरमार्क तयार करण्यासाठी स्टेग्नोग्राफी वापरा LSB वापरा एलएसबी (लेस सिग्निफिकंट बिट) स्टेग्नोग्राफी पद्धत वापरली जाईल, अन्यथा एफडी (फ्रिक्वेंसी डोमेन) स्वयं लाल डोळे काढा पासवर्ड अनलॉक करा PDF संरक्षित आहे ऑपरेशन जवळजवळ पूर्ण झाले. आता रद्द करण्यासाठी ते रीस्टार्ट करणे आवश्यक आहे तारीख सुधारली तारीख सुधारित (उलट) आकार (उलट) MIME प्रकार MIME प्रकार (उलट) विस्तार विस्तार (उलट) तारीख जोडली जोडलेली तारीख (उलट) डावीकडून उजवीकडे उजवीकडून डावीकडे वरपासून खालपर्यंत तळापासून वरपर्यंत लिक्विड ग्लास अलीकडेच घोषित केलेल्या IOS 26 आणि त्याच्या लिक्विड ग्लास डिझाइन सिस्टमवर आधारित एक स्विच प्रतिमा निवडा किंवा खाली बेस64 डेटा पेस्ट/इंपोर्ट करा सुरू करण्यासाठी इमेज लिंक टाइप करा लिंक पेस्ट करा कॅलिडोस्कोप दुय्यम कोन बाजू चॅनेल मिक्स निळा हिरवा लाल निळा हिरवा लाल लाल मध्ये हिरव्या मध्ये निळ्या रंगात निळसर किरमिजी रंग पिवळा रंग हाफटोन समोच्च स्तर ऑफसेट व्होरोनोई क्रिस्टलाइझ आकार ताणणे यादृच्छिकता डिस्पेकल पसरणे DoG दुसरी त्रिज्या बरोबरी करा चमकणे चक्कर आणि चिमूटभर Pointillize सीमा रंग ध्रुवीय निर्देशांक ध्रुवीय कडे वळवा दुरुस्त करण्यासाठी ध्रुवीय वर्तुळात उलटा आवाज कमी करा साधे सोलाराइज विणणे X अंतर Y अंतर X रुंदी वाई रुंदी फिरणे रबर स्टॅम्प स्मीअर घनता मिसळा स्फेअर लेन्स विरूपण अपवर्तन निर्देशांक चाप स्प्रेड कोन चमचमीत किरण ASCII ग्रेडियंट मेरी शरद ऋतूतील हाड जेट हिवाळा महासागर उन्हाळा वसंत छान प्रकार HSV गुलाबी गरम शब्द मॅग्मा इन्फर्नो प्लाझ्मा विरिडीस नागरिक संधिप्रकाश ट्वायलाइट शिफ्ट झाला परिप्रेक्ष्य ऑटो डेस्क्यू पीक परवानगी द्या क्रॉप किंवा दृष्टीकोन निरपेक्ष टर्बो खोल हिरवा लेन्स सुधारणा लक्ष्य लेन्स प्रोफाइल फाइल JSON फॉरमॅटमध्ये तयार लेन्स प्रोफाइल डाउनलोड करा भाग टक्के JSON म्हणून निर्यात करा json प्रतिनिधित्व म्हणून पॅलेट डेटासह स्ट्रिंग कॉपी करा शिवण कोरीव काम होम स्क्रीन लॉक स्क्रीन अंगभूत वॉलपेपर निर्यात रिफ्रेश करा वर्तमान घर, लॉक आणि अंगभूत वॉलपेपर मिळवा सर्व फायलींमध्ये प्रवेश करण्याची परवानगी द्या, वॉलपेपर पुनर्प्राप्त करण्यासाठी हे आवश्यक आहे बाह्य संचयन परवानगी व्यवस्थापित करणे पुरेसे नाही, तुम्हाला तुमच्या प्रतिमांमध्ये प्रवेश देण्याची आवश्यकता आहे, \"सर्वांना अनुमती द्या\" निवडण्याचे सुनिश्चित करा फाइलनावमध्ये प्रीसेट जोडा इमेज फाइलनावामध्ये निवडलेल्या प्रीसेटसह प्रत्यय जोडते फाइलनावामध्ये इमेज स्केल मोड जोडा इमेज फाइलनावामध्ये निवडलेल्या इमेज स्केल मोडसह प्रत्यय जोडतो Ascii कला चित्राला ascii मजकुरात रूपांतरित करा जे प्रतिमेसारखे दिसेल परम्स काही प्रकरणांमध्ये चांगल्या परिणामासाठी प्रतिमेवर नकारात्मक फिल्टर लागू करते स्क्रीनशॉटवर प्रक्रिया करत आहे स्क्रीनशॉट कॅप्चर केला नाही, पुन्हा प्रयत्न करा जतन करणे वगळले %1$s फायली वगळल्या मोठे असल्यास वगळा परिणामी फाइलचा आकार मूळपेक्षा मोठा असल्यास प्रतिमा जतन करणे वगळण्यासाठी काही साधनांना परवानगी दिली जाईल. कॅलेंडर इव्हेंट संपर्क करा ईमेल स्थान फोन मजकूर एसएमएस URL वाय-फाय नेटवर्क उघडा N/A SSID फोन संदेश पत्ता विषय शरीर नाव संघटना शीर्षक फोन ईमेल्स URLs पत्ते सारांश वर्णन स्थान आयोजक प्रारंभ तारीख शेवटची तारीख स्थिती अक्षांश रेखांश बारकोड तयार करा बारकोड संपादित करा वाय-फाय कॉन्फिगरेशन सुरक्षा संपर्क निवडा निवडलेला संपर्क वापरून ऑटोफिल करण्यासाठी सेटिंग्जमध्ये संपर्कांना परवानगी द्या संपर्क माहिती नाव मधले नाव आडनाव उच्चार फोन जोडा ईमेल जोडा पत्ता जोडा वेबसाइट वेबसाइट जोडा स्वरूपित नाव ही प्रतिमा वर बारकोड ठेवण्यासाठी वापरली जाईल कोड सानुकूलन ही प्रतिमा QR कोडच्या मध्यभागी लोगो म्हणून वापरली जाईल लोगो लोगो पॅडिंग लोगो आकार लोगोचे कोपरे चौथा डोळा खालच्या टोकाच्या कोपर्यात चौथा डोळा जोडून क्यूआर कोडमध्ये डोळ्याची सममिती जोडते पिक्सेल आकार फ्रेम आकार चेंडू आकार त्रुटी सुधारण्याची पातळी गडद रंग हलका रंग हायपर ओएस Xiaomi HyperOS सारखी शैली मुखवटा नमुना हा कोड स्कॅन करण्यायोग्य नसू शकतो, तो सर्व उपकरणांसह वाचनीय बनवण्यासाठी देखावा पॅराम्स बदला टूल्स अधिक कॉम्पॅक्ट होण्यासाठी होम स्क्रीन ॲप लाँचरसारखे दिसतील लाँचर मोड निवडलेल्या ब्रश आणि शैलीसह क्षेत्र भरते पूर भरणे फवारणी ग्राफिटी शैलीचा मार्ग काढतो चौरस कण स्प्रे कण वर्तुळांऐवजी चौरस आकाराचे असतील पॅलेट साधने इमेजमधून तुम्ही पॅलेट करता ते मूलभूत/साहित्य तयार करा किंवा वेगवेगळ्या पॅलेट फॉरमॅटमध्ये आयात/निर्यात करा पॅलेट संपादित करा विविध स्वरूपांमध्ये पॅलेट निर्यात/आयात करा रंगाचे नाव पॅलेट नाव पॅलेट स्वरूप व्युत्पन्न केलेले पॅलेट वेगवेगळ्या फॉरमॅटमध्ये एक्सपोर्ट करा वर्तमान पॅलेटमध्ये नवीन रंग जोडतो %1$s स्वरूप पॅलेट नाव प्रदान करण्यास समर्थन देत नाही Play Store धोरणांमुळे, हे वैशिष्ट्य सध्याच्या बिल्डमध्ये समाविष्ट केले जाऊ शकत नाही. या कार्यक्षमतेमध्ये प्रवेश करण्यासाठी, कृपया वैकल्पिक स्रोतावरून इमेजटूलबॉक्स डाउनलोड करा. आपण खाली GitHub वर उपलब्ध बिल्ड शोधू शकता. Github पृष्ठ उघडा निवडलेल्या फोल्डरमध्ये सेव्ह करण्याऐवजी मूळ फाइल नवीन फाइलने बदलली जाईल लपवलेला वॉटरमार्क मजकूर आढळला लपलेली वॉटरमार्क प्रतिमा शोधली ही प्रतिमा लपलेली होती जनरेटिव्ह इनपेंटिंग OpenCV वर विसंबून न राहता, AI मॉडेलचा वापर करून इमेजमधील वस्तू काढण्याची तुम्हाला अनुमती देते. हे वैशिष्ट्य वापरण्यासाठी, ॲप GitHub वरून आवश्यक मॉडेल (~200 MB) डाउनलोड करेल OpenCV वर विसंबून न राहता, AI मॉडेलचा वापर करून इमेजमधील वस्तू काढण्याची तुम्हाला अनुमती देते. हे दीर्घकाळ चालणारे ऑपरेशन असू शकते त्रुटी पातळी विश्लेषण ल्युमिनन्स ग्रेडियंट सरासरी अंतर कॉपी मूव्ह डिटेक्शन राखून ठेवा गुणांक क्लिपबोर्ड डेटा खूप मोठा आहे कॉपी करण्यासाठी डेटा खूप मोठा आहे साधे विणणे पिक्सेलीकरण स्टॅगर्ड पिक्सेलीकरण क्रॉस पिक्सेलायझेशन मायक्रो मॅक्रो पिक्सेलायझेशन ऑर्बिटल पिक्सेलायझेशन व्होर्टेक्स पिक्सेलायझेशन पल्स ग्रिड पिक्सेलायझेशन न्यूक्लियस पिक्सेलायझेशन रेडियल विणणे पिक्सेलायझेशन uri \"%1$s\" उघडू शकत नाही हिमवर्षाव मोड सक्षम केले सीमा फ्रेम ग्लिच प्रकार चॅनल शिफ्ट कमाल ऑफसेट VHS ब्लॉक ग्लिच ब्लॉक आकार CRT वक्रता वक्रता क्रोमा पिक्सेल वितळणे कमाल ड्रॉप एआय टूल्स AI मॉडेलद्वारे प्रतिमांवर प्रक्रिया करण्यासाठी विविध साधने जसे की आर्टिफॅक्ट काढून टाकणे किंवा डिनोइझिंग कॉम्प्रेशन, दातेरी रेषा व्यंगचित्रे, प्रसारण संक्षेप सामान्य कम्प्रेशन, सामान्य आवाज रंगहीन कार्टूनचा आवाज वेगवान, सामान्य कॉम्प्रेशन, सामान्य आवाज, ॲनिमेशन/कॉमिक्स/ॲनिमे पुस्तक स्कॅनिंग एक्सपोजर सुधारणा सामान्य कॉम्प्रेशन, रंगीत प्रतिमांमध्ये सर्वोत्तम सामान्य कॉम्प्रेशन, ग्रेस्केल प्रतिमांमध्ये सर्वोत्तम सामान्य कॉम्प्रेशन, ग्रेस्केल प्रतिमा, अधिक मजबूत सामान्य आवाज, रंगीत प्रतिमा सामान्य आवाज, रंगीत प्रतिमा, चांगले तपशील सामान्य आवाज, ग्रेस्केल प्रतिमा सामान्य आवाज, ग्रेस्केल प्रतिमा, अधिक मजबूत सामान्य आवाज, ग्रेस्केल प्रतिमा, सर्वात मजबूत सामान्य कम्प्रेशन सामान्य कम्प्रेशन टेक्स्चरायझेशन, h264 कॉम्प्रेशन व्हीएचएस कॉम्प्रेशन नॉन-स्टँडर्ड कॉम्प्रेशन (cinepak, msvideo1, roq) बिंक कॉम्प्रेशन, भूमितीवर चांगले बिंक कॉम्प्रेशन, मजबूत बिंक कॉम्प्रेशन, मऊ, तपशील राखून ठेवते पायर्या-चरण प्रभाव काढून टाकणे, गुळगुळीत करणे स्कॅन केलेली कला/रेखाचित्रे, सौम्य कॉम्प्रेशन, मोअर रंग बँडिंग हळू, हाफटोन काढून टाकत आहे ग्रेस्केल/bw प्रतिमांसाठी सामान्य कलरलायझर, चांगल्या परिणामांसाठी DDCcolor वापरा कडा काढणे ओव्हरशार्पनिंग काढून टाकते मंद, विचलित अँटी-अलायझिंग, सामान्य कलाकृती, CGI KDM003 स्कॅन प्रक्रिया लाइटवेट इमेज एन्हांसमेंट मॉडेल कॉम्प्रेशन आर्टिफॅक्ट काढणे कॉम्प्रेशन आर्टिफॅक्ट काढणे गुळगुळीत परिणामांसह मलमपट्टी काढणे हाफटोन नमुना प्रक्रिया डिथर नमुना काढणे V3 JPEG आर्टिफॅक्ट काढणे V2 H.264 पोत वाढवणे VHS शार्पनिंग आणि एन्हांसमेंट विलीन होत आहे भाग आकार ओव्हरलॅप आकार %1$s px पेक्षा जास्त प्रतिमा कापल्या जातील आणि भागांमध्ये प्रक्रिया केल्या जातील, दृश्यमान शिवण टाळण्यासाठी त्यांना ओव्हरलॅप मिश्रित करते. मोठ्या आकारामुळे लो-एंड डिव्हाइसेससह अस्थिरता येऊ शकते सुरू करण्यासाठी एक निवडा तुम्ही %1$s मॉडेल हटवू इच्छिता? तुम्हाला ते पुन्हा डाउनलोड करावे लागेल पुष्टी करा मॉडेल्स डाउनलोड केलेले मॉडेल उपलब्ध मॉडेल्स तयारी करत आहे सक्रिय मॉडेल सत्र उघडण्यात अयशस्वी फक्त .onnx/.ort मॉडेल आयात केले जाऊ शकतात मॉडेल आयात करा पुढील वापरासाठी सानुकूल onnx मॉडेल आयात करा, फक्त onnx/ort मॉडेल स्वीकारले जातात, जवळजवळ सर्व esrgan सारख्या प्रकारांना समर्थन देते आयात केलेले मॉडेल सामान्य आवाज, रंगीत प्रतिमा सामान्य आवाज, रंगीत प्रतिमा, अधिक मजबूत सामान्य आवाज, रंगीत प्रतिमा, सर्वात मजबूत डिथरिंग आर्टिफॅक्ट्स आणि कलर बँडिंग कमी करते, गुळगुळीत ग्रेडियंट आणि सपाट रंग क्षेत्र सुधारते. नैसर्गिक रंग जतन करून संतुलित हायलाइटसह प्रतिमेची चमक आणि कॉन्ट्रास्ट वाढवते. तपशील ठेवताना आणि जास्त एक्सपोजर टाळताना गडद प्रतिमा उजळतात. अत्यधिक रंग टोनिंग काढून टाकते आणि अधिक तटस्थ आणि नैसर्गिक रंग संतुलन पुनर्संचयित करते. बारीक तपशील आणि पोत जतन करण्यावर भर देऊन पॉसॉन-आधारित आवाज टोनिंग लागू करते. नितळ आणि कमी आक्रमक व्हिज्युअल परिणामांसाठी सॉफ्ट पॉसॉन नॉइज टोनिंग लागू करते. एकसमान आवाज टोनिंग तपशील संरक्षण आणि प्रतिमा स्पष्टतेवर केंद्रित आहे. सूक्ष्म पोत आणि गुळगुळीत दिसण्यासाठी सौम्य एकसमान आवाज टोनिंग. कलाकृती पुन्हा रंगवून आणि प्रतिमा सुसंगतता सुधारून खराब झालेले किंवा असमान भाग दुरुस्त करा. लाइटवेट डीबँडिंग मॉडेल जे कमीतकमी कार्यप्रदर्शन खर्चासह रंग बँडिंग काढून टाकते. सुधारित स्पष्टतेसाठी अतिशय उच्च कॉम्प्रेशन आर्टिफॅक्ट (0-20% गुणवत्ता) असलेल्या प्रतिमा ऑप्टिमाइझ करते. उच्च कॉम्प्रेशन आर्टिफॅक्ट्स (20-40% गुणवत्ता), तपशील पुनर्संचयित करून आणि आवाज कमी करून प्रतिमा सुधारते. मध्यम कम्प्रेशन (40-60% गुणवत्ता) सह प्रतिमा सुधारते, तीक्ष्णता आणि गुळगुळीतपणा संतुलित करते. सूक्ष्म तपशील आणि पोत वर्धित करण्यासाठी प्रकाश कॉम्प्रेशन (60-80% गुणवत्ता) सह प्रतिमा परिष्कृत करते. नैसर्गिक देखावा आणि तपशील जतन करून जवळच्या-तोटारहित प्रतिमा (80-100% गुणवत्ता) किंचित वाढवते. साधे आणि जलद रंगीकरण, व्यंगचित्रे, आदर्श नाही प्रतिमेची अस्पष्टता किंचित कमी करते, कलाकृतींचा परिचय न करता तीक्ष्णता सुधारते. लांब चालणारी ऑपरेशन्स इमेजवर प्रक्रिया करत आहे प्रक्रिया करत आहे अत्यंत कमी गुणवत्तेच्या (0-20%) प्रतिमांमधील भारी JPEG कॉम्प्रेशन आर्टिफॅक्ट्स काढून टाकते. अत्यंत संकुचित प्रतिमा (20-40%) मध्ये मजबूत JPEG कलाकृती कमी करते. प्रतिमा तपशील (40-60%) जतन करताना मध्यम JPEG कलाकृती साफ करते. हलक्या JPEG कलाकृतींना उच्च दर्जाच्या प्रतिमांमध्ये परिष्कृत करते (60-80%). जवळच्या-तोटारहित प्रतिमांमध्ये (80-100%) किरकोळ JPEG कलाकृती सूक्ष्मपणे कमी करते. बारीक तपशील आणि पोत वाढवते, जड कलाकृतींशिवाय समजलेली तीक्ष्णता सुधारते. प्रक्रिया पूर्ण झाली प्रक्रिया अयशस्वी नैसर्गिक देखावा ठेवताना त्वचेचे पोत आणि तपशील वाढवते, गतीसाठी अनुकूल. JPEG कॉम्प्रेशन आर्टिफॅक्ट्स काढून टाकते आणि कॉम्प्रेस केलेल्या फोटोंसाठी इमेज क्वालिटी रिस्टोअर करते. कमी प्रकाशाच्या स्थितीत घेतलेल्या फोटोंमधील ISO आवाज कमी करते, तपशील जतन करते. ओव्हरएक्सपोज केलेले किंवा \"जंबो\" हायलाइट्स दुरुस्त करते आणि चांगले टोनल बॅलन्स पुनर्संचयित करते. हलके आणि जलद रंगीकरण मॉडेल जे ग्रेस्केल प्रतिमांमध्ये नैसर्गिक रंग जोडते. DEJPEG Denoise रंगीत करा कलाकृती वर्धित करा ॲनिमी स्कॅन करा अपस्केल सामान्य प्रतिमांसाठी X4 अपस्केलर; लहान मॉडेल जे कमी GPU आणि वेळ वापरते, मध्यम deblur आणि denoise सह. सामान्य प्रतिमांसाठी X2 अपस्केलर, पोत आणि नैसर्गिक तपशील जतन. वर्धित पोत आणि वास्तववादी परिणामांसह सामान्य प्रतिमांसाठी X4 अपस्केलर. ऍनिम ​​प्रतिमांसाठी एक्स 4 अपस्केलर ऑप्टिमाइझ; तीक्ष्ण रेषा आणि तपशीलांसाठी 6 RRDB ब्लॉक. MSE नुकसानासह X4 अपस्केलर, सामान्य प्रतिमांसाठी नितळ परिणाम आणि कमी कलाकृती निर्माण करतो. एक्स 4 अपस्केलर ॲनिम प्रतिमांसाठी अनुकूलित; तीक्ष्ण तपशील आणि गुळगुळीत रेषांसह 4B32F प्रकार. सामान्य प्रतिमांसाठी X4 UltraSharp V2 मॉडेल; तीक्ष्णता आणि स्पष्टतेवर जोर देते. X4 अल्ट्राशार्प V2 लाइट; वेगवान आणि लहान, कमी GPU मेमरी वापरताना तपशील जतन करते. द्रुत पार्श्वभूमी काढण्यासाठी हलके मॉडेल. संतुलित कामगिरी आणि अचूकता. पोर्ट्रेट, वस्तू आणि दृश्यांसह कार्य करते. बहुतेक वापराच्या प्रकरणांसाठी शिफारस केलेले. बीजी काढा क्षैतिज सीमा जाडी अनुलंब सीमा जाडी %1$s रंग %1$s रंग वर्तमान मॉडेल चंकिंगला समर्थन देत नाही, प्रतिमेवर मूळ परिमाणांमध्ये प्रक्रिया केली जाईल, यामुळे उच्च मेमरी वापर आणि लो-एंड उपकरणांसह समस्या उद्भवू शकतात चंकिंग अक्षम केले आहे, प्रतिमेवर मूळ परिमाणांमध्ये प्रक्रिया केली जाईल, यामुळे उच्च मेमरी वापर आणि कमी-अंत उपकरणांसह समस्या उद्भवू शकतात परंतु अनुमानांवर चांगले परिणाम देऊ शकतात चंकिंग पार्श्वभूमी काढण्यासाठी उच्च-अचूकता प्रतिमा विभाजन मॉडेल लहान मेमरी वापरासह जलद पार्श्वभूमी काढण्यासाठी U2Net ची हलकी आवृत्ती. पूर्ण DDCcolor मॉडेल किमान कलाकृतींसह सामान्य प्रतिमांसाठी उच्च-गुणवत्तेचे रंगीकरण प्रदान करते. सर्व रंगीकरण मॉडेल्सची सर्वोत्तम निवड. DDColor प्रशिक्षित आणि खाजगी कलात्मक डेटासेट; कमी अवास्तव कलर आर्टिफॅक्ट्ससह वैविध्यपूर्ण आणि कलात्मक रंगीकरण परिणाम तयार करते. अचूक पार्श्वभूमी काढण्यासाठी स्विन ट्रान्सफॉर्मरवर आधारित लाइटवेट BiRefNet मॉडेल. तीक्ष्ण कडा आणि उत्कृष्ट तपशील संरक्षणासह उच्च-गुणवत्तेची पार्श्वभूमी काढणे, विशेषत: जटिल वस्तू आणि अवघड पार्श्वभूमीवर. पार्श्वभूमी काढण्याचे मॉडेल जे गुळगुळीत कडा असलेले अचूक मुखवटे तयार करते, सामान्य वस्तूंसाठी योग्य आणि मध्यम तपशील संरक्षण. मॉडेल आधीच डाउनलोड केले आहे मॉडेल यशस्वीरित्या आयात केले प्रकार कीवर्ड खूप जलद सामान्य मंद खूप हळू टक्केवारीची गणना करा किमान मूल्य %1$s आहे बोटांनी रेखाटून प्रतिमा विकृत करा ताना कडकपणा वार्प मोड हलवा वाढतात संकुचित करा फिरणे CW फिरणे CCW फिकट ताकद शीर्ष ड्रॉप तळ ड्रॉप ड्रॉप सुरू करा समाप्ती ड्रॉप डाउनलोड करत आहे गुळगुळीत आकार गुळगुळीत, अधिक नैसर्गिक आकारांसाठी मानक गोलाकार आयतांऐवजी सुपरएलिप्स वापरा आकार प्रकार कट गोलाकार गुळगुळीत गोलाकार न करता तीक्ष्ण कडा क्लासिक गोलाकार कोपरे आकार प्रकार कोपऱ्यांचा आकार गिलहरी मोहक गोलाकार UI घटक फाइलनाव स्वरूप सानुकूल मजकूर फाइल नावाच्या अगदी सुरुवातीला ठेवलेला आहे, प्रकल्प नावे, ब्रँड किंवा वैयक्तिक टॅगसाठी योग्य. मूळ फाइल नाव विस्ताराशिवाय वापरते, तुम्हाला स्त्रोत ओळख अबाधित ठेवण्यास मदत करते. प्रतिमेची रुंदी पिक्सेलमध्ये, रिझोल्यूशन बदलांचा मागोवा घेण्यासाठी किंवा परिणाम स्केलिंगसाठी उपयुक्त. प्रतिमेची उंची पिक्सेलमध्ये, गुणोत्तर किंवा निर्यातीसह कार्य करताना उपयुक्त. अद्वितीय फाइलनावांची हमी देण्यासाठी यादृच्छिक अंक व्युत्पन्न करते; डुप्लिकेट विरूद्ध अतिरिक्त सुरक्षिततेसाठी अधिक अंक जोडा. बॅच निर्यातीसाठी स्वयं-वाढीव काउंटर, एका सत्रात एकाधिक प्रतिमा जतन करताना आदर्श. फाइलनावामध्ये लागू केलेले प्रीसेट नाव समाविष्ट करते जेणेकरुन तुम्ही इमेजवर प्रक्रिया कशी केली हे सहज लक्षात ठेवू शकता. प्रक्रिया करताना वापरलेला प्रतिमा स्केलिंग मोड प्रदर्शित करते, आकार बदललेल्या, क्रॉप केलेल्या किंवा फिट केलेल्या प्रतिमा वेगळे करण्यात मदत करते. फाइल नावाच्या शेवटी ठेवलेला सानुकूल मजकूर, _v2, _edited किंवा _final सारख्या आवृत्तीसाठी उपयुक्त. फाइल विस्तार (png, jpg, webp, इ.), वास्तविक जतन केलेल्या स्वरूपाशी स्वयंचलितपणे जुळणारा. एक सानुकूल करण्यायोग्य टाइमस्टॅम्प जो तुम्हाला परिपूर्ण क्रमवारीसाठी जावा विनिर्देशानुसार तुमचे स्वतःचे स्वरूप परिभाषित करू देतो. फ्लिंग प्रकार Android नेटिव्ह iOS शैली गुळगुळीत वक्र द्रुत थांबा उछाल फ्लोटी स्नॅपी अल्ट्रा स्मूथ अनुकूल प्रवेशयोग्यता जागरूक कमी गती मूळ Android स्क्रोल भौतिकशास्त्र सामान्य वापरासाठी संतुलित, गुळगुळीत स्क्रोलिंग उच्च घर्षण iOS-सारखे स्क्रोल वर्तन वेगळ्या स्क्रोल अनुभवासाठी अद्वितीय स्प्लाइन वक्र द्रुत थांबा सह अचूक स्क्रोलिंग खेळकर, प्रतिसाद देणारा बाऊन्सी स्क्रोल सामग्री ब्राउझिंगसाठी लांब, ग्लाइडिंग स्क्रोल परस्परसंवादी UI साठी द्रुत, प्रतिसादात्मक स्क्रोलिंग विस्तारित गतीसह प्रीमियम गुळगुळीत स्क्रोलिंग फ्लिंग वेगावर आधारित भौतिकशास्त्र समायोजित करते सिस्टम प्रवेशयोग्यता सेटिंग्जचा आदर करते प्रवेशयोग्यता गरजांसाठी किमान गती प्राथमिक ओळी प्रत्येक पाचव्या ओळीत जाड ओळ जोडते रंग भरा लपलेली साधने शेअरसाठी लपवलेली साधने रंगीत लायब्ररी रंगांचा एक विशाल संग्रह ब्राउझ करा नैसर्गिक तपशिलांची देखभाल करताना प्रतिमांना तीक्ष्ण करते आणि अस्पष्टता काढून टाकते, फोकस नसलेले फोटो निश्चित करण्यासाठी आदर्श. पूर्वी आकार बदललेल्या प्रतिमा हुशारीने पुनर्संचयित करते, गमावलेले तपशील आणि पोत पुनर्प्राप्त करते. लाइव्ह-ॲक्शन सामग्रीसाठी ऑप्टिमाइझ केलेले, कॉम्प्रेशन आर्टिफॅक्ट्स कमी करते आणि मूव्ही/टीव्ही शो फ्रेममधील बारीकसारीक तपशील वाढवते. VHS-गुणवत्तेचे फुटेज HD मध्ये रूपांतरित करते, टेपचा आवाज काढून टाकते आणि विंटेज फील जतन करून रिझोल्यूशन वाढवते. मजकूर-भारी प्रतिमा आणि स्क्रीनशॉटसाठी खास, वर्ण धारदार करते आणि वाचनीयता सुधारते. विविध डेटासेटवर प्रशिक्षित प्रगत अपस्केलिंग, सामान्य-उद्देश फोटो वर्धित करण्यासाठी उत्कृष्ट. वेब-संकुचित फोटोंसाठी ऑप्टिमाइझ केलेले, JPEG कलाकृती काढून टाकते आणि नैसर्गिक स्वरूप पुनर्संचयित करते. वेब फोटोंसाठी सुधारित आवृत्ती उत्तम पोत संरक्षण आणि कलाकृती कमी करणे. ड्युअल एग्रीगेशन ट्रान्सफॉर्मर तंत्रज्ञानासह 2x अपस्केलिंग, तीक्ष्णता आणि नैसर्गिक तपशील राखते. प्रगत ट्रान्सफॉर्मर आर्किटेक्चरचा वापर करून 3x अपस्केलिंग, मध्यम विस्तार गरजांसाठी आदर्श. अत्याधुनिक ट्रान्सफॉर्मर नेटवर्कसह 4x उच्च-गुणवत्तेचे अपस्केलिंग, मोठ्या प्रमाणात सूक्ष्म तपशील जतन करते. फोटोंमधून अस्पष्टता/आवाज आणि शेक काढून टाकते. सामान्य हेतू परंतु फोटोंसाठी सर्वोत्तम. Swin2SR ट्रान्सफॉर्मर वापरून कमी-गुणवत्तेच्या प्रतिमा पुनर्संचयित करते, BSRGAN डिग्रेडेशनसाठी ऑप्टिमाइझ केलेले. हेवी कॉम्प्रेशन आर्टिफॅक्ट्स निश्चित करण्यासाठी आणि 4x स्केलवर तपशील वाढविण्यासाठी उत्तम. BSRGAN डिग्रेडेशनवर प्रशिक्षित स्विनआयआर ट्रान्सफॉर्मरसह 4x अपस्केलिंग. फोटो आणि जटिल दृश्यांमध्ये तीक्ष्ण पोत आणि अधिक नैसर्गिक तपशीलांसाठी GAN वापरते. मार्ग पीडीएफ मर्ज करा एका दस्तऐवजात अनेक पीडीएफ फाइल्स एकत्र करा फाईल्स ऑर्डर pp पीडीएफ विभाजित करा पीडीएफ दस्तऐवजातून विशिष्ट पृष्ठे काढा पीडीएफ फिरवा पृष्ठ अभिमुखता कायमचे निश्चित करा पृष्ठे पीडीएफची पुनर्रचना करा पृष्ठे पुन्हा क्रमाने लावण्यासाठी ड्रॅग आणि ड्रॉप करा पृष्ठे धरा आणि ड्रॅग करा पृष्ठ क्रमांक तुमच्या दस्तऐवजांमध्ये आपोआप क्रमांकन जोडा लेबल स्वरूप PDF टू टेक्स्ट (OCR) तुमच्या PDF दस्तऐवजांमधून साधा मजकूर काढा ब्रँडिंग किंवा सुरक्षिततेसाठी सानुकूल मजकूर आच्छादित करा स्वाक्षरी कोणत्याही दस्तऐवजात तुमची इलेक्ट्रॉनिक स्वाक्षरी जोडा हे स्वाक्षरी म्हणून वापरले जाईल पीडीएफ अनलॉक करा तुमच्या संरक्षित फाइल्समधून पासवर्ड काढा पीडीएफ संरक्षित करा मजबूत एन्क्रिप्शनसह तुमचे दस्तऐवज सुरक्षित करा यश पीडीएफ अनलॉक, तुम्ही सेव्ह किंवा शेअर करू शकता पीडीएफ दुरुस्त करा खराब झालेले किंवा न वाचलेले दस्तऐवज दुरुस्त करण्याचा प्रयत्न ग्रेस्केल सर्व दस्तऐवज एम्बेड केलेल्या प्रतिमा ग्रेस्केलमध्ये रूपांतरित करा पीडीएफ कॉम्प्रेस करा सुलभ सामायिकरणासाठी तुमचा दस्तऐवज फाइल आकार ऑप्टिमाइझ करा इमेजटूलबॉक्स अंतर्गत क्रॉस-रेफरन्स टेबलची पुनर्बांधणी करते आणि स्क्रॅचमधून फाइल संरचना पुन्हा निर्माण करते. हे \\"उघडले जाऊ शकत नाही\\" अशा अनेक फाइल्समध्ये प्रवेश पुनर्संचयित करू शकते. हे साधन सर्व दस्तऐवज प्रतिमा ग्रेस्केलमध्ये रूपांतरित करते. मुद्रित करण्यासाठी आणि फाइल आकार कमी करण्यासाठी सर्वोत्तम मेटाडेटा चांगल्या गोपनीयतेसाठी दस्तऐवज गुणधर्म संपादित करा टॅग्ज निर्माता लेखक कीवर्ड निर्माता गोपनीयता खोल स्वच्छ या दस्तऐवजासाठी सर्व उपलब्ध मेटाडेटा साफ करा पान खोल OCR दस्तऐवजातून मजकूर काढा आणि टेसरॅक्ट इंजिन वापरून एका मजकूर फाइलमध्ये संग्रहित करा सर्व पृष्ठे काढू शकत नाही PDF पृष्ठे काढा पीडीएफ दस्तऐवजातून विशिष्ट पृष्ठे काढा काढण्यासाठी टॅप करा स्वहस्ते पीडीएफ क्रॉप करा दस्तऐवजाची पृष्ठे कोणत्याही मर्यादेपर्यंत क्रॉप करा सपाट पीडीएफ दस्तऐवजाची पृष्ठे रास्टर करून PDF बदलण्यायोग्य बनवा कॅमेरा सुरू करू शकलो नाही. कृपया परवानग्या तपासा आणि ते दुसऱ्या ॲपद्वारे वापरले जात नसल्याचे सुनिश्चित करा. प्रतिमा काढा PDF मध्ये एम्बेड केलेल्या प्रतिमा त्यांच्या मूळ रिझोल्यूशनवर काढा या PDF फाइलमध्ये कोणत्याही एम्बेड केलेल्या प्रतिमा नाहीत हे साधन प्रत्येक पृष्ठ स्कॅन करते आणि पूर्ण-गुणवत्तेच्या स्त्रोत प्रतिमा पुनर्प्राप्त करते — दस्तऐवजांमधून मूळ जतन करण्यासाठी योग्य स्वाक्षरी काढा पेन परम्स दस्तऐवजांवर ठेवण्यासाठी प्रतिमा म्हणून स्वतःची स्वाक्षरी वापरा झिप पीडीएफ दिलेल्या मध्यांतरासह दस्तऐवज विभाजित करा आणि नवीन दस्तऐवज झिप आर्काइव्हमध्ये पॅक करा मध्यांतर PDF प्रिंट करा सानुकूल पृष्ठ आकारासह मुद्रणासाठी दस्तऐवज तयार करा प्रति पत्रक पृष्ठे अभिमुखता पृष्ठ आकार समास तजेला मऊ गुडघा ॲनिम आणि कार्टूनसाठी ऑप्टिमाइझ केलेले. सुधारित नैसर्गिक रंग आणि कमी कलाकृतींसह जलद अपस्केलिंग Samsung One UI 7 सारखी शैली इच्छित मूल्याची गणना करण्यासाठी येथे मूलभूत गणित चिन्हे प्रविष्ट करा (उदा. (5+5)*10) गणित अभिव्यक्ती %1$s पर्यंत प्रतिमा घ्या तारीख वेळ ठेवा तारीख आणि वेळेशी संबंधित exif टॅग नेहमी जतन करा, Keep exif पर्यायापेक्षा स्वतंत्रपणे कार्य करते अल्फा स्वरूपांसाठी पार्श्वभूमी रंग अल्फा सपोर्टसह प्रत्येक इमेज फॉरमॅटसाठी पार्श्वभूमी रंग सेट करण्याची क्षमता जोडते, जेव्हा हे अक्षम केले जाते तेव्हा हे केवळ अल्फा नसलेल्यांसाठी उपलब्ध असते प्रकल्प उघडा पूर्वी जतन केलेला प्रतिमा टूलबॉक्स प्रकल्प संपादित करणे सुरू ठेवा इमेज टूलबॉक्स प्रोजेक्ट उघडण्यात अक्षम इमेज टूलबॉक्स प्रोजेक्टमध्ये प्रोजेक्ट डेटा गहाळ आहे इमेज टूलबॉक्स प्रोजेक्ट दूषित झाला आहे असमर्थित प्रतिमा टूलबॉक्स प्रकल्प आवृत्ती: %1$d प्रकल्प जतन करा संपादन करण्यायोग्य प्रकल्प फाइलमध्ये स्तर, पार्श्वभूमी आणि संपादित इतिहास संग्रहित करा उघडण्यात अयशस्वी शोधण्यायोग्य PDF वर लिहा इमेज बॅचमधील मजकूर ओळखा आणि शोधण्यायोग्य PDF प्रतिमा आणि निवडण्यायोग्य मजकूर स्तरासह जतन करा लेयर अल्फा क्षैतिज फ्लिप उभ्या फ्लिप कुलूप सावली जोडा सावलीचा रंग मजकूर भूमिती तीक्ष्ण शैलीकरणासाठी मजकूर स्ट्रेच किंवा स्क्यू करा स्केल एक्स स्क्यू एक्स भाष्ये काढा पीडीएफ पेजेसमधून निवडक भाष्य प्रकार जसे की लिंक्स, टिप्पण्या, हायलाइट्स, आकार किंवा फॉर्म फील्ड काढून टाका हायपरलिंक्स फाइल संलग्नक ओळी पॉपअप शिक्के आकार मजकूर नोट्स मजकूर मार्कअप फॉर्म फील्ड मार्कअप अज्ञात भाष्ये गट रद्द करा कॉन्फिगर करण्यायोग्य रंग आणि ऑफसेटसह लेयरच्या मागे ब्लर शॅडो जोडा ================================================ FILE: core/resources/src/main/res/values-night/colors.xml ================================================ #2A3025 #ABE87C ================================================ FILE: core/resources/src/main/res/values-night-v31/colors.xml ================================================ @android:color/system_accent2_800 @android:color/system_accent1_100 ================================================ FILE: core/resources/src/main/res/values-nl/strings.xml ================================================ Formaat %1$s Laden… Kies afbeelding om te starten Extensie Type formaatswijziging Expliciet Flexibel Afbeelding kiezen Wil je de app echt afsluiten? App sluiten Blijven Afbeeldingswijzigingen worden teruggezet naar de oorspronkelijke waarden Waarden correct teruggezet Geen EXIF data gevonden Label toevoegen Opslaan Duidelijk EXIF wissen Annuleren Alle EXIF-gegevens van afbeeldingen worden gewist. Deze actie kan niet ongedaan gemaakt worden! Voorinstellingen Bijsnijden Alle niet-opgeslagen wijzigingen gaan verloren als je nu afsluit Broncode Ontvang de laatste updates, bespreek problemen en meer Enkele bewerking Kies kleur Kleur Kleur gekopieerd Snijd de afbeelding bij tot de gewenste afmetingen Versie Behoud EXIF Afbeeldingen: %d Pas voorvertoning aan Verwijderen Genereer kleurenpalet uit een afbeelding Genereer kleurenpalet Kleurenpalet Update Nieuwe versie %1$s Niet-ondersteund type: %1$s Kan geen palet genereren voor een bepaalde afbeelding Origineel Standaard Aangepast Niet-gespecificeerd Apparaat opslag Formaat wijzigen op gewicht Max. Grootte in KB Er is iets misgegaan:%1$s Afbeelding is te groot om een voorbeeld te bekijken, maar opslaan wordt toch geprobeerd Breedte %1$s Sluiten Afbeelding terugzetten Er is iets misgegaan Hoogte %1$s Terugzetten Gekopieerd naar klembord Kwaliteit App opnieuw starten Uitzondering EXIF bewerken Ok Specificaties wijzigen van een enkele afbeelding Kies de kleur uit de afbeelding, kopieer of deel Afbeelding Uitvoer map Opslaan Deze applicatie is geheel gratis, maar als je de projectontwikkeling wilt ondersteunen, kun je hier klikken FAB-uitlijning Controleer op updates Beeldzoom Indien ingeschakeld, worden breedte en hoogte van de opgeslagen afbeelding toegevoegd aan de naam van het uitvoerbestand EXIF verwijderen EXIF-metagegevens verwijderen van alle afbeeldingen Afbeeldingsvoorbeeld Bekijk een voorbeeld van elk type afbeelding: GIF, SVG, enzovoort Afbeeldingsbron Fotokiezer Galerij Bestandsverkenner De moderne Android-fotokiezer die onderaan het scherm verschijnt, werkt mogelijk alleen op Android 12+ en heeft problemen met EXIF-metagegevens Eenvoudige afbeeldingsgalerij-kiezer. Het werkt alleen als je een app hebt die mediaselectie mogelijk maakt Gebruik de GetContent-intentie om een afbeelding te kiezen. Werkt overal, maar kan op sommige apparaten problemen geven met het verwerken van geselecteerde afbeeldingen. Dat is niet mijn schuld. Opties arrangement Bewerken Volgorde Bepaalt de volgorde van de hulpmiddelen op het hoofdscherm Emoji\'s tellen reeksNum origineleBestandsnaam Wijzigt het formaat van afbeeldingen naar afbeeldingen met een lange zijde, gegeven door de breedte- of hoogteparameter. Alle grootteberekeningen worden uitgevoerd na het opslaan - behoudt de beeldverhouding Tint Monochroom Lijnbreedte Sobel rand Vervaging Halftoon Wervelen Uitstulping Uitzetting Bolbreking Brekingsindex Breking van glazen bol Schetsen Toon Snelle Waas Kan de rangschikking niet wijzigen terwijl optiegroepering is ingeschakeld Schermopname bewerken Achtergrond wissen Achtergrond herstellen Vervaging radius Pipet Tekenmodus Probleem maken Beide Maskervoorbeeld Indien ingeschakeld worden alle niet-gemaskeerde gebieden gefilterd in plaats van het standaardgedrag Je staat op het punt het geselecteerde filtermasker te verwijderen. Deze bewerking kan niet ongedaan worden gemaakt Tekent een dubbel wijzende pijl vanaf een bepaald pad Tekent een ovaal van beginpunt tot eindpunt Tekent een omlijnd ovaal van beginpunt tot eindpunt Tekent een omlijnde rechte lijn van beginpunt tot eindpunt Vrij Horizontaal raster Verticaal raster Steekmodus Rijen tellen Kolommen tellen Bestanden overschrijven Het originele bestand wordt vervangen door een nieuw bestand in plaats van het op te slaan in de geselecteerde map. Deze optie moet de afbeeldingsbron \"Explorer\" of GetContent zijn. Wanneer u dit omschakelt, wordt dit automatisch ingesteld Splijn Lineaire (of bilineaire, in twee dimensies) interpolatie is doorgaans goed voor het wijzigen van de grootte van een afbeelding, maar veroorzaakt enige ongewenste verzachting van details en kan nog steeds enigszins gekarteld zijn Betere schaalmethoden zijn onder meer resampling van Lanczos en Mitchell-Netravali-filters Alleen Clip Favoriet Nog geen favoriete filters toegevoegd Beeldformaat Voegt een container met geselecteerde vorm toe onder de leidende pictogrammen van kaarten Pictogramvorm Drago Aldridge Afsnijden Kan het afbeeldingsformaat niet wijzigen terwijl de optie voor het overschrijven van bestanden is ingeschakeld Emoji als kleurenschema Gebruikt de primaire kleur van emoji als app-kleurenschema in plaats van handmatig gedefinieerd Maakt een \"Material You\"-palet aan van afbeelding Donkere kleuren Maakt gebruik van het nachtmoduskleurenschema in plaats van de lichtvariant Kopieer als Jetpack Compose-code Ringvervaging Kruisvervaging Cirkelvervaging Stervervaging Lineaire tilt-shift Pas het formaat van een afbeelding aan volgens de opgegeven grootte in KB Vergelijken Vergelijk twee gegeven afbeeldingen Kies twee afbeeldingen om te beginnen Kies afbeeldingen Instellingen Nachtmodus Donker Licht Systeem Dynamische kleuren Maatwerk Afbeeldingsmonet toestaan Indien ingeschakeld, worden bij bewerking van een afbeelding, de app-kleuren erop toegepast Taal Amoled-modus Indien ingeschakeld, wordt de kleur van oppervlakken in de nachtmodus ingesteld op absoluut donker Kleurenschema Rood Groente Blauw Plak geldige aRGB-code. Niets om te plakken Kan het kleurenschema van de app niet wijzigen terwijl dynamische kleuren zijn ingeschakeld Het app-thema is gebaseerd op de geselecteerde kleur Over app Geen updates gevonden Probleemtracker Stuur hier bugrapporten en functieverzoeken Help vertalen Corrigeer vertaalfouten of lokaliseer het project naar een andere taal De zoekopdracht heeft niets opgeleverd Zoek hier Indien ingeschakeld, worden app-kleuren overgenomen in achtergrondkleuren Kan %d afbeelding(en) niet opslaan E-mail Primair Tertiair Ondergeschikt Randdikte Oppervlak Waarden Toevoegen Toestemming Studiebeurs De app heeft toegang tot je opslag nodig om afbeeldingen op te slaan om te werken, dit is noodzakelijk. Geef toestemming in het volgende dialoogvenster. De app heeft deze toestemming nodig om te werken. Verleen deze toestemming handmatig Externe opslag Monet-kleuren Indien ingeschakeld, wordt het updatevenster getoond bij het opstarten van de app Deel Voorvoegsel Bestandsnaam Emoji Selecteer welke emoji u op het hoofdscherm wilt weergeven Bestandsgrootte invoeren Originele bestandsnaam toevoegen Indien ingeschakeld, wordt de originele bestandsnaam toegevoegd aan de naam van de uitvoerafbeelding Volgnummer vervangen Indien ingeschakeld, wordt de standaardtijdstempel vervangen door het volgnummer van de afbeelding als je reeksverwerking gebruikt Het toevoegen van de originele bestandsnaam werkt niet als de afbeeldingsbron van de fotokiezer is geselecteerd Afbeelding laden van net Laad elke afbeelding van internet om deze te bekijken, in te zoomen, te bewerken en op te slaan. Geen afbeelding Afbeeldingslink Vullen Passend Inhoud schaal Forceert elke afbeelding in een afbeelding die wordt gegeven door de parameter Breedte en Hoogte - kan de beeldverhouding veranderen Helderheid Contrast Tint Verzadiging Filter toevoegen Filter Pas een filterketen toe op bepaalde afbeeldingen Filters Licht Kleurfilter Alfa Blootstelling witbalans Temperatuur Gamma Hoogtepunten en schaduwen Hoogtepunten Schaduwen Nevel Effect Afstand Helling Verscherpen Sepia Negatief Solariseren Vitaliteit Zwart en wit Dubbel gearceerd Spatiëring CGA-kleurruimte Gaussiaanse vervaging Vak-vervaging Bilaterale vervaging Embosseren Laplaciaans Vignet Begin Einde Kuwahara-verzachting Stapelvervaging Straal Schaal Vervorming Hoek Kleurmatrix Dekking Grootte van limieten wijzigen Vervaging midden x Pas het formaat van geselecteerde afbeeldingen aan om de gegeven breedte- en hoogtelimieten te volgen, terwijl de beeldverhouding behouden blijft Drempelwaarde Kwantiseringsniveaus Vlotte toon Posterformaat Niet-maximale onderdrukking Zwakke pixelopname Opzoeken Convolutie 3x3 RGB-filter Valse kleur Eerste kleur Tweede kleur Opnieuw ordenen Vervagingsgrootte Vervaging midden y Zoomvervaging Kleur balans Luminantiedrempel Je hebt de Bestanden-app uitgeschakeld. Activeer deze om deze functie te gebruiken Tekenen Teken op de afbeelding zoals in een schetsboek, of teken op de achtergrond zelf Verfkleur Verf alfa Teken op afbeelding Kies een afbeelding en teken er iets op Teken op de achtergrond Kies de achtergrondkleur en teken er bovenop Achtergrond kleur Versleuteling Versleutel en decodeer elk bestand (niet alleen de afbeelding) op basis van beschikbare versleutelingsalgoritmes Kies bestand Versleutelen Ontsleutelen Kies het bestand om te starten Decryptie Encryptie Sleutel Bestand verwerkt Bewaar dit bestand op je apparaat of gebruik de deelactie om het te plaatsen waar je maar wilt Functies Implementatie Compatibiliteit Wachtwoordgebaseerde versleuteling van bestanden. Vervolgbestanden kunnen in de geselecteerde map worden opgeslagen of gedeeld. Gedecodeerde bestanden kunnen ook direct worden geopend. AES-256, GCM-modus, geen opvulling, 12 bytes willekeurige IV\'s. Sleutels worden gebruikt als (256 bits) SHA-3-hashes. (Standaard, maar het is mogelijk om het benodigde algoritme selecteren). Bestandsgrootte The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. Houd er rekening mee dat compatibiliteit met andere bestandsversleutelingssoftware of -services niet kan worden gegarandeerd. Een iets andere versleutelings- of coderingsconfiguratie kan incompatibiliteit veroorzaken. Ongeldig wachtwoord of gekozen bestand is niet gecodeerd Als je probeert een afbeelding met de opgegeven breedte en hoogte op te slaan, kan dit een OOM-fout veroorzaken. Doe dit op eigen risico en zeg niet dat ik je niet heb gewaarschuwd! Cache Cache grootte Gevonden %1$s Automatisch cache wissen Indien ingeschakeld, wordt de app-cache gewist bij het opstarten van de app Creëren Hulpmiddelen Groepeer opties op type Groepeert opties op het hoofdscherm op type in plaats van een aangepaste lijstindeling Secundair maatwerk Schermopname Terugval optie Overslaan Kopiëren Opslaan in de modus %1$s kan onstabiel zijn, omdat het een verliesvrij formaat is Bij voorinstelling 125, wordt de afbeelding opgeslagen als 125% van de originele afbeelding. Bij voorinstelling 50, wordt de afbeelding opgeslagen met 50% grootte. De voorinstelling hier bepaalt het percentage van het uitvoerbestand, d.w.z. bij voorinstelling 50 op een afbeelding van 5 MB, krijg je na het opslaan een afbeelding van 2,5 MB Willekeurige bestandsnaam Indien ingeschakeld zal de uitvoerbestandsnaam volledig willekeurig zijn Opgeslagen in map %1$s met naam %2$s Opgeslagen in map %1$s Telegram-chat Bespreek de app en krijg feedback van andere gebruikers. Je kunt hier ook bèta-updates en inzichten krijgen. Masker bijsnijden Beeldverhouding Gebruik dit maskertype om een masker te maken van een bepaalde afbeelding. Merk op dat het een alfakanaal MOET hebben Backup en herstellen Back-up Herstellen Een back-up van de app-instellingen maken Herstel app-instellingen van een eerder gegenereerd bestand Beschadigd bestand of geen back-up Instellingen succesvol hersteld Neem contact met mij op Hiermee worden de instellingen teruggezet naar de standaardwaarden. Merk op dat dit niet ongedaan kan worden gemaakt zonder een hierboven vermeld back-upbestand. Verwijderen Je staat op het punt het geselecteerde kleurenschema te verwijderen. Deze bewerking kan niet ongedaan worden gemaakt Schema verwijderen Lettertype Tekst Lettertype schaal Standaard Het gebruik van grote lettertypeschalen kan UI-fouten en problemen veroorzaken, die niet kunnen worden opgelost. Gebruik voorzichtig. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz 0123456789 !? Emoties Eten en drinken Natuur en Dieren Voorwerpen Symbolen Schakel emoji in Reizen en plaatsen Activiteiten Achtergrondverwijderaar Verwijder de achtergrond uit de afbeelding door te tekenen of gebruik de Auto-optie Afbeelding bijsnijden De originele metagegevens van de afbeelding blijven behouden Transparante ruimtes rond de afbeelding worden bijgesneden Achtergrond automatisch wissen Afbeelding herstellen Wismodus Oeps… Er is iets misgegaan. Je kunt me schrijven met behulp van de onderstaande opties en ik zal proberen een oplossing te vinden Formaat wijzigen en converteren Wijzig de grootte van bepaalde afbeeldingen of converteer ze naar andere formaten. EXIF-metagegevens kunnen hier ook worden bewerkt als u een enkele afbeelding kiest. Maximaal aantal kleuren Hierdoor kan de app crashrapporten handmatig verzamelen Analyses Sta het verzamelen van anonieme app-gebruiksstatistieken toe Momenteel staat het %1$s-formaat alleen het lezen van EXIF-metagegevens op Android toe. De uitvoerafbeelding heeft helemaal geen metagegevens wanneer deze wordt opgeslagen. Poging Een waarde van %1$s betekent een snelle compressie, wat resulteert in een relatief grote bestandsgrootte. %2$s betekent een langzamere compressie, wat resulteert in een kleiner bestand. Wachten Opslaan bijna voltooid. Als u nu annuleert, moet u opnieuw opslaan. Updates Bèta\'s toestaan Updatecontrole omvat bèta-app-versies, indien ingeschakeld Teken pijlen Indien ingeschakeld, wordt het tekenpad weergegeven als een wijzende pijl Zachtheid van de borstel Foto\'s worden in het midden bijgesneden tot het ingevoerde formaat. Canvas wordt uitgebreid met de opgegeven achtergrondkleur als de afbeelding kleiner is dan de ingevoerde afmetingen. Bijdrage Beeldsteken Combineer de gegeven afbeeldingen om één grote te krijgen Kies minimaal 2 afbeeldingen Uitvoer afbeeldingsschaal Beeldoriëntatie Horizontaal Verticaal Schaal kleine afbeeldingen naar groot Kleine afbeeldingen worden, indien ingeschakeld, geschaald naar de grootste in de reeks Afbeeldingen bestellen Normaal Vervaging randen Tekent vage randen onder de originele afbeelding om de ruimtes eromheen te vullen in plaats van één kleur, indien ingeschakeld Pixelvorming Verbeterde pixelvorming Deze updatechecker maakt verbinding met GitHub om te controleren of er een nieuwe update beschikbaar is Penseel-pixelvorming Verbeterde ruit-pixelvorming Ruit-pixelvorming Cirkel-pixelvorming Verbeterde cirkel-pixelvorming Kleur vervangen Tolerantie Kleur om te vervangen Doelkleur Kleur om te verwijderen Kleur verwijderen Hercoderen Pixelgrootte Tekenrichting vergrendelen Indien ingeschakeld in de tekenmodus, draait het scherm niet Controleer op updates Paletstijl Tonale plek Neutrale Levendig Expressief Regenboog Fruit salade Trouw Inhoud Standaard paletstijl waarin alle vier de kleuren zijn aan te passen, bij andere kan alleen de hoofdkleur worden ingesteld Een stijl die iets chromatischer is dan monochroom Een luid thema, kleurrijkheid is maximaal voor het primaire palet, verhoogd voor anderen Een speels thema: de tint van de bronkleur komt niet voor in het thema Een monochroom thema, kleuren zijn puur zwart/wit/grijs Een schema dat de bronkleur in Scheme.primaryContainer plaatst Een schema dat sterk lijkt op het inhoudschema Aandacht Vervagende randen Gehandicapt Kleuren omkeren Begin Vervangt themakleuren door negatieve kleuren, indien ingeschakeld Centrum Zoekopdracht Maakt het mogelijk om door alle beschikbare hulpmiddelen op het hoofdscherm te zoeken PDF-hulpmiddelen Werk met PDF-bestanden: bekijk een voorbeeld, converteer naar een reeks afbeeldingen of maak er een van bepaalde afbeeldingen Voorbeeld van pdf PDF naar afbeeldingen Afbeeldingen naar PDF Eenvoudig PDF-voorbeeld Converteer PDF naar afbeeldingen in een bepaald uitvoerformaat Verpak de gegeven afbeeldingen in een uitvoer-PDF-bestand Maskerfilter Pas filterketens toe op bepaalde gemaskeerde gebieden. Elk maskergebied kan zijn eigen set filters bepalen Maskers Masker toevoegen Masker %d Maskerkleur Er wordt een getekend filtermasker weergegeven om het geschatte resultaat te tonen Omgekeerd vultype Masker verwijderen Volledig filter Pas eventuele filterketens toe op bepaalde afbeeldingen of op één afbeelding Einde Eenvoudige varianten Markeerstift Neon Pen Privacyvervaging Teken semi-transparante, scherpe markeerstiftpaden Voeg een gloeiend effect toe aan je tekeningen Standaard één, eenvoudigste: alleen de kleur Vervaagt het beeld onder het getekende pad om alles te beveiligen dat je wilt verbergen Vergelijkbaar met privacyvervaging, maar met pixelvorming i.p.v. vervaging Containers Maakt schaduwtekenen achter containers mogelijk Schuifregelaars Schakelaars FAB\'s Toetsen Maakt schaduwtekenen achter schuifregelaars mogelijk Maakt schaduwtekenen achter schakelaars mogelijk Maakt schaduwtekenen achter zwevende actieknoppen mogelijk Schakelt schaduwtekening achter standaardknoppen in App-balken Maakt schaduwtekenen achter app-balken mogelijk Waarde binnen bereik %1$s - %2$s Automatisch draaien Maakt het mogelijk om een limietvak te gebruiken voor de beeldoriëntatie Tekenpadmodus Dubbele lijnpijl Gratis tekening Dubbele pijl Lijnpijl Pijl Lijn Tekent pad als invoerwaarde Tekent het pad van beginpunt naar eindpunt als een lijn Tekent de wijzende pijl van beginpunt tot eindpunt als een lijn Tekent een wijzende pijl vanaf een bepaald pad Tekent een dubbel wijzende pijl van beginpunt tot eindpunt als een lijn Geschetst ovaal Geschetst rect ovaal Rect Tekent een rechte lijn van beginpunt tot eindpunt Lasso Tekent een gesloten gevuld pad volgens een gegeven pad Geen map \"%1$s\" gevonden. We hebben dit gewijzigd in de standaardmap. Sla het bestand opnieuw op Klembord Automatische pin Voegt automatisch opgeslagen afbeeldingen toe aan het klembord, indien ingeschakeld Trillingen Trillingssterkte Om bestanden te overschrijven moet je de afbeeldingsbron \"Explorer\" gebruiken. Probeer de afbeeldingen opnieuw te kiezen. We hebben de afbeeldingsbron gewijzigd in de benodigde bron. Leeg Achtervoegsel Schaalmodus Bilineair Catmull Bicubisch Han Hermiet Lanczos Mitchell Dichtstbijzijnde Basis Standaardwaarde Een van de eenvoudigere manieren om de grootte te vergroten, is door elke pixel te vervangen door een aantal pixels van dezelfde kleur Eenvoudigste Android-schaalmodus die in bijna alle apps wordt gebruikt Methode voor het soepel interpoleren en opnieuw bemonsteren van een reeks controlepunten, vaak gebruikt in computergraphics om vloeiende curven te creëren Windowing-functie die vaak wordt toegepast bij signaalverwerking om spectrale lekkage te minimaliseren en de nauwkeurigheid van frequentieanalyse te verbeteren door de randen van een signaal taps te maken Wiskundige interpolatietechniek die de waarden en afgeleiden aan de eindpunten van een curvesegment gebruikt om een vloeiende en continue curve te genereren Resamplingmethode die interpolatie van hoge kwaliteit handhaaft door een gewogen sinc-functie op de pixelwaarden toe te passen Resampling-methode die gebruik maakt van een convolutiefilter met aanpasbare parameters om een balans te bereiken tussen scherpte en anti-aliasing in het geschaalde beeld Maakt gebruik van stuksgewijs gedefinieerde polynomiale functies om een curve of oppervlak soepel te interpoleren en te benaderen, een flexibele en continue vormrepresentatie Opslaan naar opslag wordt niet uitgevoerd en er wordt alleen geprobeerd de afbeelding op het klembord te plaatsen Penseel herstelt de achtergrond in plaats van te wissen OCR (tekst herkennen) Herken tekst van een bepaalde afbeelding, meer dan 120 talen ondersteund De afbeelding bevat geen tekst, of de app heeft deze niet gevonden Accuracy: %1$s Herkenningstype Snel Standaard Best Geen gegevens Voor een goede werking van Tesseract OCR moeten aanvullende trainingsgegevens (%1$s) naar je apparaat worden gedownload. \nWil je %2$s gegevens downloaden? Downloaden Geen verbinding, controleer dit en probeer het opnieuw om trainingsmodellen te downloaden Gedownloade talen Beschikbare talen Segmentatiemodus Gebruik Pixelswitch Pixel-achtige schakelaar wordt gebruikt in plaats van een schakelaar gebaseerd op Google\'s Material You Overschreven bestand met naam %1$s op oorspronkelijke bestemming Vergrootglas Schakelt vergrootglas aan de bovenkant van de vinger in tekenmodi in voor betere toegankelijkheid Beginwaarde forceren Forceert dat de exif-widget in eerste instantie wordt gecontroleerd Enkele lijn Een woord Meerdere talen toestaan Dia Zij aan zij Wisseltikken Transparantie Beoordeel app Tarief Deze app is volledig gratis. Als je wilt dat hij groter wordt, geef dan een ster aan het project op Github 😄 Oriëntatie & Alleen scriptdetectie Automatische oriëntatie & Scriptdetectie Alleen automatisch Auto Enkele kolom Enkel blok verticale tekst Enkel blok Cirkel woord Enkel teken Schaarse tekst Sparse tekst Oriëntatie & Scriptdetectie Ruwe lijn Wil je OCR-trainingsgegevens van taal \"%1$s\" verwijderen voor alle herkenningstypen, of alleen voor een geselecteerd type (%2$s)? Huidig Alle Verloopmaker Creëer een verloop van een bepaald uitvoerformaat met aangepaste kleuren en uiterlijktype Lineair Radiaal Vegen Verlooptype Centrum X Centrum Y Tegelmodus Herhaald Spiegelen Klem Sticker Kleur stopt Kleur toevoegen Eigenschappen Helderheidshandhaving Scherm Verloopoverlay Stel elk verloop van de bovenkant van de gegeven afbeeldingen samen Transformaties Camera Gebruikt de camera om een foto te maken. Houd er rekening mee dat het mogelijk is om slechts één afbeelding uit deze afbeeldingsbron te halen Watermerken Omslagafbeeldingen met aanpasbare tekst-/afbeeldingswatermerken Herhaal watermerk Herhaalt het watermerk over de afbeelding in plaats van enkelvoudig op een bepaalde positie Offset X Offset Y Watermerktype Deze afbeelding wordt gebruikt als patroon voor watermerken Tekst kleur Overlay-modus GIF-hulpmiddelen Converteer afbeeldingen naar GIF-afbeelding of extraheer frames uit een bepaalde GIF-afbeelding GIF naar afbeeldingen Converteer een GIF-bestand naar een reeks afbeeldingen Converteer een reeks afbeeldingen naar een GIF-bestand Afbeeldingen naar GIF Kies een GIF-afbeelding om te beginnen Gebruik de grootte van het eerste frame Vervang de opgegeven maat door de eerste frameafmetingen Origineel afbeeldingsvoorbeeld Alpha Herhaal telling Framevertraging milli FPS Gebruik lasso Gebruikt Lasso zoals in de tekenmodus om te wissen Confetti Er wordt confetti getoond over opslaan, delen en andere primaire acties Veilige modus Verbergt inhoud bij het afsluiten, ook kan het scherm niet worden vastgelegd of opgenomen Uitgang Sierra Lite-dithering Als je het voorbeeld nu verlaat, moet je de afbeeldingen opnieuw toevoegen Dithering Quantizer Grijsschaal Bayer twee aan twee dithering Bayer drie aan drie dithering Bayer vier bij vier dithering Bayer Acht Bij Acht Dithering Floyd Steinberg Dithering Jarvis Judice Ninke Dithering Sierra dithering Sierra-dithering met twee rijen Atkinson-dithering Stucki Dithering Burkes Dithering Valse Floyd Steinberg-dithering Van links naar rechts ditheren Willekeurig ditheren Eenvoudige drempeldithering Sigma Ruimtelijke Sigma Mediane vervaging B Splijn Maakt gebruik van stuksgewijs gedefinieerde bicubische polynoomfuncties om een curve of oppervlak soepel te interpoleren en te benaderen, een flexibele en continue vormrepresentatie Native stapelvervaging Focus verleggen Hapering Hoeveelheid Zaad Anaglief Lawaai Pixel sorteren Schudden Verbeterde storing Kanaalverschuiving X Kanaalverschuiving Y Grootte van corruptie Corruptieverschuiving X Corruptieverschuiving Y Tentvervaging Zijkant vervagen Kant Bovenkant Onderkant Kracht Eroderen Anisotrope diffusie Verspreiding Geleiding Horizontale windspreiding Snelle bilaterale vervaging Poisson-vervaging Logaritmische toonmapping ACES filmische toonmapping Kristalliseren Lijnkleur Fractaal glas Amplitude Marmer Turbulentie Olie Watereffect Maat Frequentie X Frequentie Y Amplitude X Amplitude Y Perlin-vervorming ACES Hill Tone Mapping Hable filmische toonmapping Hejl Burgess-toonmapping Snelheid Ontnevelen Omega Kleurenmatrix 4x4 Kleurenmatrix 3x3 Eenvoudige effecten Polaroid Tritanomalie Deuteranomalie Protanomalie Vintage Browni Coda Chroom Nachtzicht Warm Koel Tritanopie Deuteranopie Protanopie Achromatomalie Achromatopsie Korrel Onscherp Pastel Oranje waas Roze droom Gouden uur Hete zomer Paarse mist zonsopkomst Kleurrijke werveling Zacht lentelicht Herfsttinten Lavendel droom Cyberpunk Limonade licht Spectraal vuur Nachtelijke magie Fantasielandschap Kleur explosie Elektrisch verloop Karamel Duisternis Futuristisch verloop Groene zon Regenboog wereld Donker paars Ruimteportaal Rode Werveling Digitale code Bokeh App-balk-emoji wordt voortdurend willekeurig gewijzigd in plaats van een geselecteerde te gebruiken Willekeurige emoji\'s Kan geen willekeurige emoji\'s gebruiken als emoji\'s zijn uitgeschakeld Kan geen emoji selecteren terwijl willekeurige emoji is ingeschakeld Oude televisie Shuffle-vervaging Uchimura Mobius Overgang Hoogtepunt Kleurafwijking Afbeeldingen die op de oorspronkelijke bestemming zijn overschreven Tags Om Te Verwijderen APNG-hulpmiddelen APNG naar afbeeldingen Kies APNG-afbeelding om te beginnen Bewegingsonscherpte Converteer afbeeldingen naar APNG-afbeelding of extraheer frames uit een bepaalde APNG-afbeelding Converteer een APNG-bestand naar een reeks afbeeldingen Converteer een reeks afbeeldingen naar een APNG-bestand Afbeeldingen naar APNG ZIP-hulpmiddelen Maak een zip-bestand van bepaalde bestanden of afbeeldingen Sleep handvatbreedte Confetti-type Feestelijk Explosie Regen Hoeken Probeer het opnieuw Instellingen in de breedte weergeven Indien uitgeschakeld, worden landschapsinstellingen geopend met de knop in de bovenste app-balk in plaats van altijd zichtbaar te zijn Nieuwe map toevoegen Bits Per Sample JPEG uitwisselingsformaat JPEG uitwisselingsformaatlengte Compressie Fotometrische interpretatie Samples Per Pixel Planaire configuratie Y Cb Cr sub-sampling Y Cb Cr positionering X-resolutie Y-resolutie Resolutie-eenheid Witpunt Primaire chromaticiteiten Y Cb Cr coëfficiënten Zwart-witreferentie Datum/tijd Afbeeldingsbeschrijving Aanmaken Model Software Maker Auteursrecht Flashpix-versie Exif-versie Kleurruimte Gamma Pixel X-grootte Pixel Y-grootte Gevoeligheidstype Scène-opnametype GPS-gebiedsinformatie JXL naar JPEG Verliesloze transcodering uitvoeren van JXL naar JPEG JXL-hulpmiddelen Verliesloze transcodering uitvoeren van JPEG naar JXL JPEG naar JXL Kies JXL-afbeelding om te starten Compose Kies meerdere media Kies enkele media Kiezen Gebruikt Jetpack Compose Material You, het is niet zo mooi als op weergave gebaseerd Snelle Gaussiaanse vervaging 2D Snelle Gaussiaanse vervaging 3D Snelle Gaussiaanse vervaging 4D GIF naar JXL Converteer reeks afbeeldingen naar JXL-animatie Converteer GIF-afbeeldingen naar JXL-geanimeerde afbeeldingen Schakeltype Hiermee kan de app klembordgegevens automatisch plakken, zodat deze op het hoofdscherm verschijnen en je deze kunt verwerken Resampling-methode die interpolatie van hoge kwaliteit handhaaft door een Bessel (jinc) -functie toe te passen op de pixelwaarden Maakt het maken van voorbeelden mogelijk, wat crashes op sommige apparaten kan helpen voorkomen, maar dit schakelt ook enkele framebewerkingsfuncties uit Verliesgevende compressie Gebruikt verliesgevende compressie om de bestandsgrootte te verkleinen Bedient de resulterende beelddecoderingssnelheid, dit zou moeten helpen om het resulterende beeld sneller te openen, waarde %1$s staat voor de langzaamste decodering, %2$s is de snelste, deze instelling heeft invloed op de grootte van het uitvoerbeeld Sortering Indien ingeschakeld, worden de instellingen altijd op volledig scherm weergegeven in plaats van een schuifbaar ladeblad Volledig scherm-instellingen Max Gebruikt een Windows 11-stijl schakelaar op basis van het \"Fluent\" ontwerpsysteem Pixel Afbeelding wordt voor verwerking verkleind, zodat de verwerking ervan sneller en veiliger kan worden uitgevoerd Alle eigenschappen worden ingesteld op standaardwaarden, deze actie kan niet ongedaan worden gemaakt Standaard lijndikte Ankerformaat wijzigen Vloeiend Cupertino Gebruikt iOS-achtige schakelaar op basis van een cupertino-ontwerpsysteem Kanalenconfiguratie Vandaag Gisteren Ingesloten kiezer Gebruik Image Toolbox\'s eigen afbeeldingenkiezer in plaats van de systeemkiezer Geen rechten Verzoek Verouderd LSTM-netwerk Verouderd & LSTM Automatisch plakken Kleurharmonisatie Harmonisatieniveau Converteer reeksen afbeeldingen naar een bepaald formaat Converteren APNG naar JXL Converteer APNG-afbeeldingen naar JXL-animaties JXL naar afbeeldingen Converteer JXL-animatie naar reeks fotoreeks Afbeeldingen naar JXL Lanczos Bessel Gedrag Bestandskeuze overslaan Voorbeeldweergaven genereren Indien mogelijk wordt de bestandenkiezer automatisch geopend JXL ~ JPEG-transcodering uitvoeren zonder kwaliteitsverlies, of converteer GIF/APNG naar JXL-animatie Compressietype Sortering op datum Sortering op datum (omgekeerd) Sortering op naam Sortering op naam (omgekeerd) Afbeeldingen naar SVG Traceer bepaalde afbeeldingen naar SVG Het kwantiseringspalet wordt bemonsterd als deze optie is ingeschakeld Bemonsterd palet gebruiken Pad negeren Het gebruik van dit hulpmiddel voor het traceren van grote afbeeldingen zonder verkleining wordt afgeraden, het verhoogt de verwerkingstijd en kan leiden tot een crash Afbeelding verkleinen Minimale kleurverhouding Drempelwaarde lijnen Drempelwaarde kwadratisch Tolerantie voor afronding van coördinaten Padvergroting Eigenschappen opnieuw instellen Gedetailleerd Overdrachtsfunctie Strip byte-tellingen Gerelateerd geluidsbestand Gebruikerscommentaar Makersnotitie Gecomprimeerde Bits Per Pixel Datum/tijd origineel OECF Spectrale gevoeligheid Belichtingsprogramma F-nummer Belichtingstijd Datum/tijd gedigitaliseerd Offset-tijd Offset-tijd origineel Offset-tijd gedigitaliseerd Sub Sec-tijd Sub Sec-tijd origineel CFA-patroon Bestandsbron Sensormethode Belichtingsindex Onderwerp locatie Flitsvermogen Brandpuntsafstand Onderwerpgebied Flits Meetmodus Onderwerp afstand Max. diafragmawaarde Belichtingsbiaswaarde Helderheid waarde Diafragma waarde Sluitersnelheid waarde ISO Speed lengtegraad zzz ISO Speed lengtegraad yyy ISO Speed Aanbevolen belichtingsindex Standaard uitvoergevoeligheid Scherpte Verzadiging Contrast Versterking Brandpuntsafstand bij 35mm Digitale zoomverhouding Witbalans Belichtingsmodus Aangepaste weergave GPS-versie ID Lens serienummer Lens model Lens merk Lens specificatie Body serienummer Camera eigenaar Afbeelding uniek ID Onderwerp afstandbereik Apparaatinstelling beschrijving GPS-tijdstempel GPS-hoogte GPS-lengtegraad GPS-lengtegraad ref GPS-breedtegraad GPS-breedtegraad ref GPS-bestemming Lengtegraad GPS-bestemmingskoers ref GPS-bestemmingsafstand ref GPS-bestemming lengtegraad ref GPS-bestemming breedtegraad GPS-bestemming breedtegraad ref GPS-kaart datum GPS-beeldkoers GPS-beeldkoers ref GPS-spoor GPS-spoor ref GPS-snelheid GPS-snelheid ref GPS-DOP GPS-maatmodus GPS-status GPS-satellieten Teken tekst op pad met gegeven lettertype en kleur ISO Sensor-bovenrand Sensor-rechterrand Sensor-onderrand Kaderverhouding Lengte voorbeeld Voorbeeld start Standaard bijsnijdmaat DNG-versie Interoperabiliteitsindex GPS H-positioneringsfout GPS-bestemmingsafstand GPS-verwerkingsmethode GPS-differentiaal Watermerkgrootte Lettergrootte De huidige tekst wordt herhaald tot het einde van het pad in plaats van één keer tekenen Tekst herhalen Streeplengte Deze afbeelding wordt gebruikt als een herhaalde invoer van getekend pad Gebruik de geselecteerde afbeelding om deze langs het gegeven pad te tekenen Delen als PDF Opslaan als PDF Scan starten Klik om te scannen Document-scanner Histogram vereffenen Invoer via tekstveld Driehoek Driehoek-contour Tekent een driehoek-contour van beginpunt tot eindpunt Tekent een driehoek van beginpunt tot eindpunt Hoekpunten Tekent een veelhoek-contour van beginpunt tot eindpunt Veelhoek-contour Veelhoek Tekent een veelhoek van beginpunt tot eindpunt Inwendige radiusverhouding Stercontour Tekent een stercontour van beginpunt tot eindpunt Ster Tekent een ster van beginpunt tot eindpunt Teken een regelmatige veelhoek in plaats van een vrije vorm Regelmatige veelhoek tekenen View Based Material You-schakelaar gebruiken. Dit oogt beter en bevat fraaie animaties. Teken een regelmatige ster in plaats van een vrije vorm Strip offsets Rijen per strip Percentage invoeren HSV-histogram vereffenen Onderstaande opties betreffen de opslag van afbeeldingen, niet PDF Scan documenten om er een PDF of een reeks afbeeldingen van te maken Regelmatige ster tekenen Sensor-linkerrand GPS-bestemmingskoers GPS-datumstempel GPS-hoogte ref Focusvlak resolutie-eenheid Focusvlak Y-resolutie Focusvlak X-resolutie Ruimtelijke frequentierespons Fotografische gevoeligheid Sub Sec-tijd gedigitaliseerd Maakt tekstveld achter de selectie van voorinstellingen mogelijk om deze direct in te voeren Machine-modus Maakt anti-aliasing mogelijk om scherpe randen te voorkomen Anti-alias Wanneer je een afbeelding selecteert om te openen (preview) in ImageToolbox, wordt het selectieblad voor bewerken geopend in plaats van een voorbeeld te bekijken Open Bewerken in plaats van Voorbeeld Kleurruimte-schaal Lineair Pixelvorming-histogram vereffenen Histogram adaptief vereffenen LUV Histogram adaptief vereffenen LAB Rastergrootte X Rastergrootte Y Histogram adaptief vereffenen Clahe Clahe LAB Clahe LUV Bijsnijden naar inhoud Kleur kader Kleur om te negeren Filtersjabloon Als QR-code afbeelding Als bestand Opslaan als bestand Opslaan als QR-code afbeelding Sjabloon verwijderen Je staat op het punt het geselecteerde sjabloonfilter te verwijderen. Deze operatie kan niet ongedaan worden gemaakt. Filtersjabloon toegevoegd met naam \"%1$s\" (%2$s) Filtervoorbeeld QR-code Scan QR-code voor de inhoud of plak de tekenreeks om een nieuwe te genereren Inhoudscode Sjabloon Geen filtersjabloon toegevoegd Nieuw aanmaken De gescande QR-code is geen geldig filtersjabloon Sjabloon aanmaken QR-code scannen Geselecteerd bestand bevat geen filtersjabloongegevens Sjabloonnaam Deze afbeelding wordt gebruikt als voorbeeld voor het filtersjabloon Scan QR-code om inhoud in het veld te vervangen of typ iets om nieuwe QR-code te genereren QR-beschrijving Min Geef camera toestemming in instellingen om QR-codes te scannen Blackman Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kubische interpolatie zorgt voor een soepelere schaal door de dichtstbijzijnde 16 pixels te beschouwen, wat betere resultaten oplevert dan bilineair Maakt gebruik van door het werk gedefinieerde polynoomfuncties om een curve of oppervlak soepel te interpoleren en te benaderen, flexibele en continue vormweergave Een functie die wordt gebruikt om spectrale lekkage te verminderen door de randen van een signaal af te bouwen, handig bij signaalverwerking Een functie die is ontworpen om een goede frequentieresolutie te geven met verminderde spectrale lekkage, vaak gebruikt in signaalverwerkingstoepassingen Een methode die een kwadratische functie gebruikt voor interpolatie, die soepele en continue resultaten oplevert Een geavanceerde methode van herbemonstering die hoogwaardige interpolatie biedt met minimale artefacten Een eenvoudige methode van herbemonstering die het gemiddelde van de dichtstbijzijnde pixelwaarden gebruikt, wat vaak in een blokachtig resultaat oplevert Een functie die wordt gebruikt om spectrale lekkage te verminderen, wat zorgt voor een goede frequentieresolutie in signaalverwerkingstoepassingen Een methode van herbemonstering die een Lanczos-filter met 2 lobben gebruikt voor hoogwaardige interpolatie met minimale artefacten Een methode van herbemonstering die een Lanczos-filter met 3 lobben gebruikt voor hoogwaardige interpolatie met minimale artefacten Een methode van herbemonstering die een Lanczos-filter met 4 lobben gebruikt voor hoogwaardige interpolatie met minimale artefacten Een variant van het Lanczos 2-filter dat gebruik maakt van de jinc-functie en hoogwaardige interpolatie biedt met minimale artefacten Een variant van het Lanczos 3-filter dat de jinc-functie gebruikt en hoogwaardige interpolatie biedt met minimale artefacten B-Spline Hamming Quadric Bartlett-Hann Box Hanning Welch Bartlett Robidoux Spline 16 Bohman Cubic Gaussian Robidoux Sharp Spline 36 Spline 64 Kaiser Lanczos 2 Sphinx Een op spline gebaseerde interpolatiemethode die vloeiende resultaten oplevert met een 36-tapfilter Een variant van het Hann-venster, vaak gebruikt om spectrale lekkage in signaalverwerkingstoepassingen te verminderen Een functie die een goede frequentieresolutie biedt door spectrale lekkage te minimaliseren, vaak gebruikt bij signaalverwerking Een driehoekige functie die wordt gebruikt bij signaalverwerking om spectrale lekkage te verminderen Een interpolatiemethode die een Gauss-functie toepast, handig om ruis in afbeeldingen glad te strijken en te verminderen Een hoogwaardige interpolatiemethode die is geoptimaliseerd voor het verkleinen van natuurlijke beelden, het balanceren van scherpte en gladheid Een scherpere variant van de Robidoux-methode, geoptimaliseerd voor scherp beeldformaat Een op spline gebaseerde interpolatiemethode die vloeiende resultaten oplevert met een 16-tapfilter Een op spline gebaseerde interpolatiemethode die vloeiende resultaten oplevert met een 64-tapfilter Een interpolatiemethode die gebruik maakt van het Kaiser-venster, die een goede controle biedt over de afweging tussen de breedte van de hoofdlobben en het niveau van de zijlobben Een hybride functie die de Bartlett- en Hann-vensters combineert, die wordt gebruikt om spectrale lekkage in signaalverwerking te verminderen Een variant van het Lanczos 4-filter dat gebruik maakt van de jinc-functie en hoogwaardige interpolatie biedt met minimale artefacten Elliptiisch gewogen gemiddelde (EWA) -variant van het Blackman-filter voor het minimaliseren van ringartefacten Quadric EWA Robidoux Sharp EWA Blackman EWA Hanning EWA Elliptische gewogen gemiddelde (EWA) variant van het Hanning-filter voor soepele interpolatie en resampling Robidoux EWA Elliptische gewogen gemiddelde (EWA) variant van het Robidoux-filter voor hoogwaardige resampling Elliptisch gewogen gemiddelde (EWA) -variant van het quadrische filter voor soepele interpolatie Een resamplingfilter ontworpen voor hoogwaardige beeldverwerking met een goede balans tussen scherpte en gladheid Elliptische gewogen gemiddelde (EWA) variant van het Robidoux Sharp-filter voor scherpere resultaten Lanczos 3 Jinc EWA Ginseng Elliptisch gewogen gemiddelde (EWA) variant van het Lanczos 3 Jinc filter voor hoogwaardige resampling met gereduceerde aliasing Ginseng EWA Elliptische gewogen gemiddelde (EWA) variant van het Ginseng-filter voor verbeterde beeldkwaliteit Lanczos 4 Sharpest EWA Lanczos Sharp EWA Lanczos Soft EWA Elliptische gewogen gemiddelde (EWA) variant van het Lanczos Sharp-filter voor het bereiken van scherpe resultaten met minimale artefacten Elliptisch gewogen gemiddelde (EWA) -variant van de Lanczos 4 Sharpest-filter voor extreem scherpe beeldherbinding Elliptisch gewogen gemiddelde (EWA) -variant van het Lanczos Soft-filter voor een soepelere beeldherbemonstering Haasn Soft Formaatconversie Een door Haasn ontworpen resamplingfilter voor een soepele en artefactvrije beeldschaling Reeks afbeeldingen van het ene formaat naar het andere omzetten Voor altijd afwijzen Afbeelding toevoegen Afbeeldingen stapelen Stapel afbeeldingen op elkaar met gekozen mengmodi Histogram egaliseren - Adaptieve HSL Aantal bins Clahe HSL Clahe HSV Histogram egaliseren - Adaptieve HSV Wikkelen Edge-modus Knippen Schema voor kleurblindheid Onvermogen om rode tinten waar te nemen Onvermogen om groene tinten waar te nemen Geen schema voor kleurblindheid gebruiken Sigmoidal Lagrange 3 Een 3e orde Lagrange interpolatiefilter, met betere nauwkeurigheid en vloeiendere resultaten voor beeldschaling Een 6e orde Lanczos-resamplingfilter, voor scherpere en nauwkeurigere beeldschaling Moeite met onderscheid tussen blauwe en gele tinten Onvermogen om blauwe tinten waar te nemen Verminderde gevoeligheid voor alle kleuren Volledige kleurenblindheid met alleen grijstinten Kleuren zijn precies zoals vastgelegd in het thema Lagrange 2 Een 2e orde Lagrange interpolatiefilter, geschikt voor hoogwaardige beeldschaling met vloeiende overgangen Lanczos 6 Lanczos 6 Jinc Een variant van het Lanczos 6-filter met behulp van een Jinc-functie voor verbeterde beeldresamplingkwaliteit Moeite met onderscheid tussen groene en rode tinten Moeite met onderscheid tussen rode en groene tinten Selecteer modus om themakleuren aan te passen voor een bepaalde variant in kleurblindheid Lineaire Gaussbox-vervaging Lineaire tent-vervaging Lineaire vak-vervaging Lineaire gestapelde vervaging Gaussiaanse vak-vervaging Filter vervangen Lineaire snelle Gaussiaanse vervaging Next Lineaire snelle Gaussiaanse vervaging Lineaire Gaussiaanse vervaging Kies een filter om als verf te gebruiken Kies hieronder het filter om het als penseel in je tekening te gebruiken TIFF-compressiemethode Zandschildering Low Poly Afbeelding splitsen Splits een enkele afbeelding in rijen en kolommen Talen met succes geïmporteerd Back-up OCR-modellen Importeren Exporteren Positie Midden Linksboven Passend binnen begrenzing Combineer grootte aanpassen met deze parameter om het gewenste gedrag te bereiken (Bijsnijden/Passend in verhouding) Rechtsboven Linksonder Rechtsonder Midden boven Midden rechts Midden onder Midden links Verbeterd olie Eenvoudige TV Doel HDR Gotham Palet overzetten Eenvoudige schets Zachte gloed Kleurenposter Driekleur Derde kleur Clahe Oklab Clahe Oklch Clahe Jzazbz Geclusterde 8x8 dithering Yililoma dithering Polkadot Geclusterde 2x2 dithering Geclusterde 4x4 dithering Complementair Analoog Triadisch Gescheiden Complementair Tetradisch Vierkant Analoog + Complementair Tinten Tonen Kleurmenging Kleurinformatie Geselecteerde kleur Kleur om te mengen Tinten LUT-doelafbeelding Amatorka Soft Elegance-variant Geen favoriete opties geselecteerd, voeg ze toe aan de pagina Hulpmiddelen Favorieten toevoegen Kleurentools Miss Etikate Variatie Soft Elegance Meng, maak tonen, genereer tinten en meer Kleurharmonieën 512x512 2D LUT Kleurtint Kan Monet niet gebruiken terwijl dynamische kleuren zijn ingeschakeld LUT Paletoverdracht-variant Bleach Bypass Herfstkleuren Mistige nacht Drop Blues Neutrale LUT-afbeelding verkrijgen 3D LUT Doel 3D LUT-bestand (.cube / .CUBE) Kaarslicht Kodak Gebruik eerst je favoriete fotobewerkingstoepassing om een filter toe te passen op neutrale LUT die je hier kunt verkrijgen. Om dit goed te laten werken, mag elke pixelkleur niet afhankelijk zijn van andere pixels (onverduistering werkt bijvoorbeeld niet). Als je klaar bent, gebruik je je nieuwe LUT-afbeelding als invoer voor 512*512 LUT-filter Edgy Amber Film Stock 50 Pop Art Celluloid Golden Forest Koffie Greenish Retro Yellow Geef camera toestemming in Instellingen om Document-scanner te scannen Links Voorbeeld Links Hiermee kun je een voorbeeld van een link ophalen op plaatsen waar je tekst kunt verkrijgen (QRCode, OCR etc) Ico-bestanden kunnen alleen worden opgeslagen met een maximale grootte van 256*256, grotere waarden worden naar deze afmetingen verkleind Converteer een reeks afbeeldingen naar een WEBP-bestand Images naar WEBP Kies een WEBP-afbeelding om te starten GIF naar WEBP Converteer GIF-afbeeldingen naar WEBP-animatiefoto\'s Converteer het WEBP-bestand naar een reeks afbeeldingen WEBP-hulpmiddelen Converteer afbeeldingen naar WEBP-animatie of extraheer frames van gegeven WEBP-animatie WEBP naar afbeeldingen Geen volledige toegang tot bestanden Standaard tekenkleur Standaard padmodus tekenen Tijdstempel toevoegen Schakelt Tijdstempel in en voegt deze toe aan de naam van het uitvoerbestand Tijdstempel met opmaak Schakel Tijdstempel in om de opmaak te selecteren Verleen toegang tot alle bestanden om JXL, QOI en andere afbeeldingen te zien die op Android niet worden herkend als afbeeldingsbestanden. Zonder deze toestemming, zijn deze afbeeldingen hier niet zien. Tijdstempel-opmaak inschakelen in de naam van het uitvoerbestand in plaats van standaard milliseconden Recent gebruikt CI-kanaal Eenmalige opslaglocatie Groep Eenmalige opslaglocaties bekijken en bewerken die u kunt gebruiken door lang op de opslagknop te drukken in vrijwel alle opties Sluit aan bij onze chat waar je alles kunt bespreken wat je wilt en kijk ook naar het CI-kanaal waar ik bèta\'s en aankondigingen plaats Ontvang een melding over nieuwe versies van de app en lees aankondigingen ImageToolbox op Telegram 🎉 Pas een afbeelding aan op een bepaalde afmeting en pas vervaging of kleur toe op de achtergrond Hulpmiddelen ordenen Groeperen op type Groepeer hulpmiddelen op het hoofdscherm op basis van hun type in plaats van een aangepaste lijstindeling Standaardwaarden Zichtbaarheid van systeembalken Systeembalken weergeven door te vegen Maakt vegen mogelijk om systeembalken weer te geven als ze verborgen zijn Autom. Alles weergeven Navigatiebalk verbergen Statusbalk verbergen Ruis genereren Genereer verschillende varianten ruis zoals Perlin of andere typen Frequentie Type ruis Rotatie Fractal Octaven Lacunariteit Versterking Gewogen sterkte Ping-pongsterkte Afstandsfunctie Returntype Domein Warp Uitlijning Aangepaste bestandsnaam Opgeslagen in map met aangepaste naam Alles verbergen Jitter Selecteer de locatie en bestandsnaam die worden gebruikt om de huidige afbeelding op te slaan Collagemaker Maak verschillende collages van 20 afbeeldingen Collagetype Houd de afbeelding ingedrukt om de positie te wisselen, verplaatsen en zoomen Histogram RGB- of helderheidsbeeldhistogram om je te helpen aanpassingen te maken Deze afbeelding wordt gebruikt om RGB- en helderheidshistogrammen te genereren Aangepaste opties Voer opties in volgens dit patroon: \"--{optie_naam} {waarde}\" Tesseract-opties Geef enkele invoervariabelen op voor de tesseract-engine Vrije hoeken Punten naar afbeeldingsgrenzen dwingen Inhoudsbewust invullen onder getekend pad Vlekcorrectie Autom. bijsnijden Afbeelding bijsnijden met veelhoek. (incl. perspectiefcorrectie) Punten worden niet beperkt door beeldgrenzen, dit is handig voor nauwkeurigere perspectiefcorrectie Masker Openen Morfologisch kleurverloop Top Hat Cirkelkern gebruiken Sluiten Black Hat Kleurtooncurven Curven terugzetten Curven worden hersteld naar standaardwaarden Lijnstijl Tussenruimte Gestreept Streep-punt Gestempeld Zigzag Tekent een stippellijn langs het getekende pad met gespecificeerde tussenruimte Tekent punt en stippellijn langs het gegeven pad Gewoon standaard rechte lijnen Tekent geselecteerde vormen langs het pad met gespecificeerde afstand Tekent golvende zigzag langs het pad Zigzag-verhouding Vast te maken hulpmiddel Maakt het mogelijk om voorgaande frames te verwijderen, zodat ze niet opstapelen Frames niet opstapelen Crossfade Frames maken een overgang via vermenging Aantal crossfade-frames Drempelwaarde 1 Drempelwaarde 2 Snelkoppeling aanmaken Hulpmiddel wordt als snelkoppeling toegevoegd aan het startscherm, gebruik het in combinatie met de optie \"bestandskeuze overslaan\" om het benodigde gedrag te bereiken Canny Spiegelen 101 Verbeterde zoomvervaging Sobel Eenvoudig Laplaciaans Eenvoudig Hulpraster Toont ondersteunend raster over het tekengebied om te helpen bij nauwkeurige manipulaties Rasterkleur Celbreedte Celhoogte Sommige selectiebesturingselementen gebruiken een compacte lay-out om minder ruimte in te nemen Geef de camera toestemming in de instellingen om een afbeelding vast te leggen Opmaak Titel van het hoofdscherm Compacte keuzeschakelaars Constant Rate Factor (CRF) Een waarde %1$s betekent een langzame compressie, resulterend in een relatief kleine bestandsgrootte. %2$s betekent een snellere compressie, resulterend in een groot bestand. Lut-bilbiotheek Download een verzameling LUT\'s die je vervolgens kunt toepassen Collectie LUT\'s bijwerken (alleen nieuwe worden in de wachtrij geplaatst), die je vervolgens kunt toepassen Voorbeeldweergave Standaard voorbeeldweergave voor filters wijzigen Verbergen Weergeven Aangepaste, mooi uitziende schuifregelaar met animaties, dit is standaard voor deze app Schuifregelaar Moderne Material You-schuifregelaar, futuristisch, fris, toegankelijk Material 2 Fraai Material 2 gebaseerde schuifregelaar, niet modern, eenvoudig en rechttoe rechtaan Toepassen Dialoogknoppen centreren Open source-licenties Bekijk licenties van open source-bibliotheken die in deze app worden gebruikt Knoppen van dialogen worden indien mogelijk in het midden geplaatst in plaats van aan de linkerkant Oppervlakte Tonemapping inschakelen Opnieuw bemonsteren op basis van pixelgebiedrelatie. Deze methode geniet de voorkeur beelddecimering, omdat het resultaten levert zonder moiré. Maar wanneer het beeld is ingezoomd, is het vergelijkbaar met de dichtstbijzijnde-methode. Lagen-modus met mogelijkheid om afbeeldingen, tekst en meer vrij te plaatsen Laag bewerken Lagen in achtergrond Hetzelfde als eerste optie, maar met kleur in plaats van afbeelding Geen toegang tot de site, probeer VPN te gebruiken of controleer of de url correct is Opmaaklagen Lagen over afbeelding Gebruik afbeelding als achtergrond en voeg er verschillende lagen bovenop toe % invoeren Beta Snelle instellingen-strook Voeg een zwevende strook toe aan de gekozen kant tijdens het bewerken van afbeeldingen, die snelle instellingen opent als erop wordt geklikt Selectie wissen In te stellen groep \"%1$s\" wordt standaard uitgevouwen Base64 De opgegeven waarde is geen geldige Base64-tekenreeks Base64 plakken Base64 kopiëren Laad de afbeelding om de Base64-tekenreeks te kopiëren of op te slaan, als je die hebt, kun je deze hierboven plakken om de afbeelding te verkrijgen Opties Acties Base64 importeren Base64 delen Base64-tekenreeks decoderen naar afbeelding of afbeelding coderen naar Base64-indeling In te stellen groep \"%1$s\" wordt standaard samengevouwen Base64 Hulpmiddelen Base64 opslaan Kan geen lege of ongeldige Base64-tekenreeks kopiëren Base64-actions Contourkleur Contourdikte Contour toevoegen Contour rondom tekst toevoegen met opgegeven kleur en breedte Rotatie Uitvoerafbeeldingen krijgen naam in overeenkomst met het controlegetal van de gegevens Controlegtal als bestandsnaam Controlegetallen zijn gelijk, het kan veilig zijn Controlegetallen zijn niet gelijk, bestand kan onveilig zijn! Controlegetal-hulpmiddelen Tekst-hash Controlegetal Kies het bestand om de controlesom te berekenen op basis van het geselecteerde algoritme Berekenen Meer handige software in het partnerkanaal van Android-applicaties Voer tekst in om de controlesom te berekenen op basis van het geselecteerde algoritme Maaswerk-verlopen Algoritme Bekijk de online collectie van maaswerk-verlopen Vergelijk controlegetallen, bereken hashes, maak hexadecimale tekenreeksen van bestanden met behulp van verschillende hashing-algoritmen Verschil Overeenkomst! Vrije software (Partner) Bron van controlegetal Controlegetallen vergelijken Lettertype importeren (TTF/OTF) Geïmporteerde lettertypen Alleen TTF-lettertypen kunnen worden geïmporteerd Lettertypen exporteren Geen bestandsnaam Fout tijdens poging tot opslaan, probeer de uitvoermap te wijzigen Aangepaste pagina\'s Selectie van pagina\'s Geen Bevestiging bij het afsluiten van tools Als je niet-opgeslagen wijzigingen hebt tijdens het gebruik van bepaalde tools en deze probeert te sluiten, wordt het bevestigingsdialoogvenster weergegeven EXIF bewerken Metagegevens bewerken van een enkele afbeelding zonder hercompressie Sticker wijzigen Tik om beschikbare tags te bewerken Reeks vergelijken Kies bestand/bestanden om de controlesom te berekenen op basis van het geselecteerde algoritme Breedte passend Hoogte passend Map kiezen Bestanden kiezen Lengte schaal kop Stempel Tijdstempel Binnenruimte Patroonopmaak Verticale draailijn Horizontale draailijn Selectie inverteren Het horizontaal gesneden deel wordt verlaten, in plaats van delen samen te voegen rond het snijgebied Afbeelding versnijden Knip het afbeeldingsgedeelte af en voeg het linkerdeel samen (kan omgekeerd zijn) door verticale of horizontale lijnen Verticaal gesneden deel wordt verlaten, in plaats van delen samen te voegen rond het snijgebied Een maaswerk-verloop aanmaken met een aangepast aantal knopen en resolutie Verzameling van maaswerk-verlopen Maaswerk-verloop samenstellen van de bovenkant van gegeven afbeeldingen Resolutie X Resolutie Y Resolutie Maaswerk-verloop overlay Punten aanpassen Rastergrootte Markeringskleur Pixel per pixel Pixel-vergelijking Streepjescode scannen Type streepjescode De afbeelding van de streepjescode is volledig zwart-wit en niet gekleurd op het thema van de app Scan een streepjescode (QR, EAN, AZTEC, …) om de inhoud te lezen of plak een tekst om een nieuwe te genereren Hoogteverhouding Z/W afdwingen Afbeeldingen van albumhoezen uit audiobestanden extraheren, de meest voorkomende formaten worden ondersteund Audiohoezen extraheren Geen barcode aangetroffen Gegenereerde barcode komt hier Kies audio om te starten Audio kiezen Geen hoezen gevonden Rotatie uitschakelen Voorkomt het roteren van afbeeldingen met gebaren met twee vingers Schakel uitlijnen naar randen in Na het verplaatsen of zoomen worden afbeeldingen uitgelijnd om de frameranden te vullen Logboeken verzenden Klik om het app-logboekbestand te delen. Dit kan mij helpen het probleem op te sporen en problemen op te lossen Oeps… Er is iets misgegaan U kunt contact met mij opnemen via onderstaande opties en ik zal proberen een oplossing te vinden.\n(Vergeet niet om logboeken bij te voegen) Schrijven naar bestand Extraheer tekst uit een batch afbeeldingen en sla deze op in één tekstbestand Schrijven naar metadata Extraheer tekst uit elke afbeelding en plaats deze in EXIF-info van relatieve foto\'s Onzichtbare modus Gebruik steganografie om voor het oog onzichtbare watermerken te creëren in bytes van uw afbeeldingen Gebruik LSB Er zal gebruik worden gemaakt van de LSB-steganografiemethode (Less Significant Bit), anders FD (Frequency Domain) Rode ogen automatisch verwijderen Wachtwoord Ontgrendelen PDF is beveiligd Operatie bijna voltooid. Als u nu annuleert, moet u het programma opnieuw opstarten Datum gewijzigd Datum gewijzigd (omgekeerd) Maat Grootte (omgekeerd) MIME-type MIME-type (omgekeerd) Verlenging Extensie (omgekeerd) Datum toegevoegd Datum toegevoegd (omgekeerd) Van links naar rechts Rechts naar links Van boven naar beneden Van onder naar boven Vloeibaar glas Een schakelaar gebaseerd op de onlangs aangekondigde IOS 26 en zijn vloeistofglasontwerpsysteem Kies een afbeelding of plak/importeer Base64-gegevens hieronder Typ een afbeeldingslink om te beginnen Link plakken Caleidoscoop Secundaire hoek Zijkanten Kanaalmix Blauw groen Rood blauw Groen rood In rood In groen In blauw Cyaan Magenta Geel Kleur halftoon Contour Niveaus Offset Voronoi kristalliseert Vorm Strek Willekeurigheid Ontspikkelen Diffuus Hond Tweede straal Egaliseren Gloed Draai en knijp Pointilliseren Randkleur Polaire coördinaten Rect naar polair Polair om te rectificeren Omkeren in cirkel Verminder ruis Eenvoudig Solariseren Weven X kloof Y-opening X Breedte Y-breedte Draai Rubberen stempel Smeren Dikte Mengen Vervorming van bollens Brekingsindex Boog Spreidingshoek Fonkeling Stralen ASCII Verloop Maria Herfst Bot Jet Winter Oceaan Zomer Lente Coole variant HSV Roze Heet Woord Magma Inferno Plasma Viridi\'s Burgers Schemering Schemering verschoven Perspectief Auto Rechtzetten Bijsnijden toestaan Bijsnijden of perspectief Absoluut Turbo Diepgroen Lenscorrectie Doellensprofielbestand in JSON-indeling Download kant-en-klare lensprofielen Deelpercentages Exporteren als JSON Kopieer de tekenreeks met paletgegevens als JSON-representatie Naad snijwerk Startscherm Vergrendelscherm Ingebouwd Achtergronden exporteren Vernieuwen Verkrijg huidige Home-, Lock- en Built-in-achtergronden Geef toegang tot alle bestanden, dit is nodig om achtergronden op te halen Toestemming voor externe opslag beheren is niet voldoende. U moet toegang tot uw afbeeldingen toestaan. Zorg ervoor dat u \"Alles toestaan\" selecteert Voorinstelling toevoegen aan bestandsnaam Voegt het achtervoegsel met de geselecteerde voorinstelling toe aan de bestandsnaam van de afbeelding Voeg afbeeldingsschaalmodus toe aan bestandsnaam Voegt het achtervoegsel met de geselecteerde afbeeldingsschaalmodus toe aan de bestandsnaam van de afbeelding Ascii-kunst Converteer de afbeelding naar ASCII-tekst die op een afbeelding lijkt Params Past in sommige gevallen een negatief filter toe op de afbeelding voor een beter resultaat Schermafbeelding verwerken Screenshot niet vastgelegd. Probeer het opnieuw Opslaan overgeslagen %1$s bestanden overgeslagen Overslaan toestaan ​​indien groter Sommige tools mogen het opslaan van afbeeldingen overslaan als de resulterende bestandsgrootte groter zou zijn dan het origineel Kalender evenement Contact E-mail Locatie Telefoon Tekst Sms URL Wifi Open netwerk N.v.t SSID Telefoon Bericht Adres Onderwerp Lichaam Naam Organisatie Titel Telefoons E-mails URL\'s Adressen Samenvatting Beschrijving Locatie Organisator Startdatum Einddatum Status Breedte Lengte Streepjescode maken Streepjescode bewerken Wi-Fi-configuratie Beveiliging Contactpersoon kiezen Verleen contacten toestemming in de instellingen om automatisch aan te vullen met het geselecteerde contact Contactgegevens Voornaam Middelste naam Achternaam Uitspraak Telefoon toevoegen E-mailadres toevoegen Adres toevoegen Website Website toevoegen Opgemaakte naam Deze afbeelding wordt gebruikt om boven de streepjescode te plaatsen Code-aanpassing Deze afbeelding wordt gebruikt als logo in het midden van de QR-code Logo Logo-opvulling Logo-grootte Logo-hoeken Vierde oog Voegt oogsymmetrie toe aan QR-code door een vierde oog toe te voegen aan de onderste eindhoek Pixelvorm Framevorm Bolvorm Foutcorrectieniveau Donkere kleur Lichte kleur Hyper-OS Xiaomi HyperOS-achtige stijl Maskerpatroon Deze code is mogelijk niet scanbaar. Wijzig de uiterlijkparameters om deze op alle apparaten leesbaar te maken Niet scanbaar Tools zullen er compacter uitzien als het app-opstartprogramma op het startscherm Launcher-modus Vult een gebied met het geselecteerde penseel en de geselecteerde stijl Overstromingsvulling Spuiten Tekent een pad in graffitistijl Vierkante deeltjes Spuitdeeltjes hebben een vierkante vorm in plaats van cirkels Palethulpmiddelen Genereer basismateriaal/materiaal dat u palet uit een afbeelding, of import/exporteer naar verschillende paletformaten Palet bewerken Export-/importpalet in verschillende formaten Kleur naam Naam palet Paletformaat Exporteer het gegenereerde palet naar verschillende formaten Voegt een nieuwe kleur toe aan het huidige palet De indeling %1$s biedt geen ondersteuning voor het opgeven van een paletnaam Vanwege het Play Store-beleid kan deze functie niet worden opgenomen in de huidige build. Om toegang te krijgen tot deze functionaliteit, downloadt u ImageToolbox van een alternatieve bron. Hieronder vindt u de beschikbare builds op GitHub. Open Github-pagina Het originele bestand wordt vervangen door een nieuw bestand in plaats van het op te slaan in de geselecteerde map Verborgen watermerktekst gedetecteerd Verborgen watermerkafbeelding gedetecteerd Deze afbeelding was verborgen Generatief inschilderen Hiermee kunt u objecten in een afbeelding verwijderen met behulp van een AI-model, zonder afhankelijk te zijn van OpenCV. Om deze functie te gebruiken, downloadt de app het vereiste model (~200 MB) van GitHub Hiermee kunt u objecten in een afbeelding verwijderen met behulp van een AI-model, zonder afhankelijk te zijn van OpenCV. Dit kan een langdurige operatie zijn Analyse van foutniveau Luminantiegradiënt Gemiddelde afstand Kopieerbewegingsdetectie Behouden Coëfficiënt Klembordgegevens zijn te groot Gegevens zijn te groot om te kopiëren Eenvoudige weefpixelisatie Gespreide pixelisatie Kruispixelisatie Micro-macropixelisatie Orbitale pixelisatie Vortex-pixelisatie Pulse Grid-pixelisatie Nucleuspixelisatie Radiale weefpixelisatie Kan uri \"%1$s\" niet openen Sneeuwval-modus Ingeschakeld Grenskader Glitch-variant Kanaalverschuiving Maximale compensatie VHS Blokkeerfout Blokgrootte CRT-kromming Kromming Chroma Pixel smelt Maximale daling AI-hulpmiddelen Verschillende tools om afbeeldingen te verwerken via AI-modellen, zoals het verwijderen van artefacten of het verwijderen van ruis Compressie, gekartelde lijnen Tekenfilms, compressie van uitzendingen Algemene compressie, algemeen geluid Kleurloos cartoongeluid Snel, algemene compressie, algemene ruis, animatie/strips/anime Boek scannen Belichtingscorrectie Beste bij algemene compressie, kleurenafbeeldingen Beste bij algemene compressie, grijswaardenafbeeldingen Algemene compressie, grijswaardenafbeeldingen, sterker Algemene ruis, kleurenafbeeldingen Algemene ruis, kleurenafbeeldingen, betere details Algemene ruis, grijswaardenafbeeldingen Algemene ruis, grijswaardenafbeeldingen, sterker Algemene ruis, grijswaardenafbeeldingen, het sterkst Algemene compressie Algemene compressie Texturisatie, h264-compressie VHS-compressie Niet-standaard compressie (cinepak, msvideo1, roq) Bink-compressie, beter op het gebied van geometrie Binkcompressie, sterker Binkcompressie, zacht, behoudt details Eliminatie van het traptrede-effect, gladmakend Gescande kunst/tekeningen, milde compressie, moiré Kleur strepen Langzaam, waarbij halftonen worden verwijderd Algemene inkleurer voor grijswaarden/zwart-witafbeeldingen. Gebruik DDColor voor betere resultaten Rand verwijderen Verwijdert overmatige verscherping Langzaam, aarzelend Anti-aliasing, algemene artefacten, CGI KDM003 scanverwerking Lichtgewicht model voor beeldverbetering Verwijdering van compressieartefacten Verwijdering van compressieartefacten Verbandverwijdering met soepel resultaat Halftoonpatroonverwerking Ditherpatroon verwijderen V3 Verwijdering van JPEG-artefacten V2 Verbetering van de H.264-textuur VHS-verscherping en -verbetering Samenvoegen Brokgrootte Overlapgrootte Afbeeldingen groter dan %1$s px worden in stukken gesneden en verwerkt. Overlap vermengt deze om zichtbare naden te voorkomen. Grote maten kunnen instabiliteit veroorzaken bij goedkope apparaten Selecteer er een om te starten Wilt u het %1$s model verwijderen? U moet het opnieuw downloaden Bevestigen Modellen Gedownloade modellen Beschikbare modellen Voorbereiden Actief model Kan sessie niet openen Alleen .onnx/.ort-modellen kunnen worden geïmporteerd Model importeren Importeer een aangepast onnx-model voor verder gebruik, alleen onnx/ort-modellen worden geaccepteerd, ondersteunt bijna alle esrgan-achtige varianten Geïmporteerde modellen Algemene ruis, gekleurde beelden Algemene ruis, gekleurde beelden, sterker Algemene ruis, gekleurde beelden, het sterkst Vermindert dithering-artefacten en kleurbanden, waardoor vloeiende kleurovergangen en vlakke kleurgebieden worden verbeterd. Verbetert de helderheid en het contrast van het beeld met gebalanceerde highlights terwijl de natuurlijke kleuren behouden blijven. Maakt donkere beelden helderder, terwijl de details behouden blijven en overbelichting wordt vermeden. Verwijdert overmatige kleurtonen en herstelt een meer neutrale en natuurlijke kleurbalans. Past op Poisson gebaseerde ruistoning toe met de nadruk op het behoud van fijne details en texturen. Past zachte Poisson-ruistinten toe voor vloeiendere en minder agressieve visuele resultaten. Uniforme ruistoning gericht op detailbehoud en beeldhelderheid. Zachte uniforme ruistoning voor subtiele textuur en een glad uiterlijk. Repareert beschadigde of oneffen gebieden door artefacten opnieuw te schilderen en de beeldconsistentie te verbeteren. Lichtgewicht ontbandingsmodel dat kleurbanden verwijdert met minimale prestatiekosten. Optimaliseert afbeeldingen met zeer hoge compressieartefacten (0-20% kwaliteit) voor verbeterde helderheid. Verbetert afbeeldingen met hoge compressieartefacten (20-40% kwaliteit), waardoor details worden hersteld en ruis wordt verminderd. Verbetert afbeeldingen met gematigde compressie (40-60% kwaliteit), waarbij scherpte en vloeiendheid in evenwicht worden gebracht. Verfijnt afbeeldingen met lichte compressie (60-80% kwaliteit) om subtiele details en texturen te verbeteren. Verbetert vrijwel verliesvrije beelden enigszins (80-100% kwaliteit) terwijl de natuurlijke uitstraling en details behouden blijven. Eenvoudige en snelle inkleuring, tekenfilms, niet ideaal Vermindert beeldonscherpte enigszins, waardoor de scherpte wordt verbeterd zonder artefacten te introduceren. Langlopende operaties Beeld verwerken Verwerking Verwijdert zware JPEG-compressieartefacten in afbeeldingen van zeer lage kwaliteit (0-20%). Vermindert sterke JPEG-artefacten in sterk gecomprimeerde afbeeldingen (20-40%). Ruimt gemiddelde JPEG-artefacten op terwijl de beelddetails behouden blijven (40-60%). Verfijnt lichte JPEG-artefacten in afbeeldingen van redelijk hoge kwaliteit (60-80%). Vermindert op subtiele wijze kleine JPEG-artefacten in vrijwel verliesvrije beelden (80-100%). Verbetert fijne details en texturen, waardoor de waargenomen scherpte wordt verbeterd zonder zware artefacten. Verwerking voltooid Verwerking mislukt Verbetert huidtexturen en details met behoud van een natuurlijke uitstraling, geoptimaliseerd voor snelheid. Verwijdert JPEG-compressieartefacten en herstelt de beeldkwaliteit voor gecomprimeerde foto\'s. Vermindert ISO-ruis in foto\'s gemaakt bij weinig licht, waarbij details behouden blijven. Corrigeert overbelichte of ‘jumbo’ highlights en herstelt een betere toonbalans. Lichtgewicht en snel kleurmodel dat natuurlijke kleuren toevoegt aan grijswaardenafbeeldingen. DEJPEG Denoise Inkleuren Artefacten Uitbreiden Anime Scannen Luxe X4 upscaler voor algemene afbeeldingen; klein model dat minder GPU en tijd gebruikt, met matige vervaging en ruisonderdrukking. X2-upscaler voor algemene afbeeldingen, waarbij texturen en natuurlijke details behouden blijven. X4-upscaler voor algemene afbeeldingen met verbeterde texturen en realistische resultaten. X4-upscaler geoptimaliseerd voor anime-afbeeldingen; 6 RRDB-blokken voor scherpere lijnen en details. X4-upscaler met MSE-verlies produceert vloeiendere resultaten en minder artefacten voor algemene afbeeldingen. X4 Upscaler geoptimaliseerd voor anime-afbeeldingen; 4B32F-variant met scherpere details en vloeiende lijnen. X4 UltraSharp V2-model voor algemene afbeeldingen; benadrukt scherpte en helderheid. X4 UltraSharp V2 Lite; sneller en kleiner, behoudt details terwijl minder GPU-geheugen wordt gebruikt. Lichtgewicht model voor snelle achtergrondverwijdering. Evenwichtige prestaties en nauwkeurigheid. Werkt met portretten, objecten en scènes. Aanbevolen voor de meeste gebruikssituaties. BG verwijderen Horizontale randdikte Verticale randdikte %1$s kleur %1$s kleuren Het huidige model ondersteunt geen chunking, de afbeelding wordt verwerkt in de originele afmetingen, dit kan een hoog geheugengebruik en problemen met goedkope apparaten veroorzaken Chunking uitgeschakeld, afbeelding wordt verwerkt in de oorspronkelijke afmetingen. Dit kan een hoog geheugengebruik en problemen met goedkope apparaten veroorzaken, maar kan betere resultaten opleveren bij gevolgtrekking Chunken Zeer nauwkeurig beeldsegmentatiemodel voor het verwijderen van achtergronden Lichtgewicht versie van U2Net voor snellere achtergrondverwijdering met kleiner geheugengebruik. Het volledige DDColor-model levert hoogwaardige inkleuring voor algemene afbeeldingen met minimale artefacten. Beste keuze van alle inkleuringsmodellen. DDColor Getrainde en particuliere artistieke datasets; produceert diverse en artistieke kleurresultaten met minder onrealistische kleurartefacten. Lichtgewicht BiRefNet-model gebaseerd op Swin Transformer voor nauwkeurige achtergrondverwijdering. Hoogwaardige achtergrondverwijdering met scherpe randen en uitstekend detailbehoud, vooral bij complexe objecten en lastige achtergronden. Model voor achtergrondverwijdering dat nauwkeurige maskers met gladde randen produceert, geschikt voor algemene objecten en matig detailbehoud. Model al gedownload Model succesvol geïmporteerd Type Trefwoord Zeer snel Normaal Langzaam Zeer langzaam Bereken procenten Minimale waarde is %1$s Vervorm het beeld door met de vingers te tekenen Verdraaien Hardheid Warp-modus Beweging Groeien Krimpen Werveling CW Draai CCW Vervagen sterkte Topdaling Onderste daling Begin druppel Einde daling Downloaden Gladde vormen Gebruik superellipsen in plaats van standaard afgeronde rechthoeken voor vloeiendere, natuurlijkere vormen Vormtype Snee Afgerond Zacht Scherpe randen zonder afronding Klassieke afgeronde hoeken Vormentype Hoeken maat Eekhoorn Elegante afgeronde UI-elementen Bestandsnaamformaat Aangepaste tekst helemaal aan het begin van de bestandsnaam, perfect voor projectnamen, merken of persoonlijke tags. Gebruikt de originele bestandsnaam zonder extensie, zodat u de bronidentificatie intact kunt houden. De afbeeldingsbreedte in pixels, handig voor het volgen van resolutiewijzigingen of schaalresultaten. De afbeeldingshoogte in pixels, handig bij het werken met beeldverhoudingen of exporten. Genereert willekeurige cijfers om unieke bestandsnamen te garanderen; voeg meer cijfers toe voor extra veiligheid tegen duplicaten. Automatisch oplopende teller voor batchexports, ideaal bij het opslaan van meerdere afbeeldingen in één sessie. Voegt de toegepaste voorinstellingsnaam in de bestandsnaam in, zodat u gemakkelijk kunt onthouden hoe de afbeelding is verwerkt. Toont de afbeeldingsschaalmodus die wordt gebruikt tijdens de verwerking, zodat afbeeldingen waarvan het formaat is aangepast, bijgesneden of passend zijn gemaakt, beter te onderscheiden zijn. Aangepaste tekst aan het einde van de bestandsnaam, handig voor versiebeheer zoals _v2, _edited of _final. De bestandsextensie (png, jpg, webp, etc.), die automatisch overeenkomt met het daadwerkelijk opgeslagen formaat. Een aanpasbare tijdstempel waarmee u uw eigen formaat kunt definiëren via Java-specificatie voor perfecte sortering. Fling-type Android-native iOS-stijl Gladde curve Snelle stop Veerkrachtig Zwevend Pittig Ultraglad Adaptief Toegankelijkheid bewust Verminderde beweging Native Android-scrollfysica voor basislijnvergelijking Gebalanceerd, soepel scrollen voor algemeen gebruik Hogere wrijving iOS-achtig scrollgedrag Unieke spline-curve voor een duidelijk scrollgevoel Nauwkeurig scrollen met snelle stop Speelse, responsieve veerkrachtige scroll Lange, glijdende scrolls voor het bladeren door inhoud Snel, responsief scrollen voor interactieve gebruikersinterfaces Premium soepel scrollen met uitgebreid momentum Past de fysica aan op basis van de vliegsnelheid Respecteert de instellingen voor systeemtoegankelijkheid Minimale beweging voor toegankelijkheidsbehoeften Primaire lijnen Voegt elke vijfde lijn een dikkere lijn toe Vulkleur Verborgen hulpmiddelen Hulpmiddelen verborgen om te delen Kleurenbibliotheek Blader door een uitgebreide collectie kleuren Verscherpt en verwijdert onscherpte uit afbeeldingen met behoud van natuurlijke details, ideaal voor het corrigeren van onscherpe foto\'s. Herstelt op intelligente wijze afbeeldingen waarvan het formaat eerder is gewijzigd, waarbij verloren details en texturen worden hersteld. Geoptimaliseerd voor live-action-inhoud, vermindert compressieartefacten en verbetert fijne details in film-/tv-showframes. Converteert beelden van VHS-kwaliteit naar HD, verwijdert bandruis en verbetert de resolutie met behoud van het vintage gevoel. Gespecialiseerd voor afbeeldingen en schermafbeeldingen met veel tekst, verscherpt karakters en verbetert de leesbaarheid. Geavanceerde opschaling getraind op diverse datasets, uitstekend geschikt voor algemene fotoverbetering. Geoptimaliseerd voor webgecomprimeerde foto\'s, verwijdert JPEG-artefacten en herstelt de natuurlijke uitstraling. Verbeterde versie voor webfoto\'s met beter behoud van textuur en vermindering van artefacten. 2x opschalen met Dual Aggregation Transformer-technologie, behoudt scherpte en natuurlijke details. 3x opschaling met behulp van geavanceerde transformatorarchitectuur, ideaal voor gematigde uitbreidingsbehoeften. 4x hoogwaardige opschaling met het modernste transformatornetwerk, behoudt fijne details op grotere schaal. Verwijdert onscherpte/ruis en trillingen uit foto\'s. Algemeen gebruik, maar het beste op foto\'s. Herstelt afbeeldingen van lage kwaliteit met behulp van de Swin2SR-transformator, geoptimaliseerd voor BSRGAN-degradatie. Ideaal voor het corrigeren van zware compressieartefacten en het verbeteren van details op 4x schaal. 4x opschalen met SwinIR-transformator getraind op BSRGAN-degradatie. Gebruikt GAN voor scherpere texturen en natuurlijkere details in foto\'s en complexe scènes. Pad PDF samenvoegen Combineer meerdere PDF-bestanden in één document Bestanden bestellen blz. PDF splitsen Extraheer specifieke pagina\'s uit een PDF-document PDF roteren Pagina-oriëntatie permanent corrigeren Pagina\'s PDF opnieuw rangschikken Versleep pagina\'s om ze opnieuw te ordenen Pagina\'s vasthouden en slepen Paginanummers Voeg automatisch nummering toe aan uw documenten Labelformaat PDF naar tekst (OCR) Extraheer platte tekst uit uw PDF-documenten Overlay met aangepaste tekst voor branding of beveiliging Handtekening Voeg uw elektronische handtekening toe aan elk document Deze wordt gebruikt als handtekening Ontgrendel PDF Verwijder wachtwoorden uit uw beveiligde bestanden Bescherm PDF Beveilig uw documenten met sterke encryptie Succes PDF ontgrendeld, u kunt het opslaan of delen PDF repareren Probeer beschadigde of onleesbare documenten te repareren Grijstinten Converteer alle in het document ingebedde afbeeldingen naar grijswaarden PDF comprimeren Optimaliseer de bestandsgrootte van uw document om gemakkelijker te kunnen delen ImageToolbox bouwt de interne kruisverwijzingstabel opnieuw op en genereert de bestandsstructuur helemaal opnieuw. Hierdoor kan de toegang tot veel bestanden worden hersteld die \\"niet kunnen worden geopend\\" Deze tool converteert alle documentafbeeldingen naar grijswaarden. Beste voor afdrukken en verkleinen van de bestandsgrootte Metagegevens Bewerk documenteigenschappen voor betere privacy Labels Producent Auteur Trefwoorden Schepper Privacy Diep schoon Wis alle beschikbare metagegevens voor dit document Pagina Diepe OCR Extraheer tekst uit het document en sla deze op in één tekstbestand met behulp van de Tesseract-engine Kan niet alle pagina\'s verwijderen Verwijder PDF-pagina\'s Verwijder specifieke pagina\'s uit het PDF-document Tik om te verwijderen Handmatig PDF bijsnijden Snijd documentpagina\'s bij tot de gewenste grenzen PDF plat maken Maak PDF onaanpasbaar door documentpagina\'s te rasteren Kan de camera niet starten. Controleer de rechten en zorg ervoor dat deze niet door een andere app wordt gebruikt. Afbeeldingen extraheren Extraheer afbeeldingen die zijn ingesloten in PDF\'s met hun oorspronkelijke resolutie Dit PDF-bestand bevat geen ingesloten afbeeldingen Deze tool scant elke pagina en herstelt bronafbeeldingen van volledige kwaliteit – perfect voor het opslaan van originelen uit documenten Handtekening tekenen Penparams Gebruik uw eigen handtekening als afbeelding voor plaatsing op documenten Zip-PDF Splits het document met een bepaald interval en pak nieuwe documenten in een zip-archief Interval PDF afdrukken Document voorbereiden voor afdrukken met aangepast paginaformaat Pagina\'s per vel Oriëntatie Paginagrootte Marge Bloeien Zachte knie Geoptimaliseerd voor anime en tekenfilms. Snel opschalen met verbeterde natuurlijke kleuren en minder artefacten Samsung One UI 7-achtige stijl Voer hier elementaire wiskundige symbolen in om de gewenste waarde te berekenen (bijvoorbeeld (5+5)*10) Wiskundige uitdrukking Neem maximaal %1$s afbeeldingen op Datum en tijd behouden Bewaar altijd exif-tags gerelateerd aan datum en tijd, werkt onafhankelijk van de optie exif behouden Achtergrondkleur voor alfaformaten Voegt de mogelijkheid toe om de achtergrondkleur in te stellen voor elk afbeeldingsformaat met alfa-ondersteuning, indien uitgeschakeld is dit alleen beschikbaar voor niet-alfa-formaten Project openen Ga door met het bewerken van een eerder opgeslagen Image Toolbox-project Kan het Image Toolbox-project niet openen Bij het Image Toolbox-project ontbreken projectgegevens Het Image Toolbox-project is beschadigd Niet-ondersteunde Image Toolbox-projectversie: %1$d Project opslaan Bewaar lagen, achtergronden en bewerkingsgeschiedenis in een bewerkbaar projectbestand Kan niet worden geopend Schrijf naar doorzoekbare PDF Herken tekst uit de afbeeldingsbatch en sla doorzoekbare PDF op met afbeelding en selecteerbare tekstlaag Laag-alfa Horizontale spiegeling Verticale spiegeling Slot Schaduw toevoegen Schaduwkleur Tekstgeometrie Rek tekst uit of scheef voor scherpere stilering Schaal X Scheef X Annotaties verwijderen Verwijder geselecteerde annotatietypen, zoals koppelingen, opmerkingen, markeringen, vormen of formuliervelden, van de PDF-pagina\'s Hyperlinks Bestandsbijlagen Lijnen Pop-ups Stempels Vormen Tekstnotities Tekstopmaak Formuliervelden Opmaak Onbekend Annotaties Degroeperen Voeg vervagingsschaduw toe achter de laag met configureerbare kleuren en offsets ================================================ FILE: core/resources/src/main/res/values-pa/strings.xml ================================================ ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ: %1$s ਆਕਾਰ %1$s Loading… ਪੂਰਵਦਰਸ਼ਨ ਲਈ ਚਿੱਤਰ ਬਹੁਤ ਵੱਡਾ ਹੈ, ਪਰ ਫਿਰ ਵੀ ਸੁਰੱਖਿਅਤ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਜਾਵੇਗੀ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਚਿੱਤਰ ਚੁਣੋ ਚੌੜਾਈ %1$s ਉਚਾਈ %1$s ਗੁਣਵੱਤਾ ਐਕਸਟੈਂਸ਼ਨ ਆਕਾਰ ਬਦਲੋ ਸਪਸ਼ਟ ਲਚਕੀਲਾ ਚਿੱਤਰ ਚੁਣੋ ਕੀ ਤੁਸੀਂ ਅਸਲ ਵਿੱਚ ਐਪ ਨੂੰ ਬੰਦ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? ਐਪ ਬੰਦ ਹੋ ਰਿਹਾ ਹੈ ਰਹੋ ਬੰਦ ਕਰੋ ਚਿੱਤਰ ਰੀਸੈਟ ਕਰੋ ਚਿੱਤਰ ਤਬਦੀਲੀਆਂ ਸ਼ੁਰੂਆਤੀ ਮੁੱਲਾਂ \'ਤੇ ਵਾਪਸ ਆ ਜਾਣਗੀਆਂ ਮੁੱਲ ਸਹੀ ਢੰਗ ਨਾਲ ਰੀਸੈਟ ਕਰੋ ਰੀਸੈਟ ਕਰੋ ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ ਐਪ ਨੂੰ ਰੀਸਟਾਰਟ ਕਰੋ ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ ਅਪਵਾਦ EXIF ਸੰਪਾਦਿਤ ਕਰੋ ਠੀਕ ਹੈ ਕੋਈ EXIF ਡੇਟਾ ਨਹੀਂ ਮਿਲਿਆ ਟੈਗ ਸ਼ਾਮਲ ਕਰੋ ਸੇਵ ਕਰੋ ਸਾਫ਼ EXIF ਸਾਫ਼ ਕਰੋ ਰੱਦ ਕਰੋ ਸਾਰਾ ਚਿੱਤਰ EXIF ਡੇਟਾ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ। ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ! ਪ੍ਰੀਸੈਟਸ ਫਸਲ ਸੰਭਾਲ ਰਿਹਾ ਹੈ ਜੇਕਰ ਤੁਸੀਂ ਹੁਣੇ ਬਾਹਰ ਨਿਕਲਦੇ ਹੋ ਤਾਂ ਸਾਰੀਆਂ ਅਣਰੱਖਿਅਤ ਤਬਦੀਲੀਆਂ ਖਤਮ ਹੋ ਜਾਣਗੀਆਂ ਸੂਤਰ ਸੰਕੇਤਾਵਲੀ ਨਵੀਨਤਮ ਅਪਡੇਟਸ ਪ੍ਰਾਪਤ ਕਰੋ, ਮੁੱਦਿਆਂ \'ਤੇ ਚਰਚਾ ਕਰੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਸਿੰਗਲ ਸੰਪਾਦਨ ਦਿੱਤੇ ਗਏ ਇੱਕਲੇ ਚਿੱਤਰ ਦੇ ਚਸ਼ਮੇ ਬਦਲੋ ਰੰਗ ਚੁਣੋ ਚਿੱਤਰ, ਕਾਪੀ ਜਾਂ ਸ਼ੇਅਰ ਤੋਂ ਰੰਗ ਚੁਣੋ ਚਿੱਤਰ ਰੰਗ ਰੰਗ ਕਾਪੀ ਕੀਤਾ ਗਿਆ ਚਿੱਤਰ ਨੂੰ ਕਿਸੇ ਵੀ ਹੱਦ ਤੱਕ ਕੱਟੋ ਸੰਸਕਰਣ EXIF ਰੱਖੋ Images: %d ਝਲਕ ਬਦਲੋ ਹਟਾਓ ਦਿੱਤੇ ਚਿੱਤਰ ਤੋਂ ਰੰਗ ਪੈਲੇਟ ਸਵੈਚ ਤਿਆਰ ਕਰੋ ਪੈਲੇਟ ਤਿਆਰ ਕਰੋ ਪੈਲੇਟ ਅੱਪਡੇਟ ਕਰੋ ਨਵਾਂ ਸੰਸਕਰਣ %1$s ਅਸਮਰਥਿਤ ਕਿਸਮ: %1$s ਦਿੱਤੇ ਚਿੱਤਰ ਲਈ ਪੈਲੇਟ ਤਿਆਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਮੂਲ ਆਉਟਪੁੱਟ ਫੋਲਡਰ ਡਿਫਾਲਟ ਪ੍ਰਥਾ ਨਿਰਦਿਸ਼ਟ ਡਿਵਾਈਸ ਸਟੋਰੇਜ ਭਾਰ ਦੁਆਰਾ ਆਕਾਰ ਬਦਲੋ KB ਵਿੱਚ ਅਧਿਕਤਮ ਆਕਾਰ KB ਵਿੱਚ ਦਿੱਤੇ ਆਕਾਰ ਦੇ ਬਾਅਦ ਇੱਕ ਚਿੱਤਰ ਨੂੰ ਮੁੜ ਆਕਾਰ ਦਿਓ ਤੁਲਨਾ ਕਰੋ ਦਿੱਤੇ ਗਏ ਦੋ ਚਿੱਤਰਾਂ ਦੀ ਤੁਲਨਾ ਕਰੋ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਦੋ ਚਿੱਤਰ ਚੁਣੋ ਚਿੱਤਰ ਚੁਣੋ ਸੈਟਿੰਗਾਂ ਨਾਈਟ ਮੋਡ ਹਨੇਰ ਚਾਨਣ ਸਿਸਟਮ ਗਤੀਸ਼ੀਲ ਰੰਗ ਕਸਟਮਾਈਜ਼ੇਸ਼ਨ ਚਿੱਤਰ ਮੋਨੇਟ ਦੀ ਆਗਿਆ ਦਿਓ ਜੇਕਰ ਸਮਰਥਿਤ ਹੈ, ਜਦੋਂ ਤੁਸੀਂ ਸੰਪਾਦਿਤ ਕਰਨ ਲਈ ਇੱਕ ਚਿੱਤਰ ਚੁਣਦੇ ਹੋ, ਤਾਂ ਐਪ ਦੇ ਰੰਗ ਇਸ ਚਿੱਤਰ ਲਈ ਅਪਣਾਏ ਜਾਣਗੇ ਭਾਸ਼ਾ ਅਮੋਲਡ ਮੋਡ ਜੇਕਰ ਸਮਰਥਿਤ ਸਤਹਾਂ ਦਾ ਰੰਗ ਰਾਤ ਦੇ ਮੋਡ ਵਿੱਚ ਬਿਲਕੁਲ ਗੂੜ੍ਹੇ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਜਾਵੇਗਾ ਰੰਗ ਸਕੀਮ ਲਾਲ ਹਰਾ ਨੀਲਾ ਵੈਧ aRGB-ਕੋਡ ਪੇਸਟ ਕਰੋ। ਪੇਸਟ ਕਰਨ ਲਈ ਕੁਝ ਨਹੀਂ ਡਾਇਨਾਮਿਕ ਰੰਗ ਚਾਲੂ ਹੋਣ \'ਤੇ ਐਪ ਰੰਗ ਸਕੀਮ ਨੂੰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ ਐਪ ਥੀਮ ਚੁਣੇ ਗਏ ਰੰਗ \'ਤੇ ਆਧਾਰਿਤ ਹੋਵੇਗੀ ਐਪ ਬਾਰੇ ਕੋਈ ਅੱਪਡੇਟ ਨਹੀਂ ਮਿਲੇ ਮੁੱਦਾ ਟਰੈਕਰ ਇੱਥੇ ਬੱਗ ਰਿਪੋਰਟਾਂ ਅਤੇ ਵਿਸ਼ੇਸ਼ਤਾ ਬੇਨਤੀਆਂ ਭੇਜੋ ਅਨੁਵਾਦ ਵਿੱਚ ਮਦਦ ਕਰੋ ਅਨੁਵਾਦ ਦੀਆਂ ਗਲਤੀਆਂ ਨੂੰ ਠੀਕ ਕਰੋ ਜਾਂ ਕਿਸੇ ਹੋਰ ਭਾਸ਼ਾ ਵਿੱਚ ਪ੍ਰੋਜੈਕਟ ਦਾ ਸਥਾਨੀਕਰਨ ਕਰੋ ਤੁਹਾਡੀ ਪੁੱਛਗਿੱਛ ਦੁਆਰਾ ਕੁਝ ਨਹੀਂ ਮਿਲਿਆ ਇੱਥੇ ਖੋਜ ਕਰੋ ਜੇਕਰ ਸਮਰਥਿਤ ਹੈ, ਤਾਂ ਐਪ ਦੇ ਰੰਗਾਂ ਨੂੰ ਵਾਲਪੇਪਰ ਦੇ ਰੰਗਾਂ ਲਈ ਅਪਣਾਇਆ ਜਾਵੇਗਾ %d ਚਿੱਤਰ(ਚਿੱਤਰਾਂ) ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਈ - ਮੇਲ ਪ੍ਰਾਇਮਰੀ ਤੀਜੇ ਦਰਜੇ ਸੈਕੰਡਰੀ ਬਾਰਡਰ ਮੋਟਾਈ ਸਤ੍ਹਾ ਮੁੱਲ ਸ਼ਾਮਲ ਕਰੋ ਇਜਾਜ਼ਤ ਗ੍ਰਾਂਟ ਐਪ ਨੂੰ ਕੰਮ ਕਰਨ ਲਈ ਚਿੱਤਰਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਤੁਹਾਡੀ ਸਟੋਰੇਜ ਤੱਕ ਪਹੁੰਚ ਦੀ ਲੋੜ ਹੈ, ਇਹ ਜ਼ਰੂਰੀ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਅਗਲੇ ਡਾਇਲਾਗ ਬਾਕਸ ਵਿੱਚ ਇਜਾਜ਼ਤ ਦਿਓ। ਐਪ ਨੂੰ ਕੰਮ ਕਰਨ ਲਈ ਇਸ ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਹੈ, ਕਿਰਪਾ ਕਰਕੇ ਇਸਨੂੰ ਹੱਥੀਂ ਦਿਓ ਬਾਹਰੀ ਸਟੋਰੇਜ ਮੋਨੇਟ ਰੰਗ ਇਹ ਐਪਲੀਕੇਸ਼ਨ ਪੂਰੀ ਤਰ੍ਹਾਂ ਮੁਫਤ ਹੈ, ਪਰ ਜੇ ਤੁਸੀਂ ਪ੍ਰੋਜੈਕਟ ਦੇ ਵਿਕਾਸ ਦਾ ਸਮਰਥਨ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਤੁਸੀਂ ਇੱਥੇ ਕਲਿੱਕ ਕਰ ਸਕਦੇ ਹੋ FAB ਅਲਾਈਨਮੈਂਟ ਅੱਪਡੇਟ ਲਈ ਚੈੱਕ ਕਰੋ ਜੇਕਰ ਸਮਰਥਿਤ ਹੈ, ਤਾਂ ਐਪ ਸਟਾਰਟਅੱਪ \'ਤੇ ਅੱਪਡੇਟ ਡਾਇਲਾਗ ਤੁਹਾਨੂੰ ਦਿਖਾਇਆ ਜਾਵੇਗਾ ਚਿੱਤਰ ਜ਼ੂਮ ਸ਼ੇਅਰ ਕਰੋ ਅਗੇਤਰ ਫਾਈਲ ਦਾ ਨਾਮ ਇਮੋਜੀ ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਦਰਸ਼ਿਤ ਕਰਨ ਲਈ ਕਿਹੜਾ ਇਮੋਜੀ ਚੁਣੋ ਫਾਈਲ ਦਾ ਆਕਾਰ ਸ਼ਾਮਲ ਕਰੋ ਜੇਕਰ ਸਮਰੱਥ ਹੈ, ਤਾਂ ਆਉਟਪੁੱਟ ਫਾਈਲ ਦੇ ਨਾਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਚਿੱਤਰ ਦੀ ਚੌੜਾਈ ਅਤੇ ਉਚਾਈ ਜੋੜਦਾ ਹੈ EXIF ਮਿਟਾਓ ਚਿੱਤਰਾਂ ਦੇ ਕਿਸੇ ਵੀ ਸਮੂਹ ਤੋਂ EXIF ਮੈਟਾਡੇਟਾ ਮਿਟਾਓ ਚਿੱਤਰ ਝਲਕ ਕਿਸੇ ਵੀ ਕਿਸਮ ਦੀਆਂ ਤਸਵੀਰਾਂ ਦਾ ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ: GIF, SVG, ਅਤੇ ਹੋਰ ਚਿੱਤਰ ਸਰੋਤ ਫੋਟੋ ਚੋਣਕਾਰ ਗੈਲਰੀ ਫਾਈਲ ਐਕਸਪਲੋਰਰ ਐਂਡਰੌਇਡ ਆਧੁਨਿਕ ਫੋਟੋ ਚੋਣਕਾਰ ਜੋ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ, ਸਿਰਫ ਐਂਡਰਾਇਡ 12+ \'ਤੇ ਕੰਮ ਕਰ ਸਕਦਾ ਹੈ। EXIF ਮੈਟਾਡੇਟਾ ਪ੍ਰਾਪਤ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆਵਾਂ ਹਨ ਸਧਾਰਨ ਗੈਲਰੀ ਚਿੱਤਰ ਚੋਣਕਾਰ। ਇਹ ਤਾਂ ਹੀ ਕੰਮ ਕਰੇਗਾ ਜੇਕਰ ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਐਪ ਹੈ ਜੋ ਮੀਡੀਆ ਪਿਕਿੰਗ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ ਚਿੱਤਰ ਚੁਣਨ ਲਈ GetContent ਇਰਾਦੇ ਦੀ ਵਰਤੋਂ ਕਰੋ। ਹਰ ਜਗ੍ਹਾ ਕੰਮ ਕਰਦਾ ਹੈ, ਪਰ ਕੁਝ ਡਿਵਾਈਸਾਂ \'ਤੇ ਚੁਣੀਆਂ ਗਈਆਂ ਤਸਵੀਰਾਂ ਪ੍ਰਾਪਤ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆਵਾਂ ਲਈ ਜਾਣਿਆ ਜਾਂਦਾ ਹੈ। ਇਹ ਮੇਰਾ ਕਸੂਰ ਨਹੀਂ ਹੈ। ਵਿਕਲਪ ਪ੍ਰਬੰਧ ਸੰਪਾਦਿਤ ਕਰੋ ਆਰਡਰ ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਕਲਪਾਂ ਦਾ ਕ੍ਰਮ ਨਿਰਧਾਰਤ ਕਰਦਾ ਹੈ ਇਮੋਜੀ ਦੀ ਗਿਣਤੀ sequenceNum ਅਸਲੀ ਫਾਈਲ ਨਾਮ ਅਸਲ ਫਾਈਲ ਨਾਮ ਸ਼ਾਮਲ ਕਰੋ ਜੇਕਰ ਸਮਰਥਿਤ ਹੈ ਤਾਂ ਆਉਟਪੁੱਟ ਚਿੱਤਰ ਦੇ ਨਾਮ ਵਿੱਚ ਅਸਲੀ ਫਾਈਲ ਨਾਮ ਜੋੜਦਾ ਹੈ ਕ੍ਰਮ ਨੰਬਰ ਬਦਲੋ ਜੇਕਰ ਸਮਰਥਿਤ ਹੈ ਤਾਂ ਸਟੈਂਡਰਡ ਟਾਈਮਸਟੈਂਪ ਨੂੰ ਚਿੱਤਰ ਕ੍ਰਮ ਨੰਬਰ \'ਤੇ ਬਦਲ ਦਿੰਦਾ ਹੈ ਜੇਕਰ ਤੁਸੀਂ ਬੈਚ ਪ੍ਰੋਸੈਸਿੰਗ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋ ਜੇਕਰ ਫੋਟੋ ਚੋਣਕਾਰ ਚਿੱਤਰ ਸਰੋਤ ਚੁਣਿਆ ਗਿਆ ਹੈ ਤਾਂ ਅਸਲ ਫਾਈਲ ਨਾਮ ਜੋੜਨਾ ਕੰਮ ਨਹੀਂ ਕਰਦਾ ਨੈੱਟ ਤੋਂ ਚਿੱਤਰ ਲੋਡ ਕਰੋ ਜੇਕਰ ਤੁਸੀਂ ਚਾਹੋ ਤਾਂ ਪੂਰਵਦਰਸ਼ਨ, ਜ਼ੂਮ, ਸੰਪਾਦਿਤ ਅਤੇ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਇੰਟਰਨੈਟ ਤੋਂ ਕਿਸੇ ਵੀ ਚਿੱਤਰ ਨੂੰ ਲੋਡ ਕਰੋ। ਕੋਈ ਚਿੱਤਰ ਨਹੀਂ ਚਿੱਤਰ ਲਿੰਕ ਭਰੋ ਫਿੱਟ ਸਮੱਗਰੀ ਦਾ ਪੈਮਾਨਾ ਹਰ ਤਸਵੀਰ ਨੂੰ ਚੌੜਾਈ ਅਤੇ ਉਚਾਈ ਪੈਰਾਮੀਟਰ ਦੁਆਰਾ ਦਿੱਤੇ ਗਏ ਚਿੱਤਰ ਵਿੱਚ ਮਜਬੂਰ ਕਰਦਾ ਹੈ - ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲ ਸਕਦਾ ਹੈ ਚੌੜਾਈ ਜਾਂ ਉਚਾਈ ਪੈਰਾਮੀਟਰ ਦੁਆਰਾ ਦਿੱਤੇ ਗਏ ਲੰਬੇ ਸਾਈਡ ਵਾਲੇ ਚਿੱਤਰਾਂ ਦਾ ਆਕਾਰ ਬਦਲਦਾ ਹੈ, ਸਾਰੇ ਆਕਾਰ ਦੀ ਗਣਨਾ ਸੁਰੱਖਿਅਤ ਕਰਨ ਤੋਂ ਬਾਅਦ ਕੀਤੀ ਜਾਵੇਗੀ - ਆਕਾਰ ਅਨੁਪਾਤ ਰੱਖਦਾ ਹੈ ਚਮਕ ਕੰਟ੍ਰਾਸਟ ਹਿਊ ਸੰਤ੍ਰਿਪਤਾ ਫਿਲਟਰ ਸ਼ਾਮਲ ਕਰੋ ਫਿਲਟਰ ਦਿੱਤੇ ਚਿੱਤਰਾਂ \'ਤੇ ਕੋਈ ਵੀ ਫਿਲਟਰ ਚੇਨ ਲਾਗੂ ਕਰੋ ਫਿਲਟਰ ਚਾਨਣ ਰੰਗ ਫਿਲਟਰ ਅਲਫ਼ਾ ਸੰਪਰਕ ਚਿੱਟਾ ਸੰਤੁਲਨ ਤਾਪਮਾਨ ਰੰਗਤ ਮੋਨੋਕ੍ਰੋਮ ਗਾਮਾ ਹਾਈਲਾਈਟਸ ਅਤੇ ਸ਼ੈਡੋ ਹਾਈਲਾਈਟਸ ਪਰਛਾਵੇਂ ਧੁੰਦ ਪ੍ਰਭਾਵ ਦੂਰੀ ਢਲਾਨ ਤਿੱਖਾ ਕਰੋ ਸੇਪੀਆ ਨਕਾਰਾਤਮਕ ਸੋਲਰਾਈਜ਼ ਕਰੋ ਵਾਈਬ੍ਰੈਂਸ ਕਾਲਾ ਅਤੇ ਚਿੱਟਾ ਕਰਾਸਸ਼ੈਚ ਵਿੱਥ ਲਾਈਨ ਦੀ ਚੌੜਾਈ ਸੋਬਲ ਕਿਨਾਰੇ ਧੁੰਦਲਾ ਹਾਫਟੋਨ CGA ਕਲਰਸਪੇਸ ਗੌਸੀਅਨ ਬਲਰ ਬਾਕਸ ਬਲਰ ਦੋ-ਪੱਖੀ ਧੁੰਦਲਾਪਨ ਐਮਬੌਸ ਲੈਪਲੇਸ਼ੀਅਨ ਵਿਗਨੇਟ ਸ਼ੁਰੂ ਕਰੋ ਅੰਤ ਕੁਵਾਹਰਾ ਸਮੂਥਿੰਗ ਸਟੈਕ ਬਲਰ ਰੇਡੀਅਸ ਸਕੇਲ ਵਿਗਾੜ ਕੋਣ ਘੁੰਮਣਾ ਬਲਜ ਫੈਲਾਅ ਗੋਲਾਕਾਰ ਪ੍ਰਤੀਕਰਮ ਰਿਫ੍ਰੈਕਟਿਵ ਇੰਡੈਕਸ ਕੱਚ ਦਾ ਗੋਲਾ ਅਪਵਰਤਨ ਰੰਗ ਮੈਟ੍ਰਿਕਸ ਧੁੰਦਲਾਪਨ ਸੀਮਾਵਾਂ ਦਾ ਆਕਾਰ ਬਦਲਣਾ ਦਿੱਤੀ ਗਈ ਚੌੜਾਈ ਅਤੇ ਉਚਾਈ ਸੀਮਾਵਾਂ ਦੀ ਪਾਲਣਾ ਕਰਨ ਲਈ ਚੁਣੀਆਂ ਗਈਆਂ ਤਸਵੀਰਾਂ ਦਾ ਆਕਾਰ ਅਨੁਪਾਤ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਦੇ ਹੋਏ ਮੁੜ ਆਕਾਰ ਦਿਓ ਸਕੈਚ ਥ੍ਰੈਸ਼ਹੋਲਡ ਕੁਆਂਟਾਇਜ਼ੇਸ਼ਨ ਪੱਧਰ ਨਿਰਵਿਘਨ ਟੂਨ ਟੂਨ ਪੋਸਟਰਾਈਜ਼ ਕਰੋ ਗੈਰ ਅਧਿਕਤਮ ਦਮਨ ਕਮਜ਼ੋਰ ਪਿਕਸਲ ਸੰਮਿਲਨ ਝਾਂਕਨਾ ਕਨਵੋਲਿਊਸ਼ਨ 3x3 RGB ਫਿਲਟਰ ਝੂਠਾ ਰੰਗ ਪਹਿਲਾ ਰੰਗ ਦੂਜਾ ਰੰਗ ਮੁੜ ਕ੍ਰਮਬੱਧ ਕਰੋ ਤੇਜ਼ ਬਲਰ ਧੁੰਦਲਾ ਆਕਾਰ ਧੁੰਦਲਾ ਕੇਂਦਰ x ਬਲਰ ਸੈਂਟਰ y ਜ਼ੂਮ ਬਲਰ ਰੰਗ ਸੰਤੁਲਨ ਲੂਮਿਨੈਂਸ ਥ੍ਰੈਸ਼ਹੋਲਡ ਤੁਸੀਂ Files ਐਪ ਨੂੰ ਅਯੋਗ ਕਰ ਦਿੱਤਾ ਹੈ, ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਇਸਨੂੰ ਕਿਰਿਆਸ਼ੀਲ ਕਰੋ ਡਰਾਅ ਚਿੱਤਰ \'ਤੇ ਖਿੱਚੋ ਜਿਵੇਂ ਕਿ ਇੱਕ ਸਕੈਚਬੁੱਕ ਵਿੱਚ, ਜਾਂ ਬੈਕਗ੍ਰਾਉਂਡ \'ਤੇ ਹੀ ਖਿੱਚੋ ਪੇਂਟ ਰੰਗ ਪੇਂਟ ਅਲਫ਼ਾ ਚਿੱਤਰ \'ਤੇ ਖਿੱਚੋ ਇੱਕ ਚਿੱਤਰ ਚੁਣੋ ਅਤੇ ਇਸ \'ਤੇ ਕੁਝ ਖਿੱਚੋ ਬੈਕਗ੍ਰਾਊਂਡ \'ਤੇ ਖਿੱਚੋ ਬੈਕਗ੍ਰਾਉਂਡ ਰੰਗ ਚੁਣੋ ਅਤੇ ਇਸਦੇ ਸਿਖਰ \'ਤੇ ਖਿੱਚੋ ਬੈਕਗ੍ਰਾਊਂਡ ਦਾ ਰੰਗ ਸਿਫਰ ਏਈਐਸ ਕ੍ਰਿਪਟੋ ਐਲਗੋਰਿਦਮ ਦੇ ਅਧਾਰ ਤੇ ਕਿਸੇ ਵੀ ਫਾਈਲ ਨੂੰ ਐਨਕ੍ਰਿਪਟ ਅਤੇ ਡੀਕ੍ਰਿਪਟ ਕਰੋ (ਸਿਰਫ ਚਿੱਤਰ ਹੀ ਨਹੀਂ) ਫਾਈਲ ਚੁਣੋ ਐਨਕ੍ਰਿਪਟ ਡੀਕ੍ਰਿਪਟ ਕਰੋ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਫਾਈਲ ਚੁਣੋ ਡਿਕ੍ਰਿਪਸ਼ਨ ਐਨਕ੍ਰਿਪਸ਼ਨ ਕੁੰਜੀ ਫ਼ਾਈਲ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਗਈ ਇਸ ਫ਼ਾਈਲ ਨੂੰ ਆਪਣੀ ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕਰੋ ਜਾਂ ਇਸਨੂੰ ਜਿੱਥੇ ਚਾਹੋ ਉੱਥੇ ਰੱਖਣ ਲਈ ਸਾਂਝਾਕਰਨ ਕਾਰਵਾਈ ਦੀ ਵਰਤੋਂ ਕਰੋ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਲਾਗੂ ਕਰਨ ਅਨੁਕੂਲਤਾ ਫਾਈਲਾਂ ਦੀ ਪਾਸਵਰਡ-ਅਧਾਰਿਤ ਏਨਕ੍ਰਿਪਸ਼ਨ। ਅੱਗੇ ਵਧੀਆਂ ਫਾਈਲਾਂ ਨੂੰ ਚੁਣੀ ਗਈ ਡਾਇਰੈਕਟਰੀ ਵਿੱਚ ਸਟੋਰ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਜਾਂ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਡੀਕ੍ਰਿਪਟਡ ਫਾਈਲਾਂ ਨੂੰ ਸਿੱਧੇ ਵੀ ਖੋਲ੍ਹਿਆ ਜਾ ਸਕਦਾ ਹੈ. AES-256, GCM ਮੋਡ, ਕੋਈ ਪੈਡਿੰਗ ਨਹੀਂ, 12 ਬਾਈਟ ਬੇਤਰਤੀਬੇ IVs। ਕੁੰਜੀਆਂ SHA-3 ਹੈਸ਼ਾਂ (256 ਬਿੱਟ) ਵਜੋਂ ਵਰਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਫ਼ਾਈਲ ਦਾ ਆਕਾਰ The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. ਕਿਰਪਾ ਕਰਕੇ ਨੋਟ ਕਰੋ ਕਿ ਹੋਰ ਫਾਈਲ ਐਨਕ੍ਰਿਪਸ਼ਨ ਸੌਫਟਵੇਅਰ ਜਾਂ ਸੇਵਾਵਾਂ ਲਈ ਅਨੁਕੂਲਤਾ ਦੀ ਗਰੰਟੀ ਨਹੀਂ ਹੈ। ਇੱਕ ਥੋੜ੍ਹਾ ਵੱਖਰਾ ਕੁੰਜੀ ਇਲਾਜ ਜਾਂ ਸਿਫਰ ਕੌਂਫਿਗਰੇਸ਼ਨ ਅਸੰਗਤਤਾ ਦਾ ਕਾਰਨ ਬਣ ਸਕਦੀ ਹੈ। ਅਵੈਧ ਪਾਸਵਰਡ ਜਾਂ ਚੁਣੀ ਗਈ ਫਾਈਲ ਐਨਕ੍ਰਿਪਟਡ ਨਹੀਂ ਹੈ ਦਿੱਤੀ ਗਈ ਚੌੜਾਈ ਅਤੇ ਉਚਾਈ ਦੇ ਨਾਲ ਚਿੱਤਰ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਨਾਲ ਇੱਕ OOM ਗਲਤੀ ਹੋ ਸਕਦੀ ਹੈ। ਇਹ ਆਪਣੇ ਜੋਖਮ \'ਤੇ ਕਰੋ, ਅਤੇ ਇਹ ਨਾ ਕਹੋ ਕਿ ਮੈਂ ਤੁਹਾਨੂੰ ਚੇਤਾਵਨੀ ਨਹੀਂ ਦਿੱਤੀ! ਕੈਸ਼ ਕੈਸ਼ ਆਕਾਰ %1$s ਮਿਲਿਆ ਆਟੋ ਕੈਸ਼ ਕਲੀਅਰਿੰਗ ਜੇਕਰ ਸਮਰਥਿਤ ਐਪ ਕੈਸ਼ ਐਪ ਸਟਾਰਟਅਪ \'ਤੇ ਕਲੀਅਰ ਕੀਤਾ ਜਾਵੇਗਾ ਬਣਾਓ ਸੰਦ ਕਿਸਮ ਦੇ ਅਨੁਸਾਰ ਸਮੂਹ ਵਿਕਲਪ ਇੱਕ ਕਸਟਮ ਸੂਚੀ ਪ੍ਰਬੰਧ ਦੀ ਬਜਾਏ ਉਹਨਾਂ ਦੀ ਕਿਸਮ ਦੁਆਰਾ ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਸਮੂਹ ਵਿਕਲਪ ਵਿਕਲਪ ਗਰੁੱਪਿੰਗ ਯੋਗ ਹੋਣ \'ਤੇ ਵਿਵਸਥਾ ਨੂੰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ ਸਕ੍ਰੀਨਸ਼ੌਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ ਸੈਕੰਡਰੀ ਅਨੁਕੂਲਤਾ ਸਕਰੀਨਸ਼ਾਟ ਫਾਲਬੈਕ ਵਿਕਲਪ ਛੱਡੋ ਕਾਪੀ ਕਰੋ %1$s ਮੋਡ ਵਿੱਚ ਸੰਭਾਲਣਾ ਅਸਥਿਰ ਹੋ ਸਕਦਾ ਹੈ, ਕਿਉਂਕਿ ਇਹ ਇੱਕ ਨੁਕਸਾਨ ਰਹਿਤ ਫਾਰਮੈਟ ਹੈ ਜੇਕਰ ਤੁਸੀਂ ਪ੍ਰੀਸੈਟ 125 ਦੀ ਚੋਣ ਕੀਤੀ ਹੈ, ਤਾਂ ਚਿੱਤਰ ਨੂੰ 100% ਗੁਣਵੱਤਾ ਦੇ ਨਾਲ ਅਸਲ ਚਿੱਤਰ ਦੇ 125% ਆਕਾਰ ਵਜੋਂ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਜਾਵੇਗਾ। ਜੇਕਰ ਤੁਸੀਂ ਪ੍ਰੀਸੈਟ 50 ਦੀ ਚੋਣ ਕਰਦੇ ਹੋ, ਤਾਂ ਚਿੱਤਰ ਨੂੰ 50% ਆਕਾਰ ਅਤੇ 50% ਗੁਣਵੱਤਾ ਨਾਲ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਜਾਵੇਗਾ। ਇੱਥੇ ਪ੍ਰੀਸੈਟ ਆਉਟਪੁੱਟ ਫਾਈਲ ਦਾ % ਨਿਰਧਾਰਤ ਕਰਦਾ ਹੈ, ਭਾਵ ਜੇਕਰ ਤੁਸੀਂ 5mb ਚਿੱਤਰ \'ਤੇ ਪ੍ਰੀਸੈਟ 50 ਦੀ ਚੋਣ ਕਰਦੇ ਹੋ ਤਾਂ ਤੁਹਾਨੂੰ ਸੇਵ ਕਰਨ ਤੋਂ ਬਾਅਦ 2.5mb ਚਿੱਤਰ ਮਿਲੇਗਾ। ਫਾਈਲ ਨਾਮ ਨੂੰ ਰੈਂਡਮਾਈਜ਼ ਕਰੋ ਜੇਕਰ ਸਮਰਥਿਤ ਆਉਟਪੁੱਟ ਫਾਈਲ ਨਾਮ ਪੂਰੀ ਤਰ੍ਹਾਂ ਬੇਤਰਤੀਬ ਹੋ ਜਾਵੇਗਾ %2$s ਨਾਮ ਦੇ ਨਾਲ %1$s ਫੋਲਡਰ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ %1$s ਫੋਲਡਰ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ ਟੈਲੀਗ੍ਰਾਮ ਚੈਟ ਐਪ \'ਤੇ ਚਰਚਾ ਕਰੋ ਅਤੇ ਦੂਜੇ ਉਪਭੋਗਤਾਵਾਂ ਤੋਂ ਫੀਡਬੈਕ ਪ੍ਰਾਪਤ ਕਰੋ। ਤੁਸੀਂ ਇੱਥੇ ਬੀਟਾ ਅੱਪਡੇਟ ਅਤੇ ਇਨਸਾਈਟਸ ਵੀ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਫਸਲ ਮਾਸਕ ਆਕਾਰ ਅਨੁਪਾਤ ਦਿੱਤੇ ਚਿੱਤਰ ਤੋਂ ਮਾਸਕ ਬਣਾਉਣ ਲਈ ਇਸ ਮਾਸਕ ਕਿਸਮ ਦੀ ਵਰਤੋਂ ਕਰੋ, ਧਿਆਨ ਦਿਓ ਕਿ ਇਸ ਵਿੱਚ ਅਲਫ਼ਾ ਚੈਨਲ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਬੈਕਅੱਪ ਅਤੇ ਰੀਸਟੋਰ ਬੈਕਅੱਪ ਰੀਸਟੋਰ ਕਰੋ ਇੱਕ ਫਾਈਲ ਵਿੱਚ ਆਪਣੀਆਂ ਐਪ ਸੈਟਿੰਗਾਂ ਦਾ ਬੈਕਅੱਪ ਲਓ ਪਹਿਲਾਂ ਤਿਆਰ ਕੀਤੀ ਫਾਈਲ ਤੋਂ ਐਪ ਸੈਟਿੰਗਾਂ ਨੂੰ ਰੀਸਟੋਰ ਕਰੋ ਖਰਾਬ ਫਾਈਲ ਜਾਂ ਬੈਕਅੱਪ ਨਹੀਂ ਸੈਟਿੰਗਾਂ ਸਫਲਤਾਪੂਰਵਕ ਰੀਸਟੋਰ ਕੀਤੀਆਂ ਗਈਆਂ ਮੇਰੇ ਨਾਲ ਸੰਪਰਕ ਕਰੋ ਇਹ ਤੁਹਾਡੀਆਂ ਸੈਟਿੰਗਾਂ ਨੂੰ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਮੁੱਲਾਂ \'ਤੇ ਵਾਪਸ ਭੇਜ ਦੇਵੇਗਾ। ਧਿਆਨ ਦਿਓ ਕਿ ਉੱਪਰ ਦੱਸੇ ਬੈਕਅੱਪ ਫਾਈਲ ਤੋਂ ਬਿਨਾਂ ਇਸਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਮਿਟਾਓ ਤੁਸੀਂ ਚੁਣੀ ਗਈ ਰੰਗ ਸਕੀਮ ਨੂੰ ਮਿਟਾਉਣ ਲੱਗੇ ਹੋ। ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਸਕੀਮ ਮਿਟਾਓ ਫੌਂਟ ਟੈਕਸਟ ਫੌਂਟ ਸਕੇਲ ਡਿਫਾਲਟ ਵੱਡੇ ਫੌਂਟ ਸਕੇਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਨਾਲ UI ਗੜਬੜੀਆਂ ਅਤੇ ਸਮੱਸਿਆਵਾਂ ਹੋ ਸਕਦੀਆਂ ਹਨ, ਜੋ ਠੀਕ ਨਹੀਂ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ। ਸਾਵਧਾਨੀ ਨਾਲ ਵਰਤੋ. ਅ ਆ ਇ ਈ ਉ ਊ ਏ ਐ ਓ ਔ ਕ ਖ ਗ ਘ ਙ ਚ ਛ ਜ ਝ ਞ ਟ ਠ ਡ ਢ ਣ ਤ ਥ ਦ ਧ ਨ ਪ ਫ ਬ ਭ ਮ ਯ ਰ ਲ ਵ ਸ ਹ 0123456789 !? ਜਜ਼ਬਾਤ ਭੋਜਨ ਅਤੇ ਪੀ ਕੁਦਰਤ ਅਤੇ ਜਾਨਵਰ ਵਸਤੂਆਂ ਚਿੰਨ੍ਹ ਇਮੋਜੀ ਚਾਲੂ ਕਰੋ ਯਾਤਰਾਵਾਂ ਅਤੇ ਸਥਾਨ ਗਤੀਵਿਧੀਆਂ ਬੈਕਗ੍ਰਾਊਂਡ ਰਿਮੂਵਰ ਡਰਾਇੰਗ ਦੁਆਰਾ ਚਿੱਤਰ ਤੋਂ ਪਿਛੋਕੜ ਹਟਾਓ ਜਾਂ ਆਟੋ ਵਿਕਲਪ ਦੀ ਵਰਤੋਂ ਕਰੋ ਚਿੱਤਰ ਨੂੰ ਕੱਟੋ ਅਸਲ ਚਿੱਤਰ ਮੈਟਾਡੇਟਾ ਰੱਖਿਆ ਜਾਵੇਗਾ ਚਿੱਤਰ ਦੇ ਆਲੇ-ਦੁਆਲੇ ਪਾਰਦਰਸ਼ੀ ਖਾਲੀ ਥਾਂਵਾਂ ਨੂੰ ਕੱਟਿਆ ਜਾਵੇਗਾ ਬੈਕਗ੍ਰਾਊਂਡ ਨੂੰ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਮਿਟਾਓ ਚਿੱਤਰ ਰੀਸਟੋਰ ਕਰੋ ਮਿਟਾਓ ਮੋਡ ਪਿਛੋਕੜ ਮਿਟਾਓ ਬੈਕਗ੍ਰਾਊਂਡ ਰੀਸਟੋਰ ਕਰੋ ਧੁੰਦਲਾ ਘੇਰਾ ਪਾਈਪੇਟ ਡਰਾਅ ਮੋਡ ਮੁੱਦਾ ਬਣਾਓ ਓਹੋ… ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ। ਤੁਸੀਂ ਹੇਠਾਂ ਦਿੱਤੇ ਵਿਕਲਪਾਂ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਮੈਨੂੰ ਲਿਖ ਸਕਦੇ ਹੋ ਅਤੇ ਮੈਂ ਹੱਲ ਲੱਭਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰਾਂਗਾ ਮੁੜ ਆਕਾਰ ਦਿਓ ਅਤੇ ਕਨਵਰਟ ਕਰੋ ਦਿੱਤੇ ਚਿੱਤਰਾਂ ਦਾ ਆਕਾਰ ਬਦਲੋ ਜਾਂ ਉਹਨਾਂ ਨੂੰ ਹੋਰ ਫਾਰਮੈਟਾਂ ਵਿੱਚ ਬਦਲੋ। EXIF ਮੈਟਾਡੇਟਾ ਨੂੰ ਵੀ ਇੱਥੇ ਸੰਪਾਦਿਤ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਜੇਕਰ ਇੱਕ ਸਿੰਗਲ ਚਿੱਤਰ ਨੂੰ ਚੁਣਿਆ ਜਾ ਰਿਹਾ ਹੈ. ਅਧਿਕਤਮ ਰੰਗ ਦੀ ਗਿਣਤੀ ਇਹ ਐਪ ਨੂੰ ਕਰੈਸ਼ ਰਿਪੋਰਟਾਂ ਨੂੰ ਹੱਥੀਂ ਇਕੱਠਾ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦਾ ਹੈ ਵਿਸ਼ਲੇਸ਼ਣ ਅਗਿਆਤ ਐਪ ਵਰਤੋਂ ਦੇ ਅੰਕੜੇ ਇਕੱਠੇ ਕਰਨ ਦਿਓ ਵਰਤਮਾਨ ਵਿੱਚ, %1$s ਫਾਰਮੈਟ ਸਿਰਫ ਐਂਡਰਾਇਡ \'ਤੇ EXIF ਮੈਟਾਡੇਟਾ ਨੂੰ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਜਾਣ \'ਤੇ ਆਉਟਪੁੱਟ ਚਿੱਤਰ ਵਿੱਚ ਮੇਟਾਡੇਟਾ ਬਿਲਕੁਲ ਨਹੀਂ ਹੋਵੇਗਾ। ਜਤਨ %1$s ਦੇ ਮੁੱਲ ਦਾ ਅਰਥ ਹੈ ਇੱਕ ਤੇਜ਼ ਕੰਪਰੈਸ਼ਨ, ਜਿਸਦੇ ਨਤੀਜੇ ਵਜੋਂ ਇੱਕ ਮੁਕਾਬਲਤਨ ਵੱਡਾ ਫਾਈਲ ਆਕਾਰ ਹੁੰਦਾ ਹੈ। %2$s ਦਾ ਮਤਲਬ ਹੈ ਇੱਕ ਹੌਲੀ ਕੰਪਰੈਸ਼ਨ, ਨਤੀਜੇ ਵਜੋਂ ਇੱਕ ਛੋਟੀ ਫਾਈਲ। ਉਡੀਕ ਕਰੋ ਸੰਭਾਲਣਾ ਲਗਭਗ ਪੂਰਾ ਹੋਇਆ। ਹੁਣੇ ਰੱਦ ਕਰਨ ਲਈ ਦੁਬਾਰਾ ਬੱਚਤ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇਗੀ। ਅੱਪਡੇਟ ਬੀਟਾ ਦੀ ਆਗਿਆ ਦਿਓ ਅੱਪਡੇਟ ਜਾਂਚ ਵਿੱਚ ਬੀਟਾ ਐਪ ਵਰਜਨ ਸ਼ਾਮਲ ਹੋਣਗੇ ਜੇਕਰ ਸਮਰਥਿਤ ਹੈ ਤੀਰ ਖਿੱਚੋ ਜੇਕਰ ਸਮਰਥਿਤ ਡਰਾਇੰਗ ਮਾਰਗ ਨੂੰ ਪੁਆਇੰਟਿੰਗ ਐਰੋ ਵਜੋਂ ਦਰਸਾਇਆ ਜਾਵੇਗਾ ਬੁਰਸ਼ ਨਰਮਤਾ ਤਸਵੀਰਾਂ ਨੂੰ ਦਾਖਲ ਕੀਤੇ ਆਕਾਰ ਦੇ ਵਿਚਕਾਰ ਕੱਟਿਆ ਜਾਵੇਗਾ। ਜੇਕਰ ਚਿੱਤਰ ਦਾਖਲ ਕੀਤੇ ਮਾਪਾਂ ਤੋਂ ਛੋਟਾ ਹੈ ਤਾਂ ਕੈਨਵਸ ਨੂੰ ਦਿੱਤੇ ਬੈਕਗ੍ਰਾਊਂਡ ਰੰਗ ਨਾਲ ਵਿਸਤਾਰ ਕੀਤਾ ਜਾਵੇਗਾ। ਦਾਨ ਚਿੱਤਰ ਸਿਲਾਈ ਇੱਕ ਵੱਡਾ ਚਿੱਤਰ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਦਿੱਤੇ ਚਿੱਤਰਾਂ ਨੂੰ ਜੋੜੋ ਘੱਟੋ-ਘੱਟ 2 ਚਿੱਤਰ ਚੁਣੋ ਆਉਟਪੁੱਟ ਚਿੱਤਰ ਸਕੇਲ ਚਿੱਤਰ ਸਥਿਤੀ ਹਰੀਜੱਟਲ ਵਰਟੀਕਲ ਛੋਟੇ ਚਿੱਤਰਾਂ ਨੂੰ ਵੱਡੇ ਤੱਕ ਸਕੇਲ ਕਰੋ ਛੋਟੇ ਚਿੱਤਰਾਂ ਨੂੰ ਕ੍ਰਮ ਵਿੱਚ ਸਭ ਤੋਂ ਵੱਡੇ ਚਿੱਤਰਾਂ ਤੱਕ ਸਕੇਲ ਕੀਤਾ ਜਾਵੇਗਾ ਜੇਕਰ ਸਮਰੱਥ ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ ਚਿੱਤਰ ਆਰਡਰ ਰੋਜਾਨਾ ਕਿਨਾਰਿਆਂ ਨੂੰ ਧੁੰਦਲਾ ਕਰੋ ਜੇਕਰ ਸਮਰੱਥ ਹੋਵੇ ਤਾਂ ਇੱਕਲੇ ਰੰਗ ਦੀ ਬਜਾਏ ਇਸਦੇ ਆਲੇ ਦੁਆਲੇ ਖਾਲੀ ਥਾਂਵਾਂ ਨੂੰ ਭਰਨ ਲਈ ਅਸਲ ਚਿੱਤਰ ਦੇ ਹੇਠਾਂ ਧੁੰਦਲੇ ਕਿਨਾਰਿਆਂ ਨੂੰ ਖਿੱਚਦਾ ਹੈ ਪਿਕਸਲੇਸ਼ਨ ਵਿਸਤ੍ਰਿਤ ਪਿਕਸਲੇਸ਼ਨ ਸਟ੍ਰੋਕ ਪਿਕਸਲੇਸ਼ਨ ਵਿਸਤ੍ਰਿਤ ਡਾਇਮੰਡ ਪਿਕਸਲੇਸ਼ਨ ਡਾਇਮੰਡ ਪਿਕਸਲੇਸ਼ਨ ਸਰਕਲ ਪਿਕਸਲੇਸ਼ਨ ਵਿਸਤ੍ਰਿਤ ਸਰਕਲ ਪਿਕਸਲੇਸ਼ਨ ਰੰਗ ਬਦਲੋ ਸਹਿਣਸ਼ੀਲਤਾ ਬਦਲਣ ਲਈ ਰੰਗ ਨਿਸ਼ਾਨਾ ਰੰਗ ਹਟਾਉਣ ਲਈ ਰੰਗ ਰੰਗ ਹਟਾਓ ਰੀਕੋਡ ਕਰੋ ਪਿਕਸਲ ਆਕਾਰ ਲੌਕ ਡਰਾਅ ਸਥਿਤੀ ਜੇਕਰ ਡਰਾਇੰਗ ਮੋਡ ਵਿੱਚ ਸਮਰਥਿਤ ਹੈ, ਤਾਂ ਸਕ੍ਰੀਨ ਘੁੰਮੇਗੀ ਨਹੀਂ ਅੱਪਡੇਟ ਲਈ ਚੈੱਕ ਕਰੋ ਪੈਲੇਟ ਸ਼ੈਲੀ ਟੋਨਲ ਸਪਾਟ ਨਿਰਪੱਖ ਉਤੇਜਿਤ ਭਾਵਪੂਰਤ ਸਤਰੰਗੀ ਪੀ ਫਲ ਸਲਾਦ ਵਫ਼ਾਦਾਰੀ ਸਮੱਗਰੀ ਡਿਫੌਲਟ ਪੈਲੇਟ ਸਟਾਈਲ, ਇਹ ਸਾਰੇ ਚਾਰ ਰੰਗਾਂ ਨੂੰ ਅਨੁਕੂਲਿਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦਾ ਹੈ, ਹੋਰ ਤੁਹਾਨੂੰ ਸਿਰਫ਼ ਮੁੱਖ ਰੰਗ ਸੈੱਟ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦਾ ਹੈ ਇੱਕ ਸ਼ੈਲੀ ਜੋ ਮੋਨੋਕ੍ਰੋਮ ਨਾਲੋਂ ਥੋੜ੍ਹੀ ਜ਼ਿਆਦਾ ਰੰਗੀਨ ਹੈ ਇੱਕ ਉੱਚੀ ਥੀਮ, ਰੰਗੀਨਤਾ ਪ੍ਰਾਇਮਰੀ ਪੈਲੇਟ ਲਈ ਵੱਧ ਤੋਂ ਵੱਧ ਹੈ, ਦੂਜਿਆਂ ਲਈ ਵਧੀ ਹੋਈ ਹੈ ਇੱਕ ਚੰਚਲ ਥੀਮ - ਸਰੋਤ ਰੰਗ ਦਾ ਰੰਗ ਥੀਮ ਵਿੱਚ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦਾ ਹੈ ਇੱਕ ਮੋਨੋਕ੍ਰੋਮ ਥੀਮ, ਰੰਗ ਬਿਲਕੁਲ ਕਾਲੇ / ਚਿੱਟੇ / ਸਲੇਟੀ ਹਨ ਇੱਕ ਸਕੀਮ ਜੋ Scheme.primaryContainer ਵਿੱਚ ਸਰੋਤ ਰੰਗ ਰੱਖਦੀ ਹੈ ਇੱਕ ਸਕੀਮ ਜੋ ਸਮੱਗਰੀ ਸਕੀਮ ਨਾਲ ਬਹੁਤ ਮਿਲਦੀ ਜੁਲਦੀ ਹੈ ਇਹ ਅੱਪਡੇਟ ਚੈਕਰ GitHub ਨਾਲ ਕਨੈਕਟ ਕਰੇਗਾ ਜਾਂਚ ਦੇ ਕਾਰਨ ਕਿ ਕੀ ਕੋਈ ਨਵਾਂ ਅੱਪਡੇਟ ਉਪਲਬਧ ਹੈ ਧਿਆਨ ਫੇਡਿੰਗ ਕਿਨਾਰੇ ਅਯੋਗ ਦੋਵੇਂ ਉਲਟਾ ਰੰਗ ਜੇਕਰ ਸਮਰੱਥ ਹੋਵੇ ਤਾਂ ਥੀਮ ਦੇ ਰੰਗਾਂ ਨੂੰ ਨਕਾਰਾਤਮਕ ਰੰਗਾਂ ਨਾਲ ਬਦਲਦਾ ਹੈ ਖੋਜ ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਉਪਲਬਧ ਸਾਰੇ ਵਿਕਲਪਾਂ ਰਾਹੀਂ ਖੋਜ ਕਰਨ ਦੀ ਸਮਰੱਥਾ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ PDF ਟੂਲ PDF ਫਾਈਲਾਂ ਨਾਲ ਸੰਚਾਲਿਤ ਕਰੋ: ਪੂਰਵਦਰਸ਼ਨ ਕਰੋ, ਚਿੱਤਰਾਂ ਦੇ ਬੈਚ ਵਿੱਚ ਬਦਲੋ ਜਾਂ ਦਿੱਤੀਆਂ ਤਸਵੀਰਾਂ ਵਿੱਚੋਂ ਇੱਕ ਬਣਾਓ PDF ਦੀ ਝਲਕ ਚਿੱਤਰਾਂ ਲਈ PDF PDF ਲਈ ਚਿੱਤਰ ਸਧਾਰਨ PDF ਝਲਕ ਦਿੱਤੇ ਆਉਟਪੁੱਟ ਫਾਰਮੈਟ ਵਿੱਚ PDF ਨੂੰ ਚਿੱਤਰਾਂ ਵਿੱਚ ਬਦਲੋ ਦਿੱਤੇ ਚਿੱਤਰਾਂ ਨੂੰ ਆਉਟਪੁੱਟ PDF ਫਾਈਲ ਵਿੱਚ ਪੈਕ ਕਰੋ ਮਾਸਕ ਫਿਲਟਰ ਦਿੱਤੇ ਮਾਸਕ ਵਾਲੇ ਖੇਤਰਾਂ \'ਤੇ ਫਿਲਟਰ ਚੇਨ ਲਗਾਓ, ਹਰੇਕ ਮਾਸਕ ਖੇਤਰ ਇਸ ਦੇ ਆਪਣੇ ਫਿਲਟਰਾਂ ਦੇ ਸੈੱਟ ਨੂੰ ਨਿਰਧਾਰਤ ਕਰ ਸਕਦਾ ਹੈ ਮਾਸਕ ਮਾਸਕ ਸ਼ਾਮਲ ਕਰੋ ਮਾਸਕ %d ਮਾਸਕ ਰੰਗ ਮਾਸਕ ਪੂਰਵਦਰਸ਼ਨ ਤੁਹਾਨੂੰ ਅੰਦਾਜ਼ਨ ਨਤੀਜਾ ਦਿਖਾਉਣ ਲਈ ਖਿੱਚਿਆ ਫਿਲਟਰ ਮਾਸਕ ਰੈਂਡਰ ਕੀਤਾ ਜਾਵੇਗਾ ਉਲਟ ਭਰਨ ਦੀ ਕਿਸਮ ਜੇਕਰ ਯੋਗ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਤਾਂ ਸਾਰੇ ਗੈਰ-ਮਾਸਕ ਕੀਤੇ ਖੇਤਰਾਂ ਨੂੰ ਡਿਫੌਲਟ ਵਿਵਹਾਰ ਦੀ ਬਜਾਏ ਫਿਲਟਰ ਕੀਤਾ ਜਾਵੇਗਾ ਤੁਸੀਂ ਚੁਣੇ ਹੋਏ ਫਿਲਟਰ ਮਾਸਕ ਨੂੰ ਮਿਟਾਉਣ ਲੱਗੇ ਹੋ। ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਮਾਸਕ ਮਿਟਾਓ ਪੂਰਾ ਫਿਲਟਰ ਦਿੱਤੇ ਚਿੱਤਰਾਂ ਜਾਂ ਸਿੰਗਲ ਚਿੱਤਰ \'ਤੇ ਕੋਈ ਵੀ ਫਿਲਟਰ ਚੇਨ ਲਾਗੂ ਕਰੋ ਸ਼ੁਰੂ ਕਰੋ ਕੇਂਦਰ ਅੰਤ ਸਧਾਰਨ ਰੂਪ ਹਾਈਲਾਈਟਰ ਨਿਓਨ ਕਲਮ ਪਰਦੇਦਾਰੀ ਬਲਰ ਅਰਧ-ਪਾਰਦਰਸ਼ੀ ਤਿੱਖੇ ਹਾਈਲਾਈਟਰ ਮਾਰਗ ਬਣਾਓ ਆਪਣੀਆਂ ਡਰਾਇੰਗਾਂ ਵਿੱਚ ਕੁਝ ਚਮਕਦਾਰ ਪ੍ਰਭਾਵ ਸ਼ਾਮਲ ਕਰੋ ਡਿਫੌਲਟ ਇੱਕ, ਸਰਲ - ਬਸ ਰੰਗ ਜੋ ਵੀ ਤੁਸੀਂ ਲੁਕਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ ਉਸ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਖਿੱਚੇ ਗਏ ਮਾਰਗ ਦੇ ਹੇਠਾਂ ਚਿੱਤਰ ਨੂੰ ਬਲਰ ਕਰਦਾ ਹੈ ਗੋਪਨੀਯਤਾ ਬਲਰ ਦੇ ਸਮਾਨ, ਪਰ ਧੁੰਦਲਾ ਕਰਨ ਦੀ ਬਜਾਏ ਪਿਕਸਲੇਟ ਕੰਟੇਨਰ ਕੰਟੇਨਰਾਂ ਦੇ ਪਿੱਛੇ ਸ਼ੈਡੋ ਡਰਾਇੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਸਲਾਈਡਰ ਸਵਿੱਚ FABs ਬਟਨ ਸਲਾਈਡਰਾਂ ਦੇ ਪਿੱਛੇ ਸ਼ੈਡੋ ਡਰਾਇੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਸਵਿੱਚਾਂ ਦੇ ਪਿੱਛੇ ਸ਼ੈਡੋ ਡਰਾਇੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਫਲੋਟਿੰਗ ਐਕਸ਼ਨ ਬਟਨਾਂ ਦੇ ਪਿੱਛੇ ਸ਼ੈਡੋ ਡਰਾਇੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਡਿਫੌਲਟ ਬਟਨਾਂ ਦੇ ਪਿੱਛੇ ਸ਼ੈਡੋ ਡਰਾਇੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਐਪ ਬਾਰ ਐਪ ਬਾਰਾਂ ਦੇ ਪਿੱਛੇ ਸ਼ੈਡੋ ਡਰਾਇੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਰੇਂਜ %1$s - %2$s ਵਿੱਚ ਮੁੱਲ ਆਟੋ ਘੁੰਮਾਓ ਚਿੱਤਰ ਸਥਿਤੀ ਲਈ ਸੀਮਾ ਬਾਕਸ ਨੂੰ ਅਪਣਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ ਪਾਥ ਮੋਡ ਬਣਾਓ ਡਬਲ ਲਾਈਨ ਐਰੋ ਮੁਫਤ ਡਰਾਇੰਗ ਡਬਲ ਤੀਰ ਰੇਖਾ ਤੀਰ ਤੀਰ ਲਾਈਨ ਪਾਥ ਨੂੰ ਇਨਪੁਟ ਮੁੱਲ ਦੇ ਤੌਰ \'ਤੇ ਖਿੱਚਦਾ ਹੈ ਇੱਕ ਲਾਈਨ ਦੇ ਰੂਪ ਵਿੱਚ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਮਾਰਗ ਖਿੱਚਦਾ ਹੈ ਇੱਕ ਲਾਈਨ ਦੇ ਰੂਪ ਵਿੱਚ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਪੁਆਇੰਟਿੰਗ ਤੀਰ ਖਿੱਚਦਾ ਹੈ ਦਿੱਤੇ ਮਾਰਗ ਤੋਂ ਪੁਆਇੰਟਿੰਗ ਤੀਰ ਖਿੱਚਦਾ ਹੈ ਇੱਕ ਲਾਈਨ ਦੇ ਰੂਪ ਵਿੱਚ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਡਬਲ ਪੁਆਇੰਟਿੰਗ ਤੀਰ ਖਿੱਚਦਾ ਹੈ ਦਿੱਤੇ ਮਾਰਗ ਤੋਂ ਡਬਲ ਪੁਆਇੰਟਿੰਗ ਤੀਰ ਖਿੱਚਦਾ ਹੈ ਰੂਪਰੇਖਾ ਓਵਲ ਰੂਪਰੇਖਾ Rect ਓਵਲ Rect ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਰੇਕਟ ਖਿੱਚਦਾ ਹੈ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਅੰਡਾਕਾਰ ਖਿੱਚਦਾ ਹੈ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਰੂਪਰੇਖਾ ਅੰਡਾਕਾਰ ਖਿੱਚਦਾ ਹੈ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਬਾਹਰੀ ਰੇਕਟ ਖਿੱਚਦਾ ਹੈ ਲੱਸੋ ਦਿੱਤੇ ਮਾਰਗ ਦੁਆਰਾ ਬੰਦ ਭਰੇ ਮਾਰਗ ਨੂੰ ਖਿੱਚਦਾ ਹੈ ਮੁਫ਼ਤ ਹਰੀਜ਼ੱਟਲ ਗਰਿੱਡ ਵਰਟੀਕਲ ਗਰਿੱਡ ਸਟੀਚ ਮੋਡ ਕਤਾਰਾਂ ਦੀ ਗਿਣਤੀ ਕਾਲਮਾਂ ਦੀ ਗਿਣਤੀ ਕੋਈ \"%1$s\" ਡਾਇਰੈਕਟਰੀ ਨਹੀਂ ਮਿਲੀ, ਅਸੀਂ ਇਸਨੂੰ ਡਿਫੌਲਟ ਇੱਕ ਵਿੱਚ ਬਦਲ ਦਿੱਤਾ ਹੈ, ਕਿਰਪਾ ਕਰਕੇ ਫਾਈਲ ਨੂੰ ਦੁਬਾਰਾ ਸੁਰੱਖਿਅਤ ਕਰੋ ਕਲਿੱਪਬੋਰਡ ਆਟੋ ਪਿੰਨ ਜੇਕਰ ਸਮਰਥਿਤ ਹੋਵੇ ਤਾਂ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਕਲਿੱਪਬੋਰਡ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਚਿੱਤਰ ਸ਼ਾਮਲ ਕਰਦਾ ਹੈ ਵਾਈਬ੍ਰੇਸ਼ਨ ਵਾਈਬ੍ਰੇਸ਼ਨ ਤਾਕਤ ਫਾਈਲਾਂ ਨੂੰ ਓਵਰਰਾਈਟ ਕਰਨ ਲਈ ਤੁਹਾਨੂੰ \"ਐਕਸਪਲੋਰਰ\" ਚਿੱਤਰ ਸਰੋਤ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ, ਚਿੱਤਰਾਂ ਨੂੰ ਰੀਪਿਕ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ, ਅਸੀਂ ਚਿੱਤਰ ਸਰੋਤ ਨੂੰ ਲੋੜੀਂਦੇ ਵਿੱਚ ਬਦਲ ਦਿੱਤਾ ਹੈ ਫਾਈਲਾਂ ਨੂੰ ਓਵਰਰਾਈਟ ਕਰੋ ਮੂਲ ਫਾਈਲ ਨੂੰ ਚੁਣੇ ਹੋਏ ਫੋਲਡਰ ਵਿੱਚ ਸੇਵ ਕਰਨ ਦੀ ਬਜਾਏ ਨਵੀਂ ਨਾਲ ਬਦਲ ਦਿੱਤਾ ਜਾਵੇਗਾ, ਇਸ ਵਿਕਲਪ ਨੂੰ ਚਿੱਤਰ ਸਰੋਤ \"ਐਕਸਪਲੋਰਰ\" ਜਾਂ GetContent ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ, ਜਦੋਂ ਇਸਨੂੰ ਟੌਗਲ ਕਰਦੇ ਹੋ, ਤਾਂ ਇਹ ਆਪਣੇ ਆਪ ਸੈੱਟ ਹੋ ਜਾਵੇਗਾ ਖਾਲੀ ਪਿਛੇਤਰ ਸਕੇਲ ਮੋਡ ਦੋਲੀਨੀਅਰ ਕੈਟਮੁਲ ਬਾਈਕੂਬਿਕ ਹੈਨ ਹਰਮਾਈਟ ਲੈਂਕਜ਼ੋਸ ਮਿਸ਼ੇਲ ਨਜ਼ਦੀਕੀ ਸਪਲਾਈਨ ਮੂਲ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਮੁੱਲ ਰੇਖਿਕ (ਜਾਂ ਦੋ-ਲੀਨੀਅਰ, ਦੋ ਅਯਾਮਾਂ ਵਿੱਚ) ਇੰਟਰਪੋਲੇਸ਼ਨ ਇੱਕ ਚਿੱਤਰ ਦੇ ਆਕਾਰ ਨੂੰ ਬਦਲਣ ਲਈ ਆਮ ਤੌਰ \'ਤੇ ਵਧੀਆ ਹੁੰਦਾ ਹੈ, ਪਰ ਵੇਰਵਿਆਂ ਦੇ ਕੁਝ ਅਣਚਾਹੇ ਨਰਮ ਹੋਣ ਦਾ ਕਾਰਨ ਬਣਦਾ ਹੈ ਅਤੇ ਅਜੇ ਵੀ ਕੁਝ ਹੱਦ ਤੱਕ ਜਾਗਡ ਹੋ ਸਕਦਾ ਹੈ। ਬਿਹਤਰ ਸਕੇਲਿੰਗ ਵਿਧੀਆਂ ਵਿੱਚ ਲੈਂਕਜ਼ੋਸ ਰੀਸੈਪਲਿੰਗ ਅਤੇ ਮਿਸ਼ੇਲ-ਨੇਤਰਾਵਲੀ ਫਿਲਟਰ ਸ਼ਾਮਲ ਹਨ। ਆਕਾਰ ਵਧਾਉਣ ਦਾ ਇੱਕ ਸਰਲ ਤਰੀਕਾ, ਹਰੇਕ ਪਿਕਸਲ ਨੂੰ ਇੱਕੋ ਰੰਗ ਦੇ ਕਈ ਪਿਕਸਲਾਂ ਨਾਲ ਬਦਲਣਾ ਸਭ ਤੋਂ ਸਰਲ ਐਂਡਰਾਇਡ ਸਕੇਲਿੰਗ ਮੋਡ ਜੋ ਲਗਭਗ ਸਾਰੀਆਂ ਐਪਾਂ ਵਿੱਚ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਨਿਯੰਤਰਣ ਬਿੰਦੂਆਂ ਦੇ ਇੱਕ ਸਮੂਹ ਨੂੰ ਸੁਚਾਰੂ ਰੂਪ ਵਿੱਚ ਇੰਟਰਪੋਲੇਟ ਕਰਨ ਅਤੇ ਦੁਬਾਰਾ ਨਮੂਨੇ ਬਣਾਉਣ ਲਈ ਵਿਧੀ, ਆਮ ਤੌਰ \'ਤੇ ਨਿਰਵਿਘਨ ਕਰਵ ਬਣਾਉਣ ਲਈ ਕੰਪਿਊਟਰ ਗ੍ਰਾਫਿਕਸ ਵਿੱਚ ਵਰਤੀ ਜਾਂਦੀ ਹੈ। ਵਿੰਡੋਿੰਗ ਫੰਕਸ਼ਨ ਅਕਸਰ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਨੂੰ ਘੱਟ ਕਰਨ ਅਤੇ ਸਿਗਨਲ ਦੇ ਕਿਨਾਰਿਆਂ ਨੂੰ ਟੇਪਰ ਕਰਕੇ ਬਾਰੰਬਾਰਤਾ ਵਿਸ਼ਲੇਸ਼ਣ ਦੀ ਸ਼ੁੱਧਤਾ ਨੂੰ ਬਿਹਤਰ ਬਣਾਉਣ ਲਈ ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਵਿੱਚ ਲਾਗੂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਗਣਿਤਿਕ ਇੰਟਰਪੋਲੇਸ਼ਨ ਤਕਨੀਕ ਜੋ ਇੱਕ ਨਿਰਵਿਘਨ ਅਤੇ ਨਿਰੰਤਰ ਕਰਵ ਬਣਾਉਣ ਲਈ ਇੱਕ ਕਰਵ ਹਿੱਸੇ ਦੇ ਅੰਤਮ ਬਿੰਦੂਆਂ \'ਤੇ ਮੁੱਲਾਂ ਅਤੇ ਡੈਰੀਵੇਟਿਵਜ਼ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਪਿਕਸਲ ਮੁੱਲਾਂ \'ਤੇ ਭਾਰ ਵਾਲੇ sinc ਫੰਕਸ਼ਨ ਨੂੰ ਲਾਗੂ ਕਰਕੇ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਨੂੰ ਕਾਇਮ ਰੱਖਦੀ ਹੈ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਸਕੇਲ ਕੀਤੇ ਚਿੱਤਰ ਵਿੱਚ ਤਿੱਖਾਪਨ ਅਤੇ ਐਂਟੀ-ਅਲਾਈਜ਼ਿੰਗ ਵਿਚਕਾਰ ਸੰਤੁਲਨ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਵਿਵਸਥਿਤ ਪੈਰਾਮੀਟਰਾਂ ਦੇ ਨਾਲ ਇੱਕ ਕਨਵੋਲਿਊਸ਼ਨ ਫਿਲਟਰ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ ਇੱਕ ਵਕਰ ਜਾਂ ਸਤਹ, ਲਚਕਦਾਰ ਅਤੇ ਨਿਰੰਤਰ ਸ਼ਕਲ ਦੀ ਨੁਮਾਇੰਦਗੀ ਨੂੰ ਸੁਚਾਰੂ ਰੂਪ ਵਿੱਚ ਇੰਟਰਪੋਲੇਟ ਕਰਨ ਅਤੇ ਅਨੁਮਾਨਿਤ ਕਰਨ ਲਈ ਟੁਕੜੇ-ਵਾਰ-ਪਰਿਭਾਸ਼ਿਤ ਬਹੁਪਦ ਫੰਕਸ਼ਨਾਂ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ ਸਿਰਫ਼ ਕਲਿੱਪ ਸਟੋਰੇਜ ਵਿੱਚ ਸੇਵ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ, ਅਤੇ ਚਿੱਤਰ ਨੂੰ ਸਿਰਫ਼ ਕਲਿੱਪਬੋਰਡ ਵਿੱਚ ਪਾਉਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਜਾਵੇਗੀ ਬੁਰਸ਼ ਮਿਟਾਉਣ ਦੀ ਬਜਾਏ ਪਿਛੋਕੜ ਨੂੰ ਬਹਾਲ ਕਰੇਗਾ OCR (ਟੈਕਸਟ ਪਛਾਣੋ) ਦਿੱਤੇ ਚਿੱਤਰ ਤੋਂ ਟੈਕਸਟ ਨੂੰ ਪਛਾਣੋ, 120+ ਭਾਸ਼ਾਵਾਂ ਸਮਰਥਿਤ ਹਨ ਤਸਵੀਰ ਵਿੱਚ ਕੋਈ ਟੈਕਸਟ ਨਹੀਂ ਹੈ, ਜਾਂ ਐਪ ਨੇ ਇਸਨੂੰ ਨਹੀਂ ਲੱਭਿਆ Accuracy: %1$s ਪਛਾਣ ਦੀ ਕਿਸਮ ਤੇਜ਼ ਮਿਆਰੀ ਵਧੀਆ ਕੋਈ ਡਾਟਾ ਨਹੀਂ Tesseract OCR ਦੇ ਸਹੀ ਕੰਮ ਕਰਨ ਲਈ ਵਾਧੂ ਸਿਖਲਾਈ ਡੇਟਾ (%1$s) ਨੂੰ ਤੁਹਾਡੀ ਡਿਵਾਈਸ ਤੇ ਡਾਊਨਲੋਡ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।\nਕੀ ਤੁਸੀਂ %2$s ਡੇਟਾ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? ਡਾਊਨਲੋਡ ਕਰੋ ਕੋਈ ਕਨੈਕਸ਼ਨ ਨਹੀਂ ਹੈ, ਇਸਦੀ ਜਾਂਚ ਕਰੋ ਅਤੇ ਟ੍ਰੇਨ ਮਾਡਲਾਂ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਭਾਸ਼ਾਵਾਂ ਉਪਲਬਧ ਭਾਸ਼ਾਵਾਂ ਵਿਭਾਜਨ ਮੋਡ Pixel ਸਵਿੱਚ ਵਰਤੋ ਤੁਹਾਡੇ ਦੁਆਰਾ ਆਧਾਰਿਤ ਗੂਗਲ ਦੀ ਸਮੱਗਰੀ ਦੀ ਬਜਾਏ ਪਿਕਸਲ ਵਰਗਾ ਸਵਿੱਚ ਵਰਤਿਆ ਜਾਵੇਗਾ ਮੂਲ ਮੰਜ਼ਿਲ \'ਤੇ ਨਾਮ %1$s ਨਾਲ ਓਵਰਰਾਈਟ ਕੀਤੀ ਫਾਈਲ ਵੱਡਦਰਸ਼ੀ ਬਿਹਤਰ ਪਹੁੰਚਯੋਗਤਾ ਲਈ ਡਰਾਇੰਗ ਮੋਡਾਂ ਵਿੱਚ ਉਂਗਲੀ ਦੇ ਸਿਖਰ \'ਤੇ ਵੱਡਦਰਸ਼ੀ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਸ਼ੁਰੂਆਤੀ ਮੁੱਲ ਨੂੰ ਜ਼ੋਰ ਦਿਓ ਸ਼ੁਰੂਆਤੀ ਤੌਰ \'ਤੇ exif ਵਿਜੇਟ ਦੀ ਜਾਂਚ ਕਰਨ ਲਈ ਮਜ਼ਬੂਰ ਕਰਦਾ ਹੈ ਕਈ ਭਾਸ਼ਾਵਾਂ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ ਸਲਾਈਡ ਨਾਲ ਨਾਲ ਟੈਪ ਟੌਗਲ ਕਰੋ ਪਾਰਦਰਸ਼ਤਾ ਐਪ ਨੂੰ ਰੇਟ ਕਰੋ ਦਰ ਇਹ ਐਪ ਪੂਰੀ ਤਰ੍ਹਾਂ ਮੁਫਤ ਹੈ, ਜੇਕਰ ਤੁਸੀਂ ਇਸ ਨੂੰ ਵੱਡਾ ਬਣਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਕਿਰਪਾ ਕਰਕੇ Github \'ਤੇ ਪ੍ਰੋਜੈਕਟ ਨੂੰ ਸਟਾਰ ਕਰੋ 😄 ਓਰੀਐਂਟੇਸ਼ਨ & ਸਿਰਫ਼ ਸਕ੍ਰਿਪਟ ਖੋਜ ਆਟੋ ਓਰੀਐਂਟੇਸ਼ਨ & ਸਕ੍ਰਿਪਟ ਖੋਜ ਸਿਰਫ਼ ਆਟੋ ਆਟੋ ਸਿੰਗਲ ਕਾਲਮ ਸਿੰਗਲ ਬਲਾਕ ਵਰਟੀਕਲ ਟੈਕਸਟ ਸਿੰਗਲ ਬਲਾਕ ਸਿੰਗਲ ਲਾਈਨ ਇੱਕ ਸ਼ਬਦ ਚੱਕਰ ਸ਼ਬਦ ਸਿੰਗਲ ਅੱਖਰ ਸਪਾਰਸ ਟੈਕਸਟ ਸਪਾਰਸ ਟੈਕਸਟ ਓਰੀਐਂਟੇਸ਼ਨ & ਸਕ੍ਰਿਪਟ ਖੋਜ ਕੱਚੀ ਲਾਈਨ ਕੀ ਤੁਸੀਂ ਸਾਰੀਆਂ ਮਾਨਤਾ ਕਿਸਮਾਂ ਲਈ ਭਾਸ਼ਾ \"%1$s\" OCR ਸਿਖਲਾਈ ਡੇਟਾ ਨੂੰ ਮਿਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ, ਜਾਂ ਸਿਰਫ਼ ਇੱਕ ਚੁਣੀ ਹੋਈ (%2$s) ਲਈ? ਵਰਤਮਾਨ ਸਾਰੇ ਗਰੇਡੀਐਂਟ ਮੇਕਰ ਕਸਟਮਾਈਜ਼ਡ ਰੰਗਾਂ ਅਤੇ ਦਿੱਖ ਕਿਸਮ ਦੇ ਨਾਲ ਦਿੱਤੇ ਆਉਟਪੁੱਟ ਆਕਾਰ ਦਾ ਗਰੇਡੀਐਂਟ ਬਣਾਓ ਰੇਖਿਕ ਰੇਡੀਅਲ ਸਵੀਪ ਕਰੋ ਗਰੇਡੀਐਂਟ ਕਿਸਮ ਸੈਂਟਰ ਐਕਸ ਸੈਂਟਰ ਵਾਈ ਟਾਇਲ ਮੋਡ ਦੁਹਰਾਇਆ ਮਿਰਰ ਕਲੈਂਪ Decal ਰੰਗ ਸਟਾਪ ਰੰਗ ਸ਼ਾਮਲ ਕਰੋ ਗੁਣ ਚਮਕ ਲਾਗੂ ਕਰਨਾ ਸਕਰੀਨ ਗਰੇਡੀਐਂਟ ਓਵਰਲੇ ਦਿੱਤੇ ਚਿੱਤਰ ਦੇ ਸਿਖਰ ਦਾ ਕੋਈ ਵੀ ਗਰੇਡੀਐਂਟ ਲਿਖੋ ਪਰਿਵਰਤਨ ਕੈਮਰਾ ਤਸਵੀਰ ਲੈਣ ਲਈ ਕੈਮਰੇ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ, ਧਿਆਨ ਦਿਓ ਕਿ ਇਸ ਚਿੱਤਰ ਸਰੋਤ ਤੋਂ ਸਿਰਫ ਇੱਕ ਚਿੱਤਰ ਪ੍ਰਾਪਤ ਕਰਨਾ ਸੰਭਵ ਹੈ ਵਾਟਰਮਾਰਕਿੰਗ ਅਨੁਕੂਲਿਤ ਟੈਕਸਟ/ਚਿੱਤਰ ਵਾਟਰਮਾਰਕਸ ਨਾਲ ਤਸਵੀਰਾਂ ਨੂੰ ਕਵਰ ਕਰੋ ਵਾਟਰਮਾਰਕ ਨੂੰ ਦੁਹਰਾਓ ਦਿੱਤੀ ਸਥਿਤੀ \'ਤੇ ਸਿੰਗਲ ਦੀ ਬਜਾਏ ਚਿੱਤਰ ਉੱਤੇ ਵਾਟਰਮਾਰਕ ਨੂੰ ਦੁਹਰਾਓ ਆਫਸੈੱਟ ਐਕਸ ਆਫਸੈੱਟ ਵਾਈ ਵਾਟਰਮਾਰਕ ਦੀ ਕਿਸਮ ਇਹ ਚਿੱਤਰ ਵਾਟਰਮਾਰਕਿੰਗ ਲਈ ਪੈਟਰਨ ਵਜੋਂ ਵਰਤਿਆ ਜਾਵੇਗਾ ਟੈਕਸਟ ਰੰਗ ਓਵਰਲੇ ਮੋਡ GIF ਟੂਲ ਚਿੱਤਰਾਂ ਨੂੰ GIF ਤਸਵੀਰ ਵਿੱਚ ਬਦਲੋ ਜਾਂ ਦਿੱਤੇ GIF ਚਿੱਤਰ ਤੋਂ ਫਰੇਮਾਂ ਨੂੰ ਐਕਸਟਰੈਕਟ ਕਰੋ ਚਿੱਤਰਾਂ ਲਈ GIF GIF ਫਾਈਲ ਨੂੰ ਤਸਵੀਰਾਂ ਦੇ ਬੈਚ ਵਿੱਚ ਬਦਲੋ ਚਿੱਤਰਾਂ ਦੇ ਬੈਚ ਨੂੰ GIF ਫਾਈਲ ਵਿੱਚ ਬਦਲੋ GIF ਲਈ ਚਿੱਤਰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ GIF ਚਿੱਤਰ ਚੁਣੋ ਪਹਿਲੇ ਫਰੇਮ ਦਾ ਆਕਾਰ ਵਰਤੋ ਨਿਰਧਾਰਤ ਆਕਾਰ ਨੂੰ ਪਹਿਲੇ ਫਰੇਮ ਮਾਪਾਂ ਨਾਲ ਬਦਲੋ ਦੁਹਰਾਓ ਗਿਣਤੀ ਫਰੇਮ ਦੇਰੀ ਮਿਲੀਸ FPS ਲੱਸੋ ਦੀ ਵਰਤੋਂ ਕਰੋ ਮਿਟਾਉਣ ਲਈ ਡਰਾਇੰਗ ਮੋਡ ਵਿੱਚ ਲਾਸੋ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ ਅਸਲ ਚਿੱਤਰ ਪ੍ਰੀਵਿਊ ਅਲਫ਼ਾ ਕੰਫੇਟੀ ਕਨਫੇਟੀ ਨੂੰ ਸੇਵਿੰਗ, ਸ਼ੇਅਰਿੰਗ ਅਤੇ ਹੋਰ ਪ੍ਰਾਇਮਰੀ ਐਕਸ਼ਨ \'ਤੇ ਦਿਖਾਇਆ ਜਾਵੇਗਾ ਸੁਰੱਖਿਅਤ ਮੋਡ ਬਾਹਰ ਜਾਣ \'ਤੇ ਸਮਗਰੀ ਨੂੰ ਲੁਕਾਉਂਦਾ ਹੈ, ਨਾਲ ਹੀ ਸਕ੍ਰੀਨ ਨੂੰ ਕੈਪਚਰ ਜਾਂ ਰਿਕਾਰਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਨਿਕਾਸ ਜੇਕਰ ਤੁਸੀਂ ਹੁਣੇ ਪੂਰਵਦਰਸ਼ਨ ਛੱਡ ਦਿੰਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਨੂੰ ਦੁਬਾਰਾ ਚਿੱਤਰ ਜੋੜਨ ਦੀ ਲੋੜ ਪਵੇਗੀ ਡਿਥਰਿੰਗ ਕੁਆਂਟਿਜ਼ੀਅਰ ਸਲੇਟੀ ਸਕੇਲ ਬੇਅਰ ਟੂ ਬਾਈ ਟੂ ਡਿਥਰਿੰਗ ਬੇਅਰ ਥ੍ਰੀ ਬਾਈ ਥ੍ਰੀ ਡਿਥਰਿੰਗ ਬੇਅਰ ਫੋਰ ਬਾਈ ਫੋਰ ਡਿਥਰਿੰਗ ਬੇਅਰ ਅੱਠ ਦੁਆਰਾ ਅੱਠ ਡਿਥਰਿੰਗ ਫਲੋਇਡ ਸਟੀਨਬਰਗ ਡਿਥਰਿੰਗ ਜਾਰਵਿਸ ਜੁਡੀਸ ਨਿੰਕੇ ਡਿਥਰਿੰਗ ਸੀਅਰਾ ਡਿਥਰਿੰਗ ਦੋ ਕਤਾਰ ਸੀਅਰਾ ਡਿਥਰਿੰਗ ਸੀਅਰਾ ਲਾਈਟ ਡਿਥਰਿੰਗ ਐਟਕਿੰਸਨ ਡਿਥਰਿੰਗ ਸਟਕੀ ਡਿਥਰਿੰਗ ਬਰਕਸ ਡਿਥਰਿੰਗ ਝੂਠੇ ਫਲੋਇਡ ਸਟੀਨਬਰਗ ਡਿਥਰਿੰਗ ਖੱਬੇ ਤੋਂ ਸੱਜੇ ਡਿਥਰਿੰਗ ਰੈਂਡਮ ਡਿਥਰਿੰਗ ਸਧਾਰਨ ਥ੍ਰੈਸ਼ਹੋਲਡ ਡਿਥਰਿੰਗ ਸਿਗਮਾ ਸਥਾਨਿਕ ਸਿਗਮਾ ਮੱਧਮ ਬਲਰ ਬੀ ਸਪਲਾਈਨ ਇੱਕ ਵਕਰ ਜਾਂ ਸਤਹ, ਲਚਕਦਾਰ ਅਤੇ ਨਿਰੰਤਰ ਸ਼ਕਲ ਦੀ ਨੁਮਾਇੰਦਗੀ ਨੂੰ ਸੁਚਾਰੂ ਰੂਪ ਵਿੱਚ ਇੰਟਰਪੋਲੇਟ ਕਰਨ ਅਤੇ ਅਨੁਮਾਨਿਤ ਕਰਨ ਲਈ ਟੁਕੜੇ-ਵਾਰ-ਪਰਿਭਾਸ਼ਿਤ ਬਾਈਕਿਊਬਿਕ ਪੌਲੀਨੋਮੀਅਲ ਫੰਕਸ਼ਨਾਂ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ ਨੇਟਿਵ ਸਟੈਕ ਬਲਰ ਟਿਲਟ ਸ਼ਿਫਟ ਗਲਚ ਦੀ ਰਕਮ ਬੀਜ ਐਨਾਗਲਿਫ ਰੌਲਾ ਪਿਕਸਲ ਲੜੀਬੱਧ ਸ਼ਫਲ ਵਧੀ ਹੋਈ ਗੜਬੜ ਚੈਨਲ ਸ਼ਿਫਟ ਐਕਸ ਚੈਨਲ ਸ਼ਿਫਟ ਵਾਈ ਭ੍ਰਿਸ਼ਟਾਚਾਰ ਦਾ ਆਕਾਰ ਭ੍ਰਿਸ਼ਟਾਚਾਰ ਸ਼ਿਫਟ ਐਕਸ ਭ੍ਰਿਸ਼ਟਾਚਾਰ ਸ਼ਿਫਟ ਵਾਈ ਟੈਂਟ ਬਲਰ ਪਾਸੇ ਫੇਡ ਪਾਸੇ ਸਿਖਰ ਥੱਲੇ ਤਾਕਤ ਇਰੋਡ ਐਨੀਸੋਟ੍ਰੋਪਿਕ ਫੈਲਾਅ ਫੈਲਾ ਸੰਚਾਲਨ ਹਰੀਜ਼ੱਟਲ ਵਿੰਡ ਸਟੈਗਰ ਤੇਜ਼ ਦੁਵੱਲੀ ਬਲਰ ਪੋਇਸਨ ਬਲਰ ਲੋਗਾਰਿਦਮਿਕ ਟੋਨ ਮੈਪਿੰਗ ACES ਫਿਲਮਿਕ ਟੋਨ ਮੈਪਿੰਗ ਕ੍ਰਿਸਟਾਲਾਈਜ਼ ਸਟ੍ਰੋਕ ਰੰਗ ਫ੍ਰੈਕਟਲ ਗਲਾਸ ਐਪਲੀਟਿਊਡ ਮਾਰਬਲ ਗੜਬੜ ਤੇਲ ਪਾਣੀ ਦਾ ਪ੍ਰਭਾਵ ਆਕਾਰ ਬਾਰੰਬਾਰਤਾ ਐਕਸ ਬਾਰੰਬਾਰਤਾ ਵਾਈ ਐਪਲੀਟਿਊਡ ਐਕਸ ਐਪਲੀਟਿਊਡ Y ਪਰਲਿਨ ਵਿਗਾੜ ACES ਹਿੱਲ ਟੋਨ ਮੈਪਿੰਗ ਹੈਬਲ ਫਿਲਮਿਕ ਟੋਨ ਮੈਪਿੰਗ ਹੇਜਲ ਬਰਗੇਸ ਟੋਨ ਮੈਪਿੰਗ ਗਤੀ ਦੇਹਜ਼ੇ ਓਮੇਗਾ ਰੰਗ ਮੈਟ੍ਰਿਕਸ 4x4 ਰੰਗ ਮੈਟ੍ਰਿਕਸ 3x3 ਸਧਾਰਨ ਪ੍ਰਭਾਵ ਪੋਲਰਾਇਡ ਟ੍ਰਾਈਟੋਨੋਮਲੀ ਡਿਊਟਰੋਮਾਲੀ ਪ੍ਰੋਟੋਨੋਮਲੀ ਵਿੰਟੇਜ ਬਰਾਊਨੀ ਕੋਡਾ ਕਰੋਮ ਨਾਈਟ ਵਿਜ਼ਨ ਗਰਮ ਠੰਡਾ ਤ੍ਰਿਟਾਨੋਪੀਆ ਡਿਊਟਾਰੋਨੋਟੋਪੀਆ ਪ੍ਰੋਟਾਨੋਪੀਆ ਐਕਰੋਮੈਟੋਮਾਲੀ ਐਕਰੋਮੈਟੋਪਸੀਆ ਅਨਾਜ ਅਨਸ਼ਾਰਪ ਪੇਸਟਲ ਸੰਤਰੀ ਧੁੰਦ ਗੁਲਾਬੀ ਸੁਪਨਾ ਗੋਲਡਨ ਆਵਰ ਗਰਮ ਗਰਮੀ ਜਾਮਨੀ ਧੁੰਦ ਸੂਰਜ ਚੜ੍ਹਨਾ ਰੰਗੀਨ ਘੁੰਮਣਾ ਨਰਮ ਬਸੰਤ ਰੋਸ਼ਨੀ ਪਤਝੜ ਟੋਨ ਲਵੈਂਡਰ ਡਰੀਮ ਸਾਈਬਰਪੰਕ ਨਿੰਬੂ ਪਾਣੀ ਦੀ ਰੌਸ਼ਨੀ ਸਪੈਕਟ੍ਰਲ ਅੱਗ ਰਾਤ ਦਾ ਜਾਦੂ ਕਲਪਨਾ ਲੈਂਡਸਕੇਪ ਰੰਗ ਧਮਾਕਾ ਇਲੈਕਟ੍ਰਿਕ ਗਰੇਡੀਐਂਟ ਕਾਰਾਮਲ ਹਨੇਰਾ ਭਵਿੱਖਵਾਦੀ ਗਰੇਡੀਐਂਟ ਹਰਾ ਸੂਰਜ ਰੇਨਬੋ ਵਰਲਡ ਗੂੜਾ ਜਾਮਨੀ ਸਪੇਸ ਪੋਰਟਲ ਲਾਲ ਘੁੰਮਣਾ ਡਿਜੀਟਲ ਕੋਡ ਬੋਕੇਹ ਐਪ ਬਾਰ ਇਮੋਜੀ ਨੂੰ ਚੁਣੇ ਹੋਏ ਇੱਕ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਬਜਾਏ ਲਗਾਤਾਰ ਬਦਲਿਆ ਜਾਵੇਗਾ ਬੇਤਰਤੀਬ ਇਮੋਜੀ ਇਮੋਜੀ ਬੰਦ ਹੋਣ \'ਤੇ ਬੇਤਰਤੀਬ ਇਮੋਜੀ ਚੁਣਨ ਦੀ ਵਰਤੋਂ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ ਬੇਤਰਤੀਬ ਇੱਕ ਯੋਗ ਚੁਣਦੇ ਹੋਏ ਇੱਕ ਇਮੋਜੀ ਦੀ ਚੋਣ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ ਪੁਰਾਣਾ ਟੀ.ਵੀ ਬਲਰ ਨੂੰ ਸ਼ਫਲ ਕਰੋ ਮਨਪਸੰਦ ਅਜੇ ਤੱਕ ਕੋਈ ਮਨਪਸੰਦ ਫਿਲਟਰ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤੇ ਗਏ ਹਨ ਚਿੱਤਰ ਫਾਰਮੈਟ ਕਾਰਡਾਂ ਦੇ ਪ੍ਰਮੁੱਖ ਆਈਕਨਾਂ ਦੇ ਹੇਠਾਂ ਚੁਣੀ ਹੋਈ ਸ਼ਕਲ ਵਾਲਾ ਕੰਟੇਨਰ ਜੋੜਦਾ ਹੈ ਆਈਕਨ ਆਕਾਰ ਡਰੈਗੋ ਐਲਡਰਿਜ ਬੰਦ ਕਰ ਦਿਓ ਉਚੀਮੁਰਾ ਮੋਬੀਅਸ ਤਬਦੀਲੀ ਪੀਕ ਰੰਗ ਦੀ ਵਿਗਾੜ ਅਸਲ ਮੰਜ਼ਿਲ \'ਤੇ ਚਿੱਤਰਾਂ ਨੂੰ ਓਵਰਰਾਈਟ ਕੀਤਾ ਗਿਆ ਓਵਰਰਾਈਟ ਫਾਈਲਾਂ ਵਿਕਲਪ ਯੋਗ ਹੋਣ \'ਤੇ ਚਿੱਤਰ ਫਾਰਮੈਟ ਨੂੰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ ਰੰਗ ਸਕੀਮ ਵਜੋਂ ਇਮੋਜੀ ਹੱਥੀਂ ਪਰਿਭਾਸ਼ਿਤ ਇੱਕ ਦੀ ਬਜਾਏ ਐਪ ਰੰਗ ਸਕੀਮ ਦੇ ਤੌਰ \'ਤੇ ਇਮੋਜੀ ਪ੍ਰਾਇਮਰੀ ਰੰਗ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ ਚਿੱਤਰ ਤੋਂ ਮੈਟੀਰੀਅਲ ਯੂ ਪੈਲੇਟ ਬਣਾਉਂਦਾ ਹੈ ਗੂੜ੍ਹੇ ਰੰਗ ਲਾਈਟ ਵੇਰੀਐਂਟ ਦੀ ਬਜਾਏ ਨਾਈਟ ਮੋਡ ਕਲਰ ਸਕੀਮ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ Jetpack ਕੰਪੋਜ਼ ਕੋਡ ਵਜੋਂ ਕਾਪੀ ਕਰੋ ਰਿੰਗ ਬਲਰ ਕ੍ਰਾਸ ਬਲਰ ਚੱਕਰ ਧੁੰਦਲਾ ਸਟਾਰ ਬਲਰ ਲੀਨੀਅਰ ਟਿਲਟ ਸ਼ਿਫਟ ਹਟਾਉਣ ਲਈ ਟੈਗਸ APNG ਟੂਲ ਚਿੱਤਰਾਂ ਨੂੰ APNG ਤਸਵੀਰ ਵਿੱਚ ਬਦਲੋ ਜਾਂ ਦਿੱਤੇ APNG ਚਿੱਤਰ ਤੋਂ ਫਰੇਮਾਂ ਨੂੰ ਐਕਸਟਰੈਕਟ ਕਰੋ ਚਿੱਤਰਾਂ ਲਈ APNG APNG ਫਾਈਲ ਨੂੰ ਤਸਵੀਰਾਂ ਦੇ ਬੈਚ ਵਿੱਚ ਬਦਲੋ ਚਿੱਤਰਾਂ ਦੇ ਬੈਚ ਨੂੰ APNG ਫਾਈਲ ਵਿੱਚ ਬਦਲੋ APNG ਲਈ ਚਿੱਤਰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ APNG ਚਿੱਤਰ ਚੁਣੋ ਮੋਸ਼ਨ ਬਲਰ ਦਿੱਤੀਆਂ ਫਾਈਲਾਂ ਜਾਂ ਚਿੱਤਰਾਂ ਤੋਂ ਜ਼ਿਪ ਫਾਈਲ ਬਣਾਓ ਜ਼ਿਪ ਹੈਂਡਲ ਦੀ ਚੌੜਾਈ ਨੂੰ ਘਸੀਟੋ ਕੰਫੇਟੀ ਦੀ ਕਿਸਮ ਤਿਉਹਾਰ ਵਿਸਫੋਟ ਮੀਂਹ ਕੋਨੇ JXL ਟੂਲ ਬਿਨਾਂ ਗੁਣਵੱਤਾ ਦੇ ਨੁਕਸਾਨ ਦੇ JXL ~ JPEG ਟ੍ਰਾਂਸਕੋਡਿੰਗ ਕਰੋ, ਜਾਂ GIF/APNG ਨੂੰ JXL ਐਨੀਮੇਸ਼ਨ ਵਿੱਚ ਬਦਲੋ JXL ਤੋਂ JPEG JXL ਤੋਂ JPEG ਤੱਕ ਨੁਕਸਾਨ ਰਹਿਤ ਟ੍ਰਾਂਸਕੋਡਿੰਗ ਕਰੋ JPEG ਤੋਂ JXL ਤੱਕ ਨੁਕਸਾਨ ਰਹਿਤ ਟ੍ਰਾਂਸਕੋਡਿੰਗ ਕਰੋ JPEG ਤੋਂ JXL ਸ਼ੁਰੂ ਕਰਨ ਲਈ JXL ਚਿੱਤਰ ਚੁਣੋ ਤੇਜ਼ ਗੌਸੀ ਬਲਰ 2D ਤੇਜ਼ ਗੌਸੀਅਨ ਬਲਰ 3D ਤੇਜ਼ ਗੌਸੀਅਨ ਬਲਰ 4D ਕਾਰ ਈਸਟਰ ਐਪ ਨੂੰ ਕਲਿੱਪਬੋਰਡ ਡੇਟਾ ਨੂੰ ਆਟੋ ਪੇਸਟ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ, ਇਸ ਲਈ ਇਹ ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਾਈ ਦੇਵੇਗਾ ਅਤੇ ਤੁਸੀਂ ਇਸ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੇ ਯੋਗ ਹੋਵੋਗੇ ਹਾਰਮੋਨਾਈਜ਼ੇਸ਼ਨ ਰੰਗ ਹਾਰਮੋਨਾਈਜ਼ੇਸ਼ਨ ਪੱਧਰ ਲੈਂਕਜ਼ੋਸ ਬੇਸਲ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਪਿਕਸਲ ਮੁੱਲਾਂ ਲਈ ਬੇਸਲ (ਜਿੰਕ) ਫੰਕਸ਼ਨ ਨੂੰ ਲਾਗੂ ਕਰਕੇ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਨੂੰ ਕਾਇਮ ਰੱਖਦੀ ਹੈ JXL ਨੂੰ GIF GIF ਚਿੱਤਰਾਂ ਨੂੰ JXL ਐਨੀਮੇਟਡ ਤਸਵੀਰਾਂ ਵਿੱਚ ਬਦਲੋ APNG ਤੋਂ JXL APNG ਚਿੱਤਰਾਂ ਨੂੰ JXL ਐਨੀਮੇਟਡ ਤਸਵੀਰਾਂ ਵਿੱਚ ਬਦਲੋ ਚਿੱਤਰਾਂ ਲਈ JXL JXL ਐਨੀਮੇਸ਼ਨ ਨੂੰ ਤਸਵੀਰਾਂ ਦੇ ਬੈਚ ਵਿੱਚ ਬਦਲੋ JXL ਲਈ ਚਿੱਤਰ ਤਸਵੀਰਾਂ ਦੇ ਬੈਚ ਨੂੰ JXL ਐਨੀਮੇਸ਼ਨ ਵਿੱਚ ਬਦਲੋ ਵਿਵਹਾਰ ਫਾਈਲ ਚੁਣਨਾ ਛੱਡੋ ਜੇ ਚੁਣੀ ਹੋਈ ਸਕ੍ਰੀਨ \'ਤੇ ਇਹ ਸੰਭਵ ਹੋਵੇ ਤਾਂ ਫਾਈਲ ਚੋਣਕਾਰ ਨੂੰ ਤੁਰੰਤ ਦਿਖਾਇਆ ਜਾਵੇਗਾ ਪੂਰਵਦਰਸ਼ਨ ਤਿਆਰ ਕਰੋ ਪ੍ਰੀਵਿਊ ਜਨਰੇਸ਼ਨ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ, ਇਹ ਕੁਝ ਡਿਵਾਈਸਾਂ \'ਤੇ ਕ੍ਰੈਸ਼ ਤੋਂ ਬਚਣ ਵਿੱਚ ਮਦਦ ਕਰ ਸਕਦਾ ਹੈ, ਇਹ ਸਿੰਗਲ ਐਡਿਟ ਵਿਕਲਪ ਦੇ ਅੰਦਰ ਕੁਝ ਸੰਪਾਦਨ ਕਾਰਜਸ਼ੀਲਤਾ ਨੂੰ ਵੀ ਅਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਨੁਕਸਾਨਦਾਇਕ ਸੰਕੁਚਨ ਲੌਸਲੇਸ ਦੀ ਬਜਾਏ ਫਾਈਲ ਦਾ ਆਕਾਰ ਘਟਾਉਣ ਲਈ ਨੁਕਸਾਨਦੇਹ ਕੰਪਰੈਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ ਕੰਪਰੈਸ਼ਨ ਦੀ ਕਿਸਮ ਨਤੀਜੇ ਵਜੋਂ ਚਿੱਤਰ ਡੀਕੋਡਿੰਗ ਦੀ ਗਤੀ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰਦਾ ਹੈ, ਇਸ ਨਾਲ ਨਤੀਜੇ ਵਾਲੇ ਚਿੱਤਰ ਨੂੰ ਤੇਜ਼ੀ ਨਾਲ ਖੋਲ੍ਹਣ ਵਿੱਚ ਮਦਦ ਮਿਲੇਗੀ, %1$s ਦੇ ਮੁੱਲ ਦਾ ਮਤਲਬ ਹੈ ਸਭ ਤੋਂ ਹੌਲੀ ਡੀਕੋਡਿੰਗ, ਜਦੋਂ ਕਿ %2$s - ਸਭ ਤੋਂ ਤੇਜ਼, ਇਹ ਸੈਟਿੰਗ ਆਉਟਪੁੱਟ ਚਿੱਤਰ ਆਕਾਰ ਨੂੰ ਵਧਾ ਸਕਦੀ ਹੈ ਛਾਂਟੀ ਮਿਤੀ ਮਿਤੀ (ਉਲਟ) ਨਾਮ ਨਾਮ (ਉਲਟ) ਚੈਨਲ ਸੰਰਚਨਾ ਅੱਜ ਕੱਲ੍ਹ ਏਮਬੈੱਡ ਚੋਣਕਾਰ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਦਾ ਚਿੱਤਰ ਚੋਣਕਾਰ ਕੋਈ ਇਜਾਜ਼ਤ ਨਹੀਂ ਬੇਨਤੀ ਮਲਟੀਪਲ ਮੀਡੀਆ ਚੁਣੋ ਸਿੰਗਲ ਮੀਡੀਆ ਚੁਣੋ ਚੁਣੋ ਫਿਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਲੈਂਡਸਕੇਪ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਦਿਖਾਓ ਜੇਕਰ ਇਹ ਅਸਮਰੱਥ ਹੈ, ਤਾਂ ਲੈਂਡਸਕੇਪ ਮੋਡ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਸਥਾਈ ਦਿੱਖ ਵਿਕਲਪ ਦੀ ਬਜਾਏ, ਹਮੇਸ਼ਾ ਦੀ ਤਰ੍ਹਾਂ ਸਿਖਰ ਐਪ ਬਾਰ ਵਿੱਚ ਬਟਨ \'ਤੇ ਖੋਲ੍ਹਿਆ ਜਾਵੇਗਾ। ਪੂਰੀ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ ਇਸਨੂੰ ਸਮਰੱਥ ਕਰੋ ਅਤੇ ਸੈਟਿੰਗਾਂ ਪੰਨਾ ਹਮੇਸ਼ਾ ਸਲਾਈਡ ਹੋਣ ਯੋਗ ਦਰਾਜ਼ ਸ਼ੀਟ ਦੀ ਬਜਾਏ ਪੂਰੀ ਸਕਰੀਨ ਦੇ ਤੌਰ \'ਤੇ ਖੋਲ੍ਹਿਆ ਜਾਵੇਗਾ ਸਵਿੱਚ ਦੀ ਕਿਸਮ ਕੰਪੋਜ਼ ਕਰੋ ਇੱਕ Jetpack ਕੰਪੋਜ਼ ਸਮੱਗਰੀ ਜੋ ਤੁਸੀਂ ਬਦਲਦੇ ਹੋ ਇੱਕ ਸਮੱਗਰੀ ਜੋ ਤੁਸੀਂ ਬਦਲਦੇ ਹੋ ਅਧਿਕਤਮ ਐਂਕਰ ਦਾ ਆਕਾਰ ਬਦਲੋ ਪਿਕਸਲ ਪ੍ਰਵਾਹ \"Fluent\" ਡਿਜ਼ਾਈਨ ਸਿਸਟਮ \'ਤੇ ਆਧਾਰਿਤ ਇੱਕ ਸਵਿੱਚ ਕੁਪਰਟੀਨੋ \"Cupertino\" ਡਿਜ਼ਾਈਨ ਸਿਸਟਮ \'ਤੇ ਆਧਾਰਿਤ ਇੱਕ ਸਵਿੱਚ SVG ਲਈ ਚਿੱਤਰ ਦਿੱਤੇ ਚਿੱਤਰਾਂ ਨੂੰ SVG ਚਿੱਤਰਾਂ ਨੂੰ ਟਰੇਸ ਕਰੋ ਸੈਂਪਲ ਪੈਲੇਟ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੇਕਰ ਇਹ ਵਿਕਲਪ ਯੋਗ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਤਾਂ ਕੁਆਂਟਾਈਜ਼ੇਸ਼ਨ ਪੈਲੇਟ ਦਾ ਨਮੂਨਾ ਲਿਆ ਜਾਵੇਗਾ ਮਾਰਗ ਛੱਡ ਦਿੱਤਾ ਡਾਊਨਸਕੇਲਿੰਗ ਤੋਂ ਬਿਨਾਂ ਵੱਡੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਟਰੇਸ ਕਰਨ ਲਈ ਇਸ ਟੂਲ ਦੀ ਵਰਤੋਂ ਦੀ ਸਿਫ਼ਾਰਸ਼ ਨਹੀਂ ਕੀਤੀ ਜਾਂਦੀ, ਇਹ ਕਰੈਸ਼ ਦਾ ਕਾਰਨ ਬਣ ਸਕਦੀ ਹੈ ਅਤੇ ਪ੍ਰੋਸੈਸਿੰਗ ਸਮੇਂ ਨੂੰ ਵਧਾ ਸਕਦੀ ਹੈ। ਡਾਊਨਸਕੇਲ ਚਿੱਤਰ ਪ੍ਰੋਸੈਸਿੰਗ ਤੋਂ ਪਹਿਲਾਂ ਚਿੱਤਰ ਨੂੰ ਹੇਠਲੇ ਮਾਪਾਂ ਤੱਕ ਘਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ, ਇਹ ਟੂਲ ਨੂੰ ਤੇਜ਼ ਅਤੇ ਸੁਰੱਖਿਅਤ ਕੰਮ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਦਾ ਹੈ ਘੱਟੋ-ਘੱਟ ਰੰਗ ਅਨੁਪਾਤ ਲਾਈਨਾਂ ਥ੍ਰੈਸ਼ਹੋਲਡ ਚਤੁਰਭੁਜ ਥ੍ਰੈਸ਼ਹੋਲਡ ਕੋਆਰਡੀਨੇਟਸ ਰਾਊਂਡਿੰਗ ਸਹਿਣਸ਼ੀਲਤਾ ਪਾਥ ਸਕੇਲ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਰੀਸੈਟ ਕਰੋ ਸਾਰੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਮੁੱਲਾਂ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਜਾਵੇਗਾ, ਧਿਆਨ ਦਿਓ ਕਿ ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਵਿਸਤ੍ਰਿਤ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਲਾਈਨ ਚੌੜਾਈ ਇੰਜਣ ਮੋਡ ਵਿਰਾਸਤ LSTM ਨੈੱਟਵਰਕ ਵਿਰਾਸਤ ਅਤੇ LSTM ਬਦਲੋ ਚਿੱਤਰ ਬੈਚਾਂ ਨੂੰ ਦਿੱਤੇ ਫਾਰਮੈਟ ਵਿੱਚ ਬਦਲੋ ਨਵਾਂ ਫੋਲਡਰ ਸ਼ਾਮਲ ਕਰੋ ਬਿੱਟ ਪ੍ਰਤੀ ਨਮੂਨਾ ਕੰਪਰੈਸ਼ਨ ਫੋਟੋਮੈਟ੍ਰਿਕ ਵਿਆਖਿਆ ਨਮੂਨੇ ਪ੍ਰਤੀ ਪਿਕਸਲ ਪਲੈਨਰ ​​ਸੰਰਚਨਾ Y Cb Cr ਸਬ ਸੈਂਪਲਿੰਗ Y Cb Cr ਪੋਜੀਸ਼ਨਿੰਗ ਐਕਸ ਰੈਜ਼ੋਲਿਊਸ਼ਨ Y ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਯੂਨਿਟ ਸਟ੍ਰਿਪ ਆਫਸੈਟਸ ਕਤਾਰਾਂ ਪ੍ਰਤੀ ਪੱਟੀ ਸਟ੍ਰਿਪ ਬਾਈਟ ਗਿਣਤੀ JPEG ਇੰਟਰਚੇਂਜ ਫਾਰਮੈਟ JPEG ਇੰਟਰਚੇਂਜ ਫਾਰਮੈਟ ਲੰਬਾਈ ਟ੍ਰਾਂਸਫਰ ਫੰਕਸ਼ਨ ਵ੍ਹਾਈਟ ਪੁਆਇੰਟ ਪ੍ਰਾਇਮਰੀ ਰੰਗੀਨਤਾਵਾਂ Y Cb Cr ਗੁਣਾਂਕ ਹਵਾਲਾ ਬਲੈਕ ਵ੍ਹਾਈਟ ਮਿਤੀ ਸਮਾਂ ਚਿੱਤਰ ਵਰਣਨ ਬਣਾਉ ਮਾਡਲ ਸਾਫਟਵੇਅਰ ਕਲਾਕਾਰ ਕਾਪੀਰਾਈਟ Exif ਸੰਸਕਰਣ ਫਲੈਸ਼ਪਿਕਸ ਸੰਸਕਰਣ ਰੰਗ ਸਪੇਸ ਗਾਮਾ Pixel X ਮਾਪ Pixel Y ਮਾਪ ਕੰਪਰੈੱਸਡ ਬਿੱਟ ਪ੍ਰਤੀ ਪਿਕਸਲ ਮੇਕਰ ਨੋਟ ਉਪਭੋਗਤਾ ਟਿੱਪਣੀ ਸੰਬੰਧਿਤ ਸਾਊਂਡ ਫਾਈਲ ਮਿਤੀ ਸਮਾਂ ਮੂਲ ਮਿਤੀ ਸਮਾਂ ਡਿਜੀਟਾਈਜ਼ਡ ਔਫਸੈੱਟ ਸਮਾਂ ਔਫਸੈੱਟ ਸਮਾਂ ਮੂਲ ਔਫਸੈੱਟ ਸਮਾਂ ਡਿਜੀਟਾਈਜ਼ਡ ਸਬ ਸਕਿੰਟ ਸਮਾਂ ਸਬ ਸਕਿੰਟ ਸਮਾਂ ਮੂਲ ਸਬ ਸਕਿੰਟ ਟਾਈਮ ਡਿਜੀਟਾਈਜ਼ਡ ਸੰਪਰਕ ਦਾ ਸਮਾਂ F ਨੰਬਰ ਐਕਸਪੋਜ਼ਰ ਪ੍ਰੋਗਰਾਮ ਸਪੈਕਟ੍ਰਲ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਫੋਟੋਗ੍ਰਾਫਿਕ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਓ.ਈ.ਸੀ.ਐਫ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਦੀ ਕਿਸਮ ਮਿਆਰੀ ਆਉਟਪੁੱਟ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਸਿਫਾਰਸ਼ੀ ਐਕਸਪੋਜ਼ਰ ਸੂਚਕਾਂਕ ISO ਸਪੀਡ ISO ਸਪੀਡ ਅਕਸ਼ਾਂਸ਼ yyy ISO ਸਪੀਡ ਵਿਥਕਾਰ zzz ਸ਼ਟਰ ਸਪੀਡ ਮੁੱਲ ਅਪਰਚਰ ਮੁੱਲ ਚਮਕ ਦਾ ਮੁੱਲ ਐਕਸਪੋਜ਼ਰ ਪੱਖਪਾਤ ਮੁੱਲ ਅਧਿਕਤਮ ਅਪਰਚਰ ਮੁੱਲ ਵਿਸ਼ੇ ਦੀ ਦੂਰੀ ਮੀਟਰਿੰਗ ਮੋਡ ਫਲੈਸ਼ ਵਿਸ਼ਾ ਖੇਤਰ ਫੋਕਲ ਲੰਬਾਈ ਫਲੈਸ਼ ਊਰਜਾ ਸਥਾਨਿਕ ਬਾਰੰਬਾਰਤਾ ਜਵਾਬ ਫੋਕਲ ਪਲੇਨ ਐਕਸ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਫੋਕਲ ਪਲੇਨ Y ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਫੋਕਲ ਪਲੇਨ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਯੂਨਿਟ ਵਿਸ਼ਾ ਟਿਕਾਣਾ ਐਕਸਪੋਜ਼ਰ ਸੂਚਕਾਂਕ ਸੈਂਸਿੰਗ ਵਿਧੀ ਫਾਈਲ ਸਰੋਤ CFA ਪੈਟਰਨ ਕਸਟਮ ਰੈਂਡਰ ਕੀਤਾ ਗਿਆ ਐਕਸਪੋਜ਼ਰ ਮੋਡ ਚਿੱਟਾ ਸੰਤੁਲਨ ਡਿਜੀਟਲ ਜ਼ੂਮ ਅਨੁਪਾਤ ਫੋਕਲ ਲੰਬਾਈ ਵਿੱਚ 35mm ਫਿਲਮ ਸੀਨ ਕੈਪਚਰ ਦੀ ਕਿਸਮ ਕੰਟਰੋਲ ਹਾਸਲ ਕਰੋ ਕੰਟ੍ਰਾਸਟ ਸੰਤ੍ਰਿਪਤਾ ਤਿੱਖਾਪਨ ਡਿਵਾਈਸ ਸੈਟਿੰਗ ਦਾ ਵੇਰਵਾ ਵਿਸ਼ਾ ਦੂਰੀ ਸੀਮਾ ਚਿੱਤਰ ਵਿਲੱਖਣ ID ਕੈਮਰੇ ਦੇ ਮਾਲਕ ਦਾ ਨਾਮ ਬਾਡੀ ਸੀਰੀਅਲ ਨੰਬਰ ਲੈਂਸ ਨਿਰਧਾਰਨ ਲੈਂਸ ਬਣਾਉ ਲੈਂਸ ਮਾਡਲ ਲੈਂਸ ਸੀਰੀਅਲ ਨੰਬਰ GPS ਸੰਸਕਰਣ ਆਈ.ਡੀ GPS Latitude Ref GPS ਵਿਥਕਾਰ GPS ਲੰਬਕਾਰ ਰੈਫ GPS ਲੰਬਕਾਰ GPS ਉਚਾਈ ਰੈਫ GPS ਉਚਾਈ GPS ਟਾਈਮ ਸਟੈਂਪ GPS ਸੈਟੇਲਾਈਟ GPS ਸਥਿਤੀ GPS ਮਾਪ ਮੋਡ GPS DOP GPS ਸਪੀਡ ਰੈਫ GPS ਸਪੀਡ GPS ਟਰੈਕ ਰੈਫ GPS ਟਰੈਕ GPS Img ਨਿਰਦੇਸ਼ਕ ਰੈਫ GPS Img ਦਿਸ਼ਾ GPS ਨਕਸ਼ਾ ਡਾਟਾ GPS Dest Latitude Ref GPS ਡੈਸਟ ਵਿਥਕਾਰ GPS ਡੈਸਟ ਲੰਬਕਾਰ ਰੈਫ GPS ਡੈਸਟ ਲੰਬਕਾਰ GPS ਡੈਸਟ ਬੇਅਰਿੰਗ ਰੈਫ GPS ਡੈਸਟ ਬੇਅਰਿੰਗ GPS ਡੈਸਟ ਡਿਸਟੈਂਸ ਰੈਫ GPS ਡੈਸਟ ਦੂਰੀ GPS ਪ੍ਰੋਸੈਸਿੰਗ ਵਿਧੀ GPS ਖੇਤਰ ਜਾਣਕਾਰੀ GPS ਮਿਤੀ ਸਟੈਂਪ GPS ਅੰਤਰ GPS H ਪੋਜੀਸ਼ਨਿੰਗ ਗਲਤੀ ਅੰਤਰ-ਕਾਰਜਸ਼ੀਲਤਾ ਸੂਚਕਾਂਕ DNG ਸੰਸਕਰਣ ਡਿਫੌਲਟ ਕ੍ਰੌਪ ਆਕਾਰ ਝਲਕ ਚਿੱਤਰ ਸ਼ੁਰੂ ਝਲਕ ਚਿੱਤਰ ਦੀ ਲੰਬਾਈ ਆਸਪੈਕਟ ਫਰੇਮ ਸੈਂਸਰ ਬੌਟਮ ਬਾਰਡਰ ਸੈਂਸਰ ਖੱਬਾ ਬਾਰਡਰ ਸੈਂਸਰ ਸੱਜਾ ਕਿਨਾਰਾ ਸੈਂਸਰ ਸਿਖਰ ਬਾਰਡਰ ISO ਦਿੱਤੇ ਗਏ ਫੌਂਟ ਅਤੇ ਰੰਗ ਨਾਲ ਮਾਰਗ \'ਤੇ ਟੈਕਸਟ ਡਰਾਅ ਕਰੋ ਫੌਂਟ ਦਾ ਆਕਾਰ ਵਾਟਰਮਾਰਕ ਦਾ ਆਕਾਰ ਪਾਠ ਦੁਹਰਾਓ ਮੌਜੂਦਾ ਟੈਕਸਟ ਨੂੰ ਇੱਕ ਵਾਰ ਡਰਾਇੰਗ ਦੀ ਬਜਾਏ ਮਾਰਗ ਦੇ ਅੰਤ ਤੱਕ ਦੁਹਰਾਇਆ ਜਾਵੇਗਾ ਡੈਸ਼ ਦਾ ਆਕਾਰ ਚੁਣੇ ਹੋਏ ਚਿੱਤਰ ਨੂੰ ਦਿੱਤੇ ਮਾਰਗ \'ਤੇ ਖਿੱਚਣ ਲਈ ਵਰਤੋ ਇਹ ਚਿੱਤਰ ਖਿੱਚੇ ਗਏ ਮਾਰਗ ਦੀ ਦੁਹਰਾਉਣ ਵਾਲੀ ਐਂਟਰੀ ਵਜੋਂ ਵਰਤਿਆ ਜਾਵੇਗਾ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਰੂਪਰੇਖਾ ਤਿਕੋਣ ਖਿੱਚਦਾ ਹੈ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਰੂਪਰੇਖਾ ਤਿਕੋਣ ਖਿੱਚਦਾ ਹੈ ਰੂਪਰੇਖਾ ਤਿਕੋਣ ਤਿਕੋਣ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਬਹੁਭੁਜ ਖਿੱਚਦਾ ਹੈ ਬਹੁਭੁਜ ਰੂਪਰੇਖਾ ਬਹੁਭੁਜ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਰੂਪਰੇਖਾ ਬਹੁਭੁਜ ਖਿੱਚਦਾ ਹੈ ਸਿਰਲੇਖ ਨਿਯਮਤ ਬਹੁਭੁਜ ਖਿੱਚੋ ਬਹੁਭੁਜ ਖਿੱਚੋ ਜੋ ਮੁਫਤ ਰੂਪ ਦੀ ਬਜਾਏ ਨਿਯਮਤ ਹੋਵੇਗਾ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਤਾਰਾ ਖਿੱਚਦਾ ਹੈ ਤਾਰਾ ਰੂਪਰੇਖਾਬੱਧ ਤਾਰਾ ਸ਼ੁਰੂਆਤੀ ਬਿੰਦੂ ਤੋਂ ਅੰਤ ਬਿੰਦੂ ਤੱਕ ਰੂਪਰੇਖਾਬੱਧ ਤਾਰਾ ਖਿੱਚਦਾ ਹੈ ਅੰਦਰੂਨੀ ਰੇਡੀਅਸ ਅਨੁਪਾਤ ਨਿਯਮਤ ਤਾਰਾ ਖਿੱਚੋ ਤਾਰਾ ਖਿੱਚੋ ਜੋ ਮੁਫਤ ਫਾਰਮ ਦੀ ਬਜਾਏ ਨਿਯਮਤ ਹੋਵੇਗਾ ਐਂਟੀਅਲੀਅਸ ਤਿੱਖੇ ਕਿਨਾਰਿਆਂ ਨੂੰ ਰੋਕਣ ਲਈ ਐਂਟੀਅਲਾਈਜ਼ਿੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਪੂਰਵਦਰਸ਼ਨ ਦੀ ਬਜਾਏ ਸੰਪਾਦਨ ਖੋਲ੍ਹੋ ਜਦੋਂ ਤੁਸੀਂ ਇਮੇਜਟੂਲਬਾਕਸ ਵਿੱਚ ਖੋਲ੍ਹਣ ਲਈ (ਪੂਰਵਦਰਸ਼ਨ) ਚਿੱਤਰ ਦੀ ਚੋਣ ਕਰਦੇ ਹੋ, ਤਾਂ ਪੂਰਵਦਰਸ਼ਨ ਦੀ ਬਜਾਏ ਸੰਪਾਦਨ ਚੋਣ ਸ਼ੀਟ ਖੋਲ੍ਹਿਆ ਜਾਵੇਗਾ ਦਸਤਾਵੇਜ਼ ਸਕੈਨਰ ਦਸਤਾਵੇਜ਼ਾਂ ਨੂੰ ਸਕੈਨ ਕਰੋ ਅਤੇ ਉਹਨਾਂ ਤੋਂ PDF ਜਾਂ ਵੱਖਰੀਆਂ ਤਸਵੀਰਾਂ ਬਣਾਓ ਸਕੈਨਿੰਗ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ ਸਕੈਨਿੰਗ ਸ਼ੁਰੂ ਕਰੋ ਪੀਡੀਐਫ ਵਜੋਂ ਸੇਵ ਕਰੋ ਪੀਡੀਐਫ ਵਜੋਂ ਸਾਂਝਾ ਕਰੋ ਹੇਠਾਂ ਦਿੱਤੇ ਵਿਕਲਪ ਚਿੱਤਰਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਹਨ, PDF ਨਹੀਂ ਹਿਸਟੋਗ੍ਰਾਮ HSV ਨੂੰ ਬਰਾਬਰ ਕਰੋ ਹਿਸਟੋਗ੍ਰਾਮ ਨੂੰ ਬਰਾਬਰ ਕਰੋ ਪ੍ਰਤੀਸ਼ਤ ਦਰਜ ਕਰੋ ਟੈਕਸਟ ਫੀਲਡ ਦੁਆਰਾ ਦਾਖਲ ਹੋਣ ਦਿਓ ਉਹਨਾਂ ਨੂੰ ਉੱਡਣ \'ਤੇ ਦਾਖਲ ਕਰਨ ਲਈ, ਪ੍ਰੀਸੈਟਸ ਦੀ ਚੋਣ ਦੇ ਪਿੱਛੇ ਟੈਕਸਟ ਫੀਲਡ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਸਕੇਲ ਰੰਗ ਸਪੇਸ ਰੇਖਿਕ ਹਿਸਟੋਗ੍ਰਾਮ ਪਿਕਸਲੇਸ਼ਨ ਨੂੰ ਬਰਾਬਰ ਬਣਾਓ ਗਰਿੱਡ ਦਾ ਆਕਾਰ X ਗਰਿੱਡ ਦਾ ਆਕਾਰ Y ਹਿਸਟੋਗ੍ਰਾਮ ਅਡੈਪਟਿਵ ਨੂੰ ਬਰਾਬਰ ਬਣਾਓ ਹਿਸਟੋਗ੍ਰਾਮ ਅਡੈਪਟਿਵ LUV ਨੂੰ ਬਰਾਬਰ ਬਣਾਓ ਹਿਸਟੋਗ੍ਰਾਮ ਅਡੈਪਟਿਵ LAB ਨੂੰ ਬਰਾਬਰ ਬਣਾਓ CLAHE CLAHE ਲੈਬ ਕਲੇ ਲਵ ਸਮੱਗਰੀ ਨੂੰ ਕੱਟੋ ਫਰੇਮ ਦਾ ਰੰਗ ਅਣਡਿੱਠ ਕਰਨ ਲਈ ਰੰਗ ਟੈਂਪਲੇਟ ਕੋਈ ਟੈਮਪਲੇਟ ਫਿਲਟਰ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤੇ ਗਏ ਨਵਾਂ ਬਣਾਓ ਸਕੈਨ ਕੀਤਾ QR ਕੋਡ ਇੱਕ ਵੈਧ ਫਿਲਟਰ ਟੈਮਪਲੇਟ ਨਹੀਂ ਹੈ QR ਕੋਡ ਸਕੈਨ ਕਰੋ ਚੁਣੀ ਗਈ ਫ਼ਾਈਲ ਵਿੱਚ ਕੋਈ ਫਿਲਟਰ ਟੈਮਪਲੇਟ ਡਾਟਾ ਨਹੀਂ ਹੈ ਟੈਮਪਲੇਟ ਬਣਾਓ ਟੈਮਪਲੇਟ ਦਾ ਨਾਮ ਇਸ ਚਿੱਤਰ ਦੀ ਵਰਤੋਂ ਇਸ ਫਿਲਟਰ ਟੈਮਪਲੇਟ ਦੀ ਝਲਕ ਲਈ ਕੀਤੀ ਜਾਵੇਗੀ ਟੈਮਪਲੇਟ ਫਿਲਟਰ QR ਕੋਡ ਚਿੱਤਰ ਵਜੋਂ ਫਾਈਲ ਦੇ ਰੂਪ ਵਿੱਚ ਫਾਈਲ ਦੇ ਰੂਪ ਵਿੱਚ ਸੇਵ ਕਰੋ QR ਕੋਡ ਚਿੱਤਰ ਵਜੋਂ ਸੁਰੱਖਿਅਤ ਕਰੋ ਟੈਮਪਲੇਟ ਮਿਟਾਓ ਤੁਸੀਂ ਚੁਣੇ ਹੋਏ ਟੈਮਪਲੇਟ ਫਿਲਟਰ ਨੂੰ ਮਿਟਾਉਣ ਲੱਗੇ ਹੋ। ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ \"%1$s\" (%2$s) ਨਾਮ ਨਾਲ ਫਿਲਟਰ ਟੈਮਪਲੇਟ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਫਿਲਟਰ ਪ੍ਰੀਵਿਊ QR ਅਤੇ ਬਾਰਕੋਡ QR ਕੋਡ ਨੂੰ ਸਕੈਨ ਕਰੋ ਅਤੇ ਇਸਦੀ ਸਮੱਗਰੀ ਪ੍ਰਾਪਤ ਕਰੋ ਜਾਂ ਨਵਾਂ ਬਣਾਉਣ ਲਈ ਆਪਣੀ ਸਟ੍ਰਿੰਗ ਪੇਸਟ ਕਰੋ ਕੋਡ ਸਮੱਗਰੀ ਖੇਤਰ ਵਿੱਚ ਸਮੱਗਰੀ ਨੂੰ ਬਦਲਣ ਲਈ ਕਿਸੇ ਵੀ ਬਾਰਕੋਡ ਨੂੰ ਸਕੈਨ ਕਰੋ, ਜਾਂ ਚੁਣੀ ਗਈ ਕਿਸਮ ਦੇ ਨਾਲ ਨਵਾਂ ਬਾਰਕੋਡ ਬਣਾਉਣ ਲਈ ਕੁਝ ਟਾਈਪ ਕਰੋ QR ਵਰਣਨ ਘੱਟੋ-ਘੱਟ QR ਕੋਡ ਨੂੰ ਸਕੈਨ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਕੈਮਰੇ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ ਦਸਤਾਵੇਜ਼ ਸਕੈਨਰ ਨੂੰ ਸਕੈਨ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਕੈਮਰਾ ਇਜਾਜ਼ਤ ਦਿਓ ਘਣ ਬੀ-ਸਪਲਾਈਨ ਹੈਮਿੰਗ ਹੈਨਿੰਗ ਬਲੈਕਮੈਨ ਵੈਲਚ ਚਤੁਰਭੁਜ ਗੌਸੀ ਸਪਿੰਕਸ ਬਾਰਟਲੇਟ ਰੋਬੀਡੌਕਸ ਰੋਬੀਡੌਕਸ ਸ਼ਾਰਪ ਸਪਲਾਈਨ 16 ਸਪਲਾਈਨ 36 ਸਪਲਾਈਨ 64 ਕੈਸਰ ਬਾਰਟਲੇਟ-ਉਹ ਬਾਕਸ ਬੋਹਮਨ Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 ਜਿਨਕ Lanczos 3 Jinc Lanczos 4 ਜਿਨਕ ਕਿਊਬਿਕ ਇੰਟਰਪੋਲੇਸ਼ਨ ਸਭ ਤੋਂ ਨਜ਼ਦੀਕੀ 16 ਪਿਕਸਲਾਂ \'ਤੇ ਵਿਚਾਰ ਕਰਕੇ ਨਿਰਵਿਘਨ ਸਕੇਲਿੰਗ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ, ਬਾਇਲੀਨੀਅਰ ਨਾਲੋਂ ਵਧੀਆ ਨਤੀਜੇ ਦਿੰਦਾ ਹੈ ਇੱਕ ਵਕਰ ਜਾਂ ਸਤਹ, ਲਚਕਦਾਰ ਅਤੇ ਨਿਰੰਤਰ ਸ਼ਕਲ ਦੀ ਨੁਮਾਇੰਦਗੀ ਨੂੰ ਸੁਚਾਰੂ ਰੂਪ ਵਿੱਚ ਇੰਟਰਪੋਲੇਟ ਕਰਨ ਅਤੇ ਅਨੁਮਾਨਿਤ ਕਰਨ ਲਈ ਟੁਕੜੇ-ਵਾਰ-ਪਰਿਭਾਸ਼ਿਤ ਬਹੁਪਦ ਫੰਕਸ਼ਨਾਂ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ ਇੱਕ ਵਿੰਡੋ ਫੰਕਸ਼ਨ ਇੱਕ ਸਿਗਨਲ ਦੇ ਕਿਨਾਰਿਆਂ ਨੂੰ ਟੇਪਰ ਕਰਕੇ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਨੂੰ ਘਟਾਉਣ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ, ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਵਿੱਚ ਉਪਯੋਗੀ ਹੈਨ ਵਿੰਡੋ ਦਾ ਇੱਕ ਰੂਪ, ਆਮ ਤੌਰ \'ਤੇ ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਿੱਚ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਨੂੰ ਘਟਾਉਣ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਇੱਕ ਵਿੰਡੋ ਫੰਕਸ਼ਨ ਜੋ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਨੂੰ ਘੱਟ ਕਰਕੇ ਵਧੀਆ ਬਾਰੰਬਾਰਤਾ ਰੈਜ਼ੋਲੂਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ, ਅਕਸਰ ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਵਿੱਚ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਇੱਕ ਵਿੰਡੋ ਫੰਕਸ਼ਨ ਜੋ ਘੱਟ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਦੇ ਨਾਲ ਵਧੀਆ ਬਾਰੰਬਾਰਤਾ ਰੈਜ਼ੋਲੂਸ਼ਨ ਦੇਣ ਲਈ ਤਿਆਰ ਕੀਤਾ ਗਿਆ ਹੈ, ਅਕਸਰ ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਿੱਚ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਇੱਕ ਵਿਧੀ ਜੋ ਇੰਟਰਪੋਲੇਸ਼ਨ ਲਈ ਇੱਕ ਚਤੁਰਭੁਜ ਫੰਕਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ, ਨਿਰਵਿਘਨ ਅਤੇ ਨਿਰੰਤਰ ਨਤੀਜੇ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ ਇੱਕ ਇੰਟਰਪੋਲੇਸ਼ਨ ਵਿਧੀ ਜੋ ਇੱਕ ਗੌਸੀ ਫੰਕਸ਼ਨ ਨੂੰ ਲਾਗੂ ਕਰਦੀ ਹੈ, ਚਿੱਤਰਾਂ ਵਿੱਚ ਸ਼ੋਰ ਨੂੰ ਸੁਚਾਰੂ ਬਣਾਉਣ ਅਤੇ ਘਟਾਉਣ ਲਈ ਉਪਯੋਗੀ ਇੱਕ ਉੱਨਤ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਨੂੰ ਘਟਾਉਣ ਲਈ ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਵਿੱਚ ਵਰਤਿਆ ਜਾਂਦਾ ਇੱਕ ਤਿਕੋਣੀ ਵਿੰਡੋ ਫੰਕਸ਼ਨ ਇੱਕ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਵਿਧੀ ਜੋ ਕੁਦਰਤੀ ਚਿੱਤਰ ਨੂੰ ਮੁੜ ਆਕਾਰ ਦੇਣ, ਤਿੱਖਾਪਨ ਅਤੇ ਨਿਰਵਿਘਨਤਾ ਨੂੰ ਸੰਤੁਲਿਤ ਕਰਨ ਲਈ ਅਨੁਕੂਲ ਹੈ ਰੋਬੀਡੌਕਸ ਵਿਧੀ ਦਾ ਇੱਕ ਤਿੱਖਾ ਰੂਪ, ਕਰਿਸਪ ਚਿੱਤਰ ਨੂੰ ਮੁੜ ਆਕਾਰ ਦੇਣ ਲਈ ਅਨੁਕੂਲਿਤ ਇੱਕ ਸਪਲਾਈਨ-ਅਧਾਰਿਤ ਇੰਟਰਪੋਲੇਸ਼ਨ ਵਿਧੀ ਜੋ 16-ਟੈਪ ਫਿਲਟਰ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਨਿਰਵਿਘਨ ਨਤੀਜੇ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ ਇੱਕ ਸਪਲਾਈਨ-ਅਧਾਰਿਤ ਇੰਟਰਪੋਲੇਸ਼ਨ ਵਿਧੀ ਜੋ 36-ਟੈਪ ਫਿਲਟਰ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਨਿਰਵਿਘਨ ਨਤੀਜੇ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ ਇੱਕ ਸਪਲਾਈਨ-ਅਧਾਰਿਤ ਇੰਟਰਪੋਲੇਸ਼ਨ ਵਿਧੀ ਜੋ 64-ਟੈਪ ਫਿਲਟਰ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਨਿਰਵਿਘਨ ਨਤੀਜੇ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ ਇੱਕ ਇੰਟਰਪੋਲੇਸ਼ਨ ਵਿਧੀ ਜੋ ਕੈਸਰ ਵਿੰਡੋ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ, ਮੁੱਖ-ਲੋਬ ਚੌੜਾਈ ਅਤੇ ਸਾਈਡ-ਲੋਬ ਪੱਧਰ ਦੇ ਵਿਚਕਾਰ ਵਪਾਰ-ਬੰਦ \'ਤੇ ਚੰਗਾ ਨਿਯੰਤਰਣ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ। ਬਾਰਟਲੇਟ ਅਤੇ ਹੈਨ ਵਿੰਡੋਜ਼ ਨੂੰ ਜੋੜਦਾ ਇੱਕ ਹਾਈਬ੍ਰਿਡ ਵਿੰਡੋ ਫੰਕਸ਼ਨ, ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਵਿੱਚ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਨੂੰ ਘਟਾਉਣ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਇੱਕ ਸਧਾਰਨ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਨਜ਼ਦੀਕੀ ਪਿਕਸਲ ਮੁੱਲਾਂ ਦੀ ਔਸਤ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ, ਅਕਸਰ ਇੱਕ ਬਲਾਕੀ ਦਿੱਖ ਦੇ ਨਤੀਜੇ ਵਜੋਂ ਇੱਕ ਵਿੰਡੋ ਫੰਕਸ਼ਨ ਸਪੈਕਟ੍ਰਲ ਲੀਕੇਜ ਨੂੰ ਘਟਾਉਣ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ, ਸਿਗਨਲ ਪ੍ਰੋਸੈਸਿੰਗ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਿੱਚ ਵਧੀਆ ਬਾਰੰਬਾਰਤਾ ਰੈਜ਼ੋਲੂਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ ਇੱਕ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਲਈ 2-ਲੋਬ ਲੈਂਕਜ਼ੋਸ ਫਿਲਟਰ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ ਇੱਕ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਲਈ 3-ਲੋਬ ਲੈਂਕਜ਼ੋਸ ਫਿਲਟਰ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ ਇੱਕ ਰੀਸੈਪਲਿੰਗ ਵਿਧੀ ਜੋ ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਲਈ 4-ਲੋਬ ਲੈਂਕਜ਼ੋਸ ਫਿਲਟਰ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ Lanczos 2 ਫਿਲਟਰ ਦਾ ਇੱਕ ਰੂਪ ਜੋ ਕਿ ਜਿੰਕ ਫੰਕਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ, ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ Lanczos 3 ਫਿਲਟਰ ਦਾ ਇੱਕ ਰੂਪ ਜੋ ਜਿੰਕ ਫੰਕਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ, ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ Lanczos 4 ਫਿਲਟਰ ਦਾ ਇੱਕ ਰੂਪ ਜੋ ਜਿੰਕ ਫੰਕਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ, ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਇੰਟਰਪੋਲੇਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ ਹੈਨਿੰਗ EWA ਨਿਰਵਿਘਨ ਇੰਟਰਪੋਲੇਸ਼ਨ ਅਤੇ ਰੀਸੈਪਲਿੰਗ ਲਈ ਹੈਨਿੰਗ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ Robidoux EWA ਉੱਚ-ਗੁਣਵੱਤਾ ਦੇ ਰੀਸੈਪਲਿੰਗ ਲਈ ਰੋਬਿਡੌਕਸ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ ਬਲੈਕਮੈਨ ਈ.ਵੀ ਰਿੰਗਿੰਗ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਘੱਟ ਤੋਂ ਘੱਟ ਕਰਨ ਲਈ ਬਲੈਕਮੈਨ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ Quadric EWA ਨਿਰਵਿਘਨ ਇੰਟਰਪੋਲੇਸ਼ਨ ਲਈ ਕਵਾਡ੍ਰਿਕ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਵੇਟਿਡ ਔਸਤ (EWA) ਰੂਪ ਰੋਬਿਡੌਕਸ ਸ਼ਾਰਪ EWA ਤਿੱਖੇ ਨਤੀਜਿਆਂ ਲਈ ਰੋਬਿਡੌਕਸ ਸ਼ਾਰਪ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ Lanczos 3 Jinc EWA ਲੈਂਕਜ਼ੋਸ 3 ਜਿੰਕ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਵੇਟਿਡ ਔਸਤ (EWA) ਵੇਰੀਐਂਟ ਘੱਟ ਅਲੀਅਸਿੰਗ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਦੇ ਰੀਸੈਪਲਿੰਗ ਲਈ ਜਿਨਸੇਂਗ ਤਿੱਖਾਪਨ ਅਤੇ ਨਿਰਵਿਘਨਤਾ ਦੇ ਚੰਗੇ ਸੰਤੁਲਨ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਚਿੱਤਰ ਪ੍ਰੋਸੈਸਿੰਗ ਲਈ ਤਿਆਰ ਕੀਤਾ ਗਿਆ ਇੱਕ ਰੀਸੈਪਲਿੰਗ ਫਿਲਟਰ Ginseng EWA ਵਿਸਤ੍ਰਿਤ ਚਿੱਤਰ ਗੁਣਵੱਤਾ ਲਈ ਜਿਨਸੇਂਗ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ Lanczos Sharp EWA ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਤਿੱਖੇ ਨਤੀਜੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਲੈਂਕਜ਼ੋਸ ਸ਼ਾਰਪ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ Lanczos 4 ਤਿੱਖਾ EWA ਬਹੁਤ ਹੀ ਤਿੱਖੀ ਚਿੱਤਰ ਰੀਸੈਪਲਿੰਗ ਲਈ ਲੈਂਕਜ਼ੋਸ 4 ਸ਼ਾਰਪਸਟ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ Lanczos ਸਾਫਟ EWA ਨਿਰਵਿਘਨ ਚਿੱਤਰ ਰੀਸੈਪਲਿੰਗ ਲਈ ਲੈਂਕਜ਼ੋਸ ਸਾਫਟ ਫਿਲਟਰ ਦਾ ਅੰਡਾਕਾਰ ਭਾਰ ਵਾਲਾ ਔਸਤ (EWA) ਰੂਪ ਹਸਨ ਨਰਮ ਨਿਰਵਿਘਨ ਅਤੇ ਕਲਾਤਮਕ-ਮੁਕਤ ਚਿੱਤਰ ਸਕੇਲਿੰਗ ਲਈ ਹਾਸਨ ਦੁਆਰਾ ਤਿਆਰ ਕੀਤਾ ਗਿਆ ਇੱਕ ਰੀਸੈਪਲਿੰਗ ਫਿਲਟਰ ਫਾਰਮੈਟ ਰੂਪਾਂਤਰਨ ਚਿੱਤਰਾਂ ਦੇ ਬੈਚ ਨੂੰ ਇੱਕ ਫਾਰਮੈਟ ਤੋਂ ਦੂਜੇ ਵਿੱਚ ਬਦਲੋ ਹਮੇਸ਼ਾ ਲਈ ਖਾਰਜ ਕਰੋ ਚਿੱਤਰ ਸਟੈਕਿੰਗ ਚੁਣੇ ਹੋਏ ਮਿਸ਼ਰਣ ਮੋਡਾਂ ਨਾਲ ਚਿੱਤਰਾਂ ਨੂੰ ਇੱਕ ਦੂਜੇ ਦੇ ਸਿਖਰ \'ਤੇ ਸਟੈਕ ਕਰੋ ਚਿੱਤਰ ਸ਼ਾਮਲ ਕਰੋ ਡੱਬਿਆਂ ਦੀ ਗਿਣਤੀ Clahe HSL Clahe HSV ਹਿਸਟੋਗ੍ਰਾਮ ਅਡੈਪਟਿਵ HSL ਨੂੰ ਬਰਾਬਰ ਬਣਾਓ ਹਿਸਟੋਗ੍ਰਾਮ ਅਡੈਪਟਿਵ HSV ਨੂੰ ਬਰਾਬਰ ਬਣਾਓ ਕਿਨਾਰਾ ਮੋਡ ਕਲਿੱਪ ਲਪੇਟ ਰੰਗ ਅੰਨ੍ਹਾਪਨ ਚੁਣੇ ਗਏ ਰੰਗ ਅੰਨ੍ਹੇਪਣ ਰੂਪ ਲਈ ਥੀਮ ਦੇ ਰੰਗਾਂ ਨੂੰ ਅਨੁਕੂਲ ਬਣਾਉਣ ਲਈ ਮੋਡ ਚੁਣੋ ਲਾਲ ਅਤੇ ਹਰੇ ਰੰਗ ਦੇ ਵਿਚਕਾਰ ਫਰਕ ਕਰਨ ਵਿੱਚ ਮੁਸ਼ਕਲ ਹਰੇ ਅਤੇ ਲਾਲ ਰੰਗਾਂ ਵਿੱਚ ਫਰਕ ਕਰਨ ਵਿੱਚ ਮੁਸ਼ਕਲ ਨੀਲੇ ਅਤੇ ਪੀਲੇ ਰੰਗਾਂ ਵਿੱਚ ਫਰਕ ਕਰਨ ਵਿੱਚ ਮੁਸ਼ਕਲ ਲਾਲ ਰੰਗਾਂ ਨੂੰ ਸਮਝਣ ਵਿੱਚ ਅਸਮਰੱਥਾ ਹਰੇ ਰੰਗ ਨੂੰ ਸਮਝਣ ਵਿੱਚ ਅਸਮਰੱਥਾ ਨੀਲੇ ਰੰਗ ਨੂੰ ਸਮਝਣ ਵਿੱਚ ਅਸਮਰੱਥਾ ਸਾਰੇ ਰੰਗਾਂ ਪ੍ਰਤੀ ਘੱਟ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਸੰਪੂਰਨ ਰੰਗ ਅੰਨ੍ਹਾਪਣ, ਸਿਰਫ ਸਲੇਟੀ ਰੰਗਾਂ ਨੂੰ ਵੇਖਣਾ ਕਲਰ ਬਲਾਇੰਡ ਸਕੀਮ ਦੀ ਵਰਤੋਂ ਨਾ ਕਰੋ ਰੰਗ ਬਿਲਕੁਲ ਉਸੇ ਤਰ੍ਹਾਂ ਹੋਣਗੇ ਜਿਵੇਂ ਥੀਮ ਵਿੱਚ ਸੈੱਟ ਕੀਤਾ ਗਿਆ ਹੈ ਸਿਗਮੋਇਡਲ ਲਾਗਰੇਂਜ ੨ ਆਰਡਰ 2 ਦਾ ਇੱਕ ਲੈਗਰੇਂਜ ਇੰਟਰਪੋਲੇਸ਼ਨ ਫਿਲਟਰ, ਨਿਰਵਿਘਨ ਤਬਦੀਲੀਆਂ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਚਿੱਤਰ ਸਕੇਲਿੰਗ ਲਈ ਢੁਕਵਾਂ ਲਾਗਰੇਂਜ ੩ ਆਰਡਰ 3 ਦਾ ਇੱਕ ਲੈਗਰੇਂਜ ਇੰਟਰਪੋਲੇਸ਼ਨ ਫਿਲਟਰ, ਚਿੱਤਰ ਸਕੇਲਿੰਗ ਲਈ ਬਿਹਤਰ ਸ਼ੁੱਧਤਾ ਅਤੇ ਨਿਰਵਿਘਨ ਨਤੀਜੇ ਪੇਸ਼ ਕਰਦਾ ਹੈ Lanczos 6 6 ਦੇ ਉੱਚ ਆਰਡਰ ਦੇ ਨਾਲ ਇੱਕ ਲੈਂਕਜ਼ੋਸ ਰੀਸੈਪਲਿੰਗ ਫਿਲਟਰ, ਤਿੱਖਾ ਅਤੇ ਵਧੇਰੇ ਸਹੀ ਚਿੱਤਰ ਸਕੇਲਿੰਗ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ Lanczos 6 ਜਿਨਕ ਬਿਹਤਰ ਚਿੱਤਰ ਰੀਸੈਪਲਿੰਗ ਕੁਆਲਿਟੀ ਲਈ ਜਿਨਕ ਫੰਕਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਲੈਂਕਜ਼ੋਸ 6 ਫਿਲਟਰ ਦਾ ਇੱਕ ਰੂਪ ਲੀਨੀਅਰ ਬਾਕਸ ਬਲਰ ਰੇਖਿਕ ਟੈਂਟ ਬਲਰ ਰੇਖਿਕ ਗੌਸੀ ਬਾਕਸ ਬਲਰ ਰੇਖਿਕ ਸਟੈਕ ਬਲਰ ਗੌਸੀ ਬਾਕਸ ਬਲਰ ਰੇਖਿਕ ਤੇਜ਼ ਗੌਸੀਅਨ ਬਲਰ ਅੱਗੇ ਰੇਖਿਕ ਤੇਜ਼ ਗੌਸੀ ਬਲਰ ਰੇਖਿਕ ਗੌਸੀ ਬਲਰ ਇਸ ਨੂੰ ਪੇਂਟ ਵਜੋਂ ਵਰਤਣ ਲਈ ਇੱਕ ਫਿਲਟਰ ਚੁਣੋ ਫਿਲਟਰ ਬਦਲੋ ਇਸ ਨੂੰ ਆਪਣੀ ਡਰਾਇੰਗ ਵਿੱਚ ਬੁਰਸ਼ ਵਜੋਂ ਵਰਤਣ ਲਈ ਹੇਠਾਂ ਫਿਲਟਰ ਚੁਣੋ TIFF ਕੰਪਰੈਸ਼ਨ ਸਕੀਮ ਘੱਟ ਪੌਲੀ ਰੇਤ ਪੇਂਟਿੰਗ ਚਿੱਤਰ ਵੰਡਣਾ ਇੱਕ ਚਿੱਤਰ ਨੂੰ ਕਤਾਰਾਂ ਜਾਂ ਕਾਲਮਾਂ ਦੁਆਰਾ ਵੰਡੋ ਸੀਮਾਵਾਂ ਲਈ ਫਿੱਟ ਲੋੜੀਂਦੇ ਵਿਵਹਾਰ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਇਸ ਪੈਰਾਮੀਟਰ ਦੇ ਨਾਲ ਕ੍ਰੌਪ ਰੀਸਾਈਜ਼ ਮੋਡ ਨੂੰ ਜੋੜੋ (ਪਹਿਲੂ ਅਨੁਪਾਤ ਲਈ ਕ੍ਰੌਪ/ਫਿੱਟ) ਭਾਸ਼ਾਵਾਂ ਸਫਲਤਾਪੂਰਵਕ ਆਯਾਤ ਕੀਤੀਆਂ ਗਈਆਂ ਬੈਕਅੱਪ OCR ਮਾਡਲ ਆਯਾਤ ਕਰੋ ਨਿਰਯਾਤ ਸਥਿਤੀ ਕੇਂਦਰ ਉੱਪਰ ਖੱਬੇ ਉੱਪਰ ਸੱਜੇ ਹੇਠਾਂ ਖੱਬੇ ਪਾਸੇ ਹੇਠਾਂ ਸੱਜੇ ਸਿਖਰ ਕੇਂਦਰ ਕੇਂਦਰ ਦਾ ਸੱਜਾ ਹੇਠਲਾ ਕੇਂਦਰ ਸੈਂਟਰ ਖੱਬੇ ਨਿਸ਼ਾਨਾ ਚਿੱਤਰ ਪੈਲੇਟ ਟ੍ਰਾਂਸਫਰ ਵਧਿਆ ਤੇਲ ਸਧਾਰਨ ਪੁਰਾਣਾ ਟੀ.ਵੀ ਐਚ.ਡੀ.ਆਰ ਗੋਥਮ ਸਧਾਰਨ ਸਕੈਚ ਨਰਮ ਗਲੋ ਰੰਗ ਪੋਸਟਰ ਟ੍ਰਾਈ ਟੋਨ ਤੀਜਾ ਰੰਗ ਕਲੇਹ ਓਕਲਾਬ ਕਲਾਰਾ ਓਲਚ ਕਲੇਹ ਜਜ਼ਬਜ਼ ਪੋਲਕਾ ਡਾਟ ਕਲੱਸਟਰਡ 2x2 ਡਿਥਰਿੰਗ ਕਲੱਸਟਰਡ 4x4 ਡਿਥਰਿੰਗ ਕਲੱਸਟਰਡ 8x8 ਡਿਥਰਿੰਗ ਯਿਲੀਲੋਮਾ ਡਿਥਰਿੰਗ ਕੋਈ ਪਸੰਦੀਦਾ ਵਿਕਲਪ ਨਹੀਂ ਚੁਣਿਆ ਗਿਆ, ਉਹਨਾਂ ਨੂੰ ਟੂਲਸ ਪੰਨੇ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ ਮਨਪਸੰਦ ਸ਼ਾਮਲ ਕਰੋ ਪੂਰਕ ਅਨੁਰੂਪ ਤ੍ਰਿਯਾਦਿਕ ਸਪਲਿਟ ਪੂਰਕ ਟੈਟਰਾਡਿਕ ਵਰਗ ਸਮਾਨ + ਪੂਰਕ ਰੰਗ ਸੰਦ ਮਿਕਸ ਕਰੋ, ਟੋਨ ਬਣਾਓ, ਸ਼ੇਡ ਬਣਾਓ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਰੰਗ ਹਾਰਮੋਨੀਜ਼ ਰੰਗ ਦੀ ਛਾਂ ਪਰਿਵਰਤਨ ਟਿੰਟਸ ਟੋਨਸ ਸ਼ੇਡਜ਼ ਰੰਗ ਮਿਕਸਿੰਗ ਰੰਗ ਜਾਣਕਾਰੀ ਚੁਣਿਆ ਰੰਗ ਮਿਕਸ ਕਰਨ ਲਈ ਰੰਗ ਡਾਇਨਾਮਿਕ ਰੰਗ ਚਾਲੂ ਹੋਣ \'ਤੇ ਮੋਨੇਟ ਦੀ ਵਰਤੋਂ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ 512x512 2D LUT ਟੀਚਾ LUT ਚਿੱਤਰ ਇੱਕ ਸ਼ੁਕੀਨ ਮਿਸ ਸ਼ਿਸ਼ਟਾਚਾਰ ਨਰਮ ਸੁੰਦਰਤਾ ਸਾਫਟ ਐਲੀਗੈਂਸ ਵੇਰੀਐਂਟ ਪੈਲੇਟ ਟ੍ਰਾਂਸਫਰ ਵੇਰੀਐਂਟ 3D LUT ਟਾਰਗੇਟ 3D LUT ਫਾਈਲ (.cube / .CUBE) LUT ਬਲੀਚ ਬਾਈਪਾਸ ਮੋਮਬੱਤੀ ਦੀ ਰੌਸ਼ਨੀ ਬਲੂਜ਼ ਸੁੱਟੋ ਐਡੀ ਅੰਬਰ ਪਤਝੜ ਦੇ ਰੰਗ ਫਿਲਮ ਸਟਾਕ 50 ਧੁੰਦ ਵਾਲੀ ਰਾਤ ਕੋਡਕ ਨਿਰਪੱਖ LUT ਚਿੱਤਰ ਪ੍ਰਾਪਤ ਕਰੋ ਪਹਿਲਾਂ, ਨਿਰਪੱਖ LUT \'ਤੇ ਫਿਲਟਰ ਲਗਾਉਣ ਲਈ ਆਪਣੀ ਮਨਪਸੰਦ ਫੋਟੋ ਸੰਪਾਦਨ ਐਪਲੀਕੇਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜੋ ਤੁਸੀਂ ਇੱਥੇ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਇਸਦੇ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਕਰਨ ਲਈ ਹਰੇਕ ਪਿਕਸਲ ਦਾ ਰੰਗ ਦੂਜੇ ਪਿਕਸਲਾਂ \'ਤੇ ਨਿਰਭਰ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ (ਜਿਵੇਂ ਕਿ ਬਲਰ ਕੰਮ ਨਹੀਂ ਕਰੇਗਾ)। ਇੱਕ ਵਾਰ ਤਿਆਰ ਹੋਣ \'ਤੇ, 512*512 LUT ਫਿਲਟਰ ਲਈ ਆਪਣੀ ਨਵੀਂ LUT ਚਿੱਤਰ ਨੂੰ ਇਨਪੁਟ ਵਜੋਂ ਵਰਤੋ ਪੌਪ ਆਰਟ ਸੈਲੂਲੋਇਡ ਕਾਫੀ ਸੁਨਹਿਰੀ ਜੰਗਲ ਹਰਿਆਲੀ ਰੀਟਰੋ ਪੀਲਾ ਲਿੰਕ ਪੂਰਵਦਰਸ਼ਨ ਉਹਨਾਂ ਥਾਵਾਂ \'ਤੇ ਲਿੰਕ ਪੂਰਵਦਰਸ਼ਨ ਨੂੰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਜਿੱਥੇ ਤੁਸੀਂ ਟੈਕਸਟ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ (QRCode, OCR ਆਦਿ) ਲਿੰਕ ICO ਫਾਈਲਾਂ ਨੂੰ ਸਿਰਫ 256 x 256 ਦੇ ਅਧਿਕਤਮ ਆਕਾਰ \'ਤੇ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ WEBP ਨੂੰ GIF GIF ਚਿੱਤਰਾਂ ਨੂੰ WEBP ਐਨੀਮੇਟਡ ਤਸਵੀਰਾਂ ਵਿੱਚ ਬਦਲੋ WEBP ਟੂਲ ਚਿੱਤਰਾਂ ਨੂੰ WEBP ਐਨੀਮੇਟਡ ਤਸਵੀਰ ਵਿੱਚ ਬਦਲੋ ਜਾਂ ਦਿੱਤੇ ਗਏ WEBP ਐਨੀਮੇਸ਼ਨ ਤੋਂ ਫਰੇਮਾਂ ਨੂੰ ਐਕਸਟਰੈਕਟ ਕਰੋ ਚਿੱਤਰਾਂ ਲਈ WEBP WEBP ਫਾਈਲ ਨੂੰ ਤਸਵੀਰਾਂ ਦੇ ਬੈਚ ਵਿੱਚ ਬਦਲੋ ਚਿੱਤਰਾਂ ਦੇ ਬੈਚ ਨੂੰ WEBP ਫਾਈਲ ਵਿੱਚ ਬਦਲੋ WEBP ਲਈ ਚਿੱਤਰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ WEBP ਚਿੱਤਰ ਚੁਣੋ ਫਾਈਲਾਂ ਤੱਕ ਪੂਰੀ ਪਹੁੰਚ ਨਹੀਂ ਹੈ ਸਾਰੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ JXL, QOI ਅਤੇ ਹੋਰ ਚਿੱਤਰਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਪਹੁੰਚ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ ਜੋ Android \'ਤੇ ਚਿੱਤਰਾਂ ਵਜੋਂ ਨਹੀਂ ਪਛਾਣੀਆਂ ਗਈਆਂ ਹਨ। ਇਜਾਜ਼ਤ ਤੋਂ ਬਿਨਾਂ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਉਹਨਾਂ ਚਿੱਤਰਾਂ ਨੂੰ ਦਿਖਾਉਣ ਵਿੱਚ ਅਸਮਰੱਥ ਹੈ ਡਿਫੌਲਟ ਡਰਾਅ ਰੰਗ ਡਿਫੌਲਟ ਡਰਾਅ ਮਾਰਗ ਮੋਡ ਟਾਈਮਸਟੈਂਪ ਸ਼ਾਮਲ ਕਰੋ ਟਾਈਮਸਟੈਂਪ ਨੂੰ ਆਉਟਪੁੱਟ ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਜੋੜਨ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਫਾਰਮੈਟ ਕੀਤਾ ਟਾਈਮਸਟੈਂਪ ਮੂਲ ਮਿਲੀਸ ਦੀ ਬਜਾਏ ਆਉਟਪੁੱਟ ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਟਾਈਮਸਟੈਂਪ ਫਾਰਮੈਟਿੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ ਟਾਈਮਸਟੈਂਪਸ ਨੂੰ ਉਹਨਾਂ ਦਾ ਫਾਰਮੈਟ ਚੁਣਨ ਲਈ ਸਮਰੱਥ ਬਣਾਓ ਵਨ ਟਾਈਮ ਸੇਵ ਟਿਕਾਣਾ ਵਨ ਟਾਈਮ ਸੇਵ ਟਿਕਾਣਿਆਂ ਨੂੰ ਦੇਖੋ ਅਤੇ ਸੰਪਾਦਿਤ ਕਰੋ ਜਿਸਦੀ ਵਰਤੋਂ ਤੁਸੀਂ ਜ਼ਿਆਦਾਤਰ ਸਾਰੇ ਵਿਕਲਪਾਂ ਵਿੱਚ ਸੇਵ ਬਟਨ ਨੂੰ ਦਬਾ ਕੇ ਕਰ ਸਕਦੇ ਹੋ ਹਾਲ ਹੀ ਵਿੱਚ ਵਰਤਿਆ ਗਿਆ ਸੀਆਈ ਚੈਨਲ ਸਮੂਹ ਟੈਲੀਗ੍ਰਾਮ 🎉 ਵਿੱਚ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਸਾਡੀ ਚੈਟ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ ਜਿੱਥੇ ਤੁਸੀਂ ਕਿਸੇ ਵੀ ਚੀਜ਼ \'ਤੇ ਚਰਚਾ ਕਰ ਸਕਦੇ ਹੋ ਜੋ ਤੁਸੀਂ ਚਾਹੁੰਦੇ ਹੋ ਅਤੇ CI ਚੈਨਲ ਨੂੰ ਵੀ ਦੇਖ ਸਕਦੇ ਹੋ ਜਿੱਥੇ ਮੈਂ ਬੀਟਾ ਅਤੇ ਘੋਸ਼ਣਾਵਾਂ ਪੋਸਟ ਕਰਦਾ ਹਾਂ ਐਪ ਦੇ ਨਵੇਂ ਸੰਸਕਰਣਾਂ ਬਾਰੇ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਕਰੋ, ਅਤੇ ਘੋਸ਼ਣਾਵਾਂ ਪੜ੍ਹੋ ਇੱਕ ਚਿੱਤਰ ਨੂੰ ਦਿੱਤੇ ਮਾਪਾਂ ਵਿੱਚ ਫਿੱਟ ਕਰੋ ਅਤੇ ਬੈਕਗ੍ਰਾਉਂਡ ਵਿੱਚ ਧੁੰਦਲਾ ਜਾਂ ਰੰਗ ਲਾਗੂ ਕਰੋ ਸੰਦ ਪ੍ਰਬੰਧ ਕਿਸਮ ਦੇ ਅਨੁਸਾਰ ਸਮੂਹ ਟੂਲ ਇੱਕ ਕਸਟਮ ਸੂਚੀ ਪ੍ਰਬੰਧ ਦੀ ਬਜਾਏ ਉਹਨਾਂ ਦੀ ਕਿਸਮ ਦੁਆਰਾ ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਸਮੂਹ ਟੂਲਸ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਮੁੱਲ ਸਿਸਟਮ ਬਾਰਾਂ ਦੀ ਦਿੱਖ ਸਵਾਈਪ ਦੁਆਰਾ ਸਿਸਟਮ ਬਾਰ ਦਿਖਾਓ ਸਿਸਟਮ ਬਾਰਾਂ ਨੂੰ ਦਿਖਾਉਣ ਲਈ ਸਵਾਈਪਿੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ ਜੇਕਰ ਉਹ ਲੁਕੀਆਂ ਹੋਈਆਂ ਹਨ ਆਟੋ ਸਭ ਲੁਕਾਓ ਸਭ ਦਿਖਾਓ ਨਵ ਬਾਰ ਨੂੰ ਲੁਕਾਓ ਸਥਿਤੀ ਪੱਟੀ ਨੂੰ ਲੁਕਾਓ ਸ਼ੋਰ ਪੈਦਾ ਕਰਨਾ ਪਰਲਿਨ ਜਾਂ ਹੋਰ ਕਿਸਮਾਂ ਵਰਗੇ ਵੱਖੋ-ਵੱਖਰੇ ਸ਼ੋਰ ਪੈਦਾ ਕਰੋ ਬਾਰੰਬਾਰਤਾ ਸ਼ੋਰ ਦੀ ਕਿਸਮ ਰੋਟੇਸ਼ਨ ਦੀ ਕਿਸਮ ਫ੍ਰੈਕਟਲ ਕਿਸਮ ਅਸ਼ਟ ਲਕੁਨਾਰਿਟੀ ਹਾਸਲ ਕਰੋ ਵਜ਼ਨ ਵਾਲੀ ਤਾਕਤ ਪਿੰਗ ਪੋਂਗ ਤਾਕਤ ਦੂਰੀ ਫੰਕਸ਼ਨ ਵਾਪਸੀ ਦੀ ਕਿਸਮ ਜਿਟਰ ਡੋਮੇਨ ਵਾਰਪ ਅਲਾਈਨਮੈਂਟ ਕਸਟਮ ਫਾਈਲ ਨਾਮ ਸਥਾਨ ਅਤੇ ਫਾਈਲ ਨਾਮ ਚੁਣੋ ਜੋ ਮੌਜੂਦਾ ਚਿੱਤਰ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਵਰਤੇ ਜਾਣਗੇ ਕਸਟਮ ਨਾਮ ਦੇ ਨਾਲ ਫੋਲਡਰ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ ਕੋਲਾਜ ਮੇਕਰ 20 ਚਿੱਤਰਾਂ ਤੱਕ ਕੋਲਾਜ ਬਣਾਓ ਕੋਲਾਜ ਦੀ ਕਿਸਮ ਸਥਿਤੀ ਨੂੰ ਅਨੁਕੂਲ ਕਰਨ ਲਈ ਸਵੈਪ, ਮੂਵ ਅਤੇ ਜ਼ੂਮ ਕਰਨ ਲਈ ਚਿੱਤਰ ਨੂੰ ਫੜੀ ਰੱਖੋ ਰੋਟੇਸ਼ਨ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਓ ਦੋ-ਉਂਗਲਾਂ ਦੇ ਇਸ਼ਾਰਿਆਂ ਨਾਲ ਚਿੱਤਰਾਂ ਨੂੰ ਘੁੰਮਾਉਣ ਤੋਂ ਰੋਕਦਾ ਹੈ ਬਾਰਡਰਾਂ \'ਤੇ ਸਨੈਪਿੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ ਮੂਵ ਜਾਂ ਜ਼ੂਮ ਕਰਨ ਤੋਂ ਬਾਅਦ, ਚਿੱਤਰ ਫਰੇਮ ਦੇ ਕਿਨਾਰਿਆਂ ਨੂੰ ਭਰਨ ਲਈ ਸਨੈਪ ਹੋਣਗੇ ਹਿਸਟੋਗ੍ਰਾਮ ਵਿਵਸਥਾਵਾਂ ਕਰਨ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਲਈ RGB ਜਾਂ ਬ੍ਰਾਈਟਨੈੱਸ ਚਿੱਤਰ ਹਿਸਟੋਗ੍ਰਾਮ ਇਹ ਚਿੱਤਰ RGB ਅਤੇ ਚਮਕ ਹਿਸਟੋਗ੍ਰਾਮ ਬਣਾਉਣ ਲਈ ਵਰਤਿਆ ਜਾਵੇਗਾ ਟੈਸਰੈਕਟ ਵਿਕਲਪ ਟੈਸਰੈਕਟ ਇੰਜਣ ਲਈ ਕੁਝ ਇਨਪੁਟ ਵੇਰੀਏਬਲ ਲਾਗੂ ਕਰੋ ਕਸਟਮ ਵਿਕਲਪ ਵਿਕਲਪਾਂ ਨੂੰ ਇਸ ਪੈਟਰਨ ਦੇ ਬਾਅਦ ਦਾਖਲ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ: \"--{option_name} {value}\" ਆਟੋ ਕ੍ਰੌਪ ਮੁਫਤ ਕੋਨੇ ਬਹੁਭੁਜ ਦੁਆਰਾ ਚਿੱਤਰ ਨੂੰ ਕੱਟੋ, ਇਹ ਦ੍ਰਿਸ਼ਟੀਕੋਣ ਨੂੰ ਵੀ ਠੀਕ ਕਰਦਾ ਹੈ ਚਿੱਤਰ ਸੀਮਾਵਾਂ ਲਈ ਜ਼ੋਰ ਪੁਆਇੰਟ ਪੁਆਇੰਟ ਚਿੱਤਰ ਸੀਮਾਵਾਂ ਦੁਆਰਾ ਸੀਮਿਤ ਨਹੀਂ ਹੋਣਗੇ, ਇਹ ਵਧੇਰੇ ਸਟੀਕ ਦ੍ਰਿਸ਼ਟੀਕੋਣ ਸੁਧਾਰ ਲਈ ਉਪਯੋਗੀ ਹੈ ਮਾਸਕ ਤਿਆਰ ਕੀਤੇ ਮਾਰਗ ਦੇ ਹੇਠਾਂ ਸਮੱਗਰੀ ਜਾਗਰੂਕਤਾ ਭਰੋ ਹੀਲ ਸਪਾਟ ਸਰਕਲ ਕਰਨਲ ਦੀ ਵਰਤੋਂ ਕਰੋ ਖੁੱਲ ਰਿਹਾ ਹੈ ਬੰਦ ਹੋ ਰਿਹਾ ਹੈ ਰੂਪ ਵਿਗਿਆਨਿਕ ਗਰੇਡੀਐਂਟ ਸਿਖਰ ਦੀ ਟੋਪੀ ਕਾਲੀ ਟੋਪੀ ਟੋਨ ਕਰਵਜ਼ ਕਰਵ ਰੀਸੈੱਟ ਕਰੋ ਕਰਵ ਨੂੰ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਮੁੱਲ \'ਤੇ ਰੋਲ ਕੀਤਾ ਜਾਵੇਗਾ ਲਾਈਨ ਸ਼ੈਲੀ ਗੈਪ ਦਾ ਆਕਾਰ ਡੈਸ਼ਡ ਡੌਟ ਡੈਸ਼ਡ ਮੋਹਰ ਲਗਾਈ ਜਿਗਜ਼ੈਗ ਨਿਸ਼ਚਿਤ ਅੰਤਰ ਆਕਾਰ ਦੇ ਨਾਲ ਖਿੱਚੇ ਮਾਰਗ ਦੇ ਨਾਲ ਡੈਸ਼ਡ ਲਾਈਨ ਖਿੱਚਦਾ ਹੈ ਦਿੱਤੇ ਮਾਰਗ ਦੇ ਨਾਲ ਬਿੰਦੀ ਅਤੇ ਡੈਸ਼ਡ ਲਾਈਨ ਖਿੱਚਦਾ ਹੈ ਸਿਰਫ਼ ਡਿਫੌਲਟ ਸਿੱਧੀਆਂ ਲਾਈਨਾਂ ਨਿਰਧਾਰਤ ਸਪੇਸਿੰਗ ਦੇ ਨਾਲ ਮਾਰਗ ਦੇ ਨਾਲ ਚੁਣੀਆਂ ਹੋਈਆਂ ਆਕਾਰਾਂ ਨੂੰ ਖਿੱਚਦਾ ਹੈ ਮਾਰਗ ਦੇ ਨਾਲ ਲਹਿਰਦਾਰ ਜ਼ਿਗਜ਼ੈਗ ਖਿੱਚਦਾ ਹੈ ਜ਼ਿਗਜ਼ੈਗ ਅਨੁਪਾਤ ਸ਼ਾਰਟਕੱਟ ਬਣਾਓ ਪਿੰਨ ਕਰਨ ਲਈ ਟੂਲ ਚੁਣੋ ਟੂਲ ਨੂੰ ਤੁਹਾਡੇ ਲਾਂਚਰ ਦੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਰਟਕੱਟ ਦੇ ਤੌਰ \'ਤੇ ਜੋੜਿਆ ਜਾਵੇਗਾ, ਲੋੜੀਂਦੇ ਵਿਹਾਰ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਇਸਨੂੰ \"ਫਾਇਲ ਚੁਣਨਾ ਛੱਡੋ\" ਸੈਟਿੰਗ ਦੇ ਨਾਲ ਜੋੜ ਕੇ ਵਰਤੋ। ਫਰੇਮਾਂ ਨੂੰ ਸਟੈਕ ਨਾ ਕਰੋ ਪਿਛਲੇ ਫਰੇਮਾਂ ਦੇ ਨਿਪਟਾਰੇ ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਂਦਾ ਹੈ, ਇਸਲਈ ਉਹ ਇੱਕ ਦੂਜੇ \'ਤੇ ਸਟੈਕ ਨਹੀਂ ਕਰਨਗੇ ਕਰਾਸਫੇਡ ਫਰੇਮ ਇੱਕ ਦੂਜੇ ਵਿੱਚ ਕ੍ਰਾਸਫੇਡ ਕੀਤੇ ਜਾਣਗੇ ਕਰਾਸਫੇਡ ਫਰੇਮਾਂ ਦੀ ਗਿਣਤੀ ਥ੍ਰੈਸ਼ਹੋਲਡ ਇੱਕ ਥ੍ਰੈਸ਼ਹੋਲਡ ਦੋ ਕੈਨੀ ਮਿਰਰ 101 ਵਿਸਤ੍ਰਿਤ ਜ਼ੂਮ ਬਲਰ ਲੈਪਲੇਸ਼ੀਅਨ ਸਧਾਰਨ ਸੋਬਲ ਸਧਾਰਨ ਸਹਾਇਕ ਗਰਿੱਡ ਸਹੀ ਹੇਰਾਫੇਰੀ ਵਿੱਚ ਮਦਦ ਕਰਨ ਲਈ ਡਰਾਇੰਗ ਖੇਤਰ ਦੇ ਉੱਪਰ ਸਹਾਇਕ ਗਰਿੱਡ ਦਿਖਾਉਂਦਾ ਹੈ ਗਰਿੱਡ ਦਾ ਰੰਗ ਸੈੱਲ ਚੌੜਾਈ ਸੈੱਲ ਦੀ ਉਚਾਈ ਸੰਖੇਪ ਚੋਣਕਾਰ ਕੁਝ ਚੋਣ ਨਿਯੰਤਰਣ ਘੱਟ ਜਗ੍ਹਾ ਲੈਣ ਲਈ ਇੱਕ ਸੰਖੇਪ ਖਾਕੇ ਦੀ ਵਰਤੋਂ ਕਰਨਗੇ ਚਿੱਤਰ ਨੂੰ ਕੈਪਚਰ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਕੈਮਰੇ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ ਖਾਕਾ ਮੁੱਖ ਸਕ੍ਰੀਨ ਸਿਰਲੇਖ ਸਥਿਰ ਦਰ ਕਾਰਕ (CRF) %1$s ਦੇ ਮੁੱਲ ਦਾ ਅਰਥ ਹੈ ਇੱਕ ਹੌਲੀ ਸੰਕੁਚਨ, ਨਤੀਜੇ ਵਜੋਂ ਇੱਕ ਮੁਕਾਬਲਤਨ ਛੋਟਾ ਫਾਈਲ ਆਕਾਰ। %2$s ਦਾ ਅਰਥ ਹੈ ਇੱਕ ਤੇਜ਼ ਕੰਪਰੈਸ਼ਨ, ਨਤੀਜੇ ਵਜੋਂ ਇੱਕ ਵੱਡੀ ਫਾਈਲ। ਲੂਟ ਲਾਇਬ੍ਰੇਰੀ LUTs ਦਾ ਸੰਗ੍ਰਹਿ ਡਾਊਨਲੋਡ ਕਰੋ, ਜਿਸ ਨੂੰ ਤੁਸੀਂ ਡਾਊਨਲੋਡ ਕਰਨ ਤੋਂ ਬਾਅਦ ਅਰਜ਼ੀ ਦੇ ਸਕਦੇ ਹੋ LUTs ਦਾ ਸੰਗ੍ਰਹਿ ਅੱਪਡੇਟ ਕਰੋ (ਸਿਰਫ਼ ਨਵੇਂ ਕਤਾਰਬੱਧ ਹੋਣਗੇ), ਜਿਸ ਨੂੰ ਤੁਸੀਂ ਡਾਊਨਲੋਡ ਕਰਨ ਤੋਂ ਬਾਅਦ ਅਰਜ਼ੀ ਦੇ ਸਕਦੇ ਹੋ ਫਿਲਟਰਾਂ ਲਈ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਚਿੱਤਰ ਝਲਕ ਨੂੰ ਬਦਲੋ ਚਿੱਤਰ ਦੀ ਝਲਕ ਓਹਲੇ ਦਿਖਾਓ ਸਲਾਈਡਰ ਦੀ ਕਿਸਮ ਫੈਂਸੀ ਸਮੱਗਰੀ 2 ਇੱਕ ਸ਼ਾਨਦਾਰ ਦਿੱਖ ਵਾਲਾ ਸਲਾਈਡਰ। ਇਹ ਡਿਫਾਲਟ ਵਿਕਲਪ ਹੈ ਇੱਕ ਸਮੱਗਰੀ 2 ਸਲਾਈਡਰ ਇੱਕ ਸਮੱਗਰੀ ਜੋ ਤੁਸੀਂ ਸਲਾਈਡਰ ਕਰਦੇ ਹੋ ਲਾਗੂ ਕਰੋ ਸੈਂਟਰ ਡਾਇਲਾਗ ਬਟਨ ਜੇਕਰ ਸੰਭਵ ਹੋਵੇ ਤਾਂ ਡਾਇਲਾਗਸ ਦੇ ਬਟਨ ਖੱਬੇ ਪਾਸੇ ਦੀ ਬਜਾਏ ਕੇਂਦਰ ਵਿੱਚ ਰੱਖੇ ਜਾਣਗੇ ਓਪਨ ਸੋਰਸ ਲਾਇਸੰਸ ਇਸ ਐਪ ਵਿੱਚ ਵਰਤੀਆਂ ਗਈਆਂ ਓਪਨ ਸੋਰਸ ਲਾਇਬ੍ਰੇਰੀਆਂ ਦੇ ਲਾਇਸੰਸ ਦੇਖੋ ਖੇਤਰ ਪਿਕਸਲ ਏਰੀਆ ਰਿਲੇਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਰੀਸੈਪਲਿੰਗ। ਇਹ ਚਿੱਤਰ ਨੂੰ ਖਤਮ ਕਰਨ ਲਈ ਇੱਕ ਤਰਜੀਹੀ ਢੰਗ ਹੋ ਸਕਦਾ ਹੈ, ਕਿਉਂਕਿ ਇਹ ਮੋਇਰ\'-ਮੁਕਤ ਨਤੀਜੇ ਦਿੰਦਾ ਹੈ। ਪਰ ਜਦੋਂ ਚਿੱਤਰ ਨੂੰ ਜ਼ੂਮ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਇਹ \"ਨੇੜਲੇ\" ਵਿਧੀ ਦੇ ਸਮਾਨ ਹੁੰਦਾ ਹੈ। ਟੋਨਮੈਪਿੰਗ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ % ਦਰਜ ਕਰੋ ਸਾਈਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ, VPN ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜਾਂ ਜਾਂਚ ਕਰੋ ਕਿ ਕੀ url ਸਹੀ ਹੈ ਮਾਰਕਅੱਪ ਲੇਅਰਸ ਤਸਵੀਰਾਂ, ਟੈਕਸਟ ਅਤੇ ਹੋਰ ਚੀਜ਼ਾਂ ਨੂੰ ਸੁਤੰਤਰ ਤੌਰ \'ਤੇ ਰੱਖਣ ਦੀ ਸਮਰੱਥਾ ਵਾਲਾ ਲੇਅਰ ਮੋਡ ਪਰਤ ਦਾ ਸੰਪਾਦਨ ਕਰੋ ਚਿੱਤਰ \'ਤੇ ਪਰਤਾਂ ਇੱਕ ਬੈਕਗ੍ਰਾਉਂਡ ਦੇ ਤੌਰ ਤੇ ਇੱਕ ਚਿੱਤਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਅਤੇ ਇਸਦੇ ਸਿਖਰ \'ਤੇ ਵੱਖ-ਵੱਖ ਪਰਤਾਂ ਜੋੜੋ ਪਿਛੋਕੜ \'ਤੇ ਪਰਤਾਂ ਪਹਿਲੇ ਵਿਕਲਪ ਵਾਂਗ ਹੀ ਪਰ ਚਿੱਤਰ ਦੀ ਬਜਾਏ ਰੰਗ ਨਾਲ ਬੀਟਾ ਤੇਜ਼ ਸੈਟਿੰਗ ਸਾਈਡ ਚਿੱਤਰਾਂ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰਦੇ ਸਮੇਂ ਚੁਣੇ ਹੋਏ ਪਾਸੇ \'ਤੇ ਫਲੋਟਿੰਗ ਸਟ੍ਰਿਪ ਸ਼ਾਮਲ ਕਰੋ, ਜੋ ਕਿ ਕਲਿੱਕ ਕਰਨ \'ਤੇ ਤੇਜ਼ ਸੈਟਿੰਗਾਂ ਨੂੰ ਖੋਲ੍ਹ ਦੇਵੇਗੀ ਚੋਣ ਸਾਫ਼ ਕਰੋ ਸੈੱਟਿੰਗ ਗਰੁੱਪ \"%1$s\" ਨੂੰ ਮੂਲ ਰੂਪ ਵਿੱਚ ਸਮੇਟਿਆ ਜਾਵੇਗਾ ਸੈੱਟਿੰਗ ਗਰੁੱਪ \"%1$s\" ਨੂੰ ਮੂਲ ਰੂਪ ਵਿੱਚ ਵਿਸਤਾਰ ਕੀਤਾ ਜਾਵੇਗਾ ਬੇਸ 64 ਟੂਲ ਬੇਸ 64 ਸਟ੍ਰਿੰਗ ਨੂੰ ਚਿੱਤਰ ਵਿੱਚ ਡੀਕੋਡ ਕਰੋ, ਜਾਂ ਚਿੱਤਰ ਨੂੰ ਬੇਸ64 ਫਾਰਮੈਟ ਵਿੱਚ ਏਨਕੋਡ ਕਰੋ ਬੇਸ 64 ਪ੍ਰਦਾਨ ਕੀਤਾ ਮੁੱਲ ਇੱਕ ਵੈਧ Base64 ਸਤਰ ਨਹੀਂ ਹੈ ਖਾਲੀ ਜਾਂ ਅਵੈਧ Base64 ਸਤਰ ਦੀ ਨਕਲ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ ਬੇਸ 64 ਪੇਸਟ ਕਰੋ ਬੇਸ 64 ਕਾਪੀ ਕਰੋ ਬੇਸ 64 ਸਟ੍ਰਿੰਗ ਨੂੰ ਕਾਪੀ ਜਾਂ ਸੇਵ ਕਰਨ ਲਈ ਚਿੱਤਰ ਲੋਡ ਕਰੋ। ਜੇਕਰ ਤੁਹਾਡੇ ਕੋਲ ਸਟ੍ਰਿੰਗ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਚਿੱਤਰ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਇਸਨੂੰ ਉੱਪਰ ਪੇਸਟ ਕਰ ਸਕਦੇ ਹੋ ਬੇਸ 64 ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਸ਼ੇਅਰ ਬੇਸ 64 ਵਿਕਲਪ ਕਾਰਵਾਈਆਂ ਬੇਸ 64 ਆਯਾਤ ਕਰੋ ਬੇਸ 64 ਕਾਰਵਾਈਆਂ ਰੂਪਰੇਖਾ ਸ਼ਾਮਲ ਕਰੋ ਖਾਸ ਰੰਗ ਅਤੇ ਚੌੜਾਈ ਦੇ ਨਾਲ ਟੈਕਸਟ ਦੇ ਆਲੇ-ਦੁਆਲੇ ਰੂਪਰੇਖਾ ਜੋੜੋ ਰੂਪਰੇਖਾ ਰੰਗ ਰੂਪਰੇਖਾ ਦਾ ਆਕਾਰ ਰੋਟੇਸ਼ਨ ਫਾਈਲ ਨਾਮ ਵਜੋਂ ਚੈੱਕਸਮ ਆਉਟਪੁੱਟ ਚਿੱਤਰਾਂ ਦਾ ਨਾਮ ਉਹਨਾਂ ਦੇ ਡੇਟਾ ਚੈੱਕਸਮ ਦੇ ਅਨੁਸਾਰੀ ਹੋਵੇਗਾ ਮੁਫਤ ਸਾਫਟਵੇਅਰ (ਸਾਥੀ) ਐਂਡਰਾਇਡ ਐਪਲੀਕੇਸ਼ਨਾਂ ਦੇ ਸਹਿਭਾਗੀ ਚੈਨਲ ਵਿੱਚ ਵਧੇਰੇ ਉਪਯੋਗੀ ਸੌਫਟਵੇਅਰ ਐਲਗੋਰਿਦਮ ਚੈੱਕਸਮ ਟੂਲਜ਼ ਵੱਖੋ-ਵੱਖਰੇ ਹੈਸ਼ਿੰਗ ਐਲਗੋਰਿਦਮ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਚੈੱਕਸਮ ਦੀ ਤੁਲਨਾ ਕਰੋ, ਹੈਸ਼ਾਂ ਦੀ ਗਣਨਾ ਕਰੋ ਜਾਂ ਫਾਈਲਾਂ ਤੋਂ ਹੈਕਸ ਸਤਰ ਬਣਾਓ ਗਣਨਾ ਕਰੋ ਟੈਕਸਟ ਹੈਸ਼ ਚੈੱਕਸਮ ਚੁਣੇ ਹੋਏ ਐਲਗੋਰਿਦਮ ਦੇ ਆਧਾਰ \'ਤੇ ਇਸ ਦੇ ਚੈਕਸਮ ਦੀ ਗਣਨਾ ਕਰਨ ਲਈ ਫਾਈਲ ਚੁਣੋ ਚੁਣੇ ਹੋਏ ਐਲਗੋਰਿਦਮ ਦੇ ਆਧਾਰ \'ਤੇ ਇਸ ਦੇ ਚੈਕਸਮ ਦੀ ਗਣਨਾ ਕਰਨ ਲਈ ਟੈਕਸਟ ਦਰਜ ਕਰੋ ਸਰੋਤ ਚੈੱਕਸਮ ਤੁਲਨਾ ਕਰਨ ਲਈ ਚੈੱਕਸਮ ਮੈਚ! ਅੰਤਰ ਚੈੱਕਸਮ ਬਰਾਬਰ ਹਨ, ਇਹ ਸੁਰੱਖਿਅਤ ਹੋ ਸਕਦਾ ਹੈ ਚੈੱਕਸਮ ਬਰਾਬਰ ਨਹੀਂ ਹਨ, ਫਾਈਲ ਅਸੁਰੱਖਿਅਤ ਹੋ ਸਕਦੀ ਹੈ! ਜਾਲ ਗਰੇਡੀਐਂਟ Mesh Gradients ਦੇ ਔਨਲਾਈਨ ਸੰਗ੍ਰਹਿ ਨੂੰ ਦੇਖੋ ਸਿਰਫ਼ TTF ਅਤੇ OTF ਫੋਂਟ ਹੀ ਆਯਾਤ ਕੀਤੇ ਜਾ ਸਕਦੇ ਹਨ ਫੌਂਟ ਆਯਾਤ ਕਰੋ (TTF/OTF) ਫੌਂਟ ਨਿਰਯਾਤ ਕਰੋ ਆਯਾਤ ਕੀਤੇ ਫੌਂਟ ਕੋਸ਼ਿਸ਼ ਨੂੰ ਸੰਭਾਲਣ ਦੌਰਾਨ ਗਲਤੀ, ਆਉਟਪੁੱਟ ਫੋਲਡਰ ਨੂੰ ਬਦਲਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਫਾਈਲ ਨਾਮ ਸੈੱਟ ਨਹੀਂ ਹੈ ਕੋਈ ਨਹੀਂ ਕਸਟਮ ਪੰਨੇ ਪੰਨਿਆਂ ਦੀ ਚੋਣ ਟੂਲ ਐਗਜ਼ਿਟ ਪੁਸ਼ਟੀਕਰਨ ਜੇਕਰ ਤੁਹਾਡੇ ਕੋਲ ਖਾਸ ਟੂਲਸ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਅਣ-ਸੰਭਾਲਿਤ ਤਬਦੀਲੀਆਂ ਹਨ ਅਤੇ ਇਸਨੂੰ ਬੰਦ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ, ਤਾਂ ਪੁਸ਼ਟੀ ਕਰੋ ਡਾਇਲਾਗ ਦਿਖਾਇਆ ਜਾਵੇਗਾ EXIF ਸੰਪਾਦਿਤ ਕਰੋ ਰੀਕੰਪਰੇਸ਼ਨ ਤੋਂ ਬਿਨਾਂ ਸਿੰਗਲ ਚਿੱਤਰ ਦਾ ਮੈਟਾਡੇਟਾ ਬਦਲੋ ਉਪਲਬਧ ਟੈਗਾਂ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ ਸਟਿੱਕਰ ਬਦਲੋ ਫਿੱਟ ਚੌੜਾਈ ਫਿੱਟ ਉਚਾਈ ਬੈਚ ਦੀ ਤੁਲਨਾ ਕਰੋ ਚੁਣੇ ਗਏ ਐਲਗੋਰਿਦਮ ਦੇ ਆਧਾਰ \'ਤੇ ਇਸ ਦੇ ਚੈੱਕਸਮ ਦੀ ਗਣਨਾ ਕਰਨ ਲਈ ਫਾਈਲ/ਫਾਈਲਾਂ ਨੂੰ ਚੁਣੋ ਫਾਈਲਾਂ ਚੁਣੋ ਡਾਇਰੈਕਟਰੀ ਚੁਣੋ ਸਿਰ ਦੀ ਲੰਬਾਈ ਦਾ ਪੈਮਾਨਾ ਸਟੈਂਪ ਟਾਈਮਸਟੈਂਪ ਫਾਰਮੈਟ ਪੈਟਰਨ ਪੈਡਿੰਗ ਚਿੱਤਰ ਕੱਟਣਾ ਚਿੱਤਰ ਦੇ ਹਿੱਸੇ ਨੂੰ ਕੱਟੋ ਅਤੇ ਖੱਬੇ ਪਾਸੇ ਨੂੰ ਮਿਲਾਓ (ਉਲਟਾ ਹੋ ਸਕਦਾ ਹੈ) ਲੰਬਕਾਰੀ ਜਾਂ ਖਿਤਿਜੀ ਰੇਖਾਵਾਂ ਦੁਆਰਾ ਲੰਬਕਾਰੀ ਧਰੁਵੀ ਲਾਈਨ ਹਰੀਜ਼ੱਟਲ ਧਰੁਵੀ ਰੇਖਾ ਉਲਟ ਚੋਣ ਕੱਟੇ ਹੋਏ ਖੇਤਰ ਦੇ ਆਲੇ ਦੁਆਲੇ ਹਿੱਸਿਆਂ ਨੂੰ ਮਿਲਾਉਣ ਦੀ ਬਜਾਏ, ਵਰਟੀਕਲ ਕੱਟ ਵਾਲੇ ਹਿੱਸੇ ਨੂੰ ਛੱਡ ਦਿੱਤਾ ਜਾਵੇਗਾ ਕੱਟੇ ਹੋਏ ਖੇਤਰ ਦੇ ਆਲੇ ਦੁਆਲੇ ਭਾਗਾਂ ਨੂੰ ਮਿਲਾਉਣ ਦੀ ਬਜਾਏ, ਹਰੀਜੱਟਲ ਕੱਟ ਵਾਲੇ ਹਿੱਸੇ ਨੂੰ ਛੱਡ ਦਿੱਤਾ ਜਾਵੇਗਾ ਜਾਲ ਗਰੇਡੀਐਂਟਸ ਦਾ ਸੰਗ੍ਰਹਿ ਗੰਢਾਂ ਅਤੇ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਦੀ ਕਸਟਮ ਮਾਤਰਾ ਨਾਲ ਜਾਲ ਗਰੇਡੀਐਂਟ ਬਣਾਓ ਜਾਲ ਗਰੇਡੀਐਂਟ ਓਵਰਲੇ ਦਿੱਤੇ ਚਿੱਤਰਾਂ ਦੇ ਸਿਖਰ ਦਾ ਜਾਲ ਗਰੇਡੀਐਂਟ ਲਿਖੋ ਪੁਆਇੰਟ ਕਸਟਮਾਈਜ਼ੇਸ਼ਨ ਗਰਿੱਡ ਦਾ ਆਕਾਰ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਐਕਸ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਵਾਈ ਮਤਾ Pixel by Pixel ਹਾਈਲਾਈਟ ਰੰਗ Pixel ਤੁਲਨਾ ਦੀ ਕਿਸਮ ਬਾਰਕੋਡ ਸਕੈਨ ਕਰੋ ਉਚਾਈ ਅਨੁਪਾਤ ਬਾਰਕੋਡ ਦੀ ਕਿਸਮ B/W ਲਾਗੂ ਕਰੋ ਬਾਰਕੋਡ ਚਿੱਤਰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਕਾਲਾ ਅਤੇ ਚਿੱਟਾ ਹੋਵੇਗਾ ਅਤੇ ਐਪ ਦੇ ਥੀਮ ਦੁਆਰਾ ਰੰਗੀਨ ਨਹੀਂ ਹੋਵੇਗਾ ਕਿਸੇ ਵੀ ਬਾਰਕੋਡ (QR, EAN, AZTEC, …) ਨੂੰ ਸਕੈਨ ਕਰੋ ਅਤੇ ਇਸਦੀ ਸਮੱਗਰੀ ਪ੍ਰਾਪਤ ਕਰੋ ਜਾਂ ਨਵਾਂ ਬਣਾਉਣ ਲਈ ਆਪਣਾ ਟੈਕਸਟ ਪੇਸਟ ਕਰੋ ਕੋਈ ਬਾਰਕੋਡ ਨਹੀਂ ਮਿਲਿਆ ਤਿਆਰ ਕੀਤਾ ਬਾਰਕੋਡ ਇੱਥੇ ਹੋਵੇਗਾ ਆਡੀਓ ਕਵਰ ਆਡੀਓ ਫਾਈਲਾਂ ਤੋਂ ਐਲਬਮ ਕਵਰ ਚਿੱਤਰਾਂ ਨੂੰ ਐਕਸਟਰੈਕਟ ਕਰੋ, ਸਭ ਤੋਂ ਆਮ ਫਾਰਮੈਟ ਸਮਰਥਿਤ ਹਨ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਆਡੀਓ ਚੁਣੋ ਆਡੀਓ ਚੁਣੋ ਕੋਈ ਕਵਰ ਨਹੀਂ ਮਿਲੇ ਲੌਗ ਭੇਜੋ ਐਪ ਲੌਗਸ ਫਾਈਲ ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ, ਇਹ ਸਮੱਸਿਆ ਨੂੰ ਲੱਭਣ ਅਤੇ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਹੱਲ ਕਰਨ ਵਿੱਚ ਮੇਰੀ ਮਦਦ ਕਰ ਸਕਦਾ ਹੈ ਓਹੋ… ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ ਤੁਸੀਂ ਹੇਠਾਂ ਦਿੱਤੇ ਵਿਕਲਪਾਂ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਮੇਰੇ ਨਾਲ ਸੰਪਰਕ ਕਰ ਸਕਦੇ ਹੋ ਅਤੇ ਮੈਂ ਹੱਲ ਲੱਭਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰਾਂਗਾ।\n(ਲੌਗ ਨੱਥੀ ਕਰਨਾ ਨਾ ਭੁੱਲੋ) ਫਾਈਲ ਵਿੱਚ ਲਿਖੋ ਚਿੱਤਰਾਂ ਦੇ ਬੈਚ ਤੋਂ ਟੈਕਸਟ ਐਕਸਟਰੈਕਟ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਇੱਕ ਟੈਕਸਟ ਫਾਈਲ ਵਿੱਚ ਸਟੋਰ ਕਰੋ ਮੈਟਾਡੇਟਾ \'ਤੇ ਲਿਖੋ ਹਰੇਕ ਚਿੱਤਰ ਤੋਂ ਟੈਕਸਟ ਐਕਸਟਰੈਕਟ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਸੰਬੰਧਿਤ ਫੋਟੋਆਂ ਦੀ EXIF ​​​​ਜਾਣਕਾਰੀ ਵਿੱਚ ਰੱਖੋ ਅਦਿੱਖ ਮੋਡ ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਦੇ ਬਾਈਟਾਂ ਦੇ ਅੰਦਰ ਅੱਖਾਂ ਦੇ ਅਦਿੱਖ ਵਾਟਰਮਾਰਕ ਬਣਾਉਣ ਲਈ ਸਟੈਗਨੋਗ੍ਰਾਫੀ ਦੀ ਵਰਤੋਂ ਕਰੋ LSB ਦੀ ਵਰਤੋਂ ਕਰੋ LSB (ਘੱਟ ਮਹੱਤਵਪੂਰਨ ਬਿੱਟ) ਸਟੈਗਨੋਗ੍ਰਾਫੀ ਵਿਧੀ ਵਰਤੀ ਜਾਵੇਗੀ, ਨਹੀਂ ਤਾਂ FD (ਫ੍ਰੀਕੁਐਂਸੀ ਡੋਮੇਨ) ਲਾਲ ਅੱਖਾਂ ਨੂੰ ਆਟੋ ਹਟਾਓ ਪਾਸਵਰਡ ਅਨਲੌਕ ਕਰੋ PDF ਸੁਰੱਖਿਅਤ ਹੈ ਓਪਰੇਸ਼ਨ ਲਗਭਗ ਪੂਰਾ ਹੋ ਗਿਆ ਹੈ। ਹੁਣੇ ਰੱਦ ਕਰਨ ਲਈ ਇਸਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇਗੀ ਸੰਸ਼ੋਧਿਤ ਮਿਤੀ ਸੰਸ਼ੋਧਿਤ ਮਿਤੀ (ਉਲਟ) ਆਕਾਰ ਆਕਾਰ (ਉਲਟ) MIME ਕਿਸਮ MIME ਕਿਸਮ (ਉਲਟ) ਐਕਸਟੈਂਸ਼ਨ ਐਕਸਟੈਂਸ਼ਨ (ਉਲਟ) ਜੋੜੀ ਗਈ ਮਿਤੀ ਜੋੜੀ ਗਈ ਮਿਤੀ (ਉਲਟ) ਖੱਬੇ ਤੋਂ ਸੱਜੇ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਉੱਪਰ ਤੋਂ ਹੇਠਾਂ ਤੱਕ ਹੇਠਾਂ ਤੋਂ ਸਿਖਰ ਤੱਕ ਤਰਲ ਗਲਾਸ ਹਾਲ ਹੀ ਵਿੱਚ ਘੋਸ਼ਿਤ ਆਈਓਐਸ 26 ਅਤੇ ਇਸ ਦੇ ਤਰਲ ਗਲਾਸ ਡਿਜ਼ਾਈਨ ਸਿਸਟਮ \'ਤੇ ਅਧਾਰਤ ਇੱਕ ਸਵਿੱਚ ਚਿੱਤਰ ਚੁਣੋ ਜਾਂ ਹੇਠਾਂ ਬੇਸ64 ਡੇਟਾ ਪੇਸਟ/ਆਯਾਤ ਕਰੋ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਚਿੱਤਰ ਲਿੰਕ ਟਾਈਪ ਕਰੋ ਲਿੰਕ ਪੇਸਟ ਕਰੋ ਕੈਲੀਡੋਸਕੋਪ ਸੈਕੰਡਰੀ ਕੋਣ ਪਾਸੇ ਚੈਨਲ ਮਿਕਸ ਨੀਲਾ ਹਰਾ ਲਾਲ ਨੀਲਾ ਹਰਾ ਲਾਲ ਲਾਲ ਵਿੱਚ ਹਰੇ ਵਿੱਚ ਨੀਲੇ ਵਿੱਚ ਸਿਆਨ ਮੈਜੈਂਟਾ ਪੀਲਾ ਰੰਗ ਹਾਫਟੋਨ ਕੰਟੋਰ ਪੱਧਰ ਆਫਸੈੱਟ ਵੋਰੋਨੋਈ ਕ੍ਰਿਸਟਲਾਈਜ਼ ਆਕਾਰ ਖਿੱਚੋ ਬੇਤਰਤੀਬਤਾ ਡੀਸਪੈਕਲ ਫੈਲਣਾ DoG ਦੂਜਾ ਘੇਰਾ ਬਰਾਬਰ ਕਰੋ ਗਲੋ ਚੱਕਰ ਅਤੇ ਚੂੰਡੀ Pointillize ਬਾਰਡਰ ਰੰਗ ਪੋਲਰ ਕੋਆਰਡੀਨੇਟਸ ਧਰੁਵੀ ਵੱਲ ਮੁੜੋ ਠੀਕ ਕਰਨ ਲਈ ਧਰੁਵੀ ਚੱਕਰ ਵਿੱਚ ਉਲਟ ਸ਼ੋਰ ਘਟਾਓ ਸਧਾਰਨ ਸੋਲਰਾਈਜ਼ ਬੁਣਾਈ ਐਕਸ ਗੈਪ ਵਾਈ ਗੈਪ X ਚੌੜਾਈ Y ਚੌੜਾਈ ਘੁੰਮਣਾ ਰਬੜ ਦੀ ਮੋਹਰ ਸਮੀਅਰ ਘਣਤਾ ਮਿਕਸ ਗੋਲਾਕਾਰ ਲੈਂਸ ਵਿਗਾੜ ਅਪਵਰਤਨ ਸੂਚਕਾਂਕ ਚਾਪ ਫੈਲਾਓ ਕੋਣ ਚਮਕ ਕਿਰਨਾਂ ASCII ਗਰੇਡੀਐਂਟ ਮੈਰੀ ਪਤਝੜ ਹੱਡੀ ਜੈੱਟ ਸਰਦੀਆਂ ਸਾਗਰ ਗਰਮੀਆਂ ਬਸੰਤ ਠੰਡਾ ਵੇਰੀਐਂਟ HSV ਗੁਲਾਬੀ ਗਰਮ ਸ਼ਬਦ ਮੈਗਮਾ ਇਨਫਰਨੋ ਪਲਾਜ਼ਮਾ ਵਿਰਿਡਿਸ ਨਾਗਰਿਕ ਸੰਧਿਆ ਟਵਾਈਲਾਈਟ ਸ਼ਿਫਟ ਹੋ ਗਈ ਪਰਸਪੈਕਟਿਵ ਆਟੋ ਡੈਸਕਿਊ ਫਸਲ ਦੀ ਆਗਿਆ ਦਿਓ ਫਸਲ ਜਾਂ ਦ੍ਰਿਸ਼ਟੀਕੋਣ ਸੰਪੂਰਨ ਟਰਬੋ ਡੂੰਘੇ ਹਰੇ ਲੈਂਸ ਸੁਧਾਰ JSON ਫਾਰਮੈਟ ਵਿੱਚ ਟਾਰਗੇਟ ਲੈਂਸ ਪ੍ਰੋਫਾਈਲ ਫ਼ਾਈਲ ਤਿਆਰ ਲੈਂਸ ਪ੍ਰੋਫਾਈਲਾਂ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੋ ਭਾਗ ਪ੍ਰਤੀਸ਼ਤ JSON ਵਜੋਂ ਨਿਰਯਾਤ ਕਰੋ json ਨੁਮਾਇੰਦਗੀ ਦੇ ਤੌਰ \'ਤੇ ਪੈਲੇਟ ਡੇਟਾ ਨਾਲ ਸਤਰ ਨੂੰ ਕਾਪੀ ਕਰੋ ਸੀਮ ਕਾਰਵਿੰਗ ਹੋਮ ਸਕ੍ਰੀਨ ਲਾਕ ਸਕ੍ਰੀਨ ਬਿਲਟ-ਇਨ ਵਾਲਪੇਪਰ ਨਿਰਯਾਤ ਤਾਜ਼ਾ ਕਰੋ ਮੌਜੂਦਾ ਘਰ, ਲਾਕ ਅਤੇ ਬਿਲਟ-ਇਨ ਵਾਲਪੇਪਰ ਪ੍ਰਾਪਤ ਕਰੋ ਸਾਰੀਆਂ ਫ਼ਾਈਲਾਂ ਤੱਕ ਪਹੁੰਚ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ, ਵਾਲਪੇਪਰਾਂ ਨੂੰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਇਸਦੀ ਲੋੜ ਹੈ ਬਾਹਰੀ ਸਟੋਰੇਜ ਦੀ ਇਜਾਜ਼ਤ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨਾ ਕਾਫ਼ੀ ਨਹੀਂ ਹੈ, ਤੁਹਾਨੂੰ ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਤੱਕ ਪਹੁੰਚ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣ ਦੀ ਲੋੜ ਹੈ, \"ਸਭ ਨੂੰ ਇਜਾਜ਼ਤ ਦਿਓ\" ਨੂੰ ਚੁਣਨਾ ਯਕੀਨੀ ਬਣਾਓ। ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਪ੍ਰੀਸੈਟ ਸ਼ਾਮਲ ਕਰੋ ਚਿੱਤਰ ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਚੁਣੇ ਹੋਏ ਪ੍ਰੀਸੈਟ ਨਾਲ ਪਿਛੇਤਰ ਜੋੜਦਾ ਹੈ ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਚਿੱਤਰ ਸਕੇਲ ਮੋਡ ਸ਼ਾਮਲ ਕਰੋ ਚੁਣੇ ਹੋਏ ਚਿੱਤਰ ਸਕੇਲ ਮੋਡ ਦੇ ਨਾਲ ਚਿੱਤਰ ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਪਿਛੇਤਰ ਜੋੜਦਾ ਹੈ Ascii ਕਲਾ ਤਸਵੀਰ ਨੂੰ ascii ਟੈਕਸਟ ਵਿੱਚ ਬਦਲੋ ਜੋ ਚਿੱਤਰ ਵਰਗਾ ਦਿਖਾਈ ਦੇਵੇਗਾ ਪਰਮ ਕੁਝ ਮਾਮਲਿਆਂ ਵਿੱਚ ਬਿਹਤਰ ਨਤੀਜੇ ਲਈ ਚਿੱਤਰ \'ਤੇ ਨਕਾਰਾਤਮਕ ਫਿਲਟਰ ਲਾਗੂ ਕਰਦਾ ਹੈ ਸਕ੍ਰੀਨਸ਼ੌਟ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ ਸਕ੍ਰੀਨਸ਼ੌਟ ਕੈਪਚਰ ਨਹੀਂ ਕੀਤਾ ਗਿਆ, ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਸੁਰੱਖਿਅਤ ਕਰਨਾ ਛੱਡਿਆ ਗਿਆ %1$s ਫਾਈਲਾਂ ਛੱਡੀਆਂ ਗਈਆਂ ਜੇਕਰ ਵੱਡਾ ਹੋਵੇ ਤਾਂ ਛੱਡਣ ਦਿਓ ਕੁਝ ਟੂਲਸ ਨੂੰ ਚਿੱਤਰਾਂ ਨੂੰ ਸੇਵ ਕਰਨਾ ਛੱਡਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਵੇਗੀ ਜੇਕਰ ਨਤੀਜੇ ਵਜੋਂ ਫਾਈਲ ਦਾ ਆਕਾਰ ਅਸਲ ਤੋਂ ਵੱਡਾ ਹੋਵੇਗਾ ਕੈਲੰਡਰ ਇਵੈਂਟ ਸੰਪਰਕ ਕਰੋ ਈਮੇਲ ਟਿਕਾਣਾ ਫ਼ੋਨ ਟੈਕਸਟ SMS URL ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਖੋਲ੍ਹੋ N/A SSID ਫ਼ੋਨ ਸੁਨੇਹਾ ਪਤਾ ਵਿਸ਼ਾ ਸਰੀਰ ਨਾਮ ਸੰਗਠਨ ਸਿਰਲੇਖ ਫ਼ੋਨ ਈਮੇਲਾਂ URLs ਪਤੇ ਸੰਖੇਪ ਵਰਣਨ ਟਿਕਾਣਾ ਆਯੋਜਕ ਤਾਰੀਖ ਸ਼ੁਰੂ ਸਮਾਪਤੀ ਮਿਤੀ ਸਥਿਤੀ ਵਿਥਕਾਰ ਲੰਬਕਾਰ ਬਾਰਕੋਡ ਬਣਾਓ ਬਾਰਕੋਡ ਦਾ ਸੰਪਾਦਨ ਕਰੋ Wi-Fi ਸੰਰਚਨਾ ਸੁਰੱਖਿਆ ਸੰਪਰਕ ਚੁਣੋ ਚੁਣੇ ਗਏ ਸੰਪਰਕ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਆਟੋਫਿਲ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਸੰਪਰਕਾਂ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ ਸੰਪਰਕ ਜਾਣਕਾਰੀ ਪਹਿਲਾ ਨਾਂ ਵਿਚਕਾਰਲਾ ਨਾਂ ਆਖਰੀ ਨਾਂਮ ਉਚਾਰਣ ਫ਼ੋਨ ਸ਼ਾਮਲ ਕਰੋ ਈਮੇਲ ਸ਼ਾਮਲ ਕਰੋ ਪਤਾ ਸ਼ਾਮਲ ਕਰੋ ਵੈੱਬਸਾਈਟ ਵੈੱਬਸਾਈਟ ਸ਼ਾਮਲ ਕਰੋ ਫਾਰਮੈਟ ਕੀਤਾ ਨਾਮ ਇਹ ਚਿੱਤਰ ਬਾਰਕੋਡ ਦੇ ਉੱਪਰ ਰੱਖਣ ਲਈ ਵਰਤਿਆ ਜਾਵੇਗਾ ਕੋਡ ਅਨੁਕੂਲਤਾ ਇਹ ਚਿੱਤਰ QR ਕੋਡ ਦੇ ਕੇਂਦਰ ਵਿੱਚ ਲੋਗੋ ਵਜੋਂ ਵਰਤਿਆ ਜਾਵੇਗਾ ਲੋਗੋ ਲੋਗੋ ਪੈਡਿੰਗ ਲੋਗੋ ਦਾ ਆਕਾਰ ਲੋਗੋ ਕੋਨੇ ਚੌਥੀ ਅੱਖ ਹੇਠਲੇ ਸਿਰੇ ਦੇ ਕੋਨੇ \'ਤੇ ਚੌਥੀ ਅੱਖ ਜੋੜ ਕੇ ਕਿਊਆਰ ਕੋਡ ਵਿੱਚ ਅੱਖਾਂ ਦੀ ਸਮਰੂਪਤਾ ਜੋੜਦਾ ਹੈ ਪਿਕਸਲ ਆਕਾਰ ਫਰੇਮ ਸ਼ਕਲ ਗੇਂਦ ਦੀ ਸ਼ਕਲ ਗਲਤੀ ਸੁਧਾਰ ਪੱਧਰ ਗੂੜਾ ਰੰਗ ਹਲਕਾ ਰੰਗ ਹਾਈਪਰ ਓ.ਐਸ Xiaomi HyperOS ਵਰਗੀ ਸ਼ੈਲੀ ਮਾਸਕ ਪੈਟਰਨ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਹ ਕੋਡ ਸਕੈਨ ਕਰਨ ਯੋਗ ਨਾ ਹੋਵੇ, ਇਸ ਨੂੰ ਸਾਰੀਆਂ ਡਿਵਾਈਸਾਂ ਨਾਲ ਪੜ੍ਹਨਯੋਗ ਬਣਾਉਣ ਲਈ ਦਿੱਖ ਪੈਰਾਮਾਂ ਨੂੰ ਬਦਲੋ ਸਕੈਨ ਕਰਨ ਯੋਗ ਨਹੀਂ ਟੂਲ ਵਧੇਰੇ ਸੰਖੇਪ ਹੋਣ ਲਈ ਹੋਮ ਸਕ੍ਰੀਨ ਐਪ ਲਾਂਚਰ ਵਾਂਗ ਦਿਖਾਈ ਦੇਣਗੇ ਲਾਂਚਰ ਮੋਡ ਚੁਣੇ ਹੋਏ ਬੁਰਸ਼ ਅਤੇ ਸ਼ੈਲੀ ਨਾਲ ਇੱਕ ਖੇਤਰ ਭਰਦਾ ਹੈ ਹੜ੍ਹ ਭਰਨ ਸਪਰੇਅ ਕਰੋ ਗ੍ਰੈਫਿਟੀ ਸਟਾਈਲ ਵਾਲਾ ਮਾਰਗ ਖਿੱਚਦਾ ਹੈ ਵਰਗ ਕਣ ਸਪਰੇਅ ਕਣ ਚੱਕਰ ਦੀ ਬਜਾਏ ਵਰਗ ਆਕਾਰ ਦੇ ਹੋਣਗੇ ਪੈਲੇਟ ਟੂਲ ਚਿੱਤਰ ਤੋਂ ਮੂਲ/ਪੱਤਰ ਤਿਆਰ ਕਰੋ, ਜਾਂ ਵੱਖ-ਵੱਖ ਪੈਲੇਟ ਫਾਰਮੈਟਾਂ ਵਿੱਚ ਆਯਾਤ/ਨਿਰਯਾਤ ਕਰੋ ਪੈਲੇਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ ਵੱਖ-ਵੱਖ ਫਾਰਮੈਟਾਂ ਵਿੱਚ ਨਿਰਯਾਤ/ਆਯਾਤ ਪੈਲੇਟ ਰੰਗ ਦਾ ਨਾਮ ਪੈਲੇਟ ਨਾਮ ਪੈਲੇਟ ਫਾਰਮੈਟ ਤਿਆਰ ਕੀਤੇ ਪੈਲੇਟ ਨੂੰ ਵੱਖ-ਵੱਖ ਫਾਰਮੈਟਾਂ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ ਮੌਜੂਦਾ ਪੈਲੇਟ ਵਿੱਚ ਨਵਾਂ ਰੰਗ ਜੋੜਦਾ ਹੈ %1$s ਫਾਰਮੈਟ ਪੈਲੇਟ ਨਾਮ ਪ੍ਰਦਾਨ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ ਪਲੇ ਸਟੋਰ ਨੀਤੀਆਂ ਦੇ ਕਾਰਨ, ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਮੌਜੂਦਾ ਬਿਲਡ ਵਿੱਚ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਇਸ ਕਾਰਜਕੁਸ਼ਲਤਾ ਨੂੰ ਐਕਸੈਸ ਕਰਨ ਲਈ, ਕਿਰਪਾ ਕਰਕੇ ਕਿਸੇ ਵਿਕਲਪਿਕ ਸਰੋਤ ਤੋਂ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੋ। ਤੁਸੀਂ ਹੇਠਾਂ GitHub \'ਤੇ ਉਪਲਬਧ ਬਿਲਡਾਂ ਨੂੰ ਲੱਭ ਸਕਦੇ ਹੋ। Github ਪੰਨਾ ਖੋਲ੍ਹੋ ਮੂਲ ਫਾਈਲ ਨੂੰ ਚੁਣੇ ਹੋਏ ਫੋਲਡਰ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕਰਨ ਦੀ ਬਜਾਏ ਨਵੀਂ ਫਾਈਲ ਨਾਲ ਬਦਲ ਦਿੱਤਾ ਜਾਵੇਗਾ ਲੁਕੇ ਹੋਏ ਵਾਟਰਮਾਰਕ ਟੈਕਸਟ ਦਾ ਪਤਾ ਲਗਾਇਆ ਗਿਆ ਲੁਕੇ ਹੋਏ ਵਾਟਰਮਾਰਕ ਚਿੱਤਰ ਨੂੰ ਖੋਜਿਆ ਗਿਆ ਇਹ ਚਿੱਤਰ ਲੁਕਿਆ ਹੋਇਆ ਸੀ ਜਨਰੇਟਿਵ ਪੇਂਟਿੰਗ ਤੁਹਾਨੂੰ OpenCV \'ਤੇ ਭਰੋਸਾ ਕੀਤੇ ਬਿਨਾਂ, AI ਮਾਡਲ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਇੱਕ ਚਿੱਤਰ ਵਿੱਚ ਵਸਤੂਆਂ ਨੂੰ ਹਟਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦਾ ਹੈ। ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ, ਐਪ GitHub ਤੋਂ ਲੋੜੀਂਦੇ ਮਾਡਲ (~ 200 MB) ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੇਗੀ ਤੁਹਾਨੂੰ OpenCV \'ਤੇ ਭਰੋਸਾ ਕੀਤੇ ਬਿਨਾਂ, AI ਮਾਡਲ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਇੱਕ ਚਿੱਤਰ ਵਿੱਚ ਵਸਤੂਆਂ ਨੂੰ ਹਟਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦਾ ਹੈ। ਇਹ ਇੱਕ ਲੰਬਾ ਚੱਲਦਾ ਆਪ੍ਰੇਸ਼ਨ ਹੋ ਸਕਦਾ ਹੈ ਗਲਤੀ ਪੱਧਰ ਦਾ ਵਿਸ਼ਲੇਸ਼ਣ ਲੂਮੀਨੈਂਸ ਗਰੇਡੀਐਂਟ ਔਸਤ ਦੂਰੀ ਮੂਵ ਡਿਟੈਕਸ਼ਨ ਕਾਪੀ ਕਰੋ ਬਰਕਰਾਰ ਰੱਖੋ ਗੁਣਾਂਕ ਕਲਿੱਪਬੋਰਡ ਡਾਟਾ ਬਹੁਤ ਵੱਡਾ ਹੈ ਕਾਪੀ ਕਰਨ ਲਈ ਡਾਟਾ ਬਹੁਤ ਵੱਡਾ ਹੈ ਸਧਾਰਨ ਵੇਵ ਪਿਕਸਲਾਈਜ਼ੇਸ਼ਨ ਅਟਕਿਆ ਹੋਇਆ ਪਿਕਸਲੀਕਰਨ ਕ੍ਰਾਸ Pixelization ਮਾਈਕ੍ਰੋ ਮੈਕਰੋ ਪਿਕਸਲਾਈਜ਼ੇਸ਼ਨ ਔਰਬਿਟਲ ਪਿਕਸਲਾਈਜ਼ੇਸ਼ਨ ਵੌਰਟੇਕਸ ਪਿਕਸਲਾਈਜ਼ੇਸ਼ਨ ਪਲਸ ਗਰਿੱਡ ਪਿਕਸਲਾਈਜ਼ੇਸ਼ਨ ਨਿਊਕਲੀਅਸ ਪਿਕਸਲਾਈਜ਼ੇਸ਼ਨ ਰੇਡੀਅਲ ਵੇਵ ਪਿਕਸਲਾਈਜ਼ੇਸ਼ਨ uri \"%1$s\" ਨੂੰ ਖੋਲ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ ਬਰਫ਼ਬਾਰੀ ਮੋਡ ਸਮਰਥਿਤ ਬਾਰਡਰ ਫਰੇਮ ਗਲਿਚ ਵੇਰੀਐਂਟ ਚੈਨਲ ਸ਼ਿਫਟ ਅਧਿਕਤਮ ਔਫਸੈੱਟ VHS ਬਲਾਕ ਗਲਿਚ ਬਲਾਕ ਆਕਾਰ CRT ਵਕਰਤਾ ਵਕਰਤਾ ਕ੍ਰੋਮਾ ਪਿਕਸਲ ਪਿਘਲਾ ਅਧਿਕਤਮ ਡ੍ਰੌਪ AI ਟੂਲਜ਼ ਏਆਈ ਮਾਡਲਾਂ ਦੁਆਰਾ ਚਿੱਤਰਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਲਈ ਕਈ ਟੂਲ ਜਿਵੇਂ ਕਿ ਆਰਟੀਫੈਕਟ ਨੂੰ ਹਟਾਉਣਾ ਜਾਂ ਨਕਾਰਾ ਕਰਨਾ ਕੰਪਰੈਸ਼ਨ, ਜਾਗਡ ਲਾਈਨਾਂ ਕਾਰਟੂਨ, ਪ੍ਰਸਾਰਣ ਸੰਕੁਚਨ ਆਮ ਕੰਪਰੈਸ਼ਨ, ਆਮ ਰੌਲਾ ਰੰਗਹੀਣ ਕਾਰਟੂਨ ਸ਼ੋਰ ਤੇਜ਼, ਆਮ ਕੰਪਰੈਸ਼ਨ, ਆਮ ਰੌਲਾ, ਐਨੀਮੇਸ਼ਨ/ਕਾਮਿਕਸ/ਐਨੀਮੇ ਕਿਤਾਬ ਸਕੈਨਿੰਗ ਐਕਸਪੋਜਰ ਸੁਧਾਰ ਆਮ ਕੰਪਰੈਸ਼ਨ, ਰੰਗ ਚਿੱਤਰਾਂ \'ਤੇ ਵਧੀਆ ਆਮ ਕੰਪਰੈਸ਼ਨ, ਗ੍ਰੇਸਕੇਲ ਚਿੱਤਰਾਂ \'ਤੇ ਵਧੀਆ ਆਮ ਕੰਪਰੈਸ਼ਨ, ਗ੍ਰੇਸਕੇਲ ਚਿੱਤਰ, ਮਜ਼ਬੂਤ ਆਮ ਰੌਲਾ, ਰੰਗ ਚਿੱਤਰ ਆਮ ਰੌਲਾ, ਰੰਗ ਚਿੱਤਰ, ਬਿਹਤਰ ਵੇਰਵੇ ਆਮ ਰੌਲਾ, ਗ੍ਰੇਸਕੇਲ ਚਿੱਤਰ ਆਮ ਰੌਲਾ, ਗ੍ਰੇਸਕੇਲ ਚਿੱਤਰ, ਮਜ਼ਬੂਤ ਆਮ ਰੌਲਾ, ਗ੍ਰੇਸਕੇਲ ਚਿੱਤਰ, ਸਭ ਤੋਂ ਮਜ਼ਬੂਤ ਆਮ ਕੰਪਰੈਸ਼ਨ ਆਮ ਕੰਪਰੈਸ਼ਨ ਟੈਕਸਟੁਰਾਈਜ਼ੇਸ਼ਨ, h264 ਕੰਪਰੈਸ਼ਨ VHS ਕੰਪਰੈਸ਼ਨ ਗੈਰ-ਸਟੈਂਡਰਡ ਕੰਪਰੈਸ਼ਨ (ਸਿਨਪੈਕ, msvideo1, roq) ਬਿੰਕ ਕੰਪਰੈਸ਼ਨ, ਜਿਓਮੈਟਰੀ \'ਤੇ ਬਿਹਤਰ ਬਿੰਕ ਕੰਪਰੈਸ਼ਨ, ਮਜ਼ਬੂਤ ਬਿੰਕ ਕੰਪਰੈਸ਼ਨ, ਨਰਮ, ਵੇਰਵੇ ਨੂੰ ਬਰਕਰਾਰ ਰੱਖਦਾ ਹੈ ਪੌੜੀ-ਕਦਮ ਪ੍ਰਭਾਵ ਨੂੰ ਖਤਮ ਕਰਨਾ, ਸਮੂਥਿੰਗ ਸਕੈਨ ਕੀਤੀ ਕਲਾ/ਡਰਾਇੰਗ, ਮਾਮੂਲੀ ਕੰਪਰੈਸ਼ਨ, ਮੋਇਰ ਰੰਗ ਬੈਂਡਿੰਗ ਹੌਲੀ, ਹਾਫਟੋਨਸ ਨੂੰ ਹਟਾਉਣਾ ਗ੍ਰੇਸਕੇਲ/bw ਚਿੱਤਰਾਂ ਲਈ ਜਨਰਲ ਕਲਰਾਈਜ਼ਰ, ਬਿਹਤਰ ਨਤੀਜਿਆਂ ਲਈ ਡੀਡੀਕਲਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਕਿਨਾਰੇ ਨੂੰ ਹਟਾਉਣਾ ਓਵਰਸ਼ਾਰਪਨਿੰਗ ਨੂੰ ਹਟਾਉਂਦਾ ਹੈ ਧੀਮਾ, ਭਟਕਣਾ ਐਂਟੀ-ਅਲਾਈਜ਼ਿੰਗ, ਜਨਰਲ ਆਰਟੀਫੈਕਟਸ, ਸੀ.ਜੀ.ਆਈ KDM003 ਸਕੈਨ ਪ੍ਰੋਸੈਸਿੰਗ ਲਾਈਟਵੇਟ ਚਿੱਤਰ ਸੁਧਾਰ ਮਾਡਲ ਕੰਪਰੈਸ਼ਨ ਆਰਟੀਫੈਕਟ ਹਟਾਉਣਾ ਕੰਪਰੈਸ਼ਨ ਆਰਟੀਫੈਕਟ ਹਟਾਉਣਾ ਨਿਰਵਿਘਨ ਨਤੀਜਿਆਂ ਨਾਲ ਪੱਟੀ ਨੂੰ ਹਟਾਉਣਾ ਹਾਫਟੋਨ ਪੈਟਰਨ ਪ੍ਰੋਸੈਸਿੰਗ ਡਿਥਰ ਪੈਟਰਨ ਹਟਾਉਣ V3 JPEG ਆਰਟੀਫੈਕਟ ਹਟਾਉਣਾ V2 H.264 ਟੈਕਸਟ ਸੁਧਾਰ VHS ਸ਼ਾਰਪਨਿੰਗ ਅਤੇ ਇਨਹਾਂਸਮੈਂਟ ਮਿਲਾਉਣਾ ਚੰਕ ਦਾ ਆਕਾਰ ਓਵਰਲੈਪ ਆਕਾਰ %1$s px ਤੋਂ ਵੱਧ ਚਿੱਤਰਾਂ ਨੂੰ ਟੁਕੜਿਆਂ ਵਿੱਚ ਕੱਟਿਆ ਜਾਵੇਗਾ ਅਤੇ ਸੰਸਾਧਿਤ ਕੀਤਾ ਜਾਵੇਗਾ, ਦਿਖਣਯੋਗ ਸੀਮਾਂ ਨੂੰ ਰੋਕਣ ਲਈ ਇਹਨਾਂ ਨੂੰ ਓਵਰਲੈਪ ਮਿਲਾਉਂਦਾ ਹੈ। ਵੱਡੇ ਆਕਾਰ ਘੱਟ-ਅੰਤ ਵਾਲੇ ਡਿਵਾਈਸਾਂ ਨਾਲ ਅਸਥਿਰਤਾ ਦਾ ਕਾਰਨ ਬਣ ਸਕਦੇ ਹਨ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਇੱਕ ਚੁਣੋ ਕੀ ਤੁਸੀਂ %1$s ਮਾਡਲ ਨੂੰ ਮਿਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? ਤੁਹਾਨੂੰ ਇਸਨੂੰ ਦੁਬਾਰਾ ਡਾਊਨਲੋਡ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇਗੀ ਪੁਸ਼ਟੀ ਕਰੋ ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤੇ ਮਾਡਲ ਉਪਲਬਧ ਮਾਡਲ ਤਿਆਰ ਕਰ ਰਿਹਾ ਹੈ ਸਰਗਰਮ ਮਾਡਲ ਸੈਸ਼ਨ ਖੋਲ੍ਹਣ ਵਿੱਚ ਅਸਫਲ ਸਿਰਫ਼ .onnx/.ort ਮਾਡਲਾਂ ਨੂੰ ਆਯਾਤ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਮਾਡਲ ਆਯਾਤ ਕਰੋ ਹੋਰ ਵਰਤੋਂ ਲਈ ਕਸਟਮ onnx ਮਾਡਲ ਆਯਾਤ ਕਰੋ, ਸਿਰਫ਼ onnx/ort ਮਾਡਲ ਸਵੀਕਾਰ ਕੀਤੇ ਜਾਂਦੇ ਹਨ, ਲਗਭਗ ਸਾਰੇ esrgan ਜਿਵੇਂ ਕਿ ਰੂਪਾਂ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ ਆਯਾਤ ਕੀਤੇ ਮਾਡਲ ਆਮ ਰੌਲਾ, ਰੰਗੀਨ ਚਿੱਤਰ ਆਮ ਰੌਲਾ, ਰੰਗੀਨ ਚਿੱਤਰ, ਮਜ਼ਬੂਤ ਆਮ ਰੌਲਾ, ਰੰਗੀਨ ਚਿੱਤਰ, ਸਭ ਤੋਂ ਮਜ਼ਬੂਤ ਡਿਥਰਿੰਗ ਆਰਟੀਫੈਕਟਸ ਅਤੇ ਕਲਰ ਬੈਂਡਿੰਗ ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ, ਨਿਰਵਿਘਨ ਗਰੇਡੀਐਂਟ ਅਤੇ ਫਲੈਟ ਰੰਗ ਖੇਤਰਾਂ ਨੂੰ ਸੁਧਾਰਦਾ ਹੈ। ਕੁਦਰਤੀ ਰੰਗਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦੇ ਹੋਏ ਸੰਤੁਲਿਤ ਹਾਈਲਾਈਟਸ ਦੇ ਨਾਲ ਚਿੱਤਰ ਦੀ ਚਮਕ ਅਤੇ ਵਿਪਰੀਤਤਾ ਨੂੰ ਵਧਾਉਂਦਾ ਹੈ। ਵੇਰਵਿਆਂ ਨੂੰ ਰੱਖਦੇ ਹੋਏ ਅਤੇ ਜ਼ਿਆਦਾ ਐਕਸਪੋਜ਼ਰ ਤੋਂ ਬਚਣ ਦੌਰਾਨ ਹਨੇਰੇ ਚਿੱਤਰਾਂ ਨੂੰ ਚਮਕਾਉਂਦਾ ਹੈ। ਬਹੁਤ ਜ਼ਿਆਦਾ ਰੰਗ ਟੋਨਿੰਗ ਨੂੰ ਹਟਾਉਂਦਾ ਹੈ ਅਤੇ ਵਧੇਰੇ ਨਿਰਪੱਖ ਅਤੇ ਕੁਦਰਤੀ ਰੰਗ ਸੰਤੁਲਨ ਨੂੰ ਬਹਾਲ ਕਰਦਾ ਹੈ। ਵਧੀਆ ਵੇਰਵਿਆਂ ਅਤੇ ਟੈਕਸਟ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਣ \'ਤੇ ਜ਼ੋਰ ਦੇ ਨਾਲ ਪੋਇਸਨ-ਅਧਾਰਤ ਸ਼ੋਰ ਟੋਨਿੰਗ ਲਾਗੂ ਕਰਦਾ ਹੈ। ਨਿਰਵਿਘਨ ਅਤੇ ਘੱਟ ਹਮਲਾਵਰ ਵਿਜ਼ੂਅਲ ਨਤੀਜਿਆਂ ਲਈ ਨਰਮ ਪੋਇਸਨ ਸ਼ੋਰ ਟੋਨਿੰਗ ਲਾਗੂ ਕਰਦਾ ਹੈ। ਇਕਸਾਰ ਸ਼ੋਰ ਟੋਨਿੰਗ ਵੇਰਵੇ ਦੀ ਸੰਭਾਲ ਅਤੇ ਚਿੱਤਰ ਸਪਸ਼ਟਤਾ \'ਤੇ ਕੇਂਦ੍ਰਿਤ ਹੈ। ਸੂਖਮ ਟੈਕਸਟ ਅਤੇ ਨਿਰਵਿਘਨ ਦਿੱਖ ਲਈ ਕੋਮਲ ਇਕਸਾਰ ਸ਼ੋਰ ਟੋਨਿੰਗ। ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਦੁਬਾਰਾ ਪੇਂਟ ਕਰਕੇ ਅਤੇ ਚਿੱਤਰ ਦੀ ਇਕਸਾਰਤਾ ਵਿੱਚ ਸੁਧਾਰ ਕਰਕੇ ਖਰਾਬ ਜਾਂ ਅਸਮਾਨ ਖੇਤਰਾਂ ਦੀ ਮੁਰੰਮਤ ਕਰੋ। ਲਾਈਟਵੇਟ ਡੀਬੈਂਡਿੰਗ ਮਾਡਲ ਜੋ ਘੱਟੋ-ਘੱਟ ਪ੍ਰਦਰਸ਼ਨ ਲਾਗਤ ਨਾਲ ਰੰਗ ਬੈਂਡਿੰਗ ਨੂੰ ਹਟਾਉਂਦਾ ਹੈ। ਬਿਹਤਰ ਸਪੱਸ਼ਟਤਾ ਲਈ ਬਹੁਤ ਉੱਚ ਸੰਕੁਚਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ (0-20% ਗੁਣਵੱਤਾ) ਦੇ ਨਾਲ ਚਿੱਤਰਾਂ ਨੂੰ ਅਨੁਕੂਲਿਤ ਕਰਦਾ ਹੈ। ਉੱਚ ਸੰਕੁਚਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ (20-40% ਗੁਣਵੱਤਾ), ਵੇਰਵਿਆਂ ਨੂੰ ਬਹਾਲ ਕਰਨ ਅਤੇ ਰੌਲੇ ਨੂੰ ਘਟਾਉਣ ਵਾਲੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਵਧਾਉਂਦਾ ਹੈ। ਮੱਧਮ ਕੰਪਰੈਸ਼ਨ (40-60% ਗੁਣਵੱਤਾ), ਤਿੱਖਾਪਨ ਅਤੇ ਨਿਰਵਿਘਨਤਾ ਨੂੰ ਸੰਤੁਲਿਤ ਕਰਨ ਵਾਲੇ ਚਿੱਤਰਾਂ ਨੂੰ ਸੁਧਾਰਦਾ ਹੈ। ਸੂਖਮ ਵੇਰਵਿਆਂ ਅਤੇ ਟੈਕਸਟ ਨੂੰ ਵਧਾਉਣ ਲਈ ਲਾਈਟ ਕੰਪਰੈਸ਼ਨ (60-80% ਗੁਣਵੱਤਾ) ਨਾਲ ਚਿੱਤਰਾਂ ਨੂੰ ਸੁਧਾਰਦਾ ਹੈ। ਕੁਦਰਤੀ ਦਿੱਖ ਅਤੇ ਵੇਰਵਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦੇ ਹੋਏ ਨੇੜੇ-ਨੁਕਸਾਨ ਰਹਿਤ ਚਿੱਤਰਾਂ (80-100% ਗੁਣਵੱਤਾ) ਨੂੰ ਥੋੜ੍ਹਾ ਵਧਾਉਂਦਾ ਹੈ। ਸਧਾਰਨ ਅਤੇ ਤੇਜ਼ ਰੰਗੀਕਰਨ, ਕਾਰਟੂਨ, ਆਦਰਸ਼ ਨਹੀਂ ਚਿੱਤਰ ਦੇ ਧੁੰਦਲੇਪਣ ਨੂੰ ਥੋੜ੍ਹਾ ਘਟਾਉਂਦਾ ਹੈ, ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਪੇਸ਼ ਕੀਤੇ ਬਿਨਾਂ ਤਿੱਖਾਪਨ ਨੂੰ ਸੁਧਾਰਦਾ ਹੈ। ਲੰਬੇ ਚੱਲ ਰਹੇ ਓਪਰੇਸ਼ਨ ਚਿੱਤਰ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ ਪ੍ਰੋਸੈਸਿੰਗ ਬਹੁਤ ਘੱਟ ਗੁਣਵੱਤਾ ਵਾਲੀਆਂ ਤਸਵੀਰਾਂ (0-20%) ਵਿੱਚ ਭਾਰੀ JPEG ਕੰਪਰੈਸ਼ਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਹਟਾਉਂਦਾ ਹੈ। ਬਹੁਤ ਜ਼ਿਆਦਾ ਸੰਕੁਚਿਤ ਚਿੱਤਰਾਂ (20-40%) ਵਿੱਚ ਮਜ਼ਬੂਤ ​​JPEG ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ। ਚਿੱਤਰ ਵੇਰਵਿਆਂ (40-60%) ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦੇ ਹੋਏ ਦਰਮਿਆਨੀ JPEG ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਸਾਫ਼ ਕਰਦਾ ਹੈ। ਕਾਫ਼ੀ ਉੱਚ ਗੁਣਵੱਤਾ ਵਾਲੀਆਂ ਤਸਵੀਰਾਂ (60-80%) ਵਿੱਚ ਹਲਕੇ JPEG ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਸੋਧਦਾ ਹੈ। ਨੇੜੇ-ਨੁਕਸਾਨ ਰਹਿਤ ਚਿੱਤਰਾਂ (80-100%) ਵਿੱਚ ਮਾਮੂਲੀ JPEG ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ। ਵਧੀਆ ਵੇਰਵਿਆਂ ਅਤੇ ਟੈਕਸਟ ਨੂੰ ਵਧਾਉਂਦਾ ਹੈ, ਭਾਰੀ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਬਿਨਾਂ ਸਮਝੀ ਗਈ ਤਿੱਖਾਪਨ ਨੂੰ ਸੁਧਾਰਦਾ ਹੈ। ਪ੍ਰਕਿਰਿਆ ਪੂਰੀ ਹੋਈ ਪ੍ਰਕਿਰਿਆ ਅਸਫਲ ਰਹੀ ਸਪੀਡ ਲਈ ਅਨੁਕੂਲਿਤ, ਕੁਦਰਤੀ ਦਿੱਖ ਰੱਖਦੇ ਹੋਏ ਚਮੜੀ ਦੀ ਬਣਤਰ ਅਤੇ ਵੇਰਵਿਆਂ ਨੂੰ ਵਧਾਉਂਦਾ ਹੈ। JPEG ਕੰਪਰੈਸ਼ਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਹਟਾਉਂਦਾ ਹੈ ਅਤੇ ਸੰਕੁਚਿਤ ਫੋਟੋਆਂ ਲਈ ਚਿੱਤਰ ਗੁਣਵੱਤਾ ਨੂੰ ਬਹਾਲ ਕਰਦਾ ਹੈ। ਵੇਰਵਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦੇ ਹੋਏ, ਘੱਟ ਰੋਸ਼ਨੀ ਵਾਲੀਆਂ ਸਥਿਤੀਆਂ ਵਿੱਚ ਲਈਆਂ ਗਈਆਂ ਫੋਟੋਆਂ ਵਿੱਚ ISO ਸ਼ੋਰ ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ। ਓਵਰਐਕਸਪੋਜ਼ਡ ਜਾਂ \"ਜੰਬੋ\" ਹਾਈਲਾਈਟਸ ਨੂੰ ਠੀਕ ਕਰਦਾ ਹੈ ਅਤੇ ਬਿਹਤਰ ਟੋਨਲ ਸੰਤੁਲਨ ਨੂੰ ਬਹਾਲ ਕਰਦਾ ਹੈ। ਹਲਕਾ ਅਤੇ ਤੇਜ਼ ਰੰਗੀਕਰਨ ਮਾਡਲ ਜੋ ਗ੍ਰੇਸਕੇਲ ਚਿੱਤਰਾਂ ਵਿੱਚ ਕੁਦਰਤੀ ਰੰਗ ਜੋੜਦਾ ਹੈ। DEJPEG Denoise ਰੰਗੀਨ ਕਰੋ ਕਲਾਕ੍ਰਿਤੀਆਂ ਵਧਾਓ ਅਨੀਮੀ ਸਕੈਨ ਅੱਪਸਕੇਲ ਆਮ ਚਿੱਤਰਾਂ ਲਈ X4 ਅੱਪਸਕੇਲਰ; ਛੋਟਾ ਮਾਡਲ ਜੋ ਘੱਟ GPU ਅਤੇ ਸਮਾਂ ਵਰਤਦਾ ਹੈ, ਮੱਧਮ deblur ਅਤੇ denoise ਦੇ ਨਾਲ. ਆਮ ਚਿੱਤਰਾਂ ਲਈ X2 ਅੱਪਸਕੇਲਰ, ਟੈਕਸਟ ਅਤੇ ਕੁਦਰਤੀ ਵੇਰਵਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨਾ। ਵਿਸਤ੍ਰਿਤ ਟੈਕਸਟ ਅਤੇ ਯਥਾਰਥਵਾਦੀ ਨਤੀਜਿਆਂ ਦੇ ਨਾਲ ਆਮ ਚਿੱਤਰਾਂ ਲਈ X4 ਅੱਪਸਕੇਲਰ। ਐਕਸ 4 ਅਪਸਕੇਲਰ ਐਨੀਮੇ ਚਿੱਤਰਾਂ ਲਈ ਅਨੁਕੂਲਿਤ; ਤਿੱਖੀਆਂ ਲਾਈਨਾਂ ਅਤੇ ਵੇਰਵਿਆਂ ਲਈ 6 RRDB ਬਲਾਕ। MSE ਨੁਕਸਾਨ ਦੇ ਨਾਲ X4 ਅੱਪਸਕੇਲਰ, ਆਮ ਚਿੱਤਰਾਂ ਲਈ ਨਿਰਵਿਘਨ ਨਤੀਜੇ ਅਤੇ ਘਟਾਏ ਗਏ ਕਲਾਕ੍ਰਿਤੀਆਂ ਦਾ ਉਤਪਾਦਨ ਕਰਦਾ ਹੈ। ਐਕਸ 4 ਅਪਸਕੇਲਰ ਐਨੀਮੇ ਚਿੱਤਰਾਂ ਲਈ ਅਨੁਕੂਲਿਤ; ਤਿੱਖੇ ਵੇਰਵਿਆਂ ਅਤੇ ਨਿਰਵਿਘਨ ਲਾਈਨਾਂ ਵਾਲਾ 4B32F ਵੇਰੀਐਂਟ। ਆਮ ਚਿੱਤਰਾਂ ਲਈ X4 UltraSharp V2 ਮਾਡਲ; ਤਿੱਖਾਪਨ ਅਤੇ ਸਪਸ਼ਟਤਾ \'ਤੇ ਜ਼ੋਰ ਦਿੰਦਾ ਹੈ। X4 UltraSharp V2 Lite; ਤੇਜ਼ ਅਤੇ ਛੋਟਾ, ਘੱਟ GPU ਮੈਮੋਰੀ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਵੇਰਵੇ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦਾ ਹੈ। ਤੇਜ਼ ਪਿਛੋਕੜ ਨੂੰ ਹਟਾਉਣ ਲਈ ਹਲਕਾ ਮਾਡਲ। ਸੰਤੁਲਿਤ ਪ੍ਰਦਰਸ਼ਨ ਅਤੇ ਸ਼ੁੱਧਤਾ. ਪੋਰਟਰੇਟ, ਵਸਤੂਆਂ ਅਤੇ ਦ੍ਰਿਸ਼ਾਂ ਨਾਲ ਕੰਮ ਕਰਦਾ ਹੈ। ਜ਼ਿਆਦਾਤਰ ਵਰਤੋਂ ਦੇ ਮਾਮਲਿਆਂ ਲਈ ਸਿਫਾਰਸ਼ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। BG ਨੂੰ ਹਟਾਓ ਹਰੀਜ਼ੱਟਲ ਬਾਰਡਰ ਮੋਟਾਈ ਵਰਟੀਕਲ ਬਾਰਡਰ ਮੋਟਾਈ %1$s ਰੰਗ %1$s ਰੰਗ ਮੌਜੂਦਾ ਮਾਡਲ ਚੰਕਿੰਗ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ, ਚਿੱਤਰ ਨੂੰ ਅਸਲ ਮਾਪਾਂ ਵਿੱਚ ਸੰਸਾਧਿਤ ਕੀਤਾ ਜਾਵੇਗਾ, ਇਸ ਨਾਲ ਉੱਚ ਮੈਮੋਰੀ ਦੀ ਖਪਤ ਹੋ ਸਕਦੀ ਹੈ ਅਤੇ ਘੱਟ-ਅੰਤ ਵਾਲੇ ਡਿਵਾਈਸਾਂ ਨਾਲ ਸਮੱਸਿਆਵਾਂ ਹੋ ਸਕਦੀਆਂ ਹਨ ਚੰਕਿੰਗ ਅਯੋਗ ਹੈ, ਚਿੱਤਰ ਨੂੰ ਅਸਲ ਮਾਪਾਂ ਵਿੱਚ ਸੰਸਾਧਿਤ ਕੀਤਾ ਜਾਵੇਗਾ, ਇਸ ਨਾਲ ਉੱਚ ਮੈਮੋਰੀ ਦੀ ਖਪਤ ਹੋ ਸਕਦੀ ਹੈ ਅਤੇ ਘੱਟ-ਅੰਤ ਵਾਲੇ ਡਿਵਾਈਸਾਂ ਨਾਲ ਸਮੱਸਿਆਵਾਂ ਹੋ ਸਕਦੀਆਂ ਹਨ ਪਰ ਅਨੁਮਾਨ \'ਤੇ ਵਧੀਆ ਨਤੀਜੇ ਦੇ ਸਕਦੇ ਹਨ ਚੁੰਨੀ ਪਿਛੋਕੜ ਨੂੰ ਹਟਾਉਣ ਲਈ ਉੱਚ-ਸ਼ੁੱਧਤਾ ਚਿੱਤਰ ਵਿਭਾਜਨ ਮਾਡਲ ਛੋਟੀ ਮੈਮੋਰੀ ਵਰਤੋਂ ਦੇ ਨਾਲ ਤੇਜ਼ ਪਿਛੋਕੜ ਨੂੰ ਹਟਾਉਣ ਲਈ U2Net ਦਾ ਹਲਕਾ ਸੰਸਕਰਣ। ਪੂਰਾ DDCcolor ਮਾਡਲ ਘੱਟੋ-ਘੱਟ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਦੇ ਨਾਲ ਆਮ ਚਿੱਤਰਾਂ ਲਈ ਉੱਚ-ਗੁਣਵੱਤਾ ਰੰਗੀਕਰਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ। ਸਾਰੇ ਰੰਗੀਕਰਨ ਮਾਡਲਾਂ ਦੀ ਸਭ ਤੋਂ ਵਧੀਆ ਚੋਣ। ਡੀਡੀਕਲਰ ਸਿਖਲਾਈ ਪ੍ਰਾਪਤ ਅਤੇ ਨਿੱਜੀ ਕਲਾਤਮਕ ਡੇਟਾਸੇਟ; ਘੱਟ ਗੈਰ-ਯਥਾਰਥਵਾਦੀ ਰੰਗਾਂ ਦੀਆਂ ਕਲਾਕ੍ਰਿਤੀਆਂ ਦੇ ਨਾਲ ਵਿਭਿੰਨ ਅਤੇ ਕਲਾਤਮਕ ਰੰਗੀਕਰਨ ਨਤੀਜੇ ਪੈਦਾ ਕਰਦਾ ਹੈ। ਬੈਕਗਰਾਊਂਡ ਨੂੰ ਸਹੀ ਤਰ੍ਹਾਂ ਹਟਾਉਣ ਲਈ ਸਵਿਨ ਟਰਾਂਸਫਾਰਮਰ \'ਤੇ ਆਧਾਰਿਤ ਲਾਈਟਵੇਟ BiRefNet ਮਾਡਲ। ਤਿੱਖੇ ਕਿਨਾਰਿਆਂ ਅਤੇ ਸ਼ਾਨਦਾਰ ਵੇਰਵੇ ਦੀ ਸੰਭਾਲ ਦੇ ਨਾਲ ਉੱਚ-ਗੁਣਵੱਤਾ ਵਾਲੇ ਪਿਛੋਕੜ ਨੂੰ ਹਟਾਉਣਾ, ਖਾਸ ਤੌਰ \'ਤੇ ਗੁੰਝਲਦਾਰ ਵਸਤੂਆਂ ਅਤੇ ਗੁੰਝਲਦਾਰ ਪਿਛੋਕੜਾਂ \'ਤੇ। ਬੈਕਗ੍ਰਾਉਂਡ ਹਟਾਉਣ ਵਾਲਾ ਮਾਡਲ ਜੋ ਨਿਰਵਿਘਨ ਕਿਨਾਰਿਆਂ ਦੇ ਨਾਲ ਸਹੀ ਮਾਸਕ ਪੈਦਾ ਕਰਦਾ ਹੈ, ਆਮ ਵਸਤੂਆਂ ਲਈ ਢੁਕਵਾਂ ਅਤੇ ਮੱਧਮ ਵੇਰਵੇ ਦੀ ਸੰਭਾਲ ਕਰਦਾ ਹੈ। ਮਾਡਲ ਪਹਿਲਾਂ ਹੀ ਡਾਊਨਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ ਮਾਡਲ ਸਫਲਤਾਪੂਰਵਕ ਆਯਾਤ ਕੀਤਾ ਗਿਆ ਟਾਈਪ ਕਰੋ ਕੀਵਰਡ ਬਹੁਤ ਤੇਜ਼ ਸਧਾਰਣ ਹੌਲੀ ਬਹੁਤ ਹੌਲੀ ਗਣਨਾ ਪ੍ਰਤੀਸ਼ਤ ਨਿਊਨਤਮ ਮੁੱਲ %1$s ਹੈ ਉਂਗਲਾਂ ਨਾਲ ਚਿੱਤਰ ਬਣਾ ਕੇ ਚਿੱਤਰ ਨੂੰ ਵਿਗਾੜੋ ਵਾਰਪ ਕਠੋਰਤਾ ਵਾਰਪ ਮੋਡ ਮੂਵ ਕਰੋ ਵਧੋ ਸੁੰਗੜੋ ਘੁੰਮਣਾ CW ਘੁੰਮਣਾ CCW ਫੇਡ ਤਾਕਤ ਸਿਖਰ ਡ੍ਰੌਪ ਹੇਠਲਾ ਬੂੰਦ ਡ੍ਰੌਪ ਸ਼ੁਰੂ ਕਰੋ ਡ੍ਰੌਪ ਖਤਮ ਕਰੋ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ ਨਿਰਵਿਘਨ ਆਕਾਰ ਨਿਰਵਿਘਨ, ਵਧੇਰੇ ਕੁਦਰਤੀ ਆਕਾਰਾਂ ਲਈ ਮਿਆਰੀ ਗੋਲ ਆਇਤਕਾਰ ਦੀ ਬਜਾਏ ਸੁਪਰਇਲਿਪਸ ਦੀ ਵਰਤੋਂ ਕਰੋ ਆਕਾਰ ਦੀ ਕਿਸਮ ਕੱਟੋ ਗੋਲ ਕੀਤਾ ਨਿਰਵਿਘਨ ਗੋਲ ਕੀਤੇ ਬਿਨਾਂ ਤਿੱਖੇ ਕਿਨਾਰੇ ਕਲਾਸਿਕ ਗੋਲ ਕੋਨੇ ਆਕਾਰ ਦੀ ਕਿਸਮ ਕੋਨਿਆਂ ਦਾ ਆਕਾਰ ਸਕਰਕਲ ਸ਼ਾਨਦਾਰ ਗੋਲ UI ਤੱਤ ਫਾਈਲ ਨਾਮ ਫਾਰਮੈਟ ਕਸਟਮ ਟੈਕਸਟ ਫਾਈਲ ਨਾਮ ਦੇ ਬਿਲਕੁਲ ਸ਼ੁਰੂ ਵਿੱਚ ਰੱਖਿਆ ਗਿਆ, ਪ੍ਰੋਜੈਕਟ ਦੇ ਨਾਮਾਂ, ਬ੍ਰਾਂਡਾਂ ਜਾਂ ਨਿੱਜੀ ਟੈਗਾਂ ਲਈ ਸੰਪੂਰਨ। ਸਰੋਤ ਪਛਾਣ ਨੂੰ ਬਰਕਰਾਰ ਰੱਖਣ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਕਰਦੇ ਹੋਏ, ਐਕਸਟੈਂਸ਼ਨ ਦੇ ਬਿਨਾਂ ਮੂਲ ਫਾਈਲ ਨਾਮ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਚਿੱਤਰ ਦੀ ਚੌੜਾਈ ਪਿਕਸਲਾਂ ਵਿੱਚ, ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਤਬਦੀਲੀਆਂ ਜਾਂ ਨਤੀਜਿਆਂ ਨੂੰ ਸਕੇਲਿੰਗ ਕਰਨ ਲਈ ਉਪਯੋਗੀ। ਚਿੱਤਰ ਦੀ ਉਚਾਈ ਪਿਕਸਲ ਵਿੱਚ, ਆਕਾਰ ਅਨੁਪਾਤ ਜਾਂ ਨਿਰਯਾਤ ਨਾਲ ਕੰਮ ਕਰਨ ਵੇਲੇ ਮਦਦਗਾਰ। ਵਿਲੱਖਣ ਫਾਈਲਨਾਮਾਂ ਦੀ ਗਰੰਟੀ ਦੇਣ ਲਈ ਬੇਤਰਤੀਬ ਅੰਕ ਤਿਆਰ ਕਰਦਾ ਹੈ; ਡੁਪਲੀਕੇਟ ਦੇ ਵਿਰੁੱਧ ਵਾਧੂ ਸੁਰੱਖਿਆ ਲਈ ਹੋਰ ਅੰਕ ਜੋੜੋ। ਬੈਚ ਨਿਰਯਾਤ ਲਈ ਸਵੈ-ਵਧਾਉਣ ਵਾਲਾ ਕਾਊਂਟਰ, ਇੱਕ ਸੈਸ਼ਨ ਵਿੱਚ ਕਈ ਚਿੱਤਰਾਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਵੇਲੇ ਆਦਰਸ਼। ਲਾਗੂ ਕੀਤੇ ਪ੍ਰੀਸੈਟ ਨਾਮ ਨੂੰ ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰਦਾ ਹੈ ਤਾਂ ਜੋ ਤੁਸੀਂ ਆਸਾਨੀ ਨਾਲ ਯਾਦ ਰੱਖ ਸਕੋ ਕਿ ਚਿੱਤਰ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਿਵੇਂ ਕੀਤੀ ਗਈ ਸੀ। ਪ੍ਰੋਸੈਸਿੰਗ ਦੌਰਾਨ ਵਰਤੇ ਗਏ ਚਿੱਤਰ ਸਕੇਲਿੰਗ ਮੋਡ ਨੂੰ ਪ੍ਰਦਰਸ਼ਿਤ ਕਰਦਾ ਹੈ, ਮੁੜ ਆਕਾਰ, ਕੱਟੇ ਜਾਂ ਫਿੱਟ ਕੀਤੇ ਚਿੱਤਰਾਂ ਨੂੰ ਵੱਖ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਦਾ ਹੈ। ਫਾਈਲ ਨਾਮ ਦੇ ਅੰਤ ਵਿੱਚ ਰੱਖਿਆ ਕਸਟਮ ਟੈਕਸਟ, _v2, _edited, ਜਾਂ _final ਵਰਗੇ ਸੰਸਕਰਣ ਲਈ ਉਪਯੋਗੀ। ਫਾਈਲ ਐਕਸਟੈਂਸ਼ਨ (png, jpg, webp, ਆਦਿ), ਆਪਣੇ ਆਪ ਅਸਲ ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਫਾਰਮੈਟ ਨਾਲ ਮੇਲ ਖਾਂਦੀ ਹੈ। ਇੱਕ ਅਨੁਕੂਲਿਤ ਟਾਈਮਸਟੈਂਪ ਜੋ ਤੁਹਾਨੂੰ ਸੰਪੂਰਨ ਛਾਂਟੀ ਲਈ ਜਾਵਾ ਨਿਰਧਾਰਨ ਦੁਆਰਾ ਆਪਣੇ ਖੁਦ ਦੇ ਫਾਰਮੈਟ ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਕਰਨ ਦਿੰਦਾ ਹੈ। ਫਲਿੰਗ ਦੀ ਕਿਸਮ Android ਮੂਲ ਆਈਓਐਸ ਸ਼ੈਲੀ ਨਿਰਵਿਘਨ ਕਰਵ ਤੇਜ਼ ਸਟਾਪ ਉਛਾਲ ਫਲੋਟੀ ਸਨੈਪੀ ਅਤਿ ਨਿਰਵਿਘਨ ਅਨੁਕੂਲ ਪਹੁੰਚਯੋਗਤਾ ਜਾਗਰੂਕ ਘਟੀ ਹੋਈ ਗਤੀ ਬੇਸਲਾਈਨ ਤੁਲਨਾ ਲਈ ਨੇਟਿਵ ਐਂਡਰਾਇਡ ਸਕ੍ਰੋਲ ਭੌਤਿਕ ਵਿਗਿਆਨ ਆਮ ਵਰਤੋਂ ਲਈ ਸੰਤੁਲਿਤ, ਨਿਰਵਿਘਨ ਸਕ੍ਰੋਲਿੰਗ ਉੱਚ ਰਗੜ ਆਈਓਐਸ-ਵਰਗੇ ਸਕ੍ਰੌਲ ਵਿਵਹਾਰ ਵੱਖਰੇ ਸਕ੍ਰੌਲ ਮਹਿਸੂਸ ਲਈ ਵਿਲੱਖਣ ਸਪਲਾਈਨ ਕਰਵ ਤੇਜ਼ ਰੁਕਣ ਦੇ ਨਾਲ ਸਹੀ ਸਕ੍ਰੋਲਿੰਗ ਹੁਸ਼ਿਆਰ, ਜਵਾਬਦੇਹ ਉਛਾਲ ਵਾਲੀ ਸਕ੍ਰੌਲ ਸਮੱਗਰੀ ਬ੍ਰਾਊਜ਼ਿੰਗ ਲਈ ਲੰਬੇ, ਗਲਾਈਡਿੰਗ ਸਕ੍ਰੋਲ ਇੰਟਰਐਕਟਿਵ UIs ਲਈ ਤੇਜ਼, ਜਵਾਬਦੇਹ ਸਕ੍ਰੋਲਿੰਗ ਵਿਸਤ੍ਰਿਤ ਗਤੀ ਦੇ ਨਾਲ ਪ੍ਰੀਮੀਅਮ ਨਿਰਵਿਘਨ ਸਕ੍ਰੋਲਿੰਗ ਫਲਿੰਗ ਵੇਲੋਸਿਟੀ ਦੇ ਆਧਾਰ \'ਤੇ ਭੌਤਿਕ ਵਿਗਿਆਨ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰਦਾ ਹੈ ਸਿਸਟਮ ਪਹੁੰਚਯੋਗਤਾ ਸੈਟਿੰਗਾਂ ਦਾ ਆਦਰ ਕਰਦਾ ਹੈ ਪਹੁੰਚਯੋਗਤਾ ਲੋੜਾਂ ਲਈ ਨਿਊਨਤਮ ਗਤੀ ਪ੍ਰਾਇਮਰੀ ਲਾਈਨਾਂ ਹਰ ਪੰਜਵੀਂ ਲਾਈਨ ਨੂੰ ਮੋਟੀ ਲਾਈਨ ਜੋੜਦਾ ਹੈ ਰੰਗ ਭਰੋ ਲੁਕਵੇਂ ਟੂਲ ਸ਼ੇਅਰ ਲਈ ਲੁਕੇ ਹੋਏ ਟੂਲ ਰੰਗ ਲਾਇਬ੍ਰੇਰੀ ਰੰਗਾਂ ਦਾ ਇੱਕ ਵਿਸ਼ਾਲ ਸੰਗ੍ਰਹਿ ਬ੍ਰਾਊਜ਼ ਕਰੋ ਕੁਦਰਤੀ ਵੇਰਵਿਆਂ ਨੂੰ ਬਰਕਰਾਰ ਰੱਖਦੇ ਹੋਏ ਚਿੱਤਰਾਂ ਨੂੰ ਤਿੱਖਾ ਅਤੇ ਧੁੰਦਲਾ ਕਰਦਾ ਹੈ, ਫੋਕਸ ਤੋਂ ਬਾਹਰ ਦੀਆਂ ਫੋਟੋਆਂ ਨੂੰ ਠੀਕ ਕਰਨ ਲਈ ਆਦਰਸ਼। ਸਮਝਦਾਰੀ ਨਾਲ ਉਹਨਾਂ ਚਿੱਤਰਾਂ ਨੂੰ ਮੁੜ-ਬਹਾਲ ਕਰਦਾ ਹੈ ਜਿਨ੍ਹਾਂ ਦਾ ਪਹਿਲਾਂ ਮੁੜ ਆਕਾਰ ਦਿੱਤਾ ਗਿਆ ਸੀ, ਗੁਆਚੇ ਵੇਰਵਿਆਂ ਅਤੇ ਟੈਕਸਟ ਨੂੰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ। ਲਾਈਵ-ਐਕਸ਼ਨ ਸਮੱਗਰੀ ਲਈ ਅਨੁਕੂਲਿਤ, ਕੰਪਰੈਸ਼ਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ ਅਤੇ ਮੂਵੀ/ਟੀਵੀ ਸ਼ੋਅ ਫਰੇਮਾਂ ਵਿੱਚ ਵਧੀਆ ਵੇਰਵਿਆਂ ਨੂੰ ਵਧਾਉਂਦਾ ਹੈ। VHS-ਗੁਣਵੱਤਾ ਫੁਟੇਜ ਨੂੰ HD ਵਿੱਚ ਬਦਲਦਾ ਹੈ, ਟੇਪ ਦੇ ਸ਼ੋਰ ਨੂੰ ਦੂਰ ਕਰਦਾ ਹੈ ਅਤੇ ਵਿੰਟੇਜ ਭਾਵਨਾ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦੇ ਹੋਏ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਨੂੰ ਵਧਾਉਂਦਾ ਹੈ। ਟੈਕਸਟ-ਭਾਰੀ ਚਿੱਤਰਾਂ ਅਤੇ ਸਕ੍ਰੀਨਸ਼ੌਟਸ ਲਈ ਵਿਸ਼ੇਸ਼, ਅੱਖਰਾਂ ਨੂੰ ਤਿੱਖਾ ਕਰਦਾ ਹੈ ਅਤੇ ਪੜ੍ਹਨਯੋਗਤਾ ਵਿੱਚ ਸੁਧਾਰ ਕਰਦਾ ਹੈ। ਵਿਭਿੰਨ ਡੇਟਾਸੈਟਾਂ \'ਤੇ ਸਿਖਲਾਈ ਪ੍ਰਾਪਤ ਐਡਵਾਂਸਡ ਅਪਸਕੇਲਿੰਗ, ਆਮ-ਉਦੇਸ਼ ਵਾਲੀ ਫੋਟੋ ਸੁਧਾਰ ਲਈ ਸ਼ਾਨਦਾਰ। ਵੈੱਬ-ਸੰਕੁਚਿਤ ਫੋਟੋਆਂ ਲਈ ਅਨੁਕੂਲਿਤ, JPEG ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਹਟਾਉਂਦੀ ਹੈ ਅਤੇ ਕੁਦਰਤੀ ਦਿੱਖ ਨੂੰ ਬਹਾਲ ਕਰਦੀ ਹੈ। ਵਧੀਆ ਟੈਕਸਟਚਰ ਸੰਭਾਲ ਅਤੇ ਕਲਾਤਮਕ ਕਟੌਤੀ ਦੇ ਨਾਲ ਵੈੱਬ ਫੋਟੋਆਂ ਲਈ ਸੁਧਾਰਿਆ ਸੰਸਕਰਣ। ਡੁਅਲ ਐਗਰੀਗੇਸ਼ਨ ਟ੍ਰਾਂਸਫਾਰਮਰ ਤਕਨਾਲੋਜੀ ਨਾਲ 2x ਅਪਸਕੇਲਿੰਗ, ਤਿੱਖਾਪਨ ਅਤੇ ਕੁਦਰਤੀ ਵੇਰਵਿਆਂ ਨੂੰ ਬਣਾਈ ਰੱਖਦਾ ਹੈ। ਉੱਨਤ ਟ੍ਰਾਂਸਫਾਰਮਰ ਆਰਕੀਟੈਕਚਰ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ 3x ਅਪਸਕੇਲਿੰਗ, ਮੱਧਮ ਵਾਧੇ ਦੀਆਂ ਲੋੜਾਂ ਲਈ ਆਦਰਸ਼। ਅਤਿ-ਆਧੁਨਿਕ ਟਰਾਂਸਫਾਰਮਰ ਨੈਟਵਰਕ ਦੇ ਨਾਲ 4x ਉੱਚ-ਗੁਣਵੱਤਾ ਅਪਸਕੇਲਿੰਗ, ਵੱਡੇ ਪੈਮਾਨੇ \'ਤੇ ਵਧੀਆ ਵੇਰਵਿਆਂ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਦੀ ਹੈ। ਫੋਟੋਆਂ ਤੋਂ ਧੁੰਦਲਾ/ਸ਼ੋਰ ਅਤੇ ਹਿੱਲਣ ਨੂੰ ਹਟਾਉਂਦਾ ਹੈ। ਆਮ ਮਕਸਦ ਪਰ ਫੋਟੋਆਂ \'ਤੇ ਵਧੀਆ। BSRGAN ਡਿਗਰੇਡੇਸ਼ਨ ਲਈ ਅਨੁਕੂਲਿਤ, Swin2SR ਟ੍ਰਾਂਸਫਾਰਮਰ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਘੱਟ-ਗੁਣਵੱਤਾ ਵਾਲੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਰੀਸਟੋਰ ਕਰਦਾ ਹੈ। ਭਾਰੀ ਸੰਕੁਚਨ ਕਲਾਤਮਕ ਚੀਜ਼ਾਂ ਨੂੰ ਫਿਕਸ ਕਰਨ ਅਤੇ 4x ਸਕੇਲ \'ਤੇ ਵੇਰਵਿਆਂ ਨੂੰ ਵਧਾਉਣ ਲਈ ਵਧੀਆ। BSRGAN ਡਿਗਰੇਡੇਸ਼ਨ \'ਤੇ ਸਿਖਲਾਈ ਪ੍ਰਾਪਤ ਸਵਿਨਆਈਆਰ ਟ੍ਰਾਂਸਫਾਰਮਰ ਨਾਲ 4x ਅਪਸਕੇਲਿੰਗ। ਫੋਟੋਆਂ ਅਤੇ ਗੁੰਝਲਦਾਰ ਦ੍ਰਿਸ਼ਾਂ ਵਿੱਚ ਤਿੱਖੇ ਟੈਕਸਟ ਅਤੇ ਹੋਰ ਕੁਦਰਤੀ ਵੇਰਵਿਆਂ ਲਈ GAN ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਮਾਰਗ PDF ਨੂੰ ਮਿਲਾਓ ਇੱਕ ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਕਈ PDF ਫਾਈਲਾਂ ਨੂੰ ਜੋੜੋ ਫਾਈਲਾਂ ਦਾ ਆਰਡਰ pp PDF ਵੰਡੋ PDF ਦਸਤਾਵੇਜ਼ ਤੋਂ ਖਾਸ ਪੰਨਿਆਂ ਨੂੰ ਐਕਸਟਰੈਕਟ ਕਰੋ PDF ਘੁੰਮਾਓ ਪੰਨਾ ਸਥਿਤੀ ਨੂੰ ਪੱਕੇ ਤੌਰ \'ਤੇ ਠੀਕ ਕਰੋ ਪੰਨੇ PDF ਨੂੰ ਮੁੜ ਵਿਵਸਥਿਤ ਕਰੋ ਉਹਨਾਂ ਨੂੰ ਮੁੜ ਕ੍ਰਮਬੱਧ ਕਰਨ ਲਈ ਪੰਨਿਆਂ ਨੂੰ ਖਿੱਚੋ ਅਤੇ ਛੱਡੋ ਪੰਨੇ ਫੜੋ ਅਤੇ ਖਿੱਚੋ ਪੰਨਾ ਨੰਬਰ ਆਪਣੇ ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਨੰਬਰਿੰਗ ਸ਼ਾਮਲ ਕਰੋ ਲੇਬਲ ਫਾਰਮੈਟ PDF ਤੋਂ ਟੈਕਸਟ (OCR) ਆਪਣੇ PDF ਦਸਤਾਵੇਜ਼ਾਂ ਤੋਂ ਸਾਦਾ ਟੈਕਸਟ ਐਕਸਟਰੈਕਟ ਕਰੋ ਬ੍ਰਾਂਡਿੰਗ ਜਾਂ ਸੁਰੱਖਿਆ ਲਈ ਕਸਟਮ ਟੈਕਸਟ ਨੂੰ ਓਵਰਲੇ ਕਰੋ ਦਸਤਖਤ ਕਿਸੇ ਵੀ ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਆਪਣੇ ਇਲੈਕਟ੍ਰਾਨਿਕ ਦਸਤਖਤ ਸ਼ਾਮਲ ਕਰੋ ਇਹ ਦਸਤਖਤ ਵਜੋਂ ਵਰਤਿਆ ਜਾਵੇਗਾ PDF ਨੂੰ ਅਨਲੌਕ ਕਰੋ ਆਪਣੀਆਂ ਸੁਰੱਖਿਅਤ ਫਾਈਲਾਂ ਤੋਂ ਪਾਸਵਰਡ ਹਟਾਓ PDF ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਆਪਣੇ ਦਸਤਾਵੇਜ਼ਾਂ ਨੂੰ ਮਜ਼ਬੂਤ ​​ਏਨਕ੍ਰਿਪਸ਼ਨ ਨਾਲ ਸੁਰੱਖਿਅਤ ਕਰੋ ਸਫਲਤਾ PDF ਅਨਲੌਕ, ਤੁਸੀਂ ਇਸਨੂੰ ਸੁਰੱਖਿਅਤ ਜਾਂ ਸਾਂਝਾ ਕਰ ਸਕਦੇ ਹੋ PDF ਦੀ ਮੁਰੰਮਤ ਕਰੋ ਖਰਾਬ ਜਾਂ ਨਾ-ਪੜ੍ਹਨਯੋਗ ਦਸਤਾਵੇਜ਼ਾਂ ਨੂੰ ਠੀਕ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਗ੍ਰੇਸਕੇਲ ਸਾਰੇ ਦਸਤਾਵੇਜ਼ ਏਮਬੈਡਡ ਚਿੱਤਰਾਂ ਨੂੰ ਗ੍ਰੇਸਕੇਲ ਵਿੱਚ ਬਦਲੋ ਪੀਡੀਐਫ ਨੂੰ ਸੰਕੁਚਿਤ ਕਰੋ ਆਸਾਨੀ ਨਾਲ ਸਾਂਝਾ ਕਰਨ ਲਈ ਆਪਣੇ ਦਸਤਾਵੇਜ਼ ਫਾਈਲ ਆਕਾਰ ਨੂੰ ਅਨੁਕੂਲ ਬਣਾਓ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਅੰਦਰੂਨੀ ਕਰਾਸ-ਰੈਫਰੈਂਸ ਟੇਬਲ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਉਂਦਾ ਹੈ ਅਤੇ ਸਕ੍ਰੈਚ ਤੋਂ ਫਾਈਲ ਬਣਤਰ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਉਂਦਾ ਹੈ। ਇਹ ਬਹੁਤ ਸਾਰੀਆਂ ਫਾਈਲਾਂ ਤੱਕ ਪਹੁੰਚ ਨੂੰ ਬਹਾਲ ਕਰ ਸਕਦਾ ਹੈ ਜੋ \\"ਖੋਲ੍ਹੀਆਂ ਨਹੀਂ ਜਾ ਸਕਦੀਆਂ\\"। ਇਹ ਟੂਲ ਸਾਰੇ ਦਸਤਾਵੇਜ਼ ਚਿੱਤਰਾਂ ਨੂੰ ਗ੍ਰੇਸਕੇਲ ਵਿੱਚ ਬਦਲਦਾ ਹੈ। ਪ੍ਰਿੰਟਿੰਗ ਅਤੇ ਫਾਈਲ ਦਾ ਆਕਾਰ ਘਟਾਉਣ ਲਈ ਸਭ ਤੋਂ ਵਧੀਆ ਮੈਟਾਡਾਟਾ ਬਿਹਤਰ ਗੋਪਨੀਯਤਾ ਲਈ ਦਸਤਾਵੇਜ਼ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰੋ ਟੈਗਸ ਨਿਰਮਾਤਾ ਲੇਖਕ ਕੀਵਰਡਸ ਸਿਰਜਣਹਾਰ ਗੋਪਨੀਯਤਾ ਡੂੰਘੀ ਸਾਫ਼ ਇਸ ਦਸਤਾਵੇਜ਼ ਲਈ ਉਪਲਬਧ ਸਾਰੇ ਮੈਟਾਡੇਟਾ ਨੂੰ ਸਾਫ਼ ਕਰੋ ਪੰਨਾ ਡੂੰਘੀ OCR ਡੌਕੂਮੈਂਟ ਤੋਂ ਟੈਕਸਟ ਐਕਸਟਰੈਕਟ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਟੈਸਰੈਕਟ ਇੰਜਣ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਇੱਕ ਟੈਕਸਟ ਫਾਈਲ ਵਿੱਚ ਸਟੋਰ ਕਰੋ ਸਾਰੇ ਪੰਨਿਆਂ ਨੂੰ ਹਟਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ PDF ਪੰਨਿਆਂ ਨੂੰ ਹਟਾਓ PDF ਦਸਤਾਵੇਜ਼ ਤੋਂ ਖਾਸ ਪੰਨਿਆਂ ਨੂੰ ਹਟਾਓ ਹਟਾਉਣ ਲਈ ਟੈਪ ਕਰੋ ਹੱਥੀਂ PDF ਨੂੰ ਕੱਟੋ ਦਸਤਾਵੇਜ਼ ਪੰਨਿਆਂ ਨੂੰ ਕਿਸੇ ਵੀ ਹੱਦ ਤੱਕ ਕੱਟੋ PDF ਫਲੈਟ ਕਰੋ ਦਸਤਾਵੇਜ਼ ਪੰਨਿਆਂ ਨੂੰ ਰਾਸਟਰ ਕਰਕੇ PDF ਨੂੰ ਸੋਧਣਯੋਗ ਬਣਾਓ ਕੈਮਰਾ ਚਾਲੂ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਕਿਰਪਾ ਕਰਕੇ ਅਨੁਮਤੀਆਂ ਦੀ ਜਾਂਚ ਕਰੋ ਅਤੇ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਇਹ ਕਿਸੇ ਹੋਰ ਐਪ ਦੁਆਰਾ ਨਹੀਂ ਵਰਤੀ ਜਾ ਰਹੀ ਹੈ। ਚਿੱਤਰਾਂ ਨੂੰ ਐਕਸਟਰੈਕਟ ਕਰੋ PDF ਵਿੱਚ ਏਮਬੇਡ ਕੀਤੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਉਹਨਾਂ ਦੇ ਅਸਲ ਰੈਜ਼ੋਲਿਊਸ਼ਨ \'ਤੇ ਐਕਸਟਰੈਕਟ ਕਰੋ ਇਸ PDF ਫਾਈਲ ਵਿੱਚ ਕੋਈ ਵੀ ਏਮਬੈਡਡ ਚਿੱਤਰ ਸ਼ਾਮਲ ਨਹੀਂ ਹਨ ਇਹ ਟੂਲ ਹਰ ਪੰਨੇ ਨੂੰ ਸਕੈਨ ਕਰਦਾ ਹੈ ਅਤੇ ਪੂਰੀ-ਗੁਣਵੱਤਾ ਵਾਲੇ ਸਰੋਤ ਚਿੱਤਰਾਂ ਨੂੰ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ - ਦਸਤਾਵੇਜ਼ਾਂ ਤੋਂ ਮੂਲ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਸੰਪੂਰਨ ਦਸਤਖਤ ਖਿੱਚੋ ਕਲਮ ਪਰਮ ਦਸਤਾਵੇਜ਼ਾਂ \'ਤੇ ਰੱਖਣ ਲਈ ਚਿੱਤਰ ਵਜੋਂ ਆਪਣੇ ਦਸਤਖਤ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜ਼ਿਪ PDF ਦਿੱਤੇ ਅੰਤਰਾਲ ਦੇ ਨਾਲ ਦਸਤਾਵੇਜ਼ ਨੂੰ ਵੰਡੋ ਅਤੇ ਨਵੇਂ ਦਸਤਾਵੇਜ਼ਾਂ ਨੂੰ ਜ਼ਿਪ ਆਰਕਾਈਵ ਵਿੱਚ ਪੈਕ ਕਰੋ ਅੰਤਰਾਲ PDF ਪ੍ਰਿੰਟ ਕਰੋ ਕਸਟਮ ਪੰਨੇ ਦੇ ਆਕਾਰ ਨਾਲ ਪ੍ਰਿੰਟਿੰਗ ਲਈ ਦਸਤਾਵੇਜ਼ ਤਿਆਰ ਕਰੋ ਪੰਨੇ ਪ੍ਰਤੀ ਸ਼ੀਟ ਸਥਿਤੀ ਪੰਨਾ ਆਕਾਰ ਹਾਸ਼ੀਏ ਖਿੜ ਨਰਮ ਗੋਡਾ ਐਨੀਮੇ ਅਤੇ ਕਾਰਟੂਨਾਂ ਲਈ ਅਨੁਕੂਲਿਤ। ਸੁਧਰੇ ਹੋਏ ਕੁਦਰਤੀ ਰੰਗਾਂ ਅਤੇ ਘੱਟ ਕਲਾਕ੍ਰਿਤੀਆਂ ਦੇ ਨਾਲ ਤੇਜ਼ ਅਪਸਕੇਲਿੰਗ Samsung One UI 7 ਵਰਗਾ ਸਟਾਈਲ ਲੋੜੀਂਦੇ ਮੁੱਲ ਦੀ ਗਣਨਾ ਕਰਨ ਲਈ ਇੱਥੇ ਮੂਲ ਗਣਿਤ ਚਿੰਨ੍ਹ ਦਾਖਲ ਕਰੋ (ਉਦਾਹਰਨ ਲਈ (5+5)*10) ਗਣਿਤ ਸਮੀਕਰਨ %1$s ਤੱਕ ਚਿੱਤਰ ਚੁੱਕੋ ਅਲਫ਼ਾ ਫਾਰਮੈਟਾਂ ਲਈ ਪਿਛੋਕੜ ਦਾ ਰੰਗ ਅਲਫ਼ਾ ਸਮਰਥਨ ਦੇ ਨਾਲ ਹਰ ਚਿੱਤਰ ਫਾਰਮੈਟ ਲਈ ਬੈਕਗ੍ਰਾਉਂਡ ਰੰਗ ਸੈੱਟ ਕਰਨ ਦੀ ਯੋਗਤਾ ਜੋੜਦਾ ਹੈ, ਜਦੋਂ ਇਹ ਅਸਮਰੱਥ ਹੁੰਦਾ ਹੈ ਤਾਂ ਇਹ ਸਿਰਫ਼ ਗੈਰ-ਅਲਫ਼ਾ ਲਈ ਉਪਲਬਧ ਹੁੰਦਾ ਹੈ ਮਿਤੀ ਸਮਾਂ ਰੱਖੋ ਹਮੇਸ਼ਾਂ ਮਿਤੀ ਅਤੇ ਸਮੇਂ ਨਾਲ ਸੰਬੰਧਿਤ ਐਕਸੀਫ ਟੈਗਸ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖੋ, ਐਕਸੀਫ ਵਿਕਲਪ ਦੀ ਵਰਤੋਂ ਤੋਂ ਸੁਤੰਤਰ ਤੌਰ \'ਤੇ ਕੰਮ ਕਰਦਾ ਹੈ ਪ੍ਰੋਜੈਕਟ ਖੋਲ੍ਹੋ ਪਹਿਲਾਂ ਸੁਰੱਖਿਅਤ ਕੀਤੇ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਪ੍ਰੋਜੈਕਟ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰਨਾ ਜਾਰੀ ਰੱਖੋ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਪ੍ਰੋਜੈਕਟ ਨੂੰ ਖੋਲ੍ਹਣ ਵਿੱਚ ਅਸਮਰੱਥ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਪ੍ਰੋਜੈਕਟ ਵਿੱਚ ਪ੍ਰੋਜੈਕਟ ਡੇਟਾ ਗੁੰਮ ਹੈ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਪ੍ਰੋਜੈਕਟ ਖਰਾਬ ਹੈ ਅਸਮਰਥਿਤ ਚਿੱਤਰ ਟੂਲਬਾਕਸ ਪ੍ਰੋਜੈਕਟ ਸੰਸਕਰਣ: %1$d ਪ੍ਰੋਜੈਕਟ ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਸੰਪਾਦਨਯੋਗ ਪ੍ਰੋਜੈਕਟ ਫਾਈਲ ਵਿੱਚ ਲੇਅਰਾਂ, ਬੈਕਗ੍ਰਾਉਂਡ ਅਤੇ ਸੰਪਾਦਨ ਇਤਿਹਾਸ ਨੂੰ ਸਟੋਰ ਕਰੋ ਖੋਲ੍ਹਣ ਵਿੱਚ ਅਸਫਲ ਖੋਜਯੋਗ PDF ਵਿੱਚ ਲਿਖੋ ਚਿੱਤਰ ਬੈਚ ਤੋਂ ਟੈਕਸਟ ਨੂੰ ਪਛਾਣੋ ਅਤੇ ਚਿੱਤਰ ਅਤੇ ਚੋਣਯੋਗ ਟੈਕਸਟ ਲੇਅਰ ਨਾਲ ਖੋਜਣ ਯੋਗ PDF ਨੂੰ ਸੁਰੱਖਿਅਤ ਕਰੋ ਲੇਅਰ ਅਲਫ਼ਾ ਹਰੀਜ਼ੱਟਲ ਫਲਿੱਪ ਵਰਟੀਕਲ ਫਲਿੱਪ ਤਾਲਾ ਸ਼ੈਡੋ ਸ਼ਾਮਲ ਕਰੋ ਸ਼ੈਡੋ ਰੰਗ ਟੈਕਸਟ ਜਿਓਮੈਟਰੀ ਤਿੱਖੀ ਸ਼ੈਲੀ ਲਈ ਟੈਕਸਟ ਨੂੰ ਖਿੱਚੋ ਜਾਂ ਤਿੱਖਾ ਕਰੋ ਸਕੇਲ ਐਕਸ ਸਕਿਊ ਐਕਸ ਐਨੋਟੇਸ਼ਨਾਂ ਨੂੰ ਹਟਾਓ PDF ਪੰਨਿਆਂ ਤੋਂ ਚੁਣੀਆਂ ਗਈਆਂ ਐਨੋਟੇਸ਼ਨ ਕਿਸਮਾਂ ਨੂੰ ਹਟਾਓ ਜਿਵੇਂ ਕਿ ਲਿੰਕ, ਟਿੱਪਣੀਆਂ, ਹਾਈਲਾਈਟਸ, ਆਕਾਰ ਜਾਂ ਫਾਰਮ ਖੇਤਰ ਹਾਈਪਰਲਿੰਕਸ ਫਾਈਲ ਅਟੈਚਮੈਂਟਸ ਲਾਈਨਾਂ ਪੌਪਅੱਪ ਸਟਪਸ ਆਕਾਰ ਟੈਕਸਟ ਨੋਟਸ ਟੈਕਸਟ ਮਾਰਕਅੱਪ ਫਾਰਮ ਖੇਤਰ ਮਾਰਕਅੱਪ ਅਗਿਆਤ ਐਨੋਟੇਸ਼ਨ ਅਨਗਰੁੱਪ ਕਰੋ ਸੰਰਚਨਾਯੋਗ ਰੰਗ ਅਤੇ ਆਫਸੈਟਾਂ ਦੇ ਨਾਲ ਪਰਤ ਦੇ ਪਿੱਛੇ ਬਲਰ ਸ਼ੈਡੋ ਸ਼ਾਮਲ ਕਰੋ ================================================ FILE: core/resources/src/main/res/values-pl/strings.xml ================================================ Rozmiar %1$s Szerokość %1$s Ładowanie… Obraz jest zbyt duży do podglądu, ale i tak zostanie podjęta próba jego zapisania Wybierz obraz, aby rozpocząć Coś poszło nie tak: %1$s Wysokość %1$s Jakość Rozszerzenie Typ zmiany rozmiaru Zrestartuj aplikację Wybierz obraz Czy na pewno chcesz wyjść z aplikacji? Zamykanie aplikacji Zostań Zamknij Zresetuj obraz Zmiany obrazu zostaną przywrócone do wartości początkowych Wartości prawidłowo zresetowane Resetuj Coś poszło nie tak Skopiowano do schowka Wyjątek Edytuj dane EXIF OK Tryb Amoled Jeśli ta opcja jest włączona, w trybie nocnym kolor tła zostanie ustawiony na całkowicie ciemny Czerwony Zielony Niebieski Wklej prawidłowy kod aRGB Nic do wklejenia Schemat kolorów Wyraźny Elastyczny Dodaj tag Zapisz Wyczyść Wyczyść dane EXIF Anuluj Wszystkie dane EXIF obrazu zostaną usunięte. Tej czynności nie można cofnąć! Presety Przytnij Zapisywanie Wszystkie niezapisane zmiany zostaną utracone, jeśli wyjdziesz teraz Kod źródłowy Najnowsze aktualizacje, dyskusje i nie tylko Pojedyncza edycja Modyfikacja, zmiana rozmiaru i edycja jednego obrazu Selektor kolorów Wybierz kolor z obrazu, skopiuj lub udostępnij Obraz Kolor Skopiowano kolor Przytnij obraz w dowolny sposób Wersja Zachowaj dane EXIF Obrazy: %d Usuń Własny Nieokreślony Pamięć wewnętrzna Zmień rozmiar pliku Maksymalny rozmiar w KB Zmień wymiary obrazu, aby ograniczyć jego rozmiar w KB Porównaj Porównaj dwa obrazy Wybierz dwa obrazy, aby rozpocząć Wybierz obrazy Ustawienia Tryb nocny Personalizacja Użyj kolorów obrazu Jeśli opcja ta jest włączona, po wybraniu obrazu do edycji kolory aplikacji zostaną dostosowane do tego obrazu Język Aktualizuj Nie można wygenerować palety dla danego obrazu Nieobsługiwany typ: %1$s Oryginał Systemowy Zmień podgląd Paleta Nowa wersja %1$s Wygeneruj próbkę palety kolorów z podanego obrazu Wygeneruj paletę Folder wyjściowy Domyślny Ciemny Jasny Dynamiczne kolory Nie znaleziono metadanych EXIF Nie można zmienić schematu kolorów aplikacji, gdy włączone są kolory dynamiczne Motyw aplikacji będzie oparty na wybranym kolorze O aplikacji Nie znaleziono aktualizacji Śledzenie błędów Wysyłaj raporty o błędach i prośby o funkcje tutaj Pomóż w tłumaczeniu Szukaj tutaj Popraw błędy w tłumaczeniu lub przetłumacz projekt na nowe języki Nic nie znaleziono na podstawie Twojego zapytania Jeśli opcja ta jest włączona, kolory aplikacji zostaną dostosowane do koloru tapety Nie udało się zapisać %d obrazów Powierzchnia Wyrównanie przycisków pływających Sprawdź, czy są aktualizacje Jeśli opcja ta jest włączona, okno aktualizacji będzie wyświetlane podczas uruchamiania aplikacji Wartości Dodaj Główny Trzeciorzędny Drugorzędny Uprawnienie Przyznaj Aplikacja potrzebuje dostępu do pamięci, aby móc zapisywać obrazy. Przyznaj uprawnienie w następnym oknie dialogowym. Powiększenie obrazu Prefiks Nazwa pliku Ta aplikacja jest całkowicie darmowa, ale możesz kliknąć tutaj, jeśli chcesz wesprzeć rozwój projektu Aplikacja potrzebuje tego uprawnienia do działania, prosimy o przyznanie go ręcznie. Grubość ramki Udostępnij Pamięć zewnętrzna Kolory Monet Wyostrz Sepia Szerokość linii Krawędź Sobela Selektor zdjęć Jasność Przestrzeń barw CGA Rozmycie Gaussa Promień Skala Zmiana rozmiaru przez limity Szkic Próg Poziomy kwantyzacji Gładki toon Rozmycie stosu Konwolucja 3x3 Filtr RGB Fałszywy kolor Emotikony Wybierz, które emotikony będą wyświetlane na ekranie głównym Dodaj rozmiar pliku Jeśli włączone, szerokość i wysokość zapisanego obrazu zostanie dodana do nazwy pliku wyjściowego Usuń dane EXIF Usuń metadane EXIF z dowolnego zestawu obrazów Podgląd obrazu Wyświetl podgląd dowolnego typu obrazów: GIF, SVG itd. Źródło obrazu Galeria Nowoczesny selektor zdjęć Androida u dołu ekranu, może działać tylko na Androidzie 12+. Ma problemy z odczytem danych EXIF Użyj intencji GetContent, aby wybrać obraz. Działa wszędzie, ale może też mieć problemy z odczytem wybranych obrazów na niektórych urządzeniach. To nie moja wina. Układ opcji Liczba emotikonów Użyj numeru z sekwencji Jeśli opcja ta jest włączona, zastępuje standardowy znacznik czasu numerem sekwencji w przypadku przetwarzania wielu plików Załaduj dowolny obraz z Internetu, aby go podejrzeć, powiększyć, edytować i zapisać. Brak obrazu Link do obrazu Wypełnij Dopasuj Zmienia rozmiar obrazów do podanej wysokości i szerokości. Współczynnik proporcji obrazów może ulec zmianie. Zmienia rozmiar obrazów o długim boku do podanej wysokości lub szerokości. Wszystkie obliczenia rozmiaru zostaną wykonane po zapisaniu. Proporcje obrazu zostaną zachowane. Kontrast Odcień Nasycenie Dodaj filtr Filtr Zastosuj łańcuchy filtrów do obrazów Filtry Światło Filtr koloru Kanał alfa Ekspozycja Balans bieli Temperatura Zabarwienie Monochromatyczność Gamma Światła i cienie Światła Cienie Mgła Efekt Dystans Nachylenie Negatyw Solaryzacja Żywiołowość Czarno-biały Kreskowanie Rozstaw Rozmycie Półtony Rozmycie pudełkowe Rozmycie dwustronne Tłoczenie Laplacian Winieta Początek Koniec Wygładzanie Kuwahary Zniekształcenie Kąt Wir Wypukłość Dylatacja Refrakcja kuli Współczynnik załamania światła Refrakcja szklanej kuli Matryca kolorów Krycie Zmień rozmiar obrazów do podanej wysokości i szerokości, zachowując współczynnik proporcji Toon Plakatowanie Nie maksymalne tłumienie Słaba integracja pikseli Przegląd Pierwszy kolor Drugi kolor Zmień kolejność Szybkie rozmycie Rozmiar rozmycia Rozmycie centrum x Rozmycie środka y Powiększ rozmycie Balans kolorów Próg luminancji Przeglądarka plików Prosty selektor obrazów w galerii. Działa tylko wtedy, gdy masz aplikację, która umożliwia wybieranie multimediów Ładowanie obrazu sieciowego Edycja Kolejność Określa kolejność narzędzi na ekranie głównym Skala zawartości Sekwencja liczb Oryginalna nazwa pliku Dodaj oryginalną nazwę pliku Jeśli włączone, dodaje oryginalną nazwę pliku do nazwy obrazu wyjściowego Dodawanie oryginalnej nazwy pliku nie działa, jeśli wybrano źródło obrazu w selektorze zdjęć Kopiuj Rysuj na obrazie jak w szkicowniku lub rysuj na samym tle Rysuj Zrzut ekranu Maluj alfa Pogrupuj opcje według typu Grupuje opcje na ekranie głównym według ich typu zamiast niestandardowego układu listy Edytuj zrzut ekranu Personalizacja dodatkowa Opcja rezerwowa Pomiń Zapisywanie w trybie %1$s może być niestabilne, ponieważ jest to format bezstratny Nieprawidłowe hasło lub wybrany plik nie jest zaszyfrowany Wybierz plik, aby rozpocząć Deszyfrowanie Szyfrowanie Klucz Plik przetworzony Szyfrowanie plików oparte na hasłach. Przetworzone pliki mogą być przechowywane w wybranym katalogu lub udostępniane. Odszyfrowane pliki można również otworzyć bezpośrednio. Rozmiar pliku Rozmiar pamięci podręcznej Pamięć podręczna Próba zapisania obrazu z podaną szerokością i wysokością może spowodować błąd braku pamięci. Robisz to na własne ryzyko. Znaleziono %1$s Twórz Wyłączyłeś aplikację Pliki. Aktywuj ją, aby korzystać z tej funkcji Kolor farby Narysuj na obrazie Wybierz obrazek i narysuj coś na nim Rysuj na tle Wybierz kolor tła i rysuj na nim Kolor tła Odszyfruj Zgodność Należy pamiętać, że zgodność z innym oprogramowaniem lub usługami do szyfrowania plików nie jest gwarantowana. Przyczyną niezgodności może być nieco inna obróbka klucza lub konfiguracja szyfru. Nie można zmienić układu, gdy włączone jest grupowanie opcji AES-256, tryb GCM, brak wypełnienia, domyślnie 12 bajtów losowych IV. Można wybrać odpowiedni algorytm. Klucze są używane jako 256-bitowe skróty SHA-3 Szyfr Szyfrowanie i deszyfrowanie dowolnego pliku (nie tylko obrazu) w oparciu o różne dostępne algorytmy kryptograficzne. Wybierz plik Szyfruj Zapisz ten plik na swoim urządzeniu lub użyj okna udostępniania, aby umieścić go w dowolnym miejscu Funkcje Implementacja Maksymalny rozmiar pliku jest ograniczony przez system operacyjny Android i dostępną pamięć RAM, co zależy od Twojego urządzenia. \nUwaga: pamięć RAM to nie miejsce na dane. Narzędzia Automatyczne czyszczenie pamięci podręcznej Jeśli ta opcja jest włączona, pamięć podręczna aplikacji zostanie wyczyszczona podczas uruchamiania aplikacji E-mail Kopia zapasowa i przywracanie Obiekty Kopia zapasowa Tryb rysowania Ups… Coś poszło nie tak. Możesz do mnie napisać, korzystając z opcji poniżej, postaram się znaleźć rozwiązanie Narysuj strzałki Jeśli ta opcja jest włączona, ścieżka rysowania będzie reprezentowana jako strzałka wskazująca Ulepszona pikselizacja Kolor docelowy Czat Telegram Włącz emotikony Podróże i miejsca Usuń tło poprzez obrysowane ręczne lub automatycznie. Miękkość pędzla Maska przycinania Skontaktuj się ze mną Zapisano do folderu %1$s Aktualizacje Zezwalaj na wersje beta Odwróć kolory Zamienia kolory motywu na negatywne Szukaj Umożliwia przeszukiwanie wszystkich dostępnych narzędzi na ekranie głównym Jeśli ta opcja jest włączona, małe obrazy zostaną przeskalowane do największego w sekwencji Usuwanie tła Symbole Aktywności Usuń tło Przywróć obraz Przywróć tło Promień rozmycia Gumka Utwórz problem Zmień rozmiar i konwertuj Pipeta Sprawdzanie aktualizacji obejmie wersje beta aplikacji, jeśli jest włączone Zdjęcia zostaną przycięte do środka do wprowadzonego rozmiaru. Kanwa zostanie powiększona o podany kolor tła, jeśli obraz będzie mniejszy niż wprowadzone wymiary. Ulepszona diamentowa pikselizacja Diamentowa pikselizacja Pikselizacja kołowa Ulepszona kołowa pikselizacja Pikselizacja obrysu Kolor do zamiany Kolor do usunięcia Usuń kolor Przekoduj Przeglądarka PDF Zapisz PDF jako obraz Zapisz obraz jako PDF Prosty podgląd plików PDF Narzędzia PDF Maksymalna liczba kolorów Łączenie obrazów Połącz podane obrazy, aby uzyskać jeden duży Wybierz co najmniej 2 obrazy Skala obrazu wyjściowego Rozmiar piksela Zablokuj orientację rysowania Zmień rozmiar podanych obrazów lub przekonwertuj je na inne formaty. Możesz też edytować metadane EXIF, jeśli wybierzesz jeden obraz. Jeśli opcja ta jest włączona w trybie rysowania, ekran nie będzie się obracał. Przywracanie Utwórz kopię zapasową ustawień do pliku Przywróć ustawienia aplikacji z ostatniego pliku Automatycznie usuń tło Sprawdź nowe aktualizacje Tęcza Domyślny styl palety, pozwala dostosować wszystkie cztery kolory, inne pozwalają ustawić tylko kolor kluczowy Schemat umieszczający kolor źródłowy w kontenerze Scheme.primaryContainer Darowizna Orientacja obrazu Pozioma Pionowa Skaluj małe obrazy do dużych Kolejność zdjęć Normalny Rozmyte krawędzie Rysuje rozmyte krawędzie pod oryginalnym obrazem, aby wypełnić przestrzenie wokół niego zamiast pojedynczego koloru. Pikselizacja Zamień kolor Tolerancja Styl palety Punkt tonalny Neutralny Żywy Ekspresyjny Sałatka owocowa Wierność Zawartość Styl nieco bardziej chromatyczny niż monochromatyczny Głośny motyw, kolorowość jest maksymalna dla palety podstawowej, zwiększona dla innych Zabawny motyw — odcień koloru źródłowego nie pojawia się w motywie Motyw monochromatyczny, kolory są czysto czarne / białe / szare Schemat bardzo podobny do schematu treści Operuj na plikach PDF: Podgląd, Konwertuj na partię obrazów lub utwórz jeden z podanych zdjęć Ten moduł sprawdzania aktualizacji połączy się z GitHubem w celu sprawdzenia, czy dostępna jest nowa aktualizacja Uwaga Zanikające krawędzie wyłączony Losuj nazwę pliku Jeśli włączone, nazwa pliku wyjściowego będzie w pełni losowa Zapisano w folderze %1$s z nazwą %2$s Preset określa % rozmiaru pliku wyjściowego, tj. jeśli wybierzesz ustawienie wstępne 50 na obrazie o rozmiarze 5 MB, po zapisaniu otrzymasz obraz o rozmiarze 2,5 MB Jeśli wybrano ustawienie 125, obraz zostanie zapisany w rozmiarze 125% oryginalnego obrazu. W przypadku wybrania ustawienia 50 obraz zostanie zapisany w rozmiarze 50%. Tekst Skala czcionki Domyślna Używanie dużych czcionek może powodować usterki interfejsu użytkownika i problemy, których nie da się naprawić. Używaj ostrożnie. Aa Ąą Bb Cc Ćć Dd Ee Ęę Ff Gg Hh Ii Jj Kk Ll Łł Mm Nn Ńń Oo Óó Pp Qq Rr Ss Śś Tt Uu Vv Ww Xx Yy Zz Źź Żż 0123456789 !? Przytnij obraz Spowoduje to przywrócenie ustawień domyślnych. Należy pamiętać, że nie można tego cofnąć bez pliku kopii zapasowej wspomnianego powyżej. Uszkodzony plik lub brak kopii zapasowej Obecnie format %1$s pozwala jedynie na odczyt metadanych EXIF na Androidzie. Obraz wyjściowy nie będzie zawierał metadanych po zapisaniu. Czcionka Wartość %1$s oznacza szybką kompresję, co skutkuje stosunkowo dużym rozmiarem pliku. Wartość %2$s oznacza wolniejszą kompresję, co skutkuje mniejszym plikiem. Oryginalne metadane obrazu zostaną zachowane Przezroczyste przestrzenie wokół obrazu zostaną przycięte Dzięki temu aplikacja może automatycznie zbierać raporty o awariach Podgląd maski Jeśli opcja ta jest włączona, wszystkie niezamaskowane obszary będą filtrowane zamiast domyślnego zachowania. Rysuj półprzezroczyste, zaostrzone ścieżki zakreślacza Zamierzasz usunąć wybraną maskę filtru. Operacji tej nie można cofnąć Czekaj Pełny filtr Początek Środek Koniec Zastosuj dowolne łańcuchy filtrów do podanych obrazów lub pojedynczego obrazu Konwersja plików PDF na obrazy w podanym formacie wyjściowym Spakuj podane obrazy do wyjściowego pliku PDF Wysiłek Kolor maski Maski Podyskutuj o aplikacji i poznaj opinie innych użytkowników. Można tam również uzyskać aktualizacje wersji beta. Proporcje Użyj tego typu maski, aby utworzyć maskę z danego obrazu, zauważ, że POWINNA ona mieć kanał alfa. Ustawienia przywrócone pomyślnie Usuń Emocje Wybrany schemat kolorów zostanie usunięty. Operacji tej nie można cofnąć Usuń schemat Jedzenie i picie Przyroda i zwierzęta Analityka Zezwalaj na zbieranie anonimowych statystyk użytkowania aplikacji Zapisywanie prawie zakończone. Anulowanie będzie wymagało ponownego zapisania. Maska %d Odwróć typ wypełnienia Filtr maskujący Zastosuj łańcuchy filtrów na danych maskowanych obszarach, każdy obszar maski może określić własny zestaw filtrów Dodaj maskę Narysowana maska filtra zostanie wyrenderowana, aby pokazać przybliżony wynik Usuń maskę Proste warianty Neon Pióro Rozmycie prywatności Zakreślacz Dodaj trochę blasku do swoich rysunków Domyślny, najprostszy - tylko kolor Podobne do rozmycia prywatności, ale pikselizuje zamiast rozmywać Rozmywa obraz pod narysowaną ścieżką, aby zabezpieczyć wszystko, co chcesz ukryć. Kontenery Rysuj cień za kontenerami Suwaki Przełączniki Oba Pływające przyciski Przyciski Rysuj cień za suwakami Rysuj cień za przełącznikami Rysuj cień za pływającymi przyciskami akcji Rysuj cień za paskami aplikacji Wartość z zakresu %1$s - %2$s Obrót automatyczny Prostokąt (kontury) Prostokąt Lasso Lepsze metody skalowania obejmują ponowne próbkowanie Lanczos i filtry Mitchell-Netravali Najprostszy tryb skalowania Androida, który jest używany w prawie wszystkich aplikacjach Pędzel przywróci tło zamiast je wymazywać Najlepszy Nie znaleziono katalogu \"%1$s\", zmieniliśmy go na domyślny, zapisz plik ponownie Schowek Automatyczne przypinanie Automatycznie dodaje zapisany obraz do schowka, jeśli jest włączone Wibracje Siła wibracji Aby nadpisać pliki, musisz użyć źródła obrazu \"Explorer\". Spróbuj ponownie wybrać obrazy, zmieniliśmy źródło obrazu na wymagane Nadpisz pliki Oryginalny plik zostanie zastąpiony nowym zamiast zapisywania w wybranym folderze. Ta opcja wymaga, aby źródłem obrazu był \"Explorer\" lub GetContent, po przełączeniu tej opcji zostanie ona ustawiona automatycznie. Pusty Sufiks Swobodny Rysuje zamkniętą, wypełnioną ścieżkę według podanej ścieżki Tryb rysowania ścieżki Strzałka z podwójną linią Swobodne rysowanie Podwójna strzałka Strzałka liniowa Strzałka Linia Rysuje ścieżkę jako wartość wejściową Rysuje ścieżkę od punktu początkowego do końcowego jako linię Rysuje strzałkę wskazującą od punktu początkowego do punktu końcowego jako linię Rysuje strzałkę wskazującą z danej ścieżki Rysuje podwójną strzałkę od punktu początkowego do końcowego jako linię Rysuje podwójną strzałkę wskazującą z danej ścieżki Owal Owal (kontury) Rysuje prostokąt od punktu początkowego do punktu końcowego Rysuje owal od punktu początkowego do punktu końcowego Rysuje kontury owala od punktu początkowego do punktu końcowego Rysuje kontury prostokąta od punktu początkowego do punktu końcowego Tryb skalowania Bilinearny Hann Hermite Lanczos Mitchell Najbliższy Spline Prosty Domyślna wartość Catmull Dwusześcienny Interpolacja liniowa (lub dwuliniowa, w dwóch wymiarach) jest zazwyczaj dobra do zmiany rozmiaru obrazu, ale powoduje pewne niepożądane zmiękczenie szczegółów i nadal może być nieco postrzępiona Jeden z prostszych sposobów na zwiększenie rozmiaru, zastępując każdy piksel liczbą pikseli tego samego koloru Metoda płynnej interpolacji i ponownego próbkowania zestawu punktów kontrolnych, powszechnie stosowana w grafice komputerowej do tworzenia gładkich krzywych. Matematyczna technika interpolacji, która wykorzystuje wartości i pochodne w punktach końcowych segmentu krzywej w celu wygenerowania gładkiej i ciągłej krzywej Metoda ponownego próbkowania, która utrzymuje wysoką jakość interpolacji poprzez zastosowanie ważonej funkcji sinc do wartości pikseli Metoda ponownego próbkowania wykorzystująca filtr splotowy z regulowanymi parametrami w celu uzyskania równowagi między ostrością a antyaliasingiem w skalowanym obrazie Wykorzystuje zdefiniowane fragmentarycznie funkcje wielomianowe do płynnej interpolacji i aproksymacji krzywej lub powierzchni, zapewniając elastyczną i ciągłą reprezentację kształtu Tylko przytnij Zapis do pamięci masowej nie zostanie wykonany, obraz będzie umieszczony tylko w schowku OCR (rozpoznawanie tekstu) Rozpoznawanie tekstu z danego obrazu, obsługa ponad 120 języków Obraz nie zawiera tekstu lub aplikacja go nie znalazła Dokładność: %1$s Typ rozpoznawania Szybki Standardowy Siatka pozioma Siatka pionowa Tryb ściegu Liczba wierszy Liczba kolumn Umożliwia przyjęcie ramki ograniczającej dla orientacji obrazu Rysuj cień za przyciskami Paski aplikacji Funkcja okienkowania często stosowana w przetwarzaniu sygnału w celu zminimalizowania wycieków widmowych i poprawy dokładności analizy częstotliwości poprzez zwężanie krawędzi sygnału. Tylko wykrywanie orientacji i skryptów Automatyczny Pojedyncza kolumna Pojedynczy blok tekstu pionowego Pojedyncza linia Pojedyncze słowo Surowy wiersz Ilość Używa przełącznika podobnego do Google Pixel Oceń aplikację Oceń Automatyczne wykrywanie orientacji i skryptów Zrób zdjęcie aparatem. Należy pamiętać, że z tego źródła obrazu można uzyskać tylko jeden obraz Tylko automat Pojedynczy blok Słowo w kółku Obszerny tekst Wykrywanie krawędzi i skryptu obszernego tekstu Pojedynczy znak Wszystkie Szum Losuj Brak danych Pobrane języki Egzekwowanie jasności Ekran Opóźnienie ramki Skala szarości Rozpraszanie Bayer Four By Four Rozpraszanie Sierra Rozpraszanie Sierra Lite Rozpraszanie Atkinson Rozpraszanie Bayer Eight By Eight Rozpraszanie Two Row Sierra Rozpraszanie Stucki Anaglif Ukrywa zawartość aplikacji w ostatnich aplikacjach. Nie można jej przechwycić ani nagrać. Sortowanie pikseli Glitch Ziarno Czy chcesz usunąć dane szkoleniowe OCR języka \"%1$s\" dla wszystkich typów rozpoznawania, czy tylko dla wybranego (%2$s)? Wybrany Kreator gradientów Twórz gradient o danym rozmiarze wyjściowym z niestandardowymi kolorami i typem wyglądu Ta aplikacja jest całkowicie darmowa, jeśli chcesz, aby stała się większa, oznacz projekt na Github 😄. Promieniowy Liniowy Stożkowy Rodzaj gradientu Środek X Środek Y Tryb kafelków Powtarzane Lustrzane odbicie Uchwyt Kalkomania Ograniczniki kolorów Dodaj kolor Właściwości Kwantyzator Rozpraszanie Rozpraszanie Bayer Two By Two Rozpraszanie Bayer Three By Three Rozpraszanie Jarvis Judice Ninke Rozpraszanie Burkes Rozpraszanie False Floyd Steinberg Rozpraszanie losowe Rozpraszanie z lewej do prawej Rozpraszanie Simple Threshold Sigma Spatial Sigma Rozmycie medianą Nakładka gradientu Skomponuj dowolny gradient na danych obrazach Przekształcenia Aparat Znaki wodne Nakłada konfigurowalne tekstowe/graficzne znaki wodne Powtarzanie znaku wodnego Powtarza znak wodny na obrazie zamiast pojedynczego w danej pozycji Przesunięcie X Przesunięcie Y Typ znaku wodnego Obraz ten zostanie użyty jako wzorzec dla znaku wodnego Kolor tekstu Tryb nakładania Narzędzia GIF Konwertuj obrazy na GIF-y lub wyodrębnij pojedynczą klatkę z animacji GIF Konwertuj plik GIF do partii obrazów Konwertuj partię obrazów do pliku GIF Obrazy do GIF Wybierz obraz GIF, aby rozpocząć Użyj rozmiaru pierwszej ramki Zastąp określony rozmiar wymiarami pierwszej ramki Liczba powtórzeń millis FPS Użyj Lasso Używa Lasso jak w trybie rysowania do wymazywania Podgląd kanału alfa oryginalnego obrazu Pobierz Użyj przełącznika pikseli Slajd Obok siebie Przełączanie po stuknięciu Przezroczystość Nadpisano plik o nazwie %1$s w oryginalnym miejscu docelowym Lupa Włącza lupę w trybach rysowania dla lepszej dostępności. Wymuś wartość początkową Wymusza wstępne sprawdzenie widżetu exif Zezwalaj na wiele języków B Spline Natywne rozmycie stosu Zmiana zabarwienia Konfetti Konfetti będzie wyświetlane podczas zapisywania, udostępniania i innych podstawowych działań Tryb bezpieczny Wyjdź Jeśli teraz opuścisz podgląd, będziesz musiał ponownie dodać obrazy Do poprawnego działania aplikacji Tesseract OCR konieczne jest pobranie na urządzenie dodatkowych danych szkoleniowych (%1$s). \nCzy chcesz pobrać dane %2$s? Brak połączenia, sprawdź i spróbuj ponownie, aby pobrać modele trenujące Dostępne języki Tryb segmentacji GIF do obrazów Rozpraszanie Floyd Steinberg Wykorzystuje zdefiniowane fragmentarycznie funkcje wielomianu dwumianowego do płynnej interpolacji i aproksymacji krzywej lub powierzchni, elastycznej i ciągłej reprezentacji kształtu Rozszerzony Glitch Przesuń kanał X Przesuń kanał Y Rozmiar korupcji Korupcyjna zmiana X Korupcyjna zmiana Y Tent Blur Szczyt Spód Aldridge\'a Odciąć Uchimurę Mobiusa Przemiana Szczyt Deuteranomalia Side Fade Strona Wytrzymałość Ziarno Pomarańczowa mgła Kolorowy wir Miękkie wiosenne światło Jesienne tony Karmelowa ciemność Portal kosmiczny Ulubiony Kształt ikony Drago Anomalia kolorystyczna Erodować Dyfuzja anizotropowa Dyfuzja Przewodzenie Poziomy nachylenie wiatru Szybkie rozmycie dwustronne Rozmycie Poissona Logarytmiczne mapowanie tonów Krystalizować Kolor obrysu Szkło fraktalne Amplituda Marmur Turbulencja Olej Efekt wody Rozmiar Częstotliwość X Częstotliwość Y Amplituda X Amplituda Y Zniekształcenie Perlina Hable Filmowe mapowanie tonów Mapowanie tonów Heji-Burgess Mapowanie tonów filmowych ACES Mapowanie tonów wzgórza ACES Prędkość Usuń zamglenie Omega Matryca kolorów 4x4 Matryca kolorów 3x3 Proste efekty Polaroid Tritanomalia Protanomalia Klasyczny Browni Koda Chrome Nocna wizja Ciepły Fajny Tritanopia Deutaronotopia Protanopia Achromatomalia Achromatopsja Dodaje pojemnik z wybranym kształtem pod ikonami Wyostrzyć Pastel Różowy sen Złota godzina Gorące lato Fioletowa Mgła Wschód słońca Lawendowy sen Cyberpunk Lekka lemoniada Spektralny Ogień Magia Nocy Krajobraz fantasy Eksplozja kolorów Gradient elektryczny Futurystyczny gradient Zielone słońce Tęczowy Świat Głęboki fiolet Czerwony wir Kod cyfrowy Bokeh Emotikony paska aplikacji będą zmieniać się losowo Losowe emotikony Nie można używać losowych emotikonów, gdy są one wyłączone Nie można wybrać emotikonów, gdy włączone są losowe emotikony Stary telewizor Mieszaj rozmycie Nie dodano jeszcze żadnych ulubionych filtrów Format obrazu Obrazy nadpisane w oryginalnym miejscu docelowym Emoji jako schemat kolorów Nie można zmienić formatu obrazu, gdy włączona jest opcja nadpisywania plików Używa koloru podstawowego emoji jako schematu kolorów aplikacji zamiast ręcznie zdefiniowanego Tworzy paletę \"Material You\" z obrazu Ciemne Kolory Wykorzystuje schemat kolorów trybu nocnego zamiast wariantu światła Skopiuj jako kod \"Jetpack Compose\" Rozmycie Pierścienia Rozmycie krzyżowe Rozmycie w kółko Rozmycie gwiazdami Liniowa zmiana nachylenia Tagi Do Usunięcia Konwertuj partię obrazów do pliku APNG Narzędzia APNG Konwertuj obrazy na obraz APNG lub wyodrębniaj klatki z danego obrazu APNG Rozmycie w ruchu Konwertuj plik APNG na partię zdjęć APNG do obrazów Aby rozpocząć, wybierz obraz APNG Obrazy do APNG Archiwum ZIP Utwórz plik ZIP z podanych plików lub obrazów Przeciągnij szerokość uchwytu Eksplozja Deszcz Typ konfetti Uroczysty Narożniki Narzędzia JXL Transkodowanie JXL ~ JPEG bez utraty jakości lub konwersja animacji GIF/APNG do JXL JXL do JPEG Bezstratne transkodowanie z formatu JXL do JPEG Wykonaj bezstratne transkodowanie z JPEG do JXL JPEG do JXL Wybierz obraz JXL, aby rozpocząć Umożliwia aplikacji automatyczne wklejanie danych ze schowka, dzięki czemu pojawią się one na ekranie głównym i będzie można je przetworzyć Wybór wielu mediów Wybierz pojedyncze medium Wybierz Szybkie rozmycie Gaussa 3D Szybkie rozmycie Gaussa 2D Szybkie rozmycie Gaussa 4D Konfiguracja kanałów Dziś Wczoraj Wbudowany selektor Selektor obrazów Image Toolbox Brak uprawnień Żądanie Automatyczne wklejanie Harmonizacja kolorów Harmonizacja poziomów Lanczos Bessel GIF do JXL APNG do JXL JXL do obrazów Konwersja animacji JXL do partii obrazów Konwersja obrazów APNG do animowanych obrazów JXL Konwertuj obrazy GIF na animowane obrazy JXL Obrazy do JXL Konwersja partii zdjęć do animacji JXL Zachowanie Pomiń wybieranie plików Selektor plików zostanie wyświetlony natychmiast, jeśli będzie to możliwe na wybranym ekranie Generowanie podglądów Włącza generowanie podglądu, co może pomóc uniknąć awarii na niektórych urządzeniach, a także wyłącza niektóre funkcje edycji w ramach opcji pojedynczej edycji Kompresja stratna Używa kompresji stratnej w celu zmniejszenia rozmiaru pliku zamiast bezstratnej Metoda ponownego próbkowania, która utrzymuje wysoką jakość interpolacji poprzez zastosowanie funkcji Bessela (jinc) do wartości pikseli Typ kompresji Kontroluje prędkość dekodowania obrazu wynikowego, powinno to pomóc w szybszym otwieraniu obrazu wynikowego, wartość %1$s oznacza najwolniejsze dekodowanie, podczas gdy %2$s - najszybsze, to ustawienie może zwiększyć rozmiar obrazu wyjściowego Sortowanie Data Data (odwrotnie) Nazwa Nazwa (odwrotnie) Spróbuj ponownie Pokaż ustawienia w orientacji poziomej Jeśli ta opcja jest wyłączona, to w trybie poziomym ustawienia będą otwierane na przycisku w górnym pasku aplikacji, jak zawsze, zamiast stałej, widocznej opcji. Rodzaj przełącznika Skomponuj Ustawienia trybu pełnoekranowego Włączenie tej opcji spowoduje, że strona ustawień będzie zawsze otwierana jako pełnoekranowa zamiast wysuwanego arkusza szuflady Przełącznik Material You Przełącznik \"Jetpack Compose Material You\" Maks. Zmiana rozmiaru zakotwiczenia Pixel Fluent Przełącznik oparty na systemie projektowym „Fluent” Cupertino Przełącznik oparty na systemie projektowym „Cupertino” Obrazy do SVG Konwertuj obrazy do grafik SVG Użyj próbkowanej palety Jeśli ta opcja jest włączona, próbkowana będzie paleta kwantyzacji Pomiń ścieżkę Obniżanie skali obrazu Obraz zostanie zmniejszony do niższych wymiarów przed przetworzeniem, co pomoże narzędziu pracować szybciej i bezpieczniej Minimalny współczynnik kolorów Próg linii Próg kwadratowy Tolerancja zaokrąglania współrzędnych Skala ścieżki Zresetuj właściwości Wszystkie właściwości zostaną ustawione na wartości domyślne, zauważ, że tej akcji nie można cofnąć Szczegółowy Nie zaleca się używania tego narzędzia do śledzenia dużych obrazów bez skalowania w dół, ponieważ może to spowodować awarię i wydłużyć czas przetwarzania Domyślna szerokość wiersza Tryb silnika Starszy Sieć LSTM Starszy oraz LSTM Przekonwertuj Konwersja partii obrazów do podanego formatu Próbek na piksel Dodaj nowy folder Bitów na próbkę Kompresja Interpretacja fotometryczna Przesunięcie paska Y Cb Cr Próbkowanie cząstkowe Pozycjonowanie Y Cb Cr Rozdzielczość X Rozdzielczość Y Jednostka rozdzielczości Funkcja przenoszenia Biały punkt Chromatyczność podstawowa Współczynniki Y Cb Cr Data Czas Opis obrazu Utwórz Model Oprogramowanie Twórca Prawa autorskie Wersja Exif Wersja Flashpix Przestrzeń kolorów Gamma Wiersze na pasek Liczba bajtów paska Format wymiany JPEG Długość formatu wymiany JPEG Referencyjna czerń i biel Czas otwarcia migawki Wartość jasności Wartość odchylenia ekspozycji Maksymalna wartość przysłony Wartość przysłony Lampa błyskowa Skompresowane bity na piksel Uwaga producenta Komentarz użytkownika Powiązany plik dźwiękowy Data Czas Oryginał Cyfrowa data i godzina Czas przesunięcia Czas przesunięcia Oryginał Cyfrowy czas przesunięcia Czas sub-sekundowy Czas sub-sekundowy Oryginał Czas ekspozycji Numer F Program ekspozycji Ostrość Nazwa użytkownika aparatu Nasycenie Specyfikacja obiektywu Producent obiektywu Model obiektywu Numer seryjny obiektywu Rozmiar czcionki Rozmiar znaku wodnego Skaner dokumentów Dotknij, aby zacząć skanować Rozpocznij skanowanie Zapisz jako PDF Udostępnij jako PDF Poniższe opcje dotyczą zapisywania skanów jako obrazów, a nie pliku PDF Włącza antyaliasing aby uniknąć ostrych krawędzi Skanuj dokumenty i utwórz PDF albo zapisz je jako oddzielne zdjęcia Narzędzia koloru Kolor do zmieszania Zeskanowany kod QR nie jest prawidłowym szablonem filtra Skanuj kod QR Zeskanuj dowolny kod kreskowy, aby zastąpić zawartość pola, lub wpisz coś, aby wygenerować nowy kod kreskowy z wybranym typem. Nakładanie obrazów Wybierz pliki WEBP, aby zacząć Sprawdź informacje o kolorze, mieszaj kolory, generuj odcienie koloru Informacje o kolorze Wybrany kolor Przyznaj uprawnienia dostępu do kamery aby skanować kod QR Dzielenie obrazu Potnij obraz na części w pionie lub w poziomie Konwersja formatów Zmień format plików Generator szumu Generuj różne szumy, np. takie jak Perlin Częstotliwość Typ szumu Układaj obrazy jeden na drugim w wybranym trybie nakładania Typ kolażu Utwórz kolaż Twórz kolaże z maksymalnie 20 obrazami Konwertuj pliki GIF do animowanych obrazów WEBP Narzędzia WEBP Konwertuj obrazy do animowanych plików WEBP albo lub wyodrębnij pojedyncze klatki z animacji WEBP Konwertuj serię obrazów do animacji WEBP Konwertuj animację WEBP do serii obrazów GIF do WEBP WEBP do obrazów Obrazy do WEBP Kod QR i kod kreskowy Zeskanuj kod QR i otwórz jego zawartość lub wklej ciąg znaków, aby wygenerować nowy Opis kodu QR Ten obraz zostanie wykorzystany do wygenerowania histogramów RGB i jasności W ustawieniach udziel kamerze uprawnień skanowania dla skanera dokumentów B-Spline Hamming Hanning Blackman Welch Sfinks Bartlett Robidoux Ostry Robidoux Spline 16 Kajzer Bartlett-Hann Box Bohman Lanczos 2 Jinc Metoda interpolacji wykorzystująca funkcję Gaussa, przydatna do wygładzania i redukcji szumów w obrazach Metoda interpolacji oparta na splajnie, która zapewnia gładkie wyniki przy użyciu filtra 16-krotnego Metoda interpolacji oparta na splajnie, która zapewnia gładkie wyniki przy użyciu filtra 36-krotnego Metoda interpolacji oparta na splajnie, która zapewnia gładkie wyniki przy użyciu filtra 64-krotnego Metoda interpolacji wykorzystująca okno Kajzera, zapewniająca dobrą kontrolę nad kompromisem między szerokością płata głównego a poziomem płata bocznego Hybrydowa funkcja okna łącząca okna Bartletta i Hanna, używana do redukcji wycieków widmowych w przetwarzaniu sygnału Prosta metoda ponownego próbkowania, która wykorzystuje średnią najbliższych wartości pikseli, co często skutkuje blokowym wyglądem Metoda ponownego próbkowania wykorzystująca 2-płatowy filtr Lanczosa do wysokiej jakości interpolacji z minimalną ilością artefaktów Wariant filtru Lanczos 4, który wykorzystuje funkcję jinc, zapewniając wysokiej jakości interpolację z minimalnymi artefaktami Delikatny Haasn Filtr resamplingu zaprojektowany przez Haasn do płynnego i pozbawionego artefaktów skalowania obrazu Lagrange 2 Zapasowe modele OCR Importuj Eksportuj U góry po prawej Na dole po lewej Na dole po prawej U góry pośrodku W środku po prawej Pośrodku na dole W środku po lewej Dithering klastrowy 4x4 Dithering klastrowy 8x8 Dithering Yililoma Tony Nie można używać monet, gdy włączone są dynamiczne kolory Amatorka Miss Etikate Tryb krawędziowy Lanczos 6 Jinc Grupa Sześcienny Wariant okna Hanna, powszechnie stosowany do redukcji wycieków widmowych w aplikacjach przetwarzania sygnału Metoda ponownego próbkowania wykorzystująca 3-płatowy filtr Lanczosa do wysokiej jakości interpolacji z minimalnymi artefaktami Wariant filtra Lanczos 2, który wykorzystuje funkcję jinc, zapewniając wysokiej jakości interpolację z minimalnymi artefaktami Wariant filtru Lanczos 3, który wykorzystuje funkcję jinc, zapewniając wysokiej jakości interpolację z minimalnymi artefaktami. Automatyczne kadrowanie Trójkąt Opcje powinny być wprowadzane zgodnie z tym wzorem: „--{nazwa_opcji} {wartość}\ Dithering klastrowy 2x2 Umożliwia pobieranie podglądu linków w miejscach, w których można uzyskać tekst (QRCode, OCR itp.). Wskaźnik ekspozycji Rysuje obrysowany wielokąt od punktu początkowego do punktu końcowego Nazwa szablonu Min Kontrast Przytnij do zawartości Jako plik Lanczos 3 Jinc Filtr interpolacyjny Lagrange\'a rzędu 3, oferujący lepszą dokładność i gładsze wyniki skalowania obrazu Konfiguracja płaszczyznowa Bieżący tekst będzie powtarzany do końca ścieżki zamiast jednorazowego rysowania Narysuj wielokąt foremny Narysuj tekst na ścieżce z podaną czcionką i kolorem Utwórz szablon Wariant eliptycznej średniej ważonej (EWA) filtra Lanczos 3 Jinc do wysokiej jakości resamplingu z redukcją aliasingu Ginseng Filtr resamplingu przeznaczony do wysokiej jakości przetwarzania obrazu z dobrą równowagą ostrości i gładkości Nakreślona gwiazda Wariacja Wierzchołki Eliptyczna średnia ważona (EWA) - wariant filtra Ginseng poprawiający jakość obrazu Kwadrat Dołącz do naszego czatu, gdzie możesz omówić wszystko, co chcesz, a także zajrzyj na kanał CI, gdzie publikuję bety i ogłoszenia Usuń szablon Filtr ponownego próbkowania Lanczos o wyższym rzędzie 6, zapewniający ostrzejsze i dokładniejsze skalowanie obrazu Lagrange 3 Kolor obramowania Nakreślony wielokąt Rysuje nakreśloną gwiazdę od punktu początkowego do punktu końcowego Uzupełniający Wzorzec CFA ISO Powtórz tekst Rozmiar kreski Przytrzymaj obraz, aby go zamienić, przesuń i powiększ, aby dostosować pozycję Moc lampy błyskowej Niezdolność do postrzegania odcieni zieleni Lanczos 6 Image Toolbox na Telegramie 🎉 Opcje użytkownika Niezdolność do postrzegania odcieni czerwieni Kawa Obraz ten zostanie użyty jako powtarzający się wpis narysowanej ścieżki Narysuj wielokąt, który będzie regularny zamiast dowolnego kształtu Wyrównanie histogramu Adaptive LAB Metoda wykorzystująca funkcję kwadratową do interpolacji, zapewniająca płynne i ciągłe wyniki Ten obraz będzie używany do podglądu tego szablonu filtra Triadyczny Dodano szablon filtra o nazwie „%1$s” (%2$s) Treść kodu Kwadryczny Gaussowski Spline 64 Spline 36 Lanczos 3 Jinc EWA Lanczos 4 Jinc Wykorzystuje zdefiniowane fragmentarycznie funkcje wielomianowe do płynnej interpolacji i aproksymacji krzywej lub powierzchni, elastycznej i ciągłej reprezentacji kształtu Ostrzejszy wariant metody Robidoux, zoptymalizowany pod kątem wyraźnej zmiany rozmiaru obrazu Funkcja okna używana do redukcji wycieków widmowych, zapewniająca dobrą rozdzielczość częstotliwości w aplikacjach przetwarzania sygnału Wariant eliptycznej średniej ważonej (EWA) filtra Hanninga do płynnej interpolacji i ponownego próbkowania Ostry Robidoux EWA Eliptyczna średnia ważona (EWA) - wariant filtru Quadric do płynnej interpolacji Eliptyczna średnia ważona (EWA) - wariant filtra Ostry Lanczos zapewniający ostre rezultaty przy minimalnej ilości artefaktów Funkcja okna, która zapewnia dobrą rozdzielczość częstotliwości poprzez minimalizację wycieku widmowego, często używana w przetwarzaniu sygnału Wyrównaj histogram Adaptacyjny HSL Nie używaj schematu Color Blind Ulepszony olej Przeniesienie palety Clahe Oklab Harmonie kolorów Analogiczny + Uzupełniający Cieniowanie kolorów Clahe HSL Rodzaj fraktala Narysuj zwykłą gwiazdę Narysuj gwiazdę, która będzie regularna, a nie dowolna Podgląd linków Zmniejszona wrażliwość na wszystkie kolory Ogniskowa Utwórz nowy Wymuszanie punktów do granic obrazu CLAHE Liczba pojemników Lokalizacja obiektu Metoda pomiaru Źródło pliku Niestandardowo zrenderowano Tryb ekspozycji Balans bieli Użyj wybranego obrazu, aby narysować go wzdłuż danej ścieżki Rysuje trójkąt od punktu początkowego do końcowego Rysuje trójkąt od punktu początkowego do końcowego Nakreślony trójkąt Rysuje wielokąt od punktu początkowego do punktu końcowego Wielokąt Rysuje gwiazdę od punktu początkowego do końcowego Gwiazda Współczynnik promienia wewnętrznego CLAHE LAB Kolor do pominięcia Szablon Nie dodano szablonu filtrów Wybrany plik nie zawiera danych szablonu filtra Filtr szablonu Jako obraz kodu QR Zapisz jako plik Zapisz jako obraz kodu QR Podgląd filtra Lanczos 2 Lanczos 3 Lanczos 4 Funkcja okna używana do redukcji wycieków widmowych poprzez zwężanie krawędzi sygnału, przydatna w przetwarzaniu sygnału Zaawansowana metoda ponownego próbkowania zapewniająca wysokiej jakości interpolację z minimalną ilością artefaktów Trójkątna funkcja okna używana w przetwarzaniu sygnału w celu zmniejszenia wycieku widmowego Wysokiej jakości metoda interpolacji zoptymalizowana pod kątem naturalnej zmiany rozmiaru obrazu, równoważąca ostrość i gładkość Metoda ponownego próbkowania, która wykorzystuje 4-płatowy filtr Lanczosa do wysokiej jakości interpolacji z minimalnymi artefaktami Hanning EWA Robidoux EWA Blackman EWA Eliptyczna średnia ważona (EWA) - wariant filtra Blackmana minimalizujący artefakty dzwonienia Quadric EWA Eliptyczna średnia ważona (EWA) - wariant filtru Ostry Robidoux zapewniający ostrzejsze rezultaty Ginseng EWA Ostry Lanczos EWA Najostrzejszy Lanczos 4 EWA Wariant eliptycznej średniej ważonej (EWA) filtru Najostrzejszy Lanczos 4 do wyjątkowo ostrego resamplingu obrazu Delikatny Lanczos EWA Wariant eliptycznej średniej ważonej (EWA) filtru Delikatny Lanczos do wygładzania obrazów Odrzuć na zawsze Dodaj obraz Clahe HSV Wyrównaj histogram Adaptacyjny HSV Wybierz tryb, aby dostosować kolory motywu do wybranego wariantu ślepoty barw Trudności w rozróżnianiu odcieni czerwieni i zieleni Trudności w rozróżnianiu odcieni zieleni i czerwieni Kolorowy plakat Trójtonowy Trzeci kolor Nie wybrano żadnych ulubionych opcji, dodaj je na stronie narzędzi Dodaj do ulubionych Odcienie Barwy Łączenie kolorów 512x512 2D LUT Złoty las Zielonkawy Retro żółty Linki Pliki ICO można zapisywać tylko w maksymalnym rozmiarze 256 x 256 Ostatnio używane Auto Pokaż wszystko Typ obrotu Jitter Wyrównanie Własna nazwa pliku Zapisane w folderze o niestandardowej nazwie Histogram Histogram obrazu RGB lub jasności ułatwiający dokonywanie regulacji Opcje Tesseract Wolne rogi Maska Delikatny blask CLAHE LUV Ukryj pasek stanu Wzmocnij Wytrzymałość ping ponga Kanał Cl Otrzymuj powiadomienia o nowych wersjach aplikacji i czytaj ogłoszenia Oktawy Lakunarność Zastosuj niektóre zmienne wejściowe dla silnika tesseract Ukryj wszystko Wypełnienie z uwzględnieniem zawartości pod narysowaną ścieżką Punkty nie będą ograniczone granicami obrazu, co jest przydatne do bardziej precyzyjnego korygowania perspektywy Pozycja Eliptyczna średnia ważona (EWA) - wariant filtra Robidoux do wysokiej jakości ponownego próbkowania Interpolacja sześcienna zapewnia płynniejsze skalowanie, biorąc pod uwagę najbliższe 16 pikseli, dając lepsze wyniki niż interpolacja dwuliniowa Obraz docelowy Podział uzupełniający Tetradyczny Zamierzasz usunąć wybrany filtr szablonu. Tej operacji nie można cofnąć Gotham Funkcja okna zaprojektowana w celu zapewnienia dobrej rozdzielczości częstotliwości przy zmniejszonym wycieku widmowym, często używana w aplikacjach przetwarzania sygnału Filtr interpolacyjny Lagrange\'a rzędu 2, odpowiedni do wysokiej jakości skalowania obrazu z płynnymi przejściami Prosty szkic Niezdolność do postrzegania niebieskich odcieni Kolory będą dokładnie takie, jak ustawiono w motywie Trudności w rozróżnianiu odcieni niebieskiego i żółtego Całkowita ślepota barw, widzenie tylko odcieniach szarości Wariant filtru Lanczos 6 wykorzystujący funkcję Jinc w celu poprawy jakości resamplingu obrazu Prosty stary telewizor HDR U góry po lewej Clahe Jzazbz Analogiczny Clahe Oklch Pośrodku Polka Dot Kadrowanie obrazu według wielokąta, koryguje również perspektywę Wybierz lokalizację i nazwę pliku, które zostaną użyte do zapisania bieżącego obrazu Dopasuj obraz do podanych wymiarów i zastosowanie rozmycia lub koloru tła Ukryj pasek nawigacji Wytrzymałość ważona Ślad GPS Wybierz plik, aby obliczyć jego sumę kontrolną na podstawie wybranego algorytmu Wprowadź tekst, aby obliczyć jego sumę kontrolną na podstawie wybranego algorytmu Źródłowa suma kontrolna Suma kontrolna do porównania Zgadza się! Różnica Sumy kontrolne nie są równe, plik może być niebezpieczny! Sumy kontrolne są równe, plik jest bezpieczny Gradienty siatki Algorytm Porównywanie sum kontrolnych, obliczanie skrótów, tworzenie ciągów szesnastkowych z plików przy użyciu różnych algorytmów haszujących Kierunek GPS Img Odniesienie do wysokości GPS Przestrzenna charakterystyka częstotliwościowa Czułość widmowa Wysokość GPS Satelity GPS Stan GPS Tryb pomiaru GPS GPS DOP Odniesienie do prędkości GPS Prędkość GPS Odniesienie do śladu GPS Odniesienie do kierunku GPS Img Data mapy GPS Długość geograficzna GPS Narzędzie sum kontrolnych Przelicz Zobacz kolekcję gradientów siatki online Suma kontrolna Znacznik czasu GPS Wymiar piksel X Wymiar piksel Y Digitalizowany czas podsekundowy Czułość fotograficzna OECF Gaussowskie rozmycie pudełkowe Wybierz poniższy filtr, aby użyć go jako pędzla na rysunku Malowanie piaskiem Języki zaimportowane pomyślnie Wariant Transferu palety 3D LUT Docelowy plik 3D LUT (.cube / .CUBE) LUT Kolory jesieni Materiał filmowy 50 Kodak Uzyskaj neutralny obraz LUT Sztuka pop Skrót tekstowy Rozdzielczość X płaszczyzny ogniskowej Czarny kapelusz Po wybraniu obrazu do otwarcia (podglądu) w ImageToolbox, zamiast podglądu zostanie otwarty arkusz wyboru edycji Obszar przedmiotu Odległość przedmiotu Tryb pomiaru Jednostka rozdzielczości płaszczyzny ogniskowej Rodzaj przechwytywania sceny Lustro 101 Docelowa długość geograficzna GPS Docelowa odległość GPS Odniesienie do docelowej odległości GPS Metoda przetwarzania GPS Namiar GPS Szybkie liniowe rozmycie gaussowskie Wybierz jeden filtr, aby użyć go jako farby Schemat kompresji TIFF Prawe obramowanie czujnika Górne obramowanie czujnika Wyrównaj histogram Adaptacyjny LUV Domyślny kolor rysowania Pokaż paski systemowe poprzez przesunięcie Krzywe tonalne Rozmiar szczeliny Narzędzie zostanie dodane do ekranu głównego programu uruchamiającego jako skrót, użyj go w połączeniu z ustawieniem „Pomiń wybieranie plików”, aby osiągnąć pożądane zachowanie Ramki będą przechodzić jedna w drugą Stemplowany Umożliwia przesuwanie w celu wyświetlenia pasków systemowych, jeśli są ukryte Liczba klatek przejścia Selektory kompaktowe Material 2 Wklej Base64 Pokaż Zastosuj Licencje na oprogramowanie napisane otwarty kodem źródłowym Warstwy na tle Działania Base64 Wyświetl licencje bibliotek napisanych otwartym kodem źródłowym używanych w tej aplikacji Tryb warstw z możliwością dowolnego umieszczania obrazów, tekstu i innych elementów Dodaj obrys Rozmiar obrysu Kolor obrysu Rotacja Wolne oprogramowanie (Partner) Więcej przydatnego oprogramowania w kanale partnerskim aplikacji na Androida Współczynnik zygzaka Odniesienie do docelowej długości geograficznej GPS Prędkość ISO Szerokość geograficzna yyy Przejście Tytuł ekranu głównego Domyślny rozmiar kadrowania Pobierz kolekcję LUT, które możesz zastosować po pobraniu Odniesienie do docelowej szerokości geograficznej GPS Szerokość komórki Styl linii Aspekt Ramki Grupuje narzędzia na ekranie głównym według ich typu zamiast niestandardowego układu listy Elegancki suwak. Jest to opcja domyślna Próg drugi Otwieranie Wyrównaj pikselację histogramu Podana wartość nie jest prawidłowym ciągiem Base64 Obszar Włącz mapowanie tonów Zapisz Base64 Udostępnij Base64 Opcje Działania Zaimportuj Base64 Rodzaj czułości Standardowa czułość wyjścia Zalecany wskaźnik ekspozycji Prędkość ISO Prędkość ISO Szerokość geograficzna zzz Ogniskowa filmu 35 mm Opis ustawień urządzenia Zakres odległości przedmiotu Unikalny identyfikator obrazu Numer seryjny korpusu Identyfikator wersji GPS Odniesienie do szerokości geograficznej GPS Szerokość geograficzna GPS Odniesienie do długości geograficznej GPS Docelowa szerokość geograficzna GPS Znacznik daty GPS Błąd pozycjonowania GPS H Współczynnik interoperacyjności Wersja DNG Początek podglądu obrazu Długość podglądu obrazu Lewe obramowanie czujnika Antyaliasy Otwórz edycję zamiast podglądu Wyrównaj histogram HSV Zaczep Zwiń Ślepota barw Sygmoidalny Rozmycie liniowe (pudło) Wymień filtr Docelowy obraz LUT Delikatna elegancja Wariant Delikatnej elegancji Pomijanie wybielacza Światło świecy Kropla Bluesa Elegancki bursztyn Brak pełnego dostępu do plików Rozmieszczenie narzędzi Grupuj narzędzia według typu Osnowa domeny Leczenie punktowe Użyj jądra okręgu Zamykanie Gradient morfologiczny Zresetuj krzywe Krzywe zostaną przywrócone do wartości domyślnej Rysuje wybrane kształty wzdłuż ścieżki z określonymi odstępami Rysuje falisty zygzak wzdłuż ścieżki Prosty Laplacian Prosty Sobel Siatka pomocnicza Pokazuje siatkę pomocniczą nad obszarem rysowania, aby pomóc w precyzyjnych manipulacjach Kolor siatki Wysokość komórki Układ Współczynnik stałej szybkości (CRF) Biblioteka Lut Zmiana domyślnego podglądu obrazu dla filtrów Podgląd obrazu Ukryj Typ suwaka Zmyślny Suwak oparty na Material You Wprowadź % Edytuj warstwę Warstwy na obrazie To samo, co pierwsza opcja, ale z kolorem zamiast obrazu Beta Strona szybkich ustawień Dodaj pływający pasek po wybranej stronie podczas edycji obrazów, który po kliknięciu otworzy szybkie ustawienia Wyczyść zaznaczenie Grupa ustawień „%1$s” będzie domyślnie zwinięta Grupa ustawień „%1$s” będzie domyślnie rozwinięta Narzędzia Base64 Dekodowanie ciągu Base64 na obraz lub kodowanie obrazu do formatu Base64 Base64 Nie można skopiować pustego lub nieprawidłowego ciągu Base64 Skopiuj Base64 Załaduj obraz, aby skopiować lub zapisać ciąg Base64. Jeśli masz sam ciąg znaków, możesz wkleić go powyżej, aby uzyskać obraz Suma kontrolna jako nazwa pliku Obrazy wyjściowe będą miały nazwę odpowiadającą ich sumie kontrolnej danych Rozmiar siatki X Rozmiar siatki Y Wyrównaj histogram adaptacyjnie Funkcja odległości Typ powrotu Rozmycie liniowe (namiot) Rozdzielczość Y płaszczyzny ogniskowej Kontrola wzmocnienia Punkt odniesienia namiaru GPS Współczynnik powiększenia cyfrowego Zezwalaj na wprowadzanie poprzez pole tekstowe Próg pierwszy Odchylenie GPS Skalowanie przestrzeni kolorów Canny Wprowadź wartość procentową Kreskowany Informacje o obszarze GPS Liniowe szybkie rozmycie gaussowskie kolejne Dolne obramowanie czujnika Liniowe Rozmycie liniowe gaussowskie pudełkowe Niska polaryzacja Mglista noc Lokalizacja jednorazowego zapisu Utwórz skrót Włącza pole tekstowe za wyborem ustawień wstępnych, aby wprowadzać je na bieżąco Dopasowanie do granic Wyrównaj histogram Włącz znaczniki czasu, aby wybrać ich format Rozmycie liniowe gaussowskie Domyślny tryb ścieżki rysowania Wyświetlanie i edytowanie lokalizacji jednorazowego zapisu, z których można korzystać poprzez długie naciśnięcie przycisku zapisu w większości opcji Wartości domyślne Wybierz narzędzie do przypięcia Rozmycie liniowe stosu Dodaj znacznik czasu Tylko domyślne linie proste Nie układaj ramek w stos Połącz tryb zmiany rozmiaru kadrowania z tym parametrem, aby uzyskać pożądane zachowanie (Kadrowanie/Dopasowanie do proporcji) Widoczność pasków systemowych Celluloid Najpierw użyj swojej ulubionej aplikacji do edycji zdjęć, aby zastosować filtr do neutralnego LUT, który możesz uzyskać tutaj. Aby filtr działał poprawnie, kolor każdego piksela nie może zależeć od innych pikseli (np. rozmycie nie będzie działać). Po przygotowaniu, użyj nowego obrazu LUT jako danych wejściowych dla filtra LUT 512*512 Włącza dodawanie znacznika czasu do nazwy pliku wyjściowego Kapelusz Przerywana kropka Rysuje przerywaną linię wzdłuż narysowanej ścieżki z określonym rozmiarem odstępu Sformatowany znacznik czasu Zezwól wszystkim plikom na dostęp do wyświetlania plików JXL, QOI i innych obrazów, które nie są rozpoznawane jako obrazy w systemie Android. Bez tego uprawnienia aplikacja Image Toolbox nie będzie w stanie wyświetlać tych obrazów Zygzak Włącz formatowanie znacznika czasu w nazwie pliku wyjściowego zamiast podstawowych milisekund Rysuje kropkę i przerywaną linię wzdłuż podanej ścieżki Ulepszone rozmycie powiększenia Umożliwia usuwanie poprzednich ramek, dzięki czemu nie będą się one na siebie nakładać Udzielenie kamerze uprawnień do przechwytywania obrazu w ustawieniach Niektóre elementy sterujące wyborem będą miały kompaktowy układ, aby zajmować mniej miejsca Użyj obrazu jako tła i dodaj do niego różne warstwy Środkowe przyciski dialogowe Warstwy znaczników Zaktualizuj kolekcję LUT (tylko nowe zostaną umieszczone w kolejce), które można zastosować po pobraniu Dodanie obrysu wokół tekstu o określonym kolorze i szerokości Nie można uzyskać dostępu do witryny, spróbuj użyć VPN lub sprawdź, czy adres URL jest poprawny Suwak oparty na Material 2 Wartość %1$s oznacza wolną kompresję, co skutkuje stosunkowo małym rozmiarem pliku. %2$s oznacza szybszą kompresję, skutkującą dużym plikiem. Przyciski okien dialogowych będą umieszczane na środku zamiast po lewej stronie, jeśli to możliwe Ponowne próbkowanie przy użyciu relacji obszaru pikseli. Może to być preferowana metoda odszumiania obrazu, ponieważ daje wyniki wolne od efektu mory. Ale gdy obraz jest powiększony, jest podobny do metody „Najbliższy”. Można importować tylko czcionki TTF i OTF Import czcionki (TTF/OTF) Wyeksportuj czcionki Zaimportowane czcionki Nazwa pliku nie została ustawiona Błąd podczas próby zapisu, spróbuj zmienić folder wyjściowy Brak Własne strony Wybór stron Potwierdzenie wychodzenia z narzędzi Jeśli masz niezapisane zmiany podczas korzystania z określonych narzędzi i spróbujesz je zamknąć, zostanie wyświetlone okno dialogowe potwierdzenia Edytuj EXIF Zmiana metadanych pojedynczego obrazu bez rekompresji Dotknij, aby edytować dostępne znacziki Zmień naklejkę Dopasuj do wysokości Dopasuj do szerokości Porównanie wsadowe Wybierz plik/pliki, aby obliczyć jego sumę kontrolną na podstawie wybranego algorytmu Wybierz pliki Wybierz katalog Czołowa długość skali Pieczęć Znacznik czasu Wzór formatu Wypełnienie Pionowa linia obrotu Pozioma linia obrotu Odwróć zaznaczenie Pionowa część cięcia zostanie pozostawiona, zamiast łączenia części wokół obszaru cięcia Wycinanie części obrazu i łączenie lewych części (może być odwrotnie) za pomocą pionowych lub poziomych linii Pozioma część cięcia zostanie pozostawiona, zamiast łączenia części wokół obszaru cięcia Przycinanie obrazu Kolekcja gradientów siatki Nakładka gradientu siatki Tworzenie gradientu siatki z własną ilością węzłów i rozdzielczością Skomponuj gradient siatki górnej części podanych obrazów Dostosowywanie punktów Rozmiar siatki Rozdzielczość X Rozdzielczość Y Rozdzielczość Piksel po pikselu Kolor podświetlenia Typ porównania pikseli Zeskanuj kod kreskowy Współczynnik wysokości Rodzaj kodu kreskowego Wymuś B/CZ Obraz kodu kreskowego będzie w pełni czarno-biały i nie będzie kolorowany przez motyw aplikacji Zeskanuj dowolny kod kreskowy (QR, EAN, AZTEC, …) i pobierz jego zawartość lub wklej tekst, aby wygenerować nowy Wygenerowany kod kreskowy będzie dostępny tutaj Nie znaleziono kodu kreskowego Okładki audio Wyodrębnianie obrazów okładek albumów z plików audio, obsługiwane są najpopularniejsze formaty Brak okładek Wybierz dźwięk, aby rozpocząć Wybierz dźwięk Wyślij dzienniki Kliknij, aby udostępnić plik dziennika aplikacji, pomoże mi to wykryć i naprawić problem. Ups… Coś poszło nie tak Możesz skontaktować się ze mną, korzystając z poniższych opcji, a ja postaram się znaleźć rozwiązanie.\n(Nie zapomnij załączyć dziennika) Zapisz do pliku Wyodrębnianie tekstu z partii obrazów i zapisywanie go w jednym pliku tekstowym Zapisz do metadanych Wyodrębnianie tekstu z każdego obrazu i umieszczanie go w informacjach EXIF powiązanych zdjęć Tryb niewidzialności Używaj techniki steganografii do tworzenia niewidocznych dla oka znaków wodnych w bajtach obrazów Używaj LSB Zastosowana zostanie metoda steganografii LSB (Less Significant Bit - mniej znaczący bit), w przeciwnym razie użyta zostanie metoda FD (Frequency Domain - domena częstotliwości) Automatyczne usuwanie czerwonych oczu Hasło Odblokuj PDF jest zabezpieczony Data modyfikacji Typ MIME Rozszerzenie Rozszerzenie (odwrotnie) Data dodania Rozmiar Rozmiar (odwrotnie) Typ MIME (odwrotnie) Data modyfikacji (odwrotnie) Operacja prawie zakończona. Anulowanie teraz będzie wymagało ponownego uruchomienia Data dodania (odwrotnie) Od lewej do prawej Od prawej do lewej Od góry do dołu Od dołu do góry Płynne szkło Przełącznik oparty na niedawno ogłoszonym systemie IOS 26 i jego systemie projektowania płynnego szkła Wybierz obraz lub wklej/zaimportuj dane Base64 poniżej Wpisz link do obrazu, aby rozpocząć Wklej link Kalejdoskop Dodatkowy kąt Boki Kanał mieszany Niebiesko-zielony Czerwono-niebieski Zielono-czerwony W czerwień W zieleń W błękit Cyjan Magenta Żółty Kolorowy półton Kontur Poziomy Offset Voronoi Crystallize Kształt Rozciągnij Losowość Usuwanie plam Rozprosz DoG Drugi promień Wyrównaj Blask Wir i ściskanie Punktowanie Kolor brzegu Współrzędne biegunowe Od prostokątnego do biegunowego Od biegunowego do prostokątnego Odwróć w okręgu Zmniejsz szum Prosta solaryzacja Splot Odstęp X Odstęp Y Szerokość X Szerokość Y Wiruj Pieczątka gumowa Rozmazywanie Gęstość Miksuj Zniekształcenie soczewki sferycznej Współczynnik załamania światła Łuk Kąt rozproszenia Iskra Promienie ASCII Gradient Mora Jesień Kość Odrzutowiec Zima Ocean Lato Wiosna Wariant chłodny HSV Różowy Gorąc Parula Magma Piekło Plazma Viridis Cividis Zmierzch Zmieniony zmierz Auto perspektywa Wyprostowanie Zezwalaj na przycięcie Przytnij lub perspektywa Absolutny Turbo Głęboka zieleń Korekcja soczewki Plik profilu obiektywu docelowego w formacie JSON Pobierz gotowe profile obiektywów Procenty częściowe Wyłącz rotację Zapobiega obracaniu obrazów za pomocą gestów dwoma palcami Włącz przyciąganie do krawędzi Po przesunięciu lub powiększeniu obrazy zostaną dopasowane tak, aby wypełnić krawędzie ramki Wyeksportuj do JSON Skopiuj ciąg znaków z danymi palety jako reprezentacją json Ekran główny Zablokuj ekran Wbudowany Eksport tapet Odśwież Pobierz aktualne tapety domowe, blokujące i wbudowane Zezwól na dostęp do wszystkich plików, jest to konieczne do pobrania tapet. Zarządzanie uprawnieniami do pamięci zewnętrznej nie wystarczy, musisz zezwolić na dostęp do swoich zdjęć, upewnij się, że wybrałeś opcję „Zezwól na wszystko” Dodaj ustawienie wstępne do nazwy pliku Dodaje sufiks z wybranym ustawieniem wstępnym do nazwy pliku obrazu Dodaj tryb skalowania obrazu do nazwy pliku Dodaje sufiks z wybranym trybem skalowania obrazu do nazwy pliku obrazu Sztuka ASCII Konwertuj obraz na tekst ASCII, który będzie wyglądał jak obraz Parametry W niektórych przypadkach stosuje filtr negatywowy do obrazu, aby uzyskać lepszy efekt Rzeźbienie szwów Przetwarzanie zrzutu ekranu Nie wykonano zrzutu ekranu, spróbuj ponownie Pomijanie zapisu %1$s plików pominięto Pozwól na pomijanie jeśli większe Niektóre narzędzia będą mogły pominąć zapisywanie obrazów, jeśli wynikowy rozmiar pliku będzie większy niż oryginalny Wydarzenie w kalendarzu Kontakt E-mail Lokalizacja Telefon Tekst SMS URL Wi-Fi Otwórz sieć N/D SSID Telefon Wiadomość Adres Temat Ciało Nazwa Organizacja Tytuł Telefony E-maile URL Adresy Podsumowanie Opis Lokalizacja Organizer Data rozpoczęcia Data zakończenia Stan Szerokość geograficzna Długość geograficzna Utwórz kod kreskowy Edytuj kod kreskowy Konfiguracja Wi-Fi Bezpieczeństwo Wybierz kontakt W ustawieniach udziel kontaktom pozwolenia na automatyczne wypełnianie kontaktów Dane kontaktowe Imię Drugie imię Nazwisko Wymowa Dodaj telefon Dodaj e-mail Dodaj adres Strona internetowa Dodaj stronę internetową Sformatowana nazwa Ten obraz zostanie umieszczony nad kodem kreskowym Dostosowanie kodu Ten obraz zostanie użyty jako logo w środku kodu QR Logo Wypełnienie logo Rozmiar logo Narożniki logo Czwarte oko Dodaje symetrię oczu do kodu QR poprzez dodanie czwartego oka w dolnym rogu Kształt piksela Kształt ramki Kształt kuli Poziom korekcji błędów Ciemny kolor Jasny kolor Hyper OS Styl podobny do Xiaomi HyperOS Wzór maski Ten kod może nie być skanowalny, zmień parametry wyglądu, aby był czytelny na wszystkich urządzeniach Nie nadaje się do skanowania Narzędzia będą wyglądały jak program uruchamiający aplikacje na ekranie głównym, aby były bardziej kompaktowe Tryb uruchamiania Wypełnia obszar wybranym pędzlem i stylem Wypełnianie wodą Sprej Rysuje ścieżkę w stylu graffiti Kwadratowe cząsteczki Cząsteczki rozpylane będą miały kształt kwadratowy zamiast okrągłego Narzędzia palety Generuj podstawową/materiałową paletę z obrazu lub importuj/eksportuj między różnymi formatami palet Edytuj paletę Eksportuj/importuj paletę w różnych formatach Nazwa koloru Nazwa palety Format palety Eksportuj wygenerowaną paletę do różnych formatów Dodaje nowy kolor do bieżącej palety Format %1$s nie obsługuje podawania nazwy palety Ze względu na zasady sklepu Play Store ta funkcja nie może być zawarta w obecnej wersji. Aby uzyskać dostęp do tej funkcji, pobierz ImageToolbox z alternatywnego źródła. Dostępne wersje można znaleźć na GitHub poniżej. Otwórz stronę Github %1$s kolor %1$s kolory %1$s kolorów %1$s kolorów Oryginalny plik zostanie zastąpiony nowym zamiast zapisywania w wybranym folderze Wykryto ukryty tekst znaku wodnego Wykryto ukryty obraz znaku wodnego Ten obraz został ukryty Generatywne uzupełnianie obrazów Umożliwia usuwanie obiektów z obrazu przy użyciu modelu AI, bez konieczności korzystania z OpenCV. Aby skorzystać z tej funkcji, aplikacja pobierze wymagany model (~200 MB) z serwisu GitHub. Umożliwia usuwanie obiektów z obrazu przy użyciu modelu AI, bez konieczności korzystania z OpenCV. Może to być operacja długotrwała. Analiza poziomu błędów Gradient luminancji Średnia odległość Wykrywanie kopiowania i przenoszenia Zachowaj Współczynnik Dane w schowku są zbyt duże Dane są zbyt duże, aby je skopiować Prosta pikselizacja splotu Pikselizacja rozłożona w czasie Pikselizacja krzyżowa Mikro-makro pikselizacja Pikselizacja orbitalna Pikselizacja wirowa Pikselizacja siatki impulsowej Pikselizacja jądra Pikselizacja splotu promieniowego Nie można otworzyć adresu URI „%1$s” Tryb opadów śniegu Włączono Ramka graniczna Wariant usterki Zmiana kanału Maksymalne przesunięcie VHS Usterka bloku Rozmiar bloku Krzywizna CRT Krzywizna Chroma Topnienie pikseli Maksymalny spadek Narzędzia AI Różne narzędzia do przetwarzania obrazów za pomocą modeli AI, takie jak usuwanie artefaktów lub odszumianie Kompresja, postrzępione linie Kreskówki, kompresja programów Ogólna kompresja, ogólny hałas Bezbarwny dźwięk kreskówek Szybka, ogólna kompresja, ogólny hałas, animacja/komiksy/anime Skanowanie książek Korekcja ekspozycji Najlepiej przy ogólnej kompresji, kolorowe obrazy Najlepiej przy ogólnej kompresji obrazów w skali szarości Ogólna kompresja, obrazy w skali szarości, mocniejsze Ogólny szum, kolorowe obrazy Ogólny szum, kolorowe obrazy, lepsze szczegóły Ogólny szum, obrazy w skali szarości Ogólny szum, obrazy w skali szarości, mocniejsze Ogólny szum, obrazy w skali szarości, najsilniejszy Ogólna kompresja Ogólna kompresja Teksturowanie, kompresja h264 Kompresja VHS Niestandardowa kompresja (cinepak, msvideo1, roq) Kompresja Bink, lepsza geometria Kompresja Bink, silniejsza Kompresja Bink, miękka, zachowuje szczegóły Likwidacja efektu schodkowego, wygładzenie Zeskanowane dzieła sztuki/rysunki, łagodna kompresja, efekt mory Pasowanie kolorów Powolne, usuwanie półtonów Ogólny moduł koloryzujący dla obrazów w skali szarości/czarno-białych, aby uzyskać lepsze wyniki, użyj DColor Usuwanie krawędzi Usuwa nadmierne wyostrzenie Powolny, drżący Wygładzanie, ogólne artefakty, CGI Przetwarzanie skanów KDM003 Lekki model poprawiający obraz Usuwanie artefaktów kompresji Usuwanie artefaktów kompresji Usuwanie bandaży z gładkimi wynikami Przetwarzanie wzoru rastrowego Usuwanie wzorca drgań V3 Usuwanie artefaktów JPEG V2 Ulepszenie tekstur H.264 Wyostrzanie i ulepszanie VHS Łączenie Rozmiar kawałka Rozmiar nakładania się Obrazy o rozmiarze większym niż %1$s pikseli zostaną pokrojone i przetworzone w kawałkach. Nakładanie łączy je, aby zapobiec widocznym łączeniom. Duże rozmiary mogą powodować niestabilność urządzeń z niższej półki Wybierz jeden, aby rozpocząć Czy chcesz usunąć model %1$s? Będziesz musiał pobrać go ponownie Potwierdzać Modele Pobrane modele Dostępne modele Przygotowanie Aktywny model Nie udało się otworzyć sesji Można importować tylko modele .onnx/.ort Importuj model Zaimportuj niestandardowy model onnx do dalszego wykorzystania, akceptowane są tylko modele onnx/ort, obsługuje prawie wszystkie warianty podobne do esrgan Importowane modele Ogólny hałas, kolorowe obrazy Ogólny szum, kolorowe obrazy, mocniejsze Ogólny szum, kolorowe obrazy, najsilniejszy Redukuje artefakty związane z ditheringiem i pasma kolorów, poprawiając gładkie gradienty i płaskie obszary kolorów. Zwiększa jasność i kontrast obrazu dzięki zrównoważonym rozjaśnieniom, zachowując jednocześnie naturalne kolory. Rozjaśnia ciemne obrazy, zachowując szczegóły i unikając prześwietlenia. Usuwa nadmierne tonowanie kolorów i przywraca bardziej neutralną i naturalną równowagę kolorów. Stosuje tonowanie szumu w oparciu o Poissona, kładąc nacisk na zachowanie drobnych szczegółów i tekstur. Stosuje miękkie tonowanie szumu Poissona, aby uzyskać gładsze i mniej agresywne efekty wizualne. Jednolite tonowanie szumów skupione na zachowaniu szczegółów i przejrzystości obrazu. Delikatne, jednolite tonowanie szumów zapewnia subtelną teksturę i gładki wygląd. Naprawia uszkodzone lub nierówne obszary, odmalowując artefakty i poprawiając spójność obrazu. Lekki model usuwający paski, który usuwa kolorowe pasy przy minimalnych kosztach wydajności. Optymalizuje obrazy z artefaktami o bardzo wysokim stopniu kompresji (jakość 0–20%) w celu poprawy przejrzystości. Poprawia obrazy o artefakty o wysokiej kompresji (jakość 20–40%), przywracając szczegóły i redukując szumy. Poprawia obrazy przy umiarkowanej kompresji (jakość 40–60%), równoważąc ostrość i gładkość. Poprawia obrazy za pomocą lekkiej kompresji (jakość 60–80%), aby uwydatnić subtelne szczegóły i tekstury. Nieznacznie poprawia niemal bezstratne obrazy (jakość 80–100%), zachowując jednocześnie naturalny wygląd i szczegóły. Prosta i szybka koloryzacja, kreskówki, nie idealna Nieznacznie zmniejsza rozmycie obrazu, poprawiając ostrość bez wprowadzania artefaktów. Długotrwałe operacje Przetwarzanie obrazu Przetwarzanie Usuwa duże artefakty kompresji JPEG z obrazów o bardzo niskiej jakości (0-20%). Redukuje silne artefakty JPEG w obrazach o dużej kompresji (20–40%). Usuwa umiarkowane artefakty JPEG, zachowując szczegóły obrazu (40–60%). Poprawia jasne artefakty JPEG w obrazach o dość wysokiej jakości (60–80%). Subtelnie redukuje drobne artefakty JPEG w niemal bezstratnych obrazach (80-100%). Uwydatnia drobne szczegóły i tekstury, poprawiając postrzeganą ostrość bez ciężkich artefaktów. Przetwarzanie zakończone Przetwarzanie nie powiodło się Poprawia teksturę i szczegóły skóry, zachowując jednocześnie naturalny wygląd, zoptymalizowany pod kątem szybkości. Usuwa artefakty kompresji JPEG i przywraca jakość obrazu skompresowanych zdjęć. Redukuje szumy ISO na zdjęciach robionych w warunkach słabego oświetlenia, zachowując szczegóły. Koryguje prześwietlone lub „duże” światła i przywraca lepszą równowagę tonalną. Lekki i szybki model koloryzacji, który dodaje naturalne kolory do obrazów w skali szarości. DEJPEG Odszumić Koloruj Artefakty Zwiększyć Anime Skany Ekskluzywny Upscaler X4 dla obrazów ogólnych; mały model, który zużywa mniej procesora graficznego i czasu, z umiarkowanym usuwaniem rozmycia i odszumiania. Moduł skalujący X2 do ogólnych obrazów, zachowujący tekstury i naturalne szczegóły. Moduł skalujący X4 do ogólnych obrazów z ulepszonymi teksturami i realistycznymi wynikami. Upscaler X4 zoptymalizowany pod kątem obrazów anime; 6 bloków RRDB dla ostrzejszych linii i szczegółów. Moduł zwiększający skalę X4 ze stratą MSE, zapewnia gładsze wyniki i mniej artefaktów w przypadku ogólnych obrazów. X4 Upscaler zoptymalizowany pod kątem obrazów anime; Wariant 4B32F z ostrzejszymi detalami i gładkimi liniami. Model X4 UltraSharp V2 do zdjęć ogólnych; podkreśla ostrość i klarowność. X4 UltraSharp V2 Lite; szybszy i mniejszy, zachowuje szczegóły przy mniejszym zużyciu pamięci GPU. Lekki model do szybkiego usuwania tła. Zrównoważona wydajność i dokładność. Działa z portretami, obiektami i scenami. Zalecane w większości przypadków użycia. Usuń BG Grubość obramowania poziomego Grubość obramowania pionowego Obecny model nie obsługuje fragmentowania, obraz zostanie przetworzony w oryginalnych wymiarach, może to powodować duże zużycie pamięci i problemy z urządzeniami z niższej półki Dzielenie na kawałki wyłączone, obraz zostanie przetworzony w oryginalnych wymiarach, może to powodować duże zużycie pamięci i problemy z urządzeniami z niższej półki, ale może dać lepsze wyniki na podstawie wnioskowania Kawałki Model segmentacji obrazu o wysokiej dokładności do usuwania tła Lekka wersja U2Net do szybszego usuwania tła przy mniejszym zużyciu pamięci. Model Full DColor zapewnia wysokiej jakości kolorowanie ogólnych obrazów przy minimalnej liczbie artefaktów. Najlepszy wybór ze wszystkich modeli koloryzacji. DDColor Trained i prywatne zbiory danych artystycznych; zapewnia różnorodne i artystyczne rezultaty koloryzacji z mniejszą liczbą nierealistycznych artefaktów kolorystycznych. Lekki model BiRefNet oparty na transformatorze Swin do dokładnego usuwania tła. Wysokiej jakości usuwanie tła z ostrymi krawędziami i doskonałym zachowaniem szczegółów, szczególnie w przypadku skomplikowanych obiektów i trudnych środowisk. Model usuwania tła, który tworzy dokładne maski o gładkich krawędziach, odpowiednie dla obiektów ogólnych i umiarkowanego zachowania szczegółów. Model już pobrany Model został pomyślnie zaimportowany Typ Słowo kluczowe Bardzo szybko Normalna Powolny Bardzo powolny Oblicz procenty Minimalna wartość to %1$s Zniekształcanie obrazu poprzez rysowanie palcami Osnowa Twardość Tryb wypaczenia Przenosić Rosnąć Kurczyć się Wir CW Wiruj CCW Siła blaknięcia Najwyższy spadek Dolny spadek Rozpocznij upuszczanie Koniec zrzutu Pobieranie Gładkie kształty Użyj superelips zamiast standardowych zaokrąglonych prostokątów, aby uzyskać gładsze, bardziej naturalne kształty Typ kształtu Cięcie Bułczasty Gładki Ostre krawędzie bez zaokrągleń Klasycznie zaokrąglone rogi Typ kształtów Rozmiar narożników Wiercik Eleganckie zaokrąglone elementy interfejsu użytkownika Format nazwy pliku Niestandardowy tekst umieszczany na samym początku nazwy pliku, idealny do nazw projektów, marek lub osobistych tagów. Używa oryginalnej nazwy pliku bez rozszerzenia, pomagając zachować nienaruszoną identyfikację źródła. Szerokość obrazu w pikselach, przydatna do śledzenia zmian rozdzielczości lub wyników skalowania. Wysokość obrazu w pikselach, pomocna podczas pracy ze współczynnikami proporcji lub eksportami. Generuje losowe cyfry, aby zagwarantować unikalne nazwy plików; dodaj więcej cyfr, aby zapewnić dodatkowe bezpieczeństwo przed duplikatami. Automatycznie zwiększający się licznik do eksportu wsadowego, idealny przy zapisywaniu wielu obrazów w jednej sesji. Wstawia zastosowaną nazwę ustawienia wstępnego do nazwy pliku, dzięki czemu można łatwo zapamiętać sposób przetwarzania obrazu. Wyświetla tryb skalowania obrazu używany podczas przetwarzania, pomagając rozróżnić obrazy o zmienionym rozmiarze, przycięte lub dopasowane. Niestandardowy tekst umieszczony na końcu nazwy pliku, przydatny przy wersjonowaniu, takim jak _v2, _edited lub _final. Rozszerzenie pliku (png, jpg, webp itp.), automatycznie dopasowujące się do aktualnie zapisanego formatu. Konfigurowalny znacznik czasu, który pozwala zdefiniować własny format według specyfikacji Java w celu idealnego sortowania. Typ rzucania Natywny Android Styl iOS Gładka krzywa Szybkie zatrzymanie Sprężysty Pływający Żwawy Ultragładka Adaptacyjny Świadomość dostępności Ograniczony ruch Natywna fizyka przewijania Androida Zrównoważone, płynne przewijanie do ogólnego użytku Zachowanie przewijania podobne do systemu iOS o większym tarciu Unikalna krzywa splajnu zapewniająca wyraźne wyczucie przewijania Precyzyjne przewijanie z szybkim zatrzymaniem Zabawny, responsywny, sprężysty przewijak Długie, przesuwające się zwoje do przeglądania treści Szybkie i responsywne przewijanie dla interaktywnych interfejsów użytkownika Wysokiej jakości płynne przewijanie z wydłużonym tempem Dostosowuje fizykę w oparciu o prędkość rzucania Przestrzega systemowych ustawień dostępności Minimalny ruch ze względu na potrzeby dostępności Linie podstawowe Dodaje grubszą linię co piątą linię Kolor wypełnienia Ukryte narzędzia Narzędzia ukryte do udostępnienia Biblioteka kolorów Przeglądaj bogatą kolekcję kolorów Wyostrza i usuwa rozmycia obrazów, zachowując naturalne szczegóły, idealne do poprawiania nieostrych zdjęć. Inteligentnie przywraca obrazy, których rozmiar został wcześniej zmieniony, odzyskując utracone szczegóły i tekstury. Zoptymalizowany pod kątem treści zawierających akcję na żywo, redukuje artefakty powstałe w wyniku kompresji i poprawia drobne szczegóły w klatkach filmów/programów telewizyjnych. Konwertuje materiał filmowy o jakości VHS na HD, usuwając szumy taśmy i zwiększając rozdzielczość, zachowując jednocześnie klimat vintage. Specjalizuje się w obrazach i zrzutach ekranu zawierających dużo tekstu, wyostrza znaki i poprawia czytelność. Zaawansowane skalowanie trenowane na różnych zestawach danych, doskonałe do ogólnego ulepszania zdjęć. Zoptymalizowany pod kątem zdjęć skompresowanych w Internecie, usuwa artefakty JPEG i przywraca naturalny wygląd. Ulepszona wersja zdjęć internetowych z lepszym zachowaniem tekstur i redukcją artefaktów. Dwukrotne skalowanie dzięki technologii podwójnego transformatora agregującego pozwala zachować ostrość i naturalne szczegóły. 3-krotne skalowanie przy użyciu zaawansowanej architektury transformatorowej, idealne do umiarkowanych potrzeb powiększenia. 4-krotne skalowanie wysokiej jakości dzięki najnowocześniejszej sieci transformatorowej pozwala zachować drobne szczegóły w większych skalach. Usuwa rozmycie/szumy i drgania ze zdjęć. Ogólnego przeznaczenia, ale najlepiej na zdjęciach. Przywraca obrazy o niskiej jakości przy użyciu transformatora Swin2SR, zoptymalizowanego pod kątem degradacji BSRGAN. Doskonały do ​​naprawiania artefaktów spowodowanych dużą kompresją i uwydatniania szczegółów w skali 4x. Skalowanie 4x za pomocą transformatora SwinIR trenowanego pod kątem degradacji BSRGAN. Wykorzystuje GAN, aby uzyskać ostrzejsze tekstury i bardziej naturalne szczegóły na zdjęciach i złożonych scenach. Ścieżka Scal PDF Połącz wiele plików PDF w jeden dokument Kolejność plików s. Podziel plik PDF Wyodrębnij określone strony z dokumentu PDF Obróć plik PDF Napraw na stałe orientację strony Strony Zmień kolejność plików PDF Przeciągnij i upuść strony, aby zmienić ich kolejność Przytrzymaj i przeciągnij strony Numery stron Automatycznie dodawaj numerację do swoich dokumentów Format etykiety PDF na tekst (OCR) Wyodrębnij zwykły tekst z dokumentów PDF Nałóż niestandardowy tekst dla marki lub bezpieczeństwa Podpis Dodaj swój podpis elektroniczny do dowolnego dokumentu Będzie to używane jako podpis Odblokuj PDF Usuń hasła z chronionych plików Chroń PDF Zabezpiecz swoje dokumenty silnym szyfrowaniem Sukces PDF odblokowany, możesz go zapisać lub udostępnić Napraw PDF Spróbuj naprawić uszkodzone lub nieczytelne dokumenty Skala szarości Konwertuj wszystkie obrazy osadzone w dokumencie na skalę szarości Kompresuj PDF Zoptymalizuj rozmiar pliku dokumentu, aby ułatwić udostępnianie ImageToolbox odbudowuje wewnętrzną tabelę powiązań i ponownie generuje strukturę pliku od podstaw. Może to przywrócić dostęp do wielu plików, których „nie można otworzyć” To narzędzie konwertuje wszystkie obrazy dokumentów do skali szarości. Najlepsze do drukowania i zmniejszania rozmiaru pliku Metadane Edytuj właściwości dokumentu, aby zapewnić większą prywatność Tagi Producent Autor Słowa kluczowe Twórca Głębokie czyszczenie prywatności Wyczyść wszystkie dostępne metadane dla tego dokumentu Strona Głęboki OCR Wyodrębnij tekst z dokumentu i zapisz go w jednym pliku tekstowym za pomocą silnika Tesseract Nie można usunąć wszystkich stron Usuń strony PDF Usuń określone strony z dokumentu PDF Kliknij, aby usunąć Ręcznie Przytnij plik PDF Przytnij strony dokumentu do dowolnych granic Spłaszcz plik PDF Spraw, aby plik PDF nie był modyfikowalny, rastrując strony dokumentu Nie można uruchomić aparatu. Sprawdź uprawnienia i upewnij się, że nie jest używana przez inną aplikację. Wyodrębnij obrazy Wyodrębnij obrazy osadzone w plikach PDF w ich oryginalnej rozdzielczości Ten plik PDF nie zawiera żadnych osadzonych obrazów To narzędzie skanuje każdą stronę i odzyskuje obrazy źródłowe o pełnej jakości — idealne do zapisywania oryginałów z dokumentów Narysuj podpis Parametry pióra Użyj własnego podpisu jako obrazu do umieszczenia na dokumentach Spakuj PDF Podziel dokument w określonym przedziale czasu i spakuj nowe dokumenty do archiwum zip Interwał Wydrukuj PDF Przygotuj dokument do druku z niestandardowym rozmiarem strony Strony na arkusz Orientacja Rozmiar strony Margines Kwiat Miękkie kolano Zoptymalizowany pod kątem anime i kreskówek. Szybkie skalowanie z ulepszonymi naturalnymi kolorami i mniejszą liczbą artefaktów Styl Samsung One UI 7 Wprowadź tutaj podstawowe symbole matematyczne, aby obliczyć żądaną wartość (np. (5+5)*10) Wyrażenie matematyczne Odbierz maksymalnie %1$s obrazów Zachowaj datę i godzinę Zawsze zachowuj tagi exif powiązane z datą i godziną, działa niezależnie od opcji zachowywania exif Kolor tła dla formatów Alpha Dodaje możliwość ustawienia koloru tła dla każdego formatu obrazu z obsługą alfa, gdy jest wyłączona, dostępna tylko dla formatów innych niż alfa Otwórz projekt Kontynuuj edycję wcześniej zapisanego projektu Image Toolbox Nie można otworzyć projektu Image Toolbox W projekcie Image Toolbox brakuje danych projektu Projekt Image Toolbox jest uszkodzony Nieobsługiwana wersja projektu Image Toolbox: %1$d Zapisz projekt Przechowuj warstwy, tło i historię edycji w edytowalnym pliku projektu Nie udało się otworzyć Zapisz w pliku PDF z możliwością przeszukiwania Rozpoznaj tekst z partii obrazów i zapisz plik PDF z możliwością przeszukiwania z obrazem i wybraną warstwą tekstową Warstwa alfa Odwrócenie poziome Odwrócenie w pionie Zamek Dodaj cień Kolor cienia Geometria tekstu Rozciągnij lub pochyl tekst, aby uzyskać ostrzejszą stylizację Skala X Pochylić X Usuń adnotacje Usuń wybrane typy adnotacji, takie jak łącza, komentarze, wyróżnienia, kształty lub pola formularzy ze stron PDF Hiperłącza Załączniki plików Kwestia Wyskakujące okienka Znaczki Kształty Notatki tekstowe Oznaczenia tekstowe Pola formularza Markup Nieznany Adnotacje Rozgrupuj Dodaj rozmyty cień za warstwą z konfigurowalnym kolorem i przesunięciami ================================================ FILE: core/resources/src/main/res/values-pt-rBR/strings.xml ================================================ Algo deu errado: %1$s Tamanho %1$s Carregando… A imagem é muito grande para ser visualizada, mas tentaremos salvar mesmo assim Escolha a imagem para começar Largura %1$s Altura %1$s Qualidade Extensão Tipo de redimensionamento Explícito Flexível Escolha a imagem Você tem certeza de que deseja encerrar o aplicativo? Fechar aplicativo Ficar Fechar Redefinir imagem As alterações na imagem retornarão aos valores iniciais Valores redefinidos corretamente Reiniciar Algo deu errado Reinicie o aplicativo Copiado para a área de transferência Exceção Editar EXIF OK Nenhum dado EXIF encontrado Adicionar tag Salvar Limpar Limpar EXIF Cancelar Todos os dados EXIF da imagem serão apagados. Essa ação não pode ser desfeita! Predefinições Cortar Salvando Todas as alterações não salvas serão perdidas se você sair agora Código fonte Receba as atualizações mais recentes, discuta problemas e muito mais Edição Única Modificar, redimensionar e editar uma imagem Seletor de Cores Escolha a cor da imagem, copie ou compartilhe Imagem Cor Cor copiada Cortar imagem em qualquer limite Versão Manter EXIF Imagens: %d Alterar visualização Remover Gere amostra de paleta de cores a partir de determinada imagem Gerar Paleta Paleta Atualizar Nova versão %1$s Tipo não suportado: %1$s Não é possível gerar paleta para determinada imagem Original Pasta de saída Padrão Personalizado Não especificado Dispositivo de armazenamento Redimensionar por Tamanho Tamanho máximo em KB Redimensione uma imagem seguindo o tamanho determinado em KB Comparar Compare duas imagens fornecidas Escolha duas imagens para começar Escolher imagens Configurações Modo noturno Escuro Claro Sistema Cores dinâmicas Personalização Permitir monetização de imagem Se ativado, ao escolher uma imagem para editar, as cores do aplicativo serão adotadas para esta imagem Idioma Modo AMOLED Se ativado, a cor das superfícies será definida para escuro absoluto no modo noturno Esquema de cores Vermelho Verde Azul Cole um código aRGB válido. Nada para colar O esquema de cores do aplicativo não pode ser alterado enquanto as cores dinâmicas estiverem ativadas O tema do aplicativo será baseado na cor selecionada Sobre o aplicativo Nenhuma atualização encontrada Rastreador de problemas Envie relatórios de bugs e solicitações de recursos aqui Ajude a traduzir Corrija erros de tradução ou localize o projeto para outros idiomas Nada encontrado pela sua consulta Pesquise aqui Se ativado, as cores do aplicativo serão adotadas nas cores do papel de parede Falha ao salvar %d imagens E-mail Primário Terciário Secundário Espessura da borda Superfície Valores Adicionar Permissão Conceder O aplicativo precisa de acesso ao seu armazenamento para salvar as imagens para funcionar, é necessário. Conceda permissão na próxima caixa de diálogo. O aplicativo precisa desta permissão para funcionar, por favor conceda-a manualmente Armazenamento externo Cores Monet Esta aplicação é totalmente gratuita, mas se quiser apoiar o desenvolvimento do projeto, pode clicar aqui Alinhamento do FAB Buscar atualizações Se ativado, a caixa de diálogo de atualização será mostrada na inicialização do aplicativo Zoom da imagem Compartilhar Prefixo Nome do arquivo Emoji Selecione qual emoji exibir na tela principal Adicionar tamanho de arquivo Se ativado, adiciona largura e altura da imagem salva ao nome do arquivo de saída Excluir EXIF Exclua metadados EXIF de qualquer conjunto de imagens Previa de Imagem Visualize qualquer tipo de imagem: GIF, SVG e assim por diante Origem da imagem Seletor de fotos Galeria Explorador de arquivos O seletor de fotos moderno do Android, que aparece na parte inferior da tela, pode funcionar apenas no Android 12+. Tem problemas ao receber metadados EXIF Seletor de imagens de galeria simples. Funcionará apenas se você tiver um aplicativo que forneça seleção de mídia Use a intenção GetContent para escolher a imagem. Funciona em qualquer lugar, mas é conhecido por ter problemas ao receber imagens selecionadas em alguns dispositivos. Não é minha culpa. Arranjo de opções Editar Ordem Determina a ordem das ferramentas na tela principal Contagem de emojis sequênciaNum nome do arquivo original Adicione o nome do arquivo original Se ativado, adiciona o nome do arquivo original ao nome da imagem de saída Substituir o número de sequência Se ativado, substitui o carimbo de data e hora padrão pelo número de sequência da imagem se você usar o processamento em lote Adicionar o nome do arquivo original não funciona se a fonte da imagem do seletor de fotos for selecionada Carregamento de Imagens da Web Carregue qualquer imagem da internet para visualizar, ampliar, editar e salvar se desejar. Nenhuma imagem Link da imagem Preencher Ajustar Escala de conteúdo Redimensiona as imagens para a altura e a largura fornecidas. A proporção de aspecto das imagens pode mudar. Redimensiona as imagens com um lado longo para a altura ou a largura fornecida. Todos os cálculos de tamanho serão feitos após o salvamento. A proporção de aspecto das imagens será preservada. Brilho Contraste Matiz Saturação Adicionar filtro Filtro Aplicar cadeias de filtros às imagens Filtros Iluminação Filtro de cor Alfa Exposição Balanço de branco Temperatura Matiz Monocromático Gama Destaques e sombras Destaques Sombras Confusão Efeito Distância Declive Afiado Sépia Negativo Solarizar Vibração Preto e Branco Hachura Espaçamento Espessura da linha Borda Sobel Desfoque Meio-tom Espaço de cores CGA Desfoque gaussiano Desfoque de caixa Desfoque bilateral Gravar Laplaciano Vinheta Iniciar Fim Suavização Kuwahara Desfoque de pilha Raio Escala Distorção Ângulo Redemoinho Protuberância Dilatação Refração de esfera Índice de refração Refração da esfera de vidro Matriz de cores Opacidade Redimensionar por Limites Redimensionar imagens para a altura e a largura fornecidas, mantendo a proporção de aspecto Esboço Limiar Níveis de quantização Desenho suave Desenho animado Posterizar Supressão não máxima Inclusão fraca de pixels Olho para cima Convolução 3x3 Filtro RGB Cor falsa Primeira cor Segunda cor Reordenar Desfoque rápido Tamanho do desfoque Desfocar centro x Desfocar centro y Desfoque de zoom Equilíbrio de cores Limite de luminância Você desativou o aplicativo \"Arquivos\", ative-o para usar este recurso Desenhar Desenhe na imagem como em um caderno de desenho ou no próprio fundo Cor da tinta Pintura alfa Desenhar na imagem Escolha uma imagem e desenhe algo nela Desenhe no fundo Escolha a cor de fundo e desenhe sobre ela Cor de fundo Criptografia Criptografe e descriptografe qualquer arquivo (não apenas a imagem) com base em vários algoritmos de criptografia Escolher arquivo Criptografar Descriptografar Escolha o arquivo para começar Descriptografia Criptografia Chave Arquivo processado Armazene este arquivo no seu dispositivo ou use a ação de compartilhamento para colocá-lo onde quiser Características Implementação Compatibilidade Criptografia de arquivos baseada em senha. Os arquivos processados podem ser armazenados no diretório selecionado ou compartilhados. Os arquivos descriptografados também podem ser abertos diretamente. AES-256, modo GCM, sem preenchimento, IVs aleatórios de 12 bytes por padrão. Você pode selecionar o algoritmo necessário. As chaves são usadas como hashes SHA-3 de 256 bits Tamanho do arquivo O tamanho máximo do arquivo é restrito pelo sistema operacional Android e pela memória disponível, que depende do dispositivo. \nObserve: memória não é armazenamento. Observe que a compatibilidade com outros softwares ou serviços de criptografia de arquivos não é garantida. Um tratamento de chave ou configuração de criptografia ligeiramente diferente pode causar incompatibilidade. Senha inválida ou arquivo escolhido não está criptografado A tentativa de salvar a imagem com a largura e a altura fornecidas pode causar um erro de falta de memória. Faça isso por sua própria conta e risco. Cache Tamanho da memória cache Encontrado %1$s Limpeza automática de cache Se ativado, o cache do aplicativo será limpo na inicialização do aplicativo Criar Ferramentas Opções de grupo por tipo Agrupa opções na tela principal por tipo, em vez de uma organização de lista personalizada Não é possível alterar a organização enquanto o agrupamento de opções está ativado Editar captura de tela Personalização secundária Captura de tela Opção alternativa Pular Copiar Salvar no modo %1$s pode ser instável, pois é um formato sem perdas Se você selecionou a predefinição 125, a imagem será salva com tamanho de 125% da imagem original. Se você escolher a predefinição 50, a imagem será salva com 50% tamanho A predefinição aqui determina a % do arquivo de saída, ou seja, se você selecionar a predefinição 50 em uma imagem de 5 MB, obterá uma imagem de 2,5 MB após salvar Nome do arquivo aleatório Se ativado, o nome do arquivo de saída será totalmente aleatório Salvo na pasta %1$s com o nome %2$s Salvo na pasta %1$s Telegram Discuta sobre o aplicativo e obtenha feedback de outros usuários. Você também pode obter atualizações beta e insights lá. Máscara de corte Proporção da tela Use este tipo de máscara para criar uma máscara a partir de uma determinada imagem, observe que DEVE ter canal alfa Backup e restauração Backup Restaurar Faça backup das configurações do seu aplicativo em um arquivo Restaure as configurações do aplicativo do arquivo gerado anteriormente Arquivo corrompido ou não é um backup Configurações restauradas com sucesso Me contatar Isso reverterá suas configurações para os valores padrão. Observe que isso não pode ser desfeito sem o arquivo de backup mencionado acima. Excluir Você está prestes a excluir o esquema de cores selecionado. Esta operação não pode ser desfeita Excluir esquema Fonte Texto Escala de fonte Padrão O uso de escalas de fontes grandes pode causar falhas e problemas na interface do usuário, que não serão corrigidos. Use com cautela. Aa Áá Ââ Ãã Bb Cc Çç Dd Ee Éé Êê Ff Gg Hh Ii Íí Jj Kk Ll Mm Nn Oo Óó Ôô Õõ Pp Qq Rr Ss Tt Uu Úú Vv Ww Xx Yy Zz 0123456789 !? Emoções Comida e bebida Natureza e Animais Objetos Símbolos Habilitar emoticons Viagens e lugares Atividades Removedor de Fundo Remova o fundo da imagem desenhando ou use a opção \"Auto\" Cortar imagem Os metadados da imagem original serão mantidos Os espaços transparentes ao redor da imagem serão cortados Remover automaticamente o fundo Restaurar imagem Modo remover Remover fundo Restaurar plano de fundo Raio de desfoque Pipeta Modo de desenho Criar problema Ops… algo deu errado. Você pode escrever para mim usando as opções abaixo e tentarei encontrar uma solução Redimensionar e Converter Altere o tamanho de determinadas imagens ou converta-as para outros formatos. Os metadados EXIF também podem ser editados aqui se você escolher uma única imagem. Contagem máxima de cores Isso permite que o aplicativo colete relatórios de falhas automaticamente Analytics Permitir a coleta de estatísticas anônimas de uso de aplicativos Atualmente, o formato %1$s só permite a leitura de metadados EXIF no Android. A imagem de saída não terá metadados quando salva. Esforço Um valor %1$s significa uma compactação rápida, resultando em um tamanho de arquivo relativamente grande. %2$s significa uma compactação mais lenta, resultando em um arquivo menor. Espere Salvando quase completo. Cancelar agora exigirá salvar novamente. Atualizações Permitir versões beta A verificação de atualização incluirá versões beta do aplicativo, se ativada Desenhar setas Se ativado, o caminho do desenho serárepresentado como uma seta apontando Suavidade do pincel As imagens serão cortadas no centro no tamanho inserido. A tela será expandida com determinada cor de fundo se a imagem for menor que as dimensões inseridas. Doação Costura de Imagens Combine as imagens fornecidas para obter uma grande Escolha pelo menos 2 imagens Escala da imagem de saída Orientação da imagem Horizontal Vertical Dimensione imagens pequenas para grandes Imagens pequenas serão dimensionadas para a maior na sequência, se habilitadas Ordem das imagens Regular Desfocar bordas Desenha bordas desfocadas sob a imagem original para preencher espaços ao redor dela em vez de cor única, se ativado Pixelização Pixelização aprimorada Pixelização de traços Pixelização de diamante aprimorada Pixelização de diamante Pixelização do Círculo Pixelização de círculo aprimorada Substituir cor Tolerância Cor para substituir Cor alvo Cor para remover Remover cor Recodificar Tamanho dos pixels Bloquear orientação em desenhos Se ativado no modo de desenho, a tela não gira Buscar atualizações Estilo de paleta Ponto Tonal Neutro Vibrante Expressivo Arco-íris Salada de Frutas Fidelidade Contente Estilo de paleta padrão, permite personalizar todas as quatro cores, outros permitem definir apenas a cor principal Um estilo um pouco mais cromático do que monocromático Um tema barulhento, o colorido é máximo para a paleta Primária, aumentado para outras Um tema divertido – a tonalidade da cor de origem não aparece no tema Um tema monocromático, as cores são puramente preto/branco/cinza Um esquema que coloca a cor de origem em Scheme.primaryContainer Um esquema muito semelhante ao esquema de conteúdo Este verificador de atualização se conectará ao GitHub para verificar se há uma nova atualização disponível Atenção Bordas desbotadas Desabilitado Ambos Cores invertidas Substitui as cores do tema por cores negativas, se ativado Procurar Permite a capacidade de pesquisar por todas as ferramentas disponíveis na tela principal Ferramentas de PDF Opere com arquivos PDF: visualize, converta em lote de imagens ou crie um a partir de determinadas imagens Pré-visualização PDF PDF para imagens Imagens para PDF Pré-visualização simples de PDF Converta PDF em imagens em determinado formato de saída Empacote as imagens fornecidas no arquivo PDF de saída Filtro de máscara Aplicar cadeias de filtros em determinadas áreas mascaradas; cada área de máscara pode determinar seu próprio conjunto de filtros Máscaras Adicionar máscara Máscara %d Cor da máscara Prévia da máscara A máscara de filtro desenhada será renderizada para mostrar o resultado aproximado Tipo de preenchimento inverso Se ativado todas as áreas sem máscara serão filtradas no lugar do comportamento predefinido Você está prestes a excluir a máscara de filtro selecionada. Esta operação não pode ser desfeita Excluir máscara Filtro Total Aplique um conjunto de filtro à determinadas imagens ou à uma única imagem Início Centro Fim Variantes Simples Marcador Néon Caneta Desfoque de privacidade Desenhe caminhos de realce nítidos e semitransparentes Adicione algum efeito brilhante aos seus desenhos Padrão, mais simples - apenas a cor Desfoca a imagem sob o caminho desenhado para proteger tudo o que você deseja ocultar Semelhante ao desfoque de privacidade, mas pixeliza em vez de desfocar Containers Desenhe uma sombra atrás dos contêineres Controles deslizantes Comuta FABs Botões Desenhar uma sombra atrás dos controles deslizantes Desenhe uma sombra atrás dos interruptores Desenhar uma sombra atrás dos botões de ação flutuantes Desenhar uma sombra atrás dos botões Barras do app Desenhe uma sombra atrás das barras do app Valor no intervalo %1$s - %2$s Auto rotação Permite que a caixa limite seja adotada para orientação da imagem Modo Desenhar Caminho Seta de linha dupla Desenho Livre Seta Dupla Seta de linha Seta Linha Desenha o caminho como valor de entrada Desenha o caminho do ponto inicial ao ponto final como uma linha Desenha uma seta apontando do ponto inicial ao ponto final como uma linha Desenha uma seta apontando a partir de um determinado caminho Desenha uma seta dupla apontando do ponto inicial ao ponto final como uma linha Desenha uma seta dupla apontando de um determinado caminho Oval delineado Reto delineado Oval Reto Desenha um retângulo do ponto inicial ao ponto final Desenha uma forma oval do ponto inicial ao ponto final Desenha um contorno oval do ponto inicial ao ponto final Desenha um retângulo delineado do ponto inicial ao ponto final Laço Desenha um caminho preenchido fechado por um determinado caminho Livre Grade horizontal Grade Vertical Modo de costura Contagem de linhas Contagem de colunas Nenhum diretório \"%1$s\" encontrado, mudamos para o padrão, salve o arquivo novamente Área de transferência Fixação automática Adiciona automaticamente a imagem salva à área de transferência, se ativado Vibração Força de vibração Para substituir arquivos, você precisa usar a fonte de imagem Explorer, tente repickar imagens, alteramos a fonte da imagem para a necessária Substituir arquivos O arquivo original será substituído por um novo ao invés de salvar na pasta selecionada, esta opção precisa que a fonte da imagem seja \"Explorer\" ou GetContent, ao alternar esta opção, ela será definida automaticamente Vazio Sufixo Modo de escala Bilinear Catmull Bicúbico Hann Eremita Lanczos Mitchell Mais próximo Spline Básico Valor padrão A interpolação linear (ou bilinear, em duas dimensões) normalmente é boa para alterar o tamanho de uma imagem, mas causa alguma suavização indesejável de detalhes e ainda pode ser um tanto irregular Melhores métodos de dimensionamento incluem reamostragem Lanczos e filtros Mitchell-Netravali Uma das maneiras mais simples de aumentar o tamanho, substituindo cada pixel por um número de pixels da mesma cor Modo de escalabilidade Android mais simples usado em quase todos os aplicativos Método para interpolar e reamostrar suavemente um conjunto de pontos de controle, comumente usado em computação gráfica para criar curvas suaves Função de janelamento frequentemente aplicada no processamento de sinal para minimizar o vazamento espectral e melhorar a precisão da análise de frequência, afunilando as bordas de um sinal Técnica de interpolação matemática que utiliza os valores e derivadas nas extremidades de um segmento de curva para gerar uma curva suave e contínua Método de reamostragem que mantém interpolação de alta qualidade aplicando uma função sinc ponderada aos valores de pixel Método de resampling que usa um filtro de convolução com parâmetros ajustáveis para alcançar um equilíbrio entre nitidez e suavização na imagem dimensionada Usa funções polinomiais definidas por partes para interpolar e aproximar suavemente uma curva ou superfície, proporcionando uma representação flexível e contínua da forma Apenas clipe O salvamento no armazenamento não será executado e a imagem será tentada apenas para ser colocada na área de transferência O pincel irá restaurar o fundo em vez de apagar OCR (reconhecer texto) Reconhecer texto de determinada imagem, mais de 120 idiomas suportados A imagem não tem texto ou o app não a encontrou Precisão: %1$s Tipo de reconhecimento Rápido Padrão Melhor Sem dados Para o funcionamento adequado do Tesseract OCR, dados de treinamento adicionais (%1$s) precisam ser baixados para o seu dispositivo.\nDeseja baixar dados de %2$s? Download Sem conexão, verifique e tente novamente para baixar modelos de trem Idiomas baixados idiomas disponíveis Modo de segmentação Use a troca de pixels Usa um interruptor semelhante ao do Google Pixel Arquivo sobrescrito com nome %1$s no destino original Lupa Ativa a lupa na parte superior do dedo nos modos de desenho para melhor acessibilidade Forçar valor inicial Força o widget exif a ser verificado inicialmente Permitir vários idiomas Deslizar Lado a lado Alternar Toque Transparência Avaliar aplicativo Avaliar Este aplicativo é totalmente gratuito, se você quiser que ele fique maior, marque o projeto com estrela no Github 😄 Orientação e Somente detecção de script Orientação automática e Detecção de script Somente automático Auto Coluna Única Texto vertical em bloco único Bloco único Única linha Única palavra Palavra circular Caractere único Texto esparso Orientação e texto esparso. Detecção de script Linha bruta Deseja excluir os dados de treinamento de OCR do idioma \"%1$s\" para todos os tipos de reconhecimento ou apenas para um selecionado (%2$s)? Atual Todos Criador de Gradiente Crie gradiente de determinado tamanho de saída com cores e tipo de aparência personalizados Linear Radial Varrer Tipo de gradiente Centro (horizontal) Centro (vertical) Modo lado a lado Repetido Espelho Braçadeira Decalque Paradas de cores Adicionar cor Propriedades Aplicação de Brilho Tela Sobreposição de Gradiente Componha qualquer gradiente por cima de determinadas imagens Transformações Câmera Tirar uma foto com uma câmera. Observe que é possível obter apenas uma imagem dessa fonte de imagem Marca d\'água Cubra as fotos com marcas d’água de texto/imagem personalizáveis Repetir marca d\'água Repete a marca d\'água na imagem em vez de uma única em determinada posição Deslocamento (horizontal) Deslocamento (vertical) Tipo de marca d\'água Esta imagem será usada como padrão para marca d\'água Cor do texto Modo de sobreposição Ferramentas GIF Converta imagens em imagens GIF ou extraia quadros de uma determinada imagem GIF GIF para imagens Converta arquivo GIF em lote de fotos Converta lote de imagens em arquivo GIF Imagens para GIF Escolha a imagem GIF para começar Use o tamanho do primeiro quadro Substitua o tamanho especificado pelas dimensões do primeiro quadro Repetir contagem Atraso de quadro milissegundos FPS Usar laço Usa Lasso como no modo de desenho para apagar Pré-visualização da imagem original alfa Confete Confetti será mostrado ao salvar, compartilhar e outras ações primárias Modo Seguro Oculta o conteúdo do aplicativo em aplicativos recentes. Ele não pode ser capturado ou gravado. Sair Se você sair da visualização agora, será necessário adicionar as imagens novamente Pontilhamento Quantizador Escala de Cinza Bayer dois por dois pontilhamento Bayer três por três hesitação Bayer quatro por quatro hesitação Bayer oito por oito pontilhamento Floyd Steinberg hesitante Jarvis Judice Ninke hesitante Sierra hesitante Pontilhamento Sierra de duas fileiras Pontilhamento Sierra Lite Atkinson hesitante Stucki hesitante Ruído Burkes Falsa hesitação de Floyd Steinberg Pontilhamento da esquerda para a direita Pontilhamento aleatório Dithering de limite simples Sigma Sigma Espacial Desfoque mediano Estria B Utiliza funções polinomiais bicúbicas definidas por partes para interpolar e aproximar suavemente uma curva ou superfície, representação de forma flexível e contínua Desfoque de pilha nativo Mudança de inclinação Falha Quantia Semente Anáglifo Barulho Classificação de pixels Aleatório Falha aprimorada Mudança de canal (horizontal) Mudança de canal (vertical) Tamanho da corrupção Mudança de Corrupção (horizontal) Mudança de Corrupção (vertical) Desfoque de barraca Desbotamento lateral Lado Principal Fundo Força Erodir Difusão Anisotrópica Difusão Condução Escalonamento de Vento Horizontal Desfoque bilateral rápido Desfoque Poisson Mapeamento logarítmico de tons Mapeamento de tons fílmicos ACES Cristalizar Cor do traço Vidro fractal Amplitude Mármore Turbulência Óleo Efeito Água Tamanho Frequência (horizontal) Frequência (vertical) Amplitude (horizontal) Amplitude (vertical) Distorção Perlin Mapeamento de tons de colina ACES Mapeamento de tons fílmicos Hable Mapeamento de Tons de Hejl Burgess Velocidade Desembaçar Ómega Matriz de cores 4x4 Matriz de cores 3x3 Efeitos Simples Polaróide Tritanomalia Deuteranomalia Protanomalia Vintage Brownie Coda Cromo Visão noturna Esquentar Legal Tritanopia Deutaronotopia Protanopia Acromatomia Acromatopsia Grão Não nitidez Pastel Névoa Laranja Sonho Rosa Hora dourada Verão quente Névoa Roxa Nascer do sol Redemoinho Colorido Luz suave de primavera Tons de outono Sonho Lavanda Ciberpunk Limonada Light Fogo Espectral Magia Noturna Paisagem Fantástica Explosão de cores Gradiente Elétrico Escuridão Caramelo Gradiente Futurista Sol Verde Mundo Arco-Íris Roxo profundo Portal Espacial Redemoinho Vermelho Código digital Bokeh O emoji da barra do aplicativo irá mudar aleatoriamente Emojis Aleatórios Não é possível usar emojis aleatórios enquanto os emojis estiverem desativados Não é possível selecionar um emoji enquanto os emojis aleatórios estiverem ativados TV Antiga Desfoque aleatório Favorito Nenhum filtro favorito adicionado Formato de imagem Adiciona um contêiner com a forma selecionada sob os ícones Formato dos Ícones Drago Aldridge Cortar Uchimura Móbius Transição Pico Anomalia de cor Imagens substituídas no destino original Não é possível alterar o formato da imagem enquanto a opção sobrescrever arquivos está ativada Emoji como esquema de cores Usa cor primária emoji como esquema de cores do aplicativo em vez de um definido manualmente Cria paleta \"Material You\" a partir da imagem Cores Escuras Usa o esquema de cores do modo noturno em vez da variante de luz Copiar como código \"Jetpack Compose\" Desfoque Anel Desfoque Cruz Desfoque Estrela Mudança de Inclinação Linear Desfoque Circular Tags a remover APNG para imagens Escolher imagem APNG para iniciar Desfoque de movimento Converter imagens em imagem APNG ou extrair quadros de determinada imagem APNG Imagens para APNG Ferramentas APNG Converta arquivo APNG em lote de fotos Converter lote de imagens em arquivo APNG Crie um arquivo Zip a partir de determinados arquivos ou imagens Zip Largura da alça de arrastar Tipo de Confete Festivo Explosão Chuva JXL para JPEG Ferramentas JXL Executar conversão JXL ~ JPEG sem perda de qualidade ou converta GIF/APNG em animação JXL JPEG para JXL Escolha a imagem JXL para começar Executando conversão sem perdas de JXL para JPEG Executando conversão sem perdas de JPEG para JXL Desfoque rápido Gaussiano 2D Desfoque rápido Gaussiano 3D Desfoque rápido Gaussiano 4D Permite que o aplicativo cole automaticamente os dados da área de transferência, para que apareçam na tela principal e você possa processá-los Cor de Harmonização Nível de Harmonização Cantos Colar automaticamente Lanczos Bessel GIF para JXL Converter imagens GIF em imagens animadas JXL APNG para JXL Converter imagens APNG em imagens animadas JXL JXL para Imagens Converter animação JXL em lote de imagens Imagens para JXL Converter lote de imagens em animação JXL Comportamento Pular seleção de arquivos O seletor de arquivos será mostrado imediatamente se for possível na tela escolhida Gerar visualizações Compressão com Perdas Método de reamostragem que mantém interpolação de alta qualidade aplicando uma função Bessel (jinc) aos valores de pixel Usa compactação \"com perdas\" para reduzir o tamanho do arquivo em vez de \"sem perdas\" Ativa a geração de visualização, o que pode ajudar a evitar travamentos em alguns dispositivos e também desativa algumas funcionalidades de edição na opção de edição única Tipo de compressão Controla a velocidade de decodificação da imagem resultante, o que deve ajudar a abrir a imagem resultante mais rapidamente, o valor de %1$s significa a decodificação mais lenta, enquanto %2$s - a mais rápida, essa configuração pode aumentar o tamanho da imagem de saída Data Data (Inversa) Nome Nome (Inverso) Ordenação Configuração de canais Ontem Seletor incorporado Escolha Escolha várias mídias Escolha uma mídia única Seletor de imagens do nativo do Image Toolbox Nenhuma permissão Solicitação Hoje Tente novamente Mostrar configurações no modo paisagem Se estiver desativado, as configurações do modo paisagem serão abertas no botão da barra superior do aplicativo, como sempre, em vez da opção visível permanente Configurações de tela cheia Compor Tipo de Interruptor Ative-o e a página de configurações será sempre aberta em tela cheia, em vez da folha de gaveta deslizante Um interruptor Material You Um interruptor Jetpack Material You Máx Cupertino Redimensionar âncora Pixel Fluent Um interruptor baseado no sistema de design \"Fluent\" Um interruptor baseado no sistema de design \"Cupertino\" Imagens para SVG Rastrear imagens fornecidas em imagens SVG Usar paleta de amostra A paleta de quantização será amostrada se esta opção estiver habilitada Caminho omitido O uso desta ferramenta para rastrear imagens grandes sem redução de escala não é recomendado, pois pode causar travamento e aumentar o tempo de processamento Imagem em escala reduzida A imagem será reduzida para dimensões inferiores antes do processamento, o que ajuda a ferramenta a trabalhar de forma mais rápida e segura Proporção mínima de cores Limite de linhas Limite de quadros Tolerância de arredondamento de coordenadas Caminho de escala Redefinir propriedades Todas as propriedades serão definidas com valores padrão, observe que esta ação não pode ser desfeita Detalhado Largura de linha padrão Modo de Mecanismo Legado Rede LSTM Legado & LSTM Converter lotes de imagens para determinado formato Converter Bits por amostra Compressão Configuração plana Subamostragem Cb Cr (vertical) Resolução (horizontal) Deslocamentos da faixa Linhas por faixa Formato de intercâmbio JPEG Comprimento do formato de intercâmbio JPEG Função de transferência Ponto Branco Cromaticidades Primárias Coeficientes Cb Cr (vertical) Adicionar nova pasta Interpretação fotométrica Amostras por pixel Posicionamento Cb Cr (vertical) Resolução (vertical) Unidade de Resolução Tirar contagens de bytes Data Hora Descrição da imagem Criar Modelo Software Artista Copyright Versão Exif Espaço de Cores Gama Dimensão Pixel (horizontal) Bits compactados por pixel Nota do fabricante Comentário do usuário Arquivo de som relacionado Data e hora original Data e hora digitalizada Tempo de deslocamento Tempo de deslocamento original Tempo de deslocamento digitalizado Período de exposição Número F Programa de Exposição Sensibilidade Espectral OECF Tipo de sensibilidade Sensibilidade de saída padrão Índice de exposição recomendado Velocidade ISO Latitude de velocidade ISO yyy Valor de abertura Valor de brilho Valor de polarização de exposição Valor máximo de abertura Distância entre assuntos Velocidade ISO Latitude zzz Modo de medição Flash Área da matéria Comprimento focal Energia Flash Resposta de frequência espacial Resolução do plano focal (horizontal) Unidade de resolução do plano focal Localização do assunto Índice de Exposição Método de detecção Fonte do arquivo Padrão CFA Renderização personalizada Modo de exposição Balanço de branco Taxa de zoom digital Filme de distância focal In35mm Tipo de captura de cena Controle de ganho Contraste Saturação Nitidez Descrição da configuração do dispositivo Faixa de distância do assunto ID exclusivo da imagem Nome do proprietário da câmera Número de série do corpo Marca da lente Especificação da lente Modelo da lente Número de série da lente ID da versão do GPS Latitude do GPS Referência de latitude do GPS Referência de longitude do GPS Longitude do GPS Referência de altitude do GPS Altitude do GPS Data e hora do GPS Satélites GPS Status do GPS Modo de medição do GPS Referência de velocidade do GPS Velocidade do GPS Referência de rastreamento do GPS Rastreamento do GPS Referência de direção de imagem do GPS Direção de imagem do GPS Dados de mapa do GPS Referência de latitude de destino do GPS Latitude de destino do GPS Referência de longitude de destino do GPS Longitude de destino do GPS Rolamento de destino do GPS Referência de distância de destino do GPS DOP GPS Distância de destino do GPS Método de processamento do GPS Informações da área do GPS Data e hora do GPS Diferencial GPS Erro de posicionamento H do GPS Índice de interoperabilidade Versão DNG Tamanho de corte padrão Pré-visualizar início da imagem Pré-visualizar comprimento da imagem Aspecto de quadro Borda inferior do sensor Borda esquerda do sensor Borda direita do sensor Borda superior do sensor Desenhe texto no caminho com determinada fonte e cor Tamanho da fonte Tamanho da marca d\'água Repetir texto O texto atual será repetido até o final do caminho, em vez de ser desenhado uma única vez Tamanho do traço Use a imagem selecionada para desenhá-la ao longo de um determinado caminho Essa imagem será usada como entrada repetitiva do caminho desenhado Desenha um triângulo delineado do ponto inicial ao ponto final Triângulo delineado Triângulo Polígono Polígono delineado Vínculos Desenhar polígono regular Desenhe um polígono que será regular em vez de uma forma livre Desenha uma estrela do ponto inicial ao ponto final Estrela Estrela delineada Desenha uma estrela delineada do ponto inicial ao ponto final Proporção do Raio Interno Desenhar estrela regular Desenhe uma estrela que será regular em vez de uma forma livre Ativa o antialiasing para evitar bordas afiadas Antialias Versão Flashpix Dimensão Pixel (vertical) Sensibilidade Fotográfica Valor da velocidade do obturador Resolução do plano focal (vertical) Referência do rolamento de destino do GPS ISO Desenha um triângulo delineado do ponto inicial ao ponto final Referência Preto Branco Desenha o polígono do ponto inicial ao ponto final Desenha um polígono delineado do ponto inicial ao ponto final Digitalizador de Documentos Clique para iniciar a digitalização Comece a digitalizar Salvar como PDF Compartilhar como PDF As opções abaixo servem para salvar imagens, não PDF Equalizar Histograma HSV Equalizar Histograma Digitalize documentos e crie PDF ou separe imagens deles Abrir editor em vez da visualizar Quando você seleciona a imagem para abrir (visualizar) no ImageToolbox, a folha de seleção de edição será aberta em vez de visualizar Tempo sub-segundo Tempo sub-segundo original Tempo sub-segundo digitalizado Insira a porcentagem Permitir entrada por campo de texto Ativa o campo de texto atrás da seleção de predefinições, para inseri-las instantaneamente Linear Equalizar pixelização do histograma Escalar Espaço de Cores Tamanho da grade (horizontal) Tamanho da grade (vertical) Equalizar Histograma Adaptive LAB CLAHE LAB CLAHE LUV Equalizar histograma adaptativo CLAHE Equalizar histograma LUV adaptativo Cortar para o Conteúdo Cor a ser ignorada Cor da moldura Modelo Nenhum filtro de modelo adicionado Criar um novo O código QR digitalizado não é um modelo de filtro válido Digitalizar código QR O arquivo selecionado não tem dados de modelo de filtro Criar modelo Nome do modelo Essa imagem será usada para visualizar esse modelo de filtro Filtro de modelo Como imagem de código QR Como arquivo Salvar como arquivo Salvar como imagem de código QR Excluir modelo Você está prestes a excluir o filtro de modelo selecionado. Essa operação não pode ser desfeita Modelo de filtro adicionado com o nome \"%1$s\" (%2$s) Pré-visualização do filtro QR & Barcode Digitalize um código QR e obtenha seu conteúdo ou cole seu texto para gerar um código Conteúdo do código Digitalize qualquer código de barras para substituir o conteúdo do campo ou digite algo para gerar um novo código de barras com o tipo selecionado Descrição do código QR Min Conceder permissão à câmara nas definições para digitalizar o código QR Hanning Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc A interpolação cúbica proporciona um dimensionamento mais suave ao considerar os 16 pixels mais próximos, proporcionando melhores resultados do que a interpolação bilinear Utiliza funções polinomiais definidas por partes para interpolar e aproximar suavemente uma curva ou superfície, representação de forma flexível e contínua Uma função de janela usada para reduzir o vazamento espectral, afinando as bordas de um sinal, útil no processamento de sinais Uma variante da janela de Hann, comumente usada para reduzir o vazamento espectral em aplicações de processamento de sinais Uma função de janela que fornece uma boa resolução de frequência ao minimizar o vazamento espectral, frequentemente usada no processamento de sinais Uma função de janela projetada para oferecer boa resolução de frequência com vazamento espectral reduzido, geralmente usada em aplicações de processamento de sinais Um método que usa uma função quadrática para interpolação, fornecendo resultados suaves e contínuos Um método de interpolação que aplica uma função gaussiana, útil para suavizar e reduzir o ruído em imagens Um método de interpolação de alta qualidade otimizado para redimensionamento natural de imagens, equilibrando nitidez e suavidade Um método de interpolação baseado em spline que fornece resultados suaves usando um filtro de 16 toques Um método de interpolação baseado em spline que fornece resultados suaves usando um filtro de 36 toques Um método simples de reamostragem que usa a média dos valores de pixel mais próximos, geralmente resultando em uma aparência de blocos Uma função de janela usada para reduzir o vazamento espectral, proporcionando uma boa resolução de frequência em aplicações de processamento de sinais Um método de reamostragem que usa um filtro Lanczos de 2 lóbulos para interpolação de alta qualidade com o mínimo de artefatos Um método de reamostragem que usa um filtro Lanczos de 3 lóbulos para interpolação de alta qualidade com o mínimo de artefatos Cúbico B-Spline Redundância Blackman Welch Quádruplo Gaussiano Bartlett-Hann Sphinx Bartlett Robidoux Robidoux Sharp Estria 36 Estria 64 Kaiser Estria 16 Caixa Um método avançado de reamostragem que oferece interpolação de alta qualidade com o mínimo de artefatos Uma função de janela triangular usada no processamento de sinais para reduzir o vazamento espectral Uma variante mais nítida do método Robidoux, otimizada para redimensionamento de imagens nítidas Um método de interpolação baseado em spline que fornece resultados suaves usando um filtro de 64 toques Uma função de janela híbrida que combina as janelas de Bartlett e Hann, usada para reduzir o vazamento espectral no processamento de sinais Um método de interpolação que usa a janela de Kaiser, proporcionando um bom controle sobre a compensação entre a largura do lóbulo principal e o nível do lóbulo lateral Um método de reamostragem que usa um filtro Lanczos de 4 lóbulos para interpolação de alta qualidade com o mínimo de artefatos Uma variante do filtro Lanczos 2 que usa a função jinc, proporcionando interpolação de alta qualidade com o mínimo de artefatos Uma variante do filtro Lanczos 4 que usa a função jinc, proporcionando interpolação de alta qualidade com o mínimo de artefatos Uma variante do filtro Lanczos 3 que usa a função jinc, proporcionando interpolação de alta qualidade com o mínimo de artefatos Hanning EWA Variante de média ponderada elíptica (EWA) do filtro Hanning para interpolação e reamostragem suaves Robidoux EWA Lanczos 3 Jinc EWA Variante de média ponderada elíptica (EWA) do filtro Lanczos Soft para reamostragem mais suave de imagens Haasn Soft Um filtro de reamostragem projetado por Haasn para escalonamento de imagens suave e sem artefatos Variante de média ponderada elíptica (EWA) do filtro Lanczos 3 Jinc para reamostragem de alta qualidade com aliasing reduzido Variante de média ponderada elíptica (EWA) do filtro Robidoux para reamostragem de alta qualidade Blackman EWA Variante de média ponderada elíptica (EWA) do filtro Blackman para minimizar artefatos de anelamento Variante EWA (Elliptical Weighted Average) do filtro Quadric para interpolação suave Variante de média ponderada elíptica (EWA) do filtro Robidoux Sharp para resultados mais nítidos EWA Quadriculado Robidoux Sharp EWA Ginseng EWA Ginseng Um filtro de reamostragem projetado para processamento de imagens de alta qualidade com um bom equilíbrio entre nitidez e suavidade Variante de média ponderada elíptica (EWA) do filtro Ginseng para melhorar a qualidade da imagem Lanczos Sharp EWA Lanczos Soft EWA Variante de média ponderada elíptica (EWA) do filtro Lanczos Sharp para obter resultados nítidos com o mínimo de artefatos Lanczos 4 Sharpest EWA Variante da média ponderada elíptica (EWA) do filtro Lanczos 4 Sharpest para reamostragem de imagens extremamente nítidas Converta lotes de imagens de um formato para outro Dispensar definitivamente Conversão de Formato Empilhamento de Imagens Empilhe imagens umas sobre as outras com os modos de mesclagem escolhidos Adicionar imagem Clahe HSV Equalizar histograma HSL adaptativo Equalizar histograma HSV adaptativo Contagem de posições Clahe HSL Envolver Cortar Modo de Borda Correção de Cores Incapacidade de perceber tons de vermelho Incapacidade de perceber tons de verde Incapacidade de perceber tons de azul Sensibilidade reduzida a todas as cores Selecione o modo para adaptar as cores do tema para a variante de daltonismo selecionada Dificuldade em distinguir entre tons de vermelho e verde As cores serão exatamente como definidas no tema Dificuldade em distinguir entre tons de verde e vermelho Dificuldade em distinguir entre tons de azul e amarelo Completa cegueira de cores, vendo apenas tons de cinza Não usar esquema daltônico Lagrange 2 Sigmoidal Um filtro de interpolação Lagrange de ordem 2, adequado para escalonamento de imagens de alta qualidade com transições suaves Lanczos 6 Uma variante do filtro Lanczos 6 usando uma função Jinc para melhorar a qualidade da reamostragem de imagens Lagrange 3 Lanczos 6 Jinc Um filtro de interpolação Lagrange de ordem 3, que oferece melhor precisão e resultados mais suaves para o dimensionamento de imagens Um filtro de reamostragem Lanczos com uma ordem superior a 6, proporcionando uma escala de imagem mais nítida e precisa Desfoque de Caixa Linear Brilho Suave Poster Colorido Ponto Polka Pontilhamento 4x4 Agrupado Tetrádica Quadrada Análoga + Complementar Ferramentas de Cor Harmonias de Cores Sombras Mistura de Cores Detalhes da Cor Elegância Suave Elegância Suave Variante Transferência de Paleta Variante 3D LUT Destino do Arquivo 3D LUT (.cube / .CUBE) LUT Luz de Velas Gotas de Azul Âmbar Ousado Cores de Outono Kodak Obter imagem LUT neutra Arte Pop Celuloide Café Esverdeado Amarelo Retrô Misture, crie tons, gere sombras e muito mais Salvar modelos OCR Exportar Centro Direito Centro Inferior Óleo Aprimorado TV Antiga Simples Gotham Desfoque Gaussiano Linear Desfoque Gaussiano em Caixa Idiomas importados com sucesso Limpar seleção Fontes importadas Exportar fontes Calcular Checksum Hash Checksum de Origem Coincide! Os checksums não são iguais, o arquivo pode ser inseguro! Diferença Veja a coleção on-line de Malhas de Gradientes Malha de Gradientes Esquema de compactação TIFF Ferramentas de Checksum Algoritmo Mais software útil no canal parceiro de aplicativos Android Free Software (Parceiro) Ferramentas WEBP 512x512 2D LUT Tamanho do Contorno Permite a prévia de visualização de links em locais onde você pode obter texto (QRCode, OCR etc) Converta arquivos WEBP em lotes de imagens GIF para WEBP Sombreamento de Cores Divisão de Imagens Dividir uma imagem em linhas ou colunas Substituir Filtro Rotação Modo de camadas com capacidade de colocar livremente imagens, texto e muito mais Camadas Indexadas Camadas na imagem Editar camada Miss Etikate Desfoque de Tenda Linear Desfoque Gaussiano Linear de Caixa Desfoque Linear em Pilha Desfoque Gaussiano Rápido Próximo Linear Desfoque Gaussiano Rápido Linear Escolha um filtro para usá-lo como pintura Poucos Polígonos Pintura em Areia Ajustar aos Limites Importar Posição Inferior Esquerdo Inferior Direito Centro Esquerdo Superior Direito Superior Esquerdo Superior Centro Imagem Alvo Transferência de Paleta HDR Esboço Simples Tom Triplo Terceira cor Clahe Oklab Clahe Oklch Clahe Jzazbz Pontilhamento 2x2 Agrupado Pontilhamento 8x8 Agrupado Pontilhamento Yililoma Adicionar Favoritas Complementar Análoga Triádica Separada Complementar Variação Matizes Tons Cor Selecionada Cor Para Misturar Não é possível usar monet enquanto as cores dinâmicas estiverem ativadas Imagem Alvo do LUT Amatorka Filme Stock 50 Noite Nebulosa Floresta Dourada Prévias de Links Links WEBP para imagens Convert batch of images to WEBP file Beta Camadas no plano de fundo Cor do Contorno Adicionar Contorno Ações Centro Converta imagens GIF em imagens animadas WEBP Conceda permissão de câmera nas configurações para usar o Digitalizador de Documentos Escolha o filtro abaixo para usá-lo como pincel em seu desenho Combine o modo de redimensionamento de corte com esse parâmetro para obter o comportamento desejado (Cortar/Ajustar à proporção) Não há opções favoritas selecionadas, adicione-as na página de ferramentas Escolha um arquivo para calcular sua soma de verificação com base no algoritmo selecionado Importar fonte (TTF/OTF) Somente fontes TTF e OTF podem ser importadas Opções Bypass de Branqueamento Converta imagens em imagens animadas WEBP ou extraia quadros de uma animação WEBP Os checksums são iguais, é seguro Checksum para Comparação Insira um texto para calcular sua soma de verificação com base no algoritmo selecionado Compare checksums, calcule hashes ou crie cadeias hexadecimais de arquivos usando diferentes algoritmos de hashing Modo de Desenho de Caminho Padrão Receba notificações sobre novas versões do aplicativo e leia os anúncios Cor de Desenho Padrão Faça colagens com até 20 imagens Data e Hora Formatados Histograma Participe do nosso bate-papo, onde você pode discutir o que quiser, e também dê uma olhada no Canal CI, onde eu público betas e anúncios Opções do Tesseract Image Toolbox no Telegram 🎉 Primeiro, use seu aplicativo de edição de fotos favorito para aplicar um filtro à LUT neutra que pode ser obtida aqui. Para que isso funcione corretamente, cada cor de pixel não deve depender de outros pixels (por exemplo, o desfoque não funcionará). Quando estiver pronta, use sua nova imagem LUT como entrada para o filtro LUT de 512*512 Os arquivos ICO só podem ser salvos no tamanho máximo de 256x256 Imagens para WEBP Escolha a imagem WEBP para iniciar Sem acesso total aos arquivos Permitir o acesso de todos os arquivos para ver JXL, QOI e outras imagens que não são reconhecidas como imagens no Android. Sem a permissão, o Image Toolbox não poderá exibir essas imagens Adicionar Data e Hora Usados Recentemente Canal CI Grupo Agrupa ferramentas na tela principal por tipo, em vez de um arranjo em lista personalizado Ocultar Todas Mostrar Todas Ocultar Navegação Ocultar Barra de Status Geração de Ruído Gerar ruídos diferentes, como Perlin ou outros tipos Força Ponderada Força Ping Pong Função Distância Tipo de Retorno Nome Personalizado Selecione o local e o nome do arquivo que serão usados para salvar a imagem atual Salvo em uma pasta com nome personalizado Criador de Colagens Tipo de Colagem Mantenha a imagem pressionada para trocar, mover e aplicar zoom para ajustar a posição Histograma de imagem RGB ou Brilho para ajudá-lo a fazer ajustes Aplicar algumas variáveis de entrada para o motor tesseract Opções Personalizadas Ajuste uma imagem às dimensões fornecidas e aplique desfoque ou cor ao plano de fundo Arranjo de Ferramentas Agrupar ferramentas por tipo Valores Padrão Visibilidade das Barras do Sistema Mostrar Barras do Sistema ao Deslizar Permite deslizar o dedo para mostrar as barras do sistema se elas estiverem ocultas Auto Frequência Tipo de Ruído Tipo de Rotação Tipo Fractal Octaves Lacunaridade Ganho Jitter Domínio Warp Alinhamento Preenchimento com reconhecimento de conteúdo sob o caminho desenhado A ferramenta será adicionada à tela inicial do seu launcher como atalho; use-a combinada com a configuração \"Ignorar seleção de arquivo\" para obter o comportamento necessário Não empilhar quadros Permite o descarte de quadros anteriores, para que eles não sejam empilhados uns sobre os outros Laplacian Simples Cortar imagem por polígono, o que também corrige a perspectiva Os pontos não serão limitados pelos limites da imagem, o que é útil para uma correção de perspectiva mais precisa Máscara Visualize e Edite locais de salvamento único que podem ser usados pressionando longamente o botão salvar em quase todas as opções As curvas serão revertidas para o valor padrão Transição Gradual Tamanho do Espaço Grade de Guia Um valor de %1$s significa uma compactação lenta, resultando em um arquivo de tamanho relativamente pequeno. %2$s significa uma compactação mais rápida, resultando em um arquivo grande. Fator de Taxa Constante (CRF) Apenas linhas retas padrão Habilite os carimbos de data/hora para selecionar seu formato Disposição Ativa a adição de carimbo de data/hora ao nome do arquivo de saída Ativar a formatação do carimbo de data/hora no nome do arquivo de saída em vez de milissegundos básicos Local Para Salvamento Único Essa imagem será usada para gerar histogramas RGB e de brilho As opções devem ser inseridas seguindo este padrão: \"--{option_name} {value}\" Cortar Automaticamente Cantos Livres Limitação de Pontos para os Limites das Imagens Correção de Manchas Usar Kernel Circle Fechando Gradiente Morfológica Cartola Chapéu Preto Curvas de Tom Redefinir Curvas Estilo de Linha Tracejado Ponto Traço Estampado Zigue-Zague Desenha uma linha tracejada ao longo do caminho desenhado com o tamanho de espaço especificado Desenha pontos e linhas tracejadas ao longo de um determinado caminho Desenha as formas selecionadas ao longo do caminho com o espaçamento especificado Desenha um zigue-zague ondulado ao longo do caminho Proporção do zigue-zague Criar Atalho Escolha a ferramenta a fixar Os quadros serão suavemente cruzados uns com os outros Contagem de quadros de transição Limiar Um Limiar Dois Sagaz Espelho 101 Desfoque de Zoom Aprimorado Sobel Simples Mostra a grade de suporte acima da área de desenho para ajudar em manipulações precisas Cor da Grade Largura de Célula Altura de Célula Seletores Compactos Alguns controles de seleção usarão um layout compacto para ocupar menos espaço Conceda permissão à câmera nas configurações para capturar imagens Título na Tela Principal Biblioteca LUT Baixe a coleção de LUTs, que você pode aplicar posteriormente Abrindo O nome do arquivo não está definido Um controle deslizante Material You Adicione uma listra flutuante na lateral selecionada durante a edição de imagens, que abrirá configurações rápidas quando clicada O grupo de configurações \"%1$s\" será expandido por padrão Erro ao tentar salvar, experimente alterar a pasta de saída Aplicar Alterar a imagem de visualização padrão para filtros Prévia de Imagem Ações com Base64 Use uma imagem como plano de fundo e adicione diferentes camadas sobre ela Copiar Base64 Área Reamostragem usando a relação de área do pixel. Esse pode ser o método preferido para a decimação de imagens, pois oferece resultados sem moiré. Mas quando a imagem é ampliada, ela é semelhante ao método \"Mais Próximo\". Ativar Mapeamento de Tons Atualizar a coleção de LUTs (somente as novas serão colocadas na fila), que você pode aplicar após o download Ocultar Exibir Tipo de Slider Extravagante Material 2 Um controle deslizante de aparência sofisticada. Essa é a opção padrão Um controle deslizante Material 2 Botões de Diálogo Centralizados Licenças de Código Aberto Veja as licenças das bibliotecas de código aberto usadas neste aplicativo Digite % Não é possível acessar o site, tente usar VPN ou verifique se a URL está correta O mesmo que a primeira opção, mas com cor em vez de imagem Acesso às Configurações na Lateral O grupo de configurações \"%1$s\" será recolhido por padrão Ferramentas Base64 Decodificar string Base64 para imagem ou codificar imagem para o formato Base64 Base64 O valor fornecido não é uma cadeia de caracteres Base64 válida Não é possível copiar uma string Base64 vazia ou inválida Colar Base64 Carregue a imagem para copiar ou salvar a cadeia de caracteres Base64. Se você tiver a própria cadeia de caracteres, poderá colá-la acima para obter a imagem Salvar Base64 Compartilhar Base64 Importar Base64 Adicionar contorno ao redor do texto com cor e largura especificadas Checksum como Nome de Arquivo As imagens de saída terão o nome correspondente à soma de verificação de seus dados Os botões das caixas de diálogo serão posicionados no centro em vez de no lado esquerdo, se possível Nenhum Páginas Personalizadas Seleção de Páginas Confirmação ao Sair da Ferramenta Se você tiver alterações não salvas ao usar determinadas ferramentas e tentar fechá-las, será exibida uma caixa de diálogo de confirmação Editar EXIF Alterar metadados de uma única imagem sem recompressão Toque para editar as tags disponíveis Alterar Adesivo Ajustar Largura Ajustar Altura Comparação em Lote Escolha o(s) arquivo(s) para calcular sua soma de verificação com base no algoritmo selecionado Selecionar Pasta Selecionar Arquivos Formato Padrão Carimbo de data Preenchimento Escala de Comprimento da Cabeça Carimbo Recorte de Imagem Linha de Pivô Vertical Linha de Pivô Horizontal Inverter Seleção A parte cortada verticalmente será deixada ao invés de mesclar as partes ao redor da área cortada A parte cortada horizontalmente será deixada ao invés de mesclar as partes ao redor da área cortada Corte parte da imagem e mescle as partes restantes (ou o inverso) por meio de linhas verticais ou horizontais Criar gradiente mesh com quantidade personalizada de nós e resolução Sobreposição de Gradiente Mesh Personalização de Pontos Tamanho da Grade Resolução X Resolução Y Resolução Componha o gradiente mesh por cima das imagens fornecidas Coleção de Gradientes Mesh Pixel Por Pixel Destacar Cor Tipo de Comparação de Pixels Escanear código de barras Proporção de Altura Tipo de Código de Barras Forçar P/B A imagem do código de barras será totalmente em preto e branco e não será colorida pelo tema do aplicativo Escaneie qualquer código de barras (QR, EAN, AZTEC, …) e obtenha seu conteúdo ou cole seu texto para gerar um código Nenhum Código Encontrado O código de Barras Gerado Estará Aqui Capas de Álbum Extraia imagens de capas de álbuns de arquivos de áudio, com suporte aos formatos mais comuns Escolha um áudio para iniciar Selecionar Áudio Nenhuma Capa Encontrada Enviar Registros Clique para compartilhar o arquivo de registros do aplicativo, isso pode me ajudar a identificar e corrigir o problema Ops… Algo deu errado Você pode entrar em contato comigo usando as opções abaixo e tentarei encontrar uma solução\n(Não se esqueça de anexar os registros). Extraia o texto de cada imagem e coloque-o nas informações EXIF das respectivas fotos Gravar em Arquivo Gravar nos Metadados Extraia o texto de um lote de imagens e armazene-o em um único arquivo de texto Remover Olhos Vermelhos Automaticamente O método de esteganografia LSB (Less Significant Bit, bit menos significativo) será usado, caso contrário, FD (Frequency Domain, domínio de frequência) Usar LSB Usar esteganografia para criar marcas d\'água invisíveis aos olhos dentro de bytes de suas imagens Modo Invisível Senha Desbloquear O PDF está protegido Data de Modificação Tamanho Tipo MIME (Inverso) Extensão Extensão (Inversa) Data de Adição (Inversa) Data de Modificação (Inversa) Tamanho (Inverso) Operação quase concluída. Cancelar agora exigirá a reinicialização da operação Tipo MIME Data de Adição Esquerda para a Direita Direita para a Esquerda De Baixo para Cima De Cima para Baixo Vidro Líquido Escolha a imagem ou cole/importe os dados Base64 abaixo Digite o link da imagem para iniciar Colar link Um switch baseado no recém-anunciado IOS 26 e em seu sistema de design de vidro líquido Caleidoscópio Ângulo secundário Lados Mistura de Canais Azul verde Vermelho azul Verde vermelho No vermelho No verde No azul Ciano Magenta Amarelo Meio-tom de Cor Níveis Forma Desativar rotação Impede a rotação de imagens com gestos de dois dedos Ativar encaixe nas bordas Após mover ou ampliar, as imagens se ajustarão para preencher as bordas do quadro Contorno Cristalização Voronoi Alongar Aleatoriedade Remover manchas Difuso DoG Segundo raio Equalizar Brilhar Girar e Pinçar Pontilhismo Cor da borda Coordenadas Polares Rectangular para polar Deslocamento Inverter em círculo Reduzir Ruído Solarizar Simples Entrelaçar Espaço X Espaço Y Largura X Largura Y Rodopiar Carimbo de Borracha Mancha Densidade Mistura Distorção por Lente Esférica Índice de Refração Arco Ângulo aberto Lampejo Raios ASCII Gradiente Moire Outono Osso Jato Inverno Oceano Verão Primavera Variante Legal HSV Rosa Quente Parula Magma Inferno Plasma Viridis Cividis Crepúsculo Crepúsculo Mudado Perspectiva Automática Desalinhamento Permitir corte Recorte ou Perspectiva Absoluto Turbo Verde Intenso Correção de Lentes Baixar perfis de lentes prontos Porcentagens parciais Exportar como JSON Copiar string com dados da paleta como representação json Corte de Costura Tela Inicial Tela de Bloqueio Integrado Exportação de Papéis de Parede Atualizar Obter os papéis de parede atuais da Tela de Bloqueio e Tela Inicial Permita o acesso a todos os arquivos, isso é necessário para obter os papéis de parede A permissão para Gerenciar o armazenamento externo não é suficiente, você precisa permitir o acesso às suas imagens. Certifique-se de selecionar “Permitir tudo”. Adicionar Predefinição ao Nome do Arquivo Anexa o sufixo com a predefinição selecionada ao nome do arquivo de imagem Adicionar Modo De Escala De Imagem Ao Nome Do Arquivo Anexa o sufixo com o modo de escala de imagem selecionado ao nome do arquivo de imagem Arte ASCII Converter a imagem em texto ASCII que se parecerá com a imagem Parâmetros Aplica filtro negativo à imagem para obter melhores resultados em alguns casos Processando captura de tela Captura de tela não realizada, tente novamente Salvamento ignorado %1$s arquivos ignorados Permitir Ignorar se Maior Algumas ferramentas poderão ignorar o salvamento de imagens se o tamanho do arquivo resultante for maior do que o original Evento de Calendário Contato E-mail Localização Telefone Texto SMS URL Wi-Fi Abrir rede N/A SSID Telefone Mensagem Endereço Assunto Corpo Nome Empresa Título Telefones E-mails URLs Endereços Resumo Descrição Localização Organizador Data de início Data de término Status Latitude Longitude Criar Código de Barras Editar Código de Barras Configuração de Wi-Fi Segurança Escolher contato Conceda a permissão de acesso aos contatos nas configurações para preenchimento automático usando o contato selecionado Informações de contato Nome Nome do meio Sobrenome Pronúncia Adicionar telefone Adicionar e-mail Adicionar endereço Site Adicionar site Nome formatado Esta imagem será usada para colocar acima do código de barras. Personalização do código Esta imagem será usada como logotipo no centro do código QR. Logotipo Preenchimento do logotipo Tamanho do Bloco Polar para Retangular Arquivo de perfil de lente no formato JSON Tamanho do logotipo Cantos do logotipo Quarto olho Adiciona simetria ao código QR, adicionando um quarto olho no canto inferior direito Forma do pixel Formato da moldura Forma esférica Nível de correção de erro Cor escura Cor clara Hyper OS Estilo semelhante ao HyperOS da Xiaomi Padrão da máscara Este código pode não ser digitalizável. Altere os parâmetros de aparência para torná-lo legível em todos os dispositivos Não digitalizável As ferramentas se parecerão com apps na tela inicial para serem mais compactas Modo de Abertura Preenche uma área com o pincel e o estilo selecionados Inundação Spray Desenha um caminho no estilo grafite Partículas Quadradas As partículas pulverizadas terão formato quadrado em vez de circular Ferramentas da Paleta Gerar paleta básica/material a partir de uma imagem ou importe/exporte entre diferentes formatos de paleta Editar Paleta Exportar/importar paleta em vários formatos Nome da cor Nome da paleta Formato da Paleta Exportar a paleta gerada para diferentes formatos Adiciona uma nova cor à paleta atual O formato %1$s não suporta o fornecimento de um nome da paleta Devido às políticas da Play Store, esse recurso não pode ser incluído na versão atual. Para acessar essa funcionalidade, baixe o ImageToolbox de uma fonte alternativa. Você pode encontrar as versões disponíveis no GitHub abaixo. Abrir página do Github %1$s cor %1$s cores O arquivo original será substituído por um novo, em vez de ser salvo na pasta selecionada Texto de marca d\'água oculto detectado Imagem com marca d\'água oculta detectada Esta imagem foi ocultada Preenchimento Generativo Permite remover objetos de uma imagem usando um modelo de IA, sem depender do OpenCV. Para usar esse recurso, o aplicativo baixará o modelo necessário (~200 MB) do GitHub Permite remover objetos em uma imagem usando um modelo de IA, sem depender do OpenCV. Essa pode ser uma operação demorada Análise do Nível de Erro Gradiente de Luminância Distância Média Detecção de Cópia e Movimentação Manter Coeficiente Os dados da área de transferência são muito grandes Os dados são muito grandes para serem copiados Pixelização de Tecelagem Simples Pixelização Escalonada Pixelização Cruzada Micro Macro Pixelização Pixelização Orbital Pixelização em Vórtice Pixelização em Grade de Pulsos Pixelização do Núcleo Pixelização de Tecelagem Radial Não é possível abrir o uri \"%1$s\" Modo Neve Ativado Moldura de Borda Variante Glitch Mudança de Canal Desvio Máximo VHS Bloco com Glitch Curvatura CRT Curvatura Chroma Derretimento de Pixels Queda Máxima Ferramentas de IA Várias ferramentas para processar imagens por meio de modelos de IA, como remoção de artefatos ou redução de ruído Compressão, linhas irregulares Desenhos animados, compressão de transmissão Compressão geral, ruído geral Ruído de desenho animado sem color Rápido, compressão geral, ruído geral, animação/quadrinhos/anime Digitalização de livros Correção de exposição Melhor em compressão geral, imagens coloridas Melhor em compressão geral, imagens em escala de cinza Compressão geral, imagens em escala de cinza, mais forte Ruído geral, imagens coloridas Ruído geral, imagens coloridas, melhores detalhes Ruído geral, imagens em escala de cinza Ruído geral, imagens em escala de cinza, mais forte Ruído geral, imagens em escala de cinza, a mais forte Compressão geral Compressão geral Texturização, compressão h264 Compressão VHS Compressão não padrão (cinepak, msvideo1, roq) Compressão Blink, melhor em geometria Compressão instantânea, mais forte Compressão Blink, suave, mantém detalhes Anti-aliasing Arte/desenhos digitalizados, compressão moderada, moiré Faixas coloridas Lento, removendo meios-tons Colorizador geral para imagens em escala de cinza/preto e branco Remoção de bordas Remove o excesso de nitidez Lento, dithering Anti-aliasing, artefatos gerais, CGI Processamento de digitalizações KDM003 Modelo leve de aprimoramento de imagem Modelo de colorização rápida que adiciona rapidamente cores naturais a imagens em escala de cinza. Remoção de artefatos de compressão Remoção de artefatos de compressão Remoção de curativos com resultados suaves Processamento de padrões de meio-tom Remoção do padrão de dither V3 Remoção de artefatos JPEG V2 Aprimoramento de textura H.264 Aprimoramento e nitidez de VHS Fusão Tamanho do Pedaço Tamanho da Sobreposição Imagens com mais de %1$s px serão divididas e processadas em partes, que serão sobrepostas para evitar junções visíveis. Tamanhos grandes podem causar instabilidade em dispositivos de baixo custo Selecione uma para começar Deseja excluir o modelo %1$s? Você precisará baixá-lo novamente Confirmar Modelos Modelos Baixados Modelos Disponíveis Preparando Modelo ativo Falha ao abrir sessão Apenas modelos .onnx/.ort podem ser importados Importar modelo Importar o modelo ONNX personalizado para uso posterior. Somente modelos ONNX/ORT são aceitos, com suporte para quase todas as variantes semelhantes ao ESRGAN Modelos Importados Ruído geral, imagens coloridas Ruído geral, imagens coloridas, mais forte Ruído geral, imagens coloridas, o mais forte Reduz artefatos de dithering e faixas de cor, melhorando gradientes suaves e áreas de cor planas. Melhora o brilho e o contraste da imagem com realces equilibrados, preservando as cores naturais. Ilumina imagens escuras, mantendo os detalhes e evitando a superexposição. Remove o excesso de tonalidade e restaura um equilíbrio de cores mais neutro e natural. Aplica tonalidade de ruído baseada em Poisson com ênfase na preservação de detalhes finos e texturas. Aplica um tom suave de ruído de Poisson para resultados visuais mais suaves e menos agressivos. Tonalidade uniforme do ruído com foco na preservação dos detalhes e na nitidez da imagem. Tom suave e uniforme para uma textura sutil e aparência suave. Repara áreas danificadas ou irregulares repintando artefatos e melhorando a consistência da imagem. Modelo leve de remoção de faixas que elimina as faixas de cor com um custo mínimo de desempenho. Otimiza imagens com artefatos de compressão muito elevados (qualidade de 0 a 20%) para melhorar a nitidez. Melhora imagens com artefatos de alta compressão (20-40% de qualidade), restaurando detalhes e reduzindo o ruído. Melhora as imagens com compressão moderada (40-60% de qualidade), equilibrando nitidez e suavidade. Refina imagens com compressão leve (60-80% de qualidade) para realçar detalhes e texturas sutis. Melhora ligeiramente imagens quase sem perdas (80-100% de qualidade), preservando a aparência natural e os detalhes. Reduz ligeiramente o desfoque da imagem, melhorando a nitidez sem introduzir artefatos. Operações de longa duração Processando imagem Processando Remove artefatos de compressão JPEG pesados em imagens de qualidade muito baixa (0-20%). Reduz artefatos JPEG fortes em imagens altamente comprimidas (20-40%). Limpa artefatos JPEG moderados enquanto preserva os detalhes da imagem (40-60%). Refina artefatos JPEG leves em imagens de qualidade bastante alta (60-80%). Reduz sutilmente pequenos artefatos JPEG em imagens quase sem perdas (80-100%). Aprimora detalhes e texturas finas, melhorando a nitidez percebida sem artefatos pesados. Processamento concluído Falha no processamento Melhora as texturas e detalhes da pele, mantendo uma aparência natural, otimizada para velocidade. Remove artefatos de compactação JPEG e restaura a qualidade da imagem para fotos compactadas. Reduz o ruído ISO em fotos tiradas em condições de pouca luz, preservando os detalhes. Corrige realces superexpostos ou “jumbo” e restaura um melhor equilíbrio tonal. Modelo de colorização leve e rápido que adiciona cores naturais às imagens em tons de cinza. DeJPEG Denoise Colorir Artefatos Melhorar Anime Verificações Sofisticado Upscaler X4 para imagens gerais; modelo minúsculo que usa menos GPU e tempo, com desfoque e redução de ruído moderados. Upscaler X2 para imagens gerais, preservando texturas e detalhes naturais. Upscaler X4 para imagens gerais com texturas aprimoradas e resultados realistas. Upscaler X4 otimizado para imagens de anime; 6 blocos RRDB para linhas e detalhes mais nítidos. O upscaler X4 com perda de MSE produz resultados mais suaves e artefatos reduzidos para imagens gerais. X4 Upscaler otimizado para imagens de anime; Variante 4B32F com detalhes mais nítidos e linhas suaves. Modelo X4 UltraSharp V2 para imagens gerais; enfatiza nitidez e clareza. X4 UltraSharp V2 Lite; mais rápido e menor, preserva os detalhes enquanto usa menos memória da GPU. Modelo leve para remoção rápida de fundo. Desempenho e precisão equilibrados. Trabalha com retratos, objetos e cenas. Recomendado para a maioria dos casos de uso. Remover glicemia Espessura da borda horizontal Espessura da borda vertical O modelo atual não suporta chunking, a imagem será processada nas dimensões originais, isso pode causar alto consumo de memória e problemas com dispositivos de baixo custo Chunking desativado, a imagem será processada nas dimensões originais, o que pode causar alto consumo de memória e problemas com dispositivos de baixo custo, mas pode fornecer melhores resultados na inferência Pedaço Modelo de segmentação de imagens de alta precisão para remoção de fundo Versão leve do U2Net para remoção mais rápida de fundo com menor uso de memória. O modelo Full DDColor oferece colorização de alta qualidade para imagens gerais com o mínimo de artefatos. A melhor escolha de todos os modelos de colorização. DDColor Conjuntos de dados artísticos treinados e privados; produz resultados de colorização diversos e artísticos com menos artefatos de cores irrealistas. Modelo BiRefNet leve baseado no Swin Transformer para remoção precisa do fundo. Remoção de fundo de alta qualidade com bordas nítidas e excelente preservação de detalhes, especialmente em objetos complexos e fundos complicados. Modelo de remoção de fundo que produz máscaras precisas com bordas suaves, adequadas para objetos gerais e preservação moderada de detalhes. Modelo já baixado Modelo importado com sucesso Tipo Palavra-chave Muito rápido Normal Lento Muito lento Calcular Porcentagens O valor mínimo é %1$s Distorcer a imagem desenhando com os dedos Deformação Dureza Modo de Deformação Mover Crescer Encolher Torcer SH Torcer SAH Força de desbotamento Queda superior Queda inferior Iniciar queda Fim da queda Baixando Formas Suaves Use superelipses em vez de retângulos arredondados padrão para obter formas mais suaves e naturais Tipo de forma Corte Arredondado Suave Arestas vivas sem arredondamento Cantos arredondados clássicos Tipo de formas Tamanho dos cantos Esquilo Elementos de UI elegantes e arredondados Formato do nome do arquivo Texto personalizado colocado logo no início do nome do arquivo, perfeito para nomes de projetos, marcas ou tags pessoais. Usa o nome do arquivo original sem extensão, ajudando a manter intacta a identificação da fonte. A largura da imagem em pixels, útil para rastrear alterações de resolução ou resultados de dimensionamento. A altura da imagem em pixels, útil ao trabalhar com proporções ou exportações. Gera dígitos aleatórios para garantir nomes de arquivos exclusivos; adicione mais dígitos para segurança extra contra duplicatas. Contador de incremento automático para exportações em lote, ideal ao salvar várias imagens em uma sessão. Insere o nome da predefinição aplicada no nome do arquivo para que você possa lembrar facilmente como a imagem foi processada. Exibe o modo de dimensionamento da imagem usado durante o processamento, ajudando a distinguir imagens redimensionadas, cortadas ou ajustadas. Texto personalizado colocado no final do nome do arquivo, útil para controle de versão como _v2, _edited ou _final. A extensão do arquivo (png, jpg, webp, etc.), correspondendo automaticamente ao formato real salvo. Um carimbo de data/hora personalizável que permite definir seu próprio formato por especificação Java para uma classificação perfeita. Tipo de arremesso Android nativo Estilo iOS Curva Suave Parada rápida Saltitante Flutuante Rápido Ultra Suave Adaptativo Consciente de acessibilidade Movimento Reduzido Física de rolagem nativa do Android Rolagem suave e equilibrada para uso geral Comportamento de rolagem semelhante ao iOS de maior fricção Curva spline exclusiva para uma sensação de rolagem distinta Rolagem precisa com parada rápida Rolagem saltitante divertida e responsiva Pergaminhos longos e deslizantes para navegação de conteúdo Rolagem rápida e responsiva para UIs interativas Rolagem suave premium com impulso estendido Ajusta a física com base na velocidade de arremesso Respeita as configurações de acessibilidade do sistema Movimento mínimo para necessidades de acessibilidade Linhas Primárias Adiciona linha mais grossa a cada quinta linha Cor de preenchimento Ferramentas ocultas Ferramentas ocultas para compartilhamento Biblioteca de cores Navegue por uma vasta coleção de cores Torna mais nítida e remove o desfoque das imagens, mantendo os detalhes naturais, ideal para corrigir fotos fora de foco. Restaura de forma inteligente imagens que foram redimensionadas anteriormente, recuperando detalhes e texturas perdidas. Otimizado para conteúdo de ação ao vivo, reduz artefatos de compactação e aprimora detalhes finos em quadros de filmes/programas de TV. Converte imagens com qualidade VHS em HD, removendo o ruído da fita e melhorando a resolução, preservando a sensação vintage. Especializado para imagens e capturas de tela com muito texto, torna os caracteres mais nítidos e melhora a legibilidade. Upscaling avançado treinado em diversos conjuntos de dados, excelente para aprimoramento de fotos de uso geral. Otimizado para fotos compactadas na Web, remove artefatos JPEG e restaura a aparência natural. Versão aprimorada para fotos da web com melhor preservação de textura e redução de artefatos. Upscaling 2x com tecnologia Dual Aggregation Transformer, mantém a nitidez e os detalhes naturais. Upscaling 3x usando arquitetura de transformador avançada, ideal para necessidades moderadas de ampliação. Upscaling 4x de alta qualidade com rede de transformadores de última geração, preserva detalhes finos em escalas maiores. Remove desfoque/ruído e tremores das fotos. Uso geral, mas melhor em fotos. Restaura imagens de baixa qualidade usando o transformador Swin2SR, otimizado para degradação BSRGAN. Ótimo para corrigir artefatos de compactação pesada e aprimorar detalhes em escala 4x. Upscaling 4x com transformador SwinIR treinado na degradação BSRGAN. Usa GAN para texturas mais nítidas e detalhes mais naturais em fotos e cenas complexas. Caminho Mesclar PDF Combine vários arquivos PDF em um documento Ordem dos arquivos pp. Dividir PDF Extraia páginas específicas de um documento PDF Girar PDF Corrija a orientação da página permanentemente Páginas Reorganizar PDF Arraste e solte páginas para reordená-las Segure e arraste páginas Números de página Adicione numeração aos seus documentos automaticamente Formato da etiqueta PDF para texto (OCR) Extraia texto simples de seus documentos PDF Sobreponha texto personalizado para marca ou segurança Assinatura Adicione sua assinatura eletrônica a qualquer documento Isso será usado como assinatura Desbloquear PDF Remova senhas de seus arquivos protegidos Proteger PDF Proteja seus documentos com criptografia forte Sucesso PDF desbloqueado, você pode salvá-lo ou compartilhá-lo Reparar PDF Tentativa de consertar documentos corrompidos ou ilegíveis Tons de cinza Converta todas as imagens incorporadas em documentos em tons de cinza Compactar PDF Otimize o tamanho do arquivo do seu documento para facilitar o compartilhamento ImageToolbox reconstrói a tabela de referência cruzada interna e regenera a estrutura do arquivo do zero. Isso pode restaurar o acesso a muitos arquivos que \"não podem ser abertos\" Esta ferramenta converte todas as imagens de documentos em tons de cinza. Melhor para imprimir e reduzir o tamanho do arquivo Metadados Edite as propriedades do documento para melhor privacidade Etiquetas Produtor Autor Palavras-chave Criador Limpeza Profunda de Privacidade Limpe todos os metadados disponíveis para este documento Página OCR profundo Extraia o texto do documento e armazene-o em um arquivo de texto usando o mecanismo Tesseract Não é possível remover todas as páginas Remover páginas PDF Remova páginas específicas do documento PDF Toque para remover Manualmente Cortar PDF Cortar páginas do documento em qualquer limite Achatar PDF Torne o PDF inalterável rasterizando as páginas do documento Não foi possível iniciar a câmera. Verifique as permissões e certifique-se de que não esteja sendo usado por outro aplicativo. Extrair imagens Extraia imagens incorporadas em PDFs em sua resolução original Este arquivo PDF não contém imagens incorporadas Esta ferramenta digitaliza todas as páginas e recupera imagens originais de qualidade total – perfeita para salvar originais de documentos Desenhar Assinatura Parâmetros de caneta Utilizar assinatura própria como imagem a ser colocada em documentos Compactar PDF Divida o documento com determinado intervalo e empacote novos documentos em arquivo zip Intervalo Imprimir PDF Prepare o documento para impressão com tamanho de página personalizado Páginas por folha Orientação Tamanho da página Margem Florescer Joelho macio Otimizado para anime e desenhos animados. Aprimoramento rápido com cores naturais aprimoradas e menos artefatos Estilo semelhante ao Samsung One UI 7 Insira símbolos matemáticos básicos aqui para calcular o valor desejado (por exemplo, (5+5)*10) Expressão matemática Selecione até %1$s imagens Manter data e hora Sempre preserve tags exif relacionadas à data e hora, funciona independentemente da opção manter exif Cor de fundo para formatos alfa Adiciona a capacidade de definir a cor de fundo para cada formato de imagem com suporte alfa, quando desabilitado, disponível apenas para formatos não alfa Abrir projeto Continue editando um projeto do Image Toolbox salvo anteriormente Não é possível abrir o projeto do Image Toolbox O projeto do Image Toolbox está faltando dados do projeto O projeto do Image Toolbox está corrompido Versão do projeto do Image Toolbox não compatível: %1$d Salvar projeto Armazene camadas, plano de fundo e histórico de edição em um arquivo de projeto editável Falha ao abrir Escreva em PDF pesquisável Reconheça texto de lote de imagens e salve PDF pesquisável com imagem e camada de texto selecionável Camada alfa Inversão Horizontal Inversão Vertical Trancar Adicionar sombra Cor da sombra Geometria do texto Esticar ou inclinar o texto para uma estilização mais nítida Escala X Inclinar X Remover anotações Remova os tipos de anotação selecionados, como links, comentários, destaques, formas ou campos de formulário das páginas PDF Hiperlinks Anexos de arquivo Linhas Pop-ups Selos Formas Notas de texto Marcação de texto Campos de formulário Marcação Desconhecido Anotações Desagrupar Adicione sombra desfocada atrás da camada com cores e deslocamentos configuráveis ================================================ FILE: core/resources/src/main/res/values-ro/strings.xml ================================================ Mărime %1$s Înălțime %1$s Calitatea Extensie Tip de redimensionare Explicit Flexibil Închidere aplicație Rămâneți Închideți Resetare imagine Imaginele modificate vor fi readuse la valorile inițiale Valori resetate corespunzător Resetare Repornește aplicația Copiat în clipboard Excepție Editare EXIF Ok Nu s-au găsit date EXIF Adăugați etichetă Curăță Curăță EXIF Toate datele EXIF al imaginii vor fi șterse. Această acțiune nu poate fi anulată! Presetări Decupare Salvare Toate modificările nesalvate vor fi pierdute, dacă ieșiți acum Obțineți cele mai recente actualizări, discutați probleme și multe altele Editare unică Modificați specificațiile unei singure imagini date Alegeți culoarea Imagine Culoare copiată Decupați imaginea la orice limită Versiune Păstrează EXIF Imagini: %d Modifică previzualizarea Elimină Generarea paletei de culori din imaginea dată Generarea paletei Actualizează Tip nesuportat: %1$s Original Dosar de ieșire Implicit Personalizat Stocarea dispozitivului Dimensiune maximă în KB Compară Compară două imagini date Alegeți două imagini pentru a începe Alegeți imagini Setări Modul de noapte Întunecat Luminos Culori dinamice Personalizare Permiteți monetizarea imaginii Limbă Imaginea este prea mare pentru previzualizare, dar salvarea va fi încercată oricum Se încarcă… Ceva nu a mers bine: %1$s Alegeți imaginea pentru a începe Lățime %1$s Alegeți imaginea Ești sigur să închizi aplicația? Ceva nu a mers bine Anulare Salvează Codul sursă Alegeți culoarea din imagine, copiați sau partajați Culoare Paletă Nespecificat Versiune nouă %1$s Sistem Nu se poate genera paleta din imaginea selectată Redimensionare în funcție de dimensiune Redimensionează o imagine după dimensiunea dată în KB Dacă este activată, atunci când alegeți o imagine pentru editare, culorile aplicației vor fi adoptate pentru această imagine Modul Amoled Dacă este activată, culoarea suprafețelor va fi setată la întuneric absolut în modul de noapte Roșu Verde Albastru Lipiți codul aRGB valid. Nimic de lipit Schema de culori Nu se poate schimba schema de culori a aplicației în timp ce culorile dinamice sunt activate Tema aplicației se va baza pe culoarea selectată Despre aplicație Nu s-au găsit actualizări Detector de probleme Trimiteți rapoarte de erori și solicitări de caracteristici aici Ajutați la traducere Căutați aici Corectarea greșelilor de traducere sau localizarea proiectului în alte limbi Interogarea dvs. nu a găsit nimic Dacă este activată, atunci culorile aplicației vor fi adoptate la culorile fundalului Nu a reușit să salveze imagini %d Suprafață Principal Terțiar Secundar Grosimea marginii Această aplicație este complet gratuită, dar dacă doriți să sprijiniți dezvoltarea proiectului, puteți face clic aici Valori Adăugare Permisiune Acordă Aplicația are nevoie de acces la spațiul dvs. de stocare pentru a salva imagini pentru a funcționa, este necesar. Vă rugăm să acordați permisiunea în caseta de dialog următoare. Aplicația necesită această permisiune pentru a funcționa, acordă-l manual Stocare externă Culori monet Alinierea FAB Verifică dacă există actualizări Dacă este activată, dialogul de actualizare vă va fi afișat la pornirea aplicației Mărește imaginea Prefix Numele fișierului Partajare Redimensionează imaginile în imagini cu o latură lungă dată de parametrul Lățime sau Înălțime, toate calculele de dimensiune vor fi efectuate după salvare - păstrează raportul de aspect Nuanţă Saturare Expunere Balans de alb Temperatură Repere Umbre Ceață Pantă Alb-negru Lățimea liniei Hașura încrucișată Spațiere Marginea Sobel Estompare Început Înlocuiți numărul de ordine Link imagine Umplere Potrivire Opacitate Scară Rază Bulge Limitele redimensionării Redimensionarea imaginilor selectate pentru a respecta limitele de lățime și înălțime date, păstrând în același timp raportul de aspect Schiță Prag Suprimare non maximă Includere slabă a pixelilor Priveşte în sus Emoji Selectați ce emoji va fi afișat pe ecranul principal Estompare stivă Încețoșează centrul y Încețoșare zoom Adăugați dimensiunea fișierului Dacă este activată, adaugă lățimea și înălțimea imaginii salvate la numele fișierului de ieșire Previzualizare imagine Previzualizați orice tip de imagine: GIF, SVG și așa mai departe Selector de fotografii modern Android, care apare în partea de jos a ecranului, poate funcționa numai pe Android 12+. Are probleme cu primirea metadatelor EXIF Utilizați intenția GetContent (Obțineți conținut) pentru a selecta imaginea. Funcționează peste tot, dar se știe că are probleme cu primirea imaginilor selectate pe unele dispozitive. Nu este vina mea. Determină ordinea instrumentelor pe ecranul principal sequenceNum originalFilename Adăugați numele fișierului original Dacă este activată, adaugă numele fișierului original în numele imaginii de ieșire Dacă este activată, înlocuiește marcajul de timp standard cu numărul de secvență a imaginii dacă utilizați procesarea în lot Adăugarea numelui original al fișierului nu funcționează dacă este selectată sursa de imagine a selectorului de fotografii Încărcați imaginea de pe net Încărcați orice imagine de pe internet pentru a o previzualiza, mări, edita și salva dacă doriți. Nicio imagine Forțează fiecare imagine într-o imagine dată de parametrul Lățime și Înălțime - poate schimba raportul de aspect Luminozitate Contrast Adăugați filtru Filtru Aplicați orice lanț de filtre pentru imaginile date Filtre Lumină Filtru de culoare Alfa Tentă Monocrom Gamma Lumini și umbre Efect Distanţă Ascuțire Sepia Negativ Solarizare Vibranță Semitonuri Spațiul de culori CGA Grava Laplacian Vinietă Sfârșit Netezire Kuwahara Deformare Unghi Vârtej Dilatarea Refracția sferei Indicele de refracție Refracția sferei de sticlă Matricea de culori Niveluri de cuantizare Toon neted Toon Posterizați Convoluție 3x3 filtru RGB Culoare falsă Prima culoare A doua culoare Reordonați Dimensiune estompare Încețoșează centrul x Echilibrul culorilor Pragul de luminanță Ștergeți EXIF Ștergeți metadatele EXIF din orice set de imagini Sursa imaginii Selector de fotografii Galerie Explorator de fișiere Selector simplu de imagini pentru galerie. Acesta va funcționa numai dacă aveți o aplicație care oferă selectare media Aranjament de opțiuni Editare Ordine Scara de conținut Numărul de emoji Fișier procesat Alegeți fișierul Compatibilitate Cache Criptarea fișierelor pe bază de parolă. Fișierele procesate pot fi stocate în directorul selectat sau partajate. De asemenea, fișierele decriptate pot fi deschise direct. AES-256, modul GCM, fără umplutură, IV-uri aleatoare de 12 octeți. Cheile sunt folosite ca hash-uri SHA-3 (256 de biți). Dimensiunea maximă a fișierului este restricționată de sistemul de operare Android și de memoria disponibilă, care depinde în mod evident de dispozitivul dvs. \nVă rugăm să rețineți: memoria nu este stocare. Încercarea de a salva imaginea cu lățimea și înălțimea date poate cauza o eroare OOM. Faceți acest lucru pe propriul risc și să nu spuneți că nu v-am avertizat! S-a găsit %1$s Ștergerea automată a memoriei cache Instrumente Grupați opțiunile după tip Grupează opțiunile de pe ecranul principal în funcție de tipul lor, în loc de un aranjament personalizat al listei Nu se poate modifica aranjamentul în timp ce gruparea opțiunilor este activată Editați captura de ecran Personalizare secundară Captură de ecran Parola nevalidă sau fișierul ales nu este criptat Opțiune de rezervă Copiere Ați dezactivat aplicația Fișiere, activați-o pentru a utiliza această caracteristică Salvarea în modul %1$s poate fi instabilă, deoarece este un format fără pierderi Dacă este activată, memoria cache a aplicației va fi ștearsă la pornirea aplicației Ocolire Mărimea cache-ului Creare Criptare Decriptare Alegeți fișierul pentru a începe Decriptare Implementarea Desenați pe imagine ca într-un caiet de schițe sau desenați pe fundal Alegeți o imagine și desenați ceva pe ea Desenați pe fundal Alegeți culoarea de fundal și desenați deasupra ei Culoarea fundalului Vă rugăm să rețineți că nu este garantată compatibilitatea cu alte programe sau servicii de criptare a fișierelor. Un tratament al cheii sau o configurație de cifrare ușor diferită poate cauza incompatibilitate. Mărime fișier Desenare Culoarea vopselei Vopsea alfa Desenează pe imagine Cifru Criptați și decriptați orice fișier (nu numai imaginile) pe baza algoritmului de criptare AES Criptare Cheie Stocați acest fișier pe dispozitiv sau utilizați acțiunea de partajare pentru a-l pune oriunde doriți Caracteristici Tăiere mască Simboluri Obiecte Activați emoji Călătorii și Locuri Spațiile transparente din jurul imaginii vor fi tăiate Ștergere automată a fundalului Restaurați imaginea Nume de fișier aleatoriu Dacă este activată, numele fișierului de ieșire va fi complet aleatoriu Salvat în dosarul %1$s cu numele %2$s Salvat în folderul %1$s Conversație prin Telegram Discutați despre aplicație și obțineți feedback de la alți utilizatori. De asemenea, puteți obține actualizări beta și informații de aici. Raportul de aspect Utilizați acest tip de mască pentru a crea o mască din imaginea dată, observați că aceasta TREBUIE să aibă un canal alfa. Backup și restaurare Restaurare Salvați setările aplicației într-un fișier Aa Ăă Ââ Bb Cc Dd Ee Ff Gg Hh Ii Îî Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Șș Tt Țț Uu Vv Ww Xx Yy Zz 0123456789 !? Emoții Mâncare și băutură Natură și Animale Activități Eliminarea fundalului Eliminați fundalul din imagine prin desenare sau utilizați opțiunea Auto Tăiați imaginea Metadatele imaginii originale vor fi păstrate Mod radieră Ștergeți fundalul Redimensionare și conversie Backup Număr maxim de culori Analize Setări restaurate cu succes Contactați-mă Restaurați setările aplicației din fișierul generat anterior Dacă ați selectat presetarea 125, imaginea va fi salvată cu o dimensiune de 125% din imaginea originală. Dacă ați selectat preselecția 50, atunci imaginea va fi salvată cu dimensiunea de 50% Restaurați fundalul Fișier coruptat sau nu este un backup Acest lucru vă va readuce setările la valorile implicite. Rețineți că acest lucru nu poate fi anulat fără un fișier de backup menționat mai sus. Ștergeți schema Font Text Ștergeți Sunteți pe cale să ștergeți schema de culori selectată, această operațiune nu poate fi anulată. Scara fontului Implicit Folosirea unor fonturi mari poate cauza erori și probleme ale interfeței utilizator, care nu vor putea fi remediate. Utilizați cu prudență. Raza de estompare Pipetă Mod de desen Creați o problemă Oops… Ceva nu a mers bine. Îmi puteți scrie folosind opțiunile de mai jos și voi încerca să găsesc o soluție Modificați dimensiunea imaginilor date sau convertiți-le în alte formate, de asemenea, acolo puteți edita metadatele EXIF dacă alegeți o singură imagine Pre-setarea aici determină % din fișierul de ieșire, de exemplu, dacă selectați pre-setarea 50 pe o imagine de 5 mb, atunci veți obține o imagine de 2,5 mb după salvare Acest lucru permite aplicației să colecteze rapoarte de crash-uri manual Permite colectarea de statistici anonime privind utilizarea aplicației În prezent, formatul %1$s permite doar citirea metadatelor EXIF pe Android. Imaginea de ieșire nu va avea metadate deloc, atunci când este salvată. Efort Actualizări Așteptați O valoare de %1$s înseamnă că se comprimă rapid, ceea ce duce la o dimensiune relativ mare a fișierului. %2$s înseamnă că trebuie să se petreacă mai mult timp comprimând, rezultând un fișier mai mic. Permiteți beta Salvarea este aproape finalizată. Anularea acum va necesita salvarea din nou. Verificarea actualizărilor va include versiunile beta ale aplicațiilor, dacă este activată Moliciunea periei Desenați săgeți Dacă este activată, calea de desen va fi reprezentată ca o săgeată îndreptată Imaginile vor fi decupate central la dimensiunea introdusă. Pânza va fi extinsă cu culoarea de fundal dată dacă imaginea este mai mică decât dimensiunile introduse. Donație Orizontal Recodificați Toleranță Ambele Ordinea imaginilor Fidelitate Margini blurate Curcubeu O temă jucăușă - nuanța culorii sursă nu apare în temă Margini decolorate O temă zgomotoasă, coloritul este maxim pentru paleta primară, crescut pentru ceilalți Coaserea imaginii Pixelare Un stil care este puțin mai cromatic decât monocrom Pixelare îmbunătățită Culoare țintă Alegeți cel puțin 2 imagini Acest verificator de actualizare se va conecta la GitHub pentru a verifica dacă există o nouă actualizare disponibilă. Dimensiunea pixelului O schemă care plasează culoarea sursă în Scheme.primaryContainer Spot tonal Înlocuiți culoarea Pixelare diamant Desenează margini neclare sub imaginea originală pentru a umple spațiile din jurul acesteia în loc de o singură culoare, dacă este activată Eliminați culoarea O temă monocromă, culorile sunt pur și simplu negru / alb / gri Imaginile mici vor fi scalate la cea mai mare imagine din secvență, dacă este activată Salată de fructe Pixelare diamant îmbunătățite Pixelare circular Blocați orientarea desenului Combinați imaginile date pentru a obține una mare O schemă care este foarte asemănătoare cu schema de conținut Verificați pentru actualizări Orientarea imaginii Conținut Dezactivată Vertical Expresiv Neutru Culoare de eliminat Stilul implicit al paletei, permite personalizarea tuturor celor patru culori, altele vă permit să setați numai culoarea cheie Culoare de înlocuit Scara imaginii de ieșire Dacă este activată în modul desen, ecranul nu se va roti Stilul paletului Vibrant Regular Pixelare circular îmbunătățită Atenție Scalați imaginile mici la mari Înlocuiește culorile temei cu cele negative dacă este activată Căutați Permite posibilitatea de a căuta prin toate instrumentele disponibile pe ecranul principal Inversați culorile Instrumente PDF Filtru mască Imagini în PDF Măști Previzualizare PDF PDF în Imagini Împachetați imaginile date într-un fișier PDF Previzualizare PDF simplu Adăugați mască Operați cu fișiere PDF: Previzualizați, convertiți în lot de imagini sau creați una din imaginile date Masca %d Convertiți PDF în imagini în formatul de ieșire dat Glitch îmbunătățit Schimbarea canalului X Schimbarea canalului Y Dimensiunea corupției Top Fund Putere O singură coloană Bloc unic Eroare Cantitate Sămânță Anaglifă Zgomot Sortare pixeli Amesteca Ștergeți masca Sunteți pe cale să ștergeți masca de filtru selectată. Această operațiune nu poate fi anulată Drago Nu s-a găsit niciun director \"%1$s\", l-am schimbat la unul implicit, salvați din nou fișierul Clipboard Fixare automată Adaugă automat imaginea salvată în clipboard dacă este activată Vibrație Puterea la vibrație Pentru a suprascrie fișierele, trebuie să utilizați sursa de imagine \"Explorer\", încercați să alegeți din nou imaginile, am schimbat sursa imaginii cu cea necesară Fișierul original va fi înlocuit cu unul nou în loc să fie salvat în folderul selectat, această opțiune trebuie ca sursa imaginii să fie \"Explorer\" sau GetContent, când comutați, va fi setată automat Gol Gratuit Emoji ca schemă de culori Aplicați lanțuri de filtre pe anumite zone mascate, fiecare zonă de mască poate determina propriul set de filtre Variante simple Evidențiator Neon Pix Confidențialitate estompare Desenați trasee de iluminare ascuțite semi-transparente Adăugați un efect de strălucire desenelor dvs. Una implicită, cea mai simplă - doar culoarea Estompează imaginea sub traseul desenat pentru a securiza tot ce doriți să ascundeți Similar cu ceața de confidențialitate, dar pixelizează în loc să blureze Activează desenarea umbrelor în spatele comutatoarelor Activează desenarea umbrelor în spatele butoanelor de acțiune plutitoare Activează desenarea umbrelor în spatele butoanelor implicite Activează desenarea umbrelor în spatele barelor aplicației Rotire automată Permite adoptarea casetei de limită pentru orientarea imaginii Desenează săgeată dublă indicatoare de la punctul de început până la punctul final ca o linie Desenează un oval conturat de la punctul de început până la punctul final Desenează drept conturat de la punctul de început până la punctul final Suprascrieți fișierele Sufix Catmull Bicubic Interpolarea liniară (sau biliniară, în două dimensiuni) este, de obicei, bună pentru a schimba dimensiunea unei imagini, dar provoacă o oarecare înmuiere nedorită a detaliilor și poate fi totuși oarecum dințată Metodele de scalare mai bune includ reeșantionarea Lanczos și filtrele Mitchell-Netravali Una dintre cele mai simple modalități de creștere a dimensiunii, înlocuind fiecare pixel cu un număr de pixeli de aceeași culoare Cel mai simplu mod de scalare Android utilizat în aproape toate aplicațiile Metodă pentru interpolarea fără probleme și reeșantionarea unui set de puncte de control, utilizată în mod obișnuit în grafica computerizată pentru a crea curbe netede Funcția de fereastră aplicată adesea în procesarea semnalului pentru a minimiza scurgerea spectrală și pentru a îmbunătăți acuratețea analizei de frecvență prin înclinarea marginilor unui semnal Tehnica de interpolare matematică care utilizează valorile și derivatele la punctele finale ale unui segment de curbă pentru a genera o curbă netedă și continuă Metodă de reeșantionare care menține interpolarea de înaltă calitate prin aplicarea unei funcții sinc ponderate la valorile pixelilor Metodă de reeșantionare care utilizează un filtru de convoluție cu parametri ajustabili pentru a obține un echilibru între claritate și anti-aliasing în imaginea scalată Utilizează funcții polinomiale definite în bucăți pentru a interpola fără probleme și a aproxima o curbă sau o suprafață, o reprezentare flexibilă și continuă a formei Salvarea în spațiul de stocare nu va fi efectuată, iar imaginea va fi încercată să fie pusă doar în clipboard Permite mai multe limbi Numai orientare și detectarea scripturilor Orientare automată și detectarea scripturilor Doar automat Auto Text vertical cu un singur bloc O singura linie Un singur cuvânt Cuvânt în cerc Un singur caracter Text rar Orientarea textului dispersat și detectarea scripturilor Linie brută Doriți să ștergeți datele de antrenament OCR în limba \"%1$s\" pentru toate tipurile de recunoaștere sau numai pentru unul selectat (%2$s)? Color Stops Adăugați culoare Proprietăți Repetă filigranul peste imagine în loc de unul singur în poziția dată Offset Y Această imagine va fi folosită ca model pentru filigranare Întârziere de cadru milis FPS Dithering Quantizier Scara tonurilor de gri Bayer Two Cat Two Dithering Jarvis Judice Ninke Dithering Fals Floyd Steinberg Dithering Utilizează funcții polinomiale bicubice definite în bucăți pentru a interpola fără probleme și a aproxima o curbă sau o suprafață, o reprezentare flexibilă și continuă a formei Tilt Shift Corupția Shift X Schimbul de corupție Y Cort Blur Fade lateral Latură Eroda Poisson Blur Maparea tonurilor logaritmice ACES Filmic Tone Mapping Cristaliza Turbulenţă Ulei Hejl Burgess Tone Mapping Color Matrix 4x4 Color Matrix 3x3 Efecte simple polaroid Tritanomalie Deuteranomalie Protanomalie Epocă Browni Coda Chrome Vedere nocturnă Cald Misto Tritanopia Deuteranopia Protanopia Acromatomalie Acromatopsie Orange Haze Visul roz Lumină moale de primăvară Visul de lavandă Cyberpunk Lumină de limonadă Foc spectral Magia Nopții Întunericul Caramel Gradient futurist Soarele Verde Lumea Curcubeului Mov inchis Portalul Spațial Nu au fost adăugate filtre favorite încă Forma pictogramei Aldridge A tăia calea Uchimura Mobius Tranziție Vârf Anomalie de culoare Imaginile suprascrise la destinația inițială Nu se poate schimba formatul imaginii când este activată opțiunea de suprascriere a fișierelor Utilizează culoarea primară emoji ca schemă de culori a aplicației în loc de una definită manual Difuzia anizotropă Difuzia Conducere Eșalonare orizontală a vântului Blur bilateral rapid Culoarea cursei Sticlă fractală Amplitudine Marmură Efectul apei mărimea Frecvența X Frecvența Y Amplitudinea X Amplitudinea Y Distorsiunea Perlin ACES Hill Tone Mapping Hable Filmic Tone Mapping Actual Toate Filtru complet Aplicați orice lanțuri de filtre la imaginile date sau la o singură imagine Început Centru Sfârşit Creator de gradient Creați un gradient de o dimensiune de ieșire dată, cu culori personalizate și tip de aspect Viteză Dehaze Omega Evaluați aplicația Evaluați Această aplicație este complet gratuită, dacă doriți să devină mai mare, vă rugăm să vedeți proiectul pe Github 😄 Liniar Radial Mătura Tipul gradientului Centrul X Centrul Y Repetat Oglindă Clemă Decal lasou Desenează calea umplută închisă după calea dată Modul Draw Path Săgeată cu linie dublă Desen gratuit Săgeată dublă Săgeată linie Săgeată Linia Desenează calea ca valoare de intrare Desenează calea de la punctul de început până la punctul final ca o linie Desenează săgeata indicatoare de la punctul de început la punctul final ca o linie Desenează săgeata indicatoare dintr-o anumită cale Desenează săgeată dublă indicatoare dintr-o anumită cale Oval conturat Subliniat Rect Oval Rect Desenează rect de la punctul de început până la punctul final Desenează oval de la punctul de început până la punctul final Bayer Trei Câte Trei Dithering Bayer Patru Câte Patru Dithering Bayer Eight By Eight Dithering Floyd Steinberg Dithering Sierra Dithering Two Row Sierra Dithering Sierra Lite Dithering Atkinson Dithering Stucki Dithering Burkes Dithering Dithering de la stânga la dreapta Dithering aleatoriu Dithering prag simplu Culoarea măștii Previzualizare masca Masca de filtru desenată va fi redată pentru a vă arăta rezultatul aproximativ Modul de scalare Biliniar Hann Sihastrul Lanczos Mitchell Cel mai apropiat Splina De bază Valoare implicită Valoare în intervalul %1$s - %2$s Sigma Sigma spațială Neclaritate mediană Doar Clip Adaugă containerul cu forma selectată sub pictogramele principale ale cardurilor Aplicarea luminozității Ecran Suprapunere gradient Compuneți orice gradient al părții superioare a imaginii date Transformări Cameră Utilizează camera pentru a fotografia, rețineți că este posibil să obțineți o singură imagine din această sursă de imagine Cereale Neascutit Pastel Ora de aur Vara fierbinte Purple Mist răsărit Vârtej colorat Tonuri de toamnă Peisaj fantastic Explozie de culoare Gradient electric Vârtej roșu Cod digital Filigranare Acoperiți imagini cu filigrane de text/imagine personalizabile Repetați filigranul Offset X Tip filigran Culoarea textului Modul de suprapunere Bokeh Instrumente GIF Convertiți imaginile în imagine GIF sau extrageți cadre din imaginea GIF dată GIF în imagini Convertiți fișierul GIF într-un lot de imagini Convertiți un lot de imagini în fișier GIF Imagini în GIF Alegeți imaginea GIF pentru a începe Utilizați dimensiunea Primului cadru Înlocuiți dimensiunea specificată cu dimensiunile primului cadru Repetați numărătoarea Folosește Lasso Folosește Lasso ca în modul desen pentru a efectua ștergerea Previzualizare imagine originală Alpha Emoji-ul din bara de aplicații va fi schimbat continuu prin aleatoriu în loc să fie folosit unul selectat Emoji aleatorii Nu se poate folosi alegerea aleatorie a emoji-urilor când emoji-urile sunt dezactivate Nu se poate selecta un emoji când alegeți unul aleatoriu activat E-mail televizor vechi Amestecă estomparea OCR (Recunoaștere text) Recunoașteți textul din imaginea dată, peste 120 de limbi acceptate Imaginea nu are text sau aplicația nu a găsit-o Acuratețe: %1$s Tip de recunoaștere Rapid Standard Cel mai bun Nu există date Pentru funcționarea corectă a Tesseract OCR, datele de antrenament suplimentare (%1$s) trebuie descărcate pe dispozitivul dvs. \nDoriți să descărcați %2$s date? Descarca Fără conexiune, verificați-o și încercați din nou pentru a descărca modele de tren Limbi descărcate Limbi disponibile Modul de segmentare Peria va restabili fundalul în loc să îl ștergă Grilă orizontală Grilă verticală Modul de cusătură Număr de rânduri Numărul de coloane Utilizați Pixel Switch Comutatorul asemănător pixelilor va fi folosit în locul materialului Google pe care l-ați bazat Slide Unul langa altul Comutați Atingeți Transparenţă Fișier suprascris cu numele %1$s la destinația inițială Lupă Activează lupa în partea de sus a degetului în modurile de desen pentru o mai bună accesibilitate Forțați valoarea inițială Forțează ca widgetul exif să fie verificat inițial Favorite B Spline Estompare nativă a stivei Tip de umplere inversă Dacă este activată, toate zonele care nu sunt mascate vor fi filtrate în locul comportamentului implicit Confeti Confetti vor fi afișate la salvare, partajare și alte acțiuni principale Modul securizat Ascunde conținutul la ieșire, de asemenea, ecranul nu poate fi capturat sau înregistrat Containere Permite desenarea umbrelor în spatele containerelor Glisoare Comutatoare FAB-uri Butoane Activează desenarea umbrelor în spatele glisoarelor Bare de aplicații Ieșire Dacă părăsiți previzualizarea acum, va trebui să adăugați din nou imaginile Formatul imaginii Creați paleta Material You din imagine Culori Închise Utilizează schema de culori modul de noapte în loc de varianta de lumină Copiați ca cod\" Jetpack Compose\" Blur încrucișat Inel Neclar Cercul Blur Ceață stelară Înclinare liniară Tilt Shift Etichete Pentru a elimina Instrumente APNG Convertiți imaginile în imagine APNG sau extrageți cadre din imaginea APNG dată APNG în imagini Convertiți fișierul APNG într-un lot de imagini Imagini în APNG Alegeți imaginea APNG pentru a începe Neclaritate de miscare Convertiți un lot de imagini în fișier APNG Zip Creați un fișier Zip din fișiere sau imagini date Trageți Lățimea mânerului Tip confetti Festiv Explozie Ploaie Colțuri Instrumente JXL Efectuați transcodarea JXL ~ JPEG fără pierderi de calitate sau convertiți GIF/APNG în animație JXL JXL în JPEG Efectuați transcodarea fără pierderi de la JXL la JPEG Efectuați transcodarea fără pierderi de la JPEG la JXL JPEG în JXL Scanați documente și creați PDF-uri sau imagini separate din acestea Controlează viteza de decodare a imaginii rezultate, aceasta ar trebui să ajute la deschiderea mai rapidă a imaginii rezultate, valoarea de %1$s înseamnă cea mai lentă decodare, în timp ce %2$s - cea mai rapidă, această setare poate crește dimensiunea imaginii de ieșire Nu se recomandă utilizarea acestui instrument pentru urmărirea imaginilor mari fără reducerea scalei, deoarece poate provoca blocarea și creșterea timpului de procesare Creare scurtătură Alegeți instrumentul pentru fixare Instrumentul va fi adăugat la ecranul de pornire al lansatorului dvs. ca scurtătură, utilizați-l în combinație cu setarea \"Săriți peste selectarea fișierelor\" pentru a obține comportamentul necesar Creare nou Creare șablon Alegeți imaginea JXL pentru a începe Alegeți filtrul de mai jos pentru a-l utiliza ca pensulă în desenul dvs. Alegeți un singur mediu Alegeți mai multe medii Alegere Straturi de marcare Modul straturi cu posibilitatea de a plasa liber imagini, text și multe altele Straturi pe imagine Utilizați imaginea ca fundal și adăugați diferite straturi deasupra acesteia Straturi pe fundal La fel ca prima opțiune, dar cu culoare în loc de imagine Selector încorporat Folosește propriul selector de imagini Image Toolbox în locul celor predefinite de sistem Săriți peste selectarea fișierelor Selectorul de fișiere va fi afișat imediat dacă acest lucru este posibil pe ecranul ales Alegeți imaginea WEBP pentru a începe Nu sunt selectate opțiuni favorite, adăugați-le în pagina de instrumente Adăugați favorite Imagine LUT țintă Fișier LUT 3D țintă (.cube / .CUBE) Obțineți imaginea Neutral LUT Descărcați colecția de LUT-uri, pe care le puteți aplica după descărcare Actualizați colecția de LUT-uri (numai cele noi vor fi puse la coadă), pe care le puteți aplica după descărcare Convertiți un lot de imagini dintr-un format în altul Conversie format Șablon de filtrare adăugat cu numele \"%1$s\" (%2$s) Nu s-au adăugat filtre pentru șabloane Fișierul selectat nu are date de șablon de filtrare Nume șablon Această imagine va fi utilizată pentru a previzualiza acest model de filtru Filtru șablon Ștergeți șablonul În primul rând, utilizați aplicația dvs. preferată de editare foto pentru a aplica un filtru la LUT neutru pe care îl puteți obține aici. Pentru ca acest lucru să funcționeze corect, culoarea fiecărui pixel nu trebuie să depindă de alți pixeli (de exemplu, blur nu va funcționa). Odată gata, utilizați noua imagine LUT ca intrare pentru filtrul LUT 512*512 Codul QR scanat nu este un model de filtru valid Șablon Sunteți pe cale să ștergeți filtrul șablon selectat. Această operațiune nu poate fi anulată Tipul de compresie Spațiul de culoare Scală spațiu de culoare Comprimare Schemă de compresie TIFF Modul motorului Stivuirea imaginilor Compresie cu pierderi Un filtru de interpolare Lagrange de ordinul 3, care oferă o precizie mai bună și rezultate mai netede pentru scalarea imaginilor Împărțirea imaginii O valoare de %1$s înseamnă o compresie lentă, rezultând o dimensiune relativ mică a fișierului. %2$s înseamnă o compresie mai rapidă, rezultând un fișier mare. Decodați șirul Base64 în imagine sau codificați imaginea în format Base64 Opțiuni Tesseract Împărțiți o singură imagine pe rânduri sau coloane Stivuiți imagini una peste alta cu moduri de amestecare alese Scanați codul QR și obțineți conținutul acestuia sau lipiți șirul dvs. de caractere pentru a genera unul nou Imagini în Svg Previzualizare linkuri Creare colaj Creați diverse colaje din 20 imagini Țineți apăsată imaginea pentru a o schimba, mutați și măriți pentru a ajusta poziția Tipul colajului Folosește compresia cu pierderi pentru a reduce dimensiunea fișierului în loc de compresie fără pierderi Trasarea imaginilor date în imagini SVG Convertiți loturile de imagini în formatul dat Dimensiunea filigranului Opțiunile de mai jos sunt pentru salvarea imaginilor, nu a PDF-urilor Ca imagine de cod QR Acordați permisiunea camerei în setări pentru a scana codul QR Scanați codul QR Scanați codul QR pentru a înlocui conținutul din câmp sau tastați ceva pentru a genera un nou cod QR Permite preluarea previzualizării linkurilor în locuri în care puteți obține text (QRCode, OCR etc.) Linkuri Amestecați, creați tonuri, generați nuanțe și multe altele Aplicați unele variabile de intrare pentru motorul tesseract Scanner de documente Acordați permisiunea camerei în setări pentru a scana Scanerul de documente Salvați ca imagine de cod QR Cod QR Descriere QR O metodă de interpolare bazată pe spline care oferă rezultate netede utilizând un filtru cu 64 de picături GIF în JXL Acțiuni Base64 GIF în WEBP Selectați modul de adaptare a culorilor temei pentru o anumită variantă de daltonism Daltonism complet, văzând doar nuanțe de gri Convertiți imagini GIF în imagini animate WEBP Convertiți imaginile APNG în imagini animate JXL Convertiți animația JXL în loturi de imagini Nu utilizați schema Color Blind Culorile vor fi exact așa cum sunt stabilite în temă Combinați modul de redimensionare a decupării cu acest parametru pentru a obține comportamentul dorit (Decupare/Potrivire la raportul de aspect) Toate proprietățile vor fi setate la valorile implicite, rețineți că această acțiune nu poate fi anulată Instrumente de culoare Instrumente WEBP Convertiți un lot de imagini într-un fișier WEBP Permiteți accesul tuturor fișierelor pentru a vedea JXL, QOI și alte imagini care nu sunt recunoscute pe Android ca fișiere imagine, fără a acorda permisiunea nu veți putea vedea aceste imagini aici Generarea zgomotului Generați diferite zgomote precum Perlin sau alte tipuri Unele comenzi de selecție vor utiliza un aspect compact pentru a ocupa mai puțin spațiu Configurare Instrumente Base64 Valoarea furnizată nu este un șir Base64 valid Nu se poate copia un șir Base64 gol sau invalid Copiere Base64 Lipire Base64 Încărcați imaginea pentru a copia sau salva șirul Base64. Dacă aveți șirul în sine, îl puteți lipi mai sus pentru a obține imaginea Licențe Open Source Vizualizați licențele bibliotecilor open source utilizate în această aplicație Salvare Base64 Partajare Base64 Importare Base64 Convertiți imagini GIF în imagini animate JXL JXL în imagini APNG în JXL Imagini în JXL Convertiți un lot de imagini în animație JXL Comportament Schemă pentru daltoniști Valorile implicite Aranjamentul instrumentelor Grupează instrumentele pe ecranul principal în funcție de tipul lor, în loc de un aranjament personalizat al listei Convertiți imaginile în imagini animate WEBP sau extrageți cadre din animațiile WEBP date Grupul de setări \"%1$s\" va fi extins în mod implicit Grup Alăturați-vă chat-ului nostru unde puteți discuta orice doriți și, de asemenea, uitați-vă la canalul CI unde postez versiuni beta și anunțuri Grupul de setări \"%1$s\" va fi colapsat în mod implicit Resetați proprietățile Instrumente de grup în funcție de tip Generați previzualizări Activați generarea previzualizării, acest lucru poate ajuta la evitarea blocajelor pe unele dispozitive, de asemenea, acest lucru dezactivează unele funcționalități de editare în cadrul opțiunii de editare unică O metodă de reeșantionare care utilizează un filtru Lanczos cu 2 lobi pentru interpolare de înaltă calitate cu artefacte minime O metodă de reeșantionare care utilizează un filtru Lanczos cu 3 lobi pentru o interpolare de înaltă calitate cu artefacte minime O metodă de reeșantionare care utilizează un filtru Lanczos cu 4 lobi pentru o interpolare de înaltă calitate cu artefacte minime Metodă de reeșantionare care menține interpolarea de înaltă calitate prin aplicarea unei funcții Bessel (jinc) la valorile pixelilor Canal CI Această imagine va fi utilizată pentru a genera histograme RGB și de luminozitate Factor de rată constantă (CRF) Un filtru de reeșantionare Lanczos cu un ordin mai mare de 6, care oferă o scalare mai clară și mai precisă a imaginii O metodă avansată de reeșantionare care oferă o interpolare de înaltă calitate cu artefacte minime Coordonate toleranță de rotunjire Utilizați paleta eșantionată Paleta de cuantizare va fi eșantionată dacă această opțiune este activată Redimensionare imagine Raport minim de culoare Scara căii Detaliat Imaginea va fi redusă la dimensiuni mai mici înainte de procesare, ceea ce ajută instrumentul să lucreze mai rapid și mai sigur Glisor personalizat cu aspect elegant cu animații, acesta este implicit pentru această aplicație Adăugați contur Glisor bazat pe Material 2, nu modern, simplu și direct Primiți notificări cu privire la noile versiuni ale aplicației și citiți anunțurile Titlul ecranului principal Selectori compacți Tip glisor Elegant Butoane de dialog centrale Dacă este posibil, butoanele dialogurilor vor fi poziționate în centru în loc de partea stângă Adăugați contur în jurul textului cu culoarea și lățimea specificate Armonizare culoare Nivelul armonizării Tip comutator Gradiente de plasă Uitați-vă la colecția online de Gradiente de plasă Instrumente de control Deschideți în Editare în loc de Previzualizare Imaginile de ieșire vor avea numele corespunzător sumei de control a datelor lor Permite aplicației să lipească automat datele din clipboard, astfel încât acestea vor apărea pe ecranul principal și le veți putea procesa Lățimea implicită a liniei Editare EXIF Modificarea metadatelor unei singure imagini fără recompresie Atingeți pentru a edita etichetele disponibile Culoare de desen implicită Mod implicit al căii de desenare Salvare o singură dată Locație Adăugare marcaj temporal Marcaj temporal formatat Activați formatarea marcajului temporal în numele fișierului de ieșire în loc de milisecunde de bază Vizualizați și editați locațiile de salvare unice pe care le puteți utiliza prin apăsarea lungă a butonului de salvare în majoritatea tuturor opțiunilor Setări rapide lateral Adăugați o bandă plutitoare în partea aleasă în timpul editării imaginilor, care va deschide setările rapide dacă faceți clic pe ea Suma de control ca nume de fișier Lipire automată Atunci când selectați imaginea pentru a o deschide (previzualizare) în ImageToolbox, în loc de previzualizare se va deschide foaia de selecție de editare Permiteți introducerea pe câmpul de text Activează câmpul de text din spatele selecției presetărilor, pentru a le introduce din mers Activează adăugarea marcajului temporal la numele fișierului de ieșire Compararea sumelor de control, calcularea hașurilor, crearea de șiruri hexazecimale din fișiere utilizând diferiți algoritmi de hașurare Afișarea setărilor în peisaj Dacă acest lucru este dezactivat, atunci în modul peisaj setările vor fi deschise pe butonul din bara de aplicații de sus, ca întotdeauna, în loc de opțiunea vizibilă permanentă Setări ecran complet Activați-o și pagina de setări va fi întotdeauna deschisă ca ecran complet în loc de foaia glisantă a sertarului Confirmare ieșire instrument Dacă aveți modificări nesalvate în timp ce utilizați anumite instrumente și încercați să le închideți, se va afișa un dialog de confirmare Tăierea imaginii Tăiați o parte a imaginii și îmbinați cele din stânga (pot fi inverse) prin linii verticale sau orizontale estompare gaussiană Cutie estompată estompare bilaterală estompare rapidă Pixelarea accidentului vascular cerebral Modul Tile Blur Gaussian rapid 2D Blur gaussian rapid 3D Blur Gaussian rapid 4D Lanczos Bessel Triere Data Data (inversată) Nume Nume (inversat) Configurarea canalelor Astăzi Ieri Fără permisiuni Cerere Încearcă din nou Compune Un Jetpack Compose Material pe care îl schimbați Un material pe care îl schimbi Max Redimensionați ancora Pixel Fluent Un comutator bazat pe sistemul de proiectare „Fluent”. Cupertino Un comutator bazat pe sistemul de design „Cupertino”. Calea Omite Pragul liniilor Pragul cuadratic Moştenire Rețeaua LSTM Legacy și LSTM Convertit Adăugați un dosar nou Biți pe probă Interpretare fotometrică Mostre per pixel Configurație plană Y Cb Cr Sub eșantionare Y Cb Cr Poziţionare X Rezoluție Rezoluție Y Unitatea de rezoluție Decalaje de benzi Rânduri pe bandă Strip Byte Counts Format de schimb JPEG Lungimea formatului de schimb JPEG Funcția de transfer Punctul Alb Cromatici primare Y Cb Cr Coeficienți Referință Black White Data Ora Descrierea imaginii Face Model Software Artist Drepturi de autor Versiune Exif Versiunea Flashpix Gamma Dimensiunea Pixel X Dimensiunea pixelului Y Biți comprimați per pixel Notă producătorului Comentariu utilizator Fișier de sunet înrudit Data Ora Original Data Ora digitizat Timp de compensare Offset Time Original Timp de compensare digitalizat Timp sub sec Sub Sec Time Original Timp sub sec. digitizat Timp de expunere Numărul F Programul de expunere Sensibilitatea spectrală Sensibilitate fotografică Oecf Tip de sensibilitate Sensibilitate standard de ieșire Indicele de expunere recomandat Viteza ISO Viteza ISO Latitudine aaa Viteza ISO Latitudine zzz Valoarea vitezei obturatorului Valoarea diafragmei Valoarea luminozității Valoarea părtinirii expunerii Valoarea maximă a diafragmei Distanța subiectului Modul de măsurare Flash Domeniul de subiect Distanța focală Energie Flash Răspuns în frecvență spațială Rezoluția planului focal X Rezoluția Y a planului focal Unitatea de rezoluție în planul focal Locația subiectului Indicele de expunere Metoda de detectare Sursa fișierului Model CFA Personalizat randat Modul de expunere Balanța de alb Raport de zoom digital Distanța focală în film de 35 mm Tip de captură a scenei Obțineți controlul Contrast Saturaţie Claritate Descrierea setărilor dispozitivului Distanța subiectului ID unic de imagine Numele proprietarului camerei Numărul de serie al corpului Specificații lentile Fabricarea lentilelor Model de lentile Numărul de serie al obiectivului ID versiunea GPS GPS Latitudine Ref GPS Latitudine GPS Longitudine Ref Longitudine GPS Altitudine GPS Ref Altitudine GPS Marca de timp GPS Sateliți GPS Stare GPS Modul de măsurare GPS GPS DOP Viteza GPS Ref Viteza GPS Traseu GPS Ref Track GPS Direcție imagini GPS Ref Direcția imaginii GPS Date de hartă GPS GPS Dest Latitudine Ref GPS Dest Latitude GPS Dest Longitudine Ref GPS Dest Longitudine Rulment GPS Dest Ref Rulment GPS Dest Distanța de destinație GPS Ref Distanța de destinație GPS Metoda de procesare GPS Informații despre zonă GPS Ștampila de dată GPS Diferenţial GPS Eroare de poziționare GPS H Index de interoperabilitate Versiunea DNG Dimensiune implicită de decupare Previzualizare imagine Start Previzualizează lungimea imaginii Aspect Frame Marginea inferioară a senzorului Senzor Chenar stânga Senzor Chenar drept Marginea superioară a senzorului ISO Desenați text pe cale cu font și culoare date Dimensiunea fontului Repetați textul Textul curent va fi repetat până la sfârșitul căii în loc de desenul o singură dată Dimensiunea liniuței Utilizați imaginea selectată pentru a o desena de-a lungul căii date Această imagine va fi folosită ca intrare repetitivă a căii desenate Desenează triunghiul conturat de la punctul de început până la punctul final Desenează triunghiul conturat de la punctul de început până la punctul final Triunghi conturat Triunghi Desenează poligon de la punctul de început până la punctul final Poligon Poligon conturat Desenează poligonul conturat de la punctul de început până la punctul final Noduri Desenați poligonul obișnuit Desenați poligon care va fi regulat în loc de formă liberă Desenează stea de la punctul de început până la punctul final Stea Steaua conturată Desenează stea conturată de la punctul de început până la punctul final Raportul razei interioare Desenați o stea obișnuită Desenați o stea care va fi o formă obișnuită în loc de liberă Antialias Activează antialiasing pentru a preveni marginile ascuțite Faceți clic pentru a începe scanarea Începeți scanarea Salvați ca PDF Distribuie ca PDF Egalizare histograma HSV Egalizare histograma Introduceți un procentaj Liniar Egalizați pixelarea histogramei Mărimea rețelei X Dimensiunea grilei Y Egalizare histogramă adaptivă Egalizare histogramă Adaptive LUV Egalizare histogramă Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Decupați la conținut Culoarea cadrului Culoare de ignorat Ca dosar Salvați ca fișier Previzualizare filtru Conținutul codului Min Cub B-Spline Hamming Hanning Blackman Welch Quadric gaussian Sfinxul Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-El Cutie Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Interpolarea cubică oferă o scalare mai lină, luând în considerare cei mai apropiați 16 pixeli, oferind rezultate mai bune decât biliniare Utilizează funcții polinomiale definite în bucăți pentru a interpola fără probleme și a aproxima o curbă sau o suprafață, o reprezentare flexibilă și continuă a formei O funcție de fereastră utilizată pentru a reduce scurgerea spectrală prin înclinarea marginilor unui semnal, utilă în procesarea semnalului O variantă a ferestrei Hann, folosită în mod obișnuit pentru a reduce scurgerea spectrală în aplicațiile de procesare a semnalului O funcție de fereastră care oferă o rezoluție bună de frecvență prin minimizarea scurgerilor spectrale, adesea folosită în procesarea semnalului O funcție de fereastră concepută pentru a oferi o rezoluție bună de frecvență cu scurgeri spectrale reduse, adesea folosită în aplicațiile de procesare a semnalului O metodă care utilizează o funcție pătratică pentru interpolare, oferind rezultate netede și continue O metodă de interpolare care aplică o funcție Gaussiană, utilă pentru netezirea și reducerea zgomotului în imagini O funcție de fereastră triunghiulară utilizată în procesarea semnalului pentru a reduce scurgerea spectrală O metodă de interpolare de înaltă calitate, optimizată pentru redimensionarea naturală a imaginii, echilibrând claritatea și netezimea O variantă mai clară a metodei Robidoux, optimizată pentru redimensionarea clară a imaginii O metodă de interpolare bazată pe spline care oferă rezultate netede folosind un filtru de 16 atingeri O metodă de interpolare bazată pe spline care oferă rezultate netede folosind un filtru de 36 de atingeri O metodă de interpolare care utilizează fereastra Kaiser, oferind un control bun asupra compromisului dintre lățimea lobului principal și nivelul lobului lateral O funcție de fereastră hibridă care combină ferestrele Bartlett și Hann, utilizată pentru a reduce scurgerea spectrală în procesarea semnalului O metodă simplă de reeșantionare care utilizează media celor mai apropiate valori ale pixelilor, rezultând adesea un aspect blocat O funcție de fereastră utilizată pentru a reduce scurgerea spectrală, oferind o rezoluție bună a frecvenței în aplicațiile de procesare a semnalului O variantă a filtrului Lanczos 2 care utilizează funcția jinc, oferind interpolare de înaltă calitate cu artefacte minime O variantă a filtrului Lanczos 3 care utilizează funcția jinc, oferind interpolare de înaltă calitate cu artefacte minime O variantă a filtrului Lanczos 4 care utilizează funcția jinc, oferind interpolare de înaltă calitate cu artefacte minime Hanning EWA Varianta medie ponderată eliptică (EWA) a filtrului Hanning pentru interpolare și reeșantionare lină Robidoux EWA Varianta elliptical Weighted Average (EWA) a filtrului Robidoux pentru reeșantionare de înaltă calitate Blackman EVE Varianta elliptical Weighted Average (EWA) a filtrului Blackman pentru minimizarea artefactelor de apel Quadric EWA Varianta medie ponderată eliptică (EWA) a filtrului Quadric pentru o interpolare lină Robidoux Sharp EWA Varianta medie ponderată eliptică (EWA) a filtrului Robidoux Sharp pentru rezultate mai clare Lanczos 3 Jinc EWA Varianta elliptical Weighted Average (EWA) a filtrului Lanczos 3 Jinc pentru reeșantionare de înaltă calitate cu aliasing redus Ginseng Un filtru de reeșantionare conceput pentru procesarea imaginilor de înaltă calitate, cu un echilibru bun de claritate și netezime Ginseng EWA Varianta elliptical Weighted Average (EWA) a filtrului Ginseng pentru o calitate îmbunătățită a imaginii Lanczos Sharp EWA Varianta medie ponderată eliptică (EWA) a filtrului Lanczos Sharp pentru obținerea de rezultate clare cu artefacte minime Lanczos 4 Sharpest EWA Varianta elliptical Weighted Average (EWA) a filtrului Lanczos 4 Sharpest pentru reeșantionare extrem de clară a imaginii Lanczos Soft EWA Varianta elliptical Weighted Average (EWA) a filtrului Lanczos Soft pentru o reeșantionare mai fluidă a imaginii Haasn Soft Un filtru de reeșantionare conceput de Haasn pentru o scalare lină și fără artefacte a imaginii Îndepărtează pentru totdeauna Adăugați o imagine Coșurile numără Clahe HSL Clahe HSV Egalizare histogramă Adaptive HSL Egalizare histogramă Adaptive HSV Modul Edge Clip Înfășurați Dificultatea de a face distincția între nuanțe de roșu și verde Dificultate de a face distincția între nuanțe de verde și roșu Dificultatea de a face distincția între nuanțe de albastru și galben Incapacitatea de a percepe nuanțe roșii Incapacitatea de a percepe nuanțe verzi Incapacitatea de a percepe nuanțe albastre Sensibilitate redusă la toate culorile Sigmoidală Lagrange 2 Un filtru de interpolare Lagrange de ordinul 2, potrivit pentru scalarea imaginii de înaltă calitate, cu tranziții netede Lagrange 3 Lanczos 6 Lanczos 6 Jinc O variantă a filtrului Lanczos 6 care utilizează o funcție Jinc pentru o calitate îmbunătățită a reeșantionării imaginii Blur casetă liniară Blur liniar la cort Blur cutie gaussiană liniară Neclaritate stivă liniară Gaussian Box Blur Blur Gaussian Liniar Rapid În continuare Blur Gaussian Liniar Rapid Neclaritate liniară Gaussiană Alegeți un filtru pentru a-l folosi ca vopsea Înlocuiți filtrul Low Poly Pictura pe nisip Fit To Bounds Limbi importate cu succes Backup modele OCR Import Export Poziţie Centru Sus Stânga Sus dreapta Stânga jos Dreapta jos Centru de sus Centru dreapta Centru de jos Centru stânga Imagine țintă Transfer de paletă Ulei îmbunătățit Televizor vechi simplu HDR Gotham Schiță simplă Strălucire moale Poster color Tri Ton A treia culoare Clahe Oklab Clara Olch Clahe Jzazbz Buline Dithering 2x2 în cluster Dithering 4x4 în cluster Dithering 8x8 în cluster Yililoma Dithering Complementar Analog triadic Split Complementar tetradic Pătrat Analog + complementar Armonii de culoare Umbrire de culoare Variaţie Nuanțe Tonuri Nuanțe Amestecarea culorilor Informații despre culoare Culoare selectată Culoare de amestecat Nu se poate folosi monet în timp ce culorile dinamice sunt activate 512x512 2D LUT Un amator Domnișoară Etichetă Eleganță moale Varianta Soft Elegance Varianta de transfer de paletă 3D LUT LUT Bypass pentru înălbitor Lumina lumânărilor Drop Blues Chihlimbar nervos Culori de toamnă Stoc de film 50 Noapte de ceață Kodak Pop Art Celuloid Cafea Pădurea de Aur Verzui Galben retro Fișierele ICO pot fi salvate numai la dimensiunea maximă de 256 x 256 WEBP la imagini Convertiți fișierul WEBP într-un lot de imagini Imagini pe WEBP Fără acces complet la fișiere Activați marcajele de timp pentru a le selecta formatul Folosit recent Caseta de instrumente pentru imagini în Telegram 🎉 Potriviți o imagine la dimensiunile date și aplicați estompare sau culoare fundalului Vizibilitatea barelor de sistem Afișați barele de sistem prin glisare Permite glisarea pentru a afișa barele de sistem dacă sunt ascunse Auto Ascunde tot Arată toate Ascundeți bara de navigare Ascundeți bara de stare Frecvenţă Tip de zgomot Tip de rotație Tip fractal Octave Lacunaritatea Câştig Forța ponderată Puterea Ping Pong Funcția de distanță Tip de returnare Jitter Deformarea domeniului Aliniere Nume de fișier personalizat Selectați locația și numele fișierului care vor fi folosite pentru a salva imaginea curentă Salvat în dosar cu nume personalizat Dezactivați rotația Împiedică rotirea imaginilor cu gesturi cu două degete Activați fixarea la margini După deplasare sau mărire, imaginile se vor fixa pentru a umple marginile cadrului Histogramă Histograma imaginii RGB sau Luminozitate pentru a vă ajuta să faceți ajustări Opțiuni personalizate Opțiunile trebuie introduse după acest model: \"--{option_name} {value}\" Decupare automată Colțuri libere Decupați imaginea după poligon, aceasta corectează și perspectiva Coerce Puncte la limitele imaginii Punctele nu vor fi limitate de limitele imaginii, acest lucru util pentru corectarea mai precisă a perspectivei Masca Umplerea conștientă de conținut sub calea desenată Vindecarea punctului Utilizați Circle Kernel Deschidere Închidere Gradient morfologic Top Hat Pălărie Neagră Curbe de ton Resetează curbele Curbele vor fi revenite la valoarea implicită Stil de linie Dimensiunea golului întreruptă Punct întrerupt Ștampilat Zigzag Desenează o linie întreruptă de-a lungul traseului desenat cu dimensiunea spațiului specificat Desenează punct și linie întreruptă de-a lungul căii date Doar linii drepte implicite Desenează formele selectate de-a lungul căii cu spațiere specificată Desenează în zig-zag ondulat de-a lungul căii Raport în zig-zag Nu stivuiți cadre Permite eliminarea cadrelor anterioare, astfel încât acestea să nu se strângă unele pe altele Fade încrucișată Cadrele vor fi încrucișate unele în altele Cadrele de difuzare încrucișată numără Pragul Unu Pragul doi Canny Oglinda 101 Încețoșare zoom îmbunătățită Laplacian Simplu Sobel Simplu Grilă de ajutor Afișează grila de sprijin deasupra zonei de desen pentru a ajuta la manipulări precise Culoare grilă Lățimea celulei Înălțimea celulei Acordați permisiunea camerei în setări pentru a captura imaginea Biblioteca Lut Modificați previzualizarea implicită a imaginii pentru filtre Imagine de previzualizare Ascunde Spectacol Materialul 2 Un cursor Material You Aplicați Zonă Reeșantionarea utilizând relația de suprafață a pixelilor. Poate fi o metodă preferată pentru decimarea imaginii, deoarece oferă rezultate fără moire. Dar când imaginea este mărită, este similară cu metoda „Cel mai apropiat”. Activați Tonemapping Introduceți % Nu pot accesa site-ul, încercați să utilizați VPN sau verificați dacă adresa URL este corectă Editați stratul Beta Ștergeți selecția Baza 64 Opțiuni Acțiuni Culoarea conturului Dimensiunea conturului Rotaţie Software gratuit (partener) Mai mult software util în canalul partener al aplicațiilor Android Algoritm Calcula Text Hash Sumă de control Alegeți fișierul pentru a calcula suma de control pe baza algoritmului selectat Introduceți text pentru a calcula suma de control pe baza algoritmului selectat Sumă de control sursă Sumă de control de comparat Meci! Diferenţă Sumele de control sunt egale, poate fi sigur Sumele de control nu sunt egale, fișierul poate fi nesigur! Numai fonturile TTF și OTF pot fi importate Import font (TTF/OTF) Exportați fonturi Fonturi importate Eroare la salvarea încercării, încercați să schimbați folderul de ieșire Numele fișierului nu este setat Nici unul Pagini personalizate Selectarea paginilor Schimbați autocolantul Lățimea de potrivire Înălțime de potrivire Comparare lot Alegeți fișierul/fișierele pentru a calcula suma de control pe baza algoritmului selectat Alegeți Fișiere Alegeți Director Scala de lungime a capului Ştampila Marca temporală Format Pattern Captuseala Linie pivot verticală Linie pivot orizontală Selecția inversă Partea tăiată verticală va fi părăsită, în loc de unirea părților în jurul zonei tăiate Partea tăiată orizontal va fi părăsită, în loc să îmbine părțile în jurul zonei tăiate Colecție de degrade de plasă Creați gradient de plasă cu o cantitate personalizată de noduri și rezoluție Suprapunere cu gradient de plasă Compune gradient de plasă din partea de sus a imaginilor date Personalizare puncte Dimensiunea grilei Rezoluția X Rezoluția Y Rezoluţie Pixel cu pixel Evidențiați Culoare Tip de comparație de pixeli Scanați codul de bare Raportul de înălțime Tip cod de bare Aplicați alb/negru Imaginea codului de bare va fi complet alb-negru și nu va fi colorată după tema aplicației Scanați orice cod de bare (QR, EAN, AZTEC, …) și obțineți conținutul acestuia sau inserați textul pentru a genera unul nou Nu a fost găsit niciun cod de bare Codul de bare generat va fi aici Coperți audio Extrageți imagini de copertă de album din fișierele audio, cele mai comune formate sunt acceptate Alegeți audio pentru a începe Alegeți Audio Nu s-au găsit coperți Trimiteți jurnalele Faceți clic pentru a partaja fișierul jurnal al aplicației, acest lucru mă poate ajuta să identific problema și să remediez problemele Hopa… Ceva a mers prost Mă puteți contacta folosind opțiunile de mai jos și voi încerca să găsesc o soluție.\n(Nu uitați să atașați jurnalele) Scrieți în fișier Extrageți text din lotul de imagini și stocați-l într-un singur fișier text Scrieți în metadate Extrageți text din fiecare imagine și plasați-l în informațiile EXIF ​​ale fotografiilor relative Modul invizibil Utilizați steganografia pentru a crea filigrane invizibile pentru ochi în interiorul octeților imaginilor dvs Folosiți LSB Se va folosi metoda steganografiei LSB (Less Significant Bit), în caz contrar FD (Frequency Domain). Eliminare automată a ochilor roșii Parolă Deblocați PDF-ul este protejat Operațiune aproape finalizată. Anularea acum va necesita repornirea acestuia Data modificării Data modificării (inversată) Dimensiune Dimensiune (inversată) Tip MIME Tip MIME (inversat) Extensie Extensie (inversată) Data Adăugării Data adaugarii (inversata) De la stânga la dreapta De la dreapta la stânga De sus în jos De jos în sus Sticla lichida Un comutator bazat pe IOS 26 anunțat recent și pe sistemul de design din sticlă lichidă Alegeți imaginea sau inserați/importați datele Base64 de mai jos Introduceți linkul pentru imagine pentru a începe Lipiți linkul Caleidoscop Unghiul secundar Laturile Mix de canale Albastru verde Roșu albastru verde roșu În roșu În verde În albastru Cyan Magenta Galben Culoare semiton Contur Niveluri Offset Voronoi Cristalizează Formă Întinde Aleatorie Descurcă Difuz Câine Raza a doua Egaliza Strălucire Învârtiți și Ciupiți Pointilize Culoarea chenarului Coordonatele polare Rect la polar Polar spre rect Întoarceți în cerc Reduceți zgomotul Solarizare simplă Ţese X Gap Y Gap X Latime Lățimea Y Învârti Ștampilă de cauciuc Frotiu Densitate Amesteca Distorsiunea lentilei sferice Indicele de refracție Arc Unghiul de răspândire Scânteie Raze ASCII Gradient Maria Toamnă Os Jet Iarnă Ocean Vară Primăvară Varianta cool HSV Roz Fierbinte Cuvânt Magmă Infern Plasma Viridis Cetăţeni Amurg Twilight Shifted Perspectivă Auto Declina Permite decuparea Decupare sau Perspectivă Absolut Turbo Verde adânc Corectarea lentilelor Fișierul de profil al obiectivului țintă în format JSON Descărcați profile de lentile gata Procente de parte Exportați ca JSON Copiați șirul cu date din paletă ca reprezentare json Sculptura cusături Ecranul de pornire Blocare ecran Încorporat Export de imagini de fundal Reîmprospăta Obțineți imagini de fundal actuale Acasă, Blocare și încorporate Permiteți accesul la toate fișierele, acest lucru este necesar pentru a prelua imagini de fundal Permisiunea de gestionare a stocării externe nu este suficientă, trebuie să permiteți accesul la imaginile dvs., asigurați-vă că selectați „Permiteți toate” Adăugați presetarea la numele fișierului Adaugă sufixul cu presetarea selectată la numele fișierului imagine Adăugați modul de scalare a imaginii la numele fișierului Adaugă sufixul cu modul de scară a imaginii selectat la numele fișierului imagine Ascii Art Convertiți imaginea în text ascii care va arăta ca o imagine Params Aplică filtru negativ imaginii pentru un rezultat mai bun în unele cazuri Se procesează captură de ecran Captura de ecran nu a fost capturată, încercați din nou Salvarea a fost omisă %1$s fișiere au fost ignorate Permiteți săriți dacă este mai mare Unele instrumente vor putea sări peste salvarea imaginilor dacă dimensiunea fișierului rezultat ar fi mai mare decât cea originală Eveniment din calendar Contact E-mail Locaţie Telefon Text SMS URL Wifi Rețea deschisă N / A SSID Telefon Mesaj Adresa Subiect Corp Nume Organizare Titlu Telefoane E-mailuri URL-uri Adrese Rezumat Descriere Locaţie Organizator Data de începere Data de încheiere Stare Latitudine Longitudine Creați cod de bare Editați codul de bare Configurare Wi-Fi Securitate Alegeți contact Acordați persoanelor de contact din setări permisiunea de a completa automat folosind contactul selectat Informații de contact Prenume Al doilea prenume Nume Pronunţie Adăugați telefon Adăugați e-mail Adăugați adresa Site-ul web Adăugați site-ul web Nume formatat Această imagine va fi folosită pentru a plasa deasupra codului de bare Personalizarea codului Această imagine va fi folosită ca logo în centrul codului QR Logo Captuseala cu logo Dimensiunea logo-ului Colțuri de logo Al patrulea ochi Adaugă simetria ochiului codului qr adăugând al patrulea ochi în colțul de jos Forma pixelului Forma cadrului Forma mingii Nivel de corectare a erorilor Culoare închisă Culoare deschisă Hyper OS Stilul ca Xiaomi HyperOS Model de mască Este posibil ca acest cod să nu poată fi scanat, modificați parametrii de aspect pentru a-l face citibil cu toate dispozitivele Nu se poate scana Instrumentele vor arăta ca lansatorul de aplicații pe ecranul de pornire pentru a fi mai compact Modul Lansator Umple o zonă cu pensula și stilul selectate Umplere de inundație Spray Desenează calea în stil graffity Particule pătrate Particulele de pulverizare vor avea formă pătrată în loc de cercuri Instrumente de paletă Generați de bază/materialul pe care îl paletați din imagine sau importați/exportați în diferite formate de paletă Editați paleta Paleta de export/import în diferite formate Numele culorii Numele paletei Format paletă Exportați paleta generată în diferite formate Adaugă o nouă culoare paletei curente Formatul %1$s nu acceptă furnizarea numelui paletei Din cauza politicilor Magazinului Play, această funcție nu poate fi inclusă în versiunea actuală. Pentru a accesa această funcționalitate, vă rugăm să descărcați ImageToolbox dintr-o sursă alternativă. Puteți găsi versiunile disponibile pe GitHub mai jos. Deschideți pagina Github Fișierul original va fi înlocuit cu unul nou în loc să fie salvat în folderul selectat S-a detectat text filigran ascuns S-a detectat imaginea de filigran ascunsă Această imagine a fost ascunsă Inpainting generativ Vă permite să eliminați obiecte dintr-o imagine folosind un model AI, fără a vă baza pe OpenCV. Pentru a utiliza această caracteristică, aplicația va descărca modelul necesar (~200 MB) de pe GitHub Vă permite să eliminați obiecte dintr-o imagine folosind un model AI, fără a vă baza pe OpenCV. Aceasta ar putea fi o operațiune de lungă durată Analiza nivelului de eroare Gradient de luminanță Distanța medie Copiere detecție mișcare Reţine coeficient Datele din clipboard sunt prea mari Datele sunt prea mari pentru a fi copiate Pixelizare simplă a țesăturii Pixelizare eșalonată Pixelizare încrucișată Micro Macro Pixelization Pixelizare orbitală Pixelizare vortex Pixelizare grilă de impulsuri Pixelizarea nucleului Pixelizare țesătură radială Nu se poate deschide uri \"%1$s\" Modul Zăpadă Activat Cadru de chenar Varianta Glitch Schimbarea canalului Offset maxim VHS Block Glitch Dimensiunea blocului curbura CRT Curbură Chroma Pixel Melt Max Drop Instrumente AI Diverse instrumente pentru procesarea imaginilor prin modele IA, cum ar fi eliminarea artefactelor sau eliminarea zgomotului Compresie, linii zimțate Desene animate, compresie de difuzare Compresie generală, zgomot general Zgomot incolor de desene animate Rapid, compresie generală, zgomot general, animație/ benzi desenate/anime Scanarea cărților Corectarea expunerii Cel mai bun la compresie generală, imagini color Cel mai bun la compresie generală, imagini în tonuri de gri Compresie generală, imagini în tonuri de gri, mai puternice Zgomot general, imagini color Zgomot general, imagini color, detalii mai bune Zgomot general, imagini în tonuri de gri Zgomot general, imagini în tonuri de gri, mai puternice Zgomot general, imagini în tonuri de gri, cel mai puternic Compresie generală Compresie generală Texturizare, compresie h264 compresie VHS Compresie non-standard (cinepak, msvideo1, roq) Compresie Bink, mai bună la geometrie Compresie bink, mai puternică Bink compresie, moale, păstrează detaliile Eliminarea efectului de treaptă, netezirea Artă/desene scanate, compresie ușoară, moire Bande de culoare Încet, eliminând semitonurile Colorizator general pentru imagini în tonuri de gri/bw, pentru rezultate mai bune utilizați DDColor Îndepărtarea marginilor Îndepărtează supra-ascuțirea Încet, tremurând Anti-aliasing, artefacte generale, CGI KDM003 scanează procesarea Model ușor de îmbunătățire a imaginii Îndepărtarea artefactelor de compresie Îndepărtarea artefactelor de compresie Îndepărtarea bandajului cu rezultate netede Procesare model semiton Eliminarea modelului de dither V3 Îndepărtarea artefactelor JPEG V2 Îmbunătățirea texturii H.264 Ascuțire și îmbunătățire VHS Fuzionarea Dimensiunea bucatilor Dimensiune de suprapunere Imaginile de peste %1$s px vor fi tăiate și procesate în bucăți, suprapunerea le combină pentru a preveni cusăturile vizibile. Dimensiunile mari pot cauza instabilitate cu dispozitivele low-end Selectați unul pentru a începe Doriți să ștergeți modelul %1$s? Va trebui să-l descărcați din nou Confirma Modele Modele descărcate Modele disponibile Pregătirea Model activ Sesiunea nu a putut fi deschisă Numai modelele .onnx/.ort pot fi importate Import model Importați modelul onnx personalizat pentru utilizare ulterioară, sunt acceptate doar modelele onnx/ort, acceptă aproape toate variantele de tip esrgan Modele importate Zgomot general, imagini colorate Zgomot general, imagini colorate, mai puternice Zgomot general, imagini colorate, cel mai puternic Reduce artefactele de dithering și benzile de culoare, îmbunătățind degradeurile netede și zonele de culoare plate. Îmbunătățește luminozitatea și contrastul imaginii cu lumini echilibrate, păstrând în același timp culorile naturale. Iluminează imaginile întunecate, păstrând în același timp detaliile și evitând supraexpunerea. Îndepărtează tonul excesiv de culoare și restabilește un echilibru de culoare mai neutru și natural. Aplică tonuri de zgomot bazate pe Poisson, cu accent pe păstrarea detaliilor și texturilor fine. Aplică un ton moale de zgomot Poisson pentru rezultate vizuale mai fine și mai puțin agresive. Tonificare uniformă a zgomotului axată pe păstrarea detaliilor și claritatea imaginii. Tonifiere uniformă de zgomot blând pentru o textură subtilă și un aspect neted. Repara zonele deteriorate sau neuniforme prin revopsirea artefactelor și îmbunătățirea consistenței imaginii. Model ușor de debandare care elimină benzile de culoare cu costuri minime de performanță. Optimizează imaginile cu artefacte de compresie foarte ridicate (calitate 0-20%) pentru o claritate îmbunătățită. Îmbunătățește imaginile cu artefacte de compresie ridicată (20-40% calitate), restabilind detaliile și reducând zgomotul. Îmbunătățește imaginile cu compresie moderată (40-60% calitate), echilibrând claritatea și netezimea. Rafinează imaginile cu compresie ușoară (60-80% calitate) pentru a îmbunătăți detaliile și texturile subtile. Îmbunătățește ușor imaginile aproape fără pierderi (calitate 80-100%), păstrând în același timp aspectul și detaliile naturale. Colorare simpla si rapida, desene animate, nu ideala Reduce ușor neclaritatea imaginii, îmbunătățind claritatea fără a introduce artefacte. Operațiuni de lungă durată Procesarea imaginii Prelucrare Îndepărtează artefactele mari de compresie JPEG în imagini de calitate foarte scăzută (0-20%). Reduce artefactele JPEG puternice în imaginile foarte comprimate (20-40%). Curăță artefactele JPEG moderate, păstrând în același timp detaliile imaginii (40-60%). Rafinează artefactele JPEG ușoare în imagini de calitate destul de înaltă (60-80%). Reduce subtil artefactele JPEG minore în imagini aproape fără pierderi (80-100%). Îmbunătățește detaliile și texturile fine, îmbunătățind claritatea percepută fără artefacte grele. Procesare terminată Procesarea nu a reușit Îmbunătățește texturile și detaliile pielii, păstrând în același timp un aspect natural, optimizat pentru viteză. Îndepărtează artefactele de compresie JPEG și restabilește calitatea imaginii pentru fotografiile comprimate. Reduce zgomotul ISO în fotografiile realizate în condiții de lumină scăzută, păstrând detaliile. Corectează luminile supraexpuse sau „jumbo” și restabilește un echilibru tonal mai bun. Model de colorare ușor și rapid care adaugă culori naturale imaginilor în tonuri de gri. DEJPEG Dezgomot Colorează Artefacte Spori Anime Scanări Upscale Upscaler X4 pentru imagini generale; model minuscul care utilizează mai puțin GPU și timp, cu estompare moderată și dezgomot. Upscaler X2 pentru imagini generale, păstrând texturile și detaliile naturale. Upscaler X4 pentru imagini generale cu texturi îmbunătățite și rezultate realiste. Upscaler X4 optimizat pentru imagini anime; 6 blocuri RRDB pentru linii și detalii mai clare. Upscaler X4 cu pierdere MSE, produce rezultate mai fine și artefacte reduse pentru imagini generale. X4 Upscaler optimizat pentru imagini anime; Varianta 4B32F cu detalii mai clare și linii netede. Model X4 UltraSharp V2 pentru imagini generale; accentuează claritatea și claritatea. X4 UltraSharp V2 Lite; mai rapid și mai mic, păstrează detaliile folosind mai puțină memorie GPU. Model ușor pentru îndepărtarea rapidă a fundalului. Performanță și precizie echilibrate. Funcționează cu portrete, obiecte și scene. Recomandat pentru majoritatea cazurilor de utilizare. Eliminați BG Grosimea marginii orizontale Grosimea chenarului vertical %1$s culoare %1$s culori %1$s culori Modelul actual nu acceptă fragmentarea, imaginea va fi procesată la dimensiunile originale, acest lucru poate cauza un consum mare de memorie și probleme cu dispozitivele low-end Îmbunătățirea în bucăți este dezactivată, imaginea va fi procesată la dimensiunile originale, acest lucru poate cauza un consum mare de memorie și probleme cu dispozitivele de ultimă generație, dar poate oferi rezultate mai bune la inferență Bucățire Model de segmentare a imaginii de mare precizie pentru eliminarea fundalului Versiune ușoară a U2Net pentru eliminarea mai rapidă a fundalului cu o utilizare mai mică a memoriei. Modelul complet DDColor oferă o colorare de înaltă calitate pentru imagini generale cu artefacte minime. Cea mai bună alegere dintre toate modelele de colorare. DDColor Seturi de date artistice antrenate și private; produce rezultate de colorare diverse și artistice cu mai puține artefacte de culoare nerealiste. Model ușor BiRefNet bazat pe Swin Transformer pentru eliminarea precisă a fundalului. Eliminare de fundal de înaltă calitate, cu margini ascuțite și păstrare excelentă a detaliilor, în special pe obiecte complexe și fundaluri complicate. Model de îndepărtare a fundalului care produce măști precise, cu margini netede, potrivite pentru obiecte generale și pentru conservarea moderată a detaliilor. Model deja descărcat Modelul a fost importat cu succes Tip Cuvânt cheie Foarte rapid Normal Lent Foarte lent Calculați procente Valoarea minimă este %1$s Distorsionați imaginea desenând cu degetele Urzeală Duritate Modul Warp Mişcare Creste Se micsoreaza Swirl CW Rotiți în sens invers Fade Strength Top Drop Scădere de jos Începeți Drop Sfârșește Drop Descărcarea Forme netede Folosiți superelipse în loc de dreptunghiuri rotunjite standard pentru forme mai fine și mai naturale Tip de formă Tăiați rotunjite Netezi Margini ascuțite fără rotunjire Colțuri clasice rotunjite Tip de forme Dimensiunea colțurilor Squircle Elemente elegante de UI rotunjite Format nume de fișier Text personalizat plasat chiar la începutul numelui fișierului, perfect pentru numele proiectelor, mărci sau etichete personale. Folosește numele fișierului original fără extensie, ajutându-vă să păstrați intactă identificarea sursei. Lățimea imaginii în pixeli, utilă pentru urmărirea modificărilor rezoluției sau scalarea rezultatelor. Înălțimea imaginii în pixeli, utilă atunci când lucrați cu raporturi de aspect sau exporturi. Generează cifre aleatorii pentru a garanta nume de fișiere unice; adăugați mai multe cifre pentru mai multă siguranță împotriva duplicatelor. Contor cu incrementare automată pentru exporturi în loturi, ideal atunci când salvați mai multe imagini într-o singură sesiune. Inserează numele presetat aplicat în numele fișierului, astfel încât să vă puteți aminti cu ușurință cum a fost procesată imaginea. Afișează modul de scalare a imaginii utilizat în timpul procesării, ajutând la distingerea imaginilor redimensionate, decupate sau adaptate. Text personalizat plasat la sfârșitul numelui fișierului, util pentru versiuni precum _v2, _edited sau _final. Extensia fișierului (png, jpg, webp etc.), care se potrivește automat cu formatul salvat real. O marcaj de timp personalizabil care vă permite să vă definiți propriul format după specificațiile java pentru o sortare perfectă. Tip Fling Nativ Android Stilul iOS Curba lină Oprire rapidă Bouncy Plutitor Vioi Ultra Smooth Adaptiv Conștient de accesibilitate Mișcare redusă Fizica nativă a derulării Android Defilare echilibrată, lină pentru uz general Comportament de defilare asemănător iOS cu frecare mai mare Curba spline unică pentru o senzație distinctă de defilare Defilare precisă cu oprire rapidă Scroll jucăuș și receptiv Defilări lungi, glisante, pentru navigarea prin conținut Defilare rapidă și receptivă pentru interfețele de utilizare interactive Defilare lină premium cu impuls extins Ajustează fizica pe baza vitezei de aruncare Respectă setările de accesibilitate ale sistemului Mișcare minimă pentru nevoile de accesibilitate Liniile primare Adaugă o linie mai groasă la fiecare a cincea linie Culoare de umplere Instrumente ascunse Instrumente ascunse pentru distribuire Biblioteca de culori Răsfoiți o colecție vastă de culori Clarifică și elimină neclaritatea din imagini, păstrând în același timp detaliile naturale, ideal pentru remedierea fotografiilor nefocalizate. Restaurează inteligent imaginile care au fost redimensionate anterior, recuperând detaliile și texturile pierdute. Optimizat pentru conținut live-action, reduce artefactele de compresie și îmbunătățește detaliile fine în cadrele filmului/emisiunii TV. Convertește filmările de calitate VHS în HD, eliminând zgomotul benzii și îmbunătățind rezoluția, păstrând în același timp aspectul vintage. Specializat pentru imagini și capturi de ecran cu text intens, clarifică caracterele și îmbunătățește lizibilitatea. Upscaling avansat instruit pe diverse seturi de date, excelent pentru îmbunătățirea fotografiilor de uz general. Optimizat pentru fotografii comprimate pe web, elimină artefactele JPEG și restabilește aspectul natural. Versiune îmbunătățită pentru fotografiile web, cu o mai bună conservare a texturii și reducerea artefactelor. 2x upscaling cu tehnologia Dual Aggregation Transformer, menține claritatea și detaliile naturale. Upscaling de 3x folosind arhitectura avansată a transformatorului, ideală pentru nevoi moderate de extindere. Upscaling de înaltă calitate de 4x cu o rețea de transformatoare de ultimă generație, păstrează detaliile fine la o scară mai mare. Îndepărtează neclaritatea/zgomotul și tremurăturile din fotografii. Scop general, dar cel mai bine pentru fotografii. Restaurează imagini de calitate scăzută folosind transformatorul Swin2SR, optimizat pentru degradarea BSRGAN. Excelent pentru repararea artefactelor de compresie grele și îmbunătățirea detaliilor la scară de 4x. Upscaling de 4x cu transformator SwinIR instruit pe degradarea BSRGAN. Utilizează GAN pentru texturi mai clare și detalii mai naturale în fotografii și scene complexe. Cale Îmbinați PDF Combinați mai multe fișiere PDF într-un singur document Ordinea fișierelor pp. Divizarea PDF-ului Extrage anumite pagini din documentul PDF Rotiți PDF Remediați permanent orientarea paginii Pagini Rearanjați PDF Trageți și plasați paginile pentru a le reordona Țineți apăsat și trageți paginile Numerele paginilor Adăugați automat numerotarea documentelor dvs Format etichetă PDF în text (OCR) Extrageți text simplu din documentele dumneavoastră PDF Suprapuneți text personalizat pentru branding sau securitate Semnătura Adaugă semnătura ta electronică la orice document Acesta va fi folosit ca semnătură Deblocați PDF Eliminați parolele din fișierele dvs. protejate Protejați PDF Asigurați-vă documentele cu criptare puternică Succes PDF deblocat, îl puteți salva sau partaja Repara PDF Încercați să remediați documentele corupte sau ilizibile Tonuri de gri Convertiți toate imaginile încorporate în document în tonuri de gri Comprimați PDF Optimizați dimensiunea fișierului documentului pentru o partajare mai ușoară ImageToolbox reconstruiește tabelul intern de referințe încrucișate și regenerează structura fișierului de la zero. Acest lucru poate restabili accesul la multe fișiere care „nu pot fi deschise” Acest instrument convertește toate imaginile documentului în tonuri de gri. Cel mai bun pentru tipărirea și reducerea dimensiunii fișierului Metadate Editați proprietățile documentului pentru o confidențialitate mai bună Etichete Producător Autor Cuvinte cheie Creator Confidențialitate Deep Clean Ștergeți toate metadatele disponibile pentru acest document Pagină OCR profund Extrageți textul din document și stocați-l într-un singur fișier text folosind motorul Tesseract Nu se pot elimina toate paginile Eliminați paginile PDF Eliminați anumite pagini din documentul PDF Atinge Pentru a elimina Manual Decupați PDF Decupați paginile documentului la orice limite Aplatizați PDF Faceți PDF nemodificabil prin rasterizarea paginilor documentului Camera nu a putut porni. Verificați permisiunile și asigurați-vă că nu este folosit de altă aplicație. Extrage imagini Extrageți imagini încorporate în PDF-uri la rezoluția lor originală Acest fișier PDF nu conține nicio imagine încorporată Acest instrument scanează fiecare pagină și recuperează imagini sursă de calitate completă - perfect pentru salvarea originalelor din documente Desenați Semnătura Pen Params Utilizați propria semnătură ca imagine pentru a fi plasată pe documente Zip PDF Împărțiți documentul cu un interval dat și împachetați documente noi în arhiva zip Interval Imprimați PDF Pregătiți documentul pentru imprimare cu dimensiunea paginii personalizată Pagini pe foaie Orientare Dimensiunea paginii Marja Floare Genunchi moale Optimizat pentru anime și desene animate. Upscaling rapid cu culori naturale îmbunătățite și mai puține artefacte Samsung One UI 7 ca stil Introduceți aici simbolurile matematice de bază pentru a calcula valoarea dorită (de ex. (5+5)*10) Expresia matematică Preluați până la %1$s imagini Păstrați data și ora Păstrați întotdeauna etichetele exif legate de dată și oră, funcționează independent de opțiunea de păstrare exif Culoare de fundal pentru formatele Alpha Adaugă capacitatea de a seta culoarea de fundal pentru fiecare format de imagine cu suport alfa, când este dezactivat, acesta este disponibil numai pentru cele non alfa Proiect deschis Continuați editarea unui proiect Image Toolbox salvat anterior Imposibil de deschis proiectul Image Toolbox Din proiectul Image Toolbox lipsesc date de proiect Proiectul Image Toolbox este corupt Versiunea proiectului Image Toolbox neacceptată: %1$d Salvați proiectul Stocați straturi, fundal și editează istoricul într-un fișier de proiect editabil Deschiderea eșuată Scrieți într-un PDF care poate fi căutat Recunoașteți textul din lotul de imagini și salvați PDF care poate fi căutat cu imagine și strat de text selectabil Stratul alfa Flip orizontal Flip vertical Blocare Adaugă Shadow Culoare umbră Geometria textului Întindeți sau înclinați textul pentru o stilizare mai clară Scara X Înclinați X Eliminați adnotările Eliminați tipurile de adnotări selectate, cum ar fi linkuri, comentarii, evidențieri, forme sau câmpuri de formular din paginile PDF Hiperlinkuri Fișiere atașate Linii Ferestre pop-up Timbre Forme Note de text Marcare text Câmpuri de formular Markup Necunoscut Adnotări Degrupați Adăugați umbră încețoșată în spatele stratului cu culori și decalaje configurabile ================================================ FILE: core/resources/src/main/res/values-ru/strings.xml ================================================ Что-то пошло не так: %1$s Размер %1$s Загрузка… Изображение слишком большое для предпросмотра, но мы постараемся сохранить его в любом случае Выберите изображение, чтобы начать Ширина %1$s Высота %1$s Качество Расширение Тип изменения Строгий Гибкий Выбрать изображение Вы действительно хотите закрыть приложение? Закрытие приложения Остаться Закрыть Сбросить изображение Все изменения будут отменены, а вы увидите исходное изображение Значения успешно сброшены Сброс Что-то пошло не так Перезапустить приложение Скопировано в буфер обмена Исключение Ред. EXIF Ок EXIF информация не найдена Добавить тег Сохранить Очистить Очистить EXIF Отмена Все данные EXIF будут удалены, это действие нельзя отменить! Предустановки Обрезать Сохранение Все несохраненные изменения будут отменены, если вы выйдете сейчас Иcходный код Получайте последние обновления и следите за проектом Одиночное изменение Изменение характеристик одного изображения Выбрать цвет Воспользуйтесь пипеткой и получите цветовой код необходимого фрагмента изображения Изображение Цвет Цвет скопирован Обрежьте изображение до произвольных границ Версия Сохранить EXIF Изображения: %d Изменить превью Убрать Создать образец цветовой палитры по данному изображению Сгенерировать палитру Палитра Обновление %1$s Обновить Неподдерживаемый тип: %1$s Для данного изображения не удалось сгенерировать палитру Оригинал Путь сохранения По умолчанию Произвольный Не задан Память устройства Сжатие по весу Максимальный вес в КБ Сожмите изображение, чтобы оно занимало не более заданного количества КБ Сравнить Сравните два заданных изображения Выберите два изображения, чтобы начать Выбрать изображения Настройки Ночной режим Тёмный Светлый Как в системе Динамические цвета Кастомизация Включить Monet Если включён, то цвета приложения будут подстраиваться под выбранное изображение в режиме редактирования Язык Amoled режим Если включено, то цвет поверхностей будет установлен на абсолютно тёмный в ночном режиме Цветовая палитра Красный Зеленый Синий Вставьте правильный aRGB-код Нечего вставлять Невозможно изменить цвет приложения, если включены динамические цвета Тема приложения будет основана на выбранном цвете О приложении Обновлений не найдено Трекер ошибок Отправляйте отчёты об ошибках и предлагайте новые идеи для развития проекта Помочь с переводом Исправляйте ошибки в написании слов или переводите проект на новые языки Ничего не найдено по вашему запросу Ищите здесь При включении тема приложения будет подстраиваться под ваши обои Не удалось сохранить %d изображение(я) Первичный Третичный Вторичный Толщина окантовки Поверхность Значения Добавить Разрешение Одобрить Приложению необходим доступ к памяти вашего телефона, чтобы сохранять изображения, пожалуйста, для продолжения работы разрешите доступ в появшемся окне Приложению необходимо это разрешение для работы, предоставьте его вручную Внешний накопитель Динамические цвета Это приложение полностью бесплатное, однако если вы хотите поддержать его разработку, вы можете нажать здесь Расположение кнопки Проверка обновлений Если включено, при запуске вы увидите диалог, если будет доступна новая версия приложения Приближение Поделиться Префикс Название файла Эмодзи Выберите, какие эмодзи отображать на главном экране Добавить размер файла Если включено, то при сохранении к названию файла будут добавлены ширина и высота изображения Удаление EXIF Удалить метаданные EXIF из любого набора изображений Предпросмотр Предварительный просмотр изображений любого типа: GIF, SVG и так далее Источник изображений Фото пикер Галерея Проводник Современный пикер фотографий Android, который появляется в нижней части экрана, может работать только на Android 12+. Имеет проблемы с получением метаданных EXIF Простой пикер изображений из галереи, будет работать только если такое приложение установлено Использует метод GetContent для получения изображений. Может не работать. Это не моя вина Расположение опций Изменить Порядок Определяет порядок инструментов на главном экране Количество эмодзи sequenceNum originalFilename Добавить оригинальное имя файла Если включено, добавляет имя исходного файла в имя выходного изображения Добавить порядковый номер Если включено, заменяет стандартную метку времени на порядковый номер изображения, если используется пакетная обработка Добавление оригинального имени файла не работает, если выбран источник изображения photopicker Нет изображения Загрузить из интернета Загрузите любое изображение из Интернета, чтобы просмотреть, масштабировать, отредактировать и сохранить его, если хотите Ссылка на изображение Заполнить Вписать Масштаб контента Принудительно превращает каждую картинку в изображение, заданное параметрами ширины и высоты - может изменить соотношение сторон Изменяет размер картинок до изображений с длинной стороной, заданной параметром ширины - сохраняет соотношения сторон Яркость Контраст Оттенок Фильтр Резкость Сепия Соляризация Ч/Б Размытие Полутон Гамма Насыщенность Тени Свет Баланс белого Температура Светлые Добавить фильтр Примените любую цепочку фильтров к заданным изображениям Экспозиция Оттенок Фильтры Цветофильтр Альфа Монохром Блики и тени Туман Расстояние Эффект Наклон Интервал Негатив Вибранс Штриховка Ширина линии Кромка Собеля Двухстороннее размытие Лапласа Цветовое пространство CGA Эмбосс Виньетка Размытие по Гауссу Размытие прямоугольника Начало Конец Размытие Стэком Увеличение Радиус Искажение Водоворот Сглаживание Кувахары Угол Дилация Показатель преломления Цветовая матрица Выпуклость Сферическое преломление Преломление стеклянной сферы Непрозрачность Измените размер выбранных изображений, чтобы они соответствовали заданным ограничениям по ширине и высоте, сохраняя при этом соотношение сторон Ограничение размера Количественный уровень Постеризация Эскиз (Рисунок) Гладкий мультяшный Порог Мультяшный Немаксимальное подавление Поиск Слабое включение пикселей Размытие приближением Цветовой баланс Порог свечения Конволюция 3x3 Быстрое размытие Ложный цвет Центр размытия x Центр размытия y RGB-фильтр Первый цвет Второй цвет Порядок Размер размытия Вы отключили приложение «Файлы». Активируйте его, чтобы использовать эту функцию Рисование Рисуйте на изображении, как в альбоме или на выбранном фоне Цвет кисти Прозрачность кисти Рисовать на изображении Выберите изображение и нарисуйте на нем что-нибудь Рисовать на фоне Выберите цвет фона и рисуйте поверх него Фоновый цвет Выбрать файл Расшифровка Шифровка Ключ Совместимость Шифр Зашифруйте и расшифруйте любой файл (не только изображение) на основе различных криптоалгоритмов Зашифровать Расшифровать Выберите файл, чтобы начать Файл готов Функции Реализация Шифрование файлов на основе пароля. Полученные файлы могут храниться в выбранном каталоге. Расшифрованные файлы также могут быть открыты напрямую AES-256, режим GCM, без заполнения, 12 байт случайных IV. (По умолчанию, но вы можете выбрать необходимый алгоритм) Ключи используются в виде хэшей SHA-3 (256 бит) Размер файла Максимальный размер файла ограничен ОС Android и доступной памятью, которая зависит от устройства. \nОбратите внимание: память - не внутреннее хранилище Обратите внимание, что совместимость с другим программным обеспечением или службами для шифрования файлов не гарантируется. Немного другая обработка ключа или конфигурация шифра могут быть причинами несовместимости Сохраните этот файл на своем устройстве или используйте действие «Поделиться», чтобы поместить его куда угодно Найдено %1$s Неверный пароль или выбранный файл не зашифрован Инструменты Сгруппировать опции по типу Группирует параметры на главном экране в соответствии с их типом вместо произвольного расположения списка Невозможно изменить расположение, пока включена группировка параметров Попытка сохранить изображение с заданной шириной и высотой может вызвать ошибку OOM, делайте это на свой страх и риск Автоматическая очистка кэша Кэш Размер кэша Если включено, кэш приложения будет очищаться при запуске приложения Создать Редактировать скриншот Вторичная кастомизация Скриншот Копировать Запасной вариант Пропуск Сохранение в режиме %1$s может быть нестабильным, потому что это формат без потерь Обсуждайте приложение и получайте отзывы от других пользователей. Здесь вы также можете получить бета-обновления и дополнительную информацию Если вы выбрали предустановку 125, изображение будет сохранено в размере 125% от исходного изображения. Если вы выберете предустановку 50, изображение будет сохранено с размером 50% Предустановка здесь определяет % выходного файла, т.е. если вы выберете предустановку 50 на изображении размером 5 МБ, то после сохранения вы получите изображение размером 2,5 МБ Случайное имя файла Если включено, имя выходного файла будет полностью случайным Сохранено в папку %1$s под именем %2$s Сохранено в папку %1$s Чат в телеграме Соотношение сторон Бэкап и восстановление Бэкап Сохраните ваши настройки в файл Восстановите настройки приложения из ранее сохраненного файла бэкапа Настройки успешно восстановлены Маска обрезки Используйте эту опцию, для создания маски из выбранного изображения, обратите внимание, что оно ДОЛЖНО иметь канал прозрачности Восстановление Повреждённый файл или не является файлом восстановления Связаться со мной Это вернёт ваши настройки к значениям по умолчанию. Обратите внимание, что это действие невозможно отменить без упомянутого выше файла резервной копии Удалить Вы хотите удалить выбранную цветовую схему. Это действие невозможно отменить Удалить схему Шрифт По умолчанию Текст Масштаб шрифта Использование шрифтов большого размера может привести к проблемам с интерфейсом Аа Бб Вв Гг Дд Её Жж Зз Ии́ Кк Лл Мм Нн Оо Пп Рр Сс Тт Уу Фф Хх Цц Чч Шщ Ъь Ыы Ээ Юю Яя 0123456789 !? Эмоции Объекты Символы Путешествия и места Удалите фон изображения, стерев лишние участки или используя опцию «Авто» Метаданные исходного изображения будут сохранены Прозрачное пространство вокруг изображения будет обрезано Еда и напитки Природа и животные Включить эмодзи Активности Обрезать изображение Удаление фона Восстановить изображение Режим стирания Стирание фона Восстановление фона Радиус размытия Автоматическое удаление фона Пипетка Режим рисования Создать задачу Ой… Что-то пошло не так. Напишите мне, используя приведённые ниже варианты, и я постараюсь найти решение Измените размер заданных изображений или конвертируйте их в другие форматы. Метаданные EXIF также можно редактировать здесь, если вы выбираете одно изображение Изменение размера и конвертация Максимальное количество цветов Аналитика Это позволяет приложению собирать отчеты об ошибках автоматически Разрешите собирать анонимную статистику использования приложения В настоящее время формат %1$s на Android позволяет только читать метаданные EXIF. При сохранении выходное изображение вообще не будет иметь метаданных Обновления Подождите Значение %1$s означает быстрое сжатие, приводящее к относительно большому размеру файла. %2$s означает более медленное сжатие, в результате чего файл становится меньше Разрешить обновления до бета-версий Сохранение почти завершено. Отмена сейчас потребует повторного сохранения Усилие Проверка обновлений будет включать бета-версии приложения, если она включена Мягкость кисти Рисовать стрелки Если включено, то на конце линии будет дорисовываться стрелка Изображения будут обрезаны по центру до введенного размера. Холст будет расширен с заданным цветом фона, если изображение меньше введенных размеров Пожертвование Объединение изображений Объединить входные изображения в одно большое Горизонтальная Порядок изображений Выберите минимум 2 изображения Маленькие изображения будут масштабироваться до самого большого в последовательности, если эта опция включена Ориентация изображения Вертикальная Масштаб выходного изображения Масштабируйте маленькие изображения до больших Размытие краёв Пикселизация Рисует размытые края под исходным изображением, чтобы заполнить пространство вокруг него, а не одним цветом, если включено Обычный Рекодирование Допуск Красивая пикселизация Заменяющий цвет Заменить цвет Алмазная пикселизация Удаление цвета Улучшенная алмазная пикселизация Круглая пикселизация Цвет для удаления Заменяемый цвет Строчная пикселизация Улучшенная круглая пикселизация Размер пикселя Блокировка ориентации рисования Если включено в режиме рисования, экран не будет вращаться Точность Радуга Схема, которая помещает исходный цвет в Scheme.primaryContainer Фруктовый салат Схема, которая очень похожа на схему содержимого Содержание Игривая тема: оттенок исходного цвета не отображается в теме Яркая тема, красочность максимальна для Основной палитры и повышена для остальных Стиль немного более хроматический, чем монохромный Тональное пятно Монохромная тема, цвета чисто чёрный/белый/серый Проверить наличие обновлений Выразительный Нейтральный Стиль палитры по умолчанию, позволяет настроить все четыре цвета, другие позволяют установить только ключевой цвет Стиль палитры Яркий Обе Исчезающие края Проверка обновлений будет подключаться к GitHub для проверки доступности новых версий Откл Внимание Заменяет цвета темы на негативные, если включено Инвертировать Цвета Поиск Включает возможность поиска по всем доступным инструментам на главном экране PDF инструменты Изображения в PDF Предпросмотр PDF PDF в изображения Упакуйте набор изображений в PDF файл Простой просмотрщик PDF Совершайте операции с PDF файлами: просмотрите, конвертируйте в набор изображений или создайте новый файл Конвертируйте PDF в изображения в заданном формате Маска-фильтр Применяйте цепочки фильтров к заданным замаскированным областям, каждая область маски может определять свой собственный набор фильтров Маски Добавить маску Маска %d Обратное заполнение Предпросмотр Маски Если включено, то все фильтры будут наложены на все незамаскированные области Цвет маски Нарисованная маска будет отрисована чтобы вы смогли увидеть примерный результат Удаление Маски Вы собираетесь удалить выбранную маску фильтрации. Эта операция не может быть отменена Примените любые цепочки фильтров к заданным изображениям или одному изображению Начало Полный фильтр Конец Центр Добавьте эффект свечения к вашему рисунку Неон Включает тени под контейнерами Ручка Размывает изображение под вашим пальцем чтобы защитить все что вы хотите спрятать Изначальный режим, самый простой - только цвет Слайдеры Выделите что либо полупрозрачной заливкой Выделитель Простые Варианты Такой же режим как и приватное размытие, но использует пикселизацию Приватное Размытие Контейнеры Автоповорот Кнопки Левитирующие кнопки Включает тени под левитирующими кнопками Переключатели Позволяет ограничивающим размерам адаптироваться под ориентацию изображения Включает тени под слайдерами Включить отрисовку теней под панелью действий Включает тени под кнопками Панели действий Включает тени под переключателями Значение в диапазоне %1$s - %2$s Двойная стрелка - линия Рисует двойную указательную стрелку от начальной точки до конечной точки в виде линии Рисует указывающую стрелку на заданном пути Свободное рисование Очерченный овал Линия Овал Прямоугольник Рисует очерченный прямоугольник от начальной точки до конечной Лассо Стрелка - линия Рисует путь от начальной точки до конечной в виде линии Рисует очерченный овал от начальной точки до конечной Рисует замкнутый заполненный контур по заданному пути Двойная стрелка Стрелка Рисует путь как входное значение Рисует прямоугольник от начальной точки до конечной Рисует указывающую стрелку от начальной точки до конечной точки в виде линии Рисует двойную стрелку, указывающую на заданный путь Очерченный прямоугольник Рисует овал от начальной точки до конечной Режим рисования пути Свободно Количество строк Режим строчки Горизонтальная сетка Вертикальная сетка Количество столбцов Вибрация Для того, чтобы перезаписать файлы, вам нужно использовать источник изображений \"Проводник\", попробуйте переустановить изображения, мы изменили источник изображений на нужный Автоматически добавляет сохранённое изображение в буфер обмена, если оно включено Каталог \"%1$s\" не найден, мы переключили его на каталог по умолчанию, пожалуйста, сохраните файл еще раз Буфер обмена Исходный файл будет заменен новым вместо сохранения в выбранной папке, для этой опции необходимо, чтобы источником изображения был \"Проводник\" или GetContent, при переключении этого параметра он будет установлен автоматически Сила вибрации Перезаписать файлы Пустой Суффикс Авто закрепление Катмулл Бикубический Гермит Сплайн Режим масштабирования Билинейный Ханн Ланцош Митчелл Ближайший Базовый Значение по умолчанию Линейная (или билинейная, в двух измерениях) интерполяция обычно подходит для изменения размера изображения, но вызывает нежелательное размытие деталей и все еще может быть немного зазубренной Лучшие методы масштабирования включают метод Ланцоша и фильтры Митчелла-Нетравали Один из более простых способов увеличения размера, заменяющий каждый пиксель определенным количеством пикселей того же цвета Простейший режим масштабирования Android, используемый практически во всех приложениях Метод для плавной интерполяции и пересэмплирования набора контрольных точек, широко используемый в компьютерной графике для создания плавных кривых Оконная функция, часто применяемая в обработке сигналов для минимизации утечки спектра и улучшения точности анализа частот путем заострения краев сигнала Математический метод интерполяции, использующий значения и производные на конечных точках сегмента кривой для создания плавной и непрерывной кривой Метод пересэмплирования, поддерживающий высококачественную интерполяцию с использованием взвешенной функции sinc для значений пикселей Метод пересэмплирования, использующий свертку с настраиваемыми параметрами для достижения баланса между четкостью и сглаживанием в измененном изображении Использует кусочно-заданные полиномиальные функции для плавной интерполяции и приближения кривой или поверхности, обеспечивая гибкое и непрерывное представление формы Сохранение в хранилище не будет выполнено, и изображение при возможности будет помещено в буфер обмена Только закрепление Распознавание текста Точность: %1$s Быстро Нет данных Загрузить Нет соединения с интернетом, проверьте подключение и попробуйте снова чтобы загрузить модель Загруженные языки Доступные языки Распознайте текст с заданного изображения, поддерживается более 120 языков Для правильной работы Tesseract OCR на ваше устройство необходимо загрузить дополнительные обучающие данные (%1$s). \n Вы хотите загрузить данные %2$s? На изображении нет текста, или приложение не смогло его определить Кисть будет восстанавливать стертое изображение вместо стирания Тип распознавания Стандарт Лучший Режим сегментации Использовать переключатель Pixel Будет использоваться переключатель, как на телефонах Pixel, вместо стандартного из Material You Перезаписан файл с именем %1$s в оригинальной папке Включает лупу над пальцем во время рисования для лучшего понимания происходящего EXIF переключатель будет в положении ВКЛ автоматически Разрешить несколько языков Лупа Начальное значение Бок о бок Прозрачность Оценить Оцените приложение Это приложение полностью бесплатно. Если вы хотите, чтобы оно стало масштабнее, поставьте звёздочку проекту на Github 😄 Слайд Нажатие Только автоматически Автоматически Одиночный столбец Одно слово Круговое слово Разреженный текст, обнаружение ориентации и сценария Необработанная линия Только обнаружение ориентации и сценария Автоматическое обнаружение ориентации и сценария Вертикальный текст в виде одного блока Одиночный символ Одиночный блок Разреженный текст Одиночная линия Все Развертка Тип градиента Центр X Центр У Режим плитки Зеркало Зажим Наклейка Ключевые точки Добавить цвет Параметры Вы хотите удалить данные обучения OCR языка \"%1$s\" для всех типов распознавания или только для выбранного (%2$s)? Текущий Повтор Создание градиентов Создайте градиент заданного размера с настраиваемыми цветами и типом внешнего вида Линейный Радиальный Максимальная яркость Экран Трансформации Наложение градиента Наложите любой градиент на любое изображение Камера Использует камеру для получения изображения, обратите внимание, что вы можете получить только одну фотографию, используя этот источник изображений Повтор водяного знака Смещение Х Это изображение будет использоваться в качестве шаблона для водяного знака Водяной знак Накладывает водяной знак на все изображение вместо заданной точки Наложите водяные знаки на изображения в виде текста или других изображений Смещение У Тип водяного знака Цвет текста Режим наложения Конвертируйте изображения в GIF-картинку или извлекайте кадры из заданного GIF-изображения Изображения в GIF Выберите GIF-изображение для начала Используйте размер первого кадра Количество повторений Задержка кадра мсек FPS GIF инструменты Преобразуйте GIF-файл в пакет изображений Преобразование пакета изображений в GIF-файл Замените указанный размер размерами первого кадра GIF в изображения Прозрачность предпросмотра исходного изображения Использовать Лассо Использует для стирания Лассо, как в режиме рисования Приватный режим Скрывает контент при выходе, так же экран приложения не может быть захвачен или записан Конфетти Конфетти будет показано при сохранении и других важных действиях Выйти Если вы оставите предварительный просмотр сейчас, вам придется добавить изображения снова Дизеринг Квантизатор Серые тона Дизеринг Байера два на два Дизеринг Байера три на три Дизеринг Байера четыре на четыре Дизеринг Байера восемь на восемь Дизеринг Флойда Стейнберга Дизеринг Джарвиса Джуди Нинке Дизеринг Сиерры Двухрядный дизеринг Сиерры Упрощенный дизеринг Сиерры Дизеринг Аткинсона Дизеринг Стуки Дизеринг Бёркса Ложный дизеринг Флойда Стейнберга Дизеринг слева направо Случайный дизеринг Простой пороговый дизеринг Почта Медианное размытие Сигма Пространственная сигма Нативное Размытие Стэком Наклон-смещение Би Сплайн Использует кусочно-определенные функции бикубического полинома для плавной интерполяции и аппроксимации кривой или поверхности, гибкое и непрерывное представление формы Перемешивание Сбой Зерно Количество Анаглиф Шум Сортировка Пикселей Сдвиг канала по X Размытие тентом Боковое затухание Сторона Сдвиг сбоя по У Верх Улучшенный Сбой Сдвиг канала по У Сдвиг сбоя по Х Размер сбоя Низ Сила Анизотропная диффузия Горизонтальное колебание ветра Эрозия Диффузия Проводимость Кинематографичное отображение тонов ACES Амплитуда Х Холмистое отображение тонов ACES Отображение тонов Хейла Берджесса Быстрое двухстороннее размытие Пуассоновое размытие Логарифмическое отображение тонов Кристаллизация Цвет обводки Фрактальное стекло Турбулентность Амплитуда Мрамор Масло Эффект воды Размер Частота У Искажение Перлина Частота Х Амплитуда У Кинематографичное отображение тонов Хэйбла Удаление тумана Скорость Омега Цветовая матрица 4х4 Простые эффекты Тритономалия Дейтераномалия Брауни Холод Тританопия Дейтаронотопия Протанопия Ахроматомалия Цветовая матрица 3x3 Полароид Протаномалия Винтаж Ночное видение Кода Хром Ахроматопсия Тепло Пастель Розовая мечта Жаркое лето Пурпурная струя Рассвет Мягкая весна Лавандовые мечты Киберпанк Светлый лимонад Ночная Магия Цветной взрыв Электрический градиент Карамельная тьма Футуристичный градиент Зеленое Солнце Радужный мир Космический портал Цифровой код Зернистость Анти резкость Оранжевый туман Золотой час Цветной вихрь Осенние тона Спектральный огонь Фантастический пейзаж Глубокий фиолетовый Красный вихрь Боке Невозможно выбрать эмодзи, пока включен их случайный выбор Эмодзи будет постоянно меняться случайным образом, а не использовать выбранный Случайные эмодзи Невозможно использовать случайный выбор эмодзи, пока они отключены Старый телевизор Размытие перемешиванием Избранные фильтры пока не добавлены Избранное Формат Изображения Добавляет контейнер с выбранной фигурой под ведущие значки карточек Форма иконки Учимура Мобиус Вершина Олдридж Обрезка Переход Цветовая аномалия Драго Эмодзи в качестве цветовой схемы Изображения перезаписаны в исходном месте назначения Невозможно изменить формат изображения, если включена опция перезаписи файлов Использует основной цвет эмодзи в качестве цветовой схемы приложения вместо заданной вручную Темные цвета Скопировать в Jetpack Compose Создает Material You палитру из изображения Использует цветовую схему ночного режима вместо светлого варианта Перекрестное размытие Размытие звездами Линейный сдвиг наклона Размытие кольцами Размытие кругами Тэги для удаления APNG инструменты Выберите APNG, чтобы начать Конвертация изображений в файл APNG Размытие движением Преобразование изображений в APNG или извлечение кадров из APNG APNG в изображения Преобразование файла APNG в изображения Изображения в APNG Zip Создать Zip-файл из заданных файлов или изображений Ширина перетаскиваемой ручки Взрыв Дождь Тип конфетти Праздничный Углы JXL в JPEG Выберите изображение JXL чтобы начать JXL инструменты Выполняйте перекодирование JXL ~ JPEG без потери качества или конвертируйте GIF/APNG в анимацию JXL Выполните перекодирование без потерь из JXL в JPEG Выполните перекодирование без потерь из JPEG в JXL JPEG в JXL Быстрое размытие по Гауссу 2D Быстрое размытие по Гауссу 3D Быстрое размытие по Гауссу 4D Уровень гармонизации Авто-вставка Цвет гармонизации Разрешить приложению автоматически вставлять данные из буфера обмена, чтобы они появились на главном экране, и вы смогли их обработать Ланцош Бессель GIF в JXL Конвертируйте изображения GIF в анимированные изображения JXL APNG в JXL JXL в изображения Преобразование анимации JXL в пакет изображений Изображения в JXL Преобразование пакета изображений в анимацию JXL Поведение Создание превью Сжатие с потерями Использует сжатие с потерями для уменьшения размера файла вместо сжатия без потерь Метод повторной выборки, который поддерживает высококачественную интерполяцию за счет применения функции Бесселя (jinc) к значениям пикселей Конвертируйте изображения APNG в анимированные изображения JXL Пропустить выбор файла Средство выбора файла будет показано немедленно, если это возможно, на выбранном экране Включает создание предварительного просмотра, это может помочь избежать сбоев на некоторых устройствах, это также отключает некоторые функции редактирования в рамках одной опции редактирования Тип Сжатия Управляет скоростью декодирования результирующего изображения. Это должно помочь быстрее открыть полученное изображение. Значение %1$s означает самое медленное декодирование, тогда как %2$s — самое быстрое, этот параметр может увеличить размер выходного изображения Сортировать по дате Сортировать по имени Сортировка Сортировать по дате (наоборот) Сортировать по имени (наоборот) Попробовать снова Показать настройки в альбомной ориентации Тип переключателя Композ Множественный выбор медиа Выбор медиа Выбрать Использует переключатель на основе View, он выглядит лучше других и имеет приятную анимацию Использует переключатель из Jetpack Compose, он не так красив, как на основе View Пиксель Флюент Купертино Если это отключено, то в ландшафтном режиме настройки будут открываться по кнопке в верхней панели приложения, как всегда, вместо постоянной видимой опции Полноэкранные настройки Включите его, и страница настроек всегда будет открываться в полноэкранном режиме, а не в выдвижном ящике Максимум Привязка к размеру Использует переключатель в стиле Windows 11 на основе системы дизайна Fluent Использует iOS-подобный переключатель на основе системы дизайна Купертино Конфигурация каналов Сегодня Вчера Встроенное средство выбора Использует собственный инструмент выбора изображения Image Toolbox вместо предопределенных системой Нет разрешений Запросить Использование этого инструмента для отслеживания больших изображений без уменьшения масштаба не рекомендуется, это может привести к сбою и увеличению времени обработки Изображения в SVG Трассировка данных изображений в изображения SVG Использовать выборочную палитру Палитра квантования будет выбрана, если эта опция включена Пропуск пути Уменьшение изображения Толщина линии по умолчанию Режим двигателя Старый Сеть LSTM Старый & LSTM Сбросить свойства Перед обработкой изображение будет уменьшено до меньших размеров, это поможет инструменту работать быстрее и безопаснее Минимальное соотношение цветов Порог линий Квадратичный порог Допуск округления координат Масштаб пути Для всех свойств будут установлены значения по умолчанию. Обратите внимание, что это действие невозможно отменить Подробный Конвертировать Преобразование пакетов изображений в заданный формат Планарная конфигурация Y Cb Cr сабсемплинг Y Cb Cr Позиционирование X Разрешение Y Разрешение Еденица разрешения Обрезать офсеты Стрип байтов число Формат обмена JPEG Трансферная функция Белая точка Первичная цветность Модель Производитель Время Художник Примечание создателя Пользовательский комментарий Гамма Оригинальное время Время задержки Спектральная чувствительность Тип чувствительности ISO скорость Значение диафрагмы Значение яркости Вспышка Файловый источник Баланс белого Контраст Насыщение ISO Добавить новую папку Битов на семпл Компрессия Фотометрическая интерпретация Семплов на пиксель Линий на стрип Формат обмена JPEG длина Авторские права ПО Описание изображения Соответсвующий звуковой файл Максимальное значение диафрагмы Энергия вспышки Версия DNG Время оцифрования Фотографическая чувствительность Нарисовать текст по пути с заданным шрифтом и цветом Чувствительность стандартного вывода Размер шрифта Размер водного знака Имя владельца камеры Резкость Версия Exif Коэффициенты Y Cb Cr Ссылка Черный Белый Версия Flashpix Цветовое пространство Размер X в пикселях Размер Y в пикселях Сжатые биты на пиксель Время смещения Смещение исходного времени Время смещения в цифровом формате Время в секундах Оригинал, длительность менее секунды Оцифрованное время в секундах Номер F Программа воздействия OECF Рекомендуемый индекс воздействия Широта скорости ISO yyy Скорость ISO Latitude zzz Значение скорости затвора Значение смещения экспозиции Расстояние до объекта Режим измерения Тематическая область Фокусное расстояние Пространственная частотная характеристика Разрешение по X в фокальной плоскости Разрешение по Y в фокальной плоскости Единица разрешения фокальной плоскости Местоположение объекта Индекс воздействия Метод обнаружения Шаблон CFA Пользовательская визуализация Режим экспозиции Коэффициент цифрового масштабирования Фокусное расстояние на пленке 35 мм Тип захвата сцены Управление усилением Описание настройки устройства Диапазон расстояний до объекта Уникальный идентификатор изображения Серийный номер корпуса Спецификация объектива Производитель объектива Модель объектива Серийный номер объектива Идентификатор версии GPS Ссылка на широту GPS Широта GPS Ссылка на долготу GPS Долгота GPS Ссылка на высоту по GPS Высота по GPS Отметка времени GPS Спутники GPS Состояние GPS Режим измерения GPS GPS DOP Ссылка на скорость GPS Скорость GPS Ссылка на GPS-трек GPS-трек Ссылка на направление GPS-изображения Направление GPS-изображения Система координат GPS-карты Ссылка на широту места назначения по GPS Широта пункта назначения GPS Ссылка на долготу пункта назначения по GPS Долгота назначения по GPS Ссылка на конечный пеленг GPS Назначение GPS Ссылка на конечное расстояние по GPS Расстояние назначения GPS Метод обработки GPS Информация о районе GPS Отметка даты GPS Дифференциал GPS Ошибка позиционирования GPS H Индекс совместимости Размер обрезки по умолчанию Начало предварительного просмотра изображения Длина изображения для предварительного просмотра Кадр аспекта Нижняя граница датчика Левая граница датчика Правая граница датчика Верхняя граница датчика Повторять текст Текущий текст будет повторяться до конца пути вместо однократного рисования Размер штриха Использовать выбранное изображение, чтобы нарисовать его по заданному пути Это изображение будет использоваться в качестве повторяющейся записи нарисованного пути Рисует очерченный треугольник от начальной точки до конечной точки Рисует очерченный треугольник от начальной точки до конечной точки Очерченный треугольник Треугольник Рисует многоугольник от начальной точки до конечной точки Многоугольник Контурный многоугольник Рисует контурный многоугольник от начальной точки до конечной точки Вершины Рисовать правильный многоугольник Нарисуйте многоугольник правильной формы, а не произвольной формы Рисует звезду от начальной точки до конечной точки Звезда Обведенная звезда Рисует контурную звезду от начальной точки до конечной точки Отношение внутреннего радиуса Рисовать правильную звезду Нарисуйте звезду правильной формы, а не свободной Сглаживание Включает сглаживание для предотвращения острых краев Открывать редактирование вместо предварительного просмотра Когда вы выбираете изображение для открытия (предварительного просмотра) в ImageToolbox, вместо предварительного просмотра откроется лист редактирования выбора Сканер документов Сканируйте документы и создавайте из них PDF или отдельные изображения Нажмите, чтобы начать сканирование Начать сканирование Сохранить как PDF Поделиться в формате PDF Параметры ниже предназначены для сохранения изображений, а не PDF Выровнять HSV гистограмму Выровнять гистограмму Введите процент Разрешить ввод по текстовому полю Цветовое пространство увеличения Линейное Выравнивание гистограммы пикселизации Размер сетки X Размер сетки Y Адаптивное выравнивание гистограммы Адаптивное выравнивание гистограммы LUV Адаптивное выравнивание гистограммы LAB CLAHE LAB CLAHE LUV Обрезать по содержимому Цвет рамки Цвет для игнорирования Шаблон Нет добавленных шаблонных фильтров Создать новый Сканированный QR-код не является допустимым шаблоном фильтра Сканировать QR-код Выбранный файл не содержит данных шаблона фильтра Создать шаблон Имя шаблона Это изображение будет использоваться для предварительного просмотра этого шаблона фильтра Фильтр шаблона Как изображение QR-кода Как файл Сохранить как файл Сохранить как изображение QR-кода Удалить шаблон Вы собираетесь удалить выбранный фильтр шаблона. Эта операция не может быть отменена Добавлен фильтр шаблона с именем \"%1$s\" (%2$s) Предварительный просмотр фильтра QR и баркоды Сканируйте QR-код, чтобы получить его содержимое, или вставьте свою строку для генерации нового кода Содержимое кода Сканируйте любой баркод, чтобы заменить содержимое в поле, или введите что-нибудь для генерации нового баркода выбранного типа Описание QR Мин Разрешите доступ к камере в настройках для сканирования QR-кода Кубический Би-Сплайн Хэмминг Хэннинг Блэкман Уэлч Квадратический Гауссовский Сфинкс Бартлетт Робиду Робиду Шарп Сплайн 16 Сплайн 36 Сплайн 64 Кайзер Бартлетт-Ханн Блочный Боман Ланцош 2 Ланцош 3 Ланцош 4 Ланцош 2 Jinc Ланцош 3 Jinc Ланцош 4 Jinc Кубическая интерполяция обеспечивает более гладкое масштабирование, учитывая ближайшие 16 пикселей, давая лучшие результаты, чем билинейная Использует кусочно-определенные полиномиальные функции для плавной интерполяции и аппроксимации кривой или поверхности, гибкое и непрерывное представление формы Оконная функция, используемая для уменьшения спектральных утечек путем сужения краев сигнала, полезна в обработке сигналов Вариант окна Ханна, обычно используемый для уменьшения спектральных утечек в приложениях обработки сигналов Оконная функция, обеспечивающая хорошее частотное разрешение за счет минимизации спектральных утечек, часто используемая в обработке сигналов Оконная функция, разработанная для обеспечения хорошего частотного разрешения с уменьшенными спектральными утечками, часто используемая в приложениях обработки сигналов Метод, использующий квадратическую функцию для интерполяции, обеспечивая плавные и непрерывные результаты Метод интерполяции, применяющий гауссовскую функцию, полезный для сглаживания и уменьшения шума в изображениях Продвинутый метод пересэмплирования, обеспечивающий высококачественную интерполяцию с минимальными артефактами Треугольная оконная функция, используемая в обработке сигналов для уменьшения спектральных утечек Высококачественный метод интерполяции, оптимизированный для изменения размера естественных изображений, балансирующий резкость и гладкость Более резкий вариант метода Робиду, оптимизированный для четкого изменения размера изображений Метод интерполяции на основе сплайнов, обеспечивающий плавные результаты с использованием 16-кратного фильтра Метод интерполяции на основе сплайнов, обеспечивающий плавные результаты с использованием 36-кратного фильтра Метод интерполяции на основе сплайнов, обеспечивающий плавные результаты с использованием 64-кратного фильтра Метод интерполяции, использующий окно Кайзера, обеспечивающий хороший контроль над компромиссом между шириной главного лепестка и уровнем бокового лепестка Гибридная оконная функция, объединяющая окна Бартлетта и Ханна, используемая для уменьшения спектральных утечек в обработке сигналов Простой метод пересэмплирования, использующий среднее значение ближайших значений пикселей, часто приводящий к блочному виду Оконная функция, используемая для уменьшения спектральных утечек, обеспечивающая хорошее частотное разрешение в приложениях обработки сигналов Метод пересэмплирования, использующий 2-лепестковый фильтр Ланцоша для высококачественной интерполяции с минимальными артефактами Метод пересэмплирования, использующий 3-лепестковый фильтр Ланцоша для высококачественной интерполяции с минимальными артефактами Метод пересэмплирования, использующий 4-лепестковый фильтр Ланцоша для высококачественной интерполяции с минимальными артефактами Вариант фильтра Ланцош 2, использующий функцию джинка, обеспечивающий высококачественную интерполяцию с минимальными артефактами Вариант фильтра Ланцош 3, использующий функцию джинка, обеспечивающий высококачественную интерполяцию с минимальными артефактами Вариант фильтра Ланцош 4, использующий функцию джинка, обеспечивающий высококачественную интерполяцию с минимальными артефактами Включает текстовое поле за выбором пресетов, чтобы вводить их на лету Ханнинг EWA Вариант фильтра Ханнинг с эллиптическим взвешенным средним (EWA) для плавной интерполяции и ресемплинга Робиду EWA Вариант фильтра Робиду с эллиптическим взвешенным средним (EWA) для высококачественного ресемплинга Блэкман EWA Вариант фильтра Блэкман с эллиптическим взвешенным средним (EWA) для минимизации артефактов звона Квадратический EWA Вариант квадратического фильтра с эллиптическим взвешенным средним (EWA) для плавной интерполяции Робиду Шарп EWA Вариант фильтра Робиду Шарп с эллиптическим взвешенным средним (EWA) для более четких результатов Ланцош 3 Jinc EWA Вариант фильтра Ланцош 3 Jinc с эллиптическим взвешенным средним (EWA) для высококачественного ресемплинга с уменьшением алиасинга Женьшень Фильтр ресемплинга, разработанный для высококачественной обработки изображений с хорошим балансом резкости и плавности Женьшень EWA Вариант фильтра Женьшень с эллиптическим взвешенным средним (EWA) для улучшенного качества изображения Ланцош Резкий EWA Вариант фильтра Ланцош Резкий с эллиптическим взвешенным средним (EWA) для достижения резких результатов с минимальными артефактами Ланцош 4 Самый Резкий EWA Вариант фильтра Ланцош 4 Самый Резкий с эллиптическим взвешенным средним (EWA) для крайне резкого ресемплинга изображений Ланцош Мягкий EWA Вариант фильтра Ланцош Мягкий с эллиптическим взвешенным средним (EWA) для более плавного ресемплинга изображений Хаасн Мягкий Фильтр ресемплинга, разработанный Хаасн для плавного масштабирования изображений без артефактов Конвертация формата Не показывать Накладывайте изображения друг на друга с выбранными режимами смешивания Добавить изображение Конвертируйте набор изображений из одного формата в другой Наложение изображений Количество ячеек Клахе HSL Клахе HSV Адаптивное выравнивание гистограммы HSL Адаптивное выравнивание гистограммы HSV Режим краев Обрезка Обтекание Схема для дальтоников Выберите режим для адаптации цветов темы для данного варианта дальтонизма Трудности в различении красных и зеленых оттенков Трудности в различении зеленых и красных оттенков Трудности в различении синих и желтых оттенков Неспособность воспринимать красные оттенки Неспособность воспринимать зеленые оттенки Неспособность воспринимать синие оттенки Сниженная чувствительность ко всем цветам Полная цветовая слепота, восприятие только оттенков серого Лагранж 2 Интерполяционный фильтр Лагранжа порядка 2, подходящий для высококачественного масштабирования изображений с плавными переходами Лагранж 3 Интерполяционный фильтр Лагранжа порядка 3, обеспечивающий лучшую точность и более плавные результаты при масштабировании изображений Ланцош 6 Фильтр ресемплинга Ланцоша с более высоким порядком 6, обеспечивающий более резкое и точное масштабирование изображений Ланцош 6 Jinc Вариант фильтра Ланцоша 6, использующий функцию Jinc для улучшения качества ресемплинга изображений Не использовать схему дальтонизма Сигмоидальное Цвета будут такими же как в теме Линейное размытие прямоугольником Линейное размытие тентом Линейное размытие прямоугольником по Гауссу Линейное размытие стеком Размытие прямоугольником по Гауссу Следующее быстрое линейное размытие по Гауссу Линейное быстрое размытие по Гауссу Выберите один фильтр, чтобы использовать его в качестве краски Линейное размытие по Гауссу Заменить фильтр Выберите фильтр ниже, чтобы использовать его в качестве кисти в своем наброске Схема компрессии TIFF Низкая полигональность Рисование песком Разделение изображения Разделите изображение по строкам или столбцам Центр Верхний левый Верхний правый Нижний левый Нижний правый Верхний центр Правый центр Нижний центр Левый центр Импорт Вписать в границы Объедините режим изменения размера обрезки с этим параметром для достижения желаемого поведения (обрезка/подгонка по соотношению сторон) Языки успешно импортированы Резервное копирование моделей OCR Экспорт Расположение Целевое изображение Перенос палитры Улучшенное масло Простой старый телевизор HDR Готэм Простой эскиз Мягкое свечение Цветной постер Три тона Третий цвет Clahe Oklab Clahe Oklch Clahe Jzazbz В горошек Кластерное 2x2 дизерирование Кластерное 4x4 дизерирование Кластерное 8x8 дизерирование Дизерирование Yililoma Не выбрано ни одной из любимых опций, добавьте их на странице инструментов Добавить в избранное Комплементарная Аналоговая Триадная Разделенная комплементарная Тетрадная Квадратная Аналоговая + комплементарная Инструменты цвета Смешивание, создание оттенков и многое другое Цветовые гармонии Тонирование цвета Вариация Оттенки Тоны Тени Смешивание цветов Информация о цвете Выбранный цвет Цвет для смешивания Невозможно использовать monet при включенных динамических цветах 512x512 2D LUT Целевое изображение LUT Amatorka Miss Etikate Мягкая элегантность Вариант мягкой элегантности Вариант переноса палитры 3D LUT Целевой 3D LUT файл (.cube / .CUBE) LUT Bleach Bypass Свет свечи Понижение синих Острый янтарь Осенние цвета Пленочный материал 50 Туманная ночь Kodak Получить нейтральное изображение LUT Сначала используйте ваше любимое приложение для редактирования фото, чтобы применить фильтр к нейтральному LUT, который вы можете получить здесь. Чтобы это работало правильно, цвет каждого пикселя не должен зависеть от других пикселей (например, размытие не сработает). Как только будете готовы, используйте ваше новое изображение LUT в качестве ввода для 512*512 фильтра LUT Поп-арт Целлулоид Кофе Золотой лес Зеленоватый Ретро-желтый Нет полного доступа к файлам Включите временные метки, чтобы выбрать их формат Предоставьте доступ к камере в настройках для сканирования сканера документов Ссылки Позволяет предварительно просматривать ссылки в местах, где можно получить текст (QR-код, OCR и т. д.) Файлы Ico можно сохранять только с максимальным размером 256*256, большие значения будут принудительно уменьшены в конечном изображении WEBP в изображения Конвертировать файл WEBP в пакет изображений Конвертировать пакет изображений в файл WEBP Изображения в WEBP Выберите изображение WEBP, чтобы начать Цвет рисования по умолчанию Разрешите доступ ко всем файлам для просмотра JXL, QOI и других изображений, которые не распознаются системой Android как файлы изображений. Без предоставления разрешения вы не сможете увидеть эти изображения здесь Режим рисования пути по умолчанию Включает добавление временной метки к имени выходного файла Добавить временную метку Форматированная временная метка Включить форматирование временной метки в имени выходного файла вместо базовых миллисекунд Предпросмотр ссылок GIF в WEBP Конвертируйте GIF-изображения в анимированные WEBP-изображения WEBP Инструменты Конвертируйте изображения в анимированные изображения WEBP или извлекайте кадры из заданной анимации WEBP Однократное сохранение местоположения Недавно использовано Просмотр и редактирование мест одноразового сохранения, которые можно использовать с помощью длительного нажатия кнопки сохранения практически во всех вариантах CI-канал Группа ImageToolbox в Telegram 🎉 Присоединяйтесь к нашему чату, где вы можете обсудить все, что вы хотите, а также можете зайти в канал CI, где я публикую бета-версии и объявления Получайте уведомления о новых версиях приложения и читайте объявления Пользовательские параметры Параметры следует вводить по следующему шаблону: \"--{название_опции} {значение}\" Автоматическая обрезка Свободные углы Приведение точек к границам изображения Восстанавливающая кисть Использовать ядро круга Открытие Закрытие Морфологический градиент Цилиндр Черная шляпа Вписывает изображение в заданный размер и добавляет блюр/цвет на задний фон Расположение инструментов Группировать инструменты по типу Группирует инструменты на главном экране на основе их типа вместо пользовательского расположения Значения по умолчанию Видимость системных панелей Показывать системные панели по свайпу Включает возможность показать скрытые системные панели по свайпу Авто Скрыть все Показать все Скрыть панель навигации Скрыть панель уведомлений Генерация шума Создание различных шумов, таких как Perlin или других типов Частота Тип шума Тип вращения Тип фрактала Октавы Лакунарность Прирост Взвешенная сила Сила пинг-понга Тип возврата Джиттер Деформация домена Выравнивание Сохранено в папке с пользовательским именем Тоновые кривые Сбросить кривые Кривые будут возвращены к значениям по умолчанию Стиль линии Размер зазора Пунктир Точка-пунктир Рисует точка-пунктирную линию по заданному пути Просто обычные прямые линии Рисует выбранные фигуры вдоль контура с указанным интервалом Рисует волнистый зигзаг вдоль пути Отношение зигзага Создать ярлык Выберите инструмент для закрепления Инструмент будет добавлен на главный экран вашего лаунчера в качестве ярлыка. Используйте его в сочетании с настройкой «Пропустить выбор файла» для достижения необходимого поведения Не складывайте кадры друг на друга Позволяет удалять предыдущие кадры, чтобы они не накладывались друг на друга Кадры будут плавно переходить друг в друга Плавный переход Количество кадров перехода Создание коллажей Тип коллажа Первый порог Второй порог Кэнни Зеркало 101 Простой Собель Вспомогательная сетка Цвет сетки Ширина ячейки Высота ячейки Компактные селекторы Предоставьте камере разрешение в настройках на захват изображения Макет Заголовок главного экрана Выберите местоположение и имя файла, которые будут использоваться для сохранения текущего изображения Функция дистанции Пользовательское имя файла Создавайте различные коллажи до 20 изображений Параметры Tesseract Применить некоторые входные переменные для Tesseract Обрезайте изображение по полигону, это также исправляет перспективу Точки не будут ограничены границами изображения, это полезно для более точной коррекции перспективы Маска Заливка с учетом содержимого под нарисованным контуром Зигзаг Штамповка Рисует пунктирную линию вдоль нарисованного пути с указанным размером зазора Улучшенное размытие приближением Некоторые элементы управления выбором будут использовать компактную компоновку, чтобы занимать меньше места Простой Лапласиан Отображает вспомогательную сетку над областью рисования, помогающую выполнять точные манипуляции Удерживайте изображение, чтобы поменять местами, перемещайте и масштабируйте для регулировки положения Гистограмма Гистограмма изображения RGB или яркости, которая поможет вам внести коррективы Это изображение будет использовано для создания гистограмм RGB и яркости Фактор постоянной скорости (CRF) Обновление коллекции LUT (в очередь будут поставлены только новые), которые можно применить после загрузки Предварительный просмотр изображения Material 2 Слайдер на основе Material 2, не современный, простой и понятный Современный слайдер Material You, футуристический, свежий, доступный Значение %1$s означает медленное сжатие, приводящее к относительно небольшому размеру файла. %2$s означает более быстрое сжатие, приводящее к большому размеру файла Библиотека LUT Загрузите коллекцию LUT, которую вы сможете применить после загрузки Изменить предварительный просмотр изображения по умолчанию для фильтров Тип слайдера Изысканный Пользовательский слайдер с красивым дизайном и анимацией, это слайдер по умолчанию для этого приложения Скрыть Показать Кнопки диалогов будут располагаться по центру, а не слева, если это возможно Лицензии с открытым исходным кодом Просмотр лицензий библиотек с открытым исходным кодом, используемых в этом приложении Область Повторная выборка с использованием отношения площади пикселя. Это может быть предпочтительным методом для прореживания изображений, поскольку он дает результаты без муара. Но когда изображение увеличено, это похоже на метод \"Nearest\" Включить Tonemapping Введите % Невозможно получить доступ к сайту, попробуйте использовать VPN или проверьте правильность URL Слои разметки Режим слоев с возможностью свободного размещения изображений, текста и многого другого Редактировать слой Слои на изображении Использовать изображение в качестве фона и добавлять различные слои поверх него Слои на фон То же, что и первый вариант, но с цветом вместо изображения Бета Сторона быстрых настроек Добавить плавающую полосу на выбранной стороне при редактировании изображений, которая откроет быстрые настройки при нажатии Очистить выделение Группа настроек \"%1$s\" будет свернута по умолчанию Группа настроек \"%1$s\" будет развернута по умолчанию Инструменты Base64 Декодировать Строка Base64 в изображение или кодирование изображения в формат Base64 Base64 Предоставленное значение не является допустимой строкой Base64 Невозможно скопировать пустую или недопустимую строку Base64 Вставить Base64 Копировать Base64 Загрузите изображение, чтобы скопировать или сохранить строку Base64. Если у вас есть сама строка, вы можете вставить ее выше, чтобы получить изображение Сохранить Base64 Поделиться Base64 Параметры Действия Импортировать Base64 Действия Base64 Добавить обводку Добавляет обводку вокруг текста с указанным цветом и шириной Цвет контура Размер контура Вращение Контрольная сумма как имя файла Выходные изображения будут иметь имя, соответствующее их контрольной сумме Центрирование диалоговых кнопок Применить Бесплатное ПО (Партнер) Больше полезного ПО в партнерском канале приложений Android Контрольная сумма Инструменты контрольной суммы Сравнивайте контрольные суммы, вычисляйте хеши, создавайте шестнадцатеричные строки из файлов, используя различные алгоритмы хеширования Рассчитать Алгоритм Текстовый хэш Введите текст для расчета его контрольной суммы на основе выбранного алгоритма Контрольная сумма для сравнения Исходная контрольная сумма Различие Контрольные суммы равны, это может быть безопасно Выберите файл для расчета его контрольной суммы на основе выбранного алгоритма Совпадение! Контрольные суммы не равны, файл может быть небезопасен! Пакетное сравнение Выберите файл/файлы для расчета его контрольной суммы на основе выбранного алгоритма Сеточные градиенты Создание градиентной сетки с заданным количеством узлов и разрешением Посмотрите Онлайн коллекцию сеточных градиентов Ошибка во время попытки сохранения, попробуйте изменить директорию сохранения в настройках Выберите файлы Выберите директорию Редактировать EXIF Изменить метаданные одного изображения без повторного сжатия Нажмите, чтобы редактировать доступные теги Изменить наклейку Коэффицент длины наконечника Штамп Временная метка Шаблон форматирования Отступ Импортировать шрифт (TTF/OTF) Экспорт шрифтов Только TTF шрифты могут быть импортированы Импортированные шрифты Имя файла не задано Вписать ширину Вписать высоту Ничего Пользовательские страницы Выбор страниц Подтверждение закрытия инструмента Если у вас есть несохраненные изменения при использовании определенных инструментов и вы пытаетесь закрыть их, то будет показано диалоговое окно подтверждения Вырезание CLAHE Вырезать часть изображения и объединить левые части (можно инвертировать) вертикальными или горизонтальными линиями Вертикальная линия поворота Горизонтальная линия поворота Инвертировать выделение Вертикальная часть будет оставлена, вместо объединения частей вокруг области вырезания Горизонтальная часть будет оставлена, вместо объединения частей вокруг области вырезания Коллекция сеточных градиентов Наложение сеточного градиента Наложить сеточный градиент на заданные изображения Настройка точек Размер сетки Разрешение X Разрешение Y Разрешение Попиксельно Цвет подсветки Сравнение пикселей Тип Отсканируйте штрихкод Соотношение высоты Тип штрихкода Принудительно ч/б Изображение штрихкода будет полностью чёрно-белым и не будет раскрашено темой приложения Отсканируйте любой штрихкод (QR, EAN, AZTEC и т.д.) и получите его содержимое или вставьте свой текст для создания нового Штрихкод не найден Сгенерированный штрихкод будет здесь Аудиообложки Извлечение обложек альбомов из аудиофайлов. Поддерживаются большинство распространённых форматов Выберите аудио для начала Выберите аудио Обложки не найдены Отправить логи Нажмите, чтобы поделиться файлом журнала приложения. Это поможет мне выявить и устранить проблему Упс… Что-то пошло не так Вы можете связаться со мной, используя указанные ниже способы, и я постараюсь найти решение\n(Не забудьте прикрепить логи) Записать в файл Извлечение текста из пакета изображений и сохранение его в одном текстовом файле Запись в метаданные Извлечение теsкста из каждого изображения и его размещение в EXIF-данных соответствующих фотографий Невидимый режим Использование стеганографии для создания невидимых глазу водяных знаков внутри байтов ваших изображений Использование LSB Будет использоваться метод стеганографии LSB (младший значащий бит), в противном случае — FD (частотная область) Автоматическое удаление красного Глаза Пароль Разблокировать PDF-файл защищён Операция почти завершена. Если вы отмените сейчас, потребуется начать сначала Дата изменения Дата изменения (наоборот) Размер Размер (наоборот) Тип MIME Тип MIME (наоборот) Расширение Расширение (наоборот) Дата добавления Дата добавления (наоборот) Слева направо Справа налево Сверху вниз Снизу вверх Жидкое стекло Переключатель на основе недавно анонсированной iOS 26 и её системы дизайна «жидкое стекло» Выберите изображение или вставьте/импортируйте данные Base64 ниже Введите ссылку на изображение, чтобы начать Вставить ссылку Калейдоскоп Дополнительный угол Стороны Микс каналов Сине-зелёный Красный-синий Зелёный-красный В красный В зелёный В синий Голубой Пурпурный Жёлтый Цветной полутон Контур Уровни Смещение Кристаллизация Вороного Форма Растяжение Случайность Устранение спеклов Рассеивание DoG Второй радиус Выровнять Свечение Вихрь и сжатие Пунктирование Цвет границы Полярные координаты Прямоугольная в полярную Полярная в прямоугольную Инвертировать в круг Уменьшить шум Простая Соляризация Плетение Зазор по оси X Зазор по оси Y Ширина по оси X Ширина по оси Y Закручивание Резиновый штамп Размазывание Плотность Смешивание Искажение сферической линзы Показатель преломления Дуга Угол рассеивания Блеск Лучи ASCII Градиент Муар Осень Кость Струйный Зима Океан Лето Весна Прохладный вариант HSV Розовый Горячий Парула Магма Инферно Плазма Зелёный Цивидис Сумерки Сумерки со смещением Автоперспектива Выравнивание Разрешить кадрирование Кадрирование или перспектива Абсолютный Турбо Темно-зелёный Коррекция объектива Целевой файл профиля объектива в формате JSON Скачать готовые профили Проценты частей Экспортировать в JSON Рабочий стол Экран блокировки Встроенные Экспорт обоев Обновить Получите актуальные обои рабочего стола, экрана блокировки и встроенные обои Разрешите доступ ко всем файлам, это необходимо для получения обоев Безопасность Конфигурация Wi-Fi Изменить баркод Создать баркод Долгота Широта Статус Дата окончания Дата начала Организатор Местоположение Описание Адреса Ссылки Электронные почты Номера телефонов Заголовок Организация Имя Адрес Сообщение Номер телефона Название сети Н/Д Открытая сеть Wi-Fi Ссылка СМС Текст Номер телефона Местоположение Электронная почта Контакт Мероприятие Ascii арт Преобразование изображения в текст ascii, который будет выглядеть как картинка Выдайте разрешение на доступ к контактам а настройках чтобы автозаполнять используя выбранный контакт Выбрать контакт Некоторые инструменты будут позволять не сохранять изображения, если размер получившегося файла будет больше исходного Разрешить пропускать если больше %1$s файлов пропущено Снимок экрана не сделан, попробуйте еще раз Обработка снимка экрана Применяет негативный фильтр к изображению для получения лучшего результата в некоторых случаях Параметры Добавляет к имени файла изображения суффикс с выбранным режимом масштабирования изображения Добавить режим масштабирования изображения в имя файла Название Тело Тема Сохранение пропущено Разрешить управление внешним хранилищем недостаточно, нужно разрешить доступ к изображениям, убедитесь, что выбрали \"Разрешить все\" Резьба по швам Копировать строку с данными палитры в виде json После перемещения или масштабирования изображения будут привязаны к краям кадра Включить привязку к границам Предотвращение поворота изображений с помощью жестов двумя пальцами Отключить вращение Добавить пресет в имя файла Добавляет к имени файла изображения суффикс с выбранной пресетом Информация о контакте Имя Отчество Фамилия Произношение Добавить номер телефона Добавить электронную почту Добавить адрес Вебсайт Добавить вебсайт Форматированное имя Это изображение будет использоваться для размещения над баркодом Кастомизация кода Это изображение будет использоваться в качестве логотипа в центре QR-кода Логотип Отступы логотипа Размер логотипа Углы логотипа Форма пикселя Форма рамки Форма круга Уровень коррекции ошибок Тёмный цвет Светлый цвет Hyper OS Стиль как в Xiaomi HyperOS Шаблон маски Четвертая метка Добавляет симметрию меток для QR кода с помощью добавления четвертой метки в правом нижнем углу Этот код может быть нечитаемым для сканера. Измените параметры внешнего вида, чтобы он считывался на всех устройствах Не подлежит сканированию Инструменты будут выглядеть как панель запуска приложений на главном экране, что сделает их более компактными Режим лаунчера Заполняет область выбранным цветом и стилем Заливка Спрей Рисует путь в виде граффити Квадртаные частицы Частицы спрея будут квадратными, а не круглыми Инструменты палитры Создайте базовую/материальную палитру из изображения или импортируйте/экспортируйте палитры в различные форматы Редактирование палитры Экспорт/импорт палитры в различных форматах Название цвета Название палитры Формат палитры Экспорт сгенерированной палитры в различные форматы Добавляет новый цвет в существующую палитру Формат %1$s не поддерживает указание имени палитры В соответствии с политикой Play Store, эта функция не может быть включена в текущую сборку. Для доступа к этой функциональности, пожалуйста, загрузите ImageToolbox из альтернативного источника. Доступные сборки можно найти на GitHub ниже Открыть на Github %1$s цвет %1$s цвета %1$s цветов %1$s цветов Исходный файл будет заменен новым, а не сохранен в выбранной папке Обнаружен скрытый текст водяного знака Обнаружено скрытое изображение водяного знака Это изображение было скрыто Генеративная закраска Позволяет удалять объекты на изображении с помощью модели ИИ, не полагаясь на OpenCV. Для использования этой функции приложение загрузит необходимую модель (~200 МБ) с GitHub Позволяет удалять объекты на изображении с помощью модели ИИ, не полагаясь на OpenCV. Это может быть длительная операция Анализ уровня ошибок Градиент яркости Среднее расстояние Обнаружение копирования и перемещения Удержание Коэффицент Объем данных в буфере обмена слишком велик Данные слишком велики для копирования Простая плетенная пикселизация Шахматная пикселизация Крестовая пикселизация Микро макро пикселизация Орбитальная пикселизация Вихревая пикселизация Пикселизация пульсирующей сетки Ядровая пикселизация Пикселизация радиального плетения Невозможно открыть ссылку \"%1$s\" Режим снегопада Включено Рамка Вариант сбоя Сдвиг канала Максимальное смещение VHS Блочный сбой Размер блока ЭЛТ изгиб Изгиб Цветность Плавление пикселей Максимальное падение ИИ инструменты Различные инструменты для обработки изображений с помощью моделей искусственного интеллекта, такие как удаление артефактов или шумоподавление Сжатие, рваные линии Мультфильмы, сжатие трансляций Общее сжатие, общий шум Бесцветный мультяшный шум Быстрое, общее сжатие, общий шум, анимация/комиксы/аниме Cканирование книг Коррекция экспозиции Лучше всего подходит для общего сжатия, цветных изображений Лучше всего подходит для общего сжатия, изображений в оттенках серого Общее сжатие, изображения в оттенках серого, более сильное Общий шум, цветные изображения Общий уровень шума, цветные изображения, улучшенная детализация Общий шум, изображения в оттенках серого Общий шум, изображения в оттенках серого, более сильный Общий шум, изображения в оттенках серого, самый сильный Общее сжатие Общее сжатие Texturization, h264 compression Сжатие VHS Нестандартное сжатие (cinepak, msvideo1, roq) Сжатие Bink, лучше для геометрии Сжатие Bink, более сильное Сжатие Bink, мягкое, сохраняет детали Устранение эффекта лесенки, сглаживание Отсканированные изображения/рисунки, легкое сжатие, муар Цветовая маркировка Медленно, удаляет полутона Универсальный раскрашиватель для черно-белых изображений; для достижения лучших результатов используйте DDColor Удаление краев Удаляет чрезмерную резкость Медленно, дизеринг Сглаживание, общие артефакты, компьютерная графика Обработка сканирований KDM003 Облегченная модель улучшения изображения Простая и быстрая раскраска, мультфильмы, не идеальный вариант Удаление артефактов сжатия Гладкое удаление артефактов сжатия Удаление связей с гладкими результатами Обработка полутоновых изображений Удаление шаблона дизеринга Удаление артефактов JPEG V2 Улучшение текстуры H.264 Улучшение и повышение резкости VHS-видео Слияние Размер фрагмента Размер перекрытия Изображения размером более %1$s пикселей будут разрезаны и обработаны по частям, а функция перекрытия смешивает их, чтобы предотвратить видимые швы Большие размеры могут вызывать нестабильность в работе устройств низкого ценового сегмента Выберите один из вариантов для начала Вы хотите удалить модель %1$s? Вам потребуется загрузить её заново Подтвердить Модели Загруженные модели Доступные модели Подготовка Активная модель Не удалось открыть сессию DeJPEG Шумоподавление Раскрашивание Артефакты Улучшение Аниме Сканы Импортировать можно только модели в форматах .onnx/.ort Импортировать модель Импортируйте пользовательскую модель ONNX для дальнейшего использования; принимаются только модели ONNX/ORT, поддерживаются почти все варианты, подобные ESRGAN Импортированные модели Общий шум, цветные изображения Общий шум, цветные изображения, более интенсивный Общий шум, цветные изображения, самый сильный Уменьшает артефакты дизеринга и полосы цвета, улучшая плавность градиентов и плоские цветовые области Улучшает яркость и контрастность изображения, обеспечивая сбалансированное освещение светлых участков и сохраняя естественные цвета Осветляет темные изображения, сохраняя при этом детали и избегая переэкспозиции Устраняет избыточное тонирование и восстанавливает более нейтральный и естественный цветовой баланс Применяет шумоподавление на основе распределения Пуассона с акцентом на сохранение мелких деталей и текстур Применяет мягкий пуассоновский шум для получения более плавных и менее агрессивных визуальных результатов Равномерное шумоподавление, направленное на сохранение деталей и четкости изображения Мягкое равномерное шумоподавление для создания тонкой текстуры и гладкой поверхности Восстанавливает поврежденные или неровные участки путем перекрашивания артефактов и улучшения согласованности изображения Облегченная модель устранения полос, которая удаляет цветовые полосы с минимальными потерями производительности Оптимизирует изображения с очень сильными артефактами сжатия (качество 0-20%) для повышения четкости Улучшает изображения с сильными артефактами сжатия (качество 20-40%), восстанавливая детали и уменьшая шум Улучшает изображения за счет умеренного сжатия (качество 40-60%), обеспечивая баланс между резкостью и плавностью Улучшение качества изображений с легким сжатием (60-80%) для выделения тонких деталей и текстур Слегка улучшает изображения практически без потерь качества (80-100%), сохраняя при этом естественный вид и детали Немного уменьшает размытие изображения, повышая резкость без появления артефактов Долгосрочные операции Обработка изображения Обработка Удаляет сильные артефакты сжатия JPEG на изображениях очень низкого качества (0-20%) Уменьшает сильные артефакты JPEG в сильно сжатых изображениях (на 20-40%) Устраняет умеренные артефакты JPEG, сохраняя при этом детали изображения (40-60%) Улучшает качество изображений JPEG на достаточно высоком уровне (60-80%) Незаметно уменьшает незначительные артефакты JPEG в изображениях практически без потерь качества (80-100%) Улучшает детализацию и текстуры, повышая воспринимаемую резкость без существенных артефактов Обработка завершена Обработка не удалась Улучшает текстуру и детали кожи, сохраняя при этом естественный вид, оптимизировано для высокой скорости Удаляет артефакты сжатия JPEG и восстанавливает качество изображения для сжатых фотографий Уменьшает уровень шума ISO на фотографиях, сделанных в условиях недостаточного освещения, сохраняя при этом детали Корректирует переэкспонированные или «гиперэкспонированные» участки и восстанавливает лучший тональный баланс Легкая и быстрая модель цветокоррекции, добавляющая естественные цвета к изображениям в оттенках серого Апскейл Масштабирование X4 для обычных изображений; миниатюрная модель, использующая меньше ресурсов графического процессора и времени, с умеренным устранением размытия и шумоподавлением Масштабирование X2 для обычных изображений с сохранением текстур и естественных деталей Масштабирование X4 для обычных изображений с улучшенными текстурами и реалистичными результатами Оптимизированный для аниме-изображений масштабировщик X4; 6 блоков RRDB для более четких линий и деталей Масштабирование X4 с использованием функции потерь MSE обеспечивает более плавные результаты и уменьшает артефакты для обычных изображений X4 Upscaler, оптимизированный для аниме-изображений; вариант 4B32F с более четкой детализацией и плавными линиями Модель предназначена для съемки изображений общего назначения; акцент делается на резкость и четкость Быстрее и компактнее, сохраняет детализацию, используя при этом меньше памяти графического процессора Легкая модель для быстрого удаления фона. Сбалансированная производительность и точность. Работает с портретами, объектами и сценами. Рекомендуется для большинства случаев использования Удаление фона Толщина горизонтальной границы Толщина вертикальной границы Текущая модель не поддерживает фрагментацию, изображение будет обработано в исходных размерах, это может привести к высокому потреблению памяти и проблемам с устройствами низкого уровня. Фрагментация отключена, изображение будет обрабатываться в исходных размерах. Это может привести к высокому потреблению памяти и проблемам с устройствами низкого уровня, но может дать лучшие результаты при выводе. Фрагментация Высокоточная модель сегментации изображения для удаления фона Облегченная версия U2Net для более быстрого удаления фона с меньшим использованием памяти. Модель Full DDColor обеспечивает высококачественную раскраску обычных изображений с минимальными артефактами. Лучший выбор из всех моделей раскрашивания. DDColor Обученные и частные художественные наборы данных; дает разнообразные и художественные результаты раскрашивания с меньшим количеством нереалистичных цветовых артефактов. Легкая модель BiRefNet на основе Swin Transformer для точного удаления фона. Качественное удаление фона с острыми краями и отличным сохранением деталей, особенно на сложных объектах и сложном фоне. Модель удаления фона, которая создает точные маски с гладкими краями, подходящую для обычных объектов и умеренного сохранения деталей. Модель уже скачана Модель успешно импортирована Тип Ключевое слово Очень быстро Нормально Медленно Очень медленно Движение Рост Сжатие Вихрь (→) Вихрь (←) Вычисление процентов Минимальное значение: %1$s Искажайте изображение, рисуя пальцами Деформация Твердость Режим деформации Сила исчезновения Верхняя обрезка Нижняя обрезка Левая обрезка Правая обрезка Загрузка Гладкие формы Используйте суперэллипсы вместо стандартных закругленных прямоугольников для получения более плавных и естественных форм. Тип формы Обрезанная Закругленная Гладкая Острые края без закруглений Классические закругленные углы. Тип фигуры Размер углов Сквиркл Элегантные закругленные элементы пользовательского интерфейса Формат имени файла Пользовательский текст, размещаемый в самом начале имени файла, идеально подходит для названий проектов, брендов или личных тегов. Использует исходное имя файла без расширения, что помогает сохранить идентификацию источника без изменений. Ширина изображения в пикселях, полезная для отслеживания изменений разрешения или масштабирования результатов. Высота изображения в пикселях, полезная при работе с пропорциями или экспорте. Генерирует случайные цифры, чтобы гарантировать уникальные имена файлов; добавьте больше цифр для дополнительной защиты от дубликатов. Автоматически увеличивающийся счетчик для пакетного экспорта идеален при сохранении нескольких изображений за один сеанс. Вставляет имя примененной предустановки в имя файла, чтобы вы могли легко запомнить, как было обработано изображение. Отображает режим масштабирования изображения, используемый во время обработки, помогая различать изображения с измененным размером, обрезанные или подогнанные. Пользовательский текст, помещаемый в конце имени файла, полезный для управления версиями, например _v2, _edited или _final. Расширение файла (png, jpg, webp и т. д.) автоматически соответствует фактическому сохраненному формату. Настраиваемая временная метка, позволяющая определить собственный формат по спецификации Java для идеальной сортировки. Тип броска Android Стиль iOS Гладкая кривая Быстрая остановка Надувной Плавучий Шустрый Ультра гладкий Адаптивный Доступность Уменьшенное движение Встроенная физика прокрутки Android Сбалансированная, плавная прокрутка для общего использования. Поведение прокрутки, подобное iOS, с повышенным трением Уникальная кривая сплайна для четкого ощущения прокрутки Точная прокрутка с быстрой остановкой Игривая, отзывчивая, упругая прокрутка Длинная скользящая прокрутка для просмотра контента Быстрая и отзывчивая прокрутка для интерактивных интерфейсов. Плавная прокрутка премиум-класса с увеличенным импульсом Регулирует физику в зависимости от скорости броска. Соблюдает настройки специальных возможностей системы Минимальное движение для обеспечения доступности Первичные линии Добавляет более толстую линию каждую пятую строку. Цвет заливки Скрытые инструменты Инструменты, скрытые для общего доступа Библиотека цветов Просмотрите обширную коллекцию цветов Увеличивает резкость и устраняет размытие изображений, сохраняя при этом естественные детали, что идеально подходит для исправления расфокусированных фотографий. Интеллектуальное восстановление изображений, размер которых ранее был изменен, восстанавливая потерянные детали и текстуры. Оптимизирован для контента с живыми актерами, уменьшает артефакты сжатия и улучшает мелкие детали в кадрах фильмов/телешоу. Преобразует отснятый материал с качеством VHS в HD, удаляя шум ленты и повышая разрешение, сохраняя при этом ощущение винтажности. Специально предназначен для изображений и снимков экрана с большим количеством текста, повышает резкость символов и улучшает читаемость. Расширенное масштабирование, обученное на различных наборах данных, отлично подходит для общего улучшения фотографий. Оптимизирован для фотографий, сжатых через Интернет, удаляет артефакты JPEG и восстанавливает естественный вид. Улучшенная версия для веб-фотографий с лучшим сохранением текстур и уменьшением артефактов. Двукратное масштабирование с помощью технологии Dual Aggregation Transformer обеспечивает четкость и естественные детали. 3-кратное масштабирование с использованием усовершенствованной архитектуры трансформатора, идеально подходящее для умеренных потребностей в расширении. Четырехкратное высококачественное масштабирование с использованием современной трансформаторной сети сохраняет мелкие детали в больших масштабах. Удаляет размытие/шум и дрожание фотографий. Общего назначения, но лучше всего на фотографиях. Восстанавливает изображения низкого качества с помощью преобразователя Swin2SR, оптимизированного для деградации BSRGAN. Отлично подходит для исправления сильных артефактов сжатия и улучшения деталей в 4-кратном масштабе. 4-кратное масштабирование с помощью преобразователя SwinIR, обученного ухудшению BSRGAN. Использует GAN для получения более четких текстур и более естественных деталей на фотографиях и сложных сценах. Путь Объединить PDF Объединение нескольких PDF-файлов в один документ Порядок файлов стр. Разделить PDF Извлечь определенные страницы из PDF-документа Повернуть PDF Исправить ориентацию страницы навсегда Страницы Переупорядочить PDF Перетаскивайте страницы, чтобы изменить их порядок Удерживайте и перетащите страницы Номера страниц Добавляйте нумерацию к вашим документам автоматически Формат этикетки PDF в текст (OCR) Извлекайте простой текст из ваших PDF-документов Наложение пользовательского текста для брендинга или безопасности Подпись Добавьте свою электронную подпись к любому документу Это будет использоваться в качестве подписи Разблокировать PDF Удалите пароли из ваших защищенных файлов Защитить PDF Защитите свои документы надежным шифрованием Успех PDF-файл разблокирован, вы можете сохранить его или поделиться им. Восстановить PDF Попытайтесь исправить поврежденные или нечитаемые документы. Оттенки серого Преобразование всех встроенных изображений документа в оттенки серого Сжать PDF Оптимизируйте размер файла документа для упрощения обмена ImageToolbox перестраивает внутреннюю таблицу перекрестных ссылок и восстанавливает структуру файла с нуля. Это может восстановить доступ ко многим файлам, которые «нельзя открыть». Этот инструмент преобразует все изображения документов в оттенки серого. Лучше всего подходит для печати и уменьшения размера файла. Метаданные Редактируйте свойства документа для большей конфиденциальности Теги Продюсер Автор Ключевые слова Создатель Глубокая очистка Удалите все доступные метаданные для этого документа Страница Глубокое распознавание текста Извлеките текст из документа и сохраните его в одном текстовом файле с помощью механизма Tesseract. Невозможно удалить все страницы Удалить страницы PDF Удалить определенные страницы из PDF-документа Нажмите, чтобы удалить Вручную Обрезать PDF Обрежьте страницы документа до любых границ Свести PDF Сделайте PDF неизменяемым, растрируя страницы документа Не удалось запустить камеру. Пожалуйста, проверьте разрешения и убедитесь, что она не используется другим приложением. Извлечь изображения Извлекайте изображения, встроенные в PDF-файлы, в исходном разрешении. Этот PDF-файл не содержит встроенных изображений. Этот инструмент сканирует каждую страницу и восстанавливает исходные изображения в полном качестве — идеально подходит для сохранения оригиналов из документов. Нарисовать подпись Параметры пера Использовать собственную подпись в качестве изображения для размещения на документах Zip PDF Разделить документ с заданным интервалом и упаковать новые документы в zip-архив. Интервал Распечатать PDF Подготовьте документ к печати с нестандартным размером страницы. Страниц на листе Ориентация Размер страницы Допуск Цвести Мягкое колено Оптимизирован для аниме и мультфильмов. Быстрое масштабирование с улучшенными естественными цветами и меньшим количеством артефактов. Стиль Samsung One UI 7 Введите здесь основные математические символы, чтобы вычислить желаемое значение (например, (5+5)*10). Математическое выражение Выберите до %1$s изображений Сохраняйте дату и время Всегда сохранять теги exif, связанные с датой и временем, работает независимо от опции сохранения exif. Цвет фона для альфа-форматов Добавляет возможность устанавливать цвет фона для каждого формата изображения с поддержкой альфа-канала, при отключении это доступно только для не-альфа-форматов. Открыть проект Продолжить редактирование ранее сохраненного проекта Image Toolbox Невозможно открыть проект Image Toolbox В проекте Image Toolbox отсутствуют данные проекта Проект Image Toolbox поврежден. Неподдерживаемая версия проекта Image Toolbox: %1$d. Сохранить проект Храните слои, фон и историю редактирования в редактируемом файле проекта. Не удалось открыть Запись в PDF с возможностью поиска Распознавайте текст из пакета изображений и сохраняйте PDF с возможностью поиска с изображением и выбираемым текстовым слоем. Прозрачность слоя Горизонтальный флип Вертикальный флип Замок Добавить тень Цвет тени Текстовая геометрия Растяните или наклоните текст для более четкой стилизации. Масштаб X Наклон X Удаление аннотаций Удаление выбранных типов аннотаций, таких как ссылки, комментарии, выделение, фигуры или поля форм, со страниц PDF. Гиперссылки Вложения файлов Линии Всплывающие окна Марки Формы Текстовые заметки Текстовая разметка Поля формы Разметка Неизвестный Аннотации Разгруппировать Добавьте размытую тень за слоем с настраиваемым цветом и смещениями. ================================================ FILE: core/resources/src/main/res/values-si/strings.xml ================================================ ගැටලුවක් පවතී: %1$s ප්‍රමාණය %1$s මොහොතක් සිටින්න… ඡායාරූපය විශාල බැවින් පෙරදසුනක් ඉදිරිපත් කිරීමට නොහැක, නමුත් සේව් කිරීම මගින් නැවත උත්සහ කළ හැක පටන් ගැනීමට ඡායාරූපයක් තෝරන්න පළල %1$s උස %1$s වහන්න ඉන්න ඔයාට ඇප් එක ක්ලෝස් කරන්න ඕනමද ? රූපය යළි පිහිටුවන්න රූප වෙනස් කිරීම් මුල් අගයන් වෙත පෙරළෙනු ඇත අගයන් නිවැරදිව නැවත සකසන්න යළි පිහිටුවන්න වැරදීමක් සිදුවී ඇත ඇප් එක රීස්ටාට් කරන්න EXIF සංස්කරණය කරන්න හරි EXIF දත්ත හමු නොවීය සේව් කරන්න EXIF ඉවත් කරන්න ප්‍රමාණය වෙනස් කරන්න ඡායාරූපයේ ගුණාත්මක භාවය ඡායාරූපයක් තෝරන්න ඇප් එක වසා දැමෙමින් ක්ලිප්බෝඩ් එකට පිටපත් කර ඇත ටැගය එක් කරන්න හිස් කරන්න අවලංගු කරන්න "සියලුම පින්තූර EXIF දත්ත මැකෙනු ඇත. මෙම ක්‍රියාව නැවත සිදුකළ නොහැක!" උපාංග ගබඩාව නිශ්චිතව දක්වා නැත බර අනුව ප්‍රමාණය වෙනස් කරන්න උපරිම ප්‍රමාණය KB වලින් KB වලින් ලබා දී ඇති ප්‍රමාණයෙන් රූපයේ ප්‍රමාණය වෙනස් කරන්න සසඳන්න ලබා දී ඇති පින්තූර දෙකක් සසඳන්න ආරම්භ කිරීමට පින්තූර දෙකක් තෝරන්න පින්තූර තෝරන්න සැකසුම් අඳුරු තේමාව ලයිට් ඩාක් භාෂාව පද්ධති ඔන් කර ඇත්නම්, ඔබ සංස්කරණය කිරීමට රූපයක් තෝරන විට, යෙදුම් වර්ණ මෙම රූපයට අනුගත වනු ඇත දිගු කිරීම පැහැදිලිය නම්යශීලී ව්යතිරේක පෙරසිටුවීම් බෝග ඉතිරි කිරීම ඔබ දැන් පිටවන්නේ නම්, නොසුරකින ලද සියලුම වෙනස් කිරීම් අහිමි වනු ඇත මූලාශ්ර කේතය නවතම යාවත්කාලීන ලබා ගන්න, ගැටළු සාකච්ඡා කරන්න සහ තවත් දේ තනි සංස්කරණය එක් රූපයක් වෙනස් කරන්න, ප්‍රමාණය වෙනස් කරන්න සහ සංස්කරණය කරන්න වර්ණ පිකර් රූපයෙන් වර්ණය තෝරන්න, පිටපත් කරන්න හෝ බෙදාගන්න රූපය වර්ණය වර්ණ පිටපත් කර ඇත රූපය ඕනෑම සීමාවකට කපන්න අනුවාදය EXIF තබා ගන්න පින්තූර: %d පෙරදසුන වෙනස් කරන්න ඉවත් කරන්න දී ඇති රූපයෙන් වර්ණාලේප කට්ටලයක් ජනනය කරන්න පැලට් ජනනය කරන්න පැලට් යාවත්කාලීන කරන්න නව අනුවාදය %1$s සහාය නොදක්වන වර්ගය: %1$s ලබා දී ඇති රූපය සඳහා palette ජනනය කළ නොහැක මුල් ප්රතිදාන ෆෝල්ඩරය පෙරනිමිය අභිරුචි ගතික වර්ණ අභිරුචිකරණය රූප මුදල් ඉඩ දෙන්න Amoled මාදිලිය සබල කර ඇත්නම් මතුපිට වර්ණය රාත්‍රී ප්‍රකාරයේදී නිරපේක්ෂ අඳුරු ලෙස සකසනු ඇත වර්ණ පටිපාටිය රතු කොළ පාටයි නිල් වලංගු aRGB වර්ණ කේතයක් අලවන්න ඇලවීමට කිසිවක් නැත ගතික වර්ණ ක්‍රියාත්මක කර ඇති අතර යෙදුමේ වර්ණ පටිපාටිය වෙනස් කළ නොහැක යෙදුම් තේමාව තෝරාගත් වර්ණය මත පදනම් වනු ඇත යෙදුම ගැන යාවත්කාලීන කිසිවක් හමු නොවීය නිකුත් කිරීමේ ට්රැකර් දෝෂ වාර්තා සහ විශේෂාංග ඉල්ලීම් මෙහි යවන්න පරිවර්තනයට උදව් කරන්න පරිවර්තන දෝෂ නිවැරදි කරන්න හෝ වෙනත් භාෂාවකට ව්‍යාපෘතිය ස්ථානගත කරන්න ඔබගේ විමසුමෙන් කිසිවක් සොයා ගත්තේ නැත මෙහි සොයන්න සබල කර ඇත්නම්, පසුව යෙදුම් වර්ණ බිතුපත් වර්ණවලට අනුගත වනු ඇත %d රූප(ය) සුරැකීමට අසමත් විය ඊමේල් කරන්න ප්රාථමික තෘතියික ද්විතියික මායිම් ඝණකම මතුපිට වටිනාකම් එකතු කරන්න අවසරය ප්‍රදානය කරන්න වැඩ කිරීමට පින්තූර සුරැකීමට යෙදුමට ඔබේ ගබඩාවට ප්‍රවේශය අවශ්‍ය වේ, එය අවශ්‍ය වේ. කරුණාකර ඊළඟ සංවාද කොටුවේ අවසරය ලබා දෙන්න. යෙදුමට ක්‍රියා කිරීමට මෙම අවසරය අවශ්‍යයි, කරුණාකර එය අතින් ලබා දෙන්න බාහිර ගබඩාව Monet වර්ණ මෙම යෙදුම සම්පූර්ණයෙන්ම නොමිලේ, නමුත් ඔබට ව්‍යාපෘති සංවර්ධනයට සහාය වීමට අවශ්‍ය නම්, ඔබට මෙහි ක්ලික් කළ හැකිය FAB පෙළගැස්ම යාවත්කාලීන සඳහා පරීක්ෂා කරන්න සබල කර ඇත්නම්, යෙදුම් ආරම්භයේදී යාවත්කාලීන සංවාදය ඔබට පෙන්වනු ඇත රූප විශාලනය බෙදාගන්න උපසර්ගය ගොනු නාමය ඉමොජි ප්‍රධාන තිරයේ පෙන්විය යුතු ඉමොජි තෝරන්න ගොනු ප්රමාණය එකතු කරන්න සබල කර ඇත්නම්, ප්‍රතිදාන ගොනුවේ නමට සුරකින ලද රූපයේ පළල සහ උස එක් කරයි EXIF මකන්න ඕනෑම රූප සමූහයකින් EXIF ​​පාර-දත්ත මකන්න රූප පෙරදසුන ඕනෑම ආකාරයක රූප පෙරදසුන් කරන්න: GIF, SVG, සහ යනාදිය රූප මූලාශ්රය ඡායාරූප පිකර් ගැලරිය ගොනු ගවේෂකය තිරයේ පහළින් දිස්වන Android නවීන ඡායාරූප පිකර්, ක්‍රියා කළ හැක්කේ android 12+ මත පමණි. EXIF පාර-දත්ත ලබා ගැනීමේ ගැටළු තිබේ සරල ගැලරි රූප පිකර්. එය ක්‍රියා කරන්නේ ඔබට මාධ්‍ය තෝරා ගැනීම සපයන යෙදුමක් තිබේ නම් පමණි රූපය තෝරා ගැනීමට GetContent අභිප්‍රාය භාවිතා කරන්න. සෑම තැනකම ක්‍රියා කරයි, නමුත් සමහර උපාංගවල තෝරාගත් පින්තූර ලැබීමේ ගැටලු ඇති බව දන්නා කරුණකි. ඒක මගේ වරදක් නෙවෙයි. විකල්ප සැකැස්ම සංස්කරණය කරන්න ඇණවුම් කරන්න ප්‍රධාන තිරයේ ඇති මෙවලම් අනුපිළිවෙල තීරණය කරයි ඉමෝජි ගණන් අනුක්‍රමික අංකය මුල් ගොනු නාමය මුල් ගොනු නාමය එක් කරන්න සබල කර ඇත්නම් ප්‍රතිදාන රූපයේ නමට මුල් ගොනු නාමය එක් කරයි අනුක්‍රමික අංකය ප්‍රතිස්ථාපනය කරන්න සක්‍රීය කර ඇත්නම්, ඔබ කණ්ඩායම් සැකසීම භාවිතා කරන්නේ නම් සම්මත වේලා මුද්‍රාව රූප අනුක්‍රමික අංකයට ප්‍රතිස්ථාපනය කරයි ෆොටෝ පිකර් පිංතූර මූලාශ්‍රය තේරුවහොත් මුල් ගොනු නාමය එකතු කිරීම ක්‍රියා නොකරයි වෙබ් පින්තූර පූරණය ඔබට අවශ්‍ය නම් එය පෙරදසුන් කිරීමට, විශාලනය කිරීමට, සංස්කරණය කිරීමට සහ සුරැකීමට අන්තර්ජාලයෙන් ඕනෑම රූපයක් පූරණය කරන්න. රූපයක් නැත රූප සබැඳිය පුරවන්න සුදුසුයි අන්තර්ගත පරිමාණය ලබා දී ඇති උස සහ පළලට රූප ප්‍රතිප්‍රමාණ කරයි. රූපවල දර්ශන අනුපාතය වෙනස් විය හැක. දී ඇති උසට හෝ පළලට දිගු පැත්තක් සහිත රූප ප්‍රතිප්‍රමාණ කරයි. සියලුම ප්‍රමාණයේ ගණනය කිරීම් සුරැකීමෙන් පසුව සිදු කෙරේ. රූපවල දර්ශන අනුපාතය සුරැකෙනු ඇත. දීප්තිය පරස්පරතාව පැහැය සන්තෘප්තිය පෙරහන එකතු කරන්න පෙරහන පින්තූර සඳහා පෙරහන් දාම යොදන්න පෙරහන් ආලෝකය වර්ණ පෙරහන ඇල්ෆා නිරාවරණය සුදු සමබරතාවය උෂ්ණත්වය ටින්ට් ඒකවර්ණ ගැමා ඉස්මතු කිරීම් සහ සෙවනැලි ඉස්මතු කිරීම් සෙවනැලි මීදුම බලපෑම දුර බෑවුම තියුණු කරන්න සේපියා සෘණාත්මකයි Solarize කම්පනය කළු සහ සුදු Crosshatch පරතරය රේඛා පළල සෝබෙල් දාරය බොඳ කරන්න අර්ධ ස්වරය CGA වර්ණ අවකාශය Gaussian බොඳවීම පෙට්ටිය බොඳ වීම ද්විපාර්ශ්වික බොඳවීම එම්බොස් කරන්න ලැප්ලැසියන් විග්නෙට් ආරම්භ කරන්න අවසානය කුවහර සුමටනය ස්ටැක් බොඳවීම අරය පරිමාණය විකෘතිය කෝණය කරකැවිල්ල බල්ජ් විස්තාරණය ගෝල වර්තනය වර්තන දර්ශකය වීදුරු ගෝල වර්තනය වර්ණ අනුකෘතිය පාරාන්ධතාව සීමාවන් අනුව ප්‍රමාණය වෙනස් කරන්න දර්ශන අනුපාතය තබා ගනිමින් ලබා දී ඇති උස සහ පළලට රූප ප්‍රතිප්‍රමාණ කරන්න ස්කීච් එළිපත්ත ප්‍රමාණකරණ මට්ටම් සිනිඳු ටූන් ටූන් පෝස්ටර් කරන්න උපරිම නොවන මර්දනය දුර්වල පික්සල් ඇතුළත් කිරීම සොයන්න Convolution 3x3 RGB පෙරහන බොරු පාට පළමු වර්ණය දෙවන වර්ණය නැවත ඇණවුම් කරන්න වේගවත් නොපැහැදිලි බොඳ ප්‍රමාණය නොපැහැදිලි කේන්ද්‍රය x බොඳ මධ්‍යස්ථානය y විශාලනය බොඳ කිරීම වර්ණ ශේෂය දීප්තිය එළිපත්ත ඔබ ගොනු යෙදුම අබල කර ඇත, මෙම විශේෂාංගය භාවිතා කිරීමට එය සක්‍රිය කරන්න අඳින්න ස්කීච් පොතක මෙන් රූපය මත අඳින්න, නැතහොත් පසුබිම මතම අඳින්න තීන්ත වර්ණය ඇල්ෆා තීන්ත ආලේප කරන්න රූපය මත අඳින්න රූපයක් තෝරා එය මත යමක් අඳින්න පසුබිම මත ඇඳීම පසුබිම් වර්ණය තෝරා එය මත අඳින්න පසුබිම් වර්ණය කේතාංකය පවතින විවිධ ක්‍රිප්ටෝ ඇල්ගොරිතම මත පදනම්ව ඕනෑම ගොනුවක් (රූපය පමණක් නොව) සංකේතනය කර විකේතනය කරන්න ගොනුව තෝරන්න සංකේතනය කරන්න විකේතනය කරන්න ආරම්භ කිරීමට ගොනුව තෝරන්න විකේතනය සංකේතනය යතුර ගොනුව සකසා ඇත මෙම ගොනුව ඔබගේ උපාංගයේ ගබඩා කරන්න නැතහොත් ඔබට අවශ්‍ය ඕනෑම තැනක තැබීමට බෙදාගැනීමේ ක්‍රියාව භාවිතා කරන්න විශේෂාංග ක්රියාත්මක කිරීම ගැළපුම මුරපදය මත පදනම් වූ ගොනු සංකේතනය කිරීම. ලබාගත් ගොනු තෝරාගත් නාමාවලියෙහි ගබඩා කර හෝ බෙදාගත හැක. විකේතනය කරන ලද ගොනු ද කෙලින්ම විවෘත කළ හැකිය. AES-256, GCM මාදිලිය, පිරවුමක් නැත, පෙරනිමියෙන් බයිට් 12 අහඹු IVs. ඔබට අවශ්ය ඇල්ගොරිතම තෝරාගත හැක. යතුරු 256-bit SHA-3 හැෂ් ලෙස භාවිතා කරයි ගොනු විශාලත්වය උපරිම ගොනු ප්‍රමාණය Android OS සහ පවතින මතකය මගින් සීමා කර ඇත, එය උපාංගය මත රඳා පවතී. \nකරුණාකර සලකන්න: මතකය ගබඩා කිරීම නොවේ. වෙනත් ගොනු සංකේතාංකන මෘදුකාංග හෝ සේවාවන් සඳහා ගැළපුම සහතික නොවන බව කරුණාවෙන් සලකන්න. තරමක් වෙනස් යතුරු ප්‍රතිකාරයක් හෝ කේතාංක වින්‍යාසයක් නොගැලපීම ඇති කළ හැකිය. වලංගු නොවන මුරපදයක් හෝ තෝරාගත් ගොනුවක් සංකේතනය කර නොමැත ලබා දී ඇති පළල සහ උස සමඟ රූපය සුරැකීමට උත්සාහ කිරීම මතකයේ දෝෂයක් ඇති විය හැක. මෙය ඔබේම අවදානමකින් කරන්න. හැඹිලිය හැඹිලි ප්රමාණය %1$s හමු විය ස්වයංක්‍රීය හැඹිලි ඉවත් කිරීම සබල කර ඇත්නම් යෙදුම් හැඹිලිය යෙදුම් ආරම්භයේදී හිස් කරනු ඇත නිර්මාණය කරන්න මෙවලම් වර්ගය අනුව කණ්ඩායම් විකල්ප අභිරුචි ලැයිස්තු සැකැස්මක් වෙනුවට ඒවායේ වර්ගය අනුව ප්‍රධාන තිරය මත කණ්ඩායම් විකල්ප විකල්ප සමූහකරණය සබල කර ඇති අතර විධිවිධාන වෙනස් කළ නොහැක තිර රුවක් සංස්කරණය කරන්න ද්විතියික අභිරුචිකරණය තිර රුවක් ආපසු හැරීමේ විකල්පය මඟ හරින්න පිටපත් කරන්න %1$s මාදිලියේ සුරැකීම අස්ථායී විය හැක, මන්ද එය පාඩු රහිත ආකෘතියකි ඔබ පෙරසිටූ 125 තෝරාගෙන තිබේ නම්, රූපය මුල් රූපයේ 125% ප්‍රමාණය ලෙස සුරකිනු ඇත. ඔබ පෙරසිටූ 50 තෝරා ගන්නේ නම්, එවිට රූපය 50% ප්‍රමාණයෙන් සුරකිනු ඇත මෙහි පෙරසිටීම මඟින් ප්‍රතිදාන ගොනුවේ % තීරණය කරයි, එනම් ඔබ 5 MB රූපයේ පෙරසිටූ 50 තෝරා ගන්නේ නම්, සුරැකීමෙන් පසු ඔබට 2,5 MB රූපයක් ලැබෙනු ඇත. ගොනු නාමය සසම්භාවී කරන්න සබල කර ඇත්නම් ප්‍රතිදාන ගොනු නාමය සම්පූර්ණයෙන්ම අහඹු වේ %2$s නම සහිත %1$s ෆෝල්ඩරය වෙත සුරකින ලදී %1$s ෆෝල්ඩරය වෙත සුරකින ලදී ටෙලිග්‍රාම් කතාබස් යෙදුම ගැන සාකච්ඡා කර වෙනත් පරිශීලකයින්ගෙන් ප්‍රතිපෝෂණ ලබා ගන්න. ඔබට එහි බීටා යාවත්කාලීන සහ විදසුන් ද ලබා ගත හැක. බෝග මාස්ක් දර්ශන අනුපාතය ලබා දී ඇති රූපයෙන් වෙස් මුහුණු සෑදීමට මෙම ආවරණ වර්ගය භාවිතා කරන්න, එහි ඇල්ෆා නාලිකාව තිබිය යුතු බව සලකන්න උපස්ථ සහ ප්රතිෂ්ඨාපනය උපස්ථ ප්‍රතිෂ්ඨාපනය කරන්න ඔබගේ යෙදුම් සැකසීම් ගොනුවකට උපස්ථ කරන්න කලින් ජනනය කළ ගොනුවෙන් යෙදුම් සැකසුම් ප්‍රතිසාධනය කරන්න දූෂිත ගොනුවක් හෝ උපස්ථයක් නොවේ සැකසීම් සාර්ථකව ප්‍රතිසාධනය කරන ලදී මාව සම්බන්ධ කරගන්න මෙය ඔබගේ සැකසුම් පෙරනිමි අගයන් වෙත පෙරළනු ඇත. ඉහත සඳහන් කළ උපස්ථ ගොනුවක් නොමැතිව මෙය පසුගමනය කළ නොහැකි බව සලකන්න. මකන්න ඔබ තෝරාගත් වර්ණ පටිපාටිය මකා දැමීමට සූදානම් වේ. මෙම මෙහෙයුම පසුගමනය කළ නොහැක යෝජනා ක්රමය මකන්න අකුරු පෙළ අකුරු පරිමාණය පෙරනිමිය විශාල අකුරු පරිමාණයන් භාවිතා කිරීමෙන් UI දෝෂ සහ ගැටළු ඇති විය හැක, ඒවා නිවැරදි නොවනු ඇත. පරිස්සමෙන් භාවිතා කරන්න. අ ආ ඇ ඈ ඉ ඊ උ ඌ එ ඒ ඔ ඕ ක ග ච ජ ට ඩ ත ද න ප බ ම ය ර ල ව ස හ 0123456789 !? හැඟීම් ආහාර සහ බීම සොබාදහම සහ සතුන් වස්තු සංකේත ඉමොජි සබල කරන්න සංචාර සහ ස්ථාන ක්රියාකාරකම් පසුබිම් ඉවත් කරන්නා චිත්‍ර ඇඳීමෙන් හෝ ස්වයංක්‍රීය විකල්පය භාවිතා කිරීමෙන් රූපයෙන් පසුබිම ඉවත් කරන්න රූපය කපා දමන්න මුල් රූප පාරදත්ත තබා ගනු ඇත රූපය වටා ඇති විනිවිද පෙනෙන අවකාශයන් කපා හරිනු ලැබේ පසුබිම ස්වයංක්‍රීයව මකා දැමීම රූපය ප්‍රතිසාධනය කරන්න මකන ආකාරය පසුබිම මකන්න පසුබිම ප්‍රතිසාධනය කරන්න බොඳ අරය පයිප්පෙට් ඇඳීම් මාදිලිය ගැටලුවක් සාදන්න අපොයි… යමක් වැරදී ඇත. පහත විකල්ප භාවිතයෙන් ඔබට මට ලිවිය හැකි අතර මම විසඳුමක් සෙවීමට උත්සාහ කරමි ප්‍රමාණය වෙනස් කර පරිවර්තනය කරන්න ලබා දී ඇති පින්තූරවල ප්‍රමාණය වෙනස් කරන්න හෝ ඒවා වෙනත් ආකෘතිවලට පරිවර්තනය කරන්න. තනි රූපයක් තෝරා ගන්නේ නම් EXIF ​​පාරදත්ත ද මෙහි සංස්කරණය කළ හැක. උපරිම වර්ණ ගණන මෙය යෙදුමට බිඳ වැටීම් වාර්තා ස්වයංක්‍රීයව රැස් කිරීමට ඉඩ සලසයි විශ්ලේෂණ නිර්නාමික යෙදුම් භාවිත සංඛ්‍යාලේඛන එකතු කිරීමට ඉඩ දෙන්න දැනට, %1$s ආකෘතියෙන් ඉඩ දෙන්නේ android මත EXIF ​​පාරදත්ත කියවීමට පමණි. ප්‍රතිදාන රූපය සුරකින විට පාරදත්ත කිසිසේත්ම නොතිබෙනු ඇත. උත්සාහය %1$s අගයක් යනු වේගවත් සම්පීඩනයක්, ප්‍රතිඵලයක් ලෙස සාපේක්ෂ විශාල ගොනු ප්‍රමාණයකි. %2$s යනු මන්දගාමී සම්පීඩනය, කුඩා ගොනුවක් ඇති කරයි. ඉන්න සුරැකීම බොහෝ දුරට සම්පූර්ණයි. දැන් අවලංගු කිරීමට නැවත සුරැකීමට අවශ්‍ය වනු ඇත. යාවත්කාලීන බීටා වලට ඉඩ දෙන්න යාවත්කාලීන පරීක්ෂාව සබල කර ඇත්නම් බීටා යෙදුම් අනුවාද ඇතුළත් වේ ඊතල අඳින්න සබල කර ඇත්නම් ඇඳීමේ මාර්ගය යොමු ඊතලයක් ලෙස නිරූපණය කෙරේ බුරුසු මෘදු බව පින්තූර ඇතුල් කළ ප්‍රමාණයට මැදට කපා ඇත. ඇතුළු කළ මානයන්ට වඩා රූපය කුඩා නම්, ලබා දී ඇති පසුබිම් වර්ණය සමඟ කැන්වසය පුළුල් කෙරේ. පරිත්යාග කිරීම රූප මැසීම එක් විශාල එකක් ලබා ගැනීමට ලබා දී ඇති පින්තූර ඒකාබද්ධ කරන්න අවම වශයෙන් පින්තූර 2ක්වත් තෝරන්න ප්රතිදාන රූප පරිමාණය රූප දිශානතිය තිරස් සිරස් අතට කුඩා පින්තූර විශාල කිරීමට පරිමාණය කරන්න සක්රිය කළහොත් කුඩා පින්තූර අනුපිළිවෙලෙහි විශාලතම එක දක්වා පරිමාණය කරනු ලැබේ පින්තූර අනුපිළිවෙල නිතිපතා දාර බොඳ කරන්න සබල කර ඇත්නම් තනි වර්ණයක් වෙනුවට අවට අවකාශය පිරවීමට මුල් රූපය යටතේ බොඳ වූ දාර අඳින්න පික්සලේෂන් වැඩි දියුණු කළ පික්සලේෂන් ආඝාත පික්සලේෂන් වැඩිදියුණු කළ දියමන්ති පික්සලේෂන් දියමන්ති පික්සලේෂන් Circle Pixelation වැඩිදියුණු කළ කව පික්සලේෂන් වර්ණය ප්රතිස්ථාපනය කරන්න ඉවසීම ප්‍රතිස්ථාපනය කිරීමට වර්ණය ඉලක්ක වර්ණය ඉවත් කිරීමට වර්ණය වර්ණය ඉවත් කරන්න Recode කරන්න පික්සල් ප්‍රමාණය අගුළු ඇඳීම දිශානතිය ඇඳීම් ආකාරයෙන් සබල කර ඇත්නම්, තිරය භ්‍රමණය නොවේ යාවත්කාලීන සඳහා පරීක්ෂා කරන්න පැලට් විලාසය ටෝනල් ස්පෝට් මධ්යස්ථ කම්පිත ප්රකාශිත දේදුනු පළතුරු සලාද විශ්වාසවන්තකම අන්තර්ගතය පෙරනිමි පැලට් විලාසය, එය වර්ණ හතරම අභිරුචිකරණය කිරීමට ඉඩ සලසයි, අනෙක් ඒවා ඔබට ප්‍රධාන වර්ණය පමණක් සැකසීමට ඉඩ දෙයි ඒකවර්ණයට වඩා තරමක් වර්ණවත් මෝස්තරයක් ඝෝෂාකාරී තේමාවක්, ප්‍රාථමික තලය සඳහා වර්ණවත් බව උපරිම වේ, අනෙක් අයට වැඩි වේ සෙල්ලක්කාර තේමාවක් - මූලාශ්‍ර වර්ණයෙහි පැහැය තේමාව තුළ දිස් නොවේ ඒකවර්ණ තේමාවක්, වර්ණ සම්පූර්ණයෙන්ම කළු / සුදු / අළු වේ Scheme.primaryContainer හි මූලාශ්‍ර වර්ණය ස්ථානගත කරන යෝජනා ක්‍රමයක් අන්තර්ගත යෝජනා ක්‍රමයට බෙහෙවින් සමාන යෝජනා ක්‍රමයක් මෙම යාවත්කාලීන පරීක්ෂකය GitHub වෙත සම්බන්ධ වන්නේ නව යාවත්කාලීනයක් තිබේ දැයි පරීක්ෂා කිරීම සඳහා ය අවධානය වියැකී යන දාර ආබාධිතයි දෙකම වර්ණ පෙරළන්න සබල කර ඇත්නම් තේමා වර්ණ සෘණ ඒවාට ප්‍රතිස්ථාපනය කරයි සොයන්න ප්‍රධාන තිරයේ ඇති සියලුම මෙවලම් හරහා සෙවීමේ හැකියාව සබල කරයි PDF මෙවලම් PDF ගොනු සමඟ ක්‍රියා කරන්න: පෙරදසුන්, රූප සමූහයට පරිවර්තනය කරන්න හෝ ලබා දී ඇති පින්තූරවලින් එකක් සාදන්න PDF පෙරදසුන් කරන්න පින්තූර වෙත PDF පින්තූර PDF වෙත සරල PDF පෙරදසුන ලබා දී ඇති ප්‍රතිදාන ආකෘතියෙන් පින්තූර PDF බවට පරිවර්තනය කරන්න ලබා දී ඇති පින්තූර PDF ගොනුවකට අසුරන්න මාස්ක් ෆිල්ටරය ලබා දී ඇති මාස්ක් ප්‍රදේශ මත පෙරහන් දාම යොදන්න, සෑම වෙස් මුහුණු ප්‍රදේශයකටම තමන්ගේම පෙරහන් කට්ටලයක් තීරණය කළ හැකිය වෙස් මුහුණු මාස්ක් එකතු කරන්න වෙස්මුහුණ %d මාස්ක් වර්ණය මාස්ක් පෙරදසුන ඔබට ආසන්න ප්‍රතිඵලය පෙන්වීමට ඇද ගන්නා ලද පෙරහන් වෙස් මුහුණ විදහා දක්වනු ඇත ප්රතිලෝම පිරවුම් වර්ගය සබල කර ඇත්නම්, පෙරනිමි හැසිරීම වෙනුවට වෙස්මුහුණු නොවන ප්‍රදේශ සියල්ලම පෙරීම සිදු කෙරේ ඔබ තෝරන ලද පෙරහන් ආවරණය මැකීමට සූදානම් වේ. මෙම මෙහෙයුම පසුගමනය කළ නොහැක වෙස් මුහුණ මකන්න සම්පූර්ණ පෙරහන ලබා දී ඇති පින්තූර හෝ තනි රූපයට ඕනෑම පෙරහන් දාමයක් යොදන්න ආරම්භ කරන්න මධ්යස්ථානය අවසානය සරල ප්රභේද උද්දීපනය කරන්නා නියොන් පෑන රහස්‍යතා බොඳවීම අර්ධ පාරදෘශ්‍ය තියුණු කරන ලද උද්දීපන මාර්ග අඳින්න ඔබේ චිත්‍රවලට දීප්තිමත් බලපෑමක් එක් කරන්න පෙරනිමි එක, සරලම - වර්ණය පමණි ඔබට සැඟවීමට අවශ්‍ය ඕනෑම දෙයක් සුරක්ෂිත කිරීමට ඇඳ ඇති මාර්ගය යටතේ රූපය බොඳ කරයි රහස්‍යතා බොඳ කිරීමට සමාන, නමුත් බොඳ කිරීම වෙනුවට පික්සලේට් බහාලුම් බහාලුම් පිටුපස සෙවනැල්ලක් අඳින්න ස්ලයිඩර් ස්විචයන් FABs බොත්තම් ස්ලයිඩර් පිටුපස සෙවනැල්ලක් අඳින්න ස්විචයන් පිටුපස සෙවනැල්ලක් අඳින්න පාවෙන ක්‍රියා බොත්තම් පිටුපස සෙවනැල්ලක් අඳින්න බොත්තම් පිටුපස සෙවනැල්ලක් අඳින්න යෙදුම් තීරු යෙදුම් තීරු පිටුපස සෙවනැල්ලක් අඳින්න %1$s - %2$s පරාසයේ අගය ස්වයංක්‍රීය කරකවන්න රූප දිශානතිය සඳහා සීමා පෙට්ටිය භාවිතා කිරීමට ඉඩ දෙයි මාර්ග මාදිලිය අඳින්න ද්විත්ව රේඛා ඊතලය නොමිලේ ඇඳීම ද්විත්ව ඊතලය රේඛා ඊතලය ඊතලය රේඛාව ආදාන අගය ලෙස මාර්ගය අඳින්න ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා මාර්ගය රේඛාවක් ලෙස අඳින්න ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා ඉරක් ලෙස යොමු කරන ඊතල අඳින්න දී ඇති මාර්ගයකින් යොමු ඊතලයක් අඳින්න රේඛාවක් ලෙස ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා ද්විත්ව යොමු ඊතලය අඳින්න දී ඇති මාර්ගයකින් ද්විත්ව යොමු ඊතලයක් අඳින්න දළ සටහන් ඕවල් දක්වා ඇති Rect ඕවලාකාර Rect ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා සෘජුව අඳින්න ආරම්භක ස්ථානයේ සිට අවසන් ස්ථානය දක්වා ඕවලාකාර අඳින්න ආරම්භක ස්ථානයේ සිට අවසාන ලක්ෂ්‍යය දක්වා ගෙනහැර දක්වන ලද ඕවලාකාර අඳින්න ආරම්භක ලක්ෂ්‍යයේ සිට අවසන් ස්ථානය දක්වා ගෙනහැර දක්වන ලද සෘජුකෝණාස්‍රය අඳින්න ලස්සෝ ලබා දී ඇති මාර්ගය අනුව වසා දැමූ පිරවූ මාර්ගය අඳින්න නොමිලේ තිරස් ජාලකය සිරස් ජාලකය මැහුම් මාදිලිය පේළි ගණන තීරු ගණන \"%1$s\" නාමාවලියක් හමු නොවීය, අපි එය පෙරනිමි එකට මාරු කළෙමු, කරුණාකර ගොනුව නැවත සුරකින්න පසුරු පුවරුව ස්වයංක්‍රීය පින් සබල කර ඇත්නම් ස්වයංක්‍රීයව සුරකින ලද රූපය පසුරු පුවරුවට එක් කරයි කම්පනය කම්පන ශක්තිය ගොනු උඩින් ලිවීම සඳහා ඔබට \"Explorer\" රූප මූලාශ්‍රය භාවිතා කිරීමට අවශ්‍ය වේ, පින්තූර නැවත කිරීමට උත්සාහ කරන්න, අපි අවශ්‍ය එක වෙත රූප මූලාශ්‍රය වෙනස් කර ඇත. ගොනු උඩින් ලියන්න තෝරාගත් ෆෝල්ඩරය තුළ සුරැකීම වෙනුවට මුල් ගොනුව අලුත් එකක් සමඟ ප්‍රතිස්ථාපනය වනු ඇත, මෙම විකල්පය රූප මූලාශ්‍රය \"Explorer\" හෝ GetContent විය යුතුය, මෙය ටොගල් කරන විට, එය ස්වයංක්‍රීයව සකසනු ඇත. හිස් උපසර්ගය පරිමාණ මාදිලිය බිලීනියර් කැට්මුල් බයිකුබික් ඔහු අසපුව ලැන්සෝස් මිචෙල් ආසන්නතම Spline මූලික පෙරනිමි අගය රේඛීය (හෝ ද්විපාර්ශ්වික, මාන දෙකකින්) මැදිහත්වීම රූපයේ ප්‍රමාණය වෙනස් කිරීම සඳහා සාමාන්‍යයෙන් හොඳ වේ, නමුත් සමහර අනවශ්‍ය ලෙස විස්තර මෘදු කිරීමට හේතු වන අතර එය තවමත් තරමක් හකුරු විය හැක. වඩා හොඳ පරිමාණ කිරීමේ ක්‍රමවලට Lanczos resampling සහ Mitchell-Netravali ෆිල්ටර් ඇතුළත් වේ සෑම පික්සලයක්ම එකම වර්ණයෙන් පික්සල ගණනාවක් සමඟ ප්‍රතිස්ථාපනය කරමින් ප්‍රමාණය වැඩි කිරීමේ සරල ක්‍රමවලින් එකකි සියලුම යෙදුම්වල පාහේ භාවිතා කරන සරලම ඇන්ඩ්‍රොයිඩ් පරිමාණ ප්‍රකාරය සුමට වක්‍ර නිර්මාණය කිරීම සඳහා පරිගණක ග්‍රැෆික්ස් වල බහුලව භාවිතා වන පාලන ලක්ෂ්‍ය කට්ටලයක් සුමට ලෙස අන්තර් සම්බන්ධ කිරීම සහ නැවත නියැදීම සඳහා ක්‍රමය වර්ණාවලි කාන්දු වීම අවම කිරීමට සහ සංඥාවක දාර පටිගත කිරීමෙන් සංඛ්‍යාත විශ්ලේෂණයේ නිරවද්‍යතාවය වැඩි දියුණු කිරීමට සංඥා සැකසීමේදී කවුළු ශ්‍රිතය බොහෝ විට යෙදේ. සුමට හා අඛණ්ඩ වක්‍රයක් උත්පාදනය කිරීම සඳහා වක්‍ර කොටසක අවසාන ලක්ෂ්‍යවල අගයන් සහ ව්‍යුත්පන්නයන් භාවිතා කරන ගණිතමය මැදිහත්වීමේ ක්‍රමය පික්සල් අගයන් වෙත බරිත සින්ක් ශ්‍රිතයක් යෙදීමෙන් උසස් තත්ත්වයේ අන්තර් ක්‍රියාකාරිත්වය පවත්වා ගෙන යන නැවත නියැදීමේ ක්‍රමය පරිමාණය කළ රූපයේ තියුණු බව සහ ප්‍රති-අන්වර්ථනය අතර සමතුලිතතාවයක් ලබා ගැනීම සඳහා වෙනස් කළ හැකි පරාමිති සහිත පෙරළීමේ පෙරහනක් භාවිතා කරන නැවත නියැදීමේ ක්‍රමය නම්‍යශීලී සහ අඛණ්ඩ හැඩ නිරූපණය සපයන වක්‍රයක් හෝ මතුපිටක් සුමට ලෙස අන්තර් සම්බන්ධ කිරීමට සහ ආසන්න කිරීමට කොටස් වශයෙන්-නිර්වචනය කරන ලද බහුපද ශ්‍රිත භාවිත කරයි. Clip පමණයි ආචයනය වෙත සුරැකීම සිදු නොකරන අතර, රූපය පසුරු පුවරුවට පමණක් තැබීමට උත්සාහ කරනු ඇත බුරුසුව මැකීම වෙනුවට පසුබිම ප්‍රතිසාධනය කරයි OCR (පෙළ හඳුනා ගන්න) ලබා දී ඇති රූපයෙන් පෙළ හඳුනා ගන්න, භාෂා 120+ සහය දක්වයි පින්තූරයේ පෙළක් නැත, නැතහොත් යෙදුම එය සොයා ගත්තේ නැත \"නිරවද්‍යතාව: %1$s\" හඳුනාගැනීමේ වර්ගය වේගවත් සම්මතය හොඳම දත්ත නැත Tesseract OCR හි නිසි ක්‍රියාකාරීත්වය සඳහා අමතර පුහුණු දත්ත (%1$s) ඔබගේ උපාංගයට බාගැනීමට අවශ්‍ය වේ.\nඔබට %2$s දත්ත බාගැනීමට අවශ්‍යද? බාගන්න සම්බන්ධතාවයක් නැත, එය පරීක්ෂා කර දුම්රිය ආකෘති බාගැනීම සඳහා නැවත උත්සාහ කරන්න බාගත කළ භාෂා පවතින භාෂා ඛණ්ඩන මාදිලිය Pixel Switch භාවිතා කරන්න Google Pixel වැනි ස්විචයක් භාවිතා කරයි මුල් ගමනාන්තයේ %1$s නම සහිත උඩින් ලියන ලද ගොනුව විශාලනය වඩා හොඳ ප්‍රවේශ්‍යතාවක් සඳහා ඇඳීම් ක්‍රමවල ඇඟිල්ලේ ඉහළින් ඇති විශාලනය සබල කරයි ආරම්භක අගය බල කරන්න exif widget මුලදී පරීක්ෂා කිරීමට බල කරයි බහු භාෂාවලට ඉඩ දෙන්න ස්ලයිඩය පසෙකින් ටොගල් ටැප් විනිවිදභාවය යෙදුම අගයන්න අගය කරන්න මෙම යෙදුම සම්පූර්ණයෙන්ම නොමිලේ, ඔබට එය විශාල වීමට අවශ්‍ය නම්, කරුණාකර Github හි ව්‍යාපෘතිය තරු කරන්න 😄 දිශානතිය සහ ස්ක්‍රිප්ට් අනාවරණය පමණි ස්වයංක්‍රීය දිශානතිය සහ ස්ක්‍රිප්ට් හඳුනාගැනීම ඔටෝ විතරයි ඔටෝ තනි තීරුව තනි වාරණ සිරස් පෙළ තනි බ්ලොක් තනි රේඛාව තනි වචනයක් කව වචනය තනි අක්ෂරය විරල පෙළ විරල පෙළ දිශානතිය සහ ස්ක්‍රිප්ට් හඳුනාගැනීම අමු රේඛාව ඔබට භාෂා \"%1$s\" OCR පුහුණු දත්ත සියලු හඳුනාගැනීම් වර්ග සඳහා මැකීමට අවශ්‍යද, නැතහොත් තෝරාගත් එකක් සඳහා පමණක් (%2$s)? වත්මන් සියල්ල Gradient Maker අභිරුචිකරණය කළ වර්ණ සහ පෙනුම වර්ගය සමඟ ලබා දී ඇති ප්‍රතිදාන ප්‍රමාණයේ අනුක්‍රමණය සාදන්න රේඛීය රේඩියල් අතුගාන්න Gradient වර්ගය මධ්‍යස්ථානය X මධ්යස්ථානය Y ටයිල් මාදිලිය නැවත නැවතත් කැඩපත කලම්ප Decal වර්ණ නැවතුම් වර්ණ එකතු කරන්න දේපල දීප්තිය බලාත්මක කිරීම තිරය අනුක්‍රමික ආවරණයක් ලබා දී ඇති පින්තූරවල මුදුනේ ඕනෑම අනුක්‍රමයක් සම්පාදනය කරන්න පරිවර්තනයන් කැමරාව කැමරාවකින් පින්තූරයක් ගන්න. මෙම රූප මූලාශ්‍රයෙන් ලබා ගත හැක්කේ එක් රූපයක් පමණක් බව සලකන්න ජල සලකුණු කිරීම අභිරුචිකරණය කළ හැකි පෙළ/රූප ජල සලකුණු සහිත පින්තූර ආවරණය කරන්න දිය සලකුණ නැවත කරන්න ලබා දී ඇති ස්ථානයේ තනි වෙනුවට රූපය මත ජල සලකුණ නැවත නැවත සිදු කරයි ඕෆ්සෙට් X ඕෆ්සෙට් වයි ජල සලකුණු වර්ගය මෙම රූපය ජල සලකුණු සඳහා රටාවක් ලෙස භාවිතා කරනු ඇත පෙළ වර්ණය උඩැතිරි මාදිලිය GIF මෙවලම් පින්තූර GIF පින්තූරයට පරිවර්තනය කරන්න හෝ ලබා දී ඇති GIF රූපයෙන් රාමු උපුටා ගන්න පින්තූර සඳහා GIF GIF ගොනුව පින්තූර සමූහයකට පරිවර්තනය කරන්න පින්තූර සමූහය GIF ගොනුවකට පරිවර්තනය කරන්න පින්තූර GIF වෙත ආරම්භ කිරීමට GIF රූපය තෝරන්න පළමු රාමුවේ විශාලත්වය භාවිතා කරන්න නිශ්චිත ප්‍රමාණය පළමු රාමු මානයන් සමඟ ප්‍රතිස්ථාපනය කරන්න නැවත නැවත ගණනය කරන්න රාමු ප්රමාදය මිලි FPS Lasso භාවිතා කරන්න මැකීම සිදු කිරීම සඳහා ඇඳීම් මාදිලියේ මෙන් Lasso භාවිතා කරයි මුල් රූප පෙරදසුන ඇල්ෆා කොන්ෆෙට්ටි කොන්ෆෙට්ටි සුරැකීම, බෙදාගැනීම සහ අනෙකුත් මූලික ක්‍රියා මත පෙන්වනු ඇත ආරක්ෂිත මාදිලිය මෑත යෙදුම්වල යෙදුම් අන්තර්ගතය සඟවයි. එය අල්ලා ගැනීමට හෝ පටිගත කිරීමට නොහැකිය. පිටවෙන්න ඔබ දැන් පෙරදසුන හැර ගියහොත්, ඔබට නැවත පින්තූර එක් කිරීමට අවශ්‍ය වනු ඇත ඩිදරින් Quantizier අළු පරිමාණය බේයර් ටූ බයි ටූ ඩිදරින් Bayer Three By Three Dithering Bayer Four By Four Dithering බේයර් එයිට් බයි එයිට් ඩිදරින් Floyd Steinberg Dithering Jarvis විනිසුරු Ninke Dithering Sierra Dithering පේළි දෙකක් Sierra Dithering Sierra Lite Dithering ඇට්කින්සන් ඩිතෙරින් Stucki Dithering බර්ක්ස් ඩිදරින් බොරු Floyd Steinberg Dithering වමේ සිට දකුණට දික්කසාද වීම අහඹු ඩයිදරින් සරල ත්‍රෙෂෝල්ඩ් ඩිදරින් සිග්මා අවකාශීය සිග්මා මධ්යන්ය බොඳවීම බී ස්ප්ලයින් වක්‍රයක් හෝ මතුපිටක්, නම්‍යශීලී සහ අඛණ්ඩ හැඩ නිරූපණයක් සුමට ලෙස අන්තර් සම්බන්ධ කිරීමට සහ ආසන්න කිරීමට කොටස් වශයෙන්-නිර්වචනය කරන ලද ද්විපද බහුපද ශ්‍රිත භාවිතා කරයි. දේශීය තොග බොඳවීම ටිල්ට් ෂිෆ්ට් දෝෂය මුදල බීජ ඇනග්ලිෆ් ශබ්දය පික්සල් අනුපිළිවෙල කලවම් කරන්න වැඩි දියුණු කළ දෝෂය චැනල් Shift X චැනල් Shift Y දූෂණ ප්රමාණය දූෂණ මාරුව X දූෂණ මාරුව වයි කූඩාරම් බොඳවීම සයිඩ් ෆේඩ් පැත්ත ඉහළ පහළ ශක්තිය ඊරෝඩ් ඇනිසොට්‍රොපික් විසරණය විසරණය සන්නයනය තිරස් සුළං ස්ටැගර් වේගවත් ද්විපාර්ශ්වික බොඳවීම Poisson Blur ලඝුගණක නාද සිතියම්කරණය ACES Filmic Tone Mapping ස්ඵටික කරන්න ආඝාත වර්ණය ෆ්රැක්ටල් වීදුරු විස්තාරය කිරිගරුඬ කැළඹීම තෙල් ජල බලපෑම ප්රමාණය සංඛ්යාත X සංඛ්යාත Y විස්තාරය X විස්තාරය Y පර්ලින් විකෘති කිරීම ACES හිල් ටෝන් සිතියම්කරණය Hable Filmic Tone Mapping Heji-Burgess Tone සිතියම්ගත කිරීම වේගය Dehaze ඔමේගා වර්ණ Matrix 4x4 වර්ණ Matrix 3x3 සරල බලපෑම් Polaroid ට්රයිටනොමලි ඩියුටරනොමාලි Protanomaly වින්ටේජ් බ්රවුන්ගේ කෝඩා ක්‍රෝම් රාත්රී දර්ශනය උණුසුම් සිසිල් ට්රයිටනෝපියාව ඩියුටරොනොටෝපියාව Protanopia වර්ණදේහ ඇක්රොමැටොප්සියාව ධාන්ය තියුණු කරන්න පැස්ටල් තැඹිලි හේස් රෝස සිහිනය ස්වර්ණමය හෝරාව උණුසුම් ගිම්හානය දම් පාට මීදුම හිරු උදාව වර්ණවත් සුළිය මෘදු වසන්ත ආලෝකය සරත් සෘතුවේ නාද ලැවෙන්ඩර් සිහිනය සයිබර්පන්ක් ලෙමනේඩ් ආලෝකය වර්ණාවලි ගින්න රාත්රී මැජික් ෆැන්ටසි භූ දර්ශනය වර්ණ පිපිරීම විදුලි අනුක්‍රමය කැරමල් අන්ධකාරය අනාගත අනුක්‍රමණය හරිත හිරු දේදුනු ලෝකය තද දම් පාට අභ්යවකාශ ද්වාරය රතු සුළිය ඩිජිටල් කේතය බොකේ යෙදුම් තීරු ඉමොජි අහඹු ලෙස වෙනස් වනු ඇත අහඹු ඉමෝජි ඉමෝජි අබල කර ඇති අතර ඔබට අහඹු ඉමෝජි භාවිතා කළ නොහැක අහඹු ඉමෝජි සබල කර ඇති අතර ඔබට ඉමොජියක් තෝරාගත නොහැක පැරණි රූපවාහිනිය බොඳ කිරීම කලවම් කරන්න ප්රියතම ප්‍රියතම පෙරහන් තවම එක් කර නැත රූප ආකෘතිය අයිකන යටතේ තෝරාගත් හැඩය සහිත කන්ටේනරයක් එක් කරයි අයිකන හැඩය ඩ්රැගෝ ඕල්ඩ්රිජ් විසන්ධි කරනවා ඔබ අවදි වන්න මොබියස් සංක්රමණය උච්ච වර්ණ විෂමතාව මුල් ගමනාන්තයේ උඩින් ලියන ලද පින්තූර ගොනු උඩින් ලිවීමේ විකල්පය සක්‍රීය කර ඇති අතරතුර රූප ආකෘතිය වෙනස් කළ නොහැක Emoji වර්ණ පටිපාටියක් ලෙස අතින් නිර්වචනය කරන ලද එකක් වෙනුවට යෙදුම් වර්ණ පටිපාටියක් ලෙස ඉමොජි ප්‍රාථමික වර්ණය භාවිත කරයි රූපයේ සිට Material You palette නිර්මාණය කරයි අඳුරු වර්ණ ආලෝක ප්‍රභේදය වෙනුවට රාත්‍රී මාදිලියේ වර්ණ පටිපාටිය භාවිතා කරයි Jetpack Compose code ලෙස පිටපත් කරන්න මුදු නොපැහැදිලි හරස් බොඳවීම කවය බොඳවීම තරු බොඳවීම රේඛීය ඇල-මාරුව ඉවත් කිරීමට ටැග් APNG මෙවලම් පින්තූර APNG පින්තූරයට පරිවර්තනය කරන්න හෝ ලබා දී ඇති APNG රූපයෙන් රාමු උපුටා ගන්න පින්තූර සඳහා APNG APNG ගොනුව පින්තූර සමූහයකට පරිවර්තනය කරන්න පින්තූර සමූහය APNG ගොනුවකට පරිවර්තනය කරන්න පින්තූර APNG වෙත ආරම්භ කිරීමට APNG රූපය තෝරන්න චලන බොඳවීම Zip ලබා දී ඇති ගොනු හෝ පින්තූර වලින් Zip ගොනුවක් සාදන්න හැන්ඩ්ල් පළල අදින්න කොන්ෆෙට්ටි වර්ගය උත්සව පුපුරන්න වැස්ස කොන් JXL මෙවලම් ගුණාත්මක අලාභයකින් තොරව JXL ~ JPEG ට්‍රාන්ස්කෝඩින් සිදු කරන්න, නැතහොත් GIF/APNG JXL සජීවිකරණයට පරිවර්තනය කරන්න JXL සිට JPEG දක්වා JXL සිට JPEG දක්වා පාඩු රහිත ට්‍රාන්ස්කෝඩින් සිදු කරන්න JPEG සිට JXL දක්වා පාඩු රහිත ට්‍රාන්ස්කෝඩින් සිදු කරන්න JPEG සිට JXL ආරම්භ කිරීමට JXL රූපය තෝරන්න Fast Gaussian Blur 2D Fast Gaussian Blur 3D Fast Gaussian Blur 4D කාර් පාස්කු ක්ලිප්බෝඩ් දත්ත ස්වයංක්‍රීයව ඇලවීමට යෙදුමට අවසර දෙන්න, එබැවින් එය ප්‍රධාන තිරයේ දිස්වන අතර ඔබට එය සැකසීමට හැකි වනු ඇත සමීකරණ වර්ණය එකඟතා මට්ටම Lanczos Bessel පික්සල් අගයන් සඳහා Bessel (jinc) ශ්‍රිතයක් යෙදීමෙන් උසස් තත්ත්වයේ අන්තර් ක්‍රියාකාරිත්වය පවත්වා ගෙන යන නැවත නියැදීමේ ක්‍රමය GIF සිට JXL GIF පින්තූර JXL සජීවිකරණ පින්තූර බවට පරිවර්තනය කරන්න APNG සිට JXL දක්වා APNG පින්තූර JXL සජීවිකරණ පින්තූර බවට පරිවර්තනය කරන්න JXL සිට පින්තූර දක්වා JXL සජීවිකරණය පින්තූර සමූහයකට පරිවර්තනය කරන්න JXL වෙත පින්තූර පින්තූර සමූහය JXL සජීවිකරණයට පරිවර්තනය කරන්න හැසිරීම ගොනු තේරීම මඟ හරින්න තෝරන ලද තිරය මත මෙය හැකි නම් ගොනු පිකර් වහාම පෙන්වනු ඇත පෙරදසුන් උත්පාදනය කරන්න පෙරදසුන් උත්පාදනය සක්‍රීය කරයි, මෙය සමහර උපාංගවල බිඳ වැටීම් වලක්වා ගැනීමට උදවු විය හැක, මෙය තනි සංස්කරණ විකල්පය තුළ සමහර සංස්කරණ ක්‍රියාකාරකම් ද අබල කරයි ලොසි සම්පීඩනය ලොස්ලස් වෙනුවට ගොනු ප්‍රමාණය අඩු කිරීමට පාඩු සහිත සම්පීඩනය භාවිතා කරයි සම්පීඩන වර්ගය ප්‍රතිඵලයක් ලෙස ලැබෙන රූප විකේතන වේගය පාලනය කරයි, මෙය ප්‍රතිඵලයක් ලෙස ලැබෙන රූපය ඉක්මනින් විවෘත කිරීමට උදවු විය යුතුය, %1$s හි අගය යනු මන්දගාමී විකේතනය වේ, නමුත් %2$s - වේගවත්ම, මෙම සැකසීම ප්‍රතිදාන රූප ප්‍රමාණය වැඩි කළ හැක වර්ග කිරීම දිනය දිනය (ප්‍රතිලෝම) නම නම (ප්‍රතිලෝම) නාලිකා වින්‍යාසය අද ඊයේ Embedded Picker රූප මෙවලම් පෙට්ටියේ රූප පිකර් අවසර නැත ඉල්ලීම බහු මාධ්‍ය තෝරන්න තනි මාධ්‍ය තෝරන්න තෝරාගන්න නැවත උත්සාහ කරන්න භූ දර්ශනය තුළ සැකසුම් පෙන්වන්න මෙය අක්‍රිය කර ඇත්නම්, ස්ථිර දෘශ්‍ය විකල්පය වෙනුවට, භූ දර්ශන ප්‍රකාරයේ සැකසීම් සෑම විටම ඉහළ යෙදුම් තීරුවේ බොත්තම මත විවෘත වනු ඇත. සම්පූර්ණ තිර සැකසුම් එය සක්‍රිය කරන්න, ස්ලයිඩ කළ හැකි ලාච්චු පත්‍රය වෙනුවට සැකසීම් පිටුව සැමවිටම පූර්ණ තිරය ලෙස විවෘත වේ ස්විච් වර්ගය රචනා කරන්න ඔබ මාරු කරන Jetpack Compose Material එකක් ඔබ මාරු කරන ද්‍රව්‍යයක් උපරිම නැංගුරම ප්‍රමාණය වෙනස් කරන්න පික්සල චතුර \"Fluent\" සැලසුම් පද්ධතිය මත පදනම් වූ ස්විචයක් කුපර්ටිනෝ \"Cupertino\" සැලසුම් පද්ධතිය මත පදනම් වූ ස්විචයක් SVG වෙත පින්තූර ලබා දී ඇති පින්තූර SVG පින්තූර වෙත ලුහුබඳින්න නියැදි පැලට් භාවිතා කරන්න මෙම විකල්පය සක්‍රීය කර ඇත්නම් ප්‍රමාණකරණ තලය සාම්පල කරනු ලැබේ මග හැරිය විශාල රූප අඩු කිරීමකින් තොරව ලුහුබැඳීම සඳහා මෙම මෙවලම භාවිතා කිරීම නිර්දේශ නොකරයි, එය බිඳ වැටීමට සහ සැකසුම් කාලය වැඩි කිරීමට හේතු විය හැක. පහත් පරිමාණ රූපය සැකසීමට පෙර රූපය අඩු මානයන් දක්වා පහත හෙලනු ඇත, මෙය මෙවලම වේගයෙන් සහ ආරක්ෂිතව වැඩ කිරීමට උපකාරී වේ අවම වර්ණ අනුපාතය රේඛා එළිපත්ත චතුරස්රාකාර සීමාව වටකුරු ඉවසීම සම්බන්ධීකරණය කරයි මාර්ග පරිමාණය ගුණාංග යළි පිහිටුවන්න සියලුම ගුණාංග පෙරනිමි අගයන් වෙත සකසනු ඇත, මෙම ක්‍රියාව පසුගමනය කළ නොහැකි බව සලකන්න සවිස්තරාත්මක පෙරනිමි රේඛා පළල එන්ජින් මාදිලිය උරුමය LSTM ජාලය උරුමය සහ LSTM පරිවර්තනය කරන්න රූප කාණ්ඩ ලබා දී ඇති ආකෘතියට පරිවර්තනය කරන්න නව ෆෝල්ඩරය එක් කරන්න නියැදියකට බිටු සම්පීඩනය ඡායාරූපමිතික අර්ථ නිරූපණය පික්සලයකට සාම්පල ප්ලැනර් වින්යාසය Y Cb Cr උප නියැදීම Y Cb Cr ස්ථානගත කිරීම X විභේදනය Y විභේදනය විභේදන ඒකකය තීරු ඕෆ්සෙට් තීරුවකට පේළි තීරු බයිට් ගණන JPEG අන්තර් හුවමාරු ආකෘතිය JPEG අන්තර් හුවමාරු ආකෘතියේ දිග මාරු කිරීමේ කාර්යය වයිට් පොයින්ට් ප්‍රාථමික වර්ණදේහ Y Cb Cr සංගුණක යොමු කළු සුදු දිනය වේලාව රූප විස්තරය හදන්න ආකෘතිය මෘදුකාංග කලාකරුවා ප්‍රකාශන හිමිකම Exif අනුවාදය ෆ්ලෑෂ්පික්ස් අනුවාදය වර්ණ අවකාශය ගැමා Pixel X Dimension පික්සල් Y මානය පික්සලයකට සම්පීඩිත බිටු සාදන්නා සටහන පරිශීලක අදහස් අදාළ ශබ්ද ගොනුව දිනය වේලාව මුල් දිනය වේලාව ඩිජිටල්කරණය ඕෆ්සෙට් කාලය ඕෆ්සෙට් වේලාව ඔරිජිනල් ඕෆ්සෙට් කාලය ඩිජිටල්කරණය උප තත්පර කාලය උප තත්පර කාලය මුල් පිටපත උප තත්පර කාලය ඩිජිටල්කරණය නිරාවරණ කාලය එෆ් අංකය නිරාවරණ වැඩසටහන වර්ණාවලි සංවේදීතාව ඡායාරූප සංවේදීතාව OECF සංවේදීතා වර්ගය සම්මත ප්රතිදාන සංවේදීතාව නිර්දේශිත නිරාවරණ දර්ශකය ISO වේගය ISO Speed ​​Latitude yyy ISO ස්පීඩ් අක්ෂාංශ zzz ෂටර වේග අගය විවරය අගය දීප්තියේ අගය නිරාවරණ පක්ෂග්‍රාහී අගය උපරිම විවරය අගය විෂය දුර මිනුම් මාදිලිය ෆ්ලෑෂ් විෂය ප්රදේශය නාභි දුර ෆ්ලෑෂ් බලශක්ති අවකාශීය සංඛ්යාත ප්රතිචාරය නාභීය තලය X විභේදනය නාභීය තලය Y විභේදනය නාභීය තල විභේදන ඒකකය විෂය ස්ථානය නිරාවරණ දර්ශකය සංවේදන ක්‍රමය ගොනු මූලාශ්රය CFA රටාව අභිරුචි විදැහුම්කරණය නිරාවරණ මාදිලිය සුදු ශේෂය ඩිජිටල් විශාලන අනුපාතය නාභීය දිග 35mm ෆිල්ම් දර්ශන ග්‍රහණ වර්ගය පාලනය ලබාගන්න පරස්පරතාව සන්තෘප්තිය තියුණු බව උපාංග සැකසුම් විස්තරය විෂය දුර පරාසය රූපයේ අනන්‍ය ID කැමරා හිමිකරුගේ නම ශරීර අනුක්‍රමික අංකය කාච පිරිවිතර Lens Make කාච ආකෘතිය කාච අනුක්‍රමික අංකය GPS අනුවාද ID GPS Latitude Ref GPS අක්ෂාංශ GPS දේශාංශ Ref GPS දේශාංශ GPS උන්නතාංශය Ref GPS උන්නතාංශය GPS කාල මුද්දරය GPS චන්ද්‍රිකා GPS තත්ත්වය GPS මිනුම් මාදිලිය GPS DOP GPS වේගය Ref GPS වේගය GPS Track Ref GPS ධාවන පථය GPS Img දිශාව Ref GPS Img දිශාව GPS සිතියම් දත්ත GPS Dest Latitude Ref GPS ඩෙස්ට් අක්ෂාංශ GPS ඩෙස්ට් දේශාංශ Ref GPS ඩෙස්ට් දේශාංශ GPS Dest Bearing Ref GPS ඩෙස්ට් බෙයාරිං GPS Dest Distance Ref GPS Dest දුර GPS සැකසුම් ක්‍රමය GPS ප්‍රදේශයේ තොරතුරු GPS දින මුද්දරය GPS අවකලනය GPS H ස්ථානගත කිරීමේ දෝෂය අන්තර් ක්රියාකාරීත්ව දර්ශකය DNG අනුවාදය පෙරනිමි බෝග ප්‍රමාණය පෙරදසුන් රූප ආරම්භය රූපයේ දිග පෙරදසුන් කරන්න දර්ශන රාමුව සංවේදකය පහළ මායිම සංවේදක වම් මායිම සංවේදක දකුණු මායිම සංවේදකය ඉහළ මායිම ISO ලබා දී ඇති අකුරු සහ වර්ණය සමඟ මාර්ගයේ පෙළ අඳින්න අකුරු ප්රමාණය ජල සලකුණු ප්‍රමාණය නැවත නැවත පෙළ එක් වරක් ඇඳීම වෙනුවට මාර්ගය අවසන් වන තෙක් වත්මන් පාඨය පුනරාවර්තනය වේ ඩෑෂ් ප්‍රමාණය දී ඇති මාර්ගය ඔස්සේ එය ඇඳීමට තෝරාගත් රූපය භාවිතා කරන්න මෙම රූපය අඳින ලද මාර්ගයේ පුනරාවර්තන ඇතුළත් කිරීමක් ලෙස භාවිතා කරනු ඇත ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා ගෙනහැර දක්වන ලද ත්‍රිකෝණයක් අඳින්න ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා ගෙනහැර දක්වන ලද ත්‍රිකෝණයක් අඳින්න ලුහුඬු ත්‍රිකෝණය ත්රිකෝණය ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා බහුඅස්‍රය ඇද දමයි බහුඅස්රය ගෙනහැර දක්වන ලද බහුඅස්රය ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා ගෙනහැර දක්වන ලද බහුඅස්‍ර අඳින්න සිරස් නිතිපතා බහුඅස්රය අඳින්න නිදහස් ආකෘතිය වෙනුවට සාමාන්‍ය බහුඅස්‍රය අඳින්න ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා තරු අඳිනවා තරුව ලුහුඬු තරුව ආරම්භක ලක්ෂ්‍යයේ සිට අවසාන ලක්ෂ්‍යය දක්වා ගෙනහැර දක්වන ලද තරුව අඳියි අභ්යන්තර අරය අනුපාතය සාමාන්‍ය තරුව අඳින්න නිදහස් පෝරමය වෙනුවට නිත්‍ය වන තරුව අඳින්න Antialias තියුණු දාර වලක්වා ගැනීමට antialiasing සක්රීය කරයි පෙරදසුන වෙනුවට සංස්කරණය විවෘත කරන්න ImageToolbox තුළ ඔබ රූපය විවෘත කිරීමට (පෙරදසුන) තේරූ විට, පෙරදසුන් වෙනුවට සංස්කරණ තේරීම් පත්‍රය විවෘත වේ ලේඛන ස්කෑනරය ලේඛන පරිලෝකනය කර PDF සාදන්න හෝ ඒවායින් වෙනම පින්තූර සාදන්න ස්කෑන් කිරීම ආරම්භ කිරීමට ක්ලික් කරන්න ස්කෑන් කිරීම ආරම්භ කරන්න Pdf ලෙස සුරකින්න Pdf ලෙස බෙදා ගන්න පහත විකල්ප PDF නොව පින්තූර සුරැකීම සඳහා වේ Histogram HSV සමාන කරන්න හිස්ටෝග්‍රෑම් සමාන කරන්න ප්‍රතිශතය ඇතුලත් කරන්න Text Field මගින් ඇතුල් වීමට ඉඩ දෙන්න පෙරසිටුවීම් තේරීම පිටුපස පෙළ ක්ෂේත්‍රය, පියාසර කරන විට ඒවා ඇතුළු කිරීමට සබල කරයි වර්ණ අවකාශය පරිමාණය කරන්න රේඛීය හිස්ටෝග්‍රෑම් පික්සලේෂන් සමාන කරන්න ජාලක ප්‍රමාණය X ජාල ප්‍රමාණය Y හිස්ටෝග්‍රෑම් අනුවර්තනය සමාන කරන්න Histogram Adaptive LUV සමාන කරන්න Histogram Adaptive LAB සමාන කරන්න CLAHE CLAHE රසායනාගාරය CLAHE LUV අන්තර්ගතයට කප්පාදු කරන්න රාමු වර්ණය නොසලකා හැරීමට වර්ණය සැකිල්ල අච්චු පෙරහන් එකතු කර නැත නව නිර්මාණය කරන්න ස්කෑන් කරන ලද QR කේතය වලංගු පෙරහන් අච්චුවක් නොවේ QR කේතය පරිලෝකනය කරන්න තෝරාගත් ගොනුවේ පෙරහන් අච්චු දත්ත නොමැත සැකිල්ල සාදන්න සැකිල්ල නම මෙම පෙරහන් අච්චුව පෙරදසුන් කිරීමට මෙම රූපය භාවිතා කරනු ඇත සැකිලි පෙරහන QR කේත රූපයක් ලෙස ගොනුවක් ලෙස ගොනුවක් ලෙස සුරකින්න QR කේත රූපයක් ලෙස සුරකින්න අච්චුව මකන්න ඔබ තෝරාගත් අච්චු පෙරහන මැකීමට සූදානම් වේ. මෙම මෙහෙයුම පසුගමනය කළ නොහැක \"%1$s\" (%2$s) නම සහිත පෙරහන් අච්චුව එක් කරන ලදී පෙරහන් පෙරදසුන QR සහ තීරු කේතය QR කේතය පරිලෝකනය කර එහි අන්තර්ගතය ලබා ගන්න හෝ අලුත් එකක් ජනනය කිරීමට ඔබේ තන්තුව අලවන්න කේත අන්තර්ගතය ක්ෂේත්‍රයේ අන්තර්ගතය ප්‍රතිස්ථාපනය කිරීමට ඕනෑම තීරු කේතයක් පරිලෝකනය කරන්න, නැතහොත් තෝරාගත් වර්ගය සමඟ නව තීරු කේතය ජනනය කිරීමට යමක් ටයිප් කරන්න QR විස්තරය අවම QR කේතය පරිලෝකනය කිරීමට සැකසීම් තුළ කැමරා අවසරය ලබා දෙන්න ලේඛන ස්කෑනරය පරිලෝකනය කිරීමට සැකසීම් තුළ කැමරා අවසරය ලබා දෙන්න ඝනක B-ස්ප්ලයින් හම්මිං හැනිං බ්ලැක්මන් වෙල්ච් හතරැස් ගවුසියන් ස්පින්ක්ස් බාර්ට්ලට් රොබිඩොක්ස් රොබිඩොක්ස් ෂාප් Spline 16 ස්ප්ලයින් 36 ස්ප්ලයින් 64 කයිසර් බාර්ට්ලට්-ඔහු පෙට්ටිය බෝමන් ලැන්සෝස් 2 ලැන්සෝස් 3 ලැන්සෝස් 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc cubic interpolation bilinear වලට වඩා හොඳ ප්‍රතිඵල ලබා දෙමින් ආසන්නතම පික්සල 16 සලකා බැලීමෙන් සුමට පරිමාණයක් සපයයි. වක්‍රයක් හෝ මතුපිටක්, නම්‍යශීලී සහ අඛණ්ඩ හැඩ නිරූපණයක් සුමට ලෙස අන්තර් සම්බන්ධ කිරීමට සහ ආසන්න කිරීමට කොටස් වශයෙන්-නිර්වචනය කරන ලද බහුපද ශ්‍රිත භාවිතා කරයි. සංඥා සැකසීමේදී ප්‍රයෝජනවත් වන සංඥාවක දාර පටිගත කිරීමෙන් වර්ණාවලි කාන්දු වීම අඩු කිරීමට භාවිතා කරන කවුළු ශ්‍රිතයකි හෑන් කවුළුවේ ප්‍රභේදයක්, සංඥා සැකසුම් යෙදුම්වල වර්ණාවලි කාන්දු වීම අඩු කිරීමට බහුලව භාවිතා වේ බොහෝ විට සංඥා සැකසීමේදී භාවිතා කරන වර්ණාවලි කාන්දු වීම අවම කිරීම මගින් හොඳ සංඛ්‍යාත විභේදනයක් සපයන කවුළු ශ්‍රිතයක් අඩු වර්ණාවලි කාන්දුවක් සමඟ හොඳ සංඛ්‍යාත විභේදනයක් ලබා දීමට නිර්මාණය කර ඇති කවුළු ශ්‍රිතයක්, බොහෝ විට සංඥා සැකසුම් යෙදුම්වල භාවිතා වේ සුමට හා අඛණ්ඩ ප්‍රතිඵල ලබා දෙමින් අන්තර් ක්‍රියාකාරිත්වය සඳහා චතුරස්‍ර ශ්‍රිතයක් භාවිතා කරන ක්‍රමයකි රූපවල ඝෝෂාව සුමට කිරීමට සහ අඩු කිරීමට ප්‍රයෝජනවත් වන Gaussian ශ්‍රිතයක් යොදන අන්තර් බන්ධන ක්‍රමයක් අවම කෞතුක වස්තු සහිත උසස් තත්ත්වයේ අන්තර් බන්ධනය සපයන උසස් නැවත නියැදීමේ ක්‍රමයක් වර්ණාවලි කාන්දු වීම අවම කිරීම සඳහා සංඥා සැකසීමේදී භාවිතා කරන ත්‍රිකෝණාකාර කවුළු ශ්‍රිතයක් ස්වභාවික රූපයේ ප්‍රතිප්‍රමාණය වෙනස් කිරීම, තියුණු බව සහ සුමට බව සමතුලිත කිරීම සඳහා ප්‍රශස්ත කරන ලද උසස් තත්ත්වයේ අන්තර් ක්‍රියා ක්‍රමයක් Robidoux ක්‍රමයේ තියුණු ප්‍රභේදයක්, හැපෙනසුළු රූපය ප්‍රතිප්‍රමාණ කිරීම සඳහා ප්‍රශස්ත කර ඇත 16-ටැප් ෆිල්ටරයක් ​​භාවිතයෙන් සුමට ප්‍රතිඵල ලබා දෙන spline-පාදක අන්තර් සම්බන්ධීකරණ ක්‍රමයක් 36-ටැප් ෆිල්ටරයක් ​​භාවිතයෙන් සුමට ප්‍රතිඵල ලබා දෙන ස්ප්ලයින්-පදනම් ඉන්ටර්පෝලේෂන් ක්‍රමයක් 64-ටැප් ෆිල්ටරයක් ​​භාවිතයෙන් සුමට ප්‍රතිඵල ලබා දෙන ස්ප්ලයින්-පදනම් ඉන්ටර්පෝලේෂන් ක්‍රමයක් ප්‍රධාන-ලොබ් පළල සහ පැති-ලොබ් මට්ටම අතර වෙළඳාම පිළිබඳ හොඳ පාලනයක් සපයන, කයිසර් කවුළුව භාවිතා කරන අන්තර් ක්‍රියා ක්‍රමයක් සංඥා සැකසීමේදී වර්ණාවලි කාන්දු වීම අවම කිරීම සඳහා බාට්ලට් සහ හැන් කවුළු ඒකාබද්ධ කරන දෙමුහුන් කවුළු ශ්‍රිතයක් බොහෝ විට අවහිර පෙනුමක් ඇති කරන, ආසන්නතම පික්සල් අගයන්හි සාමාන්‍යය භාවිතා කරන සරල නැවත නියැදීමේ ක්‍රමයක් සංඥා සැකසුම් යෙදුම්වල හොඳ සංඛ්‍යාත විභේදනය සපයන, වර්ණාවලි කාන්දු වීම අඩු කිරීමට භාවිතා කරන කවුළු ශ්‍රිතයක් අවම කෞතුක වස්තු සහිත උසස් තත්ත්වයේ අන්තර් සම්බන්ධනය සඳහා 2-lobe Lanczos පෙරහන භාවිතා කරන නැවත නියැදීමේ ක්‍රමයක් අවම කෞතුක වස්තු සහිත උසස්-ගුණාත්මක මැදිහත්වීම සඳහා 3-lobe Lanczos පෙරහන භාවිතා කරන නැවත නියැදීමේ ක්‍රමයක් අවම කෞතුක වස්තු සහිත උසස් තත්ත්වයේ අන්තර් සම්බන්ධනය සඳහා 4-lobe Lanczos පෙරහන භාවිතා කරන නැවත නියැදීමේ ක්‍රමයක් ජින්ක් ශ්‍රිතය භාවිතා කරන Lanczos 2 ෆිල්ටරයේ ප්‍රභේදයක්, අවම කෞතුක වස්තු සමඟ උසස් තත්ත්වයේ අන්තර් සම්බන්ධනය සපයයි ජින්ක් ශ්‍රිතය භාවිතා කරන Lanczos 3 ෆිල්ටරයේ ප්‍රභේදයක්, අවම කෞතුක වස්තු සමඟ උසස් තත්ත්වයේ අන්තර් සම්බන්ධනය සපයයි ජින්ක් ශ්‍රිතය භාවිතා කරන Lanczos 4 ෆිල්ටරයේ ප්‍රභේදයක්, අවම කෞතුක වස්තු සමඟ උසස් තත්ත්වයේ අන්තර් බන්ධනය සපයයි හැනිං EWA ඉලිප්සීය බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය සුමට මැදිහත්වීම සහ නැවත නියැදීම සඳහා Hanning පෙරහන Robidoux EWA උසස් තත්ත්වයේ නැවත සකස් කිරීම සඳහා Robidoux ෆිල්ටරයේ ඉලිප්සීය බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය Blackman EVE නාද කරන කෞතුක වස්තු අවම කිරීම සඳහා Blackman ෆිල්ටරයේ ඉලිප්සීය බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය Quadric EWA සිනිඳු අන්තර් හුවමාරුව සඳහා චතුරස්රාකාර පෙරහනෙහි ඉලිප්සීය බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය Robidoux Sharp EWA තියුණු ප්‍රතිඵල සඳහා Robidoux Sharp ෆිල්ටරයේ ඉලිප්සාකාර බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය Lanczos 3 Jinc EWA අඩු අන්වර්ථය සමඟ උසස් තත්ත්වයේ නැවත නියැදීම සඳහා Lanczos 3 Jinc පෙරහනෙහි Elliptical Weighted Average (EWA) ප්‍රභේදය ජින්සෙන්ග් තියුණු බවේ සහ සුමටතාවයේ හොඳ සමතුලිතතාවයක් සහිත උසස් තත්ත්වයේ රූප සැකසීම සඳහා නිර්මාණය කර ඇති නැවත සාම්පල පෙරහනක් Ginseng EWA වැඩි දියුණු කළ රූපයේ ගුණාත්මක භාවය සඳහා Ginseng ෆිල්ටරයේ ඉලිප්සීය බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය Lanczos Sharp EWA අවම කෞතුක වස්තු සමඟ තියුණු ප්‍රතිඵල ලබා ගැනීම සඳහා Lanczos Sharp ෆිල්ටරයේ ඉලිප්සාකාර බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය Lanczos 4 තියුණු EWA අතිශය තියුණු රූප නැවත නියැදීම සඳහා Lanczos 4 Sharpest ෆිල්ටරයේ Elliptical Weighted Average (EWA) ප්‍රභේදය Lanczos Soft EWA සුමට රූප නැවත සකස් කිරීම සඳහා Lanczos Soft ෆිල්ටරයේ ඉලිප්සීය බර සහිත සාමාන්‍ය (EWA) ප්‍රභේදය Haasn Soft සුමට සහ කෞතුක වස්තු-රහිත රූප පරිමාණය සඳහා Haasn විසින් නිර්මාණය කරන ලද නැවත සාම්පල පෙරහනක් ආකෘති පරිවර්තනය රූප සමූහය එක් ආකෘතියකින් තවත් ආකෘතියකට පරිවර්තනය කරන්න සදහටම ඉවත් කරන්න රූප ගොඩගැසීම තෝරාගත් මිශ්‍ර ක්‍රම සමඟින් පින්තූර එකින් එක අට්ටි කරන්න රූපය එකතු කරන්න බඳුන් ගණන් ක්ලේ එච්එස්එල් ක්ලේ එච්එස්වී Histogram Adaptive HSL සමාන කරන්න Histogram Adaptive HSV සමාන කරන්න Edge Mode ක්ලිප් ඔතා වර්ණ අන්ධභාවය තෝරාගත් වර්ණ අන්ධතා ප්‍රභේදය සඳහා තේමා වර්ණ අනුවර්තනය කිරීමට මාදිලිය තෝරන්න රතු සහ කොළ වර්ණ අතර වෙනස හඳුනාගැනීමේ අපහසුතාව කොළ සහ රතු වර්ණ අතර වෙනස හඳුනාගැනීමේ අපහසුතාව නිල් සහ කහ වර්ණ අතර වෙනස හඳුනාගැනීමේ අපහසුතාව රතු පැහැයන් වටහා ගැනීමට නොහැකි වීම හරිත වර්ණ තේරුම් ගැනීමට නොහැකි වීම නිල් පැහැයන් වටහා ගැනීමට නොහැකි වීම සියලුම වර්ණ සඳහා සංවේදීතාව අඩු කිරීම සම්පූර්ණ වර්ණ අන්ධභාවය, අළු වර්ණ පමණක් දැකීම වර්ණ අන්ධ යෝජනා ක්රමය භාවිතා නොකරන්න වර්ණ හරියටම තේමාව තුළ සකසා ඇත සිග්මොයිඩල් ලග්රංගේ 2 සුමට සංක්‍රාන්ති සමඟ උසස් තත්ත්වයේ රූප පරිමාණය සඳහා සුදුසු 2 වන අනුපිළිවෙලෙහි Lagrange interpolation ෆිල්ටරයක් ලග්රංගේ 3 රූප පරිමාණය සඳහා වඩා හොඳ නිරවද්‍යතාවයක් සහ සුමට ප්‍රතිඵල ලබා දෙමින්, 3 වන අනුපිළිවෙලෙහි Lagrange interpolation filter එකක් ලැන්සෝස් 6 6 හි ඉහළ අනුපිළිවෙලක් සහිත Lanczos නැවත සාම්පල පෙරහනක්, තියුණු සහ වඩාත් නිවැරදි රූප පරිමාණයක් සපයයි Lanczos 6 ජින්ක් වැඩිදියුණු කළ රූප නැවත නියැදීමේ ගුණාත්මක භාවය සඳහා Jinc ශ්‍රිතයක් භාවිතා කරන Lanczos 6 පෙරහනෙහි ප්‍රභේදයකි රේඛීය පෙට්ටිය බොඳවීම රේඛීය කූඩාරම් බොඳවීම රේඛීය Gaussian පෙට්ටිය බොඳවීම රේඛීය ස්ටැක් බොඳවීම Gaussian Box Blur රේඛීය වේගවත් Gaussian Blur Next රේඛීය වේගවත් ගවුසියන් බොඳවීම රේඛීය Gaussian Blur තීන්තයක් ලෙස භාවිතා කිරීමට එක් පෙරහනක් තෝරන්න පෙරහන ප්රතිස්ථාපනය කරන්න ඔබගේ ඇඳීමේ බුරුසුවක් ලෙස භාවිතා කිරීමට පහත පෙරහන තෝරන්න TIFF සම්පීඩන යෝජනා ක්රමය අඩු පොලි වැලි පින්තාරු කිරීම රූපය බෙදීම තනි රූපය පේළි හෝ තීරු අනුව බෙදන්න සීමාවන්ට ගැලපේ අපේක්ෂිත හැසිරීම් සාක්ෂාත් කර ගැනීම සඳහා මෙම පරාමිතිය සමඟ බෝග ප්‍රමාණය වෙනස් කිරීමේ මාදිලිය ඒකාබද්ධ කරන්න (ක්‍රෝප්/ෆිට් සිට දර්ශන අනුපාතය) භාෂා සාර්ථකව ආනයනය කරන ලදී උපස්ථ OCR ආකෘති ආනයනය කරන්න අපනයනය කරන්න තනතුර මධ්යස්ථානය ඉහළ වම් ඉහළ දකුණ පහළ වම් පහළ දකුණ ඉහළ මධ්යස්ථානය දකුණු මැද පහළ මධ්යස්ථානය වම් මැද ඉලක්ක රූපය පැලට් මාරු කිරීම වැඩි දියුණු කළ තෙල් සරල පැරණි රූපවාහිනිය HDR ගෝතම් සරල ස්කීච් මෘදු දිලිසීම වර්ණ පෝස්ටර් ට්‍රයි ටෝන් තෙවන වර්ණය ක්ලේ ඔක්ලාබ් ක්ලාරා ඔල්ච් ක්ලේ ඡාස්බ්ස් පොල්කා ඩොට් පොකුරු 2x2 ඩයිටරින් පොකුරු 4x4 ඩයිදරින් පොකුරු 8x8 ඩයිටරින් Yililoma Dithering ප්‍රියතම විකල්ප තෝරාගෙන නැත, ඒවා මෙවලම් පිටුවට එක් කරන්න ප්රියතම එකතු කරන්න අනුපූරක සමානයි ත්රිත්ව අනුපූරක බෙදීම ටෙට්රාඩික් චතුරස්රය සමාන + අනුපූරක වර්ණ මෙවලම් මිශ්‍ර කරන්න, නාද සාදන්න, සෙවන ජනනය කරන්න සහ තවත් දේ වර්ණ එකඟතා වර්ණ සෙවන විචලනය ටින්ට්ස් නාද සෙවනැලි වර්ණ මිශ්ර කිරීම වර්ණ තොරතුරු තෝරාගත් වර්ණය මිශ්ර කිරීමට වර්ණය ගතික වර්ණ සක්‍රීය කර ඇති අතර මුදල් භාවිතා කළ නොහැක 512x512 2D LUT ඉලක්ක LUT රූපය ආධුනිකයෙක් එටිකට් මෙනවිය මෘදු අලංකාරය මෘදු අලංකාර ප්රභේදය පැලට් මාරු ප්රභේදය 3D LUT ඉලක්ක 3D LUT ගොනුව (.cube / .CUBE) LUT බ්ලීච් බයිපාස් ඉටිපන්දම් ආලෝකය ඩ්‍රොප් බ්ලූස් එඩ්ජි ඇම්බර් පතන වර්ණ චිත්‍රපට කොටස් 50 මීදුම සහිත රාත්‍රිය කොඩැක් උදාසීන LUT රූපය ලබා ගන්න පළමුව, ඔබට මෙතැනින් ලබාගත හැකි උදාසීන LUT වෙත පෙරහනක් යෙදීමට ඔබේ ප්‍රියතම ඡායාරූප සංස්කරණ යෙදුම භාවිත කරන්න. මෙය නිසියාකාරව ක්‍රියා කිරීම සඳහා සෑම පික්සෙල් වර්ණයක්ම වෙනත් පික්සල මත රඳා නොසිටිය යුතුය (උදා: බොඳවීම ක්‍රියා නොකරයි). සූදානම් වූ පසු, 512*512 LUT පෙරහන සඳහා ආදානය ලෙස ඔබේ නව LUT රූපය භාවිත කරන්න පොප් කලාව සෙලියුලොයිඩ් කෝපි රන් වනාන්තරය කොළ පාටයි රෙට්රෝ කහ සබැඳි පෙරදසුන ඔබට පෙළ ලබා ගත හැකි ස්ථාන (QRCode, OCR ආදිය) සබැඳි පෙරදසුන ලබා ගැනීම සබල කරයි සබැඳි ICO ගොනු සුරැකිය හැක්කේ 256 x 256 උපරිම ප්‍රමාණයෙන් පමණි GIF සිට WEBP GIF පින්තූර WEBP සජීවිකරණ පින්තූර බවට පරිවර්තනය කරන්න WEBP මෙවලම් පින්තූර WEBP සජීවිකරණ පින්තූරයට පරිවර්තනය කරන්න හෝ ලබා දී ඇති WEBP සජීවිකරණයෙන් රාමු උපුටා ගන්න පින්තූර වෙත WEBP WEBP ගොනුව පින්තූර සමූහයකට පරිවර්තනය කරන්න පින්තූර සමූහය WEBP ගොනුවට පරිවර්තනය කරන්න පින්තූර WEBP වෙත ආරම්භ කිරීමට WEBP රූපය තෝරන්න ගොනු වෙත සම්පූර්ණ ප්‍රවේශයක් නොමැත Android මත පින්තූර ලෙස හඳුනා නොගත් JXL, QOI සහ අනෙකුත් පින්තූර බැලීමට සියලුම ගොනුවලට ප්‍රවේශ වීමට ඉඩ දෙන්න. අවසරයකින් තොරව රූප මෙවලම් පෙට්ටියට එම පින්තූර පෙන්වීමට නොහැක පෙරනිමි ඇඳීමේ වර්ණය Default Draw Path Mode කාල මුද්‍රාව එක් කරන්න ප්‍රතිදාන ගොනු නාමයට කාල මුද්‍රාව එක් කිරීම සබල කරයි ෆෝමැට් කල වේලා මුද්දරය මූලික මිලි වෙනුවට ප්‍රතිදාන ගොනු නාමයෙන් කාල මුද්‍රා හැඩතල ගැන්වීම සබල කරන්න ඒවායේ ආකෘතිය තේරීමට කාල මුද්දර සබල කරන්න එක් වරක් ඉතිරි කිරීමේ ස්ථානය බොහෝ විට සියලුම විකල්පවල සුරකින්න බොත්තම දිගු එබීමෙන් ඔබට භාවිතා කළ හැකි එක් වරක් සුරැකීමේ ස්ථාන බලන්න සහ සංස්කරණය කරන්න මෑතකදී භාවිතා කරන ලදී CI නාලිකාව කණ්ඩායම Telegram හි රූප මෙවලම් පෙට්ටිය 🎉 ඔබට අවශ්‍ය ඕනෑම දෙයක් සාකච්ඡා කළ හැකි අපගේ කතාබහට සම්බන්ධ වන්න සහ මා බීටා සහ නිවේදන පළ කරන CI නාලිකාව දෙස බලන්න. යෙදුමේ නව අනුවාද ගැන දැනුම් දෙන්න, සහ නිවේදන කියවන්න ලබා දී ඇති මානයන්ට රූපයක් සවි කර පසුබිමට නොපැහැදිලි හෝ වර්ණය යොදන්න මෙවලම් සකස් කිරීම වර්ගය අනුව මෙවලම් කණ්ඩායම් කරන්න අභිරුචි ලැයිස්තු සැකැස්මක් වෙනුවට ඒවායේ වර්ගය අනුව ප්‍රධාන තිරයේ මෙවලම් කණ්ඩායම් කරන්න පෙරනිමි අගයන් පද්ධති තීරු දෘශ්‍යතාව ස්වයිප් මගින් පද්ධති තීරු පෙන්වන්න පද්ධති තීරු සැඟවී ඇත්නම් ඒවා පෙන්වීමට ස්වයිප් කිරීම සබල කරයි ඔටෝ සියල්ල සඟවන්න සියල්ල පෙන්වන්න නව තීරුව සඟවන්න තත්ව තීරුව සඟවන්න ශබ්ද උත්පාදනය පර්ලින් හෝ වෙනත් වර්ග වැනි විවිධ ශබ්ද උත්පාදනය කරන්න සංඛ්යාතය ශබ්ද වර්ගය භ්රමණ වර්ගය ෆ්රැක්ටල් වර්ගය අෂ්ටක ලැකුනාරිටි ලබාගන්න බරැති ශක්තිය පිං පොං ශක්තිය දුරස්ථ කාර්යය ආපසු එන වර්ගය ජිටර් වසම් Warp පෙළගැස්වීම අභිරුචි ගොනු නාමය වත්මන් රූපය සුරැකීමට භාවිතා කරන ස්ථානය සහ ගොනු නාමය තෝරන්න අභිරුචි නම සහිත ෆෝල්ඩරයට සුරකින ලදී කොලෙජ් සාදන්නා රූප 20ක් දක්වා කොලෙජ් සාදන්න කොලෙජ් වර්ගය ස්ථානය සීරුමාරු කිරීමට, මාරු කිරීමට සහ විශාලනය කිරීමට රූපය අල්ලාගෙන සිටින්න භ්රමණය අක්රිය කරන්න ඇඟිලි දෙකේ අභිනයන් සමඟ රූප භ්‍රමණය වීම වළක්වයි මායිම් වෙත ස්නැප් කිරීම සබල කරන්න චලනය කිරීමෙන් හෝ විශාලනය කිරීමෙන් පසු, රාමු දාර පිරවීම සඳහා පින්තූර කඩා වැටෙනු ඇත හිස්ටෝග්රෑම් ඔබට ගැලපීම් කිරීමට උදවු කිරීමට RGB හෝ Brightness image histogram මෙම රූපය RGB සහ Brightness histograms ජනනය කිරීමට භාවිතා කරනු ඇත Tesseract විකල්ප ටෙසරැක්ට් එන්ජිම සඳහා ආදාන විචල්‍ය කිහිපයක් යොදන්න අභිරුචි විකල්ප මෙම රටාව අනුව විකල්ප ඇතුළත් කළ යුතුය: \"--{option_name} {value}\" ස්වයංක්‍රීය බෝග නිදහස් කෝනර් බහුඅස්‍රයෙන් රූපය කපන්න, මෙය ද ඉදිරිදර්ශනය නිවැරදි කරයි රූප සීමාවන්ට බලහත්කාරයෙන් ලකුණු කරන්න රූප සීමාවන් මගින් ලකුණු සීමා නොවනු ඇත, මෙය වඩාත් නිවැරදි ඉදිරිදර්ශන නිවැරදි කිරීම සඳහා ප්‍රයෝජනවත් වේ මාස්ක් අඳින ලද මාර්ගය යටතේ අන්තර්ගත දැනුවත් පුරවන්න හීල් ස්පෝට් Circle Kernel භාවිතා කරන්න විවෘත කිරීම වසා දැමීම රූප විද්‍යාත්මක අනුක්‍රමණය Top Hat කළු තොප්පිය නාද වක්‍ර වක්‍ර යළි පිහිටුවන්න වක්‍ර පෙරනිමි අගයට පෙරළෙනු ඇත රේඛා විලාසය පරතරය ප්රමාණය ඉරි තැලීය තිත් කඩයි මුද්දර දමා ඇත සිග්සැග් නියමිත පරතරය ප්‍රමාණයෙන් අඳින ලද මාර්ගය දිගේ ඉරි සහිත රේඛාවක් අඳින්න දී ඇති මාර්ගය දිගේ තිත් සහ ඉරි අඳින්න පෙරනිමි සරල රේඛා පමණි නියමිත පරතරය සහිත මාර්ගය ඔස්සේ තෝරාගත් හැඩ අඳින්න මාර්ගය දිගේ රැලි සහිත සිග්සැග් අඳින්න සිග්සැග් අනුපාතය කෙටිමං සාදන්න පින් කිරීමට මෙවලම තෝරන්න මෙවලම කෙටිමඟ ලෙස ඔබේ දියත් කිරීමේ මුල් තිරයට එක් කරනු ඇත, අවශ්‍ය හැසිරීම් සාක්ෂාත් කර ගැනීම සඳහා \"ගොනු තේරීම මඟ හරින්න\" සැකසීම සමඟ එය භාවිතා කරන්න. රාමු ගොඩ නොගසන්න පෙර රාමු බැහැර කිරීම සක්‍රීය කරයි, එබැවින් ඒවා එකිනෙක ගොඩ ගැසෙන්නේ නැත හරස්කඩ රාමු එකිනෙකට හරස් අතට හැරෙනු ඇත Crossfade රාමු ගණන් එළිපත්ත එක එළිපත්ත දෙක කැනී කැඩපත 101 වැඩි දියුණු කළ විශාලන බොඳ කිරීම Laplacian සරල සෝබෙල් සරල උපකාරක ජාලය නිරවද්‍ය උපාමාරු සඳහා උපකාර කිරීම සඳහා ඇඳීම් ප්‍රදේශයට ඉහළින් ආධාරක ජාලකය පෙන්වයි ජාලක වර්ණය සෛල පළල සෛල උස සංයුක්ත තේරීම් සමහර තේරීම් පාලන අඩු ඉඩක් ගැනීමට සංයුක්ත පිරිසැලසුමක් භාවිතා කරයි රූපය ලබා ගැනීමට සැකසීම් තුළ කැමරා අවසරය ලබා දෙන්න පිරිසැලසුම ප්රධාන තිර මාතෘකාව නියත අනුපාත සාධකය (CRF) %1$s අගයක් යනු මන්දගාමී සම්පීඩනයකි, ප්‍රතිඵලයක් ලෙස සාපේක්ෂව කුඩා ගොනු ප්‍රමාණයකි. %2$s යනු විශාල ගොනුවක් ඇති කරන වේගවත් සම්පීඩනයකි. ලූට් පුස්තකාලය ඔබට බාගත කිරීමෙන් පසු අයදුම් කළ හැකි LUT එකතුව බාගන්න ඔබට බාගත කිරීමෙන් පසු අයදුම් කළ හැකි LUT එකතුව යාවත්කාලීන කරන්න (නව ඒවා පමණක් පෝලිම් වේ). පෙරහන් සඳහා පෙරනිමි රූප පෙරදසුන වෙනස් කරන්න රූපය පෙරදසුන් කරන්න සඟවන්න පෙන්වන්න ස්ලයිඩර් වර්ගය විසිතුරු ද්රව්ය 2 විසිතුරු පෙනුමක් ඇති ස්ලයිඩරයක්. මෙය පෙරනිමි විකල්පයයි ද්රව්ය 2 ස්ලයිඩරයක් ඔබ ස්ලයිඩරය සඳහා ද්‍රව්‍යයක් අයදුම් කරන්න මධ්‍ය සංවාද බොත්තම් හැකි නම්, සංවාද බොත්තම් වම් පැත්ත වෙනුවට මධ්‍යයේ ස්ථානගත කෙරේ විවෘත මූලාශ්ර බලපත්ර මෙම යෙදුමේ භාවිතා කරන විවෘත මූලාශ්‍ර පුස්තකාලවල බලපත්‍ර බලන්න ප්රදේශය පික්සල් ප්‍රදේශ සම්බන්ධය භාවිතයෙන් නැවත නියැදීම. එය moire\'-නිදහස් ප්‍රතිඵල ලබා දෙන බැවින්, රූප විනාශය සඳහා වඩාත් කැමති ක්‍රමයක් විය හැක. නමුත් රූපය විශාලනය කළ විට එය \"ළඟම \" ක්‍රමයට සමාන වේ. Tonemapping සබල කරන්න % ඇතුලත් කරන්න වෙබ් අඩවියට පිවිසිය නොහැක, VPN භාවිතා කිරීමට උත්සාහ කරන්න හෝ url එක නිවැරදිදැයි පරීක්ෂා කරන්න සලකුණු ස්ථර පින්තූර, පෙළ සහ තවත් දේ නිදහසේ තැබීමේ හැකියාව සහිත ස්ථර මාදිලිය ස්තරය සංස්කරණය කරන්න රූපය මත ස්ථර පින්තූරයක් පසුබිමක් ලෙස භාවිතා කර එය මත විවිධ ස්ථර එකතු කරන්න පසුබිම මත ස්ථර පළමු විකල්පයට සමාන නමුත් රූපය වෙනුවට වර්ණය සමඟ බීටා වේගවත් සැකසුම් පැත්ත පින්තූර සංස්කරණය කරන අතරතුර තෝරාගත් පැත්තේ පාවෙන තීරුවක් එක් කරන්න, එය ක්ලික් කළ විට වේගවත් සැකසුම් විවෘත වේ පැහැදිලි තේරීම \"%1$s\" කණ්ඩායම සැකසීම පෙරනිමියෙන් හකුළනු ඇත \"%1$s\" කණ්ඩායම සැකසීම පෙරනිමියෙන් පුළුල් වනු ඇත Base64 මෙවලම් Base64 තන්තුව රූපයට විකේතනය කරන්න, නැතහොත් රූපය Base64 ආකෘතියට සංකේතනය කරන්න පදනම64 සපයා ඇති අගය වලංගු Base64 තන්තුවක් නොවේ හිස් හෝ වලංගු නොවන Base64 තන්තුව පිටපත් කළ නොහැක Base64 අලවන්න Base64 පිටපත් කරන්න Base64 තන්තුව පිටපත් කිරීමට හෝ සුරැකීමට රූපය පූරණය කරන්න. ඔබට තන්තුවම තිබේ නම්, ඔබට රූපය ලබා ගැනීමට එය ඉහත ඇලවිය හැක Base64 සුරකින්න Share Base64 විකල්ප ක්රියාවන් ආයාත Base64 Base64 ක්‍රියා දළ සටහන් එකතු කරන්න නිශ්චිත වර්ණය සහ පළල සමඟ පෙළ වටා දළ සටහනක් එක් කරන්න දළ සටහන් වර්ණය දළ සටහන් ප්‍රමාණය භ්රමණය ගොනු නාමය ලෙස චෙක්සම් ප්‍රතිදාන රූපවලට ඒවායේ දත්ත පිරික්සුමට අනුරූප නමක් ඇත නිදහස් මෘදුකාංග (හවුල්කරු) Android යෙදුම්වල හවුල්කාර නාලිකාවේ වඩාත් ප්‍රයෝජනවත් මෘදුකාංග ඇල්ගොරිතම චෙක්සම් මෙවලම් චෙක්සම් සංසන්දනය කරන්න, හෑෂ් ගණනය කරන්න හෝ විවිධ හැෂිං ඇල්ගොරිතම භාවිතයෙන් ගොනු වලින් හෙක්ස් තන්තු සාදන්න ගණනය කරන්න හැෂ් වෙත කෙටි පණිවිඩයක් යවන්න චෙක්සම් තෝරාගත් ඇල්ගොරිතම මත පදනම්ව එහි චෙක්සම් ගණනය කිරීමට ගොනුව තෝරන්න තෝරාගත් ඇල්ගොරිතම මත පදනම්ව එහි චෙක්සම් ගණනය කිරීමට පෙළ ඇතුළත් කරන්න මූලාශ්ර චෙක්සම් සංසන්දනය කිරීමට චෙක්සම් තරගය! වෙනස චෙක්සම් සමාන වේ, එය ආරක්ෂිත විය හැකිය චෙක්සම් සමාන නොවේ, ගොනුව අනාරක්ෂිත විය හැක! Mesh Gradients Mesh Gradients හි මාර්ගගත එකතුව බලන්න ආනයනය කළ හැක්කේ TTF සහ OTF අකුරු පමණි අකුරු ආයාත කරන්න (TTF/OTF) අකුරු අපනයනය කරන්න ආනයනික අකුරු උත්සාහය සුරැකීමේදී දෝෂයකි, ප්‍රතිදාන ෆෝල්ඩරය වෙනස් කිරීමට උත්සාහ කරන්න ගොනු නාමය සකසා නැත කිසිවක් නැත අභිරුචි පිටු පිටු තේරීම මෙවලම් පිටවීම තහවුරු කිරීම විශේෂිත මෙවලම් භාවිතා කරන අතරතුර ඔබට නොසුරකින ලද වෙනස්කම් තිබේ නම් සහ එය වසා දැමීමට උත්සාහ කරන්නේ නම්, පසුව තහවුරු කිරීමේ සංවාදය පෙන්වනු ඇත EXIF සංස්කරණය කරන්න නැවත සම්පීඩනයකින් තොරව තනි රූපයේ පාර-දත්ත වෙනස් කරන්න පවතින ටැග් සංස්කරණය කිරීමට තට්ටු කරන්න ස්ටිකරය වෙනස් කරන්න Fit පළල සුදුසු උස කණ්ඩායම් සංසන්දනය තෝරාගත් ඇල්ගොරිතම මත පදනම්ව එහි චෙක්සම් ගණනය කිරීමට ගොනුව/ගොනු තෝරන්න ගොනු තෝරන්න නාමාවලිය තෝරන්න හිස දිග පරිමාණය මුද්දර වේලා මුද්රාව ආකෘති රටා පෑඩිං රූප කැපීම රූප කොටස කපා වම් ඒවා සිරස් හෝ තිරස් රේඛා මගින් ඒකාබද්ධ කරන්න (ප්‍රතිලෝම විය හැක) සිරස් විවර්තන රේඛාව තිරස් විවර්තන රේඛාව ප්රතිලෝම තේරීම කැපූ ප්‍රදේශය වටා කොටස් ඒකාබද්ධ කිරීම වෙනුවට සිරස් කැපූ කොටස ඉතිරි වේ කැපූ ප්‍රදේශය වටා කොටස් ඒකාබද්ධ කිරීම වෙනුවට තිරස් කැපූ කොටස ඉතිරි වනු ඇත Mesh Gradients එකතුව අභිරුචි ගැට ප්‍රමාණය සහ විභේදනය සමඟ දැල් අනුක්‍රමය සාදන්න දැල් ශ්‍රේණියේ උඩැතිරිය ලබා දී ඇති රූපවල ඉහලින් දැල් අනුක්‍රමණය සම්පාදනය කරන්න ලකුණු අභිරුචිකරණය ජාලක ප්‍රමාණය විභේදනය X යෝජනාව Y විභේදනය Pixel විසින් Pixel වර්ණ උද්දීපනය කරන්න පික්සල් සංසන්දන වර්ගය තීරු කේතය පරිලෝකනය කරන්න උස අනුපාතය තීරු කේතය වර්ගය B/W බලාත්මක කරන්න තීරු කේත රූපය සම්පූර්ණයෙන්ම කළු සහ සුදු වන අතර යෙදුමේ තේමාවෙන් වර්ණවත් නොවේ ඕනෑම තීරු කේතයක් (QR, EAN, AZTEC, …) ස්කෑන් කර එහි අන්තර්ගතය ලබා ගන්න හෝ නව එකක් උත්පාදනය කිරීමට ඔබේ පෙළ අලවන්න තීරු කේතයක් හමු නොවිණි ජනනය කළ තීරු කේතය මෙහි වනු ඇත ශ්රව්ය ආවරණ ශ්‍රව්‍ය ගොනු වලින් ඇල්බම ආවරණ රූප උපුටා ගන්න, බොහෝ පොදු ආකෘති සඳහා සහය දක්වයි ආරම්භ කිරීමට ශ්‍රව්‍ය තෝරන්න ශ්රව්ය උපකරණ තෝරන්න කවරයක් හමු නොවීය ලඝු-සටහන් යවන්න යෙදුම් ලොග් ගොනුව බෙදා ගැනීමට ක්ලික් කරන්න, මෙය මට ගැටලුව හඳුනා ගැනීමට සහ ගැටලු විසඳීමට උදවු කළ හැක අපොයි… යමක් වැරදී ඇත පහත විකල්ප භාවිතයෙන් ඔබට මා හා සම්බන්ධ විය හැකි අතර මම විසඳුමක් සෙවීමට උත්සාහ කරමි.\n(ලඝු-සටහන් අමුණන්න අමතක කරන්න එපා) ගොනු කිරීමට ලියන්න පින්තූර සමූහයකින් පෙළ උපුටා ගෙන එය එක් පෙළ ගොනුවක ගබඩා කරන්න පාරදත්ත වෙත ලියන්න සෑම රූපයකින්ම පෙළ උපුටා ගෙන එය සාපේක්ෂ ඡායාරූපවල EXIF ​​තොරතුරු තුළ තබන්න නොපෙනෙන මාදිලිය ඔබේ රූපවල බයිට් ඇතුළත ඇසට නොපෙනෙන ජල සලකුණු නිර්මාණය කිරීමට ස්ටෙගනොග්‍රැෆි භාවිතා කරන්න LSB භාවිතා කරන්න LSB (Lessignificant Bit) steganography ක්‍රමය භාවිතා කරනු ඇත, FD (Frequency Domain) රතු ඇස් ස්වයංක්‍රීයව ඉවත් කරන්න මුරපදය අගුළු හරින්න PDF ආරක්ෂිතයි මෙහෙයුම බොහෝ දුරට අවසන්. දැන් අවලංගු කිරීමට එය නැවත ආරම්භ කිරීම අවශ්‍ය වේ දිනය වෙනස් කරන ලදී වෙනස් කළ දිනය (ආපසු හැර) ප්රමාණය ප්‍රමාණය (ප්‍රතිලෝම) MIME වර්ගය MIME වර්ගය (ප්‍රතිලෝම) දිගු කිරීම දිගුව (ප්‍රතිලෝම) එකතු කළ දිනය එකතු කළ දිනය (ආපසු හැර) වමේ සිට දකුණට දකුණේ සිට වමට ඉහළ සිට පහළට පහළ සිට ඉහළට දියර වීදුරු මෑතකදී නිවේදනය කරන ලද IOS 26 මත පදනම් වූ ස්විචයක් සහ එහි ද්‍රව වීදුරු සැලසුම් පද්ධතිය රූපය තෝරන්න හෝ පහත Base64 දත්ත අලවන්න/ආයාත කරන්න ආරම්භ කිරීමට රූප සබැඳිය ටයිප් කරන්න සබැඳිය අලවන්න කැලිඩෝස්කෝප් ද්විතියික කෝණය පැති නාලිකා මිශ්‍රණය නිල් කොළ රතු නිල් කොළ රතු රතු පාටට කොළ පාටට නිල් පාටට සියන් මැජෙන්ටා කහ පාට Halftone සමෝච්ඡය මට්ටම් ඕෆ්සෙට් Voronoi Crystallize හැඩය දිගු කරන්න අහඹු බව ඩෙස්පෙකල් විසරණය DoG දෙවන අරය සම කරන්න දිලිසෙනවා වර්ල් සහ පින්ච් Pointillize මායිම් වර්ණය ධ්රැවීය ඛණ්ඩාංක ධ්‍රැවීය දක්වා ධ්‍රැවීය සිට සෘජු දක්වා රවුමට පෙරළන්න ශබ්දය අඩු කරන්න සරල Solarize වියන්න X පරතරය Y පරතරය X පළල Y පළල කරකවන්න රබර් මුද්දරය ස්මියර් ඝනත්වය මිශ්ර කරන්න Sphere Lens Distortion වර්තන දර්ශකය චාප පැතිරීමේ කෝණය දීප්තිය කිරණ ASCII Gradient මරියා සරත් ඍතුව අස්ථි ජෙට් ශීත ඍතුව සාගරය ගිම්හානය වසන්තය සිසිල් ප්රභේදය එච්.එස්.වී රෝස උණුසුම් වචනය මැග්මා අපාය ප්ලාස්මා විරිඩිස් පුරවැසියන් ට්විලයිට් Twilight මාරු විය Perspective Auto ඩෙස්ක්ව් වගා කිරීමට ඉඩ දෙන්න බෝග හෝ ඉදිරිදර්ශනය නිරපේක්ෂ ටර්බෝ ගැඹුරු කොළ කාච නිවැරදි කිරීම JSON ආකෘතියෙන් ඉලක්කගත කාච පැතිකඩ ගොනුව සූදානම් කාච පැතිකඩ බාගන්න කොටස් සියයට JSON ලෙස නිර්යාත කරන්න json නියෝජනය ලෙස palette දත්ත සමඟ තන්තුව පිටපත් කරන්න මැහුම් කැටයම් මුල් තිරය අගුළු තිරය බිල්ට්-ඉන් Wallpapers අපනයනය නැවුම් කරන්න වත්මන් නිවස, අගුලු දැමීම සහ බිල්ට්-ඉන් බිතුපත් ලබා ගන්න සියලුම ගොනු වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න, බිතුපත් ලබා ගැනීමට මෙය අවශ්‍ය වේ බාහිර ගබඩා අවසරය කළමනාකරණය කිරීම ප්‍රමාණවත් නොවේ, ඔබ ඔබේ පින්තූර වෙත ප්‍රවේශ වීමට ඉඩ දිය යුතුය, \"සියල්ලට ඉඩ දෙන්න\" තේරීමට වග බලා ගන්න ගොනු නාමයට පෙරසිටුව එක් කරන්න පින්තූර ගොනු නාමයට තෝරාගත් පෙරසිටුවක් සමඟ උපසර්ගය එකතු කරයි ගොනු නාමයට රූප පරිමාණ මාදිලිය එක් කරන්න රූප ගොනු නාමයට තෝරාගත් රූප පරිමාණ මාදිලිය සමඟ උපසර්ගය එකතු කරයි Ascii කලාව පින්තූරයක් ලෙස පෙනෙන ascii පෙළට පින්තූරය පරිවර්තනය කරන්න පරාමිති සමහර අවස්ථාවලදී වඩා හොඳ ප්‍රතිඵලයක් සඳහා රූපයට සෘණ පෙරහන යොදන්න තිර රුවක් සකසමින් තිර රුවක් ග්‍රහණය කර නැත, නැවත උත්සාහ කරන්න සුරැකීම මඟ හැරිණි %1$s ගොනු මඟ හරින ලදී විශාල නම් මඟ හැරීමට ඉඩ දෙන්න ප්‍රතිඵලයක් ලෙස ලැබෙන ගොනු ප්‍රමාණය මුල් ප්‍රමාණයට වඩා විශාල නම් සමහර මෙවලම්වලට පින්තූර සුරැකීම මඟ හැරීමට ඉඩ දෙනු ලැබේ දින දර්ශන සිදුවීම අමතන්න ඊමේල් කරන්න ස්ථානය දුරකථනය පෙළ SMS URL Wifi ජාලය විවෘත කරන්න N/A SSID දුරකථනය පණිවිඩය ලිපිනය විෂය ශරීරය නම සංවිධානය මාතෘකාව දුරකථන ඊමේල් URLs ලිපිනයන් සාරාංශය විස්තරය ස්ථානය සංවිධායක ආරම්භක දිනය අවසන් දිනය තත්ත්වය අක්ෂාංශ දේශාංශ තීරු කේතය සාදන්න තීරු කේතය සංස්කරණය කරන්න Wi-Fi වින්යාසය ආරක්ෂාව සම්බන්ධතාවය තෝරන්න තෝරාගත් සම්බන්ධතා භාවිතයෙන් ස්වයංක්‍රීයව පිරවීම සඳහා සැකසීම් තුළ සම්බන්ධතා අවසර ලබා දෙන්න සම්බන්ධතා තොරතුරු මුල් නම මැද නම අවසන් නම උච්චාරණය දුරකථනය එක් කරන්න ඊමේල් එකතු කරන්න ලිපිනය එකතු කරන්න වෙබ් අඩවිය වෙබ් අඩවිය එක් කරන්න ආකෘතිගත නම මෙම රූපය තීරු කේතයට ඉහළින් තැබීමට භාවිතා කරනු ඇත කේත අභිරුචිකරණය මෙම රූපය QR කේතයේ මධ්‍යයේ ලාංඡනය ලෙස භාවිත කෙරේ ලාංඡනය ලාංඡන පිරවීම ලාංඡන ප්රමාණය ලාංඡන කොන් හතරවන ඇස පහළ කෙළවරේ සිව්වන ඇස එක් කිරීමෙන් qr කේතයට අක්ෂි සමමිතිය එක් කරයි පික්සල් හැඩය රාමු හැඩය බෝල හැඩය දෝෂ නිවැරදි කිරීමේ මට්ටම අඳුරු වර්ණය සැහැල්ලු වර්ණය අධි මෙහෙයුම් පද්ධතිය Xiaomi HyperOS වැනි ශෛලිය මාස්ක් රටාව මෙම කේතය ස්කෑන් කළ නොහැකි විය හැක, සියලු උපාංග සමඟ එය කියවිය හැකි කිරීමට පෙනුම පරාමිතීන් වෙනස් කරන්න ස්කෑන් කළ නොහැක මෙවලම් වඩාත් සංයුක්ත වීමට මුල් තිරයේ යෙදුම් දියත් කිරීමක් මෙන් පෙනෙනු ඇත දියත් කිරීමේ මාදිලිය තෝරාගත් බුරුසුවක් සහ මෝස්තරයක් සහිත ප්රදේශයක් පුරවයි ගංවතුර පිරවීම ඉසින්න ග්‍රැෆිටි හැඩැති මාර්ගය අඳිනවා හතරැස් අංශු ඉසින අංශු රවුම් වෙනුවට හතරැස් හැඩැති වනු ඇත පැලට් මෙවලම් රූපයෙන් මූලික/ද්‍රව්‍ය ජනනය කරන්න, නැතහොත් විවිධ පැලට් ආකෘති හරහා ආනයනය/අපනයනය කරන්න පලත් සංස්කරණය කරන්න විවිධ ආකෘති හරහා අපනයන/ආනයන palette වර්ණ නම පැලට් නම පැලට් ආකෘතිය ජනනය කරන ලද තලය විවිධ ආකෘති වෙත අපනයනය කරන්න වත්මන් තලයට නව වර්ණයක් එක් කරයි %1$s ආකෘතිය පැලට් නම සැපයීමට සහාය නොදක්වයි Play Store ප්‍රතිපත්ති හේතුවෙන්, මෙම විශේෂාංගය වත්මන් ගොඩනැගීමට ඇතුළත් කළ නොහැක. මෙම ක්‍රියාකාරීත්වයට ප්‍රවේශ වීමට, කරුණාකර විකල්ප මූලාශ්‍රයකින් ImageToolbox බාගන්න. ඔබට පහතින් GitHub හි පවතින ගොඩනැගීම් සොයා ගත හැක. Github පිටුව විවෘත කරන්න තෝරාගත් ෆෝල්ඩරයේ සුරැකීම වෙනුවට මුල් ගොනුව අලුත් එකක් සමඟ ප්‍රතිස්ථාපනය වේ සැඟවුණු දිය සලකුණු පෙළ අනාවරණය විය සැඟවුණු දිය සලකුණු රූපයක් අනාවරණය විය මෙම රූපය සඟවා ඇත උත්පාදක පින්තාරු කිරීම OpenCV මත රඳා නොසිට, AI ආකෘතියක් භාවිතයෙන් රූපයක ඇති වස්තූන් ඉවත් කිරීමට ඔබට ඉඩ සලසයි. මෙම විශේෂාංගය භාවිතා කිරීමට, යෙදුම GitHub වෙතින් අවශ්‍ය ආකෘතිය (~200 MB) බාගනු ඇත OpenCV මත රඳා නොසිට, AI ආකෘතියක් භාවිතයෙන් රූපයක ඇති වස්තූන් ඉවත් කිරීමට ඔබට ඉඩ සලසයි. මෙය දිගුකාලීන මෙහෙයුමක් විය හැකිය දෝෂ මට්ටම විශ්ලේෂණය දීප්තිය අනුක්‍රමණය සාමාන්ය දුර චලනය හඳුනාගැනීම පිටපත් කරන්න රඳවා ගන්න සංගුණක ක්ලිප්බෝඩ් දත්ත විශාල වැඩිය දත්ත පිටපත් කිරීමට විශාල වැඩිය සරල වියමන පික්සලකරණය එකතැන පල්වෙන පික්සලකරණය හරස් පික්සලකරණය ක්ෂුද්‍ර මැක්‍රෝ පික්සලීකරණය කක්ෂීය පික්සලකරණය Vortex Pixelization Pulse Grid Pixelization න්යෂ්ටිය පික්සලීකරණය රේඩියල් වීව් පික්සලීකරණය uri \"%1$s\" විවෘත කළ නොහැක හිම පතන මාදිලිය සබල කර ඇත මායිම් රාමුව Glitch ප්රභේදය නාලිකා මාරුව උපරිම ඕෆ්සෙට් වීඑච්එස් අවහිර ග්ලිච් බ්ලොක් ප්රමාණය CRT වක්‍රය වක්රය ක්රෝමා පික්සල් දියවීම මැක්ස් ඩ්‍රොප් AI මෙවලම් කෞතුක වස්තු ඉවත් කිරීම හෝ denoising වැනි AI ආකෘති හරහා රූප සැකසීමට විවිධ මෙවලම් සම්පීඩනය, හකුරු රේඛා කාටූන්, විකාශන සම්පීඩනය සාමාන්ය සම්පීඩනය, සාමාන්ය ශබ්දය වර්ණ රහිත කාටූන් ශබ්දය වේගවත්, සාමාන්‍ය සම්පීඩනය, සාමාන්‍ය ශබ්දය, සජීවිකරණ/විකට/ඇනිමේ පොත් ස්කෑන් කිරීම නිරාවරණ නිවැරදි කිරීම සාමාන්ය සම්පීඩනය, වර්ණ රූපවල හොඳම සාමාන්‍ය සම්පීඩනයේදී හොඳම, අළු පරිමාණ රූප සාමාන්‍ය සම්පීඩනය, අළු පරිමාණ රූප, වඩා ශක්තිමත් සාමාන්ය ශබ්දය, වර්ණ රූප සාමාන්ය ශබ්දය, වර්ණ රූප, වඩා හොඳ විස්තර සාමාන්‍ය ශබ්දය, අළු පරිමාණ රූප සාමාන්‍ය ඝෝෂාව, අළු පරිමාණ රූප, ශක්තිමත් සාමාන්‍ය ශබ්දය, අළු පරිමාණ රූප, ශක්තිමත්ම සාමාන්ය සම්පීඩනය සාමාන්ය සම්පීඩනය Texturization, h264 සම්පීඩනය VHS සම්පීඩනය සම්මත නොවන සම්පීඩනය (cinepak, msvideo1, roq) බින්ක් සම්පීඩනය, ජ්යාමිතිය මත වඩා හොඳය බින්ක් සම්පීඩනය, වඩා ශක්තිමත් බින්ක් සම්පීඩනය, මෘදු, විස්තර රඳවා තබා ගනී පඩිපෙළ-පියවර ආචරණය ඉවත් කිරීම, සිනිඳු කිරීම ස්කෑන් කළ චිත්‍ර/ඇඳීම්, මෘදු සම්පීඩනය, මෝයර් වර්ණ පටිය මන්දගාමී, අර්ධ ටෝන ඉවත් කිරීම අළු පරිමාණ/bw රූප සඳහා සාමාන්‍ය වර්ණ කාරකය, වඩා හොඳ ප්‍රතිඵල සඳහා DDColor භාවිතා කරන්න දාර ඉවත් කිරීම අධික මුවහත් වීම ඉවත් කරයි මන්දගාමී, දිරාපත් වීම Anti-aliasing, general artifacts, CGI KDM003 පරිලෝකනය සැකසීම සැහැල්ලු රූප වැඩිදියුණු කිරීමේ ආකෘතිය සම්පීඩන පුරාවස්තු ඉවත් කිරීම සම්පීඩන පුරාවස්තු ඉවත් කිරීම සුමට ප්රතිඵල සමඟ වෙළුම් පටියක් ඉවත් කිරීම Halftone රටා සැකසීම ඩිතර් රටා ඉවත් කිරීම V3 JPEG කෞතුක භාණ්ඩ ඉවත් කිරීම V2 H.264 වයනය වැඩි දියුණු කිරීම VHS තියුණු කිරීම සහ වැඩිදියුණු කිරීම ඒකාබද්ධ කිරීම කුට්ටි ප්රමාණය අතිච්ඡාදනය වන ප්‍රමාණය %1$s px ට වැඩි පින්තූර කැබලිවලට කපා සකසනු ලැබේ, දෘශ්‍ය මැහුම් වැළැක්වීම සඳහා මේවා අතිච්ඡාදනය කරයි. විශාල ප්රමාණවලින් අඩු-අන්ත උපාංග සමඟ අස්ථාවරත්වය ඇති විය හැක ආරම්භ කිරීමට එකක් තෝරන්න ඔබට %1$s මාදිලිය මැකීමට අවශ්‍යද? ඔබ එය නැවත බාගත කිරීමට අවශ්ය වනු ඇත තහවුරු කරන්න ආකෘති බාගත කළ මාදිලි පවතින මාදිලි සූදානම් කිරීම ක්රියාකාරී ආකෘතිය සැසිය විවෘත කිරීමට අසමත් විය ආනයනය කළ හැක්කේ .onnx/.ort මාදිලි පමණි ආයාත ආකෘතිය වැඩිදුර භාවිතය සඳහා අභිරුචි onnx ආකෘතිය ආයාත කරන්න, onnx/ort ආකෘති පමණක් පිළිගනු ලැබේ, esrgan වැනි ප්‍රභේද සියල්ලටම පාහේ සහය දක්වයි ආනයනික මාදිලි සාමාන්ය ශබ්දය, වර්ණ රූප සාමාන්‍ය ඝෝෂාව, වර්ණවත් රූප, ශක්තිමත් සාමාන්ය ශබ්දය, වර්ණ රූප, ශක්තිමත්ම දිරාපත්වන කෞතුක වස්තු සහ වර්ණ පටිය අඩු කරයි, සුමට අනුක්‍රමණය සහ පැතලි වර්ණ ප්‍රදේශ වැඩි දියුණු කරයි. ස්වභාවික වර්ණ සංරක්ෂණය කරමින් සමබර උද්දීපනයන් සමඟ රූපයේ දීප්තිය සහ වෙනස වැඩි දියුණු කරයි. විස්තර තබා ගැනීම සහ අධික ලෙස නිරාවරණය වීම වළක්වා ගනිමින් අඳුරු රූප දීප්තිමත් කරයි. අධික වර්ණ ටොනිං ඉවත් කිරීම සහ වඩාත් මධ්යස්ථ සහ ස්වභාවික වර්ණ සමතුලිතතාවයක් ප්රතිස්ථාපනය කරයි. සියුම් විස්තර සහ වයනය සංරක්ෂණය කිරීම අවධාරණය කරමින් Poisson-පාදක ශබ්ද ටෝනිං යොදයි. සුමට සහ අඩු ආක්‍රමණශීලී දෘශ්‍ය ප්‍රතිඵල සඳහා මෘදු Poisson ශබ්ද ටෝනිං යොදයි. ඒකාකාර ශබ්ද ටෝනිං විස්තර සංරක්ෂණය සහ රූපයේ පැහැදිලිකම කෙරෙහි අවධානය යොමු කරයි. සියුම් වයනය සහ සුමට පෙනුම සඳහා මෘදු ඒකාකාර ශබ්ද ටෝනිං. කෞතුක භාණ්ඩ නැවත පින්තාරු කිරීමෙන් සහ රූපයේ අනුකූලතාව වැඩි දියුණු කිරීමෙන් හානියට පත් හෝ අසමාන ප්‍රදේශ අලුත්වැඩියා කරයි. අවම කාර්ය සාධන පිරිවැයක් සහිත වර්ණ පටිය ඉවත් කරන සැහැල්ලු ඩිබෑන්ඩ් මාදිලිය. වැඩි දියුණු කළ පැහැදිලිකම සඳහා ඉතා ඉහළ සම්පීඩන කෞතුක වස්තු (0-20% ගුණාත්මක) සහිත රූප ප්‍රශස්ත කරයි. ඉහළ සම්පීඩන කෞතුක වස්තු (20-40% ගුණාත්මක) සමඟ රූප වැඩි දියුණු කරයි, විස්තර ප්‍රතිස්ථාපනය කිරීම සහ ශබ්දය අඩු කිරීම. මධ්‍යස්ථ සම්පීඩනය (40-60% ගුණාත්මක), තියුණු බව සහ සුමට බව සමතුලිත කිරීම සමඟ රූප වැඩි දියුණු කරයි. සියුම් විස්තර සහ වයනය වැඩි දියුණු කිරීම සඳහා සැහැල්ලු සම්පීඩනය (60-80% ගුණාත්මක) සමඟ රූප පිරිපහදු කරයි. ස්වාභාවික පෙනුම සහ විස්තර ආරක්ෂා කරන අතරම, ආසන්න පාඩු රහිත රූප (80-100% ගුණත්වය) තරමක් වැඩි දියුණු කරයි. සරල හා වේගවත් වර්ණ ගැන්වීම, කාටූන්, සුදුසු නොවේ කෞතුක වස්තු හඳුන්වාදීමකින් තොරව තියුණු බව වැඩිදියුණු කිරීම, රූපය නොපැහැදිලි කිරීම තරමක් අඩු කරයි. දිගුකාලීන මෙහෙයුම් රූපය සැකසීම සැකසීම ඉතා අඩු ගුණාත්මක රූපවල (0-20%) බර JPEG සම්පීඩන කෞතුක වස්තු ඉවත් කරයි. අතිශයින් සම්පීඩිත රූපවල (20-40%) ශක්තිමත් JPEG කෞතුක වස්තු අඩු කරයි. පින්තූර විස්තර (40-60%) සංරක්ෂණය කරමින් මධ්‍යස්ථ JPEG කෞතුක වස්තු පිරිසිදු කරයි. තරමක් උසස් තත්ත්වයේ රූප (60-80%) තුළ සැහැල්ලු JPEG කෞතුක භාණ්ඩ පිරිපහදු කරයි. අලාභ රහිත රූපවල (80-100%) කුඩා JPEG කෞතුක වස්තු සියුම් ලෙස අඩු කරයි. සියුම් විස්තර සහ වයනය වැඩි දියුණු කරයි, බර කෞතුක වස්තු නොමැතිව දැනෙන තියුණු බව වැඩි දියුණු කරයි. සැකසීම අවසන් සැකසීම අසාර්ථක විය වේගය සඳහා ප්‍රශස්ත ස්වභාවික පෙනුමක් තබා ගනිමින් සමේ වයනය සහ විස්තර වැඩි දියුණු කරයි. JPEG සම්පීඩන කෞතුක වස්තු ඉවත් කර සම්පීඩිත ඡායාරූප සඳහා රූපයේ ගුණාත්මකභාවය ප්‍රතිසාධනය කරයි. අඩු ආලෝක තත්ත්ව යටතේ ගන්නා ලද ඡායාරූපවල ISO ශබ්දය අඩු කරයි, විස්තර සංරක්ෂණය කරයි. අධික ලෙස නිරාවරණය වූ හෝ \"ජම්බෝ\" උද්දීපනය නිවැරදි කර වඩා හොඳ ටෝනල් සමතුලිතතාවය යථා තත්වයට පත් කරයි. අළු පරිමාණ රූපවලට ස්වභාවික වර්ණ එකතු කරන සැහැල්ලු සහ වේගවත් වර්ණ ගැන්වීමේ ආකෘතිය. DEJPEG ඩෙනොයිස් වර්ණ ගන්වන්න පුරාවස්තු වැඩි දියුණු කරන්න ඇනිමෙ ස්කෑන් ඉහළ මට්ටමේ සාමාන්‍ය රූප සඳහා X4 upscaler; මධ්යස්ථ deblur සහ denoise සමඟ අඩු GPU සහ කාලය භාවිතා කරන කුඩා ආකෘතිය. සාමාන්‍ය රූප, වයනය සහ ස්වභාවික විස්තර සංරක්ෂණය කිරීම සඳහා X2 ඉහළට. වැඩි දියුණු කළ වයනය සහ යථාර්ථවාදී ප්‍රතිඵල සහිත සාමාන්‍ය රූප සඳහා X4 ඉහළ පරිමාණය. X4 upscaler සජීවිකරණ රූප සඳහා ප්‍රශස්ත කර ඇත; තියුණු රේඛා සහ විස්තර සඳහා RRDB කුට්ටි 6 ක්. MSE අලාභය සහිත X4 upscaler, සාමාන්‍ය රූප සඳහා සුමට ප්‍රතිඵල සහ අඩු කරන ලද කෞතුක භාණ්ඩ නිෂ්පාදනය කරයි. X4 Upscaler සජීවිකරණ රූප සඳහා ප්‍රශස්ත කර ඇත; තියුණු විස්තර සහ සුමට රේඛා සහිත 4B32F ප්‍රභේදය. සාමාන්ය රූප සඳහා X4 UltraSharp V2 ආකෘතිය; තියුණු බව සහ පැහැදිලි බව අවධාරණය කරයි. X4 UltraSharp V2 Lite; වේගවත් හා කුඩා, අඩු GPU මතකයක් භාවිතා කරන අතරතුර විස්තර ආරක්ෂා කරයි. ඉක්මන් පසුබිම ඉවත් කිරීම සඳහා සැහැල්ලු ආකෘතිය. සමබර කාර්ය සාධනය සහ නිරවද්යතාව. ආලේඛ්‍ය චිත්‍ර, වස්තු සහ දර්ශන සමඟ ක්‍රියා කරයි. බොහෝ භාවිත අවස්ථා සඳහා නිර්දේශ කෙරේ. BG ඉවත් කරන්න තිරස් මායිම් ඝණකම සිරස් මායිම් ඝණකම %1$s වර්ණය %1$s වර්ණ වත්මන් ආකෘතිය කුට්ටි කිරීමට සහය නොදක්වයි, රූපය මුල් මානයන්ගෙන් සකසනු ඇත, මෙය ඉහළ මතක පරිභෝජනයක් සහ අඩු-අන්ත උපාංග සමඟ ගැටලු ඇති කළ හැකිය කුට්ටි කිරීම අක්‍රියයි, රූපය මුල් මානයන්ගෙන් සකසනු ඇත, මෙය ඉහළ මතක පරිභෝජනයක් සහ අඩු-අන්ත උපාංග සමඟ ගැටලු ඇති කළ හැකි නමුත් අනුමාන මත වඩා හොඳ ප්‍රතිඵල ලබා දිය හැකිය කුට්ටි කිරීම පසුබිම ඉවත් කිරීම සඳහා ඉහළ නිරවද්‍ය රූප ඛණ්ඩන ආකෘතිය කුඩා මතක භාවිතය සමඟ වේගවත් පසුබිම ඉවත් කිරීම සඳහා U2Net හි සැහැල්ලු අනුවාදය. සම්පූර්ණ DDColor ආකෘතිය අවම කෞතුක වස්තු සහිත සාමාන්‍ය රූප සඳහා උසස් තත්ත්වයේ වර්ණ ගැන්වීම ලබා දෙයි. සියලුම වර්ණ ගැන්වීමේ මාදිලියේ හොඳම තේරීම. DDColor පුහුණු සහ පුද්ගලික කලාත්මක දත්ත කට්ටල; අඩු යථාර්ථවාදී නොවන වර්ණ කෞතුක වස්තු සමඟ විවිධ සහ කලාත්මක වර්ණ ගැන්වීමේ ප්‍රතිඵල නිපදවයි. නිවැරදි පසුබිම ඉවත් කිරීම සඳහා Swin Transformer මත පදනම් වූ සැහැල්ලු BiRefNet ආකෘතිය. තියුණු දාර සහිත උසස් තත්ත්වයේ පසුබිම ඉවත් කිරීම සහ විශිෂ්ට විස්තර සංරක්ෂණය, විශේෂයෙන් සංකීර්ණ වස්තූන් සහ උපක්‍රමශීලී පසුබිම් මත. සාමාන්‍ය වස්තූන් සහ මධ්‍යස්ථ විස්තර සංරක්ෂණය සඳහා සුදුසු සුමට දාර සහිත නිවැරදි වෙස් මුහුණු නිපදවන පසුබිම් ඉවත් කිරීමේ ආකෘතිය. ආකෘතිය දැනටමත් බාගත කර ඇත ආකෘතිය සාර්ථකව ආනයනය කරන ලදී ටයිප් කරන්න මූල පදය ඉතා වේගවත් සාමාන්යයි මන්දගාමී ඉතා මන්දගාමී ප්රතිශතය ගණනය කරන්න අවම අගය %1$s වේ ඇඟිලි වලින් ඇඳීමෙන් රූපය විකෘති කරන්න Warp දැඩි බව Warp මාදිලිය චලනය කරන්න වැඩෙන්න හැකිලෙන්න Swirl CW Swirl CCW දුර්වල ශක්තිය Top Drop පහළ වැටීම වැටීම ආරම්භ කරන්න End Drop බාගත කිරීම සිනිඳු හැඩතල සිනිඳු, වඩාත් ස්වාභාවික හැඩතල සඳහා සම්මත වටකුරු සෘජුකෝණාස්‍ර වෙනුවට සුපිරි ඉලිප්සාකාර භාවිතා කරන්න හැඩ වර්ගය කපනවා වටකුරු සිනිඳුයි වටකුරු තොරව තියුණු දාර සම්භාව්ය වටකුරු කොන් හැඩතල වර්ගය කෝනර් ප්රමාණය ලේනා අලංකාර වටකුරු UI මූලද්‍රව්‍ය ගොනු නාමය ආකෘතිය ව්‍යාපෘති නම්, වෙළඳ නාම, හෝ පුද්ගලික ටැග් සඳහා පරිපූර්ණ, ගොනු නාමයේ ආරම්භයේම තබා ඇති අභිරුචි පෙළ. මුල් ගොනු නාමය දිගුවකින් තොරව භාවිත කරයි, මූලාශ්‍ර හඳුනාගැනීම නොවෙනස්ව තබා ගැනීමට ඔබට උදවු කරයි. පික්සලවල රූපයේ පළල, විභේදන වෙනස්කම් හඹා යාමට හෝ ප්‍රතිඵල පරිමාණය කිරීමට ප්‍රයෝජනවත් වේ. පික්සලවල රූපයේ උස, දර්ශන අනුපාත හෝ අපනයන සමඟ වැඩ කිරීමේදී උපකාරී වේ. අද්විතීය ගොනු නාම සහතික කිරීම සඳහා අහඹු ඉලක්කම් ජනනය කරයි; අනුපිටපත් වලට එරෙහිව අමතර ආරක්ෂාවක් සඳහා තවත් ඉලක්කම් එකතු කරන්න. කණ්ඩායම් නිර්යාත සඳහා ස්වයංක්‍රීය වර්ධක කවුන්ටරය, එක් සැසියක පින්තූර කිහිපයක් සුරැකීමේදී වඩාත් සුදුසුය. රූපය සැකසූ ආකාරය ඔබට පහසුවෙන් මතක තබා ගත හැකි වන පරිදි ගොනු නාමයට යොදන ලද පෙරසැකසුම් නාමය ඇතුළත් කරයි. ප්‍රතිප්‍රමාණ කළ, කපන ලද, හෝ සවි කළ රූප වෙන්කර හඳුනා ගැනීමට උදවු කරමින්, සැකසීමේදී භාවිත කරන ලද රූප පරිමාණ ප්‍රකාරය සංදර්ශන කරයි. අභිරුචි පෙළ ගොනු නාමයේ අවසානයේ තබා ඇත, _v2, _edited, හෝ _final වැනි අනුවාද සඳහා ප්‍රයෝජනවත් වේ. ගොනු දිගුව (png, jpg, webp, ආදිය), සත්‍ය සුරැකි ආකෘතියට ස්වයංක්‍රීයව ගැලපේ. පරිපූර්ණ වර්ග කිරීම සඳහා java පිරිවිතර මගින් ඔබේම ආකෘතිය නිර්වචනය කිරීමට ඔබට ඉඩ සලසන අභිරුචිකරණය කළ හැකි වේලා මුද්දරයක්. ෆ්ලින්ග් වර්ගය Android ස්වදේශීය iOS විලාසය Smooth Curve ඉක්මන් නැවතුම බූන්සි පාවෙන කපටි Ultra Smooth අනුවර්තනය ප්‍රවේශ්‍යතා දැනුවත් අඩු කළ චලනය මූලික සංසන්දනය සඳහා දේශීය Android අනුචලන භෞතික විද්‍යාව සාමාන්‍ය භාවිතය සඳහා සමබර, සුමට අනුචලනය ඉහළ ඝර්ෂණ iOS වැනි අනුචලන හැසිරීම වෙනස් අනුචලන හැඟීම සඳහා අද්විතීය spline වක්රය ඉක්මන් නැවතුම් සමග නිවැරදි අනුචලනය සෙල්ලක්කාර, ප්‍රතිචාරාත්මක බූන්සි අනුචලනය අන්තර්ගත බ්‍රවුස් කිරීම සඳහා දිගු, ලිස්සා යන අනුචලන අන්තර්ක්‍රියාකාරී UI සඳහා ඉක්මන්, ප්‍රතිචාරාත්මක අනුචලනය විස්තීරණ ගම්‍යතාවයක් සහිත වාරික සුමට අනුචලනය ප්‍රවේගය මත පදනම්ව භෞතික විද්‍යාව සීරුමාරු කරයි පද්ධති ප්‍රවේශ්‍යතා සැකසීම් වලට ගරු කරයි ප්රවේශ්යතා අවශ්යතා සඳහා අවම චලනය ප්රාථමික රේඛා සෑම පස්වන පේළියකම ඝන රේඛාවක් එකතු කරයි වර්ණ පුරවන්න සැඟවුණු මෙවලම් බෙදාගැනීම සඳහා සැඟවුණු මෙවලම් වර්ණ පුස්තකාලය විශාල වර්ණ එකතුවක් පිරික්සන්න ස්වාභාවික විස්තර පවත්වා ගනිමින් රූපවලින් මුවහත් කර නොපැහැදිලි ඉවත් කරයි, අවධානයෙන් බැහැර ඡායාරූප සවි කිරීම සඳහා වඩාත් සුදුසුය. කලින් ප්‍රතිප්‍රමාණ කර ඇති පින්තූර බුද්ධිමත්ව ප්‍රතිසාධනය කරයි, නැතිවූ විස්තර සහ වයනය ප්‍රතිසාධනය කරයි. සජීවී-ක්‍රියාකාරී අන්තර්ගතයන් සඳහා ප්‍රශස්ත කර ඇත, සම්පීඩන කෞතුක වස්තු අඩු කරයි සහ චිත්‍රපට/රූපවාහිනී සංදර්ශන රාමු තුළ සියුම් විස්තර වැඩි දියුණු කරයි. VHS-ගුණාත්මක දර්ශන HD බවට පරිවර්තනය කරයි, ටේප් ශබ්දය ඉවත් කිරීම සහ වින්ටේජ් හැඟීම ආරක්ෂා කරමින් විභේදනය වැඩි දියුණු කරයි. පෙළ බර රූප සහ තිරපිටපත් සඳහා විශේෂිත, අක්ෂර මුවහත් කර කියවීමේ හැකියාව වැඩි දියුණු කරයි. විවිධ දත්ත කට්ටල මත පුහුණු කරන ලද උසස් ඉහළ නැංවීම, සාමාන්‍ය කාර්ය ඡායාරූප වැඩිදියුණු කිරීම සඳහා විශිෂ්ටයි. වෙබ් සම්පීඩිත ඡායාරූප සඳහා ප්‍රශස්ත කර ඇත, JPEG කෞතුක වස්තු ඉවත් කර ස්වභාවික පෙනුම යථා තත්වයට පත් කරයි. වඩා හොඳ වයනය සංරක්ෂණය සහ කෞතුක වස්තු අඩු කිරීම සමඟ වෙබ් ඡායාරූප සඳහා වැඩිදියුණු කළ අනුවාදය. ද්විත්ව එකතු කිරීමේ ට්‍රාන්ස්ෆෝමර් තාක්‍ෂණය සමඟ 2x ඉහළ නැංවීම, තියුණු බව සහ ස්වාභාවික විස්තර පවත්වා ගනී. උසස් ට්‍රාන්ස්ෆෝමර් ගෘහ නිර්මාණ ශිල්පය භාවිතයෙන් 3x ඉහළ නැංවීම, මධ්‍යස්ථ විශාල කිරීමේ අවශ්‍යතා සඳහා වඩාත් සුදුසුය. අති නවීන ට්‍රාන්ස්ෆෝමර් ජාලය සමඟ 4x උසස් තත්ත්වයේ ඉහළ නැංවීම, විශාල පරිමාණයෙන් සියුම් තොරතුරු ආරක්ෂා කරයි. ඡායාරූප වලින් නොපැහැදිලි/ශබ්ද සහ සෙලවීම් ඉවත් කරයි. සාමාන්‍ය අරමුණ නමුත් ඡායාරූප මත හොඳම. Swin2SR ට්‍රාන්ස්ෆෝමරය භාවිතයෙන් අඩු ගුණාත්මක රූප ප්‍රතිසාධනය කරයි, BSRGAN හායනය සඳහා ප්‍රශස්ත කර ඇත. බර සම්පීඩන කෞතුක වස්තු සවි කිරීම සහ 4x පරිමාණයෙන් විස්තර වැඩි දියුණු කිරීම සඳහා විශිෂ්ටයි. BSRGAN හායනය පිළිබඳ පුහුණු කරන ලද SwinIR ට්‍රාන්ස්ෆෝමරය සමඟ 4x ඉහළ නැංවීම. ඡායාරූප සහ සංකීර්ණ දර්ශනවල තියුණු වයනය සහ වඩාත් ස්වාභාවික විස්තර සඳහා GAN භාවිතා කරයි. මාර්ගය PDF ඒකාබද්ධ කරන්න PDF ගොනු කිහිපයක් එක් ලේඛනයකට ඒකාබද්ධ කරන්න ගොනු ඇණවුම pp. PDF බෙදන්න PDF ලේඛනයෙන් නිශ්චිත පිටු උපුටා ගන්න PDF කරකවන්න පිටු දිශානතිය ස්ථිරවම නිවැරදි කරන්න පිටු PDF නැවත සකස් කරන්න නැවත ඇණවුම් කිරීමට පිටු ඇද දමන්න පිටු අල්ලාගෙන අදින්න පිටු අංක ඔබගේ ලේඛනවලට ස්වයංක්‍රීයව අංකනය එක් කරන්න ලේබල් ආකෘතිය PDF සිට පෙළ (OCR) ඔබගේ PDF ලේඛන වලින් සරල පෙළ උපුටා ගන්න සන්නාමකරණය හෝ ආරක්ෂාව සඳහා අභිරුචි පෙළ ආවරණය කරන්න අත්සන ඕනෑම ලේඛනයකට ඔබේ විද්‍යුත් අත්සන එක් කරන්න මෙය අත්සනක් ලෙස භාවිතා කරනු ඇත PDF අගුළු හරින්න ඔබගේ ආරක්ෂිත ගොනු වලින් මුරපද ඉවත් කරන්න PDF ආරක්ෂා කරන්න ශක්තිමත් සංකේතනයකින් ඔබේ ලේඛන සුරක්ෂිත කරන්න සාර්ථකත්වය PDF අගුළු හැර ඇත, ඔබට එය සුරැකීමට හෝ බෙදා ගැනීමට හැකිය PDF අලුත්වැඩියා කරන්න දූෂිත හෝ කියවිය නොහැකි ලේඛන නිවැරදි කිරීමට උත්සාහ කිරීම අළු පරිමාණ සියලුම ලේඛන කාවැද්දූ පින්තූර අළු පරිමාණයට පරිවර්තනය කරන්න PDF සම්පීඩනය කරන්න පහසු බෙදාගැනීම සඳහා ඔබේ ලේඛන ගොනු ප්‍රමාණය ප්‍රශස්ත කරන්න ImageToolbox අභ්‍යන්තර හරස් යොමු වගුව නැවත ගොඩනඟන අතර මුල සිටම ගොනු ව්‍යුහය ප්‍රතිජනනය කරයි. මෙය \\"විවෘත කළ නොහැකි\\" බොහෝ ගොනු වෙත ප්‍රවේශය ප්‍රතිසාධනය කළ හැක. මෙම මෙවලම සියලුම ලේඛන රූප අළු පරිමාණයට පරිවර්තනය කරයි. ගොනු ප්රමාණය මුද්රණය කිරීම සහ අඩු කිරීම සඳහා හොඳම වේ පාරදත්ත වඩා හොඳ පෞද්ගලිකත්වය සඳහා ලේඛන ගුණාංග සංස්කරණය කරන්න ටැග් නිෂ්පාදකයා කර්තෘ මූල පද නිර්මාතෘ පෞද්ගලිකත්වය ගැඹුරු පිරිසිදු මෙම ලේඛනය සඳහා ලබා ගත හැකි සියලුම පාර-දත්ත හිස් කරන්න පිටුව ගැඹුරු OCR ලේඛනයෙන් පෙළ උපුටා ගෙන එය Tesseract එන්ජිම භාවිතයෙන් එක් පෙළ ගොනුවක ගබඩා කරන්න සියලුම පිටු ඉවත් කළ නොහැක PDF පිටු ඉවත් කරන්න PDF ලේඛනයෙන් නිශ්චිත පිටු ඉවත් කරන්න ඉවත් කිරීමට තට්ටු කරන්න අතින් PDF කපන්න ලේඛන පිටු ඕනෑම සීමාවකට කපන්න PDF සමතලා කරන්න ලේඛන පිටු රාස්ටර් කිරීමෙන් PDF වෙනස් කළ නොහැකි කරන්න කැමරාව ආරම්භ කිරීමට නොහැකි විය. කරුණාකර අවසර පරීක්ෂා කර එය වෙනත් යෙදුමක් විසින් භාවිතා නොකරන බවට වග බලා ගන්න. පින්තූර උපුටා ගන්න PDF වල තැන්පත් කර ඇති පින්තූර ඒවායේ මුල් විභේදනයෙන් උපුටා ගන්න මෙම PDF ගොනුවේ කිසිදු කාවැද්දූ පින්තූර අඩංගු නොවේ මෙම මෙවලම සෑම පිටුවක්ම පරිලෝකනය කර සම්පූර්ණ ගුණාත්මක මූලාශ්‍ර රූප ප්‍රතිසාධන කරයි - ලේඛනවලින් මුල් පිටපත් සුරැකීමට පරිපූර්ණයි අත්සන අඳින්න පෑන් පරාමිති ලේඛනවල තැබීමට රූපයක් ලෙස තමන්ගේ අත්සන භාවිතා කරන්න Zip PDF ලබා දී ඇති පරතරය සමඟ ලේඛනය වෙන් කර නව ලේඛන zip සංරක්ෂිතයට අසුරන්න අන්තරය PDF මුද්‍රණය කරන්න අභිරුචි පිටු ප්‍රමාණය සමඟ මුද්‍රණය සඳහා ලේඛනය සකස් කරන්න පත්‍රයකට පිටු දිශානතිය පිටු ප්‍රමාණය ආන්තිකය බ්ලූම් මෘදු දණහිස සජීවිකරණ සහ කාටූන් සඳහා ප්‍රශස්ත කර ඇත. වැඩි දියුණු කළ ස්වභාවික වර්ණ සහ අඩු කෞතුක වස්තු සමඟ වේගවත් ඉහළ නැංවීම Samsung One UI 7 වැනි විලාසය අපේක්ෂිත අගය ගණනය කිරීමට මූලික ගණිත සංකේත මෙහි ඇතුළත් කරන්න (උදා. (5+5)*10) ගණිත ප්රකාශනය පින්තූර %1$s දක්වා ගන්න ඇල්ෆා ආකෘති සඳහා පසුබිම් වර්ණය ඇල්ෆා සහාය ඇතිව සෑම රූප ආකෘතියක් සඳහාම පසුබිම් වර්ණය සැකසීමේ හැකියාව එක් කරයි, අක්‍රිය කළ විට මෙය ඇල්ෆා නොවන ඒවා සඳහා පමණි දිනය වේලාව තබා ගන්න සෑම විටම දිනය සහ වේලාවට අදාළ exif ටැග් සුරකින්න, Keep exif විකල්පයෙන් ස්වාධීනව ක්‍රියා කරයි විවෘත ව්යාපෘතිය කලින් සුරකින ලද රූප මෙවලම් පෙට්ටි ව්‍යාපෘතියක් සංස්කරණය කිරීම දිගටම කරගෙන යන්න රූප මෙවලම් පෙට්ටිය ව්‍යාපෘතිය විවෘත කළ නොහැක රූප මෙවලම් පෙට්ටිය ව්‍යාපෘතියේ ව්‍යාපෘති දත්ත මඟ හැරී ඇත රූප මෙවලම් පෙට්ටිය ව්‍යාපෘතිය දූෂිතයි සහාය නොදක්වන රූප මෙවලම් පෙට්ටිය ව්‍යාපෘති අනුවාදය: %1$d ව්යාපෘතිය සුරකින්න සංස්කරණය කළ හැකි ව්‍යාපෘති ගොනුවක ස්තර, පසුබිම සහ සංස්කරණ ඉතිහාසය ගබඩා කරන්න විවෘත කිරීමට අසමත් විය සෙවිය හැකි PDF වෙත ලියන්න පින්තූර කාණ්ඩයෙන් පෙළ හඳුනාගෙන සෙවිය හැකි PDF රූපය සහ තෝරාගත හැකි පෙළ ස්ථරය සමඟ සුරකින්න ඇල්ෆා ස්ථරය තිරස් පෙරළීම සිරස් පෙරළීම අගුළු දමන්න සෙවනැල්ල එකතු කරන්න සෙවනැලි වර්ණය පෙළ ජ්යාමිතිය තියුණු ශෛලීකරණය සඳහා පෙළ දිගු කරන්න හෝ ඇල කරන්න X පරිමාණය ස්කීව් X විවරණ ඉවත් කරන්න PDF පිටුවලින් සබැඳි, අදහස්, උද්දීපනය, හැඩතල, හෝ පෝරම ක්ෂේත්‍ර වැනි තෝරාගත් විවරණ වර්ග ඉවත් කරන්න අධි සබැඳි ගොනු ඇමුණුම් රේඛා උත්පතන මුද්දර හැඩතල පෙළ සටහන් පෙළ සලකුණු කිරීම ආකෘති ක්ෂේත්ර සලකුණු කිරීම නොදන්නා විවරණ සමූහ ඉවත් කරන්න වින්‍යාසගත කළ හැකි වර්ණ සහ ඕෆ්සෙට් සහිත ස්ථරය පිටුපස බොඳ සෙවනැල්ල එක් කරන්න ================================================ FILE: core/resources/src/main/res/values-sk/strings.xml ================================================ Priblíženie obrázka Niečo sa pokazilo: %1$s Veľkosť %1$s Načítava sa… Obrázok je príliš veľký na zobrazenie náhľadu, ale aj tak sa ho pokúsime uložiť Začnite výberom obrázka Šírka %1$s Výška %1$s Kvalita Rozšírenie Zmena typu veľkosti Explicitné Flexibilné Vybrať obrázok Naozaj chcete zatvoriť aplikáciu? Aplikácia sa zatvára Zostať Zatvoriť Obnoviť obrázok Zmeny obrázka sa vrátia na pôvodné hodnoty Hodnoty boli úspešne obnovené Obnovenie Niečo sa pokazilo Reštartovať aplikáciu Skopírované do schránky Výnimka Upraviť EXIF OK Neboli nájdené žiadne údaje EXIF Pridať značku Uložiť Vymazať Vymazať EXIF Zrušiť Všetky údaje EXIF obrázka budú vymazané. Túto akciu nie je možné vrátiť späť! Predvoľby Orezať Ukladanie Všetky neuložené zmeny sa stratia, ak teraz skončíte Zdrojový kód Získajte najnovšie aktualizácie, diskutujte o problémoch a ešte oveľa viac Jednotlivá úprava Úprava, zmena veľkosti a editácia jedného obrázku Výber farieb Vybrať farbu z obrázka, kopírovať alebo zdieľať Obrázok Farba Farba skopírovaná Orezať obrázok na ľubovoľné rozmery Verzia Ponechať EXIF Obrázky: %d Zmeniť náhľad Odstrániť Vygenerovať vzorku farebnej palety z daného obrázka Vytvoriť paletu Paleta Aktualizácia Nová verzia %1$s Nepodporovaný typ: %1$s Nedá sa vygenerovať paleta pre daný obrázok Originál Výstupný priečinok Predvolené Vlastné Nešpecifikované Úložisko zariadenia Upraviť veľkosť Maximálna veľkosť v KB Zmena veľkosti obrázka podľa zadanej veľkosti v KB Porovnať Porovnať dva dané obrázky Začnite výberom dvoch obrázkov Vybrať obrázky Nastavenia Nočný režim Tmavý Svetlý Systém Dynamické farby Prispôsobenie Povoliť monetizáciu obrázka Ak je táto možnosť povolená, farby aplikácie sa prispôsobia vybranému obrázku, ktorý upravujete Jazyk Čierny tmavý režim Ak je táto možnosť povolená, farba povrchov bude v nočnom režime nastavená na úplne tmavú Farebná schéma Červená Zelená Modrá Vložte platný kód farby aRGB Nič na vloženie Farebnú schému aplikácie nie je možné zmeniť, keď sú zapnuté dynamické farby Téma aplikácie bude založená na zvolenej farbe O aplikácii Nenašli sa žiadne aktualizácie Sledovanie problémov Sem posielajte hlásenia o chybách a návrhy na nové funkcie Pomôžte s prekladom Opravte chyby v preklade alebo lokalizujte projekt do iných jazykov Podľa vášho dotazu sa nič nenašlo Hľadať tu Ak je táto možnosť povolená, farby aplikácie sa prispôsobia farbám tapety Nepodarilo sa uložiť %d obrázok (obrázkov) Primárne Terciárne Sekundárne Hrúbka okraja Povrch Hodnoty Pridať Povolenie Grant Aplikácia potrebuje prístup k vášmu úložisku na ukladanie obrázkov, je to nevyhnutné. Prosím, udeľte povolenie v nasledujúcom dialógovom okne. Aplikácia potrebuje toto povolenie na svoju funkčnosť, prosím, udeľte ho manuálne Externé úložisko Monet farby Táto aplikácia je úplne zadarmo, ale ak chcete podporiť vývoj projektu, môžete kliknúť sem Zarovnanie plávajúceho akčného tlačidla Kontrola aktualizácií Ak je táto možnosť povolená, pri spustení aplikácie sa zobrazí dialógové okno s aktualizáciou Zdieľať Predpona Názov súboru Jas Expozícia vyváženie bielej Teplota Odtieň Monochromatický Gamma Effect Vzdialenosť Vibrancia Čierna a biela Crosshatch Medzery Šírka čiary Okraj Sobel farebný priestor GCA Gaussovské rozostrenie Box rozostrenie Embosovať Odstrániť EXIF Filter Alfa Zvýraznenie Tiene Haze Rozmazať Zmeniť veľkosť s obmedzeniami Zmeňte veľkosť vybraných obrázkov na danú šírku a výšku pri zachovaní pomeru strán Skica Prah Nie maximálne potlačenie Slabé zahrnutie pixelov Emoji Vyberte, ktoré emotikony sa zobrazia na hlavnej obrazovke Rozmazanie zásobníka Konvolúcia 3x3 RGB filter Falošná farba Prvá farba Druhá farba Rýchle rozostrenie Veľkosť rozmazania Rozmazať stred x Rozmazať stred y Prahová hodnota jasu Obojstranné rozostrenie Pridajte veľkosť súboru Ak je povolené, pridá k názvu výstupného súboru šírku a výšku uloženého obrázka Odstráňte metadáta EXIF z ľubovoľného páru obrázkov Použite zámer GetContent na výber obrázka, funguje všade, ale môže mať problémy s prijímaním vybratých obrázkov na niektorých zariadeniach, nie je to moja chyba Usporiadanie možností Upraviť objednať Určuje poradie nástrojov na hlavnej obrazovke poradové číslo pôvodný názov súboru Ak je povolené, nahradí štandardnú časovú pečiatku poradovým číslom obrázka, ak používate dávkové spracovanie Načítajte ľubovoľný obrázok z internetu, zobrazte jeho ukážku, priblížte ho a ak chcete, uložte ho alebo upravte Odkaz na obrázok Vyplňte Fit Zmení veľkosť obrázkov na obrázky s dlhou stranou danou parametrom Width alebo Height, všetky výpočty veľkosti sa vykonajú po uložení - zachováva pomer strán Kontrast Hue Sýtosť Pridajte filter Na dané obrázky použite ľubovoľný reťazec filtrov Filtre Svetlo Farebný filter Svetlá a tiene Svah Zostriť Sépia Negatívne Solarizovať Poltón Laplacian Ďialničná známka Štart Koniec Kuwaharské vyhladenie Polomer Mierka Skreslenie Uhol Krúživým pohybom Vydutie Rozšírenie Sférický lom Index lomu Lom sklenenej gule Farebná matrica Nepriehľadnosť Úrovne kvantovania Hladký toon Toon Posterizovať Vyhľadať Zmeniť poradie Zoom rozostrenie Vyváženie farieb Ukážka obrázka Ukážka akéhokoľvek typu obrázkov: GIF, SVG atď Moderný nástroj na výber fotografií pre Android, ktorý sa zobrazuje v spodnej časti obrazovky, môže fungovať iba v systéme Android 12+ a má tiež problémy s prijímaním metadát EXIF Zdroj obrázka Výber fotografií Galéria Prieskumník súborov Jednoduchý výber obrázkov galérie, bude fungovať, iba ak máte túto aplikáciu Načítať obrázok z internetu Žiadny obrázok Vynúti každý obrázok na obrázok daný parametrom Šírka a Výška – môže sa zmeniť pomer strán Obsahová mierka Počet emodži Pridajte pôvodný názov súboru Ak je povolené, pridá pôvodný názov súboru do názvu výstupného obrázka Pridanie pôvodného názvu súboru nefunguje, ak je vybratý zdroj obrázka na výber fotografií Nahraďte poradové číslo Nakreslite na obrázok Veľkosť súboru Pokus o uloženie obrázka so zadanou šírkou a výškou môže spôsobiť chybu nedostatku pamäte. Robíte to na vlastné riziko Záložná možnosť Preskočiť Ukladanie v režime %1$s môže byť nestabilné, pretože ide o bezstratový formát Zakázali ste aplikáciu Súbory, aktivujte ju, aby ste mohli používať túto funkciu Kresliť Nakreslite obrázok ako v skicári alebo nakreslite samotné pozadie Vyberte si obrázok a niečo naň nakreslite Farba pozadia Dešifrovať kľúč Uložte tento súbor na svoje zariadenie alebo ho pomocou akcie zdieľania umiestnite kamkoľvek chcete Šifrovanie súborov na základe hesla. Pokračujúce súbory môžu byť uložené vo vybranom adresári alebo zdieľané. Dešifrované súbory je možné otvárať aj priamo. Maximálna veľkosť súboru je obmedzená operačným systémom Android a dostupnou pamäťou, čo samozrejme závisí od vášho zariadenia. \nPoznámka: pamäť nie je úložisko. Veľkosť vyrovnávacej pamäte Cache Nájdené %1$s Automatické vymazanie vyrovnávacej pamäte Ak je povolená, vyrovnávacia pamäť aplikácie sa vymaže pri spustení aplikácie Nie je možné zmeniť usporiadanie, kým je povolené zoskupovanie možností Upravte snímku obrazovky Sekundárne prispôsobenie Snímka obrazovky Neplatné heslo alebo zvolený súbor nie je zašifrovaný Šifrovať Vlastnosti Kreslenie na pozadí Vyberte farbu pozadia a nakreslite na ňu Začnite výberom súboru Šifrovanie Upozorňujeme, že kompatibilita s iným softvérom alebo službami na šifrovanie súborov nie je zaručená. Príčinou nekompatibility môže byť mierne odlišné spracovanie kľúča alebo konfigurácia šifry. Kopírovať Farba laku Farba alfa Šifra Šifrovanie a dešifrovanie akéhokoľvek súboru (nielen obrázka) na základe rôznych dostupných šifrovacích algoritmov Vyberte súbor Dešifrovanie Implementácia Kompatibilita AES-256, režim GCM, bez vypĺňania (paddingu), predvolene 12-bajtové náhodné IV. Môžete si vybrať požadovaný algoritmus. Kľúče sa používajú ako 256-bitové SHA-3 hashe Súbor spracovaný Nástroje Zoskupte možnosti podľa typu Možnosti skupín na hlavnej obrazovke ich typu namiesto vlastného usporiadania zoznamu Vytvorte E-mail Vylepšené pixelovanie Pixelizácia po ťahu Vylepšená diamantová pixelizácia Vymazať Maska strihu Vylepšená chyba Posun kanála Y Bočná strana Dole Príroda a zvieratá Jedlo a nápoje Polomer rozostrenia Odstráňte pozadie z obrázka kreslením alebo použite možnosť Auto. Emócie Aktualizácie Iba orientácia a rozpoznávanie písma Automatická orientácia a rozpoznávanie písma Iba automaticky Auto Vertikálny text v jednom bloku Zakrúžkuj slovo Jeden znak Surová linka Chyba Suma Semená Anaglyf Hluk Triedenie podľa pixelov Zamiešať Chystáte sa odstrániť vybranú masku filtra. Táto operácia nemôže byť vrátená späť Odstrániť masku Drago Aldridge Odstrihnúť Uchimura Mobius Prechod Vrchol Farebná anomália Schránka Nenašiel sa adresár „%1$s“, prešli sme na predvolený adresár. Uložte súbor prosím znovu. Automatický pin Ak je táto funkcia zapnutá, uložený obrázok sa automaticky pridá do schránky Vibrácie Prepísať súbory Pôvodný súbor bude nahradený novým namiesto uloženia do vybranej zložky; táto možnosť vyžaduje, aby zdrojom obrázku bol „Explorer“ alebo „GetContent“; pri zapnutí tejto funkcie sa nastavenie vykoná automaticky Prázdny Prípona Hľadať Umožňuje vyhľadávať vo všetkých dostupných nástrojoch na hlavnej obrazovke Voľne Obrázky boli prepísané v pôvodnom umiestnení Pri zapnutej možnosti prepisovania súborov nie je možné zmeniť formát obrázku Emoji ako farebná schéma Používa základnú farbu emodži ako farebnú schému aplikácie namiesto ručne definovanej Uložené do priečinka %1$s Pomer strán Tento typ masky použite na vytvorenie masky z daného obrázku, pričom je potrebné mať na pamäti, že by mala mať alfa kanál Zálohovanie a obnovenie Záloha Chystáte sa odstrániť vybranú farebnú schému. Táto operácia nemôže byť vrátená späť. Obnoviť Zálohujte nastavenia aplikácie do súboru Obnoviť nastavenia aplikácie z predtým vytvoreného súboru Poškodený súbor alebo chýbajúca záloha Kontaktujte ma Týmto sa vaše nastavenia vrátia na predvolené hodnoty. Upozorňujeme, že bez vyššie spomenutého záložného súboru nie je možné túto akciu vrátiť späť. Vymazať schému Písmo Text Veľkosť písma Predvolené nastavenie Použitie veľkých škál písma môže spôsobiť chyby a problémy v používateľskom rozhraní, ktoré nebudú opravené. Používajte opatrne. Aa Áá Ää Bb Cc Čč Dd Ďď Ee Éé Ff Gg Hh Ii Íí Jj Kk Ll Ĺĺ Ľľ Mm Nn Ňň Oo Óó Ôô Pp Qq Rr Ŕŕ Ss Šš Tt Ťť Uu Úú Vv Ww Xx Yy Zz Žž 0123456789 !? Objekty Symboly Cesty a miesta Aktivity Odstránenie pozadia Orezanie obrázku Pôvodné metadáta obrázku zostanú zachované Transparentné priestory okolo obrázku budú orezané. Automatické vymazanie pozadia Obnoviť obrázok Režim vymazania Vymazať pozadie Pipeta Režim kreslenia Vytvoriť problém Ups… Niečo sa pokazilo. Môžete mi napísať pomocou nižšie uvedených možností a ja sa pokúsim nájsť riešenie Zmeňte veľkosť daných obrázkov alebo ich konvertujte do iných formátov. Ak vyberiete jeden obrázok, môžete tu tiež upravovať metadáta EXIF. Maximálny počet farieb Povoliť zbieranie anonymných štatistík o používaní aplikácie V súčasnosti formát %1$s umožňuje čítať metadáta EXIF iba v systéme Android. Výstupný obrázok nebude mať po uložení žiadne metadáta. Hodnota %1$s znamená rýchlu kompresiu, čo má za následok relatívne veľkú veľkosť súboru. %2$s znamená pomalšiu kompresiu, čo má za následok menší súbor. Povoliť beta verzie Malé obrázky budú zväčšené na najväčší obrázok v sekvencii, ak je táto funkcia povolená Diamantová pixelizácia Kruhová pixelizácia Cieľová farba Farba k vymazaniu Odstrániť farbu Hlasná téma, farebnosť je maximálna pre primárnu paletu, zvýšená pre ostatné Hravá téma – odtieň zdrojovej farby sa v téme nezobrazuje. Monochromatická téma, farby sú čisto čierne / biele / sivé Schéma, ktorá umiestňuje zdrojovú farbu do Scheme.primaryContainer Schéma, ktorá je veľmi podobná schéme obsahu Obrázky do PDF Previesť PDF na obrázky v danom výstupnom formáte Zabaliť obrázky do výstupného súboru PDF Jednoduché varianty Zvýrazňovač Neón Pero Rozmazanie pre súkromie Pridajte svojim kresbám žiarivý efekt Predvolené, najjednoduchšie – len farba Podobné ako rozmazanie súkromia, ale namiesto rozmazania sa použije pixelizácia. Automatické otáčanie Umožňuje použiť obmedzujúce pole pre orientáciu obrázku. Dvojitá šípka Čiara Šípka Šípka Nakreslí šípku smerujúcu od počiatočného bodu k koncovému bodu ako čiaru. Nakreslí ukazovaciu šípku z danej cesty Nakreslí dvojitú šípku od počiatočného bodu do koncového bodu ako čiaru. Nakreslí dvojitú šípku z danej cesty Nakreslí ohraničený ovál od počiatočného bodu po koncový bod Nakreslí obdĺžnik s ohraničením od počiatočného bodu po koncový bod Intenzita vibrácií Ak chcete prepísať súbory, musíte použiť zdroj obrázkov „Explorer“. Skúste obrázky vybrať znovu – zdroj obrázkov sme zmenili na požadovaný. Bikubický Lineárna (alebo v dvoch rozmeroch bilineárna) interpolácia sa zvyčajne hodí na zmenu veľkosti obrázku, spôsobuje však neželané zmäkčenie detailov a výsledok môže byť stále trochu zubatý Medzi lepšie metódy škálovania patria Lanczosovo prevzorkovanie a filtre Mitchell-Netravali Jedným z jednoduchších spôsobov, ako zväčšiť obrázok, je nahradiť každý pixel viacerými pixelmi rovnakej farby Najjednoduchší režim zmeny mierky v systéme Android, ktorý sa používa takmer vo všetkých aplikáciách Metóda plynulej interpolácie a prepočítania súboru kontrolných bodov, bežne používaná v počítačovej grafike na vytváranie plynulých kriviek Funkcia okienkovania sa často používa pri spracovaní signálov na minimalizáciu spektrálneho úniku a zvýšenie presnosti frekvenčnej analýzy prostredníctvom zaoblenia okrajov signálu Matematická interpolačná technika, ktorá využíva hodnoty a derivácie v koncových bodoch úseku krivky na vytvorenie hladkej a spojitej krivky Metóda prevzorkovania, ktorá zachováva vysokú kvalitu interpolácie pomocou aplikácie váhovej sincovej funkcie na hodnoty pixelov Metóda prevzorkovania, ktorá využíva konvolučný filter s nastaviteľnými parametrami na dosiahnutie rovnováhy medzi ostrosťou a vyhladzovaním v zmenšenom obrázku Využívajú po častiach definované polynómové funkcie na plynulú interpoláciu a aproximáciu krivky alebo plochy, čím poskytujú flexibilné a spojité znázornenie tvaru Jeden stĺpec Jeden blok Jeden riadok Jedno slovo Rozptýlený text – rozpoznávanie orientácie a písma Riedky text Chcete odstrániť trénovacie údaje OCR pre jazyk „%1$s“ pre všetky typy rozpoznávania, alebo len pre vybraný typ (%2$s)? Všetko Opakovať vodoznak Vzor sa opakuje po celom obrázku namiesto toho, aby sa zobrazoval len raz na určenej pozícii Posun X Posun Y Bayer Eight By Eight Dithering Floyd Steinberg Dithering Jarvis Judice Ninke Dithering Two Row Sierra Dithering Sierra Lite Dithering Atkinson Dithering Stucki Dithering Dithering zľava doprava Náhodné rozmazávanie Posun kanála X Veľkosť korupcie Korupčný posun X Posun korupcie Y Rozmazanie stanu Prechod stránok Hore Sila Vintage Browni Coda Chrome Nočné videnie Teplý Studený Tritanopia Ružový sen Zlatá hodinka Horúce leto Fialová hmla Východ slnka Farebný vír Jemné jarné svetlo Jesenné odtiene Levanduľový sen Cyberpunk Limonádové svetlo Elektrický gradient Deep Purple Vesmírny portál Červený vír Digitálny kód Ak je táto funkcia povolená, názov výstupného súboru bude úplne náhodný. Uložené do priečinka %1$s s názvom %2$s Povoliť emoji Obnoviť pozadie Zmena veľkosti a konverzia Toto umožňuje aplikácii automaticky zbierať hlásenia o zlyhaniach Analytika Úsilie Mäkkosť štetca Obrázky budú orezané na stred na zadanú veľkosť. Plátno bude rozšírené o zadanú farbu pozadia, ak je obrázok menší ako zadané rozmery. Darcovstvo Pixelizácia Vylepšené kruhové rozostrenie Nahradiť farbu Tolerancia Farba k výmene Prekódovať Erode Anizotropná difúzia Difúzia Vedenie Horizontálne striedanie pri vetre Rýchle obojstranné rozmazanie Poissonovo rozmazanie Logaritmické mapovanie tónov ACES filmové mapovanie tónov Kryštalizovať Farba ťahu Fraktálne sklo Amplitúda Mramor Turbulencia Olej Vodný efekt Veľkosť Frekvencia X Frekvencia Y Amplitúda X Amplitúda Y Perlinovo skreslenie Hable – filmové tónové mapovanie Tónové mapovanie podľa Hejiho-Burgessa ACES Hill – mapovanie tónov Aktuálny Úplný filter Štart Uprostred Koniec Použite ľubovoľné reťazce filtrov na dané obrázky alebo jeden obrázok Práca s PDF súbormi: náhľad, konverzia na súbor obrázkov alebo vytvorenie obrázku z daných obrázkov Náhľad PDF PDF do obrázkov Jednoduchý náhľad PDF Nástroj na vytváranie prechodov Vytvorte prechod s danou výstupnou veľkosťou s vlastnými farbami a typom vzhľadu Rýchlosť Odstrániť hmlu Omega PDF nástroje Ohodnoťte aplikáciu Hodnotiť Táto aplikácia je úplne zadarmo. Ak chcete, aby sa ďalej rozvíjala, označte projekt na Githubu hviezdičkou 😄 Farebná matica 4x4 Farebná matica 3x3 Jednoduché efekty Polaroid Tritanomália Deuteranomália Protanomália Deutaronotopia Protanopia Achromatomaly Achromatopsia Lineárny Radiálny Zametanie Typ gradientu Stred X Stred Y Režim dlaždíc Opakované Zrkadlo Svorka Nálepka Farebné prechody Pridať farbu Vlastnosti Laso Nakreslí uzavretú vyplnenú krivku podľa zadaného priebehu Režim kreslenia cesty Dvojitá čiara so šípkou Voľné kreslenie Čiara Nakreslí cestu ako vstupnú hodnotu Nakreslí cestu od počiatočného bodu do koncového bodu ako čiaru. Obrysovaný ovál Obrysovaný obdĺžnik Ovál Rect Nakreslí obdĺžnik od počiatočného bodu po koncový bod Nakreslí ovál od počiatočného bodu po koncový bod Dithering Kvantizér Šedá stupnica Bayer Two By Two Dithering Bayer Three By Three Dithering Bayer Four By Four Dithering Sierra Dithering Burkes Dithering Falošný Floyd Steinberg Dithering Jednoduché prahové rozmazávanie Diskutujte o aplikácii a získajte spätnú väzbu od ostatných používateľov. Môžete tam tiež získať beta aktualizácie a zaujímavé informácie. Nastavenia boli úspešne obnovené Počkajte Ukladanie je takmer dokončené. Ak teraz zrušíte, budete musieť ukladať znova. Farba masky Náhľad masky Nakreslená maska filtra sa vykreslí, aby vám ukázala približný výsledok Režim mierky Bilineárny Hann Hermite Lanczos Mitchell Najbližší Spline Základný Predvolená hodnota Hodnota v rozsahu %1$s - %2$s Sigma Priestorová Sigma Stredné rozostrenie Catmull Len klip Uloženie do úložiska sa neuskutoční a systém sa pokúsi iba vložiť obrázok do schránky Pridá pod ikony kontajner s vybraným tvarom Tvar ikony Spojenie obrázkov Spojte dané obrázky, aby ste získali jeden veľký obrázok. Ak ste vybrali predvoľbu 125, obrázok sa uloží vo veľkosti 125 % pôvodného obrázka. Ak zvolíte predvoľbu 50, obrázok sa uloží s veľkosťou 50 %. Vynútenie jasu Obrazovka Prekrývanie s prechodom Vytvorte ľubovoľný farebný prechod na vrchnej časti daných obrázkov Premeny Kamera Vyfoťte sa fotoaparátom. Upozorňujeme, že z tohto zdroja je možné získať len jeden obrázok Vyberte aspoň 2 obrázky Mierka výstupného obrázku Orientácia obrázku Vodorovne Obilie Neostrý Pastel Oranžová hmla Spektrálny oheň Nočná mágia Fantastická krajina Farebná explózia Karamelová temnota Futuristický prechod Zelené slnko Dúhový svet Vodoznaky Obrázky na obálke s prispôsobiteľnými textovými/obrazovými vodoznakmi Typ vodoznaku Tento obrázok sa použije ako vzor pre vodotlač Farba textu Režim prekrývania Veľkosť pixelu Uzamknúť orientáciu kreslenia Ak je táto funkcia povolená v režime kreslenia, obrazovka sa nebude otáčať Bokeh Nástroje pre GIF Previesť obrázky do formátu GIF alebo extrahovať snímky z daného obrázku vo formáte GIF GIF k obrázkom Previesť súbor GIF na súbor obrázkov Previesť skupinu obrázkov do súboru GIF Obrázky do formátu GIF Vyberte obrázok vo formáte GIF a začnite Použiť veľkosť prvého snímku Nahraďte zadanú veľkosť rozmermi prvého snímku Počet opakovaní Oneskorenie snímky millis FPS Použiť Lasso Používa nástroj Lasso podobne ako v režime kreslenia na vymazávanie Náhľad pôvodného obrázka Alpha Filter masky Použiť reťazce filtrov na dané maskované oblasti, každá maskovaná oblasť môže určiť vlastnú sadu filtrov. Masky Pridať masku Emoji na paneli aplikácií sa budú náhodne meniť Náhodné emodži Keď sú emodži vypnuté, nemôžete používať náhodné emodži Ak je zapnutá funkcia náhodného výberu emodži, nemôžete si vybrať konkrétne emodži Skontrolujte aktualizácie Predvoľba tu určuje % výstupného súboru, t.j. ak vyberiete predvoľbu 50 pri 5 MB obrázku, po uložení dostanete obrázok s veľkosťou 2,5 MB Náhodné pomenovanie súboru Telegramový chat Kontrola aktualizácií bude zahŕňať beta verzie aplikácií, ak je táto funkcia povolená Kresliť šípky Stará televízia Náhodné rozmazanie OCR (rozpoznávanie textu) Rozpoznávanie textu z daného obrázku podporuje viac ako 120 jazykov Na obrázku nie je žiadny text alebo aplikácia ho nenašla Presnosť: %1$s Typ rozpoznania Rýchlo Štandardne Najlepšie Žiadne údaje Aby aplikácia Tesseract OCR správne fungovala, je potrebné do vášho zariadenia stiahnuť ďalšie trénovacie dáta (%1$s).\nChcete stiahnuť dáta %2$s? Stiahnuť Nie je pripojenie, skontrolujte ho a skúste to znova, aby ste mohli stiahnuť modely vlakov Stiahnuté jazyky Dostupné jazyky Režim segmentácie Štetec obnoví pozadie namiesto toho, aby ho vymazal Vodorovná mriežka Vertikálna mriežka Režim stehu Počet riadkov Počet stĺpcov Použiť prepínač Pixel Používa prepínač podobný tomu v telefóne Google Pixel Sklz Vedľa seba Prepnúť klepnutím Transparentnosť Súbor s názvom %1$s v pôvodnom umiestnení bol prepísaný Lupa V režimoch kreslenia aktivuje lupu na špičke prsta, čím sa zlepšuje prístupnosť Vynútiť počiatočnú hodnotu Vynúti počiatočné zaškrtnutie widgetu exif Povoliť viacero jazykov Obľúbené Zatiaľ neboli pridané žiadne obľúbené filtre B Spline Využíva po častiach definované bikubické polynómové funkcie na plynulú interpoláciu a aproximáciu krivky alebo plochy, čo umožňuje flexibilné a spojité znázornenie tvaru Pôvodné rozostrenie Zmena sklonu Pravidelný Rozmazané okraje Inverzné vyplnenie Ak je táto funkcia povolená, všetky nemaskované oblasti budú filtrované namiesto predvoleného správania Konfety Pri ukladaní, zdieľaní a ďalších základných akciách sa zobrazí konfety Zabezpečený režim Skryje obsah aplikácie v zozname nedávno použitých aplikácií. Nedá sa zachytiť ani nahrať. Ak je táto funkcia povolená, kresliaca dráha bude znázornená ako smerová šípka Vertikálne Zväčšovanie malých obrázkov na veľké Poradie obrázkov Ak je táto funkcia povolená, nakreslí rozmazané okraje pod pôvodným obrázkom, aby vyplnila priestor okolo neho namiesto jednoliatej farby Maska %d Paleta štýlu Tónový bod Neutrálny Živý Výrazový Dúha Ovocný šalát Vernosť Obsah Predvolený štýl palety, umožňuje prispôsobiť všetky štyri farby, ostatné umožňujú nastaviť len kľúčovú farbu Štýl, ktorý je o niečo farebnejší ako monochromatický Nakreslite polopriehľadné ostro zvýraznené cesty zvýrazňovača Rozmazáva obraz pod nakreslenou cestou, aby zabezpečil všetko, čo chcete skryť. Kontajnery Nakresliť tieň za kontajnery Posuvníky Prepínače FAB Tlačidlá Nakreslí tieň za posuvníkmi Nakreslí tieň za prepínačmi Nakreslí tieň za plávajúce akčné tlačidlá Nakreslí tieň za tlačidlami Lišty aplikácií Nakreslí tieň za lištami aplikácií Tento nástroj na kontrolu aktualizácií sa pripojí k GitHubu, aby skontroloval, či je k dispozícii nová aktualizácia. Pozor Vyblednuté okraje Vypnuté Oboje Invertovať farby Ak je táto funkcia povolená, nahradí farby motívu negatívnymi farbami Ukončiť Ak teraz opustíte náhľad, budete musieť obrázky pridať znova Formát obrázku Vytvorí paletu \"Material You\" na základe obrázku Tmavé farby Používa farebnú schému nočného režimu namiesto svetelného variantu Kopírovať ako kód \"Jetpack Compose\" Rozostrenie Prsteňa Krížové rozostrenie Rozmazanie kruhu Rozmazanie hviezd Lineárny posun sklonu Značky na odstránenie Nástroje APNG Preveďte obrázky na obrázok APNG alebo extrahujte snímky z daného obrázka APNG Previesť súbor APNG na dávku obrázkov Preveďte dávku obrázkov do súboru APNG Obrázky do APNG Pohybový efekt APNG k obrázkom Začnite výberom obrázka APNG PSČ Vytvorte súbor zip z daných súborov alebo obrázkov Šírka rukoväte ťahania Typ konfety Slávnostné JXL nástroje Vstavaný výberový nástroj Nastavenia na celú obrazovku Konvertujte APNG obrázky na animované JXL obrázky Dátum (obrátené poradie) Dátum Vybrať viacero médií Používa prepínač Material You založený na View, tento vyzerá lepšie ako ostatné a má pekné animácie. Rýchle Gaussovo rozostrenie 3D Cupertino Používa vlastný nástroj Image Toolbox na výber obrázkov namiesto preddefinovaných v systéme Žiadne povolenia Žiadosť Umožňuje aplikácii automaticky prilepiť údaje zo schránky, takže sa zobrazia na hlavnej obrazovke a budete ich môcť spracovať Konvertujte dávku obrázkov na JXL animáciu Ovláda rýchlosť dekódovania výsledného obrázka, čo by malo pomôcť rýchlejšiemu otvoreniu výsledného obrázka. Hodnota %1$s znamená najpomalšie dekódovanie, zatiaľ čo %2$s je najrýchlejšie. Toto nastavenie môže zvýšiť veľkosť výstupného obrázka Umožňuje generovanie náhľadov, čo môže pomôcť predísť zlyhaniu na niektorých zariadeniach. Taktiež to deaktivuje niektoré editačné funkcie v rámci jednej možnosti úprav Včera Skúste to znova Používa prepínač Material You z Jetpack Compose, no nie je taký pekný ako ten založený na View Názov Konfigurácia kanálov Názov (obrátené poradie) Dnes Vybrať jedno médium Vybrať Zobraziť nastavenia v režime na šírku Ak je toto vypnuté, v režime na šírku sa nastavenia otvoria cez tlačidlo v hornej lište aplikácie ako zvyčajne, namiesto toho, aby boli trvalo viditeľné Povoľte túto možnosť a stránka nastavení sa bude vždy otvárať na celú obrazovku namiesto posuvného panela Typ prepínača Zostaviť Max Zmena veľkosti kotvy Pixel Plynule Používa prepínač v štýle Windows 11 založený na dizajnovom systéme „Fluent“ Obrázky na SVG Používa prepínač podobný iOS, založený na dizajnovom systéme Cupertino Explodovať Dážď Rohy Vykonajte transkódovanie JXL ~ JPEG bez straty kvality alebo konvertujte GIF/APNG na JXL animáciu Vykonajte bezstratové transkódovanie z JXL do JPEG Vykonajte bezstratové transkódovanie z JPEG do JXL JXL na JPEG JPEG na JXL Vyberte JXL obrázok na začiatok Rýchle Gaussovo rozostrenie 2D Rýchle Gaussovo rozostrenie 4D Automatické prilepenie Harmonizácia farieb Úroveň harmonizácie Lanczos Bessel Metóda prevzorkovania, ktorá zachováva vysokokvalitnú interpoláciu použitím Besselovej (jinc) funkcie na hodnoty pixelov GIF na JXL Konvertujte GIF obrázky na animované JXL obrázky APNG na JXL JXL na obrázky Konvertujte JXL animáciu na dávku obrázkov Obrázky na JXL Správanie Preskočiť výber súboru Výber súboru sa zobrazí okamžite, ak je to na zvolenej obrazovke možné Generovať náhľady Stratová kompresia Používa stratovú kompresiu na zmenšenie veľkosti súboru namiesto bezstratovej Typ kompresie Triedenie Preveďte zadané obrázky na SVG obrázky Použiť vzorkovanú paletu Ak je táto možnosť zapnutá, kvantizačná paleta bude vzorkovaná Vynechať cestu Použitie tohto nástroja na trasovanie veľkých obrázkov bez zmenšenia sa neodporúča, môže to spôsobiť zlyhanie a predĺžiť čas spracovania Zmenšiť obrázok Minimálny pomer farieb Prahové hodnoty čiar Kvadratický prah Tolerancia zaokrúhľovania súradníc Mierka cesty Obnoviť vlastnosti Všetky vlastnosti sa nastavia na predvolené hodnoty. Upozorňujeme, že túto akciu nemožno vrátiť späť Podrobné Predvolená šírka čiary Režim motora LSTM sieť Zastarané Zastarané a LSTM Konvertovať Konvertovať dávky obrázkov do zadaného formátu Pridať nový priečinok Obrázok bude pred spracovaním zmenšený na menšie rozmery, čo pomáha nástroju pracovať rýchlejšie a bezpečnejšie Bitov na vzorku Kompresia Fotometrická interpretácia Vzorky na pixel Planárna konfigurácia Y Cb Cr Podvzorkovanie Y Cb Cr Polohovanie Rozlíšenie X Rozlíšenie Y Jednotka rozlíšenia Posuny pásma Prenosová funkcia Biely bod Dátum Čas Popis obrázku Vytvoriť Model Softvér Autor Autorské práva Verzia Exif Verzia Flashpix Farebný priestor Gamma Rozmer Pixel X Pixel Y Rozmer Komprimované bity na pixel Poznámka výrobcu Komentár používateľa Súvisiaci zvukový súbor Dátum Čas Originál Dátum Čas Digitalizované Časový posun Časový posun Pôvodný Časový posun digitalizovaný Sub Sec Čas Sub Sec Čas Pôvodný Sub Sec Time Digitalizovný Doba expozície Číslo F Program vystavenia Spektrálna citlivosť Fotografická citlivosť OECF Typ citlivosti Štandardná výstupná citlivosť Odporúčaný index expozície Rýchlosť ISO ISO rýchlosť Latitude yyy Rýchlosť ISO Latitude zzz Hodnota rýchlosti uzávierky Hodnota clony Flash Energy Priestorová frekvenčná charakteristika Rozlíšenie v ohniskovej rovine X Rozlíšenie v ohniskovej rovine Y Jednotka rozlíšenia ohniskovej roviny Názov Umiestnenie Metóda snímania Zdroj súboru Vzor CFA Vlastné vykresľovanie Režim expozície Vyváženie bielej Pomer digitálneho zoomu Ohnisková vzdialenosť v 35 mm filme Typ zachytenia scény Ovládanie zosilnenia Kontrast Sýtosť Ostrosť Subjekt Vzdialenosť Rozsah Jedinečné ID obrázku Meno vlastníka fotoaparátu Sériové číslo tela Špecifikácia objektívu Výrobca objektívu Model objektívu Sériové číslo objektívu ID verzie GPS Referenčná šírka GPS GPS zemepisná šírka Referenčná dĺžka GPS GPS dĺžka GPS referenčná nadmorská výška GPS Nadmorská výška Časová pečiatka GPS GPS satelity Stav GPS Režim merania GPS GPS DOP GPS referenčná rýchlosť Rýchlosť GPS Počet riadkov na pásik Počet bajtov pásika Výmenný formát JPEG Dĺžka výmenného formátu JPEG Primárne chromatickosti Y Cb Cr koeficienty Referenčná čierna biela Hodnota jasu Hodnota skreslenia expozície Maximálna hodnota clony Vzdialenosť predmetu Režim merania Flash Predmetná oblasť Ohnisková vzdialenosť Index expozície Popis nastavenia zariadenia GPS Track Ref GPS stopa Smer obrazu GPS Ref Smer obrazu GPS Dátum mapy GPS Ref. zemepisná šírka cieľa GPS Cieľová zemepisná šírka GPS Ref. zemepisná dĺžka cieľa GPS Cieľová dĺžka GPS Cieľový smer GPS Ref Cieľový smer GPS Cieľová vzdialenosť GPS Ref Cieľová vzdialenosť GPS Metóda spracovania GPS Informácie o oblasti GPS Dátumová pečiatka GPS GPS diferenciál GPS H Positioning Error Index interoperability Verzia DNG Predvolená veľkosť orezania Spustiť náhľad obrázka Dĺžka ukážkového obrázka Aspect Frame Spodný okraj snímača Ľavý okraj snímača Pravý okraj snímača Horný okraj snímača ISO Nakreslite text na cestu s daným písmom a farbou Veľkosť písma Veľkosť vodoznaku Opakujte text Aktuálny text sa bude opakovať až do konca cesty namiesto jedného kreslenia Veľkosť pomlčky Pomocou vybratého obrázka ho nakreslite pozdĺž danej cesty Tento obrázok sa použije ako opakovaný záznam nakreslenej cesty Nakreslí načrtnutý trojuholník z počiatočného bodu do koncového bodu Nakreslí načrtnutý trojuholník z počiatočného bodu do koncového bodu Obrysový trojuholník Trojuholník Nakreslí mnohouholník od počiatočného bodu po koncový bod Polygón Obrysový mnohouholník Nakreslí obrysový mnohouholník od počiatočného bodu po koncový bod Vertices Nakreslite pravidelný mnohouholník Nakreslite mnohouholník, ktorý bude pravidelný namiesto voľného tvaru Kreslí hviezdu z počiatočného bodu do koncového bodu Star Obrysová hviezda Nakreslí obrysovú hviezdu od počiatočného bodu po koncový bod Pomer vnútorného polomeru Nakreslite pravidelnú hviezdu Nakreslite hviezdu, ktorá bude pravidelná namiesto voľnej formy Antialias Umožňuje antialiasing, aby sa zabránilo ostrým hranám Namiesto ukážky otvorte Upraviť Keď v ImageToolbox vyberiete obrázok na otvorenie (ukážka), namiesto náhľadu sa otvorí hárok s výberom úprav Skener dokumentov Naskenujte dokumenty a vytvorte PDF alebo z nich oddeľte obrázky Kliknutím spustíte skenovanie Spustite skenovanie Uložiť ako Pdf Zdieľať ako Pdf Možnosti nižšie slúžia na ukladanie obrázkov, nie PDF Vyrovnajte histogram HSV Vyrovnať histogram Zadajte percento Povoliť zadávanie textovým poľom Povolí textové pole za výberom predvolieb, aby ste ich mohli zadávať za behu Mierka farebného priestoru Lineárne Vyrovnať pixeláciu histogramu Veľkosť mriežky X Veľkosť mriežky Y Vyrovnanie histogramu Adaptívne Equalize Histogram Adaptive LUV Equalize Histogram Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Orezať na obsah Farba rámu Farba na ignorovanie Šablóna Neboli pridané žiadne filtre šablón Vytvoriť nový Naskenovaný QR kód nie je platnou šablónou filtra Naskenujte QR kód Vybratý súbor nemá žiadne údaje šablóny filtra Vytvoriť šablónu Názov šablóny Tento obrázok sa použije na ukážku tejto šablóny filtra Filter šablóny Ako obrázok s QR kódom Ako súbor Uložiť ako súbor Uložiť ako obrázok s QR kódom Odstrániť šablónu Chystáte sa odstrániť vybratý filter šablóny. Táto operácia sa nedá vrátiť späť Pridaná šablóna filtra s názvom \"%1$s\" (%2$s) Ukážka filtra QR a čiarový kód Naskenujte QR kód a získajte jeho obsah alebo prilepte svoj reťazec a vygenerujte nový Obsah kódu Naskenujte ľubovoľný čiarový kód, aby ste nahradili obsah v poli, alebo napíšte niečo a vygenerujte nový čiarový kód s vybraným typom Popis QR Min V nastaveniach udeľte fotoaparátu povolenie na skenovanie QR kódu V nastaveniach udeľte fotoaparátu povolenie na skenovanie skenera dokumentov Kubický B-Spline Hamming Hanning Blackman Welch Quadric Gaussovský Sfinga Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-He Box Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kubická interpolácia poskytuje plynulejšie škálovanie pri zohľadnení najbližších 16 pixelov, čo poskytuje lepšie výsledky ako bilineárne Využíva po častiach definované polynómové funkcie na hladkú interpoláciu a aproximáciu krivky alebo povrchu, flexibilné a súvislé znázornenie tvaru Funkcia okna používaná na zníženie spektrálneho úniku zúžením hrán signálu, užitočná pri spracovaní signálu Variant Hannovho okna, ktorý sa bežne používa na zníženie spektrálneho úniku v aplikáciách spracovania signálu Funkcia okna, ktorá poskytuje dobré frekvenčné rozlíšenie minimalizovaním spektrálneho úniku, často používaná pri spracovaní signálu Funkcia okna navrhnutá tak, aby poskytovala dobré frekvenčné rozlíšenie so zníženým spektrálnym únikom, často používaná v aplikáciách spracovania signálu Metóda, ktorá využíva na interpoláciu kvadratickú funkciu, ktorá poskytuje hladké a súvislé výsledky Interpolačná metóda, ktorá aplikuje Gaussovu funkciu, užitočnú na vyhladenie a zníženie šumu v obrazoch Pokročilá metóda prevzorkovania poskytujúca vysokokvalitnú interpoláciu s minimálnymi artefaktmi Funkcia trojuholníkového okna používaná pri spracovaní signálu na zníženie spektrálneho úniku Vysokokvalitná interpolačná metóda optimalizovaná pre prirodzenú zmenu veľkosti obrazu, vyváženie ostrosti a plynulosti Ostrejší variant metódy Robidoux, optimalizovaný pre ostrú zmenu veľkosti obrazu Metóda interpolácie založená na spline, ktorá poskytuje hladké výsledky pomocou filtra so 16 klepnutiami Metóda interpolácie založená na spline, ktorá poskytuje hladké výsledky pomocou filtra s 36 klepnutiami Metóda interpolácie založená na spline, ktorá poskytuje hladké výsledky pomocou filtra so 64 klepnutiami Metóda interpolácie, ktorá využíva okno Kaiser, poskytuje dobrú kontrolu nad kompromisom medzi šírkou hlavného laloku a úrovňou bočného laloku Hybridná funkcia okien kombinujúca okná Bartlett a Hann, ktorá sa používa na zníženie spektrálneho úniku pri spracovaní signálu Jednoduchá metóda prevzorkovania, ktorá využíva priemer najbližších hodnôt pixelov, čo často vedie k blokovému vzhľadu Funkcia okna používaná na zníženie spektrálneho úniku poskytujúca dobré frekvenčné rozlíšenie v aplikáciách spracovania signálu Metóda prevzorkovania, ktorá využíva 2-lalokový Lanczosov filter pre vysokokvalitnú interpoláciu s minimálnymi artefaktmi Metóda prevzorkovania, ktorá využíva 3-lalokový Lanczosov filter pre vysokokvalitnú interpoláciu s minimálnymi artefaktmi Metóda prevzorkovania, ktorá využíva 4-lalokový Lanczosov filter pre vysokokvalitnú interpoláciu s minimálnymi artefaktmi Variant filtra Lanczos 2, ktorý využíva funkciu jinc a poskytuje vysokokvalitnú interpoláciu s minimálnymi artefaktmi Variant filtra Lanczos 3, ktorý využíva funkciu jinc a poskytuje vysokokvalitnú interpoláciu s minimálnymi artefaktmi Variant filtra Lanczos 4, ktorý využíva funkciu jinc a poskytuje vysokokvalitnú interpoláciu s minimálnymi artefaktmi Hanning EWA Eliptický vážený priemer (EWA) variant Hanningovho filtra pre hladkú interpoláciu a prevzorkovanie Robidoux EWA Variant eliptického váženého priemeru (EWA) filtra Robidoux pre vysokokvalitné prevzorkovanie Blackman EVE Variant eliptického váženého priemeru (EWA) filtra Blackman na minimalizáciu artefaktov zvonenia Quadric EWA Variant eliptického váženého priemeru (EWA) filtra Quadric pre hladkú interpoláciu Robidoux Sharp EWA Variant eliptického váženého priemeru (EWA) filtra Robidoux Sharp pre ostrejšie výsledky Lanczos 3 Jinc EWA Variant eliptického váženého priemeru (EWA) filtra Lanczos 3 Jinc pre vysokokvalitné prevzorkovanie so zníženým aliasingom Ženšen Prevzorkovací filter určený na vysokokvalitné spracovanie obrazu s dobrým vyvážením ostrosti a plynulosti Ženšen EWA Variant eliptického váženého priemeru (EWA) ženšenového filtra pre lepšiu kvalitu obrazu Lanczos Sharp EWA Variant eliptického váženého priemeru (EWA) filtra Lanczos Sharp na dosiahnutie ostrých výsledkov s minimálnymi artefaktmi Lanczos 4 najostrejšie EWA Variant eliptického váženého priemeru (EWA) filtra Lanczos 4 Sharpest pre extrémne ostré prevzorkovanie obrazu Lanczos Soft EWA Elliptical Weighted Average (EWA) variant filtra Lanczos Soft pre plynulejšie prevzorkovanie obrazu Haasn Soft Prevzorkovací filter navrhnutý spoločnosťou Haasn pre plynulé škálovanie obrazu bez artefaktov Konverzia formátu Preveďte dávku obrázkov z jedného formátu do druhého Navždy zrušiť Stohovanie obrázkov Skladanie obrázkov na seba pomocou zvolených režimov prelínania Pridať obrázok Počet košov Clahe HSL Clahe HSV Equalize Histogram Adaptive HSL Equalize Histogram Adaptive HSV Režim okraja Klip Zabaliť Farebná slepota Vyberte režim na prispôsobenie farieb témy pre vybraný variant farbosleposti Ťažkosti pri rozlišovaní medzi červenými a zelenými odtieňmi Ťažkosti pri rozlišovaní medzi zelenými a červenými odtieňmi Ťažkosti pri rozlišovaní medzi modrými a žltými odtieňmi Neschopnosť vnímať červené odtiene Neschopnosť vnímať zelené odtiene Neschopnosť vnímať modré odtiene Znížená citlivosť na všetky farby Úplná farbosleposť, videnie len odtieňov sivej Nepoužívajte schému Color Blind Farby budú presne také, ako sú nastavené v téme Sigmoidálny Lagrange 2 Lagrangeov interpolačný filter rádu 2, vhodný pre vysokokvalitné škálovanie obrazu s plynulými prechodmi Lagrange 3 Lagrangeov interpolačný filter rádu 3, ktorý ponúka lepšiu presnosť a hladšie výsledky pre zmenu mierky obrazu Lanczos 6 Prevzorkovací filter Lanczos s vyšším rádom 6, ktorý poskytuje ostrejšie a presnejšie škálovanie obrazu Lanczos 6 Jinc Variant filtra Lanczos 6 využívajúci funkciu Jinc pre lepšiu kvalitu prevzorkovania obrazu Lineárne rozmazanie rámčeka Lineárne rozmazanie stanu Lineárne rozmazanie Gaussovho poľa Lineárne rozmazanie stohu Rozmazanie Gaussovho poľa Lineárne rýchle Gaussovské rozostrenie Ďalej Lineárne rýchle Gaussovské rozostrenie Lineárne gaussovské rozostrenie Vyberte jeden filter, ktorý chcete použiť ako farbu Vymeňte filter Vyberte filter nižšie, aby ste ho mohli použiť ako štetec vo svojej kresbe Schéma kompresie TIFF Low Poly Piesková maľba Rozdelenie obrazu Rozdeľte jeden obrázok podľa riadkov alebo stĺpcov Fit To Bounds Skombinujte režim zmeny veľkosti orezania s týmto parametrom, aby ste dosiahli požadované správanie (orezať/prispôsobiť pomeru strán) Jazyky boli úspešne importované Záložné modely OCR Importovať Exportovať pozícia centrum Vľavo hore Vpravo hore Vľavo dole Vpravo dole Stred hore Stred vpravo Stred dole Stred vľavo Cieľový obrázok Prenos palety Vylepšený olej Jednoduchý starý televízor HDR Gotham Jednoduchá skica Mäkká žiara Farebný plagát Tri Tone Tretia farba Clahe Oklab Clara Olchová Clahe Jzazbz Polka Dot Clustered 2x2 Dithering Clustered 4x4 Dithering Clustered 8x8 Dithering Yililoma Dithering Nie sú vybraté žiadne obľúbené možnosti, pridajte ich na stránke nástrojov Pridať obľúbené Doplnkové Analogické Triadický Doplnkové rozdelenie Tetradic Štvorcový Analogické + doplnkové Farebné nástroje Miešajte, vytvárajte tóny, generujte odtiene a ďalšie Farebné harmónie Farebné tieňovanie Variácia Odtiene Tóny Odtiene Miešanie farieb Informácie o farbe Vybraná farba Farba na miešanie Keď sú zapnuté dynamické farby, nemožno použiť monet 512 x 512 2D LUT Cieľový obrázok LUT Amatér Slečna etiketa Mäkká elegancia Variant Soft Elegance Variant prenosu palety 3D LUT Cieľový súbor 3D LUT (.cube / .CUBE) LUT Bleach Bypass Svetlo sviečok Drop Blues Ostrý jantár Farby jesene Filmový fond 50 Hmlistá noc Kodak Získajte neutrálny obraz LUT Najprv pomocou svojej obľúbenej aplikácie na úpravu fotografií aplikujte filter na neutrálne LUT, ktorý môžete získať tu. Aby to fungovalo správne, farba každého pixelu nesmie závisieť od iných pixelov (napr. rozmazanie nebude fungovať). Keď budete pripravený, použite svoj nový obrázok LUT ako vstup pre filter 512*512 LUT Pop Art Celuloid Káva Zlatý les Nazelenalý Retro žltá Ukážka odkazov Umožňuje načítanie ukážky odkazu na miestach, kde môžete získať text (QRCode, OCR atď.) Odkazy Súbory ICO je možné uložiť len v maximálnej veľkosti 256 x 256 GIF na WEBP Prevod obrázkov GIF na animované obrázky WEBP Nástroje WEBP Preveďte obrázky na animovaný obrázok WEBP alebo extrahujte snímky z danej animácie WEBP WEBP na obrázky Previesť súbor WEBP na dávku obrázkov Preveďte dávku obrázkov do súboru WEBP Obrázky na WEBP Začnite výberom obrázka WEBP Žiadny úplný prístup k súborom Povoľte prístup k všetkým súborom, aby ste mohli vidieť obrázky JXL, QOI a ďalšie obrázky, ktoré nie sú v systéme Android rozpoznané ako obrázky. Bez povolenia Image Toolbox nemôže zobraziť tieto obrázky Predvolená farba kreslenia Predvolený režim cesty kreslenia Pridať časovú pečiatku Povolí pridanie časovej pečiatky do výstupného súboru Formátovaná časová pečiatka Povoliť formátovanie časovej pečiatky vo výstupnom súbore namiesto základných milis Povoľte časové pečiatky a vyberte ich formát Miesto na jednorazové uloženie Zobrazte a upravte miesta na jednorazové uloženie, ktoré môžete použiť dlhým stlačením tlačidla uloženia vo väčšine možností Nedávno použité CI kanál Skupina Súbor nástrojov obrázkov v telegrame 🎉 Pripojte sa k nášmu chatu, kde môžete diskutovať o čomkoľvek, čo chcete, a tiež sa pozrieť na kanál CI, kde uverejňujem beta verzie a oznámenia Získajte upozornenia na nové verzie aplikácie a prečítajte si oznámenia Prispôsobte obrázok daným rozmerom a použite rozmazanie alebo farbu na pozadie Usporiadanie nástrojov Zoskupte nástroje podľa typu Zoskupuje nástroje na hlavnej obrazovke podľa ich typu namiesto vlastného usporiadania zoznamu Predvolené hodnoty Viditeľnosť systémových pruhov Zobraziť systémové lišty potiahnutím prstom Umožňuje potiahnutím zobraziť systémové lišty, ak sú skryté Auto Skryť všetko Zobraziť všetko Skryť navigačný panel Skryť stavový riadok Generovanie hluku Generujte rôzne zvuky ako Perlin alebo iné typy Frekvencia Typ hluku Typ rotácie Fraktálny typ Oktávy Lacunárnosť Získať Vážená sila Sila ping pongu Funkcia vzdialenosti Typ návratu Jitter Warp domén Zarovnanie Vlastný názov súboru Vyberte umiestnenie a názov súboru, ktorý sa použije na uloženie aktuálneho obrázka Uložené do priečinka s vlastným názvom Tvorba koláží Vytvorte koláže až z 20 obrázkov Typ koláže Podržaním obrázka ho môžete vymeniť, posunúť a priblížiť, aby ste upravili polohu Zakázať otáčanie Zabraňuje otáčaniu obrázkov pomocou gest dvoch prstov Povoliť prichytenie k okrajom Po presunutí alebo priblížení sa obrázky prichytia tak, aby vyplnili okraje rámu Histogram Histogram obrazu RGB alebo jasu, ktorý vám pomôže vykonať úpravy Tento obrázok sa použije na generovanie histogramov RGB a jasu Možnosti Tesseract Použite niektoré vstupné premenné pre tesseract engine Vlastné možnosti Možnosti by sa mali zadať podľa tohto vzoru: \"--{názov_možnosti} {hodnota}\" Automatické orezanie Voľné rohy Orezať obrázok podľa mnohouholníka, tým sa opraví aj perspektíva Násilné body na hranice obrázkov Body nebudú obmedzené hranicami obrázka, čo je užitočné na presnejšiu korekciu perspektívy Maska Výplň pod nakreslenou cestou podľa obsahu Heal Spot Použite kruhové jadro Otvorenie Zatváranie Morfologický gradient Cylindr Čierny klobúk Tónové krivky Resetovať krivky Krivky sa vrátia späť na predvolenú hodnotu Štýl čiary Veľkosť medzery Prerušovaná Bodka prerušovaná Pečiatkované Cikcak Nakreslí prerušovanú čiaru pozdĺž nakreslenej cesty so špecifikovanou veľkosťou medzery Nakreslí bodku a prerušovanú čiaru pozdĺž danej cesty Len predvolené rovné čiary Nakreslí vybrané tvary pozdĺž cesty so zadanými medzerami Kreslí vlnitý cikcak pozdĺž cesty Kľukatý pomer Vytvoriť skratku Vyberte nástroj na pripnutie Nástroj sa pridá na domovskú obrazovku vášho spúšťača ako skratka, použite ho v kombinácii s nastavením \"Preskočiť výber súboru\" na dosiahnutie potrebného správania Neskladujte rámy Umožňuje likvidáciu predchádzajúcich rámov, takže sa nebudú na seba naskladať Prelínanie Rámy budú navzájom prelínané Počet snímok s prelínaním Prah jedna Prah dva Canny Zrkadlo 101 Vylepšené rozostrenie zoomu Laplaciánske jednoduché Sobel Jednoduché Pomocná mriežka Zobrazuje podpornú mriežku nad oblasťou kreslenia, ktorá pomáha pri presnej manipulácii Farba mriežky Šírka bunky Výška bunky Kompaktné selektory Niektoré ovládacie prvky výberu budú používať kompaktné rozloženie, aby zaberali menej miesta V nastaveniach udeľte fotoaparátu povolenie na snímanie obrázka Rozloženie Názov hlavnej obrazovky Faktor konštantnej rýchlosti (CRF) Hodnota %1$s znamená pomalú kompresiu, výsledkom čoho je relatívne malá veľkosť súboru. %2$s znamená rýchlejšiu kompresiu, výsledkom čoho je veľký súbor. Knižnica Lut Stiahnite si kolekciu LUT, ktorú môžete použiť po stiahnutí Aktualizujte kolekciu LUT (do fronty budú zaradené iba nové), ktoré môžete použiť po stiahnutí Zmeňte predvolený náhľad obrázka pre filtre Obrázok ukážky Skryť Zobraziť Typ posúvača Fancy Materiál 2 Efektne vyzerajúci posúvač. Toto je predvolená možnosť Posuvník Materiál 2 Posuvník Material You Použiť Stredové dialógové tlačidlá Tlačidlá dialógových okien budú podľa možnosti umiestnené v strede namiesto na ľavej strane Licencie otvoreného zdroja Pozrite si licencie knižníc s otvoreným zdrojovým kódom používaných v tejto aplikácii Oblasť Prevzorkovanie pomocou vzťahu plochy pixelov. Môže to byť preferovaná metóda na decimáciu obrazu, pretože poskytuje výsledky bez moaré. Ale keď sa obrázok priblíži, je to podobné ako pri metóde \"Najbližšie\". Povoliť mapovanie tónov Zadajte % Nemáte prístup na stránku, skúste použiť VPN alebo skontrolujte správnosť adresy URL Vrstvy značiek Režim vrstiev s možnosťou voľne umiestňovať obrázky, text a ďalšie Upraviť vrstvu Vrstvy na obrázku Použite obrázok ako pozadie a pridajte naň rôzne vrstvy Vrstvy na pozadí To isté ako prvá možnosť, ale s farbou namiesto obrázka Beta Strana rýchlych nastavení Pri úprave obrázkov pridajte na vybranú stranu plávajúci pás, ktorý po kliknutí otvorí rýchle nastavenia Vymazať výber Skupina nastavení \"%1$s\" bude predvolene zbalená Skupina nastavení \"%1$s\" bude predvolene rozbalená Nástroje Base64 Dekódujte reťazec Base64 na obrázok alebo zakódujte obrázok do formátu Base64 Základ 64 Poskytnutá hodnota nie je platný reťazec Base64 Nie je možné skopírovať prázdny alebo neplatný reťazec Base64 Prilepte základ 64 Kopírovať základ 64 Načítať obrázok na skopírovanie alebo uloženie reťazca Base64. Ak máte samotný reťazec, môžete ho vložiť vyššie, aby ste získali obrázok Uložiť Base64 Zdieľať Base64 Možnosti Akcie Importovať Base64 Akcie Base64 Pridať obrys Pridajte obrys okolo textu s určenou farbou a šírkou Farba obrysu Veľkosť obrysu Rotácia Kontrolný súčet ako názov súboru Výstupné obrázky budú mať názov zodpovedajúci ich kontrolnému súčtu údajov Slobodný softvér (partner) Užitočnejší softvér v partnerskom kanáli aplikácií pre Android Algoritmus Nástroje kontrolného súčtu Porovnajte kontrolné súčty, vypočítajte hash alebo vytvorte hexadecimálne reťazce zo súborov pomocou rôznych hashovacích algoritmov Vypočítajte Textový hash Kontrolný súčet Vyberte súbor a vypočítajte jeho kontrolný súčet na základe zvoleného algoritmu Zadajte text na výpočet jeho kontrolného súčtu na základe zvoleného algoritmu Kontrolný súčet zdroja Kontrolný súčet na porovnanie Zápas! Rozdiel Kontrolné súčty sú rovnaké, môže to byť bezpečné Kontrolné súčty nie sú rovnaké, súbor môže byť nebezpečný! Sieťové prechody Pozrite sa na online kolekciu sieťových prechodov Importovať je možné iba písma TTF a OTF Importovať písmo (TTF/OTF) Exportujte písma Importované písma Chyba pri pokuse o ukladanie, skúste zmeniť výstupný priečinok Názov súboru nie je nastavený žiadne Vlastné stránky Výber strán Potvrdenie ukončenia nástroja Ak máte pri používaní určitých nástrojov neuložené zmeny a pokúsite sa ich zavrieť, zobrazí sa dialógové okno na potvrdenie Upraviť EXIF Zmeňte metadáta jedného obrázka bez opätovnej kompresie Klepnutím upravíte dostupné značky Zmeniť nálepku Prispôsobiť šírku Prispôsobiť výšku Dávkové porovnanie Vyberte súbor/súbory a vypočítajte ich kontrolný súčet na základe zvoleného algoritmu Vyberte súbory Vyberte adresár Mierka dĺžky hlavy Pečiatka Časová pečiatka Vzor formátu Výplň Rezanie obrazu Vystrihnite časť obrázka a spojte ľavé (môže byť inverzné) zvislými alebo vodorovnými čiarami Vertikálna otočná čiara Horizontálna otočná čiara Inverzný výber Zvislá časť rezu bude ponechaná namiesto zlúčenia častí okolo oblasti rezu Vodorovná časť rezu bude ponechaná namiesto zlúčenia častí okolo oblasti rezu Kolekcia sieťových prechodov Vytvorte sieťový gradient s vlastným množstvom uzlov a rozlíšením Prekrytie sieťovým prechodom Vytvorte sieťový gradient hornej časti daných obrázkov Prispôsobenie bodov Veľkosť mriežky Rozlíšenie X Uznesenie Y Rozlíšenie Pixel za pixelom Farba zvýraznenia Typ porovnania pixelov Naskenujte čiarový kód Výškový pomer Typ čiarového kódu Vynútiť Č/B Obrázok čiarového kódu bude úplne čiernobiely a nebude zafarbený témou aplikácie Naskenujte ľubovoľný čiarový kód (QR, EAN, AZTEC, …) a získajte jeho obsah alebo prilepte svoj text a vygenerujte nový Nenašiel sa žiadny čiarový kód Vygenerovaný čiarový kód bude tu Audio obaly Extrahujte obrázky obalu albumu zo zvukových súborov, väčšina bežných formátov je podporovaná Začnite výberom zvuku Vyberte Zvuk Nenašli sa žiadne obaly Odoslať denníky Kliknutím zdieľajte súbor denníkov aplikácií, môže mi to pomôcť odhaliť problém a vyriešiť problémy Ojoj… Niečo sa pokazilo Môžete ma kontaktovať pomocou možností nižšie a ja sa pokúsim nájsť riešenie.\n(Nezabudnite priložiť protokoly) Zápis do súboru Extrahujte text z dávky obrázkov a uložte ho do jedného textového súboru Zápis do metadát Extrahujte text z každého obrázka a umiestnite ho do EXIF ​​informácií o príbuzných fotografiách Neviditeľný režim Použite steganografiu na vytvorenie okom neviditeľných vodoznakov vo vnútri bajtov vašich obrázkov Použite LSB Použije sa metóda steganografie LSB (Menej významný bit), inak FD (Frequency Domain) Automatické odstránenie červených očí heslo Odomknúť PDF je chránené Operácia takmer dokončená. Zrušenie teraz bude vyžadovať reštartovanie Dátum zmeny Dátum zmeny (obrátené) Veľkosť Veľkosť (obrátená) Typ MIME Typ MIME (obrátený) Rozšírenie Rozšírenie (obrátené) Dátum pridania Dátum pridania (obrátené) Zľava doprava Sprava doľava Zhora nadol Zdola nahor Tekuté sklo Prepínač založený na nedávno ohlásenom IOS 26 a jeho dizajnovom systéme z tekutého skla Nižšie vyberte obrázok alebo prilepte/importujte údaje Base64 Začnite zadaním odkazu na obrázok Prilepiť odkaz Kaleidoskop Sekundárny uhol Strany Mix kanálov Modrá zelená Červená modrá Zelená červená Do červena Do zelena Do modra azúrová purpurová Žltá Farba Poltón Obrys Úrovne Offset Voronoi kryštalizovať Tvar Natiahnuť Náhodnosť Odšpiniť Difúzne Pes Druhý polomer Vyrovnať Žiariť Vír a štipka Pointillize Farba okraja Polárne súradnice Rect to polárne Polárne až pravouhlé Invertovať v kruhu Znížte hluk Jednoduchá Solarizácia tkať X medzera Y Gap X šírka Y šírka Točte sa Pečiatka Namazať Hustota Zmiešajte Skreslenie sférickej šošovky Index lomu Arc Uhol rozpätia Sparkle Lúče ASCII Gradient Mary jeseň Kosť Jet Zima oceán leto jar Cool Variant HSV Ružová Horúce Slovo Magma Inferno Plazma Viridis Občania Súmrak Súmrak posunutý Perspektíva Auto Zošikmenie Povoliť orezanie Plodina alebo perspektíva Absolútna Turbo Deep Green Korekcia šošovky Cieľový súbor profilu objektívu vo formáte JSON Stiahnite si hotové profily šošoviek Čiastkové percentá Exportovať ako JSON Skopírujte reťazec s údajmi palety ako reprezentáciu json Vyrezávanie švíkov Domovská obrazovka Uzamknúť obrazovku Vstavaný Export tapiet Obnoviť Získajte aktuálne tapety Home, Lock a Built-in Povoľte prístup ku všetkým súborom, je to potrebné na načítanie tapiet Povolenie na správu externého úložiska nestačí, musíte povoliť prístup k svojim obrázkom, nezabudnite vybrať možnosť \"Povoliť všetko\" Pridať predvoľbu k názvu súboru K súboru obrázka pridá príponu s vybratou predvoľbou Pridať režim mierky obrázka k názvu súboru K súboru obrázka pridá príponu s vybratým režimom mierky obrázka Ascii Art Preveďte obrázok na text vo formáte ASCII, ktorý bude vyzerať ako obrázok Params Aplikuje negatívny filter na obrázok pre lepší výsledok v niektorých prípadoch Spracovanie snímky obrazovky Snímka obrazovky nebola zachytená, skúste to znova Ukladanie bolo preskočené %1$s súbory boli preskočené Povoliť preskočenie, ak je väčšie Niektoré nástroje budú môcť preskočiť ukladanie obrázkov, ak by výsledná veľkosť súboru bola väčšia ako originál Udalosť kalendára Kontaktovať Email Poloha Telefón Text SMS URL Wi-Fi Otvorená sieť N/A SSID Telefón Správa Adresa Predmet Telo Meno Organizácia Názov Telefóny E-maily URL adresy Zhrnutie Popis Poloha organizátor Dátum začiatku Dátum ukončenia Stav Zemepisná šírka Zemepisná dĺžka Vytvorte čiarový kód Upraviť čiarový kód Konfigurácia Wi-Fi Bezpečnosť Vyberte kontakt Udeľte kontaktom v nastaveniach povolenie na automatické dopĺňanie pomocou vybratého kontaktu Kontaktné údaje Krstné meno Stredné meno Priezvisko Výslovnosť Pridať telefón Pridať e-mail Pridať adresu webové stránky Pridať webovú stránku Formátovaný názov Tento obrázok sa použije na umiestnenie nad čiarový kód Prispôsobenie kódu Tento obrázok bude použitý ako logo v strede QR kódu Logo Výplň loga Veľkosť loga Rohy loga Štvrté oko Pridá do qr kódu symetriu oka pridaním štvrtého oka do dolného rohu Tvar pixelov Tvar rámu Tvar lopty Úroveň opravy chýb Tmavá farba Svetlá farba Hyper OS Štýl podobný Xiaomi HyperOS Vzor masky Tento kód sa nemusí dať naskenovať, zmeňte parametre vzhľadu, aby bol čitateľný na všetkých zariadeniach Nedá sa skenovať Nástroje budú vyzerať ako spúšťač aplikácií na domovskej obrazovke, aby boli kompaktnejšie Režim spúšťača Vyplní oblasť vybraným štetcom a štýlom Povodňová výplň Striekajte Kreslí cestu v štýle graffity Štvorcové častice Častice spreja budú mať štvorcový tvar namiesto kruhov Paletové nástroje Vygenerujte základný materiál/materiál z palety z obrázka alebo importujte/exportujte cez rôzne formáty paliet Upraviť paletu Export/import palety v rôznych formátoch Názov farby Názov palety Formát palety Exportujte vygenerovanú paletu do rôznych formátov Pridá novú farbu do aktuálnej palety Formát %1$s nepodporuje poskytnutie názvu palety Vzhľadom na pravidlá Obchodu Play túto funkciu nemožno zahrnúť do aktuálnej zostavy. Ak chcete získať prístup k tejto funkcii, stiahnite si ImageToolbox z alternatívneho zdroja. Dostupné zostavy na GitHub nájdete nižšie. Otvorte stránku Github Pôvodný súbor bude nahradený novým súborom namiesto uloženia do zvoleného priečinka Zistil sa skrytý text vodoznaku Zistil sa skrytý obrázok vodoznaku Tento obrázok bol skrytý Generatívne maľovanie Umožňuje vám odstrániť objekty z obrázka pomocou modelu AI bez spoliehania sa na OpenCV. Ak chcete použiť túto funkciu, aplikácia stiahne požadovaný model (~200 MB) z GitHubu Umožňuje vám odstrániť objekty z obrázka pomocou modelu AI bez spoliehania sa na OpenCV. Môže ísť o dlhotrvajúcu operáciu Analýza úrovne chýb Gradient jasu Priemerná vzdialenosť Detekcia pohybu kopírovania Zachovať Koeficient Údaje schránky sú príliš veľké Údaje sú príliš veľké na kopírovanie Jednoduchá Weave Pixelizácia Rozložená pixelizácia Krížová pixelizácia Mikro makro pixelizácia Orbitálna pixelizácia Vortexová pixelizácia Pixelizácia pulznej mriežky Pixelizácia jadra Radial Weave Pixelization Nedá sa otvoriť UR \"%1$s\" Režim sneženia Povolené Hraničný rám Závadový variant Posun kanálov Maximálny posun VHS Bloková chyba Veľkosť bloku CRT zakrivenie Zakrivenie Chroma Pixel Melt Maximálny pokles Nástroje AI Rôzne nástroje na spracovanie obrázkov prostredníctvom modelov AI, ako je odstraňovanie artefaktov alebo odšumovanie Kompresia, zubaté línie Karikatúry, kompresia vysielania Všeobecná kompresia, všeobecný šum Bezfarebný kreslený zvuk Rýchla, všeobecná kompresia, všeobecný šum, animácia/komiks/anime Skenovanie knihy Korekcia expozície Najlepšie pri všeobecnej kompresii, farebné obrázky Najlepšie pri všeobecnej kompresii, obrázky v odtieňoch sivej Všeobecná kompresia, obrázky v odtieňoch šedej, silnejšie Všeobecný šum, farebné obrázky Všeobecný šum, farebné obrázky, lepšie detaily Všeobecný šum, obrázky v odtieňoch šedej Všeobecný šum, obrázky v odtieňoch šedej, silnejšie Všeobecný šum, obrázky v odtieňoch šedej, najsilnejší Všeobecná kompresia Všeobecná kompresia Texturizácia, kompresia h264 VHS kompresia Neštandardná kompresia (cinepak, msvideo1, roq) Bink kompresia, lepšia na geometrii Bink kompresia, silnejšia Bink kompresia, mäkká, zachováva detaily Eliminácia schodového efektu, vyhladenie Naskenované umenie/kresby, mierna kompresia, moaré Farebné pruhovanie Pomaly, odstraňuje poltóny Všeobecný kolorizér pre obrázky v odtieňoch šedej/č. pre lepšie výsledky použite DDColor Odstránenie okrajov Odstraňuje nadmerné ostrenie Pomaly, váhavo Anti-aliasing, všeobecné artefakty, CGI Spracovanie skenov KDM003 Ľahký model na vylepšenie obrazu Odstránenie artefaktov kompresie Odstránenie artefaktov kompresie Odstránenie obväzu s hladkým výsledkom Spracovanie poltónového vzoru Odstránenie vzoru rozkladu V3 Odstránenie artefaktov JPEG V2 Vylepšenie textúry H.264 Zostrenie a vylepšenie VHS Zlučovanie Veľkosť kúska Veľkosť prekrytia Obrázky väčšie ako %1$s pixlov budú rozrezané a spracované na kúsky, pričom sa tieto prekrývajú, aby sa predišlo viditeľným švom. Veľké veľkosti môžu spôsobiť nestabilitu zariadení nižšej kategórie Začnite výberom jedného Chcete odstrániť %1$s model? Budete si ho musieť stiahnuť znova Potvrďte Modelky Stiahnuté modely Dostupné modely Príprava Aktívny model Nepodarilo sa otvoriť reláciu Importovať je možné iba modely .onnx/.ort Importovať model Importujte vlastný model onnx na ďalšie použitie, akceptované sú iba modely onnx/ort, podporuje takmer všetky varianty podobné esrgan Importované modely Všeobecný hluk, farebné obrázky Všeobecný šum, farebné obrázky, silnejšie Všeobecný šum, farebné obrázky, najsilnejší Znižuje artefakty rozkladu a farebné pruhy, zlepšuje hladké prechody a ploché farebné oblasti. Zlepšuje jas a kontrast obrazu s vyváženými svetlami pri zachovaní prirodzených farieb. Zosvetlí tmavé obrázky a zároveň zachová detaily a zabráni preexponovaniu. Odstraňuje nadmerné farebné tónovanie a obnovuje neutrálnejšiu a prirodzenú rovnováhu farieb. Aplikuje tónovanie šumu založené na Poissonovi s dôrazom na zachovanie jemných detailov a textúr. Aplikuje jemné tónovanie Poissonovho šumu pre hladšie a menej agresívne vizuálne výsledky. Jednotné tónovanie šumu zamerané na zachovanie detailov a čistotu obrazu. Jemné rovnomerné tónovanie hluku pre jemnú textúru a hladký vzhľad. Opravuje poškodené alebo nerovné oblasti premaľovaním artefaktov a zlepšením konzistencie obrazu. Ľahký model na odstraňovanie pásov, ktorý odstraňuje farebné pruhy s minimálnymi nákladmi na výkon. Optimalizuje obrázky s veľmi vysokou kompresiou artefaktov (0-20% kvalita) pre lepšiu čistotu. Vylepšuje obrázky s vysoko kompresnými artefaktmi (20-40% kvalita), obnovuje detaily a znižuje šum. Zlepšuje obrázky s miernou kompresiou (40-60% kvalita), vyvážením ostrosti a plynulosti. Spresňuje obrázky pomocou ľahkej kompresie (60 – 80 % kvalita) na vylepšenie jemných detailov a textúr. Mierne vylepšuje takmer bezstratový obraz (80 – 100 % kvalita), pričom zachováva prirodzený vzhľad a detaily. Jednoduché a rýchle kolorovanie, kreslené, nie ideálne Mierne znižuje rozmazanie obrazu a zlepšuje ostrosť bez vnášania artefaktov. Dlhobežné operácie Spracovanie obrázka Spracovanie Odstraňuje ťažké artefakty kompresie JPEG na obrázkoch s veľmi nízkou kvalitou (0 – 20 %). Znižuje silné JPEG artefakty vo vysoko komprimovaných obrázkoch (20-40%). Vyčistí mierne JPEG artefakty pri zachovaní detailov obrazu (40 – 60 %). Zjemňuje svetlé JPEG artefakty v pomerne vysokej kvalite obrázkov (60-80%). Jemne redukuje drobné JPEG artefakty v takmer bezstratových obrázkoch (80 – 100 %). Zlepšuje jemné detaily a textúry a zlepšuje vnímanú ostrosť bez ťažkých artefaktov. Spracovanie ukončené Spracovanie zlyhalo Zlepšuje textúry a detaily pokožky, pričom zachováva prirodzený vzhľad, optimalizovaný pre rýchlosť. Odstraňuje artefakty kompresie JPEG a obnovuje kvalitu obrazu pre komprimované fotografie. Znižuje šum ISO na fotografiách zhotovených pri slabom osvetlení, pričom zachováva detaily. Opravuje preexponované alebo „jumbo“ zvýraznenia a obnovuje lepšie vyváženie tónov. Ľahký a rýchly kolorizačný model, ktorý dodáva obrázkom v odtieňoch sivej prirodzené farby. DEJPEG Odhlučniť Zafarbiť Artefakty Vylepšiť Anime Skenuje Upscale X4 upscaler pre všeobecné obrázky; malý model, ktorý využíva menej GPU a času, s miernym rozmazaním a odšumovaním. X2 upscaler pre všeobecné obrázky, zachovanie textúr a prirodzených detailov. X4 upscaler pre všeobecné obrázky s vylepšenými textúrami a realistickými výsledkami. X4 upscaler optimalizovaný pre anime obrázky; 6 blokov RRDB pre ostrejšie línie a detaily. X4 upscaler so stratou MSE poskytuje hladšie výsledky a znížené artefakty pre všeobecné obrázky. X4 Upscaler optimalizovaný pre anime obrázky; Variant 4B32F s ostrejšími detailmi a hladkými líniami. Model X4 UltraSharp V2 pre všeobecné obrázky; zdôrazňuje ostrosť a jasnosť. X4 UltraSharp V2 Lite; rýchlejšie a menšie, zachováva detaily pri použití menšieho množstva pamäte GPU. Ľahký model pre rýchle odstránenie pozadia. Vyvážený výkon a presnosť. Pracuje s portrétmi, objektmi a scénami. Odporúča sa pre väčšinu prípadov použitia. Odstráňte BG Hrúbka horizontálneho okraja Hrúbka vertikálneho okraja %1$s dňa %1$s farba %1$s farieb %1$s farieb Aktuálny model nepodporuje chunking, obraz bude spracovaný v pôvodných rozmeroch, čo môže spôsobiť vysokú spotrebu pamäte a problémy s low-end zariadeniami Chunking deaktivovaný, obrázok bude spracovaný v pôvodných rozmeroch, čo môže spôsobiť vysokú spotrebu pamäte a problémy s lacnejšími zariadeniami, ale môže poskytnúť lepšie výsledky pri odvodzovaní Chunking Vysoko presný model segmentácie obrazu na odstránenie pozadia Odľahčená verzia U2Net pre rýchlejšie odstraňovanie pozadia s menšou spotrebou pamäte. Úplný model DDColor poskytuje vysokokvalitné zafarbenie pre bežné obrázky s minimálnymi artefaktmi. Najlepšia voľba zo všetkých farebných modelov. DDColor vyškolené a súkromné ​​​​umelecké súbory údajov; vytvára rôznorodé a umelecké výsledky sfarbenia s menším počtom nerealistických farebných artefaktov. Ľahký model BiRefNet založený na Swin Transformer pre presné odstránenie pozadia. Vysokokvalitné odstránenie pozadia s ostrými hranami a vynikajúcim zachovaním detailov, najmä na zložitých objektoch a zložitých pozadiach. Model odstraňovania pozadia, ktorý vytvára presné masky s hladkými okrajmi, vhodné pre bežné objekty a miernym zachovaním detailov. Model je už stiahnutý Model bol úspešne importovaný Typ Kľúčové slovo Veľmi rýchly Normálne Pomaly Veľmi pomalé Vypočítajte percentá Minimálna hodnota je %1$s Skreslenie obrazu kresbou prstami Warp Tvrdosť Režim Warp Pohybujte sa Rast Zmenšiť Swirl CW Krúžte CCW Sila slabnutia Top Drop Spodná kvapka Spustite aplikáciu Drop End Drop Sťahovanie Hladké tvary Pre hladšie a prirodzenejšie tvary použite superelipsy namiesto štandardných zaoblených obdĺžnikov Typ tvaru Vystrihnúť Zaoblené Hladký Ostré hrany bez zaoblenia Klasické zaoblené rohy Tvary Typ Veľkosť rohov Squircle Elegantné zaoblené prvky používateľského rozhrania Formát názvu súboru Vlastný text umiestnený na samom začiatku názvu súboru, ideálny pre názvy projektov, značky alebo osobné značky. Používa pôvodný názov súboru bez prípony, čo vám pomôže zachovať identifikáciu zdroja neporušenú. Šírka obrázka v pixeloch, užitočná na sledovanie zmien rozlíšenia alebo škálovanie výsledkov. Výška obrázka v pixeloch, užitočná pri práci s pomermi strán alebo pri exporte. Generuje náhodné číslice na zaručenie jedinečných názvov súborov; pridajte viac číslic pre väčšiu bezpečnosť pred duplikátmi. Automatické počítadlo pre dávkové exporty, ideálne pri ukladaní viacerých obrázkov v jednej relácii. Vloží použitý názov predvoľby do názvu súboru, aby ste si ľahko zapamätali, ako bol obrázok spracovaný. Zobrazuje režim zmeny mierky obrazu použitý počas spracovania, čím pomáha rozlíšiť obrázky so zmenenou veľkosťou, orezané alebo prispôsobené. Vlastný text umiestnený na konci názvu súboru, užitočný pri vytváraní verzií ako _v2, _edited alebo _final. Prípona súboru (png, jpg, webp atď.), ktorá sa automaticky zhoduje so skutočným uloženým formátom. Prispôsobiteľná časová pečiatka, ktorá vám umožní definovať svoj vlastný formát podľa špecifikácie Java pre dokonalé triedenie. Typ flingu Android Natívne Štýl iOS Hladká krivka Rýchle zastavenie Skákanie Plávajúce Snappy Ultra hladké Adaptívny Prístupnosť Aware Znížený pohyb Natívna fyzika posúvania systému Android Vyvážené, plynulé rolovanie pre všeobecné použitie Vyššie trenie pri posúvaní podobnému iOS Jedinečná krivka spline pre zreteľný pocit rolovania Presné rolovanie s rýchlym zastavením Hravé, pohotové skákacie rolovanie Dlhé, kĺzavé rolky na prehliadanie obsahu Rýchle a citlivé posúvanie pre interaktívne používateľské rozhrania Prémiové plynulé rolovanie s predĺženou dynamikou Upravuje fyziku na základe rýchlosti odletu Rešpektuje nastavenia prístupnosti systému Minimálny pohyb pre potreby dostupnosti Primárne linky Každý piaty riadok pridá hrubší riadok Farba výplne Skryté nástroje Nástroje skryté na zdieľanie Farebná knižnica Prezrite si rozsiahlu kolekciu farieb Zaostruje a odstraňuje rozmazanie z obrázkov pri zachovaní prirodzených detailov, čo je ideálne na opravu rozostrených fotografií. Inteligentne obnovuje obrázky, ktorých veľkosť bola predtým zmenená, a obnovuje stratené detaily a textúry. Optimalizované pre obsah naživo, znižuje kompresné artefakty a vylepšuje jemné detaily v snímkach filmu/televíznej relácie. Konvertuje záznam v kvalite VHS na HD, odstraňuje šum na páske a zvyšuje rozlíšenie pri zachovaní vintage nádychu. Špecializovaný na obrázky a snímky obrazovky s vysokým obsahom textu, zaostruje znaky a zlepšuje čitateľnosť. Pokročilé upscaling trénované na rôznych súboroch údajov, vynikajúce pre všeobecné vylepšenie fotografií. Optimalizované pre fotografie komprimované na webe, odstraňuje JPEG artefakty a obnovuje prirodzený vzhľad. Vylepšená verzia pre webové fotografie s lepším zachovaním textúry a redukciou artefaktov. 2x upscaling s technológiou Dual Aggregation Transformer, zachováva ostrosť a prirodzené detaily. 3x upscaling pomocou pokročilej architektúry transformátora, ideálne pre potreby mierneho zväčšenia. 4x vysokokvalitný upscaling s najmodernejšou sieťou transformátorov, zachováva jemné detaily vo väčších mierkach. Odstraňuje rozmazanie/šum a chvenie z fotografií. Všeobecný účel, ale najlepšie na fotografiách. Obnovuje obrázky nízkej kvality pomocou transformátora Swin2SR, optimalizovaného pre degradáciu BSRGAN. Skvelé na opravu ťažkých kompresných artefaktov a vylepšenie detailov v 4-násobnej mierke. 4x upscaling s transformátorom SwinIR vyškoleným na degradáciu BSRGAN. Používa GAN pre ostrejšie textúry a prirodzenejšie detaily na fotografiách a zložitých scénach. Cesta Zlúčiť PDF Skombinujte viacero súborov PDF do jedného dokumentu Poradie súborov pp. Rozdeliť PDF Extrahujte konkrétne strany z dokumentu PDF Otočiť PDF Opravte orientáciu strany natrvalo Stránky Preusporiadať PDF Presuňte stránky a zmeňte ich poradie Podržte a potiahnite stránky Čísla strán Automaticky pridajte číslovanie do svojich dokumentov Formát štítku PDF na text (OCR) Extrahujte obyčajný text z dokumentov PDF Prekrytie vlastného textu pre budovanie značky alebo zabezpečenie Podpis Pridajte svoj elektronický podpis do akéhokoľvek dokumentu Toto sa použije ako podpis Odomknúť PDF Odstráňte heslá z chránených súborov Chráňte PDF Zabezpečte svoje dokumenty silným šifrovaním Úspech PDF je odomknuté, môžete ho uložiť alebo zdieľať Oprava PDF Pokúste sa opraviť poškodené alebo nečitateľné dokumenty Odtiene šedej Previesť všetky obrázky vložené do dokumentu na odtiene sivej Komprimovať PDF Optimalizujte veľkosť súboru dokumentu pre jednoduchšie zdieľanie ImageToolbox prestavuje internú tabuľku krížových odkazov a regeneruje štruktúru súborov od začiatku. To môže obnoviť prístup k mnohým súborom, ktoré \\"nemožno otvoriť\\" Tento nástroj skonvertuje všetky obrázky dokumentov do odtieňov sivej. Najlepšie na tlač a zmenšenie veľkosti súboru Metadáta Upravte vlastnosti dokumentu pre lepšie súkromie Tagy Producent Autor Kľúčové slová Tvorca Ochrana osobných údajov Deep Clean Vymažte všetky dostupné metadáta pre tento dokument Stránka Hlboké OCR Extrahujte text z dokumentu a uložte ho do jedného textového súboru pomocou nástroja Tesseract Nie je možné odstrániť všetky stránky Odstráňte stránky PDF Odstráňte konkrétne strany z dokumentu PDF Klepnite na Odstrániť Manuálne Orezať PDF Orezať strany dokumentu na ľubovoľné hranice Vyrovnať PDF Urobte PDF nezmeniteľný rastrovaním strán dokumentu Nepodarilo sa spustiť fotoaparát. Skontrolujte povolenia a uistite sa, že ho nepoužíva iná aplikácia. Extrahovať obrázky Extrahujte obrázky vložené do súborov PDF v pôvodnom rozlíšení Tento súbor PDF neobsahuje žiadne vložené obrázky Tento nástroj naskenuje každú stránku a obnoví zdrojové obrázky v plnej kvalite – ideálne na ukladanie originálov z dokumentov Nakreslite podpis Parametre pera Použite vlastný podpis ako obrázok, ktorý sa má umiestniť na dokumenty Zip vo formáte PDF Rozdeľte dokument s daným intervalom a zabaľte nové dokumenty do zip archívu Interval Tlač PDF Pripravte dokument na tlač s vlastnou veľkosťou strany Počet strán na hárok Orientácia Veľkosť strany Marža Bloom Mäkké koleno Optimalizované pre anime a kreslené filmy. Rýchle prevzorkovanie s vylepšenými prirodzenými farbami a menším počtom artefaktov Samsung One UI 7 ako štýl Tu zadajte základné matematické symboly na výpočet požadovanej hodnoty (napr. (5+5)*10) Matematický výraz Vyzdvihnite až %1$s obrázkov Ponechajte dátum a čas Vždy zachovávajte značky exif súvisiace s dátumom a časom, funguje nezávisle od možnosti zachovať exif Farba pozadia pre formáty alfa Pridáva možnosť nastaviť farbu pozadia pre každý formát obrázka s podporou alfa kanálov, keď je táto možnosť zakázaná, je k dispozícii iba pre iné než alfa verzie Otvorte projekt Pokračujte v úpravách predtým uloženého projektu Image Toolbox Nie je možné otvoriť projekt Image Toolbox Projektu Image Toolbox chýbajú projektové údaje Projekt Image Toolbox je poškodený Nepodporovaná verzia projektu Image Toolbox: %1$d Uložiť projekt Uložte vrstvy, pozadie a históriu úprav do upraviteľného súboru projektu Nepodarilo sa otvoriť Zápis do prehľadávateľného PDF Rozpoznajte text z obrázkovej dávky a uložte prehľadávateľný PDF s obrázkom a voliteľnou textovou vrstvou Vrstva alfa Horizontálne preklopenie Vertikálne preklopenie Zámok Pridajte tieň Farba tieňa Geometria textu Roztiahnutím alebo zošikmením textu dosiahnete ostrejšiu štylizáciu Mierka X Skočiť X Odstráňte anotácie Odstráňte vybraté typy anotácií, ako sú prepojenia, komentáre, zvýraznenia, tvary alebo polia formulárov zo stránok PDF Hypertextové odkazy Prílohy súborov Čiary Vyskakovacie okná Známky Tvary Textové poznámky Označenie textu Polia formulára značkovanie Neznámy Anotácie Zrušiť zoskupenie Pridajte tieň rozostrenia za vrstvu s konfigurovateľnou farbou a posunmi ================================================ FILE: core/resources/src/main/res/values-sr/strings.xml ================================================ Restart app About app No poboljšati pronađen Dodati Image: %d Reset Nešto ići zabluda Prepisivati na clipboard Izuzetak Edit EXIF. Ok No EXIF podatak pronađen Dodati tag Spremati Čist Čist EXIF. Ukidati All EXIF podatak of image volja biti raščišćavati, this mjera limenka ne biti undo! Presets Urod Saving All unsaved mijena volja biti izgubljen, li free leave sad Izvor kôd Dobiti the nov poboljšati, raspraviti tema a više Single resize Mijena specs of single dan image Izabrati color Izabrati color from image, kopija ili dio Image Color Color prepisivati Urod image na any bounds Inačica Tvrđava EXIF. Mijena preview Brisati Generate color paleta swatch from dan image Generate paleta Paleta Poboljšati Limenka ne generate paleta for dan image Original Izlaz folder Default Običaj Neodređen New inačica %1$s Unsupported tip: %1$s Aparat spremište Resize by težina Max broj in KB Resize a image following dan broj in KB Uporediti Uporediti dvojica dan image Izabrati dvojica image na početak Izabrati image Setting Noć mode Dark Svjetlo Sustav Dinamičan colors Customization Odobriti image monet Li omogućivati, kada free birati a image na edit, app colors volja biti usvojiti na this image Jezik Amoled mode Li omogućivati podloga color volja biti pribor na apsolutan dark in noć mode Color scheme Red Green Modrina Pasta važeći ARGB-Kôd. Ništica na pasta Limenka ne mijena app color scheme while dinamičan colors biti turn on App tema volja biti bazirati on color, which free volja select Tema tracker Slati tuda bug dojava a feature zamolba Pomoć prevoditi Ispravan translation greška ili localize projekt na another jezik Ništica pronađen by tvoj query Potraga tuda Li omogućivati, tadašnji app colors volja biti usvojiti na wallpaper colors Osnovan Tertiary Srednji Granica debljina Podloga Vrednota Dozvola Grant App nužda pristup na tvoj spremište na spremati image, free i neodložan, without da free moći ne rad, naime please grant dozvola on the budući dialog Zakazati na spremati %d image(s) App nužda this dozvola na rad, please grant free manually Vanjski spremište Monet colors This prijava i sasvim free, ali li free want to pomoć the projekt razvoj, free limenka click tuda FAB alignment Pregled for poboljšati Li omogućivati, poboljšati dialog volja biti pokazati na free after app startup Image zoom Dio Prefiks Filename Nešto ići zabluda: %1$s Broj %1$s Loading… Image i odveć veliko na preview, ali free volja biti kušati na spremati free anyway Izabrati image na početak Svojstvo Produživanje Resize tip Explicit Fleksibilan Izabrati mage. Biti free tada want to close the app? App zatvaranje Boravak Close Reset image Image mijena volja biti rollback na početak vrednota Vrednota properly reset Širina %1$s Kota %1$s баланс беле Температура Нијанса Моноцхроме Хигхлигхтс Сенке Вибранце Цроссхатцх Собел едге Избриши ЕКСИФ Опције аранжмана Уредити Филл Светлост Крај Кувахара смоотхинг Радијус Скала Ограничења промене величине Скица Праг Нивои квантизације Смоотх тоон Тоон Не максимално сузбијање Емоји Стацк блур Цонволутион 3к3 Праг осветљености Изаберите који емотикони ће бити приказани на главном екрану Додајте величину датотеке Ако је омогућено, додаје ширину и висину сачуване слике имену излазне датотеке Избришите ЕКСИФ метаподатке са било које пар слика Прегледајте било коју врсту слика: ГИФ, СВГ и тако даље Филе екплорер Андроид модерни бирач фотографија који се појављује на дну екрана, може да ради само на андроиду 12+ и такође има проблема са примањем ЕКСИФ метаподатака Једноставан бирач слика у галерији, радиће само ако имате ту апликацију Користите ГетЦонтент намеру да изаберете слику, ради свуда, али такође може имати проблема са пријемом изабраних слика на неким уређајима, то није моја грешка Ред Одређује редослед опција на главном екрану Замените редни број Ако је омогућено, стандардну временску ознаку замењује редним бројем слике ако користите групну обраду Учитај слику са мреже Учитајте било коју слику са интернета, прегледајте је, зумирајте, а такође је сачувајте или уредите ако желите Нема слике Слика линк Фит Присилно претвара сваку слику у слику дату параметром ширине и висине - може променити однос ширине и висине Промена величине слика на слике са дугачком страном датом параметром ширине или висине, сви прорачуни величине ће се обавити након чувања - задржава однос ширине и висине Осветљеност Контраст Нијанса Засићење Додајте филтер Филтер Примените било који ланац филтера на дате слике Филтери Филтер у боји Алпха Изложеност Гама Нагласци и сенке Хазе Ефекат Удаљеност Нагиб Схарпен Сепиа Негативно Соларизуј Црно и бело Размак Ширина линије Блур Полутон ГЦА простор боја Гаусово замућење Замућење кутије Двострано замућење Ембосс Лапласов Вињета Почетак Дисторзија Угао Свирл Булге Дилатација Рефракција сфере Индекс преламања Рефракција стаклене сфере Матрица боја Непрозирност Промените величину дате слике да бисте пратили дате границе ширине и висине уз чување односа ширине и висине Постеризе Слабо укључивање пиксела Потражити РГБ филтер Лажна боја Прва боја Друга боја Реордер Брзо замућење Величина замућења Центар замућења к Замагљивање центар и Зоом блур Баланс боја Преглед слике Извор слике Бирач фотографија Галерија Скала садржаја Емоџији се рачунају секвенцаНум оригиналФиленаме Додајте оригинално име датотеке Ако је омогућено, додаје оригинално име датотеке у име излазне слике Додавање оригиналног назива датотеке не функционише ако је изабран извор слике за бирач фотографија Драв Величина фајла Фајл обрађен Максимална величина датотеке је ограничена Андроид ОС-ом и доступном меморијом, што очигледно зависи од вашег уређаја. \nИмајте на уму: меморија није складиште. Цацхе Боја боје Паинт алпха Изаберите слику и нацртајте нешто на њој Изаберите датотеку за почетак Шифровање Кључ Имплементација Компатибилност Шифровање датотека на основу лозинке. Настављене датотеке се могу чувати у изабраном директоријуму или делити. Дешифроване датотеке се такође могу директно отворити. Величина кеша Пронађено %1$s Алати Резервна опција Скип Неважећа лозинка или изабрана датотека није шифрована Дешифровање Снимак екрана Цртајте на слици као у књизи за цртање или цртајте по самој позадини Имајте на уму да компатибилност са другим софтвером или услугама за шифровање датотека није загарантована. Нешто другачији третман кључа или конфигурација шифре могу бити разлози за некомпатибилност. Цртајте на слици Цртајте на позадини Изаберите боју позадине и нацртајте на њој Боја позадине Ципхер Аутоматско брисање кеша Ако је омогућена, кеш апликације ће бити обрисан при покретању апликације Уредите снимак екрана Секундарно прилагођавање Копирај Чување у режиму %1$s може бити нестабилно, јер је формат без губитака Покушај да сачувате слику са датом ширином и висином може изазвати ООМ грешку, урадите то на сопствену одговорност и немојте рећи да вас нисам упозорио! Групирајте опције по типу Групе опције на главном екрану њиховог типа уместо прилагођеног распореда листе Није могуће променити распоред док је груписање опција омогућено Онемогућили сте апликацију Датотеке, активирајте је да бисте користили ову функцију Шифрујте и дешифрујте било коју датотеку (не само слику) на основу АЕС крипто алгоритма Изаберите датотеку Шифруј Дешифрујте Сачувајте ову датотеку на свом уређају или користите акцију дељења да бисте је ставили где год желите Карактеристике АЕС-256, ГЦМ режим, без допуна, 12 бајтова насумичних ИВ. Кључеви се користе као СХА-3 хеш (256 бита). Креирај Енханцед Пикелатион Строке Пикелатион Побољшана дијамантска пикселација Режим цртања Померање канала Кс Тент Блур Емаил Цртање стрелица Врати слику Аутоматско брисање позадине Емоције Пипета Текст Избриши шему Ажурирања Оријентација & Само откривање скрипте Сингле Цолумн Вертикални текст у једном блоку Једна реч Сирова линија Квар Износ Семе Анаглиф Бука Пикел Сорт Драго Алдридге Одрезати Уцхимура Мобиус Прелаз Пеак Аномалија боја Није пронађен директоријум \"%1$s\", пребацили смо га на подразумевани, сачувајте датотеку поново Цлипбоард Ауто пин Аутоматски додаје сачувану слику у међуспремник ако је омогућено Оверврите Филес Оригинална датотека ће бити замењена новом уместо чувања у изабраном фолдеру, ова опција треба да извор слике буде \"Екплорер\" или ГетЦонтент, када ово пребаците, аутоматски ће бити подешена Претрага бесплатно Цреате Иссуе Ако сте изабрали унапред подешено 125, слика ће бити сачувана као 125% величине оригиналне слике са 100% квалитетом. Ако изаберете унапред подешено 50, слика ће бити сачувана са 50% величине и 50% квалитета. Пресет овде одређује % излазне датотеке, тј. ако изаберете унапред подешено 50 на слици од 5мб онда ћете након чувања добити слику од 2,5мб Ако је омогућено излазно име датотеке ће бити потпуно насумично Сачувано у фолдеру %1$s са именом %2$s Сачувано у фолдеру %1$s Телеграм ћаскање Прављење резервних копија и враћање Разговарајте о апликацији и добијте повратне информације од других корисника. Такође можете добити бета ажурирања и увиде овде. Цроп маск Однос ширине и висине Користите овај тип маске да креирате маску од дате слике, приметите да ТРЕБА да има алфа канал Бацкуп Ресторе Направите резервну копију подешавања апликације у датотеку Вратите подешавања апликације из претходно генерисане датотеке Оштећена датотека или није резервна копија Подешавања су успешно враћена Контактирај ме Ово ће вратити ваша подешавања на подразумеване вредности. Имајте на уму да ово не може да се поништи без горе поменуте резервне копије. Избриши Спремате се да избришете изабрану шему боја. Ова операција се не може опозвати Фонт Скала фонта Уобичајено Коришћење великих размера фонта може да изазове грешке у корисничком интерфејсу и проблеме, који се неће поправити. Користите опрезно. Аа Бб Вв Гг Дд Ђђ Ее Жж Зз Ии Јј Кк Лл Љљ Мм Нн Њњ Оо Пп Рр Сс Тт Ћћ Уу Фф Хх Цц Чч Џџ Шш 0123456789 !? Храна и пиће Природа и животиње Симболи Омогући емоџи Путовања и места Активности Трим имаге Оригинални метаподаци слике ће бити сачувани Прозирни простори око слике ће бити исечени Режим брисања Обриши позадину Врати позадину Радијус замућења Упс… Нешто је пошло наопако. Можете ми писати користећи опције испод и ја ћу покушати да пронађем решење Промените величину датих слика или их конвертујте у друге формате. ЕКСИФ метаподаци се такође могу уређивати овде ако одаберете једну слику. Ово омогућава апликацији да ручно прикупља извештаје о паду Аналитика Дозволи прикупљање анонимне статистике коришћења апликације Тренутно формат %1$s дозвољава само читање ЕКСИФ метаподатака на андроид-у. Излазна слика уопште неће имати метаподатке када се сачува. Напор Вредност %1$s значи брзу компресију, што резултира релативно великом величином датотеке. %2$s значи спорију компресију, што резултира мањом датотеком. Чување је скоро завршено. Ако сада откажете, поново ћете морати да сачувате. Дозволи бета верзије Ако је омогућено, путања за цртање ће бити представљена као показивачка стрелица Провера ажурирања ће укључити бета верзије апликације ако је омогућена Мекоћа четкице Донација Изаберите најмање 2 слике Скала излазне слике Оријентација слике Хоризонтално Вертикала Скалирајте мале слике на велике Мале слике ће бити скалиране на највећу у низу ако је омогућено Редослед слика Замагљивање ивица Црта замућене ивице испод оригиналне слике да попуни просторе око ње уместо једне боје ако је омогућено Неутрално Вибрант Пикселација Побољшана кружна пикселација Уклони боју Рецоде Палетте стиле Тонал Спот Експресивно Раинбов Воћна салата Верност Подразумевани стил палете, омогућава прилагођавање све четири боје, друге вам омогућавају да подесите само кључну боју Стил који је мало више хроматичан од монохроматског Садржај Гласна тема, шареност је максимална за Примари палету, повећана за друге Разиграна тема - нијанса изворне боје се не појављује у теми Хигхлигхтер И једно и друго Омогућава могућност претраживања свих доступних опција на главном екрану Једноставан ПДФ преглед Претворите ПДФ у слике у датом излазном формату Спакујте дате слике у излазну ПДФ датотеку Филтер маске Примените ланце филтера на дате маскиране области, свака област маске може да одреди сопствени сет филтера Маске Додајте маску маска %d Спремате се да избришете изабрану маску филтера. Ова операција се не може опозвати Обриши маску Крај Једноставне варијанте Неон Хемијска оловка Приваци Блур Додајте неки сјајни ефекат својим цртежима Подразумевано, најједноставније - само боја Замагљује слику испод нацртане путање да бисте заштитили све што желите да сакријете Слично замућењу приватности, али пикселира уместо замагљивања Омогућава цртање сенке иза клизача Омогућава цртање сенки иза прекидача Омогућава цртање сенке иза плутајућих акционих дугмади Омогућава цртање сенке иза подразумеваних дугмади Омогућава цртање сенке иза трака апликације Доубле Арров Црта путању од почетне до крајње тачке као права Црта стрелицу која показује од почетне до крајње тачке као линију Вибрације Вибратион Стренгтх Да бисте заменили датотеке потребно је да користите извор слике \"Екплорер\", покушајте да поново изаберете слике, променили смо извор слике у потребан Празан Суфикс Цатмулл Бицубиц Басиц Најједноставнији Андроид режим скалирања који се користи у скоро свим апликацијама Метода за глатку интерполацију и поновно узорковање скупа контролних тачака, који се обично користи у компјутерској графици за креирање глатких кривих Линеарна (или билинеарна, у две димензије) интерполација је типично добра за промену величине слике, али узрокује неко нежељено омекшавање детаља и још увек може бити донекле назубљена Боље методе скалирања укључују Ланчосово поновно узорковање и Митцхелл-Нетравали филтере Један од једноставнијих начина повећања величине, замена сваког пиксела са бројем пиксела исте боје Техника математичке интерполације која користи вредности и деривате на крајњим тачкама сегмента криве да генерише глатку и континуирану криву Функција прозора се често примењује у обради сигнала да би се смањило спектрално цурење и побољшала тачност анализе фреквенције сужавањем ивица сигнала Омогућава лупу на врху прста у режимима цртања ради боље приступачности Метода поновног узорковања која одржава интерполацију високог квалитета применом пондерисане синк функције на вредности пиксела Користи полиномске функције дефинисане по комадима за глатку интерполацију и апроксимацију криве или површине, флексибилно и континуирано представљање облика Форсира екиф виџет да се прво провери Дозволи више језика Аутоматска оријентација & Детекција скрипте Само аутоматски Ауто Појединачни блок Једна линија Заокружи реч Сингле цхар Ретки текст Оријентација ретког текста & Откривање скрипте Да ли желите да избришете језик \"%1$s\" ОЦР податке о обуци за све типове препознавања или само за изабрани (%2$s)? Децал Боја зауставља Додај боју Својства Понавља водени жиг преко слике уместо појединачног на датој позицији Покријте слике са прилагодљивим воденим жиговима за текст/слику Оффсет Кс Оффсет И Ватермарк Типе Ова слика ће се користити као образац за водени жиг Боја текста Користите величину првог кадра Замените наведену величину димензијама првог оквира Репеат Цоунт Фраме Делаи миллис ФПС Баиер Тхрее Би Тхрее Дитхеринг Баиер Фоур Би Фоур Дитхеринг Баиер Еигхт Би Еигхт Дитхеринг Флоид Стеинберг Дитхеринг Нативе Стацк Блур Тилт Схифт мешање Енханцед Глитцх Померање канала И Величина корупције Корупција Схифт Кс Корупција Схифт И Сиде Фаде Сиде Врх Дно Снага Мермер Турбуленција Учесталост И Амплитуда Кс АЦЕС Хилл Тоне Маппинг Хабле Филмиц Тоне Маппинг Хејл Бургесс Тоне Маппинг Цолор Матрик 4к4 Цолор Матрик 3к3 Симпле Еффецтс Берба Цода Цхроме Полароид Тритономалија Деутаромалија Протономалија Ноћна визија Топло Хладан Тританопиа Унсхарп Пастел Оранге Хазе Пинк Дреам Златни сат Топло лето Пурпле Мист излазак Сунца Цолорфул Свирл Софт Спринг Лигхт Аутумн Тонес Лаванда Дреам Циберпунк Лемонаде Лигхт Спецтрал Фире Нигхт Магиц Фантаси Ландсцапе Експлозија боје Елецтриц Градиент Царамел Даркнесс Футуристички градијент Дигитал Цоде Облик иконе Слике су преписане на оригиналном одредишту Није могуће променити формат слике док је омогућена опција преписивања датотека Емоџи као шема боја Користи примарну боју емоџија као шему боја апликације уместо ручно дефинисане Објекти Уклањање позадине Уклоните позадину са слике цртањем или користите опцију Ауто Промени величину и претвори Диамонд Пикелатион Цирцле Пикелатион Замените боју Толеранција Боја за замену Таргет Цолор Боја за уклањање Ероде Анизотропна дифузија Дифузија Спровођење Хоризонтално тетурање ветра Брзо двострано замућење Поиссон Блур Логаритамско пресликавање тонова Кристализуј Строке Цолор Фрацтал Гласс Амплитуда уље Ватер Еффецт Величина Учесталост Кс Амплитуда И Перлин Дистортион АЦЕС Филмиц Тоне Маппинг Тренутни Све Фулл Филтер Почетак Центар Примените све ланце филтера на дате слике или једну слику Радите са ПДФ датотекама: Прегледајте, Конвертујте у групу слика или креирајте једну од датих слика Преглед ПДФ ПДФ у слике Слике у ПДФ Градиент Макер Направите градијент задате излазне величине са прилагођеним бојама и типом изгледа Брзина Дехазе Омега ПДФ алати Рангирај апликацију Рате Ова апликација је потпуно бесплатна, ако желите да постане већа, означите пројекат на Гитхуб-у 😄 Бровни Деутаронотопиа Протанопија Ахроматомалија Ахроматопсија Линеар Радиал Свееп Градиент Типе Центар Кс Центар И Тиле Моде Поновљено Огледало Стезаљка Ласо Црта затворену попуњену путању датом путањом Режим цртања путање Двострука линијска стрелица Бесплатно цртање Лине Арров Стрелац Линија Црта путању као улазну вредност Црта стрелицу која показује са дате путање Црта двоструку стрелицу од почетне до крајње тачке као линију Црта двоструку стрелицу са дате путање Оутлинед Овал Оутлинед Рецт Овал Рецт Црта правоугаоник од почетне до крајње тачке Црта овално од почетне до крајње тачке Црта оцртани овал од почетне до крајње тачке Црта оцртани правоугаоник од почетне до крајње тачке Дитхеринг Куантизиер Граи Сцале Баиер Тво Би Тво Дитхеринг Јарвис Јудице Нинке Дитхеринг Сиерра Дитхеринг Сиерра Лите Дитхеринг Тво Ров Сиерра Дитхеринг Аткинсон Дитхеринг Стуцки Дитхеринг Буркес Дитхеринг Фалсе Флоид Стеинберг Дитхеринг Дитхеринг са лева на десно Рандом Дитхеринг Симпле Тхресхолд Дитхеринг Чекати Боја маске Преглед маске Нацртана маска филтера ће бити приказана да вам покаже приближан резултат Сцале моде Билинеар Ханн Хермите Ланцзос Митцхелл Најближи Сплине Задана вриједност Вредност у опсегу %1$s - %2$s Сигма Спатиал Сигма Медиан Блур Метода поновног узорковања која користи филтер конволуције са подесивим параметрима да би се постигао баланс између оштрине и анти-алиасинг-а на скалираној слици Онли Цлип Чување у меморију неће бити извршено, а слика ће се покушати ставити само у међуспремник Додаје контејнер са изабраним обликом испод водећих икона картица Имаге Ститцхинг Комбинујте дате слике да бисте добили једну велику Насумично подеси име датотеке Спровођење осветљености Екран Градиент Оверлаи Направите било који градијент врха дате слике Трансформације Камера Користи камеру за снимање слике, имајте на уму да је могуће добити само једну слику са овог извора слике Зрно Греен Сун Раинбов Ворлд Дееп Пурпле Свемирски портал Ред Свирл Ватермаркинг Поновите водени жиг Оверлаи Моде Величина пиксела Закључајте оријентацију цртања Максимални број боја Ако је омогућено у режиму цртања, екран се неће ротирати Бокех ГИФ алати Претворите слике у ГИФ слику или издвојите оквире из дате ГИФ слике ГИФ за слике Претворите ГИФ датотеку у групу слика Претворите серију слика у ГИФ датотеку Слике у ГИФ Изаберите ГИФ слику за почетак Користите ласо Користи Лассо као у режиму цртања за брисање Алфа преглед оригиналне слике Емоџи траке апликација ће се непрекидно мењати насумично уместо да се користе изабрани Насумични емоџи Не може да се користи насумично бирање емоџија док су емоџији онемогућени Не могу да изаберем емоџи док је омогућено насумично бирање Провери ажурирања Олд ТВ Схуффле Блур ОЦР (препознавање текста) Препознајте текст са дате слике, подржано је 120+ језика Слика нема текст или је апликација није нашла Accuracy: %1$s Рецогнитион Типе Фаст Стандард Најбољи Нема података За правилно функционисање Тессерацт ОЦР-а потребно је да се на ваш уређај преузму додатни подаци за обуку (%1$s). \nДа ли желите да преузмете %2$s податке? Преузимање Нема везе, проверите и покушајте поново да бисте преузели моделе возова Преузети језици Доступни језици Режим сегментације Четкица ће вратити позадину уместо брисања Хоризонтал Грид Вертицал Грид Ститцх Моде Број редова Цолумнс Цоунт Користите Пикел Свитцх Прекидач налик на пиксел ће се користити уместо гоогле материјала који сте базирали Тобоган Раме уз раме Тоггле Тап Транспарентност Замењена датотека са именом %1$s на оригиналном одредишту Лупа Присилна почетна вредност Фаворите Још увек није додат ниједан омиљени филтер Б Сплине Користи по комадима дефинисане бикубне полиномске функције за глатку интерполацију и апроксимацију криве или површине, флексибилно и континуирано представљање облика Редовно Инверзни тип попуњавања Ако је омогућено, све немаскиране области ће бити филтриране уместо подразумеваног понашања Конфети Конфете ће бити приказане приликом чувања, дељења и других примарних радњи Сецуре Моде Сакрива садржај при изласку, такође се екран не може снимити или снимити Слике ће бити изрезане по средини на унету величину. Платно ће бити проширено датом бојом позадине ако је слика мања од унесених димензија. Ауто Ротате Омогућава усвајање оквира ограничења за оријентацију слике Монохромна тема, боје су чисто црне/беле/сиве Шема која поставља изворну боју у Сцхеме.примариЦонтаинер Шема која је веома слична шеми садржаја Нацртајте полупровидне изоштрене путање маркера Контејнери Омогућава цртање сенке иза контејнера Клизачи Прекидачи ФАБс Дугмад Апп Барс Овај алат за проверу ажурирања ће се повезати са ГитХуб-ом у циљу провере да ли је доступно ново ажурирање Пажња Фадинг Едгес Онемогућено Инверт Цолорс Замењује боје теме негативним ако је омогућено Изађи Ако сада напустите преглед, мораћете поново да додате слике Формат слике Ствара палету \"Material You \" са слике Тамне боје Користи шему боја ноћног режима уместо светле опције Копирајте као код \"Jetpack Compose\" Замагљивање прстена Унакрсно замућење Замагљивање круга Звездана тачка Линеарни помак нагиба Ознаке за брисање АПНГ алати Претворите слике у АПНГ слику или издвојите оквире из дате АПНГ слике АПНГ за слике Изаберите АПНГ слику за почетак Претворите серију слика у АПНГ датотеку Слике у АПНГ Мотион Блур Претворите АПНГ датотеку у серију слика Зип Креирајте Зип датотеку од датих датотека или слика Ширина ручке превлачења Цонфетти Типе Свечана Експлодирај Киша Углови ЈКСЛ Тоолс Извршите ЈКСЛ ~ ЈПЕГ транскодирање без губитка квалитета или конвертујте ГИФ/АПНГ у ЈКСЛ анимацију ЈКСЛ у ЈПЕГ Извршите транскодирање без губитака из ЈКСЛ у ЈПЕГ Извршите транскодирање без губитака из ЈПЕГ у ЈКСЛ ЈПЕГ у ЈКСЛ Изаберите ЈКСЛ слику за почетак Фаст Гауссиан Блур 2Д Фаст Гауссиан Блур 3Д Фаст Гауссиан Блур 4Д Ауто Ускрс Дозвољава апликацији да аутоматски налепи податке међумеморије, тако да ће се појавити на главном екрану и моћи ћете да их обрадите Хармонизатион Цолор Ниво хармонизације Ланцзос Бессел Метода поновног узорковања која одржава интерполацију високог квалитета применом Беселове (јинц) функције на вредности пиксела ГИФ у ЈКСЛ Претворите ГИФ слике у ЈКСЛ анимиране слике АПНГ у ЈКСЛ Претворите АПНГ слике у ЈКСЛ анимиране слике ЈКСЛ у слике Претворите ЈКСЛ анимацију у скуп слика Слике у ЈКСЛ Претворите серију слика у ЈКСЛ анимацију Понашање Прескочи бирање датотека Бирач датотека ће се одмах приказати ако је то могуће на одабраном екрану Генеришите прегледе Омогућава генерисање прегледа, ово може помоћи да се избегну рушења на неким уређајима, ово такође онемогућава неке функције уређивања унутар једне опције за уређивање Компресија са губитком Користи компресију са губицима да смањи величину датотеке уместо без губитака Тип компресије Контролише брзину декодирања резултујуће слике, ово би требало да помогне да се добијена слика брже отвори, вредност %1$s значи најспорије декодирање, док %2$s - најбрже, ово подешавање може повећати величину излазне слике Сортирање Датум Датум (обрнуто) Име Име (обрнуто) Конфигурација канала данас Јучер Уграђени бирач Бирач слика Имаге Тоолбок-а Нема дозвола Захтев Изаберите више медија Изаберите Сингле Медиа Пицк Покушајте поново Прикажи подешавања у пејзажу Ако је ово онемогућено, у пејзажном режиму подешавања ће бити отворена на дугмету на горњој траци апликација као и увек, уместо трајно видљиве опције Подешавања целог екрана Омогућите га и страница са подешавањима ће се увек отварати на целом екрану уместо листа фиоке који се може померати Тип прекидача Цомпосе Јетпацк Цомпосе Материјал који мењате Материјал који мењате Макс Промени величину сидра Пикел Течно Прекидач заснован на систему дизајна \"Флуент\". Цупертино Прекидач заснован на систему дизајна \"Цупертино\". Слике у СВГ Пратите дате слике у СВГ слике Користите узорковану палету Палета квантизације ће бити узоркована ако је ова опција омогућена Патх Изостави Употреба овог алата за праћење великих слика без смањења скалирања се не препоручује, може изазвати пад и продужити време обраде Смањење слике Слика ће бити смањена на мање димензије пре обраде, што помаже алату да ради брже и безбедније Минимални однос боја Линије Праг Куадратиц Тхресхолд Толеранција заокруживања координата Патх Сцале Ресетујте својства Сва својства ће бити постављена на подразумеване вредности, приметите да се ова радња не може опозвати Детаљно Подразумевана ширина линије Енгине Моде Легаци ЛСТМ мрежа Легаци &amp; ЛСТМ Цонверт Конвертујте групе слика у дати формат Додај нову фасциклу Битс пер Сампле Компресија Пхотометриц Интерпретатион Узорци по пикселу Планарна конфигурација И Цб Цр Подузорковање И Цб Цр Позиционирање Кс Резолуција И Ресолутион Ресолутион Унит Стрип Оффсетс Редови по траци Стрип Бите Цоунтс Формат за размену ЈПЕГ ЈПЕГ Интерцханге Формат Дужина Трансфер Функција Вхите Поинт Примари Цхроматицитиес И Цб Цр коефицијенти Референца Блацк Вхите Датум и време Опис слике Маке Модел софтвер Уметник Цопиригхт Екиф верзија Фласхпик верзија Цолор Спаце Гама Пикел Кс димензија Пикел И димензија Компримовани битови по пикселу Напомена произвођача Коментар корисника Повезана звучна датотека Датум Време Оригинал Датум и време дигитализовано Оффсет Тиме Оффсет Тиме Оригинал Оффсет Тиме Дигитализед Суб Сец Тиме Суб Сец Време Оригинал Подсек Време Дигитализовано Време излагања Ф број Програм експозиције Спектрална осетљивост Пхотограпхиц Сенситивити Оецф Тип осетљивости Стандардна излазна осетљивост Препоручени индекс изложености ИСО брзина ИСО брзина Латитуде иии ИСО брзина Латитуде ззз Вредност брзине затварача Апертуре Валуе Бригхтнесс Валуе Вредност пристраности експозиције Максимална вредност отвора бленде Субјецт Дистанце Режим мерења Фласх Предметна област Фоцал Ленгтх Фласх Енерги Просторни фреквентни одзив Резолуција фокусне равни Кс Резолуција жаришне равни И Јединица резолуције фокусне равни Локација предмета Индекс експозиције Сенсинг Метход Извор датотеке ЦФА Паттерн Цустом Рендеред Режим експозиције Баланс белог Однос дигиталног зума Фокална дужина у филму од 35 мм Тип снимања сцене Гаин Цонтрол Контраст Сатуратион Оштрина Опис подешавања уређаја Опсег удаљености субјекта Јединствени ИД слике Име власника камере Серијски број тела Спецификација објектива Ленс Маке Модел објектива Серијски број објектива ИД верзије ГПС-а ГПС Латитуде Реф ГПС Латитуде ГПС дужина Реф ГПС Лонгитуде ГПС висина Реф ГПС Алтитуде ГПС временска ознака ГПС сателити ГПС статус ГПС режим мерења ГПС ДОП ГПС брзина Реф ГПС Спеед ГПС Трак Реф ГПС Трацк ГПС Имг Дирецтион Реф ГПС Имг Дирецтион ГПС Мап Датум ГПС Дест Латитуде Реф ГПС Дест Латитуде ГПС дужина одредишта Реф ГПС географска дужина одредишта ГПС Дест Беаринг Реф ГПС одредиште ГПС одредишна удаљеност Реф ГПС одредишна удаљеност Метода ГПС обраде ГПС информације о подручју ГПС печат датума ГПС Дифферентиал Грешка ГПС Х позиционирања Индекс интероперабилности ДНГ верзија Подразумевана величина исецања Почетак прегледа слике Превиев Имаге Ленгтх Аспецт Фраме Доња ивица сензора Лева ивица сензора Десна ивица сензора Горња ивица сензора ИСО Нацртајте текст на путањи са датим фонтом и бојом Величина фонта Величина воденог жига Поновите текст Тренутни текст ће се понављати до краја путање уместо једнократног цртања Дасх Сизе Користите изабрану слику да је нацртате дуж дате путање Ова слика ће се користити као понављајући унос нацртане путање Црта оцртани троугао од почетне до крајње тачке Црта оцртани троугао од почетне до крајње тачке Оцртани троугао Троугао Црта полигон од почетне до крајње тачке Полигон Оцртани полигон Црта оцртани полигон од почетне до крајње тачке Вертицес Нацртајте правилан полигон Нацртајте полигон који ће бити правилан уместо слободног облика Црта звезду од почетне до крајње тачке Звезда Оутлинед Стар Црта оцртану звезду од почетне до крајње тачке Однос унутрашњег радијуса Нацртај редовну звезду Извуците звезду која ће бити регуларна уместо слободног облика Антиалиас Омогућава антиалиасинг да спречи оштре ивице Отворите Уреди уместо прегледа Када изаберете слику за отварање (преглед) у ИмагеТоолбок-у, отвориће се листа за уређивање уместо прегледа Скенер докумената Скенирајте документе и креирајте ПДФ или одвојене слике од њих Кликните да бисте започели скенирање Започните скенирање Сачувај као ПДФ Подели као ПДФ Опције испод су за чување слика, а не ПДФ-а Изједначите хистограм ХСВ Изједначите хистограм Унесите проценат Дозволите унос преко текстуалног поља Омогућава текстуално поље иза избора унапред подешених поставки да бисте их унели у ходу Сцале Цолор Спаце Линеар Изједначите пикселацију хистограма Величина мреже Кс Величина мреже И Екуализе Хистограм Адаптиве Екуализе Хистограм Адаптиве ЛУВ Екуализе Хистограм Адаптиве ЛАБ ЦЛАХЕ ЦЛАХЕ ЛАБ ЦЛАХЕ ЛУВ Изрежите до садржаја Боја оквира Боја за игнорисање Темплате Нема додатих филтера шаблона Цреате Нев Скенирани КР код није важећи шаблон филтера Скенирајте КР код Изабрана датотека нема податке шаблона филтера Цреате Темплате Име шаблона Ова слика ће се користити за преглед овог шаблона филтера Филтер шаблона Као слика КР кода Као фајл Сачувај као датотеку Сачувај као слику КР кода Избриши шаблон Спремате се да избришете изабрани филтер шаблона. Ова операција се не може опозвати Додан шаблон филтера са именом \"%1$s\" (%2$s) Преглед филтера КР и бар код Скенирајте КР код и преузмите његов садржај или налепите стринг да бисте генерисали нови Садржај кода Скенирајте било који бар код да бисте заменили садржај у пољу или унесите нешто да бисте генерисали нови бар код са изабраним типом КР Десцриптион Мин Дајте камери дозволу у подешавањима за скенирање КР кода Дајте камери дозволу у подешавањима за скенирање скенера докумената Цубиц Б-сплине Хаминг Ханнинг Блацкман Велцх Куадриц Гаусов Спхинк Бартлетт Робидоук Робидоук Схарп Сплине 16 Сплине 36 Сплине 64 Каисер Бартлетт-Хе Кутија Бохман Ланчош 2 Ланчош 3 Ланчош 4 Ланцзос 2 Јинц Ланчош 3 Јинц Ланцзос 4 Јинц Кубична интерполација обезбеђује глатко скалирање узимајући у обзир најближих 16 пиксела, дајући боље резултате од билинеарне Користи полиномске функције дефинисане по комадима за глатку интерполацију и апроксимацију криве или површине, флексибилно и континуирано представљање облика Функција прозора која се користи за смањење спектралног цурења сужавањем ивица сигнала, корисна у обради сигнала Варијанта Ханновог прозора, који се обично користи за смањење спектралног цурења у апликацијама за обраду сигнала Функција прозора која обезбеђује добру резолуцију фреквенције минимизирањем спектралног цурења, често се користи у обради сигнала Функција прозора дизајнирана да даје добру резолуцију фреквенције са смањеним спектралним цурењем, која се често користи у апликацијама за обраду сигнала Метода која користи квадратну функцију за интерполацију, дајући глатке и континуиране резултате Метода интерполације која примењује Гаусову функцију, корисна за изглађивање и смањење шума на сликама Напредни метод поновног узорковања који обезбеђује висококвалитетну интерполацију са минималним артефактима Функција троугластог прозора која се користи у обради сигнала за смањење спектралног цурења Метода интерполације високог квалитета оптимизована за природну промену величине слике, балансирање оштрине и глаткоће Оштрија варијанта Робидоук методе, оптимизована за оштру промену величине слике Метода интерполације заснована на сплине-у која даје глатке резултате користећи филтер са 16 тап Метода интерполације заснована на сплине-у која даје глатке резултате коришћењем филтера од 36 додира Метода интерполације заснована на сплине-у која даје глатке резултате користећи филтер са 64 додира Метода интерполације која користи Кајзеров прозор, пружајући добру контролу над компромисом између ширине главног режња и нивоа бочног режња Хибридна функција прозора која комбинује прозоре Бартлетт и Ханн, која се користи за смањење спектралног цурења у обради сигнала Једноставан метод поновног узорковања који користи просек вредности најближих пиксела, што често доводи до блокираног изгледа Функција прозора која се користи за смањење спектралног цурења, пружајући добру резолуцију фреквенције у апликацијама за обраду сигнала Метода поновног узорковања која користи Ланчос филтер са 2 режња за висококвалитетну интерполацију са минималним артефактима Метода поновног узорковања која користи Ланцзос филтер са 3 режња за висококвалитетну интерполацију са минималним артефактима Метода поновног узорковања која користи Ланчос филтер са 4 режња за висококвалитетну интерполацију са минималним артефактима Варијанта Ланцзос 2 филтера која користи јинц функцију, пружајући висококвалитетну интерполацију са минималним артефактима Варијанта Ланцзос 3 филтера која користи јинц функцију, пружајући висококвалитетне интерполације са минималним артефактима Варијанта Ланцзос 4 филтера која користи јинц функцију, пружајући висококвалитетну интерполацију са минималним артефактима Ханнинг ЕВА Елиптична пондерисана просек (ЕВА) варијанта Ханинговог филтера за глатку интерполацију и поновно узорковање Робидоук ЕВА Еллиптицал Веигхтед Авераге (ЕВА) варијанта Робидоук филтера за висококвалитетно поновно узорковање Блацкман ЕВЕ Еллиптицал Веигхтед Авераге (ЕВА) варијанта Блацкман филтера за минимизирање артефаката звоњења Куадриц ЕВА Елиптични пондерисани просек (ЕВА) варијанта Куадриц филтера за глатку интерполацију Робидоук Схарп ЕВА Еллиптицал Веигхтед Авераге (ЕВА) варијанта Робидоук Схарп филтера за оштрије резултате Ланцзос 3 Јинц ЕВА Еллиптицал Веигхтед Авераге (ЕВА) варијанта Ланцзос 3 Јинц филтера за висококвалитетно поновно узорковање са смањеним алиасингом Гинсенг Филтер за поновно узорковање дизајниран за висококвалитетну обраду слике са добрим балансом оштрине и глаткоће Гинсенг ЕВА Елиптична пондерисана просек (ЕВА) варијанта Гинсенг филтера за побољшани квалитет слике Ланцзос Схарп ЕВА Еллиптицал Веигхтед Авераге (ЕВА) варијанта Ланцзос Схарп филтера за постизање оштрих резултата уз минималне артефакте Ланцзос 4 Схарпест ЕВА Еллиптицал Веигхтед Авераге (ЕВА) варијанта филтера Ланцзос 4 Схарпест за изузетно оштро поновно узорковање слике Ланцзос Софт ЕВА Еллиптицал Веигхтед Авераге (ЕВА) варијанта Ланцзос Софт филтера за глатко поновно узорковање слике Хаасн Софт Филтер за поновно узорковање који је дизајнирао Хаасн за глатко скалирање слике без артефаката Формат Цонверсион Претворите серију слика из једног формата у други Дисмисс Форевер Имаге Стацкинг Сложите слике једну на другу са изабраним режимима мешања Додај слику Бинс цоунт Цлахе ХСЛ Цлахе ХСВ Екуализе Хистограм Адаптиве ХСЛ Екуализе Хистограм Адаптиве ХСВ Едге Моде Цлип Замотајте Далтонизам Изаберите режим да бисте прилагодили боје теме за изабрану варијанту слепила за боје Потешкоће у разликовању црвене и зелене нијансе Потешкоће у разликовању зелених и црвених нијанси Потешкоће у разликовању плавих и жутих нијанси Немогућност опажања црвених нијанси Немогућност опажања зелених нијанси Немогућност опажања плавих нијанси Смањена осетљивост на све боје Потпуно слепило за боје, види само нијансе сиве Не користите шему далтониста Боје ће бити тачно онако како су постављене у теми Сигмоидални Лагранж 2 Лагранжов интерполациони филтер реда 2, погодан за скалирање слике високог квалитета са глатким прелазима Лагранге 3 Лагранжов интерполациони филтер реда 3, који нуди бољу тачност и глаткије резултате за скалирање слике Ланчош 6 Ланчосов филтер за поновно узорковање са вишим редом од 6, пружајући оштрије и прецизније скалирање слике Ланцзос 6 Јинц Варијанта Ланцзос 6 филтера који користи функцију Јинц за побољшани квалитет поновног узорковања слике Линеар Бок Блур Линеар Тент Блур Линеар Гауссиан Бок Блур Линеар Стацк Блур Гауссиан Бок Блур Линеар Фаст Гауссиан Блур Нект Линеарно брзо Гаусово замућење Линеарно Гаусово замућење Изаберите један филтер да бисте га користили као боју Замените филтер Изаберите филтер испод да бисте га користили као четкицу на свом цртежу Шема компресије ТИФФ-а Лов Поли Санд Паинтинг Раздвајање слике Поделите једну слику по редовима или колонама Фит То Боундс Комбинујте режим промене величине исецања са овим параметром да бисте постигли жељено понашање (Исецање/Уклапање у однос ширине и висине) Језици су успешно увезени Резервна копија ОЦР модела Увоз Извоз Положај Центар Горе лево Горе десно Доле лево Доле десно Топ Центер Центар десно Боттом Центер Центар лево Циљна слика Палетте Трансфер Енханцед Оил Једноставан стари ТВ ХДР Готхам Симпле Скетцх Софт Глов Постер у боји Три Тоне Трећа боја Цлахе Оклаб Цлара Олцх Цлахе Јзазбз Полка Дот Цлустеред 2к2 Дитхеринг Груписани 4к4 Дитхеринг Груписано 8к8 Дитхеринг Иилилома Дитхеринг Није изабрана ниједна омиљена опција, додајте их на страницу са алаткама Додај фаворите Комплементарно Аналогно Триадиц Сплит Цомплементари Тетрадиц Скуаре Аналогно + комплементарно Цолор Тоолс Мешајте, правите тонове, стварајте нијансе и још много тога Цолор Хармониес Сјенчање боја Варијација Нијансе Тонови Схадес Мешање боја Информације о боји Изабрана боја Боја за мешање Не може да се користи монет док су динамичке боје укључене 512к512 2Д ЛУТ Циљна ЛУТ слика Аматер Мисс Етикуетте Софт Елеганце Варијанта меке елеганције Варијанта преноса палете 3Д ЛУТ Циљна 3Д ЛУТ датотека (.цубе / .ЦУБЕ) ЛУТ Блеацх Бипасс Свећа Дроп Блуес Едги Амбер Фалл Цолорс Филмска залиха 50 Магловита ноћ Кодак Добијте неутралну ЛУТ слику Прво користите своју омиљену апликацију за уређивање фотографија да примените филтер на неутрални ЛУТ који можете добити овде. Да би ово исправно функционисало, свака боја пиксела не сме да зависи од других пиксела (нпр. замућење неће функционисати). Када будете спремни, користите своју нову ЛУТ слику као улаз за 512*512 ЛУТ филтер Поп Арт Целулоид кафу Златна шума Зеленкасто Ретро Иеллов Преглед линкова Омогућава преузимање прегледа линка на местима где можете да добијете текст (КРЦ код, ОЦР итд.) Линкови ИЦО датотеке се могу сачувати само у максималној величини од 256 к 256 ГИФ на ВЕБП Претворите ГИФ слике у ВЕБП анимиране слике ВЕБП Тоолс Претворите слике у ВЕБП анимирану слику или издвојите оквире из дате ВЕБП анимације ВЕБП на слике Претворите ВЕБП датотеку у групу слика Претворите серију слика у ВЕБП датотеку Слике на ВЕБП Изаберите ВЕБП слику за почетак Нема пуног приступа датотекама Дозволите приступ свим датотекама да бисте видели ЈКСЛ, КОИ и друге слике које нису препознате као слике на Андроид-у. Без дозволе Имаге Тоолбок не може да прикаже те слике Подразумевана боја цртања Подразумевани режим цртања путање Додај временску ознаку Омогућава додавање временске ознаке имену излазне датотеке Форматирана временска ознака Омогућите форматирање временске ознаке у називу излазне датотеке уместо основних милиса Омогућите временске ознаке да изаберете њихов формат Локација за једнократну уштеду Прегледајте и уредите једнократне локације за чување које можете користити дугим притиском на дугме за чување углавном у свим опцијама Недавно коришћено ЦИ канал Група Кутија са алаткама за слике у Телеграму 🎉 Придружите се нашем ћаскању где можете да разговарате о свему што желите и такође погледајте ЦИ канал где објављујем бета верзије и најаве Добијајте обавештења о новим верзијама апликације и читајте најаве Прилагодите слику датим димензијама и примените замућење или боју на позадину Аранжман алата Групирајте алате по типу Групише алате на главном екрану према њиховом типу уместо према прилагођеном распореду листе Подразумеване вредности Видљивост системских трака Прикажи системске траке превлачењем Омогућава превлачење за приказ системских трака ако су скривене Ауто Сакриј све Прикажи све Сакриј траку за навигацију Сакриј статусну траку Генерисање буке Генеришите различите звукове попут Перлина или других врста Фреквенција Тип буке Ротатион Типе Фрактални тип Октаве Лакунарност Добитак Веигхтед Стренгтх Снага за пинг понг Функција удаљености Ретурн Типе Јиттер Домаин Варп Поравнање Прилагођено име датотеке Изаберите локацију и назив датотеке који ће се користити за чување тренутне слике Сачувано у фасциклу са прилагођеним именом Цоллаге Макер Направите колаже од до 20 слика Цоллаге Типе Држите слику за замену, померање и зумирање да бисте подесили положај Онемогући ротацију Спречава ротирање слика покретима са два прста Омогућите спајање на ивице Након померања или зумирања, слике ће шкљоцнути да попуне ивице оквира Хистограм РГБ или Бригхтнесс хистограм слике који ће вам помоћи да извршите подешавања Ова слика ће се користити за генерисање РГБ и Бригхтнесс хистограма Тессерацт Оптионс Примените неке улазне варијабле за тесеракт мотор Прилагођене опције Опције треба унети према овом шаблону: \"--{оптион_наме} {валуе}\" Ауто Цроп Фрее Цорнерс Изрежите слику по полигон, ово такође исправља перспективу Присиљавање указује на границе слике Тачке неће бити ограничене границама слике, што је корисно за прецизније исправљање перспективе Маска Испуна под уцртаном путањом свесна садржаја Хеал Спот Користите Цирцле Кернел Отварање Затварање Морфолошки градијент Топ Хат Црни шешир Тоне Цурвес Ресетујте криве Криве ће бити враћене на подразумевану вредност Стил линије Гап Сизе Дасхед Дот Дасхед Стампед цик-цак Црта испрекидану линију дуж нацртане путање са наведеном величином размака Црта тачку и испрекидану линију дуж дате путање Само подразумеване равне линије Црта изабране облике дуж путање са одређеним размаком Црта таласасте цик-цак дуж стазе Цик-цак однос Креирајте пречицу Изаберите алатку за закачење Алат ће бити додат на почетни екран вашег покретача као пречица, користите га у комбинацији са поставком „Прескочи бирање датотека“ да бисте постигли потребно понашање Немојте слагати оквире Омогућава одлагање претходних оквира, тако да се неће слагати један на други Цроссфаде Оквири ће бити укрштени један у други Број фрејмова са унакрсним бледењем Праг један Праг два Цанни Огледало 101 Побољшано замућење зума Лапласов Симпле Собел Симпле Помоћна мрежа Приказује помоћну мрежу изнад области за цртање да би се помогло прецизним манипулацијама Боја мреже Целл Видтх Висина ћелије Компактни селектори Неке контроле избора ће користити компактан распоред да заузму мање простора Дајте камери дозволу у подешавањима за снимање слике Лаиоут Наслов главног екрана Фактор константне стопе (ЦРФ) Вредност %1$s значи спору компресију, што резултира релативно малом величином датотеке. %2$s значи бржу компресију, што резултира великом датотеком. Лут Либрари Преузмите колекцију ЛУТ-ова, коју можете применити након преузимања Ажурирајте колекцију ЛУТ-ова (само нови ће бити стављени у ред чекања), коју можете применити након преузимања Промените подразумевани преглед слике за филтере Преглед слике Сакриј се Схов Тип клизача Фанци Материјал 2 Фантастичан клизач. Ово је подразумевана опција А Материал 2 клизач Клизач за материјал Пријавите се Централна дугмад за дијалог Дугмад дијалога ће бити позиционирана у средини уместо на левој страни ако је могуће Лиценце отвореног кода Погледајте лиценце библиотека отвореног кода које се користе у овој апликацији Подручје Поновно узорковање коришћењем односа површине пиксела. То може бити пожељан метод за децимацију слике, јер даје резултате без муара. Али када је слика зумирана, она је слична методи „Најближи“. Омогући мапирање тонова Унесите % Не можете да приступите сајту, покушајте да користите ВПН или проверите да ли је урл тачан Маркуп Лаиерс Режим слојева са могућношћу слободног постављања слика, текста и још много тога Уреди слој Слојеви на слици Користите слику као позадину и додајте различите слојеве на њу Слојеви на позадини Исто као и прва опција, али са бојом уместо слике Бета Фаст Сеттингс Сиде Додајте плутајућу траку на изабрану страну док уређујете слике, која ће отворити брза подешавања када се кликне Обриши избор Група подешавања \"%1$s\" ће подразумевано бити скупљена Група подешавања \"%1$s\" ће подразумевано бити проширена Басе64 Тоолс Декодирајте Басе64 стринг у слику или кодирајте слику у Басе64 формат Басе64 Наведена вредност није важећи Басе64 стринг Није могуће копирати празан или неважећи низ Басе64 Пасте Басе64 Цопи Басе64 Учитајте слику да бисте копирали или сачували Басе64 стринг. Ако имате сам низ, можете га налепити изнад да бисте добили слику Саве Басе64 Схаре Басе64 Опције Акције Импорт Басе64 Басе64 Ацтионс Адд Оутлине Додајте обрис око текста са наведеном бојом и ширином Боја контуре Оутлине Сизе Ротација Контролна сума као име датотеке Излазне слике ће имати назив који одговара њиховој контролној суми података Бесплатни софтвер (Партнер) Још кориснији софтвер на партнерском каналу Андроид апликација Алгоритам Алати за проверу Упоредите контролне суме, израчунајте хешове или креирајте хексадецималне низове из датотека користећи различите алгоритме хеширања Израчунај Тект Хасх Контролни збир Изаберите датотеку да бисте израчунали њен контролни збир на основу изабраног алгоритма Унесите текст да бисте израчунали његову контролну суму на основу изабраног алгоритма Изворна контролна сума Контролни збир за поређење Матцх! Разлика Контролне суме су једнаке, може бити безбедно Контролне суме нису једнаке, датотека може бити несигурна! Месх Градиентс Погледајте онлајн колекцију Месх Градијената Могу се увести само ТТФ и ОТФ фонтови Увезите фонт (ТТФ/ОТФ) Извези фонтове Увезени фонтови Грешка при покушају чувања, покушајте да промените излазну фасциклу Име датотеке није подешено Ниједан Прилагођене странице Избор страница Потврда излаза из алатке Ако имате несачуване промене док користите одређене алате и покушате да их затворите, тада ће се приказати дијалог за потврду Уреди ЕКСИФ Промените метаподатке једне слике без поновне компресије Додирните да бисте изменили доступне ознаке Цханге Стицкер Фит Видтх Фит Хеигхт Батцх Цомпаре Изаберите датотеку/датотеке да бисте израчунали њен контролни збир на основу изабраног алгоритма Изаберите датотеке Изаберите именик Скала дужине главе Печат Временска ознака Формат Паттерн Паддинг Резање слике Изрежите део слике и спојите леве (може бити инверзне) вертикалним или хоризонталним линијама Вертикална обртна линија Хоризонтална обртна линија Инверзна селекција Вертикални исечени део ће бити остављен, уместо спајања делова око области реза Хоризонтални исечени део ће бити остављен, уместо спајања делова око области реза Збирка мрежастих градијента Направите градијент мреже са прилагођеном количином чворова и резолуцијом Месх Градиент Оверлаи Направи мрежасти градијент врха датих слика Поинтс Цустомизатион Величина мреже Резолуција Кс Резолуција И Резолуција Пикел Би Пикел Хигхлигхт Цолор Тип поређења пиксела Скенирајте бар код Однос висине Тип баркода Спровести црно-бело Слика бар кода ће бити потпуно црно-бела и неће бити обојена темом апликације Скенирајте било који бар код (КР, ЕАН, АЗТЕЦ,…) и преузмите његов садржај или налепите свој текст да бисте генерисали нови Баркод није пронађен Генерисани бар код ће бити овде Аудио омоти Извуците слике омота албума из аудио датотека, подржани су најчешћи формати Изаберите аудио за почетак Изаберите Аудио Но Цоверс Фоунд Пошаљи дневнике Кликните да бисте поделили датотеку евиденције апликације, ово ми може помоћи да уочим проблем и решим проблеме Упс… Нешто је пошло наопако Можете ме контактирати користећи опције испод и покушаћу да пронађем решење.\н(Не заборавите да приложите евиденције) Врите То Филе Извуците текст из серије слика и сачувајте га у једној текстуалној датотеци Врите То Метадата Извуците текст из сваке слике и ставите га у ЕКСИФ информације релативних фотографија Невидљиви режим Користите стеганографију да направите оку невидљиве водене жигове унутар бајтова ваших слика Користите ЛСБ Метода стеганографије ЛСБ (мање значајног бита) ће се користити, у супротном ФД (фреквенцијски домен) Аутоматско уклањање црвених очију Лозинка Откључај ПДФ је заштићен Операција је скоро завршена. Отказивање сада захтева поновно покретање Датум измене Датум измене (обрнуто) Величина Величина (обрнуто) МИМЕ Типе МИМЕ тип (обрнуто) Продужетак Продужетак (обрнуто) Датум додавања Датум додавања (обрнуто) С лева на десно Десно налево Од врха до дна Одоздо ка врху Течно стакло Прекидач заснован на недавно најављеном ИОС 26 и његовом систему дизајна течног стакла Изаберите слику или налепите/увезите Басе64 податке испод Унесите везу за слику да бисте започели Налепите везу Калеидосцопе Секундарни угао Сидес Цханнел Мик Плаво зелено Црвено плаво Зелено црвено У црвено У зелено У плаво Циан Магента Жута Цолоур Халфтоне Цонтоур Нивои Оффсет Воронои Цристаллизе Облик Стретцх Случајност Деспецкле Дифузно ДоГ Други радијус Изједначити Глов Вртлог и штипање Поинтилизе Боја ивице Поларне координате Рецт то полар Поларни до правоугаони Обрни у круг Редуце Ноисе Симпле Соларизе Веаве Кс Гап И Гап Кс ширина И Видтх Твирл Руббер Стамп Смеар Густина Мик Дисторзија сферног сочива Индекс преламања Арц Угао ширења Спаркле Раис АСЦИИ Градијент Мари јесен Боне Јет Зима Оцеан Лето пролеће Цоол Вариант ХСВ Пинк Хот Реч Магма Инферно Плазма Виридис Грађани Твилигхт Твилигхт Схифтед Перспецтиве Ауто Дескев Дозволи обрезивање Обрезивање или перспектива Апсолутно Турбо Дееп Греен Ленс Цоррецтион Датотека профила циљног сочива у ЈСОН формату Преузмите спремне профиле сочива Део процената Извези као ЈСОН Копирајте стринг са подацима о палети као јсон приказ Сеам Царвинг Почетни екран Закључавање екрана Уграђени Извоз позадина Освежи Набавите тренутне позадине за дом, закључавање и уграђене позадине Дозволите приступ свим датотекама, ово је потребно за преузимање позадина Дозвола за управљање спољним складиштем није довољна, потребно је да дозволите приступ својим сликама, обавезно изаберите „Дозволи све“ Додај унапред подешено име датотеке Додаје суфикс са изабраним унапред подешеним именом датотеке слике Додајте режим размере слике имену датотеке Додаје суфикс са изабраним режимом размера слике имену датотеке слике Асции Арт Претворите слику у асции текст који ће изгледати као слика Парамс Примењује негативни филтер на слику за бољи резултат у неким случајевима Обрада снимка екрана Снимак екрана није снимљен, покушајте поново Чување је прескочено %1$s датотека је прескочено Дозволи прескакање ако је веће Неким алатима ће бити дозвољено да прескоче чување слика ако би резултујућа величина датотеке била већа од оригиналне Календарски догађај Контакт Емаил Локација Телефон Текст СМС УРЛ Ви-Фи Отворена мрежа Н/А ССИД Телефон Порука Адреса Предмет Тело Име Организација Наслов Телефони Емаилс УРЛ адресе Аддрессес Резиме Опис Локација Организатор Датум почетка Датум завршетка Статус Латитуде Географска дужина Креирајте бар код Уреди бар код Ви-Фи конфигурација Безбедност Изаберите контакт Дајте контактима дозволу у подешавањима за аутоматско попуњавање помоћу изабраног контакта Контакт информације Име Средње име Презиме Пронунциатион Додај телефон Додајте е-пошту Додајте адресу Вебсите Додајте веб локацију Форматирано име Ова слика ће се користити за постављање изнад бар кода Прилагођавање кода Ова слика ће се користити као лого у центру КР кода Лого Лого паддинг Величина логотипа Лого углови Четврто око Додаје симетрију ока кр коду додавањем четвртог ока у доњем крајњем углу Облик пиксела Облик оквира Облик лопте Ниво исправке грешке Тамна боја Светла боја Хипер ОС Стил попут Ксиаоми ХиперОС-а Узорак маске Овај код се можда не може скенирати, промените параметре изгледа да бисте га учинили читљивим на свим уређајима Није могуће скенирати Алати ће изгледати као покретач апликација на почетном екрану да би били компактнији Лаунцхер Моде Испуњава област одабраном четком и стилом Флоод Филл Спреј Црта путању у стилу графита Скуаре Партицлес Честице спреја ће бити квадратног облика уместо кругова Палетте Тоолс Генеришите основни/материјал који палете са слике или увезите/извезите у различите формате палета Уреди палету Извоз/увоз палете у различитим форматима Назив боје Име палете Палетте Формат Извезите генерисану палету у различите формате Додаје нову боју тренутној палети %1$s формат не подржава давање назива палете Због смерница Плаи продавнице, ова функција не може бити укључена у актуелну верзију. Да бисте приступили овој функцији, преузмите ИмагеТоолбок са алтернативног извора. Доступне верзије на ГитХуб-у можете пронаћи испод. Отворите Гитхуб страницу Оригинална датотека ће бити замењена новом уместо чувања у изабраном фолдеру Откривен је скривени текст воденог жига Откривена је скривена слика воденог жига Ова слика је била скривена Генеративе Инпаинтинг Омогућава вам да уклоните објекте са слике користећи АИ модел, без ослањања на ОпенЦВ. Да би користила ову функцију, апликација ће преузети потребни модел (~200 МБ) са ГитХуб-а Омогућава вам да уклоните објекте са слике користећи АИ модел, без ослањања на ОпенЦВ. Ово би могла бити дуготрајна операција Анализа нивоа грешке Градијент осветљености Просечна удаљеност Цопи Мове Детецтион Задржи Коефицијент Подаци међумеморије су превелики Подаци су превелики за копирање Једноставна пикселизација ткања Постепена пикселизација Цросс Пикелизатион Микро Макро Пикселизација Орбитална пикселизација Вортек Пикелизатион Пикселизација пулсне мреже Нуцлеус Пикелизатион Пикселизација радијалног ткања Није могуће отворити ури \"%1$s\" Снежни режим Омогућено Бордер Фраме Глитцх Вариант Цханнел Схифт Мак Оффсет ВХС Блоцк Глитцх Величина блока ЦРТ закривљеност Закривљеност Цхрома Пикел Мелт Мак Дроп АИ Тоолс Различити алати за обраду слика кроз АИ моделе као што су уклањање артефаката или уклањање шума Компресија, назубљене линије Цртани филмови, компресија емитовања Општа компресија, општа бука Безбојна бука из цртаних филмова Брза, општа компресија, општа бука, анимација/стрипови/аниме Скенирање књига Корекција експозиције Најбољи у општој компресији, сликама у боји Најбољи у општој компресији, слике у сивим тоновима Општа компресија, слике у нијансама сиве, јаче Општи шум, слике у боји Општи шум, слике у боји, бољи детаљи Општи шум, слике у сивим тоновима Општи шум, слике у нијансама сиве, јачи Општи шум, слике у нијансама сиве, најјаче Општа компресија Општа компресија Текстуризација, компресија х264 ВХС компресија Нестандардна компресија (цинепак, мсвидео1, рок) Бинк компресија, боља у геометрији Бинк компресија, јача Бинк компресија, мекана, задржава детаље Уклањање ефекта степеница, заглађивање Скенирана уметност/цртежи, блага компресија, моар Боја трака Споро, уклањајући полутонове Општи колоризер за слике у нијансама сиве/црне боје, за боље резултате користите ДДЦолор Уклањање ивица Уклања преоштрење Споро, немирно Анти-алиасинг, општи артефакти, ЦГИ КДМ003 обрада скенирања Лагани модел за побољшање слике Уклањање артефакта компресије Уклањање артефакта компресије Уклањање завоја са глатким резултатима Обрада полутонског узорка Уклањање дитер шаблона В3 Уклањање ЈПЕГ артефаката В2 Х.264 побољшање текстуре ВХС изоштравање и побољшање Спајање Величина комада Величина преклапања Слике веће од %1$s пк ће бити исечене и обрађене у деловима, преклапање их спаја да би се спречиле видљиве шавове. Велике величине могу узроковати нестабилност код уређаја ниске класе Изаберите једну за почетак Да ли желите да избришете %1$s модел? Мораћете поново да га преузмете Потврди Модели Преузети модели Доступни модели Припрема Активни модел Отварање сесије није успело Могу се увозити само .оннк/.орт модели Увозни модел Увезите прилагођени оннк модел за даљу употребу, прихватају се само оннк/орт модели, подржава скоро све есрган варијанте Увезени модели Општи шум, слике у боји Општи шум, слике у боји, јачи Општи шум, слике у боји, најјаче Смањује артефакте дитхеринга и траке боја, побољшавајући глатке нагибе и равне области боја. Повећава осветљеност и контраст слике уравнотеженим светлима уз очување природних боја. Осветљава тамне слике, задржавајући детаље и избегавајући прекомерну експозицију. Уклања прекомерно тонирање боја и враћа неутралнији и природнији баланс боја. Примењује тонирање буке засновано на Поиссону са нагласком на очувању финих детаља и текстура. Примењује меку Поиссонову буку за глађе и мање агресивне визуелне резултате. Уједначено тонирање шума фокусирано на очување детаља и јасноћу слике. Нежно равномерно тонирање буке за суптилну текстуру и гладак изглед. Поправља оштећена или неравна подручја префарбавањем артефаката и побољшањем конзистентности слике. Лагани модел за уклањање трака који уклања траке у боји уз минималне трошкове перформанси. Оптимизује слике са веома високим артефактима компресије (0-20% квалитета) за побољшану јасноћу. Побољшава слике са високим артефактима компресије (20-40% квалитета), враћајући детаље и смањујући шум. Побољшава слике умереном компресијом (40-60% квалитета), балансирајући оштрину и глаткоћу. Рафинира слике лаганом компресијом (60-80% квалитета) како би побољшао суптилне детаље и текстуре. Мало побољшава слике скоро без губитака (80-100% квалитета) уз очување природног изгледа и детаља. Једноставна и брза колоризација, цртани, није идеално Благо смањује замућење слике, побољшавајући оштрину без уношења артефаката. Дуготрајне операције Обрада слике Обрада Уклања тешке артефакте ЈПЕГ компресије на сликама веома лошег квалитета (0-20%). Смањује јаке ЈПЕГ артефакте у високо компресованим сликама (20-40%). Чисти умерене ЈПЕГ артефакте уз очување детаља слике (40-60%). Рафинира лаке ЈПЕГ артефакте у сликама прилично високог квалитета (60-80%). Суптилно смањује мање ЈПЕГ артефакте у сликама скоро без губитака (80-100%). Побољшава фине детаље и текстуре, побољшавајући перципирану оштрину без тешких артефаката. Обрада је завршена Обрада није успела Побољшава текстуру и детаље коже, задржавајући природан изглед, оптимизован за брзину. Уклања артефакте ЈПЕГ компресије и враћа квалитет слике за компримоване фотографије. Смањује ИСО шум на фотографијама снимљеним у условима слабог осветљења, чувајући детаље. Коригује преекспониране или „џамбо“ светле делове и враћа бољу равнотежу тонова. Лаган и брз модел колоризације који додаје природне боје сликама у сивим тоновима. ДЕЈПЕГ Деноисе Цолоризе Артефакти Енханце Аниме Скенирања Упсцале Кс4 упсцалер за опште слике; мали модел који користи мање ГПУ-а и времена, са умереним замагљивањем и шумом. Кс2 упсцалер за опште слике, очување текстура и природних детаља. Кс4 упсцалер за опште слике са побољшаним текстурама и реалистичним резултатима. Кс4 упсцалер оптимизован за аниме слике; 6 РРДБ блокова за оштрије линије и детаље. Кс4 упсцалер са губитком МСЕ, даје глаткије резултате и смањене артефакте за опште слике. Кс4 Упсцалер оптимизован за аниме слике; 4Б32Ф варијанта са оштријим детаљима и глатким линијама. Кс4 УлтраСхарп В2 модел за опште слике; наглашава оштрину и јасноћу. Кс4 УлтраСхарп В2 Лите; бржи и мањи, чува детаље док користи мање ГПУ меморије. Лагани модел за брзо уклањање позадине. Уравнотежене перформансе и тачност. Ради са портретима, објектима и сценама. Препоручује се за већину случајева употребе. Уклони БГ Хоризонтална дебљина ивице Вертикална дебљина ивице %1$s боја %1$s боја %1$s боја Тренутни модел не подржава ломљење, слика ће бити обрађена у оригиналним димензијама, што може узроковати велику потрошњу меморије и проблеме са уређајима ниске класе Резање на комаде је онемогућено, слика ће бити обрађена у оригиналним димензијама, ово може узроковати велику потрошњу меморије и проблеме са нижим уређајима, али може дати боље резултате при закључивању Цхункинг Модел сегментације слике високе прецизности за уклањање позадине Лагана верзија У2Нета за брже уклањање позадине уз мању употребу меморије. Пун модел ДДЦолор пружа висококвалитетну колоризацију за опште слике са минималним артефактима. Најбољи избор од свих модела колоризације. ДДЦолор Обучени и приватни уметнички скупови података; производи разноврсне и уметничке резултате колоризације са мање нереалних артефаката у боји. Лагани БиРефНет модел заснован на Свин Трансформеру за прецизно уклањање позадине. Висококвалитетно уклањање позадине са оштрим ивицама и одличним очувањем детаља, посебно на сложеним објектима и лукавим позадинама. Модел уклањања позадине који производи прецизне маске са глатким ивицама, погодне за опште објекте и умерено очување детаља. Модел је већ преузет Модел је успешно увезен Тип Кључна реч Врло брзо Нормално Споро Веома Споро Израчунај проценте Минимална вредност је __ПХ_0__ Искривите слику цртањем прстима Варп Тврдоћа Варп Моде Помери се Расте Смањи се Свирл ЦВ Свирл ЦЦВ Фаде Стренгтх Топ Дроп Боттом Дроп Старт Дроп Енд Дроп Преузимање Смоотх Схапес Користите суперелипсе уместо стандардних заобљених правоугаоника за глаткије, природније облике Облик облика Цут Заобљен Смоотх Оштре ивице без заобљења Класични заобљени углови Схапес Типе Цорнерс Сизе Скуирцле Елегантни заобљени елементи корисничког интерфејса Формат имена датотеке Прилагођени текст постављен на самом почетку назива датотеке, савршен за називе пројеката, брендове или личне ознаке. Користи оригинално име датотеке без екстензије, помажући вам да идентификацију извора сачувате нетакнутом. Ширина слике у пикселима, корисна за праћење промена резолуције или скалирања резултата. Висина слике у пикселима, корисна када радите са пропорцијама или извозима. Генерише насумичне цифре да гарантује јединствена имена датотека; додајте још цифара за додатну сигурност од дупликата. Бројач са аутоматским повећањем за групни извоз, идеалан за чување више слика у једној сесији. Умеће примењено унапред подешено име у назив датотеке тако да можете лако да запамтите како је слика обрађена. Приказује режим скалирања слике који се користи током обраде, помажући у разликовању слика промењене величине, исечених или уклопљених слика. Прилагођени текст постављен на крај назива датотеке, користан за верзионисање као што је _в2, _едитед или _финал. Екстензија датотеке (пнг, јпг, вебп, итд.), аутоматски се подудара са стварним сачуваним форматом. Прилагодљива временска ознака која вам омогућава да дефинишете сопствени формат помоћу Јава спецификације за савршено сортирање. Флинг Типе Андроид изворни иОС стил Смоотх Цурве Куицк Стоп Боунци Флоати Снаппи Ултра Смоотх Адаптиве Аццессибилити Аваре Редуцед Мотион Природна физика Андроид скроловања за основно поређење Уравнотежено, глатко померање за општу употребу Понашање померања попут иОС-а са већим трењем Јединствена сплине крива за јасан осећај померања Прецизно померање са брзим заустављањем Разиграни, покретни померајући се померај Дуги, клизећи скроловање за прегледање садржаја Брзо и брзо померање за интерактивни кориснички интерфејс Врхунско глатко померање са продуженим замахом Подешава физику на основу брзине бацања Поштује подешавања приступачности система Минимално кретање за потребе приступачности Примари Линес Додаје дебљу линију сваке пете линије Филл Цолор Скривени алати Алати скривени за дељење Библиотека боја Прегледајте огромну колекцију боја Изоштрава и уклања замућење са слика уз задржавање природних детаља, идеално за поправљање фотографија које нису у фокусу. Интелигентно враћа слике којима је претходно промењена величина, враћајући изгубљене детаље и текстуре. Оптимизовано за садржај уживо, смањује артефакте компресије и побољшава фине детаље у кадровима филмова/ТВ емисија. Конвертује снимке ВХС квалитета у ХД, уклањајући шум траке и побољшавајући резолуцију уз очување винтаге осећаја. Специјализован за слике и снимке екрана са тешким текстом, изоштрава знакове и побољшава читљивост. Напредно повећање величине обучено на различитим скуповима података, одлично за побољшање фотографија опште намене. Оптимизован за веб компресоване фотографије, уклања ЈПЕГ артефакте и враћа природан изглед. Побољшана верзија за веб фотографије са бољим очувањем текстуре и смањењем артефаката. 2к повећање помоћу технологије Дуал Аггрегатион Трансформер, одржава оштрину и природне детаље. 3к повећање помоћу напредне трансформаторске архитектуре, идеално за умерене потребе повећања. 4к висококвалитетно повећање са најсавременијом мрежом трансформатора, чува фине детаље у већим размерама. Уклања замућење/шум и подрхтавање фотографија. Опште намене, али најбоље на фотографијама. Враћа слике ниског квалитета помоћу Свин2СР трансформатора, оптимизованог за БСРГАН деградацију. Одлично за поправљање артефаката тешке компресије и побољшање детаља на скали од 4к. 4к повећање са СвинИР трансформатором обученим за БСРГАН деградацију. Користи ГАН за оштрије текстуре и природније детаље на фотографијама и сложеним сценама. Пут Споји ПДФ Комбинујте више ПДФ датотека у један документ Филес Ордер стр. Сплит ПДФ Извуците одређене странице из ПДФ документа Ротирајте ПДФ Трајно поправи оријентацију странице Пагес Преуредите ПДФ Превуците и отпустите странице да бисте их променили Задржите и превуците странице Бројеви страница Аутоматски додајте нумерацију у своје документе Лабел Формат ПДФ у текст (ОЦР) Извуците обичан текст из ваших ПДФ докумената Преклапање прилагођеног текста за брендирање или безбедност Потпис Додајте свој електронски потпис на било који документ Ово ће се користити као потпис Откључај ПДФ Уклоните лозинке из заштићених датотека Заштитите ПДФ Осигурајте своје документе снажном енкрипцијом Успех ПДФ је откључан, можете га сачувати или поделити Поправи ПДФ Покушајте да поправите оштећене или нечитљиве документе Граисцале Претворите све слике уграђене у документе у сивим тоновима Цомпресс ПДФ Оптимизујте величину датотеке документа за лакше дељење ИмагеТоолбок поново гради интерну табелу унакрсних референци и регенерише структуру датотеке од нуле. Ово може да врати приступ многим датотекама које се „не могу отворити“ Овај алат претвара све слике документа у сиве тонове. Најбоље за штампање и смањење величине датотеке Метаподаци Уредите својства документа ради боље приватности Ознаке Произвођач Аутор Кључне речи Креатор Приватност Дееп Цлеан Обришите све доступне метаподатке за овај документ Страница Дубоки ОЦР Извуците текст из документа и сачувајте га у једној текстуалној датотеци користећи Тессерацт енгине Није могуће уклонити све странице Уклоните ПДФ странице Уклоните одређене странице из ПДФ документа Додирните За уклањање Ручно Изрежите ПДФ Опсеците странице документа на било коју границу Изравнајте ПДФ Учините ПДФ непромењивим растером страница документа Није могуће покренути камеру. Проверите дозволе и уверите се да је не користи друга апликација. Ектрацт Имагес Извуците слике уграђене у ПДФ-ове у њиховој оригиналној резолуцији Ова ПДФ датотека не садржи уграђене слике Овај алат скенира сваку страницу и враћа изворне слике пуног квалитета — савршено за чување оригинала из докумената Драв Сигнатуре Пен Парамс Користите сопствени потпис као слику која се поставља на документе Зип ПДФ Поделите документ са датим интервалом и спакујте нове документе у зип архиву Интервал Штампај ПДФ Припремите документ за штампање са прилагођеном величином странице Странице по листу Оријентација Величина странице Маргина Блоом Софт Кнее Оптимизовано за аниме и цртане филмове. Брзо повећање са побољшаним природним бојама и мање артефаката Стил попут Самсунг Оне УИ 7 Унесите основне математичке симболе овде да бисте израчунали жељену вредност (нпр. (5+5)*10) Математички израз Покупите до __ПХ_0__ слика Задржите датум и време Увек сачувајте екиф ознаке повезане са датумом и временом, ради независно од опције Кееп екиф Боја позадине за алфа формате Додаје могућност постављања боје позадине за сваки формат слике са алфа подршком, када је онемогућена ово је доступно само за оне које нису алфа Отворени пројекат Наставите са уређивањем претходно сачуваног пројекта Имаге Тоолбок Није могуће отворити пројекат Имаге Тоолбок Пројекту Имаге Тоолбок недостају подаци о пројекту Пројекат Имаге Тоолбок је оштећен Неподржана верзија пројекта Имаге Тоолбок: __ПХ_0__ Сачувај пројекат Чувајте слојеве, позадину и историју уређивања у датотеци пројекта који се може уређивати Отварање није успело Врите То Сеарцхабле ПДФ Препознајте текст из групе слика и сачувајте претраживи ПДФ са сликом и слојем текста који се може изабрати Слој алфа Хоризонтал Флип Вертицал Флип Закључај Додај сенку Схадов Цолор Тект Геометри Растегните или искосите текст за оштрију стилизацију Скала Кс Скев Кс Уклоните напомене Уклоните изабране типове напомена као што су везе, коментари, истакнути делови, облици или поља обрасца са ПДФ страница Хипервезе Филе Аттацхментс Линије Попупс Марке Облици Тект Нотес Означавање текста Форм Фиелдс Маркуп Непознато Напомене Разгрупиши Додајте замућену сенку иза слоја са подесивом бојом и офсетима ================================================ FILE: core/resources/src/main/res/values-sv/strings.xml ================================================ Något gick fel: %1$s Storlek %1$s Laddar… Bilden är för stor för att förhandsgranska, men sparandet kommer att provas ändå Välj bild för att börja Bredd %1$s Höjd %1$s Kvalitet Förlängning Typ av storleksförändring Explicit Flexibel Välj bild Vill Du avsluta appen? Appen avslutar Stanna kvar Stäng Återställ bild Bildändringar kommer att rulla tillbaka till initiala värden Värden korrekt återställda Återställ Något gick fel Starta om appen Kopierad till urklipp Undantag Redigera EXIF OK Ingen EXIF-data hittades Lägg till märkning Spara Rensa Rensa EXIF Avbryt Alla bildEXIF-data kommer att raderas. Denna åtgärd kan inte ångras! Förinställningar Beskär Sparar Alla osparade förändringar kommer att gå förlorade, om du lämnar nu Källkod Få senaste uppdateringarna, diskutera frågor och mer En redigering Modifiera, ändra storlek och redigera en bild Färg-väljare Välj färg från bild, kopiera eller dela med Bild Färg Färg kopierad Beskär bild till alla gränser Version Behåll EXIF Bilder: %d Ändra förhandsgranskning Ta bort Ram-tjocklek Yta Värde Lägg till Tillåtelse Tillåt För att bilder ska fungera behöver appen tillgång till lagring, det är absolut nödvändigt. Vänligen bevilja behörighet i nästa dialogruta. Appen behöver denna behörighet för att fungera, vänligen bevilja manuellt Extern lagring Monet-färger Detta program är helt gratis, men om du vill stödja projektutvecklingen kan du klicka här FAB-anpassning Sök uppdatering Om aktiverad visas uppdateringsrutan vid appstart Bild-zoom Dela Prefix Filnamn Emoji Välj vilken emoji som ska visas på startskärmen Lägg till fil-storlek Om aktiverad läggs bredd och höjd till i fil-namnet Radera EXIF Ta bort EXIF-metadata från en uppsättning bilder Förhandsgranskning Förhandsgranska bild: GIF, SVG, and so on Bild-källa Bild-väljare Galleri Filhanterare Androids moderna fotoväljare, vilket visas längst ner på skärmen, fungerar bara på Android 12+. Har problem med att ta emot EXIF-metadata Enkel bildväljare i galleriet. Fungerar endast om du har en app som har mediaupplockning Använd GetContent för att välja bild. Fungerar överallt men är känd för att ha problem med att ta emot valda bilder på vissa enheter. Det är inte mitt fel. Alternativa arrangemang Redigera Ordning Bestämmer ordningen på verktygen på huvudskärmen Antal Emoji\'s Ersätt sekvens-nummer Aktiverad ersätter tidsstämpeln i bildens sekvensnummer om du batch-processar Filter Effekt Filter Ljus Färg-filter Alpha Ljusstyrka Kontrast Nyans Mättnad Lägg till filter Ingen bild Bild-länk Ifyllnad Anpassa Internet-bild laddar Ladda bild från internet för förhandsgranskning, zooma, redigera och spara den om du vill. Använd filterkedjor på bilder Exponering Vit-balans Temperatur Ton Monokrom Gamma Ljusa partier och skuggor Ljusa partier Skuggor Dis Avstånd Lutning Skarpare Sepia Negativ Solariserad Intensitet Svart och vitt Korshatch Avstånd Linje-bredd Sobel-kant Oskärpa Halv-ton CGA färgyta Gaussisk oskärpa Generera färgpalett från bild Generera palett Palett Uppdatera Ny version %1$s Ogiltig filtyp: %1$s Ej möjligt att generera palett från bild Original Spara i mapp Förinställt Anpassad Ospecifierat Enhetens lagringsutrymme Storleksändra i förhållande till vikt Max storlek i KB Jämför Jämför två bilder Börja med att välja två bilder Välj bilder Inställningar Natt-läge Mörk Ljus System Dynamiska färger Egna inställningar Språk Amoled-läge Färgschema Röd Grön Blå Att ta en skärmdump misslyckades, försök igen Efter flytt eller zoomning kommer bilderna att fästa för att fylla ramkanter Histogram Denna bild används för att generera RGB och Ljusstyrke-histogram Tesseract-val Använd några inmatningsvariabler för tesseract-motorn Anpassade alternativ Alternativen ska anges efter detta mönster: \"--{val_namn} {värde}\" Automatisk beskärning Flytande hörn Polygon-beskär bild, detta val korrigerar perspektiv Tvinga punkter till bildens gränser Punkter kommer inte att begränsas av bildens gränser, är användbart för mer exakt perspektivkorrigering Dölj Innehållsmedveten fyllning under ritad bana Läkningspunkt Använd rund kärna Öppning Stängning Morfologisk gradient Svart hatt Tonkurvor Återställ kurvor Kurvor återställs till standard-värde Linje-stil Avståndsstorlek Streckad Punktstreckad Stämplad Sicksack Ritar streckad linje längs ritade bana med angiven avståndsstorlek Ritar punk och streckad linje längs given bana Ritar valda former längs banan med angivet mellanrum Ritar en vågig sicksack längs banan Sicksack-förhållande Skapa genväg Välj verktyg att fästa Verktyget kommer att läggas till på hemskärmen av din startprogram som en genväg. Använd det i kombination med inställningen \'Hoppa över filval\' för att uppnå önskat beteende Stapla inte ramar Aktiverar borttagning av tidigare ramar, så att de inte staplas på varandra Korsblekna Ramarna kommer att korsbleknas in i varandra Tröskel Ett Tröskel Två Canny Spegel 101 Förstärk zoom-oskärpa Enkel Laplacian Enkel Sobel Hjälp-rutmönster Visar ett nätmönster ovanför ritområdet för att hjälpa med precis manipulering Nät-färg Cell-bredd Cell-höjd Kompakta valtillbehör Ändra storlek på en bild enligt angiven storlek i KB Tillåt bildmonet Om aktiverat, när du väljer en bild att redigera, kommer appens färger att anpassas till denna bild Om aktiverat kommer ytfärgen att ställas in på absolut mörk i nattläge Klistra in en giltig aRGB-färgkod Inget att klistra in Appens färgschema kan inte ändras medan dynamiska färger är aktiverade Appens tema baseras på vald färg Om appen Inga uppdateringar hittades Problem-spårare Skicka buggrapporter och funktionsförfrågningar här Hjälp till att översätta Rätta översättningsfel eller lokalisera projektet till andra språk Din fråga gav inga svar Sök här Om aktiverad kommer appens färger att anpassas till bakgrundsbildens färger Lyckades inte spara %d bild(er) Email Främsta I tredje hand I andra hand Lägg till filens originalnamn Om aktiverad läggs ursprungliga filnamnet till i namnet på utgående bild Att lägga till ursprungligt filnamn fungerar inte om bildkällan för foto-väljaren är vald Innehållsskala Laplacian Vignett Börja Slut Ändrar storlek på bilder till angiven höjd och bredd. Bildens ratio kan ändras. Ändrar storlek på bilder med en lång sida till den angiven höjd eller bredd. Alla storleksberäkningar kommer att göras efter att de har sparats. Bildens ratio kommer att bevaras. Bilaterial oskärpa Skärpa Kuwahara-utjämning Stapeloskärpa Radie Skala Distorsion Vinkel Virvel Utbuckning Utvidgning Sfärisk refraktion Färg nummer två Omordna Snabb-oskärpa Storlek på oskärpa sekvensNum originalfilnamn Box oskärpa Brytningsindex Glassfärens brytning Färgmatris Opacitet Ändra storlek efter gränser Ändra storlek på bilder till den angivna höjden och bredden samtidigt som bildförhållandet behålls Skiss Tröskel Kvantiseringsnivåer Slät ton Toon Posterisera Icke maximal undertryckning Svag pixelinkludering Uppslag Convolution 3x3 RGB-filter Falsk färg Första färgen Oskärpa mitten x Oskärpa mitten y Zoomoskärpa Färgbalans Luminans tröskel Du inaktiverade appen Filer, aktivera den för att använda den här funktionen Dra Rita på bild som i en skissbok, eller rita på själva bakgrunden Måla färg Måla alfa Rita på bild Välj en bild och rita något på den Rita på bakgrund Välj bakgrundsfärg och rita ovanpå den Bakgrundsfärg Chiffer Kryptera och dekryptera vilken fil som helst (inte bara bilden) baserat på olika tillgängliga kryptoalgoritmer Välj fil Kryptera Dekryptera Välj fil för att starta Dekryptering Kryptering Nyckel Filen behandlas Lagra den här filen på din enhet eller använd dela för att placera den var du vill Drag Genomförande Kompatibilitet Lösenordsbaserad kryptering av filer. Fortsatta filer kan lagras i den valda katalogen eller delas. Dekrypterade filer kan också öppnas direkt. AES-256, GCM-läge, ingen utfyllnad, 12 bytes slumpmässiga IVs som standard. Du kan välja den algoritm som behövs. Nycklar används som 256-bitars SHA-3-hashar Fil-storlek Den maximala filstorleken begränsas av Android OS och tillgängligt minne, vilket är enhetsberoende. \nObservera: minnet är inte lagringsutrymme. Observera att kompatibilitet med annan filkrypteringsmjukvara eller tjänster inte garanteras. En något annorlunda nyckelbehandling eller chifferkonfiguration kan orsaka inkompatibilitet. Ogiltigt lösenord eller vald fil är inte krypterad Om du försöker spara bilden med den angivna bredden och höjden kan det leda till att minnet är slut. Gör detta på egen risk. Cache Cachestorlek Hittade %1$s Automatisk cacherensning Om aktiverat rensas appcachen vid start av appen Skapa Verktyg Gruppera alternativ efter typ Grupperar alternativ på huvudskärmen efter deras typ istället för ett anpassat listarrangemang Kan inte ändra arrangemang medan alternativgruppering är aktiverad Redigera skärmdump Sekundär anpassning Skärmdump Reservalternativ Hoppa Kopiera Att spara i läget %1$s kan vara instabilt eftersom det är ett förlustfritt format Om du har valt förinställning 125, kommer bilden att sparas som 125 % av originalbilden. Om du väljer förinställning 50, kommer bilden att sparas med 50 % storlek Förinställning här bestämmer % av utdatafilen, d.v.s. om du väljer förinställning 50 på 5 MB bild så får du en 2,5 MB bild efter att du har sparat Randomisera filnamn Om det är aktiverat kommer filnamnet att vara helt slumpmässigt Sparad i mappen %1$s med namnet %2$s Sparad i mappen %1$s Telegram chatt Diskutera appen och få feedback från andra användare. Du kan också få betauppdateringar och insikter där. Beskärningsmask Bildförhållande Använd den här masktypen för att skapa mask från en given bild, lägg märke till att den SKA ha alfakanal Säkerhetskopiera och återställa Säkerhetskopiering Återställ Säkerhetskopiera appinställningar till en fil Återställ appinställningar från tidigare genererad fil Skadad fil eller inte en säkerhetskopia Inställningarna har återställts Kontakta mig Detta kommer att återställa dina inställningar till standardvärdena. Observera att detta inte kan ångras utan en säkerhetskopia som nämns ovan. Radera Du håller på att ta bort det valda färgschemat. Denna operation kan inte ångras Ta bort schema Font Text Teckensnittsskala Standard Användning av stora teckensnittsskalor kan orsaka gränssnittsfel och problem som inte kommer att åtgärdas. Använd med försiktighet. Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz Åå Ää Öö 0123456789 !? Känslor Mat och dryck Natur och djur Objekt Symboler Aktivera emoji Resor och platser Aktiviteter Bakgrundsborttagare Ta bort bakgrunden från bilden genom att rita eller använd alternativet Auto Beskär bild Originalbildens metadata kommer att behållas Genomskinliga utrymmen runt bilden kommer att beskäras Autoradera bakgrund Återställ bild Raderings-läge Radera bakgrund Återställ bakgrund Oskarphetsradie Pipett Ritläge Skapa problem Hoppsan… Något gick fel. Du kan skriva till mig med hjälp av alternativen nedan så ska jag försöka hitta en lösning Storleksändra och konvertera Ändra storlek på givna bilder eller konvertera dem till andra format. EXIF-metadata kan också redigeras här om du väljer enskild bild. Max antal färger Detta gör att appen kan samla in kraschrapporter automatiskt Analyser Tillåt anonym insamling av appanvändningsstatistik För närvarande tillåter formatet %1$s endast läsning av EXIF-metadata på Android. Utdatabild kommer inte inkludera metadata när den sparas. Ansträngning Ett värde på %1$s betyder snabb komprimering, vilket resulterar i en relativt stor filstorlek. %2$s betyder en långsammare komprimering, vilket resulterar i en mindre fil. Vänta Sparningen är nästan klar. Om du avbryter nu måste du spara igen. Uppdateringar Tillåt betaversioner Uppdateringskontrollen kommer att inkludera betaappversioner om den är aktiverad Rita pilar Om aktiverat kommer ritbanan att representeras som pekpil Borsta mjukhet Bilderna kommer att beskäras i mitten till den angivna storleken. Canvas kommer att utökas med given bakgrundsfärg om bilden är mindre än angivna mått. Donation Bildsömnad Kombinera de givna bilderna för att få en stor Välj minst 2 bilder Utdatabilds skala Bildorientering Horisontell Vertikal Skala små bilder till stora Små bilder skalas till den största i sekvensen om det är aktiverat Beställning av bilder Regelbunden Sudda kanter Ritar suddiga kanter under originalbilden för att fylla utrymmen runt den istället för enfärgad om aktiverat Pixelering Förbättrad pixelering Stroke Pixelation Förbättrad diamantpixelering Diamond Pixelation Cirkelpixelering Förbättrad cirkelpixelering Byt ut färg Tolerans Färg att ersätta Målfärg Färg att ta bort Ta bort färg Koda om Pixelstorlek Lås ritriktning Om det är aktiverat i ritläge kommer skärmen inte att rotera Sök efter uppdateringar Palett stil Tonal punkt Neutral Vibrerande Uttrycksfull Regnbåge Fruktsallad Trohet Innehåll Standard palettstil, det tillåter att anpassa alla fyra färgerna, andra låter dig ställa in endast nyckelfärgen En stil som är något mer kromatisk än monokrom Ett högljutt tema, färgglattheten är maximal för Primär palett, ökad för andra Ett lekfullt tema - källfärgens nyans visas inte i temat Ett monokromt tema, färgerna är rent svart / vit / grå Ett schema som placerar källfärgen i Scheme.primaryContainer Ett schema som är väldigt likt innehållsschemat Denna uppdateringskontroll kommer att ansluta till GitHub för att kontrollera om det finns en ny uppdatering tillgänglig Uppmärksamhet Bleknade kanter Inaktiverad Både Invertera färger Ersätter temafärger till negativa om det är aktiverat Söka Möjliggör möjligheten att söka igenom alla tillgängliga verktyg på huvudskärmen PDF-verktyg Arbeta med PDF-filer: Förhandsgranska, Konvertera till en grupp bilder eller skapa en från givna bilder Förhandsgranska PDF PDF till bilder Bilder till PDF Enkel PDF-förhandsvisning Konvertera PDF till bilder i givet utdataformat Packa givna bilder till utdata-PDF-fil Maskfilter Applicera filterkedjor på givna maskerade områden, varje maskområde kan bestämma sin egen uppsättning filter Masker Lägg till mask Mask %d Mask färg Mask förhandsgranskning Ritad filtermask kommer att renderas för att visa dig det ungefärliga resultatet Omvänd fyllningstyp Om aktiverat kommer alla icke-maskerade områden att filtreras istället för standardbeteende Du håller på att ta bort den valda filtermasken. Denna operation kan inte ångras Ta bort mask Fullt filter Använd eventuella filterkedjor på givna bilder eller enstaka bilder Start Centrum Avsluta Enkla varianter Highlighter Neon Penna Sekretessoskärpa Rita halvtransparenta vässade överstrykningspennor Lägg till en glödande effekt på dina ritningar Standard en, enklast - bara färgen Gör bilden suddig under den ritade banan för att säkra allt du vill dölja Liknar sekretessoskärpa, men pixlar istället för oskärpa Behållare Rita en skugga bakom behållare Reglage Växlar FABs Knappar Rita en skugga bakom reglagen Rita en skugga bakom strömbrytare Rita en skugga bakom flytande actionknappar Rita en skugga bakom knappar App barer Rita en skugga bakom appstaplar Värde inom intervallet %1$s - %2$s Autorotera Tillåter att gränsruta används för bildorientering Rita sökvägsläge Dubbel linjepil Gratis ritning Dubbel pil Linjepil Pil Linje Ritar sökväg som indatavärde Ritar banan från startpunkt till slutpunkt som en linje Ritar pekpil från startpunkt till slutpunkt som en linje Ritar en pekande pil från en given bana Ritar dubbel pekande pil från startpunkt till slutpunkt som en linje Ritar dubbelpekande pil från en given bana Oval kontur Skisserad Rect Oval Rect Ritar rät från startpunkt till slutpunkt Ritar oval från startpunkt till slutpunkt Ritar en oval med kontur från startpunkt till slutpunkt Ritar konturerade rekt från startpunkt till slutpunkt Lasso Ritar stängd fylld bana efter given bana Gratis Horisontellt rutnät Vertikalt rutnät Stygnläge Räknar rader Antal kolumner Ingen \"%1$s\"-katalog hittades, vi bytte den till standardmappen, spara filen igen Urklipp Auto pin Lägger automatiskt till sparad bild till urklipp om aktiverat Vibration Vibrationsstyrka För att skriva över filer måste du använda \"Utforskaren\" bildkälla, försök återplocka bilder, vi har ändrat bildkällan till den som behövs Skriv över filer Originalfilen kommer att ersättas med en ny istället för att sparas i vald mapp, det här alternativet måste vara \"Explorer\" eller GetContent som bildkälla, när du växlar detta kommer det att ställas in automatiskt Tömma Ändelse Skalläge bilinjär Kattmull Bikubisk Han Eremit Lanczos Mitchell Närmast Spline Grundläggande Standardvärde Linjär (eller bilinjär, i två dimensioner) interpolation är vanligtvis bra för att ändra storleken på en bild, men orsakar en oönskad uppmjukning av detaljer och kan fortfarande vara något ojämn Bättre skalningsmetoder inkluderar Lanczos resampling och Mitchell-Netravali-filter Ett av de enklare sätten att öka storleken genom att ersätta varje pixel med ett antal pixlar av samma färg Enklaste skalningsläget för Android som används i nästan alla appar Metod för att smidigt interpolera och omsampla en uppsättning kontrollpunkter, som vanligtvis används i datorgrafik för att skapa jämna kurvor Fönsterfunktion används ofta i signalbehandling för att minimera spektralläckage och förbättra noggrannheten i frekvensanalys genom att avsmalna kanterna på en signal Matematisk interpolationsteknik som använder värden och derivator vid ändpunkterna av ett kurvsegment för att generera en jämn och kontinuerlig kurva Omsamplingsmetod som upprätthåller interpolering av hög kvalitet genom att tillämpa en viktad sinc-funktion på pixelvärdena Omsamplingsmetod som använder ett faltningsfilter med justerbara parametrar för att uppnå en balans mellan skärpa och kantutjämning i den skalade bilden Använder bitvis definierade polynomfunktioner för att jämnt interpolera och approximera en kurva eller yta, vilket ger flexibel och kontinuerlig formrepresentation Endast klipp Spara till lagring kommer inte att utföras, och bilden kommer endast att försöka läggas in i urklipp Penseln återställer bakgrunden istället för att radera OCR (känn igen text) Känn igen text från en given bild, 120+ språk stöds Bilden har ingen text, eller så hittade appen den inte \"Noggrannhet: %1$s\" Typ av igenkänning Snabb Standard Bäst Inga data För att Tesseract OCR ska fungera korrekt måste ytterligare träningsdata (%1$s) laddas ner till din enhet.\nVill du ladda ner %2$s-data? Ladda ner Ingen anslutning, kontrollera det och försök igen för att ladda ner tågmodeller Nedladdade språk Tillgängliga språk Segmenteringsläge Använd Pixel Switch Använder en Google Pixel-liknande switch Överskriven fil med namnet %1$s vid den ursprungliga destinationen Förstoringsglas Aktiverar förstoringsglas överst på fingret i ritlägen för bättre tillgänglighet Forcera initialt värde Tvingar exif-widgeten att kontrolleras initialt Tillåt flera språk Glida Sida vid sida Växla Tryck Genomskinlighet Betygsätt App Hastighet Denna app är helt gratis, om du vill att den ska bli större, vänligen stjärna projektet på Github 😄 Endast orientering och skriptdetektion Automatisk orientering och skriptidentifiering Endast auto Bil Enstaka kolumn Ett block vertikal text Enkelt block En rad Ett enda ord Ringa in ord Enstaka röding Sparsam text Gles textorientering &amp; skriptdetektion Rå linje Vill du radera språket \"%1$s\" OCR-träningsdata för alla igenkänningstyper, eller bara för den valda (%2$s)? Nuvarande Alla Gradient Maker Skapa gradient av given utdatastorlek med anpassade färger och utseendetyp Linjär Radiell Sopa Gradienttyp Center X Center Y Kakelläge Upprepad Spegel Klämma Dekal Färgen stannar Lägg till färg Egenskaper Upprätthållande av ljusstyrka Skärm Gradient Overlay Komponera valfri gradient av toppen av givna bilder Transformationer Kamera Ta en bild med en kamera. Observera att det bara är möjligt att få en bild från denna bildkälla Vattenmärkning Täck bilder med anpassningsbara text-/bildvattenstämplar Upprepa vattenstämpel Upprepar vattenstämpel över bild istället för singel vid given position Offset X Offset Y Typ av vattenstämpel Den här bilden kommer att användas som mönster för vattenmärkning Text färg Överlagringsläge GIF-verktyg Konvertera bilder till GIF-bild eller extrahera ramar från given GIF-bild GIF till bilder Konvertera GIF-fil till en serie bilder Konvertera parti bilder till GIF-fil Bilder till GIF Välj GIF-bild för att starta Använd storleken på den första ramen Ersätt specificerad storlek med första rammått Upprepa räkning Frame Delay millis FPS Använd Lasso Använder lasso som i ritläge för att utföra radering Förhandsgranskning av originalbild Alpha Konfetti Konfetti kommer att visas på sparande, delning och andra primära åtgärder Säkert läge Döljer appinnehåll i de senaste apparna. Det går inte att fånga eller spela in. Utgång Om du lämnar förhandsgranskningen nu måste du lägga till bilderna igen Dithering Kvantifierare Gråskala Bayer två och två dithering Bayer tre och tre dithering Bayer fyra vid fyra dithering Bayer Eight By Eight Dithering Floyd Steinberg Dithering Jarvis domare Ninke Dithering Sierra Dithering Två rader Sierra Dithering Sierra Lite Dithering Atkinson Dithering Stucki Dithering Burkes Dithering Falsk Floyd Steinberg Dithering Vänster till höger dithering Slumpmässig dithering Enkel tröskelvibrering Sigma Spatial Sigma Median oskärpa B Spline Använder bitvis definierade bikubiska polynomfunktioner för att smidigt interpolera och approximera en kurva eller yta, flexibel och kontinuerlig formrepresentation Native Stack Blur Tilt Shift Glitch Belopp Utsäde Anaglyph Buller Pixelsort Blanda Förbättrad glitch Channel Shift X Kanalskift Y Korruptionsstorlek Korruption Shift X Korruptionsskift Y Tältskärpa Sida blekna Sida Bästa Botten Styrka Erodera Anisotropisk diffusion Diffusion Ledning Horisontell vindsvängning Snabb bilateral oskärpa Poisson Blur Logaritmisk tonmappning ACES Filmic Tone Mapping Kristallisera Slagfärg Fraktal glas Amplitud Marmor Turbulens Olja Vatteneffekt Storlek Frekvens X Frekvens Y Amplitud X Amplitud Y Perlin Distortion ACES Hill Tone Mapping Hable Filmic Tone Mapping Heji-Burgess Tonkartläggning Hastighet Dehaze Omega Färgmatris 4x4 Färgmatris 3x3 Enkla effekter Polaroid Tritanomali Deuteranomali Protanomali Årgång Browns Coda Chrome Nattseende Värma Sval Tritanopia Deutaronotopia Protanopia Akromatomali Achromatopsi Spannmål Oskarp Pastell Orange Haze Rosa dröm Gyllene timmen Varm sommar Purple Mist Soluppgång Färgglad virvel Mjukt fjäderljus Hösttoner Lavendel dröm Cyberpunk Lemonad ljus Spektral eld Nattmagi Fantasilandskap Färgexplosion Elektrisk gradient Caramel Darkness Futuristisk gradient Grön sol Rainbow World Deep Purple Rymdportal Röd virvel Digital kod Bokeh Appfältets emoji kommer att ändras slumpmässigt Slumpmässiga emojis Du kan inte använda slumpmässiga emojis medan emojis är inaktiverade Du kan inte välja en emoji medan slumpmässiga emojis är aktiverade Gammal tv Blanda oskärpa Favorit Inga favoritfilter har lagts till ännu Bildformat Lägger till en behållare med den valda formen under ikonerna Ikon Form Drago Aldridge Cutoff Du vaknar Mobius Övergång Topp Färgavvikelse Bilder skrivna över vid den ursprungliga destinationen Det går inte att ändra bildformat medan alternativet för överskrivning av filer är aktiverat Emoji som färgschema Använder emoji primärfärg som appfärgschema istället för manuellt definierad Skapar material du palett från bild Mörka färger Använder nattläges färgschema istället för ljusvariant Kopiera som Jetpack Compose-kod Ringoskärpa Korsoskärpa Cirkel oskärpa Stjärnoskärpa Linjär Tilt-Shift Taggar att ta bort APNG-verktyg Konvertera bilder till APNG-bild eller extrahera ramar från given APNG-bild APNG till bilder Konvertera APNG-fil till bildserie Konvertera parti bilder till APNG-fil Bilder till APNG Välj APNG-bild för att starta Rörelseoskärpa Blixtlås Skapa zip-fil från givna filer eller bilder Drahandtagsbredd Typ av konfetti Festlig Explodera Regn Hörn JXL-verktyg Utför JXL ~ JPEG-omkodning utan kvalitetsförlust, eller konvertera GIF/APNG till JXL-animation JXL till JPEG Utför förlustfri omkodning från JXL till JPEG Utför förlustfri omkodning från JPEG till JXL JPEG till JXL Välj JXL-bild för att börja Snabb Gaussisk oskärpa 2D Snabb Gaussisk oskärpa 3D Snabb Gaussisk oskärpa 4D Bilpåsk Tillåter appen att automatiskt klistra in urklippsdata, så det kommer att visas på huvudskärmen och du kommer att kunna bearbeta det Harmoniseringsfärg Harmoniseringsnivå Lanczos Bessel Omsamplingsmetod som upprätthåller interpolering av hög kvalitet genom att tillämpa en Bessel-funktion (jinc) på pixelvärdena GIF till JXL Konvertera GIF-bilder till JXL-animerade bilder APNG till JXL Konvertera APNG-bilder till JXL-animerade bilder JXL till bilder Konvertera JXL-animation till en serie bilder Bilder till JXL Konvertera parti bilder till JXL-animation Beteende Hoppa över filval Filväljaren kommer att visas omedelbart om detta är möjligt på den valda skärmen Skapa förhandsvisningar Aktiverar generering av förhandsgranskning, detta kan hjälpa till att undvika krascher på vissa enheter, detta inaktiverar också vissa redigeringsfunktioner inom ett enda redigeringsalternativ Förlustig kompression Använder förlustfri komprimering för att minska filstorleken istället för förlustfri Kompressionstyp Styr den resulterande bildavkodningshastigheten, detta bör hjälpa till att öppna den resulterande bilden snabbare, värdet %1$s betyder långsammaste avkodning, medan %2$s - snabbast, den här inställningen kan öka utdatabildens storlek Sortering Datum Datum (omvänt) Namn Namn (omvänt) Kanalkonfiguration I dag I går Inbäddad väljare Image Toolboxs bildväljare Inga behörigheter Begäran Välj flera media Välj Single Media Plocka Försök igen Visa inställningar i liggande Om detta är inaktiverat kommer inställningarna i liggande läge att öppnas på knappen i övre appfältet som alltid, istället för permanent synligt alternativ Inställningar för helskärm Aktivera det och inställningssidan kommer alltid att öppnas som helskärm istället för skjutbart lådblad Switch Typ Komponera Ett Jetpack Compose Material Du byter Ett material du byter Max Ändra storlek på ankare Pixel Flytande En switch baserad på designsystemet \"Flytande\". Cupertino En switch baserad på designsystemet \"Cupertino\". Bilder till SVG Spåra givna bilder till SVG-bilder Använd Sampled Palette Kvantiseringspalett kommer att samplas om detta alternativ är aktiverat Väg utelämna Användning av detta verktyg för att spåra stora bilder utan nedskalning rekommenderas inte, det kan orsaka kraschar och öka bearbetningstiden Nedskalad bild Bilden kommer att skalas ner till lägre dimensioner innan bearbetning, detta hjälper verktyget att arbeta snabbare och säkrare Minsta färgförhållande Linjer Tröskel Kvadratisk tröskel Koordinater avrundningstolerans Path Skala Återställ egenskaper Alla egenskaper kommer att ställas in på standardvärden. Observera att denna åtgärd inte kan ångras Detaljerad Standardlinjebredd Motorläge Arv LSTM-nätverk Legacy &amp; LSTM Konvertera Konvertera bildsatser till givet format Lägg till ny mapp Bitar per prov Kompression Fotometrisk tolkning Prover per pixel Plan konfiguration Y Cb Cr Subsampling Y Cb Cr Positionering X Upplösning Y Upplösning Upplösningsenhet Strip Offsets Rader per remsa Strip Byte Counts JPEG Interchange Format JPEG Interchange Format Längd Överföringsfunktion Vit punkt Primär kromaticitet Y Cb Cr-koefficienter Referens Svart Vit Datum Tid Bildbeskrivning Göra Modell Programvara Konstnär Upphovsrätt Exif-version Flashpix version Färgrymd Gamma Pixel X Dimension Pixel Y-dimension Komprimerade bitar per pixel Maker Note Användarkommentar Relaterad ljudfil Datum Tid Original Datum Tid Digitaliserad Offsettid Offset Time Original Offsettid digitaliserad Undersekundstid Undersek. Tid Original Undersek. Tid Digitaliserad Exponeringstid F-nummer Exponeringsprogram Spektral känslighet Fotografisk känslighet Oecf Känslighetstyp Standardutgångskänslighet Rekommenderat exponeringsindex ISO-hastighet ISO Speed ​​Latitude ååå ISO Speed ​​Latitude zzz Slutarhastighetsvärde Bländarvärde Ljusstyrka värde Exponeringsbiasvärde Max bländarvärde Ämnesavstånd Mätläge Flash Ämnesområde Brännvidd Flash energi Spatial Frequency Response Focal Plane X-upplösning Fokalplan Y-upplösning Focal Plane Resolution Unit Ämnesplats Exponeringsindex Avkänningsmetod Filkälla CFA-mönster Custom Rendered Exponeringsläge Vitbalans Digitalt zoomförhållande Brännvidd i 35 mm film Sceninspelningstyp Få kontroll Kontrast Mättnad Skärpa Beskrivning av enhetsinställning Ämnesavståndsområde Bild unikt ID Kameraägarens namn Kroppens serienummer Objektivspecifikation Objektivfabrikat Objektivmodell Linsens serienummer GPS-versions-ID GPS Latitude Ref GPS-latitud GPS Longitud Ref GPS Longitud GPS Höjd Ref GPS-höjd GPS tidsstämpel GPS-satelliter GPS-status GPS-mätläge GPS DOP GPS-hastighet Ref GPS-hastighet GPS Track Ref GPS-spår GPS Img Riktning Ref GPS-bildriktning GPS-karta datum GPS Dest Latitude Ref GPS Dest Latitude GPS Dest Longitud Ref GPS Dest Longitud GPS Dest Bearing Ref GPS Dest Bearing GPS Dest Distance Ref GPS Dest Distance GPS-bearbetningsmetod GPS-områdesinformation GPS datumstämpel GPS-differential GPS H-positioneringsfel Interoperabilitetsindex DNG-version Standard beskärningsstorlek Förhandsgranska bild Start Förhandsgranska bildlängd Aspektram Sensor bottenkant Sensor vänster kant Sensor höger kant Sensor övre kant ISO Rita text på banan med angivet teckensnitt och färg Fontstorlek Vattenstämpel storlek Upprepa text Aktuell text kommer att upprepas tills sökvägen slutar istället för att rita en gång Dash storlek Använd den valda bilden för att rita den längs en given bana Denna bild kommer att användas som upprepad inmatning av ritad bana Ritar en triangel med kontur från startpunkt till slutpunkt Ritar en triangel med kontur från startpunkt till slutpunkt Konturerad triangel Triangel Ritar polygon från startpunkt till slutpunkt Polygon Konturerad polygon Ritar polygon med kontur från startpunkt till slutpunkt Vertices Rita vanlig polygon Rita polygon som kommer att vara regelbunden istället för fri form Ritar stjärna från startpunkt till slutpunkt Stjärna Konturerad stjärna Ritar konturstjärna från startpunkt till slutpunkt Inre radieförhållande Rita vanlig stjärna Rita stjärna som kommer att vara vanlig istället för fri form Antialias Möjliggör kantutjämning för att förhindra skarpa kanter Öppna Redigera istället för förhandsgranskning När du väljer bild som ska öppnas (förhandsgranska) i ImageToolbox öppnas redigera urvalsark istället för att förhandsgranska Dokumentskanner Skanna dokument och skapa PDF eller separera bilder från dem Klicka för att börja skanna Börja skanna Spara som pdf Dela som pdf Alternativen nedan är för att spara bilder, inte PDF Utjämna Histogram HSV Utjämna histogram Ange procent Tillåt inträde via textfält Aktiverar textfält bakom val av förinställningar, för att ange dem i farten Skala färgrymd Linjär Utjämna histogrampixelering Rutnätsstorlek X Rutnätsstorlek Y Utjämna Histogram Adaptive Utjämna Histogram Adaptive LUV Utjämna Histogram Adaptive LAB CLAHE CLAHE LAB CLAHE LUV Beskär till innehåll Ram färg Färg att ignorera Mall Inga mallfilter har lagts till Skapa nytt Den skannade QR-koden är inte en giltig filtermall Skanna QR-koden Den valda filen har inga filtermallsdata Skapa mall Mallnamn Den här bilden kommer att användas för att förhandsgranska filtermallen Mallfilter Som QR-kodbild Som fil Spara som fil Spara som QR-kodbild Ta bort mall Du håller på att ta bort valt mallfilter. Denna operation kan inte ångras Lade till filtermall med namnet \"%1$s\" (%2$s) Förhandsgranska filter QR &amp; streckkod Skanna QR-koden och få dess innehåll eller klistra in din sträng för att skapa en ny Kodinnehåll Skanna valfri streckkod för att ersätta innehållet i fältet, eller skriv något för att generera ny streckkod med vald typ QR Beskrivning Min Ge kameratillstånd i inställningarna för att skanna QR-kod Ge kameratillstånd i inställningarna för att skanna dokumentskanner Kubisk B-Spline Hamming Hanning Blackman Welch Quadric Gaussisk Sfinx Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-He Låda Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kubisk interpolering ger jämnare skalning genom att beakta de närmaste 16 pixlarna, vilket ger bättre resultat än bilinjär Använder bitvis definierade polynomfunktioner för att smidigt interpolera och approximera en kurva eller yta, flexibel och kontinuerlig formrepresentation En fönsterfunktion som används för att minska spektralläckage genom att avsmalna kanterna på en signal, användbar vid signalbehandling En variant av Hann-fönstret, som vanligtvis används för att minska spektralläckage i signalbehandlingsapplikationer En fönsterfunktion som ger bra frekvensupplösning genom att minimera spektralläckage, som ofta används vid signalbehandling En fönsterfunktion designad för att ge bra frekvensupplösning med minskat spektralläckage, ofta använd i signalbehandlingsapplikationer En metod som använder en kvadratisk funktion för interpolation, vilket ger jämna och kontinuerliga resultat En interpolationsmetod som använder en Gaussisk funktion, användbar för att jämna ut och minska brus i bilder En avancerad omsamplingsmetod som ger högkvalitativ interpolering med minimala artefakter En triangulär fönsterfunktion som används vid signalbehandling för att minska spektralläckage En högkvalitativ interpolationsmetod optimerad för naturlig bildstorlek, balansering av skärpa och jämnhet En skarpare variant av Robidoux-metoden, optimerad för skarp bildstorlek En spline-baserad interpolationsmetod som ger jämna resultat med ett 16-tapps filter En spline-baserad interpolationsmetod som ger jämna resultat med ett 36-tapps filter En spline-baserad interpolationsmetod som ger jämna resultat med ett 64-tapps filter En interpolationsmetod som använder Kaiser-fönstret, vilket ger god kontroll över avvägningen mellan huvudlobens bredd och sidolobsnivån En hybridfönsterfunktion som kombinerar Bartlett- och Hann-fönstren, som används för att minska spektralläckage i signalbehandling En enkel omsamplingsmetod som använder genomsnittet av de närmaste pixelvärdena, vilket ofta resulterar i ett blockigt utseende En fönsterfunktion som används för att minska spektralt läckage, vilket ger bra frekvensupplösning i signalbehandlingsapplikationer En omsamplingsmetod som använder ett 2-lobs Lanczos-filter för högkvalitativ interpolering med minimala artefakter En omsamplingsmetod som använder ett 3-lobs Lanczos-filter för högkvalitativ interpolering med minimala artefakter En omsamplingsmetod som använder ett 4-lobs Lanczos-filter för högkvalitativ interpolering med minimala artefakter En variant av Lanczos 2-filtret som använder jinc-funktionen, ger högkvalitativ interpolering med minimala artefakter En variant av Lanczos 3-filtret som använder jinc-funktionen, vilket ger högkvalitativ interpolering med minimala artefakter En variant av Lanczos 4-filtret som använder jinc-funktionen, ger högkvalitativ interpolering med minimala artefakter Hanning EWA Elliptical Weighted Average (EWA) variant av Hanning-filtret för smidig interpolering och omsampling Robidoux EWA Elliptical Weighted Average (EWA) variant av Robidoux-filtret för högkvalitativ omsampling Blackman EVE Elliptical Weighted Average (EWA) variant av Blackman-filtret för att minimera ringsignalsartefakter Quadric EWA Elliptical Weighted Average (EWA) variant av Quadric-filtret för smidig interpolation Robidoux Sharp EWA Elliptical Weighted Average (EWA) variant av Robidoux Sharp-filtret för skarpare resultat Lanczos 3 Jinc EWA Elliptical Weighted Average (EWA) variant av Lanczos 3 Jinc-filtret för högkvalitativ omsampling med reducerad aliasing Ginseng Ett omsamplingsfilter designat för högkvalitativ bildbehandling med en bra balans mellan skärpa och jämnhet Ginseng EWA Elliptical Weighted Average (EWA) variant av ginsengfiltret för förbättrad bildkvalitet Lanczos Sharp EWA Elliptical Weighted Average (EWA) variant av Lanczos Sharp-filtret för att uppnå skarpa resultat med minimala artefakter Lanczos 4 Sharpest EWA Elliptical Weighted Average (EWA) variant av Lanczos 4 Sharpest-filtret för extremt skarp omsampling av bilder Lanczos Soft EWA Elliptical Weighted Average (EWA) variant av Lanczos Soft-filtret för jämnare bildomsampling Haasn Soft Ett omsamplingsfilter designat av Haasn för jämn och artefaktfri bildskalning Formatkonvertering Konvertera partier av bilder från ett format till ett annat Avvisa för alltid Bildstapling Stapla bilder ovanpå varandra med valda blandningslägen Lägg till bild Papperskorgar räknas Clahe HSL Clahe HSV Utjämna Histogram Adaptive HSL Utjämna Histogram Adaptive HSV Kantläge Klämma Sjal Färgblindhet Välj läge för att anpassa temafärger för den valda färgblindhetsvarianten Svårt att skilja mellan röda och gröna nyanser Svårt att skilja mellan gröna och röda nyanser Svårt att skilja mellan blå och gula nyanser Oförmåga att uppfatta röda nyanser Oförmåga att uppfatta gröna nyanser Oförmåga att uppfatta blå nyanser Minskad känslighet för alla färger Fullständig färgblindhet, ser bara nyanser av grått Använd inte färgblindschema Färgerna kommer att vara exakt som inställda i temat Sigmoidal Lagrange 2 Ett Lagrange-interpolationsfilter av ordning 2, lämpligt för högkvalitativ bildskalning med mjuka övergångar Lagrange 3 Ett Lagrange-interpolationsfilter av ordning 3, erbjuder bättre noggrannhet och jämnare resultat för bildskalning Lanczos 6 Ett Lanczos omsamplingsfilter med en högre ordning på 6, vilket ger skarpare och mer exakt bildskalning Lanczos 6 Jinc En variant av Lanczos 6-filtret som använder en Jinc-funktion för förbättrad bildomsamplingskvalitet Linjär Box oskärpa Linjär tältskärpa Linjär Gaussisk Box oskärpa Linjär stackoskärpa Gaussisk boxoskärpa Linjär Snabb Gaussisk oskärpa Nästa Linjär snabb Gaussisk oskärpa Linjär Gaussisk oskärpa Välj ett filter för att använda det som färg Byt ut filter Välj filter nedan för att använda det som pensel i din ritning TIFF-komprimeringsschema Låg poly Sandmålning Bilddelning Dela en bild efter rader eller kolumner Fit To Bounds Kombinera läget för beskärningsändring med denna parameter för att uppnå önskat beteende (Beskär/Anpassa till bildförhållande) Språk har importerats Backup OCR-modeller Importera Exportera Placera Centrum Överst till vänster Överst till höger Nederst till vänster Nederst till höger Top Center Mitten höger Nedre mitten Mitten vänster Målbild Palettöverföring Förbättrad olja Enkel gammal TV HDR Gotham Enkel skiss Mjuk glöd Färg affisch Tri Tone Tredje färgen Clahe Oklab Clara Olch Clahe Jzazbz Polka Dot Clustered 2x2 Dithering Clustered 4x4 Dithering Clustered 8x8 Dithering Yililoma Dithering Inga favoritalternativ har valts, lägg till dem på verktygssidan Lägg till favoriter Komplementär Analog Triadisk Dela Kompletterande Tetradisk Fyrkant Analog + Komplementär Färgverktyg Mixa, skapa toner, generera nyanser och mer Färgharmonier Färgskuggning Variation Nyanser Toner Nyanser Färgblandning Färginformation Vald färg Färg att blanda Det går inte att använda monet när dynamiska färger är aktiverade 512x512 2D LUT Mål LUT-bild Amatör Fröken Etikett Mjuk elegans Mjuk elegansvariant Palettöverföringsvariant 3D LUT Mål 3D LUT-fil (.cube / .CUBE) LUT Bleach Bypass Levande ljus Drop Blues Edgy Amber Höstfärger Filmlager 50 Dimmig natt Kodak Få neutral LUT-bild Använd först ditt favoritprogram för fotoredigering för att tillämpa ett filter på neutral LUT som du kan få här. För att detta ska fungera korrekt får varje pixelfärg inte bero på andra pixlar (t.ex. oskärpa fungerar inte). När du är klar använder du din nya LUT-bild som indata för 512*512 LUT-filter Popkonst Celluloid Kaffe Gyllene skogen Grönaktig Retro gul Förhandsgranskning av länkar Möjliggör länkförhandsvisning på platser där du kan hämta text (QRCode, OCR etc) Länkar ICO-filer kan endast sparas med en maximal storlek på 256 x 256 GIF till WEBP Konvertera GIF-bilder till WEBP-animerade bilder WEBP-verktyg Konvertera bilder till WEBP-animerade bilder eller extrahera ramar från given WEBP-animation WEBP till bilder Konvertera WEBP-fil till bildserie Konvertera parti bilder till WEBP-fil Bilder till WEBP Välj WEBP-bild för att starta Ingen fullständig åtkomst till filer Tillåt alla filer åtkomst för att se JXL, QOI och andra bilder som inte känns igen som bilder på Android. Utan tillstånd kan Image Toolbox inte visa dessa bilder Standard ritfärg Standardläge för ritningsväg Lägg till tidsstämpel Aktiverar tidsstämpel tillägg till utdatafilnamnet Formaterad tidsstämpel Aktivera tidsstämpelformatering i utdatafilnamn istället för grundläggande millis Aktivera tidsstämplar för att välja format Engångsspara plats Visa och redigera engångssparplatser som du kan använda genom att trycka länge på spara-knappen i de flesta alternativ Nyligen använd CI-kanal Grupp Bildverktygslåda i Telegram 🎉 Gå med i vår chatt där du kan diskutera vad du vill och titta även in på CI-kanalen där jag lägger upp betaversioner och tillkännagivanden Få aviseringar om nya versioner av appen och läs meddelanden Anpassa en bild till givna mått och applicera oskärpa eller färg på bakgrunden Verktygsarrangemang Gruppera verktyg efter typ Grupperar verktyg på huvudskärmen efter deras typ istället för ett anpassat listarrangemang Standardvärden Systembars synlighet Visa systemfält genom att svepa Aktiverar svepning för att visa systemfält om de är dolda Bil Göm alla Visa alla Dölj navigeringsfältet Dölj statusfältet Bullergenerering Generera olika ljud som Perlin eller andra typer Frekvens Bullertyp Rotationstyp Fraktal typ Oktaver Lacunaritet Viktad styrka Ping Pong Styrka Avståndsfunktion Returtyp Jitter Domän Warp Inriktning Anpassat filnamn Välj plats och filnamn som ska användas för att spara aktuell bild Sparad i mapp med anpassat namn Collage Maker Gör collage av upp till 20 bilder Collage typ Håll bilden för att byta, flytta och zooma för att justera position Inaktivera rotation Förhindrar roterande bilder med tvåfingergester Aktivera fästning till gränser RGB eller Ljusstyrka bildhistogram för att hjälpa dig att göra justeringar Hög hatt Bara standard raka linjer Crossfade-ramar räknas Vissa valkontroller använder en kompakt layout för att ta mindre plats Ge kameratillstånd i inställningarna för att ta bild Layout Huvudskärmens titel Konstant hastighetsfaktor (CRF) Ett värde på %1$s betyder en långsam komprimering, vilket resulterar i en relativt liten filstorlek. %2$s betyder en snabbare komprimering, vilket resulterar i en stor fil. Lut bibliotek Ladda ner samling av LUT, som du kan använda efter nedladdning Uppdatera samling av LUT:er (endast nya kommer att stå i kö), som du kan använda efter nedladdning Ändra standardbildförhandsvisning för filter Förhandsgranska bild Dölja Visa Slider Typ Lust Material 2 En snygg reglage. Detta är standardalternativet Ett material 2-reglage Ett material du reglage Tillämpas Center dialogknappar Knappar för dialogrutor kommer att placeras i mitten istället för på vänster sida om möjligt Licenser med öppen källkod Visa licenser för bibliotek med öppen källkod som används i den här appen Område Omsampling med hjälp av pixelarearelation. Det kan vara en föredragen metod för bilddecimering, eftersom det ger moaréfria resultat. Men när bilden är zoomad liknar den metoden \"Närmast\". Aktivera tonmappning Ange % Kan inte komma åt sidan, försök använda VPN eller kontrollera om webbadressen är korrekt Markeringslager Lagerläge med möjlighet att fritt placera bilder, text och mer Redigera lager Lager på bilden Använd en bild som bakgrund och lägg till olika lager ovanpå den Lager på bakgrund Samma som första alternativet men med färg istället för bild Beta Snabb inställningssida Lägg till en flytande remsa på den valda sidan medan du redigerar bilder, vilket öppnar snabba inställningar när du klickar på den Rensa val Inställningsgruppen \"%1$s\" kommer att komprimeras som standard Inställningsgruppen \"%1$s\" kommer att utökas som standard Base64-verktyg Avkoda Base64-sträng till bild, eller koda bild till Base64-format Bas 64 Det angivna värdet är inte en giltig Base64-sträng Kan inte kopiera tom eller ogiltig Base64-sträng Klistra in Base64 Kopiera Base64 Ladda bilden för att kopiera eller spara Base64-strängen. Om du har själva strängen kan du klistra in den ovan för att få en bild Spara Base64 Aktiebas64 Alternativ Åtgärder Import Base64 Base64-åtgärder Lägg till disposition Lägg till kontur runt text med angiven färg och bredd Konturfärg Konturstorlek Rotation Kontrollsumma som filnamn Utdatabilder kommer att ha ett namn som motsvarar deras datakontrollsumma Fri programvara (partner) Mer användbar programvara i partnerkanalen för Android-applikationer Algoritm Kontrollsummeverktyg Jämför kontrollsummor, beräkna hash eller skapa hexsträngar från filer med hjälp av olika hashalgoritmer Kalkylera Text Hash Kontrollsumma Välj fil för att beräkna dess kontrollsumma baserat på vald algoritm Skriv in text för att beräkna dess kontrollsumma baserat på vald algoritm Källkontrollsumma Kontrollsumma att jämföra Match! Skillnad Kontrollsummor är lika, det kan vara säkert Kontrollsummor är inte lika, filen kan vara osäker! Mesh Gradienter Titta på online-samlingen av Mesh Gradients Endast TTF- och OTF-teckensnitt kan importeras Importera teckensnitt (TTF/OTF) Exportera teckensnitt Importerade teckensnitt Fel när försöket skulle sparas, försök att ändra utdatamapp Filnamn är inte angivet Ingen Anpassade sidor Sidval Bekräftelse av verktygsutgång Om du har osparade ändringar medan du använder vissa verktyg och försöker stänga den, kommer bekräftelsedialogrutan att visas Redigera EXIF Ändra metadata för en enda bild utan omkomprimering Tryck för att redigera tillgängliga taggar Byt klistermärke Passa bredd Passa höjd Batchjämför Välj fil/filer för att beräkna dess kontrollsumma baserat på vald algoritm Välj filer Välj katalog Skala för huvudlängd Stämpel Tidsstämpel Formatera mönster Stoppning Bildskärning Klipp ut bilddelen och slå samman de vänstra (kan vara omvända) med vertikala eller horisontella linjer Vertikal svänglinje Horisontell svänglinje Omvänt urval Vertikal skuren del kommer att lämnas, istället för att slå samman delar runt klippområdet Horisontell skuren del kommer att lämnas, istället för att slå samman delar runt klippområdet Samling av meshgradienter Skapa meshgradient med anpassad mängd knutar och upplösning Mesh Gradient Overlay Komponera mesh-gradient av toppen av givna bilder Poänganpassning Rutnätsstorlek Upplösning X Upplösning Y Upplösning Pixel By Pixel Markera färg Pixeljämförelsetyp Skanna streckkoden Höjdförhållande Streckkodstyp Genomför svartvitt Streckkodsbilden kommer att vara helt svartvit och inte färgad av appens tema Skanna vilken streckkod som helst (QR, EAN, AZTEC, …) och hämta dess innehåll eller klistra in din text för att skapa en ny Ingen streckkod hittades Genererad streckkod kommer att finnas här Audio Covers Extrahera skivomslagsbilder från ljudfiler, de vanligaste formaten stöds Välj ljud för att starta Välj ljud Inga omslag hittades Skicka loggar Klicka för att dela apploggfil, detta kan hjälpa mig att upptäcka problemet och åtgärda problem Hoppsan… Något gick fel Du kan kontakta mig med hjälp av alternativen nedan så ska jag försöka hitta en lösning.\n(Glöm inte att bifoga loggar) Skriv till fil Extrahera text från en serie bilder och lagra den i en textfil Skriv till metadata Extrahera text från varje bild och placera den i EXIF-info av relativa bilder Osynligt läge Använd steganografi för att skapa osynliga vattenstämplar i dina bilder Använd LSB LSB (Less Significant Bit) steganografimetod kommer att användas, FD (Frequency Domain) annars Ta bort röda ögon automatiskt Lösenord Låsa upp PDF är skyddad Operation nästan klar. Om du avbryter nu måste du starta om den Ändrad datum Ändringsdatum (omvänt) Storlek Storlek (omvänd) MIME-typ MIME-typ (omvänd) Förlängning Förlängning (omvänd) Datum tillagt Tillagt datum (omvänt) Vänster till höger Höger till vänster Topp till botten Botten till toppen Flytande glas En switch baserad på nyligen tillkännagivna IOS 26 och dess designsystem för flytande glas Välj bild eller klistra in/importera Base64-data nedan Skriv bildlänk för att börja Klistra in länken Kalejdoskop Sekundär vinkel Sidor Kanalmix Blå grön Röd blå Grön röd Till rött In i grönt Till blått Cyan Magenta Gul Färg Halvton Kontur Nivåer Offset Voronoi kristallisera Form Sträcka Slumpmässighet Avfläcka Diffus Hund Andra radie Utjämna Glöd Virvla och nypa Pointillisera Kantfärg Polära koordinater Rekta till polar Polar till rät Invertera i cirkel Minska brus Enkel solarisering Väva X Gap Y Gap X bredd Y Bredd Snurra Gummi stämpel Smeta Densitet Blanda Sphere Lens Distortion Brytningsindex Båge Spridningsvinkel Gnistra Strålar ASCII Lutning Mary Höst Ben Jet Vinter Hav Sommar Fjädra Cool variant HSV Rosa Varm Ord Magma Inferno Plasma Viridis Medborgare Skymning Twilight Shifted Perspektiv Auto Deskew Tillåt beskärning Beskär eller perspektiv Absolut Turbo Deep Green Linskorrigering Målobjektivprofilfil i JSON-format Ladda ner färdiga linsprofiler Delprocent Exportera som JSON Kopiera sträng med en palettdata som json-representation Sömsnideri Hemskärmen Låsskärm Inbyggt Bakgrundsexport Uppdatera Skaffa aktuella hem-, lås- och inbyggda bakgrundsbilder Tillåt åtkomst till alla filer, detta behövs för att hämta bakgrundsbilder Det räcker inte med behörighet att hantera extern lagring, du måste tillåta åtkomst till dina bilder, se till att välja \"Tillåt alla\" Lägg till förinställning till filnamn Lägger till suffix med vald förinställning till bildfilnamn Lägg till bildskalningsläge till filnamn Lägger till suffix med valt bildskalningsläge till bildfilnamnet Ascii Art Konvertera bild till ascii-text som kommer att se ut som en bild Params Tillämpar negativt filter på bilden för bättre resultat i vissa fall Bearbetar skärmdump Sparandet hoppades över %1$s filer hoppade över Tillåt Hoppa över om det är större Vissa verktyg kommer att tillåtas att hoppa över att spara bilder om den resulterande filstorleken skulle vara större än originalet Kalenderhändelse Kontakta E-post Plats Telefon Text SMS URL Wi-Fi Öppet nätverk N/A SSID Telefon Meddelande Adress Ämne Kropp Namn Organisation Titel Telefoner E-postmeddelanden webbadresser Adresser Sammanfattning Beskrivning Plats Arrangör Startdatum Slutdatum Status Latitud Longitud Skapa streckkod Redigera streckkod Wi-Fi-konfiguration Säkerhet Välj kontakt Ge kontakter behörighet i inställningarna att autofylla med vald kontakt Kontaktinformation Förnamn Mellannamn Efternamn Uttal Lägg till telefon Lägg till e-post Lägg till adress Webbplats Lägg till webbplats Formaterat namn Den här bilden kommer att användas för att placera ovan streckkoden Kodanpassning Den här bilden kommer att användas som logotyp i mitten av QR-koden Logotyp Logotyp stoppning Logotypstorlek Logotyp hörn Fjärde ögat Lägger till ögonsymmetri till qr-koden genom att lägga till det fjärde ögat i det nedre hörnet Pixelform Ramform Kulform Felkorrigeringsnivå Mörk färg Ljus färg Hyper OS Xiaomi HyperOS-liknande stil Maskmönster Den här koden kanske inte går att skanna, ändra utseendeparametrar för att göra den läsbar med alla enheter Kan inte skannas Verktyg kommer att se ut som startskärmens appstartare för att vara mer kompakt Launcher-läge Fyller ett område med utvald pensel och stil Översvämningsfyllning Spray Ritar graffitistilad bana Fyrkantiga partiklar Spraypartiklar kommer att vara kvadratiska istället för cirklar Palettverktyg Generera bas/material du palett från bild, eller importera/exportera över olika palettformat Redigera palett Exportera/importera palett i olika format Färgnamn Palettnamn Palettformat Exportera genererad palett till olika format Lägger till ny färg till nuvarande palett Formatet %1$s stöder inte att ange palettnamn På grund av Play Butiks policyer kan den här funktionen inte inkluderas i den aktuella versionen. För att komma åt denna funktion, ladda ner ImageToolbox från en alternativ källa. Du kan hitta de tillgängliga versionerna på GitHub nedan. Öppna Github-sidan Originalfilen kommer att ersättas med en ny istället för att sparas i den valda mappen Upptäckt dold vattenstämpeltext Upptäckte dold vattenstämpelbild Den här bilden var gömd Generativ målning Låter dig ta bort objekt i en bild med hjälp av en AI-modell, utan att förlita dig på OpenCV. För att använda den här funktionen laddar appen ner den modell som krävs (~200 MB) från GitHub Låter dig ta bort objekt i en bild med hjälp av en AI-modell, utan att förlita dig på OpenCV. Detta kan vara en långvarig operation Felnivåanalys Luminansgradient Genomsnittligt avstånd Kopiera rörelsedetektering Behålla Koefficient Urklippsdata är för stor Data är för stor för att kopieras Enkel vävpixelisering Förskjuten pixelisering Korspixelisering Mikromakropixelisering Orbital pixelisering Vortexpixelisering Pulsrutnätspixelisering Kärnpixelisering Radiell vävpixelisering Kan inte öppna uri \"%1$s\" Snöfallsläge Aktiverad Kantram Glitch-variant Kanalskifte Max offset VHS Block Glitch Blockstorlek CRT-krökning Krökning Chroma Pixel Melt Max Drop AI-verktyg Olika verktyg för att bearbeta bilder genom ai-modeller som att ta bort artefakter eller denoising Kompression, taggiga linjer Tecknad film, sändningskomprimering Allmän kompression, allmänt brus Färglöst tecknat brus Snabb, allmän komprimering, allmänt brus, animation/serier/anime Bokskanning Exponeringskorrigering Bäst på allmän komprimering, färgbilder Bäst på allmän komprimering, gråskalebilder Allmän komprimering, gråskalebilder, starkare Allmänt brus, färgbilder Allmänt brus, färgbilder, bättre detaljer Allmänt brus, bilder i gråskala Allmänt brus, gråskalebilder, starkare Allmänt brus, gråskalebilder, starkast Allmän kompression Allmän kompression Texturisering, h264-komprimering VHS-komprimering Icke-standard komprimering (cinepak, msvideo1, roq) Bink komprimering, bättre på geometri Bink kompression, starkare Bink kompression, mjuk, behåller detaljer Eliminerar trappstegseffekten, utjämnar Skannad konst/teckningar, mild kompression, moaré Färgband Långsam, tar bort halvtoner Allmän färgsättare för gråskala/bw-bilder, för bättre resultat använd DDColor Kantborttagning Tar bort överskärpning Långsamt, vibrerande Kantutjämning, allmänna artefakter, CGI KDM003 skannar bearbetning Lätt modell för bildförbättring Borttagning av kompressionsartefakter Ta bort kompressionsartefakter Bandageborttagning med jämnt resultat Halvtonsmönsterbearbetning Borttagning av rastermönster V3 JPEG-artefaktborttagning V2 H.264 texturförbättring VHS skärpa och förbättring Sammanslagning Klumpstorlek Överlappningsstorlek Bilder över %1$s px kommer att skivas och bearbetas i bitar, överlappande blandar dessa för att förhindra synliga sömmar. Stora storlekar kan orsaka instabilitet med low-end enheter Välj en för att starta Vill du ta bort modellen %1$s? Du måste ladda ner den igen Bekräfta Modeller Nedladdade modeller Tillgängliga modeller Förbereder Aktiv modell Det gick inte att öppna sessionen Endast .onnx/.ort-modeller kan importeras Importmodell Importera anpassad onnx-modell för vidare användning, endast onnx/ort-modeller accepteras, stöder nästan alla esrgan-liknande varianter Importerade modeller Allmänt brus, färgade bilder Allmänt brus, färgade bilder, starkare Allmänt brus, färgade bilder, starkast Reducerar vibrerande artefakter och färgband, förbättrar jämna övertoningar och platta färgområden. Förbättrar bildens ljusstyrka och kontrast med balanserade högdagrar samtidigt som naturliga färger bevaras. Gör mörka bilder ljusare samtidigt som du behåller detaljer och undviker överexponering. Tar bort överdriven färgtoning och återställer en mer neutral och naturlig färgbalans. Applicerar Poisson-baserad brustoning med tonvikt på att bevara fina detaljer och texturer. Applicerar mjuk Poisson-brustoning för mjukare och mindre aggressiva visuella resultat. Enhetlig brustoning fokuserad på detaljbevarande och bildskärpa. Mild enhetlig brustoning för subtil textur och mjukt utseende. Reparerar skadade eller ojämna områden genom att måla om artefakter och förbättra bildens konsistens. Lättviktsmodell som tar bort färgband med minimal prestandakostnad. Optimerar bilder med mycket höga komprimeringsartefakter (0-20 % kvalitet) för förbättrad skärpa. Förbättrar bilder med artefakter med hög komprimering (20-40 % kvalitet), återställer detaljer och minskar brus. Förbättrar bilder med måttlig komprimering (40-60 % kvalitet), balanserar skärpa och jämnhet. Förfinar bilder med lätt komprimering (60-80 % kvalitet) för att förbättra subtila detaljer och texturer. Förbättrar nästan förlustfria bilder något (80-100 % kvalitet) samtidigt som det naturliga utseendet och detaljerna bevaras. Enkel och snabb färgsättning, tecknade serier, inte idealiskt Reducerar bildens oskärpa något, förbättrar skärpan utan att introducera artefakter. Långa operationer Bearbetar bild Bearbetning Tar bort tunga JPEG-komprimeringsartefakter i bilder med mycket låg kvalitet (0-20%). Reducerar starka JPEG-artefakter i mycket komprimerade bilder (20-40%). Rensar upp måttliga JPEG-artefakter samtidigt som bilddetaljerna bevaras (40-60%). Förfinar lätta JPEG-artefakter i bilder av ganska hög kvalitet (60-80%). Reducerar subtilt mindre JPEG-artefakter i nästan förlustfria bilder (80-100%). Förbättrar fina detaljer och texturer, förbättrar upplevd skärpa utan tunga artefakter. Bearbetningen avslutad Bearbetningen misslyckades Förbättrar hudstrukturer och detaljer samtidigt som den behåller en naturlig look, optimerad för hastighet. Tar bort JPEG-komprimeringsartefakter och återställer bildkvaliteten för komprimerade foton. Minskar ISO-brus i bilder tagna i svagt ljus och bevarar detaljer. Korrigerar överexponerade eller \"jumbo\" högdagrar och återställer bättre tonbalans. Lätt och snabb färgsättningsmodell som lägger till naturliga färger till gråskalebilder. DEJPEG Denoise Färglägg Artefakter Öka Anime Skanningar Exklusivt X4 upscaler för allmänna bilder; liten modell som använder mindre GPU och tid, med måttlig suddighet och denoise. X2 upscaler för allmänna bilder, bevara texturer och naturliga detaljer. X4 uppskalare för allmänna bilder med förbättrade texturer och realistiska resultat. X4 upscaler optimerad för animebilder; 6 RRDB-block för skarpare linjer och detaljer. X4 uppskalare med MSE-förlust, ger jämnare resultat och minskade artefakter för allmänna bilder. X4 Upscaler optimerad för animebilder; 4B32F-variant med skarpare detaljer och mjuka linjer. X4 UltraSharp V2-modell för allmänna bilder; betonar skärpa och tydlighet. X4 UltraSharp V2 Lite; snabbare och mindre, bevarar detaljer samtidigt som du använder mindre GPU-minne. Lätt modell för snabb borttagning av bakgrund. Balanserad prestanda och noggrannhet. Fungerar med porträtt, objekt och scener. Rekommenderas för de flesta användningsfall. Ta bort BG Horisontell kanttjocklek Vertikal kanttjocklek %1$s färg %1$s färger Den nuvarande modellen stöder inte chunking, bilden kommer att bearbetas i originaldimensioner, detta kan orsaka hög minnesförbrukning och problem med low-end enheter Chunking inaktiverad, bilden kommer att bearbetas i originaldimensioner, detta kan orsaka hög minnesförbrukning och problem med low-end enheter men kan ge bättre resultat vid slutledning Chunking Högnoggrann bildsegmenteringsmodell för borttagning av bakgrund Lätt version av U2Net för snabbare borttagning av bakgrund med mindre minnesanvändning. Full DDColor-modell ger högkvalitativ färgsättning för allmänna bilder med minimala artefakter. Bästa valet av alla färgsättningsmodeller. DDColor Utbildade och privata konstnärliga datamängder; ger olika och konstnärliga färgningsresultat med färre orealistiska färgartefakter. Lätt BiRefNet-modell baserad på Swin Transformer för exakt bakgrundsborttagning. Högkvalitativ bakgrundsborttagning med skarpa kanter och utmärkt bevarande av detaljer, särskilt på komplexa föremål och knepiga bakgrunder. Bakgrundsborttagningsmodell som ger exakta masker med släta kanter, lämplig för allmänna föremål och måttlig detaljbevarande. Modellen har redan laddats ned Modellen har importerats Typ Nyckelord Mycket snabb Normal Långsam Mycket långsam Beräkna procent Minsta värde är %1$s Förvräng bilden genom att rita med fingrar Varp Hårdhet Warp-läge Flytta Växa Krympa Virvla CW Snurra moturs Tona styrka Top Drop Nedre droppe Starta Drop Avsluta Drop Laddar ner Släta former Använd superellipser istället för vanliga rundade rektanglar för jämnare, mer naturliga former Form typ Skära Avrundad Jämna Skarpa kanter utan avrundning Klassiska rundade hörn Former Typ Hörnstorlek Squircle Eleganta rundade UI-element Filnamnsformat Anpassad text placerad i början av filnamnet, perfekt för projektnamn, varumärken eller personliga taggar. Använder det ursprungliga filnamnet utan förlängning, vilket hjälper dig att hålla källidentifieringen intakt. Bildbredden i pixlar, användbar för att spåra upplösningsändringar eller skalningsresultat. Bildhöjden i pixlar, användbart när du arbetar med bildförhållanden eller exporter. Genererar slumpmässiga siffror för att garantera unika filnamn; lägg till fler siffror för extra säkerhet mot dubbletter. Automatisk inkrementerande räknare för batchexport, perfekt när du sparar flera bilder i en session. Infogar det använda förinställningsnamnet i filnamnet så att du enkelt kan komma ihåg hur bilden bearbetades. Visar bildskalningsläget som används under bearbetningen, vilket hjälper till att särskilja bilder som har ändrats, beskurna eller anpassade. Anpassad text placerad i slutet av filnamnet, användbar för versionshantering som _v2, _edited eller _final. Filtillägget (png, jpg, webp, etc.), matchar automatiskt det faktiska sparade formatet. En anpassningsbar tidsstämpel som låter dig definiera ditt eget format med java-specifikation för perfekt sortering. Släng typ Android Native iOS-stil Jämn kurva Snabbt stopp Studsande Flytande Snappy Ultra Smidig Adaptiv Tillgänglighetsmedveten Minskad rörelse Inbyggd Android rullningsfysik Balanserad, mjuk rullning för allmänt bruk Högre friktion iOS-liknande rullningsbeteende Unik splinekurva för distinkt rullkänsla Exakt rullning med snabb stopp Lekfull, lyhörd studsande rullning Långa, glidande rullar för innehållssökning Snabb, lyhörd rullning för interaktiva användargränssnitt Premium mjuk rullning med utökat momentum Justerar fysiken baserat på flinghastighet Respekterar systemtillgänglighetsinställningar Minimal rörelse för tillgänglighetsbehov Primära linjer Lägger till tjockare linje var femte rad Fyllningsfärg Dolda verktyg Verktyg dolda för delning Färgbibliotek Bläddra i en stor samling färger Skärper och tar bort oskärpa från bilder med bibehållen naturliga detaljer, perfekt för att fixa ofokuserade bilder. Återställer intelligent bilder som tidigare har ändrats, och återställer förlorade detaljer och texturer. Optimerad för live-action-innehåll, minskar komprimeringsartefakter och förbättrar fina detaljer i film-/TV-programramar. Konverterar videomaterial av VHS-kvalitet till HD, tar bort bandbrus och förbättrar upplösningen samtidigt som vintagekänslan bevaras. Specialiserad för texttunga bilder och skärmdumpar, skärper tecken och förbättrar läsbarheten. Avancerad uppskalning utbildad på olika datauppsättningar, utmärkt för allmänna fotoförbättringar. Optimerad för webbkomprimerade foton, tar bort JPEG-artefakter och återställer det naturliga utseendet. Förbättrad version för webbfoton med bättre texturbevarande och artefakterminskning. 2x uppskalning med Dual Aggregation Transformer-teknik, bibehåller skärpa och naturliga detaljer. 3x uppskalning med avancerad transformatorarkitektur, perfekt för måttliga förstoringsbehov. 4x högkvalitativ uppskalning med toppmodernt transformatornätverk, bevarar fina detaljer i större skala. Tar bort oskärpa/brus och skakningar från foton. Allmänt men bäst på bilder. Återställer bilder av låg kvalitet med Swin2SR-transformator, optimerad för BSRGAN-nedbrytning. Perfekt för att fixa tunga kompressionsartefakter och förbättra detaljer i 4x skala. 4x uppskalning med SwinIR-transformator utbildad på BSRGAN-nedbrytning. Använder GAN för skarpare texturer och mer naturliga detaljer i foton och komplexa scener. Väg Slå samman PDF Kombinera flera PDF-filer till ett dokument Beställning av filer pp. Dela PDF Extrahera specifika sidor från PDF-dokument Rotera PDF Fixa sidorienteringen permanent Sidor Ordna om PDF Dra och släpp sidor för att ändra ordning på dem Håll och dra sidor Sidnummer Lägg till numrering till dina dokument automatiskt Etikettformat PDF till text (OCR) Extrahera vanlig text från dina PDF-dokument Överlägg anpassad text för varumärkesbyggande eller säkerhet Signatur Lägg till din elektroniska signatur i alla dokument Detta kommer att användas som signatur Lås upp PDF Ta bort lösenord från dina skyddade filer Skydda PDF Säkra dina dokument med stark kryptering Framgång PDF upplåst, du kan spara eller dela den Reparera PDF Försök att fixa korrupta eller oläsbara dokument Gråskala Konvertera alla dokumentinbäddade bilder till gråskala Komprimera PDF Optimera din dokumentfilstorlek för enklare delning ImageToolbox bygger om den interna korsreferenstabellen och regenererar filstrukturen från början. Detta kan återställa åtkomst till många filer som \\"inte kan öppnas\\" Detta verktyg konverterar alla dokumentbilder till gråskala. Bäst för att skriva ut och minska filstorleken Metadata Redigera dokumentegenskaper för bättre sekretess Taggar Producent Författare Nyckelord Skapare Sekretess Deep Clean Rensa all tillgänglig metadata för detta dokument Sida Djup OCR Extrahera text från dokument och lagra den i en textfil med hjälp av Tesseract-motorn Kan inte ta bort alla sidor Ta bort PDF-sidor Ta bort specifika sidor från PDF-dokument Tryck för att ta bort Manuellt Beskär PDF Beskär dokumentsidor till valfri gräns Platta till PDF Gör PDF oföränderlig genom att rastra dokumentsidor Kunde inte starta kameran. Kontrollera behörigheter och se till att den inte används av en annan app. Extrahera bilder Extrahera bilder inbäddade i PDF-filer med sin ursprungliga upplösning Denna PDF-fil innehåller inga inbäddade bilder Det här verktyget skannar varje sida och återställer källbilder i full kvalitet – perfekt för att spara original från dokument Rita signatur Pen Params Använd egen signatur som bild som ska placeras på dokument Zip PDF Dela dokument med givet intervall och packa nya dokument i zip-arkivet Intervall Skriv ut PDF Förbered dokument för utskrift med anpassad sidstorlek Sidor per ark Orientering Sidstorlek Marginal Blomma Optimerad för anime och tecknade serier. Snabb uppskalning med förbättrade naturliga färger och färre artefakter Mjukt knä Samsung One UI 7 gillar stil Ange grundläggande matematiska symboler här för att beräkna önskat värde (t.ex. (5+5)*10) Matematiskt uttryck Plocka upp till %1$s bilder Håll datum tid Bevara alltid exif-taggar relaterade till datum och tid, fungerar oberoende av keep exif-alternativet Bakgrundsfärg För Alfaformat Lägger till möjlighet att ställa in bakgrundsfärg för varje bildformat med alfa-stöd, när det är inaktiverat är detta endast tillgängligt för icke-alfa-one Fortsätt redigera ett tidigare sparat Image Toolbox-projekt Det gick inte att öppna Image Toolbox-projektet Image Toolbox-projektet saknar projektdata Image Toolbox-projektet är skadat Image Toolbox-projektversion som inte stöds: %1$d Lagra lager, bakgrund och redigeringshistorik i en redigerbar projektfil Skriv till sökbar PDF Känn igen text från bildbatch och spara sökbar PDF med bild och valbart textlager Öppet projekt Spara projekt Det gick inte att öppna Lager alfa Horisontell Vänd Vertikal flip Låsa Lägg till skugga Skuggfärg Textgeometri Sträck ut eller skeva text för skarpare stilisering Skala X Skev X Ta bort anteckningar Ta bort valda anteckningstyper som länkar, kommentarer, markeringar, former eller formulärfält från PDF-sidorna Hyperlänkar Filbilagor Rader Popup-fönster Frimärken Former Textanteckningar Textuppmärkning Formulärfält Pålägg Okänd Anteckningar Dela upp gruppen Lägg till oskärpa bakom lagret med konfigurerbar färg och offset ================================================ FILE: core/resources/src/main/res/values-ta/strings.xml ================================================ தொடங்குவதற்கு படத்தைத் தேர்ந்தெடுக்கவும் நெகிழ்வான இருங்கள் படத்தை மீட்டமைக்கவும் ரத்து செய் வண்ணம் நகலெடுக்கப்பட்டது நீலம் பகிர் வேகமான மங்கல் வரை மறைக்குறியீடு வகையின்படி குழு விருப்பங்கள் ஈமோஜியை இயக்கு முயற்சி மாற்ற வேண்டிய வண்ணம் லாக் டிரா நோக்குநிலை தட்டு பாணி வானவில் பழ சாலட் கவனம் எளிய PDF முன்னோட்டம் பேனா கொள்கலன்கள் அம்பு ஓடு முறை மீண்டும் கண்ணாடி கிளாம்ப் டெக்கால் வண்ண நிறுத்தங்கள் ஆஃப்செட் எக்ஸ் வெளியேறு அட்கின்சன் டிதெரிங் அளவு ஏதோ தவறாகிவிட்டது: %1$s அளவு %1$s ஏற்றுகிறது… படம் முன்னோட்டமிட மிகப் பெரியது, ஆனால் சேமிப்பு எப்படியும் முயற்சிக்கப்படும் அகலம் %1$s உயரம் %1$s தரம் படத்தை தேர்ந்தெடு நீட்டிப்பு மறுஅளவிடுதல் வகை வெளிப்படையானது பயன்பாட்டை மூட விரும்புகிறீர்களா? பயன்பாட்டை மூடுகிறது நெருக்கமான பட மாற்றங்கள் ஆரம்ப மதிப்புகளுக்கு திரும்பும் மதிப்புகள் சரியாக மீட்டமைக்கப்படுகின்றன மீட்டமை ஏதோ தவறு நடந்துவிட்டது பயன்பாட்டை மறுதொடக்கம் செய்யுங்கள் கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது விதிவிலக்கு EXIF ஐ திருத்து சரி EXIF தரவு எதுவும் இல்லை குறியைச் சேர்க்கவும் சேமிக்கவும் தெளிவு EXIF ஐ அழிக்கவும் அனைத்து பட EXIF தரவும் அழிக்கப்படும். இந்தச் செயலைச் செயல்தவிர்க்க முடியாது! முன்னமைவுகள் பயிர் சேமிப்பு நீங்கள் இப்போது வெளியேறினால், சேமிக்கப்படாத அனைத்து மாற்றங்களும் இழக்கப்படும் மூல குறியீடு சமீபத்திய புதுப்பிப்புகளைப் பெறவும், சிக்கல்களைப் பற்றி விவாதிக்கவும் மற்றும் பல ஒற்றை திருத்து ஒரு படத்தை மாற்றவும், மறுஅளவிடவும் திருத்தவும் வண்ண தேர்வாளர் படத்திலிருந்து வண்ணத்தைத் தேர்ந்தெடுக்கவும், நகலெடுக்கவும் அல்லது பகிரவும் படம் நிறம் எந்த எல்லைக்கும் படத்தை செதுக்குங்கள் பதிப்பு EXIF ஐ வைத்திருங்கள் படங்கள்: %d முன்னோட்டத்தை மாற்றவும் அகற்று கொடுக்கப்பட்ட படத்திலிருந்து வண்ணத் தட்டு ஸ்வாட்சை உருவாக்கவும் தட்டு உருவாக்கு தட்டு புதுப்பிக்கவும் புதிய பதிப்பு %1$s ஆதரிக்கப்படாத வகை: %1$s கொடுக்கப்பட்ட படத்திற்கான தட்டு உருவாக்க முடியாது அசல் வெளியீடு கோப்புறை குறிப்பிடப்படாதது சாதன சேமிப்பு இயல்புநிலை தனிப்பயன் எடை மூலம் மறுஅளவிடுங்கள் அதிகபட்ச அளவு KB இல் ஒப்பிடு KB இல் கொடுக்கப்பட்ட அளவைத் தொடர்ந்து படத்தின் அளவை மாற்றவும் கொடுக்கப்பட்ட இரண்டு படங்களை ஒப்பிடுக தொடங்குவதற்கு இரண்டு படங்களைத் தேர்ந்தெடுக்கவும் படங்களைத் தேர்ந்தெடுங்கள் அமைப்புகள் இருள் இரவு நிலை ஒளி அமைப்பு டைனமிக் நிறங்கள் தனிப்பயனாக்கம் படத்தை மொனெட்டை அனுமதிக்கவும் மொழி அமோல்ட் பயன்முறை இயக்கப்பட்டால், திருத்துவதற்குப் படத்தைத் தேர்ந்தெடுக்கும்போது, இந்தப் படத்திற்கு ஆப்ஸ் வண்ணங்கள் ஏற்றுக்கொள்ளப்படும் அதிர்வு வலிமை இயக்கப்பட்டால், மேற்பரப்புகளின் நிறம் இரவு பயன்முறையில் முற்றிலும் இருட்டாக அமைக்கப்படும் வண்ண திட்டம் சிவப்பு பச்சை செல்லுபடியாகும் ARGB வண்ணக் குறியீட்டை ஒட்டவும் ஒட்டுவதற்கு ஒன்றுமில்லை மாறும் வண்ணங்கள் இயக்கப்படும் போது பயன்பாட்டின் வண்ணத் திட்டத்தை மாற்ற முடியாது ஆப்ஸ் தீம் தேர்ந்தெடுக்கப்பட்ட நிறத்தின் அடிப்படையில் இருக்கும் பயன்பாட்டைப் பற்றி புதுப்பிப்புகள் எதுவும் இல்லை பிரச்சினை டிராக்கர் பிழை அறிக்கைகள் மற்றும் அம்ச கோரிக்கைகளை இங்கு அனுப்பவும் மொழிபெயர்க்க உதவுங்கள் மொழிபெயர்ப்புத் தவறுகளைச் சரிசெய்யவும் அல்லது திட்டத்தை வேறொரு மொழிக்கு மொழிபெயர்க்கவும் உங்கள் வினவலில் எதுவும் கிடைக்கவில்லை இங்கே தேடவும் இயக்கப்பட்டால், ஆப்ஸ் வண்ணங்கள் வால்பேப்பர் வண்ணங்களாக மாற்றப்படும் %d படத்தை(களை) சேமிக்க முடியவில்லை மின்னஞ்சல் முதன்மை மூன்றாம் நிலை பார்டர் தடிமன் இரண்டாம் நிலை மேற்பரப்பு மதிப்புகள் கூட்டு அனுமதி மானியம் பயன்பாட்டிற்கு படங்களைச் சேமிக்க உங்கள் சேமிப்பகத்திற்கான அணுகல் தேவை, அது அவசியம். அடுத்த உரையாடல் பெட்டியில் அனுமதி வழங்கவும். பயன்பாட்டிற்கு வேலை செய்ய இந்த அனுமதி தேவை, அதை கைமுறையாக வழங்கவும் வெளிப்புற சேமிப்பு மோனெட் நிறங்கள் இந்த பயன்பாடு முற்றிலும் இலவசம், ஆனால் நீங்கள் திட்ட வளர்ச்சியை ஆதரிக்க விரும்பினால், நீங்கள் இங்கே கிளிக் செய்யலாம் FAB சீரமைப்பு புதுப்பிப்புகளைச் சரிபார்க்கவும் இயக்கப்பட்டால், ஆப்ஸ் தொடக்கத்தில் புதுப்பிப்பு உரையாடல் உங்களுக்குக் காண்பிக்கப்படும் படத்தை பெரிதாக்கவும் முன்னொட்டு கோப்பு பெயர் ஈமோஜி பிரதான திரையில் எந்த ஈமோஜியைக் காட்ட வேண்டும் என்பதைத் தேர்ந்தெடுக்கவும் கோப்பு அளவைச் சேர்க்கவும் இயக்கப்பட்டால், சேமித்த படத்தின் அகலத்தையும் உயரத்தையும் வெளியீட்டு கோப்பின் பெயரில் சேர்க்கும் EXIF ஐ நீக்கு எந்தவொரு படத்தொகுப்பிலிருந்தும் EXIF மெட்டாடேட்டாவை நீக்கவும் பட முன்னோட்டம் எந்த வகையான படங்களையும் முன்னோட்டமிடவும்: GIF, SVG மற்றும் பல படத்தின் ஆதாரம் புகைப்படம் எடுப்பவர் கேலரி கோப்பு எக்ஸ்ப்ளோரர் எளிய கேலரி படத் தேர்வி. மீடியா பிக்கிங் வழங்கும் ஆப்ஸ் உங்களிடம் இருந்தால் மட்டுமே அது வேலை செய்யும் திரையின் அடிப்பகுதியில் தோன்றும் Android நவீன புகைப்படத் தேர்வி, android 12+ இல் மட்டுமே வேலை செய்யும். EXIF மெட்டாடேட்டாவைப் பெறுவதில் சிக்கல்கள் உள்ளன படத்தை எடுக்க GetContent நோக்கத்தைப் பயன்படுத்தவும். எல்லா இடங்களிலும் வேலை செய்யும், ஆனால் சில சாதனங்களில் தேர்ந்தெடுக்கப்பட்ட படங்களைப் பெறுவதில் சிக்கல்கள் இருப்பதாக அறியப்படுகிறது. அது என் தவறு அல்ல. விருப்பங்கள் ஏற்பாடு தொகு ஆர்டர் பிரதான திரையில் உள்ள விருப்பங்களின் வரிசையை தீர்மானிக்கிறது எமோஜிகள் எண்ணிக்கை வரிசை எண் அசல் கோப்பு பெயர் இயக்கப்பட்டால், நீங்கள் தொகுதி செயலாக்கத்தைப் பயன்படுத்தினால், நிலையான நேர முத்திரையை பட வரிசை எண்ணுக்கு மாற்றும் அசல் கோப்பு பெயரைச் சேர்க்கவும் இயக்கப்பட்டால், வெளியீட்டுப் படத்தின் பெயரில் அசல் கோப்புப் பெயரைச் சேர்க்கும் வரிசை எண்ணை மாற்றவும் ஃபோட்டோ பிக்கர் பட மூலத்தைத் தேர்ந்தெடுத்தால் அசல் கோப்புப் பெயரைச் சேர்ப்பது வேலை செய்யாது வலை பட ஏற்றுதல் நீங்கள் விரும்பினால், முன்னோட்டம், பெரிதாக்க, திருத்த மற்றும் சேமிக்க இணையத்திலிருந்து எந்தப் படத்தையும் ஏற்றவும். உருவம் இல்லை பட இணைப்பு நிரப்பவும் பொருத்தம் உள்ளடக்க அளவு கொடுக்கப்பட்ட உயரத்திற்கும் அகலத்திற்கும் படத்தை மறுஅளவிடுங்கள். படங்களின் விகித விகிதம் மாறக்கூடும். கொடுக்கப்பட்ட உயரம் அல்லது அகலத்திற்கு நீண்ட பக்கத்துடன் படத்தை மறுஅளவிடுங்கள். சேமித்த பிறகு அனைத்து அளவுகள் கணக்கீடு செய்யப்படும். படங்களின் விகித விகிதம் பாதுகாக்கப்படும். பிரகாசம் மாறுபாடு சாயல் செறிவூட்டல் வடிகட்டி சேர்க்கவும் வடிகட்டி படங்களுக்கு வடிகட்டி சங்கிலிகளைப் பயன்படுத்துங்கள் வடிப்பான்கள் நேரிடுவது ஒளி வண்ண வடிகட்டி ஆல்பா வெள்ளை சமநிலை வெப்ப நிலை சாயல் ஒரே வண்ணமுடையது காமா சிறப்பம்சங்கள் மற்றும் நிழல்கள் சிறப்பம்சங்கள் நிழல்கள் மூடுபனி விளைவு தூரம் சாய்வு கூர்மைப்படுத்து செபியா எதிர்மறை சோலரைஸ் அதிர்வு கருப்பு வெள்ளை குறுக்குவெட்டு இடைவெளி கோட்டின் அளவு சோபல் விளிம்பு தெளிவின்மை ஹால்ஃப்டோன் CGA வண்ணவெளி காஸியன் தெளிவின்மை பெட்டி மங்கலானது இருதரப்பு தெளிவின்மை புடைப்பு லாப்லாசியன் விக்னெட் தொடங்கு முடிவு குவஹாரா மென்மையாக்குதல் மங்கலான அடுக்கி ஆரம் அளவுகோல் திரித்தல் கோணம் சுழி வீக்கம் விரிவடைதல் கோள ஒளிவிலகல் ஒளிவிலகல் கண்ணாடி கோள ஒளிவிலகல் வண்ண அணி ஒளிபுகாநிலை வரம்புகளால் மறுஅளவிடுங்கள் விகித விகிதத்தை வைத்திருக்கும் போது கொடுக்கப்பட்ட உயரத்திற்கும் அகலத்திற்கும் படங்களை மறுஅளவிடுங்கள் ஓவியம் வாசல் அளவீட்டு நிலைகள் மென்மையான டூன் டூன் போஸ்டரைடு அதிகபட்ச அடக்கம் அல்ல பலவீனமான பிக்சல் சேர்க்கை தேடு கன்வல்யூஷன் 3x3 RGB வடிகட்டி தவறான நிறம் முதல் நிறம் இரண்டாவது நிறம் மறுவரிசைப்படுத்து மங்கலான அளவு மங்கலான மையம் x மங்கலான மையம் y பெரிதாக்கு தெளிவின்மை வண்ண சமநிலை ஒளிர்வு வாசல் கோப்புகள் பயன்பாட்டை முடக்கியுள்ளீர்கள், இந்த அம்சத்தைப் பயன்படுத்த அதைச் செயல்படுத்தவும் ஸ்கெட்ச்புக்கில் உள்ளதைப் போல படத்தை வரையவும் அல்லது பின்னணியிலேயே வரையவும் பெயிண்ட் நிறம் பெயிண்ட் ஆல்பா படத்தை வரையவும் ஒரு படத்தைத் தேர்ந்தெடுத்து அதில் ஏதாவது வரையவும் பின்னணியில் வரையவும் பின்னணி நிறத்தைத் தேர்ந்தெடுத்து அதன் மேல் வரையவும் பின்னணி நிறம் கிடைக்கக்கூடிய பல்வேறு மறையீட்டு வழிமுறைகளின் அடிப்படையில் எந்தவொரு கோப்பையும் (படம் மட்டுமல்ல) குறியாக்கவும் மறைகுறியாக்கவும் கோப்பைத் தேர்ந்தெடு குறியாக்கம் மறைகுறியாக்கம் தொடங்க கோப்பைத் தேர்ந்தெடுக்கவும் மறைகுறியாக்கம் குறியாக்கம் முக்கிய கோப்பு செயலாக்கப்பட்டது இந்தக் கோப்பை உங்கள் சாதனத்தில் சேமிக்கவும் அல்லது நீங்கள் விரும்பும் இடத்தில் வைக்க, பகிர்வு செயலைப் பயன்படுத்தவும் அம்சங்கள் செயல்படுத்தல் இணக்கத்தன்மை கோப்புகளின் கடவுச்சொல் அடிப்படையிலான குறியாக்கம். தொடரப்பட்ட கோப்புகளை தேர்ந்தெடுக்கப்பட்ட கோப்பகத்தில் சேமிக்கலாம் அல்லது பகிரலாம். மறைகுறியாக்கப்பட்ட கோப்புகளையும் நேரடியாக திறக்க முடியும். AES-256, GCM பயன்முறை, திணிப்பு இல்லை, 12 பைட்டுகள் சீரற்ற IV கள் இயல்பாகவே. தேவையான வழிமுறையை நீங்கள் தேர்ந்தெடுக்கலாம். விசைகள் 256-பிட் சா -3 ஆச்களாக பயன்படுத்தப்படுகின்றன கோப்பின் அளவு அதிகபட்ச கோப்பு அளவு ஆண்ட்ராய்டு OS மற்றும் கிடைக்கக்கூடிய நினைவகத்தால் கட்டுப்படுத்தப்படுகிறது, இது சாதனம் சார்ந்தது.\n தயவுசெய்து கவனிக்கவும்: நினைவகம் சேமிப்பு அல்ல. கோப்புகளை மேலெழுதவும் கோப்புகளை மேலெழுத நீங்கள் \"எக்ஸ்ப்ளோரர்\" பட மூலத்தைப் பயன்படுத்த வேண்டும், படங்களை மீண்டும் எடுக்க முயற்சிக்கவும், பட மூலத்தை தேவையானதாக மாற்றியுள்ளோம். தவறான கடவுச்சொல் அல்லது தேர்ந்தெடுக்கப்பட்ட கோப்பு குறியாக்கம் செய்யப்படவில்லை மற்ற கோப்பு குறியாக்க மென்பொருள் அல்லது சேவைகளுடன் இணக்கம் உத்தரவாதம் இல்லை என்பதை நினைவில் கொள்ளவும். சற்று வித்தியாசமான முக்கிய சிகிச்சை அல்லது சைஃபர் உள்ளமைவு இணக்கமின்மையை ஏற்படுத்தலாம். கொடுக்கப்பட்ட அகலம் மற்றும் உயரத்துடன் படத்தை சேமிக்க முயற்சிப்பது நினைவக பிழையை ஏற்படுத்தக்கூடும். இதை உங்கள் சொந்த ஆபத்தில் செய்யுங்கள். தற்காலிக சேமிப்பு கேச் அளவு கிடைத்தது %1$s தானியங்கு கேச் அழிக்கும் இயக்கப்பட்டால், ஆப்ஸ் துவக்கத்தில் ஆப் கேச் அழிக்கப்படும் உருவாக்கு கருவிகள் தனிப்பயன் பட்டியல் ஏற்பாட்டிற்குப் பதிலாக முதன்மைத் திரையில் அவற்றின் வகையின்படி குழுக்கள் விருப்பங்கள் விருப்பக் குழுவாக்கம் இயக்கப்பட்டிருக்கும் போது ஏற்பாட்டை மாற்ற முடியாது ஸ்கிரீன்ஷாட்டைத் திருத்து இரண்டாம் நிலை தனிப்பயனாக்கம் ஸ்கிரீன்ஷாட் ஃபால்பேக் விருப்பம் தவிர்க்கவும் நகலெடுக்கவும் %1$s பயன்முறையில் சேமிப்பது நிலையற்றதாக இருக்கலாம், ஏனெனில் இது இழப்பற்ற வடிவமாகும் நீங்கள் முன்னமைக்கப்பட்ட 125 ஐத் தேர்ந்தெடுத்திருந்தால், அசல் படத்தின் 125% அளவு 100% தரத்துடன் படம் சேமிக்கப்படும். நீங்கள் முன்னமைக்கப்பட்ட 50 ஐ தேர்வு செய்தால், படம் 50% அளவு மற்றும் 50% தரத்துடன் சேமிக்கப்படும். முன்னமைக்கப்பட்ட இங்கே வெளியீட்டு கோப்பின் % ஐ தீர்மானிக்கிறது, அதாவது 5 எம்பி படத்தில் 50 முன்னமைக்கப்பட்ட 50 ஐத் தேர்ந்தெடுத்தால், சேமித்த பிறகு 2,5 எம்பி படத்தைப் பெறுவீர்கள் கோப்பு பெயரை சீரற்றதாக்கு இயக்கப்பட்டால், வெளியீட்டு கோப்பு பெயர் முற்றிலும் சீரற்றதாக இருக்கும் %2$s என்ற பெயருடன் %1$s கோப்புறையில் சேமிக்கப்பட்டது %1$s கோப்புறையில் சேமிக்கப்பட்டது தந்தி அரட்டை பயன்பாட்டைப் பற்றி விவாதித்து பிற பயனர்களிடமிருந்து கருத்துகளைப் பெறுங்கள். நீங்கள் பீட்டா புதுப்பிப்புகள் மற்றும் நுண்ணறிவுகளையும் அங்கு பெறலாம். பயிர் முகமூடி விகிதம் கொடுக்கப்பட்ட படத்திலிருந்து முகமூடியை உருவாக்க இந்த மாஸ்க் வகையைப் பயன்படுத்தவும், அதில் ஆல்பா சேனல் இருக்க வேண்டும் என்பதைக் கவனியுங்கள் காப்பு மற்றும் மீட்பு காப்புப்பிரதி மீட்டமை உங்கள் பயன்பாட்டு அமைப்புகளை ஒரு கோப்பில் காப்புப் பிரதி எடுக்கவும் முன்பு உருவாக்கப்பட்ட கோப்பிலிருந்து பயன்பாட்டு அமைப்புகளை மீட்டமைக்கவும் சிதைந்த கோப்பு அல்லது காப்புப்பிரதி இல்லை அமைப்புகள் வெற்றிகரமாக மீட்டெடுக்கப்பட்டன என்னை தொடர்பு கொள் இது உங்கள் அமைப்புகளை இயல்புநிலை மதிப்புகளுக்கு திரும்பும். மேலே குறிப்பிட்டுள்ள காப்பு கோப்பு இல்லாமல் இதை செயல்தவிர்க்க முடியாது என்பதைக் கவனியுங்கள். அழி தேர்ந்தெடுக்கப்பட்ட வண்ணத் திட்டத்தை நீக்க உள்ளீர்கள். இந்தச் செயல்பாட்டைச் செயல்தவிர்க்க முடியாது திட்டத்தை நீக்கு எழுத்துரு உரை எழுத்துரு அளவு இயல்புநிலை பெரிய எழுத்துரு அளவுகோல்களைப் பயன்படுத்துவது UI குறைபாடுகள் மற்றும் சிக்கல்களை ஏற்படுத்தலாம், அவை சரி செய்யப்படாது. எச்சரிக்கையுடன் பயன்படுத்தவும். அ ஆ இ ஈ உ ஊ எ ஏ ஐ ஒ ஓ ஔ க ங ச ஞ ட ண த ந ப ம ய ர ல வ ழ ள ற ன ஃ 0123456789 !? உணர்ச்சிகள் உணவு மற்றும் பானம் இயற்கை மற்றும் விலங்குகள் பொருள்கள் சின்னங்கள் பயணங்கள் மற்றும் இடங்கள் செயல்பாடுகள் பின்னணி நீக்கி வரைவதன் மூலம் படத்திலிருந்து பின்னணியை அகற்றவும் அல்லது தானியங்கு விருப்பத்தைப் பயன்படுத்தவும் படத்தை ஒழுங்கமைக்கவும் அசல் பட மெட்டாடேட்டா வைக்கப்படும் படத்தைச் சுற்றியுள்ள வெளிப்படையான இடைவெளிகள் குறைக்கப்படும் பின்னணியை தானாக அழித்தல் படத்தை மீட்டமை அழித்தல் முறை பின்னணியை அழிக்கவும் பின்னணியை மீட்டமை மங்கலான ஆரம் குழாய் வரைதல் முறை சிக்கலை உருவாக்கவும் அச்சச்சோ… ஏதோ தவறாகிவிட்டது. கீழே உள்ள விருப்பங்களைப் பயன்படுத்தி நீங்கள் எனக்கு எழுதலாம், நான் தீர்வு காண முயற்சிப்பேன் அளவை மாற்றி மாற்றவும் கொடுக்கப்பட்ட படங்களின் அளவை மாற்றவும் அல்லது வேறு வடிவங்களுக்கு மாற்றவும். ஒரு படத்தை எடுத்தால் EXIF மெட்டாடேட்டாவையும் இங்கே திருத்தலாம். அதிகபட்ச வண்ண எண்ணிக்கை இது செயலிழப்பு அறிக்கைகளை தானாக சேகரிக்க பயன்பாட்டை அனுமதிக்கிறது பகுப்பாய்வு அநாமதேய பயன்பாட்டு புள்ளிவிவரங்களைச் சேகரிக்க அனுமதிக்கவும் தேர்ந்தெடுக்கப்பட்ட கோப்புறையில் சேமிப்பதற்குப் பதிலாக அசல் கோப்பு புதியதாக மாற்றப்படும், இந்த விருப்பத்திற்கு பட ஆதாரம் \"Explorer\" அல்லது GetContent ஆக இருக்க வேண்டும், இதை மாற்றும்போது, தானாகவே அமைக்கப்படும் காலியாக பின்னொட்டு அளவீட்டு முறை பிலினியர் கேட்முல் பைகுபிக் ஹான் துறவி லான்சோஸ் மிட்செல் அருகில் ஸ்ப்லைன் அடிப்படை இயல்புநிலை மதிப்பு நேரியல் (அல்லது பிலினியர், இரு பரிமாணங்களில்) இடைக்கணிப்பு பொதுவாக ஒரு படத்தின் அளவை மாற்றுவதற்கு நல்லது, ஆனால் சில விரும்பத்தகாத விவரங்களை மென்மையாக்குகிறது மற்றும் இன்னும் ஓரளவு துண்டிக்கப்படலாம். சிறந்த அளவிடுதல் முறைகளில் லான்சோஸ் மறு மாதிரி மற்றும் மிட்செல்-நேத்ராவலி வடிகட்டிகள் அடங்கும் அளவை அதிகரிப்பதற்கான எளிய வழிகளில் ஒன்று, ஒவ்வொரு பிக்சலையும் ஒரே நிறத்தின் பல பிக்சல்களுடன் மாற்றுகிறது கிட்டத்தட்ட எல்லா பயன்பாடுகளிலும் பயன்படுத்தப்படும் எளிமையான ஆண்ட்ராய்டு அளவிடுதல் பயன்முறை மென்மையான வளைவுகளை உருவாக்க கணினி வரைகலைகளில் பொதுவாகப் பயன்படுத்தப்படும் கட்டுப்பாட்டுப் புள்ளிகளின் தொகுப்பை சீராக இடைக்கணித்து மறு மாதிரியாக்குவதற்கான முறை ஸ்பெக்ட்ரல் கசிவைக் குறைக்கவும், சிக்னலின் விளிம்புகளைத் தட்டுவதன் மூலம் அதிர்வெண் பகுப்பாய்வின் துல்லியத்தை மேம்படுத்தவும் சிக்னல் செயலாக்கத்தில் சாளர செயல்பாடு அடிக்கடி பயன்படுத்தப்படுகிறது. ஒரு மென்மையான மற்றும் தொடர்ச்சியான வளைவை உருவாக்க ஒரு வளைவுப் பிரிவின் இறுதிப் புள்ளிகளில் மதிப்புகள் மற்றும் வழித்தோன்றல்களைப் பயன்படுத்தும் கணித இடைக்கணிப்பு நுட்பம் பிக்சல் மதிப்புகளுக்கு எடையுள்ள சின்க் செயல்பாட்டைப் பயன்படுத்துவதன் மூலம் உயர்தர இடைக்கணிப்பைப் பராமரிக்கும் மறு மாதிரி முறை அளவிடப்பட்ட படத்தில் கூர்மை மற்றும் மாற்றுப்பெயர்ப்புக்கு இடையில் சமநிலையை அடைய சரிசெய்யக்கூடிய அளவுருக்கள் கொண்ட கன்வல்யூஷன் ஃபில்டரைப் பயன்படுத்தும் மறு மாதிரி முறை ஒரு வளைவு அல்லது மேற்பரப்பை சீராக இடைக்கணிக்கவும் தோராயமாகவும், நெகிழ்வான மற்றும் தொடர்ச்சியான வடிவ பிரதிநிதித்துவத்தை வழங்குகிறது கிளிப் மட்டும் சேமிப்பகத்தில் சேமிக்கப்படாது, மேலும் படம் கிளிப்போர்டில் மட்டும் வைக்க முயற்சிக்கும் அழிப்பதற்கு பதிலாக பின்னணியை தூரிகை மீட்டெடுக்கும் OCR (உரையை அங்கீகரிக்கவும்) கொடுக்கப்பட்ட படத்திலிருந்து உரையை அங்கீகரிக்கவும், 120+ மொழிகள் ஆதரிக்கப்படுகின்றன படத்தில் உரை இல்லை அல்லது ஆப்ஸ் அதைக் கண்டுபிடிக்கவில்லை துல்லியம்: %1$s அங்கீகார வகை வேகமாக தரநிலை சிறந்த தகவல் இல்லை Tesseract OCR சரியாகச் செயல்பட, கூடுதல் பயிற்சித் தரவு (%1$s) உங்கள் சாதனத்தில் பதிவிறக்கம் செய்யப்பட வேண்டும். \nநீங்கள் %2$s தரவைப் பதிவிறக்க விரும்புகிறீர்களா? பதிவிறக்க Tamil இணைப்பு இல்லை, அதைச் சரிபார்த்து, ரயில் மாடல்களைப் பதிவிறக்க மீண்டும் முயற்சிக்கவும் பதிவிறக்கம் செய்யப்பட்ட மொழிகள் கிடைக்கும் மொழிகள் பிரிவு முறை பிக்சல் சுவிட்சைப் பயன்படுத்தவும் கூகிள் படப்புள்ளி போன்ற சுவிட்சைப் பயன்படுத்துகிறது அசல் இலக்கில் %1$s என்ற பெயரில் மேலெழுதப்பட்ட கோப்பு உருப்பெருக்கி சிறந்த அணுகலுக்காக வரைதல் முறைகளில் விரலின் மேற்பகுதியில் உருப்பெருக்கியை இயக்குகிறது ஆரம்ப மதிப்பை கட்டாயப்படுத்தவும் exif விட்ஜெட்டை முதலில் சரிபார்க்க வேண்டும் பல மொழிகளை அனுமதிக்கவும் ஸ்லைடு சைட் பை சைட் தட்டுவதை நிலைமாற்று வெளிப்படைத்தன்மை பயன்பாட்டை மதிப்பிடவும் மதிப்பிடவும் இந்த ஆப்ஸ் முற்றிலும் இலவசம், இது பெரியதாக மாற விரும்பினால், தயவுசெய்து கிதுப்பில் ப்ராஜெக்ட்டை நட்சத்திரமிடுங்கள் 😄 ஓரியண்டேஷன் & ஸ்கிரிப்ட் கண்டறிதல் மட்டும் தானியங்கு நோக்குநிலை & ஸ்கிரிப்ட் கண்டறிதல் ஆட்டோ மட்டும் ஆட்டோ ஒற்றை நெடுவரிசை ஒற்றைத் தொகுதி செங்குத்து உரை ஒற்றைத் தொகுதி ஒற்றை வரி ஒற்றை வார்த்தை வட்டச் சொல் ஒற்றை எழுத்து அரிதான உரை அரிதான உரை நோக்குநிலை & ஸ்கிரிப்ட் கண்டறிதல் மூல வரி அனைத்து அங்கீகார வகைகளுக்கான மொழி \"%1$s\" OCR பயிற்சி தரவை நீக்க விரும்புகிறீர்களா அல்லது தேர்ந்தெடுக்கப்பட்ட ஒன்றிற்கு மட்டும் (%2$s) நீக்க வேண்டுமா? தற்போதைய அனைத்து கிரேடியன்ட் மேக்கர் தனிப்பயனாக்கப்பட்ட வண்ணங்கள் மற்றும் தோற்ற வகையுடன் கொடுக்கப்பட்ட வெளியீட்டு அளவின் சாய்வை உருவாக்கவும் நேரியல் ரேடியல் துடைக்கவும் சாய்வு வகை மையம் எக்ஸ் மையம் ஒய் வண்ணத்தைச் சேர்க்கவும் பண்புகள் பிரகாசம் அமலாக்கம் திரை சாய்வு மேலடுக்கு கொடுக்கப்பட்ட படங்களின் மேல் எந்த சாய்வையும் எழுதுங்கள் உருமாற்றங்கள் புகைப்பட கருவி கேமராவுடன் படம் எடுக்கவும். இந்த பட மூலத்திலிருந்து ஒரே ஒரு படத்தை மட்டுமே பெற முடியும் என்பதை நினைவில் கொள்க வாட்டர்மார்க்கிங் தனிப்பயனாக்கக்கூடிய உரை/பட வாட்டர்மார்க்ஸுடன் படங்களை கவர் வாட்டர்மார்க் செய்யவும் கொடுக்கப்பட்ட நிலையில் ஒற்றைக்கு பதிலாக படத்தின் மீது வாட்டர்மார்க் மீண்டும் செய்கிறது ஆஃப்செட் ஒய் வாட்டர்மார்க் வகை இந்தப் படம் வாட்டர்மார்க்கிங்கிற்கான மாதிரியாகப் பயன்படுத்தப்படும் உரை நிறம் மேலடுக்கு முறை GIF கருவிகள் படங்களை GIF படமாக மாற்றவும் அல்லது கொடுக்கப்பட்ட GIF படத்திலிருந்து பிரேம்களைப் பிரித்தெடுக்கவும் படங்களுக்கு GIF GIF கோப்பை படங்களின் தொகுப்பாக மாற்றவும் படங்களின் தொகுப்பை GIF கோப்பாக மாற்றவும் GIFக்கு படங்கள் தொடங்க GIF படத்தைத் தேர்ந்தெடுக்கவும் முதல் சட்டத்தின் அளவைப் பயன்படுத்தவும் குறிப்பிட்ட அளவை முதல் சட்ட பரிமாணங்களுடன் மாற்றவும் மீண்டும் எண்ணிக்கை பிரேம் தாமதம் மில்லி Fps லாசோவைப் பயன்படுத்தவும் அழிப்பதைச் செய்ய வரைதல் முறையில் Lassoவைப் பயன்படுத்துகிறது அசல் பட முன்னோட்டம் ஆல்பா கான்ஃபெட்டி கான்ஃபெட்டி சேமிப்பு, பகிர்தல் மற்றும் பிற முதன்மை செயல்களில் காட்டப்படும் பாதுகாப்பான பயன்முறை அண்மைக் கால பயன்பாடுகளில் பயன்பாட்டு உள்ளடக்கத்தை மறைக்கிறது. அதை கைப்பற்றவோ பதிவு செய்யவோ முடியாது. நீங்கள் இப்போது முன்னோட்டத்தை விட்டுவிட்டால், படங்களை மீண்டும் சேர்க்க வேண்டும் டித்தரிங் குவாண்டிசியர் கிரே ஸ்கேல் பேயர் டூ பை டூ டித்தரிங் பேயர் த்ரீ பை த்ரீ டித்தரிங் பேயர் ஃபோர் பை ஃபோர் டித்தரிங் பேயர் எயிட் பை எய்ட் டித்தரிங் ஃபிலாய்ட் ஸ்டெய்ன்பெர்க் டித்தரிங் ஜார்விஸ் ஜூடிஸ் நின்கே டிதெரிங் சியரா டித்தரிங் இரண்டு வரிசை சியரா டித்தரிங் சியரா லைட் டித்தரிங் ஸ்டக்கி டித்தரிங் பர்க்ஸ் டித்தரிங் தவறான ஃபிலாய்ட் ஸ்டீன்பெர்க் டித்தரிங் லெஃப்ட் டு ரைட் டைதரிங் ரேண்டம் டித்தரிங் எளிய த்ரெஷோல்ட் டித்தரிங் சிக்மா இடஞ்சார்ந்த சிக்மா சராசரி தெளிவின்மை பி ஸ்ப்லைன் நேட்டிவ் ஸ்டாக் மங்கலானது ஒரு வளைவு அல்லது மேற்பரப்பை, நெகிழ்வான மற்றும் தொடர்ச்சியான வடிவ பிரதிநிதித்துவத்தை சீராக இடைக்கணித்து தோராயமாக்க, துண்டு துண்டாக வரையறுக்கப்பட்ட பைகுபிக் பல்லுறுப்புக்கோவை செயல்பாடுகளைப் பயன்படுத்துகிறது. டில்ட் ஷிப்ட் தடுமாற்றம் தொகை விதை அனாக்லிஃப் சத்தம் பிக்சல் வரிசை கலக்கு மேம்படுத்தப்பட்ட தடுமாற்றம் சேனல் ஷிப்ட் எக்ஸ் சேனல் ஷிப்ட் ஒய் ஊழல் அளவு ஊழல் மாற்றம் X ஊழல் மாற்றம் ஒய் கூடாரம் மங்கலானது சைட் ஃபேட் பக்கம் மேல் கீழே வலிமை ஈரோடு அனிசோட்ரோபிக் பரவல் பரவல் நடத்துதல் கிடைமட்ட காற்று ஸ்டாக்கர் வேகமான இருதரப்பு மங்கலானது பாய்சன் மங்கலானது மடக்கை டோன் மேப்பிங் ACES ஃபிலிமிக் டோன் மேப்பிங் படிகமாக்கு பக்கவாதம் நிறம் ஃப்ராக்டல் கண்ணாடி வீச்சு பளிங்கு கொந்தளிப்பு எண்ணெய் நீர் விளைவு அதிர்வெண் X அதிர்வெண் ஒய் அலைவீச்சு X அலைவீச்சு ஒய் பெர்லின் சிதைவு ACES ஹில் டோன் மேப்பிங் ஹேபிள் ஃபிலிமிக் டோன் மேப்பிங் எசி-பர்கச் டோன் மேப்பிங் வேகம் டீஹேஸ் ஒமேகா கலர் மேட்ரிக்ஸ் 4x4 கலர் மேட்ரிக்ஸ் 3x3 எளிய விளைவுகள் போலராய்டு டிரிடோனோமலி டியூடரோமலி புரோட்டோனோமலி விண்டேஜ் பிரவுனி கோடா குரோம் இரவு பார்வை சூடான குளிர் டிரிடானோபியா டியூட்டரனோபியா புரோட்டானோபியா அக்ரோமடோமலி அக்ரோமடோப்சியா தானியம் கூர்மை நீக்கவும் வெளிர் ஆரஞ்சு மூட்டம் இளஞ்சிவப்பு கனவு கோல்டன் ஹவர் சூடான கோடை ஊதா நிற மூடுபனி சூரிய உதயம் வண்ணமயமான சுழல் மென்மையான வசந்த ஒளி இலையுதிர் டோன்கள் லாவெண்டர் கனவு சைபர்பங்க் லெமனேட் விளக்கு ஸ்பெக்ட்ரல் தீ இரவு மந்திரம் பேண்டஸி நிலப்பரப்பு வண்ண வெடிப்பு மின்சார சாய்வு கேரமல் இருள் எதிர்கால சாய்வு பச்சை சூரியன் ரெயின்போ உலகம் அடர் ஊதா விண்வெளி போர்டல் சிவப்பு சுழல் டிஜிட்டல் குறியீடு பொக்கே ஆப் பார் ஈமோசி தோராயமாக மாறும் சீரற்ற எமோஜிகள் ஈமோசிகள் முடக்கப்பட்டிருக்கும் போது நீங்கள் சீரற்ற ஈமோசிகளைப் பயன்படுத்த முடியாது சீரற்ற ஈமோசிகள் இயக்கப்பட்டிருக்கும்போது நீங்கள் ஒரு ஈமோசியைத் தேர்ந்தெடுக்க முடியாது பழைய டி.வி கலக்கு மங்கல் பிடித்தது பிடித்த வடிப்பான்கள் எதுவும் இதுவரை சேர்க்கப்படவில்லை பட வடிவம் ஐகான்களின் கீழ் தேர்ந்தெடுக்கப்பட்ட வடிவத்துடன் ஒரு கொள்கலனைச் சேர்க்கிறது ஐகான் வடிவம் டிராகோ ஆல்ட்ரிட்ஜ் கட்ஆஃப் உச்சிமுரா மொபியஸ் மாற்றம் உச்சம் வண்ண முரண்பாடு அசல் இலக்கில் மேலெழுதப்பட்ட படங்கள் கோப்புகளை மேலெழுதும் விருப்பம் இயக்கப்பட்டிருக்கும் போது பட வடிவமைப்பை மாற்ற முடியாது வண்ணத் திட்டமாக ஈமோஜி கைமுறையாக வரையறுக்கப்பட்டதற்குப் பதிலாக ஈமோஜி முதன்மை வண்ணத்தை பயன்பாட்டு வண்ணத் திட்டமாகப் பயன்படுத்துகிறது தற்போது, ஆண்ட்ராய்டில் EXIF மெட்டாடேட்டாவைப் படிக்க மட்டுமே %1$s வடிவம் அனுமதிக்கிறது. அவுட்புட் படத்தில் சேமிக்கப்படும் போது மெட்டாடேட்டா இருக்காது. %1$s இன் மதிப்பு என்பது வேகமான சுருக்கத்தைக் குறிக்கிறது, இதன் விளைவாக ஒப்பீட்டளவில் பெரிய கோப்பு அளவு இருக்கும். %2$s என்பது மெதுவான சுருக்கத்தைக் குறிக்கிறது, இதன் விளைவாக சிறிய கோப்பு கிடைக்கும். காத்திரு சேமிப்பு கிட்டத்தட்ட முடிந்தது. இப்போது ரத்துசெய்ய மீண்டும் சேமிக்க வேண்டும். புதுப்பிப்புகள் பீட்டாக்களை அனுமதிக்கவும் இயக்கப்பட்டால், புதுப்பிப்புச் சரிபார்ப்பில் பீட்டா ஆப்ஸ் பதிப்புகள் இருக்கும் அம்புகளை வரையவும் செயல்படுத்தப்பட்டால், வரைதல் பாதை சுட்டிக்காட்டும் அம்புக்குறியாகக் காட்டப்படும் தூரிகை மென்மை உள்ளிடப்பட்ட அளவிற்கு படங்கள் மையச் செதுக்கப்படும். உள்ளிடப்பட்ட பரிமாணங்களை விட படம் சிறியதாக இருந்தால், கொடுக்கப்பட்ட பின்னணி வண்ணத்துடன் கேன்வாஸ் விரிவாக்கப்படும். தானம் பட தையல் ஒரு பெரிய படத்தைப் பெற கொடுக்கப்பட்ட படங்களை இணைக்கவும் குறைந்தது 2 படங்களைத் தேர்ந்தெடுக்கவும் வெளியீட்டு பட அளவு பட நோக்குநிலை கிடைமட்ட செங்குத்து சிறிய படங்களை பெரியதாக அளவிடவும் இயக்கப்பட்டால், சிறிய படங்கள் வரிசையில் பெரியதாக அளவிடப்படும் படங்கள் வரிசை வழக்கமான மங்கலான விளிம்புகள் இயக்கப்பட்டிருந்தால் ஒற்றை நிறத்திற்குப் பதிலாக அதைச் சுற்றியுள்ள இடைவெளிகளை நிரப்ப அசல் படத்தின் கீழ் மங்கலான விளிம்புகளை வரைகிறது பிக்ஸலேஷன் மேம்படுத்தப்பட்ட பிக்சலேஷன் ஸ்ட்ரோக் பிக்ஸலேஷன் மேம்படுத்தப்பட்ட டயமண்ட் பிக்ஸலேஷன் டயமண்ட் பிக்ஸலேஷன் வட்டம் பிக்சலேஷன் மேம்படுத்தப்பட்ட வட்ட பிக்ஸலேஷன் நிறத்தை மாற்றவும் சகிப்புத்தன்மை இலக்கு நிறம் நீக்க வேண்டிய வண்ணம் நிறத்தை அகற்று மறுகுறியீடு பிக்சல் அளவு வரைதல் பயன்முறையில் இயக்கப்பட்டால், திரை சுழலாது புதுப்பிப்புகளைச் சரிபார்க்கவும் நடுநிலை டோனல் ஸ்பாட் துடிப்பான வெளிப்படுத்தும் விசுவாசம் உள்ளடக்கம் இயல்புநிலை தட்டு பாணி, இது நான்கு வண்ணங்களையும் தனிப்பயனாக்க அனுமதிக்கிறது, மற்றவை முக்கிய வண்ணத்தை மட்டுமே அமைக்க உங்களை அனுமதிக்கின்றன மோனோக்ரோமை விட சற்றே கூடுதலான நிறமுடைய ஒரு பாணி உரத்த தீம், முதன்மை தட்டுக்கு வண்ணமயமானது அதிகபட்சம், மற்றவர்களுக்கு அதிகரித்தது ஒரு விளையாட்டுத்தனமான தீம் - மூல நிறத்தின் சாயல் தீமில் தோன்றாது ஒரே வண்ணமுடைய தீம், நிறங்கள் முற்றிலும் கருப்பு / வெள்ளை / சாம்பல் ஆகும் Scheme.primaryContainer இல் மூல நிறத்தை வைக்கும் திட்டம் உள்ளடக்கத் திட்டத்துடன் மிகவும் ஒத்த திட்டம் இந்த புதுப்பிப்பு சரிபார்ப்பு புதிய புதுப்பிப்பு உள்ளதா என்பதைச் சரிபார்க்கும் காரணத்திற்காக GitHub உடன் இணைக்கப்படும் மங்கலான விளிம்புகள் முடக்கப்பட்டது தேடு இரண்டும் தலைகீழாக நிறங்கள் இயக்கப்பட்டிருந்தால் தீம் வண்ணங்களை எதிர்மறையாக மாற்றும் முதன்மைத் திரையில் கிடைக்கக்கூடிய அனைத்து விருப்பங்களையும் தேடும் திறனை செயல்படுத்துகிறது PDF கருவிகள் PDF கோப்புகளுடன் இயக்கவும்: முன்னோட்டம், படங்களின் தொகுப்பாக மாற்றவும் அல்லது கொடுக்கப்பட்ட படங்களிலிருந்து ஒன்றை உருவாக்கவும் PDF மாதிரிக்காட்சி படங்களுக்கு PDF படங்கள் PDFக்கு கொடுக்கப்பட்ட வெளியீட்டு வடிவத்தில் PDF ஐ படங்களாக மாற்றவும் கொடுக்கப்பட்ட படங்களை அவுட்புட் PDF கோப்பில் தொகுக்கவும் மாஸ்க் வடிகட்டி கொடுக்கப்பட்ட முகமூடி பகுதிகளில் வடிகட்டி சங்கிலிகளைப் பயன்படுத்துங்கள், ஒவ்வொரு முகமூடி பகுதியும் அதன் சொந்த வடிப்பான்களைத் தீர்மானிக்க முடியும் முகமூடிகள் முகமூடியைச் சேர்க்கவும் முகமூடி %d முகமூடி நிறம் முகமூடி முன்னோட்டம் தோராயமான முடிவை உங்களுக்குக் காட்ட வரையப்பட்ட வடிகட்டி முகமூடி ரெண்டர் செய்யப்படும் தலைகீழ் நிரப்பு வகை இயக்கப்பட்டால், முகமூடி இல்லாத பகுதிகள் அனைத்தும் இயல்பு நடத்தைக்குப் பதிலாக வடிகட்டப்படும் தேர்ந்தெடுக்கப்பட்ட வடிகட்டி முகமூடியை நீக்க உள்ளீர்கள். இந்தச் செயல்பாட்டைச் செயல்தவிர்க்க முடியாது முகமூடியை நீக்கு முழு வடிகட்டி கொடுக்கப்பட்ட படங்கள் அல்லது ஒற்றைப் படத்திற்கு ஏதேனும் வடிகட்டி சங்கிலிகளைப் பயன்படுத்தவும் தொடங்கு மையம் முடிவு எளிய மாறுபாடுகள் ஹைலைட்டர் நியான் தனியுரிமை மங்கல் அரை-வெளிப்படையான கூர்மையான ஹைலைட்டர் பாதைகளை வரையவும் உங்கள் வரைபடங்களில் சில ஒளிரும் விளைவைச் சேர்க்கவும் இயல்புநிலை ஒன்று, எளிமையானது - நிறம் மட்டுமே நீங்கள் மறைக்க விரும்பும் எதையும் பாதுகாக்க, வரையப்பட்ட பாதையின் கீழ் படத்தை மங்கலாக்கும் தனியுரிமை மங்கலைப் போன்றது, ஆனால் மங்கலாக்குவதற்குப் பதிலாக பிக்சலேட்டுகள் கொள்கலன்களுக்குப் பின்னால் ஒரு நிழலை வரையவும் ஸ்லைடர்கள் மாறுகிறது FABகள் பொத்தான்கள் ச்லைடர்களுக்குப் பின்னால் ஒரு நிழலை வரையவும் சுவிட்சுகளுக்குப் பின்னால் ஒரு நிழலை வரையவும் மிதக்கும் செயல் பொத்தான்களுக்குப் பின்னால் ஒரு நிழலை வரையவும் பொத்தான்களுக்குப் பின்னால் ஒரு நிழலை வரையவும் பயன்பாட்டு பார்கள் பயன்பாட்டு பார்களுக்குப் பின்னால் ஒரு நிழலை வரையவும் வரம்பில் உள்ள மதிப்பு %1$s - %2$s தானாக சுழற்று பட நோக்குநிலைக்கு வரம்புப் பெட்டியை ஏற்றுக்கொள்ள அனுமதிக்கிறது பாதை பயன்முறையை வரையவும் இரட்டை வரி அம்பு இலவச வரைதல் இரட்டை அம்பு வரி அம்பு வரி உள்ளீட்டு மதிப்பாக பாதையை வரைகிறது தொடக்கப் புள்ளியிலிருந்து இறுதிப் புள்ளி வரை பாதையை ஒரு கோடாக வரைகிறது தொடக்கப் புள்ளியிலிருந்து இறுதிப் புள்ளி வரை அம்புக்குறியை ஒரு கோட்டாக வரைகிறது கொடுக்கப்பட்ட பாதையிலிருந்து சுட்டிக்காட்டும் அம்புக்குறியை ஈர்க்கிறது தொடக்கப் புள்ளியிலிருந்து இறுதிப் புள்ளி வரை இரட்டைச் சுட்டி அம்புக்குறியை ஒரு கோட்டாக வரைகிறது கோடிட்ட ஓவல் கொடுக்கப்பட்ட பாதையிலிருந்து இரட்டை சுட்டி அம்புக்குறியை வரைகிறது கோடிட்ட ரெக்ட் ஓவல் ரெக்ட் தொடக்கப் புள்ளியிலிருந்து இறுதிப் புள்ளி வரை நேராக வரைகிறது தொடக்கப் புள்ளியிலிருந்து இறுதிப் புள்ளி வரை ஓவல் வரைகிறது தொடக்கப் புள்ளியிலிருந்து இறுதிப் புள்ளி வரை கோடிட்ட ஓவல் வரைகிறது தொடக்கப் புள்ளியிலிருந்து இறுதிப் புள்ளி வரை கோடிட்டுக் காட்டப்பட்ட வளைவை வரைகிறது லாஸ்ஸோ கொடுக்கப்பட்ட பாதையின் மூலம் மூடிய நிரப்பப்பட்ட பாதையை வரைகிறது இலவசம் கிடைமட்ட கட்டம் செங்குத்து கட்டம் தையல் முறை வரிசைகளின் எண்ணிக்கை நெடுவரிசைகளின் எண்ணிக்கை \"%1$s\" கோப்பகம் இல்லை, அதை இயல்புநிலைக்கு மாற்றியுள்ளோம், கோப்பை மீண்டும் சேமிக்கவும் கிளிப்போர்டு ஆட்டோ பின் இயக்கப்பட்டிருந்தால், தானாகவே சேமிக்கப்பட்ட படத்தை கிளிப்போர்டில் சேர்க்கும் அதிர்வு படத்திலிருந்து \"Material You\" தட்டு உருவாக்குகிறது இருண்ட நிறங்கள் ஒளி மாறுபாட்டிற்கு பதிலாக இரவு பயன்முறை வண்ணத் திட்டத்தைப் பயன்படுத்துகிறது \"Jetpack Compose\" குறியீடாக நகலெடுக்கவும் ரிங் மங்கலான குறுக்கு மங்கலான வட்டம் மங்கலானது நட்சத்திர மங்கலானது நேரியல் சாய்வு-மாற்றம் நீக்க குறிச்சொற்கள் APNG கருவிகள் படங்களை APNG படமாக மாற்றவும் அல்லது கொடுக்கப்பட்ட APNG படத்திலிருந்து பிரேம்களைப் பிரித்தெடுக்கவும் படங்களுக்கு APNG APNGக்கு படங்கள் படங்களின் தொகுப்பை APNG கோப்பாக மாற்றவும் APNG கோப்பை படங்களின் தொகுப்பாக மாற்றவும் தொடங்குவதற்கு APNG படத்தைத் தேர்ந்தெடுக்கவும் மோஷன் மங்கல் ஜிப் கொடுக்கப்பட்ட கோப்புகள் அல்லது படங்களிலிருந்து ஜிப் கோப்பை உருவாக்கவும் கைப்பிடி அகலத்தை இழுக்கவும் கைசர் ராபிடோக்ச் ஈவா லான்சோச் மென்மையான ஈவா சிவப்பு சாயல்களை உணர இயலாமை வண்ண குருட்டுத் திட்டத்தைப் பயன்படுத்த வேண்டாம் மேம்படுத்தப்பட்ட எண்ணெய் கிளாஏ Jzazbz டோன்கள் நிழல்கள் வண்ண கலவை Lut நடுநிலை LUT படத்தைப் பெறுங்கள் இலக்கு படம் இயல்புநிலை டிரா பாதை பயன்முறை நேர முத்திரையைச் சேர்க்கவும் விளிம்பு பயன்முறை வண்ண குருட்டுத்தன்மை லாக்ரேஞ்ச் 2 லாக்ரேஞ்ச் 3 லான்சோச் 6 லான்சோச் 6 சின்க் 6 இன் உயர் வரிசையுடன் ஒரு லான்சோச் மறுசீரமைப்பு வடிகட்டியை, கூர்மையான மற்றும் துல்லியமான பட அளவிடுதல் வழங்குகிறது மீண்டும் முயற்சிக்கவும் நிலப்பரப்பில் அமைப்புகளைக் காட்டு புதிய கோப்புறையைச் சேர்க்கவும் வண்ண இடம் காமா படப்புள்ளி ஃச் பரிமாணம் படப்புள்ளி ஒய் பரிமாணம் தயாரிப்பாளர் குறிப்பு துணை நொடி நேரம் டிசிட்டல் மயமாக்கப்பட்டது நேரிடுதல் காலம் துளை மதிப்பு கோப்பு மூல சி.எஃப்.ஏ முறை கட்டுப்பாட்டைப் பெறுங்கள் படம் தனித்துவமான ஐடி கேமரா உரிமையாளர் பெயர் உடல் வரிசை எண் சி.பி.எச் விரைவு சி.பி.எச் டிராக் ரெஃப் Jxl பெறுநர் jpeg JXL இலிருந்து JPEG க்கு இழப்பற்ற டிரான்ச்கோடிங்கைச் செய்யுங்கள் JINC செயல்பாட்டைப் பயன்படுத்தும் லான்சோச் 4 வடிப்பானின் மாறுபாடு, குறைந்தபட்ச கலைப்பொருட்களுடன் உயர்தர இடைக்கணிப்பை வழங்குகிறது உயர்தர மறுசீரமைப்பிற்கான ராபிடோக்ச் வடிகட்டியின் நீள்வட்ட சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு ரிங்கிங் கலைப்பொருட்களைக் குறைப்பதற்கான பிளாக்மேன் வடிகட்டியின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு மென்மையான இடைக்கணிப்பு மற்றும் மறுசீரமைப்பிற்கான அன்னிங் வடிப்பானின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு ராபிடோக்ச் சார்ப் ஈவா கூர்மையான முடிவுகளுக்காக ராபிடோக்ச் கூர்மையான வடிப்பானின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு லான்சோச் 3 சின்க் ஈவா குணசிங்கி கூர்மை மற்றும் மென்மையின் நல்ல சமநிலையுடன் உயர்தர பட செயலாக்கத்திற்காக வடிவமைக்கப்பட்ட ஒரு மறுசீரமைப்பு வடிகட்டி சின்செங் ஈவா மேம்பட்ட பட தரத்திற்கான சின்செங் வடிகட்டியின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு லான்சோச் சார்ப் ஈவா குறைந்தபட்ச கலைப்பொருட்களுடன் கூர்மையான முடிவுகளை அடைய லான்சோச் கூர்மையான வடிகட்டியின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு லான்சோச் 4 கூர்மையான ஈவா வடிவமைப்பு மாற்றம் மென்மையான பட மறுசீரமைப்பிற்கான லான்சோச் மென்மையான வடிகட்டியின் நீள்வட்ட எடை சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு மென்மையான மற்றும் கலைப்பொருள் இல்லாத பட அளவிடுதலுக்காக HAASN ஆல் வடிவமைக்கப்பட்ட ஒரு மறுசீரமைப்பு வடிகட்டி என்றென்றும் தள்ளுபடி செய்யுங்கள் தேர்ந்தெடுக்கப்பட்ட கலப்பு முறைகளுடன் ஒருவருக்கொருவர் மேல் படங்களை அடுக்கி வைக்கவும் கிளாஏ எச்.எச்.எல் கிளாஏ எச்.எச்.வி. இச்டோகிராம் தகவமைப்பு HSV ஐ சமப்படுத்தவும் மடக்கு தேர்ந்தெடுக்கப்பட்ட வண்ண குருட்டுத்தன்மை மாறுபாட்டிற்கான கருப்பொருள் வண்ணங்களை மாற்றியமைக்க பயன்முறையைத் தேர்ந்தெடுக்கவும் பச்சை மற்றும் சிவப்பு சாயல்களுக்கு இடையில் வேறுபடுவதில் தொல்லை நீல மற்றும் மஞ்சள் நிறங்களுக்கு இடையில் வேறுபடுவதில் தொல்லை பச்சை நிறங்களை உணர இயலாமை நீல நிற சாயல்களை உணர இயலாமை எல்லா வண்ணங்களுக்கும் உணர்திறன் குறைக்கப்பட்டுள்ளது ஆர்டர் 3 இன் லாக்ரேஞ்ச் இடைக்கணிப்பு வடிகட்டி, பட அளவிடுதலுக்கான சிறந்த துல்லியம் மற்றும் மென்மையான முடிவுகளை வழங்குகிறது மேம்பட்ட பட மறுசீரமைப்பு தரத்திற்காக JINC செயல்பாட்டைப் பயன்படுத்தி லான்சோச் 6 வடிகட்டியின் மாறுபாடு நேரியல் பெட்டி மங்கலானது நேரியல் கூடாரம் மங்கலானது நேரியல் காசியன் பெட்டி மங்கலானது நேரியல் அடுக்கு மங்கலானது காசியன் பெட்டி மங்கலானது நேரியல் ஃபாச்ட் காசியன் அடுத்து மங்கலானது நேரியல் ஃபாச்ட் காசியன் மங்கலானது நேரியல் காசியன் மங்கலானது வண்ணப்பூச்சாகப் பயன்படுத்த ஒரு வடிப்பானைத் தேர்வுசெய்க வடிகட்டியை மாற்றவும் உங்கள் வரைபடத்தில் அதை தூரிகையாகப் பயன்படுத்த கீழே உள்ள வடிகட்டியைத் தேர்ந்தெடுங்கள் TIFF சுருக்க திட்டம் குறைந்த பாலி மணல் ஓவியம் கான்ஃபெட்டி வகை பண்டிகை வெடிக்கும் மழை மூலைகள் JXL கருவிகள் எல்லைக்கு பொருந்தும் காப்புப்பிரதி OCR மாதிரிகள் தர இழப்பு இல்லாமல் JXL ~ JPEG டிரான்ச்கோடிங்கைச் செய்யுங்கள், அல்லது GIF/APNG ஐ JXL அனிமேசனாக மாற்றவும் JPEG இலிருந்து JXL க்கு இழப்பற்ற டிரான்ச்கோடிங்கைச் செய்யுங்கள் Jpeg பெறுநர் jxl தொடங்க JXL படத்தைத் தேர்ந்தெடுக்கவும் ஃபாச்ட் காசியன் மங்கலான 2 டி ஃபாச்ட் காசியன் மங்கலான 3D ஃபாச்ட் காசியன் மங்கலான 4 டி இடைநிலைப்பலகை தரவை தானாக பேச்ட் செய்ய பயன்பாட்டை அனுமதிக்கிறது, எனவே இது முதன்மையான திரையில் தோன்றும், மேலும் நீங்கள் அதை செயலாக்க முடியும் லான்சோச் பெசெல் பிடித்த விருப்பங்கள் எதுவும் தேர்ந்தெடுக்கப்படவில்லை, அவற்றை கருவிகள் பக்கத்தில் சேர்க்கவும் பிடித்தவைகளைச் சேர்க்கவும் நிரப்பு ஒப்பீட்டு முக்கோண நிறைவு நாற்கை ஒப்புமை + நிரப்பு வண்ண கருவிகள் கலக்கவும், டோன்களை உருவாக்கவும், நிழல்களை உருவாக்கவும் வண்ண இணக்கங்கள் வண்ண நிழல் மாறுபாடு வண்ண செய்தி தேர்ந்தெடுக்கப்பட்ட நிறம் கலக்க நிறம் மாறும் வண்ணங்கள் இயக்கப்படும் போது மோனெட்டைப் பயன்படுத்த முடியாது 512x512 2D LUT இலக்கு LUT படம் அமேட்டர் மிச் நெறிமுறைகள் மென்மையான நேர்த்தியானது மென்மையான நேர்த்தியான மாறுபாடு தட்டு பரிமாற்ற மாறுபாடு வார்டு ப்ளீச் பைபாச் ஃபிலிம் ச்டாக் 50 மெழுகுவர்த்தி ப்ளூச் கைவிடவும் கோடக் நேர முத்திரைகள் அவற்றின் வடிவமைப்பைத் தேர்ந்தெடுக்க இயக்கவும் ஒரு முறை இருப்பிடத்தை சேமிக்கவும் இந்த முறையைப் பின்பற்றி விருப்பங்கள் உள்ளிடப்பட வேண்டும்: \"-{option_name} {value}\" வாகன பயிர் இலவச மூலைகள் பலகோணத்தால் பயிர் படம், இது முன்னோக்கையும் சரிசெய்கிறது பட எல்லைகளை CORCE சுட்டிக்காட்டுகிறது பட வரம்புகளால் புள்ளிகள் மட்டுப்படுத்தப்படாது, இது மிகவும் துல்லியமான முன்னோக்குக்கு பயனுள்ளதாக இருக்கும் மறைப்பு உள்ளடக்க விழிப்புணர்வு வரையப்பட்ட பாதையின் கீழ் நிரப்பு இடத்தை குணப்படுத்துங்கள் வட்டம் கர்னலைப் பயன்படுத்தவும் திறப்பு நிறைவு உருவவியல் சாய்வு மேல் தொப்பி கருப்பு தொப்பி தொனி வளைவுகள் வளைவுகளை மீட்டமைக்கவும் வளைவுகள் இயல்புநிலை மதிப்புக்கு மீண்டும் உருட்டப்படும் முத்திரையிடப்பட்டது இடைவெளி அளவு புள்ளி கோடு கொடுக்கப்பட்ட பாதையில் புள்ளி மற்றும் கோடு கோட்டை ஈர்க்கிறது அலை அலையான சிக்சாக் பாதையில் ஈர்க்கிறது குறுக்குவழியை உருவாக்கவும் முள் செய்ய கருவியைத் தேர்வுசெய்க படப்புள்ளி மதிப்புகளுக்கு பெசல் (சின்க்) செயல்பாட்டைப் பயன்படுத்துவதன் மூலம் உயர்தர இடைக்கணிப்பை பராமரிக்கும் மறுசீரமைப்பு முறை Gif பெறுநர் jxl GIF படங்களை JXL அனிமேசன் படங்களாக மாற்றவும் APNG படங்களை JXL அனிமேசன் படங்களாக மாற்றவும் படங்களுக்கு jxl JXL அனிமேசனை தொகுதி படங்களாக மாற்றவும் படங்கள் JXL க்கு படங்களின் தொகுப்பை JXL அனிமேசனாக மாற்றவும் நடத்தை கோப்பு எடுப்பதைத் தவிர்க்கவும் தேர்ந்தெடுக்கப்பட்ட திரையில் இது முடிந்தால் கோப்பு எடுப்பவர் உடனடியாக காண்பிக்கப்படும் முன்னோட்டங்களை உருவாக்கவும் முன்னோட்ட தலைமுறையை இயக்கவும், இது சில சாதனங்களில் விபத்துக்களைத் தவிர்க்க உதவக்கூடும், இது ஒற்றை திருத்து விருப்பத்திற்குள் சில திருத்துதல் செயல்பாடுகளையும் முடக்கியது இழப்பு சுருக்க இழப்பற்ற தன்மைக்கு பதிலாக கோப்பு அளவைக் குறைக்க இழப்பு சுருக்கத்தைப் பயன்படுத்துகிறது அளவீட்டு முறை ஃபிளாச் குவிநீளம், குவியத் தொலைவு ஃபிளாச் ஆற்றல் இடஞ்சார்ந்த அதிர்வெண் பதில் குவிய வானூர்தி ஃச் தீர்மானம் குவிய வானூர்தி ஒய் தீர்மானம் குவிய வானூர்தி தீர்மானம் பிரிவு பொருள் இடம் வெளிப்பாடு அட்டவணை காட்சி பிடிப்பு வகை மாறுபாடு தெவிட்டல் கூர்மையானது சாதன அமைப்பு விளக்கம் பொருள் தூர வரம்பு லென்ச் விவரக்குறிப்பு லென்ச் செய்யுங்கள் லென்ச் மாதிரி லென்ச் வரிசை எண் சி.பி.எச் பதிப்பு ஐடி சி.பி.எச் அட்சரேகை குறிப்பு சி.பி.எச் தீர்க்கரேகை குறிப்பு சி.பி.எச் உயரம் சி.பி.எச் நேர முத்திரை சி.பி.எச் அட்சரேகை சி.பி.எச் செயற்கைக்கோள்கள் சி.பி.எச் நிலை சி.பி.எச் அளவீட்டு முறை சி.பி.எச் விரைவு குறிப்பு சி.பி.எச் டிராக் சி.பி.எச் ஐ.எம்.சி திசை குறிப்பு சி.பி.எச் ஐ.எம்.சி திசை சி.பி.எச் வரைபட தரவு சி.பி.எச் சி.பி.எச் டெச்ட் அட்சரேகை சி.பி.எச் சி.பி.எச் சி.பி.எச் டெச்ட் தாங்கி ref சி.பி.எச் சி.பி.எச் செயலாக்க முறை சி.பி.எச் பகுதி செய்தி சி.பி.எச் டெச்ட் தாங்கி சி.பி.எச் சி.பி.எச் தேதி முத்திரை சி.பி.எச் வேறுபாடு சி.பி.எச் எச் பொருத்துதல் பிழை இயங்குதன்மை குறியீடு டி.என்.சி பதிப்பு இயல்புநிலை பயிர் அளவு அம்ச சட்டகம் சென்சார் கீழ் எல்லை சென்சார் இடது எல்லை சென்சார் வலது எல்லை சென்சார் மேல் எல்லை ஐசோ பிரேம்களை அடுக்கி வைக்க வேண்டாம் கிராச்ஃபேட் பிரேம்கள் ஒருவருக்கொருவர் குறுக்குவெட்டு செய்யப்படும் கிராச்ஃபேட் பிரேம்கள் எண்ணிக்கை வாசல் ஒன்று வாசல் இரண்டு மிரர் 101 எழுத்துரு அளவு வாட்டர்மார்க் அளவு உரையை மீண்டும் செய்யவும் தற்போதைய உரை ஒரு முறை வரைபடத்திற்கு பதிலாகப் பாதை முடியும் வரை மறுநிகழ்வு செய்யப்படும் கொடுக்கப்பட்ட பாதையில் அதை வரைய தேர்ந்தெடுக்கப்பட்ட படத்தைப் பயன்படுத்தவும் கொடுக்கப்பட்ட எழுத்துரு மற்றும் வண்ணத்துடன் பாதையில் உரையை வரையவும் மேம்படுத்தப்பட்ட சூம் மங்கலானது லாப்லாசியன் எளிமையானது சோபல் எளிமையானது உதவி கட்டம் இந்தப் படம் வரையப்பட்ட பாதையின் மறுநிகழ்வு நுழைவாகப் பயன்படுத்தப்படும் தொடக்க புள்ளியிலிருந்து இறுதி புள்ளி வரை கோடிட்டுள்ள முக்கோணத்தை ஈர்க்கிறது தொடக்க புள்ளியிலிருந்து இறுதி புள்ளி வரை கோடிட்டுள்ள முக்கோணத்தை ஈர்க்கிறது கோடிட்டுக் காட்டப்பட்ட முக்கோணம் தொடக்க புள்ளியில் இருந்து இறுதி புள்ளி வரை பலகோணத்தை ஈர்க்கிறது பலகோணம் செங்குத்துகள் தொடக்க புள்ளியிலிருந்து இறுதி புள்ளி வரை அவுட்லைன் பலகோணத்தை வரையவும் வழக்கமான பலகோணத்தை வரையவும் இலவச படிவத்திற்கு பதிலாக வழக்கமானதாக இருக்கும் பலகோணத்தை வரையவும் தொடக்க புள்ளியிலிருந்து இறுதி புள்ளி வரை நட்சத்திரத்தை ஈர்க்கிறது விண்மீன் கோடிட்டுக் காட்டப்பட்ட விண்மீன் தொடக்க புள்ளியிலிருந்து இறுதி புள்ளி வரை கோடிட்ட நட்சத்திரத்தை ஈர்க்கிறது உள் ஆரம் விகிதம் ஆவணங்களை ச்கேன் செய்து அவற்றிலிருந்து PDF அல்லது தனி படங்களை உருவாக்கவும் ச்கேனிங் தொடங்க சொடுக்கு செய்க கீழே உள்ள விருப்பங்கள் PDF அல்ல, படங்களை சேமிப்பதற்காக இச்டோகிராம் எச்.எச்.வி. இச்டோகிராம் சமப்படுத்தவும் சதவீதத்தை உள்ளிடவும் உரை புலத்தால் உள்ளிட அனுமதிக்கவும் முன்னமைவுகள் தேர்வுக்கு பின்னால் உரை புலத்தை செயல்படுத்துகிறது, அவற்றை பறக்கும்போது உள்ளிடவும் அளவிலான வண்ண இடம் நேரியல் கட்டம் அளவு ஃச் இச்டோகிராம் பிக்சலேசனை சமப்படுத்தவும் கட்டம் அளவு ஒய் இச்டோகிராம் தகவமைப்பை சமப்படுத்தவும் இச்டோகிராம் தகவமைப்பு LUV ஐ சமப்படுத்தவும் இச்டோகிராம் தகவமைப்பு ஆய்வகத்தை சமப்படுத்தவும் கிளாஏ ஆய்வகம் கிளாஏ லவ் புறக்கணிக்க வண்ணம் தேர்ந்தெடுக்கப்பட்ட கோப்பில் வடிகட்டி வார்ப்புரு தரவு இல்லை வார்ப்புருவை உருவாக்கவும் வார்ப்புரு பெயர் இந்த வடிகட்டி வார்ப்புருவை முன்னோட்டமிட இந்த படம் பயன்படுத்தப்படும் QR குறியீடு படமாக கோப்பாக சேமிக்கவும் QR குறியீடு படமாக சேமிக்கவும் வார்ப்புருவை நீக்கு நீங்கள் தேர்ந்தெடுக்கப்பட்ட வார்ப்புரு வடிப்பானை நீக்க உள்ளீர்கள். இந்த செயல்பாட்டை செயல்தவிர்க்க முடியாது பிளாக்மேன் வெல்ச் குவாட்ரிக் காசியன் சூரரிமாச்சிலை பார்ட்லெட் ராபிடோக்ச் ராபிடோக்ச் சார்ப் ச்ப்லைன் 16 ச்ப்லைன் 36 லான்சோச் 2 சின்க் துல்லியமான கையாளுதல்களுக்கு உதவுவதற்காக வரைதல் பகுதிக்கு மேலே துணை கட்டத்தைக் காட்டுகிறது கட்டம் நிறம் செல் அகலம் செல் உயரம் சில தேர்வுக் கட்டுப்பாடுகள் குறைந்த இடத்தை எடுக்க ஒரு சிறிய தளவமைப்பைப் பயன்படுத்தும் படத்தைப் பிடிக்க அமைப்புகளில் கேமரா இசைவு வழங்கவும் மனையமைவு நிலையான வீத காரணி (சிஆர்எஃப்) %1$s இன் மதிப்பு மெதுவான சுருக்கத்தைக் குறிக்கிறது, இதன் விளைவாக ஒப்பீட்டளவில் சிறிய கோப்பு அளவு ஏற்படுகிறது. %2$s என்பது வேகமான சுருக்கத்தைக் குறிக்கிறது, இதன் விளைவாக ஒரு பெரிய கோப்பு. LUT நூலகம் முதன்மையான திரை தலைப்பு பதிவிறக்கிய பிறகு நீங்கள் விண்ணப்பிக்கக்கூடிய LUTS இன் சேகரிப்பைப் பதிவிறக்குக LUTS இன் புதுப்பிப்பு சேகரிப்பு (புதியவை மட்டுமே வரிசையில் நிற்கப்படும்), பதிவிறக்கம் செய்த பிறகு நீங்கள் விண்ணப்பிக்கலாம் மறை காட்டு வடிப்பான்களுக்கான இயல்புநிலை பட முன்னோட்டத்தை மாற்றவும் படம் முன்னோட்டம் ச்லைடர் வகை பொருள் 2 ஒரு ஆடம்பரமான தோற்றமுடைய ச்லைடர். இது இயல்புநிலை விருப்பம் ஒரு பொருள் 2 ச்லைடர் நீங்கள் ச்லைடர் ஒரு பொருள் இடு மைய உரையாடல் பொத்தான்கள் முடிந்தால் இடது பக்கத்திற்கு பதிலாக உரையாடல்களின் பொத்தான்கள் மையத்தில் நிலைநிறுத்தப்படும் திறந்த மூல உரிமங்கள் இந்த பயன்பாட்டில் பயன்படுத்தப்படும் திறந்த மூல நூலகங்களின் உரிமங்களைக் காண்க பெட்டி லான்சோச் 2 லான்சோச் 3 லான்சோச் 4 க்யூபிக் இடைக்கணிப்பு மிக நெருக்கமான 16 பிக்சல்களைக் கருத்தில் கொண்டு மென்மையான அளவை வழங்குகிறது, இது பிலினியரை விட சிறந்த முடிவுகளை அளிக்கிறது குறைக்கப்பட்ட நிறமாலை கசிவுடன் நல்ல அதிர்வெண் தெளிவுத்திறனைக் கொடுக்க வடிவமைக்கப்பட்ட ஒரு சாளர செயல்பாடு, பெரும்பாலும் சமிக்ஞை செயலாக்க பயன்பாடுகளில் பயன்படுத்தப்படுகிறது இடைக்கணிப்புக்கு இருபடி செயல்பாட்டைப் பயன்படுத்தும் ஒரு முறை, மென்மையான மற்றும் தொடர்ச்சியான முடிவுகளை வழங்கும் ராபிடோக்ச் முறையின் கூர்மையான மாறுபாடு, மிருதுவான பட மறுஅளவிடுவதற்கு உகந்ததாகும் அனுமதிகள் இல்லை எச்.வி.சி.க்கு படங்கள் பயனர் கருத்து பொருள் பகுதி சி.பி.எச் டாப் பட நீளத்தை முன்னோட்டமிடுங்கள் கோடு அளவு கோடிட்டுள்ள பலகோணம் PDF ஆக பகிரவும் உள்ளடக்கத்திற்கு பயிர் கோப்பாக ச்ப்லைன் 64 பார்ட்லெட் - அவர் Haasn மென்மையான சிக்மாய்டல் சாயல்கள் இலக்கு 3D LUT கோப்பு (.cube / .cube) சிக்சாக் விகிதம் Apng பெறுநர் jxl குறியீடு உள்ளடக்கம் மூடுபனி இரவு சிக்சாக் வட்டமான சகிப்புத்தன்மையை ஒருங்கிணைக்கிறது பிரகாசமான மதிப்பு ஆஃப்செட் நேரம் டிசிட்டல் மயமாக்கப்பட்டது தனிப்பயன் பெயருடன் கோப்புறையில் சேமிக்கப்பட்டது சி.பி.எச் உயரம் குறிப்பு ஃச் தீர்மானம் சட்ட நிறம் ஆன் நிங் இ வா லான்சோச் 3 சின்க் வடிகட்டியின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு குறைக்கப்பட்ட மாற்றியுடன் உயர்தர மறுசீரமைப்பிற்கு கொடுக்கப்பட்ட வடிவத்திற்கு பட தொகுதிகளை மாற்றவும் நேற்று குறிப்பிட்ட இடைவெளியுடன் பாதையில் தேர்ந்தெடுக்கப்பட்ட வடிவங்களை ஈர்க்கிறது ஆடம்பரமான இச்டோகிராம் தகவமைப்பு HSL ஐ சமப்படுத்தவும் சி.பி.எச் தீர்க்கரேகை டெட்ராடிக் ச்கேன் செய்யத் தொடங்குங்கள் இணைப்புகள் முன்னோட்டம் மென்மையான இடைக்கணிப்புக்கான குவாட்ரிக் வடிகட்டியின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு பட அடுக்கு கோரிக்கை இருபடி வாசல் சுருக்க வகை இயல்புநிலை வரி அகலம் பட பிரித்தல் வரிசைகள் அல்லது நெடுவரிசைகளால் ஒற்றை படத்தை பிரிக்கவும் எழுதுங்கள் சரளமாக யிலிலோமா டிட்டரிங் முன்னோட்டம் படத் தொடங்கு பகுதி கிளாஏ எஞ்சின் பயன்முறை கொடுக்கப்பட்ட பரிமாணங்களுக்கு ஒரு படத்தைப் பொருத்தி, பின்னணியில் மங்கல அல்லது வண்ணத்தைப் பயன்படுத்துங்கள் கருவிகள் ஏற்பாடு வகை மூலம் குழு கருவிகள் அனைத்தையும் மறைக்கவும் அனைத்தையும் காட்டு NAV பட்டியை மறைக்கவும் நிலை பட்டியை மறைக்கவும் பிங் பாங் வலிமை தூர செயல்பாடு திரும்ப வகை நடுக்கம் டொமைன் வார்ப் வரி நடை குறிப்பிட்ட இடைவெளி அளவுடன் வரையப்பட்ட பாதையில் விடுபட்ட கோடு வரைதல் இயல்புநிலை நேர் கோடுகள் பயிர் மறுசீரமைப்பு பயன்முறையை இந்த அளவுருவுடன் இணைக்க விரும்பிய நடத்தை (பயிர்/அம்ச விகிதத்திற்கு பொருத்தம்) மொழிகள் வெற்றிகரமாக இறக்குமதி செய்யப்பட்டன இறக்குமதி வெளியீட்டு படங்களுக்கு அவற்றின் தரவு செக்சமுக்கு ஒத்த பெயர் இருக்கும் பகிர்வு BASE64 விருப்பங்கள் செயல்கள் இறக்குமதி BASE64 அடிப்படை 64 செயல்கள் அவுட்லைன் சேர்க்கவும் குறிப்பிட்ட வண்ணம் மற்றும் அகலத்துடன் உரையைச் சுற்றி அவுட்லைன் சேர்க்கவும் அவுட்லைன் நிறம் அவுட்லைன் அளவு சுழற்சி கோப்பு பெயராக செக்சம் கார் பாச்தா ஒத்திசைவு நிறம் ஒத்திசைவு நிலை பல ஊடகங்களைத் தேர்ந்தெடுங்கள் தேர்ந்தெடு சட்டர் வேக மதிப்பு வெளிப்பாடு சார்பு மதிப்பு அதிகபட்ச துளை மதிப்பு உணர்திறன் முறை தனிப்பயன் வழங்கப்பட்டது வெளிப்பாடு பயன்முறை வெள்ளை இருப்பு டிசிட்டல் சூம் விகிதம் 35 மிமீ படத்தில் குவிய நீளம் முக்கோணம் போமன் இயற்கையான பட மறுஅளவிடுதலுக்காக உகந்ததாக இருக்கும் உயர்தர இடைக்கணிப்பு முறை, கூர்மை மற்றும் மென்மையை சமநிலைப்படுத்துகிறது படத்தைச் சேர்க்கவும் பின்கள் எண்ணிக்கை Webp கோப்பை படங்களின் தொகுதிகளாக மாற்றவும் தொஒ சேனல் மாற்றங்களைச் செய்ய உங்களுக்கு உதவும் RGB அல்லது பிரகாசமான பட இச்டோகிராம் இந்த படம் RGB மற்றும் பிரகாசமான இச்டோகிராம்களை உருவாக்க பயன்படுத்தப்படும் கோடு அடுக்கு திருத்து BASE64 சரத்தை நகலெடுக்க அல்லது சேமிக்க படத்தை ஏற்றவும். உங்களிடம் சரம் இருந்தால், படத்தைப் பெற மேலே ஒட்டலாம் BASE64 ஐ சேமிக்கவும் \"சரளமாக\" வடிவமைப்பு அமைப்பின் அடிப்படையில் ஒரு சுவிட்ச் குபெர்டினோ \"குப்பெர்டினோ\" வடிவமைப்பு அமைப்பை அடிப்படையாகக் கொண்ட ஒரு சுவிட்ச் குறைந்தபட்ச வண்ண விகிதம் தெளிவுத்திறன் அலகு உரி ஆஃப்செட்டுகள் ஒரு துண்டுக்கு வரிசைகள் உரி பைட் எண்ணிக்கைகள் Jpeg பரிமாற்ற வடிவம் JPEG பரிமாற்ற வடிவமைப்பு நீளம் மாற்றச் சார்பு வெள்ளை புள்ளி முதன்மை வண்ணமயமாக்கல்கள் குறிப்பு கருப்பு வெள்ளை தேதி நேரம் பட விவரம் உருவாக்கு OECF உணர்திறன் வகை நிலையான வெளியீட்டு உணர்திறன் பரிந்துரைக்கப்பட்ட வெளிப்பாடு அட்டவணை ஐஎச்ஓ விரைவு ஐஎச்ஓ வேக அட்சரேகை YYY ஐஎச்ஓ வேக அட்சரேகை Zzz வழக்கமான நட்சத்திரத்தை வரையவும் இலவச படிவத்திற்கு பதிலாக வழக்கமானதாக இருக்கும் நட்சத்திரத்தை வரையவும் ஆன்டிஆசா கூர்மையான விளிம்புகளைத் தடுக்க ஆன்டிலியாசிங்கை செயல்படுத்துகிறது முன்னோட்டத்திற்கு பதிலாக திருத்து திறக்கவும் இமேசெட்டூல் பாக்சில் திறக்க (முன்னோட்டம்) படத்தைத் தேர்ந்தெடுக்கும்போது, முன்னோட்டத்திற்கு பதிலாக தேர்வுத் தாள் திறக்கப்படும் ஆவண ச்கேனர் PDF ஆக சேமிக்கவும் வார்ப்புரு வார்ப்புரு வடிப்பான்கள் எதுவும் சேர்க்கப்படவில்லை புதியதை உருவாக்கவும் வருடு செய்யப்பட்ட QR குறியீடு சரியான வடிகட்டி வார்ப்புரு அல்ல QR குறியீட்டை வருடு செய்யுங்கள் வார்ப்புரு வடிகட்டி \"%1$s\" (%2$s) என்ற பெயருடன் வடிகட்டி வார்ப்புரு சேர்க்கப்பட்டது வடிகட்டி முன்னோட்டம் QR & பார்கோடு QR குறியீட்டை வருடு செய்து அதன் உள்ளடக்கத்தைப் பெறுங்கள் அல்லது புதிய ஒன்றை உருவாக்க உங்கள் சரத்தை ஒட்டவும் புலத்தில் உள்ளடக்கத்தை மாற்ற எந்த பார்கோடையும் வருடு செய்யுங்கள் அல்லது தேர்ந்தெடுக்கப்பட்ட வகையுடன் புதிய பார்கோடு உருவாக்க ஏதாவது தட்டச்சு செய்க QR விளக்கம் மணித்துளி QR குறியீட்டை ச்கேன் செய்ய அமைப்புகளில் கேமரா இசைவு வழங்கவும் கன பி-ச்பைன் ஏமிங் அன்னிங் லான்சோச் 3 சின்க் லான்சோச் 4 சின்க் ஒரு வளைவு அல்லது மேற்பரப்பு, நெகிழ்வான மற்றும் தொடர்ச்சியான வடிவ பிரதிநிதித்துவம் சமிக்ஞையின் விளிம்புகளைத் தட்டுவதன் மூலம் நிறமாலை கசிவைக் குறைக்கப் பயன்படுத்தப்படும் ஒரு சாளர செயல்பாடு, சமிக்ஞை செயலாக்கத்தில் பயனுள்ளதாக இருக்கும் சமிக்ஞை செயலாக்க பயன்பாடுகளில் நிறமாலை கசிவைக் குறைக்க பொதுவாகப் பயன்படுத்தப்படும் ஆன் சாளரத்தின் மாறுபாடு சமிக்ஞை செயலாக்கத்தில் பெரும்பாலும் பயன்படுத்தப்படும் ச்பெக்ட்ரல் கசிவைக் குறைப்பதன் மூலம் நல்ல அதிர்வெண் தெளிவுத்திறனை வழங்கும் சாளர செயல்பாடு JINC செயல்பாட்டைப் பயன்படுத்தும் லான்சோச் 2 வடிப்பானின் மாறுபாடு, குறைந்தபட்ச கலைப்பொருட்களுடன் உயர்தர இடைக்கணிப்பை வழங்குகிறது பிளாக்மேன் ஈவா குவாட்ரிக் ஈவா லான்சோசின் நீள்வட்ட எடையுள்ள சராசரி (ஈ.டபிள்யூ.ஏ) மாறுபாடு 4 மிகவும் கூர்மையான பட மறுசீரமைப்பிற்கான கூர்மையான வடிகட்டி படங்களின் தொகுதிகளை ஒரு வடிவத்திலிருந்து இன்னொரு வடிவத்திற்கு மாற்றவும் கிளிப் சிவப்பு மற்றும் பச்சை நிறங்களுக்கு இடையில் வேறுபடுவதில் தொல்லை முழுமையான வண்ண குருட்டுத்தன்மை, சாம்பல் நிற நிழல்களை மட்டுமே பார்க்கிறது வண்ணங்கள் கருப்பொருளில் அமைக்கப்பட்டிருக்கும் ஆர்டர் 2 இன் ஒரு லாக்ரேஞ்ச் இடைக்கணிப்பு வடிகட்டி, மென்மையான மாற்றங்களுடன் உயர்தர பட அளவிடலுக்கு ஏற்றது ஏற்றுமதி நிலை நடுவண் மேல் இடது மேல் வலது கீழே இடது கீழே வலது மேல் நடுவண் நடுவண் சரியானது கீழே நடுவண் நடுவண் இடது தட்டு பரிமாற்றம் எளிய பழைய டிவி எச்.டி.ஆர் கோதம் எளிய ச்கெட்ச் மென்மையான பளபளப்பு வண்ண சுவரொட்டி ட்ரை டோன் மூன்றாவது நிறம் கிளாஏ ஓக்லாப் கிளாரா ஓல்ச் போலந்து புள்ளி கொத்து 2x2 டிதரிங் கொத்தாக 4x4 ditering கொத்து 8x8 டிதரிங் ஆவண ச்கேனரை ச்கேன் செய்ய அமைப்புகளில் கேமரா இசைவு வழங்கவும் கசப்பான அம்பர் வீழ்ச்சி வண்ணங்கள் ஆக்டேவ்ச் லாகுனாரிட்டி பெருக்கம் எடையுள்ள வலிமை இருப்புவழி தனிப்பயன் கோப்பு பெயர் தற்போதைய படத்தை சேமிக்க பயன்படுத்தப்படும் இடம் மற்றும் கோப்பு பெயரைத் தேர்ந்தெடுக்கவும் படத்தொகுப்பு வகை சிறிய தேர்வாளர்கள் Gif பெறுநர் webp ஒய் cb சிஆர் குணகங்கள் உங்கள் துவக்கத்தின் முகப்புத் திரையில் சார்ட்கட்டாக கருவி சேர்க்கப்படும், தேவையான நடத்தையை அடைய \"ச்கிப் கோப்பு எடுக்கும்\" அமைப்புடன் இதைப் பயன்படுத்தவும் முந்தைய பிரேம்களை அப்புறப்படுத்த உதவுகிறது, எனவே அவை ஒருவருக்கொருவர் அடுக்கி வைக்காது கேனி பீட்டா எஃப் எண் வெளிப்பாடு திட்டம் நிறமாலை உணர்திறன் புகைப்பட உணர்திறன் இதன் விளைவாக பட டிகோடிங் வேகத்தை கட்டுப்படுத்துகிறது, இது விளைந்த படத்தை வேகமாக திறக்க உதவும், %1$s இன் மதிப்பு மெதுவான டிகோடிங் என்று பொருள், அதேசமயம் %2$s - வேகமாக, இந்த அமைப்பு வெளியீட்டு பட அளவை அதிகரிக்கக்கூடும் வரிசைப்படுத்துதல் திகதி தேதி (தலைகீழ்) பெயர் பெயர் (தலைகீழ்) சேனல்கள் உள்ளமைவு இன்று உட்பொதிக்கப்பட்ட பிக்கர் பட கருவிப்பெட்டியின் பட எடுப்பர் ஒற்றை ஊடகத்தைத் தேர்ந்தெடுங்கள் இந்த முடக்கப்பட்டால், நிலப்பரப்பு பயன்முறையில் அமைப்புகள் எப்போதும் போல பயன்பாட்டுப் பட்டியில் உள்ள பொத்தானில் திறக்கப்படும், இது நிரந்தர புலப்படும் விருப்பத்திற்கு பதிலாக முழுத்திரை அமைப்புகள் அதை இயக்கு மற்றும் அமைப்புகள் பக்கம் எப்போதும் ச்லிபிள் டிராயர் தாளுக்கு பதிலாக முழுத்திரையாக திறக்கப்படும் சுவிட்ச் வகை ஒரு செட் பேக் நீங்கள் மாறும் பொருளை உருவாக்குகிறது நீங்கள் மாறும் ஒரு பொருள் அதிகபட்சம் மறுஅளவிடுதல் நங்கூரம் படப்புள்ளி எச்.வி.சி படங்களுக்கு கொடுக்கப்பட்ட படங்களை சுவடுங்கள் மாதிரி தட்டு பயன்படுத்தவும் இந்த விருப்பம் இயக்கப்பட்டால் அளவீட்டு தட்டு மாதிரி செய்யப்படும் பாதை தவிர்க்கவும் பொருள் தூரம் குறைவில்லாமல் பெரிய படங்களை கண்டுபிடிப்பதற்கான இந்த கருவியின் பயன்பாடு பரிந்துரைக்கப்படவில்லை, இது செயலிழப்பை ஏற்படுத்தும் மற்றும் செயலாக்க நேரத்தை அதிகரிக்கும் கீழ்நிலை படம் செயலாக்கத்திற்கு முன் படம் குறைந்த பரிமாணங்களுக்கு குறைக்கப்படும், இது கருவி வேகமாகவும் பாதுகாப்பாகவும் செயல்பட உதவுகிறது கோடுகள் வாசல் பாதை அளவு பண்புகளை மீட்டமை எல்லா பண்புகளும் இயல்புநிலை மதிப்புகளுக்கு அமைக்கப்படும், இந்த செயலை செயல்தவிர்க்க முடியாது என்பதைக் கவனியுங்கள் விவரிக்கப்பட்ட மரபு எல்.எச்.டி.எம் பிணையம் லெகசி & எல்.எச்.டி.எம் மாற்றவும் ஒரு மாதிரிக்கு பிட்கள் சுருக்க ஃபோட்டோமெட்ரிக் விளக்கம் பிக்சலுக்கு மாதிரிகள் பிளானர் உள்ளமைவு ஒய் cb சிஆர் துணை மாதிரி ஒய் cb சிஆர் பொருத்துதல் ஒய் தீர்மானம் மாதிரியுரு மென்பொருள் கலைஞர் பதிப்புரிமை Exif பதிப்பு ஃப்ளாச்பிக்ச் பதிப்பு பிக்சலுக்கு சுருக்கப்பட்ட பிட்கள் தொடர்புடைய ஒலி கோப்பு தேதி நேரம் அசல் தேதி நேரம் டிசிட்டல் மயமாக்கப்பட்டது நேரம் ஈடுசெய்யும் நேரம் ஆஃப்செட் நேரம் அசல் துணை நொடி நேரம் துணை நொடி நேரம் அசல் காசியன் செயல்பாட்டைப் பயன்படுத்தும் ஒரு இடைக்கணிப்பு முறை, படங்களில் சத்தத்தை மென்மையாக்குவதற்கும் குறைக்கவும் பயனுள்ளதாக இருக்கும் குறைந்தபட்ச கலைப்பொருட்களுடன் உயர்தர இடைக்கணிப்பை வழங்கும் மேம்பட்ட மறுசீரமைப்பு முறை நிறமாலை கசிவைக் குறைக்க சமிக்ஞை செயலாக்கத்தில் பயன்படுத்தப்படும் ஒரு முக்கோண சாளர செயல்பாடு 16-TAP வடிப்பானைப் பயன்படுத்தி மென்மையான முடிவுகளை வழங்கும் ஒரு ச்ப்லைன் அடிப்படையிலான இடைக்கணிப்பு முறை 36-TAP வடிப்பானைப் பயன்படுத்தி மென்மையான முடிவுகளை வழங்கும் ஒரு ச்ப்லைன் அடிப்படையிலான இடைக்கணிப்பு முறை 64-TAP வடிப்பானைப் பயன்படுத்தி மென்மையான முடிவுகளை வழங்கும் ஒரு ச்ப்லைன் அடிப்படையிலான இடைக்கணிப்பு முறை கைசர் சாளரத்தைப் பயன்படுத்தும் ஒரு இடைக்கணிப்பு முறை, பிரதான-லோப் அகலம் மற்றும் பக்க-லோப் நிலைக்கு இடையிலான வர்த்தகத்தின் மீது நல்ல கட்டுப்பாட்டை வழங்குகிறது சமிக்ஞை செயலாக்கத்தில் நிறமாலை கசிவைக் குறைக்கப் பயன்படும் பார்ட்லெட் மற்றும் ஆன் விண்டோசை இணைக்கும் ஒரு கலப்பின சாளர செயல்பாடு அருகிலுள்ள படப்புள்ளி மதிப்புகளின் சராசரியைப் பயன்படுத்தும் ஒரு எளிய மறுசீரமைப்பு முறை, பெரும்பாலும் தடுப்பு தோற்றத்தை ஏற்படுத்துகிறது சமிக்ஞை செயலாக்க பயன்பாடுகளில் நல்ல அதிர்வெண் தீர்மானத்தை வழங்கும் ச்பெக்ட்ரல் கசிவைக் குறைக்கப் பயன்படுத்தப்படும் ஒரு சாளர செயல்பாடு குறைந்தபட்ச கலைப்பொருட்களுடன் உயர்தர இடைக்கணிப்புக்கு 2-லோப் லான்சோச் வடிப்பானைப் பயன்படுத்தும் ஒரு மறுசீரமைப்பு முறை குறைந்தபட்ச கலைப்பொருட்களுடன் உயர்தர இடைக்கணிப்புக்கு 3-லோப் லான்சோச் வடிப்பானைப் பயன்படுத்தும் ஒரு மறுசீரமைப்பு முறை குறைந்தபட்ச கலைப்பொருட்களுடன் உயர்தர இடைக்கணிப்புக்கு 4-லோப் லான்சோச் வடிப்பானைப் பயன்படுத்தும் ஒரு மறுசீரமைப்பு முறை JINC செயல்பாட்டைப் பயன்படுத்தும் லான்சோச் 3 வடிப்பானின் மாறுபாடு, குறைந்தபட்ச கலைப்பொருட்களுடன் உயர்தர இடைக்கணிப்பை வழங்குகிறது முதலில், நீங்கள் இங்கே பெறக்கூடிய நடுநிலை LUT க்கு வடிகட்டியைப் பயன்படுத்த உங்களுக்கு பிடித்த புகைப்பட திருத்துதல் பயன்பாட்டைப் பயன்படுத்தவும். இது சரியாக வேலை செய்ய ஒவ்வொரு படப்புள்ளி நிறமும் மற்ற பிக்சல்களை சார்ந்து இருக்கக்கூடாது (எ.கா. மங்கலானது வேலை செய்யாது). தயாரானதும், உங்கள் புதிய LUT படத்தை 512*512 LUT வடிகட்டிக்கு உள்ளீடாகப் பயன்படுத்தவும் பாப் கலை செல்லுலாய்டு காபி கோல்டன் காடு பச்சை ரெட்ரோ மஞ்சள் நீங்கள் உரையைப் பெறக்கூடிய இடங்களில் இணைப்பு முன்னோட்டத்தை மீட்டெடுப்பதை இயக்குகிறது (QRCode, OCR போன்றவை) இணைப்புகள் ஐ.சி.ஓ கோப்புகளை அதிகபட்ச அளவு 256 ஃச் 256 இல் மட்டுமே சேமிக்க முடியும் GIF படங்களை வலை அனிமேசன் படங்களுக்கு மாற்றவும் வலை கருவிகள் படங்களை வெப் அனிமேசன் படமாக மாற்றவும் அல்லது கொடுக்கப்பட்ட வெப்.பி அனிமேசனில் இருந்து பிரேம்களைப் பிரித்தெடுக்கவும் படங்களுக்கு வலை படங்களின் தொகுப்பை Webp கோப்பாக மாற்றவும் வலைப்பக்கத்திற்கு படங்கள் தொடங்க வலை படத்தைத் தேர்ந்தெடுக்கவும் கோப்புகளுக்கு முழு அணுகல் இல்லை ஆண்ட்ராய்டு இல் படங்களாக அங்கீகரிக்கப்படாத JXL, QOI மற்றும் பிற படங்களை பார்க்க எல்லா கோப்புகளையும் அணுக அனுமதிக்கவும். இசைவு இல்லாமல் பட கருவிப்பெட்டியால் அந்த படங்களை காட்ட முடியவில்லை இயல்புநிலை டிரா நிறம் வெளியீட்டு கோப்பு பெயரில் நேர முத்திரை சேர்க்க உதவுகிறது வடிவமைக்கப்பட்ட நேர முத்திரை அடிப்படை மில்லிசுக்கு பதிலாக வெளியீட்டு கோப்பு பெயரில் நேர முத்திரை வடிவமைப்பை இயக்கவும் பெரும்பாலும் அனைத்து விருப்பங்களிலும் சேமி பொத்தானை நீண்ட நேரம் அழுத்துவதன் மூலம் நீங்கள் பயன்படுத்தக்கூடிய இடங்களை சேமிக்கவும் திருத்தவும் அண்மைக் காலத்தில் பயன்படுத்தப்பட்டது குழு டெலிகிராமில் பட கருவிப்பெட்டி எங்கள் அரட்டையில் சேருங்கள், அங்கு நீங்கள் விரும்பும் எதையும் விவாதிக்கலாம், மேலும் நான் பீட்டாக்கள் மற்றும் அறிவிப்புகளை இடுகையிடும் தொஒ சேனலையும் பாருங்கள் பயன்பாட்டின் புதிய பதிப்புகள் குறித்து அறிவிக்கப்பட்டு, அறிவிப்புகளைப் படியுங்கள் தனிப்பயன் பட்டியல் ஏற்பாட்டிற்கு பதிலாக முதன்மையான திரையில் குழுக்கள் கருவிகள் அவற்றின் வகையால் இயல்புநிலை மதிப்புகள் கணினி பார்கள் தெரிவுநிலை ச்வைப் மூலம் கணினி பார்களைக் காட்டு கணினி பார்கள் மறைக்கப்பட்டால் அவற்றைக் காட்ட ச்வைப் செய்வதை செயல்படுத்துகிறது தானி ஒலி உருவாக்கம் பெர்லின் அல்லது பிற வகைகள் போன்ற வெவ்வேறு சத்தங்களை உருவாக்குங்கள் மீடிறன், மீள்திறன், நிகழ்வெண், நிகழ்வு இரைச்சல் வகை சுழற்சி வகை பின்னல் வகை படத்தொகுப்பு தயாரிப்பாளர் 20 படங்கள் வரை படத்தொகுப்புகளை உருவாக்குங்கள் நிலையை சரிசெய்ய இடமாற்றம், நகர்த்த மற்றும் பெரிதாக்க படத்தை வைத்திருங்கள் செவ்வகப்படம் டெசராக்ட் விருப்பங்கள் டெசராக்ட் எஞ்சினுக்கு சில உள்ளீட்டு மாறிகளைப் பயன்படுத்துங்கள் தனிப்பயன் விருப்பங்கள் படப்புள்ளி பகுதி உறவைப் பயன்படுத்தி மறுசீரமைத்தல். படத்தை அழிப்பதற்கான விருப்பமான முறையாக இது இருக்கலாம், ஏனெனில் இது மொயரின் இல்லாத முடிவுகளைத் தருகிறது. ஆனால் படம் பெரிதாக்கப்படும்போது, அது \"அருகிலுள்ள\" முறைக்கு ஒத்ததாகும். டான்மாப்பிங் இயக்கவும் % உள்ளிடவும் தளத்தை அணுக முடியாது, VPN ஐப் பயன்படுத்த முயற்சிக்கவும் அல்லது முகவரி சரியானதா என்று சரிபார்க்கவும் மார்க்அப் அடுக்குகள் படங்கள், உரை மற்றும் பலவற்றை சுதந்திரமாக வைக்கும் திறன் கொண்ட அடுக்குகள் பயன்முறை படத்தில் அடுக்குகள் ஒரு படத்தை பின்னணியாகப் பயன்படுத்தவும், அதன் மேல் வெவ்வேறு அடுக்குகளைச் சேர்க்கவும் பின்னணியில் அடுக்குகள் முதல் விருப்பத்திற்கு அதே ஆனால் படத்திற்கு பதிலாக வண்ணத்துடன் வேகமான அமைப்புகள் பக்கம் படங்களைத் திருத்தும் போது தேர்ந்தெடுக்கப்பட்ட பக்கத்தில் ஒரு மிதக்கும் துண்டு சேர்க்கவும், இது சொடுக்கு செய்யும் போது வேகமான அமைப்புகளைத் திறக்கும் தெளிவான தேர்வு குழு \"%1$s\" அமைப்பது இயல்பாகவே சரிந்துவிடும் குழு \"%1$s\" அமைப்பது இயல்பாக விரிவாக்கப்படும் அடிப்படை 64 கருவிகள் BASE64 சரத்தை படத்திற்கு டிகோட் செய்யுங்கள், அல்லது படத்தை BASE64 வடிவத்திற்கு குறியாக்கவும் அடிப்படை 64 வழங்கப்பட்ட மதிப்பு சரியான அடிப்படை 64 சரம் அல்ல வெற்று அல்லது தவறான BASE64 சரத்தை நகலெடுக்க முடியாது பேச் 64 ஐ ஒட்டவும் BASE64 ஐ நகலெடுக்கவும் சுழற்சியை முடக்கு இரண்டு விரல் சைகைகளுடன் சுழலும் படங்களைத் தடுக்கிறது எல்லைகளுக்கு ஒடிப்பதை இயக்கவும் நகரும் அல்லது பெரிதாக்கிய பிறகு, பிரேம் விளிம்புகளை நிரப்ப படங்கள் ஒடிக்கும் இலவச மென்பொருள் (கூட்டாளர்) ஆண்ட்ராய்டு பயன்பாடுகளின் கூட்டாளர் சேனலில் மிகவும் பயனுள்ள மென்பொருள் படிமுறை செக்சம் கருவிகள் வெவ்வேறு ஆசிங் வழிமுறைகளைப் பயன்படுத்தி கோப்புகளிலிருந்து செக்சம்களை ஒப்பிட்டுப் பாருங்கள், ஆச்களைக் கணக்கிடுங்கள் அல்லது ஃச் சரங்களை உருவாக்கவும் கணக்கிடுங்கள் உரை ஆச் செக்சம் தேர்ந்தெடுக்கப்பட்ட வழிமுறையின் அடிப்படையில் அதன் செக்சமைக் கணக்கிட கோப்பைத் தேர்ந்தெடுக்கவும் தேர்ந்தெடுக்கப்பட்ட வழிமுறையின் அடிப்படையில் அதன் செக்சமைக் கணக்கிட உரையை உள்ளிடவும் மூல செக்சம் ஒப்பிடுவதற்கு செக்சம் போட்டி! வேறுபாடு செக்சம்கள் சமம், அது பாதுகாப்பாக இருக்கும் செக்சம்கள் சமமாக இல்லை, கோப்பு பாதுகாப்பற்றதாக இருக்கும்! கண்ணி சாய்வு கண்ணி சாய்வுகளின் நிகழ்நிலை தொகுப்பைப் பாருங்கள் TTF மற்றும் OTF எழுத்துருக்களை மட்டுமே இறக்குமதி செய்ய முடியும் இறக்குமதி எழுத்துரு (TTF/OTF) எழுத்துருக்கள் ஏற்றுமதி இறக்குமதி செய்யப்பட்ட எழுத்துருக்கள் முயற்சியைச் சேமிக்கும் போது பிழை, வெளியீட்டு கோப்புறையை மாற்ற முயற்சிக்கவும் கோப்பு பெயர் அமைக்கப்படவில்லை எதுவுமில்லை தனிப்பயன் பக்கங்கள் பக்கங்கள் தேர்வு கருவி வெளியேறும் உறுதிப்படுத்தல் குறிப்பிட்ட கருவிகளைப் பயன்படுத்தும் போது நீங்கள் சேமிக்கப்படாத மாற்றங்கள் இருந்தால் அதை மூட முயற்சித்தால், உரையாடல் காண்பிக்கப்படும் என்பதை உறுதிப்படுத்தவும் Exif திருத்து பின்னடைவு இல்லாமல் ஒற்றை படத்தின் மெட்டாடேட்டாவை மாற்றவும் கிடைக்கக்கூடிய குறிச்சொற்களைத் திருத்த தட்டவும் ச்டிக்கரை மாற்றவும் பொருந்தக்கூடிய அகலம் பொருத்தமான உயரத்திற்கு தொகுதி ஒப்பிடுக தேர்ந்தெடுக்கப்பட்ட வழிமுறையின் அடிப்படையில் அதன் செக்சமைக் கணக்கிட கோப்பு/கோப்புகளைத் தேர்ந்தெடுக்கவும் கோப்புகளைத் தேர்ந்தெடுங்கள் கோப்பகத்தைத் தேர்ந்தெடுங்கள் தலை நீள அளவு முத்திரை நேர முத்திரை வடிவமைப்பு முறை திணிப்பு பட வெட்டுதல் பட பகுதியை வெட்டி, இடதுவற்றை செங்குத்து அல்லது கிடைமட்ட கோடுகளால் ஒன்றிணைக்கவும் (தலைகீழ் ஆகலாம்) செங்குத்து பிவோட் வரி கிடைமட்ட பிவோட் வரி தலைகீழ் தேர்வு வெட்டப்பட்ட பகுதியைச் சுற்றி பகுதிகளை இணைப்பதற்கு பதிலாக, செங்குத்து வெட்டு பகுதி பாயும் வெட்டப்பட்ட பகுதியைச் சுற்றி பகுதிகளை இணைப்பதற்கு பதிலாக, கிடைமட்ட வெட்டு பகுதி பாயும் கண்ணி சாய்வுகளின் தொகுப்பு தனிப்பயன் அளவு முடிச்சுகள் மற்றும் தெளிவுத்திறனுடன் கண்ணி சாய்வு உருவாக்கவும் கண்ணி சாய்வு மேலடுக்கு கொடுக்கப்பட்ட படங்களின் மேல் கண்ணி சாய்வை எழுதுங்கள் புள்ளிகள் தனிப்பயனாக்கம் கட்டம் அளவு தீர்மானம் ஃச் தீர்மானம் ஒய் பகுத்தல் படப்புள்ளி படப்புள்ளியாக வண்ணத்தை முன்னிலைப்படுத்தவும் படப்புள்ளி ஒப்பீட்டு வகை வருடு பார்கோடு உயர விகிதம் பார்கோடு வகை B/w ஐ செயல்படுத்தவும் பார்கோடு படம் முழுமையாக கருப்பு மற்றும் வெள்ளை நிறமாக இருக்கும், மேலும் பயன்பாட்டின் கருப்பொருளால் வண்ணமயமாக்காது எந்த பார்கோடு (qr, ean, aztec,…) வருடு செய்து அதன் உள்ளடக்கத்தைப் பெறுங்கள் அல்லது புதிய ஒன்றை உருவாக்க உங்கள் உரையை ஒட்டவும் பார்கோடு எதுவும் கிடைக்கவில்லை உருவாக்கப்பட்ட பார்கோடு இங்கே இருக்கும் ஆடியோ கவர்கள் ஆடியோ கோப்புகளிலிருந்து ஆல்பம் கவர் படங்களை பிரித்தெடுக்கவும், மிகவும் பொதுவான வடிவங்கள் ஆதரிக்கப்படுகின்றன தொடங்க ஆடியோவைத் தேர்ந்தெடுக்கவும் ஆடியோவைத் தேர்ந்தெடுங்கள் கவர்கள் எதுவும் கிடைக்கவில்லை பதிவுகள் அனுப்பவும் பயன்பாட்டு பதிவுகள் கோப்பைப் பகிர சொடுக்கு செய்க, இது சிக்கலைக் கண்டறியவும் சிக்கல்களை சரிசெய்யவும் எனக்கு உதவும் அச்சச்சோ… ஏதோ தவறு நடந்தது கீழே உள்ள விருப்பங்களைப் பயன்படுத்தி நீங்கள் என்னை தொடர்பு கொள்ளலாம், மேலும் தீர்வைக் கண்டுபிடிக்க முயற்சிப்பேன். \n(பதிவுகளை இணைக்க மறக்காதீர்கள்) கோப்புக்கு எழுதுங்கள் படங்களின் தொகுப்பிலிருந்து உரையை பிரித்தெடுத்து ஒரு உரை கோப்பில் சேமிக்கவும் மெட்டாடேட்டாவுக்கு எழுதுங்கள் ஒவ்வொரு படத்திலிருந்தும் உரையைப் பிரித்தெடுத்து, உறவினர் புகைப்படங்களின் exif தகவலில் வைக்கவும் கண்ணுக்கு தெரியாத பயன்முறை உங்கள் படங்களின் பைட்டுகளுக்குள் கண் கண்ணுக்கு தெரியாத வாட்டர்மார்க்சை உருவாக்க ச்டிகனோகிராஃபி பயன்படுத்தவும் LSB ஐப் பயன்படுத்தவும் எல்.எச்.பி (குறைவான குறிப்பிடத்தக்க பிட்) ச்டிகனோகிராபி முறை பயன்படுத்தப்படும், இல்லையெனில், இல்லையெனில் பயன்படுத்தப்படும் ஆட்டோ சிவப்பு கண்களை அகற்றவும் கடவுச்சொல் திறக்க PDF பாதுகாக்கப்படுகிறது செயல்பாடு கிட்டத்தட்ட முடிந்தது. இப்போது ரத்து செய்ய அதை மறுதொடக்கம் செய்ய வேண்டும் தேதி மாற்றியமைக்கப்பட்டது தேதி மாற்றியமைக்கப்பட்ட (தலைகீழ்) அளவு அளவு (தலைகீழ்) மைம் வகை மைம் வகை (தலைகீழ்) நீட்டிப்பு நீட்டிப்பு (தலைகீழ்) தேதி சேர்க்கப்பட்டது தேதி சேர்க்கப்பட்டது (தலைகீழ்) இடமிருந்து வலமாக வலமிருந்து இடமாக மேலிருந்து கீழே கீழே முதல் திரவ கண்ணாடி அண்மைக் காலத்தில் அறிவிக்கப்பட்ட ஐஇமு 26 ஐ அடிப்படையாகக் கொண்ட ஒரு சுவிட்ச் மற்றும் இது திரவ கண்ணாடி வடிவமைப்பு அமைப்பு கீழே படத்தைத் தேர்ந்தெடுக்கவும் அல்லது BASE64 தரவை கீழே எடுக்கவும்/இறக்குமதி செய்யவும் தொடங்க பட இணைப்பைத் தட்டச்சு செய்க இணைப்பை ஒட்டவும் கெலிடோச்கோப் இரண்டாம் நிலை கோணம் பக்கங்களும் சேனல் கலவை நீல பச்சை சிவப்பு நீலம் பச்சை சிவப்பு சிவப்பு நிறத்தில் பச்சை நிறத்தில் நீல நிறத்தில் சியான் மெசந்தா மஞ்சள் வண்ண ஆல்ஃபோன் விளிம்பு நிலைகள் ஈடுசெய்யும் வோரோனோய் படிகமாக்குகிறார் வடிவம் நீட்டிக்க சீரற்ற தன்மை சர்வாதிகாரம் பரவுகிறது நாய் இரண்டாவது ஆரம் சமப்படுத்தவும் பளபளப்பு சுழல் மற்றும் பிஞ்ச் சுட்டிக்காட்டுங்கள் எல்லை நிறம் துருவ ஆயங்கள், முனை ஆயங்கள் துருவத்திற்கு செவ்வகம் செவ்வகத்திற்கு துருவ வட்டத்தில் தலைகீழ் சத்தத்தைக் குறைக்கவும் எளிய சோலரைச் நெசவு ஃச் இடைவெளி ஒய் இடைவெளி ஃச் அகலம் ஒய் அகலம் சுழல் ரப்பர் முத்திரை ச்மியர் அடர்த்தி கலக்க கோள லென்ச் விலகல் ஒளிவிலகல் அட்டவணை வில் கோணத்தை பரப்பவும் சிறு தீப்பொறி கதிர்கள் ASCII சரிவு மேரி இலையுதிர் காலம் எலும்பு தாரைப் பறனை குளிர்காலம் கடல் கோடை காலம் வேனில் குளிர் மாறுபாடு எச்.எச்.வி. இளஞ்சிவப்பு சூடான பேசுங்கள் கற்குழம்பு இன்ஃபெர்னோ மின்மம் விரிடிச் சிடிச் அந்தி அந்தி மாற்றப்பட்டது முன்னோக்கு ஆட்டோ திட்டம் பயிர் அனுமதிக்கவும் பயிர் அல்லது முன்னோக்கு தனி, சார்பிலா டர்போ ஆழமான பச்சை லென்ச் திருத்தம் சாதொபொகு வடிவத்தில் இலக்கு லென்ச் சுயவிவர கோப்பு தயாராக லென்ச் சுயவிவரங்களைப் பதிவிறக்கவும் பகுதி பெர்சென்ட் சாதொபொகு ஆக ஏற்றுமதி சாதொபொகு பிரதிநிதித்துவமாக ஒரு தட்டு தரவுடன் சரத்தை நகலெடுக்கவும் மடிப்பு செதுக்குதல் முகப்புத் திரை பூட்டுத் திரை உள்ளமைக்கப்பட்ட வால்பேப்பர்கள் ஏற்றுமதி புதுப்பிப்பு தற்போதைய வீடு, பூட்டு மற்றும் உள்ளமைக்கப்பட்ட வால்பேப்பர்களைப் பெறுங்கள் எல்லா கோப்புகளுக்கும் அணுகலை அனுமதிக்கவும், வால்பேப்பர்களை மீட்டெடுக்க இது தேவை வெளிப்புற சேமிப்பக அனுமதியை நிர்வகித்தல் போதாது, உங்கள் படங்களை அணுக அனுமதிக்க வேண்டும், \"அனைத்தையும் அனுமதிக்கவும்\" என்பதைத் தேர்ந்தெடுக்கவும் கோப்பு பெயருக்கு முன்னமைவைச் சேர்க்கவும் பட கோப்பு பெயருக்கு தேர்ந்தெடுக்கப்பட்ட முன்னமைவுடன் பின்னொட்டைச் சேர்க்கிறது கோப்பு பெயருக்கு பட அளவிலான பயன்முறையைச் சேர்க்கவும் தேர்ந்தெடுக்கப்பட்ட பட அளவுகோல் பயன்முறையுடன் பின்னொட்டு பட கோப்பு பெயருக்குச் சேர்க்கிறது தபஅநிகு கலை படத்தைப் போல தோற்றமளிக்கும் படத்தை ASCII உரையாக மாற்றவும் அளவுரு சில சந்தர்ப்பங்களில் சிறந்த முடிவுக்கு படத்திற்கு எதிர்மறை வடிகட்டியைப் பயன்படுத்துகிறது திரைக்காட்சி செயலாக்க திரைக்காட்சி பிடிக்கப்படவில்லை, மீண்டும் முயற்சிக்கவும் சேமிப்பு தவிர்க்கப்பட்டது %1$s கோப்புகள் தவிர்க்கப்பட்டன பெரியதாக இருந்தால் தவிர்க்க அனுமதிக்கவும் இதன் விளைவாக கோப்பு அளவு அசலை விட பெரியதாக இருந்தால் சில கருவிகள் படங்களை சேமிப்பதைத் தவிர்க்க அனுமதிக்கப்படும் காலண்டர் நிகழ்வு தொடர்பு மின்னஞ்சல் இடம் தொலைபேசி உரை எச்.எம்.எச் வலைமுகவரி இல் திறந்த பிணையம் இதற்கில்லை SSID தொலைபேசி செய்தி முகவரி பொருள் உடல் பெயர் நிறுவனம் தலைப்பு தொலைபேசிகள் மின்னஞ்சல்கள் URLகள் முகவரிகள் சுருக்கம் விவரம் இடம் அமைப்பாளர் தொடக்க தேதி முடிவு தேதி நிலைமை அகலாங்கு நெட்டாங்கு பார்கோடு உருவாக்கவும் பார்கோடு திருத்தவும் வைஃபை உள்ளமைவு பாதுகாப்பு தொடர்பைத் தேர்ந்தெடுக்கவும் தேர்ந்தெடுக்கப்பட்ட தொடர்பைப் பயன்படுத்தி தானாக நிரப்ப அமைப்புகளில் தொடர்புகளுக்கு இசைவு வழங்கவும் தொடர்பு செய்தி முதல் பெயர் நடுத்தர பெயர் கடைசி பெயர் உச்சரிப்பு தொலைபேசியைச் சேர்க்கவும் மின்னஞ்சலைச் சேர்க்கவும் முகவரியைச் சேர்க்கவும் இணையதளம் இணையதளத்தைச் சேர்க்கவும் வடிவமைக்கப்பட்ட பெயர் பார்கோடு மேலே வைக்க இந்தப் படம் பயன்படுத்தப்படும் குறியீடு தனிப்பயனாக்கம் இந்தப் படம் QR குறியீட்டின் மையத்தில் லோகோவாகப் பயன்படுத்தப்படும் சின்னம் லோகோ திணிப்பு லோகோ அளவு லோகோ மூலைகள் நான்காவது கண் கீழ் முனை மூலையில் நான்காவது கண்ணைச் சேர்ப்பதன் மூலம் qr குறியீட்டில் கண் சமச்சீர் சேர்க்கிறது படப்புள்ளி வடிவம் சட்ட வடிவம் பந்து வடிவம் பிழை திருத்த நிலை அடர் நிறம் வெளிர் நிறம் ஐப்பர் ஓஎச் Xiaomi HyperOS போன்ற பாணி முகமூடி முறை இந்தக் குறியீட்டை ஸ்கேன் செய்ய முடியாமல் போகலாம், எல்லாச் சாதனங்களிலும் இதைப் படிக்கக்கூடிய வகையில் தோற்ற அளவுருக்களை மாற்றவும் ஸ்கேன் செய்ய முடியாது கருவிகள் மிகவும் கச்சிதமாக இருக்க முகப்புத் திரை ஆப் லாஞ்சர் போல இருக்கும் துவக்கி பயன்முறை தேர்ந்தெடுக்கப்பட்ட தூரிகை மற்றும் பாணியுடன் ஒரு பகுதியை நிரப்புகிறது வெள்ளம் நிரப்பு தெளிக்கவும் கிராஃபிட்டி பாணியிலான பாதையை வரைகிறது சதுர துகள்கள் ஸ்ப்ரே துகள்கள் வட்டங்களுக்கு பதிலாக சதுர வடிவில் இருக்கும் தட்டு கருவிகள் படத்திலிருந்து அடிப்படை/பொருளை உருவாக்கவும் அல்லது வெவ்வேறு தட்டு வடிவங்களில் இறக்குமதி/ஏற்றுமதி தட்டு திருத்தவும் பல்வேறு வடிவங்களில் ஏற்றுமதி/இறக்குமதி தட்டு வண்ண பெயர் தட்டு பெயர் தட்டு வடிவம் உருவாக்கப்பட்ட தட்டுகளை வெவ்வேறு வடிவங்களுக்கு ஏற்றுமதி செய்யவும் தற்போதைய தட்டுக்கு புதிய வண்ணத்தைச் சேர்க்கிறது %1$s வடிவம் தட்டுப் பெயரை வழங்குவதை ஆதரிக்காது Play Store கொள்கைகள் காரணமாக, இந்த அம்சத்தை தற்போதைய கட்டமைப்பில் சேர்க்க முடியாது. இந்தச் செயல்பாட்டை அணுக, மாற்று மூலத்திலிருந்து ImageToolboxஐப் பதிவிறக்கவும். கீழே உள்ள GitHub இல் கிடைக்கும் உருவாக்கங்களைக் காணலாம். கிதுப் பக்கத்தைத் திறக்கவும் தேர்ந்தெடுக்கப்பட்ட கோப்புறையில் சேமிப்பதற்குப் பதிலாக அசல் கோப்பு புதியதாக மாற்றப்படும் மறைக்கப்பட்ட வாட்டர்மார்க் உரை கண்டறியப்பட்டது மறைக்கப்பட்ட வாட்டர்மார்க் படம் கண்டறியப்பட்டது இந்த படம் மறைக்கப்பட்டது உருவாக்கும் ஓவியம் OpenCV ஐ நம்பாமல், AI மாதிரியைப் பயன்படுத்தி ஒரு படத்தில் உள்ள பொருட்களை அகற்ற உங்களை அனுமதிக்கிறது. இந்த அம்சத்தைப் பயன்படுத்த, ஆப்ஸ் GitHub இலிருந்து தேவையான மாதிரியை (~200 MB) பதிவிறக்கும் OpenCV ஐ நம்பாமல், AI மாதிரியைப் பயன்படுத்தி ஒரு படத்தில் உள்ள பொருட்களை அகற்ற உங்களை அனுமதிக்கிறது. இது நீண்ட கால நடவடிக்கையாக இருக்கலாம் பிழை நிலை பகுப்பாய்வு ஒளிர்வு சாய்வு சராசரி தூரம் நகலெடு கண்டறிதல் தக்கவைத்துக்கொள் திறன்மிக்கது கிளிப்போர்டு தரவு மிகவும் பெரியதாக உள்ளது தரவு நகலெடுக்க மிகவும் பெரியது எளிய நெசவு பிக்சலைசேஷன் நிலைகுலைந்த பிக்சலைசேஷன் குறுக்கு பிக்சலைசேஷன் மைக்ரோ மேக்ரோ பிக்சலைசேஷன் சுற்றுப்பாதை பிக்சலைசேஷன் சுழல் பிக்சலைசேஷன் பல்ஸ் கிரிட் பிக்சலைசேஷன் நியூக்ளியஸ் பிக்சலைசேஷன் ரேடியல் வீவ் பிக்சலைசேஷன் uri \"%1$s\" ஐ திறக்க முடியவில்லை பனிப்பொழிவு முறை இயக்கப்பட்டது பார்டர் ஃப்ரேம் தடுமாற்றம் மாறுபாடு சேனல் ஷிப்ட் அதிகபட்ச ஆஃப்செட் விஎச்எஸ் தடுமாற்றம் தொகுதி அளவு CRT வளைவு வளைவு குரோமா பிக்சல் உருகும் மேக்ஸ் டிராப் AI கருவிகள் AI மாதிரிகள் மூலம் படங்களைச் செயலாக்க பல்வேறு கருவிகள் கலைப் பொருட்களை அகற்றுதல் அல்லது நீக்குதல் போன்றவை சுருக்க, துண்டிக்கப்பட்ட கோடுகள் கார்ட்டூன்கள், ஒளிபரப்பு சுருக்கம் பொது சுருக்கம், பொது இரைச்சல் நிறமற்ற கார்ட்டூன் சத்தம் வேகமான, பொதுவான சுருக்கம், பொது இரைச்சல், அனிமேஷன்/காமிக்ஸ்/அனிம் புத்தக ஸ்கேனிங் வெளிப்பாடு திருத்தம் பொதுவான சுருக்க, வண்ணப் படங்களில் சிறந்தது பொதுவான சுருக்கம், கிரேஸ்கேல் படங்கள் ஆகியவற்றில் சிறந்தது பொதுவான சுருக்கம், கிரேஸ்கேல் படங்கள், வலுவானது பொது இரைச்சல், வண்ண படங்கள் பொது இரைச்சல், வண்ணப் படங்கள், சிறந்த விவரங்கள் பொது இரைச்சல், கிரேஸ்கேல் படங்கள் பொதுவான சத்தம், கிரேஸ்கேல் படங்கள், வலிமையானது பொதுவான சத்தம், கிரேஸ்கேல் படங்கள், வலிமையானது பொது சுருக்கம் பொது சுருக்கம் டெக்ஸ்டுரைசேஷன், h264 சுருக்கம் VHS சுருக்கம் தரமற்ற சுருக்கம் (சினிபாக், எம்எஸ்வீடியோ1, ரோக்) பிங்க் சுருக்கம், வடிவவியலில் சிறந்தது பிங்க் சுருக்கம், வலுவானது பிங்க் சுருக்க, மென்மையானது, விவரங்களைத் தக்கவைக்கிறது படிக்கட்டு-படி விளைவை நீக்குதல், மென்மையாக்குதல் ஸ்கேன் செய்யப்பட்ட கலை/வரைபடங்கள், லேசான சுருக்கம், மோயர் கலர் பேண்டிங் மெதுவாக, ஹால்ஃப்டோன்களை நீக்குகிறது கிரேஸ்கேல்/பிடபிள்யூ படங்களுக்கான ஜெனரல் கலரைசர், சிறந்த முடிவுகளுக்கு DDColor ஐப் பயன்படுத்தவும் விளிம்பு அகற்றுதல் அதிகப்படியான கூர்மையை நீக்குகிறது மெதுவான, சலிப்பு மாற்று மாற்று, பொது கலைப்பொருட்கள், CGI KDM003 செயலாக்கத்தை ஸ்கேன் செய்கிறது இலகுரக படத்தை மேம்படுத்தும் மாதிரி சுருக்க கலைப்பொருளை அகற்றுதல் சுருக்க கலைப்பொருளை அகற்றுதல் மென்மையான முடிவுகளுடன் கட்டு அகற்றுதல் ஹாஃப்டோன் மாதிரி செயலாக்கம் டிதர் பேட்டர்ன் அகற்றுதல் V3 JPEG கலைப்பொருள் அகற்றுதல் V2 H.264 அமைப்பு விரிவாக்கம் VHS கூர்மைப்படுத்துதல் மற்றும் மேம்படுத்துதல் இணைத்தல் துண்டு அளவு ஒன்றுடன் ஒன்று அளவு %1$s pxக்கு மேலான படங்கள் வெட்டப்பட்டு, துண்டுகளாகச் செயலாக்கப்படும், காணக்கூடிய சீம்களைத் தடுக்க இவற்றை ஒன்றுடன் ஒன்று இணைக்கும். பெரிய அளவுகள் குறைந்த-இறுதி சாதனங்களுடன் உறுதியற்ற தன்மையை ஏற்படுத்தும் தொடங்குவதற்கு ஒன்றைத் தேர்ந்தெடுக்கவும் %1$s மாதிரியை நீக்க விரும்புகிறீர்களா? நீங்கள் அதை மீண்டும் பதிவிறக்கம் செய்ய வேண்டும் உறுதிப்படுத்தவும் மாதிரிகள் பதிவிறக்கம் செய்யப்பட்ட மாதிரிகள் கிடைக்கும் மாதிரிகள் தயாராகிறது செயலில் உள்ள மாதிரி அமர்வைத் திறக்க முடியவில்லை .onnx/.ort மாதிரிகளை மட்டுமே இறக்குமதி செய்ய முடியும் இறக்குமதி மாதிரி மேலும் பயன்படுத்த தனிப்பயன் onnx மாதிரியை இறக்குமதி செய்யவும், onnx/ort மாதிரிகள் மட்டுமே ஏற்றுக்கொள்ளப்படும், கிட்டத்தட்ட அனைத்து esrgan போன்ற மாறுபாடுகளையும் ஆதரிக்கிறது இறக்குமதி செய்யப்பட்ட மாதிரிகள் பொது இரைச்சல், வண்ண படங்கள் பொதுவான சத்தம், வண்ணப் படங்கள், வலிமையானது பொதுவான சத்தம், வண்ண படங்கள், வலிமையானது கலைப்பொருட்கள் மற்றும் வண்ணப் பிணைப்பைக் குறைக்கிறது, மென்மையான சாய்வு மற்றும் தட்டையான வண்ணப் பகுதிகளை மேம்படுத்துகிறது. இயற்கையான வண்ணங்களைப் பாதுகாக்கும் போது சமநிலை சிறப்பம்சங்களுடன் படத்தின் பிரகாசம் மற்றும் மாறுபாட்டை மேம்படுத்துகிறது. இருண்ட படங்களைப் பிரகாசமாக்குகிறது, அதே நேரத்தில் விவரங்கள் மற்றும் அதிகப்படியான வெளிப்பாட்டைத் தவிர்க்கிறது. அதிகப்படியான வண்ண டோனிங்கை நீக்குகிறது மற்றும் மிகவும் நடுநிலை மற்றும் இயற்கையான வண்ண சமநிலையை மீட்டெடுக்கிறது. நுண்ணிய விவரங்கள் மற்றும் அமைப்புகளைப் பாதுகாப்பதில் முக்கியத்துவத்துடன் பாய்சன் அடிப்படையிலான இரைச்சல் டோனிங்கைப் பயன்படுத்துகிறது. மென்மையான மற்றும் குறைவான ஆக்ரோஷமான காட்சி முடிவுகளுக்கு மென்மையான பாய்சன் இரைச்சல் டோனிங்கைப் பயன்படுத்துகிறது. சீரான இரைச்சல் டோனிங் விவரம் பாதுகாப்பு மற்றும் படத்தின் தெளிவு ஆகியவற்றில் கவனம் செலுத்துகிறது. நுட்பமான அமைப்பு மற்றும் மென்மையான தோற்றத்திற்காக மென்மையான சீரான இரைச்சல் டோனிங். கலைப்பொருட்களை மீண்டும் வர்ணம் பூசுவதன் மூலமும், படத்தின் நிலைத்தன்மையை மேம்படுத்துவதன் மூலமும் சேதமடைந்த அல்லது சீரற்ற பகுதிகளை சரிசெய்கிறது. லைட்வெயிட் டிபாண்டிங் மாடல், இது குறைந்த செயல்திறன் செலவில் வண்ணப் பட்டையை நீக்குகிறது. மேம்பட்ட தெளிவுக்காக மிக உயர்ந்த சுருக்க கலைப்பொருட்கள் (0-20% தரம்) கொண்ட படங்களை மேம்படுத்துகிறது. உயர் சுருக்க கலைப்பொருட்கள் (20-40% தரம்) கொண்ட படங்களை மேம்படுத்துகிறது, விவரங்களை மீட்டமைக்கிறது மற்றும் சத்தத்தைக் குறைக்கிறது. மிதமான சுருக்கத்துடன் படங்களை மேம்படுத்துகிறது (40-60% தரம்), கூர்மை மற்றும் மென்மையை சமநிலைப்படுத்துகிறது. நுட்பமான விவரங்கள் மற்றும் அமைப்புகளை மேம்படுத்த, ஒளி சுருக்கத்துடன் (60-80% தரம்) படங்களைச் செம்மைப்படுத்துகிறது. இயற்கையான தோற்றத்தையும் விவரங்களையும் பாதுகாக்கும் அதே வேளையில் இழப்பற்ற படங்களை (80-100% தரம்) சற்று மேம்படுத்துகிறது. எளிய மற்றும் வேகமான வண்ணமயமாக்கல், கார்ட்டூன்கள், சிறந்தவை அல்ல கலைப்பொருட்களை அறிமுகப்படுத்தாமல், பட மங்கலை சிறிது குறைக்கிறது, கூர்மையை மேம்படுத்துகிறது. நீண்ட கால செயல்பாடுகள் படத்தை செயலாக்குகிறது செயலாக்கம் மிகக் குறைந்த தரமான படங்களில் (0-20%) கனமான JPEG சுருக்கக் கலைப்பொருட்களை நீக்குகிறது. மிகவும் சுருக்கப்பட்ட படங்களில் (20-40%) வலுவான JPEG கலைப்பொருட்களைக் குறைக்கிறது. பட விவரங்களை (40-60%) பாதுகாக்கும் போது மிதமான JPEG கலைப்பொருட்களை சுத்தம் செய்கிறது. ஒளி JPEG கலைப்பொருட்களை மிகவும் உயர்தர படங்களில் (60-80%) செம்மைப்படுத்துகிறது. சிறிய JPEG கலைப்பொருட்களை கிட்டத்தட்ட இழப்பற்ற படங்களில் (80-100%) நுட்பமாகக் குறைக்கிறது. சிறந்த விவரங்கள் மற்றும் அமைப்புகளை மேம்படுத்துகிறது, கனமான கலைப்பொருட்கள் இல்லாமல் உணரப்பட்ட கூர்மையை மேம்படுத்துகிறது. செயலாக்கம் முடிந்தது செயலாக்கம் தோல்வியடைந்தது வேகத்திற்கு உகந்ததாக, இயற்கையான தோற்றத்தை வைத்து, தோல் அமைப்புகளையும் விவரங்களையும் மேம்படுத்துகிறது. JPEG சுருக்க கலைப்பொருட்களை நீக்குகிறது மற்றும் சுருக்கப்பட்ட புகைப்படங்களுக்கான படத்தின் தரத்தை மீட்டெடுக்கிறது. குறைந்த வெளிச்சத்தில் எடுக்கப்பட்ட புகைப்படங்களில் ISO சத்தத்தைக் குறைக்கிறது, விவரங்களைப் பாதுகாக்கிறது. அதிகப்படியான அல்லது \"ஜம்போ\" சிறப்பம்சங்களை சரிசெய்து சிறந்த டோனல் சமநிலையை மீட்டெடுக்கிறது. கிரேஸ்கேல் படங்களுக்கு இயற்கையான வண்ணங்களைச் சேர்க்கும் இலகுரக மற்றும் வேகமான வண்ணமயமாக்கல் மாதிரி. DEJPEG டெனோயிஸ் வண்ணமயமாக்கு கலைப்பொருட்கள் மேம்படுத்து அசையும் ஸ்கேன் செய்கிறது மேல்தட்டு பொதுப் படங்களுக்கான X4 அப்ஸ்கேலர்; குறைந்த GPU மற்றும் நேரத்தைப் பயன்படுத்தும் சிறிய மாடல், மிதமான தேய்மானம் மற்றும் denoise. பொதுவான படங்கள், இழைமங்களைப் பாதுகாத்தல் மற்றும் இயற்கையான விவரங்களுக்கு X2 அப்ஸ்கேலர். மேம்படுத்தப்பட்ட இழைமங்கள் மற்றும் யதார்த்தமான முடிவுகளுடன் கூடிய பொதுவான படங்களுக்கான X4 அப்ஸ்கேலர். அனிம் படங்களுக்கு உகந்ததாக X4 அப்ஸ்கேலர்; கூர்மையான கோடுகள் மற்றும் விவரங்களுக்கு 6 RRDB தொகுதிகள். MSE இழப்புடன் கூடிய X4 அப்ஸ்கேலர், மென்மையான முடிவுகளைத் தருகிறது மற்றும் பொதுவான படங்களுக்குக் குறைக்கப்பட்ட கலைப்பொருட்கள். X4 Upscaler அனிம் படங்களுக்கு உகந்ததாக உள்ளது; கூர்மையான விவரங்கள் மற்றும் மென்மையான கோடுகள் கொண்ட 4B32F மாறுபாடு. பொதுப் படங்களுக்கான X4 UltraSharp V2 மாதிரி; கூர்மை மற்றும் தெளிவை வலியுறுத்துகிறது. X4 அல்ட்ராஷார்ப் V2 லைட்; வேகமான மற்றும் சிறிய, குறைவான GPU நினைவகத்தைப் பயன்படுத்தும் போது விவரங்களைப் பாதுகாக்கிறது. விரைவான பின்னணியை அகற்றுவதற்கான இலகுரக மாதிரி. சீரான செயல்திறன் மற்றும் துல்லியம். உருவப்படங்கள், பொருள்கள் மற்றும் காட்சிகளுடன் வேலை செய்கிறது. பெரும்பாலான பயன்பாட்டு நிகழ்வுகளுக்கு பரிந்துரைக்கப்படுகிறது. பிஜியை அகற்று கிடைமட்ட பார்டர் தடிமன் செங்குத்து பார்டர் தடிமன் %1$s நிறம் %1$s வண்ணங்கள் தற்போதைய மாடல் துண்டிப்பதை ஆதரிக்காது, படம் அசல் பரிமாணங்களில் செயலாக்கப்படும், இது அதிக நினைவக நுகர்வு மற்றும் குறைந்த-இறுதி சாதனங்களில் சிக்கல்களை ஏற்படுத்தலாம் துண்டிப்பு முடக்கப்பட்டது, படம் அசல் பரிமாணங்களில் செயலாக்கப்படும், இது அதிக நினைவக நுகர்வு மற்றும் குறைந்த-இறுதி சாதனங்களில் சிக்கல்களை ஏற்படுத்தலாம் ஆனால் அனுமானத்தில் சிறந்த முடிவுகளை அளிக்கலாம் துண்டித்தல் பின்னணியை அகற்றுவதற்கான உயர் துல்லியமான படப் பிரிவு மாதிரி U2Net இன் இலகுரக பதிப்பு, சிறிய நினைவகப் பயன்பாட்டுடன் வேகமான பின்னணியை அகற்றும். முழு DDColor மாதிரியானது குறைந்தபட்ச கலைப்பொருட்கள் கொண்ட பொதுவான படங்களுக்கு உயர்தர வண்ணமயமாக்கலை வழங்குகிறது. அனைத்து வண்ணமயமாக்கல் மாதிரிகளின் சிறந்த தேர்வு. DDColor பயிற்சி பெற்ற மற்றும் தனிப்பட்ட கலை தரவுத்தொகுப்புகள்; குறைவான யதார்த்தமற்ற வண்ண கலைப்பொருட்களுடன் மாறுபட்ட மற்றும் கலை வண்ணமயமாக்கல் முடிவுகளை உருவாக்குகிறது. துல்லியமான பின்னணியை அகற்றுவதற்காக ஸ்வின் டிரான்ஸ்ஃபார்மரை அடிப்படையாகக் கொண்ட இலகுரக BiRefNet மாதிரி. குறிப்பாக சிக்கலான பொருள்கள் மற்றும் தந்திரமான பின்னணியில் கூர்மையான விளிம்புகள் மற்றும் சிறந்த விவரங்களைப் பாதுகாப்பதன் மூலம் உயர்தர பின்னணி நீக்கம். மென்மையான விளிம்புகள் கொண்ட துல்லியமான முகமூடிகளை உருவாக்கும் பின்னணி அகற்றும் மாதிரி, பொதுவான பொருள்கள் மற்றும் மிதமான விவரங்களைப் பாதுகாப்பதற்கு ஏற்றது. மாடல் ஏற்கனவே பதிவிறக்கம் செய்யப்பட்டுள்ளது மாடல் வெற்றிகரமாக இறக்குமதி செய்யப்பட்டது வகை முக்கிய வார்த்தை மிக வேகமாக இயல்பானது மெதுவாக மிக மெதுவாக சதவீதங்களைக் கணக்கிடுங்கள் குறைந்தபட்ச மதிப்பு %1$s விரல்களால் வரைவதன் மூலம் படத்தை சிதைக்கவும் வார்ப் கடினத்தன்மை வார்ப் பயன்முறை நகர்த்தவும் வளருங்கள் சுருக்கு சுழி CW சுழல் CCW மங்கலான வலிமை டாப் டிராப் பாட்டம் டிராப் துளியைத் தொடங்கு என்ட் டிராப் பதிவிறக்குகிறது மென்மையான வடிவங்கள் மென்மையான, இயற்கையான வடிவங்களுக்கு நிலையான வட்டமான செவ்வகங்களுக்குப் பதிலாக சூப்பர் எலிப்ஸைப் பயன்படுத்தவும் வடிவ வகை வெட்டு வட்டமானது மென்மையானது ரவுண்டிங் இல்லாமல் கூர்மையான விளிம்புகள் கிளாசிக் வட்டமான மூலைகள் வடிவங்களின் வகை மூலைகளின் அளவு அணில் நேர்த்தியான வட்டமான UI கூறுகள் கோப்பு பெயர் வடிவம் திட்டப் பெயர்கள், பிராண்டுகள் அல்லது தனிப்பட்ட குறிச்சொற்களுக்கு ஏற்றவாறு, கோப்பின் பெயரின் தொடக்கத்திலேயே தனிப்பயன் உரை வைக்கப்பட்டுள்ளது. அசல் கோப்பு பெயரை நீட்டிப்பு இல்லாமல் பயன்படுத்துகிறது, இது மூல அடையாளத்தை அப்படியே வைத்திருக்க உதவுகிறது. பிக்சல்களில் உள்ள படத்தின் அகலம், தெளிவுத்திறன் மாற்றங்கள் அல்லது அளவிடுதல் முடிவுகளைக் கண்காணிக்கப் பயன்படுகிறது. பிக்சல்களில் உள்ள படத்தின் உயரம், விகிதங்கள் அல்லது ஏற்றுமதிகளுடன் பணிபுரியும் போது உதவியாக இருக்கும். தனிப்பட்ட கோப்புப் பெயர்களுக்கு உத்தரவாதம் அளிக்க சீரற்ற இலக்கங்களை உருவாக்குகிறது; நகல்களுக்கு எதிராக கூடுதல் பாதுகாப்பிற்காக அதிக இலக்கங்களைச் சேர்க்கவும். தொகுப்பு ஏற்றுமதிகளுக்கான தானியங்கு-அதிகரிப்பு கவுண்டர், ஒரு அமர்வில் பல படங்களைச் சேமிக்கும் போது சிறந்தது. பயன்படுத்தப்பட்ட முன்னமைக்கப்பட்ட பெயரை கோப்பு பெயரில் செருகும், இதன் மூலம் படம் எவ்வாறு செயலாக்கப்பட்டது என்பதை நீங்கள் எளிதாக நினைவில் கொள்ளலாம். செயலாக்கத்தின் போது பயன்படுத்தப்படும் பட அளவிடுதல் பயன்முறையைக் காட்டுகிறது, மறுஅளவிடப்பட்ட, செதுக்கப்பட்ட அல்லது பொருத்தப்பட்ட படங்களை வேறுபடுத்த உதவுகிறது. _v2, _edited, அல்லது _final போன்ற பதிப்பிற்குப் பயன்படும், கோப்புப் பெயரின் இறுதியில் வைக்கப்படும் தனிப்பயன் உரை. கோப்பு நீட்டிப்பு (png, jpg, webp, முதலியன), உண்மையான சேமிக்கப்பட்ட வடிவத்துடன் தானாகவே பொருந்தும். ஒரு தனிப்பயனாக்கக்கூடிய நேர முத்திரை, சரியான வரிசைப்படுத்துதலுக்கான ஜாவா விவரக்குறிப்பு மூலம் உங்கள் சொந்த வடிவமைப்பை வரையறுக்க உதவுகிறது. ஃபிளிங் வகை ஆண்ட்ராய்டு நேட்டிவ் iOS உடை மென்மையான வளைவு விரைவு நிறுத்து துள்ளல் மிதக்கும் ஸ்னாப்பி அல்ட்ரா ஸ்மூத் தழுவல் அணுகல் விழிப்புணர்வு குறைக்கப்பட்ட இயக்கம் நேட்டிவ் ஆண்ட்ராய்டு ஸ்க்ரோல் இயற்பியல் பொதுவான பயன்பாட்டிற்கு சீரான, மென்மையான ஸ்க்ரோலிங் அதிக உராய்வு iOS போன்ற உருள் நடத்தை தனித்துவமான ஸ்க்ரோல் ஃபீலுக்கு தனித்துவமான ஸ்ப்லைன் வளைவு விரைவான நிறுத்தத்துடன் துல்லியமான ஸ்க்ரோலிங் விளையாட்டுத்தனமான, பதிலளிக்கக்கூடிய துள்ளல் உருள் உள்ளடக்க உலாவலுக்கான நீண்ட, சறுக்கும் சுருள்கள் ஊடாடும் UIகளுக்கான விரைவான, பதிலளிக்கக்கூடிய ஸ்க்ரோலிங் நீட்டிக்கப்பட்ட வேகத்துடன் பிரீமியம் மென்மையான ஸ்க்ரோலிங் பறக்கும் வேகத்தின் அடிப்படையில் இயற்பியலைச் சரிசெய்கிறது கணினி அணுகல்தன்மை அமைப்புகளை மதிக்கிறது அணுகல் தேவைகளுக்கான குறைந்தபட்ச இயக்கம் முதன்மை கோடுகள் ஒவ்வொரு ஐந்தாவது வரியும் தடிமனான வரியைச் சேர்க்கிறது நிறத்தை நிரப்பவும் மறைக்கப்பட்ட கருவிகள் பகிர்வுக்காக மறைக்கப்பட்ட கருவிகள் வண்ண நூலகம் வண்ணங்களின் பரந்த தொகுப்பை உலாவவும் இயற்கையான விவரங்களைப் பராமரிக்கும் போது படங்களிலிருந்து கூர்மையாக்குகிறது மற்றும் மங்கலை நீக்குகிறது, கவனம் செலுத்தாத புகைப்படங்களைச் சரிசெய்ய சிறந்தது. முன்னர் மறுஅளவிடப்பட்ட படங்களை புத்திசாலித்தனமாக மீட்டெடுக்கிறது, இழந்த விவரங்கள் மற்றும் அமைப்புகளை மீட்டெடுக்கிறது. நேரடி-நடவடிக்கை உள்ளடக்கத்திற்கு உகந்ததாக, சுருக்க கலைப்பொருட்களைக் குறைக்கிறது மற்றும் திரைப்படம்/டிவி ஷோ பிரேம்களில் சிறந்த விவரங்களை மேம்படுத்துகிறது. VHS-தரமான காட்சிகளை HD க்கு மாற்றுகிறது, டேப் இரைச்சலை நீக்குகிறது மற்றும் விண்டேஜ் உணர்வைப் பாதுகாக்கும் போது தெளிவுத்திறனை மேம்படுத்துகிறது. உரை-கனமான படங்கள் மற்றும் ஸ்கிரீன்ஷாட்களுக்கு நிபுணத்துவம் வாய்ந்தது, எழுத்துக்களைக் கூர்மைப்படுத்துகிறது மற்றும் வாசிப்புத்திறனை மேம்படுத்துகிறது. பலதரப்பட்ட தரவுத்தொகுப்புகளில் பயிற்சியளிக்கப்பட்ட மேம்பட்ட மேம்பாடு, பொது நோக்கத்திற்கான புகைப்பட மேம்பாட்டிற்கு சிறந்தது. இணைய சுருக்கப்பட்ட புகைப்படங்களுக்கு உகந்ததாக உள்ளது, JPEG கலைப்பொருட்களை நீக்குகிறது மற்றும் இயற்கையான தோற்றத்தை மீட்டெடுக்கிறது. சிறந்த அமைப்புப் பாதுகாப்பு மற்றும் கலைப்பொருள் குறைப்பு ஆகியவற்றுடன் இணையப் புகைப்படங்களுக்கான மேம்படுத்தப்பட்ட பதிப்பு. டூயல் அக்ரிகேஷன் டிரான்ஸ்ஃபார்மர் தொழில்நுட்பத்துடன் 2x அப்ஸ்கேலிங், கூர்மை மற்றும் இயற்கை விவரங்களை பராமரிக்கிறது. மேம்பட்ட மின்மாற்றி கட்டமைப்பைப் பயன்படுத்தி 3x உயர்த்துதல், மிதமான விரிவாக்கத் தேவைகளுக்கு ஏற்றது. அதிநவீன மின்மாற்றி நெட்வொர்க்குடன் 4x உயர்தர மேம்பாடு, பெரிய அளவுகளில் சிறந்த விவரங்களைப் பாதுகாக்கிறது. படங்களிலிருந்து மங்கல்/சத்தம் மற்றும் குலுக்கல்களை நீக்குகிறது. பொதுவான நோக்கம் ஆனால் புகைப்படங்களில் சிறந்தது. Swin2SR மின்மாற்றியைப் பயன்படுத்தி குறைந்த தரமான படங்களை மீட்டமைக்கிறது, BSRGAN சிதைவுக்கு உகந்தது. கனமான சுருக்க கலைப்பொருட்களை சரிசெய்வதற்கும், விவரங்களை 4x அளவில் மேம்படுத்துவதற்கும் சிறந்தது. BSRGAN சிதைவு குறித்து பயிற்சியளிக்கப்பட்ட SwinIR மின்மாற்றியுடன் 4x உயர்நிலை. புகைப்படங்கள் மற்றும் சிக்கலான காட்சிகளில் கூர்மையான அமைப்பு மற்றும் இயற்கையான விவரங்களுக்கு GAN ஐப் பயன்படுத்துகிறது. பாதை PDF ஐ இணைக்கவும் பல PDF கோப்புகளை ஒரு ஆவணத்தில் இணைக்கவும் கோப்புகள் ஆர்டர் பக். PDF ஐப் பிரிக்கவும் PDF ஆவணத்திலிருந்து குறிப்பிட்ட பக்கங்களைப் பிரித்தெடுக்கவும் PDF ஐ சுழற்று பக்க நோக்குநிலையை நிரந்தரமாக சரிசெய்யவும் பக்கங்கள் PDF ஐ மறுசீரமைக்கவும் மறுவரிசைப்படுத்த பக்கங்களை இழுத்து விடவும் பக்கங்களைப் பிடித்து இழுக்கவும் பக்க எண்கள் உங்கள் ஆவணங்களில் தானாக எண்ணைச் சேர்க்கவும் லேபிள் வடிவம் PDF to Text (OCR) உங்கள் PDF ஆவணங்களிலிருந்து எளிய உரையைப் பிரித்தெடுக்கவும் பிராண்டிங் அல்லது பாதுகாப்பிற்கான தனிப்பயன் உரை மேலடுக்கு கையெழுத்து எந்த ஆவணத்திலும் உங்கள் மின்னணு கையொப்பத்தைச் சேர்க்கவும் இது கையொப்பமாக பயன்படுத்தப்படும் PDF ஐ திறக்கவும் உங்கள் பாதுகாக்கப்பட்ட கோப்புகளிலிருந்து கடவுச்சொற்களை அகற்றவும் PDF ஐப் பாதுகாக்கவும் வலுவான குறியாக்கத்துடன் உங்கள் ஆவணங்களைப் பாதுகாக்கவும் வெற்றி PDF திறக்கப்பட்டது, நீங்கள் அதை சேமிக்கலாம் அல்லது பகிரலாம் PDF பழுது சிதைந்த அல்லது படிக்க முடியாத ஆவணங்களைச் சரிசெய்யும் முயற்சி கிரேஸ்கேல் அனைத்து ஆவண உட்பொதிக்கப்பட்ட படங்களையும் கிரேஸ்கேலுக்கு மாற்றவும் PDF ஐ சுருக்கவும் எளிதாகப் பகிர உங்கள் ஆவணக் கோப்பின் அளவை மேம்படுத்தவும் ImageToolbox உள் குறுக்கு-குறிப்பு அட்டவணையை மீண்டும் உருவாக்குகிறது மற்றும் புதிதாக கோப்பு கட்டமைப்பை மீண்டும் உருவாக்குகிறது. இது \\"திறக்க முடியாத\\" பல கோப்புகளுக்கான அணுகலை மீட்டெடுக்கும். இந்தக் கருவி அனைத்து ஆவணப் படங்களையும் கிரேஸ்கேலுக்கு மாற்றுகிறது. கோப்பு அளவை அச்சிடுவதற்கும் குறைப்பதற்கும் சிறந்தது மெட்டாடேட்டா சிறந்த தனியுரிமைக்காக ஆவண பண்புகளைத் திருத்தவும் குறிச்சொற்கள் தயாரிப்பாளர் ஆசிரியர் முக்கிய வார்த்தைகள் படைப்பாளி தனியுரிமை ஆழமான சுத்தம் இந்த ஆவணத்திற்கு கிடைக்கக்கூடிய அனைத்து மெட்டாடேட்டாவையும் அழிக்கவும் பக்கம் ஆழமான OCR ஆவணத்திலிருந்து உரையைப் பிரித்தெடுத்து டெஸராக்ட் என்ஜினைப் பயன்படுத்தி ஒரு உரை கோப்பில் சேமிக்கவும் எல்லா பக்கங்களையும் அகற்ற முடியாது PDF பக்கங்களை அகற்று PDF ஆவணத்திலிருந்து குறிப்பிட்ட பக்கங்களை அகற்றவும் அகற்ற தட்டவும் கைமுறையாக செதுக்கு PDF ஆவணப் பக்கங்களை எந்த எல்லைக்கும் செதுக்குங்கள் PDF ஐத் தட்டவும் ஆவணப் பக்கங்களை மதிப்பாய்வு செய்வதன் மூலம் PDF ஐ மாற்ற முடியாததாக மாற்றவும் கேமராவைத் தொடங்க முடியவில்லை. அனுமதிகளைச் சரிபார்த்து, அது வேறொரு ஆப்ஸால் பயன்படுத்தப்படவில்லை என்பதை உறுதிப்படுத்தவும். படங்களை பிரித்தெடுக்கவும் PDFகளில் உட்பொதிக்கப்பட்ட படங்களை அவற்றின் அசல் தெளிவுத்திறனில் பிரித்தெடுக்கவும் இந்த PDF கோப்பில் உட்பொதிக்கப்பட்ட படங்கள் எதுவும் இல்லை இந்தக் கருவி ஒவ்வொரு பக்கத்தையும் ஸ்கேன் செய்து முழுத் தரமான மூலப் படங்களை மீட்டெடுக்கிறது - ஆவணங்களிலிருந்து அசலைச் சேமிப்பதற்கு ஏற்றது கையொப்பத்தை வரையவும் பேனா பரம்ஸ் ஆவணங்களில் வைக்கப்படுவதற்கு சொந்த கையொப்பத்தை படமாக பயன்படுத்தவும் ஜிப் PDF கொடுக்கப்பட்ட இடைவெளியுடன் ஆவணத்தைப் பிரித்து புதிய ஆவணங்களை ஜிப் காப்பகத்தில் பேக் செய்யவும் இடைவெளி PDF ஐ அச்சிடவும் தனிப்பயன் பக்க அளவுடன் அச்சிடுவதற்கு ஆவணத்தைத் தயாரிக்கவும் ஒவ்வொரு தாளுக்கும் பக்கங்கள் நோக்குநிலை பக்க அளவு விளிம்பு ப்ளூம் மென்மையான முழங்கால் அனிம் மற்றும் கார்ட்டூன்களுக்கு உகந்ததாக உள்ளது. மேம்படுத்தப்பட்ட இயற்கை வண்ணங்கள் மற்றும் குறைவான கலைப்பொருட்கள் மூலம் விரைவான மேம்பாடு Samsung One UI 7 போன்ற பாணி விரும்பிய மதிப்பைக் கணக்கிட அடிப்படை கணிதக் குறியீடுகளை இங்கே உள்ளிடவும் (எ.கா. (5+5)*10) கணித வெளிப்பாடு %1$s படங்கள் வரை எடுக்கவும் ஆல்பா வடிவங்களுக்கான பின்னணி நிறம் ஆல்பா ஆதரவுடன் ஒவ்வொரு பட வடிவமைப்பிற்கும் பின்னணி வண்ணத்தை அமைக்கும் திறனைச் சேர்க்கிறது, இது முடக்கப்பட்டால் ஆல்பா அல்லாதவர்களுக்கு மட்டுமே கிடைக்கும் தேதி நேரத்தை வைத்திருங்கள் எப்பொழுதும் தேதி மற்றும் நேரம் தொடர்பான exif குறிச்சொற்களை பாதுகாக்கவும், எக்ஸிஃப் விருப்பத்தை வைத்து சுயாதீனமாக செயல்படும் திட்டத்தைத் திறக்கவும் முன்பு சேமித்த படக் கருவிப்பெட்டி திட்டப்பணியைத் தொடர்ந்து திருத்தவும் பட கருவிப்பெட்டி திட்டத்தை திறக்க முடியவில்லை படக் கருவிப்பெட்டி திட்டத்தில் திட்டத் தரவு இல்லை பட கருவிப்பெட்டி திட்டம் சிதைந்துள்ளது ஆதரிக்கப்படாத பட கருவிப்பெட்டி திட்டப் பதிப்பு: %1$d திட்டத்தை சேமிக்கவும் திருத்தக்கூடிய திட்டக் கோப்பில் அடுக்குகள், பின்னணி மற்றும் திருத்த வரலாற்றை சேமிக்கவும் திறக்க முடியவில்லை தேடக்கூடிய PDFக்கு எழுதவும் படத் தொகுப்பிலிருந்து உரையை அங்கீகரித்து, படம் மற்றும் தேர்ந்தெடுக்கக்கூடிய உரை அடுக்குடன் தேடக்கூடிய PDF ஐச் சேமிக்கவும் அடுக்கு ஆல்பா கிடைமட்ட திருப்பு செங்குத்து ஃபிளிப் பூட்டு நிழலைச் சேர்க்கவும் நிழல் நிறம் உரை வடிவியல் கூர்மையான ஸ்டைலைசேஷன் செய்ய உரையை நீட்டவும் அல்லது வளைக்கவும் அளவு X ஸ்க்யூ எக்ஸ் சிறுகுறிப்புகளை அகற்று PDF பக்கங்களிலிருந்து இணைப்புகள், கருத்துகள், சிறப்பம்சங்கள், வடிவங்கள் அல்லது படிவப் புலங்கள் போன்ற தேர்ந்தெடுக்கப்பட்ட சிறுகுறிப்பு வகைகளை அகற்றவும் ஹைப்பர்லிங்க்கள் கோப்பு இணைப்புகள் கோடுகள் பாப்அப்கள் முத்திரைகள் வடிவங்கள் உரை குறிப்புகள் உரை மார்க்அப் படிவ புலங்கள் மார்க்அப் தெரியவில்லை சிறுகுறிப்புகள் குழுவிலக்கு கட்டமைக்கக்கூடிய வண்ணம் மற்றும் ஆஃப்செட்களுடன் லேயருக்குப் பின்னால் மங்கலான நிழலைச் சேர்க்கவும் ================================================ FILE: core/resources/src/main/res/values-te/strings.xml ================================================ పరిమాణం %1$s యాప్‌ను మూసివేస్తోంది చిత్రాన్ని ఎంచుకోండి చిత్రాన్ని రీసెట్ చేయండి ఎత్తు %1$s మీరు నిజంగా యాప్‌ను మూసివేయాలనుకుంటున్నారా? ప్రారంభించడానికి చిత్రాన్ని ఎంచుకోండి అనువైన వెడల్పు %1$s పొడిగింపు ఉండు ఏదో తప్పు జరిగింది: %1$s నాణ్యత ప్రివ్యూ చేయడానికి చిత్రం చాలా పెద్దదిగా ఉంది, అయితే దీన్ని ఎలాగైనా సేవ్ చేయడానికి ప్రయత్నించబడుతుంది రీసైజ్ రకం చెరిపి వేయి మీరు ఇప్పుడు నిష్క్రమిస్తే, నిల్వ చేయని మార్పులు అన్నీ కోల్పోతారు ఏక సవరణ ఇచ్చిన చిత్ర లక్షణాలు మార్చండి రంగు ఎంచుకోండి చిత్రం నుంచి రంగు ఎంచుకోండి, నకలు చేయండి లేదా పంచుకోండి రంగు రంగు నకలు చేయబడింది కొత్త సంస్కరణ %1$s లోడవుతుంది… చిత్ర మార్పులు యథా స్థానాలకు మార్చబడతాయి ఏదో తప్పు జరిగింది అనువర్తనాన్ని పునఃప్రారంభించు ఇచ్చిన KB పరిమాణం ఆధారంగా చిత్రాన్ని పరిమాణం చేస్తుంది ఇచ్చిన రెండు చిత్రాలని సరిపోల్చి చూడండి చిత్రాలను ఎంచుకోండి అమరికలు మొదలు పెట్టడానికి రెండు చిత్రాలని ఎంచుకోండి ఇది ఎంచుకుంటే, మీరు సవరించడానికి చిత్రం ఎంచుకుంటే, ఈ చిత్ర రంగు అనువర్తననికి స్వీకరించబడుతుంది ఇది ప్రారంభిస్తే ఉపరితల రంగు రాత్రి తీరు లో సంపూర్ణ చీకటికి అమర్చబడుతుంది నీలం చెల్లుబాటు అయ్యే aRGB-కోడ్ ని అతికించండి. అనువదించడానికి సహాయపదండి అనువాద తప్పులను సరిచేయండి లేదా అనువర్తనాన్ని మరొక భాషలోకి స్థానికరించండి %d చిత్రాలను నిల్వ ఉంచడం లో విఫలం సరిహద్దు మందం సరిగ్గా పనిచేయుటకు అనువర్తననికి ఈ అనుమతి అవసరం, దయచేసి మానవీయంగా మంజూరు చేయండి నవీకరణల కోసం వెతకండి ఏమోజీ భాగస్వామ్యం ఇది ప్రారంభిస్తే, ఫైలు పేరుకు వెడల్పు మరియు పొడవు ను జోడిస్తుంది అనువర్తన రంగు పథకాన్ని క్రియాశీలక రీతి లో ఉన్నప్పుడు మార్చడం సాధ్యం కాదు బగ్ నివేదికలు మరియు ఫీచర్ అభ్యర్ధనలు ఇక్కడ పంపండి మీ చిత్రాలను నిల్వ ఉంచడానికి అనువర్తననికి మీ నిల్వ అనుమతి కావాలి, ఇది అవశ్యం. దయచేసి తదుపరి పెట్టె లే అనుమతి మంజూరు చేయండి. మూసివేయి విలువలు సరిగ్గా మార్చబడతాయి పూర్వ స్థితి క్లిప్ బోర్డుకు నకలు చేయండి మినహాయింపు EXIFను సవరించండి సరే EXIF సమాచారం దొరకలేదు గుర్తు జోడించండి ఉంచు EXIFను చెరిపి వేయి రద్దు చిత్రం యొక్క EXIF సమాచారం చెరిపివేయబడుతుంది. ఈ చర్య రద్దు చేయబడదు! ముందే అమార్చబడినవి కత్తిరించు పొదుపు చేస్తోంది కోడ్ మూలం సరికొత్త నవీకరణలు పొందండి, సమస్యలపై చర్చించండి, ఇంకా మరెన్నో చిత్రం చిత్రాన్ని ఏదైనా పరిమితులకు కత్తిరించండి సంస్కరణ EXIFను ఉంచండి చిత్రాలు: %d పరిదృశ్యాన్ని మార్చు తొలగించు ఇచ్చిన చిత్రం నుండి రంగుల ఫలకం రూపొందించండి రంగుల ఫలకం ఫలకం నవీకరించు మద్దతు లేని రకం: %1$s ఇచ్చిన చిత్రం నుంచి రంగుల ఫలకం రూపొందించడం సాధ్యం కాలేదు అసలైనది అవుట్‌పుట్ ఫోల్డర్ వ్యక్తిగతికరించబడినది అనిర్దిష్టమైనది పరికర నిల్వ బరువు ఆధారంగా పరిమాణం చేయండి KB లో గరిష్ట పరిమాణం సరిపోల్చు చీకటి తీరు చీకటి వెలుతురు వ్యవస్థ క్రియాశీల రంగులు వ్యక్తిగతికరించుట చిత్రం మోనెట్‌ను అనుమతించండి భాష Amoled విధానం రంగు పథకం ఎరుపు పచ్చ అతికించడానికి ఏమి లేదు అనువర్తన నేపథ్యం ఎంచుకున్న రంగు పైన ఆధారపడి ఉంటుంది అనువర్తనం గురించి నవీకరణలు ఏమి దొరకలేదు సమస్యల జాడ మీ ప్రశ్న ద్వారా ఏమీ కనుగొనబడలేదు ఇక్కడ వెతకండి ఇది ప్రారంభిస్తే, అనువర్తన రంగులు వాల్ పేపర్ రంగులను స్వీకరిస్తాయి ప్రాధమిక తృతీయ ద్వితీయ ఉపరితలం విలువలు జోడించు అనుమతి మంజూరు బాహ్య నిల్వ మొనెట్ రంగులు ఈ అనువర్తనం సంపూర్ణంగా ఉచితం, కానీ మీరు మద్దతు తెలపాలి అనుకుంటే, ఇక్కడ నొక్కండి FAB అమరిక ఇది ప్రారంభిస్తే, అనువర్తనం తెరిచినప్పుడు నవీకరణ పేటిక చూపబడుతుంది చిత్రాన్ని దగ్గర చేయి ఉపసర్గ ఫైలు పేరు ప్రధాన తెర పై ఏ ఏమోజీ చూపలో ఎంచుకోండి ఫైలు పరిమాణాన్ని జోడించండి EXIF ను తొలగించు మెరుగైన పిక్సెలేషన్ స్ట్రోక్ పిక్సెలేషన్ మెరుగైన డైమండ్ పిక్సెలేషన్ డైమండ్ పిక్సెలేషన్ మెరుగైన గ్లిచ్ ఓరియంటేషన్ & స్క్రిప్ట్ డిటెక్షన్ మాత్రమే ఒకే పదం సర్కిల్ పదం గ్లిచ్ మొత్తం విత్తనం మాస్క్‌ని తొలగించండి \"%1$s\" డైరెక్టరీ కనుగొనబడలేదు, మేము దానిని డిఫాల్ట్‌గా మార్చాము, దయచేసి ఫైల్‌ని మళ్లీ సేవ్ చేయండి క్లిప్‌బోర్డ్ ఆటో పిన్ ఖాళీ ప్రత్యయం వెతకండి ప్రధాన స్క్రీన్‌లో అందుబాటులో ఉన్న అన్ని ఎంపికల ద్వారా శోధించే సామర్థ్యాన్ని ప్రారంభిస్తుంది ఉచిత చిత్రం ప్రివ్యూ ఏ రకమైన చిత్రాలనైనా ప్రివ్యూ చేయండి: GIF, SVG మరియు మొదలైనవి చిత్ర మూలం స్క్రీన్ దిగువన కనిపించే Android ఆధునిక ఫోటో పికర్, Android 12+లో మాత్రమే పని చేస్తుంది. EXIF మెటాడేటాను స్వీకరించడంలో సమస్యలు ఉన్నాయి సాధారణ గ్యాలరీ ఇమేజ్ పికర్. మీ వద్ద మీడియా పికింగ్‌ను అందించే యాప్ ఉంటేనే ఇది పని చేస్తుంది సవరించు ఆర్డర్ చేయండి ప్రధాన స్క్రీన్‌పై ఎంపికల క్రమాన్ని నిర్ణయిస్తుంది ఎమోజీల లెక్క క్రమం సంఖ్య అసలు ఫైల్ పేరు అసలు ఫైల్ పేరును జోడించండి క్రమం సంఖ్యను భర్తీ చేయండి ప్రారంభించబడితే, అవుట్‌పుట్ చిత్రం పేరులో అసలు ఫైల్ పేరును జోడిస్తుంది ప్రారంభించబడితే, మీరు బ్యాచ్ ప్రాసెసింగ్‌ని ఉపయోగిస్తే, స్టాండర్డ్ టైమ్‌స్టాంప్‌ని ఇమేజ్ సీక్వెన్స్ నంబర్‌కి భర్తీ చేస్తుంది ఫోటో పికర్ ఇమేజ్ సోర్స్ ఎంచుకుంటే అసలు ఫైల్ పేరుని జోడించడం పని చేయదు నెట్ నుండి చిత్రాన్ని లోడ్ చేయండి మీకు కావాలంటే ప్రివ్యూ చేయడానికి, జూమ్ చేయడానికి, సవరించడానికి మరియు సేవ్ చేయడానికి ఇంటర్నెట్ నుండి ఏదైనా చిత్రాన్ని లోడ్ చేయండి. చిత్రం లేదు చిత్రం లింక్ పూరించండి ఫిట్ కంటెంట్ స్కేల్ వెడల్పు మరియు ఎత్తు పరామితి ద్వారా అందించబడిన ఇమేజ్‌కి ప్రతి చిత్రాన్ని బలవంతం చేస్తుంది - కారక నిష్పత్తిని మార్చవచ్చు వెడల్పు లేదా ఎత్తు పరామితి ద్వారా అందించబడిన పొడవాటి వైపు ఉన్న చిత్రాలకు చిత్రాల పరిమాణాన్ని మారుస్తుంది, సేవ్ చేసిన తర్వాత అన్ని పరిమాణ గణనలు చేయబడతాయి - కారక నిష్పత్తిని ఉంచుతుంది ప్రకాశం విరుద్ధంగా రంగు సంతృప్తత ఫిల్టర్‌ని జోడించండి ఫిల్టర్ చేయండి ఇచ్చిన చిత్రాలకు ఏదైనా ఫిల్టర్ చైన్‌ని వర్తింపజేయండి ఫిల్టర్లు కాంతి రంగు ఫిల్టర్ ఆల్ఫా బహిరంగపరచడం తెలుపు సంతులనం ఉష్ణోగ్రత లేతరంగు మోనోక్రోమ్ గామా ముఖ్యాంశాలు మరియు నీడలు ముఖ్యాంశాలు పొగమంచు ప్రభావం దూరం వాలు పదును పెట్టండి సెపియా ప్రతికూలమైనది సోలారైజ్ చేయండి కంపనం నలుపు మరియు తెలుపు క్రాస్‌షాచ్ అంతరం లైన్ వెడల్పు సోబెల్ అంచు బ్లర్ హాఫ్టోన్ CGA కలర్స్పేస్ గాస్సియన్ బ్లర్ బాక్స్ బ్లర్ ద్వైపాక్షిక బ్లర్ ఎంబాస్ లాప్లాసియన్ వ్యాసార్థం విగ్నేట్ ప్రారంభించండి ముగింపు స్కేల్ కువహరా స్మూత్టింగ్ స్టాక్ బ్లర్ వక్రీకరణ కోణం స్విర్ల్ ఉబ్బెత్తు వ్యాకోచం గోళ వక్రీభవనం వక్రీభవన సూచిక గ్లాస్ స్పియర్ వక్రీభవనం రంగు మాతృక అస్పష్టత పరిమితుల పరిమాణాన్ని మార్చండి స్కెచ్ థ్రెషోల్డ్ పరిమాణీకరణ స్థాయిలు స్మూత్ టూన్ టూన్ పోస్టరైజ్ చేయండి నాన్ గరిష్ట అణచివేత బలహీనమైన పిక్సెల్ చేరిక కన్వల్యూషన్ 3x3 RGB ఫిల్టర్ తప్పుడు రంగు మొదటి రంగు రెండవ రంగు క్రమాన్ని మార్చండి వేగవంతమైన బ్లర్ బ్లర్ సైజు బ్లర్ సెంటర్ x బ్లర్ సెంటర్ y జూమ్ బ్లర్ రంగు సంతులనం ప్రకాశం థ్రెషోల్డ్ మీరు ఫైల్‌ల యాప్‌ని నిలిపివేసారు, ఈ ఫీచర్‌ని ఉపయోగించడానికి దాన్ని యాక్టివేట్ చేయండి గీయండి స్కెచ్‌బుక్‌లో ఉన్నట్లుగా చిత్రంపై గీయండి లేదా నేపథ్యంపైనే గీయండి పెయింట్ రంగు ఆల్ఫాను పెయింట్ చేయండి చిత్రంపై గీయండి ఒక చిత్రాన్ని ఎంచుకుని దానిపై ఏదైనా గీయండి నేపథ్యంలో గీయండి నేపథ్య రంగును ఎంచుకుని, దాని పైన గీయండి నేపథ్య రంగు సాంకేతికలిపి AES క్రిప్టో అల్గోరిథం ఆధారంగా ఏదైనా ఫైల్ (చిత్రం మాత్రమే కాదు) ఎన్‌క్రిప్ట్ చేయండి మరియు డీక్రిప్ట్ చేయండి ఫైల్‌ని ఎంచుకోండి ఎన్‌క్రిప్ట్ చేయండి డీక్రిప్ట్ చేయండి ప్రారంభించడానికి ఫైల్‌ని ఎంచుకోండి డిక్రిప్షన్ ఎన్క్రిప్షన్ కీ ఫైల్ ప్రాసెస్ చేయబడింది ఈ ఫైల్‌ను మీ పరికరంలో నిల్వ చేయండి లేదా మీకు కావలసిన చోట ఉంచడానికి భాగస్వామ్య చర్యను ఉపయోగించండి లక్షణాలు అమలు అనుకూలత AES-256, GCM మోడ్, పాడింగ్ లేదు, 12 బైట్‌లు యాదృచ్ఛిక IVలు. కీలు SHA-3 హాష్‌లుగా ఉపయోగించబడతాయి (256 బిట్‌లు). ఫైల్ పరిమాణం The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. ఇతర ఫైల్ ఎన్‌క్రిప్షన్ సాఫ్ట్‌వేర్ లేదా సేవలకు అనుకూలత హామీ ఇవ్వబడదని దయచేసి గమనించండి. కొద్దిగా భిన్నమైన కీ చికిత్స లేదా సాంకేతికలిపి కాన్ఫిగరేషన్ అననుకూలతకు కారణం కావచ్చు. చెల్లని పాస్‌వర్డ్ లేదా ఎంచుకున్న ఫైల్ ఎన్‌క్రిప్ట్ చేయబడలేదు ఇచ్చిన వెడల్పు మరియు ఎత్తుతో చిత్రాన్ని సేవ్ చేయడానికి ప్రయత్నిస్తే OOM లోపం సంభవించవచ్చు. దీన్ని మీ స్వంత పూచీతో చేయండి మరియు నేను మిమ్మల్ని హెచ్చరించలేదని చెప్పకండి! కాష్ కాష్ పరిమాణం కనుగొనబడింది %1$s ఆటో కాష్ క్లియరింగ్ ప్రారంభించబడితే యాప్ కాష్ యాప్ స్టార్టప్‌లో క్లియర్ చేయబడుతుంది సృష్టించు ఉపకరణాలు రకం ద్వారా సమూహ ఎంపికలు కస్టమ్ జాబితా అమరికకు బదులుగా వాటి రకాన్ని బట్టి ప్రధాన స్క్రీన్‌పై సమూహాల ఎంపికలు ఎంపిక సమూహీకరణ ప్రారంభించబడినప్పుడు అమరికను మార్చలేరు స్క్రీన్‌షాట్‌ని సవరించండి ద్వితీయ అనుకూలీకరణ స్క్రీన్షాట్ ఫాల్‌బ్యాక్ ఎంపిక దాటవేయి కాపీ చేయండి %1$s మోడ్‌లో సేవ్ చేయడం అస్థిరంగా ఉంటుంది, ఎందుకంటే ఇది లాస్‌లెస్ ఫార్మాట్ మీరు ప్రీసెట్ 125ని ఎంచుకున్నట్లయితే, చిత్రం 100% నాణ్యతతో అసలు చిత్రం యొక్క 125% పరిమాణంగా సేవ్ చేయబడుతుంది. మీరు ప్రీసెట్ 50ని ఎంచుకుంటే, చిత్రం 50% పరిమాణం మరియు 50% నాణ్యతతో సేవ్ చేయబడుతుంది. ఫైల్ పేరును యాదృచ్ఛికంగా మార్చండి ప్రారంభించబడితే, అవుట్‌పుట్ ఫైల్ పేరు పూర్తిగా యాదృచ్ఛికంగా ఉంటుంది %2$s పేరుతో %1$s ఫోల్డర్‌కి సేవ్ చేయబడింది %1$s ఫోల్డర్‌లో సేవ్ చేయబడింది యాప్ గురించి చర్చించి, ఇతర వినియోగదారుల నుండి అభిప్రాయాన్ని పొందండి. మీరు ఇక్కడ బీటా అప్‌డేట్‌లు మరియు అంతర్దృష్టులను కూడా పొందవచ్చు. కారక నిష్పత్తి ఇచ్చిన చిత్రం నుండి మాస్క్‌ని రూపొందించడానికి ఈ మాస్క్ రకాన్ని ఉపయోగించండి, దీనికి ఆల్ఫా ఛానెల్ ఉండాలని గమనించండి బ్యాకప్ మరియు పునరుద్ధరించండి బ్యాకప్ మునుపు రూపొందించిన ఫైల్ నుండి యాప్ సెట్టింగ్‌లను పునరుద్ధరించండి పాడైన ఫైల్ లేదా బ్యాకప్ కాదు ఇది మీ సెట్టింగ్‌లను డిఫాల్ట్ విలువలకు రోల్ బ్యాక్ చేస్తుంది. పైన పేర్కొన్న బ్యాకప్ ఫైల్ లేకుండా ఇది రద్దు చేయబడదని గమనించండి. సెట్టింగ్‌లు విజయవంతంగా పునరుద్ధరించబడ్డాయి నన్ను సంప్రదించండి మీరు ఎంచుకున్న రంగు పథకాన్ని తొలగించబోతున్నారు. ఈ ఆపరేషన్ రద్దు చేయబడదు ఫాంట్ ఫాంట్ స్కేల్ డిఫాల్ట్ పెద్ద ఫాంట్ స్కేల్‌లను ఉపయోగించడం వలన UI గ్లిచ్‌లు మరియు సమస్యలు ఏర్పడవచ్చు, అవి పరిష్కరించబడవు. జాగ్రత్తగా వాడండి. అ ఆ ఇ ఈ ఉ ఊ ఋ ఎ ఏ ఐ ఒ ఓ ఔ క ఖ గ ఘ ఙ చ ఛ జ ఝ ఞ ట ఠ డ ఢ ణ త థ ద ధ న ప ఫ బ భ మ య ర ల వ శ ష స హ 0123456789 !? ఆహారం మరియు పానీయం ప్రకృతి మరియు జంతువులు వస్తువులు కార్యకలాపాలు బ్యాక్‌గ్రౌండ్ రిమూవర్ గీయడం ద్వారా చిత్రం నుండి నేపథ్యాన్ని తీసివేయండి లేదా ఆటో ఎంపికను ఉపయోగించండి ఒరిజినల్ ఇమేజ్ మెటాడేటా ఉంచబడుతుంది చిత్రం చుట్టూ ఉన్న పారదర్శక ఖాళీలు కత్తిరించబడతాయి నేపథ్యాన్ని స్వయంచాలకంగా తొలగించండి చిత్రాన్ని పునరుద్ధరించండి ఎరేజ్ మోడ్ నేపథ్యాన్ని తొలగించండి బ్లర్ వ్యాసార్థం డ్రా మోడ్ సమస్యను సృష్టించండి అయ్యో… ఏదో తప్పు జరిగింది. దిగువ ఎంపికలను ఉపయోగించి మీరు నాకు వ్రాయవచ్చు మరియు నేను పరిష్కారాన్ని కనుగొనడానికి ప్రయత్నిస్తాను పరిమాణాన్ని మార్చండి మరియు మార్చండి ఇచ్చిన చిత్రాల పరిమాణాన్ని మార్చండి లేదా వాటిని ఇతర ఫార్మాట్‌లకు మార్చండి. ఒకే చిత్రాన్ని ఎంచుకుంటే EXIF మెటాడేటా కూడా ఇక్కడ సవరించబడుతుంది. ఇది క్రాష్ నివేదికలను మాన్యువల్‌గా సేకరించడానికి యాప్‌ని అనుమతిస్తుంది విశ్లేషణలు అనామక యాప్ వినియోగ గణాంకాలను సేకరించడాన్ని అనుమతించండి ప్రస్తుతం, %1$s ఫార్మాట్ Androidలో EXIF మెటాడేటాను చదవడానికి మాత్రమే అనుమతిస్తుంది. అవుట్‌పుట్ ఇమేజ్ సేవ్ చేయబడినప్పుడు మెటాడేటాను కలిగి ఉండదు. ప్రయత్నం వేచి ఉండండి %1$s విలువ అంటే వేగవంతమైన కుదింపు, దీని ఫలితంగా సాపేక్షంగా పెద్ద ఫైల్ పరిమాణం ఉంటుంది. %2$s అంటే నెమ్మదిగా కుదింపు, ఫలితంగా చిన్న ఫైల్ ఏర్పడుతుంది. సేవ్ చేయడం దాదాపు పూర్తయింది. ఇప్పుడు రద్దు చేస్తే మళ్లీ ఆదా చేయాల్సి ఉంటుంది. అప్‌డేట్ చెకింగ్ ప్రారంభించబడితే బీటా యాప్ వెర్షన్‌లను కలిగి ఉంటుంది బ్రష్ మృదుత్వం చిత్రాలు ఎంటర్ చేసిన పరిమాణానికి మధ్యలో కత్తిరించబడతాయి. చిత్రం ఎంటర్ చేసిన కొలతల కంటే చిన్నగా ఉంటే, ఇచ్చిన నేపథ్య రంగుతో కాన్వాస్ విస్తరించబడుతుంది. చిత్రం కుట్టడం ఒక పెద్దదాన్ని పొందడానికి ఇచ్చిన చిత్రాలను కలపండి కనీసం 2 చిత్రాలను ఎంచుకోండి అవుట్‌పుట్ ఇమేజ్ స్కేల్ చిన్న చిత్రాలను పెద్దదిగా స్కేల్ చేయండి చిత్రం ఓరియంటేషన్ అడ్డంగా నిలువుగా ప్రారంభించబడితే, చిన్న చిత్రాలు వరుసగా పెద్దదానికి స్కేల్ చేయబడతాయి చిత్రాల క్రమం సర్కిల్ పిక్సెలేషన్ రంగును భర్తీ చేయండి ఓరిమి భర్తీ చేయడానికి రంగు లక్ష్య రంగు తీసివేయవలసిన రంగు పండ్ల ముక్కలు విశ్వసనీయత విషయము డిఫాల్ట్ పాలెట్ శైలి, ఇది నాలుగు రంగులను అనుకూలీకరించడానికి అనుమతిస్తుంది, ఇతరులు కీ రంగును మాత్రమే సెట్ చేయడానికి మిమ్మల్ని అనుమతిస్తారు మోనోక్రోమ్ కంటే కొంచెం ఎక్కువ క్రోమాటిక్ శైలి బిగ్గరగా ఉండే థీమ్, ప్రాథమిక పాలెట్‌కు రంగుల రంగు గరిష్టంగా ఉంటుంది, ఇతరులకు పెరిగింది ఉల్లాసభరితమైన థీమ్ - మూలం రంగు యొక్క రంగు థీమ్‌లో కనిపించదు మోనోక్రోమ్ థీమ్, రంగులు పూర్తిగా నలుపు / తెలుపు / బూడిద రంగులో ఉంటాయి చిత్రాలు PDFకి Scheme.primaryContainerలో మూల రంగును ఉంచే పథకం కంటెంట్ స్కీమ్‌కు చాలా పోలి ఉండే స్కీమ్ PDF ఫైల్‌లతో ఆపరేట్ చేయండి: ప్రివ్యూ, చిత్రాల బ్యాచ్‌గా మార్చండి లేదా ఇచ్చిన చిత్రాల నుండి ఒకదాన్ని సృష్టించండి ప్రివ్యూ PDF చిత్రాలకు PDF సాధారణ PDF ప్రివ్యూ ఇచ్చిన అవుట్‌పుట్ ఫార్మాట్‌లో PDFని ఇమేజ్‌లుగా మార్చండి ఇచ్చిన చిత్రాలను అవుట్‌పుట్ PDF ఫైల్‌లో ప్యాక్ చేయండి మీరు ఎంచుకున్న ఫిల్టర్ మాస్క్‌ని తొలగించబోతున్నారు. ఈ ఆపరేషన్ రద్దు చేయబడదు సాధారణ రూపాంతరాలు హైలైటర్ నియాన్ పెన్ గోప్యత బ్లర్ సెమీ పారదర్శక పదును ఉన్న హైలైటర్ మార్గాలను గీయండి మీ డ్రాయింగ్‌లకు కొంత మెరుస్తున్న ప్రభావాన్ని జోడించండి డిఫాల్ట్ ఒకటి, సరళమైనది - కేవలం రంగు మీరు దాచాలనుకునే దేన్నైనా భద్రపరచడానికి గీసిన మార్గం క్రింద చిత్రాన్ని అస్పష్టం చేస్తుంది గోప్యత బ్లర్ లాగానే ఉంటుంది, కానీ బ్లర్ చేయడానికి బదులుగా పిక్సెల్‌లు యాప్ బార్‌లు యాప్ బార్‌ల వెనుక షాడో డ్రాయింగ్‌ను ప్రారంభిస్తుంది %1$s - %2$s పరిధిలో విలువ ఆటో రొటేట్ ఇమేజ్ ఓరియంటేషన్ కోసం పరిమితి పెట్టెని స్వీకరించడానికి అనుమతిస్తుంది ప్రారంభించబడితే, స్వయంచాలకంగా సేవ్ చేయబడిన చిత్రాన్ని క్లిప్‌బోర్డ్‌కు జోడిస్తుంది కంపనం కంపన బలం ఫైల్‌లను ఓవర్‌రైట్ చేయడానికి మీరు \"ఎక్స్‌ప్లోరర్\" ఇమేజ్ సోర్స్‌ని ఉపయోగించాలి, రిపిక్ ఇమేజ్‌లను ట్రై చేయండి, మేము ఇమేజ్ సోర్స్‌ని అవసరమైన దానికి మార్చాము ఫైల్‌లను ఓవర్‌రైట్ చేయండి ఎంచుకున్న ఫోల్డర్‌లో సేవ్ చేయడానికి బదులుగా అసలు ఫైల్ కొత్త దానితో భర్తీ చేయబడుతుంది, ఈ ఎంపికకు ఇమేజ్ సోర్స్ \"Explorer\" లేదా GetContent ఉండాలి, దీన్ని టోగుల్ చేస్తున్నప్పుడు, ఇది స్వయంచాలకంగా సెట్ చేయబడుతుంది క్యాట్ముల్ బిక్యూబిక్ సన్యాసి లాంజోస్ మిచెల్ సమీపంలోని స్ప్లైన్ ప్రాథమిక చిత్రం యొక్క పరిమాణాన్ని మార్చడానికి లీనియర్ (లేదా బైలీనియర్, రెండు కోణాలలో) ఇంటర్‌పోలేషన్ సాధారణంగా మంచిది, కానీ వివరాలు కొంత అవాంఛనీయమైన మృదుత్వం కలిగిస్తుంది మరియు ఇప్పటికీ కొంత బెల్లం ఉంటుంది. మెరుగైన స్కేలింగ్ పద్ధతులలో లాంజోస్ రీసాంప్లింగ్ మరియు మిచెల్-నేట్రావాలి ఫిల్టర్‌లు ఉన్నాయి ప్రతి పిక్సెల్‌ని ఒకే రంగులోని అనేక పిక్సెల్‌లతో భర్తీ చేయడం ద్వారా పరిమాణాన్ని పెంచే సులభమైన మార్గాలలో ఒకటి దాదాపు అన్ని యాప్‌లలో ఉపయోగించే సరళమైన ఆండ్రాయిడ్ స్కేలింగ్ మోడ్ కంట్రోల్ పాయింట్ల సెట్‌ను సజావుగా ఇంటర్‌పోలేట్ చేయడానికి మరియు రీసాంప్లింగ్ చేయడానికి పద్ధతి, సాధారణంగా కంప్యూటర్ గ్రాఫిక్స్‌లో మృదువైన వక్రతలను సృష్టించడానికి ఉపయోగిస్తారు వర్ణపట లీకేజీని తగ్గించడానికి మరియు సిగ్నల్ అంచులను తగ్గించడం ద్వారా ఫ్రీక్వెన్సీ విశ్లేషణ యొక్క ఖచ్చితత్వాన్ని మెరుగుపరచడానికి సిగ్నల్ ప్రాసెసింగ్‌లో విండో ఫంక్షన్ తరచుగా వర్తించబడుతుంది. గణిత ఇంటర్‌పోలేషన్ టెక్నిక్ ఒక మృదువైన మరియు నిరంతర వక్రతను రూపొందించడానికి ఒక వక్ర భాగపు ముగింపు బిందువుల వద్ద విలువలు మరియు ఉత్పన్నాలను ఉపయోగిస్తుంది పిక్సెల్ విలువలకు వెయిటెడ్ సింక్ ఫంక్షన్‌ని వర్తింపజేయడం ద్వారా అధిక-నాణ్యత ఇంటర్‌పోలేషన్‌ను నిర్వహించే రీసాంప్లింగ్ పద్ధతి స్కేల్ చేయబడిన ఇమేజ్‌లో షార్ప్‌నెస్ మరియు యాంటీ-అలియాసింగ్ మధ్య సమతుల్యతను సాధించడానికి సర్దుబాటు చేయగల పారామితులతో కన్వల్యూషన్ ఫిల్టర్‌ని ఉపయోగించే రీసాంప్లింగ్ పద్ధతి వక్రరేఖ లేదా ఉపరితలం, అనువైన మరియు నిరంతర ఆకార ప్రాతినిధ్యాన్ని సజావుగా ఇంటర్‌పోలేట్ చేయడానికి మరియు అంచనా వేయడానికి పీస్‌వైస్-డిఫైన్డ్ పాలినోమియల్ ఫంక్షన్‌లను ఉపయోగిస్తుంది అసలు గమ్యస్థానంలో %1$s పేరుతో ఓవర్‌రైట్ చేయబడిన ఫైల్ మాగ్నిఫైయర్ మెరుగైన ప్రాప్యత కోసం డ్రాయింగ్ మోడ్‌లలో వేలు ఎగువన మాగ్నిఫైయర్‌ని ప్రారంభిస్తుంది ప్రారంభ విలువను బలవంతం చేయండి ఎక్సిఫ్ విడ్జెట్‌ని మొదట్లో తనిఖీ చేయమని బలవంతం చేస్తుంది బహుళ భాషలను అనుమతించండి ఆటో ఓరియంటేషన్ & స్క్రిప్ట్ డిటెక్షన్ ఆటో మాత్రమే దానంతట అదే సింగిల్ కాలమ్ సింగిల్ బ్లాక్ నిలువు వచనం సింగిల్ బ్లాక్ సింగిల్ లైన్ ఒకే అక్షరం చిన్న వచనం స్పేర్ టెక్స్ట్ ఓరియంటేషన్ & స్క్రిప్ట్ డిటెక్షన్ ముడి లైన్ మీరు అన్ని గుర్తింపు రకాల కోసం భాష \"%1$s\" OCR శిక్షణ డేటాను తొలగించాలనుకుంటున్నారా లేదా ఎంచుకున్న ఒక (%2$s) కోసం మాత్రమే? ప్రస్తుత అనుకూలీకరించిన రంగులు మరియు ప్రదర్శన రకంతో ఇచ్చిన అవుట్‌పుట్ పరిమాణం యొక్క ప్రవణతను సృష్టించండి టైల్ మోడ్ పునరావృతమైంది అద్దం బిగింపు డెకాల్ అనుకూలీకరించదగిన టెక్స్ట్/ఇమేజ్ వాటర్‌మార్క్‌లతో చిత్రాలను కవర్ చేయండి ఇచ్చిన స్థానం వద్ద సింగిల్‌కు బదులుగా చిత్రంపై వాటర్‌మార్క్ పునరావృతమవుతుంది ఆఫ్‌సెట్ X ఆఫ్‌సెట్ Y టెక్స్ట్ రంగు ప్రారంభించడానికి GIF చిత్రాన్ని ఎంచుకోండి మొదటి ఫ్రేమ్ పరిమాణాన్ని ఉపయోగించండి నిష్క్రమణలో కంటెంట్‌ను దాచిపెడుతుంది, స్క్రీన్ క్యాప్చర్ చేయబడదు లేదా రికార్డ్ చేయబడదు క్వాంటిజైయర్ గ్రే స్కేల్ బేయర్ టూ బై టూ డైథరింగ్ ఫ్లాయిడ్ స్టెయిన్‌బర్గ్ డిథరింగ్ జార్విస్ జూడిస్ నింకే డిథరింగ్ రెండు వరుస సియెర్రా డిథరింగ్ అట్కిన్సన్ డిథరింగ్ ఫాల్స్ ఫ్లాయిడ్ స్టెయిన్‌బర్గ్ డిథరింగ్ రాండమ్ డిథరింగ్ సాధారణ థ్రెషోల్డ్ డైథరింగ్ ఒక వక్రత లేదా ఉపరితలం, అనువైన మరియు నిరంతర ఆకార ప్రాతినిధ్యాన్ని సజావుగా ఇంటర్‌పోలేట్ చేయడానికి మరియు అంచనా వేయడానికి పీస్‌వైస్-డిఫైన్డ్ బైక్యూబిక్ బహుపది ఫంక్షన్‌లను ఉపయోగిస్తుంది స్థానిక స్టాక్ బ్లర్ టిల్ట్ షిఫ్ట్ అనాగ్లిఫ్ శబ్దం పిక్సెల్ క్రమబద్ధీకరణ షఫుల్ చేయండి ఛానల్ షిఫ్ట్ X ఛానల్ షిఫ్ట్ వై అవినీతి పరిమాణం అవినీతి షిఫ్ట్ X అవినీతి షిఫ్ట్ వై టెంట్ బ్లర్ సైడ్ ఫేడ్ వైపు టాప్ దిగువన బలం స్ఫటికీకరించండి వ్యాప్తి మార్బుల్ అల్లకల్లోలం నూనె కలర్ మ్యాట్రిక్స్ 4x4 కలర్ మ్యాట్రిక్స్ 3x3 పోలరాయిడ్ ట్రిటోనోమలీ డ్యూటోరోమలీ ప్రోటోనోమలీ పాతకాలపు బ్రౌనీ కోడా క్రోమ్ రాత్రి దృష్టి వెచ్చగా కూల్ ట్రిటానోపియా డ్యూటారోనోటోపియా ప్రొటానోపియా అక్రోమటోమలీ అక్రోమాటోప్సియా పదును తీసివేయు పాస్టెల్ ఆరెంజ్ హేజ్ పింక్ కల గోల్డెన్ అవర్ వేడి వేసవి పర్పుల్ మిస్ట్ సూర్యోదయం సాఫ్ట్ స్ప్రింగ్ లైట్ ఆటం టోన్లు లావెండర్ డ్రీం సైబర్‌పంక్ నిమ్మరసం లైట్ స్పెక్ట్రల్ ఫైర్ రాత్రి మేజిక్ ఫాంటసీ ల్యాండ్‌స్కేప్ రంగు పేలుడు రెయిన్బో వరల్డ్ డీప్ పర్పుల్ స్పేస్ పోర్టల్ రెడ్ స్విర్ల్ డిజిటల్ కోడ్ కార్డ్‌ల ప్రముఖ చిహ్నాల క్రింద ఎంచుకున్న ఆకారంతో కంటైనర్‌ను జోడిస్తుంది ఐకాన్ ఆకారం డ్రాగో ఆల్డ్రిడ్జ్ కత్తిరించిన ఉచిమురా మోబియస్ పరివర్తన శిఖరం రంగు క్రమరాహిత్యం చిత్రాలు అసలు గమ్యస్థానంలో భర్తీ చేయబడ్డాయి ఓవర్‌రైట్ ఫైల్‌ల ఎంపిక ప్రారంభించబడినప్పుడు చిత్ర ఆకృతిని మార్చలేరు రంగు పథకం వలె ఎమోజి మాన్యువల్‌గా నిర్వచించిన వాటికి బదులుగా ఎమోజి ప్రాథమిక రంగును యాప్ కలర్ స్కీమ్‌గా ఉపయోగిస్తుంది ఇక్కడ ప్రీసెట్ అవుట్‌పుట్ ఫైల్ %ని నిర్ణయిస్తుంది, అనగా మీరు 5mb ఇమేజ్‌పై ప్రీసెట్ 50ని ఎంచుకుంటే, సేవ్ చేసిన తర్వాత మీరు 2.5mb ఇమేజ్‌ని పొందుతారు టెలిగ్రామ్ చాట్ క్రాప్ మాస్క్ పునరుద్ధరించు మీ యాప్ సెట్టింగ్‌లను ఫైల్‌కి బ్యాకప్ చేయండి తొలగించు పథకాన్ని తొలగించండి వచనం భావోద్వేగాలు చిహ్నాలు ఎమోజీని ప్రారంభించండి ప్రయాణాలు మరియు ప్రదేశాలు చిత్రాన్ని కత్తిరించండి నేపథ్యాన్ని పునరుద్ధరించండి పైపెట్ గరిష్ట రంగుల సంఖ్య నవీకరణలు బీటాలను అనుమతించండి బాణాలు గీయండి ప్రారంభించబడితే డ్రాయింగ్ మార్గం పాయింటింగ్ బాణం వలె సూచించబడుతుంది మెరుగైన సర్కిల్ పిక్సెలేషన్ రంగును తీసివేయండి రీకోడ్ చేయండి ఈరోడ్ కండక్షన్ అనిసోట్రోపిక్ వ్యాప్తి వ్యాప్తి క్షితిజసమాంతర గాలి స్టాగర్ వేగవంతమైన ద్వైపాక్షిక బ్లర్ పాయిజన్ బ్లర్ లాగరిథమిక్ టోన్ మ్యాపింగ్ స్ట్రోక్ రంగు ఫ్రాక్టల్ గ్లాస్ నీటి ప్రభావం పరిమాణం ఫ్రీక్వెన్సీ X ఫ్రీక్వెన్సీ Y వ్యాప్తి X వ్యాప్తి Y పెర్లిన్ వక్రీకరణ హేబుల్ ఫిల్మిక్ టోన్ మ్యాపింగ్ Hejl బర్గెస్ టోన్ మ్యాపింగ్ ACES ఫిల్మిక్ టోన్ మ్యాపింగ్ ACES హిల్ టోన్ మ్యాపింగ్ అన్నీ పూర్తి ఫిల్టర్ ప్రారంభించండి కేంద్రం ముగింపు ఇచ్చిన ఇమేజ్‌లు లేదా సింగిల్ ఇమేజ్‌కి ఏదైనా ఫిల్టర్ చైన్‌లను వర్తింపజేయండి PDF సాధనాలు గ్రేడియంట్ మేకర్ వేగం డీహేజ్ ఒమేగా యాప్‌ని రేట్ చేయండి రేట్ చేయండి ఈ యాప్ పూర్తిగా ఉచితం, మీరు దీన్ని పెద్దదిగా చేయాలనుకుంటే, దయచేసి Github 😄లో ప్రాజెక్ట్‌కి నక్షత్రం వేయండి సాధారణ ప్రభావాలు లీనియర్ రేడియల్ స్వీప్ చేయండి గ్రేడియంట్ రకం సెంటర్ X సెంటర్ Y రంగు స్టాప్స్ రంగును జోడించండి లక్షణాలు లాస్సో ఇచ్చిన మార్గం ద్వారా మూసి నిండిన మార్గాన్ని గీస్తుంది పాత్ మోడ్‌ని గీయండి డబుల్ లైన్ బాణం ఉచిత డ్రాయింగ్ డబుల్ బాణం లైన్ బాణం బాణం లైన్ ఇన్‌పుట్ విలువగా మార్గాన్ని గీస్తుంది ప్రారంభ స్థానం నుండి ముగింపు బిందువు వరకు మార్గాన్ని లైన్‌గా గీస్తుంది ప్రారంభ స్థానం నుండి ముగింపు బిందువు వరకు పాయింటింగ్ బాణాన్ని లైన్‌గా గీస్తుంది ఇచ్చిన మార్గం నుండి పాయింటింగ్ బాణాన్ని గీస్తుంది స్టార్ట్ పాయింట్ నుండి ఎండ్ పాయింట్ వరకు డబుల్ పాయింటింగ్ బాణాన్ని లైన్‌గా గీస్తుంది ఇచ్చిన మార్గం నుండి డబుల్ పాయింటింగ్ బాణాన్ని గీస్తుంది వివరించిన ఓవల్ వివరించిన రెక్ట్ ఓవల్ రెక్ట్ ప్రారంభ స్థానం నుండి ముగింపు బిందువు వరకు నేరుగా గీస్తుంది ప్రారంభ బిందువు నుండి ముగింపు బిందువు వరకు ఓవల్ గీస్తుంది ప్రారంభ బిందువు నుండి ముగింపు బిందువు వరకు వివరించిన అండాకారాన్ని గీస్తుంది ప్రారంభ స్థానం నుండి ముగింపు బిందువు వరకు వివరించిన రెక్ట్‌ను గీస్తుంది డిథరింగ్ బేయర్ త్రీ బై త్రీ డైథరింగ్ బేయర్ ఫోర్ బై ఫోర్ డైథరింగ్ బేయర్ ఎయిట్ బై ఎయిట్ డైథరింగ్ సియెర్రా డిథరింగ్ సియెర్రా లైట్ డిథరింగ్ స్టకీ డిథరింగ్ బర్క్స్ డిథరింగ్ ఎడమ నుండి కుడికి డైథరింగ్ మాస్క్ రంగు మాస్క్ ప్రివ్యూ మీకు సుమారుగా ఫలితాన్ని చూపడానికి డ్రా ఫిల్టర్ మాస్క్ రెండర్ చేయబడుతుంది స్కేల్ మోడ్ బైలీనియర్ హాన్ డిఫాల్ట్ విలువ సిగ్మా ప్రాదేశిక సిగ్మా మధ్యస్థ బ్లర్ క్లిప్ మాత్రమే నిల్వకు సేవ్ చేయడం అమలు చేయబడదు మరియు చిత్రం క్లిప్‌బోర్డ్‌లో మాత్రమే ఉంచడానికి ప్రయత్నించబడుతుంది ప్రకాశం అమలు స్క్రీన్ గ్రేడియంట్ ఓవర్‌లే ఇచ్చిన చిత్రం పైభాగంలో ఏదైనా గ్రేడియంట్‌ని కంపోజ్ చేయండి రూపాంతరాలు కెమెరా చిత్రాన్ని తీయడానికి కెమెరాను ఉపయోగిస్తుంది, ఈ ఇమేజ్ సోర్స్ నుండి ఒక చిత్రాన్ని మాత్రమే పొందడం సాధ్యమవుతుందని గమనించండి ధాన్యం రంగుల స్విర్ల్ ఎలక్ట్రిక్ గ్రేడియంట్ కారామెల్ డార్క్నెస్ ఫ్యూచరిస్టిక్ గ్రేడియంట్ ఆకుపచ్చ సూర్యుడు వాటర్‌మార్కింగ్ వాటర్‌మార్క్‌ని పునరావృతం చేయండి వాటర్‌మార్క్ రకం ఈ చిత్రం వాటర్‌మార్కింగ్ కోసం నమూనాగా ఉపయోగించబడుతుంది అతివ్యాప్తి మోడ్ ఫైల్‌ల పాస్‌వర్డ్ ఆధారిత ఎన్‌క్రిప్షన్. కొనసాగించబడిన ఫైల్‌లు ఎంచుకున్న డైరెక్టరీలో నిల్వ చేయబడతాయి లేదా భాగస్వామ్యం చేయబడతాయి. డీక్రిప్ట్ చేసిన ఫైల్‌లను కూడా నేరుగా తెరవవచ్చు. చిత్రాన్ని ఎంచుకోవడానికి GetContent ఉద్దేశాన్ని ఉపయోగించండి. ప్రతిచోటా పని చేస్తుంది, కానీ కొన్ని పరికరాలలో ఎంచుకున్న చిత్రాలను స్వీకరించడంలో సమస్యలు ఉన్నట్లు తెలిసింది. అది నా తప్పు కాదు. కారక నిష్పత్తిని సేవ్ చేస్తున్నప్పుడు ఇచ్చిన వెడల్పు మరియు ఎత్తు పరిమితులను అనుసరించడానికి ఎంచుకున్న చిత్రాల పరిమాణాన్ని మార్చండి పిక్సెల్ పరిమాణం లాక్ డ్రా ఓరియంటేషన్ డ్రాయింగ్ మోడ్‌లో ప్రారంభించబడితే, స్క్రీన్ తిప్పబడదు బోకె GIF సాధనాలు చిత్రాలను GIF చిత్రంగా మార్చండి లేదా ఇచ్చిన GIF చిత్రం నుండి ఫ్రేమ్‌లను సంగ్రహించండి చిత్రాలకు GIF GIF ఫైల్‌ను చిత్రాల బ్యాచ్‌గా మార్చండి చిత్రాల బ్యాచ్‌ని GIF ఫైల్‌గా మార్చండి GIFకి చిత్రాలు పేర్కొన్న పరిమాణాన్ని మొదటి ఫ్రేమ్ కొలతలతో భర్తీ చేయండి రిపీట్ కౌంట్ ఫ్రేమ్ ఆలస్యం మిల్లీస్ FPS లాస్సో ఉపయోగించండి ఎరేసింగ్ చేయడానికి డ్రాయింగ్ మోడ్‌లో లాస్సోని ఉపయోగిస్తుంది ఒరిజినల్ ఇమేజ్ ప్రివ్యూ ఆల్ఫా ముసుగులు మాస్క్ జోడించండి మాస్క్ %d మాస్క్ ఫిల్టర్ ఇచ్చిన మాస్క్‌డ్ ఏరియాల్లో ఫిల్టర్ చైన్‌లను వర్తింపజేయండి, ప్రతి మాస్క్ ఏరియా దాని స్వంత ఫిల్టర్‌ల సెట్‌ను గుర్తించగలదు స్పష్టమైన ఎంపికల అమరిక నీడలు పైకి చూడు దానం యాప్ బార్ ఎమోజి ఎంపిక చేసిన దాన్ని ఉపయోగించకుండా యాదృచ్ఛికంగా నిరంతరం మార్చబడుతుంది యాదృచ్ఛిక ఎమోజీలు ఎమోజీలు నిలిపివేయబడినప్పుడు యాదృచ్ఛిక ఎమోజి పికింగ్‌ని ఉపయోగించలేరు యాదృచ్ఛికంగా ప్రారంభించబడిన ఎమోజీని ఎంచుకునే సమయంలో ఎమోజీని ఎంచుకోలేరు తాజాకరణలకోసం ప్రయత్నించండి డిఫాల్ట్ ఇమెయిల్ ఏదైనా చిత్రాల సెట్ నుండి EXIF మెటాడేటాను తొలగించండి ఫోటో పికర్ గ్యాలరీ ఫైల్ ఎక్స్‌ప్లోరర్ పాత టీవీ బ్లర్ షఫుల్ చేయండి OCR (వచనాన్ని గుర్తించండి) అందించిన చిత్రం నుండి వచనాన్ని గుర్తించండి, 120+ భాషలకు మద్దతు ఉంది చిత్రంలో వచనం లేదు లేదా యాప్ దానిని కనుగొనలేదు Accuracy: %1$s గుర్తింపు రకం వేగంగా ప్రామాణికం ఉత్తమమైనది సమాచారం లేదు Tesseract OCR యొక్క సరైన పనితీరు కోసం అదనపు శిక్షణ డేటా (%1$s) మీ పరికరానికి డౌన్‌లోడ్ చేయబడాలి. \nమీరు %2$s డేటాను డౌన్‌లోడ్ చేయాలనుకుంటున్నారా? డౌన్‌లోడ్ చేయండి కనెక్షన్ లేదు, రైలు నమూనాలను డౌన్‌లోడ్ చేయడానికి దాన్ని తనిఖీ చేసి, మళ్లీ ప్రయత్నించండి డౌన్‌లోడ్ చేయబడిన భాషలు అందుబాటులో ఉన్న భాషలు సెగ్మెంటేషన్ మోడ్ బ్రష్ చెరిపివేయడానికి బదులుగా నేపథ్యాన్ని పునరుద్ధరిస్తుంది క్షితిజసమాంతర గ్రిడ్ నిలువు గ్రిడ్ స్టిచ్ మోడ్ వరుసల కౌంట్ నిలువు వరుసల సంఖ్య పిక్సెల్ స్విచ్ ఉపయోగించండి మీరు ఆధారితమైన Google మెటీరియల్‌కు బదులుగా పిక్సెల్ లాంటి స్విచ్ ఉపయోగించబడుతుంది స్లయిడ్ పక్కపక్కన టోగుల్ ట్యాప్ పారదర్శకత ఇష్టమైన ఇంకా ఇష్టమైన ఫిల్టర్‌లు ఏవీ జోడించబడలేదు బి స్ప్లైన్ రెగ్యులర్ అంచులను అస్పష్టం చేయండి ప్రారంభించబడితే దాని చుట్టూ ఉన్న ఖాళీలను ఒకే రంగుకు బదులుగా పూరించడానికి అసలైన చిత్రం కింద అస్పష్టమైన అంచులను గీస్తుంది పిక్సెలేషన్ విలోమ పూరక రకం ప్రారంభించబడితే, మాస్క్ లేని ప్రాంతాలన్నీ డిఫాల్ట్ ప్రవర్తనకు బదులుగా ఫిల్టర్ చేయబడతాయి కాన్ఫెట్టి కాన్ఫెట్టి సేవ్ చేయడం, భాగస్వామ్యం చేయడం మరియు ఇతర ప్రాథమిక చర్యలపై చూపబడుతుంది సురక్షిత మోడ్ పాలెట్ శైలి టోనల్ స్పాట్ తటస్థ వైబ్రంట్ వ్యక్తీకరణ ఇంద్రధనస్సు కంటైనర్లు కంటైనర్‌ల వెనుక షాడో డ్రాయింగ్‌ను ప్రారంభిస్తుంది స్లయిడర్‌లు స్విచ్‌లు FABలు బటన్లు స్లయిడర్‌ల వెనుక షాడో డ్రాయింగ్‌ని ప్రారంభిస్తుంది స్విచ్‌ల వెనుక షాడో డ్రాయింగ్‌ని ప్రారంభిస్తుంది ఫ్లోటింగ్ యాక్షన్ బటన్‌ల వెనుక షాడో డ్రాయింగ్‌ను ప్రారంభిస్తుంది డిఫాల్ట్ బటన్‌ల వెనుక షాడో డ్రాయింగ్‌ని ప్రారంభిస్తుంది కొత్త అప్‌డేట్ అందుబాటులో ఉందో లేదో తనిఖీ చేయడానికి ఈ అప్‌డేట్ చెకర్ GitHubకి కనెక్ట్ అవుతుంది శ్రద్ధ ఫేడింగ్ ఎడ్జెస్ వికలాంగుడు రెండు విలోమ రంగులు ప్రారంభించబడితే, థీమ్ రంగులను ప్రతికూల వాటికి భర్తీ చేస్తుంది బయటకి దారి మీరు ఇప్పుడు ప్రివ్యూను వదిలివేస్తే, మీరు మళ్లీ చిత్రాలను జోడించాలి చిత్రం ఫార్మాట్ చిత్రం నుండి \"Material You \" పాలెట్ సృష్టిస్తుంది ముదురు రంగులు బదులుగా కాంతి వేరియంట్ రాత్రి మోడ్ రంగు పథకం ఉపయోగిస్తుంది \"Jetpack Compose\" కోడ్గా కాపీ చేయండి రింగ్ బ్లర్ క్రాస్ బ్లర్ సర్కిల్ అస్పష్టత నక్షత్ర అస్పష్టత సరళ వంపు మార్పు తొలగించు టాగ్లు మోషన్ బ్లర్ APNGకి చిత్రాలు ప్రారంభించడానికి APNG చిత్రాన్ని ఎంచుకోండి APNG సాధనాలు చిత్రాలను APNG చిత్రానికి మార్చండి లేదా ఇచ్చిన APNG చిత్రం నుండి ఫ్రేమ్‌లను సంగ్రహించండి చిత్రాలకు APNG APNG ఫైల్‌ను చిత్రాల బ్యాచ్‌గా మార్చండి చిత్రాల బ్యాచ్‌ని APNG ఫైల్‌గా మార్చండి జిప్ ఇచ్చిన ఫైల్‌లు లేదా చిత్రాల నుండి జిప్ ఫైల్‌ను సృష్టించండి హ్యాండిల్ వెడల్పును లాగండి కాన్ఫెట్టి రకం పండుగ పేలుడు వర్షం మూలలు JXL సాధనాలు నాణ్యత నష్టం లేకుండా JXL ~ JPEG ట్రాన్స్‌కోడింగ్ చేయండి లేదా GIF/APNGని JXL యానిమేషన్‌గా మార్చండి JXL నుండి JPEG JXL నుండి JPEGకి లాస్‌లెస్ ట్రాన్స్‌కోడింగ్‌ను అమలు చేయండి JPEG నుండి JXL వరకు లాస్‌లెస్ ట్రాన్స్‌కోడింగ్‌ను అమలు చేయండి JPEG నుండి JXL ప్రారంభించడానికి JXL చిత్రాన్ని ఎంచుకోండి ఫాస్ట్ గాస్సియన్ బ్లర్ 2D ఫాస్ట్ గాస్సియన్ బ్లర్ 3D ఫాస్ట్ గాస్సియన్ బ్లర్ 4D కారు ఈస్టర్ క్లిప్‌బోర్డ్ డేటాను ఆటోమేటిక్‌గా అతికించడానికి యాప్‌ని అనుమతిస్తుంది, కనుక ఇది ప్రధాన స్క్రీన్‌పై కనిపిస్తుంది మరియు మీరు దీన్ని ప్రాసెస్ చేయగలరు హార్మోనైజేషన్ రంగు హార్మోనైజేషన్ స్థాయి లాంజోస్ బెస్సెల్ పిక్సెల్ విలువలకు బెస్సెల్ (జింక్) ఫంక్షన్‌ని వర్తింపజేయడం ద్వారా అధిక-నాణ్యత ఇంటర్‌పోలేషన్‌ను నిర్వహించే రీసాంప్లింగ్ పద్ధతి GIF నుండి JXL GIF చిత్రాలను JXL యానిమేటెడ్ చిత్రాలుగా మార్చండి APNG నుండి JXL APNG చిత్రాలను JXL యానిమేటెడ్ చిత్రాలుగా మార్చండి చిత్రాలకు JXL JXL యానిమేషన్‌ను చిత్రాల బ్యాచ్‌గా మార్చండి JXLకి చిత్రాలు చిత్రాల బ్యాచ్‌ని JXL యానిమేషన్‌కి మార్చండి ప్రవర్తన ఫైల్ ఎంపికను దాటవేయి ఎంచుకున్న స్క్రీన్‌పై ఇది సాధ్యమైతే ఫైల్ పికర్ వెంటనే చూపబడుతుంది ప్రివ్యూలను రూపొందించండి ప్రివ్యూ జనరేషన్‌ని ప్రారంభిస్తుంది, ఇది కొన్ని పరికరాల్లో క్రాష్‌లను నివారించడంలో సహాయపడవచ్చు, ఇది సింగిల్ ఎడిట్ ఆప్షన్‌లో కొన్ని ఎడిటింగ్ ఫంక్షనాలిటీని కూడా డిసేబుల్ చేస్తుంది లాస్సీ కంప్రెషన్ లాస్‌లెస్‌కి బదులుగా ఫైల్ పరిమాణాన్ని తగ్గించడానికి లాస్సీ కంప్రెషన్‌ని ఉపయోగిస్తుంది కుదింపు రకం ఫలితంగా ఇమేజ్ డీకోడింగ్ వేగాన్ని నియంత్రిస్తుంది, ఇది ఫలిత చిత్రాన్ని వేగంగా తెరవడంలో సహాయపడుతుంది, %1$s విలువ అంటే నెమ్మదిగా డీకోడింగ్ అవుతుంది, అయితే %2$s - వేగంగా, ఈ సెట్టింగ్ అవుట్‌పుట్ చిత్ర పరిమాణాన్ని పెంచుతుంది క్రమబద్ధీకరణ తేదీ తేదీ (రివర్స్డ్) పేరు పేరు (రివర్స్డ్) ఛానెల్‌ల కాన్ఫిగరేషన్ ఈరోజు నిన్న ఎంబెడెడ్ పికర్ ఇమేజ్ టూల్‌బాక్స్ యొక్క ఇమేజ్ పికర్ అనుమతులు లేవు అభ్యర్థన బహుళ మీడియాను ఎంచుకోండి సింగిల్ మీడియాను ఎంచుకోండి ఎంచుకోండి మళ్లీ ప్రయత్నించండి ల్యాండ్‌స్కేప్‌లో సెట్టింగ్‌లను చూపించు ఇది నిలిపివేయబడితే, ల్యాండ్‌స్కేప్ మోడ్ సెట్టింగ్‌లలో శాశ్వతంగా కనిపించే ఎంపికకు బదులుగా ఎగువ యాప్ బార్‌లోని బటన్‌పై ఎప్పటిలాగే తెరవబడుతుంది పూర్తి స్క్రీన్ సెట్టింగ్‌లు దీన్ని ప్రారంభించండి మరియు సెట్టింగ్‌ల పేజీ ఎల్లప్పుడూ స్లైడబుల్ డ్రాయర్ షీట్‌కు బదులుగా పూర్తి స్క్రీన్‌గా తెరవబడుతుంది స్విచ్ రకం కంపోజ్ చేయండి మీరు మారే జెట్‌ప్యాక్ కంపోజ్ మెటీరియల్ మీరు మార్చే మెటీరియల్ గరిష్టంగా యాంకర్ పరిమాణాన్ని మార్చండి పిక్సెల్ నిష్ణాతులు \"ఫ్లూయెంట్\" డిజైన్ సిస్టమ్ ఆధారంగా ఒక స్విచ్ కుపెర్టినో \"Cupertino\" డిజైన్ సిస్టమ్ ఆధారంగా ఒక స్విచ్ SVGకి చిత్రాలు ఇచ్చిన చిత్రాలను SVG చిత్రాలకు గుర్తించండి నమూనా పాలెట్ ఉపయోగించండి ఈ ఎంపిక ప్రారంభించబడితే పరిమాణ పాలెట్ నమూనా చేయబడుతుంది మార్గం విస్మరించబడింది డౌన్‌స్కేలింగ్ లేకుండా పెద్ద చిత్రాలను కనుగొనడం కోసం ఈ సాధనం యొక్క ఉపయోగం సిఫార్సు చేయబడదు, ఇది క్రాష్‌కు కారణమవుతుంది మరియు ప్రాసెసింగ్ సమయాన్ని పెంచుతుంది తక్కువ స్థాయి చిత్రం ప్రాసెస్ చేయడానికి ముందు చిత్రం తక్కువ కొలతలకు తగ్గించబడుతుంది, ఇది సాధనం వేగంగా మరియు సురక్షితంగా పని చేయడానికి సహాయపడుతుంది కనిష్ట రంగు నిష్పత్తి లైన్స్ థ్రెషోల్డ్ క్వాడ్రాటిక్ థ్రెషోల్డ్ కోఆర్డినేట్ రౌండ్ టాలరెన్స్ మార్గం స్కేల్ లక్షణాలను రీసెట్ చేయండి అన్ని లక్షణాలు డిఫాల్ట్ విలువలకు సెట్ చేయబడతాయి, ఈ చర్యను రద్దు చేయడం సాధ్యం కాదని గమనించండి వివరంగా డిఫాల్ట్ లైన్ వెడల్పు ఇంజిన్ మోడ్ వారసత్వం LSTM నెట్‌వర్క్ లెగసీ &amp; LSTM మార్చు ఇమేజ్ బ్యాచ్‌లను ఇచ్చిన ఆకృతికి మార్చండి కొత్త ఫోల్డర్‌ని జోడించండి ప్రతి నమూనాకు బిట్స్ కుదింపు ఫోటోమెట్రిక్ ఇంటర్‌ప్రెటేషన్ ప్రతి పిక్సెల్‌కు నమూనాలు ప్లానర్ కాన్ఫిగరేషన్ Y Cb Cr ఉప నమూనా Y Cb Cr పొజిషనింగ్ X రిజల్యూషన్ Y రిజల్యూషన్ రిజల్యూషన్ యూనిట్ స్ట్రిప్ ఆఫ్‌సెట్‌లు ప్రతి స్ట్రిప్‌కి వరుసలు స్ట్రిప్ బైట్ గణనలు JPEG ఇంటర్‌చేంజ్ ఫార్మాట్ JPEG ఇంటర్‌చేంజ్ ఫార్మాట్ పొడవు బదిలీ ఫంక్షన్ వైట్ పాయింట్ ప్రాథమిక క్రోమాటిటీస్ Y Cb Cr గుణకాలు సూచన బ్లాక్ వైట్ తేదీ సమయం చిత్ర వివరణ తయారు చేయండి మోడల్ సాఫ్ట్‌వేర్ కళాకారుడు కాపీరైట్ ఎక్సిఫ్ వెర్షన్ Flashpix వెర్షన్ కలర్ స్పేస్ గామా పిక్సెల్ X డైమెన్షన్ పిక్సెల్ Y డైమెన్షన్ పిక్సెల్‌కు కంప్రెస్డ్ బిట్స్ మేకర్ నోట్ వినియోగదారు వ్యాఖ్య సంబంధిత సౌండ్ ఫైల్ తేదీ సమయం అసలైనది తేదీ సమయం డిజిటైజ్ చేయబడింది ఆఫ్‌సెట్ సమయం ఆఫ్‌సెట్ టైమ్ ఒరిజినల్ ఆఫ్‌సెట్ టైమ్ డిజిటైజ్ చేయబడింది ఉప సెకను సమయం ఉప సెకను సమయం అసలైనది సబ్ సెక్షన్ టైమ్ డిజిటైజ్ చేయబడింది బహిర్గతం అయిన సమయం F సంఖ్య ఎక్స్పోజర్ ప్రోగ్రామ్ స్పెక్ట్రల్ సెన్సిటివిటీ ఫోటోగ్రాఫిక్ సున్నితత్వం OECF సున్నితత్వం రకం ప్రామాణిక అవుట్‌పుట్ సున్నితత్వం సిఫార్సు చేయబడిన ఎక్స్‌పోజర్ సూచిక ISO వేగం ISO స్పీడ్ అక్షాంశం yyy ISO స్పీడ్ అక్షాంశం zzz షట్టర్ స్పీడ్ విలువ ఎపర్చరు విలువ ప్రకాశం విలువ ఎక్స్పోజర్ బయాస్ విలువ గరిష్ట ఎపర్చరు విలువ విషయం దూరం మీటరింగ్ మోడ్ ఫ్లాష్ సబ్జెక్ట్ ఏరియా ఫోకల్ లెంగ్త్ ఫ్లాష్ ఎనర్జీ స్పేషియల్ ఫ్రీక్వెన్సీ రెస్పాన్స్ ఫోకల్ ప్లేన్ X రిజల్యూషన్ ఫోకల్ ప్లేన్ Y రిజల్యూషన్ ఫోకల్ ప్లేన్ రిజల్యూషన్ యూనిట్ విషయం స్థానం ఎక్స్పోజర్ ఇండెక్స్ సెన్సింగ్ పద్ధతి ఫైల్ మూలం CFA నమూనా కస్టమ్ రెండర్ చేయబడింది ఎక్స్పోజర్ మోడ్ వైట్ బ్యాలెన్స్ డిజిటల్ జూమ్ నిష్పత్తి ఫోకల్ లెంగ్త్ 35mm ఫిల్మ్ సీన్ క్యాప్చర్ రకం నియంత్రణ పొందండి కాంట్రాస్ట్ సంతృప్తత పదును పరికర సెట్టింగ్ వివరణ విషయ దూర పరిధి చిత్రం ప్రత్యేక ID కెమెరా ఓనర్ పేరు శరీర క్రమ సంఖ్య లెన్స్ స్పెసిఫికేషన్ లెన్స్ మేక్ లెన్స్ మోడల్ లెన్స్ సీరియల్ నంబర్ GPS వెర్షన్ ID GPS అక్షాంశం Ref GPS అక్షాంశం GPS లాంగిట్యూడ్ రెఫ్ GPS లాంగిట్యూడ్ GPS ఎత్తు రెఫ్ GPS ఎత్తు GPS టైమ్ స్టాంప్ GPS ఉపగ్రహాలు GPS స్థితి GPS కొలత మోడ్ GPS DOP GPS స్పీడ్ రెఫ్ GPS వేగం GPS ట్రాక్ Ref GPS ట్రాక్ GPS Img దిశ రెఫ్ GPS Img దిశ GPS మ్యాప్ డేటా GPS డెస్ట్ అక్షాంశం Ref GPS డెస్ట్ అక్షాంశం GPS డెస్ట్ లాంగిట్యూడ్ రెఫ్ GPS డెస్ట్ లాంగిట్యూడ్ GPS డెస్ట్ బేరింగ్ Ref GPS డెస్ట్ బేరింగ్ GPS డెస్ట్ దూరం రెఫ్ GPS డెస్ట్ దూరం GPS ప్రాసెసింగ్ పద్ధతి GPS ప్రాంత సమాచారం GPS తేదీ స్టాంప్ GPS డిఫరెన్షియల్ GPS H స్థాన లోపం ఇంటర్‌ఆపరేబిలిటీ ఇండెక్స్ DNG వెర్షన్ డిఫాల్ట్ క్రాప్ పరిమాణం ప్రివ్యూ చిత్రం ప్రారంభం ప్రివ్యూ చిత్రం పొడవు యాస్పెక్ట్ ఫ్రేమ్ సెన్సార్ బాటమ్ బోర్డర్ సెన్సార్ లెఫ్ట్ బోర్డర్ సెన్సార్ కుడి అంచు సెన్సార్ టాప్ బోర్డర్ ISO ఇచ్చిన ఫాంట్ మరియు రంగుతో మార్గంలో వచనాన్ని గీయండి ఫాంట్ పరిమాణం వాటర్‌మార్క్ పరిమాణం వచనాన్ని పునరావృతం చేయండి ఒక పర్యాయ డ్రాయింగ్‌కు బదులుగా పాత్ ముగిసే వరకు ప్రస్తుత వచనం పునరావృతమవుతుంది డాష్ పరిమాణం ఇచ్చిన మార్గంలో గీయడానికి ఎంచుకున్న చిత్రాన్ని ఉపయోగించండి ఈ చిత్రం గీసిన మార్గం యొక్క పునరావృత ఎంట్రీగా ఉపయోగించబడుతుంది ప్రారంభ బిందువు నుండి ముగింపు బిందువు వరకు వివరించిన త్రిభుజాన్ని గీస్తుంది ప్రారంభ బిందువు నుండి ముగింపు బిందువు వరకు వివరించిన త్రిభుజాన్ని గీస్తుంది వివరించిన త్రిభుజం త్రిభుజం ప్రారంభ స్థానం నుండి ముగింపు బిందువు వరకు బహుభుజిని గీస్తుంది బహుభుజి వివరించిన బహుభుజి ప్రారంభ స్థానం నుండి ముగింపు బిందువు వరకు వివరించిన బహుభుజిని గీస్తుంది శీర్షాలు రెగ్యులర్ బహుభుజి గీయండి ఉచిత రూపానికి బదులుగా రెగ్యులర్‌గా ఉండే బహుభుజిని గీయండి స్టార్ట్ పాయింట్ నుండి ఎండ్ పాయింట్ వరకు నక్షత్రాన్ని గీస్తుంది నక్షత్రం వివరించిన నక్షత్రం ప్రారంభ స్థానం నుండి ముగింపు బిందువు వరకు వివరించిన నక్షత్రాన్ని గీస్తుంది అంతర్గత వ్యాసార్థ నిష్పత్తి రెగ్యులర్ స్టార్ గీయండి ఉచిత ఫారమ్‌కు బదులుగా రెగ్యులర్‌గా ఉండే నక్షత్రాన్ని గీయండి యాంటిలియాస్ పదునైన అంచులను నిరోధించడానికి యాంటీఅలియాసింగ్‌ని ప్రారంభిస్తుంది ప్రివ్యూకి బదులుగా సవరణను తెరవండి మీరు ImageToolboxలో తెరవడానికి (ప్రివ్యూ) చిత్రాన్ని ఎంచుకున్నప్పుడు, ప్రివ్యూకి బదులుగా సవరణ ఎంపిక షీట్ తెరవబడుతుంది డాక్యుమెంట్ స్కానర్ పత్రాలను స్కాన్ చేయండి మరియు వాటి నుండి PDF లేదా ప్రత్యేక చిత్రాలను సృష్టించండి స్కానింగ్ ప్రారంభించడానికి క్లిక్ చేయండి స్కానింగ్ ప్రారంభించండి Pdf గా సేవ్ చేయండి Pdfగా షేర్ చేయండి దిగువన ఉన్న ఎంపికలు చిత్రాలను సేవ్ చేయడం కోసం, PDF కాదు హిస్టోగ్రాం HSVని సమం చేయండి హిస్టోగ్రాంను సమం చేయండి శాతాన్ని నమోదు చేయండి టెక్స్ట్ ఫీల్డ్ ద్వారా నమోదు చేయడానికి అనుమతించండి ప్రీసెట్‌ల ఎంపిక వెనుక ఉన్న టెక్స్ట్ ఫీల్డ్‌ను ఎనేబుల్ చేస్తుంది, వాటిని ఫ్లైలో నమోదు చేయండి స్కేల్ కలర్ స్పేస్ లీనియర్ హిస్టోగ్రాం పిక్సెలేషన్‌ను సమం చేయండి గ్రిడ్ పరిమాణం X గ్రిడ్ పరిమాణం Y హిస్టోగ్రాం అడాప్టివ్‌ను సమం చేయండి హిస్టోగ్రాం అడాప్టివ్ LUVని సమం చేయండి హిస్టోగ్రాం అడాప్టివ్ LABని సమం చేయండి CLAHE CLAHE ల్యాబ్ CLAHE LUV కంటెంట్‌కి కత్తిరించండి ఫ్రేమ్ రంగు విస్మరించాల్సిన రంగు మూస టెంప్లేట్ ఫిల్టర్‌లు జోడించబడలేదు క్రొత్తదాన్ని సృష్టించండి స్కాన్ చేయబడిన QR కోడ్ చెల్లుబాటు అయ్యే ఫిల్టర్ టెంప్లేట్ కాదు QR కోడ్‌ని స్కాన్ చేయండి ఎంచుకున్న ఫైల్‌లో ఫిల్టర్ టెంప్లేట్ డేటా లేదు టెంప్లేట్ సృష్టించండి టెంప్లేట్ పేరు ఈ ఫిల్టర్ టెంప్లేట్‌ని ప్రివ్యూ చేయడానికి ఈ చిత్రం ఉపయోగించబడుతుంది టెంప్లేట్ ఫిల్టర్ QR కోడ్ చిత్రం వలె ఫైల్‌గా ఫైల్‌గా సేవ్ చేయండి QR కోడ్ ఇమేజ్‌గా సేవ్ చేయండి టెంప్లేట్‌ను తొలగించండి మీరు ఎంచుకున్న టెంప్లేట్ ఫిల్టర్‌ను తొలగించబోతున్నారు. ఈ ఆపరేషన్ రద్దు చేయబడదు \"%1$s\" (%2$s) పేరుతో ఫిల్టర్ టెంప్లేట్ జోడించబడింది ఫిల్టర్ ప్రివ్యూ QR &amp; బార్‌కోడ్ QR కోడ్‌ని స్కాన్ చేయండి మరియు దాని కంటెంట్‌ను పొందండి లేదా కొత్తదాన్ని రూపొందించడానికి మీ స్ట్రింగ్‌ను అతికించండి కోడ్ కంటెంట్ ఫీల్డ్‌లోని కంటెంట్‌ని భర్తీ చేయడానికి ఏదైనా బార్‌కోడ్‌ని స్కాన్ చేయండి లేదా ఎంచుకున్న రకంతో కొత్త బార్‌కోడ్‌ను రూపొందించడానికి ఏదైనా టైప్ చేయండి QR వివరణ కనిష్ట QR కోడ్‌ని స్కాన్ చేయడానికి సెట్టింగ్‌లలో కెమెరా అనుమతిని మంజూరు చేయండి డాక్యుమెంట్ స్కానర్‌ని స్కాన్ చేయడానికి సెట్టింగ్‌లలో కెమెరా అనుమతిని మంజూరు చేయండి క్యూబిక్ బి-స్ప్లైన్ హామింగ్ హన్నింగ్ నల్ల మనిషి వెల్చ్ చతుర్భుజం గాస్సియన్ సింహిక బార్ట్లెట్ రాబిడౌక్స్ రాబిడౌక్స్ షార్ప్ స్ప్లైన్ 16 స్ప్లైన్ 36 స్ప్లైన్ 64 కైజర్ బార్ట్లెట్-అతను పెట్టె బోహ్మన్ లాంజోస్ 2 లాంజోస్ 3 లాంజోస్ 4 లాంజోస్ 2 జింక్ లాంజోస్ 3 జింక్ లాంజోస్ 4 జింక్ క్యూబిక్ ఇంటర్‌పోలేషన్ దగ్గరి 16 పిక్సెల్‌లను పరిగణనలోకి తీసుకోవడం ద్వారా సున్నితమైన స్కేలింగ్‌ను అందిస్తుంది, ఇది బిలినియర్ కంటే మెరుగైన ఫలితాలను ఇస్తుంది వక్రరేఖ లేదా ఉపరితలం, అనువైన మరియు నిరంతర ఆకార ప్రాతినిధ్యాన్ని సజావుగా ఇంటర్‌పోలేట్ చేయడానికి మరియు అంచనా వేయడానికి పీస్‌వైస్-డిఫైన్డ్ బహుపది ఫంక్షన్‌లను ఉపయోగిస్తుంది సిగ్నల్ ప్రాసెసింగ్‌లో ఉపయోగపడే సిగ్నల్ అంచులను తగ్గించడం ద్వారా స్పెక్ట్రల్ లీకేజీని తగ్గించడానికి ఉపయోగించే విండో ఫంక్షన్ సిగ్నల్ ప్రాసెసింగ్ అప్లికేషన్‌లలో స్పెక్ట్రల్ లీకేజీని తగ్గించడానికి సాధారణంగా ఉపయోగించే హాన్ విండో యొక్క వేరియంట్ సిగ్నల్ ప్రాసెసింగ్‌లో తరచుగా ఉపయోగించే స్పెక్ట్రల్ లీకేజీని తగ్గించడం ద్వారా మంచి ఫ్రీక్వెన్సీ రిజల్యూషన్‌ను అందించే విండో ఫంక్షన్ తగ్గిన స్పెక్ట్రల్ లీకేజీతో మంచి ఫ్రీక్వెన్సీ రిజల్యూషన్‌ని అందించడానికి రూపొందించబడిన విండో ఫంక్షన్, తరచుగా సిగ్నల్ ప్రాసెసింగ్ అప్లికేషన్‌లలో ఉపయోగించబడుతుంది ఇంటర్‌పోలేషన్ కోసం క్వాడ్రాటిక్ ఫంక్షన్‌ను ఉపయోగించే ఒక పద్ధతి, మృదువైన మరియు నిరంతర ఫలితాలను అందిస్తుంది గాస్సియన్ ఫంక్షన్‌ను వర్తింపజేసే ఇంటర్‌పోలేషన్ పద్ధతి, చిత్రాలలో శబ్దాన్ని సున్నితంగా మరియు తగ్గించడానికి ఉపయోగపడుతుంది కనిష్ట కళాఖండాలతో అధిక-నాణ్యత ఇంటర్‌పోలేషన్‌ను అందించే అధునాతన రీసాంప్లింగ్ పద్ధతి స్పెక్ట్రల్ లీకేజీని తగ్గించడానికి సిగ్నల్ ప్రాసెసింగ్‌లో ఉపయోగించే త్రిభుజాకార విండో ఫంక్షన్ సహజమైన ఇమేజ్ రీసైజింగ్, బ్యాలెన్సింగ్ షార్ప్‌నెస్ మరియు స్మూత్‌నెస్ కోసం ఆప్టిమైజ్ చేయబడిన అధిక-నాణ్యత ఇంటర్‌పోలేషన్ పద్ధతి Robidoux పద్ధతి యొక్క పదునైన రూపాంతరం, స్ఫుటమైన చిత్రం పునఃపరిమాణం కోసం ఆప్టిమైజ్ చేయబడింది 16-ట్యాప్ ఫిల్టర్‌ని ఉపయోగించి సున్నితమైన ఫలితాలను అందించే స్ప్లైన్-ఆధారిత ఇంటర్‌పోలేషన్ పద్ధతి 36-ట్యాప్ ఫిల్టర్‌ని ఉపయోగించి సున్నితమైన ఫలితాలను అందించే స్ప్లైన్-ఆధారిత ఇంటర్‌పోలేషన్ పద్ధతి 64-ట్యాప్ ఫిల్టర్‌ని ఉపయోగించి సున్నితమైన ఫలితాలను అందించే స్ప్లైన్-ఆధారిత ఇంటర్‌పోలేషన్ పద్ధతి కైజర్ విండోను ఉపయోగించే ఇంటర్‌పోలేషన్ పద్ధతి, మెయిన్-లోబ్ వెడల్పు మరియు సైడ్-లోబ్ స్థాయి మధ్య ట్రేడ్-ఆఫ్‌పై మంచి నియంత్రణను అందిస్తుంది సిగ్నల్ ప్రాసెసింగ్‌లో స్పెక్ట్రల్ లీకేజీని తగ్గించడానికి ఉపయోగించే బార్ట్‌లెట్ మరియు హాన్ విండోలను కలిపి ఒక హైబ్రిడ్ విండో ఫంక్షన్ సమీప పిక్సెల్ విలువల సగటును ఉపయోగించే సరళమైన రీసాంప్లింగ్ పద్ధతి, తరచుగా అడ్డంకిగా కనిపించేలా చేస్తుంది సిగ్నల్ ప్రాసెసింగ్ అప్లికేషన్‌లలో మంచి ఫ్రీక్వెన్సీ రిజల్యూషన్‌ని అందించడానికి స్పెక్ట్రల్ లీకేజీని తగ్గించడానికి ఉపయోగించే విండో ఫంక్షన్ కనిష్ట కళాఖండాలతో అధిక-నాణ్యత ఇంటర్‌పోలేషన్ కోసం 2-లోబ్ లాంజోస్ ఫిల్టర్‌ని ఉపయోగించే రీసాంప్లింగ్ పద్ధతి కనిష్ట కళాఖండాలతో అధిక-నాణ్యత ఇంటర్‌పోలేషన్ కోసం 3-లోబ్ లాంక్జోస్ ఫిల్టర్‌ని ఉపయోగించే రీసాంప్లింగ్ పద్ధతి కనిష్ట కళాఖండాలతో అధిక-నాణ్యత ఇంటర్‌పోలేషన్ కోసం 4-లోబ్ లాంక్జోస్ ఫిల్టర్‌ని ఉపయోగించే రీసాంప్లింగ్ పద్ధతి జింక్ ఫంక్షన్‌ను ఉపయోగించే Lanczos 2 ఫిల్టర్ యొక్క రూపాంతరం, తక్కువ కళాఖండాలతో అధిక-నాణ్యత ఇంటర్‌పోలేషన్‌ను అందిస్తుంది జింక్ ఫంక్షన్‌ను ఉపయోగించే Lanczos 3 ఫిల్టర్ యొక్క రూపాంతరం, తక్కువ కళాఖండాలతో అధిక-నాణ్యత ఇంటర్‌పోలేషన్‌ను అందిస్తుంది జింక్ ఫంక్షన్‌ను ఉపయోగించే Lanczos 4 ఫిల్టర్ యొక్క వేరియంట్, కనిష్ట కళాఖండాలతో అధిక-నాణ్యత ఇంటర్‌పోలేషన్‌ను అందిస్తుంది హన్నింగ్ EWA మృదువైన ఇంటర్‌పోలేషన్ మరియు రీసాంప్లింగ్ కోసం హన్నింగ్ ఫిల్టర్ యొక్క ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ రాబిడౌక్స్ EWA అధిక నాణ్యత రీసాంప్లింగ్ కోసం Robidoux ఫిల్టర్ యొక్క ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ బ్లాక్‌మ్యాన్ EVE రింగింగ్ కళాఖండాలను తగ్గించడానికి బ్లాక్‌మ్యాన్ ఫిల్టర్ యొక్క ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ క్వాడ్రిక్ EWA మృదువైన ఇంటర్‌పోలేషన్ కోసం క్వాడ్రిక్ ఫిల్టర్ యొక్క ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ Robidoux షార్ప్ EWA పదునైన ఫలితాల కోసం రాబిడౌక్స్ షార్ప్ ఫిల్టర్ యొక్క ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ లాంజోస్ 3 జింక్ EWA ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ లాంక్జోస్ 3 జింక్ ఫిల్టర్ తగ్గిన మారుపేరుతో అధిక-నాణ్యత రీసాంప్లింగ్ కోసం జిన్సెంగ్ పదును మరియు సున్నితత్వం యొక్క మంచి బ్యాలెన్స్‌తో అధిక-నాణ్యత ఇమేజ్ ప్రాసెసింగ్ కోసం రూపొందించబడిన రీసాంప్లింగ్ ఫిల్టర్ జిన్సెంగ్ EWA మెరుగైన చిత్ర నాణ్యత కోసం జిన్‌సెంగ్ ఫిల్టర్ యొక్క ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ లాంజోస్ షార్ప్ EWA ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ లాంక్జోస్ షార్ప్ ఫిల్టర్ కనిష్ట కళాఖండాలతో పదునైన ఫలితాలను సాధించడం కోసం లాంజోస్ 4 షార్పెస్ట్ EWA ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ లాంక్జోస్ 4 షార్పెస్ట్ ఫిల్టర్ చాలా షార్ప్ ఇమేజ్ రీసాంప్లింగ్ కోసం లాంజోస్ సాఫ్ట్ EWA సున్నితమైన ఇమేజ్ రీసాంప్లింగ్ కోసం లాంక్జోస్ సాఫ్ట్ ఫిల్టర్ యొక్క ఎలిప్టికల్ వెయిటెడ్ యావరేజ్ (EWA) వేరియంట్ హాసన్ సాఫ్ట్ స్మూత్ మరియు ఆర్టిఫాక్ట్-ఫ్రీ ఇమేజ్ స్కేలింగ్ కోసం హాస్న్ రూపొందించిన రీసాంప్లింగ్ ఫిల్టర్ ఫార్మాట్ మార్పిడి చిత్రాల బ్యాచ్‌ను ఒక ఫార్మాట్ నుండి మరొక ఫార్మాట్‌కి మార్చండి ఎప్పటికీ తీసివేయండి చిత్రం స్టాకింగ్ ఎంచుకున్న బ్లెండ్ మోడ్‌లతో చిత్రాలను ఒకదానిపై ఒకటి పేర్చండి చిత్రాన్ని జోడించండి డబ్బాలు లెక్కించబడతాయి క్లాహే HSL క్లాహే HSV హిస్టోగ్రాం అడాప్టివ్ HSLని సమం చేయండి హిస్టోగ్రాం అడాప్టివ్ HSVని సమం చేయండి ఎడ్జ్ మోడ్ క్లిప్ చుట్టు వర్ణాంధత్వం ఎంచుకున్న రంగు అంధత్వం వేరియంట్ కోసం థీమ్ రంగులను స్వీకరించడానికి మోడ్‌ను ఎంచుకోండి ఎరుపు మరియు ఆకుపచ్చ రంగుల మధ్య తేడాను గుర్తించడంలో ఇబ్బంది ఆకుపచ్చ మరియు ఎరుపు రంగుల మధ్య తేడాను గుర్తించడంలో ఇబ్బంది నీలం మరియు పసుపు రంగుల మధ్య తేడాను గుర్తించడంలో ఇబ్బంది ఎరుపు రంగులను గ్రహించలేకపోవడం ఆకుపచ్చ రంగులను గ్రహించలేకపోవడం నీలం రంగులను గ్రహించలేకపోవడం అన్ని రంగులకు తగ్గిన సున్నితత్వం పూర్తి వర్ణాంధత్వం, బూడిద రంగు షేడ్స్ మాత్రమే చూడటం కలర్ బ్లైండ్ స్కీమ్‌ని ఉపయోగించవద్దు రంగులు థీమ్‌లో సెట్ చేసిన విధంగానే ఉంటాయి సిగ్మోయిడల్ లాగ్రాంజ్ 2 ఆర్డర్ 2 యొక్క లాగ్రాంజ్ ఇంటర్‌పోలేషన్ ఫిల్టర్, సున్నితమైన పరివర్తనతో అధిక-నాణ్యత ఇమేజ్ స్కేలింగ్‌కు అనుకూలం లాగ్రాంజ్ 3 ఆర్డర్ 3 యొక్క లాగ్రాంజ్ ఇంటర్‌పోలేషన్ ఫిల్టర్, ఇమేజ్ స్కేలింగ్ కోసం మెరుగైన ఖచ్చితత్వం మరియు సున్నితమైన ఫలితాలను అందిస్తుంది లాంజోస్ 6 6 అధిక ఆర్డర్‌తో లాంజోస్ రీసాంప్లింగ్ ఫిల్టర్, పదునైన మరియు మరింత ఖచ్చితమైన ఇమేజ్ స్కేలింగ్‌ను అందిస్తుంది లాంజోస్ 6 జింక్ మెరుగైన ఇమేజ్ రీసాంప్లింగ్ నాణ్యత కోసం జింక్ ఫంక్షన్‌ని ఉపయోగించి Lanczos 6 ఫిల్టర్ యొక్క వేరియంట్ లీనియర్ బాక్స్ బ్లర్ లీనియర్ టెంట్ బ్లర్ లీనియర్ గాస్సియన్ బాక్స్ బ్లర్ లీనియర్ స్టాక్ బ్లర్ గాస్సియన్ బాక్స్ బ్లర్ లీనియర్ ఫాస్ట్ గాస్సియన్ బ్లర్ తదుపరి లీనియర్ ఫాస్ట్ గాస్సియన్ బ్లర్ లీనియర్ గాస్సియన్ బ్లర్ పెయింట్‌గా ఉపయోగించడానికి ఒక ఫిల్టర్‌ని ఎంచుకోండి ఫిల్టర్‌ని భర్తీ చేయండి మీ డ్రాయింగ్‌లో బ్రష్‌గా ఉపయోగించడానికి దిగువ ఫిల్టర్‌ని ఎంచుకోండి TIFF కుదింపు పథకం తక్కువ పాలీ ఇసుక పెయింటింగ్ చిత్రం విభజన ఒకే చిత్రాన్ని అడ్డు వరుసలు లేదా నిలువు వరుసల వారీగా విభజించండి హద్దులకు సరిపోతాయి కావలసిన ప్రవర్తనను సాధించడానికి ఈ పరామితితో క్రాప్ రీసైజ్ మోడ్‌ను కలపండి (క్రాప్/ఫిట్ టు యాస్పెక్ట్ రేషియో) భాషలు విజయవంతంగా దిగుమతి అయ్యాయి OCR మోడల్‌లను బ్యాకప్ చేయండి దిగుమతి ఎగుమతి చేయండి స్థానం కేంద్రం ఎగువ ఎడమ ఎగువ కుడి దిగువ ఎడమ దిగువ కుడి టాప్ సెంటర్ కుడి మధ్యలో దిగువ కేంద్రం ఎడమవైపు మధ్యలో లక్ష్య చిత్రం పాలెట్ బదిలీ మెరుగైన నూనె సాధారణ పాత టీవీ HDR గోతం సాధారణ స్కెచ్ సాఫ్ట్ గ్లో రంగు పోస్టర్ ట్రై టోన్ మూడవ రంగు క్లాహే ఓక్లాబ్ క్లారా ఓల్క్స్ క్లాహే జ్జాజ్బ్జ్ పోల్కా డాట్ క్లస్టర్డ్ 2x2 డైథరింగ్ క్లస్టర్డ్ 4x4 డైథరింగ్ క్లస్టర్డ్ 8x8 డైథరింగ్ యిలిలోమా డిథరింగ్ ఇష్టమైన ఎంపికలు ఏవీ ఎంచుకోబడలేదు, వాటిని సాధనాల పేజీలో జోడించండి ఇష్టమైనవి జోడించండి కాంప్లిమెంటరీ సారూప్యమైనది త్రయోదశి స్ప్లిట్ కాంప్లిమెంటరీ టెట్రాడిక్ చతురస్రం సాదృశ్యం + కాంప్లిమెంటరీ రంగు సాధనాలు కలపండి, టోన్‌లను తయారు చేయండి, షేడ్స్‌ని రూపొందించండి మరియు మరిన్ని చేయండి రంగు సామరస్యాలు రంగు షేడింగ్ వైవిధ్యం టింట్స్ టోన్లు షేడ్స్ కలర్ మిక్సింగ్ రంగు సమాచారం ఎంచుకున్న రంగు కలపడానికి రంగు డైనమిక్ రంగులు ఆన్‌లో ఉన్నప్పుడు మోనెట్‌ని ఉపయోగించలేరు 512x512 2D LUT లక్ష్యం LUT చిత్రం ఒక ఔత్సాహిక మిస్ మర్యాదలు సాఫ్ట్ గాంభీర్యం సాఫ్ట్ గాంభీర్యం వేరియంట్ పాలెట్ బదిలీ వేరియంట్ 3D LUT టార్గెట్ 3D LUT ఫైల్ (.cube / .CUBE) LUT బ్లీచ్ బైపాస్ కొవ్వొత్తి వెలుగు డ్రాప్ బ్లూస్ ఎడ్జీ అంబర్ పతనం రంగులు ఫిల్మ్ స్టాక్ 50 పొగమంచు రాత్రి కొడాక్ తటస్థ LUT చిత్రాన్ని పొందండి ముందుగా, మీరు ఇక్కడ పొందగలిగే తటస్థ LUTకి ఫిల్టర్‌ని వర్తింపజేయడానికి మీకు ఇష్టమైన ఫోటో ఎడిటింగ్ అప్లికేషన్‌ను ఉపయోగించండి. ఇది సరిగ్గా పని చేయడానికి ప్రతి పిక్సెల్ రంగు ఇతర పిక్సెల్‌లపై ఆధారపడకూడదు (ఉదా. బ్లర్ పని చేయదు). సిద్ధమైన తర్వాత, 512*512 LUT ఫిల్టర్ కోసం మీ కొత్త LUT చిత్రాన్ని ఇన్‌పుట్‌గా ఉపయోగించండి పాప్ ఆర్ట్ సెల్యులాయిడ్ కాఫీ గోల్డెన్ ఫారెస్ట్ పచ్చటి రెట్రో పసుపు లింక్‌ల ప్రివ్యూ మీరు టెక్స్ట్ (QRCode, OCR మొదలైనవి) పొందగలిగే ప్రదేశాలలో లింక్ ప్రివ్యూను తిరిగి పొందడాన్ని ప్రారంభిస్తుంది లింకులు ICO ఫైల్‌లు గరిష్టంగా 256 x 256 పరిమాణంలో మాత్రమే సేవ్ చేయబడతాయి GIF నుండి WEBP GIF చిత్రాలను WEBP యానిమేటెడ్ చిత్రాలుగా మార్చండి WEBP సాధనాలు చిత్రాలను WEBP యానిమేటెడ్ చిత్రానికి మార్చండి లేదా ఇచ్చిన WEBP యానిమేషన్ నుండి ఫ్రేమ్‌లను సంగ్రహించండి చిత్రాలకు WEBP WEBP ఫైల్‌ను చిత్రాల బ్యాచ్‌గా మార్చండి చిత్రాల బ్యాచ్‌ని WEBP ఫైల్‌గా మార్చండి WEBPకి చిత్రాలు ప్రారంభించడానికి WEBP చిత్రాన్ని ఎంచుకోండి ఫైల్‌లకు పూర్తి యాక్సెస్ లేదు Androidలో చిత్రాలుగా గుర్తించబడని JXL, QOI మరియు ఇతర చిత్రాలను చూడటానికి అన్ని ఫైల్‌లను యాక్సెస్ చేయడానికి అనుమతించండి. అనుమతి లేకుండా ఇమేజ్ టూల్‌బాక్స్ ఆ చిత్రాలను చూపించదు డిఫాల్ట్ డ్రా రంగు డిఫాల్ట్ డ్రా పాత్ మోడ్ టైమ్‌స్టాంప్ జోడించండి అవుట్‌పుట్ ఫైల్ పేరుకు టైమ్‌స్టాంప్ జోడించడాన్ని ప్రారంభిస్తుంది ఫార్మాట్ చేసిన టైమ్‌స్టాంప్ ప్రాథమిక మిల్లీలకు బదులుగా అవుట్‌పుట్ ఫైల్ పేరులో టైమ్‌స్టాంప్ ఫార్మాటింగ్‌ని ప్రారంభించండి టైమ్‌స్టాంప్‌ల ఆకృతిని ఎంచుకోవడానికి వాటిని ప్రారంభించండి వన్ టైమ్ సేవ్ లొకేషన్ చాలా వరకు అన్ని ఎంపికలలో సేవ్ బటన్‌ను ఎక్కువసేపు నొక్కడం ద్వారా మీరు ఉపయోగించగల వన్ టైమ్ సేవ్ స్థానాలను వీక్షించండి మరియు సవరించండి ఇటీవల ఉపయోగించబడింది CI ఛానల్ సమూహం టెలిగ్రామ్‌లో ఇమేజ్ టూల్‌బాక్స్ 🎉 మా చాట్‌లో చేరండి, ఇక్కడ మీరు మీకు కావలసిన ఏదైనా చర్చించవచ్చు మరియు నేను బీటాలు మరియు ప్రకటనలను పోస్ట్ చేసే CI ఛానెల్‌ని కూడా చూడండి యాప్ యొక్క కొత్త వెర్షన్‌ల గురించి నోటిఫికేషన్ పొందండి మరియు ప్రకటనలను చదవండి ఇచ్చిన కొలతలకు చిత్రాన్ని అమర్చండి మరియు నేపథ్యానికి బ్లర్ లేదా రంగును వర్తింపజేయండి సాధనాల అమరిక రకం ద్వారా సమూహ సాధనాలు కస్టమ్ జాబితా అమరికకు బదులుగా వాటి రకాన్ని బట్టి ప్రధాన స్క్రీన్‌పై సమూహ సాధనాలు డిఫాల్ట్ విలువలు సిస్టమ్ బార్‌ల దృశ్యమానత స్వైప్ ద్వారా సిస్టమ్ బార్‌లను చూపించు సిస్టమ్ బార్‌లు దాచబడి ఉంటే వాటిని చూపడానికి స్వైపింగ్‌ని ప్రారంభిస్తుంది ఆటో అన్నీ దాచు అన్నీ చూపించు నవ్ బార్‌ను దాచండి స్థితి పట్టీని దాచండి నాయిస్ జనరేషన్ పెర్లిన్ లేదా ఇతర రకాల వంటి విభిన్న శబ్దాలను రూపొందించండి ఫ్రీక్వెన్సీ శబ్దం రకం భ్రమణ రకం ఫ్రాక్టల్ రకం అష్టపదులు లాకునారిటీ లాభం వెయిటెడ్ స్ట్రెంత్ పింగ్ పాంగ్ బలం దూరం ఫంక్షన్ రిటర్న్ రకం జిట్టర్ డొమైన్ వార్ప్ అమరిక అనుకూల ఫైల్ పేరు ప్రస్తుత చిత్రాన్ని సేవ్ చేయడానికి ఉపయోగించే స్థానం మరియు ఫైల్ పేరును ఎంచుకోండి అనుకూల పేరుతో ఫోల్డర్‌లో సేవ్ చేయబడింది కోల్లెజ్ మేకర్ గరిష్టంగా 20 చిత్రాల నుండి దృశ్య రూపకల్పనలను రూపొందించండి కోల్లెజ్ రకం స్థానాన్ని సర్దుబాటు చేయడానికి స్వాప్ చేయడానికి, తరలించడానికి మరియు జూమ్ చేయడానికి చిత్రాన్ని పట్టుకోండి భ్రమణాన్ని నిలిపివేయండి రెండు వేళ్ల సంజ్ఞలతో చిత్రాలను తిప్పడాన్ని నిరోధిస్తుంది సరిహద్దులకు స్నాపింగ్ చేయడాన్ని ప్రారంభించండి తరలించిన లేదా జూమ్ చేసిన తర్వాత, ఫ్రేమ్ అంచులను పూరించడానికి చిత్రాలు స్నాప్ అవుతాయి హిస్టోగ్రాం మీకు సర్దుబాట్లు చేయడంలో సహాయపడటానికి RGB లేదా బ్రైట్‌నెస్ ఇమేజ్ హిస్టోగ్రాం ఈ చిత్రం RGB మరియు బ్రైట్‌నెస్ హిస్టోగ్రామ్‌లను రూపొందించడానికి ఉపయోగించబడుతుంది టెసెరాక్ట్ ఎంపికలు టెస్రాక్ట్ ఇంజిన్ కోసం కొన్ని ఇన్‌పుట్ వేరియబుల్స్‌ని వర్తింపజేయండి అనుకూల ఎంపికలు ఈ నమూనాను అనుసరించి ఎంపికలు నమోదు చేయాలి: \"--{option_name} {value}\" ఆటో క్రాప్ ఉచిత మూలలు బహుభుజి ద్వారా చిత్రాన్ని కత్తిరించండి, ఇది దృక్కోణాన్ని కూడా సరిచేస్తుంది చిత్ర సరిహద్దులకు బలవంతపు పాయింట్లు పాయింట్లు ఇమేజ్ హద్దుల ద్వారా పరిమితం చేయబడవు, ఇది మరింత ఖచ్చితమైన దృక్పథం సరిదిద్దడానికి ఉపయోగపడుతుంది ముసుగు గీయబడిన మార్గం క్రింద కంటెంట్ అవగాహన నింపండి హీల్ స్పాట్ సర్కిల్ కెర్నల్ ఉపయోగించండి తెరవడం మూసివేయడం పదనిర్మాణ ప్రవణత టాప్ టోపీ నల్ల టోపీ టోన్ వక్రతలు వక్రతలను రీసెట్ చేయండి వక్రతలు డిఫాల్ట్ విలువకు తిరిగి మార్చబడతాయి లైన్ శైలి గ్యాప్ పరిమాణం డాష్ చేయబడింది డాట్ డాష్ చేయబడింది స్టాంప్ చేయబడింది జిగ్జాగ్ పేర్కొన్న గ్యాప్ పరిమాణంతో గీసిన మార్గంలో డాష్ చేసిన గీతను గీస్తుంది ఇచ్చిన మార్గంలో చుక్క మరియు చుక్కల గీతను గీస్తుంది కేవలం డిఫాల్ట్ సరళ రేఖలు పేర్కొన్న అంతరంతో మార్గంలో ఎంచుకున్న ఆకృతులను గీస్తుంది మార్గం వెంట ఉంగరాల జిగ్‌జాగ్‌ని గీస్తుంది జిగ్‌జాగ్ నిష్పత్తి సత్వరమార్గాన్ని సృష్టించండి పిన్ చేయడానికి సాధనాన్ని ఎంచుకోండి సాధనం మీ లాంచర్ హోమ్ స్క్రీన్‌కు సత్వరమార్గంగా జోడించబడుతుంది, అవసరమైన ప్రవర్తనను సాధించడానికి \"ఫైల్ పికింగ్‌ను దాటవేయి\" సెట్టింగ్‌తో కలపడం ద్వారా దీన్ని ఉపయోగించండి ఫ్రేమ్‌లను పేర్చవద్దు మునుపటి ఫ్రేమ్‌లను పారవేయడాన్ని ప్రారంభిస్తుంది, కాబట్టి అవి ఒకదానికొకటి పేర్చబడవు క్రాస్‌ఫేడ్ ఫ్రేమ్‌లు ఒకదానికొకటి క్రాస్‌ఫేడ్ చేయబడతాయి క్రాస్‌ఫేడ్ ఫ్రేమ్‌లు లెక్కించబడతాయి థ్రెషోల్డ్ ఒకటి థ్రెషోల్డ్ రెండు కానీ అద్దం 101 మెరుగైన జూమ్ బ్లర్ లాప్లాసియన్ సింపుల్ సోబెల్ సింపుల్ సహాయక గ్రిడ్ ఖచ్చితమైన అవకతవకలతో సహాయం చేయడానికి డ్రాయింగ్ ఏరియా పైన సపోర్టింగ్ గ్రిడ్‌ని చూపుతుంది గ్రిడ్ రంగు సెల్ వెడల్పు సెల్ ఎత్తు కాంపాక్ట్ సెలెక్టర్లు కొన్ని ఎంపిక నియంత్రణలు తక్కువ స్థలాన్ని తీసుకోవడానికి కాంపాక్ట్ లేఅవుట్‌ని ఉపయోగిస్తాయి చిత్రాన్ని క్యాప్చర్ చేయడానికి సెట్టింగ్‌లలో కెమెరా అనుమతిని మంజూరు చేయండి లేఅవుట్ ప్రధాన స్క్రీన్ శీర్షిక స్థిరమైన రేటు కారకం (CRF) %1$s విలువ అంటే స్లో కంప్రెషన్, ఫలితంగా ఫైల్ పరిమాణం చాలా తక్కువగా ఉంటుంది. %2$s అంటే వేగవంతమైన కుదింపు, ఫలితంగా పెద్ద ఫైల్ ఏర్పడుతుంది. లట్ లైబ్రరీ LUTల సేకరణను డౌన్‌లోడ్ చేయండి, మీరు డౌన్‌లోడ్ చేసిన తర్వాత దరఖాస్తు చేసుకోవచ్చు LUTల సేకరణను నవీకరించండి (కొత్తవి మాత్రమే క్యూలో ఉంచబడతాయి), వీటిని డౌన్‌లోడ్ చేసిన తర్వాత మీరు దరఖాస్తు చేసుకోవచ్చు ఫిల్టర్‌ల కోసం డిఫాల్ట్ ఇమేజ్ ప్రివ్యూని మార్చండి ప్రివ్యూ చిత్రం దాచు చూపించు స్లైడర్ రకం ఫ్యాన్సీ పదార్థం 2 ఫాన్సీగా కనిపించే స్లయిడర్. ఇది డిఫాల్ట్ ఎంపిక మెటీరియల్ 2 స్లయిడర్ ఒక మెటీరియల్ మీరు స్లయిడర్ దరఖాస్తు చేసుకోండి మధ్య డైలాగ్ బటన్లు వీలైతే డైలాగ్‌ల బటన్‌లు ఎడమ వైపుకు బదులుగా మధ్యలో ఉంచబడతాయి ఓపెన్ సోర్స్ లైసెన్స్‌లు ఈ యాప్‌లో ఉపయోగించిన ఓపెన్ సోర్స్ లైబ్రరీల లైసెన్స్‌లను వీక్షించండి ప్రాంతం పిక్సెల్ ఏరియా రిలేషన్ ఉపయోగించి రీసాంప్లింగ్. ఇమేజ్ డెసిమేషన్ కోసం ఇది ప్రాధాన్య పద్ధతి కావచ్చు, ఎందుకంటే ఇది మోయిర్\'-ఫ్రీ ఫలితాలను ఇస్తుంది. కానీ చిత్రాన్ని జూమ్ చేసినప్పుడు, అది \"సమీప\" పద్ధతిని పోలి ఉంటుంది. టోన్‌మ్యాపింగ్‌ని ప్రారంభించండి % నమోదు చేయండి సైట్‌ను యాక్సెస్ చేయడం సాధ్యపడదు, VPNని ఉపయోగించి ప్రయత్నించండి లేదా url సరైనదేనా అని తనిఖీ చేయండి మార్కప్ పొరలు చిత్రాలు, వచనం మరియు మరిన్నింటిని ఉచితంగా ఉంచగల సామర్థ్యంతో లేయర్‌ల మోడ్ లేయర్‌ని సవరించండి చిత్రంపై పొరలు చిత్రాన్ని నేపథ్యంగా ఉపయోగించండి మరియు దాని పైన వివిధ లేయర్‌లను జోడించండి నేపథ్యంలో పొరలు మొదటి ఎంపిక వలె ఉంటుంది కానీ చిత్రానికి బదులుగా రంగుతో ఉంటుంది బీటా ఫాస్ట్ సెట్టింగుల వైపు ఇమేజ్‌లను ఎడిట్ చేస్తున్నప్పుడు ఎంచుకున్న వైపు ఫ్లోటింగ్ స్ట్రిప్‌ను జోడించండి, ఇది క్లిక్ చేసినప్పుడు ఫాస్ట్ సెట్టింగ్‌లను తెరుస్తుంది ఎంపికను క్లియర్ చేయండి \"%1$s\" సమూహాన్ని సెట్ చేయడం డిఫాల్ట్‌గా కుదించబడుతుంది \"%1$s\" సమూహాన్ని సెట్ చేయడం డిఫాల్ట్‌గా విస్తరించబడుతుంది Base64 సాధనాలు Base64 స్ట్రింగ్‌ని ఇమేజ్‌కి డీకోడ్ చేయండి లేదా ఇమేజ్‌ని Base64 ఫార్మాట్‌కి ఎన్‌కోడ్ చేయండి బేస్64 అందించిన విలువ చెల్లుబాటు అయ్యే Base64 స్ట్రింగ్ కాదు ఖాళీ లేదా చెల్లని Base64 స్ట్రింగ్‌ను కాపీ చేయడం సాధ్యపడదు బేస్ 64ని అతికించండి కాపీ బేస్ 64 Base64 స్ట్రింగ్‌ను కాపీ చేయడానికి లేదా సేవ్ చేయడానికి చిత్రాన్ని లోడ్ చేయండి. మీరు స్ట్రింగ్‌ను కలిగి ఉన్నట్లయితే, చిత్రాన్ని పొందేందుకు మీరు దానిని పైన అతికించవచ్చు బేస్ 64ని సేవ్ చేయండి Share Base64 ఎంపికలు చర్యలు దిగుమతి బేస్64 బేస్ 64 చర్యలు అవుట్‌లైన్ జోడించండి పేర్కొన్న రంగు మరియు వెడల్పుతో టెక్స్ట్ చుట్టూ అవుట్‌లైన్ జోడించండి అవుట్‌లైన్ రంగు అవుట్‌లైన్ పరిమాణం భ్రమణం ఫైల్ పేరుగా చెక్సమ్ అవుట్‌పుట్ ఇమేజ్‌లు వాటి డేటా చెక్‌సమ్‌కు అనుగుణమైన పేరును కలిగి ఉంటాయి ఉచిత సాఫ్ట్‌వేర్ (భాగస్వామి) Android అప్లికేషన్ల భాగస్వామి ఛానెల్‌లో మరింత ఉపయోగకరమైన సాఫ్ట్‌వేర్ అల్గోరిథం చెక్సమ్ సాధనాలు చెక్‌సమ్‌లను సరిపోల్చండి, హ్యాష్‌లను లెక్కించండి లేదా విభిన్న హ్యాషింగ్ అల్గారిథమ్‌లను ఉపయోగించి ఫైల్‌ల నుండి హెక్స్ స్ట్రింగ్‌లను సృష్టించండి లెక్కించు టెక్స్ట్ హాష్ చెక్సమ్ ఎంచుకున్న అల్గోరిథం ఆధారంగా దాని చెక్‌సమ్‌ను లెక్కించడానికి ఫైల్‌ను ఎంచుకోండి ఎంచుకున్న అల్గోరిథం ఆధారంగా దాని చెక్‌సమ్‌ను లెక్కించడానికి టెక్స్ట్‌ని నమోదు చేయండి మూలం చెక్సమ్ సరిపోల్చడానికి చెక్‌సమ్ మ్యాచ్! తేడా చెక్‌సమ్‌లు సమానంగా ఉంటాయి, ఇది సురక్షితంగా ఉంటుంది చెక్‌సమ్‌లు సమానంగా ఉండవు, ఫైల్ సురక్షితం కాదు! మెష్ గ్రేడియంట్స్ మెష్ గ్రేడియంట్స్ యొక్క ఆన్‌లైన్ సేకరణను చూడండి TTF మరియు OTF ఫాంట్‌లను మాత్రమే దిగుమతి చేసుకోవచ్చు ఫాంట్‌ను దిగుమతి చేయండి (TTF/OTF) ఫాంట్‌లను ఎగుమతి చేయండి దిగుమతి చేసుకున్న ఫాంట్‌లు ప్రయత్నాన్ని సేవ్ చేస్తున్నప్పుడు లోపం, అవుట్‌పుట్ ఫోల్డర్‌ని మార్చడానికి ప్రయత్నించండి ఫైల్ పేరు సెట్ చేయబడలేదు ఏదీ లేదు అనుకూల పేజీలు పేజీల ఎంపిక సాధనం నిష్క్రమణ నిర్ధారణ మీరు నిర్దిష్ట సాధనాలను ఉపయోగిస్తున్నప్పుడు సేవ్ చేయని మార్పులను కలిగి ఉంటే మరియు దాన్ని మూసివేయడానికి ప్రయత్నిస్తే, కన్ఫర్మ్ డైలాగ్ చూపబడుతుంది EXIFని సవరించండి రీకంప్రెషన్ లేకుండా ఒకే చిత్రం యొక్క మెటాడేటాను మార్చండి అందుబాటులో ఉన్న ట్యాగ్‌లను సవరించడానికి నొక్కండి స్టిక్కర్‌ని మార్చండి ఫిట్ వెడల్పు ఫిట్ ఎత్తు బ్యాచ్ సరిపోల్చండి ఎంచుకున్న అల్గోరిథం ఆధారంగా దాని చెక్‌సమ్‌ను లెక్కించడానికి ఫైల్/ఫైల్‌లను ఎంచుకోండి ఫైళ్లను ఎంచుకోండి డైరెక్టరీని ఎంచుకోండి తల పొడవు స్కేల్ స్టాంప్ సమయముద్ర ఆకృతి నమూనా పాడింగ్ చిత్రం కట్టింగ్ చిత్రం భాగాన్ని కత్తిరించండి మరియు నిలువు లేదా క్షితిజ సమాంతర రేఖల ద్వారా ఎడమ వాటిని (విలోమంగా ఉండవచ్చు) విలీనం చేయండి నిలువు పివోట్ లైన్ క్షితిజసమాంతర పివోట్ లైన్ విలోమ ఎంపిక కత్తిరించిన ప్రాంతం చుట్టూ భాగాలను విలీనం చేయడానికి బదులుగా నిలువుగా కత్తిరించిన భాగం వదిలివేయబడుతుంది కత్తిరించిన ప్రాంతం చుట్టూ భాగాలను విలీనం చేయడానికి బదులుగా క్షితిజసమాంతర కట్ భాగం వదిలివేయబడుతుంది మెష్ గ్రేడియంట్స్ యొక్క సేకరణ కస్టమ్ మొత్తం నాట్లు మరియు రిజల్యూషన్‌తో మెష్ గ్రేడియంట్‌ని సృష్టించండి మెష్ గ్రేడియంట్ ఓవర్‌లే ఇచ్చిన చిత్రాల పైభాగంలో మెష్ గ్రేడియంట్‌ని కంపోజ్ చేయండి పాయింట్ల అనుకూలీకరణ గ్రిడ్ పరిమాణం రిజల్యూషన్ X రిజల్యూషన్ Y రిజల్యూషన్ పిక్సెల్ ద్వారా పిక్సెల్ హైలైట్ రంగు పిక్సెల్ పోలిక రకం బార్‌కోడ్‌ని స్కాన్ చేయండి ఎత్తు నిష్పత్తి బార్‌కోడ్ రకం B/W అమలు చేయండి బార్‌కోడ్ చిత్రం పూర్తిగా నలుపు మరియు తెలుపు రంగులో ఉంటుంది మరియు యాప్ యొక్క థీమ్ ద్వారా రంగు వేయబడదు ఏదైనా బార్‌కోడ్‌ని (QR, EAN, AZTEC, …) స్కాన్ చేయండి మరియు దాని కంటెంట్‌ను పొందండి లేదా కొత్తదాన్ని రూపొందించడానికి మీ వచనాన్ని అతికించండి బార్‌కోడ్ కనుగొనబడలేదు రూపొందించిన బార్‌కోడ్ ఇక్కడ ఉంటుంది ఆడియో కవర్లు ఆడియో ఫైల్‌ల నుండి ఆల్బమ్ కవర్ చిత్రాలను సంగ్రహించండి, అత్యంత సాధారణ ఫార్మాట్‌లకు మద్దతు ఉంది ప్రారంభించడానికి ఆడియోను ఎంచుకోండి ఆడియోను ఎంచుకోండి కవర్లు ఏవీ కనుగొనబడలేదు లాగ్లను పంపండి యాప్ లాగ్‌ల ఫైల్‌ను షేర్ చేయడానికి క్లిక్ చేయండి, ఇది సమస్యను గుర్తించడంలో మరియు సమస్యలను పరిష్కరించడంలో నాకు సహాయపడుతుంది అయ్యో… ఏదో తప్పు జరిగింది దిగువ ఎంపికలను ఉపయోగించి మీరు నన్ను సంప్రదించవచ్చు మరియు నేను పరిష్కారాన్ని కనుగొనడానికి ప్రయత్నిస్తాను.\n(లాగ్‌లను జోడించడం మర్చిపోవద్దు) ఫైల్‌కి వ్రాయండి చిత్రాల బ్యాచ్ నుండి వచనాన్ని సంగ్రహించి, దానిని ఒక టెక్స్ట్ ఫైల్‌లో నిల్వ చేయండి మెటాడేటాకు వ్రాయండి ప్రతి చిత్రం నుండి వచనాన్ని సంగ్రహించి, సంబంధిత ఫోటోల EXIF ​​సమాచారంలో ఉంచండి అదృశ్య మోడ్ మీ చిత్రాల బైట్‌ల లోపల కంటికి కనిపించని వాటర్‌మార్క్‌లను సృష్టించడానికి స్టెగానోగ్రఫీని ఉపయోగించండి LSBని ఉపయోగించండి LSB (తక్కువ ముఖ్యమైన బిట్) స్టెగానోగ్రఫీ పద్ధతి ఉపయోగించబడుతుంది, లేకపోతే FD (ఫ్రీక్వెన్సీ డొమైన్) రెడ్ ఐస్ ను ఆటో రిమూవ్ చేయండి పాస్వర్డ్ అన్‌లాక్ చేయండి PDF రక్షించబడింది ఆపరేషన్ దాదాపు పూర్తయింది. ఇప్పుడు రద్దు చేయడం వలన దానిని పునఃప్రారంభించవలసి ఉంటుంది తేదీ సవరించబడింది తేదీ సవరించబడింది (రివర్స్ చేయబడింది) పరిమాణం పరిమాణం (రివర్స్డ్) MIME రకం MIME రకం (రివర్స్డ్) పొడిగింపు పొడిగింపు (రివర్స్డ్) తేదీ జోడించబడింది తేదీ జోడించబడింది (రివర్స్ చేయబడింది) ఎడమ నుండి కుడికి కుడి నుండి ఎడమ ఎగువ నుండి దిగువ వరకు దిగువ నుండి పైకి లిక్విడ్ గ్లాస్ ఇటీవల ప్రకటించిన IOS 26 మరియు దాని లిక్విడ్ గ్లాస్ డిజైన్ సిస్టమ్ ఆధారంగా ఒక స్విచ్ చిత్రాన్ని ఎంచుకోండి లేదా దిగువన Base64 డేటాను అతికించండి/దిగుమతి చేయండి ప్రారంభించడానికి చిత్రం లింక్‌ని టైప్ చేయండి లింక్‌ని అతికించండి కాలిడోస్కోప్ ద్వితీయ కోణం వైపులా ఛానెల్ మిక్స్ నీలం ఆకుపచ్చ ఎరుపు నీలం ఆకుపచ్చ ఎరుపు ఎరుపు రంగులోకి ఆకుపచ్చ రంగులోకి నీలం రంగులోకి నీలవర్ణం మెజెంటా పసుపు రంగు హాఫ్టోన్ ఆకృతి స్థాయిలు ఆఫ్‌సెట్ వోరోనోయ్ స్ఫటికీకరణ ఆకారం సాగదీయండి యాదృచ్ఛికత డెస్పెకిల్ ప్రసరించు DoG రెండవ వ్యాసార్థం సమానం చేయండి గ్లో గిరగిరా మరియు చిటికెడు సూచించు అంచు రంగు పోలార్ కోఆర్డినేట్స్ ధ్రువానికి రెక్ట్ పోలార్ నుండి రెక్ట్ వరకు వృత్తంలో తిరగండి నాయిస్ తగ్గించండి సింపుల్ సోలారైజ్ నేత X గ్యాప్ Y గ్యాప్ X వెడల్పు Y వెడల్పు తిరుగుట రబ్బరు స్టాంప్ స్మెర్ సాంద్రత కలపండి స్పియర్ లెన్స్ వక్రీకరణ వక్రీభవన సూచిక ఆర్క్ స్ప్రెడ్ కోణం మెరుపు కిరణాలు ASCII ప్రవణత మేరీ శరదృతువు ఎముక జెట్ శీతాకాలం మహాసముద్రం వేసవి వసంతం కూల్ వేరియంట్ HSV పింక్ వేడి మాట శిలాద్రవం నరకయాతన ప్లాస్మా విరిడిస్ పౌరులు ట్విలైట్ ట్విలైట్ మారింది పెర్స్పెక్టివ్ ఆటో డెస్క్యూ పంటను అనుమతించండి క్రాప్ లేదా పెర్స్పెక్టివ్ సంపూర్ణమైన టర్బో లోతైన ఆకుపచ్చ లెన్స్ కరెక్షన్ JSON ఫార్మాట్‌లో టార్గెట్ లెన్స్ ప్రొఫైల్ ఫైల్ సిద్ధంగా ఉన్న లెన్స్ ప్రొఫైల్‌లను డౌన్‌లోడ్ చేయండి పార్ట్ శాతాలు JSONగా ఎగుమతి చేయండి json ప్రాతినిధ్యంగా పాలెట్ డేటాతో స్ట్రింగ్‌ను కాపీ చేయండి సీమ్ కార్వింగ్ హోమ్ స్క్రీన్ లాక్ స్క్రీన్ అంతర్నిర్మిత వాల్‌పేపర్‌ల ఎగుమతి రిఫ్రెష్ చేయండి ప్రస్తుత హోమ్, లాక్ మరియు అంతర్నిర్మిత వాల్‌పేపర్‌లను పొందండి అన్ని ఫైల్‌లకు యాక్సెస్‌ను అనుమతించండి, వాల్‌పేపర్‌లను తిరిగి పొందడానికి ఇది అవసరం బాహ్య నిల్వ అనుమతిని నిర్వహించడం సరిపోదు, మీరు మీ చిత్రాలకు ప్రాప్యతను అనుమతించాలి, \"అన్నీ అనుమతించు\"ని ఎంచుకున్నారని నిర్ధారించుకోండి ఫైల్ పేరుకు ప్రీసెట్ జోడించండి ఇమేజ్ ఫైల్ పేరుకు ఎంచుకున్న ప్రీసెట్‌తో ప్రత్యయం జోడిస్తుంది ఫైల్ పేరుకు ఇమేజ్ స్కేల్ మోడ్‌ను జోడించండి ఇమేజ్ ఫైల్ పేరుకు ఎంచుకున్న ఇమేజ్ స్కేల్ మోడ్‌తో ప్రత్యయాన్ని జోడిస్తుంది Ascii ఆర్ట్ చిత్రం వలె కనిపించే చిత్రాన్ని ascii టెక్స్ట్‌గా మార్చండి పరములు కొన్ని సందర్భాల్లో మెరుగైన ఫలితం కోసం చిత్రానికి ప్రతికూల ఫిల్టర్‌ని వర్తింపజేస్తుంది స్క్రీన్‌షాట్‌ను ప్రాసెస్ చేస్తోంది స్క్రీన్‌షాట్ క్యాప్చర్ చేయబడలేదు, మళ్లీ ప్రయత్నించండి సేవ్ చేయడం దాటవేయబడింది %1$s ఫైల్‌లు దాటవేయబడ్డాయి పెద్దది అయితే దాటవేయడాన్ని అనుమతించండి ఫలితంగా ఫైల్ పరిమాణం అసలైన దానికంటే పెద్దదిగా ఉంటే, కొన్ని సాధనాలు చిత్రాలను సేవ్ చేయడాన్ని దాటవేయడానికి అనుమతించబడతాయి క్యాలెండర్ ఈవెంట్ సంప్రదించండి ఇమెయిల్ స్థానం ఫోన్ వచనం SMS URL Wi-Fi నెట్‌వర్క్‌ని తెరవండి N/A SSID ఫోన్ సందేశం చిరునామా విషయం శరీరం పేరు సంస్థ శీర్షిక ఫోన్లు ఇమెయిల్‌లు URLలు చిరునామాలు సారాంశం వివరణ స్థానం ఆర్గనైజర్ ప్రారంభ తేదీ ముగింపు తేదీ స్థితి అక్షాంశం రేఖాంశం బార్‌కోడ్‌ని సృష్టించండి బార్‌కోడ్‌ని సవరించండి Wi-Fi కాన్ఫిగరేషన్ భద్రత పరిచయాన్ని ఎంచుకోండి ఎంచుకున్న పరిచయాన్ని ఉపయోగించి ఆటోఫిల్ చేయడానికి సెట్టింగ్‌లలో పరిచయాల అనుమతిని మంజూరు చేయండి సంప్రదింపు సమాచారం మొదటి పేరు మధ్య పేరు ఇంటిపేరు ఉచ్చారణ ఫోన్ జోడించండి ఇమెయిల్ జోడించండి చిరునామాను జోడించండి వెబ్సైట్ వెబ్‌సైట్‌ని జోడించండి ఫార్మాట్ చేయబడిన పేరు బార్‌కోడ్ పైన ఉంచడానికి ఈ చిత్రం ఉపయోగించబడుతుంది కోడ్ అనుకూలీకరణ ఈ చిత్రం QR కోడ్ మధ్యలో లోగోగా ఉపయోగించబడుతుంది లోగో లోగో పాడింగ్ లోగో పరిమాణం లోగో మూలలు నాల్గవ కన్ను దిగువ చివర మూలలో నాల్గవ కన్ను జోడించడం ద్వారా qr కోడ్‌కు కంటి సమరూపతను జోడిస్తుంది పిక్సెల్ ఆకారం ఫ్రేమ్ ఆకారం బంతి ఆకారం లోపం దిద్దుబాటు స్థాయి ముదురు రంగు లేత రంగు హైపర్ OS Xiaomi HyperOS వంటి శైలి ముసుగు నమూనా ఈ కోడ్ స్కాన్ చేయలేకపోవచ్చు, అన్ని పరికరాలతో చదవగలిగేలా రూపాన్ని మార్చండి స్కాన్ చేయలేము సాధనాలు మరింత కాంపాక్ట్‌గా ఉండటానికి హోమ్ స్క్రీన్ యాప్ లాంచర్ లాగా కనిపిస్తాయి లాంచర్ మోడ్ ఎంచుకున్న బ్రష్ మరియు శైలితో ప్రాంతాన్ని నింపుతుంది ఫ్లడ్ ఫిల్ స్ప్రే గ్రాఫిటీ శైలి మార్గాన్ని గీస్తుంది స్క్వేర్ పార్టికల్స్ స్ప్రే కణాలు సర్కిల్‌లకు బదులుగా చతురస్రాకారంలో ఉంటాయి పాలెట్ సాధనాలు చిత్రం నుండి ప్రాథమిక/మెటీరియల్‌ని రూపొందించండి లేదా వివిధ పాలెట్ ఫార్మాట్‌లలో దిగుమతి/ఎగుమతి చేయండి పాలెట్‌ని సవరించండి వివిధ ఫార్మాట్లలో ఎగుమతి/దిగుమతి ప్యాలెట్ రంగు పేరు పాలెట్ పేరు పాలెట్ ఫార్మాట్ విభిన్న ఫార్మాట్‌లకు ఉత్పత్తి చేయబడిన పాలెట్‌ను ఎగుమతి చేయండి ప్రస్తుత పాలెట్‌కు కొత్త రంగును జోడిస్తుంది %1$s ఫార్మాట్ పాలెట్ పేరును అందించడానికి మద్దతు ఇవ్వదు Play Store విధానాల కారణంగా, ఈ ఫీచర్ ప్రస్తుత బిల్డ్‌లో చేర్చబడదు. ఈ కార్యాచరణను యాక్సెస్ చేయడానికి, దయచేసి ప్రత్యామ్నాయ మూలం నుండి ImageToolboxని డౌన్‌లోడ్ చేయండి. మీరు దిగువన GitHubలో అందుబాటులో ఉన్న బిల్డ్‌లను కనుగొనవచ్చు. Github పేజీని తెరవండి ఎంచుకున్న ఫోల్డర్‌లో సేవ్ చేయడానికి బదులుగా అసలు ఫైల్ కొత్త దానితో భర్తీ చేయబడుతుంది దాచిన వాటర్‌మార్క్ వచనం కనుగొనబడింది దాచిన వాటర్‌మార్క్ చిత్రం కనుగొనబడింది ఈ చిత్రం దాచబడింది జనరేటివ్ పెయింటింగ్ OpenCVపై ఆధారపడకుండా, AI మోడల్‌ని ఉపయోగించి ఇమేజ్‌లోని వస్తువులను తీసివేయడానికి మిమ్మల్ని అనుమతిస్తుంది. ఈ ఫీచర్‌ని ఉపయోగించడానికి, యాప్ GitHub నుండి అవసరమైన మోడల్‌ని (~200 MB) డౌన్‌లోడ్ చేస్తుంది OpenCVపై ఆధారపడకుండా, AI మోడల్‌ని ఉపయోగించి ఇమేజ్‌లోని వస్తువులను తీసివేయడానికి మిమ్మల్ని అనుమతిస్తుంది. ఇది సుదీర్ఘమైన ఆపరేషన్ కావచ్చు లోపం స్థాయి విశ్లేషణ ప్రకాశం గ్రేడియంట్ సగటు దూరం మూవ్ డిటెక్షన్‌ని కాపీ చేయండి నిలుపుకోండి కోఎఫిసెంట్ క్లిప్‌బోర్డ్ డేటా చాలా పెద్దది కాపీ చేయడానికి డేటా చాలా పెద్దది సాధారణ నేత పిక్సలైజేషన్ అస్థిరమైన పిక్సలైజేషన్ క్రాస్ పిక్సలైజేషన్ మైక్రో మాక్రో పిక్సలైజేషన్ ఆర్బిటల్ పిక్సలైజేషన్ వోర్టెక్స్ పిక్సలైజేషన్ పల్స్ గ్రిడ్ పిక్సలైజేషన్ న్యూక్లియస్ పిక్సలైజేషన్ రేడియల్ వీవ్ పిక్సలైజేషన్ uri \"%1$s\"ని తెరవలేరు హిమపాతం మోడ్ ప్రారంభించబడింది సరిహద్దు ఫ్రేమ్ గ్లిచ్ వేరియంట్ ఛానెల్ షిఫ్ట్ గరిష్ట ఆఫ్‌సెట్ VHS బ్లాక్ గ్లిచ్ బ్లాక్ పరిమాణం CRT వక్రత వక్రత క్రోమా పిక్సెల్ మెల్ట్ మాక్స్ డ్రాప్ AI సాధనాలు ఆర్టిఫ్యాక్ట్ రిమూవల్ లేదా డీనోయిజింగ్ వంటి AI మోడల్స్ ద్వారా ఇమేజ్‌లను ప్రాసెస్ చేయడానికి వివిధ సాధనాలు కుదింపు, బెల్లం పంక్తులు కార్టూన్లు, ప్రసార కుదింపు సాధారణ కుదింపు, సాధారణ శబ్దం రంగులేని కార్టూన్ శబ్దం వేగవంతమైన, సాధారణ కుదింపు, సాధారణ శబ్దం, యానిమేషన్/కామిక్స్/యానిమే బుక్ స్కానింగ్ ఎక్స్పోజర్ దిద్దుబాటు సాధారణ కుదింపు, రంగు చిత్రాలలో ఉత్తమమైనది సాధారణ కుదింపు, గ్రేస్కేల్ చిత్రాలలో ఉత్తమమైనది సాధారణ కుదింపు, గ్రేస్కేల్ చిత్రాలు, బలమైనవి సాధారణ శబ్దం, రంగు చిత్రాలు సాధారణ శబ్దం, రంగు చిత్రాలు, మెరుగైన వివరాలు సాధారణ శబ్దం, గ్రేస్కేల్ చిత్రాలు సాధారణ శబ్దం, గ్రేస్కేల్ చిత్రాలు, బలమైనవి సాధారణ శబ్దం, గ్రేస్కేల్ చిత్రాలు, బలమైనవి సాధారణ కుదింపు సాధారణ కుదింపు టెక్స్చరైజేషన్, h264 కుదింపు VHS కుదింపు ప్రామాణికం కాని కుదింపు (సినిపాక్, msvideo1, roq) బింక్ కంప్రెషన్, జ్యామితిపై ఉత్తమం బింక్ కంప్రెషన్, బలమైనది బింక్ కంప్రెషన్, మృదువైన, వివరాలను కలిగి ఉంటుంది మెట్ల-దశ ప్రభావాన్ని తొలగించడం, సున్నితంగా చేయడం స్కాన్ చేసిన కళ/డ్రాయింగ్‌లు, తేలికపాటి కంప్రెషన్, మోయిర్ రంగు బ్యాండింగ్ నెమ్మదిగా, హాఫ్‌టోన్‌లను తొలగిస్తుంది గ్రేస్కేల్/బిడబ్ల్యు చిత్రాల కోసం సాధారణ కలర్‌రైజర్, మెరుగైన ఫలితాల కోసం DDColorని ఉపయోగించండి అంచు తొలగింపు ఓవర్‌షార్పెనింగ్‌ను తొలగిస్తుంది నెమ్మది, క్షీణించడం యాంటీ-అలియాసింగ్, సాధారణ కళాఖండాలు, CGI KDM003 స్కాన్ ప్రాసెసింగ్ తేలికపాటి ఇమేజ్ మెరుగుదల మోడల్ కంప్రెషన్ ఆర్టిఫాక్ట్ తొలగింపు కంప్రెషన్ ఆర్టిఫాక్ట్ తొలగింపు మృదువైన ఫలితాలతో కట్టు తొలగింపు హాఫ్టోన్ నమూనా ప్రాసెసింగ్ డిథర్ నమూనా తొలగింపు V3 JPEG ఆర్టిఫ్యాక్ట్ రిమూవల్ V2 H.264 ఆకృతి మెరుగుదల VHS పదునుపెట్టడం మరియు మెరుగుదల విలీనం భాగం పరిమాణం అతివ్యాప్తి పరిమాణం %1$s px కంటే ఎక్కువ ఉన్న చిత్రాలు ముక్కలుగా చేసి, భాగాలుగా ప్రాసెస్ చేయబడతాయి, కనిపించే సీమ్‌లను నిరోధించడానికి వీటిని అతివ్యాప్తి చేస్తాయి. పెద్ద పరిమాణాలు తక్కువ-ముగింపు పరికరాలతో అస్థిరతను కలిగిస్తాయి ప్రారంభించడానికి ఒకదాన్ని ఎంచుకోండి మీరు %1$s మోడల్‌ను తొలగించాలనుకుంటున్నారా? మీరు దీన్ని మళ్లీ డౌన్‌లోడ్ చేసుకోవాలి నిర్ధారించండి మోడల్స్ డౌన్‌లోడ్ చేసిన మోడల్స్ అందుబాటులో ఉన్న నమూనాలు సిద్ధమౌతోంది క్రియాశీల మోడల్ సెషన్‌ను తెరవడం విఫలమైంది .onnx/.ort మోడల్‌లను మాత్రమే దిగుమతి చేసుకోవచ్చు దిగుమతి మోడల్ మరింత ఉపయోగించడానికి అనుకూల onnx మోడల్‌ని దిగుమతి చేయండి, onnx/ort మోడల్‌లు మాత్రమే ఆమోదించబడతాయి, దాదాపు అన్ని esrgan వంటి వేరియంట్‌లకు మద్దతు ఇస్తుంది దిగుమతి చేసుకున్న మోడల్స్ సాధారణ శబ్దం, రంగు చిత్రాలు సాధారణ శబ్దం, రంగుల చిత్రాలు, బలమైనవి సాధారణ శబ్దం, రంగుల చిత్రాలు, బలమైనవి డిథరింగ్ ఆర్టిఫ్యాక్ట్‌లు మరియు కలర్ బ్యాండింగ్‌ను తగ్గిస్తుంది, స్మూత్ గ్రేడియంట్స్ మరియు ఫ్లాట్ కలర్ ఏరియాలను మెరుగుపరుస్తుంది. సహజ రంగులను సంరక్షించేటప్పుడు బ్యాలెన్స్‌డ్ హైలైట్‌లతో ఇమేజ్ బ్రైట్‌నెస్ మరియు కాంట్రాస్ట్‌ను మెరుగుపరుస్తుంది. వివరాలను ఉంచేటప్పుడు మరియు ఓవర్ ఎక్స్‌పోజర్‌ను నివారించేటప్పుడు చీకటి చిత్రాలను ప్రకాశవంతం చేస్తుంది. అధిక రంగు టోనింగ్‌ను తొలగిస్తుంది మరియు మరింత తటస్థ మరియు సహజ రంగు సమతుల్యతను పునరుద్ధరిస్తుంది. పాయిజన్ ఆధారిత నాయిస్ టోనింగ్‌ను వర్తింపజేస్తుంది, ఇది చక్కటి వివరాలు మరియు అల్లికలను సంరక్షించడంపై దృష్టి పెడుతుంది. సున్నితమైన మరియు తక్కువ దూకుడు దృశ్య ఫలితాల కోసం సాఫ్ట్ పాయిసన్ నాయిస్ టోనింగ్‌ని వర్తింపజేస్తుంది. ఏకరీతి నాయిస్ టోనింగ్ వివరాల సంరక్షణ మరియు చిత్ర స్పష్టతపై దృష్టి పెట్టింది. సూక్ష్మ ఆకృతి మరియు మృదువైన ప్రదర్శన కోసం సున్నితమైన ఏకరీతి నాయిస్ టోనింగ్. కళాఖండాలను మళ్లీ పెయింట్ చేయడం మరియు ఇమేజ్ స్థిరత్వాన్ని మెరుగుపరచడం ద్వారా దెబ్బతిన్న లేదా అసమాన ప్రాంతాలను రిపేర్ చేస్తుంది. కనిష్ట పనితీరు ఖర్చుతో కలర్ బ్యాండింగ్‌ను తీసివేసే తేలికపాటి డీబాండింగ్ మోడల్. మెరుగైన స్పష్టత కోసం చాలా ఎక్కువ కంప్రెషన్ ఆర్టిఫ్యాక్ట్‌లతో (0-20% నాణ్యత) చిత్రాలను ఆప్టిమైజ్ చేస్తుంది. అధిక కుదింపు కళాఖండాలతో చిత్రాలను మెరుగుపరుస్తుంది (20-40% నాణ్యత), వివరాలను పునరుద్ధరించడం మరియు శబ్దాన్ని తగ్గించడం. మోడరేట్ కంప్రెషన్ (40-60% నాణ్యత), బ్యాలెన్సింగ్ షార్ప్‌నెస్ మరియు స్మూత్‌నెస్‌తో ఇమేజ్‌లను మెరుగుపరుస్తుంది. సూక్ష్మ వివరాలు మరియు అల్లికలను మెరుగుపరచడానికి లైట్ కంప్రెషన్ (60-80% నాణ్యత)తో చిత్రాలను మెరుగుపరుస్తుంది. సహజమైన రూపాన్ని మరియు వివరాలను సంరక్షించేటప్పుడు నష్టం లేని చిత్రాలను (80-100% నాణ్యత) కొద్దిగా మెరుగుపరుస్తుంది. సాధారణ మరియు వేగవంతమైన రంగులు, కార్టూన్లు, ఆదర్శం కాదు చిత్ర అస్పష్టతను కొద్దిగా తగ్గిస్తుంది, కళాఖండాలను పరిచయం చేయకుండా పదును మెరుగుపరుస్తుంది. దీర్ఘకాలిక కార్యకలాపాలు చిత్రాన్ని ప్రాసెస్ చేస్తోంది ప్రాసెసింగ్ చాలా తక్కువ నాణ్యత గల చిత్రాలలో (0-20%) భారీ JPEG కంప్రెషన్ కళాఖండాలను తొలగిస్తుంది. అత్యంత కుదించబడిన చిత్రాలలో (20-40%) బలమైన JPEG కళాఖండాలను తగ్గిస్తుంది. ఇమేజ్ వివరాలను (40-60%) భద్రపరిచేటప్పుడు మోడరేట్ JPEG కళాఖండాలను శుభ్రపరుస్తుంది. తేలికపాటి JPEG కళాఖండాలను అధిక నాణ్యత గల చిత్రాలలో (60-80%) మెరుగుపరుస్తుంది. దాదాపు నష్టం లేని చిత్రాలలో (80-100%) చిన్న JPEG కళాఖండాలను సూక్ష్మంగా తగ్గిస్తుంది. చక్కటి వివరాలు మరియు అల్లికలను మెరుగుపరుస్తుంది, భారీ కళాఖండాలు లేకుండా గ్రహించిన పదును మెరుగుపరుస్తుంది. ప్రాసెసింగ్ పూర్తయింది ప్రాసెసింగ్ విఫలమైంది సహజమైన రూపాన్ని, వేగం కోసం ఆప్టిమైజ్‌గా ఉంచుతూ చర్మ ఆకృతిని మరియు వివరాలను మెరుగుపరుస్తుంది. JPEG కంప్రెషన్ కళాఖండాలను తీసివేస్తుంది మరియు కంప్రెస్ చేయబడిన ఫోటోల కోసం చిత్ర నాణ్యతను పునరుద్ధరిస్తుంది. తక్కువ-కాంతి పరిస్థితుల్లో తీసిన ఫోటోలలో ISO శబ్దాన్ని తగ్గిస్తుంది, వివరాలను భద్రపరుస్తుంది. అతిగా బహిర్గతమైన లేదా \"జంబో\" హైలైట్‌లను సరిచేస్తుంది మరియు మెరుగైన టోనల్ బ్యాలెన్స్‌ని పునరుద్ధరిస్తుంది. గ్రేస్కేల్ చిత్రాలకు సహజ రంగులను జోడించే తేలికపాటి మరియు వేగవంతమైన రంగుల నమూనా. DEJPEG డెనోయిస్ రంగులు వేయండి కళాఖండాలు మెరుగుపరచండి అనిమే స్కాన్ చేస్తుంది ఉన్నత స్థాయి సాధారణ చిత్రాల కోసం X4 అప్‌స్కేలర్; తక్కువ GPU మరియు సమయాన్ని ఉపయోగించే చిన్న మోడల్, మోడరేట్ డిబ్లర్ మరియు డెనోయిస్‌తో. సాధారణ చిత్రాల కోసం X2 అప్‌స్కేలర్, అల్లికలు మరియు సహజ వివరాలను సంరక్షించడం. మెరుగైన అల్లికలు మరియు వాస్తవిక ఫలితాలతో సాధారణ చిత్రాల కోసం X4 అప్‌స్కేలర్. X4 అప్‌స్కేలర్ అనిమే చిత్రాల కోసం ఆప్టిమైజ్ చేయబడింది; పదునైన పంక్తులు మరియు వివరాల కోసం 6 RRDB బ్లాక్‌లు. MSE నష్టంతో X4 అప్‌స్కేలర్, సాధారణ చిత్రాల కోసం సున్నితమైన ఫలితాలను మరియు తగ్గిన కళాఖండాలను ఉత్పత్తి చేస్తుంది. X4 అప్‌స్కేలర్ అనిమే చిత్రాల కోసం ఆప్టిమైజ్ చేయబడింది; పదునైన వివరాలు మరియు మృదువైన గీతలతో 4B32F వేరియంట్. సాధారణ చిత్రాల కోసం X4 అల్ట్రాషార్ప్ V2 మోడల్; పదును మరియు స్పష్టతను నొక్కి చెబుతుంది. X4 అల్ట్రాషార్ప్ V2 లైట్; వేగంగా మరియు చిన్నదిగా, తక్కువ GPU మెమరీని ఉపయోగిస్తున్నప్పుడు వివరాలను భద్రపరుస్తుంది. శీఘ్ర నేపథ్య తొలగింపు కోసం తేలికపాటి మోడల్. సమతుల్య పనితీరు మరియు ఖచ్చితత్వం. పోర్ట్రెయిట్‌లు, వస్తువులు మరియు దృశ్యాలతో పని చేస్తుంది. చాలా సందర్భాలలో ఉపయోగం కోసం సిఫార్సు చేయబడింది. BGని తీసివేయండి క్షితిజసమాంతర అంచు మందం నిలువు అంచు మందం %1$s రంగు %1$s రంగులు ప్రస్తుత మోడల్ చంకింగ్‌కు మద్దతు ఇవ్వదు, చిత్రం అసలు కొలతలలో ప్రాసెస్ చేయబడుతుంది, ఇది అధిక మెమరీ వినియోగం మరియు తక్కువ-ముగింపు పరికరాలతో సమస్యలను కలిగిస్తుంది చంకింగ్ డిజేబుల్ చేయబడింది, ఇమేజ్ అసలు కొలతల్లో ప్రాసెస్ చేయబడుతుంది, ఇది అధిక మెమరీ వినియోగం మరియు తక్కువ-ముగింపు పరికరాలతో సమస్యలకు కారణం కావచ్చు కానీ అనుమితిపై మెరుగైన ఫలితాలను ఇవ్వవచ్చు చంకింగ్ బ్యాక్‌గ్రౌండ్ రిమూవల్ కోసం హై-కచ్చితత్వంతో కూడిన ఇమేజ్ సెగ్మెంటేషన్ మోడల్ తక్కువ మెమరీ వినియోగంతో వేగవంతమైన నేపథ్య తొలగింపు కోసం U2Net యొక్క తేలికపాటి వెర్షన్. పూర్తి DDColor మోడల్ కనీస కళాఖండాలతో సాధారణ చిత్రాల కోసం అధిక-నాణ్యత రంగులను అందిస్తుంది. అన్ని రంగుల నమూనాల ఉత్తమ ఎంపిక. DDColor శిక్షణ పొందిన మరియు ప్రైవేట్ కళాత్మక డేటాసెట్‌లు; తక్కువ అవాస్తవిక రంగు కళాఖండాలతో విభిన్న మరియు కళాత్మక రంగుల ఫలితాలను ఉత్పత్తి చేస్తుంది. ఖచ్చితమైన బ్యాక్‌గ్రౌండ్ రిమూవల్ కోసం స్విన్ ట్రాన్స్‌ఫార్మర్ ఆధారంగా తేలికైన BiRefNet మోడల్. పదునైన అంచులు మరియు అద్భుతమైన వివరాల సంరక్షణతో అధిక-నాణ్యత నేపథ్య తొలగింపు, ముఖ్యంగా సంక్లిష్ట వస్తువులు మరియు గమ్మత్తైన నేపథ్యాలపై. సాధారణ వస్తువులు మరియు మితమైన వివరాల సంరక్షణకు అనువైన మృదువైన అంచులతో ఖచ్చితమైన మాస్క్‌లను ఉత్పత్తి చేసే బ్యాక్‌గ్రౌండ్ రిమూవల్ మోడల్. మోడల్ ఇప్పటికే డౌన్‌లోడ్ చేయబడింది మోడల్ విజయవంతంగా దిగుమతి చేయబడింది టైప్ చేయండి కీవర్డ్ చాలా ఫాస్ట్ సాధారణ నెమ్మదిగా చాలా స్లో శాతాలను లెక్కించండి కనిష్ట విలువ %1$s వేళ్లతో గీయడం ద్వారా చిత్రాన్ని వక్రీకరించండి వార్ప్ కాఠిన్యం వార్ప్ మోడ్ తరలించు పెరుగుతాయి కుదించు స్విర్ల్ CW స్విర్ల్ CCW ఫేడ్ స్ట్రెంత్ టాప్ డ్రాప్ బాటమ్ డ్రాప్ డ్రాప్ ప్రారంభించండి ఎండ్ డ్రాప్ డౌన్‌లోడ్ చేస్తోంది స్మూత్ ఆకారాలు మృదువైన, మరింత సహజమైన ఆకారాల కోసం ప్రామాణిక గుండ్రని దీర్ఘచతురస్రాలకు బదులుగా సూపర్‌ఎల్లిప్స్‌లను ఉపయోగించండి ఆకార రకం కట్ గుండ్రంగా మృదువైన గుండ్రంగా లేకుండా పదునైన అంచులు క్లాసిక్ గుండ్రని మూలలు ఆకారాల రకం మూలల పరిమాణం ఉడుత సొగసైన గుండ్రని UI మూలకాలు ఫైల్ పేరు ఫార్మాట్ ప్రాజెక్ట్ పేర్లు, బ్రాండ్‌లు లేదా వ్యక్తిగత ట్యాగ్‌ల కోసం అనుకూలమైన వచనం ఫైల్ పేరు ప్రారంభంలోనే ఉంచబడుతుంది. పొడిగింపు లేకుండా అసలు ఫైల్ పేరును ఉపయోగిస్తుంది, సోర్స్ గుర్తింపును అలాగే ఉంచడంలో మీకు సహాయపడుతుంది. పిక్సెల్‌లలో ఇమేజ్ వెడల్పు, రిజల్యూషన్ మార్పులు లేదా స్కేలింగ్ ఫలితాలను ట్రాక్ చేయడానికి ఉపయోగపడుతుంది. పిక్సెల్‌లలో ఇమేజ్ ఎత్తు, కారక నిష్పత్తులు లేదా ఎగుమతులతో పని చేస్తున్నప్పుడు సహాయకరంగా ఉంటుంది. ప్రత్యేకమైన ఫైల్ పేర్లకు హామీ ఇవ్వడానికి యాదృచ్ఛిక అంకెలను రూపొందిస్తుంది; నకిలీలకు వ్యతిరేకంగా అదనపు భద్రత కోసం మరిన్ని అంకెలను జోడించండి. బ్యాచ్ ఎగుమతుల కోసం ఆటో-ఇంక్రిమెంటింగ్ కౌంటర్, ఒక సెషన్‌లో బహుళ చిత్రాలను సేవ్ చేసేటప్పుడు అనువైనది. ఫైల్ పేరులో వర్తింపజేయబడిన ప్రీసెట్ పేరును చొప్పిస్తుంది, తద్వారా చిత్రం ఎలా ప్రాసెస్ చేయబడిందో మీరు సులభంగా గుర్తుంచుకోగలరు. ప్రాసెసింగ్ సమయంలో ఉపయోగించిన ఇమేజ్ స్కేలింగ్ మోడ్‌ను ప్రదర్శిస్తుంది, పరిమాణం మార్చబడిన, కత్తిరించిన లేదా అమర్చిన చిత్రాలను వేరు చేయడంలో సహాయపడుతుంది. _v2, _edited లేదా _final వంటి సంస్కరణకు ఉపయోగపడే అనుకూల వచనం ఫైల్ పేరు చివరిలో ఉంచబడుతుంది. ఫైల్ పొడిగింపు (png, jpg, webp, మొదలైనవి), స్వయంచాలకంగా సేవ్ చేయబడిన వాస్తవ ఆకృతికి సరిపోలుతుంది. అనుకూలీకరించదగిన టైమ్‌స్టాంప్, ఇది ఖచ్చితమైన సార్టింగ్ కోసం జావా స్పెసిఫికేషన్ ద్వారా మీ స్వంత ఆకృతిని నిర్వచించటానికి మిమ్మల్ని అనుమతిస్తుంది. ఫ్లింగ్ రకం Android స్థానిక iOS శైలి స్మూత్ కర్వ్ త్వరిత ఆపు ఎగిరి పడే తేలియాడే చురుకైన అల్ట్రా స్మూత్ అనుకూలమైనది యాక్సెసిబిలిటీ అవేర్ తగ్గిన చలనం స్థానిక ఆండ్రాయిడ్ స్క్రోల్ ఫిజిక్స్ సాధారణ ఉపయోగం కోసం సమతుల్య, మృదువైన స్క్రోలింగ్ అధిక ఘర్షణ iOS-వంటి స్క్రోల్ ప్రవర్తన ప్రత్యేకమైన స్క్రోల్ అనుభూతి కోసం ప్రత్యేకమైన స్ప్లైన్ కర్వ్ త్వరిత స్టాపింగ్‌తో ఖచ్చితమైన స్క్రోలింగ్ ఉల్లాసభరితమైన, ప్రతిస్పందించే ఎగిరి పడే స్క్రోల్ కంటెంట్ బ్రౌజింగ్ కోసం పొడవైన, గ్లైడింగ్ స్క్రోల్‌లు ఇంటరాక్టివ్ UIల కోసం త్వరిత, ప్రతిస్పందించే స్క్రోలింగ్ పొడిగించిన మొమెంటంతో ప్రీమియం మృదువైన స్క్రోలింగ్ ఫ్లింగ్ వేగం ఆధారంగా భౌతిక శాస్త్రాన్ని సర్దుబాటు చేస్తుంది సిస్టమ్ యాక్సెసిబిలిటీ సెట్టింగ్‌లను గౌరవిస్తుంది ప్రాప్యత అవసరాల కోసం కనీస చలనం ప్రాథమిక పంక్తులు ప్రతి ఐదవ పంక్తికి మందమైన పంక్తిని జోడిస్తుంది రంగును పూరించండి దాచిన సాధనాలు భాగస్వామ్యం కోసం దాచబడిన సాధనాలు రంగు లైబ్రరీ రంగుల విస్తారమైన సేకరణను బ్రౌజ్ చేయండి ఫోకస్ లేని ఫోటోలను ఫిక్సింగ్ చేయడానికి అనువైన సహజ వివరాలను నిర్వహించేటప్పుడు చిత్రాల నుండి పదును మరియు బ్లర్‌ను తొలగిస్తుంది. మునుపు పరిమాణం మార్చబడిన చిత్రాలను తెలివిగా పునరుద్ధరిస్తుంది, కోల్పోయిన వివరాలు మరియు అల్లికలను తిరిగి పొందుతుంది. లైవ్-యాక్షన్ కంటెంట్ కోసం ఆప్టిమైజ్ చేయబడింది, కుదింపు కళాఖండాలను తగ్గిస్తుంది మరియు సినిమా/టీవీ షో ఫ్రేమ్‌లలో చక్కటి వివరాలను మెరుగుపరుస్తుంది. VHS-నాణ్యత ఫుటేజీని HDకి మారుస్తుంది, టేప్ శబ్దాన్ని తీసివేస్తుంది మరియు పాతకాలపు అనుభూతిని కాపాడుతూ రిజల్యూషన్‌ను మెరుగుపరుస్తుంది. టెక్స్ట్-హెవీ ఇమేజ్‌లు మరియు స్క్రీన్‌షాట్‌ల కోసం ప్రత్యేకించబడింది, అక్షరాలను పదునుపెడుతుంది మరియు రీడబిలిటీని మెరుగుపరుస్తుంది. విభిన్న డేటాసెట్‌లపై శిక్షణ పొందిన అధునాతన అప్‌స్కేలింగ్, సాధారణ ప్రయోజన ఫోటో మెరుగుదల కోసం అద్భుతమైనది. వెబ్-కంప్రెస్ చేయబడిన ఫోటోల కోసం ఆప్టిమైజ్ చేయబడింది, JPEG కళాఖండాలను తీసివేస్తుంది మరియు సహజ రూపాన్ని పునరుద్ధరిస్తుంది. మెరుగైన ఆకృతి సంరక్షణ మరియు కళాఖండాల తగ్గింపుతో వెబ్ ఫోటోల కోసం మెరుగైన సంస్కరణ. డ్యూయల్ అగ్రిగేషన్ ట్రాన్స్‌ఫార్మర్ టెక్నాలజీతో 2x అప్‌స్కేలింగ్, పదును మరియు సహజ వివరాలను నిర్వహిస్తుంది. ఆధునిక ట్రాన్స్‌ఫార్మర్ ఆర్కిటెక్చర్ ఉపయోగించి 3x అప్‌స్కేలింగ్, మితమైన విస్తరణ అవసరాలకు అనువైనది. అత్యాధునిక ట్రాన్స్‌ఫార్మర్ నెట్‌వర్క్‌తో 4x అధిక-నాణ్యత అప్‌స్కేలింగ్, పెద్ద ప్రమాణాల వద్ద చక్కటి వివరాలను భద్రపరుస్తుంది. ఫోటోల నుండి బ్లర్/నాయిస్ మరియు షేక్‌లను తొలగిస్తుంది. సాధారణ ప్రయోజనం కానీ ఫోటోలలో ఉత్తమమైనది. Swin2SR ట్రాన్స్‌ఫార్మర్‌ని ఉపయోగించి తక్కువ-నాణ్యత చిత్రాలను పునరుద్ధరిస్తుంది, BSRGAN అధోకరణం కోసం ఆప్టిమైజ్ చేయబడింది. భారీ కుదింపు కళాఖండాలను ఫిక్సింగ్ చేయడానికి మరియు 4x స్కేల్‌లో వివరాలను మెరుగుపరచడానికి గొప్పది. BSRGAN క్షీణతపై శిక్షణ పొందిన SwinIR ట్రాన్స్‌ఫార్మర్‌తో 4x అప్‌స్కేలింగ్. ఫోటోలు మరియు సంక్లిష్ట దృశ్యాలలో పదునైన అల్లికలు మరియు మరింత సహజమైన వివరాల కోసం GANని ఉపయోగిస్తుంది. మార్గం PDFని విలీనం చేయండి బహుళ PDF ఫైల్‌లను ఒక పత్రంలో కలపండి ఫైల్స్ ఆర్డర్ పేజీలు PDFని విభజించండి PDF పత్రం నుండి నిర్దిష్ట పేజీలను సంగ్రహించండి PDFని తిప్పండి పేజీ విన్యాసాన్ని శాశ్వతంగా పరిష్కరించండి పేజీలు PDFని మళ్లీ అమర్చండి వాటిని క్రమాన్ని మార్చడానికి పేజీలను లాగండి మరియు వదలండి పేజీలను పట్టుకుని లాగండి పేజీ సంఖ్యలు మీ పత్రాలకు స్వయంచాలకంగా నంబరింగ్ జోడించండి లేబుల్ ఫార్మాట్ PDF నుండి టెక్స్ట్ (OCR) మీ PDF పత్రాల నుండి సాదా వచనాన్ని సంగ్రహించండి బ్రాండింగ్ లేదా భద్రత కోసం అనుకూల వచనాన్ని అతివ్యాప్తి చేయండి సంతకం ఏదైనా పత్రానికి మీ ఎలక్ట్రానిక్ సంతకాన్ని జోడించండి ఇది సంతకం వలె ఉపయోగించబడుతుంది PDFని అన్‌లాక్ చేయండి మీ రక్షిత ఫైల్‌ల నుండి పాస్‌వర్డ్‌లను తీసివేయండి PDFని రక్షించండి బలమైన ఎన్‌క్రిప్షన్‌తో మీ పత్రాలను భద్రపరచండి విజయం PDF అన్‌లాక్ చేయబడింది, మీరు దీన్ని సేవ్ చేయవచ్చు లేదా భాగస్వామ్యం చేయవచ్చు PDFని రిపేర్ చేయండి పాడైన లేదా చదవలేని పత్రాలను పరిష్కరించడానికి ప్రయత్నం గ్రేస్కేల్ అన్ని డాక్యుమెంట్ ఎంబెడెడ్ ఇమేజ్‌లను గ్రేస్కేల్‌కి మార్చండి PDFని కుదించుము సులభంగా భాగస్వామ్యం చేయడానికి మీ డాక్యుమెంట్ ఫైల్ పరిమాణాన్ని ఆప్టిమైజ్ చేయండి ImageToolbox అంతర్గత క్రాస్-రిఫరెన్స్ పట్టికను పునర్నిర్మిస్తుంది మరియు మొదటి నుండి ఫైల్ నిర్మాణాన్ని పునరుత్పత్తి చేస్తుంది. ఇది \\"తెరవలేని\\" అనేక ఫైల్‌లకు యాక్సెస్‌ని పునరుద్ధరించగలదు ఈ సాధనం అన్ని డాక్యుమెంట్ చిత్రాలను గ్రేస్కేల్‌కి మారుస్తుంది. ఫైల్ పరిమాణాన్ని ముద్రించడానికి మరియు తగ్గించడానికి ఉత్తమమైనది మెటాడేటా మెరుగైన గోప్యత కోసం డాక్యుమెంట్ ప్రాపర్టీలను సవరించండి ట్యాగ్‌లు నిర్మాత రచయిత కీలకపదాలు సృష్టికర్త గోప్యత డీప్ క్లీన్ ఈ పత్రం కోసం అందుబాటులో ఉన్న మొత్తం మెటాడేటాను క్లియర్ చేయండి పేజీ లోతైన OCR పత్రం నుండి వచనాన్ని సంగ్రహించి, టెసెరాక్ట్ ఇంజిన్‌ని ఉపయోగించి ఒక టెక్స్ట్ ఫైల్‌లో నిల్వ చేయండి అన్ని పేజీలను తీసివేయలేరు PDF పేజీలను తీసివేయండి PDF పత్రం నుండి నిర్దిష్ట పేజీలను తీసివేయండి తీసివేయడానికి నొక్కండి మానవీయంగా PDFని కత్తిరించండి డాక్యుమెంట్ పేజీలను ఏదైనా హద్దులకు కత్తిరించండి PDFని చదును చేయండి డాక్యుమెంట్ పేజీలను రేస్టరింగ్ చేయడం ద్వారా PDFని సవరించలేనిదిగా చేయండి కెమెరాను ప్రారంభించడం సాధ్యపడలేదు. దయచేసి అనుమతులను తనిఖీ చేయండి మరియు ఇది మరొక యాప్ ద్వారా ఉపయోగించబడటం లేదని నిర్ధారించుకోండి. చిత్రాలను సంగ్రహించండి PDFలలో పొందుపరిచిన చిత్రాలను వాటి అసలు రిజల్యూషన్‌లో సంగ్రహించండి ఈ PDF ఫైల్‌లో పొందుపరిచిన చిత్రాలు ఏవీ లేవు ఈ సాధనం ప్రతి పేజీని స్కాన్ చేస్తుంది మరియు పూర్తి-నాణ్యత మూలాధార చిత్రాలను తిరిగి పొందుతుంది - పత్రాల నుండి అసలైన వాటిని సేవ్ చేయడానికి సరైనది సంతకాన్ని గీయండి పెన్ పారామ్స్ పత్రాలపై ఉంచడానికి సొంత సంతకాన్ని చిత్రంగా ఉపయోగించండి జిప్ PDF ఇచ్చిన విరామంతో పత్రాన్ని విభజించి, కొత్త పత్రాలను జిప్ ఆర్కైవ్‌లో ప్యాక్ చేయండి ఇంటర్వెల్ PDFని ముద్రించండి అనుకూల పేజీ పరిమాణంతో ప్రింటింగ్ కోసం పత్రాన్ని సిద్ధం చేయండి ప్రతి షీట్‌కి పేజీలు ఓరియంటేషన్ పేజీ పరిమాణం మార్జిన్ బ్లూమ్ మృదువైన మోకాలి అనిమే మరియు కార్టూన్‌ల కోసం ఆప్టిమైజ్ చేయబడింది. మెరుగైన సహజ రంగులు మరియు తక్కువ కళాఖండాలతో వేగవంతమైన అప్‌స్కేలింగ్ Samsung One UI 7 వంటి శైలి కావలసిన విలువను లెక్కించడానికి ఇక్కడ ప్రాథమిక గణిత చిహ్నాలను నమోదు చేయండి (ఉదా. (5+5)*10) గణిత వ్యక్తీకరణ %1$s చిత్రాల వరకు ఎంచుకోండి తేదీ సమయాన్ని ఉంచండి తేదీ మరియు సమయానికి సంబంధించిన ఎక్సిఫ్ ట్యాగ్‌లను ఎల్లప్పుడూ భద్రపరచండి, కీప్ ఎక్సిఫ్ ఎంపికతో సంబంధం లేకుండా స్వతంత్రంగా పనిచేస్తుంది ఆల్ఫా ఫార్మాట్‌ల కోసం నేపథ్య రంగు ఆల్ఫా సపోర్ట్‌తో ప్రతి ఇమేజ్ ఫార్మాట్‌కి బ్యాక్‌గ్రౌండ్ కలర్ సెట్ చేసే సామర్థ్యాన్ని జోడిస్తుంది, డిసేబుల్ చేసినప్పుడు ఆల్ఫా కాని వాటికి మాత్రమే ఇది అందుబాటులో ఉంటుంది ప్రాజెక్ట్ తెరవండి మునుపు సేవ్ చేసిన ఇమేజ్ టూల్‌బాక్స్ ప్రాజెక్ట్‌ని సవరించడం కొనసాగించండి ఇమేజ్ టూల్‌బాక్స్ ప్రాజెక్ట్‌ను తెరవడం సాధ్యం కాలేదు ఇమేజ్ టూల్‌బాక్స్ ప్రాజెక్ట్ ప్రాజెక్ట్ డేటా లేదు ఇమేజ్ టూల్‌బాక్స్ ప్రాజెక్ట్ పాడైంది మద్దతు లేని ఇమేజ్ టూల్‌బాక్స్ ప్రాజెక్ట్ వెర్షన్: %1$d ప్రాజెక్ట్‌ను సేవ్ చేయండి సవరించగలిగే ప్రాజెక్ట్ ఫైల్‌లో లేయర్‌లు, నేపథ్యం మరియు సవరణ చరిత్రను నిల్వ చేయండి తెరవడంలో విఫలమైంది శోధించదగిన PDFకి వ్రాయండి ఇమేజ్ బ్యాచ్ నుండి వచనాన్ని గుర్తించి, శోధించదగిన PDFని ఇమేజ్ మరియు ఎంచుకోదగిన టెక్స్ట్ లేయర్‌తో సేవ్ చేయండి ఆల్ఫా పొర క్షితిజసమాంతర ఫ్లిప్ వర్టికల్ ఫ్లిప్ తాళం వేయండి షాడో జోడించండి నీడ రంగు టెక్స్ట్ జ్యామితి పదునైన స్టైలైజేషన్ కోసం వచనాన్ని సాగదీయండి లేదా వక్రంగా మార్చండి స్కేల్ X స్కేవ్ X ఉల్లేఖనాలను తీసివేయండి PDF పేజీల నుండి లింక్‌లు, వ్యాఖ్యలు, ముఖ్యాంశాలు, ఆకారాలు లేదా ఫారమ్ ఫీల్డ్‌ల వంటి ఎంచుకున్న ఉల్లేఖన రకాలను తీసివేయండి హైపర్‌లింక్‌లు ఫైల్ జోడింపులు లైన్లు పాపప్‌లు స్టాంపులు ఆకారాలు టెక్స్ట్ నోట్స్ టెక్స్ట్ మార్కప్ ఫారమ్ ఫీల్డ్స్ మార్కప్ తెలియదు ఉల్లేఖనాలు సమూహాన్ని తీసివేయండి కాన్ఫిగర్ చేయగల రంగు మరియు ఆఫ్‌సెట్‌లతో లేయర్ వెనుక బ్లర్ షాడోని జోడించండి ================================================ FILE: core/resources/src/main/res/values-th/strings.xml ================================================ การปิดแอป อยู่ ปิด รีเซ็ตภาพ เพิ่มแท็ก โฟลเดอร์เอาต์พุต ค่าเริ่มต้น กำหนดเอง การตั้งค่า ไม่มีอะไรจะวาง หลัก ตติยภูมิ สีโมเน่ต์ แอปพลิเคชันนี้ไม่มีค่าใช้จ่ายใด ๆ แต่ถ้าคุณต้องการสนับสนุนการพัฒนาโครงการ คุณสามารถคลิกที่นี่ การจัดตำแหน่ง FAB ตรวจสอบสำหรับการอัพเดต หากเปิดใช้งาน กล่องโต้ตอบการอัปเดตจะแสดงให้คุณเห็นหลังจากเริ่มต้นแอป ซูมภาพ เกิดข้อผิดพลาด: %1$s ขนาด %1$s กำลังโหลด… รูปภาพมีขนาดใหญ่เกินไปที่จะดูตัวอย่าง แต่จะพยายามบันทึกต่อไป เลือกรูปภาพเพื่อเริ่มต้น ความกว้าง %1$s ความสูง %1$s คุณภาพ ส่วนขยาย ประเภทการปรับขนาด ชัดเจน ยืดหยุ่นได้ เลือกรูปภาพ การเปลี่ยนแปลงรูปภาพจะถูกย้อนกลับเป็นค่าเริ่มต้น คุณต้องการปิดแอปจริงๆ หรือไม่? รีเซ็ตค่าอย่างถูกต้อง รีเซ็ต บางอย่างผิดพลาด รีสตาร์ทแอป คัดลอกไปที่คลิปบอร์ดแล้ว ข้อยกเว้น แก้ไข EXIF ตกลง ไม่พบข้อมูล EXIF บันทึก ชัดเจน ล้าง EXIF ยกเลิก ข้อมูล EXIF ทั้งหมดของรูปภาพจะถูกล้าง การดำเนินการนี้ไม่สามารถยกเลิกได้! ค่าที่ตั้งไว้ล่วงหน้า ครอบตัด ประหยัด การเปลี่ยนแปลงที่ไม่ได้บันทึกทั้งหมดจะหายไป หากคุณออกตอนนี้ รหัสแหล่งที่มา รับข้อมูลอัปเดตล่าสุด หารือเกี่ยวกับปัญหา และอื่นๆ ปรับขนาดเดียว เปลี่ยนข้อกำหนดของภาพเดียวที่กำหนด เลือกสี เลือกสีจากภาพ คัดลอกหรือแชร์ ภาพ สี ทำสำเนาสีแล้ว ครอบตัดรูปภาพไปยังขอบเขตใดก็ได้ รุ่น เก็บ EXIF ภาพ: %d เปลี่ยนการแสดงตัวอย่าง ลบ สร้างแถบสีจากภาพที่กำหนด สร้างจานสี จานสี อัปเดต รุ่นใหม่ %1$s ประเภทที่ไม่รองรับ: %1$s ไม่สามารถสร้างจานสีสำหรับภาพที่กำหนด ต้นฉบับ ไม่ระบุ ที่เก็บข้อมูลอุปกรณ์ ปรับขนาดตามน้ำหนัก ขนาดสูงสุดเป็น KB ปรับขนาดรูปภาพตามขนาดที่กำหนดเป็น KB เปรียบเทียบ เปรียบเทียบสองภาพที่กำหนด เลือกสองภาพเพื่อเริ่มต้น เลือกภาพ โหมดกลางคืน มืด แสงสว่าง ระบบ สีแบบไดนามิก การปรับแต่ง อนุญาตภาพเงิน หากเปิดใช้งาน เมื่อคุณเลือกภาพที่จะแก้ไข สีของแอพจะถูกนำไปใช้กับภาพนี้ ภาษา โหมด Amoled หากเปิดใช้งานสีพื้นผิวจะถูกตั้งค่าเป็นโหมดมืดสนิทในโหมดกลางคืน รูปแบบสี สีแดง สีเขียว สีฟ้า วางรหัส RGB ที่ถูกต้อง ไม่สามารถเปลี่ยนรูปแบบสีของแอพได้ในขณะที่เปิดสีไดนามิก ธีมของแอพจะขึ้นอยู่กับสีซึ่งคุณจะเลือก เกี่ยวกับแอพ ไม่พบการอัปเดต ตัวติดตามปัญหา ส่งรายงานข้อผิดพลาดและคำขอคุณสมบัติที่นี่ ช่วยแปล แก้ไขข้อผิดพลาดในการแปลหรือแปลโครงการเป็นภาษาอื่น ไม่พบการค้นหาของคุณ ค้นหาที่นี่ หากเปิดใช้งาน สีของแอพจะถูกนำไปใช้กับสีวอลเปเปอร์ บันทึกภาพ %d ไม่สำเร็จ รอง ความหนาของเส้นขอบ พื้นผิว ค่า เพิ่ม การอนุญาต ยินยอม แอปจำเป็นต้องเข้าถึงพื้นที่เก็บข้อมูลของคุณเพื่อบันทึกภาพ ซึ่งจำเป็น หากไม่สามารถทำงานได้ ดังนั้นโปรดให้สิทธิ์ในกล่องโต้ตอบถัดไป แอปต้องการการอนุญาตนี้จึงจะทำงานได้ โปรดอนุญาตด้วยตนเอง จัดเก็บข้อมูลภายนอก แบ่งปัน คำนำหน้า ชื่อไฟล์ การรับสัมผัสเชื้อ สมดุลสีขาว อุณหภูมิ แกมมา ไฮไลท์และเงา เหลา ครอสแฮทช์ ระยะห่าง ความกว้างของเส้น เพิ่มขนาดไฟล์ ลบ EXIF โหลดรูปจากเน็ต ฟิลเตอร์สี เชิงลบ ความสั่นสะเทือน ดำและขาว ขอบโซเบล เบลอ ลาปลาเซียน การบิดเบือน รัศมี มาตราส่วน จำกัดการปรับขนาด อีโมจิ เบลออย่างรวดเร็ว ขนาดเบลอ ศูนย์เบลอ x ศูนย์เบลอ y ซูมเบลอ เกณฑ์ความสว่าง หากเปิดใช้ ให้เพิ่มความกว้างและความสูงของภาพที่บันทึกไว้ในชื่อไฟล์ที่ส่งออก ลบข้อมูลเมตา EXIF ออกจากภาพสองสามภาพ ตัวสำรวจไฟล์ เครื่องมือเลือกรูปภาพที่ทันสมัยของ Android ซึ่งปรากฏที่ด้านล่างของหน้าจอ อาจใช้งานได้กับ Android 12+ เท่านั้น และยังมีปัญหาเกี่ยวกับการรับข้อมูลเมตา EXIF เครื่องมือเลือกรูปภาพในแกลเลอรีอย่างง่ายจะทำงานได้ก็ต่อเมื่อคุณมีแอปนั้น การจัดตัวเลือก แก้ไข คำสั่ง กำหนดลำดับของตัวเลือกบนหน้าจอหลัก แทนที่หมายเลขลำดับ หากเปิดใช้งาน จะแทนที่การประทับเวลามาตรฐานเป็นหมายเลขลำดับภาพ หากคุณใช้การประมวลผลเป็นชุด โหลดรูปภาพจากอินเทอร์เน็ต ดูตัวอย่าง ซูม และบันทึกหรือแก้ไขหากคุณต้องการ ไม่มีรูป ลิงค์รูปภาพ เติม พอดี บังคับให้ทุกภาพเป็นภาพที่กำหนดโดยพารามิเตอร์ความกว้างและความสูง - อาจเปลี่ยนอัตราส่วนกว้างยาว ปรับขนาดรูปภาพเป็นภาพที่มีด้านยาวที่กำหนดโดยพารามิเตอร์ความกว้างหรือความสูง การคำนวณขนาดทั้งหมดจะทำหลังจากบันทึก - คงอัตราส่วนไว้ ความสว่าง ตัดกัน เว้ ความอิ่มตัว เพิ่มตัวกรอง กรอง ใช้ห่วงโซ่ตัวกรองกับภาพที่กำหนด ตัวกรอง แสงสว่าง อัลฟ่า สีอ่อน ขาวดำ ไฮไลท์ เงา หมอกควัน ผล ระยะทาง ความลาดชัน ซีเปีย โซลาไรซ์ ฮาล์ฟโทน GCA คัลเลอร์สเปซ เกาส์เบลอ กล่องเบลอ เบลอแบบทวิภาคี นูน บทความสั้น เริ่ม จบ คุวาฮาระปรับให้เรียบ มุม หมุน นูน การขยาย การหักเหของทรงกลม ดัชนีหักเห การหักเหของทรงกลมแก้ว เมทริกซ์สี ความทึบ ปรับขนาดรูปภาพที่กำหนดตามขีดจำกัดความกว้างและความสูงที่กำหนดพร้อมบันทึกอัตราส่วนภาพ ร่าง เกณฑ์ ระดับปริมาณ ตูนเนียน ตูน โปสเตอร์ การปราบปรามไม่สูงสุด การรวมพิกเซลที่อ่อนแอ ค้นหา เลือกอีโมจิที่จะแสดงบนหน้าจอหลัก ซ้อนภาพเบลอ คอนโวลูชั่น 3x3 ตัวกรอง RGB สีเท็จ สีแรก สีที่สอง จัดลำดับใหม่ ความสมดุลของสี ภาพตัวอย่าง ดูตัวอย่างรูปภาพประเภทใดก็ได้: GIF, SVG และอื่นๆ ที่มาของภาพ เครื่องมือเลือกรูปภาพ แกลลอรี่ ใช้ความตั้งใจของ GetContent ในการเลือกภาพ ทำงานได้ทุกที่ แต่อาจมีปัญหาในการรับภาพที่เลือกบนอุปกรณ์บางอย่าง นั่นไม่ใช่ความผิดของฉัน ขนาดเนื้อหา อิโมจินับ ลำดับหมายเลข ชื่อไฟล์ต้นฉบับ เพิ่มชื่อไฟล์ต้นฉบับ หากเปิดใช้งาน ให้เพิ่มชื่อไฟล์ต้นฉบับในชื่อของภาพที่ส่งออก การเพิ่มชื่อไฟล์ต้นฉบับไม่ทำงานหากเลือกแหล่งที่มาของรูปภาพในเครื่องมือเลือกรูปภาพ ความเข้ากันได้ พบ %1$s วาดภาพเหมือนในสมุดสเก็ตช์หรือวาดบนพื้นหลังเอง สีเพ้นท์ ทาสีอัลฟ่า วาดภาพ เลือกภาพและวาดบางอย่างลงไป เลือกสีพื้นหลังและวาดทับลงไป วาดบนพื้นหลัง สีพื้นหลัง รหัส ถอดรหัส สำคัญ ขนาดแคช การล้างแคชอัตโนมัติ สำเนา ข้าม การบันทึกในโหมด %1$s อาจไม่เสถียร เนื่องจากเป็นรูปแบบที่ไม่สูญเสียข้อมูล แคช การปรับแต่งรอง คุณปิดใช้งานแอพ Files เปิดใช้งานเพื่อใช้คุณสมบัตินี้ รหัสผ่านไม่ถูกต้องหรือไฟล์ที่เลือกไม่ถูกเข้ารหัส เข้ารหัสและถอดรหัสไฟล์ใด ๆ (ไม่ใช่เฉพาะรูปภาพ) ตามอัลกอริทึมการเข้ารหัส AES การเข้ารหัส คุณสมบัติ ขนาดไฟล์สูงสุดถูกจำกัดโดยระบบปฏิบัติการ Android และหน่วยความจำที่มี ซึ่งขึ้นอยู่กับอุปกรณ์ของคุณ \nโปรดทราบ: หน่วยความจำไม่ใช่ที่เก็บข้อมูล การพยายามบันทึกรูปภาพด้วยความกว้างและความสูงที่กำหนดอาจทำให้เกิดข้อผิดพลาด OOM คุณต้องยอมรับความเสี่ยงเอง และอย่าหาว่าฉันไม่เตือนคุณ! หากเปิดใช้งานแคชของแอปจะถูกล้างเมื่อเริ่มต้นแอป ภาพหน้าจอ ตัวเลือกสำรอง วาด เลือกไฟล์ เข้ารหัส ถอดรหัส เลือกไฟล์เพื่อเริ่มต้น จัดเก็บไฟล์นี้บนอุปกรณ์ของคุณหรือใช้การดำเนินการแชร์เพื่อวางไว้ทุกที่ที่คุณต้องการ การดำเนินการ AES-256, โหมด GCM, ไม่มีการเติม, IV แบบสุ่ม 12 ไบต์ คีย์ใช้เป็นแฮช SHA-3 (256 บิต) ขนาดไฟล์ โปรดทราบว่าไม่รับประกันความเข้ากันได้กับซอฟต์แวร์หรือบริการเข้ารหัสไฟล์อื่นๆ การรักษาคีย์ที่แตกต่างกันเล็กน้อยหรือการกำหนดค่ารหัสอาจเป็นสาเหตุของความเข้ากันไม่ได้ การเข้ารหัสไฟล์โดยใช้รหัสผ่าน ไฟล์ที่ดำเนินการแล้วสามารถเก็บไว้ในไดเร็กทอรีที่เลือกหรือใช้ร่วมกัน สามารถเปิดไฟล์ที่ถอดรหัสได้โดยตรง ประมวลผลไฟล์แล้ว เครื่องมือ จัดกลุ่มตัวเลือกตามประเภท ตัวเลือกการจัดกลุ่มบนหน้าจอหลักของประเภทแทนการจัดเรียงรายการแบบกำหนดเอง ไม่สามารถเปลี่ยนการจัดเรียงในขณะที่เปิดใช้งานการจัดกลุ่มตัวเลือก สร้าง แก้ไขภาพหน้าจอ พิกเซลที่ได้รับการปรับปรุง การเกิดพิกเซลแบบสโตรค ค่าเริ่มต้น ความผิดพลาดที่เพิ่มขึ้น การเปลี่ยนช่อง Y ซีดข้าง วาดลูกศร ความนุ่มนวลของแปรง อนุญาตให้รวบรวมสถิติการใช้งานแอปที่ไม่ระบุชื่อ บันทึกไปยัง %1$s โฟลเดอร์ชื่อ %2$s ความพยายาม สุ่มชื่อไฟล์ วัตถุ การใช้ขนาดตัวอักษรขนาดใหญ่อาจทำให้เกิดข้อผิดพลาดและปัญหา UI ซึ่งจะไม่ได้รับการแก้ไข ใช้ด้วยความระมัดระวัง บริจาค การเดินทางและสถานที่ อัพเดท การวางแนว & การตรวจจับสคริปต์เท่านั้น การวางแนวอัตโนมัติ & การตรวจจับสคริปต์ อัตโนมัติเท่านั้น อัตโนมัติ คอลัมน์เดียว บล็อคเดียว คำเดียว ข้อความกระจัดกระจาย การวางแนวข้อความกระจัดกระจาย & การตรวจจับสคริปต์ เส้นดิบ ความผิดพลาด จำนวน แอนากลิฟ เสียงรบกวน การเรียงลำดับพิกเซล สับเปลี่ยน โมเบียส ไม่พบไดเรกทอรี \"%1$s\" เราได้เปลี่ยนเป็นไดเรกทอรีเริ่มต้นแล้ว โปรดบันทึกไฟล์อีกครั้ง คลิปบอร์ด พินอัตโนมัติ เพิ่มภาพที่บันทึกไว้ลงในคลิปบอร์ดโดยอัตโนมัติหากเปิดใช้งาน หากต้องการเขียนทับไฟล์ คุณต้องใช้แหล่งรูปภาพ \"Explorer\" ลองเลือกรูปภาพใหม่ เราได้เปลี่ยนแหล่งรูปภาพเป็นแหล่งที่ต้องการ เขียนทับไฟล์ ว่างเปล่า คำต่อท้าย ค้นหา ช่วยให้สามารถค้นหาตัวเลือกที่มีอยู่ทั้งหมดบนหน้าจอหลักได้ ฟรี รูปภาพถูกเขียนทับที่ปลายทางเดิม อิโมจิเป็นโครงร่างสี สำรองการตั้งค่าแอปของคุณเป็นไฟล์ หากคุณเลือกค่าที่ตั้งไว้ล่วงหน้า 125 รูปภาพจะถูกบันทึกเป็นขนาด 125% ของรูปภาพต้นฉบับพร้อมคุณภาพ 100% หากคุณเลือกค่าที่ตั้งล่วงหน้า 50 รูปภาพจะถูกบันทึกด้วยขนาด 50% และคุณภาพ 50% หากเปิดใช้งานชื่อไฟล์เอาต์พุตจะเป็นการสุ่มอย่างสมบูรณ์ การตั้งค่าล่วงหน้าที่นี่จะกำหนด % ของไฟล์เอาต์พุต เช่น หากคุณเลือกการตั้งค่าล่วงหน้า 50 บนรูปภาพขนาด 5mb คุณจะได้รูปภาพขนาด 2.5mb หลังจากบันทึก สำรองข้อมูล คืนค่าการตั้งค่าแอปจากไฟล์ที่สร้างไว้ก่อนหน้านี้ ไฟล์เสียหายหรือไม่ใช่ข้อมูลสำรอง กู้คืนการตั้งค่าเรียบร้อยแล้ว ติดต่อฉัน แบบอักษร ข้อความ ขนาดตัวอักษร ก ข ฃ ค ฅ ฆ ง จ ฉ ช ซ ฌ ญ ฎ ฏ ฐ ฑ ฒ ณ ด ต ถ ท ธ น บ ป ผ ฝ พ ฟ ภ ม ย ร ล ว ศ ษ ส ห ฬ อ ฮ 0123456789 !? กิจกรรม เครื่องมือลบพื้นหลัง ลบพื้นหลังออกจากภาพโดยการวาดหรือใช้ตัวเลือกอัตโนมัติ ตัดภาพ ลบพื้นหลังอัตโนมัติ โหมดลบ ลบพื้นหลัง คืนค่าพื้นหลัง รัศมีเบลอ โหมดการวาด สร้างประเด็น อ๊ะ… มีบางอย่างผิดพลาด คุณสามารถเขียนถึงฉันโดยใช้ตัวเลือกด้านล่าง แล้วฉันจะพยายามหาทางแก้ไข เปลี่ยนขนาดของรูปภาพที่กำหนดหรือแปลงเป็นรูปแบบอื่น ข้อมูลเมตา EXIF สามารถแก้ไขได้ที่นี่หากเลือกภาพเดียว จำนวนสีสูงสุด ซึ่งช่วยให้แอปรวบรวมรายงานข้อขัดข้องด้วยตนเอง การวิเคราะห์ ปัจจุบัน รูปแบบ %1$s อนุญาตให้อ่านเฉพาะข้อมูลเมตา EXIF บน Android เท่านั้น รูปภาพที่ส่งออกจะไม่มีข้อมูลเมตาเลยเมื่อบันทึก ค่า %1$s หมายถึงการบีบอัดที่รวดเร็ว ส่งผลให้ขนาดไฟล์ค่อนข้างใหญ่ %2$s หมายถึงการบีบอัดที่ช้าลง ส่งผลให้ไฟล์มีขนาดเล็กลง รอ ออมทรัพย์ใกล้จะสมบูรณ์แล้ว การยกเลิกตอนนี้จะต้องบันทึกอีกครั้ง อนุญาตเบต้า การตรวจสอบการอัปเดตจะรวมแอปเวอร์ชันเบต้าด้วยหากเปิดใช้งาน หากเปิดใช้งานเส้นทางการวาดภาพจะแสดงเป็นลูกศรชี้ รูปภาพจะถูกครอบตัดตรงกลางตามขนาดที่ป้อน แคนวาสจะถูกขยายด้วยสีพื้นหลังที่กำหนดหากรูปภาพมีขนาดเล็กกว่าขนาดที่ป้อน ปรับขนาดภาพเล็กให้เป็นภาพใหญ่ รูปภาพขนาดเล็กจะถูกปรับขนาดให้ใหญ่ที่สุดตามลำดับหากเปิดใช้งาน สีเป้าหมาย สีที่จะลบ ลบสี เข้ารหัสใหม่ สไตล์พาเลท จุดวรรณยุกต์ เป็นกลาง มีชีวิตชีวา แสดงออก รุ้ง สลัดผลไม้ ความจงรักภักดี เนื้อหา รูปแบบจานสีเริ่มต้น อนุญาตให้ปรับแต่งสีทั้งสี่สี ส่วนสไตล์อื่นๆ อนุญาตให้คุณตั้งค่าเฉพาะสีหลักเท่านั้น สไตล์ที่มีสีมากกว่าสีเดียวเล็กน้อย ธีมที่ดัง สีสันเป็นสูงสุดสำหรับจานสีหลัก และเพิ่มขึ้นสำหรับจานสีอื่นๆ ธีมที่สนุกสนาน - สีของต้นฉบับไม่ปรากฏในธีม ธีมขาวดำ มีสีดำ / ขาว / เทาล้วนๆ แบบแผนที่วางสีต้นฉบับใน Scheme.primaryContainer รูปแบบที่คล้ายกับรูปแบบเนื้อหามาก ดูตัวอย่าง PDF อย่างง่าย แปลง PDF เป็นรูปภาพในรูปแบบเอาต์พุตที่กำหนด แพ็ครูปภาพที่กำหนดลงในไฟล์ PDF เอาท์พุต คุณกำลังจะลบมาสก์ตัวกรองที่เลือก การดำเนินการนี้ไม่สามารถยกเลิกได้ ความเป็นส่วนตัวเบลอ วาดเส้นทางปากกาเน้นข้อความที่คมชัดแบบกึ่งโปร่งใส เพิ่มเอฟเฟกต์เรืองแสงให้กับภาพวาดของคุณ ค่าเริ่มต้น ง่ายที่สุด - แค่สี เบลอภาพใต้เส้นทางที่วาดเพื่อรักษาความปลอดภัยสิ่งที่คุณต้องการซ่อน คล้ายกับการเบลอความเป็นส่วนตัว แต่เป็นพิกเซลแทนที่จะเบลอ เปิดใช้งานการวาดเงาด้านหลังแถบแอป อนุญาตให้ใช้กล่องจำกัดสำหรับการวางแนวรูปภาพ ลูกศร วาดวงรีที่มีโครงร่างจากจุดเริ่มต้นไปยังจุดสิ้นสุด วาดโครงร่างเป็นเส้นตรงจากจุดเริ่มต้นไปยังจุดสิ้นสุด การสั่นสะเทือน ความแรงของการสั่นสะเทือน ไฟล์ต้นฉบับจะถูกแทนที่ด้วยไฟล์ใหม่แทนที่จะบันทึกในโฟลเดอร์ที่เลือก ตัวเลือกนี้จะต้องแหล่งที่มาของรูปภาพเป็น \"Explorer\" หรือ GetContent เมื่อสลับสิ่งนี้ มันจะถูกตั้งค่าโดยอัตโนมัติ โดยทั่วไปการประมาณค่าเชิงเส้น (หรือแบบไบลิเนียร์ในสองมิติ) มักจะดีสำหรับการเปลี่ยนขนาดของรูปภาพ แต่จะทำให้รายละเอียดอ่อนลงอย่างไม่พึงประสงค์และยังสามารถเป็นรอยหยักได้บ้าง วิธีการปรับขนาดที่ดีกว่า ได้แก่ การสุ่มตัวอย่าง Lanczos และตัวกรอง Mitchell-Netravali หนึ่งในวิธีที่ง่ายกว่าในการเพิ่มขนาด โดยแทนที่ทุกพิกเซลด้วยจำนวนพิกเซลที่มีสีเดียวกัน โหมดปรับขนาด Android ที่ง่ายที่สุดที่ใช้ในแอพเกือบทั้งหมด ฟังก์ชัน Windowing มักใช้ในการประมวลผลสัญญาณเพื่อลดการรั่วไหลของสเปกตรัมและปรับปรุงความแม่นยำของการวิเคราะห์ความถี่โดยการลดขอบของสัญญาณ เทคนิคการประมาณค่าทางคณิตศาสตร์ที่ใช้ค่าและอนุพันธ์ที่จุดสิ้นสุดของส่วนของเส้นโค้งเพื่อสร้างเส้นโค้งที่ราบรื่นและต่อเนื่อง ไฟล์ที่ถูกเขียนทับด้วยชื่อ %1$s ที่ปลายทางเดิม แว่นขยาย เปิดใช้งานแว่นขยายที่ด้านบนของนิ้วในโหมดการวาดภาพเพื่อให้เข้าถึงได้ดีขึ้น บังคับค่าเริ่มต้น บังคับให้มีการตรวจสอบวิดเจ็ต exif ในขั้นต้น อนุญาตให้มีหลายภาษา ข้อความแนวตั้งบล็อกเดียว แถวเดียว วงกลมคำว่า ถ่านตัวเดียว ปัจจุบัน ทั้งหมด ครอบคลุมรูปภาพด้วยลายน้ำข้อความ/รูปภาพที่ปรับแต่งได้ ใส่ลายน้ำซ้ำ ทำซ้ำลายน้ำบนรูปภาพแทนที่จะเป็นลายน้ำเดียวในตำแหน่งที่กำหนด ออฟเซ็ต X ออฟเซ็ต Y ประเภทลายน้ำ รูปภาพนี้จะถูกใช้เป็นรูปแบบสำหรับลายน้ำ ความล่าช้าของเฟรม มิลลิวินาที เฟรมต่อวินาที ไบเออร์สองต่อสอง Dithering ไบเออร์สามต่อสาม Dithering ไบเออร์โฟร์บายโฟร์ไดเทอร์ริ่ง ไบเออร์แปดโดยแปด Dithering ฟลอยด์ สไตน์เบิร์ก ดิเธอริง จาร์วิส จูดิซ นินเก้ ดิเธอริง เซียร์รา ดิเธอริง Sierra Dithering สองแถว เซียร์รา ไลต์ ดิเธอริง แอตกินสัน ไดเทอร์ริ่ง เบิร์กส์ ดิเธอริง ค่ามัธยฐานเบลอ ใช้ฟังก์ชันพหุนาม bicubic ที่กำหนดเป็นชิ้นๆ เพื่อประมาณค่าเส้นโค้งหรือพื้นผิวได้อย่างราบรื่น การแสดงรูปร่างที่ยืดหยุ่นและต่อเนื่อง เมล็ดพันธุ์ ช่อง Shift X ขนาดคอร์รัปชั่น คอร์รัปชัน ชิฟท์ X การคอร์รัปชั่น Shift Y เต็นท์เบลอ ด้านข้าง สูงสุด ด้านล่าง ความแข็งแกร่ง การทำแผนที่ ACES Hill Tone การทำแผนที่โทนสีภาพยนตร์ Hable เมทริกซ์สี 4x4 เมทริกซ์สี 3x3 เอฟเฟกต์ง่าย ๆ โพลารอยด์ ไทรโทโนมาลี ดิวทาโรมาลี โปรโตโนมาลี วินเทจ บราวนี่ โคด้า โครม การมองเห็นตอนกลางคืน อบอุ่น เย็น ทริตาโนเปีย ดิวตาโรโนโทเปีย อาการอะโครมาโทเซีย โทนสีฤดูใบไม้ร่วง ไซเบอร์พังค์ น้ำมะนาวไลท์ สเปกตรัมไฟ ไนท์เมจิก ภูมิทัศน์แฟนตาซี การระเบิดของสี ไล่ระดับไฟฟ้า คาราเมลความมืด การไล่ระดับสีแห่งอนาคต เพิ่มคอนเทนเนอร์ที่มีรูปร่างที่เลือกไว้ใต้ไอคอนนำหน้าของการ์ด ดราโก้ อัลดริดจ์ ทางลัด อุจิมูระ การเปลี่ยนแปลง จุดสูงสุด ความผิดปกติของสี ไม่สามารถเปลี่ยนรูปแบบภาพในขณะที่เปิดใช้งานตัวเลือกการเขียนทับไฟล์ ใช้สีหลักของอีโมจิเป็นรูปแบบสีของแอป แทนที่จะกำหนดด้วยตนเอง แชทโทรเลข บันทึกลงในโฟลเดอร์ %1$s แล้ว หารือเกี่ยวกับแอปและรับคำติชมจากผู้ใช้รายอื่น คุณยังรับการอัปเดตและข้อมูลเชิงลึกรุ่นเบต้าได้ที่นี่ หน้ากากครอบตัด ใช้มาสก์ประเภทนี้เพื่อสร้างมาสก์จากรูปภาพที่กำหนด โปรดทราบว่าควรมีช่องอัลฟ่า สำรองและเรียกคืน คืนค่า นี่จะย้อนกลับการตั้งค่าของคุณกลับเป็นค่าเริ่มต้น โปรดสังเกตว่าการดำเนินการนี้ไม่สามารถยกเลิกได้หากไม่มีไฟล์สำรองที่กล่าวถึงข้างต้น ลบแบบแผน อารมณ์ อาหารและเครื่องดื่ม ธรรมชาติและสัตว์ สัญลักษณ์ ข้อมูลเมตาของรูปภาพต้นฉบับจะถูกเก็บไว้ พื้นที่โปร่งใสรอบๆ รูปภาพจะถูกตัดออก คืนค่ารูปภาพ ปิเปต ปรับขนาดและแปลง ลำดับภาพ พิกเซลเพชรที่ได้รับการปรับปรุง พิกเซลเพชร ความอดทน สีที่จะแทนที่ กัดเซาะ การแพร่กระจายแบบแอนไอโซทรอปิก การแพร่กระจาย การนำ ลมโซเซแนวนอน เบลอทวิภาคีอย่างรวดเร็ว ปัวซองเบลอ การทำแผนที่โทนลอการิทึม ตกผลึก สีเส้นโครงร่าง แก้วแฟร็กทัล แอมพลิจูด หินอ่อน ความปั่นป่วน น้ำมัน เอฟเฟกต์น้ำ ขนาด ความถี่ X ความถี่ Y แอมพลิจูด X แอมพลิจูด Y การบิดเบือนของเพอร์ลิน การทำแผนที่โทนของ Hejl Burgess การทำแผนที่โทนสีภาพยนตร์ ACES คุณต้องการลบข้อมูลการฝึกอบรม OCR ภาษา \"%1$s\" สำหรับการจดจำทุกประเภท หรือเฉพาะประเภทที่เลือก (%2$s) หรือไม่? ตัวกรองแบบเต็ม เริ่ม ใช้กลุ่มตัวกรองกับรูปภาพที่กำหนดหรือรูปภาพเดี่ยว PDF เป็นรูปภาพ รูปภาพเป็น PDF เครื่องไล่ระดับสี สร้างการไล่ระดับสีของขนาดเอาต์พุตที่กำหนดด้วยสีและประเภทลักษณะที่กำหนดเอง ความเร็ว ลดหมอกควัน โอเมก้า เครื่องมือ PDF ให้คะแนนแอพ ประเมิน แอพนี้ไม่มีค่าใช้จ่ายใดๆ ทั้งสิ้น หากคุณต้องการให้มันใหญ่ขึ้น โปรดติดดาวโปรเจ็กต์บน Github 😄 โพรโทเปีย ความผิดปกติ เชิงเส้น เรเดียล กวาด ประเภทการไล่ระดับสี เซ็นเตอร์เอ็กซ์ เซ็นเตอร์ วาย โหมดไทล์ ซ้ำแล้วซ้ำเล่า กระจกเงา ที่หนีบ รูปลอก หยุดสี เพิ่มสี คุณสมบัติ อีเมล ลาสโซ ดึงเส้นทางที่ปิดสนิทตามเส้นทางที่กำหนด วาดโหมดเส้นทาง ลูกศรเส้นคู่ การวาดภาพฟรี ลูกศรคู่ ลูกศรเส้น เส้น วาดเส้นทางเป็นค่าอินพุต วาดเส้นทางจากจุดเริ่มต้นไปยังจุดสิ้นสุดเป็นเส้น วาดลูกศรชี้จากจุดเริ่มต้นไปยังจุดสิ้นสุดเป็นเส้น ดึงลูกศรชี้จากเส้นทางที่กำหนด วาดลูกศรชี้คู่จากจุดเริ่มต้นไปยังจุดสิ้นสุดเป็นเส้น ดึงลูกศรชี้คู่จากเส้นทางที่กำหนด วงรีที่ระบุไว้ ร่างสี่เหลี่ยมผืนผ้า วงรี สี่เหลี่ยมผืนผ้า ลากเส้นตรงจากจุดเริ่มต้นไปยังจุดสิ้นสุด วาดวงรีจากจุดเริ่มต้นไปยังจุดสิ้นสุด การทำสี ควอนติซิเออร์ ระดับสีเทา Stucki Dithering False Floyd Steinberg Dithering จากซ้ายไปขวา การสุ่มตัวอย่าง Dithering เกณฑ์ง่าย ๆ ดูตัวอย่างหน้ากาก หน้ากากตัวกรองที่วาดไว้จะแสดงผลเพื่อแสดงผลลัพธ์โดยประมาณ ลบ คุณกำลังจะลบชุดสีที่เลือก การดำเนินการนี้ไม่สามารถยกเลิกได้ โหมดสเกล ไบลิเนียร์ ฮัน ฤาษี แลนโซส มิทเชล ใกล้ที่สุด เส้นโค้ง ขั้นพื้นฐาน ค่าเริ่มต้น ค่าในช่วง %1$s - %2$s ซิกมา ซิกม่าเชิงพื้นที่ แคทมัล ไบคิวบิก วิธีการแก้ไขและสุ่มชุดจุดควบคุมใหม่อย่างราบรื่น ซึ่งใช้กันทั่วไปในคอมพิวเตอร์กราฟิกส์เพื่อสร้างเส้นโค้งที่ราบรื่น วิธีการสุ่มตัวอย่างที่รักษาการประมาณค่าคุณภาพสูงโดยการใช้ฟังก์ชัน Weighted Sinc กับค่าพิกเซล วิธีการสุ่มตัวอย่างที่ใช้ตัวกรองแบบบิดพร้อมพารามิเตอร์ที่ปรับได้เพื่อให้เกิดความสมดุลระหว่างความคมชัดและการลดรอยหยักในภาพที่ปรับขนาด ใช้ฟังก์ชันพหุนามที่กำหนดเป็นชิ้นๆ เพื่อประมาณค่าเส้นโค้งหรือพื้นผิวได้อย่างราบรื่น การแสดงรูปร่างที่ยืดหยุ่นและต่อเนื่อง คลิปเท่านั้น การบันทึกลงพื้นที่เก็บข้อมูลจะไม่ดำเนินการ และจะพยายามใส่รูปภาพลงในคลิปบอร์ดเท่านั้น รูปร่างไอคอน การต่อภาพ รวมภาพที่กำหนดเพื่อให้ได้ภาพใหญ่หนึ่งภาพ การบังคับใช้ความสว่าง หน้าจอ การซ้อนทับแบบไล่ระดับสี เขียนการไล่ระดับสีที่ด้านบนของรูปภาพที่กำหนด การเปลี่ยนแปลง กล้อง ใช้กล้องในการถ่ายภาพ โปรดทราบว่าสามารถรับได้เพียงภาพเดียวจากแหล่งภาพนี้ เลือกอย่างน้อย 2 ภาพ ขนาดภาพที่ส่งออก การวางแนวรูปภาพ แนวนอน แนวตั้ง ธัญพืช ไม่คมชัด สีพาสเทล หมอกสีส้ม ความฝันสีชมพู ชั่วโมงทอง ฤดูร้อน หมอกสีม่วง พระอาทิตย์ขึ้น หมุนวนที่มีสีสัน แสงสปริงอันนุ่มนวล ลาเวนเดอร์ดรีม กรีนซัน โลกสายรุ้ง สีม่วงเข้ม พอร์ทัลอวกาศ วงเวียนแดง รหัสดิจิทัล ลายน้ำ สีข้อความ โหมดโอเวอร์เลย์ ขนาดพิกเซล ล็อคการวางแนวการวาด หากเปิดใช้งานในโหมดการวาดภาพ หน้าจอจะไม่หมุน โบเก้ เครื่องมือ GIF แปลงรูปภาพเป็นรูปภาพ GIF หรือแยกเฟรมจากรูปภาพ GIF ที่กำหนด GIF เป็นรูปภาพ แปลงไฟล์ GIF เป็นชุดรูปภาพ แปลงชุดรูปภาพเป็นไฟล์ GIF รูปภาพเป็น GIF เลือกภาพ GIF เพื่อเริ่มต้น ใช้ขนาดของเฟรมแรก แทนที่ขนาดที่ระบุด้วยขนาดเฟรมแรก นับซ้ำ ใช้ Lasso ใช้ Lasso เช่นเดียวกับในโหมดการวาดภาพเพื่อทำการลบ ภาพตัวอย่างต้นฉบับอัลฟ่า แผ่นกรองหน้ากาก ใช้ห่วงโซ่ตัวกรองกับพื้นที่ที่มาสก์ที่กำหนด แต่ละพื้นที่ของมาส์กสามารถกำหนดชุดตัวกรองของตัวเองได้ มาสก์ เพิ่มมาส์ก หน้ากาก %d อิโมจิของแถบแอปจะถูกเปลี่ยนอย่างต่อเนื่องโดยการสุ่ม แทนที่จะใช้อันที่เลือก อิโมจิแบบสุ่ม ไม่สามารถใช้การเลือกอีโมจิแบบสุ่มในขณะที่ปิดใช้งานอีโมจิได้ ไม่สามารถเลือกอิโมจิในขณะที่เปิดใช้งานการสุ่มเลือกได้ ตรวจสอบสำหรับการอัพเดต อัตราส่วนภาพ เปิดใช้งานอีโมจิ ทีวีเก่า สุ่มเบลอ OCR (จดจำข้อความ) จดจำข้อความจากรูปภาพที่กำหนด รองรับมากกว่า 120 ภาษา รูปภาพไม่มีข้อความ หรือแอปไม่พบ Accuracy: %1$s ประเภทการรับรู้ เร็ว มาตรฐาน ดีที่สุด ไม่มีข้อมูล เพื่อให้การทำงานที่เหมาะสมของ Tesseract OCR ต้องดาวน์โหลดข้อมูลการฝึกอบรมเพิ่มเติม (%1$s) ลงในอุปกรณ์ของคุณ \nคุณต้องการดาวน์โหลดข้อมูล %2$s หรือไม่? ดาวน์โหลด ไม่มีการเชื่อมต่อ โปรดตรวจสอบแล้วลองอีกครั้งเพื่อดาวน์โหลดโมเดลรถไฟ ภาษาที่ดาวน์โหลด ภาษาที่ใช้ได้ โหมดการแบ่งส่วน แปรงจะคืนค่าพื้นหลังแทนการลบ ตารางแนวนอน ตารางแนวตั้ง โหมดการเย็บร้อย จำนวนแถว จำนวนคอลัมน์ ใช้สวิตช์พิกเซล สวิตช์แบบพิกเซลจะถูกใช้แทนเนื้อหาของ Google ที่คุณใช้ สไลด์ เคียงบ่าเคียงไหล่ สลับการแตะ ความโปร่งใส ที่ชื่นชอบ ยังไม่มีการเพิ่มตัวกรองที่ชื่นชอบ บี สไปลน์ Native Stack Blur เอียงกะ ปกติ ขอบเบลอ ประเภทการเติมผกผัน หากเปิดใช้งาน พื้นที่ที่ไม่ปกปิดทั้งหมดจะถูกกรองแทนการทำงานเริ่มต้น ลูกปา ลูกปาจะแสดงในการบันทึก การแชร์ และการดำเนินการหลักอื่นๆ โหมดปลอดภัย ซ่อนเนื้อหาเมื่อออก และไม่สามารถจับภาพหรือบันทึกหน้าจอได้ วาดขอบเบลอใต้รูปภาพต้นฉบับเพื่อเติมช่องว่างรอบๆ แทนที่จะเป็นสีเดียวหากเปิดใช้งาน พิกเซล วงกลมพิกเซล พิกเซลวงกลมที่ได้รับการปรับปรุง เปลี่ยนสี พิการ ทั้งคู่ ดำเนินการกับไฟล์ PDF: ดูตัวอย่าง แปลงเป็นชุดรูปภาพ หรือสร้างจากรูปภาพที่กำหนด ดูตัวอย่าง PDF สีหน้ากาก ลบมาสก์ ศูนย์ จบ ตัวแปรที่เรียบง่าย ปากกาเน้นข้อความ นีออน ปากกา หมุนอัตโนมัติ ตู้คอนเทนเนอร์ เปิดใช้งานการวาดเงาด้านหลังคอนเทนเนอร์ สไลเดอร์ สวิตช์ FAB ปุ่ม เปิดใช้งานการวาดเงาด้านหลังแถบเลื่อน เปิดใช้งานการวาดเงาด้านหลังสวิตช์ เปิดใช้งานการวาดเงาด้านหลังปุ่มการทำงานแบบลอย เปิดใช้งานการวาดเงาด้านหลังปุ่มเริ่มต้น แถบแอพ ความสนใจ ขอบซีดจาง ตัวตรวจสอบการอัปเดตนี้จะเชื่อมต่อกับ GitHub เพื่อตรวจสอบว่ามีการอัปเดตใหม่หรือไม่ สลับสี เปลี่ยนสีของธีมเป็นสีเนกาทีฟหากเปิดใช้งาน ออก หากคุณออกจากการแสดงตัวอย่างตอนนี้ คุณจะต้องเพิ่มรูปภาพอีกครั้ง รูปแบบภาพ สร้าง\"Material You\"จานสีจากภาพ สีเข้ม ใช้โทนสีโหมดกลางคืนแทนตัวแปรแสง คัดลอกเป็น\"Jetpack Compose\"รหัส แหวนเบลอ ดาวเบลอ กะเอียงเชิงเส้น แท็กที่จะลบ ข้ามเบลอ วงกลมเบลอ เครื่องมือ APNG แปลงรูปภาพเป็นรูปภาพ APNG หรือแยกเฟรมจากรูปภาพ APNG ที่กำหนด APNG เป็นรูปภาพ แปลงไฟล์ APNG เป็นชุดรูปภาพ แปลงชุดรูปภาพเป็นไฟล์ APNG รูปภาพไปยัง APNG โมชั่นเบลอ ซิป สร้างไฟล์ Zip จากไฟล์หรือรูปภาพที่กำหนด เลือกรูปภาพ APNG เพื่อเริ่มต้น ลากความกว้างของที่จับ ประเภทกระดาษโปรย งานรื่นเริง ระเบิด ฝน มุม เครื่องมือ JXL ดำเนินการแปลงรหัส JXL ~ JPEG โดยไม่สูญเสียคุณภาพ หรือแปลงภาพเคลื่อนไหว GIF/APNG เป็น JXL JXL เป็น JPEG ทำการแปลงรหัสแบบไม่สูญเสียจาก JXL เป็น JPEG ทำการแปลงรหัสแบบไม่สูญเสียจาก JPEG เป็น JXL JPEG เป็น JXL เลือกภาพ JXL เพื่อเริ่มต้น เกาส์เซียนเบลอ 2D อย่างรวดเร็ว เกาส์เซียนเบลอ 3 มิติอย่างรวดเร็ว เกาส์เซียนเบลอ 4D อย่างรวดเร็ว รถอีสเตอร์ อนุญาตให้แอปวางข้อมูลคลิปบอร์ดโดยอัตโนมัติ ดังนั้นข้อมูลจะปรากฏบนหน้าจอหลักและคุณจะสามารถดำเนินการได้ สีที่ประสานกัน ระดับการประสานกัน แลนซอส เบสเซล วิธีการสุ่มตัวอย่างที่รักษาการประมาณค่าคุณภาพสูงโดยใช้ฟังก์ชัน Bessel (jinc) กับค่าพิกเซล GIF เป็น JXL แปลงภาพ GIF เป็นภาพเคลื่อนไหว JXL APNG เป็น JXL แปลงภาพ APNG เป็นภาพเคลื่อนไหว JXL JXL เป็นรูปภาพ แปลงภาพเคลื่อนไหว JXL เป็นชุดรูปภาพ รูปภาพเป็น JXL แปลงชุดรูปภาพเป็นแอนิเมชั่น JXL พฤติกรรม ข้ามการเลือกไฟล์ ตัวเลือกไฟล์จะแสดงทันทีหากเป็นไปได้บนหน้าจอที่เลือก สร้างตัวอย่าง เปิดใช้งานการสร้างหน้าตัวอย่าง ซึ่งอาจช่วยหลีกเลี่ยงข้อขัดข้องในอุปกรณ์บางชนิด และยังปิดใช้ฟังก์ชันการแก้ไขบางอย่างภายในตัวเลือกการแก้ไขเดียวอีกด้วย การบีบอัดแบบสูญเสีย ใช้การบีบอัดแบบ lossy เพื่อลดขนาดไฟล์แทนที่จะเป็นแบบ lossless ประเภทการบีบอัด ควบคุมความเร็วในการถอดรหัสรูปภาพผลลัพธ์ ซึ่งจะช่วยให้เปิดรูปภาพผลลัพธ์ได้เร็วขึ้น ค่า %1$s หมายถึงการถอดรหัสช้าที่สุด ในขณะที่ %2$s - เร็วที่สุด การตั้งค่านี้อาจเพิ่มขนาดรูปภาพเอาต์พุต การเรียงลำดับ วันที่ วันที่ (กลับด้าน) ชื่อ ชื่อ (กลับรายการ) การกำหนดค่าช่องสัญญาณ วันนี้ เมื่อวาน เครื่องมือเลือกแบบฝัง เครื่องมือเลือกรูปภาพของ Image Toolbox ไม่มีสิทธิ์ ขอ เลือกสื่อหลายรายการ เลือกสื่อเดี่ยว เลือก ลองอีกครั้ง แสดงการตั้งค่าในแนวนอน หากปิดใช้งานการตั้งค่าโหมดแนวนอนจะเปิดขึ้นที่ปุ่มในแถบแอปด้านบนเช่นเคย แทนที่จะเป็นตัวเลือกที่มองเห็นได้อย่างถาวร การตั้งค่าเต็มหน้าจอ เปิดใช้งานและหน้าการตั้งค่าจะเปิดเป็นแบบเต็มหน้าจอเสมอ แทนที่จะเป็นแผ่นลิ้นชักแบบเลื่อนได้ ประเภทสวิตช์ เขียน วัสดุการเขียน Jetpack ที่คุณเปลี่ยน วัสดุที่คุณเปลี่ยน สูงสุด ปรับขนาดจุดยึด พิกเซล คล่องแคล่ว สวิตช์ที่ใช้ระบบการออกแบบ \"Fluent\" คูเปอร์ติโน สวิตช์ที่ใช้ระบบการออกแบบ \"คูเปอร์ติโน\" รูปภาพเป็น SVG ติดตามภาพที่กำหนดไปยังภาพ SVG ใช้จานสีตัวอย่าง ระบบจะสุ่มตัวอย่างจานสีปริมาณหากเปิดใช้งานตัวเลือกนี้ ละเว้นเส้นทาง ไม่แนะนำให้ใช้เครื่องมือนี้ในการติดตามภาพขนาดใหญ่โดยไม่ต้องลดขนาด เนื่องจากอาจทำให้เกิดความผิดพลาดและเพิ่มเวลาในการประมวลผลได้ ลดขนาดรูปภาพ รูปภาพจะถูกลดขนาดลงเป็นขนาดที่ต่ำกว่าก่อนการประมวลผล ซึ่งจะช่วยให้เครื่องมือทำงานเร็วและปลอดภัยยิ่งขึ้น อัตราส่วนสีขั้นต่ำ เกณฑ์บรรทัด เกณฑ์กำลังสอง พิกัดความเผื่อการปัดเศษ ขนาดเส้นทาง รีเซ็ตคุณสมบัติ คุณสมบัติทั้งหมดจะถูกตั้งค่าเป็นค่าเริ่มต้น โปรดทราบว่าการดำเนินการนี้ไม่สามารถยกเลิกได้ รายละเอียด ความกว้างของเส้นเริ่มต้น โหมดเครื่องยนต์ มรดก เครือข่ายแอลเอสทีเอ็ม มรดกและ LSTM แปลง แปลงชุดรูปภาพเป็นรูปแบบที่กำหนด เพิ่มโฟลเดอร์ใหม่ บิตต่อตัวอย่าง การบีบอัด การตีความโฟโตเมตริก ตัวอย่างต่อพิกเซล การกำหนดค่าระนาบ การสุ่มตัวอย่างย่อย Y Cb Cr การวางตำแหน่ง Y Cb Cr ความละเอียด X Y ความละเอียด หน่วยความละเอียด สตริปออฟเซ็ต แถวต่อแถบ สตริปไบต์นับ รูปแบบการแลกเปลี่ยน JPEG ความยาวรูปแบบการแลกเปลี่ยน JPEG ฟังก์ชั่นการถ่ายโอน จุดขาว รงค์หลัก Y Cb Cr สัมประสิทธิ์ อ้างอิงสีดำสีขาว วันที่ เวลา คำอธิบายรูปภาพ ทำ แบบอย่าง ซอฟต์แวร์ ศิลปิน ลิขสิทธิ์ เวอร์ชั่น เอ็กซิฟ เวอร์ชั่นแฟลชพิกซ์ พื้นที่สี แกมมา มิติพิกเซล X มิติพิกเซล Y บิตที่ถูกบีบอัดต่อพิกเซล หมายเหตุผู้สร้าง ความคิดเห็นของผู้ใช้ ไฟล์เสียงที่เกี่ยวข้อง วันที่ เวลา ต้นฉบับ วันที่ เวลา แปลงเป็นดิจิทัล เวลาออฟเซ็ต เวลาออฟเซ็ต ต้นฉบับ เวลาออฟเซ็ตแปลงเป็นดิจิทัล เวลาย่อยวินาที เวลาย่อยเป็นต้นฉบับ เวลาย่อยเป็นดิจิทัล เวลารับสัมผัสเชื้อ เอฟ นัมเบอร์ โปรแกรมการสัมผัส ความไวแสงสเปกตรัม ความไวแสงของการถ่ายภาพ อฟ ประเภทความไว ความไวเอาต์พุตมาตรฐาน ดัชนีการสัมผัสที่แนะนำ ความไวแสง ISO ละติจูดความเร็ว ISO yyy ละติจูดความเร็ว ISO zzz ค่าความเร็วชัตเตอร์ ค่ารูรับแสง ค่าความสว่าง ค่าอคติของการสัมผัส ค่ารูรับแสงสูงสุด ระยะห่างของเรื่อง โหมดการวัดแสง แฟลช สาขาวิชา ทางยาวโฟกัส พลังงานแฟลช การตอบสนองความถี่เชิงพื้นที่ ความละเอียดระนาบโฟกัส X ความละเอียดระนาบโฟกัส Y หน่วยความละเอียดระนาบโฟกัส สถานที่ตั้งของเรื่อง ดัชนีการสัมผัส วิธีการตรวจจับ แหล่งที่มาของไฟล์ รูปแบบ CFA แสดงผลแบบกำหนดเอง โหมดการรับแสง สมดุลสีขาว อัตราส่วนการซูมแบบดิจิตอล ทางยาวโฟกัสในฟิล์ม 35 มม ประเภทการถ่ายภาพฉาก ได้รับการควบคุม ตัดกัน ความอิ่มตัว ความคม คำอธิบายการตั้งค่าอุปกรณ์ ช่วงระยะทางของวัตถุ รหัสเฉพาะของรูปภาพ ชื่อเจ้าของกล้อง หมายเลขซีเรียลของร่างกาย ข้อมูลจำเพาะของเลนส์ ยี่ห้อเลนส์ รุ่นเลนส์ หมายเลขซีเรียลเลนส์ รหัสเวอร์ชัน GPS การอ้างอิงละติจูด GPS ละติจูด GPS การอ้างอิงลองจิจูด GPS ลองจิจูด GPS การอ้างอิงระดับความสูง GPS ระดับความสูงของจีพีเอส ประทับเวลา GPS ดาวเทียม GPS สถานะ GPS โหมดการวัด GPS จีพีเอส สปส อ้างอิงความเร็ว GPS ความเร็วจีพีเอส อ้างอิง GPS Track จีพีเอสติดตาม อ้างอิงทิศทางภาพ GPS ทิศทางภาพ GPS ข้อมูลแผนที่ GPS การอ้างอิงละติจูดปลายทาง GPS ละติจูดปลายทาง GPS การอ้างอิงลองจิจูดปลายทาง GPS ลองจิจูดปลายทาง GPS อ้างอิงแบริ่งปลายทาง GPS แบริ่งปลายทาง GPS การอ้างอิงระยะทางปลายทาง GPS ระยะทางปลายทาง GPS วิธีการประมวลผล GPS ข้อมูลพื้นที่ GPS ประทับวันที่ GPS จีพีเอสดิฟเฟอเรนเชียล ข้อผิดพลาดการวางตำแหน่ง GPS H ดัชนีการทำงานร่วมกัน เวอร์ชัน DNG ขนาดครอบตัดเริ่มต้น ดูตัวอย่างภาพเริ่มต้น ดูตัวอย่างความยาวของภาพ กรอบมุมมอง ขอบด้านล่างของเซนเซอร์ เซ็นเซอร์ขอบด้านซ้าย ขอบขวาของเซนเซอร์ ขอบด้านบนของเซ็นเซอร์ ไอเอสโอ วาดข้อความบนเส้นทางด้วยแบบอักษรและสีที่กำหนด ขนาดตัวอักษร ขนาดลายน้ำ ทำซ้ำข้อความ ข้อความปัจจุบันจะถูกทำซ้ำจนกว่าเส้นทางจะสิ้นสุดแทนที่จะวาดเพียงครั้งเดียว ขนาดเส้นประ ใช้รูปภาพที่เลือกเพื่อวาดตามเส้นทางที่กำหนด รูปภาพนี้จะถูกใช้เป็นการป้อนข้อมูลซ้ำของเส้นทางที่วาด วาดรูปสามเหลี่ยมที่มีโครงร่างจากจุดเริ่มต้นไปยังจุดสิ้นสุด วาดรูปสามเหลี่ยมที่มีโครงร่างจากจุดเริ่มต้นไปยังจุดสิ้นสุด สามเหลี่ยมที่มีโครงร่าง สามเหลี่ยม วาดรูปหลายเหลี่ยมจากจุดเริ่มต้นไปยังจุดสิ้นสุด รูปหลายเหลี่ยม รูปหลายเหลี่ยมที่สรุปไว้ วาดรูปหลายเหลี่ยมที่มีโครงร่างจากจุดเริ่มต้นไปยังจุดสิ้นสุด จุดยอด วาดรูปหลายเหลี่ยมปกติ วาดรูปหลายเหลี่ยมซึ่งจะเป็นรูปหลายเหลี่ยมปกติแทนที่จะเป็นรูปแบบอิสระ ดึงดาวจากจุดเริ่มต้นไปยังจุดสิ้นสุด ดาว ดาวเด่น วาดดาวที่มีโครงร่างจากจุดเริ่มต้นไปยังจุดสิ้นสุด อัตราส่วนรัศมีภายใน วาดดาวปกติ วาดดาวซึ่งจะสม่ำเสมอแทนที่จะเป็นรูปแบบอิสระ แอนติเลียส เปิดใช้งานการลดรอยหยักเพื่อป้องกันขอบคม เปิดแก้ไขแทนการแสดงตัวอย่าง เมื่อคุณเลือกภาพที่จะเปิด (ดูตัวอย่าง) ใน ImageToolbox แผ่นแก้ไขการเลือกจะถูกเปิดแทนการดูตัวอย่าง เครื่องสแกนเอกสาร สแกนเอกสารและสร้าง PDF หรือแยกรูปภาพจากเอกสารเหล่านั้น คลิกเพื่อเริ่มการสแกน เริ่มการสแกน บันทึกเป็น PDF แบ่งปันเป็น PDF ตัวเลือกด้านล่างมีไว้สำหรับบันทึกรูปภาพ ไม่ใช่ PDF ปรับฮิสโตแกรม HSV ให้เท่ากัน ปรับฮิสโตแกรมให้เท่ากัน ป้อนเปอร์เซ็นต์ อนุญาตให้ป้อนโดยช่องข้อความ เปิดใช้งานช่องข้อความด้านหลังการเลือกค่าที่ตั้งล่วงหน้า เพื่อป้อนได้ทันที สเกลปริภูมิสี เชิงเส้น ปรับพิกเซลฮิสโตแกรมให้เท่ากัน ตารางขนาด X ตารางขนาด Y ปรับฮิสโตแกรมให้เท่ากัน ปรับฮิสโตแกรม Adaptive LUV ให้เท่ากัน ปรับฮิสโตแกรม Adaptive LAB ให้เท่ากัน แคลเฮ แคลเฮ่ แล็บ คลาเฮ่ ลูฟ ครอบตัดเป็นเนื้อหา สีกรอบ สีที่ควรละเว้น แม่แบบ ไม่มีการเพิ่มตัวกรองเทมเพลต สร้างใหม่ รหัส QR ที่สแกนไม่ใช่เทมเพลตตัวกรองที่ถูกต้อง สแกนรหัส QR ไฟล์ที่เลือกไม่มีข้อมูลเทมเพลตตัวกรอง สร้างเทมเพลต ชื่อเทมเพลต รูปภาพนี้จะถูกใช้เพื่อดูตัวอย่างเทมเพลตตัวกรองนี้ ตัวกรองเทมเพลต เป็นภาพรหัส QR เป็นไฟล์ บันทึกเป็นไฟล์ บันทึกเป็นภาพรหัส QR ลบเทมเพลต คุณกำลังจะลบตัวกรองเทมเพลตที่เลือก การดำเนินการนี้ไม่สามารถยกเลิกได้ เพิ่มเทมเพลตตัวกรองชื่อ \"%1$s\" (%2$s) ดูตัวอย่างตัวกรอง คิวอาร์และบาร์โค้ด สแกนโค้ด QR และรับเนื้อหาหรือวางสตริงของคุณเพื่อสร้างโค้ดใหม่ เนื้อหาโค้ด สแกนบาร์โค้ดเพื่อแทนที่เนื้อหาในช่อง หรือพิมพ์บางอย่างเพื่อสร้างบาร์โค้ดใหม่ตามประเภทที่เลือก คำอธิบาย QR นาที ให้สิทธิ์กล้องในการตั้งค่าเพื่อสแกนโค้ด QR ให้สิทธิ์กล้องในการตั้งค่าเพื่อสแกนเครื่องสแกนเอกสาร คิวบิก B-Spline แฮมมิง ฮันนิ่ง แบล็คแมน เวลช์ สี่เหลี่ยม เกาส์เซียน สฟิงซ์ บาร์ตเลตต์ โรบิดูซ์ โรบิดูซ์ ชาร์ป เส้นโค้ง 16 สไปลน์ 36 สไปลน์ 64 ไกเซอร์ บาร์ตเลตต์-เขา กล่อง โบห์แมน ลันโซส 2 ลันโซส 3 ลันโซส 4 แลนซอส 2 จินซี แลนซอส 3 จินซี แลนซอส 4 จินซี การแก้ไขแบบลูกบาศก์ช่วยให้ปรับขนาดได้ราบรื่นขึ้นโดยพิจารณาจาก 16 พิกเซลที่ใกล้เคียงที่สุด ซึ่งให้ผลลัพธ์ที่ดีกว่าแบบไบลิเนียร์ ใช้ฟังก์ชันพหุนามที่กำหนดเป็นชิ้นๆ เพื่อประมาณค่าเส้นโค้งหรือพื้นผิวได้อย่างราบรื่น การแสดงรูปร่างที่ยืดหยุ่นและต่อเนื่อง ฟังก์ชันหน้าต่างที่ใช้เพื่อลดการรั่วไหลของสเปกตรัมโดยการลดขอบของสัญญาณ ซึ่งมีประโยชน์ในการประมวลผลสัญญาณ รูปแบบหนึ่งของหน้าต่าง Hann ซึ่งใช้กันทั่วไปเพื่อลดการรั่วไหลของสเปกตรัมในแอปพลิเคชันการประมวลผลสัญญาณ ฟังก์ชั่นหน้าต่างที่ให้ความละเอียดความถี่ที่ดีโดยลดการรั่วไหลของสเปกตรัม ซึ่งมักใช้ในการประมวลผลสัญญาณ ฟังก์ชันหน้าต่างที่ออกแบบมาเพื่อให้ความละเอียดความถี่ที่ดีพร้อมการลดการรั่วไหลของสเปกตรัม ซึ่งมักใช้ในการประมวลผลสัญญาณ วิธีการที่ใช้ฟังก์ชันสมการกำลังสองในการประมาณค่า เพื่อให้ได้ผลลัพธ์ที่ราบรื่นและต่อเนื่อง วิธีการประมาณค่าที่ใช้ฟังก์ชัน Gaussian ซึ่งมีประโยชน์ในการปรับให้เรียบและลดสัญญาณรบกวนในภาพ วิธีการสุ่มตัวอย่างขั้นสูงที่ให้การแก้ไขคุณภาพสูงโดยมีข้อผิดพลาดน้อยที่สุด ฟังก์ชันหน้าต่างสามเหลี่ยมที่ใช้ในการประมวลผลสัญญาณเพื่อลดการรั่วไหลของสเปกตรัม วิธีการแก้ไขคุณภาพสูงที่ปรับให้เหมาะสมสำหรับการปรับขนาดภาพที่เป็นธรรมชาติ ปรับสมดุลความคมชัดและความเรียบเนียน รูปแบบที่คมชัดยิ่งขึ้นของวิธี Robidoux ซึ่งได้รับการปรับให้เหมาะสมเพื่อการปรับขนาดภาพที่คมชัด วิธีการประมาณค่าแบบ spline ที่ให้ผลลัพธ์ที่ราบรื่นโดยใช้ตัวกรอง 16-tap วิธีการประมาณค่าแบบ spline ที่ให้ผลลัพธ์ที่ราบรื่นโดยใช้ตัวกรอง 36-tap วิธีการประมาณค่าแบบ spline ที่ให้ผลลัพธ์ที่ราบรื่นโดยใช้ตัวกรอง 64-tap วิธีการประมาณค่าที่ใช้หน้าต่าง Kaiser ช่วยให้สามารถควบคุมการแลกเปลี่ยนระหว่างความกว้างของกลีบหลักและระดับของกลีบด้านข้างได้ดี ฟังก์ชันหน้าต่างไฮบริดที่รวมหน้าต่าง Bartlett และ Hann ใช้เพื่อลดการรั่วไหลของสเปกตรัมในการประมวลผลสัญญาณ วิธีการสุ่มตัวอย่างแบบง่ายๆ ที่ใช้ค่าเฉลี่ยของค่าพิกเซลที่ใกล้ที่สุด ซึ่งมักส่งผลให้มีลักษณะเป็นบล็อก ฟังก์ชันหน้าต่างที่ใช้เพื่อลดการรั่วไหลของสเปกตรัม ให้ความละเอียดความถี่ที่ดีในการใช้งานการประมวลผลสัญญาณ วิธีการสุ่มตัวอย่างใหม่ที่ใช้ตัวกรอง Lanczos แบบ 2 กลีบเพื่อการประมาณค่าคุณภาพสูงโดยมีสิ่งแปลกปลอมน้อยที่สุด วิธีการสุ่มตัวอย่างใหม่ที่ใช้ตัวกรอง Lanczos แบบ 3 กลีบเพื่อการประมาณค่าคุณภาพสูงโดยมีสิ่งแปลกปลอมน้อยที่สุด วิธีการสุ่มตัวอย่างใหม่ที่ใช้ตัวกรอง Lanczos แบบ 4 กลีบเพื่อการประมาณค่าคุณภาพสูงโดยมีสิ่งแปลกปลอมน้อยที่สุด ตัวกรอง Lanczos 2 รุ่นหนึ่งที่ใช้ฟังก์ชัน Jinc ให้การแก้ไขคุณภาพสูงโดยมีสิ่งแปลกปลอมน้อยที่สุด ตัวกรอง Lanczos 3 รุ่นต่างๆ ที่ใช้ฟังก์ชัน Jinc ให้การแก้ไขคุณภาพสูงโดยมีสิ่งแปลกปลอมน้อยที่สุด ตัวกรอง Lanczos 4 รุ่นต่างๆ ที่ใช้ฟังก์ชัน Jinc ให้การแก้ไขคุณภาพสูงโดยมีสิ่งแปลกปลอมน้อยที่สุด ฮานนิ่ง อีดับบลิวเอ รูปแบบ Elliptical Weighted Average (EWA) ของตัวกรอง Hanning เพื่อการประมาณค่าและการสุ่มตัวอย่างใหม่อย่างราบรื่น โรบิดูซ์ อีดับเบิลยูเอ รูปแบบ Elliptical Weighted Average (EWA) ของตัวกรอง Robidoux เพื่อการสุ่มตัวอย่างคุณภาพสูง แบล็คแมน อีฟ ตัวแปร Elliptical Weighted Average (EWA) ของตัวกรอง Blackman เพื่อลดปัญหาเสียงเรียกเข้า EWA สี่เหลี่ยม รูปแบบ Elliptical Weighted Average (EWA) ของตัวกรอง Quadric เพื่อการประมาณค่าที่ราบรื่น โรบิดูซ์ ชาร์ป EWA รูปแบบ Elliptical Weighted Average (EWA) ของตัวกรอง Robidoux Sharp เพื่อผลลัพธ์ที่คมชัดยิ่งขึ้น แลนซอส 3 จินค์ อีวา รูปแบบ Elliptical Weighted Average (EWA) ของตัวกรอง Lanczos 3 Jinc สำหรับการสุ่มตัวอย่างใหม่คุณภาพสูงด้วยนามแฝงที่ลดลง โสม ฟิลเตอร์รีแซมปลิงที่ออกแบบมาเพื่อการประมวลผลภาพคุณภาพสูงโดยมีความสมดุลระหว่างความคมชัดและความนุ่มนวล โสม EWA ตัวแปร Elliptical Weighted Average (EWA) ของฟิลเตอร์โสมเพื่อเพิ่มคุณภาพของภาพ แลนซอส ชาร์ป อีดับเบิลยูเอ รูปแบบ Elliptical Weighted Average (EWA) ของฟิลเตอร์ Lanczos Sharp เพื่อให้ได้ผลลัพธ์ที่คมชัดโดยมีจุดบกพร่องน้อยที่สุด Lanczos 4 EWA ที่คมชัดที่สุด รูปแบบ Elliptical Weighted Average (EWA) ของฟิลเตอร์ Lanczos 4 Sharpest สำหรับการสุ่มตัวอย่างภาพที่คมชัดอย่างยิ่ง แลนซอส ซอฟท์ อีดับเบิลยูเอ รูปแบบ Elliptical Weighted Average (EWA) ของฟิลเตอร์ Lanczos Soft เพื่อการสุ่มตัวอย่างภาพที่นุ่มนวลยิ่งขึ้น ฮาซัน ซอฟท์ ตัวกรองการสุ่มตัวอย่างใหม่ซึ่งออกแบบโดย Haasn เพื่อการปรับขนาดภาพที่ราบรื่นและไม่มีข้อผิดพลาด การแปลงรูปแบบ แปลงชุดรูปภาพจากรูปแบบหนึ่งเป็นอีกรูปแบบหนึ่ง ยกเลิกตลอดไป การซ้อนภาพ ซ้อนภาพซ้อนทับกันด้วยโหมดผสมผสานที่เลือก เพิ่มรูปภาพ ถังขยะนับ คลาห์ HSL คลาห์ HSV ปรับ Histogram Adaptive HSL ให้เท่ากัน ปรับ Histogram Adaptive HSV ให้เท่ากัน โหมดขอบ คลิป ห่อ ตาบอดสี เลือกโหมดเพื่อปรับสีของธีมสำหรับตัวแปรตาบอดสีที่เลือก ความยากในการแยกแยะระหว่างเฉดสีแดงและสีเขียว ความยากในการแยกแยะระหว่างเฉดสีเขียวและสีแดง ความยากในการแยกแยะระหว่างเฉดสีน้ำเงินและสีเหลือง ไม่สามารถรับรู้สีแดงได้ ไม่สามารถรับรู้เฉดสีเขียวได้ ไม่สามารถรับรู้เฉดสีฟ้าได้ ลดความไวต่อทุกสี ตาบอดสีโดยสมบูรณ์เห็นแต่เฉดสีเทา อย่าใช้แผนตาบอดสี สีจะตรงตามที่กำหนดไว้ในธีม ซิกมอยด์ ลากรองจ์ 2 ฟิลเตอร์การแก้ไขลากรองจ์ลำดับที่ 2 เหมาะสำหรับการปรับขนาดภาพคุณภาพสูงพร้อมการเปลี่ยนภาพที่ราบรื่น ลากรองจ์ 3 ตัวกรองการแก้ไข Lagrange ในลำดับที่ 3 ให้ความแม่นยำที่ดีขึ้นและผลลัพธ์ที่ราบรื่นยิ่งขึ้นสำหรับการปรับขนาดภาพ ลันโซส 6 ตัวกรองการสุ่มตัวอย่าง Lanczos ที่มีลำดับสูงกว่าที่ 6 ช่วยให้ปรับขนาดภาพที่คมชัดและแม่นยำยิ่งขึ้น แลนซอส 6 จินซี ตัวกรอง Lanczos 6 รูปแบบหนึ่งที่ใช้ฟังก์ชัน Jinc เพื่อปรับปรุงคุณภาพการสุ่มตัวอย่างรูปภาพ กล่องเชิงเส้นเบลอ เชิงเส้นเต็นท์เบลอ กล่องลิเนียร์เกาส์เซียนเบลอ สแต็คเบลอเชิงเส้น กล่องเกาส์เซียนเบลอ ถัดไปเป็น Linear Fast Gaussian Blur Linear Fast Gaussian Blur ลิเนียร์เกาส์เซียนเบลอ เลือกฟิลเตอร์หนึ่งอันเพื่อใช้เป็นสี เปลี่ยนตัวกรอง เลือกตัวกรองด้านล่างเพื่อใช้เป็นแปรงในภาพวาดของคุณ รูปแบบการบีบอัด TIFF โพลีต่ำ จิตรกรรมทราย การแยกภาพ แยกภาพเดี่ยวตามแถวหรือคอลัมน์ พอดีกับขอบเขต รวมโหมดการปรับขนาดครอบตัดเข้ากับพารามิเตอร์นี้เพื่อให้ได้ลักษณะการทำงานที่ต้องการ (ครอบตัด/ปรับให้พอดีกับอัตราส่วนภาพ) นำเข้าภาษาเรียบร้อยแล้ว โมเดล OCR สำรอง นำเข้า ส่งออก ตำแหน่ง ศูนย์ ซ้ายบน ขวาบน ล่างซ้าย ล่างขวา ท็อปเซ็นเตอร์ ตรงกลางขวา กลางล่าง กลางซ้าย รูปภาพเป้าหมาย การถ่ายโอนจานสี น้ำมันเสริม ทีวีเก่าที่เรียบง่าย เอชดีอาร์ ก็อตแธม ร่างที่เรียบง่าย โกลว์นุ่มนวล โปสเตอร์สี ไตรโทน สีที่สาม คลาเฮอ โอแล็บ คลารา โอลช์ คลาเฮ่ จาซบซ์ ลายจุด การจัดกลุ่ม 2x2 Dithering การทำคลัสเตอร์ 4x4 Dithering การจัดกลุ่ม 8x8 Dithering ยีลิโลมา ไดเธอร์ริง ไม่ได้เลือกตัวเลือกที่ชื่นชอบ เพิ่มลงในหน้าเครื่องมือ เพิ่มรายการโปรด เสริม คล้ายคลึงกัน ไตรเอดิก แยกส่วนเสริม เตตราดิก สี่เหลี่ยม อะนาล็อก + ส่วนเสริม เครื่องมือสี ผสม สร้างโทนสี สร้างเฉดสี และอื่นๆ ความกลมกลืนของสี การแรเงาสี การเปลี่ยนแปลง โทนสี โทนเสียง เฉดสี การผสมสี ข้อมูลสี สีที่เลือก สีที่จะผสม ไม่สามารถใช้ monet ในขณะที่เปิดสีไดนามิก 512x512 2D LUT รูปภาพ LUT เป้าหมาย มือสมัครเล่น นางสาวมารยาท นุ่มนวลสง่างาม รุ่น Soft Elegance ตัวแปรการถ่ายโอนจานสี 3D ลุต ไฟล์ 3D LUT เป้าหมาย (.cube / .CUBE) ลุต สารฟอกขาวบายพาส แสงเทียน วางบลูส์ เอ็ดดี้ แอมเบอร์ สีฤดูใบไม้ร่วง สต็อกฟิล์ม 50 คืนหมอก โกดัก รับภาพ LUT ที่เป็นกลาง ขั้นแรก ใช้แอปพลิเคชันแก้ไขภาพที่คุณชื่นชอบเพื่อใช้ฟิลเตอร์กับ LUT ที่เป็นกลาง ซึ่งคุณสามารถหาได้ที่นี่ เพื่อให้ทำงานได้อย่างถูกต้อง แต่ละสีพิกเซลจะต้องไม่ขึ้นอยู่กับพิกเซลอื่นๆ (เช่น การเบลอจะไม่ทำงาน) เมื่อพร้อมแล้ว ให้ใช้อิมเมจ LUT ใหม่ของคุณเป็นอินพุตสำหรับตัวกรอง LUT 512*512 ศิลปะป๊อป เซลลูลอยด์ กาแฟ ป่าทอง เขียว ย้อนยุคเหลือง ดูตัวอย่างลิงก์ เปิดใช้งานการดึงข้อมูลตัวอย่างลิงก์ในตำแหน่งที่คุณสามารถรับข้อความได้ (QRCode, OCR ฯลฯ) ลิงค์ ไฟล์ ICO สามารถบันทึกได้ที่ขนาดสูงสุด 256 x 256 เท่านั้น GIF เป็น WEBP แปลงภาพ GIF เป็นภาพเคลื่อนไหว WEBP เครื่องมือเว็บพี แปลงรูปภาพเป็นภาพเคลื่อนไหว WEBP หรือแยกเฟรมจากภาพเคลื่อนไหว WEBP ที่กำหนด WEBP เป็นรูปภาพ แปลงไฟล์ WEBP เป็นชุดรูปภาพ แปลงชุดรูปภาพเป็นไฟล์ WEBP รูปภาพไปยัง WEBP เลือกรูปภาพ WEBP เพื่อเริ่มต้น ไม่มีสิทธิ์เข้าถึงไฟล์โดยสมบูรณ์ อนุญาตให้เข้าถึงไฟล์ทั้งหมดเพื่อดู JXL, QOI และรูปภาพอื่น ๆ ที่ไม่ได้รับการยอมรับว่าเป็นรูปภาพบน Android หากไม่ได้รับอนุญาต Image Toolbox จะไม่สามารถแสดงภาพเหล่านั้นได้ สีวาดเริ่มต้น โหมดเส้นทางการวาดเริ่มต้น เพิ่มการประทับเวลา เปิดใช้งานการประทับเวลาโดยเพิ่มชื่อไฟล์เอาต์พุต การประทับเวลาที่จัดรูปแบบ เปิดใช้งานการจัดรูปแบบการประทับเวลาในชื่อไฟล์เอาต์พุตแทนมิลลิวินาทีพื้นฐาน เปิดใช้งานการประทับเวลาเพื่อเลือกรูปแบบ บันทึกตำแหน่งครั้งเดียว ดูและแก้ไขตำแหน่งบันทึกครั้งเดียวซึ่งคุณสามารถใช้งานได้โดยกดปุ่มบันทึกค้างไว้ในตัวเลือกส่วนใหญ่ทั้งหมด ใช้ล่าสุด ช่องซีไอ กลุ่ม กล่องเครื่องมือรูปภาพในโทรเลข 🎉 เข้าร่วมแชทของเราที่คุณสามารถพูดคุยอะไรก็ได้ที่คุณต้องการและดูที่ช่อง CI ที่ฉันโพสต์เบต้าและประกาศต่างๆ รับการแจ้งเตือนเกี่ยวกับแอปเวอร์ชันใหม่และอ่านประกาศ ปรับภาพให้พอดีกับขนาดที่กำหนด และใช้การเบลอหรือสีกับพื้นหลัง การจัดเครื่องมือ จัดกลุ่มเครื่องมือตามประเภท จัดกลุ่มเครื่องมือบนหน้าจอหลักตามประเภท แทนที่จะจัดเรียงรายการแบบกำหนดเอง ค่าเริ่มต้น การมองเห็นแถบระบบ แสดงแถบระบบโดยการปัด เปิดใช้งานการปัดเพื่อแสดงแถบระบบหากซ่อนอยู่ อัตโนมัติ ซ่อนทั้งหมด แสดงทั้งหมด ซ่อนแถบนำทาง ซ่อนแถบสถานะ การสร้างเสียงรบกวน สร้างเสียงต่างๆ เช่น เสียงเพอร์ลินหรือเสียงประเภทอื่นๆ ความถี่ ประเภทเสียงรบกวน ประเภทการหมุน ประเภทแฟร็กทัล อ็อกเทฟ ความไม่ชัดเจน ได้รับ ความแข็งแกร่งแบบถ่วงน้ำหนัก ความแรงของปิงปอง ฟังก์ชันระยะทาง ประเภทการส่งคืน กระวนกระวายใจ โดเมนวาร์ป การจัดตำแหน่ง ชื่อไฟล์ที่กำหนดเอง เลือกตำแหน่งและชื่อไฟล์ที่จะใช้ในการบันทึกภาพปัจจุบัน บันทึกลงในโฟลเดอร์ด้วยชื่อที่กำหนดเอง เครื่องสร้างคอลลาจ สร้างภาพต่อกันจากภาพสูงสุด 20 ภาพ ประเภทคอลลาจ กดภาพค้างไว้เพื่อสลับ ย้าย และซูมเพื่อปรับตำแหน่ง ปิดการใช้งานการหมุน ป้องกันการหมุนภาพด้วยท่าทางสองนิ้ว เปิดใช้งานการจัดชิดขอบ หลังจากย้ายหรือซูม รูปภาพจะจัดชิดขอบเฟรม ฮิสโตแกรม ฮิสโตแกรมภาพ RGB หรือความสว่างเพื่อช่วยคุณปรับแต่ง รูปภาพนี้จะถูกนำมาใช้เพื่อสร้างฮิสโตแกรม RGB และความสว่าง ตัวเลือกเทสเซอร์แรค ใช้ตัวแปรอินพุตบางตัวสำหรับเอ็นจิ้น tesseract ตัวเลือกที่กำหนดเอง ควรป้อนตัวเลือกตามรูปแบบนี้: \"--{option_name} {value}\" ครอบตัดอัตโนมัติ มุมฟรี ครอบตัดรูปภาพตามรูปหลายเหลี่ยม ซึ่งจะช่วยแก้ไขเปอร์สเปคทีฟด้วย บังคับชี้ไปที่ขอบเขตของภาพ จุดจะไม่ถูกจำกัดด้วยขอบเขตของภาพ ซึ่งมีประโยชน์สำหรับการแก้ไขเปอร์สเปคทีฟที่แม่นยำยิ่งขึ้น หน้ากาก เนื้อหารับรู้การเติมภายใต้เส้นทางที่วาด จุดรักษา ใช้เคอร์เนลวงกลม กำลังเปิด ปิด การไล่ระดับทางสัณฐานวิทยา หมวกทรงสูง หมวกดำ เส้นโค้งโทน รีเซ็ตเส้นโค้ง เส้นโค้งจะถูกย้อนกลับเป็นค่าเริ่มต้น สไตล์เส้น ขนาดช่องว่าง ประ ดอทแดช ประทับตรา ซิกแซก วาดเส้นประตามเส้นทางที่วาดด้วยขนาดช่องว่างที่ระบุ วาดจุดและเส้นประตามเส้นทางที่กำหนด แค่เส้นตรงเริ่มต้น วาดรูปร่างที่เลือกไปตามเส้นทางโดยมีระยะห่างที่ระบุ ดึงซิกแซกหยักไปตามเส้นทาง อัตราส่วนซิกแซก สร้างทางลัด เลือกเครื่องมือที่จะปักหมุด เครื่องมือจะถูกเพิ่มลงในหน้าจอหลักของ Launcher ของคุณเป็นทางลัด โดยใช้ร่วมกับการตั้งค่า \"ข้ามการเลือกไฟล์\" เพื่อให้บรรลุการทำงานที่จำเป็น อย่าซ้อนเฟรม ช่วยให้สามารถกำจัดเฟรมก่อนหน้าได้ ดังนั้นเฟรมเหล่านั้นจะไม่ซ้อนกัน ครอสเฟด เฟรมจะถูกครอสเฟดเข้าหากัน จำนวนเฟรมครอสเฟด เกณฑ์ที่หนึ่ง เกณฑ์ที่สอง แคนนี่ กระจกเงา 101 ปรับปรุงการซูมเบลอ ลาปลาเซียนธรรมดา โซเบล ซิมเพิล ผู้ช่วยกริด แสดงตารางสนับสนุนเหนือพื้นที่วาดภาพเพื่อช่วยในการปรับแต่งที่แม่นยำ สีตาราง ความกว้างของเซลล์ ความสูงของเซลล์ ตัวเลือกขนาดกะทัดรัด ตัวควบคุมการเลือกบางตัวจะใช้รูปแบบกะทัดรัดเพื่อใช้พื้นที่น้อยลง ให้สิทธิ์กล้องในการตั้งค่าเพื่อจับภาพ เค้าโครง ชื่อหน้าจอหลัก ปัจจัยอัตราคงที่ (CRF) ค่า %1$s หมายถึงการบีบอัดที่ช้า ส่งผลให้ขนาดไฟล์ค่อนข้างเล็ก %2$s หมายถึงการบีบอัดที่เร็วขึ้น ส่งผลให้ไฟล์มีขนาดใหญ่ ห้องสมุดลุต ดาวน์โหลดชุด LUT ซึ่งคุณสามารถสมัครได้หลังจากดาวน์โหลด อัปเดตคอลเลกชันของ LUT (เฉพาะรายการใหม่เท่านั้นที่จะถูกจัดคิว) ซึ่งคุณสามารถใช้ได้หลังจากดาวน์โหลด เปลี่ยนการแสดงตัวอย่างรูปภาพเริ่มต้นสำหรับตัวกรอง ดูตัวอย่างรูปภาพ ซ่อน แสดง ประเภทสไลเดอร์ ไม่ธรรมดา วัสดุ 2 แถบเลื่อนที่ดูแฟนซี นี่คือตัวเลือกเริ่มต้น แถบเลื่อนวัสดุ 2 แถบเลื่อน Material You นำมาใช้ ปุ่มโต้ตอบตรงกลาง ปุ่มของกล่องโต้ตอบจะถูกวางตำแหน่งตรงกลางแทนที่จะเป็นด้านซ้ายหากเป็นไปได้ ใบอนุญาตโอเพ่นซอร์ส ดูใบอนุญาตของไลบรารีโอเพ่นซอร์สที่ใช้ในแอปนี้ พื้นที่ การสุ่มตัวอย่างใหม่โดยใช้ความสัมพันธ์ของพื้นที่พิกเซล อาจเป็นวิธีที่นิยมใช้สำหรับการทำลายภาพ เนื่องจากให้ผลลัพธ์ที่ปราศจากรอยมัว แต่เมื่อซูมภาพ จะคล้ายกับวิธี \"ใกล้ที่สุด\" เปิดใช้งาน Tonemapping เข้า % ไม่สามารถเข้าถึงไซต์ได้ ลองใช้ VPN หรือตรวจสอบว่า URL ถูกต้องหรือไม่ เลเยอร์มาร์กอัป โหมดเลเยอร์ที่มีความสามารถในการวางรูปภาพ ข้อความ และอื่นๆ ได้อย่างอิสระ แก้ไขเลเยอร์ เลเยอร์บนภาพ ใช้รูปภาพเป็นพื้นหลังและเพิ่มเลเยอร์ต่างๆ ไว้ด้านบน เลเยอร์บนพื้นหลัง เช่นเดียวกับตัวเลือกแรก แต่มีสีแทนรูปภาพ เบต้า ด้านการตั้งค่าด่วน เพิ่มแถบลอยที่ด้านที่เลือกขณะแก้ไขรูปภาพ ซึ่งจะเปิดการตั้งค่าที่รวดเร็วเมื่อคลิก ล้างการเลือก การตั้งค่ากลุ่ม \"%1$s\" จะถูกยุบโดยค่าเริ่มต้น การตั้งค่ากลุ่ม \"%1$s\" จะถูกขยายตามค่าเริ่มต้น เครื่องมือ Base64 ถอดรหัสสตริง Base64 เป็นรูปภาพ หรือเข้ารหัสรูปภาพเป็นรูปแบบ Base64 ฐาน64 ค่าที่ระบุไม่ใช่สตริง Base64 ที่ถูกต้อง ไม่สามารถคัดลอกสตริง Base64 ที่ว่างเปล่าหรือไม่ถูกต้อง วางฐาน64 คัดลอก Base64 โหลดรูปภาพเพื่อคัดลอกหรือบันทึกสตริง Base64 หากคุณมีสตริง คุณสามารถวางที่ด้านบนเพื่อรับรูปภาพได้ บันทึก Base64 แบ่งปัน Base64 ตัวเลือก การดำเนินการ นำเข้า Base64 การกระทำ Base64 เพิ่มโครงร่าง เพิ่มเค้าร่างรอบข้อความด้วยสีและความกว้างที่ระบุ สีเค้าร่าง ขนาดเค้าร่าง การหมุน Checksum เป็นชื่อไฟล์ รูปภาพที่ส่งออกจะมีชื่อที่สอดคล้องกับผลรวมการตรวจสอบข้อมูล ซอฟต์แวร์ฟรี (พันธมิตร) ซอฟต์แวร์ที่มีประโยชน์มากขึ้นในช่องพันธมิตรของแอปพลิเคชัน Android อัลกอริทึม เครื่องมือตรวจสอบผลรวม เปรียบเทียบเช็คซัม คำนวณแฮช หรือสร้างสตริงเลขฐานสิบหกจากไฟล์โดยใช้อัลกอริธึมแฮชที่แตกต่างกัน คำนวณ แฮชข้อความ เช็คซัม เลือกไฟล์เพื่อคำนวณผลรวมตรวจสอบตามอัลกอริทึมที่เลือก ป้อนข้อความเพื่อคำนวณผลรวมตรวจสอบตามอัลกอริทึมที่เลือก การตรวจสอบแหล่งที่มา เช็คซัมเพื่อเปรียบเทียบ จับคู่! ความแตกต่าง เช็คซัมเท่ากันก็ปลอดภัยได้ เช็คซัมไม่เท่ากัน ไฟล์อาจไม่ปลอดภัย! การไล่ระดับสีแบบตาข่าย ดูคอลเลกชันออนไลน์ของการไล่ระดับสีแบบตาข่าย สามารถนำเข้าได้เฉพาะแบบอักษร TTF และ OTF เท่านั้น นำเข้าแบบอักษร (TTF/OTF) ส่งออกแบบอักษร แบบอักษรที่นำเข้า เกิดข้อผิดพลาดขณะบันทึกความพยายาม พยายามเปลี่ยนโฟลเดอร์เอาต์พุต ไม่ได้ตั้งชื่อไฟล์ ไม่มี หน้าที่กำหนดเอง การเลือกหน้า การยืนยันการออกจากเครื่องมือ หากคุณยังไม่ได้บันทึกการเปลี่ยนแปลงในขณะที่ใช้เครื่องมือเฉพาะและพยายามปิด กล่องโต้ตอบการยืนยันจะปรากฏขึ้น แก้ไข EXIF เปลี่ยนข้อมูลเมตาของภาพเดียวโดยไม่ต้องบีบอัดใหม่ แตะเพื่อแก้ไขแท็กที่มีอยู่ เปลี่ยนสติ๊กเกอร์ พอดีความกว้าง พอดีกับความสูง เปรียบเทียบแบทช์ เลือกไฟล์/ไฟล์เพื่อคำนวณผลรวมตามอัลกอริทึมที่เลือก เลือกไฟล์ เลือกไดเรกทอรี สเกลความยาวศีรษะ แสตมป์ การประทับเวลา รูปแบบรูปแบบ ช่องว่างภายใน การตัดภาพ ตัดส่วนของรูปภาพและรวมส่วนด้านซ้าย (สามารถกลับด้านได้) ด้วยเส้นแนวตั้งหรือแนวนอน เส้นเดือยแนวตั้ง เส้นหมุนแนวนอน การเลือกแบบผกผัน ส่วนที่ตัดแนวตั้งจะปล่อยทิ้งไว้ แทนที่จะรวมส่วนที่ตัดไว้รอบๆ บริเวณที่ตัด ส่วนที่ตัดแนวนอนจะเหลือไว้ แทนที่จะรวมส่วนที่ตัดไว้รอบๆ บริเวณที่ตัด คอลเลกชันของเมชเกรเดียน สร้างการไล่ระดับสีแบบตาข่ายด้วยจำนวนนอตและความละเอียดที่กำหนดเอง การซ้อนทับแบบไล่ระดับแบบตาข่าย เขียนการไล่ระดับสีแบบตาข่ายที่ด้านบนของรูปภาพที่กำหนด การปรับแต่งคะแนน ขนาดตาราง ความละเอียด X ความละเอียด Y ปณิธาน พิกเซลต่อพิกเซล ไฮไลท์สี ประเภทการเปรียบเทียบพิกเซล สแกนบาร์โค้ด อัตราส่วนความสูง ประเภทบาร์โค้ด บังคับใช้ขาวดำ รูปภาพบาร์โค้ดจะเป็นสีขาวดำทั้งหมด และไม่มีสีตามธีมของแอป สแกนบาร์โค้ด (QR, EAN, AZTEC, …) และรับเนื้อหาหรือวางข้อความของคุณเพื่อสร้างใหม่ ไม่พบบาร์โค้ด บาร์โค้ดที่สร้างขึ้นจะอยู่ที่นี่ ปกเสียง แยกภาพปกอัลบั้มออกจากไฟล์เสียง รองรับรูปแบบทั่วไปส่วนใหญ่ เลือกเสียงเพื่อเริ่มต้น เลือกเสียง ไม่พบปก ส่งบันทึก คลิกเพื่อแชร์ไฟล์บันทึกของแอป ซึ่งสามารถช่วยฉันระบุปัญหาและแก้ไขปัญหาได้ อ๊ะ… มีบางอย่างผิดพลาด คุณสามารถติดต่อฉันได้โดยใช้ตัวเลือกด้านล่าง แล้วฉันจะพยายามหาวิธีแก้ปัญหา\n(อย่าลืมแนบบันทึก) เขียนลงไฟล์ แยกข้อความออกจากชุดรูปภาพและจัดเก็บไว้ในไฟล์ข้อความเดียว เขียนถึงข้อมูลเมตา แยกข้อความจากแต่ละภาพและวางไว้ในข้อมูล EXIF ​​ของภาพถ่ายที่เกี่ยวข้อง โหมดที่มองไม่เห็น ใช้การอำพรางเพื่อสร้างลายน้ำที่มองไม่เห็นด้วยตาภายในจำนวนไบต์ของรูปภาพของคุณ ใช้ LSB จะใช้วิธี Steganography แบบ LSB (Less Significant Bit) มิฉะนั้น FD (โดเมนความถี่) ลบตาแดงอัตโนมัติ รหัสผ่าน ปลดล็อค PDF ได้รับการคุ้มครอง ปฏิบัติการใกล้จะเสร็จสมบูรณ์แล้ว การยกเลิกตอนนี้จะต้องเริ่มต้นใหม่อีกครั้ง วันที่แก้ไข วันที่แก้ไข (กลับรายการ) ขนาด ขนาด (กลับด้าน) ประเภทไมม์ ประเภท MIME (กลับด้าน) ส่วนขยาย ส่วนขยาย (กลับด้าน) วันที่เพิ่ม วันที่เพิ่ม (กลับรายการ) จากซ้ายไปขวา ขวาไปซ้าย จากบนลงล่าง จากล่างขึ้นบน แก้วน้ำ สวิตช์ที่ใช้ระบบปฏิบัติการ iOS 26 ที่เพิ่งประกาศเมื่อเร็วๆ นี้ และระบบการออกแบบกระจกเหลว เลือกรูปภาพหรือวาง/นำเข้าข้อมูล Base64 ด้านล่าง พิมพ์ลิงค์รูปภาพเพื่อเริ่มต้น วางลิงก์ ลานตา มุมรอง ด้านข้าง มิกซ์ช่อง ฟ้าเขียว แดงน้ำเงิน เขียวแดง เป็นสีแดง กลายเป็นสีเขียว เป็นสีฟ้า สีฟ้า สีม่วงแดง สีเหลือง ฮาล์ฟโทนสี คอนทัวร์ ระดับ ออฟเซ็ต โวโรน้อย คริสตัลไลซ์ รูปร่าง ยืด ความบังเอิญ เดสเปคเคิล กระจาย สุนัข รัศมีที่สอง ทำให้เท่าเทียมกัน เรืองแสง วนและหยิก ชี้นิ้ว สีขอบ พิกัดเชิงขั้ว ตรงไปที่ขั้วโลก ขั้วโลกเพื่อแก้ไข พลิกกลับเป็นวงกลม ลดเสียงรบกวน โซลาไรซ์อย่างง่าย สาน เอ็กซ์ แกป วาย แกป เอ็กซ์ กว้าง ความกว้าง Y หมุนวน ตรายาง ละเลง ความหนาแน่น ผสม การบิดเบือนเลนส์ทรงกลม ดัชนีการหักเหของแสง อาร์ค มุมกระจาย สปาร์คเคิล รังสี แอสกี การไล่ระดับสี แมรี่ ฤดูใบไม้ร่วง กระดูก เจ็ต ฤดูหนาว มหาสมุทร ฤดูร้อน ฤดูใบไม้ผลิ ตัวแปรเด็ด HSV สีชมพู ร้อน คำ แม็กม่า นรก พลาสมา วิริดิส พลเมือง ทไวไลท์ ทไวไลท์เปลี่ยนไป มุมมองอัตโนมัติ เดซิว อนุญาตให้ครอบตัด ครอบตัดหรือเปอร์สเปคทีฟ แน่นอน เทอร์โบ สีเขียวเข้ม การแก้ไขเลนส์ ไฟล์โปรไฟล์เลนส์เป้าหมายในรูปแบบ JSON ดาวน์โหลดโปรไฟล์เลนส์พร้อม เปอร์เซ็นต์ส่วนหนึ่ง ส่งออกเป็น JSON คัดลอกสตริงที่มีข้อมูลจานสีเป็นตัวแทน json การแกะสลักตะเข็บ หน้าจอหลัก ล็อคหน้าจอ บิวท์อิน ส่งออกวอลเปเปอร์ รีเฟรช รับวอลเปเปอร์ Home, Lock และ Built-in ปัจจุบัน อนุญาตให้เข้าถึงไฟล์ทั้งหมด ซึ่งจำเป็นสำหรับการดึงวอลเปเปอร์ สิทธิ์ในการจัดการที่จัดเก็บข้อมูลภายนอกไม่เพียงพอ คุณต้องอนุญาตการเข้าถึงรูปภาพของคุณ ตรวจสอบให้แน่ใจว่าได้เลือก \"อนุญาตทั้งหมด\" เพิ่มค่าที่ตั้งไว้ล่วงหน้าในชื่อไฟล์ เพิ่มส่วนต่อท้ายด้วยค่าที่ตั้งไว้ล่วงหน้าที่เลือกไว้กับชื่อไฟล์ภาพ เพิ่มโหมดมาตราส่วนรูปภาพในชื่อไฟล์ เพิ่มส่วนต่อท้ายด้วยโหมดขนาดภาพที่เลือกไว้ต่อท้ายชื่อไฟล์ภาพ ศิลปะแอสกี้ แปลงรูปภาพเป็นข้อความ ASCII ซึ่งจะมีลักษณะเหมือนรูปภาพ พารามิเตอร์ ใช้ตัวกรองเชิงลบกับรูปภาพเพื่อให้ได้ผลลัพธ์ที่ดีขึ้นในบางกรณี กำลังประมวลผลภาพหน้าจอ ไม่ได้จับภาพหน้าจอ โปรดลองอีกครั้ง ข้ามการบันทึกแล้ว %1$s ไฟล์ถูกข้าม อนุญาตให้ข้ามหากใหญ่กว่านี้ เครื่องมือบางอย่างจะได้รับอนุญาตให้ข้ามการบันทึกรูปภาพได้ หากขนาดไฟล์ที่ได้จะใหญ่กว่าต้นฉบับ กิจกรรมในปฏิทิน ติดต่อ อีเมล ที่ตั้ง โทรศัพท์ ข้อความ เอสเอ็มเอส URL อินเตอร์เน็ตไร้สาย เปิดเครือข่าย ไม่มี SSID โทรศัพท์ ข้อความ ที่อยู่ เรื่อง ร่างกาย ชื่อ องค์กร ชื่อ โทรศัพท์ อีเมล URL ที่อยู่ สรุป คำอธิบาย ที่ตั้ง ออแกไนเซอร์ วันที่เริ่มต้น วันที่สิ้นสุด สถานะ ละติจูด ลองจิจูด สร้างบาร์โค้ด แก้ไขบาร์โค้ด การกำหนดค่า Wi-Fi ความปลอดภัย เลือกผู้ติดต่อ ให้สิทธิ์ผู้ติดต่อในการตั้งค่าเพื่อป้อนอัตโนมัติโดยใช้ผู้ติดต่อที่เลือก ข้อมูลการติดต่อ ชื่อ ชื่อกลาง นามสกุล การออกเสียง เพิ่มโทรศัพท์ เพิ่มอีเมล เพิ่มที่อยู่ เว็บไซต์ เพิ่มเว็บไซต์ ชื่อที่จัดรูปแบบ รูปภาพนี้จะถูกนำมาใช้เพื่อวางไว้เหนือบาร์โค้ด การปรับแต่งโค้ด รูปภาพนี้จะใช้เป็นโลโก้ตรงกลางโค้ด QR โลโก้ การขยายโลโก้ ขนาดโลโก้ มุมโลโก้ ตาที่สี่ เพิ่มความสมมาตรของดวงตาให้กับโค้ด QR โดยเพิ่มตาที่สี่ที่มุมล่างสุด รูปร่างพิกเซล รูปร่างกรอบ รูปร่างลูก ระดับการแก้ไขข้อผิดพลาด สีเข้ม สีอ่อน ไฮเปอร์ระบบปฏิบัติการ Xiaomi HyperOS ชอบสไตล์ รูปแบบหน้ากาก รหัสนี้อาจสแกนไม่ได้ โปรดเปลี่ยนพารามิเตอร์ลักษณะที่ปรากฏเพื่อให้สามารถอ่านได้กับทุกอุปกรณ์ สแกนไม่ได้. เครื่องมือจะมีลักษณะเหมือนเครื่องเรียกใช้งานแอปบนหน้าจอหลักเพื่อให้มีขนาดกะทัดรัดมากขึ้น โหมดตัวเรียกใช้ เติมพื้นที่ด้วยแปรงและสไตล์ที่เลือก น้ำท่วม สเปรย์ วาดเส้นทางสไตล์กราฟิตี้ อนุภาคสี่เหลี่ยม อนุภาคสเปรย์จะเป็นรูปทรงสี่เหลี่ยมแทนที่จะเป็นวงกลม เครื่องมือจานสี สร้างจานสีพื้นฐาน/วัสดุจากรูปภาพ หรือนำเข้า/ส่งออกในรูปแบบจานสีต่างๆ แก้ไขจานสี ส่งออก/นำเข้าจานสีในรูปแบบต่างๆ ชื่อสี ชื่อจานสี รูปแบบจานสี ส่งออกจานสีที่สร้างขึ้นเป็นรูปแบบต่างๆ เพิ่มสีใหม่ให้กับจานสีปัจจุบัน รูปแบบ %1$s ไม่รองรับการระบุชื่อจานสี เนื่องจากนโยบายของ Play Store ฟีเจอร์นี้จึงไม่สามารถรวมไว้ในรุ่นปัจจุบันได้ หากต้องการเข้าถึงฟังก์ชันนี้ โปรดดาวน์โหลด ImageToolbox จากแหล่งอื่น คุณสามารถค้นหารุ่นที่มีอยู่บน GitHub ด้านล่าง เปิดหน้า Github ไฟล์ต้นฉบับจะถูกแทนที่ด้วยไฟล์ใหม่แทนที่จะบันทึกในโฟลเดอร์ที่เลือก ตรวจพบข้อความลายน้ำที่ซ่อนอยู่ ตรวจพบภาพลายน้ำที่ซ่อนอยู่ ภาพนี้ถูกซ่อนไว้ กำเนิด Inpainting ช่วยให้คุณสามารถลบวัตถุในรูปภาพโดยใช้โมเดล AI โดยไม่ต้องพึ่งพา OpenCV หากต้องการใช้ฟีเจอร์นี้ แอปจะดาวน์โหลดโมเดลที่ต้องการ (~200 MB) จาก GitHub ช่วยให้คุณสามารถลบวัตถุในรูปภาพโดยใช้โมเดล AI โดยไม่ต้องพึ่งพา OpenCV นี่อาจเป็นการดำเนินการที่ใช้เวลานาน การวิเคราะห์ระดับข้อผิดพลาด การไล่ระดับความสว่าง ระยะทางเฉลี่ย คัดลอกการตรวจจับการเคลื่อนไหว เก็บไว้ ค่าสัมประสิทธิ์ ข้อมูลคลิปบอร์ดมีขนาดใหญ่เกินไป ข้อมูลมีขนาดใหญ่เกินกว่าจะคัดลอกได้ การสานพิกเซลอย่างง่าย การสลับพิกเซลแบบเซ ข้ามพิกเซล ไมโครมาโครพิกเซล การทำให้เป็นพิกเซลของวงโคจร การทำให้เป็นพิกเซลของวอร์เท็กซ์ พิกเซลกริดพัลส์ การทำให้เป็นพิกเซลของนิวเคลียส การสร้างพิกเซลแบบสานเรเดียล ไม่สามารถเปิด uri \"%1$s\" โหมดหิมะตก เปิดใช้งานแล้ว เส้นขอบเฟรม ตัวแปรความผิดพลาด การเปลี่ยนช่อง แม็กซ์ออฟเซ็ต วีดิทัศน์ บล็อกความผิดพลาด ขนาดบล็อก ความโค้งของซีอาร์ที ความโค้ง โครมา พิกเซลละลาย แม็กซ์ดรอป เครื่องมือเอไอ เครื่องมือต่างๆ ในการประมวลผลภาพผ่านโมเดล AI เช่น การลบวัตถุหรือการลดสัญญาณรบกวน การบีบอัดเส้นหยัก การ์ตูนอัดรายการออกอากาศ การบีบอัดทั่วไป, เสียงรบกวนทั่วไป เสียงการ์ตูนไม่มีสี รวดเร็ว การบีบอัดทั่วไป สัญญาณรบกวนทั่วไป แอนิเมชั่น/การ์ตูน/อนิเมะ การสแกนหนังสือ การแก้ไขค่าแสง ดีที่สุดสำหรับการบีบอัดภาพสีทั่วไป ดีที่สุดสำหรับการบีบอัดภาพระดับสีเทาทั่วไป การบีบอัดทั่วไป ภาพระดับสีเทา เข้มขึ้น สัญญาณรบกวนทั่วไป, ภาพสี สัญญาณรบกวนทั่วไป ภาพสี รายละเอียดดีขึ้น สัญญาณรบกวนทั่วไป, ภาพระดับสีเทา สัญญาณรบกวนทั่วไป ภาพระดับสีเทา เข้มกว่า สัญญาณรบกวนทั่วไป ภาพระดับสีเทา เข้มที่สุด การบีบอัดทั่วไป การบีบอัดทั่วไป การสร้างพื้นผิว การบีบอัด h264 การบีบอัดวีเอชเอส การบีบอัดที่ไม่ได้มาตรฐาน (cinepak, msvideo1, roq) การบีบอัด Bink ดีกว่าในเรขาคณิต การบีบอัด Bink แข็งแกร่งขึ้น การบีบอัด Bink นุ่มนวล คงรายละเอียด ขจัดเอฟเฟกต์ขั้นบันไดให้เรียบ งานศิลปะ/ภาพวาดที่สแกน การบีบอัดแบบอ่อน ลายมัวร์ แถบสี ช้า กำลังลบฮาล์ฟโทน โปรแกรมปรับสีทั่วไปสำหรับภาพระดับสีเทา/bw เพื่อผลลัพธ์ที่ดีกว่า ให้ใช้ DDColor การกำจัดขอบ ลบการเหลามากเกินไป ช้า, น่าเบื่อ การต่อต้านนามแฝง, สิ่งประดิษฐ์ทั่วไป, CGI KDM003 สแกนการประมวลผล รุ่นปรับปรุงภาพน้ำหนักเบา การกำจัดสิ่งประดิษฐ์การบีบอัด การกำจัดสิ่งประดิษฐ์การบีบอัด การกำจัดผ้าพันแผลด้วยผลลัพธ์ที่ราบรื่น การประมวลผลรูปแบบฮาล์ฟโทน การลบรูปแบบ Dither V3 การลบสิ่งประดิษฐ์ JPEG V2 การปรับปรุงพื้นผิว H.264 การเหลาและการเพิ่มประสิทธิภาพ VHS การผสาน ขนาดก้อน ขนาดที่ทับซ้อนกัน รูปภาพที่มีขนาดเกิน %1$s พิกเซลจะถูกแบ่งและประมวลผลเป็นชิ้นๆ โดยจะซ้อนทับกันเพื่อป้องกันไม่ให้เกิดรอยต่อที่มองเห็นได้ ขนาดใหญ่อาจทำให้เกิดความไม่เสถียรกับอุปกรณ์ระดับล่าง เลือกหนึ่งรายการเพื่อเริ่มต้น คุณต้องการลบโมเดล %1$s หรือไม่ คุณจะต้องดาวน์โหลดอีกครั้ง ยืนยัน โมเดล โมเดลที่ดาวน์โหลด รุ่นที่มีจำหน่าย กำลังเตรียมตัว โมเดลที่ใช้งานอยู่ ไม่สามารถเปิดเซสชันได้ สามารถนำเข้าได้เฉพาะรุ่น .onnx/.ort เท่านั้น โมเดลนำเข้า นำเข้าโมเดล onnx ที่กำหนดเองเพื่อใช้งานต่อไป โดยยอมรับเฉพาะโมเดล onnx/ort เท่านั้น รองรับรูปแบบ esrgan เกือบทั้งหมด โมเดลนำเข้า สัญญาณรบกวนทั่วไป, ภาพสี สัญญาณรบกวนทั่วไป ภาพสี เข้มขึ้น สัญญาณรบกวนทั่วไป ภาพสี แรงที่สุด ลดความผิดเพี้ยนของสีและแถบสี ปรับปรุงการไล่ระดับสีที่ราบรื่นและพื้นที่สีเรียบ เพิ่มความสว่างและคอนทราสต์ของภาพด้วยไฮไลท์ที่สมดุลในขณะที่ยังคงสีที่เป็นธรรมชาติ ทำให้ภาพที่มืดสว่างขึ้นโดยยังคงรักษารายละเอียดและป้องกันไม่ให้ได้รับแสงมากเกินไป ลบโทนสีที่มากเกินไปและคืนความสมดุลของสีที่เป็นกลางและเป็นธรรมชาติมากขึ้น ใช้การปรับสีสัญญาณรบกวนแบบปัวซอง โดยเน้นการรักษารายละเอียดและพื้นผิวที่ละเอียดอ่อน ใช้การปรับสีสัญญาณรบกวนปัวซองแบบนุ่มนวลเพื่อผลลัพธ์ภาพที่นุ่มนวลและรุนแรงน้อยลง การปรับสีสัญญาณรบกวนที่สม่ำเสมอเน้นที่การรักษารายละเอียดและความคมชัดของภาพ การปรับสีเสียงรบกวนที่สม่ำเสมออย่างอ่อนโยนเพื่อเนื้อสัมผัสที่ละเอียดอ่อนและรูปลักษณ์ที่เรียบเนียน ซ่อมแซมพื้นที่ที่เสียหายหรือไม่สม่ำเสมอด้วยการทาสีสิ่งประดิษฐ์ใหม่และปรับปรุงความสม่ำเสมอของภาพ รุ่นลอกแถบน้ำหนักเบาที่ช่วยขจัดแถบสีโดยมีค่าใช้จ่ายด้านประสิทธิภาพน้อยที่สุด ปรับภาพให้เหมาะสมด้วยการบีบอัดที่สูงมาก (คุณภาพ 0-20%) เพื่อความชัดเจนที่ดีขึ้น ปรับปรุงภาพด้วยการบีบอัดข้อมูลสูง (คุณภาพ 20-40%) คืนรายละเอียดและลดสัญญาณรบกวน ปรับปรุงภาพด้วยการบีบอัดปานกลาง (คุณภาพ 40-60%) ปรับสมดุลความคมชัดและความเรียบเนียน ปรับแต่งภาพด้วยการบีบอัดแสง (คุณภาพ 60-80%) เพื่อเพิ่มรายละเอียดและพื้นผิวที่ละเอียดอ่อน ปรับปรุงภาพที่เกือบไม่มีการสูญเสียเล็กน้อย (คุณภาพ 80-100%) ในขณะที่ยังคงรูปลักษณ์และรายละเอียดที่เป็นธรรมชาติ การระบายสีการ์ตูนที่ง่ายและรวดเร็วไม่เหมาะ ลดความเบลอของภาพลงเล็กน้อย ปรับปรุงความคมชัดโดยไม่ทำให้เกิดสิ่งแปลกปลอม การดำเนินงานที่ยาวนาน กำลังประมวลผลภาพ กำลังประมวลผล ลบสิ่งแปลกปลอมในการบีบอัด JPEG จำนวนมากในภาพคุณภาพต่ำมาก (0-20%) ลดข้อผิดพลาด JPEG ที่แข็งแกร่งในภาพที่มีการบีบอัดสูง (20-40%) ทำความสะอาดสิ่งประดิษฐ์ JPEG ระดับปานกลางในขณะที่รักษารายละเอียดของภาพ (40-60%) ปรับแต่งสิ่งประดิษฐ์ JPEG แบบแสงในภาพคุณภาพสูงพอสมควร (60-80%) ลดขนาดภาพ JPEG เล็กน้อยในภาพที่ไม่มีการสูญเสียข้อมูล (80-100%) อย่างละเอียด ปรับปรุงรายละเอียดและพื้นผิวที่ละเอียดอ่อน ปรับปรุงความคมชัดในการรับรู้โดยไม่มีสิ่งแปลกปลอมที่หนักหน่วง การประมวลผลเสร็จสิ้น การประมวลผลล้มเหลว ปรับปรุงพื้นผิวและรายละเอียดในขณะที่ยังคงรูปลักษณ์ที่เป็นธรรมชาติ ปรับให้เหมาะสมเพื่อความเร็ว ลบสิ่งแปลกปลอมในการบีบอัด JPEG และคืนคุณภาพของภาพสำหรับภาพถ่ายที่ถูกบีบอัด ลดสัญญาณรบกวน ISO ในภาพที่ถ่ายในสภาพแสงน้อย โดยคงรายละเอียดไว้ แก้ไขการไฮไลท์ที่เปิดรับแสงมากเกินไปหรือ “จัมโบ้” และคืนสมดุลของโทนสีที่ดีขึ้น โมเดลการปรับสีน้ำหนักเบาและรวดเร็วที่เพิ่มสีที่เป็นธรรมชาติให้กับภาพระดับสีเทา เดเจเพ็ก เดนัวส์ ปรับสี สิ่งประดิษฐ์ ยกระดับ อะนิเมะ สแกน หรู ตัวเพิ่มสเกล X4 สำหรับรูปภาพทั่วไป รุ่นจิ๋วที่ใช้ GPU และเวลาน้อยกว่า โดยมีความเบลอและการลดทอนในระดับปานกลาง ตัวเพิ่มสเกล X2 สำหรับภาพทั่วไป รักษาพื้นผิวและรายละเอียดที่เป็นธรรมชาติ ตัวเพิ่มสเกล X4 สำหรับภาพทั่วไปพร้อมพื้นผิวที่ได้รับการปรับปรุงและผลลัพธ์ที่สมจริง ตัวเพิ่มสเกล X4 ปรับให้เหมาะสมสำหรับภาพอนิเมะ 6 บล็อก RRDB เพื่อเส้นและรายละเอียดที่คมชัดยิ่งขึ้น ตัวอัปสเกล X4 พร้อมการสูญเสีย MSE ให้ผลลัพธ์ที่นุ่มนวลยิ่งขึ้น และลดข้อผิดพลาดสำหรับรูปภาพทั่วไป X4 Upscaler ปรับให้เหมาะสมสำหรับภาพอนิเมะ รุ่น 4B32F ที่มีรายละเอียดคมชัดกว่าและเส้นเรียบ รุ่น X4 UltraSharp V2 สำหรับภาพทั่วไป เน้นความคมชัดและความคมชัด X4 อัลตร้าชาร์ป V2 Lite; เร็วขึ้นและเล็กลง รักษารายละเอียดในขณะที่ใช้หน่วยความจำ GPU น้อยลง รุ่นน้ำหนักเบาเพื่อการลบพื้นหลังอย่างรวดเร็ว ประสิทธิภาพและความแม่นยำที่สมดุล ใช้งานได้กับภาพบุคคล วัตถุ และฉาก แนะนำสำหรับกรณีการใช้งานส่วนใหญ่ ลบบีจี ความหนาของเส้นขอบแนวนอน ความหนาของเส้นขอบแนวตั้ง %1$s สี รุ่นปัจจุบันไม่รองรับการรวมเป็นก้อน รูปภาพจะถูกประมวลผลในขนาดดั้งเดิม ซึ่งอาจทำให้เกิดการใช้หน่วยความจำสูงและมีปัญหากับอุปกรณ์ระดับล่าง ปิดใช้งานการแบ่งส่วน รูปภาพจะถูกประมวลผลในขนาดดั้งเดิม ซึ่งอาจทำให้ใช้หน่วยความจำสูงและมีปัญหากับอุปกรณ์ระดับล่าง แต่อาจให้ผลลัพธ์ที่ดีกว่าในการอนุมาน ก้อน โมเดลการแบ่งส่วนภาพที่มีความแม่นยำสูงสำหรับการลบพื้นหลัง U2Net เวอร์ชันน้ำหนักเบาเพื่อการลบพื้นหลังที่รวดเร็วขึ้นโดยใช้หน่วยความจำน้อยลง โมเดล DDColor เต็มรูปแบบมอบการปรับสีคุณภาพสูงสำหรับภาพทั่วไปที่มีจุดบกพร่องน้อยที่สุด ตัวเลือกที่ดีที่สุดของโมเดลการปรับสีทั้งหมด ชุดข้อมูลศิลปะที่ได้รับการฝึกอบรมและส่วนตัวของ DDColor สร้างผลลัพธ์การให้สีที่หลากหลายและเป็นศิลปะโดยมีสิ่งเจือปนของสีที่ไม่สมจริงน้อยลง รุ่น BiRefNet น้ำหนักเบาที่ใช้ Swin Transformer เพื่อการลบพื้นหลังที่แม่นยำ การลบพื้นหลังคุณภาพสูงพร้อมขอบคมและการรักษารายละเอียดที่ยอดเยี่ยม โดยเฉพาะกับวัตถุที่ซับซ้อนและพื้นหลังที่ยุ่งยาก โมเดลการลบพื้นหลังที่สร้างมาสก์ที่แม่นยำพร้อมขอบเรียบ เหมาะสำหรับวัตถุทั่วไปและการรักษารายละเอียดปานกลาง ดาวน์โหลดโมเดลแล้ว นำเข้าโมเดลเรียบร้อยแล้ว พิมพ์ คำสำคัญ เร็วมาก ปกติ ช้า ช้ามาก เปอร์เซ็นต์การคำนวณ ค่าต่ำสุดคือ %1$s บิดเบือนภาพด้วยการวาดภาพด้วยมือ วาร์ป ความแข็ง โหมดวาร์ป เคลื่อนไหว เติบโต หด หมุน CW ทวนเข็มนาฬิกาหมุนวน จางลงความแข็งแรง ดรอปยอดนิยม ดรอปล่าง เริ่มดรอป สิ้นสุดดรอป กำลังดาวน์โหลด รูปร่างเรียบเนียน ใช้ซูเปอร์รีลิปส์แทนสี่เหลี่ยมมนมาตรฐานเพื่อให้รูปทรงเรียบเนียนและเป็นธรรมชาติยิ่งขึ้น ประเภทรูปร่าง ตัด โค้งมน เรียบ ขอบคมโดยไม่ต้องปัดเศษ มุมโค้งมนสุดคลาสสิก ประเภทรูปร่าง ขนาดมุม สควอเคิล องค์ประกอบ UI แบบโค้งมนที่หรูหรา รูปแบบชื่อไฟล์ ข้อความแบบกำหนดเองวางไว้ที่จุดเริ่มต้นของชื่อไฟล์ เหมาะสำหรับชื่อโปรเจ็กต์ แบรนด์ หรือแท็กส่วนตัว ใช้ชื่อไฟล์ต้นฉบับโดยไม่มีนามสกุล ช่วยให้การระบุแหล่งที่มาไม่เสียหาย ความกว้างของภาพเป็นพิกเซล มีประโยชน์สำหรับการติดตามการเปลี่ยนแปลงความละเอียดหรือการปรับขนาดผลลัพธ์ ความสูงของรูปภาพเป็นพิกเซล ซึ่งมีประโยชน์เมื่อทำงานกับอัตราส่วนภาพหรือการส่งออก สร้างตัวเลขสุ่มเพื่อรับประกันชื่อไฟล์ที่ไม่ซ้ำใคร เพิ่มตัวเลขเพิ่มเติมเพื่อความปลอดภัยเป็นพิเศษจากการซ้ำซ้อน ตัวนับที่เพิ่มขึ้นอัตโนมัติสำหรับการส่งออกเป็นชุด เหมาะอย่างยิ่งเมื่อบันทึกหลายภาพในเซสชันเดียว แทรกชื่อที่ตั้งไว้ล่วงหน้าที่ใช้ลงในชื่อไฟล์ เพื่อให้คุณสามารถจดจำวิธีการประมวลผลภาพได้อย่างง่ายดาย แสดงโหมดการปรับขนาดภาพที่ใช้ในระหว่างการประมวลผล ช่วยแยกแยะความแตกต่างของภาพที่ปรับขนาด ครอบตัด หรือพอดี ข้อความที่กำหนดเองวางไว้ท้ายชื่อไฟล์ ซึ่งมีประโยชน์สำหรับการกำหนดเวอร์ชัน เช่น _v2, _edited หรือ _final นามสกุลไฟล์ (png, jpg, webp ฯลฯ) จะตรงกับรูปแบบที่บันทึกไว้จริงโดยอัตโนมัติ การประทับเวลาที่ปรับแต่งได้ซึ่งช่วยให้คุณกำหนดรูปแบบของคุณเองตามข้อกำหนดของ Java เพื่อการเรียงลำดับที่สมบูรณ์แบบ ประเภทพุ่ง ระบบปฏิบัติการ Android iOS สไตล์ เส้นโค้งเรียบ หยุดด่วน เด้ง ลอย เร็ว เรียบเนียนเป็นพิเศษ ปรับตัวได้ การรับรู้การเข้าถึง ลดการเคลื่อนไหว ฟิสิกส์การเลื่อน Android ดั้งเดิมสำหรับการเปรียบเทียบพื้นฐาน การเลื่อนที่สมดุลและราบรื่นสำหรับการใช้งานทั่วไป พฤติกรรมการเลื่อนเหมือน iOS ที่มีแรงเสียดทานสูงขึ้น เส้นโค้งที่ไม่ซ้ำใครเพื่อความรู้สึกในการเลื่อนที่แตกต่างกัน การเลื่อนที่แม่นยำพร้อมการหยุดอย่างรวดเร็ว เลื่อนเด้งที่สนุกสนานและตอบสนอง การเลื่อนแบบเลื่อนยาวสำหรับการเรียกดูเนื้อหา การเลื่อนที่รวดเร็วและตอบสนองสำหรับ UI แบบโต้ตอบ การเลื่อนอย่างราบรื่นระดับพรีเมียมพร้อมโมเมนตัมที่ขยายออกไป ปรับฟิสิกส์ตามความเร็วการเหวี่ยง เคารพการตั้งค่าการเข้าถึงระบบ การเคลื่อนไหวน้อยที่สุดสำหรับความต้องการในการเข้าถึง สายหลัก เพิ่มเส้นหนาทุกๆ บรรทัดที่ห้า เติมสี เครื่องมือที่ซ่อนอยู่ เครื่องมือที่ซ่อนอยู่เพื่อการแบ่งปัน ห้องสมุดสี เรียกดูคอลเลกชันสีที่หลากหลาย ปรับความคมชัดและลบความเบลอออกจากภาพโดยยังคงรายละเอียดที่เป็นธรรมชาติ เหมาะสำหรับการแก้ไขภาพที่อยู่นอกโฟกัส กู้คืนรูปภาพที่ได้รับการปรับขนาดก่อนหน้านี้อย่างชาญฉลาด โดยกู้คืนรายละเอียดและพื้นผิวที่สูญหาย ปรับให้เหมาะสมสำหรับเนื้อหาไลฟ์แอ็กชั่น ลดปัญหาการบีบอัด และเพิ่มรายละเอียดเล็กๆ น้อยๆ ในเฟรมภาพยนตร์/รายการทีวี แปลงฟุตเทจคุณภาพ VHS เป็น HD ช่วยขจัดสัญญาณรบกวนของเทปและเพิ่มความละเอียดในขณะที่ยังคงรักษาความรู้สึกแบบวินเทจ ออกแบบมาสำหรับรูปภาพและภาพหน้าจอที่มีข้อความจำนวนมาก ปรับความคมชัดของตัวอักษร และปรับปรุงความสามารถในการอ่าน การลดขนาดขั้นสูงที่ได้รับการฝึกอบรมบนชุดข้อมูลที่หลากหลาย เหมาะอย่างยิ่งสำหรับการปรับปรุงภาพถ่ายทั่วไป ปรับให้เหมาะสมสำหรับภาพถ่ายที่บีบอัดทางเว็บ ลบสิ่งประดิษฐ์ JPEG และคืนลักษณะที่เป็นธรรมชาติ เวอร์ชันที่ได้รับการปรับปรุงสำหรับภาพถ่ายบนเว็บพร้อมการรักษาพื้นผิวและการลดสิ่งแปลกปลอมที่ดีขึ้น เพิ่มสเกล 2 เท่าด้วยเทคโนโลยี Dual Aggregation Transformer รักษาความคมชัดและรายละเอียดที่เป็นธรรมชาติ การขยายขนาด 3 เท่าโดยใช้สถาปัตยกรรมหม้อแปลงขั้นสูง เหมาะสำหรับความต้องการในการขยายระดับปานกลาง การเพิ่มสเกลคุณภาพสูง 4 เท่าด้วยเครือข่ายหม้อแปลงที่ล้ำสมัย ช่วยรักษารายละเอียดเล็กๆ น้อยๆ ในสเกลที่ใหญ่ขึ้น ลบภาพเบลอ/นอยส์ และความสั่นออกจากภาพถ่าย วัตถุประสงค์ทั่วไป แต่ดีที่สุดในภาพถ่าย คืนค่าอิมเมจคุณภาพต่ำโดยใช้หม้อแปลง Swin2SR ซึ่งได้รับการปรับให้เหมาะสมสำหรับการย่อยสลาย BSRGAN เหมาะสำหรับการแก้ไขส่วนบีบอัดขนาดใหญ่และเพิ่มรายละเอียดในระดับ 4x การเพิ่มสเกล 4 เท่าด้วยหม้อแปลง SwinIR ที่ได้รับการฝึกเรื่องการย่อยสลาย BSRGAN ใช้ GAN เพื่อให้พื้นผิวคมชัดขึ้นและรายละเอียดที่เป็นธรรมชาติมากขึ้นในภาพถ่ายและฉากที่ซับซ้อน เส้นทาง รวม PDF รวมไฟล์ PDF หลายไฟล์ไว้ในเอกสารเดียว ลำดับไฟล์ หน้า แยก PDF แยกหน้าเฉพาะจากเอกสาร PDF หมุน PDF แก้ไขการวางแนวหน้าอย่างถาวร หน้า จัดเรียง PDF ใหม่ ลากและวางหน้าเพื่อเรียงลำดับใหม่ กดค้างและลากหน้า หมายเลขหน้า เพิ่มการกำหนดหมายเลขให้กับเอกสารของคุณโดยอัตโนมัติ รูปแบบฉลาก PDF เป็นข้อความ (OCR) แยกข้อความธรรมดาออกจากเอกสาร PDF ของคุณ วางซ้อนข้อความที่กำหนดเองสำหรับการสร้างแบรนด์หรือความปลอดภัย ลายเซ็น เพิ่มลายเซ็นอิเล็กทรอนิกส์ของคุณลงในเอกสารใดๆ ซึ่งจะใช้เป็นลายเซ็น ปลดล็อค PDF ลบรหัสผ่านออกจากไฟล์ที่ได้รับการป้องกัน ป้องกัน PDF รักษาความปลอดภัยเอกสารของคุณด้วยการเข้ารหัสที่รัดกุม ความสำเร็จ ปลดล็อค PDF แล้ว คุณสามารถบันทึกหรือแชร์ได้ ซ่อมแซม PDF พยายามแก้ไขเอกสารที่เสียหายหรืออ่านไม่ได้ ระดับสีเทา แปลงรูปภาพที่ฝังในเอกสารทั้งหมดให้เป็นระดับสีเทา บีบอัด PDF ปรับขนาดไฟล์เอกสารของคุณให้เหมาะสมเพื่อการแชร์ที่ง่ายขึ้น ImageToolbox สร้างตารางอ้างอิงโยงภายในใหม่ และสร้างโครงสร้างไฟล์ใหม่ตั้งแต่ต้น ซึ่งสามารถคืนค่าการเข้าถึงไฟล์จำนวนมากที่ \\"ไม่สามารถเปิดได้\\" เครื่องมือนี้แปลงรูปภาพเอกสารทั้งหมดเป็นโทนสีเทา เหมาะสำหรับการพิมพ์และลดขนาดไฟล์ ข้อมูลเมตา แก้ไขคุณสมบัติของเอกสารเพื่อความเป็นส่วนตัวที่ดีขึ้น แท็ก ผู้ผลิต ผู้เขียน คำหลัก ผู้สร้าง ความเป็นส่วนตัวอย่างล้ำลึก ล้างข้อมูลเมตาที่มีอยู่ทั้งหมดสำหรับเอกสารนี้ หน้าหนังสือ OCR ลึก แยกข้อความออกจากเอกสารและจัดเก็บไว้ในไฟล์ข้อความเดียวโดยใช้เครื่องมือ Tesseract ไม่สามารถลบหน้าทั้งหมดได้ ลบหน้า PDF ลบหน้าเฉพาะออกจากเอกสาร PDF แตะเพื่อลบ ด้วยตนเอง ครอบตัด PDF ครอบตัดหน้าเอกสารเป็นขอบเขตใดก็ได้ แผ่ PDF ทำให้ PDF ไม่สามารถแก้ไขได้โดยการแรสเตอร์หน้าเอกสาร ไม่สามารถสตาร์ทกล้องได้ โปรดตรวจสอบการอนุญาตและให้แน่ใจว่าแอปอื่นไม่ได้ใช้งานอยู่ แยกรูปภาพ แยกภาพที่ฝังอยู่ใน PDF ด้วยความละเอียดดั้งเดิม ไฟล์ PDF นี้ไม่มีภาพที่ฝังอยู่ เครื่องมือนี้จะสแกนทุกหน้าและกู้คืนรูปภาพต้นฉบับคุณภาพเต็ม ซึ่งเหมาะสำหรับการบันทึกต้นฉบับจากเอกสาร วาดลายเซ็น ปากกา พาราม ใช้ลายเซ็นของตนเองเป็นรูปภาพเพื่อวางบนเอกสาร ซิป PDF แยกเอกสารตามช่วงเวลาที่กำหนดและแพ็คเอกสารใหม่ลงในไฟล์ zip ช่วงเวลา พิมพ์ PDF เตรียมเอกสารสำหรับการพิมพ์ด้วยขนาดหน้าที่กำหนดเอง จำนวนหน้าต่อแผ่น ปฐมนิเทศ ขนาดหน้า ขอบ บลูม เข่าอ่อน ปรับให้เหมาะสมสำหรับอะนิเมะและการ์ตูน การลดขนาดอย่างรวดเร็วด้วยสีที่เป็นธรรมชาติที่ได้รับการปรับปรุงและส่วนแปลกปลอมน้อยลง สไตล์เหมือน Samsung One UI 7 ป้อนสัญลักษณ์ทางคณิตศาสตร์พื้นฐานที่นี่เพื่อคำนวณค่าที่ต้องการ (เช่น (5+5)*10) นิพจน์ทางคณิตศาสตร์ เลือกได้สูงสุด %1$s ภาพ เก็บวันที่และเวลา เก็บแท็ก exif ที่เกี่ยวข้องกับวันที่และเวลาไว้เสมอ โดยทำงานโดยไม่ขึ้นอยู่กับตัวเลือก Keep exif สีพื้นหลังสำหรับรูปแบบอัลฟ่า เพิ่มความสามารถในการตั้งค่าสีพื้นหลังสำหรับรูปแบบภาพทุกรูปแบบด้วยการรองรับอัลฟ่า เมื่อปิดใช้งานแล้วจะใช้ได้กับรูปแบบที่ไม่ใช่อัลฟ่าเท่านั้น เปิดโครงการ แก้ไขโปรเจ็กต์ Image Toolbox ที่บันทึกไว้ก่อนหน้านี้ต่อไป ไม่สามารถเปิดโปรเจ็กต์กล่องเครื่องมือรูปภาพได้ โครงการ Image Toolbox ไม่มีข้อมูลโครงการ โครงการกล่องเครื่องมือรูปภาพเสียหาย เวอร์ชันโปรเจ็กต์กล่องเครื่องมือรูปภาพที่ไม่รองรับ: %1$d บันทึกโครงการ จัดเก็บเลเยอร์ พื้นหลัง และประวัติการแก้ไขในไฟล์โปรเจ็กต์ที่แก้ไขได้ ไม่สามารถเปิดได้ เขียนเป็น PDF ที่ค้นหาได้ จดจำข้อความจากชุดรูปภาพและบันทึก PDF ที่ค้นหาได้ด้วยรูปภาพและเลเยอร์ข้อความที่เลือกได้ เลเยอร์อัลฟ่า พลิกแนวนอน พลิกแนวตั้ง ล็อค เพิ่มเงา สีเงา เรขาคณิตข้อความ ยืดหรือเอียงข้อความเพื่อให้มีสไตล์ที่คมชัดยิ่งขึ้น สเกล X สกิว เอ็กซ์ ลบคำอธิบายประกอบ ลบประเภทคำอธิบายประกอบที่เลือก เช่น ลิงก์ ความคิดเห็น ไฮไลต์ รูปร่าง หรือฟิลด์แบบฟอร์มออกจากหน้า PDF ไฮเปอร์ลิงก์ ไฟล์แนบ เส้น ป๊อปอัป แสตมป์ รูปร่าง หมายเหตุข้อความ มาร์กอัปข้อความ ฟิลด์แบบฟอร์ม มาร์กอัป ไม่ทราบ คำอธิบายประกอบ ยกเลิกการจัดกลุ่ม เพิ่มเงาเบลอด้านหลังเลเยอร์ด้วยสีและออฟเซ็ตที่กำหนดค่าได้ ================================================ FILE: core/resources/src/main/res/values-tr/strings.xml ================================================ Bir şeyler ters gitti: %1$s Boyut: %1$s Yükleniyor… Resim önizlemek için çok büyük, ancak yine de kaydedilmeye çalışılacak Başlamak için bir resim seçin Genişlik: %1$s Yükseklik: %1$s Kalite Uzantı Yeniden boyutlandırma türü Belirgin Esnek Resim Seç Uygulamayı kapatmak istediğinizden emin misiniz? Uygulama kapatılıyor Kal Kapat Resmi sıfırla Resimdeki değişiklikler başlangıç değerlerine geri alınacak Değerler başarıyla sıfırlandı Sıfırla Bir şeyler ters gitti Uygulamayı yeniden başlat Panoya kopyalandı İstisna EXIF Düzenle TAMAM EXIF verisi bulunamadı Etiket ekle Kaydet Temizle EXIF verilerini temizle İptal Tüm resim EXIF verileri silinecektir. Bu işlem geri alınamaz! Ön Ayarlar Kırp Kaydediliyor Şimdi çıkarsanız kaydedilmemiş tüm değişiklikler kaybolacak Kaynak Kodu En son güncellemeleri alın, sorunları tartışın ve daha fazlası Tekli Düzenleme Tek bir resmi değiştirin, yeniden boyutlandırın ve düzenleyin Renk Seçici Resimden renk seçin, kopyalayın veya paylaşın Resim Renk Renk kopyalandı Resmi herhangi bir sınıra göre kırpın Sürüm EXIF verilerini koru Resimler: %d Önizlemeyi değiştir Kaldır Verilen resimden bir renk paleti örneği oluşturun Palet Oluştur Palet Güncelle Yeni sürüm %1$s Desteklenmeyen tür: %1$s Verilen resim için palet oluşturulamıyor Orijinal Çıktı klasörü Varsayılan Özel Belirtilmemiş Cihaz depolama alanı Ağırlığa Göre Yeniden Boyutlandır KB cinsinden maksimum boyut Bir resmi KB cinsinden verilen boyuta göre yeniden boyutlandırın Karşılaştır Verilen iki resmi karşılaştırın Başlamak için iki resim seçin Resimleri seçin Ayarlar Gece Modu Karanlık Aydınlık Sistem Dinamik renkler Özelleştirme Resimden renk alımına izin ver Etkinleştirilirse, düzenlemek için bir resim seçtiğinizde uygulama renkleri bu resme uyarlanır Dil Amoled modu Etkinleştirilirse, yüzeylerin rengi gece modunda mutlak siyaha ayarlanır Renk şeması Kırmızı Yeşil Mavi Geçerli bir aRGB renk kodu yapıştırın Yapıştırılacak bir şey yok Dinamik renkler açıkken uygulamanın renk şeması değiştirilemez Uygulama teması seçilen renge göre oluşturulacaktır Uygulama hakkında Güncelleme bulunamadı Sorun takipçisi Hata raporlarını ve özellik isteklerini buraya gönderin Çeviriye yardım et Çeviri hatalarını düzeltin veya projeyi başka dillere yerelleştirin Sorgunuza göre hiçbir şey bulunamadı Burada ara Etkinleştirilirse, uygulama renkleri duvar kağıdı renklerine uyarlanacaktır %d resim kaydedilemedi E-posta Birincil Üçüncül İkincil Kenarlık kalınlığı Yüzey Değerler Ekle İzin İzin Ver Uygulamanın çalışabilmesi için resimleri kaydetmek üzere depolama alanınıza erişmesi gerekir, bu zorunludur. Lütfen bir sonraki iletişim kutusunda izin verin. Uygulamanın çalışması için bu izne ihtiyacı var, lütfen manuel olarak izin verin Harici depolama Monet renkleri Bu uygulama tamamen ücretsizdir, ancak proje gelişimini desteklemek isterseniz buraya tıklayabilirsiniz FAB hizalaması Güncellemeleri kontrol et Etkinleştirilirse, uygulama başlangıcında size güncelleme iletişim kutusu gösterilir Resim yakınlaştırma Paylaş Ön Ek Dosya adı Emoji Ana ekranda hangi emojinin gösterileceğini seçin Dosya boyutu ekle Etkinleştirilirse, kaydedilen resmin genişliğini ve yüksekliğini çıktı dosyasının adına ekler EXIF Sil Herhangi bir resim setinden EXIF meta verilerini silin Resim Önizleme Her tür resmi önizleyin: GIF, SVG ve benzeri Resim kaynağı Fotoğraf seçici Galeri Dosya gezgini Ekranın altında görünen, yalnızca Android 12+ üzerinde çalışabilen modern Android fotoğraf seçicisi. EXIF meta verilerini almada sorunları var Basit galeri resim seçici. Sadece medya seçimi sağlayan bir uygulamanız varsa çalışır Resim seçmek için GetContent niyetini kullanın. Her yerde çalışır, ancak bazı cihazlarda seçilen resimleri almada sorunlar yaşadığı bilinmektedir. Bu benim hatam değil. Seçeneklerin düzenlenmesi Düzenle Sıra Ana ekrandaki araçların sırasını belirler Emoji sayısı sıraNumarası orijinalDosyaAdı Orijinal dosya adını ekle Etkinleştirilirse, çıktı resminin adına orijinal dosya adını ekler Sıra numarasını değiştir Etkinleştirilirse, toplu işlem kullanıyorsanız standart zaman damgasını resim sıra numarasıyla değiştirir Fotoğraf seçici resim kaynağı seçiliyken orijinal dosya adı ekleme çalışmaz Web\'den Resim Yükleme İnternetten herhangi bir resmi önizlemek, yakınlaştırmak, düzenlemek ve isterseniz kaydetmek için yükleyin. Resim yok Resim bağlantısı Doldur Sığdır İçerik ölçeği Resimleri verilen yükseklik ve genişliğe göre yeniden boyutlandırır. Resimlerin en boy oranı değişebilir. Resimleri uzun kenarı verilen yükseklik veya genişliğe göre yeniden boyutlandırır. Tüm boyut hesaplamaları kaydettikten sonra yapılacaktır. Resimlerin en boy oranı korunacaktır. Parlaklık Kontrast Ton Doygunluk Filtre ekle Filtre Resimlere filtre zincirleri uygulayın Filtreler Işık Renk filtresi Alfa Pozlama Beyaz dengesi Sıcaklık Renk tonu Monokrom Gama Açık tonlar ve gölgeler Açık Tonlar Gölgeler Pus Efekt Mesafe Eğim Keskinleştir Sepya Negatif Solarizasyon Canlılık Siyah Beyaz Çapraz Tarama Aralık Çizgi genişliği Sobel kenar Bulanıklaştır Yarım Ton CGA renk uzayı Gauss bulanıklığı Kutu bulanıklığı Bilateral bulanıklık Kabartma Laplacian Vinyet Başlangıç Bitiş Kuwahara yumuşatma Yığın bulanıklığı Yarıçap Ölçek Bozulma Açı Girdap Şişirme Genişleme Küre kırılması Kırılma indisi Cam küre kırılması Renk matrisi Opaklık Sınırlara Göre Yeniden Boyutlandır En boy oranını koruyarak resimleri verilen yükseklik ve genişliğe göre yeniden boyutlandırın Taslak Eşik Kuantalama seviyeleri Yumuşak çizgi film Çizgi film Posterleştir Maksimum olmayan bastırma Zayıf piksel dahil etme Arama Tablosu (Lookup) Konvolüsyon 3x3 RGB filtresi Yanlış Renk İlk renk İkinci renk Yeniden sırala Hızlı bulanıklık Bulanıklık boyutu Bulanıklık merkezi x Bulanıklık merkezi y Yakınlaştırma bulanıklığı Renk dengesi Parlaklık eşiği Dosyalar uygulamasını devre dışı bıraktınız, bu özelliği kullanmak için etkinleştirin Çiz Bir eskiz defterindeki gibi resmin üzerine çizin veya doğrudan arka planın üzerine çizin Boya rengi Boya alfa değeri Resim üzerine çiz Bir resim seçin ve üzerine bir şeyler çizin Arka plan üzerine çiz Bir arka plan rengi seçin ve üzerine çizin Arka plan rengi Şifreleme Mevcut çeşitli kripto algoritmalarına dayanarak herhangi bir dosyayı (sadece resmi değil) şifreleyin ve şifresini çözün Dosya Seç Şifrele Şifreyi Çöz Başlamak için bir dosya seçin Şifre Çözme Şifreleme Anahtar Dosya işlendi Bu dosyayı cihazınıza kaydedin veya istediğiniz yere koymak için paylaşma eylemini kullanın Özellikler Uygulama Uyumluluk Dosyaların parola tabanlı şifrelenmesi. İşlenen dosyalar seçilen dizinde saklanabilir veya paylaşılabilir. Şifresi çözülen dosyalar da doğrudan açılabilir. AES-256, GCM modu, dolgusuz, varsayılan olarak 12 bayt rastgele IV. İhtiyaç duyulan algoritmayı seçebilirsiniz. Anahtarlar 256-bit SHA-3 hash olarak kullanılır Dosya boyutu Maksimum dosya boyutu Android işletim sistemi ve mevcut bellek tarafından kısıtlanmıştır ve cihaza bağlıdır. \nLütfen unutmayın: bellek, depolama alanı değildir. Lütfen diğer dosya şifreleme yazılımları veya hizmetleriyle uyumluluğun garanti edilmediğini unutmayın. Biraz farklı bir anahtar işlemi veya şifre yapılandırması uyumsuzluğa neden olabilir. Geçersiz parola veya seçilen dosya şifrelenmemiş Resmi verilen genişlik ve yükseklikle kaydetmeye çalışmak bellek yetersizliği hatasına neden olabilir. Bunu kendi sorumluluğunuzda yapın. Önbellek Önbellek boyutu %1$s bulundu Otomatik önbellek temizleme Etkinleştirilirse uygulama önbelleği uygulama başlangıcında temizlenir Oluştur Araçlar Seçenekleri türe göre gruplandır Ana ekrandaki seçenekleri özel bir liste düzenlemesi yerine türlerine göre gruplandırır Seçenek gruplaması etkinken düzenleme değiştirilemez Ekran görüntüsünü düzenle İkincil özelleştirme Ekran Görüntüsü Yedek seçenek Atla Kopyala %1$s modunda kaydetme kararsız olabilir, çünkü bu kayıpsız bir formattır 125 ön ayarını seçtiyseniz, resim orijinal resmin %125 boyutunda kaydedilecektir. Eğer 50 ön ayarını seçerseniz, resim %50 boyutunda kaydedilir Buradaki ön ayar, çıktı dosyasının yüzdesini belirler, yani 5 MB\'lık bir resimde %50 ön ayarını seçerseniz, kaydettikten sonra 2,5 MB\'lık bir resim elde edersiniz Dosya adını rastgele yap Etkinleştirilirse çıktı dosya adı tamamen rastgele olacaktır %1$s klasörüne %2$s adıyla kaydedildi %1$s klasörüne kaydedildi Telegram sohbeti Uygulamayı tartışın ve diğer kullanıcılardan geri bildirim alın. Ayrıca orada beta güncellemeleri ve bilgiler alabilirsiniz. Kırpma maskesi En Boy Oranı Verilen resimden maske oluşturmak için bu maske türünü kullanın, alfa kanalına sahip OLMASI gerektiğini unutmayın Yedekle ve Geri Yükle Yedekle Geri Yükle Uygulama ayarlarınızı bir dosyaya yedekleyin Uygulama ayarlarını önceden oluşturulmuş bir dosyadan geri yükleyin Bozuk dosya veya yedekleme değil Ayarlar başarıyla geri yüklendi Bana Ulaşın Bu, ayarlarınızı varsayılan değerlere geri döndürecektir. Yukarıda bahsedilen yedekleme dosyası olmadan bu işlemin geri alınamayacağını unutmayın. Sil Seçili renk şemasını silmek üzeresiniz. Bu işlem geri alınamaz Şemayı sil Yazı Tipi Metin Yazı tipi ölçeği Varsayılan Büyük yazı tipi ölçekleri kullanmak, düzeltilmeyecek olan kullanıcı arayüzü hatalarına ve sorunlarına neden olabilir. Dikkatli kullanın. Aa Bb Cc Çç Dd Ee Ff Gg Ğğ Hh Ii İi Jj Kk Ll Mm Nn Oo Öö Pp Qq Rr Ss Şş Tt Uu Üü Vv Ww Xx Yy Zz 0123456789 !? Duygular Yiyecek ve İçecek Doğa ve Hayvanlar Nesneler Semboller Emojiyi etkinleştir Seyahat ve Yerler Aktiviteler Arka Plan Temizleyici Çizerek veya Otomatik seçeneğini kullanarak resimden arka planı kaldırın Resmi kırp Orijinal resim meta verileri korunacaktır Resmin etrafındaki şeffaf boşluklar kırpılacaktır Arka planı otomatik sil Resmi geri yükle Silme modu Arka planı sil Arka planı geri yükle Bulanıklık yarıçapı Damlatıcı Çizim modu Sorun Oluştur Hay aksi… Bir şeyler ters gitti. Aşağıdaki seçenekleri kullanarak bana yazabilirsiniz, bir çözüm bulmaya çalışacağım Yeniden Boyutlandır ve Dönüştür Verilen resimlerin boyutunu değiştirin veya onları başka formatlara dönüştürün. Tek bir resim seçilirse EXIF meta verileri de burada düzenlenebilir. Maksimum renk sayısı Bu, uygulamanın çökme raporlarını otomatik olarak toplamasına olanak tanır Analiz Anonim uygulama kullanım istatistiklerinin toplanmasına izin ver Şu anda, %1$s formatı yalnızca Android\'de EXIF meta verilerinin okunmasına izin vermektedir. Çıktı resmi kaydedildiğinde hiçbir meta veriye sahip olmayacaktır. Efor %1$s değeri hızlı bir sıkıştırma anlamına gelir ve nispeten büyük bir dosya boyutuyla sonuçlanır. %2$s daha yavaş bir sıkıştırma anlamına gelir ve daha küçük bir dosyayla sonuçlanır. Bekle Kaydetme neredeyse tamamlandı. Şimdi iptal etmek yeniden kaydetmeyi gerektirecektir. Güncellemeler Beta sürümlerine izin ver Etkinleştirilirse güncelleme kontrolü beta uygulama sürümlerini de içerecektir Ok Çiz Etkinleştirilirse çizim yolu bir ok işareti olarak temsil edilecektir Fırça yumuşaklığı Resimler, girilen boyuta göre merkezden kırpılacaktır. Resim girilen boyutlardan küçükse, tuval verilen arka plan rengiyle genişletilecektir. Bağış Resim Birleştirme Verilen resimleri birleştirerek büyük bir resim elde edin En az 2 resim seçin Çıktı resmi ölçeği Resim Yönü Yatay Dikey Küçük resimleri büyüğe ölçekle Etkinleştirilirse küçük resimler dizideki en büyük resme göre ölçeklenecektir Resimlerin sırası Normal Kenarları bulanıklaştır Etkinleştirilirse, resmin etrafındaki boşlukları doldurmak için tek bir renk yerine orijinal resmin altına bulanık kenarlar çizer Pikselleştirme Gelişmiş Pikselleştirme Konturlu Pikselleştirme Gelişmiş Elmas Pikselleştirme Elmas Pikselleştirme Daire Pikselleştirme Gelişmiş Daire Pikselleştirme Rengi Değiştir Tolerans Değiştirilecek Renk Hedef Renk Kaldırılacak Renk Rengi Kaldır Yeniden Kodla Piksel Boyutu Çizim yönünü kilitle Çizim modunda etkinleştirilirse ekran dönmez Güncellemeleri denetle Palet stili Tonal Nokta Nötr Canlı Etkileyici Gökkuşağı Meyve Salatası Sadakat İçerik Varsayılan palet stili, dört rengin tamamını özelleştirmenize olanak tanır, diğerleri yalnızca anahtar rengi ayarlamanıza izin verir Monokromdan biraz daha kromatik bir stil Gürültülü bir tema, Birincil palet için renklilik maksimumdur, diğerleri için artırılmıştır Eğlenceli bir tema - kaynak rengin tonu temada görünmez Monokrom bir tema, renkler tamamen siyah/beyaz/gri Kaynak rengi Scheme.primaryContainer\'a yerleştiren bir şema İçerik şemasına çok benzeyen bir şema Bu güncelleme denetleyicisi, yeni bir güncelleme olup olmadığını kontrol etmek amacıyla GitHub\'a bağlanacaktır Dikkat Solan Kenarlar Devre Dışı Her ikisi de Renkleri Ters Çevir Etkinleştirilirse tema renklerini negatif olanlarla değiştirir Ara Ana ekranda mevcut tüm araçlar arasında arama yapma yeteneğini etkinleştirir PDF Araçları PDF dosyalarıyla çalışın: Önizleyin, bir grup resme dönüştürün veya verilen resimlerden bir tane oluşturun PDF Önizle PDF\'den Resimlere Resimlerden PDF\'e Basit PDF önizlemesi PDF\'yi verilen çıktı formatında Resimlere dönüştürün Verilen Resimleri çıktı PDF dosyasına paketleyin Maske Filtresi Verilen maskelenmiş alanlara filtre zincirleri uygulayın, her maske alanı kendi filtre setini belirleyebilir Maskeler Maske Ekle Maske %d Maske Rengi Maske önizlemesi Çizilen filtre maskesi, size yaklaşık sonucu göstermek için oluşturulacaktır Ters Doldurma Türü Etkinleştirilirse, varsayılan davranış yerine maskelenmemiş alanların tümü filtrelenecektir Seçili filtre maskesini silmek üzeresiniz. Bu işlem geri alınamaz Maskeyi sil Tam Filtre Verilen resimlere veya tek bir resme herhangi bir filtre zinciri uygulayın Başlangıç Merkez Bitiş Basit Varyantlar Vurgulayıcı Neon Kalem Gizlilik Bulanıklığı Yarı saydam keskinleştirilmiş vurgulayıcı yolları çizin Çizimlerinize parlayan bir efekt ekleyin Varsayılan olan, en basiti - sadece renk Gizlemek istediğiniz her şeyi güvence altına almak için çizilen yolun altındaki resmi bulanıklaştırır Gizlilik bulanıklığına benzer, ancak bulanıklaştırmak yerine pikselleştirir Konteynerler Konteynerlerin arkasına bir gölge çizin Kaydırıcılar Anahtarlar FAB\'lar Düğmeler Kaydırıcıların arkasına bir gölge çizin Anahtarların arkasına bir gölge çizin Hareketli eylem düğmelerinin arkasına bir gölge çizin Düğmelerin arkasına bir gölge çizin Uygulama çubukları Uygulama çubuklarının arkasına bir gölge çizin %1$s - %2$s aralığında değer Otomatik Döndür Sınır kutusunun resim yönüne uyarlanmasına izin verir Çizim Yolu Modu Çift Çizgili Ok Serbest Çizim Çift Ok Çizgi Ok Ok Çizgi Yolu girdi değeri olarak çizer Başlangıç noktasından bitiş noktasına bir çizgi olarak yol çizer Başlangıç noktasından bitiş noktasına bir çizgi olarak işaret eden bir ok çizer Verilen bir yoldan işaret eden bir ok çizer Başlangıç noktasından bitiş noktasına bir çizgi olarak çift işaret eden bir ok çizer Verilen bir yoldan çift işaret eden bir ok çizer Dış Çizgili Oval Dış Çizgili Dikdörtgen Oval Dikdörtgen Başlangıç noktasından bitiş noktasına dikdörtgen çizer Başlangıç noktasından bitiş noktasına oval çizer Başlangıç noktasından bitiş noktasına dış çizgili oval çizer Başlangıç noktasından bitiş noktasına dış çizgili dikdörtgen çizer Kement Verilen yolla kapalı, doldurulmuş bir yol çizer Serbest Yatay Izgara Dikey Izgara Birleştirme Modu Satır Sayısı Sütun Sayısı \"%1$s\" dizini bulunamadı, varsayılan dizine geçtik, lütfen dosyayı tekrar kaydedin Pano Otomatik sabitle Etkinleştirilirse kaydedilen resmi otomatik olarak panoya ekler Titreşim Titreşim Şiddeti Dosyaların üzerine yazmak için \"Gezgin\" resim kaynağını kullanmanız gerekir, resimleri yeniden seçmeyi deneyin, resim kaynağını gerekli olana değiştirdik Dosyaların Üzerine Yaz Orijinal dosya, seçilen klasöre kaydetmek yerine yenisiyle değiştirilir, bu seçenek resim kaynağının \"Gezgin\" veya GetContent olmasını gerektirir, bu seçeneği değiştirdiğinizde otomatik olarak ayarlanacaktır Boş Son Ek Ölçekleme modu Bilinear Catmull Bicubic Hann Hermite Lanczos Mitchell En Yakın Spline Temel Varsayılan Değer Doğrusal (veya iki boyutta bilinear) enterpolasyon genellikle bir resmin boyutunu değiştirmek için iyidir, ancak ayrıntıların istenmeyen bir şekilde yumuşamasına neden olur ve hala biraz pürüzlü olabilir Daha iyi ölçekleme yöntemleri arasında Lanczos yeniden örnekleme ve Mitchell-Netravali filtreleri bulunur Boyutu artırmanın daha basit yollarından biri, her pikseli aynı renkteki bir dizi pikselle değiştirmektir Hemen hemen tüm uygulamalarda kullanılan en basit Android ölçekleme modu Bir dizi kontrol noktasını düzgün bir şekilde enterpole etmek ve yeniden örneklemek için bir yöntem, genellikle bilgisayar grafiklerinde pürüzsüz eğriler oluşturmak için kullanılır Spektral sızıntıyı en aza indirmek ve bir sinyalin kenarlarını daraltarak frekans analizinin doğruluğunu artırmak için sinyal işlemede sıklıkla uygulanan bir pencereleme fonksiyonu Pürüzsüz ve sürekli bir eğri oluşturmak için bir eğri segmentinin uç noktalarındaki değerleri ve türevleri kullanan matematiksel bir enterpolasyon tekniği Piksel değerlerine ağırlıklı bir sinc fonksiyonu uygulayarak yüksek kaliteli enterpolasyonu koruyan bir yeniden örnekleme yöntemi Ölçeklenmiş resimde keskinlik ve kenar yumuşatma arasında bir denge sağlamak için ayarlanabilir parametrelere sahip bir konvolüsyon filtresi kullanan bir yeniden örnekleme yöntemi Esnek ve sürekli şekil temsili sağlayarak bir eğriyi veya yüzeyi pürüzsüz bir şekilde enterpole etmek ve yaklaştırmak için parçalı tanımlanmış polinom fonksiyonları kullanır Sadece Panoya Kopyala Depolama alanına kaydetme yapılmayacak ve resim yalnızca panoya konulmaya çalışılacaktır Fırça, silmek yerine arka planı geri yükleyecektir OCR (metin tanıma) Verilen resimden metin tanıyın, 120+ dil desteklenir Resimde metin yok veya uygulama bulamadı Doğruluk: %1$s Tanıma Türü Hızlı Standart En İyi Veri Yok Tesseract OCR\'nin düzgün çalışması için ek eğitim verilerinin (%1$s) cihazınıza indirilmesi gerekir.\n%2$s verilerini indirmek istiyor musunuz? İndir Bağlantı yok, kontrol edin ve eğitim modellerini indirmek için tekrar deneyin İndirilen Diller Mevcut Diller Segmentasyon Modu Piksel Anahtarı Kullan Google Pixel benzeri bir anahtar kullanır Orijinal hedefe %1$s adıyla dosyanın üzerine yazıldı Büyüteç Daha iyi erişilebilirlik için çizim modlarında parmağın üstünde büyüteci etkinleştirir Başlangıç değerini zorla EXIF aracının başlangıçta işaretli olmasını zorlar Çoklu Dil İzni Kaydır Yan Yana Dokunarak Değiştir Şeffaflık Uygulamayı Oyla Oyla Bu uygulama tamamen ücretsizdir, daha da büyümesini isterseniz, lütfen projeyi Github\'da yıldızlayın 😄 Sadece Yön ve Yazı Tipi Tespiti Otomatik Yön ve Yazı Tipi Tespiti Sadece Otomatik Otomatik Tek Sütun Tek blok dikey metin Tek blok Tek satır Tek kelime Daire kelime Tek karakter Seyrek metin Seyrek metin Yön ve Yazı Tipi Tespiti Ham satır \"%1$s\" dili OCR eğitim verilerini tüm tanıma türleri için mi, yoksa sadece seçili olan (%2$s) için mi silmek istiyorsunuz? Mevcut Tümü Gradyan Oluşturucu Özelleştirilmiş renkler ve görünüm türü ile verilen çıktı boyutunda gradyan oluşturun Doğrusal Radyal Süpürme Gradyan Türü Merkez X Merkez Y Döşeme Modu Tekrarlanan Ayna Sıkıştır Çıkartma Renk Durakları Renk Ekle Özellikler Parlaklık Zorlaması Ekran Gradyan Kaplaması Verilen resimlerin üzerine herhangi bir gradyan oluşturun Dönüşümler Kamera Kamera ile bir fotoğraf çekin. Bu resim kaynağından yalnızca bir resim alınabileceğini unutmayın Filigran Ekleme Resimleri özelleştirilebilir metin/resim filigranları ile kaplayın Filigranı tekrarla Verilen konumda tek bir filigran yerine resim üzerinde tekrarlar Ofset X Ofset Y Filigran Türü Bu resim filigranlama için desen olarak kullanılacaktır Metin Rengi Kaplama Modu GIF Araçları Resimleri GIF resmine dönüştürün veya verilen GIF resminden kareler çıkarın GIF\'den resimlere GIF dosyasını bir dizi resme dönüştürün Bir dizi resmi GIF dosyasına dönüştürün Resimlerden GIF\'e Başlamak için GIF resmi seçin İlk karenin boyutunu kullan Belirtilen boyutu ilk karenin boyutlarıyla değiştir Tekrar Sayısı Kare Gecikmesi milisaniye FPS Kement kullan Silme işlemini gerçekleştirmek için çizim modundaki gibi Kement kullanır Orijinal Resim Önizleme Alfa Değeri Konfeti Kaydetme, paylaşma ve diğer birincil eylemlerde konfeti gösterilecektir Güvenli Mod Son uygulamalarda uygulama içeriğini gizler. Yakalanamaz veya kaydedilemez. Çıkış Şimdi önizlemeden ayrılırsanız, resimleri yeniden eklemeniz gerekecek Titreşim (Dithering) Kuantalayıcı Gri Tonlama Bayer 2x2 Titreşim Bayer 3x3 Titreşim Bayer 4x4 Titreşim Bayer 8x8 Titreşim Floyd Steinberg Titreşim Jarvis Judice Ninke Titreşim Sierra Titreşim İki Sıralı Sierra Titreşim Sierra Lite Titreşim Atkinson Titreşim Stucki Titreşim Burkes Titreşim Yanlış Floyd Steinberg Titreşim Soldan Sağa Titreşim Rastgele Titreşim Basit Eşik Titreşimi Sigma Mekansal Sigma Medyan Bulanıklığı B Spline Esnek ve sürekli şekil temsili için bir eğriyi veya yüzeyi pürüzsüzce enterpole etmek ve yaklaştırmak için parçalı tanımlanmış iki küplü polinom fonksiyonları kullanır Doğal Yığın Bulanıklığı Tilt Shift Glitch (Bozulma) Miktar Tohum Anaglif Gürültü Piksel Sıralama Karıştır Gelişmiş Glitch Kanal Kaydırma X Kanal Kaydırma Y Bozulma Boyutu Bozulma Kaydırma X Bozulma Kaydırma Y Çadır Bulanıklığı Yan Solma Yan Üst Alt Güç Aşındırma Anizotropik Difüzyon Dağılım İletim Yatay Rüzgar Titremesi Hızlı Bilateral Bulanıklık Poisson Bulanıklığı Logaritmik Ton Eşleme ACES Filmik Ton Eşleme Kristalleştir Kontur Rengi Fraktal Cam Genlik Mermer Türbülans Yağlı Boya Su Efekti Boyut Frekans X Frekans Y Genlik X Genlik Y Perlin Bozulması ACES Hill Ton Eşleme Hable Filmik Ton Eşleme Heji-Burgess Ton Eşleme Hız Pus Giderme Omega Renk Matrisi 4x4 Renk Matrisi 3x3 Basit Efektler Polaroid Tritanomali Döteranomali Protanomali Vintage Browni Coda Krom Gece Görüşü Sıcak Soğuk Tritanopi Döteranotopi Protanopi Akromatopsi Anomalisi Akromatopsi Gren Bulanıklaştırmayı Geri Al Pastel Turuncu Pus Pembe Rüya Altın Saat Sıcak Yaz Mor Sis Gün Doğumu Renkli Girdap Yumuşak Bahar Işığı Sonbahar Tonları Lavanta Rüyası Siberpunk Limonata Işığı Spektral Ateş Gece Büyüsü Fantastik Manzara Renk Patlaması Elektrik Gradyanı Karamel Karanlığı Fütüristik Gradyan Yeşil Güneş Gökkuşağı Dünyası Derin Mor Uzay Portalı Kırmızı Girdap Dijital Kod Bokeh Uygulama çubuğu emojisi rastgele değişecektir Rastgele Emojiler Emojiler devre dışı bırakılmışken rastgele emojileri kullanamazsınız Rastgele emojiler etkinken bir emoji seçemezsiniz Eski TV Karıştırma Bulanıklığı Favori Henüz favori filtre eklenmedi Resim Formatı Simgelerin altına seçilen şekilde bir kap ekler Simge Şekli Drago Aldridge Kesme Uchimura Mobius Geçiş Zirve Renk Anomalisi Resimlerin üzerine orijinal hedeflerinde yazıldı Dosyaların üzerine yazma seçeneği etkinken resim formatı değiştirilemez Renk Şeması Olarak Emoji Manuel olarak tanımlanan yerine uygulamanın renk şeması olarak emojinin birincil rengini kullanır Resimden Material You paleti oluşturur Koyu Renkler Aydınlık varyant yerine gece modu renk şemasını kullanır Jetpack Compose kodu olarak kopyala Halka Bulanıklığı Çapraz Bulanıklık Daire Bulanıklığı Yıldız Bulanıklığı Doğrusal Tilt-Shift Kaldırılacak Etiketler APNG Araçları Resimleri APNG resmine dönüştürün veya verilen APNG resminden kareler ayıklayın APNG\'den resimlere APNG dosyasını bir dizi resme dönüştürün Bir dizi resmi APNG dosyasına dönüştürün Resimlerden APNG\'ye Başlamak için APNG resmi seçin Hareket Bulanıklığı Zip Verilen dosyalardan veya resimlerden Zip dosyası oluşturun Sürükleme Kolu Genişliği Konfeti Türü Şenlikli Patlama Yağmur Köşeler JXL Araçları Kalite kaybı olmadan JXL ~ JPEG dönüştürme yapın veya GIF/APNG\'yi JXL animasyonuna dönüştürün JXL\'den JPEG\'e JXL\'den JPEG\'e kayıpsız dönüştürme yapın JPEG\'den JXL\'e kayıpsız dönüştürme yapın JPEG\'den JXL\'e Başlamak için JXL resmi seçin Hızlı Gauss Bulanıklığı 2D Hızlı Gauss Bulanıklığı 3D Hızlı Gauss Bulanıklığı 4D Otomatik Yapıştır Uygulamanın pano verilerini otomatik olarak yapıştırmasına izin verir, böylece ana ekranda görünür ve işleyebilirsiniz Uyumlaştırma Rengi Uyumlaştırma Seviyesi Lanczos Bessel Piksel değerlerine bir Bessel (jinc) fonksiyonu uygulayarak yüksek kaliteli enterpolasyonu koruyan bir yeniden örnekleme yöntemi GIF\'den JXL\'e GIF resimlerini JXL animasyonlu resimlere dönüştürün APNG\'den JXL\'e APNG resimlerini JXL animasyonlu resimlere dönüştürün JXL\'den Resimlere JXL animasyonunu bir dizi resme dönüştürün Resimlerden JXL\'e Bir dizi resmi JXL animasyonuna dönüştürün Davranış Dosya Seçimini Atla Seçilen ekranda mümkünse dosya seçici hemen gösterilecektir Önizlemeleri Oluştur Önizleme oluşturmayı etkinleştirir, bu bazı cihazlarda çökmeleri önlemeye yardımcı olabilir, ayrıca tekli düzenleme seçeneğinde bazı düzenleme işlevlerini devre dışı bırakır Kayıplı Sıkıştırma Kayıpsız yerine dosya boyutunu azaltmak için kayıplı sıkıştırma kullanır Sıkıştırma Türü Sonuçta ortaya çıkan resmin kod çözme hızını kontrol eder, bu, sonuç resminin daha hızlı açılmasına yardımcı olmalıdır, %1$s değeri en yavaş kod çözme anlamına gelirken, %2$s en hızlı anlamına gelir, bu ayar çıktı resminin boyutunu artırabilir Sıralama Tarih Tarih (Ters) İsim İsim (Ters) Kanal Yapılandırması Bugün Dün Gömülü Seçici Image Toolbox\'ın resim seçicisi İzin yok Talep et Çoklu Medya Seç Tek Medya Seç Seç Tekrar dene Ayarları Yatay Modda Göster Bu devre dışı bırakılırsa, yatay modda ayarlar kalıcı olarak görünen bir seçenek yerine her zamanki gibi üst uygulama çubuğundaki düğmede açılır Tam Ekran Ayarları Etkinleştirirseniz, ayarlar sayfası kaydırılabilir bir çekmece sayfası yerine her zaman tam ekran olarak açılır Anahtar Tipi Compose Bir Jetpack Compose Material You anahtarı Bir Material You anahtarı Maks Yeniden Boyutlandırma Çapası Piksel Fluent Fluent tasarım sistemine dayalı bir anahtar Cupertino Cupertino tasarım sistemine dayalı bir anahtar Resimleri SVG\'ye Dönüştür Verilen resimleri SVG resimlerine dönüştürün Örneklenmiş Palet Kullan Bu seçenek etkinleştirilirse kuantalama paleti örneklenecektir Yol Atla Bu aracın büyük resimleri küçültmeden izlemek için kullanılması önerilmez, çökme ve işlem süresinin artmasına neden olabilir Resmi küçült Resim, işlemeden önce daha düşük boyutlara küçültülecektir, bu aracın daha hızlı ve daha güvenli çalışmasına yardımcı olur Minimum Renk Oranı Çizgi Eşiği Kuadratik Eşik Koordinat Yuvarlama Toleransı Yol Ölçeği Özellikleri sıfırla Tüm özellikler varsayılan değerlere ayarlanacaktır, bu işlemin geri alınamayacağını unutmayın Detaylı Varsayılan Çizgi Genişliği Motor Modu Eski LSTM ağı Eski & LSTM Dönüştür Resim gruplarını verilen formata dönüştürün Yeni Klasör Ekle Örnek Başına Bit Sıkıştırma Fotometrik Yorumlama Piksel Başına Örnek Düzlemsel Yapılandırma Y Cb Cr Alt Örnekleme Y Cb Cr Konumlandırma X Çözünürlüğü Y Çözünürlüğü Çözünürlük Birimi Şerit Ofsetleri Şerit Başına Satır Şerit Bayt Sayıları JPEG Değişim Formatı JPEG Değişim Formatı Uzunluğu Aktarım Fonksiyonu Beyaz Nokta Birincil Renklilikler Y Cb Cr Katsayıları Referans Siyah Beyaz Tarih Saat Resim Açıklaması Marka Model Yazılım Sanatçı Telif Hakkı Exif Sürümü Flashpix Sürümü Renk Uzayı Gama Piksel X Boyutu Piksel Y Boyutu Piksel Başına Sıkıştırılmış Bit Üretici Notu Kullanıcı Yorumu İlgili Ses Dosyası Orijinal Tarih Saat Dijitalleştirilmiş Tarih Saat Zaman Ofseti Orijinal Zaman Ofseti Dijitalleştirilmiş Zaman Ofseti Alt Saniye Zamanı Orijinal Alt Saniye Zamanı Dijitalleştirilmiş Alt Saniye Zamanı Pozlama Süresi F Numarası Pozlama Programı Spektral Hassasiyet Fotoğrafik Hassasiyet OECF Hassasiyet Türü Standart Çıkış Hassasiyeti Önerilen Pozlama İndeksi ISO Hızı ISO Hızı Enlem yyy ISO Hızı Enlem zzz Enstantane Hızı Değeri Diyafram Değeri Parlaklık Değeri Pozlama Telafisi Değeri Maksimum Diyafram Değeri Konu Mesafesi Ölçüm Modu Flaş Konu Alanı Odak Uzunluğu Flaş Enerjisi Mekansal Frekans Tepkisi Odak Düzlemi X Çözünürlüğü Odak Düzlemi Y Çözünürlüğü Odak Düzlemi Çözünürlük Birimi Konu Konumu Pozlama İndeksi Algılama Yöntemi Dosya Kaynağı CFA Deseni Özel İşlenmiş Pozlama Modu Beyaz Dengesi Dijital Yakınlaştırma Oranı 35mm Filmdeki Odak Uzunluğu Sahne Yakalama Türü Kazanç Kontrolü Kontrast Doygunluk Keskinlik Cihaz Ayar Açıklaması Konu Mesafe Aralığı Resim Benzersiz Kimliği Kamera Sahibi Adı Gövde Seri Numarası Lens Özellikleri Lens Markası Lens Modeli Lens Seri Numarası GPS Sürüm Kimliği GPS Enlem Referansı GPS Enlem GPS Boylam Referansı GPS Boylam GPS İrtifa Referansı GPS İrtifa GPS Zaman Damgası GPS Uyduları GPS Durumu GPS Ölçüm Modu GPS DOP GPS Hız Referansı GPS Hızı GPS İz Referansı GPS İzi GPS Resim Yönü Referansı GPS Resim Yönü GPS Harita Datumu GPS Hedef Enlem Referansı GPS Hedef Enlem GPS Hedef Boylam Referansı GPS Hedef Boylam GPS Hedef Kerteriz Referansı GPS Hedef Kerteriz GPS Hedef Mesafe Referansı GPS Hedef Mesafe GPS İşleme Yöntemi GPS Alan Bilgisi GPS Tarih Damgası GPS Diferansiyel GPS Y Konumlandırma Hatası Birlikte Çalışabilirlik İndeksi DNG Sürümü Varsayılan Kırpma Boyutu Önizleme Resmi Başlangıcı Önizleme Resmi Uzunluğu En Boy Çerçevesi Sensör Alt Kenarlığı Sensör Sol Kenarlığı Sensör Sağ Kenarlığı Sensör Üst Kenarlığı ISO Verilen yazı tipi ve renkle yol üzerine Metin çizin Yazı Tipi Boyutu Filigran Boyutu Metni Tekrarla Mevcut metin, tek seferlik çizim yerine yolun sonuna kadar tekrarlanacaktır Tire Boyutu Verilen yol boyunca çizmek için seçili resmi kullanın Bu resim, çizilen yolun tekrar eden girişi olarak kullanılacaktır Başlangıç noktasından bitiş noktasına dış çizgili üçgen çizer Başlangıç noktasından bitiş noktasına üçgen çizer Dış Çizgili Üçgen Üçgen Başlangıç noktasından bitiş noktasına çokgen çizer Çokgen Dış Çizgili Çokgen Başlangıç noktasından bitiş noktasına dış çizgili çokgen çizer Köşeler Düzgün Çokgen Çiz Serbest form yerine düzgün olacak bir çokgen çizin Başlangıç noktasından bitiş noktasına yıldız çizer Yıldız Dış Çizgili Yıldız Başlangıç noktasından bitiş noktasına dış çizgili yıldız çizer İç Yarıçap Oranı Düzgün Yıldız Çiz Serbest form yerine düzgün olacak bir yıldız çizin Kenar Yumuşatma Keskin kenarları önlemek için kenar yumuşatmayı etkinleştirir Önizleme Yerine Düzenlemeyi Aç ImageToolbox\'ta açmak (önizlemek) için bir resim seçtiğinizde, önizleme yerine düzenleme seçim sayfası açılacaktır Belge Tarayıcı Belgeleri tarayın ve bunlardan PDF veya ayrı resimler oluşturun Taramayı başlatmak için tıklayın Taramayı Başlat PDF Olarak Kaydet PDF olarak paylaş Aşağıdaki seçenekler PDF için değil, resimleri kaydetmek içindir Histogram Eşitleme HSV Histogram Eşitleme Yüzde Girin Metin Alanı ile girişe izin ver Ön ayar seçiminin arkasında Metin Alanını etkinleştirir, böylece anında girebilirsiniz Renk Uzayını Ölçekle Doğrusal Histogram Eşitleme Pikselleştirme Izgara Boyutu X Izgara Boyutu Y Uyarlanabilir Histogram Eşitleme Uyarlanabilir Histogram Eşitleme LUV Uyarlanabilir Histogram Eşitleme LAB CLAHE CLAHE LAB CLAHE LUV İçeriğe Göre Kırp Çerçeve Rengi Yoksayılacak Renk Şablon Şablon filtresi eklenmemiş Yeni Oluştur Taranan QR kodu geçerli bir filtre şablonu değil QR kodu tara Seçilen dosyanın filtre şablonu verisi yok Şablon Oluştur Şablon Adı Bu resim, bu filtre şablonunu önizlemek için kullanılacaktır Şablon Filtresi QR kodu resmi olarak Dosya olarak Dosya olarak kaydet QR kodu resmi olarak kaydet Şablonu Sil Seçilen şablon filtresini silmek üzeresiniz. Bu işlem geri alınamaz \"%1$s\" (%2$s) adıyla filtre şablonu eklendi Filtre Önizlemesi QR ve Barkod QR kodunu tarayın ve içeriğini alın veya yeni bir tane oluşturmak için dizenizi yapıştırın Kod İçeriği Alandaki içeriği değiştirmek için herhangi bir barkodu tarayın veya seçilen türle yeni bir barkod oluşturmak için bir şeyler yazın QR Açıklaması Min QR kodunu taramak için ayarlardan kamera izni verin Belge Tarayıcıyı taramak için ayarlardan kamera izni verin Kübik B-Spline Hamming Hanning Blackman Welch Kuadrik Gauss Sfenks Bartlett Robidoux Robidoux Keskin Spline 16 Spline 36 Spline 64 Kaiser Bartlett-Hann Kutu Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Kübik enterpolasyon, en yakın 16 pikseli dikkate alarak daha pürüzsüz ölçekleme sağlar ve bilinear\'den daha iyi sonuçlar verir Parçalı tanımlanmış polinom fonksiyonlarını kullanarak bir eğriyi veya yüzeyi pürüzsüzce enterpole eder ve yaklaştırır, esnek ve sürekli şekil temsili sağlar Sinyal işlemede spektral sızıntıyı azaltmak için bir sinyalin kenarlarını daraltan bir pencere fonksiyonu Sinyal işleme uygulamalarında spektral sızıntıyı azaltmak için yaygın olarak kullanılan Hann penceresinin bir varyantı Spektral sızıntıyı en aza indirerek iyi frekans çözünürlüğü sağlayan, sinyal işlemede sıklıkla kullanılan bir pencere fonksiyonu Sinyal işleme uygulamalarında sıklıkla kullanılan, azaltılmış spektral sızıntı ile iyi frekans çözünürlüğü sağlamak için tasarlanmış bir pencere fonksiyonu Enterpolasyon için kuadratik bir fonksiyon kullanan, pürüzsüz ve sürekli sonuçlar sağlayan bir yöntem Gauss fonksiyonu uygulayan, resimlerde gürültüyü azaltmak ve pürüzsüzleştirmek için kullanışlı bir enterpolasyon yöntemi Minimum artefakt ile yüksek kaliteli enterpolasyon sağlayan gelişmiş bir yeniden örnekleme yöntemi Sinyal işlemede spektral sızıntıyı azaltmak için kullanılan üçgen bir pencere fonksiyonu Doğal resim yeniden boyutlandırması için optimize edilmiş, keskinlik ve pürüzsüzlüğü dengeleyen yüksek kaliteli bir enterpolasyon yöntemi Net resim yeniden boyutlandırması için optimize edilmiş Robidoux yönteminin daha keskin bir varyantı 16-tap filtre kullanarak pürüzsüz sonuçlar sağlayan spline tabanlı bir enterpolasyon yöntemi 36-tap filtre kullanarak pürüzsüz sonuçlar sağlayan spline tabanlı bir enterpolasyon yöntemi 64-tap filtre kullanarak pürüzsüz sonuçlar sağlayan spline tabanlı bir enterpolasyon yöntemi Kaiser penceresini kullanan, ana lob genişliği ile yan lob seviyesi arasındaki denge üzerinde iyi kontrol sağlayan bir enterpolasyon yöntemi Bartlett ve Hann pencerelerini birleştiren, sinyal işlemede spektral sızıntıyı azaltmak için kullanılan hibrit bir pencere fonksiyonu En yakın piksel değerlerinin ortalamasını kullanan, genellikle bloklu bir görünüme neden olan basit bir yeniden örnekleme yöntemi Sinyal işleme uygulamalarında iyi frekans çözünürlüğü sağlayarak spektral sızıntıyı azaltmak için kullanılan bir pencere fonksiyonu Minimum artefakt ile yüksek kaliteli enterpolasyon için 2 loblu bir Lanczos filtresi kullanan bir yeniden örnekleme yöntemi Minimum artefakt ile yüksek kaliteli enterpolasyon için 3 loblu bir Lanczos filtresi kullanan bir yeniden örnekleme yöntemi Minimum artefakt ile yüksek kaliteli enterpolasyon için 4 loblu bir Lanczos filtresi kullanan bir yeniden örnekleme yöntemi Jinc fonksiyonunu kullanan, minimum artefakt ile yüksek kaliteli enterpolasyon sağlayan Lanczos 2 filtresinin bir varyantı Jinc fonksiyonunu kullanan, minimum artefakt ile yüksek kaliteli enterpolasyon sağlayan Lanczos 3 filtresinin bir varyantı Jinc fonksiyonunu kullanan, minimum artefakt ile yüksek kaliteli enterpolasyon sağlayan Lanczos 4 filtresinin bir varyantı Hanning EWA Pürüzsüz enterpolasyon ve yeniden örnekleme için Hanning filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Robidoux EWA Yüksek kaliteli yeniden örnekleme için Robidoux filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Blackman EWA Zil sesini en aza indirmek için Blackman filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Kuadrik EWA Pürüzsüz enterpolasyon için Kuadrik filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Robidoux Keskin EWA Daha keskin sonuçlar için Robidoux Keskin filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Lanczos 3 Jinc EWA Azaltılmış örtüşme ile yüksek kaliteli yeniden örnekleme için Lanczos 3 Jinc filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Ginseng Keskinlik ve pürüzsüzlük arasında iyi bir denge ile yüksek kaliteli resim işleme için tasarlanmış bir yeniden örnekleme filtresi Ginseng EWA Gelişmiş resim kalitesi için Ginseng filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Lanczos Keskin EWA Minimum artefakt ile keskin sonuçlar elde etmek için Lanczos Keskin filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Lanczos 4 En Keskin EWA Son derece keskin resim yeniden örnekleme için Lanczos 4 En Keskin filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Lanczos Yumuşak EWA Daha pürüzsüz resim yeniden örnekleme için Lanczos Yumuşak filtresinin Eliptik Ağırlıklı Ortalama (EWA) varyantı Haasn Yumuşak Haasn tarafından pürüzsüz ve artefaktsız resim ölçekleme için tasarlanmış bir yeniden örnekleme filtresi Format Dönüşümü Bir grup resmi bir formattan diğerine dönüştürün Sonsuza Kadar Kapat Resim Yığınlama Resimleri seçilen harmanlama modlarıyla üst üste yığınlayın Resim Ekle Bölme sayısı Clahe HSL Clahe HSV Uyarlanabilir Histogram Eşitleme HSL Uyarlanabilir Histogram Eşitleme HSV Kenar Modu Kırp Sarma Renk Körlüğü Tema renklerini seçilen renk körlüğü varyantına uyarlamak için bir mod seçin Kırmızı ve yeşil tonlarını ayırt etmede zorluk Yeşil ve kırmızı tonlarını ayırt etmede zorluk Mavi ve sarı tonlarını ayırt etmede zorluk Kırmızı tonlarını algılayamama Yeşil tonlarını algılayamama Mavi tonlarını algılayamama Tüm renklere karşı azalmış hassasiyet Tam renk körlüğü, sadece gri tonlarını görme Renk Körlüğü şeması kullanma Renkler temada ayarlandığı gibi olacaktır Sigmoidal Lagrange 2 Pürüzsüz geçişlerle yüksek kaliteli resim ölçekleme için uygun olan 2. dereceden bir Lagrange enterpolasyon filtresi Lagrange 3 Daha iyi doğruluk ve daha pürüzsüz sonuçlar sunan 3. dereceden bir Lagrange enterpolasyon filtresi Lanczos 6 Daha keskin ve daha doğru resim ölçekleme sağlayan 6. dereceden daha yüksek bir Lanczos yeniden örnekleme filtresi Lanczos 6 Jinc Geliştirilmiş resim yeniden örnekleme kalitesi için Jinc fonksiyonu kullanan Lanczos 6 filtresinin bir varyantı Doğrusal Kutu Bulanıklığı Doğrusal Çadır Bulanıklığı Doğrusal Gauss Kutu Bulanıklığı Doğrusal Yığın Bulanıklığı Gauss Kutu Bulanıklığı Doğrusal Hızlı Gauss Bulanıklığı Sonraki Doğrusal Hızlı Gauss Bulanıklığı Doğrusal Gauss Bulanıklığı Boya olarak kullanmak için bir filtre seçin Filtreyi Değiştir Çiziminizde fırça olarak kullanmak için aşağıdaki filtreyi seçin TIFF sıkıştırma şeması Düşük Poli Kum Boyama Resim Bölme Tek bir resmi satırlara veya sütunlara göre bölün Sınırlara Sığdır İstenen davranışı elde etmek için kırpma yeniden boyutlandırma modunu bu parametreyle birleştirin (Kırp/En boy oranına sığdır) Diller başarıyla içe aktarıldı OCR modellerini yedekle İçe Aktar Dışa Aktar Konum Merkez Sol Üst Sağ Üst Sol Alt Sağ Alt Orta Üst Orta Sağ Orta Alt Orta Sol Hedef Resim Palet Aktarımı Gelişmiş Yağlı Boya Basit Eski TV HDR Gotham Basit Taslak Yumuşak Parıltı Renk Posteri Üç Tonlu Üçüncü renk Clahe Oklab Clahe Oklch Clahe Jzazbz Puantiye Kümelenmiş 2x2 Titreşim Kümelenmiş 4x4 Titreşim Kümelenmiş 8x8 Titreşim Yililoma Titreşim Favori seçenek seçilmedi, araçlar sayfasından ekleyin Favorilere Ekle Tamamlayıcı Analog Üçlü Ayrık Tamamlayıcı Dörtlü Kare Analog + Tamamlayıcı Renk Araçları Karıştırın, tonlar oluşturun, gölgeler üretin ve daha fazlası Renk Uyumları Renk Gölgelendirme Varyasyon Açık Tonlar Tonlar Gölgeler Renk Karıştırma Renk Bilgisi Seçili Renk Karıştırılacak Renk Dinamik renkler açıkken monet kullanılamaz 512x512 2D LUT Hedef LUT resmi Amatorka Miss Etikate Yumuşak Zarafet Yumuşak Zarafet Varyantı Palet Aktarım Varyantı 3D LUT Hedef 3D LUT Dosyası (.cube / .CUBE) LUT Ağartma Atlatma Mum Işığı Mavileri Bırak Keskin Kehribar Sonbahar Renkleri Film Stoğu 50 Sisli Gece Kodak Nötr LUT resmi al İlk olarak, favori fotoğraf düzenleme uygulamanızı kullanarak burada elde edebileceğiniz nötr LUT\'a bir filtre uygulayın. Bunun düzgün çalışması için her piksel rengi diğer piksellere bağlı olmamalıdır (örneğin bulanıklık çalışmaz). Hazır olduğunda, yeni LUT resminizi 512*512 LUT filtresi için girdi olarak kullanın Pop Art Selüloit Kahve Altın Orman Yeşilimsi Retro Sarı Bağlantı Önizlemesi Metin alabileceğiniz yerlerde (QRCode, OCR vb.) bağlantı önizlemesi almayı etkinleştirir Bağlantılar ICO dosyaları yalnızca maksimum 256 x 256 boyutunda kaydedilebilir GIF\'ten WEBP\'ye GIF resimlerini WEBP animasyonlu resimlere dönüştürün WEBP Araçları Resimleri WEBP animasyonlu resme dönüştürün veya verilen WEBP animasyonundan kareler ayıklayın WEBP\'den resimlere WEBP dosyasını bir dizi resme dönüştürün Bir dizi resmi WEBP dosyasına dönüştürün Resimlerden WEBP\'ye Başlamak için WEBP resmi seçin Dosyalara tam erişim yok Android\'de resim olarak tanınmayan JXL, QOI ve diğer resimleri görmek için tüm dosyalara erişime izin verin. İzin olmadan Image Toolbox bu resimleri gösteremez Varsayılan Çizim Rengi Varsayılan Çizim Yolu Modu Zaman Damgası Ekle Çıktı dosya adına Zaman Damgası eklemeyi etkinleştirir Biçimlendirilmiş Zaman Damgası Temel milisaniye yerine çıktı dosya adında Zaman Damgası biçimlendirmesini etkinleştir Biçimlerini seçmek için Zaman Damgalarını etkinleştirin Tek Seferlik Kayıt Konumu Çoğu seçenekte kaydet düğmesine uzun basarak kullanabileceğiniz tek seferlik kayıt konumlarını görüntüleyin ve düzenleyin Son Kullanılanlar CI kanalı Grup Telegram\'da Image Toolbox 🎉 İstediğiniz her şeyi tartışabileceğiniz sohbetimize katılın ve ayrıca betaları ve duyuruları yayınladığım CI kanalına göz atın Uygulamanın yeni sürümleri hakkında bildirim alın ve duyuruları okuyun Bir resmi verilen boyutlara sığdırın ve arka plana bulanıklık veya renk uygulayın Araçların Düzenlenmesi Araçları türe göre gruplandır Ana ekrandaki araçları özel bir liste düzenlemesi yerine türlerine göre gruplandırır Varsayılan Değerler Sistem Çubukları Görünürlüğü Kaydırarak Sistem Çubuklarını Göster Gizliyse sistem çubuklarını göstermek için kaydırmayı etkinleştirir Otomatik Tümünü Gizle Tümünü Göster Gezinme Çubuğunu Gizle Durum Çubuğunu Gizle Gürültü Üretimi Perlin veya diğer türler gibi farklı gürültüler üretin Frekans Gürültü Türü Döndürme Türü Fraktal Türü Oktavlar Boşlukluluk Kazanç Ağırlıklı Güç Ping Pong Gücü Mesafe Fonksiyonu Dönüş Türü Titreklik Alan Bükme Hizalama Özel Dosya Adı Geçerli resmi kaydetmek için kullanılacak konumu ve dosya adını seçin Özel adla klasöre kaydedildi Kolaj Oluşturucu 20 resme kadar kolaj yapın Kolaj Türü Değiştirmek için resmi basılı tutun, konumu ayarlamak için taşıyın ve yakınlaştırın Histogram Ayarlamalar yapmanıza yardımcı olacak RGB veya Parlaklık resim histogramı Bu resim, RGB ve Parlaklık histogramları oluşturmak için kullanılacaktır Tesseract Seçenekleri Tesseract motoru için bazı girdi değişkenleri uygulayın Özel Seçenekler Seçenekler şu desene göre girilmelidir: \"--{seçenek_adı} {değer}\" Otomatik Kırp Serbest Köşeler Resmi çokgen ile kırpın, bu ayrıca perspektifi de düzeltir Noktaları Resim Sınırlarına Zorla Noktalar resim sınırlarıyla sınırlı olmayacaktır, bu daha hassas perspektif düzeltme için kullanışlıdır Maske Çizilen yolun altında içeriğe duyarlı doldurma Noktayı İyileştir Daire Çekirdeği Kullan Açma Kapama Morfolojik Gradyan Üst Şapka Siyah Şapka Ton Eğrileri Eğrileri Sıfırla Eğriler varsayılan değere geri döndürülecektir Çizgi Stili Boşluk Boyutu Kesikli Noktalı Kesikli Damgalı Zikzak Belirtilen boşluk boyutuyla çizilen yol boyunca kesikli çizgi çizer Verilen yol boyunca noktalı ve kesikli çizgi çizer Sadece varsayılan düz çizgiler Belirtilen aralıklarla yol boyunca seçilen şekilleri çizer Yol boyunca dalgalı zikzak çizer Zikzak oranı Kısayol Oluştur Sabitlenecek aracı seçin Araç, başlatıcınızın ana ekranına kısayol olarak eklenecektir, istenen davranışı elde etmek için \"Dosya seçimini atla\" ayarıyla birlikte kullanın Kareleri yığma Önceki karelerin atılmasını sağlar, böylece birbirlerinin üzerine yığılmazlar Çapraz Geçiş Kareler birbirine çapraz geçiş yapacak Çapraz geçiş kare sayısı Eşik Bir Eşik İki Canny Ayna 101 Gelişmiş Yakınlaştırma Bulanıklığı Laplacian Basit Sobel Basit Yardımcı Izgara Hassas manipülasyonlara yardımcı olmak için çizim alanının üzerinde destekleyici bir ızgara gösterir Izgara Rengi Hücre Genişliği Hücre Yüksekliği Kompakt Seçiciler Bazı seçim kontrolleri daha az yer kaplamak için kompakt bir düzen kullanacaktır Resim çekmek için ayarlardan kamera izni verin Düzen Ana Ekran Başlığı Sabit Oran Faktörü (CRF) %1$s değeri yavaş bir sıkıştırma anlamına gelir ve nispeten küçük bir dosya boyutuyla sonuçlanır. %2$s daha hızlı bir sıkıştırma anlamına gelir ve büyük bir dosyayla sonuçlanır. Lut Kütüphanesi İndirdikten sonra uygulayabileceğiniz LUT koleksiyonunu indirin İndirdikten sonra uygulayabileceğiniz LUT koleksiyonunu güncelleyin (sadece yeniler sıraya alınacaktır) Filtreler için varsayılan resim önizlemesini değiştirin Önizleme Resmi Gizle Göster Kaydırıcı Tipi Süslü Material 2 Süslü görünümlü bir kaydırıcı. Bu varsayılan seçenektir Bir Material 2 kaydırıcısı Bir Material You kaydırıcısı Uygula Diyalog Düğmelerini Ortala Diyalogların düğmeleri mümkünse sol taraf yerine merkezde konumlandırılacaktır Açık Kaynak Lisansları Bu uygulamada kullanılan açık kaynaklı kütüphanelerin lisanslarını görüntüleyin Alan Piksel alan ilişkisi kullanarak yeniden örnekleme. Moire içermeyen sonuçlar verdiği için resim küçültme için tercih edilen bir yöntem olabilir. Ancak resim yakınlaştırıldığında, En Yakın yöntemine benzer. Ton Eşlemeyi Etkinleştir Yüzde Gir Siteye erişilemiyor, VPN kullanmayı veya URL\'nin doğru olup olmadığını kontrol etmeyi deneyin İşaretleme Katmanları Resimleri, metinleri ve daha fazlasını serbestçe yerleştirme yeteneğine sahip katmanlar modu Katmanı düzenle Resim üzerindeki katmanlar Arka plan olarak bir resim kullanın ve üzerine farklı katmanlar ekleyin Arka plan üzerindeki katmanlar İlk seçenekle aynı ama resim yerine renkle Beta Hızlı Ayarlar Tarafı Resimleri düzenlerken seçilen tarafa, tıklandığında hızlı ayarları açacak bir kayan şerit ekleyin Seçimi temizle \"%1$s\" ayar grubu varsayılan olarak daraltılmış olacaktır \"%1$s\" ayar grubu varsayılan olarak genişletilmiş olacaktır Base64 Araçları Base64 dizesini resme çözün veya resmi Base64 formatına kodlayın Base64 Sağlanan değer geçerli bir Base64 dizesi değil Boş veya geçersiz Base64 dizesi kopyalanamaz Base64 Yapıştır Base64 Kopyala Base64 dizesini kopyalamak veya kaydetmek için resim yükleyin. Dizenin kendisi varsa, resmi elde etmek için yukarıya yapıştırabilirsiniz Base64 Kaydet Base64 Paylaş Seçenekler Eylemler Base64 İçe Aktar Base64 Eylemleri Dış Çizgi Ekle Metnin etrafına belirtilen renk ve genişlikte bir dış çizgi ekleyin Dış Çizgi Rengi Dış Çizgi Boyutu Döndürme Dosya Adı Olarak Sağlama Toplamı Çıktı resimleri, veri sağlama toplamlarına karşılık gelen bir ada sahip olacaktır Özgür Yazılım (Ortak) Android uygulamalarının ortak kanalında daha fazla kullanışlı yazılım Algoritma Sağlama Toplamı Araçları Sağlama toplamlarını karşılaştırın, hash hesaplayın veya farklı hash algoritmaları kullanarak dosyalardan hex dizeleri oluşturun Hesapla Metin Hash Sağlama Toplamı Seçilen algoritmaya göre sağlama toplamını hesaplamak için bir dosya seçin Seçilen algoritmaya göre sağlama toplamını hesaplamak için metin girin Kaynak Sağlama Toplamı Karşılaştırılacak Sağlama Toplamı Eşleşti! Farklılık Sağlama toplamları eşit, güvenli olabilir Sağlama toplamları eşit değil, dosya güvensiz olabilir! Ağ Gradyanları Ağ Gradyanlarının çevrimiçi koleksiyonuna bakın Yalnızca TTF ve OTF yazı tipleri içe aktarılabilir Yazı tipi içe aktar (TTF/OTF) Yazı tiplerini dışa aktar İçe aktarılan yazı tipleri Kaydetme denemesi sırasında hata, çıktı klasörünü değiştirmeyi deneyin Dosya adı ayarlanmadı Hiçbiri Özel Sayfalar Sayfa Seçimi Araçtan Çıkış Onayı Belirli araçları kullanırken kaydedilmemiş değişiklikleriniz varsa ve kapatmaya çalışırsanız, onay iletişim kutusu gösterilecektir EXIF Düzenle Tek bir resmin meta verilerini yeniden sıkıştırma olmadan değiştirin Mevcut etiketleri düzenlemek için dokunun Çıkartmayı Değiştir Genişliğe Sığdır Yüksekliğe Sığdır Toplu Karşılaştırma Seçilen algoritmaya göre sağlama toplamını hesaplamak için dosya/dosyalar seçin Dosyaları Seç Dizin Seç Baş Uzunluğu Ölçeği Damga Zaman Damgası Format Deseni İç boşluk Resim Kesme Resim parçasını kesin ve kalanları dikey veya yatay çizgilerle birleştirin (tersi de olabilir) Dikey Eksen Çizgisi Yatay Eksen Çizgisi Ters Seçim Dikey kesim parçası, kesim alanı etrafındaki parçaları birleştirmek yerine bırakılacaktır Yatay kesim parçası, kesim alanı etrafındaki parçaları birleştirmek yerine bırakılacaktır Ağ Gradyanları Koleksiyonu Özel düğüm sayısı ve çözünürlük ile ağ gradyanı oluşturun Ağ Gradyanı Kaplaması Verilen resimlerin üzerine ağ gradyanı oluşturun Nokta Özelleştirme Izgara Boyutu Çözünürlük X Çözünürlük Y Çözünürlük Piksel Piksel Vurgu Rengi Piksel Karşılaştırma Türü Barkod tara Yükseklik Oranı Barkod Türü S/B Zorla Barkod Resmi tamamen siyah beyaz olacak ve uygulamanın temasıyla renklendirilmeyecek Herhangi bir Barkodu (QR, EAN, AZTEC, …) tarayın ve içeriğini alın veya yeni bir tane oluşturmak için metninizi yapıştırın Barkod Bulunamadı Oluşturulan Barkod Burada Olacak Ses Kapakları Ses dosyalarından albüm kapağı resimlerini ayıklayın, en yaygın formatlar desteklenir Başlamak için bir ses seçin Ses Seç Kapak Bulunamadı Günlükleri Gönder Uygulama günlükleri dosyasını paylaşmak için tıklayın, bu sorunu tespit etmeme ve sorunları çözmeme yardımcı olabilir Hay aksi… Bir şeyler ters gitti Aşağıdaki seçenekleri kullanarak benimle iletişime geçebilirsiniz ve bir çözüm bulmaya çalışacağım.\n(Günlükleri eklemeyi unutmayın) Dosyaya Yaz Bir grup resimden metin ayıklayın ve tek bir metin dosyasında saklayın Meta Veriye Yaz Her resimden metin ayıklayın ve ilgili fotoğrafların EXIF bilgisine yerleştirin Görünmez Mod Resimlerinizin baytları içinde gözle görünmeyen filigranlar oluşturmak için steganografi kullanın LSB kullan LSB (En Az Anlamlı Bit) steganografi yöntemi kullanılacaktır, aksi takdirde FD (Frekans Alanı) Kırmızı Gözleri Otomatik Kaldır Parola Kilidi Aç PDF korumalı İşlem neredeyse tamamlandı. Şimdi iptal etmek yeniden başlatmayı gerektirecektir Değiştirilme Tarihi Değiştirilme Tarihi (Ters) Boyut Boyut (Ters) MIME Türü MIME Türü (Ters) Uzantı Uzantı (Ters) Eklenme Tarihi Eklenme Tarihi (Ters) Soldan Sağa Sağdan Sola Yukarıdan Aşağıya Aşağıdan Yukarıya Sıvı Cam Yakın zamanda duyurulan IOS 26 ve onun sıvı cam tasarım sistemine dayalı bir anahtar Bir resim seçin veya aşağıya Base64 verisi yapıştırın/içe aktarın Başlamak için resim bağlantısı yazın Bağlantıyı yapıştır %1$s renk %1$s renk Dönmeyi kapat İki parmak hareketiyle görüntülerin dönmesini engeller Kenarlara yapıştırmayı etkinleştir Taşıma veya yakınlaştırmadan sonra görüntüler çerçeve kenarlarını doldurmak için otomatik olarak yerleştirilir Kaleydoskop İkincil açı Yanlar Kanal Karışımı Mavi yeşil Kırmızı mavi Yeşil kırmızı Kırmızıya Yeşile Maviye Camgöbeği Eflatun Sarı Renkli Yarım Ton Kontur Seviyeler Çıkıntı Voronoi Kristalleştirme Şekil Esnetme Rastgelelik Kusurları gider Dağıt DoG İkinci yarıçap Eşitle Parıltı Döndür ve Sıkıştır Noktalama Kenar rengi Kutup Koordinatları Dikden kutupa Kutupdan dike Dairede ters çevir Gürültüyü Azalt Basit Soluluk Dokuma X Boşluğu Y Boşluğu X Eni Y Eni Döndür Istampa Karala Yoğunluk Karışım Küre Lens Bozulması Kırılım indisi Ark Yayılma açısı Işıltı Işınlar ASCII Gradyan Hare Sonbahar Kemik Jet Kış Okyanus Yaz İlkbahar Serin Varyant HSV Pembe Sıcak Parula Mağma Aşırı ısı Plazma Viridis Çivit mavisi Alacakaranlık Alacakaranlık kayması Oto Perspektif Perspektif Düzeltme Kırpmaya izin ver Kırpma veya Perspektif Mutlak Turbo Derin Yeşil Lens Düzeltme JSON formatında hedef lens profili dosyası Hazır lens profillerini indirin Parça yüzdeleri JSON olarak dışarı aktar Palet verisini JSON olarak kopyala Akıllı Ölçeklendirme Ana Ekran Kilit Ekranı Dahili Duvar Kağıtları Dışa Aktar Yenile Güncel Ana Sayfa, Kilit ve dahili duvar kağıtlarını edinin Tüm dosyalara erişim izni verin, bu duvar kağıtlarını almak için gereklidir. Harici depolama iznini yönetmek yeterli değildir, resimlerinize erişime izin vermeniz gerekir, \"Tümüne izin ver\" seçeneğini seçtiğinizden emin olun. Dosya Adına Ön Ayarı Ekle Seçilen ön ayar ile son eki görüntü dosya adına ekler Dosya Adına Görüntü Ölçek Modunu Ekle Seçilen görüntü ölçekleme modunu görüntü dosya adına son ek olarak ekler ASCII Sanatı Resmi, görüntü gibi görünecek ASCII metnine dönüştür Parametreler Bazı durumlarda daha iyi sonuç elde etmek için görüntüye negatif filtre uygular. Ekran görüntüsü işleniyor Ekran görüntüsü yakalanamadı, tekrar deneyin Kaydedilme atlandı %1$s dosya atlandı Eğer Büyükse Atlamaya İzin Ver Bazı araçlar, sonuçta ortaya çıkan dosya boyutu orijinalinden daha büyük olacaksa görüntüleri kaydetmeyi atlayabilir. Takvim Etkinliği İletişim Eposta Konum Telefon Yazı SMS URL Wi-Fi Ağı aç N/A SSID Telefon Mesaj Adres Konu Gövde İsim Organizasyon Başlık Telefonlar Epostalar URL\'ler Adresler Özet Açıklama Konum Organizatör Başlangıç tarihi Bitiş tarihi Durum Enlem Boylam Barkod oluştur Barkod düzenle Wi-Fi konfigürasyonu Güvenlik Kişi seçin Seçilen kişiyi kullanarak otomatik doldurma için ayarlardan kişilere izin ver Kişi bilgisi İlk isim Orta isim Son isim Okunuş Telefon ekle Eposta ekle Adres ekle Website Website ekle Formatlanmış isim Bu görüntü barkodun üzerine yerleştirilmek üzere kullanılacaktır Kod özelleştirmesi Bu görüntü, QR kodunun ortasında logo olarak kullanılacaktır Logo Logo iç boşluğu Logo boyutu Logo köşeleri Dördüncü göz Alt köşeye dördüncü göz ekleyerek QR koduna göz simetrisi ekler Piksel şekli Çerçeve şekli Top şekli Hata düzeltme seviyesi Koyu renk Açık renk Hyper OS Xiaomi HyperOS gibi stil Maske deseni Bu kod taranamayabilir, tüm cihazlarda okunabilir hale getirmek için görünüm parametrelerini değiştirin. Taranamıyor Araçlar, daha kompakt olması için ana ekran uygulama başlatıcısı gibi görünecek Başlatıcı Modu Seçilen fırça ve stil ile bir alanı doldurur Dolgu Sprey Grafiti tarzı yol çizer Kare Parçacıklar Sprey parçacıkları daire şeklinde değil kare şeklinde olacaktır Palet Araçları Görüntüden temel/material you paleti oluşturun, veya farklı palet formatları arasında içe/dışa aktarın Paleti düzenle Çeşitli formatlarda palet içe/dışa aktarma Renk ismi Palet ismi Palet Formatı Oluşturulan paleti farklı formatlara aktarın Mevcut palete yeni renk ekler %1$s formatı palet adı sağlamayı desteklemiyor Play Store politikaları nedeniyle, bu özellik mevcut sürümde bulunmamaktadır. Bu özelliğe erişmek için lütfen ImageToolbox\'ı alternatif bir kaynaktan indirin. Mevcut sürümleri aşağıdaki GitHub adresinde bulabilirsiniz. GitHub sayfasını aç Orijinal dosya, seçilen klasöre kaydedilmek yerine yenisiyle değiştirilecektir. Gizli filigran metni algılandı Gizli filigran görüntüsü algılandı Bu resim gizlendi Üretken İçboya OpenCV\'ye bağlı kalmadan, bir AI modeli kullanarak görüntüdeki nesneleri kaldırmanıza olanak tanır. Bu özelliği kullanmak için, uygulama gerekli modeli (~200 MB) GitHub\'dan indirecektir OpenCV\'ye bağlı kalmadan, bir AI modeli kullanarak görüntüdeki nesneleri kaldırmanıza olanak tanır. Bu, uzun süren bir işlem olabilir Hata Seviyesi Analizi Parlaklık Gradyanı Ortalama Uzaklık Kopyalama Hareket Algılama Sürdür Katsayı Pano verileri çok büyük Veriler kopyalanamayacak kadar büyük Basit Dokuma Pikselleştirme Kademeli Pikselleştirme Çapraz Pikselleştirme Mikro Makro Pikselleştirme Orbital Pikselleştirme Vortex Pikselleştirme Atım Izgara Pikselleştirmesi Çekirdek Pikselleştirme Radyal Dokuma Pikselleştirme \"%1$s\" URI açılamıyor Kar Yağışı Modu Aktif Sınır Çerçevesi Glitch Varyantı Kanal Değişimi Maks Çıkıntı VHS Blok Glitch Blok Boyutu CRT eğriliği Eğrilik Renk Parlaklığı Piksel Erime Maks Düşüş AI Araçları Artefakt giderme veya gürültü giderme gibi yapay zeka modelleriyle görüntüleri işlemek için çeşitli araçlar Sıkıştırma, pürüzlü çizgiler Çizgi filmler, yayın sıkıştırma Genel sıkıştırma, genel gürültü Renksiz çizgi film gürültüsü Hızlı, genel sıkıştırma, genel gürültü, animasyon/çizgi roman/anime Kitap tarama Pozlama düzeltmesi Genel sıkıştırmada en iyisi, renkli görüntüler Genel sıkıştırmada en iyi, grisel görüntüler Genel sıkıştırma, grisel görüntüler, daha güçlü Genel gürültü, renkli görüntüler Genel gürültü, renkli görüntüler, daha iyi ayrıntılar Genel gürültü, grisel görüntüler Genel gürültü, grisel görüntüler, daha güçlü Genel gürültü, grisel görüntüler, en güçlü Genel sıkıştırma Genel sıkıştırma Tekstürleme, h264 sıkıştırma VHS sıkıştırma Standart olmayan sıkıştırma (cinepak, msvideo1, roq) Blink sıkıştırması, geometri için daha iyi Blink sıkıştırma, daha güçlü Blink sıkıştırma, yumuşak, ayrıntıları korur Merdiven basamağı etkisini ortadan kaldırma, düzleştirme Taranmış çizimler, hafif sıkıştırma, muare Renk bantlaması Yavaş, yarı tonları kaldırma Grisel/siyah beyaz görüntüler için genel renklendirici, daha iyi sonuçlar için DDColor kullanın Kenar kaldırma Aşırı keskinleştirmeyi kaldırır Yavaş, titreşimli Kenar yumuşatma, genel artefaktlar, CGI KDM003 tarama işlemleri Hafif görüntü iyileştirme modeli Basit ve hızlı renklendirme, çizgi filmler, ideal değil Sıkıştırma artefaktı giderme Sıkıştırma artefaktı giderme Pürüzsüz sonuçlarla bandaj çıkarma Yarım ton desen işleme Titreşim deseni kaldırma V3 JPEG artefakt giderme V2 H.264 doku iyileştirme VHS keskinleştirme ve iyileştirme Birleştirme Parça Boyutu Örtüşme Boyutu %1$s px\'den büyük görüntüler dilimlenecek ve parçalar halinde işlenecek, üst üste binmeler görünür ek yerlerini önlemek için bunları karıştıracaktır. Büyük boyutlar, düşük kaliteli cihazlarda kararsızlığa neden olabilir. Birini seçerek başla %1$s modelini silmek istiyor musunuz? Tekrar indirmeniz gerekecektir. Kabul Modeller İndirilen Modeller Mevcut Modeller Hazırlanıyor Aktif Model Oturum açılamadı Yalnızca .onnx/.ort modelleri içe aktarılabilir Model içe aktar Özel onnx modelini daha sonra kullanmak üzere içe aktarın, yalnızca onnx/ort modelleri kabul edilir, neredeyse tüm esrgan benzeri varyantları destekler İçeri Aktarılan Modeller Genel gürültü, renkli görüntüler Genel gürültü, renkli görüntüler, daha güçlü Genel gürültü, renkli görüntüler, en güçlü Titreşim artefaktlarını ve renk bantlamasını azaltarak, yumuşak gradyanları ve düz renk alanlarını iyileştirir. Doğal renkleri korurken, dengeli vurgularla görüntünün parlaklığını ve kontrastını artırır. Ayrıntıları koruyarak ve aşırı pozlamayı önleyerek karanlık görüntüleri aydınlatır. Aşırı renk tonlamasını giderir ve daha nötr ve doğal bir renk dengesi sağlar. İnce ayrıntıları ve dokuları korumaya önem vererek Poisson tabanlı gürültü tonlaması uygular. Daha yumuşak ve daha az agresif görsel sonuçlar için yumuşak Poisson gürültü tonlaması uygular. Kaldır Detayların korunması ve görüntü netliğine odaklanan tek tip gürültü tonlama. Hafif ve düzgün bir doku ve pürüzsüz bir görünüm için hafif ve düzgün bir gürültü tonlaması. Artefaktları yeniden boyayarak ve görüntü tutarlılığını iyileştirerek hasarlı veya düzensiz alanları onarır. Performans maliyetini en aza indirerek renk bantlamasını ortadan kaldıran hafif debanding modeli. Görüntüleri çok yüksek sıkıştırma artefaktları (kalite %0-20) ile optimize ederek netliği artırır. Yüksek sıkıştırma artefaktları olan görüntüleri iyileştirir (kalite %20-40), ayrıntıları geri yükler ve gürültüyü azaltır. Görüntüleri orta düzeyde sıkıştırma (kalite %40-60) ile iyileştirir, keskinlik ve pürüzsüzlük arasında denge sağlar. Hafif sıkıştırma (kalite %60-80) ile görüntüleri iyileştirerek ince ayrıntıları ve dokuları güçlendirir. Doğal görünümü ve ayrıntıları korurken, neredeyse kayıpsız görüntüleri (kalite %80-100) hafifçe iyileştirir. Görüntü bulanıklığını hafifçe azaltır, artefakt oluşturmadan keskinliği artırır. Uzun süreli işlemler Resim işleniyor İşleniyor Çok düşük kaliteli görüntülerdeki (0-20%) ağır JPEG sıkıştırma artefaktlarını giderir. Yüksek oranda sıkıştırılmış görüntülerdeki güçlü JPEG artefaktlarını azaltır (20-40%). Görüntü ayrıntılarını korurken orta derecede JPEG artefaktlarını temizler (40-60%). Oldukça yüksek kaliteli görüntülerde (60-80%) hafif JPEG artefaktlarını iyileştirir. Neredeyse kayıpsız görüntülerdeki (80-100%) küçük JPEG artefaktlarını ince bir şekilde azaltır. İnce ayrıntıları ve dokuları geliştirerek, ağır yapaylık olmadan algılanan keskinliği artırır. İşleme bitti İşleme başarısız Cilt dokularını ve ayrıntılarını iyileştirirken doğal görünümü korur, hız için optimize edilmiştir. JPEG sıkıştırma artefaktlarını giderir ve sıkıştırılmış fotoğrafların görüntü kalitesini geri yükler. Düşük ışık koşullarında çekilen fotoğraflardaki ISO gürültüsünü azaltır ve ayrıntıları korur. Aşırı pozlanmış veya \"jumbo\" vurguları düzeltir ve daha iyi ton dengesi sağlar. Gri tonlu görüntülere doğal renkler ekleyen hafif ve hızlı renklendirme modeli. DEJPEG Gürültü giderme Renklendirme Artefaktlar Geliştir Anime Tarama Kaliteleştirme Genel görüntüler için X4 yükseltici; daha az GPU ve zaman kullanan, orta düzeyde bulanıklık giderme ve gürültü azaltma özelliğine sahip küçük model. Genel görüntüler için X2 yükseltici, dokuları ve doğal ayrıntıları korur. Gelişmiş dokular ve gerçekçi sonuçlar sunan genel görüntüler için X4 yükseltici. Anime görüntüleri için optimize edilmiş X4 yükseltici; daha keskin çizgiler ve ayrıntılar için 6 RRDB bloğu. MSE kaybı ile X4 yükseltici, genel görüntüler için daha pürüzsüz sonuçlar ve daha az artefakt üretir. Anime görüntüleri için optimize edilmiş X4 Upscaler; daha keskin ayrıntılar ve pürüzsüz çizgiler sunan 4B32F varyantı. Genel görüntüler için X4 UltraSharp V2 modeli; keskinlik ve netliği vurgular. X4 UltraSharp V2 Lite; daha hızlı ve daha küçük, daha az GPU belleği kullanırken ayrıntıları korur. Hızlı arka plan kaldırma için hafif model. Dengeli performans ve doğruluk. Portreler, nesneler ve sahnelerle çalışır. Çoğu kullanım durumu için önerilir. Yatay Kenar Kalınlığı Dikey Kenar Kalınlığı Mevcut model parçalamayı desteklemiyor, görüntü orijinal boyutlarda işlenecek, bu durum yüksek bellek tüketimine ve düşük kaliteli cihazlarda sorunlara neden olabilir Parçalama devre dışı, görüntü orijinal boyutlarında işlenecek; bu, yüksek bellek tüketimine ve düşük kaliteli cihazlarda sorunlara neden olabilir ancak çıkarımda daha iyi sonuçlar verebilir Parçalama Arka planı kaldırmak için yüksek doğruluklu görüntü segmentasyon modeli Daha az bellek kullanımıyla daha hızlı arka plan kaldırma için U2Net\'in hafif versiyonu. Tam DDColor modeli, minimum düzeyde bozulmayla genel görüntüler için yüksek kaliteli renklendirme sunar. Tüm renklendirme modelleri arasında en iyi seçim. DDColor Eğitimli ve özel sanatsal veri kümeleri; daha az gerçekçi olmayan renk yapısı ile çeşitli ve sanatsal renklendirme sonuçları üretir. Doğru arka plan kaldırma için Swin Transformer\'ı temel alan hafif BiRefNet modeli. Özellikle karmaşık nesnelerde ve zorlu arka planlarda keskin kenarlar ve mükemmel ayrıntı korumasıyla yüksek kaliteli arka plan kaldırma. Genel nesneler için uygun ve orta düzeyde ayrıntı korumasına sahip, pürüzsüz kenarlı doğru maskeler üreten arka plan kaldırma modeli. Model zaten indirildi Model başarıyla içe aktarıldı Tip Anahtar kelime Çok hızlı Normal Yavaş Çok Yavaş Yüzdeleri Hesapla Minimum değer %1$s\'dir Parmaklarınızla çizim yaparak görüntüyü deforme edin Çözgü Sertlik Çözgü Modu Taşınmak Büyümek Çekmek CW girdap Girdap CCW Solmaya Dayanım En İyi Düşüş Alttan Düşme Bırakmayı Başlat Bırakmayı Sonlandır İndiriliyor Pürüzsüz Şekiller Daha düzgün, daha doğal şekiller için standart yuvarlatılmış dikdörtgenler yerine süper elipsler kullanın Şekil Türü Kesmek Yuvarlak Düz Yuvarlama olmadan keskin kenarlar Klasik yuvarlatılmış köşeler Şekiller Türü Köşe Boyutu sincap Zarif yuvarlak kullanıcı arayüzü öğeleri Dosya Adı Formatı Dosya adının en başına yerleştirilen özel metin; proje adları, markalar veya kişisel etiketler için mükemmeldir. Orijinal dosya adını uzantısız olarak kullanarak kaynak kimliğini olduğu gibi korumanıza yardımcı olur. Çözünürlük değişikliklerini izlemek veya sonuçları ölçeklendirmek için yararlı olan piksel cinsinden görüntü genişliği. Görüntünün piksel cinsinden yüksekliği, en boy oranlarıyla veya dışa aktarmalarla çalışırken faydalıdır. Benzersiz dosya adlarını garanti etmek için rastgele rakamlar üretir; kopyalara karşı ekstra güvenlik için daha fazla rakam ekleyin. Toplu dışa aktarmalar için otomatik artan sayaç, tek oturumda birden fazla görüntüyü kaydederken idealdir. Uygulanan ön ayar adını dosya adına ekler, böylece görüntünün nasıl işlendiğini kolayca hatırlayabilirsiniz. İşleme sırasında kullanılan görüntü ölçeklendirme modunu görüntüleyerek yeniden boyutlandırılan, kırpılan veya takılan görüntülerin ayırt edilmesine yardımcı olur. Dosya adının sonuna yerleştirilen ve _v2, _edited veya _final gibi sürüm oluşturma için yararlı olan özel metin. Gerçek kaydedilen formatla otomatik olarak eşleşen dosya uzantısı (png, jpg, webp vb.). Mükemmel sıralama için Java spesifikasyonuna göre kendi formatınızı tanımlamanıza olanak tanıyan özelleştirilebilir bir zaman damgası. Fırlatma Türü Android Yerel iOS Stili Pürüzsüz Eğri Hızlı Durdurma kabarık Yüzen Hızlı Ultra Pürüzsüz Uyarlanabilir Erişilebilirlik Farkındalığı Azaltılmış Hareket Yerel Android kaydırma fiziği Genel kullanım için dengeli, düzgün kaydırma Daha yüksek sürtünmeli iOS benzeri kaydırma davranışı Farklı kaydırma hissi için benzersiz spline eğrisi Hızlı durdurmayla hassas kaydırma Eğlenceli, hızlı tepki veren zıplayan kaydırma İçeriğe göz atmak için uzun, kayan kaydırmalar Etkileşimli kullanıcı arayüzleri için hızlı, duyarlı kaydırma Genişletilmiş momentumla birinci sınıf yumuşak kaydırma Fırlatma hızına göre fiziği ayarlar Sistem erişilebilirlik ayarlarına saygı duyar Erişilebilirlik ihtiyaçları için minimum hareket Ana Hatlar Her beşinci satırda bir daha kalın çizgi ekler Dolgu Rengi Gizli Araçlar Paylaşım İçin Gizlenen Araçlar Renk Kitaplığı Geniş bir renk koleksiyonuna göz atın Odak dışı fotoğrafları düzeltmek için ideal olan, doğal ayrıntıları korurken görüntülerdeki bulanıklığı keskinleştirir ve ortadan kaldırır. Daha önce yeniden boyutlandırılmış görüntüleri akıllıca geri yükleyerek kayıp ayrıntıları ve dokuları kurtarır. Canlı aksiyon içeriği için optimize edilmiştir, sıkıştırma bozulmalarını azaltır ve film/TV şovu karelerindeki ince ayrıntıları geliştirir. VHS kalitesinde çekimi HD\'ye dönüştürerek bant gürültüsünü ortadan kaldırır ve çözünürlüğü artırırken aynı zamanda vintage hissi korur. Metin ağırlıklı görüntüler ve ekran görüntüleri için özel olarak tasarlanmıştır, karakterleri keskinleştirir ve okunabilirliği artırır. Genel amaçlı fotoğraf geliştirme için mükemmel olan, çeşitli veri kümeleri üzerinde eğitilmiş gelişmiş yükseltme. Web\'de sıkıştırılmış fotoğraflar için optimize edilmiştir, JPEG bozulmalarını kaldırır ve doğal görünümü geri kazandırır. Web fotoğrafları için daha iyi doku koruması ve artefakt azaltma özellikleriyle geliştirilmiş sürüm. Çift Toplama Transformatörü teknolojisiyle 2 kat yükseltme, keskinliği ve doğal ayrıntıları korur. Orta düzeydeki genişletme ihtiyaçları için ideal olan gelişmiş transformatör mimarisini kullanan 3 kat ölçeklendirme. Son teknoloji trafo ağıyla 4 kat yüksek kalite yükseltme, daha büyük ölçeklerde ince ayrıntıları korur. Fotoğraflardan bulanıklığı/gürültüyü ve titremeyi giderir. Genel amaçlı ama fotoğraflarda en iyisi. BSRGAN bozulması için optimize edilmiş Swin2SR transformatörünü kullanarak düşük kaliteli görüntüleri geri yükler. Ağır sıkıştırma bozukluklarını düzeltmek ve ayrıntıları 4x ölçekte geliştirmek için idealdir. BSRGAN bozulması konusunda eğitilmiş SwinIR transformatörüyle 4 kat yükseltme. Fotoğraflar ve karmaşık sahnelerde daha keskin dokular ve daha doğal ayrıntılar için GAN\'ı kullanır. Yol PDF\'yi birleştir Birden fazla PDF dosyasını tek bir belgede birleştirin Dosya Sırası s. PDF\'yi böl PDF belgesinden belirli sayfaları çıkarın PDF\'yi döndür Sayfa yönlendirmesini kalıcı olarak düzeltin Sayfalar PDF\'yi yeniden düzenle Sayfaları yeniden sıralamak için sürükleyip bırakın Sayfaları Tut ve Sürükle Sayfa Numaraları Belgelerinize otomatik olarak numaralandırma ekleyin Etiket Formatı PDF\'den Metne (OCR) PDF belgelerinizden düz metin çıkarın Marka bilinci oluşturma veya güvenlik için özel metni katmanlayın İmza Elektronik imzanızı herhangi bir belgeye ekleyin Bu imza olarak kullanılacaktır PDF\'nin kilidini aç Korunan dosyalarınızdan şifreleri kaldırın PDF\'yi koruyun Belgelerinizi güçlü şifrelemeyle koruyun Başarı PDF\'nin kilidi açıldı, kaydedebilir veya paylaşabilirsiniz PDF\'yi onar Bozuk veya okunamayan belgeleri düzeltmeye çalışın Gri tonlamalı Tüm belgeye gömülü görüntüleri gri tonlamaya dönüştürün PDF\'yi sıkıştır Daha kolay paylaşım için belgenizin dosya boyutunu optimize edin ImageToolbox dahili çapraz referans tablosunu yeniden oluşturur ve dosya yapısını sıfırdan yeniden oluşturur. Bu, \\"açılamayan\\" birçok dosyaya erişimi geri yükleyebilir Bu araç, tüm belge resimlerini gri tonlamaya dönüştürür. Dosya boyutunu yazdırmak ve küçültmek için en iyisi Meta veriler Daha iyi gizlilik için belge özelliklerini düzenleyin Etiketler yapımcı Yazar Anahtar Kelimeler Yaratıcı Gizlilik Derinlemesine Temizlik Bu belge için mevcut tüm meta verileri temizle Sayfa Derin OCR Tesseract motorunu kullanarak belgeden metni çıkarın ve tek bir metin dosyasında saklayın Tüm sayfalar kaldırılamıyor PDF sayfalarını kaldır PDF belgesinden belirli sayfaları kaldırın Kaldırmak İçin Dokunun Manuel olarak PDF\'yi kırp Belge sayfalarını istediğiniz sınırlara göre kırpın PDF\'yi düzleştir Belge sayfalarını tarayarak PDF\'yi değiştirilemez hale getirin Kamera başlatılamadı. Lütfen izinleri kontrol edin ve başka bir uygulama tarafından kullanılmadığından emin olun. Görüntüleri Çıkart PDF\'lere gömülü görüntüleri orijinal çözünürlüklerinde çıkarın Bu PDF dosyası herhangi bir gömülü resim içermiyor Bu araç her sayfayı tarar ve tam kalitede kaynak görüntüleri kurtarır; belgelerdeki orijinalleri kaydetmek için mükemmeldir İmza Çek Kalem Parametreleri Belgelere yerleştirilecek resim olarak kendi imzanızı kullanın PDF\'yi sıkıştır Belgeyi belirli aralıklarla bölün ve yeni belgeleri zip arşivine paketleyin Aralık PDF\'yi yazdır Belgeyi özel sayfa boyutunda yazdırmaya hazırlayın Sayfa Başına Sayfa Oryantasyon Sayfa Boyutu Marj Çiçek açmak Yumuşak Diz Anime ve çizgi filmler için optimize edilmiştir. İyileştirilmiş doğal renkler ve daha az yapaylık ile hızlı ölçeklendirme Samsung One UI 7 benzeri tarz İstenilen değeri hesaplamak için temel matematik sembollerini buraya girin (örn. (5+5)*10) Matematik ifadesi %1$s adede kadar resim alın Tarih Saati Tut Tarih ve saate ilişkin exif etiketlerini her zaman koruyun, exif\'i sakla seçeneğinden bağımsız olarak çalışır Alfa Formatları İçin Arka Plan Rengi Alfa desteğiyle her görüntü formatı için arka plan rengini ayarlama olanağı ekler; devre dışı bırakıldığında bu yalnızca alfa olmayanlar için geçerlidir Projeyi aç Önceden kaydedilmiş bir Image Toolbox projesini düzenlemeye devam edin Image Toolbox projesi açılamıyor Image Toolbox projesinde proje verileri eksik Image Toolbox projesi bozuk Desteklenmeyen Image Toolbox proje sürümü: %1$d Projeyi kaydet Katmanları, arka planı ve düzenleme geçmişini düzenlenebilir bir proje dosyasında saklayın Açılamadı Aranabilir PDF\'ye Yaz Görüntü kümesindeki metni tanıyın ve görüntü ve seçilebilir metin katmanıyla aranabilir PDF\'yi kaydedin Katman alfa Yatay Çevirme Dikey Çevirme Kilit Gölge Ekle Gölge Rengi Metin Geometrisi Daha keskin stilizasyon için metni uzatın veya eğriltin Ölçek X X\'i Eğikleştir Ek açıklamaları kaldır Bağlantılar, yorumlar, vurgular, şekiller veya form alanları gibi seçili ek açıklama türlerini PDF sayfalarından kaldırın Köprüler Dosya Ekleri çizgiler Pop-up\'lar Pullar Şekiller Metin Notları Metin İşaretleme Form Alanları İşaretleme Bilinmiyor Ek açıklamalar Grubu çöz Yapılandırılabilir renk ve ofsetlerle katmanın arkasına bulanıklık gölgesi ekleyin ================================================ FILE: core/resources/src/main/res/values-ug/strings.xml ================================================ سۈرەت بەك چوڭ، ئالدىن كۆرگىلى بولمايدۇ، لېكىن يەنىلا ساقلاشنى سىناپ باقىمىز رەسىمنى تاللاپ باشلاڭ ئېگىزلىك %1$s سۈپىتى فورماتى تارايتىش تىپى مەجبۇر ئۆزى ماسلىشىش قايتا ئۆزگەرتىش رەسىمنى دەسلەپكى ھالىتىگە قايتۇرۇش دەسلەپكى ھالىتىگە قايىتتى قايتا كىلەي ۋايجان~بىريەلىرى بىرقاندايا بۇنىڭ ئەپنى ئەسلى ھالىتىگە قايتۇرۇش چاپلاق تاختىسىغا كۆچۈرۈلدى نەزەردىن ساقىت قىلىش تۈرى EXIFئۇچۇرىنى ئۆزگەرتىش مۇقۇملاشتۇرۇش EXIFئۇچۇرى يوقكەن بەلگە قوشۇش ساقلاش قۇرقداش EXIFئۇچۇرىنى قۇرۇقداش بولدىلا بارلىق رەسىم EXIF سانلىق مەلۇماتلىرى ئۆچۈرۈلىدۇ. بۇ ھەرىكەتنى ئەمەلدىن قالدۇرغىلى بولمايدۇ! چاتاق چىقتى: %1$s چوڭلۇقى %1$s يۈكلىنىۋاتىدۇ… كەڭلىك %1$s رەسىم تاللاش ئۆچۈرۈش راستىنلا چېكىنەمسىز؟ ئەپتىن چېكىنىش بولدىلا ئالدىن تەسىس قىلىش كىسىش ساقلاش ئەگەر ھازىر چېكىنىپ چىقسا، بارلىق ساقلانمىغان ئۆزگىرىشلەر يوقاپ كېتىدۇ ئەسلى كود ئەڭ يېڭى يېڭىلاش، مەسىلىلەرنى مۇزاكىرە قىلىش قاتارلىقلارغا ئېرىشىش بىر پارچە رەسىمنى چوڭايتىش رەڭ تاللىغۇچ رەسىمدىن رەڭ تاللاش، ھەمدە كۆپەيتىش ياكى ئورتاقلىشىش رەسىم رەڭ رەسىمنى ھەر قانداق چوڭلۇقتا كېسىش نۇسخا نومۇرى رەسىم سانى :%d ئالدىن كۆرۈش رەسىمىنى ئالماشتۇرش چىقىرىۋېتىش رەڭ ئۇسلۇبى رەڭ ئۇسلۇبى يېڭىلاش يېڭى نەشىرى %1$s قوللىمايدىغان تىپ : %1$s بەلگىلەنگەن رەسىمنىڭ رەڭ ئۇسلۇبىنى ھاسىل قىلغىلى بولمىدى ئەسلى رەسىم كۆڭۈلدىكى ئۆز ئالدىغا ئېنىقلىما بېرىش ئۈسكۈنە ساقلاش ئورنى بىر پارچە رەسىمنىڭ ئۆلچىمىنى ئۆزگەرتىش رەڭ كودى كۆپەيتىلدى EXIF ئۇچۇرىنى ساقلاپ قېلىش بەلگىلەنگەن رەسىمنىڭ رەڭ ئۇسلۇبىنى ھاسىل قىلىش چىقىرىش مۇندەرىجىسى بەلگىلەنمىگەن Drago Aldridge Cutoff Amoled mode ئەگەر قوزغىتىلغان يۈزلەرنىڭ رەڭگى كېچىدە مۇتلەق قاراڭغۇ قىلىپ تەڭشىلىدۇ رەڭ لايىھىسى قىزىل يېشىل كۆك ئىناۋەتلىك aRGB- كود چاپلاڭ. چاپلاشقا ھېچنېمە يوق ئەپ ھەققىدە ھېچقانداق يېڭىلانما تېپىلمىدى ئىز قوغلىغۇچى بۇ يەرگە خاتالىق دوكلاتى ۋە ئىقتىدار تەلەپلىرىنى ئەۋەتىڭ تەرجىمە قىلىشقا ياردەم قىلىڭ تەرجىمە خاتالىقىنى تۈزىتىڭ ياكى تۈرنى باشقا تىللارغا يەرلىكلەشتۈرۈڭ سوئالىڭىزدىن ھېچ نەرسە تېپىلمىدى بۇ يەردىن ئىزدەڭ ئەگەر قوزغىتىلغان بولسا ، ئەپ رەڭلىرى تام قەغىزىنىڭ رەڭگىگە قوللىنىلىدۇ %d رەسىم (لەر) نى ساقلىيالمىدى Primary Tertiary Secondary چېگرا قېلىنلىقى Surface قىممەت قوش ئىجازەت Grant قوللىنىشچان پروگراممىلارنى ساقلاش ئۈچۈن ساقلاش بوشلۇقىڭىزغا كىرىشى كېرەك ، ئۇ زۆرۈر. كېيىنكى سۆزلىشىش رامكىسىغا ئىجازەت بېرىڭ. سىرتقى ساقلاش ئەپ ئىشلەش ئۈچۈن بۇ ئىجازەتكە موھتاج ، قولدا بېرىڭ مون رەڭلىرى بۇ پروگرامما پۈتۈنلەي ھەقسىز ، ئەمما تۈر تەرەققىياتىنى قوللىماقچى بولسىڭىز ، بۇ يەرنى چېكىڭ FAB توغرىلاش يېڭىلانمىلارنى تەكشۈرۈڭ ئەگەر قوزغىتىلسا ، ئەپ قوزغالغاندا يېڭىلاش دىئالوگى سىزگە كۆرسىتىلىدۇ رەسىم چوڭايتىش ھەمبەھىرلەش Prefix ھۆججەت ئىسمى Emoji ئەگەر قوزغىتىلسا ، چىقىرىلغان ھۆججەتنىڭ نامىغا ساقلانغان رەسىمنىڭ كەڭلىكى ۋە ئېگىزلىكىنى قوشىدۇ ئاساسلىق ئېكراندا قايسى emoji نى كۆرسىتىشنى تاللاڭ ھۆججەت چوڭلۇقى قوشۇڭ EXIF نى ئۆچۈرۈڭ ھەر قانداق رەسىمدىن EXIF مېتا سانلىق مەلۇماتلىرىنى ئۆچۈرۈڭ رەسىم ئالدىن كۆرۈش ھەر خىل رەسىملەرنى ئالدىن كۆرۈڭ: GIF ، SVG قاتارلىقلار رەسىم مەنبەسى رەسىم تاللىغۇچ Gallery ھۆججەت ئىزدىگۈچى ئاددىي رەسىمخانا رەسىم تاللىغۇچ. مېدىيا تاللاش بىلەن تەمىنلەيدىغان ئەپ بولسىلا ئىشلەيدۇ ئېكراننىڭ ئاستىدا كۆرۈنگەن ئاندىرويىد زامانىۋى رەسىم تاللىغۇچ پەقەت ئاندىرويىد 12+ دە ئىشلەيدۇ. EXIF مېتا سانلىق مەلۇماتلىرىنى قوبۇل قىلىش مەسىلىسى بار GetContent نى ئىشلىتىپ رەسىم تاللاڭ. ھەممە يەردە ئىشلەيدۇ ، ئەمما بەزى ئۈسكۈنىلەردە تاللانغان رەسىملەرنى قوبۇل قىلىش مەسىلىسى بارلىقى مەلۇم. بۇ مېنىڭ خاتالىقىم ئەمەس. تاللانما ئورۇنلاشتۇرۇش تەھرىر زاكاز ئاساسىي ئېكراندىكى تاللاشلارنىڭ تەرتىپىنى بەلگىلەيدۇ Emojis count sequNum originalFilename قوزغىتىلغان بولسا چىقىرىش سۈرىتىنىڭ نامىغا ئەسلى ھۆججەت نامىنى قوشىدۇ ئەسلى ھۆججەت نامىنى قوشۇڭ تەرتىپ نومۇرىنى ئالماشتۇرۇڭ ئەگەر قوزغىتىلغان بولسا تۈركۈملەپ بىر تەرەپ قىلىشنى ئىشلەتسىڭىز ئۆلچەملىك ۋاقىت تامغىسىنى رەسىم رەت نومۇرىغا ئالماشتۇرىدۇ ئەگەر رەسىم تاللىغۇچى رەسىم مەنبەسى تاللانغان بولسا ئەسلى ھۆججەت نامىنى قوشۇش ئىشلىمەيدۇ توردىن رەسىم يۈكلەڭ خالىغان رەسىمنى توردىن ئالدىن كۆرۈش ، چوڭايتىش ، تەھرىرلەش ۋە ساقلاش ئۈچۈن يۈكلەڭ. رەسىم يوق رەسىم ئۇلىنىشى تولدۇر Fit مەزمۇن كۆلىمى ھەر بىر رەسىمنى كەڭلىك ۋە ئېگىزلىك پارامېتىرى بەرگەن رەسىمگە زورلايدۇ - تەرەپ نىسبىتىنى ئۆزگەرتىشى مۇمكىن كەڭلىك ياكى ئېگىزلىك پارامېتىرى بەرگەن ئۇزۇن تەرىپى بىلەن رەسىملەرنىڭ چوڭ-كىچىكلىكىنى چوڭايتىدۇ ، بارلىق چوڭ-كىچىك ھېسابلاشلار تېجەپ بولغاندىن كېيىن ئېلىپ بېرىلىدۇ - تەرەپ نىسبىتىنى ساقلايدۇ. Brightness سېلىشتۇرما Hue Saturation سۈزگۈچ قوشۇڭ سۈزگۈچ بېرىلگەن رەسىملەرگە سۈزگۈچ زەنجىرىنى ئىشلىتىڭ سۈزگۈچ نۇر رەڭ سۈزگۈچ Alpha ئاشكارىلاش ئاق تەڭپۇڭلۇق تېمپېراتۇرا Tint Monochrome گامما يارقىن نۇقتىلار ۋە سايە يارقىن نۇقتىلار سايە تۇمان ئۈنۈم ئارىلىق يانتۇ Sharpen Sepia سەلبىي Solarize تەۋرىنىش قارا ۋە ئاق Crosshatch بوشلۇق قۇر كەڭلىكى Sobel edge Blur Halftone CGA رەڭ بوشلۇقى Gaussian blur Bilaterial blur Box blur Emboss Laplacian Vignette باشلاش ئاخىر Stack blur Kuwahara سىلىقلاش Radius تارازا بۇرمىلاش Angle Swirl Bulge Dilation دائىرە سۇندۇرۇش سۇندۇرۇش كۆرسەتكۈچى Sketch ئەينەك دائىرىسىنى سۇندۇرۇش رەڭلىك ماترىسسا Opacity چەكلىمىنىڭ چوڭ-كىچىكلىكى تاللانغان رەسىملەرنىڭ چوڭ-كىچىكلىكى ۋە كەڭلىك چەكلىمىسى بويىچە ئەگىشىڭ Threshold مىقدارلاشتۇرۇش دەرىجىسى يۇمىلاق چىش Toon Posterize ئەڭ چوڭ باستۇرۇش ئاجىز پىكسېلنى ئۆز ئىچىگە ئالىدۇ ئىزدەش Convolution 3x3 Reorder RGB سۈزگۈچ خاتا رەڭ بىرىنچى رەڭ تېز تۇتۇق ئىككىنچى رەڭ چوڭ-كىچىكلىكى Blur center x Blur center y چوڭايتىش رەڭ تەڭپۇڭلۇقى يورۇقلۇق دەرىجىسى سىز ھۆججەت دېتالىنى چەكلىدىڭىز ، ئۇنى بۇ ئىقتىدارنى ئىشلىتىش ئۈچۈن قوزغىتىڭ سىزىش سىزما دەپتىرىگە ئوخشاش رەسىمنى سىزىڭ ياكى تەگلىكنىڭ ئۆزىدە سىزىڭ رەڭگى رەڭدار ئالفا رەسىمگە سىزىڭ رەسىم تاللاڭ ۋە ئۇنىڭغا بىر نەرسە سىزىڭ تەگلىك سىزىڭ تەگلىك رەڭگىنى تاللاڭ ۋە ئۇنىڭ ئۈستىگە سىزىڭ تەگلىك رەڭگى Cipher AES crypto algorithm ئاساسىدىكى ھەرقانداق ھۆججەتنى (رەسىمنىلا ئەمەس) شىفىرلاش ۋە شىفىرلاش ھۆججەت تاللاڭ شىفىرلاش يېشىش باشلاش ئۈچۈن ھۆججەت تاللاڭ شىفىر يېشىش شىفىرلاش ئاچقۇچ ھۆججەت بىر تەرەپ قىلىندى بۇ ھۆججەتنى ئۈسكۈنىڭىزگە ساقلاڭ ياكى ئورتاقلىشىش ھەرىكىتىنى ئىشلىتىپ خالىغان يەرگە قويۇڭ Features ئەمەلىيلەشتۈرۈش ماسلىشىشچانلىقى ھۆججەتلەرنى مەخپىي شىفىرلاش. ئىشلەنگەن ھۆججەتلەرنى تاللانغان مۇندەرىجىدە ساقلىغىلى ياكى ئورتاقلىشقىلى بولىدۇ. شىفىرلانغان ھۆججەتلەرنىمۇ بىۋاسىتە ئاچقىلى بولىدۇ. AES-256 ، GCM ھالىتى ، تاختا يوق ، 12 بايىت ئىختىيارى IV. ئاچقۇچ SHA-3 hashes (256 bit) سۈپىتىدە ئىشلىتىلىدۇ. ھۆججەت چوڭلۇقى The maximum file size is restricted by the Android OS and available memory, which is device dependent. \nPlease note: memory is not storage. شۇنىڭغا دىققەت قىلىڭكى ، باشقا ھۆججەت مەخپىيلەشتۈرۈش يۇمشاق دېتالى ياكى مۇلازىمىتىگە ماسلىشىشچانلىقى كاپالەتكە ئىگە ئەمەس. سەل ئوخشىمايدىغان ئاچقۇچلۇق داۋالاش ياكى سىفىرلىق سەپلىمە ماسلاشماسلىقنى كەلتۈرۈپ چىقىرىشى مۇمكىن. ئىناۋەتسىز مەخپىي نومۇر ياكى تاللانغان ھۆججەت شىفىرلانمايدۇ بېرىلگەن كەڭلىك ۋە ئېگىزلىكتىكى رەسىمنى ساقلىماقچى بولسىڭىز OOM خاتالىقىنى كەلتۈرۈپ چىقىرىشى مۇمكىن. بۇنى ئۆزىڭىزنىڭ خەتىرىگە ئاساسەن قىلىڭ ، مەن سىزنى ئاگاھلاندۇرمىدىم دېمەڭ! Cache Cache size تېپىلدى %1$s ئاپتوماتىك ساقلىغۇچ تازىلاش ئەگەر قوزغىتىلغان ئەپ غەملەكلىرى قوزغالغاندا تازىلىنىدۇ قۇر قوراللار گۇرۇپپا تاللانمىلىرى گۇرۇپپا تاللاشلىرى ئاساسلىق تىزىملىكتىكى تۈرلەر بويىچە ئۇلارنىڭ تىزىملىكى بويىچە بولىدۇ تاللاش گۇرۇپپىلىرى قوزغىتىلغان ۋاقىتتا ئورۇنلاشتۇرۇشنى ئۆزگەرتەلمەيدۇ ئېكران رەسىمىنى تەھرىرلەش ئىككىلەمچى خاسلاشتۇرۇش ئېكران رەسىمى خاتالىق تاللاش ئاتلاش كۆچۈرۈڭ %1$s ھالەتتە تېجەش تۇراقسىز بولىدۇ ، چۈنكى ئۇ زىيانسىز فورمات ئالدىن بېكىتىلگەن 125 نى تاللىغان بولسىڭىز ، رەسىم 100% سۈپەتلىك ئەسلى رەسىمنىڭ% 125 چوڭلۇقىدا ساقلىنىدۇ. ئەگەر ئالدىن بېكىتىلگەن 50 نى تاللىسىڭىز ، ئۇنداقتا رەسىم% 50 چوڭلۇق ۋە% 50 سۈپەتلىك ساقلىنىدۇ. بۇ يەردىكى ئالدىن بەلگىلەش چىقىرىش ھۆججىتىنىڭ%% نى بەلگىلەيدۇ ، يەنى 5mb لىق رەسىمگە ئالدىن 50 نى تاللىسىڭىز ، ساقلىغاندىن كېيىن 2.5mb لىق رەسىمگە ئېرىشىسىز ھۆججەت نامىنى ئىختىيارىي قىلىڭ قوزغىتىلغان چىقىرىش ھۆججەت ئىسمى تولۇق ئىختىيارى بولىدۇ تم الحفظ في المجلد %1$s بالاسم %2$s %1$s ھۆججەت قىسقۇچىغا ساقلاندى تېلېگرامما پاراڭ بۇ دېتالنى مۇلاھىزە قىلىپ ، باشقا ئىشلەتكۈچىلەرنىڭ تەكلىپ-پىكىرلىرىگە ئېرىشىڭ. بۇ يەردىن سىناق يېڭىلانمىلىرى ۋە چۈشەنچىلىرىگە ئېرىشەلەيسىز. زىرائەت ماسكىسى نىسبەت نىسبىتى بۇ ماسكا تۈرىنى ئىشلىتىپ بېرىلگەن رەسىمدىن ماسكا ھاسىل قىلىڭ ، ئۇنىڭ ئالفا قانىلى بولۇشى كېرەكلىكىگە دىققەت قىلىڭ زاپاسلاش ۋە ئەسلىگە كەلتۈرۈش زاپاسلاش ئەسلىگە كەلتۈرۈش ئەپ تەڭشەكلىرىڭىزنى ھۆججەتكە زاپاسلاڭ ئىلگىرى ھاسىل قىلىنغان ھۆججەتتىن ئەپ تەڭشىكىنى ئەسلىگە كەلتۈرۈڭ بۇزۇلغان ھۆججەت ياكى زاپاسلاش ئەمەس تەڭشەك مۇۋەپپەقىيەتلىك ئەسلىگە كەلدى مەن بىلەن ئالاقىلىشىڭ بۇ تەڭشەكلىرىڭىزنى سۈكۈتتىكى قىممەتكە قايتۇرىدۇ. دىققەت ، يۇقىرىدا تىلغا ئېلىنغان زاپاس ھۆججەت بولمىسا ، بۇنى ئەمەلدىن قالدۇرغىلى بولمايدۇ. ئۆچۈرۈش تاللانغان رەڭ لايىھىسىنى ئۆچۈرمەكچى بولۇۋاتىسىز. بۇ مەشغۇلاتنى ئەمەلدىن قالدۇرغىلى بولمايدۇ لايىھەنى ئۆچۈرۈڭ خەت نۇسخىسى تېكىست خەت نۇسخىسى سۈكۈتتىكى چوڭ خەت نۇسخىسىنى ئىشلىتىش UI كاشىلا ۋە مەسىلىلەرنى كەلتۈرۈپ چىقىرىشى مۇمكىن ، بۇ ئوڭشالمايدۇ. ئېھتىيات بىلەن ئىشلىتىڭ. ا ە ب پ ت ج چ خ د ر ز ژ س ش غ ف ق ك گ ڭ ل م ن ھ و ۇ ۆ ۈ ۋ ی 0123456789 !؟ ھېسسىيات يېمەكلىك ۋە ئىچىملىك تەبىئەت ۋە ھايۋانلار ئوبيېكت بەلگىلەر Emoji نى قوزغىتىڭ ساياھەت ۋە ئورۇن پائالىيەت تەگلىك ئۆچۈرۈش رەسىم ئارقىلىق تەگلىكنى ئۆچۈرۈڭ ياكى ئاپتوماتىك تاللاشنى ئىشلىتىڭ Trim image ئەسلى رەسىم مېتا سانلىق مەلۇماتلىرى ساقلىنىدۇ رەسىم ئەتراپىدىكى سۈزۈك بوشلۇقلار رەتلىنىدۇ تەگلىكنى ئاپتوماتىك ئۆچۈرۈڭ رەسىمنى ئەسلىگە كەلتۈرۈش ئۆچۈرۈش ھالىتى تەگلىكنى ئۆچۈرۈڭ تەگلىكنى ئەسلىگە كەلتۈرۈش رادىئاتسىيە Pipette سىزىش ھالىتى مەسىلە قۇرۇش ئوۋ… بىر نەرسە خاتا بولدى. تۆۋەندىكى تاللاشلارنى ئىشلىتىپ ماڭا خەت يازسىڭىز بولىدۇ ، مەن ھەل قىلىش چارىسى تېپىشقا تىرىشىمەن رازمېرى ۋە ئايلاندۇرۇش بېرىلگەن رەسىملەرنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرتىڭ ياكى باشقا فورماتلارغا ئۆزگەرتىڭ. EXIF مېتا سانلىق مەلۇماتلىرىنى بۇ يەردە تەھرىرلىگىلى بولىدۇ. ئەڭ چوڭ رەڭ سانى بۇ ئەپنىڭ كاشىلا دوكلاتىنى قولدا توپلىشىغا يول قويىدۇ Analytics نامسىز ئەپ ئىشلىتىش ستاتىستىكىسىنى توپلاشقا يول قويۇڭ ھازىر %1$s فورماتى پەقەت ئاندىرويىدتا EXIF مېتا سانلىق مەلۇماتلىرىنى ئوقۇشقا يول قويىدۇ. چىقىرىلغان رەسىم ساقلانغان ۋاقىتتا مېتا سانلىق مەلۇماتقا ئېرىشەلمەيدۇ. تىرىشچانلىق القيمة %1$s تعني ضغطًا سريعًا، مما يؤدي إلى حجم ملف كبير نسبيًا. %2$s يعني ضغطًا أبطأ، مما يؤدي إلى ملف أصغر. ساقلاپ تۇرۇڭ تېجەش ئاساسەن تاماملاندى. ھازىر ئەمەلدىن قالدۇرۇش قايتا تېجەشنى تەلەپ قىلىدۇ. يېڭىلانمىلار Betas غا يول قويۇڭ يېڭىلاش تەكشۈرۈش قوزغىتىلغان بولسا beta ئەپ نۇسخىسىنى ئۆز ئىچىگە ئالىدۇ يا ئوق سىزىش ئەگەر قوزغىتىلغان سىزىش يولى كۆرسەتكۈچ يا ئوق سۈپىتىدە ئىپادىلىنىدۇ چوتكىلاش يۇمشاق رەسىملەر چوڭ-كىچىكلىكى بويىچە كېسىلىدۇ. ئەگەر رەسىم كىرگۈزۈلگەن ئۆلچەمدىن كىچىك بولسا ، Canvas بېرىلگەن تەگلىك رەڭگى بىلەن كېڭەيتىلىدۇ. ئىئانە رەسىم تىكىش بېرىلگەن رەسىملەرنى بىرلەشتۈرۈپ بىر چوڭ رەسىمگە ئېرىشىڭ كەم دېگەندە 2 پارچە رەسىم تاللاڭ چىقىرىش سۈرىتى رەسىم يۆنىلىشى توغرىسىغا ۋېرتىكال كىچىك رەسىملەرنى چوڭايتىڭ كىچىك رەسىملەر قوزغىتىلسا تەرتىپ بويىچە ئەڭ چوڭ رەسىمگە تارتىلىدۇ رەسىم تەرتىپى دائىملىق قىرغاق ئەسلى رەسىمنىڭ ئاستىدا تۇتۇق گىرۋەكلەرنى سىزىپ ، قوزغىتىلسا ئەتراپىدىكى بوشلۇقنى تاق رەڭنىڭ ئورنىغا تولدۇرىدۇ Pixelation كۈچەيتىلگەن Pixelation سەكتە Pixelation كۈچەيتىلگەن ئالماس پىكسېل Diamond Pixelation Circle Pixelation كۈچەيتىلگەن چەمبەر پىكسېل رەڭنى ئالماشتۇرۇڭ كەڭ قورساقلىق ئالماشتۇرۇشنىڭ رەڭگى نىشان رەڭ ئۆچۈرۈش ئۈچۈن رەڭ رەڭنى ئۆچۈرۈڭ Recode Pixel Size قۇلۇپ سىزىش يۆنىلىشى ئەگەر رەسىم سىزىش شەكلىدە قوزغىتىلسا ، ئېكران ئايلانمايدۇ يېڭىلانمىلارنى تەكشۈرۈڭ Palette style Tonal Spot بىتەرەپ جۇشقۇن ئىپادىلەش Rainbow مېۋە سالات Fidelity مەزمۇن سۈكۈتتىكى پالتا ئۇسلۇبى ، ئۇ تۆت خىل رەڭنىڭ ھەممىسىنى خاسلاشتۇرالايدۇ ، باشقىلار پەقەت ئاچقۇچلۇق رەڭنى تەڭشىيەلەيدۇ يەككە ئۇسلۇبقا قارىغاندا سەل خىروم ئۇسلۇب يۇقىرى ئاۋازلىق باشتېما ، رەڭدارلىقى ئەڭ چوڭ بولۇپ ، باشقىلار ئۈچۈن كۆپەيتىلگەن ئوينايدىغان تېما - ئەسلى رەڭنىڭ رەڭگى باشتېمىدا كۆرۈنمەيدۇ يەككە ئۇسلۇب ، رەڭلەر پۈتۈنلەي قارا / ئاق / كۈلرەڭ مەنبە رەڭنى Scheme.primaryContainer غا ئورۇنلاشتۇرىدىغان لايىھە مەزمۇن پىلانىغا ناھايىتى ئوخشايدىغان لايىھە بۇ يېڭىلاش تەكشۈرگۈچى يېڭى يېڭىلانمىنىڭ بار-يوقلۇقىنى تەكشۈرۈش سەۋەبىدىن GitHub غا ئۇلىنىدۇ دىققەت Fading Edges چەكلەنگەن ھەر ئىككىلىسى رەڭلەرنى ئۆزگەرتىش قوزغىتىلسا ئۇسلۇب رەڭلىرىنى مەنپىي رەڭگە ئالماشتۇرىدۇ ئىزدەش ئاساسىي ئېكراندىكى بارلىق تاللاشلارنى ئىزدەش ئىقتىدارىنى قوزغىتىدۇ PDF قوراللىرى PDF ھۆججىتى بىلەن مەشغۇلات قىلىڭ: ئالدىن كۆرۈش ، بىر تۈركۈم رەسىملەرگە ئايلاندۇرۇش ياكى بېرىلگەن رەسىملەردىن بىرنى قۇرۇش PDF ئالدىن كۆرۈش رەسىملەرگە PDF رەسىملەر PDF بېرىلگەن چىقىرىش فورماتىدا PDF نى رەسىمگە ئايلاندۇرۇڭ ئاددىي PDF ئالدىن كۆرۈش بېرىلگەن رەسىملەرنى PDF ھۆججىتىگە قاچىلاڭ ماسكا سۈزگۈچ بېرىلگەن نىقابلانغان رايونلارغا سۈزگۈچ زەنجىر ئىشلىتىڭ ، ھەر بىر ماسكا رايونى ئۇنىڭ ئۆزىنىڭ سۈزگۈچنى بەلگىلىيەلەيدۇ ماسكا ماسكا قوشۇڭ ماسكا %d ماسكا رەڭگى ماسكا ئالدىن كۆرۈش سىزىلغان سۈزگۈچ نىقاب سىزگە تەخمىنىي نەتىجىنى كۆرسىتىپ بېرىدۇ تەتۈر تولدۇرۇش تىپى ئەگەر قوزغىتىلغان بولسا نىقابلانمىغان رايونلارنىڭ ھەممىسى سۈكۈتتىكى ھەرىكەتنىڭ ئورنىغا سۈزۈلىدۇ تاللانغان سۈزگۈچ نىقابىنى ئۆچۈرمەكچى بولۇۋاتىسىز. بۇ مەشغۇلاتنى ئەمەلدىن قالدۇرغىلى بولمايدۇ ماسكىنى ئۆچۈرۈڭ تولۇق سۈزگۈچ بېرىلگەن رەسىم ياكى يەككە رەسىمگە سۈزگۈچ زەنجىرىنى ئىشلىتىڭ باشلاش Center ئاخىر ئاددىي ۋارىيانتلار Highlighter Neon قەلەم مەخپىيەتلىك بىلوگى يېرىم سۈزۈك ئۆتكۈر يورۇتۇش يولىنى سىزىڭ رەسىملىرىڭىزگە ئازراق پارقىراق ئۈنۈم قوشۇڭ سۈكۈتتىكىسى ، ئەڭ ئاددىي - پەقەت رەڭ سىز يوشۇرماقچى بولغان ھەر قانداق نەرسىگە كاپالەتلىك قىلىش ئۈچۈن سىزىلغان يولنىڭ ئاستىدىكى رەسىمنى خىرەلەشتۈرۈڭ مەخپىيەتلىكنى قالايمىقانلاشتۇرۇۋەتكەنگە ئوخشاش ، ئەمما پېكسىللاشنىڭ ئورنىغا كونتېينېر قاچىلارنىڭ ئارقىسىدا سايە سىزىشنى قوزغىتىدۇ سىيرىلغۇچ ئالماشتۇرغۇچ FABs كۇنۇپكىلار سىيرىلغۇچنىڭ ئارقىسىدا سايە سىزىشنى قوزغىتىدۇ ئالماشتۇرغۇچنىڭ ئارقىسىدا سايە سىزىشنى قوزغىتىدۇ لەيلىمە ھەرىكەت كۇنۇپكىلىرىنىڭ ئارقىسىدا سايە سىزىشنى قوزغىتىدۇ سۈكۈتتىكى كۇنۇپكىلارنىڭ ئارقىسىدا سايە سىزىشنى قوزغىتىدۇ App Bars ئەپ بالدىقىنىڭ ئارقىسىدا سايە سىزىشنى قوزغىتىدۇ دائىرە قىممىتى %1$s - %2$s Auto Rotate رەسىم يۆنىلىشى ئۈچۈن چەك ساندۇقىنىڭ قوللىنىلىشىغا يول قويىدۇ يول ھالىتىنى سىزىڭ قوش سىزىقلىق ئوق ھەقسىز رەسىم سىزىش قوش ئوق Line Arrow يا ئوق Line كىرگۈزۈش قىممىتى سۈپىتىدە يول سىزىدۇ باشلىنىش نۇقتىسىدىن ئاخىرىغىچە بىر سىزىق سۈپىتىدە يول سىزىدۇ ئوقنى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە بىر قۇر قىلىپ سىزىدۇ بېرىلگەن يولدىن ئوقنى كۆرسىتىدۇ قوش يۆنىلىشلىك ئوقنى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىق قىلىپ سىزىدۇ بېرىلگەن يولدىن قوش يۆنىلىشلىك يا ئوق سىزىدۇ البيضاوي المبين كۆرسىتىلگەن تۈز بيضاوي تۈز باشلىنىش نۇقتىسىدىن ئاخىرقى نۇقتىغا توغرىلىنىدۇ تۇخۇمنى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىدۇ ھەقسىز Horizontal Grid Vertical Grid تۇخۇمنى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىدۇ سىزىقنى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىدۇ Lasso بېرىلگەن يول بىلەن تولغان يولنى سىزىدۇ تىكىش ھالىتى قۇر سانى ستون سانى لم يتم العثور على دليل \"%1$s\"، لقد قمنا بتحويله إلى الدليل الافتراضي، يرجى حفظ الملف مرة أخرى Auto pin چاپلاش تاختىسى قوزغىتىلغان بولسا ئاپتوماتىك ھالدا ساقلانغان رەسىمنى چاپلاش تاختىسىغا قوشىدۇ تەۋرىنىش تەۋرىنىش كۈچى Explorer \" رەسىم مەنبەسىنى ئىشلىتىشكە ئېھتىياجلىق ھۆججەتلەرنى قاپلىۋېلىش ئۈچۈن ، رەسىملەرنى قايتا سىناپ بېقىڭ ، بىز رەسىم مەنبەسىنى لازىملىق ھۆججەتكە ئۆزگەرتتۇق. ھۆججەتلەرنى قاپلىۋېتىڭ سيتم استبدال الملف الأصلي بملف جديد بدلاً من حفظه في المجلد المحدد، ويجب أن يكون هذا الخيار مصدر الصورة هو Explorer أو GetContent، وعند تبديل هذا، سيتم تعيينه تلقائيًا قۇرۇق Suffix كۆلەم ھالىتى Bilinear Catmull Bicubic Hann Hermite Lanczos مىچىل ئەڭ يېقىن Spline Basic كۆڭۈلدىكى قىممەت سىزىقلىق (ياكى قوش يۆنىلىشلىك ، ئىككى ئۆلچەملىك) ئارىلىشىش ئادەتتە رەسىمنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرتىشكە پايدىلىق ، ئەمما بەزى ئىنچىكە ھالقىلارنىڭ يۇمشىشىنى كەلتۈرۈپ چىقىرىدۇ ، يەنىلا مەلۇم دەرىجىدە چېتىلىپ قالىدۇ. تېخىمۇ ياخشى كۆلەملەشتۈرۈش ئۇسۇللىرى Lanczos قايتا قۇرۇش ۋە Mitchell-Netravali سۈزگۈچنى ئۆز ئىچىگە ئالىدۇ چوڭ-كىچىكلىكىنى ئاشۇرۇشنىڭ ئەڭ ئاددىي ئۇسۇللىرىنىڭ بىرى ، ھەر بىر پېكسىلنى ئوخشاش رەڭدىكى بىر قانچە پېكسىلغا ئالماشتۇرۇش بارلىق ئەپلەردە دېگۈدەك ئىشلىتىلىدىغان ئەڭ ئاددىي ئاندىرويىد كىچىكلىتىش ھالىتى بىر يۈرۈش كونترول نۇقتىلىرىنى ئوڭۇشلۇق ئۆزئارا باغلاش ۋە قايتا قۇرۇش ئۇسۇلى ، ئادەتتە كومپيۇتېر گرافىكىدا سىلىق ئەگرى سىزىق ھاسىل قىلىشقا ئىشلىتىلىدۇ كۆزنەك ئىقتىدارى دائىم سىگنال بىر تەرەپ قىلىشتا قوللىنىلىپ ، سپېكترا ئېقىپ كېتىشنى ئەڭ تۆۋەن چەككە چۈشۈرۈپ ، سىگنالنىڭ گىرۋىكىنى چېكىش ئارقىلىق چاستوتا ئانالىزىنىڭ توغرىلىقىنى ئۆستۈرىدۇ. ماتېماتىكىلىق ئۆزئارا بىرلەشتۈرۈش تېخنىكىسى ئەگرى سىزىقنىڭ ئاخىرقى نۇقتىسىدىكى قىممەت ۋە تۇغۇندى مەھسۇلاتلارنى ئىشلىتىپ سىلىق ۋە ئۈزلۈكسىز ئەگرى سىزىق ھاسىل قىلىدۇ. پىكسېل قىممىتىگە ئېغىر دەرىجىدىكى سىنك فۇنكسىيەسىنى قوللىنىش ئارقىلىق يۇقىرى سۈپەتلىك ئۆزئارا ماسلىشىشنى ساقلايدىغان ئۇسۇل تەڭشىگىلى بولىدىغان پارامېتىرلار ئارقىلىق تەۋرىنىش سۈزگۈچنى ئىشلىتىپ ، كىچىكلىتىلگەن رەسىمدىكى ئۆتكۈرلۈك ۋە قارشىلىشىشقا قارشى تەڭپۇڭلۇقنى ئەمەلگە ئاشۇرۇش. ئوخشاش ئېنىقلىما بېرىلگەن كۆپ قۇتۇپلۇق ئىقتىداردىن پايدىلىنىپ ئەگرى سىزىق ياكى يۈزنى سىلىق ئۆز-ئارا ماسلاشتۇرىدۇ ۋە يېقىنلاشتۇرىدۇ ، جانلىق ۋە ئۈزلۈكسىز شەكىل ئىپادىلەيدۇ. پەقەت Clip ساقلاشقا ساقلاش ئەمەلگە ئاشمايدۇ ، رەسىم پەقەت چاپلاش تاختىسىغا سېلىشقا ئۇرۇنىدۇ چوتكا ئۆچۈرۈشنىڭ ئورنىغا تەگلىكنى ئەسلىگە كەلتۈرىدۇ OCR (تېكىستنى تونۇش) بېرىلگەن رەسىمدىن تېكىستنى ئېتىراپ قىلىڭ ، 120+ تىل قوللايدۇ رەسىمنىڭ تېكىستى يوق ، ياكى ئەپ تاپالمىدى Accuracy: %1$s تونۇش تىپى تېز ئۆلچەملىك ئەڭ ياخشى سانلىق مەلۇمات يوق من أجل التشغيل السليم لـ Tesseract OCR، يجب تنزيل بيانات التدريب الإضافية (%1$s) على جهازك. \nهل تريد تنزيل بيانات %2$s؟ چۈشۈرۈش ئۇلىنىش يوق ، ئۇنى تەكشۈرۈپ پويىز مودېللىرىنى چۈشۈرۈش ئۈچۈن قايتا سىناڭ چۈشۈرۈلگەن تىللار ئىشلەتكىلى بولىدىغان تىللار بۆلەك ھالىتى Pixel Switch نى ئىشلىتىڭ Pixel غا ئوخشاش ئالماشتۇرغۇچ google نىڭ ماتېرىيالىنىڭ ئورنىغا ئىشلىتىلىدۇ ئەسلى مەنزىلدە {8 name ئىسمى يېزىلغان ھۆججەتتمت الكتابة فوق الملف بالاسم %1$s في الوجهة الأصلية Magnifier تېخىمۇ ياخشى زىيارەت قىلىش ئۈچۈن رەسىم سىزىش شەكلىدە بارماقنىڭ ئۈستىدىكى چوڭايتىشنى قوزغىتىدۇ كىچىك قوراللار دەسلەپتە تەكشۈرۈلىدۇ دەسلەپكى قىممەتنى زورلاڭ كۆپ خىل تىللارغا يول قويۇڭ تام تەسۋىر Side by Side Toggle Tap Transparency Rate App باھا بۇ دېتال پۈتۈنلەي ھەقسىز ، ئەگەر ئۇنىڭ تېخىمۇ چوڭ بولۇشىنى ئۈمىد قىلسىڭىز ، Github on تۈرىنى باشلاڭ يۆنىلىش & قوليازما بايقاش ئاپتوماتىك يۆنىلىش & قوليازما بايقاش پەقەت ئاپتوماتىك ئاپتوماتىك يەككە ئىستون تاق بۆلەك تىك تېكىست تاق بۆلەك يەككە سىزىق يەككە سۆز ئايلانما سۆز Single char قىسقا تېكىست شالاڭ تېكىست يۆنىلىشى & قوليازما بايقاش خام سىزىق هل تريد حذف بيانات التدريب على التعرف الضوئي على الحروف الخاصة باللغة \"%1$s\" لجميع أنواع التعرف، أم للنوع المحدد فقط (%2$s)؟ نۆۋەتتىكى ھەممىسى Gradient Maker خاسلاشتۇرۇلغان رەڭ ۋە كۆرۈنۈش تىپى بىلەن بېرىلگەن چىقىرىش چوڭلۇقىنىڭ دەرىجىسىنى تەدرىجىي ھاسىل قىلىڭ Linear Radial Sweep Gradient Type Center X. Center Y. Tile Mode تەكرارلاندى ئەينەك Clamp Decal رەڭ توختايدۇ رەڭ قوشۇڭ خاسلىقى يورۇقلۇقنى ئىجرا قىلىش ئېكران Gradient Overlay بېرىلگەن رەسىمنىڭ ئۈستىدىكى ھەر قانداق گرادېنتنى تۈزۈڭ ئۆزگەرتىش كامېرا رەسىمگە تارتىش ئۈچۈن كامېرا ئىشلىتىپ ، بۇ رەسىم مەنبەسىدىن پەقەت بىرلا رەسىمگە ئېرىشكىلى بولىدىغانلىقىغا دىققەت قىلىڭ سۇ بەلگىسىنى تەكرارلاڭ سۇ بەلگىسى رەسىملەرنى خاسلاشتۇرغىلى بولىدىغان تېكىست / رەسىم سۇ بەلگىسى بىلەن يېپىش بېرىلگەن ئورۇنغا يەككە ئورنىدا رەسىم بەلگىسىنى تەكرارلايدۇ Offset X. Offset Y. بۇ رەسىم سۇ بەلگىسى ئۈچۈن ئەندىزە سۈپىتىدە ئىشلىتىلىدۇ تېكىست رەڭ قاپلاش ھالىتى GIF قوراللىرى رەسىملەرنى GIF رەسىمىگە ئايلاندۇرۇڭ ياكى بېرىلگەن GIF رەسىمدىن رامكا ئېلىڭ رەسىملەرگە GIF GIF ھۆججىتىنى بىر تۈركۈم رەسىملەرگە ئايلاندۇرۇڭ بىر تۈركۈم رەسىملەرنى GIF ھۆججىتىگە ئايلاندۇرۇڭ GIF غا رەسىملەر باشلاش ئۈچۈن GIF رەسىمىنى تاللاڭ بىرىنچى رامكىنىڭ چوڭ-كىچىكلىكىنى ئىشلىتىڭ Confetti بەلگىلەنگەن چوڭلۇقنى بىرىنچى رامكا ئۆلچىمى بىلەن ئالماشتۇرۇڭ قايتىلاش Frame Delay millis FPS Lasso نى ئىشلىتىڭ ئۆچۈرۈش ئۈچۈن Lasso نى رەسىم سىزىش ھالىتىگە ئوخشاش ئىشلىتىدۇ ئەسلى رەسىمنى كۆرۈش ئالفا Confetti تېجەش ، ئورتاقلىشىش ۋە باشقا دەسلەپكى ھەرىكەتلەردە كۆرسىتىلىدۇ بىخەتەر ھالەت چىقىشتىكى مەزمۇننى يوشۇرىدۇ ، ئېكراننىمۇ خاتىرىلىگىلى ياكى خاتىرىلىگىلى بولمايدۇ چىقىش ئەگەر ئالدىن كۆرۈشتىن ئايرىلسىڭىز ، رەسىملەرنى قايتا قوشۇشىڭىز كېرەك Dithering Quantizier كۈلرەڭ تارازا Bayer Two By Two Dithering Bayer Three By Three Dithering Bayer Four By Four Dithering Bayer Eight By Sight Dithering Floyd Steinberg Dithering Jarvis Judice Ninke Dithering Sierra Dithering ئىككى قاتار Sierra Dithering Sierra Lite Dithering Atkinson Dithering Stucki Dithering Burkes Dithering يالغان Floyd Steinberg Dithering سولدىن ئوڭغا بۇرۇلۇش Random Dithering ئاددىي بوسۇغا يۆلىنىش سىگما بوشلۇقتىكى سىگما Median Blur B Spline ئايرىم ئېنىقلانغان ئىككى قۇتۇپلۇق كۆپ ئىقتىدارلىق فۇنكسىيەدىن پايدىلىنىپ ، ئەگرى سىزىق ياكى يۈزنى سىلىق ئۆز-ئارا ماسلاشتۇرىدۇ ۋە يېقىنلاشتۇرىدۇ ، جانلىق ۋە ئۈزلۈكسىز شەكىل ئىپادىلەيدۇ. Native Stack Blur Tilt Shift Glitch سومما ئۇرۇق Anaglyph شاۋقۇن Pixel Sort Shuffle كۈچەيتىلگەن Glitch Channel Shift X. Channel Shift Y. چىرىكلىك كۆلىمى چىرىكلىك Shift X. چىرىكلىك Shift Y. Tent Blur Side Fade يان تەرەپ ئۈستى ئاستى كۈچ Erode Anisotropic Diffusion Diffusion Conduct گورىزونتال شامال تەۋرىنىشى Fast Bilaterial Blur Poisson Blur لوگارىزىملىق ئاۋاز خەرىتىسى ACES كىنو ئاۋاز خەرىتىسى Crystallize سەكتە رەڭگى سۇنۇق ئەينەك Amplitude مەرمەر تۇراقسىزلىق نېفىت Water Effect چوڭلۇقى چاستوتا X. Frequency Y. Amplitude X. Amplitude Y. پېرلىن بۇرمىلىنىش ACES Hill Tone خەرىتىسى Hable Filmic Tone Mapping Hejl Burgess Tone Mapping سۈرئەت Dehaze Omega رەڭ Matrix 4x4 رەڭ Matrix 3x3 ئاددىي ئۈنۈم Deutaromaly Protonomaly Vintage Browni Coda Chrome Night Vision ئىللىق Cool Tritanopia Deutaronotopia دان Protanopia Achromatomaly Achromatopsia Unsharp Pastel ئاپېلسىن تۇمان ھالرەڭ چۈش ئالتۇن سائەت ئىسسىق ياز بىنەپشە تۇمان Sunrise رەڭلىك سۋرەت يۇمشاق بۇلاق نۇرى كۈزلۈك ئاھاڭ Lavender Dream Cyberpunk لىمون نۇرى Spectral Fire Night Magic فانتازىيىلىك مەنزىرە رەڭ پارتىلاش Electric Gradient Caramel Darkness Futuristic Gradient يېشىل قۇياش Rainbow World Deep Purple ئالەم بوشلۇقى Red Swirl رەقەملىك كود Bokeh ئەپ بالدىقى emoji تاللانغاننى ئىشلىتىشنىڭ ئورنىغا تاسادىپىي ئۆزگەرتىلىدۇ ئىختىيارى Emojis Emojis چەكلەنگەندە تاسادىپىي emoji تاللاشقا بولمايدۇ ئىختىيارىي تاللانغان ۋاقىتتا emoji نى تاللىيالمايدۇ Old Tv Shuffle Blur ئامراق ياقتۇرىدىغان سۈزگۈچلەر تېخى قوشۇلمىدى Uchimura Mobius ئۆتكۈنچى چوققا Color Anomaly ئەسلى مەنزىلگە يېزىلغان رەسىملەر ھۆججەتلەرنى قايتا يېزىش ئىقتىدارى قوزغىتىلغاندا رەسىم فورماتىنى ئۆزگەرتەلمەيدۇ Emoji رەڭ لايىھىسى سۈپىتىدە Emoji دەسلەپكى رەڭنى قولدا بېكىتىلگەن رەڭنىڭ ئورنىغا ئەپ رەڭ لايىھىسى سۈپىتىدە ئىشلىتىڭ كارتىنىڭ ئالدىنقى سىنبەلگىسى ئاستىدا تاللانغان شەكىلدىكى قاچا قوشىدۇ سىنبەلگە شەكلى ئېغىرلىقى بويىچە چوڭايتىڭ KB دىكى ئەڭ چوڭ چوڭلۇقى KB دا بېرىلگەن چوڭلۇقتىكى رەسىمنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرتىڭ سېلىشتۇرۇش بېرىلگەن ئىككى رەسىمنى سېلىشتۇرۇڭ باشلاش ئۈچۈن ئىككى رەسىمنى تاللاڭ رەسىملەرنى تاللاڭ تەڭشەك كەچلىك ھالەت قاراڭغۇ نۇر سىستېما ھەرىكەتچان رەڭلەر خاسلاشتۇرۇش رەسىم پۇلغا يول قويۇڭ ئەگەر قوزغىتىلغان بولسا ، تەھرىرلەيدىغان رەسىمنى تاللىسىڭىز ، بۇ رەسىمگە ئەپ رەڭلىرى قوللىنىلىدۇ تىل ھەرىكەتچان رەڭلەر ئېچىلغاندا ئەپ رەڭ لايىھىسىنى ئۆزگەرتەلمەيدۇ ئەپ تېمىسى تاللانغان رەڭنى ئاساس قىلىدۇ ئېلخەت سۇ بەلگىسى تىپى Polaroid Tritonomaly رەسىم فورماتى «Jetpack Compose» كودى سۈپىتىدە كۆچۈرۈڭ رەسىمدىن «Material You » پالتىنى ھاسىل قىلىدۇ قېنىق رەڭلەر يورۇقلۇق ۋارىيانتىنىڭ ئورنىغا كەچلىك ھالەت رەڭ لايىھىسىنى ئىشلىتىدۇ ئۈزۈك تۇتۇق كرېست تۇتۇق چەمبىرەك تۇتۇق چولپان تۇتۇق سىزىقلىق يانتۇ بۇرۇلۇش ئۆچۈرۈش ئۈچۈن خەتكۈچلەر APNG قوراللىرى رەسىملەرنى APNG رەسىمىگە ئايلاندۇرۇڭ ياكى بېرىلگەن APNG رەسىمدىن رامكا ئېلىڭ رەسىملەرگە APNG APNG غا رەسىملەر باشلاش ئۈچۈن APNG رەسىمىنى تاللاڭ ھەرىكەت APNG ھۆججىتىنى بىر تۈركۈم رەسىملەرگە ئايلاندۇرۇڭ بىر تۈركۈم رەسىملەرنى APNG ھۆججىتىگە ئايلاندۇرۇڭ Zip بېرىلگەن ھۆججەت ياكى رەسىملەردىن Zip ھۆججىتى قۇرۇش سۆرەش كەڭلىكى Confetti Type بايرام پارتىلاش يامغۇر بۇلۇڭلار JXL قوراللىرى سۈپەتسىز زىيانسىز JXL ~ JPEG كودلاشنى ئىجرا قىلىڭ ياكى GIF / APNG نى JXL كارتونغا ئايلاندۇرۇڭ JXL دىن JPEG JXL دىن JPEG غا زىيانسىز كودلاشنى ئىجرا قىلىڭ JPEG دىن JXL غا زىيانسىز كودلاشنى ئىجرا قىلىڭ JPEG دىن JXL باشلاش ئۈچۈن JXL رەسىمىنى تاللاڭ Fast Gaussian Blur 2D Fast Gaussian Blur 3D Fast Gaussian Blur 4D ماشىنا پاسخا بايرىمى ئەپنىڭ چاپلاش تاختىسى سانلىق مەلۇماتلىرىنى ئاپتوماتىك چاپلىشىغا يول قويىدۇ ، شۇڭا ئۇ ئاساسلىق ئېكراندا كۆرۈنىدۇ ھەمدە ئۇنى بىر تەرەپ قىلالايسىز ماسلاشتۇرۇش رەڭگى ماسلىشىش دەرىجىسى Lanczos Bessel پېكسىل قىممىتىگە Bessel (jinc) ئىقتىدارىنى قوللىنىش ئارقىلىق يۇقىرى سۈپەتلىك ئۆز-ئارا ماسلىشىشنى ساقلايدىغان قايتا ئىشلىتىش ئۇسۇلى GIF to JXL GIF رەسىملىرىنى JXL كارتون رەسىملىرىگە ئايلاندۇرۇڭ APNG دىن JXL APNG رەسىملىرىنى JXL كارتون رەسىمگە ئايلاندۇرۇڭ JXL to Images JXL كارتوننى بىر تۈركۈم رەسىملەرگە ئايلاندۇرۇڭ JXL غا رەسىملەر بىر تۈركۈم رەسىملەرنى JXL كارتونغا ئايلاندۇرۇڭ Behavior ھۆججەت تاللاشتىن ئاتلاش ئەگەر تاللانغان ئېكراندا مۇمكىن بولسا ھۆججەت تاللىغۇچ دەرھال كۆرسىتىلىدۇ ئالدىن كۆرۈش ھاسىل قىلىڭ ئالدىن كۆرۈش ئەۋلادلىرىنى قوزغىتىدۇ ، بۇ بەلكىم بەزى ئۈسكۈنىلەردە سوقۇلۇشتىن ساقلىنىشى مۇمكىن ، بۇ يەككە تەھرىرلەش تاللانمىلىرى ئىچىدىكى بەزى تەھرىرلەش ئىقتىدارلىرىنىمۇ چەكلەيدۇ. Lossy Compression زىيان تارتماي ، ھۆججەتنىڭ چوڭ-كىچىكلىكىنى ئازايتىش ئۈچۈن زىيانلىق پىرىسلاشنى ئىشلىتىدۇ پىرىسلاش تىپى ھاسىل بولغان رەسىم يېشىش سۈرئىتىنى كونترول قىلىدۇ ، بۇ ھاسىل بولغان رەسىمنىڭ تېزرەك ئېچىلىشىغا ياردەم بېرىشى كېرەك ، %1$s نىڭ قىممىتى يېشىش سۈرئىتىنىڭ ئەڭ ئاستا ئىكەنلىكىنى بىلدۈرىدۇ ، ھالبۇكى %2$s - ئەڭ تېز ، بۇ تەڭشەك چىقىرىش رەسىمنىڭ چوڭ-كىچىكلىكىنى ئاشۇرۇشى مۇمكىن. تەرتىپلەش چېسلا چېسلا (قايتۇرۇلغان) ئىسمى ئىسمى (قايتۇرۇلغان) قانال سەپلىمىسى بۈگۈن تۈنۈگۈن قىستۇرما تاللىغۇچ رەسىم قورال ساندۇقىنىڭ رەسىم تاللىغۇچ رۇخسەت يوق تەلەپ كۆپ مېدىيانى تاللاڭ يەككە مېدىيانى تاللاڭ تاللاڭ قايتا سىناڭ مەنزىرە رايونىدا تەڭشەكلەرنى كۆرسەت ئەگەر بۇ چەكلەنگەن بولسا ، مەنزىرە ھالىتى تەڭشىكىدە دائىم كۆرۈلىدىغان تاللاشنىڭ ئورنىغا ئۈستۈنكى ئەپ بالدىقىدىكى كۇنۇپكا ئېچىلىدۇ تولۇق ئېكران تەڭشىكى ئۇنى قوزغىتىڭ ۋە تەڭشەك بېتى سىيرىلما تارتما جەدۋەلنىڭ ئورنىغا ھەمىشە تولۇق ئېكران سۈپىتىدە ئېچىلىدۇ ئالماشتۇرۇش تىپى Compose سىز ئالماشتۇرغان Jetpack بىرىكمە ماتېرىيال سىز ئالماشتۇرىدىغان ماتېرىيال Max لەڭگەرنىڭ چوڭ-كىچىكلىكى Pixel راۋان \ \"راۋان \" لايىھىلەش سىستېمىسىنى ئاساس قىلغان ئالماشتۇرغۇچ Cupertino \ \"Cupertino \" لايىھىلەش سىستېمىسىنى ئاساس قىلغان ئالماشتۇرغۇچ SVG غا رەسىملەر SVG رەسىملىرىگە بېرىلگەن رەسىملەرنى ئىز قوغلاڭ Sampled Palette نى ئىشلىتىڭ ئەگەر بۇ تاللاش قوزغىتىلسا مىقدارلاشتۇرۇش پالتىسى ئەۋرىشكە ئېلىنىدۇ Path Omit بۇ قورالنى كىچىك رەسىملەرنى ئىز قوغلاشتا ئىشلىتىش تەۋسىيە قىلىنمايدۇ ، ئۇ كاشىلا پەيدا قىلىپ ، بىر تەرەپ قىلىش ۋاقتىنى ئاشۇرۇۋېتىدۇ كىچىك رەسىم رەسىم بىر تەرەپ قىلىشتىن ئىلگىرى تۆۋەن ئۆلچەمگە چۈشۈرۈلىدۇ ، بۇ قورالنىڭ تېخىمۇ تېز ۋە بىخەتەر ئىشلىشىگە ياردەم بېرىدۇ ئەڭ تۆۋەن رەڭ نىسبىتى Lines Threshold Quadratic Threshold يۇمىلاق چىدامچانلىقنى ماسلاشتۇرىدۇ Path Scale خاسلىقنى ئەسلىگە كەلتۈرۈش بارلىق خاسلىق سۈكۈتتىكى قىممەتكە تەڭشەلدى ، دىققەت قىلىڭ ، بۇ ھەرىكەتنى ئەمەلدىن قالدۇرغىلى بولمايدۇ تەپسىلىي كۆڭۈلدىكى سىزىق كەڭلىكى ماتور ھالىتى مىراس LSTM تورى Legacy &amp; LSTM ئايلاندۇرۇش رەسىم گۇرۇپپىسىنى بېرىلگەن فورماتقا ئۆزگەرتىڭ يېڭى ھۆججەت قىسقۇچ قوشۇڭ ھەر بىر ئۈلگە پىرىسلاش Photometric Interpretation Pixel نىڭ ئەۋرىشكىسى پىلانلىق تەڭشەش Y Cb Cr Sub Sampling Y Cb Cr ئورۇن بەلگىلەش X ئېنىقلىق Y قارار ئېنىقلىق بىرلىكى Strip Offsets ھەر بىر قۇر Strip Byte Counts JPEG ئالماشتۇرۇش فورماتى JPEG ئالماشتۇرۇش فورماتىنىڭ ئۇزۇنلۇقى يۆتكەش ئىقتىدارى ئاق نۇقتا Primary Chromaticities Y Cb Cr كوئېففىتسېنتى قارا ئاق چېسلا ۋاقتى رەسىم چۈشەندۈرۈشى ياساڭ Model يۇمشاق دېتال سەنئەتكار نەشر ھوقۇقى Exif نەشرى Flashpix نەشرى رەڭ بوشلۇقى گامما Pixel X ئۆلچىمى Pixel Y ئۆلچىمى پىكسېلغا پىرىسلانغان بىت ياسىغۇچى ئەسكەرتىش ئىشلەتكۈچى ئىنكاس مۇناسىۋەتلىك ئاۋاز ھۆججىتى چېسلا ۋاقتى ئەسلى چېسلا ۋاقىت رەقەملەشتۈرۈلدى Offset Time Offset Time Original Offset Time Digitized Sub Sec Time Sub Sec Time Original تارماق سېكۇنت ۋاقىت رەقەملەشتۈرۈلدى ئاشكارلىنىش ۋاقتى F نومۇرى ئاشكارىلاش پروگراممىسى Spectral Sensitivity سۈرەتتىكى سەزگۈرلۈك Oecf سەزگۈرلۈك تىپى ئۆلچەملىك چىقىرىش سەزگۈرلۈكى تەۋسىيە قىلىنغان كۆرسەتكۈچ ISO سۈرئەت ISO سۈرئەت كەڭلىكى yyy ISO سۈرئەت كەڭلىكى zzz Shutter سۈرئەت قىممىتى Aperture Value يورۇقلۇق قىممىتى ئاشكارىلاش بىر تەرەپ قىلىش قىممىتى Max Aperture Value تېما ئارىلىقى ئۆلچەش ھالىتى Flash تېما رايونى فوكۇس ئۇزۇنلۇقى Flash Energy بوشلۇق چاستوتىسى ئىنكاسى فوكۇس ئايروپىلانى X ئېنىقلىق دەرىجىسى فوكۇس ئايروپىلانى Y قارارى فوكۇس ئايروپىلانى ئېنىقلاش بىرلىكى تېما ئورنى ئاشكارىلاش كۆرسەتكۈچى سېزىش ئۇسۇلى ھۆججەت مەنبەسى CFA Pattern Custom Rendered ئاشكارىلاش ھالىتى White Balance رەقەملىك چوڭلۇق نىسبىتى فوكۇس ئۇزۇنلۇقى 35 مىللىمېتىرلىق فىلىم كۆرۈنۈشنى تۇتۇش تىپى كونترول قىلىش سېلىشتۇرما Saturation ئۆتكۈرلۈك ئۈسكۈنىلەرنى تەڭشەش چۈشەندۈرۈشى تېما ئارىلىقى رەسىم Unique ID كامېرا ئىگىسىنىڭ ئىسمى بەدەن رەت نومۇرى لىنزا ئۆلچىمى Lens Make لىنزا مودېلى لىنزا رەت نومۇرى GPS نەشرى كىملىكى GPS Latitude Ref GPS كەڭلىكى GPS Longitude Ref GPS ئۇزۇنلۇقى GPS Altitude Ref GPS ئېگىزلىكى GPS ۋاقىت تامغىسى GPS سۈنئىي ھەمراھى GPS ھالىتى GPS ئۆلچەش ھالىتى GPS DOP GPS سۈرئەت Ref GPS سۈرئەت GPS Track Ref GPS ئىز GPS Img Direction Ref GPS Img يۆنىلىشى GPS خەرىتە سانلىق مەلۇمات ئامبىرى GPS Dest Latitude Ref GPS Dest Latitude GPS Dest Longitude Ref GPS Dest Longitude GPS Dest Bearing Ref GPS Dest Bearing GPS Dest Distance Ref GPS Dest Distance GPS بىر تەرەپ قىلىش ئۇسۇلى GPS رايون ئۇچۇرى GPS چېسلا تامغىسى GPS پەرقى GPS H ئورۇن بەلگىلەش خاتالىقى ئۆز-ئارا ماسلىشىش كۆرسەتكۈچى DNG نەشرى كۆڭۈلدىكى زىرائەت چوڭلۇقى رەسىم باشلاش رەسىم ئۇزۇنلۇقىنى ئالدىن كۆرۈش Aspect Frame سېنزور تۆۋەن چېگرىسى سېنزور سول چېگرا سېنزور ئوڭ چېگراسى Sensor Top Border ISO بېرىلگەن خەت ۋە رەڭ بىلەن يولدا تېكىست سىزىڭ خەت چوڭلۇقى Watermark Size تېكىستنى تەكرارلاش نۆۋەتتىكى تېكىست بىر قېتىم سىزىشنىڭ ئورنىغا يول ئاخىرلاشقۇچە تەكرارلىنىدۇ Dash Size تاللانغان رەسىمنى ئىشلىتىپ ئۇنى بېرىلگەن يولدا سىزىڭ بۇ رەسىم سىزىلغان يولنىڭ تەكرارلىنىشى سۈپىتىدە ئىشلىتىلىدۇ باشلىنىش نۇقتىسىدىن ئاخىرىغىچە ئۈچبۇلۇڭنى سىزىدۇ باشلىنىش نۇقتىسىدىن ئاخىرىغىچە ئۈچبۇلۇڭنى سىزىدۇ كۆرسىتىلگەن ئۈچبۇلۇڭ ئۈچبۇلۇڭ كۆپ قۇتۇپنى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىدۇ كۆپ گۈللۈك كۆپ قۇتۇپلۇق كۆپ نۇقتىنى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىدۇ Vertices دائىملىق كۆپ قىرلىق رەسىم سىزىڭ ھەقسىز شەكىلنىڭ ئورنىغا دائىملىق بولىدىغان كۆپ قىرلىق سىزىڭ چولپاننى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىدۇ Star Outline Star چولپاننى باشلىنىش نۇقتىسىدىن ئاخىرىغىچە سىزىدۇ ئىچكى رادىئاتسىيە نىسبىتى دائىملىق چولپان سىزىڭ ھەقسىز شەكىلنىڭ ئورنىغا دائىملىق يۇلتۇز سىزىڭ Antialias ئۆتكۈر قىرلارنىڭ ئالدىنى ئېلىش ئۈچۈن ئوكسىدلىنىشقا قارشى تۇرۇشنى قوزغىتىدۇ ئالدىن كۆرۈشنىڭ ئورنىغا تەھرىرلەشنى ئېچىڭ ImageToolbox دا ئېچىش (ئالدىن كۆرۈش) ئۈچۈن رەسىم تاللىغاندا ، تەھرىرلەش تاللاش جەدۋىلى ئالدىن كۆرۈشنىڭ ئورنىغا ئېچىلىدۇ ھۆججەت سايىلىغۇچ ھۆججەتلەرنى سايىلەپ ، PDF ياكى ئۇلاردىن ئايرىم رەسىم ھاسىل قىلىڭ سىكانىرلاشنى باشلاڭ سايىلەشنى باشلاڭ Pdf نى ساقلاڭ Pdf سۈپىتىدە ھەمبەھىرلەڭ تۆۋەندىكى تاللاشلار PDF ئەمەس ، رەسىملەرنى ساقلاش ئۈچۈن Histogram HSV نى تەڭلەشتۈرۈڭ Histogram نى تەڭلەشتۈرۈڭ پىرسەنتنى كىرگۈزۈڭ Text Field ئارقىلىق كىرىشكە يول قويۇڭ ئالدىن تاللاشنىڭ ئارقىسىدىكى Text Field نى قوزغىتىپ ، ئۇلارنى ئۇچۇشقا كىرگۈزۈڭ تارازا رەڭ بوشلۇقى Linear Histogram Pixelation نى تەڭلەشتۈرۈڭ Grid Size X. Grid Size Y. گىستوگرام ماسلىشىشچانلىقىنى تەڭلەشتۈرۈڭ Histogram Adaptive LUV نى تەڭلەشتۈرۈڭ Histogram Adaptive LAB نى تەڭلەشتۈرۈڭ CLAHE CLAHE LAB CLAHE LUV Crop to Content رامكا رەڭگى سەل قاراش قېلىپ قېلىپ سۈزگۈچ قوشۇلمىدى يېڭى قۇر سايىلەنگەن QR كودى ئۈنۈملۈك سۈزگۈچ قېلىپى ئەمەس QR كودىنى سايىلەڭ تاللانغان ھۆججەتتە سۈزگۈچ قېلىپ سانلىق مەلۇماتلىرى يوق قېلىپ قۇرۇش قېلىپ ئىسمى بۇ رەسىم بۇ سۈزگۈچ قېلىپىنى ئالدىن كۆرۈشكە ئىشلىتىلىدۇ قېلىپ سۈزگۈچ QR كود سۈرىتى سۈپىتىدە ھۆججەت سۈپىتىدە ھۆججەت سۈپىتىدە ساقلاڭ QR كود رەسىمى سۈپىتىدە ساقلاڭ قېلىپنى ئۆچۈرۈڭ تاللانغان قېلىپ سۈزگۈچنى ئۆچۈرمەكچى بولۇۋاتىسىز. بۇ مەشغۇلاتنى ئەمەلدىن قالدۇرغىلى بولمايدۇ ئىسمى \ \"%1$s \" بىلەن سۈزگۈچ قېلىپى قوشۇلدى (%2$s) سۈزگۈچ ئالدىن كۆرۈش QR &amp; تاياقچە كودى QR كودىنى سايىلەپ ئۇنىڭ مەزمۇنىغا ئېرىشىڭ ياكى تىزمىڭىزنى چاپلاپ يېڭى كود ھاسىل قىلىڭ كود مەزمۇنى ساھەدىكى مەزمۇنلارنى ئالماشتۇرۇش ئۈچۈن ھەر قانداق تاياقچە كودنى سايىلەڭ ياكى تاللانغان تىپ بىلەن يېڭى تاياقچە كود ھاسىل قىلىدىغان نەرسە كىرگۈزۈڭ QR چۈشەندۈرۈش Min تەڭشەكلەردە QR كودىنى سايىلەش ئۈچۈن كامېرا ئىجازەتنامىسى بېرىڭ تەڭشەكلەردە ھۆججەت سايىلىرىنى سايىلەش ئۈچۈن كامېرا ئىجازەتنامىسى بېرىڭ Cubic B-Spline بولقا Hanning Blackman Welch Quadric Gaussian Sphinx Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 64-نومۇر قەيسەر Bartlett-He Box Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc كۇب ئارىلىقى ئەڭ يېقىن 16 پىكسېلنى ئويلاش ئارقىلىق تېخىمۇ كىچىك كۆلەمدە تەمىنلەيدۇ ، بىليارتتىن ياخشى ئۈنۈم بېرىدۇ ئوخشاش ئېنىقلىما بېرىلگەن كۆپ قۇتۇپلۇق ئىقتىداردىن پايدىلىنىپ ئەگرى سىزىق ياكى يۈزنى سىلىق ئۆز-ئارا ماسلاشتۇرىدۇ ۋە يېقىنلاشتۇرىدۇ ، جانلىق ۋە ئۈزلۈكسىز شەكىل ئىپادىلەيدۇ. سىگنالنىڭ قىرلىرىنى چېكىش ئارقىلىق سپېكترا ئېقىپ كېتىشنى ئازايتىش ئۈچۈن ئىشلىتىلىدىغان كۆزنەك ئىقتىدارى ، سىگنال بىر تەرەپ قىلىشقا پايدىلىق Hann كۆزنىكىنىڭ بىر خىل ۋارىيانتى ، ئادەتتە سىگنال بىر تەرەپ قىلىش پروگراممىلىرىدا سپېكترا ئېقىپ كېتىشنى ئازايتىشتا ئىشلىتىلىدۇ سىگنال بىر تەرەپ قىلىشتا دائىم ئىشلىتىلىدىغان سپېكترا ئېقىپ كېتىشنى ئازايتىش ئارقىلىق ياخشى چاستوتا ئېنىقلىقى بىلەن تەمىنلەيدىغان كۆزنەك ئىقتىدارى سىگنال بىر تەرەپ قىلىش پروگراممىلىرىدا دائىم ئىشلىتىلىدىغان سپېكترا ئېقىمى تۆۋەنلەپ ياخشى چاستوتا ئېنىقلىق دەرىجىسى ئۈچۈن لايىھەلەنگەن كۆزنەك ئىقتىدارى ئۆز-ئارا ماسلىشىش ئۈچۈن كۇئادرات فۇنكسىيەنى ئىشلىتىدىغان ئۇسۇل ، سىلىق ۋە ئۈزلۈكسىز ئۈنۈم بېرىدۇ گاۋسىيىلىك ئىقتىدارنى قوللىنىدىغان ئىنتېرپوللاش ئۇسۇلى ، رەسىمدىكى شاۋقۇننى راۋانلاشتۇرۇش ۋە ئازايتىشقا پايدىلىق ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئەلا سۈپەتلىك ئىنتېرپول بىلەن تەمىنلەيدىغان ئىلغار قايتا قۇرۇش ئۇسۇلى سىگنال بىر تەرەپ قىلىشتا ئىشلىتىلىدىغان ئۈچبۇلۇڭلۇق كۆزنەك ئىقتىدارى سپېكترا ئېقىپ كېتىشنى ئازايتىدۇ تەبىئىي سۈرەتنىڭ چوڭ-كىچىكلىكىنى تەڭشەش ، ئۆتكۈرلۈك ۋە سىلىقلىقنى تەڭپۇڭلاشتۇرۇش ئۈچۈن ئەلا سۈپەتلىك ئىنتېرپوللاش ئۇسۇلى ئەلالاشتۇرۇلدى Robidoux ئۇسۇلىنىڭ تېخىمۇ ئۆتكۈر نۇسخىسى ، ئىنچىكە رەسىمنىڭ چوڭ-كىچىكلىكىنى ئەلالاشتۇردى 16 چېكىش سۈزگۈچ ئارقىلىق سىلىق نەتىجىلەرنى تەمىنلەيدىغان يۇمىلاق شەكىللىك ئىنتېرپوللاش ئۇسۇلى 36 چېكىش سۈزگۈچ ئارقىلىق سىلىق نەتىجىلەرنى تەمىنلەيدىغان يۇمىلاق شەكىللىك ئىنتېرپوللاش ئۇسۇلى 64 چېكىش سۈزگۈچ ئارقىلىق سىلىق نەتىجىلەرنى تەمىنلەيدىغان يۇمىلاق شەكىللىك ئىنتېرپوللاش ئۇسۇلى Kaiser كۆزنىكىنى ئىشلىتىدىغان ئۆز-ئارا باغلىنىش ئۇسۇلى ، ئاساسلىق كەڭلىك كەڭلىكى بىلەن يان تەرەپتىكى سەۋىيىدىكى سودىنى ياخشى كونترول قىلىدۇ. Bartlett بىلەن Hann كۆزنەكلىرىنى بىرلەشتۈرگەن ئارىلاشما كۆزنەك ئىقتىدارى سىگنال بىر تەرەپ قىلىشتا سپېكترا ئېقىپ كېتىشنى ئازايتىشقا ئىشلىتىلىدۇ ئەڭ يېقىن پېكسىل قىممەتنىڭ ئوتتۇرىچە قىممىتىنى ئىشلىتىدىغان ئاددىي قايتا ئۆزگەرتىش ئۇسۇلى ، دائىم توسۇلۇپ قېلىشنى كەلتۈرۈپ چىقىرىدۇ سپېكترا ئېقىپ كېتىشنى ئازايتىش ئۈچۈن ئىشلىتىلىدىغان كۆزنەك ئىقتىدارى ، سىگنال بىر تەرەپ قىلىش پروگراممىلىرىدا ياخشى چاستوتا ئېنىقلىق دەرىجىسى بىلەن تەمىنلەيدۇ ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئەلا سۈپەتلىك ئۆز-ئارا ماسلىشىش ئۈچۈن 2 لۆڭلىك Lanczos سۈزگۈچ ئىشلىتىدىغان قايتا قۇرۇش ئۇسۇلى ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئەلا سۈپەتلىك ئۆز-ئارا ماسلىشىش ئۈچۈن 3 لېتىرلىق Lanczos سۈزگۈچ ئىشلىتىدىغان قايتا قۇرۇش ئۇسۇلى ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئەلا سۈپەتلىك ئۆز-ئارا ماسلىشىش ئۈچۈن 4 لېتىرلىق Lanczos سۈزگۈچ ئىشلىتىدىغان قايتا قۇرۇش ئۇسۇلى Lanczos 2 سۈزگۈچنىڭ جىنس ئىقتىدارىنى ئىشلىتىدىغان بىر خىل ۋارىيانتى ، ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئەلا سۈپەتلىك ئىنتېرپول بىلەن تەمىنلەيدۇ. Lanczos 3 سۈزگۈچنىڭ جىنس ئىقتىدارىنى ئىشلىتىدىغان بىر خىل ۋارىيانتى ، ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئەلا سۈپەتلىك ئىنتېرپول بىلەن تەمىنلەيدۇ. Lanczos 4 سۈزگۈچنىڭ جىنك ئىقتىدارىنى ئىشلىتىدىغان بىر خىل ۋارىيانتى ، ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئەلا سۈپەتلىك ئىنتېرپول بىلەن تەمىنلەيدۇ. Hanning EWA سىلىق ئارىلىشىش ۋە قايتا قۇرۇش ئۈچۈن خەننىڭ سۈزگۈچىنىڭ ئېللىپتىك ئېغىرلىق ئوتتۇرىچە كۆرسەتكۈچى (EWA) ۋارىيانتى Robidoux EWA Elliptical Weight Average Average (EWA) variant of Robidoux filter for high quality resampling Blackman EVE قوڭغۇراق بويۇملىرىنى ئەڭ تۆۋەن چەككە چۈشۈرۈش ئۈچۈن Blackman سۈزگۈچنىڭ ئېللىپتىك ئېغىرلىقتىكى ئوتتۇرىچە (EWA) ۋارىيانتى Quadric EWA سىلىق ئارىلىشىش ئۈچۈن Quadric سۈزگۈچنىڭ Elliptical Weight Average (EWA) ۋارىيانتى Robidoux Sharp EWA تېخىمۇ كەسكىن نەتىجىگە ئېرىشىش ئۈچۈن Robidoux ئۆتكۈر سۈزگۈچنىڭ ئېللىپتىك ئېغىرلىق ئوتتۇرىچە كۆرسەتكۈچى (EWA) Lanczos 3 Jinc EWA Lanczos 3 Jinc سۈزگۈچنىڭ Elliptical Weight Average Average (EWA) variant variant for high quality resampling with aliasing reduced Ginseng سۈزۈكلۈك ۋە سىلىقلىق تەڭپۇڭلۇقى بىلەن يۇقىرى سۈپەتلىك رەسىم بىر تەرەپ قىلىش ئۈچۈن لايىھەلەنگەن قايتا سۈزگۈچ Ginseng EWA رەسىم سۈپىتىنى يۇقىرى كۆتۈرۈش ئۈچۈن جىنسېڭ سۈزگۈچنىڭ ئېللىپىس ئېغىرلىق ئوتتۇرىچە كۆرسەتكۈچى (EWA) Lanczos Sharp EWA ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئۆتكۈر نەتىجىگە ئېرىشىش ئۈچۈن Lanczos ئۆتكۈر سۈزگۈچنىڭ Elliptical Weight Average (EWA) ۋارىيانتى Lanczos 4 Sharpest EWA Lanczos 4 ئۆتكۈر سۈزگۈچنىڭ Elliptical Weight Average (EWA) ۋارىيانتى Lanczos Soft EWA رەسىمنى راۋانلاشتۇرۇش ئۈچۈن Lanczos يۇمشاق سۈزگۈچنىڭ Elliptical Weight Average (EWA) ۋارىيانتى Haasn Soft ھەسەن لايىھەلىگەن سۈزۈك ۋە سۈنئىي رەسىمسىز رەسىمنى كىچىكلىتىش ئۈچۈن لايىھەلەنگەن فورمات ئۆزگەرتىش بىر تۈركۈم رەسىملەرنى بىر فورماتتىن يەنە بىر فورماتقا ئۆزگەرتىڭ مەڭگۈ ئەمەلدىن قالدۇرۇڭ رەسىم تىزىش تاللانغان ئارىلاشما ھالەتتىكى رەسىملەرنى بىر-بىرىنىڭ ئۈستىگە تىزىڭ رەسىم قوشۇڭ Bins count Clahe HSL Clahe HSV Histogram Adaptive HSL نى تەڭلەشتۈرۈڭ Histogram Adaptive HSV نى تەڭلەشتۈرۈڭ Edge Mode Clip Wrap رەڭ قارىغۇ تاللانغان رەڭ قارىغۇلار تىپىغا ئۇسلۇب رەڭلىرىنى ماسلاشتۇرۇش ھالىتىنى تاللاڭ قىزىل ۋە يېشىل رەڭلەرنى پەرقلەندۈرۈش قىيىن يېشىل ۋە قىزىل رەڭلەرنى پەرقلەندۈرۈش قىيىن كۆك بىلەن سېرىق رەڭنى پەرقلەندۈرۈش قىيىن قىزىل رەڭنى ھېس قىلالماسلىق يېشىل رەڭلەرنى ھېس قىلالماسلىق كۆك رەڭنى ھېس قىلالماسلىق بارلىق رەڭلەرگە بولغان سەزگۈرلۈكنى تۆۋەنلىتىدۇ پۈتۈنلەي رەڭ قارىغۇسى ، پەقەت كۈلرەڭ رەڭلەرنىلا كۆرىدۇ رەڭ قارىغۇ پىلانىنى ئىشلەتمەڭ رەڭلەر باشتېمىدا بېكىتىلگەندەك بولىدۇ Sigmoidal Lagrange 2 لاگېرانگ ئارىلىقىدىكى سۈزگۈچ 2-سۈزگۈچ ، سىلىق ئۆتۈشۈش ئارقىلىق ئەلا سۈپەتلىك رەسىم كۆلىمىگە ماس كېلىدۇ Lagrange 3 3-نومۇرلۇق لاگېرانگ ئارىلىشىش سۈزگۈچ بولۇپ ، رەسىمنى كىچىكلىتىش ئۈچۈن تېخىمۇ ياخشى توغرىلىق ۋە تېخىمۇ راۋان نەتىجىلەرنى تەمىنلەيدۇ Lanczos 6 Lanczos نىڭ سۈزگۈچ سۈزگۈچ سۈزگۈچ سۈزگۈچ دەرىجىسى 6 بولۇپ ، تېخىمۇ سۈزۈك ۋە تېخىمۇ توغرا بولغان سۈرەت بىلەن تەمىنلەيدۇ Lanczos 6 Jinc Lanczos 6 سۈزگۈچنىڭ Jinc فۇنكسىيەسىنى ئىشلىتىپ رەسىمنى قايتا قۇرۇش سۈپىتىنى يۇقىرى كۆتۈرگەن Linear Box Blur تۈز سىزىقلىق چېدىر Linear Gaussian Box Blur Linear Stack Blur Gaussian Box Blur Linear Fast Gaussian Blur Next سىزىقلىق تېز گاۋسىيىلىك تۇتۇق Linear Gaussian Blur بوياق قىلىپ ئىشلىتىش ئۈچۈن بىر سۈزگۈچنى تاللاڭ سۈزگۈچنى ئالماشتۇرۇڭ ئاستىدىكى سۈزگۈچنى تاللاڭ ، ئۇنى رەسىمىڭىزدە چوتكا قىلىپ ئىشلىتىڭ TIFF پىرىسلاش پىلانى تۆۋەن پول قۇم رەسىم رەسىم بۆلۈش يەككە رەسىمنى قۇر ياكى ستونغا بۆلۈڭ چەكلىمىگە ماس كېلىدۇ بۇ پارامېتىر بىلەن زىرائەتنىڭ چوڭ-كىچىكلىكىنى تەڭشەش ھالىتىنى بىرلەشتۈرۈپ ، كۆڭۈلدىكىدەك ھەرىكەتكە ئېرىشىڭ (Crop / Fit to aspect ratio) مۇۋەپپەقىيەتلىك ئىمپورت قىلىنغان تىللار OCR مودېللىرىنى زاپاسلاش ئەكىرىش ئېكسپورت ئورنى Center سول ئۈستى ئۈستى ئوڭ ئاستى سول ئاستى ئوڭ Top Center Center Right Bottom Center Center Left نىشانلىق رەسىم Palette Transfer كۈچەيتىلگەن نېفىت ئاددىي كونا تېلېۋىزور HDR Gotham ئاددىي سىزما Soft Glow رەڭلىك ئېلان Tri Tone ئۈچىنچى رەڭ Clahe Oklab Clara Olks Clahe Jzazbz Polka Dot Clustered 2x2 Dithering Clxered 4x4 Dithering توپلانغان 8x8 Dithering Yililoma Dithering ياقتۇرىدىغان تاللاشلار تاللانمىدى ، ئۇلارنى قوراللار بېتىگە قوشۇڭ ياقتۇرىدىغانلارنى قوشۇڭ تولۇقلىما Analogous Triadic Split Complementary Tetradic مەيدان Analogous + Complementary رەڭ قوراللىرى ئارىلاشتۇرۇش ، ئاھاڭ ياساش ، سايە ھاسىل قىلىش ۋە باشقىلار Color Harmonies رەڭ سايىسى Variation Tints ئاھاڭ سايە رەڭ ئارىلاشتۇرۇش رەڭ ئۇچۇرى تاللانغان رەڭ ئارىلاش رەڭ ھەرىكەتچان رەڭلەر ئېچىلغاندا پۇل ئىشلەتكىلى بولمايدۇ 512x512 2D LUT نىشان LUT رەسىم ھەۋەسكار Miss Etiquette Soft Elegance يۇمشاق نەپىس ۋارىيانت Palette Transfer Variant 3D LUT نىشان 3D LUT ھۆججىتى (.cube / .CUBE) LUT Bleach Bypass شام كۆكنى تاشلاڭ Edgy Amber Fall Colors Film Stock 50 تۇمان كېچىسى كوداك نېيترال LUT رەسىمگە ئېرىشىش ئالدى بىلەن ، ئۆزىڭىز ياقتۇرىدىغان رەسىم تەھرىرلەش پروگراممىسىنى ئىشلىتىپ ، بۇ يەردىن ئېرىشەلەيدىغان نېيترال LUT غا سۈزگۈچ ئىشلىتىڭ. بۇنىڭ نورمال ئىشلىشى ئۈچۈن ھەر بىر پېكسىل رەڭ باشقا پېكسىللارغا باغلىق بولماسلىقى كېرەك (مەسىلەن تۇتۇق ئىشلىمەيدۇ). تەييارلاپ بولغاندىن كېيىن ، يېڭى LUT رەسىمىڭىزنى 512 * 512 LUT سۈزگۈچكە كىرگۈزۈڭ Pop Art Celluloid قەھۋە ئالتۇن ئورمان يېشىل Retro Yellow ئۇلىنىش ئالدىن كۆرۈش تېكىست (QRCode, OCR قاتارلىقلار) غا ئېرىشەلەيدىغان ئورۇنلاردا ئۇلىنىشنى ئالدىن كۆرۈشنى قوزغىتىدۇ. ئۇلىنىشلار ICO ھۆججەتلىرىنى ئەڭ چوڭ بولغاندا 256 x 256 چوڭلۇقتا ساقلىغىلى بولىدۇ WEBP غا سوۋغات GIF رەسىملىرىنى WEBP كارتون رەسىملىرىگە ئايلاندۇرۇڭ WEBP قوراللىرى رەسىملەرنى WEBP كارتون رەسىمگە ئايلاندۇرۇش ياكى بېرىلگەن WEBP كارتوندىن رامكا ئېلىش رەسىملەرگە WEBP WEBP ھۆججىتىنى بىر تۈركۈم رەسىملەرگە ئايلاندۇرۇڭ بىر تۈركۈم رەسىملەرنى WEBP ھۆججىتىگە ئايلاندۇرۇڭ WEBP غا رەسىملەر باشلاش ئۈچۈن WEBP رەسىمىنى تاللاڭ ھۆججەتلەرنى تولۇق زىيارەت قىلىشقا بولمايدۇ بارلىق ھۆججەتلەرنىڭ ئاندىرويىدتىكى رەسىم دەپ تونۇلمىغان JXL ، QOI ۋە باشقا رەسىملەرنى كۆرۈشىگە يول قويۇڭ. رۇخسەتسىز رەسىم قورال ساندۇقى بۇ رەسىملەرنى كۆرسىتەلمەيدۇ كۆڭۈلدىكى سىزىش رەڭگى كۆڭۈلدىكى رەسىم سىزىش ھالىتى ۋاقىت جەدۋىلىنى قوشۇڭ چىقىرىش ھۆججىتىگە Timestamp قوشۇشنى قوزغىتىدۇ فورماتلانغان ۋاقىت جەدۋىلى ئاساسىي مىللىسنىڭ ئورنىغا چىقىرىش ھۆججەت نامىدا Timestamp فورماتىنى قوزغىتىڭ ئۇلارنىڭ فورماتىنى تاللاش ئۈچۈن ۋاقىت تامغىسىنى قوزغىتىڭ بىر قېتىم ئورۇننى ساقلاش بىر قېتىم ساقلاش ۋە تەھرىرلەش كۆپىنچە تاللاشلاردا ساقلاش كۇنۇپكىسىنى ئۇزۇن بېسىپ ئىشلەتسىڭىز بولىدۇ يېقىندا ئىشلىتىلدى CI قانىلى گۇرۇپپا تېلېگراممىدىكى رەسىم قورال ساندۇقى 🎉 ئۆزىڭىز خالىغان نەرسىنى مۇزاكىرە قىلالايدىغان پاراڭلىرىمىزغا قوشۇلۇڭ ، شۇنداقلا مەن beta ۋە ئېلانلارنى يوللايدىغان CI قانىلىغا قاراڭ ئەپنىڭ يېڭى نەشرى ھەققىدە خەۋەردار بولۇڭ ۋە ئېلانلارنى ئوقۇڭ رەسىمنى بېرىلگەن ئۆلچەمگە ماسلاشتۇرۇڭ ھەمدە تەگلىككە سۇس ياكى رەڭ ئىشلىتىڭ قوراللارنى ئورۇنلاشتۇرۇش گۇرۇپپا قوراللىرى گۇرۇپپا ئېكرانى ئاساسلىق ئېكراندىكى تۈرلەرنى ئىختىيارى تىزىملىكنىڭ ئورنىغا ئەمەس كۆڭۈلدىكى قىممەت سىستېما بالداقلىرى سىيرىلما سىستېما بالدىقىنى كۆرسەت ئەگەر يوشۇرۇن بولسا سىستېما بالدىقىنى كۆرسىتىش ئۈچۈن سۈرتۈشنى قوزغىتىدۇ ئاپتوماتىك ھەممىنى يوشۇر ھەممىنى كۆرسەت Nav Bar نى يوشۇرۇش ھالەت بالدىقىنى يوشۇرۇش شاۋقۇن ئەۋلاد پېرلىن ياكى باشقا تىپلارغا ئوخشاش ئوخشىمىغان ئاۋازلارنى ھاسىل قىلىڭ چاستوتىسى شاۋقۇن تىپى ئايلىنىش تىپى Fractal Type Octaves Lacunarity Gain ئېغىرلىق كۈچى Ping Pong Strength ئارىلىق ئىقتىدارى قايتىش تىپى Jitter Domain Warp توغرىلاش خاس ھۆججەت ئىسمى نۆۋەتتىكى رەسىمنى ساقلاشقا ئىشلىتىلىدىغان ئورۇن ۋە ھۆججەت نامىنى تاللاڭ خاس ئىسىم بىلەن ھۆججەت قىسقۇچقا ساقلاندى Collage Maker 20 پارچە رەسىمگە قەدەر كوللاگېن ياساڭ Collage Type ئورۇننى تەڭشەش ئۈچۈن رەسىمنى ئالماشتۇرۇش ، يۆتكەش ۋە چوڭايتىش ئايلىنىشنى چەكلەڭ ئىككى بارماق ئىزى بىلەن رەسىمنىڭ ئايلىنىشىنىڭ ئالدىنى ئالىدۇ چېگرانى تارتىۋېلىشنى قوزغىتىڭ يۆتكەلگەن ياكى چوڭايتقاندىن كېيىن ، رەسىملەر تارلىنىپ رامكا قىرلىرىنى تولدۇرىدۇ Histogram RGB ياكى Brightness رەسىم گىستوگراممىسى سىزنىڭ تەڭشىشىڭىزگە ياردەم بېرىدۇ بۇ رەسىم RGB ۋە Brightness histograms ھاسىل قىلىشقا ئىشلىتىلىدۇ سىناق تاللانمىلىرى سىناق ماتورىغا بىر قىسىم كىرگۈزگۈچى ئۆزگەرگۈچى مىقدارلارنى ئىشلىتىڭ ئىختىيارى تاللانما بۇ ئەندىزە بويىچە تاللاشلار كىرگۈزۈلۈشى كېرەك: \ \"- {option_name} {value} \" Auto Crop ھەقسىز بۇلۇڭ كۆپ قىرلىق رەسىمنى كېسىش ، بۇمۇ كۆز قاراشنى توغرىلايدۇ رەسىم چەكلىمىسىگە مەجبۇرلاش نۇقتىلىرى نۇقتا رەسىم چەكلىمىسى بىلەنلا چەكلەنمەيدۇ ، بۇ تېخىمۇ ئېنىق بولغان كۆز قاراشنى تۈزىتىشكە پايدىلىق ماسكا سىزىلغان يول ئاستىدا مەزمۇننى تولدۇرۇش Heal Spot Circle Kernel نى ئىشلىتىڭ ئېچىش تاقاش Morphological Gradient Top Hat Black Hat تون ئەگرى سىزىق ئەگرى سىزىقنى ئەسلىگە كەلتۈرۈش ئەگرى قىممەت سۈكۈتتىكى قىممەتكە قايتۇرۇلىدۇ Line Style Gap Size Dashed Dot Dashed تامغا بېسىلغان Zigzag سىزىلغان يولنى بويلاپ سىزىقنىڭ سىزىقىنى سىزىپ چىقتى بېرىلگەن يولنى بويلاپ چېكىت ۋە سىزىق سىزىدۇ سۈكۈتتىكى تۈز سىزىقلار بەلگىلەنگەن ئارىلىق بىلەن يول بويى تاللانغان شەكىللەرنى سىزىدۇ يول بويى دولقۇنسىمان زىگزا سىزىدۇ Zigzag نىسبىتى تېزلەتمە قۇرۇش مىخلاش قورالىنى تاللاڭ قورال قوزغاتقۇچنىڭ باش ئېكرانىغا تېزلەتمە سۈپىتىدە قوشۇلىدۇ ، ئۇنى \ \"ھۆججەت تاللاشتىن ئاتلاش \" تەڭشىكى بىلەن بىرلەشتۈرۈپ ، لازىملىق ھەرىكەتنى ئەمەلگە ئاشۇرۇڭ. رامكىنى دۆۋىلەپ قويماڭ ئالدىنقى رامكىلارنى بىر تەرەپ قىلىشنى قوزغىتىدۇ ، شۇڭا ئۇلار بىر-بىرىگە چاپلاشمايدۇ Crossfade رامكىلار بىر-بىرىگە گىرەلىشىپ كېتىدۇ ھالقىما رامكا سانى Threshold One Threshold Two Canny Mirror 101 كۈچەيتىلگەن چوڭايتىش Laplacian Simple Sobel Simple Helper Grid رەسىم سىزىش ئۈستىدىكى تىرەك تورىنى كۆرسىتىپ ، ئېنىق كونترول قىلىشقا ياردەم بېرىدۇ Grid Color Cell Width Cell Height ئىخچام تاللىغۇچىلار بەزى تاللاش كونتروللىرى ئىخچام ئورۇنلاشتۇرۇش ئارقىلىق ئازراق بوشلۇق ئالىدۇ رەسىمگە تارتىش ئۈچۈن تەڭشەكلەردە كامېرا ئىجازەتنامىسى بېرىڭ Layout ئاساسلىق ئېكران ئىسمى تۇراقلىق باھا ئامىلى (CRF) %1$s نىڭ قىممىتى ئاستا پىرىسلاشنى كۆرسىتىدۇ ، نەتىجىدە ھۆججەت چوڭلۇقى بىر قەدەر كىچىك بولىدۇ. %2$s تېخىمۇ تېز پىرىسلاشنى كۆرسىتىدۇ ، نەتىجىدە چوڭ ھۆججەت چىقىدۇ. لۇت كۇتۇپخانىسى چۈشۈرگەندىن كېيىن قوللانسىڭىز بولىدىغان LUTs توپلىمىنى چۈشۈرۈڭ LUTs توپلىمىنى يېڭىلاڭ (پەقەت يېڭىلىرى ئۆچرەتتە تۇرىدۇ) ، چۈشۈرگەندىن كېيىن ئىلتىماس قىلسىڭىز بولىدۇ سۈزگۈچلەرنىڭ سۈكۈتتىكى سۈرىتىنى ئۆزگەرتىڭ رەسىمنى ئالدىن كۆرۈش يوشۇر Show سىيرىلما تىپى Fancy Material 2 ئېسىل كۆرۈنىدىغان سىيرىلغۇچ. بۇ سۈكۈتتىكى تاللاش ماتېرىيال 2 سىيرىلغۇچ سىيرىلما ماتېرىيال ئىلتىماس قىلىڭ مەركىزى دىئالوگ كۇنۇپكىسى دىئالوگ كۇنۇپكىلىرى مۇمكىن بولسا سول تەرەپنىڭ ئورنىغا مەركەزگە ئورۇنلاشتۇرۇلىدۇ ئوچۇق كود ئىجازەتنامىسى بۇ ئەپتە ئىشلىتىلگەن ئوچۇق كود كۈتۈپخانىلىرىنىڭ ئىجازەتنامىسىنى كۆرۈڭ رايون پېكسىل رايون مۇناسىۋىتىنى ئىشلىتىپ قايتا قۇرۇش. ئۇ رەسىمنى يوقىتىشنىڭ ياقتۇرىدىغان ئۇسۇلى بولۇشى مۇمكىن ، چۈنكى ئۇ moire \ \'- ھەقسىز نەتىجىنى بېرىدۇ. ئەمما رەسىمنى چوڭايتقاندا ، ئۇ \ \"ئەڭ يېقىن \" ئۇسۇلىغا ئوخشايدۇ. Tonemapping نى قوزغىتىڭ % نى كىرگۈزۈڭ تور بېكەتنى زىيارەت قىلالمايدۇ ، VPN ئىشلىتىپ سىناپ بېقىڭ ياكى url نىڭ توغرا ياكى ئەمەسلىكىنى تەكشۈرۈڭ Markup Layers رەسىم ، تېكىست ۋە باشقىلارنى ئەركىن قويۇش ئىقتىدارىغا ئىگە قەۋەت ھالىتى قەۋەتنى تەھرىرلەش رەسىمدىكى قەۋەت رەسىمنى تەگلىك قىلىپ ئىشلىتىڭ ھەمدە ئۇنىڭ ئۈستىگە ئوخشىمىغان قاتلاملارنى قوشۇڭ تەگلىكتىكى قەۋەت بىرىنچى تاللاش بىلەن ئوخشاش ، ئەمما رەسىمنىڭ ئورنىغا رەڭ بىلەن Beta تېز تەڭشەكلەر رەسىملەرنى تەھرىرلەۋاتقاندا تاللانغان تەرەپكە لەيلىمە بەلۋاغ قوشۇڭ ، چەككەندە تېز تەڭشەكلەر ئېچىلىدۇ تاللاشنى تازىلاش "گۇرۇپپا تەڭشەش \"%1$s \" سۈكۈتتىكى ھالەتتە يىمىرىلىدۇ" "گۇرۇپپا تەڭشەش \"%1$s \" سۈكۈتتىكى ھالەتتە كېڭەيتىلىدۇ" Base64 قوراللىرى Base64 تىزمىسىنى رەسىمگە يېشىش ياكى رەسىمنى Base64 فورماتىغا كودلاش Base64 تەمىنلەنگەن قىممەت ئىناۋەتلىك Base64 تىزمىسى ئەمەس قۇرۇق ياكى ئىناۋەتسىز Base64 تىزمىسىنى كۆچۈرگىلى بولمايدۇ Paste Base64 Base64 نى كۆچۈرۈڭ Base64 تىزمىسىنى كۆچۈرۈش ياكى ساقلاش ئۈچۈن رەسىم يۈكلەڭ. ئەگەر سىزدە بۇ تىزمىنىڭ ئۆزى بولسا ، ئۇنى چاپلاپ رەسىمگە ئېرىشسىڭىز بولىدۇ Base64 نى ساقلاڭ Base64 نى ئورتاقلىشىش تاللانما ھەرىكەتلەر Base64 نى ئەكىرىش Base64 Actions Outline نى قوشۇڭ بەلگىلەنگەن رەڭ ۋە كەڭلىكتىكى تېكىست ئەتراپىغا سىزىق قوشۇڭ سىزىق رەڭ سىزىق چوڭلۇقى ئايلىنىش ھۆججەت ئىسمى چىقىرىلغان رەسىملەرنىڭ سانلىق مەلۇمات تەكشۈرۈشىگە ماس كېلىدىغان ئىسمى بولىدۇ ھەقسىز يۇمشاق دېتال (ھەمكارلاشقۇچى) ئاندىرويىد قوللىنىشچان پروگراممىلىرىنىڭ ھەمكارلىق قانىلىدىكى تېخىمۇ پايدىلىق يۇمشاق دېتاللار ئالگورىزىم تەكشۈرۈش قوراللىرى تەكشۈرۈشنى سېلىشتۇرۇڭ ، ھەش-پەش دېگۈچە ھېسابلاڭ ياكى ئوخشىمىغان ئالدىراش ھېسابلاش ئۇسۇلى ئارقىلىق ھۆججەتلەردىن ئالتە تال سىزىق ھاسىل قىلىڭ ھېسابلاپ بېقىڭ Text Hash Checksum تاللانغان ئالگورىزىمغا ئاساسەن تەكشۈرۈشنى ھېسابلاش ئۈچۈن ھۆججەت تاللاڭ تاللانغان ئالگورىزىمغا ئاساسەن تېكىستنى كىرگۈزۈڭ Source Checksum سېلىشتۇرۇش ئۈچۈن تەكشۈرۈش ماس! پەرقى تەكشۈرۈش باراۋەر ، بىخەتەر بولىدۇ تەكشۈرۈش باراۋەر ئەمەس ، ھۆججەت بىخەتەر ئەمەس! Mesh Gradients Mesh Gradients نىڭ تور توپلىمىغا قاراڭ پەقەت TTF ۋە OTF خەت نۇسخىسىنىلا ئىمپورتلىغىلى بولىدۇ خەت نۇسخىسىنى ئەكىرىش (TTF / OTF) خەت نۇسخىسىنى چىقىرىش ئىمپورت قىلىنغان خەت نۇسخىسى ساقلاشنى ساقلاش جەريانىدا خاتالىق ، چىقىرىش قىسقۇچىنى ئۆزگەرتىشكە تىرىشىڭ ھۆججەت ئىسمى بېكىتىلمىگەن ياق ئىختىيارى بەتلەر بەت تاللاش قورالدىن چىقىشنى جەزملەشتۈرۈش ئەگەر ئالاھىدە قوراللارنى ئىشلەتكەندە ساقلانمىغان ئۆزگىرىشلەر بولۇپ ، ئۇنى تاقاپ سىناپ بېقىڭ ، ئۇنداقتا سۆزلىشىشنىڭ كۆرسىتىلىدىغانلىقىنى جەزملەشتۈرۈڭ EXIF نى تەھرىرلەڭ يەككە رەسىمنىڭ مېتا سانلىق مەلۇماتلىرىنى قايتا-قايتا ئۆزگەرتمەي ئۆزگەرتىڭ ئىشلەتكىلى بولىدىغان خەتكۈچلەرنى تەھرىرلەش ئۈچۈن چېكىڭ Sticker نى ئۆزگەرتىڭ Fit Width Fit Height Batch Compare تاللانغان ئالگورىزىمغا ئاساسەن ھۆججەت / ھۆججەتلەرنى تاللاڭ ھۆججەتلەرنى تاللاڭ مۇندەرىجىنى تاللاڭ باش ئۇزۇنلۇقى تامغا Timestamp فورمات ئۈلگىسى Padding رەسىم كېسىش رەسىم قىسمىنى كېسىپ ، تىك ياكى توغرىسىغا سىزىق ئارقىلىق سول تەرەپلەرنى (تەتۈر يۆنىلىشتە) بىرلەشتۈرۈڭ Vertical Pivot Line توغرىسىغا توغرىلاش لىنىيىسى تەتۈر تاللاش كېسىلگەن رايون ئەتراپىدىكى زاپچاسلارنى بىرلەشتۈرۈشنىڭ ئورنىغا تىك كېسىلگەن قىسمى قالدۇرۇلىدۇ كېسىلگەن رايون ئەتراپىدىكى بۆلەكلەرنى بىرلەشتۈرۈشنىڭ ئورنىغا ، توغرىسىغا كېسىلگەن قىسمى قالدۇرۇلىدۇ Mesh Gradients توپلىمى خاس مىقداردىكى تۈگۈن ۋە ئېنىقلىق دەرىجىسى بىلەن تور دەرىجىسىنى ھاسىل قىلىڭ Mesh Gradient Overlay بېرىلگەن رەسىملەرنىڭ ئۈستىگە تور دەرىجىسىنى تەدرىجىي تۈزۈڭ نۇقتىنى خاسلاشتۇرۇش Grid Size ئېنىقلىق X. قارار Y. قارار Pixel By Pixel رەڭنى گەۋدىلەندۈرۈش Pixel سېلىشتۇرۇش تىپى تاياقچە كودىنى سايىلەڭ ئېگىزلىك نىسبىتى تاياقچە كود تىپى B / W. تاياقچە كودى رەسىمى پۈتۈنلەي قارا ۋە ئاق بولىدۇ ، ئەپنىڭ تېمىسى رەڭدار بولمايدۇ ھەر قانداق تاياقچە كودنى سايىلاڭ (QR, EAN, AZTEC,…) ۋە ئۇنىڭ مەزمۇنىغا ئېرىشىڭ ياكى تېكىستىڭىزنى چاپلاپ يېڭى ھاسىل قىلىڭ تاياقچە كودى تېپىلمىدى ھاسىل قىلىنغان تاياقچە كودى بۇ يەردە بولىدۇ Audio Covers ئاۋاز ھۆججىتىدىن پىلاستىنكا مۇقاۋا رەسىملىرىنى چىقىرىڭ ، كۆپ ئۇچرايدىغان فورماتلارنى قوللايدۇ باشلاش ئۈچۈن ئاۋاز تاللاڭ ئاۋازنى تاللاڭ Covers تېپىلمىدى خاتىرە ئەۋەتىڭ ئەپ خاتىرىسىنى ئورتاقلىشىش ئۈچۈن چېكىڭ ، بۇ مېنىڭ مەسىلىنى بايقىشىم ۋە مەسىلىلەرنى ھەل قىلىشىمغا ياردەم بېرەلەيدۇ ئاپلا… چاتاق چىقتى تۆۋەندىكى تاللاشلار ئارقىلىق مەن بىلەن ئالاقىلاشسىڭىز بولىدۇ ، مەن ھەل قىلىش چارىسى تېپىشقا تىرىشىمەن. \ N (خاتىرە باغلاشنى ئۇنتۇپ قالماڭ) ھۆججەتكە يېزىڭ بىر تۈركۈم رەسىملەردىن تېكىستنى چىقىرىپ بىر تېكىست ھۆججىتىگە ساقلاڭ Metadata غا يېزىڭ ھەر بىر رەسىمدىن تېكىست چىقىرىپ ، مۇناسىۋەتلىك رەسىملەرنىڭ EXIF ​​ئۇچۇرىغا قويۇڭ كۆرۈنمەيدىغان ھالەت Steganography ئارقىلىق رەسىملىرىڭىزنىڭ بايتلىرى ئىچىدە كۆزگە كۆرۈنمەيدىغان سۇ بەلگىسى ھاسىل قىلىڭ LSB نى ئىشلىتىڭ LSB (ئانچە مۇھىم بولمىغان Bit) ستېگانوگرافىيە ئۇسۇلى قوللىنىلىدۇ ، FD (چاستوتا دائىرە) قىزىل كۆزنى ئاپتوماتىك ئۆچۈرۈڭ پارول قۇلۇپ ئېچىش PDF قوغدالدى مەشغۇلات ئاساسەن دېگۈدەك تاماملاندى. ھازىر ئەمەلدىن قالدۇرۇش ئۇنى قايتا قوزغىتىشنى تەلەپ قىلىدۇ چېسلا ئۆزگەرتىلدى ئۆزگەرتىلگەن ۋاقىت (قايتۇرۇلغان) چوڭلۇقى چوڭلۇقى (قايتۇرۇلغان) MIME تىپى MIME تىپى (قايتۇرۇلغان) كېڭەيتىش كېڭەيتىش (قايتۇرۇلغان) چېسلا قوشۇلدى قوشۇلغان ۋاقىت (قايتۇرۇلغان) سولدىن ئوڭغا ئوڭدىن سولغا ئۈستىدىن ئاستىغا ئاستىدىن يۇقىرىغا سۇيۇق ئەينەك يېقىندا ئېلان قىلىنغان IOS 26 نى ئاساس قىلغان ئالماشتۇرغۇچ ۋە ئۇنىڭ سۇيۇق ئەينەك لايىھىلەش سىستېمىسى تۆۋەندىكى رەسىم 64 نى تاللاڭ ياكى چاپلاڭ / ئىمپورت قىلىڭ باشلاش ئۈچۈن رەسىم ئۇلانمىسىنى كىرگۈزۈڭ ئۇلىنىشنى چاپلاڭ Kaleidoscope ئىككىلەمچى بۇلۇڭ يان تەرەپ Channel Mix كۆك يېشىل قىزىل كۆك يېشىل قىزىل قىزىل رەڭگە يېشىل رەڭدە كۆك رەڭدە Cyan Magenta سېرىق رەڭ Halftone Contour Levels Offset Voronoi Crystallize شەكىل Stretch تاسادىپىيلىق Despeckle Diffuse DoG ئىككىنچى رادىئو تەڭگە پارقىراق Whirl and Pinch Pointillize چېگرا رەڭگى قۇتۇپ كوردىناتى قۇتۇپقا توغرىلاڭ قۇتۇپ تۈز چەمبەرگە ئايلاندۇرۇش شاۋقۇننى ئازايتىش ئاددىي Solarize توقۇمىچىلىق X Gap Y Gap X Width Y Width Twirl كاۋچۇك تامغىسى Smear زىچلىقى Mix دائىرە لىنزىسىنى بۇرمىلاش سۇندۇرۇش كۆرسەتكۈچى Arc بۇلۇڭ Sparkle نۇر ASCII Gradient مەريەم كۈز سۆڭەك Jet قىش Ocean ياز باھار Cool Variant HSV ھالرەڭ قىزىق سۆز Magma Inferno پلازما Viridis پۇقرالار Twilight Twilight Shifted Perspective Auto Deskew زىرائەتلەرگە يول قويۇڭ زىرائەت ياكى ئىستىقبال مۇتلەق Turbo Deep Green لىنزا تۈزىتىش JSON فورماتىدىكى لىنزا ئارخىپى ھۆججىتى تەييار كامېرا ئارخىپىنى چۈشۈرۈڭ قىسمەن پىرسەنت JSON قىلىپ چىقىرىش Json نىڭ ئىپادىسى سۈپىتىدە پالتا سانلىق مەلۇماتلىرى بىلەن تىزمىنى كۆچۈرۈڭ Seam Carving باش ئېكران قۇلۇپ ئېكرانى ئىچىگە قاچىلانغان تام قەغەز ئېكسپورتى يېڭىلاش ھازىرقى ئۆي ، قۇلۇپ ۋە ئىچىگە سېلىنغان تام قەغىزىگە ئېرىشىڭ بارلىق ھۆججەتلەرنى زىيارەت قىلىشقا يول قويۇڭ ، بۇ تام قەغىزىنى ئەسلىگە كەلتۈرۈش ئۈچۈن كېرەك سىرتقى ساقلاش ئىجازەتنامىسىنى باشقۇرۇش يېتەرلىك ئەمەس ، رەسىملىرىڭىزنى زىيارەت قىلىشىڭىزغا رۇخسەت قىلىشىڭىز ، \ \"ھەممىگە يول قويۇش\" نى جەزملەشتۈرۈڭ. ھۆججەت نامىغا ئالدىن بەلگىلەڭ رەسىم ھۆججىتىنىڭ نامىغا تاللانغان ئالدىن بەلگىلەش قوشۇلىدۇ ھۆججەت نامىغا رەسىم چوڭلۇقى ھالىتىنى قوشۇڭ رەسىم ھۆججەت نامىغا تاللانغان رەسىم ئۆلچىمى ھالىتى بىلەن قوشۇمچە ھۆججەت قوشۇلىدۇ Ascii Art رەسىمنى رەسىمگە ئوخشايدىغان ascii تېكىستىگە ئايلاندۇرۇڭ پاراملار بەزى ئەھۋاللاردا تېخىمۇ ياخشى نەتىجىگە ئېرىشىش ئۈچۈن رەسىمگە پاسسىپ سۈزگۈچ قوللىنىدۇ ئېكران رەسىمىنى بىر تەرەپ قىلىش ئېكران رەسىمى تۇتۇلمىدى ، قايتا سىناڭ تېجەپ قالدى %1$s ھۆججەتلىرى ئاتلاپ كەتتى ئەگەر چوڭ بولسا ئاتلاشقا يول قويۇڭ بەزى قوراللارنىڭ ساقلانغان ھۆججەتنىڭ سىغىمى ئەسلىدىكىدىن چوڭ بولسا ، تېجەشلىك رەسىملەرنى ئاتلاپ ئۆتۈپ كېتىشىگە رۇخسەت قىلىنىدۇ كالېندار پائالىيىتى ئالاقىلىشىڭ ئېلخەت ئورنى تېلېفون تېكىست قىسقا ئۇچۇر URL Wi-Fi تورنى ئېچىڭ N / A. SSID تېلېفون ئۇچۇر ئادرېس تېما بەدەن ئىسمى تەشكىلات ماۋزۇ تېلېفون Email URL ئادرېس خۇلاسە چۈشەندۈرۈش ئورنى تەشكىللىگۈچى باشلىنىش ۋاقتى ئاخىرلىشىش ۋاقتى ھالەت كەڭلىك ئۇزۇنلۇق تاياقچە كود قۇرۇش تاياقچە كودىنى تەھرىرلەڭ Wi-Fi سەپلىمىسى بىخەتەرلىك ئالاقىنى تاللاڭ تاللانغان ئالاقىلىشىش ئارقىلىق ئاپتوماتىك تولدۇرۇش ئۈچۈن تەڭشەكلەردە ئالاقىلىشىش ئىجازەتنامىسى بېرىڭ ئالاقىلىشىش ئۇچۇرلىرى ئىسمى ئوتتۇرا ئىسمى فامىلىسى تەلەپپۇز تېلېفون قوشۇڭ ئېلېكترونلۇق خەت قوشۇڭ ئادرېس قوشۇڭ تور بېكەت توربېكەت قوشۇڭ فورماتلانغان ئىسىم بۇ رەسىم تاياقچە كودنىڭ ئۈستىگە قويۇشقا ئىشلىتىلىدۇ كودنى خاسلاشتۇرۇش بۇ رەسىم QR كودىنىڭ مەركىزىدە بەلگە سۈپىتىدە ئىشلىتىلىدۇ Logo بەلگە چاپلاش Logo size بەلگە بۇلۇڭ تۆتىنچى كۆز تۆۋەنكى بۇلۇڭغا تۆتىنچى كۆز قوشۇش ئارقىلىق qr كودىغا كۆز سىممېترىكلىكى قوشىدۇ Pixel شەكلى رامكا شەكلى توپ شەكلى خاتالىق تۈزىتىش دەرىجىسى قېنىق رەڭ سۇس رەڭ Hyper OS شياۋمى HyperOS ئۇسلۇبنى ياخشى كۆرىدۇ ماسكا ئەندىزىسى بۇ كودنى سىكانېرلىغىلى بولمايدۇ ، كۆرۈنۈش پارامېتىرلىرىنى ئۆزگەرتىپ بارلىق ئۈسكۈنىلەر بىلەن ئوقۇغىلى بولىدۇ سايىلىگىلى بولمايدۇ قوراللار تېخىمۇ ئىخچام بولۇش ئۈچۈن ئائىلە ئېكرانى ئەپ ئاچقۇچىغا ئوخشايدۇ قوزغىتىش ھالىتى تاللانغان چوتكا ۋە ئۇسلۇب بىلەن رايوننى تولدۇرىدۇ كەلكۈن پۈركۈش تارتىش كۈچى ئۇسلۇبتىكى يولنى سىزىدۇ Square Particles پۈركۈش زەررىچىلىرى چەمبەرنىڭ ئورنىغا چاسا شەكىللىك بولىدۇ Palette Tools رەسىمدىن سىزغان ئاساسىي / ماتېرىيال ھاسىل قىلىڭ ياكى ئوخشىمىغان پالتا فورماتىدا ئىمپورت-ئېكىسپورت قىلىڭ Palette نى تەھرىرلەڭ ھەر خىل فورماتتىكى پالتىنى چىقىرىش / ئىمپورت قىلىش رەڭ ئىسمى Palette name Palette Format ھاسىل قىلىنغان پالتىنى ئوخشىمىغان فورماتلارغا چىقىرىش ھازىرقى پالتىغا يېڭى رەڭ قوشىدۇ %1$s فورماتى palette نامىنى تەمىنلەشنى قوللىمايدۇ Play Store سىياسىتى سەۋەبىدىن ، بۇ ئىقتىدارنى ھازىرقى قۇرۇلۇشقا كىرگۈزگىلى بولمايدۇ. بۇ ئىقتىدارنى زىيارەت قىلىش ئۈچۈن ImageToolbox نى باشقا مەنبەدىن چۈشۈرۈڭ. تۆۋەندىكى GitHub دا بار بولغان قۇرۇلۇشلارنى تاپالايسىز. Github بېتىنى ئېچىڭ ئەسلى ھۆججەت تاللانغان ھۆججەت قىسقۇچتا ساقلاشنىڭ ئورنىغا يېڭى ھۆججەتكە ئالماشتۇرۇلىدۇ يوشۇرۇن سۇ بەلگىسى تېكىستى بايقالدى يوشۇرۇن سۇ بەلگىسى رەسىمى بايقالدى بۇ رەسىم يوشۇرۇنغان Generative Inpainting OpenCV غا تايانماي ، سۈنئىي ئەقىل مودېلى ئارقىلىق رەسىمدىكى نەرسىلەرنى ئۆچۈرۈشىڭىزگە يول قويىدۇ. بۇ ئىقتىدارنى ئىشلىتىش ئۈچۈن ، ئەپ GitHub دىن لازىملىق مودېلنى (~ 200 MB) چۈشۈرۈۋالىدۇ OpenCV غا تايانماي ، سۈنئىي ئەقىل مودېلى ئارقىلىق رەسىمدىكى نەرسىلەرنى ئۆچۈرۈشىڭىزگە يول قويىدۇ. بۇ ئۇزۇنغا سوزۇلغان مەشغۇلات بولۇشى مۇمكىن خاتالىق دەرىجىسىنى تەھلىل قىلىش Luminance Gradient ئوتتۇرىچە ئارىلىق كۆچۈرۈشنى كۆچۈرۈش ساقلاپ قېلىش Coefficent چاپلاش تاختىسى سانلىق مەلۇماتلىرى بەك چوڭ كۆچۈرگىلى بولىدىغان سانلىق مەلۇمات بەك چوڭ ئاددىي توقۇمىچىلىق Staggered Pixelization Cross Pixelization Micro Macro Pixelization Orbital Pixelization Vortex Pixelization Pulse Grid Pixelization Nucleus Pixelization Radial Weave Pixelization "Uri \"%1$s \" نى ئاچقىلى بولمايدۇ" قار يېغىش ھالىتى قوزغىتىلدى چېگرا رامكىسى Glitch Variant Channel Shift Max Offset VHS Block Glitch چوڭلۇقنى چەكلەش CRT ئەگرى سىزىقى ئەگرى سىزىق خىروم Pixel Melt Max Drop AI قوراللىرى سۈنئىي ئۇسۇلدا ئېلىۋېتىش ياكى رەتلەش قاتارلىق ئاي مودېللىرى ئارقىلىق رەسىملەرنى بىر تەرەپ قىلىدىغان ھەر خىل قوراللار پىرىسلاش ، باغلانغان سىزىقلار كارتون ، تارقىتىش پىرىسلاش ئادەتتىكى پىرىسلاش ، ئادەتتىكى شاۋقۇن رەڭسىز كارتون شاۋقۇنى تېز ، ئادەتتىكى پىرىسلاش ، ئادەتتىكى شاۋقۇن ، كارتون / يۇمۇر / كارتون كىتاب سايىلەش ئاشكارىلاش تۈزىتىش ئادەتتىكى پىرىسلاش ، رەڭلىك رەسىملەر ئەڭ ياخشى ئادەتتىكى پىرىسلاش ، كۈلرەڭ رەسىملەر ئادەتتىكى پىرىسلاش ، كۈلرەڭ رەسىملەر تېخىمۇ كۈچلۈك ئادەتتىكى شاۋقۇن ، رەڭلىك رەسىملەر ئادەتتىكى شاۋقۇن ، رەڭلىك رەسىملەر ، تېخىمۇ ياخشى تەپسىلاتلار ئادەتتىكى شاۋقۇن ، كۈلرەڭ رەسىملەر ئادەتتىكى شاۋقۇن ، كۈلرەڭ رەسىملەر تېخىمۇ كۈچلۈك ئادەتتىكى شاۋقۇن ، كۈلرەڭ رەسىملەر ، ئەڭ كۈچلۈك ئادەتتىكى پىرىسلاش ئادەتتىكى پىرىسلاش تېكىستلەشتۈرۈش ، h264 پىرىسلاش VHS پىرىسلاش ئۆلچەملىك پىرىسلاش (cinepak, msvideo1, roq) بىنەپشە پىرىسلاش ، گېئومېتىرىيەدىن ياخشى بىنەپشە پىرىسلاش تېخىمۇ كۈچلۈك بىنەپشە پىرىسلاش ، يۇمشاق ، تەپسىلاتنى ساقلاپ قالىدۇ پەلەمپەيسىمان ئۈنۈمنى يوقىتىش ، راۋانلاشتۇرۇش سكاننېرلانغان سەنئەت / سىزمىلار ، يېنىك پىرىسلاش ، نەملىك رەڭ باغلاش ئاستا ، يېرىم تاشنى ئېلىۋېتىش كۈلرەڭ / bw رەسىملەرنىڭ ئادەتتىكى رەڭدار ، تېخىمۇ ياخشى ئۈنۈمگە ئېرىشىش ئۈچۈن DDColor نى ئىشلىتىڭ قىرلارنى ئېلىۋېتىش چەكتىن ئاشۇرۇۋېتىشنى يوقىتىدۇ ئاستا ، يۆنىلىش چەتئەلگە قارشى تۇرۇش ، ئادەتتىكى بۇيۇملار ، CGI KDM003 بىر تەرەپ قىلىشنى سايىلەيدۇ يېنىك دەرىجىدىكى رەسىمنى ئاشۇرۇش ئەندىزىسى پىرىسلاش بۇيۇملىرىنى ئېلىۋېتىش پىرىسلاش بۇيۇملىرىنى ئېلىۋېتىش ئوڭۇشلۇق بولغان بەلۋاغنى ئېلىۋېتىش Halftone ئەندىزىسىنى بىر تەرەپ قىلىش ئەندىزە ئېلىۋېتىش V3 JPEG ئاسارە-ئەتىقىلەرنى يوقىتىش V2 H.264 تېكىستنى ئاشۇرۇش VHS ئۆتكۈرلەشتۈرۈش ۋە كۈچەيتىش بىرلەشتۈرۈش Chunk Size چوڭ-كىچىكلىكى %1$s px ئۈستىدىكى رەسىملەر ئۇششاق-چۈششەك ۋە ئۇششاق-چۈششەك پىششىقلاپ ئىشلىنىدۇ ، بۇلارنى بىر-بىرىگە ئارىلاشتۇرۇپ ، كۆرۈنەرلىك يوچۇقلارنىڭ ئالدىنى ئالىدۇ. چوڭ رازمېر تۆۋەن ئۈسكۈنىلەر بىلەن مۇقىمسىزلىقنى كەلتۈرۈپ چىقىرىدۇ باشلاش ئۈچۈن بىرنى تاللاڭ %1$s مودېلىنى ئۆچۈرمەكچىمۇ؟ ئۇنى قايتا چۈشۈرۈشىڭىز كېرەك جەزملەشتۈرۈڭ مودېللار چۈشۈرۈلگەن مودېللار ئىشلەتكىلى بولىدىغان مودېللار تەييارلىق قىلىش ئاكتىپ مودېل يىغىن ئېچىلمىدى پەقەت .onnx / .ort تىپلىرىنىلا ئىمپورتلىغىلى بولىدۇ ئىمپورت ئەندىزىسى تېخىمۇ كۆپ ئىشلىتىش ئۈچۈن خاسلاشتۇرۇلغان onnx مودېلىنى ئەكىرىڭ ، پەقەت onnx / ort تىپلىرىلا قوبۇل قىلىنىدۇ ، ۋارىيانتلارغا ئوخشاش بارلىق esrgan نى قوللايدۇ. ئىمپورت قىلىنغان مودېللار ئادەتتىكى شاۋقۇن ، رەڭلىك رەسىملەر ئادەتتىكى شاۋقۇن ، رەڭلىك رەسىملەر تېخىمۇ كۈچلۈك ئادەتتىكى شاۋقۇن ، رەڭلىك رەسىملەر ، ئەڭ كۈچلۈك بويالغان بۇيۇملار ۋە رەڭ باغلاشنى ئازايتىدۇ ، سىلىق رېشاتكىلار ۋە تەكشى رەڭلىك رايونلارنى ياخشىلايدۇ. تەبىئىي رەڭلەرنى ساقلاش بىلەن بىر ۋاقىتتا ، سۈرەتنىڭ يورۇقلۇقى ۋە تەڭپۇڭلۇق يارقىن نۇقتىلار بىلەن سېلىشتۇرۇشنى كۈچەيتىدۇ. تەپسىلاتلارنى ساقلاش ۋە ھەددىدىن زىيادە كۆپ بولۇپ كېتىشتىن ساقلىنىش بىلەن بىرگە قاراڭغۇ رەسىملەرنى يورۇق قىلىدۇ. ھەددىدىن زىيادە رەڭنى يوقىتىشنى يوقىتىپ ، تېخىمۇ نېيترال ۋە تەبىئىي رەڭ تەڭپۇڭلۇقىنى ئەسلىگە كەلتۈرىدۇ. ئىنچىكە تەپسىلاتلار ۋە توقۇلمىلارنى ساقلاشقا ئەھمىيەت بېرىپ ، پويسوننى ئاساس قىلغان شاۋقۇننى تەڭشەشنى قوللىنىدۇ. يۇمىلاق ۋە تاجاۋۇزچىلىق كۆرۈش ئۈنۈمى ئۈچۈن يۇمشاق پويسون شاۋقۇنىنى تەڭشەشنى قوللىنىدۇ. بىرلىككە كەلگەن شاۋقۇن ئاۋازى ئىنچىكە قوغداش ۋە سۈرەتنىڭ سۈزۈكلۈكىگە مەركەزلەشتى. نازۇك توقۇلمىلار ۋە سىلىق كۆرۈنۈش ئۈچۈن مۇلايىم بىرلىككە كەلگەن شاۋقۇن ئاۋازى. ئاسارە-ئەتىقىلەرنى بوياش ۋە رەسىمنىڭ بىردەكلىكىنى ياخشىلاش ئارقىلىق بۇزۇلغان ياكى تەكشى بولمىغان رايونلارنى رېمونت قىلىدۇ. ئەڭ تۆۋەن ئىقتىدار تەننەرخى بىلەن رەڭلىك بەلۋاغنى چىقىرىپ تاشلايدىغان يېنىك دەرىجىدىكى مودېل. سۈزۈكلۈك دەرىجىسى يۇقىرى بولغان پىرىسلاش بۇيۇملىرى (% 0-% 20) بولغان رەسىملەرنى ئەلالاشتۇرىدۇ. يۇقىرى پىرىسلاش بۇيۇملىرى (% 20-40% سۈپەت) ئارقىلىق سۈرەتنى ياخشىلايدۇ ، تەپسىلاتلارنى ئەسلىگە كەلتۈرىدۇ ۋە شاۋقۇننى پەسەيتىدۇ. ئوتتۇراھال پىرىسلاش (% 40-% 60 سۈپەت) ئارقىلىق سۈرەتنى ياخشىلايدۇ ، ئۆتكۈرلۈك ۋە سىلىقلىقنى تەڭپۇڭلاشتۇرىدۇ. نۇرنى پىرىسلاش (60-80% سۈپەتلىك) رەسىملەرنى ئىنچىكە پىششىقلاپ ، ئىنچىكە تەپسىلاتلار ۋە توقۇلمىلارنى ئۆستۈرىدۇ. تەبىئىي كۆرۈنۈش ۋە ئىنچىكە ھالقىلارنى ساقلاپ قېلىش بىلەن بىللە ، زىيانسىز رەسىملەرنى (% 80-100 سۈپەت) ئازراق ئۆستۈرىدۇ. ئاددىي ۋە تېز رەڭلەش ، كارتون ، كۆڭۈلدىكىدەك ئەمەس رەسىمنىڭ سۇسلىقىنى ئازراق تۆۋەنلىتىدۇ ، ئاسارە-ئەتىقىلەرنى تونۇشتۇرماي ئۆتكۈرلۈكنى ئۆستۈرىدۇ. ئۇزۇن مەشغۇلات رەسىم بىر تەرەپ قىلىش بىر تەرەپ قىلىش ئىنتايىن تۆۋەن سۈپەتلىك رەسىمدىكى ئېغىر JPEG پىرىسلاش بۇيۇملىرىنى چىقىرىپ تاشلايدۇ (% 0-20). يۇقىرى پرېسلانغان رەسىملەردە كۈچلۈك JPEG ئاسارە-ئەتىقىلەرنى ئازايتىدۇ (% 20-40). رەسىم تەپسىلاتلىرىنى ساقلاش بىلەن بىللە ئوتتۇراھال JPEG ئاسارە-ئەتىقىلەرنى تازىلايدۇ (% 40-60). بىر قەدەر يۇقىرى سۈپەتلىك رەسىملەردە يېنىك JPEG ئاسارە-ئەتىقىلەرنى ساپلاشتۇرىدۇ (% 60-80). زىيانسىز رەسىملەردە كىچىك JPEG ئاسارە-ئەتىقىلەرنى ئەپچىللىك بىلەن ئازايتىدۇ (% 80-100). ئىنچىكە تەپسىلاتلار ۋە توقۇلمىلارنى كۈچەيتىدۇ ، ئېغىر ئاسارە-ئەتىقىلەر بولماي تۇرۇپ ھېس قىلىنغان ئۆتكۈرلۈكنى ئۆستۈرىدۇ. بىر تەرەپ قىلىش تاماملاندى بىر تەرەپ قىلىش مەغلۇب بولدى تەبىئىي كۆرۈنۈشنى ساقلاش بىلەن بىللە تېرىنىڭ تۈزۈلۈشى ۋە تەپسىلاتلىرىنى كۈچەيتىدۇ ، سۈرئەتنى ئەلالاشتۇرىدۇ. JPEG پىرىسلاش بۇيۇملىرىنى چىقىرىپ تاشلاپ ، پىرىسلانغان رەسىملەرنىڭ رەسىم سۈپىتىنى ئەسلىگە كەلتۈرىدۇ. تۆۋەن يورۇقلۇق شارائىتىدا تارتىلغان سۈرەتلەردە ISO شاۋقۇنىنى تۆۋەنلىتىدۇ ، تەپسىلاتلارنى ساقلايدۇ. ھەددىدىن زىيادە كۆپ ياكى «جۇمۇ» يارقىن نۇقتىلىرىنى تۈزىتىپ ، تېخىمۇ ياخشى ئاۋاز تەڭپۇڭلۇقىنى ئەسلىگە كەلتۈرىدۇ. كۈلرەڭ رەسىملەرگە تەبىئىي رەڭ قوشىدىغان يېنىك ۋە تېز رەڭلەش ئەندىزىسى. DEJPEG Denoise رەڭگى ئاسارە-ئەتىقىلەر كۈچەيتىڭ Anime سايىلەش Upscale ئادەتتىكى رەسىملەر ئۈچۈن X4 يۇقىرى كۆتۈرگۈچ كىچىك تىپتىكى GPU ۋە ۋاقىتنى ئىشلىتىدىغان كىچىك تىپتىكى مودېل ، ئوتتۇراھال چۈشۈش ۋە ئېنىقلىق دەرىجىسى بار. ئادەتتىكى رەسىملەر ئۈچۈن X2 يۇقىرى كۆتۈرۈلۈش ، تۈزۈلۈش ۋە تەبىئىي تەپسىلاتلارنى ساقلاش. X4 يۇقىرى كۆتۈرۈلگەن تېكىستلەر ۋە ئەمەلىي ئۈنۈمگە ئىگە. X4 يۇقىرى كۆتۈرۈلۈش كارتون رەسىملىرى ئۈچۈن ئەلالاشتۇرۇلغان ئۆتكۈر سىزىق ۋە تەپسىلاتلار ئۈچۈن 6 RRDB توسىدۇ. MSE زىيىنى بىلەن X4 يۇقىرى كۆتۈرگۈچى ، تېخىمۇ راۋان نەتىجىلەرنى يارىتىدۇ ۋە ئادەتتىكى رەسىملەر ئۈچۈن ئاسارە-ئەتىقىلەرنى ئازايتىدۇ. X4 Upscaler كارتون رەسىملەر ئۈچۈن ئەلالاشتۇرۇلغان. تېخىمۇ ئىنچىكە ھالقىلار ۋە سىلىق سىزىقلىق 4B32F تىپى. ئادەتتىكى رەسىملەر ئۈچۈن X4 UltraSharp V2 مودېلى ئۆتكۈرلۈك ۋە ئېنىقلىقنى تەكىتلەيدۇ. X4 UltraSharp V2 Lite; تېخىمۇ تېز ۋە كىچىكرەك ، GPU ئىچكى ساقلىغۇچنى ئاز ئىشلەتكەندە تەپسىلاتلارنى ساقلايدۇ. تەگلىكنى تېز ئۆچۈرۈۋېتىشنىڭ يېنىك مودېلى. تەڭپۇڭلۇق ئىقتىدار ۋە توغرىلىق. سۈرەت ، ئوبيېكت ۋە كۆرۈنۈشلەر بىلەن ئىشلەيدۇ. كۆپ ئىشلىتىلىدىغان ئەھۋاللارغا تەۋسىيە قىلىنىدۇ. BG نى ئۆچۈرۈڭ توغرىسىغا چېگرا قېلىنلىقى تىك چېگرا قېلىنلىقى %1$s رەڭلەر %1$s رەڭلەر ھازىرقى مودېل چۇۋۇشنى قوللىمايدۇ ، رەسىم ئەسلى ئۆلچەمدە بىر تەرەپ قىلىنىدۇ ، بۇ بەلكىم ئىچكى ساقلىغۇچ سەرپىياتى ۋە تۆۋەن سەپلىمىلىك ئۈسكۈنىلەردە مەسىلە پەيدا قىلىشى مۇمكىن چۇۋۇش چەكلەنگەن ، رەسىم ئەسلى ئۆلچەمدە بىر تەرەپ قىلىنىدۇ ، بۇ بەلكىم ئىچكى ساقلىغۇچنىڭ يۇقىرى سەرپىياتىنى ۋە تۆۋەن سەپلىمىلىك ئۈسكۈنىلەردە مەسىلە پەيدا قىلىشى مۇمكىن ، ئەمما يەكۈن چىقىرىشتا تېخىمۇ ياخشى ئۈنۈم بېرىشى مۇمكىن Chunking تەگلىكنى ئۆچۈرۈش ئۈچۈن يۇقىرى ئېنىقلىقتىكى رەسىم بۆلەك مودېلى كىچىكرەك ئىچكى ساقلىغۇچ ئىشلىتىش ئارقىلىق تېخىمۇ تېز تەگلىك ئۆچۈرۈش ئۈچۈن U2Net نىڭ يېنىك نۇسخىسى. تولۇق DDColor مودېلى ئەڭ تۆۋەن ئاسارە-ئەتىقىلەر بىلەن ئادەتتىكى رەسىملەرگە يۇقىرى سۈپەتلىك رەڭ بېرىدۇ. بارلىق رەڭلەش مودېللىرىنىڭ ئەڭ ياخشى تاللىشى. DDColor تەربىيەلەنگەن ۋە شەخسىي سەنئەت سانلىق مەلۇماتلىرى ئاز بولمىغان رېئال سەنئەت بۇيۇملىرى بىلەن كۆپ خىل ۋە بەدىئىي رەڭلەش نەتىجىسىنى ھاسىل قىلىدۇ. ئارقا كۆرۈنۈشنى ئېلىۋېتىش ئۈچۈن Swin Transformer نى ئاساس قىلغان يېنىك تىپتىكى BiRefNet مودېلى. ئۆتكۈر گىرۋەك ۋە ئېسىل تەپسىلاتلارنى ساقلاش بىلەن ئەلا سۈپەتلىك تەگلىك ئېلىۋېتىش ، بولۇپمۇ مۇرەككەپ جىسىملار ۋە مۇرەككەپ ئارقا كۆرۈنۈشلەردە. تەگلىك ئېلىۋېتىش ئەندىزىسى ، گىرۋەكلىرى سىلىق ماسكا ھاسىل قىلىپ ، ئادەتتىكى جىسىملارغا ۋە مۇۋاپىق تەپسىلاتلارنى ساقلاشقا ماس كېلىدۇ. مودېل ئاللىبۇرۇن چۈشۈرۈلدى مودېل مۇۋەپپەقىيەتلىك ئىمپورت قىلىندى تىپ ئاچقۇچلۇق سۆز Very Fast نورمال ئاستا Very Slow ھېسابلاش نىسبىتى ئەڭ تۆۋەن قىممىتى %1$s بارماق بىلەن رەسىم سىزىش ئارقىلىق رەسىمنى بۇرمىلاش Warp قاتتىقلىق Warp Mode يۆتكەڭ ئۆسۈڭ كىچىكلىتىش Swirl CW Swirl CCW Fade Strength Top Drop ئاستى تامچە باشلاش ئاخىرلىشىش چۈشۈرۈش سىلىق شەكىللەر تېخىمۇ يۇمىلاق ، تەبىئىي شەكىللەر ئۈچۈن ئۆلچەملىك يۇمىلاق شەكىللىك تىك تۆت بۇلۇڭنىڭ ئورنىغا ئادەتتىن تاشقىرى تېز سۈرئەتلىك نۇر ئىشلىتىڭ شەكىل تىپى كېسىڭ يۇمىلاق ئۈستەل سىلىق يۇمىلاق قىرلار يۇمىلاق ئەمەس كلاسسىك يۇمىلاق بۇلۇڭ شەكىل تىپى بۇلۇڭ چوڭلۇقى Squircle نەپىس يۇمىلاق UI ئېلېمېنتلىرى ھۆججەت ئىسمى فورماتى ھۆججەت نامىنىڭ بېشىدا قويۇلغان ئىختىيارى تېكىست ، تۈر ئىسمى ، ماركا ياكى شەخسىي خەتكۈچلەرگە ماس كېلىدۇ. ئەسلى ھۆججەت نامىنى كېڭەيتمەي ئىشلىتىڭ ، مەنبە پەرقلەندۈرۈشنى ساقلىشىڭىزغا ياردەم بېرىدۇ. پىكسېلدىكى رەسىم كەڭلىكى ، ئېنىقلىق دەرىجىسىنى ئۆزگەرتىش ياكى ئۆلچەش نەتىجىسىنى ئىز قوغلاشقا پايدىلىق. رەسىمنىڭ ئېگىزلىكى پېكسىل بولۇپ ، تەرەپ نىسبىتى ياكى ئېكسپورتى بىلەن ئىشلىگەندە پايدىلىق. ئۆزگىچە ھۆججەت نامىغا كاپالەتلىك قىلىش ئۈچۈن ئىختىيارى سان ھاسىل قىلىدۇ. كۆپەيتىلگەنگە قارشى قوشۇمچە بىخەتەرلىك ئۈچۈن تېخىمۇ كۆپ رەقەم قوشۇڭ. تۈركۈملەپ ئېكسپورت قىلىش ئۈچۈن ئاپتوماتىك كۆپەيتىش ھېساباتى ، بىر بۆلەكتە كۆپ رەسىمنى ساقلىغاندا كۆڭۈلدىكىدەك. قوللىنىشچان ئالدىن بېكىتىلگەن ئىسىمنى ھۆججەت نامىغا قىستۇرۇڭ ، بۇنداق بولغاندا رەسىمنىڭ قانداق بىر تەرەپ قىلىنغانلىقىنى ئاسانلا ئەستە ساقلىيالايسىز. بىر تەرەپ قىلىش جەريانىدا ئىشلىتىلگەن رەسىمنى كىچىكلىتىش ھالىتىنى كۆرسىتىدۇ ، چوڭ-كىچىكلىكى ، كېسىلگەن ياكى ماسلاشتۇرۇلغان رەسىملەرنى پەرقلەندۈرۈشكە ياردەم بېرىدۇ. ھۆججەت نامىنىڭ ئاخىرىغا قويۇلغان ئىختىيارى تېكىست ، _v2 ، _edited ياكى _final غا ئوخشاش نەشىر قىلىشقا پايدىلىق. ھۆججەت كېڭەيتىلمىسى (png, jpg ، webp قاتارلىقلار) ، ئاپتوماتىك ساقلانغان فورماتقا ئاپتوماتىك ماس كېلىدۇ. مۇكەممەل تەرتىپلەش ئۈچۈن java ئۆلچىمى ئارقىلىق ئۆزىڭىزنىڭ فورماتىنى بەلگىلىيەلەيدىغان خاسلاشتۇرغىلى بولىدىغان ۋاقىت تامغىسى. Fling Type Android Native iOS ئۇسلۇبى يۇمىلاق ئەگرى سىزىق Quick Stop Bouncy Floaty Snappy Ultra Smooth ماسلىشىشچان Accessibility Aware ھەرىكەتنى ئازايتتى يەرلىك ئاندىرويىد دومىلىما فىزىكىسى ئادەتتىكى ئىشلىتىش ئۈچۈن تەڭپۇڭ ، سىلىق ئۆرۈش تېخىمۇ يۇقىرى سۈركىلىش iOS كە ئوخشاش سىيرىلما ھەرىكەت روشەن سىيرىلىش تۇيغۇسى ئۈچۈن ئۆزگىچە ئەگرى سىزىق تېز توختاش بىلەن توغرا سىيرىلىش ئوينايدىغان ، ئىنكاس قايتۇرىدىغان قاڭقىش مەزمۇن كۆرۈش ئۈچۈن ئۇزۇن ، سىيرىلما دومىلىما ئۆز-ئارا تەسىر كۆرسىتىدىغان UI ئۈچۈن تېز ، ئىنكاس قايتۇرۇش ئۇزارتىلغان ھەرىكەتلەندۈرگۈچ كۈچ بىلەن ئەلا سۈپەتلىك سىيرىلىش فىزىكىنى تەڭشەش سۈرئىتىگە ئاساسەن تەڭشەيدۇ سىستېمىنىڭ زىيارەت قىلىش تەڭشىكىگە ھۆرمەت قىلىدۇ زىيارەت قىلىش ئېھتىياجى ئۈچۈن ئەڭ تۆۋەن ھەرىكەت Primary Line ھەر بەشىنچى قۇرغا قېلىن سىزىق قوشىدۇ رەڭنى تولدۇرۇڭ يوشۇرۇن قوراللار ئورتاقلىشىش ئۈچۈن يوشۇرۇنغان قوراللار رەڭلىك كۇتۇپخانا كەڭ رەڭلەرنى كۆرۈڭ تەبىئىي تەپسىلاتلارنى ساقلاش بىلەن بىر ۋاقىتتا سۈرەتتىكى تۇتۇقلىقنى ئۆتكۈرلەشتۈرۈۋېتىدۇ ۋە چىقىرىپ تاشلايدۇ ، فوكۇس سىرتىدىكى سۈرەتلەرنى ئوڭشاشقا ماس كېلىدۇ. ئەقلىي ئىقتىدار ئىلگىرى چوڭايتىلغان رەسىملەرنى ئەسلىگە كەلتۈرۈپ ، يوقاپ كەتكەن تەپسىلاتلار ۋە توقۇلمىلارنى ئەسلىگە كەلتۈرىدۇ. نەق مەيدان ھەرىكەت مەزمۇنىغا ئەلالاشتۇرۇلغان ، پىرىسلاش بۇيۇملىرىنى ئازايتىدۇ ۋە كىنو / تېلېۋىزىيە پروگرامما رامكىلىرىدىكى ئىنچىكە ھالقىلارنى ئۆستۈرىدۇ. VHS سۈپەتلىك كۆرۈنۈشلەرنى HD غا ئايلاندۇرىدۇ ، لېنتا شاۋقۇنىنى چىقىرىپ تاشلايدۇ ۋە ئۈزۈم تۇيغۇسىنى ساقلايدۇ. تېكىست ئېغىر رەسىم ۋە ئېكران كۆرۈنۈشلىرى ئۈچۈن مەخسۇس ، ھەرپلەرنى ئۆتكۈرلەشتۈرۈپ ، ئوقۇشچانلىقىنى ئۆستۈرىدۇ. كۆپ خىل سانلىق مەلۇمات سانلىق مەلۇماتلىرى بويىچە تەربىيىلەنگەن ئىلغار دەرىجىگە كۆتۈرۈش ، ئادەتتىكى مەقسەتلىك رەسىمنى ئاشۇرۇش ئۈچۈن ناھايىتى ياخشى. تور بېسىلغان رەسىملەر ئۈچۈن ئەلالاشتۇرۇلغان ، JPEG ئاسارە-ئەتىقىلەرنى چىقىرىپ تاشلاپ ، تەبىئىي كۆرۈنۈشنى ئەسلىگە كەلتۈرىدۇ. تېخىمۇ ياخشى توقۇلمىلارنى قوغداش ۋە ئاسارە-ئەتىقىلەرنى ئازايتىش بىلەن تور رەسىملىرىنىڭ نەشىرى ياخشىلاندى. قوش يىغىلىش تىرانسفورموتور تېخنىكىسى بىلەن 2x يۇقىرى كۆتۈرۈلۈپ ، ئۆتكۈرلۈك ۋە تەبىئىي تەپسىلاتلارنى ساقلايدۇ. ئىلغار تىرانسفورموتور قۇرۇلمىسىنى ئىشلىتىپ 3x يۇقىرى كۆتۈرۈش ، ئوتتۇراھال چوڭايتىش ئېھتىياجىغا ماس كېلىدۇ. زامانىۋى تىرانسفورموتور تورى بىلەن 4x يۇقىرى سۈپەتلىك يۇقىرى كۆتۈرۈلۈش ، تېخىمۇ چوڭ كۆلەمدە ئىنچىكە تەپسىلاتلارنى ساقلايدۇ. سۈرەتتىكى تۇتۇق / شاۋقۇننى ۋە تەۋرىنىشنى يوقىتىدۇ. ئومۇمىي مەقسەت ئەمما سۈرەتتىكى ئەڭ ياخشى. BSRGAN نىڭ ناچارلىشىشىغا ئەلالاشتۇرۇلغان Swin2SR تىرانسفورموتور ئارقىلىق تۆۋەن سۈپەتلىك رەسىملەرنى ئەسلىگە كەلتۈرىدۇ. ئېغىر دەرىجىدىكى پىرىسلاش بۇيۇملىرىنى ئوڭشاش ۋە 4x ئۆلچەمدە تەپسىلاتلارنى ئاشۇرۇشقا ناھايىتى ماس كېلىدۇ. BSRGAN نىڭ ناچارلىشىشى توغرىسىدا تەربىيەلەنگەن SwinIR تىرانسفورموتور بىلەن 4x يۇقىرى كۆتۈرۈلگەن. رەسىم ۋە مۇرەككەپ كۆرۈنۈشلەردە تېخىمۇ ئۆتكۈر توقۇلمىلار ۋە تېخىمۇ تەبىئىي تەپسىلاتلار ئۈچۈن GAN نى ئىشلىتىڭ. Path PDF نى بىرلەشتۈرۈش بىر نەچچە PDF ھۆججىتىنى بىر ھۆججەتكە بىرلەشتۈرۈڭ Files Order pp. PDF نى پارچىلاش PDF ھۆججىتىدىن كونكرېت بەتلەرنى چىقىرىڭ PDF نى ئايلاندۇرۇش بەت يۆنىلىشىنى مەڭگۈلۈك ئوڭشاڭ بەتلەر PDF نى قايتا رەتلەڭ بەتلەرنى سۆرەپ تاشلاڭ بەتلەرنى تۇتۇش ۋە سۆرەش بەت سانى ھۆججەتلىرىڭىزگە ئاپتوماتىك نومۇر قوشۇڭ بەلگە فورماتى PDF دىن تېكىست (OCR) PDF ھۆججىتىڭىزدىن ئاددىي تېكىستنى ئېلىڭ ماركا ياكى بىخەتەرلىك ئۈچۈن خاس تېكىستنى قاپلاڭ ئىمزا ھەر قانداق ھۆججەتكە ئېلېكترونلۇق ئىمزاسىڭىزنى قوشۇڭ بۇ ئىمزا سۈپىتىدە ئىشلىتىلىدۇ PDF قۇلۇپىنى ئېچىڭ قوغدىلىدىغان ھۆججەتلىرىڭىزدىن پارولنى ئۆچۈرۈڭ PDF نى قوغداش كۈچلۈك مەخپىيلەشتۈرۈش ئارقىلىق ھۆججەتلىرىڭىزنى بىخەتەر قىلىڭ مۇۋەپپەقىيەت PDF قۇلۇپ ئېچىلدى ، ئۇنى ساقلىسىڭىز ياكى ئورتاقلىشالايسىز PDF نى رېمونت قىلىڭ بۇزۇلغان ياكى ئوقۇغىلى بولمايدىغان ھۆججەتلەرنى ئوڭشاشقا ئۇرۇنۇش كۈلرەڭ بارلىق ھۆججەت قىستۇرۇلغان رەسىملەرنى كۈلرەڭگە ئۆزگەرتىڭ PDF نى بېسىڭ ئورتاقلىشىش ئۈچۈن ھۆججەت ھۆججىتىنىڭ چوڭ-كىچىكلىكىنى ئەلالاشتۇرۇڭ ImageToolbox ئىچكى ھالقىما پايدىلىنىش جەدۋىلىنى قايتا قۇرۇپ ، ھۆججەت قۇرۇلمىسىنى باشتىن-ئاخىر ئەسلىگە كەلتۈرىدۇ. بۇ \ \"ئاچقىلى بولمايدىغان \\" نۇرغۇن ھۆججەتلەرنى زىيارەت قىلىشنى ئەسلىگە كەلتۈرەلەيدۇ. بۇ قورال بارلىق ھۆججەت رەسىملىرىنى كۈلرەڭگە ئايلاندۇرىدۇ. ھۆججەتنىڭ چوڭ-كىچىكلىكىنى بېسىپ چىقىرىش ۋە ئازايتىشتا ئەڭ ياخشى Metadata تېخىمۇ ياخشى مەخپىيەتلىك ئۈچۈن ھۆججەت خاسلىقىنى تەھرىرلەڭ خەتكۈچ ئىشلەپچىقارغۇچى ئاپتور ئاچقۇچلۇق سۆزلەر ياراتقۇچى مەخپىيەتلىك چوڭقۇر بۇ ھۆججەتنىڭ بارلىق ئىشلەتكىلى بولىدىغان مېتا سانلىق مەلۇماتلىرىنى تازىلاڭ بەت چوڭقۇر OCR ھۆججەتتىن تېكىست چىقىرىپ ، Tesseract ماتورى ئارقىلىق بىر تېكىست ھۆججىتىگە ساقلاڭ بارلىق بەتلەرنى ئۆچۈرەلمەيدۇ PDF بەتلىرىنى ئۆچۈرۈڭ PDF ھۆججىتىدىن ئالاھىدە بەتلەرنى ئۆچۈرۈڭ ئۆچۈرۈشنى چېكىڭ قولدا Crop PDF ھۆججەت بەتلىرىنى خالىغانچە كېسىڭ تەكشى PDF ھۆججەت بەتلىرىنى رەتلەش ئارقىلىق PDF نى ئۆزگەرتكىلى بولمايدۇ كامېرانى قوزغىتالمىدى. ئىجازەتنى تەكشۈرۈپ ، ئۇنىڭ باشقا بىر دېتال تەرىپىدىن ئىشلىتىلمىگەنلىكىنى جەزملەشتۈرۈڭ. رەسىملەرنى چىقىرىڭ ئەسلى ئېنىقلىقتىكى PDF لارغا قىستۇرۇلغان رەسىملەرنى چىقىرىڭ بۇ PDF ھۆججىتىدە ھېچقانداق قىستۇرما رەسىم يوق بۇ قورال ھەر بىر بەتنى سايىلەپ ، تولۇق سۈپەتلىك رەسىملەرنى ئەسلىگە كەلتۈرىدۇ - ئەسلى ھۆججەتلەرنى ھۆججەتلەردىن ساقلاشقا ماس كېلىدۇ ئىمزا سىزىش قەلەم پاراملىرى ھۆججەتلەرگە قويماقچى بولغان رەسىمنى رەسىم سۈپىتىدە ئىشلىتىڭ Zip PDF بېرىلگەن ئارىلىق بىلەن ھۆججەتنى پارچىلاپ ، يېڭى ھۆججەتلەرنى zip ئارخىپىغا قاچىلاڭ ئارىلىق PDF نى بېسىڭ ئىختىيارى بەت چوڭلۇقى بىلەن بېسىپ چىقىرىش ئۈچۈن ھۆججەت تەييارلاڭ ھەر بىر بەت يۆنىلىش بەت چوڭلۇقى Margin بلۇم يۇمشاق تىز كارتون ۋە كارتون ئۈچۈن ئەلالاشتۇرۇلغان. ياخشىلانغان تەبىئىي رەڭلەر ۋە ئاسارە-ئەتىقىلەر ئاز سامسۇڭ One UI 7 ئۇسلۇبىنى ياقتۇرىدۇ لازىملىق قىممەتنى ھېسابلاش ئۈچۈن بۇ يەرگە ئاساسىي ماتېماتىكا بەلگىسىنى كىرگۈزۈڭ (مەسىلەن (5 + 5) * 10) ماتېماتىكا ئىپادىلەش %1$s رەسىملەرنى تاللاڭ ئالفا فورماتىنىڭ تەگلىك رەڭگى ھەر بىر رەسىم فورماتىغا ئالفا قوللىشى بىلەن تەگلىك رەڭ بەلگىلەش ئىقتىدارى قوشىدۇ ، بۇنى پەقەت ئالفا بولمىغانلارلا ئىشلىتەلەيدۇ چېسلا ۋاقتىنى ساقلاڭ چېسلا ۋە ۋاقىتقا مۇناسىۋەتلىك exif خەتكۈچلىرىنى ھەر ۋاقىت ساقلاپ قويۇڭ ، exif تاللاشنى ساقلاپ قېلىشتىن مۇستەقىل ئىشلەيدۇ تۈرنى ئېچىڭ ئىلگىرى ساقلانغان رەسىم قورال ساندۇقى تۈرىنى تەھرىرلەشنى داۋاملاشتۇرۇڭ رەسىم قورال ساندۇقى تۈرىنى ئاچالمىدى رەسىم قورال ساندۇقى تۈرى تۈر سانلىق مەلۇماتلىرىنى كەملەپ قالدى رەسىم قورال ساندۇقى تۈرى بۇزۇلغان قوللىمايدىغان رەسىم قورال ساندۇقى تۈر نۇسخىسى: %1$d تۈرنى ساقلاش تەھرىرلىگىلى بولىدىغان تۈر ھۆججىتىدە قەۋەت ، تەگلىك ۋە تەھرىرلەش تارىخىنى ساقلاڭ ئېچىلمىدى ئىزدەشكە بولىدىغان PDF غا يېزىڭ رەسىم گۇرۇپپىسىدىكى تېكىستنى تونۇپ ، رەسىم ۋە تاللىغىلى بولىدىغان تېكىست قەۋىتى بىلەن ئىزدەشكە بولىدىغان PDF نى ساقلاڭ قەۋەت ئالفا توغرىسىغا توغرىلاش Vertical Flip قۇلۇپ سايە قوشۇڭ سايە رەڭگى Text Geometry ئۆتكۈر ئۇسلۇب ئۈچۈن تېكىستنى سوزۇش ياكى ئېغىش Scale X. Skew X. ئىزاھلارنى ئۆچۈرۈڭ ئۇلىنىش ، باھا ، يارقىن نۇقتا ، شەكىل ياكى شەكىل بۆلەكلىرى قاتارلىق تاللانغان ئىزاھات تىپلىرىنى PDF بېتىدىن ئۆچۈرۈڭ Hyperlinks ھۆججەت قوشۇمچە ھۆججەتلىرى قۇرلار پوپلار ماركا شەكىللەر تېكىست ئىزاھلىرى Text Markup شەكىل مەيدانى Markup نامەلۇم ئىزاھلار Ungroup سەپلىگىلى بولىدىغان رەڭ ۋە سىزىقلار بىلەن قەۋەتنىڭ ئارقىسىغا تۇتۇق سايە قوشۇڭ ================================================ FILE: core/resources/src/main/res/values-uk/strings.xml ================================================ Зображення завелике для попереднього перегляду, але спроба збереження все одно буде здійснена Виберіть зображення для початку Тип масштабування Ви впевнені, що хочете закрити програму? Застосунок закривається Скинуто Перезавантаження Скопійовано до буфера Редагувати EXIF EXIF не знайдено Очистити EXIF Обрізати Всі незбережені зміни будуть втрачені, якщо ви підете зараз Колір скопійовано Довільно обрізати зображення Зберегти EXIF Вилучити Створити зразок палітри кольорів із заданого зображення Згенерувати палітру Нова версія %1$s За замовчуванням Власний Не визначено Пам\'ять пристрою Змінити розмір за вагою Максимальний розмір в КБ Змінити розмір зображення відповідно до заданого розміру в КБ Щось пішло не так: %1$s Висота %1$s Розмір %1$s Завантаження… Розширення Ширина %1$s Якість Закрити Оберіть зображення Точний Скинути зображення Гнучкий Залишитися Додати тег Скасувати Вихідний код Зображення будуть повернуті до початкового вигляду Виняток Щось пішло не так Перезапустити застосунок Гаразд Пресети Змінити, змінити розмір та відредагувати одне зображення Очистити Зберегти Всі EXIF-дані зображення будуть очищені, цю дію неможливо скасувати! Збереження Отримуйте останні оновлення, обговорюйте проблеми та багато іншого Одинарне редагування Виберіть колір із зображення, скопіюйте або поділіться ним Вибір кольору Зображення Колір Версія Зображення: %d Попередній перегляд змін Палітра Непідтримуваний тип: %1$s Оновити Оригінал Вихідна папка Не вдається згенерувати палітру для заданого зображення Порівняти Порівняння двох наведених зображень Виберіть два зображення для початку Виберіть зображення Налаштування Нічний режим Темний Світлий Як у системі Динамічні кольори Увімкнути Monet Мова Якщо ввімкнено, кольори застосунку будуть підлаштовуватися під вибране зображення в режимі редагування Налаштування Режим amoled Червоний Зелений Синій Вставте дійсний aRGB код кольору Колірна гамма Якщо ввімкнути, колір поверхонь буде встановлено на абсолютну темряву в нічному режимі Нічого вставляти Колірну схему програми не можна змінити, якщо ввімкнено динамічні кольори Тема застосунку базуватиметься на кольорі, який ви виберете Оновлень не знайдено Про застосунок Відстеження проблем Надсилайте сюди звіти про помилки та запити щодо функцій Допоможіть перекласти Шукайте тут Виправте помилки перекладу або перекладіть проєкт іншими мовами За вашим запитом нічого не знайдено Якщо ввімкнено, то кольори додатку підлаштуються під шпалери Не вдалося зберегти %d зображення Первинний Третинний Вторинний Поверхня Товщина рамки Значення Додати Дозвіл Дозволити Застосунку потрібен доступ до вашого сховища для збереження зображень. Будь ласка, надайте дозвіл у наступному діалоговому вікні Застосунку потрібен цей дозвіл для роботи, будь ласка, надайте його вручну Зовнішня пам\'ять Кольори Monet Цей застосунок є абсолютно безплатним, але якщо ви хочете підтримати розвиток проєкту, ви можете натиснути тут Вирівнювання FAB Якщо увімкнено, після запуску застосунку буде показано діалогове вікно оновлення Масштабування зображення Перевірка наявності оновлення Префікс Ім\'я файлу Поділитися Виберіть, який смайл буде відображатися на головному екрані Смайли Додати розмір файлу Видалити EXIF Видалення метаданих EXIF з будь-якої пари зображень Якщо увімкнено, додає ширину та висоту збереженого зображення до назви вихідного файлу Попередній перегляд зображення Переглядайте будь-які типи зображень: GIF, SVG тощо Джерело зображення Вибір фотографій Галерея Файловий провідник Сучасний вибір фото в Android (знизу екрана), працює лише на Android 12+. Має проблеми з отриманням метаданих EXIF. Використовувати GetContent для отримання зображення, працює скрізь, але на деяких пристроях можуть виникати проблеми з отриманням вибраних зображень, це не моя провина. Простий вибір зображень галереї, він працюватиме, лише якщо у вас є цей застосунок Розташування опцій Редагувати Порядок Визначає порядок інструментів на головному екрані Кількість смайлів Додати порядковий номер порядковий номер Додати оригінальне ім\'я файлу Якщо ввімкнено, замінює стандартну мітку часу на порядковий номер зображення, якщо ви використовуєте пакетну обробку оригінальне ім\'я файлу Якщо ввімкнено, додає назву оригінального файлу до назви вихідного зображення Додавання оригінальної назви файлу не працює, якщо вибрано джерело зображення за допомогою фотопікселя Завантаження веб-зображення Завантажуйте будь-яке зображення з Інтернету, переглядайте його, масштабуйте, а також зберігайте або редагуйте його за бажанням Немає зображення Посилання на зображення Заповнити Адаптувати Змінює розмір зображень до заданої висоти та ширини. Співвідношення сторін зображень може змінюватися. Масштаб змісту Перехресне штрихування Інтервал Яскравість Контраст Відтінок Насиченість Додати фільтр Кольоровий фільтр Альфа Баланс білого Температура Ширина лінії Кромка Собель Змінює розмір зображень з довгою стороною до заданої висоти або ширини. Усі розрахунки розміру будуть виконані після збереження. Співвідношення сторін зображень буде збережено. Фільтри Фільтр Застосування ланцюжків фільтрів до зображень Світлий Експозиція Напівтон Розмиття Колірний простір GCA Гаусове розмиття Розмиття поля Тонування Монохромний Гамма Відблиски і тіні Відблиски Тіні Туман Ефект Відстань Опуклість Радіус Масштаб Змінити розмір зображень до заданої висоти та ширини, зберігаючи співвідношення сторін Ескіз Поріг Рівні квантування Гладкий мультфільм Мультфільм Постеризувати Не максимальне придушення Слабке включення пікселів Пошук Стек розмиття Згортка 3х3 RGB фільтр Швидке розмиття Розмір розмиття Поріг яскравості Нахил Різкість Сепія Негатив Соляризація Вібранс Чорний і білий Двостороннє розмиття Тиснення Лапласіан Віньєтка Старт Кінець Згладжування Кувахара Спотворення Кут Вир Розширення Заломлення сфери Показник заломлення Рефракція скляної сфери Кольорова матриця Непрозорість Змінити розмір за лімітами Фальшивий колір Перший колір Другий колір Змінити порядок Розмиття по центру x Центр розмиття y Збільшити розмиття Колірний баланс Малювати Малюйте на зображенні, як в альбомі, або малюйте на самому фоні Ви вимкнули застосунок \"Файли\", ввімкніть його, щоб скористатися цією функцією Виберіть зображення та намалюйте щось на ньому Малювати на фоні Колір фону Малювати на зображенні Колір фарби Виберіть колір фону і малюйте поверх нього Прозорість Розшифрувати Виберіть файл для запуску Розшифровка Функції Шифр Зашифрувати та розшифрувати будь-який файл (не тільки зображення) на основі різних криптоалгоритмів Сумісність Максимальний розмір файлу обмежений операційною системою Android і доступною пам\'яттю, яка, очевидно, залежить від вашого пристрою. \nЗверніть увагу: пам\'ять - це не сховище. Виберіть файл Ключ Збережіть цей файл на своєму пристрої або скористайтеся \"Поділитися\", щоб розмістити його де завгодно Реалізація Шифрування файлів на основі пароля. Отримані файли можна зберігати у вибраному каталозі або надавати спільний доступ. Розшифровані файли також можна безпосередньо відкрити. AES-256, режим GCM, без доповнення, 12 байт випадкових IV за замовчуванням. Ви можете вибрати потрібний алгоритм. Ключі використовуються як 256-бітні SHA-3 хеші. Шифрувати Файл оброблено Розмір файлу Шифрування Зверніть увагу, що сумісність з іншими програмами або сервісами для шифрування файлів не гарантується. Причиною несумісності може бути дещо інша обробка ключів або конфігурація шифру. Групує параметри на головному екрані за їхнім типом, а не за власним розташуванням списків Неправильний пароль або вибраний файл не зашифровано Спроба зберегти зображення із заданою шириною та висотою може призвести до нестачі пам\'яті. Кеш Розмір кешу Знайдено %1$s Автоматичне очищення кешу Якщо ввімкнено, кеш застосунку буде очищено під час запуску застосунку Інструменти Групування параметрів за типом Неможливо змінити розташування, якщо ввімкнено групування параметрів Створити Редагувати знімок екрана Вторинне налаштування Знімок екрана Запасний варіант Пропустити Копіювати Збереження в режимі %1$s може бути нестабільним, оскільки це формат без втрат Якщо ви вибрали пресет 125, зображення буде збережено у розмірі 125% від оригінального зображення. Якщо ви вибрали пресет 50, зображення буде збережено у розмірі 50%. Попереднє налаштування визначає відсоток вихідного файлу, тобто якщо ви оберете попереднє налаштування 50 для зображення розміром 5 МБ, то після збереження ви отримаєте зображення розміром 2,5 МБ. Збережено до теки %1$s Обговоріть додаток та отримайте відгуки від інших користувачів. Ви також можете отримати там бета-оновлення та аналітику. Випадкова назва файла Якщо ввімкнено, назва файлу буде повністю випадковою Збережено в теки %1$s з назвою %2$s Чат Telegram Маска для обрізання Співвідношення сторін Використовуйте цей тип маски для створення маски з даного зображення, зверніть увагу, що вона ПОВИННА мати альфа-канал Резервне копіювання та відновлення Резервна копія Резервне копіювання налаштувань застосунку до файлу Пошкоджений файл або відсутня резервна копія Налаштування успішно відновлено Зв’язатися зі мною Відновлення Відновлення налаштувань застосунку з раніше створеного файлу Це поверне ваші налаштування до значень за замовчуванням. Зверніть увагу, що це неможливо скасувати без резервної копії, згаданої вище. Видалити Ви збираєтеся видалити вибрану колірну схему. Цю дію неможливо скасувати. Видалити схему Шрифт Текст Розмір тексту За замовчуванням Емоції Їжа та напої Природа і тварини Об\'єкти Символи Увімкнути емодзі Подорожі та місця Активності Вилучення фону Видаліть фон із зображення, намалювавши або скориставшись опцією Auto Обрізати зображення Автоматичне видалення фону Використання більшого розміру тексту призведе до проблем з інтерфейсом, які неможливо вирішити. Використовуйте лише на свій розсуд Оригінальні метадані зображення будуть збережені Відновити зображення Прозорий простір навколо зображення буде обрізано Упс… Щось пішло не так. Ви можете написати мені, використовуючи опції нижче, і я спробую знайти рішення. Максимальна кількість кольорів Наразі формат %1$s на android дозволяє лише читати exif, а не змінювати/зберігати його, це означає, що вихідне зображення взагалі не матиме метаданих Дозволити збирати анонімну статистику використання програми Режим малювання Стерти фон Ступінь розмиття Це дозволяє програмі автоматично збирати звіти про збої Повідомити про помилку Піпетка Аналітика Змінити розмір і конвертувати Відновити фон Змініть розмір заданих зображень або конвертуйте їх в інші формати, також там ви можете редагувати метадані EXIF, якщо ви вибрали одне зображення Режим стирання Оновлення Зачекайте Значення %1$s означає швидке стиснення, що призводить до відносно великого розміру файлу. %2$s означає витрачати більше часу на стиснення, в результаті чого менший файл Дозволити бета-версії Аа Бб Вв Гг Ґґ Дд Ее Єє Жж Зз Ии Іі Її Йй Кк Лл Мм Нн Оо Пп Рр Сс Тт Уу Фф Хх Цц Чч Шш Щщ Ьь Юю Яя 0123456789 !? Збереження майже завершено, якщо ви скасуєте, що тепер вам потрібно буде почати збереження знову Зусилля Перевірка оновлень включатиме бета-версії застосунків, якщо ввімкнено Горизонтальна Перекодування Допущення Обидва Порядок зображень Змінює кольори теми на негативні, якщо увімкнено Точність М\'якість пензлика Розмиття країв Веселка Грайлива тема: відтінок вихідного кольору не зображається в темі Зникання країв Яскрава тема, барвистість максимальна для Основної палітри і підвищена для інших Об\'єднання зображень Пікселізація Інструменти PDF Стиль, який трохи більш хроматичний, ніж монохромний Покращена пікселізація Цільовий колір Намалювати стрілки Виберіть мінімум 2 зображення Перевірка оновлень буде підключатися до GitHub, щоб перевірити, чи доступне нове оновлення Розмір Пікселя Схема, яка поміщає вихідний колір у Scheme.primaryContainer Тональна пляма Пошук Змінити колір Діамантова пікселізація Малює розмиті краї під вихідним зображенням, щоб заповнити простір навколо нього, а не одним кольором, якщо ввімкнено. Вилучити колір Зображення до PDF Якщо ввімкнено, то на кінці лінії буде домальовуватися стрілка Монохромна тема, кольори суто чорний/білий/сірий. Якщо увімкнено, маленькі зображення будуть масштабовані до найбільшого в послідовності Фруктовий салат Покращена діамантова пікселізація Кругова пікселізація Блокування орієнтації малювання Об\'єднати вхідні зображення в одне велике Передперегляд PDF Схема, яка дуже схожа на схему вмісту Перевірити оновлення Дозволяє шукати серед усіх доступних інструментів на головному екрані Орієнтація зображення Вміст Вимкнуто Вертикальна PDF на зображення Виразний Нейтральний Колір для видалення Стиль типової палітри дозволяє налаштувати всі чотири кольори, інші дозволяють встановити лише ключовий колір Колір для заміни Пакування заданих зображень у файл PDF Зображення будуть обрізані по центру до введеного розміру. Полотно буде розширене із заданим кольором фону, якщо зображення менше введених розмірів. Пожертвування Простий переглядач PDF Масштаб вихідного зображення Якщо ввімкнено в режимі малювання, екран не обертатиметься Стиль палітри Інвертувати кольори Яскравий Штрихова пікселізація Звичайний Опрацювання файлів PDF: Попередній перегляд, перетворення на пакет зображень або створення із заданих зображень Покращена кругова пікселізація Застереження Масштабування маленьких зображень до великих Перетворення PDF на зображення в заданому форматі Вібрація Буфер обміну Сила вібрації Розпізнавайте текст із заданого зображення, підтримується понад 120 мов На зображенні немає тексту або застосунок не знайшов його Автоматично додає збережене зображення до буфера обміну, якщо ввімкнено Збереження в сховищі не буде виконано, і зображення за можливості буде поміщене в буфер обміну Значення в діапазоні %1$s - %2$s Тільки натискання OCR (розпізнавання тексту) Панелі програм Намалюйте тінь за панелями програм Покращений глюк Зсув каналу X Зсув каналу Y Розмір корупції Корупційний зсув X Корупційний зсув Ю Розмиття намету Бічне вицвітання сторона Топ Орієнтація & Лише виявлення сценарію Автоматична орієнтація & Виявлення сценарію Тільки авто Авто Глюк Сума Сортування пікселів Перетасувати Драго Відрізати Мебіус Каталог \"%1$s\" не знайдено, ми змінили його на стандартний, збережіть файл ще раз Автоматична шпилька Щоб перезаписати файли, потрібно використовувати джерело зображень \"Провідник\", спробуйте повторно вибрати зображення, ми змінили джерело зображень на потрібне Перезаписати файли Оригінальний файл буде замінено новим замість збереження у вибраній папці. Джерелом зображення для цієї опції має бути \"Провідник\" або GetContent, коли ввімкнути цю опцію, вона буде встановлена автоматично Порожній Суфікс безкоштовно Емодзі як колірна схема Електронна пошта Намальована маска фільтра буде відрендерена, щоб показати вам приблизний результат Неон Розмиття конфіденційності Додайте сяючий ефект своїм малюнкам Стрілка лінія Малює вказівну стрілку від заданого шляху Овальний Rect Малює прямокутник від початкової до кінцевої точки Малює овал від початкової до кінцевої точки Малює контурний овал від початкової до кінцевої точки Малює окреслений прямокутник від початкової до кінцевої точки Лінійна (або білінійна, у двох вимірах) інтерполяція зазвичай хороша для зміни розміру зображення, але спричиняє деяке небажане пом’якшення деталей і все ще може бути дещо нерівним Найпростіший режим масштабування Android, який використовується майже у всіх програмах Метод плавної інтерполяції та повторної вибірки набору контрольних точок, який зазвичай використовується в комп’ютерній графіці для створення плавних кривих Функція вікон, яка часто застосовується в обробці сигналу, щоб мінімізувати спектральний витік і підвищити точність частотного аналізу шляхом звуження країв сигналу Техніка математичної інтерполяції, яка використовує значення та похідні в кінцевих точках сегмента кривої для створення гладкої та безперервної кривої Метод повторної дискретизації, який підтримує високоякісну інтерполяцію шляхом застосування зваженої функції sinc до значень пікселів Метод передискретизації, який використовує фільтр згортки з регульованими параметрами для досягнення балансу між різкістю та згладжуванням у масштабованому зображенні Використовує кусково-визначені поліноміальні функції для плавної інтерполяції та апроксимації кривої або поверхні, забезпечуючи гнучке та безперервне представлення форми Одна колонка Одноблоковий вертикальний текст Одиночний блок Одна лінія Одне слово Обведіть слово Один символ Розріджений текст Орієнтація розрідженого тексту & Виявлення сценарію Необроблена лінія Ви бажаєте видалити навчальні дані оптичного розпізнавання мови \"%1$s\" для всіх типів розпізнавання чи лише для вибраного (%2$s)? все Зсув X Зсув Y Тип водяного знака Повторити підрахунок мілі FPS Джарвіс Джудіс Нінке Дітерінг Аткінсон Дітерінг Беркс Дізерінг False Floyd Steinberg Dithering Просте порогове згладжування насіння Анагліф Шум Дно Сила Логарифмічне відображення тонів Кристалізуватися Колір обведення Спотворення Перліна Дейтераномалія Протаномалія Вінтаж Брауні Coda Chrome Нічне бачення Теплий круто Тританопія Деутаронотопія Протанопія Ахроматомалія Золота година Спекотне літо Фіолетовий туман Схід сонця Кольоровий вибух Електричний градієнт Додає контейнер із вибраною фігурою під значками Олдрідж Учімура Перехід пік Колірна аномалія Зображення перезаписано в оригінальному місці призначення Неможливо змінити формат зображення, якщо ввімкнено параметр перезапису файлів Використовує основний колір емодзі як колірну схему програми замість визначеної вручну Роз’їдати Анізотропна дифузія дифузія Проведення Горизонтальний вітер Швидке двостороннє розмиття Розмиття Пуассона Фрактальне скло Амплітуда Мармур Турбулентність олія Ефект води Розмір Частота X Частота Y Амплітуда X Амплітуда Y Hable Filmic Tone Mapping Тонове відображення Хеджі-Берджеса ACES Filmic Tone Mapping ACES Hill Tone Mapping поточний Повний фільтр старт центр Кінець Застосуйте будь-які ланцюжки фільтрів до заданих зображень або окремого зображення Створення градієнта Створіть градієнт заданого вихідного розміру з налаштованими кольорами та типом вигляду швидкість Розтушовувати Омега Оцініть додаток Оцінка Ця програма абсолютно безкоштовна, якщо ви хочете, щоб вона стала більшою, позначте проект зірочкою на Github 😄 Кольорова матриця 4х4 Кольорова матриця 3х3 Прості ефекти Полароїд Тританомалія Ахроматопсія Лінійний Радіальний підмітати Тип градієнта Центр X Центр Ю Режим плитки Повторюється Дзеркало Затискач Декаль Кольорові зупинки Додайте колір Властивості Ласо Малює замкнутий контур із заливкою за заданим контуром Режим малювання шляху подвійна лінія стрілка Безкоштовне малювання Подвійна стрілка Лінія стрілка Малює шлях як вхідне значення Малює шлях від початкової до кінцевої точки як лінію Малює вказівну стрілку від початкової до кінцевої точки як лінію Малює подвійну стрілку від початкової точки до кінцевої точки як лінію Малює подвійну стрілку від заданого шляху Контурований овал Окреслений Rect Дизерінг Квантизер Шкала сірого Bayer Two By Two Dithering Bayer Three By Three Dithering Bayer Four By Four Dithering Bayer Eight By Eight Dithering Флойд Стайнберг Дітерінг Sierra Dithering Two Row Sierra Dithering Sierra Lite Dithering Стукі Дізерінг Змішування зліва направо Випадкове згладжування Попередній перегляд маски Режим масштабування Білінійний Ханн Ерміт Ланцош Мітчелл Найближчий Сплайн Базовий Значення за замовчуванням Сигма Просторова сигма Середнє розмиття Кетмулл Бікубічний Кращі методи масштабування включають перевибірку Ланцоша та фільтри Мітчелла-Нетравалі Один із найпростіших способів збільшення розміру, заміна кожного пікселя кількома пікселями одного кольору Форма значка Максимальна яскравість Екран Градієнтне накладання Створити будь-який градієнт верхньої частини заданих зображень Трансформації Камера Зробіть фотографію камерою. Зверніть увагу, що з цього джерела зображень можна отримати лише одне зображення. зерно Нерізкий Пастель Помаранчевий серпанок Рожева мрія Барвистий вир М\'яке весняне світло Осінні тони Лавандова мрія Кіберпанк Лимонад Light Спектральний вогонь Нічна магія Фантазійний пейзаж Карамельна темрява Футуристичний градієнт Зелене сонечко Райдужний світ Deep Purple Космічний портал Червоний вир Цифровий код Водяний знак Зображення на обкладинці з настроюваними текстовими/графічними водяними знаками Повторіть водяний знак Повторює водяний знак поверх зображення замість одного в заданому місці Це зображення буде використано як шаблон для водяних знаків Колір тексту Режим накладання Боке Інструменти GIF Перетворіть зображення на зображення GIF або витягніть кадри з заданого зображення GIF GIF до зображень Перетворіть файл GIF на пакет зображень Перетворіть пакет зображень у файл GIF Зображення в GIF Виберіть зображення GIF для початку Використовуйте розмір першого кадру Замініть вказаний розмір першими розмірами кадру Затримка кадру Використовуйте ласо Оригінальний попередній перегляд зображення Альфа Використовує ласо, як у режимі малювання, для виконання стирання Фільтр масок Застосовуйте ланцюжки фільтрів до заданих маскованих областей, кожна область маски може визначати свій власний набір фільтрів маски Додати маску Маска %d Емодзі панелі програм змінюватиметься випадковим чином Випадкові емодзі Ви не можете використовувати випадкові емодзі, поки емодзі вимкнено Ви не можете вибрати емодзі, коли ввімкнено випадкові емодзі Старий телевізор Розмиття у випадковому порядку Accuracy: %1$s Тип розпізнавання швидко Стандартний Найкращий Немає даних Для належної роботи Tesseract OCR необхідно завантажити додаткові навчальні дані (%1$s) на ваш пристрій. \nВи хочете завантажити дані %2$s? Завантажити Немає з’єднання, перевірте його та повторіть спробу, щоб завантажити моделі поїздів Завантажені мови Доступні мови Режим сегментації Пензель відновить фон замість стирання Горизонтальна сітка Вертикальна сітка Режим стібка Підрахунок рядків Підрахунок стовпців Використовуйте Pixel Switch Використовує перемикач, подібний до Google Pixel Слайд Пліч-о-пліч Перемикач Прозорість Перезаписаний файл із назвою %1$s у початковому місці призначення лупа Вмикає лупу у верхній частині пальця в режимах малювання для кращої доступності Примусове початкове значення Примусово спочатку перевіряє віджет exif Дозволити кілька мов улюблений Вибраних фільтрів ще не додано Б Сплайн Використовує кусково визначені бікубічні поліноміальні функції для плавної інтерполяції та апроксимації кривої або поверхні, гнучкого та безперервного представлення форми Власне розмиття стека Tilt Shift Тип зворотного заповнення Якщо ввімкнено, усі незамасковані області буде відфільтровано замість поведінки за замовчуванням Конфетті Конфетті буде показано під час збереження, обміну та інших основних дій Безпечний режим Приховує вміст нещодавно використаних програм. Його неможливо записати. Колір маски Ви збираєтеся видалити вибрану маску фільтра. Цю операцію не можна скасувати Видалити маску Прості варіанти Хайлайтер Перо За замовчуванням один, найпростіший - тільки колір Розмиває зображення під намальованим контуром, щоб захистити все, що ви хочете приховати Подібно до розмиття конфіденційності, але пікселізує замість розмиття Автоматичний поворот Дозволяє використовувати рамку обмеження для орієнтації зображення Намалюйте напівпрозорі чіткі контури підсвічування Контейнери Намалюйте тінь за контейнерами Повзунки Перемикачі FABs кнопки Намалюйте тінь за повзунками Намалюйте тінь за вимикачами Намалюйте тінь за плаваючими кнопками дій Намалюйте тінь за кнопками Вихід Якщо ви покинете попередній перегляд зараз, вам потрібно буде знову додати зображення Формат зображення Створює палітру \"Material You\" із зображення Темні кольори Використовує колірну схему нічного режиму замість світлого варіанту Скопіюйте як код \"Jetpack Compose\" Розмиття кільця Перехресне розмиття Розмиття кола Розмиття зірками Лінійний нахил-зсув Ярлики для видалення Інструменти APNG Перетворіть файл APNG на пакет зображень Рух Розмитість Зображення в APNG Щоб почати, виберіть зображення APNG APNG до зображень Перетворіть пакет зображень у файл APNG Zip Створіть файл Zip із заданих файлів або зображень Перетворіть зображення на зображення APNG або витягніть кадри з заданого зображення APNG Ширина маркера перетягування Перекодування без втрат з JPEG у JXL Перекодування без втрат з JXL у JPEG Святковий Дата (у зворотному порядку) Перед обробкою зображення буде зменшено до менших розмірів, що допоможе інструменту працювати швидше та безпечніше Зображення у SVG Детально Зменшення зображення Сьогодні Вибух GIF у JXL Без дозволів Запит Спробуйте ще раз Максимум Піксель Купертіно Мінімальне співвідношення кольорів Поріг ліній Квадратичний поріг Скинути властивості Усі властивості будуть встановлені до значень за замовчуванням, зверніть увагу, що цю дію не можна скасувати Режим двигуна Перемикач, заснований на системі проектування \"Купертіно\" Дощ Кути JXL інструменти JXL у JPEG JPEG у JXL APNG у JXL JXL у зображення Зображення у JXL Поведінка Сортування Дата Ім\'я Ім\'я (у зворотному порядку) Конфігурація каналів Вчора Тип конфетті Виконайте перекодування JXL ~ JPEG без втрати якості або конвертуйте анімацію GIF/APNG в JXL Виберіть зображення JXL для початку Швидке гауссове розмиття 2D Швидке гауссове розмиття 3D Швидке гауссове розмиття 4D Великодній автомобіль Дозволяє програмі автоматично вставляти дані з буфера обміну, щоб вони відображалися на головному екрані, і ви могли їх обробити Гармонізація кольору Рівень гармонізації Ланцош Бессель Метод передискретизації, який підтримує високоякісну інтерполяцію шляхом застосування функції Бесселя (JINC) до значень пікселів Конвертувати зображення GIF в анімовані зображення JXL Конвертувати зображення APNG в анімовані зображення JXL Конвертувати JXL-анімацію в пакет зображень Конвертувати пакет зображень в JXL-анімацію Пропустити вибір файлів Вибір файлів буде відображено негайно, якщо це можливо, на вибраному екрані Згенерувати попередній перегляд Увімкнення попереднього перегляду, що може допомогти уникнути збоїв на деяких пристроях, а також вимикання деяких функцій редагування в межах одного параметра редагування Стиснення з втратами Використовує стиснення з втратами для зменшення розміру файлу замість стиснення без втрат Тип стиснення Контролює швидкість декодування результуючого зображення, це має допомогти швидше відкрити результуюче зображення, значення %1$s означає найповільніше декодування, тоді як %2$s – найшвидше, цей параметр може збільшити розмір вихідного зображення Вбудований засіб вибору Вибір зображень у Image Toolbox Виберіть кілька медіа Виберіть один медіафайл Вибрати Показати налаштування в альбомній орієнтації Якщо це вимкнено, то в альбомному режимі налаштування відкриватимуться на кнопці у верхній панелі програми, як завжди, замість постійно видимої опції. Налаштування повноекранного режиму Увімкніть цю функцію, і сторінка налаштувань завжди відкриватиметься на весь екран, а не у висувному списку. Тип перемикача Написати Матеріал для створення Jetpack. Ви перемикаєтесь. Матеріал, який ви перемикаєте Змінити розмір якоря Вільне володіння Перемикач на основі системи проектування \"Fluent\" Трасування заданих зображень до зображень SVG Використовувати вибіркову палітру Палітра квантування буде семплуватися, якщо ця опція увімкнена Пропустити шлях Використання цього інструменту для трасування великих зображень без зменшення масштабу не рекомендується, це може призвести до збою та збільшення часу обробки. Допуск округлення координат Масштаб шляху Ширина лінії за замовчуванням Спадщина Мережа LSTM Застарілі та LSTM Конвертувати Конвертувати пакети зображень у заданий формат Додати нову папку Бітів на зразок Стиснення Фотометрична інтерпретація Зразків на піксель Плоска конфігурація Y Cb Cr Субсемплінг Позиціонування Y Cb Cr Роздільна здатність X Роздільна здатність Y Одиниця роздільної здатності Зміщення смуги Рядків на смузі Кількість байтів смуги Формат обміну JPEG Довжина формату обміну JPEG Передаточна функція Вайт-Пойнт Первинні хроматичні якості Коефіцієнти Y Cb Cr Довідка Чорно-білий Дата Час Опис зображення Зробити Модель Програмне забезпечення Художник Авторське право Версія Exif Версія Flashpix Колірний простір Гамма Розмір пікселя X Розмір пікселя Y Стиснутих бітів на піксель Примітка виробника Коментар користувача Пов\'язаний звуковий файл Дата Час Оригінал Дата та час оцифровані Час зміщення Оригінал часу зсуву Оцифрований час зсуву Час у добі Оригінал субсекундного часу Оцифрований час субсекундного часу Час контакту Діафрагмове число Програма експозиції Спектральна чутливість Фотографічна чутливість OECF Тип чутливості Стандартна вихідна чутливість Рекомендований індекс експозиції Чутливість ISO Чутливість ISO Широта yyy Чутливість ISO Широта zzz Значення витримки Значення діафрагми Значення яскравості Значення зміщення експозиції Максимальне значення діафрагми Відстань до об\'єкта Режим вимірювання Спалах Предметна область Фокусна відстань Енергія спалаху Просторова частотна характеристика Роздільна здатність фокальної площини X Роздільна здатність фокальної площини Y Блок роздільної здатності фокальної площини Розташування суб\'єкта Індекс експозиції Метод зондування Джерело файлу Шаблон CFA Налаштований рендеринг Режим експозиції Баланс Білого Коефіцієнт цифрового масштабування Фокусна відстань на плівці 35 мм Тип захоплення сцени Контроль посилення Контраст Насиченість Різкістю Опис налаштувань пристрою Діапазон відстані до об\'єкта Унікальний ідентифікатор зображення Ім\'я власника камери Серійний номер кузова Специфікація об\'єктива Виробник об\'єктива Модель об\'єктива Серійний номер об\'єктива Ідентифікатор версії GPS Довідка широти GPS GPS-широта Довідка довготи GPS Довгота за GPS Висота за GPS Висота За GPS Мітка часу GPS GPS-супутники Стан GPS Режим вимірювання GPS GPS DOP Довідка швидкості GPS Швидкість GPS Довідка GPS-треку GPS-треки Довідка напрямку зображення GPS Напрямок зображення GPS Дата GPS-карта Довідкова широта пункту призначення GPS Широта пункту призначення GPS Довідкова довгота пункту призначення GPS Довгота пункту призначення GPS GPS-референтний пеленг Пеленг пункту призначення GPS Відстань до пункту призначення GPS Відстань до Пункту призначення GPS Метод обробки GPS Інформація про GPS-локацію Штамп Дата GPS Диференціал GPS Помилка горизонтального позиціонування GPS Індекс сумісності Версія DNG Розмір кадрування за замовчуванням Початок попереднього перегляду зображення Довжина зображення попереднього перегляду Рамка співвідношення сторін Нижня межа датчика Ліва межа датчика Права межа датчика Верхня межа датчика ISO Намалюйте текст на шляху заданим шрифтом і кольором Розмір шрифту Розмір водяного знака Повторити текст Поточний текст буде повторюватися до кінця контуру, замість одноразового малювання Розмір тире Використати вибране зображення, щоб намалювати його вздовж заданого шляху Це зображення буде використано як повторюваний запис намальованого шляху Малює контурний трикутник від початкової точки до кінцевої точки Малює контурний трикутник від початкової точки до кінцевої точки Окреслений трикутник Трикутник Малює багатокутник від початкової точки до кінцевої точки Багатокутник Окреслений багатокутник Малює окреслений полігон від початкової до кінцевої точки Вершини Намалюйте правильний багатокутник Намалюйте багатокутник, який буде правильної, а не вільної форми Малює зірку від початкової точки до кінцевої точки Зірка Зірка з контурами Малює контурну зірку від початкової до кінцевої точки Співвідношення внутрішнього радіуса Намалюйте звичайну зірку Намалюйте зірку правильної, а не вільної форми Згладжування Увімкнення згладжування для запобігання різким краям Відкрити редагування замість попереднього перегляду Коли ви вибираєте зображення для відкриття (попереднього перегляду) в ImageToolbox, замість попереднього перегляду відкриється аркуш редагування вибору. Сканер документів Скануйте документи та створюйте PDF-файли або окремі зображення з них Натисніть, щоб розпочати сканування Розпочати сканування Зберегти як PDF Поділитися як PDF Наведені нижче опції призначені для збереження зображень, а не PDF-файлів. Зрівняти гістограму HSV Вирівняти гістограму Введіть відсоток Дозволити введення за допомогою текстового поля Вмикає текстове поле за вибором пресетів, щоб вводити їх на льоту Шкала колірного простору Лінійний Вирівняти пікселізацію гістограми Розмір сітки X Розмір сітки Y Адаптивне вирівнювання гістограми Вирівнювання гістограми Адаптивна LUV Адаптивний LAB для вирівнювання гістограми CLAHE CLAHE LAB CLAHE LUV Обрізати до вмісту Колір рамки Колір, який потрібно ігнорувати Шаблон Не додано фільтрів шаблонів Створити нове Відсканований QR-код не є дійсним шаблоном фільтра Відскануйте QR-код Вибраний файл не містить даних шаблону фільтра Створити шаблон Назва шаблону Це зображення буде використано для попереднього перегляду цього шаблону фільтра Фільтр шаблонів Як зображення QR-коду Як файл Зберегти як файл Зберегти як зображення з QR-кодом Видалити шаблон Ви збираєтеся видалити вибраний фільтр шаблону. Цю операцію не можна скасувати. Додано шаблон фільтра з назвою \"%1$s\" (%2$s) Попередній перегляд фільтра QR-код та штрих-код Відскануйте QR-код та отримайте його вміст або вставте свій рядок, щоб створити новий Вміст коду Відскануйте будь-який штрих-код, щоб замінити вміст у полі, або введіть щось, щоб створити новий штрих-код із вибраним типом Опис QR-коду Хв Надайте камері дозвіл у налаштуваннях для сканування QR-коду Надайте камері дозвіл у налаштуваннях для сканування Сканера документів Кубічний B-сплайн Хеммінг Ганнінг Блекмен Уелч Квадрик Гаусівський Сфінкс Бартлетт Робіду Робіду Шарп Сплайн 16 Сплайн 36 Сплайн 64 Кайзер Бартлетт-Ганн Коробка Бохман Ланцош 2 Ланцош 3 Ланцош 4 Ланцош 2 Їнц Ланцош 3 Їнц Ланцош 4 Їнц Кубічна інтерполяція забезпечує плавніше масштабування, враховуючи найближчі 16 пікселів, даючи кращі результати, ніж білінійна Використовує кусково-визначені поліноміальні функції для плавної інтерполяції та апроксимації кривої або поверхні, гнучке та безперервне представлення форми Віконна функція, що використовується для зменшення спектрального витоку шляхом звуження країв сигналу, корисна при обробці сигналів. Варіант вікна Ханна, який зазвичай використовується для зменшення спектрального витоку в програмах обробки сигналів. Віконна функція, яка забезпечує хорошу роздільну здатність по частоті, мінімізуючи спектральний витік, часто використовується в обробці сигналів Віконна функція, розроблена для забезпечення гарної роздільної здатності по частоті зі зменшеним спектральним витоком, часто використовується в програмах обробки сигналів. Метод, який використовує квадратичну функцію для інтерполяції, забезпечуючи плавні та безперервні результати Метод інтерполяції, який застосовує функцію Гауса, корисний для згладжування та зменшення шуму на зображеннях Удосконалений метод передискретизації, що забезпечує високоякісну інтерполяцію з мінімальними артефактами Трикутна віконна функція, що використовується в обробці сигналів для зменшення спектрального витоку Високоякісний метод інтерполяції, оптимізований для природного зміни розміру зображення, балансування різкості та плавності Чіткіший варіант методу Робіду, оптимізований для чіткого зміни розміру зображення Метод інтерполяції на основі сплайнів, який забезпечує плавні результати з використанням 16-канального фільтра Метод інтерполяції на основі сплайнів, який забезпечує плавні результати з використанням 36-канального фільтра Метод інтерполяції на основі сплайнів, який забезпечує плавні результати з використанням 64-відбивного фільтра Метод інтерполяції, що використовує вікно Кайзера, що забезпечує хороший контроль над компромісом між шириною головної пелюстки та рівнем бічних пелюсток. Гібридна віконна функція, що поєднує вікна Бартлетта та Ханна, що використовується для зменшення спектрального витоку під час обробки сигналів Простий метод передискретизації, який використовує середнє значення найближчих пікселів, що часто призводить до блокового вигляду Віконна функція, що використовується для зменшення спектрального витоку, що забезпечує хорошу роздільну здатність по частоті в програмах обробки сигналів Метод передискретизації, який використовує 2-пелюстковий фільтр Ланцоша для високоякісної інтерполяції з мінімальними артефактами Метод передискретизації, який використовує 3-пелюстковий фільтр Ланцоша для високоякісної інтерполяції з мінімальними артефактами Метод передискретизації, який використовує 4-пелюстковий фільтр Ланцоша для високоякісної інтерполяції з мінімальними артефактами Варіант фільтра Ланцоша 2, який використовує функцію jinc, забезпечуючи високоякісну інтерполяцію з мінімальними артефактами Варіант фільтра Ланцоша 3, який використовує функцію jinc, забезпечуючи високоякісну інтерполяцію з мінімальними артефактами Варіант фільтра Ланцоша 4, який використовує функцію jinc, забезпечуючи високоякісну інтерполяцію з мінімальними артефактами Ханнінг ЕВА Варіант фільтра Ханнінга з еліптично зваженим середнім (EWA) для плавної інтерполяції та повторної дискретизації Робіду EWA Варіант фільтра Робіду з еліптично зваженим середнім (EWA) для високоякісної передискретизації Блекман, EWA Варіант фільтра Блекмана з еліптично зваженим середнім (EWA) для мінімізації артефактів дзвінка Квадрик EWA Варіант еліптично зваженого середнього (EWA) квадричного фільтра для плавної інтерполяції Робіду Шарп EWA Варіант фільтра Robidoux Sharp з еліптично зваженим середнім (EWA) для чіткіших результатів Ланцош 3 Їнц EWA Варіант фільтра Lanczos 3 Jinc з еліптично зваженим усередненням (EWA) для високоякісної передискретизації зі зменшеним аліасингом Женьшень Фільтр передискретизації, розроблений для високоякісної обробки зображень з хорошим балансом різкості та плавності Женьшень EWA Варіант фільтра женьшеню з еліптично зваженим усередненням (EWA) для покращеної якості зображення Ланцош Шарп EWA Варіант фільтра Ланцоша Шарпа з еліптично зваженим середнім (EWA) для досягнення чітких результатів з мінімальними артефактами Ланцош 4 Sharpest EWA Варіант еліптично зваженого середнього (EWA) фільтра Lanczos 4 Sharpest для надзвичайно чіткої передискретизації зображення Ланцош М\'який EWA Варіант еліптично зваженого середнього (EWA) фільтра Lanczos Soft для плавнішої передискретизації зображення Haasn Soft Фільтр передискретизації, розроблений Haasn, для плавного масштабування зображення без артефактів Конвертація формату Конвертувати пакет зображень з одного формату в інший Закрити назавжди Стекування зображень Накладайте зображення одне на одне за допомогою вибраних режимів накладання Додати зображення Кількість контейнерів Клахе HSL Клахе HSV Вирівнювання гістограми Адаптивний HSL Вирівнювання гістограми Адаптивний HSV Режим краю Кліп Обгортка Колір Дальтонізм Виберіть режим для адаптації кольорів теми до вибраного варіанту дальтонізму Складність розрізнення червоних і зелених відтінків Складність розрізнення зелених і червоних відтінків Складність розрізнення синього та жовтого відтінків Нездатність сприймати червоні відтінки Нездатність сприймати зелені відтінки Нездатність сприймати відтінки синього Знижена чутливість до всіх кольорів Повна дальтонізм, бачення лише відтінків сірого Не використовуйте схему для дальтоніків Кольори будуть точно такими, як встановлено в темі Сигмоїдальний Лагранж 2 Інтерполяційний фільтр Лагранжа 2-го порядку, придатний для високоякісного масштабування зображень з плавними переходами Лагранж 3 Інтерполяційний фільтр Лагранжа третього порядку, що забезпечує кращу точність та плавніші результати масштабування зображення Ланцош 6 Фільтр передискретизації Ланцоша вищого порядку 6, що забезпечує чіткіше та точніше масштабування зображення Ланцош 6 Їнц Варіант фільтра Ланцоша 6, що використовує функцію Jinc для покращення якості передискретизації зображення Лінійне розмиття рамки Лінійне розмиття намету Лінійне гауссове розмиття рамки Лінійне розмиття стеку Гаусове розмиття рамки Лінійне швидке гауссове розмиття Далі Лінійне швидке гаусове розмиття Лінійне гаусове розмиття Виберіть один фільтр, щоб використовувати його як фарбу Замінити фільтр Виберіть фільтр нижче, щоб використовувати його як пензель у своєму малюнку Схема стиснення TIFF Низький полігональний Малювання на піску Розділення зображення Розділити одне зображення за рядками або стовпцями Пристосувати до меж Поєднайте режим зміни розміру кадрування з цим параметром, щоб досягти бажаної поведінки (Кадрування/Припасування до співвідношення сторін) Мови успішно імпортовано Резервні копії моделей OCR Імпорт Експорт Позиція Центр Зверху ліворуч Угорі праворуч Внизу ліворуч Внизу праворуч Верхній центр Центр праворуч Нижній центр Центр лівий Цільове зображення Перенесення палітри Покращена олія Простий старий телевізор HDR Готем Простий ескіз М\'яке сяйво Кольоровий плакат Тритон Третій колір Клей Оклаб Клахе Оклч Клахе Джзазбз Горошок Кластерне 2x2 згладжування Кластерне 4x4 згладжування Кластерне згладжування 8x8 Згладжування Їліломи Не вибрано жодних улюблених опцій, додайте їх на сторінці інструментів Додати до обраного Доповнюючий Аналогічний Тріадний Розділений додатковий Тетрадичний Квадрат Аналогічний + Доповнювальний Інструменти для роботи з кольором Змішуйте, створюйте тони, генеруйте відтінки та багато іншого Колірні гармонії Затінення кольорів Варіація Відтінки Тони Відтінки\' Змішування кольорів Інформація про колір Вибраний колір Колір для змішування Неможливо використовувати Monet, коли ввімкнено динамічні кольори 512x512 2D LUT Цільове зображення LUT Аматорка Міс Етікейт М\'яка елегантність Варіант м’якої елегантності Варіант перенесення палітри 3D LUT Цільовий 3D LUT-файл (.cube / .CUBE) LUT Обхід відбілювача Світло свічок Падіння блюзу Зухвалий бурштин Осінні кольори Плівка 50 Туманна ніч Кодак Отримати нейтральне зображення LUT Спочатку скористайтеся своїм улюбленим застосунком для редагування фотографій, щоб застосувати фільтр до нейтральної LUT-таблиці, який можна завантажити тут. Щоб це працювало належним чином, колір кожного пікселя не повинен залежати від інших пікселів (наприклад, розмиття не працюватиме). Після того, як ви будете готові, використовуйте нове зображення LUT як вхідні дані для фільтра LUT 512*512. Поп-арт Целулоїд Кава Золотий ліс Зеленуватий Ретро жовтий Попередній перегляд посилань Дозволяє переглядати посилання в місцях, де можна отримати текст (QR-код, OCR тощо) Посилання Файли ICO можна зберігати лише з максимальним розміром 256 x 256 GIF у WEBP Конвертувати зображення GIF в анімовані зображення WEBP Інструменти WEBP Конвертувати зображення в анімовану картинку WEBP або витягувати кадри з заданої анімації WEBP WEBP до зображень Конвертувати файл WEBP у пакет зображень Конвертувати пакет зображень у файл WEBP Зображення до WEBP Виберіть зображення WEBP для початку Немає повного доступу до файлів Надайте доступ до всіх файлів, щоб переглядати JXL, QOI та інші зображення, які не розпізнаються як зображення на Android. Без дозволу Image Toolbox не зможе показати ці зображення. Колір малювання за замовчуванням Режим малювання контуру за замовчуванням Додати позначку часу Дозволяє додавати позначку часу до імені вихідного файлу Відформатована позначка часу Увімкнути форматування позначки часу в назві вихідного файлу замість базового мілісику Увімкнути формат позначок часу Одноразове збереження місця розташування Переглядайте та редагуйте місця збереження для одного разу, які можна використовувати, довго натискаючи кнопку збереження майже у всіх опціях Нещодавно використані Канал CI Група Інструменти для роботи з зображеннями в Telegram 🎉 Приєднуйтесь до нашого чату, де ви можете обговорити все, що забажаєте, а також загляньте в канал CI, де я публікую бета-версії та анонси. Отримуйте сповіщення про нові версії програми та читайте оголошення Підігнати зображення до заданих розмірів та застосувати розмиття або колір до фону Розташування інструментів Групувати інструменти за типом Групує інструменти на головному екрані за типом, а не за власним списком Значення За замовчуванням Видимість системних панелей Відображати системні панелі за допомогою свайпу Дозволяє проводити пальцем, щоб показати системні панелі, якщо вони приховані Авто Приховати все Показати все Приховати панель навігації Приховати рядок стану Генерація шуму Генеруйте різні шуми, такі як Перлін або інші типи Частота Тип шуму Тип обертання Фрактальний тип Октав Лакунарність Посилення Зважена сила Сила в пінг-понгу Функція відстані Тип повернення Тремтіння Деформація домену Вирівнювання Користувацьке ім\'я файлу Виберіть місцезнаходження та ім\'я файлу, які будуть використані для збереження поточного зображення Збережено в папку з власною назвою Конструктор колажів Створюйте колажі з максимум 20 зображень Тип колажу Утримуйте зображення для переміщення, переміщення та масштабування для налаштування положення Гістограма Гістограма зображення RGB або яскравості, яка допоможе вам внести корективи Це зображення буде використано для створення гістограм RGB та яскравості. Параметри Тессеракту Застосуйте деякі вхідні змінні для двигуна Tesseract Налаштовані параметри Параметри слід вводити за такою схемою: \"--{option_name} {value}\" Автоматичне обрізання Безкоштовні кути Обрізати зображення за полігоном, це також виправляє перспективу Приведення точок до меж зображення Точки не будуть обмежені межами зображення, що корисно для точнішої корекції перспективи. Маска Заповнення під намальованим контуром з урахуванням вмісту Місце зцілення Використовуйте ядро Circle Відкриття Закриття Морфологічний градієнт Циліндр Чорний капелюх Тонові криві Скинути криві Криві будуть повернуті до значень за замовчуванням Стиль лінії Розмір зазору Пунктирна Крапка-штрих Штампований Зигзаг Малює пунктирну лінію вздовж намальованого шляху із заданим розміром проміжку Малює крапку та пунктирну лінію вздовж заданого шляху Просто прямі лінії за замовчуванням Малює вибрані фігури вздовж контуру із заданим інтервалом Малює хвилястий зигзаг вздовж шляху Зигзагоподібне співвідношення Створити ярлик Виберіть інструмент для закріплення Інструмент буде додано на головний екран вашої панелі запуску як ярлик, використовуйте його в поєднанні з налаштуванням \"Пропустити вибір файлів\", щоб досягти потрібної поведінки. Не складайте рамки один на одного Дозволяє позбутися попередніх кадрів, щоб вони не складалися один на одного Кросфейд Кадри будуть перетікатися один в одного Кількість кадрів кросфейду Поріг один Поріг другий Канні Дзеркало 101 Покращене розмиття при збільшенні Лапласова проста Собел Симпл Допоміжна сітка Показує допоміжну сітку над областю малювання для полегшення точного виконання маніпуляцій Колір сітки Ширина комірки Висота комірки Компактні селектори Деякі елементи керування виділенням використовуватимуть компактне розташування, щоб займати менше місця Надайте камері дозвіл на зйомку зображення в налаштуваннях Макет Заголовок головного екрана Коефіцієнт постійної ставки (CRF) Значення %1$s означає повільне стиснення, що призводить до відносно невеликого розміру файлу. %2$s означає швидше стиснення, що призводить до великого файлу. Бібліотека Лут Завантажте колекцію LUT, яку можна буде застосувати після завантаження Оновлення колекції LUT (до черги будуть поставлені лише нові), яку можна застосувати після завантаження Змінити попередній перегляд зображення за замовчуванням для фільтрів Попередній Перегляд зображення Приховати Показати Тип слайдера Фенсі Матеріал 2 Вишуканий слайдер. Це опція за замовчуванням. Слайдер «Матеріал 2» Слайдер Material You Застосувати Центральні кнопки діалогового вікна Кнопки діалогових вікон будуть розташовані по центру, а не ліворуч, якщо це можливо. Ліцензії з відкритим кодом Переглянути ліцензії бібліотек з відкритим кодом, що використовуються в цьому додатку Площа Передискретизація з використанням відношення площі пікселя. Це може бути кращим методом для проріджування зображення, оскільки він дає результати без муару. Але коли зображення масштабується, він схожий на метод \"Найближчий\". Увімкнути тонову схему Введіть % Не вдається отримати доступ до сайту, спробуйте скористатися VPN або перевірте правильність URL-адреси Шари розмітки Режим шарів з можливістю вільного розміщення зображень, тексту тощо Редагувати шар Шари на зображенні Використовуйте зображення як фон і додайте поверх нього різні шари Шари на фоні Те саме, що й перший варіант, але з кольором замість зображення Бета-версія Швидкі налаштування збоку Додати плаваючу смугу з вибраного боку під час редагування зображень, яка відкриватиме швидкі налаштування після натискання Очистити вибір Групу налаштувань \"%1$s\" буде згорнуто за замовчуванням Групу налаштувань \"%1$s\" буде розгорнуто за замовчуванням Інструменти Base64 Декодувати рядок Base64 у зображення або кодувати зображення у формат Base64 Base64 Надане значення не є дійсним рядком Base64 Неможливо скопіювати порожній або недійсний рядок Base64 Вставити Base64 Копіювати Base64 Завантажте зображення, щоб скопіювати або зберегти рядок Base64. Якщо у вас є сам рядок, ви можете вставити його вище, щоб отримати зображення. Зберегти Base64 Поділитися Base64 Опції Дії Імпорт Base64 Дії Base64 Додати контур Додати контур навколо тексту заданим кольором і шириною Колір контуру Розмір контуру Обертання Контрольна сума як ім\'я файлу Вихідні зображення матимуть назву, що відповідає їхній контрольній сумі даних. Безкоштовне програмне забезпечення (партнер) Більше корисного програмного забезпечення в партнерському каналі Android-додатків Алгоритм Інструменти для перевірки контрольних сум Порівнюйте контрольні суми, обчислюйте хеші або створюйте шістнадцяткові рядки з файлів, використовуючи різні алгоритми хешування Розрахувати Хеш тексту Контрольна сума Виберіть файл для обчислення його контрольної суми на основі вибраного алгоритму Введіть текст для обчислення його контрольної суми на основі вибраного алгоритму Контрольна сума джерела Контрольна сума для порівняння Матч! Різниця Контрольні суми рівні, це може бути безпечно Контрольні суми не рівні, файл може бути небезпечним! Сітчасті градієнти Перегляньте онлайн-колекцію сітчастих градієнтів Можна імпортувати лише шрифти TTF та OTF Імпорт шрифту (TTF/OTF) Експорт шрифтів Імпортовані шрифти Помилка під час спроби збереження, спробуйте змінити папку виводу Ім\'я файлу не встановлено Жоден Користувацькі сторінки Вибір сторінок Підтвердження виходу інструменту Якщо під час використання певних інструментів у вас є незбережені зміни, і ви намагаєтеся закрити їх, то з\'явиться діалогове вікно підтвердження. Редагувати EXIF Зміна метаданих одного зображення без повторного стиснення Натисніть, щоб редагувати доступні теги Змінити наклейку Ширина по висоті Висота, що підходить Порівняння пакетів Виберіть файл/файли для обчислення контрольної суми на основі вибраного алгоритму Вибрати файли Вибрати каталог Шкала довжини голови Штамп Позначка часу Шаблон форматування Заповнення Обрізання зображення Вирізати частину зображення та об\'єднати ліві частини (можна інвертувати) вертикальними або горизонтальними лініями Вертикальна лінія повороту Горизонтальна лінія повороту Зворотний вибір Вертикальна розрізана частина буде залишена, замість об\'єднання частин навколо області розрізу. Горизонтально вирізана частина буде залишена, замість об\'єднання частин навколо області вирізу. Колекція сітчастих градієнтів Створення сітчастого градієнта з власною кількістю вузлів та роздільною здатністю Сітчасте градієнтне накладання Створити сітчастий градієнт верхньої частини заданих зображень Налаштування балів Розмір сітки Роздільна Здатність Х Роздільна Здатність Y Роздільна здатність Піксель за пікселем Колір виділення Тип порівняння пікселів Скануйте штрих-код Співвідношення висоти Тип штрих-коду Забезпечити чорно-біле виконання Зображення штрих-коду буде повністю чорно-білим, а не кольоровим за темою програми Відскануйте будь-який штрих-код (QR, EAN, AZTEC тощо) та отримайте його вміст або вставте свій текст, щоб створити новий Штрих-код не знайдено Згенерований штрих-код буде тут Аудіообкладинки Вилучення зображень обкладинок альбомів з аудіофайлів, підтримуються найпоширеніші формати Виберіть аудіо для початку Вибрати аудіо Обкладинок не знайдено Надіслати журнали Натисніть, щоб поділитися файлом журналів програми. Це може допомогти мені виявити проблему та виправити її. Ой… Щось пішло не так Ви можете зв\'язатися зі мною, використовуючи наведені нижче опції, і я спробую знайти рішення.\n(Не забудьте додати логи) Записати у файл Витяг тексту з пакету зображень та збереження його в одному текстовому файлі Запис у метадані Витягніть текст з кожного зображення та помістіть його в EXIF-інформацію відповідних фотографій Невидимий режим Використовуйте стеганографію для створення невидимих оком водяних знаків всередині байтів ваших зображень Використовувати молодший біт (MLB) Буде використано метод стеганографії LSB (менш значущий біт), в іншому випадку - FD (частотна область). Автоматичне вилучення ефекту червоних очей Пароль Розблокувати PDF-файл захищено Операцію майже завершено. Щоб скасувати зараз, потрібно буде перезапустити її. Дата зміни Дата зміни (скасування) Розмір Розмір (у зворотному напрямку) Тип MIME Тип MIME (зворотний) Розширення\' Розширення (зворотне) Дата додавання Дата додавання (у зворотному порядку) Зліва направо Справа наліво Зверху вниз Знизу вгору Liquid Glass Перемикач на базі нещодавно анонсованої iOS 26 та її системи дизайну з рідким склом Виберіть зображення або вставте/імпортуйте дані Base64 нижче Введіть посилання на зображення, щоб розпочати Вставити посилання Калейдоскоп Вторинний кут Сторони Мікс каналів Синьо-зелений Червоний синій Зелений червоний У червоний колір У зелений У синій колір Блакитний Магента Жовтий Кольоровий півтон Контур Рівні Зсув Кристалізація Вороного Форма Розтягнути Випадковість Видалити плями Дифузний DoG Другий радіус Зрівняти Сяйво Кружляти та щипати Пуантилізувати Колір межі Полярні координати Від прямокутника до полярного Полярний до прямокутного Інвертувати по колу Зменшення шуму Проста соляризація Плетіння X-проміжок Y-проміжок X Ширина Y Ширина Крутити Гумовий штамп Мазок Щільність Змішати Спотворення сферичної лінзи Показник Заломлення Дуга Кут розкиду Блиск Промені ASCII Градієнт Мері Осінь Кістка Джет Зима Океан Літо Весна Класний варіант HSV Рожевий Гарячий Слово Магма Пекло Плазма Віридіс Чівідіс Сутінки Сутінковий зсув Перспективний авто Вирівнювання\' Дозволити обрізання Обрізання або перспектива Абсолютний Турбо Глибоко зелений Корекція об\'єктива Файл профілю цільової лінзи у форматі JSON Завантажте готові профілі об\'єктивів Частина відсотків Вимкнути обертання Запобігає обертанню зображень жестами двома пальцями Увімкнути прив\'язку до меж Після переміщення або масштабування зображення будуть прив\'язані до країв кадру Експортувати як JSON Копіювати рядок з даними палітри у вигляді JSON-представлення Різьблення швів Головний екран Екран блокування Вбудований Експорт шпалер Оновити Отримайте актуальні шпалери для головної сторінки, блокування та вбудованих екранів Дозволити доступ до всіх файлів, це потрібно для отримання шпалер Дозволів на керування зовнішнім сховищем недостатньо, потрібно дозволити доступ до зображень, переконайтеся, що вибрано \"Дозволити всім\" Додати пресет до назви файлу Додає суфікс із вибраним пресетом до назви файлу зображення Додати режим масштабування зображення до назви файлу Додає суфікс із вибраним режимом масштабування зображення до назви файлу зображення ASCII Art Перетворити зображення на текст ASCII, який виглядатиме як зображення Параметри Застосовує негативний фільтр до зображення для кращого результату в деяких випадках Обробка знімка екрана Знімок екрана не зроблено, спробуйте ще раз Збереження пропущено Пропущено %1$s файлів Дозволити пропуск, якщо більший Деяким інструментам буде дозволено пропускати збереження зображень, якщо розмір отриманого файлу буде більшим за оригінал. Подія календаря Контакти Електронна пошта Розташування Телефон Текст SMS URL Wi-Fi Відкрита мережа N/A SSID Телефон Повідомлення Адреса Тема Тіло Ім\'я Організація Назва Телефони Електронні листи URLs Адреси Короткий зміст Опис Розташування Організатор Дата початку Дата завершення Статус Широта Довгота Створити штрих-код Редагувати штрих-код Конфігурація Wi-Fi Безпека Вибрати контакт Надайте контактам у налаштуваннях дозвіл на автозаповнення за допомогою вибраного контакту Контактна інформація Особисті Ім\'я По батькові Прізвище Вимова Додати телефон Додати електронну пошту Додати адресу Вебсайт Додати веб-сайт Форматоване ім\'я Це зображення буде розміщено над штрих-кодом Налаштування коду Це зображення буде використано як логотип у центрі QR-коду Логотип Відступи логотипу Розмір логотипу Куточки логотипу Четверте око Додає симетрію очей до QR-коду, додаючи четверте око в нижньому кутку Форма пікселя Форма рами Форма кулі Рівень корекції помилок Темний колір Світлий колір Hyper OS Стиль, схожий на Xiaomi HyperOS Візерунок маски Цей код може бути нечитабельним, змініть параметри зовнішнього вигляду, щоб зробити його читабельним на всіх пристроях. Недоступно для сканування Інструменти виглядатимуть як панель запуску програм на головному екрані, щоб бути компактнішими. Режим запуску Заповнює область вибраним пензлем та стилем Заливка Спрей Малює шлях у стилі графіті Квадратні частинки Частинки розпилення будуть квадратними, а не круглими Інструменти палітри Генеруйте основну/матеріальну палітру з зображення або імпортуйте/експортуйте її в різні формати палітр Редагувати палітру Експорт/імпорт палітри в різних форматах Назва кольору Назва палітри Формат палітри Експорт згенерованої палітри в різні формати Додає новий колір до поточної палітри Формат %1$s не підтримує надання назви палітри Через політику Play Store цю функцію неможливо включити до поточної збірки. Щоб отримати доступ до цієї функції, завантажте ImageToolbox з альтернативного джерела. Ви можете знайти доступні збірки на GitHub нижче. Відкрити сторінку Github %1$s колір %1$s кольори %1$s кольорів %1$s кольорів Оригінальний файл буде замінено новим, а не збережено у вибраній папці Виявлено прихований текст водяного знака Виявлено приховане зображення водяного знака Це зображення було приховано Генеративне інпайнінг Дозволяє видаляти об\'єкти на зображенні за допомогою моделі штучного інтелекту, не покладаючись на OpenCV. Щоб скористатися цією функцією, програма завантажить необхідну модель (~200 МБ) з GitHub. Дозволяє видаляти об\'єкти на зображенні за допомогою моделі штучного інтелекту, не покладаючись на OpenCV. Це може бути тривалою операцією. Аналіз рівня помилок Градієнт яскравості Середня відстань Виявлення переміщення копіювання Зберігати Коефіцієнт Дані буфера обміну занадто великі Дані занадто великі для копіювання Проста пікселізація переплетення Ступінчаста пікселізація Крос-пікселізація Мікро-макро-пікселізація Орбітальна пікселізація Пікселізація вихрів Пікселізація імпульсної сітки Пікселізація ядра Пікселізація радіального переплетення Не вдається відкрити uri \"%1$s\" Режим снігопаду Увімкнено Рамка кордону Варіант глюка Зміна каналу Максимальне зміщення VHS Збій блоку Розмір блоку Кривизна ЕПТ Кривизна Хрома Розплавлення пікселів Макс. падіння Інструменти штучного інтелекту Різні інструменти для обробки зображень за допомогою моделей штучного інтелекту, такі як видалення артефактів або шумозаглушення Стиснення, нерівні лінії Мультфільми, стиснення трансляцій Загальне стиснення, загальний шум Безбарвний мультяшний шум Швидке, загальне стиснення, загальний шум, анімація/комікси/аніме Сканування книг Корекція експозиції Найкраще підходить для загального стиснення, кольорові зображення Найкраще підходить для загального стиснення, зображення у градаціях сірого Загальне стиснення, зображення у градаціях сірого, сильніше Загальний шум, кольорові зображення Загальний шум, кольорові зображення, краща деталізація Загальний шум, зображення у градаціях сірого Загальний шум, зображення у відтінках сірого, сильніший Загальний шум, зображення у градаціях сірого, найсильніший Загальне стиснення Загальне стиснення Текстурування, стиснення h264 стиснення VHS Нестандартне стиснення (cinepak, msvideo1, roq) Стиснення моргання, краще для геометрії Стиснення моргання, сильніше Стиснення моргання, м\'яке, зберігає деталі Усунення ефекту сходів, згладжування Відскановані ілюстрації/малюнки, легке стиснення, муар Кольорове смугастість Повільно, вилучення півтонів Загальний засіб для забарвлення зображень у градаціях сірого/чорно-білих, для кращих результатів використовуйте DDColor Вилучення країв Вилучає надмірну різкість Повільно, тремтіння Згладжування, загальні артефакти, CGI Злиття Обробка сканів KDM003 Легка модель покращення зображення Проста та швидка розфарбовка, мультфільми, не ідеально Вилучення артефактів стиснення Вилучення артефактів стиснення Зняття пов\'язки з гладкими результатами Обробка напівтонових візерунків Вилучення шаблону тремтіння V3 Вилучення артефактів JPEG V2 Покращення текстур H.264 Підвищення різкості та покращення якості запису VHS Розмір шматка Розмір перекриття Зображення розміром понад %1$s пікселів будуть нарізані та оброблені фрагментами, а перекриття об\'єднує їх, щоб уникнути видимих швів. Великі розміри можуть спричинити нестабільність у роботі з низькоякісними пристроями Виберіть один, щоб почати Ви хочете видалити модель %1$s? Вам потрібно буде завантажити її знову. Підтвердити Моделі Завантажені моделі Доступні моделі Підготовка Активна модель Не вдалося відкрити сеанс Імпортувати можна лише моделі .onnx/.ort Імпорт моделі Імпортуйте власну модель onnx для подальшого використання, приймаються лише моделі onnx/ort, підтримується майже всі варіанти, подібні до esrgan Імпортовані моделі Загальний шум, кольорові зображення Загальний шум, кольорові зображення, сильніший Загальний шум, кольорові зображення, найсильніший Зменшує артефакти розмивання кольорів та кольорові смуги, покращуючи плавність градієнтів та рівні кольорові області. Покращує яскравість і контрастність зображення зі збалансованими світлими ділянками, зберігаючи при цьому природні кольори. Освітлює темні зображення, зберігаючи деталі та уникаючи переекспонування. Видаляє надмірне тонування кольору та відновлює більш нейтральний і природний колірний баланс. Застосовує пуассонівське тонування шуму з акцентом на збереженні дрібних деталей і текстур. Застосовує м’яке тонування пуассонівським шумом для плавніших та менш агресивних візуальних результатів. Рівномірне тонування шуму, зосереджене на збереженні деталей та чіткості зображення. Ніжне рівномірне тонування шуму для витонченої текстури та гладкого вигляду. Відновлює пошкоджені або нерівні ділянки, перефарбовуючи артефакти та покращуючи узгодженість зображення. Легка модель для видалення кольорових смуг з мінімальними витратами на продуктивність. Оптимізує зображення з дуже високими артефактами стиснення (якість 0-20%) для покращення чіткості. Покращує зображення з артефактами високого стиснення (якість 20-40%), відновлюючи деталі та зменшуючи шум. Покращує зображення з помірним стисненням (якість 40-60%), балансуючи різкість та плавність. Удосконалює зображення за допомогою легкого стиснення (якість 60-80%) для покращення тонких деталей та текстур. Трохи покращує зображення майже без втрат якості (якість 80-100%), зберігаючи при цьому природний вигляд і деталі. Трохи зменшує розмиття зображення, покращуючи різкість без появи артефактів. Тривалі операції Обробка зображення Обробка Видаляє значні артефакти стиснення JPEG у зображеннях дуже низької якості (0-20%). Зменшує сильні артефакти JPEG у сильно стиснутих зображеннях (на 20-40%). Видаляє помірні артефакти JPEG, зберігаючи деталі зображення (40-60%). Удосконалює світлі артефакти JPEG у зображеннях досить високої якості (60-80%). Незначно зменшує незначні артефакти JPEG у зображеннях майже без втрат якості (80-100%). Покращує дрібні деталі та текстури, покращуючи сприйняття різкості без значних артефактів. Обробку завершено Обробка не вдалася Покращує текстуру та деталі шкіри, зберігаючи природний вигляд, оптимізовано для швидкості. Видаляє артефакти стиснення JPEG та відновлює якість зображення для стиснутих фотографій. Зменшує шум ISO на фотографіях, зроблених в умовах слабкого освітлення, зберігаючи деталі. Виправляє переекспоновані або «надмірні» світлі ділянки та відновлює кращий тональний баланс. Легка та швидка модель розфарбовування, яка додає природні кольори до зображень у градаціях сірого. DEJPEG Знищення шуму Розфарбувати Артефакти Покращити Аніме Сканування Висококласний X4 масштабування для загальних зображень; крихітна модель, яка використовує менше графічного процесора та часу, з помірним усуненням розмиття та шуму. X2-кратне масштабування для загальних зображень, зберігаючи текстури та природні деталі. Покращення масштабування X4 для загальних зображень з покращеними текстурами та реалістичними результатами. X4-модуль масштабування, оптимізований для аніме-зображень; 6 блоків RRDB для чіткіших ліній та деталей. Масштабування X4 з втратою MSE забезпечує плавніші результати та зменшує артефакти для загальних зображень. X4 Upscaler, оптимізований для зображень аніме; Варіант 4B32F із більш чіткими деталями та плавними лініями. Модель X4 UltraSharp V2 для загальних зображень; підкреслює різкість і чіткість. X4 UltraSharp V2 Lite; швидший і менший, зберігає деталі, використовуючи менше пам’яті GPU. Легка модель для швидкого видалення фону. Збалансована продуктивність і точність. Працює з портретами, об’єктами та сценами. Рекомендовано для більшості випадків використання. Видаліть BG Товщина горизонтальної межі Товщина вертикальної межі Поточна модель не підтримує фрагментування, зображення буде оброблено в оригінальних розмірах, це може спричинити велике споживання пам’яті та проблеми з пристроями низького класу Розбиття на фрагменти вимкнено, зображення буде оброблено в оригінальних розмірах, це може спричинити велике споживання пам’яті та проблеми з пристроями низького класу, але може дати кращі результати під час висновку Чанкінг Високоточна модель сегментації зображення для видалення фону Полегшена версія U2Net для швидшого видалення фону з меншим використанням пам’яті. Модель Full DDColor забезпечує високоякісне розфарбовування загальних зображень із мінімальними артефактами. Найкращий вибір серед усіх моделей забарвлення. DDColor Навчені та приватні художні набори даних; дає різноманітні та художні результати забарвлення з меншою кількістю нереалістичних кольорових артефактів. Легка модель BiRefNet на основі Swin Transformer для точного видалення фону. Високоякісне видалення фону з чіткими краями та чудовим збереженням деталей, особливо на складних об’єктах і складному фоні. Модель видалення фону, яка створює точні маски з гладкими краями, придатні для звичайних об’єктів і помірне збереження деталей. Модель вже завантажено Модель успішно імпортовано Тип Ключове слово Дуже швидко нормальний Повільно Дуже повільно Обчисліть відсотки Мінімальне значення – %1$s Спотворюйте зображення, малюючи пальцями Деформація Твердість Режим деформації Перемістити Рости Зменшити Вир CW Вир CCW Сила згасання Top Drop Нижня падіння Почати Drop End Drop Завантажується Плавні фігури Використовуйте супереліпси замість стандартних заокруглених прямокутників для більш гладких і природних форм Тип форми Вирізати Округлі Гладкий Гострі краї без округлення Класичні закруглені кути Тип форми Розмір кутів Squircle Елегантні округлі елементи інтерфейсу користувача Формат імені файлу Спеціальний текст, розміщений на самому початку назви файлу, ідеально підходить для назв проектів, брендів або особистих тегів. Використовує оригінальну назву файлу без розширення, що допомагає зберегти ідентифікацію джерела. Ширина зображення в пікселях, корисна для відстеження змін роздільної здатності або масштабування результатів. Висота зображення в пікселях, корисна під час роботи зі співвідношенням сторін або експорту. Генерує випадкові цифри, щоб гарантувати унікальні імена файлів; додайте більше цифр для додаткового захисту від дублікатів. Лічильник із автоматичним збільшенням для пакетного експорту, ідеальний під час збереження кількох зображень за один сеанс. Вставляє застосовану назву попереднього налаштування в назву файлу, щоб ви могли легко запам’ятати, як було оброблено зображення. Відображає режим масштабування зображення, що використовується під час обробки, допомагаючи розрізняти зображення зі зміненим розміром, обрізані чи підібрані. Спеціальний текст, розміщений у кінці назви файлу, корисний для керування версіями, наприклад _v2, _edited або _final. Розширення файлу (png, jpg, webp тощо), що автоматично відповідає дійсному збереженому формату. Настроювана часова позначка, яка дозволяє визначити власний формат за специфікацією Java для ідеального сортування. Тип кидка Android Native Стиль iOS Плавна крива Швидка зупинка Стрибкий Плаваючий Стрімкий Ультра гладкий Адаптивний Спеціальні можливості Зменшений рух Вбудована фізика прокручування Android для базового порівняння Збалансована плавна прокрутка для загального використання Поведінка прокручування, подібна до iOS, з підвищеним тертям Унікальна сплайн-крива для чіткого відчуття прокручування Точне прокручування зі швидкою зупинкою Грайливий, чуйний пружний сувій Довгі ковзаючі прокручування для перегляду вмісту Швидке, чутливе прокручування для інтерактивних інтерфейсів користувача Преміум плавне прокручування з розширеним імпульсом Коригує фізику на основі швидкості метання Поважає налаштування доступності системи Мінімальний рух для потреб у доступності Первинні лінії Кожний п’ятий рядок додає товстішу лінію Колір заливки Приховані інструменти Інструменти, приховані для спільного використання Бібліотека кольорів Перегляньте величезну колекцію кольорів Збільшує різкість і усуває розмитість зображень, зберігаючи природні деталі, що ідеально підходить для виправлення розфокусованих фотографій. Інтелектуально відновлює зображення, розмір яких раніше було змінено, відновлюючи втрачені деталі та текстури. Оптимізовано для живого контенту, зменшує артефакти стиснення та покращує дрібні деталі в кадрах фільмів/телешоу. Перетворює відзнятий матеріал із якістю VHS у HD, усуваючи шум стрічки та підвищуючи роздільну здатність, зберігаючи вінтажне відчуття. Спеціалізується на зображеннях із великою кількістю тексту та скріншотах, підвищує різкість символів і покращує читабельність. Розширене масштабування, навчене на різноманітних наборах даних, чудово підходить для покращення фотографій загального призначення. Оптимізовано для фотографій, стиснених у Інтернеті, видаляє артефакти JPEG і відновлює природний вигляд. Покращена версія для веб-фотографій із кращим збереженням текстури та зменшенням артефактів. 2-кратне збільшення за допомогою технології Dual Aggregation Transformer, зберігає різкість і природні деталі. 3-кратне збільшення за допомогою вдосконаленої трансформаторної архітектури, що ідеально підходить для потреб помірного розширення. 4-кратне високоякісне збільшення за допомогою найсучаснішої трансформаторної мережі, зберігає дрібні деталі у великих масштабах. Усуває розмиття/шуми та тремтіння фотографій. Загального призначення, але найкраще на фото. Відновлює зображення низької якості за допомогою трансформатора Swin2SR, оптимізованого для деградації BSRGAN. Чудово підходить для виправлення важких артефактів стиснення та покращення деталей у 4-кратному масштабі. 4-кратне збільшення за допомогою трансформатора SwinIR, навченого на деградацію BSRGAN. Використовує GAN для більш чітких текстур і більш природних деталей на фотографіях і складних сценах. шлях Об’єднати PDF Об’єднайте кілька файлів PDF в один документ Порядок файлів пп. Розділити PDF Витягніть певні сторінки з документа PDF Повернути PDF Назавжди виправити орієнтацію сторінки сторінки Перевпорядкувати PDF Перетягніть сторінки, щоб змінити їх порядок Утримуйте та перетягуйте сторінки Номери сторінок Автоматично додавайте нумерацію до документів Формат етикетки PDF в текст (OCR) Витягніть звичайний текст із PDF-документів Накладення спеціального тексту для брендингу або безпеки Підпис Додайте свій електронний підпис до будь-якого документа Це буде використано як підпис Розблокувати PDF Видаліть паролі з захищених файлів Захист PDF Захистіть свої документи надійним шифруванням Успіх PDF розблоковано, ви можете зберегти або поділитися ним Відремонтувати PDF Спробуйте виправити пошкоджені або нечитабельні документи Відтінки сірого Перетворення всіх зображень, вбудованих у документ, у відтінки сірого Стиснути PDF Оптимізуйте розмір файлу документа, щоб простіше ділитися ним ImageToolbox перебудовує внутрішню таблицю перехресних посилань і генерує файлову структуру з нуля. Це може відновити доступ до багатьох файлів, які \\"неможливо відкрити\\" Цей інструмент перетворює всі зображення документів на градації сірого. Найкраще підходить для друку та зменшення розміру файлу Метадані Відредагуйте властивості документа для кращої конфіденційності Теги Продюсер Автор Ключові слова Творець Глибоке очищення конфіденційності Очистити всі доступні метадані для цього документа Сторінка Глибоке OCR Витягніть текст із документа та збережіть його в одному текстовому файлі за допомогою механізму Tesseract Неможливо видалити всі сторінки Видаліть сторінки PDF Видалити певні сторінки з документа PDF Натисніть, щоб видалити Вручну Обрізати PDF Обрізайте сторінки документа до будь-яких меж Звести PDF Зробіть файл PDF незмінним за допомогою растрування сторінок документа Не вдалося запустити камеру. Будь ласка, перевірте дозволи та переконайтеся, що він не використовується іншою програмою. Видобуток зображень Витягніть зображення, вбудовані в PDF-файли, з їх оригінальною роздільною здатністю Цей PDF-файл не містить жодних вбудованих зображень Цей інструмент сканує кожну сторінку та відновлює повноякісні вихідні зображення — ідеально підходить для збереження оригіналів із документів Намалювати підпис Параметри пера Використовуйте власний підпис як зображення для розміщення на документах Zip PDF Розділіть документ із заданим інтервалом і запакуйте нові документи в zip-архів Інтервал Роздрукувати PDF Підготуйте документ до друку з нестандартним розміром сторінки Сторінок на аркуші Орієнтація Розмір сторінки Маржа цвітіння М\'яке коліно Оптимізовано для аніме та мультфільмів. Швидке масштабування з покращеними природними кольорами та меншою кількістю артефактів Стиль, схожий на Samsung One UI 7 Введіть тут основні математичні символи, щоб обчислити потрібне значення (наприклад, (5+5)*10) Математичний вираз Виберіть до %1$s зображень Зберегти дату та час Завжди зберігати теги exif, пов’язані з датою та часом, працює незалежно від параметра keep exif Колір фону для альфа-форматів Додає можливість установлювати фоновий колір для кожного формату зображення з підтримкою альфа-версії, якщо вимкнено, це доступно лише для неальфа-версії Відкритий проект Продовжити редагування раніше збереженого проекту Image Toolbox Неможливо відкрити проект Image Toolbox У проекті Image Toolbox відсутні дані проекту Проект Image Toolbox пошкоджено Непідтримувана версія проекту Image Toolbox: %1$d Зберегти проект Зберігайте шари, фон і історію редагування у файлі проекту, який можна редагувати Не вдалося відкрити Запис у PDF з можливістю пошуку Розпізнавайте текст із пакету зображень і зберігайте файл PDF із можливістю пошуку із зображенням і текстовим шаром, який можна вибрати Шар альфа Горизонтальний фліп Вертикальний фліп Замок Додати тінь Колір тіні Геометрія тексту Розтягніть або перекосіть текст для чіткішої стилізації Шкала X Перекіс X Видаліть анотації Видаліть зі сторінок PDF вибрані типи анотацій, як-от посилання, коментарі, виділення, фігури або поля форми Гіперпосилання Вкладення файлів Лінії Спливаючі вікна Марки Фігури Текстові примітки Розмітка тексту Поля форми Розмітка Невідомий Анотації Розгрупувати Додайте розмиту тінь за шаром із настроюваним кольором і зсувами ================================================ FILE: core/resources/src/main/res/values-v26/bools.xml ================================================ true ================================================ FILE: core/resources/src/main/res/values-v31/colors.xml ================================================ @android:color/system_accent1_100 @android:color/system_accent1_700 ================================================ FILE: core/resources/src/main/res/values-vi/strings.xml ================================================ Có gì đó sai: %1$s Kích thước %1$s Đang tải… Hình ảnh quá lớn để xem trước nhưng vẫn sẽ cố lưu lại Chọn hình ảnh để bắt đầu Chiều rộng %1$s Chiều cao %1$s Chất lượng Tiện ích mở rộng Kiểu thay đổi kích thước Minh bạch Linh hoạt Chọn hình ảnh Bạn có thực sự muốn đóng ứng dụng? Ứng dụng đang đóng Ở lại Đóng Đặt lại hình ảnh Các thay đổi về hình ảnh sẽ quay trở lại giá trị ban đầu Các giá trị được đặt lại chính xác Đặt lại Đã xảy ra lỗi Khởi động lại ứng dụng Đã sao chép vào khay nhớ tạm Ngoại lệ Chỉnh sửa EXIF Ok Không tìm thấy dữ liệu EXIF Thêm thẻ Lưu Xóa Xóa EXIF Hủy Tất cả dữ liệu EXIF hình ảnh sẽ bị xóa. Hành động này không thể hoàn tác được! Thiết đặt trước Cắt Đang lưu Tất cả các thay đổi chưa được lưu sẽ bị mất nếu bạn thoát ngay bây giờ Mã nguồn Nhận thông tin cập nhật mới nhất, thảo luận các vấn đề và hơn thế nữa Chỉnh sửa đơn Thay đổi thông số kỹ thuật của một hình ảnh cụ thể Chọn màu Chọn màu từ hình ảnh, sao chép hoặc chia sẻ Hình ảnh Màu Đã sao chép màu Cắt hình ảnh theo bất kỳ giới hạn nào Phiên bản Giữ EXIF Hình ảnh: %d Thay đổi bản xem trước Loại bỏ Tạo bảng màu từ hình ảnh đã cho Tạo bảng màu Bảng màu Cập nhật Phiên bản mới %1$s Loại không được hỗ trợ: %1$s Không thể tạo bảng màu cho hình ảnh đã cho Bản gốc Thư mục đầu ra Mặc định Tùy chỉnh Không xác định Bộ nhớ thiết bị Thay đổi kích thước theo dung lượng Kích thước tối đa tính bằng KB Thay đổi kích thước hình ảnh theo kích thước nhất định tính bằng KB So sánh So sánh hai hình ảnh đã cho Chọn hai hình ảnh để bắt đầu Chọn hình ảnh Thiết đặt Chế độ đêm Tối Sáng Hệ thống Màu sắc sống động Tùy chỉnh Cho phép hình ảnh monet Nếu được bật, khi bạn chọn một hình ảnh để chỉnh sửa, màu ứng dụng sẽ được áp dụng giống hình ảnh này Ngôn ngữ Chế độ Amoled Nếu được bật, màu bề mặt sẽ được đặt thành tối tuyệt đối ở chế độ đêm Cách phối màu Đỏ Lục Lam Dán Mã aRGB hợp lệ. Không có gì để dán Không thể thay đổi cách phối màu của ứng dụng khi bật màu động Chủ đề ứng dụng sẽ dựa trên màu đã chọn Giới thiệu về ứng dụng Không tìm thấy bản cập nhật nào Trình theo dõi vấn đề Gửi báo cáo lỗi và yêu cầu tính năng tại đây Trợ giúp dịch Sửa lỗi dịch thuật hoặc bản địa hóa dự án sang ngôn ngữ khác Truy vấn của bạn không tìm thấy gì Tìm kiếm ở đây Nếu được bật thì màu ứng dụng sẽ được áp dụng giống màu hình nền Không lưu được %d hình ảnh Email Sơ cấp Cao cấp Trung cấp Độ dày viền Bề mặt Giá trị Thêm Quyền Cấp Ứng dụng cần quyền truy cập vào bộ nhớ của bạn để lưu hình ảnh để hoạt động, điều đó là cần thiết. Vui lòng cấp quyền trong hộp thoại tiếp theo. Ứng dụng cần có quyền này để hoạt động, vui lòng cấp quyền này theo cách thủ công Bộ nhớ ngoài Màu monet Ứng dụng này hoàn toàn miễn phí, nhưng nếu bạn muốn hỗ trợ phát triển dự án, bạn có thể nhấp vào đây Căn chỉnh FAB Kiểm tra cập nhật Nếu được bật, hộp thoại cập nhật sẽ được hiển thị cho bạn khi khởi động ứng dụng Thu phóng hình ảnh Chia sẻ Tiền tố Tên tệp Biểu tượng cảm xúc Chọn biểu tượng cảm xúc nào sẽ hiển thị trên màn hình chính Thêm kích thước tập tin Nếu được bật, sẽ thêm chiều rộng và chiều cao của hình ảnh đã lưu vào tên tệp đầu ra Xóa EXIF Xóa siêu dữ liệu EXIF khỏi bất kỳ bộ hình ảnh nào Xem trước hình ảnh Xem trước mọi loại hình ảnh: GIF, SVG, v.v. Nguồn hình ảnh Bộ chọn ảnh Thư viện Trình khám phá tệp Bộ chọn ảnh hiện đại của Android xuất hiện ở cuối màn hình, chỉ có thể hoạt động trên Android 12+. Có vấn đề khi nhận siêu dữ liệu EXIF Bộ chọn hình ảnh thư viện đơn giản. Nó sẽ chỉ hoạt động nếu bạn có ứng dụng cung cấp tính năng chọn phương tiện Sử dụng mục đích GetContent để chọn hình ảnh. Hoạt động ở mọi nơi nhưng được biết là có vấn đề khi nhận hình ảnh được chọn trên một số thiết bị. Đó không phải lỗi của tôi. Sắp xếp tùy chọn Chỉnh sửa Thứ tự Xác định thứ tự các tùy chọn trên màn hình chính Số lượng biểu tượng cảm xúc số thứ tự tên tập tin gốc Thêm tên tệp gốc Nếu được bật, sẽ thêm tên tệp gốc vào tên của hình ảnh đầu ra Thay thế số thứ tự Nếu được bật sẽ thay thế dấu thời gian tiêu chuẩn thành số thứ tự hình ảnh nếu bạn sử dụng xử lý hàng loạt Việc thêm tên tệp gốc không hoạt động nếu nguồn hình ảnh của bộ chọn ảnh được chọn Tải hình ảnh từ mạng Tải bất kỳ hình ảnh nào từ internet để xem trước, thu phóng, chỉnh sửa và lưu nó nếu bạn muốn. Không có hình ảnh Liên kết hình ảnh Lấp đầy Phù hợp Tỷ lệ nội dung Buộc mọi hình ảnh vào một hình ảnh được cung cấp bởi tham số Chiều rộng và Chiều cao - có thể thay đổi tỷ lệ khung hình Thay đổi kích thước hình ảnh thành hình ảnh có cạnh dài được cung cấp bởi tham số Chiều rộng hoặc Chiều cao, mọi tính toán kích thước sẽ được thực hiện sau khi lưu - giữ nguyên tỷ lệ khung hình Độ sáng Độ tương phản Sắc độ Độ bão hòa Thêm bộ lọc Bộ lọc Áp dụng bất kỳ chuỗi bộ lọc nào cho hình ảnh nhất định Bộ lọc Ánh sáng Bộ lọc màu Alpha Tiếp xúc Cân bằng trắng Nhiệt độ Pha màu Đơn sắc Gamma Tô sáng và đổ bóng Tô sáng Đổ bóng Sương mù Hiệu ứng Khoảng cách Độ dốc Làm sắc nét Màu nâu đỏ Phủ định Năng lượng mặt trời Rung động Đen và Trắng Chữ thập Cách khoảng Độ rộng dòng Cạnh Sobel Làm mờ Bán âm Không gian màu CGA Làm mờ Gaussian Làm mờ hộp Làm mờ song phương Chạm nổi Laplacian Họa tiết Bắt đầu Kết thúc Làm mịn Kuwahara Làm mờ ngăn xếp Bán kính Tỷ lệ Biến dạng Góc Xoáy Phồng lên Sự giãn nở Khúc xạ hình cầu Chỉ số khúc xạ Khúc xạ cầu thủy tinh Ma trận màu Độ trong suốt Giới hạn thay đổi kích thước Thay đổi kích thước hình ảnh đã chọn để tuân theo giới hạn chiều rộng và chiều cao nhất định trong khi lưu tỷ lệ khung hình Phác thảo Ngưỡng Mức độ lượng tử hóa Hoạt hình mượt mà hoạt hình Poster hóa Ngăn chặn không tối đa Bao gồm pixel yếu Tra cứu Tích chập 3x3 Bộ lọc RGB Màu sai Màu đầu tiên Màu thứ hai Sắp xếp lại Làm mờ nhanh Kích thước mờ Làm mờ trung tâm x Làm mờ trung tâm y Thu phóng mờ Cân bằng màu sắc Ngưỡng độ chói Bạn đã tắt ứng dụng Tệp, hãy kích hoạt ứng dụng này để sử dụng tính năng này Vẽ Vẽ trên hình ảnh như trong sổ phác thảo hoặc vẽ trên nền Màu sơn Sơn alpha Vẽ lên hình ảnh Chọn một hình ảnh và vẽ thứ gì đó lên nó Vẽ trên nền Chọn màu nền và vẽ lên trên nó Màu nền Mật mã Mã hóa và Giải mã bất kỳ tập tệp nào (không chỉ hình ảnh) dựa trên nhiều thuật toán mã hóa khả dụng Chọn tập tin Mã hóa Giải mã Chọn tập tin để bắt đầu Giải mã Mã hóa Chìa khóa Tệp đã được xử lý Lưu trữ tệp này trên thiết bị của bạn hoặc sử dụng tác vụ chia sẻ để đặt nó ở bất cứ đâu bạn muốn Tính năng Triển khai Khả năng tương thích Mã hóa tập tin dựa trên mật khẩu. Các tập tin đã xử lý có thể được lưu trữ trong thư mục đã chọn hoặc được chia sẻ. Các tập tin được giải mã cũng có thể được mở trực tiếp. AES-256, chế độ GCM, không có phần đệm, IV ngẫu nhiên 12 byte. Các khóa được sử dụng làm hàm băm SHA-3 (256 bit). Kích thước tệp Kích thước tệp tối đa bị giới hạn bởi hệ điều hành Android và bộ nhớ khả dụng, tùy thuộc vào thiết bị. \nXin lưu ý: bộ nhớ không phải là nơi lưu trữ. Xin lưu ý rằng khả năng tương thích với các phần mềm hoặc dịch vụ mã hóa tệp khác không được đảm bảo. Cách xử lý khóa hoặc cấu hình mật mã hơi khác một chút có thể gây ra sự không tương thích. Mật khẩu không hợp lệ hoặc tập tin đã chọn không được mã hóa Cố gắng lưu hình ảnh với chiều rộng và chiều cao đã cho có thể gây ra một lỗi hết bộ nhớ. Thực hiện điều này với rủi ro của riêng bạn Bộ đệm Kích thước bộ đệm Đã tìm thấy %1$s Tự động xóa bộ nhớ đệm Nếu được bật bộ đệm ứng dụng sẽ bị xóa khi khởi động ứng dụng Tạo Công cụ Nhóm tùy chọn theo loại Nhóm các tùy chọn trên màn hình chính theo loại thay vì sắp xếp danh sách tùy chỉnh Không thể thay đổi cách sắp xếp khi nhóm tùy chọn được bật Chỉnh sửa ảnh chụp màn hình Tùy chỉnh phụ Ảnh chụp màn hình Tùy chọn dự phòng Bỏ qua Sao chép Việc lưu ở chế độ %1$s có thể không ổn định vì đây là định dạng không mất dữ liệu Nếu bạn đã chọn thiết đặt trước 125, ảnh sẽ được lưu ở kích thước 125% của ảnh gốc với chất lượng 100%. Nếu bạn chọn thiết đặt trước 50 thì ảnh sẽ được lưu với kích thước 50% và chất lượng 50%. Thiết đặt trước ở đây xác định % của file đầu ra, tức là nếu bạn chọn thiết đặt trước 50 trên ảnh 5mb thì sau khi lưu bạn sẽ nhận được ảnh 2,5mb Chọn ngẫu nhiên tên tệp Nếu được bật, tên tệp đầu ra sẽ hoàn toàn ngẫu nhiên Đã lưu vào thư mục %1$s có tên %2$s Đã lưu vào thư mục %1$s Trò chuyện Telegram Thảo luận về ứng dụng và nhận phản hồi từ những người dùng khác. Bạn cũng có thể nhận thông tin cập nhật và thông tin chi tiết về phiên bản beta tại đây. Cắt mặt nạ Tỷ lệ khung hình Sử dụng loại mặt nạ này để tạo mặt nạ từ hình ảnh đã cho, lưu ý rằng nó PHẢI có kênh alpha Sao lưu và khôi phục Sao lưu Khôi phục Sao lưu thiết đặt ứng dụng của bạn vào một tệp Khôi phục thiết đặt ứng dụng từ tệp được tạo trước đó Tệp bị hỏng hoặc không phải là bản sao lưu Đã khôi phục thiết đặt thành công Liên hệ với tôi Điều này sẽ khôi phục cài đặt của bạn về giá trị mặc định. Lưu ý rằng thao tác này không thể hoàn tác nếu không có tệp sao lưu được đề cập ở trên. Xóa Bạn sắp xóa bảng màu đã chọn. Thao tác này không thể hoàn tác được Xóa bảng màu Phông chữ Văn bản Tỷ lệ phông chữ Mặc định Việc sử dụng tỷ lệ phông chữ lớn có thể gây ra trục trặc và sự cố về giao diện người dùng mà sẽ không thể khắc phục được. Sử dụng thận trọng. Aa Ăă Ââ Bb Cc Dd Đđ Ee Êê Gg Hh Ii Kk Ll Mm Nn Oo Ôô Ơơ Pp Qq Rr Ss Tt Uu Ưư Vv Xx Yy 0123456789 !? Cảm xúc Thức ăn và đồ uống Thiên nhiên và Động vật Đối tượng Ký hiệu Bật biểu tượng cảm xúc Du lịch và Địa điểm Hoạt động Xóa nền Xóa nền khỏi hình ảnh bằng cách vẽ hoặc sử dụng tùy chọn Tự động Cắt hình ảnh Siêu dữ liệu hình ảnh gốc sẽ được giữ lại Các khoảng trống trong suốt xung quanh hình ảnh sẽ bị cắt bớt Tự động xóa nền Khôi phục hình ảnh Chế độ xóa Xóa nền Khôi phục nền Bán kính mờ Pipet Chế độ vẽ Tạo vấn đề Rất tiếc… Đã xảy ra lỗi. Bạn có thể viết thư cho tôi bằng các tùy chọn bên dưới và tôi sẽ cố gắng tìm giải pháp Thay đổi kích thước và chuyển đổi Thay đổi kích thước của hình ảnh nhất định hoặc chuyển đổi chúng sang các định dạng khác. Siêu dữ liệu EXIF cũng có thể được chỉnh sửa tại đây nếu chọn một hình ảnh. Số màu tối đa Điều này cho phép ứng dụng thu thập báo cáo sự cố theo cách thủ công Phân tích Cho phép thu thập số liệu thống kê sử dụng ứng dụng ẩn danh Hiện tại, định dạng %1$s chỉ cho phép đọc siêu dữ liệu EXIF trên Android. Hình ảnh đầu ra sẽ không có siêu dữ liệu nào khi được lưu. Lực nén Giá trị %1$s có nghĩa là nén nhanh, dẫn đến kích thước tệp tương đối lớn. %2$s có nghĩa là nén chậm hơn, dẫn đến tệp nhỏ hơn. Đợi Việc lưu gần như hoàn tất. Việc hủy bây giờ sẽ yêu cầu lưu lại. Cập nhật Cho phép beta Kiểm tra cập nhật sẽ bao gồm các phiên bản ứng dụng beta nếu được bật Vẽ mũi tên Nếu kích hoạt đường vẽ sẽ được biểu diễn dưới dạng mũi tên trỏ Độ mềm của cọ Hình ảnh sẽ được cắt ở giữa theo kích thước đã nhập. Canvas sẽ được mở rộng với màu nền nhất định nếu hình ảnh nhỏ hơn kích thước đã nhập. Quyên góp Ghép ảnh Kết hợp các hình ảnh đã cho để có được một hình ảnh lớn Chọn ít nhất 2 hình ảnh Tỷ lệ hình ảnh đầu ra Hướng hình ảnh Ngang Dọc Chia tỷ lệ hình ảnh nhỏ thành lớn Hình ảnh nhỏ sẽ được phóng to thành hình ảnh lớn nhất trong chuỗi nếu được bật Thứ tự hình ảnh Thông thường Làm mờ các cạnh Vẽ các cạnh mờ bên dưới ảnh gốc để lấp đầy khoảng trống xung quanh nó thay vì một màu nếu được bật Pixel hóa Pixel nâng cao Pixel hóa nét Điểm ảnh kim cương nâng cao Pixel kim cương Pixel hóa vòng tròn Pixel hóa vòng tròn nâng cao Thay thế màu Dung sai Màu cần thay thế Màu mục tiêu Màu cần xóa Xóa màu Mã hóa lại Kích thước pixel Khóa hướng vẽ Nếu được bật ở chế độ vẽ, màn hình sẽ không xoay Kiểm tra cập nhật Kiểu bảng màu Điểm tông màu Trung lập Sống động Biểu cảm Cầu vồng Salad trái cây Sự trung thực Nội dung Kiểu bảng màu mặc định, nó cho phép tùy chỉnh tất cả bốn màu, những màu khác cho phép bạn chỉ đặt màu chính Một phong cách có nhiều màu sắc hơn một chút Chủ đề ồn ào, màu sắc tối đa cho bảng màu Chính, tăng cho các bảng màu khác Một chủ đề vui nhộn - sắc thái của màu nguồn không xuất hiện trong chủ đề Chủ đề đơn sắc, màu sắc hoàn toàn là đen / trắng / xám Một cơ chế đặt màu nguồn vào Scheme.primaryContainer Một cơ chế rất giống với cơ chế nội dung Trình kiểm tra cập nhật này sẽ kết nối với GitHub để kiểm tra xem có bản cập nhật mới hay không Chú ý Các cạnh mờ dần Đã tắt Cả hai Đảo ngược màu Thay thế màu chủ đề thành màu âm nếu được bật Tìm kiếm Cho phép khả năng tìm kiếm thông qua tất cả các tùy chọn có sẵn trên màn hình chính Công cụ PDF Hoạt động với các tệp PDF: Xem trước, Chuyển đổi sang hàng loạt hình ảnh hoặc tạo một hình ảnh từ các hình ảnh nhất định Xem trước PDF PDF sang hình ảnh Hình ảnh sang PDF Xem trước bản PDF đơn giản Chuyển đổi PDF thành hình ảnh ở định dạng đầu ra nhất định Đóng gói các hình ảnh đã cho vào tệp PDF đầu ra Bộ lọc mặt nạ Áp dụng chuỗi bộ lọc lên các vùng được che phủ đã cho; mỗi vùng che phủ có thể tự xác định bộ lọc riêng của mình Mặt nạ Thêm mặt nạ Mặt nạ %d Màu mặt nạ Xem trước mặt nạ Mặt nạ lọc đã vẽ sẽ được hiển thị để hiển thị cho bạn kết quả gần đúng Đảo ngược kiểu lấp đầy Nếu được bật, tất cả các khu vực không bị che sẽ được lọc thay vì hoạt động mặc định Bạn sắp xóa mặt nạ lọc đã chọn. Thao tác này không thể hoàn tác được Xóa mặt nạ Bộ lọc đầy đủ Áp dụng bất kỳ chuỗi bộ lọc nào cho hình ảnh nhất định hoặc hình ảnh đơn lẻ Bắt đầu Trung tâm Kết thúc Các biến thể đơn giản Bút đánh dấu Neon Bút Làm mờ quyền riêng tư Vẽ các đường tô sáng được làm sắc nét bán trong suốt Thêm một số hiệu ứng phát sáng vào bản vẽ của bạn Mặc định, đơn giản nhất - chỉ là màu sắc Làm mờ hình ảnh theo đường dẫn đã vẽ để bảo mật mọi thứ bạn muốn ẩn Tương tự như làm mờ quyền riêng tư, nhưng tạo pixel thay vì làm mờ Vùng chứa Cho phép vẽ bóng phía sau vùng chứa Thanh trượt Công tắc FAB Các nút Cho phép vẽ bóng phía sau thanh trượt Cho phép vẽ bóng phía sau công tắc Cho phép vẽ bóng phía sau các nút hành động nổi Cho phép vẽ bóng phía sau các nút mặc định Thanh ứng dụng Cho phép vẽ bóng phía sau thanh ứng dụng Giá trị trong phạm vi %1$s - %2$s Tự động xoay Cho phép sử dụng hộp giới hạn để định hướng hình ảnh Chế độ vẽ đường dẫn Mũi tên đường đôi Vẽ tự do Mũi tên đôi Mũi tên dòng Mũi tên Dòng Vẽ đường dẫn làm giá trị đầu vào Vẽ đường đi từ điểm bắt đầu đến điểm kết thúc dưới dạng một dòng Vẽ mũi tên trỏ từ điểm đầu đến điểm cuối dưới dạng một dòng Vẽ mũi tên trỏ từ một đường dẫn nhất định Vẽ mũi tên chỉ kép từ điểm bắt đầu đến điểm kết thúc dưới dạng một dòng Vẽ mũi tên chỉ kép từ một đường dẫn nhất định Hình bầu dục có viền ngoài Hình chữ nhật được phác thảo Hình bầu dục Hình chữ nhật Vẽ hình chữ nhật từ điểm đầu đến điểm cuối Vẽ hình bầu dục từ điểm đầu đến điểm cuối Vẽ đường viền hình bầu dục từ điểm đầu đến điểm cuối Vẽ đường viền hình chữ nhật từ điểm đầu đến điểm cuối Lasso Vẽ đường dẫn đã đóng theo đường dẫn đã cho Tự do Lưới ngang Lưới dọc Chế độ khâu Số hàng Số cột Không tìm thấy thư mục \"%1$s\" nào, chúng tôi đã chuyển nó về thư mục mặc định, vui lòng lưu lại tệp Clipboard Tự động ghim Tự động thêm hình ảnh đã lưu vào clipboard nếu được bật Rung Cường độ rung Để ghi đè lên tập tin, bạn cần sử dụng nguồn hình ảnh \"Explorer\", hãy thử chọn lại hình ảnh, chúng tôi đã thay đổi nguồn hình ảnh thành nguồn cần thiết Ghi đè tập tin Tệp gốc sẽ được thay thế bằng tệp mới thay vì lưu vào thư mục đã chọn, tùy chọn này cần nguồn hình ảnh là \"Explorer\" hoặc GetContent, khi chuyển đổi tùy chọn này, nó sẽ được đặt tự động Trống Hậu tố Chế độ tỷ lệ Song tuyến tính Catmull Bicubic Hann Ẩn sĩ Lanczos Mitchell Gần nhất Đường cong Cơ bản Giá trị mặc định Nội suy tuyến tính (hoặc song tuyến tính, trong hai chiều) thường tốt cho việc thay đổi kích thước của hình ảnh, nhưng gây ra một số chi tiết bị mềm đi không mong muốn và vẫn có thể bị lởm chởm một chút Các phương pháp mở rộng quy mô tốt hơn bao gồm lấy mẫu lại Lanczos và bộ lọc Mitchell-Netravali Một trong những cách tăng kích thước đơn giản hơn là thay thế từng pixel bằng một số pixel cùng màu Chế độ chia tỷ lệ Android đơn giản nhất được sử dụng trong hầu hết các ứng dụng Phương pháp nội suy và lấy mẫu lại một cách trơn tru một tập hợp các điểm kiểm soát, thường được sử dụng trong đồ họa máy tính để tạo các đường cong mượt mà Chức năng cửa sổ thường được áp dụng trong xử lý tín hiệu để giảm thiểu rò rỉ quang phổ và cải thiện độ chính xác của phân tích tần số bằng cách làm thon dần các cạnh của tín hiệu Kỹ thuật nội suy toán học sử dụng các giá trị và đạo hàm tại điểm cuối của một đoạn đường cong để tạo ra một đường cong trơn tru và liên tục Phương pháp lấy mẫu lại duy trì phép nội suy chất lượng cao bằng cách áp dụng hàm chân trọng có trọng số cho các giá trị pixel Phương pháp lấy mẫu lại sử dụng bộ lọc tích chập với các tham số có thể điều chỉnh để đạt được sự cân bằng giữa độ sắc nét và khử răng cưa trong hình ảnh được chia tỷ lệ Sử dụng các hàm đa thức được xác định theo từng phần để nội suy và xấp xỉ một cách mượt mà một đường cong hoặc bề mặt, biểu diễn hình dạng linh hoạt và liên tục Chỉ Clip Việc lưu vào bộ nhớ sẽ không được thực hiện và hình ảnh sẽ chỉ được cố gắng đưa vào clipboard Brush sẽ khôi phục nền thay vì xóa OCR (Nhận dạng văn bản) Nhận dạng văn bản từ hình ảnh nhất định, hỗ trợ hơn 120 ngôn ngữ Ảnh không có văn bản hoặc ứng dụng không tìm thấy nó Độ chính xác: %1$s Loại nhận dạng Nhanh Tiêu chuẩn Tốt nhất Không có dữ liệu Để Tesseract OCR hoạt động bình thường, dữ liệu đào tạo bổ sung (%1$s) cần được tải xuống thiết bị của bạn.\nBạn có muốn tải xuống dữ liệu %2$s không? Tải xuống Không có kết nối, hãy kiểm tra và thử lại để tải xuống mô hình đào tạo Ngôn ngữ đã tải xuống Ngôn ngữ có sẵn Chế độ phân đoạn Sử dụng Pixel Switch Công tắc giống pixel sẽ được sử dụng thay cho tài liệu của Google mà bạn dựa trên Tệp bị ghi đè có tên %1$s tại đích ban đầu Kính lúp Bật kính lúp ở đầu ngón tay trong chế độ vẽ để có khả năng truy cập tốt hơn Buộc giá trị ban đầu Buộc kiểm tra tiện ích Exif ban đầu Cho phép nhiều ngôn ngữ Trượt Bên cạnh nhau Chuyển đổi Nhấn Trong suốt Xếp hạng ứng dụng Xếp hạng Ứng dụng này hoàn toàn miễn phí, nếu bạn muốn nó lớn mạnh, hãy đánh dấu sao cho dự án trên Github 😄 Định hướng & Chỉ phát hiện tập lệnh Tự động định hướng & Phát hiện tập lệnh Chỉ tự động Tự động Cột đơn Văn bản dọc khối đơn Khối đơn Dòng đơn Từ đơn Vòng tròn từ Ký tự đơn Văn bản thưa thớt Định hướng văn bản thưa thớt & Phát hiện tập lệnh Dòng thô Bạn muốn xóa dữ liệu đào tạo OCR ngôn ngữ \"%1$s\" cho tất cả các loại nhận dạng hay chỉ cho một loại đã chọn (%2$s)? Hiện tại Tất cả Trình tạo chuyển màu Tạo độ dốc của kích thước đầu ra nhất định với màu sắc và kiểu hiển thị tùy chỉnh Tuyến tính Bán kính Quét Loại chuyển màu Trung tâm X Trung tâm Y Chế độ xếp ô Đã lặp lại Gương Kẹp Đề can Điểm dừng màu Thêm màu Thuộc tính Thực thi độ sáng Màn hình Lớp phủ chuyển màu Soạn bất kỳ độ dốc nào ở trên cùng của hình ảnh đã cho Sự biến đổi Máy ảnh Sử dụng camera để chụp ảnh, lưu ý rằng chỉ có thể lấy một hình ảnh từ nguồn hình ảnh này Watermark Che ảnh bằng watermark văn bản/hình ảnh có thể tùy chỉnh Lặp lại watermark Lặp lại watermark trên hình ảnh thay vì một watermark ở vị trí nhất định Bù đắp X Bù đắp Y Loại watermark Hình ảnh này sẽ được sử dụng làm mẫu cho watermark Màu văn bản Chế độ lớp phủ Công cụ GIF Chuyển đổi hình ảnh thành ảnh GIF hoặc trích xuất khung hình từ ảnh GIF đã cho GIF thành hình ảnh Chuyển đổi tập tin GIF thành hàng loạt ảnh Chuyển đổi hàng loạt hình ảnh thành tệp GIF Hình ảnh thành GIF Chọn ảnh GIF để bắt đầu Sử dụng kích thước của khung hình đầu tiên Thay thế kích thước được chỉ định bằng kích thước khung đầu tiên Số lần lặp lại Độ trễ khung hình mili giây FPS Sử dụng Lasso Sử dụng Lasso giống như trong chế độ vẽ để thực hiện xóa Bản xem trước ảnh gốc Alpha Hoa giấy Hoa giấy sẽ được hiển thị khi lưu, chia sẻ và các hành động chính khác Chế độ bảo mật Ẩn nội dung khi thoát, đồng thời không thể chụp hoặc ghi lại màn hình Thoát Nếu bây giờ bạn để chế độ xem trước, bạn sẽ cần thêm lại hình ảnh Phối màu Bộ lượng tử hóa Thang màu xám Phối màu Bayer Two By Two Phối màu Bayer Three By Three Phối màu Bayer Four By Four Phối màu Bayer Eight By Eight Phối màu Floyd Steinberg Phối màu Jarvis Judice Ninke Phối màu Sierra Phối màu Two Row Sierra Phối màu Sierra Lite Phối màu Atkinson Phối màu Stucki Phối màu Burkes Phối màu False Floyd Steinberg Phối màu Left To Right Phối màu Random Phối màu Simple Threshold Sigma Sigma không gian Độ mờ trung bình Đường trục B Sử dụng các hàm đa thức hai khối được xác định từng phần để nội suy và xấp xỉ một cách mượt mà một đường cong hoặc bề mặt, biểu diễn hình dạng linh hoạt và liên tục Làm mờ ngăn xếp gốc Dịch chuyển nghiêng Trục trặc Số lượng Hạt giống Anaglyph Tiếng ồn Sắp xếp pixel Xáo trộn Trục trặc nâng cao Dịch chuyển kênh X Dịch chuyển kênh Y Kích thước sai lệch Dịch chuyển sai lệch X Dịch chuyển sai lệch Y Lều mờ Làm mờ bên Bên Trên đầu Dưới cùng Cường độ Bào mòn Khuếch tán dị hướng Khuếch tán Truyền dẫn Gió giật ngang Làm mờ song phương nhanh Làm mờ Poisson Ánh xạ giai điệu logarit Ánh xạ giai điệu phim ACES Kết tinh Màu nét Kính Fractal Biên độ Đá cẩm thạch Sự hỗn loạn Dầu Hiệu ứng nước Kích thước Tần số X Tần số Y Biên độ X Biên độ Y Biến dạng Perlin Bản đồ giai điệu đồi ACES Bản đồ giai điệu phim Hable Bản đồ giai điệu Hejl Burgess Tốc độ Khử khói Omega Ma trận màu 4x4 Ma trận màu 3x3 Hiệu ứng đơn giản Polaroid Tritonomaly Deutaromaly Protonomaly Cổ điển Browni Coda Chrome Tầm nhìn ban đêm Ấm Lạnh Tritanopia Deutaronotopia Protanopia Bệnh sắc tố Achromatopsia Ngũ cốc Không sắc nét Phấn màu Sương mù màu cam Giấc mơ hồng Giờ vàng Mùa hè nóng nực Sương tím Bình minh Vòng xoáy đầy màu sắc Ánh Xuân êm dịu Giai điệu mùa thu Giấc mơ hoa oải hương Cyberpunk Nước chanh nhẹ Ngọn lửa quang phổ Phép thuật bóng đêm Phong cảnh huyền ảo Bùng nổ màu sắc Độ dốc điện Bóng tối caramel Độ dốc tương lai Mặt Trời Xanh Thế giới cầu vồng Màu tím đậm Cổng không gian Vòng xoáy đỏ Mã kỹ thuật số Hiệu ứng mờ Biểu tượng cảm xúc trên thanh ứng dụng sẽ liên tục được thay đổi ngẫu nhiên thay vì sử dụng biểu tượng đã chọn Biểu tượng cảm xúc ngẫu nhiên Không thể sử dụng tính năng chọn biểu tượng cảm xúc ngẫu nhiên khi biểu tượng cảm xúc bị tắt Không thể chọn biểu tượng cảm xúc trong khi chọn biểu tượng cảm xúc ngẫu nhiên đã bật Tivi cũ Làm mờ ngẫu nhiên Yêu thích Chưa có bộ lọc yêu thích nào được thêm vào Định dạng hình ảnh Thêm vùng chứa có hình dạng đã chọn bên dưới các biểu tượng hàng đầu của thẻ Hình dạng biểu tượng Drago Aldridge Cutoff Uchimura Mobius Chuyển tiếp Đỉnh Màu sắc bất thường Hình ảnh bị ghi đè ở đích ban đầu Không thể thay đổi định dạng hình ảnh khi bật tùy chọn ghi đè tệp Biểu tượng cảm xúc dưới dạng bảng màu Sử dụng màu chính của biểu tượng cảm xúc làm bảng màu ứng dụng thay vì màu được xác định thủ công Tạo bảng màu Material You từ hình ảnh Màu tối Sử dụng bảng màu của chế độ ban đêm thay vì biến thể sáng Sao chép dưới dạng Jetpack Soạn mã Làm mờ vòng Làm mờ chéo Làm mờ vòng tròn Sao mờ Dịch chuyển nghiêng tuyến tính Thẻ cần xóa Công cụ APNG Chuyển đổi hình ảnh thành ảnh APNG hoặc trích xuất khung hình từ hình ảnh APNG đã cho APNG vào hình ảnh Chuyển đổi tập tin APNG thành hàng loạt ảnh Chuyển đổi hàng loạt hình ảnh sang tệp APNG Hình ảnh sang APNG Chọn hình ảnh APNG để bắt đầu Làm mờ chuyển động Zip Tạo tệp Zip từ các tệp hoặc hình ảnh nhất định Chiều rộng tay cầm kéo Loại hoa giấy Lễ hội Nổ tung Mưa Các góc Công cụ JXL Thực hiện chuyển mã JXL ~ JPEG mà không làm giảm chất lượng hoặc chuyển đổi hoạt ảnh GIF/APNG sang JXL JXL sang JPEG Thực hiện chuyển mã không mất dữ liệu từ JXL sang JPEG Thực hiện chuyển mã không mất dữ liệu từ JPEG sang JXL JPEG sang JXL Chọn hình ảnh JXL để bắt đầu Làm mờ Gaussian nhanh 2D Làm mờ Gaussian nhanh 3D Làm mờ Gaussian nhanh 4D Tự động dán Cho phép ứng dụng tự động dán dữ liệu clipboard để nó sẽ xuất hiện trên màn hình chính và bạn có thể xử lý nó Màu hài hòa Mức độ hài hòa Lanczos Bessel Phương pháp lấy mẫu lại duy trì phép nội suy chất lượng cao bằng cách áp dụng hàm Bessel (jinc) cho các giá trị pixel GIF sang JXL Chuyển đổi ảnh GIF thành ảnh động JXL APNG tới JXL Chuyển đổi hình ảnh APNG thành hình ảnh động JXL JXL sang hình ảnh Chuyển đổi hình ảnh động JXL thành hàng loạt hình ảnh Hình ảnh tới JXL Chuyển đổi hàng loạt hình ảnh sang hoạt hình JXL Hành vi Bỏ qua việc chọn tập tin Bộ chọn tệp sẽ được hiển thị ngay lập tức nếu có thể trên màn hình đã chọn Tạo bản xem trước Cho phép tạo bản xem trước, điều này có thể giúp tránh sự cố trên một số thiết bị, điều này cũng vô hiệu hóa một số chức năng chỉnh sửa trong tùy chọn chỉnh sửa duy nhất Nén mất mát Sử dụng tính năng nén có mất dữ liệu để giảm kích thước tệp thay vì không mất dữ liệu Kiểu nén Kiểm soát tốc độ giải mã hình ảnh thu được, điều này sẽ giúp mở hình ảnh thu được nhanh hơn, giá trị %1$s có nghĩa là giải mã chậm nhất, trong khi %2$s - nhanh nhất, cài đặt này có thể tăng kích thước hình ảnh đầu ra Sắp xếp Sắp xếp theo ngày Sắp xếp theo ngày (Đảo ngược) Sắp xếp theo tên Sắp xếp theo tên (Đảo ngược) Cấu hình kênh Hôm nay Hôm qua Bộ chọn nhúng Sử dụng bộ chọn hình ảnh riêng của Hộp công cụ Hình ảnh thay vì bộ chọn hình ảnh được hệ thống xác định trước Không có quyền Yêu cầu Chọn nhiều phương tiện Chọn một phương tiện Chọn Thử lại Hiển thị cài đặt theo chiều ngang Nếu tính năng này bị tắt thì cài đặt ở chế độ ngang sẽ được mở trên nút ở thanh ứng dụng trên cùng như mọi khi, thay vì tùy chọn hiển thị vĩnh viễn Cài đặt toàn màn hình Kích hoạt nó và trang cài đặt sẽ luôn được mở ở dạng toàn màn hình thay vì trang ngăn kéo có thể trượt Loại chuyển đổi Soạn Sử dụng tài liệu Jetpack Compose mà bạn chuyển đổi, nó không đẹp bằng chế độ xem Sử dụng tài liệu dựa trên Chế độ xem mà bạn chuyển đổi, tài liệu này trông đẹp hơn những tài liệu khác và có hình ảnh động đẹp mắt Tối đa Thay đổi kích thước mẩu neo Điểm ảnh Thông thạo Sử dụng công tắc kiểu Windows 11 dựa trên hệ thống thiết kế \"Fluent\" Cupertino Một công tắc dựa trên hệ thống thiết kế \"Cupertino\" Hình ảnh thành SVG Theo dõi hình ảnh đã cho thành hình ảnh SVG Sử dụng Bảng màu được lấy mẫu Bảng lượng tử hóa sẽ được lấy mẫu nếu tùy chọn này được bật Bỏ qua đường dẫn Không nên sử dụng công cụ này để theo dõi các hình ảnh lớn mà không thu nhỏ kích thước, nó có thể gây ra sự cố và tăng thời gian xử lý Thu nhỏ hình ảnh Hình ảnh sẽ được giảm kích thước xuống kích thước thấp hơn trước khi xử lý, điều này giúp công cụ hoạt động nhanh hơn và an toàn hơn Tỷ lệ màu tối thiểu Ngưỡng dòng Ngưỡng bậc hai Dung sai làm tròn tọa độ Tỷ lệ đường dẫn Đặt lại thuộc tính Tất cả các thuộc tính sẽ được đặt thành giá trị mặc định, lưu ý rằng hành động này không thể hoàn tác Chi tiết Độ rộng dòng mặc định Chế độ động cơ Di sản Mạng LSTM Legacy & LSTM Chuyển đổi Chuyển đổi hàng loạt hình ảnh sang định dạng nhất định Thêm thư mục mới Số bit trên mỗi mẫu Nén Giải thích trắc quang Mẫu trên mỗi pixel Cấu hình phẳng Lấy mẫu phụ Y Cb Cr Định vị Y Cb Cr Độ phân giải X Độ phân giải Y Đơn vị độ phân giải Dải bù trừ Hàng trên mỗi dải Loại bỏ số byte Định dạng trao đổi JPEG Độ dài định dạng trao đổi JPEG Hàm truyền Điểm trắng Màu sắc cơ bản Hệ số Y Cb Cr Tham khảo Đen Trắng Ngày Giờ Mô tả hình ảnh Cấu tạo Người mẫu Phần mềm Nghệ sĩ Bản quyền Phiên bản Exif Phiên bản Flashpix Không gian màu Gamma Kích thước pixel X Kích thước pixel Y Số bit được nén trên mỗi pixel Ghi chú của nhà sản xuất Bình luận của người dùng Tệp âm thanh liên quan Ngày Giờ Bản gốc Ngày Giờ Số hóa Thời gian bù trừ Thời gian bù trừ gốc Số hóa thời gian bù trừ Thời gian phụ giây Thời gian phụ giây gốc Số giây phụ Thời gian được số hóa Thời gian phơi sáng Số F Chương trình tiếp xúc Độ nhạy quang phổ Độ nhạy chụp ảnh OECF Loại độ nhạy Độ nhạy đầu ra tiêu chuẩn Chỉ số phơi nhiễm được đề xuất Tốc độ ISO Vĩ độ tốc độ ISO yyy Vĩ độ tốc độ ISO zzz Giá trị tốc độ màn trập Giá trị khẩu độ Giá trị độ sáng Giá trị thiên vị phơi sáng Giá trị khẩu độ tối đa Khoảng cách chủ đề Chế độ đo sáng Flash Lĩnh vực chủ đề Tiêu cự Năng lượng chớp nhoáng Đáp ứng tần số không gian Độ phân giải mặt phẳng tiêu điểm X Độ phân giải mặt phẳng tiêu điểm Y Đơn vị độ phân giải mặt phẳng tiêu điểm Vị trí chủ đề Chỉ số phơi nhiễm Phương pháp cảm biến Nguồn tập tin Mẫu CFA Hiển thị tùy chỉnh Chế độ phơi sáng Cân bằng trắng Tỷ lệ thu phóng kỹ thuật số Tiêu cự phim 35mm Kiểu chụp cảnh Giành quyền kiểm soát Độ tương phản Độ bão hòa Độ sắc nét Mô tả thiết đặt thiết bị Phạm vi khoảng cách chủ thể ID duy nhất của hình ảnh Tên chủ sở hữu máy ảnh Số sê-ri cơ thể Thông số ống kính Cấu tạo ống kính Mẫu ống kính Số sê-ri ống kính ID phiên bản GPS Tham chiếu Vĩ độ GPS Vĩ độ GPS Tham chiếu kinh độ GPS Kinh độ GPS Tham chiếu độ cao GPS Độ cao GPS Dấu thời gian GPS Vệ tinh GPS Trạng thái GPS Chế độ đo GPS DOP GPS Tham chiếu tốc độ GPS Tốc độ GPS Tham chiếu đường đi GPS Đường đi GPS Tham chiếu hướng hình ảnh GPS Hướng hình ảnh GPS Dữ liệu bản đồ GPS Tham chiếu Vĩ độ Đích GPS Vĩ độ đích của GPS Tham chiếu kinh độ đích GPS Kinh độ đích GPS Tham chiếu vòng bi đích GPS Vòng bi đích GPS Tham chiếu khoảng cách đích GPS Khoảng cách đích GPS Phương pháp xử lý GPS Thông tin khu vực GPS Dấu ngày GPS GPS vi sai Lỗi định vị GPS H Chỉ số khả năng tương tác Phiên bản DNG Kích thước cắt mặc định Xem trước hình ảnh bắt đầu Xem trước độ dài hình ảnh Khung khía cạnh Viền dưới cảm biến Cảm biến viền trái Cảm biến viền phải Đường viền trên cùng của cảm biến ISO Vẽ văn bản trên đường dẫn với phông chữ và màu sắc nhất định Cỡ chữ Kích thước watermark Lặp lại văn bản Văn bản hiện tại sẽ được lặp lại cho đến khi kết thúc đường dẫn thay vì vẽ một lần Kích thước dấu gạch ngang Sử dụng hình ảnh đã chọn để vẽ nó dọc theo đường dẫn đã cho Hình ảnh này sẽ được sử dụng làm mục nhập lặp đi lặp lại của đường dẫn đã vẽ Vẽ đường viền tam giác từ điểm đầu đến điểm cuối Vẽ đường viền tam giác từ điểm đầu đến điểm cuối Tam giác viền ngoài Tam giác Vẽ đa giác từ điểm đầu đến điểm cuối Đa giác Đa giác viền Vẽ đa giác có đường viền từ điểm đầu đến điểm cuối Các đỉnh Vẽ đa giác đều Vẽ đa giác sẽ có dạng thông thường thay vì dạng tự do Vẽ ngôi sao từ điểm đầu đến điểm cuối Ngôi sao Ngôi sao có viền Vẽ ngôi sao có đường viền từ điểm đầu đến điểm cuối Tỷ lệ bán kính bên trong Vẽ ngôi sao thông thường Vẽ ngôi sao sẽ có dạng thông thường thay vì dạng tự do Khử răng cưa Cho phép khử răng cưa để tránh các cạnh sắc nét Mở Chỉnh sửa thay vì Xem trước Khi bạn chọn hình ảnh để mở (xem trước) trong ImageToolbox, bảng lựa chọn chỉnh sửa sẽ được mở thay vì xem trước Máy quét tài liệu Quét tài liệu và tạo PDF hoặc tách hình ảnh khỏi chúng Nhấp để bắt đầu quét Bắt đầu quét Lưu dưới dạng PDF Chia sẻ như PDF Các tùy chọn bên dưới là để lưu hình ảnh chứ không phải PDF Cân bằng biểu đồ HSV Cân bằng biểu đồ Nhập phần trăm Cho phép nhập theo trường văn bản Bật Trường văn bản phía sau lựa chọn đặt trước để nhập chúng nhanh chóng Không gian màu tỷ lệ Tuyến tính Cân bằng pixel biểu đồ Kích thước lưới X Kích thước lưới Y Cân bằng biểu đồ Thích ứng Cân bằng biểu đồ LUV thích ứng Cân bằng biểu đồ LAB thích ứng Clahe Clahe LAB Clahe LUV Cắt theo nội dung Màu khung Màu cần bỏ qua Mẫu Không có bộ lọc mẫu nào được thêm vào Tạo mới Mã QR được quét không phải là mẫu bộ lọc hợp lệ Quét mã QR Tệp đã chọn không có dữ liệu mẫu bộ lọc Tạo mẫu Tên mẫu Hình ảnh này sẽ được sử dụng để xem trước mẫu bộ lọc này Bộ lọc mẫu Dưới dạng hình ảnh mã QR Dưới dạng tệp Lưu dưới dạng tệp Lưu dưới dạng hình ảnh mã QR Xóa mẫu Bạn sắp xóa bộ lọc mẫu đã chọn. Thao tác này không thể hoàn tác được Đã thêm mẫu bộ lọc có tên \"%1$s\" (%2$s) Xem trước bộ lọc Mã QR Quét mã QR và lấy nội dung hoặc dán chuỗi của bạn để tạo mã mới Nội dung mã Quét bất kỳ mã vạch nào để thay thế nội dung trong trường, hoặc nhập nội dung để tạo mã vạch mới với loại đã chọn Mô tả QR Tối thiểu Cấp quyền cho máy ảnh trong cài đặt để quét mã QR Cubic B-Spline Hamming Hanning Blackman Welch Quadric Gaussian Sphinx Bartlett Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-Hann Box Bohman Lanczos 2 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc Nội suy khối giúp chia tỷ lệ mượt mà hơn bằng cách xem xét 16 pixel gần nhất, cho kết quả tốt hơn so với nội suy song tuyến tính Sử dụng các hàm đa thức được xác định theo từng phần để nội suy và xấp xỉ một cách mượt mà một đường cong hoặc bề mặt, biểu diễn hình dạng linh hoạt và liên tục Một chức năng cửa sổ được sử dụng để giảm rò rỉ quang phổ bằng cách làm thon dần các cạnh của tín hiệu, hữu ích trong việc xử lý tín hiệu Một biến thể của cửa sổ Hann, thường được sử dụng để giảm rò rỉ quang phổ trong các ứng dụng xử lý tín hiệu Chức năng cửa sổ cung cấp độ phân giải tần số tốt bằng cách giảm thiểu rò rỉ quang phổ, thường được sử dụng trong xử lý tín hiệu Một chức năng cửa sổ được thiết kế để mang lại độ phân giải tần số tốt và giảm rò rỉ quang phổ, thường được sử dụng trong các ứng dụng xử lý tín hiệu Một phương pháp sử dụng hàm bậc hai để nội suy, mang lại kết quả mượt mà và liên tục Một phương pháp nội suy áp dụng hàm Gaussian, hữu ích cho việc làm mịn và giảm nhiễu trong hình ảnh Một phương pháp lấy mẫu lại nâng cao cung cấp phép nội suy chất lượng cao với lượng tạo tác tối thiểu Chức năng cửa sổ tam giác được sử dụng trong xử lý tín hiệu để giảm rò rỉ quang phổ Phương pháp nội suy chất lượng cao được tối ưu hóa để thay đổi kích thước hình ảnh một cách tự nhiên, cân bằng độ sắc nét và độ mịn Một biến thể sắc nét hơn của phương pháp Robidoux, được tối ưu hóa để thay đổi kích thước hình ảnh sắc nét Phương pháp nội suy dựa trên spline mang lại kết quả mượt mà bằng bộ lọc 16 lần nhấn Phương pháp nội suy dựa trên spline mang lại kết quả mượt mà bằng bộ lọc 36 lần nhấn Phương pháp nội suy dựa trên spline mang lại kết quả mượt mà bằng bộ lọc 64 lần nhấn Một phương pháp nội suy sử dụng cửa sổ Kaiser, cung cấp khả năng kiểm soát tốt sự cân bằng giữa chiều rộng thùy chính và mức thùy bên Chức năng cửa sổ lai kết hợp cửa sổ Bartlett và Hann, được sử dụng để giảm rò rỉ quang phổ trong xử lý tín hiệu Một phương pháp lấy mẫu lại đơn giản sử dụng giá trị trung bình của các giá trị pixel gần nhất, thường dẫn đến hình dạng khối Chức năng cửa sổ được sử dụng để giảm rò rỉ quang phổ, cung cấp độ phân giải tần số tốt trong các ứng dụng xử lý tín hiệu Phương pháp lấy mẫu lại sử dụng bộ lọc Lanczos 2 thùy để nội suy chất lượng cao với lượng tạo tác tối thiểu Phương pháp lấy mẫu lại sử dụng bộ lọc Lanczos 3 thùy để nội suy chất lượng cao với lượng tạo tác tối thiểu Phương pháp lấy mẫu lại sử dụng bộ lọc Lanczos 4 thùy để nội suy chất lượng cao với lượng tạo tác tối thiểu Một biến thể của bộ lọc Lanczos 2 sử dụng hàm jinc, cung cấp phép nội suy chất lượng cao với lượng tạo tác tối thiểu Một biến thể của bộ lọc Lanczos 3 sử dụng hàm jinc, cung cấp phép nội suy chất lượng cao với lượng tạo tác tối thiểu Một biến thể của bộ lọc Lanczos 4 sử dụng hàm jinc, cung cấp phép nội suy chất lượng cao với lượng tạo tác tối thiểu Hanning EWA Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Hanning để nội suy và lấy mẫu lại mượt mà Robidoux EWA Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Robidoux để lấy mẫu lại chất lượng cao Blackman EWA Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Blackman để giảm thiểu hiện tượng đổ chuông EWA bậc hai Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Quadric để nội suy mượt mà Robidoux Sharp EWA Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Robidoux Sharp cho kết quả sắc nét hơn Lanczos 3 Jinc EWA Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Lanczos 3 Jinc để lấy mẫu lại chất lượng cao với giảm răng cưa Nhân sâm Bộ lọc lấy mẫu lại được thiết kế để xử lý hình ảnh chất lượng cao với sự cân bằng tốt giữa độ sắc nét và độ mịn Nhân sâm EWA Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Nhân sâm để nâng cao chất lượng hình ảnh Lanczos Sharp EWA Biến thể trung bình có trọng số hình elip (EWA) của bộ lọc Lanczos Sharp để đạt được kết quả sắc nét với ít tạo tác nhất Lanczos 4 EWA sắc nét nhất Biến thể Trung bình Trọng số Hình elip (EWA) của bộ lọc Lanczos 4 Sharpest để lấy mẫu lại hình ảnh cực kỳ sắc nét Lanczos Soft EWA Biến thể Trung bình Trọng số Hình elip (EWA) của bộ lọc Lanczos Soft để lấy mẫu lại hình ảnh mượt mà hơn Haasn Soft Bộ lọc lấy mẫu lại được thiết kế bởi Haasn để chia tỷ lệ hình ảnh mượt mà và không có hiện tượng giả Chuyển đổi định dạng Chuyển đổi hàng loạt hình ảnh từ định dạng này sang định dạng khác Loại bỏ vĩnh viễn Xếp chồng hình ảnh Xếp chồng các hình ảnh lên nhau với các chế độ hòa trộn đã chọn Thêm hình ảnh Số thùng Clahe HSL Clahe HSV Cân bằng biểu đồ HSL thích ứng Cân bằng biểu đồ HSV thích ứng Chế độ cạnh Đoạn phim Cuộn Sơ đồ mù màu Chọn chế độ để điều chỉnh màu chủ đề cho biến thể mù màu nhất định Khó phân biệt giữa màu đỏ và màu lục Khó phân biệt giữa màu lục và màu đỏ Khó phân biệt giữa màu lam và màu vàng Không có khả năng nhận biết màu đỏ Không có khả năng nhận biết màu lục Không có khả năng nhận biết màu lam Giảm độ nhạy với tất cả các màu Mù màu hoàn toàn, chỉ nhìn thấy các sắc thái màu xám Không sử dụng sơ đồ mù màu Màu sắc sẽ chính xác như được đặt trong chủ đề Sigmoidal Bộ lọc nội suy Lagrange bậc 2, thích hợp cho việc chia tỷ lệ hình ảnh chất lượng cao với các chuyển tiếp mượt mà Bộ lọc nội suy Lagrange bậc 3, mang lại độ chính xác cao hơn và kết quả mượt mà hơn cho việc chia tỷ lệ hình ảnh Bộ lọc lấy mẫu lại Lanczos với bậc 6 cao hơn, mang lại tỷ lệ hình ảnh sắc nét và chính xác hơn Một biến thể của bộ lọc Lanczos 6 sử dụng chức năng Jinc để cải thiện chất lượng lấy mẫu lại hình ảnh Cấp quyền cho máy ảnh trong cài đặt để quét Máy quét tài liệu Lagrange 2 Lagrange 3 Lanczos 6 Lanczos 6 Jinc Làm mờ hộp tuyến tính Làm mờ lều tuyến tính Làm mờ hộp Gaussian tuyến tính Làm mờ ngăn xếp tuyến tính Làm mờ hộp Gaussian Làm mờ Gaussian nhanh tuyến tính Tiếp theo Làm mờ Gaussian nhanh tuyến tính Làm mờ Gaussian tuyến tính Chọn một bộ lọc để sử dụng nó làm sơn Thay thế bộ lọc Chọn bộ lọc bên dưới để sử dụng nó làm cọ vẽ trong bản vẽ của bạn Sơ đồ nén TIFF Poly thấp Tranh cát Tách ảnh Tách một hình ảnh theo hàng hoặc cột Phù hợp với giới hạn Kết hợp chế độ thay đổi kích thước cắt xén với tham số này để đạt được hành vi mong muốn (Cắt/Vừa với tỷ lệ khung hình) Ngôn ngữ được nhập thành công Sao lưu mô hình OCR Nhập khẩu Xuất khẩu Chức vụ Trung tâm Trên cùng bên trái Trên cùng bên phải Dưới cùng bên trái Dưới cùng bên phải Trung tâm hàng đầu Giữa bên phải Trung tâm dưới cùng Giữa bên trái Hình ảnh mục tiêu Chuyển bảng màu Dầu tăng cường TV cũ đơn giản HDR Gotham Phác thảo đơn giản Ánh sáng mềm mại Áp phích màu Tri giai điệu Màu thứ ba Clahe Oklab Clara Olks Clahe Jzazbz chấm bi Phối màu 2x2 theo cụm Phối màu theo cụm 4x4 Phối màu 8x8 theo cụm Phối màu Yililoma Không có tùy chọn yêu thích nào được chọn, hãy thêm chúng vào trang công cụ Thêm yêu thích bổ sung Tương tự bộ ba Chia bổ sung tứ giác Quảng trường Tương tự + Bổ sung Công cụ màu sắc Trộn, tạo tông màu, tạo sắc thái và hơn thế nữa Màu sắc hài hòa Màu bóng Biến thể Sắc thái Âm sắc thái Trộn màu Thông tin màu sắc Màu đã chọn Màu để trộn Không thể sử dụng tiền khi bật màu động 512x512 2D LUT Hình ảnh LUT mục tiêu Một người nghiệp dư nghi thức hoa hậu Thanh lịch mềm mại Biến thể thanh lịch mềm mại Biến thể chuyển bảng màu LUT 3D Nhắm mục tiêu tệp LUT 3D (.cube / .CUBE) LUT Bỏ qua thuốc tẩy Dưới ánh nến Thả nhạc blues Hổ phách sắc sảo Màu sắc mùa thu Kho phim 50 Đêm sương mù Kodak Nhận hình ảnh LUT trung tính Trước tiên, hãy sử dụng ứng dụng chỉnh sửa ảnh yêu thích của bạn để áp dụng bộ lọc cho LUT trung tính mà bạn có thể lấy tại đây. Để tính năng này hoạt động bình thường, mỗi màu pixel không được phụ thuộc vào các pixel khác (ví dụ: độ mờ sẽ không hoạt động). Sau khi sẵn sàng, hãy sử dụng hình ảnh LUT mới của bạn làm đầu vào cho bộ lọc LUT 512*512 Nghệ thuật đại chúng Celluloid Cà phê Rừng Vàng Hơi xanh Màu vàng cổ điển Xem trước liên kết Cho phép truy xuất bản xem trước liên kết ở những nơi bạn có thể lấy văn bản (QRCode, OCR, v.v.) Liên kết Các tệp ICO chỉ có thể được lưu ở kích thước tối đa 256 x 256 GIF sang WEBP Chuyển đổi ảnh GIF thành ảnh động WEBP Công cụ WEBP Chuyển đổi hình ảnh thành ảnh động WEBP hoặc trích xuất khung hình từ hoạt ảnh WEBP nhất định WEBP vào hình ảnh Chuyển đổi tập tin WEBP thành hàng loạt hình ảnh Chuyển đổi hàng loạt hình ảnh thành tệp WEBP Hình ảnh tới WEBP Chọn hình ảnh WEBP để bắt đầu Không có quyền truy cập đầy đủ vào các tập tin Cho phép tất cả các tệp truy cập để xem JXL, QOI và các hình ảnh khác không được nhận dạng là hình ảnh trên Android. Nếu không có sự cho phép Hộp công cụ hình ảnh không thể hiển thị những hình ảnh đó Màu vẽ mặc định Chế độ đường vẽ mặc định Thêm dấu thời gian Cho phép thêm Dấu thời gian vào tên tệp đầu ra Dấu thời gian được định dạng Bật định dạng Dấu thời gian trong tên tệp đầu ra thay vì mili cơ bản Bật Dấu thời gian để chọn định dạng của chúng Vị trí lưu một lần Xem và chỉnh sửa các vị trí lưu một lần mà bạn có thể sử dụng bằng cách nhấn và giữ nút lưu trong hầu hết tất cả các tùy chọn Được sử dụng gần đây kênh CI Nhóm Hộp công cụ hình ảnh trong Telegram 🎉 Tham gia cuộc trò chuyện của chúng tôi, nơi bạn có thể thảo luận bất cứ điều gì bạn muốn và cũng có thể xem kênh CI nơi tôi đăng bản beta và thông báo Nhận thông báo về các phiên bản mới của ứng dụng và đọc thông báo Điều chỉnh hình ảnh theo kích thước nhất định và áp dụng độ mờ hoặc màu cho nền Sắp xếp công cụ Nhóm công cụ theo loại Nhóm các công cụ trên màn hình chính theo loại thay vì sắp xếp danh sách tùy chỉnh Giá trị mặc định Hiển thị thanh hệ thống Hiển thị thanh hệ thống bằng cách vuốt Cho phép vuốt để hiển thị thanh hệ thống nếu chúng bị ẩn Tự động Ẩn tất cả Hiển thị tất cả Ẩn thanh điều hướng Ẩn thanh trạng thái Tạo tiếng ồn Tạo ra các tiếng ồn khác nhau như Perlin hoặc các loại khác Tính thường xuyên Loại tiếng ồn Kiểu xoay Loại phân dạng Quãng tám Thiếu sót Nhận được Sức mạnh có trọng số Sức mạnh bóng bàn Hàm khoảng cách Kiểu trả về Giật giật Biến dạng tên miền Căn chỉnh Tên tệp tùy chỉnh Chọn vị trí và tên tệp sẽ được sử dụng để lưu hình ảnh hiện tại Đã lưu vào thư mục có tên tùy chỉnh Trình tạo ảnh ghép Tạo ảnh ghép từ tối đa 20 hình ảnh Loại ảnh ghép Giữ hình ảnh để hoán đổi, di chuyển và thu phóng để điều chỉnh vị trí Vô hiệu hóa xoay Ngăn chặn xoay hình ảnh bằng cử chỉ hai ngón tay Cho phép chụp nhanh vào đường viền Sau khi di chuyển hoặc zoom, hình ảnh sẽ chụp nhanh để lấp đầy các cạnh khung biểu đồ Biểu đồ hình ảnh RGB hoặc Độ sáng để giúp bạn điều chỉnh Hình ảnh này sẽ được sử dụng để tạo biểu đồ RGB và Độ sáng Tùy chọn Tesseract Áp dụng một số biến đầu vào cho công cụ tesseract Tùy chọn tùy chỉnh Các tùy chọn phải được nhập theo mẫu sau: \"--{option_name} {value}\" Tự động cắt Góc miễn phí Cắt hình ảnh theo đa giác, điều này cũng điều chỉnh phối cảnh Buộc trỏ vào giới hạn hình ảnh Các điểm sẽ không bị giới hạn bởi giới hạn hình ảnh, điều này hữu ích để điều chỉnh phối cảnh chính xác hơn Mặt nạ Nội dung nhận biết điền vào đường dẫn đã vẽ Chữa lành vết thương Sử dụng hạt nhân vòng tròn Khai mạc Đóng cửa Độ dốc hình thái Mũ chóp Mũ đen Đường cong giai điệu Đặt lại đường cong Đường cong sẽ được khôi phục về giá trị mặc định Kiểu đường Kích thước khoảng cách nét đứt Dấu chấm đứt nét đóng dấu ngoằn ngoèo Vẽ đường đứt nét dọc theo đường dẫn đã vẽ với kích thước khoảng cách được chỉ định Vẽ dấu chấm và đường đứt nét dọc theo đường dẫn đã cho Chỉ mặc định đường thẳng Vẽ các hình đã chọn dọc theo đường dẫn với khoảng cách được chỉ định Vẽ đường ngoằn ngoèo lượn sóng dọc theo đường dẫn Tỷ lệ ngoằn ngoèo Tạo lối tắt Chọn công cụ để ghim Công cụ sẽ được thêm vào màn hình chính của trình khởi chạy dưới dạng lối tắt, hãy sử dụng công cụ này kết hợp với cài đặt \"Bỏ qua việc chọn tệp\" để đạt được hành vi cần thiết Đừng xếp chồng các khung Cho phép loại bỏ các khung hình trước đó để chúng không xếp chồng lên nhau Crossfade Các khung hình sẽ được lồng vào nhau Số lượng khung hình chéo Ngưỡng một Ngưỡng hai Khôn ngoan Gương 101 Làm mờ thu phóng nâng cao Laplacian đơn giản Sobel đơn giản Lưới trợ giúp Hiển thị lưới hỗ trợ phía trên vùng vẽ để giúp thao tác chính xác Màu lưới Chiều rộng ô Chiều cao tế bào Bộ chọn nhỏ gọn Một số điều khiển lựa chọn sẽ sử dụng bố cục nhỏ gọn để chiếm ít không gian hơn Cấp quyền cho máy ảnh trong cài đặt để chụp ảnh Cách trình bày Tiêu đề màn hình chính Hệ số tỷ lệ không đổi (CRF) Giá trị %1$s có nghĩa là nén chậm, dẫn đến kích thước tệp tương đối nhỏ. %2$s có nghĩa là nén nhanh hơn, tạo ra tệp lớn. Thư viện Lut Tải xuống bộ sưu tập LUT mà bạn có thể áp dụng sau khi tải xuống Cập nhật bộ sưu tập LUT (chỉ những cái mới sẽ được xếp hàng đợi) mà bạn có thể áp dụng sau khi tải xuống Thay đổi bản xem trước hình ảnh mặc định cho các bộ lọc Xem trước hình ảnh Trốn Trình diễn Loại thanh trượt Si mê Chất liệu 2 Một thanh trượt trông lạ mắt. Đây là tùy chọn mặc định Thanh trượt Vật liệu 2 Thanh trượt Chất liệu của bạn Áp dụng Nút hộp thoại ở giữa Các nút hộp thoại sẽ được đặt ở giữa thay vì ở bên trái nếu có thể Giấy phép nguồn mở Xem giấy phép của các thư viện nguồn mở được sử dụng trong ứng dụng này Khu vực Lấy mẫu lại bằng cách sử dụng quan hệ vùng pixel. Đây có thể là một phương pháp ưa thích để giảm số thập phân hình ảnh vì nó mang lại kết quả không có hiện tượng moire. Nhưng khi phóng to hình ảnh, nó tương tự như phương pháp \"Gần nhất\". Bật bản đồ giai điệu Đi vào % Không thể truy cập trang web, hãy thử sử dụng VPN hoặc kiểm tra xem url có chính xác không Lớp đánh dấu Chế độ lớp với khả năng tự do đặt hình ảnh, văn bản và hơn thế nữa Chỉnh sửa lớp Lớp trên hình ảnh Sử dụng hình ảnh làm nền và thêm các lớp khác nhau lên trên nó Các lớp trên nền Tương tự như tùy chọn đầu tiên nhưng có màu sắc thay vì hình ảnh bản thử nghiệm Bên cài đặt nhanh Thêm dải nổi ở phía đã chọn trong khi chỉnh sửa hình ảnh, thao tác này sẽ mở cài đặt nhanh khi nhấp vào Xóa lựa chọn Nhóm cài đặt \"%1$s\" sẽ được thu gọn theo mặc định Nhóm cài đặt \"%1$s\" sẽ được mở rộng theo mặc định Công cụ Base64 Giải mã chuỗi Base64 thành hình ảnh hoặc mã hóa hình ảnh sang định dạng Base64 cơ sở64 Giá trị được cung cấp không phải là chuỗi Base64 hợp lệ Không thể sao chép chuỗi Base64 trống hoặc không hợp lệ Dán Base64 Sao chép Base64 Tải hình ảnh để sao chép hoặc lưu chuỗi Base64. Nếu bạn có chính chuỗi đó, bạn có thể dán nó lên trên để có được hình ảnh Lưu Base64 Chia sẻ Base64 Tùy chọn hành động Cơ sở nhập khẩu64 Hành động Base64 Thêm Đề cương Thêm đường viền xung quanh văn bản với màu sắc và chiều rộng được chỉ định Màu phác thảo Kích thước phác thảo Xoay Tổng kiểm tra dưới dạng tên tệp Hình ảnh đầu ra sẽ có tên tương ứng với tổng kiểm tra dữ liệu của chúng Phần mềm miễn phí (Đối tác) Thêm nhiều phần mềm hữu ích trên kênh đối tác ứng dụng Android Thuật toán Công cụ kiểm tra tổng So sánh tổng kiểm tra, tính toán giá trị băm hoặc tạo chuỗi hex từ các tệp bằng các thuật toán băm khác nhau Tính toán Băm văn bản Tổng kiểm tra Chọn tệp để tính tổng kiểm tra dựa trên thuật toán đã chọn Nhập văn bản để tính tổng kiểm tra dựa trên thuật toán đã chọn Tổng kiểm tra nguồn Tổng kiểm tra để so sánh Cuộc thi đấu! Sự khác biệt Tổng kiểm tra bằng nhau, nó có thể an toàn Tổng kiểm tra không bằng nhau, tệp có thể không an toàn! Độ dốc lưới Xem bộ sưu tập trực tuyến của Mesh Gradents Chỉ có thể nhập phông chữ TTF và OTF Nhập phông chữ (TTF/OTF) Xuất phông chữ Phông chữ đã nhập Lỗi khi lưu lần thử, hãy thử thay đổi thư mục đầu ra Tên tệp chưa được đặt Không có Trang tùy chỉnh Lựa chọn trang Xác nhận thoát công cụ Nếu bạn có những thay đổi chưa được lưu trong khi sử dụng các công cụ cụ thể và cố gắng đóng nó, thì hộp thoại xác nhận sẽ hiện Chỉnh sửa EXIF Thay đổi siêu dữ liệu của một hình ảnh mà không cần nén lại Nhấn để chỉnh sửa các thẻ có sẵn Thay đổi nhãn dán Vừa chiều rộng Chiều cao phù hợp So sánh hàng loạt Chọn tệp/các tệp để tính tổng kiểm tra dựa trên thuật toán đã chọn Chọn tập tin Chọn thư mục Thang đo chiều dài đầu Con tem Dấu thời gian Mẫu định dạng Phần đệm Cắt ảnh Cắt phần ảnh và ghép phần bên trái (có thể nghịch đảo) theo đường dọc hoặc ngang Đường trục dọc Đường trục ngang Lựa chọn nghịch đảo Phần cắt dọc sẽ được tách rời, thay vì ghép các phần xung quanh vùng cắt Phần cắt ngang sẽ được tách rời, thay vì ghép các phần xung quanh vùng cắt Bộ sưu tập các gradient lưới Tạo gradient lưới với số lượng nút thắt và độ phân giải tùy chỉnh Lớp phủ gradient lưới Soạn gradient lưới ở trên cùng của hình ảnh đã cho Tùy chỉnh điểm Kích thước lưới Độ phân giải X Độ phân giải Y Nghị quyết Pixel theo pixel Màu nổi bật Loại so sánh pixel Quét mã vạch Tỷ lệ chiều cao Loại mã vạch Thực thi B/W Hình ảnh mã vạch sẽ có màu đen trắng hoàn toàn và không được tô màu theo chủ đề của ứng dụng Quét bất kỳ Mã vạch nào (QR, EAN, AZTEC, …) và lấy nội dung của nó hoặc dán văn bản của bạn để tạo mã mới Không tìm thấy mã vạch Mã vạch được tạo sẽ ở đây Bìa âm thanh Trích xuất ảnh bìa album từ file âm thanh, hỗ trợ hầu hết các định dạng phổ biến Chọn âm thanh để bắt đầu Chọn âm thanh Không tìm thấy bìa Gửi nhật ký Nhấp để chia sẻ tệp nhật ký ứng dụng, điều này có thể giúp tôi phát hiện sự cố và khắc phục sự cố Rất tiếc… Đã xảy ra lỗi Bạn có thể liên hệ với tôi bằng các tùy chọn bên dưới và tôi sẽ cố gắng tìm giải pháp.\n(Đừng quên đính kèm nhật ký) Viết vào tập tin Trích xuất văn bản từ hàng loạt hình ảnh và lưu trữ nó trong một tệp văn bản Ghi vào siêu dữ liệu Trích xuất văn bản từ mỗi hình ảnh và đặt nó vào thông tin EXIF của các bức ảnh tương ứng Chế độ ẩn Sử dụng kỹ thuật steganography để tạo hình mờ vô hình trong mắt bên trong byte hình ảnh của bạn Sử dụng LSB Phương pháp steganography LSB (Ít quan trọng hơn) sẽ được sử dụng, FD (Miền tần số) nếu không Tự động loại bỏ mắt đỏ Mật khẩu Mở khóa PDF được bảo vệ Hoạt động gần như hoàn tất. Việc hủy bây giờ sẽ yêu cầu khởi động lại nó Ngày sửa đổi Ngày sửa đổi (Đảo ngược) Kích cỡ Kích thước (Đảo ngược) Loại MIME Loại MIME (Đảo ngược) Sự mở rộng Phần mở rộng (Đảo ngược) Ngày thêm Ngày thêm (Đảo ngược) Trái sang Phải Phải sang trái Từ trên xuống dưới Từ dưới lên trên Thủy tinh lỏng Một switch dựa trên iOS 26 được công bố gần đây và hệ thống thiết kế kính lỏng của nó Chọn hình ảnh hoặc dán/nhập dữ liệu Base64 bên dưới Nhập liên kết hình ảnh để bắt đầu Dán liên kết kính vạn hoa Góc phụ bên Trộn kênh Lục lam Đỏ xanh Xanh đỏ Vào màu đỏ Vào màu xanh lá cây Vào màu xanh lục lam Màu đỏ tươi Màu vàng Bán sắc màu đường viền Cấp độ Bù lại Kết tinh Voronoi Hình dạng Kéo dài Tính ngẫu nhiên lốm đốm khuếch tán Chó Bán kính thứ hai Cân bằng Ánh sáng Xoay và véo Chấm điểm Màu viền tọa độ cực Trực tràng sang cực Cực để chỉnh lưu Đảo ngược trong vòng tròn Giảm tiếng ồn Năng lượng mặt trời đơn giản Dệt Khoảng cách X Khoảng cách Y Chiều rộng X Chiều rộng Y Xoay tròn Con dấu cao su bôi nhọ Tỉ trọng Trộn Biến dạng thấu kính hình cầu chỉ số khúc xạ vòng cung Góc trải rộng lấp lánh Tia ASCII Độ dốc Mary Mùa thu Xương Máy bay phản lực Mùa đông Đại dương Mùa hè Mùa xuân Biến thể thú vị HSV Hồng Nóng Từ dung nham địa ngục Huyết tương viridis Công dân Chạng vạng Hoàng hôn đã thay đổi Phối cảnh tự động nghiêng Cho phép cắt Cắt xén hoặc phối cảnh tuyệt đối tăng áp Màu xanh đậm Hiệu chỉnh ống kính Tệp hồ sơ ống kính mục tiêu ở định dạng JSON Tải xuống hồ sơ ống kính sẵn sàng Phần phần trăm Xuất dưới dạng JSON Sao chép chuỗi có dữ liệu bảng màu dưới dạng biểu diễn json Khắc đường may Màn hình chính Màn hình khóa Tích hợp sẵn Xuất hình nền Làm cho khỏe lại Lấy hình nền Home, Lock và Built-in hiện tại Cho phép truy cập vào tất cả các tập tin, điều này là cần thiết để lấy hình nền Quyền quản lý bộ nhớ ngoài là chưa đủ, bạn cần cho phép truy cập vào hình ảnh của mình, đảm bảo chọn \"Cho phép tất cả\" Thêm cài đặt sẵn vào tên tệp Nối hậu tố với giá trị đặt trước đã chọn vào tên tệp hình ảnh Thêm chế độ tỷ lệ hình ảnh vào tên tệp Nối hậu tố với chế độ tỷ lệ hình ảnh đã chọn vào tên tệp hình ảnh Nghệ thuật ASCII Chuyển đổi hình ảnh thành văn bản ASCII trông giống như hình ảnh Thông số Áp dụng bộ lọc âm cho hình ảnh để có kết quả tốt hơn trong một số trường hợp Đang xử lý ảnh chụp màn hình Chưa chụp được ảnh màn hình, hãy thử lại Đã bỏ qua quá trình lưu %1$s tệp bị bỏ qua Cho phép bỏ qua nếu lớn hơn Một số công cụ sẽ được phép bỏ qua việc lưu hình ảnh nếu kích thước tệp kết quả lớn hơn bản gốc Sự kiện lịch Liên hệ E-mail Vị trí Điện thoại Chữ tin nhắn SMS URL Wi-Fi Mạng mở không áp dụng SSID Điện thoại Tin nhắn Địa chỉ Chủ thể Thân hình Tên Tổ chức Tiêu đề Điện thoại Email URL Địa chỉ Bản tóm tắt Sự miêu tả Vị trí Người tổ chức Ngày bắt đầu Ngày kết thúc Trạng thái Vĩ độ Kinh độ Tạo mã vạch Chỉnh sửa mã vạch cấu hình Wi-Fi Bảo vệ Chọn liên hệ Cấp quyền cho liên hệ trong cài đặt để tự động điền bằng liên hệ đã chọn Thông tin liên hệ Tên Tên đệm Họ Cách phát âm Thêm điện thoại Thêm email Thêm địa chỉ Trang web Thêm trang web Tên được định dạng Hình ảnh này sẽ được sử dụng để đặt phía trên mã vạch Tùy chỉnh mã Hình ảnh này sẽ được dùng làm logo ở giữa mã QR biểu tượng Phần đệm logo Kích thước biểu tượng Góc logo Con mắt thứ tư Thêm tính đối xứng của mắt vào mã qr bằng cách thêm con mắt thứ tư ở góc cuối cùng Hình dạng pixel Hình dạng khung Hình dạng quả bóng Mức độ sửa lỗi Màu tối Màu sáng siêu hệ điều hành Phong cách giống Xiaomi HyperOS Mẫu mặt nạ Mã này có thể không quét được, hãy thay đổi các thông số về giao diện để có thể đọc được trên tất cả các thiết bị Không thể quét được Công cụ sẽ trông giống như trình khởi chạy ứng dụng trên màn hình chính để nhỏ gọn hơn Chế độ trình khởi chạy Đổ đầy vùng bằng cọ và kiểu đã chọn Lũ lụt Xịt Vẽ đường dẫn theo phong cách graffity hạt vuông Hạt phun sẽ có hình vuông thay vì hình tròn Công cụ bảng màu Tạo bảng màu cơ bản/chất liệu từ hình ảnh hoặc nhập/xuất trên các định dạng bảng màu khác nhau Chỉnh sửa bảng màu Xuất/nhập bảng màu trên nhiều định dạng khác nhau Tên màu Tên bảng màu Định dạng bảng màu Xuất bảng màu được tạo sang các định dạng khác nhau Thêm màu mới vào bảng màu hiện tại Định dạng %1$s không hỗ trợ cung cấp tên bảng màu Do chính sách của Cửa hàng Play, tính năng này không thể được đưa vào bản dựng hiện tại. Để truy cập chức năng này, vui lòng tải xuống ImageToolbox từ một nguồn thay thế. Bạn có thể tìm thấy các bản dựng có sẵn trên GitHub bên dưới. Mở trang Github Tệp gốc sẽ được thay thế bằng tệp mới thay vì lưu vào thư mục đã chọn Đã phát hiện văn bản hình mờ ẩn Đã phát hiện hình ảnh mờ ẩn Hình ảnh này đã bị ẩn Inpainting sáng tạo Cho phép bạn xóa các đối tượng trong hình ảnh bằng mô hình AI mà không cần dựa vào OpenCV. Để sử dụng tính năng này, ứng dụng sẽ tải xuống mô hình cần thiết (~200 MB) từ GitHub Cho phép bạn xóa các đối tượng trong hình ảnh bằng mô hình AI mà không cần dựa vào OpenCV. Đây có thể là một hoạt động kéo dài Phân tích mức độ lỗi Độ sáng độ sáng Khoảng cách trung bình Sao chép phát hiện di chuyển Giữ lại hệ số Dữ liệu bảng nhớ tạm quá lớn Dữ liệu quá lớn để sao chép Pixel hóa dệt đơn giản Pixelization so le Pixel hóa chéo Pixel hóa vi mô Pixel hóa quỹ đạo Pixel hóa xoáy Pixel hóa lưới xung Pixel hóa hạt nhân Pixel hóa xuyên tâm Không thể mở uri \"%1$s\" Chế độ tuyết rơi Đã bật Khung viền Biến thể trục trặc Chuyển kênh Bù tối đa VHS Chặn trục trặc Kích thước khối độ cong CRT độ cong sắc độ Điểm ảnh tan chảy Giảm tối đa Công cụ AI Nhiều công cụ khác nhau để xử lý hình ảnh thông qua các mô hình ai như loại bỏ hoặc khử nhiễu Đường nén, răng cưa Phim hoạt hình, nén phát sóng Nén chung, nhiễu chung Tiếng ồn hoạt hình không màu Nhanh, nén chung, nhiễu chung, hoạt hình/truyện tranh/anime Quét sách Chỉnh sửa phơi sáng Tốt nhất ở khả năng nén chung, hình ảnh màu Tốt nhất ở khả năng nén chung, hình ảnh thang độ xám Nén chung, hình ảnh thang độ xám, mạnh hơn Nhiễu chung, hình ảnh màu Nhiễu chung, hình ảnh màu sắc, chi tiết tốt hơn Nhiễu chung, hình ảnh thang độ xám Nhiễu chung, hình ảnh thang độ xám, mạnh hơn Nhiễu chung, hình ảnh thang độ xám, mạnh nhất Nén chung Nén chung Kết cấu, nén h264 nén VHS Nén không chuẩn (cinepak, msvideo1, roq) Nén Bink, tốt hơn về hình học Nén Bink, mạnh mẽ hơn Nén Bink, mềm mại, giữ lại chi tiết Loại bỏ hiệu ứng bậc thang, làm mịn Nghệ thuật/bản vẽ được quét, nén nhẹ, moire Dải màu Chậm, loại bỏ ảnh bán sắc Bộ tạo màu chung cho hình ảnh thang độ xám/bw, để có kết quả tốt hơn, hãy sử dụng DDColor Loại bỏ cạnh Loại bỏ hiện tượng sắc nét quá mức Chậm, phối màu Khử răng cưa, tạo tác chung, CGI Xử lý quét KDM003 Mô hình nâng cao hình ảnh nhẹ Loại bỏ tạo tác nén Loại bỏ tạo tác nén Loại bỏ băng với kết quả mịn màng Xử lý mẫu bán sắc Loại bỏ mô hình hoà sắc V3 Loại bỏ tạo tác JPEG V2 Cải tiến kết cấu H.264 Làm sắc nét và nâng cao VHS Sáp nhập Kích thước đoạn Kích thước chồng chéo Hình ảnh trên %1$s px sẽ được cắt và xử lý thành nhiều phần, chồng chéo các phần này để tránh nhìn thấy các đường nối. Kích thước lớn có thể gây mất ổn định với thiết bị cấp thấp Chọn một để bắt đầu Bạn có muốn xóa mô hình %1$s không? Bạn sẽ cần phải tải xuống lại Xác nhận Người mẫu Mô hình đã tải xuống Các mẫu có sẵn Chuẩn bị Mô hình hoạt động Không mở được phiên Chỉ có thể nhập các mô hình .onnx/.ort Mô hình nhập khẩu Nhập mô hình onnx tùy chỉnh để sử dụng tiếp, chỉ các mô hình onnx/ort mới được chấp nhận, hỗ trợ hầu hết tất cả các biến thể giống như esrgan Model nhập khẩu Tiếng ồn chung, hình ảnh màu Nhiễu chung, hình ảnh có màu sắc, mạnh mẽ hơn Nhiễu chung, hình ảnh màu, mạnh nhất Giảm hiện tượng phối màu và dải màu, cải thiện độ chuyển màu mượt mà và vùng màu phẳng. Tăng cường độ sáng và độ tương phản của hình ảnh với các điểm sáng cân bằng trong khi vẫn giữ được màu sắc tự nhiên. Làm sáng hình ảnh tối trong khi vẫn giữ được chi tiết và tránh phơi sáng quá mức. Loại bỏ tông màu quá mức và khôi phục lại sự cân bằng màu sắc trung tính và tự nhiên hơn. Áp dụng giảm nhiễu dựa trên Poisson với sự nhấn mạnh vào việc giữ nguyên các chi tiết và kết cấu đẹp. Áp dụng giảm nhiễu Poisson mềm mại để mang lại kết quả hình ảnh mượt mà hơn và ít hung hãn hơn. Giảm nhiễu đồng đều tập trung vào việc bảo toàn chi tiết và độ rõ nét của hình ảnh. Giảm tiếng ồn đồng đều nhẹ nhàng cho kết cấu tinh tế và vẻ ngoài mịn màng. Sửa chữa các khu vực bị hư hỏng hoặc không bằng phẳng bằng cách sơn lại các đồ tạo tác và cải thiện tính nhất quán của hình ảnh. Mô hình tháo gỡ nhẹ giúp loại bỏ dải màu với chi phí hiệu suất tối thiểu. Tối ưu hóa hình ảnh có độ nén rất cao (chất lượng 0-20%) để cải thiện độ rõ nét. Nâng cao hình ảnh với các tạo tác nén cao (chất lượng 20-40%), khôi phục chi tiết và giảm nhiễu. Cải thiện hình ảnh với độ nén vừa phải (chất lượng 40-60%), cân bằng độ sắc nét và mượt mà. Tinh chỉnh hình ảnh bằng cách nén nhẹ (chất lượng 60-80%) để nâng cao các chi tiết và kết cấu tinh tế. Tăng cường một chút hình ảnh gần như không bị mất chất lượng (chất lượng 80-100%) trong khi vẫn giữ được vẻ tự nhiên và chi tiết. Tô màu đơn giản và nhanh chóng, phim hoạt hình, không lý tưởng Giảm nhẹ độ mờ của hình ảnh, cải thiện độ sắc nét mà không gây hiện tượng giả tạo. Hoạt động chạy dài Đang xử lý hình ảnh Xử lý Loại bỏ các thành phần nén JPEG nặng ở hình ảnh có chất lượng rất thấp (0-20%). Giảm hiện vật JPEG mạnh ở hình ảnh có độ nén cao (20-40%). Dọn dẹp các hiện vật JPEG vừa phải trong khi vẫn giữ được chi tiết hình ảnh (40-60%). Tinh chỉnh các tạo tác JPEG nhẹ ở hình ảnh chất lượng khá cao (60-80%). Giảm một cách tinh tế các hiện vật JPEG nhỏ trong hình ảnh gần như không mất dữ liệu (80-100%). Tăng cường các chi tiết và kết cấu đẹp mắt, cải thiện độ sắc nét cảm nhận được mà không tạo ra hiện tượng giả tạo nặng nề. Xử lý xong Xử lý không thành công Tăng cường kết cấu và chi tiết da trong khi vẫn giữ vẻ tự nhiên, tối ưu hóa tốc độ. Loại bỏ các thành phần nén JPEG và khôi phục chất lượng hình ảnh cho ảnh nén. Giảm nhiễu ISO trong ảnh chụp trong điều kiện ánh sáng yếu, giữ nguyên chi tiết. Chỉnh sửa các điểm nổi bật bị phơi sáng quá mức hoặc “jumbo” và khôi phục lại sự cân bằng tông màu tốt hơn. Mô hình tô màu nhẹ và nhanh giúp bổ sung màu sắc tự nhiên cho hình ảnh thang độ xám. DEJPEG Khử nhiễu Tô màu Hiện vật Nâng cao Anime Quét cao cấp Trình nâng cấp X4 cho hình ảnh chung; mô hình nhỏ sử dụng ít GPU và thời gian hơn, với khả năng khử nhiễu và làm mờ vừa phải. Bộ nâng cấp X2 cho hình ảnh tổng thể, giữ nguyên kết cấu và chi tiết tự nhiên. Trình nâng cấp X4 cho hình ảnh chung với kết cấu nâng cao và kết quả chân thực. Trình nâng cấp X4 được tối ưu hóa cho hình ảnh anime; 6 khối RRDB cho đường nét và chi tiết sắc nét hơn. Bộ nâng cấp X4 với tính năng mất MSE, tạo ra kết quả mượt mà hơn và giảm hiện vật giả cho hình ảnh thông thường. X4 Upscaler được tối ưu hóa cho hình ảnh anime; Biến thể 4B32F với các chi tiết sắc nét hơn và đường nét mượt mà hơn. Model X4 UltraSharp V2 cho hình ảnh thông thường; nhấn mạnh độ sắc nét và rõ ràng. X4 UltraSharp V2 Lite; nhanh hơn và nhỏ hơn, bảo toàn chi tiết trong khi sử dụng ít bộ nhớ GPU hơn. Mô hình nhẹ để loại bỏ nền nhanh chóng. Hiệu suất cân bằng và độ chính xác. Hoạt động với chân dung, đồ vật và cảnh. Được đề xuất cho hầu hết các trường hợp sử dụng. Xóa BG Độ dày viền ngang Độ dày viền dọc %1$s màu sắc Model hiện tại không hỗ trợ chunking, hình ảnh sẽ được xử lý ở kích thước gốc, điều này có thể gây ra mức tiêu thụ bộ nhớ cao và sự cố với các thiết bị cấp thấp Tính năng phân đoạn bị tắt, hình ảnh sẽ được xử lý ở kích thước ban đầu, điều này có thể gây ra mức tiêu thụ bộ nhớ cao và sự cố với các thiết bị cấp thấp nhưng có thể cho kết quả suy luận tốt hơn Cắt nhỏ Mô hình phân đoạn hình ảnh có độ chính xác cao để loại bỏ nền Phiên bản nhẹ của U2Net giúp xóa nền nhanh hơn với mức sử dụng bộ nhớ nhỏ hơn. Mô hình DDColor đầy đủ mang lại màu sắc chất lượng cao cho hình ảnh thông thường với ít hiện tượng giả mạo nhất. Sự lựa chọn tốt nhất trong tất cả các mô hình tô màu. DDColor Bộ dữ liệu nghệ thuật riêng tư và được đào tạo; tạo ra kết quả tô màu đa dạng và nghệ thuật với ít hiện vật màu phi thực tế hơn. Mô hình BiRefNet nhẹ dựa trên Swin Transformer để loại bỏ nền chính xác. Loại bỏ nền chất lượng cao với các cạnh sắc nét và bảo toàn chi tiết tuyệt vời, đặc biệt là trên các đối tượng phức tạp và nền phức tạp. Mô hình xóa nền tạo ra mặt nạ chính xác với các cạnh mịn, phù hợp với các đối tượng thông thường và bảo toàn chi tiết vừa phải. Mô hình đã được tải xuống Đã nhập mô hình thành công Kiểu Từ khóa Rất nhanh Bình thường Chậm Rất chậm Tính phần trăm Giá trị tối thiểu là %1$s Làm biến dạng hình ảnh bằng cách vẽ bằng ngón tay Làm cong vênh độ cứng Chế độ dọc Di chuyển Phát triển Thu nhỏ Xoáy CW xoáy CCW Sức mạnh phai nhạt Thả hàng đầu Thả dưới cùng Bắt đầu thả Thả cuối Đang tải xuống Hình dạng mượt mà Sử dụng hình siêu elip thay vì hình chữ nhật bo tròn tiêu chuẩn để có hình dạng mượt mà, tự nhiên hơn Loại hình dạng Cắt làm tròn Trơn tru Các cạnh sắc nét mà không làm tròn Các góc bo tròn cổ điển Loại hình dạng Kích thước góc hình tròn Các thành phần UI được bo tròn trang nhã Định dạng tên tệp Văn bản tùy chỉnh được đặt ở đầu tên tệp, hoàn hảo cho tên dự án, nhãn hiệu hoặc thẻ cá nhân. Sử dụng tên tệp gốc không có phần mở rộng, giúp bạn giữ nguyên nhận dạng nguồn. Chiều rộng hình ảnh tính bằng pixel, hữu ích để theo dõi các thay đổi về độ phân giải hoặc kết quả chia tỷ lệ. Chiều cao hình ảnh tính bằng pixel, hữu ích khi làm việc với tỷ lệ khung hình hoặc xuất. Tạo các chữ số ngẫu nhiên để đảm bảo tên tệp duy nhất; thêm nhiều chữ số để tăng cường an toàn chống lại sự trùng lặp. Bộ đếm tăng tự động để xuất hàng loạt, lý tưởng khi lưu nhiều hình ảnh trong một phiên. Chèn tên đặt trước đã áp dụng vào tên tệp để bạn có thể dễ dàng nhớ cách xử lý hình ảnh. Hiển thị chế độ chia tỷ lệ hình ảnh được sử dụng trong quá trình xử lý, giúp phân biệt hình ảnh đã thay đổi kích thước, cắt xén hoặc vừa vặn. Văn bản tùy chỉnh được đặt ở cuối tên tệp, hữu ích cho việc lập phiên bản như _v2, _edited hoặc _final. Phần mở rộng tệp (png, jpg, webp, v.v.), tự động khớp với định dạng đã lưu thực tế. Một dấu thời gian có thể tùy chỉnh cho phép bạn xác định định dạng riêng theo đặc tả Java để sắp xếp hoàn hảo. Loại ném Android gốc Phong cách iOS Đường cong mượt mà Dừng nhanh nảy nổi nhanh nhẹn siêu mịn Thích ứng Nhận thức về khả năng truy cập Giảm chuyển động Vật lý cuộn Android gốc Cuộn cân bằng, mượt mà để sử dụng chung Hành vi cuộn giống iOS có độ ma sát cao hơn Đường cong spline độc đáo cho cảm giác cuộn khác biệt Cuộn chính xác với tính năng dừng nhanh Cuộn nảy vui tươi, nhạy bén Cuộn dài, lướt để duyệt nội dung Cuộn nhanh, nhạy cho giao diện người dùng tương tác Cuộn mượt mà cao cấp với động lượng kéo dài Điều chỉnh vật lý dựa trên tốc độ ném Tôn trọng cài đặt khả năng truy cập của hệ thống Chuyển động tối thiểu cho nhu cầu tiếp cận Dòng chính Thêm dòng dày hơn vào mỗi dòng thứ năm Tô màu Công cụ ẩn Công cụ ẩn để chia sẻ Thư viện màu Duyệt qua một bộ sưu tập lớn các màu sắc Làm sắc nét và xóa mờ khỏi hình ảnh trong khi vẫn duy trì các chi tiết tự nhiên, lý tưởng để sửa ảnh mất nét. Khôi phục thông minh các hình ảnh đã được thay đổi kích thước trước đó, khôi phục các chi tiết và kết cấu bị mất. Được tối ưu hóa cho nội dung người thật đóng, giảm hiện tượng nén giả và tăng cường chi tiết đẹp trong khung hình phim/chương trình truyền hình. Chuyển đổi cảnh quay chất lượng VHS thành HD, loại bỏ nhiễu băng và nâng cao độ phân giải trong khi vẫn giữ được cảm giác cổ điển. Chuyên dùng cho hình ảnh và ảnh chụp màn hình có nhiều văn bản, làm sắc nét các ký tự và cải thiện khả năng đọc. Nâng cấp nâng cao được đào tạo trên các bộ dữ liệu đa dạng, tuyệt vời để cải thiện ảnh cho mục đích chung. Tối ưu hóa cho ảnh nén trên web, loại bỏ các thành phần giả JPEG và khôi phục hình thức tự nhiên. Phiên bản cải tiến dành cho ảnh trên web với khả năng bảo toàn kết cấu và giảm hiện vật tốt hơn. Nâng cấp gấp 2 lần với công nghệ Biến áp tập hợp kép, duy trì độ sắc nét và chi tiết tự nhiên. Nâng kích thước 3x sử dụng kiến trúc biến áp tiên tiến, lý tưởng cho nhu cầu phóng to vừa phải. Nâng cấp chất lượng cao gấp 4 lần với mạng biến áp hiện đại, bảo toàn các chi tiết đẹp ở quy mô lớn hơn. Loại bỏ hiện tượng mờ/nhiễu và rung lắc khỏi ảnh. Mục đích chung nhưng tốt nhất trên ảnh. Khôi phục hình ảnh chất lượng thấp bằng biến áp Swin2SR, được tối ưu hóa cho sự suy giảm BSRGAN. Tuyệt vời để sửa các hiện vật nén nặng và nâng cao chi tiết ở tỷ lệ 4x. Nâng cấp gấp 4 lần với máy biến áp SwinIR được đào tạo về suy giảm BSRGAN. Sử dụng GAN để có kết cấu sắc nét hơn và nhiều chi tiết tự nhiên hơn trong ảnh và cảnh phức tạp. Con đường Hợp nhất PDF Kết hợp nhiều tệp PDF vào một tài liệu Thứ tự tập tin trang. Tách PDF Trích xuất các trang cụ thể từ tài liệu PDF Xoay PDF Sửa hướng trang vĩnh viễn Trang Sắp xếp lại PDF Kéo và thả các trang để sắp xếp lại chúng Giữ và kéo trang Số trang Tự động thêm đánh số vào tài liệu của bạn Định dạng nhãn PDF sang văn bản (OCR) Trích xuất văn bản thuần túy từ tài liệu PDF của bạn Lớp phủ văn bản tùy chỉnh để xây dựng thương hiệu hoặc bảo mật Chữ ký Thêm chữ ký điện tử của bạn vào bất kỳ tài liệu nào Điều này sẽ được sử dụng làm chữ ký Mở khóa PDF Xóa mật khẩu khỏi các tệp được bảo vệ của bạn Bảo vệ PDF Bảo mật tài liệu của bạn bằng mã hóa mạnh mẽ Thành công Đã mở khóa PDF, bạn có thể lưu hoặc chia sẻ nó Sửa chữa PDF Cố gắng sửa các tài liệu bị hỏng hoặc không thể đọc được Thang độ xám Chuyển đổi tất cả các hình ảnh được nhúng trong tài liệu sang thang độ xám Nén PDF Tối ưu hóa kích thước tệp tài liệu của bạn để chia sẻ dễ dàng hơn ImageToolbox xây dựng lại bảng tham chiếu chéo nội bộ và tạo lại cấu trúc tệp từ đầu. Điều này có thể khôi phục quyền truy cập vào nhiều tệp \\"không thể mở được\\" Công cụ này chuyển đổi tất cả hình ảnh tài liệu sang thang độ xám. Tốt nhất để in và giảm kích thước tập tin Siêu dữ liệu Chỉnh sửa thuộc tính tài liệu để bảo mật tốt hơn Thẻ Nhà sản xuất Tác giả Từ khóa Người sáng tạo Quyền riêng tư Sạch sâu Xóa tất cả siêu dữ liệu có sẵn cho tài liệu này Trang OCR sâu Trích xuất văn bản từ tài liệu và lưu trữ nó trong một tệp văn bản bằng công cụ Tesseract Không thể xóa tất cả các trang Xóa các trang PDF Xóa các trang cụ thể khỏi tài liệu PDF Nhấn để xóa thủ công Cắt PDF Cắt các trang tài liệu theo bất kỳ giới hạn nào Làm phẳng PDF Làm cho PDF không thể sửa đổi bằng cách rastering các trang tài liệu Không thể khởi động máy ảnh. Vui lòng kiểm tra quyền và đảm bảo rằng nó không được ứng dụng khác sử dụng. Trích xuất hình ảnh Trích xuất hình ảnh được nhúng trong tệp PDF ở độ phân giải gốc Tệp PDF này không chứa bất kỳ hình ảnh nhúng nào Công cụ này quét mọi trang và khôi phục hình ảnh nguồn có chất lượng đầy đủ — hoàn hảo để lưu bản gốc từ tài liệu Vẽ chữ ký Thông số bút Sử dụng chữ ký của chính mình làm hình ảnh để đặt trên tài liệu Nén PDF Chia tài liệu theo khoảng thời gian nhất định và đóng gói tài liệu mới vào kho lưu trữ zip Khoảng thời gian In PDF Chuẩn bị tài liệu để in với kích thước trang tùy chỉnh Số trang trên mỗi tờ Định hướng Kích thước trang Lề Hoa Đầu gối mềm Tối ưu hóa cho anime và phim hoạt hình. Nâng cấp nhanh chóng với màu sắc tự nhiên được cải thiện và ít hiện vật hơn Phong cách giống Samsung One UI 7 Nhập các ký hiệu toán học cơ bản vào đây để tính giá trị mong muốn (ví dụ: (5+5)*10) biểu thức toán học Chọn tối đa %1$s hình ảnh Giữ ngày giờ Luôn lưu giữ các thẻ Exif liên quan đến ngày và giờ, hoạt động độc lập với tùy chọn giữ nguyên Màu nền cho định dạng Alpha Thêm khả năng đặt màu nền cho mọi định dạng hình ảnh có hỗ trợ alpha, khi bị tắt, tính năng này chỉ khả dụng cho những định dạng không phải alpha Dự án mở Tiếp tục chỉnh sửa dự án Hộp công cụ Hình ảnh đã lưu trước đó Không thể mở dự án Hộp công cụ hình ảnh Dự án Hộp công cụ hình ảnh thiếu dữ liệu dự án Dự án Hộp công cụ Hình ảnh bị hỏng Phiên bản dự án Hộp công cụ Hình ảnh không được hỗ trợ: %1$d Lưu dự án Lưu trữ các lớp, nền và lịch sử chỉnh sửa trong một tệp dự án có thể chỉnh sửa Không thể mở được Viết vào PDF có thể tìm kiếm Nhận dạng văn bản từ hàng loạt hình ảnh và lưu tệp PDF có thể tìm kiếm bằng hình ảnh và lớp văn bản có thể chọn Lớp alpha Lật ngang Lật dọc Khóa Thêm bóng Màu bóng Hình học văn bản Kéo dài hoặc nghiêng văn bản để cách điệu sắc nét hơn Thang đo X nghiêng X Xóa chú thích Xóa các loại chú thích đã chọn như liên kết, nhận xét, đánh dấu, hình dạng hoặc trường biểu mẫu khỏi trang PDF Siêu liên kết Tệp đính kèm dòng Cửa sổ bật lên Tem Hình dạng Ghi chú văn bản Đánh dấu văn bản Trường biểu mẫu Đánh dấu Không xác định Chú thích Ungroup Thêm bóng mờ phía sau lớp với màu sắc và độ lệch có thể định cấu hình ================================================ FILE: core/resources/src/main/res/values-zh-rCN/strings.xml ================================================ 发生错误 选择图片以开始 关闭 发生错误:%1$s 大小%1$s 正在加载…… 图片太大,无法预览,但仍然会尝试保存 宽%1$s 质量 格式 缩放类型 强制 自适应 关闭应用 您确定要关闭本应用吗? 取消 重置更改 重置 已复制到剪贴板 排除项 编辑EXIF信息 确定 未找到EXIF信息 添加标签 保存 清空EXIF信息 更改已重置 取消 图片的全部EXIF信息都将被擦除,此操作无法撤销! 预设 裁剪 保存 如果现在退出,所有未保存的更改都将丢失 源代码 获得最新更新、讨论问题等 编辑 修改、调整大小和编辑一张图片 取色器 从图片中选取颜色,并复制或分享 图片 颜色 颜色代码已复制 将图片裁剪至任意大小 版本号 保留EXIF信息 图片数量:%d 更换预览图片 移除 生成指定图片的颜色风格 生成调色板 颜色风格 更新 新版本%1$s 不支持的类型:%1$s 无法生成指定图片的颜色风格 原图 输出目录 默认 自定义 未指定 设备存储 按占用大小缩放 最大大小(KB) 按指定大小(KB)缩放图片 对比 对比两张指定的图片 选择两张图片以开始 选择图片 高%1$s 选择图片 图片更改将回到初始值 重启应用 清空 设置 夜间模式 深色 浅色 跟随系统 自定义 允许来自图片的莫奈颜色 如果启用,当你选择一张要编辑的图片时,应用程序的颜色将根据此图片改变 语言 动态颜色 Amoled模式 如果启用,在夜间模式下背景色将设为纯黑 红色 绿色 蓝色 粘贴一个有效的aRGB颜色代码 配色方案 无可粘贴的图片 在开启动态颜色时,无法更改应用程序的颜色方案 应用程序的主题将基于选择的颜色 未找到更新 问题跟踪器 帮助翻译 关于应用 在这里发送错误报告和功能请求 在此搜索 纠正翻译错误或将项目本地化为其他语言 没有发现您所查询的内容 如果启用,则应用颜色将更改为壁纸颜色 无法保存%d张图片 添加 权限 授予 应用程序必须访问您的存储来保存图片。请在接下来的对话框中授予权限 应用程序需要此权限才能工作,请手动授权 边框宽度 表面 该应用程序完全免费。如果你想支持该项目开发,你可以点击这里 FAB对齐方式 检查更新 如果启用,更新对话框将在应用程序启动时显示 主色 第三色 辅助色 图片缩放 前缀 文件名 分享 外部存储 莫奈取色 在主屏幕上选择要显示的表情符号。 表情符号 如果启用,会将保存图片的宽度和高度添加到输出文件的名称中。 添加文件尺寸 删除EXIF信息 删除任意一组图片的EXIF元数据 预览图片 预览任何类型的图片:GIF、SVG等等 颜色滤镜 Alpha 值 色相 扫描线 线框 左侧 右侧 方框模糊 双边模糊 功能管理 加载网络图片 图片链接 亮度 玻璃球面折射 桑原平滑 半径 强度 按限制缩放 在保持纵横比的同时将图片调整为给定的高度和宽度 色调分离 非极大值抑制(NMS) 弱像素包含(WPI) 抬头 堆叠模糊 第二种颜色 重新排序 快速模糊 模糊大小 出现在屏幕底部的安卓新版照片选择器可能只适用于安卓12+系统,它接收EXIF元数据时有问题 使用 GetContent 意图选择图片。它无论在何处都可用,但已知在某些设备上接收选择的图片时会出现问题。这与本APP无关。 编辑 顺序 确定主屏幕上工具的顺序 表情符号数 替换序号 如果启用,则在使用批处理功能时将标准时间戳替换为图片序号 如果选择照片选择器作为图片源,则添加原始文件名不可用 填充 适合 将图片调整为给定的高度和宽度。图片的纵横比可能会改变。 将长边符合给定高度或宽度的图片调整尺寸。所有尺寸计算将在保存后完成。图片的纵横比将保持不变。 对比度 色调 饱和度 添加滤镜 滤镜 应用滤镜链于图像 滤镜 光照 曝光 白平衡 色温 着色 单色 伽马 高光和阴影 高光 阴影 雾气 效果 距离 坡度 锐化 做旧 反色 曝光 黑白 间距 线宽 模糊 半色调 GCA 色彩空间 高斯模糊 浮雕 拉普拉斯过滤 晕光 失真 角度 漩涡扭曲 凸起扭曲 扩张 球面折射 折射率 颜色矩阵 不透明度 草图 临界点 量化级别 平滑色调 卡通化 3x3 卷积矩阵 RGB 滤镜 伪色 第一种颜色 模糊中心 x 模糊中心 y 中心模糊 色彩平衡 亮度阈值 从互联网加载任何图片进行预览,缩放,保存或进一步编辑 图片来源 照片选择器 图库 文件管理器 简单的图库图片选取器。它仅在你有可提供媒体选择的应用程序时可用。 没有图片 内容缩放 序号 原始文件名 添加原始文件名 如果启用,则在输出图片的文件名中添加原始文件名 画板 你禁用了“文件”应用,请先激活该应用以使用该功能。 在图片上素描,或者在纯色背景上绘制 颜料颜色 在图片上绘画 选择一张图片并在上面绘画 水印透明度 在纯色背景上绘制 选择背景色并在该背景上进行绘制 背景色 选择文件 加密 解密 选择文件以开始 解密 加密 密钥 功能 执行 兼容性 AES-256,GCM模式,无填充,默认使用12字节随机IV。您可以选择所需的算法。密钥以256位SHA-3哈希形式使用 文件大小 请注意,我们不保证与其他文件加密软件或服务的兼容性,密钥处理或密码配置稍有不同可能会不兼容。 加密 在这里您可以进行基于密码的文件加密。加密完成的文件可以存储在选定的目录或直接共享,解密后的文件也可以直接打开。 密码无效或选中的文件未加密 已完成的文件 尝试以指定的宽度和高度保存图片可能会导致内存溢出错误。请自行承担风险操作 缓存 缓存区大小 已发现%1$s缓存 自动清理缓存 如果开启该选项,应用缓存将会在应用启动时自动清理 工具 根据功能类型将功能分组 对主屏幕上的选项按选项类型分组,代替按自定义列表排列 启用功能分组时无法更改排列 创作 基于各种可用的加密算法对任何文件(不限于图片)进行加密和解密 将此文件存储在您的设备上,或使用共享操作将其传送至任何位置 最大文件大小受 Android 系统和可用内存的限制,这取决于您的设备。 \n请注意:内存 (RAM) 不是存储 (ROM)。 截屏 跳过 二次定制 后备选项 复制 以%1$s模式保存可能不稳定,因为它是无损格式 编辑截图 纵横比 使用这个遮罩类型从给定的图片创建遮罩,注意它应该有alpha通道 备份和恢复 裁剪遮罩 备份 恢复 将应用程序设置备份到一个文件中 从以前生成的文件中恢复应用程序设置 文件已损坏或不是备份文件 这将把你的设置恢复到默认值。注意,如果之前没有备份文件,则不能撤销。 删除 您即将删除所选配色方案。此操作无法撤消 删除方案 字体 文本 恢复设置成功 联系我 字体大小 默认 使用大字体可能会导致 UI 故障和问题,这是无法修复的。谨慎使用。 随机文件名 如果启用,输出文件名将是完全随机的 如果选择预设值125,图片将保存为原始图片大小的125%。如果选择预设值50,图片将保存为原始图片大小的50% 此处的预设决定输出文件的百分比,例如如果你在5MB的图片上选择预设50,则保存后将得到一张2.5MB的图片 已使用名称%2$s保存到%1$s文件夹 已保存到%1$s文件夹 Telegram 聊天 讨论这款应用程序并从其他用户那里获得反馈。你也可以在那里获得beta版更新和见解。 擦除模式 恢复图片 表情 食物和饮料 自然与动物 物体 旅游和地方 活动 移除背景 通过绘图或使用自动选项从图片中删除背景 符号 启用表情 擦除背景 原始图片元数据将被保留 修剪图片 图片周围的透明空间将被修剪 自动擦除背景 恢复背景 模糊半径 吸管 绘画模式 创建问题 哎呀……软件出错了。你可以使用下面的选项给我留言,我会尽力找到解决办法。 调整大小和转换 更改给定图片的大小或将其转换为其他格式。如果选择单个图片,还可以编辑EXIF元数据 汉字示例 0123456789 !? 更新 最大颜色数 等待 目前,%1$s格式在Android上只允许读取EXIF元数据。在保存时,输出图片将完全没有元数据。 值为%1$s意味着快速压缩,导致文件大小相对较大。 %2$s表示压缩速度较慢,文件较小。 允许收集匿名应用使用统计数据 允许接收测试版 这允许此应用程序自动收集崩溃报告 保存快要完成了。现在取消将需要重新开始保存 分析 压缩尝试次数 如果启用,更新检查时将包括测试版 横向 重新编码 图片顺序 模糊边缘 图片拼接 像素化 增强像素化 目标颜色 绘制箭头 选择至少 2 张图片 像素大小 替换颜色 如果启用,在原始图片下绘制模糊边缘以填充周围的空间,而不是使用单一颜色 移除颜色 如果启用,绘制路径将表示为指向箭头 如果启用,小图片将被缩放到序列中最大的图片 锁定绘制方向 将给定的图片组合成一个大的图片 检查更新 图片方向 纵向 要去除的颜色 要更换的颜色 图片将被中间裁剪到输入的大小。如果图片小于输入的尺寸,画布将以给定的背景颜色展开。 捐赠 输出图片缩放 如果在绘图模式下启用,屏幕将不会旋转 将小尺寸的图片放大至大尺寸 容错度 两个都 如果启用,将主题颜色替换为负值 富达 刷子柔软度 彩虹 有趣的主题 - 源颜色的色调不会出现在主题中 边缘褪色 响亮的主题,主要调色板的色彩度最大,其他调色板的色彩度增加 色彩比单色稍多的风格 此更新检查器将连接到 GitHub,以检查是否有可用的新更新 将源颜色放置在Scheme.primaryContainer中的方案 色调点 钻石像素化 单色主题,颜色纯黑/白/灰 水果沙拉 增强钻石像素化 圆形像素化 与内容方案非常相似的方案 内容 禁用 富有表现力 中性的 默认调色板样式,它允许自定义所有四种颜色,其他允许您仅设置关键颜色 调色板风格 反转颜色 充满活力 笔画像素化 常规 增强圆形像素化 注意力 PDF工具 搜索 图片转PDF 预览PDF 允许在主屏幕上搜索所有可用工具 PDF转图片 打包所选图片输出为PDF文件 简单PDF预览 操作PDF文件:预览、转换成一系列图片或使用所选图片创建PDF文件 设定输出格式的PDF转图片 行数 双线箭头 为您的绘图添加一些发光效果 切换模式 荧光 在开关后方绘制阴影 将从起点到终点的双箭头绘制成一条线 悬浮按钮 从给定路径绘制一个指示箭头 自动旋转 删除遮罩 自由绘制 水平网格 椭圆形轮廓 线 垂直网格 滑块 在容器后方绘制阴影 椭圆形 遮罩滤镜 矩形 列数 绘制从起点到终点的矩形轮廓 允许采用限制框来调整图片方向 套索 模糊绘制路径下的图片,以确保任何您想要隐藏的内容 最简单的默认设置 - 仅颜色 允许在给定的遮蔽区域使用滤镜链,每个遮蔽区域可自行决定滤镜集 直线箭头 反转填充类型 容器 用一条线绘制从起点到终点的路径 滤镜预览 滤镜 您将删除选中的滤镜遮罩。此操作无法撤销 绘制从起点到终点的椭圆形轮廓图 应用程序栏 绘制半透明锐化荧光笔路径 按给定路径绘制封闭填充路径 自由 双箭头 箭头 对给定的单张或多张图片应用任意滤镜链 绘制路径作为输入值 如果启用,将过滤掉所有非屏蔽区域并代替默认行为 左侧 概要 按钮 绘制从起点到终点的矩形 滤镜颜色 在滑块后方绘制阴影 开关按钮 在应用栏后方绘制阴影 %1$s - %2$s范围内的值 将起点到终点的指向箭头绘制成一条线 简单变体 与隐私模糊相似,但像素化而非模糊化 全滤镜 从给定路径绘制双箭头 添加滤镜 右侧 轮廓图 绘制的滤镜遮罩将进行渲染,以显示大致效果 隐私模糊 在悬浮操作按钮后方绘制阴影 从起点到终点绘制椭圆形 遮罩%d 居中 在按钮后方绘制阴影 绘制路径模式 震动 要覆盖文件,您需要使用 \"资源管理器 \"图片源,请尝试重新提供图片,我们已将图片源更改为所需的图片源 如果启用,自动将保存的图片添加至剪贴板 未找到 \"%1$s\"目录,我们已将其切换至默认目录,请重新保存文件 剪切板 原始文件将被新文件替换,而不是保存在选定的文件夹中。此选项要求图片源为「资源管理器」或 GetContent,当切换此选项时,它将自动设置。 震动强度 覆写文件 后缀 自动复制 缩放模式 双线性 Hermite Lanczos 近似像素式 花键式 基本式 默认值 Hann Mitchell 更好的缩放方法包括 Lanczos 重采样和 Mitchell-Netravali 滤波器 利用曲线段端点的数值和导数生成平滑连续曲线的数学插值技术 线性插值(或二维双线性插值)通常可以很好地改变图片的大小,但会导致一些不理想的细节弱化,而且仍会有些锯齿状的效果。 其中一种更简单的增大尺寸的方法是,用相同颜色的像素替换每个像素 最简单的安卓缩放模式,几乎适用于所有应用程序 信号处理中经常使用的渐变函数,通过渐变信号的边缘,最大限度地减少频谱泄漏,提高频率分析的准确性 对一组控制点进行平滑插值和重采样的方法,常用于计算机制图,以创建平滑曲线 通过对像素值应用加权 sinc 函数来保持高质量插值的重采样方法 重采样方法,使用参数可调的卷积滤波器,在缩放图片的清晰度和抗锯齿之间取得平衡 Bicubic Catmull 使用分段定义的多项式函数对曲线或曲面进行平滑插值和近似,提供灵活且连续的形状表示 仅剪切 不会将图片保存到本地存储,只会尝试将图片放入剪贴板中 OCR(文本识别) 精确度: %1$s 识别类型 快速 最优 无数据 下载 已下载的语言 分割模式 刷子将会还原背景,而不是擦除 从给定图片识别文字,支持120多种语言 图片中不含文字,或者应用程序没有找到 标准 为使 Tesseract OCR 正常运行,将需要下载额外的训练数据 (%1$s) 到您的设备。 \n您要下载%2$s数据吗? 无网络连接,请检查并重试,以便下载训练模型 可用语言 使用类似谷歌Pixel的开关 使用 Pixel 开关 在原始目标位置覆盖了名称为%1$s的文件 在绘图模式下,在手指顶部启用放大镜,以提高易用性 强制初始检查EXIF部件 滑动 并排 切换轻触 透明度 放大镜 强制初始值 允许多种语言 给应用程序评分 评分 此应用程序完全免费,如果您希望它变得更加强大,请在 Github 上为项目加星😄。 单行文本 单个字符 环形词语 原始行 当前 所有 仅检测文本及其方向 仅自动 单段纵向文本 单段文本 自动 单个词语 单列文本 松散文本 检测松散文本及其方向 自动检测文本及其方向 您要删除所有识别类型的语言 \"%1$s\" OCR 训练数据,还是只删除选定类型 (%2$s) 的数据? 渐变制作器 创建指定输出尺寸的渐变效果,并自定义颜色和外观类型 线性 放射式 平扫式 渐变类型 中心 X 中心 Y 平铺模式 重复 固定 贴纸 添加颜色 属性 颜色终止点 亮度强制 镜像 屏幕 渐变叠加 转换 相机 用相机拍摄一张照片。请注意,从此图片源只能获取一张图片 合成给定图片顶部的任意梯度 用自定义文字/图片水印覆盖图片 偏移 Y 水印类型 文本颜色 水印 重复水印 在图片上重复水印,而不是在给定位置重复单个水印 偏移 X 叠加模式 该图片将作为水印图案使用 GIF工具 GIF转换为图片 GIF文件转换为一组图片 图片转换为GIF 选择 GIF 图片以开始 使用第一帧的尺寸 用第一帧尺寸替换指定尺寸 重复次数 帧延迟 帧率 毫秒 像在绘图模式中一样使用套索来执行擦除操作 原始图片预览 Alpha 将图片转换为GIF图片,或从给定的GIF图片中提取帧 将一组图片转换为GIF文件 使用套索 在保存、分享和其他主要操作时会显示纸屑特效 安全模式 纸屑特效 在最近使用的应用中隐藏应用内容,无法被捕获或录制。 退出 如果现在离开预览,则需要重新添加图片 电子邮件 量化器 灰阶 抖动 Bayer 四对四抖动算法 Sierra 抖动算法 双列 Sierra 抖动算法 假 Floyd Steinberg 抖动算法 从左到右抖动 随机抖动 简单阈值抖动 Bayer 八对八抖动算法 Bayer 二对二抖动算法 Jarvis Judice Ninke 抖动算法 Floyd Steinberg 抖动算法 Bayer 三对三抖动算法 Sierra 轻型抖动算法 Atkinson 抖动算法 Stucki 抖动算法 Burkes 抖动算法 Sigma 空间 Sigma 中值模糊 B 样条 利用片断定义的双三次多项式函数平滑插值和近似曲线或曲面,灵活且连续地表示形状 原生堆栈模糊 倾斜移位 像素排序 毛刺 数量 种子 噪音 浮雕 随机播放 通道偏移 X 通道偏移 Y 故障规模 故障转移 X 帐篷模糊 侧面淡化 侧边 顶部 增强毛刺 故障转移 Y 底部 强度 扩散 传导 泊松模糊 对数色调映射 晶体化 幅度 大理石 湍流 油脂 尺寸 幅度 X 幅度 Y 柏林失真 Hable 胶片色调映射 ACES 山峰色调映射 侵蚀 各向异性扩散 快速双色模糊 水平风向交错 ACES 胶片色调映射 笔画颜色 分形玻璃 水纹效果 频率 X 频率 Y Hejl Burgess 色调映射 Dehaze 速度 Omega 颜色矩阵 3x3 简单效果 宝丽来 红色弱 布朗尼 Coda Chrome 夜视 凉爽 蓝色盲 红色盲 全色盲 非锐化 粉彩 颜色矩阵 4x4 蓝色弱 绿色弱 复古 温暖 绿色盲 不全色盲 微粒 奇幻风景 粉红之梦 黄金时刻 炎热夏季 紫色薄雾 日出之时 彩色漩涡 柔美春光 秋日色调 薰衣草之梦 赛博朋克 柠檬水光 黑夜魔法 色彩爆炸 电流渐变 焦糖黑暗 未来渐变 绿色太阳 彩虹世界 幽深紫色 红色漩涡 数字代码 橙色迷雾 光谱火焰 空间传送 虚化 随机表情符号 当表情符号功能被禁用时,你无法使用随机表情符号 当启用随机表情符号时,你无法选择单个表情符号 应用栏表情符号将会随机变化 老式电视 随机模糊 收藏夹 尚未添加收藏夹过滤器 图片格式 在图标下方添加一个具有所选形状的容器 图标形状 内村 莫比乌斯 峰值 Drago Aldridge 色彩异常 过渡 截止 在原位置覆盖图片 启用覆盖文件选项时无法更改图片格式 使用表情符号作为配色方案 使用表情符号的主色调作为应用程序配色方案,代替手动定义的配色方案 暗色 作为 Jetpack Compose 代码复制 从图片创建 Material You 调色板 采用夜间模式配色方案,代替灯光变体 交叉模糊 圆圈模糊 星光模糊 环形模糊 线性倾角移位 要移除的标签 选择 APNG 图片以开始 APNG工具 将图片转换为APNG图片,或从给定的APNG图片中提取帧 批量将APNG文件转换为图片 将一系列图片转换为APNG文件 图片转换为APNG APNG转换为图片 动态模糊 Zip 根据给定文件或图片创建Zip压缩包文件 拖动手柄宽度 彩纸类型 节日 爆炸 下雨 边角 JPEG转JXL 选择 JXL 图片以开始 将JXL无损转码为JPEG JXL工具 将JPEG无损转码为JXL 无质量损失地执行JXL ~ JPEG转码,或将GIF/APNG转换为JXL动画 JXL转JPEG 快速高斯模糊 3D 快速高斯模糊 4D 快速高斯模糊 2D 自动粘贴 允许应用程序自动粘贴剪贴板数据,这样它就会出现在主屏幕上,可以做进一步处理了 调和颜色 调和等级 Lanczos Bessel GIF转JXL 将GIF图片转换为JXL动画图片 APNG转JXL 将APNG图片转换为JXL动画图片 JXL转为图片 将JXL动画转换为系列图片 图片转为JXL 将一组图片转换为JXL动画 行为 跳过文件选取 如果可以,文件选择器将立即显示在所选屏幕上 生成预览 有损压缩 使用有损压缩代替无损压缩来减少文件大小 启用预览生成,这可能有助于避免在某些设备上出现崩溃,同时也会禁用单个编辑选项中的某些编辑功能 通过对像素值应用贝塞尔(jinc)函数来保持高质量插值的重采样方法 压缩方式 控制生成图片的解码速度,这将有助于更快地打开生成图片,%1$s表示最慢的解码速度,而%2$s表示最快的解码速度,此设置可能会增加输出图片的大小 筛选 日期 日期(反向) 名称 名称(反向) 选择多种媒体 选取 选择单一媒体 通道配置 今日 昨日 嵌入式选取器 Image Toolbox的图片选择器 无权限 请求 横向显示设置 再试一次 如果禁用,则在横向模式下,设置将始终在顶部应用栏的按钮上打开,而不是永久可见选项 开关类型 全屏设置 Compose 将“启用”设置为“总是以全屏打开设置页面,而不是可滑动抽屉页”。 一个Material You风格的开关 一个使用Jetpack Compose实现的Material You风格开关。 最大 调整锚点大小 Fluent Pixel Cupertino 一个基于“Cupertino”设计系统的开关 一个基于“Fluent”设计系统的开关 将给定图片追踪为SVG图片 如果启用该选项,将对量化调色板进行采样 路径省略 缩放图片 图片将在处理前缩放为较小的尺寸,这有助于此工具更快、更安全地工作 最小色比 线条阈值 路径标度 重置属性 图片转SVG 使用采样调色板 不建议使用该工具在不缩放的情况下追踪大型图片,否则会导致崩溃并增加处理时间 所有属性都将设置为默认值,注意此操作无法撤销 协调宽容 二次阈值 详细 默认线宽 引擎模式 传统 LSTM 网络 传统和LSTM 转换 将批量图片转换为给定格式 添加新文件夹 每个采样比特 每像素样本数 数据排列方式 色度抽样 色度抽样位置 X 分辨率 Y 分辨率 数据块偏移量 每条带行数 条带字节数 传输功能 白点 主色调 Y Cb Cr 系数 日期时间 图片描述 压缩方法 光度解释 分辨率单位 JPEG 交换格式 JPEG 交换格式长度 参考黑白色阶 型号 Exif版本 色彩空间 伽马 制造商 压缩每像素位数 制造商注释 用户备注 Y轴像素尺寸 X轴像素尺寸 水印体积 软件 艺术家 版权 Flashpix 版本 关联的音频文件 原始日期时间 原始时间偏移量 数字化时间偏移量 秒内细分时间 F值 曝光模式 光谱灵敏度 感光度(ISO感光度) 图片传感器类型 CFA模式 自定义图片处理 曝光模式 白平衡 35毫米胶片焦距 场景拍摄类型 场景控制 饱和度 锐度 图片唯一ID 设备拥有者名称 镜头规格 镜头制造商 镜头序列号 GPS版本ID GPS纬度参考 GPS时间戳 GPS接收器状态 GPS测量模式 GPS测量精度 GPS速度参考 GPS接收器速度 GPS移动方位 GPS图片方位 GPS目标纬度坐标 GPS目标距离 GPS处理方法 GPS区域信息 GPS日期印记 GPS差分改正 GPS水平定位误差 DNG版本 默认裁剪尺寸 预览图片长度 传感器下边框 传感器右边框 传感器上边框 在给定字体和颜色下沿路径绘制文本 字体大小 虚线长度 此图片将作为绘制路径的重复元素使用 标准输出感光度 数字化日期时间 时间偏移量 原始秒内细分时间 曝光时间 数字化秒内细分时间 光电转换函数(OECF) 感光类型 亮度值 曝光补偿值 推荐曝光指数 ISO速度宽容度zzz ISO速度 ISO速度宽容度yyy 光圈值 快门速度值 最大光圈值 拍摄距离 测光模式 闪光灯状态 闪光灯强度 主体区域 焦距 曝光指数 空间频率反应 焦距平面X轴解析度 焦距平面解析度单位 焦距平面Y轴解析度 主体位置 源文件 数字变焦 镜头型号 对比度 设备设定描述 主体距离范围 机身序列号 GPS纬度 GPS经度参考 GPS经度 GPS海拔参照值 GPS海拔 GPS接收卫星 GPS移动方位参照 GPS图片方位参照 GPS地图基准面 GPS目标纬度参考方向 GPS目标经度参考方向 GPS目标经度坐标 GPS到达目标方向参考 纵横比框架 GPS到达目标方位角 GPS目标距离参考 互操作性指数 预览图片起始位置 传感器左边框 ISO 重复文本 当前文本将会沿着路径重复绘制,直到路径结束,而非仅绘制一次。 使用选中的图片沿给定路径绘制 绘制从起点到终点的三角形轮廓线 三角形轮廓 三角形 绘制从起点到终点的三角形轮廓线 多边形 多边形轮廓 节点 绘制从起点到终点的多边形 绘制从起点到终点的多边形轮廓 绘制正多边形 绘制规则多边形,而不是自由形状的多边形 从起点到终点绘制星形 星形 星形轮廓 绘制从起点到终点的星形轮廓图 内部半径比 绘制规则而非自由形状的星星 绘制规则星形 抗锯齿 启用抗锯齿功能以防止出现尖锐边缘 打开编辑而不是预览 在 ImageToolbox 中选择要打开(预览)的图片时,将打开编辑选择表,而不是预览 文档扫描器 点击开始扫描 均衡直方图 HSV 均衡直方图 扫描文档并创建PDF或单独的图片 以PDF格式分享 以下选项用于保存图片,而非PDF文件 开始扫描 保存为PDF 输入百分数 允许通过文本字段输入 启用预设选择后面的文本字段,以便即时输入预设 均衡直方图像素化 比例色彩空间 线性 网格大小 Y 均衡直方图自适应 LUV 均衡直方图自适应 LAB CLAHE CLAHE LAB CLAHE LUV 边框颜色 忽略颜色 网格大小 X 均衡直方图自适应 裁剪为内容 新建 所选文件无过滤模板数据 创建模板 模板名称 该图片将用于预览该滤镜模板 作为二维码图片 作为文件 保存为文件 另存为二维码图片 删除模板 滤镜预览 二维码&条形码 扫描二维码并获取其内容,或粘贴字符串以生成新的二维码 代码内容 扫描任意条形码以替换字段中的内容,或输入内容以使用所选类型生成新条形码 QR 说明 您将删除选定的模板滤镜。 此操作无法撤销 模板滤镜 未添加模板滤镜 模板 扫描的二维码不是有效的滤镜模板 扫描二维码 已添加滤镜模板,名称为\"%1$s\" (%2$s) 最小 在设置中授予相机扫描二维码的权限 在设置中授予相机文档扫描器的权限 立方体 高斯型 斯芬克斯 Robidoux Robidoux Sharp 样条16 样条36 样条64 Kaiser Bohman Lanczos 2 Lanczos 3 立方插值法通过考虑最近的 16 个像素来实现更平滑的缩放,比双线性插值法效果更好。 利用分段定义的多项式函数平滑插值和近似曲线或曲面,灵活、连续地表示形状 一种窗口函数,用于通过渐变信号边缘来减少频谱泄漏,在信号处理中非常有用 Hann 窗口的一种变体,常用于减少信号处理应用中的频谱泄漏 一种窗口函数,通过最大限度地减少频谱泄漏来提供良好的频率分辨率,常用于信号处理中 一种应用高斯函数的插值方法,可用于平滑和减少图片中的噪音 信号处理中用于减少频谱泄漏的三角窗函数 高质量的插值方法经过优化,可自然调整图片大小,兼顾清晰度和平滑度 Robidoux方法的一个更清晰的变体,针对清晰的图片大小调整进行了优化 基于样条线的插值方法,使用 16 抽头滤镜提供平滑结果 基于样条线的插值方法,使用 36 抽头滤镜提供平滑结果 基于样条线的插值方法,使用 64 抽头滤镜提供平滑结果 一种结合了Bartlett窗和Hann窗的混合窗函数,用于减少信号处理中的频谱泄漏 一种简单的重取样方法,使用最近像素值的平均值,通常会产生块状外观 一种窗口函数,用于减少频谱泄漏,在信号处理应用中提供良好的频率分辨率 一种使用双叶 Lanczos 滤镜的重采样方法,可实现最小伪影的高质量插值 Lanczos 2 滤镜的变体,使用 jinc 函数,可提供高质量的插值,并将伪影降至最低 Robidoux EWA Blackman EWA Quadric EWA Robidoux Sharp EWA Lanczos 3 Jinc EWA Hanning EWA Hanning 滤镜的椭圆加权平均 (EWA) 变体,用于平滑插值和重采样 Ginseng Bartlett B样条 Hamming Blackman Hanning Welch 四面体 Bartlett-Hann Lanczos 4 Lanczos 2 Jinc Lanczos 3 Jinc Lanczos 4 Jinc 一种窗口函数,旨在提供良好的频率分辨率,减少频谱泄漏,常用于信号处理应用中 一种使用二次函数进行插值的方法,可提供平滑、连续的结果 一种重采样方法,使用 4叶 Lanczos 滤镜进行高质量插值,将伪影降到最低 先进的重采样方法可提供高质量的插值,并将人工痕迹降至最低 Lanczos 3 滤镜的变体,使用 jinc 函数,可提供高质量的插值,并将伪影降至最低 使用 Kaiser 窗口的插值方法,能很好地控制主叶宽度和侧叶水平之间的权衡 使用三叶 Lanczos 滤镜进行高质量插值的重采样方法,将伪影降到最低 Lanczos 4 滤镜的变体,使用 jinc 函数,可提供高质量的插值,并将伪影降至最低 Ginseng EWA Lanczos Sharp EWA Lanczos Soft EWA Lanczos 4 Sharpest EWA Haasn Soft Robidoux Sharp 滤镜的椭圆加权平均 (EWA) 变体,效果更清晰 用于平滑插值的四元滤镜椭圆加权平均 (EWA) 变体 Blackman 滤镜的椭圆加权平均 (EWA) 变体,用于尽量减少振铃伪影 Ginseng 滤镜的椭圆加权平均 (EWA) 变体,提高图片质量 重采样滤镜专为高质量图片处理而设计,在清晰度和平滑度之间取得了良好的平衡 Lanczos 3 Jinc 滤镜的椭圆加权平均 (EWA) 变体,用于减少混叠的高质量重采样 格式转换 永久忽略 图片堆叠 使用选定的混合模式将图片堆叠在一起 添加图片 Lanczos Sharp 滤镜的椭圆加权平均 (EWA) 变体,以最小的伪影获得清晰的效果 椭圆加权平均 (EWA) Lanczos Soft 滤镜变体,用于更平滑的图片重采样 将批量图片从一种格式转换为另一种格式 Lanczos 4 Sharpest 滤镜的椭圆加权平均 (EWA) 变体,用于对图片进行极其清晰的重采样 由 Haasn 设计的重采样滤镜,可实现平滑、无伪影的图片缩放 用于高质量重采样的 Robidoux 滤镜椭圆加权平均(EWA)变体 Clahe HSV Clahe HSL 箱数 边缘模式 剪切 缠绕 均衡直方图自适应 HSL 均衡直方图自适应 HSV 色盲 选择模式以为所选色盲类型适配主题颜色 难以区分红色和绿色色调 难以区分绿色和红色色调 难以区分蓝色和黄色色调 拉格朗日 2 Lanczos 6 Jinc 阶数为 2 的拉格朗日插值滤波器,适用于平滑过渡的高质量图片缩放 拉格朗日 3 阶数为 6 的 Lanczos 重采样滤波器,可提供更清晰、更准确的图片缩放效果 Lanczos 6 使用 Jinc 函数改进图片重采样质量的Lanczos 6 滤波器变体 无法感知红色色调 无法感知绿色色调 无法感知蓝色色调 完全色盲,只能看到灰色阴影 对所有颜色的敏感度降低 不要使用色盲方案 颜色将与主题中的设置完全一致 乙字形 阶数为 3 的拉格朗日插值滤波器,可为图片缩放提供更高的精度和更平滑的结果 线性帐篷模糊 线性快速高斯 Next 线性盒状模糊 高斯盒状模糊 线性快速高斯 线性高斯 线性高斯盒状模糊 线性叠加模糊 选择一个滤镜,将其作为颜料使用 替换滤镜 选择以下滤镜,将其用作绘图中的笔刷 TIFF 压缩方案 低多边形 沙画 图片分割 按行或列分割单张图片 备份OCR模型 导出 位置 左上 右上 左下 右下 中上 右中 中下 左中 将裁剪调整大小模式与该参数相结合,以实现所需的行为(裁剪/适应宽高比)。 适应边界 导入 语言导入成功 中心 调色板转换 油画效果增强 目标图片 简易老式电视 HDR 哥谭 简易素描 柔光 彩色海报 第三色 Clahe Oklab Clahe Oklch 三原色 Clahe Jzazbz 波尔卡圆点 集群式 2x2 抖动 集群式 4x4 抖动 集群式 8x8 抖动 Yililoma 抖动 添加收藏夹 互补 色彩工具 方型色 临近色 + 互补色 混合、制作色调、生成色调等 色彩调和 色彩阴影 变体 阴影 色调 淡色 已选择颜色 颜色信息 颜色混合 开启动态色彩时无法使用莫奈取色 目标 LUT 图片 Amatorka 烛光 未选择收藏夹选项,请在工具页面添加这些选项 临近色 分割互补色 三角色 四方色 要混合的颜色 柔美优雅变体 目标 3D LUT 文件 (.cube / .CUBE) 色彩查找表(LUT) 漂白旁路 调色板转移变体 512x512 2D LUT 柔美优雅 Miss Etikate 3D LUT 雾夜朦胧 50号胶片 金秋色彩 锐利琥珀 获取中性 LUT 图片 沉寂蓝调 首先,使用你最喜欢的图片编辑应用将一个滤镜应用于中性LUT(颜色查找表),你可以在这里获取它。为了使这个操作正常工作,每个像素的颜色不能依赖于其他像素(例如,模糊效果是不行的)。一旦准备就绪,将你的新LUT图片作为输入用于512x512 LUT滤镜。 柯达胶卷 流行艺术 金色森林 咖啡 Celluloid 青翠之影 时光黄影 链接预览 链接 在可获取文本(二维码、OCR 等)的地方启用链接预览检索功能 ICO文件只能以最大256×256的尺寸保存 GIF 转为 WEBP 将WEBP文件转换为一批图片 将一批图片转换为WEBP文件 图片转为WEBP 将GIF图片转换为WEBP动画图片 WEBP工具 WEBP转为多张图片 选取 WEBP 图片以开始 将图片转换为WEBP动画图片,或从给定的WEBP动画中提取帧 默认绘图颜色 默认绘制路径模式 添加时间戳 在输出文件名中启用时间戳格式,代替基本的毫秒数 无法完全访问文件 允许所有文件访问以查看JXL、QOI和其他在Android上未被识别为图片的图片。如果没有此权限,Image Toolbox将无法显示这些图片 启用在输出文件名中添加时间戳 格式化时间戳 启用时间戳以选择其格式 最近使用 获取应用程序新版本的通知并阅读公告 自动构建(CI)频道 单次保存位置 Telegram群组 查看和编辑单次保存位置,您可以在大多数选项中长按保存按钮来使用这些位置 Image Toolbox 在 Telegram 🎉 加入我们的聊天室,在这里你可以讨论任何你想讨论的问题,还可以查看 CI 频道,我将在这里发布测试版和公告。 根据给定尺寸调整图片,并为背景添加模糊效果或颜色 工具排列 按类型分组工具 在主屏幕上按工具类型分组,代替按自定义列表排列 默认值 系统栏可见性 通过轻划显示系统栏 如果系统栏被隐藏,则启用轻划以显示系统栏 自动 隐藏全部 显示全部 隐藏导航栏 隐藏状态栏 噪点生成 频率 噪点类型 产生不同的噪点,如柏林或其他类型的噪点 旋转类型 八分音符 Lacunarity 增益 加权强度 乒乓强度 距离功能 返回类型 分形类型 抖动 对齐 领域扭曲 自定义文件名 选择用于保存当前图片的位置和文件名 保存到以自定义名称的文件夹中 拼贴画制作器 拼贴画类型 用最多20张图片制作拼贴画 按住图片进行交换、移动和缩放以调整位置 该图片将用于生成 RGB 和亮度直方图 直方图 RGB 或亮度图片直方图来帮助您进行调整 魔方选项 为 tesseract 引擎应用一些输入变量 自定义选项 应按照以下模式输入选项 \"--{选项名} {值}\" 自由圆角 滤镜 在绘制的路径下进行内容感知填充 修复斑点 自动裁剪 强制点到图片边界 按多边形裁剪图片,这也可以修正透视 点将不受图片边界的限制,这有助于进行更精确的透视校正 形态渐变 使用圆形内核 正在开启 正在关闭 顶帽 黑帽 线条风格 间隙大小 虚线 圆点虚线 盖章 之字形 以指定的间隙大小沿绘制路径绘制虚线 沿指定路径绘制点和虚线 仅默认直线 按指定间距沿路径绘制选定图形 沿着路径画出波浪之字形 之字形比率 创建快捷方式 选择要固定的工具 该工具将作为快捷方式添加到启动器的主屏幕上,结合 “跳过文件拾取”设置使用,即可实现所需的功能 不要堆叠帧 处理预留帧,使其不会相互堆叠 交叉渐变 帧与帧之间会交叉淡入淡出 交叉渐变帧数 色调曲线 曲线将回滚到默认值 重置曲线 阈值一 阈值二 Canny 镜子 101 增强缩放模糊 简易拉普拉斯 辅助网格 在绘图区域上方显示辅助网格,以帮助进行精确操作。 简易索贝尔 网格颜色 单元格宽度 单元格高度 紧凑型选择器 一些选择控件将使用紧凑型布局以占用更少的空间。 在设置中授予相机权限以捕获图片。 布局 主屏幕标题 恒定比率系数 (CRF) %1$s表示压缩速度较慢,因此文件相对较小。%2$s表示压缩速度较快,因此文件较大。 预览图片 Lut 库 下载 LUT 集合,你可以在下载后应用它们 更改滤镜的默认图片预览 更新 LUT 集合(只有新的 LUT 会被加入队列),你可以在下载后应用它们 隐藏 显示 精致 Material 2 滑块类型 一个Material 2风格的滑块 一个外观精美的滑块。此为默认选项 一个Material You风格的滑块 接受 居中对话框按钮 开源许可证 查看此应用中所使用的开源库的许可证 如果可能的话,对话框的按钮将被置于居中位置,而非左侧。 区域 启用色调映射 使用像素面积关系进行重采样。这可能是图片抽取的一种首选方法,因为它能产生无摩尔纹的结果。但当对图片进行放大时,它与最近邻法类似 。 输入% 无法连接到此网站,请尝试使用VPN或检查URL是否正确 编辑图层 使用一张图片作为背景,并在其上添加不同的图层 与第一个选项相同,但用颜色代替图片 标记图层 图层模式,可自由放置图片、文本等内容 图片上的图层 背景图层 Beta 快速设置边栏 编辑图片时,在选定的侧边添加一个浮动条,点击该浮动条会打开快速设置 清除选择项 设置组 \"%1$s\" 将默认折叠 设置组“%1$s”将默认展开 Base64工具 将Base64字符串解码为图片,或将图片编码为Base64格式 Base64 提供的值不是有效的Base64字符串 粘贴Base64 保存Base64 选项 导入Base64 无法复制空的或无效的Base64字符串 复制Base64 加载图片以复制或保存Base64字符串。如果您已有Base64字符串,可以粘贴到上方以获取图片 分享Base64 操作 Base64 操作 添加大纲 在特殊颜色和边框文字添加大纲 大纲颜色 大纲字体 旋转 校验和作为文件名 输出图片将采用与其数据校验和相对应的名称 计算 校验和 文本哈希 输入文本,根据所选算法来计算其校验和 源校验和 用于比较的校验和 不同 校验和不相等,文件可能不安全! 匹配! 安卓应用合作伙伴渠道中有更多实用软件 免费软件(合作伙伴) 使用不同的哈希算法比较校验和、计算哈希值或从文件创建十六进制字符串。 选择文件,根据所选算法来计算其校验和 校验和相等,可能是安全的 算法 校验和工具 网格渐变 查看网格渐变在线收藏 导入字体(TTF/OTF) 已导入的字体 只有TTF和OTF字体可以被导入 导出字体 未设置文件名 尝试保存时出错,请尝试更改输出文件夹 自定义页面 工具退出确认 如果在使用特定工具时存在未保存的更改,而您尝试关闭该工具,系统将弹出确认对话框 页面选择 编辑EXIF 无需重新压缩即可更改单张图片的元数据 点击编辑可用标签 更改贴纸 选择文件以根据所选算法计算其校验和 批量比较 适应宽度 适应高度 选取文件 选取目录 头部长度缩放 格式化模式 填充 印章 时间戳 垂直枢轴线 水平枢轴线 反向选择 将保留垂直切割部分,而不是合并切割区域周围的部分 将保留水平切割部分,而不是合并切割区域周围的部分。 图片裁剪 裁剪图片部分,并通过垂直线或水平线合并左侧部分(可反向操作) 自定义网格渐变的节点数量和分辨率 网格大小 网格渐变集合 网格渐变叠加 在给定图片的顶部合成网格渐变 自定义点 X 轴分辨率 Y 轴分辨率 分辨率 逐个像素 突出显示颜色 像素比较类型 扫描条形码 强制使用黑白色 条形码图片将完全为黑白色,不会因应用程序的主题而带有颜色 高度比 条形码类型 扫描任何条形码(二维码、欧洲商品编码、阿兹特克码等)并获取其内容,或者粘贴你的文本以生成新的条形码 生成的条形码将显示在此处 未找到条形码 从音频文件中提取专辑封面图片,支持大多数常见的音频格式 音频封面 选择音频以开始 选择音频 未找到专辑封面 发送日志 点击分享应用程序日志文件,这可以帮助我发现问题并解决问题 哎呀……出错了 你可以使用下面的选项与我联系,我会尽力找到解决方案。\n(别忘了附上日志文件) 写入文件 写入元数据 从每张图片中提取文本,并将其放置在相应图片的EXIF信息中。 从一批图片中提取文本,并将其存储在一个文本文件中 隐写模式 使用隐写术在你的图片字节内创建肉眼不可见的水印 使用LSB 将使用最低有效位(LSB)隐写术方法,否则将使用频域(FD)方法 自动去除红眼 密码 解锁 PDF已受保护 大小 大小(反向) MIME 类型 MIME 类型(反向) 添加日期 添加日期(反向) 扩展名 操作基本完成。现在取消需要重新启动 扩展名这(反向) 修改日期 修改日期(反向) 从左到右 从右到左 从上到下 从下到上 液态玻璃 一个基于近期发布的iOS 26及其液态玻璃设计系统的开关 粘贴链接 选取图片或粘贴/导入下面的Base64数据 输入图片链接以开始 万花筒 辅助角 通道混合 蓝绿色 红蓝色 绿红色 品红色 融入红色 融入绿色 融入蓝色 青色 黄色 彩色半色调 轮廓 等级 偏移 沃罗诺伊结晶化 形状 拉伸 随机性 去斑点 扩散 高斯差分 第二半径 均衡化 发光 旋转与挤压 点阵化 边框颜色 极坐标 直角坐标转极坐标 极坐标转直角坐标 圆形内反相 减少噪点 简易曝光过度 交织纹理 X 轴间距 Y 轴间距 X 轴宽度 Y 轴宽度 漩涡效果 橡皮图章 涂抹 密度 混合 球面镜头畸变 折射率 弧线 扩散角度 光束 渐变 闪烁 ASCII 摩尔纹 秋色 骨色 伪彩色 冬色 海色 夏色 春色 冷色变体 粉红色 热色 熔岩 炼狱 等离子 翠绿 涡轮 暮光 暮光(偏移) 自动透视校正 自动扶正 允许裁剪 裁剪或透视校正 绝对 深绿 镜头校正 目标镜头配置文件(JSON 格式) 下载预设镜头配置文件 区域百分比 HSV Cividis Parula 禁用旋转 防止用双指手势旋转图片 启用贴靠到边缘 移动或缩放后,图像将自动贴靠以填满帧边缘 导出为JSON格式 将包含调色板数据的字符串以JSON格式表示并复制 内容感知缩放 主屏幕 锁屏 内置 壁纸导出 刷新 获取当前的主屏幕、锁屏及内置壁纸 允许访问所有文件,此操作是获取壁纸所必需的 仅管理外部存储权限是不够的,您需要允许访问您的图片,请务必选择 “全部允许” 将预设添加到文件名中 将带有选定预设的后缀添加到图片文件名中 将图像缩放模式添加到文件名中 将带有选定图像缩放模式的后缀添加到图片文件名中 ASCII艺术 将图片转换为外观与原图相似的ASCII文本 参数 对图像应用负片滤镜,以在某些情况下获得更好的效果 处理截图 截图未捕获,请再试一次 跳过保存 %1$s 个文件已跳过 若文件过大,允许跳过 某些工具在生成的文件大小超过原文件时,将被允许跳过图像保存步骤 文本 短信 电话 位置 电子邮箱 联系人 Wi-Fi 日历事件 N/A URL 开放网络 SSID 手机 信息 地址 主题 正文 名称 组织 职位 手机 电子邮箱 URLs 地址 摘要 描述 位置 组织者 开始日期 结束日期 状态 创建条形码 编辑条形码 安全 Wi-Fi配置 纬度 经度 发音 添加手机 添加电子邮箱 添加地址 网站 添加网站 格式化名称 选择联系人 在设置中授予联系人权限,以使用选定的联系人自动填充 联系人信息 姓氏 中间名 名字 深色 浅色 Hyper OS 小米澎湃OS风格 掩码图案 此图像将用于放置在条形码上方 代码定制 此图像将用作二维码中心的logo Logo Logo内边距 Logo大小 Logo角部 第四只眼 通过在底端角落添加第四只眼,为二维码增加眼睛对称性 像素形状 框形 球形 纠错等级 此二维码可能无法扫描,请调整外观参数,确保所有设备均可识别 不可扫描的 工具将呈现为桌面应用启动器样式,以更简洁紧凑 启动器模式 用所选画笔和样式填充指定区域 油漆桶填充 喷雾 绘制涂鸦风格路径 方形粒子 喷雾粒子将呈现方形,而不是圆形 调色板工具 从图片中生成基础配色方案或Material You动态配色方案,也可在不同配色格式间导入或导出配色 编辑调色板 跨多种格式导入/导出调色板 颜色名称 调色板名称 调色板格式 将生成的调色板导出为多种格式 向当前调色板添加新颜色 %1$s 格式不支持提供调色板名称 由于应用商店政策限制,当前版本无法包含此功能。如需使用该功能,请从其他渠道下载ImageToolbox。你可在下方的GitHub上获取可用版本。 打开GitHub页面 %1$s 颜色 原始文件将被新文件替换,而不是保存到所选文件夹中 检测到隐藏水印文本 检测到隐藏水印图像 该图像已被隐藏 生成式修复 可通过AI模型移除图像中的物体,无需依赖OpenCV。使用此功能前,应用将从GitHub下载所需模型(约200MB) 可通过人工智能模型移除图像中的物体,无需依赖OpenCV,此操作可能耗时较久 错误等级分析 亮度梯度 平均距离 复制移动检测 保留 系数 剪贴板数据过大 数据过大,无法复制 简单编织像素化 交错像素化 交叉像素化 微观宏观像素化 轨道像素化 涡旋像素化 脉冲网格像素化 核心像素化 径向编织像素化 无法打开URI“%1$s” 降雪模式 启用 边框框架 故障变体 通道偏移 最大偏移量 方块故障 方块大小 CRT曲率 曲率 色度 像素融解 最大落差 VHS AI工具 通过AI模型处理图像的各类工具,例如伪影去除或降噪 压缩,锯齿线 卡通,广播压缩 通用压缩,通用噪声 无色卡通噪点 快速、通用压缩、通用噪声、动画/漫画/动漫 书籍扫描 曝光校正 擅长通用压缩、彩色图像 擅长通用压缩、灰度图像 通用压缩、灰度图像、增强型 通用噪声、彩色图像 通用噪声、彩色图像、细节优化 通用噪声、灰度图像 通用噪声、灰度图像、增强型 通用噪声、灰度图像、强效型 通用压缩 通用压缩 纹理化、H.264压缩 VHS压缩 非标准压缩(Cinepak、MSVideo1、RoQ) 宾克压缩,几何形状更好 宾克压缩,增强型 宾克压缩,柔和,保留细节 消除阶梯效应,实现平滑处理 扫描艺术品/画作,轻度压缩,摩尔纹 色带失真 慢速、去除半色调网点 灰度/黑白图像通用着色器,使用DDColor可获得更佳效果 边缘去除– 去除过度锐化 慢速、抖动处理 抗锯齿、通用伪影、计算机生成图像 合并 KDM003扫描处理中 轻量级图像增强模型 简易快速着色,适用于卡通图像,效果欠佳 压缩伪影去除 压缩伪影消除 平滑效果的伪影消除 半色调图案处理 抖动图案去除V3 JPEG伪影去除V2 H.264纹理增强 VHS画质锐化与增强 块大小 重叠大小 超过 %1$s 像素的图像将被切片并分块处理,重叠区域会进行融合以避免出现明显接缝。 大尺寸图像会导致低端设备运行不稳定 选择一个以开始 你是否要删除 %1$s 模型?删除后你需要重新下载该模型 确认 模型 已下载的模型 可用模型 准备中 启用模型 会话打开失败 仅支持导入.onnx/.ort格式的模型 导入模型 导入自定义ONNX模型以进一步使用,仅支持ONNX/ORT格式的模型,兼容几乎所有类ESRGAN变体 已导入的模型 通用噪点,彩色图像 通用噪点,彩色图像,强度更高 一般噪声,彩色图像,最强 减少抖动伪影和色彩带状,提升平滑渐变和纯色区域的显示效果。 增强图像亮度和对比度,同时保持自然色彩平衡。 使暗部图像变亮,同时保留细节并避免过度曝光。 去除过度的色彩调色,恢复更中性和自然的色彩平衡。 应用基于泊松的噪声调色,强调保留精细细节和纹理。 应用柔和的泊松噪声调色,使视觉效果更平滑且不那么激进。 均匀噪声调色专注于细节保留和图像清晰度。 温和的均匀噪声调色,使纹理更细腻,外观更平滑。 通过重新绘制瑕疵并改善图像一致性来修复损坏或不均匀的区域。 轻量级去色带模型,以最小的性能成本去除色带。 优化具有非常高的压缩伪影(0-20%质量)的图像,以提高清晰度。 优化具有较高压缩伪影(20-40%质量)的图像,恢复细节并减少噪声。 优化中等压缩(40-60%质量)的图像,平衡清晰度和平滑度。 优化轻度压缩(60-80%质量)的图像,以增强细微细节和纹理。 优化接近无损图像(质量 80-100%),同时保留自然外观和细节。 略微减少图像模糊,提高清晰度而不引入伪影。 长时间运行的操作 处理图像 处理中 去除在极低质量图像(0-20%)中存在的严重JPEG压缩伪影。 减少高度压缩图像(20-40%)中的强烈JPEG伪影。 清除中等程度的JPEG伪影,同时保留图像细节(40-60%)。 优化60-80%质量图像中的轻微JPEG伪影。 略微减少近乎无损图像(80-100%)中的轻微JPEG伪影。 增强精细细节和纹理,提升感知清晰度,同时避免严重的伪影。 处理完成 处理失败 增强皮肤纹理和细节,同时保持自然外观,优化速度。 去除JPEG压缩伪影并恢复压缩照片的图像质量。 降低在低光条件下拍摄的照片中的ISO噪点,同时保留细节。 修正曝光过度或“巨大”的高光,并恢复更好的色调平衡。 轻量级且快速的着色模型,为灰度图像添加自然色彩。 DeJPEG 降噪 着色 伪影 增强 动漫 扫描 放大 适用于大多数图像的 X4 放大器;体积小巧的模型,使用更少的 GPU 和更短的时间,具有适度的去模糊和去噪功能。 适用于大多数图像的 X2 放大器,保留纹理和自然细节。 适用于大多数图像的 X4 放大器,有着增强的纹理和逼真的效果。 为动漫图像优化的 X4 放大器;具有6个RRDB块,使线条更清晰,细节更丰富。 带有 MSE 损失的 X4 放大器,为普通图像生成更平滑的结果并减少伪影。 抠图 水平边框厚度 垂直边框厚度 轻量级模型,可快速去除背景。平衡速度与效果。适用于肖像、物体和场景­。大多数图像皆可使用。 X4 UltraSharp V2 Lite;更快且更小,在使用更少显存的同时保留细节。 X4 UltraSharp V2 模型,适用于一般图像;强调锐度和清晰度。 针对动漫图像进行了优化的 X4 放大器;4B32F 变体,细节更清晰,线条流畅。 当前模型不支持分块,图像将以原始尺寸处理,这可能会导致高内存消耗并且在低性能设备上产生问题 禁用分块,图像将以原始尺寸进行处理,这可能会导致高内存消耗并在低性能设备上产生问题,但可能会给出更好的推理结果 分块 高精度抠图用图像分割模型 U2Net 的轻量级版本,使用更少内存并更快地去除背景。 完整的 DDColor 模型可以很高的质量为大多数图像上色,且伪影最少。它是所有着色模型的最佳选择。 DDColor 训练有素的私人艺术数据集;产生多样化和艺术化的色彩结果,减少不切实际的色彩伪影。 基于 Swin Transformer 的轻量级 BiRefNet 模型,可实现准确的抠图效果。 高质量的抠图效果,具有锐利的边缘和出色的细节保留能力,尤其对于复杂的物体和棘手的背景。 抠图模型,可生成平滑的边缘与精确的蒙版,适用于一般物体并能适度保留细节。 模型已下载 模型导入成功 类型 关键词 很快 正常 很慢 计算百分比 最小值为 %1$s 用手指绘图扭曲图像 扭曲 硬度 扭曲模式 移动 扩大 收缩 顺时针漩涡 逆时针漩涡 褪色强度 顶部掉落 底部掉落 开始掉落 结束掉落 下载中 光滑的形状 使用超椭圆代替标准圆角矩形以获得更平滑、更自然的形状 形状类型 切角 圆角 光滑 边缘锐利,无圆角 经典圆角 形状类型 边角大小 方圆形 雅致的圆角UI元素 文件名格式 自定义文本放置在文件名的开头,非常适合项目名称、品牌或个人标签。 使用不带扩展名的原始文件名,帮助您保持源标识完整。 图像宽度(以像素为单位),可用于跟踪分辨率变化或缩放结果。 图像高度(以像素为单位),在处理宽高比或导出时很有帮助。 生成随机数字以保证唯一的文件名;添加更多数字以提高安全性,防止重复。 用于批量导出的自动递增计数器,非常适合在一个会话中保存多个图像时。 将应用的预设名称插入文件名中,以便您可以轻松记住图像的处理方式。 显示处理过程中使用的图像缩放模式,帮助区分调整大小、裁剪或拟合的图像。 放置在文件名末尾的自定义文本,对于 _v2、_edited 或 _final 等版本控制很有用。 文件扩展名(png、jpg、webp等),自动匹配实际保存格式。 可自定义的时间戳,让您可以通过 Java 规范定义自己的格式以实现完美排序。 滑动类型 安卓原生 iOS风格 平滑曲线 快速停止 弹力 飘逸 活泼 超光滑 自适应 无障碍意识 减少运动 原生 Android 滚动物理 平衡、平滑的滚动,适合一般用途 更高的摩擦力,类似 iOS 的滚动行为 独特的样条曲线带来独特的滚动感觉 精确滚动并快速停止 有趣、反应灵敏的弹性滚动 用于内容浏览的长滑动卷轴 交互式 UI 的快速、响应式滚动 优质平滑滚动,动力强劲 根据投掷速度调整物理 尊重系统辅助功能设置 满足无障碍需求的最小运动 主要线路 每五行添加较粗的线 填充颜色 隐藏工具 隐藏共享工具 颜色库 浏览海量颜色集合 锐化并消除图像模糊,同时保持自然细节,是修复失焦照片的理想选择。 智能恢复之前调整过大小的图像,恢复丢失的细节和纹理。 针对真人内容进行了优化,减少了压缩伪影并增强了电影/电视节目帧中的精细细节。 将 VHS 质量的素材转换为高清,消除磁带噪音并提高分辨率,同时保留复古感。 专门用于文本较多的图像和屏幕截图,锐化字符并提高可读性。 在不同数据集上进行高级升级训练,非常适合通用照片增强。 针对网络压缩照片进行了优化,消除了 JPEG 伪影并恢复自然外观。 网页照片的改进版本,具有更好的纹理保留和伪影减少。 使用双聚合变压器技术进行 2 倍升级,保持清晰度和自然细节。 使用先进的变压器架构进行 3 倍放大,非常适合中等放大需求。 通过最先进的变压器网络进行 4 倍高质量放大,在更大的尺度上保留精细细节。 消除照片中的模糊/噪点和抖动。通用但最适合照片。 使用 Swin2SR 转换器恢复低质量图像,并针对 BSRGAN 退化进行了优化。非常适合修复严重的压缩伪影并以 4 倍比例增强细节。 使用经过 BSRGAN 退化训练的 SwinIR 变压器进行 4 倍升级。使用 GAN 在照片和复杂场景中获得更清晰的纹理和更自然的细节。 小路 合并PDF 将多个PDF文件合并为一个文档 文件顺序 页数 分割PDF 从PDF文档中提取特定页面 旋转PDF 永久修复页面方向 页数 重新排列PDF 拖放页面以重新排序 按住并拖动页面 页码 自动为您的文档添加编号 标签格式 PDF转文本 (OCR) 从PDF文档中提取纯文本 叠加自定义文字,用于品牌标识或安全防护 签名 将您的电子签名添加到任何文档中 这将用作签名 解锁PDF 从受保护的文件中删除密码 保护PDF 通过强大的加密保护您的文档 成功 PDF已解锁,您可以保存或分享 修复PDF 尝试修复损坏或无法读取的文档 灰度 将所有文档嵌入图像转换为灰度 压缩PDF 优化文档文件大小以方便共享 ImageToolbox 重建内部交叉引用表并从头开始重新生成文件结构。这可以恢复对许多“无法打开”文件的访问 该工具将所有文档图像转换为灰度。最适合打印和减小文件大小 元数据 编辑文档属性以获得更好的隐私 标签 制作人 作者 关键词 创作者 隐私深度清洁 清除该文档的所有可用元数据 深度OCR 使用 Tesseract 引擎从文档中提取文本并将其存储在一个文本文件中 无法删除所有页面 删除PDF页面 从PDF文档中删除特定页面 点击删除 手动 裁剪PDF 将文档页面裁剪到任意范围 拼合PDF 通过光栅化文档页面使PDF不可修改 无法启动相机。请检查权限并确保它未被其他应用程序使用。 提取图像 以原始分辨率提取PDF中嵌入的图像 此PDF文件不包含任何嵌入图像 该工具扫描每一页并恢复全质量源图像 - 非常适合保存文档中的原件 绘制签名 笔参数 使用自己的签名作为图像放置在文档上 压缩PDF 以给定的间隔分割文档并将新文档打包到zip存档中 间隔 打印PDF 准备用于使用自定义页面尺寸打印的文档 每张页数 方向 页面尺寸 利润 盛开 软膝 针对动漫和卡通进行了优化。快速升级,改善自然色彩并减少伪影 类似三星 One UI 7 的风格 在此输入基本数学符号以计算所需的值(例如(5+5)*10) 数学表达式 选取最多 %1$s 张图片 保留日期时间 始终保留与日期和时间相关的 exif 标签,独立于 keep exif 选项 Alpha 格式的背景颜色 增加了为每种具有 Alpha 支持的图像格式设置背景颜色的功能,禁用后,此功能仅适用于非 Alpha 格式 打开项目 继续编辑之前保存的 Image Toolbox 项目 无法打开 Image Toolbox 项目 Image Toolbox 项目缺少项目数据 Image Toolbox 项目已损坏 不受支持的 Image Toolbox 项目版本:%1$d 保存项目 将图层、背景和编辑历史记录存储在可编辑的项目文件中 打开失败 写入可搜索的 PDF 从图像批次中识别文本并保存带有图像和可选文本层的可搜索 PDF 阿尔法层 水平翻转 垂直翻转 添加阴影 阴影颜色 文本几何 拉伸或倾斜文本以获得更清晰的风格 规模 X 倾斜 X 删除注释 从 PDF 页面中删除选定的注释类型,例如链接、注释、突出显示、形状或表单字段 超链接 文件附件 线路 弹出窗口 邮票 形状 文字注释 文本标记 表单字段 标记 未知 注释 取消分组 使用可配置的颜色和偏移在图层后面添加模糊阴影 ================================================ FILE: core/resources/src/main/res/values-zh-rTW/strings.xml ================================================ 大小 %1$s 載入中… 沒有發現 EXIF 資料 選取圖片以開始 關閉 重置圖片 品質 高度 %1$s 發生了一些錯誤 編輯 EXIF 清除 原始碼 藍色 發生了一些錯誤:%1$s 寬度 %1$s 選取圖片 圖片太大以致無法預覽,但無論如何都會嘗試儲存它 已複製到剪貼簿 重置 清除 EXIF 儲存 新增標籤 取消 預設集 裁切 所有圖片的 EXIF 資料將會被清除,此動作無法被復原! 如果您現在離開,將會遺失所有未儲存的變更 儲存 取得最新的更新,討論問題等 從圖片選取色彩,並複製或分享 色彩 選取色彩 圖片 色彩已複製複製 版本 裁切圖片到任何尺寸 移除 新版本 %1$s 保留 EXIF 更換預覽圖 圖片:%d 更新 未支援類型:%1$s 純黑深色模式 預設 未指定 輸出資料夾 選取圖片 自訂 最大大小(KB) 裝置儲存區 系統 比較 夜間模式 淺色 深色 選取兩張圖片以開始 設定 動態色彩 自訂 紅色 綠色 色彩方案 語言 圖片變更將會被退回到初始狀態 沒有任何東西可以貼上 當動態顏色被啟用時,無法變更應用程式色彩方案 未發現更新 應用程式主題將會以選擇的顏色為基礎 關於應用程式 問題追蹤器 在此發送錯誤報告與功能請求 幫助翻譯 更正翻譯錯誤或在地化專案至另一種語言 在此搜尋 格式 強制 沒有發現任何您查詢的內容 如果啟用,當您選擇一個圖片以編輯,將會採用此圖片色彩為應用程式色彩 如果啟用,則將會採用桌布色彩為應用程式色彩 依選取的檔案大小(KB)調整圖片尺寸 如果啟用,在夜間模式下背景色彩將會被設定為純黑 貼上有效的色碼 調整尺寸方式 依檔案大小調整尺寸 確定 無法從選取的圖片產生調色盤 彈性 產生調色盤 調色盤 原始 比較兩個選取的圖片 關閉應用程式 允許圖片莫內 您確定要關閉此應用程式嗎? 重新啟動應用程式 取消 編輯單一圖片 變更已重置 例外 編輯單一圖片 從選取的圖片產生調色盤樣本 無法儲存 %d 圖片 邊框厚度 表面 這個應用程式是完全免費的,但如果您想支持專案開發,您可以點擊這裡 應用程式需要存取您的儲存區以儲存圖片,這是必要的。請在下一個對話框中授予權限 懸浮動作按鈕對齊 檢查更新 如果啟用,更新對話框將在應用程式啟動時顯示給您 設定值 加入 初级 三级 次級 權限 授予 圖片縮放 前置詞 檔案名稱 應用程式需要這個權限才能工作,請手動授予它 分享 外部儲存區 莫內色彩 表情符號 選擇將在主介面上顯示的表情符號 加入檔案大小 如果啟用,將儲存圖片的寬度和高度插入到輸出檔案的名稱 刪除 EXIF 從任意一組圖片中刪除 EXIF 詮釋資料 圖片預覽 預覽任何類型的圖片:GIF、SVG 等 圖片來源 照片選擇器 圖片庫 出現在畫面底部的 Android 現代照片選擇器可能僅適用於 Adnroid 12+,並且在接收 EXIF 詮釋資料時也存在問題 選項排列 編輯 順序 確定主介面上工具的順序 表情符號數量 序號 原始檔案名稱 加入原始檔案名稱 如果啟用,則在輸出圖片的檔案名稱中插入原始檔名 如果選擇了照片選擇器圖片來源,加入原始檔案名稱將不起作用 檔案管理器 簡易的圖片選取器,只有當您有任何提供媒體選取的應用程式時,它才能工作。 使用 GetContent 隨時隨地選取圖片,但已知在某些裝置上接收選取的圖片時也存在問題,這不是我的錯。 亮度 伽馬 高亮與陰影 適合 新增濾鏡 色彩濾鏡 白平衡 色溫 單色 高亮 陰影 強度 銳利化 如果啟用,則在使用批次處哩時會取代標準時間戳記為圖片序號 取代序號 從網路載入圖片 從網際網路載入任何圖片以預覽、縮放、編輯與儲存,如果您想要的話。 圖片連結 調整圖片尺寸至指定的寬度與高度。可能會改變寬高比。 濾鏡 飽和度 應用濾鏡至圖片 濾鏡 使用長邊為準由指定的寬度或高度調整圖片尺寸,所有的尺寸計算將在儲存之後完成 - 維持寬高比例 沒有圖片 填滿 對比 自然飽和度 暈影 開始 色相 光線 曝光 著色 效果 Alpha 霧度 變化量 方框模糊 棕褐色調 負片 曝光過度 黑白色 半色調 GCA 色彩空間 線條寬度 間距 索伯運算子 浮雕 結束 拉普拉斯運算子 凹凸鏡 球面折射 膨脹 Kuwahara 平滑化 半徑 強度 扭曲化 角度 漩渦 不透明度 限制性調整尺寸 調整選擇的圖片的尺寸至指定寬高度並維持寬高比例 素描 閾值 量化等級 色調分離 卡通化 細緻卡通化 非極大值抑制(NMS) 交叉底紋 模糊化 高斯模糊 雙邊模糊 玻璃球折射 折射率 色彩矩陣 內容縮放 弱像素包含(WPI) 抬頭 堆疊模糊 卷積 3x3 RGB 濾鏡 假色 第一種顏色 第二種顏色 重新排序 快速模糊 模糊尺寸 模糊中心 x 模糊中心 y 縮放模糊 色彩平衡 亮度閾值 您禁用了「檔案」應用程式,請將其啟用才能使用此功能 繪畫 如同在素描本上一樣在圖片上繪畫,或在背景本身上繪畫 顏料色彩 顏料透明度 在圖片上繪畫 選取一個圖片並在上面畫一些東西 在背景上繪畫 加密 AES-256,GCM 模式,無填充,12 位元組隨機 IV。密鑰將用作 SHA-3 雜湊值(256 位元)。 檔案已處裡 嘗試以指定的寬度和高度儲存圖片可能會導致記憶體溢出錯誤,此風險將由您自行承擔。 快取 快取大小 已發現 %1$s 自動清除快取 如果啟用,應用程式快取將在應用程式啟動時清除 創作 選擇背景顏色並在其上繪畫 背景顏色 加密 基於各種可用的加密演算法進行加密與解密任何檔案(不僅是圖片) 選擇檔案 加密 解密 選擇檔案以開始 解密 密鑰 將此檔案儲存在您的設備上或使用分享功能將其放在您想要的任何位置 功能 執行 相容性 檔案大小 Android 作業系統及裝置的可用記憶體會影響檔案大小的最大限制。 \n提醒您:記憶體和儲存空間不是同一回事。 請注意,不保證與其他檔案加密軟體或服務的相容性。稍有不同的密鑰處理或密碼組態可能是不相容的原因。 基於密碼的檔案加密。處理過的檔案可以儲存在選擇的目錄中或分享。解密後的檔案也可以直接開啟。 密碼無效或選擇的檔案未加密 工具 按類型對選項進行分組 將主介面上的選項按類型分組,而不是自定義列表排列 啟用選項分組時無法變更排列 以 %1$s 模式儲存可能不穩定,因為它是無損格式 略過 螢幕截圖 複製 後備選項 編輯螢幕截圖 二次自訂 寬高比 備份與還原 裁切遮罩 使用此遮罩類型以從選取圖片建立遮罩,注意:它應該有 alpha 通道 從之前產生的檔案還原應用程式設定 損壞的檔案或非備份檔 設定還原成功 聯絡我 備份 還原 備份您的應用程式設定為檔案 隨機化檔案名稱 如果您選擇了預設值 125,影像會儲存為原始影像的 125%。如果您選擇預設值 50,則影像會儲存為原始影像的 50% 大小。 這裡的預設決定了輸出檔案的百分比,如果您選擇了預設 50 於 5MB 圖片,則在儲存後您將會獲得 2.5MB 的圖片 如果您啟用了,則輸出檔名將會是完全隨機的 已儲存名為 %2$s 至 %1$s 資料夾 已儲存至 %1$s 資料夾 Telegram 聊天 討論此應用程式與從其他使用者獲取意見回饋,您也可以在此獲得 beta 更新與見解。 這將會恢復您的設定至預設值,請注意,如果沒有備份檔案,則無法復原動作。 哎呀… 發生了一些錯誤,您可以使用以下的選項寫信給我,我將會嘗試尋找解決方案 自然與動物 最大色彩數量 活動 目前,%1$s 在 Android 上僅允許讀取 EXIF。這意味著輸出圖片完全不會有詮釋資料。 飲食 允許收集匿名應用程式使用統計資料 字型大小 繪畫模式 圖片周圍的透明空間將會被修剪 擦除背景 還原圖片 刪除 模糊半徑 去除背景 修剪圖片 通過繪圖或自動選項從圖片去除背景 您即將刪除選擇的色彩方案,此動作無法被復原。 自動擦除背景 這允許應用程式自動地收集崩潰報告 建立問題 吸管 符號 分析 調整尺寸與轉換 物體 文字 預設 刪除方案 還原背景 字型 變更選取的圖片尺寸或轉換它們至其他格式。如果您選取了單一圖片,也可以在此編輯其 EXIF 詮釋資料 使用大字型也許會導致 UI 故障與一些問題,這無法修復,請謹慎使用。 圖片原始的詮釋資料將會被保留 擦除模式 旅遊與地點 軟體更新 請稍候 值 %1$s 表示快速的壓縮,會產生相對較大的檔案大小;而值 %2$s 則是較慢的壓縮,可以得到較小的檔案。 允許接收測試版本 漢字示例 0123456789 !? 情感模式 儲存幾乎完成了。若您現在取消,將需要從頭開始儲存 點擊以啟用表情符號 壓縮選項 若您選擇啟用,則在檢查更新時會包括測試版。 畫筆柔度 繪製箭頭 若啟用,繪畫路徑將顯示為指向箭頭。 圖片會根據輸入的大小進行中心裁剪。如果圖片小於輸入的尺寸,畫布會依指定的背景顏色進行擴展。 捐款 圖片拼接 將選取的圖片合成為一幅大圖片 橫向 圖片排序 請選取至少兩張圖片 若開啟此選項,小圖片將會放大至序列中最大的圖片大小 圖片定位 縱向 輸出圖片縮放比例 將小圖片放大至大尺寸 重新編碼 模糊邊緣 像素化 增強像素化 目標色彩 替換色彩 鑽石像素化 移除色彩 增強鑽石像素化 要刪除的色彩 要取代的色彩 筆畫像素化 標準 容錯度 如果啟用,在原圖之下繪製模糊的邊界而不是單色,以填充其周圍的空間。 圓形像素化 增強圓形像素化 像素大小 鎖定繪畫方向 如果啟用,在繪畫模式中畫面將不會旋轉 檢查更新 忠實 彩虹 趣味主題 - 主題中不包含源顏色的色調 色彩豐富的主題,主要調色盤最為鮮艷,其他調色盤色彩也較為增加 略帶彩色的單色風格 將來源顏色置於 Scheme.primaryContainer 的配色方案 色調點綴 純單色主題,顏色僅為黑/白/灰 水果沙拉 與主題內容方案高度相似的方案 內容 富有表現力 中性 預設的調色盤風格,它允許自定義所有四個顏色,其他的只允許設定主要的顏色 調色盤風格 鮮豔 兩者 若啟用,將會將主題顏色更換為相反顏色 邊緣淡化 這個更新檢查功能會連線至 GitHub,以檢查是否有新版本的更新可供下載。 已停用 顏色反轉 提醒 搜尋 能夠搜尋主介面上的所有可用工具 PDF 工具 圖像轉 PDF 預覽 PDF PDF 轉圖像 將指定圖像打包成輸出 PDF 檔案 簡易 PDF 預覽 操作 PDF 檔案:預覽、轉換為一組圖像或從給定圖片中建立 將 PDF 轉換為指定輸出格式的圖像 遮罩濾鏡 在指定的遮罩區域應用濾鏡鏈,每個遮罩區域都可以確定其自己的濾鏡集 遮罩 新增遮罩 遮罩 %d 遮罩預覽 遮罩顏色 所繪製的濾鏡遮罩會被渲染,以展示預期的效果大概是什麼樣子 刪除遮罩 反向填充類型 您即將刪除所選濾鏡遮罩。此操作無法復原 對給定的圖像或單張圖像應用任何濾鏡鏈 如果啟用,所有非遮罩區域將進行濾鏡處理,而不是預設行為 開始 全濾鏡 結束 中心 螢光筆 隱私模糊 自動旋轉 按鈕 應用程式列 容器 覆蓋檔案 震動 震動強度 剪貼簿 自動釘選 如果啟用,自動地新增儲存的圖片至剪貼簿 尾綴 自由 拼接模式 此應用程式完全地免費,如果您想要它更加強大,請在 Github 上為此專案給星 😄 水平網格 依給定的路徑繪製封閉填充路徑 套索 繪製路徑模式 繪製路徑為輸入值 從給定的路定繪製指向箭頭 從起點至終點連為一線繪製雙指向箭頭 橢圓形 矩形 從起點至終點繪製矩形 橢圓形外框 矩形外框 從起點至終點繪製矩形外框 下載 最佳 雙箭頭 自由繪製 雙線箭頭 箭頭 線條 從起點至終點連為一線繪製路徑 從起點至終點連為一線繪製指向箭頭 從給定的路徑繪製雙指向箭頭 從起點至終點繪製橢圓形 從起點至終點繪製橢圓形外框 直線箭頭 縮放模式 預設值 從給定的圖片中辨識文字,支援 120 種以上的語言 沒有資料 快速 標準 OCR(文字辨識) 圖片中沒有圖片或應用程式找不不到 已下載的語言 可用的語言 垂直網格 欄數 並排 透明度 允許多種語言 強制初始值 在原始路徑覆蓋了名稱為 %1$s 的檔案 放大鏡 在繪圖模式中,在手指頂端啟用放大鏡以達到更好可及性 沒有網路連接,請檢查並再次嘗試,以便下載訓練模型 列數 繪製半透明銳利化螢光筆路徑 為您的繪圖新增一些發光效果 簡單變體 螢光 模糊繪製路線下的影像,以確保隱藏任何您想要的內容 類似隱私模糊,但是像素化而非模糊 在 %1$s - %2$s 範圍內的值 切換按鈕 為使 Tesseract OCR 正常運作,須要將訓練資料(%1$s)下載至您的裝置中。 \n您是否想要下載 %2$s 資料? 找不到「%1$s」目錄,我們已經將其切換至預設目錄,請再次儲存檔案 若需要覆蓋檔案,您需要使用「檔案管理器」圖片來源,嘗試重新選取圖片,我們已變更圖片來源至所需的來源 在選擇的資料夾中原始的檔案將被新的儲存所取代,此選項需要圖片來源為「檔案管理器」或 GetContent,當它開啟時,將被自動設定。 給應用程式評分 評分 一種更簡單的增加尺寸的方法,取代每一個像素為多個相同顏色的像素 儲存至儲存空間將不會被執行,並且圖片將僅被嘗試放入剪貼簿中 僅剪輯 辨識類型 精確度:%1$s 分割模式 刷子將會復原背景,而不是擦除 切換輕觸 滑動 最簡單的 Android 縮放模式,用於幾乎所有的應用程式 允許限制框採用圖片方向 滑動塊 在滑動塊後方繪製陰影 在容器後方繪製陰影 在切換按鈕後方繪製陰影 在懸浮動作按鈕後方繪製陰影 懸浮動作按鈕 在預設按鈕後方繪製陰影 在應用程式列後方繪製陰影 漸層疊加 僅檢測文字及其方向 僅自動 自動 單列文字 單段縱向文字 單段文字 環形詞語 單個字元 鬆散文字 原始行 要刪除「%1$s」語言所有辨識類型的 OCR 訓練資料,還是僅刪除選擇的(%2$s)? 目前 所有 漸變製作器 線性 放射式 平掃式 漸變類型 中心 X 中心 Y 平鋪模式 重複 映象 固定 貼紙 顏色終止點 新增顏色 屬性 Hann Nearest Spline 基本 Catmull 線性插值(或二維雙線性插值)通常可以很好地改變影像的大小,但會導致一些不理想的細節弱化,而且仍會有些鋸齒狀的效果。 更好的縮放方法包括 Lanczos 重取樣和 Mitchell-Netravali 濾波器 對一組控制點進行平滑插值和重取樣的方法,常用於計算機製圖,以建立平滑曲線 訊號處理中經常使用的漸變函數,通過漸變訊號的邊緣,最大限度地減少頻譜洩漏,提高頻率分析的準確性 利用曲線段端點的數值和導數生成平滑連續曲線的數學插值技術 重取樣方法,使用參數可調的卷積濾波器,在縮放影像的清晰度和抗鋸齒之間取得平衡 利用分段定義的多項式函數平滑插值和近似曲線或曲面,靈活、連續地表示形狀 亮度強制 螢幕 轉換 相機 使用相機拍照,注意該影像源只能獲取一張影像 最簡單的預設設定 - 僅顏色 雙線性 Bicubic Lanczos Hermite Mitchell 通過對畫素值應用加權 sinc 函數來保持高質量插值的重取樣方法 自動檢測文字及其方向 單行文字 單個詞語 檢測鬆散文字及其方向 建立指定輸出尺寸的漸變效果,並自定義顏色和外觀類型 合成給定圖像頂部的任意梯度 使用 Pixel 開關 使用類似 Google Pixel 的開關 強制初始檢查 EXIF 部件 在圖片上重複水印,而不是在給定位置重複單個水印 偏移 X 偏移 Y 水印類型 文字顏色 疊加模式 水印 用自定義文字/影像水印覆蓋圖片 重複水印 該圖片將作為水印圖案使用 GIF工具 將影像轉換為 GIF 圖片,或從給定的 GIF 影像中提取幀 GIF 轉換為影像 GIF 檔案轉換為批量圖片 批量影像轉換為 GIF 檔案 影像轉換為 GIF 選擇 GIF 影像以開始 使用第一幀的尺寸 用第一幀尺寸替換指定尺寸 重複次數 毫秒 幀率 使用套索 像在绘图模式中一样使用套索来执行擦除操作 原始影像預覽 Alpha 幀延遲 五彩紙屑 五彩紙屑將顯示在保存、分享和其他主要操作上 安全模式 在最近的應用程式中隱藏內容,也無法進行擷取或錄製。 退出 如果現在離開預覽,則需要重新新增影像 Burkes 抖動演算法 毛刺 數量 種子 浮雕 噪音 像素排序 隨機播放 電子郵件 抖動 量化器 灰階 Bayer 二對二抖動演算法 Bayer 三對三抖動演算法 Bayer 四對四抖動演算法 Bayer 八對八抖動演算法 雙列 Sierra 抖動演算法 Atkinson 抖動演算法 假 Floyd Steinberg 抖動演算法 從左到右抖動 隨機抖動 簡單閾值抖動 Sigma 空間 Sigma 中位數模糊 B Spline 利用片斷定義的雙三次多項式函數平滑插值和近似曲線或曲面,靈活且連續地表示形狀 原生堆疊模糊 傾斜移位 Floyd Steinberg 抖動演算法 Jarvis Judice Ninke 抖動演算法 Sierra 抖動演算法 Sierra 輕型抖動演算法 Stucki 抖動演算法 增強毛刺 通道偏移 X 通道偏移 Y 色彩偏移 X 色彩偏移 Y 三角形模糊 側邊漸變 側邊 頂部 底部 強度 侵蝕 各向異性擴散 擴散 傳導 水平風移 快速雙邊模糊 泊松模糊 對數色調映射 結晶化 描邊顏色 分形噪聲 幅度 大理石 湍流 油畫 尺寸 頻率 X 幅度 X 幅度 Y Perlin 扭曲 Hable 膠片式色調映射 Hejl Burgess 色調映射 ACES 膠片色調映射 ACES Hill 色調映射 速度 去霧 歐米茄 顏色矩陣 4x4 顏色矩陣 3x3 基礎效果 拍立得 三維空間 棕褐色 色彩偏移程度 頻率 Y 水紋效果 第一色盲 第二色盲 復古 Coda Chrome 夜視 溫暖 涼爽 粉紅夢 綠色盲 紅色盲 不全色盲 炎熱的夏天 圖示形狀 全色盲 Tritanopia 黃金時段 紫霧 日出 柔和的春光 七彩漩渦 秋天的色調 檸檬水燈 薰衣草之夢 賽博朋克 光譜火 夜間魔法 色彩爆炸 在卡片的前導圖示下方新增具有所選形狀的容器 微粒 非銳化 粉彩 橘色薄霧 奇幻風景 電梯度 焦糖黑度 未來派漸變 綠太陽 彩虹世界 深紫色 空間傳送 紅色漩渦 數位代碼 散景 應用程式列表情符號將隨機變更 隨機表情符號 停用表情符號時無法使用隨機表情符號選擇 啟用隨機選擇表情符號時無法選擇表情符號 老式電視 隨機模糊 最喜歡的 尚未新增最喜歡的濾鏡 影像格式 德拉戈 奧爾德里奇 隔斷 內村 莫比烏斯 頂峰 顏色異常 過渡 影像在原始目的地被覆蓋 啟用覆蓋檔案選項時無法變更影像格式 表情符號作為配色方案 使用表情符號原色作為應用程式配色方案,而不是手動定義的配色方案 從圖像創建「Material You」調色板 暗色 使用夜間模式配色方案而不是燈光變體 複製為「Jetpack Compose」代碼 環形模糊 交叉模糊 圓圈模糊 星光模糊 線性移軸 要刪除的標籤 APNG 工具 APNG 轉圖片 將一系列圖片轉換為 APNG 檔案 圖片轉 APNG 將 APNG 檔案轉換為一系列圖片 選擇 APNG 圖片以開始 將圖片轉換為 APNG 圖片或從給定的 APNG 圖片中提取幀 運動模糊 從給定的檔案或圖像創建 Zip 檔案 Zip 拖曳手柄寬度 五彩紙屑類型 節日 霹靂 邊角 JXL 工具 無品質損失地執行 JXL ~ JPEG 轉碼,或將 GIF/APNG 轉換為 JXL 動畫 JXL 轉 JPEG 執行從 JXL 到 JPEG 的無損轉碼 執行從 JPEG 到 JXL 的無損轉碼 JPEG 轉 JXL 選擇 JXL 影像開始 快速高斯模糊 2D 快速高斯模糊 3D 快速高斯模糊 4D 自動貼上 允許應用程式自動貼上剪貼簿資料,因此它將顯示在主介面上,您將能夠處理它 協調顏色 協調水平 生成預覽 選擇多種媒體 選擇單一媒體 選取 通道配置 今天 昨天 嵌入式選擇器 Image Toolbox 的圖片選取器 無權限 請求 Lanczos Bessel GIF 轉 JXL 將 GIF 圖像轉換為 JXL 動畫圖片 APNG 轉 JXL 將 APNG 圖像轉換為 JXL 動畫圖片 JXL 轉圖像 將 JXL 動畫轉換為大量圖片 影像轉 JXL 將大量圖片轉換為 JXL 動畫 行為 略過檔案選取 如果可能的話,檔案選擇器將立即顯示在所選畫面上 啟用預覽生成,這可能有助於避免在某些裝置上出現崩潰,同時也會停用單一編輯選項中的某些編輯功能 有損壓縮 使用有損壓縮而不是無損壓縮來減少檔案大小 透過對像素值應用貝塞爾(jinc)函數來保持高品質插值的重採樣方法 壓縮方式 控制生成圖像的解碼速度,這將有助於更快地打開生成圖像,%1$s 表示最慢的解碼速度,而%2$s 表示最快的解碼速度,此設定可能會增加輸出圖像的大小 篩選 日期 日期(反向) 名稱 名稱(反向) 再試一次 橫向顯示設定 如果停用此選項,則在橫向模式下,設定將始終在頂部應用列中的按鈕上開啟,而不是始終可見的選項 全螢幕設定 開關類型 Compose 將「啟用」設定為「設定頁面總是全螢幕開啟,而不是可滑動抽屜頁」。 Jetpack Compose Material You 開關 Material You 開關 最大 Pixel Fluent Cupertino 基於「Cupertino」設計系統的開關 調整錨點大小 基於「Fluent」設計系統的開關 預設線寬 影像轉 SVG 將給定圖像追蹤為 SVG 圖像 使用取樣調色板 如果啟用此選項,將對量化調色板進行取樣 路徑省略 不建議使用此工具在不縮小尺寸的情況下追蹤大圖像,它可能會導致崩潰並增加處理時間 縮小影像 在處理之前圖像將被縮小到較低的尺寸,這有助於工具更快、更安全地工作 最小色彩比例 線路閾值 二次閾值 座標捨入容限 路徑比例 重置屬性 所有屬性將設定為預設值,請注意此動作無法復原 詳細 新增資料夾 每個樣本的位元數 光度測定解釋 每像素樣本數 平面配置 Y Cb Cr 子取樣 Y Cb Cr 定位 X 解析度 Y 解析度 解析度單位 JPEG 交換格式 JPEG 交換格式長度 轉移功能 白點 主要色度 Y Cb Cr 系數 參考黑白色階 圖片描述 型號 軟體 藝術家 版權 Exif 版本 Flashpix 版本 伽瑪 色彩空間 使用者評論 相關的聲音檔案 原始日期時間 數位化日期時間 OECF 閃光燈 對比 飽和度 引擎模式 LSTM 網絡 日期時間 轉換 將圖片批次轉換為給定的格式 壓縮方法 時間偏移 原始時間偏移 曝光時間 光圈(F 數值) 曝光模式 感光度(ISO值) 光圈值 舊式 舊式與 LSTM 製作 創作者備註 X 軸尺寸(Pixel) Y 軸尺寸(Pixel) 感光度 光譜靈敏度 建議曝光指數 亮度 測光模式 焦距 閃光能量 曝光指數 檔案來源 色彩濾波矩陣 主題位置 客製化渲染 曝光模式 白平衡 數位變焦比率 35mm 等效焦距 場景類型 增益控制 銳利化 裝置設定描述 主體距離範圍 圖像唯一識別碼 相機擁有者 機身序號 鏡頭資訊 鏡頭製造商 鏡頭型號 鏡頭序號 GPS 版本 GPS 緯度參考 GPS 經度參考 GPS 經度 GPS 高度 GPS 時間戳記 GPS 衛星 GPS 狀態 GPS 定位模式 GPS DOP(經度衰減因子) GPS速度參考值 GPS速度 Ginseng 一種設計給高品質影像的重新取樣濾鏡,可以在銳利度和平滑度之間取得美好的平衡 永遠講掰掰 GPS 緯度 GPS 高度參考 Lanczos 6 濾波器的一種變體,使用 Jinc 函數來提升圖像重取樣的質品質。 快門速度 曝光偏差 最大光圈值 標準輸出靈敏度 拍攝距離 文件掃描器 掃描文件並創建PDF或圖像 開始掃描 保存為PDF 以PDF分享 反鋸齒 新增圖像 保存為QR Code 刪除模板 掃描的 QR Code 不是有效的濾鏡範本 掃描 QR Code 建立模板 模板名稱 濾鏡模板 新建 儲存為檔案 您將刪除此模板。此操作無法復原 濾鏡預覽 QR Code 剝離偏移 每條帶行數 剝離位元組計數 原始秒內細分時間 數位化秒內細分時間 靈敏度類型 ISO 感光度 緯度 yyy ISO 感光度 緯度 zzz 主體領域 空間頻率響應 焦平面 X 分辨率 焦平面 Y 分辨率 焦平面解析度單元 感測方式 GPS軌跡 GPS 軌跡參考 GPS 地圖日期 每像素壓縮位數 偏移時間數位化 秒內細分時間 GPS 影像方向參考 GPS 影像方向 GPS 目標緯度參考 語言匯入成功 備份 OCR 模型 匯入 匯出 位置 目標圖片 調色盤轉移 色彩工具 色彩調和 啟用時間戳記格式替代檔案名稱中的基本毫秒 啟用時間戳以選擇其格式 邊緣模式 難以區分綠色與紅色色調 字體大小 浮水印尺吋 已選擇的顏色 GPS 目標經度 批次將圖片轉換至另一種格式 預設裁切尺吋 感測器頂部邊框 在設定中授予相機權限給文件掃描器 以下選項用於儲存圖片,而非 PDF GPS 目標距離參考 色彩將與主題中的設定完全一致 降低對所有顏色的敏感度 GPS 目標經度參考 感測器右邊框 混合、製作色調、產生色調等等 掃描 QR code 並獲取其內容,或貼上您的字串以產生新的 QR code 自動 Cubic TIFF 壓縮方案 取代濾鏡 星形外框 邊框顏色 預設繪製顏色 GPS 目標緯度 圖片分割 依照行或列分割圖片 選擇一個濾鏡以將其作為顏料 一次性儲存位置 無法感知紅色調 要混合的顏色 連結預覽 連結 格式轉換 線性 寬高比框架 感測器底部邊框 感測器左邊框 重複文字 星形 點擊以開始掃圖 輸入百分比 允許由文字框輸入 網格尺吋 X 網格尺吋 Y 裁切為內容 忽略顏色 範本 沒有加入範本濾鏡 選擇的檔案沒有濾鏡範本資料 該圖片將被用於預覽此濾鏡範本 作為 QR Code 圖片 作為檔案 新增了濾鏡範本,其名稱為「%1$s」(%2$s) QR 描述 最小 在設定中授予相機權限以掃描 QR code 使用選取的混合模式將圖片堆疊在一起 色彩校正 選擇模式,為選擇的色盲變體調整主題顏色 難以區分紅色與綠色色調 難以區分藍色與黃色色調 無法感知綠色色調 無法感知藍色調 完全色盲,只能看到灰色陰影 ­不要使用色盲方案 選擇以下的濾鏡以將其作為繪畫中用的筆刷 左下 右下 中右 中下 中左 新增最愛 未選擇最愛的選項,請在工具頁面中新增它們 色彩陰影 變體 顏色混合 顏色資訊 取得中性的 LUT 圖片 轉換 WEBP 檔案為一批圖片 轉換一批圖片為 WEBP 檔案 轉換 GIF 圖片為 WEBP 動畫圖片 WEBP 工具 轉換圖片為 WEBP 動畫圖片,或者從給予的圖片提取影格 選取 WEBP 圖片以開始 無法完整的存取檔案 預設繪製路徑模式 新增時間戳記 啟用在檔案名稱中加入時間戳記 格式化時間戳記 最近使用過 取得關於應用程式的新版本,與閱讀公告的通知 預設值 隱藏全部 顯示全部 隱藏導覽列 隱藏狀態列 噪點產生 頻率 噪點類型 旋轉類型 距離功能 返回類型 自訂檔案名稱 自動裁切 圖片堆疊 中心 左上 右上 中上 代碼內容 GPS 目標距離 GPS 區域資訊 GPS 水平定位誤差 DNG 版本 預覽圖片長度 使用給定字體和顏色沿路徑繪製文本 當前文本將重複繪製直到路徑結束而非僅繪製一次 虛線長度 使用所選圖片沿著給定路徑繪製 檢視圖片起始位置 計算 核對和 文字雜湊 選取檔案以計算它基於所選演算法的核對和 輸入文字以計算它基於所選演算法的核對和 要比較的核對和 符合! 核對和不相同,檔案安全 不符合 核對和不相同,檔案可能不安全! ISO 批次比較 選取一或多個檔案以計算它(們)基於所選演算法的核對和 嘗試儲存時發生錯誤,請嘗試變更輸出資料夾 未設定檔名 從起點至終點繪製多邊形 核對和工具 演算法 比較核對和、計算雜湊值,或使用不同雜湊演算法從檔案建立十六進位字串 提供的值不是有效的 Base64 字串 GIF 轉 WEBP 外框顏色 清除選擇 選取檔案 選取目錄 複製 Base64 輸入 % 與第一個選項相同,但以顏色代替圖片 選擇用於儲存目前圖片的位置與檔案名稱 輕觸以編輯可用的標籤 變更貼紙 編輯 EXIF 變更單一圖片的詮釋資料但不需要重新壓縮 選項 動作 分享 Base64 從起點至終點繪製三角形外框 從起點至終點繪製三角形外框 三角形外框 多邊形外框 三角形 多邊形 從起點至終點繪製多邊形外框 繪製正多邊形 頂點 從起點至終點繪製星形外框 從起點至終點繪製星形 繪製等邊星形 內部半徑比 啟用反鋸齒以防止出現尖銳的邊緣 開啟編輯而不是預覽 適合邊界 分割互補色 WEBP 轉(多個)圖片 (多個)圖片轉 WEBP 允許存取所有檔案以檢視 JXL、QOI 以及其它未被 Android 識為圖片檔案的其它圖片。若未授予權限您將無法在此檢視這些圖片 檢視與編輯一次性儲存位置,您可以在大多數選項中通過長按儲存按鈕來使用它們 系統列可見性 通過滑動顯示系統列 工具排列 依照類型分組工具 在主介面依照類型分組工具,而不是依自訂列表排列 調整圖片以適合給定的尺寸,並使用模效果或色彩作為背景 產生不同的噪點,例如 Perlin 或其他類型 儲存至自訂名稱的資料夾中 拼貼畫製作器 拼貼類型 按住圖片進行交換、移動與縮放以調整位置 使用圖片製作拼貼畫 對齊 自訂選項 線條樣式 選擇要釘選的工具 建立捷徑 沿路徑以指定間距繪製選擇的圖形 蓋章 佈局 預覽圖片 隱藏 顯示 滑動塊類型 置中對話框按鈕 如果可能,對話框的按鈕將位於中間而不是左邊 區域 標記圖層 編輯圖層 圖片上的圖層 背景圖層 快速設定側邊欄 Base64 工具 解碼 Base64 字串為圖片,或編碼圖片為 Base64 格式 Base64 設定群組「%1$s」將被預設為摺疊 設定群組「%1$s」將被預設為展開 貼上 Base64 載入圖片以複製或儲存 Base64 字串。如果您已有 Base64 字串,您可以在上方貼上它以獲得圖片 匯入 Base64 新增外框文字 新增具有指定顏色與寬度的外框文字 儲存 Base64 外框大小 核對和為檔名 旋轉 工具將會被新增至您的主畫面應用程式作為捷徑,使用它結合「略過檔案選取」設定來實現所需的行為 時間戳記 填充 格式化模式 印章 適合高度 自訂頁面 頁面選擇 反向選擇 已匯入的字型 適合寬度 來源核對和 輸出圖片將具有相對於它們資料核對和的名稱 繪製等邊多邊形,而不是自由形式的形狀 圖層模式,可自由放置圖片、文字等等 使用圖片作為背景,並且在其上新增不同的圖層 匯入字型(TTF/OTF) 僅限 TTF 與 OTF 字型可以被匯入 匯出字型 無法複製空或無效的 Base64 字串 繪製等邊星形,而不是自由形式的形狀 當您在 ImageToolbox 中選擇圖片要開啟(預覽)時,將開啟編輯選單而不是預覽 掃描 QR Code 以取代欄位中的內容,或輸入內容以產生新的 QR 代碼 啟用滑動以顯示系統列(如果它們被隱藏) 工具退出確認 如果您在使用特定工具並嘗試關閉它時有未儲存的變更,將會顯示確認對話框 GPS 日期戳記 開放原始碼授權 低多邊形 在可以獲取文字的地方(QRCode,、OCR 等等)啟用連結預覽檢索 群組 正在開啟 正在關閉 應用 檢視在此應用程式中使用的開放原始碼函式庫的授權 當動態色彩開啟時無法使用莫內色彩 在 Android 合作夥伴頻道中有更多實用軟體 精美滑動塊,這是預設選項 Material 2 滑動塊 Material You 滑動塊 當編輯圖片時,在所選的一邊加入懸浮條,當點擊後將開啟快速設定 柔光 三原色 柔和優雅變體 HDR 彩色海報 線性方框模糊 高斯方框模糊 線性高斯模糊 以指定的間隙大小沿著繪製路徑繪製虛線 沿著路徑繪製波浪狀的鋸齒形 重置曲線 間隙大小 GPS 處理方法 啟用色調映射 此圖片將被作為繪製路徑的重複元素使用 線性堆疊模糊 線性快速高斯模糊 Next 線性快速高斯模糊 沙畫 波卡點 互補色 色調 柔和優雅 調色盤轉移變體 咖啡 自由角 依多邊形裁切圖片,這也可以修正透視 強制點到圖片邊界 虛線 點虛線 鋸齒形 鋸齒形比率 沿著指定的路徑繪製點與虛線 色調曲線 曲線將回滾至預設值 僅預設直線 恆定品質因子(CRF) 主介面標題 變更濾鏡的預設圖片預覽 互通性指標 點將不受圖片邊界的限制,這有助於更精確的透視修正 GPS 差分 秋色 遮罩 Lanczos 3 Lanczos 4 Lanczos 2 Jinc Quadric EWA 第三色 Clahe Oklab Clahe Oklch Clahe Jzazbz 增強的油畫 類似色 + 互補色 方形配色 簡易老式電視 線性三角形模糊 線性高斯方框模糊 S 形 Lagrange 2 Lagrange 3 2 級的 Lagrange 插值濾鏡,適合用於平化過度的高品質圖片縮放 3 級的 Lagrange 插值濾鏡,對於圖片的縮放提供了更好的準確性與平滑效果 Quadric Welch 高斯 Sphinx Lanczos 3 Jinc Lanczos 4 Jinc 利用分段定義的多項式函數平滑插值和近似曲線或曲面,靈活、連續地表示形狀 一種窗函數,用於通過漸變訊號邊緣來減少頻譜洩漏,在訊號處理中非常有用 一種窗函數,藉由最小化頻譜洩漏來提供良好的頻率解析度,通常用於訊號處理 一種窗函數,旨在提供良好的頻率解析度,減少頻譜洩漏,通常用於訊號處理應用 一種高品質插值方法,最佳化了自然圖片的調整尺寸、平衡清晰度與平滑度 一種 Robidoux 方法的更清晰的變體,最佳化了清晰圖片的調整尺寸 纏繞 自由軟體(合作夥伴) Hanning 濾鏡的橢圓加權(EWA)變體,用於平滑插值和重新採樣 Lanczos 4 Sharpest EWA 柯達底片 Lanczos Soft EWA 陰影 使用 jinc 函數的 Lanczos 2 濾鏡變體,提供最小偽影的高品質插值 等化直方圖自適應 HSL 無法存取此網站,請嘗試使用 VPN 或檢查 URL 是否正確 精緻 使用像素面積關係進行重新取樣。這可能是圖片抽取的一種首選方法,因為它能產生無摩爾紋的結果。但當對圖片進行放大時,它與最近「Nearest」類似 。 單元格高度 等化直方圖像素化 等化直方圖自適應 等化直方圖自適應 LUV 等化直方圖 Clahe Clahe LAB Clahe LUV Hamming Hanning Bartlett B-Spline Blackman Robidoux Robidoux Sharp Spline 16 Spline 36 Spline 64 Kaiser Bartlett-Hann Lanczos 2 Bohman Box 一種應用高斯函數的插值方法,用於平滑化與減少圖片中的噪點 一種先進的重新取樣方法,提供最小偽影的高品質的插值 一種三角窗函數,用於訊號處理以減少頻譜洩漏 一種結合了 Bartlett 與 Hann Windows 的混合窗函數,用於減少信號處理中的頻譜洩漏 一種以 Spline 為基礎的插值方法,使用了 16-tap 濾鏡提供了平滑效果 一種以 Spline 為基礎的插值方法,使用了 36-tap 濾鏡提供了平滑效果 一種以 Spline 為基礎的插值方法,使用了 64-tap 濾鏡提供了平滑效果 一種簡易重新取樣方法,使用鄰近像素值的平均,通常會產生塊狀外觀 使用 2-lobe Lanczos 濾鏡的一種重新取樣方法,以獲得最小偽影的高品質插值 使用 3-lobe Lanczos 濾鏡的一種重新取樣方法,以獲得最小偽影的高品質插值 一種用於減少光譜洩漏的窗函數,在訊號處理應用中提供良好的頻率解析度 使用 4-lobe Lanczos 濾鏡的一種重新取樣方法,以獲得最小偽影的高品質插值 使用 jinc 函數的 Lanczos 3 濾鏡變體,提供最小偽影的高品質插值 Hanning EWA Robidoux EWA Robidoux 濾鏡的橢圓加權平均(EWA)變體,以獲得高品量的重新取樣 Blackman EWA Blackman 濾鏡的橢圓加權平均(EWA)變體,以獲得最小化振鈴偽影 Lanczos 3 Jinc EWA Robidoux Sharp EWA Robidoux Sharp 濾鏡的橢圓加權平均(EWA)變體,以獲得更清晰的效果 Quadric 濾鏡的橢圓加權平均(EWA)變體,以獲得平滑的插值 Lanczos 3 Jinc 濾鏡的橢圓加權平均(EWA)變體,以獲得減少混疊的高品質重新取樣 Lanczos Sharp EWA Ginseng EWA Ginseng 濾鏡的橢圓加權平均變體,以獲得增強的影像品質 Lanczos 3 Sharp 濾鏡的橢圓加權平均(EWA)變體,以實現最小偽影的清晰效果 Lanczos 4 Sharpest 濾鏡的橢圓加權平均(EWA)變體,以獲得極其清晰的影像重新取樣 Lanczos Soft 濾鏡的橢圓加權平均(EWA)變體,以獲得更平滑的影像重新取樣 Clahe HSL 等化直方圖自適應 HSV Clahe HSV Lanczos 6 6 級的 Lanczos 重新取樣濾鏡,提供更清晰與更加準確的圖片縮放 Lanczos 6 Jinc 簡易素描 高譚 群集式 2x2 抖動 群集式 4x4抖動 Yililoma 抖動 三角配色 類似色 四角配色 淡色 霧夜 跳漂白 燭光 復古黃 金色森林 流行藝術 賽璐珞 分形類型 加權強度 乒乓強度 抖動 領域扭曲 Tesseract 選項 選項應照以下模式輸入: \"--{選項名稱} {值}\" 為 Tesseract 引擎應用一些輸入變數 黑帽 交叉淡入/出 處置預留影格,使其不會互相堆疊 影格之間將交叉淡入淡出 交叉淡入/出影格數量 閾值一 閾值二 網格顏色 緊湊型選擇器 一些選擇控制元件將使用緊湊的佈局以減少佔用空間 在設定中授予相機權限以擷取圖片 增強的縮放模糊 輔助網格 單元格寬度 Canny 簡易拉普拉斯運算子 簡易索伯運算子 Material 2 不要堆疊影格 群集式 8x8 抖動 網格尺寸 X 軸解析度 直方圖 此圖片將用於產生 RGB 與亮度直方圖 解析度 GPS 目的地方位角 GPS 目的地方位角參考 點對點 高帽 Cubic 插值藉由考慮最接近的 16 像素來實現更平滑的縮放,給出比 Bilinear 更好的結果 使用 jinc 函數的 Lanczos 4 濾鏡變體,提供最小偽影的高品質插值 一種使用二次函數進行插值的方法,提供平滑與連續的效果 Hann 窗函數的一種變體,通常用於減少訊號處理應用中的頻譜洩漏 Haasn Soft 一種由 Haasn 設計的重新取樣濾鏡,以獲得平滑與無偽影的圖像縮放 Y 軸解析度 RGB 或亮度圖片直方圖幫助您進行調整 等化直方圖 HSV 等化直方圖自適應 LAB Beta 下載 LUT 集合,您可以在下載之後應用它們 LUT 函式庫 在繪圖區域上方顯示輔助網格以協助精確繪畫操縱 %1$s 代表緩慢的壓縮,因而產生相對較小的檔案。%2$s 代表較快的壓縮,因而產生較大的檔案。 更新 LUT 集合(只有新的項目會被加入下載隊列),您可以在下載之後應用它們 圖片裁剪 通過垂直或水平線剪除部分圖片並合併其餘部分(可逆向操作) 垂直樞軸線 水平樞軸線 垂直剪下部分將被保留,而不是合併剪下區域周圍的部分 水平剪下部分將被保留,而不是合併剪下區域周圍的部分 像素比較類型 掃描條碼 條碼類型 條碼圖片將為完全黑白,而不是取決於應用程式主題彩色 掃描任何條碼(QR、EAN、AZTEC…)並獲得其內容,或貼上您的文字以產生新的條碼 強制黑白 啟用預設集後方的文字欄位,以便立即輸入 箱數 目標 LUT 圖片 512x512 2D LUT LUT(色彩查找表) 3D LUT 目標 3D LUT 檔案 (.cube / .CUBE) 50 號底片 CI 頻道 加 入我們的聊天頻道,您可以討論任何想要的議題,還可以查看我發布 Beta 和公告的 CI 頻道。 八分音符 在繪製路徑下進行內容感知填滿 點狀癒合 高亮顏色 以自訂節點數量與解析度建立網格梯度 網格梯度 剪輯 比例色彩空間 高度比 一種使用 Kaiser 窗的插值方法,圖供良好控制主瓣寬度與旁瓣水平之間的權衡 Amatorka Miss Etikate 藍色衰減 銳利琥珀 淡綠 ICO 檔案的最大儲存尺寸僅為 256*256 Telegram 中的 Image Toolbox 🎉 Lacunarity 增益 使用圓形內核 形態梯度 Base64 動作 頭部長度縮放 網格梯度重疊 將裁切調整尺寸模式與此參數結合,以實現所需行為(裁切/適合寬高比) 首先,使用您最愛的照片編輯應用軟體將一個濾鏡應用於中性 LUT,您可以在此獲取它。為此正常工作,每個像素顏色不能依賴於其它像素(例如,模糊化將無法正常工作)。一旦準備就緒,使用您新的 LUT 圖片作為輸入用於 512*512 LUT 濾鏡。 鏡子 101 檢視線上網格梯度集合 在給定圖片頂部合成網格梯度 網格梯度集合 自訂點 找不到條碼 產生的條碼將展示在此處 從音訊檔案匯出專輯封面圖片,支援大多數常見的格式 音訊封面匯出器 選取音訊以開始 選取音訊 找不到封面 傳送日誌 點擊以分享應用程式日誌檔案,這可以幫助我發現並解決問題 哎呀…發生了一些錯誤 您可以使用下方的選項聯絡我,我將嘗試找到解決方案。(不要忘記附加日誌) 寫入檔案 寫入詮釋資料 自動移除紅眼 使用 LSB 修改日期 大小 大小(反向) MIME 類型 MIME 類型(反向) 修改日期(反向) 副檔名 副檔名(反向) 新增日期 新增日期(反向) 密碼 解鎖 PDF 已受保護 由左至右 由右至左 由上至下 由下至上 禁用旋轉 防止用兩指手勢旋轉圖像 啟用對齊邊框 移動或縮放後,圖像將對齊以填充框架邊緣 從一批圖像中提取文字並將其儲存在一個文字檔案中 從每張圖像中提取文字並將其放入相關照片的 EXIF 資訊中 隱形模式 使用隱寫術在圖像字節內創建肉眼看不見的水印 將使用 LSB(低有效位)隱寫術方法,否則使用 FD(頻域) 操作即將完成。現在取消需要重新啟動 液態玻璃 基於最近發佈的 IOS 26 及其液態玻璃設計系統的交換機 選擇下面的圖像或貼上/匯入 Base64 資料 輸入圖像連結以開始 貼上連結 萬花筒 次要角度 側面 頻道組合 藍綠色 紅藍 綠紅 成紅色 走進綠色 變成藍色 青色 品紅 黃色的 彩色半色調 輪廓 等級 抵消 沃羅諾伊結晶 形狀 拉緊 隨機性 去斑 擴散 第二半徑 均衡 輝光 旋轉和捏 點化 邊框顏色 極座標 直角到極座標 極地到直角 翻轉成圓圈 減少噪音 簡單的日曬 編織 X 間隙 Y 間隙 X 寬度 Y 寬度 橡皮戳 塗抹 密度 混合 球面透鏡畸變 折射率 展開角 火花 射線 ASCII 坡度 瑪麗 秋天 噴射 冬天 海洋 夏天 春天 酷變體 單純皰疹病毒 粉色的 熱的 單詞 岩漿 地獄 等離子體 維裡迪斯 公民 暮光轉移 透視自動 相差校正 允許裁切 裁切或透視 絕對 渦輪 深綠色 鏡頭校正 JSON 格式的目標鏡頭組態檔案 下載現成的鏡頭組態檔案 零件百分比 匯出為 JSON 將帶有調色盤資料的字串複製為 json 表示形式 縫雕 主畫面 鎖定螢幕 內建 桌布匯出 重新整理 獲取當前的主頁、鎖和內建桌布 允許存取所有檔案,這是檢索桌布所必需的 管理外部儲存權限還不夠,您需要允許存取您的圖像,請務必選擇「允許全部」 將預設添加到檔名 將帶有所選預設的尾綴附加到圖像檔名 將圖像縮放模式添加到檔名 將具有所選圖像縮放模式的尾綴附加到圖像檔名 ASCII 藝術 將圖片轉換為 ascii 文字,看起來像圖像 參數 在某些情況下對圖像應用負濾鏡以獲得更好的結果 處理截圖 截圖未捕獲,請重試 跳過保存 跳過 %1$s 檔案 如果較大則允許跳過 如果生成的檔案大小大於原始檔案大小,某些工具將被允許跳過保存圖像 日曆事件 聯絡人 電子郵件 地點 電話 文字 簡訊 網址 無線上網 開放網路 不適用 SSID 電話 訊息 地址 主題 身體 姓名 組織 標題 電話 電子郵件 網址 地址 概括 描述 地點 組織者 開始日期 結束日期 地位 緯度 經度 創建條碼 編輯條碼 無線網路組態 安全 選擇聯絡方式 在設定中授予聯絡人使用所選聯絡人自動填充的權限 聯絡方式 中間名 發音 添加電話 添加電子郵件 添加地址 網站 添加網站 格式化名稱 該圖像將用於放置在條碼上方 代碼定製 該圖像將用作二維碼中心的徽標 標識 徽標填充 標誌尺寸 標誌角 第四隻眼 通過在底端角添加第四隻眼睛,為二維碼添加眼睛對稱性 像素形狀 鏡框形狀 球形 錯誤校正等級 深色 淺色 超級操作系統 類似小米 HyperOS 的風格 面具圖案 此代碼可能無法掃描,請更改外觀參數以使其在所有裝置上都可讀 不可掃描 工具將看起來像主畫面應用程式啟動器,更加緊湊 啟動器模式 使用選定的畫筆和樣式填充區域 洪水填充 繪製塗鴉風格的路徑 方形顆粒 噴霧顆粒將是方形而不是圓形 調色盤工具 從圖像生成基本/材質調色盤,或跨不同調色盤格式匯入/匯出 編輯調色盤 跨多種格式匯出/匯入調色盤 顏色名稱 調色盤名稱 調色盤格式 將生成的調色盤匯出為不同的格式 向當前調色盤添加新顏色 %1$s 格式不支持提供調色盤名稱 由於 Play 商店政策,此功能無法包含在當前版本中。要存取此功能,請從其他來源下載 ImageToolbox。您可以在下面的 GitHub 上找到可用的版本。 開啟Github頁面 原始檔案將被新檔案替換,而不是保存在所選檔案夾中 檢測到隱藏水印文字 檢測到隱藏水印圖像 該圖片已被隱藏 生成修復 允許您使用 AI 模型刪除圖像中的對象,而無需依賴 OpenCV。要使用此功能,應用程式將從 GitHub 下載所需的模型(約 200 MB) 允許您使用 AI 模型刪除圖像中的對象,而無需依賴 OpenCV。這可能是一個長時間運行的操作 錯誤等級分析 亮度梯度 平均距離 複製移動檢測 保持 係數 剪貼板資料太大 資料太大無法複製 簡單的編織像素化 交錯像素化 交叉像素化 微觀宏觀像素化 軌道像素化 渦旋像素化 脈衝網格像素化 核像素化 徑向編織像素化 無法開啟 uri \"%1$s\" 降雪模式 啟用 邊框 故障變體 頻道轉換 最大偏移量 錄像帶 塊故障 塊大小 CRT曲率 曲率 色度 像素融化 最大落差 人工智能工具 通過人工智能模型處理圖像的各種工具,例如偽影消除或去噪 壓縮、鋸齒線 卡通、廣播壓縮 一般壓縮、一般噪音 無色卡通噪音 快速、一般壓縮、一般噪音、動畫/漫畫/動漫 書籍掃描 曝光校正 最擅長一般壓縮、彩色圖像 最擅長一般壓縮、灰度圖像 一般壓縮,灰度圖像,更強 一般噪聲、彩色圖像 一般噪點、彩色圖像、更好的細節 一般噪聲、灰度圖像 一般噪點,灰度圖像,較強 一般噪聲,灰度圖像,最強 一般壓縮 一般壓縮 紋理化、h264 壓縮 VHS 壓縮 非標準壓縮(cinepak、msvideo1、roq) Bink 壓縮,幾何效果更好 Bink 壓縮,更強 Bink 壓縮,柔軟,保留細節 消除階梯效應,平滑 掃描藝術品/圖紙、輕度壓縮、莫爾條紋 色帶 緩慢,消除半色調 用於灰度/黑白圖像的通用著色器,為了獲得更好的結果,請使用 DDColor 邊緣去除 消除過度銳化 緩慢、抖動 抗鋸齒、一般偽像、CGI KDM003 掃描處理 輕量級圖像增強模型 壓縮偽影去除 壓縮偽影去除 繃帶去除效果順利 半色調圖案處理 抖動模式去除 V3 JPEG 偽影去除 V2 H.264 紋理增強 VHS 銳化和增強 合併 塊大小 重疊尺寸 超過 %1$s 像素的圖像將被切成塊進行切片和處理,重疊混合這些以防止可見的接縫。 大尺寸可能會導致低階裝置不穩定 選擇一個開始 您想刪除 %1$s 模型嗎?您需要重新下載 確認 型號 下載模型 可用型號 準備中 主動模型 無法開啟會話 只能匯入 .onnx/.ort 模型 進口型號 匯入自定義onnx模型以進一步使用,僅接受 onnx/ort 模型,支持幾乎所有 esrgan 之類的變體 進口型號 一般噪聲、彩色圖像 一般噪點,彩色圖像,較強 一般噪聲、彩色圖像、最強 減少抖動偽影和色帶,改善平滑的漸變和平坦的顏色區域。 通過平衡高光增強圖像亮度和對比度,同時保留自然色彩。 提亮暗圖像,同時保留細節並避免過度曝光。 消除過多的色調並恢復更加中性和自然的色彩平衡。 應用基於泊松的噪聲色調,重點是保留精細的細節和紋理。 應用柔和的泊松噪點色調以獲得更平滑且不那麼激進的視覺效果。 均勻的噪點色調側重於細節保留和圖像清晰度。 柔和均勻的噪音色調,帶來微妙的紋理和光滑的外觀。 通過重新繪製偽影並提高圖像一致性來修復損壞或不均勻的區域。 輕量級去帶模型,以最小的性能成本消除色帶。 最佳化具有非常高壓縮偽影(0-20% 品質)的圖像,以提高清晰度。 增強具有高壓縮偽影(20-40% 品質)的圖像,恢復細節並減少噪點。 通過適度壓縮(40-60% 品質)改進圖像,平衡清晰度和平滑度。 通過輕度壓縮(60-80% 品質)細化圖像,以增強微妙的細節和紋理。 稍微增強近乎無損的圖像(80-100% 品質),同時保留自然的外觀和細節。 著色簡單快速,卡通,不理想 稍微減少圖像模糊,提高清晰度,而不會引入偽影。 長時間運行的操作 處理圖像 加工 消除品質極低的圖像 (0-20%) 中嚴重的 JPEG 壓縮偽影。 減少高度壓縮圖像中強烈的 JPEG 偽影 (20-40%)。 清除中等程度的 JPEG 偽影,同時保留圖像細節 (40-60%)。 改善相當高品質圖像 (60-80%) 中的輕微 JPEG 偽影。 巧妙地減少近乎無損圖像中的輕微 JPEG 偽影 (80-100%)。 增強精細細節和紋理,提高感知清晰度,而不會出現嚴重偽影。 加工完成 處理失敗 增強皮膚紋理和細節,同時保持自然外觀,最佳化速度。 消除 JPEG 壓縮偽影並恢復壓縮照片的圖像品質。 減少在弱光條件下拍攝的照片中的 ISO 噪點,保留細節。 糾正過度曝光或「巨型」高光並恢復更好的色調平衡。 輕量級快速著色模型,為灰度圖像添加自然色彩。 DEJPEG 去噪 著色 文物 提高 日本動畫片 掃描 高檔 用於一般圖像的 X4 升級器;使用較少 GPU 和時間的微型模型,具有適度的去模糊和降噪功能。 X2 放大器適用於一般圖像,保留紋理和自然細節。 X4 升級器,適用於具有增強紋理和逼真效果的一般圖像。 X4 upscaler 針對動漫圖像進行了最佳化; 6 個 RRDB 塊,線條和細節更清晰。 具有 MSE 損失的 X4 升頻器可產生更平滑的結果並減少一般圖像的偽影。 X4 Upscaler 針對動漫圖像進行了最佳化; 4B32F 型號具有更銳利的細節和流暢的線條。 X4 UltraSharp V2 模型,適用於一般圖像;強調銳度和清晰度。 X4 UltraSharp V2 Lite;更快、更小,在使用更少 GPU 記憶體的同時保留細節。 用於快速背景去除的輕量級模型。平衡的性能和準確性。適用於肖像、物體和場景。推薦用於大多數用例。 刪除背景 水平邊框厚度 垂直邊框厚度 %1$s 顏色 當前模型不支持分塊,圖像將以原始尺寸處理,這可能會導致高記憶體消耗和低階裝置問題 禁用分塊,圖像將以原始尺寸進行處理,這可能會導致高記憶體消耗和低階裝置的問題,但可能會給出更好的推理結果 分塊 高精度背景去除圖像分割模型 U2Net 的輕量級版本,可以以更小的記憶體使用更快地去除背景。 完整的 DDColor 模型可為一般圖像提供高品質的彩色化,且偽影最少。所有著色模型的最佳選擇。 DDColor 訓練有素的私人藝術資料集;產生多樣化和藝術化的色彩結果,減少不切實際的色彩偽影。 基於 Swin Transformer 的輕量級 BiRefNet 模型,可實現準確的背景去除。 高品質的背景去除,具有銳利的邊緣和出色的細節保留,尤其是在複雜的物體和棘手的背景上。 背景去除模型可產生具有平滑邊緣的精確掩模,適用於一般物體和適度的細節保留。 模型已下載 模型匯入成功 類型 關鍵詞 非常快 普通的 慢的 非常慢 計算百分比 最小值為 %1$s 用手指繪圖扭曲圖像 硬度 扭曲模式 移動 生長 收縮 旋流連續波 逆時針旋流 褪色強度 頂部落差 底部下降 開始掉落 結束掉落 正在下載 光滑的形狀 使用超橢圓代替標準圓角矩形以獲得更平滑、更自然的形狀 形狀類型 圓形 光滑的 邊緣鋒利,無倒圓角 經典圓角 形狀類型 邊角尺寸 松鼠 優雅的圓形 UI 元素 檔名格式 自定義文字放置在檔名的開頭,非常適合項目名稱、品牌或個人標籤。 使用不帶副檔名的原始檔名,幫助您保持源標識完整。 圖像寬度(以像素為單位),可用於跟蹤解析度變化或縮放結果。 圖像高度(以像素為單位),在處理寬高比或匯出時很有幫助。 生成隨機數字以保證唯一的檔名;添加更多數字以提高安全性,防止重複。 用於批量匯出的自動遞增計數器,非常適合在一個會話中保存多個圖像時。 將應用的預設名稱插入檔名中,以便您可以輕鬆記住圖像的處理方式。 顯示處理過程中使用的圖像縮放模式,幫助區分調整大小、裁切或擬合的圖像。 放置在檔名末尾的自定義文字,對於 _v2、_edited 或 _final 等版本控制很有用。 檔案副檔名(png、jpg、webp等),自動匹配實際保存格式。 可自定義的時間戳,讓您可以通過 java 規範定義自己的格式以實現完美排序。 快速停止 活潑 自適應 原生 Android 滾動物理 平衡、平滑的滾動,適合一般用途 更高的摩擦力,類似 iOS 的滾動行為 獨特的樣條曲線帶來獨特的滾動感覺 精確滾動並快速停止 有趣、反應靈敏的彈性滾動 用於內容瀏覽的長滑動卷軸 交互式 UI 的快速、響應式滾動 優質平滑滾動,動力強勁 根據投擲速度調整物理 尊重系統輔助功能設定 滿足無障礙需求的最小運動 投擲型 Android 原生 iOS 風格 平滑曲線 彈力 飄逸 超光滑 無障礙意識 減少運動 主要線路 每五行添加較粗的線 填充顏色 隱藏工具 隱藏共享工具 顏色庫 瀏覽大量顏色 銳化並消除圖像模糊,同時保持自然細節,是修復失焦照片的理想選擇。 智能恢復之前調整過大小的圖像,恢復丟失的細節和紋理。 針對真人內容進行了最佳化,減少了壓縮偽影並增強了電影/電視節目幀中的精細細節。 將 VHS 品質的素材轉換為高清,消除磁帶噪音並提高解析度,同時保留復古感。 專門用於文字較多的圖像和屏幕截圖,銳化字符並提高可讀性。 在不同資料集上進行高級升級訓練,非常適合通用照片增強。 針對網路壓縮照片進行了最佳化,消除了 JPEG 偽影並恢復自然外觀。 網頁照片的改進版本,具有更好的紋理保留和偽影減少。 使用雙聚合變壓器技術進行 2 倍升級,保持清晰度和自然細節。 使用先進的變壓器架構進行 3 倍放大,非常適合中等放大需求。 通過最先進的變壓器網路進行 4 倍高品質放大,在更大的尺度上保留精細細節。 消除照片中的模糊/噪點和抖動。通用但最適合照片。 使用 Swin2SR 轉換器恢復低品質圖像,並針對 BSRGAN 退化進行了最佳化。非常適合修復嚴重的壓縮偽影並以 4 倍比例增強細節。 使用經過 BSRGAN 退化訓練的 SwinIR 變壓器進行 4 倍升級。使用 GAN 在照片和複雜場景中獲得更清晰的紋理和更自然的細節。 小路 合併PDF 將多個 PDF 檔案合併為一個文件 檔案順序 頁數 分割 PDF 從 PDF 文件中提取特定頁面 旋轉 PDF 永久修復頁面方向 頁數 重新排列 PDF 拖放頁面以重新排序 按住並拖動頁面 頁碼 自動為您的文件添加編號 標籤格式 PDF 轉文字 (OCR) 從 PDF 文件中提取純文字 覆蓋自定義文字以實現品牌或安全 簽名 將您的電子簽名添加到任何文件中 這將用作簽名 解鎖 PDF 從受保護的檔案中刪除密碼 保護 PDF 通過強大的加密保護您的文件 成功 PDF 已解鎖,您可以保存或分享 修復 PDF 嘗試修復損壞或無法讀取的文件 灰度 將所有文件嵌入圖像轉換為灰度 壓縮PDF 最佳化文件檔案大小以方便共享 ImageToolbox 重建內部交叉引用表並從頭開始重新生成檔案結構。這可以恢復對許多「無法開啟」檔案的存取 該工具將所有文件圖像轉換為灰度。最適合列印和減小檔案大小 詮釋資料 編輯文件屬性以獲得更好的隱私 標籤 製片人 作者 關鍵詞 創作者 隱私深度清潔 清除該文件的所有可用詮釋資料 深度 OCR 使用 Tesseract 引擎從文件中提取文字並將其儲存在一個文字檔案中 無法刪除所有頁面 刪除 PDF 頁面 從 PDF 文件中刪除特定頁面 點擊刪除 手動 裁切 PDF 將文件頁面裁切到任意範圍 拼合 PDF 通過光柵化文件頁面使 PDF 不可修改 無法啟動相機。請檢查權限並確保它未被其他應用程式使用。 提取圖像 以原始解析度提取 PDF 中嵌入的圖像 此 PDF 檔案不包含任何嵌入圖像 該工具掃描每一頁並恢復全品質源圖像 - 非常適合保存文件中的原件 簽名 筆參數 使用自己的簽名作為圖像放置在文件上 壓縮 PDF 以給定的間隔分割文件並將新文件打包到 zip 存檔中 間隔 列印 PDF 準備用於使用自定義頁面尺寸列印的文件 每張頁數 方向 頁面尺寸 利潤 盛開 軟膝 針對動漫和卡通進行了最佳化。快速升級,改善自然色彩並減少偽影 类似三星 One UI 7 的风格 在此输入基本数学符号以计算所需的值(例如(5+5)*10) 数学表达式 选取最多 %1$s 张图片 保留日期时间 始终保留与日期和时间相关的 exif 标签,独立于 keep exif 选项 Alpha 格式的背景颜色 增加了为每种具有 Alpha 支持的图像格式设置背景颜色的功能,禁用后,此功能仅适用于非 Alpha 格式 打开项目 继续编辑之前保存的 Image Toolbox 项目 无法打开图像工具箱项目 Image Toolbox 项目缺少项目数据 图像工具箱项目已损坏 不受支持的 Image Toolbox 项目版本:%1$d 保存项目 将图层、背景和编辑历史记录存储在可编辑的项目文件中 打开失败 写入可搜索的 PDF 从图像批次中识别文本并保存带有图像和可选文本层的可搜索 PDF 阿尔法层 水平翻转 垂直翻转 添加阴影 阴影颜色 文本几何 拉伸或倾斜文本以获得更清晰的风格 规模 X 倾斜 X 删除注释 从 PDF 页面中删除选定的注释类型,例如链接、注释、突出显示、形状或表单字段 超链接 文件附件 线路 弹出窗口 邮票 形状 文字注释 文本标记 表单字段 标记 未知 注释 取消分组 使用可配置的颜色和偏移在图层后面添加模糊阴影 ================================================ FILE: core/resources/src/main/res/xml/backup_rules.xml ================================================ ================================================ FILE: core/resources/src/main/res/xml/data_extraction_rules.xml ================================================ ================================================ FILE: core/resources/src/main/res/xml/file_paths.xml ================================================ ================================================ FILE: core/settings/.gitignore ================================================ /build ================================================ FILE: core/settings/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.core.settings" dependencies { implementation(libs.datastore.preferences.android) implementation(libs.datastore.core.android) implementation(libs.kotlinx.collections.immutable) implementation(libs.coil) implementation(projects.lib.dynamicTheme) implementation(projects.core.domain) implementation(projects.core.resources) implementation(projects.core.di) } ================================================ FILE: core/settings/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/di/SettingsStateEntryPoint.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.di import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent @EntryPoint @InstallIn(SingletonComponent::class) interface SettingsStateEntryPoint { val settingsManager: SettingsManager } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsInteractor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.model.PerformanceClass import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.settings.domain.model.ColorHarmonizer import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.domain.model.DomainFontFamily import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.settings.domain.model.NightMode import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.domain.model.SnowfallMode import com.t8rin.imagetoolbox.core.settings.domain.model.SwitchType interface SettingsInteractor : SimpleSettingsInteractor { suspend fun toggleAddSequenceNumber() suspend fun toggleAddOriginalFilename() suspend fun setEmojisCount(count: Int) suspend fun setImagePickerMode(mode: Int) suspend fun toggleAddFileSize() suspend fun setEmoji(emoji: Int) suspend fun setFilenamePrefix(name: String) suspend fun toggleShowUpdateDialogOnStartup() suspend fun setColorTuple(colorTuple: String) suspend fun setPresets(newPresets: List) suspend fun toggleDynamicColors() override suspend fun setBorderWidth(width: Float) suspend fun toggleAllowImageMonet() suspend fun toggleAmoledMode() suspend fun setNightMode(nightMode: NightMode) suspend fun setSaveFolderUri(uri: String?) suspend fun setColorTuples(colorTuples: String) suspend fun setAlignment(align: Int) suspend fun setScreenOrder(data: String) suspend fun toggleClearCacheOnLaunch() suspend fun toggleGroupOptionsByTypes() suspend fun toggleRandomizeFilename() suspend fun createBackupFile(): ByteArray suspend fun restoreFromBackupFile( backupFileUri: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit, ) suspend fun resetSettings() fun createBackupFilename(): String suspend fun setFont(font: DomainFontFamily) suspend fun setFontScale(scale: Float) suspend fun toggleAllowCrashlytics() suspend fun toggleAllowAnalytics() suspend fun toggleAllowBetas() suspend fun toggleDrawContainerShadows() suspend fun toggleDrawButtonShadows() suspend fun toggleDrawSliderShadows() suspend fun toggleDrawSwitchShadows() suspend fun toggleDrawFabShadows() suspend fun toggleLockDrawOrientation() suspend fun setThemeStyle(value: Int) suspend fun setThemeContrast(value: Double) suspend fun toggleInvertColors() suspend fun toggleScreensSearchEnabled() suspend fun toggleDrawAppBarShadows() suspend fun setCopyToClipboardMode(copyToClipboardMode: CopyToClipboardMode) suspend fun setVibrationStrength(strength: Int) suspend fun setFilenameSuffix(name: String) suspend fun setDefaultImageScaleMode(imageScaleMode: ImageScaleMode) suspend fun toggleExifWidgetInitialState() suspend fun setInitialOCRLanguageCodes(list: List) suspend fun setScreensWithBrightnessEnforcement(data: List) suspend fun toggleConfettiEnabled() suspend fun toggleSecureMode() suspend fun toggleUseRandomEmojis() suspend fun setIconShape(iconShape: Int) suspend fun toggleUseEmojiAsPrimaryColor() suspend fun setDragHandleWidth(width: Int) suspend fun setConfettiType(type: Int) suspend fun toggleAllowAutoClipboardPaste() suspend fun setConfettiHarmonizer(colorHarmonizer: ColorHarmonizer) suspend fun setConfettiHarmonizationLevel(level: Float) suspend fun toggleGeneratePreviews() suspend fun toggleSkipImagePicking() suspend fun toggleShowSettingsInLandscape() suspend fun toggleUseFullscreenSettings() suspend fun setSwitchType(type: SwitchType) suspend fun setDefaultDrawLineWidth(value: Float) suspend fun toggleOpenEditInsteadOfPreview() suspend fun toggleCanEnterPresetsByTextField() suspend fun adjustPerformance(performanceClass: PerformanceClass) suspend fun registerDonateDialogOpen() suspend fun setNotShowDonateDialogAgain() suspend fun setColorBlindType(value: Int?) suspend fun toggleFavoriteScreen(screenId: Int) suspend fun toggleIsLinkPreviewEnabled() suspend fun setDefaultDrawColor(color: ColorModel) suspend fun setDefaultDrawPathMode(modeOrdinal: Int) suspend fun toggleAddTimestampToFilename() suspend fun toggleUseFormattedFilenameTimestamp() suspend fun registerTelegramGroupOpen() suspend fun setDefaultResizeType(resizeType: ResizeType) suspend fun setSystemBarsVisibility(systemBarsVisibility: SystemBarsVisibility) suspend fun toggleIsSystemBarsVisibleBySwipe() suspend fun setInitialOcrMode(mode: Int) suspend fun toggleUseCompactSelectorsLayout() suspend fun setMainScreenTitle(title: String) suspend fun setSliderType(type: SliderType) suspend fun toggleIsCenterAlignDialogButtons() suspend fun setFastSettingsSide(side: FastSettingsSide) suspend fun setChecksumTypeForFilename(type: HashingType?) suspend fun setCustomFonts(fonts: List) suspend fun importCustomFont(uri: String): DomainFontFamily.Custom? suspend fun removeCustomFont(font: DomainFontFamily.Custom) suspend fun createCustomFontsExport(): String? suspend fun toggleEnableToolExitConfirmation() suspend fun createLogsExport(): String suspend fun toggleAddPresetInfoToFilename() suspend fun toggleAddImageScaleModeInfoToFilename() suspend fun toggleAllowSkipIfLarger() suspend fun toggleIsScreenSelectionLauncherMode() suspend fun setSnowfallMode(snowfallMode: SnowfallMode) suspend fun setDefaultImageFormat(imageFormat: ImageFormat?) suspend fun setDefaultQuality(quality: Quality) suspend fun setShapesType(shapeType: ShapeType) suspend fun setFilenamePattern(pattern: String?) suspend fun setFlingType(type: FlingType) suspend fun setHiddenForShareScreens(data: List) suspend fun toggleKeepDateTime() suspend fun toggleEnableBackgroundColorForAlphaFormats() } fun SettingsInteractor.toSimpleSettingsInteractor(): SimpleSettingsInteractor = object : SimpleSettingsInteractor by this {} ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain interface SettingsManager : SettingsProvider, SettingsInteractor ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SettingsProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import kotlinx.coroutines.flow.StateFlow interface SettingsProvider { val settingsState: StateFlow suspend fun getSettingsState(): SettingsState } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/SimpleSettingsInteractor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.settings.domain.model.OneTimeSaveLocation interface SimpleSettingsInteractor { suspend fun toggleMagnifierEnabled() suspend fun setOneTimeSaveLocations(value: List) suspend fun toggleRecentColor( color: ColorModel, forceExclude: Boolean = false ) suspend fun toggleFavoriteColor( color: ColorModel, forceExclude: Boolean = true ) fun isInstalledFromPlayStore(): Boolean suspend fun toggleSettingsGroupVisibility( key: Int, value: Boolean ) suspend fun clearRecentColors() suspend fun updateFavoriteColors( colors: List ) suspend fun setBackgroundColorForNoAlphaFormats( color: ColorModel ) suspend fun toggleCustomAsciiGradient(gradient: String) suspend fun toggleOverwriteFiles() suspend fun setSpotHealMode(mode: Int) suspend fun setBorderWidth(width: Float) } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ColorHarmonizer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed class ColorHarmonizer( val ordinal: Int ) { data class Custom( val color: Int ) : ColorHarmonizer(color) data object Primary : ColorHarmonizer(1) data object Secondary : ColorHarmonizer(2) data object Tertiary : ColorHarmonizer(3) companion object { val entries by lazy { listOf( Primary, Secondary, Tertiary, Custom(0) ) } fun fromInt(ordinal: Int) = when (ordinal) { 1 -> Primary 2 -> Secondary 3 -> Tertiary else -> Custom(ordinal) } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/CopyToClipboardMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed class CopyToClipboardMode( open val value: Int ) { data object Disabled : CopyToClipboardMode(0) sealed class Enabled( override val value: Int ) : CopyToClipboardMode(value) { data object WithoutSaving : Enabled(1) data object WithSaving : Enabled(2) } companion object { fun fromInt( value: Int ): CopyToClipboardMode = when (value) { 1 -> Enabled.WithoutSaving 2 -> Enabled.WithSaving else -> Disabled } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/DomainFontFamily.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("SpellCheckingInspection") package com.t8rin.imagetoolbox.core.settings.domain.model sealed class DomainFontFamily(val ordinal: Int) { data object Montserrat : DomainFontFamily(1) data object Caveat : DomainFontFamily(2) data object Comfortaa : DomainFontFamily(3) data object Handjet : DomainFontFamily(4) data object YsabeauSC : DomainFontFamily(5) data object Jura : DomainFontFamily(6) data object Podkova : DomainFontFamily(7) data object Tektur : DomainFontFamily(8) data object DejaVu : DomainFontFamily(9) data object BadScript : DomainFontFamily(10) data object RuslanDisplay : DomainFontFamily(11) data object Catterdale : DomainFontFamily(12) data object FRM32 : DomainFontFamily(13) data object TokeelyBrookings : DomainFontFamily(14) data object Nunito : DomainFontFamily(15) data object Nothing : DomainFontFamily(16) data object WOPRTweaked : DomainFontFamily(17) data object AlegreyaSans : DomainFontFamily(18) data object MinecraftGnu : DomainFontFamily(19) data object GraniteFixed : DomainFontFamily(20) data object NokiaPixel : DomainFontFamily(21) data object Ztivalia : DomainFontFamily(22) data object Axotrel : DomainFontFamily(23) data object LcdOctagon : DomainFontFamily(24) data object LcdMoving : DomainFontFamily(25) data object Unisource : DomainFontFamily(26) data object System : DomainFontFamily(0) class Custom( val name: String?, val filePath: String ) : DomainFontFamily(-1) { override fun asString(): String = "$name:$filePath" override fun equals(other: Any?): Boolean { if (other !is Custom) return false return filePath == other.filePath } override fun hashCode(): Int { return filePath.hashCode() } override fun toString(): String { return "Custom(name = $name, filePath = $filePath)" } } open fun asString(): String = ordinal.toString() companion object { fun fromString(string: String?): DomainFontFamily? { val int = string?.toIntOrNull() val family = when (int) { 0 -> System 1 -> Montserrat 2 -> Caveat 3 -> Comfortaa 4 -> Handjet 5 -> YsabeauSC 6 -> Jura 7 -> Podkova 8 -> Tektur 9 -> DejaVu 10 -> BadScript 11 -> RuslanDisplay 12 -> Catterdale 13 -> FRM32 14 -> TokeelyBrookings 15 -> Nunito 16 -> Nothing 17 -> WOPRTweaked 18 -> AlegreyaSans 19 -> MinecraftGnu 20 -> GraniteFixed 21 -> NokiaPixel 22 -> Ztivalia 23 -> Axotrel 24 -> LcdOctagon 25 -> LcdMoving 26 -> Unisource else -> null } return family ?: string?.split(":")?.let { Custom( name = it[0], filePath = it[1] ) } } } } sealed interface FontType { data class Resource(val resId: Int) : FontType data class File(val path: String) : FontType } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FastSettingsSide.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed class FastSettingsSide( val ordinal: Int ) { data object None : FastSettingsSide(0) data object CenterEnd : FastSettingsSide(1) data object CenterStart : FastSettingsSide(2) companion object { val entries: List by lazy { listOf( None, CenterEnd, CenterStart ) } fun fromOrdinal(ordinal: Int?): FastSettingsSide? = ordinal?.let(entries::getOrNull) } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FilenameBehavior.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model import com.t8rin.imagetoolbox.core.domain.model.HashingType sealed interface FilenameBehavior { class None : FilenameBehavior class Overwrite : FilenameBehavior class Random : FilenameBehavior data class Checksum( val hashingType: HashingType ) : FilenameBehavior } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/FlingType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model enum class FlingType { DEFAULT, SMOOTH, IOS_STYLE, SMOOTH_CURVE, QUICK_STOP, BOUNCY, FLOATY, SNAPPY, ULTRA_SMOOTH, ADAPTIVE, ACCESSIBILITY_AWARE, REDUCED_MOTION } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/NightMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed class NightMode(val ordinal: Int) { data object Light : NightMode(0) data object Dark : NightMode(1) data object System : NightMode(2) companion object { fun fromOrdinal(int: Int?): NightMode? = when (int) { 0 -> Light 1 -> Dark 2 -> System else -> null } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/OneTimeSaveLocation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model data class OneTimeSaveLocation( val uri: String, val date: Long?, val count: Int ) { override fun toString(): String { return listOf(uri, date, count).joinToString(delimiter) } companion object { fun fromString(string: String): OneTimeSaveLocation? { val data = string.split(delimiter) val uri = data.getOrNull(0) ?: return null val date = data.getOrNull(1)?.toLongOrNull() val count = data.getOrNull(2)?.toIntOrNull() ?: 0 return OneTimeSaveLocation(uri, date, count) } } } private const val delimiter = "\n" ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SettingsState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Preset.Percentage import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.domain.utils.Flavor data class SettingsState( val nightMode: NightMode, val isDynamicColors: Boolean, val allowChangeColorByImage: Boolean, val emojisCount: Int, val isAmoledMode: Boolean, val appColorTuple: String, val borderWidth: Float, val presets: List, val aspectRatios: List, val fabAlignment: Int, val selectedEmoji: Int?, val picturePickerModeInt: Int, val clearCacheOnLaunch: Boolean, val showUpdateDialogOnStartup: Boolean, val groupOptionsByTypes: Boolean, val screenList: List, val colorTupleList: String?, val addSequenceNumber: Boolean, val saveFolderUri: String?, val filenamePrefix: String, val addSizeInFilename: Boolean, val addOriginalFilename: Boolean, val font: DomainFontFamily, val fontScale: Float?, val allowCollectCrashlytics: Boolean, val allowCollectAnalytics: Boolean, val allowBetas: Boolean, val drawContainerShadows: Boolean, val drawButtonShadows: Boolean, val drawSliderShadows: Boolean, val drawSwitchShadows: Boolean, val drawFabShadows: Boolean, val drawAppBarShadows: Boolean, val appOpenCount: Int, val lockDrawOrientation: Boolean, val themeContrastLevel: Double, val themeStyle: Int, val isInvertThemeColors: Boolean, val screensSearchEnabled: Boolean, val copyToClipboardMode: CopyToClipboardMode, val hapticsStrength: Int, val filenameSuffix: String, val defaultImageScaleMode: ImageScaleMode, val magnifierEnabled: Boolean, val exifWidgetInitialState: Boolean, val initialOcrCodes: List, val screenListWithMaxBrightnessEnforcement: List, val isConfettiEnabled: Boolean, val isSecureMode: Boolean, val useRandomEmojis: Boolean, val iconShape: Int?, val useEmojiAsPrimaryColor: Boolean, val dragHandleWidth: Int, val confettiType: Int, val allowAutoClipboardPaste: Boolean, val confettiColorHarmonizer: ColorHarmonizer, val confettiHarmonizationLevel: Float, val skipImagePicking: Boolean, val generatePreviews: Boolean, val showSettingsInLandscape: Boolean, val useFullscreenSettings: Boolean, val switchType: SwitchType, val defaultDrawLineWidth: Float, val oneTimeSaveLocations: List, val openEditInsteadOfPreview: Boolean, val canEnterPresetsByTextField: Boolean, val donateDialogOpenCount: Int, val colorBlindType: Int?, val favoriteScreenList: List, val isLinkPreviewEnabled: Boolean, val defaultDrawColor: ColorModel, val defaultDrawPathMode: Int, val addTimestampToFilename: Boolean, val useFormattedFilenameTimestamp: Boolean, val favoriteColors: List, val defaultResizeType: ResizeType, val systemBarsVisibility: SystemBarsVisibility, val isSystemBarsVisibleBySwipe: Boolean, val isCompactSelectorsLayout: Boolean, val mainScreenTitle: String, val sliderType: SliderType, val isCenterAlignDialogButtons: Boolean, val fastSettingsSide: FastSettingsSide, val settingGroupsInitialVisibility: Map, val customFonts: List, val enableToolExitConfirmation: Boolean, val recentColors: List, val backgroundForNoAlphaImageFormats: ColorModel, val addPresetInfoToFilename: Boolean, val addImageScaleModeInfoToFilename: Boolean, val allowSkipIfLarger: Boolean, val customAsciiGradients: Set, val isScreenSelectionLauncherMode: Boolean, val isTelegramGroupOpened: Boolean, val initialOcrMode: Int, val spotHealMode: Int, val snowfallMode: SnowfallMode, val defaultImageFormat: ImageFormat?, val defaultQuality: Quality, val shapesType: ShapeType, val filenamePattern: String?, val filenameBehavior: FilenameBehavior, val flingType: FlingType, val hiddenForShareScreens: List, val keepDateTime: Boolean, val enableBackgroundColorForAlphaFormats: Boolean, ) { companion object { val Default by lazy { SettingsState( nightMode = NightMode.System, isDynamicColors = true, allowChangeColorByImage = true, emojisCount = 1, isAmoledMode = false, appColorTuple = "", borderWidth = -1f, presets = List(6) { Percentage(100 - it * 10) }, fabAlignment = 1, selectedEmoji = 0, picturePickerModeInt = 0, clearCacheOnLaunch = false, showUpdateDialogOnStartup = !Flavor.isFoss(), groupOptionsByTypes = true, screenList = emptyList(), colorTupleList = null, addSequenceNumber = true, saveFolderUri = null, filenamePrefix = "ResizedImage", addSizeInFilename = false, addOriginalFilename = false, font = DomainFontFamily.System, fontScale = 1f, allowCollectCrashlytics = true, allowCollectAnalytics = true, allowBetas = !Flavor.isFoss(), drawContainerShadows = true, drawButtonShadows = true, drawSwitchShadows = true, drawSliderShadows = true, drawFabShadows = true, drawAppBarShadows = true, appOpenCount = 0, aspectRatios = DomainAspectRatio.defaultList, lockDrawOrientation = false, themeContrastLevel = 0.0, themeStyle = 0, isInvertThemeColors = false, screensSearchEnabled = false, hapticsStrength = 1, filenameSuffix = "", defaultImageScaleMode = ImageScaleMode.Default, copyToClipboardMode = CopyToClipboardMode.Disabled, magnifierEnabled = false, exifWidgetInitialState = false, initialOcrCodes = listOf("eng"), screenListWithMaxBrightnessEnforcement = emptyList(), isConfettiEnabled = true, isSecureMode = false, useRandomEmojis = false, iconShape = 0, useEmojiAsPrimaryColor = false, dragHandleWidth = 64, confettiType = 0, allowAutoClipboardPaste = false, confettiColorHarmonizer = ColorHarmonizer.Primary, confettiHarmonizationLevel = 0.5f, skipImagePicking = false, generatePreviews = true, showSettingsInLandscape = true, useFullscreenSettings = false, switchType = SwitchType.Compose, defaultDrawLineWidth = 20f, oneTimeSaveLocations = emptyList(), openEditInsteadOfPreview = false, canEnterPresetsByTextField = false, donateDialogOpenCount = 0, colorBlindType = null, favoriteScreenList = emptyList(), isLinkPreviewEnabled = true, defaultDrawColor = ColorModel(-0x1000000), defaultDrawPathMode = 0, addTimestampToFilename = true, useFormattedFilenameTimestamp = true, favoriteColors = emptyList(), defaultResizeType = ResizeType.Explicit, systemBarsVisibility = SystemBarsVisibility.Auto, isSystemBarsVisibleBySwipe = true, isCompactSelectorsLayout = false, mainScreenTitle = "", sliderType = SliderType.Fancy, isCenterAlignDialogButtons = false, fastSettingsSide = FastSettingsSide.CenterEnd, settingGroupsInitialVisibility = emptyMap(), customFonts = emptyList(), enableToolExitConfirmation = true, recentColors = emptyList(), backgroundForNoAlphaImageFormats = ColorModel(-0x1000000), addPresetInfoToFilename = false, addImageScaleModeInfoToFilename = false, allowSkipIfLarger = false, customAsciiGradients = emptySet(), isScreenSelectionLauncherMode = false, isTelegramGroupOpened = false, initialOcrMode = 1, spotHealMode = 0, snowfallMode = SnowfallMode.Auto, defaultImageFormat = null, defaultQuality = Quality.Base(), shapesType = ShapeType.Rounded(), filenamePattern = null, filenameBehavior = FilenameBehavior.None(), flingType = FlingType.DEFAULT, hiddenForShareScreens = emptyList(), keepDateTime = false, enableBackgroundColorForAlphaFormats = false, ) } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/ShapeType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed interface ShapeType { val ordinal: Int get() = entries.indexOf(this) val strength: Float fun copy(strength: Float): ShapeType = when (this) { is Cut -> Cut(strength = strength) is Rounded -> Rounded(strength = strength) is Squircle -> Squircle(strength = strength) is Smooth -> Smooth(strength = strength) } class Rounded( override val strength: Float = 1f ) : ShapeType class Cut( override val strength: Float = 1f ) : ShapeType class Squircle( override val strength: Float = 1f ) : ShapeType class Smooth( override val strength: Float = 1f ) : ShapeType companion object { val entries by lazy { listOf( Rounded(), Cut(), Squircle(), Smooth() ) } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SliderType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed class SliderType( val ordinal: Int ) { data object MaterialYou : SliderType(0) data object Fancy : SliderType(1) data object Material : SliderType(2) data object HyperOS : SliderType(3) companion object { fun fromInt(ordinal: Int) = when (ordinal) { 1 -> Fancy 2 -> Material 3 -> HyperOS else -> MaterialYou } val entries by lazy { listOf( MaterialYou, Fancy, Material, HyperOS ) } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SnowfallMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed interface SnowfallMode { val ordinal: Int get() = entries.indexOf(this) data object Auto : SnowfallMode data object Enabled : SnowfallMode data object Disabled : SnowfallMode companion object { val entries by lazy { listOf( Auto, Enabled, Disabled ) } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/domain/model/SwitchType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.domain.model sealed class SwitchType(val ordinal: Int) { data object MaterialYou : SwitchType(0) data object Compose : SwitchType(1) data object Pixel : SwitchType(2) data object Fluent : SwitchType(3) data object Cupertino : SwitchType(4) data object LiquidGlass : SwitchType(5) data object HyperOS : SwitchType(6) data object OneUI : SwitchType(7) companion object { fun fromInt(ordinal: Int) = when (ordinal) { 1 -> Compose 2 -> Pixel 3 -> Fluent 4 -> Cupertino 5 -> LiquidGlass 6 -> HyperOS 7 -> OneUI else -> MaterialYou } val entries by lazy { listOf( MaterialYou, Compose, Pixel, Fluent, Cupertino, LiquidGlass, HyperOS, OneUI ) } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/EditPresetsController.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.presentation.model import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver class EditPresetsController( initialVisibility: Boolean = false ) { private val _isVisible: MutableState = mutableStateOf(initialVisibility) val isVisible by _isVisible fun open() { _isVisible.value = true } fun close() { _isVisible.value = false } companion object { val Saver: Saver = Saver( save = { it.isVisible }, restore = { EditPresetsController(it) } ) } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/IconShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.presentation.model import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialShapes import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.shapes.ArrowShape import com.t8rin.imagetoolbox.core.resources.shapes.BookmarkShape import com.t8rin.imagetoolbox.core.resources.shapes.BurgerShape import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.resources.shapes.DropletShape import com.t8rin.imagetoolbox.core.resources.shapes.EggShape import com.t8rin.imagetoolbox.core.resources.shapes.ExplosionShape import com.t8rin.imagetoolbox.core.resources.shapes.HeartShape import com.t8rin.imagetoolbox.core.resources.shapes.MapShape import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.resources.shapes.OctagonShape import com.t8rin.imagetoolbox.core.resources.shapes.OvalShape import com.t8rin.imagetoolbox.core.resources.shapes.PentagonShape import com.t8rin.imagetoolbox.core.resources.shapes.PillShape import com.t8rin.imagetoolbox.core.resources.shapes.ShieldShape import com.t8rin.imagetoolbox.core.resources.shapes.ShurikenShape import com.t8rin.imagetoolbox.core.resources.shapes.SimpleHeartShape import com.t8rin.imagetoolbox.core.resources.shapes.SmallMaterialStarShape import com.t8rin.imagetoolbox.core.resources.shapes.SquircleShape import com.t8rin.imagetoolbox.core.settings.presentation.utils.toShape import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList data class IconShape( val shape: Shape, val padding: Dp = 4.dp, val iconSize: Dp = 24.dp ) { fun takeOrElseFrom( iconShapesList: List ): IconShape = if (this == Random) iconShapesList .filter { it != Random } .random() else this companion object { val Random by lazy { IconShape( shape = RectangleShape, padding = 0.dp, iconSize = 0.dp ) } val entriesNoRandom: ImmutableList by lazy { listOf( IconShape(SquircleShape), IconShape(RoundedCornerShape(15)), IconShape(RoundedCornerShape(25)), IconShape(RoundedCornerShape(35)), IconShape(RoundedCornerShape(45)), IconShape(CutCornerShape(25)), IconShape(CutCornerShape(35), 8.dp, 22.dp), IconShape(CutCornerShape(50), 10.dp, 18.dp), IconShape(CloverShape), IconShape(MaterialStarShape, 6.dp, 22.dp), IconShape(SmallMaterialStarShape, 6.dp, 22.dp), IconShape(BookmarkShape, 8.dp, 22.dp), IconShape(PillShape, 10.dp, 22.dp), IconShape(BurgerShape, 6.dp, 22.dp), IconShape(OvalShape, 6.dp), IconShape(ShieldShape, 8.dp, 20.dp), IconShape(EggShape, 8.dp, 20.dp), IconShape(DropletShape, 6.dp, 22.dp), IconShape(ArrowShape, 10.dp, 20.dp), IconShape(PentagonShape, 6.dp, 22.dp), IconShape(OctagonShape, 6.dp, 22.dp), IconShape(ShurikenShape, 8.dp, 22.dp), IconShape(ExplosionShape, 6.dp), IconShape(MapShape, 10.dp, 22.dp), IconShape(HeartShape, 10.dp, 18.dp), IconShape(SimpleHeartShape, 12.dp, 16.dp), ).toMutableList().apply { val shapes = listOf( MaterialShapes.Slanted, MaterialShapes.Arch, MaterialShapes.SemiCircle, MaterialShapes.Oval, MaterialShapes.Diamond, MaterialShapes.ClamShell, MaterialShapes.Gem, MaterialShapes.Sunny, MaterialShapes.VerySunny, MaterialShapes.Cookie4Sided, MaterialShapes.Cookie6Sided, MaterialShapes.Cookie9Sided, MaterialShapes.Cookie12Sided, MaterialShapes.Ghostish, MaterialShapes.Clover4Leaf, MaterialShapes.Clover8Leaf, MaterialShapes.Burst, MaterialShapes.SoftBurst, MaterialShapes.Boom, MaterialShapes.SoftBoom, MaterialShapes.Flower, MaterialShapes.Puffy, MaterialShapes.PuffyDiamond, MaterialShapes.PixelCircle, MaterialShapes.Bun ).map { IconShape(it.toShape(), 10.dp, 20.dp) } addAll(shapes) }.toPersistentList() } val entries: ImmutableList by lazy { (entriesNoRandom + Random).toPersistentList() } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/PicturePickerMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.presentation.model import android.os.Build import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PhotoCameraBack import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FolderImage import com.t8rin.imagetoolbox.core.resources.icons.ImageEmbedded import com.t8rin.imagetoolbox.core.resources.icons.ImagesMode import com.t8rin.imagetoolbox.core.resources.icons.PhotoPickerMobile sealed class PicturePickerMode( val ordinal: Int, val icon: ImageVector, val title: Int, val subtitle: Int ) { data object Embedded : PicturePickerMode( ordinal = 0, icon = Icons.Outlined.ImageEmbedded, title = R.string.embedded_picker, subtitle = R.string.embedded_picker_sub ) data object PhotoPicker : PicturePickerMode( ordinal = 1, icon = Icons.Outlined.PhotoPickerMobile, title = R.string.photo_picker, subtitle = R.string.photo_picker_sub ) data object Gallery : PicturePickerMode( ordinal = 2, icon = Icons.Outlined.ImagesMode, title = R.string.gallery_picker, subtitle = R.string.gallery_picker_sub ) data object GetContent : PicturePickerMode( ordinal = 3, icon = Icons.Outlined.FolderImage, title = R.string.file_explorer_picker, subtitle = R.string.file_explorer_picker_sub ) data object CameraCapture : PicturePickerMode( ordinal = 4, icon = Icons.Outlined.PhotoCameraBack, title = R.string.camera, subtitle = R.string.camera_sub ) companion object { val SafeEmbedded by lazy { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Embedded } else PhotoPicker } fun fromInt( ordinal: Int ) = when (ordinal) { 0 -> SafeEmbedded 1 -> PhotoPicker 2 -> Gallery 3 -> GetContent 4 -> CameraCapture else -> SafeEmbedded } val entries by lazy { listOf( SafeEmbedded, PhotoPicker, Gallery, GetContent, CameraCapture ).distinct() } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/Setting.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.presentation.model import com.t8rin.imagetoolbox.core.resources.R sealed class Setting( val title: Int, val subtitle: Int?, ) { data object AddFileSize : Setting( title = R.string.add_file_size, subtitle = R.string.add_file_size_sub ) data object AddOriginalFilename : Setting( title = R.string.add_original_filename, subtitle = R.string.add_original_filename_sub ) data object AllowBetas : Setting( title = R.string.allow_betas, subtitle = R.string.allow_betas_sub ) data object AllowImageMonet : Setting( title = R.string.allow_image_monet, subtitle = R.string.allow_image_monet_sub ) data object AmoledMode : Setting( title = R.string.amoled_mode, subtitle = R.string.amoled_mode_sub ) data object Analytics : Setting( title = R.string.analytics, subtitle = R.string.analytics_sub ) data object Author : Setting( title = R.string.app_developer, subtitle = R.string.app_developer_nick ) data object AutoCacheClear : Setting( title = R.string.auto_cache_clearing, subtitle = R.string.auto_cache_clearing_sub ) data object AutoCheckUpdates : Setting( title = R.string.check_updates, subtitle = R.string.check_updates_sub ) data object Backup : Setting( title = R.string.backup, subtitle = R.string.backup_sub ) data object BorderThickness : Setting( title = R.string.border_thickness, subtitle = null ) data object ChangeFont : Setting( title = R.string.font, subtitle = null ) data object ChangeLanguage : Setting( title = R.string.language, subtitle = null ) data object CheckUpdatesButton : Setting( title = R.string.check_updates, subtitle = R.string.check_updates_sub ) data object ClearCache : Setting( title = R.string.cache, subtitle = R.string.cache_size ) data object ColorScheme : Setting( title = R.string.color_scheme, subtitle = R.string.color_scheme ) data object Crashlytics : Setting( title = R.string.crashlytics, subtitle = R.string.crashlytics_sub ) data object CurrentVersionCode : Setting( title = R.string.app_name, subtitle = R.string.version ) data object Donate : Setting( title = R.string.donation, subtitle = R.string.donation_sub ) data object DynamicColors : Setting( title = R.string.dynamic_colors, subtitle = R.string.dynamic_colors_sub ) data object EmojisCount : Setting( title = R.string.emojis_count, subtitle = null ) data object Emoji : Setting( title = R.string.emoji, subtitle = R.string.emoji_sub ) data object ContainerShadows : Setting( title = R.string.containers_shadow, subtitle = R.string.containers_shadow_sub ) data object AppBarShadows : Setting( title = R.string.app_bars_shadow, subtitle = R.string.app_bars_shadow_sub ) data object SliderShadows : Setting( title = R.string.sliders_shadow, subtitle = R.string.sliders_shadow_sub ) data object SwitchShadows : Setting( title = R.string.switches_shadow, subtitle = R.string.switches_shadow_sub ) data object FABShadows : Setting( title = R.string.fabs_shadow, subtitle = R.string.fabs_shadow_sub ) data object ButtonShadows : Setting( title = R.string.buttons_shadow, subtitle = R.string.buttons_shadow_sub ) data object FabAlignment : Setting( title = R.string.fab_alignment, subtitle = null ) data object FilenamePrefix : Setting( title = R.string.prefix, subtitle = null ) data object FontScale : Setting( title = R.string.font_scale, subtitle = null ) data object GroupOptions : Setting( title = R.string.group_tools_by_type, subtitle = R.string.group_tools_by_type_sub ) data object HelpTranslate : Setting( title = R.string.help_translate, subtitle = R.string.help_translate_sub ) data object ImagePickerMode : Setting( title = R.string.photo_picker, subtitle = R.string.photo_picker_sub ) data object IssueTracker : Setting( title = R.string.issue_tracker, subtitle = R.string.issue_tracker_sub ) data object LockDrawOrientation : Setting( title = R.string.lock_draw_orientation, subtitle = R.string.lock_draw_orientation_sub ) data object NightMode : Setting( title = R.string.night_mode, subtitle = null ) data object Presets : Setting( title = R.string.presets, subtitle = R.string.presets_sub ) data object RandomizeFilename : Setting( title = R.string.randomize_filename, subtitle = R.string.randomize_filename_sub ) data object ReplaceSequenceNumber : Setting( title = R.string.replace_sequence_number, subtitle = R.string.replace_sequence_number_sub ) data object Reset : Setting( title = R.string.reset, subtitle = R.string.reset_settings_sub ) data object Restore : Setting( title = R.string.restore, subtitle = R.string.restore_sub ) data object SavingFolder : Setting( title = R.string.folder, subtitle = null ) data object ScreenOrder : Setting( title = R.string.order, subtitle = R.string.order_sub ) data object ScreenSearch : Setting( title = R.string.search_option, subtitle = R.string.search_option_sub ) data object SourceCode : Setting( title = R.string.check_source_code, subtitle = R.string.check_source_code_sub ) data object TelegramGroup : Setting( title = R.string.tg_chat, subtitle = R.string.tg_chat_sub ) data object AutoPinClipboard : Setting( title = R.string.auto_pin, subtitle = R.string.auto_pin_sub ) data object AutoPinClipboardOnlyClip : Setting( title = R.string.only_clip, subtitle = R.string.only_clip_sub ) data object VibrationStrength : Setting( title = R.string.vibration_strength, subtitle = null ) data object OverwriteFiles : Setting( title = R.string.overwrite_files, subtitle = R.string.overwrite_files_sub ) data object FilenameSuffix : Setting( title = R.string.suffix, subtitle = null ) data object DefaultScaleMode : Setting( title = R.string.scale_mode, subtitle = null ) data object DefaultColorSpace : Setting( title = R.string.tag_color_space, subtitle = null ) data object SwitchType : Setting( title = R.string.switch_type, subtitle = null ) data object Magnifier : Setting( title = R.string.magnifier, subtitle = R.string.magnifier_sub ) data object ExifWidgetInitialState : Setting( title = R.string.force_exif_widget_initial_value, subtitle = R.string.force_exif_widget_initial_value_sub ) data object BrightnessEnforcement : Setting( title = R.string.brightness_enforcement, subtitle = null ) data object Confetti : Setting( title = R.string.confetti, subtitle = R.string.confetti_sub ) data object SecureMode : Setting( title = R.string.secure_mode, subtitle = R.string.secure_mode_sub ) data object UseRandomEmojis : Setting( title = R.string.random_emojis, subtitle = R.string.random_emojis_sub ) data object IconShape : Setting( title = R.string.icon_shape, subtitle = R.string.icon_shape_sub ) data object DragHandleWidth : Setting( title = R.string.drag_handle_width, subtitle = null ) data object ConfettiType : Setting( title = R.string.confetti_type, subtitle = null ) data object AllowAutoClipboardPaste : Setting( title = R.string.auto_paste, subtitle = R.string.auto_paste_sub ) data object ConfettiHarmonizer : Setting( title = R.string.harmonization_color, subtitle = null ) data object ConfettiHarmonizationLevel : Setting( title = R.string.harmonization_level, subtitle = null ) data object SkipFilePicking : Setting( title = R.string.skip_file_picking, subtitle = R.string.skip_file_picking_sub ) data object GeneratePreviews : Setting( title = R.string.generate_previews, subtitle = R.string.generate_previews_sub ) data object ShowSettingsInLandscape : Setting( title = R.string.show_settings_in_landscape, subtitle = R.string.show_settings_in_landscape_sub ) data object UseFullscreenSettings : Setting( title = R.string.fullscreen_settings, subtitle = R.string.fullscreen_settings_sub ) data object DefaultDrawLineWidth : Setting( title = R.string.default_line_width, subtitle = null ) data object OpenEditInsteadOfPreview : Setting( title = R.string.open_edit_instead_of_preview, subtitle = R.string.open_edit_instead_of_preview_sub ) data object CanEnterPresetsByTextField : Setting( title = R.string.allow_enter_by_text_field, subtitle = R.string.allow_enter_by_text_field_sub ) data object ColorBlindScheme : Setting( title = R.string.color_blind_scheme, subtitle = R.string.color_blind_scheme_sub ) data object EnableLinksPreview : Setting( title = R.string.links_preview, subtitle = R.string.links_preview_sub ) data object DefaultDrawColor : Setting( title = R.string.default_draw_color, subtitle = null ) data object DefaultDrawPathMode : Setting( title = R.string.default_draw_path_mode, subtitle = null ) data object AddTimestampToFilename : Setting( title = R.string.add_timestamp, subtitle = R.string.add_timestamp_sub ) data object UseFormattedFilenameTimestamp : Setting( title = R.string.formatted_timestamp, subtitle = R.string.formatted_timestamp_sub ) data object OneTimeSaveLocation : Setting( title = R.string.one_time_save_location, subtitle = R.string.one_time_save_location_sub ) data object TelegramChannel : Setting( title = R.string.ci_channel, subtitle = R.string.ci_channel_sub ) data object FreeSoftwarePartner : Setting( title = R.string.free_software_partner, subtitle = R.string.free_software_partner_sub ) data object DefaultResizeType : Setting( title = R.string.resize_type, subtitle = null ) data object SystemBarsVisibility : Setting( title = R.string.system_bars_visibility, subtitle = null ) data object ShowSystemBarsBySwipe : Setting( title = R.string.show_system_bars_by_swipe, subtitle = R.string.show_system_bars_by_swipe_sub ) data object UseCompactSelectors : Setting( title = R.string.compact_selectors, subtitle = R.string.compact_selectors_sub ) data object MainScreenTitle : Setting( title = R.string.main_screen_title, subtitle = null ) data object SliderType : Setting( title = R.string.slider_type, subtitle = null ) data object CenterAlignDialogButtons : Setting( title = R.string.center_align_dialog_buttons, subtitle = R.string.center_align_dialog_buttons_sub ) data object OpenSourceLicenses : Setting( title = R.string.open_source_licenses, subtitle = R.string.open_source_licenses_sub ) data object FastSettingsSide : Setting( title = R.string.fast_settings_side, subtitle = R.string.fast_settings_side_sub ) data object ChecksumAsFilename : Setting( title = R.string.checksum_as_filename, subtitle = R.string.checksum_as_filename_sub ) data object EnableToolExitConfirmation : Setting( title = R.string.tool_exit_confirmation, subtitle = R.string.tool_exit_confirmation_sub ) data object SendLogs : Setting( title = R.string.send_logs, subtitle = R.string.send_logs_sub ) data object AddPresetToFilename : Setting( title = R.string.add_preset_to_filename, subtitle = R.string.add_preset_to_filename_sub ) data object AddImageScaleModeToFilename : Setting( title = R.string.add_image_scale_mode_to_filename, subtitle = R.string.add_image_scale_mode_to_filename_sub ) data object AllowSkipIfLarger : Setting( title = R.string.allow_skip_if_larger, subtitle = R.string.allow_skip_if_larger_sub ) data object EnableLauncherMode : Setting( title = R.string.launcher_mode, subtitle = R.string.launcher_mode_sub ) data object SnowfallMode : Setting( title = R.string.snowfall_mode, subtitle = null ) data object DefaultImageFormat : Setting( title = R.string.image_format, subtitle = null ) data object DefaultQuality : Setting( title = R.string.quality, subtitle = null ) data object ShapeType : Setting( title = R.string.shapes_type, subtitle = null ) data object CornersSize : Setting( title = R.string.corners_size, subtitle = null ) data object FilenamePattern : Setting( title = R.string.filename_format, subtitle = null ) data object FlingType : Setting( title = R.string.fling_type, subtitle = null ) data object ToolsHiddenForShare : Setting( title = R.string.hidden_for_share, subtitle = null ) data object KeepDateTime : Setting( title = R.string.keep_date_time, subtitle = R.string.keep_date_time_sub ) data object EnableBackgroundColorForAlphaFormats : Setting( title = R.string.background_color_for_alpha_formats, subtitle = R.string.background_color_for_alpha_formats_sub ) } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/SettingsGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("KotlinConstantConditions") package com.t8rin.imagetoolbox.core.settings.presentation.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Celebration import androidx.compose.material.icons.rounded.Description import androidx.compose.material.icons.rounded.Info import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.domain.utils.Flavor import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ClipboardFile import com.t8rin.imagetoolbox.core.resources.icons.Cool import com.t8rin.imagetoolbox.core.resources.icons.Database import com.t8rin.imagetoolbox.core.resources.icons.DesignServices import com.t8rin.imagetoolbox.core.resources.icons.Draw import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.resources.icons.Firebase import com.t8rin.imagetoolbox.core.resources.icons.FolderOpened import com.t8rin.imagetoolbox.core.resources.icons.Glyphs import com.t8rin.imagetoolbox.core.resources.icons.HardDrive import com.t8rin.imagetoolbox.core.resources.icons.ImageSearch import com.t8rin.imagetoolbox.core.resources.icons.LabelPercent import com.t8rin.imagetoolbox.core.resources.icons.Mobile import com.t8rin.imagetoolbox.core.resources.icons.MobileArrowDown import com.t8rin.imagetoolbox.core.resources.icons.MobileCast import com.t8rin.imagetoolbox.core.resources.icons.MobileLayout import com.t8rin.imagetoolbox.core.resources.icons.MobileVibrate import com.t8rin.imagetoolbox.core.resources.icons.Psychology import com.t8rin.imagetoolbox.core.resources.icons.ResponsiveLayout import com.t8rin.imagetoolbox.core.resources.icons.Routine import com.t8rin.imagetoolbox.core.resources.icons.Shadow import com.t8rin.imagetoolbox.core.resources.icons.SquareFoot sealed class SettingsGroup( val id: Int, val titleId: Int, val icon: ImageVector, val settingsList: List, val initialState: Boolean, ) { data object ContactMe : SettingsGroup( id = 0, icon = Icons.Rounded.MobileCast, titleId = R.string.contact_me, settingsList = listOf( Setting.Author, Setting.SendLogs, Setting.Donate ), initialState = true ) data object PrimaryCustomization : SettingsGroup( id = 1, icon = Icons.Rounded.DesignServices, titleId = R.string.customization, settingsList = listOf( Setting.ColorScheme, Setting.DynamicColors, Setting.AmoledMode, Setting.IconShape ), initialState = true ) data object SecondaryCustomization : SettingsGroup( id = 2, icon = Icons.TwoTone.DesignServices, titleId = R.string.secondary_customization, settingsList = listOf( Setting.ColorBlindScheme, Setting.AllowImageMonet, Setting.BorderThickness, Setting.MainScreenTitle ), initialState = false ) data object Layout : SettingsGroup( id = 3, icon = Icons.Rounded.MobileLayout, titleId = R.string.layout, settingsList = listOf( Setting.SwitchType, Setting.SliderType, Setting.ShapeType, Setting.CornersSize, Setting.FlingType, Setting.UseCompactSelectors, Setting.DragHandleWidth, Setting.CenterAlignDialogButtons, Setting.FabAlignment ), initialState = false ) data object NightMode : SettingsGroup( id = 4, icon = Icons.Rounded.Routine, titleId = R.string.night_mode, settingsList = listOf( Setting.NightMode ), initialState = false ) data object Shadows : SettingsGroup( id = 5, icon = Icons.Outlined.Shadow, titleId = R.string.shadows, settingsList = listOf( Setting.ContainerShadows, Setting.AppBarShadows, Setting.ButtonShadows, Setting.FABShadows, Setting.SwitchShadows, Setting.SliderShadows ), initialState = false ) data object Font : SettingsGroup( id = 6, icon = Icons.Outlined.Glyphs, titleId = R.string.text, settingsList = listOf( Setting.ChangeLanguage, Setting.ChangeFont, Setting.FontScale ), initialState = false ) data object ToolsArrangement : SettingsGroup( id = 7, icon = Icons.Rounded.ResponsiveLayout, titleId = R.string.tools_arrangement, settingsList = listOf( Setting.ScreenOrder, Setting.ScreenSearch, Setting.EnableLauncherMode, Setting.GroupOptions ), initialState = false ) data object Presets : SettingsGroup( id = 8, icon = Icons.Rounded.LabelPercent, titleId = R.string.presets, settingsList = listOf( Setting.Presets, Setting.CanEnterPresetsByTextField ), initialState = false ) data object DefaultValues : SettingsGroup( id = 9, icon = Icons.Rounded.SquareFoot, titleId = R.string.default_values, settingsList = listOf( Setting.DefaultScaleMode, Setting.DefaultColorSpace, Setting.DefaultImageFormat, Setting.DefaultQuality, Setting.DefaultResizeType ), initialState = false ) data object Draw : SettingsGroup( id = 10, icon = Icons.Rounded.Draw, titleId = R.string.draw, settingsList = listOf( Setting.LockDrawOrientation, Setting.DefaultDrawLineWidth, Setting.DefaultDrawColor, Setting.DefaultDrawPathMode, Setting.Magnifier ), initialState = false ) data object Exif : SettingsGroup( id = 11, icon = Icons.Rounded.Exif, titleId = R.string.exif, settingsList = listOf( Setting.ExifWidgetInitialState, Setting.KeepDateTime ), initialState = false ) data object Folder : SettingsGroup( id = 12, icon = Icons.Rounded.FolderOpened, titleId = R.string.folder, settingsList = listOf( Setting.SavingFolder, Setting.OneTimeSaveLocation ), initialState = false ) data object Filename : SettingsGroup( id = 13, icon = Icons.Rounded.Description, titleId = R.string.filename, settingsList = listOf( Setting.FilenamePrefix, Setting.FilenameSuffix, Setting.FilenamePattern, Setting.AddFileSize, Setting.AddOriginalFilename, Setting.ReplaceSequenceNumber, Setting.AddTimestampToFilename, Setting.UseFormattedFilenameTimestamp, Setting.AddPresetToFilename, Setting.AddImageScaleModeToFilename, Setting.OverwriteFiles, Setting.ChecksumAsFilename, Setting.RandomizeFilename ), initialState = false ) data object Cache : SettingsGroup( id = 14, icon = Icons.Rounded.Database, titleId = R.string.cache, settingsList = listOf( Setting.ClearCache, Setting.AutoCacheClear ), initialState = false ) data object ImageSource : SettingsGroup( id = 15, icon = Icons.Rounded.ImageSearch, titleId = R.string.image_source, settingsList = listOf( Setting.ImagePickerMode ), initialState = false ) data object BackupRestore : SettingsGroup( id = 16, icon = Icons.Rounded.HardDrive, titleId = R.string.backup_and_restore, settingsList = listOf( Setting.Backup, Setting.Restore, Setting.Reset ), initialState = false ) data object Firebase : SettingsGroup( id = 17, icon = Icons.Outlined.Firebase, titleId = R.string.firebase, settingsList = listOf( Setting.Crashlytics, Setting.Analytics ), initialState = false ) data object Updates : SettingsGroup( id = 18, icon = Icons.Rounded.MobileArrowDown, titleId = R.string.updates, settingsList = listOf( Setting.AutoCheckUpdates, Setting.AllowBetas, Setting.CheckUpdatesButton ), initialState = false ) data object AboutApp : SettingsGroup( id = 19, icon = Icons.Rounded.Info, titleId = R.string.about_app, settingsList = listOf( Setting.CurrentVersionCode, Setting.OpenSourceLicenses, Setting.HelpTranslate, Setting.IssueTracker, Setting.FreeSoftwarePartner, Setting.TelegramGroup, Setting.TelegramChannel, Setting.SourceCode ), initialState = true ) data object Clipboard : SettingsGroup( id = 20, icon = Icons.Rounded.ClipboardFile, titleId = R.string.clipboard, settingsList = listOf( Setting.AutoPinClipboard, Setting.AutoPinClipboardOnlyClip, Setting.AllowAutoClipboardPaste ), initialState = false ) data object Haptics : SettingsGroup( id = 21, icon = Icons.Rounded.MobileVibrate, titleId = R.string.vibration, settingsList = listOf( Setting.VibrationStrength ), initialState = false ) data object Screen : SettingsGroup( id = 22, icon = Icons.Rounded.Mobile, titleId = R.string.screen, settingsList = listOf( Setting.BrightnessEnforcement, Setting.SecureMode, Setting.SystemBarsVisibility, Setting.ShowSystemBarsBySwipe ), initialState = false ) data object Emoji : SettingsGroup( id = 23, icon = Icons.Rounded.Cool, titleId = R.string.emoji, settingsList = listOf( Setting.Emoji, Setting.EmojisCount, Setting.UseRandomEmojis ), initialState = false ) data object Confetti : SettingsGroup( id = 24, icon = Icons.Rounded.Celebration, titleId = R.string.confetti, settingsList = listOf( Setting.Confetti, Setting.ConfettiHarmonizer, Setting.ConfettiHarmonizationLevel, Setting.ConfettiType ), initialState = false ) data object Behavior : SettingsGroup( id = 25, icon = Icons.Rounded.Psychology, titleId = R.string.behavior, settingsList = listOf( Setting.SkipFilePicking, Setting.AllowSkipIfLarger, Setting.ToolsHiddenForShare, Setting.EnableToolExitConfirmation, Setting.EnableBackgroundColorForAlphaFormats, Setting.ShowSettingsInLandscape, Setting.UseFullscreenSettings, Setting.FastSettingsSide, Setting.OpenEditInsteadOfPreview, Setting.SnowfallMode, Setting.EnableLinksPreview, Setting.GeneratePreviews ), initialState = false ) companion object { val entries: List by lazy { listOf( ContactMe, PrimaryCustomization, SecondaryCustomization, NightMode, Layout, Emoji, Confetti, Shadows, Haptics, Screen, Font, Behavior, ToolsArrangement, Presets, DefaultValues, Draw, Exif, Folder, Filename, Clipboard, Cache, ImageSource, BackupRestore, Firebase, Updates, AboutApp ).filter { !(it is Firebase && Flavor.isFoss()) } } } } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiFontFamily.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("MemberVisibilityCanBePrivate") package com.t8rin.imagetoolbox.core.settings.presentation.model import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontVariation import androidx.compose.ui.text.font.FontWeight import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.DomainFontFamily import com.t8rin.imagetoolbox.core.settings.domain.model.FontType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import java.io.File sealed class UiFontFamily( val name: String?, private val variable: Boolean, val type: FontType? = null ) { val isVariable: Boolean? get() = variable.takeIf { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O } val fontFamily: FontFamily get() = type?.let { when (it) { is FontType.File -> fontFamilyFromFile(file = File(it.path)) is FontType.Resource -> fontFamilyResource(resId = it.resId) } } ?: FontFamily.Default constructor( name: String?, variable: Boolean, fontRes: Int ) : this( name = name, variable = variable, type = FontType.Resource(fontRes) ) constructor( name: String?, variable: Boolean, filePath: String ) : this( name = name, variable = variable, type = FontType.File(filePath) ) operator fun component1() = fontFamily operator fun component2() = name operator fun component3() = isVariable operator fun component4() = type data object Montserrat : UiFontFamily( fontRes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { R.font.montserrat_variable } else R.font.montserrat_regular, name = "Montserrat", variable = true ) data object Caveat : UiFontFamily( fontRes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { R.font.caveat_variable } else R.font.caveat_regular, name = "Caveat", variable = true ) data object System : UiFontFamily( name = null, variable = true ) data object Comfortaa : UiFontFamily( fontRes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { R.font.comfortaa_varibale } else R.font.comfortaa_regular, name = "Comfortaa", variable = true ) data object Handjet : UiFontFamily( fontRes = R.font.handjet_varibale, name = "Handjet", variable = true ) data object YsabeauSC : UiFontFamily( fontRes = R.font.ysabeau_sc_variable, name = "Ysabeau SC", variable = true ) data object Jura : UiFontFamily( fontRes = R.font.jura_variable, name = "Jura", variable = true ) data object Tektur : UiFontFamily( fontRes = R.font.tektur_variable, name = "Tektur", variable = true ) data object Podkova : UiFontFamily( fontRes = R.font.podkova_variable, name = "Podkova", variable = true ) data object DejaVu : UiFontFamily( fontRes = R.font.dejavu_regular, name = "Deja Vu", variable = false ) data object BadScript : UiFontFamily( fontRes = R.font.bad_script_regular, name = "Bad Script", variable = false ) data object RuslanDisplay : UiFontFamily( fontRes = R.font.ruslan_display_regular, name = "Ruslan Display", variable = false ) data object Catterdale : UiFontFamily( fontRes = R.font.cattedrale_regular, name = "Catterdale", variable = false ) data object FRM32 : UiFontFamily( fontRes = R.font.frm32_regular, name = "FRM32", variable = false ) data object TokeelyBrookings : UiFontFamily( fontRes = R.font.tokeely_brookings_regular, name = "Tokeely Brookings", variable = false ) data object Nunito : UiFontFamily( fontRes = R.font.nunito_variable, name = "Nunito", variable = true ) data object Nothing : UiFontFamily( fontRes = R.font.nothing_font_regular, name = "Nothing", variable = false ) data object WOPRTweaked : UiFontFamily( fontRes = R.font.wopr_tweaked_regular, name = "WOPR Tweaked", variable = false ) data object AlegreyaSans : UiFontFamily( fontRes = R.font.alegreya_sans_regular, name = "Alegreya Sans", variable = false ) data object MinecraftGnu : UiFontFamily( fontRes = R.font.minecraft_gnu_regular, name = "Minecraft GNU", variable = false ) data object GraniteFixed : UiFontFamily( fontRes = R.font.granite_fixed_regular, name = "Granite Fixed", variable = false ) data object NokiaPixel : UiFontFamily( fontRes = R.font.nokia_pixel_regular, name = "Nokia Pixel", variable = false ) data object Ztivalia : UiFontFamily( fontRes = R.font.ztivalia_regular, name = "Ztivalia", variable = false ) data object Axotrel : UiFontFamily( fontRes = R.font.axotrel_regular, name = "Axotrel", variable = false ) data object LcdOctagon : UiFontFamily( fontRes = R.font.lcd_octagon_regular, name = "LCD Octagon", variable = false ) data object LcdMoving : UiFontFamily( fontRes = R.font.lcd_moving_regular, name = "LCD Moving", variable = false ) data object Unisource : UiFontFamily( fontRes = R.font.unisource_regular, name = "Unisource", variable = false ) class Custom( name: String?, val filePath: String ) : UiFontFamily( name = name, variable = false, filePath = filePath ) { override fun equals(other: Any?): Boolean { if (other !is Custom) return false return filePath == other.filePath } override fun hashCode(): Int { return filePath.hashCode() } override fun toString(): String { return "Custom(name = $name, filePath = $filePath)" } } fun asDomain(): DomainFontFamily { return when (this) { Caveat -> DomainFontFamily.Caveat Comfortaa -> DomainFontFamily.Comfortaa System -> DomainFontFamily.System Handjet -> DomainFontFamily.Handjet Jura -> DomainFontFamily.Jura Podkova -> DomainFontFamily.Podkova Tektur -> DomainFontFamily.Tektur YsabeauSC -> DomainFontFamily.YsabeauSC Montserrat -> DomainFontFamily.Montserrat DejaVu -> DomainFontFamily.DejaVu BadScript -> DomainFontFamily.BadScript RuslanDisplay -> DomainFontFamily.RuslanDisplay Catterdale -> DomainFontFamily.Catterdale FRM32 -> DomainFontFamily.FRM32 TokeelyBrookings -> DomainFontFamily.TokeelyBrookings Nunito -> DomainFontFamily.Nunito Nothing -> DomainFontFamily.Nothing WOPRTweaked -> DomainFontFamily.WOPRTweaked AlegreyaSans -> DomainFontFamily.AlegreyaSans MinecraftGnu -> DomainFontFamily.MinecraftGnu GraniteFixed -> DomainFontFamily.GraniteFixed NokiaPixel -> DomainFontFamily.NokiaPixel Ztivalia -> DomainFontFamily.Ztivalia Axotrel -> DomainFontFamily.Axotrel LcdMoving -> DomainFontFamily.LcdMoving LcdOctagon -> DomainFontFamily.LcdOctagon Unisource -> DomainFontFamily.Unisource is Custom -> DomainFontFamily.Custom(name, filePath) } } companion object { val entries: List @Composable get() = defaultEntries + customEntries val defaultEntries: List by lazy { listOf( Montserrat, Caveat, Comfortaa, Handjet, Jura, Podkova, Tektur, YsabeauSC, DejaVu, BadScript, RuslanDisplay, Catterdale, FRM32, TokeelyBrookings, Nunito, Nothing, WOPRTweaked, AlegreyaSans, MinecraftGnu, GraniteFixed, NokiaPixel, Ztivalia, Axotrel, LcdOctagon, LcdMoving, Unisource, System ).sortedBy { it.name } } val customEntries: List @Composable get() { val customFonts = LocalSettingsState.current.customFonts return remember(customFonts) { derivedStateOf { customFonts.sortedBy { it.name } } }.value } } } @Composable fun FontType?.toUiFont(): UiFontFamily { val entries = UiFontFamily.entries return remember(entries, this) { derivedStateOf { when (this) { is FontType.File -> UiFontFamily.Custom( name = File(path).nameWithoutExtension.replace("[:\\-_.,]".toRegex(), " "), filePath = path ) is FontType.Resource -> entries.find { it.type == this } ?: UiFontFamily.System null -> UiFontFamily.System } } }.value } fun FontType?.asUi(): UiFontFamily { val entries = UiFontFamily.defaultEntries return when (this) { is FontType.File -> UiFontFamily.Custom( name = File(path).nameWithoutExtension.replace("[:\\-_.,]".toRegex(), " "), filePath = path ) is FontType.Resource -> entries.find { it.type == this } ?: UiFontFamily.System null -> UiFontFamily.System } } fun FontType?.asDomain(): DomainFontFamily = this?.asUi()?.asDomain() ?: DomainFontFamily.System fun DomainFontFamily?.asFontType(): FontType? = this?.toUiFont()?.type fun DomainFontFamily.toUiFont(): UiFontFamily = when (this) { DomainFontFamily.Caveat -> UiFontFamily.Caveat DomainFontFamily.Comfortaa -> UiFontFamily.Comfortaa DomainFontFamily.System -> UiFontFamily.System DomainFontFamily.Handjet -> UiFontFamily.Handjet DomainFontFamily.Jura -> UiFontFamily.Jura DomainFontFamily.Montserrat -> UiFontFamily.Montserrat DomainFontFamily.Podkova -> UiFontFamily.Podkova DomainFontFamily.Tektur -> UiFontFamily.Tektur DomainFontFamily.YsabeauSC -> UiFontFamily.YsabeauSC DomainFontFamily.DejaVu -> UiFontFamily.DejaVu DomainFontFamily.BadScript -> UiFontFamily.BadScript DomainFontFamily.RuslanDisplay -> UiFontFamily.RuslanDisplay DomainFontFamily.Catterdale -> UiFontFamily.Catterdale DomainFontFamily.FRM32 -> UiFontFamily.FRM32 DomainFontFamily.TokeelyBrookings -> UiFontFamily.TokeelyBrookings DomainFontFamily.Nunito -> UiFontFamily.Nunito DomainFontFamily.Nothing -> UiFontFamily.Nothing DomainFontFamily.WOPRTweaked -> UiFontFamily.WOPRTweaked DomainFontFamily.AlegreyaSans -> UiFontFamily.AlegreyaSans DomainFontFamily.MinecraftGnu -> UiFontFamily.MinecraftGnu DomainFontFamily.GraniteFixed -> UiFontFamily.GraniteFixed DomainFontFamily.NokiaPixel -> UiFontFamily.NokiaPixel DomainFontFamily.Ztivalia -> UiFontFamily.Ztivalia DomainFontFamily.Axotrel -> UiFontFamily.Axotrel DomainFontFamily.LcdMoving -> UiFontFamily.LcdMoving DomainFontFamily.LcdOctagon -> UiFontFamily.LcdOctagon DomainFontFamily.Unisource -> UiFontFamily.Unisource is DomainFontFamily.Custom -> UiFontFamily.Custom( name = name, filePath = filePath ) } private fun fontFamilyResource(resId: Int) = FontFamily( Font( resId = resId, weight = FontWeight.Light, variationSettings = FontVariation.Settings( weight = FontWeight.Light, style = FontStyle.Normal ) ), Font( resId = resId, weight = FontWeight.Normal, variationSettings = FontVariation.Settings( weight = FontWeight.Normal, style = FontStyle.Normal ) ), Font( resId = resId, weight = FontWeight.Medium, variationSettings = FontVariation.Settings( weight = FontWeight.Medium, style = FontStyle.Normal ) ), Font( resId = resId, weight = FontWeight.SemiBold, variationSettings = FontVariation.Settings( weight = FontWeight.SemiBold, style = FontStyle.Normal ) ), Font( resId = resId, weight = FontWeight.Bold, variationSettings = FontVariation.Settings( weight = FontWeight.Bold, style = FontStyle.Normal ) ) ) private fun fontFamilyFromFile(file: File) = FontFamily( Font( file = file, weight = FontWeight.Light, variationSettings = FontVariation.Settings( weight = FontWeight.Light, style = FontStyle.Normal ) ), Font( file = file, weight = FontWeight.Normal, variationSettings = FontVariation.Settings( weight = FontWeight.Normal, style = FontStyle.Normal ) ), Font( file = file, weight = FontWeight.Medium, variationSettings = FontVariation.Settings( weight = FontWeight.Medium, style = FontStyle.Normal ) ), Font( file = file, weight = FontWeight.SemiBold, variationSettings = FontVariation.Settings( weight = FontWeight.SemiBold, style = FontStyle.Normal ) ), Font( file = file, weight = FontWeight.Bold, variationSettings = FontVariation.Settings( weight = FontWeight.Bold, style = FontStyle.Normal ) ) ) ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/model/UiSettingsState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.presentation.model import android.net.Uri import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.net.toUri import coil3.imageLoader import coil3.request.ImageRequest import coil3.toBitmap import com.t8rin.dynamic.theme.ColorBlindType import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.dynamic.theme.extractPrimaryColor import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.resources.BuildConfig import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.emoji.Emoji import com.t8rin.imagetoolbox.core.settings.domain.model.ColorHarmonizer import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.settings.domain.model.NightMode import com.t8rin.imagetoolbox.core.settings.domain.model.OneTimeSaveLocation import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.domain.model.SnowfallMode import com.t8rin.imagetoolbox.core.settings.domain.model.SwitchType import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.launch @Stable data class UiSettingsState( val isNightMode: Boolean, val isDynamicColors: Boolean, val allowChangeColorByImage: Boolean, val emojisCount: Int, val isAmoledMode: Boolean, val appColorTuple: ColorTuple, val borderWidth: Dp, val presets: List, val fabAlignment: Alignment, val showUpdateDialogOnStartup: Boolean, val selectedEmoji: Uri?, val picturePickerMode: PicturePickerMode, val clearCacheOnLaunch: Boolean, val groupOptionsByTypes: Boolean, val screenList: List, val colorTupleList: List, val addSequenceNumber: Boolean, val saveFolderUri: Uri?, val filenamePrefix: String, val addSizeInFilename: Boolean, val addOriginalFilename: Boolean, val font: UiFontFamily, val fontScale: Float?, val allowCollectCrashlytics: Boolean, val allowCollectAnalytics: Boolean, val allowBetas: Boolean, val drawContainerShadows: Boolean, val drawButtonShadows: Boolean, val drawSliderShadows: Boolean, val drawSwitchShadows: Boolean, val drawFabShadows: Boolean, val drawAppBarShadows: Boolean, val appOpenCount: Int, val aspectRatios: List, val lockDrawOrientation: Boolean, val themeContrastLevel: Double, val themeStyle: PaletteStyle, val isInvertThemeColors: Boolean, val screensSearchEnabled: Boolean, val copyToClipboardMode: CopyToClipboardMode, val hapticsStrength: Int, val filenameSuffix: String, val defaultImageScaleMode: ImageScaleMode, val magnifierEnabled: Boolean, val exifWidgetInitialState: Boolean, val screenListWithMaxBrightnessEnforcement: List, val isConfettiEnabled: Boolean, val isSecureMode: Boolean, val useRandomEmojis: Boolean, val iconShape: IconShape?, val useEmojiAsPrimaryColor: Boolean, val dragHandleWidth: Dp, val confettiType: Int, val allowAutoClipboardPaste: Boolean, val confettiColorHarmonizer: ColorHarmonizer, val confettiHarmonizationLevel: Float, val skipImagePicking: Boolean, val generatePreviews: Boolean, val showSettingsInLandscape: Boolean, val useFullscreenSettings: Boolean, val switchType: SwitchType, val defaultDrawLineWidth: Float, val oneTimeSaveLocations: List, val openEditInsteadOfPreview: Boolean, val canEnterPresetsByTextField: Boolean, val donateDialogOpenCount: Int?, val colorBlindType: ColorBlindType?, val favoriteScreenList: List, val isLinkPreviewEnabled: Boolean, val defaultDrawColor: Color, val defaultDrawPathMode: Int, val addTimestampToFilename: Boolean, val useFormattedFilenameTimestamp: Boolean, val favoriteColors: List, val defaultResizeType: ResizeType, val systemBarsVisibility: SystemBarsVisibility, val isSystemBarsVisibleBySwipe: Boolean, val isCompactSelectorsLayout: Boolean, val mainScreenTitle: String, val sliderType: SliderType, val isCenterAlignDialogButtons: Boolean, val fastSettingsSide: FastSettingsSide, val settingGroupsInitialVisibility: Map, val customFonts: List, val enableToolExitConfirmation: Boolean, val recentColors: List, val backgroundForNoAlphaImageFormats: Color, val addPresetInfoToFilename: Boolean, val addImageScaleModeInfoToFilename: Boolean, val allowSkipIfLarger: Boolean, val customAsciiGradients: Set, val isScreenSelectionLauncherMode: Boolean, val spotHealMode: Int, val snowfallMode: SnowfallMode, val defaultImageFormat: ImageFormat?, val defaultQuality: Quality, val shapesType: ShapeType, val filenamePattern: String?, val filenameBehavior: FilenameBehavior, val flingType: FlingType, val hiddenForShareScreens: List, val keepDateTime: Boolean, val enableBackgroundColorForAlphaFormats: Boolean, ) fun UiSettingsState.isFirstLaunch( approximate: Boolean = true, ) = if (approximate) { appOpenCount <= 3 } else appOpenCount <= 1 @Composable fun SettingsState.toUiState( randomEmojiKey: Any? = null, ): UiSettingsState { val allEmojis = Emoji.allIcons() val allIconShapes: ImmutableList = IconShape.entries val context = LocalContext.current val scope = rememberCoroutineScope() val isNightMode = nightMode.isNightMode() val selectedEmojiIndex by remember(selectedEmoji, useRandomEmojis, randomEmojiKey) { derivedStateOf { selectedEmoji?.takeIf { it != -1 }?.let { if (useRandomEmojis) allEmojis.indices.random() else it } } } var emojiColorTuple: ColorTuple? by remember { mutableStateOf(null) } val appColorTupleComposed by remember( allEmojis, selectedEmojiIndex, appColorTuple, useEmojiAsPrimaryColor ) { derivedStateOf { if (useEmojiAsPrimaryColor) { selectedEmojiIndex?.let { index -> scope.launch { context.imageLoader.execute( ImageRequest.Builder(context) .target { emojiColorTuple = ColorTuple(it.toBitmap().extractPrimaryColor()) } .data(allEmojis[index].toString()) .build() ) } } } else { emojiColorTuple = null } appColorTuple.asColorTuple() } } val appColorTuple by remember(appColorTupleComposed, appColorTuple) { derivedStateOf { emojiColorTuple ?: appColorTupleComposed } } val borderWidth by animateDpAsState(borderWidth.dp) val presets by remember(presets) { derivedStateOf { presets.mapNotNull(Preset::value) } } val selectedEmoji by remember(selectedEmojiIndex, allEmojis) { derivedStateOf { selectedEmojiIndex?.let(allEmojis::getOrNull) } } val colorTupleList by remember(colorTupleList) { derivedStateOf { colorTupleList.toColorTupleList() } } val saveFolderUri by remember(saveFolderUri) { derivedStateOf { saveFolderUri?.toUri()?.takeIf { it != Uri.EMPTY } } } val font by remember(font) { derivedStateOf { font.toUiFont() } } val themeStyle by remember(themeStyle) { derivedStateOf { PaletteStyle .entries .getOrNull(themeStyle) ?: PaletteStyle.TonalSpot } } val iconShape by remember(iconShape) { derivedStateOf { iconShape?.let(allIconShapes::getOrNull) } } val dragHandleWidth by animateDpAsState(dragHandleWidth.dp) val colorBlindType by remember(colorBlindType) { derivedStateOf { colorBlindType?.let { ColorBlindType.entries.getOrNull(it) } } } val favoriteColors by remember(favoriteColors) { derivedStateOf { favoriteColors.map { Color(it.colorInt) } } } val recentColors by remember(recentColors) { derivedStateOf { recentColors.map { Color(it.colorInt) } } } val mainScreenTitle = mainScreenTitle.ifEmpty { stringResource(R.string.app_name) } val customFonts by remember(customFonts) { derivedStateOf { customFonts.map { it.toUiFont() as UiFontFamily.Custom } } } return remember(this, selectedEmoji) { derivedStateOf { UiSettingsState( isNightMode = isNightMode, isDynamicColors = isDynamicColors, allowChangeColorByImage = allowChangeColorByImage, emojisCount = emojisCount, isAmoledMode = isAmoledMode, appColorTuple = appColorTuple, borderWidth = borderWidth, presets = presets, fabAlignment = fabAlignment.toAlignment(), showUpdateDialogOnStartup = showUpdateDialogOnStartup, selectedEmoji = selectedEmoji, picturePickerMode = PicturePickerMode.fromInt(picturePickerModeInt), clearCacheOnLaunch = clearCacheOnLaunch, groupOptionsByTypes = groupOptionsByTypes, screenList = screenList, colorTupleList = colorTupleList, addSequenceNumber = addSequenceNumber, saveFolderUri = saveFolderUri, filenamePrefix = filenamePrefix, addSizeInFilename = addSizeInFilename, addOriginalFilename = addOriginalFilename, font = font, fontScale = fontScale?.takeIf { it > 0 }, allowCollectCrashlytics = allowCollectCrashlytics, allowCollectAnalytics = allowCollectAnalytics, allowBetas = allowBetas, drawContainerShadows = drawContainerShadows, drawButtonShadows = drawButtonShadows, drawFabShadows = drawFabShadows, drawSliderShadows = drawSliderShadows, drawSwitchShadows = drawSwitchShadows, drawAppBarShadows = drawAppBarShadows, appOpenCount = appOpenCount, aspectRatios = aspectRatios, lockDrawOrientation = lockDrawOrientation, themeContrastLevel = themeContrastLevel, themeStyle = themeStyle, isInvertThemeColors = isInvertThemeColors, screensSearchEnabled = screensSearchEnabled, copyToClipboardMode = copyToClipboardMode, hapticsStrength = hapticsStrength, filenameSuffix = filenameSuffix, defaultImageScaleMode = defaultImageScaleMode, magnifierEnabled = magnifierEnabled, exifWidgetInitialState = exifWidgetInitialState, screenListWithMaxBrightnessEnforcement = screenListWithMaxBrightnessEnforcement, isConfettiEnabled = isConfettiEnabled, isSecureMode = isSecureMode, useRandomEmojis = useRandomEmojis, iconShape = iconShape, useEmojiAsPrimaryColor = useEmojiAsPrimaryColor, dragHandleWidth = dragHandleWidth, confettiType = confettiType, allowAutoClipboardPaste = allowAutoClipboardPaste, confettiColorHarmonizer = confettiColorHarmonizer, confettiHarmonizationLevel = confettiHarmonizationLevel, skipImagePicking = skipImagePicking, generatePreviews = generatePreviews, showSettingsInLandscape = showSettingsInLandscape, useFullscreenSettings = useFullscreenSettings, switchType = switchType, defaultDrawLineWidth = defaultDrawLineWidth, oneTimeSaveLocations = oneTimeSaveLocations, openEditInsteadOfPreview = openEditInsteadOfPreview, canEnterPresetsByTextField = canEnterPresetsByTextField, donateDialogOpenCount = donateDialogOpenCount.takeIf { it >= 0 }, colorBlindType = colorBlindType, favoriteScreenList = favoriteScreenList, isLinkPreviewEnabled = isLinkPreviewEnabled, defaultDrawColor = Color(defaultDrawColor.colorInt), defaultDrawPathMode = defaultDrawPathMode, addTimestampToFilename = addTimestampToFilename, useFormattedFilenameTimestamp = useFormattedFilenameTimestamp, favoriteColors = favoriteColors, defaultResizeType = defaultResizeType, systemBarsVisibility = systemBarsVisibility, isSystemBarsVisibleBySwipe = isSystemBarsVisibleBySwipe, isCompactSelectorsLayout = isCompactSelectorsLayout, mainScreenTitle = mainScreenTitle, sliderType = sliderType, isCenterAlignDialogButtons = isCenterAlignDialogButtons, fastSettingsSide = fastSettingsSide, settingGroupsInitialVisibility = settingGroupsInitialVisibility, customFonts = customFonts, enableToolExitConfirmation = enableToolExitConfirmation, recentColors = recentColors, backgroundForNoAlphaImageFormats = Color(backgroundForNoAlphaImageFormats.colorInt), addPresetInfoToFilename = addPresetInfoToFilename, addImageScaleModeInfoToFilename = addImageScaleModeInfoToFilename, allowSkipIfLarger = allowSkipIfLarger, customAsciiGradients = customAsciiGradients, isScreenSelectionLauncherMode = isScreenSelectionLauncherMode, spotHealMode = spotHealMode, snowfallMode = snowfallMode, defaultImageFormat = defaultImageFormat, defaultQuality = defaultQuality, shapesType = shapesType, filenamePattern = filenamePattern, filenameBehavior = filenameBehavior, flingType = flingType, hiddenForShareScreens = hiddenForShareScreens, keepDateTime = keepDateTime, enableBackgroundColorForAlphaFormats = enableBackgroundColorForAlphaFormats, ) } }.value } private fun String?.toColorTupleList(): List { val list = mutableListOf() this?.split("*")?.forEach { colorTuple -> val temp = colorTuple.split("/") temp.getOrNull(0)?.toIntOrNull()?.toColor()?.let { list.add( ColorTuple( primary = it, secondary = temp.getOrNull(1)?.toIntOrNull()?.toColor(), tertiary = temp.getOrNull(2)?.toIntOrNull()?.toColor(), surface = temp.getOrNull(3)?.toIntOrNull()?.toColor() ) ) } } if (list.isEmpty()) { list.add(defaultColorTuple) } return list.toHashSet().toList() } private fun Int.toColor() = Color(this) fun String.asColorTuple(): ColorTuple { val colorTuple = split("*") return ColorTuple( primary = colorTuple.getOrNull(0)?.toIntOrNull()?.let { Color(it) } ?: defaultColorTuple.primary, secondary = colorTuple.getOrNull(1)?.toIntOrNull()?.let { Color(it) }, tertiary = colorTuple.getOrNull(2)?.toIntOrNull()?.let { Color(it) }, surface = colorTuple.getOrNull(3)?.toIntOrNull()?.let { Color(it) }, ) } private fun Int.toAlignment() = when (this) { 0 -> Alignment.BottomStart 1 -> Alignment.BottomCenter else -> Alignment.BottomEnd } @Composable private fun NightMode.isNightMode(): Boolean = when (this) { NightMode.System -> isSystemInDarkTheme() else -> this is NightMode.Dark } val defaultColorTuple = ColorTuple( if (BuildConfig.DEBUG) Color(0xFF3ADBD6) else Color(0xFF8FDB3A) ) ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/provider/LocalSettingsState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.presentation.provider import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.saveable.rememberSaveable import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.rememberAppColorTuple import com.t8rin.imagetoolbox.core.settings.domain.SimpleSettingsInteractor import com.t8rin.imagetoolbox.core.settings.presentation.model.EditPresetsController import com.t8rin.imagetoolbox.core.settings.presentation.model.UiSettingsState val LocalSettingsState = compositionLocalOf { error("UiSettingsState not present") } val LocalSimpleSettingsInteractor = compositionLocalOf { error("SimpleSettingInteractor not present") } val LocalEditPresetsController = compositionLocalOf { error("EditPresetsController not present") } @Composable fun rememberAppColorTuple( settingsState: UiSettingsState = LocalSettingsState.current ): ColorTuple = rememberAppColorTuple( defaultColorTuple = settingsState.appColorTuple, dynamicColor = settingsState.isDynamicColors, darkTheme = settingsState.isNightMode ) @Composable fun rememberEditPresetsController( initialVisibility: Boolean = false ) = rememberSaveable(initialVisibility, saver = EditPresetsController.Saver) { EditPresetsController(initialVisibility) } ================================================ FILE: core/settings/src/main/java/com/t8rin/imagetoolbox/core/settings/presentation/utils/RoundedPolygonUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.settings.presentation.utils import androidx.compose.foundation.shape.GenericShape import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.util.fastForEach import androidx.graphics.shapes.Cubic import androidx.graphics.shapes.RoundedPolygon import kotlin.math.PI import kotlin.math.atan2 fun RoundedPolygon.toShape(startAngle: Int = 0): Shape { return GenericShape { size, _ -> rewind() addPath(toPath(startAngle = startAngle)) val scaleMatrix = Matrix().apply { scale(x = size.width, y = size.height) } transform(scaleMatrix) } } fun RoundedPolygon.toPath( path: Path = Path(), startAngle: Int = 0, repeatPath: Boolean = false, closePath: Boolean = true, ): Path { pathFromCubics( path = path, startAngle = startAngle, repeatPath = repeatPath, closePath = closePath, cubics = cubics, rotationPivotX = centerX, rotationPivotY = centerY ) return path } private fun pathFromCubics( path: Path, startAngle: Int, repeatPath: Boolean, closePath: Boolean, cubics: List, rotationPivotX: Float, rotationPivotY: Float ) { var first = true var firstCubic: Cubic? = null path.rewind() cubics.fastForEach { if (first) { path.moveTo(it.anchor0X, it.anchor0Y) if (startAngle != 0) { firstCubic = it } first = false } path.cubicTo( it.control0X, it.control0Y, it.control1X, it.control1Y, it.anchor1X, it.anchor1Y ) } if (repeatPath) { var firstInRepeat = true cubics.fastForEach { if (firstInRepeat) { path.lineTo(it.anchor0X, it.anchor0Y) firstInRepeat = false } path.cubicTo( it.control0X, it.control0Y, it.control1X, it.control1Y, it.anchor1X, it.anchor1Y ) } } if (closePath) path.close() if (startAngle != 0 && firstCubic != null) { val angleToFirstCubic = radiansToDegrees( atan2( y = cubics[0].anchor0Y - rotationPivotY, x = cubics[0].anchor0X - rotationPivotX ) ) // Rotate the Path to to start from the given angle. path.transform(Matrix().apply { rotateZ(-angleToFirstCubic + startAngle) }) } } private fun radiansToDegrees(radians: Float): Float { return (radians * 180.0 / PI).toFloat() } ================================================ FILE: core/ui/.gitignore ================================================ /build ================================================ FILE: core/ui/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.core.ui" dependencies { api(projects.core.resources) api(projects.core.domain) api(projects.core.utils) implementation(projects.core.di) implementation(projects.core.settings) // Navigation api(libs.decompose) api(libs.decomposeExtensions) //AndroidX api(libs.activityCompose) api(libs.splashScreen) api(libs.appCompat) api(libs.androidx.documentfile) //Konfetti api(libs.konfetti.compose) //Coil api(libs.coil) api(libs.coilCompose) api(libs.coilGif) api(libs.coilSvg) api(libs.coilNetwork) api(libs.ktor) //Modules api(libs.toolbox.uCrop) api(projects.lib.cropper) api(projects.lib.dynamicTheme) api(projects.lib.colors) api(projects.lib.gesture) api(projects.lib.image) api(projects.lib.modalsheet) api(libs.logger) api(projects.lib.zoomable) api(projects.lib.snowfall) api(libs.toolbox.histogram) api(libs.reorderable) api(libs.shadowGadgets) api(libs.shadowsPlus) api(libs.kotlinx.collections.immutable) api(libs.fadingEdges) api(libs.scrollbar) implementation(libs.datastore.preferences.android) implementation(libs.datastore.core.android) api(libs.material) "marketImplementation"(platform(libs.firebase.bom)) "marketImplementation"(libs.firebase.crashlytics) "marketImplementation"(libs.firebase.analytics) "marketImplementation"(libs.review.ktx) "marketImplementation"(libs.app.update) "marketImplementation"(libs.app.update.ktx) "marketImplementation"(libs.mlkit.document.scanner) "fossImplementation"(projects.lib.documentscanner) "marketImplementation"(libs.quickie.bundled) "fossImplementation"(libs.quickie.foss) implementation(libs.zxing.core) implementation(projects.lib.qrose) implementation(libs.jsoup) api(libs.androidliquidglass) api(libs.capsule) api(libs.squircle.shape) api(libs.evaluator) api(libs.flinger) } ================================================ FILE: core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.provider.MediaStore import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CameraAlt import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.core.content.ContextCompat import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ScanResult import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.utils.appContext import com.websitebeaver.documentscanner.DocumentScanner as DocumentScannerDelegate private class DocumentScannerImpl( private val context: Context, private val scanner: DocumentScannerDelegate, private val scannerLauncher: ManagedActivityResultLauncher, private val requestPermissionLauncher: ManagedActivityResultLauncher ) : DocumentScanner { override fun scan() { if (ContextCompat.checkSelfPermission( context, Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED ) { scannerLauncher.launch(scanner.createDocumentScanIntent()) } else { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } } @Composable internal fun rememberDocumentScannerImpl( onSuccess: (ScanResult) -> Unit ): DocumentScanner { val context = LocalComponentActivity.current val scanner = remember(context) { DocumentScannerDelegate( activity = context, successHandler = { imageUris -> onSuccess( ScanResult(imageUris.map { it.toUri() }) ) }, errorHandler = { if (it.contains(MediaStore.ACTION_IMAGE_CAPTURE)) { AppToastHost.showToast( message = context.getString(R.string.camera_failed_to_open), icon = Icons.Outlined.CameraAlt ) } else { AppToastHost.showFailureToast(it) } } ) } val scannerLauncher = rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> scanner.handleDocumentScanIntentResult(result) } val requestPermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (isGranted) { scannerLauncher.launch(scanner.createDocumentScanIntent()) } else { AppToastHost.showToast( message = appContext.getString(R.string.grant_camera_permission_to_scan_document_scanner), icon = Icons.Outlined.CameraAlt ) } } return remember(context, scannerLauncher) { DocumentScannerImpl( context = context, scanner = scanner, scannerLauncher = scannerLauncher, requestPermissionLauncher = requestPermissionLauncher ) } } ================================================ FILE: core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.app.Activity import android.content.Context import android.content.Intent import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.t8rin.imagetoolbox.core.utils.appContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch internal object ReviewHandlerImpl : ReviewHandler() { private val scope = CoroutineScope(Dispatchers.IO) private val Context.dataStore by preferencesDataStore("saves_count") private val SAVES_COUNT = intPreferencesKey("SAVES_COUNT") private val NOT_SHOW_AGAIN = booleanPreferencesKey("NOT_SHOW_AGAIN") private val _showNotShowAgainButton = mutableStateOf(false) override val showNotShowAgainButton: Boolean by _showNotShowAgainButton override fun showReview(activity: Activity) { scope.launch { activity.dataStore.edit { if (it[NOT_SHOW_AGAIN] != true) { val saves = it[SAVES_COUNT] ?: 0 it[SAVES_COUNT] = saves + 1 _showNotShowAgainButton.value = saves >= 30 if (saves % 10 == 0) { activity.startActivity( Intent(activity, activity::class.java).apply { action = Intent.ACTION_BUG_REPORT flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } ) } } else { _showNotShowAgainButton.value = false } } } } override fun notShowReviewAgain() { scope.launch { appContext.dataStore.edit { it[NOT_SHOW_AGAIN] = true } } } } ================================================ FILE: core/ui/src/foss/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import androidx.compose.runtime.Composable @Composable internal fun UpdateSheetImpl( changelog: String, tag: String, visible: Boolean, onDismiss: () -> Unit ) { DefaultUpdateSheet( changelog = changelog, tag = tag, visible = visible, onDismiss = onDismiss ) } ================================================ FILE: core/ui/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Color.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.ui.theme import androidx.annotation.FloatRange import androidx.compose.animation.animateColorAsState import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalInspectionMode import androidx.core.graphics.ColorUtils import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState fun ColorScheme.outlineVariant( luminance: Float = 0.3f, onTopOf: Color = surfaceContainer ) = onSecondaryContainer .copy(alpha = luminance) .compositeOver(onTopOf) @Composable inline fun takeColorFromScheme( action: @Composable ColorScheme.(isNightMode: Boolean) -> Color ) = animateColorAsState( MaterialTheme.colorScheme.run { action( if (LocalInspectionMode.current) false else LocalSettingsState.current.isNightMode ) } ).value fun ColorScheme.suggestContainerColorBy(color: Color) = when (color) { onPrimary -> primary onSecondary -> secondary onTertiary -> tertiary onPrimaryContainer -> primaryContainer onSecondaryContainer -> secondaryContainer onTertiaryContainer -> tertiaryContainer onError -> error onErrorContainer -> errorContainer onSurfaceVariant -> surfaceVariant inverseOnSurface -> inverseSurface else -> surface } inline val ColorScheme.mixedContainer: Color @Composable get() = run { tertiaryContainer.blend( primaryContainer, 0.4f ) } inline val ColorScheme.primaryContainerFixed: Color @Composable get() = run { if (LocalSettingsState.current.isNightMode) primaryContainer.copy(alpha = 0.6f) else primary.copy(alpha = 0.6f) } inline val ColorScheme.onPrimaryContainerFixed: Color @Composable get() = run { if (LocalSettingsState.current.isNightMode) onPrimaryContainer else onPrimary }.blend(Color.White) inline val ColorScheme.secondaryContainerFixed: Color @Composable get() = run { if (LocalSettingsState.current.isNightMode) secondaryContainer.copy(alpha = 0.6f) else secondary.copy(alpha = 0.6f) } inline val ColorScheme.onSecondaryContainerFixed: Color @Composable get() = run { if (LocalSettingsState.current.isNightMode) onSecondaryContainer else onSecondary }.blend(Color.White) inline val ColorScheme.tertiaryContainerFixed: Color @Composable get() = run { if (LocalSettingsState.current.isNightMode) tertiaryContainer.copy(alpha = 0.6f) else tertiary.copy(alpha = 0.6f) } inline val ColorScheme.onTertiaryContainerFixed: Color @Composable get() = run { if (LocalSettingsState.current.isNightMode) onTertiaryContainer else onTertiary }.blend(Color.White) inline val ColorScheme.onMixedContainer: Color @Composable get() = run { onTertiaryContainer.blend( onPrimaryContainer, 0.4f ) } inline fun Color.takeIf(predicate: Boolean) = if (predicate) this else Color.Unspecified inline fun Color.takeUnless(predicate: Boolean) = takeIf(!predicate) fun Color.blend( color: Color, @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f ): Color = Color(ColorUtils.blendARGB(this.toArgb(), color.toArgb(), fraction)) @Composable fun Color.inverse( fraction: (Boolean) -> Float = { 0.5f }, darkMode: Boolean = LocalSettingsState.current.isNightMode, ): Color = if (darkMode) blend(Color.White, fraction(true)) else blend(Color.Black, fraction(false)) fun Color.inverseByLuma( fraction: (Boolean) -> Float = { 0.5f }, ): Color = if (luminance() < 0.3f) blend(Color.White, fraction(true)) else blend(Color.Black, fraction(false)) @Composable fun Color.inverse( fraction: (Boolean) -> Float = { 0.5f }, color: (Boolean) -> Color, darkMode: Boolean = LocalSettingsState.current.isNightMode, ): Color = if (darkMode) blend(color(true), fraction(true)) else blend(color(true), fraction(false)) fun Int.blend( color: Color, @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.2f ): Int = ColorUtils.blendARGB(this, color.toArgb(), fraction) @Composable fun Color.harmonizeWithPrimary( @FloatRange( from = 0.0, to = 1.0 ) fraction: Float = 0.2f ): Color = blend(MaterialTheme.colorScheme.primary, fraction) fun Int.toColor() = Color(this) inline val Green: Color @Composable get() = Color(0xFFBADB94).harmonizeWithPrimary(0.2f) inline val Red: Color @Composable get() = Color(0xFFE06565).harmonizeWithPrimary(0.2f) inline val Blue: Color @Composable get() = Color(0xFF0088CC).harmonizeWithPrimary(0.2f) inline val Black: Color @Composable get() = Color(0xFF142329).harmonizeWithPrimary(0.2f) inline val StrongBlack: Color @Composable get() = Color(0xFF141414).harmonizeWithPrimary(0.07f) inline val White: Color @Composable get() = Color(0xFFFFFFFF).harmonizeWithPrimary(0.07f) inline val BitcoinColor: Color @Composable get() = Color(0xFFF7931A).harmonizeWithPrimary(0.2f) inline val USDTColor: Color @Composable get() = Color(0xFF50AF95).harmonizeWithPrimary(0.2f) inline val TONSpaceColor: Color @Composable get() = Color(0xFF232328).harmonizeWithPrimary(0.1f) inline val TONColor: Color @Composable get() = Color(0xFF0098EA).harmonizeWithPrimary(0.2f) inline val BoostyColor: Color @Composable get() = Color(0xFFF15F2C).harmonizeWithPrimary(0.2f) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Motion.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused", "UNCHECKED_CAST") package com.t8rin.imagetoolbox.core.ui.theme import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.material3.MotionScheme import com.t8rin.imagetoolbox.core.domain.utils.cast import com.t8rin.imagetoolbox.core.ui.utils.animation.FancyTransitionEasing internal val CustomMotionScheme: MotionScheme = object : MotionScheme { val SpringDefaultSpatialDamping = 0.8f val SpringDefaultSpatialStiffness = 380.0f val SpringDefaultEffectsDamping = 1.0f val SpringDefaultEffectsStiffness = 1600.0f val SpringFastSpatialDamping = 0.6f val SpringFastSpatialStiffness = 800.0f val SpringFastEffectsDamping = 1.0f val SpringFastEffectsStiffness = 3800.0f val SpringSlowSpatialDamping = 0.8f val SpringSlowSpatialStiffness = 200.0f val SpringSlowEffectsDamping = 1.0f val SpringSlowEffectsStiffness = 800.0f private val defaultSpatialSpec = tween( durationMillis = 400, easing = FancyTransitionEasing ) private val fastSpatialSpec = spring( dampingRatio = SpringFastSpatialDamping, stiffness = SpringFastSpatialStiffness ) private val slowSpatialSpec = spring( dampingRatio = SpringSlowSpatialDamping, stiffness = SpringSlowSpatialStiffness ) private val defaultEffectsSpec = spring( dampingRatio = SpringDefaultEffectsDamping, stiffness = SpringDefaultEffectsStiffness ) private val fastEffectsSpec = tween( durationMillis = 300, easing = FancyTransitionEasing ) private val slowEffectsSpec = tween( durationMillis = 500, easing = FancyTransitionEasing ) override fun defaultSpatialSpec(): FiniteAnimationSpec = defaultSpatialSpec.cast() override fun fastSpatialSpec(): FiniteAnimationSpec = fastSpatialSpec.cast() override fun slowSpatialSpec(): FiniteAnimationSpec = slowSpatialSpec.cast() override fun defaultEffectsSpec(): FiniteAnimationSpec = defaultEffectsSpec.cast() override fun fastEffectsSpec(): FiniteAnimationSpec = fastEffectsSpec.cast() override fun slowEffectsSpec(): FiniteAnimationSpec = slowEffectsSpec.cast() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Theme.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.theme import android.annotation.SuppressLint import android.os.Build import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Shapes import androidx.compose.material3.Surface import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.DynamicTheme import com.t8rin.dynamic.theme.rememberDynamicThemeState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberAppColorTuple import com.t8rin.imagetoolbox.core.ui.utils.animation.FancyTransitionEasing import com.t8rin.imagetoolbox.core.ui.utils.helper.DeviceInfo import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape @SuppressLint("NewApi") @Composable fun ImageToolboxTheme( content: @Composable () -> Unit ) { val settingsState = LocalSettingsState.current val context = LocalContext.current DynamicTheme( typography = rememberTypography(settingsState.font), state = rememberDynamicThemeState(rememberAppColorTuple()), colorBlindType = settingsState.colorBlindType, defaultColorTuple = settingsState.appColorTuple, dynamicColor = settingsState.isDynamicColors, dynamicColorsOverride = { isNightMode -> if (Build.VERSION.SDK_INT == Build.VERSION_CODES.BAKLAVA && DeviceInfo.isPixel()) { val colors = if (isNightMode) { dynamicDarkColorScheme(context) } else { dynamicLightColorScheme(context) } ColorTuple( primary = colors.primary, secondary = colors.secondary, tertiary = colors.tertiary, surface = colors.surface ) } else null }, amoledMode = settingsState.isAmoledMode, isDarkTheme = settingsState.isNightMode, contrastLevel = settingsState.themeContrastLevel, style = settingsState.themeStyle, isInvertColors = settingsState.isInvertThemeColors, colorAnimationSpec = tween( durationMillis = 400, easing = FancyTransitionEasing ), content = { MaterialTheme( motionScheme = CustomMotionScheme, colorScheme = modifiedColorScheme(), shapes = modifiedShapes(), content = content ) } ) } @Composable fun ImageToolboxThemeSurface( content: @Composable BoxScope.() -> Unit ) { ImageToolboxTheme { Surface( modifier = Modifier.fillMaxSize(), content = { Box( modifier = Modifier.fillMaxSize(), content = content ) } ) } } @Composable internal fun modifiedShapes(): Shapes { val shapes = MaterialTheme.shapes val shapesType = LocalSettingsState.current.shapesType return remember(shapes, shapesType) { derivedStateOf { shapes.copy( extraSmall = AutoCornersShape( topStart = shapes.extraSmall.topStart, topEnd = shapes.extraSmall.topEnd, bottomEnd = shapes.extraSmall.bottomEnd, bottomStart = shapes.extraSmall.bottomStart, shapesType = shapesType ), small = AutoCornersShape( topStart = shapes.small.topStart, topEnd = shapes.small.topEnd, bottomEnd = shapes.small.bottomEnd, bottomStart = shapes.small.bottomStart, shapesType = shapesType ), medium = AutoCornersShape( topStart = shapes.medium.topStart, topEnd = shapes.medium.topEnd, bottomEnd = shapes.medium.bottomEnd, bottomStart = shapes.medium.bottomStart, shapesType = shapesType ), large = AutoCornersShape( topStart = shapes.large.topStart, topEnd = shapes.large.topEnd, bottomEnd = shapes.large.bottomEnd, bottomStart = shapes.large.bottomStart, shapesType = shapesType ), extraLarge = AutoCornersShape( topStart = shapes.extraLarge.topStart, topEnd = shapes.extraLarge.topEnd, bottomEnd = shapes.extraLarge.bottomEnd, bottomStart = shapes.extraLarge.bottomStart, shapesType = shapesType ), largeIncreased = AutoCornersShape( topStart = shapes.largeIncreased.topStart, topEnd = shapes.largeIncreased.topEnd, bottomEnd = shapes.largeIncreased.bottomEnd, bottomStart = shapes.largeIncreased.bottomStart, shapesType = shapesType ), extraLargeIncreased = AutoCornersShape( topStart = shapes.extraLargeIncreased.topStart, topEnd = shapes.extraLargeIncreased.topEnd, bottomEnd = shapes.extraLargeIncreased.bottomEnd, bottomStart = shapes.extraLargeIncreased.bottomStart, shapesType = shapesType ), extraExtraLarge = AutoCornersShape( topStart = shapes.extraExtraLarge.topStart, topEnd = shapes.extraExtraLarge.topEnd, bottomEnd = shapes.extraExtraLarge.bottomEnd, bottomStart = shapes.extraExtraLarge.bottomStart, shapesType = shapesType ) ) } }.value } @Composable internal fun modifiedColorScheme(): ColorScheme { val scheme = MaterialTheme.colorScheme return remember(scheme) { derivedStateOf { scheme.copy( errorContainer = scheme.errorContainer.blend( color = scheme.primary, fraction = 0.15f ) ) } }.value } const val DisabledAlpha = 0.38f ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/ThemePreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.theme import android.graphics.BlurMaskFilter import android.graphics.Canvas import android.graphics.LinearGradient import android.graphics.Paint import android.graphics.RadialGradient import android.graphics.Shader import androidx.compose.animation.core.snap import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.core.graphics.createBitmap import coil3.asImage import coil3.compose.AsyncImagePreviewHandler import coil3.compose.LocalAsyncImagePreviewHandler import coil3.request.ImageRequest import coil3.size.pxOrElse import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.DynamicTheme import com.t8rin.dynamic.theme.rememberDynamicThemeState import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.settings.domain.SimpleSettingsInteractor import com.t8rin.imagetoolbox.core.settings.domain.model.OneTimeSaveLocation import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.presentation.model.defaultColorTuple import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getStringLocalized import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberScreenSize import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.initAppContext import java.util.Locale import kotlin.math.max @Composable fun ImageToolboxThemeForPreview( isDarkTheme: Boolean, keyColor: Color? = defaultColorTuple.primary, shapesType: ShapeType = ShapeType.Rounded(), mapSettings: (SettingsState) -> SettingsState = { it }, content: @Composable () -> Unit ) { LocalContext.current.applicationContext.initAppContext() FakeLoader( ColorTuple(keyColor ?: Color.Transparent) ) { DynamicTheme( state = rememberDynamicThemeState( initialColorTuple = ColorTuple(keyColor ?: Color.Transparent) ), dynamicColor = keyColor == null, isDarkTheme = isDarkTheme, defaultColorTuple = ColorTuple(keyColor ?: Color.Transparent), colorAnimationSpec = snap(), content = { CompositionLocalProvider( LocalSettingsState provides mapSettings(SettingsState.Default).toUiState().copy( shapesType = shapesType ), LocalSimpleSettingsInteractor provides FakeSettings, LocalResourceManager provides FakeRes, LocalScreenSize provides rememberScreenSize() ) { MaterialTheme( motionScheme = CustomMotionScheme, colorScheme = modifiedColorScheme(), shapes = modifiedShapes(), content = { Surface { content() } } ) } } ) } } @Composable private fun FakeLoader( tuple: ColorTuple, content: @Composable () -> Unit ) { DynamicTheme( state = rememberDynamicThemeState(tuple), isDarkTheme = false, defaultColorTuple = tuple ) { val colorScheme = MaterialTheme.colorScheme val fakeLoader = remember(colorScheme) { AsyncImagePreviewHandler( image = { request: ImageRequest -> val size = request.sizeResolver.size() val width = size.width.pxOrElse { 800 }.coerceAtLeast(600) - 200 val height = size.height.pxOrElse { 800 }.coerceAtLeast(600) val bitmap = createBitmap(width, height) val canvas = Canvas(bitmap) val base = LinearGradient( 0f, 0f, width.toFloat(), height.toFloat(), intArrayOf( colorScheme.primary.toArgb(), colorScheme.tertiary.toArgb(), colorScheme.secondary.toArgb(), colorScheme.error.toArgb(), ), floatArrayOf(0f, 0.3f, 0.7f, 1f), Shader.TileMode.CLAMP ) canvas.drawRect( 0f, 0f, width.toFloat(), height.toFloat(), Paint(Paint.ANTI_ALIAS_FLAG).apply { shader = base } ) val blurPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = colorScheme.primaryContainer.toArgb() maskFilter = BlurMaskFilter(height * 0.08f, BlurMaskFilter.Blur.NORMAL) } canvas.drawOval( width * 0.1f, height * 0.2f, width * 0.7f, height * 0.8f, blurPaint ) canvas.drawOval( width * 0.5f, height * 0.1f, width * 0.95f, height * 0.6f, blurPaint ) val noisePaint = Paint().apply { color = colorScheme.tertiaryContainer.copy(0.5f).toArgb() } repeat((width * height) / 6) { val x = (Math.random() * width).toFloat() val y = (Math.random() * height).toFloat() canvas.drawPoint(x, y, noisePaint) } val vignette = RadialGradient( width / 2f, height / 2f, max(width, height) * 0.9f, intArrayOf( Color.Transparent.toArgb(), colorScheme.tertiaryContainer.copy(0.5f).toArgb(), Color.Transparent.toArgb(), ), floatArrayOf(0.6f, 0.85f, 1f), Shader.TileMode.CLAMP ) canvas.drawRect( 0f, 0f, width.toFloat(), height.toFloat(), Paint().apply { shader = vignette } ) bitmap.asImage() } ) } CompositionLocalProvider( LocalAsyncImagePreviewHandler provides fakeLoader ) { content() } } } private val FakeSettings = object : SimpleSettingsInteractor { override suspend fun toggleMagnifierEnabled() = Unit override suspend fun setOneTimeSaveLocations(value: List) = Unit override suspend fun toggleRecentColor( color: ColorModel, forceExclude: Boolean ) = Unit override suspend fun toggleFavoriteColor( color: ColorModel, forceExclude: Boolean ) = Unit override fun isInstalledFromPlayStore(): Boolean = false override suspend fun toggleSettingsGroupVisibility( key: Int, value: Boolean ) = Unit override suspend fun clearRecentColors() = Unit override suspend fun updateFavoriteColors(colors: List) = Unit override suspend fun setBackgroundColorForNoAlphaFormats(color: ColorModel) = Unit override suspend fun toggleCustomAsciiGradient(gradient: String) = Unit override suspend fun toggleOverwriteFiles() = Unit override suspend fun setSpotHealMode(mode: Int) = Unit override suspend fun setBorderWidth(width: Float) = Unit } private val FakeRes = object : ResourceManager { override fun getString(resId: Int): String = appContext.getString(resId) override fun getString( resId: Int, vararg formatArgs: Any ): String = appContext.getString(resId, formatArgs) override fun getStringLocalized( resId: Int, language: String ): String = appContext.getStringLocalized(resId, Locale.forLanguageTag(language)) override fun getStringLocalized( resId: Int, language: String, vararg formatArgs: Any ): String = appContext.getStringLocalized(resId, Locale.forLanguageTag(language)) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/theme/Type.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.theme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Typography import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontSynthesis import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily @Composable fun rememberTypography( fontRes: UiFontFamily ): Typography = remember(fontRes) { derivedStateOf { Typography( displayLarge = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Normal, fontSize = 57.sp, lineHeight = 64.sp, letterSpacing = (-0.25).sp, fontSynthesis = FontSynthesis.All ), displayMedium = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Normal, fontSize = 45.sp, lineHeight = 52.sp, letterSpacing = 0.sp, fontSynthesis = FontSynthesis.All ), displaySmall = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Normal, fontSize = 36.sp, lineHeight = 44.sp, letterSpacing = 0.sp, fontSynthesis = FontSynthesis.All ), headlineLarge = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Normal, fontSize = 32.sp, lineHeight = 40.sp, letterSpacing = 0.sp, fontSynthesis = FontSynthesis.All ), headlineMedium = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.SemiBold, fontSize = 28.sp, lineHeight = 36.sp, letterSpacing = 0.sp, fontSynthesis = FontSynthesis.All ), headlineSmall = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.SemiBold, fontSize = 24.sp, lineHeight = 32.sp, letterSpacing = 0.sp, textAlign = TextAlign.Center, fontSynthesis = FontSynthesis.All ), titleLarge = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.SemiBold, fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.sp, fontSynthesis = FontSynthesis.All ), titleMedium = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Bold, fontSize = 18.sp, lineHeight = 24.sp, letterSpacing = 0.1.sp, fontSynthesis = FontSynthesis.All ), titleSmall = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.1.sp, fontSynthesis = FontSynthesis.All ), bodyLarge = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.5.sp, fontSynthesis = FontSynthesis.All ), bodyMedium = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.25.sp, textAlign = TextAlign.Center, fontSynthesis = FontSynthesis.All ), bodySmall = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.4.sp, fontSynthesis = FontSynthesis.All ), labelLarge = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.1.sp, fontSynthesis = FontSynthesis.All ), labelMedium = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.5.sp, fontSynthesis = FontSynthesis.All ), labelSmall = TextStyle( fontFamily = fontRes.fontFamily, fontWeight = FontWeight.SemiBold, fontSize = 10.sp, lineHeight = 16.sp, letterSpacing = 0.sp, fontSynthesis = FontSynthesis.All ), ) } }.value @Composable fun ProvideTypography( fontRes: UiFontFamily, content: @Composable () -> Unit ) { MaterialTheme( typography = rememberTypography(fontRes), content = content ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/transformation/ImageInfoTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.transformation import android.graphics.Bitmap import coil3.size.Size import coil3.size.pxOrElse import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.ui.utils.helper.asCoil import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlin.math.roundToInt import coil3.transform.Transformation as CoilTransformation class ImageInfoTransformation @AssistedInject internal constructor( @Assisted private val imageInfo: ImageInfo, @Assisted private val preset: Preset, @Assisted private val transformations: List>, private val imageTransformer: ImageTransformer, private val imageScaler: ImageScaler, private val imagePreviewCreator: ImagePreviewCreator, private val shareProvider: ImageShareProvider ) : CoilTransformation(), Transformation { override val cacheKey: String get() = Triple(imageInfo, preset, transformations).hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = transform(input, size.asCoil()) override suspend fun transform( input: Bitmap, size: Size ): Bitmap { val transformedInput = imageScaler.scaleImage( image = input, width = size.width.pxOrElse { imageInfo.width }, height = size.height.pxOrElse { imageInfo.height }, resizeType = ResizeType.Flexible ) val originalUri = shareProvider.cacheImage( image = input, imageInfo = ImageInfo( width = input.width, height = input.height ) ) val presetValue = preset.value() val presetInfo = imageTransformer.applyPresetBy( image = transformedInput, preset = preset, currentInfo = imageInfo.copy( originalUri = originalUri ) ) val info = presetInfo.copy( originalUri = originalUri, resizeType = presetInfo.resizeType.withOriginalSizeIfCrop( if (presetValue != null) { if (presetValue > 100) { IntegerSize( (transformedInput.width.toFloat() / (presetValue / 100f)).roundToInt(), (transformedInput.height.toFloat() / (presetValue / 100f)).roundToInt() ) } else { IntegerSize( transformedInput.width, transformedInput.height ) } } else { IntegerSize(input.width, input.height) } ) ) return imagePreviewCreator.createPreview( image = transformedInput, imageInfo = info, transformations = transformations, onGetByteCount = {} ) ?: input } @AssistedFactory interface Factory { operator fun invoke( imageInfo: ImageInfo, preset: Preset = Preset.Original, transformations: List> = emptyList(), ): ImageInfoTransformation } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/BaseComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.saving.track import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.helper.SaveResultHandler import com.t8rin.imagetoolbox.core.ui.utils.helper.SaveResultHandlerImpl import com.t8rin.imagetoolbox.core.ui.utils.navigation.coroutineScope import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.logger.makeLog import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.launch as internalLaunch @Stable @Immutable abstract class BaseComponent( private val dispatchersHolder: DispatchersHolder, private val componentContext: ComponentContext ) : ComponentContext by componentContext, DispatchersHolder by dispatchersHolder, SaveResultHandler by SaveResultHandlerImpl { val componentScope = coroutineScope protected open val _isImageLoading: MutableState = mutableStateOf(false) open val isImageLoading: Boolean by _isImageLoading private var imageCalculationJob: Job? by smartJob { _isImageLoading.update { false } } inline fun debounce( time: Long = 150, crossinline block: suspend () -> Unit ) { componentScope.launch { delay(time) block() } } protected open val _haveChanges: MutableState = mutableStateOf(false) open val haveChanges: Boolean by _haveChanges fun trackProgress( action: suspend KeepAliveService.() -> Unit ): Job = componentScope.launch { keepAliveService.track( onFailure = { it.makeLog("CRITICAL") }, action = action ) } protected fun registerSave() { _haveChanges.update { false } } protected fun registerChangesCleared() { _haveChanges.update { false } } protected fun registerChanges() { _haveChanges.update { true } } private var resetJob by smartJob() protected open fun debouncedImageCalculation( onFinish: suspend () -> Unit = {}, delay: Long = 600L, action: suspend () -> Unit ) { imageCalculationJob = componentScope.launch( CoroutineExceptionHandler { _, t -> t.makeLog() _isImageLoading.update { false } } + defaultDispatcher ) { _isImageLoading.update { true } delay(delay) ensureActive() action() ensureActive() _isImageLoading.update { false } onFinish() } } private fun cancelResetState() { resetJob?.cancel() resetJob = null } private fun resetStateDelayed() { resetJob = componentScope.launch { delay(1500) resetState() } } @Composable fun AttachLifecycle() { DisposableEffect(Unit) { cancelResetState() onDispose { resetStateDelayed() } } } fun cancelImageLoading() { _isImageLoading.update { false } imageCalculationJob?.cancel() imageCalculationJob = null } open fun resetState(): Unit = throw IllegalAccessException("Cannot reset state of ${this::class.simpleName}") fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job = internalLaunch(context, start) { delay(50L) block() } companion object { internal var keepAliveService: KeepAliveService = object : KeepAliveService { override fun updateOrStart( title: String, description: String, progress: Float ) = Unit override fun stop(removeNotification: Boolean) = Unit } fun inject(keepAliveService: KeepAliveService) { this.keepAliveService = keepAliveService } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeActivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils import android.content.Context import android.content.Intent import android.content.res.Configuration import android.os.Bundle import android.view.WindowManager import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.toArgb import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.lifecycleScope import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColorsOptions import com.t8rin.imagetoolbox.core.di.entryPoint import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.domain.remote.AnalyticsManager import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FileController.Companion.toMetadataProvider import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.settings.di.SettingsStateEntryPoint import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.domain.toSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.settings.presentation.model.asColorTuple import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.utils.ComposeApplication.Companion.wrap import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.adjustFontSize import com.t8rin.imagetoolbox.core.ui.utils.helper.ReviewHandler import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalKeepAliveService import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalMetadataProvider import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.utils.provider.setContentWithWindowSizeClass import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.logger.makeLog import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.plus import kotlinx.coroutines.runBlocking import javax.inject.Inject @AndroidEntryPoint abstract class ComposeActivity : AppCompatActivity() { @Inject lateinit var analyticsManager: AnalyticsManager @Inject lateinit var dispatchersHolder: DispatchersHolder @Inject lateinit var fileController: FileController @Inject lateinit var keepAliveService: KeepAliveService @Inject lateinit var resourceManager: ResourceManager private lateinit var settingsManager: SettingsManager private val activityScope: CoroutineScope get() = lifecycleScope + dispatchersHolder.defaultDispatcher private val windowInsetsController: WindowInsetsControllerCompat? get() = window?.let { WindowCompat.getInsetsController(it, it.decorView) } private val _settingsState = mutableStateOf(SettingsState.Default) private val settingsState: SettingsState by _settingsState @Composable abstract fun Content() open fun onFirstLaunch() = handleIntent(intent) open fun handleIntent(intent: Intent) = Unit override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleIntent(intent) } override fun attachBaseContext(newBase: Context) { newBase.entryPoint { this@ComposeActivity.settingsManager = this.settingsManager _settingsState.update { runBlocking { settingsManager.getSettingsState() } } handleSystemBarsBehavior() handleSecureMode() } val newOverride = Configuration(newBase.resources?.configuration) settingsState.fontScale?.let { newOverride.fontScale = it } applyOverrideConfiguration(newOverride) super.attachBaseContext(newBase) } override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) enableEdgeToEdge() wrap(application)?.runSetup() observeReview() settingsManager .settingsState .onEach { state -> _settingsState.update { state } handleSystemBarsBehavior() handleSecureMode() updateFirebaseParams() applyDynamicColors() } .launchIn(activityScope) adjustFontSize(settingsState.fontScale) updateFirebaseParams() handleSystemBarsBehavior() handleSecureMode() if (savedInstanceState == null) onFirstLaunch() setContentWithWindowSizeClass { CompositionLocalProvider( LocalSimpleSettingsInteractor provides settingsManager.toSimpleSettingsInteractor(), LocalMetadataProvider provides fileController.toMetadataProvider(), LocalKeepAliveService provides keepAliveService, LocalResourceManager provides resourceManager, content = ::Content ) } } fun applyDynamicColors() { val colorTuple = settingsState.appColorTuple.asColorTuple() DynamicColors.applyToActivityIfAvailable( this@ComposeActivity, DynamicColorsOptions.Builder() .setContentBasedSource(colorTuple.primary.toArgb()) .build() ) } private fun updateFirebaseParams() = analyticsManager.apply { updateAllowCollectCrashlytics(settingsState.allowCollectCrashlytics) updateAnalyticsCollectionEnabled(settingsState.allowCollectCrashlytics) } private var recreationJob: Job? by smartJob() override fun recreate() { recreationJob = activityScope.launch { delay(200L) runOnUiThread { super.recreate() } } } override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) handleSystemBarsBehavior() handleSecureMode() } override fun onAttachedToWindow() { super.onAttachedToWindow() handleSecureMode() } private fun handleSystemBarsBehavior() = runOnUiThread { windowInsetsController?.apply { when (settingsState.systemBarsVisibility) { SystemBarsVisibility.Auto -> { val orientation = resources.configuration.orientation show(STATUS_BARS) if (orientation == Configuration.ORIENTATION_LANDSCAPE) { hide(NAV_BARS) } else { show(NAV_BARS) } } SystemBarsVisibility.HideAll -> { hide(SYSTEM_BARS) } SystemBarsVisibility.ShowAll -> { show(SYSTEM_BARS) } SystemBarsVisibility.HideNavigationBar -> { show(STATUS_BARS) hide(NAV_BARS) } SystemBarsVisibility.HideStatusBar -> { show(NAV_BARS) hide(STATUS_BARS) } } systemBarsBehavior = if (settingsState.isSystemBarsVisibleBySwipe) { WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } else WindowInsetsControllerCompat.BEHAVIOR_DEFAULT } } private fun handleSecureMode() = runOnUiThread { if (settingsState.isSecureMode) { window?.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) } else { window?.clearFlags( WindowManager.LayoutParams.FLAG_SECURE ) } } private fun observeReview() { lifecycleScope.launch { ReviewHandler.current.apply { reviewRequests.collect { makeLog("collect reviewRequests") showReview(this@ComposeActivity) } } } } companion object { private val NAV_BARS = WindowInsetsCompat.Type.navigationBars() private val SYSTEM_BARS = WindowInsetsCompat.Type.systemBars() private val STATUS_BARS = WindowInsetsCompat.Type.statusBars() } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/ComposeApplication.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils import android.app.Application abstract class ComposeApplication : Application() { abstract fun runSetup() companion object { fun wrap(application: Application): ComposeApplication? = application as? ComposeApplication } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animate.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.animation import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.VisibilityThreshold import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.runtime.Composable import androidx.compose.ui.unit.Dp @Composable fun Float.animate( animationSpec: AnimationSpec = spring(), visibilityThreshold: Float = 0.01f, label: String = "FloatAnimation", finishedListener: ((Float) -> Unit)? = null ): Float = animateFloatAsState( targetValue = this, animationSpec = animationSpec, visibilityThreshold = visibilityThreshold, label = label, finishedListener = finishedListener ).value @Composable fun Dp.animate( animationSpec: AnimationSpec = spring(visibilityThreshold = Dp.VisibilityThreshold), label: String = "FloatAnimation", finishedListener: ((Dp) -> Unit)? = null ): Dp = animateDpAsState( targetValue = this, animationSpec = animationSpec, label = label, finishedListener = finishedListener ).value ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Animations.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.ui.utils.animation import androidx.compose.animation.ContentTransform import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation import com.arkivanov.decompose.extensions.compose.stack.animation.fade import com.arkivanov.decompose.extensions.compose.stack.animation.plus import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.androidPredictiveBackAnimatableV1 import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.predictiveBackAnimation import com.arkivanov.decompose.extensions.compose.stack.animation.scale import com.arkivanov.decompose.extensions.compose.stack.animation.slide import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation import com.arkivanov.essenty.backhandler.BackHandler import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen fun fancySlideTransition( isForward: Boolean, screenWidthPx: Int, duration: Int = 600 ): ContentTransform = if (isForward) { slideInHorizontally( animationSpec = tween(duration, easing = FancyTransitionEasing), initialOffsetX = { screenWidthPx }) + fadeIn( tween(300, 100) ) togetherWith slideOutHorizontally( animationSpec = tween(duration, easing = FancyTransitionEasing), targetOffsetX = { -screenWidthPx }) + fadeOut( tween(300, 100) ) } else { slideInHorizontally( animationSpec = tween(600, easing = FancyTransitionEasing), initialOffsetX = { -screenWidthPx }) + fadeIn( tween(300, 100) ) togetherWith slideOutHorizontally( animationSpec = tween(600, easing = FancyTransitionEasing), targetOffsetX = { screenWidthPx }) + fadeOut( tween(300, 100) ) } fun toolboxPredictiveBackAnimation( backHandler: BackHandler, onBack: () -> Unit ): StackAnimation? = predictiveBackAnimation( backHandler = backHandler, onBack = onBack, fallbackAnimation = stackAnimation( fade( tween( durationMillis = 300, easing = AlphaEasing ) ) + slide( tween( durationMillis = 400, easing = FancyTransitionEasing ) ) + scale( tween( durationMillis = 500, easing = PointToPointEasing ) ) ), selector = { backEvent, _, _ -> androidPredictiveBackAnimatableV1(backEvent) }, ) inline fun springySpec() = spring( dampingRatio = 0.35f, stiffness = Spring.StiffnessLow ) inline fun lessSpringySpec() = spring( dampingRatio = 0.4f, stiffness = Spring.StiffnessLow ) @Composable fun animateFloatingRangeAsState( range: ClosedFloatingPointRange, animationSpec: AnimationSpec = spring() ): State> { val start = animateFloatAsState( targetValue = range.start, animationSpec = animationSpec ) val end = animateFloatAsState( targetValue = range.endInclusive, animationSpec = animationSpec ) return remember(start, end) { derivedStateOf { start.value..end.value } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/CombinedMutableInteractionSource.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.animation import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.merge @Stable @Immutable class CombinedMutableInteractionSource( private val sources: List ) : MutableInteractionSource { constructor(vararg sources: MutableInteractionSource) : this(sources.toList()) override val interactions: Flow = merge(*sources.map { it.interactions }.toTypedArray()) override suspend fun emit(interaction: Interaction) { sources.forEach { it.emit(interaction) } } override fun tryEmit(interaction: Interaction): Boolean { return sources.all { it.tryEmit(interaction) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/animation/Easing.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.animation import androidx.compose.animation.core.CubicBezierEasing val FancyTransitionEasing = CubicBezierEasing(0.48f, 0.19f, 0.05f, 1.03f) val AlphaEasing = CubicBezierEasing(0.4f, 0.4f, 0.17f, 0.9f) val PointToPointEasing = CubicBezierEasing(0.55f, 0.55f, 0f, 1f) val FastInvokeEasing = CubicBezierEasing(0f, 0f, 0f, 1f) val OverslideEasing = CubicBezierEasing(0.5f, 0.5f, 1.0f, 0.25f) val RotationEasing = CubicBezierEasing(0.46f, 0.03f, 0.52f, 0.96f) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/Capturable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.capturable import android.os.Build import androidx.compose.ui.Modifier import com.t8rin.imagetoolbox.core.ui.utils.capturable.impl.capturableNew import com.t8rin.imagetoolbox.core.ui.utils.capturable.impl.capturableOld /** * Adds a capture-ability on the Composable which can draw Bitmap from the Composable component. * * Example usage: * * ``` * val captureController = rememberCaptureController() * val uiScope = rememberCoroutineScope() * * // The content to be captured in to Bitmap * Column( * modifier = Modifier.capturable(captureController), * ) { * // Composable content * } * * Button(onClick = { * // Capture content * val bitmapAsync = captureController.captureAsync() * try { * val bitmap = bitmapAsync.await() * // Do something with `bitmap`. * } catch (error: Throwable) { * // Error occurred, do something. * } * }) { ... } * ``` * * @param controller A [com.t8rin.imagetoolbox.core.ui.utils.capturable.CaptureController] which gives control to capture the Composable content. */ fun Modifier.capturable(controller: CaptureController): Modifier { val sdk = Build.VERSION.SDK_INT return when { sdk > Build.VERSION_CODES.N -> capturableNew(controller) else -> capturableOld(controller) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/CaptureController.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.capturable import android.graphics.Bitmap import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.rememberGraphicsLayer import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow /** * Controller for capturing [Composable] content. * @see capturable for implementation details. */ class CaptureController(internal val graphicsLayer: GraphicsLayer) { /** * Medium for providing capture requests * * Earlier, we were using `MutableSharedFlow` here but it was incapable of serving requests * which are created as soon as composition starts because this flow was collected later * underneath. So Channel with UNLIMITED capacity just works here and solves the issue as well. * See issue: https://github.com/PatilShreyas/Capturable/issues/202 */ private val _captureRequests = Channel(capacity = Channel.UNLIMITED) internal val captureRequests = _captureRequests.receiveAsFlow() /** * Creates and requests for a Bitmap capture and returns * an [ImageBitmap] asynchronously. * * This method is safe to be called from the "main" thread directly. * * Make sure to call this method as a part of callback function and not as a part of the * [Composable] function itself. * */ fun captureAsync(): Deferred { val deferredImageBitmap = CompletableDeferred() return deferredImageBitmap.also { _captureRequests.trySend(CaptureRequest(imageBitmapDeferred = it)) } } suspend fun bitmap(): Bitmap = captureAsync().await().asAndroidBitmap() /** * Holds information of capture request */ internal class CaptureRequest(val imageBitmapDeferred: CompletableDeferred) } /** * Creates [CaptureController] and remembers it. */ @Composable fun rememberCaptureController(): CaptureController { val graphicsLayer = rememberGraphicsLayer() return remember(graphicsLayer) { CaptureController(graphicsLayer) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableNew.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.capturable.impl import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.CacheDrawModifierNode import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.InspectorInfo import com.t8rin.imagetoolbox.core.ui.utils.capturable.CaptureController import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch /** * Adds a capture-ability on the Composable which can draw Bitmap from the Composable component. * * Example usage: * * ``` * val captureController = rememberCaptureController() * val uiScope = rememberCoroutineScope() * * // The content to be captured in to Bitmap * Column( * modifier = Modifier.capturable(captureController), * ) { * // Composable content * } * * Button(onClick = { * // Capture content * val bitmapAsync = captureController.captureAsync() * try { * val bitmap = bitmapAsync.await() * // Do something with `bitmap`. * } catch (error: Throwable) { * // Error occurred, do something. * } * }) { ... } * ``` * * @param controller A [com.t8rin.imagetoolbox.core.ui.utils.capturable.CaptureController] which gives control to capture the Composable content. */ @ExperimentalComposeUiApi fun Modifier.capturableNew(controller: CaptureController): Modifier { return this then CapturableModifierNodeElement(controller) } /** * Modifier implementation of Capturable */ private data class CapturableModifierNodeElement( private val controller: CaptureController ) : ModifierNodeElement() { override fun create(): CapturableModifierNode { return CapturableModifierNode(controller) } override fun update(node: CapturableModifierNode) { node.updateController(controller) } override fun InspectorInfo.inspectableProperties() { name = "capturable" properties["controller"] = controller } } /** * Capturable Modifier node which delegates task to the [CacheDrawModifierNode] for drawing in * runtime when content capture is requested * [CacheDrawModifierNode] is used for drawing Composable UI from Canvas to the Picture and then * this node converts picture into a Bitmap. * * @param controller A [CaptureController] which gives control to capture the Composable content. */ @Suppress("unused") private class CapturableModifierNode( controller: CaptureController ) : Modifier.Node(), DrawModifierNode { /** * State to hold the current [CaptureController] instance. * This can be updated via [updateController] method. */ private val currentController = MutableStateFlow(controller) private val currentGraphicsLayer get() = currentController.value.graphicsLayer override fun onAttach() { super.onAttach() coroutineScope.launch { observeCaptureRequestsAndServe() } } /** * Sets new [CaptureController] */ fun updateController(newController: CaptureController) { currentController.value = newController } @OptIn(ExperimentalCoroutinesApi::class) private suspend fun observeCaptureRequestsAndServe() { currentController .flatMapLatest { it.captureRequests } .collect { request -> val completable = request.imageBitmapDeferred try { completable.complete(currentGraphicsLayer.toImageBitmap()) } catch (error: Throwable) { completable.completeExceptionally(error) } } } // Ref: // https://developer.android.com/develop/ui/compose/graphics/draw/modifiers#composable-to-bitmap override fun ContentDrawScope.draw() { currentGraphicsLayer.record { // draw the contents of the composable into the graphics layer this@draw.drawContent() } // draw the graphics layer on the visible canvas drawLayer(currentGraphicsLayer) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/capturable/impl/CapturableOld.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.capturable.impl import android.annotation.SuppressLint import android.graphics.Bitmap import android.graphics.Color import android.graphics.Picture import android.os.Build import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.CacheDrawModifierNode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.draw import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.node.DelegatableNode import androidx.compose.ui.node.DelegatingNode import androidx.compose.ui.node.ModifierNodeElement import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.ui.utils.capturable.CaptureController import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * Adds a capture-ability on the Composable which can draw Bitmap from the Composable component. * * Example usage: * * ``` * val captureController = rememberCaptureController() * val uiScope = rememberCoroutineScope() * * // The content to be captured in to Bitmap * Column( * modifier = Modifier.capturable(captureController), * ) { * // Composable content * } * * Button(onClick = { * // Capture content * val bitmapAsync = captureController.captureAsync() * try { * val bitmap = bitmapAsync.await() * // Do something with `bitmap`. * } catch (error: Throwable) { * // Error occurred, do something. * } * }) { ... } * ``` * * @param controller A [com.t8rin.imagetoolbox.core.ui.utils.capturable.CaptureController] which gives control to capture the Composable content. */ @ExperimentalComposeUiApi fun Modifier.capturableOld(controller: CaptureController): Modifier { return this then CapturableModifierNodeElementOld(controller) } /** * Modifier implementation of Capturable */ @SuppressLint("ModifierNodeInspectableProperties") private data class CapturableModifierNodeElementOld( private val controller: CaptureController ) : ModifierNodeElement() { override fun create(): CapturableModifierNodeOld { return CapturableModifierNodeOld(controller) } override fun update(node: CapturableModifierNodeOld) { node.updateController(controller) } } /** * Capturable Modifier node which delegates task to the [CacheDrawModifierNode] for drawing in * runtime when content capture is requested * [CacheDrawModifierNode] is used for drawing Composable UI from Canvas to the Picture and then * this node converts picture into a Bitmap. * * @param controller A [CaptureController] which gives control to capture the Composable content. */ @Suppress("unused") private class CapturableModifierNodeOld( controller: CaptureController ) : DelegatingNode(), DelegatableNode { /** * State to hold the current [CaptureController] instance. * This can be updated via [updateController] method. */ private val currentController = MutableStateFlow(controller) override fun onAttach() { super.onAttach() coroutineScope.launch { observeCaptureRequestsAndServe() } } /** * Sets new [CaptureController] */ fun updateController(newController: CaptureController) { currentController.value = newController } @OptIn(ExperimentalCoroutinesApi::class) private suspend fun observeCaptureRequestsAndServe() { currentController .flatMapLatest { it.captureRequests } .collect { request -> val completable = request.imageBitmapDeferred try { val picture = getCurrentContentAsPicture() val bitmap = withContext(Dispatchers.Default) { picture.asBitmap(Bitmap.Config.ARGB_8888) } completable.complete(bitmap.asImageBitmap()) } catch (error: Throwable) { completable.completeExceptionally(error) } } } private suspend fun getCurrentContentAsPicture(): Picture { return Picture().apply { drawCanvasIntoPicture(this) } } /** * Draws the current content into the provided [picture] */ private suspend fun drawCanvasIntoPicture(picture: Picture) { // CompletableDeferred to wait until picture is drawn from the Canvas content val pictureDrawn = CompletableDeferred() // Delegate the task to draw the content into the picture val delegatedNode = delegate( CacheDrawModifierNode { val width = this.size.width.toInt() val height = this.size.height.toInt() onDrawWithContent { val pictureCanvas = Canvas(picture.beginRecording(width, height)) draw(this, this.layoutDirection, pictureCanvas, this.size) { this@onDrawWithContent.drawContent() } picture.endRecording() drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) // Notify that picture is drawn pictureDrawn.complete(Unit) } } } ) // Wait until picture is drawn pictureDrawn.await() // As task is accomplished, remove the delegation of node to prevent draw operations on UI // updates or recompositions. undelegate(delegatedNode) } } /** * Creates a [Bitmap] from a [Picture] with provided [config] */ private fun Picture.asBitmap(config: Bitmap.Config): Bitmap { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { Bitmap.createBitmap(this@asBitmap) } else { val bitmap = createBitmap(this@asBitmap.width, this@asBitmap.height, config) val canvas = android.graphics.Canvas(bitmap) canvas.drawColor(Color.WHITE) canvas.drawPicture(this@asBitmap) bitmap } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/ConfettiHostState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.confetti import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.settings.domain.model.ColorHarmonizer import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration import com.t8rin.imagetoolbox.core.ui.widget.other.ToastHost import com.t8rin.imagetoolbox.core.ui.widget.other.ToastHostState import nl.dionsegijn.konfetti.compose.KonfettiView import nl.dionsegijn.konfetti.core.Party @Stable @Immutable class ConfettiHostState : ToastHostState() { suspend fun showConfetti( duration: ToastDuration = ToastDuration(4500L) ) = showToast(message = "", duration = duration) } @Composable fun ConfettiHost( hostState: ConfettiHostState, particles: @Composable (harmonizer: Color) -> List ) { ToastHost( hostState = hostState, transitionSpec = { fadeIn() togetherWith fadeOut() }, toast = { val settingsState = LocalSettingsState.current val colorScheme = MaterialTheme.colorScheme val confettiHarmonizationLevel = settingsState.confettiHarmonizationLevel val harmonizationColor = when ( val harmonizer = settingsState.confettiColorHarmonizer ) { is ColorHarmonizer.Custom -> Color(harmonizer.color) ColorHarmonizer.Primary -> colorScheme.primary ColorHarmonizer.Secondary -> colorScheme.secondary ColorHarmonizer.Tertiary -> colorScheme.tertiary } KonfettiView( modifier = Modifier.fillMaxSize(), parties = particles(harmonizationColor.copy(confettiHarmonizationLevel)) ) }, enableSwipes = false ) } @Composable fun ConfettiHost() { val settingsState = LocalSettingsState.current AnimatedVisibility(settingsState.isConfettiEnabled) { ConfettiHost( hostState = AppToastHost.confettiState, particles = { harmonizer -> val particlesType by remember(settingsState.confettiType) { derivedStateOf { Particles.Type.entries.first { it.ordinal == settingsState.confettiType } } } remember { Particles( harmonizer = harmonizer ).build(particlesType) } } ) } if (!settingsState.isConfettiEnabled) { SideEffect { AppToastHost.confettiState.currentToastData?.dismiss() } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/confetti/Particles.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.confetti import androidx.appcompat.content.res.AppCompatResources import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.utils.appContext import nl.dionsegijn.konfetti.core.Angle import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.Position import nl.dionsegijn.konfetti.core.Spread import nl.dionsegijn.konfetti.core.emitter.Emitter import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.konfetti.xml.image.DrawableImage import java.util.concurrent.TimeUnit import kotlin.math.roundToInt import kotlin.random.Random private val Color1 = Color(0xfffce18a) private val Color2 = Color(0xFF009688) private val Color3 = Color(0xfff4306d) private val Color4 = Color(0xffb48def) private val Color5 = Color(0xFF95FF82) private val Color6 = Color(0xFF82ECFF) private val Color7 = Color(0xFFFF9800) private val Color8 = Color(0xFF0E008A) private val defaultColors = listOf( Color1, Color2, Color3, Color4, Color5, Color6, Color7, Color8 ) private fun List.mapToPrimary(primary: Color): List = map { it.blend(primary.copy(1f), primary.alpha).toArgb() } private val defaultShapes by lazy { listOf(Shape.Square, Shape.Circle, Shape.Rectangle(0.2f)) } private val confettiCache = mutableMapOf>>() @Stable class Particles( private val harmonizer: Color ) { fun build( type: Type ): List = confettiCache[harmonizer]?.get(type) ?: when (type) { Type.Default -> default(harmonizer) Type.Festive -> festiveBottom(harmonizer) Type.Explode -> explode(harmonizer) Type.Rain -> rain(harmonizer) Type.Side -> side(harmonizer) Type.Corners -> festiveCorners(harmonizer) Type.Toolbox -> toolbox(harmonizer) }.also { if (confettiCache[harmonizer]?.put(type, it) == null) { confettiCache[harmonizer] = mutableMapOf(type to it) } } companion object { private fun festive( primary: Color, xPos: Double = 0.5, yPos: Double = 1.0, angle: Int = Angle.TOP, duration: Long = 500, delay: Int = 0, spread: Int = 45 ): List = Party( speed = 30f, maxSpeed = 50f, damping = 0.9f, angle = angle, spread = spread, shapes = defaultShapes, delay = delay, timeToLive = 3000L, colors = defaultColors.mapToPrimary(primary), emitter = Emitter(duration = duration, TimeUnit.MILLISECONDS).max(30), position = Position.Relative(xPos, yPos) ).let { party -> listOf( party, party.copy( speed = 55f, maxSpeed = 65f, spread = (spread * 0.22f).roundToInt(), emitter = Emitter(duration = duration, TimeUnit.MILLISECONDS).max(10), ), party.copy( speed = 50f, maxSpeed = 60f, spread = (spread * 2.67f).roundToInt(), emitter = Emitter(duration = duration, TimeUnit.MILLISECONDS).max(40), ), party.copy( speed = 65f, maxSpeed = 80f, spread = (spread * 0.22f).roundToInt(), emitter = Emitter(duration = duration, TimeUnit.MILLISECONDS).max(10), ) ) } fun default( primary: Color ): List = listOf( Party( speed = 0f, maxSpeed = 15f, damping = 0.9f, angle = Angle.BOTTOM, spread = Spread.ROUND, colors = defaultColors.mapToPrimary(primary), shapes = defaultShapes, emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), position = Position.Relative(0.0, 0.0).between(Position.Relative(1.0, 0.0)) ), Party( speed = 10f, maxSpeed = 30f, damping = 0.9f, angle = Angle.RIGHT - 45, spread = 60, colors = defaultColors.mapToPrimary(primary), shapes = defaultShapes, emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), position = Position.Relative(0.0, 1.0) ), Party( speed = 10f, maxSpeed = 30f, damping = 0.9f, angle = Angle.RIGHT - 135, spread = 60, colors = defaultColors.mapToPrimary(primary), shapes = defaultShapes, emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), position = Position.Relative(1.0, 1.0) ) ) fun festiveBottom( primary: Color ): List = festive(primary, 0.2) .plus(festive(primary, 0.8)) fun explode( primary: Color, shape: Shape? = null, initialDelay: Int = 0 ): List = Party( speed = 0f, maxSpeed = 30f, damping = 0.9f, spread = 360, shapes = shape?.let { listOf(it) } ?: defaultShapes, timeToLive = 3000, colors = defaultColors.mapToPrimary(primary), emitter = Emitter(duration = 200, TimeUnit.MILLISECONDS).max(100) ).let { party -> val (x1, y1) = Random.nextDouble(0.0, 0.3) to Random.nextDouble(0.0, 0.5) val (x2, y2) = Random.nextDouble(0.0, 0.3) to Random.nextDouble(0.5, 1.0) val (x3, y3) = Random.nextDouble(0.3, 0.7) to Random.nextDouble(0.0, 1.0) val (x4, y4) = Random.nextDouble(0.7, 1.0) to Random.nextDouble(0.0, 0.5) val (x5, y5) = Random.nextDouble(0.7, 1.0) to Random.nextDouble(0.5, 1.0) listOf( party.copy( position = Position.Relative(x1, y1), delay = initialDelay ), party.copy( position = Position.Relative(x2, y2), delay = initialDelay + 200 ), party.copy( position = Position.Relative(x3, y3), delay = initialDelay + 400 ), party.copy( position = Position.Relative(x4, y4), delay = initialDelay + 600 ), party.copy( position = Position.Relative(x5, y5), delay = initialDelay + 800 ) ) } fun rain( primary: Color ): List = Party( speed = 10f, maxSpeed = 30f, damping = 0.9f, shapes = defaultShapes, colors = defaultColors.mapToPrimary(primary), emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), ).let { party -> listOf( party.copy( angle = 45, position = Position.Relative(0.0, 0.0), spread = 90, ), party.copy( angle = 90, position = Position.Relative(0.5, 0.0), spread = 360, ), party.copy( angle = 135, position = Position.Relative(1.0, 0.0), spread = 90, ) ) } fun side( primary: Color ): List = listOf( festive(primary, 0.0, 0.0, Angle.RIGHT, 1000), festive(primary, 1.0, 0.33, Angle.LEFT, 1000, 150), festive(primary, 0.0, 0.66, Angle.RIGHT, 1000, 300), festive(primary, 1.0, 1.0, Angle.LEFT, 1000, 450), ).flatten() fun festiveCorners( primary: Color ): List = listOf( festive(primary, 0.0, 0.0, 45, 1000, 0, 25), festive(primary, 1.0, 0.0, 135, 1000, 150, 25), festive(primary, 0.0, 1.0, -45, 1000, 300, 25), festive(primary, 1.0, 1.0, 225, 1000, 450, 25), ).flatten() fun toolbox( primary: Color ): List = Shape.DrawableShape( AppCompatResources.getDrawable( appContext, R.drawable.ic_launcher_monochrome_24 )!!.let { DrawableImage( drawable = it, width = it.intrinsicWidth, height = it.intrinsicHeight ) } ).let { shape -> val delay = 400 explode(primary, shape) + explode(primary, shape, delay) } } enum class Type { Default, Festive, Explode, Rain, Side, Corners, Toolbox } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/BarcodeScanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CameraAlt import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.onPrimaryContainerFixed import com.t8rin.imagetoolbox.core.ui.theme.onTertiaryContainerFixed import com.t8rin.imagetoolbox.core.ui.theme.primaryContainerFixed import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.core.utils.toQrType import com.t8rin.logger.makeLog import io.github.g00fy2.quickie.QRResult import io.github.g00fy2.quickie.ScanCustomCode import io.github.g00fy2.quickie.config.BarcodeFormat import io.github.g00fy2.quickie.config.ScannerConfig private class BarcodeScannerImpl( private val tint: Color, private val container: Color, private val frame: Color, private val frameHighlighted: Color, private val topIcon: Color, private val topText: Color, private val scannerLauncher: ManagedActivityResultLauncher ) : BarcodeScanner { override fun scan() { val config = ScannerConfig.build { setBarcodeFormats(listOf(BarcodeFormat.ALL_FORMATS)) setOverlayStringRes(R.string.scan_barcode) setOverlayDrawableRes(R.drawable.ic_24_barcode_scanner) setHapticSuccessFeedback(true) setShowTorchToggle(true) setShowCloseButton(true) setKeepScreenOn(true) setColors( buttonTint = tint.toArgb(), buttonBackground = container.toArgb(), frame = frame.toArgb(), frameHighlighted = frameHighlighted.toArgb(), topIcon = topIcon.toArgb(), topText = topText.toArgb() ) }.makeLog("Barcode Scanner") scannerLauncher.launch(config) } } @Stable @Immutable interface BarcodeScanner : Scanner @Composable fun rememberBarcodeScanner( onSuccess: (QrType) -> Unit ): BarcodeScanner { val scannerLauncher = rememberLauncherForActivityResult(ScanCustomCode()) { result -> result.makeLog("Barcode Scanner") when (result) { is QRResult.QRError -> { appContext.apply { val message = result.exception.localizedMessage ?: "" AppToastHost.showFailureToast( if ("NotFound" in message) { getString(R.string.no_barcode_found) } else { getString(R.string.smth_went_wrong, message) } ) } } QRResult.QRMissingPermission -> { AppToastHost.showToast( message = getString(R.string.grant_camera_permission_to_scan_qr_code), icon = Icons.Outlined.CameraAlt ) } is QRResult.QRSuccess -> onSuccess(result.content.toQrType()) QRResult.QRUserCanceled -> Unit } } val tint = MaterialTheme.colorScheme.onPrimaryContainerFixed val container = MaterialTheme.colorScheme.primaryContainerFixed val frame = MaterialTheme.colorScheme.onPrimaryContainerFixed val frameHighlighted = MaterialTheme.colorScheme.onTertiaryContainerFixed val topIcon = MaterialTheme.colorScheme.secondaryFixed val topText = MaterialTheme.colorScheme.secondaryFixed return remember(tint, container, frame, frameHighlighted, topIcon, topText, scannerLauncher) { BarcodeScannerImpl( tint = tint, container = container, frame = frame, frameHighlighted = frameHighlighted, topIcon = topIcon, topText = topText, scannerLauncher = scannerLauncher, ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ContactPicker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.database.Cursor import android.net.Uri import android.provider.ContactsContract import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.launch import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PersonOutline import androidx.compose.material.icons.rounded.Person import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.logger.makeLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext private data class ContactPickerImpl( val context: Context, val pickContact: ManagedActivityResultLauncher, val requestPermissionLauncher: ManagedActivityResultLauncher, val onFailure: (Throwable) -> Unit ) : ContactPicker { override fun pickContact() { "Pick Contact Start".makeLog() runCatching { if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_CONTACTS ) == PackageManager.PERMISSION_GRANTED ) { pickContact.launch() } else { requestPermissionLauncher.launch(Manifest.permission.READ_CONTACTS) } }.onFailure { it.makeLog("Pick Contact Failure") onFailure(it) }.onSuccess { "Pick Contact Success".makeLog() } } } @Stable @Immutable interface ContactPicker : ResultLauncher { fun pickContact() override fun launch() = pickContact() } @Composable fun rememberContactPicker( onFailure: () -> Unit = {}, onSuccess: (Contact) -> Unit, ): ContactPicker { val scope = rememberCoroutineScope() val context = LocalComponentActivity.current val pickContact = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickContact(), onResult = { uri -> uri?.takeIf { it != Uri.EMPTY }?.let { scope.launch { delay(200) onSuccess(it.parseContact()) } } ?: onFailure() } ) val requestPermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (isGranted) { pickContact.launch() } else { AppToastHost.showToast( message = getString(R.string.grant_contact_permission), icon = Icons.Outlined.PersonOutline ) } } return remember(pickContact) { derivedStateOf { ContactPickerImpl( context = context, pickContact = pickContact, requestPermissionLauncher = requestPermissionLauncher, onFailure = { onFailure() AppToastHost.showFailureToast(it) } ) } }.value } @Composable fun ContactPickerButton(onPicked: (Contact) -> Unit) { val contactPicker = rememberContactPicker(onSuccess = onPicked) EnhancedButton( onClick = contactPicker::pickContact, modifier = Modifier.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.mixedContainer, contentColor = MaterialTheme.colorScheme.onMixedContainer ) { Row( verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Rounded.Person, contentDescription = null ) Spacer(Modifier.width(4.dp)) Text( text = stringResource(R.string.pick_contact) ) } } } data class Contact( val addresses: List
, val emails: List, val name: PersonName, val organization: String, val phones: List, val title: String, val urls: List ) { constructor() : this( addresses = emptyList(), emails = emptyList(), name = PersonName(), organization = "", phones = emptyList(), title = "", urls = emptyList() ) data class Address( val addressLines: List, val type: Int ) data class PersonName( val first: String, val formattedName: String, val last: String, val middle: String, val prefix: String, val pronunciation: String, val suffix: String ) { constructor() : this( first = "", formattedName = "", last = "", middle = "", prefix = "", pronunciation = "", suffix = "" ) fun isEmpty() = first.isBlank() && formattedName.isBlank() && last.isBlank() && middle.isBlank() && prefix.isBlank() && pronunciation.isBlank() && suffix.isBlank() } data class Email( val address: String, val body: String, val subject: String, val type: Int ) { constructor() : this( address = "", body = "", subject = "", type = 0 ) fun isEmpty(): Boolean = address.isBlank() && body.isBlank() && subject.isBlank() } data class Phone( val number: String, val type: Int ) { constructor() : this( number = "", type = 0 ) fun isEmpty(): Boolean = number.isBlank() } fun isEmpty(): Boolean = addresses.isEmpty() && emails.isEmpty() && name.isEmpty() && organization.isBlank() && phones.isEmpty() && title.isBlank() && urls.isEmpty() } private suspend fun Uri.parseContact(): Contact = withContext(Dispatchers.IO) { val context = appContext val resolver = context.contentResolver var contactId: String? = null var name = Contact.PersonName() var organization = "" var title = "" val phones = mutableListOf() val emails = mutableListOf() val addresses = mutableListOf() val urls = mutableListOf() resolver.query(this@parseContact, null, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { contactId = cursor.getStringOrEmpty(ContactsContract.Contacts._ID) } } contactId?.let { id -> // StructuredName resolver.query( ContactsContract.Data.CONTENT_URI, null, "${ContactsContract.Data.CONTACT_ID}=? AND ${ContactsContract.Data.MIMETYPE}=?", arrayOf(id, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE), null )?.use { c -> if (c.moveToFirst()) { name = Contact.PersonName( first = c.getStringOrEmpty(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME), formattedName = c.getStringOrEmpty(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME), last = c.getStringOrEmpty(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME), middle = c.getStringOrEmpty(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME), prefix = c.getStringOrEmpty(ContactsContract.CommonDataKinds.StructuredName.PREFIX), pronunciation = c.getStringOrEmpty(ContactsContract.Contacts.PHONETIC_NAME), suffix = c.getStringOrEmpty(ContactsContract.CommonDataKinds.StructuredName.SUFFIX) ) } } // Phones resolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?", arrayOf(id), null )?.use { c -> while (c.moveToNext()) { phones.add( Contact.Phone( number = c.getStringOrEmpty(ContactsContract.CommonDataKinds.Phone.NUMBER), type = c.getIntOrZero(ContactsContract.CommonDataKinds.Phone.TYPE) ) ) } } // Emails resolver.query( ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, "${ContactsContract.CommonDataKinds.Email.CONTACT_ID} = ?", arrayOf(id), null )?.use { c -> while (c.moveToNext()) { emails.add( Contact.Email( address = c.getStringOrEmpty(ContactsContract.CommonDataKinds.Email.ADDRESS), body = "", subject = "", type = c.getIntOrZero(ContactsContract.CommonDataKinds.Email.TYPE) ) ) } } // Addresses resolver.query( ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI, null, "${ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID} = ?", arrayOf(id), null )?.use { c -> while (c.moveToNext()) { val address = c.getStringOrEmpty( ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS ) if (address.isNotBlank()) { addresses.add( Contact.Address( addressLines = address.split("\n"), type = c.getIntOrZero(ContactsContract.CommonDataKinds.StructuredPostal.TYPE) ) ) } } } // Organization + Title resolver.query( ContactsContract.Data.CONTENT_URI, null, "${ContactsContract.Data.CONTACT_ID}=? AND ${ContactsContract.Data.MIMETYPE}=?", arrayOf(id, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE), null )?.use { c -> if (c.moveToFirst()) { organization = c.getStringOrEmpty(ContactsContract.CommonDataKinds.Organization.COMPANY) title = c.getStringOrEmpty(ContactsContract.CommonDataKinds.Organization.TITLE) } } // Websites resolver.query( ContactsContract.Data.CONTENT_URI, null, "${ContactsContract.Data.CONTACT_ID}=? AND ${ContactsContract.Data.MIMETYPE}=?", arrayOf(id, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE), null )?.use { c -> while (c.moveToNext()) { val url = c.getStringOrEmpty(ContactsContract.CommonDataKinds.Website.URL) if (url.isNotBlank()) urls.add(url) } } } Contact( addresses = addresses, emails = emails, name = name, organization = organization, phones = phones, title = title, urls = urls ) } private fun Cursor.getStringOrEmpty(column: String): String = runCatching { getString(getColumnIndexOrThrow(column)) }.getOrNull() ?: "" private fun Cursor.getIntOrZero(column: String): Int = runCatching { getInt(getColumnIndexOrThrow(column)) }.getOrNull() ?: 0 ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import com.t8rin.imagetoolbox.core.ui.utils.helper.ScanResult @Stable @Immutable interface DocumentScanner : Scanner @Composable fun rememberDocumentScanner( onSuccess: (ScanResult) -> Unit ): DocumentScanner = rememberDocumentScannerImpl(onSuccess) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FileMaker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import android.net.Uri import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.logger.makeLog import kotlinx.coroutines.delay import kotlinx.coroutines.launch private data class FileMakerImpl( val createDocument: ManagedActivityResultLauncher, val onFailure: (Throwable) -> Unit ) : FileMaker { override fun make(name: String) { "File Make Start".makeLog() runCatching { createDocument.launch(name) }.onFailure { it.makeLog("File Make Failure") onFailure(it) }.onSuccess { "File Make Success".makeLog() } } } @Stable @Immutable interface FileMaker : ResultLauncher { fun make(name: String) override fun launch() = make("") } @Composable fun rememberFileCreator( mimeType: MimeType.Single = MimeType.All, onFailure: () -> Unit = {}, onSuccess: (Uri) -> Unit, ): FileMaker { val scope = rememberCoroutineScope() val createDocument = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument(mimeType.entry), onResult = { uri -> scope.launch { delay(300) uri?.takeIf { it != Uri.EMPTY }?.let { onSuccess(it) } ?: onFailure() } } ) return remember(createDocument) { derivedStateOf { FileMakerImpl( createDocument = createDocument, onFailure = { onFailure() AppToastHost.handleFileSystemFailure(it) } ) } }.value } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FilePicker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import android.content.Context import android.net.Uri import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.logger.makeLog import kotlinx.coroutines.delay import kotlinx.coroutines.launch private data class FilePickerImpl( val context: Context, val type: FileType, val mimeType: MimeType, val openDocument: ManagedActivityResultLauncher, Uri?>, val openDocumentMultiple: ManagedActivityResultLauncher, List>, val onFailure: (Throwable) -> Unit ) : FilePicker { override fun pickFile() { (type to mimeType).makeLog("File Picker Start") runCatching { when (type) { FileType.Single -> openDocument.launch(mimeType.entries.toTypedArray()) FileType.Multiple -> openDocumentMultiple.launch(mimeType.entries.toTypedArray()) } }.onFailure { it.makeLog("File Picker Failure") onFailure(it) }.onSuccess { (type to mimeType).makeLog("File Picker Success") } } } @Stable @Immutable interface FilePicker : ResultLauncher { fun pickFile() override fun launch() = pickFile() } enum class FileType { Single, Multiple } @Composable fun rememberFilePicker( type: FileType, mimeType: MimeType = MimeType.All, onFailure: () -> Unit = {}, onSuccess: (List) -> Unit, ): FilePicker { val context = LocalContext.current val scope = rememberCoroutineScope() val openDocument = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenDocument(), onResult = { uri -> scope.launch { delay(300) uri?.takeIf { it != Uri.EMPTY }?.let { onSuccess(listOf(it)) } ?: onFailure() } } ) val openDocumentMultiple = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenMultipleDocuments(), onResult = { uris -> scope.launch { delay(300) uris.takeIf { it.isNotEmpty() }?.let(onSuccess) ?: onFailure() } } ) return remember( type, mimeType, openDocument, openDocumentMultiple ) { derivedStateOf { FilePickerImpl( context = context, type = type, mimeType = mimeType, openDocument = openDocument, openDocumentMultiple = openDocumentMultiple, onFailure = { onFailure() AppToastHost.handleFileSystemFailure(it) } ) } }.value } @JvmName("rememberMultipleFilePicker") @Composable fun rememberFilePicker( mimeType: MimeType = MimeType.All, onFailure: () -> Unit = {}, onSuccess: (List) -> Unit, ): FilePicker = rememberFilePicker( type = FileType.Multiple, mimeType = mimeType, onFailure = onFailure, onSuccess = onSuccess ) @JvmName("rememberSingleFilePicker") @Composable fun rememberFilePicker( mimeType: MimeType = MimeType.All, onFailure: () -> Unit = {}, onSuccess: (Uri) -> Unit, ): FilePicker = rememberFilePicker( type = FileType.Single, mimeType = mimeType, onFailure = onFailure, onSuccess = { it.firstOrNull()?.let(onSuccess) } ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/FolderPicker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import android.net.Uri import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.takePersistablePermission import com.t8rin.logger.makeLog import kotlinx.coroutines.delay import kotlinx.coroutines.launch private data class FolderPickerImpl( val openDocumentTree: ManagedActivityResultLauncher, val onFailure: (Throwable) -> Unit ) : FolderPicker { override fun pickFolder(initialLocation: Uri?) { "Folder Open Start".makeLog() runCatching { openDocumentTree.launch(initialLocation) }.onFailure { it.makeLog("Folder Open Failure") onFailure(it) }.onSuccess { "Folder Open Success".makeLog() } } } @Stable @Immutable interface FolderPicker : ResultLauncher { fun pickFolder(initialLocation: Uri? = null) override fun launch() = pickFolder() } @Composable fun rememberFolderPicker( onFailure: () -> Unit = {}, onSuccess: (Uri) -> Unit, ): FolderPicker { val scope = rememberCoroutineScope() val openDocumentTree = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenDocumentTree(), onResult = { uri -> scope.launch { delay(300) uri?.takeIf { it != Uri.EMPTY }?.let { onSuccess(uri.takePersistablePermission()) } ?: onFailure() } } ) return remember(openDocumentTree) { derivedStateOf { FolderPickerImpl( openDocumentTree = openDocumentTree, onFailure = { onFailure() AppToastHost.handleFileSystemFailure(it) } ) } }.value } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ImagePicker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import android.Manifest import android.content.Context import android.content.Intent import android.net.Uri import android.provider.MediaStore import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CameraAlt import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color import androidx.core.content.FileProvider import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.PicturePickerMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.IntentUtils.parcelable import com.t8rin.imagetoolbox.core.ui.utils.helper.IntentUtils.parcelableArrayList import com.t8rin.imagetoolbox.core.ui.utils.helper.clipList import com.t8rin.imagetoolbox.core.ui.utils.helper.createMediaPickerIntent import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.logger.makeLog import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.io.File import kotlin.random.Random private class ImagePickerImpl( private val context: Context, private val mode: ImagePickerMode, private val currentAccent: Color, private val photoPickerSingle: ManagedActivityResultLauncher, private val photoPickerMultiple: ManagedActivityResultLauncher>, private val getContent: ManagedActivityResultLauncher, private val takePhoto: ManagedActivityResultLauncher, private val onCreateTakePhotoUri: (Uri) -> Unit, private val imageExtension: String, private val onFailure: (Throwable) -> Unit, ) : ImagePicker { override fun pickImage() { val cameraAction = { val imagesFolder = File(context.cacheDir, "images") runCatching { imagesFolder.mkdirs() val file = File(imagesFolder, "${Random.nextLong()}.jpg") FileProvider.getUriForFile( context, context.getString(R.string.file_provider), file ) }.onFailure { it.makeLog("Image Picker") }.onSuccess { onCreateTakePhotoUri(it) takePhoto.launch(it) } } val singlePhotoPickerAction = { photoPickerSingle.launch( PickVisualMediaRequest( ActivityResultContracts.PickVisualMedia.ImageOnly ) ) } val multiplePhotoPickerAction = { photoPickerMultiple.launch( PickVisualMediaRequest( ActivityResultContracts.PickVisualMedia.ImageOnly ) ) } val galleryAction = { val intent = Intent(Intent.ACTION_PICK).apply { setDataAndType( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/$imageExtension" ) if (mode == ImagePickerMode.GalleryMultiple) { putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) } } getContent.launch( Intent.createChooser( intent, context.getString(R.string.pick_image) ) ) } val embeddedAction = { getContent.launch( createMediaPickerIntent( context = context, allowMultiple = mode == ImagePickerMode.EmbeddedMultiple, currentAccent = currentAccent, imageExtension = imageExtension ) ) } val getContentAction = { val intent = Intent().apply { type = "image/$imageExtension" action = Intent.ACTION_OPEN_DOCUMENT putExtra( Intent.EXTRA_ALLOW_MULTIPLE, mode == ImagePickerMode.GetContentMultiple ) } getContent.launch( Intent.createChooser( intent, context.getString(R.string.pick_image) ) ) } mode.makeLog("Image Picker Start") runCatching { when (mode) { ImagePickerMode.PhotoPickerSingle -> singlePhotoPickerAction() ImagePickerMode.PhotoPickerMultiple -> multiplePhotoPickerAction() ImagePickerMode.CameraCapture -> cameraAction() ImagePickerMode.GallerySingle, ImagePickerMode.GalleryMultiple -> galleryAction() ImagePickerMode.GetContentSingle, ImagePickerMode.GetContentMultiple -> getContentAction() ImagePickerMode.Embedded, ImagePickerMode.EmbeddedMultiple -> embeddedAction() } }.onFailure { it.makeLog("Image Picker Failure") if (it is SecurityException && mode == ImagePickerMode.CameraCapture) { onFailure(CameraException()) } else onFailure(it) }.onSuccess { mode.makeLog("Image Picker Success") } } override fun pickImageWithMode( picker: Picker, picturePickerMode: PicturePickerMode ) { val multiple = picker == Picker.Multiple val mode = when (picturePickerMode) { PicturePickerMode.Embedded -> if (multiple) ImagePickerMode.EmbeddedMultiple else ImagePickerMode.Embedded PicturePickerMode.PhotoPicker -> if (multiple) ImagePickerMode.PhotoPickerMultiple else ImagePickerMode.PhotoPickerSingle PicturePickerMode.Gallery -> if (multiple) ImagePickerMode.GalleryMultiple else ImagePickerMode.GallerySingle PicturePickerMode.GetContent -> if (multiple) ImagePickerMode.GetContentMultiple else ImagePickerMode.GetContentSingle PicturePickerMode.CameraCapture -> ImagePickerMode.CameraCapture } val basePicker = ImagePickerImpl( context = context, mode = mode, currentAccent = currentAccent, photoPickerSingle = photoPickerSingle, photoPickerMultiple = photoPickerMultiple, getContent = getContent, takePhoto = takePhoto, onCreateTakePhotoUri = onCreateTakePhotoUri, imageExtension = imageExtension, onFailure = onFailure ) basePicker.pickImage() } } @Stable @Immutable interface ImagePicker : ResultLauncher { fun pickImage() fun pickImageWithMode( picker: Picker, picturePickerMode: PicturePickerMode ) override fun launch() = pickImage() } enum class ImagePickerMode { Embedded, EmbeddedMultiple, PhotoPickerSingle, PhotoPickerMultiple, GallerySingle, GalleryMultiple, GetContentSingle, GetContentMultiple, CameraCapture } enum class Picker { Single, Multiple } @Composable fun localImagePickerMode( picker: Picker = Picker.Single, mode: PicturePickerMode = LocalSettingsState.current.picturePickerMode, ): ImagePickerMode { return remember(mode, picker) { derivedStateOf { val multiple = picker == Picker.Multiple when (mode) { PicturePickerMode.Embedded -> if (multiple) ImagePickerMode.EmbeddedMultiple else ImagePickerMode.Embedded PicturePickerMode.PhotoPicker -> if (multiple) ImagePickerMode.PhotoPickerMultiple else ImagePickerMode.PhotoPickerSingle PicturePickerMode.Gallery -> if (multiple) ImagePickerMode.GalleryMultiple else ImagePickerMode.GallerySingle PicturePickerMode.GetContent -> if (multiple) ImagePickerMode.GetContentMultiple else ImagePickerMode.GetContentSingle PicturePickerMode.CameraCapture -> ImagePickerMode.CameraCapture } } }.value } @Composable fun rememberImagePicker( picker: Picker = Picker.Single, imageExtension: String = DefaultExtension, onFailure: () -> Unit = {}, onSuccess: (List) -> Unit, ): ImagePicker = rememberImagePicker( mode = localImagePickerMode(picker = picker), imageExtension = imageExtension, onFailure = onFailure, onSuccess = onSuccess ) @JvmName("rememberSingleImagePicker") @Composable fun rememberImagePicker( imageExtension: String = DefaultExtension, onFailure: () -> Unit = {}, onSuccess: (Uri) -> Unit, ): ImagePicker = rememberImagePicker( mode = localImagePickerMode(picker = Picker.Single), imageExtension = imageExtension, onFailure = onFailure, onSuccess = { it.firstOrNull()?.let(onSuccess) } ) @JvmName("rememberMultipleImagePicker") @Composable fun rememberImagePicker( imageExtension: String = DefaultExtension, onFailure: () -> Unit = {}, onSuccess: (List) -> Unit, ): ImagePicker = rememberImagePicker( mode = localImagePickerMode(picker = Picker.Multiple), imageExtension = imageExtension, onFailure = onFailure, onSuccess = onSuccess ) @Composable fun rememberImagePicker( mode: ImagePickerMode, imageExtension: String = DefaultExtension, onFailure: () -> Unit = {}, onSuccess: (List) -> Unit, ): ImagePicker { val scope = rememberCoroutineScope() val context = LocalComponentActivity.current val photoPickerSingle = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia(), onResult = { uri -> scope.launch { delay(300) uri?.takeIf { it != Uri.EMPTY }?.let { onSuccess(listOf(it)) } ?: onFailure() } } ) val photoPickerMultiple = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickMultipleVisualMedia(), onResult = { uris -> scope.launch { delay(300) uris.takeIf { it.isNotEmpty() }?.let(onSuccess) ?: onFailure() } } ) val getContent = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult(), onResult = { result -> val intent = result.data val data = intent?.data val clipData = intent?.clipData val resultList: List = clipData?.clipList() ?: if (data != null) { listOf(data) } else if (intent?.action == Intent.ACTION_SEND_MULTIPLE) { intent.parcelableArrayList(Intent.EXTRA_STREAM) ?: emptyList() } else if (intent?.action == Intent.ACTION_SEND) { listOfNotNull(intent.parcelable(Intent.EXTRA_STREAM)) } else { emptyList() } scope.launch { delay(300) resultList.takeIf { it.isNotEmpty() }?.let(onSuccess) ?: onFailure() } } ) var takePhotoUri by rememberSaveable { mutableStateOf(null) } val takePhoto = rememberLauncherForActivityResult( contract = ActivityResultContracts.TakePicture(), onResult = { scope.launch { val uri = takePhotoUri delay(300) if (it && uri != null && uri != Uri.EMPTY) { onSuccess(listOf(uri)) } else onFailure() takePhotoUri = null } } ) val currentAccent = LocalDynamicThemeState.current.colorTuple.value.primary val requestPermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (!isGranted) { AppToastHost.showToast( message = context.getString(R.string.grant_camera_permission_to_capture_image), icon = Icons.Outlined.CameraAlt ) } } return remember( imageExtension, currentAccent, photoPickerSingle, photoPickerMultiple, getContent, takePhoto, mode ) { derivedStateOf { ImagePickerImpl( context = context, mode = mode, currentAccent = currentAccent, photoPickerSingle = photoPickerSingle, photoPickerMultiple = photoPickerMultiple, getContent = getContent, takePhoto = takePhoto, onCreateTakePhotoUri = { takePhotoUri = it }, imageExtension = imageExtension, onFailure = { onFailure() when (it) { is CameraException -> requestPermissionLauncher.launch(Manifest.permission.CAMERA) else -> AppToastHost.handleFileSystemFailure(it) } } ) } }.value } private class CameraException : Throwable("No Camera permission") private const val DefaultExtension: String = "*" ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/ResultLauncher.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable @Stable @Immutable interface ResultLauncher { fun launch() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/Scanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable @Stable @Immutable interface Scanner : ResultLauncher { fun scan() override fun launch() = scan() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ActivityUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.Context import android.content.Intent import android.provider.MediaStore import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.utils.Flavor import com.t8rin.imagetoolbox.core.resources.BuildConfig val AppActivityClass: Class<*> by lazy { Class.forName( "com.t8rin.imagetoolbox.app.presentation.AppActivity" ) } val MediaPickerActivityClass: Class<*> by lazy { Class.forName( "com.t8rin.imagetoolbox.feature.media_picker.presentation.MediaPickerActivity" ) } fun createMediaPickerIntent( context: Context, allowMultiple: Boolean, currentAccent: Color, imageExtension: String ): Intent = Intent( Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, context, MediaPickerActivityClass ).apply { setDataAndType( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/$imageExtension" ) if (allowMultiple) { putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) } putExtra(ColorSchemeName, currentAccent.toArgb()) } val AppVersionPreRelease: String by lazy { BuildConfig.VERSION_NAME .replace(BuildConfig.FLAVOR, "") .split("-") .takeIf { it.size > 1 } ?.drop(1)?.first() ?.takeWhile { it.isLetter() } ?: "" } val AppVersionPreReleaseFlavored: String by lazy { if (!Flavor.isFoss()) { AppVersionPreRelease } else { "${BuildConfig.FLAVOR} $AppVersionPreRelease" }.uppercase() } val AppVersion: String by lazy { BuildConfig.VERSION_NAME + if (Flavor.isFoss()) "-foss" else "" } const val ColorSchemeName = "scheme" ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/AppToastHost.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.ActivityNotFoundException import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FolderOff import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.confetti.ConfettiHostState import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration import com.t8rin.imagetoolbox.core.ui.widget.other.ToastHostState import com.t8rin.imagetoolbox.core.ui.widget.other.showFailureToast import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlin.coroutines.CoroutineContext data object AppToastHost { private var context: CoroutineContext = Dispatchers.Unconfined private val scope by lazy { CoroutineScope(context) } val state = ToastHostState() val confettiState = ConfettiHostState() fun init(context: CoroutineContext) { this.context = context } fun showToast( message: String, icon: ImageVector? = null, duration: ToastDuration = ToastDuration.Short ) { scope.launch { state.showToast( message = message, icon = icon, duration = duration ) } } fun showToast( message: Int, icon: ImageVector? = null, duration: ToastDuration = ToastDuration.Short ) { scope.launch { state.showToast( message = getString(message), icon = icon, duration = duration ) } } fun showFailureToast(throwable: Throwable) { scope.launch { state.showFailureToast( context = appContext, throwable = throwable ) } } fun showFailureToast(message: String) { scope.launch { state.showFailureToast( message = message ) } } fun showFailureToast(res: Int) { scope.launch { state.showFailureToast( message = appContext.getString(res) ) } } fun dismissToasts() { state.currentToastData?.dismiss() confettiState.currentToastData?.dismiss() } fun showConfetti( duration: ToastDuration ) { scope.launch { confettiState.showConfetti(duration) } } fun showConfetti() { showConfetti(ToastDuration(4500L)) } fun handleFileSystemFailure(throwable: Throwable) { when (throwable) { is ActivityNotFoundException -> showActivateFilesToast() else -> showFailureToast(throwable) } } const val PERMISSION = "REQUEST_PERMISSION" private fun showActivateFilesToast() { showToast( message = appContext.getString(R.string.activate_files), icon = Icons.Outlined.FolderOff, duration = ToastDuration.Long ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/BlendingModeExt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.os.Build import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode val BlendingMode.Companion.entries: List get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { newEntries } else oldEntries ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Clipboard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.net.Uri import android.os.Build import androidx.annotation.StringRes import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CopyAll import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ClipEntry import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString object Clipboard { private val clipboard by lazy { AndroidClipboardManager() } fun copy( clipEntry: ClipEntry?, onSuccess: () -> Unit = {} ) { runCatching { clipboard.setClip(clipEntry) }.onSuccess { onSuccess() }.onFailure { AppToastHost.showFailureToast(getString(R.string.data_is_too_large_to_copy)) } } fun copy( uri: Uri, @StringRes message: Int = R.string.copied, icon: ImageVector = Icons.Rounded.CopyAll ) { copy( clipEntry = uri.asClip(appContext), onSuccess = { AppToastHost.showConfetti() if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { AppToastHost.showToast( message = getString(message), icon = icon ) } } ) } fun copy( text: CharSequence, @StringRes message: Int = R.string.copied, icon: ImageVector = Icons.Rounded.CopyAll ) { copy( clipEntry = ClipEntry(text.toClipData()), onSuccess = { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { AppToastHost.showToast( message = getString(message), icon = icon ) } } ) } fun getText( onSuccess: (String) -> Unit ) { runCatching { clipboard.getClip() ?.clipData?.let { primaryClip -> if (primaryClip.itemCount > 0) { primaryClip.getItemAt(0)?.text } else { null } }?.takeIf { it.isNotEmpty() }?.let { onSuccess(it.toString()) } }.onFailure { AppToastHost.showFailureToast(getString(R.string.clipboard_data_is_too_large)) } } fun clear() { runCatching { clipboard.setClip(null) } } } private class AndroidClipboardManager { private val clipboardManager: ClipboardManager get() = appContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager fun getClip(): ClipEntry? = clipboardManager.primaryClip?.let(::ClipEntry) fun setClip(clipEntry: ClipEntry?) { if (clipEntry == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { clipboardManager.clearPrimaryClip() } else { clipboardManager.setPrimaryClip(ClipData.newPlainText("", "")) } } else { clipboardManager.setPrimaryClip(clipEntry.clipData) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ClipboardUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.ClipData import android.content.ClipDescription import android.content.ClipboardManager import android.content.Context import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.LocalContext import androidx.core.content.getSystemService import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isFromAppFileProvider import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.moveToCache @Composable fun rememberClipboardData(): State> { val settingsState = LocalSettingsState.current val allowPaste = settingsState.allowAutoClipboardPaste val context = LocalContext.current val clipboardManager = remember(context) { context.getSystemService() } val clip = remember { mutableStateOf( if (allowPaste) { clipboardManager.clipList() } else emptyList() ) }.apply { value = if (allowPaste) { clipboardManager.clipList() } else emptyList() } val callback = remember { ClipboardManager.OnPrimaryClipChangedListener { if (allowPaste) { clip.value = clipboardManager.clipList() } } } DisposableEffect(clipboardManager, allowPaste) { if (allowPaste) { clipboardManager?.addPrimaryClipChangedListener(callback) } onDispose { clipboardManager?.removePrimaryClipChangedListener(callback) } } return clip } @Composable fun rememberClipboardText(): State { val settingsState = LocalSettingsState.current val allowPaste = settingsState.allowAutoClipboardPaste val context = LocalContext.current val clipboardManager = remember(context) { context.getSystemService() } val clip = remember { mutableStateOf( if (allowPaste) { clipboardManager.clipText() } else "" ) }.apply { value = if (allowPaste) { clipboardManager.clipText() } else "" } val callback = remember { ClipboardManager.OnPrimaryClipChangedListener { if (allowPaste) { clip.value = clipboardManager.clipText() } } } DisposableEffect(clipboardManager, allowPaste) { if (allowPaste) { clipboardManager?.addPrimaryClipChangedListener(callback) } onDispose { clipboardManager?.removePrimaryClipChangedListener(callback) } } return clip } fun ClipboardManager?.clipList(): List = runCatching { this?.primaryClip?.clipList() }.getOrNull() ?: emptyList() fun ClipboardManager?.clipText(): String = runCatching { this?.primaryClip?.getItemAt(0)?.text?.toString() }.getOrNull() ?: "" fun ClipData.clipList() = List( size = itemCount, init = { index -> getItemAt(index).uri?.let { uri -> if (uri.isFromAppFileProvider()) uri else uri.moveToCache() } } ).filterNotNull() fun List.toClipData( description: String = "Images", mimeTypes: Array = arrayOf("image/*") ): ClipData? { if (this.isEmpty()) return null return ClipData( ClipDescription( description, mimeTypes ), ClipData.Item(this.first()) ).apply { this@toClipData.drop(1).forEach { addItem(ClipData.Item(it)) } } } fun CharSequence.toClipData( label: String = "plain text" ): ClipData = ClipData.newPlainText(label, this) fun Uri.asClip( context: Context, label: String = "Image" ): ClipEntry = ClipEntry( ClipData.newUri( context.contentResolver, label, this ) ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CoilUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.graphics.Bitmap import coil3.size.Size import coil3.size.pxOrElse import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import coil3.transform.Transformation as CoilTransformation fun Size.asDomain(): IntegerSize = if (this == Size.ORIGINAL) IntegerSize.Undefined else IntegerSize(width.pxOrElse { 1 }, height.pxOrElse { 1 }) fun IntegerSize.asCoil(): Size = if (this == IntegerSize.Undefined) Size.ORIGINAL else Size(width, height) fun Transformation.toCoil(): CoilTransformation = object : CoilTransformation() { private val instance = this@toCoil override fun toString(): String = instance::class.simpleName.toString() override val cacheKey: String get() = instance.cacheKey override suspend fun transform( input: Bitmap, size: Size ): Bitmap = instance.transform(input, size.asDomain()) } fun CoilTransformation.asDomain(): Transformation = object : Transformation { override val cacheKey: String get() = this@asDomain.cacheKey override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = this@asDomain.transform(input, size.asCoil()) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ColorUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.model.ColorModel fun Color.toHex(): String = String.format("#%08X", (0xFFFFFFFF and this.toArgb().toLong())).replace("#FF", "#") inline fun ColorModel.toColor() = Color(colorInt) inline fun Color.toModel() = ColorModel(toArgb()) inline val ColorModel.red: Float get() = toColor().red inline val ColorModel.green: Float get() = toColor().green inline val ColorModel.blue: Float get() = toColor().blue inline val ColorModel.alpha: Float get() = toColor().alpha ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/CompositionLocalUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ProvidableCompositionLocal import kotlin.reflect.KProperty @Composable fun ProvidableCompositionLocal.ProvidesValue( value: T, content: @Composable () -> Unit ) = CompositionLocalProvider(value = this provides value, content) @Composable operator fun ProvidableCompositionLocal.getValue( t: T?, property: KProperty<*> ): T = current ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ContextUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.Manifest import android.annotation.SuppressLint import android.app.Activity import android.app.ActivityManager import android.content.ClipboardManager import android.content.ContentResolver import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.content.res.Configuration import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.net.Uri import android.os.Build import android.provider.Settings import android.webkit.MimeTypeMap import android.widget.Toast import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatDelegate import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.unit.Density import androidx.core.app.ActivityCompat import androidx.core.app.PendingIntentCompat import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.toColorInt import androidx.core.net.toUri import androidx.core.os.LocaleListCompat import com.t8rin.imagetoolbox.core.domain.model.PerformanceClass import com.t8rin.imagetoolbox.core.domain.utils.FileMode import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector.toImageBitmap import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionStatus import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionUtils.askUserToRequestPermissionExplicitly import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionUtils.checkPermissions import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionUtils.hasPermissionAllowed import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionUtils.setPermissionsAllowed import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.logger.makeLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File import java.io.RandomAccessFile import java.util.Locale import kotlin.math.ceil import kotlin.random.Random object ContextUtils { fun Activity.requestStoragePermission() = requestPermissions( permissions = listOf( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE ) ) fun Activity.requestPermissions(permissions: List) { val state = checkPermissions(permissions) when (state.permissionStatus.values.first()) { PermissionStatus.NOT_GIVEN -> { ActivityCompat.requestPermissions( this, permissions.toTypedArray(), 0 ) } PermissionStatus.DENIED_PERMANENTLY -> { askUserToRequestPermissionExplicitly() AppToastHost.showToast( message = R.string.grant_permission_manual, duration = ToastDuration.Long ) } PermissionStatus.ALLOWED -> Unit } } fun Context.buildIntent( clazz: Class<*>, intentBuilder: Intent.() -> Unit, ): Intent = Intent(applicationContext, clazz).apply(intentBuilder) fun Context.postToast( textRes: Int, vararg formatArgs: Any, ) { mainLooperAction { Toast.makeText( applicationContext, getString( textRes, *formatArgs ), Toast.LENGTH_SHORT ).show() } } fun Context.postToast( textRes: Int, isLong: Boolean = false, vararg formatArgs: Any, ) { mainLooperAction { Toast.makeText( applicationContext, getString( textRes, *formatArgs ), if (isLong) { Toast.LENGTH_LONG } else Toast.LENGTH_SHORT ).show() } } fun Context.needToShowStoragePermissionRequest(): Boolean { val permissions = listOf( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE ) val show = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) false else !permissions.all { (this as Activity).hasPermissionAllowed(it) } if (!show) setPermissionsAllowed(permissions) return show } fun Context.adjustFontSize( scale: Float?, ): Context { val configuration = resources.configuration configuration.fontScale = scale ?: resources.configuration.fontScale return createConfigurationContext(configuration) } fun Context.isInstalledFromPlayStore(): Boolean = verifyInstallerId( listOf( "com.android.vending", "com.google.android.feedback" ) ) private fun Context.verifyInstallerId( validInstallers: List, ): Boolean = validInstallers.contains(getInstallerPackageName(packageName)) private fun Context.getInstallerPackageName(packageName: String): String? { runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) return packageManager.getInstallSourceInfo(packageName).installingPackageName @Suppress("DEPRECATION") return packageManager.getInstallerPackageName(packageName) } return null } @Composable fun rememberFilename(uri: Uri): String? { return remember(uri) { derivedStateOf { uri.filename() } }.value } @Composable fun rememberFileExtension(uri: Uri): String? { return remember(uri) { derivedStateOf { uri.getExtension() } }.value } val Context.performanceClass: PerformanceClass get() { val androidVersion = Build.VERSION.SDK_INT val cpuCount = Runtime.getRuntime().availableProcessors() val memoryClass = getSystemService()!!.memoryClass var totalCpuFreq = 0 var freqResolved = 0 for (i in 0 until cpuCount) { runCatching { val reader = RandomAccessFile( String.format( Locale.ENGLISH, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i ), FileMode.Read.mode ) val line = reader.readLine() if (line != null) { totalCpuFreq += line.toInt() / 1000 freqResolved++ } reader.close() } } val maxCpuFreq = if (freqResolved == 0) -1 else ceil((totalCpuFreq / freqResolved.toFloat()).toDouble()) .toInt() return if (androidVersion < 21 || cpuCount <= 2 || memoryClass <= 100 || cpuCount <= 4 && maxCpuFreq != -1 && maxCpuFreq <= 1250 || cpuCount <= 4 && maxCpuFreq <= 1600 && memoryClass <= 128 && androidVersion <= 21 || cpuCount <= 4 && maxCpuFreq <= 1300 && memoryClass <= 128 && androidVersion <= 24) { PerformanceClass.Low } else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 2050 || maxCpuFreq == -1 && cpuCount == 8 && androidVersion <= 23) { PerformanceClass.Average } else { PerformanceClass.High } } @Suppress("unused", "MemberVisibilityCanBePrivate") tailrec fun Context.findActivity(): Activity? = when (this) { is Activity -> this is ContextWrapper -> baseContext.findActivity() else -> null } fun Context.getStringLocalized( @StringRes resId: Int, locale: Locale, ): String = createConfigurationContext( Configuration(resources.configuration).apply { setLocale(locale) } ).getText(resId).toString() fun Context.pasteColorFromClipboard( onPastedColor: (Color) -> Unit, onPastedColorFailure: (String) -> Unit, ) { val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val item = clipboard.primaryClip?.getItemAt(0) val text = item?.text?.toString() text?.let { runCatching { onPastedColor(Color(it.toColorInt())) }.getOrElse { onPastedColorFailure(getString(R.string.clipboard_paste_invalid_color_code)) } } ?: run { onPastedColorFailure(getString(R.string.clipboard_paste_invalid_empty)) } } fun Context.getLanguages(): Map { val languages = mutableListOf("" to getString(R.string.system)).apply { addAll( LocaleConfigCompat(this@getLanguages) .supportedLocales!!.toList() .map { it.toLanguageTag() to it.getDisplayName(it) .replaceFirstChar(Char::uppercase) } ) } return languages.let { tags -> listOf(tags.first()) + tags.drop(1).sortedBy { it.second } }.toMap() } fun Context.getCurrentLocaleString(): String { val locales = AppCompatDelegate.getApplicationLocales() if (locales == LocaleListCompat.getEmptyLocaleList()) { return getString(R.string.system) } return locales.getDisplayName() } fun LocaleListCompat.getDisplayName(): String = getDisplayName(toLanguageTags()) fun getDisplayName( lang: String?, useDefaultLocale: Boolean = false ): String { if (lang == null) { return "" } val locale = when (lang) { "" -> LocaleListCompat.getAdjustedDefault()[0] else -> Locale.forLanguageTag(lang) } return locale!!.getDisplayName( if (useDefaultLocale) Locale.getDefault() else locale ).replaceFirstChar { it.uppercase(locale) } } private const val SCREEN_ID_EXTRA = "screen_id" const val SHORTCUT_OPEN_ACTION = "shortcut" fun Intent?.getScreenExtra(): Screen? { if (this?.hasExtra(SCREEN_ID_EXTRA) != true) return null val screenIdExtra = getIntExtra(SCREEN_ID_EXTRA, -100).takeIf { it != -100 } ?: return null return Screen.entries.find { it.id == screenIdExtra } } fun Intent.putScreenExtra(screen: Screen?) = apply { if (screen == null) { removeExtra(SCREEN_ID_EXTRA) } else { putExtra(SCREEN_ID_EXTRA, screen.id) } } fun Intent?.getScreenOpeningShortcut( onNavigate: (Screen) -> Unit, ): Boolean { if (this == null) return false val screenExtra = getScreenExtra() if (action == SHORTCUT_OPEN_ACTION && screenExtra != null) { onNavigate(screenExtra) return true } return false } suspend fun Context.createScreenShortcut( screen: Screen, tint: Color = Color.Unspecified, onFailure: (Throwable) -> Unit = {}, ) = withContext(Dispatchers.Main.immediate) { runCatching { val context = this@createScreenShortcut if (context.canPinShortcuts() && screen.icon != null) { val imageBitmap = screen.icon!!.toImageBitmap( context = context, width = 256, height = 256, tint = tint.takeOrElse { Color(0xFF5F823E) } ) val info = ShortcutInfoCompat.Builder(context, screen.id.toString()) .setShortLabel(getString(screen.title)) .setLongLabel(getString(screen.subtitle)) .setIcon(IconCompat.createWithBitmap(imageBitmap.asAndroidBitmap())) .setIntent( context.buildIntent(AppActivityClass) { action = SHORTCUT_OPEN_ACTION flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK putScreenExtra(screen) } ) .build() val callbackIntent = ShortcutManagerCompat.createShortcutResultIntent(context, info) val successCallback = PendingIntentCompat.getBroadcast( context, 0, callbackIntent, 0, false ) ShortcutManagerCompat.requestPinShortcut( context, info, successCallback?.intentSender ) } else { onFailure(UnsupportedOperationException()) } }.onFailure(onFailure) } fun Context.canPinShortcuts(): Boolean = runCatching { ShortcutManagerCompat.isRequestPinShortcutSupported(this) }.getOrNull() == true @SuppressLint("MissingPermission") fun Context.isNetworkAvailable(): Boolean { return getSystemService()?.run { val capabilities = getNetworkCapabilities( activeNetwork ?: return false ) ?: return false possibleCapabilities.any(capabilities::hasTransport) } ?: false } private val possibleCapabilities = listOf( NetworkCapabilities.TRANSPORT_WIFI, NetworkCapabilities.TRANSPORT_CELLULAR, NetworkCapabilities.TRANSPORT_ETHERNET, NetworkCapabilities.TRANSPORT_BLUETOOTH ) fun Context.shareText(value: String) { val sendIntent = Intent().apply { action = Intent.ACTION_SEND type = "text/plain" putExtra(Intent.EXTRA_TEXT, value) } val shareIntent = Intent.createChooser(sendIntent, getString(R.string.share)) shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(shareIntent) } fun Context.shareUris(uris: List) { if (uris.isEmpty()) return val sendIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris)) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) type = MimeTypeMap.getSingleton() .getMimeTypeFromExtension( uris.first().getExtension() ) ?: "*/*" } val shareIntent = Intent.createChooser(sendIntent, getString(R.string.share)) shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(shareIntent) } fun Uri.getExtension(): String? = runCatching { val filename = filename().orEmpty() if (filename.endsWith(".qoi")) return "qoi" if (filename.endsWith(".jxl")) return "jxl" return if (ContentResolver.SCHEME_CONTENT == scheme) { MimeTypeMap.getSingleton() .getExtensionFromMimeType( appContext.contentResolver.getType(this@getExtension) ) } else { MimeTypeMap.getFileExtensionFromUrl(this@getExtension.toString()) .lowercase(Locale.getDefault()) } }.getOrNull() val Context.density: Density get() = object : Density { override val density: Float get() = resources.displayMetrics.density override val fontScale: Float get() = resources.configuration.fontScale } @RequiresApi(Build.VERSION_CODES.R) fun manageAllFilesIntent() = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) @RequiresApi(Build.VERSION_CODES.R) fun Context.manageAppAllFilesIntent(): Intent { return Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) .setData("package:${packageName}".toUri()) } fun Context.appSettingsIntent(): Intent { return Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setData("package:${packageName}".toUri()) } fun Uri.takePersistablePermission(): Uri = apply { runCatching { appContext.contentResolver.takePersistableUriPermission( this, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) }.onFailure { it.makeLog("takePersistablePermission") } } fun Uri.moveToCache(): Uri? = appContext.run { contentResolver.openInputStream(this@moveToCache)?.use { stream -> val file = File( cacheDir, filename() ?: "cache_${Random.nextInt()}.tmp" ).apply { createNewFile() } file.outputStream().use { stream.copyTo(it) } file.toUri() } } fun Uri.isFromAppFileProvider() = toString().run { contains("content://media/external") || contains(appContext.getString(R.string.file_provider)) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DensityUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.util.fastRoundToInt @Composable fun Dp.toSp(): TextUnit = with(LocalDensity.current) { toSp() } @Composable fun Dp.toPx(): Float = with(LocalDensity.current) { toPx() } @Composable fun Dp.roundToPx(): Int = toPx().fastRoundToInt() @Composable fun TextUnit.toPx(): Float = with(LocalDensity.current) { toPx() } @Composable fun TextUnit.roundToPx(): Int = toPx().fastRoundToInt() @Composable fun Int.toDp(): Dp = with(LocalDensity.current) { toDp() } @Composable fun Int.toSp(): TextUnit = toDp().toSp() @Composable fun Float.toDp(): Dp = with(LocalDensity.current) { toDp() } @Composable fun Float.toSp(): TextUnit = toDp().toSp() ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DeviceInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.os.Build.BRAND import android.os.Build.DEVICE import android.os.Build.MODEL import android.os.Build.VERSION.RELEASE import android.os.Build.VERSION.SDK_INT import androidx.appcompat.app.AppCompatDelegate import com.t8rin.imagetoolbox.core.resources.BuildConfig.FLAVOR import com.t8rin.imagetoolbox.core.resources.BuildConfig.VERSION_CODE import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getDisplayName import com.t8rin.logger.makeLog @ConsistentCopyVisibility data class DeviceInfo private constructor( val device: String, val sdk: String, val appVersion: String, val flavor: String, val locale: String ) { companion object { fun get(): DeviceInfo { val device = "$MODEL ($BRAND - $DEVICE)" val sdk = "$SDK_INT (Android $RELEASE)" val appVersion = "$AppVersion ($VERSION_CODE)" val flavor = FLAVOR val locale = AppCompatDelegate.getApplicationLocales().getDisplayName() return DeviceInfo( device = device, sdk = sdk, appVersion = appVersion, flavor = flavor, locale = locale ) } fun getAsString(): String { val (device, sdk, appVersion, flavor, locale) = get() return listOf( "Device: $device", "SDK: $sdk", "App Version: $appVersion", "Flavor: $flavor", "Locale: $locale" ).joinToString("\n") } fun pushLog(extra: String? = null) { getAsString().makeLog("DeviceInfo".plus(extra?.let { " $it" }.orEmpty())) } fun isPixel() = getAsString().contains( other = "google", ignoreCase = true ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/DrawUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.graphics.Matrix import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import kotlin.math.cos import kotlin.math.sin fun Path.scaleToFitCanvas( currentSize: IntegerSize, oldSize: IntegerSize, onGetScale: (Float, Float) -> Unit = { _, _ -> } ): Path { val sx = currentSize.width.toFloat() / oldSize.width val sy = currentSize.height.toFloat() / oldSize.height onGetScale(sx, sy) return android.graphics.Path(this.asAndroidPath()).apply { transform( Matrix().apply { setScale(sx, sy) } ) }.asComposePath() } fun Offset.rotate( angle: Double ): Offset = Offset( x = (x * cos(Math.toRadians(angle)) - y * sin(Math.toRadians(angle))).toFloat(), y = (x * sin(Math.toRadians(angle)) + y * cos(Math.toRadians(angle))).toFloat() ) fun Path.moveTo(offset: Offset) = moveTo(offset.x, offset.y) fun Path.lineTo(offset: Offset) = lineTo(offset.x, offset.y) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandleDeeplinks.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.Intent import android.net.Uri import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ErrorOutline import com.t8rin.imagetoolbox.core.domain.BACKUP_FILE_EXT import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getScreenExtra import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getScreenOpeningShortcut import com.t8rin.imagetoolbox.core.ui.utils.helper.IntentUtils.parcelable import com.t8rin.imagetoolbox.core.ui.utils.helper.IntentUtils.parcelableArrayList import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.filename fun Intent?.handleDeeplinks( onStart: () -> Unit, onColdStart: () -> Unit, onNavigate: (Screen) -> Unit, onGetUris: (List) -> Unit, onHasExtraDataType: (ExtraDataType) -> Unit, isHasUris: Boolean, onWantGithubReview: () -> Unit, isOpenEditInsteadOfPreview: Boolean, ) { val intent = this ?: return onStart() val type = intent.type if (type != null && !isHasUris) onColdStart() val action = intent.action if (action == Intent.ACTION_BUG_REPORT) { onWantGithubReview() return } if (intent.getScreenOpeningShortcut(onNavigate)) return val data = intent.data val clipData = intent.clipData runCatching { fun String?.isMarkupProjectName(): Boolean { val value = this?.lowercase().orEmpty() return value.endsWith(".itp") || value.endsWith(".itp.zip") } fun Uri?.isMarkupProject(): Boolean = this?.toString().isMarkupProjectName() || this?.filename().isMarkupProjectName() val startsWithImage = type?.startsWith("image/") == true val hasExtraFormats = clipData?.clipList() ?.any { it.toString().endsWith(".jxl") || it.toString().endsWith(".qoi") || it.toString().endsWith(".itp") || it.toString().endsWith(".itp.zip") } == true val dataHasExtraFormats = data.toString().let { it.endsWith(".jxl") || it.endsWith(".qoi") || it.endsWith(".itp") || it.endsWith(".itp.zip") } if (data.isMarkupProject()) { onNavigate(Screen.MarkupLayers(data)) } else if ((startsWithImage || hasExtraFormats || dataHasExtraFormats)) { when (action) { Intent.ACTION_VIEW -> { val uris = clipData?.clipList() ?: data?.let { listOf(it) } ?: return@runCatching if (isOpenEditInsteadOfPreview) { onGetUris(uris) } else { onNavigate(Screen.ImagePreview(uris)) } } Intent.ACTION_SEND -> { intent.parcelable(Intent.EXTRA_STREAM)?.let { when (intent.getScreenExtra()) { is Screen.PickColorFromImage -> onNavigate(Screen.PickColorFromImage(it)) is Screen.PaletteTools -> onNavigate(Screen.PaletteTools(it)) else -> { if (type?.contains("gif") == true) { onHasExtraDataType(ExtraDataType.Gif) } onGetUris(listOf(it)) } } } } Intent.ACTION_SEND_MULTIPLE -> { intent.parcelableArrayList(Intent.EXTRA_STREAM)?.let { if (type?.contains("gif") == true) { onHasExtraDataType(ExtraDataType.Gif) it.firstOrNull()?.let { uri -> onGetUris(listOf(uri)) } } else onGetUris(it) } } Intent.ACTION_EDIT, Intent.ACTION_INSERT, Intent.ACTION_INSERT_OR_EDIT -> { val uris = clipData?.clipList() ?: data?.let { listOf(it) } ?: return@runCatching if (type?.contains("gif") == true) { onHasExtraDataType(ExtraDataType.Gif) } onGetUris(uris) } else -> { data?.let { if (type?.contains("gif") == true) { onHasExtraDataType(ExtraDataType.Gif) } onGetUris(listOf(it)) } } } } else if (type != null) { val text = intent.getStringExtra(Intent.EXTRA_TEXT) if (text != null) { onHasExtraDataType(ExtraDataType.Text(text)) onGetUris(listOf()) } else { val isPdf = type.contains("pdf") val isAudio = type.startsWith("audio/") when (action) { Intent.ACTION_SEND_MULTIPLE -> { intent.parcelableArrayList(Intent.EXTRA_STREAM)?.let { when { isAudio -> { onHasExtraDataType(ExtraDataType.Audio) onGetUris(it) } isPdf -> { onHasExtraDataType(ExtraDataType.Pdf) onGetUris(it) } else -> onNavigate(Screen.Zip(it)) } } } Intent.ACTION_SEND -> { intent.parcelable(Intent.EXTRA_STREAM)?.let { if (it.isMarkupProject()) { onNavigate(Screen.MarkupLayers(it)) return } if (it.toString().contains(BACKUP_FILE_EXT, true)) { onHasExtraDataType(ExtraDataType.Backup(it.toString())) return } when { isAudio -> onHasExtraDataType(ExtraDataType.Audio) isPdf -> onHasExtraDataType(ExtraDataType.Pdf) else -> onHasExtraDataType(ExtraDataType.File) } onGetUris(listOf(it)) } } Intent.ACTION_VIEW -> { val uris = clipData?.clipList() ?: data?.let { listOf(it) } ?: listOfNotNull(intent.parcelable(Intent.EXTRA_STREAM)) if (uris.size == 1) { val uri = uris.first() if (uri.isMarkupProject()) { onNavigate(Screen.MarkupLayers(uri)) return } if (uri.toString().contains(BACKUP_FILE_EXT, true)) { onHasExtraDataType(ExtraDataType.Backup(uri.toString())) return } when { isPdf -> { onNavigate(Screen.PdfTools.Preview(uri)) return } isAudio -> { onHasExtraDataType(ExtraDataType.Audio) } else -> { onHasExtraDataType(ExtraDataType.File) } } onGetUris(uris) } else if (uris.isNotEmpty()) { when { isPdf -> { onHasExtraDataType(ExtraDataType.Pdf) onGetUris(uris) } isAudio -> { onHasExtraDataType(ExtraDataType.Audio) onGetUris(uris) } else -> { onNavigate(Screen.Zip(uris)) } } } else { Unit } } else -> null } ?: AppToastHost.showToast( message = appContext.getString(R.string.unsupported_type, type), icon = Icons.Rounded.ErrorOutline ) } } else Unit }.getOrNull() ?: AppToastHost.showToast( message = appContext.getString(R.string.something_went_wrong), icon = Icons.Rounded.ErrorOutline ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/HandlerUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.os.Handler import android.os.Looper fun mainLooperDelayedAction( delay: Long, action: () -> Unit ) = Handler(Looper.getMainLooper()).postDelayed(action, delay) fun mainLooperAction( action: () -> Unit ) = Handler(Looper.getMainLooper()).post(action) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ImageUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.graphics.pdf.PdfRenderer import android.net.Uri import android.os.Build import android.os.Build.VERSION.SDK_INT import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.key import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.core.graphics.BitmapCompat import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.scale import androidx.core.text.isDigitsOnly import coil3.Image import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.utils.FileMode import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getStringLocalized import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.fileSize import java.util.Locale object ImageUtils { fun Drawable.toBitmap(): Bitmap = toBitmap(config = getSuitableConfig()) fun MetadataTag.localizedName( context: Context, locale: Locale? = null ): String { val resId = titleResId return locale?.let { context.getStringLocalized( resId = resId, locale = locale ) } ?: context.getString(resId) } private val MetadataTag.titleResId: Int @StringRes get() = when (this) { is MetadataTag.BitsPerSample -> R.string.tag_bits_per_sample is MetadataTag.Compression -> R.string.tag_compression is MetadataTag.PhotometricInterpretation -> R.string.tag_photometric_interpretation is MetadataTag.SamplesPerPixel -> R.string.tag_samples_per_pixel is MetadataTag.PlanarConfiguration -> R.string.tag_planar_configuration is MetadataTag.YCbCrSubSampling -> R.string.tag_y_cb_cr_sub_sampling is MetadataTag.YCbCrPositioning -> R.string.tag_y_cb_cr_positioning is MetadataTag.XResolution -> R.string.tag_x_resolution is MetadataTag.YResolution -> R.string.tag_y_resolution is MetadataTag.ResolutionUnit -> R.string.tag_resolution_unit is MetadataTag.StripOffsets -> R.string.tag_strip_offsets is MetadataTag.RowsPerStrip -> R.string.tag_rows_per_strip is MetadataTag.StripByteCounts -> R.string.tag_strip_byte_counts is MetadataTag.JpegInterchangeFormat -> R.string.tag_jpeg_interchange_format is MetadataTag.JpegInterchangeFormatLength -> R.string.tag_jpeg_interchange_format_length is MetadataTag.TransferFunction -> R.string.tag_transfer_function is MetadataTag.WhitePoint -> R.string.tag_white_point is MetadataTag.PrimaryChromaticities -> R.string.tag_primary_chromaticities is MetadataTag.YCbCrCoefficients -> R.string.tag_y_cb_cr_coefficients is MetadataTag.ReferenceBlackWhite -> R.string.tag_reference_black_white is MetadataTag.Datetime -> R.string.tag_datetime is MetadataTag.ImageDescription -> R.string.tag_image_description is MetadataTag.Make -> R.string.tag_make is MetadataTag.Model -> R.string.tag_model is MetadataTag.Software -> R.string.tag_software is MetadataTag.Artist -> R.string.tag_artist is MetadataTag.Copyright -> R.string.tag_copyright is MetadataTag.ExifVersion -> R.string.tag_exif_version is MetadataTag.FlashpixVersion -> R.string.tag_flashpix_version is MetadataTag.ColorSpace -> R.string.tag_color_space is MetadataTag.Gamma -> R.string.tag_gamma is MetadataTag.PixelXDimension -> R.string.tag_pixel_x_dimension is MetadataTag.PixelYDimension -> R.string.tag_pixel_y_dimension is MetadataTag.CompressedBitsPerPixel -> R.string.tag_compressed_bits_per_pixel is MetadataTag.MakerNote -> R.string.tag_maker_note is MetadataTag.UserComment -> R.string.tag_user_comment is MetadataTag.RelatedSoundFile -> R.string.tag_related_sound_file is MetadataTag.DatetimeOriginal -> R.string.tag_datetime_original is MetadataTag.DatetimeDigitized -> R.string.tag_datetime_digitized is MetadataTag.OffsetTime -> R.string.tag_offset_time is MetadataTag.OffsetTimeOriginal -> R.string.tag_offset_time_original is MetadataTag.OffsetTimeDigitized -> R.string.tag_offset_time_digitized is MetadataTag.SubsecTime -> R.string.tag_subsec_time is MetadataTag.SubsecTimeOriginal -> R.string.tag_subsec_time_original is MetadataTag.SubsecTimeDigitized -> R.string.tag_subsec_time_digitized is MetadataTag.ExposureTime -> R.string.tag_exposure_time is MetadataTag.FNumber -> R.string.tag_f_number is MetadataTag.ExposureProgram -> R.string.tag_exposure_program is MetadataTag.SpectralSensitivity -> R.string.tag_spectral_sensitivity is MetadataTag.PhotographicSensitivity -> R.string.tag_photographic_sensitivity is MetadataTag.Oecf -> R.string.tag_oecf is MetadataTag.SensitivityType -> R.string.tag_sensitivity_type is MetadataTag.StandardOutputSensitivity -> R.string.tag_standard_output_sensitivity is MetadataTag.RecommendedExposureIndex -> R.string.tag_recommended_exposure_index is MetadataTag.IsoSpeed -> R.string.tag_iso_speed is MetadataTag.IsoSpeedLatitudeYyy -> R.string.tag_iso_speed_latitude_yyy is MetadataTag.IsoSpeedLatitudeZzz -> R.string.tag_iso_speed_latitude_zzz is MetadataTag.ShutterSpeedValue -> R.string.tag_shutter_speed_value is MetadataTag.ApertureValue -> R.string.tag_aperture_value is MetadataTag.BrightnessValue -> R.string.tag_brightness_value is MetadataTag.ExposureBiasValue -> R.string.tag_exposure_bias_value is MetadataTag.MaxApertureValue -> R.string.tag_max_aperture_value is MetadataTag.SubjectDistance -> R.string.tag_subject_distance is MetadataTag.MeteringMode -> R.string.tag_metering_mode is MetadataTag.Flash -> R.string.tag_flash is MetadataTag.SubjectArea -> R.string.tag_subject_area is MetadataTag.FocalLength -> R.string.tag_focal_length is MetadataTag.FlashEnergy -> R.string.tag_flash_energy is MetadataTag.SpatialFrequencyResponse -> R.string.tag_spatial_frequency_response is MetadataTag.FocalPlaneXResolution -> R.string.tag_focal_plane_x_resolution is MetadataTag.FocalPlaneYResolution -> R.string.tag_focal_plane_y_resolution is MetadataTag.FocalPlaneResolutionUnit -> R.string.tag_focal_plane_resolution_unit is MetadataTag.SubjectLocation -> R.string.tag_subject_location is MetadataTag.ExposureIndex -> R.string.tag_exposure_index is MetadataTag.SensingMethod -> R.string.tag_sensing_method is MetadataTag.FileSource -> R.string.tag_file_source is MetadataTag.CfaPattern -> R.string.tag_cfa_pattern is MetadataTag.CustomRendered -> R.string.tag_custom_rendered is MetadataTag.ExposureMode -> R.string.tag_exposure_mode is MetadataTag.WhiteBalance -> R.string.tag_white_balance is MetadataTag.DigitalZoomRatio -> R.string.tag_digital_zoom_ratio is MetadataTag.FocalLengthIn35mmFilm -> R.string.tag_focal_length_in_35mm_film is MetadataTag.SceneCaptureType -> R.string.tag_scene_capture_type is MetadataTag.GainControl -> R.string.tag_gain_control is MetadataTag.Contrast -> R.string.tag_contrast is MetadataTag.Saturation -> R.string.tag_saturation is MetadataTag.Sharpness -> R.string.tag_sharpness is MetadataTag.DeviceSettingDescription -> R.string.tag_device_setting_description is MetadataTag.SubjectDistanceRange -> R.string.tag_subject_distance_range is MetadataTag.ImageUniqueId -> R.string.tag_image_unique_id is MetadataTag.CameraOwnerName -> R.string.tag_camera_owner_name is MetadataTag.BodySerialNumber -> R.string.tag_body_serial_number is MetadataTag.LensSpecification -> R.string.tag_lens_specification is MetadataTag.LensMake -> R.string.tag_lens_make is MetadataTag.LensModel -> R.string.tag_lens_model is MetadataTag.LensSerialNumber -> R.string.tag_lens_serial_number is MetadataTag.GpsVersionId -> R.string.tag_gps_version_id is MetadataTag.GpsLatitudeRef -> R.string.tag_gps_latitude_ref is MetadataTag.GpsLatitude -> R.string.tag_gps_latitude is MetadataTag.GpsLongitudeRef -> R.string.tag_gps_longitude_ref is MetadataTag.GpsLongitude -> R.string.tag_gps_longitude is MetadataTag.GpsAltitudeRef -> R.string.tag_gps_altitude_ref is MetadataTag.GpsAltitude -> R.string.tag_gps_altitude is MetadataTag.GpsTimestamp -> R.string.tag_gps_timestamp is MetadataTag.GpsSatellites -> R.string.tag_gps_satellites is MetadataTag.GpsStatus -> R.string.tag_gps_status is MetadataTag.GpsMeasureMode -> R.string.tag_gps_measure_mode is MetadataTag.GpsDop -> R.string.tag_gps_dop is MetadataTag.GpsSpeedRef -> R.string.tag_gps_speed_ref is MetadataTag.GpsSpeed -> R.string.tag_gps_speed is MetadataTag.GpsTrackRef -> R.string.tag_gps_track_ref is MetadataTag.GpsTrack -> R.string.tag_gps_track is MetadataTag.GpsImgDirectionRef -> R.string.tag_gps_img_direction_ref is MetadataTag.GpsImgDirection -> R.string.tag_gps_img_direction is MetadataTag.GpsMapDatum -> R.string.tag_gps_map_datum is MetadataTag.GpsDestLatitudeRef -> R.string.tag_gps_dest_latitude_ref is MetadataTag.GpsDestLatitude -> R.string.tag_gps_dest_latitude is MetadataTag.GpsDestLongitudeRef -> R.string.tag_gps_dest_longitude_ref is MetadataTag.GpsDestLongitude -> R.string.tag_gps_dest_longitude is MetadataTag.GpsDestBearingRef -> R.string.tag_gps_dest_bearing_ref is MetadataTag.GpsDestBearing -> R.string.tag_gps_dest_bearing is MetadataTag.GpsDestDistanceRef -> R.string.tag_gps_dest_distance_ref is MetadataTag.GpsDestDistance -> R.string.tag_gps_dest_distance is MetadataTag.GpsProcessingMethod -> R.string.tag_gps_processing_method is MetadataTag.GpsAreaInformation -> R.string.tag_gps_area_information is MetadataTag.GpsDatestamp -> R.string.tag_gps_datestamp is MetadataTag.GpsDifferential -> R.string.tag_gps_differential is MetadataTag.GpsHPositioningError -> R.string.tag_gps_h_positioning_error is MetadataTag.InteroperabilityIndex -> R.string.tag_interoperability_index is MetadataTag.DngVersion -> R.string.tag_dng_version is MetadataTag.DefaultCropSize -> R.string.tag_default_crop_size is MetadataTag.OrfPreviewImageStart -> R.string.tag_orf_preview_image_start is MetadataTag.OrfPreviewImageLength -> R.string.tag_orf_preview_image_length is MetadataTag.OrfAspectFrame -> R.string.tag_orf_aspect_frame is MetadataTag.Rw2SensorBottomBorder -> R.string.tag_rw2_sensor_bottom_border is MetadataTag.Rw2SensorLeftBorder -> R.string.tag_rw2_sensor_left_border is MetadataTag.Rw2SensorRightBorder -> R.string.tag_rw2_sensor_right_border is MetadataTag.Rw2SensorTopBorder -> R.string.tag_rw2_sensor_top_border is MetadataTag.Rw2Iso -> R.string.tag_rw2_iso } val MetadataTag.localizedName: String @Composable get() { val context = LocalContext.current return remember(this, context) { localizedName(context) } } private val possibleConfigs = mutableListOf().apply { if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { add(Bitmap.Config.RGBA_1010102) } if (SDK_INT >= Build.VERSION_CODES.O) { add(Bitmap.Config.RGBA_F16) } add(Bitmap.Config.ARGB_8888) add(Bitmap.Config.RGB_565) } fun getSuitableConfig( image: Bitmap? = null ): Bitmap.Config = image?.config?.takeIf { it in possibleConfigs } ?: Bitmap.Config.ARGB_8888 @Composable fun rememberFileSize(uri: Uri?): Long { return remember(uri) { derivedStateOf { uri?.fileSize() ?: 0L } }.value } @Composable fun rememberHumanFileSize(uri: Uri): String { val size = rememberFileSize(uri) return remember(size, uri) { derivedStateOf { humanFileSize(size) } }.value } @Composable fun rememberPdfPages(uri: Uri?): State = key(uri) { produceState(0) { if (uri == null) { value = 0 return@produceState } val pageCount = runCatching { appContext .contentResolver .openFileDescriptor(uri, FileMode.Read.mode) ?.use { fd -> PdfRenderer(fd).use { it.pageCount } } ?: 0 }.getOrDefault(0) value = pageCount } } @Composable fun rememberHumanFileSize( byteCount: Long ): String { return remember(byteCount) { derivedStateOf { humanFileSize( bytes = byteCount ) } }.value } object Dimens { const val MAX_IMAGE_SIZE = 8388607 * 16 } fun String.restrict(with: Int): String { if (isEmpty()) return this return if ((this.toIntOrNull() ?: 0) >= with) with.toString() else if (this.isDigitsOnly() && (this.toIntOrNull() == null)) "" else this.trim() .filter { !listOf('-', '.', ',', ' ', "\n").contains(it) } } fun Bitmap.createScaledBitmap( width: Int, height: Int ): Bitmap { if (width == this.width && height == this.height) return this return if (width < this.width && height < this.height) { BitmapCompat.createScaledBitmap( this, width, height, null, true ) } else { this.scale(width, height) } } fun ImageInfo.haveChanges(original: Bitmap?): Boolean { if (original == null) return false return quality.qualityValue != 100 || rotationDegrees != 0f || isFlipped || width != original.width || height != original.height } val Bitmap.aspectRatio: Float get() = width / height.toFloat() val ImageBitmap.aspectRatio: Float get() = width / height.toFloat() val Drawable.aspectRatio: Float get() = intrinsicWidth / intrinsicHeight.toFloat() val Image.aspectRatio: Float get() = width / height.toFloat() val Bitmap.safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) val ImageBitmap.safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) val Image.safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) val Drawable.safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) val Bitmap.Config.isHardware: Boolean get() = SDK_INT >= 26 && this == Bitmap.Config.HARDWARE fun Bitmap.Config?.toSoftware(): Bitmap.Config { return if (this == null || isHardware) Bitmap.Config.ARGB_8888 else this } fun Bitmap.flexibleScale( max: Int, filter: Boolean = true ): Bitmap { return runCatching { if (height >= width) { val aspectRatio = aspectRatio val targetWidth = (max * aspectRatio).toInt() scale(targetWidth, max, filter) } else { val aspectRatio = 1f / aspectRatio val targetHeight = (max * aspectRatio).toInt() scale(max, targetHeight, filter) } }.getOrNull() ?: this } fun Bitmap.applyPadding(padding: Int, paddingColor: Color = Color.White): Bitmap { val newWidth = this.width + padding * 2 val newHeight = this.height + padding * 2 return createBitmap(newWidth, newHeight, getSuitableConfig(this)).applyCanvas { drawColor(paddingColor.toArgb()) drawBitmap(this@applyPadding, padding.toFloat(), padding.toFloat(), null) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/IntentUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.Intent import android.os.Parcelable import androidx.core.content.IntentCompat object IntentUtils { inline fun Intent.parcelable(key: String): T? = runCatching { IntentCompat.getParcelableExtra(this, key, T::class.java) }.getOrNull() inline fun Intent.parcelableArrayList(key: String): ArrayList? = runCatching { IntentCompat.getParcelableArrayListExtra(this, key, T::class.java) }.getOrNull() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LazyUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.SideEffect import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @Composable fun LazyListState.isScrollingUp(): Boolean { var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } return remember(this) { derivedStateOf { if (previousIndex != firstVisibleItemIndex) { previousIndex > firstVisibleItemIndex } else { previousScrollOffset >= firstVisibleItemScrollOffset }.also { previousIndex = firstVisibleItemIndex previousScrollOffset = firstVisibleItemScrollOffset } } }.value } @Composable fun ScrollState.isScrollingUp(enabled: Boolean = true): Boolean { var previousScrollOffset by remember(this) { mutableIntStateOf(value) } return remember(this, enabled) { derivedStateOf { if (enabled) { (previousScrollOffset >= value).also { previousScrollOffset = value } } else true } }.value } @Composable fun LazyStaggeredGridState.isScrollingUp(enabled: Boolean = true): Boolean { var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } return remember(this, enabled) { derivedStateOf { if (enabled) { if (previousIndex != firstVisibleItemIndex) { previousIndex > firstVisibleItemIndex } else { previousScrollOffset >= firstVisibleItemScrollOffset }.also { previousIndex = firstVisibleItemIndex previousScrollOffset = firstVisibleItemScrollOffset } } else true } }.value } @Composable fun rememberCurrentOffset(state: LazyStaggeredGridState): State { val position = remember { derivedStateOf { state.firstVisibleItemIndex } } val itemOffset = remember { derivedStateOf { state.firstVisibleItemScrollOffset } } val lastPosition = rememberPrevious(position.value) val lastItemOffset = rememberPrevious(itemOffset.value) val currentOffset = remember { mutableIntStateOf(0) } LaunchedEffect(position.value, itemOffset.value) { if (lastPosition == null || position.value == 0) { currentOffset.intValue = itemOffset.value } else if (lastPosition == position.value) { currentOffset.intValue += (itemOffset.value - (lastItemOffset ?: 0)) } else if (lastPosition > position.value) { currentOffset.intValue -= (lastItemOffset ?: 0) } else { // lastPosition.value < position.value currentOffset.intValue += itemOffset.value } } return currentOffset } @Composable fun rememberPrevious( current: T, shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b }, ): T? { val ref = rememberRef() // launched after render, so the current render will have the old value anyway SideEffect { if (shouldUpdate(ref.value, current)) { ref.value = current } } return ref.value } /** * Returns a dummy MutableState that does not cause render when setting it */ @Composable fun rememberRef(): MutableState { // for some reason it always recreated the value with vararg keys, // leaving out the keys as a parameter for remember for now return remember { object : MutableState { override var value: T? = null override fun component1(): T? = value override fun component2(): (T?) -> Unit = { value = it } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LinkUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import com.t8rin.imagetoolbox.core.domain.USER_AGENT import com.t8rin.imagetoolbox.core.domain.remote.Cache import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import org.jsoup.Jsoup import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds object LinkUtils { fun parseLinks(text: String): Set { val regex = Regex("""\b(?:https?://|www\.|http?://)\S+\b""") val matches = regex.findAll(text) return matches.map { it.value }.toSet() } } @ConsistentCopyVisibility data class LinkPreview internal constructor( val title: String?, val description: String?, val image: String?, val url: String?, val link: String? ) { companion object { fun empty(link: String) = LinkPreview( link = link, image = null, title = null, description = null, url = null ) } } suspend fun fetchLinkPreview( link: String ): LinkPreview = linksCache.call(link) { withContext(Dispatchers.IO) { var image: String? = null var title: String? = null var description: String? = null var url: String? = null runSuspendCatching { withTimeoutOrNull(30.seconds) { Jsoup .connect(link) .userAgent(USER_AGENT) .execute() .parse() .getElementsByTag("meta") .forEach { element -> when (element.attr("property")) { "og:image" -> image = element.attr("content") "og:title" -> title = element.attr("content") "og:description" -> description = element.attr("content") "og:url" -> url = element.attr("content") } } } } LinkPreview( link = link, image = image, title = title, description = description, url = url ) } } private val linksCache = Cache(1.minutes) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocalFilterPreviewModel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.graphics.Bitmap import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.flexibleScale val LocalFilterPreviewModelProvider = compositionLocalOf { error("FilterPreviewProvider not present") } @Composable fun rememberFilterPreviewProvider( preview: ImageModel, canSetDynamicFilterPreview: Boolean ): FilterPreviewProvider { return remember(preview) { FilterPreviewProviderImpl( default = preview, canSetDynamicFilterPreview = canSetDynamicFilterPreview ) }.also { LaunchedEffect(it, canSetDynamicFilterPreview) { it._canSetDynamicFilterPreview.value = canSetDynamicFilterPreview } } } interface FilterPreviewProvider { val canSetDynamicFilterPreview: Boolean val preview: ImageModel @Composable fun ProvidePreview(preview: Any?) } private class FilterPreviewProviderImpl( private val default: ImageModel, canSetDynamicFilterPreview: Boolean ) : FilterPreviewProvider { private val _preview = mutableStateOf(default) override val preview: ImageModel by _preview val _canSetDynamicFilterPreview = mutableStateOf(canSetDynamicFilterPreview) override val canSetDynamicFilterPreview by _canSetDynamicFilterPreview private var updatesCount: Int = 0 override fun toString(): String { return "FilterPreviewProviderImpl(preview = $preview, canSetDynamicFilterPreview = $canSetDynamicFilterPreview, updatesCount = $updatesCount)" } @Composable override fun ProvidePreview(preview: Any?) { DisposableEffect(Unit) { onDispose { _preview.value = default } } LaunchedEffect(preview, canSetDynamicFilterPreview) { updatesCount++ _preview.value = if (canSetDynamicFilterPreview) { when (preview) { is ImageModel -> preview is Bitmap -> ImageModel(preview.flexibleScale(300)) is Any -> ImageModel(preview) else -> default } } else { default } } } } @Composable fun ProvideFilterPreview(preview: Any?) { LocalFilterPreviewModelProvider.current.ProvidePreview(preview) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/LocaleConfigCompat.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.app.LocaleConfig import android.content.Context import android.content.res.XmlResourceParser import android.os.Build import androidx.annotation.RequiresApi import androidx.annotation.XmlRes import androidx.core.content.res.ResourcesCompat import androidx.core.os.LocaleListCompat import com.t8rin.logger.makeLog import org.xmlpull.v1.XmlPullParser import java.io.FileNotFoundException import java.util.Locale /** * @see android.app.LocaleConfig */ class LocaleConfigCompat(context: Context) { var status = 0 private set var supportedLocales: LocaleListCompat? = null private set init { val impl = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Api33Impl(context) } else { Api21Impl(context) } status = impl.status supportedLocales = impl.supportedLocales } companion object { /** * Succeeded reading the LocaleConfig structure stored in an XML file. */ const val STATUS_SUCCESS = 0 /** * No android:localeConfig tag on . */ const val STATUS_NOT_SPECIFIED = 1 /** * Malformed input in the XML file where the LocaleConfig was stored. */ const val STATUS_PARSING_FAILED = 2 } private abstract class Impl { abstract val status: Int abstract val supportedLocales: LocaleListCompat? } private class Api21Impl(context: Context) : Impl() { override var status = 0 override var supportedLocales: LocaleListCompat? = null init { val resourceId = try { getLocaleConfigResourceId(context) } catch (e: Throwable) { "The resource file pointed to by the given resource ID isn't found.".makeLog(TAG) ResourcesCompat.ID_NULL } if (resourceId == ResourcesCompat.ID_NULL) { status = STATUS_NOT_SPECIFIED } else { val resources = context.resources try { supportedLocales = resources.getXml(resourceId).use { parseLocaleConfig(it) } status = STATUS_SUCCESS } catch (e: Throwable) { val resourceEntryName = resources.getResourceEntryName(resourceId) "Failed to parse XML configuration from $resourceEntryName".makeLog(TAG) status = STATUS_PARSING_FAILED } } } // @see com.android.server.pm.pkg.parsing.ParsingPackageUtils @XmlRes private fun getLocaleConfigResourceId(context: Context): Int { // Java cookies starts at 1, while passing 0 (invalid cookie for Java) makes // AssetManager pick the last asset containing such a file name. // We should go over all the assets containing AndroidManifest.xml, however there's no // API to do that, so the best we can do is to start from the first asset and iterate // until we can't find the next asset containing AndroidManifest.xml. var cookie = 1 var isAndroidManifestFound = false while (true) { val parser = try { context.assets.openXmlResourceParser(cookie, FILE_NAME_ANDROID_MANIFEST) } catch (_: FileNotFoundException) { if (!isAndroidManifestFound) { ++cookie continue } else { break } } isAndroidManifestFound = true parser.use { do { if (parser.eventType != XmlPullParser.START_TAG) { continue } if (parser.name != TAG_MANIFEST) { parser.skipCurrentTag() continue } if (parser.getAttributeValue(null, ATTR_PACKAGE) != context.packageName) { break } while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { continue } if (parser.name != TAG_APPLICATION) { parser.skipCurrentTag() continue } return parser.getAttributeResourceValue( NAMESPACE_ANDROID, ATTR_LOCALE_CONFIG, ResourcesCompat.ID_NULL ) } } while (parser.next() != XmlPullParser.END_DOCUMENT) } ++cookie } return ResourcesCompat.ID_NULL } private fun parseLocaleConfig(parser: XmlResourceParser): LocaleListCompat { val localeNames = mutableSetOf() do { if (parser.eventType != XmlPullParser.START_TAG) { continue } if (parser.name != TAG_LOCALE_CONFIG) { parser.skipCurrentTag() continue } while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { continue } if (parser.name != TAG_LOCALE) { parser.skipCurrentTag() continue } localeNames += parser.getAttributeValue(NAMESPACE_ANDROID, ATTR_NAME) parser.skipCurrentTag() } } while (parser.next() != XmlPullParser.END_DOCUMENT) return LocaleListCompat.forLanguageTags(localeNames.joinToString(",")) } private fun XmlPullParser.skipCurrentTag() { val outerDepth = depth var type: Int do { type = next() } while (type != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || depth > outerDepth) ) } companion object { private const val TAG = "LocaleConfigCompat" private const val FILE_NAME_ANDROID_MANIFEST = "AndroidManifest.xml" private const val TAG_APPLICATION = "application" private const val TAG_LOCALE_CONFIG = "locale-config" private const val TAG_LOCALE = "locale" private const val TAG_MANIFEST = "manifest" private const val NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android" private const val ATTR_LOCALE_CONFIG = "localeConfig" private const val ATTR_NAME = "name" private const val ATTR_PACKAGE = "package" } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) private class Api33Impl(context: Context) : Impl() { override var status: Int = 0 override var supportedLocales: LocaleListCompat? = null init { val platformLocaleConfig = LocaleConfig(context) status = platformLocaleConfig.status supportedLocales = platformLocaleConfig.supportedLocales ?.let { LocaleListCompat.wrap(it) } } } } fun LocaleListCompat.toList(): List = List(size()) { this[it]!! } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PaddingUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.content.res.Configuration import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalInspectionMode import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalWindowSizeClass @Composable fun isPortraitOrientationAsState(): State { if (LocalInspectionMode.current) return remember { mutableStateOf(false) } val configuration = LocalConfiguration.current val sizeClass = LocalWindowSizeClass.current return remember(configuration, sizeClass) { derivedStateOf { configuration.orientation != Configuration.ORIENTATION_LANDSCAPE || sizeClass.widthSizeClass == WindowWidthSizeClass.Compact } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/PredictiveBackObserver.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.activity.compose.PredictiveBackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import kotlinx.coroutines.launch import kotlin.coroutines.cancellation.CancellationException @Composable fun PredictiveBackObserver( onProgress: (Float) -> Unit, onClean: suspend (isCompleted: Boolean) -> Unit, enabled: Boolean = true ) { val scope = rememberCoroutineScope() if (!enabled) return PredictiveBackHandler { progress -> try { progress.collect { event -> if (event.progress <= 0.05f) { onClean(false) } onProgress(event.progress) } scope.launch { onClean(true) } } catch (_: CancellationException) { onClean(false) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Preview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.compose.ui.tooling.preview.Preview @Preview( name = "Preview", locale = "en" ) annotation class EnPreview @Preview(heightDp = 300, widthDp = 800, locale = "en") annotation class EnPreviewLandscape ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Rect.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.compose.ui.geometry.Rect import com.t8rin.imagetoolbox.core.domain.model.RectModel fun Rect.toModel() = RectModel( left = left, top = top, right = right, bottom = bottom ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.app.Activity import com.t8rin.logger.makeLog import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.receiveAsFlow abstract class ReviewHandler { private val _reviewRequests: Channel = Channel(Channel.BUFFERED) val reviewRequests: Flow = _reviewRequests.receiveAsFlow() fun requestReview() { makeLog("requestReview") _reviewRequests.trySend(Unit) } open val showNotShowAgainButton: Boolean = false open fun notShowReviewAgain() = Unit abstract fun showReview(activity: Activity) companion object { val current: ReviewHandler = ReviewHandlerImpl } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/Ripple.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import androidx.compose.foundation.Indication import androidx.compose.material.LocalContentColor import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.unit.Dp @Composable fun rememberRipple( bounded: Boolean = true, radius: Dp = Dp.Unspecified, contentColor: Color = Color.Unspecified ): Indication { val contentColor = contentColor.takeOrElse { LocalContentColor.current } return remember(bounded, radius) { ripple( color = { contentColor }, bounded = bounded, radius = radius ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SafeUriHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.app.Activity import android.content.Intent import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.platform.UriHandler import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.getString @Composable fun rememberSafeUriHandler(): UriHandler { val activity = LocalActivity.current return remember(activity) { SafeUriHandler( activity = activity ) } } @Stable @Immutable private class SafeUriHandler( private val activity: Activity? ) : UriHandler { override fun openUri(uri: String) { tryActions( first = { rawOpenUri(uri) }, second = { val trimmed = uri.trim() val modifiedUrl = when { trimmed.startsWith(WWW, ignoreCase = true) -> trimmed.replace(WWW, HTTPS) !trimmed.startsWith(HTTP) && !trimmed.startsWith(HTTPS) -> HTTPS + trimmed else -> trimmed } rawOpenUri(modifiedUrl) }, onFailure = { AppToastHost.showFailureToast(getString(R.string.cannot_open_uri, uri)) } ) } private fun rawOpenUri(uri: String) { activity?.startActivity( Intent(Intent.ACTION_VIEW, uri.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ) } private fun tryActions( first: () -> Unit, second: () -> Unit, onFailure: () -> Unit ) { runCatching(first).onSuccess { return } runCatching(second).onSuccess { return } onFailure() } fun asUnsafe(): UriHandler = object : UriHandler { override fun openUri(uri: String) = rawOpenUri(uri) } } fun UriHandler.asUnsafe(): UriHandler = if (this is SafeUriHandler) asUnsafe() else this private const val WWW = "www." private const val HTTPS = "https://" private const val HTTP = "http://" ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/SaveResultHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:SuppressLint("StringFormatMatches") package com.t8rin.imagetoolbox.core.ui.utils.helper import android.annotation.SuppressLint import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.rounded.Save import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.firstOfType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.logger.makeLog interface SaveResultHandler { fun parseSaveResult(saveResult: SaveResult) fun parseFileSaveResult(saveResult: SaveResult) fun parseSaveResults(results: List) } internal object SaveResultHandlerImpl : SaveResultHandler { override fun parseSaveResult(saveResult: SaveResult) { when (saveResult) { is SaveResult.Error.Exception -> { saveResult.throwable.makeLog("parseSaveResult") AppToastHost.showFailureToast( throwable = saveResult.throwable ) } is SaveResult.Skipped -> { AppToastHost.showToast( message = getString(R.string.skipped_saving), icon = Icons.Outlined.Info, duration = ToastDuration.Short ) } is SaveResult.Success -> { saveResult.message?.let { AppToastHost.showToast( message = it, icon = Icons.Rounded.Save, duration = ToastDuration.Long ) } AppToastHost.showConfetti() ReviewHandler.current.requestReview() } SaveResult.Error.MissingPermissions -> AppToastHost.showToast(AppToastHost.PERMISSION) } } override fun parseFileSaveResult(saveResult: SaveResult) { when (saveResult) { is SaveResult.Error.Exception -> { AppToastHost.showFailureToast( throwable = saveResult.throwable ) } is SaveResult.Skipped -> { AppToastHost.showToast( message = getString(R.string.skipped_saving), icon = Icons.Outlined.Info ) } is SaveResult.Success -> { AppToastHost.showToast( message = getString(R.string.saved_to_without_filename, ""), icon = Icons.Rounded.Save ) AppToastHost.showConfetti() ReviewHandler.current.requestReview() } SaveResult.Error.MissingPermissions -> AppToastHost.showToast(AppToastHost.PERMISSION) } } override fun parseSaveResults(results: List) { if (results.size == 1) { return parseSaveResult( saveResult = results.first() ) } if (results.any { it == SaveResult.Error.MissingPermissions }) { AppToastHost.showToast(AppToastHost.PERMISSION) return } val skipped = results.count { it is SaveResult.Skipped } val failed = results.count { it is SaveResult.Error } val done = results.count { it is SaveResult.Success } if (failed == 0 && done > 0) { if (done == 1) { val saveResult = results.firstOfType() val savingPath = saveResult?.savingPath ?: getString(R.string.default_folder) AppToastHost.showToast( message = saveResult?.message ?: getString( R.string.saved_to_without_filename, savingPath ), icon = Icons.Rounded.Save, duration = ToastDuration.Long ) } else { val saveResult = results.firstOfType() if (saveResult?.isOverwritten == true) { AppToastHost.showToast( message = getString(R.string.images_overwritten), icon = Icons.Rounded.Save, duration = ToastDuration.Long ) } else { val savingPath = saveResult?.savingPath ?: getString(R.string.default_folder) AppToastHost.showToast( message = getString( R.string.saved_to_without_filename, savingPath ), icon = Icons.Rounded.Save, duration = ToastDuration.Long ) } } if (skipped > 0) { AppToastHost.showToast( message = getString(R.string.skipped_saving_multiple, skipped), icon = Icons.Outlined.Info, duration = ToastDuration.Short ) } AppToastHost.showConfetti() ReviewHandler.current.requestReview() return } if (failed > 0) { val saveResult = results.firstOfType() val errorSaveResult = results.firstOfType() if (done > 0) { AppToastHost.showToast( message = saveResult?.message ?: getString( R.string.saved_to_without_filename, saveResult?.savingPath ), icon = Icons.Rounded.Save, duration = ToastDuration.Long ) } AppToastHost.showFailureToast(getString(R.string.failed_to_save, failed)) AppToastHost.showToast( message = getString( R.string.smth_went_wrong, errorSaveResult?.throwable?.localizedMessage ?: "" ) ) if (skipped > 0) { AppToastHost.showToast( message = getString(R.string.skipped_saving_multiple, skipped), icon = Icons.Outlined.Info, duration = ToastDuration.Short ) } return } if (skipped > 0 && done == 0 && failed == 0) { AppToastHost.showToast( message = getString(R.string.skipped_saving_multiple, skipped), icon = Icons.Outlined.Info, duration = ToastDuration.Short ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/ScanResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.net.Uri data class ScanResult( val imageUris: List = emptyList(), val pdfUri: Uri? = null ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/DrawCache.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmapConfig import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.toSize /** * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] * which can be drawn directly in another [DrawScope] instance. This is useful to cache * complicated drawing commands across frames especially if the content has not changed. * Additionally some drawing operations such as rendering paths are done purely in * software so it is beneficial to cache the result and render the contents * directly through a texture as done by [DrawScope.drawImage] */ internal class DrawCache { @PublishedApi internal var mCachedImage: ImageBitmap? = null private var cachedCanvas: Canvas? = null private var scopeDensity: Density? = null private var layoutDirection: LayoutDirection = LayoutDirection.Ltr private var size: IntSize = IntSize.Zero private var config: ImageBitmapConfig = ImageBitmapConfig.Argb8888 private val cacheScope = CanvasDrawScope() /** * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided * size. If the same size is provided across calls, the same [ImageBitmap] instance is * re-used and the contents are cleared out before drawing content in it again */ fun drawCachedImage( config: ImageBitmapConfig, size: IntSize, density: Density, layoutDirection: LayoutDirection, block: DrawScope.() -> Unit ) { this.scopeDensity = density this.layoutDirection = layoutDirection var targetImage = mCachedImage var targetCanvas = cachedCanvas if (targetImage == null || targetCanvas == null || size.width > targetImage.width || size.height > targetImage.height || this.config != config ) { targetImage = ImageBitmap(size.width, size.height, config = config) targetCanvas = Canvas(targetImage) mCachedImage = targetImage cachedCanvas = targetCanvas this.config = config } this.size = size cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) { clear() block() } targetImage.prepareToDraw() } /** * Draw the cached content into the provided [DrawScope] instance */ fun drawInto( target: DrawScope, alpha: Float = 1.0f, colorFilter: ColorFilter? = null ) { val targetImage = mCachedImage require(targetImage != null) { "drawCachedImage must be invoked first before attempting to draw the result " + "into another destination" } target.drawImage(targetImage, srcSize = size, alpha = alpha, colorFilter = colorFilter) } /** * Helper method to clear contents of the draw environment from the given bounds of the * DrawScope */ private fun DrawScope.clear() { drawRect(color = Color.Black, blendMode = BlendMode.Clear) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/GroupComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.withTransform import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.isUnspecified import androidx.compose.ui.graphics.vector.DefaultGroupName import androidx.compose.ui.graphics.vector.DefaultPivotX import androidx.compose.ui.graphics.vector.DefaultPivotY import androidx.compose.ui.graphics.vector.DefaultRotation import androidx.compose.ui.graphics.vector.DefaultScaleX import androidx.compose.ui.graphics.vector.DefaultScaleY import androidx.compose.ui.graphics.vector.DefaultTranslationX import androidx.compose.ui.graphics.vector.DefaultTranslationY import androidx.compose.ui.graphics.vector.EmptyPath import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.VectorGroup import androidx.compose.ui.graphics.vector.VectorPath import androidx.compose.ui.graphics.vector.toPath import androidx.compose.ui.util.fastForEach internal class GroupComponent : VNode() { private var groupMatrix: Matrix? = null private val children = mutableListOf() /** * Flag to determine if the contents of this group can be rendered with a single color * This is true if all the paths and groups within this group can be rendered with the * same color */ var isTintable = true private set /** * Tint color to render all the contents of this group. This is configured only if all the * contents within the group are the same color */ var tintColor = Color.Unspecified private set /** * Helper method to inspect whether the provided brush matches the current color of paths * within the group in order to help determine if only an alpha channel bitmap can be allocated * and tinted in order to save on memory overhead. */ private fun markTintForBrush(brush: Brush?) { if (!isTintable) { return } if (brush != null) { if (brush is SolidColor) { markTintForColor(brush.value) } else { // If the brush is not a solid color then we require a explicit ARGB channels in the // cached bitmap markNotTintable() } } } /** * Helper method to inspect whether the provided color matches the current color of paths * within the group in order to help determine if only an alpha channel bitmap can be allocated * and tinted in order to save on memory overhead. */ private fun markTintForColor(color: Color) { if (!isTintable) { return } if (color.isSpecified) { if (tintColor.isUnspecified) { // Initial color has not been specified, initialize the target color to the // one provided tintColor = color } else if (!tintColor.rgbEqual(color)) { // The given color does not match the rgb channels if our previous color // Therefore we require explicit ARGB channels in the cached bitmap markNotTintable() } } } private fun markTintForVNode(node: VNode) { if (node is PathComponent) { markTintForBrush(node.fill) markTintForBrush(node.stroke) } else if (node is GroupComponent) { if (node.isTintable && isTintable) { markTintForColor(node.tintColor) } else { markNotTintable() } } } private fun markNotTintable() { isTintable = false tintColor = Color.Unspecified } var clipPathData = EmptyPath set(value) { field = value isClipPathDirty = true invalidate() } private val willClipPath: Boolean get() = clipPathData.isNotEmpty() private var isClipPathDirty = true private var clipPath: Path? = null override var invalidateListener: ((VNode) -> Unit)? = null private val wrappedListener: (VNode) -> Unit = { node -> markTintForVNode(node) invalidateListener?.invoke(node) } private fun updateClipPath() { if (willClipPath) { var targetClip = clipPath if (targetClip == null) { targetClip = Path() clipPath = targetClip } // toPath() will reset the path we send clipPathData.toPath(targetClip) } } // If the name changes we should re-draw as individual nodes could // be modified based off of this name parameter. var name = DefaultGroupName set(value) { field = value invalidate() } var rotation = DefaultRotation set(value) { field = value isMatrixDirty = true invalidate() } var pivotX = DefaultPivotX set(value) { field = value isMatrixDirty = true invalidate() } var pivotY = DefaultPivotY set(value) { field = value isMatrixDirty = true invalidate() } var scaleX = DefaultScaleX set(value) { field = value isMatrixDirty = true invalidate() } var scaleY = DefaultScaleY set(value) { field = value isMatrixDirty = true invalidate() } var translationX = DefaultTranslationX set(value) { field = value isMatrixDirty = true invalidate() } var translationY = DefaultTranslationY set(value) { field = value isMatrixDirty = true invalidate() } private val numChildren: Int get() = children.size private var isMatrixDirty = true private fun updateMatrix() { val matrix: Matrix val target = groupMatrix if (target == null) { matrix = Matrix() groupMatrix = matrix } else { matrix = target matrix.reset() } // M = T(translationX + pivotX, translationY + pivotY) * // R(rotation) * S(scaleX, scaleY) * // T(-pivotX, -pivotY) matrix.translate(translationX + pivotX, translationY + pivotY) matrix.rotateZ(degrees = rotation) matrix.scale(scaleX, scaleY, 1f) matrix.translate(-pivotX, -pivotY) } fun insertAt( index: Int, instance: VNode ) { if (index < numChildren) { children[index] = instance } else { children.add(instance) } markTintForVNode(instance) instance.invalidateListener = wrappedListener invalidate() } fun remove( index: Int, count: Int ) { repeat(count) { if (index < children.size) { children[index].invalidateListener = null children.removeAt(index) } } invalidate() } override fun DrawScope.draw() { if (isMatrixDirty) { updateMatrix() isMatrixDirty = false } if (isClipPathDirty) { updateClipPath() isClipPathDirty = false } withTransform({ groupMatrix?.let { transform(it) } val targetClip = clipPath if (willClipPath && targetClip != null) { clipPath(targetClip) } }) { children.fastForEach { node -> with(node) { this@draw.draw() } } } } override fun toString(): String { val sb = StringBuilder().append("VGroup: ").append(name) children.fastForEach { node -> sb.append("\t").append(node.toString()).append("\n") } return sb.toString() } } /** * statically create a a GroupComponent from the VectorGroup representation provided from * an [ImageVector] instance */ internal fun GroupComponent.createGroupComponent(currentGroup: VectorGroup): GroupComponent { for (index in 0 until currentGroup.size) { val vectorNode = currentGroup[index] if (vectorNode is VectorPath) { val pathComponent = PathComponent().apply { pathData = vectorNode.pathData pathFillType = vectorNode.pathFillType name = vectorNode.name fill = vectorNode.fill fillAlpha = vectorNode.fillAlpha stroke = vectorNode.stroke strokeAlpha = vectorNode.strokeAlpha strokeLineWidth = vectorNode.strokeLineWidth strokeLineCap = vectorNode.strokeLineCap strokeLineJoin = vectorNode.strokeLineJoin strokeLineMiter = vectorNode.strokeLineMiter trimPathStart = vectorNode.trimPathStart trimPathEnd = vectorNode.trimPathEnd trimPathOffset = vectorNode.trimPathOffset } insertAt(index, pathComponent) } else if (vectorNode is VectorGroup) { val groupComponent = GroupComponent().apply { name = vectorNode.name rotation = vectorNode.rotation scaleX = vectorNode.scaleX scaleY = vectorNode.scaleY translationX = vectorNode.translationX translationY = vectorNode.translationY pivotX = vectorNode.pivotX pivotY = vectorNode.pivotY clipPathData = vectorNode.clipPathData createGroupComponent(vectorNode) } insertAt(index, groupComponent) } } return this } /** * helper method to verify if the rgb channels are equal excluding comparison of the alpha * channel */ private fun Color.rgbEqual(other: Color) = this.red == other.red && this.green == other.green && this.blue == other.blue ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/ImageVectorUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector import android.content.Context import android.graphics.PorterDuff import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.RootGroupName import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.density @Composable fun imageVectorPainter(imageVector: ImageVector): Painter { val density = LocalDensity.current return remember(density, imageVector) { derivedStateOf { imageVector.toPainter(density) } }.value } fun ImageVector.toPainter( density: Density ): Painter = createVectorPainterFromImageVector( density = density, imageVector = this, root = GroupComponent().apply { createGroupComponent(root) } ) fun ImageVector.toPainter( context: Context ): Painter = toPainter(context.density) fun ImageVector.toImageBitmap( context: Context, width: Int, height: Int, tint: Color = Color.Unspecified, backgroundColor: Color = Color.Transparent, iconPadding: Int = 0 ): ImageBitmap { val imageBitmap = ImageBitmap(width, height) val density = context.density val painter = toPainter(context) val canvas = Canvas(imageBitmap).apply { with(nativeCanvas) { drawColor(Color.Transparent.toArgb(), PorterDuff.Mode.CLEAR) drawColor(backgroundColor.toArgb()) } } CanvasDrawScope().draw( density = density, layoutDirection = LayoutDirection.Ltr, canvas = canvas, size = Size(width.toFloat(), height.toFloat()) ) { translate(iconPadding.toFloat(), iconPadding.toFloat()) { with(painter) { draw( size = Size( width = (width - iconPadding * 2).toFloat(), height = (height - iconPadding * 2).toFloat() ), colorFilter = ColorFilter.tint(tint) ) } } } return imageBitmap } /** * Helper method to configure the properties of a VectorPainter that maybe re-used */ private fun VectorPainter.configureVectorPainter( defaultSize: Size, viewportSize: Size, name: String = RootGroupName, intrinsicColorFilter: ColorFilter?, autoMirror: Boolean = false, ): VectorPainter = apply { this.size = defaultSize this.autoMirror = autoMirror this.intrinsicColorFilter = intrinsicColorFilter this.viewportSize = viewportSize this.name = name } /** * Helper method to create a VectorPainter instance from an ImageVector */ private fun createVectorPainterFromImageVector( density: Density, imageVector: ImageVector, root: GroupComponent ): VectorPainter { val defaultSize = density.obtainSizePx(imageVector.defaultWidth, imageVector.defaultHeight) val viewport = obtainViewportSize( defaultSize, imageVector.viewportWidth, imageVector.viewportHeight ) return VectorPainter(root).configureVectorPainter( defaultSize = defaultSize, viewportSize = viewport, name = imageVector.name, intrinsicColorFilter = createColorFilter(imageVector.tintColor, imageVector.tintBlendMode), autoMirror = imageVector.autoMirror ) } /** * Helper method to conditionally create a ColorFilter to tint contents if [tintColor] is * specified, that is [Color.isSpecified] returns true */ private fun createColorFilter( tintColor: Color, tintBlendMode: BlendMode ): ColorFilter? = if (tintColor.isSpecified) { ColorFilter.tint(tintColor, tintBlendMode) } else { null } /** * Helper method to calculate the viewport size. If the viewport width/height are not specified * this falls back on the default size provided */ private fun obtainViewportSize( defaultSize: Size, viewportWidth: Float, viewportHeight: Float ) = Size( if (viewportWidth.isNaN()) defaultSize.width else viewportWidth, if (viewportHeight.isNaN()) defaultSize.height else viewportHeight ) private fun Density.obtainSizePx( defaultWidth: Dp, defaultHeight: Dp ) = Size(defaultWidth.toPx(), defaultHeight.toPx()) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/PathComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathMeasure import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.vector.DefaultFillType import androidx.compose.ui.graphics.vector.DefaultPathName import androidx.compose.ui.graphics.vector.DefaultStrokeLineCap import androidx.compose.ui.graphics.vector.DefaultStrokeLineJoin import androidx.compose.ui.graphics.vector.DefaultStrokeLineMiter import androidx.compose.ui.graphics.vector.DefaultStrokeLineWidth import androidx.compose.ui.graphics.vector.DefaultTrimPathEnd import androidx.compose.ui.graphics.vector.DefaultTrimPathOffset import androidx.compose.ui.graphics.vector.DefaultTrimPathStart import androidx.compose.ui.graphics.vector.EmptyPath import androidx.compose.ui.graphics.vector.toPath internal class PathComponent : VNode() { var name = DefaultPathName set(value) { field = value invalidate() } var fill: Brush? = null set(value) { field = value invalidate() } var fillAlpha = 1.0f set(value) { field = value invalidate() } var pathData = EmptyPath set(value) { field = value isPathDirty = true invalidate() } var pathFillType = DefaultFillType set(value) { field = value renderPath.fillType = value invalidate() } var strokeAlpha = 1.0f set(value) { field = value invalidate() } var strokeLineWidth = DefaultStrokeLineWidth set(value) { field = value isStrokeDirty = true invalidate() } var stroke: Brush? = null set(value) { field = value invalidate() } var strokeLineCap = DefaultStrokeLineCap set(value) { field = value isStrokeDirty = true invalidate() } var strokeLineJoin = DefaultStrokeLineJoin set(value) { field = value isStrokeDirty = true invalidate() } var strokeLineMiter = DefaultStrokeLineMiter set(value) { field = value isStrokeDirty = true invalidate() } var trimPathStart = DefaultTrimPathStart set(value) { field = value isTrimPathDirty = true invalidate() } var trimPathEnd = DefaultTrimPathEnd set(value) { field = value isTrimPathDirty = true invalidate() } var trimPathOffset = DefaultTrimPathOffset set(value) { field = value isTrimPathDirty = true invalidate() } private var isPathDirty = true private var isStrokeDirty = true private var isTrimPathDirty = false private var strokeStyle: Stroke? = null private val path = Path() private var renderPath = path private val pathMeasure: PathMeasure by lazy(LazyThreadSafetyMode.NONE) { PathMeasure() } private fun updatePath() { // The call below resets the path pathData.toPath(path) updateRenderPath() } private fun updateRenderPath() { if (trimPathStart == DefaultTrimPathStart && trimPathEnd == DefaultTrimPathEnd) { renderPath = path } else { if (renderPath == path) { renderPath = Path() } else { // Rewind unsets the fill type so reset it here val fillType = renderPath.fillType renderPath.rewind() renderPath.fillType = fillType } pathMeasure.setPath(path, false) val length = pathMeasure.length val start = ((trimPathStart + trimPathOffset) % 1f) * length val end = ((trimPathEnd + trimPathOffset) % 1f) * length if (start > end) { pathMeasure.getSegment(start, length, renderPath, true) pathMeasure.getSegment(0f, end, renderPath, true) } else { pathMeasure.getSegment(start, end, renderPath, true) } } } override fun DrawScope.draw() { if (isPathDirty) { updatePath() } else if (isTrimPathDirty) { updateRenderPath() } isPathDirty = false isTrimPathDirty = false fill?.let { drawPath(renderPath, brush = it, alpha = fillAlpha) } stroke?.let { var targetStroke = strokeStyle if (isStrokeDirty || targetStroke == null) { targetStroke = Stroke(strokeLineWidth, strokeLineMiter, strokeLineCap, strokeLineJoin) strokeStyle = targetStroke isStrokeDirty = false } drawPath(renderPath, brush = it, alpha = strokeAlpha, style = targetStroke) } } override fun toString() = path.toString() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VNode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector import androidx.compose.ui.graphics.drawscope.DrawScope internal sealed class VNode { /** * Callback invoked whenever the node in the vector tree is modified in a way that would * change the output of the Vector */ internal open var invalidateListener: ((VNode) -> Unit)? = null fun invalidate() { invalidateListener?.invoke(this) } abstract fun DrawScope.draw() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.BlendModeColorFilter import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmapConfig import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.vector.DefaultGroupName import androidx.compose.ui.unit.IntSize import kotlin.math.ceil internal class VectorComponent(val root: GroupComponent) : VNode() { init { root.invalidateListener = { doInvalidate() } } var name: String = DefaultGroupName private fun doInvalidate() { isDirty = true invalidateCallback.invoke() } private var isDirty = true private val cacheDrawScope = DrawCache() private val cacheBitmapConfig: ImageBitmapConfig get() = cacheDrawScope.mCachedImage?.config ?: ImageBitmapConfig.Argb8888 internal var invalidateCallback = {} internal var intrinsicColorFilter: ColorFilter? by mutableStateOf(null) // Conditional filter used if the vector is all one color. In this case we allocate a // alpha8 channel bitmap and tint the result to the desired color private var tintFilter: ColorFilter? = null internal var viewportSize by mutableStateOf(Size.Zero) private var previousDrawSize = Size.Unspecified private var rootScaleX = 1f private var rootScaleY = 1f /** * Cached lambda used to avoid allocating the lambda on each draw invocation */ private val drawVectorBlock: DrawScope.() -> Unit = { with(root) { scale(rootScaleX, rootScaleY, pivot = Offset.Zero) { draw() } } } fun DrawScope.draw( alpha: Float, colorFilter: ColorFilter? ) { // If the content of the vector has changed, or we are drawing a different size // update the cached image to ensure we are scaling the vector appropriately val isOneColor = root.isTintable && root.tintColor.isSpecified val targetImageConfig = if (isOneColor && intrinsicColorFilter.tintableWithAlphaMask() && colorFilter.tintableWithAlphaMask() ) { ImageBitmapConfig.Alpha8 } else { ImageBitmapConfig.Argb8888 } if (isDirty || previousDrawSize != size || targetImageConfig != cacheBitmapConfig) { tintFilter = if (targetImageConfig == ImageBitmapConfig.Alpha8) { ColorFilter.tint(root.tintColor) } else { null } rootScaleX = size.width / viewportSize.width rootScaleY = size.height / viewportSize.height cacheDrawScope.drawCachedImage( targetImageConfig, IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()), this@draw, layoutDirection, drawVectorBlock ) isDirty = false previousDrawSize = size } val targetFilter = colorFilter ?: if (intrinsicColorFilter != null) { intrinsicColorFilter } else { tintFilter } cacheDrawScope.drawInto(this, alpha, targetFilter) } override fun DrawScope.draw() { draw(1.0f, null) } override fun toString(): String { return buildString { append("Params: ") append("\tname: ").append(name).append("\n") append("\tviewportWidth: ").append(viewportSize.width).append("\n") append("\tviewportHeight: ").append(viewportSize.height).append("\n") } } /** * Helper method to determine if a particular ColorFilter will generate the same output * if the bitmap has an Alpha8 or ARGB8888 configuration */ private fun ColorFilter?.tintableWithAlphaMask() = if (this is BlendModeColorFilter) { this.blendMode == BlendMode.SrcIn || this.blendMode == BlendMode.SrcOver } else { this == null } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/helper/image_vector/VectorPainter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper.image_vector import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.LayoutDirection /** * [Painter] implementation that abstracts the drawing of a Vector graphic. * This can be represented by either a [ImageVector] or a programmatic * composition of a vector */ internal class VectorPainter internal constructor( root: GroupComponent = GroupComponent() ) : Painter() { internal var size by mutableStateOf(Size.Zero) internal var autoMirror by mutableStateOf(false) /** * configures the intrinsic tint that may be defined on a VectorPainter */ internal var intrinsicColorFilter: ColorFilter? get() = vector.intrinsicColorFilter set(value) { vector.intrinsicColorFilter = value } internal var viewportSize: Size get() = vector.viewportSize set(value) { vector.viewportSize = value } internal var name: String get() = vector.name set(value) { vector.name = value } internal val vector = VectorComponent(root).apply { invalidateCallback = { if (drawCount == invalidateCount) { invalidateCount++ } } } private var invalidateCount by mutableIntStateOf(0) private var currentAlpha: Float = 1.0f private var currentColorFilter: ColorFilter? = null override val intrinsicSize: Size get() = size private var drawCount = -1 override fun DrawScope.onDraw() { with(vector) { val filter = currentColorFilter ?: intrinsicColorFilter if (autoMirror && layoutDirection == LayoutDirection.Rtl) { mirror { draw(currentAlpha, filter) } } else { draw(currentAlpha, filter) } } // This assignment is necessary to obtain invalidation callbacks as the state is // being read here which adds this callback to the snapshot observation drawCount = invalidateCount } override fun applyAlpha(alpha: Float): Boolean { currentAlpha = alpha return true } override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { currentColorFilter = colorFilter return true } private inline fun DrawScope.mirror(block: DrawScope.() -> Unit) { scale(-1f, 1f, block = block) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Decompose.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.navigation import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.MutableValue import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.updateAndGet import com.arkivanov.essenty.lifecycle.Lifecycle import com.arkivanov.essenty.lifecycle.LifecycleOwner import com.arkivanov.essenty.lifecycle.doOnDestroy import com.t8rin.logger.makeLog import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlin.coroutines.CoroutineContext import kotlin.reflect.KProperty val ComponentContext.coroutineScope: CoroutineScope get() = coroutineScope() /** * Creates and returns a new [CoroutineScope] with the specified [context]. * The returned [CoroutineScope] is automatically cancelled when the [Lifecycle] is destroyed. * * @param context a [CoroutineContext] to be used for creating the [CoroutineScope], default * is [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] * if available on the current platform, or [Dispatchers.Main] otherwise. */ fun LifecycleOwner.coroutineScope( context: CoroutineContext = Dispatchers.Main.immediate + SupervisorJob() + CoroutineExceptionHandler { _, t -> t.makeLog( "Component CRITICAL ISSUE" ) }, ): CoroutineScope = CoroutineScope(context = context).withLifecycle(lifecycle) /** * Automatically cancels this [CoroutineScope] when the specified [lifecycle] is destroyed. * * @return the same (this) [CoroutineScope]. */ fun CoroutineScope.withLifecycle(lifecycle: Lifecycle): CoroutineScope { lifecycle.doOnDestroy(::cancel) return this } @JvmInline value class Nullable( val value: T? ) { operator fun component1(): T? = value operator fun getValue( t: T?, property: KProperty<*> ): T? = value fun copy(value: T?) = Nullable(value) } fun T?.wrap(): Nullable = Nullable(this) fun MutableValue>.updateNullable(function: (T?) -> T?) { updateAndGet { function(this.value.value).wrap() } } typealias NullableValue = Value> typealias MutableNullableValue = MutableValue> ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/Screen.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("PLUGIN_IS_NOT_ENABLED") package com.t8rin.imagetoolbox.core.ui.utils.navigation import androidx.annotation.StringRes import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AutoFixHigh import androidx.compose.material.icons.outlined.FilePresent import androidx.compose.material.icons.rounded.Animation import androidx.compose.material.icons.rounded.Gif import androidx.compose.material.icons.rounded.Texture import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Apng import com.t8rin.imagetoolbox.core.resources.icons.ArtTrack import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.resources.icons.Jpg import com.t8rin.imagetoolbox.core.resources.icons.Jxl import com.t8rin.imagetoolbox.core.resources.icons.Pdf import com.t8rin.imagetoolbox.core.resources.icons.TextSearch import com.t8rin.imagetoolbox.core.resources.icons.Webp import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @Stable @Immutable sealed class Screen( open val id: Int, @StringRes val title: Int, @StringRes val subtitle: Int ) { val isBetaFeature: Boolean by lazy { isBetaFeature() } val simpleName: String by lazy { simpleName() } val icon: ImageVector? by lazy { icon() } val twoToneIcon: ImageVector? by lazy { twoToneIcon() } @Serializable data class LibraryDetails( val name: String, val htmlDescription: String, val link: String? ) : Screen( id = -5, title = 0, subtitle = 0 ) @Serializable data object LibrariesInfo : Screen( id = -4, title = 0, subtitle = 0 ) @Serializable data class Settings( val searchQuery: String = "" ) : Screen( id = -3, title = 0, subtitle = 0 ) @Serializable data object EasterEgg : Screen( id = -2, title = 0, subtitle = 0 ) @Serializable data object Main : Screen( id = -1, title = 0, subtitle = 0 ) @Serializable data class SingleEdit( val uri: Uri? = null ) : Screen( id = 0, title = R.string.single_edit, subtitle = R.string.single_edit_sub ) @Serializable data class ResizeAndConvert( val uris: List? = null ) : Screen( id = 1, title = R.string.resize_and_convert, subtitle = R.string.resize_and_convert_sub ) @Serializable data class WeightResize( val uris: List? = null ) : Screen( id = 2, title = R.string.by_bytes_resize, subtitle = R.string.by_bytes_resize_sub ) @Serializable data class Crop( val uri: Uri? = null ) : Screen( id = 3, title = R.string.crop, subtitle = R.string.crop_sub ) @Serializable data class Filter( @SerialName("dataType") val type: Type? = null ) : Screen( id = 4, title = R.string.filter, subtitle = R.string.filter_sub ) { @Serializable sealed class Type( @StringRes val title: Int, @StringRes val subtitle: Int ) { val icon: ImageVector get() = when (this) { is Masking -> Icons.Rounded.Texture is Basic -> Icons.Outlined.AutoFixHigh } @Serializable data class Masking( val uri: Uri? = null ) : Type( title = R.string.mask_filter, subtitle = R.string.mask_filter_sub ) @Serializable data class Basic( val uris: List? = null ) : Type( title = R.string.full_filter, subtitle = R.string.full_filter_sub ) companion object { val entries by lazy { listOf( Basic(), Masking() ) } } } } @Serializable data class Draw( val uri: Uri? = null ) : Screen( id = 5, title = R.string.draw, subtitle = R.string.draw_sub ) @Serializable data class Cipher( val uri: Uri? = null ) : Screen( id = 6, title = R.string.cipher, subtitle = R.string.cipher_sub ) @Serializable data class EraseBackground( val uri: Uri? = null ) : Screen( id = 7, title = R.string.background_remover, subtitle = R.string.background_remover_sub ) @Serializable data class ImagePreview( val uris: List? = null ) : Screen( id = 8, title = R.string.image_preview, subtitle = R.string.image_preview_sub ) @Serializable data class ImageStitching( val uris: List? = null ) : Screen( id = 9, title = R.string.image_stitching, subtitle = R.string.image_stitching_sub ) @Serializable data class LoadNetImage( val url: String = "" ) : Screen( id = 10, title = R.string.load_image_from_net, subtitle = R.string.load_image_from_net_sub ) @Serializable data class PickColorFromImage( val uri: Uri? = null ) : Screen( id = 11, title = R.string.pick_color, subtitle = R.string.pick_color_sub ) @Serializable data class PaletteTools( val uri: Uri? = null ) : Screen( id = 12, title = R.string.palette_tools, subtitle = R.string.palette_tools_sub ) @Serializable data class DeleteExif( val uris: List? = null ) : Screen( id = 13, title = R.string.delete_exif, subtitle = R.string.delete_exif_sub ) @Serializable data class Compare( val uris: List? = null ) : Screen( id = 14, title = R.string.compare, subtitle = R.string.compare_sub ) @Serializable data class LimitResize( val uris: List? = null ) : Screen( id = 15, title = R.string.limits_resize, subtitle = R.string.limits_resize_sub ) @Serializable data object PdfTools : Screen( id = 16, title = R.string.pdf_tools, subtitle = R.string.pdf_tools_sub ) { val options: List by lazy { listOf( Preview(), ImagesToPdf(), ExtractPages(), Merge(), Split(), RemovePages(), Rotate(), Rearrange(), Crop(), PageNumbers(), Watermark(), Signature(), Compress(), RemoveAnnotations(), Flatten(), Print(), Grayscale(), Repair(), Protect(), Unlock(), Metadata(), ExtractImages(), OCR(), ZipConvert(), ) } @Serializable data class Merge( val uris: List? = null ) : Screen( id = 44, title = R.string.merge_pdf, subtitle = R.string.merge_pdf_sub ) @Serializable data class Split( val uri: Uri? = null ) : Screen( id = 45, title = R.string.split_pdf, subtitle = R.string.split_pdf_sub ) @Serializable data class Rotate( val uri: Uri? = null ) : Screen( id = 46, title = R.string.rotate_pdf, subtitle = R.string.rotate_pdf_sub ) @Serializable data class Rearrange( val uri: Uri? = null ) : Screen( id = 47, title = R.string.rearrange_pdf, subtitle = R.string.rearrange_pdf_sub ) @Serializable data class PageNumbers( val uri: Uri? = null ) : Screen( id = 48, title = R.string.page_numbers, subtitle = R.string.page_numbers_sub ) @Serializable data class OCR( val uri: Uri? = null ) : Screen( id = 49, title = R.string.pdf_to_text, subtitle = R.string.pdf_to_text_sub ) @Serializable data class Watermark( val uri: Uri? = null ) : Screen( id = 50, title = R.string.watermarking, subtitle = R.string.watermark_pdf_sub ) @Serializable data class Signature( val uri: Uri? = null ) : Screen( id = 51, title = R.string.signature, subtitle = R.string.signature_sub ) @Serializable data class Protect( val uri: Uri? = null ) : Screen( id = 52, title = R.string.protect_pdf, subtitle = R.string.protect_pdf_sub ) @Serializable data class Unlock( val uri: Uri? = null ) : Screen( id = 53, title = R.string.unlock_pdf, subtitle = R.string.unlock_pdf_sub ) @Serializable data class Compress( val uri: Uri? = null ) : Screen( id = 54, title = R.string.compress_pdf, subtitle = R.string.compress_pdf_sub ) @Serializable data class Grayscale( val uri: Uri? = null ) : Screen( id = 55, title = R.string.grayscale, subtitle = R.string.grayscale_pdf_sub ) @Serializable data class Repair( val uri: Uri? = null ) : Screen( id = 56, title = R.string.repair_pdf, subtitle = R.string.repair_pdf_sub ) @Serializable data class Metadata( val uri: Uri? = null ) : Screen( id = 57, title = R.string.metadata, subtitle = R.string.metadata_pdf_sub ) @Serializable data class RemovePages( val uri: Uri? = null ) : Screen( id = 58, title = R.string.remove_pages_pdf, subtitle = R.string.remove_pages_pdf_sub ) @Serializable data class Crop( val uri: Uri? = null ) : Screen( id = 59, title = R.string.crop_pdf, subtitle = R.string.crop_pdf_sub ) @Serializable data class Flatten( val uri: Uri? = null ) : Screen( id = 60, title = R.string.flatten_pdf, subtitle = R.string.flatten_pdf_sub ) @Serializable data class ExtractImages( val uri: Uri? = null ) : Screen( id = 61, title = R.string.extract_images, subtitle = R.string.extract_images_sub ) @Serializable data class ZipConvert( val uri: Uri? = null ) : Screen( id = 62, title = R.string.zip_pdf, subtitle = R.string.zip_pdf_sub ) @Serializable data class Print( val uri: Uri? = null ) : Screen( id = 63, title = R.string.print_pdf, subtitle = R.string.print_pdf_sub ) @Serializable data class Preview( val uri: Uri? = null ) : Screen( id = 64, title = R.string.preview_pdf, subtitle = R.string.preview_pdf_sub ) @Serializable data class ImagesToPdf( val uris: List? = null ) : Screen( id = 65, title = R.string.images_to_pdf, subtitle = R.string.images_to_pdf_sub ) @Serializable data class ExtractPages( val uri: Uri? = null ) : Screen( id = 66, title = R.string.pdf_to_images, subtitle = R.string.pdf_to_images_sub ) @Serializable data class RemoveAnnotations( val uri: Uri? = null ) : Screen( id = 67, title = R.string.remove_annotations, subtitle = R.string.remove_annotations_sub ) } @Serializable data class RecognizeText( @SerialName("dataType") val type: Type? = null ) : Screen( id = 17, title = R.string.recognize_text, subtitle = R.string.recognize_text_sub ) { @Serializable sealed class Type( @StringRes val title: Int, @StringRes val subtitle: Int ) { val icon: ImageVector get() = when (this) { is Extraction -> Icons.Outlined.TextSearch is WriteToFile -> Icons.Outlined.FilePresent is WriteToMetadata -> Icons.Outlined.Exif is WriteToSearchablePdf -> Icons.Outlined.Pdf } @Serializable data class Extraction( val uri: Uri? = null ) : Type( title = R.string.recognize_text, subtitle = R.string.recognize_text_sub ) @Serializable data class WriteToFile( val uris: List? = null ) : Type( title = R.string.ocr_write_to_file, subtitle = R.string.ocr_write_to_file_sub ) @Serializable data class WriteToMetadata( val uris: List? = null ) : Type( title = R.string.ocr_write_to_metadata, subtitle = R.string.ocr_write_to_metadata_sub ) @Serializable data class WriteToSearchablePdf( val uris: List? = null ) : Type( title = R.string.ocr_write_to_searchable_pdf, subtitle = R.string.ocr_write_to_searchable_pdf_sub ) companion object { val entries by lazy { listOf( Extraction(), WriteToFile(), WriteToMetadata(), WriteToSearchablePdf() ) } } } } @Serializable data class GradientMaker( val uris: List? = null ) : Screen( id = 18, title = R.string.gradient_maker, subtitle = R.string.gradient_maker_sub, ) @Serializable data class Watermarking( val uris: List? = null ) : Screen( id = 19, title = R.string.watermarking, subtitle = R.string.watermarking_sub, ) @Serializable data class GifTools( @SerialName("dataType") val type: Type? = null ) : Screen( id = 20, title = R.string.gif_tools, subtitle = R.string.gif_tools_sub ) { @Serializable sealed class Type( @StringRes val title: Int, @StringRes val subtitle: Int ) { val icon: ImageVector get() = when (this) { is GifToImage -> Icons.Outlined.ArtTrack is GifToJxl -> Icons.Filled.Jxl is ImageToGif -> Icons.Rounded.Gif is GifToWebp -> Icons.Rounded.Webp } @Serializable data class GifToImage( val gifUri: Uri? = null ) : Type( title = R.string.gif_type_to_image, subtitle = R.string.gif_type_to_image_sub ) @Serializable data class ImageToGif( val imageUris: List? = null ) : Type( title = R.string.gif_type_to_gif, subtitle = R.string.gif_type_to_gif_sub ) @Serializable data class GifToJxl( val gifUris: List? = null ) : Type( title = R.string.gif_type_to_jxl, subtitle = R.string.gif_type_to_jxl_sub ) @Serializable data class GifToWebp( val gifUris: List? = null ) : Type( title = R.string.gif_type_to_webp, subtitle = R.string.gif_type_to_webp_sub ) companion object { val entries by lazy { listOf( ImageToGif(), GifToImage(), GifToJxl(), GifToWebp() ) } } } } @Serializable data class ApngTools( @SerialName("dataType") val type: Type? = null ) : Screen( id = 21, title = R.string.apng_tools, subtitle = R.string.apng_tools_sub ) { @Serializable sealed class Type( @StringRes val title: Int, @StringRes val subtitle: Int ) { val icon: ImageVector get() = when (this) { is ApngToImage -> Icons.Outlined.ArtTrack is ApngToJxl -> Icons.Filled.Jxl is ImageToApng -> Icons.Rounded.Apng } @Serializable data class ApngToImage( val apngUri: Uri? = null ) : Type( title = R.string.apng_type_to_image, subtitle = R.string.apng_type_to_image_sub ) @Serializable data class ImageToApng( val imageUris: List? = null ) : Type( title = R.string.apng_type_to_apng, subtitle = R.string.apng_type_to_apng_sub ) @Serializable data class ApngToJxl( val apngUris: List? = null ) : Type( title = R.string.apng_type_to_jxl, subtitle = R.string.apng_type_to_jxl_sub ) companion object { val entries by lazy { listOf( ImageToApng(), ApngToImage(), ApngToJxl() ) } } } } @Serializable data class Zip( val uris: List? = null ) : Screen( id = 22, title = R.string.zip, subtitle = R.string.zip_sub ) @Serializable data class JxlTools( @SerialName("dataType") val type: Type? = null ) : Screen( id = 23, title = R.string.jxl_tools, subtitle = R.string.jxl_tools_sub ) { @Serializable sealed class Type( @StringRes val title: Int, @StringRes val subtitle: Int ) { val icon: ImageVector get() = when (this) { is ImageToJxl -> Icons.Rounded.Animation is JpegToJxl -> Icons.Filled.Jxl is JxlToImage -> Icons.Outlined.ArtTrack is JxlToJpeg -> Icons.Outlined.Jpg } @Serializable data class JxlToJpeg( val jxlImageUris: List? = null ) : Type( title = R.string.jxl_type_to_jpeg, subtitle = R.string.jxl_type_to_jpeg_sub ) @Serializable data class JpegToJxl( val jpegImageUris: List? = null ) : Type( title = R.string.jpeg_type_to_jxl, subtitle = R.string.jpeg_type_to_jxl_sub ) @Serializable data class JxlToImage( val jxlUri: Uri? = null ) : Type( title = R.string.jxl_type_to_images, subtitle = R.string.jxl_type_to_images_sub ) @Serializable data class ImageToJxl( val imageUris: List? = null ) : Type( title = R.string.jxl_type_to_jxl, subtitle = R.string.jxl_type_to_jxl_sub ) companion object { val entries by lazy { listOf( JpegToJxl(), JxlToJpeg(), JxlToImage(), ImageToJxl() ) } } } } @Serializable data class SvgMaker( val uris: List? = null ) : Screen( id = 24, title = R.string.images_to_svg, subtitle = R.string.images_to_svg_sub ) @Serializable data class FormatConversion( val uris: List? = null ) : Screen( id = 25, title = R.string.format_conversion, subtitle = R.string.format_conversion_sub ) @Serializable data object DocumentScanner : Screen( id = 26, title = R.string.document_scanner, subtitle = R.string.document_scanner_sub ) @Serializable data class ScanQrCode( val qrCodeContent: String? = null, val uriToAnalyze: Uri? = null ) : Screen( id = 27, title = R.string.qr_code, subtitle = R.string.barcodes_sub ) @Serializable data class ImageStacking( val uris: List? = null ) : Screen( id = 28, title = R.string.image_stacking, subtitle = R.string.image_stacking_sub ) @Serializable data class ImageSplitting( val uri: Uri? = null ) : Screen( id = 29, title = R.string.image_splitting, subtitle = R.string.image_splitting_sub ) @Serializable data object ColorTools : Screen( id = 30, title = R.string.color_tools, subtitle = R.string.color_tools_sub ) @Serializable data class WebpTools( @SerialName("dataType") val type: Type? = null ) : Screen( id = 31, title = R.string.webp_tools, subtitle = R.string.webp_tools_sub ) { @Serializable sealed class Type( @StringRes val title: Int, @StringRes val subtitle: Int ) { val icon: ImageVector get() = when (this) { is WebpToImage -> Icons.Outlined.ArtTrack is ImageToWebp -> Icons.Rounded.Webp } @Serializable data class WebpToImage( val webpUri: Uri? = null ) : Type( title = R.string.webp_type_to_image, subtitle = R.string.webp_type_to_image_sub ) @Serializable data class ImageToWebp( val imageUris: List? = null ) : Type( title = R.string.webp_type_to_webp, subtitle = R.string.webp_type_to_webp_sub ) companion object { val entries by lazy { listOf( ImageToWebp(), WebpToImage() ) } } } } @Serializable data object NoiseGeneration : Screen( id = 32, title = R.string.noise_generation, subtitle = R.string.noise_generation_sub ) @Serializable data class CollageMaker( val uris: List? = null ) : Screen( id = 33, title = R.string.collage_maker, subtitle = R.string.collage_maker_sub ) @Serializable data class MarkupLayers( val uri: Uri? = null ) : Screen( id = 34, title = R.string.markup_layers, subtitle = R.string.markup_layers_sub ) @Serializable data class Base64Tools( val uri: Uri? = null ) : Screen( id = 35, title = R.string.base_64_tools, subtitle = R.string.base_64_tools_sub ) @Serializable data class ChecksumTools( val uri: Uri? = null, ) : Screen( id = 36, title = R.string.checksum_tools, subtitle = R.string.checksum_tools_sub ) @Serializable data object MeshGradients : Screen( id = -5, title = 0, subtitle = 0 ) @Serializable data class EditExif( val uri: Uri? = null, ) : Screen( id = 37, title = R.string.edit_exif_screen, subtitle = R.string.edit_exif_screen_sub ) @Serializable data class ImageCutter( val uris: List? = null ) : Screen( id = 38, title = R.string.image_cutting, subtitle = R.string.image_cutting_sub ) @Serializable data class AudioCoverExtractor( val uris: List? = null ) : Screen( id = 39, title = R.string.audio_cover_extractor, subtitle = R.string.audio_cover_extractor_sub ) @Serializable data object WallpapersExport : Screen( id = 40, title = R.string.wallpapers_export, subtitle = R.string.wallpapers_export_sub ) @Serializable data class AsciiArt( val uri: Uri? = null, ) : Screen( id = 41, title = R.string.ascii_art, subtitle = R.string.ascii_art_sub ) @Serializable data class AiTools( val uris: List? = null ) : Screen( id = 42, title = R.string.ai_tools, subtitle = R.string.ai_tools_sub ) @Serializable data object ColorLibrary : Screen( id = 43, title = R.string.color_library, subtitle = R.string.color_library_sub ) companion object : ScreenConstants by ScreenConstants } data class ScreenGroup( val entries: List, @StringRes val title: Int, val selectedIcon: ImageVector, val baseIcon: ImageVector ) { fun icon(isSelected: Boolean) = if (isSelected) selectedIcon else baseIcon } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/navigation/ScreenUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.navigation import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Album import androidx.compose.material.icons.outlined.AutoFixHigh import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.outlined.FilterBAndW import androidx.compose.material.icons.outlined.FolderZip import androidx.compose.material.icons.outlined.GifBox import androidx.compose.material.icons.outlined.Gradient import androidx.compose.material.icons.rounded.Tag import androidx.compose.material.icons.twotone.Album import androidx.compose.material.icons.twotone.AutoFixHigh import androidx.compose.material.icons.twotone.ColorLens import androidx.compose.material.icons.twotone.FilterBAndW import androidx.compose.material.icons.twotone.FolderZip import androidx.compose.material.icons.twotone.GifBox import androidx.compose.material.icons.twotone.Gradient import androidx.compose.material.icons.twotone.Preview import androidx.compose.material.icons.twotone.Tag import androidx.compose.ui.graphics.vector.ImageVector import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ApngBox import com.t8rin.imagetoolbox.core.resources.icons.ArtTrack import com.t8rin.imagetoolbox.core.resources.icons.Ascii import com.t8rin.imagetoolbox.core.resources.icons.Base64 import com.t8rin.imagetoolbox.core.resources.icons.Bolt import com.t8rin.imagetoolbox.core.resources.icons.BubbleDelete import com.t8rin.imagetoolbox.core.resources.icons.Build import com.t8rin.imagetoolbox.core.resources.icons.Collage import com.t8rin.imagetoolbox.core.resources.icons.Compare import com.t8rin.imagetoolbox.core.resources.icons.Counter import com.t8rin.imagetoolbox.core.resources.icons.CropSmall import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep import com.t8rin.imagetoolbox.core.resources.icons.DocumentScanner import com.t8rin.imagetoolbox.core.resources.icons.Draw import com.t8rin.imagetoolbox.core.resources.icons.Encrypted import com.t8rin.imagetoolbox.core.resources.icons.Eraser import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.resources.icons.ExifEdit import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper import com.t8rin.imagetoolbox.core.resources.icons.FileImage import com.t8rin.imagetoolbox.core.resources.icons.FindInPage import com.t8rin.imagetoolbox.core.resources.icons.FormatPaintVariant import com.t8rin.imagetoolbox.core.resources.icons.ImageCombine import com.t8rin.imagetoolbox.core.resources.icons.ImageConvert import com.t8rin.imagetoolbox.core.resources.icons.ImageDownload import com.t8rin.imagetoolbox.core.resources.icons.ImageEdit import com.t8rin.imagetoolbox.core.resources.icons.ImageOverlay import com.t8rin.imagetoolbox.core.resources.icons.ImageResize import com.t8rin.imagetoolbox.core.resources.icons.ImageWeight import com.t8rin.imagetoolbox.core.resources.icons.Jxl import com.t8rin.imagetoolbox.core.resources.icons.KeyVariant import com.t8rin.imagetoolbox.core.resources.icons.Landscape import com.t8rin.imagetoolbox.core.resources.icons.MiniEditLarge import com.t8rin.imagetoolbox.core.resources.icons.MultipleImageEdit import com.t8rin.imagetoolbox.core.resources.icons.Neurology import com.t8rin.imagetoolbox.core.resources.icons.NoiseAlt import com.t8rin.imagetoolbox.core.resources.icons.PaletteSwatch import com.t8rin.imagetoolbox.core.resources.icons.Panorama import com.t8rin.imagetoolbox.core.resources.icons.Pdf import com.t8rin.imagetoolbox.core.resources.icons.Preview import com.t8rin.imagetoolbox.core.resources.icons.Print import com.t8rin.imagetoolbox.core.resources.icons.QrCode import com.t8rin.imagetoolbox.core.resources.icons.Rotate90Cw import com.t8rin.imagetoolbox.core.resources.icons.Scanner import com.t8rin.imagetoolbox.core.resources.icons.ScissorsSmall import com.t8rin.imagetoolbox.core.resources.icons.ServiceToolbox import com.t8rin.imagetoolbox.core.resources.icons.ShieldLock import com.t8rin.imagetoolbox.core.resources.icons.SplitAlt import com.t8rin.imagetoolbox.core.resources.icons.Stacks import com.t8rin.imagetoolbox.core.resources.icons.Stylus import com.t8rin.imagetoolbox.core.resources.icons.SwapVerticalCircle import com.t8rin.imagetoolbox.core.resources.icons.TagText import com.t8rin.imagetoolbox.core.resources.icons.TextSearch import com.t8rin.imagetoolbox.core.resources.icons.Unarchive import com.t8rin.imagetoolbox.core.resources.icons.VectorPolyline import com.t8rin.imagetoolbox.core.resources.icons.WallpaperAlt import com.t8rin.imagetoolbox.core.resources.icons.WandShine import com.t8rin.imagetoolbox.core.resources.icons.Watermark import com.t8rin.imagetoolbox.core.resources.icons.WebpBox import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.AiTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ApngTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.AsciiArt import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.AudioCoverExtractor import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Base64Tools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ChecksumTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Cipher import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.CollageMaker import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ColorLibrary import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ColorTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Compare import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Crop import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.DeleteExif import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.DocumentScanner import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Draw import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.EasterEgg import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.EditExif import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.EraseBackground import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Filter import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.FormatConversion import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.GifTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.GradientMaker import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ImageCutter import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ImagePreview import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ImageSplitting import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ImageStacking import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ImageStitching import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.JxlTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.LibrariesInfo import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.LibraryDetails import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.LimitResize import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.LoadNetImage import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Main import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.MarkupLayers import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.MeshGradients import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.NoiseGeneration import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.PaletteTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.PdfTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.PickColorFromImage import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.RecognizeText import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ResizeAndConvert import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.ScanQrCode import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Settings import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.SingleEdit import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.SvgMaker import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.WallpapersExport import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Watermarking import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.WebpTools import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.WeightResize import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen.Zip import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import android.net.Uri as AndroidUri @Suppress("UnusedReceiverParameter") internal fun Screen.isBetaFeature(): Boolean = false internal fun Screen.simpleName(): String = when (this) { is ApngTools -> "APNG_Tools" is Cipher -> "Cipher" is Compare -> "Compare" is Crop -> "Crop" is DeleteExif -> "Delete_Exif" is Draw -> "Draw" is EasterEgg -> "Easter_Egg" is EraseBackground -> "Erase_Background" is Filter -> "Filter" is PaletteTools -> "Palette_Tools" is GifTools -> "GIF_Tools" is GradientMaker -> "Gradient_Maker" is ImagePreview -> "Image_Preview" is ImageStitching -> "Image_Stitching" is JxlTools -> "JXL_Tools" is LimitResize -> "Limit_Resize" is LoadNetImage -> "Load_Net_Image" is Main -> "Main" is PdfTools -> "PDF_Tools" is PickColorFromImage -> "Pick_Color_From_Image" is RecognizeText -> "Recognize_Text" is ResizeAndConvert -> "Resize_And_Convert" is WeightResize -> "Resize_By_Bytes" is Settings -> "Settings" is SingleEdit -> "Single_Edit" is Watermarking -> "Watermarking" is Zip -> "Zip" is SvgMaker -> "Svg" is FormatConversion -> "Convert" is DocumentScanner -> "Document_Scanner" is ScanQrCode -> "QR_Code" is ImageStacking -> "Image_Stacking" is ImageSplitting -> "Image_Splitting" is ColorTools -> "Color_Tools" is WebpTools -> "WEBP_Tools" is NoiseGeneration -> "Noise_Generation" is CollageMaker -> "Collage_Maker" is LibrariesInfo -> "Libraries_Info" is MarkupLayers -> "Markup_Layers" is Base64Tools -> "Base64_Tools" is ChecksumTools -> "Checksum_Tools" is MeshGradients -> "Mesh_Gradients" is EditExif -> "Edit_EXIF" is ImageCutter -> "Image_Cutting" is AudioCoverExtractor -> "Audio_Cover_Extractor" is LibraryDetails -> "Library_Details" is WallpapersExport -> "Wallpapers_Export" is AsciiArt -> "Ascii_Art" is AiTools -> "Ai_Tools" is ColorLibrary -> "ColorLibrary" is PdfTools.Merge -> "PdfTools_Merge" is PdfTools.Split -> "PdfTools_Split" is PdfTools.Rotate -> "PdfTools_Rotate" is PdfTools.Rearrange -> "PdfTools_Rearrange" is PdfTools.PageNumbers -> "PdfTools_PageNumbers" is PdfTools.OCR -> "PdfTools_OCR" is PdfTools.Watermark -> "PdfTools_Watermark" is PdfTools.Signature -> "PdfTools_Signature" is PdfTools.Protect -> "PdfTools_Protect" is PdfTools.Unlock -> "PdfTools_Unlock" is PdfTools.Compress -> "PdfTools_Compress" is PdfTools.Grayscale -> "PdfTools_Grayscale" is PdfTools.Repair -> "PdfTools_Repair" is PdfTools.Metadata -> "PdfTools_Metadata" is PdfTools.RemovePages -> "PdfTools_RemovePages" is PdfTools.Crop -> "PdfTools_Crop" is PdfTools.Flatten -> "PdfTools_Flatten" is PdfTools.ExtractImages -> "PdfTools_ExtractImages" is PdfTools.ZipConvert -> "PdfTools_ZipConvert" is PdfTools.Print -> "PdfTools_Print" is PdfTools.Preview -> "PdfTools_Preview" is PdfTools.ImagesToPdf -> "PdfTools_ImagesToPdf" is PdfTools.ExtractPages -> "PdfTools_ExtractPages" is PdfTools.RemoveAnnotations -> "PdfTools_RemoveAnnotations" } internal fun Screen.icon(): ImageVector? = when (this) { is EasterEgg, is Main, is Settings, is LibrariesInfo, is MeshGradients, is LibraryDetails -> null is SingleEdit -> Icons.Outlined.ImageEdit is ApngTools -> Icons.Outlined.ApngBox is Cipher -> Icons.Outlined.Encrypted is Compare -> Icons.Outlined.Compare is Crop -> Icons.Rounded.CropSmall is DeleteExif -> Icons.Outlined.Exif is Draw -> Icons.Outlined.Draw is EraseBackground -> Icons.Rounded.Eraser is Filter -> Icons.Outlined.AutoFixHigh is PaletteTools -> Icons.Outlined.PaletteSwatch is GifTools -> Icons.Outlined.GifBox is GradientMaker -> Icons.Outlined.Gradient is ImagePreview -> Icons.Outlined.Landscape is ImageStitching -> Icons.Rounded.ImageCombine is JxlTools -> Icons.Filled.Jxl is LimitResize -> Icons.Outlined.ImageResize is LoadNetImage -> Icons.Outlined.ImageDownload is PdfTools -> Icons.Outlined.Pdf is PickColorFromImage -> Icons.Outlined.Eyedropper is RecognizeText -> Icons.Outlined.TextSearch is ResizeAndConvert -> Icons.Outlined.MultipleImageEdit is WeightResize -> Icons.Outlined.ImageWeight is Watermarking -> Icons.Outlined.Watermark is Zip -> Icons.Outlined.FolderZip is SvgMaker -> Icons.Outlined.VectorPolyline is FormatConversion -> Icons.Outlined.ImageConvert is DocumentScanner -> Icons.Outlined.DocumentScanner is ScanQrCode -> Icons.Outlined.QrCode is ImageStacking -> Icons.Outlined.ImageOverlay is ImageSplitting -> Icons.Outlined.SplitAlt is ColorTools -> Icons.Outlined.ColorLens is WebpTools -> Icons.Outlined.WebpBox is NoiseGeneration -> Icons.Outlined.NoiseAlt is CollageMaker -> Icons.Outlined.Collage is MarkupLayers -> Icons.Outlined.Stacks is Base64Tools -> Icons.Outlined.Base64 is ChecksumTools -> Icons.Rounded.Tag is EditExif -> Icons.Outlined.ExifEdit is ImageCutter -> Icons.Outlined.ScissorsSmall is AudioCoverExtractor -> Icons.Outlined.Album is WallpapersExport -> Icons.Outlined.WallpaperAlt is AsciiArt -> Icons.Outlined.Ascii is AiTools -> Icons.Outlined.Neurology is ColorLibrary -> Icons.Outlined.FormatPaintVariant is PdfTools.Merge -> Icons.Rounded.ImageCombine is PdfTools.Split -> Icons.Outlined.SplitAlt is PdfTools.Rotate -> Icons.Outlined.Rotate90Cw is PdfTools.Rearrange -> Icons.Outlined.SwapVerticalCircle is PdfTools.PageNumbers -> Icons.Outlined.Counter is PdfTools.OCR -> Icons.Outlined.FindInPage is PdfTools.Watermark -> Icons.Outlined.Watermark is PdfTools.Signature -> Icons.Outlined.Stylus is PdfTools.Protect -> Icons.Outlined.ShieldLock is PdfTools.Unlock -> Icons.Outlined.KeyVariant is PdfTools.Compress -> Icons.Outlined.Bolt is PdfTools.Grayscale -> Icons.Outlined.FilterBAndW is PdfTools.Repair -> Icons.Outlined.Build is PdfTools.Metadata -> Icons.Outlined.TagText is PdfTools.RemovePages -> Icons.Outlined.DeleteSweep is PdfTools.Crop -> Icons.Rounded.CropSmall is PdfTools.Flatten -> Icons.Outlined.Panorama is PdfTools.ExtractImages -> Icons.Outlined.Unarchive is PdfTools.ZipConvert -> Icons.Outlined.FolderZip is PdfTools.Print -> Icons.Outlined.Print is PdfTools.Preview -> Icons.Outlined.Preview is PdfTools.ImagesToPdf -> Icons.Outlined.Scanner is PdfTools.ExtractPages -> Icons.Outlined.ArtTrack is PdfTools.RemoveAnnotations -> Icons.Outlined.BubbleDelete } internal fun Screen.twoToneIcon(): ImageVector? = when (this) { is EasterEgg, is Main, is Settings, is LibrariesInfo, is MeshGradients, is LibraryDetails -> null is SingleEdit -> Icons.TwoTone.ImageEdit is ApngTools -> Icons.TwoTone.ApngBox is Cipher -> Icons.TwoTone.Encrypted is Compare -> Icons.TwoTone.Compare is Crop -> Icons.TwoTone.CropSmall is DeleteExif -> Icons.TwoTone.Exif is Draw -> Icons.TwoTone.Draw is EraseBackground -> Icons.TwoTone.Eraser is Filter -> Icons.TwoTone.AutoFixHigh is PaletteTools -> Icons.TwoTone.PaletteSwatch is GifTools -> Icons.TwoTone.GifBox is GradientMaker -> Icons.TwoTone.Gradient is ImagePreview -> Icons.TwoTone.Landscape is ImageStitching -> Icons.TwoTone.ImageCombine is JxlTools -> Icons.Filled.Jxl is LimitResize -> Icons.TwoTone.ImageResize is LoadNetImage -> Icons.TwoTone.ImageDownload is PdfTools -> Icons.TwoTone.Pdf is PickColorFromImage -> Icons.TwoTone.Eyedropper is RecognizeText -> Icons.Outlined.TextSearch is ResizeAndConvert -> Icons.TwoTone.MultipleImageEdit is WeightResize -> Icons.TwoTone.ImageWeight is Watermarking -> Icons.TwoTone.Watermark is Zip -> Icons.TwoTone.FolderZip is SvgMaker -> Icons.TwoTone.VectorPolyline is FormatConversion -> Icons.TwoTone.ImageConvert is DocumentScanner -> Icons.TwoTone.DocumentScanner is ScanQrCode -> Icons.TwoTone.QrCode is ImageStacking -> Icons.TwoTone.ImageOverlay is ImageSplitting -> Icons.TwoTone.SplitAlt is ColorTools -> Icons.TwoTone.ColorLens is WebpTools -> Icons.TwoTone.WebpBox is NoiseGeneration -> Icons.Outlined.NoiseAlt is CollageMaker -> Icons.TwoTone.Collage is MarkupLayers -> Icons.TwoTone.Stacks is Base64Tools -> Icons.TwoTone.Base64 is ChecksumTools -> Icons.TwoTone.Tag is EditExif -> Icons.TwoTone.ExifEdit is ImageCutter -> Icons.TwoTone.ScissorsSmall is AudioCoverExtractor -> Icons.TwoTone.Album is WallpapersExport -> Icons.Outlined.WallpaperAlt is AsciiArt -> Icons.Outlined.Ascii is AiTools -> Icons.TwoTone.Neurology is ColorLibrary -> Icons.TwoTone.FormatPaintVariant is PdfTools.Merge -> Icons.TwoTone.ImageCombine is PdfTools.Split -> Icons.TwoTone.SplitAlt is PdfTools.Rotate -> Icons.TwoTone.Rotate90Cw is PdfTools.Rearrange -> Icons.TwoTone.SwapVerticalCircle is PdfTools.PageNumbers -> Icons.TwoTone.Counter is PdfTools.OCR -> Icons.TwoTone.FindInPage is PdfTools.Watermark -> Icons.TwoTone.Watermark is PdfTools.Signature -> Icons.TwoTone.Stylus is PdfTools.Protect -> Icons.TwoTone.ShieldLock is PdfTools.Unlock -> Icons.TwoTone.KeyVariant is PdfTools.Compress -> Icons.TwoTone.Bolt is PdfTools.Grayscale -> Icons.TwoTone.FilterBAndW is PdfTools.Repair -> Icons.TwoTone.Build is PdfTools.Metadata -> Icons.TwoTone.TagText is PdfTools.RemovePages -> Icons.TwoTone.DeleteSweep is PdfTools.Crop -> Icons.TwoTone.CropSmall is PdfTools.Flatten -> Icons.TwoTone.Panorama is PdfTools.ExtractImages -> Icons.TwoTone.Unarchive is PdfTools.ZipConvert -> Icons.TwoTone.FolderZip is PdfTools.Print -> Icons.TwoTone.Print is PdfTools.Preview -> Icons.TwoTone.Preview is PdfTools.ImagesToPdf -> Icons.TwoTone.Scanner is PdfTools.ExtractPages -> Icons.TwoTone.ArtTrack is PdfTools.RemoveAnnotations -> Icons.TwoTone.BubbleDelete } internal object UriSerializer : KSerializer { override val descriptor = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING) override fun deserialize( decoder: Decoder ): AndroidUri = decoder.decodeString().toUri() override fun serialize( encoder: Encoder, value: AndroidUri ) = encoder.encodeString(value.toString()) } internal typealias Uri = @Serializable(UriSerializer::class) AndroidUri internal interface ScreenConstants { val typedEntries: List val entries: List val FEATURES_COUNT: Int companion object : ScreenConstants by ScreenConstantsImpl } private object ScreenConstantsImpl : ScreenConstants { override val typedEntries by lazy { listOf( ScreenGroup( entries = listOf( SingleEdit(), ResizeAndConvert(), FormatConversion(), Crop(), ImageCutter(), WeightResize(), LimitResize(), EditExif(), DeleteExif(), ), title = R.string.edit, selectedIcon = Icons.Rounded.MiniEditLarge, baseIcon = Icons.Outlined.MiniEditLarge ), ScreenGroup( entries = listOf( Filter(), Draw(), EraseBackground(), MarkupLayers(), AiTools(), CollageMaker(), ImageStitching(), ImageStacking(), ImageSplitting(), Watermarking(), GradientMaker(), NoiseGeneration, ), title = R.string.create, selectedIcon = Icons.Rounded.WandShine, baseIcon = Icons.Outlined.WandShine ), ScreenGroup( entries = listOf( PickColorFromImage(), RecognizeText(), Compare(), ImagePreview(), WallpapersExport, Base64Tools(), SvgMaker(), PaletteTools(), LoadNetImage(), ), title = R.string.image, selectedIcon = Icons.Rounded.FileImage, baseIcon = Icons.Outlined.FileImage ), ScreenGroup( entries = listOf( PdfTools, DocumentScanner, ScanQrCode(), ColorTools, ColorLibrary, GifTools(), Cipher(), ChecksumTools(), Zip(), AsciiArt(), JxlTools(), ApngTools(), WebpTools(), AudioCoverExtractor() ), title = R.string.tools, selectedIcon = Icons.Rounded.ServiceToolbox, baseIcon = Icons.Outlined.ServiceToolbox ) ) } override val entries by lazy { typedEntries.flatMap { it.entries } .plus(PdfTools.options) .distinctBy { it.id } .sortedBy { it.id } } override val FEATURES_COUNT = 82 + PdfTools.options.size } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/CenterCropPainter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.painter import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.painter.Painter private class CenterCropPainter( private val wrappedPainter: Painter ) : Painter() { override val intrinsicSize: Size get() = wrappedPainter.intrinsicSize override fun DrawScope.onDraw() { val srcW = intrinsicSize.width val srcH = intrinsicSize.height val dstW = size.width val dstH = size.height val srcAspect = srcW / srcH val dstAspect = dstW / dstH val scale = if (srcAspect > dstAspect) { dstH / srcH } else { dstW / srcW } val scaledW = (srcW * scale) val scaledH = (srcH * scale) val offsetX = (dstW - scaledW) / 2f val offsetY = (dstH - scaledH) / 2f clipRect { translate(left = offsetX, top = offsetY) { with(wrappedPainter) { draw( Size(scaledW, scaledH) ) } } } } } fun Painter.centerCrop(): Painter = CenterCropPainter(this) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/painter/RoundCornersPainter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.painter import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.graphics.painter.Painter private class RoundCornersPainter( private val wrappedPainter: Painter, private val cornerFactor: Float ) : Painter() { override val intrinsicSize: Size get() = wrappedPainter.intrinsicSize override fun DrawScope.onDraw() { val cornerRadius = size.minDimension / 2f * cornerFactor.coerceIn(0f, 1f) clipPath( Path().apply { addRoundRect( RoundRect( rect = Rect(Offset.Zero, size), cornerRadius = CornerRadius(cornerRadius, cornerRadius) ) ) } ) { with(wrappedPainter) { draw(size) } } } } fun Painter.roundCorners(size: Float): Painter = RoundCornersPainter(this, size) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.permission class PermissionResult { var permissionStatus: HashMap = hashMapOf() var finalStatus: PermissionStatus = PermissionStatus.NOT_GIVEN } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionStatus.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.permission enum class PermissionStatus { ALLOWED, NOT_GIVEN, DENIED_PERMANENTLY } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/permission/PermissionUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.permission import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.provider.Settings import androidx.core.content.ContextCompat import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.runBlocking object PermissionUtils { fun Context.checkPermissions( permissions: List ): PermissionResult { val permissionPreference = PermissionPreference(this) val permissionResult = PermissionResult() val permissionStatus: HashMap = hashMapOf() permissions.forEach { permission -> permissionPreference.setPermissionRequested(permission) if (hasPermissionAllowed(permission)) { permissionPreference.setPermissionAllowed(permission) permissionStatus[permission] = PermissionStatus.ALLOWED } else { val permissionRequestCount = permissionPreference.permissionRequestCount(permission) when { permissionRequestCount > 2 -> { permissionStatus[permission] = PermissionStatus.DENIED_PERMANENTLY } else -> { permissionStatus[permission] = PermissionStatus.NOT_GIVEN } } } } permissionResult.permissionStatus = permissionStatus val isAnyPermissionDeniedPermanently = permissionStatus.values.any { it == PermissionStatus.DENIED_PERMANENTLY } if (isAnyPermissionDeniedPermanently) { permissionResult.finalStatus = PermissionStatus.DENIED_PERMANENTLY return permissionResult } val isAnyPermissionNotGiven = permissionStatus.values.any { it == PermissionStatus.NOT_GIVEN } if (isAnyPermissionNotGiven) { permissionResult.finalStatus = PermissionStatus.NOT_GIVEN return permissionResult } permissionResult.finalStatus = PermissionStatus.ALLOWED return permissionResult } fun Context.askUserToRequestPermissionExplicitly() { val intent = Intent().apply { action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS data = Uri.fromParts("package", packageName, null) } startActivity(intent) } fun Context.hasPermissionAllowed(permission: String): Boolean { return ContextCompat.checkSelfPermission( this, permission ) == PackageManager.PERMISSION_GRANTED } fun Context.setPermissionsAllowed(permissions: List) { permissions.forEach { permission -> PermissionPreference(this).setPermissionAllowed(permission) } } } private val Context.dataStore by preferencesDataStore( name = "permissionPreference" ) private class PermissionPreference(private val context: Context) { private fun get( key: Preferences.Key, default: T ): T { return runBlocking { context.dataStore.edit {}[key] ?: default } } fun permissionRequestCount(permission: String): Int { return get(intPreferencesKey(permission), 0) } fun setPermissionRequested(permission: String) { runBlocking { context.dataStore.edit { it[intPreferencesKey(permission)] = (it[intPreferencesKey(permission)] ?: 0) + 1 } } } fun setPermissionAllowed(permission: String) { runBlocking { context.dataStore.edit { it[intPreferencesKey(permission)] = 0 } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/ImageToolboxCompositionLocals.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.ProvidedValue import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalUriHandler import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.settings.presentation.model.UiSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalEditPresetsController import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberEditPresetsController import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeSurface import com.t8rin.imagetoolbox.core.ui.utils.confetti.ConfettiHost import com.t8rin.imagetoolbox.core.ui.utils.helper.LocalFilterPreviewModelProvider import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberFilterPreviewProvider import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberSafeUriHandler import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.rememberEnhancedHapticFeedback import com.t8rin.imagetoolbox.core.ui.widget.other.ToastHost import kotlinx.coroutines.delay @Composable fun ImageToolboxCompositionLocals( settingsState: UiSettingsState, filterPreviewModel: ImageModel? = null, canSetDynamicFilterPreview: Boolean = false, currentScreen: Screen? = null, content: @Composable BoxScope.() -> Unit ) { val editPresetsController = rememberEditPresetsController() val customHapticFeedback = rememberEnhancedHapticFeedback(settingsState.hapticsStrength) val screenSize = rememberScreenSize() val previewProvider = filterPreviewModel?.let { rememberFilterPreviewProvider( preview = it, canSetDynamicFilterPreview = canSetDynamicFilterPreview ) } val safeUriHandler = rememberSafeUriHandler() val values = remember( settingsState, editPresetsController, customHapticFeedback, screenSize, filterPreviewModel, currentScreen, safeUriHandler ) { derivedStateOf { listOfNotNull( LocalSettingsState provides settingsState, LocalEditPresetsController provides editPresetsController, LocalFilterPreviewModelProvider providesOrNull previewProvider, LocalHapticFeedback provides customHapticFeedback, LocalScreenSize provides screenSize, LocalCurrentScreen provides currentScreen, LocalUriHandler provides safeUriHandler ).toTypedArray() } } CompositionLocalProvider( *values.value, content = { ImageToolboxThemeSurface { content() ConfettiHost() ToastHost() } } ) } val LocalCurrentScreen = compositionLocalOf { error("LocalCurrentScreen not present") } @Composable fun currentScreenTwoToneIcon( default: ImageVector ): ImageVector { val currentScreen = LocalCurrentScreen.current val screenIcon = currentScreen?.twoToneIcon var previous by rememberSaveable { mutableStateOf(currentScreen?.simpleName) } var currentIcon by remember { mutableStateOf(screenIcon ?: default) } LaunchedEffect(currentScreen) { if (currentScreen?.simpleName != previous) delay(600) previous = currentScreen?.simpleName currentIcon = screenIcon ?: default } return currentIcon } private infix fun ProvidableCompositionLocal.providesOrNull( value: T? ): ProvidedValue? = if (value != null) provides(value) else null ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalComponentActivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import androidx.activity.ComponentActivity import androidx.activity.compose.LocalActivity import androidx.compose.runtime.compositionLocalWithComputedDefaultOf val LocalComponentActivity = compositionLocalWithComputedDefaultOf { LocalActivity.currentValue as ComponentActivity } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalContainerShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.takeOrElse val LocalContainerShape = compositionLocalOf { null } val LocalContainerColor = compositionLocalOf { null } val SafeLocalContainerColor @Composable get() = LocalContainerColor.current?.takeOrElse { MaterialTheme.colorScheme.surfaceContainerLow } ?: MaterialTheme.colorScheme.surfaceContainerLow @Composable fun ProvideContainerDefaults( shape: Shape? = null, color: Color? = null, content: @Composable () -> Unit ) = CompositionLocalProvider( values = arrayOf( LocalContainerShape provides shape, LocalContainerColor provides color ), content = content ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalKeepAliveService.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import androidx.compose.runtime.compositionLocalOf import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService val LocalKeepAliveService = compositionLocalOf { error("No KeepAlive") } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalMetadataProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.MetadataProvider val LocalMetadataProvider = compositionLocalOf { error("MetadataProvider not registered") } @Composable fun rememberImageMetadataAsState(imageUri: Uri): State { val provider = LocalMetadataProvider.current val metadata = remember { mutableStateOf(null) } LaunchedEffect(imageUri) { metadata.value = provider.readMetadata(imageUri.toString()) } return metadata } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalResourceManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import androidx.compose.runtime.compositionLocalOf import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager val LocalResourceManager = compositionLocalOf { error("ResourceManager") } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalScreenSize.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.unit.Dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import com.t8rin.dynamic.theme.observeAsState val LocalScreenSize = compositionLocalOf { error("ScreenSize not present") } @ConsistentCopyVisibility data class ScreenSize internal constructor( val width: Dp, val height: Dp, val widthPx: Int, val heightPx: Int ) @Composable fun rememberScreenSize(): ScreenSize { val windowInfo = LocalWindowInfo.current return remember(windowInfo) { derivedStateOf { windowInfo.run { ScreenSize( width = containerDpSize.width, height = containerDpSize.height, widthPx = containerSize.width, heightPx = containerSize.height ) } } }.value } @Composable fun rememberCurrentLifecycleEvent(): Lifecycle.Event = LocalLifecycleOwner.current.lifecycle.observeAsState().value ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/provider/LocalWindowSizeClass.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.provider import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue val LocalWindowSizeClass = compositionLocalOf { error("SizeClass not present") } fun ComponentActivity.setContentWithWindowSizeClass( content: @Composable () -> Unit ) = setContent { LocalWindowSizeClass.ProvidesValue( value = calculateWindowSizeClass(this), content = content ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/ObjectSaverDelegate.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.state import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.t8rin.imagetoolbox.core.domain.saving.ObjectSaver import com.t8rin.imagetoolbox.core.domain.utils.ReadWriteDelegate import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch fun ObjectSaver.savable( scope: CoroutineScope, initial: O, key: String = initial::class.simpleName.toString(), delay: Long = 0, ): ReadWriteDelegate = ObjectSaverDelegate( delay = delay, saver = this, scope = scope, initial = initial, key = key ) private class ObjectSaverDelegate( delay: Long, private val saver: ObjectSaver, private val scope: CoroutineScope, initial: O, private val key: String ) : ReadWriteDelegate { private var value: O by mutableStateOf(initial) init { scope.launch { if (delay > 0) delay(delay) value = saver.restoreObject( key = key, kClass = initial::class ) ?: initial } } override fun set(value: O) { this.value = value scope.launch { saver.saveObject( key = key, value = value ) } } override fun get(): O = value } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/utils/state/Update.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.state import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember inline fun MutableState.update( transform: (T) -> T ): T = run { transform(this.value).also { this.value = it } } inline fun MutableState.updateNotNull( transform: (T) -> T ): T? = run { this.value?.let { nonNull -> transform(nonNull).also { this.value = it } } ?: this.value } inline fun MutableState.update( onValueChanged: () -> Unit, transform: (T) -> T ): T = run { transform(this.value).also { if (this.value != it) onValueChanged() this.value = it } } inline fun MutableState.updateIf( predicate: (T) -> Boolean, transform: (T) -> T ): MutableState = apply { if (predicate(this.value)) { this.value = transform(this.value) } } @Composable fun derivedValueOf( vararg keys: Any?, calculation: () -> T ): T = remember(keys) { derivedStateOf(calculation) }.value //fun T.asFun(): Function1 { // val value = this // return { value } //} ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveBottomScaffoldLayoutScreen.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SheetValue import androidx.compose.material3.Surface import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberStandardBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.animation.fancySlideTransition import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitBackHandler import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.onSwipeDown import kotlinx.coroutines.launch @Composable fun AdaptiveBottomScaffoldLayoutScreen( title: @Composable () -> Unit, onGoBack: () -> Unit, shouldDisableBackHandler: Boolean, actions: @Composable RowScope.(BottomSheetScaffoldState) -> Unit, modifier: Modifier = Modifier, topAppBarPersistentActions: @Composable RowScope.(BottomSheetScaffoldState) -> Unit = {}, mainContent: @Composable () -> Unit, mainContentWeight: Float = 0.5f, controls: @Composable ColumnScope.(BottomSheetScaffoldState) -> Unit, buttons: @Composable (actions: @Composable RowScope.() -> Unit) -> Unit, noDataControls: @Composable () -> Unit = {}, canShowScreenData: Boolean, showActionsInTopAppBar: Boolean = true, collapseTopAppBarWhenHaveData: Boolean = true, autoClearFocus: Boolean = true, enableNoDataScroll: Boolean = true ) { val isPortrait by isPortraitOrientationAsState() val screenWidthPx = LocalScreenSize.current.widthPx val settingsState = LocalSettingsState.current val scrollBehavior = if (collapseTopAppBarWhenHaveData && canShowScreenData) null else TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = rememberStandardBottomSheetState( confirmValueChange = { when (it) { SheetValue.Hidden -> false else -> true } } ) ) val focus = LocalFocusManager.current LaunchedEffect(scaffoldState.bottomSheetState.currentValue) { if (scaffoldState.bottomSheetState.currentValue != SheetValue.Expanded) { focus.clearFocus() } } val content: @Composable (PaddingValues) -> Unit = { paddingValues -> Box( Modifier .fillMaxSize() .padding(paddingValues) .then( if (scrollBehavior != null) { Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) } else Modifier ) ) { Scaffold( topBar = { EnhancedTopAppBar( type = if (collapseTopAppBarWhenHaveData && canShowScreenData) EnhancedTopAppBarType.Normal else EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior, title = title, navigationIcon = { EnhancedIconButton( onClick = onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, actions = { if (!isPortrait && canShowScreenData && showActionsInTopAppBar) actions( scaffoldState ) topAppBarPersistentActions(scaffoldState) }, ) }, contentWindowInsets = WindowInsets() ) { contentPadding -> AnimatedContent( targetState = canShowScreenData, modifier = Modifier .fillMaxSize() .padding(contentPadding), transitionSpec = { fancySlideTransition( isForward = targetState, screenWidthPx = screenWidthPx ) } ) { canShowScreenData -> if (canShowScreenData) { if (isPortrait) { mainContent() } else { Row( verticalAlignment = Alignment.CenterVertically ) { Box( Modifier .zIndex(-100f) .container(shape = RectangleShape, resultPadding = 0.dp) .weight(0.8f) ) { mainContent() } val scrollState = rememberScrollState() Column( Modifier .weight(mainContentWeight) .enhancedVerticalScroll(scrollState) ) { controls(scaffoldState) } buttons { actions(scaffoldState) } } } } else { Column( modifier = Modifier .then( if (enableNoDataScroll) { val scrollState = rememberScrollState() Modifier .fillMaxSize() .enhancedVerticalScroll(scrollState) .padding( bottom = 88.dp, top = 20.dp, start = 20.dp, end = 20.dp ) .windowInsetsPadding( WindowInsets.navigationBars.union( WindowInsets.displayCutout.only( WindowInsetsSides.Horizontal ) ) ) } else Modifier ), horizontalAlignment = Alignment.CenterHorizontally ) { noDataControls() } } } } if (!canShowScreenData) { Box( modifier = Modifier.align(settingsState.fabAlignment) ) { buttons { actions(scaffoldState) } } } } } Surface( color = MaterialTheme.colorScheme.background, modifier = modifier.clearFocusOnTap(autoClearFocus) ) { AnimatedContent( targetState = isPortrait && canShowScreenData, transitionSpec = { fancySlideTransition( isForward = targetState, screenWidthPx = screenWidthPx ) }, modifier = Modifier.fillMaxSize() ) { useScaffold -> if (useScaffold) { val screenHeight = LocalScreenSize.current.height val sheetSwipeEnabled = scaffoldState.bottomSheetState.currentValue == SheetValue.PartiallyExpanded && !scaffoldState.bottomSheetState.isAnimationRunning BottomSheetScaffold( modifier = Modifier.fillMaxSize(), scaffoldState = scaffoldState, sheetPeekHeight = 80.dp + WindowInsets.navigationBars.asPaddingValues() .calculateBottomPadding(), sheetDragHandle = null, sheetShape = RectangleShape, sheetSwipeEnabled = sheetSwipeEnabled, sheetContent = { Scaffold( modifier = Modifier .heightIn(max = screenHeight * 0.7f) .clearFocusOnTap(), topBar = { val scope = rememberCoroutineScope() Box( modifier = Modifier.onSwipeDown(!sheetSwipeEnabled) { scope.launch { scaffoldState.bottomSheetState.partialExpand() } } ) { buttons { actions(scaffoldState) } } }, contentWindowInsets = WindowInsets() ) { contentPadding -> ProvideContainerDefaults( color = EnhancedBottomSheetDefaults.contentContainerColor ) { val scrollState = rememberScrollState() Column( modifier = Modifier .enhancedVerticalScroll(scrollState) .padding(contentPadding) ) { controls(scaffoldState) } } } }, content = content ) } else { Box( modifier = Modifier.fillMaxSize() ) { content(PaddingValues()) } } } } ExitBackHandler( enabled = !shouldDisableBackHandler, onBack = onGoBack ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/AdaptiveLayoutScreen.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.animation.fancySlideTransition import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitBackHandler import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.imageStickyHeader import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.utils.isExpanded import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberAvailableHeight import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberImageState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun AdaptiveLayoutScreen( title: @Composable () -> Unit, onGoBack: () -> Unit, shouldDisableBackHandler: Boolean, actions: @Composable RowScope.() -> Unit, topAppBarPersistentActions: @Composable RowScope.() -> Unit = {}, imagePreview: @Composable () -> Unit, controls: (@Composable ColumnScope.(LazyListState) -> Unit)?, buttons: @Composable (actions: @Composable RowScope.() -> Unit) -> Unit, noDataControls: @Composable () -> Unit = {}, canShowScreenData: Boolean, forceImagePreviewToMax: Boolean = false, contentPadding: Dp = 20.dp, showImagePreviewAsStickyHeader: Boolean = true, autoClearFocus: Boolean = true, placeImagePreview: Boolean = true, useRegularStickyHeader: Boolean = false, addHorizontalCutoutPaddingIfNoPreview: Boolean = true, showActionsInTopAppBar: Boolean = true, underTopAppBarContent: (@Composable ColumnScope.() -> Unit)? = null, insetsForNoData: WindowInsets = WindowInsets.navigationBars.union( WindowInsets.displayCutout.only( WindowInsetsSides.Horizontal ) ), listState: LazyListState = rememberLazyListState(), placeControlsSeparately: Boolean = false, portraitTopPadding: Dp = 0.dp ) { val isPortrait by isPortraitOrientationAsState() val settingsState = LocalSettingsState.current var imageState by rememberImageState() val topAppBarState = rememberTopAppBarState() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( state = topAppBarState, canScroll = { !imageState.isExpanded() && !forceImagePreviewToMax } ) LaunchedEffect(imageState, forceImagePreviewToMax) { if (imageState.isExpanded() || forceImagePreviewToMax) { while (topAppBarState.heightOffset > topAppBarState.heightOffsetLimit) { topAppBarState.heightOffset -= 5f delay(1) } } } Surface( color = MaterialTheme.colorScheme.background, modifier = Modifier.clearFocusOnTap(autoClearFocus) ) { Box( modifier = Modifier .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection) ) { Scaffold( modifier = Modifier.fillMaxSize(), topBar = { Column { EnhancedTopAppBar( type = EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior, title = title, drawHorizontalStroke = underTopAppBarContent == null, navigationIcon = { EnhancedIconButton( onClick = onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, actions = { if (!isPortrait && canShowScreenData && showActionsInTopAppBar) actions() topAppBarPersistentActions() } ) underTopAppBarContent?.invoke(this) } }, contentWindowInsets = WindowInsets() ) { scaffoldPadding -> val screenWidthPx = LocalScreenSize.current.widthPx AnimatedContent( targetState = canShowScreenData, transitionSpec = { fancySlideTransition( isForward = targetState, screenWidthPx = screenWidthPx ) }, modifier = Modifier .fillMaxSize() .padding(scaffoldPadding) ) { canShowScreenData -> Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, ) { val direction = LocalLayoutDirection.current if (!isPortrait && canShowScreenData && placeImagePreview) { Box( modifier = Modifier .then( if (controls != null) { Modifier.container( shape = RectangleShape, color = MaterialTheme.colorScheme.surfaceContainerLow ) } else Modifier ) .fillMaxHeight() .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ) .weight(1.2f) .padding(20.dp), contentAlignment = Alignment.Center ) { imagePreview() } } if (placeControlsSeparately && controls != null && canShowScreenData) { Column( modifier = Modifier .weight(1f) .fillMaxHeight() .clipToBounds() ) { controls(listState) } } else { val internalHeight = rememberAvailableHeight( imageState = imageState, expanded = forceImagePreviewToMax ) val cutout = if (!placeImagePreview && addHorizontalCutoutPaddingIfNoPreview) { WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) } else 0.dp var isScrolled by rememberSaveable(canShowScreenData) { mutableStateOf(false) } val scope = rememberCoroutineScope { Dispatchers.Main.immediate } LazyColumn( state = listState, contentPadding = PaddingValues( bottom = WindowInsets .navigationBars .union(WindowInsets.ime) .asPaddingValues() .calculateBottomPadding() + (if (!isPortrait && canShowScreenData) contentPadding else 100.dp), top = if (!canShowScreenData || !isPortrait) contentPadding else portraitTopPadding, start = contentPadding + cutout, end = contentPadding ), modifier = Modifier .weight( if (controls == null) 0.01f else 1f ) .fillMaxHeight() .clipToBounds(), flingBehavior = enhancedFlingBehavior() ) { if (useRegularStickyHeader && isPortrait && canShowScreenData && showImagePreviewAsStickyHeader && placeImagePreview) { stickyHeader { imagePreview() } } else { imageStickyHeader( visible = isPortrait && canShowScreenData && showImagePreviewAsStickyHeader && placeImagePreview, internalHeight = internalHeight, imageState = imageState, onStateChange = { imageState = it }, imageBlock = imagePreview, onGloballyPositioned = { if (!isScrolled) { scope.launch { delay(200) listState.animateScrollToItem(0) isScrolled = true } } } ) } item { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { if (canShowScreenData) { AnimatedVisibility( visible = !showImagePreviewAsStickyHeader && isPortrait && placeImagePreview ) { imagePreview() } if (controls != null) controls(listState) } else { Box( modifier = Modifier.windowInsetsPadding( insetsForNoData ) ) { noDataControls() } } } } } } AnimatedVisibility(!isPortrait && canShowScreenData) { buttons(actions) } } } } AnimatedVisibility( visible = isPortrait || !canShowScreenData, modifier = Modifier.align(settingsState.fabAlignment) ) { buttons(actions) } ExitBackHandler( enabled = !shouldDisableBackHandler, onBack = onGoBack ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/BottomButtonsBlock.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Save import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButtonType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.ProvideFABType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke @Composable fun BottomButtonsBlock( isNoData: Boolean, onSecondaryButtonClick: () -> Unit, onSecondaryButtonLongClick: (() -> Unit)? = null, secondaryButtonIcon: ImageVector = Icons.Rounded.AddPhotoAlt, secondaryButtonText: String = stringResource(R.string.pick_image_alt), onPrimaryButtonClick: () -> Unit, onPrimaryButtonLongClick: (() -> Unit)? = null, primaryButtonIcon: ImageVector = Icons.Rounded.Save, primaryButtonText: String = "", isPrimaryButtonVisible: Boolean = true, isSecondaryButtonVisible: Boolean = true, showNullDataButtonAsContainer: Boolean = false, middleFab: (@Composable ColumnScope.() -> Unit)? = null, actions: @Composable RowScope.() -> Unit, isPrimaryButtonEnabled: Boolean = true, showMiddleFabInRow: Boolean = false, isScreenHaveNoDataContent: Boolean = false, primaryButtonContainerColor: Color = MaterialTheme.colorScheme.primaryContainer, primaryButtonContentColor: Color = contentColorFor(primaryButtonContainerColor), enableHorizontalStroke: Boolean = true ) { val isPortrait by isPortraitOrientationAsState() val spacing = 8.dp AnimatedContent( targetState = Triple(isNoData, isPortrait, isScreenHaveNoDataContent), transitionSpec = { fadeIn() + slideInVertically { it / 2 } togetherWith fadeOut() + slideOutVertically { it / 2 } } ) { (isEmptyState, portrait, isHaveNoDataContent) -> if (isEmptyState) { val cutout = WindowInsets.displayCutout.only( WindowInsetsSides.Horizontal ) val button = @Composable { Row( modifier = Modifier .windowInsetsPadding( WindowInsets.navigationBars.union(cutout) ) .padding(16.dp), horizontalArrangement = Arrangement.spacedBy(spacing), verticalAlignment = Alignment.CenterVertically ) { val middle = @Composable { if (showMiddleFabInRow && middleFab != null) { ProvideFABType(EnhancedFloatingActionButtonType.SecondaryHorizontal) { Column( content = middleFab ) } } } if (!isPrimaryButtonVisible) middle() EnhancedFloatingActionButton( onClick = onSecondaryButtonClick, onLongClick = onSecondaryButtonLongClick, content = { Spacer(Modifier.width(16.dp)) Icon( imageVector = secondaryButtonIcon, contentDescription = null ) Spacer(Modifier.width(16.dp)) Text(secondaryButtonText) Spacer(Modifier.width(16.dp)) } ) if (isPrimaryButtonVisible) middle() } } if (showNullDataButtonAsContainer) { if (!isPortrait && isHaveNoDataContent) { Column( modifier = Modifier .fillMaxHeight() .background( MaterialTheme.colorScheme.surfaceContainerLow ) .consumeWindowInsets(cutout.only(WindowInsetsSides.Start)), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { button() } } else { Row( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke( top = true, enabled = enableHorizontalStroke ) .background( MaterialTheme.colorScheme.surfaceContainer ), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { button() } } } else { button() } } else if (portrait) { BottomAppBar( modifier = Modifier.drawHorizontalStroke( top = true, enabled = enableHorizontalStroke ), actions = actions, floatingActionButton = { Row { val middle = @Composable { AnimatedVisibility(visible = showMiddleFabInRow) { middleFab?.let { ProvideFABType(EnhancedFloatingActionButtonType.SecondaryHorizontal) { Column( modifier = Modifier.padding(end = spacing), content = { it() } ) } } } } if (!isPrimaryButtonVisible) middle() AnimatedVisibility(visible = isSecondaryButtonVisible) { EnhancedFloatingActionButton( onClick = onSecondaryButtonClick, onLongClick = onSecondaryButtonLongClick, containerColor = takeColorFromScheme { if (isPrimaryButtonVisible) tertiaryContainer else primaryContainer }, type = if (isPrimaryButtonVisible) { EnhancedFloatingActionButtonType.SecondaryHorizontal } else { EnhancedFloatingActionButtonType.Primary }, modifier = Modifier.padding(end = spacing) ) { Icon( imageVector = secondaryButtonIcon, contentDescription = null ) } } if (isPrimaryButtonVisible) middle() AnimatedVisibility(visible = isPrimaryButtonVisible) { EnhancedFloatingActionButton( onClick = onPrimaryButtonClick.takeIf { isPrimaryButtonEnabled }, onLongClick = onPrimaryButtonLongClick.takeIf { isPrimaryButtonEnabled }, interactionSource = remember { MutableInteractionSource() }.takeIf { isPrimaryButtonEnabled }, containerColor = takeColorFromScheme { if (isPrimaryButtonEnabled) primaryButtonContainerColor else surfaceContainerHighest }, contentColor = takeColorFromScheme { if (isPrimaryButtonEnabled) primaryButtonContentColor else outline } ) { AnimatedContent( targetState = primaryButtonIcon to primaryButtonText, transitionSpec = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() } ) { (icon, text) -> Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { if (text.isNotEmpty()) { Spacer(Modifier.width(16.dp)) } Icon( imageVector = icon, contentDescription = null ) if (text.isNotEmpty()) { Spacer(Modifier.width(16.dp)) Text(text) Spacer(Modifier.width(16.dp)) } } } } } } } ) } else { val direction = LocalLayoutDirection.current Column( modifier = Modifier .fillMaxHeight() .container( shape = RectangleShape, color = MaterialTheme.colorScheme.surfaceContainerLow ) .enhancedVerticalScroll(rememberScrollState()) .padding(horizontal = 16.dp) .navigationBarsPadding() .padding( end = WindowInsets.displayCutout .asPaddingValues() .calculateEndPadding(direction) ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { val middle = @Composable { middleFab?.let { Spacer(Modifier.height(spacing)) ProvideFABType(EnhancedFloatingActionButtonType.SecondaryVertical) { it() } } } Row { actions() } if (!isPrimaryButtonVisible) middle() Spacer(Modifier.height(spacing)) AnimatedVisibility(visible = isSecondaryButtonVisible) { EnhancedFloatingActionButton( onClick = onSecondaryButtonClick, onLongClick = onSecondaryButtonLongClick, containerColor = takeColorFromScheme { if (isPrimaryButtonVisible) tertiaryContainer else primaryContainer }, type = if (isPrimaryButtonVisible) { EnhancedFloatingActionButtonType.SecondaryVertical } else { EnhancedFloatingActionButtonType.Primary } ) { Icon( imageVector = secondaryButtonIcon, contentDescription = null ) } } if (isPrimaryButtonVisible) middle() AnimatedVisibility(visible = isPrimaryButtonVisible) { EnhancedFloatingActionButton( onClick = onPrimaryButtonClick.takeIf { isPrimaryButtonEnabled }, onLongClick = onPrimaryButtonLongClick.takeIf { isPrimaryButtonEnabled }, interactionSource = remember { MutableInteractionSource() }.takeIf { isPrimaryButtonEnabled }, containerColor = takeColorFromScheme { if (isPrimaryButtonEnabled) primaryButtonContainerColor else surfaceContainerHighest }, contentColor = takeColorFromScheme { if (isPrimaryButtonEnabled) primaryButtonContentColor else outline }, modifier = Modifier.padding(top = spacing) ) { AnimatedContent( targetState = primaryButtonIcon to primaryButtonText, transitionSpec = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() } ) { (icon, text) -> Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { if (text.isNotEmpty()) { Spacer(Modifier.width(16.dp)) } Icon( imageVector = icon, contentDescription = null ) if (text.isNotEmpty()) { Spacer(Modifier.width(16.dp)) Text(text) Spacer(Modifier.width(16.dp)) } } } } } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/CompareButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Compare import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton @Composable fun CompareButton( onClick: () -> Unit, visible: Boolean ) { AnimatedVisibility( visible = visible, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = onClick ) { Icon( imageVector = Icons.Outlined.Compare, contentDescription = stringResource(R.string.compare) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/EraseModeButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.animation.animateColorAsState import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Eraser import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton @Composable fun EraseModeButton( selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true ) { EnhancedIconButton( modifier = modifier, enabled = enabled, containerColor = animateColorAsState( if (selected) MaterialTheme.colorScheme.mixedContainer else Color.Transparent ).value, contentColor = animateColorAsState( if (selected) MaterialTheme.colorScheme.onMixedContainer else MaterialTheme.colorScheme.onSurface ).value, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = onClick ) { Icon( imageVector = Icons.Rounded.Eraser, contentDescription = stringResource(R.string.erase_mode) ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/MediaCheckBox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.outlined.Circle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.suggestContainerColorBy import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun MediaCheckBox( modifier: Modifier = Modifier, isChecked: Boolean, selectionIndex: Int = -1, onCheck: (() -> Unit)? = null, checkedIcon: ImageVector = Icons.Filled.CheckCircle, checkedColor: Color = MaterialTheme.colorScheme.primary, uncheckedColor: Color = MaterialTheme.colorScheme.onSurface, addContainer: Boolean = false ) { val image = if (isChecked) { checkedIcon } else Icons.Outlined.Circle val color by animateColorAsState( if (isChecked) checkedColor else uncheckedColor ) if (onCheck != null) { EnhancedIconButton( onClick = onCheck, modifier = modifier, containerColor = animateColorAsState( if (addContainer) MaterialTheme.colorScheme.suggestContainerColorBy(color) else Color.Transparent ).value ) { AnimatedContent( targetState = image, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { icon -> Icon( imageVector = icon, contentDescription = null, tint = color ) } } } else { AnimatedContent( targetState = Triple(isChecked, image, selectionIndex), transitionSpec = { fadeIn() togetherWith fadeOut() } ) { (isChecked, image, selectionIndex) -> if (selectionIndex >= 0) { if (isChecked) { Box( modifier = modifier .size(24.dp) .padding(2.dp) .clip(ShapeDefaults.circle) .background(color), contentAlignment = Alignment.Center ) { AutoSizeText( text = (selectionIndex + 1).toString(), color = contentColorFor(color), style = MaterialTheme.typography.bodySmall ) } } else { Icon( imageVector = image, modifier = modifier, contentDescription = null, tint = color ) } } else { Icon( imageVector = image, modifier = modifier, contentDescription = null, tint = color ) } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PagerScrollPanel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.PagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBackIos import androidx.compose.material.icons.automirrored.rounded.ArrowForwardIos import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import kotlinx.coroutines.launch @Composable fun PagerScrollPanel( pagerState: PagerState, modifier: Modifier = Modifier ) { val scope = rememberCoroutineScope() Row( modifier = modifier.container( shape = ShapeDefaults.circle, resultPadding = 4.dp ), verticalAlignment = Alignment.CenterVertically ) { EnhancedIconButton( onClick = { scope.launch { pagerState.animateScrollToPage( (pagerState.currentPage - 1).takeIf { it >= 0 } ?: (pagerState.pageCount - 1) ) } }, containerColor = MaterialTheme.colorScheme.surface ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBackIos, contentDescription = null, modifier = Modifier.size(20.dp) ) } Text( text = "${pagerState.currentPage + 1} / ${pagerState.pageCount}", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, fontSize = 18.sp ) EnhancedIconButton( onClick = { scope.launch { pagerState.animateScrollToPage( (pagerState.currentPage + 1) % pagerState.pageCount ) } }, containerColor = MaterialTheme.colorScheme.surface ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowForwardIos, contentDescription = null, modifier = Modifier.size(20.dp) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/PanModeButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.animation.animateColorAsState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FrontHand import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton @Composable fun PanModeButton( selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ) { EnhancedIconButton( modifier = modifier, containerColor = animateColorAsState( if (selected) MaterialTheme.colorScheme.primary else Color.Transparent ).value, contentColor = animateColorAsState( if (selected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface ).value, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = onClick ) { Icon( imageVector = Icons.Rounded.FrontHand, contentDescription = stringResource(R.string.draw) ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShareButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Image import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material.icons.rounded.Share import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults @Composable fun ShareButton( enabled: Boolean = true, onShare: () -> Unit, onEdit: (() -> Unit)? = null, onCopy: (() -> Unit)? = null, dialogTitle: String = stringResource(R.string.image), dialogIcon: ImageVector = Icons.Outlined.Image ) { var showSelectionDialog by rememberSaveable { mutableStateOf(false) } EnhancedIconButton( onClick = { if (onCopy != null || onEdit != null) { showSelectionDialog = true } else { onShare() } }, enabled = enabled ) { Icon( imageVector = Icons.Rounded.Share, contentDescription = stringResource(R.string.share) ) } EnhancedAlertDialog( visible = showSelectionDialog && (onEdit != null || onCopy != null), onDismissRequest = { showSelectionDialog = false }, confirmButton = { EnhancedButton( onClick = { showSelectionDialog = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.cancel)) } }, title = { Text(dialogTitle) }, icon = { Icon( imageVector = dialogIcon, contentDescription = null ) }, text = { val scrollState = rememberScrollState() Column( modifier = Modifier .fadingEdges( scrollableState = scrollState, isVertical = true ) .enhancedVerticalScroll(scrollState), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { PreferenceItem( title = stringResource(R.string.share), shape = ShapeDefaults.top, startIcon = Icons.Rounded.Share, onClick = { showSelectionDialog = false onShare() }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) if (onCopy != null) { Spacer(Modifier.height(4.dp)) PreferenceItem( title = stringResource(R.string.copy), shape = if (onEdit == null) ShapeDefaults.bottom else ShapeDefaults.center, startIcon = Icons.Rounded.ContentCopy, onClick = { showSelectionDialog = false onCopy() }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) } if (onEdit != null) { Spacer(Modifier.height(4.dp)) PreferenceItem( title = stringResource(R.string.edit), shape = ShapeDefaults.bottom, startIcon = Icons.Rounded.MiniEdit, onClick = { showSelectionDialog = false onEdit() }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) } } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ShowOriginalButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.History import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction @Composable fun ShowOriginalButton( canShow: Boolean = true, onStateChange: (Boolean) -> Unit ) { val haptics = LocalHapticFeedback.current val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = ShapeDefaults.circle, pressedShape = ShapeDefaults.mini, interactionSource = interactionSource ) Box( modifier = Modifier .clip(shape) .indication( interactionSource = interactionSource, indication = LocalIndication.current ) .pointerInput(Unit) { detectTapGestures( onPress = { haptics.longPress() val press = PressInteraction.Press(it) interactionSource.emit(press) if (canShow) onStateChange(true) tryAwaitRelease() onStateChange(false) interactionSource.emit( PressInteraction.Release( press ) ) } ) } ) { Icon( imageVector = Icons.Rounded.History, contentDescription = stringResource(R.string.original), tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier .align(Alignment.Center) .padding(8.dp) ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/SupportingButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction @Composable fun SupportingButton( onClick: () -> Unit, modifier: Modifier = Modifier, icon: ImageVector = Icons.Outlined.Info, containerColor: Color = MaterialTheme.colorScheme.secondaryContainer, contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor), style: TextStyle = LocalTextStyle.current, shape: Shape = AutoCircleShape(), iconPadding: Dp = 1.dp ) { val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = shape, pressedShape = ShapeDefaults.extraSmall, interactionSource = interactionSource ) Icon( imageVector = icon, contentDescription = icon.name, tint = contentColor, modifier = modifier .clip(shape) .background(containerColor) .hapticsClickable( onClick = onClick, interactionSource = interactionSource, indication = LocalIndication.current ) .padding(iconPadding) .size( with(LocalDensity.current) { style.fontSize.toDp() } ) ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/buttons/ZoomButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.buttons import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ZoomIn import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton @Composable fun ZoomButton( onClick: () -> Unit, visible: Boolean ) { AnimatedVisibility( visible = visible, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = onClick ) { Icon( imageVector = Icons.Rounded.ZoomIn, contentDescription = stringResource(R.string.zoom) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/AvailableColorTuplesSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.EmojiEmotions import androidx.compose.material.icons.rounded.AddCircleOutline import androidx.compose.material.icons.rounded.Contrast import androidx.compose.material.icons.rounded.InvertColors import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.ColorTupleItem import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.dynamic.theme.rememberColorScheme import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.nearestFor import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.EditAlt import com.t8rin.imagetoolbox.core.resources.icons.PaletteBox import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.presentation.model.defaultColorTuple import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalSheetDragHandle import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedHorizontalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.palette_selection.PaletteStyleSelection import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.delay @Composable fun AvailableColorTuplesSheet( visible: Boolean, onDismiss: () -> Unit, colorTupleList: List, currentColorTuple: ColorTuple, onOpenColorPicker: () -> Unit, colorPicker: @Composable () -> Unit, onPickTheme: (ColorTuple) -> Unit, onUpdateThemeContrast: (Float) -> Unit, onThemeStyleSelected: (PaletteStyle) -> Unit, onToggleInvertColors: () -> Unit, onToggleUseEmojiAsPrimaryColor: () -> Unit, onUpdateColorTuples: (List) -> Unit, ) { var showEditColorPicker by rememberSaveable { mutableStateOf(false) } val settingsState = LocalSettingsState.current EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!it) onDismiss() }, dragHandle = { EnhancedModalSheetDragHandle { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { TitleItem( text = stringResource(R.string.color_scheme), icon = Icons.Outlined.PaletteBox ) } } }, title = { var showConfirmDeleteDialog by remember { mutableStateOf(false) } EnhancedAlertDialog( visible = showConfirmDeleteDialog, onDismissRequest = { showConfirmDeleteDialog = false }, confirmButton = { EnhancedButton( onClick = { showConfirmDeleteDialog = false } ) { Text(stringResource(R.string.cancel)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showConfirmDeleteDialog = false if ((colorTupleList - currentColorTuple).isEmpty()) { onPickTheme(defaultColorTuple) } else { colorTupleList.nearestFor(currentColorTuple) ?.let { onPickTheme(it) } } onUpdateColorTuples(colorTupleList - currentColorTuple) } ) { Text(stringResource(R.string.delete)) } }, title = { Text(stringResource(R.string.delete_color_scheme_title)) }, icon = { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete) ) }, text = { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { ColorTupleItem( colorTuple = currentColorTuple, modifier = Modifier .padding(2.dp) .size(64.dp) .container( shape = MaterialStarShape, color = rememberColorScheme( isDarkTheme = settingsState.isNightMode, amoledMode = settingsState.isAmoledMode, colorTuple = currentColorTuple, contrastLevel = settingsState.themeContrastLevel, style = settingsState.themeStyle, dynamicColor = false, isInvertColors = settingsState.isInvertThemeColors ).surfaceVariant.copy(alpha = 0.8f), borderColor = MaterialTheme.colorScheme.outlineVariant(0.2f), resultPadding = 0.dp ) .padding(3.dp) .clip(ShapeDefaults.circle), backgroundColor = Color.Transparent ) Spacer(modifier = Modifier.height(8.dp)) Text(stringResource(R.string.delete_color_scheme_warn)) } } ) Row { AnimatedVisibility( visible = currentColorTuple !in ColorTupleDefaults.defaultColorTuples && !settingsState.useEmojiAsPrimaryColor ) { Row { EnhancedButton( containerColor = MaterialTheme.colorScheme.errorContainer, contentColor = MaterialTheme.colorScheme.onErrorContainer, onClick = { showConfirmDeleteDialog = true }, borderColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.errorContainer ) ) { Icon( imageVector = Icons.Rounded.Delete, contentDescription = stringResource(R.string.delete) ) } Spacer(Modifier.width(8.dp)) } } EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showEditColorPicker = true } ) { Icon( imageVector = Icons.Rounded.EditAlt, contentDescription = stringResource(R.string.edit) ) } } }, sheetContent = { val isPortrait by isPortraitOrientationAsState() val isPickersEnabled = !settingsState.useEmojiAsPrimaryColor val palette = @Composable { PaletteStyleSelection( onThemeStyleSelected = onThemeStyleSelected, shape = ShapeDefaults.top, ) } val invertColors = @Composable { PreferenceRowSwitch( title = stringResource(R.string.invert_colors), subtitle = stringResource(R.string.invert_colors_sub), checked = settingsState.isInvertThemeColors, modifier = Modifier, startIcon = Icons.Rounded.InvertColors, shape = ShapeDefaults.center, onClick = { onToggleInvertColors() } ) } val emojiAsPrimary = @Composable { PreferenceRowSwitch( title = stringResource(R.string.emoji_as_color_scheme), subtitle = stringResource(R.string.emoji_as_color_scheme_sub), checked = settingsState.useEmojiAsPrimaryColor, modifier = Modifier, startIcon = Icons.Outlined.EmojiEmotions, shape = ShapeDefaults.center, onClick = { onToggleUseEmojiAsPrimaryColor() } ) } val contrast = @Composable { EnhancedSliderItem( value = settingsState.themeContrastLevel.toFloat().roundToTwoDigits(), icon = Icons.Rounded.Contrast, title = stringResource(id = R.string.contrast), valueRange = -1f..1f, shape = ShapeDefaults.bottom, onValueChange = { }, internalStateTransformation = { it.roundToTwoDigits() }, steps = 198, onValueChangeFinished = { onUpdateThemeContrast(it) }, modifier = Modifier.padding(bottom = 4.dp) ) } val defaultValues = @Composable { val listState = rememberScrollState() val defList = ColorTupleDefaults.defaultColorTuples val density = LocalDensity.current val cellSize = 60.dp LaunchedEffect(visible, isPickersEnabled) { delay(100) // delay for sheet init if (currentColorTuple in defList) { listState.scrollTo(defList.indexOf(currentColorTuple) * with(density) { cellSize.roundToPx() }) } } Column( modifier = Modifier .padding(bottom = 16.dp) .container(ShapeDefaults.extraLarge) ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( fontWeight = FontWeight.Medium, text = stringResource(R.string.simple_variants), color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(top = 16.dp), fontSize = 18.sp ) } Box { Row( modifier = Modifier .enhancedHorizontalScroll(listState) .padding(PaddingValues(16.dp)), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { defList.forEach { colorTuple -> ColorTuplePreview( isDefaultItem = true, modifier = Modifier.size(cellSize), colorTuple = colorTuple, appColorTuple = currentColorTuple, onClick = { onPickTheme(colorTuple) } ) } } Box( modifier = Modifier .align(Alignment.CenterStart) .width(8.dp) .height(64.dp) .background( brush = Brush.horizontalGradient( 0f to MaterialTheme.colorScheme.surfaceContainer, 1f to Color.Transparent ) ) ) Box( modifier = Modifier .align(Alignment.CenterEnd) .width(8.dp) .height(64.dp) .background( brush = Brush.horizontalGradient( 0f to Color.Transparent, 1f to MaterialTheme.colorScheme.surfaceContainer ) ) ) } } } Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { if (!isPortrait) { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .weight(0.8f) .padding(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { palette() invertColors() emojiAsPrimary() contrast() } } LazyVerticalGrid( modifier = Modifier.weight(1f), columns = GridCells.Adaptive(64.dp), contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy( space = 4.dp, alignment = Alignment.CenterHorizontally ), verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), flingBehavior = enhancedFlingBehavior() ) { if (isPortrait) { item( span = { GridItemSpan(maxLineSpan) } ) { palette() } item( span = { GridItemSpan(maxLineSpan) } ) { invertColors() } item( span = { GridItemSpan(maxLineSpan) } ) { emojiAsPrimary() } item( span = { GridItemSpan(maxLineSpan) } ) { contrast() } } item( span = { GridItemSpan(maxLineSpan) } ) { DisableContainer(isPickersEnabled, defaultValues) } items(colorTupleList) { colorTuple -> DisableContainer(isPickersEnabled) { ColorTuplePreview( colorTuple = colorTuple, appColorTuple = currentColorTuple, onClick = { onPickTheme(colorTuple) } ) } } item { DisableContainer(isPickersEnabled) { ColorTupleItem( colorTuple = ColorTuple( primary = MaterialTheme.colorScheme.secondary, secondary = MaterialTheme.colorScheme.secondary, tertiary = MaterialTheme.colorScheme.secondary ), modifier = Modifier .aspectRatio(1f) .container( shape = MaterialStarShape, color = MaterialTheme.colorScheme.surfaceVariant, borderColor = MaterialTheme.colorScheme.outlineVariant(0.2f), resultPadding = 0.dp ) .hapticsClickable(onClick = onOpenColorPicker) .padding(3.dp) .clip(ShapeDefaults.circle), backgroundColor = Color.Transparent ) { Icon( imageVector = Icons.Rounded.AddCircleOutline, contentDescription = stringResource(R.string.add), tint = MaterialTheme.colorScheme.onSecondary, modifier = Modifier.size(24.dp) ) } } } } } }, confirmButton = { EnhancedButton( onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } }, ) ColorTuplePicker( visible = showEditColorPicker, onDismiss = { showEditColorPicker = false }, colorTuple = currentColorTuple, onColorChange = { onUpdateColorTuples(colorTupleList + it - currentColorTuple) onPickTheme(it) } ) colorPicker() if (settingsState.isDynamicColors) onDismiss() } @Composable private fun DisableContainer( enabled: Boolean, content: @Composable () -> Unit ) { Box( modifier = Modifier.alpha( animateFloatAsState( if (enabled) 1f else 0.5f ).value ) ) { content() if (!enabled) { Surface( color = Color.Transparent, modifier = Modifier.matchParentSize() ) {} } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material.icons.rounded.ContentPaste import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.colors.util.ColorUtil.colorToHex import com.t8rin.colors.util.ColorUtil.colorToHexAlpha import com.t8rin.colors.util.HexUtil import com.t8rin.colors.util.HexVisualTransformation import com.t8rin.colors.util.hexRegexSingleChar import com.t8rin.colors.util.hexWithAlphaRegex import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.pasteColorFromClipboard import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import kotlinx.coroutines.delay import kotlin.random.Random @Composable fun ColorInfo( color: Color, onColorChange: (Color) -> Unit, onSupportButtonClick: () -> Unit = { onColorChange( Color(Random.nextInt()).copy(alpha = color.alpha) ) }, supportButtonIcon: ImageVector = Icons.Rounded.Shuffle, modifier: Modifier = Modifier, infoContainerColor: Color = Color.Unspecified, ) { val context = LocalContext.current val colorPasteError = rememberSaveable { mutableStateOf(null) } val onCopyCustomColor = { Clipboard.copy( text = getFormattedColor(color), message = R.string.color_copied ) } val onPasteCustomColor = { context.pasteColorFromClipboard( onPastedColor = onColorChange, onPastedColorFailure = { colorPasteError.value = it }, ) } LaunchedEffect(colorPasteError.value) { delay(1500) colorPasteError.value = null } Row( modifier = modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { Card( modifier = Modifier .size(56.dp) .container( shape = MaterialTheme.shapes.medium, color = color, resultPadding = 0.dp ) .transparencyChecker() .background( color = color, shape = MaterialTheme.shapes.medium ), colors = CardDefaults.cardColors( containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onSurface ) ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { EnhancedIconButton( onClick = onSupportButtonClick ) { Icon( imageVector = supportButtonIcon, contentDescription = stringResource(R.string.edit), tint = animateColorAsState( color.inverse( fraction = { cond -> if (cond) 0.8f else 0.5f }, darkMode = color.luminance() < 0.3f ) ).value, modifier = Modifier .size(28.dp) .background( color = color.copy(alpha = 1f), shape = ShapeDefaults.mini ) .padding(2.dp) ) } } } Card( modifier = Modifier .height(60.dp) .fillMaxWidth() .padding(start = 16.dp) .container( shape = MaterialTheme.shapes.medium, color = infoContainerColor ), colors = CardDefaults.cardColors( containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onSurface ) ) { AnimatedContent( colorPasteError.value != null ) { error -> var expanded by remember { mutableStateOf(false) } Row( modifier = Modifier .fillMaxSize() .padding(start = 8.dp) .padding(end = 8.dp), verticalAlignment = Alignment.CenterVertically ) { if (error) { Text( modifier = Modifier.fillMaxWidth(), text = colorPasteError.value ?: "", style = MaterialTheme.typography.labelMedium, textAlign = TextAlign.Center ) } else { Row(modifier = Modifier.weight(1f)) { AutoSizeText( text = getFormattedColor(color), style = MaterialTheme.typography.titleMedium, maxLines = 1, modifier = Modifier .clip(ShapeDefaults.pressed) .hapticsClickable { expanded = true } .padding(4.dp) ) } Row(Modifier.width(80.dp)) { EnhancedIconButton( onClick = onCopyCustomColor ) { Icon( imageVector = Icons.Rounded.ContentCopy, contentDescription = stringResource(R.string.copy) ) } EnhancedIconButton( onClick = onPasteCustomColor ) { Icon( imageVector = Icons.Rounded.ContentPaste, contentDescription = stringResource(R.string.pastel) ) } } } } var value by remember(expanded) { mutableStateOf(getFormattedColor(color)) } EnhancedAlertDialog( visible = expanded, onDismissRequest = { expanded = false }, icon = { val hexColorInt by remember(value) { derivedStateOf { if (hexWithAlphaRegex.matches(value)) { HexUtil.hexToColor(value).toArgb() } else null } } AnimatedContent(hexColorInt) { colorFromHex -> if (colorFromHex != null) { Box( modifier = Modifier .size(28.dp) .container( shape = ShapeDefaults.circle, color = Color(colorFromHex), resultPadding = 0.dp ) ) } else { Icon( imageVector = Icons.Outlined.Palette, contentDescription = null ) } } }, title = { Text(stringResource(R.string.color)) }, text = { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { val style = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center) OutlinedTextField( shape = ShapeDefaults.default, textStyle = style, maxLines = 1, value = value.removePrefix("#"), visualTransformation = HexVisualTransformation(true), onValueChange = { val hex = it.replace("#", "") if (hex.length <= 8) { var validHex = true for (index in hex.indices) { validHex = hexRegexSingleChar.matches(hex[index].toString()) if (!validHex) break } if (validHex) { value = "#${hex.uppercase()}" } } }, placeholder = { Text( text = "#AARRGGBB", style = style, modifier = Modifier.fillMaxWidth() ) } ) } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { if (hexWithAlphaRegex.matches(value)) { onColorChange(HexUtil.hexToColor(value)) } expanded = false } ) { Text(stringResource(R.string.apply)) } } ) } } } } /** Receive the clipboard data. */ private fun getFormattedColor(color: Color): String { return if (color.alpha == 1f) { colorToHex(color) } else { colorToHexAlpha(color) }.uppercase() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPicker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize import com.t8rin.colors.util.ColorUtil import com.t8rin.gesture.detectMotionEvents import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable fun ColorPicker( selectedColor: Color, onColorSelected: (Color) -> Unit, containerColor: Color = Color.Unspecified, modifier: Modifier = Modifier, hueSliderConfig: HueSliderThumbConfig = HueSliderThumbConfig.Default, ) { var hue by remember { mutableFloatStateOf(0f) } var saturation by remember { mutableFloatStateOf(0f) } var value by remember { mutableFloatStateOf(0f) } var alpha by remember { mutableFloatStateOf(1f) } LaunchedEffect(selectedColor) { val (h, s, v) = ColorUtil.colorToHSV(selectedColor) hue = h saturation = s value = v alpha = selectedColor.alpha } LaunchedEffect(hue, saturation, value, alpha) { onColorSelected( Color.hsv( hue = hue, saturation = saturation, value = value, alpha = alpha ) ) } Row(modifier = modifier) { SelectorRectSaturationValueHSV( modifier = Modifier .weight(1f) .fillMaxSize() .container( shape = ShapeDefaults.pressed, resultPadding = 0.dp, color = containerColor, clip = false ), hue = hue, saturation = saturation, value = value ) { s, v -> saturation = s value = v } Spacer(modifier = Modifier.width(8.dp)) HueSlider( hue = hue, onHueSelected = { hue = it }, thumbConfig = hueSliderConfig, modifier = Modifier.width(36.dp), ) if (hueSliderConfig.withAlpha) { Spacer(modifier = Modifier.width(8.dp)) AlphaSlider( alpha = alpha, onAlphaSelected = { alpha = it }, color = selectedColor, thumbConfig = hueSliderConfig, modifier = Modifier.width(36.dp), ) } } } @Immutable data class HueSliderThumbConfig( val height: Dp = 12.dp, val color: Color = Color.White, val borderSize: Dp = 2.dp, val borderRadius: Float = 100f, val withAlpha: Boolean = false ) { companion object { val Default = HueSliderThumbConfig() } } @Composable private fun HueSlider( hue: Float, onHueSelected: (Float) -> Unit, modifier: Modifier = Modifier, thumbConfig: HueSliderThumbConfig = HueSliderThumbConfig.Default, ) { var sliderSize by remember { mutableStateOf(Size.Zero) } val thumbHeightPx = with(LocalDensity.current) { thumbConfig.height.toPx() } fun updateThumbByOffset(offsetY: Float) { if (sliderSize.height <= 0f) return val maxThumbY = sliderSize.height - thumbHeightPx val clampedY = offsetY.coerceIn(0f, maxThumbY) val newHue = ((clampedY / maxThumbY) * 359f).coerceIn(0f, 359f) onHueSelected(newHue) } Box( modifier = modifier .fillMaxHeight() .onSizeChanged { sliderSize = it.toSize() } .pointerInput(Unit) { detectTapGestures { offset -> updateThumbByOffset(offset.y) } } .pointerInput(Unit) { detectDragGestures { change, _ -> updateThumbByOffset(change.position.y) } } ) { Canvas( modifier = Modifier .fillMaxSize() .padding(horizontal = thumbConfig.borderSize) .clip(ShapeDefaults.extraSmall) ) { drawRect(brush = Brush.verticalGradient(Color.colorList)) } if (sliderSize.height > 0f) { Canvas(modifier = Modifier.fillMaxSize()) { val thumbY = (hue / 359f) * (sliderSize.height - thumbHeightPx) drawRoundRect( color = thumbConfig.color, topLeft = Offset( x = thumbConfig.borderSize.toPx() / 2, y = thumbY ), size = Size( width = sliderSize.width - thumbConfig.borderSize.toPx(), height = thumbHeightPx ), style = Stroke(width = thumbConfig.borderSize.toPx()), cornerRadius = CornerRadius( thumbConfig.borderRadius, thumbConfig.borderRadius ) ) } } } } @Composable private fun AlphaSlider( onAlphaSelected: (Float) -> Unit, modifier: Modifier = Modifier, alpha: Float, color: Color, thumbConfig: HueSliderThumbConfig = HueSliderThumbConfig.Default, ) { var sliderSize by remember { mutableStateOf(Size.Zero) } val density = LocalDensity.current val thumbHeightPx = with(density) { thumbConfig.height.toPx() } val updateAlpha by rememberUpdatedState(onAlphaSelected) fun onThumbPositionChange(newOffset: Offset) { if (sliderSize.height <= 0f) return val maxThumbY = sliderSize.height - thumbHeightPx val clampedY = newOffset.y.coerceIn(0f, maxThumbY) val newAlpha = ((maxThumbY - clampedY) / maxThumbY).coerceIn(0f, 1f) updateAlpha(newAlpha) } Box( modifier = modifier .fillMaxHeight() .onSizeChanged { sliderSize = it.toSize() } .pointerInput(Unit) { detectTapGestures { offset -> onThumbPositionChange(offset) } } .pointerInput(Unit) { detectDragGestures { change, _ -> onThumbPositionChange(change.position) change.consume() } } ) { val check = if (sliderSize.width > 0f) { with(density) { sliderSize.width.toDp() / 3.3f } } else { 10.dp } Canvas( modifier = Modifier .fillMaxSize() .padding(horizontal = thumbConfig.borderSize) .clip(ShapeDefaults.extraSmall) .transparencyChecker( checkerWidth = check, checkerHeight = check ) ) { drawRect( Brush.verticalGradient( colors = listOf( color.copy(alpha = 1f), color.copy(alpha = 0f) ) ) ) } if (!sliderSize.isEmpty()) { Canvas(modifier = Modifier.fillMaxSize()) { val thumbY = (1f - alpha) * (sliderSize.height - thumbHeightPx) drawRoundRect( color = thumbConfig.color, topLeft = Offset( x = thumbConfig.borderSize.toPx() / 2f, y = thumbY ), size = Size( width = sliderSize.width - thumbConfig.borderSize.toPx(), height = thumbHeightPx ), style = Stroke(width = thumbConfig.borderSize.toPx()), cornerRadius = CornerRadius(thumbConfig.borderRadius, thumbConfig.borderRadius) ) } } } } private val Color.Companion.colorList by lazy { IntArray(359) { it }.map { deg -> Color.hsv( hue = deg.toFloat(), saturation = 1f, value = 0.8f ) } } /** * Rectangle Saturation and Vale selector for * [HSV](https://en.wikipedia.org/wiki/HSL_and_HSV) color model * @param hue is in [0f..360f] of HSL color * @param value is in [0f..1f] of HSL color * @param selectionRadius radius of selection circle that moves based on touch position * @param onChange callback that returns [hue] and [value] * when position of touch in this selector has changed. */ @Composable private fun SelectorRectSaturationValueHSV( modifier: Modifier = Modifier, hue: Float, saturation: Float = 0.5f, value: Float = 0.5f, selectionRadius: Dp = Dp.Unspecified, onChange: (Float, Float) -> Unit ) { val valueGradient = valueGradient(hue) val hueSaturation = saturationHSVGradient(hue) SelectorRect( modifier = modifier, saturation = saturation, property = value, brushSrc = hueSaturation, brushDst = valueGradient, selectionRadius = selectionRadius, onChange = onChange ) } @Composable private fun SelectorRect( modifier: Modifier = Modifier, saturation: Float = 0.5f, property: Float = 0.5f, brushSrc: Brush, brushDst: Brush, selectionRadius: Dp = Dp.Unspecified, onChange: (Float, Float) -> Unit ) { BoxWithConstraints(modifier) { val density = LocalDensity.current.density val width = constraints.maxWidth.toFloat() val height = constraints.maxHeight.toFloat() // Center position of color picker val center = Offset(width / 2, height / 2) /** * Circle selector radius for setting [saturation] and [property] by gesture */ val selectorRadius = if (selectionRadius != Dp.Unspecified) selectionRadius.value * density else width.coerceAtMost(height) * .04f var currentPosition by remember { mutableStateOf(center) } val posX = saturation * width val posY = (1 - property) * height currentPosition = Offset(posX, posY) val canvasModifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectMotionEvents( onDown = { val position = it.position val saturationChange = (position.x / width).coerceIn(0f, 1f) val valueChange = (1 - (position.y / height)).coerceIn(0f, 1f) onChange(saturationChange, valueChange) it.consume() }, onMove = { val position = it.position val saturationChange = (position.x / width).coerceIn(0f, 1f) val valueChange = (1 - (position.y / height)).coerceIn(0f, 1f) onChange(saturationChange, valueChange) it.consume() }, delayAfterDownInMillis = 20 ) } SelectorRectImpl( modifier = canvasModifier, brushSrc = brushSrc, brushDst = brushDst, selectorPosition = currentPosition, selectorRadius = selectorRadius ) } } @Composable private fun SelectorRectImpl( modifier: Modifier, brushSrc: Brush, brushDst: Brush, selectorPosition: Offset, selectorRadius: Float ) { Canvas(modifier = modifier) { drawBlendingRectGradient( dst = brushDst, src = brushSrc, blendMode = BlendMode.Multiply ) drawHueSelectionCircle( center = selectorPosition, radius = selectorRadius ) } } private fun saturationHSVGradient( hue: Float, value: Float = 1f, alpha: Float = 1f, start: Offset = Offset.Zero, end: Offset = Offset(Float.POSITIVE_INFINITY, 0f) ): Brush { return Brush.linearGradient( colors = listOf( Color.hsv(hue = hue, saturation = 0f, value = value, alpha = alpha), Color.hsv(hue = hue, saturation = 1f, value = value, alpha = alpha) ), start = start, end = end ) } /** * Vertical gradient that goes from 1 value to 0 with 90 degree rotation by default. */ fun valueGradient( hue: Float, alpha: Float = 1f, start: Offset = Offset.Zero, end: Offset = Offset(0f, Float.POSITIVE_INFINITY) ): Brush { return Brush.linearGradient( colors = listOf( Color.hsv(hue = hue, saturation = 0f, value = 1f, alpha = alpha), Color.hsv(hue = hue, saturation = 0f, value = 0f, alpha = alpha) ), start = start, end = end ) } private fun DrawScope.drawBlendingRectGradient( dst: Brush, dstTopLeft: Offset = Offset.Zero, dstSize: Size = this.size, src: Brush, srcTopLeft: Offset = Offset.Zero, srcSize: Size = this.size, blendMode: BlendMode = BlendMode.Multiply ) { with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) drawRect(dst, dstTopLeft, dstSize) drawRect(src, srcTopLeft, srcSize, blendMode = blendMode) restoreToCount(checkPoint) } } private fun DrawScope.drawHueSelectionCircle( center: Offset, radius: Float ) { drawCircle( Color.White, radius = radius, center = center, style = Stroke(width = radius / 4) ) drawCircle( Color.Black, radius = radius + radius / 8, center = center, style = Stroke(width = radius / 8) ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorPickerSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.material.icons.rounded.BookmarkRemove import androidx.compose.material.icons.rounded.ColorLens import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.toColorModel import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.launch @Composable fun ColorPickerSheet( visible: Boolean, onDismiss: () -> Unit, color: Color?, onColorSelected: (Color) -> Unit, allowAlpha: Boolean ) { val scope = rememberCoroutineScope() var tempColor by remember(visible) { mutableStateOf(color ?: Color.Black) } val settingsState = LocalSettingsState.current val simpleSettingsInteractor = LocalSimpleSettingsInteractor.current EnhancedModalBottomSheet( sheetContent = { Box { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState(), reverseScrolling = true) .padding(24.dp) ) { RecentAndFavoriteColorsCard( onRecentColorClick = { tempColor = it }, onFavoriteColorClick = { tempColor = it } ) ColorSelection( value = tempColor, onValueChange = { tempColor = it }, withAlpha = allowAlpha ) } } }, visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.color), icon = Icons.Rounded.ColorLens ) }, confirmButton = { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { val favoriteColors = settingsState.favoriteColors val inFavorite by remember(tempColor, favoriteColors) { derivedStateOf { tempColor in favoriteColors } } val containerColor by animateColorAsState( if (inFavorite) MaterialTheme.colorScheme.tertiaryContainer else MaterialTheme.colorScheme.surfaceContainer ) val contentColor by animateColorAsState( if (inFavorite) MaterialTheme.colorScheme.onTertiaryContainer else MaterialTheme.colorScheme.onBackground ) EnhancedIconButton( containerColor = containerColor, contentColor = contentColor, onClick = { scope.launch { simpleSettingsInteractor.toggleFavoriteColor( tempColor.toArgb().toColorModel() ) } } ) { Icon( imageVector = if (inFavorite) { Icons.Rounded.BookmarkRemove } else { Icons.Rounded.Bookmark }, contentDescription = null ) } EnhancedButton( onClick = { scope.launch { simpleSettingsInteractor.toggleRecentColor( tempColor.toArgb().toColorModel() ) } onColorSelected(tempColor) onDismiss() } ) { AutoSizeText(stringResource(R.string.ok)) } } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @Composable fun ColorSelection( value: Color, onValueChange: (Color) -> Unit, withAlpha: Boolean = false, infoContainerColor: Color = Color.Unspecified, ) { Column { ColorInfo( color = value.let { if (withAlpha) it else it.copy(1f) }, onColorChange = onValueChange, infoContainerColor = infoContainerColor ) Spacer(Modifier.height(16.dp)) ColorPicker( onColorSelected = onValueChange, selectedColor = value, containerColor = infoContainerColor, modifier = Modifier .fillMaxWidth() .height(200.dp), hueSliderConfig = HueSliderThumbConfig( withAlpha = withAlpha ) ) Spacer(Modifier.height(16.dp)) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorSelectionRow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Error import androidx.compose.material.icons.rounded.Block import androidx.compose.material.icons.rounded.DoneAll import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.pasteColorFromClipboard import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerColor import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import kotlinx.coroutines.delay @Composable fun ColorSelectionRow( modifier: Modifier = Modifier, defaultColors: List = ColorSelectionRowDefaults.colorList, allowAlpha: Boolean = false, allowScroll: Boolean = true, value: Color?, onValueChange: (Color) -> Unit, contentPadding: PaddingValues = PaddingValues(), onNullClick: (() -> Unit)? = null ) { val context = LocalContext.current var customColor by remember { mutableStateOf(null) } var showColorPicker by remember { mutableStateOf(false) } val listState = rememberLazyListState() LaunchedEffect(value) { if (value !in defaultColors) { customColor = value } } LaunchedEffect(Unit) { delay(250) if (value == customColor) { listState.animateScrollToItem(0) } else if (value in defaultColors) { listState.animateScrollToItem(defaultColors.indexOf(value)) } } val itemSize = 42.dp ProvideContainerDefaults( color = LocalContainerColor.current ) { LazyRow( state = listState, modifier = modifier .fillMaxWidth() .height(64.dp) .fadingEdges(listState), userScrollEnabled = allowScroll, contentPadding = contentPadding, horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, flingBehavior = enhancedFlingBehavior() ) { if (onNullClick != null) { item { val background = MaterialTheme.colorScheme.surfaceVariant val isSelected = value == null val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = if (isSelected) ShapeDefaults.small else AutoCornersShape(itemSize / 2), pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) Box( modifier = Modifier .height(itemSize) .aspectRatio( ratio = animateFloatAsState( targetValue = if (isSelected) 1.5f else 1f, animationSpec = tween(400) ).value, matchHeightConstraintsFirst = true ) .container( shape = shape, color = background, resultPadding = 0.dp ) .transparencyChecker() .background(background, shape) .hapticsCombinedClickable( indication = LocalIndication.current, interactionSource = interactionSource, onClick = onNullClick ), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.Block, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp) ) } } } item { val background = customColor ?: MaterialTheme.colorScheme.primary val isSelected = customColor != null val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = if (isSelected) ShapeDefaults.small else AutoCornersShape(itemSize / 2), pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) Box( modifier = Modifier .height(itemSize) .aspectRatio( ratio = animateFloatAsState( targetValue = if (isSelected) 1.5f else 1f, animationSpec = tween(400) ).value, matchHeightConstraintsFirst = true ) .container( shape = shape, color = background, resultPadding = 0.dp ) .transparencyChecker() .background(background, shape) .hapticsCombinedClickable( indication = LocalIndication.current, interactionSource = interactionSource, onLongClick = { context.pasteColorFromClipboard( onPastedColor = { val color = if (allowAlpha) it else it.copy(1f) onValueChange(color) customColor = color }, onPastedColorFailure = { message -> AppToastHost.showToast( message = message, icon = Icons.Outlined.Error ) } ) }, onClick = { showColorPicker = true } ), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.Palette, contentDescription = null, tint = background.inverse( fraction = { if (it) 0.8f else 0.5f }, darkMode = background.luminance() < 0.3f ), modifier = Modifier .size(32.dp) .background( color = background.copy(alpha = 1f), shape = shape ) .padding(4.dp) ) } } items( items = defaultColors, key = { it.toArgb() } ) { color -> val isSelected = value == color && customColor == null val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = if (isSelected) ShapeDefaults.small else AutoCornersShape(itemSize / 2), pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) Box( modifier = Modifier .height(itemSize) .aspectRatio( ratio = animateFloatAsState( targetValue = if (isSelected) 1.5f else 1f, animationSpec = tween(400) ).value, matchHeightConstraintsFirst = true ) .container( shape = shape, color = color, resultPadding = 0.dp ) .transparencyChecker() .background(color, shape) .hapticsClickable( interactionSource = interactionSource, indication = LocalIndication.current, onClick = { onValueChange(color.copy(if (allowAlpha) color.alpha else 1f)) customColor = null } ), contentAlignment = Alignment.Center ) { AnimatedVisibility( visible = isSelected, modifier = Modifier.fillMaxSize() ) { Box( contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.DoneAll, contentDescription = null, tint = color.inverse( fraction = { if (it) 0.8f else 0.5f }, darkMode = color.luminance() < 0.3f ), modifier = Modifier.size(20.dp) ) } } } } } } ColorPickerSheet( visible = showColorPicker, onDismiss = { showColorPicker = false }, color = customColor, onColorSelected = { val color = it.copy(if (allowAlpha) it.alpha else 1f) onValueChange(color) customColor = color }, allowAlpha = allowAlpha ) } object ColorSelectionRowDefaults { val colorList by lazy { listOf( Color(0xFF7a000b), Color(0xFF8a000b), Color(0xFF97000c), Color(0xFFa5000d), Color(0xFFb2070d), Color(0xFFbf100e), Color(0xFFca120e), Color(0xFFd8150e), Color(0xFFf8130d), Color(0xFFff3306), Color(0xFFff4f05), Color(0xFFff6b02), Color(0xFFff7900), Color(0xFFf89c08), Color(0xFFf8b40c), Color(0xFFfcf721), Color(0xFFe0e11c), Color(0xFFc9e11a), Color(0xFFc0dc18), Color(0xFFaee020), Color(0xFF96df20), Color(0xFF88dd20), Color(0xFF50cbb5), Color(0xFF36c0b3), Color(0xFF1eb0b0), Color(0xFF01a0a3), Color(0xFF05bfc0), Color(0xFF07cfd3), Color(0xFF59cbf0), Color(0xFF3b9df0), Color(0xFF1c7fff), Color(0xFF005FFF), Color(0xFF034087), Color(0xFF022b6d), Color(0xFF2d1d9c), Color(0xFF3f1f9a), Color(0xFF642bec), Color(0xFF7b2bec), Color(0xFF8e36f4), Color(0xFF9836f5), Color(0xFFaa3ef8), Color(0xFFb035f8), Color(0xFFc78afe), Color(0xFFd294fe), Color(0xFFdb94fe), Color(0xFFe76cd9), Color(0xFFfa64e1), Color(0xFFfc50a6), Color(0xFFe10076), Color(0xFFd7036a), Color(0xFFFFFFFF), Color(0xFF768484), Color(0xFF333333), Color(0xFF000000), ) } val colorListVariant by lazy { colorList.takeLast(4) + colorList.dropLast(4) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTupleDefaults.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.ui.graphics.Color import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.calculateSecondaryColor import com.t8rin.dynamic.theme.calculateSurfaceColor import com.t8rin.dynamic.theme.calculateTertiaryColor import com.t8rin.imagetoolbox.core.ui.theme.toColor object ColorTupleDefaults { val defaultColorTuples by lazy { listOf( Color(0xFFf8130d), Color(0xFF7a000b), Color(0xFF8a3a00), Color(0xFFff7900), Color(0xFFfcf721), Color(0xFF88dd20), Color(0xFF16B16E), Color(0xFF01a0a3), Color(0xFF005FFF), Color(0xFFfa64e1), Color(0xFFd7036a), Color(0xFFdb94fe), Color(0xFF7b2bec), Color(0xFF022b6d), Color(0xFFFFFFFF), Color(0xFF000000), ).map { ColorTuple( primary = it, secondary = it.calculateSecondaryColor().toColor(), tertiary = it.calculateTertiaryColor().toColor(), surface = it.calculateSurfaceColor().toColor() ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePicker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.rounded.ContentPaste import androidx.compose.material.icons.rounded.FormatColorFill import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.dynamic.theme.calculateSecondaryColor import com.t8rin.dynamic.theme.calculateSurfaceColor import com.t8rin.dynamic.theme.calculateTertiaryColor import com.t8rin.dynamic.theme.rememberAppColorTuple import com.t8rin.dynamic.theme.rememberColorScheme import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.delay @Composable fun ColorTuplePicker( visible: Boolean, onDismiss: () -> Unit, colorTuple: ColorTuple, title: String = stringResource(R.string.color_scheme), onColorChange: (ColorTuple) -> Unit, ) { val settingsState = LocalSettingsState.current var primary by rememberSaveable(colorTuple, stateSaver = ColorSaver) { mutableStateOf(colorTuple.primary) } var secondary by rememberSaveable(colorTuple, stateSaver = ColorSaver) { mutableStateOf( colorTuple.secondary ?: colorTuple.primary.calculateSecondaryColor().toColor() ) } var tertiary by rememberSaveable(colorTuple, stateSaver = ColorSaver) { mutableStateOf( colorTuple.tertiary ?: colorTuple.primary.calculateTertiaryColor().toColor() ) } var surface by rememberSaveable(colorTuple, stateSaver = ColorSaver) { mutableStateOf( colorTuple.surface ?: colorTuple.primary.calculateSurfaceColor().toColor() ) } val appColorTuple = rememberAppColorTuple( defaultColorTuple = settingsState.appColorTuple, dynamicColor = true, darkTheme = true ) val scheme = rememberColorScheme( amoledMode = false, isDarkTheme = true, colorTuple = appColorTuple, contrastLevel = settingsState.themeContrastLevel, style = settingsState.themeStyle, dynamicColor = false, isInvertColors = settingsState.isInvertThemeColors ) LaunchedEffect(visible) { if (!visible) { delay(1000) primary = colorTuple.primary secondary = colorTuple.secondary ?: colorTuple.primary.calculateSecondaryColor().toColor() tertiary = colorTuple.tertiary ?: colorTuple.primary.calculateTertiaryColor().toColor() surface = colorTuple.surface ?: colorTuple.primary.calculateSurfaceColor().toColor() } } EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = title, icon = Icons.Outlined.Palette ) }, sheetContent = { Box { LazyVerticalGrid( columns = GridCells.Adaptive(260.dp), horizontalArrangement = Arrangement.spacedBy( 16.dp, Alignment.CenterHorizontally ), verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), contentPadding = PaddingValues(16.dp), flingBehavior = enhancedFlingBehavior() ) { item( span = { GridItemSpan(maxCurrentLineSpan) } ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier .container(ShapeDefaults.extraLarge) .padding(16.dp) ) { Icon( imageVector = Icons.Rounded.FormatColorFill, contentDescription = null ) Spacer(Modifier.width(8.dp)) Text(stringResource(R.string.monet_colors)) Spacer(Modifier.width(8.dp)) Spacer(Modifier.weight(1f)) EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { scheme.apply { primary = this.primary if (settingsState.themeStyle == PaletteStyle.TonalSpot) { secondary = this.secondary tertiary = this.tertiary surface = this.surface } } }, ) { Icon( imageVector = Icons.Rounded.ContentPaste, contentDescription = stringResource(R.string.pastel) ) } } } item( span = { if (settingsState.themeStyle != PaletteStyle.TonalSpot) { GridItemSpan( maxCurrentLineSpan ) } else GridItemSpan(1) } ) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .container(ShapeDefaults.extraLarge) .padding(horizontal = 20.dp) ) { TitleItem(text = stringResource(R.string.primary)) ColorSelection( value = primary, onValueChange = { if (primary != it && settingsState.themeStyle == PaletteStyle.TonalSpot) { secondary = it.calculateSecondaryColor().toColor() tertiary = it.calculateTertiaryColor().toColor() surface = it.calculateSurfaceColor().toColor() } primary = it }, infoContainerColor = MaterialTheme.colorScheme.surface ) } } if (settingsState.themeStyle == PaletteStyle.TonalSpot) { item { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .container(ShapeDefaults.extraLarge) .padding(horizontal = 20.dp) ) { TitleItem(text = stringResource(R.string.secondary)) ColorSelection( value = secondary, onValueChange = { secondary = it }, infoContainerColor = MaterialTheme.colorScheme.surface ) } } item { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .container(ShapeDefaults.extraLarge) .padding(horizontal = 20.dp) ) { TitleItem(text = stringResource(R.string.tertiary)) ColorSelection( value = tertiary, onValueChange = { tertiary = it }, infoContainerColor = MaterialTheme.colorScheme.surface ) } } item { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .container(ShapeDefaults.extraLarge) .padding(horizontal = 20.dp) ) { TitleItem(text = stringResource(R.string.surface)) ColorSelection( value = surface, onValueChange = { surface = it }, infoContainerColor = MaterialTheme.colorScheme.surface ) } } } } } }, confirmButton = { EnhancedButton( onClick = { onColorChange( ColorTuple( primary = primary, secondary = secondary, tertiary = tertiary, surface = surface ) ) onDismiss() } ) { AutoSizeText(stringResource(R.string.save)) } }, ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/ColorTuplePreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.ColorTupleItem import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.dynamic.theme.rememberColorScheme import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun ColorTuplePreview( modifier: Modifier = Modifier, isDefaultItem: Boolean = false, colorTuple: ColorTuple, appColorTuple: ColorTuple, onClick: () -> Unit ) { val settingsState = LocalSettingsState.current ColorTupleItem( colorTuple = remember(settingsState.themeStyle, colorTuple) { derivedStateOf { if (settingsState.themeStyle == PaletteStyle.TonalSpot) { colorTuple } else colorTuple.run { copy(secondary = primary, tertiary = primary) } } }.value, modifier = modifier .aspectRatio(1f) .container( shape = MaterialStarShape, color = rememberColorScheme( isDarkTheme = settingsState.isNightMode, amoledMode = settingsState.isAmoledMode, colorTuple = colorTuple, contrastLevel = settingsState.themeContrastLevel, style = settingsState.themeStyle, dynamicColor = false, isInvertColors = settingsState.isInvertThemeColors ).surfaceVariant.copy(alpha = 0.8f), borderColor = MaterialTheme.colorScheme.outlineVariant(0.2f), resultPadding = 0.dp ) .hapticsClickable(onClick = onClick) .padding(3.dp) .clip(ShapeDefaults.circle), backgroundColor = Color.Transparent ) { AnimatedContent( targetState = (colorTuple == appColorTuple) .and( if (colorTuple in ColorTupleDefaults.defaultColorTuples) { isDefaultItem } else true ) ) { selected -> BoxWithConstraints( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize() ) { if (selected) { Box( modifier = Modifier .size(this.maxWidth * (5 / 9f)) .background( color = animateColorAsState( colorTuple.primary.inverse( fraction = { cond -> if (cond) 0.8f else 0.5f }, darkMode = colorTuple.primary.luminance() < 0.3f ) ).value, shape = ShapeDefaults.circle ) ) Icon( imageVector = Icons.Rounded.Done, contentDescription = stringResource(R.string.ok), tint = colorTuple.primary, modifier = Modifier.size(maxWidth * (1 / 3f)) ) } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/color_picker/RecentAndFavoriteColorsCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.color_picker import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.rounded.BookmarkBorder import androidx.compose.material.icons.rounded.ContentPasteGo import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.launch import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState import kotlin.math.roundToInt @Composable fun RecentAndFavoriteColorsCard( onFavoriteColorClick: (Color) -> Unit, onRecentColorClick: (Color) -> Unit ) { val settingsState = LocalSettingsState.current val recentColors = settingsState.recentColors val favoriteColors = settingsState.favoriteColors val interactor = LocalSimpleSettingsInteractor.current val scope = rememberCoroutineScope() BoxAnimatedVisibility( visible = recentColors.isNotEmpty() || favoriteColors.isNotEmpty() ) { val itemWidth = with(LocalDensity.current) { 48.dp.toPx() } Column( modifier = Modifier .padding(bottom = 16.dp) .container( shape = ShapeDefaults.extraLarge, resultPadding = 0.dp ) .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { BoxAnimatedVisibility(recentColors.isNotEmpty()) { Column { TitleItem( text = stringResource(R.string.recently_used), icon = Icons.Outlined.History, modifier = Modifier, endContent = { EnhancedIconButton( onClick = { scope.launch { interactor.clearRecentColors() } }, modifier = Modifier.offset(x = 8.dp) ) { Icon( imageVector = Icons.Outlined.DeleteSweep, contentDescription = null, tint = takeColorFromScheme { primary.blend(error, 0.8f) } ) } } ) Spacer(Modifier.height(12.dp)) val recentState = rememberLazyListState() val possibleCount by remember(recentState, itemWidth) { derivedStateOf { (recentState.layoutInfo.viewportSize.width / itemWidth).roundToInt() } } LazyRow( state = recentState, modifier = Modifier .fillMaxWidth() .fadingEdges(recentState), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, flingBehavior = enhancedFlingBehavior() ) { items( items = recentColors, key = { it.toArgb() } ) { color -> Box( modifier = Modifier .size(40.dp) .aspectRatio(1f) .container( shape = ShapeDefaults.circle, color = color, resultPadding = 0.dp ) .transparencyChecker() .background(color, ShapeDefaults.circle) .hapticsClickable { onRecentColorClick(color) } .animateItem(), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.ContentPasteGo, contentDescription = null, tint = color.inverse( fraction = { if (it) 0.8f else 0.5f }, darkMode = color.luminance() < 0.3f ), modifier = Modifier .size(24.dp) .background( color = color.copy(alpha = 1f), shape = ShapeDefaults.circle ) .padding(3.dp) ) } } if (recentColors.size < possibleCount) { items(possibleCount - recentColors.size) { Box( modifier = Modifier .size(40.dp) .clip(ShapeDefaults.circle) .alpha(0.4f) .transparencyChecker() .animateItem() ) } } } } } BoxAnimatedVisibility(favoriteColors.isNotEmpty()) { Column { TitleItem( text = stringResource(R.string.favorite), icon = Icons.Rounded.BookmarkBorder, modifier = Modifier ) Spacer(Modifier.height(12.dp)) val favoriteState = rememberLazyListState() val possibleCount by remember(favoriteState, itemWidth) { derivedStateOf { (favoriteState.layoutInfo.viewportSize.width / itemWidth).roundToInt() } } val data = remember(favoriteColors) { mutableStateOf(favoriteColors) } val haptics = LocalHapticFeedback.current val reorderableState = rememberReorderableLazyListState( lazyListState = favoriteState, onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } } ) LazyRow( state = favoriteState, modifier = Modifier .fillMaxWidth() .fadingEdges(favoriteState), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, flingBehavior = enhancedFlingBehavior() ) { items( items = data.value, key = { it.toArgb() } ) { color -> ReorderableItem( state = reorderableState, key = color.toArgb() ) { isDragging -> Box( modifier = Modifier .size(40.dp) .aspectRatio(1f) .scale( animateFloatAsState( if (!reorderableState.isAnyItemDragging || isDragging) 1f else 0.8f ).value ) .container( shape = ShapeDefaults.circle, color = color, resultPadding = 0.dp ) .transparencyChecker() .background(color, ShapeDefaults.circle) .hapticsClickable { onFavoriteColorClick(color) } .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { scope.launch { interactor.updateFavoriteColors(data.value.map { it.toModel() }) } } ) .animateItem(), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.ContentPasteGo, contentDescription = null, tint = color.inverse( fraction = { if (it) 0.8f else 0.5f }, darkMode = color.luminance() < 0.3f ), modifier = Modifier .size(24.dp) .background( color = color.copy(alpha = 1f), shape = ShapeDefaults.circle ) .padding(3.dp) ) } } } if (favoriteColors.size < possibleCount) { items(possibleCount - favoriteColors.size) { Box( modifier = Modifier .size(40.dp) .clip(ShapeDefaults.circle) .alpha(0.4f) .transparencyChecker() .animateItem() ) } } } } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FileReorderVerticalList.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import android.net.Uri import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Add import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.utils.sortedByType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState import kotlin.random.Random @Composable fun FileReorderVerticalList( files: List?, onReorder: (List) -> Unit, modifier: Modifier = Modifier .container( shape = ShapeDefaults.extraLarge, clip = false ), onNeedToAddFile: () -> Unit, onNeedToRemoveFileAt: (Int) -> Unit, additionalInfo: (@Composable (Uri) -> String)? = { val pages by rememberPdfPages(it) "$pages ${stringResource(R.string.pages_short)}" }, title: String = stringResource(R.string.files_order), coerceHeight: Boolean = true ) { val data = remember { mutableStateOf(files ?: emptyList()) } val haptics = LocalHapticFeedback.current val listState = rememberLazyListState() val state = rememberReorderableLazyListState( lazyListState = listState, onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } } ) LaunchedEffect(files) { if (data.value.sorted() != files?.sorted()) { data.value = files ?: emptyList() listState.animateScrollToItem(data.value.lastIndex.coerceAtLeast(0)) } } Column( modifier = modifier .then( if (coerceHeight) { Modifier .heightIn( max = LocalWindowInfo.current.containerDpSize.height * 0.7f ) } else { Modifier } ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier .padding( top = 16.dp, bottom = 8.dp, start = 16.dp, end = 8.dp ) ) { Text( fontWeight = FontWeight.Medium, text = title, modifier = Modifier.weight(1f), fontSize = 18.sp, ) Spacer(Modifier.width(16.dp)) EnhancedIconButton( onClick = onNeedToAddFile, containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, contentColor = MaterialTheme.colorScheme.onSurfaceVariant, forceMinimumInteractiveComponentSize = false, modifier = Modifier .padding(start = 8.dp, end = 8.dp) .size(30.dp), ) { Icon( imageVector = Icons.Rounded.Add, contentDescription = stringResource(R.string.add), modifier = Modifier.size(20.dp) ) } val scope = rememberCoroutineScope() SortButton( modifier = Modifier .padding(end = 8.dp) .size(30.dp), onSortTypeSelected = { sortType -> scope.launch(Dispatchers.Default) { val newValue = files .orEmpty() .sortedByType( sortType = sortType ) withContext(Dispatchers.Main.immediate) { data.value = newValue onReorder(newValue) } } } ) } Box( modifier = Modifier.weight(1f, false) ) { val showButton = (files?.size ?: 0) >= 2 LazyColumn( state = listState, modifier = Modifier .fadingEdges( scrollableState = listState, isVertical = true ) .animateContentSizeNoClip(), contentPadding = PaddingValues(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data.value, key = { _, uri -> uri.toString() + uri.hashCode() } ) { index, uri -> ReorderableItem( state = state, key = uri.toString() + uri.hashCode(), ) { isDragging -> val alpha by animateFloatAsState(if (isDragging) 0.3f else 0.6f) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .scale( animateFloatAsState( if (isDragging) 1.05f else 1f ).value ) .container( color = MaterialTheme.colorScheme.surface, resultPadding = 8.dp ) .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { onReorder(data.value) } ) ) { Box( modifier = Modifier .height(80.dp) .container( shape = ShapeDefaults.small, color = Color.Transparent, resultPadding = 0.dp ) .widthIn(max = 120.dp) ) { Picture( model = uri, modifier = Modifier .fillMaxHeight() .defaultMinSize(minWidth = 60.dp), shape = RectangleShape, contentScale = ContentScale.Fit ) Box( modifier = Modifier .matchParentSize() .background( MaterialTheme.colorScheme .surfaceContainer .copy(alpha) ), contentAlignment = Alignment.Center ) { Text( text = "${index + 1}", color = MaterialTheme.colorScheme.onSurface, fontSize = 20.sp, fontWeight = FontWeight.Bold ) } } Spacer(Modifier.width(16.dp)) Column( modifier = Modifier.weight(1f) ) { Text( text = rememberFilename(uri) ?: uri.toString(), style = PreferenceItemDefaults.TitleFontStyle ) Spacer(Modifier.height(4.dp)) val size = rememberHumanFileSize(uri) Text( text = additionalInfo?.let { "$size • ${additionalInfo(uri)}" } ?: size, fontSize = 12.sp, textAlign = TextAlign.Start, fontWeight = FontWeight.Normal, lineHeight = 14.sp, color = LocalContentColor.current.copy(alpha = 0.5f) ) } Spacer(Modifier.width(16.dp)) BoxAnimatedVisibility( visible = showButton, enter = expandVertically(tween(300)) + fadeIn(), exit = shrinkVertically(tween(300)) + fadeOut() ) { EnhancedIconButton( onClick = { onNeedToRemoveFileAt(index) }, containerColor = MaterialTheme.colorScheme.errorContainer.copy( 0.4f ), contentColor = MaterialTheme.colorScheme.onErrorContainer, modifier = Modifier.padding(top = 4.dp) ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null ) } } } } } } } } } @Composable @EnPreview private fun Preview() = ImageToolboxThemeForPreview(true) { var files by remember { mutableStateOf( List(15) { "file:///uri_$it.pdf".toUri() } ) } LazyColumn { item { FileReorderVerticalList( files = files, onReorder = { files = it }, onNeedToAddFile = { files += "file:///uri_TEST${Random.nextInt()}.pdf".toUri() }, additionalInfo = { "30 pages" }, onNeedToRemoveFileAt = { files = files.toMutableList().apply { removeAt(it) } } ) } items(30) { Text("TEST $it") } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/FormatExifWarning.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun FormatExifWarning( imageFormat: ImageFormat? ) { AnimatedVisibility( visible = imageFormat?.canWriteExif == false, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column( modifier = Modifier .padding(12.dp) .container( color = MaterialTheme.colorScheme.errorContainer.copy( alpha = 0.7f ), resultPadding = 0.dp, shape = ShapeDefaults.default ) ) { Text( text = stringResource(R.string.image_exif_warning, imageFormat?.title ?: ""), fontSize = 12.sp, modifier = Modifier.padding(8.dp), textAlign = TextAlign.Center, fontWeight = FontWeight.SemiBold, lineHeight = 14.sp, color = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.5f) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/IcoSizeWarning.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun IcoSizeWarning(visible: Boolean) { AnimatedVisibility( visible = visible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Box( modifier = Modifier .padding( top = 12.dp ) .container( color = MaterialTheme.colorScheme.secondaryContainer.copy( alpha = 0.7f ), resultPadding = 4.dp, shape = ShapeDefaults.default ) ) { Text( text = stringResource(R.string.ico_size_warning), fontSize = 12.sp, modifier = Modifier.padding(8.dp), textAlign = TextAlign.Center, fontWeight = FontWeight.SemiBold, lineHeight = 14.sp, color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.5f) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageReorderCarousel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import android.net.Uri import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Add import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.shareUris import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.image.ImagePager import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.utils.sortedByType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState @Composable fun ImageReorderCarousel( images: List?, onReorder: (List) -> Unit, modifier: Modifier = Modifier .container(ShapeDefaults.extraLarge), onNeedToAddImage: () -> Unit, onNeedToRemoveImageAt: (Int) -> Unit, onNavigate: (Screen) -> Unit, title: String = stringResource(R.string.images_order) ) { val data = remember { mutableStateOf(images ?: emptyList()) } val context = LocalContext.current val haptics = LocalHapticFeedback.current val listState = rememberLazyListState() val state = rememberReorderableLazyListState( lazyListState = listState, onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } } ) LaunchedEffect(images) { if (data.value.sorted() != images?.sorted()) { data.value = images ?: emptyList() listState.animateScrollToItem(data.value.lastIndex.coerceAtLeast(0)) } } var previewUri by rememberSaveable { mutableStateOf(null) } Column( modifier = modifier, verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier.padding(top = 16.dp, bottom = 8.dp) ) { Text( fontWeight = FontWeight.Medium, text = title, modifier = Modifier.padding(start = 8.dp), fontSize = 18.sp ) EnhancedIconButton( onClick = onNeedToAddImage, containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, contentColor = MaterialTheme.colorScheme.onSurfaceVariant, forceMinimumInteractiveComponentSize = false, modifier = Modifier .padding(start = 8.dp, end = 8.dp) .size(30.dp), ) { Icon( imageVector = Icons.Rounded.Add, contentDescription = stringResource(R.string.add), modifier = Modifier.size(20.dp) ) } val scope = rememberCoroutineScope() SortButton( modifier = Modifier .padding(end = 8.dp) .size(30.dp), onSortTypeSelected = { sortType -> scope.launch(Dispatchers.Default) { val newValue = images .orEmpty() .sortedByType( sortType = sortType ) withContext(Dispatchers.Main.immediate) { data.value = newValue onReorder(newValue) } } } ) } Box { val showButton = (images?.size ?: 0) > 2 && !state.isAnyItemDragging LazyRow( state = listState, modifier = Modifier .fadingEdges(scrollableState = listState) .animateContentSizeNoClip(), contentPadding = PaddingValues(12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data.value, key = { _, uri -> uri.toString() + uri.hashCode() } ) { index, uri -> ReorderableItem( state = state, key = uri.toString() + uri.hashCode() ) { isDragging -> val alpha by animateFloatAsState(if (isDragging) 0.3f else 0.6f) val shape = animateShape( if (showButton) ShapeDefaults.top else ShapeDefaults.default ) Column( horizontalAlignment = Alignment.CenterHorizontally ) { Box( modifier = Modifier .size(120.dp) .scale( animateFloatAsState( if (isDragging) 1.05f else 1f ).value ) .container( shape = shape, color = Color.Transparent, resultPadding = 0.dp ) .hapticsClickable { previewUri = uri } .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { onReorder(data.value) } ) ) { Picture( model = uri, modifier = Modifier.fillMaxSize(), shape = RectangleShape, contentScale = ContentScale.Fit ) Box( modifier = Modifier .size(120.dp) .background( MaterialTheme.colorScheme .surfaceContainer .copy(alpha) ), contentAlignment = Alignment.Center ) { Text( text = "${index + 1}", color = MaterialTheme.colorScheme.onSurface, fontSize = 20.sp, fontWeight = FontWeight.Bold ) } } BoxAnimatedVisibility( visible = showButton, enter = expandVertically(tween(300)) + fadeIn(), exit = shrinkVertically(tween(300)) + fadeOut(), modifier = Modifier.width(120.dp) ) { EnhancedButton( contentPadding = PaddingValues(), onClick = { onNeedToRemoveImageAt(index) }, containerColor = MaterialTheme.colorScheme.secondaryContainer.copy( 0.5f ), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, shape = ShapeDefaults.bottom, modifier = Modifier .padding(top = 4.dp) .height(30.dp) .width(120.dp) ) { Text(stringResource(R.string.remove), fontSize = 11.sp) } } } } } } } } ImagePager( visible = previewUri != null, selectedUri = previewUri, uris = images, onNavigate = onNavigate, onUriSelected = { previewUri = it }, onShare = { context.shareUris(listOf(it)) }, onDismiss = { previewUri = null } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ImageTransformBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateIntAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.RotateLeft import androidx.compose.material.icons.automirrored.rounded.RotateRight import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.CropSmall import com.t8rin.imagetoolbox.core.resources.icons.Curve import com.t8rin.imagetoolbox.core.resources.icons.Draw import com.t8rin.imagetoolbox.core.resources.icons.Eraser import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.resources.icons.Flip import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun ImageTransformBar( onEditExif: () -> Unit = {}, imageFormat: ImageFormat? = null, onRotateLeft: () -> Unit, onFlip: () -> Unit, onRotateRight: () -> Unit, canRotate: Boolean = true, leadingContent: @Composable RowScope.() -> Unit = {}, ) { val shape = AutoCornersShape( animateIntAsState(if (imageFormat?.canWriteExif == false) 20 else 50).value ) Column( modifier = Modifier .container(shape) .animateContentSizeNoClip() ) { Row(verticalAlignment = Alignment.CenterVertically) { AnimatedVisibility( visible = imageFormat != null, modifier = Modifier.weight(1f, false) ) { Row { Spacer(Modifier.width(4.dp)) EnhancedButton( modifier = Modifier.height(40.dp), containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentPadding = PaddingValues(8.dp), onClick = onEditExif ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Spacer(Modifier.width(4.dp)) Icon( imageVector = Icons.Rounded.Exif, contentDescription = stringResource(R.string.edit_exif) ) Spacer(Modifier.width(8.dp)) Text( text = stringResource(R.string.edit_exif) ) Spacer(Modifier.width(8.dp)) } } Spacer( Modifier .weight(1f) .padding(4.dp) ) } } leadingContent() EnhancedIconButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onRotateLeft, enabled = canRotate ) { Icon( imageVector = Icons.AutoMirrored.Rounded.RotateLeft, contentDescription = "Rotate Left" ) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onFlip ) { Icon( imageVector = Icons.Outlined.Flip, contentDescription = "Flip" ) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onRotateRight, enabled = canRotate ) { Icon( imageVector = Icons.AutoMirrored.Rounded.RotateRight, contentDescription = "Rotate Right" ) } } FormatExifWarning(imageFormat) } } @Composable fun ImageExtraTransformBar( onCrop: () -> Unit, onFilter: () -> Unit, onDraw: () -> Unit, onEraseBackground: () -> Unit, onApplyCurves: () -> Unit ) { if (LocalSettingsState.current.generatePreviews) { Row(Modifier.container(shape = ShapeDefaults.circle)) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer.copy(0.6f), contentColor = MaterialTheme.colorScheme.onMixedContainer, onClick = onCrop ) { Icon( imageVector = Icons.Rounded.CropSmall, contentDescription = stringResource(R.string.crop) ) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer.copy(0.6f), contentColor = MaterialTheme.colorScheme.onMixedContainer, onClick = onApplyCurves ) { Icon( imageVector = Icons.Outlined.Curve, contentDescription = stringResource(R.string.tone_curves) ) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer.copy(0.6f), contentColor = MaterialTheme.colorScheme.onMixedContainer, onClick = onFilter ) { Icon( imageVector = Icons.Rounded.AutoFixHigh, contentDescription = stringResource(R.string.filter) ) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer.copy(0.6f), contentColor = MaterialTheme.colorScheme.onMixedContainer, onClick = onDraw ) { Icon( imageVector = Icons.Rounded.Draw, contentDescription = stringResource(R.string.draw) ) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer.copy(0.6f), contentColor = MaterialTheme.colorScheme.onMixedContainer, onClick = onEraseBackground ) { Icon( imageVector = Icons.Rounded.Eraser, contentDescription = stringResource(R.string.erase_background) ) } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/OOMWarning.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun OOMWarning( visible: Boolean, modifier: Modifier = Modifier.padding( top = 12.dp ) ) { AnimatedVisibility( visible = visible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column( modifier = modifier .container( color = MaterialTheme.colorScheme.errorContainer.copy( alpha = 0.7f ), resultPadding = 0.dp, shape = ShapeDefaults.default ) ) { Text( text = stringResource(R.string.image_size_warning), fontSize = 12.sp, modifier = Modifier.padding(8.dp), textAlign = TextAlign.Center, fontWeight = FontWeight.SemiBold, lineHeight = 14.sp, color = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.5f) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ResizeImageField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Calculate import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.restrict import com.t8rin.imagetoolbox.core.ui.widget.dialogs.CalculatorDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField @Composable fun ResizeImageField( imageInfo: ImageInfo, originalSize: IntegerSize?, onWidthChange: (Int) -> Unit, onHeightChange: (Int) -> Unit, modifier: Modifier = Modifier, showWarning: Boolean = false, enabled: Boolean = true, ) { Column( modifier = modifier .container(shape = ShapeDefaults.extraLarge) .padding(8.dp) .animateContentSizeNoClip() ) { Row { val widthField: @Composable RowScope.() -> Unit = { ResizeImageFieldImpl( value = imageInfo.width.takeIf { it > 0 } .let { it ?: "" } .toString(), onValueChange = { value -> val maxValue = if (imageInfo.height > 0) { (ImageUtils.Dimens.MAX_IMAGE_SIZE / imageInfo.height).coerceAtMost(32768) } else 32768 onWidthChange( value .restrict(maxValue) .toIntOrNull() ?: 0 ) }, shape = ShapeDefaults.smallStart, label = { AutoSizeText( stringResource( R.string.width, originalSize?.width?.toString() ?: "" ) ) }, modifier = Modifier.weight(1f), enabled = enabled ) } val heightField: @Composable RowScope.() -> Unit = { ResizeImageFieldImpl( value = imageInfo.height.takeIf { it > 0 } .let { it ?: "" } .toString(), onValueChange = { value -> val maxValue = if (imageInfo.width > 0) { (ImageUtils.Dimens.MAX_IMAGE_SIZE / imageInfo.width).coerceAtMost(32768) } else 32768 onHeightChange( value .restrict(maxValue) .toIntOrNull() ?: 0 ) }, shape = ShapeDefaults.smallEnd, label = { AutoSizeText( stringResource( R.string.height, originalSize?.height?.toString() ?: "" ) ) }, modifier = Modifier.weight(1f), enabled = enabled ) } widthField() Spacer(modifier = Modifier.width(4.dp)) heightField() } IcoSizeWarning( visible = imageInfo.run { imageFormat == ImageFormat.Ico && (width > 256 || height > 256) } ) OOMWarning(visible = showWarning) } } @Composable internal fun ResizeImageFieldImpl( modifier: Modifier, value: String, onValueChange: (String) -> Unit, label: @Composable () -> Unit, shape: Shape, enabled: Boolean ) { val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() var showCalculator by rememberSaveable { mutableStateOf(false) } RoundedTextField( value = value, onValueChange = onValueChange, shape = shape, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = label, endIcon = { AnimatedVisibility( visible = isFocused, enter = scaleIn() + fadeIn(), exit = scaleOut() + fadeOut() ) { EnhancedIconButton( onClick = { showCalculator = true } ) { Icon( imageVector = Icons.Outlined.Calculate, contentDescription = null ) } } }, modifier = modifier, interactionSource = interactionSource, enabled = enabled ) CalculatorDialog( visible = showCalculator, onDismiss = { showCalculator = false }, initialValue = value.toBigDecimalOrNull(), onValueChange = { onValueChange(it.toInt().toString()) } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SaveExifWidget.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun SaveExifWidget( checked: Boolean, imageFormat: ImageFormat, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, backgroundColor: Color = Color.Unspecified ) { val settingsState = LocalSettingsState.current LaunchedEffect(Unit) { onCheckedChange(settingsState.exifWidgetInitialState) } PreferenceRowSwitch( modifier = modifier, title = stringResource(R.string.keep_exif), subtitle = if (imageFormat.canWriteExif) { stringResource(R.string.keep_exif_sub) } else { stringResource( R.string.image_exif_warning, imageFormat.title ) }, checked = checked, enabled = imageFormat.canWriteExif, shape = ShapeDefaults.extraLarge, containerColor = backgroundColor, onClick = onCheckedChange, startIcon = Icons.Outlined.Exif ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/ScaleSmallImagesToLargeToggle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.LinearScale import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ScaleSmallImagesToLargeToggle( modifier: Modifier = Modifier, checked: Boolean, onCheckedChange: (Boolean) -> Unit ) { PreferenceRowSwitch( modifier = modifier, title = stringResource(R.string.scale_small_images_to_large), subtitle = stringResource(R.string.scale_small_images_to_large_sub), checked = checked, containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge, onClick = onCheckedChange, startIcon = Icons.Rounded.LinearScale ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/SortButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FilterAlt import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.SortType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun SortButton( modifier: Modifier = Modifier, iconSize: Dp = 20.dp, containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHighest, contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, onSortTypeSelected: (SortType) -> Unit ) { var showSortTypeSelection by rememberSaveable { mutableStateOf(false) } EnhancedIconButton( onClick = { showSortTypeSelection = true }, containerColor = containerColor, contentColor = contentColor, forceMinimumInteractiveComponentSize = false, modifier = modifier ) { Icon( imageVector = Icons.Rounded.FilterAlt, contentDescription = stringResource(R.string.sorting), modifier = Modifier.size(iconSize) ) } EnhancedModalBottomSheet( visible = showSortTypeSelection, onDismiss = { showSortTypeSelection = it }, title = { TitleItem( text = stringResource(R.string.sorting), icon = Icons.Rounded.FilterAlt ) }, confirmButton = { EnhancedButton( onClick = { showSortTypeSelection = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } } ) { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { val items = SortType.entries items.forEachIndexed { index, item -> Column( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.byIndex( index = index, size = items.size ), resultPadding = 0.dp ) .hapticsClickable { onSortTypeSelected(item) showSortTypeSelection = false } ) { TitleItem(text = item.title) } } } } } private val SortType.title: String @Composable get() = when (this) { SortType.DateModified -> stringResource(R.string.sort_by_date_modified) SortType.DateModifiedReversed -> stringResource(R.string.sort_by_date_modified_reversed) SortType.Name -> stringResource(R.string.sort_by_name) SortType.NameReversed -> stringResource(R.string.sort_by_name_reversed) SortType.Size -> stringResource(R.string.sort_by_size) SortType.SizeReversed -> stringResource(R.string.sort_by_size_reversed) SortType.MimeType -> stringResource(R.string.sort_by_mime_type) SortType.MimeTypeReversed -> stringResource(R.string.sort_by_mime_type_reversed) SortType.Extension -> stringResource(R.string.sort_by_extension) SortType.ExtensionReversed -> stringResource(R.string.sort_by_extension_reversed) SortType.DateAdded -> stringResource(R.string.sort_by_date_added) SortType.DateAddedReversed -> stringResource(R.string.sort_by_date_added_reversed) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.page import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Pages import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton @Composable fun PageInputDialog( visible: Boolean, onDismiss: () -> Unit, value: List?, onValueChange: (List) -> Unit, pagesCount: Int ) { var pages by rememberSaveable(visible) { mutableStateOf(value ?: emptyList()) } EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, title = { Text(stringResource(R.string.pages_selection)) }, icon = { Icon( imageVector = Icons.Rounded.Pages, contentDescription = null ) }, text = { PageInputField( selectedPages = pages, onPagesChanged = { pages = it } ) }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.close)) } }, confirmButton = { EnhancedButton( onClick = { onValueChange(pages.filter { it < pagesCount }) onDismiss() } ) { Text(stringResource(R.string.apply)) } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageInputField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.page import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField @Composable internal fun PageInputField( selectedPages: List, onPagesChanged: (List) -> Unit ) { var text by remember { mutableStateOf(PagesSelectionParser.formatPageOutput(selectedPages)) } RoundedTextField( value = text, onValueChange = { text = it val parsedPages = PagesSelectionParser.parsePageInput(it) onPagesChanged(parsedPages) }, textStyle = LocalTextStyle.current.copy( textAlign = TextAlign.Start ), label = stringResource(R.string.custom_pages), modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), singleLine = false ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PageSelectionItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.page import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Pages import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.utils.getString @Composable fun PageSelectionItem( value: List?, onValueChange: (List) -> Unit, pageCount: Int ) { var showSelector by rememberSaveable { mutableStateOf(false) } PreferenceItem( title = stringResource(R.string.pages_selection), subtitle = remember(value, pageCount) { derivedStateOf { value?.takeIf { it.isNotEmpty() } ?.let { if (it.size == pageCount) { getString(R.string.all) } else { PagesSelectionParser.formatPageOutput(it) } } ?: getString(R.string.none) } }.value, onClick = { showSelector = true }, modifier = Modifier.fillMaxWidth(), startIcon = Icons.Rounded.Pages, endIcon = Icons.Rounded.MiniEdit ) PageInputDialog( visible = showSelector, onDismiss = { showSelector = false }, value = value, onValueChange = onValueChange, pagesCount = pageCount ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/page/PagesSelectionParser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.page internal object PagesSelectionParser { fun parsePageInput(input: String): List { val pages = mutableSetOf() val regex = "\\d+(-\\d+)?".toRegex() regex.findAll(input).forEach { match -> val rangeParts = match.value.split("-").mapNotNull { it.toIntOrNull() } when (rangeParts.size) { 1 -> pages.add(rangeParts[0] - 1) 2 -> if (rangeParts[0] <= rangeParts[1]) { pages.addAll((rangeParts[0] - 1)..): String { if (pages.isEmpty()) return "" val pages = pages.sorted() val result = mutableListOf() var start = pages[0] var prev = pages[0] for (i in 1 until pages.size) { if (pages[i] != prev + 1) { result.add(if (start == prev) "${start + 1}" else "${start + 1}-${prev + 1}") start = pages[i] } prev = pages[i] } result.add(if (start == prev) "${start + 1}" else "${start + 1}-${prev + 1}") return result.joinToString(", ") } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/ResizeTypeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ResizeAnchor import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.state.derivedValueOf import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group.components.BlurRadiusSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group.components.UseBlurredBackgroundToggle import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PositionSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun ResizeTypeSelector( modifier: Modifier = Modifier, enabled: Boolean, value: ResizeType, onValueChange: (ResizeType) -> Unit, ) { var isSheetVisible by rememberSaveable { mutableStateOf(false) } var canvasColor by rememberSaveable(stateSaver = ColorSaver) { mutableStateOf(Color.Transparent) } var useBlurredBgInsteadOfColor by rememberSaveable { mutableStateOf(true) } var blurRadius by rememberSaveable { mutableIntStateOf(35) } var position by rememberSaveable { mutableStateOf(Position.Center) } val centerCropResizeType by remember( canvasColor, useBlurredBgInsteadOfColor, blurRadius, position ) { derivedStateOf { ResizeType.CenterCrop( canvasColor = canvasColor.toArgb() .takeIf { !useBlurredBgInsteadOfColor }, blurRadius = blurRadius, position = position ) } } val updateCropResizeType = { onValueChange(centerCropResizeType) } val fitResizeType by remember( canvasColor, useBlurredBgInsteadOfColor, blurRadius, position ) { derivedStateOf { ResizeType.Fit( canvasColor = canvasColor.toArgb() .takeIf { !useBlurredBgInsteadOfColor }, blurRadius = blurRadius, position = position ) } } val updateFitResizeType = { onValueChange(fitResizeType) } Column( modifier = modifier .container(shape = ShapeDefaults.extraLarge) .animateContentSizeNoClip(), horizontalAlignment = Alignment.CenterHorizontally ) { EnhancedButtonGroup( modifier = Modifier.padding(start = 3.dp, end = 2.dp), enabled = enabled, title = { Column { Spacer(modifier = Modifier.height(8.dp)) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( text = stringResource(R.string.resize_type), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.width(8.dp)) SupportingButton( onClick = { isSheetVisible = true } ) } Spacer(modifier = Modifier.height(8.dp)) } }, itemCount = ResizeType.entries.size, selectedIndex = derivedValueOf(value) { ResizeType.entries.indexOfFirst { it::class.isInstance(value) } }, onIndexChange = { onValueChange( when (it) { 0 -> ResizeType.Explicit 1 -> ResizeType.Flexible 2 -> centerCropResizeType 3 -> fitResizeType else -> ResizeType.Explicit } ) }, itemContent = { Text(stringResource(ResizeType.entries[it].getTitle())) } ) AnimatedVisibility( visible = value is ResizeType.Flexible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val entries = remember { ResizeAnchor.entries } val selectedIndex by remember(entries, value) { derivedStateOf { entries.indexOfFirst { it == (value as? ResizeType.Flexible)?.resizeAnchor } } } EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .padding(8.dp) .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ), itemCount = entries.size, selectedIndex = selectedIndex, itemContent = { Text(entries[it].title) }, onIndexChange = { onValueChange( ResizeType.Flexible(entries[it]) ) }, title = { Text( text = stringResource(R.string.resize_anchor), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium, modifier = Modifier .weight(1f) .padding(8.dp) ) }, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer ) } AnimatedVisibility( visible = value is ResizeType.CenterCrop, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column( modifier = Modifier.padding(8.dp) ) { PositionSelector( value = position, onValueChange = { position = it updateCropResizeType() }, shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) UseBlurredBackgroundToggle( checked = useBlurredBgInsteadOfColor, onCheckedChange = { useBlurredBgInsteadOfColor = it updateCropResizeType() }, shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) AnimatedContent(targetState = useBlurredBgInsteadOfColor) { showBlurRadius -> if (showBlurRadius) { BlurRadiusSelector( modifier = Modifier, value = blurRadius, color = MaterialTheme.colorScheme.surface, onValueChange = { blurRadius = it updateCropResizeType() }, shape = ShapeDefaults.bottom ) } else { ColorRowSelector( modifier = Modifier .container( shape = ShapeDefaults.bottom, color = MaterialTheme.colorScheme.surface ), value = canvasColor, onValueChange = { canvasColor = it updateCropResizeType() } ) } } } } AnimatedVisibility( visible = value is ResizeType.Fit, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column( modifier = Modifier.padding(8.dp) ) { PositionSelector( value = position, onValueChange = { position = it updateFitResizeType() }, shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) UseBlurredBackgroundToggle( checked = useBlurredBgInsteadOfColor, onCheckedChange = { useBlurredBgInsteadOfColor = it updateFitResizeType() }, shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) AnimatedContent(targetState = useBlurredBgInsteadOfColor) { showBlurRadius -> if (showBlurRadius) { BlurRadiusSelector( modifier = Modifier, value = blurRadius, color = MaterialTheme.colorScheme.surface, onValueChange = { blurRadius = it updateFitResizeType() }, shape = ShapeDefaults.bottom ) } else { ColorRowSelector( modifier = Modifier .container( shape = ShapeDefaults.bottom, color = MaterialTheme.colorScheme.surface ), value = canvasColor, onValueChange = { canvasColor = it updateFitResizeType() } ) } } } } } EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { ResizeType.entries.forEachIndexed { index, item -> Column( Modifier .fillMaxWidth() .container( shape = ShapeDefaults.byIndex( index = index, size = ResizeType.entries.size ), resultPadding = 0.dp ) ) { TitleItem(text = stringResource(item.getTitle())) Text( text = stringResource(item.getSubtitle()), modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 16.dp ), fontSize = 14.sp, lineHeight = 18.sp ) } } } }, visible = isSheetVisible, onDismiss = { isSheetVisible = it }, title = { TitleItem(text = stringResource(R.string.resize_type)) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { isSheetVisible = false } ) { AutoSizeText(stringResource(R.string.close)) } } ) } private val ResizeAnchor.title: String @Composable get() = when (this) { ResizeAnchor.Min -> stringResource(R.string.min) ResizeAnchor.Max -> stringResource(R.string.max) ResizeAnchor.Width -> stringResource(R.string.width, "") ResizeAnchor.Height -> stringResource(R.string.height, "") ResizeAnchor.Default -> stringResource(R.string.basic, "") } private fun ResizeType.getTitle(): Int = when (this) { is ResizeType.CenterCrop -> R.string.crop is ResizeType.Explicit -> R.string.explicit is ResizeType.Flexible -> R.string.flexible is ResizeType.Fit -> R.string.fit } private fun ResizeType.getSubtitle(): Int = when (this) { is ResizeType.CenterCrop -> R.string.crop_description is ResizeType.Explicit -> R.string.explicit_description is ResizeType.Flexible -> R.string.flexible_description is ResizeType.Fit -> R.string.fit_description } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/BlurRadiusSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BlurCircular import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlin.math.roundToInt @Composable fun BlurRadiusSelector( modifier: Modifier, value: Int, color: Color = MaterialTheme.colorScheme.surfaceContainer, valueRange: ClosedFloatingPointRange = 5f..100f, onValueChange: (Int) -> Unit, shape: Shape = ShapeDefaults.default ) { EnhancedSliderItem( modifier = modifier, value = value, title = stringResource(R.string.blur_radius), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.BlurCircular, valueRange = valueRange, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange(it.roundToInt()) }, shape = shape, containerColor = color ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/resize_group/components/UseBlurredBackgroundToggle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BlurLinear import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun UseBlurredBackgroundToggle( modifier: Modifier = Modifier, checked: Boolean, onCheckedChange: (Boolean) -> Unit, shape: Shape = ShapeDefaults.default ) { PreferenceRowSwitch( modifier = modifier, title = stringResource(R.string.blur_edges), subtitle = stringResource(R.string.blur_edges_sub), checked = checked, shape = shape, containerColor = MaterialTheme.colorScheme.surface, onClick = onCheckedChange, startIcon = Icons.Rounded.BlurLinear ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/AlphaSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Opacity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun AlphaSelector( value: Float, onValueChange: (Float) -> Unit, onValueChangeFinished: ((Float) -> Unit)? = null, modifier: Modifier = Modifier, color: Color = Color.Unspecified, shape: Shape = ShapeDefaults.extraLarge, title: String = stringResource(R.string.paint_alpha), icon: ImageVector = Icons.Rounded.Opacity ) { EnhancedSliderItem( modifier = modifier, value = value, icon = icon, title = title, sliderModifier = Modifier .padding(top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(it.roundToTwoDigits()) }, onValueChangeFinished = onValueChangeFinished, shape = shape, containerColor = color ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/BlendingModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Layers import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.entries import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun BlendingModeSelector( value: BlendingMode, onValueChange: (BlendingMode) -> Unit, entries: List = remember { mutableListOf().apply { add(BlendingMode.SrcOver) addAll( BlendingMode .entries .toList() - listOf( BlendingMode.SrcOver, BlendingMode.Clear, BlendingMode.Src, BlendingMode.Dst ).toSet() ) } }, modifier: Modifier = Modifier, shape: Shape = ShapeDefaults.large, color: Color = MaterialTheme.colorScheme.surface ) { DataSelector( value = value, onValueChange = onValueChange, entries = entries, title = stringResource(R.string.overlay_mode), titleIcon = Icons.Outlined.Layers, itemContentText = { it.toString() }, modifier = modifier, shape = shape, containerColor = color ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ColorRowSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.outlined.PushPin import androidx.compose.material.icons.rounded.Block import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RichTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRow import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.launch @Composable fun ColorRowSelector( value: Color?, onValueChange: (Color) -> Unit, modifier: Modifier = Modifier, title: String = stringResource(R.string.background_color), icon: ImageVector? = Icons.Outlined.Palette, allowAlpha: Boolean = true, allowScroll: Boolean = true, defaultColors: List = ColorSelectionRowDefaults.colorListVariant, topEndIcon: (@Composable () -> Unit)? = null, contentHorizontalPadding: Dp = 12.dp, onNullClick: (() -> Unit)? = null ) { val isCompactLayout = LocalSettingsState.current.isCompactSelectorsLayout val tooltipState = rememberTooltipState() val scope = rememberCoroutineScope() Column( modifier = modifier.then( if (isCompactLayout && icon != null) { Modifier.pointerInput(Unit) { detectTapGestures( onLongPress = { scope.launch { tooltipState.show() } } ) } } else Modifier ), ) { if (!isCompactLayout) { TitleItem( icon = icon, text = title, iconEndPadding = 14.dp, modifier = Modifier.padding( top = if (topEndIcon == null) { 12.dp } else { 6.dp }, start = contentHorizontalPadding, end = if (topEndIcon == null) { contentHorizontalPadding } else { (contentHorizontalPadding - 8.dp).coerceAtLeast(0.dp) } ), endContent = topEndIcon?.let { { it() } } ) } Row( verticalAlignment = Alignment.CenterVertically ) { if (isCompactLayout) { Box { AnimatedContent(icon) { icon -> if (icon != null) { TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider( TooltipAnchorPosition.Above ), tooltip = { RichTooltip( colors = TooltipDefaults.richTooltipColors( containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer.copy( 0.5f ), titleContentColor = MaterialTheme.colorScheme.onTertiaryContainer ), title = { Text(title) }, text = { if (value != null) { Box( modifier = Modifier .size(24.dp) .container( shape = ShapeDefaults.circle, color = value, resultPadding = 0.dp ) ) } else { Icon( imageVector = Icons.Rounded.Block, contentDescription = null, modifier = Modifier .size(24.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceVariant, resultPadding = 0.dp ), tint = MaterialTheme.colorScheme.onSurfaceVariant ) } } ) }, state = tooltipState, content = { IconShapeContainer( content = { Icon( imageVector = icon, contentDescription = null ) }, modifier = Modifier .padding( start = contentHorizontalPadding ) .clip( LocalSettingsState.current.iconShape?.shape ?: ShapeDefaults.circle ) .hapticsCombinedClickable( onLongClick = { scope.launch { tooltipState.show() } }, onClick = { scope.launch { tooltipState.show() } } ) ) } ) } } BoxAnimatedVisibility(icon == null) { Text( text = title, modifier = Modifier .padding( start = contentHorizontalPadding ) .widthIn(max = 100.dp), style = MaterialTheme.typography.bodyMedium, lineHeight = 16.sp ) } } Spacer(Modifier.width(12.dp)) } ColorSelectionRow( defaultColors = defaultColors, allowAlpha = allowAlpha, contentPadding = PaddingValues( start = if (isCompactLayout) 0.dp else contentHorizontalPadding, end = contentHorizontalPadding ), allowScroll = allowScroll, value = value, onValueChange = onValueChange, modifier = Modifier.weight(1f), onNullClick = onNullClick ) if (isCompactLayout && topEndIcon != null) { topEndIcon() } } } } @Composable @Preview private fun Preview() = ImageToolboxThemeForPreview(false) { var color by remember { mutableStateOf(null) } CompositionLocalProvider( LocalSettingsState provides LocalSettingsState.current.copy(isCompactSelectorsLayout = true) ) { ColorRowSelector( value = color, onNullClick = { color = null }, onValueChange = { color = it }, modifier = Modifier .padding(20.dp) .padding(vertical = 100.dp) .fillMaxWidth() .container( shape = ShapeDefaults.large ), icon = Icons.Rounded.Palette, title = stringResource(R.string.selected_color), topEndIcon = { EnhancedIconButton( onClick = {} ) { Icon( imageVector = Icons.Outlined.PushPin, contentDescription = null ) } } ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/DataSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.delay @Composable fun DataSelector( value: T, onValueChange: (T) -> Unit, entries: List, title: String?, titleIcon: ImageVector?, itemContentText: @Composable (T) -> String, itemContentIcon: ((T, Boolean) -> ImageVector?)? = null, itemEqualityDelegate: (T, T) -> Boolean = { t, o -> t == o }, spanCount: Int = 3, modifier: Modifier = Modifier, badgeContent: (@Composable RowScope.() -> Unit)? = null, shape: Shape = ShapeDefaults.large, containerColor: Color = Color.Unspecified, selectedItemColor: Color = MaterialTheme.colorScheme.tertiary, initialExpanded: Boolean = false, canExpand: Boolean = true, contentPadding: PaddingValues = PaddingValues(8.dp), behaveAsContainer: Boolean = true, titlePadding: PaddingValues = PaddingValues( top = 12.dp, start = 12.dp, bottom = 8.dp ) ) { val realSpanCount = spanCount.coerceAtLeast(1) Column( modifier = modifier.then( if (behaveAsContainer) { Modifier.container( shape = shape, color = containerColor ) } else Modifier ) ) { var expanded by rememberSaveable(initialExpanded, realSpanCount, canExpand) { mutableStateOf( if (canExpand) initialExpanded && realSpanCount > 1 else true ) } val showExpand = realSpanCount > 1 && canExpand val showTitle = badgeContent != null || title != null if (showExpand || showTitle) { Row { if (showTitle) { Row( modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically ) { title?.let { TitleItem( text = title, icon = titleIcon, modifier = Modifier .padding( top = titlePadding.calculateTopPadding(), start = titlePadding.calculateStartPadding( LocalLayoutDirection.current ), bottom = titlePadding.calculateBottomPadding() ) .weight(1f, false) ) } badgeContent?.let { EnhancedBadge( content = badgeContent, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = titlePadding.calculateBottomPadding() + 4.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } } } if (showExpand) { val rotation by animateFloatAsState(if (expanded) 180f else 0f) EnhancedIconButton( containerColor = Color.Transparent, onClick = { expanded = !expanded } ) { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate(rotation) ) } } } } if (canExpand) { val state = rememberLazyStaggeredGridState() LaunchedEffect(value, entries) { delay(300) val targetIndex = entries.indexOf(value).takeIf { it >= 0 } ?: 0 if (state.layoutInfo.visibleItemsInfo.all { it.index != targetIndex }) { state.scrollToItem(targetIndex) } } LazyHorizontalStaggeredGrid( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), state = state, horizontalItemSpacing = 8.dp, rows = StaggeredGridCells.Adaptive(30.dp), modifier = Modifier .heightIn( max = animateDpAsState( if (expanded) { 52.dp * realSpanCount - 8.dp * (realSpanCount - 1) } else 52.dp ).value ) .fadingEdges( scrollableState = state, isVertical = false, spanCount = realSpanCount ), contentPadding = contentPadding, flingBehavior = enhancedFlingBehavior() ) { items(entries) { item -> ChipItem( item = item, value = value, onValueChange = onValueChange, itemContentText = itemContentText, itemContentIcon = itemContentIcon, itemEqualityDelegate = itemEqualityDelegate, selectedItemColor = selectedItemColor ) } } } else { FlowRow( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.Start ), modifier = Modifier.padding(contentPadding) ) { entries.forEach { item -> ChipItem( item = item, value = value, onValueChange = onValueChange, itemContentText = itemContentText, itemContentIcon = itemContentIcon, itemEqualityDelegate = itemEqualityDelegate, selectedItemColor = selectedItemColor ) } } } } } @Composable private fun ChipItem( item: T, value: T, onValueChange: (T) -> Unit, itemContentText: @Composable (T) -> String, itemContentIcon: ((T, Boolean) -> ImageVector?)?, itemEqualityDelegate: (T, T) -> Boolean, selectedItemColor: Color, ) { val selected by remember(item, value) { derivedStateOf { itemEqualityDelegate(value, item) } } EnhancedChip( selected = selected, onClick = { onValueChange(item) }, selectedColor = selectedItemColor, contentPadding = PaddingValues( horizontal = 12.dp, vertical = 8.dp ), modifier = Modifier.height(36.dp) ) { Row( verticalAlignment = Alignment.CenterVertically ) { AnimatedContent( targetState = itemContentIcon?.invoke(item, selected), contentKey = { it == null } ) { icon -> icon?.let { Icon( imageVector = icon, contentDescription = null, modifier = Modifier .padding(end = 8.dp) .size(18.dp) ) } } Text( text = itemContentText(item) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/FontSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TextFields import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.ui.theme.ProvideTypography import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun FontSelector( value: UiFontFamily, onValueChange: (UiFontFamily) -> Unit, modifier: Modifier = Modifier, title: String = stringResource(R.string.font), containerColor: Color = MaterialTheme.colorScheme.surface, shape: Shape = ShapeDefaults.large, behaveAsContainer: Boolean = true ) { Column( modifier = modifier.then( if (behaveAsContainer) { Modifier.container( shape = shape, color = containerColor ) } else Modifier ) ) { val fonts = UiFontFamily.entries var expanded by rememberSaveable { mutableStateOf(false) } Row(verticalAlignment = Alignment.CenterVertically) { val rotation by animateFloatAsState(if (expanded) 180f else 0f) TitleItem( text = title, icon = if (behaveAsContainer) Icons.Outlined.TextFields else null, modifier = Modifier.padding(top = 12.dp, start = 12.dp, bottom = 8.dp) ) EnhancedBadge( content = { Text(fonts.size.toString()) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) Spacer(modifier = Modifier.weight(1f)) EnhancedIconButton( containerColor = Color.Transparent, onClick = { expanded = !expanded } ) { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate(rotation) ) } } val state = rememberLazyStaggeredGridState() LazyHorizontalStaggeredGrid( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), state = state, horizontalItemSpacing = 8.dp, rows = StaggeredGridCells.Adaptive(30.dp), modifier = Modifier .heightIn(max = animateDpAsState(if (expanded) 140.dp else 52.dp).value) .fadingEdges( scrollableState = state, isVertical = false, spanCount = 3 ), contentPadding = PaddingValues(8.dp), flingBehavior = enhancedFlingBehavior() ) { items(fonts) { font -> ProvideTypography(font) { EnhancedChip( selected = font == value, onClick = { onValueChange(font) }, selectedColor = MaterialTheme.colorScheme.secondary, contentPadding = PaddingValues( horizontal = 12.dp, vertical = 8.dp ), modifier = Modifier.height(36.dp) ) { AutoSizeText( text = font.name ?: stringResource(id = R.string.system), maxLines = 1 ) } } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/HelperGridParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.outlined.LineWeight import androidx.compose.material.icons.outlined.TableRows import androidx.compose.material.icons.outlined.ViewColumn import androidx.compose.material.icons.rounded.FormatLineSpacing import androidx.compose.material.icons.rounded.GridOn import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun HelperGridParamsSelector( value: HelperGridParams, onValueChange: (HelperGridParams) -> Unit, modifier: Modifier = Modifier, shape: Shape = ShapeDefaults.extraLarge, ) { Column( modifier = modifier.container( shape = shape, resultPadding = 0.dp ) ) { PreferenceRowSwitch( modifier = Modifier.clip(shape), startIcon = Icons.Rounded.GridOn, drawContainer = false, checked = value.enabled, onClick = { onValueChange(value.copy(enabled = it)) }, title = stringResource(R.string.helper_grid), subtitle = stringResource(R.string.helper_grid_sub) ) AnimatedVisibility(value.enabled) { Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { Spacer(Modifier.height(4.dp)) ColorRowSelector( value = value.color.toColor(), onValueChange = { onValueChange(value.copy(color = it.toArgb())) }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .container( shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp ) .padding(start = 4.dp), icon = Icons.Outlined.ColorLens, title = stringResource(R.string.grid_color) ) EnhancedSliderItem( value = value.linesWidth, title = stringResource(R.string.line_width), icon = Icons.Outlined.LineWeight, internalStateTransformation = { it.roundToTwoDigits() }, valueSuffix = " Pt", shape = ShapeDefaults.center, onValueChange = { onValueChange(value.copy(linesWidth = it)) }, valueRange = 0f..1.5f, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier.padding(horizontal = 8.dp) ) EnhancedSliderItem( value = value.cellWidth, title = stringResource(R.string.cell_width), icon = Icons.Outlined.ViewColumn, internalStateTransformation = { it.roundToTwoDigits() }, valueSuffix = " Pt", shape = ShapeDefaults.center, onValueChange = { onValueChange(value.copy(cellWidth = it)) }, valueRange = 1f..100f, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier.padding(horizontal = 8.dp) ) EnhancedSliderItem( value = value.cellHeight, title = stringResource(R.string.cell_height), icon = Icons.Outlined.TableRows, internalStateTransformation = { it.roundToTwoDigits() }, valueSuffix = " Pt", shape = ShapeDefaults.center, onValueChange = { onValueChange(value.copy(cellHeight = it)) }, valueRange = 1f..100f, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier.padding(horizontal = 8.dp) ) PreferenceRowSwitch( startIcon = Icons.Rounded.FormatLineSpacing, drawContainer = true, shape = ShapeDefaults.bottom, checked = value.withPrimaryLines, onClick = { onValueChange(value.copy(withPrimaryLines = it)) }, title = stringResource(R.string.primary_lines), subtitle = stringResource(R.string.primary_lines_sub), containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier.padding(horizontal = 8.dp) ) Spacer(Modifier.height(4.dp)) } } } } @Composable @Preview private fun Preview() = ImageToolboxThemeForPreview(false) { var value by remember { mutableStateOf(HelperGridParams(enabled = true)) } HelperGridParamsSelector( value = value, onValueChange = { value = it } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageFormatSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnnecessaryVariable") package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import android.os.Build import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Architecture import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.alphaContainedEntries import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImagesearchRoller import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.modifier.Disableable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.utils.getString import kotlinx.coroutines.launch @Composable fun ImageFormatSelector( modifier: Modifier = Modifier, backgroundColor: Color = Color.Unspecified, entries: List = ImageFormatGroup.entries, forceEnabled: Boolean = false, value: ImageFormat?, quality: Quality? = null, onValueChange: (ImageFormat) -> Unit, shape: Shape = ShapeDefaults.extraLarge, enableItemsCardBackground: Boolean = true, title: @Composable ColumnScope.() -> Unit = { Text( text = stringResource(R.string.image_format), modifier = Modifier .fillMaxWidth() .padding(bottom = 4.dp), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) }, onAutoClick: (() -> Unit)? = null ) { val settingsState = LocalSettingsState.current val cannotChangeFormat: () -> Unit = { AppToastHost.showToast( message = getString(R.string.cannot_change_image_format), icon = Icons.Rounded.Architecture ) } val allFormats by remember(entries) { derivedStateOf { entries.flatMap { it.formats } } } LaunchedEffect(value, allFormats) { if (value != null && value !in allFormats) { onValueChange( if (ImageFormat.Png.Lossless in allFormats) { ImageFormat.Png.Lossless } else { allFormats.first() } ) } } Column( modifier = modifier .container( shape = shape, color = backgroundColor ) .animateContentSizeNoClip(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) { val formats by remember(value) { derivedStateOf { entries.firstOrNull { value in it.formats }?.formats ?: emptyList() } } val filteredFormats = if (enableItemsCardBackground) { formats.filteredFormats() } else { allFormats.filteredFormats() } val enableBackgroundColorForAlphaFormats = settingsState.enableBackgroundColorForAlphaFormats val isNonAlpha = value !in ImageFormat.alphaContainedEntries || quality?.isNonAlpha() == true val showBackgroundSelector = value != null && (isNonAlpha || enableBackgroundColorForAlphaFormats) val entriesSize = (if (filteredFormats.size > 1) 1 else 0) .plus(if (showBackgroundSelector) 1 else 0) .plus(1) Disableable( enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite || forceEnabled, onDisabledClick = cannotChangeFormat ) { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) { Spacer(Modifier.height(4.dp)) title(this) AnimatedContent( targetState = entries.filtered(), modifier = Modifier.fillMaxWidth() ) { items -> FlowRow( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterHorizontally ), modifier = Modifier .fillMaxWidth() .then( if (enableItemsCardBackground) { Modifier .padding(horizontal = 8.dp) .container( shape = ShapeDefaults.byIndex(0, entriesSize), color = MaterialTheme.colorScheme.surface ) .padding(horizontal = 8.dp, vertical = 12.dp) } else Modifier.padding(8.dp) ) ) { onAutoClick?.let { EnhancedChip( onClick = onAutoClick, selected = value == null, label = { Text(text = stringResource(R.string.auto)) }, selectedColor = MaterialTheme.colorScheme.tertiary, contentPadding = PaddingValues( horizontal = 16.dp, vertical = 6.dp ) ) } if (enableItemsCardBackground) { items.forEach { EnhancedChip( onClick = { onValueChange(it.formats[0]) }, selected = value in it.formats, label = { Text(text = it.title) }, selectedColor = MaterialTheme.colorScheme.tertiary, contentPadding = PaddingValues( horizontal = 16.dp, vertical = 6.dp ) ) } } else { filteredFormats.forEach { EnhancedChip( onClick = { onValueChange(it) }, selected = value == it, label = { Text(text = it.title) }, selectedColor = MaterialTheme.colorScheme.tertiary, contentPadding = PaddingValues( horizontal = 16.dp, vertical = 6.dp ) ) } } } } } } AnimatedVisibility( visible = filteredFormats.size > 1 && enableItemsCardBackground, modifier = Modifier.fillMaxWidth() ) { Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .container( color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp, shape = ShapeDefaults.byIndex(1, entriesSize), ) .padding(horizontal = 16.dp, vertical = 12.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = stringResource(R.string.compression_type), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.height(16.dp)) AnimatedContent( targetState = filteredFormats, modifier = Modifier.fillMaxWidth() ) { items -> FlowRow( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterHorizontally ), modifier = Modifier.fillMaxWidth() ) { items.forEach { EnhancedChip( onClick = { onValueChange(it) }, selected = value == it, label = { Text(text = it.title) }, selectedColor = MaterialTheme.colorScheme.tertiary, contentPadding = PaddingValues( horizontal = 16.dp, vertical = 6.dp ) ) } } } } } val scope = rememberCoroutineScope() val simpleSettingsInteractor = LocalSimpleSettingsInteractor.current var previousEnabled by rememberSaveable { mutableStateOf(showBackgroundSelector) } LaunchedEffect(showBackgroundSelector, value) { if (previousEnabled != showBackgroundSelector) { val previous = value previous?.let { onValueChange( ImageFormat.entries.run { rightFrom(indexOf(value)) } ) onValueChange(previous) } previousEnabled = showBackgroundSelector } } AnimatedVisibility( visible = showBackgroundSelector, modifier = Modifier.fillMaxWidth() ) { val index = if (filteredFormats.size > 1 && enableItemsCardBackground) 2 else 1 ColorRowSelector( modifier = Modifier .fillMaxWidth() .then( if (enableItemsCardBackground) { Modifier .padding(horizontal = 8.dp) .container( color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp, shape = ShapeDefaults.byIndex(index, entriesSize), ) .padding(8.dp) } else Modifier ), value = settingsState.backgroundForNoAlphaImageFormats, icon = Icons.Outlined.ImagesearchRoller, onValueChange = { scope.launch { simpleSettingsInteractor.setBackgroundColorForNoAlphaFormats( color = it.toModel() ) val previous = value previous?.let { onValueChange( ImageFormat.entries.run { rightFrom(indexOf(value)) } ) onValueChange(previous) } } }, allowAlpha = !isNonAlpha && enableBackgroundColorForAlphaFormats ) } if (enableItemsCardBackground) Spacer(Modifier.height(4.dp)) } } @Composable private fun List.filtered(): List = remember(this) { if (Build.VERSION.SDK_INT <= 24) this - ImageFormatGroup.highLevelFormats.toSet() else this } @Composable private fun List.filteredFormats(): List = remember(this) { if (Build.VERSION.SDK_INT <= 24) this - ImageFormat.highLevelFormats.toSet() else this } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ImageSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.twotone.InsertDriveFile import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload @Composable fun ImageSelector( value: Any?, onValueChange: (Uri) -> Unit, title: String = stringResource(id = R.string.image), subtitle: String?, modifier: Modifier = Modifier, autoShadowElevation: Dp = 1.dp, color: Color = MaterialTheme.colorScheme.surfaceContainerLow, shape: Shape = ShapeDefaults.large, contentScale: ContentScale = ContentScale.Crop ) { val imagePicker = rememberImagePicker(onSuccess = onValueChange) var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } PreferenceItemOverload( title = title, subtitle = subtitle, onClick = imagePicker::pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, autoShadowElevation = autoShadowElevation, startIcon = { Picture( contentScale = contentScale, model = value, shape = CloverShape, modifier = Modifier.size(48.dp), error = { Icon( imageVector = Icons.TwoTone.AddPhotoAlt, contentDescription = null, modifier = Modifier .fillMaxSize() .clip(CloverShape) .background( color = MaterialTheme.colorScheme.secondaryContainer .copy(0.5f) .compositeOver(color) ) .padding(8.dp) ) } ) }, endIcon = { Icon( imageVector = Icons.Rounded.MiniEdit, contentDescription = stringResource(R.string.edit) ) }, modifier = modifier, shape = shape, containerColor = color, drawStartIconContainer = false ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) } @Composable fun FileSelector( value: String?, onValueChange: (Uri) -> Unit, title: String = stringResource(id = R.string.pick_file), subtitle: String?, modifier: Modifier = Modifier, autoShadowElevation: Dp = 1.dp, color: Color = MaterialTheme.colorScheme.surfaceContainerLow, shape: Shape = ShapeDefaults.large ) { val pickFileLauncher = rememberFilePicker(onSuccess = onValueChange) PreferenceItemOverload( title = title, subtitle = if (subtitle == null && value != null) { rememberFilename(value.toUri()) } else subtitle, onClick = pickFileLauncher::pickFile, autoShadowElevation = autoShadowElevation, startIcon = { Picture( contentScale = ContentScale.Crop, model = value, shape = CloverShape, modifier = Modifier.size(48.dp), error = { Icon( imageVector = Icons.AutoMirrored.TwoTone.InsertDriveFile, contentDescription = null, modifier = Modifier .fillMaxSize() .clip(CloverShape) .background( MaterialTheme.colorScheme.secondaryContainer .copy(0.5f) .compositeOver(color) ) .padding(8.dp) ) } ) }, endIcon = { Icon( imageVector = Icons.Rounded.MiniEdit, contentDescription = stringResource(R.string.edit) ) }, modifier = modifier, shape = shape, containerColor = color, drawStartIconContainer = false ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/MagnifierEnabledSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ZoomIn import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import kotlinx.coroutines.launch @Composable fun MagnifierEnabledSelector( modifier: Modifier = Modifier, shape: Shape = ShapeDefaults.default, ) { val scope = rememberCoroutineScope() val settingsState = LocalSettingsState.current val settingsInteractor = LocalSimpleSettingsInteractor.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.magnifier), subtitle = stringResource(R.string.magnifier_sub), checked = settingsState.magnifierEnabled, onClick = { scope.launch { settingsInteractor.toggleMagnifierEnabled() } }, startIcon = Icons.Outlined.ZoomIn ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PositionSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Place import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun PositionSelector( value: Position, onValueChange: (Position) -> Unit, entries: List = Position.entries, modifier: Modifier = Modifier, shape: Shape = ShapeDefaults.large, color: Color = MaterialTheme.colorScheme.surface, selectedItemColor: Color = MaterialTheme.colorScheme.tertiary, ) { DataSelector( value = value, onValueChange = onValueChange, entries = entries, spanCount = 2, title = stringResource(R.string.position), titleIcon = Icons.Outlined.Place, itemContentText = { it.translatedName }, modifier = modifier, shape = shape, containerColor = color, selectedItemColor = selectedItemColor ) } private val Position.translatedName: String @Composable get() = when (this) { Position.Center -> stringResource(id = R.string.center) Position.TopLeft -> stringResource(id = R.string.top_left) Position.TopRight -> stringResource(id = R.string.top_right) Position.BottomLeft -> stringResource(id = R.string.bottom_left) Position.BottomRight -> stringResource(id = R.string.bottom_right) Position.TopCenter -> stringResource(id = R.string.top_center) Position.CenterRight -> stringResource(id = R.string.center_right) Position.BottomCenter -> stringResource(id = R.string.bottom_center) Position.CenterLeft -> stringResource(id = R.string.center_left) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/PresetSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FitScreen import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.rounded.AspectRatio import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.EditAlt import com.t8rin.imagetoolbox.core.resources.icons.Telegram import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalEditPresetsController import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.controls.OOMWarning import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.AspectRatioSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.other.RevealDirection import com.t8rin.imagetoolbox.core.ui.widget.other.RevealValue import com.t8rin.imagetoolbox.core.ui.widget.other.SwipeToReveal import com.t8rin.imagetoolbox.core.ui.widget.other.rememberRevealState import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextFieldColors import kotlinx.coroutines.launch @Composable fun PresetSelector( value: Preset, includeTelegramOption: Boolean = false, includeAspectRatioOption: Boolean = false, isBytesResize: Boolean = false, showWarning: Boolean = false, onValueChange: (Preset) -> Unit ) { val settingsState = LocalSettingsState.current val editPresetsController = LocalEditPresetsController.current val data by remember(settingsState.presets, value) { derivedStateOf { settingsState.presets.let { val currentValue = value.value() if (currentValue !in it && !value.isTelegram() && currentValue != null) { listOf(currentValue) + it } else it } } } val state = rememberRevealState() val scope = rememberCoroutineScope() var showPresetInfoDialog by remember { mutableStateOf(false) } val canEnterPresetsByTextField = settingsState.canEnterPresetsByTextField SwipeToReveal( directions = setOf( RevealDirection.EndToStart ), maxRevealDp = 88.dp, state = state, swipeableContent = { Column( modifier = Modifier .container(shape = ShapeDefaults.extraLarge) .pointerInput(Unit) { detectTapGestures( onLongPress = { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } }, onDoubleTap = { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } } ) } .animateContentSizeNoClip(), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(Modifier.height(8.dp)) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( text = stringResource(R.string.presets), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.width(8.dp)) SupportingButton( onClick = { showPresetInfoDialog = true } ) } Spacer(Modifier.height(8.dp)) AnimatedVisibility(visible = value is Preset.AspectRatio && includeAspectRatioOption) { val aspectRatios = remember { DomainAspectRatio.defaultList.drop(3) } Column( modifier = Modifier.padding(horizontal = 8.dp) ) { AspectRatioSelector( modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(8.dp), selectedAspectRatio = remember(value, aspectRatios) { derivedStateOf { aspectRatios.firstOrNull { it.value == (value as? Preset.AspectRatio)?.ratio } } }.value, onAspectRatioChange = { domainAspectRatio, _ -> if (value is Preset.AspectRatio) { onValueChange( value.copy(ratio = domainAspectRatio.value) ) } else { onValueChange( Preset.AspectRatio( ratio = domainAspectRatio.value, isFit = false ) ) } }, title = {}, aspectRatios = aspectRatios, shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface, unselectedCardColor = MaterialTheme.colorScheme.surfaceContainerHigh ) Spacer(modifier = Modifier.height(4.dp)) PreferenceRowSwitch( modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.fit_to_bounds), subtitle = stringResource(R.string.fit_to_bounds_sub), checked = (value as? Preset.AspectRatio)?.isFit == true, onClick = { if (value is Preset.AspectRatio) { onValueChange( value.copy(isFit = it) ) } else { onValueChange( Preset.AspectRatio( ratio = 1f, isFit = it ) ) } }, startIcon = Icons.Outlined.FitScreen, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(8.dp)) } } Box( contentAlignment = Alignment.Center, modifier = Modifier.padding(bottom = 8.dp) ) { val listState = rememberLazyListState() LazyRow( state = listState, modifier = Modifier .fadingEdges(listState) .padding(vertical = 1.dp), horizontalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterHorizontally ), contentPadding = PaddingValues(horizontal = 8.dp), flingBehavior = enhancedFlingBehavior() ) { if (includeTelegramOption && settingsState.filenameBehavior !is FilenameBehavior.Overwrite) { item(key = "tg") { val selected = value.isTelegram() EnhancedChip( selected = selected, onClick = { onValueChange(Preset.Telegram) }, selectedColor = MaterialTheme.colorScheme.primary, shape = MaterialTheme.shapes.medium ) { Icon( imageVector = Icons.Rounded.Telegram, contentDescription = stringResource(R.string.telegram) ) } } } if (includeAspectRatioOption) { item(key = "aspect") { val selected = value.isAspectRatio() EnhancedChip( selected = selected, onClick = { onValueChange( Preset.AspectRatio( ratio = 1f, isFit = false ) ) }, selectedColor = MaterialTheme.colorScheme.primary, shape = MaterialTheme.shapes.medium ) { Icon( imageVector = Icons.Rounded.AspectRatio, contentDescription = stringResource(R.string.aspect_ratio) ) } } } items( items = data, key = { it } ) { val selected = value.value() == it EnhancedChip( selected = selected, onClick = { onValueChange(Preset.Percentage(it)) }, selectedColor = MaterialTheme.colorScheme.primary, shape = MaterialTheme.shapes.medium ) { AutoSizeText(it.toString()) } } } } AnimatedVisibility(canEnterPresetsByTextField) { var textValue by remember(value) { mutableStateOf( value.value()?.toString() ?: "" ) } RoundedTextField( onValueChange = { targetText -> if (targetText.isEmpty()) { textValue = "" onValueChange(Preset.None) } else { val newValue = targetText.filter { it.isDigit() }.toIntOrNull()?.coerceIn(0, 500) textValue = newValue?.toString() ?: "" newValue?.let { onValueChange( Preset.Percentage(it) ) } ?: onValueChange(Preset.None) } }, value = textValue, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = stringResource(R.string.enter_percentage), modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp), colors = RoundedTextFieldColors( isError = false, containerColor = MaterialTheme.colorScheme.surface, focusedIndicatorColor = MaterialTheme.colorScheme.secondary ).let { it.copy( unfocusedIndicatorColor = it.unfocusedIndicatorColor.copy(0.5f) .compositeOver( it.unfocusedContainerColor ) ) } ) } OOMWarning( visible = showWarning, modifier = Modifier.padding(4.dp) ) } }, revealedContentEnd = { Box( modifier = Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.surfaceContainerLowest, shape = ShapeDefaults.extraLarge, autoShadowElevation = 0.5.dp ) ) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, onClick = editPresetsController::open, modifier = Modifier .padding(16.dp) .align(Alignment.CenterEnd) ) { Icon( imageVector = Icons.Rounded.EditAlt, contentDescription = stringResource(R.string.edit) ) } } } ) EnhancedAlertDialog( visible = showPresetInfoDialog, onDismissRequest = { showPresetInfoDialog = false }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showPresetInfoDialog = false } ) { Text(stringResource(R.string.ok)) } }, title = { Text(stringResource(R.string.presets)) }, icon = { Icon( imageVector = Icons.Outlined.Info, contentDescription = stringResource(R.string.about_app) ) }, text = { if (isBytesResize) Text(stringResource(R.string.presets_sub_bytes)) else Text(stringResource(R.string.presets_sub)) } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/QualitySelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.outlined.Speed import androidx.compose.material.icons.rounded.Stream import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.TiffCompressionScheme import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.QualityHigh import com.t8rin.imagetoolbox.core.resources.icons.QualityLow import com.t8rin.imagetoolbox.core.resources.icons.QualityMedium import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.saver.OneTimeEffect import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable fun QualitySelector( imageFormat: ImageFormat, quality: Quality, onQualityChange: (Quality) -> Unit, modifier: Modifier = Modifier, icon: ImageVector? = null, shape: Shape = ShapeDefaults.extraLarge, inactiveButtonColor: Color = MaterialTheme.colorScheme.surfaceContainer, activeButtonColor: Color = MaterialTheme.colorScheme.secondary, autoCoerce: Boolean = true ) { val settingsState = LocalSettingsState.current var actualImageFormat by remember { mutableStateOf(imageFormat) } LaunchedEffect(imageFormat, quality) { if ( actualImageFormat.canChangeCompressionValue == imageFormat.canChangeCompressionValue || !actualImageFormat.canChangeCompressionValue ) { actualImageFormat = imageFormat } else { launch { delay(1000) }.invokeOnCompletion { actualImageFormat = imageFormat } } if (autoCoerce) { onQualityChange( quality.coerceIn(imageFormat) ) } } AnimatedVisibility( visible = imageFormat.canChangeCompressionValue, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { if (autoCoerce) { OneTimeEffect { if (quality != settingsState.defaultQuality) { onQualityChange(settingsState.defaultQuality) } } } Column( modifier = modifier.container(shape) ) { actualImageFormat.compressionTypes.forEach { type -> val currentIcon by remember(quality, icon) { derivedStateOf { when { icon != null -> icon actualImageFormat.isHighQuality(quality.qualityValue) -> Icons.Outlined.QualityHigh actualImageFormat.isMidQuality(quality.qualityValue) -> Icons.Outlined.QualityMedium else -> Icons.Outlined.QualityLow } } } val isQuality = type is ImageFormat.CompressionType.Quality val isEffort = type is ImageFormat.CompressionType.Effort val compressingLiteral = if (isQuality) "%" else "" EnhancedSliderItem( value = when (type) { is ImageFormat.CompressionType.Effort -> { when (quality) { is Quality.Base -> quality.qualityValue is Quality.Jxl -> quality.effort is Quality.PngLossy -> quality.compressionLevel is Quality.Avif -> quality.effort is Quality.Tiff -> quality.qualityValue } } is ImageFormat.CompressionType.Quality -> quality.qualityValue }, title = if (isQuality) { stringResource(R.string.quality) } else stringResource(R.string.effort), icon = if (isQuality) currentIcon else Icons.Rounded.Stream, valueRange = type.compressionRange.let { it.first.toFloat()..it.last.toFloat() }, steps = type.compressionRange.let { it.last - it.first - 1 }, internalStateTransformation = { it.roundToInt().coerceIn(type.compressionRange).toFloat() }, onValueChange = { when (type) { is ImageFormat.CompressionType.Effort -> { onQualityChange( when (quality) { is Quality.Base -> quality.copy(qualityValue = it.toInt()) is Quality.Jxl -> quality.copy(effort = it.toInt()) is Quality.PngLossy -> quality.copy(compressionLevel = it.toInt()) is Quality.Avif -> quality.copy(effort = it.toInt()) is Quality.Tiff -> quality.copy(compressionScheme = it.toInt()) }.coerceIn(actualImageFormat) ) } is ImageFormat.CompressionType.Quality -> { onQualityChange( when (quality) { is Quality.Base -> quality.copy(qualityValue = it.toInt()) is Quality.Jxl -> quality.copy(qualityValue = it.toInt()) is Quality.PngLossy -> quality.copy(compressionLevel = it.toInt()) is Quality.Avif -> quality.copy(qualityValue = it.toInt()) is Quality.Tiff -> quality.copy(compressionScheme = it.toInt()) }.coerceIn(actualImageFormat) ) } } }, valueSuffix = " $compressingLiteral", behaveAsContainer = false, titleFontWeight = FontWeight.Medium ) { AnimatedVisibility(isEffort) { Text( text = stringResource( R.string.effort_sub, type.compressionRange.first, type.compressionRange.last ), fontSize = 12.sp, textAlign = TextAlign.Center, lineHeight = 12.sp, color = LocalContentColor.current.copy(0.5f), modifier = Modifier .padding(4.dp) .container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) .padding(6.dp) ) } } } AnimatedVisibility(actualImageFormat is ImageFormat.Jxl) { val jxlQuality = quality as? Quality.Jxl Column { EnhancedSliderItem( value = jxlQuality?.speed ?: 0, title = stringResource(R.string.speed), icon = Icons.Outlined.Speed, valueRange = 0f..4f, steps = 3, internalStateTransformation = { it.roundToInt().coerceIn(0..4).toFloat() }, onValueChange = { jxlQuality?.copy( speed = it.roundToInt() )?.coerceIn(actualImageFormat)?.let(onQualityChange) }, behaveAsContainer = false ) { Text( text = stringResource( R.string.speed_sub, 0, 4 ), fontSize = 12.sp, textAlign = TextAlign.Center, lineHeight = 12.sp, color = LocalContentColor.current.copy(0.5f), modifier = Modifier .padding(4.dp) .container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) .padding(6.dp) ) } val items = remember { Quality.Channels.entries } EnhancedButtonGroup( itemCount = items.size, itemContent = { Text(items[it].title) }, modifier = Modifier .fillMaxWidth() .padding(4.dp) .container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) .padding(4.dp), title = { Text( text = stringResource(R.string.channels_configuration), modifier = Modifier.padding(vertical = 4.dp) ) }, selectedIndex = items.indexOfFirst { it == jxlQuality?.channels }, onIndexChange = { jxlQuality?.copy( channels = Quality.Channels.fromInt(it) )?.coerceIn(actualImageFormat)?.let(onQualityChange) }, inactiveButtonColor = inactiveButtonColor, activeButtonColor = activeButtonColor ) } } AnimatedVisibility(actualImageFormat is ImageFormat.Png.Lossy) { val pngLossyQuality = quality as? Quality.PngLossy EnhancedSliderItem( value = pngLossyQuality?.maxColors ?: 0, title = stringResource(R.string.max_colors_count), icon = Icons.Outlined.ColorLens, valueRange = 2f..1024f, internalStateTransformation = { it.roundToInt().coerceIn(2..1024).toFloat() }, onValueChange = { pngLossyQuality?.copy( maxColors = it.roundToInt() )?.coerceIn(actualImageFormat)?.let(onQualityChange) }, behaveAsContainer = false ) } AnimatedVisibility(actualImageFormat is ImageFormat.Tiff || actualImageFormat is ImageFormat.Tif) { val tiffQuality = quality as? Quality.Tiff val compressionItems = TiffCompressionScheme.safeEntries Column( horizontalAlignment = Alignment.CenterHorizontally ) { TitleItem( text = stringResource(R.string.tiff_compression_scheme), modifier = Modifier .padding(top = 12.dp, start = 12.dp, bottom = 8.dp, end = 12.dp) ) Column( modifier = Modifier .fillMaxWidth() .padding(4.dp) .container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) ) { val state = rememberLazyStaggeredGridState() LazyHorizontalStaggeredGrid( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), state = state, horizontalItemSpacing = 8.dp, rows = StaggeredGridCells.Adaptive(30.dp), modifier = Modifier .heightIn(max = 100.dp) .fadingEdges( scrollableState = state, isVertical = false, spanCount = 2 ), contentPadding = PaddingValues(8.dp), flingBehavior = enhancedFlingBehavior() ) { items(compressionItems) { val selected by remember(it, tiffQuality?.compressionScheme) { derivedStateOf { tiffQuality?.compressionScheme == it.ordinal } } EnhancedChip( selected = selected, onClick = { tiffQuality?.copy( compressionScheme = it.ordinal )?.coerceIn(actualImageFormat)?.let(onQualityChange) }, selectedColor = MaterialTheme.colorScheme.tertiary, contentPadding = PaddingValues( horizontal = 12.dp, vertical = 8.dp ), modifier = Modifier.height(36.dp) ) { AutoSizeText( text = compressionItems[it.ordinal].title, maxLines = 1 ) } } } } } } } } } private fun ImageFormat.isHighQuality(quality: Int): Boolean { val range = compressionTypes[0].compressionRange.run { endInclusive - start } return quality > range * (4 / 5f) } private fun ImageFormat.isMidQuality(quality: Int): Boolean { val range = compressionTypes[0].compressionRange.run { endInclusive - start } return quality > range * (2 / 5f) } private val TiffCompressionScheme.title: String get() = when (this) { TiffCompressionScheme.CCITTRLE -> "RLE" TiffCompressionScheme.CCITTFAX3 -> "FAX 3" TiffCompressionScheme.CCITTFAX4 -> "FAX 4" TiffCompressionScheme.ADOBE_DEFLATE -> "ADOBE DEFLATE" else -> this.name } private val Quality.Channels.title @Composable get() = when (this) { Quality.Channels.RGBA -> "RGBA" Quality.Channels.RGB -> "RGB" Quality.Channels.Monochrome -> stringResource(R.string.monochrome) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/controls/selection/ScaleModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.controls.selection import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.ScaleColorSpace import com.t8rin.imagetoolbox.core.domain.image.model.title import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun ScaleModeSelector( value: ImageScaleMode, onValueChange: (ImageScaleMode) -> Unit, modifier: Modifier = Modifier, backgroundColor: Color = Color.Unspecified, shape: Shape = ShapeDefaults.extraLarge, enableItemsCardBackground: Boolean = true, titlePadding: PaddingValues = PaddingValues(top = 8.dp), titleArrangement: Arrangement.Horizontal = Arrangement.Center, entries: List = ImageScaleMode.defaultEntries(), title: @Composable RowScope.() -> Unit = { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = stringResource(R.string.scale_mode), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium, modifier = Modifier.weight(1f, false) ) EnhancedBadge( content = { Text(entries.size.toString()) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } } ) { val isColorSpaceSelectionVisible = enableItemsCardBackground && value !is ImageScaleMode.Base var showInfoSheet by rememberSaveable { mutableStateOf(false) } val settingsState = LocalSettingsState.current LaunchedEffect(settingsState) { if (value != settingsState.defaultImageScaleMode) { onValueChange(settingsState.defaultImageScaleMode) } } Column( modifier = modifier .container( shape = shape, color = backgroundColor ), horizontalAlignment = Alignment.CenterHorizontally ) { Row( modifier = Modifier .fillMaxWidth() .padding(titlePadding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = titleArrangement ) { title() Spacer(modifier = Modifier.width(8.dp)) SupportingButton( onClick = { showInfoSheet = true } ) } Spacer(modifier = Modifier.height(8.dp)) val chipsModifier = Modifier .fillMaxWidth() .then( if (enableItemsCardBackground) { Modifier .padding(horizontal = 8.dp) .container( color = MaterialTheme.colorScheme.surface, shape = animateShape( if (isColorSpaceSelectionVisible) { ShapeDefaults.top } else ShapeDefaults.default ) ) .padding(horizontal = 8.dp, vertical = 12.dp) } else Modifier.padding(8.dp) ) val state = rememberLazyStaggeredGridState() LazyHorizontalStaggeredGrid( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), state = state, horizontalItemSpacing = 8.dp, rows = StaggeredGridCells.Adaptive(30.dp), modifier = Modifier .heightIn(max = if (enableItemsCardBackground) 160.dp else 140.dp) .then(chipsModifier) .fadingEdges( scrollableState = state, isVertical = false, spanCount = 3 ), contentPadding = PaddingValues(2.dp), flingBehavior = enhancedFlingBehavior() ) { items(entries) { val selected by remember(value, it) { derivedStateOf { value::class.isInstance(it) } } EnhancedChip( onClick = { onValueChange(it.copy(value.scaleColorSpace)) }, selected = selected, label = { Text(text = stringResource(id = it.title)) }, contentPadding = PaddingValues(horizontal = 16.dp, vertical = 6.dp), selectedColor = MaterialTheme.colorScheme.outlineVariant( 0.2f, MaterialTheme.colorScheme.tertiary ), selectedContentColor = MaterialTheme.colorScheme.onTertiary, unselectedContentColor = MaterialTheme.colorScheme.onSurface ) } } AnimatedVisibility( visible = isColorSpaceSelectionVisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val items = remember { ScaleColorSpace.entries } DataSelector( value = value.scaleColorSpace, onValueChange = { onValueChange( value.copy(it) ) }, spanCount = 2, entries = items, title = stringResource(R.string.tag_color_space), titleIcon = Icons.Outlined.ColorLens, itemContentText = { it.title }, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.bottom, modifier = Modifier .fillMaxWidth() .padding(top = 4.dp) .padding(horizontal = 8.dp), selectedItemColor = MaterialTheme.colorScheme.secondary ) } AnimatedVisibility(isColorSpaceSelectionVisible || enableItemsCardBackground) { Spacer(Modifier.height(8.dp)) } } EnhancedModalBottomSheet( sheetContent = { LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed(entries) { index, item -> val selected by remember(value, item) { derivedStateOf { value::class.isInstance(item) } } val containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor } val contentColor = takeColorFromScheme { if (selected) onSecondaryContainer else onSurface } Column( modifier = Modifier .fillMaxWidth() .container( color = containerColor, shape = ShapeDefaults.byIndex( index = index, size = entries.size ), resultPadding = 0.dp ) .hapticsClickable { onValueChange(item) } ) { TitleItem(text = stringResource(id = item.title)) Text( text = stringResource(id = item.subtitle), modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 16.dp ), fontSize = 14.sp, lineHeight = 18.sp, color = contentColor ) } } } }, visible = showInfoSheet, onDismiss = { showInfoSheet = it }, title = { TitleItem(text = stringResource(R.string.scale_mode)) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showInfoSheet = false } ) { AutoSizeText(stringResource(R.string.close)) } } ) } @Composable fun ImageScaleMode.Companion.defaultEntries(): List { val context = LocalResourceManager.current return remember { listOf(ImageScaleMode.Base) + simpleEntries.sortedBy { context.getString(it.title) } + complexEntries.sortedBy { context.getString(it.title) } } } val ScaleColorSpace.title: String @Composable get() = when (this) { is ScaleColorSpace.Linear -> stringResource(R.string.linear) is ScaleColorSpace.SRGB -> "sRGB" is ScaleColorSpace.LAB -> "LAB" is ScaleColorSpace.LUV -> "LUV" is ScaleColorSpace.Sigmoidal -> stringResource(R.string.sigmoidal) is ScaleColorSpace.XYZ -> "XYZ" is ScaleColorSpace.F32Gamma22 -> "${stringResource(R.string.gamma)} 2.2" is ScaleColorSpace.F32Gamma28 -> "${stringResource(R.string.gamma)} 2.8" is ScaleColorSpace.F32Rec709 -> "Rec.709" is ScaleColorSpace.F32sRGB -> "F32 sRGB" is ScaleColorSpace.LCH -> "LCH" is ScaleColorSpace.OklabGamma22 -> "Oklab G2.2" is ScaleColorSpace.OklabGamma28 -> "Oklab G2.8" is ScaleColorSpace.OklabRec709 -> "Oklab Rec.709" is ScaleColorSpace.OklabSRGB -> "Oklab sRGB" is ScaleColorSpace.JzazbzGamma22 -> "Jzazbz ${stringResource(R.string.gamma)} 2.2" is ScaleColorSpace.JzazbzGamma28 -> "Jzazbz ${stringResource(R.string.gamma)} 2.8" is ScaleColorSpace.JzazbzRec709 -> "Jzazbz Rec.709" is ScaleColorSpace.JzazbzSRGB -> "Jzazbz sRGB" } private val ImageScaleMode.subtitle: Int get() = when (this) { ImageScaleMode.Base, ImageScaleMode.NotPresent -> R.string.basic_sub is ImageScaleMode.Bilinear -> R.string.bilinear_sub is ImageScaleMode.Nearest -> R.string.nearest_sub is ImageScaleMode.Cubic -> R.string.cubic_sub is ImageScaleMode.Mitchell -> R.string.mitchell_sub is ImageScaleMode.Catmull -> R.string.catmull_sub is ImageScaleMode.Hermite -> R.string.hermite_sub is ImageScaleMode.BSpline -> R.string.bspline_sub is ImageScaleMode.Hann -> R.string.hann_sub is ImageScaleMode.Bicubic -> R.string.bicubic_sub is ImageScaleMode.Hamming -> R.string.hamming_sub is ImageScaleMode.Hanning -> R.string.hanning_sub is ImageScaleMode.Blackman -> R.string.blackman_sub is ImageScaleMode.Welch -> R.string.welch_sub is ImageScaleMode.Quadric -> R.string.quadric_sub is ImageScaleMode.Gaussian -> R.string.gaussian_sub is ImageScaleMode.Sphinx -> R.string.sphinx_sub is ImageScaleMode.Bartlett -> R.string.bartlett_sub is ImageScaleMode.Robidoux -> R.string.robidoux_sub is ImageScaleMode.RobidouxSharp -> R.string.robidoux_sharp_sub is ImageScaleMode.Spline16 -> R.string.spline16_sub is ImageScaleMode.Spline36 -> R.string.spline36_sub is ImageScaleMode.Spline64 -> R.string.spline64_sub is ImageScaleMode.Kaiser -> R.string.kaiser_sub is ImageScaleMode.BartlettHann -> R.string.bartlett_hann_sub is ImageScaleMode.Box -> R.string.box_sub is ImageScaleMode.Bohman -> R.string.bohman_sub is ImageScaleMode.Lanczos2 -> R.string.lanczos2_sub is ImageScaleMode.Lanczos3 -> R.string.lanczos3_sub is ImageScaleMode.Lanczos4 -> R.string.lanczos4_sub is ImageScaleMode.Lanczos2Jinc -> R.string.lanczos2_jinc_sub is ImageScaleMode.Lanczos3Jinc -> R.string.lanczos3_jinc_sub is ImageScaleMode.Lanczos4Jinc -> R.string.lanczos4_jinc_sub is ImageScaleMode.EwaHanning -> R.string.ewa_hanning_sub is ImageScaleMode.EwaRobidoux -> R.string.ewa_robidoux_sub is ImageScaleMode.EwaBlackman -> R.string.ewa_blackman_sub is ImageScaleMode.EwaQuadric -> R.string.ewa_quadric_sub is ImageScaleMode.EwaRobidouxSharp -> R.string.ewa_robidoux_sharp_sub is ImageScaleMode.EwaLanczos3Jinc -> R.string.ewa_lanczos3_jinc_sub is ImageScaleMode.Ginseng -> R.string.ginseng_sub is ImageScaleMode.EwaGinseng -> R.string.ewa_ginseng_sub is ImageScaleMode.EwaLanczosSharp -> R.string.ewa_lanczos_sharp_sub is ImageScaleMode.EwaLanczos4Sharpest -> R.string.ewa_lanczos_4_sharpest_sub is ImageScaleMode.EwaLanczosSoft -> R.string.ewa_lanczos_soft_sub is ImageScaleMode.HaasnSoft -> R.string.haasn_soft_sub is ImageScaleMode.Lagrange2 -> R.string.lagrange_2_sub is ImageScaleMode.Lagrange3 -> R.string.lagrange_3_sub is ImageScaleMode.Lanczos6 -> R.string.lanczos_6_sub is ImageScaleMode.Lanczos6Jinc -> R.string.lanczos_6_jinc_sub is ImageScaleMode.Area -> R.string.area_sub } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/CalculatorDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Calculate import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.github.keelar.exprk.Expressions import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import java.math.BigDecimal @Composable fun CalculatorDialog( visible: Boolean, onDismiss: () -> Unit, initialValue: BigDecimal?, onValueChange: (BigDecimal) -> Unit ) { var calculatorExpression by rememberSaveable(initialValue, visible) { mutableStateOf(initialValue?.toString() ?: "") } EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, confirmButton = { EnhancedButton( onClick = { runCatching { Expressions().eval(calculatorExpression) }.onFailure { AppToastHost.showFailureToast(it) }.onSuccess { onValueChange(it) onDismiss() } } ) { Text(stringResource(R.string.apply)) } }, title = { Text( text = stringResource(R.string.calculate) ) }, icon = { Icon( imageVector = Icons.Outlined.Calculate, contentDescription = null ) }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.close)) } }, text = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { OutlinedTextField( shape = ShapeDefaults.default, value = calculatorExpression, textStyle = MaterialTheme.typography.titleMedium.copy( textAlign = TextAlign.Center ), maxLines = 1, placeholder = { Text( text = stringResource(R.string.math_expression), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) }, onValueChange = { expr -> calculatorExpression = expr.filter { !it.isWhitespace() } }, supportingText = { Text(stringResource(R.string.calculate_hint)) } ) } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ExitWithoutSavingDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.activity.compose.BackHandler import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton @Composable fun ExitWithoutSavingDialog( onExit: () -> Unit, onDismiss: () -> Unit, visible: Boolean, placeAboveAll: Boolean = false, text: String = stringResource(R.string.image_not_saved_sub), title: String = stringResource(R.string.image_not_saved), icon: ImageVector = Icons.Outlined.Save ) { val settingsState = LocalSettingsState.current if (!settingsState.enableToolExitConfirmation) { LaunchedEffect(visible) { if (visible) onExit() } } else { EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, placeAboveAll = placeAboveAll, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onDismiss() onExit() } ) { Text(stringResource(R.string.exit)) } }, confirmButton = { EnhancedButton( onClick = onDismiss ) { Text(stringResource(R.string.stay)) } }, title = { Text(text = title) }, text = { Text( text = text, textAlign = TextAlign.Center ) }, icon = { Icon( imageVector = icon, contentDescription = null ) } ) } } @Composable fun ExitBackHandler( enabled: Boolean = true, onBack: () -> Unit ) { val settingsState = LocalSettingsState.current if (settingsState.enableToolExitConfirmation) { BackHandler( enabled = enabled, onBack = onBack ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/LoadingDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.keepScreenOn import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.BasicEnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable @Composable fun LoadingDialog( visible: Boolean, onCancelLoading: () -> Unit = {}, canCancel: Boolean = true, isForSaving: Boolean = true ) { var showWantDismissDialog by remember(canCancel, visible) { mutableStateOf(false) } BasicEnhancedAlertDialog( visible = visible, onDismissRequest = { showWantDismissDialog = canCancel }, modifier = Modifier.keepScreenOn() ) { val focus = LocalFocusManager.current LaunchedEffect(focus) { focus.clearFocus() } Box( modifier = Modifier .fillMaxSize() .tappable { showWantDismissDialog = canCancel }, contentAlignment = Alignment.Center, content = { EnhancedLoadingIndicator(modifier = Modifier.size(108.dp)) } ) } WantCancelLoadingDialog( visible = showWantDismissDialog, onCancelLoading = onCancelLoading, onDismissDialog = { showWantDismissDialog = false }, isForSaving = isForSaving ) } @Composable fun LoadingDialog( visible: Boolean, done: Int, left: Int, onCancelLoading: () -> Unit, canCancel: Boolean = true, ) { if (left < 0) { LoadingDialog( visible = visible, onCancelLoading = onCancelLoading, canCancel = canCancel && visible ) } else { ProgressLoadingDialog( visible = visible, done = done, left = left, onCancelLoading = onCancelLoading, canCancel = canCancel && visible ) } } @Composable fun LoadingDialog( visible: Boolean, progress: () -> Float, onCancelLoading: () -> Unit = {}, canCancel: Boolean = true, loaderSize: Dp = 60.dp, switchToIndicator: Boolean = false, isLayoutSwappable: Boolean = true, additionalContent: @Composable (Dp) -> Unit = {} ) { val progress = progress() if (progress == 1f && visible && isLayoutSwappable) { LoadingDialog( visible = true, onCancelLoading = onCancelLoading, canCancel = canCancel ) } else { var showWantDismissDialog by remember(canCancel, visible) { mutableStateOf(false) } BasicEnhancedAlertDialog( visible = visible, onDismissRequest = { showWantDismissDialog = canCancel }, modifier = Modifier.keepScreenOn() ) { val focus = LocalFocusManager.current LaunchedEffect(focus) { focus.clearFocus() } Box( modifier = Modifier .fillMaxSize() .tappable(Unit) { showWantDismissDialog = canCancel }, contentAlignment = Alignment.Center, content = { AnimatedContent( targetState = switchToIndicator, modifier = Modifier .size(120.dp) .align(Alignment.Center) ) { isIndicator -> Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { if (isIndicator) { EnhancedLoadingIndicator( modifier = Modifier.size(108.dp) ) } else { EnhancedLoadingIndicator( progress = progress(), loaderSize = loaderSize, additionalContent = additionalContent ) } } } } ) } WantCancelLoadingDialog( visible = showWantDismissDialog, onCancelLoading = onCancelLoading, onDismissDialog = { showWantDismissDialog = false }, modifier = Modifier.keepScreenOn() ) } } @Composable private fun ProgressLoadingDialog( visible: Boolean, done: Int, left: Int, onCancelLoading: () -> Unit, canCancel: Boolean = true, ) { var showWantDismissDialog by remember(canCancel, visible) { mutableStateOf(false) } BasicEnhancedAlertDialog( visible = visible, onDismissRequest = { showWantDismissDialog = canCancel }, modifier = Modifier.keepScreenOn() ) { val focus = LocalFocusManager.current LaunchedEffect(focus) { focus.clearFocus() } Box( modifier = Modifier .fillMaxSize() .tappable(Unit) { showWantDismissDialog = canCancel }, contentAlignment = Alignment.Center, content = { EnhancedLoadingIndicator( done = done, left = left ) } ) } WantCancelLoadingDialog( visible = showWantDismissDialog, onCancelLoading = onCancelLoading, onDismissDialog = { showWantDismissDialog = false }, modifier = Modifier.keepScreenOn() ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeImagePickingDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ImageSearch import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.PicturePickerMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.saver.PicturePickerModeSaver @Composable fun OneTimeImagePickingDialog( visible: Boolean, onDismiss: () -> Unit, picker: Picker, imagePicker: ImagePicker ) { val settingsState = LocalSettingsState.current var selectedPickerMode by rememberSaveable(stateSaver = PicturePickerModeSaver) { mutableStateOf(settingsState.picturePickerMode) } EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, confirmButton = { EnhancedButton( onClick = { onDismiss() imagePicker.pickImageWithMode( picker = picker, picturePickerMode = selectedPickerMode ) }, containerColor = MaterialTheme.colorScheme.primary ) { Text(text = stringResource(id = R.string.pick)) } }, dismissButton = { EnhancedButton( onClick = onDismiss, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(text = stringResource(id = R.string.close)) } }, icon = { Icon( imageVector = Icons.Outlined.ImageSearch, contentDescription = stringResource(id = R.string.image_source) ) }, title = { Text(text = stringResource(id = R.string.image_source)) }, text = { val scrollState = rememberScrollState() ProvideTextStyle(LocalTextStyle.current.copy(textAlign = TextAlign.Start)) { Column( modifier = Modifier .fadingEdges( scrollableState = scrollState, isVertical = true ) .enhancedVerticalScroll(scrollState) .padding(vertical = 2.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { val data = remember { PicturePickerMode.entries } data.forEachIndexed { index, mode -> val selected = selectedPickerMode.ordinal == mode.ordinal val shape = ShapeDefaults.byIndex( index = index, size = data.size ) PreferenceItem( shape = shape, onClick = { selectedPickerMode = mode }, title = stringResource(mode.title), startIcon = mode.icon, containerColor = takeColorFromScheme { if (selected) secondaryContainer.copy(0.7f) else SafeLocalContainerColor }, endIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else Icons.Rounded.RadioButtonUnchecked, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) { MaterialTheme.colorScheme.onSecondaryContainer.copy( 0.5f ) } else Color.Transparent ).value, shape = shape ) ) } } } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/OneTimeSaveLocationSelectionDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CreateNewFolder import androidx.compose.material.icons.outlined.DriveFileRenameOutline import androidx.compose.material.icons.outlined.SaveAs import androidx.compose.material.icons.rounded.Folder import androidx.compose.material.icons.rounded.FolderOpen import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.FileReplace import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.domain.model.OneTimeSaveLocation import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFolderPicker import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.other.RevealDirection import com.t8rin.imagetoolbox.core.ui.widget.other.RevealValue import com.t8rin.imagetoolbox.core.ui.widget.other.SwipeToReveal import com.t8rin.imagetoolbox.core.ui.widget.other.rememberRevealState import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.core.utils.uiPath import kotlinx.coroutines.launch @Composable fun OneTimeSaveLocationSelectionDialog( visible: Boolean, onDismiss: () -> Unit, onSaveRequest: ((String?) -> Unit)?, formatForFilenameSelection: ImageFormat? = null ) { val settingsState = LocalSettingsState.current val settingsInteractor = LocalSimpleSettingsInteractor.current var tempSelectedSaveFolderUri by rememberSaveable(visible) { mutableStateOf(settingsState.saveFolderUri?.toString()) } var selectedSaveFolderUri by rememberSaveable(visible) { mutableStateOf(settingsState.saveFolderUri?.toString()) } EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, confirmButton = { onSaveRequest?.let { EnhancedButton( onClick = { onDismiss() onSaveRequest(selectedSaveFolderUri) }, containerColor = MaterialTheme.colorScheme.primary ) { Text(text = stringResource(id = R.string.save)) } } }, dismissButton = { EnhancedButton( onClick = onDismiss, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(text = stringResource(id = R.string.close)) } }, icon = { Icon( imageVector = Icons.Outlined.SaveAs, contentDescription = stringResource(id = R.string.folder) ) }, title = { Text(text = stringResource(id = R.string.folder)) }, text = { val data by remember(settingsState.oneTimeSaveLocations, tempSelectedSaveFolderUri) { derivedStateOf { settingsState.oneTimeSaveLocations.plus( tempSelectedSaveFolderUri?.let { OneTimeSaveLocation( uri = it, date = null, count = 0 ) } ).plus( settingsState.saveFolderUri?.toString()?.let { OneTimeSaveLocation( uri = it, date = null, count = 0 ) } ).distinctBy { it?.uri } } } val scope = rememberCoroutineScope() val scrollState = rememberScrollState() Column( modifier = Modifier .fadingEdges( scrollableState = scrollState, isVertical = true ) .enhancedVerticalScroll(scrollState) ) { Spacer(Modifier.height(4.dp)) data.forEachIndexed { index, item -> val title by remember(item) { derivedStateOf { val default = getString(R.string.default_folder) item?.uri?.toUri()?.uiPath(default = default) ?: default } } val subtitle by remember(item) { derivedStateOf { if (item?.uri == settingsState.saveFolderUri?.toString()) { getString(R.string.default_value) } else { val time = item?.date?.let { timestamp( format = "dd MMMM yyyy", date = it ) } ?: "" "$time ${ item?.count?.takeIf { it > 0 } ?.let { "($it)" } ?: "" }".trim() .takeIf { it.isNotEmpty() } } } } val selected = selectedSaveFolderUri == item?.uri val state = rememberRevealState() val interactionSource = remember { MutableInteractionSource() } val isDragged by interactionSource.collectIsDraggedAsState() val shape = ShapeDefaults.byIndex( index = index, size = data.size + 1, forceDefault = isDragged ) val canDeleteItem by remember(item, settingsState) { derivedStateOf { item != null && item in settingsState.oneTimeSaveLocations } } SwipeToReveal( state = state, revealedContentEnd = { Box( modifier = Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.errorContainer, shape = shape, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } scope.launch { settingsInteractor.setOneTimeSaveLocations((settingsState.oneTimeSaveLocations - item).filterNotNull()) if (item?.uri == selectedSaveFolderUri) { selectedSaveFolderUri = null tempSelectedSaveFolderUri = null } } } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete), modifier = Modifier .padding(16.dp) .padding(end = 8.dp) .align(Alignment.CenterEnd), tint = MaterialTheme.colorScheme.onErrorContainer ) } }, directions = setOf(RevealDirection.EndToStart), swipeableContent = { PreferenceItem( title = title, subtitle = subtitle, shape = shape, titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, onClick = { if (item != null) { tempSelectedSaveFolderUri = item.uri } selectedSaveFolderUri = item?.uri }, onLongClick = if (item != null) { { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } } } else null, enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite, startIconTransitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier.fillMaxWidth(), startIcon = if (selected) { Icons.Rounded.Folder } else Icons.Rounded.FolderOpen, endIcon = if (selected) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked, containerColor = takeColorFromScheme { if (selected) surface else surfaceContainer } ) }, enableSwipe = canDeleteItem && settingsState.filenameBehavior !is FilenameBehavior.Overwrite, interactionSource = interactionSource, modifier = Modifier .fadingEdges( scrollableState = null, length = 4.dp ) .padding(horizontal = 4.dp, vertical = 2.dp) ) } val currentFolderUri = selectedSaveFolderUri?.toUri() ?: settingsState.saveFolderUri val launcher = rememberFolderPicker( onSuccess = { uri -> tempSelectedSaveFolderUri = uri.toString() selectedSaveFolderUri = uri.toString() } ) PreferenceItem( title = stringResource(id = R.string.add_new_folder), startIcon = Icons.Outlined.CreateNewFolder, shape = ShapeDefaults.bottom, titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, onClick = { launcher.pickFolder(currentFolderUri) }, enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite, modifier = Modifier .fillMaxWidth() .padding(horizontal = 4.dp, vertical = 2.dp), containerColor = MaterialTheme.colorScheme.surfaceContainer ) if (formatForFilenameSelection != null) { val createLauncher = rememberFileCreator( mimeType = formatForFilenameSelection.mimeType, onSuccess = { uri -> onSaveRequest?.invoke(uri.toString()) onDismiss() } ) val imageString = stringResource(R.string.image) PreferenceItem( title = stringResource(id = R.string.custom_filename), subtitle = stringResource(id = R.string.custom_filename_sub), startIcon = Icons.Outlined.DriveFileRenameOutline, shape = ShapeDefaults.default, titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, enabled = settingsState.filenameBehavior !is FilenameBehavior.Overwrite, onClick = { createLauncher.make("$imageString.${formatForFilenameSelection.extension}") }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 4.dp, vertical = 2.dp), containerColor = MaterialTheme.colorScheme.surfaceContainer ) } PreferenceRowSwitch( title = stringResource(id = R.string.overwrite_files), subtitle = stringResource(id = R.string.overwrite_files_sub_short), startIcon = Icons.Outlined.FileReplace, enabled = settingsState.filenameBehavior is FilenameBehavior.Overwrite || settingsState.filenameBehavior is FilenameBehavior.None, shape = ShapeDefaults.default, titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, checked = settingsState.filenameBehavior is FilenameBehavior.Overwrite, onClick = { scope.launch { settingsInteractor.toggleOverwriteFiles() } }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 4.dp, vertical = 2.dp), containerColor = MaterialTheme.colorScheme.surfaceContainer ) } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/PasswordRequestDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Password import androidx.compose.material.icons.outlined.Shield import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField @Composable fun PasswordRequestDialog( isVisible: Boolean, onDismiss: () -> Unit, onFillPassword: (String) -> Unit ) { var password by remember(isVisible) { mutableStateOf("") } var hidePassword by remember(isVisible) { mutableStateOf(true) } EnhancedAlertDialog( visible = isVisible, onDismissRequest = {}, icon = { Icon( imageVector = Icons.Outlined.Shield, contentDescription = null ) }, title = { Text(stringResource(R.string.password)) }, text = { RoundedTextField( value = password, onValueChange = { password = it }, textStyle = LocalTextStyle.current.copy( textAlign = TextAlign.Center ), label = null, modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), singleLine = true, visualTransformation = if (hidePassword) { PasswordVisualTransformation() } else { VisualTransformation.None }, hint = { Text( text = stringResource(R.string.pdf_is_protected), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) }, startIcon = { Icon( imageVector = Icons.Filled.Password, contentDescription = null ) }, endIcon = { EnhancedIconButton( onClick = { hidePassword = !hidePassword } ) { Icon( imageVector = if (hidePassword) { Icons.Outlined.VisibilityOff } else { Icons.Outlined.Visibility }, contentDescription = null ) } } ) }, confirmButton = { EnhancedButton( enabled = password.isNotEmpty(), onClick = { onFillPassword(password) } ) { Text(stringResource(R.string.unlock)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.close)) } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/ResetDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DoneOutline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.utils.getString @Composable fun ResetDialog( visible: Boolean, onDismiss: () -> Unit, onReset: () -> Unit, title: String = stringResource(R.string.reset_image), text: String = stringResource(R.string.reset_image_sub), icon: ImageVector = Icons.Rounded.ImageReset ) { EnhancedAlertDialog( visible = visible, icon = { Icon( imageVector = icon, contentDescription = title ) }, title = { Text(title) }, text = { Text( text = text, modifier = Modifier.fillMaxWidth() ) }, onDismissRequest = onDismiss, confirmButton = { EnhancedButton( onClick = onDismiss ) { Text(stringResource(R.string.close)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onReset() onDismiss() AppToastHost.showToast( message = getString(R.string.values_reset), icon = Icons.Rounded.DoneOutline ) } ) { Text(stringResource(R.string.reset)) } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/dialogs/WantCancelLoadingDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.dialogs import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCircularProgressIndicator @Composable fun WantCancelLoadingDialog( visible: Boolean, onCancelLoading: () -> Unit, onDismissDialog: () -> Unit, modifier: Modifier = Modifier, isForSaving: Boolean = true, ) { EnhancedAlertDialog( visible = visible, onDismissRequest = onDismissDialog, confirmButton = { EnhancedButton( onClick = onDismissDialog ) { Text(stringResource(id = R.string.wait)) } }, title = { Text(stringResource(id = R.string.loading)) }, text = { Text( text = stringResource( if (isForSaving) R.string.saving_almost_complete else R.string.operation_almost_complete ) ) }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onCancelLoading ) { Text(stringResource(id = R.string.cancel)) } }, icon = { EnhancedCircularProgressIndicator( modifier = Modifier.size(24.dp), trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), strokeWidth = 3.dp ) }, modifier = modifier ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedAlertDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.sizeIn import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.semantics.paneTitle import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.alertDialogBorder import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable import com.t8rin.modalsheet.FullscreenPopup import kotlinx.coroutines.delay @Composable fun EnhancedAlertDialog( visible: Boolean, onDismissRequest: () -> Unit, confirmButton: @Composable () -> Unit, modifier: Modifier = Modifier, dismissButton: @Composable (() -> Unit)? = null, icon: @Composable (() -> Unit)? = null, title: @Composable (() -> Unit)? = null, text: @Composable (() -> Unit)? = null, placeAboveAll: Boolean = false, shape: Shape = AlertDialogDefaults.shape, containerColor: Color = AlertDialogDefaults.containerColor, iconContentColor: Color = AlertDialogDefaults.iconContentColor, titleContentColor: Color = AlertDialogDefaults.titleContentColor, textContentColor: Color = AlertDialogDefaults.textContentColor, tonalElevation: Dp = AlertDialogDefaults.TonalElevation ) { BasicEnhancedAlertDialog( visible = visible, onDismissRequest = onDismissRequest, placeAboveAll = placeAboveAll, content = { val isCenterAlignButtons = LocalSettingsState.current.isCenterAlignDialogButtons EnhancedAlertDialogContent( buttons = { FlowRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy( space = ButtonsHorizontalSpacing, alignment = if (dismissButton != null && isCenterAlignButtons) { Alignment.CenterHorizontally } else Alignment.End ), verticalArrangement = Arrangement.spacedBy( space = ButtonsVerticalSpacing, alignment = if (dismissButton != null && isCenterAlignButtons) { Alignment.CenterVertically } else Alignment.Bottom ), itemVerticalAlignment = Alignment.CenterVertically ) { dismissButton?.invoke() confirmButton() } }, icon = icon, title = title, text = text, shape = shape, containerColor = containerColor, tonalElevation = tonalElevation, // Note that a button content color is provided here from the dialog's token, but in // most cases, TextButtons should be used for dismiss and confirm buttons. // TextButtons will not consume this provided content color value, and will be used their // own defined or default colors. buttonContentColor = MaterialTheme.colorScheme.primary, iconContentColor = iconContentColor, titleContentColor = titleContentColor, textContentColor = textContentColor, modifier = modifier .alertDialogBorder() .sizeIn( minWidth = DialogMinWidth, maxWidth = DialogMaxWidth ) .then(Modifier.semantics { paneTitle = "Dialog" }) ) } ) } @Composable fun BasicEnhancedAlertDialog( visible: Boolean, onDismissRequest: (() -> Unit)?, modifier: Modifier = Modifier, placeAboveAll: Boolean = false, content: @Composable BoxScope.() -> Unit ) { var visibleAnimated by remember { mutableStateOf(false) } var scale by remember { mutableFloatStateOf(1f) } val animatedScale by animateFloatAsState(scale) LaunchedEffect(visible) { if (visible) { scale = 1f visibleAnimated = true } } if (visibleAnimated) { FullscreenPopupForPreview(placeAboveAll = placeAboveAll) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { var animateIn by rememberSaveable { mutableStateOf(false) } LaunchedEffect(Unit) { animateIn = true } AnimatedVisibility( visible = animateIn && visible, enter = fadeIn(), exit = fadeOut(), ) { val alpha = 0.5f * animatedScale Box( modifier = Modifier .tappable { onDismissRequest?.invoke() } .background(MaterialTheme.colorScheme.scrim.copy(alpha = alpha)) .fillMaxSize() ) } AnimatedVisibility( visible = animateIn && visible, enter = fadeIn(tween(300)) + scaleIn( initialScale = .8f, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMediumLow ) ), exit = fadeOut(tween(300)) + scaleOut( targetScale = .8f, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMediumLow ) ), modifier = Modifier.scale(animatedScale) ) { Box( modifier = modifier .safeDrawingPadding() .padding(horizontal = 48.dp, vertical = 24.dp), contentAlignment = Alignment.Center, content = content ) } } DisposableEffect(Unit) { onDispose { visibleAnimated = false } } if (onDismissRequest != null) { PredictiveBackObserver( onProgress = { progress -> scale = (1f - progress / 6f).coerceAtLeast(0.85f) }, onClean = { isCompleted -> if (isCompleted) { onDismissRequest() delay(400) } scale = 1f }, enabled = visible ) } } } } @Composable private fun EnhancedAlertDialogContent( buttons: @Composable () -> Unit, modifier: Modifier = Modifier, icon: (@Composable () -> Unit)?, title: (@Composable () -> Unit)?, text: @Composable (() -> Unit)?, shape: Shape, containerColor: Color, tonalElevation: Dp, buttonContentColor: Color, iconContentColor: Color, titleContentColor: Color, textContentColor: Color, ) { Surface( modifier = modifier, shape = shape, color = containerColor, tonalElevation = tonalElevation, ) { Column(modifier = Modifier.padding(DialogPadding)) { icon?.let { CompositionLocalProvider(LocalContentColor provides iconContentColor) { Box( Modifier .padding(IconPadding) .align(Alignment.CenterHorizontally) ) { IconShapeContainer( contentColor = iconContentColor, containerColor = MaterialTheme.colorScheme.surfaceContainerLow, content = { icon() } ) } } } title?.let { ProvideContentColorTextStyle( contentColor = titleContentColor, textStyle = MaterialTheme.typography.headlineSmall ) { Box( // Align the title to the center when an icon is present. Modifier .padding(TitlePadding) .align( if (icon == null) { Alignment.Start } else { Alignment.CenterHorizontally } ) ) { title() } } } text?.let { val textStyle = MaterialTheme.typography.bodyMedium ProvideContentColorTextStyle( contentColor = textContentColor, textStyle = textStyle ) { Box( Modifier .weight(weight = 1f, fill = false) .padding(TextPadding) .align(Alignment.Start) ) { text() } } } Box(modifier = Modifier.align(Alignment.End)) { val textStyle = MaterialTheme.typography.labelLarge ProvideContentColorTextStyle( contentColor = buttonContentColor, textStyle = textStyle, content = buttons ) } } } } @Composable fun ProvideContentColorTextStyle( contentColor: Color, textStyle: TextStyle, content: @Composable () -> Unit ) { val mergedStyle = LocalTextStyle.current.merge(textStyle) CompositionLocalProvider( LocalContentColor provides contentColor, LocalTextStyle provides mergedStyle, content = content ) } private val DialogMinWidth = 280.dp private val DialogMaxWidth = 480.dp private val ButtonsHorizontalSpacing = 8.dp private val ButtonsVerticalSpacing = 12.dp // Paddings for each of the dialog's parts. private val DialogPadding = PaddingValues(all = 24.dp) private val IconPadding = PaddingValues(bottom = 16.dp) private val TitlePadding = PaddingValues(bottom = 16.dp) private val TextPadding = PaddingValues(bottom = 24.dp) @Composable private fun FullscreenPopupForPreview( onDismiss: (() -> Unit)? = null, placeAboveAll: Boolean = false, content: @Composable () -> Unit ) { if (LocalInspectionMode.current) { Dialog( properties = DialogProperties(usePlatformDefaultWidth = false), onDismissRequest = { onDismiss?.invoke() } ) { content() } } else { FullscreenPopup( onDismiss = onDismiss, placeAboveAll = placeAboveAll, content = content ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedBadge.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.BadgeDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun EnhancedBadge( modifier: Modifier = Modifier, containerColor: Color = BadgeDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), shape: Shape = ShapeDefaults.circle, content: @Composable (RowScope.() -> Unit)? = null, ) { val size = if (content != null) 16.dp else 6.dp Row( modifier = modifier .defaultMinSize(minWidth = size, minHeight = size) .background(color = containerColor, shape = shape) .then( if (content != null) Modifier.padding(horizontal = 4.dp) else Modifier ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, ) { if (content != null) { val mergedStyle = LocalTextStyle.current.merge(MaterialTheme.typography.labelSmall) CompositionLocalProvider( LocalContentColor provides contentColor, LocalTextStyle provides mergedStyle, content = { content() } ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.DisabledAlpha import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.materialShadow import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest @Composable fun EnhancedButton( onClick: () -> Unit, modifier: Modifier = Modifier, onLongClick: (() -> Unit)? = null, enabled: Boolean = true, containerColor: Color = MaterialTheme.colorScheme.primary, contentColor: Color = contentColor(containerColor), borderColor: Color = MaterialTheme.colorScheme.outlineVariant(onTopOf = containerColor), shape: Shape = AutoCircleShape(), pressedShape: Shape = ButtonDefaults.pressedShape, contentPadding: PaddingValues = ButtonDefaults.ContentPadding, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, isShadowClip: Boolean = containerColor.alpha != 1f, content: @Composable RowScope.() -> Unit ) { val settingsState = LocalSettingsState.current val haptics = LocalHapticFeedback.current val focus = LocalFocusManager.current LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { Box { if (onLongClick != null) { val viewConfiguration = LocalViewConfiguration.current LaunchedEffect(interactionSource) { var isLongClick = false interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> { isLongClick = false delay(viewConfiguration.longPressTimeoutMillis) isLongClick = true onLongClick() focus.clearFocus() haptics.longPress() } is PressInteraction.Release -> { if (!isLongClick) { onClick() focus.clearFocus() haptics.press() } } is PressInteraction.Cancel -> { isLongClick = false } } } } } val animatedShape = shapeByInteraction( shape = shape, pressedShape = pressedShape, interactionSource = interactionSource ) OutlinedButton( onClick = { if (onLongClick == null) { onClick() focus.clearFocus() haptics.longPress() } }, modifier = modifier .materialShadow( shape = animatedShape, elevation = animateDpAsState( if (settingsState.borderWidth > 0.dp || !enabled) 0.dp else 0.5.dp ).value, enabled = LocalSettingsState.current.drawButtonShadows, isClipped = isShadowClip ), shape = animatedShape, colors = ButtonDefaults.buttonColors( contentColor = animateColorAsState( if (enabled) contentColor else MaterialTheme.colorScheme.onSurface.copy(DisabledAlpha) ).value, containerColor = animateColorAsState( if (enabled) containerColor else MaterialTheme.colorScheme.onSurface.copy(0.12f) ).value ), enabled = true, border = BorderStroke( width = settingsState.borderWidth, color = borderColor ), contentPadding = contentPadding, interactionSource = interactionSource, content = content ) if (!enabled) { Surface(color = Color.Transparent, modifier = Modifier.matchParentSize()) {} } } } } @Composable private fun contentColor( backgroundColor: Color ) = MaterialTheme.colorScheme.contentColorFor(backgroundColor).takeOrElse { if (backgroundColor == MaterialTheme.colorScheme.mixedContainer) MaterialTheme.colorScheme.onMixedContainer else LocalContentColor.current } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedButtonGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CornerSize import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonGroupDefaults import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MotionScheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.material3.ToggleButtonDefaults import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.DisabledAlpha import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable fun EnhancedButtonGroup( modifier: Modifier = defaultModifier, enabled: Boolean = true, items: List, selectedIndex: Int, title: String? = null, onIndexChange: (Int) -> Unit, inactiveButtonColor: Color = MaterialTheme.colorScheme.surface ) { EnhancedButtonGroup( enabled = enabled, items = items, selectedIndex = selectedIndex, onIndexChange = onIndexChange, modifier = modifier, title = { title?.let { Text( text = it, textAlign = TextAlign.Center, fontWeight = FontWeight.Medium, modifier = Modifier.padding(vertical = 8.dp) ) } }, inactiveButtonColor = inactiveButtonColor ) } @Composable fun EnhancedButtonGroup( modifier: Modifier = defaultModifier, enabled: Boolean, items: List, selectedIndex: Int, title: @Composable RowScope.() -> Unit = {}, onIndexChange: (Int) -> Unit, inactiveButtonColor: Color = MaterialTheme.colorScheme.surface ) { EnhancedButtonGroup( modifier = modifier, enabled = enabled, itemCount = items.size, selectedIndex = selectedIndex, itemContent = { AutoSizeText( text = items[it], style = LocalTextStyle.current.copy( fontSize = 13.sp ), maxLines = 1 ) }, onIndexChange = onIndexChange, title = title, inactiveButtonColor = inactiveButtonColor ) } @Composable fun EnhancedButtonGroup( modifier: Modifier = defaultModifier, enabled: Boolean = true, entries: List, value: T, itemContent: @Composable (item: T) -> Unit, title: String?, onValueChange: (T) -> Unit, inactiveButtonColor: Color = MaterialTheme.colorScheme.surface, activeButtonColor: Color = MaterialTheme.colorScheme.secondary, isScrollable: Boolean = true, contentPadding: PaddingValues = DefaultContentPadding, useClassFinding: Boolean = false ) { EnhancedButtonGroup( modifier = modifier, enabled = enabled, itemCount = entries.size, selectedIndex = if (useClassFinding) { entries.indexOfFirst { it::class.isInstance(value) } } else { entries.indexOf(value) }, itemContent = { itemContent(entries[it]) }, onIndexChange = { onValueChange( if (useClassFinding && value::class.isInstance(entries[it])) { value } else { entries[it] } ) }, title = { title?.let { Text( text = title, style = PreferenceItemDefaults.TitleFontStyleCentered, modifier = Modifier .weight(1f) .padding(8.dp) ) } }, inactiveButtonColor = inactiveButtonColor, activeButtonColor = activeButtonColor, isScrollable = isScrollable, contentPadding = contentPadding ) } @Composable fun EnhancedButtonGroup( modifier: Modifier = defaultModifier, enabled: Boolean = true, itemCount: Int, selectedIndex: Int, itemContent: @Composable (item: Int) -> Unit, title: @Composable RowScope.() -> Unit = {}, onIndexChange: (Int) -> Unit, inactiveButtonColor: Color = MaterialTheme.colorScheme.surface, activeButtonColor: Color = MaterialTheme.colorScheme.secondary, isScrollable: Boolean = true, contentPadding: PaddingValues = DefaultContentPadding ) { EnhancedButtonGroup( modifier = modifier, enabled = enabled, itemCount = itemCount, selectedIndices = setOf(selectedIndex), itemContent = itemContent, title = title, onIndexChange = onIndexChange, inactiveButtonColor = inactiveButtonColor, activeButtonColor = activeButtonColor, isScrollable = isScrollable, contentPadding = contentPadding ) } @Composable fun EnhancedButtonGroup( modifier: Modifier = defaultModifier, enabled: Boolean = true, entries: List, values: List, itemContent: @Composable (item: T) -> Unit, title: String?, onValueChange: (T) -> Unit, inactiveButtonColor: Color = MaterialTheme.colorScheme.surface, activeButtonColor: Color = MaterialTheme.colorScheme.secondary, isScrollable: Boolean = true, contentPadding: PaddingValues = DefaultContentPadding ) { val selectedIndices by remember(values, entries) { derivedStateOf { values.mapTo(mutableSetOf()) { entries.indexOf(it) } } } EnhancedButtonGroup( modifier = modifier, enabled = enabled, itemCount = entries.size, selectedIndices = selectedIndices, itemContent = { itemContent(entries[it]) }, onIndexChange = { onValueChange( entries[it] ) }, title = { title?.let { Text( text = title, style = PreferenceItemDefaults.TitleFontStyleCentered, modifier = Modifier .weight(1f) .padding(8.dp) ) } }, inactiveButtonColor = inactiveButtonColor, activeButtonColor = activeButtonColor, isScrollable = isScrollable, contentPadding = contentPadding ) } @Composable fun EnhancedButtonGroup( modifier: Modifier = defaultModifier, enabled: Boolean = true, itemCount: Int, selectedIndices: Set, itemContent: @Composable (item: Int) -> Unit, title: @Composable RowScope.() -> Unit = {}, onIndexChange: (Int) -> Unit, inactiveButtonColor: Color = MaterialTheme.colorScheme.surface, activeButtonColor: Color = MaterialTheme.colorScheme.secondary, isScrollable: Boolean = true, contentPadding: PaddingValues = DefaultContentPadding ) { val settingsState = LocalSettingsState.current val disabledColor = MaterialTheme.colorScheme.onSurface .copy(alpha = DisabledAlpha) .compositeOver(MaterialTheme.colorScheme.surface) ProvideTextStyle( value = LocalTextStyle.current.copy( color = if (!enabled) disabledColor else Color.Unspecified ) ) { Column( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, content = title ) val scrollState = rememberScrollState() val elevation by animateDpAsState( if (settingsState.borderWidth > 0.dp || !enabled) 0.dp else 0.5.dp ) LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { MaterialTheme( motionScheme = object : MotionScheme by MotionScheme.expressive() { override fun fastSpatialSpec(): FiniteAnimationSpec = tween(400) } ) { Row( modifier = Modifier .height(IntrinsicSize.Max) .then( if (isScrollable) { Modifier .fadingEdges(scrollState) .enhancedHorizontalScroll(scrollState) } else Modifier.fillMaxWidth() ) .padding(contentPadding), horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween), ) { repeat(itemCount) { index -> val activeContainerColor = if (enabled) { activeButtonColor } else { MaterialTheme.colorScheme.surfaceContainer } val selected = index in selectedIndices val disableSmoothness = !selected && index == 0 || index == itemCount - 1 val settingsState = LocalSettingsState.current LocalSettingsState.ProvidesValue( if (disableSmoothness && settingsState.shapesType is ShapeType.Smooth) { settingsState.copy( shapesType = ShapeType.Rounded() ) } else { settingsState } ) { EnhancedToggleButton( enabled = enabled, onCheckedChange = { onIndexChange(index) }, border = BorderStroke( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant( onTopOf = if (selected) activeContainerColor else inactiveButtonColor ) ), colors = ToggleButtonDefaults.toggleButtonColors( containerColor = inactiveButtonColor, contentColor = contentColorFor(inactiveButtonColor), checkedContainerColor = activeContainerColor, checkedContentColor = contentColorFor(activeContainerColor) ), checked = selected, shapes = when (index) { 0 -> ButtonGroupDefaults.connectedLeadingButtonShapes( shape = AutoCornersShape( topStart = CornerFull, bottomStart = CornerFull, topEnd = CornerValueSmall, bottomEnd = CornerValueSmall, ), pressedShape = ButtonDefaults.pressedShape, checkedShape = AutoCircleShape() ) itemCount - 1 -> ButtonGroupDefaults.connectedTrailingButtonShapes( shape = AutoCornersShape( topEnd = CornerFull, bottomEnd = CornerFull, topStart = CornerValueSmall, bottomStart = CornerValueSmall, ), pressedShape = ButtonDefaults.pressedShape, checkedShape = AutoCircleShape() ) else -> ButtonGroupDefaults.connectedMiddleButtonShapes( shape = ShapeDefaults.mini, pressedShape = ButtonDefaults.pressedShape, checkedShape = AutoCircleShape() ) }, elevation = elevation, modifier = Modifier.then( if (isScrollable) Modifier else Modifier.weight(1f) ) ) { if (!isScrollable) { Row( modifier = Modifier.marquee() ) { itemContent(index) } } else { itemContent(index) } } } } } } } } } } private val defaultModifier = Modifier .fillMaxWidth() .padding(8.dp) private val DefaultContentPadding = PaddingValues( start = 6.dp, end = 6.dp, bottom = 6.dp, top = 8.dp ) private val CornerFull: CornerSize = CornerSize(50) private val CornerValueSmall = CornerSize(8.0.dp) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCheckbox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxColors import androidx.compose.material3.CheckboxDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import kotlin.math.floor @Composable fun EnhancedCheckbox( checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, enabled: Boolean = true, colors: CheckboxColors = CheckboxDefaults.colors(), interactionSource: MutableInteractionSource? = null ) { val haptics = LocalHapticFeedback.current val strokeWidthPx = with(LocalDensity.current) { floor(CheckboxDefaults.StrokeWidth.toPx()) } val stroke = remember(strokeWidthPx) { Stroke( width = strokeWidthPx, join = StrokeJoin.Round, cap = StrokeCap.Round ) } Checkbox( checked = checked, onCheckedChange = if (onCheckedChange != null) { { haptics.longPress() onCheckedChange(it) } } else null, outlineStroke = stroke, checkmarkStroke = stroke, modifier = modifier, enabled = enabled, colors = colors, interactionSource = interactionSource ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedChip.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction @Composable fun EnhancedChip( selected: Boolean, onClick: (() -> Unit)?, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(6.dp), selectedColor: Color, selectedContentColor: Color = MaterialTheme.colorScheme.contentColorFor(selectedColor), unselectedColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, unselectedContentColor: Color = MaterialTheme.colorScheme.onSurface, shape: Shape = ShapeDefaults.small, pressedShape: Shape = ShapeDefaults.extraSmall, interactionSource: MutableInteractionSource? = null, defaultMinSize: Dp = 36.dp, label: @Composable () -> Unit ) { val color by animateColorAsState( if (selected) selectedColor else unselectedColor ) val contentColor by animateColorAsState( if (selected) selectedContentColor else unselectedContentColor ) val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } val resultShape = shapeByInteraction( shape = shape, pressedShape = pressedShape, interactionSource = realInteractionSource ) CompositionLocalProvider( LocalTextStyle provides MaterialTheme.typography.labelLarge.copy( fontWeight = FontWeight.SemiBold, color = contentColor ), LocalContentColor provides contentColor, LocalContainerShape provides null ) { Box( modifier = modifier .defaultMinSize(defaultMinSize, defaultMinSize) .container( color = color, resultPadding = 0.dp, borderColor = if (!selected) MaterialTheme.colorScheme.outlineVariant() else selectedColor .copy(alpha = 0.9f) .compositeOver(Color.Black), shape = resultShape, autoShadowElevation = 0.5.dp ) .then( onClick?.let { Modifier.hapticsClickable( indication = LocalIndication.current, interactionSource = realInteractionSource, onClick = onClick ) } ?: Modifier ), contentAlignment = Alignment.Center ) { Box( modifier = Modifier.padding(contentPadding), contentAlignment = Alignment.Center ) { label() } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedCircularProgressIndicator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularWavyProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.WavyProgressIndicatorDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.icons.CancelSmall @Composable fun EnhancedCircularProgressIndicator( modifier: Modifier = Modifier, color: Color = ProgressIndicatorDefaults.circularColor, strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth, trackColor: Color = ProgressIndicatorDefaults.circularIndeterminateTrackColor, strokeCap: StrokeCap = StrokeCap.Round, gapSize: Dp = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize, type: EnhancedCircularProgressIndicatorType = EnhancedCircularProgressIndicatorType.Wavy() ) { when (type) { EnhancedCircularProgressIndicatorType.Normal -> { CircularProgressIndicator( modifier = modifier.background(Color.Red), color = color, strokeWidth = strokeWidth, trackColor = trackColor, strokeCap = strokeCap, gapSize = gapSize ) } is EnhancedCircularProgressIndicatorType.Wavy -> { CircularWavyProgressIndicator( modifier = modifier, color = color, trackColor = trackColor, gapSize = gapSize, trackStroke = Stroke( width = with(LocalDensity.current) { strokeWidth.toPx() }, cap = strokeCap ), stroke = Stroke( width = with(LocalDensity.current) { strokeWidth.toPx() }, cap = strokeCap ), amplitude = type.amplitude(0.5f), wavelength = type.wavelength, waveSpeed = type.waveSpeed ) } } } @Composable fun EnhancedCircularProgressIndicator( progress: () -> Float, modifier: Modifier = Modifier, color: Color = ProgressIndicatorDefaults.circularColor, strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth, trackColor: Color = ProgressIndicatorDefaults.circularIndeterminateTrackColor, strokeCap: StrokeCap = StrokeCap.Round, gapSize: Dp = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize, type: EnhancedCircularProgressIndicatorType = EnhancedCircularProgressIndicatorType.Wavy() ) { when (type) { EnhancedCircularProgressIndicatorType.Normal -> { CircularProgressIndicator( progress = progress, modifier = modifier, color = color, strokeWidth = strokeWidth, trackColor = trackColor, strokeCap = strokeCap, gapSize = gapSize ) } is EnhancedCircularProgressIndicatorType.Wavy -> { CircularWavyProgressIndicator( progress = progress, modifier = modifier, color = color, trackColor = trackColor, gapSize = gapSize, trackStroke = Stroke( width = with(LocalDensity.current) { strokeWidth.toPx() }, cap = strokeCap ), stroke = Stroke( width = with(LocalDensity.current) { strokeWidth.toPx() }, cap = strokeCap ), amplitude = type.amplitude, wavelength = type.wavelength, waveSpeed = type.waveSpeed ) } } } @Composable fun EnhancedAutoCircularProgressIndicator( progress: () -> Float, modifier: Modifier = Modifier, color: Color = ProgressIndicatorDefaults.circularColor, strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth, trackColor: Color = ProgressIndicatorDefaults.circularIndeterminateTrackColor, strokeCap: StrokeCap = StrokeCap.Round, gapSize: Dp = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize, type: EnhancedCircularProgressIndicatorType = EnhancedCircularProgressIndicatorType.Wavy() ) { if (progress() > 0f) { EnhancedCircularProgressIndicator( progress = progress, modifier = modifier, color = color, strokeWidth = strokeWidth, trackColor = trackColor, strokeCap = strokeCap, gapSize = gapSize, type = type ) } else { EnhancedCircularProgressIndicator( modifier = modifier, color = color, strokeWidth = strokeWidth, trackColor = trackColor, strokeCap = strokeCap, gapSize = gapSize, type = type ) } } @Composable fun EnhancedCancellableCircularProgressIndicator( progress: () -> Float, modifier: Modifier = Modifier, color: Color = ProgressIndicatorDefaults.circularColor, strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth, trackColor: Color = ProgressIndicatorDefaults.circularIndeterminateTrackColor, strokeCap: StrokeCap = StrokeCap.Round, cancelIconColor: Color = MaterialTheme.colorScheme.secondary.copy(0.7f), onCancel: () -> Unit, gapSize: Dp = ProgressIndicatorDefaults.CircularIndicatorTrackGapSize, type: EnhancedCircularProgressIndicatorType = EnhancedCircularProgressIndicatorType.Wavy() ) { Box( modifier = modifier .pointerInput(onCancel) { detectTapGestures { onCancel() } }, contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Outlined.CancelSmall, contentDescription = null, tint = cancelIconColor, modifier = Modifier.size(18.dp) ) EnhancedAutoCircularProgressIndicator( progress = progress, modifier = Modifier.matchParentSize(), color = color, strokeWidth = strokeWidth, trackColor = trackColor, strokeCap = strokeCap, gapSize = gapSize, type = type ) } } sealed class EnhancedCircularProgressIndicatorType { data object Normal : EnhancedCircularProgressIndicatorType() data class Wavy( val amplitude: (progress: Float) -> Float = { progress -> if (progress <= 0.1f || progress >= 0.95f) { 0f } else { 1f } }, val wavelength: Dp = WavyProgressIndicatorDefaults.CircularWavelength, val waveSpeed: Dp = wavelength, ) : EnhancedCircularProgressIndicatorType() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDatePickerDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Keyboard import androidx.compose.material.icons.outlined.Schedule import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerColors import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.DatePickerFormatter import androidx.compose.material3.DatePickerState import androidx.compose.material3.DateRangePicker import androidx.compose.material3.DateRangePickerDefaults import androidx.compose.material3.DateRangePickerState import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TimeInput import androidx.compose.material3.TimePicker import androidx.compose.material3.TimePickerColors import androidx.compose.material3.TimePickerDefaults import androidx.compose.material3.TimePickerDialogDefaults import androidx.compose.material3.TimePickerDisplayMode import androidx.compose.material3.TimePickerLayoutType import androidx.compose.material3.TimePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.layout.layoutId import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastFirst import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.alertDialogBorder import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import kotlin.math.truncate @Composable fun EnhancedDatePickerDialog( visible: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, placeAboveAll: Boolean = false, state: DatePickerState, onDatePicked: (Long) -> Unit, colors: DatePickerColors = DatePickerDefaults.colors( containerColor = AlertDialogDefaults.containerColor ), dateFormatter: DatePickerFormatter = remember { DatePickerDefaults.dateFormatter() }, title: (@Composable () -> Unit)? = { DatePickerDefaults.DatePickerTitle( displayMode = state.displayMode, modifier = Modifier.padding(DatePickerTitlePadding), contentColor = colors.titleContentColor, ) }, headline: (@Composable () -> Unit)? = { DatePickerDefaults.DatePickerHeadline( selectedDateMillis = state.selectedDateMillis, displayMode = state.displayMode, dateFormatter = dateFormatter, modifier = Modifier.padding(DatePickerHeadlinePadding), contentColor = colors.headlineContentColor, ) }, showModeToggle: Boolean = true, focusRequester: FocusRequester? = remember { FocusRequester() }, shape: Shape = AlertDialogDefaults.shape, tonalElevation: Dp = AlertDialogDefaults.TonalElevation, ) { EnhancedDatePickerDialogContainer( visible = visible, onDismissRequest = onDismissRequest, modifier = modifier, placeAboveAll = placeAboveAll, shape = shape, tonalElevation = tonalElevation, containerColor = colors.containerColor, onConfirmClick = { state.selectedDateMillis?.let { onDatePicked(it) onDismissRequest() } }, isConfirmEnabled = state.selectedDateMillis != null ) { val scrollState = rememberScrollState() DatePicker( modifier = Modifier .fadingEdges( scrollableState = scrollState, isVertical = true ) .enhancedVerticalScroll(scrollState), state = state, dateFormatter = dateFormatter, colors = colors, title = title, headline = headline, showModeToggle = showModeToggle, focusRequester = focusRequester ) } } @Composable fun EnhancedDateRangePickerDialog( visible: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, placeAboveAll: Boolean = false, state: DateRangePickerState, onDatePicked: (start: Long, end: Long) -> Unit, colors: DatePickerColors = DatePickerDefaults.colors( containerColor = AlertDialogDefaults.containerColor ), dateFormatter: DatePickerFormatter = remember { DatePickerDefaults.dateFormatter(selectedDateSkeleton = "dd.MM.yy") }, title: (@Composable () -> Unit)? = { DateRangePickerDefaults.DateRangePickerTitle( displayMode = state.displayMode, modifier = Modifier.padding(DatePickerTitlePadding), contentColor = colors.titleContentColor, ) }, headline: (@Composable () -> Unit)? = { DateRangePickerDefaults.DateRangePickerHeadline( selectedStartDateMillis = state.selectedStartDateMillis, selectedEndDateMillis = state.selectedEndDateMillis, displayMode = state.displayMode, dateFormatter = dateFormatter, modifier = Modifier.padding(DatePickerHeadlinePadding), contentColor = colors.headlineContentColor, ) }, showModeToggle: Boolean = true, focusRequester: FocusRequester? = remember { FocusRequester() }, shape: Shape = AlertDialogDefaults.shape, tonalElevation: Dp = AlertDialogDefaults.TonalElevation, ) { EnhancedDatePickerDialogContainer( visible = visible, onDismissRequest = onDismissRequest, modifier = modifier, placeAboveAll = placeAboveAll, shape = shape, tonalElevation = tonalElevation, containerColor = colors.containerColor, onConfirmClick = { val start = state.selectedStartDateMillis val end = state.selectedEndDateMillis if (start != null && end != null) { onDatePicked(start, end) onDismissRequest() } }, isConfirmEnabled = state.selectedStartDateMillis != null && state.selectedEndDateMillis != null ) { DateRangePicker( modifier = Modifier .fadingEdges( scrollableState = null, isVertical = true, length = 8.dp ), state = state, dateFormatter = dateFormatter, colors = colors, title = title, headline = headline, showModeToggle = showModeToggle, focusRequester = focusRequester ) } } @Composable fun EnhancedTimePickerDialog( visible: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, placeAboveAll: Boolean = false, state: TimePickerState, onTimePicked: (hour: Int, minute: Int) -> Unit, colors: TimePickerColors = TimePickerDefaults.colors( containerColor = AlertDialogDefaults.containerColor ), layoutType: TimePickerLayoutType = TimePickerDefaults.layoutType(), shape: Shape = AlertDialogDefaults.shape, tonalElevation: Dp = AlertDialogDefaults.TonalElevation, ) { var displayMode by remember { mutableStateOf(TimePickerDisplayMode.Picker) } EnhancedDatePickerDialogContainer( visible = visible, onDismissRequest = onDismissRequest, modifier = modifier, placeAboveAll = placeAboveAll, shape = shape, tonalElevation = tonalElevation, containerColor = colors.containerColor, onConfirmClick = {}, showButtons = false, isConfirmEnabled = false ) { TimePickerCustomLayout( title = { TimePickerDialogDefaults.Title(displayMode) }, actions = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { EnhancedIconButton( modifier = modifier, onClick = { displayMode = if (displayMode == TimePickerDisplayMode.Picker) { TimePickerDisplayMode.Input } else { TimePickerDisplayMode.Picker } } ) { val icon = if (displayMode == TimePickerDisplayMode.Picker) { Icons.Outlined.Keyboard } else { Icons.Outlined.Schedule } Icon( imageVector = icon, contentDescription = icon.name ) } Spacer(modifier = Modifier.weight(1f)) EnhancedButton( onClick = { onTimePicked(state.hour, state.minute) onDismissRequest() } ) { Text(stringResource(R.string.ok)) } } }, content = { AnimatedContent(displayMode) { mode -> if (mode == TimePickerDisplayMode.Picker) { TimePicker( state = state, colors = colors, layoutType = layoutType ) } else { TimeInput( state = state, colors = colors ) } } } ) } } @Composable private fun EnhancedDatePickerDialogContainer( visible: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, placeAboveAll: Boolean = false, shape: Shape, tonalElevation: Dp, containerColor: Color, onConfirmClick: () -> Unit, isConfirmEnabled: Boolean, showButtons: Boolean = true, content: @Composable ColumnScope.() -> Unit ) { BasicEnhancedAlertDialog( visible = visible, onDismissRequest = onDismissRequest, modifier = modifier, placeAboveAll = placeAboveAll, ) { Surface( modifier = Modifier .alertDialogBorder() .then( if (showButtons) Modifier.requiredWidth(ContainerWidth) else Modifier ) .heightIn(max = ContainerHeight), shape = shape, color = containerColor, tonalElevation = tonalElevation, ) { Column(verticalArrangement = Arrangement.SpaceBetween) { DatePickerContainer(content = content) if (showButtons) { val isCenterAlignButtons = LocalSettingsState.current.isCenterAlignDialogButtons Row( modifier = Modifier .align(Alignment.End) .padding(DialogButtonsPadding) ) { FlowRow( modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy( space = ButtonsHorizontalSpacing, alignment = if (isCenterAlignButtons) { Alignment.CenterHorizontally } else Alignment.End ), verticalArrangement = Arrangement.spacedBy( space = ButtonsVerticalSpacing, alignment = if (isCenterAlignButtons) { Alignment.CenterVertically } else Alignment.Bottom ), itemVerticalAlignment = Alignment.CenterVertically ) { EnhancedButton( onClick = onDismissRequest, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } EnhancedButton( onClick = onConfirmClick, enabled = isConfirmEnabled ) { Text(stringResource(R.string.ok)) } } } } } } } } @Composable private fun TimePickerCustomLayout( title: @Composable () -> Unit, actions: @Composable () -> Unit, content: @Composable ColumnScope.() -> Unit, ) { val content = @Composable { Box(modifier = Modifier.layoutId("title")) { title() } Box(modifier = Modifier.layoutId("actions")) { actions() } Column(modifier = Modifier.layoutId("timePickerContent"), content = content) } val measurePolicy = MeasurePolicy { measurables, constraints -> val titleMeasurable = measurables.fastFirst { it.layoutId == "title" } val contentMeasurable = measurables.fastFirst { it.layoutId == "timePickerContent" } val actionsMeasurable = measurables.fastFirst { it.layoutId == "actions" } val contentPadding = 24.dp.roundToPx() val landMaxDialogHeight = 384.dp.roundToPx() val landTitleTopPadding = 24.dp.roundToPx() val landContentTopPadding = 16.dp.roundToPx() val landContentActionsPadding = 4.dp.roundToPx() val landActionsBottomPadding = 8.dp.roundToPx() val portTitleTopPadding = 24.dp.roundToPx() val portActionsBottomPadding = 24.dp.roundToPx() val contentPlaceable = contentMeasurable.measure(constraints.copy(minHeight = 0)) // Input mode will be smaller than the smallest TimePickerContent (currently 200.dp) // But will always use portrait layout for correctness. val isLandscape = contentPlaceable.width > contentPlaceable.height && contentPlaceable.height >= truncate(ClockDialMinContainerSize.toPx()) val dialogWidth = if (isLandscape) { contentPlaceable.width + contentPadding * 2 } else { contentPlaceable.width + contentPadding * 2 } val actionsPlaceable = actionsMeasurable.measure( constraints.copy(minWidth = 0, minHeight = 0, maxWidth = contentPlaceable.width) ) val titlePlaceable = titleMeasurable.measure( constraints.copy(minWidth = 0, minHeight = 0, maxWidth = contentPlaceable.width) ) val layoutHeight = if (isLandscape) { val contentTotalHeight = contentPlaceable.height + actionsPlaceable.height + landActionsBottomPadding + landContentTopPadding + landContentActionsPadding if (constraints.hasBoundedHeight) constraints.maxHeight else contentTotalHeight } else { portTitleTopPadding + titlePlaceable.height + contentPlaceable.height + actionsPlaceable.height + portActionsBottomPadding } layout(width = dialogWidth, height = layoutHeight) { if (isLandscape) { val contentHeight = landContentTopPadding + contentPlaceable.height + landContentActionsPadding + actionsPlaceable.height + landActionsBottomPadding val remainingSpace = layoutHeight - contentHeight val adjustedActionsBottomPadding = if (layoutHeight >= landMaxDialogHeight) { 16.dp.roundToPx() } else { 0 } titlePlaceable.place(x = landTitleTopPadding, y = landTitleTopPadding) val timePickerContentX = contentPadding val timePickerContentY = landContentTopPadding + remainingSpace / 2 contentPlaceable.place(x = timePickerContentX, y = timePickerContentY) val actionsY = timePickerContentY + contentPlaceable.height + landContentActionsPadding - adjustedActionsBottomPadding + remainingSpace / 2 actionsPlaceable.place(x = timePickerContentX, y = actionsY) } else { val titleX = landTitleTopPadding titlePlaceable.place(x = titleX, y = portTitleTopPadding) val contentX = (dialogWidth - contentPlaceable.width) / 2 val contentY = portTitleTopPadding + titlePlaceable.height contentPlaceable.place(x = contentX, y = contentY) val actionsX = (dialogWidth - actionsPlaceable.width) / 2 val actionsY = contentY + contentPlaceable.height actionsPlaceable.place(x = actionsX, y = actionsY) } } } Layout(content = content, measurePolicy = measurePolicy) } @Composable private fun ColumnScope.DatePickerContainer( content: @Composable ColumnScope.() -> Unit ) { // Wrap the content with a Box and Modifier.weight(1f) to ensure that any "confirm" // and "dismiss" buttons are not pushed out of view when running on small screens, // or when nesting a DateRangePicker. // Fill is false to support collapsing the dialog's height when switching to input // mode. Box( modifier = Modifier.weight(1f, fill = false) ) { this@DatePickerContainer.content() } } private val DialogButtonsPadding = PaddingValues(24.dp) private val ButtonsHorizontalSpacing = 8.dp private val ButtonsVerticalSpacing = 12.dp private val DatePickerTitlePadding = PaddingValues(start = 24.dp, end = 12.dp, top = 16.dp) private val DatePickerHeadlinePadding = PaddingValues(start = 24.dp, end = 12.dp, bottom = 12.dp) private val ContainerWidth = 360.dp private val ContainerHeight = 568.dp private val TimePickerMaxHeight = 384.dp private val TimePickerMidHeight = 330.dp private val ClockDialMidContainerSize = 238.dp internal val ClockDialMinContainerSize = 200.dp ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedDropdownMenu.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.DropdownMenu import androidx.compose.material3.LocalAbsoluteTonalElevation import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant @Composable fun EnhancedDropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, shape: Shape = MaterialTheme.shapes.medium, containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHighest, offset: DpOffset = DpOffset(0.dp, 0.dp), scrollState: ScrollState = rememberScrollState(), properties: PopupProperties = PopupProperties(focusable = true), enableAutoShadows: Boolean = true, content: @Composable ColumnScope.() -> Unit ) { val settings = LocalSettingsState.current CompositionLocalProvider( LocalAbsoluteTonalElevation provides (-3).dp ) { DropdownMenu( expanded = expanded, onDismissRequest = onDismissRequest, modifier = modifier, offset = offset, shape = shape, scrollState = scrollState, properties = properties, content = content, containerColor = containerColor, tonalElevation = 0.dp, shadowElevation = if (settings.drawContainerShadows && enableAutoShadows) 1.dp else 0.dp, border = if (settings.borderWidth > 0.dp) { BorderStroke( width = settings.borderWidth, color = MaterialTheme.colorScheme.outlineVariant() ) } else null ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFlingBehavior.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.OverscrollEffect import androidx.compose.foundation.ScrollState import androidx.compose.foundation.gestures.FlingBehavior import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.composed import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import io.iamjosephmj.flinger.adaptive.AdaptiveMode import io.iamjosephmj.flinger.behaviours.FlingPresets @Composable fun enhancedFlingBehavior( flingType: FlingType = LocalSettingsState.current.flingType ): FlingBehavior = when (flingType) { FlingType.DEFAULT -> FlingPresets.androidNative() FlingType.SMOOTH -> FlingPresets.smooth() FlingType.IOS_STYLE -> FlingPresets.iOSStyle() FlingType.SMOOTH_CURVE -> FlingPresets.smoothCurve() FlingType.QUICK_STOP -> FlingPresets.quickStop() FlingType.BOUNCY -> FlingPresets.bouncy() FlingType.FLOATY -> FlingPresets.floaty() FlingType.SNAPPY -> FlingPresets.snappy() FlingType.ULTRA_SMOOTH -> FlingPresets.ultraSmooth() FlingType.ADAPTIVE -> FlingPresets.adaptive(AdaptiveMode.Precision) FlingType.ACCESSIBILITY_AWARE -> FlingPresets.accessibilityAware() FlingType.REDUCED_MOTION -> FlingPresets.reducedMotion() } fun Modifier.enhancedVerticalScroll( state: ScrollState, enabled: Boolean = true, flingBehavior: FlingBehavior? = null, reverseScrolling: Boolean = false, ) = this.composed { Modifier.verticalScroll( state = state, enabled = enabled, flingBehavior = flingBehavior ?: enhancedFlingBehavior(), reverseScrolling = reverseScrolling, ) } fun Modifier.enhancedVerticalScroll( state: ScrollState, overscrollEffect: OverscrollEffect?, enabled: Boolean = true, flingBehavior: FlingBehavior? = null, reverseScrolling: Boolean = false, ) = this.composed { Modifier.verticalScroll( state = state, enabled = enabled, flingBehavior = flingBehavior ?: enhancedFlingBehavior(), reverseScrolling = reverseScrolling, overscrollEffect = overscrollEffect ) } fun Modifier.enhancedHorizontalScroll( state: ScrollState, enabled: Boolean = true, flingBehavior: FlingBehavior? = null, reverseScrolling: Boolean = false, ) = this.composed { Modifier.horizontalScroll( state = state, enabled = enabled, flingBehavior = flingBehavior ?: enhancedFlingBehavior(), reverseScrolling = reverseScrolling, ) } fun Modifier.enhancedHorizontalScroll( state: ScrollState, overscrollEffect: OverscrollEffect?, enabled: Boolean = true, flingBehavior: FlingBehavior? = null, reverseScrolling: Boolean = false, ) = this.composed { Modifier.horizontalScroll( state = state, enabled = enabled, flingBehavior = flingBehavior ?: enhancedFlingBehavior(), reverseScrolling = reverseScrolling, overscrollEffect = overscrollEffect ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedFloatingActionButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.sizeIn import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.containerFabBorder import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest @Composable fun EnhancedFloatingActionButton( onClick: (() -> Unit)?, modifier: Modifier = Modifier, onLongClick: (() -> Unit)? = null, type: EnhancedFloatingActionButtonType = LocalFABType.current, containerColor: Color = MaterialTheme.colorScheme.primaryContainer, contentColor: Color = contentColor(containerColor), autoElevation: Dp = 1.5.dp, interactionSource: MutableInteractionSource? = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit ) { val settingsState = LocalSettingsState.current val width by animateDpAsState(type.width) val height by animateDpAsState(type.height) val haptics = LocalHapticFeedback.current val focus = LocalFocusManager.current val shape = shapeByInteraction( shape = type.shape(), pressedShape = AutoCornersShape(max(width, height) / 8), interactionSource = interactionSource ) val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { if (onLongClick != null) { val viewConfiguration = LocalViewConfiguration.current LaunchedEffect(realInteractionSource) { var isLongClick = false realInteractionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> { isLongClick = false delay(viewConfiguration.longPressTimeoutMillis) isLongClick = true onLongClick() focus.clearFocus() haptics.longPress() } is PressInteraction.Release -> { if (!isLongClick && onClick != null) { onClick() focus.clearFocus() haptics.press() } } is PressInteraction.Cancel -> { isLongClick = false } } } } } FloatingActionButton( onClick = { if (onLongClick == null && onClick != null) { onClick() focus.clearFocus() haptics.longPress() } }, modifier = modifier .sizeIn(minWidth = width, minHeight = height) .containerFabBorder( shape = shape, autoElevation = animateDpAsState( if (settingsState.drawFabShadows) autoElevation else 0.dp ).value ), elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(), contentColor = animateColorAsState(contentColor).value, shape = shape, containerColor = animateColorAsState(containerColor).value, interactionSource = realInteractionSource, content = { Row( verticalAlignment = Alignment.CenterVertically, content = content ) } ) } } @Composable private fun contentColor( backgroundColor: Color ) = MaterialTheme.colorScheme.contentColorFor(backgroundColor).takeOrElse { if (backgroundColor == MaterialTheme.colorScheme.mixedContainer) MaterialTheme.colorScheme.onMixedContainer else LocalContentColor.current } sealed class EnhancedFloatingActionButtonType( val width: Dp, val height: Dp, val shape: @Composable () -> Shape ) { constructor( size: Dp, shape: @Composable () -> Shape ) : this( width = size, height = size, shape = shape ) data object Small : EnhancedFloatingActionButtonType( size = 40.dp, shape = { ShapeDefaults.small } ) data object Primary : EnhancedFloatingActionButtonType( size = 56.dp, shape = { ShapeDefaults.default } ) data object SecondaryHorizontal : EnhancedFloatingActionButtonType( width = 42.dp, height = 56.dp, shape = { ShapeDefaults.circle } ) data object SecondaryVertical : EnhancedFloatingActionButtonType( width = 56.dp, height = 42.dp, shape = { ShapeDefaults.circle } ) data object Large : EnhancedFloatingActionButtonType( size = 96.dp, shape = { ShapeDefaults.extremeLarge } ) class Custom( size: Dp, shape: Shape ) : EnhancedFloatingActionButtonType( size = size, shape = { shape } ) } val LocalFABType = compositionLocalOf { EnhancedFloatingActionButtonType.Primary } @Composable fun ProvideFABType( type: EnhancedFloatingActionButtonType, content: @Composable () -> Unit ) { LocalFABType.ProvidesValue( value = type, content = content ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedHapticFeedback.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("DEPRECATION") @file:SuppressLint("UnnecessaryComposedModifier") package com.t8rin.imagetoolbox.core.ui.widget.enhanced import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.view.HapticFeedbackConstants import android.view.View import android.view.accessibility.AccessibilityManager import androidx.compose.foundation.Indication import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.hapticfeedback.HapticFeedback import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalView import androidx.compose.ui.semantics.Role import androidx.core.content.getSystemService import com.t8rin.logger.makeLog private fun View.vibrate() = reallyPerformHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) private fun View.vibrateStrong() = reallyPerformHapticFeedback(HapticFeedbackConstants.LONG_PRESS) private fun View.reallyPerformHapticFeedback(feedbackConstant: Int) { runCatching { if (context.isTouchExplorationEnabled()) return isHapticFeedbackEnabled = true performHapticFeedback(feedbackConstant, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) }.onFailure { it.makeLog("reallyPerformHapticFeedback feedbackConstant = $feedbackConstant") } } private fun Context.isTouchExplorationEnabled(): Boolean = getSystemService()?.isTouchExplorationEnabled == true @SuppressLint("InlinedApi") internal data class EnhancedHapticFeedback( val hapticsStrength: Int, val view: View ) : HapticFeedback { override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) { when (hapticFeedbackType) { HapticFeedbackType.LongPress -> { when (hapticsStrength) { 1 -> view.vibrate() 2 -> view.vibrateStrong() } } HapticFeedbackType.TextHandleMove -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { view.reallyPerformHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE) } else view.reallyPerformHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) } HapticFeedbackType.Confirm -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.CONFIRM) HapticFeedbackType.ContextClick -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) HapticFeedbackType.GestureEnd -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.GESTURE_END) HapticFeedbackType.GestureThresholdActivate -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE) HapticFeedbackType.Reject -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.REJECT) HapticFeedbackType.SegmentFrequentTick -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK) HapticFeedbackType.SegmentTick -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.SEGMENT_TICK) HapticFeedbackType.ToggleOff -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.TOGGLE_OFF) HapticFeedbackType.ToggleOn -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.TOGGLE_ON) HapticFeedbackType.VirtualKey -> view.reallyPerformHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) } } } internal data object EmptyHaptics : HapticFeedback { override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) = Unit } fun HapticFeedback.press() = performHapticFeedback(HapticFeedbackType.TextHandleMove) fun HapticFeedback.longPress() = performHapticFeedback(HapticFeedbackType.LongPress) @Composable fun rememberEnhancedHapticFeedback(hapticsStrength: Int): HapticFeedback { val view = LocalView.current val haptics by remember(hapticsStrength) { derivedStateOf { if (hapticsStrength == 0) EmptyHaptics else EnhancedHapticFeedback( hapticsStrength = hapticsStrength, view = view ) } } return haptics } fun Modifier.hapticsClickable( interactionSource: MutableInteractionSource?, indication: Indication?, enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, enableHaptics: Boolean = true, onClick: () -> Unit ): Modifier = this.composed { val haptics = LocalHapticFeedback.current Modifier.clickable( interactionSource = interactionSource, indication = indication, enabled = enabled, onClickLabel = onClickLabel, role = role, onClick = { if (enableHaptics) haptics.longPress() onClick() } ) } fun Modifier.hapticsClickable( enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, onClick: () -> Unit ): Modifier = this.composed { hapticsClickable( interactionSource = null, indication = LocalIndication.current, enabled = enabled, onClickLabel = onClickLabel, role = role, onClick = onClick ) } fun Modifier.hapticsCombinedClickable( interactionSource: MutableInteractionSource?, indication: Indication?, enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, onLongClickLabel: String? = null, onLongClick: (() -> Unit)? = null, onDoubleClick: (() -> Unit)? = null, onClick: () -> Unit ) = this.composed { val haptics = LocalHapticFeedback.current Modifier.combinedClickable( interactionSource = interactionSource, indication = indication, enabled = enabled, onClickLabel = onClickLabel, role = role, onLongClickLabel = onLongClickLabel, onLongClick = if (onLongClick != null) { { haptics.longPress() onLongClick() } } else null, onDoubleClick = if (onDoubleClick != null) { { haptics.press() haptics.longPress() onDoubleClick() } } else null, hapticFeedbackEnabled = false, onClick = { haptics.longPress() onClick() } ) } fun Modifier.hapticsCombinedClickable( enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, onLongClickLabel: String? = null, onLongClick: (() -> Unit)? = null, onDoubleClick: (() -> Unit)? = null, onClick: () -> Unit ) = this.composed { hapticsCombinedClickable( interactionSource = null, indication = LocalIndication.current, enabled = enabled, onClickLabel = onClickLabel, role = role, onLongClickLabel = onLongClickLabel, onLongClick = onLongClick, onDoubleClick = onDoubleClick, onClick = onClick ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedIconButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.material.minimumInteractiveComponentSize import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.materialShadow import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest @Composable fun EnhancedIconButton( onClick: () -> Unit, modifier: Modifier = Modifier, onLongClick: (() -> Unit)? = null, enabled: Boolean = true, containerColor: Color = Color.Transparent, contentColor: Color = contentColor(containerColor), borderColor: Color = MaterialTheme.colorScheme.outlineVariant(onTopOf = containerColor), shape: Shape = AutoCircleShape(), pressedShape: Shape = IconButtonDefaults.smallPressedShape, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, forceMinimumInteractiveComponentSize: Boolean = true, enableAutoShadowAndBorder: Boolean = containerColor != Color.Transparent, isShadowClip: Boolean = containerColor.alpha != 1f || !enabled, content: @Composable () -> Unit ) { val settingsState = LocalSettingsState.current val haptics = LocalHapticFeedback.current LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { if (onLongClick != null) { val viewConfiguration = LocalViewConfiguration.current LaunchedEffect(interactionSource) { var isLongClick = false interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> { isLongClick = false delay(viewConfiguration.longPressTimeoutMillis) isLongClick = true onLongClick() haptics.longPress() } is PressInteraction.Release -> { if (!isLongClick) { onClick() haptics.press() } } is PressInteraction.Cancel -> { isLongClick = false } } } } } val animatedShape = shapeByInteraction( shape = shape, pressedShape = pressedShape, interactionSource = interactionSource ) OutlinedIconButton( onClick = { if (onLongClick == null) { onClick() haptics.longPress() } }, modifier = modifier .then( if (forceMinimumInteractiveComponentSize) Modifier.minimumInteractiveComponentSize() else Modifier ) .materialShadow( shape = animatedShape, isClipped = isShadowClip, enabled = LocalSettingsState.current.drawButtonShadows, elevation = if (settingsState.borderWidth > 0.dp || !enableAutoShadowAndBorder) 0.dp else 0.7.dp ), shape = animatedShape, colors = IconButtonDefaults.iconButtonColors( contentColor = contentColor, containerColor = containerColor ), enabled = enabled, border = BorderStroke( width = animateDpAsState( if (enableAutoShadowAndBorder) settingsState.borderWidth else 0.dp ).value, color = animateColorAsState( if (enableAutoShadowAndBorder) borderColor else Color.Transparent ).value ), interactionSource = interactionSource, content = content ) } } @Composable private fun contentColor( backgroundColor: Color ) = MaterialTheme.colorScheme.contentColorFor(backgroundColor).takeOrElse { if (backgroundColor == MaterialTheme.colorScheme.mixedContainer) MaterialTheme.colorScheme.onMixedContainer else LocalContentColor.current } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedLoadingIndicator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.keepScreenOn import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.gigamole.composeshadowsplus.rsblur.rsBlurShadow import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.animation.RotationEasing import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun EnhancedLoadingIndicator(modifier: Modifier = Modifier) { EnhancedLoadingIndicatorContainer( modifier = modifier .then( if (modifier == Modifier) { Modifier.sizeIn(maxWidth = 76.dp, maxHeight = 76.dp) } else Modifier ) .aspectRatio(1f), content = { LoadingIndicator( modifier = Modifier.size(this.minHeight / 1.2f) ) } ) } @Composable fun BoxScope.EnhancedLoadingIndicator( progress: Float, loaderSize: Dp = 60.dp, additionalContent: @Composable (Dp) -> Unit = {} ) { EnhancedLoadingIndicatorContainer( modifier = Modifier .size(108.dp) .align(Alignment.Center), content = { BoxWithConstraints( modifier = Modifier.size(loaderSize), contentAlignment = Alignment.Center ) { EnhancedCircularProgressIndicator( modifier = Modifier.size(this.maxWidth), color = MaterialTheme.colorScheme.secondary.copy(0.3f), trackColor = MaterialTheme.colorScheme.surfaceContainer ) val progressAnimated = animateFloatAsState(targetValue = progress) EnhancedCircularProgressIndicator( modifier = Modifier.size(maxWidth), progress = { progressAnimated.value }, color = MaterialTheme.colorScheme.primary, trackColor = Color.Transparent ) additionalContent(maxWidth) } } ) } @Composable fun BoxScope.EnhancedLoadingIndicator(done: Int, left: Int) { val progress = done / left.toFloat() if (progress.isFinite() && progress >= 0 && left > 1) { EnhancedLoadingIndicator( progress = progress, additionalContent = { AutoSizeText( text = "$done / $left", maxLines = 1, fontWeight = FontWeight.Medium, modifier = Modifier.width(it * 0.7f), textAlign = TextAlign.Center ) } ) } else { EnhancedLoadingIndicator( modifier = Modifier .align(Alignment.Center) .size(108.dp) ) } } @Composable private fun EnhancedLoadingIndicatorContainer( modifier: Modifier, content: @Composable BoxWithConstraintsScope.() -> Unit ) { val infiniteTransition = rememberInfiniteTransition() val rotation by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( tween( durationMillis = 1300, easing = RotationEasing ) ) ) BoxWithConstraints( modifier = modifier.keepScreenOn(), contentAlignment = Alignment.Center ) { val settingsState = LocalSettingsState.current val borderWidth = settingsState.borderWidth val backgroundColor = takeColorFromScheme { surfaceContainerHighest.blend( color = primaryContainer, fraction = 0.1f ) } Spacer( modifier = Modifier .size(minHeight) .rotate(rotation) .then( if (borderWidth <= 0.dp && settingsState.drawContainerShadows) { Modifier.rsBlurShadow( radius = 1.05.dp, shape = MaterialStarShape, isAlphaContentClip = true, offset = DpOffset.Zero, spread = if (settingsState.isNightMode) 1.15.dp else 0.44.dp, color = MaterialTheme.colorScheme.scrim.copy(0.31f) ) } else Modifier ) .background( color = backgroundColor, shape = MaterialStarShape ) .border( width = borderWidth, color = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f, onTopOf = backgroundColor ), shape = MaterialStarShape ) ) content() } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalBottomSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.GraphicsLayerScope import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.animation.FancyTransitionEasing import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.CornerSides import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.autoElevatedBorder import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.modifier.only import com.t8rin.modalsheet.ModalBottomSheetValue import com.t8rin.modalsheet.ModalSheet import com.t8rin.modalsheet.rememberModalBottomSheetState import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun EnhancedModalBottomSheet( nestedScrollEnabled: Boolean = false, cancelable: Boolean = true, dragHandle: @Composable ColumnScope.() -> Unit = { EnhancedModalSheetDragHandle() }, visible: Boolean, onDismiss: (Boolean) -> Unit, sheetContent: @Composable ColumnScope.() -> Unit, ) { EnhancedModalSheetImpl( cancelable = cancelable, nestedScrollEnabled = nestedScrollEnabled, dragHandle = dragHandle, visible = visible, onVisibleChange = onDismiss, content = sheetContent ) } @Composable fun EnhancedModalBottomSheet( nestedScrollEnabled: Boolean, cancelable: Boolean = true, confirmButton: @Composable RowScope.() -> Unit, dragHandle: @Composable ColumnScope.() -> Unit = { EnhancedModalSheetDragHandle() }, title: @Composable () -> Unit, endConfirmButtonPadding: Dp = 12.dp, visible: Boolean, onDismiss: (Boolean) -> Unit, enableBackHandler: Boolean = true, sheetContent: @Composable ColumnScope.() -> Unit, ) { EnhancedModalSheetImpl( cancelable = cancelable, nestedScrollEnabled = nestedScrollEnabled, dragHandle = dragHandle, visible = visible, onVisibleChange = onDismiss, enableBackHandler = enableBackHandler, content = { Column( modifier = Modifier.weight(1f, false), horizontalAlignment = Alignment.CenterHorizontally, content = sheetContent ) Row( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke(true, autoElevation = 6.dp) .background(EnhancedBottomSheetDefaults.barContainerColor) .padding(8.dp) .navigationBarsPadding() .padding(end = endConfirmButtonPadding), verticalAlignment = Alignment.CenterVertically ) { title() Spacer(modifier = Modifier.weight(1f)) confirmButton() } } ) } @Composable fun EnhancedModalBottomSheet( nestedScrollEnabled: Boolean = false, cancelable: Boolean = true, confirmButton: (@Composable RowScope.() -> Unit)? = null, dragHandle: @Composable ColumnScope.() -> Unit = { EnhancedModalSheetDragHandle() }, title: (@Composable RowScope.() -> Unit)? = null, visible: Boolean, onDismiss: (Boolean) -> Unit, enableBackHandler: Boolean = true, enableBottomContentWeight: Boolean = true, sheetContent: @Composable ColumnScope.() -> Unit, ) { EnhancedModalSheetImpl( cancelable = cancelable, nestedScrollEnabled = nestedScrollEnabled, dragHandle = dragHandle, visible = visible, onVisibleChange = onDismiss, enableBackHandler = enableBackHandler, content = { Column( modifier = Modifier.weight(1f, false), horizontalAlignment = Alignment.CenterHorizontally, content = sheetContent ) if (confirmButton != null && title != null) { Row( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke(true, autoElevation = 6.dp) .background(EnhancedBottomSheetDefaults.barContainerColor) .navigationBarsPadding() .padding(8.dp) .then( if (enableBottomContentWeight) Modifier.padding(end = 12.dp) else Modifier ), verticalAlignment = Alignment.CenterVertically ) { Row( modifier = Modifier.then( if (enableBottomContentWeight) { Modifier.weight(1f) } else Modifier ) ) { title() } confirmButton() } } } ) } @Composable private fun EnhancedModalSheetImpl( visible: Boolean, onVisibleChange: (Boolean) -> Unit, modifier: Modifier = Modifier, dragHandle: @Composable ColumnScope.() -> Unit = { EnhancedModalSheetDragHandle() }, nestedScrollEnabled: Boolean = false, animationSpec: AnimationSpec = EnhancedBottomSheetDefaults.animationSpec, sheetModifier: Modifier = Modifier, cancelable: Boolean = true, skipHalfExpanded: Boolean = true, shape: Shape = EnhancedBottomSheetDefaults.shape, elevation: Dp = 0.dp, containerColor: Color = EnhancedBottomSheetDefaults.containerColor, contentColor: Color = contentColorFor(containerColor), scrimColor: Color = EnhancedBottomSheetDefaults.scrimColor, enableBackHandler: Boolean = true, content: @Composable ColumnScope.() -> Unit, ) { if (LocalInspectionMode.current && visible) { return ProvideContainerDefaults( color = EnhancedBottomSheetDefaults.contentContainerColor ) { ModalBottomSheet( onDismissRequest = { onVisibleChange(false) }, containerColor = containerColor, contentColor = contentColor, shape = shape, sheetState = androidx.compose.material3.rememberModalBottomSheetState( skipPartiallyExpanded = skipHalfExpanded ), dragHandle = { Column(content = dragHandle) }, content = content ) } } var predictiveBackProgress by remember { mutableFloatStateOf(0f) } val animatedPredictiveBackProgress by animateFloatAsState(predictiveBackProgress) val clean = { predictiveBackProgress = 0f } LaunchedEffect(visible) { if (!visible) { delay(300L) clean() } } // Hold cancelable flag internally and set to true when modal sheet is dismissed via "visible" property in // non-cancellable modal sheet. This ensures that "confirmValueChange" will return true when sheet is set to hidden // state. val internalCancelable = remember { mutableStateOf(cancelable) } val sheetState = rememberModalBottomSheetState( skipHalfExpanded = skipHalfExpanded, initialValue = ModalBottomSheetValue.Hidden, animationSpec = animationSpec, confirmValueChange = { // Intercept and disallow hide gesture / action if (it == ModalBottomSheetValue.Hidden && !internalCancelable.value) { return@rememberModalBottomSheetState false } true }, ) val scope = rememberCoroutineScope() var isAnimating by remember { mutableStateOf(false) } LaunchedEffect(visible, cancelable) { if (visible) { internalCancelable.value = cancelable isAnimating = true scope.launch { sheetState.show() isAnimating = false } } else { internalCancelable.value = true isAnimating = true scope.launch { sheetState.hide() isAnimating = false } } } LaunchedEffect(sheetState.currentValue, sheetState.targetValue, sheetState.progress) { delay(600) if (sheetState.progress == 1f && sheetState.currentValue == sheetState.targetValue) { val newVisible = sheetState.isVisible if (newVisible != visible) { onVisibleChange(newVisible) } } } if (!visible && sheetState.currentValue == sheetState.targetValue && !sheetState.isVisible && !isAnimating) return val settingsState = LocalSettingsState.current val autoElevation by animateDpAsState( if (settingsState.drawContainerShadows) 16.dp else 0.dp ) ProvideContainerDefaults( color = EnhancedBottomSheetDefaults.contentContainerColor ) { ModalSheet( sheetState = sheetState, onDismiss = { if (cancelable) { onVisibleChange(false) } }, dragHandle = dragHandle, nestedScrollEnabled = nestedScrollEnabled, sheetModifier = sheetModifier .statusBarsPadding() .graphicsLayer { val sheetOffset = 0f val sheetHeight = size.height if (!sheetOffset.isNaN() && !sheetHeight.isNaN() && sheetHeight != 0f) { val progress = animatedPredictiveBackProgress scaleX = calculatePredictiveBackScaleX(progress) scaleY = calculatePredictiveBackScaleY(progress) transformOrigin = TransformOrigin( pivotFractionX = 0.5f, pivotFractionY = (sheetOffset + sheetHeight) / sheetHeight ) } } .offset(y = (settingsState.borderWidth + 1.dp)) .autoElevatedBorder( shape = shape, autoElevation = autoElevation ) .autoElevatedBorder( height = 0.dp, shape = shape, autoElevation = autoElevation ) .clip(shape) .animateContentSizeNoClip(spring()), modifier = modifier, shape = shape, elevation = elevation, containerColor = containerColor, contentColor = contentColor, scrimColor = scrimColor, content = { PredictiveBackObserver( onProgress = { progress -> predictiveBackProgress = progress / 6f }, onClean = { isCompleted -> if (isCompleted) { onVisibleChange(false) delay(400) } clean() }, enabled = visible && enableBackHandler ) content() }, ) } } private fun GraphicsLayerScope.calculatePredictiveBackScaleX(progress: Float): Float { val width = size.width return if (width.isNaN() || width == 0f) { 1f } else { (1f - progress).coerceAtLeast(0.85f) } } private fun GraphicsLayerScope.calculatePredictiveBackScaleY(progress: Float): Float { val height = size.height return if (height.isNaN() || height == 0f) { 1f } else { (1f - progress).coerceAtLeast(0.85f) } } object EnhancedBottomSheetDefaults { val shape @Composable get() = ShapeDefaults.extremeLarge.only(CornerSides.Top) val barContainerColor: Color @Composable get() = MaterialTheme.colorScheme.surfaceContainerHigh val containerColor: Color @Composable get() = MaterialTheme.colorScheme.surfaceContainerLow val contentContainerColor: Color @Composable get() = MaterialTheme.colorScheme.surfaceContainer val scrimColor: Color @Composable get() = MaterialTheme.colorScheme.scrim.copy(0.32f) val animationSpec: AnimationSpec = tween( durationMillis = 400, easing = FancyTransitionEasing ) val dragHandleHeight: Dp = 4.dp } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedModalSheetDragHandle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import kotlin.math.tan @Composable fun EnhancedModalSheetDragHandle( modifier: Modifier = Modifier, color: Color = EnhancedBottomSheetDefaults.barContainerColor, drawStroke: Boolean = true, showDragHandle: Boolean = true, bendAngle: Float = 0f, strokeWidth: Dp = EnhancedBottomSheetDefaults.dragHandleHeight, heightWhenDisabled: Dp = 0.dp, content: @Composable ColumnScope.() -> Unit = {}, ) { val dragHandleWidth = LocalSettingsState.current.dragHandleWidth Column( modifier .then( if (drawStroke) { Modifier .drawHorizontalStroke(autoElevation = 3.dp) .zIndex(Float.MAX_VALUE) } else Modifier ) .background(color), horizontalAlignment = Alignment.CenterHorizontally ) { if (showDragHandle && dragHandleWidth > 0.dp) { Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 22.dp), horizontalArrangement = Arrangement.Center ) { BendableDragHandle( width = dragHandleWidth, angleDegrees = bendAngle, strokeWidth = strokeWidth, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.4f).compositeOver( MaterialTheme.colorScheme.surface ) ) } } else { Spacer(modifier = Modifier.height(heightWhenDisabled)) } content() } } @Composable private fun BendableDragHandle( width: Dp, angleDegrees: Float, strokeWidth: Dp, color: Color, modifier: Modifier = Modifier ) { val density = LocalDensity.current val stroke = with(density) { strokeWidth.toPx() } val totalWidth = with(density) { width.toPx() } val halfWidth = totalWidth / 2f val halfStroke = stroke / 2f val angleRadians = Math.toRadians((angleDegrees * (64 / width.value).coerceAtMost(1f)).toDouble()).toFloat() val height = tan(angleRadians) * halfWidth Canvas( modifier = modifier .width(width) .height(height.dp + strokeWidth) ) { val centerY = size.height / 2f val centerX = size.width / 2f val leftStart = Offset(0f, centerY) val center = Offset(centerX, centerY + height) val rightEnd = Offset(size.width, centerY) drawLine( color = color, start = leftStart, end = center, strokeWidth = stroke, cap = StrokeCap.Round ) drawLine( color = color, start = center, end = rightEnd, strokeWidth = stroke, cap = StrokeCap.Round ) drawCircle( color = color, radius = halfStroke, center = center ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationBarItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.selection.selectable import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBarItemColors import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastFirst import androidx.compose.ui.util.fastFirstOrNull import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import kotlin.math.roundToInt @Composable fun RowScope.EnhancedNavigationBarItem( selected: Boolean, onClick: () -> Unit, icon: @Composable () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, label: @Composable (() -> Unit)? = null, alwaysShowLabel: Boolean = true, colors: NavigationBarItemColors = NavigationBarItemDefaults.colors(), interactionSource: MutableInteractionSource? = null, ) { @Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val colorAnimationSpec = MaterialTheme.motionScheme.defaultEffectsSpec() val styledIcon = @Composable { val iconColor by animateColorAsState( targetValue = colors.iconColor(selected = selected, enabled = enabled), animationSpec = colorAnimationSpec, ) // If there's a label, don't have a11y services repeat the icon description. val clearSemantics = label != null && (alwaysShowLabel || selected) Box(modifier = if (clearSemantics) Modifier.clearAndSetSemantics {} else Modifier) { CompositionLocalProvider(LocalContentColor provides iconColor, content = icon) } } val styledLabel: @Composable (() -> Unit)? = label?.let { @Composable { val style = NavigationBarTokens.LabelTextFont val textColor by animateColorAsState( targetValue = colors.textColor(selected = selected, enabled = enabled), animationSpec = colorAnimationSpec, ) ProvideContentColorTextStyle( contentColor = textColor, textStyle = style, content = label, ) } } var itemWidth by remember { mutableIntStateOf(0) } Box( modifier .selectable( selected = selected, onClick = onClick, enabled = enabled, role = Role.Tab, interactionSource = interactionSource, indication = null, ) .defaultMinSize(minHeight = NavigationBarHeight) .weight(1f) .onSizeChanged { itemWidth = it.width }, contentAlignment = Alignment.Center, propagateMinConstraints = true, ) { val alphaAnimationProgress: State = animateFloatAsState( targetValue = if (selected) 1f else 0f, animationSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), ) val sizeAnimationProgress: State = animateFloatAsState( targetValue = if (selected) 1f else 0f, animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(), ) // The entire item is selectable, but only the indicator pill shows the ripple. To achieve // this, we re-map the coordinates of the item's InteractionSource into the coordinates of // the indicator. val density = LocalDensity.current val calculateDeltaOffset = { with(density) { val indicatorWidth = NavigationBarVerticalItemTokens.ActiveIndicatorWidth.roundToPx() Offset((itemWidth - indicatorWidth).toFloat() / 2, IndicatorVerticalOffset.toPx()) } } val offsetInteractionSource = remember(interactionSource, calculateDeltaOffset) { MappedInteractionSource(interactionSource, calculateDeltaOffset) } val indicatorShape = shapeByInteraction( shape = NavigationBarTokens.ItemActiveIndicatorShape, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) // The indicator has a width-expansion animation which interferes with the timing of the // ripple, which is why they are separate composables val indicatorRipple = @Composable { Box( Modifier .layoutId(IndicatorRippleLayoutIdTag) .clip(indicatorShape) .indication(offsetInteractionSource, ripple()) ) } val indicator = @Composable { Box( Modifier .layoutId(IndicatorLayoutIdTag) .graphicsLayer { alpha = alphaAnimationProgress.value } .background( color = colors.selectedIndicatorColor, shape = indicatorShape ) ) } NavigationBarItemLayout( indicatorRipple = indicatorRipple, indicator = indicator, icon = styledIcon, label = styledLabel, alwaysShowLabel = alwaysShowLabel, alphaAnimationProgress = { alphaAnimationProgress.value }, sizeAnimationProgress = { sizeAnimationProgress.value }, ) } } @Composable private fun NavigationBarItemLayout( indicatorRipple: @Composable () -> Unit, indicator: @Composable () -> Unit, icon: @Composable () -> Unit, label: @Composable (() -> Unit)?, alwaysShowLabel: Boolean, alphaAnimationProgress: () -> Float, sizeAnimationProgress: () -> Float, ) { Layout( modifier = Modifier.badgeBounds(), content = { indicatorRipple() indicator() Box(Modifier.layoutId(IconLayoutIdTag)) { icon() } if (label != null) { Box( Modifier .layoutId(LabelLayoutIdTag) .graphicsLayer { alpha = if (alwaysShowLabel) 1f else alphaAnimationProgress() } ) { label() } } }, ) { measurables, constraints -> @Suppress("NAME_SHADOWING") // Ensure that the progress is >= 0. It may be negative on bouncy springs, for example. val animationProgress = sizeAnimationProgress().coerceAtLeast(0f) val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0) val iconPlaceable = measurables.fastFirst { it.layoutId == IconLayoutIdTag }.measure(looseConstraints) val totalIndicatorWidth = iconPlaceable.width + (IndicatorHorizontalPadding * 2).roundToPx() val animatedIndicatorWidth = (totalIndicatorWidth * animationProgress).roundToInt() val indicatorHeight = iconPlaceable.height + (IndicatorVerticalPadding * 2).roundToPx() val indicatorRipplePlaceable = measurables .fastFirst { it.layoutId == IndicatorRippleLayoutIdTag } .measure(Constraints.fixed(width = totalIndicatorWidth, height = indicatorHeight)) val indicatorPlaceable = measurables .fastFirstOrNull { it.layoutId == IndicatorLayoutIdTag } ?.measure( Constraints.fixed(width = animatedIndicatorWidth, height = indicatorHeight) ) val labelPlaceable = label?.let { measurables.fastFirst { it.layoutId == LabelLayoutIdTag }.measure(looseConstraints) } if (label == null) { placeIcon(iconPlaceable, indicatorRipplePlaceable, indicatorPlaceable, constraints) } else { placeLabelAndIcon( labelPlaceable!!, iconPlaceable, indicatorRipplePlaceable, indicatorPlaceable, constraints, alwaysShowLabel, animationProgress, ) } } } /** Places the provided [Placeable]s in the center of the provided [constraints]. */ private fun MeasureScope.placeIcon( iconPlaceable: Placeable, indicatorRipplePlaceable: Placeable, indicatorPlaceable: Placeable?, constraints: Constraints, ): MeasureResult { val width = if (constraints.maxWidth == Constraints.Infinity) { iconPlaceable.width + NavigationBarItemToIconMinimumPadding.roundToPx() * 2 } else { constraints.maxWidth } val height = constraints.constrainHeight(NavigationBarHeight.roundToPx()) val iconX = (width - iconPlaceable.width) / 2 val iconY = (height - iconPlaceable.height) / 2 val rippleX = (width - indicatorRipplePlaceable.width) / 2 val rippleY = (height - indicatorRipplePlaceable.height) / 2 return layout(width, height) { indicatorPlaceable?.let { val indicatorX = (width - it.width) / 2 val indicatorY = (height - it.height) / 2 it.placeRelative(indicatorX, indicatorY) } iconPlaceable.placeRelative(iconX, iconY) indicatorRipplePlaceable.placeRelative(rippleX, rippleY) } } /** * Places the provided [Placeable]s in the correct position, depending on [alwaysShowLabel] and * [animationProgress]. * * When [alwaysShowLabel] is true, the positions do not move. The [iconPlaceable] and * [labelPlaceable] will be placed together in the center with padding between them, according to * the spec. * * When [animationProgress] is 1 (representing the selected state), the positions will be the same * as above. * * Otherwise, when [animationProgress] is 0, [iconPlaceable] will be placed in the center, like in * [placeIcon], and [labelPlaceable] will not be shown. * * When [animationProgress] is animating between these values, [iconPlaceable] and [labelPlaceable] * will be placed at a corresponding interpolated position. * * [indicatorRipplePlaceable] and [indicatorPlaceable] will always be placed in such a way that to * share the same center as [iconPlaceable]. * * @param labelPlaceable text label placeable inside this item * @param iconPlaceable icon placeable inside this item * @param indicatorRipplePlaceable indicator ripple placeable inside this item * @param indicatorPlaceable indicator placeable inside this item, if it exists * @param constraints constraints of the item * @param alwaysShowLabel whether to always show the label for this item. If true, icon and label * positions will not change. If false, positions transition between 'centered icon with no label' * and 'top aligned icon with label'. * @param animationProgress progress of the animation, where 0 represents the unselected state of * this item and 1 represents the selected state. Values between 0 and 1 interpolate positions of * the icon and label. */ private fun MeasureScope.placeLabelAndIcon( labelPlaceable: Placeable, iconPlaceable: Placeable, indicatorRipplePlaceable: Placeable, indicatorPlaceable: Placeable?, constraints: Constraints, alwaysShowLabel: Boolean, animationProgress: Float, ): MeasureResult { val contentHeight = iconPlaceable.height + IndicatorVerticalPadding.toPx() + NavigationBarIndicatorToLabelPadding.toPx() + labelPlaceable.height val contentVerticalPadding = ((constraints.minHeight - contentHeight) / 2).coerceAtLeast(IndicatorVerticalPadding.toPx()) val height = contentHeight + contentVerticalPadding * 2 // Icon (when selected) should be `contentVerticalPadding` from top val selectedIconY = contentVerticalPadding val unselectedIconY = if (alwaysShowLabel) selectedIconY else (height - iconPlaceable.height) / 2 // How far the icon needs to move between unselected and selected states. val iconDistance = unselectedIconY - selectedIconY // The interpolated fraction of iconDistance that all placeables need to move based on // animationProgress. val offset = iconDistance * (1 - animationProgress) // Label should be fixed padding below icon val labelY = selectedIconY + iconPlaceable.height + IndicatorVerticalPadding.toPx() + NavigationBarIndicatorToLabelPadding.toPx() val containerWidth = if (constraints.maxWidth == Constraints.Infinity) { iconPlaceable.width + NavigationBarItemToIconMinimumPadding.roundToPx() * 2 } else { constraints.maxWidth } val labelX = (containerWidth - labelPlaceable.width) / 2 val iconX = (containerWidth - iconPlaceable.width) / 2 val rippleX = (containerWidth - indicatorRipplePlaceable.width) / 2 val rippleY = selectedIconY - IndicatorVerticalPadding.toPx() return layout(containerWidth, height.roundToInt()) { indicatorPlaceable?.let { val indicatorX = (containerWidth - it.width) / 2 val indicatorY = selectedIconY - IndicatorVerticalPadding.roundToPx() it.placeRelative(indicatorX, (indicatorY + offset).roundToInt()) } if (alwaysShowLabel || animationProgress != 0f) { labelPlaceable.placeRelative(labelX, (labelY + offset).roundToInt()) } iconPlaceable.placeRelative(iconX, (selectedIconY + offset).roundToInt()) indicatorRipplePlaceable.placeRelative(rippleX, (rippleY + offset).roundToInt()) } } private const val IndicatorRippleLayoutIdTag: String = "indicatorRipple" private const val IndicatorLayoutIdTag: String = "indicator" private const val IconLayoutIdTag: String = "icon" private const val LabelLayoutIdTag: String = "label" private val NavigationBarHeight: Dp = NavigationBarTokens.TallContainerHeight /*@VisibleForTesting*/ internal val NavigationBarIndicatorToLabelPadding: Dp = 4.dp private val IndicatorHorizontalPadding: Dp = (NavigationBarVerticalItemTokens.ActiveIndicatorWidth - NavigationBarVerticalItemTokens.IconSize) / 2 /*@VisibleForTesting*/ internal val IndicatorVerticalPadding: Dp = (NavigationBarVerticalItemTokens.ActiveIndicatorHeight - NavigationBarVerticalItemTokens.IconSize) / 2 private val IndicatorVerticalOffset: Dp = 12.dp /*@VisibleForTesting*/ internal val NavigationBarItemToIconMinimumPadding: Dp = 44.dp internal object NavigationBarTokens { val ItemActiveIndicatorShape @Composable get() = AutoCornersShape(16.dp) val TallContainerHeight = 80.0.dp val LabelTextFont @Composable get() = MaterialTheme.typography.labelMedium } internal object NavigationBarVerticalItemTokens { val ActiveIndicatorHeight = 32.0.dp val ActiveIndicatorWidth = 56.0.dp val IconSize = 24.0.dp } @Stable internal fun NavigationBarItemColors.iconColor(selected: Boolean, enabled: Boolean): Color = when { !enabled -> disabledIconColor selected -> selectedIconColor else -> unselectedIconColor } @Stable internal fun NavigationBarItemColors.textColor(selected: Boolean, enabled: Boolean): Color = when { !enabled -> disabledTextColor selected -> selectedTextColor else -> unselectedTextColor } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedNavigationRailItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.selection.selectable import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.layout.HorizontalRuler import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.VerticalRuler import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastFirst import androidx.compose.ui.util.fastFirstOrNull import com.t8rin.imagetoolbox.core.ui.theme.DisabledAlpha import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import kotlinx.coroutines.flow.map import kotlin.math.roundToInt @Composable fun EnhancedNavigationRailItem( selected: Boolean, onClick: () -> Unit, icon: @Composable () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, label: @Composable (() -> Unit)? = null, alwaysShowLabel: Boolean = true, colors: NavigationRailItemColors = NavigationRailItemDefaults.colors(), interactionSource: MutableInteractionSource? = null, ) { @Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val colorAnimationSpec = MaterialTheme.motionScheme.defaultEffectsSpec() val styledIcon = @Composable { val iconColor by animateColorAsState( targetValue = colors.iconColor(selected = selected, enabled = enabled), animationSpec = colorAnimationSpec, ) // If there's a label, don't have a11y services repeat the icon description. val clearSemantics = label != null && (alwaysShowLabel || selected) Box(modifier = if (clearSemantics) Modifier.clearAndSetSemantics {} else Modifier) { CompositionLocalProvider(LocalContentColor provides iconColor, content = icon) } } val styledLabel: @Composable (() -> Unit)? = label?.let { @Composable { val style = NavigationRailVerticalItemTokens.LabelTextFont val textColor by animateColorAsState( targetValue = colors.textColor(selected = selected, enabled = enabled), animationSpec = colorAnimationSpec, ) ProvideContentColorTextStyle( contentColor = textColor, textStyle = style, content = label, ) } } Box( modifier .selectable( selected = selected, onClick = onClick, enabled = enabled, role = Role.Tab, interactionSource = interactionSource, indication = null, ) .defaultMinSize(minHeight = NavigationRailItemHeight) .widthIn(min = NavigationRailItemWidth), contentAlignment = Alignment.Center, propagateMinConstraints = true, ) { val alphaAnimationProgress: State = animateFloatAsState( targetValue = if (selected) 1f else 0f, animationSpec = MaterialTheme.motionScheme.defaultEffectsSpec(), ) val sizeAnimationProgress: State = animateFloatAsState( targetValue = if (selected) 1f else 0f, animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(), ) // The entire item is selectable, but only the indicator pill shows the ripple. To achieve // this, we re-map the coordinates of the item's InteractionSource into the coordinates of // the indicator. val density = LocalDensity.current val calculateDeltaOffset = { with(density) { val itemWidth = NavigationRailItemWidth.roundToPx() val indicatorWidth = NavigationRailVerticalItemTokens.ActiveIndicatorWidth.roundToPx() Offset((itemWidth - indicatorWidth).toFloat() / 2, 0f) } } val offsetInteractionSource = remember(interactionSource, calculateDeltaOffset) { MappedInteractionSource(interactionSource, calculateDeltaOffset) } val indicatorShape = shapeByInteraction( shape = NavigationRailBaselineItemTokens.ActiveIndicatorShape, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) // The indicator has a width-expansion animation which interferes with the timing of the // ripple, which is why they are separate composables val indicatorRipple = @Composable { Box( Modifier .layoutId(IndicatorRippleLayoutIdTag) .clip(indicatorShape) .indication(offsetInteractionSource, ripple()) ) } val indicator = @Composable { Box( Modifier .layoutId(IndicatorLayoutIdTag) .graphicsLayer { alpha = alphaAnimationProgress.value } .background(color = colors.indicatorColor, shape = indicatorShape) ) } NavigationRailItemLayout( indicatorRipple = indicatorRipple, indicator = indicator, icon = styledIcon, label = styledLabel, alwaysShowLabel = alwaysShowLabel, alphaAnimationProgress = { alphaAnimationProgress.value }, sizeAnimationProgress = { sizeAnimationProgress.value }, ) } } object NavigationRailItemDefaults { @Composable fun colors( selectedIconColor: Color = MaterialTheme.colorScheme.onSecondaryContainer, selectedTextColor: Color = MaterialTheme.colorScheme.secondary, indicatorColor: Color = MaterialTheme.colorScheme.secondaryContainer, unselectedIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, unselectedTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, disabledIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = DisabledAlpha), disabledTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = DisabledAlpha), ): NavigationRailItemColors = NavigationRailItemColors( selectedIconColor = selectedIconColor, selectedTextColor = selectedTextColor, selectedIndicatorColor = indicatorColor, unselectedIconColor = unselectedIconColor, unselectedTextColor = unselectedTextColor, disabledIconColor = disabledIconColor, disabledTextColor = disabledTextColor, ) } @Immutable class NavigationRailItemColors( val selectedIconColor: Color, val selectedTextColor: Color, val selectedIndicatorColor: Color, val unselectedIconColor: Color, val unselectedTextColor: Color, val disabledIconColor: Color, val disabledTextColor: Color, ) { /** * Returns a copy of this NavigationRailItemColors, optionally overriding some of the values. * This uses the Color.Unspecified to mean “use the value from the source” */ fun copy( selectedIconColor: Color = this.selectedIconColor, selectedTextColor: Color = this.selectedTextColor, selectedIndicatorColor: Color = this.selectedIndicatorColor, unselectedIconColor: Color = this.unselectedIconColor, unselectedTextColor: Color = this.unselectedTextColor, disabledIconColor: Color = this.disabledIconColor, disabledTextColor: Color = this.disabledTextColor, ) = NavigationRailItemColors( selectedIconColor.takeOrElse { this.selectedIconColor }, selectedTextColor.takeOrElse { this.selectedTextColor }, selectedIndicatorColor.takeOrElse { this.selectedIndicatorColor }, unselectedIconColor.takeOrElse { this.unselectedIconColor }, unselectedTextColor.takeOrElse { this.unselectedTextColor }, disabledIconColor.takeOrElse { this.disabledIconColor }, disabledTextColor.takeOrElse { this.disabledTextColor }, ) /** * Represents the icon color for this item, depending on whether it is [selected]. * * @param selected whether the item is selected * @param enabled whether the item is enabled */ @Stable internal fun iconColor(selected: Boolean, enabled: Boolean): Color = when { !enabled -> disabledIconColor selected -> selectedIconColor else -> unselectedIconColor } /** * Represents the text color for this item, depending on whether it is [selected]. * * @param selected whether the item is selected * @param enabled whether the item is enabled */ @Stable internal fun textColor(selected: Boolean, enabled: Boolean): Color = when { !enabled -> disabledTextColor selected -> selectedTextColor else -> unselectedTextColor } /** Represents the color of the indicator used for selected items. */ internal val indicatorColor: Color get() = selectedIndicatorColor override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || other !is NavigationRailItemColors) return false if (selectedIconColor != other.selectedIconColor) return false if (unselectedIconColor != other.unselectedIconColor) return false if (selectedTextColor != other.selectedTextColor) return false if (unselectedTextColor != other.unselectedTextColor) return false if (selectedIndicatorColor != other.selectedIndicatorColor) return false if (disabledIconColor != other.disabledIconColor) return false if (disabledTextColor != other.disabledTextColor) return false return true } override fun hashCode(): Int { var result = selectedIconColor.hashCode() result = 31 * result + unselectedIconColor.hashCode() result = 31 * result + selectedTextColor.hashCode() result = 31 * result + unselectedTextColor.hashCode() result = 31 * result + selectedIndicatorColor.hashCode() result = 31 * result + disabledIconColor.hashCode() result = 31 * result + disabledTextColor.hashCode() return result } } @Composable private fun NavigationRailItemLayout( indicatorRipple: @Composable () -> Unit, indicator: @Composable () -> Unit, icon: @Composable () -> Unit, label: @Composable (() -> Unit)?, alwaysShowLabel: Boolean, alphaAnimationProgress: () -> Float, sizeAnimationProgress: () -> Float, ) { Layout( modifier = Modifier.badgeBounds(), content = { indicatorRipple() indicator() Box(Modifier.layoutId(IconLayoutIdTag)) { icon() } if (label != null) { Box( Modifier .layoutId(LabelLayoutIdTag) .graphicsLayer { alpha = if (alwaysShowLabel) 1f else alphaAnimationProgress() } ) { label() } } }, ) { measurables, constraints -> @Suppress("NAME_SHADOWING") // Ensure that the progress is >= 0. It may be negative on bouncy springs, for example. val animationProgress = sizeAnimationProgress().coerceAtLeast(0f) val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0) val iconPlaceable = measurables.fastFirst { it.layoutId == IconLayoutIdTag }.measure(looseConstraints) val totalIndicatorWidth = iconPlaceable.width + (IndicatorHorizontalPadding * 2).roundToPx() val animatedIndicatorWidth = (totalIndicatorWidth * animationProgress).roundToInt() val indicatorVerticalPadding = if (label == null) { IndicatorVerticalPaddingNoLabel } else { IndicatorVerticalPaddingWithLabel } val indicatorHeight = iconPlaceable.height + (indicatorVerticalPadding * 2).roundToPx() val indicatorRipplePlaceable = measurables .fastFirst { it.layoutId == IndicatorRippleLayoutIdTag } .measure(Constraints.fixed(width = totalIndicatorWidth, height = indicatorHeight)) val indicatorPlaceable = measurables .fastFirstOrNull { it.layoutId == IndicatorLayoutIdTag } ?.measure( Constraints.fixed(width = animatedIndicatorWidth, height = indicatorHeight) ) val labelPlaceable = label?.let { measurables.fastFirst { it.layoutId == LabelLayoutIdTag }.measure(looseConstraints) } if (label == null) { placeIcon(iconPlaceable, indicatorRipplePlaceable, indicatorPlaceable, constraints) } else { placeLabelAndIcon( labelPlaceable!!, iconPlaceable, indicatorRipplePlaceable, indicatorPlaceable, constraints, alwaysShowLabel, animationProgress, ) } } } /** Places the provided [Placeable]s in the center of the provided [constraints]. */ private fun MeasureScope.placeIcon( iconPlaceable: Placeable, indicatorRipplePlaceable: Placeable, indicatorPlaceable: Placeable?, constraints: Constraints, ): MeasureResult { val width = constraints.constrainWidth( maxOf( iconPlaceable.width, indicatorRipplePlaceable.width, indicatorPlaceable?.width ?: 0, ) ) val height = constraints.constrainHeight(NavigationRailItemHeight.roundToPx()) val iconX = (width - iconPlaceable.width) / 2 val iconY = (height - iconPlaceable.height) / 2 val rippleX = (width - indicatorRipplePlaceable.width) / 2 val rippleY = (height - indicatorRipplePlaceable.height) / 2 return layout(width, height) { indicatorPlaceable?.let { val indicatorX = (width - it.width) / 2 val indicatorY = (height - it.height) / 2 it.placeRelative(indicatorX, indicatorY) } iconPlaceable.placeRelative(iconX, iconY) indicatorRipplePlaceable.placeRelative(rippleX, rippleY) } } /** * Places the provided [Placeable]s in the correct position, depending on [alwaysShowLabel] and * [animationProgress]. * * When [alwaysShowLabel] is true, the positions do not move. The [iconPlaceable] and * [labelPlaceable] will be placed together in the center with padding between them, according to * the spec. * * When [animationProgress] is 1 (representing the selected state), the positions will be the same * as above. * * Otherwise, when [animationProgress] is 0, [iconPlaceable] will be placed in the center, like in * [placeIcon], and [labelPlaceable] will not be shown. * * When [animationProgress] is animating between these values, [iconPlaceable] and [labelPlaceable] * will be placed at a corresponding interpolated position. * * [indicatorRipplePlaceable] and [indicatorPlaceable] will always be placed in such a way that to * share the same center as [iconPlaceable]. * * @param labelPlaceable text label placeable inside this item * @param iconPlaceable icon placeable inside this item * @param indicatorRipplePlaceable indicator ripple placeable inside this item * @param indicatorPlaceable indicator placeable inside this item, if it exists * @param constraints constraints of the item * @param alwaysShowLabel whether to always show the label for this item. If true, icon and label * positions will not change. If false, positions transition between 'centered icon with no label' * and 'top aligned icon with label'. * @param animationProgress progress of the animation, where 0 represents the unselected state of * this item and 1 represents the selected state. Values between 0 and 1 interpolate positions of * the icon and label. */ private fun MeasureScope.placeLabelAndIcon( labelPlaceable: Placeable, iconPlaceable: Placeable, indicatorRipplePlaceable: Placeable, indicatorPlaceable: Placeable?, constraints: Constraints, alwaysShowLabel: Boolean, animationProgress: Float, ): MeasureResult { val contentHeight = iconPlaceable.height + IndicatorVerticalPaddingWithLabel.toPx() + NavigationRailItemVerticalPadding.toPx() + labelPlaceable.height val contentVerticalPadding = ((constraints.minHeight - contentHeight) / 2).coerceAtLeast( IndicatorVerticalPaddingWithLabel.toPx() ) val height = contentHeight + contentVerticalPadding * 2 // Icon (when selected) should be `contentVerticalPadding` from the top val selectedIconY = contentVerticalPadding val unselectedIconY = if (alwaysShowLabel) selectedIconY else (height - iconPlaceable.height) / 2 // How far the icon needs to move between unselected and selected states val iconDistance = unselectedIconY - selectedIconY // The interpolated fraction of iconDistance that all placeables need to move based on // animationProgress, since the icon is higher in the selected state. val offset = iconDistance * (1 - animationProgress) // Label should be fixed padding below icon val labelY = selectedIconY + iconPlaceable.height + IndicatorVerticalPaddingWithLabel.toPx() + NavigationRailItemVerticalPadding.toPx() val width = constraints.constrainWidth( maxOf(iconPlaceable.width, labelPlaceable.width, indicatorPlaceable?.width ?: 0) ) val labelX = (width - labelPlaceable.width) / 2 val iconX = (width - iconPlaceable.width) / 2 val rippleX = (width - indicatorRipplePlaceable.width) / 2 val rippleY = selectedIconY - IndicatorVerticalPaddingWithLabel.toPx() return layout(width, height.roundToInt()) { indicatorPlaceable?.let { val indicatorX = (width - it.width) / 2 val indicatorY = selectedIconY - IndicatorVerticalPaddingWithLabel.toPx() it.placeRelative(indicatorX, (indicatorY + offset).roundToInt()) } if (alwaysShowLabel || animationProgress != 0f) { labelPlaceable.placeRelative(labelX, (labelY + offset).roundToInt()) } iconPlaceable.placeRelative(iconX, (selectedIconY + offset).roundToInt()) indicatorRipplePlaceable.placeRelative(rippleX, (rippleY + offset).roundToInt()) } } private const val IndicatorRippleLayoutIdTag: String = "indicatorRipple" private const val IndicatorLayoutIdTag: String = "indicator" private const val IconLayoutIdTag: String = "icon" private const val LabelLayoutIdTag: String = "label" internal val NavigationRailItemWidth: Dp = NavigationRailCollapsedTokens.NarrowContainerWidth internal val NavigationRailItemHeight: Dp = NavigationRailVerticalItemTokens.ActiveIndicatorWidth internal val NavigationRailItemVerticalPadding: Dp = 4.dp private val IndicatorHorizontalPadding: Dp = (NavigationRailVerticalItemTokens.ActiveIndicatorWidth - NavigationRailBaselineItemTokens.IconSize) / 2 private val IndicatorVerticalPaddingWithLabel: Dp = (NavigationRailVerticalItemTokens.ActiveIndicatorHeight - NavigationRailBaselineItemTokens.IconSize) / 2 private val IndicatorVerticalPaddingNoLabel: Dp = (NavigationRailVerticalItemTokens.ActiveIndicatorWidth - NavigationRailBaselineItemTokens.IconSize) / 2 internal val BadgeTopRuler = HorizontalRuler() internal val BadgeEndRuler = VerticalRuler() internal fun Modifier.badgeBounds() = this.layout { measurable, constraints -> val placeable = measurable.measure(constraints) layout( width = placeable.width, height = placeable.height, rulers = { // use provides instead of provideRelative cause we will place relative // in the badge code BadgeEndRuler provides coordinates.size.width.toFloat() BadgeTopRuler provides 0f }, ) { placeable.place(0, 0) } } internal object NavigationRailVerticalItemTokens { val ActiveIndicatorHeight = 32.0.dp val ActiveIndicatorWidth = 56.0.dp val LabelTextFont @Composable get() = MaterialTheme.typography.labelMedium } internal object NavigationRailBaselineItemTokens { val ActiveIndicatorShape @Composable get() = AutoCornersShape(16.dp) val IconSize = 24.0.dp } internal object NavigationRailCollapsedTokens { val NarrowContainerWidth = 80.0.dp } internal class MappedInteractionSource( underlyingInteractionSource: InteractionSource, private val calculateDelta: () -> Offset, ) : InteractionSource { private val mappedPresses = mutableMapOf() override val interactions = underlyingInteractionSource.interactions.map { interaction -> when (interaction) { is PressInteraction.Press -> { val mappedPress = mapPress(interaction) mappedPresses[interaction] = mappedPress mappedPress } is PressInteraction.Cancel -> { val mappedPress = mappedPresses.remove(interaction.press) if (mappedPress == null) { interaction } else { PressInteraction.Cancel(mappedPress) } } is PressInteraction.Release -> { val mappedPress = mappedPresses.remove(interaction.press) if (mappedPress == null) { interaction } else { PressInteraction.Release(mappedPress) } } else -> interaction } } private fun mapPress(press: PressInteraction.Press): PressInteraction.Press = PressInteraction.Press(press.pressPosition - calculateDelta()) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRadioButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButtonColors import androidx.compose.material3.RadioButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalHapticFeedback @Composable fun EnhancedRadioButton( selected: Boolean, onClick: (() -> Unit)?, modifier: Modifier = Modifier, enabled: Boolean = true, colors: RadioButtonColors = RadioButtonDefaults.colors(), interactionSource: MutableInteractionSource? = null ) { val haptics = LocalHapticFeedback.current RadioButton( selected = selected, onClick = if (onClick != null) { { haptics.longPress() onClick() } } else null, modifier = modifier, enabled = enabled, colors = colors, interactionSource = interactionSource ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedRangeSliderItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RichTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.contentColorFor import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.value.ValueDialog import com.t8rin.imagetoolbox.core.ui.widget.value.ValueText import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.launch @Composable fun EnhancedRangeSliderItem( value: ClosedFloatingPointRange, title: String, modifier: Modifier = Modifier, sliderModifier: Modifier = Modifier.padding(horizontal = 6.dp, vertical = 6.dp), icon: ImageVector? = null, valueRange: ClosedFloatingPointRange, onValueChange: (ClosedFloatingPointRange) -> Unit, onValueChangeFinished: ((ClosedFloatingPointRange) -> Unit)? = null, steps: Int = 0, topContentPadding: Dp = 8.dp, valueSuffix: String = "", internalStateTransformation: (ClosedFloatingPointRange) -> ClosedFloatingPointRange = { it }, visible: Boolean = true, color: Color = Color.Unspecified, contentColor: Color? = null, shape: Shape = ShapeDefaults.default, valueTextTapEnabled: Boolean = true, behaveAsContainer: Boolean = true, enabled: Boolean = true, titleHorizontalPadding: Dp = if (behaveAsContainer) 16.dp else 6.dp, valuesPreviewMapping: ImmutableMap = remember { persistentMapOf() }, additionalContent: (@Composable () -> Unit)? = null, ) { val internalColor = contentColor ?: if (color == MaterialTheme.colorScheme.surfaceContainer) { contentColorFor(backgroundColor = MaterialTheme.colorScheme.surfaceVariant) } else contentColorFor(backgroundColor = color) var showStartValueDialog by rememberSaveable { mutableStateOf(false) } var showEndValueDialog by rememberSaveable { mutableStateOf(false) } val internalState = remember(value) { mutableStateOf(internalStateTransformation(value)) } val isCompactLayout = LocalSettingsState.current.isCompactSelectorsLayout val tooltipState = rememberTooltipState() val scope = rememberCoroutineScope() AnimatedVisibility(visible = visible) { LocalContentColor.ProvidesValue(internalColor) { Column( modifier = modifier .then( if (behaveAsContainer) { Modifier.container( shape = shape, color = color ) } else Modifier ) .alpha( animateFloatAsState(if (enabled) 1f else 0.5f).value ) .animateContentSizeNoClip() .then( if (isCompactLayout && icon != null) { Modifier.pointerInput(Unit) { detectTapGestures( onLongPress = { scope.launch { tooltipState.show() } } ) } } else Modifier ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { val slider = @Composable { AnimatedContent( targetState = Pair( valueRange, steps ) ) { (valueRange, steps) -> EnhancedRangeSlider( modifier = if (isCompactLayout) { Modifier.padding( top = topContentPadding, start = 12.dp, end = 12.dp ) } else { sliderModifier }, enabled = enabled, value = internalState.value, onValueChange = { internalState.value = internalStateTransformation(it) onValueChange(it) }, onValueChangeFinished = onValueChangeFinished?.let { { it(internalState.value) } }, valueRange = valueRange, steps = steps ) } } AnimatedContent( targetState = isCompactLayout, transitionSpec = { fadeIn() + expandVertically() togetherWith fadeOut() + shrinkVertically() } ) { isCompactLayout -> Column { if (isCompactLayout) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 8.dp) ) { AnimatedContent(icon) { icon -> if (icon != null) { TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider( TooltipAnchorPosition.Above ), tooltip = { RichTooltip( colors = TooltipDefaults.richTooltipColors( containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer.copy( 0.5f ), titleContentColor = MaterialTheme.colorScheme.onTertiaryContainer ), title = { Text(title) }, text = { val trimmedStart = internalState.value.start.toString() .trimTrailingZero() val trimmedEnd = internalState.value.endInclusive.toString() .trimTrailingZero() val startPart = valuesPreviewMapping[internalState.value.start] ?: trimmedStart val endPart = valuesPreviewMapping[internalState.value.endInclusive] ?: trimmedEnd Text( "$startPart..$endPart $valueSuffix" ) } ) }, state = tooltipState, content = { IconShapeContainer( content = { Icon( imageVector = icon, contentDescription = null ) }, modifier = Modifier .padding( top = topContentPadding, start = 12.dp ) .clip( LocalSettingsState.current.iconShape?.shape ?: ShapeDefaults.circle ) .hapticsCombinedClickable( onLongClick = { scope.launch { tooltipState.show() } }, onClick = { scope.launch { tooltipState.show() } } ) ) } ) } } AnimatedVisibility(icon == null) { Text( text = title, modifier = Modifier .padding( top = topContentPadding, start = 12.dp ) .widthIn(max = 100.dp), style = MaterialTheme.typography.bodyMedium, lineHeight = 16.sp ) } Row( modifier = Modifier.weight(1f) ) { slider() } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding( top = topContentPadding, end = 8.dp ) ) { ValueText( enabled = valueTextTapEnabled && enabled, value = internalStateTransformation(internalState.value).start, valueSuffix = valueSuffix, customText = valuesPreviewMapping[internalState.value.start], modifier = Modifier.width( if (valuesPreviewMapping.isNotEmpty()) 108.dp else 72.dp ), onClick = { showStartValueDialog = true } ) Text("··") ValueText( enabled = valueTextTapEnabled && enabled, value = internalStateTransformation(internalState.value).endInclusive, valueSuffix = valueSuffix, customText = valuesPreviewMapping[internalState.value.endInclusive], modifier = Modifier.width( if (valuesPreviewMapping.isNotEmpty()) 108.dp else 72.dp ), onClick = { showEndValueDialog = true } ) } } } else { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 8.dp) ) { AnimatedContent(icon) { icon -> if (icon != null) { IconShapeContainer( content = { Icon( imageVector = icon, contentDescription = null ) }, modifier = Modifier.padding( top = topContentPadding, start = 12.dp ) ) } } Text( text = title, modifier = Modifier .padding( top = topContentPadding, end = titleHorizontalPadding, start = titleHorizontalPadding ) .weight(1f), lineHeight = 18.sp, fontWeight = if (behaveAsContainer) { FontWeight.Medium } else FontWeight.Normal ) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .height(IntrinsicSize.Max) .padding( top = topContentPadding, end = 14.dp ) ) { ValueText( enabled = valueTextTapEnabled && enabled, value = internalStateTransformation(internalState.value).start, customText = valuesPreviewMapping[internalState.value.start], valueSuffix = valueSuffix, modifier = Modifier.fillMaxHeight(), onClick = { showStartValueDialog = true } ) Text("··") ValueText( enabled = valueTextTapEnabled && enabled, value = internalStateTransformation(internalState.value).endInclusive, customText = valuesPreviewMapping[internalState.value.endInclusive], valueSuffix = valueSuffix, modifier = Modifier.fillMaxHeight(), onClick = { showEndValueDialog = true } ) } } slider() } } } additionalContent?.invoke() } } } ValueDialog( roundTo = null, valueRange = valueRange.start..internalState.value.endInclusive, valueState = internalState.value.start.toString(), expanded = visible && showStartValueDialog, onDismiss = { showStartValueDialog = false }, onValueUpdate = { val range = it.coerceAtMost(internalState.value.endInclusive)..internalState.value.endInclusive onValueChange(range) onValueChangeFinished?.invoke(range) } ) ValueDialog( roundTo = null, valueRange = internalState.value.start..valueRange.endInclusive, valueState = internalState.value.endInclusive.toString(), expanded = visible && showEndValueDialog, onDismiss = { showEndValueDialog = false }, onValueUpdate = { val range = internalState.value.start..it.coerceAtLeast(internalState.value.start) onValueChange(range) onValueChangeFinished?.invoke(range) } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSlider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SliderColors import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalHapticFeedback import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.sliders.FancyRangeSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.FancySlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.HyperOSRangeSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.HyperOSSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.M2RangeSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.M2Slider import com.t8rin.imagetoolbox.core.ui.widget.sliders.M3RangeSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.M3Slider @Composable fun EnhancedSlider( value: Float, onValueChange: (Float) -> Unit, modifier: Modifier = Modifier, onValueChangeFinished: (() -> Unit)? = null, valueRange: ClosedFloatingPointRange, steps: Int = 0, enabled: Boolean = true, colors: SliderColors? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, drawContainer: Boolean = true, isAnimated: Boolean = true ) { val settingsState = LocalSettingsState.current val sliderType = settingsState.sliderType val realColors = colors ?: when (sliderType) { SliderType.Fancy -> { SliderDefaults.colors( activeTickColor = MaterialTheme.colorScheme.inverseSurface, inactiveTickColor = MaterialTheme.colorScheme.surface, activeTrackColor = MaterialTheme.colorScheme.primaryContainer, inactiveTrackColor = SwitchDefaults.colors().disabledCheckedTrackColor, disabledThumbColor = SliderDefaults.colors().disabledThumbColor, disabledActiveTrackColor = SliderDefaults.colors().disabledActiveTrackColor, thumbColor = MaterialTheme.colorScheme.onPrimaryContainer ) } SliderType.MaterialYou -> SliderDefaults.colors() SliderType.Material -> SliderDefaults.colors() SliderType.HyperOS -> SliderDefaults.colors() } if (steps != 0) { var compositions by remember { mutableIntStateOf(0) } val haptics = LocalHapticFeedback.current val updatedValue by rememberUpdatedState(newValue = value) LaunchedEffect(updatedValue) { if (compositions > 0) haptics.press() compositions++ } } val value = if (isAnimated) { animateFloatAsState( targetValue = value, animationSpec = tween(100) ).value } else { value } when (sliderType) { SliderType.Fancy -> { FancySlider( value = value, enabled = enabled, colors = realColors, interactionSource = interactionSource, thumbShape = if (settingsState.shapesType is ShapeType.Cut) { AutoCircleShape( shapesType = settingsState.shapesType.copy( strength = settingsState.shapesType.strength.coerceAtLeast(0.5f) ) ) } else { MaterialStarShape }, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } SliderType.MaterialYou -> { M3Slider( value = value, enabled = enabled, colors = realColors, interactionSource = interactionSource, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } SliderType.Material -> { M2Slider( value = value, enabled = enabled, colors = realColors, interactionSource = interactionSource, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } SliderType.HyperOS -> { HyperOSSlider( value = value, enabled = enabled, colors = realColors, interactionSource = interactionSource, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } } } @Composable fun EnhancedRangeSlider( value: ClosedFloatingPointRange, onValueChange: (ClosedFloatingPointRange) -> Unit, modifier: Modifier = Modifier, onValueChangeFinished: (() -> Unit)? = null, valueRange: ClosedFloatingPointRange, steps: Int = 0, enabled: Boolean = true, colors: SliderColors? = null, startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, drawContainer: Boolean = true ) { val settingsState = LocalSettingsState.current val sliderType = settingsState.sliderType val realColors = colors ?: when (sliderType) { SliderType.Fancy -> { SliderDefaults.colors( activeTickColor = MaterialTheme.colorScheme.inverseSurface, inactiveTickColor = MaterialTheme.colorScheme.surface, activeTrackColor = MaterialTheme.colorScheme.primaryContainer, inactiveTrackColor = SwitchDefaults.colors().disabledCheckedTrackColor, disabledThumbColor = SliderDefaults.colors().disabledThumbColor, disabledActiveTrackColor = SliderDefaults.colors().disabledActiveTrackColor, thumbColor = MaterialTheme.colorScheme.onPrimaryContainer ) } SliderType.MaterialYou -> SliderDefaults.colors() SliderType.Material -> SliderDefaults.colors() SliderType.HyperOS -> SliderDefaults.colors() } if (steps != 0) { var compositions by remember { mutableIntStateOf(0) } val haptics = LocalHapticFeedback.current val updatedValue by rememberUpdatedState(newValue = value) LaunchedEffect(updatedValue) { if (compositions > 0) haptics.press() compositions++ } } when (sliderType) { SliderType.Fancy -> { FancyRangeSlider( value = value, enabled = enabled, colors = realColors, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, thumbShape = if (settingsState.shapesType is ShapeType.Cut) { AutoCircleShape( shapesType = settingsState.shapesType.copy( strength = settingsState.shapesType.strength.coerceAtLeast(0.5f) ) ) } else { MaterialStarShape }, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } SliderType.MaterialYou -> { M3RangeSlider( value = value, enabled = enabled, colors = realColors, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } SliderType.Material -> { M2RangeSlider( value = value, enabled = enabled, colors = realColors, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } SliderType.HyperOS -> { HyperOSRangeSlider( value = value, enabled = enabled, colors = realColors, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, modifier = modifier, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, drawContainer = drawContainer ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSliderItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RichTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.contentColorFor import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.value.ValueDialog import com.t8rin.imagetoolbox.core.ui.widget.value.ValueText import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.launch @Composable fun EnhancedSliderItem( value: Number, title: String, modifier: Modifier = Modifier, sliderModifier: Modifier = Modifier.padding(horizontal = 6.dp, vertical = 6.dp), icon: ImageVector? = null, valueRange: ClosedFloatingPointRange, onValueChange: (Float) -> Unit, onValueChangeFinished: ((Float) -> Unit)? = null, steps: Int = 0, topContentPadding: Dp = 8.dp, valueSuffix: String = "", internalStateTransformation: (Float) -> Number = { it }, visible: Boolean = true, containerColor: Color = Color.Unspecified, contentColor: Color? = null, shape: Shape = ShapeDefaults.default, valueTextTapEnabled: Boolean = true, behaveAsContainer: Boolean = true, enabled: Boolean = true, titleHorizontalPadding: Dp = if (behaveAsContainer) 16.dp else 6.dp, valuesPreviewMapping: ImmutableMap = remember { persistentMapOf() }, titleFontWeight: FontWeight = if (behaveAsContainer) { FontWeight.Medium } else FontWeight.Normal, isAnimated: Boolean = true, canInputValue: Boolean = true, additionalContent: (@Composable () -> Unit)? = null ) { val internalColor = contentColor ?: if (containerColor == MaterialTheme.colorScheme.surfaceContainer) { contentColorFor(backgroundColor = MaterialTheme.colorScheme.surfaceVariant) } else contentColorFor(backgroundColor = containerColor) var showValueDialog by rememberSaveable(canInputValue) { mutableStateOf(false) } val internalState = remember(value) { mutableStateOf(value) } val isCompactLayout = LocalSettingsState.current.isCompactSelectorsLayout val tooltipState = rememberTooltipState() val scope = rememberCoroutineScope() AnimatedVisibility(visible = visible) { LocalContentColor.ProvidesValue(internalColor) { Column( modifier = modifier .then( if (behaveAsContainer) { Modifier.container( shape = shape, color = containerColor ) } else Modifier ) .alpha( animateFloatAsState(if (enabled) 1f else 0.5f).value ) .animateContentSizeNoClip() .then( if (isCompactLayout && icon != null) { Modifier.pointerInput(Unit) { detectTapGestures( onLongPress = { scope.launch { tooltipState.show() } } ) } } else Modifier ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { val slider = @Composable { val nonAnimated: @Composable (ClosedFloatingPointRange, Int) -> Unit = { valueRange, steps -> EnhancedSlider( modifier = if (isCompactLayout) { Modifier.padding( top = topContentPadding, start = 12.dp, end = 12.dp ) } else { sliderModifier }, enabled = enabled, value = internalState.value.toFloat(), onValueChange = { internalState.value = internalStateTransformation(it) onValueChange(it) }, onValueChangeFinished = onValueChangeFinished?.let { { it(internalState.value.toFloat()) } }, valueRange = valueRange, steps = steps, isAnimated = isAnimated ) } if (isAnimated) { AnimatedContent( targetState = valueRange to steps ) { (valueRange, steps) -> nonAnimated(valueRange, steps) } } else { nonAnimated(valueRange, steps) } } AnimatedContent( targetState = isCompactLayout, transitionSpec = { fadeIn() + expandVertically() togetherWith fadeOut() + shrinkVertically() } ) { isCompactLayout -> Column { if (isCompactLayout) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 8.dp) ) { AnimatedContent(icon) { icon -> if (icon != null) { TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider( TooltipAnchorPosition.Above ), tooltip = { RichTooltip( colors = TooltipDefaults.richTooltipColors( containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer.copy( 0.5f ), titleContentColor = MaterialTheme.colorScheme.onTertiaryContainer ), title = { Text(title) }, text = { val trimmed = internalState.value.toString() .trimTrailingZero() Text( valuesPreviewMapping[internalState.value.toFloat()] ?: "$trimmed$valueSuffix" ) } ) }, state = tooltipState, content = { IconShapeContainer( content = { Icon( imageVector = icon, contentDescription = null ) }, modifier = Modifier .padding( top = topContentPadding, start = 12.dp ) .clip( LocalSettingsState.current.iconShape?.shape ?: ShapeDefaults.circle ) .hapticsCombinedClickable( onLongClick = { scope.launch { tooltipState.show() } }, onClick = { scope.launch { tooltipState.show() } } ) ) } ) } } AnimatedVisibility(icon == null) { Text( text = title, modifier = Modifier .padding( top = topContentPadding, start = 12.dp ) .widthIn(max = 100.dp), style = MaterialTheme.typography.bodyMedium, lineHeight = 16.sp ) } Row( modifier = Modifier.weight(1f) ) { slider() } ValueText( enabled = valueTextTapEnabled && enabled, value = internalStateTransformation(internalState.value.toFloat()), valueSuffix = valueSuffix, customText = valuesPreviewMapping[internalState.value.toFloat()], modifier = Modifier .width(108.dp) .padding( top = topContentPadding, end = 8.dp ), onClick = if (canInputValue) { { showValueDialog = true } } else null ) } } else { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 8.dp) ) { AnimatedContent(icon) { icon -> if (icon != null) { IconShapeContainer( content = { Icon( imageVector = icon, contentDescription = null ) }, modifier = Modifier.padding( top = topContentPadding, start = 12.dp ) ) } } Text( text = title, modifier = Modifier .padding( top = topContentPadding, end = titleHorizontalPadding, start = titleHorizontalPadding ) .weight(1f), lineHeight = 18.sp, fontWeight = titleFontWeight ) ValueText( enabled = valueTextTapEnabled && enabled, value = internalStateTransformation(internalState.value.toFloat()), customText = valuesPreviewMapping[internalState.value.toFloat()], valueSuffix = valueSuffix, modifier = Modifier.padding( top = topContentPadding, end = 14.dp ), onClick = if (canInputValue) { { showValueDialog = true } } else null ) } slider() } } } additionalContent?.invoke() } } } ValueDialog( roundTo = null, valueRange = valueRange, valueState = internalStateTransformation(value.toFloat()).toString(), expanded = visible && showValueDialog, onDismiss = { showValueDialog = false }, onValueUpdate = { onValueChange(it) onValueChangeFinished?.invoke(it) } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedSwitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.AnimatedContent import androidx.compose.animation.SizeTransform import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.size import androidx.compose.material.minimumInteractiveComponentSize import androidx.compose.material3.Icon import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.domain.model.SwitchType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.switches.CupertinoSwitch import com.t8rin.imagetoolbox.core.ui.widget.switches.CupertinoSwitchDefaults import com.t8rin.imagetoolbox.core.ui.widget.switches.FluentSwitch import com.t8rin.imagetoolbox.core.ui.widget.switches.HyperOSSwitch import com.t8rin.imagetoolbox.core.ui.widget.switches.LiquidGlassSwitch import com.t8rin.imagetoolbox.core.ui.widget.switches.M3Switch import com.t8rin.imagetoolbox.core.ui.widget.switches.OneUISwitch import com.t8rin.imagetoolbox.core.ui.widget.switches.PixelSwitch @Composable fun EnhancedSwitch( checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, thumbIcon: ImageVector? = null, enabled: Boolean = true, colors: SwitchColors? = null, interactionSource: MutableInteractionSource? = null, colorUnderSwitch: Color = Color.Unspecified ) { val switchColors = colors ?: SwitchDefaults.colors( disabledUncheckedThumbColor = MaterialTheme.colorScheme.onSurface .copy(alpha = 0.12f) .compositeOver(MaterialTheme.colorScheme.surface) ) val settingsState = LocalSettingsState.current val haptics = LocalHapticFeedback.current val focus = LocalFocusManager.current LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { val switchModifier = modifier .minimumInteractiveComponentSize() .container( shape = ShapeDefaults.circle, resultPadding = 0.dp, autoShadowElevation = animateDpAsState( if (settingsState.drawSwitchShadows) 1.dp else 0.dp ).value, borderColor = Color.Transparent, isShadowClip = true, isStandaloneContainer = false, color = Color.Transparent ) val switchOnCheckedChange: ((Boolean) -> Unit)? = onCheckedChange?.let { { boolean -> onCheckedChange(boolean) haptics.longPress() focus.clearFocus() } } val thumbContent: (@Composable () -> Unit)? = thumbIcon?.let { { AnimatedContent(thumbIcon) { thumbIcon -> Icon( imageVector = thumbIcon, contentDescription = null, modifier = Modifier.size(SwitchDefaults.IconSize) ) } } } AnimatedContent( targetState = settingsState.switchType, transitionSpec = { fadeIn() togetherWith fadeOut() using SizeTransform(false) } ) { switchType -> when (switchType) { is SwitchType.MaterialYou -> { M3Switch( modifier = modifier, internalModifier = switchModifier, colors = switchColors, checked = checked, enabled = enabled, onCheckedChange = switchOnCheckedChange, interactionSource = interactionSource ) } is SwitchType.Compose -> { Switch( modifier = switchModifier, colors = switchColors, checked = checked, enabled = enabled, onCheckedChange = switchOnCheckedChange, interactionSource = interactionSource, thumbContent = thumbContent ) } is SwitchType.Pixel -> { PixelSwitch( modifier = switchModifier, colors = switchColors, checked = checked, enabled = enabled, onCheckedChange = switchOnCheckedChange, interactionSource = interactionSource ) } is SwitchType.Fluent -> { FluentSwitch( modifier = switchModifier, colors = switchColors, checked = checked, enabled = enabled, onCheckedChange = switchOnCheckedChange, interactionSource = interactionSource ) } is SwitchType.Cupertino -> { CupertinoSwitch( checked = checked, onCheckedChange = switchOnCheckedChange, modifier = switchModifier, enabled = enabled, interactionSource = interactionSource, colors = CupertinoSwitchDefaults.colors() ) } is SwitchType.LiquidGlass -> { LiquidGlassSwitch( checked = checked, onCheckedChange = switchOnCheckedChange, internalModifier = switchModifier, modifier = modifier, enabled = enabled, interactionSource = interactionSource, colors = CupertinoSwitchDefaults.colors(), backgroundColor = colorUnderSwitch ) } is SwitchType.HyperOS -> { HyperOSSwitch( modifier = switchModifier, colors = switchColors.copy( uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerHigh ), checked = checked, enabled = enabled, onCheckedChange = switchOnCheckedChange, interactionSource = interactionSource ) } is SwitchType.OneUI -> { OneUISwitch( modifier = modifier .minimumInteractiveComponentSize(), colors = switchColors.copy( uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerLow ), checked = checked, enabled = enabled, onCheckedChange = switchOnCheckedChange, interactionSource = interactionSource ) } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedToggleButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.ToggleButtonColors import androidx.compose.material3.ToggleButtonDefaults import androidx.compose.material3.ToggleButtonShapes import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun EnhancedToggleButton( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, shapes: ToggleButtonShapes = ToggleButtonDefaults.shapesFor(ButtonDefaults.MinHeight), colors: ToggleButtonColors = ToggleButtonDefaults.toggleButtonColors(), elevation: Dp = 0.dp, border: BorderStroke? = null, contentPadding: PaddingValues = ButtonDefaults.contentPaddingFor(ButtonDefaults.MinHeight), interactionSource: MutableInteractionSource? = null, content: @Composable RowScope.() -> Unit ) { val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } val containerColor = colors.containerColor(enabled, checked) val contentColor = colors.contentColor(enabled, checked) val buttonShape = shapeByInteraction( shape = if (checked) shapes.checkedShape else shapes.shape, pressedShape = shapes.pressedShape, interactionSource = realInteractionSource ) val haptics = LocalHapticFeedback.current val focus = LocalFocusManager.current val scope = rememberCoroutineScope() LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { Surface( checked = checked, onCheckedChange = { haptics.longPress() onCheckedChange(it) scope.launch { delay(200) focus.clearFocus() } }, modifier = modifier.semantics { role = Role.Checkbox }, enabled = enabled, shape = buttonShape, color = containerColor, contentColor = contentColor, shadowElevation = elevation, border = border, interactionSource = realInteractionSource ) { val mergedStyle = LocalTextStyle.current.merge(MaterialTheme.typography.labelLarge) CompositionLocalProvider( LocalContentColor provides contentColor, LocalTextStyle provides mergedStyle ) { Row( modifier = Modifier .defaultMinSize(minHeight = ToggleButtonDefaults.MinHeight) .padding(contentPadding), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content ) } } } } @Stable private fun ToggleButtonColors.containerColor(enabled: Boolean, checked: Boolean): Color { return when { enabled && checked -> checkedContainerColor enabled && !checked -> containerColor else -> disabledContainerColor } } @Stable private fun ToggleButtonColors.contentColor(enabled: Boolean, checked: Boolean): Color { return when { enabled && checked -> checkedContentColor enabled && !checked -> contentColor else -> disabledContentColor } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/EnhancedTopAppBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.safeDrawing import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke @Composable fun EnhancedTopAppBar( title: @Composable () -> Unit, modifier: Modifier = Modifier, navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = EnhancedTopAppBarDefaults.windowInsets, colors: TopAppBarColors = EnhancedTopAppBarDefaults.colors(), scrollBehavior: TopAppBarScrollBehavior? = null, type: EnhancedTopAppBarType = EnhancedTopAppBarType.Normal, drawHorizontalStroke: Boolean = true ) { AnimatedContent( targetState = type, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { when (it) { EnhancedTopAppBarType.Center -> { CenterAlignedTopAppBar( title = title, modifier = modifier.drawHorizontalStroke( enabled = drawHorizontalStroke ), navigationIcon = navigationIcon, actions = actions, windowInsets = windowInsets, colors = colors, scrollBehavior = scrollBehavior ) } EnhancedTopAppBarType.Normal -> { TopAppBar( title = title, modifier = modifier.drawHorizontalStroke( enabled = drawHorizontalStroke ), navigationIcon = navigationIcon, actions = actions, windowInsets = windowInsets, colors = colors, scrollBehavior = scrollBehavior ) } EnhancedTopAppBarType.Medium -> { MediumTopAppBar( title = title, modifier = modifier.drawHorizontalStroke( enabled = drawHorizontalStroke ), navigationIcon = navigationIcon, actions = actions, windowInsets = windowInsets, colors = colors, scrollBehavior = scrollBehavior ) } EnhancedTopAppBarType.Large -> { LargeTopAppBar( title = title, modifier = modifier.drawHorizontalStroke( enabled = drawHorizontalStroke ), navigationIcon = navigationIcon, actions = actions, windowInsets = windowInsets, colors = colors, scrollBehavior = scrollBehavior ) } } } } enum class EnhancedTopAppBarType { Center, Normal, Medium, Large } object EnhancedTopAppBarDefaults { val windowInsets: WindowInsets @Composable get() = WindowInsets.safeDrawing .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) @Composable fun colors( containerColor: Color = MaterialTheme.colorScheme.surfaceContainer, scrolledContainerColor: Color = Color.Unspecified, navigationIconContentColor: Color = Color.Unspecified, titleContentColor: Color = Color.Unspecified, actionIconContentColor: Color = Color.Unspecified, ): TopAppBarColors = TopAppBarDefaults.topAppBarColors( containerColor = containerColor, scrolledContainerColor = scrolledContainerColor, navigationIconContentColor = navigationIconContentColor, titleContentColor = titleContentColor, actionIconContentColor = actionIconContentColor ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/enhanced/derivative/OnlyAllowedSliderItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.enhanced.derivative import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlinx.collections.immutable.toPersistentMap import kotlin.math.roundToInt @Composable fun OnlyAllowedSliderItem( label: String, icon: ImageVector, value: Int, allowed: Collection, maxAllowed: Int = Int.MAX_VALUE, onValueChange: (Int) -> Unit, valueSuffix: String = " px", shape: Shape = ShapeDefaults.large, ) { val availableAllowed = allowed.filter { it < maxAllowed } val effectiveAllowed = availableAllowed.ifEmpty { listOf(allowed.first()) } val clampedValue = value.coerceAtMost(effectiveAllowed.last()) var index by remember(clampedValue, effectiveAllowed) { mutableIntStateOf(effectiveAllowed.indexOf(clampedValue).coerceAtLeast(0)) } LaunchedEffect(maxAllowed) { if (value >= maxAllowed && effectiveAllowed.isNotEmpty()) { onValueChange(effectiveAllowed.last()) } } EnhancedSliderItem( value = index, internalStateTransformation = { it.roundToInt() }, onValueChange = { val newIdx = it.roundToInt().coerceIn(effectiveAllowed.indices) if (newIdx != index) { index = newIdx onValueChange(effectiveAllowed[newIdx]) } }, valueRange = 0f..(effectiveAllowed.lastIndex.toFloat().coerceAtLeast(0f)), steps = (effectiveAllowed.size - 2).coerceAtLeast(0), enabled = effectiveAllowed.size > 1, title = label, valuesPreviewMapping = remember(effectiveAllowed) { buildMap { effectiveAllowed.forEachIndexed { index, value -> put(index.toFloat(), "${value}${valueSuffix}") } }.toPersistentMap() }, icon = icon, isAnimated = false, canInputValue = false, shape = shape, ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/icon_shape/IconShapeContainer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.icon_shape import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.luminance import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.shapes.ArrowShape import com.t8rin.imagetoolbox.core.resources.shapes.BookmarkShape import com.t8rin.imagetoolbox.core.resources.shapes.PentagonShape import com.t8rin.imagetoolbox.core.resources.shapes.SimpleHeartShape import com.t8rin.imagetoolbox.core.settings.presentation.model.IconShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerColor import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerShape import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.container object IconShapeDefaults { val contentColor: Color @Composable get() { val colorScheme = MaterialTheme.colorScheme val localContainer = SafeLocalContainerColor val localContent = LocalContentColor.current val container = containerColor val settingsState = LocalSettingsState.current return remember(colorScheme, localContainer, localContent, container, settingsState) { derivedStateOf { val containerLuma = container.compositeOver(localContainer).luminance() val isLight = containerLuma > 0.2f val baseColor = if (isLight) { Color.Black.blend( color = colorScheme.onPrimaryContainer, fraction = 0.35f ) } else { Color.White.blend( color = colorScheme.primary, fraction = 0.35f ) } if (settingsState.isAmoledMode && settingsState.isNightMode) { baseColor.blend(Color.Black) } else { baseColor } } }.value } val containerColor: Color @Composable get() = takeColorFromScheme { if (it) primary.blend(primaryContainer).copy(0.2f) else primaryContainer.blend(primary).copy(0.35f) } } @Composable fun IconShapeContainer( enabled: Boolean = true, modifier: Modifier = Modifier, iconShape: IconShape? = LocalSettingsState.current.iconShape, contentColor: Color = LocalIconShapeContentColor.current ?: IconShapeDefaults.contentColor, containerColor: Color = LocalIconShapeContainerColor.current ?: IconShapeDefaults.containerColor, content: @Composable (Boolean) -> Unit = {} ) { CompositionLocalProvider( values = arrayOf( LocalContainerShape provides null, LocalContainerColor provides null, LocalContentColor provides if (enabled && contentColor.isSpecified && iconShape != null) { contentColor } else LocalContentColor.current ) ) { AnimatedContent( targetState = remember(iconShape) { derivedStateOf { iconShape?.takeOrElseFrom(IconShape.entries) } }.value, modifier = modifier ) { iconShapeAnimated -> Box( modifier = if (enabled && iconShapeAnimated != null) { Modifier.container( shape = iconShapeAnimated.shape, color = containerColor, autoShadowElevation = 0.65.dp, resultPadding = iconShapeAnimated.padding, composeColorOnTopOfBackground = false, isShadowClip = true ) } else Modifier, contentAlignment = Alignment.Center ) { Box( modifier = if (enabled && iconShapeAnimated != null) { Modifier .size(iconShapeAnimated.iconSize) .offset( y = when (iconShapeAnimated.shape) { PentagonShape -> 2.dp BookmarkShape -> (-1).dp SimpleHeartShape -> (-1.5).dp ArrowShape -> 2.dp else -> 0.dp } ) } else Modifier ) { content(iconShapeAnimated == null) } } } } } val LocalIconShapeContentColor = compositionLocalOf { null } val LocalIconShapeContainerColor = compositionLocalOf { null } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatioSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CropFree import androidx.compose.material.icons.outlined.DashboardCustomize import androidx.compose.material.icons.outlined.Image import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.model.CropAspectRatio import com.t8rin.cropper.util.createRectShape import com.t8rin.cropper.widget.AspectRatioSelectionCard import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.utils.ifCasts import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import kotlin.math.abs @Composable fun AspectRatioSelector( modifier: Modifier = Modifier, selectedAspectRatio: DomainAspectRatio? = DomainAspectRatio.Free, unselectedCardColor: Color = MaterialTheme.colorScheme.surfaceContainerLowest, contentPadding: PaddingValues = PaddingValues( start = 16.dp, top = 4.dp, bottom = 16.dp, end = 16.dp + WindowInsets .navigationBars .asPaddingValues() .calculateEndPadding(LocalLayoutDirection.current) ), title: @Composable ColumnScope.() -> Unit = { Text( text = stringResource(id = R.string.aspect_ratio), modifier = Modifier .padding( start = 8.dp, end = 8.dp, top = 16.dp, bottom = 8.dp ), fontWeight = FontWeight.Medium ) }, enableFadingEdges: Boolean = true, onAspectRatioChange: (DomainAspectRatio, AspectRatio) -> Unit, color: Color = Color.Unspecified, shape: Shape = ShapeDefaults.extraLarge, aspectRatios: List = aspectRatios() ) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier .container( color = color, shape = shape, resultPadding = 0.dp ) .clearFocusOnTap() ) { title() val listState = rememberLazyListState() LazyRow( state = listState, horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), contentPadding = contentPadding, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fadingEdges( scrollableState = listState, enabled = enableFadingEdges ), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = aspectRatios, key = { _, a -> a.toCropAspectRatio( original = "0", free = "1", custom = "2" ).title } ) { index, item -> val selected = (item == selectedAspectRatio) .or( item is DomainAspectRatio.Custom && selectedAspectRatio is DomainAspectRatio.Custom ) val cropAspectRatio = item.toCropAspectRatio( original = stringResource(R.string.original), free = stringResource(R.string.free), custom = stringResource(R.string.custom) ) val isNumeric by remember(item) { derivedStateOf { item != DomainAspectRatio.Original && item != DomainAspectRatio.Free && item !is DomainAspectRatio.Custom } } if (isNumeric) { AspectRatioSelectionCard( modifier = Modifier .width(90.dp) .container( resultPadding = 0.dp, color = animateColorAsState( targetValue = if (selected) { MaterialTheme.colorScheme.primaryContainer } else unselectedCardColor, ).value, borderColor = if (selected) MaterialTheme.colorScheme.onPrimaryContainer.copy( 0.7f ) else MaterialTheme.colorScheme.outlineVariant() ) .hapticsClickable { onAspectRatioChange( aspectRatios[index], cropAspectRatio.aspectRatio ) } .padding(start = 12.dp, top = 12.dp, end = 12.dp, bottom = 2.dp), contentColor = Color.Transparent, color = MaterialTheme.colorScheme.onSurface, cropAspectRatio = cropAspectRatio ) } else { Box( modifier = Modifier .height(106.dp) .container( resultPadding = 0.dp, color = animateColorAsState( targetValue = if (selected) { MaterialTheme.colorScheme.primaryContainer } else unselectedCardColor, ).value, borderColor = takeColorFromScheme { if (selected) onPrimaryContainer.copy(0.7f) else outlineVariant() } ) .hapticsClickable { if (!item::class.isInstance(selectedAspectRatio)) { onAspectRatioChange( aspectRatios[index], cropAspectRatio.aspectRatio ) } } .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), contentAlignment = Alignment.Center ) { Column( horizontalAlignment = Alignment.CenterHorizontally ) { when (item) { is DomainAspectRatio.Original -> { Icon( imageVector = Icons.Outlined.Image, contentDescription = null ) } is DomainAspectRatio.Free -> { Icon( imageVector = Icons.Outlined.CropFree, contentDescription = null ) } else -> { Icon( imageVector = Icons.Outlined.DashboardCustomize, contentDescription = null ) } } Text( text = cropAspectRatio.title, fontSize = 14.sp, lineHeight = 14.sp ) } } } } } AnimatedVisibility(visible = selectedAspectRatio is DomainAspectRatio.Custom) { Row( Modifier .padding(8.dp) .container( shape = ShapeDefaults.extraLarge, color = unselectedCardColor ) ) { var tempWidth by remember { mutableStateOf(selectedAspectRatio?.widthProportion?.toString() ?: "1") } var tempHeight by remember { mutableStateOf(selectedAspectRatio?.heightProportion?.toString() ?: "1") } RoundedTextField( value = tempWidth, onValueChange = { value -> tempWidth = value val width = abs(value.toFloatOrNull() ?: 0f).coerceAtLeast(1f) selectedAspectRatio.ifCasts { aspect -> onAspectRatioChange( aspect.copy( widthProportion = width ), AspectRatio( (width / aspect.heightProportion).takeIf { !it.isNaN() } ?: 1f ) ) } }, shape = ShapeDefaults.smallStart, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = { Text(stringResource(R.string.width, " ")) }, supportingText = abs(tempWidth.toFloatOrNull() ?: 0f).takeIf { it < 1f }?.let { { Text(stringResource(R.string.minimum_value_is, 1)) } }, modifier = Modifier .weight(1f) .padding( start = 8.dp, top = 8.dp, bottom = 8.dp, end = 2.dp ) ) RoundedTextField( value = tempHeight, onValueChange = { value -> tempHeight = value val height = abs(value.toFloatOrNull() ?: 1f).coerceAtLeast(1f) selectedAspectRatio.ifCasts { aspect -> onAspectRatioChange( aspect.copy( heightProportion = height ), AspectRatio( (aspect.widthProportion / height).takeIf { !it.isNaN() } ?: 1f ) ) } }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), shape = ShapeDefaults.smallEnd, label = { Text(stringResource(R.string.height, " ")) }, supportingText = abs(tempHeight.toFloatOrNull() ?: 0f).takeIf { it < 1f }?.let { { Text(stringResource(R.string.minimum_value_is, 1)) } }, modifier = Modifier .weight(1f) .padding( start = 2.dp, top = 8.dp, bottom = 8.dp, end = 8.dp ), ) } } } } fun DomainAspectRatio.toCropAspectRatio( original: String, free: String, custom: String ): CropAspectRatio = when (this) { is DomainAspectRatio.Original -> { CropAspectRatio( title = original, shape = createRectShape(AspectRatio.Original), aspectRatio = AspectRatio.Original ) } is DomainAspectRatio.Free -> { CropAspectRatio( title = free, shape = createRectShape(AspectRatio.Original), aspectRatio = AspectRatio.Original ) } is DomainAspectRatio.Custom -> { CropAspectRatio( title = custom, shape = createRectShape(AspectRatio(value)), aspectRatio = AspectRatio(value) ) } else -> { val width = widthProportion.toString() .trimTrailingZero() val height = heightProportion.toString() .trimTrailingZero() CropAspectRatio( title = "$width:$height", shape = createRectShape(AspectRatio(value)), aspectRatio = AspectRatio(value) ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AspectRatios.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio @Composable fun aspectRatios() = remember { DomainAspectRatio.defaultList } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/AutoFilePicker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost @Composable fun AutoFilePicker( onAutoPick: () -> Unit, isPickedAlready: Boolean ) { val settingsState = LocalSettingsState.current var picked by rememberSaveable(isPickedAlready) { mutableStateOf(isPickedAlready) } LaunchedEffect(Unit) { if (settingsState.skipImagePicking && !picked) { runCatching { onAutoPick() picked = true }.onFailure(AppToastHost::handleFileSystemFailure) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/BadImageWidget.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun BadImageWidget() { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .container() .padding(16.dp) ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/HistogramChart.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.graphics.Bitmap import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.histogram.HistogramType import com.t8rin.histogram.ImageHistogram import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun HistogramChart( model: Any?, modifier: Modifier, initialType: HistogramType = HistogramType.RGB, onSwapType: ((HistogramType) -> HistogramType)? = { type -> when (type) { HistogramType.RGB -> HistogramType.Brightness HistogramType.Brightness -> HistogramType.Camera HistogramType.Camera -> HistogramType.RGB } }, harmonizationColor: Color = MaterialTheme.colorScheme.primary, linesThickness: Dp = 0.5.dp, bordersColor: Color = MaterialTheme.colorScheme.outline, bordersShape: Shape = ShapeDefaults.extraSmall ) { when (model) { is Bitmap -> { ImageHistogram( image = model, modifier = modifier, initialType = initialType, onSwapType = onSwapType, harmonizationColor = harmonizationColor, linesThickness = linesThickness, bordersColor = bordersColor, bordersShape = bordersShape ) } else -> { ImageHistogram( model = model, modifier = modifier, initialType = initialType, onSwapType = onSwapType, harmonizationColor = harmonizationColor, linesThickness = linesThickness, bordersColor = bordersColor, bordersShape = bordersShape ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageContainer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent import androidx.compose.animation.SizeTransform import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator @Composable fun ImageContainer( modifier: Modifier = Modifier, imageInside: Boolean, showOriginal: Boolean, previewBitmap: Bitmap?, originalBitmap: Bitmap?, isLoading: Boolean, shouldShowPreview: Boolean, animatePreviewChange: Boolean = true, containerModifier: Modifier = Modifier.fillMaxSize() ) { val generatePreviews = LocalSettingsState.current.generatePreviews if (animatePreviewChange) { AnimatedContent( modifier = containerModifier, targetState = remember(previewBitmap, isLoading, showOriginal) { derivedStateOf { Triple(previewBitmap, isLoading, showOriginal) } }.value, transitionSpec = { fadeIn() togetherWith fadeOut() using SizeTransform(false) } ) { (bmp, loading, showOrig) -> Box( modifier = modifier, contentAlignment = Alignment.Center ) { Box( contentAlignment = Alignment.Center, modifier = Modifier.then( if (!imageInside) { Modifier.padding( bottom = WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding() ) } else Modifier ) ) { if (showOrig) { SimplePicture( bitmap = originalBitmap, loading = loading ) } else { SimplePicture( loading = loading, bitmap = bmp, visible = shouldShowPreview ) if (!loading && (bmp == null || !shouldShowPreview) || !generatePreviews) { BadImageWidget() } } if (loading) EnhancedLoadingIndicator() } } } } else { AnimatedContent( modifier = containerModifier, targetState = remember(isLoading, showOriginal) { derivedStateOf { isLoading to showOriginal } }.value, transitionSpec = { fadeIn() togetherWith fadeOut() using SizeTransform(false) } ) { (loading, showOrig) -> Box( modifier = modifier, contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Box( contentAlignment = Alignment.Center, modifier = Modifier.then( if (!imageInside) { Modifier.padding( bottom = WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding() ) } else Modifier ) ) { previewBitmap?.let { if (!showOrig) { SimplePicture( bitmap = it, loading = loading ) } else { SimplePicture( loading = loading, bitmap = originalBitmap, visible = true ) } } ?: if (!generatePreviews) { BadImageWidget() } else Unit if (previewBitmap == null && loading) { EnhancedLoadingIndicator() } } } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageCounter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CornerSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ChangeCircle import androidx.compose.material3.Icon import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun ImageCounter( imageCount: Int?, onRepick: () -> Unit, modifier: Modifier = Modifier ) { AnimatedVisibility( modifier = modifier.padding(bottom = 16.dp), visible = imageCount != null, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .container(shape = ShapeDefaults.circle) .padding(start = 3.dp), horizontalArrangement = Arrangement.spacedBy((-1).dp) ) { LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { EnhancedButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(0.3f), contentColor = MaterialTheme.colorScheme.onTertiaryContainer.copy(0.9f), onClick = { if ((imageCount ?: 0) > 1) onRepick() }, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f, onTopOf = MaterialTheme .colorScheme .tertiaryContainer .copy(0.1f), ), isShadowClip = true, shape = AutoCornersShape( topStart = CornerSize(50), topEnd = CornerSize(4.dp), bottomStart = CornerSize(50), bottomEnd = CornerSize(4.dp), ) ) { Text(stringResource(R.string.images, imageCount ?: 0L)) } EnhancedIconButton( onClick = { if ((imageCount ?: 0) > 1) onRepick() }, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f, onTopOf = MaterialTheme .colorScheme .tertiaryContainer .copy(0.1f), ), containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(0.3f), contentColor = MaterialTheme.colorScheme.onTertiaryContainer.copy(0.9f), isShadowClip = true, shape = AutoCornersShape( topEnd = CornerSize(50), topStart = CornerSize(4.dp), bottomEnd = CornerSize(50), bottomStart = CornerSize(4.dp), ) ) { Icon( imageVector = Icons.Rounded.ChangeCircle, contentDescription = stringResource(R.string.change_preview) ) } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageHeaderState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image data class ImageHeaderState( val position: Int = 1, val isBlocked: Boolean = true ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageNotPickedWidget.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.ui.widget.image import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.FileOpen import androidx.compose.material.icons.twotone.Image import androidx.compose.material3.Icon import androidx.compose.material3.MaterialShapes import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min import androidx.graphics.shapes.Morph import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.shapes.MorphShape import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.utils.animation.springySpec import com.t8rin.imagetoolbox.core.ui.utils.provider.currentScreenTwoToneIcon import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filterIsInstance @Composable fun ImageNotPickedWidget( onPickImage: () -> Unit, modifier: Modifier = Modifier, text: String = stringResource(R.string.pick_image), containerColor: Color = Color.Unspecified, ) { SourceNotPickedWidget( onClick = onPickImage, modifier = modifier, text = text, icon = currentScreenTwoToneIcon(Icons.TwoTone.Image), containerColor = containerColor ) } @Composable fun FileNotPickedWidget( onPickFile: () -> Unit, modifier: Modifier = Modifier, text: String = stringResource(R.string.pick_file_to_start), containerColor: Color = Color.Unspecified, ) { SourceNotPickedWidget( onClick = onPickFile, modifier = modifier, text = text, icon = currentScreenTwoToneIcon(Icons.TwoTone.FileOpen), containerColor = containerColor ) } @Composable fun SourceNotPickedWidget( modifier: Modifier = Modifier, onClick: (() -> Unit)?, text: String, icon: ImageVector, maxLines: Int = 3, containerColor: Color = Color.Unspecified, ) { BoxWithConstraints( contentAlignment = Alignment.Center ) { val targetSize = min(min(maxWidth, maxHeight), 300.dp) Column( modifier = modifier .animateContentSizeNoClip() .padding(0.5.dp) .container(color = containerColor), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(Modifier.height(16.dp)) ClickableActionIcon( icon = icon, onClick = onClick, modifier = Modifier.size(targetSize / 3) ) AutoSizeText( text = text, modifier = Modifier.padding(16.dp), textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onSurfaceVariant, key = { it.length }, maxLines = maxLines ) } } } @Composable fun ClickableActionIcon( icon: ImageVector, onClick: (() -> Unit)?, modifier: Modifier = Modifier ) { val interactionSource = remember { MutableInteractionSource() } val pressed by interactionSource.collectIsPressedAsState() val haptics = LocalHapticFeedback.current LaunchedEffect(interactionSource, haptics) { interactionSource.interactions.filterIsInstance().collectLatest { haptics.longPress() } } val percentage = animateFloatAsState( targetValue = if (pressed) 1f else 0.2f, animationSpec = springySpec() ) val scale by animateFloatAsState( if (pressed) 1f else 1.1f ) val morph = remember { Morph( start = MaterialShapes.Cookie4Sided, end = MaterialShapes.Square ) } val shape = remember { MorphShape( morph = morph, percentage = { percentage.value } ) } Box( modifier = modifier .size(100.dp) .scale(scale) .container( shape = shape, resultPadding = 0.dp, color = MaterialTheme.colorScheme.mixedContainer.copy(0.8f) ) .then( if (onClick != null) { Modifier.hapticsClickable( onClick = onClick, interactionSource = interactionSource, indication = LocalIndication.current, enableHaptics = false ) } else Modifier ) .scale(1f / scale) ) { AnimatedContent( targetState = icon, modifier = Modifier.fillMaxSize() ) { imageVector -> Icon( imageVector = imageVector, contentDescription = null, modifier = Modifier .fillMaxSize() .padding(12.dp), tint = MaterialTheme.colorScheme.onMixedContainer ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.gestures.AnchoredDraggableDefaults import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.snapTo import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Share import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.resources.icons.EditAlt import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.onPrimaryContainerFixed import com.t8rin.imagetoolbox.core.ui.theme.primaryContainerFixed import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.toShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.withLayoutCorners import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.modalsheet.FullscreenPopup import kotlinx.coroutines.delay import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.toggleScale import net.engawapg.lib.zoomable.zoomable import kotlin.math.roundToInt @Composable fun ImagePager( visible: Boolean, selectedUri: Uri?, uris: List?, onNavigate: (Screen) -> Unit, onUriSelected: (Uri?) -> Unit, onShare: (Uri) -> Unit, onDismiss: () -> Unit ) { FullscreenPopup { var predictiveBackProgress by remember { mutableFloatStateOf(0f) } val animatedPredictiveBackProgress by animateFloatAsState(predictiveBackProgress) val scale = (1f - animatedPredictiveBackProgress).coerceAtLeast(0.75f) LaunchedEffect(predictiveBackProgress, visible) { if (!visible && predictiveBackProgress != 0f) { delay(600) predictiveBackProgress = 0f } } AnimatedVisibility( visible = visible, modifier = Modifier.fillMaxSize(), enter = fadeIn(tween(500)), exit = fadeOut(tween(500)) ) { val density = LocalDensity.current val screenHeight = LocalScreenSize.current.height + WindowInsets.systemBars.asPaddingValues() .let { it.calculateTopPadding() + it.calculateBottomPadding() } val anchors = with(density) { DraggableAnchors { true at 0f false at -screenHeight.toPx() } } val draggableState = remember(anchors) { AnchoredDraggableState( initialValue = true, anchors = anchors ) } LaunchedEffect(draggableState.settledValue) { if (!draggableState.settledValue) { onDismiss() delay(600) draggableState.snapTo(true) } } var wantToEdit by rememberSaveable { mutableStateOf(false) } val pagerState = rememberPagerState( initialPage = selectedUri?.let { uris?.indexOf(it) }?.takeIf { it >= 0 } ?: 0, pageCount = { uris?.size ?: 0 } ) LaunchedEffect(pagerState.currentPage) { onUriSelected( uris?.getOrNull(pagerState.currentPage) ) } val progress by remember(draggableState) { derivedStateOf { draggableState.progress( from = false, to = true ) } } Box( modifier = Modifier .fillMaxSize() .withLayoutCorners { corners -> graphicsLayer { scaleX = scale scaleY = scale shape = corners.toShape(animatedPredictiveBackProgress) clip = true } } .background( MaterialTheme.colorScheme.scrim.copy(alpha = 0.6f * progress) ) ) { val imageErrorPages = remember { mutableStateListOf() } var hideControls by remember(animatedPredictiveBackProgress) { mutableStateOf(false) } HorizontalPager( state = pagerState, modifier = Modifier.fillMaxSize(), beyondViewportPageCount = 5, pageSpacing = if (pagerState.pageCount > 1) 16.dp else 0.dp ) { page -> Box( modifier = Modifier.fillMaxSize() ) { val zoomState = rememberZoomState(20f) Picture( showTransparencyChecker = false, model = uris?.getOrNull(page), modifier = Modifier .fillMaxSize() .clipToBounds() .systemBarsPadding() .displayCutoutPadding() .offset { IntOffset( x = 0, y = -draggableState .requireOffset() .roundToInt(), ) } .anchoredDraggable( state = draggableState, enabled = zoomState.scale < 1.01f && !pagerState.isScrollInProgress, orientation = Orientation.Vertical, reverseDirection = true, flingBehavior = AnchoredDraggableDefaults.flingBehavior( animationSpec = tween(500), state = draggableState ) ) .zoomable( zoomEnabled = !imageErrorPages.contains(page), zoomState = zoomState, onTap = { hideControls = !hideControls }, onDoubleTap = { zoomState.toggleScale( targetScale = 5f, position = it ) } ), enableUltraHDRSupport = true, contentScale = ContentScale.Fit, shape = RectangleShape, onSuccess = { imageErrorPages.remove(page) }, onError = { imageErrorPages.add(page) }, error = { Box( contentAlignment = Alignment.Center, modifier = Modifier.background( takeColorFromScheme { isNightMode -> errorContainer.copy( if (isNightMode) 0.25f else 1f ).compositeOver(surface) } ) ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, modifier = Modifier.fillMaxSize(0.5f), tint = MaterialTheme.colorScheme.onErrorContainer.copy(0.8f) ) } } ) } } val showTopBar by remember(draggableState, hideControls) { derivedStateOf { draggableState.offset == 0f && !hideControls } } val selectedUriFilename = selectedUri?.let { rememberFilename(it) } val selectedUriFileSize = selectedUri?.let { rememberFileSize(it) } val showBottomHist = pagerState.currentPage !in imageErrorPages val showBottomBar by remember(draggableState, showBottomHist, hideControls) { derivedStateOf { draggableState.offset == 0f && showBottomHist && !hideControls } } AnimatedVisibility( visible = showTopBar, modifier = Modifier.fillMaxWidth(), enter = fadeIn() + slideInVertically(), exit = fadeOut() + slideOutVertically() ) { EnhancedTopAppBar( colors = EnhancedTopAppBarDefaults.colors( containerColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.5f) ), type = EnhancedTopAppBarType.Center, drawHorizontalStroke = false, title = { uris?.size?.takeIf { it > 1 }?.let { Text( text = "${pagerState.currentPage + 1}/$it", modifier = Modifier .padding(vertical = 4.dp, horizontal = 12.dp), color = White ) } }, actions = { AnimatedVisibility( visible = !uris.isNullOrEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { Row(verticalAlignment = Alignment.CenterVertically) { EnhancedIconButton( onClick = { selectedUri?.let { onShare(it) } } ) { Icon( imageVector = Icons.Rounded.Share, contentDescription = stringResource(R.string.share), tint = White ) } EnhancedIconButton( onClick = { wantToEdit = true } ) { Icon( imageVector = Icons.Rounded.EditAlt, contentDescription = stringResource(R.string.edit), tint = White ) } } } }, navigationIcon = { AnimatedVisibility(!uris.isNullOrEmpty()) { EnhancedIconButton( onClick = { onDismiss() onUriSelected(null) } ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = White ) } } } ) } AnimatedVisibility( visible = showBottomBar, modifier = Modifier.align(Alignment.BottomEnd), enter = fadeIn() + slideInVertically { it / 2 }, exit = fadeOut() + slideOutVertically { it / 2 } ) { Row( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.scrim.copy(0.5f)) .navigationBarsPadding() .padding( WindowInsets.displayCutout .only( WindowInsetsSides.Horizontal ) .asPaddingValues() ) .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Row( modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically ) { selectedUriFilename?.let { Text( text = it, modifier = Modifier .animateContentSizeNoClip() .weight(1f, false), color = White, style = MaterialTheme.typography.labelLarge, fontSize = 13.sp ) } selectedUriFileSize ?.takeIf { it > 0 } ?.let { size -> Spacer(Modifier.width(8.dp)) Text( text = rememberHumanFileSize(size), modifier = Modifier .animateContentSizeNoClip() .background( color = MaterialTheme.colorScheme.primaryContainerFixed, shape = ShapeDefaults.circle ) .padding(horizontal = 8.dp, vertical = 4.dp), color = MaterialTheme.colorScheme.onPrimaryContainerFixed, style = MaterialTheme.typography.labelMedium ) } MetadataPreviewButton( uri = selectedUri, name = { selectedUriFilename }, fileSize = { selectedUriFileSize?.let { humanFileSize(it) } } ) } Spacer(Modifier.width(16.dp)) HistogramChart( model = uris?.getOrNull(pagerState.currentPage) ?: Uri.EMPTY, modifier = Modifier .height(50.dp) .width(90.dp), bordersColor = Color.White ) } } } ProcessImagesPreferenceSheet( uris = listOfNotNull(selectedUri), visible = wantToEdit, onDismiss = { wantToEdit = false }, onNavigate = onNavigate ) PredictiveBackObserver( onProgress = { predictiveBackProgress = it / 6f }, onClean = { isCompleted -> if (isCompleted) { onDismiss() onUriSelected(null) delay(400) } predictiveBackProgress = 0f }, enabled = visible ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagePreviewGrid.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBars import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors @Composable fun ImagePreviewGrid( data: List?, onNavigate: (Screen) -> Unit, imageFrames: ImageFrames?, onFrameSelectionChange: (ImageFrames) -> Unit, onAddImages: ((List) -> Unit)?, onShareImage: (Uri) -> Unit, onRemove: ((Uri) -> Unit)?, verticalCellSize: Dp = 120.dp, horizontalCellSize: Dp = verticalCellSize, contentPadding: PaddingValues? = null, isSelectable: Boolean = false, initialShowImagePreviewDialog: Boolean = false, modifier: Modifier = Modifier ) { val imagePicker = rememberImagePicker { uriList: List -> val uris = (data ?: emptyList()).toMutableList() uriList.forEach { if (it !in uris) uris.add(it) } onAddImages?.invoke(uris) } var selectedUri by rememberSaveable(initialShowImagePreviewDialog) { mutableStateOf( if (initialShowImagePreviewDialog) data?.firstOrNull()?.toString() else null ) } val cutout = WindowInsets.displayCutout.asPaddingValues() val direction = LocalLayoutDirection.current var isSelectionMode by rememberSaveable { mutableStateOf(false) } var isSelectionModePrevious by rememberSaveable { mutableStateOf(false) } ImagesPreviewWithSelection( imageUris = data.orEmpty(), imageFrames = imageFrames ?: ImageFrames.ManualSelection(emptyList()), modifier = modifier, onFrameSelectionChange = { if (it.isEmpty()) { isSelectionMode = false } onFrameSelectionChange(it) }, isPortrait = false, isLoadingImages = false, isAutoExpandLayout = false, onError = { onRemove?.invoke(it.toUri()) }, contentPadding = contentPadding ?: PaddingValues( bottom = 88.dp + WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding(), top = 12.dp, end = 12.dp + cutout.calculateEndPadding(direction), start = 12.dp + cutout.calculateStartPadding(direction) ), isSelectionMode = isSelectionMode, onItemClick = { index -> if (!isSelectionModePrevious) { data?.get(index)?.let { selectedUri = it.toString() } } isSelectionModePrevious = isSelectionMode }, onItemLongClick = { isSelectionModePrevious = true isSelectionMode = true }, verticalCellSize = verticalCellSize, horizontalCellSize = horizontalCellSize, aboveImageContent = {}, isAboveImageScrimEnabled = isSelectionMode, endAdditionalItem = if (!isSelectionMode && !data.isNullOrEmpty() && onAddImages != null) { { Box( modifier = Modifier .fillMaxSize() .aspectRatio(1f) .container( shape = MaterialTheme.shapes.extraSmall, resultPadding = 0.dp, color = MaterialTheme.colorScheme.tertiaryContainer.copy(0.3f) ) .hapticsClickable(onClick = imagePicker::pickImage), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_images), modifier = Modifier.fillMaxSize(0.4f), tint = MaterialTheme.colorScheme.onTertiaryContainer.copy(0.8f) ) } } } else null, isContentAlignToCenter = false, enableSelection = isSelectable ) ImagePager( visible = !selectedUri.isNullOrEmpty() && !data.isNullOrEmpty(), selectedUri = selectedUri?.toUri(), uris = data, onUriSelected = { selectedUri = it?.toString() }, onShare = onShareImage, onDismiss = { selectedUri = null }, onNavigate = { selectedUri = null onNavigate(it) } ) AutoContentBasedColors( model = selectedUri ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImageStickyHeader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Lock import androidx.compose.material.icons.rounded.LockOpen import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.gigamole.composefadingedges.FadingEdgesGravity import com.t8rin.gesture.PointerRequisite import com.t8rin.gesture.detectPointerTransformGestures import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSlider import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import kotlinx.coroutines.delay import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.snapBackZoomable import kotlin.math.abs fun LazyListScope.imageStickyHeader( visible: Boolean, imageState: ImageHeaderState, internalHeight: Dp, onStateChange: (ImageHeaderState) -> Unit, backgroundColor: Color = Color.Unspecified, padding: Dp = 20.dp, isControlsVisibleIndefinitely: Boolean = false, onGloballyPositioned: (LayoutCoordinates) -> Unit = {}, imageModifier: Modifier = Modifier, imageBlock: @Composable () -> Unit, ) { val content = @Composable { var controlsVisible by rememberSaveable { mutableStateOf(true) } val sliderInteractionSource = remember { MutableInteractionSource() } val interactions by sliderInteractionSource.interactions.collectAsState(initial = null) LaunchedEffect(controlsVisible, interactions) { if (controlsVisible && !(interactions is DragInteraction.Start || interactions is PressInteraction.Press)) { delay(2500) controlsVisible = isControlsVisibleIndefinitely } } val screenWidth = LocalScreenSize.current.width val density = LocalDensity.current Column( modifier = Modifier .layout { measurable, constraints -> val result = measurable.measure( constraints.copy( maxWidth = with(density) { screenWidth.roundToPx() }.coerceAtLeast(constraints.minWidth) ) ) layout(result.measuredWidth, result.measuredHeight) { result.place(0, 0) } } .tappable(!isControlsVisibleIndefinitely) { controlsVisible = true } .onGloballyPositioned(onGloballyPositioned) .animateContentSizeNoClip() ) { val color = if (backgroundColor.isSpecified) { backgroundColor } else MaterialTheme.colorScheme.surface.copy(alpha = 0.85f) val settingsState = LocalSettingsState.current Column( modifier = Modifier .fillMaxWidth() .height(internalHeight) .fadingEdges( scrollableState = null, isVertical = true, length = animateDpAsState( if (imageState.position == 4) 0.dp else 16.dp ).value, gravity = FadingEdgesGravity.End ) .background(color) .padding( start = padding, end = padding, top = padding, bottom = animateDpAsState( if (controlsVisible) padding / 2 else padding ).value ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { val zoomState = rememberZoomState() Box( modifier = Modifier .weight(1f, false) .then(imageModifier) .then( if (!isControlsVisibleIndefinitely) { Modifier.pointerInput(Unit) { var touchPointerOffset = Offset.Zero detectPointerTransformGestures( consume = false, onGestureEnd = { val diff = touchPointerOffset - it.position if (abs(diff.x) < 10f && abs(diff.y) < 10f) { controlsVisible = true it.consume() } }, requisite = PointerRequisite.EqualTo, onGestureStart = { touchPointerOffset = it.position }, onGesture = { _, _, _, _, _, _ -> } ) } } else Modifier ) .snapBackZoomable(zoomState = zoomState) ) { imageBlock() } BoxAnimatedVisibility( visible = controlsVisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Row( modifier = Modifier .fillMaxWidth(0.7f) .padding(vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { EnhancedSlider( interactionSource = sliderInteractionSource, modifier = Modifier .weight(1f) .padding(horizontal = 10.dp), value = imageState.position.toFloat(), onValueChange = { controlsVisible = true onStateChange(imageState.copy(position = it.toInt())) }, colors = SliderDefaults.colors( inactiveTrackColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.tertiaryContainer ).copy(0.5f), activeTrackColor = MaterialTheme.colorScheme.tertiary.copy(0.5f), thumbColor = if (settingsState.sliderType == SliderType.Fancy) { MaterialTheme.colorScheme.onTertiary } else MaterialTheme.colorScheme.tertiary, activeTickColor = MaterialTheme.colorScheme.tertiaryContainer, inactiveTickColor = MaterialTheme.colorScheme.tertiary.copy(0.5f) ), steps = 3, valueRange = 0f..4f ) EnhancedIconButton( onClick = { controlsVisible = true onStateChange(imageState.copy(isBlocked = !imageState.isBlocked)) }, containerColor = takeColorFromScheme { if (imageState.isBlocked) { tertiary.copy(0.8f) } else { surfaceVariant.copy(0.5f) } }, contentColor = takeColorFromScheme { if (imageState.isBlocked) { onTertiary } else { onSurfaceVariant } } ) { AnimatedContent(targetState = imageState.isBlocked) { blocked -> if (blocked) { Icon( imageVector = Icons.Rounded.Lock, contentDescription = "Lock Image" ) } else { Icon( imageVector = Icons.Rounded.LockOpen, contentDescription = "Unlock Image" ) } } } } } } } } if (!imageState.isBlocked) { item( key = "stickyHeader", contentType = "stickyHeader" ) { AnimatedContent( targetState = visible, modifier = Modifier.fillMaxWidth() ) { if (it) content() else Spacer(Modifier) } } } else { stickyHeader( key = "stickyHeader", contentType = "stickyHeader" ) { AnimatedContent( targetState = visible, modifier = Modifier.fillMaxWidth() ) { if (it) content() else Spacer(Modifier) } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/ImagesPreviewWithSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.updateTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridItemScope import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.RadioButtonUnchecked import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.layout import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFileExtension import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.advancedShadow import com.t8rin.imagetoolbox.core.ui.widget.modifier.dragHandler @Composable fun ImagesPreviewWithSelection( imageUris: List, imageFrames: ImageFrames, onFrameSelectionChange: (ImageFrames) -> Unit, isPortrait: Boolean, isLoadingImages: Boolean, spacing: Dp = 8.dp, onError: (String) -> Unit = {}, isAutoExpandLayout: Boolean = true, verticalCellSize: Dp = 90.dp, horizontalCellSize: Dp = 120.dp, contentPadding: PaddingValues = PaddingValues(12.dp), isSelectionMode: Boolean = true, isAboveImageScrimEnabled: Boolean = true, onItemClick: (Int) -> Unit = {}, onItemLongClick: (Int) -> Unit = {}, aboveImageContent: @Composable BoxScope.(index: Int) -> Unit = { index -> Text( text = (index + 1).toString(), color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Medium ) }, showExtension: Boolean = true, endAdditionalItem: (@Composable LazyGridItemScope.() -> Unit)? = null, isContentAlignToCenter: Boolean = true, contentScale: ContentScale = ContentScale.Crop, enableSelection: Boolean = true, modifier: Modifier? = null ) { val state = rememberLazyGridState() val getUris: () -> Set = { val indexes = imageFrames .getFramePositions(imageUris.size) .map { it - 1 } imageUris.mapIndexedNotNull { index, _ -> if (index in indexes) index + 1 else null }.toSet() } val selectedItems by remember(imageUris, imageFrames) { mutableStateOf(getUris()) } val privateSelectedItems = remember { mutableStateOf(selectedItems) } LaunchedEffect(imageFrames, selectedItems) { if (imageFrames !is ImageFrames.ManualSelection || selectedItems.isEmpty()) { privateSelectedItems.value = selectedItems } } val screenWidth = LocalScreenSize.current.width val gridModifier = modifier ?: if (isPortrait) { Modifier.height( (130.dp * imageUris.size).coerceAtMost(420.dp) ) } else { Modifier }.then( if (isAutoExpandLayout) { Modifier.layout { measurable, constraints -> val result = measurable.measure( if (isPortrait) { constraints.copy( maxWidth = screenWidth.roundToPx() ) } else { constraints.copy( maxHeight = constraints.maxHeight + 48.dp.roundToPx() ) } ) layout(result.measuredWidth, result.measuredHeight) { result.place(0, 0) } } } else Modifier ) Box(modifier = gridModifier) { if (isPortrait) { LazyHorizontalGrid( rows = GridCells.Adaptive(horizontalCellSize), state = state, modifier = Modifier .fillMaxSize() .dragHandler( key = enableSelection to imageUris, lazyGridState = state, isVertical = false, selectedItems = privateSelectedItems, onSelectionChange = { onFrameSelectionChange(ImageFrames.ManualSelection(it.toList())) }, tapEnabled = isSelectionMode, onTap = onItemClick, onLongTap = onItemLongClick ), verticalArrangement = Arrangement.spacedBy( space = spacing, alignment = if (isContentAlignToCenter) Alignment.CenterVertically else Alignment.Top ), horizontalArrangement = Arrangement.spacedBy( space = spacing, alignment = if (isContentAlignToCenter) Alignment.CenterHorizontally else Alignment.Start ), contentPadding = contentPadding, flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = imageUris, key = { index, uri -> "$uri-${index + 1}" } ) { index, uri -> val selected by remember(index, privateSelectedItems.value) { derivedStateOf { index + 1 in privateSelectedItems.value } } ImageItem( selected = selected, modifier = Modifier .fillMaxSize() .aspectRatio(1f), index = index, uri = uri, onError = onError, isAboveImageScrimEnabled = isAboveImageScrimEnabled, isSelectionMode = isSelectionMode, aboveImageContent = aboveImageContent, showExtension = showExtension, contentScale = contentScale ) } endAdditionalItem?.let { item { endAdditionalItem() } } item { AnimatedVisibility(isLoadingImages) { Box( modifier = Modifier .fillMaxSize() .aspectRatio(1f), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } } else { LazyVerticalGrid( columns = GridCells.Adaptive(verticalCellSize), state = state, modifier = Modifier .fillMaxSize() .dragHandler( key = enableSelection to imageUris, lazyGridState = state, isVertical = true, selectedItems = if (enableSelection) { privateSelectedItems } else { remember { mutableStateOf(emptySet()) } }, onSelectionChange = { onFrameSelectionChange(ImageFrames.ManualSelection(it.toList())) }, tapEnabled = isSelectionMode, onTap = onItemClick, onLongTap = { if (enableSelection) onItemLongClick(it) else onItemClick(it) } ), verticalArrangement = Arrangement.spacedBy( space = spacing, alignment = if (isContentAlignToCenter) Alignment.CenterVertically else Alignment.Top ), horizontalArrangement = Arrangement.spacedBy( space = spacing, alignment = if (isContentAlignToCenter) Alignment.CenterHorizontally else Alignment.Start ), contentPadding = contentPadding, flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = imageUris, key = { index, uri -> "$uri-${index + 1}" } ) { index, uri -> val selected by remember(index, privateSelectedItems.value) { derivedStateOf { index + 1 in privateSelectedItems.value } } ImageItem( selected = selected, modifier = Modifier .fillMaxSize() .aspectRatio(1f), index = index, uri = uri, onError = onError, aboveImageContent = aboveImageContent, isSelectionMode = isSelectionMode, isAboveImageScrimEnabled = isAboveImageScrimEnabled, showExtension = showExtension, contentScale = contentScale ) } endAdditionalItem?.let { item { endAdditionalItem() } } item { AnimatedVisibility(isLoadingImages) { Box( modifier = Modifier .fillMaxSize() .aspectRatio(1f), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } } } } @Composable private fun ImageItem( modifier: Modifier, uri: Any, index: Int, onError: (String) -> Unit, selected: Boolean, isSelectionMode: Boolean, isAboveImageScrimEnabled: Boolean, showExtension: Boolean, aboveImageContent: @Composable BoxScope.(index: Int) -> Unit, contentScale: ContentScale ) { val extracted = remember(uri) { uri.extractUri() } val transition = updateTransition(selected) val padding by transition.animateDp { s -> if (s) 10.dp else 0.dp } val corners by transition.animateDp { s -> if (s) 16.dp else 0.dp } val bgColor = MaterialTheme.colorScheme.secondaryContainer val shape = AutoCornersShape(corners) Box( modifier .clip(ShapeDefaults.extraSmall) .background(bgColor) ) { Picture( modifier = Modifier .matchParentSize() .padding(padding) .clip(shape) .background(MaterialTheme.colorScheme.surface), onError = extracted?.let { { onError(extracted.toString()) } }, error = { Box( contentAlignment = Alignment.Center, modifier = Modifier.background( takeColorFromScheme { isNightMode -> errorContainer.copy( if (isNightMode) 0.25f else 1f ).compositeOver(surface) } ) ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, modifier = Modifier.fillMaxSize(0.5f), tint = MaterialTheme.colorScheme.onErrorContainer.copy(0.8f) ) } }, filterQuality = FilterQuality.High, shape = RectangleShape, model = uri, contentScale = contentScale ) Box( modifier = Modifier .fillMaxSize() .padding(padding) .clip(shape) .then( if (isAboveImageScrimEnabled) { Modifier.background(MaterialTheme.colorScheme.scrim.copy(0.32f)) } else Modifier ), contentAlignment = Alignment.Center, content = { aboveImageContent(index) if (showExtension && extracted != null) { val extension = rememberFileExtension(extracted)?.uppercase() val humanFileSize = rememberHumanFileSize(extracted) extension?.let { Row( modifier = Modifier .align(Alignment.TopEnd) .padding(8.dp) .padding(vertical = 2.dp) .advancedShadow( cornersRadius = 4.dp, shadowBlurRadius = 6.dp, alpha = 0.4f ) .padding(horizontal = 2.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier, text = it, style = MaterialTheme.typography.labelMedium, color = White ) } } Row( modifier = Modifier .align(Alignment.BottomStart) .padding(8.dp) .padding(vertical = 2.dp) .advancedShadow( cornersRadius = 4.dp, shadowBlurRadius = 6.dp, alpha = 0.4f ) .padding(horizontal = 2.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier, text = humanFileSize, style = MaterialTheme.typography.labelMedium, color = White ) } } } ) AnimatedContent( targetState = selected to isSelectionMode, transitionSpec = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() } ) { (selected, isSelectionMode) -> if (isSelectionMode) { if (selected) { Icon( imageVector = Icons.Filled.CheckCircle, tint = MaterialTheme.colorScheme.primary, contentDescription = null, modifier = Modifier .padding(4.dp) .border(2.dp, bgColor, ShapeDefaults.circle) .clip(ShapeDefaults.circle) .background(bgColor) ) } else { Icon( imageVector = Icons.Filled.RadioButtonUnchecked, tint = Color.White.copy(alpha = 0.7f), contentDescription = null, modifier = Modifier.padding(6.dp) ) } } } } } private fun Any.extractUri() = (this as? String)?.toUri() ?: this as? Uri ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/MetadataPreviewButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.toMap import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.theme.onSecondaryContainerFixed import com.t8rin.imagetoolbox.core.ui.theme.secondaryContainerFixed import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.localizedName import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberImageMetadataAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextFieldColors import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.dateAdded import com.t8rin.imagetoolbox.core.utils.fileSize import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.core.utils.lastModified import com.t8rin.imagetoolbox.core.utils.path @Composable fun MetadataPreviewButton( uri: Uri?, dateModified: (Uri) -> Long? = { it.lastModified() }, dateAdded: (Uri) -> Long? = { it.dateAdded() }, path: (Uri) -> String? = { it.path() }, name: (Uri) -> String? = { it.filename() }, fileSize: (Uri) -> String? = { humanFileSize(it.fileSize() ?: 0L) } ) { AnimatedContent( targetState = uri ) { uri -> val metadata by rememberImageMetadataAsState( uri ?: return@AnimatedContent ) val tagMap by remember(metadata) { derivedStateOf { metadata?.toMap().orEmpty().toList() .filter { it.second.isNotBlank() } } } val info by remember(uri, dateModified, dateAdded, path, name, fileSize) { derivedStateOf { UriInfo( dateModified = dateModified(uri), dateAdded = dateAdded(uri), path = path(uri), name = name(uri), fileSize = fileSize(uri) ) } } if (tagMap.isNotEmpty() || info.data.isNotEmpty()) { var showExif by rememberSaveable { mutableStateOf(false) } SupportingButton( onClick = { showExif = true }, contentColor = MaterialTheme.colorScheme.onSecondaryContainerFixed, containerColor = MaterialTheme.colorScheme.secondaryContainerFixed, style = MaterialTheme.typography.labelMedium, modifier = Modifier .padding(start = 4.dp) .size(20.dp), iconPadding = 2.dp ) EnhancedModalBottomSheet( visible = showExif, onDismiss = { showExif = false }, title = { TitleItem( text = stringResource(R.string.exif), icon = Icons.Rounded.Exif ) }, confirmButton = { EnhancedButton( onClick = { showExif = false } ) { Text(text = stringResource(R.string.close)) } }, ) { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed(info.data) { index, (name, value) -> ValueField( label = stringResource(name), value = value, shape = ShapeDefaults.byIndex( index = index, size = info.data.size ) ) } if (info.data.isNotEmpty() && tagMap.isNotEmpty()) { item { Spacer(Modifier.height(4.dp)) } } itemsIndexed(tagMap) { index, (tag, value) -> ValueField( label = tag.localizedName, value = value, shape = ShapeDefaults.byIndex( index = index, size = tagMap.size ) ) } } } } } } @Composable private fun ValueField( label: String, value: String, shape: Shape ) { RoundedTextField( onValueChange = {}, readOnly = true, value = value, label = label, textStyle = LocalTextStyle.current.copy( fontSize = 16.sp, fontWeight = FontWeight.Bold ), colors = RoundedTextFieldColors( isError = false, containerColor = EnhancedBottomSheetDefaults.contentContainerColor, unfocusedIndicatorColor = Color.Transparent ).copy( unfocusedLabelColor = MaterialTheme.colorScheme .surfaceVariant.inverse({ 0.3f }) ), singleLine = false, maxLines = Int.MAX_VALUE, shape = shape, modifier = Modifier .fillMaxWidth() .container( color = EnhancedBottomSheetDefaults.contentContainerColor, shape = shape, resultPadding = 0.dp ) ) } private data class UriInfo( val dateModified: Long?, val dateAdded: Long?, val path: String?, val name: String?, val fileSize: String? ) { val data: List> = buildList { name?.takeIf { it.isNotBlank() }?.let { add(R.string.filename to it) } fileSize?.takeIf { it.isNotBlank() }?.let { add(R.string.file_size to it) } val dateAddedFormatted = dateAdded?.takeIf { it > 0 }?.let { timestamp( format = "d MMMM, yyyy • HH:mm", date = it ) } val dateModifiedFormatted = dateModified?.takeIf { it > 0 }?.let { timestamp( format = "d MMMM, yyyy • HH:mm", date = it ) } if (dateModifiedFormatted != dateAddedFormatted) { dateModifiedFormatted?.let { add(R.string.sort_by_date_modified to it) } } dateAddedFormatted?.let { add(R.string.sort_by_date_added to it) } path?.takeIf { it.isNotBlank() } ?.removeSuffix("/$name") ?.removeSuffix("/${name?.substringBeforeLast('.')}") ?.let { add(R.string.path to it) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/Picture.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.content.pm.ActivityInfo import android.graphics.Bitmap import android.os.Build import androidx.compose.foundation.Image import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import coil3.SingletonImageLoader import coil3.compose.AsyncImageModelEqualityDelegate import coil3.compose.AsyncImagePainter import coil3.compose.LocalAsyncImageModelEqualityDelegate import coil3.compose.LocalPlatformContext import coil3.compose.SubcomposeAsyncImage import coil3.compose.SubcomposeAsyncImageScope import coil3.imageLoader import coil3.request.ImageRequest import coil3.request.allowHardware import coil3.request.crossfade import coil3.request.transformations import coil3.toBitmap import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.domain.transformation.GenericTransformation import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.findActivity import com.t8rin.imagetoolbox.core.ui.utils.helper.toCoil import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext @Composable fun Picture( model: Any?, modifier: Modifier = Modifier, transformations: List? = null, contentDescription: String? = null, shape: Shape = RectangleShape, contentScale: ContentScale = if (model.isCompose()) ContentScale.Fit else ContentScale.Crop, loading: @Composable (SubcomposeAsyncImageScope.(AsyncImagePainter.State.Loading) -> Unit)? = null, success: @Composable (SubcomposeAsyncImageScope.(AsyncImagePainter.State.Success) -> Unit)? = null, error: @Composable (SubcomposeAsyncImageScope.(AsyncImagePainter.State.Error) -> Unit)? = null, onLoading: ((AsyncImagePainter.State.Loading) -> Unit)? = null, onSuccess: ((AsyncImagePainter.State.Success) -> Unit)? = null, onError: ((AsyncImagePainter.State.Error) -> Unit)? = null, onState: ((AsyncImagePainter.State) -> Unit)? = null, alignment: Alignment = Alignment.Center, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = if (model is ImageVector) { ColorFilter.tint(LocalContentColor.current) } else null, filterQuality: FilterQuality = FilterQuality.None, shimmerEnabled: Boolean = true, crossfadeEnabled: Boolean = true, allowHardware: Boolean = true, showTransparencyChecker: Boolean = true, isLoadingFromDifferentPlace: Boolean = false, enableUltraHDRSupport: Boolean = false, size: Int? = null, contentPadding: PaddingValues = PaddingValues() ) { CompositionLocalProvider( LocalAsyncImageModelEqualityDelegate provides AsyncImageModelEqualityDelegate.AllProperties ) { when (model) { is ImageBitmap -> { Image( bitmap = model, contentDescription = contentDescription, modifier = modifier, alignment = alignment, contentScale = contentScale, alpha = alpha, colorFilter = colorFilter, filterQuality = filterQuality ) } is Painter -> { Image( painter = model, contentDescription = contentDescription, modifier = modifier, alignment = alignment, contentScale = contentScale, alpha = alpha, colorFilter = colorFilter ) } is ImageVector -> { Image( imageVector = model, contentDescription = contentDescription ?: model.name, modifier = modifier, alignment = alignment, contentScale = contentScale, alpha = alpha, colorFilter = colorFilter ) } else -> { CoilPicture( model = model, modifier = modifier, transformations = transformations, contentDescription = contentDescription, shape = shape, contentScale = contentScale, loading = loading, success = success, error = error, onLoading = onLoading, onSuccess = onSuccess, onError = onError, onState = onState, alignment = alignment, alpha = alpha, colorFilter = colorFilter, filterQuality = filterQuality, shimmerEnabled = shimmerEnabled, crossfadeEnabled = crossfadeEnabled, allowHardware = allowHardware, showTransparencyChecker = showTransparencyChecker, isLoadingFromDifferentPlace = isLoadingFromDifferentPlace, enableUltraHDRSupport = enableUltraHDRSupport, size = size, contentPadding = contentPadding ) } } } } @Composable private fun CoilPicture( model: Any?, modifier: Modifier, transformations: List?, contentDescription: String?, shape: Shape, contentScale: ContentScale, loading: @Composable (SubcomposeAsyncImageScope.(AsyncImagePainter.State.Loading) -> Unit)?, success: @Composable (SubcomposeAsyncImageScope.(AsyncImagePainter.State.Success) -> Unit)?, error: @Composable (SubcomposeAsyncImageScope.(AsyncImagePainter.State.Error) -> Unit)?, onLoading: ((AsyncImagePainter.State.Loading) -> Unit)?, onSuccess: ((AsyncImagePainter.State.Success) -> Unit)?, onError: ((AsyncImagePainter.State.Error) -> Unit)?, onState: ((AsyncImagePainter.State) -> Unit)?, alignment: Alignment, alpha: Float, colorFilter: ColorFilter?, filterQuality: FilterQuality, shimmerEnabled: Boolean, crossfadeEnabled: Boolean, allowHardware: Boolean, showTransparencyChecker: Boolean, isLoadingFromDifferentPlace: Boolean, enableUltraHDRSupport: Boolean, size: Int?, contentPadding: PaddingValues = PaddingValues() ) { val context = LocalContext.current var errorOccurred by rememberSaveable { mutableStateOf(false) } var shimmerVisible by rememberSaveable { mutableStateOf(true) } val imageLoader = context.imageLoader val hdrTransformation = remember(context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && enableUltraHDRSupport) { listOf( GenericTransformation { bitmap -> withContext(Dispatchers.Main.immediate) { delay(1000) context.findActivity()?.window?.colorMode = if (bitmap.hasGainmap()) { ActivityInfo.COLOR_MODE_HDR } else ActivityInfo.COLOR_MODE_DEFAULT } bitmap }.toCoil() ) } else emptyList() } val request = model as? ImageRequest ?: remember( context, model, crossfadeEnabled, allowHardware, transformations, hdrTransformation, size ) { ImageRequest.Builder(context) .data(model) .crossfade(crossfadeEnabled) .allowHardware(allowHardware) .transformations( (transformations ?: emptyList()) + hdrTransformation ) .apply { size?.let { size(it) } } .build() } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && enableUltraHDRSupport) { DisposableEffect(model) { onDispose { context.findActivity()?.window?.colorMode = ActivityInfo.COLOR_MODE_DEFAULT } } } SubcomposeAsyncImage( model = request, imageLoader = if (LocalInspectionMode.current) { SingletonImageLoader.get(LocalPlatformContext.current) } else imageLoader, contentDescription = contentDescription, modifier = modifier .clip(shape) .then( if (!LocalInspectionMode.current) { Modifier .then(if (showTransparencyChecker) Modifier.transparencyChecker() else Modifier) .then(if (shimmerEnabled) Modifier.shimmer(shimmerVisible || isLoadingFromDifferentPlace) else Modifier) } else { Modifier } ) .padding(contentPadding), contentScale = contentScale, loading = { if (loading != null) loading(it) shimmerVisible = true }, success = success, error = error, onSuccess = { if (model is ImageRequest && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && enableUltraHDRSupport) { context.findActivity()?.window?.colorMode = if (it.result.image.toBitmap(400, 400).hasGainmap()) { ActivityInfo.COLOR_MODE_HDR } else ActivityInfo.COLOR_MODE_DEFAULT } shimmerVisible = false onSuccess?.invoke(it) onState?.invoke(it) }, onLoading = { onLoading?.invoke(it) onState?.invoke(it) }, onError = { if (error != null) shimmerVisible = false onError?.invoke(it) onState?.invoke(it) errorOccurred = true }, alignment = alignment, alpha = alpha, colorFilter = colorFilter, filterQuality = filterQuality ) //Needed for triggering recomposition LaunchedEffect(errorOccurred) { if (errorOccurred && error == null) { shimmerVisible = false shimmerVisible = true errorOccurred = false } } } private fun Any?.isCompose(): Boolean = this is Painter || this is ImageVector || this is ImageBitmap ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/SimplePicture.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.graphics.Bitmap import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable fun SimplePicture( bitmap: Bitmap?, modifier: Modifier = Modifier, scale: ContentScale = ContentScale.Fit, boxModifier: Modifier = Modifier, enableContainer: Boolean = true, loading: Boolean = false, visible: Boolean = true ) { bitmap?.asImageBitmap() ?.takeIf { visible } ?.let { Box( modifier = boxModifier .then( if (enableContainer) { Modifier .container() .padding(4.dp) } else Modifier ), contentAlignment = Alignment.Center ) { Picture( model = it, contentScale = scale, contentDescription = null, modifier = modifier .aspectRatio( it.safeAspectRatio ) .clip(MaterialTheme.shapes.medium) .transparencyChecker() .shimmer(loading) ) } } } //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // val activity = LocalComponentActivity.current // DisposableEffect(it) { // activity.window.colorMode = if (bitmap.hasGainmap()) { // ActivityInfo.COLOR_MODE_HDR // } else ActivityInfo.COLOR_MODE_DEFAULT // onDispose { // activity.window.colorMode = ActivityInfo.COLOR_MODE_DEFAULT // } // } //} ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisCarousel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.image import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import coil3.request.ImageRequest import coil3.request.crossfade import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedHorizontalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.utils.appContext import kotlin.math.roundToInt import kotlin.random.Random @Composable internal fun UrisCarousel(uris: List) { Row( horizontalArrangement = Arrangement.spacedBy( 4.dp, Alignment.CenterHorizontally ), verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .layout { measurable, constraints -> val placeable = measurable.measure( constraints.copy( maxWidth = constraints.maxWidth + 32.dp.roundToPx(), maxHeight = (constraints.maxWidth * 0.5f) .coerceAtLeast(100f) .coerceAtMost(constraints.maxHeight * 0.5f) .roundToInt() ) ) layout( placeable.width, placeable.height ) { placeable.place(0, 0) } } .enhancedHorizontalScroll(rememberScrollState()) .padding( PaddingValues( start = 16.dp, end = 16.dp, bottom = 16.dp ) ) ) { uris.fastForEach { uri -> val key = rememberSaveable(uri) { "$uri${Random.nextInt()}" } var aspectRatio by rememberSaveable { mutableFloatStateOf(0.5f) } Picture( model = remember(uri, key) { ImageRequest.Builder(appContext) .data(uri) .size(1000) .crossfade(true) .memoryCacheKey(key) .diskCacheKey(key) .build() }, onSuccess = { aspectRatio = it.result.image.safeAspectRatio }, modifier = Modifier .animateContentSizeNoClip() .fillMaxHeight() .aspectRatio( ratio = aspectRatio, matchHeightConstraintsFirst = true ) .container( shape = MaterialTheme.shapes.medium, resultPadding = 0.dp ), shape = RectangleShape, contentScale = ContentScale.Fit ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/image/UrisPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.ui.widget.image import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.InsertDriveFile import androidx.compose.material.icons.automirrored.rounded.NoteAdd import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.shareUris import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun UrisPreview( modifier: Modifier = Modifier, uris: List, isPortrait: Boolean, onRemoveUri: ((Uri) -> Unit)?, onAddUris: (() -> Unit)?, isAddUrisVisible: Boolean = true, addUrisContent: @Composable BoxScope.(width: Dp) -> Unit = { width -> Icon( imageVector = Icons.AutoMirrored.Rounded.NoteAdd, contentDescription = stringResource(R.string.add), modifier = Modifier.size(width / 3f) ) }, onClickUri: ((Uri) -> Unit)? = null, errorContent: @Composable BoxScope.(index: Int, width: Dp) -> Unit = { _, width -> Icon( imageVector = Icons.AutoMirrored.Outlined.InsertDriveFile, contentDescription = null, modifier = Modifier .size(width / 3f) .align(Alignment.Center), tint = MaterialTheme.colorScheme.primary ) }, showTransparencyChecker: Boolean = true, showScrimForNonSuccess: Boolean = true, filenameSource: (index: Int) -> Uri = { uris[it] }, onNavigate: ((Screen) -> Unit)? = null ) { var previewUri by rememberSaveable { mutableStateOf(null) } BoxWithConstraints { val size = uris.size + 1f val count = if (isPortrait) { size.coerceAtLeast(2f).coerceAtMost(3f) } else { size.coerceAtLeast(2f).coerceAtMost(8f) } val width = this.maxWidth / count - 4.dp * (count - 1) FlowRow( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { uris.forEachIndexed { index, uri -> if (uri != Uri.EMPTY) { Box( modifier = Modifier.container( shape = ShapeDefaults.extraSmall, resultPadding = 0.dp, color = MaterialTheme.colorScheme.surfaceContainerHighest ) ) { var isLoaded by remember(uri) { mutableStateOf(false) } Picture( model = uri, error = { Box( contentAlignment = Alignment.Center, content = { errorContent(index, width) } ) }, onSuccess = { isLoaded = true }, modifier = Modifier .then( if (onClickUri != null) { Modifier.hapticsClickable { onClickUri(uri) } } else { Modifier.hapticsClickable { previewUri = uri } } ) .width(width) .aspectRatio(1f), showTransparencyChecker = showTransparencyChecker ) Box( modifier = Modifier .matchParentSize() .background( takeColorFromScheme { scrim.copy( if (isLoaded || showScrimForNonSuccess) 0.5f else 0f ) } ) ) { Text( text = (index + 1).toString(), color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Medium, modifier = Modifier .padding(8.dp) .align(Alignment.TopStart) ) if (onRemoveUri != null) { Icon( imageVector = Icons.Rounded.RemoveCircleOutline, contentDescription = stringResource(R.string.remove), modifier = Modifier .padding(4.dp) .clip(ShapeDefaults.circle) .background( MaterialTheme.colorScheme.scrim.copy( animateFloatAsState(if (uris.size > 1) 0.2f else 0f).value ) ) .hapticsClickable( enabled = uris.size > 1 ) { onRemoveUri(uri) } .padding(4.dp) .align(Alignment.TopEnd), tint = Color.White.copy( animateFloatAsState(if (uris.size > 1) 0.7f else 0f).value ), ) } val filename = rememberFilename(filenameSource(index)) filename?.let { AutoSizeText( text = it, style = LocalTextStyle.current.copy( color = Color.White, fontSize = 11.sp, lineHeight = 12.sp, fontWeight = FontWeight.Medium, textAlign = TextAlign.End ), maxLines = 3, modifier = Modifier .padding(8.dp) .align(Alignment.BottomEnd) ) } } } } else { AnimatedVisibility(visible = isAddUrisVisible) { Box( modifier = Modifier .container( shape = ShapeDefaults.extraSmall, resultPadding = 0.dp, color = MaterialTheme.colorScheme.surfaceContainerHigh ) .width(width) .aspectRatio(1f) .then( if (onAddUris != null) { Modifier.hapticsClickable(onClick = onAddUris) } else Modifier ), contentAlignment = Alignment.Center, content = { addUrisContent(width) } ) } } } } } if (onClickUri == null && onNavigate != null) { val context = LocalContext.current ImagePager( visible = previewUri != null, selectedUri = previewUri, uris = uris, onNavigate = onNavigate, onUriSelected = { previewUri = it }, onShare = { context.shareUris(listOf(it)) }, onDismiss = { previewUri = null } ) } } fun Modifier.urisPreview( scrollState: ScrollState? = null ): Modifier = this.composed { val isPortrait by isPortraitOrientationAsState() if (!isPortrait) { Modifier .layout { measurable, constraints -> val placeable = measurable.measure( constraints = constraints.copy( maxHeight = constraints.maxHeight + 48.dp.roundToPx() ) ) layout(placeable.width, placeable.height) { placeable.place(0, 0) } } .enhancedVerticalScroll(scrollState ?: rememberScrollState()) } else { Modifier }.padding(vertical = 24.dp) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AdvancedShadow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.nativePaint import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp fun Modifier.advancedShadow( color: Color = Color.Black, alpha: Float = 1f, cornersRadius: Dp = 0.dp, shadowBlurRadius: Dp = 0.dp, offsetY: Dp = 0.dp, offsetX: Dp = 0.dp ) = drawBehind { val shadowColor = color.copy(alpha = alpha).toArgb() val transparentColor = color.copy(alpha = 0f).toArgb() drawIntoCanvas { val paint = Paint() val frameworkPaint = paint.nativePaint frameworkPaint.color = transparentColor frameworkPaint.setShadowLayer( shadowBlurRadius.toPx(), offsetX.toPx(), offsetY.toPx(), shadowColor ) it.drawRoundRect( 0f, 0f, this.size.width, this.size.height, cornersRadius.toPx(), cornersRadius.toPx(), paint ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AlertDialogBorder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.animateDpAsState import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant fun Modifier.alertDialogBorder() = this.composed { Modifier .autoElevatedBorder( color = MaterialTheme.colorScheme.outlineVariant( luminance = 0.15f, onTopOf = MaterialTheme.colorScheme.surfaceContainerHigh ), shape = AlertDialogDefaults.shape, autoElevation = animateDpAsState( if (LocalSettingsState.current.drawContainerShadows) 16.dp else 0.dp ).value ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AnimateContentSizeNoClip.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationEndReason import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.AnimationVector2D import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.Spring import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.spring import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.layout.IntrinsicMeasurable import androidx.compose.ui.layout.IntrinsicMeasureScope import androidx.compose.ui.layout.LayoutModifier import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.InspectorInfo import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.constrain import kotlinx.coroutines.launch /** * This modifier animates its own size when its child modifier (or the child composable if it * is already at the tail of the chain) changes size. This allows the parent modifier to observe * a smooth size change, resulting in an overall continuous visual change. * * A [FiniteAnimationSpec] can be optionally specified for the size change animation. By default, * [spring] will be used. * * An optional [finishedListener] can be supplied to get notified when the size change animation is * finished. Since the content size change can be dynamic in many cases, both initial value and * target value (i.e. final size) will be passed to the [finishedListener]. __Note:__ if the * animation is interrupted, the initial value will be the size at the point of interruption. This * is intended to help determine the direction of the size change (i.e. expand or collapse in x and * y dimensions). * * @sample androidx.compose.animation.samples.AnimateContent * * @param animationSpec a finite animation that will be used to animate size change, [spring] by * default * @param finishedListener an optional listener to be called when the content change animation is * completed. */ fun Modifier.animateContentSizeNoClip( animationSpec: FiniteAnimationSpec = spring( stiffness = Spring.StiffnessMediumLow ), alignment: Alignment = Alignment.TopCenter, isClipped: Boolean = false, finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null ): Modifier = this .then( if (isClipped) Modifier.clipToBounds() else Modifier ) .then( SizeAnimationModifierElement( animationSpec = animationSpec, alignment = alignment, finishedListener = finishedListener ) ) fun Modifier.animateContentSizeNoClip( animationSpec: FiniteAnimationSpec = spring( stiffness = Spring.StiffnessMediumLow ), alignment: Alignment = Alignment.TopCenter, finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null ) = this .clipToBounds() .then( SizeAnimationModifierElement( animationSpec = animationSpec, alignment = alignment, finishedListener = finishedListener ) ) private data class SizeAnimationModifierElement( val animationSpec: FiniteAnimationSpec, val alignment: Alignment, val finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? ) : ModifierNodeElement() { override fun create(): SizeAnimationModifierNode = SizeAnimationModifierNode( animationSpec = animationSpec, alignment = alignment, listener = finishedListener ) override fun update(node: SizeAnimationModifierNode) { node.animationSpec = animationSpec node.listener = finishedListener node.alignment = alignment } override fun InspectorInfo.inspectableProperties() { name = "animateContentSizeNoClip" properties["animationSpec"] = animationSpec properties["alignment"] = alignment properties["finishedListener"] = finishedListener } } internal val InvalidSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE) internal val IntSize.isValid: Boolean get() = this != InvalidSize /** * This class creates a [LayoutModifier] that measures children, and responds to children's size * change by animating to that size. The size reported to parents will be the animated size. */ private class SizeAnimationModifierNode( var animationSpec: AnimationSpec, var alignment: Alignment = Alignment.TopStart, var listener: ((startSize: IntSize, endSize: IntSize) -> Unit)? = null ) : LayoutModifierNodeWithPassThroughIntrinsics() { private var lookaheadSize: IntSize = InvalidSize private var lookaheadConstraints: Constraints = Constraints() set(value) { field = value lookaheadConstraintsAvailable = true } private var lookaheadConstraintsAvailable: Boolean = false private fun targetConstraints(default: Constraints) = if (lookaheadConstraintsAvailable) { lookaheadConstraints } else { default } data class AnimData(val anim: Animatable, var startSize: IntSize) var animData: AnimData? by mutableStateOf(null) override fun onReset() { super.onReset() // Reset is an indication that the node may be re-used, in such case, animData becomes stale animData = null } override fun onAttach() { super.onAttach() // When re-attached, we may be attached to a tree without lookahead scope. lookaheadSize = InvalidSize lookaheadConstraintsAvailable = false } override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val placeable = if (isLookingAhead) { lookaheadConstraints = constraints measurable.measure(constraints) } else { // Measure with lookahead constraints when available, to avoid unnecessary relayout // in child during the lookahead animation. measurable.measure(targetConstraints(constraints)) } val measuredSize = IntSize(placeable.width, placeable.height) val (width, height) = if (isLookingAhead) { lookaheadSize = measuredSize measuredSize } else { animateTo(if (lookaheadSize.isValid) lookaheadSize else measuredSize).let { // Constrain the measure result to incoming constraints, so that parent doesn't // force center this layout. constraints.constrain(it) } } return layout(width, height) { val offset = alignment.align( size = measuredSize, space = IntSize(width, height), layoutDirection = this@measure.layoutDirection ) placeable.place(offset) } } fun animateTo(targetSize: IntSize): IntSize { val data = animData?.apply { // TODO(b/322878517): Figure out a way to seamlessly continue the animation after // re-attach. Note that in some cases restarting the animation is the correct // behavior. val wasInterrupted = (targetSize != anim.value && !anim.isRunning) if (targetSize != anim.targetValue || wasInterrupted) { startSize = anim.value coroutineScope.launch { val result = anim.animateTo(targetSize, animationSpec) if (result.endReason == AnimationEndReason.Finished) { listener?.invoke(startSize, result.endState.value) } } } } ?: AnimData( Animatable(targetSize, IntSize.VectorConverter, IntSize(1, 1)), targetSize ) animData = data return data.anim.value } } internal abstract class LayoutModifierNodeWithPassThroughIntrinsics : LayoutModifierNode, Modifier.Node() { override fun IntrinsicMeasureScope.minIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ) = measurable.minIntrinsicWidth(height) override fun IntrinsicMeasureScope.minIntrinsicHeight( measurable: IntrinsicMeasurable, width: Int ) = measurable.minIntrinsicHeight(width) override fun IntrinsicMeasureScope.maxIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ) = measurable.maxIntrinsicWidth(height) override fun IntrinsicMeasureScope.maxIntrinsicHeight( measurable: IntrinsicMeasurable, width: Int ) = measurable.maxIntrinsicHeight(width) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoCornersShape.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:SuppressLint("ComposableNaming") @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.core.ui.widget.modifier import android.annotation.SuppressLint import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Size import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.kyant.capsule.Continuity import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import sv.lib.squircleshape.CornerSmoothing import sv.lib.squircleshape.SquircleShape private val continuity = Continuity.Default private val smoothing = CornerSmoothing.Medium @Stable fun AutoCornersShape( corner: CornerSize, shapesType: ShapeType ) = AutoCornersShape( topStart = corner, topEnd = corner, bottomEnd = corner, bottomStart = corner, shapesType = shapesType ) @Stable fun AutoCornersShape( size: Dp, shapesType: ShapeType ) = AutoCornersShape( corner = CornerSize(size), shapesType = shapesType ) @Stable fun AutoCornersShape( size: Float, shapesType: ShapeType ) = AutoCornersShape( corner = CornerSize(size), shapesType = shapesType ) @Stable fun AutoCornersShape( percent: Int, shapesType: ShapeType ) = AutoCornersShape( corner = CornerSize(percent), shapesType = shapesType ) @Stable fun AutoCornersShape( topStart: Dp = 0.dp, topEnd: Dp = 0.dp, bottomEnd: Dp = 0.dp, bottomStart: Dp = 0.dp, shapesType: ShapeType ) = AutoCornersShape( topStart = CornerSize(topStart), topEnd = CornerSize(topEnd), bottomEnd = CornerSize(bottomEnd), bottomStart = CornerSize(bottomStart), shapesType = shapesType ) @Stable fun AutoCornersShape( topStart: Float = 0.0f, topEnd: Float = 0.0f, bottomEnd: Float = 0.0f, bottomStart: Float = 0.0f, shapesType: ShapeType ) = AutoCornersShape( topStart = CornerSize(topStart), topEnd = CornerSize(topEnd), bottomEnd = CornerSize(bottomEnd), bottomStart = CornerSize(bottomStart), shapesType = shapesType ) @Stable fun AutoCornersShape( topStartPercent: Int = 0, topEndPercent: Int = 0, bottomEndPercent: Int = 0, bottomStartPercent: Int = 0, shapesType: ShapeType ) = AutoCornersShape( topStart = CornerSize(topStartPercent), topEnd = CornerSize(topEndPercent), bottomEnd = CornerSize(bottomEndPercent), bottomStart = CornerSize(bottomStartPercent), shapesType = shapesType ) @Stable fun AutoCornersShape( topStart: CornerSize, topEnd: CornerSize, bottomEnd: CornerSize, bottomStart: CornerSize, shapesType: ShapeType ): CornerBasedShape { if (shapesType.strength <= 0f) return CornerBasedRectangleShape return when (shapesType) { is ShapeType.Cut -> CutCornerShape( topStart = topStart.toAuto(shapesType), topEnd = topEnd.toAuto(shapesType), bottomEnd = bottomEnd.toAuto(shapesType), bottomStart = bottomStart.toAuto(shapesType), ) is ShapeType.Rounded -> RoundedCornerShape( topStart = topStart.toAuto(shapesType), topEnd = topEnd.toAuto(shapesType), bottomEnd = bottomEnd.toAuto(shapesType), bottomStart = bottomStart.toAuto(shapesType), ) is ShapeType.Smooth -> ContinuousRoundedRectangle( topStart = topStart.toAuto(shapesType), topEnd = topEnd.toAuto(shapesType), bottomEnd = bottomEnd.toAuto(shapesType), bottomStart = bottomStart.toAuto(shapesType), continuity = continuity ) is ShapeType.Squircle -> SquircleShape( topStartCorner = topStart.toAuto(shapesType), topEndCorner = topEnd.toAuto(shapesType), bottomEndCorner = bottomEnd.toAuto(shapesType), bottomStartCorner = bottomStart.toAuto(shapesType), smoothing = smoothing ) } } @Stable fun AutoCircleShape(shapesType: ShapeType) = when (shapesType) { is ShapeType.Cut -> CutCircleShape is ShapeType.Rounded -> CircleShape is ShapeType.Smooth -> SmoothCircleShape is ShapeType.Squircle -> SquircleCircleShape }.let { shape -> if (shapesType.strength >= 1f) { shape } else { shape.copy(shape.topStart.toAuto(shapesType)) } } @Stable @Composable fun AutoCornersShape(size: Dp) = rememberSettings(size) { shapesType -> AutoCornersShape( size = size, shapesType = shapesType ) } @Stable @Composable fun AutoCornersShape(size: Float) = rememberSettings(size) { shapesType -> AutoCornersShape( size = size, shapesType = shapesType ) } @Stable @Composable fun AutoCornersShape(percent: Int) = rememberSettings(percent) { shapesType -> AutoCornersShape( percent = percent, shapesType = shapesType ) } @Stable @Composable fun AutoCornersShape( topStart: Dp = 0.dp, topEnd: Dp = 0.dp, bottomEnd: Dp = 0.dp, bottomStart: Dp = 0.dp, ) = rememberSettings(topStart, topEnd, bottomEnd, bottomStart) { shapesType -> AutoCornersShape( topStart = topStart, topEnd = topEnd, bottomEnd = bottomEnd, bottomStart = bottomStart, shapesType = shapesType ) } @Stable @Composable fun AutoCornersShape( topStart: Float = 0.0f, topEnd: Float = 0.0f, bottomEnd: Float = 0.0f, bottomStart: Float = 0.0f, ) = rememberSettings(topStart, topEnd, bottomEnd, bottomStart) { shapesType -> AutoCornersShape( topStart = topStart, topEnd = topEnd, bottomEnd = bottomEnd, bottomStart = bottomStart, shapesType = shapesType ) } @Stable @Composable fun AutoCornersShape( topStartPercent: Int = 0, topEndPercent: Int = 0, bottomEndPercent: Int = 0, bottomStartPercent: Int = 0, ) = rememberSettings( topStartPercent, topEndPercent, bottomEndPercent, bottomStartPercent ) { shapesType -> AutoCornersShape( topStartPercent = topStartPercent, topEndPercent = topEndPercent, bottomEndPercent = bottomEndPercent, bottomStartPercent = bottomStartPercent, shapesType = shapesType ) } @Stable @Composable fun AutoCornersShape( topStart: CornerSize, topEnd: CornerSize, bottomEnd: CornerSize, bottomStart: CornerSize, ) = rememberSettings(topStart, topEnd, bottomEnd, bottomStart) { shapesType -> AutoCornersShape( topStart = topStart, topEnd = topEnd, bottomEnd = bottomEnd, bottomStart = bottomStart, shapesType = shapesType ) } @Stable @Composable fun AutoCircleShape() = rememberSettings { shapesType -> AutoCircleShape(shapesType) } @Stable val SmoothCircleShape = ContinuousCapsule(continuity) @Stable val CutCircleShape = CutCornerShape(50) @Stable val SquircleCircleShape = SquircleShape( percent = 50, smoothing = smoothing ) @Stable val CornerBasedRectangleShape = RoundedCornerShape(0.dp) @Stable @Composable private fun rememberSettings( vararg keys: Any?, calculation: (type: ShapeType) -> CornerBasedShape ): CornerBasedShape { val shapesType = LocalSettingsState.current.shapesType return remember(*keys, shapesType) { calculation(shapesType) } } @Stable private fun CornerSize.toAuto(shapeType: ShapeType) = toAuto(shapeType.strength) @Stable private fun CornerSize.toAuto(strength: Float) = if (strength == 1f) { this } else if (this is AutoCornerSize) { copy( strength = strength ) } else { AutoCornerSize( parent = this, strength = strength ) } @Stable @Immutable private data class AutoCornerSize( private val parent: CornerSize, private val strength: Float ) : CornerSize { override fun toPx(shapeSize: Size, density: Density): Float = (parent.toPx(shapeSize, density) * strength).coerceAtLeast(0f) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/AutoElevatedBorder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.border import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isUnspecified import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.suggestContainerColorBy fun Modifier.autoElevatedBorder( height: Dp = Dp.Unspecified, shape: Shape? = null, color: Color = Color.Unspecified, autoElevation: Dp = 6.dp ) = this.composed { val h = if (height.isUnspecified) { LocalSettingsState.current.borderWidth.takeIf { it > 0.dp } } else null val shape1 = shape ?: FloatingActionButtonDefaults.shape if (h == null) { Modifier } else { Modifier.border( width = h, color = if (color.isSpecified) color else { MaterialTheme.colorScheme.outlineVariant( luminance = 0.3f, onTopOf = MaterialTheme.colorScheme.suggestContainerColorBy(LocalContentColor.current) ) }, shape = shape1 ) }.materialShadow( elevation = animateDpAsState(if (h == null) autoElevation else 0.dp).value, shape = shape1 ) } fun Modifier.containerFabBorder( autoElevation: Dp = 1.5.dp, shape: Shape? = null ) = autoElevatedBorder( autoElevation = autoElevation, shape = shape ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Blink.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.Animatable import androidx.compose.animation.core.tween import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isUnspecified fun Modifier.blink( key: Any? = Unit, blinkColor: Color = Color.Unspecified, iterations: Int = 2, enabled: Boolean = true ) = composed { val animatable = remember(key) { Animatable(Color.Transparent) } val color = if (blinkColor.isUnspecified) { MaterialTheme.colorScheme.inverseSurface.copy(alpha = 0.4f) } else blinkColor LaunchedEffect(key) { kotlinx.coroutines.delay(500L) repeat(iterations) { animatable.animateTo(color, animationSpec = tween(durationMillis = 1000)) animatable.animateTo(Color.Transparent, animationSpec = tween(durationMillis = 1000)) } } if (enabled) Modifier.drawWithContent { drawContent() drawRect(animatable.value, size = this.size) } else Modifier } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Container.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("AnimateAsStateLabel") package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.isUnspecified import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.takeOrElse import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerShape import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor fun Modifier.container( shape: Shape? = null, color: Color = Color.Unspecified, resultPadding: Dp = 4.dp, borderWidth: Dp = Dp.Unspecified, borderColor: Color? = null, autoShadowElevation: Dp = if (color != Color.Transparent) 1.dp else 0.dp, clip: Boolean = true, composeColorOnTopOfBackground: Boolean = true, isShadowClip: Boolean = color.alpha < 1f, isStandaloneContainer: Boolean = color.alpha < 1f, shadowColor: Color = Color.Black ) = this.composed { val localContainerShape = LocalContainerShape.current val resultShape = localContainerShape ?: shape ?: ShapeDefaults.default val settingsState = LocalSettingsState.current val targetBorderWidth = borderWidth.takeOrElse { settingsState.borderWidth } val colorScheme = MaterialTheme.colorScheme val containerColor = if (color.isUnspecified) { SafeLocalContainerColor } else { if (composeColorOnTopOfBackground) color.compositeOver(colorScheme.background) else color } val density = LocalDensity.current val genericModifier = Modifier.drawWithCache { val outline = resultShape.createOutline( size = size, layoutDirection = layoutDirection, density = density ) onDrawWithContent { drawOutline( outline = outline, color = containerColor ) if (targetBorderWidth > 0.dp) { drawOutline( outline = outline, color = borderColor ?: colorScheme.outlineVariant(0.1f, containerColor), style = Stroke(with(density) { targetBorderWidth.toPx() }) ) } drawContent() } } val cornerModifier = Modifier .background( color = containerColor, shape = resultShape ) .border( width = targetBorderWidth, color = borderColor ?: colorScheme.outlineVariant(0.1f, containerColor), shape = resultShape ) Modifier .materialShadow( shape = resultShape, elevation = animateDpAsState( if (targetBorderWidth > 0.dp) { 0.dp } else autoShadowElevation.coerceAtLeast(0.dp) ).value, enabled = if (isStandaloneContainer) { settingsState.drawContainerShadows } else true, isClipped = isShadowClip, color = shadowColor ) .then( if (resultShape is CornerBasedShape) cornerModifier else genericModifier ) .then(if (clip) Modifier.clip(resultShape) else Modifier) .then(if (resultPadding > 0.dp) Modifier.padding(resultPadding) else Modifier) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ContiniousRoundedRectangle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.annotation.FloatRange import androidx.annotation.IntRange import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.toRect import androidx.compose.ui.graphics.Outline import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection.Ltr import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn import com.kyant.capsule.Continuity import kotlin.math.min @Immutable open class ContinuousRoundedRectangle( topStart: CornerSize, topEnd: CornerSize, bottomEnd: CornerSize, bottomStart: CornerSize, open val continuity: Continuity = Continuity.Default ) : CornerBasedShape( topStart = topStart, topEnd = topEnd, bottomEnd = bottomEnd, bottomStart = bottomStart ) { override fun createOutline( size: Size, topStart: Float, topEnd: Float, bottomEnd: Float, bottomStart: Float, layoutDirection: LayoutDirection ): Outline { if (topStart + topEnd + bottomEnd + bottomStart == 0f) { return Outline.Rectangle(size.toRect()) } val maxRadius = min(size.width, size.height) * 0.5f val topLeft = (if (layoutDirection == Ltr) topStart else topEnd).fastCoerceIn(0f, maxRadius) val topRight = (if (layoutDirection == Ltr) topEnd else topStart).fastCoerceIn(0f, maxRadius) val bottomRight = (if (layoutDirection == Ltr) bottomEnd else bottomStart).fastCoerceIn(0f, maxRadius) val bottomLeft = (if (layoutDirection == Ltr) bottomStart else bottomEnd).fastCoerceIn(0f, maxRadius) return continuity.createRoundedRectangleOutline( size = size, topLeft = topLeft, topRight = topRight, bottomRight = bottomRight, bottomLeft = bottomLeft ) } override fun copy( topStart: CornerSize, topEnd: CornerSize, bottomEnd: CornerSize, bottomStart: CornerSize ): ContinuousRoundedRectangle { return ContinuousRoundedRectangle( topStart = topStart, topEnd = topEnd, bottomEnd = bottomEnd, bottomStart = bottomStart, continuity = continuity ) } fun copy( topStart: CornerSize = this.topStart, topEnd: CornerSize = this.topEnd, bottomEnd: CornerSize = this.bottomEnd, bottomStart: CornerSize = this.bottomStart, continuity: Continuity = this.continuity ): ContinuousRoundedRectangle { return ContinuousRoundedRectangle( topStart = topStart, topEnd = topEnd, bottomEnd = bottomEnd, bottomStart = bottomStart, continuity = continuity ) } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ContinuousRoundedRectangle) return false if (topStart != other.topStart) return false if (topEnd != other.topEnd) return false if (bottomEnd != other.bottomEnd) return false if (bottomStart != other.bottomStart) return false if (continuity != other.continuity) return false return true } override fun hashCode(): Int { var result = topStart.hashCode() result = 31 * result + topEnd.hashCode() result = 31 * result + bottomEnd.hashCode() result = 31 * result + bottomStart.hashCode() result = 31 * result + continuity.hashCode() return result } override fun toString(): String { return "ContinuousRoundedRectangle(topStart=$topStart, topEnd=$topEnd, bottomEnd=$bottomEnd, " + "bottomStart=$bottomStart, continuity=$continuity)" } } @Stable val ContinuousRectangle: ContinuousRoundedRectangle = ContinuousRectangleImpl() @Suppress("FunctionName") @Stable fun ContinuousRectangle(continuity: Continuity = Continuity.Default): ContinuousRoundedRectangle = ContinuousRectangleImpl(continuity) @Immutable private data class ContinuousRectangleImpl( override val continuity: Continuity = Continuity.Default ) : ContinuousRoundedRectangle( topStart = ZeroCornerSize, topEnd = ZeroCornerSize, bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize, continuity = continuity ) { override fun toString(): String { return "ContinuousRectangle(continuity=$continuity)" } } private val FullCornerSize = CornerSize(50) @Stable val ContinuousCapsule: ContinuousRoundedRectangle = ContinuousCapsule() @Suppress("FunctionName") @Stable fun ContinuousCapsule(continuity: Continuity = Continuity.Default): ContinuousRoundedRectangle = ContinuousCapsuleImpl(continuity) @Immutable private data class ContinuousCapsuleImpl( override val continuity: Continuity = Continuity.Default ) : ContinuousRoundedRectangle( topStart = FullCornerSize, topEnd = FullCornerSize, bottomEnd = FullCornerSize, bottomStart = FullCornerSize, continuity = continuity ) { override fun createOutline( size: Size, topStart: Float, topEnd: Float, bottomEnd: Float, bottomStart: Float, layoutDirection: LayoutDirection ): Outline = continuity.createCapsuleOutline(size) override fun toString(): String { return "ContinuousCapsule(continuity=$continuity)" } } @Stable fun ContinuousRoundedRectangle( corner: CornerSize, continuity: Continuity = Continuity.Default ): ContinuousRoundedRectangle = ContinuousRoundedRectangle( topStart = corner, topEnd = corner, bottomEnd = corner, bottomStart = corner, continuity = continuity ) @Stable fun ContinuousRoundedRectangle( size: Dp, continuity: Continuity = Continuity.Default ): ContinuousRoundedRectangle = ContinuousRoundedRectangle( corner = CornerSize(size), continuity = continuity ) @Stable fun ContinuousRoundedRectangle( @FloatRange(from = 0.0) size: Float, continuity: Continuity = Continuity.Default ): ContinuousRoundedRectangle = ContinuousRoundedRectangle( corner = CornerSize(size), continuity = continuity ) @Stable fun ContinuousRoundedRectangle( @IntRange(from = 0, to = 100) percent: Int, continuity: Continuity = Continuity.Default ): ContinuousRoundedRectangle = ContinuousRoundedRectangle( corner = CornerSize(percent), continuity = continuity ) @Stable fun ContinuousRoundedRectangle( topStart: Dp = 0f.dp, topEnd: Dp = 0f.dp, bottomEnd: Dp = 0f.dp, bottomStart: Dp = 0f.dp, continuity: Continuity = Continuity.Default ): ContinuousRoundedRectangle = ContinuousRoundedRectangle( topStart = CornerSize(topStart), topEnd = CornerSize(topEnd), bottomEnd = CornerSize(bottomEnd), bottomStart = CornerSize(bottomStart), continuity = continuity ) @Stable fun ContinuousRoundedRectangle( @FloatRange(from = 0.0) topStart: Float = 0f, @FloatRange(from = 0.0) topEnd: Float = 0f, @FloatRange(from = 0.0) bottomEnd: Float = 0f, @FloatRange(from = 0.0) bottomStart: Float = 0f, continuity: Continuity = Continuity.Default ): ContinuousRoundedRectangle = ContinuousRoundedRectangle( topStart = CornerSize(topStart), topEnd = CornerSize(topEnd), bottomEnd = CornerSize(bottomEnd), bottomStart = CornerSize(bottomStart), continuity = continuity ) @Stable fun ContinuousRoundedRectangle( @IntRange(from = 0, to = 100) topStartPercent: Int = 0, @IntRange(from = 0, to = 100) topEndPercent: Int = 0, @IntRange(from = 0, to = 100) bottomEndPercent: Int = 0, @IntRange(from = 0, to = 100) bottomStartPercent: Int = 0, continuity: Continuity = Continuity.Default ): ContinuousRoundedRectangle = ContinuousRoundedRectangle( topStart = CornerSize(topStartPercent), topEnd = CornerSize(topEndPercent), bottomEnd = CornerSize(bottomEndPercent), bottomStart = CornerSize(bottomStartPercent), continuity = continuity ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DetectSwipe.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.gestures.detectVerticalDragGestures import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput fun Modifier.onSwipeLeft(onSwipe: () -> Unit): Modifier { var dx = 0F return this.pointerInput(Unit) { detectHorizontalDragGestures( onDragEnd = { if (dx < 0) { dx = 0F onSwipe() } }, onHorizontalDrag = { _, dragAmount -> dx = dragAmount } ) } } fun Modifier.onSwipeRight(onSwipe: () -> Unit): Modifier { var dx = 0F return this.pointerInput(Unit) { detectHorizontalDragGestures( onDragEnd = { if (dx > 0) { dx = 0F onSwipe() } }, onHorizontalDrag = { _, dragAmount -> dx = dragAmount } ) } } fun Modifier.onSwipeDown( enabled: Boolean = true, onSwipe: () -> Unit ): Modifier { if (!enabled) return this var dy = 0F return this.pointerInput(Unit) { detectVerticalDragGestures( onDragEnd = { if (dy > 0) { dy = 0F onSwipe() } }, onVerticalDrag = { _, dragAmount -> dy = dragAmount } ) } } fun Modifier.detectSwipes( key: Any? = Unit, onSwipeLeft: () -> Unit, onSwipeRight: () -> Unit ): Modifier { var dx = 0F return this.pointerInput(key) { detectHorizontalDragGestures( onDragEnd = { if (dx > 0) { onSwipeRight() } else { onSwipeLeft() } dx = 0F }, onHorizontalDrag = { _, dragAmount -> dx = dragAmount } ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DragHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import android.annotation.SuppressLint import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.compose.ui.unit.toIntRect import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import kotlinx.coroutines.delay import kotlinx.coroutines.isActive /** [Modifier] which helps you to implement google photos selection grid, to make it work pass item key which is should be "[key]-index" **/ @SuppressLint("UnnecessaryComposedModifier") fun Modifier.dragHandler( key: Any?, isVertical: Boolean, lazyGridState: LazyGridState, selectedItems: MutableState>, onSelectionChange: (Set) -> Unit = {}, enabled: Boolean = true, onTap: (Int) -> Unit = {}, onLongTap: (Int) -> Unit = {}, shouldHandleLongTap: Boolean = true, tapEnabled: Boolean = true ): Modifier = this.composed { val haptics = LocalHapticFeedback.current val isRtl = !isVertical && LocalLayoutDirection.current == LayoutDirection.Rtl val autoScrollThreshold = with(LocalDensity.current) { 40.dp.toPx() } val autoScrollSpeed: MutableState = remember { mutableFloatStateOf(0f) } LaunchedEffect(autoScrollSpeed.value) { if (autoScrollSpeed.value != 0f) { while (isActive) { lazyGridState.scrollBy(autoScrollSpeed.value) delay(10) } } } val isPortrait by isPortraitOrientationAsState() Modifier .pointerInput(key, tapEnabled, enabled, isRtl, isPortrait) { if (enabled) { detectTapGestures { offset -> lazyGridState .gridItemKeyAtPosition( if (isRtl) offset.copy(x = size.width - offset.x) else offset ) ?.let { key -> if (tapEnabled) { val newItems = if (selectedItems.value.contains(key)) { selectedItems.value - key } else { selectedItems.value + key } selectedItems.update { newItems } onSelectionChange(newItems) } haptics.longPress() onTap(key - 1) } } } } .pointerInput(key, shouldHandleLongTap, enabled, isRtl, isPortrait) { if (enabled) { var initialKey: Int? = null var currentKey: Int? = null detectDragGesturesAfterLongPress( onDragStart = { offset -> lazyGridState .gridItemKeyAtPosition( if (isRtl) offset.copy(x = size.width - offset.x) else offset ) ?.let { key -> if (!selectedItems.value.contains(key) && shouldHandleLongTap) { initialKey = key currentKey = key val newItems = selectedItems.value + key selectedItems.update { newItems } onSelectionChange(newItems) } haptics.longPress() onLongTap(key - 1) } }, onDragCancel = { initialKey = null autoScrollSpeed.value = 0f }, onDragEnd = { initialKey = null autoScrollSpeed.value = 0f }, onDrag = { change, _ -> if (initialKey != null) { val position = if (isRtl) change.position.copy(x = size.width - change.position.x) else change.position val distFromBottom = if (isVertical) { lazyGridState.layoutInfo.viewportSize.height - position.y } else lazyGridState.layoutInfo.viewportSize.width - position.x val distFromTop = if (isVertical) { position.y } else position.x autoScrollSpeed.value = when { distFromBottom < autoScrollThreshold -> autoScrollThreshold - distFromBottom distFromTop < autoScrollThreshold -> -(autoScrollThreshold - distFromTop) else -> 0f } lazyGridState .gridItemKeyAtPosition(position) ?.let { key -> if (currentKey != key) { val newItems = selectedItems.value .minus(initialKey!!..currentKey!!) .minus(currentKey!!..initialKey!!) .plus(initialKey!!..key) .plus(key..initialKey!!) selectedItems.update { newItems } onSelectionChange(newItems) currentKey = key } } } } ) } } } private fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? { val find = layoutInfo.visibleItemsInfo.find { itemInfo -> itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset) } val itemKey = find?.key return itemKey?.toString()?.takeLastWhile { it != '-' }?.toIntOrNull() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/DrawHorizontalStroke.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.animateDpAsState import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isUnspecified import androidx.compose.ui.zIndex import com.gigamole.composeshadowsplus.softlayer.softLayerShadow import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant fun Modifier.drawHorizontalStroke( top: Boolean = false, height: Dp = Dp.Unspecified, autoElevation: Dp = 6.dp, enabled: Boolean = true ) = this.composed { if (!enabled) return@composed Modifier val settingsState = LocalSettingsState.current val borderWidth = settingsState.borderWidth val h = if (height.isUnspecified) { borderWidth.takeIf { it > 0.dp } } else height val color = MaterialTheme.colorScheme.outlineVariant( 0.1f, onTopOf = MaterialTheme.colorScheme.surfaceContainer ) val shadow = Modifier .softLayerShadow( spread = (-2).dp, shape = RectangleShape, radius = animateDpAsState( if (h == null && settingsState.drawAppBarShadows) { autoElevation } else 0.dp ).value ) .zIndex(100f) if (h == null) { shadow } else { val heightPx = with(LocalDensity.current) { h.toPx() } Modifier .zIndex(100f) .drawWithContent { drawContent() drawRect( color = color, topLeft = if (top) Offset(0f, 0f) else Offset(0f, this.size.height), size = Size(this.size.width, heightPx) ) } .then(shadow) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/FadingEdges.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import android.annotation.SuppressLint import androidx.compose.foundation.ScrollState import androidx.compose.foundation.gestures.ScrollableState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.gigamole.composefadingedges.FadingEdgesGravity import com.gigamole.composefadingedges.content.FadingEdgesContentType import com.gigamole.composefadingedges.content.scrollconfig.FadingEdgesScrollConfig import com.gigamole.composefadingedges.fill.FadingEdgesFillType import com.gigamole.composefadingedges.horizontalFadingEdges import com.gigamole.composefadingedges.verticalFadingEdges @SuppressLint("UnnecessaryComposedModifier") fun Modifier.fadingEdges( scrollableState: ScrollableState? = null, color: Color = Color.Unspecified, isVertical: Boolean = false, spanCount: Int? = null, scrollFactor: Float = 1.25f, enabled: Boolean = true, length: Dp = 16.dp, gravity: FadingEdgesGravity = FadingEdgesGravity.All ) = this.composed { if (!enabled) Modifier else { val fillType = if (color.isSpecified) { FadingEdgesFillType.FadeColor( color = color ) } else { FadingEdgesFillType.FadeClip() } val scrollConfig = FadingEdgesScrollConfig.Dynamic( scrollFactor = scrollFactor ) when (scrollableState) { is ScrollState -> { if (isVertical) { Modifier.verticalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Scroll( scrollConfig = scrollConfig, state = scrollableState ), fillType = fillType, length = length ) } else { Modifier.horizontalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Scroll( scrollConfig = scrollConfig, state = scrollableState ), fillType = fillType, length = length ) } } is LazyListState -> { if (isVertical) { Modifier.verticalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Lazy.List( scrollConfig = scrollConfig, state = scrollableState ), fillType = fillType, length = length ) } else { Modifier.horizontalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Lazy.List( scrollConfig = scrollConfig, state = scrollableState ), fillType = fillType, length = length ) } } is LazyGridState -> { if (isVertical) { Modifier.verticalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Lazy.Grid( scrollConfig = scrollConfig, state = scrollableState, spanCount = spanCount ?: remember(scrollableState) { derivedStateOf { scrollableState.layoutInfo.maxSpan } }.value ), fillType = fillType, length = length ) } else { Modifier.horizontalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Lazy.Grid( scrollConfig = scrollConfig, state = scrollableState, spanCount = spanCount ?: remember(scrollableState) { derivedStateOf { scrollableState.layoutInfo.maxSpan } }.value ), fillType = fillType, length = length ) } } is LazyStaggeredGridState -> { require(spanCount != null) if (isVertical) { Modifier.verticalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid( scrollConfig = scrollConfig, state = scrollableState, spanCount = spanCount ), fillType = fillType, length = length ) } else { Modifier.horizontalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Dynamic.Lazy.StaggeredGrid( scrollConfig = scrollConfig, state = scrollableState, spanCount = spanCount ), fillType = fillType, length = length ) } } else -> { if (isVertical) { Modifier.verticalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Static, fillType = fillType, length = length ) } else { Modifier.horizontalFadingEdges( gravity = gravity, contentType = FadingEdgesContentType.Static, fillType = fillType, length = length ) } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/HelperGrid.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.tooling.preview.Preview import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.toColor @Stable @Immutable data class HelperGridParams( val color: Int = Color.Black.copy(0.5f).toArgb(), val cellWidth: Float = 20f, val cellHeight: Float = 20f, val linesWidth: Float = 0f, val enabled: Boolean = false, val withPrimaryLines: Boolean = false ) fun Modifier.drawHelperGrid( params: HelperGridParams, ) = drawWithCache { with(params) { val width = size.width val height = size.height val canvasSize = IntegerSize( width = width.toInt(), height = height.toInt() ) val cellWidthPx = cellWidth.pt.toPx(canvasSize) val cellHeightPx = cellHeight.pt.toPx(canvasSize) val linesWidthPx = linesWidth.pt.toPx(canvasSize) val horizontalSteps = (width / cellWidthPx).toInt() val verticalSteps = (height / cellHeightPx).toInt() val composeColor = color.toColor() onDrawWithContent { drawContent() if (enabled) { for (x in 0..horizontalSteps) { drawLine( color = composeColor, start = Offset(x = x * cellWidthPx, y = 0f), end = Offset(x = x * cellWidthPx, y = height), strokeWidth = if (x % 5 == 0 && x != 0 && withPrimaryLines) { if (linesWidthPx == 0f) 2f else linesWidthPx * 2 } else { linesWidthPx } ) } for (y in 0..verticalSteps) { drawLine( color = composeColor, start = Offset(x = 0f, y = y * cellHeightPx), end = Offset(x = width, y = y * cellHeightPx), strokeWidth = if (y % 5 == 0 && y != 0 && withPrimaryLines) { if (linesWidthPx == 0f) 2f else linesWidthPx * 2 } else { linesWidthPx } ) } } } } } @Composable @Preview private fun Preview() = ImageToolboxThemeForPreview(false) { Surface( modifier = Modifier .fillMaxSize() .drawHelperGrid( HelperGridParams( enabled = true, withPrimaryLines = true ) ) ) { } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/LayoutCorners.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import android.content.Context import android.os.Build import android.view.RoundedCorner import android.view.View import android.view.WindowManager import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.boundsInRoot import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp fun Modifier.withLayoutCorners( block: Modifier.(LayoutCorners) -> Modifier ): Modifier = composed { val context = LocalContext.current val density = LocalDensity.current val screenInfo = remember(context) { context.getScreenInfo(density) } if (screenInfo != null) { val rootView = LocalView.current val layoutDirection = LocalLayoutDirection.current var positionOnScreen by remember { mutableStateOf(null) } val corners = getLayoutCorners(screenInfo, positionOnScreen, layoutDirection) onGloballyPositioned { cords -> positionOnScreen = getBoundsOnScreen(rootView = rootView, boundsInRoot = cords.boundsInRoot()) }.block(corners) } else { block(LayoutCorners()) } } private fun Context.getScreenInfo(density: Density): ScreenInfo? { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { return null } val windowMetrics = getSystemService(WindowManager::class.java)?.maximumWindowMetrics ?: return null val insets = windowMetrics.windowInsets return with(density) { ScreenInfo( cornerRadii = CornerRadii( topLeft = insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)?.radius?.toDp(), topRight = insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT)?.radius?.toDp(), bottomRight = insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT)?.radius?.toDp(), bottomLeft = insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT)?.radius?.toDp(), ), width = windowMetrics.bounds.width(), height = windowMetrics.bounds.height(), ) } } private fun getLayoutCorners( screenInfo: ScreenInfo, positionOnScreen: Rect?, layoutDirection: LayoutDirection, ): LayoutCorners { if (positionOnScreen == null) { return LayoutCorners() } val (cornerRadius, screenWidth, screenHeight) = screenInfo val (left, top, right, bottom) = positionOnScreen val topLeft = getLayoutCorner( radius = cornerRadius.topLeft, isFixed = (left <= 0) && (top <= 0) ) val topRight = getLayoutCorner( radius = cornerRadius.topRight, isFixed = (right >= screenWidth) && (top <= 0) ) val bottomRight = getLayoutCorner( radius = cornerRadius.bottomRight, isFixed = (right >= screenWidth) && (bottom >= screenHeight) ) val bottomLeft = getLayoutCorner( radius = cornerRadius.bottomLeft, isFixed = (left <= 0) && (bottom >= screenHeight) ) return when (layoutDirection) { LayoutDirection.Ltr -> LayoutCorners( topStart = topLeft, topEnd = topRight, bottomEnd = bottomRight, bottomStart = bottomLeft ) LayoutDirection.Rtl -> LayoutCorners( topStart = topRight, topEnd = topLeft, bottomEnd = bottomLeft, bottomStart = bottomRight ) } } private fun getLayoutCorner( radius: Dp?, isFixed: Boolean ): LayoutCorner = if (radius == null) { LayoutCorner() } else { LayoutCorner( radius = radius, isFixed = isFixed ) } private fun getBoundsOnScreen( rootView: View, boundsInRoot: Rect ): Rect { val rootViewLeftTopOnScreen = IntArray(2) rootView.getLocationOnScreen(rootViewLeftTopOnScreen) val (rootViewLeftOnScreen, rootViewTopOnScreen) = rootViewLeftTopOnScreen return Rect( left = rootViewLeftOnScreen + boundsInRoot.left, top = rootViewTopOnScreen + boundsInRoot.top, right = rootViewLeftOnScreen + boundsInRoot.right, bottom = rootViewTopOnScreen + boundsInRoot.bottom, ) } private data class ScreenInfo( val cornerRadii: CornerRadii, val width: Int, val height: Int, ) private data class CornerRadii( val topLeft: Dp? = null, val topRight: Dp? = null, val bottomRight: Dp? = null, val bottomLeft: Dp? = null, ) data class LayoutCorners( val topStart: LayoutCorner = LayoutCorner(), val topEnd: LayoutCorner = LayoutCorner(), val bottomEnd: LayoutCorner = LayoutCorner(), val bottomStart: LayoutCorner = LayoutCorner(), ) data class LayoutCorner( val radius: Dp = 16.dp, val isFixed: Boolean = false, ) fun LayoutCorners.toShape(progress: Float): RoundedCornerShape = RoundedCornerShape( topStart = topStart.getProgressRadius(progress), topEnd = topEnd.getProgressRadius(progress), bottomEnd = bottomEnd.getProgressRadius(progress), bottomStart = bottomStart.getProgressRadius(progress), ) private fun LayoutCorner.getProgressRadius(progress: Float): Dp = if (isFixed && progress > 0f) radius else radius * progress ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Line.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import kotlin.math.abs import kotlin.math.cos import kotlin.math.sin @Stable @Immutable data class Line( val startX: Float, val startY: Float, val endX: Float, val endY: Float ) { companion object { val CenterVertical = Line( startX = 0.5f, startY = 0f, endX = 0.5f, endY = 1f ) val CenterHorizontal = Line( startX = 0f, startY = 0.5f, endX = 1f, endY = 0.5f ) @Suppress("FunctionName") fun Rotated(angle: Float): Line = if (abs(angle) % 360 != 0f) { CenterVertical.rotate(angle) } else { CenterVertical } @Suppress("FunctionName") fun Bundle(size: Int): List = if (size > 0) { List(size) { Rotated(it * (360f / size)) } } else { listOf() } } fun rotate(angle: Float): Line { val centerX = (startX + endX) / 2 val centerY = (startY + endY) / 2 val radians = Math.toRadians(angle.toDouble()).toFloat() val cosA = cos(radians) val sinA = sin(radians) fun rotatePoint( x: Float, y: Float ): Pair { val dx = x - centerX val dy = y - centerY val newX = centerX + dx * cosA - dy * sinA val newY = centerY + dx * sinA + dy * cosA return newX to newY } val (newStartX, newStartY) = rotatePoint(startX, startY) val (newEndX, newEndY) = rotatePoint(endX, endY) return copy(startX = newStartX, startY = newStartY, endX = newEndX, endY = newEndY) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MaterialShadow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import android.os.Build import androidx.compose.animation.core.animateDpAsState import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.gigamole.composeshadowsplus.rsblur.rsBlurShadow import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.zedalpha.shadowgadgets.compose.clippedShadow fun Modifier.materialShadow( shape: Shape, elevation: Dp, enabled: Boolean = true, isClipped: Boolean = true, color: Color = Color.Black ) = composed { val elev = animateDpAsState(if (enabled) elevation else 0.dp).value if (elev > 0.dp) { val shape by remember(shape) { derivedStateOf { if ((shape is AnimatedShape && shape.shapesType is ShapeType.Smooth)) { @Stable object : Shape by shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline = shape.createOutline( size = size, layoutDirection = layoutDirection, density = density, shapesType = ShapeType.Rounded() ) } } else shape } } val isConcavePath by remember(shape) { derivedStateOf { shape.createOutline( size = Size(1f, 1f), layoutDirection = LayoutDirection.Ltr, density = Density(1f) ).let { it is Outline.Generic && !it.path.isConvex } } } when { isConcavePath && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q -> { val api21Shadow = Modifier.rsBlurShadow( shape = shape, radius = elev, isAlphaContentClip = isClipped, color = color ) api21Shadow } else -> { val api29Shadow = if (isClipped) { Modifier.clippedShadow( shape = shape, elevation = elev, ambientColor = color, spotColor = color ) } else { Modifier.shadow( shape = shape, elevation = elev, ambientColor = color, spotColor = color ) } api29Shadow } } } else Modifier } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/MeshGradient.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathMeasure import androidx.compose.ui.graphics.PointMode import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.VertexMode import androidx.compose.ui.graphics.Vertices import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.lerp @Composable fun Modifier.meshGradient( points: List>>, resolutionX: Int = 1, resolutionY: Int = 1, showPoints: Boolean = false, indicesModifier: (List) -> List = { it }, alpha: Float = 1f ): Modifier { val pointData by remember(points, resolutionX, resolutionY) { derivedStateOf { PointData(points, resolutionX, resolutionY) } } return drawBehind { drawIntoCanvas { canvas -> canvas.drawMeshGradient( pointData = pointData, showPoints = showPoints, indicesModifier = indicesModifier, size = size, alpha = alpha ) } } } fun Canvas.drawMeshGradient( pointData: PointData, showPoints: Boolean = false, indicesModifier: (List) -> List = { it }, size: Size, alpha: Float ) { CanvasDrawScope().apply { drawContext.canvas = this@drawMeshGradient drawContext.size = size with(drawContext.canvas) { scale( scaleX = size.width, scaleY = size.height, pivot = Offset.Zero ) { drawVertices( vertices = Vertices( vertexMode = VertexMode.Triangles, positions = pointData.offsets, textureCoordinates = pointData.offsets, colors = pointData.colors, indices = indicesModifier(pointData.indices) ), blendMode = BlendMode.Dst, paint = Paint().apply { this.alpha = alpha } ) } if (showPoints) { val flattenedPaint = Paint() flattenedPaint.color = Color.White.copy(alpha = .9f) flattenedPaint.strokeWidth = 4f * .001f flattenedPaint.strokeCap = StrokeCap.Round flattenedPaint.blendMode = BlendMode.SrcOver scale( scaleX = size.width, scaleY = size.height, pivot = Offset.Zero ) { drawPoints( pointMode = PointMode.Points, points = pointData.offsets, paint = flattenedPaint ) } } } } } class PointData( private val points: List>>, private val stepsX: Int, private val stepsY: Int, ) { val offsets: MutableList val colors: MutableList val indices: List private val xLength: Int = (points[0].size * stepsX) - (stepsX - 1) private val yLength: Int = (points.size * stepsY) - (stepsY - 1) private val measure = PathMeasure() private val indicesBlocks: List init { offsets = buildList { repeat((xLength - 0) * (yLength - 0)) { add(Offset(0f, 0f)) } }.toMutableList() colors = buildList { repeat((xLength - 0) * (yLength - 0)) { add(Color.Transparent) } }.toMutableList() indicesBlocks = buildList { for (y in 0..yLength - 2) { for (x in 0..xLength - 2) { val a = (y * xLength) + x val b = a + 1 val c = ((y + 1) * xLength) + x val d = c + 1 add( IndicesBlock( indices = buildList { add(a) add(c) add(d) add(a) add(b) add(d) }, x = x, y = y ) ) } } } indices = indicesBlocks.flatMap { it.indices } generateInterpolatedOffsets() } private fun generateInterpolatedOffsets() { for (y in 0..points.lastIndex) { for (x in 0..points[y].lastIndex) { this[x * stepsX, y * stepsY] = points[y][x].first this[x * stepsX, y * stepsY] = points[y][x].second if (x != points[y].lastIndex) { val path = cubicPathX( point1 = points[y][x].first, point2 = points[y][x + 1].first, when (x) { 0 -> 0 points[y].lastIndex - 1 -> 2 else -> 1 } ) measure.setPath(path, false) for (i in 1.. 0 points[y].lastIndex - 1 -> 2 else -> 1 } ) measure.setPath(path, false) for (i in (1.., val x: Int, val y: Int ) operator fun get( x: Int, y: Int ): Offset { val index = (y * xLength) + x return offsets[index] } private fun getColor( x: Int, y: Int ): Color { val index = (y * xLength) + x return colors[index] } private operator fun set( x: Int, y: Int, offset: Offset ) { val index = (y * xLength) + x offsets[index] = Offset(offset.x, offset.y) } private operator fun set( x: Int, y: Int, color: Color ) { val index = (y * xLength) + x colors[index] = color } } private fun cubicPathX( point1: Offset, point2: Offset, position: Int ): Path { val path = Path().apply { moveTo(point1.x, point1.y) val delta = (point2.x - point1.x) * .5f when (position) { 0 -> cubicTo( point1.x, point1.y, point2.x - delta, point2.y, point2.x, point2.y ) 2 -> cubicTo( point1.x + delta, point1.y, point2.x, point2.y, point2.x, point2.y ) else -> cubicTo( point1.x + delta, point1.y, point2.x - delta, point2.y, point2.x, point2.y ) } lineTo(point2.x, point2.y) } return path } private fun cubicPathY( point1: Offset, point2: Offset, position: Int ): Path { val path = Path().apply { moveTo(point1.x, point1.y) val delta = (point2.y - point1.y) * .5f when (position) { 0 -> cubicTo( point1.x, point1.y, point2.x, point2.y - delta, point2.x, point2.y ) 2 -> cubicTo( point1.x, point1.y + delta, point2.x, point2.y, point2.x, point2.y ) else -> cubicTo( point1.x, point1.y + delta, point2.x, point2.y - delta, point2.x, point2.y ) } lineTo(point2.x, point2.y) } return path } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/NegativePadding.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.foundation.layout.PaddingValues import androidx.compose.ui.Modifier import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset fun Modifier.negativePadding( paddingValues: PaddingValues ): Modifier = this.layout { measurable, constraints -> val horizontal = paddingValues.calculateLeftPadding(layoutDirection).roundToPx() + paddingValues.calculateRightPadding(layoutDirection).roundToPx() val vertical = paddingValues.calculateTopPadding().roundToPx() + paddingValues.calculateBottomPadding().roundToPx() val placeable = measurable.measure(constraints.offset(horizontal, vertical)) layout( width = placeable.measuredWidth, height = placeable.measuredHeight ) { placeable.place(0, 0) } } fun Modifier.negativePadding( all: Dp ): Modifier = negativePadding( PaddingValues(all) ) fun Modifier.negativePadding( horizontal: Dp = 0.dp, vertical: Dp = 0.dp ) = negativePadding( PaddingValues( horizontal = horizontal, vertical = vertical ) ) fun Modifier.negativePadding( start: Dp = 0.dp, top: Dp = 0.dp, end: Dp = 0.dp, bottom: Dp = 0.dp ) = negativePadding( PaddingValues( start = start, top = top, end = end, bottom = bottom ) ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ObservePointersCount.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.util.fastAny import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.isActive import kotlin.coroutines.cancellation.CancellationException fun Modifier.observePointersCountWithOffset( enabled: Boolean = true, onChange: (Int, Offset) -> Unit ) = this then if (enabled) Modifier.pointerInput(Unit) { onEachGesture { val context = currentCoroutineContext() awaitPointerEventScope { do { val event = awaitPointerEvent() onChange( event.changes.size, event.changes.firstOrNull()?.position ?: Offset.Unspecified ) } while (event.changes.any { it.pressed } && context.isActive) onChange(0, Offset.Unspecified) } } } else Modifier fun Modifier.observePointersCount( enabled: Boolean = true, onChange: (Int) -> Unit ) = this.observePointersCountWithOffset(enabled) { size, _ -> onChange(size) } suspend fun PointerInputScope.onEachGesture(block: suspend PointerInputScope.() -> Unit) { val currentContext = currentCoroutineContext() while (currentContext.isActive) { try { block() // Wait for all pointers to be up. Gestures start when a finger goes down. awaitAllPointersUp() } catch (e: CancellationException) { if (currentContext.isActive) { // The current gesture was canceled. Wait for all fingers to be "up" before looping // again. awaitAllPointersUp() } else { // forEachGesture was cancelled externally. Rethrow the cancellation exception to // propagate it upwards. throw e } } } } private suspend fun PointerInputScope.awaitAllPointersUp() { awaitPointerEventScope { awaitAllPointersUp() } } private suspend fun AwaitPointerEventScope.awaitAllPointersUp() { if (!allPointersUp()) { do { val events = awaitPointerEvent(PointerEventPass.Final) } while (events.changes.fastAny { it.pressed }) } } private fun AwaitPointerEventScope.allPointersUp(): Boolean = !currentEvent.changes.fastAny { it.pressed } @Composable fun smartDelayAfterDownInMillis(pointersCount: Int): Long { var delayAfterDownInMillis by remember { mutableLongStateOf(20L) } var previousCount by remember { mutableIntStateOf(pointersCount) } LaunchedEffect(pointersCount) { delayAfterDownInMillis = if (pointersCount <= 1 && previousCount >= 2) 5L else 20L previousCount = pointersCount } return delayAfterDownInMillis } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Placholder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.annotation.FloatRange import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.InfiniteRepeatableSpec import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.rememberTransition import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.toRect import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.node.Ref import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.util.lerp import kotlin.math.max /** * Contains default values used by [Modifier.placeholder] and [PlaceholderHighlight]. */ object PlaceholderDefaults { /** * The default [InfiniteRepeatableSpec] to use for [fade]. */ val fadeAnimationSpec: InfiniteRepeatableSpec by lazy { infiniteRepeatable( animation = tween(delayMillis = 200, durationMillis = 600), repeatMode = RepeatMode.Reverse, ) } /** * The default [InfiniteRepeatableSpec] to use for [shimmer]. */ val shimmerAnimationSpec: InfiniteRepeatableSpec by lazy { infiniteRepeatable( animation = tween(durationMillis = 1700, delayMillis = 200), repeatMode = RepeatMode.Restart ) } } /** * Draws some skeleton UI which is typically used whilst content is 'loading'. * * A version of this modifier which uses appropriate values for Material themed apps is available * in the 'Placeholder Material' library. * * You can provide a [PlaceholderHighlight] which runs an highlight animation on the placeholder. * The [shimmer] and [fade] implementations are provided for easy usage. * * A cross-fade transition will be applied to the content and placeholder UI when the [visible] * value changes. The transition can be customized via the [contentFadeTransitionSpec] and * [placeholderFadeTransitionSpec] parameters. * * You can find more information on the pattern at the Material Theming * [Placeholder UI](https://material.io/design/communication/launch-screen.html#placeholder-ui) * guidelines. * * @param visible whether the placeholder should be visible or not. * @param color the color used to draw the placeholder UI. * @param shape desired shape of the placeholder. Defaults to [RectangleShape]. * @param highlight optional highlight animation. * @param placeholderFadeTransitionSpec The transition spec to use when fading the placeholder * on/off screen. The boolean parameter defined for the transition is [visible]. * @param contentFadeTransitionSpec The transition spec to use when fading the content * on/off screen. The boolean parameter defined for the transition is [visible]. */ fun Modifier.placeholder( visible: Boolean, color: Color, shape: Shape = RectangleShape, highlight: PlaceholderHighlight? = null, placeholderFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() }, contentFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() }, ): Modifier = composed( inspectorInfo = debugInspectorInfo { name = "placeholder" value = visible properties["visible"] = visible properties["color"] = color properties["highlight"] = highlight properties["shape"] = shape } ) { // Values used for caching purposes val lastSize = remember { Ref() } val lastLayoutDirection = remember { Ref() } val lastOutline = remember { Ref() } // The current highlight animation progress var highlightProgress: Float by remember { mutableFloatStateOf(0f) } // This is our crossfade transition val transitionState = remember { MutableTransitionState(visible) }.apply { targetState = visible } val transition = rememberTransition(transitionState, "placeholder_crossfade") val placeholderAlpha by transition.animateFloat( transitionSpec = placeholderFadeTransitionSpec, label = "placeholder_fade", targetValueByState = { placeholderVisible -> if (placeholderVisible) 1f else 0f } ) val contentAlpha by transition.animateFloat( transitionSpec = contentFadeTransitionSpec, label = "content_fade", targetValueByState = { placeholderVisible -> if (placeholderVisible) 0f else 1f } ) // Run the optional animation spec and update the progress if the placeholder is visible val animationSpec = highlight?.animationSpec if (animationSpec != null && (visible || placeholderAlpha >= 0.01f)) { val infiniteTransition = rememberInfiniteTransition(label = "") highlightProgress = infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = animationSpec, label = "", ).value } val paint = remember { Paint() } remember(color, shape, highlight) { drawWithContent { // Draw the composable content first if (contentAlpha in 0.01f..0.99f) { // If the content alpha is between 1% and 99%, draw it in a layer with // the alpha applied paint.alpha = contentAlpha withLayer(paint) { with(this@drawWithContent) { drawContent() } } } else if (contentAlpha >= 0.99f) { // If the content alpha is > 99%, draw it with no alpha drawContent() } if (placeholderAlpha in 0.01f..0.99f) { // If the placeholder alpha is between 1% and 99%, draw it in a layer with // the alpha applied paint.alpha = placeholderAlpha withLayer(paint) { lastOutline.value = drawPlaceholder( shape = shape, color = color, highlight = highlight, progress = highlightProgress, lastOutline = lastOutline.value, lastLayoutDirection = lastLayoutDirection.value, lastSize = lastSize.value, ) } } else if (placeholderAlpha >= 0.99f) { // If the placeholder alpha is > 99%, draw it with no alpha lastOutline.value = drawPlaceholder( shape = shape, color = color, highlight = highlight, progress = highlightProgress, lastOutline = lastOutline.value, lastLayoutDirection = lastLayoutDirection.value, lastSize = lastSize.value, ) } // Keep track of the last size & layout direction lastSize.value = size lastLayoutDirection.value = layoutDirection } } } private fun DrawScope.drawPlaceholder( shape: Shape, color: Color, highlight: PlaceholderHighlight?, progress: Float, lastOutline: Outline?, lastLayoutDirection: LayoutDirection?, lastSize: Size?, ): Outline? { // shortcut to avoid Outline calculation and allocation if (shape === RectangleShape) { // Draw the initial background color drawRect(color = color) if (highlight != null) { drawRect( brush = highlight.brush(progress, size), alpha = highlight.alpha(progress), ) } // We didn't create an outline so return null return null } // Otherwise we need to create an outline from the shape val outline = lastOutline.takeIf { size == lastSize && layoutDirection == lastLayoutDirection } ?: shape.createOutline(size, layoutDirection, this) // Draw the placeholder color drawOutline(outline = outline, color = color) if (highlight != null) { drawOutline( outline = outline, brush = highlight.brush(progress, size), alpha = highlight.alpha(progress), ) } // Return the outline we used return outline } private inline fun DrawScope.withLayer( paint: Paint, drawBlock: DrawScope.() -> Unit, ) = drawIntoCanvas { canvas -> canvas.saveLayer(size.toRect(), paint) drawBlock() canvas.restore() } /** * A class which provides a brush to paint placeholder based on progress. */ @Stable interface PlaceholderHighlight { /** * The optional [AnimationSpec] to use when running the animation for this highlight. */ val animationSpec: InfiniteRepeatableSpec? /** * Return a [Brush] to draw for the given [progress] and [size]. * * @param progress the current animated progress in the range of 0f..1f. * @param size The size of the current layout to draw in. */ fun brush( @FloatRange(from = 0.0, to = 1.0) progress: Float, size: Size ): Brush /** * Return the desired alpha value used for drawing the [Brush] returned from [brush]. * * @param progress the current animated progress in the range of 0f..1f. */ @FloatRange(from = 0.0, to = 1.0) fun alpha(progress: Float): Float companion object } /** * Creates a [Fade] brush with the given initial and target colors. * * @param highlightColor the color of the highlight which is faded in/out. * @param animationSpec the [AnimationSpec] to configure the animation. */ fun PlaceholderHighlight.Companion.fade( highlightColor: Color, animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.fadeAnimationSpec, ): PlaceholderHighlight = Fade( highlightColor = highlightColor, animationSpec = animationSpec, ) /** * Creates a [PlaceholderHighlight] which 'shimmers', using the given [highlightColor]. * * The highlight starts at the top-start, and then grows to the bottom-end during the animation. * During that time it is also faded in, from 0f..progressForMaxAlpha, and then faded out from * progressForMaxAlpha..1f. * * @param highlightColor the color of the highlight 'shimmer'. * @param animationSpec the [AnimationSpec] to configure the animation. * @param progressForMaxAlpha The progress where the shimmer should be at it's peak opacity. * Defaults to 0.6f. */ fun PlaceholderHighlight.Companion.shimmer( highlightColor: Color, animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.shimmerAnimationSpec, @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6f, ): PlaceholderHighlight = Shimmer( highlightColor = highlightColor, animationSpec = animationSpec, progressForMaxAlpha = progressForMaxAlpha, ) private data class Fade( private val highlightColor: Color, override val animationSpec: InfiniteRepeatableSpec, ) : PlaceholderHighlight { private val brush = SolidColor(highlightColor) override fun brush(progress: Float, size: Size): Brush = brush override fun alpha(progress: Float): Float = progress } private data class Shimmer( private val highlightColor: Color, override val animationSpec: InfiniteRepeatableSpec, private val progressForMaxAlpha: Float = 0.6f, ) : PlaceholderHighlight { override fun brush( progress: Float, size: Size, ): Brush = Brush.radialGradient( colors = listOf( highlightColor.copy(alpha = 0f), highlightColor, highlightColor.copy(alpha = 0f), ), center = Offset(x = 0f, y = 0f), radius = (max(size.width, size.height) * progress * 2).coerceAtLeast(0.01f), ) override fun alpha(progress: Float): Float = when { // From 0f...ProgressForOpaqueAlpha we animate from 0..1 progress <= progressForMaxAlpha -> { lerp( start = 0f, stop = 1f, fraction = progress / progressForMaxAlpha ) } // From ProgressForOpaqueAlpha..1f we animate from 1..0 else -> { lerp( start = 1f, stop = 0f, fraction = (progress - progressForMaxAlpha) / (1f - progressForMaxAlpha) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/PointerInput.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputScope import kotlinx.coroutines.coroutineScope suspend fun PointerInputScope.interceptTap( pass: PointerEventPass = PointerEventPass.Initial, onTap: ((Offset) -> Unit)? = null, ) = coroutineScope { if (onTap == null) return@coroutineScope awaitEachGesture { val down = awaitFirstDown(pass = pass) val downTime = System.currentTimeMillis() val tapTimeout = viewConfiguration.longPressTimeoutMillis val tapPosition = down.position do { val event = awaitPointerEvent(pass) val currentTime = System.currentTimeMillis() if (event.changes.size != 1) break // More than one event: not a tap if (currentTime - downTime >= tapTimeout) break // Too slow: not a tap val change = event.changes[0] if ((change.position - tapPosition).getDistance() > viewConfiguration.touchSlop) break if (change.id == down.id && !change.pressed) { change.consume() onTap(change.position) } } while (event.changes.any { it.id == down.id && it.pressed }) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Pulsate.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.scale fun Modifier.pulsate( range: ClosedFloatingPointRange = 0.95f..1.05f, duration: Int = 1000, enabled: Boolean = true ) = this.composed { val infiniteTransition = rememberInfiniteTransition() val scale by infiniteTransition.animateFloat( initialValue = range.start, targetValue = range.endInclusive, animationSpec = infiniteRepeatable( animation = tween(durationMillis = duration), repeatMode = RepeatMode.Reverse ) ) if (enabled) Modifier.scale(scale) else this } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RealisticSnowfall.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import android.annotation.SuppressLint import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.Painter import com.t8rin.snowfall.snowfall import com.t8rin.snowfall.types.FlakeType import kotlin.random.Random @SuppressLint("UnnecessaryComposedModifier") fun Modifier.realisticSnowfall( enabled: Boolean = true ): Modifier = this.composed { if (enabled) { Modifier.snowfall( type = FlakeType.Custom(flakes), color = MaterialTheme.colorScheme.primary ) } else Modifier } private val flakes = List(100) { val size = (40 * Random.nextDouble(0.3, 1.0)).toFloat() object : Painter() { override val intrinsicSize: Size = Size(size, size) override fun DrawScope.onDraw() { drawCircle( brush = Brush.radialGradient( listOf( Color.White, Color.Transparent, ) ) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/RotateAnimation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.rotate fun Modifier.rotateAnimation( range: IntRange = 0..180, duration: Int = 1000, enabled: Boolean = true ) = this.composed { val infiniteTransition = rememberInfiniteTransition() val rotation by infiniteTransition.animateFloat( initialValue = range.first.toFloat(), targetValue = range.last.toFloat(), animationSpec = infiniteRepeatable( animation = tween(durationMillis = duration), repeatMode = RepeatMode.Reverse ) ) if (enabled) Modifier.rotate(rotation) else this } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ScaleOnTap.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.scale import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalHapticFeedback import com.t8rin.imagetoolbox.core.ui.utils.animation.springySpec import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import kotlinx.coroutines.delay fun Modifier.scaleOnTap( initial: Float = 1f, min: Float = 0.8f, max: Float = 1.3f, interactionSource: MutableInteractionSource? = null, onHold: () -> Unit = {}, onRelease: (time: Long) -> Unit ) = this.composed { var scaleState by remember(initial) { mutableFloatStateOf(initial) } val scale by animateFloatAsState( targetValue = scaleState, animationSpec = springySpec() ) val haptics = LocalHapticFeedback.current val onHoldState = rememberUpdatedState(onHold) val onReleaseState = rememberUpdatedState(onRelease) Modifier .scale(scale) .pointerInput(min, max, initial) { detectTapGestures( onPress = { val time = System.currentTimeMillis() scaleState = max haptics.longPress() val press = PressInteraction.Press(it) interactionSource?.emit(press) onHoldState.value() delay(200) tryAwaitRelease() onReleaseState.value(System.currentTimeMillis() - time) haptics.longPress() interactionSource?.emit(PressInteraction.Release(press)) scaleState = min delay(200) scaleState = initial } ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/ShapeDefaults.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("NOTHING_TO_INLINE") package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.CornerSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn import androidx.lifecycle.compose.LifecycleResumeEffect import com.t8rin.imagetoolbox.core.domain.utils.autoCast import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.animation.lessSpringySpec import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch object ShapeDefaults { @Composable fun byIndex( index: Int, size: Int, forceDefault: Boolean = false, vertical: Boolean = true ): Shape { val internalShape = when { index == -1 || size == 1 || forceDefault -> default index == 0 && size > 1 -> if (vertical) top else start index == size - 1 -> if (vertical) bottom else end else -> center } return AutoCornersShape( topStart = internalShape.topStart.animate(), topEnd = internalShape.topEnd.animate(), bottomStart = internalShape.bottomStart.animate(), bottomEnd = internalShape.bottomEnd.animate() ) } val top @Composable get() = AutoCornersShape( topStart = 16.dp, topEnd = 16.dp, bottomStart = 4.dp, bottomEnd = 4.dp ) val center @Composable get() = AutoCornersShape( topStart = 4.dp, topEnd = 4.dp, bottomStart = 4.dp, bottomEnd = 4.dp ) val bottom @Composable get() = AutoCornersShape( topStart = 4.dp, topEnd = 4.dp, bottomStart = 16.dp, bottomEnd = 16.dp ) val start @Composable get() = AutoCornersShape( topStart = 16.dp, topEnd = 4.dp, bottomStart = 16.dp, bottomEnd = 4.dp ) val end @Composable get() = AutoCornersShape( topStart = 4.dp, topEnd = 16.dp, bottomStart = 4.dp, bottomEnd = 16.dp ) val topEnd @Composable get() = AutoCornersShape( topEnd = 16.dp, topStart = 4.dp, bottomEnd = 4.dp, bottomStart = 4.dp ) val topStart @Composable get() = AutoCornersShape( topEnd = 4.dp, topStart = 16.dp, bottomEnd = 4.dp, bottomStart = 4.dp ) val bottomEnd @Composable get() = AutoCornersShape( topEnd = 4.dp, topStart = 4.dp, bottomEnd = 16.dp, bottomStart = 4.dp ) val smallTop @Composable get() = AutoCornersShape( topStart = 12.dp, topEnd = 12.dp, bottomStart = 4.dp, bottomEnd = 4.dp ) val smallBottom @Composable get() = AutoCornersShape( topStart = 4.dp, topEnd = 4.dp, bottomStart = 12.dp, bottomEnd = 12.dp ) val smallStart @Composable get() = AutoCornersShape( topStart = 12.dp, topEnd = 4.dp, bottomStart = 12.dp, bottomEnd = 4.dp ) val smallEnd @Composable get() = AutoCornersShape( topEnd = 12.dp, topStart = 4.dp, bottomEnd = 12.dp, bottomStart = 4.dp ) val extremeSmall @Composable get() = AutoCornersShape(2.dp) val extraSmall @Composable get() = AutoCornersShape(4.dp) val pressed @Composable get() = AutoCornersShape(6.dp) val mini @Composable get() = AutoCornersShape(8.dp) val smallMini @Composable get() = AutoCornersShape(10.dp) val small @Composable get() = AutoCornersShape(12.dp) val default @Composable get() = AutoCornersShape(16.dp) val large @Composable get() = AutoCornersShape(20.dp) val extraLarge @Composable get() = AutoCornersShape(24.dp) val extremeLarge @Composable get() = AutoCornersShape(28.dp) val circle @Composable get() = AutoCircleShape() @Composable private inline fun CornerSize.animate(): Dp = animateDpAsState( targetValue = with(LocalDensity.current) { toPx( shapeSize = Size.Unspecified, density = this ).toDp() }, animationSpec = lessSpringySpec() ).value } @JvmInline value class CornerSides internal constructor(private val mask: Int) { companion object { val TopStart = CornerSides(1 shl 0) val TopEnd = CornerSides(1 shl 1) val BottomStart = CornerSides(1 shl 2) val BottomEnd = CornerSides(1 shl 3) val Top = TopStart + TopEnd val Bottom = BottomStart + BottomEnd val Start = TopStart + BottomStart val End = TopEnd + BottomEnd val All = Top + Bottom val None = CornerSides(0) } operator fun plus(other: CornerSides): CornerSides = CornerSides(mask or other.mask) operator fun contains(other: CornerSides): Boolean = (mask and other.mask) == other.mask fun has(other: CornerSides): Boolean = contains(other) } inline fun S.only( sides: CornerSides ): S = autoCast { copy( topStart = if (sides.has(CornerSides.TopStart)) topStart else ZeroCornerSize, topEnd = if (sides.has(CornerSides.TopEnd)) topEnd else ZeroCornerSize, bottomEnd = if (sides.has(CornerSides.BottomEnd)) bottomEnd else ZeroCornerSize, bottomStart = if (sides.has(CornerSides.BottomStart)) bottomStart else ZeroCornerSize, ) } val ZeroCornerSize: CornerSize = CornerSize(0f) @Stable internal class AnimatedShape( initialShape: CornerBasedShape, val shapesType: ShapeType, private val density: Density, private val animationSpec: FiniteAnimationSpec, ) : Shape { private var size: Size = Size( width = with(density) { 48.dp.toPx() }, height = with(density) { 48.dp.toPx() } ) private val halfHeight: Float get() = size.height / 2f private val topStart = Animatable(initialShape.topStart.toPx()) private val topEnd = Animatable(initialShape.topEnd.toPx()) private val bottomStart = Animatable(initialShape.bottomStart.toPx()) private val bottomEnd = Animatable(initialShape.bottomEnd.toPx()) private inline fun CornerSize.toPx() = toPx(size, density) private suspend inline fun ShapeAnimatable.animateTo( cornerSize: CornerSize ) = animateTo( targetValue = cornerSize.toPx(), animationSpec = animationSpec ) private inline fun ShapeAnimatable.boundedValue() = value.fastCoerceIn(0f, halfHeight) suspend fun animateTo(targetShape: CornerBasedShape) = coroutineScope { launch { topStart.animateTo(targetShape.topStart) } launch { topEnd.animateTo(targetShape.topEnd) } launch { bottomStart.animateTo(targetShape.bottomStart) } launch { bottomEnd.animateTo(targetShape.bottomEnd) } } override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline = createOutline( size = size, layoutDirection = layoutDirection, density = density, shapesType = shapesType ) fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density, shapesType: ShapeType ): Outline { if (size.width > 1f && size.height > 1f) { this.size = size } return AutoCornersShape( topStart = CornerSize(topStart.boundedValue()), topEnd = CornerSize(topEnd.boundedValue()), bottomStart = CornerSize(bottomStart.boundedValue()), bottomEnd = CornerSize(bottomEnd.boundedValue()), shapesType = shapesType.copy(1f) ).createOutline( size = size, layoutDirection = layoutDirection, density = density ) } } internal typealias ShapeAnimatable = Animatable @Composable internal fun rememberAnimatedShape( currentShape: CornerBasedShape, animationSpec: FiniteAnimationSpec = lessSpringySpec(), ): AnimatedShape { val density = LocalDensity.current val shapesType = LocalSettingsState.current.shapesType val state = remember(animationSpec, density, shapesType) { AnimatedShape( shapesType = shapesType, initialShape = currentShape, animationSpec = animationSpec, density = density ) } val channel = remember { Channel(Channel.CONFLATED) } SideEffect { channel.trySend(currentShape) } LifecycleResumeEffect(Unit) { channel.trySend(currentShape) onPauseOrDispose { channel.trySend(currentShape) } } LaunchedEffect(state, channel) { for (target in channel) { val newTarget = channel.tryReceive().getOrNull() ?: target launch { state.animateTo(newTarget) } } } return state } @Composable fun animateShape( targetValue: CornerBasedShape, animationSpec: FiniteAnimationSpec = lessSpringySpec(), ): Shape = rememberAnimatedShape( currentShape = targetValue, animationSpec = animationSpec ) @Composable fun shapeByInteraction( shape: Shape, pressedShape: Shape, interactionSource: InteractionSource?, animationSpec: FiniteAnimationSpec = lessSpringySpec(), enabled: Boolean = true ): Shape { if (!enabled || interactionSource == null) return shape val pressed by interactionSource.collectIsPressedAsState() val focused by interactionSource.collectIsFocusedAsState() val usePressedShape = pressed || focused val targetShape = if (usePressedShape) pressedShape else shape if (targetShape is CornerBasedShape) { return animateShape( targetValue = targetShape, animationSpec = animationSpec, ) } return targetShape } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Shimmer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.material3.MaterialTheme import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.harmonizeWithPrimary fun Modifier.shimmer( visible: Boolean, color: Color = Color.Unspecified ) = this.composed { Modifier.placeholder( visible = visible, color = color.takeOrElse { MaterialTheme.colorScheme.surfaceColorAtElevation(16.dp) }, highlight = PlaceholderHighlight.shimmer( highlightColor = color.harmonizeWithPrimary(0.5f) ) ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/Tappable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager fun Modifier.tappable( key1: Any? = Unit, onTap: PointerInputScope.(Offset) -> Unit ): Modifier = pointerInput(key1) { detectTapGestures { onTap(it) } } fun Modifier.clearFocusOnTap(enabled: Boolean = true) = composed { val focus = LocalFocusManager.current if (enabled) { Modifier.pointerInput(focus) { detectTapGestures( onTap = { focus.clearFocus() } ) } } else { Modifier } } @Composable fun Disableable( enabled: Boolean, onDisabledClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Box( modifier = modifier .alpha(if (enabled) 1f else 0.5f) ) { content() if (!enabled) { Surface( color = Color.Transparent, modifier = Modifier .matchParentSize() .tappable(onDisabledClick) { onDisabledClick() } ) {} } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/TransparencyChecker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.blend fun Modifier.transparencyChecker( colorScheme: ColorScheme? = null, checkerWidth: Dp = 10.dp, checkerHeight: Dp = 10.dp, ) = this.composed { val scheme = colorScheme ?: MaterialTheme.colorScheme Modifier.drawWithCache { val width = this.size.width val height = this.size.height val checkerWidthPx = checkerWidth.toPx() val checkerHeightPx = checkerHeight.toPx() val horizontalSteps = (width / checkerWidthPx).toInt() val verticalSteps = (height / checkerHeightPx).toInt() onDrawBehind { drawRect( scheme.surfaceColorAtElevation(20.dp).blend(scheme.surface, 0.5f) ) for (y in 0..verticalSteps) { for (x in 0..horizontalSteps) { val isGrayTile = ((x + y) % 2 == 1) drawRect( color = if (isGrayTile) { scheme.surfaceColorAtElevation(20.dp) } else scheme.surface, topLeft = Offset(x * checkerWidthPx, y * checkerHeightPx), size = Size(checkerWidthPx, checkerHeightPx) ) } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/modifier/WithModifier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.modifier import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @Composable fun (@Composable () -> Unit).withModifier( modifier: Modifier, contentAlignment: Alignment = Alignment.Center ) = Box( modifier = modifier, contentAlignment = contentAlignment ) { this@withModifier() } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/AnimatedBorder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @Composable fun AnimatedBorder( modifier: Modifier, alpha: Float, scale: Float, shape: Shape ) { val pathEffect = rememberAnimatedBorder() val density = LocalDensity.current val colorScheme = MaterialTheme.colorScheme Canvas(modifier = modifier) { val outline = shape.createOutline( size = size, layoutDirection = layoutDirection, density = density ) drawOutline( outline = outline, color = colorScheme.primary.copy(alpha), style = Stroke( width = 3.dp.toPx() * (1f / scale) ) ) drawOutline( outline = outline, color = colorScheme.primaryContainer.copy(alpha), style = Stroke( width = 3.dp.toPx() * (1f / scale), pathEffect = pathEffect ) ) } } @Composable fun rememberAnimatedBorder( intervals: FloatArray = floatArrayOf(20f, 20f), phase: Float = 80f, repeatDuration: Int = 1000 ): PathEffect = PathEffect.dashPathEffect( intervals = intervals, phase = rememberAnimatedBorderPhase( phase = phase, repeatDuration = repeatDuration ) ) @Composable fun rememberAnimatedBorderPhase( phase: Float = 80f, repeatDuration: Int = 1000 ): Float { val transition = rememberInfiniteTransition() val animatedPhase by transition.animateFloat( initialValue = 0f, targetValue = phase, animationSpec = infiniteRepeatable( animation = tween( durationMillis = repeatDuration, easing = LinearEasing ), repeatMode = RepeatMode.Restart ) ) return animatedPhase } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/BoxAnimatedVisibility.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.expandIn import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkOut import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @Composable fun BoxAnimatedVisibility( visible: Boolean, modifier: Modifier = Modifier, enter: EnterTransition = fadeIn() + expandIn(), exit: ExitTransition = shrinkOut() + fadeOut(), label: String = "AnimatedVisibility", content: @Composable AnimatedVisibilityScope.() -> Unit ) = androidx.compose.animation.AnimatedVisibility(visible, modifier, enter, exit, label, content) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ColorWithNameItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.BookmarkBorder import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.luminance import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.colors.parser.ColorNameParser import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.utils.animation.CombinedMutableInteractionSource import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.utils.appContext import kotlinx.coroutines.runBlocking @Composable fun ColorWithNameItem( color: Color, name: String? = null, isFavorite: Boolean = false, onToggleFavorite: (() -> Unit)? = null, onCopy: () -> Unit, modifier: Modifier = Modifier, containerShape: Shape = ShapeDefaults.default ) { val boxColor by animateColorAsState(color) val contentColor = boxColor.inverse( fraction = { cond -> if (cond) 0.8f else 0.5f }, darkMode = boxColor.luminance() < 0.3f ) val favoriteInteractionSource = remember { MutableInteractionSource() } val copyInteractionSource = remember { MutableInteractionSource() } val interactionSource = remember { CombinedMutableInteractionSource( favoriteInteractionSource, copyInteractionSource ) } val shape = shapeByInteraction( shape = containerShape, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) val colorName = name ?: remember(color) { ColorNameParser.parseColorName(color) } Column( modifier = modifier .heightIn(min = 100.dp) .fillMaxWidth() .clip(shape) .then( if (onToggleFavorite == null) { Modifier.hapticsClickable( indication = LocalIndication.current, interactionSource = copyInteractionSource, onClick = onCopy ) } else { Modifier } ) .then( if (color.alpha < 1f) Modifier.transparencyChecker() else Modifier ) .background(boxColor), verticalArrangement = Arrangement.SpaceBetween ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { AutoSizeText( text = remember(color) { color.toHex() }, color = contentColor, modifier = Modifier .weight(1f, false) .padding( start = 4.dp, top = 4.dp, bottom = 4.dp ) .background( color = boxColor.copy(alpha = 1f), shape = ShapeDefaults.mini ) .padding(4.dp), style = LocalTextStyle.current.copy( fontSize = 12.sp, lineHeight = 12.5.sp ), key = { color to colorName } ) Icon( imageVector = Icons.Rounded.ContentCopy, contentDescription = stringResource(R.string.copy), tint = contentColor.copy(0.8f), modifier = Modifier .padding( end = 2.dp, top = 4.dp, bottom = 4.dp ) .size(26.dp) .clip(ShapeDefaults.mini) .background(boxColor.copy(alpha = 1f)) .hapticsClickable( indication = LocalIndication.current, interactionSource = copyInteractionSource, onClick = onCopy ) .padding(4.dp) ) } Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.SpaceBetween ) { if (onToggleFavorite != null) { Icon( imageVector = if (isFavorite) { Icons.Rounded.BookmarkRemove } else { Icons.Outlined.BookmarkBorder }, contentDescription = stringResource(R.string.favorite), tint = contentColor.copy(0.8f), modifier = Modifier .padding( start = 2.dp, top = 4.dp, bottom = 4.dp ) .size(26.dp) .clip(ShapeDefaults.mini) .background(boxColor.copy(alpha = 1f)) .hapticsClickable( indication = LocalIndication.current, interactionSource = favoriteInteractionSource, onClick = onToggleFavorite ) .padding(4.dp) ) } else { Spacer(Modifier) } Text( text = colorName, color = contentColor, modifier = Modifier .padding( end = 4.dp, top = 4.dp, bottom = 4.dp ) .background( color = boxColor.copy(alpha = 1f), shape = ShapeDefaults.mini ) .padding(4.dp), fontSize = 12.sp, lineHeight = 12.5.sp, textAlign = TextAlign.End ) } } } @Preview @Composable private fun Preview() = ImageToolboxThemeForPreview(true) { runBlocking { ColorNameParser.init(appContext) } val colors = remember { ColorNameParser.colorNames.values.sortedBy { it.name }.toList() } var fav by remember { mutableStateOf(setOf()) } LazyVerticalGrid( contentPadding = PaddingValues(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior(), columns = GridCells.Adaptive(150.dp) ) { items(colors) { colorWithName -> ColorWithNameItem( color = colorWithName.color, name = colorWithName.name, isFavorite = colorWithName.name in fav, onToggleFavorite = { fav = fav.toggle(colorWithName.name) }, onCopy = {}, modifier = Modifier.animateItem() ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/DrawLockScreenOrientation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import android.app.Activity import android.content.pm.ActivityInfo import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity @Composable fun DrawLockScreenOrientation() { if (!LocalSettingsState.current.lockDrawOrientation) return val activity = LocalComponentActivity.current DisposableEffect(activity) { val originalOrientation = activity.requestedOrientation activity.lockOrientation() onDispose { activity.requestedOrientation = originalOrientation } } } @Composable fun LockScreenOrientation( mode: Int? = null ) { val activity = LocalComponentActivity.current DisposableEffect(activity) { val originalOrientation = activity.requestedOrientation activity.lockOrientation(mode) onDispose { activity.requestedOrientation = originalOrientation } } } private fun Activity.lockOrientation(mode: Int? = null) { val display = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { display } else { @Suppress("DEPRECATION") windowManager.defaultDisplay } val rotation = display?.rotation ?: 0 val orientation: Int = when (resources.configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { if (rotation == 0 || rotation == 90) { ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } else { ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE } } Configuration.ORIENTATION_PORTRAIT -> { if (rotation == 0 || rotation == 270) { ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } else { ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT } } else -> 0 } requestedOrientation = mode ?: orientation } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/EmojiItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import coil3.compose.rememberAsyncImagePainter import coil3.imageLoader import coil3.request.ImageRequest import coil3.request.crossfade import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.utils.appContext @Composable fun EmojiItem( emoji: String?, modifier: Modifier = Modifier, fontSize: TextUnit = LocalTextStyle.current.fontSize, fontScale: Float, onNoEmoji: @Composable (size: Dp) -> Unit = {} ) { val dens = LocalDensity.current val density by remember(dens, fontScale) { derivedStateOf { Density( density = dens.density, fontScale = fontScale ) } } var shimmering by rememberSaveable { mutableStateOf(true) } val painter = rememberAsyncImagePainter( model = remember(emoji) { derivedStateOf { ImageRequest.Builder(appContext) .data(emoji) .memoryCacheKey(emoji) .diskCacheKey(emoji) .size(512) .listener( onStart = { shimmering = true }, onSuccess = { _, _ -> shimmering = false } ) .crossfade(true) .build() } }.value, imageLoader = appContext.imageLoader, filterQuality = FilterQuality.High ) AnimatedContent( targetState = emoji to fontSize, modifier = modifier, transitionSpec = { fadeIn() + scaleIn(initialScale = 0.85f) togetherWith fadeOut() + scaleOut(targetScale = 0.85f) } ) { (emoji, fontSize) -> val size by remember(fontSize, density) { derivedStateOf { with(density) { fontSize.toDp() } } } emoji?.let { Box { Icon( painter = painter, contentDescription = emoji, modifier = remember(size) { Modifier .size(size + 4.dp) .offset(1.dp, 1.dp) .padding(2.dp) }, tint = Color(0, 0, 0, 40) ) Icon( painter = painter, contentDescription = emoji, modifier = remember(size, shimmering) { Modifier .size(size + 4.dp) .clip(CloverShape) .shimmer(shimmering) .padding(2.dp) }, tint = Color.Unspecified ) } } ?: onNoEmoji(size) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ExpandableItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.animation.FancyTransitionEasing import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction @Composable fun ExpandableItem( modifier: Modifier = Modifier, visibleContent: @Composable RowScope.(Boolean) -> Unit, expandableContent: @Composable ColumnScope.(Boolean) -> Unit, initialState: Boolean = false, verticalArrangement: Arrangement.Vertical = Arrangement.Top, shape: Shape = ShapeDefaults.large, pressedShape: Shape = ShapeDefaults.pressed, color: Color = Color.Unspecified, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, canExpand: Boolean = true, onClick: () -> Unit = {}, onLongClick: (() -> Unit)? = null, expansionIconContainerColor: Color = Color.Transparent ) { val animatedShape = shapeByInteraction( shape = shape, pressedShape = pressedShape, interactionSource = interactionSource ) Column( modifier = Modifier .then(modifier) .container( color = color, resultPadding = 0.dp, shape = animatedShape ) .animateContentSizeNoClip( animationSpec = spec(10) ) ) { var expanded by rememberSaveable(initialState) { mutableStateOf(initialState) } val rotation by animateFloatAsState(if (expanded) 180f else 0f) Row( modifier = Modifier .clip(animatedShape) .hapticsCombinedClickable( interactionSource = interactionSource, indication = LocalIndication.current, onLongClick = onLongClick ) { if (canExpand) { expanded = !expanded } onClick() } .padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { Row(Modifier.weight(1f)) { visibleContent(expanded) } if (canExpand) { EnhancedIconButton( containerColor = expansionIconContainerColor, onClick = { expanded = !expanded } ) { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate(rotation) ) } } } BoxAnimatedVisibility( visible = expanded, enter = fadeIn(spec(500)) + expandVertically(spec(500)), exit = fadeOut(spec(700)) + shrinkVertically(spec(700)) ) { Column(verticalArrangement = verticalArrangement) { Spacer(modifier = Modifier.height(8.dp)) expandableContent(expanded) Spacer(modifier = Modifier.height(8.dp)) } } } } private fun spec(duration: Int): FiniteAnimationSpec = tween( durationMillis = duration, easing = FancyTransitionEasing ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FeatureNotAvailableContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.FabPosition import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.APP_RELEASES import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Block import com.t8rin.imagetoolbox.core.resources.icons.Github import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.Black import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.image.SourceNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun FeatureNotAvailableContent( title: @Composable () -> Unit, onGoBack: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val linkHandler = LocalUriHandler.current val settingsState = LocalSettingsState.current Scaffold( topBar = { EnhancedTopAppBar( type = EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior, title = title, navigationIcon = { EnhancedIconButton( onClick = onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, actions = { TopAppBarEmoji() } ) }, floatingActionButtonPosition = when (settingsState.fabAlignment) { Alignment.BottomStart -> FabPosition.Start Alignment.BottomCenter -> FabPosition.Center else -> FabPosition.End }, floatingActionButton = { EnhancedFloatingActionButton( onClick = { linkHandler.openUri(APP_RELEASES) }, containerColor = Black, contentColor = White, content = { Spacer(Modifier.width(16.dp)) Icon( imageVector = Icons.Rounded.Github, contentDescription = stringResource(R.string.restart_app) ) Spacer(Modifier.width(16.dp)) AutoSizeText( text = stringResource(R.string.open_github_page), maxLines = 1 ) Spacer(Modifier.width(16.dp)) } ) } ) { Column( modifier = Modifier .nestedScroll(scrollBehavior.nestedScrollConnection) .enhancedVerticalScroll(rememberScrollState()) .fillMaxSize() .padding(it) .padding(20.dp) ) { SourceNotPickedWidget( onClick = { linkHandler.openUri(APP_RELEASES) }, text = stringResource(R.string.wallpapers_export_not_avaialbe), icon = Icons.TwoTone.Block, maxLines = Int.MAX_VALUE ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/FontSelectionItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.ProvideTypography import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun FontSelectionItem( font: UiFontFamily, onClick: () -> Unit, modifier: Modifier = Modifier, onLongClick: (() -> Unit)? = null, shape: Shape = ShapeDefaults.default ) { val settingsState = LocalSettingsState.current val (_, name, isVariable) = font val selected = font == settingsState.font ProvideTypography(font) { PreferenceItem( onClick = onClick, onLongClick = onLongClick, title = (name ?: stringResource(id = R.string.system)) + isVariable.toVariable(), subtitle = stringResource(R.string.alphabet_and_numbers), containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor }, modifier = modifier .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme .colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = ShapeDefaults.default ), shape = shape, endIcon = if (selected) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked ) } } @Composable private fun Boolean?.toVariable(): String { if (this == null || this) return "" return " (${stringResource(R.string.regular)})" } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/GradientEdge.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.foundation.background import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color @Composable fun GradientEdge( modifier: Modifier, orientation: Orientation = Orientation.Vertical, startColor: Color, endColor: Color ) { when (orientation) { Orientation.Vertical -> { Box( modifier = modifier .background( brush = Brush.verticalGradient( 0f to startColor, 0.7f to startColor, 1f to endColor ) ) ) } Orientation.Horizontal -> { Box( modifier = modifier .background( brush = Brush.horizontalGradient( 0f to startColor, 0.7f to startColor, 1f to endColor ) ) ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/InfoContainer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun InfoContainer( text: String, modifier: Modifier = Modifier, containerColor: Color = MaterialTheme.colorScheme.secondaryContainer.copy(0.2f), contentColor: Color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.5f), shape: Shape = ShapeDefaults.default, icon: ImageVector? = Icons.Outlined.Info, textAlign: TextAlign = TextAlign.Center ) { Row( modifier = modifier .container( color = containerColor, resultPadding = 0.dp, shape = shape ) .padding(12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { icon?.let { Icon( imageVector = icon, contentDescription = null, tint = contentColor ) Spacer(modifier = Modifier.width(8.dp)) } Text( text = text, fontSize = 12.sp, textAlign = textAlign, fontWeight = FontWeight.SemiBold, lineHeight = 14.sp, color = contentColor ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Link import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.LinkPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun LinkPreviewCard( linkPreview: LinkPreview, shape: Shape ) { val linkHandler = LocalUriHandler.current Row( modifier = Modifier .fillMaxWidth() .container( shape = shape, color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp ) .hapticsCombinedClickable( onClick = { linkPreview.link?.let(linkHandler::openUri) }, onLongClick = { linkPreview.link?.let { Clipboard.copy( text = it, icon = Icons.Default.Link ) } }, ), verticalAlignment = Alignment.CenterVertically ) { var sizeOfRight by remember { mutableStateOf(80.dp) } val density = LocalDensity.current Picture( model = linkPreview.image, contentDescription = stringResource(R.string.image_link), contentScale = ContentScale.Crop, modifier = Modifier .width(80.dp) .height(sizeOfRight), alignment = Alignment.Center, error = { Icon( imageVector = Icons.Default.Language, contentDescription = null, modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surface) .padding(12.dp) .clip(MaterialStarShape) .background( MaterialTheme.colorScheme.tertiaryContainer .copy(0.5f) .compositeOver(MaterialTheme.colorScheme.surface) ) .padding(8.dp) ) }, filterQuality = FilterQuality.High ) Column( modifier = Modifier .weight(1f) .onSizeChanged { sizeOfRight = if (linkPreview.title.isNullOrBlank() && linkPreview.description.isNullOrBlank()) { 80.dp } else { with(density) { it.height.toDp() } } } .padding(12.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start ) { if (!linkPreview.title.isNullOrBlank()) { Text( text = linkPreview.title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium.copy( fontSize = 18.sp, fontWeight = FontWeight.SemiBold ), modifier = Modifier.padding(bottom = 6.dp) ) } if (!linkPreview.description.isNullOrBlank()) { Text( text = linkPreview.description, maxLines = if (linkPreview.title.isNullOrBlank()) 3 else 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Start, lineHeight = 16.sp ) } if ((linkPreview.description.isNullOrBlank() || linkPreview.title.isNullOrBlank()) && (!linkPreview.url.isNullOrBlank() || !linkPreview.link.isNullOrBlank())) { Text( text = linkPreview.url ?: linkPreview.link ?: "", maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium.copy( fontSize = 18.sp, fontWeight = FontWeight.SemiBold ) ) } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/LinkPreviewList.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material.icons.rounded.Link import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.LinkPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.LinkUtils import com.t8rin.imagetoolbox.core.ui.utils.helper.fetchLinkPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun LinkPreviewList( text: String, externalLinks: List? = null, modifier: Modifier ) { val settingsState = LocalSettingsState.current if (!settingsState.isLinkPreviewEnabled) return var isLoading by rememberSaveable { mutableStateOf(false) } var linkPreviewList by remember { mutableStateOf(emptyList()) } var expanded by rememberSaveable { mutableStateOf(true) } val rotation by animateFloatAsState(if (expanded) 180f else 0f) LaunchedEffect(text, externalLinks) { if (linkPreviewList.isNotEmpty() && text.isNotEmpty()) delay(500) isLoading = true val links = LinkUtils.parseLinks(text) + externalLinks.orEmpty() linkPreviewList = links.map(LinkPreview::empty) launch { linkPreviewList = links.map { link -> async { fetchLinkPreview(link) } }.awaitAll() } isLoading = false } val links = remember(expanded, linkPreviewList) { if (linkPreviewList.size > 3 && expanded) { linkPreviewList } else linkPreviewList.take(3) } AnimatedVisibility( modifier = Modifier.fillMaxWidth(), visible = !isLoading && linkPreviewList.isNotEmpty() ) { Column( modifier = Modifier .then(modifier) .container( shape = ShapeDefaults.large, resultPadding = 0.dp ) .padding(8.dp) ) { Column { TitleItem( text = stringResource(R.string.links), icon = Icons.Rounded.Link, modifier = Modifier.padding(8.dp), endContent = { AnimatedVisibility( visible = linkPreviewList.size > 3, modifier = Modifier.size(32.dp) ) { EnhancedIconButton( onClick = { expanded = !expanded } ) { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate(rotation) ) } } } ) Spacer(modifier = Modifier.padding(4.dp)) Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { links.forEachIndexed { index, link -> LinkPreviewCard( linkPreview = link, shape = ShapeDefaults.byIndex( index = index, size = links.size ) ) } } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/QrPainter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.core.ui.widget.other import android.graphics.Bitmap import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialShapes import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.addOutline import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min import androidx.core.graphics.createBitmap import coil3.compose.asPainter import coil3.imageLoader import coil3.request.ImageRequest import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.MultiFormatWriter import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.resources.shapes.ArrowShape import com.t8rin.imagetoolbox.core.resources.shapes.BookmarkShape import com.t8rin.imagetoolbox.core.resources.shapes.BurgerShape import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.resources.shapes.DropletShape import com.t8rin.imagetoolbox.core.resources.shapes.EggShape import com.t8rin.imagetoolbox.core.resources.shapes.ExplosionShape import com.t8rin.imagetoolbox.core.resources.shapes.MapShape import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.resources.shapes.OctagonShape import com.t8rin.imagetoolbox.core.resources.shapes.OvalShape import com.t8rin.imagetoolbox.core.resources.shapes.PentagonShape import com.t8rin.imagetoolbox.core.resources.shapes.PillShape import com.t8rin.imagetoolbox.core.resources.shapes.ShieldShape import com.t8rin.imagetoolbox.core.resources.shapes.ShurikenShape import com.t8rin.imagetoolbox.core.resources.shapes.SmallMaterialStarShape import com.t8rin.imagetoolbox.core.resources.shapes.SquircleShape import com.t8rin.imagetoolbox.core.settings.presentation.model.IconShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.utils.toShape import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.applyPadding import com.t8rin.imagetoolbox.core.ui.utils.painter.centerCrop import com.t8rin.imagetoolbox.core.ui.utils.painter.roundCorners import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams.BallShape.Shaped import com.t8rin.imagetoolbox.core.utils.appContext import io.github.alexzhirkevich.qrose.QrCodePainter import io.github.alexzhirkevich.qrose.options.Neighbors import io.github.alexzhirkevich.qrose.options.QrBallShape import io.github.alexzhirkevich.qrose.options.QrBrush import io.github.alexzhirkevich.qrose.options.QrColors import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrFrameShape import io.github.alexzhirkevich.qrose.options.QrLogo import io.github.alexzhirkevich.qrose.options.QrLogoPadding import io.github.alexzhirkevich.qrose.options.QrLogoShape import io.github.alexzhirkevich.qrose.options.QrOptions import io.github.alexzhirkevich.qrose.options.QrPixelShape import io.github.alexzhirkevich.qrose.options.QrShapes import io.github.alexzhirkevich.qrose.options.circle import io.github.alexzhirkevich.qrose.options.cutCorners import io.github.alexzhirkevich.qrose.options.horizontalLines import io.github.alexzhirkevich.qrose.options.roundCorners import io.github.alexzhirkevich.qrose.options.solid import io.github.alexzhirkevich.qrose.options.square import io.github.alexzhirkevich.qrose.options.verticalLines import io.github.alexzhirkevich.qrose.qrcode.MaskPattern import io.github.alexzhirkevich.qrose.rememberQrCodePainter import io.github.alexzhirkevich.qrose.toImageBitmap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.withContext /** * Creates a [Painter] that draws a QR code for the given [content]. * The [size] parameter defines the size of the QR code in dp. * The [padding] parameter defines the padding of the QR code in dp. */ @Composable private fun rememberBarcodePainter( content: String, params: BarcodeParams, onLoading: () -> Unit = {}, onSuccess: () -> Unit = {}, onFailure: (Throwable) -> Unit = {} ): Painter = when (params) { is BarcodeParams.Barcode -> { val width = params.width val height = params.height val foregroundColor = params.foregroundColor val backgroundColor = params.backgroundColor val type = params.type val density = LocalDensity.current val widthPx = with(density) { width.roundToPx() } val heightPx = with(density) { height.roundToPx() } val paddingPx = with(density) { 0.dp.roundToPx() } val bitmapState = remember(content) { mutableStateOf(null) } // Use dependency on 'content' to re-trigger the effect when content changes LaunchedEffect( content, type, widthPx, heightPx, foregroundColor, backgroundColor ) { onLoading() delay(50) if (content.isNotEmpty()) { bitmapState.value = runSuspendCatching { generateQrBitmap( content = content, widthPx = widthPx, heightPx = heightPx, paddingPx = paddingPx, foregroundColor = foregroundColor, backgroundColor = backgroundColor, format = type.zxingFormat ) }.onFailure(onFailure).getOrNull() } else { bitmapState.value = null } if (bitmapState.value != null) onSuccess() } val bitmap = bitmapState.value ?: createDefaultBitmap(widthPx, heightPx) remember(bitmap) { bitmap?.asImageBitmap()?.let { BitmapPainter(it) } ?: EmptyPainter(widthPx, heightPx) } } is BarcodeParams.Qr -> { rememberQrCodePainter( data = content, options = params.options, onSuccess = onSuccess, onFailure = { onLoading() onFailure(it) } ) } } /** * Generates a QR code bitmap for the given [content]. * The [widthPx] parameter defines the size of the QR code in pixels. * The [paddingPx] parameter defines the padding of the QR code in pixels. * Returns null if the QR code could not be generated. * This function is suspendable and should be called from a coroutine is thread-safe. */ private suspend fun generateQrBitmap( content: String, widthPx: Int, heightPx: Int, paddingPx: Int, foregroundColor: Color = Color.Black, backgroundColor: Color = Color.White, format: BarcodeFormat = BarcodeFormat.QR_CODE ): Bitmap = withContext(Dispatchers.IO) { val encodeHints = mutableMapOf() .apply { this[EncodeHintType.CHARACTER_SET] = "utf-8" if (format == BarcodeFormat.QR_CODE) { this[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L } this[EncodeHintType.MARGIN] = paddingPx } val bitmapMatrix = MultiFormatWriter().encode(content, format, widthPx, heightPx, encodeHints) val matrixWidth = bitmapMatrix.width val matrixHeight = bitmapMatrix.height val colors = IntArray(matrixWidth * matrixHeight) { index -> val x = index % matrixWidth val y = index / matrixWidth val shouldColorPixel = bitmapMatrix.get(x, y) if (shouldColorPixel) foregroundColor.toArgb() else backgroundColor.toArgb() } Bitmap.createBitmap(colors, matrixWidth, matrixHeight, Bitmap.Config.ARGB_8888) } private sealed interface BarcodeParams { data class Qr( val options: QrOptions ) : BarcodeParams data class Barcode( val width: Dp = 150.dp, val height: Dp = width, val type: BarcodeType, val foregroundColor: Color, val backgroundColor: Color, ) : BarcodeParams { init { check(width >= 0.dp && height >= 0.dp) { "Size must be non negative" } } } } private class EmptyPainter( private val widthPx: Int, private val heightPx: Int ) : Painter() { override val intrinsicSize: Size get() = Size(widthPx.toFloat(), heightPx.toFloat()) override fun DrawScope.onDraw() = Unit } /** * Creates a default bitmap with the given [widthPx], [heightPx]. * The bitmap is transparent. * This is used as a fallback if the QR code could not be generated. * The bitmap is created on the UI thread. */ private fun createDefaultBitmap( widthPx: Int, heightPx: Int ): Bitmap? { return if (widthPx > 0 && heightPx > 0) { createBitmap(widthPx, heightPx).apply { eraseColor(Color.Transparent.toArgb()) } } else null } data class QrCodeParams( val foregroundColor: Color? = null, val backgroundColor: Color? = null, val logo: Any? = null, val logoPadding: Float = 0.25f, val logoSize: Float = 0.2f, val logoCorners: Float = 0f, val pixelShape: PixelShape = PixelShape.Square, val frameShape: FrameShape = FrameShape.Square, val ballShape: BallShape = BallShape.Square, val errorCorrectionLevel: ErrorCorrectionLevel = ErrorCorrectionLevel.Auto, val maskPattern: MaskPattern = MaskPattern.Auto ) { sealed interface PixelShape { sealed interface Predefined : PixelShape data object Square : Predefined data object RoundSquare : Predefined data object Circle : Predefined data object Vertical : Predefined data object Horizontal : Predefined data object Random : PixelShape data class Shaped(val shape: Shape) : PixelShape companion object { val entries by lazy { listOf( Random, Square, RoundSquare, Circle, Vertical, Horizontal, ) + IconShape.entriesNoRandom.map { Shaped(it.shape) } } } } sealed interface FrameShape { data class Corners( val percent: Float, val sides: List = CornerSide.entries, val isCut: Boolean = false ) : FrameShape { enum class CornerSide { TopLeft, TopRight, BottomRight, BottomLeft } val topLeft = CornerSide.TopLeft in sides val topRight = CornerSide.TopRight in sides val bottomLeft = CornerSide.BottomLeft in sides val bottomRight = CornerSide.BottomRight in sides } companion object { val Square = Corners(0.00f) val Circle = Corners(0.50f) val entries: List by lazy { listOf( Square, Corners(0.25f), Circle, Corners(0.05f), Corners(0.10f), Corners(0.15f), Corners(0.20f), Corners(0.30f), Corners(0.35f), Corners(0.40f), Corners(0.45f), Corners( percent = 0.05f, isCut = true ), Corners( percent = 0.10f, isCut = true ), Corners( percent = 0.15f, isCut = true ), Corners( percent = 0.20f, isCut = true ), Corners( percent = 0.25f, isCut = true ), Corners( percent = 0.30f, isCut = true ), Corners( percent = 0.35f, isCut = true ), ) } } } sealed interface BallShape { sealed interface Predefined : BallShape data object Square : Predefined data object Circle : Predefined data class Shaped(val shape: Shape) : BallShape companion object { val entries by lazy { listOf( Square, Circle, ) + listOf( Shaped(SquircleShape), Shaped(CutCornerShape(25)), Shaped(CutCornerShape(35)), Shaped(CutCornerShape(50)), Shaped(RoundedCornerShape(15)), Shaped(RoundedCornerShape(25)), Shaped(RoundedCornerShape(35)), Shaped(RoundedCornerShape(45)), Shaped(CloverShape), Shaped(MaterialStarShape), Shaped(SmallMaterialStarShape), Shaped(BookmarkShape), Shaped(PillShape), Shaped(BurgerShape), Shaped(OvalShape), Shaped(ShieldShape), Shaped(EggShape), Shaped(DropletShape), Shaped(ArrowShape), Shaped(PentagonShape), Shaped(OctagonShape), Shaped(ShurikenShape), Shaped(ExplosionShape), Shaped(MapShape), ) + listOf( MaterialShapes.Slanted, MaterialShapes.Arch, MaterialShapes.Oval, MaterialShapes.Diamond, MaterialShapes.Gem, MaterialShapes.Sunny, MaterialShapes.VerySunny, MaterialShapes.Cookie4Sided, MaterialShapes.Cookie6Sided, MaterialShapes.Cookie9Sided, MaterialShapes.Cookie12Sided, MaterialShapes.Ghostish, MaterialShapes.Clover4Leaf, MaterialShapes.Clover8Leaf, MaterialShapes.Burst, MaterialShapes.SoftBurst, MaterialShapes.SoftBoom, MaterialShapes.Flower, MaterialShapes.Puffy, MaterialShapes.PuffyDiamond, MaterialShapes.PixelCircle ).map { Shaped(it.toShape()) } } } } enum class ErrorCorrectionLevel { Auto, L, M, Q, H } enum class MaskPattern { Auto, P_000, P_001, P_010, P_011, P_100, P_101, P_110, P_111 } } @Composable fun defaultQrColors(): Pair { val settingsState = LocalSettingsState.current val backgroundColor = if (settingsState.isNightMode) { MaterialTheme.colorScheme.onSurface } else { MaterialTheme.colorScheme.surfaceContainerHigh } val foregroundColor = if (settingsState.isNightMode) { MaterialTheme.colorScheme.surfaceContainer } else { MaterialTheme.colorScheme.onSurface } return remember(backgroundColor, foregroundColor) { backgroundColor to foregroundColor } } @Composable fun QrCode( content: String, modifier: Modifier, cornerRadius: Dp = 4.dp, heightRatio: Float = 2f, qrParams: QrCodeParams, type: BarcodeType = BarcodeType.QR_CODE, onFailure: (Throwable) -> Unit = {}, onSuccess: () -> Unit = {}, ) { BoxWithConstraints( modifier = modifier, contentAlignment = Alignment.Center ) { val width = min(this.maxWidth, maxHeight) val height = animateDpAsState( if (type.isSquare && type != BarcodeType.DATA_MATRIX) width else width / heightRatio ).value val (bg, fg) = defaultQrColors() val backgroundColor = qrParams.backgroundColor ?: bg val foregroundColor = qrParams.foregroundColor ?: fg var isLoading by remember { mutableStateOf(true) } var logoPainterRaw by remember(qrParams.logo) { mutableStateOf(null) } val logoPainter = remember(logoPainterRaw, qrParams.logoCorners) { logoPainterRaw?.roundCorners(qrParams.logoCorners) } LaunchedEffect(qrParams.logo) { logoPainterRaw = appContext.imageLoader.execute( ImageRequest.Builder(appContext) .data(qrParams.logo) .size(1024) .build() ).image?.asPainter(appContext)?.centerCrop() } val density = LocalDensity.current val params by remember( width, height, type, foregroundColor, backgroundColor, qrParams, logoPainter, density ) { derivedStateOf { when (type) { BarcodeType.QR_CODE -> { BarcodeParams.Qr( QrOptions( colors = QrColors( dark = QrBrush.solid(foregroundColor), light = QrBrush.solid(backgroundColor), ball = QrBrush.solid(foregroundColor), frame = QrBrush.solid(foregroundColor), background = QrBrush.solid(backgroundColor), ), logo = logoPainter?.let { QrLogo( painter = logoPainter, shape = QrLogoShape.roundCorners(qrParams.logoCorners), padding = QrLogoPadding.Natural(qrParams.logoPadding), size = qrParams.logoSize ) } ?: QrLogo(), shapes = QrShapes( darkPixel = qrParams.pixelShape.toLib(density), frame = qrParams.frameShape.toLib(), ball = qrParams.ballShape.toLib(density) ), errorCorrectionLevel = qrParams.errorCorrectionLevel.toLib(), maskPattern = qrParams.maskPattern.toLib() ) ) } else -> { BarcodeParams.Barcode( width = width, height = height, foregroundColor = foregroundColor, backgroundColor = backgroundColor, type = type, ) } } } } val painter = rememberBarcodePainter( content = content, params = params, onLoading = { isLoading = true }, onSuccess = { isLoading = false onSuccess() }, onFailure = onFailure ) val padding = 16.dp Picture( model = painter, modifier = Modifier .size( width = width, height = height ) .clip(AutoCornersShape(cornerRadius)) .background(backgroundColor) .padding(padding) .alpha( animateFloatAsState( targetValue = if (isLoading) 0f else 1f, animationSpec = tween(500) ).value ), contentDescription = null ) Box( modifier = Modifier .size( width = width, height = height ) .clip(AutoCornersShape((cornerRadius - 1.dp).coerceAtLeast(0.dp))) .shimmer(isLoading) ) } } suspend fun QrCodeParams.renderAsQr( content: String, type: BarcodeType ): Bitmap? = coroutineScope { val widthPx = 1500 val heightPx = 1500 val paddingPx = 100 val logoPainter: Painter? = logo?.let { appContext.imageLoader.execute( ImageRequest.Builder(appContext) .data(logo) .size(1024) .build() ).image?.asPainter(appContext)?.centerCrop()?.roundCorners(logoCorners) } val options = QrOptions( colors = QrColors( dark = QrBrush.solid(foregroundColor ?: Color.Black), light = QrBrush.solid(backgroundColor ?: Color.White), ball = QrBrush.solid(foregroundColor ?: Color.Black), frame = QrBrush.solid(foregroundColor ?: Color.Black), background = QrBrush.solid(backgroundColor ?: Color.White), ), logo = logoPainter?.let { QrLogo( painter = logoPainter, shape = QrLogoShape.roundCorners(logoCorners), padding = QrLogoPadding.Natural(logoPadding), size = logoSize ) } ?: QrLogo(), shapes = QrShapes( darkPixel = pixelShape.toLib(Density(1f)), frame = frameShape.toLib(), ball = ballShape.toLib(Density(1f)) ), errorCorrectionLevel = errorCorrectionLevel.toLib(), maskPattern = maskPattern.toLib() ) runSuspendCatching { when (type) { BarcodeType.QR_CODE -> { QrCodePainter( data = content, options = options, onSuccess = {}, onFailure = {} ).toImageBitmap(widthPx, heightPx).asAndroidBitmap().applyPadding(paddingPx) } else -> { generateQrBitmap( content = content, widthPx = widthPx, heightPx = heightPx, paddingPx = paddingPx, foregroundColor = foregroundColor ?: Color.Black, backgroundColor = backgroundColor ?: Color.White, format = type.zxingFormat ) } } }.getOrNull() } private fun pixelShape( density: Density, shape: () -> Shape ) = QrPixelShape { size, _ -> apply { addOutline( shape().createOutline( size = Size(size, size), layoutDirection = LayoutDirection.Ltr, density = density ) ) } } private fun Shape.toBallShape(density: Density) = object : QrBallShape { override fun Path.path( size: Float, neighbors: Neighbors ): Path = apply { addOutline( createOutline( size = Size(size, size), layoutDirection = LayoutDirection.Ltr, density = density ) ) } } private fun QrCodeParams.BallShape.toLib(density: Density): QrBallShape = when (this) { QrCodeParams.BallShape.Square -> QrBallShape.square() QrCodeParams.BallShape.Circle -> QrBallShape.circle() is Shaped -> shape.toBallShape(density) } private fun QrCodeParams.FrameShape.toLib(): QrFrameShape = when (this) { is QrCodeParams.FrameShape.Corners -> { if (isCut) { QrFrameShape.cutCorners( corner = percent, topLeft = topLeft, topRight = topRight, bottomLeft = bottomLeft, bottomRight = bottomRight ) } else { QrFrameShape.roundCorners( corner = percent, topLeft = topLeft, topRight = topRight, bottomLeft = bottomLeft, bottomRight = bottomRight ) } } } private fun QrCodeParams.PixelShape.toLib(density: Density): QrPixelShape = when (this) { QrCodeParams.PixelShape.Square -> QrPixelShape.square() QrCodeParams.PixelShape.RoundSquare -> QrPixelShape.roundCorners() QrCodeParams.PixelShape.Circle -> QrPixelShape.circle() QrCodeParams.PixelShape.Vertical -> QrPixelShape.verticalLines() QrCodeParams.PixelShape.Horizontal -> QrPixelShape.horizontalLines() QrCodeParams.PixelShape.Random -> pixelShape(density) { IconShape.entriesNoRandom.random().shape } is QrCodeParams.PixelShape.Shaped -> pixelShape(density) { shape } } private fun QrCodeParams.ErrorCorrectionLevel.toLib(): QrErrorCorrectionLevel = when (this) { QrCodeParams.ErrorCorrectionLevel.Auto -> QrErrorCorrectionLevel.Auto QrCodeParams.ErrorCorrectionLevel.L -> QrErrorCorrectionLevel.Low QrCodeParams.ErrorCorrectionLevel.M -> QrErrorCorrectionLevel.Medium QrCodeParams.ErrorCorrectionLevel.Q -> QrErrorCorrectionLevel.MediumHigh QrCodeParams.ErrorCorrectionLevel.H -> QrErrorCorrectionLevel.High } private fun QrCodeParams.MaskPattern.toLib(): MaskPattern? = when (this) { QrCodeParams.MaskPattern.Auto -> null QrCodeParams.MaskPattern.P_000 -> MaskPattern.PATTERN000 QrCodeParams.MaskPattern.P_001 -> MaskPattern.PATTERN001 QrCodeParams.MaskPattern.P_010 -> MaskPattern.PATTERN010 QrCodeParams.MaskPattern.P_011 -> MaskPattern.PATTERN011 QrCodeParams.MaskPattern.P_100 -> MaskPattern.PATTERN100 QrCodeParams.MaskPattern.P_101 -> MaskPattern.PATTERN101 QrCodeParams.MaskPattern.P_110 -> MaskPattern.PATTERN110 QrCodeParams.MaskPattern.P_111 -> MaskPattern.PATTERN111 } enum class BarcodeType( internal val zxingFormat: BarcodeFormat, val isSquare: Boolean ) { QR_CODE(BarcodeFormat.QR_CODE, true), AZTEC(BarcodeFormat.AZTEC, true), CODABAR(BarcodeFormat.CODABAR, false), CODE_39(BarcodeFormat.CODE_39, false), CODE_93(BarcodeFormat.CODE_93, false), CODE_128(BarcodeFormat.CODE_128, false), DATA_MATRIX(BarcodeFormat.DATA_MATRIX, true), EAN_8(BarcodeFormat.EAN_8, false), EAN_13(BarcodeFormat.EAN_13, false), ITF(BarcodeFormat.ITF, false), PDF_417(BarcodeFormat.PDF_417, false), UPC_A(BarcodeFormat.UPC_A, false), UPC_E(BarcodeFormat.UPC_E, false) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SearchBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedHorizontalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.text.isKeyboardVisibleAsState import kotlinx.coroutines.delay @Composable fun SearchBar( searchString: String, onValueChange: (String) -> Unit ) { val windowInfo = LocalWindowInfo.current val focusRequester = remember { FocusRequester() } val localFocusManager = LocalFocusManager.current val state = rememberScrollState() val isKeyboardVisible by isKeyboardVisibleAsState() var isKeyboardWasVisible by rememberSaveable { mutableStateOf(null) } LaunchedEffect(isKeyboardVisible) { if (!isKeyboardVisible) { delay(600) if (isKeyboardWasVisible == true) { isKeyboardWasVisible = false } } else { isKeyboardWasVisible = true } } LaunchedEffect(windowInfo) { snapshotFlow { windowInfo.isWindowFocused }.collect { isWindowFocused -> if (isWindowFocused && isKeyboardWasVisible != false) { delay(500) focusRequester.requestFocus() isKeyboardWasVisible = true } } } BasicTextField( modifier = Modifier .fillMaxWidth() .fadingEdges(state) .enhancedHorizontalScroll(state) .focusRequester(focusRequester), value = searchString, textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onBackground), keyboardActions = KeyboardActions( onDone = { localFocusManager.clearFocus() } ), singleLine = true, cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), onValueChange = onValueChange ) if (searchString.isEmpty()) { Text( text = stringResource(R.string.search_here), modifier = Modifier .fillMaxWidth() .padding(start = 8.dp), style = LocalTextStyle.current.copy( color = MaterialTheme.colorScheme.onBackground.copy( 0.5f ) ) ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/SwipeToReveal.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("TYPEALIAS_EXPANSION_DEPRECATION", "DEPRECATION") package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.core.Easing import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.material.FractionalThreshold import androidx.compose.material.ResistanceConfig import androidx.compose.material.SwipeableDefaults import androidx.compose.material.SwipeableState import androidx.compose.material.rememberSwipeableState import androidx.compose.material.swipeable import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.animation.AlphaEasing import kotlin.math.absoluteValue import kotlin.math.roundToInt typealias RevealState = SwipeableState @Composable fun SwipeToReveal( modifier: Modifier = Modifier, enableSwipe: Boolean = true, alphaEasing: Easing = AlphaEasing, maxRevealDp: Dp = 75.dp, maxAmountOfOverflow: Dp = 250.dp, directions: Set = setOf( RevealDirection.StartToEnd, ), alphaTransformEnabled: Boolean = false, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, state: RevealState = rememberRevealState(), revealedContentEnd: @Composable BoxScope.() -> Unit = {}, revealedContentStart: @Composable BoxScope.() -> Unit = {}, swipeableContent: @Composable () -> Unit, ) { Box(modifier) { val maxRevealPx = with(LocalDensity.current) { maxRevealDp.toPx() } val draggedRatio = (state.offset.value.absoluteValue / maxRevealPx.absoluteValue).coerceIn(0f, 1f) val alpha = if (alphaTransformEnabled) alphaEasing.transform(draggedRatio) else 1f // non swipable with hidden content Box( modifier = Modifier.matchParentSize() ) { Row( modifier = Modifier .fillMaxSize() .alpha(alpha), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .weight(1f) .fillMaxHeight(), contentAlignment = Alignment.CenterStart, content = revealedContentStart ) Box( modifier = Modifier .weight(1f) .fillMaxHeight(), contentAlignment = Alignment.CenterEnd, content = revealedContentEnd ) } } Box( modifier = Modifier .then( if (enableSwipe) { Modifier .offset { IntOffset( state.offset.value.roundToInt(), 0 ) } .revealSwipeable( state = state, maxRevealPx = maxRevealPx, maxAmountOfOverflow = maxAmountOfOverflow, directions = directions, interactionSource = interactionSource ) } else Modifier ) ) { swipeableContent() } } } fun Modifier.revealSwipeable( maxRevealPx: Float, maxAmountOfOverflow: Dp, directions: Set, state: RevealState, enabled: Boolean = true, interactionSource: MutableInteractionSource ) = this.composed { val maxAmountOfOverflowPx = with(LocalDensity.current) { maxAmountOfOverflow.toPx() } val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl val anchors = mutableMapOf(0f to RevealValue.Default) if (RevealDirection.StartToEnd in directions) anchors += maxRevealPx to RevealValue.FullyRevealedEnd if (RevealDirection.EndToStart in directions) anchors += -maxRevealPx to RevealValue.FullyRevealedStart val thresholds = { _: RevealValue, _: RevealValue -> FractionalThreshold(0.5f) } val minFactor = if (RevealDirection.EndToStart in directions) SwipeableDefaults.StandardResistanceFactor else SwipeableDefaults.StiffResistanceFactor val maxFactor = if (RevealDirection.StartToEnd in directions) SwipeableDefaults.StandardResistanceFactor else SwipeableDefaults.StiffResistanceFactor Modifier.swipeable( state = state, anchors = anchors, thresholds = thresholds, orientation = Orientation.Horizontal, enabled = enabled, // state.value == RevealValue.System, reverseDirection = isRtl, resistance = ResistanceConfig( basis = maxAmountOfOverflowPx, factorAtMin = minFactor, factorAtMax = maxFactor ), interactionSource = interactionSource ) } enum class RevealDirection { /** * Can be dismissed by swiping in the reading direction. */ StartToEnd, /** * Can be dismissed by swiping in the reverse of the reading direction. */ EndToStart } /** * Possible values of [RevealState]. */ enum class RevealValue { /** * Indicates the component has not been revealed yet. */ Default, /** * Fully revealed to end */ FullyRevealedEnd, /** * Fully revealed to start */ FullyRevealedStart, } /** * Create and [remember] a [RevealState] with the default animation clock. * * @param initialValue The initial value of the state. * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change. */ @Composable fun rememberRevealState( initialValue: RevealValue = RevealValue.Default, confirmStateChange: (RevealValue) -> Boolean = { true }, ): RevealState { return rememberSwipeableState( initialValue = initialValue, confirmStateChange = confirmStateChange ) } /** * Reset the component to the default position, with an animation. */ suspend fun RevealState.reset() { animateTo( targetValue = RevealValue.Default, ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/ToastHost.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import android.content.Context import androidx.activity.compose.LocalActivity import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ErrorOutline import androidx.compose.material.icons.rounded.Folder import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.AccessibilityManager import androidx.compose.ui.platform.LocalAccessibilityManager import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.lerp import androidx.compose.ui.zIndex import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.harmonizeWithPrimary import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.animation.lessSpringySpec import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.requestStoragePermission import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.autoElevatedBorder import com.t8rin.imagetoolbox.core.utils.decodeEscaped import com.t8rin.modalsheet.FullscreenPopup import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlin.coroutines.resume import kotlin.math.abs @Composable fun ToastHost( hostState: ToastHostState = AppToastHost.state, modifier: Modifier = Modifier.fillMaxSize(), alignment: Alignment = Alignment.BottomCenter, transitionSpec: AnimatedContentTransitionScope.() -> ContentTransform = { ToastDefaults.transition }, toast: @Composable (ToastData) -> Unit = { Toast(it) }, enableSwipes: Boolean = true ) { val screenSize = LocalScreenSize.current val sizeMin = screenSize.width.coerceAtMost(screenSize.height) val currentToastData = hostState.currentToastData val accessibilityManager = LocalAccessibilityManager.current val activity = LocalActivity.current LaunchedEffect(currentToastData) { if (currentToastData != null) { if (currentToastData.visuals.message == AppToastHost.PERMISSION) { activity?.requestStoragePermission() return@LaunchedEffect } val duration = currentToastData.visuals.duration.toMillis(accessibilityManager) delay(duration) currentToastData.dismiss() } } val scope = rememberCoroutineScope() val offsetX = remember { Animatable(0f) } val alpha = remember { Animatable(1f) } val threshold = 300f FullscreenPopup( placeAboveAll = true ) { AnimatedContent( modifier = Modifier.zIndex(100f), targetState = currentToastData, transitionSpec = transitionSpec ) { data -> if (enableSwipes) { val reset: CoroutineScope.() -> Unit = { launch { alpha.animateTo( targetValue = 1f, animationSpec = lessSpringySpec() ) } launch { offsetX.animateTo( targetValue = 0f, animationSpec = lessSpringySpec() ) } } LaunchedEffect(data) { reset() } Box(modifier = modifier) { data?.let { toastData -> Box( modifier = Modifier .align(alignment) .padding( bottom = sizeMin * 0.2f, top = 24.dp, start = 12.dp, end = 12.dp ) .imePadding() .systemBarsPadding() .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen this.alpha = alpha.value translationX = offsetX.value } .pointerInput(toastData) { detectHorizontalDragGestures( onHorizontalDrag = { _, drag -> scope.launch { val new = offsetX.value + drag launch { offsetX.snapTo( targetValue = new ) } launch { alpha.snapTo( targetValue = lerp( start = 1f, stop = 0.35f, fraction = (abs(new) / threshold).fastCoerceIn( 0f, 1f ) ) ) } } }, onDragEnd = { scope.launch { if (abs(offsetX.value) > threshold) { toastData.dismiss() reset() } else { reset() } } } ) } ) { toast(toastData) } } } } else { Box(modifier = modifier) { Box(modifier = Modifier.align(alignment)) { data?.let { toast(it) } } } } } } } @Composable fun Toast( toastData: ToastData, modifier: Modifier = Modifier, shape: Shape = ToastDefaults.shape, containerColor: Color = ToastDefaults.color, contentColor: Color = ToastDefaults.contentColor, ) { val screenSize = LocalScreenSize.current val sizeMin = screenSize.width.coerceAtMost(screenSize.height) Card( colors = CardDefaults.cardColors( containerColor = containerColor, contentColor = contentColor ), modifier = modifier .heightIn(min = 48.dp) .widthIn(min = 0.dp, max = (sizeMin * 0.7f)) .autoElevatedBorder( color = MaterialTheme.colorScheme .outlineVariant(0.3f, contentColor) .copy(alpha = 0.92f), shape = shape, autoElevation = animateDpAsState( if (LocalSettingsState.current.drawContainerShadows) 6.dp else 0.dp ).value ) .alpha(0.95f), shape = shape ) { Row( modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { toastData.visuals.icon?.let { icon -> IconShapeContainer( containerColor = containerColor .blend(MaterialTheme.colorScheme.secondary, 0.5f) .blend(MaterialTheme.colorScheme.primaryContainer, 0.05f), contentColor = contentColor ) { Icon( imageVector = icon, contentDescription = null ) } Spacer(modifier = Modifier.width(8.dp)) } Text( style = MaterialTheme.typography.bodySmall, text = toastData.visuals.message, textAlign = TextAlign.Center ) } } } @EnPreview @Composable private fun Preview() = ImageToolboxThemeForPreview( isDarkTheme = false, keyColor = Color.Yellow, mapSettings = { it.copy(drawContainerShadows = false) } ) { Toast( object : ToastData { override val visuals: ToastVisuals get() = object : ToastVisuals { override val message: String get() = "File successfully saved to Documents/ImageToolbox" override val icon: ImageVector get() = Icons.Rounded.Folder override val duration: ToastDuration get() = ToastDuration.Long } override fun dismiss() = Unit } ) } @Stable @Immutable open class ToastHostState { private val mutex = Mutex() var currentToastData by mutableStateOf(null) private set suspend fun showToast( message: String, icon: ImageVector? = null, duration: ToastDuration = ToastDuration.Short ) = showToast(ToastVisualsImpl(message, icon, duration)) suspend fun showToast(visuals: ToastVisuals) = mutex.withLock { try { suspendCancellableCoroutine { continuation -> currentToastData = ToastDataImpl(visuals, continuation) } } finally { currentToastData = null } } private class ToastVisualsImpl( override val message: String, override val icon: ImageVector? = null, override val duration: ToastDuration ) : ToastVisuals { override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false other as ToastVisualsImpl if (message != other.message) return false if (icon != other.icon) return false return duration == other.duration } override fun hashCode(): Int { var result = message.hashCode() result = 31 * result + icon.hashCode() result = 31 * result + duration.hashCode() return result } } private class ToastDataImpl( override val visuals: ToastVisuals, private val continuation: CancellableContinuation ) : ToastData { override fun dismiss() { if (continuation.isActive) continuation.resume(Unit) } override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false other as ToastDataImpl if (visuals != other.visuals) return false return continuation == other.continuation } override fun hashCode(): Int { var result = visuals.hashCode() result = 31 * result + continuation.hashCode() return result } } } @Stable @Immutable interface ToastData { val visuals: ToastVisuals fun dismiss() } @Stable @Immutable interface ToastVisuals { val message: String val icon: ImageVector? val duration: ToastDuration } @Stable @Immutable open class ToastDuration(val time: kotlin.Long) { object Short : ToastDuration(3500L) object Long : ToastDuration(6500L) } @Stable @Immutable object ToastDefaults { val transition: ContentTransform get() = fadeIn(tween(300)) + scaleIn( animationSpec = spring( dampingRatio = 0.65f, stiffness = Spring.StiffnessMediumLow ), transformOrigin = TransformOrigin(0.5f, 1f) ) + slideInVertically( spring( stiffness = Spring.StiffnessHigh ) ) { it / 2 } togetherWith fadeOut(tween(250)) + slideOutVertically( tween(500) ) { it / 2 } + scaleOut( animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMediumLow ), transformOrigin = TransformOrigin(0.5f, 1f) ) val contentColor: Color @Composable get() = MaterialTheme.colorScheme.inverseOnSurface.harmonizeWithPrimary() val color: Color @Composable get() = MaterialTheme.colorScheme.inverseSurface.harmonizeWithPrimary() val shape: Shape @Composable get() = AutoCornersShape(32.dp) } private fun ToastDuration.toMillis( accessibilityManager: AccessibilityManager? ): Long { val original = this.time return accessibilityManager?.calculateRecommendedTimeoutMillis( original, containsIcons = true, containsText = true ) ?: original } suspend fun ToastHostState.showFailureToast( context: Context, throwable: Throwable ) = showFailureToast( message = context.getString( R.string.smth_went_wrong, throwable.localizedMessage?.decodeEscaped() ?: "" ) ) suspend fun ToastHostState.showFailureToast( message: String ) = showToast( message = message, icon = Icons.Rounded.ErrorOutline, duration = ToastDuration.Long ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/other/TopAppBarEmoji.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.other import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap @Composable fun TopAppBarEmoji() { val settingsState = LocalSettingsState.current Box( modifier = Modifier .padding(end = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) { Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) { repeat(5) { AnimatedVisibility( visible = settingsState.emojisCount > it, enter = fadeIn() + slideInHorizontally(), exit = fadeOut() + slideOutHorizontally() ) { EmojiItem( fontScale = LocalSettingsState.current.fontScale ?: LocalDensity.current.fontScale, emoji = settingsState.selectedEmoji?.toString(), fontSize = MaterialTheme.typography.headlineMedium.fontSize ) } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteMappings.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.palette_selection import android.content.Context import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.imagetoolbox.core.resources.R fun PaletteStyle.getTitle(context: Context): String { return when (this) { PaletteStyle.TonalSpot -> context.getString(R.string.tonal_spot) PaletteStyle.Neutral -> context.getString(R.string.neutral) PaletteStyle.Vibrant -> context.getString(R.string.vibrant) PaletteStyle.Expressive -> context.getString(R.string.expressive) PaletteStyle.Rainbow -> context.getString(R.string.rainbow) PaletteStyle.FruitSalad -> context.getString(R.string.fruit_salad) PaletteStyle.Monochrome -> context.getString(R.string.monochrome) PaletteStyle.Fidelity -> context.getString(R.string.fidelity) PaletteStyle.Content -> context.getString(R.string.content) } } fun PaletteStyle.getSubtitle(context: Context): String { return when (this) { PaletteStyle.TonalSpot -> context.getString(R.string.tonal_spot_sub) PaletteStyle.Neutral -> context.getString(R.string.neutral_sub) PaletteStyle.Vibrant -> context.getString(R.string.vibrant_sub) PaletteStyle.Expressive -> context.getString(R.string.playful_scheme) PaletteStyle.Rainbow -> context.getString(R.string.playful_scheme) PaletteStyle.FruitSalad -> context.getString(R.string.playful_scheme) PaletteStyle.Monochrome -> context.getString(R.string.monochrome_sub) PaletteStyle.Fidelity -> context.getString(R.string.fidelity_sub) PaletteStyle.Content -> context.getString(R.string.content_sub) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.palette_selection import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.material.icons.Icons import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.Swatch import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.appContext @Composable fun PaletteStyleSelection( onThemeStyleSelected: (PaletteStyle) -> Unit, shape: Shape, modifier: Modifier = Modifier, color: Color = Color.Unspecified ) { val settingsState = LocalSettingsState.current var showPaletteStyleSelectionSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( title = stringResource(R.string.palette_style), subtitle = remember(settingsState.themeStyle) { derivedStateOf { settingsState.themeStyle.getTitle(appContext) } }.value, shape = shape, containerColor = color, modifier = modifier, startIcon = Icons.Rounded.Swatch, endIcon = Icons.Rounded.MiniEdit, onClick = { showPaletteStyleSelectionSheet = true } ) EnhancedModalBottomSheet( visible = showPaletteStyleSelectionSheet, onDismiss = { showPaletteStyleSelectionSheet = it }, title = { TitleItem( text = stringResource(R.string.palette_style), icon = Icons.Rounded.Swatch ) }, confirmButton = { EnhancedButton( onClick = { showPaletteStyleSelectionSheet = false } ) { Text(text = stringResource(R.string.close)) } }, sheetContent = { LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(250.dp), contentPadding = PaddingValues(16.dp), verticalItemSpacing = 8.dp, horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), flingBehavior = enhancedFlingBehavior() ) { items(PaletteStyle.entries) { style -> PaletteStyleSelectionItem( style = style, onClick = { onThemeStyleSelected(style) } ) } } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/palette_selection/PaletteStyleSelectionItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.palette_selection import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.utils.appContext @Composable fun PaletteStyleSelectionItem( style: PaletteStyle, onClick: () -> Unit ) { val settingsState = LocalSettingsState.current val selected = settingsState.themeStyle == style PreferenceItem( onClick = onClick, title = style.getTitle(appContext), subtitle = style.getSubtitle(appContext), containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor }, modifier = Modifier .fillMaxWidth() .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme.colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = ShapeDefaults.default ), endIcon = if (selected) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.preferences import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform import androidx.compose.animation.SizeTransform import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults private val DefaultStartTransition: AnimatedContentTransitionScope.() -> ContentTransform = { fadeIn() + scaleIn() + slideInVertically() togetherWith fadeOut() + scaleOut() + slideOutVertically() using SizeTransform( clip = false ) } private val DefaultEndTransition: AnimatedContentTransitionScope.() -> ContentTransform = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() using SizeTransform(clip = false) } @Composable fun PreferenceItem( onClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, title: String, enabled: Boolean = true, subtitle: String? = null, startIcon: ImageVector? = null, endIcon: ImageVector? = null, autoShadowElevation: Dp = 1.dp, shape: Shape = ShapeDefaults.default, pressedShape: Shape = ShapeDefaults.pressed, containerColor: Color = Color.Unspecified, contentColor: Color = contentColorFor(backgroundColor = containerColor), overrideIconShapeContentColor: Boolean = false, drawStartIconContainer: Boolean = true, titleFontStyle: TextStyle = PreferenceItemDefaults.TitleFontStyle, startIconTransitionSpec: AnimatedContentTransitionScope.() -> ContentTransform = DefaultStartTransition, endIconTransitionSpec: AnimatedContentTransitionScope.() -> ContentTransform = DefaultEndTransition, onDisabledClick: (() -> Unit)? = null, placeBottomContentInside: Boolean = false, bottomContent: (@Composable () -> Unit)? = null, modifier: Modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp) ) { val targetIcon: (@Composable () -> Unit)? = if (startIcon == null) null else { { AnimatedContent( targetState = startIcon, transitionSpec = startIconTransitionSpec ) { icon -> Icon( imageVector = icon, contentDescription = null ) } } } val targetEndIcon: (@Composable () -> Unit)? = if (endIcon == null) null else { { Box { AnimatedContent( targetState = endIcon, transitionSpec = endIconTransitionSpec ) { endIcon -> Icon( imageVector = endIcon, contentDescription = null ) } } } } PreferenceItemOverload( autoShadowElevation = autoShadowElevation, contentColor = contentColor, onClick = onClick, onLongClick = onLongClick, enabled = enabled, title = title, subtitle = subtitle, startIcon = targetIcon, endIcon = targetEndIcon, overrideIconShapeContentColor = overrideIconShapeContentColor, shape = shape, pressedShape = pressedShape, containerColor = containerColor, modifier = modifier, titleFontStyle = titleFontStyle, onDisabledClick = onDisabledClick, drawStartIconContainer = drawStartIconContainer, placeBottomContentInside = placeBottomContentInside, bottomContent = bottomContent ) } object PreferenceItemDefaults { val TitleFontStyle: TextStyle @Composable get() = LocalTextStyle.current.copy( fontSize = 16.sp, fontWeight = FontWeight.Medium, lineHeight = 18.sp ) val TitleFontStyleCentered: TextStyle @Composable get() = TitleFontStyle.copy( textAlign = TextAlign.Center ) val TitleFontStyleCenteredSmall: TextStyle @Composable get() = TitleFontStyleSmall.copy( textAlign = TextAlign.Center ) val TitleFontStyleSmall: TextStyle @Composable get() = LocalTextStyle.current.copy( fontSize = 14.sp, fontWeight = FontWeight.Medium, lineHeight = 16.sp, textAlign = TextAlign.Start ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceItemOverload.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.preferences import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction @Composable fun PreferenceItemOverload( onClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, title: String, enabled: Boolean = true, subtitle: String? = null, autoShadowElevation: Dp = 1.dp, startIcon: (@Composable () -> Unit)? = null, endIcon: (@Composable () -> Unit)? = null, badge: (@Composable RowScope.() -> Unit)? = null, shape: Shape = ShapeDefaults.default, pressedShape: Shape = ShapeDefaults.pressed, containerColor: Color = Color.Unspecified, contentColor: Color = contentColorFor(backgroundColor = containerColor), overrideIconShapeContentColor: Boolean = false, resultModifier: Modifier = Modifier.padding(16.dp), modifier: Modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp), titleFontStyle: TextStyle = PreferenceItemDefaults.TitleFontStyle, onDisabledClick: (() -> Unit)? = null, drawStartIconContainer: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, placeBottomContentInside: Boolean = false, bottomContent: (@Composable () -> Unit)? = null ) { CompositionLocalProvider( LocalSettingsState provides LocalSettingsState.current.let { if (!enabled) it.copy( drawButtonShadows = false, drawContainerShadows = false, drawFabShadows = false, drawSwitchShadows = false, drawSliderShadows = false ) else it } ) { val animatedShape = shapeByInteraction( shape = shape, pressedShape = pressedShape, interactionSource = interactionSource ) Card( shape = RectangleShape, modifier = modifier .container( shape = animatedShape, resultPadding = 0.dp, color = containerColor, autoShadowElevation = autoShadowElevation ) .alpha(animateFloatAsState(targetValue = if (enabled) 1f else 0.5f).value), colors = CardDefaults.cardColors( containerColor = Color.Transparent, contentColor = contentColor ) ) { val onClickModifier = onClick ?.let { if (enabled) { Modifier.hapticsCombinedClickable( interactionSource = interactionSource, indication = LocalIndication.current, onClick = onClick, onLongClick = onLongClick ) } else { if (onDisabledClick != null) { Modifier.hapticsClickable(onClick = onDisabledClick) } else Modifier } } ?: Modifier Column( modifier = Modifier .then( onClickModifier.takeIf { placeBottomContentInside } ?: Modifier ) ) { Row( modifier = Modifier .then(onClickModifier.takeIf { !placeBottomContentInside } ?: Modifier) .then(resultModifier), verticalAlignment = Alignment.CenterVertically ) { startIcon?.let { ProvideContainerDefaults( color = containerColor ) { Row { IconShapeContainer( enabled = drawStartIconContainer, contentColor = if (overrideIconShapeContentColor) { Color.Unspecified } else IconShapeDefaults.contentColor, content = { startIcon() } ) Spacer(modifier = Modifier.width(16.dp)) } } } Column( Modifier .weight(1f) .padding(end = 16.dp) ) { Row { AnimatedContent( targetState = title, transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier.weight(1f, fill = badge == null) ) { title -> Text( text = title, style = titleFontStyle ) } badge?.invoke(this) } AnimatedContent( targetState = subtitle, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { sub -> sub?.let { Column { Spacer(modifier = Modifier.height(2.dp)) Text( text = sub, fontSize = 12.sp, textAlign = TextAlign.Start, fontWeight = FontWeight.Normal, lineHeight = 14.sp, color = LocalContentColor.current.copy(alpha = 0.5f) ) } } } if (placeBottomContentInside) bottomContent?.invoke() } ProvideContainerDefaults { endIcon?.invoke() } } if (!placeBottomContentInside) bottomContent?.invoke() } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRow.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.preferences import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction @Composable fun PreferenceRow( modifier: Modifier = Modifier, title: String, subtitle: String? = null, containerColor: Color = Color.Unspecified, enabled: Boolean = true, shape: Shape = ShapeDefaults.default, pressedShape: Shape = ShapeDefaults.pressed, contentColor: Color? = null, applyHorizontalPadding: Boolean = true, maxLines: Int = Int.MAX_VALUE, startContent: (@Composable () -> Unit)? = null, endContent: (@Composable () -> Unit)? = null, titleFontStyle: TextStyle = PreferenceItemDefaults.TitleFontStyle, resultModifier: Modifier = Modifier.padding( horizontal = if (startContent != null) 0.dp else 16.dp, vertical = 8.dp ), changeAlphaWhenDisabled: Boolean = true, drawStartIconContainer: Boolean = false, onClick: (() -> Unit)?, onDisabledClick: (() -> Unit)? = null, autoShadowElevation: Dp = 1.dp, additionalContent: (@Composable () -> Unit)? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, drawContainer: Boolean = true, ) { val animatedShape = shapeByInteraction( shape = shape, pressedShape = pressedShape, interactionSource = interactionSource ) val internalColor = contentColor ?: contentColorFor(backgroundColor = containerColor) CompositionLocalProvider( LocalContentColor provides internalColor, LocalSettingsState provides LocalSettingsState.current.let { if (!enabled) it.copy( drawButtonShadows = false, drawContainerShadows = false, drawFabShadows = false, drawSwitchShadows = false, drawSliderShadows = false ) else it } ) { val rowModifier = modifier .then( if (applyHorizontalPadding) { Modifier.padding(horizontal = 16.dp) } else Modifier ) .then( if (drawContainer) { Modifier.container( color = containerColor, shape = animatedShape, resultPadding = 0.dp, autoShadowElevation = autoShadowElevation ) } else Modifier ) .then( onClick ?.let { if (enabled) { Modifier.hapticsClickable( interactionSource = interactionSource, indication = LocalIndication.current, onClick = onClick ) } else Modifier.then( if (onDisabledClick != null) { Modifier.hapticsClickable( interactionSource = interactionSource, indication = LocalIndication.current, onClick = onDisabledClick ) } else Modifier ) } ?: Modifier ) .then(resultModifier) .then( if (changeAlphaWhenDisabled) Modifier.alpha(animateFloatAsState(targetValue = if (enabled) 1f else 0.5f).value) else Modifier ) val rowContent: @Composable (Modifier) -> Unit = { modifier -> Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { startContent?.let { content -> ProvideContainerDefaults( color = containerColor ) { if (drawStartIconContainer) { IconShapeContainer( content = { content() }, modifier = Modifier.padding(end = 16.dp) ) } else content() } } Column(modifier = Modifier.weight(1f)) { AnimatedContent( targetState = title, transitionSpec = { fadeIn().togetherWith(fadeOut()) } ) { Text( text = it, maxLines = maxLines, style = titleFontStyle, modifier = Modifier.fillMaxWidth() ) } Spacer(modifier = Modifier.height(2.dp)) AnimatedContent( targetState = subtitle, transitionSpec = { fadeIn().togetherWith(fadeOut()) } ) { it?.let { Text( text = it, fontSize = 12.sp, textAlign = TextAlign.Start, fontWeight = FontWeight.Normal, lineHeight = 14.sp, color = LocalContentColor.current.copy(alpha = 0.5f) ) } } } Spacer(Modifier.width(8.dp)) ProvideContainerDefaults(null) { endContent?.invoke() } } } if (additionalContent != null) { Column(rowModifier) { rowContent(Modifier) Column( modifier = Modifier.pointerInput(Unit) { detectTapGestures { } } ) { additionalContent() } } } else { rowContent(rowModifier) } } } @Composable fun PreferenceRow( modifier: Modifier = Modifier, title: String, enabled: Boolean = true, subtitle: String? = null, autoShadowElevation: Dp = 1.dp, color: Color = Color.Unspecified, drawStartIconContainer: Boolean = true, onDisabledClick: (() -> Unit)? = null, changeAlphaWhenDisabled: Boolean = true, contentColor: Color? = null, shape: Shape = ShapeDefaults.default, titleFontStyle: TextStyle = PreferenceItemDefaults.TitleFontStyle, startIcon: ImageVector?, endContent: (@Composable () -> Unit)? = null, additionalContent: (@Composable () -> Unit)? = null, onClick: (() -> Unit)?, ) { PreferenceRow( modifier = modifier, title = title, enabled = enabled, subtitle = subtitle, changeAlphaWhenDisabled = changeAlphaWhenDisabled, autoShadowElevation = autoShadowElevation, containerColor = color, contentColor = contentColor, shape = shape, titleFontStyle = titleFontStyle, onDisabledClick = onDisabledClick, drawStartIconContainer = false, startContent = startIcon?.let { { IconShapeContainer( enabled = drawStartIconContainer, content = { AnimatedContent(startIcon) { startIcon -> Icon( imageVector = startIcon, contentDescription = null ) } }, modifier = Modifier.padding(end = 16.dp) ) } }, endContent = endContent, resultModifier = if (endContent != null) { Modifier.padding( top = 8.dp, start = 16.dp, bottom = 8.dp ) } else Modifier.padding(16.dp), applyHorizontalPadding = false, onClick = onClick, additionalContent = additionalContent ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/PreferenceRowSwitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.preferences import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Check import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSwitch import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun PreferenceRowSwitch( modifier: Modifier = Modifier, title: String, enabled: Boolean = true, subtitle: String? = null, autoShadowElevation: Dp = 1.dp, applyHorizontalPadding: Boolean = true, checked: Boolean, containerColor: Color = Color.Unspecified, contentColor: Color? = null, shape: Shape = ShapeDefaults.default, startContent: (@Composable () -> Unit)? = null, resultModifier: Modifier = Modifier.padding( horizontal = if (startContent != null) 0.dp else 16.dp, vertical = 8.dp ), drawStartIconContainer: Boolean = false, onDisabledClick: (() -> Unit)? = null, onClick: (Boolean) -> Unit, additionalContent: (@Composable () -> Unit)? = null, changeAlphaWhenDisabled: Boolean = true, drawContainer: Boolean = true, contentInsteadOfSwitch: (@Composable () -> Unit)? = null, titleFontStyle: TextStyle = PreferenceItemDefaults.TitleFontStyle, ) { val interactionSource = remember { MutableInteractionSource() } PreferenceRow( autoShadowElevation = autoShadowElevation, enabled = enabled, modifier = modifier, resultModifier = resultModifier, applyHorizontalPadding = applyHorizontalPadding, title = title, contentColor = contentColor, shape = shape, changeAlphaWhenDisabled = changeAlphaWhenDisabled, containerColor = containerColor, subtitle = subtitle, startContent = startContent, onDisabledClick = onDisabledClick, onClick = { if (contentInsteadOfSwitch == null) { onClick(!checked) } }, drawStartIconContainer = drawStartIconContainer, endContent = { AnimatedContent( targetState = contentInsteadOfSwitch, modifier = Modifier.padding(start = 8.dp), ) { contentOfSwitch -> contentOfSwitch?.invoke() ?: EnhancedSwitch( thumbIcon = if (checked) Icons.Rounded.Check else null, colors = SwitchDefaults.colors( uncheckedBorderColor = MaterialTheme.colorScheme.outline.blend( containerColor, 0.3f ), uncheckedThumbColor = MaterialTheme.colorScheme.outline.blend( containerColor, 0.3f ), uncheckedTrackColor = containerColor, disabledUncheckedThumbColor = MaterialTheme.colorScheme.onSurface .copy(alpha = 0.12f) .compositeOver(MaterialTheme.colorScheme.surface), checkedIconColor = MaterialTheme.colorScheme.primary ), enabled = enabled, checked = checked, onCheckedChange = onClick, interactionSource = interactionSource, colorUnderSwitch = containerColor.takeOrElse { SafeLocalContainerColor } ) } }, interactionSource = interactionSource, drawContainer = drawContainer, additionalContent = additionalContent, titleFontStyle = titleFontStyle ) } @Composable fun PreferenceRowSwitch( modifier: Modifier = Modifier, title: String, enabled: Boolean = true, subtitle: String? = null, autoShadowElevation: Dp = 1.dp, checked: Boolean, containerColor: Color = Color.Unspecified, onDisabledClick: (() -> Unit)? = null, changeAlphaWhenDisabled: Boolean = true, contentColor: Color? = null, shape: Shape = ShapeDefaults.default, startIcon: ImageVector?, onClick: (Boolean) -> Unit, additionalContent: (@Composable () -> Unit)? = null, drawContainer: Boolean = true, resultModifier: Modifier = Modifier.padding(16.dp), contentInsteadOfSwitch: (@Composable () -> Unit)? = null, titleFontStyle: TextStyle = PreferenceItemDefaults.TitleFontStyle, ) { PreferenceRowSwitch( modifier = modifier, title = title, enabled = enabled, subtitle = subtitle, changeAlphaWhenDisabled = changeAlphaWhenDisabled, autoShadowElevation = autoShadowElevation, checked = checked, containerColor = containerColor, contentColor = contentColor, shape = shape, onDisabledClick = onDisabledClick, drawStartIconContainer = true, startContent = startIcon?.let { { Icon( imageVector = startIcon, contentDescription = null ) } }, resultModifier = resultModifier, applyHorizontalPadding = false, onClick = onClick, additionalContent = additionalContent, drawContainer = drawContainer, contentInsteadOfSwitch = contentInsteadOfSwitch, titleFontStyle = titleFontStyle ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/preferences/ScreenPreference.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.preferences import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen @Composable internal fun ScreenPreference( screen: Screen, navigate: (Screen) -> Unit ) { val basePreference = @Composable { PreferenceItem( onClick = { navigate(screen) }, startIcon = screen.icon, title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), modifier = Modifier.fillMaxWidth() ) } when (screen) { is Screen.GifTools -> { if (screen.type != null) { PreferenceItem( onClick = { navigate(screen) }, startIcon = screen.type.icon, title = stringResource(screen.type.title), subtitle = stringResource(screen.type.subtitle), modifier = Modifier.fillMaxWidth() ) } else basePreference() } is Screen.Filter -> { if (screen.type != null) { PreferenceItem( onClick = { navigate(screen) }, startIcon = screen.type.icon, title = stringResource(screen.type.title), subtitle = stringResource(screen.type.subtitle), modifier = Modifier.fillMaxWidth() ) } else basePreference() } is Screen.ApngTools -> { if (screen.type != null) { PreferenceItem( onClick = { navigate(screen) }, startIcon = screen.type.icon, title = stringResource(screen.type.title), subtitle = stringResource(screen.type.subtitle), modifier = Modifier.fillMaxWidth() ) } else basePreference() } is Screen.JxlTools -> { if (screen.type != null) { PreferenceItem( onClick = { navigate(screen) }, startIcon = screen.type.icon, title = stringResource(screen.type.title), subtitle = stringResource(screen.type.subtitle), modifier = Modifier.fillMaxWidth() ) } else basePreference() } is Screen.WebpTools -> { if (screen.type != null) { PreferenceItem( onClick = { navigate(screen) }, startIcon = screen.type.icon, title = stringResource(screen.type.title), subtitle = stringResource(screen.type.subtitle), modifier = Modifier.fillMaxWidth() ) } else basePreference() } is Screen.RecognizeText -> { if (screen.type != null) { PreferenceItem( onClick = { navigate(screen) }, startIcon = screen.type.icon, title = stringResource(screen.type.title), subtitle = stringResource(screen.type.subtitle), modifier = Modifier.fillMaxWidth() ) } else basePreference() } else -> basePreference() } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/OneTimeEffect.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.saver import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import kotlinx.coroutines.CoroutineScope @Composable fun OneTimeEffect( block: suspend CoroutineScope.() -> Unit ) { var isPerformed by rememberSaveable { mutableStateOf(false) } LaunchedEffect(Unit) { if (!isPerformed) { block() isPerformed = true } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/saver/Savers.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.saver import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.settings.presentation.model.PicturePickerMode import com.t8rin.imagetoolbox.core.ui.theme.toColor val ColorSaver: Saver = Saver( save = { it.toArgb() }, restore = { it.toColor() } ) val DpSaver: Saver = Saver( save = { it.value }, restore = { it.dp } ) val PicturePickerModeSaver: Saver = Saver( save = { PicturePickerMode.entries.indexOf(it) }, restore = { PicturePickerMode.entries[it] } ) val PtSaver: Saver = Saver( save = { it.value }, restore = { it.pt } ) val OffsetSaver: Saver = listSaver( save = { listOfNotNull(it?.x, it?.y) }, restore = { if (it.isEmpty()) null else Offset(it[0], it[1]) } ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/AddExifSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.AddCircleOutline import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.localizedName import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.appContext import java.util.Locale @Composable fun AddExifSheet( visible: Boolean, onDismiss: (Boolean) -> Unit, selectedTags: List, onTagSelected: (MetadataTag) -> Unit, isTagsRemovable: Boolean = false ) { val tags by remember(selectedTags, isTagsRemovable) { derivedStateOf { if (isTagsRemovable) { val addedTags = MetadataTag.entries.filter { it in selectedTags }.sorted() val notAddedTags = (MetadataTag.entries - addedTags.toSet()).sorted() addedTags + notAddedTags } else { MetadataTag.entries.filter { it !in selectedTags }.sorted() } } } if (tags.isEmpty()) { SideEffect { onDismiss(false) } } var isSearching by rememberSaveable { mutableStateOf(false) } var searchKeyword by rememberSaveable(isSearching) { mutableStateOf("") } val list by remember(tags, searchKeyword) { derivedStateOf { tags.filter { it.localizedName(appContext).contains(searchKeyword, true) || it.localizedName(appContext, Locale.ENGLISH) .contains(searchKeyword, true) } } } EnhancedModalBottomSheet( visible = visible, onDismiss = onDismiss, confirmButton = {}, enableBottomContentWeight = false, title = { AnimatedContent( targetState = isSearching ) { searching -> if (searching) { BackHandler { searchKeyword = "" isSearching = false } ProvideTextStyle(value = MaterialTheme.typography.bodyLarge) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = searchKeyword, onValueChange = { searchKeyword = it }, startIcon = { EnhancedIconButton( onClick = { searchKeyword = "" isSearching = false }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = MaterialTheme.colorScheme.onSurface ) } }, endIcon = { AnimatedVisibility( visible = searchKeyword.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = { searchKeyword = "" }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface ) } } }, shape = ShapeDefaults.circle ) } } else { Row( verticalAlignment = Alignment.CenterVertically ) { TitleItem( text = stringResource(R.string.add_tag), icon = Icons.Rounded.Exif ) Spacer(modifier = Modifier.weight(1f)) EnhancedIconButton( onClick = { isSearching = true }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Icon( imageVector = Icons.Rounded.Search, contentDescription = stringResource(R.string.search_here) ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onDismiss(false) } ) { AutoSizeText(stringResource(R.string.close)) } Spacer(Modifier.width(8.dp)) } } } }, sheetContent = { AnimatedContent(list.isNotEmpty()) { haveData -> if (haveData) { LazyColumn( contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = list, key = { _, t -> t.key } ) { index, tag -> val isSelected by remember(isTagsRemovable, tag, selectedTags) { derivedStateOf { isTagsRemovable && tag in selectedTags } } val endIcon by remember(isSelected) { derivedStateOf { if (isSelected) { Icons.Rounded.RemoveCircleOutline } else Icons.Rounded.AddCircleOutline } } PreferenceItem( title = tag.localizedName, modifier = Modifier, endIcon = endIcon, containerColor = MaterialTheme.colorScheme.secondaryContainer.copy( animateFloatAsState(if (isSelected) 0.35f else 0.1f).value ), shape = ShapeDefaults.byIndex( index = index, size = list.size ), onClick = { onTagSelected(tag) } ) } } } else { Column( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.5f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/DefaultUpdateSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.NewReleases import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.APP_RELEASES import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.HtmlText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun DefaultUpdateSheet( changelog: String, tag: String, visible: Boolean, onDismiss: () -> Unit ) { EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.new_version, tag), icon = Icons.Rounded.NewReleases ) }, sheetContent = { ProvideTextStyle(value = MaterialTheme.typography.bodyMedium) { HtmlText( html = changelog.trimIndent().trim(), modifier = Modifier .fillMaxWidth() .enhancedVerticalScroll(rememberScrollState()) .padding(12.dp) .container(resultPadding = 0.dp) .padding( start = 16.dp, end = 16.dp, top = 8.dp, bottom = 2.dp ) .offset(y = 8.dp) ) } }, confirmButton = { val linkHandler = LocalUriHandler.current EnhancedButton( onClick = { linkHandler.openUri("$APP_RELEASES/tag/${tag}") } ) { AutoSizeText(stringResource(id = R.string.update)) } } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EditExifSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AddCircleOutline import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.image.toMap import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.DeleteSweep import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.localizedName import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun EditExifSheet( visible: Boolean, onDismiss: () -> Unit, exif: Metadata?, onClearExif: () -> Unit, onUpdateTag: (MetadataTag, String) -> Unit, onRemoveTag: (MetadataTag) -> Unit ) { var showClearExifDialog by rememberSaveable { mutableStateOf(false) } val showAddExifDialog = rememberSaveable { mutableStateOf(false) } var exifMap by remember(exif) { mutableStateOf(exif?.toMap()) } EnhancedModalBottomSheet( confirmButton = { val count = remember(exifMap) { MetadataTag.entries.count { it !in (exifMap?.keys ?: emptyList()) } } Row( modifier = Modifier.offset(x = 8.dp), verticalAlignment = Alignment.CenterVertically ) { AnimatedVisibility(exifMap?.isEmpty() == false) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.errorContainer, onClick = { showClearExifDialog = true }, forceMinimumInteractiveComponentSize = false ) { Icon( imageVector = Icons.Outlined.DeleteSweep, contentDescription = null ) } } AnimatedVisibility(count > 0) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showAddExifDialog.value = true } ) { Icon( imageVector = Icons.Rounded.AddCircleOutline, contentDescription = null ) } } Spacer(Modifier.width(4.dp)) EnhancedButton( onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } } }, title = { TitleItem( text = stringResource(id = R.string.edit_exif), icon = Icons.Rounded.Exif, modifier = Modifier.padding(top = 16.dp, bottom = 16.dp, start = 8.dp, end = 16.dp) ) }, visible = visible, onDismiss = { if (!it) onDismiss() } ) { val data by remember(exifMap) { derivedStateOf { exifMap!!.toList() } } if (exifMap?.isEmpty() == false) { Box { LazyColumn( contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data, key = { _, t -> t.first.key } ) { index, (tag, value) -> Column( Modifier .fillMaxWidth() .container( color = EnhancedBottomSheetDefaults.contentContainerColor, shape = ShapeDefaults.byIndex( index = index, size = data.size ) ) ) { Row { Text( text = tag.localizedName, fontSize = 16.sp, modifier = Modifier .padding(12.dp) .weight(1f), textAlign = TextAlign.Start ) EnhancedIconButton( onClick = { onRemoveTag(tag) exifMap = exifMap?.toMutableMap() ?.apply { remove(tag) } } ) { Icon( imageVector = Icons.Rounded.RemoveCircleOutline, contentDescription = stringResource(R.string.remove) ) } } OutlinedTextField( onValueChange = { onUpdateTag(tag, it) exifMap = exifMap?.toMutableMap() ?.apply { this[tag] = it } }, value = value, textStyle = LocalTextStyle.current.copy( fontSize = 16.sp, fontWeight = FontWeight.Bold ), keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Next, autoCorrectEnabled = null ), modifier = Modifier .fillMaxWidth() .padding(8.dp) ) } } } } } else { Column( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.5f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.no_exif), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Outlined.Exif, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } AddExifSheet( visible = showAddExifDialog.value, onDismiss = { showAddExifDialog.value = it }, selectedTags = (exifMap?.keys?.toList() ?: emptyList()), onTagSelected = { tag -> onRemoveTag(tag) exifMap = exifMap?.toMutableMap() ?.apply { this[tag] = "" } } ) EnhancedAlertDialog( visible = showClearExifDialog, onDismissRequest = { showClearExifDialog = false }, title = { Text(stringResource(R.string.clear_exif)) }, icon = { Icon( imageVector = Icons.Outlined.DeleteSweep, contentDescription = null ) }, confirmButton = { EnhancedButton( onClick = { showClearExifDialog = false } ) { Text(stringResource(R.string.cancel)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showClearExifDialog = false onClearExif() exifMap = emptyMap() } ) { Text(stringResource(R.string.clear)) } }, text = { Text(stringResource(R.string.clear_exif_sub)) } ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/EmojiSelectionSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import android.net.Uri import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Face5 import androidx.compose.material.icons.outlined.Face6 import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.emoji.Emoji import com.t8rin.imagetoolbox.core.resources.emoji.EmojiData import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.other.EmojiItem import com.t8rin.imagetoolbox.core.ui.widget.other.GradientEdge import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.random.Random @Composable fun EmojiSelectionSheet( selectedEmojiIndex: Int?, emojiWithCategories: ImmutableList = Emoji.allIconsCategorized(), allEmojis: ImmutableList = Emoji.allIcons(), onEmojiPicked: (Int) -> Unit, visible: Boolean, icon: ImageVector = Icons.Outlined.Face5, onDismiss: () -> Unit ) { val state = rememberLazyGridState() LaunchedEffect(visible) { delay(600) if ((selectedEmojiIndex ?: -1) >= 0) { var count = 0 val item = emojiWithCategories.find { (_, _, emojis) -> count = 0 emojis.forEach { emoji -> count++ val index = allEmojis.indexOf(emoji) if (index == selectedEmojiIndex) return@find true } return@find false } ?: return@LaunchedEffect val index = emojiWithCategories.indexOf(item) state.animateScrollToItem( index = index, scrollOffset = 60 * count / 6 ) } } val emojiEnabled by remember(selectedEmojiIndex) { derivedStateOf { selectedEmojiIndex != -1 } } val scope = rememberCoroutineScope() EnhancedModalBottomSheet( confirmButton = { Row( verticalAlignment = Alignment.CenterVertically ) { if (selectedEmojiIndex != null) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, enabled = emojiEnabled, onClick = { onEmojiPicked(Random.nextInt(0, allEmojis.lastIndex)) scope.launch { state.animateScrollToItem(selectedEmojiIndex) } }, ) { Icon( imageVector = Icons.Rounded.Shuffle, contentDescription = stringResource(R.string.shuffle) ) } } EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } } }, title = { TitleItem( text = stringResource(R.string.emoji), icon = icon ) }, visible = visible, onDismiss = { if (!it) onDismiss() } ) { val alphaState by remember(emojiEnabled) { derivedStateOf { if (emojiEnabled) 1f else 0.4f } } Box { val density = LocalDensity.current var topPadding by remember { mutableStateOf(0.dp) } val contentPadding by remember(topPadding) { derivedStateOf { PaddingValues( start = 16.dp, end = 16.dp, bottom = 16.dp, top = topPadding ) } } var expandedCategories by rememberSaveable(visible) { mutableStateOf( if ((selectedEmojiIndex ?: -1) >= 0) { emojiWithCategories.find { (_, _, emojis) -> emojis.forEach { emoji -> val index = allEmojis.indexOf(emoji) if (index == selectedEmojiIndex) return@find true } return@find false }?.title ?: "" } else "" ) } Column( modifier = Modifier.fillMaxSize() ) { LazyVerticalGrid( state = state, columns = GridCells.Adaptive(55.dp), modifier = Modifier .weight(1f) .fillMaxWidth() .alpha( animateFloatAsState(alphaState).value ), userScrollEnabled = emojiEnabled, verticalArrangement = Arrangement.spacedBy( 6.dp, Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( 6.dp, Alignment.CenterHorizontally ), contentPadding = contentPadding, flingBehavior = enhancedFlingBehavior() ) { emojiWithCategories.forEach { (title, icon, emojis) -> item( span = { GridItemSpan(maxLineSpan) }, key = icon.name ) { val expanded by remember(title, expandedCategories) { derivedStateOf { title in expandedCategories } } val interactionSource = remember { MutableInteractionSource() } TitleItem( modifier = Modifier .padding( bottom = animateDpAsState( if (expanded) 8.dp else 0.dp ).value ) .container( color = MaterialTheme.colorScheme.surfaceContainerHigh, resultPadding = 0.dp, shape = shapeByInteraction( shape = ShapeDefaults.default, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) ) .hapticsClickable( interactionSource = interactionSource, indication = LocalIndication.current ) { expandedCategories = if (expanded) { expandedCategories.replace(title, "") } else expandedCategories + title } .padding(16.dp), text = title, icon = icon, endContent = { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = null, modifier = Modifier.rotate( animateFloatAsState( if (expanded) 180f else 0f ).value ) ) } ) } if (title in expandedCategories) { emojis.forEach { emoji -> item( key = emoji ) { val index by remember(allEmojis, emoji) { derivedStateOf { allEmojis.indexOf(emoji) } } val selected by remember(index, selectedEmojiIndex) { derivedStateOf { index == selectedEmojiIndex } } val color by animateColorAsState( if (selected) MaterialTheme.colorScheme.primaryContainer else SafeLocalContainerColor ) val borderColor by animateColorAsState( if (selected) { MaterialTheme.colorScheme.onPrimaryContainer.copy(0.7f) } else MaterialTheme.colorScheme.onSecondaryContainer.copy( alpha = 0.1f ) ) Box( modifier = Modifier .animateItem() .aspectRatio(1f) .container( color = color, shape = CloverShape, borderColor = borderColor, resultPadding = 0.dp ) .hapticsClickable { onEmojiPicked(index) }, contentAlignment = Alignment.Center ) { EmojiItem( emoji = emoji.toString(), fontSize = MaterialTheme.typography.headlineLarge.fontSize, fontScale = 1f ) } } } item( span = { GridItemSpan(maxLineSpan) } ) { Spacer(Modifier.height(2.dp)) } } } } } if (selectedEmojiIndex != null) { Column( modifier = Modifier.onGloballyPositioned { topPadding = with(density) { it.size.height.toDp() } } ) { var toggleEmoji by remember { mutableIntStateOf(0) } var checked by remember { mutableStateOf(emojiEnabled) } LaunchedEffect(toggleEmoji) { if (toggleEmoji > 0) { if (checked) onEmojiPicked(Random.nextInt(0, allEmojis.lastIndex)) else onEmojiPicked(-1) } } PreferenceRowSwitch( title = stringResource(R.string.enable_emoji), containerColor = animateColorAsState( if (emojiEnabled) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainer ).value, modifier = Modifier .fillMaxWidth() .background(EnhancedBottomSheetDefaults.containerColor) .padding(start = 16.dp, top = 20.dp, bottom = 8.dp, end = 16.dp), shape = ShapeDefaults.extremeLarge, checked = emojiEnabled, startIcon = Icons.Outlined.Face6, onClick = { checked = it; toggleEmoji++ } ) GradientEdge( modifier = Modifier .fillMaxWidth() .height(16.dp), startColor = EnhancedBottomSheetDefaults.containerColor, endColor = Color.Transparent ) } } else { LaunchedEffect(Unit) { topPadding = 16.dp } } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/PickImageFromUrisSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import android.net.Uri import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.PhotoLibrary import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.domain.utils.notNullAnd import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun PickImageFromUrisSheet( visible: Boolean, onDismiss: () -> Unit, transformations: List? = null, uris: List?, selectedUri: Uri?, onUriRemoved: (Uri) -> Unit, columns: Int, onUriPicked: (Uri) -> Unit ) { val hasUris = uris.notNullAnd { it.size >= 2 } if (!hasUris) onDismiss() EnhancedModalBottomSheet( sheetContent = { val gridState = rememberLazyGridState() LaunchedEffect(Unit) { gridState.scrollToItem( uris?.indexOf(selectedUri) ?: 0 ) } Box { LazyVerticalGrid( columns = GridCells.Fixed(columns), contentPadding = PaddingValues(8.dp), state = gridState, verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), horizontalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterHorizontally ), flingBehavior = enhancedFlingBehavior() ) { uris?.let { uris -> items( items = uris, key = { it.toString() + it.hashCode() } ) { uri -> val selected = selectedUri == uri val color by animateColorAsState( if (selected) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceContainerHigh ) val padding by animateDpAsState(if (selected) 12.dp else 4.dp) val pictureShape = animateShape( if (selected) ShapeDefaults.large else ShapeDefaults.mini ) val borderWidth by animateDpAsState(if (selected) 1.5.dp else (-1).dp) val borderColor by animateColorAsState( if (selected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent ) Box( modifier = Modifier .container( shape = ShapeDefaults.mini, resultPadding = 0.dp, color = color ) .animateItem() ) { Picture( transformations = transformations, model = uri, modifier = Modifier .aspectRatio(1f) .clip(pictureShape) .padding(padding) .clip(pictureShape) .hapticsClickable { onUriPicked(uri) onDismiss() } .border( width = borderWidth, color = borderColor, shape = pictureShape ), shape = RectangleShape, contentScale = ContentScale.Fit ) BoxAnimatedVisibility( visible = selected, modifier = Modifier.align(Alignment.TopEnd) ) { Box { Box( modifier = Modifier .padding(1.dp) .size(36.dp) .clip( AutoCornersShape( bottomStartPercent = 50 ) ) .background(MaterialTheme.colorScheme.primaryContainer) ) Box( modifier = Modifier .width(38.dp) .height(padding) .background(color) ) Box( modifier = Modifier .align(Alignment.BottomEnd) .width(padding) .height(38.dp) .background(color) ) } } EnhancedIconButton( onClick = { onUriRemoved(uri) }, forceMinimumInteractiveComponentSize = false, containerColor = color, enabled = hasUris, modifier = Modifier .size(36.dp) .align(Alignment.TopEnd), enableAutoShadowAndBorder = false, shape = AutoCornersShape( bottomStartPercent = 50 ) ) { Icon( imageVector = Icons.Rounded.RemoveCircleOutline, contentDescription = stringResource(R.string.remove) ) } } } } } } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss, ) { AutoSizeText(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(R.string.change_preview), icon = Icons.Rounded.PhotoLibrary ) }, visible = visible, onDismiss = { if (!it) onDismiss() } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ProcessImagesPreferenceSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Image import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material.icons.rounded.VisibilityOff import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.LayersSearchOutline import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.UrisCarousel import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.preferences.ScreenPreference import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.utils.screenList import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.Locale @Composable fun ProcessImagesPreferenceSheet( uris: List, extraDataType: ExtraDataType? = null, visible: Boolean, onDismiss: () -> Unit, onNavigate: (Screen) -> Unit ) { val resourceManager = LocalResourceManager.current var isSearching by rememberSaveable { mutableStateOf(false) } var searchKeyword by rememberSaveable(isSearching) { mutableStateOf("") } val (rawScreenList, hiddenScreenList) = uris.screenList(extraDataType).value val urisCorrespondingScreens by remember(hiddenScreenList, rawScreenList, searchKeyword) { derivedStateOf { if (searchKeyword.isNotBlank()) { (rawScreenList + hiddenScreenList).filter { val string = resourceManager.getString(it.title) + " " + resourceManager.getString(it.subtitle) val stringEn = resourceManager.getStringLocalized(it.title, Locale.ENGLISH.language) .plus(" ") .plus( resourceManager.getStringLocalized( it.subtitle, Locale.ENGLISH.language ) ) stringEn.contains(other = searchKeyword, ignoreCase = true).or( string.contains(other = searchKeyword, ignoreCase = true) ) } } else { rawScreenList } } } EnhancedModalBottomSheet( title = { AnimatedContent( targetState = isSearching ) { searching -> if (searching) { BackHandler { searchKeyword = "" isSearching = false } ProvideTextStyle(value = MaterialTheme.typography.bodyLarge) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = searchKeyword, onValueChange = { searchKeyword = it }, startIcon = { EnhancedIconButton( onClick = { searchKeyword = "" isSearching = false }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = MaterialTheme.colorScheme.onSurface ) } }, endIcon = { AnimatedVisibility( visible = searchKeyword.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = { searchKeyword = "" }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface ) } } }, shape = ShapeDefaults.circle ) } } else { Row( verticalAlignment = Alignment.CenterVertically ) { TitleItem( text = stringResource(R.string.image), icon = Icons.Rounded.Image ) Spacer(modifier = Modifier.weight(1f)) EnhancedIconButton( onClick = { isSearching = true }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Icon( imageVector = Icons.Outlined.LayersSearchOutline, contentDescription = stringResource(R.string.search_here) ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } Spacer(Modifier.width(8.dp)) } } } }, sheetContent = { val scope = rememberCoroutineScope() AnimatedContent( targetState = urisCorrespondingScreens.isNotEmpty(), modifier = Modifier.fillMaxWidth() ) { isNotEmpty -> if (isNotEmpty) { var showHidden by rememberSaveable { mutableStateOf(false) } LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(250.dp), contentPadding = PaddingValues(16.dp), verticalItemSpacing = 8.dp, horizontalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior() ) { if (extraDataType == null || extraDataType == ExtraDataType.Gif) { item( span = StaggeredGridItemSpan.FullLine ) { UrisCarousel(uris) } } items( items = urisCorrespondingScreens, key = { it.toString() } ) { screen -> ScreenPreference( screen = screen, navigate = { scope.launch { onDismiss() delay(200) onNavigate(screen) } } ) } if (hiddenScreenList.isNotEmpty() && searchKeyword.isBlank()) { item( span = StaggeredGridItemSpan.FullLine ) { val interactionSource = remember { MutableInteractionSource() } TitleItem( modifier = Modifier .padding( vertical = animateDpAsState( if (showHidden) 8.dp else 0.dp ).value ) .container( color = MaterialTheme.colorScheme.surfaceContainerHigh, resultPadding = 0.dp, shape = shapeByInteraction( shape = ShapeDefaults.default, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) ) .hapticsClickable( interactionSource = interactionSource, indication = LocalIndication.current ) { showHidden = !showHidden } .padding(16.dp), text = stringResource(R.string.hidden_tools), icon = if (showHidden) { Icons.Rounded.Visibility } else { Icons.Rounded.VisibilityOff }, endContent = { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = null, modifier = Modifier.rotate( animateFloatAsState( if (showHidden) 180f else 0f ).value ) ) } ) } if (showHidden) { items( items = hiddenScreenList, key = { it.toString() } ) { screen -> ScreenPreference( screen = screen, navigate = { scope.launch { onDismiss() delay(200) onNavigate(screen) } } ) } } } } } else { Column( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.5f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } } }, confirmButton = {}, enableBottomContentWeight = false, visible = visible, onDismiss = { if (!it) onDismiss() } ) } @Preview @Composable private fun Preview() = ImageToolboxThemeForPreview(true) { ProcessImagesPreferenceSheet( uris = listOf("fff".toUri()), visible = true, extraDataType = null, onDismiss = {}, onNavigate = {} ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import androidx.compose.runtime.Composable @Composable fun UpdateSheet( changelog: String, tag: String, visible: Boolean, onDismiss: () -> Unit ) { UpdateSheetImpl( changelog = changelog, tag = tag, visible = visible, onDismiss = onDismiss ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sheets/ZoomModalSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import android.content.res.Resources import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ZoomIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.asDrawable import coil3.transform.Transformation import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalSheetDragHandle import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable fun ZoomModalSheet( data: Any?, visible: Boolean, onDismiss: () -> Unit, transformations: List = emptyList() ) { val sheetContent: @Composable ColumnScope.() -> Unit = { val zoomState = rememberZoomState(maxScale = 15f) var aspectRatio by remember(data) { mutableFloatStateOf(1f) } Column( modifier = Modifier.navigationBarsPadding() ) { Box( modifier = Modifier.weight(1f) ) { Box( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp) .container( shape = ShapeDefaults.extraSmall, color = MaterialTheme.colorScheme .outlineVariant() .copy(alpha = 0.1f), resultPadding = 0.dp ) .transparencyChecker() .clipToBounds() .zoomable( zoomState = zoomState ), contentAlignment = Alignment.Center ) { Picture( model = data, contentDescription = null, onSuccess = { aspectRatio = it.result.image.asDrawable(Resources.getSystem()).safeAspectRatio }, contentScale = ContentScale.FillBounds, showTransparencyChecker = false, transformations = transformations, shape = RectangleShape, modifier = Modifier.aspectRatio(aspectRatio) ) } val zoomLevel = zoomState.scale BoxAnimatedVisibility( visible = zoomLevel > 1f, modifier = Modifier .padding( horizontal = 24.dp, vertical = 8.dp ) .align(Alignment.TopStart), enter = scaleIn() + fadeIn(), exit = scaleOut() + fadeOut() ) { Text( text = stringResource(R.string.zoom) + " ${zoomLevel.roundToTwoDigits()}x", modifier = Modifier .background( MaterialTheme.colorScheme.scrim.copy(0.4f), ShapeDefaults.circle ) .padding(horizontal = 8.dp, vertical = 4.dp), color = Color.White ) } } Row( modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { TitleItem(text = stringResource(R.string.zoom), icon = Icons.Rounded.ZoomIn) Spacer(Modifier.weight(1f)) EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss, modifier = Modifier.padding(horizontal = 12.dp) ) { AutoSizeText(stringResource(R.string.close)) } } } } if (data != null) { EnhancedModalBottomSheet( sheetContent = sheetContent, visible = visible, onDismiss = { if (!it) onDismiss() }, dragHandle = { EnhancedModalSheetDragHandle( color = Color.Transparent, drawStroke = false, heightWhenDisabled = 20.dp ) } ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/FancySlider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.hoverable import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SliderColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.animation.animateFloatingRangeAsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberRipple import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.materialShadow import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomRangeSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSliderColors import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSliderDefaults import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSliderState @Composable fun FancySlider( value: Float, enabled: Boolean, colors: SliderColors, interactionSource: MutableInteractionSource, thumbShape: Shape, modifier: Modifier, onValueChange: (Float) -> Unit, onValueChangeFinished: (() -> Unit)?, valueRange: ClosedFloatingPointRange, steps: Int, drawContainer: Boolean = true ) { val thumbColor by animateColorAsState( if (enabled) colors.thumbColor else colors.disabledThumbColor ) val settingsState = LocalSettingsState.current val thumb: @Composable (CustomSliderState) -> Unit = { sliderState -> val sliderFraction by remember(value, sliderState) { derivedStateOf { (value - sliderState.valueRange.start) / (sliderState.valueRange.endInclusive - sliderState.valueRange.start) } } Spacer( Modifier .zIndex(100f) .rotate(1080f * sliderFraction) .size(26.dp) .indication( interactionSource = interactionSource, indication = rememberRipple( bounded = false, radius = 24.dp ) ) .hoverable(interactionSource = interactionSource) .materialShadow( shape = thumbShape, elevation = 1.dp, enabled = settingsState.drawSliderShadows ) .background(thumbColor, thumbShape) ) } LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { CustomSlider( interactionSource = interactionSource, thumb = thumb, enabled = enabled, modifier = modifier .then( if (drawContainer) { Modifier .container( shape = ShapeDefaults.circle, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) { 1.dp } else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme .outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ) .copy(0.3f), color = SafeLocalContainerColor .copy(0.5f) .compositeOver(MaterialTheme.colorScheme.surface) .copy(colors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) .padding(horizontal = 6.dp) } else Modifier ), colors = colors.toCustom(), value = value, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, track = { sliderState -> CustomSliderDefaults.Track( sliderState = sliderState, colors = colors.toCustom(), trackHeight = 38.dp, enabled = enabled ) } ) } } @Composable fun FancyRangeSlider( value: ClosedFloatingPointRange, enabled: Boolean, colors: SliderColors, startInteractionSource: MutableInteractionSource, endInteractionSource: MutableInteractionSource, thumbShape: Shape, modifier: Modifier, onValueChange: (ClosedFloatingPointRange) -> Unit, onValueChangeFinished: (() -> Unit)?, valueRange: ClosedFloatingPointRange, steps: Int, drawContainer: Boolean = true ) { val thumbColor by animateColorAsState( if (enabled) colors.thumbColor else colors.disabledThumbColor ) val settingsState = LocalSettingsState.current LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) { CustomRangeSlider( startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, enabled = enabled, modifier = modifier .then( if (drawContainer) { Modifier .container( shape = ShapeDefaults.circle, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) { 1.dp } else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme .outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ) .copy(0.3f), color = SafeLocalContainerColor .copy(0.5f) .compositeOver(MaterialTheme.colorScheme.surface) .copy(colors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) .padding(horizontal = 6.dp) } else Modifier ), colors = colors.toCustom(), value = animateFloatingRangeAsState(value).value, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, startThumb = { Spacer( Modifier .zIndex(100f) .size(26.dp) .indication( interactionSource = startInteractionSource, indication = rememberRipple( bounded = false, radius = 24.dp ) ) .hoverable(interactionSource = startInteractionSource) .materialShadow( shape = thumbShape, elevation = 1.dp, enabled = LocalSettingsState.current.drawSliderShadows ) .background(thumbColor, thumbShape) ) }, endThumb = { Spacer( Modifier .zIndex(100f) .size(26.dp) .indication( interactionSource = endInteractionSource, indication = rememberRipple( bounded = false, radius = 24.dp ) ) .hoverable(interactionSource = endInteractionSource) .materialShadow( shape = thumbShape, elevation = 1.dp, enabled = LocalSettingsState.current.drawSliderShadows ) .background(thumbColor, thumbShape) ) }, steps = steps, track = { sliderState -> CustomSliderDefaults.Track( rangeSliderState = sliderState, colors = colors.toCustom(), trackHeight = 38.dp, enabled = enabled ) } ) } } @Stable internal fun SliderColors.toCustom(): CustomSliderColors = CustomSliderColors( thumbColor = thumbColor, activeTrackColor = activeTrackColor, activeTickColor = activeTickColor, inactiveTrackColor = inactiveTrackColor, inactiveTickColor = inactiveTickColor, disabledThumbColor = disabledThumbColor, disabledActiveTrackColor = disabledActiveTrackColor, disabledActiveTickColor = disabledActiveTickColor, disabledInactiveTrackColor = disabledInactiveTrackColor, disabledInactiveTickColor = disabledInactiveTickColor ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/HyperOSSlider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomRangeSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSliderDefaults import androidx.compose.material3.SliderColors as M3SliderColors import androidx.compose.material3.SliderDefaults as M3Defaults @Composable fun HyperOSSlider( value: Float, onValueChange: (Float) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, onValueChangeFinished: (() -> Unit)? = null, valueRange: ClosedFloatingPointRange = 0f..1f, colors: M3SliderColors = M3Defaults.colors(), steps: Int = 0, drawContainer: Boolean = true ) { val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val shape = AutoCircleShape() val settingsState = LocalSettingsState.current val sliderColors = colors.toCustom() CustomSlider( value = value, onValueChange = onValueChange, modifier = modifier, enabled = enabled, onValueChangeFinished = onValueChangeFinished, colors = sliderColors, interactionSource = interactionSource, steps = steps, thumb = {}, track = { sliderState -> CustomSliderDefaults.Track( colors = sliderColors, enabled = enabled, sliderState = sliderState, trackHeight = 30.dp, modifier = Modifier.then( if (drawContainer) { Modifier.container( shape = shape, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) 1.dp else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ).copy(0.3f), color = SafeLocalContainerColor .copy(0.3f) .compositeOver( takeColorFromScheme { if (it) tertiaryContainer .blend(secondaryContainer, 0.5f) .copy(0.1f) else secondaryContainer .blend(tertiaryContainer, 0.3f) .copy(0.2f) } ) .copy(sliderColors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) } else Modifier ), strokeCap = StrokeCap.Butt ) }, valueRange = valueRange ) } @Composable fun HyperOSRangeSlider( value: ClosedFloatingPointRange, onValueChange: (ClosedFloatingPointRange) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, onValueChangeFinished: (() -> Unit)? = null, valueRange: ClosedFloatingPointRange = 0f..1f, colors: M3SliderColors = M3Defaults.colors(), steps: Int = 0, drawContainer: Boolean = true, ) { val shape = AutoCircleShape() val settingsState = LocalSettingsState.current val sliderColors = colors.toCustom() CustomRangeSlider( value = value, onValueChange = onValueChange, modifier = modifier, enabled = enabled, onValueChangeFinished = onValueChangeFinished, colors = sliderColors, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, steps = steps, startThumb = {}, endThumb = {}, track = { rangeSliderState -> CustomSliderDefaults.Track( colors = sliderColors, enabled = enabled, rangeSliderState = rangeSliderState, trackHeight = 30.dp, modifier = Modifier.then( if (drawContainer) { Modifier.container( shape = shape, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) 1.dp else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ).copy(0.3f), color = SafeLocalContainerColor .copy(0.3f) .compositeOver( takeColorFromScheme { if (it) tertiaryContainer .blend(secondaryContainer, 0.5f) .copy(0.1f) else secondaryContainer .blend(tertiaryContainer, 0.3f) .copy(0.2f) } ) .copy(sliderColors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) } else Modifier ), strokeCap = StrokeCap.Butt ) }, valueRange = valueRange ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M2Slider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SliderColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.animation.animateFloatingRangeAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomRangeSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSlider import com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider.CustomSliderDefaults @Composable fun M2Slider( value: Float, enabled: Boolean, colors: SliderColors, interactionSource: MutableInteractionSource, modifier: Modifier, onValueChange: (Float) -> Unit, onValueChangeFinished: (() -> Unit)?, valueRange: ClosedFloatingPointRange, steps: Int, drawContainer: Boolean = true ) { val settingsState = LocalSettingsState.current CustomSlider( interactionSource = interactionSource, enabled = enabled, modifier = modifier .then( if (drawContainer) { Modifier .container( shape = ShapeDefaults.circle, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) { 1.dp } else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme .outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ) .copy(0.3f), color = SafeLocalContainerColor .copy(0.3f) .compositeOver( takeColorFromScheme { if (it) { tertiaryContainer .blend( secondaryContainer, 0.5f ) .copy(0.1f) } else { secondaryContainer .blend( tertiaryContainer, 0.3f ) .copy(0.2f) } } ) .copy(colors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) .padding(horizontal = 12.dp) } else Modifier ), value = value, colors = colors.toCustom(), onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, track = { CustomSliderDefaults.Track( sliderState = it, colors = colors.toCustom(), trackHeight = 4.dp, enabled = enabled ) } ) } @Composable fun M2RangeSlider( value: ClosedFloatingPointRange, enabled: Boolean, colors: SliderColors, startInteractionSource: MutableInteractionSource, endInteractionSource: MutableInteractionSource, modifier: Modifier, onValueChange: (ClosedFloatingPointRange) -> Unit, onValueChangeFinished: (() -> Unit)?, valueRange: ClosedFloatingPointRange, steps: Int, drawContainer: Boolean = true ) { val settingsState = LocalSettingsState.current CustomRangeSlider( startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, enabled = enabled, modifier = modifier .then( if (drawContainer) { Modifier .container( shape = ShapeDefaults.circle, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) { 1.dp } else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme .outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ) .copy(0.3f), color = SafeLocalContainerColor .copy(0.3f) .compositeOver( takeColorFromScheme { if (it) { tertiaryContainer .blend( secondaryContainer, 0.5f ) .copy(0.1f) } else { secondaryContainer .blend( tertiaryContainer, 0.3f ) .copy(0.2f) } } ) .copy(colors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) .padding(horizontal = 12.dp) } else Modifier ), value = animateFloatingRangeAsState(value).value, colors = colors.toCustom(), onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, track = { CustomSliderDefaults.Track( rangeSliderState = it, colors = colors.toCustom(), trackHeight = 4.dp, enabled = enabled ) } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/M3Slider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RangeSlider import androidx.compose.material3.Slider import androidx.compose.material3.SliderColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.animation.animateFloatingRangeAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun M3Slider( value: Float, enabled: Boolean, colors: SliderColors, interactionSource: MutableInteractionSource, modifier: Modifier, onValueChange: (Float) -> Unit, onValueChangeFinished: (() -> Unit)?, valueRange: ClosedFloatingPointRange, steps: Int, drawContainer: Boolean = true ) { val settingsState = LocalSettingsState.current Slider( interactionSource = interactionSource, enabled = enabled, modifier = modifier .then( if (drawContainer) { Modifier .padding(vertical = 2.dp) .container( shape = ShapeDefaults.small, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) { 1.dp } else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme .outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ) .copy(0.3f), color = SafeLocalContainerColor .copy(0.3f) .compositeOver( takeColorFromScheme { if (it) { tertiaryContainer .blend( secondaryContainer, 0.5f ) .copy(0.1f) } else { secondaryContainer .blend( tertiaryContainer, 0.3f ) .copy(0.2f) } } ) .copy(colors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) .padding(horizontal = 12.dp, vertical = 6.dp) } else Modifier ), value = value, colors = colors, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, ) } @Composable fun M3RangeSlider( value: ClosedFloatingPointRange, enabled: Boolean, colors: SliderColors, startInteractionSource: MutableInteractionSource, endInteractionSource: MutableInteractionSource, modifier: Modifier, onValueChange: (ClosedFloatingPointRange) -> Unit, onValueChangeFinished: (() -> Unit)?, valueRange: ClosedFloatingPointRange, steps: Int, drawContainer: Boolean = true ) { val settingsState = LocalSettingsState.current RangeSlider( startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, enabled = enabled, modifier = modifier .then( if (drawContainer) { Modifier .padding(vertical = 2.dp) .container( shape = ShapeDefaults.small, autoShadowElevation = animateDpAsState( if (settingsState.drawSliderShadows) { 1.dp } else 0.dp ).value, resultPadding = 0.dp, borderColor = MaterialTheme.colorScheme .outlineVariant( luminance = 0.1f, onTopOf = SwitchDefaults.colors().disabledCheckedTrackColor ) .copy(0.3f), color = SafeLocalContainerColor .copy(0.3f) .compositeOver( takeColorFromScheme { if (it) { tertiaryContainer .blend( secondaryContainer, 0.5f ) .copy(0.1f) } else { secondaryContainer .blend( tertiaryContainer, 0.3f ) .copy(0.2f) } } ) .copy(colors.activeTrackColor.alpha), composeColorOnTopOfBackground = false ) .padding(horizontal = 12.dp, vertical = 6.dp) } else Modifier ), value = animateFloatingRangeAsState(value).value, colors = colors, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, steps = steps, ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSlider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.annotation.IntRange import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.requiredSizeIn import androidx.compose.foundation.progressSemantics import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset import androidx.compose.ui.util.fastFirst import androidx.compose.ui.util.lerp import kotlin.math.abs import kotlin.math.roundToInt /** * Material Design Range slider. * * Range Sliders expand upon [CustomSlider] using the same concepts but allow the user to select 2 values. * * The two values are still bounded by the value range but they also cannot cross each other. * * Use continuous Range Sliders to allow users to make meaningful selections that don’t * require a specific values: * * * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values: * * * @param value current values of the RangeSlider. If either value is outside of [valueRange] * provided, it will be coerced to this range. * @param onValueChange lambda in which values should be updated * @param modifier modifiers for the Range Slider layout * @param enabled whether or not component is enabled and can we interacted with or not * @param valueRange range of values that Range Slider values can take. Passed [value] will be * coerced to this range * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed * between across the whole value range. If 0, range slider will behave as a continuous slider and * allow to choose any value from the range specified. Must not be negative. * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback * shouldn't be used to update the range slider values (use [onValueChange] for that), but rather to * know when the user has completed selecting a new value by ending a drag or a click. * @param colors [CustomSliderColors] that will be used to determine the color of the Range Slider * parts in different state. See [CustomSliderDefaults.colors] to customize. */ @Composable fun CustomRangeSlider( value: ClosedFloatingPointRange, onValueChange: (ClosedFloatingPointRange) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, valueRange: ClosedFloatingPointRange = 0f..1f, @IntRange(from = 0) steps: Int = 0, onValueChangeFinished: (() -> Unit)? = null, colors: CustomSliderColors = CustomSliderDefaults.colors() ) { val startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() } val endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() } CustomRangeSlider( value = value, onValueChange = onValueChange, modifier = modifier, enabled = enabled, valueRange = valueRange, steps = steps, onValueChangeFinished = onValueChangeFinished, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, startThumb = { CustomSliderDefaults.Thumb( interactionSource = startInteractionSource, colors = colors, enabled = enabled ) }, endThumb = { CustomSliderDefaults.Thumb( interactionSource = endInteractionSource, colors = colors, enabled = enabled ) }, track = { rangeSliderState -> CustomSliderDefaults.Track( colors = colors, enabled = enabled, rangeSliderState = rangeSliderState ) } ) } /** * Material Design Range slider. * * Range Sliders expand upon [CustomSlider] using the same concepts but allow the user to select 2 values. * * The two values are still bounded by the value range but they also cannot cross each other. * * It uses the provided startThumb for the slider's start thumb and endThumb for the * slider's end thumb. It also uses the provided track for the slider's track. If nothing is * passed for these parameters, it will use [CustomSliderDefaults.Thumb] and [CustomSliderDefaults.Track] * for the thumbs and track. * * Use continuous Range Sliders to allow users to make meaningful selections that don’t * require a specific values: * * * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values: * * @param value current values of the RangeSlider. If either value is outside of [valueRange] * provided, it will be coerced to this range. * @param onValueChange lambda in which values should be updated * @param modifier modifiers for the Range Slider layout * @param enabled whether or not component is enabled and can we interacted with or not * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback * shouldn't be used to update the range slider values (use [onValueChange] for that), but rather to * know when the user has completed selecting a new value by ending a drag or a click. * @param colors [CustomSliderColors] that will be used to determine the color of the Range Slider * parts in different state. See [CustomSliderDefaults.colors] to customize. * @param startInteractionSource the [MutableInteractionSource] representing the stream of * [Interaction]s for the start thumb. You can create and pass in your own * `remember`ed instance to observe. * @param endInteractionSource the [MutableInteractionSource] representing the stream of * [Interaction]s for the end thumb. You can create and pass in your own * `remember`ed instance to observe. * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed * between across the whole value range. If 0, range slider will behave as a continuous slider and * allow to choose any value from the range specified. Must not be negative. * @param startThumb the start thumb to be displayed on the Range Slider. The lambda receives a * [CustomRangeSliderState] which is used to obtain the current active track. * @param endThumb the end thumb to be displayed on the Range Slider. The lambda receives a * [CustomRangeSliderState] which is used to obtain the current active track. * @param track the track to be displayed on the range slider, it is placed underneath the thumb. * The lambda receives a [CustomRangeSliderState] which is used to obtain the current active track. * @param valueRange range of values that Range Slider values can take. Passed [value] will be * coerced to this range. */ @Composable fun CustomRangeSlider( value: ClosedFloatingPointRange, onValueChange: (ClosedFloatingPointRange) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, valueRange: ClosedFloatingPointRange = 0f..1f, onValueChangeFinished: (() -> Unit)? = null, colors: CustomSliderColors = CustomSliderDefaults.colors(), startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, startThumb: @Composable (CustomRangeSliderState) -> Unit = { CustomSliderDefaults.Thumb( interactionSource = startInteractionSource, colors = colors, enabled = enabled ) }, endThumb: @Composable (CustomRangeSliderState) -> Unit = { CustomSliderDefaults.Thumb( interactionSource = endInteractionSource, colors = colors, enabled = enabled ) }, track: @Composable (CustomRangeSliderState) -> Unit = { rangeSliderState -> CustomSliderDefaults.Track( colors = colors, enabled = enabled, rangeSliderState = rangeSliderState ) }, @IntRange(from = 0) steps: Int = 0 ) { val state = remember( steps, valueRange, onValueChangeFinished ) { CustomRangeSliderState( value.start, value.endInclusive, steps, onValueChangeFinished, valueRange ) } state.onValueChange = { onValueChange(it.start..it.endInclusive) } state.activeRangeStart = value.start state.activeRangeEnd = value.endInclusive CustomRangeSlider( modifier = modifier, state = state, enabled = enabled, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, startThumb = startThumb, endThumb = endThumb, track = track ) } /** * Material Design Range slider. * * Range Sliders expand upon [CustomSlider] using the same concepts but allow the user to select 2 values. * * The two values are still bounded by the value range but they also cannot cross each other. * * It uses the provided startThumb for the slider's start thumb and endThumb for the * slider's end thumb. It also uses the provided track for the slider's track. If nothing is * passed for these parameters, it will use [CustomSliderDefaults.Thumb] and [CustomSliderDefaults.Track] * for the thumbs and track. * * Use continuous Range Sliders to allow users to make meaningful selections that don’t * require a specific values: * * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values: * * A custom start/end thumb and track can be provided: * * @param state [CustomRangeSliderState] which contains the current values of the RangeSlider. * @param modifier modifiers for the Range Slider layout * @param enabled whether or not component is enabled and can we interacted with or not * @param colors [CustomSliderColors] that will be used to determine the color of the Range Slider * parts in different state. See [CustomSliderDefaults.colors] to customize. * @param startInteractionSource the [MutableInteractionSource] representing the stream of * [Interaction]s for the start thumb. You can create and pass in your own * `remember`ed instance to observe. * @param endInteractionSource the [MutableInteractionSource] representing the stream of * [Interaction]s for the end thumb. You can create and pass in your own * `remember`ed instance to observe. * @param startThumb the start thumb to be displayed on the Range Slider. The lambda receives a * [CustomRangeSliderState] which is used to obtain the current active track. * @param endThumb the end thumb to be displayed on the Range Slider. The lambda receives a * [CustomRangeSliderState] which is used to obtain the current active track. * @param track the track to be displayed on the range slider, it is placed underneath the thumb. * The lambda receives a [CustomRangeSliderState] which is used to obtain the current active track. */ @Composable fun CustomRangeSlider( state: CustomRangeSliderState, modifier: Modifier = Modifier, enabled: Boolean = true, colors: CustomSliderColors = CustomSliderDefaults.colors(), startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }, startThumb: @Composable (CustomRangeSliderState) -> Unit = { CustomSliderDefaults.Thumb( interactionSource = startInteractionSource, colors = colors, enabled = enabled ) }, endThumb: @Composable (CustomRangeSliderState) -> Unit = { CustomSliderDefaults.Thumb( interactionSource = endInteractionSource, colors = colors, enabled = enabled ) }, track: @Composable (CustomRangeSliderState) -> Unit = { rangeSliderState -> CustomSliderDefaults.Track( colors = colors, enabled = enabled, rangeSliderState = rangeSliderState ) } ) { require(state.steps >= 0) { "steps should be >= 0" } CustomRangeSliderImpl( modifier = modifier, state = state, enabled = enabled, startInteractionSource = startInteractionSource, endInteractionSource = endInteractionSource, startThumb = startThumb, endThumb = endThumb, track = track ) } @Composable private fun CustomRangeSliderImpl( modifier: Modifier, state: CustomRangeSliderState, enabled: Boolean, startInteractionSource: MutableInteractionSource, endInteractionSource: MutableInteractionSource, startThumb: @Composable ((CustomRangeSliderState) -> Unit), endThumb: @Composable ((CustomRangeSliderState) -> Unit), track: @Composable ((CustomRangeSliderState) -> Unit) ) { state.isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl val pressDrag = Modifier.rangeSliderPressDragModifier( state, startInteractionSource, endInteractionSource, enabled ) val startThumbSemantics = Modifier.rangeSliderStartThumbSemantics(state, enabled) val endThumbSemantics = Modifier.rangeSliderEndThumbSemantics(state, enabled) Layout( { Box( modifier = Modifier .layoutId(RangeSliderComponents.START_THUMB) .focusable(enabled, startInteractionSource) .then(startThumbSemantics) ) { startThumb(state) } Box( modifier = Modifier .layoutId(RangeSliderComponents.END_THUMB) .focusable(enabled, endInteractionSource) .then(endThumbSemantics) ) { endThumb(state) } Box(modifier = Modifier.layoutId(RangeSliderComponents.TRACK)) { track(state) } }, modifier = modifier .minimumInteractiveComponentSize() .requiredSizeIn( minWidth = 20.dp, minHeight = 20.dp ) .then(pressDrag) ) { measurables, constraints -> val startThumbPlaceable = measurables.fastFirst { it.layoutId == RangeSliderComponents.START_THUMB }.measure( constraints ) val endThumbPlaceable = measurables.fastFirst { it.layoutId == RangeSliderComponents.END_THUMB }.measure( constraints ) val trackPlaceable = measurables.fastFirst { it.layoutId == RangeSliderComponents.TRACK }.measure( constraints.offset( horizontal = -(startThumbPlaceable.width + endThumbPlaceable.width) / 2 ).copy(minHeight = 0) ) val sliderWidth = trackPlaceable.width + (startThumbPlaceable.width + endThumbPlaceable.width) / 2 val sliderHeight = maxOf( trackPlaceable.height, startThumbPlaceable.height, endThumbPlaceable.height ) state.startThumbWidth = startThumbPlaceable.width.toFloat() state.endThumbWidth = endThumbPlaceable.width.toFloat() state.totalWidth = sliderWidth state.updateMinMaxPx() val trackOffsetX = startThumbPlaceable.width / 2 val startThumbOffsetX = (trackPlaceable.width * state.coercedActiveRangeStartAsFraction) .roundToInt() // When start thumb and end thumb have different widths, // we need to add a correction for the centering of the slider. val endCorrection = (startThumbPlaceable.width - endThumbPlaceable.width) / 2 val endThumbOffsetX = (trackPlaceable.width * state.coercedActiveRangeEndAsFraction + endCorrection) .roundToInt() val trackOffsetY = (sliderHeight - trackPlaceable.height) / 2 val startThumbOffsetY = (sliderHeight - startThumbPlaceable.height) / 2 val endThumbOffsetY = (sliderHeight - endThumbPlaceable.height) / 2 layout( sliderWidth, sliderHeight ) { trackPlaceable.placeRelative( trackOffsetX, trackOffsetY ) startThumbPlaceable.placeRelative( startThumbOffsetX, startThumbOffsetY ) endThumbPlaceable.placeRelative( endThumbOffsetX, endThumbOffsetY ) } } } private fun Modifier.rangeSliderStartThumbSemantics( state: CustomRangeSliderState, enabled: Boolean ): Modifier { val valueRange = state.valueRange.start..state.activeRangeEnd return semantics { if (!enabled) disabled() setProgress( action = { targetValue -> var newValue = targetValue.coerceIn( valueRange.start, valueRange.endInclusive ) val originalVal = newValue val resolvedValue = if (state.startSteps > 0) { var distance: Float = newValue for (i in 0..state.startSteps + 1) { val stepValue = lerp( valueRange.start, valueRange.endInclusive, i.toFloat() / (state.startSteps + 1) ) if (abs(stepValue - originalVal) <= distance) { distance = abs(stepValue - originalVal) newValue = stepValue } } newValue } else { newValue } // This is to keep it consistent with AbsSeekbar.java: return false if no // change from current. if (resolvedValue == state.activeRangeStart) { false } else { val resolvedRange = CustomSliderRange(resolvedValue, state.activeRangeEnd) val activeRange = CustomSliderRange(state.activeRangeStart, state.activeRangeEnd) if (resolvedRange != activeRange) { if (state.onValueChange != null) { state.onValueChange?.let { it(resolvedRange) } } else { state.activeRangeStart = resolvedRange.start state.activeRangeEnd = resolvedRange.endInclusive } } state.onValueChangeFinished?.invoke() true } } ) }.progressSemantics( state.activeRangeStart, valueRange, state.startSteps ) } private fun Modifier.rangeSliderEndThumbSemantics( state: CustomRangeSliderState, enabled: Boolean ): Modifier { val valueRange = state.activeRangeStart..state.valueRange.endInclusive return semantics { if (!enabled) disabled() setProgress( action = { targetValue -> var newValue = targetValue.coerceIn(valueRange.start, valueRange.endInclusive) val originalVal = newValue val resolvedValue = if (state.endSteps > 0) { var distance: Float = newValue for (i in 0..state.endSteps + 1) { val stepValue = lerp( valueRange.start, valueRange.endInclusive, i.toFloat() / (state.endSteps + 1) ) if (abs(stepValue - originalVal) <= distance) { distance = abs(stepValue - originalVal) newValue = stepValue } } newValue } else { newValue } // This is to keep it consistent with AbsSeekbar.java: return false if no // change from current. if (resolvedValue == state.activeRangeEnd) { false } else { val resolvedRange = CustomSliderRange(state.activeRangeStart, resolvedValue) val activeRange = CustomSliderRange(state.activeRangeStart, state.activeRangeEnd) if (resolvedRange != activeRange) { if (state.onValueChange != null) { state.onValueChange?.let { it(resolvedRange) } } else { state.activeRangeStart = resolvedRange.start state.activeRangeEnd = resolvedRange.endInclusive } } state.onValueChangeFinished?.invoke() true } } ) }.progressSemantics( state.activeRangeEnd, valueRange, state.endSteps ) } private enum class RangeSliderComponents { END_THUMB, START_THUMB, TRACK } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.annotation.IntRange import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import kotlin.math.floor import kotlin.math.max import kotlin.math.min /** * Class that holds information about [CustomRangeSlider]'s active range. * * @param activeRangeStart [Float] that indicates the initial * start of the active range of the slider. If outside of [valueRange] * provided, value will be coerced to this range. * @param activeRangeEnd [Float] that indicates the initial * end of the active range of the slider. If outside of [valueRange] * provided, value will be coerced to this range. * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed * between across the whole value range. If 0, range slider will behave as a continuous slider and * allow to choose any value from the range specified. Must not be negative. * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback * shouldn't be used to update the range slider values (use [onValueChange] for that), but rather * to know when the user has completed selecting a new value by ending a drag or a click. * @param valueRange range of values that Range Slider values can take. [activeRangeStart] * and [activeRangeEnd] will be coerced to this range. */ @Stable class CustomRangeSliderState( activeRangeStart: Float = 0f, activeRangeEnd: Float = 1f, @IntRange(from = 0) val steps: Int = 0, val onValueChangeFinished: (() -> Unit)? = null, val valueRange: ClosedFloatingPointRange = 0f..1f ) { private var activeRangeStartState by mutableFloatStateOf(activeRangeStart) private var activeRangeEndState by mutableFloatStateOf(activeRangeEnd) /** * [Float] that indicates the start of the current active range for the [CustomRangeSlider]. */ var activeRangeStart: Float set(newVal) { val coercedValue = newVal.coerceIn(valueRange.start, activeRangeEnd) val snappedValue = snapValueToTick( coercedValue, tickFractions, valueRange.start, valueRange.endInclusive ) activeRangeStartState = snappedValue } get() = activeRangeStartState /** * [Float] that indicates the end of the current active range for the [CustomRangeSlider]. */ var activeRangeEnd: Float set(newVal) { val coercedValue = newVal.coerceIn(activeRangeStart, valueRange.endInclusive) val snappedValue = snapValueToTick( coercedValue, tickFractions, valueRange.start, valueRange.endInclusive ) activeRangeEndState = snappedValue } get() = activeRangeEndState internal var onValueChange: ((CustomSliderRange) -> Unit)? = null internal val tickFractions = stepsToTickFractions(steps) internal var startThumbWidth by mutableFloatStateOf(0f) internal var endThumbWidth by mutableFloatStateOf(0f) internal var totalWidth by mutableIntStateOf(0) internal var rawOffsetStart by mutableFloatStateOf(0f) internal var rawOffsetEnd by mutableFloatStateOf(0f) internal var isRtl by mutableStateOf(false) internal val gestureEndAction: (Boolean) -> Unit = { onValueChangeFinished?.invoke() } private var maxPx by mutableFloatStateOf(0f) private var minPx by mutableFloatStateOf(0f) internal fun onDrag( isStart: Boolean, offset: Float ) { val offsetRange = if (isStart) { rawOffsetStart = (rawOffsetStart + offset) rawOffsetEnd = scaleToOffset(minPx, maxPx, activeRangeEnd) val offsetEnd = rawOffsetEnd var offsetStart = rawOffsetStart.coerceIn(minPx, offsetEnd) offsetStart = snapValueToTick(offsetStart, tickFractions, minPx, maxPx) CustomSliderRange(offsetStart, offsetEnd) } else { rawOffsetEnd = (rawOffsetEnd + offset) rawOffsetStart = scaleToOffset(minPx, maxPx, activeRangeStart) val offsetStart = rawOffsetStart var offsetEnd = rawOffsetEnd.coerceIn(offsetStart, maxPx) offsetEnd = snapValueToTick(offsetEnd, tickFractions, minPx, maxPx) CustomSliderRange(offsetStart, offsetEnd) } val scaledUserValue = scaleToUserValue(minPx, maxPx, offsetRange) if (scaledUserValue != CustomSliderRange(activeRangeStart, activeRangeEnd)) { if (onValueChange != null) { onValueChange?.let { it(scaledUserValue) } } else { this.activeRangeStart = scaledUserValue.start this.activeRangeEnd = scaledUserValue.endInclusive } } } internal val coercedActiveRangeStartAsFraction get() = calcFraction( valueRange.start, valueRange.endInclusive, activeRangeStart ) internal val coercedActiveRangeEndAsFraction get() = calcFraction( valueRange.start, valueRange.endInclusive, activeRangeEnd ) internal val startSteps get() = floor(steps * coercedActiveRangeEndAsFraction).toInt() internal val endSteps get() = floor(steps * (1f - coercedActiveRangeStartAsFraction)).toInt() // scales range offset from within minPx..maxPx to within valueRange.start..valueRange.end private fun scaleToUserValue( minPx: Float, maxPx: Float, offset: CustomSliderRange ) = scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive) // scales float userValue within valueRange.start..valueRange.end to within minPx..maxPx private fun scaleToOffset( minPx: Float, maxPx: Float, userValue: Float ) = scale(valueRange.start, valueRange.endInclusive, userValue, minPx, maxPx) internal fun updateMinMaxPx() { val newMaxPx = max(totalWidth - endThumbWidth / 2, 0f) val newMinPx = min(startThumbWidth / 2, newMaxPx) if (minPx != newMinPx || maxPx != newMaxPx) { minPx = newMinPx maxPx = newMaxPx rawOffsetStart = scaleToOffset( minPx, maxPx, activeRangeStart ) rawOffsetEnd = scaleToOffset( minPx, maxPx, activeRangeEnd ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomRangeSliderUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.horizontalDrag import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlin.math.abs @Stable internal fun Modifier.rangeSliderPressDragModifier( state: CustomRangeSliderState, startInteractionSource: MutableInteractionSource, endInteractionSource: MutableInteractionSource, enabled: Boolean ): Modifier = if (enabled) { pointerInput(startInteractionSource, endInteractionSource, state) { val rangeSliderLogic = RangeSliderLogic( state, startInteractionSource, endInteractionSource ) coroutineScope { awaitEachGesture { val event = awaitFirstDown(requireUnconsumed = false) val interaction = DragInteraction.Start() var posX = if (state.isRtl) state.totalWidth - event.position.x else event.position.x val compare = rangeSliderLogic.compareOffsets(posX) var draggingStart = if (compare != 0) { compare < 0 } else { state.rawOffsetStart > posX } awaitSlop(event.id, event.type)?.let { val slop = viewConfiguration.pointerSlop(event.type) val shouldUpdateCapturedThumb = abs(state.rawOffsetEnd - posX) < slop && abs(state.rawOffsetStart - posX) < slop if (shouldUpdateCapturedThumb) { val dir = it.second draggingStart = if (state.isRtl) dir >= 0f else dir < 0f posX += it.first.positionChange().x } } rangeSliderLogic.captureThumb( draggingStart, posX, interaction, this@coroutineScope ) val finishInteraction = try { val success = horizontalDrag(pointerId = event.id) { val deltaX = it.positionChange().x state.onDrag(draggingStart, if (state.isRtl) -deltaX else deltaX) } if (success) { DragInteraction.Stop(interaction) } else { DragInteraction.Cancel(interaction) } } catch (_: CancellationException) { DragInteraction.Cancel(interaction) } state.gestureEndAction(draggingStart) launch { rangeSliderLogic .activeInteraction(draggingStart) .emit(finishInteraction) } } } } } else { this } internal suspend fun AwaitPointerEventScope.awaitSlop( id: PointerId, type: PointerType ): Pair? { var initialDelta = 0f val postPointerSlop = { pointerInput: PointerInputChange, offset: Float -> pointerInput.consume() initialDelta = offset } val afterSlopResult = awaitHorizontalPointerSlopOrCancellation(id, type, postPointerSlop) return if (afterSlopResult != null) afterSlopResult to initialDelta else null } internal class RangeSliderLogic( val state: CustomRangeSliderState, val startInteractionSource: MutableInteractionSource, val endInteractionSource: MutableInteractionSource ) { fun activeInteraction(draggingStart: Boolean): MutableInteractionSource = if (draggingStart) startInteractionSource else endInteractionSource fun compareOffsets(eventX: Float): Int { val diffStart = abs(state.rawOffsetStart - eventX) val diffEnd = abs(state.rawOffsetEnd - eventX) return diffStart.compareTo(diffEnd) } fun captureThumb( draggingStart: Boolean, posX: Float, interaction: Interaction, scope: CoroutineScope ) { state.onDrag( draggingStart, posX - if (draggingStart) state.rawOffsetStart else state.rawOffsetEnd ) scope.launch { activeInteraction(draggingStart).emit(interaction) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSlider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("SameParameterValue") package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.annotation.IntRange import androidx.compose.foundation.focusable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.requiredSizeIn import androidx.compose.foundation.progressSemantics import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset import androidx.compose.ui.util.fastFirst import androidx.compose.ui.util.lerp import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt /** * Material Design slider. * * Sliders allow users to make selections from a range of values. * * It uses [CustomSliderDefaults.Thumb] and [CustomSliderDefaults.Track] as the thumb and track. * * Sliders reflect a range of values along a bar, from which users may select a single value. * They are ideal for adjusting settings such as volume, brightness, or applying image filters. * * ![Sliders image](https://developer.android.com/images/reference/androidx/compose/material3/sliders.png) * * Use continuous sliders to allow users to make meaningful selections that don’t * require a specific value: * * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values: * * @param value current value of the slider. If outside of [valueRange] provided, value will be * coerced to this range. * @param onValueChange callback in which value should be updated * @param modifier the [Modifier] to be applied to this slider * @param enabled controls the enabled state of this slider. When `false`, this component will not * respond to user input, and it will appear visually disabled and disabled to accessibility * services. * @param valueRange range of values that this slider can take. The passed [value] will be coerced * to this range. * @param steps if greater than 0, specifies the amount of discrete allowable values, evenly * distributed across the whole value range. If 0, the slider will behave continuously and allow any * value from the range specified. Must not be negative. * @param onValueChangeFinished called when value change has ended. This should not be used to * update the slider value (use [onValueChange] instead), but rather to know when the user has * completed selecting a new value by ending a drag or a click. * @param colors [CustomSliderColors] that will be used to resolve the colors used for this slider in * different states. See [CustomSliderDefaults.colors]. * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s * for this slider. You can create and pass in your own `remember`ed instance to observe * [Interaction]s and customize the appearance / behavior of this slider in different states. */ @Composable fun CustomSlider( value: Float, onValueChange: (Float) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, valueRange: ClosedFloatingPointRange = 0f..1f, @IntRange(from = 0) steps: Int = 0, onValueChangeFinished: (() -> Unit)? = null, colors: CustomSliderColors = CustomSliderDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { CustomSlider( value = value, onValueChange = onValueChange, modifier = modifier, enabled = enabled, onValueChangeFinished = onValueChangeFinished, colors = colors, interactionSource = interactionSource, steps = steps, thumb = { CustomSliderDefaults.Thumb( interactionSource = interactionSource, colors = colors, enabled = enabled ) }, track = { sliderState -> CustomSliderDefaults.Track( colors = colors, enabled = enabled, sliderState = sliderState ) }, valueRange = valueRange ) } /** * Material Design slider. * * Sliders allow users to make selections from a range of values. * * Sliders reflect a range of values along a bar, from which users may select a single value. * They are ideal for adjusting settings such as volume, brightness, or applying image filters. * * ![Sliders image](https://developer.android.com/images/reference/androidx/compose/material3/sliders.png) * * Use continuous sliders to allow users to make meaningful selections that don’t * require a specific value: * * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values: * * @param value current value of the slider. If outside of [valueRange] provided, value will be * coerced to this range. * @param onValueChange callback in which value should be updated * @param modifier the [Modifier] to be applied to this slider * @param enabled controls the enabled state of this slider. When `false`, this component will not * respond to user input, and it will appear visually disabled and disabled to accessibility * services. * @param onValueChangeFinished called when value change has ended. This should not be used to * update the slider value (use [onValueChange] instead), but rather to know when the user has * completed selecting a new value by ending a drag or a click. * @param colors [CustomSliderColors] that will be used to resolve the colors used for this slider in * different states. See [CustomSliderDefaults.colors]. * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s * for this slider. You can create and pass in your own `remember`ed instance to observe * [Interaction]s and customize the appearance / behavior of this slider in different states. * @param steps if greater than 0, specifies the amount of discrete allowable values, evenly * distributed across the whole value range. If 0, the slider will behave continuously and allow any * value from the range specified. Must not be negative. * @param thumb the thumb to be displayed on the slider, it is placed on top of the track. The * lambda receives a [CustomSliderState] which is used to obtain the current active track. * @param track the track to be displayed on the slider, it is placed underneath the thumb. The * lambda receives a [CustomSliderState] which is used to obtain the current active track. * @param valueRange range of values that this slider can take. The passed [value] will be coerced * to this range. */ @Composable fun CustomSlider( value: Float, onValueChange: (Float) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, onValueChangeFinished: (() -> Unit)? = null, colors: CustomSliderColors = CustomSliderDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, @IntRange(from = 0) steps: Int = 0, thumb: @Composable (CustomSliderState) -> Unit = { CustomSliderDefaults.Thumb( interactionSource = interactionSource, colors = colors, enabled = enabled ) }, track: @Composable (CustomSliderState) -> Unit = { sliderState -> CustomSliderDefaults.Track( colors = colors, enabled = enabled, sliderState = sliderState ) }, valueRange: ClosedFloatingPointRange = 0f..1f ) { val state = remember( steps, valueRange ) { CustomSliderState( value, steps, valueRange ) } state.onValueChangeFinished = onValueChangeFinished state.onValueChange = onValueChange state.value = value CustomSlider( state = state, modifier = modifier, enabled = enabled, interactionSource = interactionSource, thumb = thumb, track = track ) } /** * Material Design slider. * * Sliders allow users to make selections from a range of values. * * Sliders reflect a range of values along a bar, from which users may select a single value. * They are ideal for adjusting settings such as volume, brightness, or applying image filters. * * ![Sliders image](https://developer.android.com/images/reference/androidx/compose/material3/sliders.png) * * Use continuous sliders to allow users to make meaningful selections that don’t * require a specific value: * * * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values: * * * @param state [CustomSliderState] which contains the slider's current value. * @param modifier the [Modifier] to be applied to this slider * @param enabled controls the enabled state of this slider. When `false`, this component will not * respond to user input, and it will appear visually disabled and disabled to accessibility * services. * @param colors [CustomSliderColors] that will be used to resolve the colors used for this slider in * different states. See [CustomSliderDefaults.colors]. * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s * for this slider. You can create and pass in your own `remember`ed instance to observe * [Interaction]s and customize the appearance / behavior of this slider in different states. * @param thumb the thumb to be displayed on the slider, it is placed on top of the track. The * lambda receives a [CustomSliderState] which is used to obtain the current active track. * @param track the track to be displayed on the slider, it is placed underneath the thumb. The * lambda receives a [CustomSliderState] which is used to obtain the current active track. */ @Composable fun CustomSlider( state: CustomSliderState, modifier: Modifier = Modifier, enabled: Boolean = true, colors: CustomSliderColors = CustomSliderDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, thumb: @Composable (CustomSliderState) -> Unit = { CustomSliderDefaults.Thumb( interactionSource = interactionSource, colors = colors, enabled = enabled ) }, track: @Composable (CustomSliderState) -> Unit = { sliderState -> CustomSliderDefaults.Track( colors = colors, enabled = enabled, sliderState = sliderState ) } ) { require(state.steps >= 0) { "steps should be >= 0" } CustomSliderImpl( state = state, modifier = modifier, enabled = enabled, interactionSource = interactionSource, thumb = thumb, track = track ) } @Composable private fun CustomSliderImpl( modifier: Modifier, state: CustomSliderState, enabled: Boolean, interactionSource: MutableInteractionSource, thumb: @Composable (CustomSliderState) -> Unit, track: @Composable (CustomSliderState) -> Unit ) { state.isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl val press = Modifier.sliderTapModifier( state, interactionSource, enabled ) val drag = Modifier.draggable( orientation = Orientation.Horizontal, reverseDirection = state.isRtl, enabled = enabled, interactionSource = interactionSource, onDragStopped = { state.gestureEndAction() }, startDragImmediately = state.isDragging, state = state ) Layout( { Box(modifier = Modifier.layoutId(SliderComponents.THUMB)) { thumb(state) } Box(modifier = Modifier.layoutId(SliderComponents.TRACK)) { track(state) } }, modifier = modifier .minimumInteractiveComponentSize() .requiredSizeIn( minWidth = 20.dp, minHeight = 20.dp ) .sliderSemantics( state, enabled ) .focusable(enabled, interactionSource) .then(press) .then(drag) ) { measurables, constraints -> val thumbPlaceable = measurables.fastFirst { it.layoutId == SliderComponents.THUMB }.measure(constraints) val trackPlaceable = measurables.fastFirst { it.layoutId == SliderComponents.TRACK }.measure( constraints.offset( horizontal = -thumbPlaceable.width ).copy(minHeight = 0) ) val sliderWidth = thumbPlaceable.width + trackPlaceable.width val sliderHeight = max(trackPlaceable.height, thumbPlaceable.height) state.updateDimensions( thumbPlaceable.width.toFloat(), sliderWidth ) val trackOffsetX = thumbPlaceable.width / 2 val thumbOffsetX = ((trackPlaceable.width) * state.coercedValueAsFraction).roundToInt() val trackOffsetY = (sliderHeight - trackPlaceable.height) / 2 val thumbOffsetY = (sliderHeight - thumbPlaceable.height) / 2 layout(sliderWidth, sliderHeight) { trackPlaceable.placeRelative( trackOffsetX, trackOffsetY ) thumbPlaceable.placeRelative( thumbOffsetX, thumbOffsetY ) } } } private fun Modifier.sliderSemantics( state: CustomSliderState, enabled: Boolean ): Modifier { return semantics { if (!enabled) disabled() setProgress( action = { targetValue -> var newValue = targetValue.coerceIn( state.valueRange.start, state.valueRange.endInclusive ) val originalVal = newValue val resolvedValue = if (state.steps > 0) { var distance: Float = newValue for (i in 0..state.steps + 1) { val stepValue = lerp( state.valueRange.start, state.valueRange.endInclusive, i.toFloat() / (state.steps + 1) ) if (abs(stepValue - originalVal) <= distance) { distance = abs(stepValue - originalVal) newValue = stepValue } } newValue } else { newValue } // This is to keep it consistent with AbsSeekbar.java: return false if no // change from current. if (resolvedValue == state.value) { false } else { if (resolvedValue != state.value) { if (state.onValueChange != null) { state.onValueChange?.let { it(resolvedValue) } } else { state.value = resolvedValue } } state.onValueChangeFinished?.invoke() true } } ) }.progressSemantics( state.value, state.valueRange.start..state.valueRange.endInclusive, state.steps ) } @Stable private fun Modifier.sliderTapModifier( state: CustomSliderState, interactionSource: MutableInteractionSource, enabled: Boolean ) = if (enabled) { pointerInput(state, interactionSource) { detectTapGestures( onPress = { state.onPress(it) }, onTap = { state.dispatchRawDelta(0f) state.gestureEndAction() } ) } } else { this } private enum class SliderComponents { THUMB, TRACK } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderColors.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse @Immutable class CustomSliderColors( val thumbColor: Color, val activeTrackColor: Color, val activeTickColor: Color, val inactiveTrackColor: Color, val inactiveTickColor: Color, val disabledThumbColor: Color, val disabledActiveTrackColor: Color, val disabledActiveTickColor: Color, val disabledInactiveTrackColor: Color, val disabledInactiveTickColor: Color ) { /** * Returns a copy of this SelectableChipColors, optionally overriding some of the values. * This uses the Color.Unspecified to mean “use the value from the source” */ fun copy( thumbColor: Color = this.thumbColor, activeTrackColor: Color = this.activeTrackColor, activeTickColor: Color = this.activeTickColor, inactiveTrackColor: Color = this.inactiveTrackColor, inactiveTickColor: Color = this.inactiveTickColor, disabledThumbColor: Color = this.disabledThumbColor, disabledActiveTrackColor: Color = this.disabledActiveTrackColor, disabledActiveTickColor: Color = this.disabledActiveTickColor, disabledInactiveTrackColor: Color = this.disabledInactiveTrackColor, disabledInactiveTickColor: Color = this.disabledInactiveTickColor, ) = CustomSliderColors( thumbColor.takeOrElse { this.thumbColor }, activeTrackColor.takeOrElse { this.activeTrackColor }, activeTickColor.takeOrElse { this.activeTickColor }, inactiveTrackColor.takeOrElse { this.inactiveTrackColor }, inactiveTickColor.takeOrElse { this.inactiveTickColor }, disabledThumbColor.takeOrElse { this.disabledThumbColor }, disabledActiveTrackColor.takeOrElse { this.disabledActiveTrackColor }, disabledActiveTickColor.takeOrElse { this.disabledActiveTickColor }, disabledInactiveTrackColor.takeOrElse { this.disabledInactiveTrackColor }, disabledInactiveTickColor.takeOrElse { this.disabledInactiveTickColor }, ) @Stable internal fun thumbColor(enabled: Boolean): Color = if (enabled) thumbColor else disabledThumbColor @Stable internal fun trackColor( enabled: Boolean, active: Boolean ): Color = if (enabled) { if (active) activeTrackColor else inactiveTrackColor } else { if (active) disabledActiveTrackColor else disabledInactiveTrackColor } @Stable internal fun tickColor( enabled: Boolean, active: Boolean ): Color = if (enabled) { if (active) activeTickColor else inactiveTickColor } else { if (active) disabledActiveTickColor else disabledInactiveTickColor } override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || other !is CustomSliderColors) return false if (thumbColor != other.thumbColor) return false if (activeTrackColor != other.activeTrackColor) return false if (activeTickColor != other.activeTickColor) return false if (inactiveTrackColor != other.inactiveTrackColor) return false if (inactiveTickColor != other.inactiveTickColor) return false if (disabledThumbColor != other.disabledThumbColor) return false if (disabledActiveTrackColor != other.disabledActiveTrackColor) return false if (disabledActiveTickColor != other.disabledActiveTickColor) return false if (disabledInactiveTrackColor != other.disabledInactiveTrackColor) return false return disabledInactiveTickColor == other.disabledInactiveTickColor } override fun hashCode(): Int { var result = thumbColor.hashCode() result = 31 * result + activeTrackColor.hashCode() result = 31 * result + activeTickColor.hashCode() result = 31 * result + inactiveTrackColor.hashCode() result = 31 * result + inactiveTickColor.hashCode() result = 31 * result + disabledThumbColor.hashCode() result = 31 * result + disabledActiveTrackColor.hashCode() result = 31 * result + disabledActiveTickColor.hashCode() result = 31 * result + disabledInactiveTrackColor.hashCode() result = 31 * result + disabledInactiveTickColor.hashCode() return result } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderDefaults.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.hoverable import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.lerp import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberRipple import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.materialShadow /** * Object to hold defaults used by [CustomSlider] */ @Stable object CustomSliderDefaults { /** * Creates a [CustomSliderColors] that represents the different colors used in parts of the * [CustomSlider] in different states. */ @Composable fun colors() = colors( activeTickColor = MaterialTheme.colorScheme.inverseSurface, inactiveTickColor = MaterialTheme.colorScheme.surface, activeTrackColor = MaterialTheme.colorScheme.primaryContainer, inactiveTrackColor = SwitchDefaults.colors().disabledCheckedTrackColor, disabledThumbColor = SwitchDefaults.colors().disabledCheckedThumbColor, thumbColor = MaterialTheme.colorScheme.onPrimaryContainer ) /** * Creates a [CustomSliderColors] that represents the different colors used in parts of the * [CustomSlider] in different states. * * For the name references below the words "active" and "inactive" are used. Active part of * the slider is filled with progress, so if slider's progress is 30% out of 100%, left (or * right in RTL) 30% of the track will be active, while the rest is inactive. * * @param thumbColor thumb color when enabled * @param activeTrackColor color of the track in the part that is "active", meaning that the * thumb is ahead of it * @param activeTickColor colors to be used to draw tick marks on the active track, if `steps` * is specified * @param inactiveTrackColor color of the track in the part that is "inactive", meaning that the * thumb is before it * @param inactiveTickColor colors to be used to draw tick marks on the inactive track, if * `steps` are specified on the Slider is specified * @param disabledThumbColor thumb colors when disabled * @param disabledActiveTrackColor color of the track in the "active" part when the Slider is * disabled * @param disabledActiveTickColor colors to be used to draw tick marks on the active track * when Slider is disabled and when `steps` are specified on it * @param disabledInactiveTrackColor color of the track in the "inactive" part when the * Slider is disabled * @param disabledInactiveTickColor colors to be used to draw tick marks on the inactive part * of the track when Slider is disabled and when `steps` are specified on it */ @Composable fun colors( thumbColor: Color = Color.Unspecified, activeTrackColor: Color = Color.Unspecified, activeTickColor: Color = Color.Unspecified, inactiveTrackColor: Color = Color.Unspecified, inactiveTickColor: Color = Color.Unspecified, disabledThumbColor: Color = Color.Unspecified, disabledActiveTrackColor: Color = Color.Unspecified, disabledActiveTickColor: Color = Color.Unspecified, disabledInactiveTrackColor: Color = Color.Unspecified, disabledInactiveTickColor: Color = Color.Unspecified ): CustomSliderColors = CustomSliderColors( thumbColor = thumbColor, activeTrackColor = activeTrackColor, activeTickColor = activeTickColor, inactiveTrackColor = inactiveTrackColor, inactiveTickColor = inactiveTickColor, disabledThumbColor = disabledThumbColor, disabledActiveTrackColor = disabledActiveTrackColor, disabledActiveTickColor = disabledActiveTickColor, disabledInactiveTrackColor = disabledInactiveTrackColor, disabledInactiveTickColor = disabledInactiveTickColor ) /** * The Default thumb for [CustomSlider] and [CustomRangeSlider] * * @param interactionSource the [MutableInteractionSource] representing the stream of * [Interaction]s for this thumb. You can create and pass in your own `remember`ed * instance to observe * @param modifier the [Modifier] to be applied to the thumb. * @param colors [CustomSliderColors] that will be used to resolve the colors used for this thumb in * different states. See [CustomSliderDefaults.colors]. * @param enabled controls the enabled state of this slider. When `false`, this component will * not respond to user input, and it will appear visually disabled and disabled to * accessibility services. */ @Composable fun Thumb( interactionSource: MutableInteractionSource, modifier: Modifier = Modifier, colors: CustomSliderColors = colors(), enabled: Boolean = true, thumbSize: DpSize = ThumbSize ) { val interactions = remember { mutableStateListOf() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> interactions.add(interaction) is PressInteraction.Release -> interactions.remove(interaction.press) is PressInteraction.Cancel -> interactions.remove(interaction.press) is DragInteraction.Start -> interactions.add(interaction) is DragInteraction.Stop -> interactions.remove(interaction.start) is DragInteraction.Cancel -> interactions.remove(interaction.start) } } } val elevation = if (interactions.isNotEmpty()) { ThumbPressedElevation } else { ThumbDefaultElevation } val shape = ShapeDefaults.circle Spacer( modifier .size(thumbSize) .indication( interactionSource = interactionSource, indication = rememberRipple( bounded = false, radius = 40.dp / 2 ) ) .hoverable(interactionSource = interactionSource) .materialShadow( shape = shape, elevation = if (enabled) elevation else 0.dp, isClipped = false ) .background(colors.thumbColor(enabled), shape) ) } /** * The Default track for [CustomSlider] * * @param sliderState [CustomSliderState] which is used to obtain the current active track. * @param modifier the [Modifier] to be applied to the track. * @param colors [CustomSliderColors] that will be used to resolve the colors used for this track in * different states. See [CustomSliderDefaults.colors]. * @param enabled controls the enabled state of this slider. When `false`, this component will * not respond to user input, and it will appear visually disabled and disabled to * accessibility services. */ @Composable fun Track( sliderState: CustomSliderState, modifier: Modifier = Modifier, colors: CustomSliderColors = colors(), enabled: Boolean = true, trackHeight: Dp = 32.dp, strokeCap: StrokeCap = StrokeCap.Round ) { val inactiveTrackColor = colors.trackColor(enabled, active = false) val activeTrackColor = colors.trackColor(enabled, active = true) val inactiveTickColor = colors.tickColor(enabled, active = false) val activeTickColor = colors.tickColor(enabled, active = true) Canvas( modifier .fillMaxWidth() .height(trackHeight) ) { drawTrack( sliderState.tickFractions, 0f, sliderState.coercedValueAsFraction, inactiveTrackColor, activeTrackColor, inactiveTickColor, activeTickColor, trackHeight.toPx(), strokeCap ) } } /** * The Default track for [CustomRangeSlider] * * @param rangeSliderState [CustomRangeSliderState] which is used to obtain the current active track. * @param modifier the [Modifier] to be applied to the track. * @param colors [CustomSliderColors] that will be used to resolve the colors used for this track in * different states. See [CustomSliderDefaults.colors]. * @param enabled controls the enabled state of this slider. When `false`, this component will * not respond to user input, and it will appear visually disabled and disabled to * accessibility services. */ @Composable fun Track( rangeSliderState: CustomRangeSliderState, modifier: Modifier = Modifier, colors: CustomSliderColors = colors(), enabled: Boolean = true, trackHeight: Dp = 32.dp, strokeCap: StrokeCap = StrokeCap.Round ) { val inactiveTrackColor = colors.trackColor(enabled, active = false) val activeTrackColor = colors.trackColor(enabled, active = true) val inactiveTickColor = colors.tickColor(enabled, active = false) val activeTickColor = colors.tickColor(enabled, active = true) Canvas( modifier .fillMaxWidth() .height(trackHeight) ) { drawTrack( rangeSliderState.tickFractions, rangeSliderState.coercedActiveRangeStartAsFraction, rangeSliderState.coercedActiveRangeEndAsFraction, inactiveTrackColor, activeTrackColor, inactiveTickColor, activeTickColor, trackHeight.toPx(), strokeCap ) } } private fun DrawScope.drawTrack( tickFractions: FloatArray, activeRangeStart: Float, activeRangeEnd: Float, inactiveTrackColor: Color, activeTrackColor: Color, inactiveTickColor: Color, activeTickColor: Color, trackHeight: Float, strokeCap: StrokeCap ) { val isRtl = layoutDirection == LayoutDirection.Rtl val sliderLeft = Offset(0f, center.y) val sliderRight = Offset(size.width, center.y) val sliderStart = if (isRtl) sliderRight else sliderLeft val sliderEnd = if (isRtl) sliderLeft else sliderRight val tickSize = TickSize.toPx() drawLine( inactiveTrackColor, sliderStart, sliderEnd, trackHeight, strokeCap ) val sliderValueEnd = Offset( sliderStart.x + (sliderEnd.x - sliderStart.x) * activeRangeEnd, center.y ) val sliderValueStart = Offset( sliderStart.x + (sliderEnd.x - sliderStart.x) * activeRangeStart, center.y ) drawLine( activeTrackColor, sliderValueStart, sliderValueEnd, trackHeight, strokeCap ) for (tick in tickFractions) { val outsideFraction = tick > activeRangeEnd || tick < activeRangeStart drawCircle( color = if (outsideFraction) inactiveTickColor else activeTickColor, center = Offset(lerp(sliderStart, sliderEnd, tick).x, center.y), radius = tickSize / 2f ) } } private val ThumbWidth = 20.dp private val ThumbHeight = 20.dp private val ThumbSize = DpSize(ThumbWidth, ThumbHeight) private val ThumbDefaultElevation = 1.dp private val ThumbPressedElevation = 6.dp private val TickSize = 2.dp } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderRange.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.util.packFloats import androidx.compose.ui.util.unpackFloat1 import androidx.compose.ui.util.unpackFloat2 /** * Immutable float range for [CustomRangeSlider] * * Used in [CustomRangeSlider] to determine the active track range for the component. * The range is as follows: SliderRange.start..SliderRange.endInclusive. */ @Immutable @JvmInline internal value class CustomSliderRange( val packedValue: Long ) { /** * start of the [CustomSliderRange] */ @Stable val start: Float get() { // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified check(this.packedValue != Unspecified.packedValue) { "SliderRange is unspecified" } return unpackFloat1(packedValue) } /** * End (inclusive) of the [CustomSliderRange] */ @Stable val endInclusive: Float get() { // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified check(this.packedValue != Unspecified.packedValue) { "SliderRange is unspecified" } return unpackFloat2(packedValue) } companion object { /** * Represents an unspecified [CustomSliderRange] value, usually a replacement for `null` * when a primitive value is desired. */ @Stable val Unspecified = CustomSliderRange(Float.NaN, Float.NaN) } /** * String representation of the [CustomSliderRange] */ override fun toString() = if (isSpecified) { "$start..$endInclusive" } else { "FloatRange.Unspecified" } } /** * Creates a [CustomSliderRange] from a given start and endInclusive float. * It requires endInclusive to be >= start. * * @param start float that indicates the start of the range * @param endInclusive float that indicates the end of the range */ @Stable internal fun CustomSliderRange( start: Float, endInclusive: Float ): CustomSliderRange { val isUnspecified = start.isNaN() && endInclusive.isNaN() require(isUnspecified || start <= endInclusive) { "start($start) must be <= endInclusive($endInclusive)" } return CustomSliderRange(packFloats(start, endInclusive)) } /** * Creates a [CustomSliderRange] from a given [ClosedFloatingPointRange]. * It requires range.endInclusive >= range.start. * * @param range the ClosedFloatingPointRange for the range. */ @Stable internal fun CustomSliderRange(range: ClosedFloatingPointRange): CustomSliderRange { val start = range.start val endInclusive = range.endInclusive val isUnspecified = start.isNaN() && endInclusive.isNaN() require(isUnspecified || start <= endInclusive) { "ClosedFloatingPointRange.start($start) must be <= " + "ClosedFloatingPoint.endInclusive($endInclusive)" } return CustomSliderRange(packFloats(start, endInclusive)) } /** * Check for if a given [CustomSliderRange] is not [CustomSliderRange.Unspecified]. */ @Stable internal val CustomSliderRange.isSpecified: Boolean get() = packedValue != CustomSliderRange.Unspecified.packedValue ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("SameParameterValue") package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.annotation.IntRange import androidx.compose.foundation.MutatePriority import androidx.compose.foundation.MutatorMutex import androidx.compose.foundation.gestures.DragScope import androidx.compose.foundation.gestures.DraggableState import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import kotlinx.coroutines.coroutineScope import kotlin.math.max import kotlin.math.min /** * Class that holds information about [CustomSlider]'s active range. * * @param value [Float] that indicates the initial * position of the thumb. If outside of [valueRange] * provided, value will be coerced to this range. * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed * between across the whole value range. If 0, range slider will behave as a continuous slider and * allow to choose any value from the range specified. Must not be negative. * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback * shouldn't be used to update the range slider values (use [onValueChange] for that), * but rather to know when the user has completed selecting a new value by ending a drag or a click. * @param valueRange range of values that Slider values can take. [value] will be * coerced to this range. */ @Stable class CustomSliderState( value: Float = 0f, @IntRange(from = 0) val steps: Int = 0, val valueRange: ClosedFloatingPointRange = 0f..1f ) : DraggableState { private var valueState by mutableFloatStateOf(value) /** * [Float] that indicates the current value that the thumb * currently is in respect to the track. */ var value: Float set(newVal) { val coercedValue = newVal.coerceIn(valueRange.start, valueRange.endInclusive) val snappedValue = snapValueToTick( coercedValue, tickFractions, valueRange.start, valueRange.endInclusive ) valueState = snappedValue } get() = valueState override suspend fun drag( dragPriority: MutatePriority, block: suspend DragScope.() -> Unit ): Unit = coroutineScope { isDragging = true scrollMutex.mutateWith(dragScope, dragPriority, block) isDragging = false } override fun dispatchRawDelta(delta: Float) { val maxPx = max(totalWidth - thumbWidth / 2, 0f) val minPx = min(thumbWidth / 2, maxPx) rawOffset = (rawOffset + delta + pressOffset) pressOffset = 0f val offsetInTrack = snapValueToTick(rawOffset, tickFractions, minPx, maxPx) val scaledUserValue = scaleToUserValue(minPx, maxPx, offsetInTrack) if (scaledUserValue != this.value) { if (onValueChange != null) { onValueChange?.let { it(scaledUserValue) } } else { this.value = scaledUserValue } } } var onValueChangeFinished: (() -> Unit)? = null /** * callback in which value should be updated */ internal var onValueChange: ((Float) -> Unit)? = null internal val tickFractions = stepsToTickFractions(steps) private var totalWidth by mutableIntStateOf(0) internal var isRtl = false private var thumbWidth by mutableFloatStateOf(0f) internal val coercedValueAsFraction get() = calcFraction( valueRange.start, valueRange.endInclusive, value.coerceIn(valueRange.start, valueRange.endInclusive) ) internal var isDragging by mutableStateOf(false) private set internal fun updateDimensions( newThumbWidth: Float, newTotalWidth: Int ) { thumbWidth = newThumbWidth totalWidth = newTotalWidth } internal val gestureEndAction = { if (!isDragging) { // check isDragging in case the change is still in progress (touch -> drag case) onValueChangeFinished?.invoke() } } internal fun onPress(pos: Offset) { val to = if (isRtl) totalWidth - pos.x else pos.x pressOffset = to - rawOffset } private var rawOffset by mutableFloatStateOf(scaleToOffset(0f, 0f, value)) private var pressOffset by mutableFloatStateOf(0f) private val dragScope: DragScope = object : DragScope { override fun dragBy(pixels: Float): Unit = dispatchRawDelta(pixels) } private val scrollMutex = MutatorMutex() private fun scaleToUserValue( minPx: Float, maxPx: Float, offset: Float ) = scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive) private fun scaleToOffset( minPx: Float, maxPx: Float, userValue: Float ) = scale(valueRange.start, valueRange.endInclusive, userValue, minPx, maxPx) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/sliders/custom_slider/CustomSliderUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sliders.custom_slider import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.lerp import kotlin.math.abs import kotlin.math.sign // Calculate the 0..1 fraction that `pos` value represents between `a` and `b` internal fun calcFraction( a: Float, b: Float, pos: Float ) = (if (b - a == 0f) 0f else (pos - a) / (b - a)).coerceIn(0f, 1f) // Scale x1 from a1..b1 range to a2..b2 range internal fun scale( a1: Float, b1: Float, x1: Float, a2: Float, b2: Float ) = lerp(a2, b2, calcFraction(a1, b1, x1)) // Scale x.start, x.endInclusive from a1..b1 range to a2..b2 range internal fun scale( a1: Float, b1: Float, x: CustomSliderRange, a2: Float, b2: Float ) = CustomSliderRange( scale(a1 = a1, b1 = b1, x1 = x.start, a2 = a2, b2 = b2), scale(a1, b1, x.endInclusive, a2, b2) ) private val mouseSlop = 0.125.dp private val defaultTouchSlop = 18.dp // The default touch slop on Android devices private val mouseToTouchSlopRatio = mouseSlop / defaultTouchSlop internal fun ViewConfiguration.pointerSlop(pointerType: PointerType): Float { return when (pointerType) { PointerType.Mouse -> touchSlop * mouseToTouchSlopRatio else -> touchSlop } } // Copy-paste version of DragGestureDetector.kt. Please don't change this file without changing // DragGestureDetector.kt internal suspend fun AwaitPointerEventScope.awaitHorizontalPointerSlopOrCancellation( pointerId: PointerId, pointerType: PointerType, onPointerSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit ) = awaitPointerSlopOrCancellation( pointerId = pointerId, pointerType = pointerType, onPointerSlopReached = onPointerSlopReached, getDragDirectionValue = { it.x } ) /** * Waits for drag motion along one axis based on [getDragDirectionValue] to pass pointer slop, * using [pointerId] as the pointer to examine. If [pointerId] is raised, another pointer * from those that are down will be chosen to lead the gesture, and if none are down, * `null` is returned. If [pointerId] is not down when [awaitPointerSlopOrCancellation] is called, * then `null` is returned. * * When pointer slop is detected, [onPointerSlopReached] is called with the change and the distance * beyond the pointer slop. [getDragDirectionValue] should return the position change in the * direction of the drag axis. If [onPointerSlopReached] does not consume the position change, * pointer slop will not have been considered detected and the detection will continue or, * if it is consumed, the [PointerInputChange] that was consumed will be returned. * * This works with [awaitTouchSlopOrCancellation] for the other axis to ensure that only horizontal * or vertical dragging is done, but not both. * * @return The [PointerInputChange] of the event that was consumed in [onPointerSlopReached] or * `null` if all pointers are raised or the position change was consumed by another gesture * detector. */ private suspend inline fun AwaitPointerEventScope.awaitPointerSlopOrCancellation( pointerId: PointerId, pointerType: PointerType, onPointerSlopReached: (PointerInputChange, Float) -> Unit, getDragDirectionValue: (Offset) -> Float ): PointerInputChange? { if (currentEvent.isPointerUp(pointerId)) { return null // The pointer has already been lifted, so the gesture is canceled } val touchSlop = viewConfiguration.pointerSlop(pointerType) var pointer: PointerId = pointerId var totalPositionChange = 0f while (true) { val event = awaitPointerEvent() val dragEvent = event.changes.fastFirstOrNull { it.id == pointer }!! if (dragEvent.isConsumed) { return null } else if (dragEvent.changedToUpIgnoreConsumed()) { val otherDown = event.changes.fastFirstOrNull { it.pressed } if (otherDown == null) { // This is the last "up" return null } else { pointer = otherDown.id } } else { val currentPosition = dragEvent.position val previousPosition = dragEvent.previousPosition val positionChange = getDragDirectionValue(currentPosition) - getDragDirectionValue(previousPosition) totalPositionChange += positionChange val inDirection = abs(totalPositionChange) if (inDirection < touchSlop) { // verify that nothing else consumed the drag event awaitPointerEvent(PointerEventPass.Final) if (dragEvent.isConsumed) { return null } } else { onPointerSlopReached( dragEvent, totalPositionChange - (sign(totalPositionChange) * touchSlop) ) if (dragEvent.isConsumed) { return dragEvent } else { totalPositionChange = 0f } } } } } private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean = changes.fastFirstOrNull { it.id == pointerId }?.pressed != true internal fun snapValueToTick( current: Float, tickFractions: FloatArray, minPx: Float, maxPx: Float ): Float { // target is a closest anchor to the `current`, if exists return tickFractions .minByOrNull { abs(lerp(minPx, maxPx, it) - current) } ?.run { lerp(minPx, maxPx, this) } ?: current } internal fun stepsToTickFractions(steps: Int): FloatArray { return if (steps == 0) floatArrayOf() else FloatArray(steps + 2) { it.toFloat() / (steps + 1) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/CupertinoSwitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("KDocUnresolvedReference") package com.t8rin.imagetoolbox.core.ui.widget.switches import android.os.Build import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.kyant.backdrop.backdrops.rememberCanvasBackdrop import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container /** * Cupertino Design Switch. * * Switches toggle the state of a single item on or off. * * @param checked whether or not this switch is checked * @param onCheckedChange called when this switch is clicked. If `null`, then this switch will not * be intractable, unless something else handles its input events and updates its state. * @param modifier the [Modifier] to be applied to this switch * @param enabled controls the enabled state of this switch. When `false`, this component will not * respond to user input, and it will appear visually disabled and disabled to accessibility * services. * @param colors [CupertinoSwitchColors] that will be used to resolve the colors used for this switch in * different states. See [CupertinoSwitchDefaults.colors]. * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s * for this switch. You can create and pass in your own `remember`ed instance to observe * [Interaction]s and customize the appearance / behavior of this switch in different states. */ @Composable fun CupertinoSwitch( checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, colors: CupertinoSwitchColors = CupertinoSwitchDefaults.colors(), enabled: Boolean = true, interactionSource: MutableInteractionSource? = null ) { val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } val isPressed by realInteractionSource.collectIsPressedAsState() val isDragged by realInteractionSource.collectIsDraggedAsState() val animatedAspectRatio by animateFloatAsState( targetValue = if (isPressed || isDragged) 1.25f else 1f, animationSpec = AspectRationAnimationSpec ) val animatedBackground by animateColorAsState( targetValue = colors.trackColor(enabled, checked).value, animationSpec = ColorAnimationSpec ) var alignment by remember(checked) { mutableFloatStateOf( if (checked) 1f else -1f ) } val state = rememberDraggableState { alignment = (alignment + it).coerceIn(-1f, 1f) } val animatedAlignment by animateFloatAsState( targetValue = alignment, animationSpec = AlignmentAnimationSpec ) Column( modifier .toggleable( value = checked, onValueChange = { onCheckedChange?.invoke(it) }, enabled = enabled, role = Role.Switch, interactionSource = realInteractionSource, indication = null ) .draggable( state = state, orientation = Orientation.Horizontal, interactionSource = realInteractionSource, enabled = enabled, onDragStopped = { if (alignment < 1 / 2f) { alignment = -1f if (checked) onCheckedChange?.invoke(false) } else { alignment = 1f if (!checked) onCheckedChange?.invoke(true) } } ) .wrapContentSize(Alignment.Center) .requiredSize(CupertinoSwitchDefaults.Width, CupertinoSwitchDefaults.height) .clip(CupertinoSwitchDefaults.Shape) .background(animatedBackground) .padding(2.dp), ) { Box( Modifier .fillMaxHeight() .aspectRatio(animatedAspectRatio) .align(BiasAlignment.Horizontal(animatedAlignment)) .container( shape = CupertinoSwitchDefaults.Shape, resultPadding = 0.dp, autoShadowElevation = animateDpAsState( if (enabled && LocalSettingsState.current.drawSwitchShadows) { CupertinoSwitchDefaults.EnabledThumbElevation } else 0.dp ).value, borderColor = Color.Transparent, isShadowClip = true, isStandaloneContainer = false, color = colors.thumbColor(enabled).value ) ) } } @Composable fun LiquidGlassSwitch( checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, internalModifier: Modifier = Modifier, colors: CupertinoSwitchColors = CupertinoSwitchDefaults.colors(), enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, backgroundColor: Color ) { val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Box( contentAlignment = Alignment.Center ) { FallbackLiquidGlassSwitch( checked = checked, enabled = false, onCheckedChange = null, modifier = internalModifier, colors = CupertinoSwitchDefaults.transparentColors(), interactionSource = realInteractionSource ) LiquidToggle( checked = { checked }, onCheckedChange = onCheckedChange, backdrop = rememberCanvasBackdrop { drawRect(backgroundColor) }, enabled = enabled, colors = colors, interactionSource = realInteractionSource, modifier = modifier, ) } } else { FallbackLiquidGlassSwitch( checked = checked, onCheckedChange = onCheckedChange, modifier = internalModifier, colors = colors, enabled = enabled, interactionSource = realInteractionSource ) } } @Composable private fun FallbackLiquidGlassSwitch( checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, colors: CupertinoSwitchColors = CupertinoSwitchDefaults.colors(), enabled: Boolean = true, interactionSource: MutableInteractionSource ) { val isPressed by interactionSource.collectIsPressedAsState() val isDragged by interactionSource.collectIsDraggedAsState() val animatedAspectRatio by animateFloatAsState( targetValue = if (isPressed || isDragged) 1.8f else 1.6f, animationSpec = AspectRationAnimationSpec ) val animatedBackground by animateColorAsState( targetValue = colors.trackColor(enabled, checked).value, animationSpec = ColorAnimationSpec ) var alignment by remember(checked) { mutableFloatStateOf( if (checked) 1f else -1f ) } val state = rememberDraggableState { alignment = (alignment + it).coerceIn(-1f, 1f) } val animatedAlignment by animateFloatAsState( targetValue = alignment, animationSpec = AlignmentAnimationSpec ) Column( modifier .toggleable( value = checked, onValueChange = { onCheckedChange?.invoke(it) }, enabled = enabled, role = Role.Switch, interactionSource = interactionSource, indication = null ) .draggable( state = state, orientation = Orientation.Horizontal, interactionSource = interactionSource, enabled = enabled, onDragStopped = { if (alignment < 1 / 2f) { alignment = -1f if (checked) onCheckedChange?.invoke(false) } else { alignment = 1f if (!checked) onCheckedChange?.invoke(true) } } ) .wrapContentSize(Alignment.Center) .requiredSize( width = CupertinoSwitchDefaults.LiquidWidth, height = CupertinoSwitchDefaults.LiquidHeight ) .clip(CupertinoSwitchDefaults.Shape) .background(animatedBackground) .padding(2.dp), ) { Box( Modifier .fillMaxHeight() .aspectRatio(animatedAspectRatio) .align(BiasAlignment.Horizontal(animatedAlignment)) .container( shape = CupertinoSwitchDefaults.Shape, resultPadding = 0.dp, autoShadowElevation = animateDpAsState( if (enabled && LocalSettingsState.current.drawSwitchShadows) { CupertinoSwitchDefaults.EnabledThumbElevation } else 0.dp ).value, borderColor = Color.Transparent, isShadowClip = true, isStandaloneContainer = false, color = colors.thumbColor(enabled).value ) ) } } /** * Represents the colors used by a [CupertinoSwitch] in different states * * See [CupertinoSwitchDefaults.colors] for the default implementation that follows Material * specifications. */ @Immutable class CupertinoSwitchColors internal constructor( private val thumbColor: Color, private val disabledThumbColor: Color, private val checkedTrackColor: Color, private val checkedIconColor: Color, private val uncheckedTrackColor: Color, private val uncheckedIconColor: Color, private val disabledCheckedTrackColor: Color, private val disabledCheckedIconColor: Color, private val disabledUncheckedTrackColor: Color, private val disabledUncheckedIconColor: Color ) { /** * Represents the color used for the switch's thumb, depending on [enabled] and [checked]. * * @param enabled whether the Switch is enabled or not */ @Composable internal fun thumbColor(enabled: Boolean): State { return animateColorAsState( targetValue = if (enabled) { thumbColor } else { disabledThumbColor }, animationSpec = ColorAnimationSpec ) } /** * Represents the color used for the switch's track, depending on [enabled] and [checked]. * * @param enabled whether the Switch is enabled or not * @param checked whether the Switch is checked or not */ @Composable internal fun trackColor( enabled: Boolean, checked: Boolean ): State { return animateColorAsState( targetValue = if (enabled) { if (checked) checkedTrackColor else uncheckedTrackColor } else { if (checked) disabledCheckedTrackColor else disabledUncheckedTrackColor }, animationSpec = ColorAnimationSpec ) } /** * Represents the content color passed to the icon if used * * @param enabled whether the Switch is enabled or not * @param checked whether the Switch is checked or not */ @Composable internal fun iconColor( enabled: Boolean, checked: Boolean ): State { return animateColorAsState( targetValue = if (enabled) { if (checked) checkedIconColor else uncheckedIconColor } else { if (checked) disabledCheckedIconColor else disabledUncheckedIconColor }, animationSpec = ColorAnimationSpec ) } override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || other !is CupertinoSwitchColors) return false if (thumbColor != other.thumbColor) return false if (checkedTrackColor != other.checkedTrackColor) return false if (checkedIconColor != other.checkedIconColor) return false if (uncheckedTrackColor != other.uncheckedTrackColor) return false if (uncheckedIconColor != other.uncheckedIconColor) return false if (disabledThumbColor != other.disabledThumbColor) return false if (disabledCheckedTrackColor != other.disabledCheckedTrackColor) return false if (disabledCheckedIconColor != other.disabledCheckedIconColor) return false if (disabledUncheckedTrackColor != other.disabledUncheckedTrackColor) return false if (disabledUncheckedIconColor != other.disabledUncheckedIconColor) return false return true } override fun hashCode(): Int { var result = thumbColor.hashCode() result = 31 * result + checkedTrackColor.hashCode() result = 31 * result + checkedIconColor.hashCode() result = 31 * result + uncheckedTrackColor.hashCode() result = 31 * result + uncheckedIconColor.hashCode() result = 31 * result + disabledThumbColor.hashCode() result = 31 * result + disabledCheckedTrackColor.hashCode() result = 31 * result + disabledCheckedIconColor.hashCode() result = 31 * result + disabledUncheckedTrackColor.hashCode() result = 31 * result + disabledUncheckedIconColor.hashCode() return result } } @Immutable object CupertinoSwitchDefaults { internal val EnabledThumbElevation = 4.dp val Width: Dp = 51.dp val height: Dp = 31.dp val LiquidWidth: Dp = 64.dp val LiquidHeight: Dp = 28.dp internal val Shape: CornerBasedShape @Composable get() = ShapeDefaults.circle @Composable @ReadOnlyComposable fun colors( thumbColor: Color = if (LocalSettingsState.current.isNightMode) { MaterialTheme.colorScheme.onSurface } else MaterialTheme.colorScheme.surfaceContainerLowest, disabledThumbColor: Color = thumbColor, checkedTrackColor: Color = if (LocalSettingsState.current.isNightMode) { MaterialTheme.colorScheme.primary.blend(Color.Black, 0.5f) } else MaterialTheme.colorScheme.primary, checkedIconColor: Color = MaterialTheme.colorScheme.outlineVariant, uncheckedTrackColor: Color = MaterialTheme.colorScheme.outline.copy( alpha = .33f ), uncheckedIconColor: Color = checkedIconColor, disabledCheckedTrackColor: Color = checkedTrackColor.copy(alpha = .33f), disabledCheckedIconColor: Color = checkedIconColor, disabledUncheckedTrackColor: Color = uncheckedTrackColor, disabledUncheckedIconColor: Color = checkedIconColor, ): CupertinoSwitchColors = CupertinoSwitchColors( thumbColor = thumbColor, disabledThumbColor = disabledThumbColor, checkedTrackColor = checkedTrackColor, checkedIconColor = checkedIconColor, uncheckedTrackColor = uncheckedTrackColor, uncheckedIconColor = uncheckedIconColor, disabledCheckedTrackColor = disabledCheckedTrackColor, disabledCheckedIconColor = disabledCheckedIconColor, disabledUncheckedTrackColor = disabledUncheckedTrackColor, disabledUncheckedIconColor = disabledUncheckedIconColor ) @Composable @ReadOnlyComposable fun transparentColors() = colors( thumbColor = Color.Transparent, disabledThumbColor = Color.Transparent, checkedTrackColor = Color.Transparent, checkedIconColor = Color.Transparent, uncheckedTrackColor = Color.Transparent, uncheckedIconColor = Color.Transparent, disabledCheckedTrackColor = Color.Transparent, disabledCheckedIconColor = Color.Transparent, disabledUncheckedTrackColor = Color.Transparent, disabledUncheckedIconColor = Color.Transparent ) } private val AspectRationAnimationSpec = tween(durationMillis = 300) private val ColorAnimationSpec = tween(durationMillis = 300) private val AlignmentAnimationSpec = AspectRationAnimationSpec ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/FluentSwitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.switches import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.toggleable import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.animation.FastInvokeEasing import com.t8rin.imagetoolbox.core.ui.utils.animation.PointToPointEasing import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberRipple import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun FluentSwitch( checked: Boolean, modifier: Modifier = Modifier, onCheckedChange: ((Boolean) -> Unit)?, enabled: Boolean = true, colors: SwitchColors = SwitchDefaults.colors(), interactionSource: MutableInteractionSource? = null ) { val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } val pressed by realInteractionSource.collectIsPressedAsState() val dragged by realInteractionSource.collectIsDraggedAsState() val height by animateDpAsState( when { pressed || dragged -> 14.dp else -> 12.dp }, tween(Duration, easing = FastInvokeEasing) ) val width by animateDpAsState( when { pressed || dragged -> 17.dp else -> 12.dp }, tween(Duration, easing = FastInvokeEasing) ) val maxValue = 32.dp - (width / 2) val minValue = 2.dp val density = LocalDensity.current var offsetAnimated by remember(checked) { mutableFloatStateOf( with(density) { if (checked) { maxValue } else { minValue }.toPx() } ) } val state = rememberDraggableState { offsetAnimated = with(density) { (offsetAnimated + it).coerceIn(minValue.toPx(), maxValue.toPx()) } } val offset by animateFloatAsState( targetValue = offsetAnimated, animationSpec = tween(Duration, easing = PointToPointEasing) ) Row( modifier = modifier .toggleable( value = checked, indication = null, interactionSource = realInteractionSource, role = Role.Switch, onValueChange = { onCheckedChange?.invoke(it) }, enabled = enabled ) .draggable( state = state, orientation = Orientation.Horizontal, interactionSource = realInteractionSource, enabled = enabled, onDragStopped = { with(density) { if (it < (maxValue.toPx() - minValue.toPx()) / 2f) { offsetAnimated = minValue.toPx() if (checked) onCheckedChange?.invoke(false) } else { offsetAnimated = maxValue.toPx() if (!checked) onCheckedChange?.invoke(true) } } } ), verticalAlignment = Alignment.CenterVertically ) { val trackColor by animateColorAsState( targetValue = trackColor(enabled, checked, colors), animationSpec = tween(Duration, easing = PointToPointEasing) ) val thumbColor by animateColorAsState( targetValue = thumbColor(enabled, checked, colors), animationSpec = tween(Duration, easing = PointToPointEasing) ) val borderColor by animateColorAsState( targetValue = borderColor(enabled, checked, colors), animationSpec = tween(Duration, easing = PointToPointEasing) ) Box( modifier = Modifier .size(48.dp, 24.dp) .border( width = 1.dp, color = borderColor, shape = ShapeDefaults.circle ) .clip(ShapeDefaults.circle) .background(trackColor) .padding(horizontal = 4.dp), contentAlignment = Alignment.CenterStart ) { Box( modifier = Modifier .size(width, height) .graphicsLayer { translationX = offset transformOrigin = TransformOrigin.Center } .indication( interactionSource = realInteractionSource, indication = rememberRipple( bounded = false, radius = 16.dp ) ) .clip(ShapeDefaults.circle) .background(thumbColor) ) } } } private const val Duration = 600 @Stable private fun trackColor( enabled: Boolean, checked: Boolean, colors: SwitchColors ): Color = if (enabled) { if (checked) colors.checkedTrackColor else colors.uncheckedTrackColor } else { if (checked) colors.disabledCheckedTrackColor else colors.disabledUncheckedTrackColor } @Stable private fun thumbColor( enabled: Boolean, checked: Boolean, colors: SwitchColors ): Color = if (enabled) { if (checked) colors.checkedThumbColor else colors.uncheckedThumbColor } else { if (checked) colors.disabledCheckedThumbColor else colors.disabledUncheckedThumbColor } @Stable private fun borderColor( enabled: Boolean, checked: Boolean, colors: SwitchColors ): Color = if (enabled) { if (checked) colors.checkedBorderColor else colors.uncheckedBorderColor } else { if (checked) colors.disabledCheckedBorderColor else colors.disabledUncheckedBorderColor } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/HyperOSSwitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.switches import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.foundation.hoverable import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.toggleable import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.animation.PointToPointEasing import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.math.absoluteValue import androidx.compose.material3.SwitchColors as M3SwitchColors @Composable fun HyperOSSwitch( checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, colors: M3SwitchColors = SwitchDefaults.colors(), interactionSource: MutableInteractionSource? = null, enabled: Boolean = true ) { val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val colors = com.t8rin.imagetoolbox.core.ui.widget.switches.SwitchDefaults.switchColors(colors) val isPressed by interactionSource.collectIsPressedAsState() val isDragged by interactionSource.collectIsDraggedAsState() val isHovered by interactionSource.collectIsHoveredAsState() val springSpec = remember { spring( dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessMedium ) } var dragOffset by remember { mutableFloatStateOf(0f) } val thumbOffset by animateDpAsState( targetValue = if (checked) { if (!enabled) 26.dp else if (isPressed || isDragged || isHovered) 24.dp else 26.dp } else { if (!enabled) 4.dp else if (isPressed || isDragged || isHovered) 3.dp else 4.dp } + dragOffset.dp, animationSpec = tween(600, easing = PointToPointEasing) ) val thumbSize by animateDpAsState( targetValue = if (!enabled) 20.dp else if (isPressed || isDragged || isHovered) 23.dp else 20.dp, animationSpec = springSpec ) val thumbColor by animateColorAsState( if (checked) colors.checkedThumbColor(enabled) else colors.uncheckedThumbColor(enabled) ) val backgroundColor by animateColorAsState( if (checked) colors.checkedTrackColor(enabled) else colors.uncheckedTrackColor(enabled), animationSpec = tween(durationMillis = 200) ) val toggleableModifier = if (onCheckedChange != null) { Modifier.toggleable( value = checked, onValueChange = onCheckedChange, enabled = enabled, role = Role.Switch, interactionSource = interactionSource, indication = null ) } else { Modifier } val shape = AutoCircleShape() val density = LocalDensity.current Box( modifier = modifier .wrapContentSize(Alignment.Center) .size(50.dp, 28.5.dp) .requiredSize(50.dp, 28.5.dp) .clip(shape) .drawBehind { drawRect(backgroundColor) } .hoverable( interactionSource = interactionSource, enabled = enabled ) .then(toggleableModifier) ) { val scope = rememberCoroutineScope() Box( modifier = Modifier .align(Alignment.CenterStart) .offset(x = thumbOffset) .size(thumbSize) .drawBehind { drawOutline( outline = shape.createOutline( size = size, layoutDirection = layoutDirection, density = density ), color = thumbColor ) } .pointerInput(checked, enabled) { if (!enabled) return@pointerInput awaitEachGesture { val pressInteraction: PressInteraction.Press val down = awaitFirstDown().also { pressInteraction = PressInteraction.Press(it.position) interactionSource.tryEmit(pressInteraction) } waitForUpOrCancellation().also { interactionSource.tryEmit(PressInteraction.Cancel(pressInteraction)) } awaitVerticalTouchSlopOrCancellation(down.id) { _, _ -> interactionSource.tryEmit(PressInteraction.Cancel(pressInteraction)) } } } .pointerInput(checked, enabled, onCheckedChange) { if (!enabled) return@pointerInput val dragInteraction: DragInteraction.Start = DragInteraction.Start() detectHorizontalDragGestures( onDragStart = { interactionSource.tryEmit(dragInteraction) }, onDragEnd = { if (dragOffset.absoluteValue > 21f / 2) onCheckedChange?.invoke(!checked) interactionSource.tryEmit(DragInteraction.Stop(dragInteraction)) scope.launch { delay(50) dragOffset = 0f } }, onDragCancel = { interactionSource.tryEmit(DragInteraction.Cancel(dragInteraction)) dragOffset = 0f } ) { _, dragAmount -> dragOffset = (dragOffset + dragAmount / 2).let { if (checked) it.coerceIn(-21f, 0f) else it.coerceIn(0f, 21f) } } } ) } } private object SwitchDefaults { @Composable fun switchColors( switchColors: M3SwitchColors ): SwitchColors = SwitchColors( checkedThumbColor = switchColors.checkedThumbColor, uncheckedThumbColor = switchColors.uncheckedThumbColor, disabledCheckedThumbColor = switchColors.disabledCheckedThumbColor, disabledUncheckedThumbColor = switchColors.disabledUncheckedThumbColor, checkedTrackColor = switchColors.checkedTrackColor, uncheckedTrackColor = switchColors.uncheckedTrackColor, disabledCheckedTrackColor = switchColors.disabledCheckedTrackColor, disabledUncheckedTrackColor = switchColors.disabledUncheckedTrackColor ) } @Immutable private class SwitchColors( private val checkedThumbColor: Color, private val uncheckedThumbColor: Color, private val disabledCheckedThumbColor: Color, private val disabledUncheckedThumbColor: Color, private val checkedTrackColor: Color, private val uncheckedTrackColor: Color, private val disabledCheckedTrackColor: Color, private val disabledUncheckedTrackColor: Color ) { @Stable fun checkedThumbColor(enabled: Boolean): Color = if (enabled) checkedThumbColor else disabledCheckedThumbColor @Stable fun uncheckedThumbColor(enabled: Boolean): Color = if (enabled) uncheckedThumbColor else disabledUncheckedThumbColor @Stable fun checkedTrackColor(enabled: Boolean): Color = if (enabled) checkedTrackColor else disabledCheckedTrackColor @Stable fun uncheckedTrackColor(enabled: Boolean): Color = if (enabled) uncheckedTrackColor else disabledUncheckedTrackColor } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/LiquidToggle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.switches import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.spring import androidx.compose.foundation.MutatorMutex import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.lerp import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.lerp import com.kyant.backdrop.Backdrop import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberBackdrop import com.kyant.backdrop.backdrops.rememberCombinedBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.highlight.Highlight import com.kyant.backdrop.shadow.InnerShadow import com.kyant.backdrop.shadow.Shadow import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.android.awaitFrame import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlin.math.abs @Composable internal fun LiquidToggle( checked: () -> Boolean, onCheckedChange: ((Boolean) -> Unit)?, backdrop: Backdrop, colors: CupertinoSwitchColors = CupertinoSwitchDefaults.colors(), enabled: Boolean = true, interactionSource: MutableInteractionSource, modifier: Modifier = Modifier ) { val interactions by interactionSource.interactions.collectAsState(initial = null) val accentColor by colors.trackColor( enabled = enabled, checked = true ) val trackColor by colors.trackColor( enabled = enabled, checked = false ) val thumbColor by colors.thumbColor(enabled) val internalChange = remember { mutableStateOf(false) } val density = LocalDensity.current val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr val dragWidth = with(density) { 20f.dp.toPx() } val animationScope = rememberCoroutineScope() var didDrag by remember { mutableStateOf(false) } var fraction by remember { mutableFloatStateOf(if (checked()) 1f else 0f) } var start by remember { mutableStateOf(null) } val dampedDragAnimation = remember(animationScope, enabled, onCheckedChange) { DampedDragAnimation( enabled = enabled, animationScope = animationScope, initialValue = fraction, valueRange = 0f..1f, visibilityThreshold = 0.001f, initialScale = 1f, pressedScale = 1.5f, onDragStarted = { interactionSource.tryEmit(DragInteraction.Start().also { start = it }) }, onDragStopped = { isCancel -> if (isCancel) { start?.let { interactionSource.tryEmit(DragInteraction.Cancel(it)) } fraction = if (checked()) 1f else 0f } else { start?.let { interactionSource.tryEmit(DragInteraction.Stop(it)) } if (didDrag) { internalChange.value = true fraction = if (fraction >= 0.5f) 1f else 0f didDrag = false } else { internalChange.value = false fraction = if (checked()) 0f else 1f } } }, onDrag = { _, dragAmount -> if (!didDrag) { didDrag = dragAmount.x != 0f } val delta = dragAmount.x / dragWidth internalChange.value = false fraction = if (isLtr) (fraction + delta).fastCoerceIn(0f, 1f) else (fraction - delta).fastCoerceIn(0f, 1f) } ) } LaunchedEffect(dampedDragAnimation) { snapshotFlow { fraction } .collectLatest { newFraction -> if (internalChange.value) { onCheckedChange?.invoke(newFraction == 1f) } dampedDragAnimation.updateValue(newFraction) } } LaunchedEffect(checked()) { val target = if (checked()) 1f else 0f if (target != fraction) { internalChange.value = false fraction = target dampedDragAnimation.animateToValue(target) } } LaunchedEffect(dampedDragAnimation.value, onCheckedChange, checked(), interactions) { delay(500) val new = dampedDragAnimation.value >= 0.5f if (new != checked() && dampedDragAnimation.pressProgress <= 0f && interactions == null) { onCheckedChange?.invoke(new) } } val trackBackdrop = rememberLayerBackdrop() val shape = AutoCircleShape() Box( modifier = modifier, contentAlignment = Alignment.CenterStart ) { Box( Modifier .layerBackdrop(trackBackdrop) .clip(shape) .drawBehind { val fraction = dampedDragAnimation.value drawRect(lerp(trackColor, accentColor, fraction)) } .size(64f.dp, 28f.dp) ) key(shape) { Box( Modifier .graphicsLayer { val fraction = dampedDragAnimation.value val padding = 2f.dp.toPx() translationX = if (isLtr) lerp(padding, padding + dragWidth, fraction) else lerp(-padding, -(padding + dragWidth), fraction) } .semantics { role = Role.Switch } .then(dampedDragAnimation.modifier) .drawBackdrop( backdrop = rememberCombinedBackdrop( backdrop, rememberBackdrop(trackBackdrop) { drawBackdrop -> val progress = dampedDragAnimation.pressProgress val scaleX = lerp(2f / 3f, 0.75f, progress) val scaleY = lerp(0f, 0.75f, progress) scale(scaleX, scaleY) { drawBackdrop() } } ), shape = { shape }, effects = { val progress = dampedDragAnimation.pressProgress blur(8f.dp.toPx() * (1f - progress)) lens( 5f.dp.toPx() * progress, 10f.dp.toPx() * progress, chromaticAberration = true ) }, highlight = { val progress = dampedDragAnimation.pressProgress Highlight.Ambient.copy( width = Highlight.Ambient.width / 1.5f, blurRadius = Highlight.Ambient.blurRadius / 1.5f, alpha = progress ) }, shadow = { Shadow( radius = 4f.dp, color = Color.Black.copy(alpha = 0.05f) ) }, innerShadow = { val progress = dampedDragAnimation.pressProgress InnerShadow( radius = 4f.dp * progress, alpha = progress ) }, layerBlock = { scaleX = dampedDragAnimation.scaleX scaleY = dampedDragAnimation.scaleY val velocity = dampedDragAnimation.velocity / 50f scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) }, onDrawSurface = { val progress = dampedDragAnimation.pressProgress drawRect(thumbColor.copy(alpha = 1f - progress)) } ) .size(40f.dp, 24f.dp) ) } } } private class DampedDragAnimation( enabled: Boolean, private val animationScope: CoroutineScope, val initialValue: Float, val valueRange: ClosedRange, val visibilityThreshold: Float, val initialScale: Float, val pressedScale: Float, val onDragStarted: DampedDragAnimation.(position: Offset) -> Unit, val onDragStopped: DampedDragAnimation.(isCancel: Boolean) -> Unit, val onDrag: DampedDragAnimation.(size: IntSize, dragAmount: Offset) -> Unit, ) { private val valueAnimationSpec = spring(1f, 1000f, visibilityThreshold) private val velocityAnimationSpec = spring(0.5f, 300f, visibilityThreshold * 10f) private val pressProgressAnimationSpec = spring(1f, 1000f, 0.001f) private val scaleXAnimationSpec = spring(0.6f, 250f, 0.001f) private val scaleYAnimationSpec = spring(0.7f, 250f, 0.001f) private val valueAnimation = Animatable(initialValue, visibilityThreshold) private val velocityAnimation = Animatable(0f, 5f) private val pressProgressAnimation = Animatable(0f, 0.001f) private val scaleXAnimation = Animatable(initialScale, 0.001f) private val scaleYAnimation = Animatable(initialScale, 0.001f) private val mutatorMutex = MutatorMutex() private val velocityTracker = VelocityTracker() val value: Float get() = valueAnimation.value // val progress: Float get() = (value - valueRange.start) / (valueRange.endInclusive - valueRange.start) val targetValue: Float get() = valueAnimation.targetValue val pressProgress: Float get() = pressProgressAnimation.value val scaleX: Float get() = scaleXAnimation.value val scaleY: Float get() = scaleYAnimation.value val velocity: Float get() = velocityAnimation.value val modifier: Modifier = if (enabled) { Modifier.pointerInput(Unit) { inspectDragGestures( onDragStart = { down -> onDragStarted(down.position) press() }, onDragEnd = { onDragStopped(false) release() }, onDragCancel = { onDragStopped(true) release() } ) { _, dragAmount -> onDrag(size, dragAmount) } } } else Modifier fun press() { velocityTracker.resetTracking() animationScope.launch { launch { pressProgressAnimation.animateTo(1f, pressProgressAnimationSpec) } launch { scaleXAnimation.animateTo(pressedScale, scaleXAnimationSpec) } launch { scaleYAnimation.animateTo(pressedScale, scaleYAnimationSpec) } } } fun release() { animationScope.launch { awaitFrame() if (value != targetValue) { val threshold = (valueRange.endInclusive - valueRange.start) * 0.025f snapshotFlow { valueAnimation.value } .filter { abs(it - valueAnimation.targetValue) < threshold } .first() } launch { pressProgressAnimation.animateTo(0f, pressProgressAnimationSpec) } launch { scaleXAnimation.animateTo(initialScale, scaleXAnimationSpec) } launch { scaleYAnimation.animateTo(initialScale, scaleYAnimationSpec) } } } fun updateValue(value: Float) { val targetValue = value.coerceIn(valueRange) animationScope.launch { launch { valueAnimation.animateTo( targetValue, valueAnimationSpec ) { updateVelocity() } } } } fun animateToValue(value: Float) { animationScope.launch { mutatorMutex.mutate { press() val targetValue = value.coerceIn(valueRange) launch { valueAnimation.animateTo(targetValue, valueAnimationSpec) } if (velocity != 0f) { launch { velocityAnimation.animateTo(0f, velocityAnimationSpec) } } release() } } } private fun updateVelocity() { velocityTracker.addPosition( System.currentTimeMillis(), Offset(value, 0f) ) val targetVelocity = velocityTracker.calculateVelocity().x / (valueRange.endInclusive - valueRange.start) animationScope.launch { velocityAnimation.animateTo(targetVelocity, velocityAnimationSpec) } } } private suspend fun PointerInputScope.inspectDragGestures( onDragStart: (down: PointerInputChange) -> Unit = {}, onDragEnd: (change: PointerInputChange) -> Unit = {}, onDragCancel: () -> Unit = {}, onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit ) { awaitEachGesture { val initialDown = awaitFirstDown(false, PointerEventPass.Initial) val down = awaitFirstDown(false) onDragStart(down) onDrag(initialDown, Offset.Zero) val upEvent = drag( pointerId = initialDown.id, onDrag = { onDrag(it, it.positionChange()) } ) if (upEvent == null) { onDragCancel() } else { onDragEnd(upEvent) } } } private suspend inline fun AwaitPointerEventScope.drag( pointerId: PointerId, onDrag: (PointerInputChange) -> Unit ): PointerInputChange? { val isPointerUp = currentEvent.changes.fastFirstOrNull { it.id == pointerId }?.pressed != true if (isPointerUp) { return null } var pointer = pointerId while (true) { val change = awaitDragOrUp(pointer) ?: return null if (change.isConsumed) { return null } if (change.changedToUpIgnoreConsumed()) { return change } onDrag(change) pointer = change.id } } private suspend inline fun AwaitPointerEventScope.awaitDragOrUp( pointerId: PointerId ): PointerInputChange? { var pointer = pointerId while (true) { val event = awaitPointerEvent() val dragEvent = event.changes.fastFirstOrNull { it.id == pointer } ?: return null if (dragEvent.changedToUpIgnoreConsumed()) { val otherDown = event.changes.fastFirstOrNull { it.pressed } if (otherDown == null) { return dragEvent } else { pointer = otherDown.id } } else { val hasDragged = dragEvent.previousPosition != dragEvent.position if (hasDragged) { return dragEvent } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/M3Switch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.switches import android.annotation.SuppressLint import android.content.res.ColorStateList import android.view.MotionEvent import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box import androidx.compose.material3.Switch import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.viewinterop.AndroidView import com.google.android.material.materialswitch.MaterialSwitch import kotlinx.coroutines.launch @SuppressLint("ClickableViewAccessibility") @Composable fun M3Switch( checked: Boolean, modifier: Modifier = Modifier, internalModifier: Modifier = modifier, onCheckedChange: ((Boolean) -> Unit)?, enabled: Boolean = true, colors: SwitchColors = SwitchDefaults.colors(), interactionSource: MutableInteractionSource? = null, ) { val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } val isPressed by realInteractionSource.collectIsPressedAsState() var view by remember { mutableStateOf(null) } val scope = rememberCoroutineScope() DisposableEffect(view, checked, onCheckedChange) { view?.isChecked = checked view?.setOnCheckedChangeListener { _, value -> onCheckedChange?.let { onCheckedChange(value) } } onDispose { view?.setOnCheckedChangeListener(null) } } LaunchedEffect(view, isPressed) { view?.isPressed = isPressed } Box { Switch( checked = false, onCheckedChange = {}, modifier = internalModifier, colors = SwitchDefaults.transparentColors() ) var press by remember { mutableStateOf(null) } var drag by remember { mutableStateOf(null) } AndroidView( modifier = modifier, factory = { context -> MaterialSwitch(context).apply { view = this isHapticFeedbackEnabled = false setOnTouchListener { _, event -> when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { press = PressInteraction.Press( Offset(event.x, event.y) ).also { scope.launch { realInteractionSource.emit(it) } } } MotionEvent.ACTION_MOVE -> { drag = DragInteraction.Start().also { scope.launch { realInteractionSource.emit(it) } } } MotionEvent.ACTION_UP -> { scope.launch { press?.let { realInteractionSource.emit(PressInteraction.Release(it)) } drag?.let { realInteractionSource.emit(DragInteraction.Stop(it)) } press = null drag = null } } MotionEvent.ACTION_CANCEL -> { scope.launch { press?.let { realInteractionSource.emit(PressInteraction.Cancel(it)) } drag?.let { realInteractionSource.emit(DragInteraction.Cancel(it)) } press = null drag = null } } else -> Unit } false } } }, update = { it.isEnabled = enabled val states = arrayOf( intArrayOf(-android.R.attr.state_checked), intArrayOf(-android.R.attr.state_enabled), intArrayOf(android.R.attr.state_checked) ) val trackColors = intArrayOf( colors.uncheckedTrackColor.toArgb(), if (checked) { colors.disabledCheckedTrackColor } else { colors.disabledUncheckedTrackColor }.toArgb(), colors.checkedTrackColor.toArgb() ) it.trackTintList = ColorStateList(states, trackColors) val thumbColors = intArrayOf( colors.uncheckedThumbColor.toArgb(), if (checked) { colors.disabledCheckedThumbColor } else { colors.disabledUncheckedThumbColor }.toArgb(), colors.checkedThumbColor.toArgb() ) it.thumbTintList = ColorStateList(states, thumbColors) val borderColors = intArrayOf( colors.uncheckedBorderColor.toArgb(), if (checked) { colors.disabledCheckedBorderColor } else { colors.disabledUncheckedBorderColor }.toArgb(), colors.checkedBorderColor.toArgb() ) it.trackDecorationTintList = ColorStateList(states, borderColors) } ) } } @Suppress("UnusedReceiverParameter") private fun SwitchDefaults.transparentColors() = SwitchColors( checkedThumbColor = Color.Transparent, checkedTrackColor = Color.Transparent, checkedBorderColor = Color.Transparent, checkedIconColor = Color.Transparent, uncheckedThumbColor = Color.Transparent, uncheckedTrackColor = Color.Transparent, uncheckedBorderColor = Color.Transparent, uncheckedIconColor = Color.Transparent, disabledCheckedThumbColor = Color.Transparent, disabledCheckedTrackColor = Color.Transparent, disabledCheckedBorderColor = Color.Transparent, disabledCheckedIconColor = Color.Transparent, disabledUncheckedThumbColor = Color.Transparent, disabledUncheckedTrackColor = Color.Transparent, disabledUncheckedBorderColor = Color.Transparent, disabledUncheckedIconColor = Color.Transparent ) ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/OneUISwitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.switches import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.switches.ActualSwitchColors.Companion.forConfig import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun OneUISwitch( modifier: Modifier = Modifier, colors: SwitchColors = SwitchDefaults.colors(), onCheckedChange: ((Boolean) -> Unit)? = { }, enabled: Boolean = true, checked: Boolean = false, interactionSource: MutableInteractionSource? = null ) { val interactionSource = interactionSource ?: remember { MutableInteractionSource() } var isAnimating by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() val actualColors = colors.forConfig(enabled, checked) val spec = tween(animDuration) val thumbColor by animateColorAsState( targetValue = actualColors.thumb, label = "Switch thumb color", animationSpec = spec ) val track by animateColorAsState( targetValue = actualColors.track, label = "Switch track color", animationSpec = spec ) val stroke by animateColorAsState( targetValue = actualColors.stroke, label = "Switch stroke color", animationSpec = spec ) val rippleAlphaFactor by animateFloatAsState( targetValue = if (isAnimating) 1F else 0F, label = "Switch ripple alpha", animationSpec = tween(animDuration) ) val rippleRadius by animateDpAsState( targetValue = if (isAnimating) rippleRadius else 0.dp, label = "Switch ripple radius", animationSpec = tween(animDuration) ) val ripple = MaterialTheme.colorScheme.outline.copy(0.1f) var dragProgress by remember { mutableStateOf(null) } val progress by animateFloatAsState( targetValue = dragProgress ?: if (checked) 1f else 0f, label = "Switch progress", animationSpec = tween(animDuration) ) val shape = ShapeDefaults.circle Canvas( modifier = modifier .width(trackSize.width + (thumbOvershoot * 2)) .height(thumbSize.height) .hapticsClickable( enabled = enabled, interactionSource = interactionSource, indication = null, role = Role.Switch, onClick = { onCheckedChange?.invoke(!checked) isAnimating = true scope.launch { delay(animDuration.toLong()) isAnimating = false } } ) .pointerInput(enabled, onCheckedChange) { if (!enabled) return@pointerInput var interaction: DragInteraction.Start? = null detectDragGestures( onDragStart = { interaction = DragInteraction.Start().also(interactionSource::tryEmit) isAnimating = true dragProgress = progress }, onDragEnd = { val newChecked = (dragProgress ?: progress) > 0.5f onCheckedChange?.invoke(newChecked) scope.launch { dragProgress = if (newChecked) 1f else 0f delay(animDuration.toLong()) dragProgress = null isAnimating = false } interaction?.let { interactionSource.tryEmit(DragInteraction.Stop(it)) } }, onDragCancel = { val newChecked = (dragProgress ?: progress) > 0.5f onCheckedChange?.invoke(newChecked) scope.launch { dragProgress = if (newChecked) 1f else 0f delay(animDuration.toLong()) dragProgress = null } isAnimating = false interaction?.let { interactionSource.tryEmit(DragInteraction.Cancel(it)) } }, onDrag = { change, dragAmount -> change.consume() dragProgress = ((dragProgress ?: progress) + dragAmount.x).coerceIn(0f, 1f) } ) } ) { drawTrack( color = track, dpSize = trackSize, spacingStart = thumbOvershoot, shape = shape ) val thumbPos = thumbPosition( overshoot = thumbOvershoot, progress = progress, radius = thumbSize.width / 2, size = size, density = this ) drawThumb( color = thumbColor, radius = thumbSize.width / 2, position = thumbPos, shape = shape ) drawOutline( color = stroke, radius = (thumbSize.width + strokeWidth) / 2, position = thumbPos, strokeWidth = strokeWidth, shape = shape ) drawRipple( color = ripple.copy(alpha = ripple.alpha * rippleAlphaFactor), radius = rippleRadius, position = thumbPos, shape = shape ) } } private fun DrawScope.drawTrack( color: Color, dpSize: DpSize, spacingStart: Dp, shape: Shape ) { val dif = size.height - dpSize.height.toPx() val outline = shape.createOutline( size = dpSize.toSize(), layoutDirection = LayoutDirection.Ltr, density = this ) translate( left = spacingStart.toPx(), top = dif / 2 ) { drawOutline(outline = outline, color = color) } } private fun DrawScope.drawThumb( color: Color, radius: Dp, position: Offset, shape: Shape ) { val size = Size(radius.toPx() * 2, radius.toPx() * 2) val outline = shape.createOutline( size = size, layoutDirection = LayoutDirection.Ltr, density = this ) translate( left = position.x - radius.toPx(), top = position.y - radius.toPx() ) { drawOutline(outline = outline, color = color) } } private fun DrawScope.drawOutline( color: Color, radius: Dp, position: Offset, strokeWidth: Dp, shape: Shape ) { val size = Size(radius.toPx() * 2, radius.toPx() * 2) val outline = shape.createOutline( size = size, layoutDirection = LayoutDirection.Ltr, density = this ) translate( left = position.x - radius.toPx(), top = position.y - radius.toPx() ) { drawOutline( outline = outline, color = color, style = Stroke(width = strokeWidth.toPx()) ) } } private fun DrawScope.drawRipple( color: Color, radius: Dp, position: Offset, shape: Shape ) { val size = Size(radius.toPx() * 2, radius.toPx() * 2) val outline = shape.createOutline( size = size, layoutDirection = LayoutDirection.Ltr, density = this ) translate( left = position.x - radius.toPx(), top = position.y - radius.toPx() ) { drawOutline(outline = outline, color = color) } } private fun thumbPosition( overshoot: Dp, progress: Float, radius: Dp, size: Size, density: Density ): Offset { val yCenter = size.height / 2 val width = size.width val start = with(density) { thumbOvershoot.toPx() + (radius.toPx() - overshoot.toPx()) } val end = width - start val pos = mapRange( value = progress, targetStart = start, targetEnd = end ) return Offset( x = pos, y = yCenter ) } private fun mapRange( value: Float, origStart: Float = 0f, origEnd: Float = 1f, targetStart: Float, targetEnd: Float ): Float = (value - origStart) / (origEnd - origStart) * (targetEnd - targetStart) + targetStart private data class ActualSwitchColors( val thumb: Color, val track: Color, val stroke: Color ) { companion object { @Suppress("KotlinConstantConditions") fun SwitchColors.forConfig( enabled: Boolean, checked: Boolean ): ActualSwitchColors = if ( enabled && checked ) ActualSwitchColors( thumb = checkedThumbColor, track = checkedTrackColor, stroke = checkedTrackColor ) else if ( !enabled && checked ) ActualSwitchColors( thumb = disabledCheckedThumbColor, track = disabledCheckedTrackColor, stroke = disabledCheckedTrackColor ) else if ( enabled && !checked ) ActualSwitchColors( thumb = uncheckedTrackColor.copy(1f), track = uncheckedThumbColor.copy(1f), stroke = uncheckedThumbColor.copy(1f), ) else ActualSwitchColors( thumb = disabledUncheckedTrackColor, track = disabledUncheckedThumbColor, stroke = disabledUncheckedThumbColor ) } } private const val animDuration = 250 private val strokeWidth = 2.dp private val thumbSize = DpSize( width = 22.dp, height = 22.dp ) private val thumbOvershoot = 2.dp private val trackSize = DpSize( width = 35.dp, height = 18.5.dp ) private val rippleRadius = 20.dp @Composable @EnPreview private fun Preview() = ImageToolboxThemeForPreview(true, keyColor = Color.Green) { val colors = SwitchDefaults.colors( disabledUncheckedThumbColor = MaterialTheme.colorScheme.onSurface .copy(alpha = 0.12f) .compositeOver(MaterialTheme.colorScheme.surface) ).copy( uncheckedTrackColor = MaterialTheme.colorScheme.surfaceContainerLow ) Surface { Column( modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { var checked by remember { mutableStateOf(true) } OneUISwitch( checked = checked, colors = colors, onCheckedChange = { checked = it } ) OneUISwitch( checked = false, colors = colors ) OneUISwitch( checked = true, enabled = false, colors = colors ) OneUISwitch( checked = false, enabled = false, colors = colors ) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/switches/PixelSwitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.switches import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.animation.PointToPointEasing import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberRipple import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlin.math.roundToInt @Composable fun PixelSwitch( checked: Boolean, modifier: Modifier = Modifier, onCheckedChange: ((Boolean) -> Unit)?, enabled: Boolean = true, colors: SwitchColors = SwitchDefaults.colors(), interactionSource: MutableInteractionSource? = null, ) { val realInteractionSource = interactionSource ?: remember { MutableInteractionSource() } val trackColor by animateColorAsState( targetValue = trackColor(enabled, checked, colors), animationSpec = tween(Duration, easing = PointToPointEasing) ) val thumbColor by animateColorAsState( targetValue = thumbColor(enabled, checked, colors), animationSpec = tween(Duration, easing = PointToPointEasing) ) val borderColor by animateColorAsState( targetValue = borderColor(enabled, checked, colors), animationSpec = tween(Duration, easing = PointToPointEasing) ) val thumbSize by animateDpAsState( targetValue = if (checked) SelectedHandleSize else UnselectedHandleSize, animationSpec = tween(Duration, easing = PointToPointEasing) ) val density = LocalDensity.current var offsetAnimated by remember(checked) { mutableFloatStateOf( with(density) { if (checked) { ThumbPaddingEnd } else { ThumbPaddingStart }.toPx() } ) } val state = rememberDraggableState { offsetAnimated = with(density) { (offsetAnimated + it).coerceIn(ThumbPaddingStart.toPx(), ThumbPaddingEnd.toPx()) } } val thumbOffset by animateFloatAsState( targetValue = offsetAnimated, animationSpec = tween(Duration, easing = PointToPointEasing) ) Box( modifier = modifier .hapticsClickable( interactionSource = realInteractionSource, indication = null, enabled = enabled, onClickLabel = null, role = Role.Switch, enableHaptics = false, onClick = { onCheckedChange?.invoke(!checked) } ) .draggable( state = state, orientation = Orientation.Horizontal, interactionSource = realInteractionSource, enabled = enabled, onDragStopped = { with(density) { if (it < (ThumbPaddingEnd.toPx() - ThumbPaddingStart.toPx()) / 2f) { offsetAnimated = ThumbPaddingStart.toPx() if (checked) onCheckedChange?.invoke(false) } else { offsetAnimated = ThumbPaddingEnd.toPx() if (!checked) onCheckedChange?.invoke(true) } } } ) .background(trackColor, ShapeDefaults.circle) .size(TrackWidth, TrackHeight) .border( width = TrackOutlineWidth, color = borderColor, shape = ShapeDefaults.circle ) ) { Box( modifier = Modifier .offset { IntOffset(x = thumbOffset.roundToInt(), y = 0) } .indication( interactionSource = realInteractionSource, indication = rememberRipple( bounded = false, radius = 16.dp ) ) .align(Alignment.CenterStart) .background(thumbColor, ShapeDefaults.circle) .size(thumbSize) ) } } private const val Duration = 500 @Stable private fun trackColor( enabled: Boolean, checked: Boolean, colors: SwitchColors ): Color = if (enabled) { if (checked) colors.checkedTrackColor else colors.uncheckedTrackColor } else { if (checked) colors.disabledCheckedTrackColor else colors.disabledUncheckedTrackColor } @Stable private fun thumbColor( enabled: Boolean, checked: Boolean, colors: SwitchColors ): Color = if (enabled) { if (checked) colors.checkedThumbColor else colors.uncheckedThumbColor } else { if (checked) colors.disabledCheckedThumbColor else colors.disabledUncheckedThumbColor } @Stable private fun borderColor( enabled: Boolean, checked: Boolean, colors: SwitchColors ): Color = if (enabled) { if (checked) colors.checkedBorderColor else colors.uncheckedBorderColor } else { if (checked) colors.disabledCheckedBorderColor else colors.disabledUncheckedBorderColor } private val TrackWidth = 56.0.dp private val TrackHeight = 28.0.dp private val TrackOutlineWidth = 1.8.dp private val SelectedHandleSize = 20.0.dp private val UnselectedHandleSize = 20.0.dp private val ThumbPaddingStart = (TrackHeight - UnselectedHandleSize) / 2 private val ThumbPaddingEnd = TrackWidth - SelectedHandleSize / 2 - TrackHeight / 2 ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/AutoSizeText.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit @Composable fun AutoSizeText( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = 1, minLines: Int = 1, style: TextStyle = LocalTextStyle.current, key: (String) -> Any? = { it } ) { var sizedStyle by remember(style, key(text)) { mutableStateOf(style) } var readyToDraw by remember(minLines, key(text)) { mutableStateOf(false) } Text( text = text, color = color, maxLines = maxLines, fontStyle = fontStyle, fontWeight = fontWeight, fontFamily = fontFamily, letterSpacing = letterSpacing, textDecoration = textDecoration, textAlign = textAlign, lineHeight = lineHeight, overflow = overflow, softWrap = softWrap, style = style, fontSize = sizedStyle.fontSize, minLines = minLines, onTextLayout = { if (it.didOverflowHeight) { sizedStyle = sizedStyle.copy(fontSize = sizedStyle.fontSize * 0.9) } else { readyToDraw = true } }, modifier = modifier.drawWithContent { if (readyToDraw) drawContent() } ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/HtmlText.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.foundation.text.BasicText import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow @Composable fun HtmlText( html: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Left) .copy(color = MaterialTheme.colorScheme.onSurface), hyperlinkStyle: TextStyle = style.copy( color = MaterialTheme.colorScheme.primary, textDecoration = TextDecoration.Underline, fontWeight = style.fontWeight?.run { FontWeight(weight + 100) } ), softWrap: Boolean = true, overflow: TextOverflow = TextOverflow.Ellipsis, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {} ) { BasicText( text = remember(html) { val linkStyle = TextLinkStyles( style = hyperlinkStyle.toSpanStyle() ) AnnotatedString.fromHtml( htmlString = html, linkStyles = linkStyle ).linkify(linkStyle) }, modifier = modifier, style = style, softWrap = softWrap, overflow = overflow, maxLines = maxLines, onTextLayout = onTextLayout ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/IsKeyboardVisibleAsState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.ime import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager @Composable fun isKeyboardVisibleAsState(): State { val isImeVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0 return rememberUpdatedState(isImeVisible) } @Composable fun KeyboardFocusHandler() { val focus = LocalFocusManager.current val isKeyboardVisible by isKeyboardVisibleAsState() LaunchedEffect(isKeyboardVisible) { if (!isKeyboardVisible) focus.clearFocus() } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Linkify.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.buildAnnotatedString fun AnnotatedString.linkify( linkStyles: TextLinkStyles ): AnnotatedString = buildAnnotatedString { append(this@linkify) val text = this@linkify.text val matches = URL_PATTERN.findAll(text).toList() for (match in matches) { val url = match.value if (url.contains("@") && !url.startsWith("mailto:")) { continue } val start = match.range.first val end = match.range.last + 1 val hasLinkAnnotation = this@linkify.getStringAnnotations( tag = url, start = start, end = end ).any { it.item == url } if (!hasLinkAnnotation) { addLink( url = LinkAnnotation.Url( url = url, styles = linkStyles ), start = start, end = end ) } } } private val URL_PATTERN = Regex("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.\\S{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.\\S{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.\\S{2,}|www\\.[a-zA-Z0-9]+\\.\\S{2,})") ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/Marquee.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.foundation.basicMarquee import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.layout.layout import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp import com.gigamole.composefadingedges.fill.FadingEdgesFillType import com.gigamole.composefadingedges.marqueeHorizontalFadingEdges fun Modifier.marquee( edgesColor: Color = Color.Unspecified, ) = this.composed { var showMarquee by remember { mutableStateOf(false) } Modifier .clipToBounds() .then( if (showMarquee) { Modifier.marqueeHorizontalFadingEdges( fillType = if (edgesColor.isSpecified) { FadingEdgesFillType.FadeColor( color = edgesColor ) } else FadingEdgesFillType.FadeClip(), length = 10.dp, isMarqueeAutoLayout = false ) { Modifier.basicMarquee( iterations = Int.MAX_VALUE, velocity = 48.dp, repeatDelayMillis = 1500 ) } } else Modifier ) .layout { measurable, constraints -> val childConstraints = constraints.copy(maxWidth = Constraints.Infinity) val placeable = measurable.measure(childConstraints) val containerWidth = constraints.constrainWidth(placeable.width) val contentWidth = placeable.width if (!showMarquee) { showMarquee = contentWidth > containerWidth } layout(containerWidth, placeable.height) { placeable.placeWithLayer(x = 0, y = 0) } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/OutlinedText.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.foundation.layout.Box import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit @Stable @Immutable data class OutlineParams( val stroke: Stroke, val color: Color ) @Composable fun OutlinedText( text: String, outlineParams: OutlineParams?, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: ((TextLayoutResult) -> Unit)? = null, style: TextStyle = LocalTextStyle.current ) { Box(modifier = modifier) { outlineParams?.let { params -> Text( text = text, modifier = Modifier.semantics { hideFromAccessibility() }, color = params.color, fontSize = fontSize, fontStyle = fontStyle, fontWeight = fontWeight, fontFamily = fontFamily, letterSpacing = letterSpacing, textDecoration = null, textAlign = textAlign, lineHeight = lineHeight, overflow = overflow, softWrap = softWrap, maxLines = maxLines, minLines = minLines, style = style.copy( shadow = null, drawStyle = params.stroke, ), ) } Text( text = text, color = color, fontSize = fontSize, fontStyle = fontStyle, fontWeight = fontWeight, fontFamily = fontFamily, letterSpacing = letterSpacing, textDecoration = textDecoration, textAlign = textAlign, lineHeight = lineHeight, overflow = overflow, softWrap = softWrap, maxLines = maxLines, minLines = minLines, onTextLayout = onTextLayout, style = style, ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/PatternHighlightTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme data class PatternHighlightTransformation( private val mapping: Map ) : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { val annotated = buildAnnotatedString { append(text) mapping.forEach { (re, color) -> re.findAll(text).forEach { match -> addStyle( style = SpanStyle( color = color, fontWeight = FontWeight.SemiBold, background = color.copy(0.15f) ), start = match.range.first, end = match.range.last + 1 ) } } } return TransformedText( text = annotated, offsetMapping = OffsetMapping.Identity ) } companion object { @Composable fun default(): PatternHighlightTransformation { val color = takeColorFromScheme { primary.blend(primaryContainer, 0.1f) } val colorUpper = takeColorFromScheme { tertiary.blend(tertiaryContainer, 0.1f) } return remember(color, colorUpper) { PatternHighlightTransformation( mapOf( PATTERN_TOKENS to color, UPPER_PATTERN_TOKENS to colorUpper ) ) } } } } private val PATTERN_TOKENS = Regex( """\\[pwdhrcoimse](\{[^}]*\})?""" ) private val UPPER_PATTERN_TOKENS = Regex( """\\[PDOIMSE](\{[^}]*\})?""" ) @Preview @Composable private fun Preview() = ImageToolboxThemeForPreview( isDarkTheme = true, keyColor = Color.Blue ) { TextField( value = FilenamePattern.Default + "\\E", onValueChange = {}, visualTransformation = PatternHighlightTransformation.default() ) } @Preview @Composable private fun Preview1() = ImageToolboxThemeForPreview( isDarkTheme = false, keyColor = Color.Blue ) { TextField( value = FilenamePattern.Default + "\\E", onValueChange = {}, visualTransformation = PatternHighlightTransformation.default() ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/RoundedTextField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import android.annotation.SuppressLint import androidx.compose.animation.Animatable import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.TextSelectionColors import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import kotlinx.coroutines.cancel import kotlinx.coroutines.launch @Composable fun RoundedTextField( modifier: Modifier = Modifier, onValueChange: (String) -> Unit, label: String = "", hint: String = "", shape: Shape = ShapeDefaults.small, startIcon: ImageVector? = null, value: String, isError: Boolean = false, loading: Boolean = false, supportingText: (@Composable (isError: Boolean) -> Unit)? = null, supportingTextVisible: Boolean = true, endIcon: (@Composable (Boolean) -> Unit)? = null, formatText: String.() -> String = { this }, textStyle: TextStyle = LocalTextStyle.current, onLoseFocusTransformation: String.() -> String = { this }, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardActions: KeyboardActions = KeyboardActions.Default, singleLine: Boolean = true, readOnly: Boolean = false, colors: TextFieldColors = RoundedTextFieldColors(isError), enabled: Boolean = true, maxLines: Int = Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, minLines: Int = 1, maxSymbols: Int = Int.MAX_VALUE ) { val labelImpl = @Composable { Text( text = label ) } val hintImpl = @Composable { Text(text = hint, modifier = Modifier.padding(start = 4.dp)) } val leadingIconImpl = @Composable { Icon( imageVector = startIcon!!, contentDescription = null ) } RoundedTextField( modifier = modifier, value = value, onValueChange = { onValueChange(it.formatText()) }, textStyle = textStyle, colors = colors, shape = shape, singleLine = singleLine, readOnly = readOnly, keyboardOptions = keyboardOptions, visualTransformation = visualTransformation, endIcon = endIcon, startIcon = if (startIcon != null) leadingIconImpl else null, label = if (label.isNotEmpty()) labelImpl else null, hint = if (hint.isNotEmpty()) hintImpl else null, isError = isError, loading = loading, supportingText = supportingText, supportingTextVisible = supportingTextVisible, formatText = formatText, onLoseFocusTransformation = onLoseFocusTransformation, keyboardActions = keyboardActions, enabled = enabled, maxLines = maxLines, interactionSource = interactionSource, minLines = minLines, maxSymbols = maxSymbols ) } @Composable fun RoundedTextField( modifier: Modifier = Modifier, onValueChange: (String) -> Unit, label: (@Composable () -> Unit)? = null, hint: (@Composable () -> Unit)? = null, shape: Shape = ShapeDefaults.small, startIcon: (@Composable () -> Unit)? = null, value: String, isError: Boolean = false, loading: Boolean = false, supportingText: (@Composable (isError: Boolean) -> Unit)? = null, supportingTextVisible: Boolean = true, endIcon: (@Composable (Boolean) -> Unit)? = null, formatText: String.() -> String = { this }, textStyle: TextStyle = LocalTextStyle.current, onLoseFocusTransformation: String.() -> String = { this }, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardActions: KeyboardActions = KeyboardActions.Default, singleLine: Boolean = true, readOnly: Boolean = false, colors: TextFieldColors = RoundedTextFieldColors(isError), enabled: Boolean = true, maxLines: Int = Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, minLines: Int = 1, maxSymbols: Int = Int.MAX_VALUE ) { val focus = LocalFocusManager.current val focused = interactionSource.collectIsFocusedAsState().value val colorScheme = MaterialTheme.colorScheme val unfocusedColor = if (!enabled) colorScheme.outlineVariant( onTopOf = colors.focusedContainerColor, luminance = 0.2f ) else colors.unfocusedIndicatorColor val focusedColor = if (isError) { colors.errorIndicatorColor } else colors.focusedIndicatorColor val borderColor by remember(focusedColor, enabled, focused) { derivedStateOf { Animatable( initialValue = if (!focused) unfocusedColor else focusedColor ) } } val scope = rememberCoroutineScope() LaunchedEffect(isError) { borderColor.animateTo(if (focused) focusedColor else unfocusedColor) } val mergedModifier = Modifier .fillMaxWidth() .border( width = animateDpAsState( if (borderColor.value == unfocusedColor) 1.dp else 2.dp ).value, color = borderColor.value, shape = shape ) .onFocusChanged { scope.launch { if (readOnly) { focus.clearFocus() cancel() } if (it.isFocused) borderColor.animateTo(focusedColor) else { if (!isError) borderColor.animateTo(unfocusedColor) onValueChange(value.onLoseFocusTransformation()) } cancel() } } .animateContentSizeNoClip() val supportingTextImpl = @Composable { if (!loading && supportingText != null) { supportingText(isError) } } Column( modifier = modifier.animateContentSizeNoClip() ) { val showSupportingText = !loading && (isError || (supportingText != null && supportingTextVisible)) TextField( modifier = mergedModifier.clip(shape), value = value, onValueChange = { onValueChange(it.take(maxSymbols).formatText()) }, textStyle = textStyle, colors = colors, shape = shape, singleLine = singleLine, readOnly = readOnly, keyboardOptions = keyboardOptions, visualTransformation = visualTransformation, trailingIcon = endIcon?.let { { it(focused) } }, leadingIcon = startIcon, label = label, placeholder = hint, keyboardActions = keyboardActions, enabled = enabled, maxLines = maxLines, interactionSource = interactionSource, minLines = minLines, ) val showMaxSymbols = maxSymbols != Int.MAX_VALUE && value.isNotEmpty() AnimatedVisibility( visible = showSupportingText || showMaxSymbols, modifier = Modifier.fillMaxWidth() ) { Row( modifier = Modifier .fillMaxWidth() .padding(top = 6.dp) .padding(horizontal = 8.dp) ) { if (showSupportingText) { ProvideTextStyle( LocalTextStyle.current.copy( color = MaterialTheme.colorScheme.error, fontSize = 12.sp, lineHeight = 12.sp, ) ) { Row { supportingTextImpl() Spacer(modifier = Modifier.width(16.dp)) } } } if (showMaxSymbols) { Text( text = "${value.length} / $maxSymbols", modifier = Modifier.weight(1f), textAlign = TextAlign.End, fontSize = 12.sp, lineHeight = 12.sp, color = borderColor.value ) } } } } } @SuppressLint("ComposableNaming") @Composable fun RoundedTextFieldColors( isError: Boolean, containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, focusedIndicatorColor: Color = MaterialTheme.colorScheme.primary, unfocusedIndicatorColor: Color = MaterialTheme.colorScheme.surfaceVariant.inverse({ 0.2f }) ): TextFieldColors = MaterialTheme.colorScheme.run { val containerColorNew = if (isError) { containerColor.blend(error) } else containerColor TextFieldDefaults.colors( focusedContainerColor = containerColorNew, unfocusedContainerColor = containerColorNew, disabledContainerColor = containerColorNew, cursorColor = if (isError) error else focusedIndicatorColor, focusedIndicatorColor = focusedIndicatorColor, unfocusedIndicatorColor = unfocusedIndicatorColor, focusedLeadingIconColor = if (isError) error else focusedIndicatorColor, unfocusedLeadingIconColor = if (isError) error else surfaceVariant.inverse(), focusedTrailingIconColor = if (isError) error else focusedIndicatorColor, unfocusedTrailingIconColor = if (isError) error else surfaceVariant.inverse(), focusedLabelColor = if (isError) error else focusedIndicatorColor, unfocusedLabelColor = if (isError) error else MaterialTheme.colorScheme.onSurfaceVariant.copy( 0.9f ), selectionColors = TextSelectionColors( handleColor = focusedIndicatorColor.copy(1f), backgroundColor = focusedIndicatorColor.copy(0.4f) ) ) } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TitleItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import android.annotation.SuppressLint import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.icons.Firebase import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults @Composable fun TitleItem( icon: ImageVector? = null, text: String, endContent: @Composable RowScope.() -> Unit, @SuppressLint("ModifierParameter") modifier: Modifier = Modifier.padding(16.dp), ) { Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { icon?.let { icon -> IconShapeContainer( content = { Icon( imageVector = icon, contentDescription = null ) } ) Spacer(Modifier.width(8.dp)) } Text(text = text, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f)) endContent.let { Spacer(Modifier.width(8.dp)) it() } } } @Composable fun TitleItem( icon: ImageVector? = null, text: String, modifier: Modifier = Modifier.padding(16.dp), subtitle: String? = null, iconEndPadding: Dp = 8.dp, endContent: (@Composable RowScope.() -> Unit)? = null, iconContainerColor: Color = IconShapeDefaults.containerColor, iconContentColor: Color = IconShapeDefaults.contentColor, ) { Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { icon?.let { icon -> IconShapeContainer( content = { Icon( imageVector = icon, contentDescription = null, tint = if (icon == Icons.TwoTone.Firebase) Color.Unspecified else LocalContentColor.current ) }, containerColor = iconContainerColor, contentColor = iconContentColor ) Spacer(Modifier.width(iconEndPadding)) } Column( modifier = Modifier.weight(1f, endContent != null) ) { Text( text = text, style = PreferenceItemDefaults.TitleFontStyle ) subtitle?.let { Spacer(modifier = Modifier.height(2.dp)) Text( text = subtitle, fontSize = 12.sp, textAlign = TextAlign.Start, fontWeight = FontWeight.Normal, lineHeight = 14.sp, color = LocalContentColor.current.copy(alpha = 0.5f) ) } } endContent?.let { Spacer(Modifier.width(8.dp)) it() } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/text/TopAppBarTitle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.text import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.Green import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize @Composable fun TopAppBarTitle( title: String, input: T?, isLoading: Boolean, size: Long?, originalSize: Long? = null, updateOnSizeChange: Boolean = true ) { if (updateOnSizeChange) { AnimatedContent( targetState = Triple( input, isLoading, size ), transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier.marquee() ) { (inp, loading, size) -> if (loading) { Text( stringResource(R.string.loading) ) } else if (inp == null || size == null) { AnimatedContent(targetState = title) { Text(it) } } else { AnimatedContent(originalSize) { originalSize -> val readableOriginal = if ((originalSize ?: 0) > 0) { rememberHumanFileSize(originalSize ?: 0) } else { "? B" } val readableCompressed = if (size > 0) { rememberHumanFileSize(size) } else { "(...)" } val isSizesEqual = size == originalSize || readableCompressed == readableOriginal val color = takeColorFromScheme { when { isSizesEqual || originalSize == null -> onBackground size > originalSize -> error.blend(errorContainer) size <= 0 -> tertiary else -> Green } } val textStyle = LocalTextStyle.current val sizeString = stringResource(R.string.size, readableCompressed) val text by remember( originalSize, isSizesEqual, sizeString, readableOriginal, readableCompressed, textStyle ) { derivedStateOf { buildAnnotatedString { append( if (originalSize == null || isSizesEqual) sizeString else "" ) originalSize?.takeIf { !isSizesEqual }?.let { append(readableOriginal) append(" -> ") withStyle(textStyle.toSpanStyle().copy(color)) { append(readableCompressed) } } } } } Text( text = text ) } } } } else { AnimatedContent( targetState = input to isLoading, transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier.marquee() ) { (inp, loading) -> if (loading) { Text( stringResource(R.string.loading) ) } else if (inp == null || size == null || size <= 0) { AnimatedContent(targetState = title) { Text(it) } } else { val readableOriginal = rememberHumanFileSize(originalSize ?: 0) val readableCompressed = rememberHumanFileSize(size) val isSizesEqual = size == originalSize || readableCompressed == readableOriginal val color = takeColorFromScheme { when { isSizesEqual || originalSize == null -> onBackground size > originalSize -> error.blend(errorContainer) else -> Green } } val textStyle = LocalTextStyle.current val sizeString = stringResource(R.string.size, readableCompressed) val text by remember( originalSize, isSizesEqual, sizeString, readableOriginal, readableCompressed, textStyle ) { derivedStateOf { buildAnnotatedString { append( if (originalSize == null || isSizesEqual) sizeString else "" ) originalSize?.takeIf { !isSizesEqual }?.let { append(readableOriginal) append(" -> ") withStyle(textStyle.toSpanStyle().copy(color)) { append(readableCompressed) } } } } } Text( text = text ) } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AutoContentBasedColors.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.utils import android.graphics.Bitmap import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isSpecified import coil3.imageLoader import coil3.request.ImageRequest import coil3.toBitmap import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberAppColorTuple import com.t8rin.imagetoolbox.core.utils.appContext @Composable fun AutoContentBasedColors( model: T?, allowChangeColor: Boolean = true ) { AutoContentBasedColors( model = model, selector = { appContext.imageLoader.execute( ImageRequest.Builder(appContext).data(model).build() ).image?.toBitmap() }, allowChangeColor = allowChangeColor ) } @Composable fun AutoContentBasedColors( model: T?, selector: suspend (T) -> Bitmap?, allowChangeColor: Boolean = true ) { val appColorTuple = rememberAppColorTuple() val settingsState = LocalSettingsState.current val themeState = LocalDynamicThemeState.current val internalAllowChangeColor = settingsState.allowChangeColorByImage && allowChangeColor LaunchedEffect(model, appColorTuple, internalAllowChangeColor) { internalAllowChangeColor.takeIf { it } ?.let { model?.let { selector(it) } }?.let { themeState.updateColorByImage(it) } ?: themeState.updateColorTuple(appColorTuple) } } @Composable fun AutoContentBasedColors( model: Bitmap?, allowChangeColor: Boolean = true ) { AutoContentBasedColors( model = model, selector = { model }, allowChangeColor = allowChangeColor ) } @Composable fun AutoContentBasedColors( model: Color, allowChangeColor: Boolean = true ) { val appColorTuple = rememberAppColorTuple() val settingsState = LocalSettingsState.current val themeState = LocalDynamicThemeState.current val internalAllowChangeColor = settingsState.allowChangeColorByImage && allowChangeColor LaunchedEffect(model, appColorTuple, internalAllowChangeColor) { internalAllowChangeColor.takeIf { it && model.isSpecified } ?.let { themeState.updateColor(model) } ?: themeState.updateColorTuple(appColorTuple) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/AvailableHeight.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.utils import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberCurrentLifecycleEvent import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarDefaults import com.t8rin.imagetoolbox.core.ui.widget.image.ImageHeaderState import com.t8rin.modalsheet.FullscreenPopup @Composable fun rememberAvailableHeight( imageState: ImageHeaderState, expanded: Boolean = false ): Dp { val fullHeight = rememberFullHeight() return animateDpAsState( targetValue = fullHeight.times( when { expanded || imageState.position == 4 -> 1f imageState.position == 3 -> 0.7f imageState.position == 2 -> 0.5f imageState.position == 1 -> 0.35f else -> 0.2f } ) ).value } @Composable fun rememberFullHeight(): Dp { val screenSize = LocalScreenSize.current val currentLifecycleEvent = rememberCurrentLifecycleEvent() var fullHeight by remember(screenSize, currentLifecycleEvent) { mutableStateOf(0.dp) } val density = LocalDensity.current if (fullHeight == 0.dp) { FullscreenPopup { Column( modifier = Modifier.fillMaxSize() ) { TopAppBar( title = { Text(" ") }, colors = EnhancedTopAppBarDefaults.colors(Color.Transparent) ) Spacer( Modifier .weight(1f) .onSizeChanged { with(density) { fullHeight = it.height.toDp() } } ) BottomAppBar( containerColor = Color.Transparent, floatingActionButton = { Spacer(Modifier.height(56.dp)) }, actions = {} ) } } } return fullHeight } fun ImageHeaderState.isExpanded() = this.position == 4 && isBlocked fun middleImageState() = ImageHeaderState() @Composable fun rememberImageState(): MutableState { val settingsState = LocalSettingsState.current return remember(settingsState.generatePreviews) { mutableStateOf( if (settingsState.generatePreviews) middleImageState() else ImageHeaderState(0) ) } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/RememberRetainedLazyListState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.utils import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.saveable.rememberSaveable /** * Static field, contains all scroll values */ private val SaveMap = mutableMapOf() private data class KeyParams( val params: String, val index: Int, val scrollOffset: Int ) /** * Save scroll state on all time. * @param key value for comparing screen * @param params arguments for find different between equals screen * @param initialFirstVisibleItemIndex see [LazyListState.firstVisibleItemIndex] * @param initialFirstVisibleItemScrollOffset see [LazyListState.firstVisibleItemScrollOffset] */ @Composable fun rememberRetainedLazyListState( key: String, params: String = "", initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { val scrollState = rememberSaveable(saver = LazyListState.Saver) { var savedValue = SaveMap[key] if (savedValue?.params != params) savedValue = null val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex val savedOffset = savedValue?.scrollOffset ?: initialFirstVisibleItemScrollOffset LazyListState( savedIndex, savedOffset ) } DisposableEffect(Unit) { onDispose { val lastIndex = scrollState.firstVisibleItemIndex val lastOffset = scrollState.firstVisibleItemScrollOffset SaveMap[key] = KeyParams(params, lastIndex, lastOffset) } } return scrollState } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/utils/ScreenList.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.utils import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getExtension import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen @Composable internal fun List.screenList( extraDataType: ExtraDataType? ): State, List>> { val uris = this fun Uri?.type( vararg extensions: String ): Boolean { if (this == null) return false val extension = getExtension() ?: return false return extensions.any(extension::contains) } val filesAvailableScreens by remember(uris) { derivedStateOf { if (uris.size > 1) { listOf(Screen.Zip(uris)) } else { listOf( Screen.Cipher(uris.firstOrNull()), Screen.ChecksumTools(uris.firstOrNull()), Screen.Zip(uris) ) } } } val audioAvailableScreens by remember(uris) { derivedStateOf { listOf( Screen.AudioCoverExtractor(uris) ) + filesAvailableScreens } } val gifAvailableScreens by remember(uris) { derivedStateOf { listOf( Screen.GifTools( Screen.GifTools.Type.GifToImage( uris.firstOrNull() ) ), Screen.GifTools( Screen.GifTools.Type.GifToJxl(uris) ), Screen.GifTools( Screen.GifTools.Type.GifToWebp(uris) ) ) + filesAvailableScreens } } val pdfAvailableScreens by remember(uris) { derivedStateOf { val multiplePdf = Screen.PdfTools.Merge(uris.takeIf { it.isNotEmpty() }) val pdfScreens = if (uris.size == 1) { listOf( Screen.PdfTools.Preview( uris.firstOrNull() ), Screen.PdfTools.ExtractPages( uris.firstOrNull() ), multiplePdf, Screen.PdfTools.Split(uris.firstOrNull()), Screen.PdfTools.RemovePages(uris.firstOrNull()), Screen.PdfTools.Rotate(uris.firstOrNull()), Screen.PdfTools.Rearrange(uris.firstOrNull()), Screen.PdfTools.Crop(uris.firstOrNull()), Screen.PdfTools.PageNumbers(uris.firstOrNull()), Screen.PdfTools.Watermark(uris.firstOrNull()), Screen.PdfTools.Signature(uris.firstOrNull()), Screen.PdfTools.Compress(uris.firstOrNull()), Screen.PdfTools.RemoveAnnotations(uris.firstOrNull()), Screen.PdfTools.Flatten(uris.firstOrNull()), Screen.PdfTools.Print(uris.firstOrNull()), Screen.PdfTools.Grayscale(uris.firstOrNull()), Screen.PdfTools.Repair(uris.firstOrNull()), Screen.PdfTools.Protect(uris.firstOrNull()), Screen.PdfTools.Unlock(uris.firstOrNull()), Screen.PdfTools.Metadata(uris.firstOrNull()), Screen.PdfTools.ExtractImages(uris.firstOrNull()), Screen.PdfTools.OCR(uris.firstOrNull()), Screen.PdfTools.ZipConvert(uris.firstOrNull()), ) } else { listOf(multiplePdf) } pdfScreens + filesAvailableScreens } } val singleImageScreens by remember(uris) { derivedStateOf { listOf( Screen.SingleEdit(uris.firstOrNull()), Screen.ResizeAndConvert(uris), Screen.FormatConversion(uris), Screen.WeightResize(uris), Screen.Crop(uris.firstOrNull()), Screen.Filter( type = Screen.Filter.Type.Basic(uris) ), Screen.Draw(uris.firstOrNull()), Screen.RecognizeText( Screen.RecognizeText.Type.Extraction(uris.firstOrNull()) ), Screen.EraseBackground(uris.firstOrNull()), Screen.Filter( type = Screen.Filter.Type.Masking(uris.firstOrNull()) ), Screen.AiTools(uris), Screen.MarkupLayers(uris.firstOrNull()), Screen.Watermarking(uris), Screen.ImageStitching(uris), Screen.ImageStacking(uris), Screen.ImageSplitting(uris.firstOrNull()), Screen.ImageCutter(uris), Screen.ScanQrCode(uriToAnalyze = uris.firstOrNull()), Screen.GradientMaker(uris), Screen.PdfTools.ImagesToPdf(uris), Screen.GifTools( Screen.GifTools.Type.ImageToGif(uris) ), Screen.Base64Tools(uris.firstOrNull()), Screen.ImagePreview(uris), Screen.PickColorFromImage(uris.firstOrNull()), Screen.PaletteTools(uris.firstOrNull()), Screen.AsciiArt(uris.firstOrNull()), Screen.ApngTools( Screen.ApngTools.Type.ImageToApng(uris) ), Screen.JxlTools( Screen.JxlTools.Type.ImageToJxl(uris) ), Screen.SvgMaker(uris), Screen.EditExif(uris.firstOrNull()), Screen.DeleteExif(uris), Screen.LimitResize(uris) ).let { list -> val mergedList = list + filesAvailableScreens val uri = uris.firstOrNull() if (uri.type("png")) { mergedList + Screen.ApngTools( Screen.ApngTools.Type.ApngToImage(uris.firstOrNull()) ) } else if (uri.type("jpg", "jpeg")) { mergedList + Screen.JxlTools( Screen.JxlTools.Type.JpegToJxl(uris) ) } else if (uri.type("jxl")) { mergedList + Screen.JxlTools( Screen.JxlTools.Type.JxlToJpeg(uris) ) + Screen.JxlTools( Screen.JxlTools.Type.JxlToImage(uris.firstOrNull()) ) } else if (uri.type("webp")) { mergedList + Screen.WebpTools( Screen.WebpTools.Type.WebpToImage(uris.firstOrNull()) ) } else mergedList } } } val multipleImageScreens by remember(uris) { derivedStateOf { mutableListOf( Screen.ResizeAndConvert(uris), Screen.WeightResize(uris), Screen.FormatConversion(uris), Screen.Filter( type = Screen.Filter.Type.Basic(uris) ), ).apply { add(Screen.ImageStitching(uris)) add(Screen.PdfTools.ImagesToPdf(uris)) if (uris.size == 2) add(Screen.Compare(uris)) if (uris.size in 1..20) { add(Screen.CollageMaker(uris)) } add(Screen.AiTools(uris)) add(Screen.GradientMaker(uris)) add( Screen.RecognizeText( Screen.RecognizeText.Type.WriteToFile(uris) ) ) add( Screen.RecognizeText( Screen.RecognizeText.Type.WriteToMetadata(uris) ) ) add( Screen.RecognizeText( Screen.RecognizeText.Type.WriteToSearchablePdf(uris) ) ) add(Screen.Watermarking(uris)) add( Screen.GifTools( Screen.GifTools.Type.ImageToGif(uris) ) ) add(Screen.ImageStacking(uris)) add(Screen.ImageCutter(uris)) add(Screen.ImagePreview(uris)) add(Screen.LimitResize(uris)) addAll(filesAvailableScreens) add(Screen.SvgMaker(uris)) var haveJpeg = false var haveJxl = false for (uri in uris) { if (uri.type("jpg", "jpeg")) { haveJpeg = true } else if (uri.type("jxl")) { haveJxl = true } if (haveJpeg && haveJxl) break } if (haveJpeg) { add( Screen.JxlTools( Screen.JxlTools.Type.JpegToJxl(uris) ) ) } else if (haveJxl) { add( Screen.JxlTools( Screen.JxlTools.Type.JxlToJpeg(uris) ) ) add( Screen.JxlTools( Screen.JxlTools.Type.JxlToImage(uris.firstOrNull()) ) ) } add( Screen.JxlTools( Screen.JxlTools.Type.ImageToJxl(uris) ) ) add( Screen.ApngTools( Screen.ApngTools.Type.ImageToApng(uris) ) ) add( Screen.WebpTools( Screen.WebpTools.Type.ImageToWebp(uris) ) ) add(Screen.DeleteExif(uris)) } } } val imageScreens by remember(uris) { derivedStateOf { if (uris.size == 1) singleImageScreens else multipleImageScreens } } val textAvailableScreens by remember(extraDataType) { derivedStateOf { val text = (extraDataType as? ExtraDataType.Text)?.text ?: "" listOf( Screen.ScanQrCode(text), Screen.LoadNetImage(text) ) } } val settingsState = LocalSettingsState.current val favoriteScreens = settingsState.favoriteScreenList val hiddenForShareScreens = settingsState.hiddenForShareScreens val screenOrder = settingsState.screenList return remember( favoriteScreens, extraDataType, uris, pdfAvailableScreens, audioAvailableScreens, imageScreens, hiddenForShareScreens ) { derivedStateOf { val baseScreens = when (extraDataType) { is ExtraDataType.Backup -> filesAvailableScreens is ExtraDataType.Text -> textAvailableScreens ExtraDataType.Audio -> audioAvailableScreens ExtraDataType.File -> filesAvailableScreens ExtraDataType.Gif -> gifAvailableScreens ExtraDataType.Pdf -> pdfAvailableScreens null -> imageScreens } val orderIndex = screenOrder.withIndex().associate { it.value to it.index } val favSet = favoriteScreens.toSet() val allScreens = baseScreens .sortedWith( compareByDescending { it.id in favSet } .thenBy { orderIndex[it.id] ?: Int.MAX_VALUE } ) allScreens.partition { it.id !in hiddenForShareScreens } } } } ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.value import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Counter import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import kotlin.math.pow import kotlin.math.roundToInt @Composable fun ValueDialog( roundTo: Int?, valueRange: ClosedFloatingPointRange, valueState: String, expanded: Boolean, onDismiss: () -> Unit, onValueUpdate: (Float) -> Unit ) { var value by remember(valueState, expanded) { mutableStateOf(valueState.trimTrailingZero()) } EnhancedAlertDialog( visible = expanded, modifier = Modifier.clearFocusOnTap(), onDismissRequest = onDismiss, icon = { Icon( imageVector = Icons.Outlined.Counter, contentDescription = null ) }, title = { Text( stringResource( R.string.value_in_range, valueRange.start.toString().trimTrailingZero(), valueRange.endInclusive.toString().trimTrailingZero() ) ) }, text = { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { OutlinedTextField( shape = ShapeDefaults.default, value = value, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), textStyle = MaterialTheme.typography.titleMedium.copy(textAlign = TextAlign.Center), maxLines = 1, onValueChange = { number -> value = number.filterDecimal() } ) } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onDismiss() onValueUpdate( (value.toFloatOrNull() ?: 0f).roundTo(roundTo).coerceIn(valueRange) ) }, ) { Text(stringResource(R.string.ok)) } } ) } fun String.filterDecimal(): String { var tempS = trim { it !in listOf( '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.', '-' ) } tempS = (if (tempS.firstOrNull() == '-') "-" else "").plus( tempS.replace("-", "") ) val temp = tempS.split(".") return when (temp.size) { 1 -> temp[0] 2 -> temp[0] + "." + temp[1] else -> { temp[0] + "." + temp[1] + temp.drop(2).joinToString("") } } } private fun Float.roundTo( digits: Int? = 2 ) = digits?.let { (this * 10f.pow(digits)).roundToInt() / (10f.pow(digits)) } ?: this ================================================ FILE: core/ui/src/main/kotlin/com/t8rin/imagetoolbox/core/ui/widget/value/ValueText.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.value import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerColor import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun ValueText( modifier: Modifier = Modifier.padding( top = 8.dp, end = 8.dp ), value: Number, enabled: Boolean = true, valueSuffix: String = "", customText: String? = null, onClick: (() -> Unit)?, backgroundColor: Color = MaterialTheme.colorScheme.secondaryContainer.copy( 0.25f ) ) { val text by remember(customText, value, valueSuffix) { derivedStateOf { customText ?: "${value.toString().trimTrailingZero()}$valueSuffix" } } val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = AutoCircleShape(), pressedShape = ButtonDefaults.pressedShape, interactionSource = interactionSource ) ProvideContainerDefaults( color = LocalContainerColor.current ) { AnimatedContent( targetState = text, transitionSpec = { fadeIn(tween(100)) togetherWith fadeOut(tween(100)) }, modifier = modifier .container( shape = shape, color = backgroundColor, resultPadding = 0.dp, autoShadowElevation = if (enabled) 0.7.dp else 0.dp ) ) { AutoSizeText( text = it, color = LocalContentColor.current.copy(0.5f), textAlign = TextAlign.Center, modifier = Modifier .then( if (onClick != null) { Modifier.hapticsClickable( enabled = enabled, onClick = onClick, interactionSource = interactionSource, indication = LocalIndication.current ) } else Modifier ) .padding(horizontal = 16.dp, vertical = 6.dp), lineHeight = 18.sp ) } } } ================================================ FILE: core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/content_pickers/DocumentScannerImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.content_pickers import android.app.Activity import androidx.activity.ComponentActivity import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResult import androidx.activity.result.IntentSenderRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions import com.google.mlkit.vision.documentscanner.GmsDocumentScanning import com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ScanResult import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity private class DocumentScannerImpl( private val context: ComponentActivity, private val scannerLauncher: ManagedActivityResultLauncher, private val onFailure: (Throwable) -> Unit ) : DocumentScanner { override fun scan() { val options = GmsDocumentScannerOptions.Builder() .setGalleryImportAllowed(true) .setResultFormats( GmsDocumentScannerOptions.RESULT_FORMAT_JPEG, GmsDocumentScannerOptions.RESULT_FORMAT_PDF ) .setScannerMode(GmsDocumentScannerOptions.SCANNER_MODE_FULL) .build() val scanner = GmsDocumentScanning.getClient(options) scanner.getStartScanIntent(context) .addOnSuccessListener { intentSender -> scannerLauncher.launch(IntentSenderRequest.Builder(intentSender).build()) } .addOnFailureListener(onFailure) } } @Composable internal fun rememberDocumentScannerImpl( onSuccess: (ScanResult) -> Unit ): DocumentScanner { val context = LocalComponentActivity.current val scannerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartIntentSenderForResult() ) { result -> if (result.resultCode == Activity.RESULT_OK) { runCatching { GmsDocumentScanningResult.fromActivityResultIntent(result.data)?.apply { onSuccess( ScanResult( imageUris = pages?.let { pages -> pages.map { it.imageUri } } ?: emptyList(), pdfUri = pdf?.uri ) ) } }.onFailure(AppToastHost::showFailureToast) } } return remember(context, scannerLauncher) { DocumentScannerImpl( context = context, scannerLauncher = scannerLauncher, onFailure = AppToastHost::showFailureToast ) } } ================================================ FILE: core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/utils/helper/ReviewHandlerImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.utils.helper import android.app.Activity import com.google.android.play.core.review.ReviewManagerFactory import com.t8rin.logger.makeLog internal object ReviewHandlerImpl : ReviewHandler() { override fun showReview(activity: Activity) { runCatching { ReviewManagerFactory.create(activity).let { reviewManager -> reviewManager .requestReviewFlow() .addOnCompleteListener { if (it.isSuccessful) { reviewManager.launchReviewFlow(activity, it.result) } } } }.onFailure { it.makeLog("showReview") } } } ================================================ FILE: core/ui/src/market/java/com/t8rin/imagetoolbox/core/ui/widget/sheets/UpdateSheetImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.ui.widget.sheets import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileDownloadOff import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import com.google.android.play.core.appupdate.AppUpdateManagerFactory import com.google.android.play.core.appupdate.AppUpdateOptions import com.google.android.play.core.install.model.AppUpdateType import com.google.android.play.core.install.model.UpdateAvailability import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.utils.getString @Composable internal fun UpdateSheetImpl( changelog: String, tag: String, visible: Boolean, onDismiss: () -> Unit ) { val context = LocalComponentActivity.current if (context.isInstalledFromPlayStore()) { LaunchedEffect(visible) { if (visible) { runCatching { val appUpdateManager = AppUpdateManagerFactory.create(context) val appUpdateInfoTask = appUpdateManager.appUpdateInfo appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) ) { appUpdateManager.startUpdateFlow( appUpdateInfo, context, AppUpdateOptions.defaultOptions(AppUpdateType.IMMEDIATE) ) } else { AppToastHost.showToast( icon = Icons.Rounded.FileDownloadOff, message = getString(R.string.no_updates) ) } } }.onFailure { AppToastHost.showToast( icon = Icons.Rounded.FileDownloadOff, message = getString(R.string.no_updates) ) } } } } else { DefaultUpdateSheet( changelog = changelog, tag = tag, visible = visible, onDismiss = onDismiss ) } } ================================================ FILE: core/utils/.gitignore ================================================ /build ================================================ FILE: core/utils/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) alias(libs.plugins.image.toolbox.hilt) } android.namespace = "com.t8rin.imagetoolbox.core.utils" dependencies { implementation(projects.core.domain) implementation(projects.core.resources) implementation(projects.core.settings) implementation(libs.logger) implementation(libs.androidx.documentfile) "marketImplementation"(libs.quickie.bundled) "fossImplementation"(libs.quickie.foss) implementation(libs.zxing.core) } ================================================ FILE: core/utils/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/AppContext.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.utils import android.content.Context import android.content.ContextWrapper import androidx.annotation.StringRes class AppContext private constructor( application: Context ) : ContextWrapper(application) { companion object { internal var appContext: AppContext? = null internal fun init(application: Context) { appContext = AppContext(application) } } } fun Context.initAppContext() = AppContext.init(this) val appContext: AppContext get() = checkNotNull(AppContext.appContext) { "AppContext not initialized" } fun getString(@StringRes resId: Int): String = appContext.getString(resId) fun getString( @StringRes resId: Int, vararg formatArgs: Any? ): String = appContext.getString(resId, *formatArgs) ================================================ FILE: core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/QrType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.utils import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.model.QrType.Wifi.EncryptionType import io.github.g00fy2.quickie.content.QRContent import io.github.g00fy2.quickie.content.QRContent.CalendarEvent.CalendarDateTime import io.github.g00fy2.quickie.content.QRContent.ContactInfo.Address.AddressType import io.github.g00fy2.quickie.content.QRContent.Email.EmailType import io.github.g00fy2.quickie.content.QRContent.Phone.PhoneType import io.github.g00fy2.quickie.extensions.DataType import java.util.Calendar import java.util.Date fun QRContent.toQrType(): QrType { val raw = rawValue ?: rawBytes?.toString(Charsets.UTF_8).orEmpty() if (raw.startsWith("geo:", true)) { val data = raw.drop(4).split(";") return QrType.Geo( raw = raw, latitude = data.getOrNull(0)?.toDoubleOrNull(), longitude = data.getOrNull(1)?.toDoubleOrNull() ) } return when (this) { is QRContent.Plain -> QrType.Plain(raw) is QRContent.Wifi -> QrType.Wifi( raw = raw, ssid = ssid, password = password, encryptionType = EncryptionType.entries.getOrNull(encryptionType - 1) ?: EncryptionType.OPEN ) is QRContent.Url -> QrType.Url( raw = raw, title = title, url = url ) is QRContent.Sms -> QrType.Sms( raw = raw, message = message, phoneNumber = phoneNumber ) is QRContent.GeoPoint -> QrType.Geo( raw = raw, latitude = lat, longitude = lng ) is QRContent.Email -> QrType.Email( raw = raw, address = address, body = body, subject = subject, type = type.toData() ) is QRContent.Phone -> QrType.Phone( raw = raw, number = number, type = type.toData() ) is QRContent.ContactInfo -> QrType.Contact( raw = raw, addresses = addresses.map { QrType.Contact.Address( addressLines = it.addressLines, type = it.type.toData() ) }, emails = emails.map { QrType.Email( raw = raw, address = it.address, body = it.body, subject = it.subject, type = it.type.toData() ) }, name = QrType.Contact.PersonName( first = name.first, formattedName = name.formattedName, last = name.last, middle = name.middle, prefix = name.prefix, pronunciation = name.pronunciation, suffix = name.suffix ), organization = organization, phones = phones.map { QrType.Phone( raw = raw, number = it.number, type = it.type.toData() ) }, title = title, urls = urls ) is QRContent.CalendarEvent -> QrType.Calendar( raw = raw, description = description, end = end.toDate(), location = location, organizer = organizer, start = start.toDate(), status = status, summary = summary ) } } private fun PhoneType.toData(): Int = when (this) { PhoneType.WORK -> DataType.TYPE_WORK PhoneType.HOME -> DataType.TYPE_HOME PhoneType.FAX -> DataType.TYPE_FAX PhoneType.MOBILE -> DataType.TYPE_MOBILE else -> DataType.TYPE_UNKNOWN } private fun AddressType.toData(): Int = when (this) { AddressType.WORK -> DataType.TYPE_WORK AddressType.HOME -> DataType.TYPE_HOME else -> DataType.TYPE_UNKNOWN } private fun EmailType.toData(): Int = when (this) { EmailType.WORK -> DataType.TYPE_WORK EmailType.HOME -> DataType.TYPE_HOME else -> DataType.TYPE_UNKNOWN } private fun CalendarDateTime.toDate(): Date { val cal = Calendar.getInstance().apply { set(Calendar.YEAR, year) set(Calendar.MONTH, month - 1) set(Calendar.DAY_OF_MONTH, day) set(Calendar.HOUR_OF_DAY, hours) set(Calendar.MINUTE, minutes) set(Calendar.SECOND, seconds) set(Calendar.MILLISECOND, 0) } return cal.time } ================================================ FILE: core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Typeface.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.utils import android.graphics.Typeface import androidx.core.content.res.ResourcesCompat import com.t8rin.imagetoolbox.core.settings.domain.model.FontType fun FontType?.toTypeface(): Typeface? = when (this) { null -> null is FontType.File -> Typeface.createFromFile(this.path) is FontType.Resource -> ResourcesCompat.getFont( appContext, this.resId ) } ================================================ FILE: core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Update.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.utils import com.t8rin.imagetoolbox.core.domain.utils.cast import com.t8rin.imagetoolbox.core.resources.BuildConfig import org.w3c.dom.Element import java.io.InputStream import javax.xml.parsers.DocumentBuilderFactory fun isNeedUpdate( updateName: String, allowBetas: Boolean ): Boolean { val currentName = BuildConfig.VERSION_NAME val betaList = listOf( "alpha", "beta", "rc" ) val currentVersionCodeString = currentName.toVersionCodeString(betaList) val updateVersionCodeString = updateName.toVersionCodeString(betaList) val maxLength = maxOf(currentVersionCodeString.length, updateVersionCodeString.length) val currentVersionCode = currentVersionCodeString.padEnd(maxLength, '0').toIntOrNull() ?: -1 val updateVersionCode = updateVersionCodeString.padEnd(maxLength, '0').toIntOrNull() ?: -1 return if (!updateName.startsWith(currentName)) { if (betaList.all { it !in updateName }) { updateVersionCode > currentVersionCode } else { if (allowBetas || betaList.any { it in currentName }) { updateVersionCode > currentVersionCode } else false } } else false } fun InputStream.parseChangelog(): Changelog { var tag = "" var changelog = "" val tree = DocumentBuilderFactory.newInstance() .newDocumentBuilder().parse(this) .getElementsByTagName("feed") repeat(tree.length) { val line = tree.item(it).cast() .getElementsByTagName("entry").item(0) .cast() tag = line.getElementsByTagName("title").item(0).textContent changelog = line.getElementsByTagName("content").item(0).textContent } return Changelog( tag = tag, changelog = changelog ) } data class Changelog( val tag: String, val changelog: String ) private fun String.toVersionCodeString(betaList: List): String { return replace( regex = Regex("0\\d"), transform = { it.value.replace("0", "") } ).replace("-", "") .replace(".", "") .replace("_", "") .let { version -> if (betaList.any { it in version }) version else version + "4" } .replace("alpha", "1") .replace("beta", "2") .replace("rc", "3") .replace("foss", "") .replace("jxl", "") } ================================================ FILE: core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/UriUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.utils import android.content.ContentResolver import android.content.ContentUris import android.content.Context import android.net.Uri import android.os.Build import android.provider.DocumentsContract import android.provider.MediaStore import android.provider.OpenableColumns import android.webkit.MimeTypeMap import androidx.core.net.toFile import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.SortType import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.sortedByKey import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.logger.makeLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import java.net.URLDecoder import java.net.URLEncoder import java.util.LinkedList import java.util.Locale fun Uri?.uiPath( default: String ): String = this?.let { uri -> if (DocumentFile.isDocumentUri(appContext, uri)) { DocumentFile.fromSingleUri(appContext, uri) } else { DocumentFile.fromTreeUri(appContext, uri) }?.uri?.path?.split(":") ?.lastOrNull()?.let { p -> val endPath = p.takeIf { it.isNotEmpty() }?.let { "/$it" } ?: "" val startPath = if ( uri.toString() .split("%")[0] .contains("primary") ) appContext.getString(R.string.device_storage) else appContext.getString(R.string.external_storage) startPath + endPath }?.decodeEscaped() } ?: default fun Uri.lastModified(): Long? = tryExtractOriginal().run { runCatching { if (this.toString().startsWith("file:///")) { return toFile().lastModified() } with(appContext.contentResolver) { val query = query(this@lastModified, null, null, null, null) query?.use { cursor -> if (cursor.moveToFirst()) { val columnNames = listOf( DocumentsContract.Document.COLUMN_LAST_MODIFIED, "datetaken", // When sharing an Image from Google Photos into the app. ) val millis = columnNames.firstNotNullOfOrNull { val index = cursor.getColumnIndex(it) if (!cursor.isNull(index)) { cursor.getLong(index) } else { null } } return millis } } } return DocumentFile.fromSingleUri(appContext, this)?.lastModified() }.onFailure { it.printStackTrace() } return null } fun Uri.path(): String? = tryExtractOriginal().run { getStringColumn(MediaStore.MediaColumns.DATA)?.takeIf { ".transforms" !in it }.orEmpty().ifEmpty { runCatching { val path = DocumentFile.fromSingleUri(appContext, this)?.uri?.path?.split(":") ?.lastOrNull() ?: return@run null if ("cloud" in path) { "/cloud/${path.substringAfterLast('/')}" } else { path } }.getOrNull() } } fun Uri.dateAdded(): Long? = tryExtractOriginal().run { getLongColumn(MediaStore.MediaColumns.DATE_ADDED)?.times(1000) } fun Uri.filename( context: Context = appContext ): String? = tryExtractOriginal().run { if (this.toString().startsWith("file:///")) { this.toString().takeLastWhile { it != '/' } } else { DocumentFile.fromSingleUri(context, this)?.name }?.decodeEscaped() } fun Uri.extension( context: Context = appContext ): String? { val filename = filename(context) ?: "" if (filename.endsWith(".qoi")) return "qoi" if (filename.endsWith(".jxl")) return "jxl" return if (ContentResolver.SCHEME_CONTENT == scheme) { MimeTypeMap.getSingleton() .getExtensionFromMimeType( context.contentResolver.getType(this) ) } else { MimeTypeMap.getFileExtensionFromUrl(this.toString()).lowercase(Locale.getDefault()) }?.replace(".", "") } fun Uri.fileSize(): Long? = tryExtractOriginal().run { if (this.scheme == "content") { runCatching { appContext.contentResolver .query(this, null, null, null, null, null) .use { cursor -> if (cursor != null && cursor.moveToFirst()) { val sizeIndex: Int = cursor.getColumnIndex(OpenableColumns.SIZE) if (!cursor.isNull(sizeIndex)) { return cursor.getLong(sizeIndex) } } } } } else { runCatching { return this.toFile().length() } } return null } fun Uri.tryExtractOriginal(): Uri = try { if ("com.android.externalstorage.documents" in this.toString()) { return this.makeLog("tryExtractOriginal") { "already ok - $it" } } val mimeType = getStringColumn(MediaStore.MediaColumns.MIME_TYPE).orEmpty() val contentUri = when { "image" in mimeType -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI "video" in mimeType -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI "audio" in mimeType -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI else -> return this } ContentUris.withAppendedId( contentUri, this.toString().decodeEscaped().substringAfterLast('/').filter { it.isDigit() }.toLong() ) } catch (e: Throwable) { e.makeLog("tryExtractOriginal") this.makeLog("tryExtractOriginal") { "failed - $it" } } suspend fun List.sortedByType( sortType: SortType, ): List = coroutineScope { when (sortType) { SortType.DateModified -> sortedByDateModified() SortType.DateModifiedReversed -> sortedByDateModified(descending = true) SortType.Name -> sortedByName() SortType.NameReversed -> sortedByName(descending = true) SortType.Size -> sortedBySize() SortType.SizeReversed -> sortedBySize(descending = true) SortType.MimeType -> sortedByMimeType() SortType.MimeTypeReversed -> sortedByMimeType(descending = true) SortType.Extension -> sortedByExtension() SortType.ExtensionReversed -> sortedByExtension(descending = true) SortType.DateAdded -> sortedByDateAdded() SortType.DateAddedReversed -> sortedByDateAdded(descending = true) } } fun ImageModel.toUri(): Uri? = when (data) { is Uri -> data as Uri is String -> (data as String).toUri() else -> null } fun Any.toImageModel() = ImageModel(this) fun String.toFileModel() = FileModel(this) fun String.decodeEscaped(): String = runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { URLDecoder.decode(URLDecoder.decode(this, Charsets.UTF_8), Charsets.UTF_8) } else { @Suppress("DEPRECATION") URLDecoder.decode(URLDecoder.decode(this)) } }.getOrDefault(this) fun String.encodeEscaped(): String { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { URLEncoder.encode(this, Charsets.UTF_8) } else { @Suppress("DEPRECATION") URLEncoder.encode(this) } } fun Uri.isApng(): Boolean { return filename().toString().endsWith(".png") .or(appContext.contentResolver.getType(this)?.contains("png") == true) .or(appContext.contentResolver.getType(this)?.contains("apng") == true) } fun Uri.isWebp(): Boolean { return filename().toString().endsWith(".webp") .or(appContext.contentResolver.getType(this)?.contains("webp") == true) } fun Uri.isJxl(): Boolean { return filename().toString().endsWith(".jxl") .or(appContext.contentResolver.getType(this)?.contains("jxl") == true) } fun Uri.isGif(): Boolean { return filename().toString().endsWith(".gif") .or(appContext.contentResolver.getType(this)?.contains("gif") == true) } suspend fun Uri.listFilesInDirectory(): List = listFilesInDirectoryAsFlowImpl().filterIsInstance().first().uris fun Uri.listFilesInDirectoryProgressive(): Flow = listFilesInDirectoryAsFlowImpl() .filterIsInstance() .map { it.uri } fun String?.getPath( context: Context = appContext ) = this?.takeIf { it.isNotEmpty() }?.toUri().uiPath( default = context.getString(R.string.default_folder) ) private fun Uri.listFilesInDirectoryAsFlowImpl(): Flow = channelFlow { val rootUri = this@listFilesInDirectoryAsFlowImpl var childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( rootUri, DocumentsContract.getTreeDocumentId(rootUri) ) val files: MutableList> = LinkedList() val dirNodes: MutableList = LinkedList() dirNodes.add(childrenUri) while (dirNodes.isNotEmpty()) { childrenUri = dirNodes.removeAt(0) appContext.contentResolver.query( childrenUri, arrayOf( DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_LAST_MODIFIED, DocumentsContract.Document.COLUMN_MIME_TYPE, ), null, null, null ).use { while (it!!.moveToNext()) { val docId = it.getString(0) val lastModified = it.getLong(1) val mime = it.getString(2) if (isDirectory(mime)) { val newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId) dirNodes.add(newNode) } else { val uri = DocumentsContract.buildDocumentUriUsingTree(rootUri, docId) channel.send(DirUri.Entry(uri)) files.add( uri to lastModified ) } } } } files.sortedByDescending { it.second }.map { it.first }.also { channel.send(DirUri.All(it)) channel.close() } }.flowOn(Dispatchers.IO) private sealed interface DirUri { data class Entry(val uri: Uri) : DirUri data class All(val uris: List) : DirUri } private fun isDirectory(mimeType: String): Boolean { return DocumentsContract.Document.MIME_TYPE_DIR == mimeType } private fun List.sortedByExtension( descending: Boolean = false ) = sortedByKey(descending) { it.filename()?.substringAfterLast( delimiter = '.', missingDelimiterValue = "" )?.lowercase() } private fun List.sortedByDateModified( descending: Boolean = false ) = sortedByKey(descending) { it.lastModified() } private fun List.sortedByName( descending: Boolean = false ) = sortedByKey(descending) { it.filename() } private fun List.sortedBySize( descending: Boolean = false ) = sortedByKey(descending) { it.getLongColumn(OpenableColumns.SIZE) } private fun List.sortedByMimeType( descending: Boolean = false ) = sortedByKey(descending) { it.getStringColumn( column = DocumentsContract.Document.COLUMN_MIME_TYPE ) } private fun List.sortedByDateAdded( descending: Boolean = false ) = sortedByKey(descending) { it.dateAdded() } private fun Uri.getLongColumn(column: String): Long? = appContext.contentResolver.query(this, arrayOf(column), null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { val index = cursor.getColumnIndex(column) if (index != -1 && !cursor.isNull(index)) cursor.getLong(index) else null } else null } private fun Uri.getStringColumn(column: String): String? = appContext.contentResolver.query(this, arrayOf(column), null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { val index = cursor.getColumnIndex(column) if (index != -1 && !cursor.isNull(index)) cursor.getString(index) else null } else null } ================================================ FILE: core/utils/src/main/java/com/t8rin/imagetoolbox/core/utils/Zip.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.core.utils import java.io.InputStream import java.io.OutputStream import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream inline fun OutputStream.createZip( block: (ZipOutputStream) -> T ): T = ZipOutputStream(this).use(block) fun ZipOutputStream.putEntry( name: String, input: InputStream ) { putNextEntry(ZipEntry(name)) input.use { it.copyTo(this) } closeEntry() } fun ZipOutputStream.putEntry( name: String, write: (ZipOutputStream) -> Unit ) { putNextEntry(ZipEntry(name)) write(this) closeEntry() } ================================================ FILE: fastlane/metadata/android/en-US/changelogs/13.txt ================================================ * Update strings.xml by @Ilithy in #11 * Redesign * Bug fixes ================================================ FILE: fastlane/metadata/android/en-US/changelogs/16.txt ================================================ ### What's new * Only cropping image * Color picking * Design improved * Bug fixes ================================================ FILE: fastlane/metadata/android/en-US/full_description.txt ================================================

✨ Image Toolbox ✨

Powerful all-in-one image editor, AI toolkit and format converter for creators and power users.

Note: This is only a partial list of features. Full list is available in the README.

Core Features

  • Batch processing
  • 310+ filters + custom filter chains (share via QR)
  • File encryption/decryption (100+ algorithms)
  • EXIF editing/deleting
  • Load images from the internet
  • Background removal (manual + AI models)
  • Markup layers (stickers, text, shapes)
  • Advanced drawing tools (pen, neon, highlighter, warp, pixelation, blur, spot healing)
  • Flexible resizing (adaptive, aspect ratio, limits, multiple algorithms)
  • Quality compression & size reduction

AI Tools

  • 81 ready AI models
  • Upscale, denoise, colorize, enhance

OCR (Text from Images)

  • 120+ languages
  • Fast / Standard / Best modes
  • Batch extraction
  • Write results to files or EXIF

Conversion & Media

  • HEIF, HEIC, AVIF, WEBP, JPEG, PNG, JXL, TIFF, QOI, ICO

PDF & Documents

  • PDF → Images
  • Images → PDF
  • Document scanning

Image Tools

  • Stitching, stacking, splitting
  • Batch cutting
  • Raster → SVG tracing
  • Collages (2–10 images, 180+ layouts)
  • Watermarking (text, image, timestamp, steganography)

Barcodes

  • Create & scan 13 formats (QR, Aztec, Data Matrix, EAN, Code 128, PDF417, UPC)
  • Share as images
================================================ FILE: fastlane/metadata/android/en-US/short_description.txt ================================================ Edit, create & convert images with powerful AI — your all-in-one studio ================================================ FILE: fastlane/metadata/android/ru/full_description.txt ================================================ Набор инструментов для работы с изображениями ✨ Возможности:
  • Пакетная обработка
  • Применение цепочек фильтров (более 45 различных фильтров)
  • AES-256 GCM No Padding шифрование файлов
  • Редактирование/удаление метаданных EXIF
  • Загрузка изображений из Интернета
  • Удаление фона
    • Путем рисования
    • Автоматически
  • Рисование на изображении/фоне
    • Ручка
    • Неон
    • Хайлайтер
  • Изменение размера изображения
    • Изменение ширины
    • Изменение высоты
    • Адаптивное изменение размера
    • Изменить размер с сохранением соотношения сторон
    • Изменить размер в заданных пределах
  • Уменьшить изображение
    • Сжатие по качеству
    • Сжатие по преднастройке
    • Уменьшение размера на заданный вес (в килобайтах)
  • Обрезка
    • Обычная обрезка
    • Обрезка по соотношению сторон
    • Обрезка с помощью маски формы
      • Закругленные углы
      • Срезанные углы
      • Овал
      • Прямоугольник
      • Восьмиугольник
      • Закругленный пятиугольник
      • Клевер
      • Звезда Давида
      • Логотип Kotlin
      • Сердце
      • Звезда
      • Маска изображения
  • Преобразование формата
    • HEIF
    • HEIC
    • AVIF
    • WEBP
    • JPEG
    • JPG
    • PNG
    • SVG, GIF в WEBP, PNG, JPEG, JPG, HEIF, HEIC, AVIF
    • Стикеры для Telegram в формате PNG
  • Цветовые утилиты
    • Создание палитры
    • Выбор цвета из изображения
  • Дополнительные возможности
    • Вращение
    • Переворачивание
    • Сравнение изображений
    • Предварительный просмотр SVG, GIF и в почти всех типов изображений
    • Сохранение в любую папку
================================================ FILE: fastlane/metadata/android/ru/short_description.txt ================================================ Фильтр, Растягивание, Сравнение, Обрезание - делайте с изображениями что угодно ================================================ FILE: feature/ai-tools/.gitignore ================================================ /build ================================================ FILE: feature/ai-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.ai_tools" dependencies { implementation(projects.lib.neuralTools) } ================================================ FILE: feature/ai-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AiProcessor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UNCHECKED_CAST") package com.t8rin.imagetoolbox.feature.ai_tools.data import ai.onnxruntime.OnnxJavaType import ai.onnxruntime.OnnxTensor import ai.onnxruntime.OrtEnvironment import ai.onnxruntime.OrtSession import ai.onnxruntime.TensorInfo import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Color import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.saving.track import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.feature.ai_tools.data.model.ChunkInfo import com.t8rin.imagetoolbox.feature.ai_tools.data.model.ModelInfo import com.t8rin.imagetoolbox.feature.ai_tools.domain.AiProgressListener import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralParams import com.t8rin.logger.makeLog import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream import java.lang.Float.floatToIntBits import java.lang.Float.intBitsToFloat import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.FloatBuffer import javax.inject.Inject import kotlin.math.ceil internal class AiProcessor @Inject constructor( @ApplicationContext private val context: Context, private val service: KeepAliveService, dispatchersHolder: DispatchersHolder, resourceManager: ResourceManager ) : DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { private val chunksDir: File get() = File(context.cacheDir, "processing_chunks").apply(File::mkdirs) suspend fun processImage( session: OrtSession, inputBitmap: Bitmap, model: NeuralModel, params: NeuralParams, listener: AiProgressListener ): Bitmap? = withContext(defaultDispatcher) { service.track( onCancel = { "Cancelled".makeLog("AiProcessor") clearChunks() }, onFailure = { e -> listener.onError( e.localizedMessage ?: e.message ?: getString(R.string.something_went_wrong) ) }, action = { processBitmapImpl( session = session, inputBitmap = inputBitmap, listener = object : AiProgressListener { override fun onError(error: String) { listener.onError(error) stop() } override fun onProgress(currentChunkIndex: Int, totalChunks: Int) { listener.onProgress( currentChunkIndex = currentChunkIndex, totalChunks = totalChunks ) if (totalChunks > 0) { updateProgress( done = currentChunkIndex, total = totalChunks ) } } }, info = ModelInfo( strength = params.strength, session = session, chunkSize = params.chunkSize, overlap = params.overlap, model = model, disableChunking = !params.enableChunking ) ) } ) } private suspend fun processBitmapImpl( session: OrtSession, inputBitmap: Bitmap, listener: AiProgressListener, info: ModelInfo, ): Bitmap = withContext(defaultDispatcher) { val width = inputBitmap.getWidth() val height = inputBitmap.getHeight() val processingConfig = Bitmap.Config.ARGB_8888 // Use the smaller of chunkSize or model's fixed dimensions val effectiveMaxChunkSize = if (info.isNonChunkable) { Int.MAX_VALUE } else { if (info.expectedWidth != null && info.expectedHeight != null) { minOf(info.chunkSize, info.expectedWidth, info.expectedHeight) } else { info.chunkSize } } if (width > effectiveMaxChunkSize || height > effectiveMaxChunkSize) { processTiled( session = session, inputBitmap = inputBitmap, listener = listener, info = info, config = processingConfig, maxChunkSize = effectiveMaxChunkSize ) } else { processChunk( session = session, chunk = inputBitmap.copy(processingConfig, true), config = processingConfig, info = info ) } } private suspend fun processTiled( session: OrtSession, inputBitmap: Bitmap, listener: AiProgressListener, info: ModelInfo, config: Bitmap.Config, maxChunkSize: Int ): Bitmap = withContext(defaultDispatcher) { ensureActive() val width = inputBitmap.width val height = inputBitmap.height val overlap = info.overlap val stride = maxChunkSize - overlap val cols = if (width <= maxChunkSize) 1 else ceil((width - overlap).toFloat() / stride).toInt() val rows = if (height <= maxChunkSize) 1 else ceil((height - overlap).toFloat() / stride).toInt() "Processing tiled: image=${width}x${height}, chunkSize=$maxChunkSize, stride=$stride, grid=${cols}x${rows}, overlap=$overlap".makeLog( "AiProcessor" ) val totalChunks = cols * rows val chunkInfoList = mutableListOf() "Phase 1: Extracting $totalChunks chunks to disk".makeLog("AiProcessor") var chunkIndex = 0 for (row in 0 until rows) { for (col in 0 until cols) { ensureActive() val chunkX = col * stride val chunkY = row * stride val chunkW = minOf(chunkX + maxChunkSize, width) - chunkX val chunkH = minOf(chunkY + maxChunkSize, height) - chunkY if (chunkW <= 0 || chunkH <= 0) continue val chunk = Bitmap.createBitmap(inputBitmap, chunkX, chunkY, chunkW, chunkH) val converted = if (chunk.config != config) { val temp = chunk.copy(config, true) chunk.recycle() temp } else { chunk } ensureActive() val inputChunkFile = File(chunksDir, "chunk_${chunkIndex}.png") val processedChunkFile = File(chunksDir, "chunk_${chunkIndex}_processed.png") withContext(Dispatchers.IO) { FileOutputStream(inputChunkFile).use { converted.compress(Bitmap.CompressFormat.PNG, 100, it) } } converted.recycle() chunkInfoList.add( ChunkInfo( index = chunkIndex, inputFile = inputChunkFile, processedFile = processedChunkFile, x = chunkX, y = chunkY, width = chunkW, height = chunkH, col = col, row = row ) ) chunkIndex++ } } "Saved ${chunkInfoList.size} chunks to ${chunksDir.absolutePath}".makeLog("AiProcessor") "Phase 2: Processing $totalChunks chunks".makeLog("AiProcessor") if (totalChunks > 1) { listener.onProgress(0, totalChunks) } for (chunkInfo in chunkInfoList) { ensureActive() val loadedChunk = BitmapFactory.decodeFile(chunkInfo.inputFile.absolutePath) val processed = processChunk( session = session, chunk = loadedChunk, config = config, info = info ) loadedChunk.recycle() withContext(ioDispatcher) { FileOutputStream(chunkInfo.processedFile).use { processed.compress(Bitmap.CompressFormat.PNG, 100, it) } chunkInfo.inputFile.delete() } ensureActive() processed.recycle() if (totalChunks > 1) { val nextChunkIndex = chunkInfo.index + 1 listener.onProgress(nextChunkIndex, totalChunks) } } val resultWidth = width * info.scaleFactor val resultHeight = height * info.scaleFactor val result = createBitmap(resultWidth, resultHeight, config) for (chunkInfo in chunkInfoList) { ensureActive() val loadedProcessed = withContext(ioDispatcher) { BitmapFactory.decodeFile(chunkInfo.processedFile.absolutePath) } ?: throw Exception("Failed to load processed chunk ${chunkInfo.index}") mergeChunkWithBlending( result = result, processedChunk = loadedProcessed, chunkInfo = chunkInfo, overlap = overlap, scaleFactor = info.scaleFactor ) loadedProcessed.recycle() } clearChunks() result } private suspend fun processChunk( session: OrtSession, chunk: Bitmap, config: Bitmap.Config, info: ModelInfo ): Bitmap = withContext(defaultDispatcher) { ensureActive() val originalW = chunk.width val originalH = chunk.height var w: Int var h: Int val minModelSize = info.minSpatialSize if (info.isScuNetColor || minModelSize > 256) { w = if (info.expectedWidth != null && info.expectedWidth > 0) { info.expectedWidth } else { val padFactor = 8 val paddedW = ((originalW + padFactor - 1) / padFactor) * padFactor maxOf(paddedW, minModelSize) } h = if (info.expectedHeight != null && info.expectedHeight > 0) { info.expectedHeight } else { val padFactor = 8 val paddedH = ((originalH + padFactor - 1) / padFactor) * padFactor maxOf(paddedH, minModelSize) } } else { w = if (info.expectedWidth != null && info.expectedWidth > 0) { info.expectedWidth } else { val padFactor = 8 ((originalW + padFactor - 1) / padFactor) * padFactor } h = if (info.expectedHeight != null && info.expectedHeight > 0) { info.expectedHeight } else { val padFactor = 8 ((originalH + padFactor - 1) / padFactor) * padFactor } } val needsPadding = w != originalW || h != originalH val paddedChunk = if (needsPadding) { "Padding chunk from ${originalW}x${originalH} to ${w}x${h}".makeLog("AiProcessor") val padded = createBitmap(w, h, config) val canvas = Canvas(padded) canvas.drawBitmap(chunk, 0f, 0f, null) if (w > originalW) { val rightStrip = Bitmap.createBitmap(chunk, originalW - 1, 0, 1, originalH) for (x in originalW until w) { ensureActive() canvas.drawBitmap(rightStrip, x.toFloat(), 0f, null) } rightStrip.recycle() } if (h > originalH) { val bottomStrip = Bitmap.createBitmap(padded, 0, originalH - 1, w, 1) for (y in originalH until h) { ensureActive() canvas.drawBitmap(bottomStrip, 0f, y.toFloat(), null) } bottomStrip.recycle() } padded } else { chunk } val hasAlpha = chunk.hasAlpha() val inputChannels = info.inputChannels val outputChannels = info.outputChannels val pixels = IntArray(w * h) paddedChunk.getPixels(pixels, 0, w, 0, 0, w, h) val inputArray = FloatArray(inputChannels * w * h) val alphaChannel = if (hasAlpha) FloatArray(w * h) else null for (i in 0 until w * h) { ensureActive() val color = pixels[i] if (inputChannels == 1) { val gray = (Color.red(color) + Color.green(color) + Color.blue(color)) / 3 inputArray[i] = gray / 255f } else { inputArray[i] = Color.red(color) / 255f inputArray[w * h + i] = Color.green(color) / 255f inputArray[2 * w * h + i] = Color.blue(color) / 255f } if (hasAlpha) { alphaChannel!![i] = Color.alpha(color) / 255f } } val env = OrtEnvironment.getEnvironment() val inputShape = longArrayOf(1, inputChannels.toLong(), h.toLong(), w.toLong()) val inputs = mutableMapOf() val inputTensor = if (info.isFp16) { val fp16Array = ShortArray(inputArray.size) { i -> floatToFloat16(inputArray[i]) } val byteBuffer = ByteBuffer.allocateDirect(fp16Array.size * 2).order(ByteOrder.nativeOrder()) val shortBuffer = byteBuffer.asShortBuffer() shortBuffer.put(fp16Array) byteBuffer.rewind() OnnxTensor.createTensor(env, byteBuffer, inputShape, OnnxJavaType.FLOAT16) } else { OnnxTensor.createTensor(env, FloatBuffer.wrap(inputArray), inputShape) } inputs[info.inputName] = inputTensor for ((key, nodeInfo) in info.inputInfoMap) { ensureActive() if (key == info.inputName) continue val tensorInfo = nodeInfo.info as? TensorInfo ?: continue if (tensorInfo.type == OnnxJavaType.FLOAT || tensorInfo.type == OnnxJavaType.FLOAT16) { val shape = tensorInfo.shape.clone() for (i in shape.indices) { ensureActive() if (shape[i] == -1L) shape[i] = 1L } if (shape.size == 2 && shape[0] == 1L && shape[1] == 1L) { ensureActive() val strengthTensor = if (tensorInfo.type == OnnxJavaType.FLOAT16) { val strengthFp16 = floatToFloat16(info.strength / 100f) val byteBuffer = ByteBuffer.allocateDirect(2).order(ByteOrder.nativeOrder()) byteBuffer.asShortBuffer().put(strengthFp16) byteBuffer.rewind() OnnxTensor.createTensor(env, byteBuffer, shape, OnnxJavaType.FLOAT16) } else { OnnxTensor.createTensor( env, FloatBuffer.wrap(floatArrayOf(info.strength / 100f)), shape ) } inputs[key] = strengthTensor } } } try { ensureActive() session.run(inputs).use { sessionResult -> val outputH = h * info.scaleFactor val outputW = w * info.scaleFactor val (outputArray, actualOutputChannels) = extractOutputArray( outputValue = sessionResult[0].value, channels = outputChannels, h = outputH, w = outputW ) val fullResultBitmap = createBitmap(width = outputW, height = outputH, config = config) val outPixels = IntArray(outputW * outputH) for (i in 0 until outputW * outputH) { ensureActive() val alpha = if (hasAlpha) { val srcY = ((i / outputW) / info.scaleFactor).coerceIn(0, h - 1) val srcX = ((i % outputW) / info.scaleFactor).coerceIn(0, w - 1) val srcIdx = srcY * w + srcX clamp255(alphaChannel!![srcIdx] * 255f) } else { 255 } if (actualOutputChannels == 1) { val gray = clamp255(outputArray[i] * 255f) outPixels[i] = Color.argb(alpha, gray, gray, gray) } else { val r = clamp255(outputArray[i] * 255f) val g = clamp255(outputArray[outputW * outputH + i] * 255f) val b = clamp255(outputArray[2 * outputW * outputH + i] * 255f) outPixels[i] = Color.argb(alpha, r, g, b) } } fullResultBitmap.setPixels(outPixels, 0, outputW, 0, 0, outputW, outputH) if (needsPadding) { val croppedW = originalW * info.scaleFactor val croppedH = originalH * info.scaleFactor val cropped = Bitmap.createBitmap(fullResultBitmap, 0, 0, croppedW, croppedH) fullResultBitmap.recycle() cropped } else { fullResultBitmap } } } finally { inputs.values.forEach { it.close() } if (needsPadding) { paddedChunk.recycle() } } } private suspend fun mergeChunkWithBlending( result: Bitmap, processedChunk: Bitmap, chunkInfo: ChunkInfo, overlap: Int, scaleFactor: Int ) = withContext(defaultDispatcher) { val width = processedChunk.width val height = processedChunk.height val x = chunkInfo.x * scaleFactor val y = chunkInfo.y * scaleFactor val scaledOverlap = overlap * scaleFactor val needsLeftBlend = chunkInfo.col > 0 val needsTopBlend = chunkInfo.row > 0 if (!needsLeftBlend && !needsTopBlend) { val canvas = Canvas(result) canvas.drawBitmap(processedChunk, x.toFloat(), y.toFloat(), null) return@withContext } val existingPixels = IntArray(width * height) try { result.getPixels(existingPixels, 0, width, x, y, width, height) } catch (_: Throwable) { val canvas = Canvas(result) canvas.drawBitmap(processedChunk, x.toFloat(), y.toFloat(), null) return@withContext } val newPixels = IntArray(width * height) processedChunk.getPixels(newPixels, 0, width, 0, 0, width, height) for (localY in 0 until height) { for (localX in 0 until width) { ensureActive() val inLeftOverlap = needsLeftBlend && localX < scaledOverlap val inTopOverlap = needsTopBlend && localY < scaledOverlap if (!inLeftOverlap && !inTopOverlap) continue val idx = localY * width + localX var blendFactor = 1.0f if (inLeftOverlap) { val t = (localX.toFloat() / (scaledOverlap - 1).coerceAtLeast(1)).coerceIn(0f, 1f) blendFactor = minOf(blendFactor, t * t * (3f - 2f * t)) } if (inTopOverlap) { val t = (localY.toFloat() / (scaledOverlap - 1).coerceAtLeast(1)).coerceIn(0f, 1f) blendFactor = minOf(blendFactor, t * t * (3f - 2f * t)) } val existingColor = existingPixels[idx] val newColor = newPixels[idx] val r = ((1 - blendFactor) * Color.red(existingColor) + blendFactor * Color.red(newColor)).toInt() val g = ((1 - blendFactor) * Color.green(existingColor) + blendFactor * Color.green( newColor )).toInt() val b = ((1 - blendFactor) * Color.blue(existingColor) + blendFactor * Color.blue( newColor )).toInt() val a = ((1 - blendFactor) * Color.alpha(existingColor) + blendFactor * Color.alpha( newColor )).toInt() newPixels[idx] = Color.argb(a, r, g, b) } } result.setPixels(newPixels, 0, width, x, y, width, height) } private suspend fun extractOutputArray( outputValue: Any, channels: Int, h: Int, w: Int ): Pair = withContext(defaultDispatcher) { ensureActive() "Output type received: ${outputValue.javaClass.name}".makeLog("AiProcessor") when (outputValue) { is FloatArray -> { "Output is FloatArray (FP32 or auto-converted from FP16)".makeLog("AiProcessor") outputValue to channels } is ShortArray -> { "Output is ShortArray (FP16) - converting to Float32".makeLog("AiProcessor") FloatArray(outputValue.size) { i -> float16ToFloat(outputValue[i]) } to channels } is Array<*> -> { try { val arr = outputValue as Array>> "Output is multi-dimensional FloatArray".makeLog("AiProcessor") val actualBatches = arr.size val actualChannels = arr[0].size val actualHeight = arr[0][0].size val actualWidth = arr[0][0][0].size val channelsToProcess = maxOf(channels, actualChannels) "Actual tensor shape: [$actualBatches, $actualChannels, $actualHeight, $actualWidth]".makeLog( "AiProcessor" ) "Requested extraction shape: [$channelsToProcess, $h, $w]".makeLog("AiProcessor") val out = FloatArray(channelsToProcess * h * w) for (ch in 0 until channelsToProcess) { for (y in 0 until h) { for (x in 0 until w) { ensureActive() out[ch * h * w + y * w + x] = arr[0][ch][y][x] } } } out to channelsToProcess } catch (e: Throwable) { try { val arr = outputValue as Array>> "Output is multi-dimensional ShortArray (FP16)".makeLog("AiProcessor") val actualBatches = arr.size val actualChannels = arr[0].size val actualHeight = arr[0][0].size val actualWidth = arr[0][0][0].size val channelsToProcess = maxOf(channels, actualChannels) "Actual tensor shape: [$actualBatches, $actualChannels, $actualHeight, $actualWidth]".makeLog( "AiProcessor" ) "Requested extraction shape: [$channelsToProcess, $h, $w]".makeLog("AiProcessor") val out = FloatArray(channelsToProcess * h * w) for (ch in 0 until channelsToProcess) { for (y in 0 until h) { for (x in 0 until w) { ensureActive() out[ch * h * w + y * w + x] = float16ToFloat(arr[0][ch][y][x]) } } } out to channelsToProcess } catch (e2: Throwable) { throw RuntimeException("Failed to extract output array: ${e.message}, ${e2.message}") } } } else -> throw RuntimeException("Unexpected ONNX output type: ${outputValue.javaClass}") } } private fun clamp255(v: Float): Int = v.toInt().coerceIn(0, 255) private fun floatToFloat16(value: Float): Short { val bits = floatToIntBits(value) val sign = (bits ushr 16) and 0x8000 val exponent = ((bits ushr 23) and 0xFF) - 127 + 15 var mantissa = bits and 0x7FFFFF if (exponent <= 0) { if (exponent < -10) { return sign.toShort() } mantissa = mantissa or 0x800000 mantissa = mantissa shr (1 - exponent) return (sign or (mantissa shr 13)).toShort() } else if (exponent >= 0x1F) { return (sign or 0x7C00).toShort() } return (sign or (exponent shl 10) or (mantissa shr 13)).toShort() } private fun float16ToFloat(fp16: Short): Float { val bits = fp16.toInt() and 0xFFFF val sign = (bits and 0x8000) shl 16 val exponent = (bits and 0x7C00) ushr 10 val mantissa = bits and 0x3FF if (exponent == 0) { if (mantissa == 0) { return intBitsToFloat(sign) } var e = -14 var m = mantissa while ((m and 0x400) == 0) { m = m shl 1 e-- } m = m and 0x3FF return intBitsToFloat(sign or ((e + 127) shl 23) or (m shl 13)) } else if (exponent == 0x1F) { return intBitsToFloat(sign or 0x7F800000 or (mantissa shl 13)) } return intBitsToFloat(sign or ((exponent - 15 + 127) shl 23) or (mantissa shl 13)) } private fun clearChunks() = chunksDir.deleteRecursively() } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/AndroidAiToolsRepository.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.data import ai.onnxruntime.OrtEnvironment import ai.onnxruntime.OrtException import ai.onnxruntime.OrtSession import android.content.Context import android.graphics.Bitmap import androidx.core.net.toUri import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import com.t8rin.imagetoolbox.core.data.image.utils.healAlpha import com.t8rin.imagetoolbox.core.data.saving.io.FileReadable import com.t8rin.imagetoolbox.core.data.saving.io.FileWriteable import com.t8rin.imagetoolbox.core.data.saving.io.UriReadable import com.t8rin.imagetoolbox.core.data.utils.computeFromReadable import com.t8rin.imagetoolbox.core.data.utils.observeHasChanges import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.remote.DownloadManager import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.track import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.ai_tools.domain.AiProgressListener import com.t8rin.imagetoolbox.feature.ai_tools.domain.AiToolsRepository import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralConstants import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralParams import com.t8rin.logger.makeLog import com.t8rin.neural_tools.bgremover.BgRemover import com.t8rin.neural_tools.bgremover.GenericBackgroundRemover import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import javax.inject.Inject internal class AndroidAiToolsRepository @Inject constructor( @ApplicationContext private val context: Context, private val dataStore: DataStore, private val appScope: AppScope, private val processor: AiProcessor, private val keepAliveService: KeepAliveService, dispatchersHolder: DispatchersHolder, resourceManager: ResourceManager, private val downloadManager: DownloadManager, private val fileController: FileController ) : AiToolsRepository, DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { init { appScope.launch { extractU2NetP() } } private var isProcessingImage = false private val modelsDir: File get() = File( context.filesDir, NeuralConstants.DIR ).apply(File::mkdirs) private val updateFlow: MutableSharedFlow = MutableSharedFlow() override val occupiedStorageSize: MutableStateFlow = MutableStateFlow(0) override val downloadedModels: StateFlow> = merge( modelsDir.observeHasChanges().debounce(100), updateFlow ).map { fetchDownloadedModels { files -> occupiedStorageSize.update { files.sumOf { it.length() } } }.sortedWith( compareBy( { NeuralModel.entries.indexOfFirst { e -> e.name == it.name } }, { it.title }, ) ) }.stateIn( scope = appScope, started = SharingStarted.Eagerly, initialValue = emptyList() ) override val selectedModel: StateFlow = combine( downloadedModels, dataStore.data ) { downloaded, data -> downloaded.find { it.name == data[SELECTED_MODEL] } }.stateIn( scope = appScope, started = SharingStarted.Eagerly, initialValue = null ) private var session: OrtSession? = null override fun downloadModel( model: NeuralModel ): Flow = channelFlow { ensureActive() if (model.name.contains("u2netp")) { extractU2NetP() selectModelForced(model) close() } else { downloadManager.download( url = model.downloadLink, onStart = { trySend( DownloadProgress( currentPercent = 0f, currentTotalSize = model.downloadSize ) ) }, onProgress = ::trySend, destinationPath = model.file.absolutePath, onFinish = { if (it == null) selectModelForced(model) close(it) } ) } }.flowOn(ioDispatcher) private suspend fun CoroutineScope.selectModelForced(model: NeuralModel) { updateFlow.emit(Unit) ensureActive() selectModel( model = model, forced = true ) model.asBgRemover()?.checkModel() } override suspend fun importModel( uri: String ): SaveResult = withContext(ioDispatcher) { val modelToImport = UriReadable( uri = uri.toUri(), context = context ) val modelChecksum = HashingType.SHA_256.computeFromReadable(modelToImport) val possibleModel = NeuralModel.entries.find { it.checksum == modelChecksum } val modelName = possibleModel?.name ?: uri.toUri().filename().orEmpty().ifEmpty { "imported_model_($modelChecksum).onnx" } val alreadyDownloaded = downloadedModels.value.find { it.checksum == modelChecksum } if (alreadyDownloaded != null) { selectModelForced(alreadyDownloaded) return@withContext SaveResult.Skipped } fileController.transferBytes( fromUri = uri, to = FileWriteable( File( modelsDir, modelName ).apply(File::createNewFile) ) ).also { selectModelForced( NeuralModel.Imported( name = modelName, checksum = modelChecksum ) ) } } override suspend fun processImage( image: Bitmap, listener: AiProgressListener, params: NeuralParams ): Bitmap? = withContext(defaultDispatcher) { "start processing".makeLog() val model = selectedModel.value when { model == null -> return@withContext listener.failedSession() model.type == NeuralModel.Type.REMOVE_BG -> { processImage { withClosedSession(listener) { model.asBgRemover()?.removeBackground(image = image)!!.healAlpha(image) } } } else -> { processImage { val ortSession = session.makeLog("Held session") ?: createSession(selectedModel.value).makeLog("New session") ?: return@withContext null.also { listener.onError(getString(R.string.failed_to_open_session)) } processor.processImage( session = ortSession, inputBitmap = image, params = params, listener = listener, model = selectedModel.value!! ) } } } } private suspend fun withClosedSession( listener: AiProgressListener, function: suspend () -> Bitmap? ): Bitmap? { closeSession() return keepAliveService.track( onFailure = { listener.onError(it.message ?: it::class.simpleName.orEmpty()) }, action = { function() } ) } private fun AiProgressListener.failedSession(): T? = null.also { onError(getString(R.string.failed_to_open_session)) } override suspend fun deleteModel(model: NeuralModel) = withContext(ioDispatcher) { model.file.delete() if (selectedModel.value?.name == model.name) selectModel(null) updateFlow.emit(Unit) } override fun cleanup() { BgRemover.closeAll() closeSession() System.gc() } override suspend fun selectModel( model: NeuralModel?, forced: Boolean ): Boolean = withContext(ioDispatcher) { if (isProcessingImage) return@withContext false if (!forced && model != null && downloadedModels.value.none { it.name == model.name }) return@withContext false if (model != null && model.name == selectedModel.value?.name) return@withContext false dataStore.edit { it[SELECTED_MODEL] = model?.name.orEmpty() } cleanup() return@withContext true } private fun createSession(model: NeuralModel?): OrtSession? { return runCatching { val modelName = model?.name.orEmpty() val options = OrtSession.SessionOptions().apply { val processors = Runtime.getRuntime().availableProcessors() try { setIntraOpNumThreads(if (processors <= 2) 1 else (processors * 3) / 4) } catch (e: OrtException) { "Error setting IntraOpNumThreads: ${e.message}".makeLog("ModelManager") } try { setInterOpNumThreads(4) } catch (e: OrtException) { "Error setting InterOpNumThreads: ${e.message}".makeLog("ModelManager") } try { when { modelName.endsWith(".ort") -> { // prevent double optimizations (.ort models are already optimized) setOptimizationLevel( OrtSession.SessionOptions.OptLevel.NO_OPT ) } modelName.startsWith("fbcnn_") -> setOptimizationLevel( OrtSession.SessionOptions.OptLevel.EXTENDED_OPT ) modelName.startsWith("scunet_") -> setOptimizationLevel( OrtSession.SessionOptions.OptLevel.NO_OPT ) } } catch (e: OrtException) { "Error setting OptimizationLevel: ${e.message}".makeLog("ModelManager") } } OrtEnvironment.getEnvironment() .createSession((model ?: return null).file.absolutePath, options) .also { session = it } }.onFailure { e -> e.makeLog("createSession") model?.let { appScope.launch { deleteModel(it) } } }.getOrNull() } private fun closeSession() { session?.close() session = null } private suspend fun fetchDownloadedModels( onGetFiles: (List) -> Unit ) = withContext(ioDispatcher) { modelsDir.listFiles().orEmpty().toList().filter { !it.name.orEmpty().endsWith(".tmp") && !it.name.isNullOrEmpty() && it.length() > 0 }.also(onGetFiles).mapNotNull { val name = it.name if (name.isNullOrEmpty() || it.length() <= 0) return@mapNotNull null NeuralModel.find(name) ?: NeuralModel.Imported( name = name, checksum = HashingType.SHA_256.computeFromReadable(FileReadable(it)) ) } } private val NeuralModel.file: File get() = File(modelsDir, name) private fun NeuralModel.asBgRemover(): GenericBackgroundRemover? { return BgRemover.getRemover( when { name.startsWith("u2netp") -> BgRemover.Type.U2NetP name.startsWith("u2net") -> BgRemover.Type.U2Net name.startsWith("inspyrenet") -> BgRemover.Type.InSPyReNet name.startsWith("RMBG_1.4") -> BgRemover.Type.RMBG1_4 name.startsWith("birefnet_swin_tiny") -> BgRemover.Type.BiRefNetTiny name.startsWith("isnet") -> BgRemover.Type.ISNet else -> return null } ) } private suspend fun extractU2NetP() { //Extraction from assets BgRemover.downloadModel(BgRemover.Type.U2NetP).collect() } private inline fun processImage(action: () -> T): T = try { isProcessingImage = true action() } finally { isProcessingImage = false } } private val SELECTED_MODEL = stringPreferencesKey("SELECTED_MODEL") ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ChunkInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.data.model import java.io.File internal data class ChunkInfo( val index: Int, val inputFile: File, val processedFile: File, val x: Int, val y: Int, val width: Int, val height: Int, val col: Int, val row: Int ) ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/data/model/ModelInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.data.model import ai.onnxruntime.NodeInfo import ai.onnxruntime.OnnxJavaType import ai.onnxruntime.OrtSession import ai.onnxruntime.TensorInfo import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import com.t8rin.logger.makeLog import kotlin.math.abs internal class ModelInfo( val strength: Float, val overlap: Int, chunkSize: Int, disableChunking: Boolean, session: OrtSession, model: NeuralModel ) { val inputName: String val inputInfoMap: Map = session.inputInfo val inputChannels: Int val outputChannels: Int val isFp16: Boolean val expectedWidth: Int? val expectedHeight: Int? val isScuNet = model.name.startsWith("scunet_") val isScuNetColor = model.name.startsWith("scunet_color") val isNonChunkable = model.isNonChunkable || disableChunking val chunkSize = if (isScuNet) { minOf(chunkSize, 256) } else { chunkSize } val minSpatialSize = getMinSpatialSize(model.name) val scaleFactor: Int = scaleMap.entries.find { model.name.contains(it.key) }?.value ?: 1 init { "Initialized with chunkSize: $chunkSize, overlap: $overlap" .makeLog("ModelInfo") var foundInputName: String? = null var foundInputChannels = 3 var foundOutputChannels = 3 var foundIsFp16 = false var foundExpectedWidth: Int? = null var foundExpectedHeight: Int? = null for ((key, nodeInfo) in inputInfoMap) { val tensorInfo = nodeInfo.info as? TensorInfo ?: continue val shape = tensorInfo.shape if ((tensorInfo.type == OnnxJavaType.FLOAT || tensorInfo.type == OnnxJavaType.FLOAT16) && shape.size == 4) { foundInputName = key foundInputChannels = if (shape[1] == 1L) 1 else 3 foundIsFp16 = (tensorInfo.type == OnnxJavaType.FLOAT16) if (shape[2] > 0) foundExpectedHeight = shape[2].toInt() if (shape[3] > 0) foundExpectedWidth = shape[3].toInt() break } } val outputInfoMap = session.outputInfo for ((_, nodeInfo) in outputInfoMap) { val tensorInfo = nodeInfo.info as? TensorInfo ?: continue val shape = tensorInfo.shape if ((tensorInfo.type == OnnxJavaType.FLOAT || tensorInfo.type == OnnxJavaType.FLOAT16) && shape.size == 4) { foundOutputChannels = if (isScuNet) { if (shape[1] == 1L) 1 else 3 } else { if (abs(shape[1]) == 1L) 1 else 3 } break } } inputName = foundInputName ?: throw RuntimeException("Could not find valid input tensor") inputChannels = foundInputChannels outputChannels = foundOutputChannels isFp16 = foundIsFp16 expectedWidth = foundExpectedWidth expectedHeight = foundExpectedHeight "Model input type: ${if (isFp16) "FP16" else "FP32"}, input channels: $inputChannels, output channels: $outputChannels, expected dimensions: ${expectedWidth ?: "dynamic"}x${expectedHeight ?: "dynamic"}, scaleFactor: $scaleFactor" .makeLog("ModelInfo") } } private val scaleMap = buildMap { repeat(16) { val scale = it + 1 put("x$scale", scale) put("${scale}x", scale) } } private val minSpatial = mapOf( "nafnet" to 512 ) private fun getMinSpatialSize(modelName: String?): Int { val normalized = modelName?.lowercase() ?: return 256 for ((pattern, size) in minSpatial) { if (normalized.contains(pattern)) { return size } } return 256 } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/di/AiToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.ai_tools.data.AndroidAiToolsRepository import com.t8rin.imagetoolbox.feature.ai_tools.domain.AiToolsRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface AiToolsModule { @Binds @Singleton fun repository(impl: AndroidAiToolsRepository): AiToolsRepository } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiProgressListener.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.domain interface AiProgressListener { fun onError(error: String) fun onProgress(currentChunkIndex: Int, totalChunks: Int) } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/AiToolsRepository.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.domain import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralParams import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface AiToolsRepository { val occupiedStorageSize: StateFlow val downloadedModels: StateFlow> val selectedModel: StateFlow suspend fun selectModel( model: NeuralModel?, forced: Boolean = false ): Boolean fun downloadModel( model: NeuralModel ): Flow suspend fun importModel( uri: String ): SaveResult suspend fun processImage( image: Image, listener: AiProgressListener, params: NeuralParams ): Image? suspend fun deleteModel(model: NeuralModel) fun cleanup() } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralConstants.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.domain.model internal object NeuralConstants { const val DIR = "ai_models" } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralModel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.feature.ai_tools.domain.model import com.t8rin.imagetoolbox.core.domain.HF_BASE_URL import com.t8rin.imagetoolbox.core.resources.R data class NeuralModel( val downloadLink: String, val name: String = downloadLink.substringAfterLast("/"), val title: String, val description: Int?, val type: Type?, val speed: Speed?, val downloadSize: Long, val checksum: String ) { val supportsStrength = name.contains("fbcnn_", true) val isImported = downloadLink == "imported" val isNonChunkable = name.contains("ddcolor") || type == Type.REMOVE_BG val pointerLink: String = downloadLink.replace("/resolve/", "/blob/") enum class Type { UPSCALE, REMOVE_BG, COLORIZE, DE_JPEG, DENOISE, ARTIFACTS, ENHANCE, ANIME, SCANS } sealed interface Speed { val speedValue: Float fun clone(value: Float): Speed = when (this) { is Fast -> copy(speedValue = value) is Normal -> copy(speedValue = value) is Slow -> copy(speedValue = value) is VeryFast -> copy(speedValue = value) is VerySlow -> copy(speedValue = value) } data class VeryFast(override val speedValue: Float) : Speed data class Fast(override val speedValue: Float) : Speed data class Normal(override val speedValue: Float) : Speed data class Slow(override val speedValue: Float) : Speed data class VerySlow(override val speedValue: Float) : Speed companion object { val entries by lazy { listOf( VeryFast(0f), Fast(0f), Normal(0f), Slow(0f), VerySlow(0f) ) } } } companion object { val entries: List by lazy { listOf( NeuralModel( downloadLink = res("onnx/enhance/fbcnn/fbcnn_color_fp16.onnx"), title = "FBCNN Color", description = R.string.model_fbcnn_color_fp16, type = Type.DE_JPEG, downloadSize = 143910675, speed = Speed.Fast(2.003f), checksum = "1a678ff4f721b557fd8a7e560b99cb94ba92f201545c7181c703e7808b93e922" ), NeuralModel( downloadLink = res("onnx/enhance/fbcnn/fbcnn_grey_fp16.onnx"), title = "FBCNN Grayscale", description = R.string.model_fbcnn_gray_fp16, type = Type.DE_JPEG, downloadSize = 143903294, speed = Speed.VeryFast(1.992f), checksum = "e220b9637a9f2c34a36c98b275b2c9d2b9c2c029e365be82111072376afbec54" ), NeuralModel( downloadLink = res("onnx/enhance/fbcnn/fbcnn_gray_double_fp16.onnx"), title = "FBCNN Grayscale Strong", description = R.string.model_fbcnn_gray_double_fp16, type = Type.DE_JPEG, downloadSize = 143903294, speed = Speed.VeryFast(1.934f), checksum = "17feadd8970772f5ff85596cb9fb152ae3c2b82bca4deb52a7c8b3ecb2f7ac14" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_color-GAN.onnx"), title = "SCUNet Color GAN", description = R.string.model_scunet_color_gan_fp16, type = Type.DENOISE, downloadSize = 91264256, speed = Speed.Fast(2.715f), checksum = "79ae6073c91c2d25d1f199137a67c8d0f0807df27219cdd7d890f3cc6d5b43e7" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_color-PSNR.onnx"), title = "SCUNet Color PSNR", description = R.string.model_scunet_color_psnr_fp16, type = Type.DENOISE, downloadSize = 91264256, speed = Speed.Fast(2.633f), checksum = "b0f8c12f1575bb49e39a85924152f1c6d4b527a4aae0432c9e5c7397123465e3" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_gray_15_fp16.onnx"), title = "SCUNet Grayscale 15", description = R.string.model_scunet_gray_15_fp16, type = Type.DENOISE, downloadSize = 37741895, speed = Speed.Fast(3.048f), checksum = "8e8740cea4306c9a61215194f315e5c0dc9e06c726a9ddea77d978d804da7663" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_gray_25_fp16.onnx"), title = "SCUNet Grayscale 25", description = R.string.model_scunet_gray_25_fp16, type = Type.DENOISE, downloadSize = 37741895, speed = Speed.Fast(3.033f), checksum = "dec631fbdca7705bbff1fc779cf85a657dcb67f55359c368464dd6e734e1f2b7" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_gray_50_fp16.onnx"), title = "SCUNet Grayscale 50", description = R.string.model_scunet_gray_50_fp16, type = Type.DENOISE, downloadSize = 37741895, speed = Speed.Fast(3.058f), checksum = "48b7d07229a03d98b892d2b33aa4c572ea955301772e7fcb5fd10723552a1874" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_color_15_fp16.onnx"), title = "SCUNet Color 15", description = R.string.model_scunet_color_15_fp16, type = Type.DENOISE, downloadSize = 42555584, speed = Speed.Fast(7.095f), checksum = "25a3a07de278867df9d29e9d08fe555523bb0f9f78f8956c4af943a4eeb8c934" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_color_25_fp16.onnx"), title = "SCUNet Color 25", description = R.string.model_scunet_color_25_fp16, type = Type.DENOISE, downloadSize = 42555584, speed = Speed.Fast(6.994f), checksum = "34d25ec2187d24f9f25b9dc9d918e94e87217c129471adda8c9fdf2e5a1cb62a" ), NeuralModel( downloadLink = res("onnx/enhance/scunet/scunet_color_50_fp16.onnx"), title = "SCUNet Color 50", description = R.string.model_scunet_color_50_fp16, type = Type.DENOISE, downloadSize = 42555584, speed = Speed.Normal(7.229f), checksum = "1c6bdc6d9e0c1dea314cf22d41c261d4c744bf0ae1ae6c59b9505c4b4d50febb" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/nanomodels/1x-AnimeUndeint-Compact-fp16.onnx"), title = "Anime Undeint", description = R.string.model_anime_undeint, type = Type.ANIME, downloadSize = 2391605, speed = Speed.VeryFast(0.497f), checksum = "e2927fe5c09ad61975bfa52f7a879e3cf044a190d5b25f147a363540cd00ccd3" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/nanomodels/1x-BroadcastToStudio_Compact-fp16.onnx"), title = "Broadcast To Studio", description = R.string.model_broadcast, type = Type.ARTIFACTS, downloadSize = 1200682, speed = Speed.VeryFast(0.625f), checksum = "52836c782140058bcc695e90102c3ef54961ebab2c12e66298eaba25d42570bc" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/nanomodels/1x-RGB-max-Denoise-fp16.onnx"), title = "RGB Max Denoise", description = R.string.model_rgb_max_denoise_fp16, type = Type.DENOISE, downloadSize = 310212, speed = Speed.VeryFast(0.172f), checksum = "1bb02e6444f306fd8ad17758ed474f6e21b909cceda1cba9606b6e923f65a102" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/nanomodels/1x-WB-Denoise-fp16.onnx"), title = "WB Denoise", description = R.string.model_wb_denoise, type = Type.DENOISE, downloadSize = 310212, speed = Speed.VeryFast(0.177f), checksum = "50978ca2777b5720d1d57c8f284d5274c96746c8721c3c2fdccb1dfcea823af1" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/nanomodels/1x-span-anime-pretrain-fp16.onnx"), title = "SPAN Anime Pretrain", description = R.string.model_span_anime_pretrain, type = Type.ANIME, downloadSize = 825768, speed = Speed.VeryFast(0.399f), checksum = "1311da8ad10af1d763b6b22797150429b377f36dda1fb26af5e3ec9bcc2701d2" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/nanomodels/1xBook-Compact-fp16.onnx"), title = "Book Scan", description = R.string.model_book_scan, type = Type.SCANS, downloadSize = 1200682, speed = Speed.VeryFast(0.452f), checksum = "7305ec3a592c5209fc2887d1f12d9b79163fca37020a719f55c83344effdb3c0" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/nanomodels/1xOverExposureCorrection_compact-fp16.onnx"), title = "Overexposure Correction", description = R.string.model_overexposure, type = Type.ENHANCE, downloadSize = 2391605, speed = Speed.VeryFast(0.492f), checksum = "8fef8e73062d2eff56aacea17caa1162b094a8c8a8010f51084c7e2cf9403ded" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-Anti-Aliasing-fp16.onnx"), title = "Anti-Aliasing", description = R.string.model_antialias, type = Type.ARTIFACTS, downloadSize = 33456842, speed = Speed.Slow(14.806f), checksum = "acd4f12a59ec606772df496f422e27099d629316671b41793b7362e6e13fe8dd" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_ColorizerV2_22000G-fp16.onnx"), title = "Colorizer", description = R.string.model_colorizer, type = Type.COLORIZE, downloadSize = 33456842, speed = Speed.Slow(15.125f), checksum = "c42520288f29a61d85107439ffea4a755129cb2f3eddfabb396598dc5d867f40" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_DeSharpen-fp16.onnx"), title = "DeSharpen", description = R.string.model_desharpen, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.VerySlow(15.593f), checksum = "3c08f894e9b05bba52f3b10cf1fb712a2370a5f352b88c3cdb246a3c295c6531" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_DeEdge-fp16.onnx"), title = "DeEdge", description = R.string.model_deedge, type = Type.ARTIFACTS, downloadSize = 33456842, speed = Speed.Normal(14.099f), checksum = "ead4873ec5f6870343e7dbe121f3054335941b12cf6fa4d0b54010c2cc3c0675" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_GainresV4-fp16.onnx"), title = "GainRes", description = R.string.model_gainres, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.Normal(14.415f), checksum = "a23d8157c6f809492ebbdd42632b865ce3e6c360221e7195aecc519ce610f3ac" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-DeBink-v4.onnx"), title = "DeBink v4", description = R.string.model_debink_v4, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.Normal(12.947f), checksum = "be52e251dff5a0c4504c559bb84d1b611c2e809edc99f1c261db646cd89a12fb" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-DeBink-v5.onnx"), title = "DeBink v5", description = R.string.model_debink_v5, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.Normal(13.597f), checksum = "f545222af1655b96f5b2aa3d46c40d8f256937cd2cbc2f77a6f89f29abf01e46" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-DeBink-v6.onnx"), title = "DeBink v6", description = R.string.model_debink_v6, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.Normal(13.789f), checksum = "e1c216804795a061218aa29617db52b91fb741c92bb00691da7caf9a64ef4f18" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-KDM003-scans-fp16.onnx"), title = "KDM003 Scans", description = R.string.model_kdm003_scans, type = Type.SCANS, downloadSize = 33456842, speed = Speed.Slow(14.948f), checksum = "85c740d5bdf8aa714b5b53eea1aa67c4c9e5483652702a31779a10f7a2ec9e45" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-NMKD-Jaywreck3-Lite-fp16.onnx"), title = "NMKD Jaywreck3 Lite", description = R.string.model_nmkd_jaywreck3_lite, type = Type.ENHANCE, downloadSize = 10114140, speed = Speed.Fast(4.896f), checksum = "4f72d8532e0632e87bcb0d03ff611bdac348e615873bb7c990c6291b0f08a495" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-SpongeColor-Lite-fp16.onnx"), title = "SpongeColor Lite", description = R.string.model_spongecolor_lite, type = Type.COLORIZE, downloadSize = 10112988, speed = Speed.Fast(4.731f), checksum = "c0b9df99d0cb0857eb96e0f8fb718c9010c2eeab9c30c17b0deacee79e2d2851" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-cinepak-fp16.onnx"), title = "Cinepak", description = R.string.model_cinepak, type = Type.ARTIFACTS, downloadSize = 33456842, speed = Speed.VerySlow(15.515f), checksum = "55cd3df1d3700dd31f09309beedce65f4d178d4b6e7777ecce05818bb60a2c67" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_BCGone-DetailedV2_40-60_115000_G-fp16.onnx"), title = "BCGone Detailed V2", description = R.string.model_bcgone_detailed_v2, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.Normal(14.694f), checksum = "a5237ef67f250871626020af3fc817bb6647888886cfc5b82a8e49718e52d3f4" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_BCGone_Smooth_110000_G-fp16.onnx"), title = "BCGone Smooth", description = R.string.model_bcgone_smooth, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.VerySlow(15.44f), checksum = "597ac90e1cc3105631b3a06c354cf055f1bccdfc440e4fdfe069918d228c3cdf" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_Bandage-Smooth-fp16.onnx"), title = "Bandage Smooth", description = R.string.model_bandage_smooth, type = Type.ARTIFACTS, downloadSize = 33456842, speed = Speed.Slow(15.176f), checksum = "40f98955aba23e2360b7f9f2e800e896be0d6a8071a3ff0a4bbb6cf24cd8656d" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_Bendel_Halftone-fp32.onnx"), title = "Bendel Halftone", description = R.string.model_bendel_halftone, type = Type.ARTIFACTS, downloadSize = 8660947, speed = Speed.Fast(4.019f), checksum = "6982ab0c770ac4c210dd6df435c96aca609dfbec8745d8ff6c57db6bae88eead" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_DitherDeleterV3-Smooth-fp16.onnx"), title = "Dither Deleter V3 Smooth", description = R.string.model_dither_deleter_v3_smooth, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.VerySlow(15.618f), checksum = "55b0189c77caa8aa848d8b4c1dbf737a265d2a22f3ad0ea3805d9d6fc0881b3b" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_JPEGDestroyerV2_96000G-fp16.onnx"), title = "JPEG Destroyer V2", description = R.string.model_jpeg_destroyer_v2, type = Type.DE_JPEG, downloadSize = 33456842, speed = Speed.Slow(14.9f), checksum = "96734e021e1b616ab3e74376244895bbeed118e454e051a548f5b98a113305c9" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_NMKD-h264Texturize-fp16.onnx"), title = "NMKD H264 Texturize", description = R.string.model_nmkd_h264_texturize, type = Type.ARTIFACTS, downloadSize = 33456842, speed = Speed.Slow(14.886f), checksum = "4291323f8f4eee0623d9c01416cb8f918b61285b6e4f4112f0dfc341c0f39397" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/VHS-Sharpen-1x_46000_G-fp16.onnx"), title = "VHS Sharpen", description = R.string.model_vhs_sharpen, type = Type.ENHANCE, downloadSize = 33456842, speed = Speed.Slow(15.192f), checksum = "6d69cc4fb4fdcc34dd779a95032ddf5f32e99d5399c9dffcd557c0c3859ab866" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_artifacts_dithering_alsa-fp16.onnx"), title = "Artifacts Dithering ALSA", description = R.string.model_artifacts_dithering_alsa, type = Type.ARTIFACTS, downloadSize = 33421271, speed = Speed.VerySlow(15.392f), checksum = "668bf366457c774e10cb46a756bf9744165c53671180969d4bab47ce935cd520" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_NMKD-BrightenRedux_200k-fp16.onnx"), title = "NMKD Brighten Redux", description = R.string.model_nmkd_brighten_redux, type = Type.ENHANCE, downloadSize = 33421271, speed = Speed.Slow(14.863f), checksum = "93f50904fa78948da69ff541b42fd9ce3e0fa096d87ca22f1f2f0ebb929f1c76" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_nmkdbrighten_10000_G-fp16.onnx"), title = "NMKD Brighten", description = R.string.model_nmkd_brighten, type = Type.ENHANCE, downloadSize = 33421271, speed = Speed.Slow(14.803f), checksum = "e1c354370c04bc0433bdb54fc81898c1ade0c2db02cc02878a58aac6195c7f52" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_NMKDDetoon_97500_G-fp16.onnx"), title = "NMKD Detoon", description = R.string.model_nmkd_detoon, type = Type.ENHANCE, downloadSize = 33421271, speed = Speed.Slow(14.817f), checksum = "b75acf61a6067777dc591cb31c77a79fb73db3b72468796d69ceec73e58ac32d" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_NoiseToner-Poisson-Detailed_108000_G-fp16.onnx"), title = "Noise Toner Poisson Detailed", description = R.string.model_noise_toner_poisson_detailed, type = Type.DENOISE, downloadSize = 33421271, speed = Speed.Normal(14.463f), checksum = "ed6bb382f71385032df8fcf16ea0a1d0469de801338ed32abcc5a01557944460" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_NoiseToner-Poisson-Soft_101000_G-fp16.onnx"), title = "Noise Toner Poisson Soft", description = R.string.model_noise_toner_poisson_soft, type = Type.DENOISE, downloadSize = 33421271, speed = Speed.Normal(14.606f), checksum = "e4d18cc399e4fbf2b5239b00b640c8ff0e80811bf88ded7f151eec6760eb0471" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_NoiseToner-Uniform-Detailed_100000_G-fp16.onnx"), title = "Noise Toner Uniform Detailed", description = R.string.model_noise_toner_uniform_detailed, type = Type.DENOISE, downloadSize = 33421271, speed = Speed.Normal(14.802f), checksum = "e5602a5e1aabb8ec6ee98969d834224ac1b4b366f89a4ec26b3fae93077a4dbf" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_NoiseToner-Uniform-Soft_100000_G-fp16.onnx"), title = "Noise Toner Uniform Soft", description = R.string.model_noise_toner_uniform_soft, type = Type.DENOISE, downloadSize = 33421271, speed = Speed.Slow(15.001f), checksum = "54622791711ab457565fa0a750f668f209171a0194d0f94c206243d9a649f797" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_Repainter_20000_G-fp16.onnx"), title = "Repainter", description = R.string.model_repainter, type = Type.ENHANCE, downloadSize = 33421271, speed = Speed.Normal(14.775f), checksum = "202fdce20d84ab0da25ea0d348d2f2c29546a28509e778ef8d699fb82fcf873d" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-Debandurh-FS-Ultra-lite-fp16.onnx"), title = "Debandurh FS Ultra Lite", description = R.string.model_debandurh_fs_ultra_lite, type = Type.ENHANCE, downloadSize = 1371141, speed = Speed.VeryFast(0.741f), checksum = "6aef5e015176c48984db80e95c31539d0d1e2c1fef9bac04b335253ce372b8a9" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_JPEG_00-20.ort"), title = "JPEG 0-20", description = R.string.model_jpeg_0_20, type = Type.DE_JPEG, downloadSize = 35277480, speed = Speed.VerySlow(15.994f), checksum = "9230d501dc34fd6ef3c6dcc7640df95c20627482ff93cb236a93060bd0c9826b" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_JPEG_20-40.ort"), title = "JPEG 20-40", description = R.string.model_jpeg_20_40, type = Type.DE_JPEG, downloadSize = 35277488, speed = Speed.VerySlow(15.754f), checksum = "bbdbd9cdb86d56a5a88b0ccf8ad833d6a1ebfc4202cc25c6d139bb09a6a2535d" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_JPEG_40-60.ort"), title = "JPEG 40-60", description = R.string.model_jpeg_40_60, type = Type.DE_JPEG, downloadSize = 35277480, speed = Speed.VerySlow(15.959f), checksum = "dc248a7166cbcdcf8577d6cbd80a6d8aab3a3a7c9e2ebda29341353b4050664b" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_JPEG_60-80.ort"), title = "JPEG 60-80", description = R.string.model_jpeg_60_80, type = Type.DE_JPEG, downloadSize = 35277488, speed = Speed.Normal(14.307f), checksum = "21ece5396cf88f39f35a264000baf2ff2ec084ca7fd41b125759d845b1bfb9c3" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_JPEG_80-100.ort"), title = "JPEG 80-100", description = R.string.model_jpeg_80_100, type = Type.DE_JPEG, downloadSize = 35277488, speed = Speed.Slow(15.018f), checksum = "1041060d1d151150e14e8d51c65cd5f6608606bb4bf7ac6b5271ae074e5c3913" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_DeBLR.ort"), title = "DeBLR", description = R.string.model_deblr, type = Type.ENHANCE, downloadSize = 35277480, speed = Speed.VerySlow(16.079f), checksum = "261eb9690bc284d8cee5d81dd6156d92f9c3338c582019387a3852257c8a7b3a" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_artifacts_jpg_00_20_alsa-fp16.ort"), title = "JPEG Artifacts 0-20", description = R.string.model_artifacts_jpg_0_20, type = Type.DE_JPEG, downloadSize = 34729312, speed = Speed.Slow(15.041f), checksum = "8a416ffa7f1d78de7b3a8d211a123964b277fcb7c253f78b8e79970c7ed114bb" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_artifacts_jpg_20_40_alsa-fp16.ort"), title = "JPEG Artifacts 20-40", description = R.string.model_artifacts_jpg_20_40, type = Type.DE_JPEG, downloadSize = 34729312, speed = Speed.Normal(14.733f), checksum = "2abe485266cd1895e17b4166c5609f30a1d4d7283b9d8b2503c94f008a39244f" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_artifacts_jpg_40_60_alsa-fp16.ort"), title = "JPEG Artifacts 40-60", description = R.string.model_artifacts_jpg_40_60, type = Type.DE_JPEG, downloadSize = 34729312, speed = Speed.Slow(14.973f), checksum = "bd0116861368b83a459af687308a8acd7dc4c89aa8ba5c3dab76adb67ef9002f" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_artifacts_jpg_60_80_alsa-fp16.ort"), title = "JPEG Artifacts 60-80", description = R.string.model_artifacts_jpg_60_80, type = Type.DE_JPEG, downloadSize = 34729312, speed = Speed.Normal(14.572f), checksum = "5282f04a4520bcff4882836d134e99dbfc207f7fcaf4bf9166a99d623f579f4e" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_artifacts_jpg_80_100_alsa-fp16.ort"), title = "JPEG Artifacts 80-100", description = R.string.model_artifacts_jpg_80_100, type = Type.DE_JPEG, downloadSize = 34729312, speed = Speed.VerySlow(15.404f), checksum = "8d11451909d9318f54e1b292c0bf901856633bce835ee838bbcf4bfaf5cd174a" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_ReDetail_v2_126000_G-fp16.ort"), title = "ReDetail v2", description = R.string.model_redetail_v2, type = Type.ENHANCE, downloadSize = 34724216, speed = Speed.Slow(15.265f), checksum = "4a1790847f8e3b51ace425875abe928215d32e00d0b80af0ce90dd3481d6f272" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x-ITF-SkinDiffDetail-Lite-v1.ort"), title = "ITF Skin DiffDetail Lite", description = R.string.model_itf_skin_diffdetail_lite, type = Type.ENHANCE, downloadSize = 11070576, speed = Speed.Fast(5.138f), checksum = "813351fcee2447ff9037c86bf2807baee7a32be1db20352646c5a3eda8ced7b8" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_SBDV-DeJPEG-Lite_130000_G.ort"), title = "SBDV DeJPEG", description = R.string.model_sbdv_dejpeg, type = Type.DE_JPEG, downloadSize = 11070576, speed = Speed.Fast(5.158f), checksum = "3039271c69e3f5b3ca9af3673bea98749dffa9331930f88d057047b57d6001dc" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_ISO_denoise_v1.ort"), title = "ISO Denoise v1", description = R.string.model_iso_denoise_v1, type = Type.DENOISE, downloadSize = 35277480, speed = Speed.VerySlow(15.463f), checksum = "a16b9253a10a7e95aa10e451b2ad21b3d60124c1316ddaca746071f5843b312b" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/1x_DeJumbo.ort"), title = "DeJumbo", description = R.string.model_dejumbo, type = Type.ARTIFACTS, downloadSize = 35277488, speed = Speed.VerySlow(15.717f), checksum = "17202a453ad7f7c89c6969d2dd0cd844b01f6685584876e54092e59f589ad180" ), NeuralModel( downloadLink = res("onnx/enhance/other-models/ddcolor_paper_tiny.ort"), title = "DDColor Tiny", description = R.string.model_ddcolor_tiny, type = Type.COLORIZE, downloadSize = 220853232, speed = Speed.VeryFast(0.159f), checksum = "8186a8c21a5075c0c37860d7313043b6edb8a672067f73f3d8f5d362509f1bd3" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/RealESRGAN-x4v3.ort"), title = "RealESRGAN x4v3", description = R.string.model_realesrgan_x4v3, type = Type.UPSCALE, downloadSize = 2621440, speed = Speed.VeryFast(1.215f), checksum = "8dbd9c316f436c6e1d35a11ed29dbea0ce8f5b5b3ef082c9358ec93603a0398b" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/RealESRGAN_x2plus.ort"), title = "RealESRGAN x2 Plus", description = R.string.model_realesrgan_x2plus, type = Type.UPSCALE, downloadSize = 35456784, speed = Speed.Fast(4.437f), checksum = "cd6986ac4bd2d10d460c281dcd4f1df638fddb241f4810b53cb0eb98cfb420cd" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/RealESRGAN_x4plus.ort"), title = "RealESRGAN x4 Plus", description = R.string.model_realesrgan_x4plus, type = Type.UPSCALE, downloadSize = 35437480, speed = Speed.VerySlow(18.018f), checksum = "110818e1a29309d1da6087e8bbe201f50e43ea7679483ebca1df95ab333d2a66" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/RealESRGAN_x4plus_anime_6B.ort"), title = "RealESRGAN x4 Plus Anime 6B", description = R.string.model_realesrgan_x4plus_anime, type = Type.UPSCALE, downloadSize = 9488256, speed = Speed.Fast(5.793f), checksum = "e73004a743169923b28434b1487ed2f8c329fa99c057ffa15018502612b8bd36" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/RealESRNet_x4plus.ort"), title = "RealESRNet x4 Plus", description = R.string.model_realesrnet_x4plus, type = Type.UPSCALE, downloadSize = 35437480, speed = Speed.VerySlow(17.11f), checksum = "ed82b5cd61a6281db0eafa722c92803cecbf9eda8c88194c058e93e21407d38f" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/RealESRGAN_x4plus_anime_4B32F.ort"), title = "RealESRGAN x4 Plus Anime 4B", description = R.string.model_realesrgan_x4plus_anime_4b32f, type = Type.UPSCALE, downloadSize = 5241720, speed = Speed.VeryFast(1.534f), checksum = "ca05a00f6cb42fb1fdf03e3b132ae792a83d748fc8df0c0fa62f67e798385fd7" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/x4-UltraSharpV2_fp32_op17.ort"), title = "UltraSharp x4 V2", description = R.string.model_ultrasharp_v2_x4, type = Type.UPSCALE, downloadSize = 80518784, speed = Speed.VerySlow(36.695f), checksum = "6bbf8c91bced8c7c6fdacf841f34713b66b223fe5ccd5f579af04e077fa68302" ), NeuralModel( downloadLink = res("onnx/enhance/upscale/x4-UltraSharpV2_Lite_fp16_op17.ort"), title = "UltraSharp x4 V2 Lite", description = R.string.model_ultrasharp_v2_lite_x4, type = Type.UPSCALE, downloadSize = 16055408, speed = Speed.Normal(7.718f), checksum = "f65092e0ee88c41bdbb1eb633aa4014479599228c150f796e85d429aea4d43a0" ), NeuralModel( downloadLink = res("onnx/bgremove/RMBG_1.4.ort"), title = "RMBG 1.4", description = R.string.model_rmbg_1_4, type = Type.REMOVE_BG, downloadSize = 88704616, speed = Speed.Normal(0.603f), checksum = "ecefc6e25e88a403762e74e2d112cd8f9dea4f4628c73f3da914ef169c91d86f" ), NeuralModel( downloadLink = res("onnx/bgremove/inspyrenet.onnx"), title = "InSPyReNet", description = R.string.model_inspyrenet, type = Type.REMOVE_BG, downloadSize = 395316574, speed = Speed.Slow(3.029f), checksum = "c108dd92b3ddfe3a2d9e9ac2b74730cc5acbb2ddc7ea863330c43f56ae832aa3" ), NeuralModel( downloadLink = res("onnx/bgremove/u2net.onnx"), title = "U2Net", description = R.string.model_u2net, type = Type.REMOVE_BG, downloadSize = 175997641, speed = Speed.Fast(0.19f), checksum = "8d10d2f3bb75ae3b6d527c77944fc5e7dcd94b29809d47a739a7a728a912b491" ), NeuralModel( downloadLink = res("onnx/bgremove/u2netp.onnx"), title = "U2NetP", description = R.string.model_u2netp, type = Type.REMOVE_BG, downloadSize = 4574861, speed = Speed.VeryFast(0.074f), checksum = "309c8469258dda742793dce0ebea8e6dd393174f89934733ecc8b14c76f4ddd8" ), NeuralModel( downloadLink = res("ddcolor_modelscope.onnx"), title = "DDColor", description = R.string.model_ddcolor, type = Type.COLORIZE, downloadSize = 911801965, speed = Speed.VeryFast(0.362f), checksum = "cda896f3e61c0e489f2e11a657c6dfb711d0958364286335e3cd48fadbd621be" ), NeuralModel( downloadLink = res("ddcolor_artistic.onnx"), title = "DDColor Artistic", description = R.string.model_ddcolor_artistic, type = Type.COLORIZE, downloadSize = 911801965, speed = Speed.VeryFast(0.334f), checksum = "e7f6d8e48d609be3f2615b70637fbc7019cbad545038279a7ba26f229503d61c" ), NeuralModel( downloadLink = res("birefnet_swin_tiny.ort"), title = "BiRefNet", description = R.string.model_birefnet, type = Type.REMOVE_BG, downloadSize = 247356800, speed = Speed.VerySlow(4.117f), checksum = "46512ae91e17171c09476e3154fa2f2f8b0a557b3c8e9c91c10d11f35bc3f70c" ), NeuralModel( downloadLink = res("isnet-general-use.onnx"), title = "ISNet", description = R.string.model_isnet, type = Type.REMOVE_BG, downloadSize = 178647984, speed = Speed.Normal(0.573f), checksum = "4fcc3f7f7af1d16565dd7ec767e6e2500565ed6ba76c5c30b9934116ca32153e" ), NeuralModel( downloadLink = res("upscalers/1x-Fatality-DeBlur.onnx"), title = "Fatality DeBlur", description = R.string.model_fatality_deblur, type = Type.ARTIFACTS, downloadSize = 33456842, speed = Speed.Normal(11.609f), checksum = "ea7b0d0d873151265ed096a55f9911240a7ef0b66db419c6f6912f2cf0c94888" ), NeuralModel( downloadLink = res("upscalers/1x-UnResize-V3.onnx"), title = "UnResize V3", description = R.string.model_unresize_v3, type = Type.ARTIFACTS, downloadSize = 33456842, speed = Speed.Normal(12.356f), checksum = "f4d67d604a8cc3fb184a662f3e2fe427a80cef444e87870be8b8eedfda774fd2" ), NeuralModel( downloadLink = res("upscalers/2xLiveActionV1_SPAN_490000.onnx"), title = "LiveAction V1 SPAN x2", description = R.string.model_liveaction_v1_span, type = Type.UPSCALE, downloadSize = 1654748, speed = Speed.VeryFast(0.311f), checksum = "bfa72f3c6347076aed140d0836cee30c27ea434c047beeaf9466469483836ecc" ), NeuralModel( downloadLink = res("upscalers/2xVHS2HD-RealPLKSR.onnx"), title = "VHS to HD x2", description = R.string.model_vhs2hd_realplksr, type = Type.UPSCALE, downloadSize = 29718646, speed = Speed.Fast(5.591f), checksum = "441c8c8acedee4cf79c5629779e7720aef25952cfc68ca137034d835bbfa66f4" ), NeuralModel( downloadLink = res("upscalers/2x_Text2HD_v.1-RealPLKSR.onnx"), title = "Text2HD x2", description = R.string.model_text2hd_v1, type = Type.UPSCALE, downloadSize = 29718646, speed = Speed.Fast(5.550f), checksum = "0c3ef0e30de53ff0156da5fb4593dd4cbc89eee4707ca8e84d640302e4b9c3df" ), NeuralModel( downloadLink = res("upscalers/4x-FrankendataPretrainer_SRFormer400K.onnx"), title = "SRFormer x4", description = R.string.model_frankendata_pretrainer, type = Type.UPSCALE, downloadSize = 120837871, speed = Speed.Slow(15.130f), checksum = "6f505d321e07dadaa3f2f2cb91e4cb37cc47ba630a601fa4207abae6f8877bbc" ), NeuralModel( downloadLink = res("upscalers/4xRealWebPhoto_v2_rgt_s.onnx"), title = "RealWebPhoto v2 RGT-S x4", description = R.string.model_realwebphoto_v2, type = Type.UPSCALE, downloadSize = 48824340, speed = Speed.VerySlow(39.655f), checksum = "1c014ca191c9e3c41c47e26a6fe859a6847dd00d9d49073386e7104ac438450e" ), NeuralModel( downloadLink = res("upscalers/4xRealWebPhoto_v4_dat2.onnx"), title = "RealWebPhoto v4 DAT2 x4", description = R.string.model_realwebphoto_v4, type = Type.UPSCALE, downloadSize = 48760847, speed = Speed.VerySlow(25.959f), checksum = "a9a3a9099e0108b793556ed9cf2123a61a727bbc611dd8d93475f7144596589e" ), NeuralModel( downloadLink = res("upscalers/DAT_2_x2.onnx"), title = "DAT2 x2", description = R.string.model_dat_2x, type = Type.UPSCALE, downloadSize = 49503576, speed = Speed.VerySlow(25.567f), checksum = "fee6f4901207b1b5f68900ebbc7e0a2dbcb881b8ddfac97219f118af2cc53066" ), NeuralModel( downloadLink = res("upscalers/DAT_2_x3.onnx"), title = "DAT2 x3", description = R.string.model_dat_3x, type = Type.UPSCALE, downloadSize = 50242136, speed = Speed.VerySlow(25.736f), checksum = "59aeb6835c29f57dc9cab3b1367ec670eddc5b51d5a8177f3f921ad7ffb63ad9" ), NeuralModel( downloadLink = res("upscalers/DAT_2_x4.onnx"), title = "DAT2 x4", description = R.string.model_dat_4x, type = Type.UPSCALE, downloadSize = 50094919, speed = Speed.VerySlow(26.169f), checksum = "e6354e8fdd1b31b6e1114300843bf7bc31f91e4294fbb8e41282fc43f0a46cbc" ), NeuralModel( downloadLink = res("deblurring_nafnet_2025may.onnx"), title = "NAFNET deblurring", description = R.string.model_nafnet_deblurring, type = Type.DENOISE, downloadSize = 91736251, speed = Speed.VeryFast(1.371f), checksum = "07263f416febecce10193dd648e950b22e397cf521eedab1a114ef77b2bc9587" ), NeuralModel( downloadLink = res("upscalers/swin2SR-x4-bsrgan-psnr.onnx"), title = "Swin2SR BSRGAN PSNR x4", description = R.string.model_swin2sr_x4_bsrgan_psnr, type = Type.UPSCALE, downloadSize = 53827735, speed = Speed.Normal(14.478f), checksum = "987d88b356554161cbb8f67b7a8f4162cad6dc147839c344e3d5142140f25d6f" ), NeuralModel( downloadLink = res("upscalers/BSRGAN_SwinIR-M_x4_GAN.onnx"), title = "SwinIR-M BSRGAN GAN x4", description = R.string.model_bsrgan_swinir_m_x4_gan, type = Type.UPSCALE, downloadSize = 61316800, speed = Speed.Normal(13.116f), checksum = "50a7b6fcaad6c4f342c5e1d059916a3e5d48e89d1d311270a476a7ce6a2ea09f" ), NeuralModel( downloadLink = res("upscalers/RealESR-AnimeVideo-x4v3.onnx"), title = "RealESR AnimeVideo x4v3", description = R.string.model_realesr_animevideo_v3x4, type = Type.UPSCALE, downloadSize = 2495473, speed = Speed.VeryFast(0.437f), checksum = "3d2dc0af5e2cdf3a31655c4c673df9ad74f14775d74237bd0dc68fc885bd6841" ), ).sortedBy { it.type?.ordinal } } fun find(name: String?): NeuralModel? = entries.find { model -> model.name.equals(name, true) } fun Imported( name: String, checksum: String ): NeuralModel = NeuralModel( downloadLink = "imported", name = name, title = name.replace("_", " ").replace("-", " ").substringBefore('.'), description = null, type = null, speed = null, downloadSize = 0, checksum = checksum ) private fun res(path: String): String = HF_BASE_URL.replace("*", path) } } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/domain/model/NeuralParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.domain.model data class NeuralParams( val strength: Float, val chunkSize: Int, val overlap: Int, val enableChunking: Boolean ) { companion object { val Default by lazy { NeuralParams( strength = 65f, chunkSize = 512, overlap = 16, enableChunking = true ) } } } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/AiToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation import android.net.Uri import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.image.urisPreview import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import com.t8rin.imagetoolbox.feature.ai_tools.presentation.components.AiToolsControls import com.t8rin.imagetoolbox.feature.ai_tools.presentation.components.NeuralSaveProgressDialog import com.t8rin.imagetoolbox.feature.ai_tools.presentation.screenLogic.AiToolsComponent @Composable fun AiToolsContent( component: AiToolsComponent ) { val imagePicker = rememberImagePicker { uris: List -> component.updateUris( uris = uris ) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() val addImagesImagePicker = rememberImagePicker { uris: List -> component.addUris( uris = uris ) } val selectedModel by component.selectedModel.collectAsStateWithLifecycle() AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.ai_tools) ) EnhancedBadge( content = { Text( text = NeuralModel.entries.size.toString() ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( onShare = component::shareBitmaps, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, showImagePreviewAsStickyHeader = false, imagePreview = { UrisPreview( modifier = Modifier.urisPreview(), uris = component.uris.orEmpty(), isPortrait = true, onRemoveUri = component::removeUri, onAddUris = addImagesImagePicker::pickImage, onNavigate = component.onNavigate ) }, controls = { AiToolsControls( component = component ) }, noDataControls = { ImageNotPickedWidget(onPickImage = pickImage) }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), isPrimaryButtonVisible = selectedModel != null, onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, topAppBarPersistentActions = { if (component.uris.isNullOrEmpty()) { TopAppBarEmoji() } }, canShowScreenData = !component.uris.isNullOrEmpty() ) NeuralSaveProgressDialog( component = component ) LoadingDialog( visible = component.isImageLoading, canCancel = false ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/AiToolsControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.WarningAmber import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Cube import com.t8rin.imagetoolbox.core.resources.icons.Exercise import com.t8rin.imagetoolbox.core.resources.icons.Stacks import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.derivative.OnlyAllowedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import com.t8rin.imagetoolbox.feature.ai_tools.presentation.screenLogic.AiToolsComponent import kotlin.math.roundToInt @Composable internal fun AiToolsControls(component: AiToolsComponent) { val selectedModel by component.selectedModel.collectAsStateWithLifecycle() val downloadedModels by component.downloadedModels.collectAsStateWithLifecycle() val notDownloadedModels by component.notDownloadedModels.collectAsStateWithLifecycle() val occupiedStorageSize by component.occupiedStorageSize.collectAsStateWithLifecycle() val isModelChunkable = selectedModel?.isNonChunkable != true val isChunkable = isModelChunkable && component.params.enableChunking NeuralModelSelector( value = selectedModel, onSelectModel = component::selectModel, onDownloadModel = component::downloadModel, onDeleteModel = component::deleteModel, onImportModel = component::importModel, downloadedModels = downloadedModels, notDownloadedModels = notDownloadedModels, downloadProgresses = component.downloadProgresses, occupiedStorageSize = occupiedStorageSize, onCancelDownload = component::cancelDownload ) AnimatedVisibility( visible = isChunkable, modifier = Modifier.fillMaxSize() ) { Column { AnimatedVisibility( visible = selectedModel?.supportsStrength == true, modifier = Modifier.fillMaxWidth() ) { EnhancedSliderItem( value = component.params.strength, internalStateTransformation = { it.roundToInt() }, steps = 100, valueRange = 0f..100f, onValueChange = { component.updateParams { copy(strength = it) } }, title = stringResource(R.string.strength), icon = Icons.Outlined.Exercise, modifier = Modifier.padding(top = 8.dp), shape = ShapeDefaults.large, ) } Spacer(Modifier.height(8.dp)) val chunkPowers = remember { generateSequence(128) { it * 2 }.takeWhile { it <= 2048 }.toList() } val overlapPowers = remember { generateSequence(16) { it * 2 }.takeWhile { it <= 128 }.toList() } OnlyAllowedSliderItem( label = stringResource(id = R.string.chunk_size), icon = Icons.Outlined.Cube, value = component.params.chunkSize, allowed = chunkPowers, onValueChange = { component.updateParams { copy(chunkSize = it) } } ) AnimatedVisibility( visible = component.params.chunkSize >= 2048, modifier = Modifier.fillMaxWidth() ) { Box( contentAlignment = Alignment.Center ) { InfoContainer( text = stringResource(R.string.large_chunk_warning), containerColor = MaterialTheme.colorScheme.errorContainer.copy(0.4f), contentColor = MaterialTheme.colorScheme.onErrorContainer.copy(0.7f), icon = Icons.Rounded.WarningAmber, modifier = Modifier.padding(top = 8.dp) ) } } Spacer(Modifier.height(8.dp)) OnlyAllowedSliderItem( label = stringResource(R.string.overlap_size), icon = Icons.Outlined.Stacks, value = component.params.overlap, allowed = overlapPowers, maxAllowed = component.params.chunkSize, onValueChange = { component.updateParams { copy(overlap = it) } } ) } } Spacer(Modifier.height(8.dp)) InfoContainer( text = if (isChunkable) { stringResource(R.string.note_chunk_info, component.params.chunkSize) } else if (!isModelChunkable) { stringResource(R.string.current_model_not_chunkable) } else { stringResource(R.string.chunking_disabled) }, containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.4f), contentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.8f) ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = component.imageFormat, entries = if (selectedModel?.type != NeuralModel.Type.REMOVE_BG) { ImageFormatGroup.entries } else { ImageFormatGroup.alphaContainedEntries }, onValueChange = component::setImageFormat, onAutoClick = { component.setImageFormat(null) } ) } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/DeleteModelDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel @Composable internal fun DeleteModelDialog( model: NeuralModel?, onDismiss: () -> Unit, onDeleteModel: (NeuralModel) -> Unit ) { EnhancedAlertDialog( visible = model != null, icon = { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null ) }, title = { Text(stringResource(id = R.string.delete)) }, text = { Text( stringResource( id = R.string.delete_model_sub, model?.title ?: "", ) ) }, onDismissRequest = onDismiss, confirmButton = { EnhancedButton( onClick = { model?.let(onDeleteModel) onDismiss() } ) { Text(stringResource(R.string.confirm)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.cancel)) } } ) } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/FilteredModels.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getStringLocalized import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import java.util.Locale @Composable internal fun filteredModels( downloadedModels: List, notDownloadedModels: List, typeFilters: List, speedFilters: List, keywordFilter: String, ): State, List>> = remember( downloadedModels, notDownloadedModels, typeFilters, speedFilters, keywordFilter ) { derivedStateOf { downloadedModels.filter( typeFilters = typeFilters, speedFilters = speedFilters, keywordFilter = keywordFilter.trim() ) to notDownloadedModels.filter( typeFilters = typeFilters, speedFilters = speedFilters, keywordFilter = keywordFilter.trim() ) } } private fun List.filter( typeFilters: List, speedFilters: List, keywordFilter: String ): List { return if (typeFilters.isEmpty() && speedFilters.isEmpty() && keywordFilter.isBlank()) this else filter { model -> val hasType = typeFilters.isEmpty() || model.type == null || model.type in typeFilters val hasSpeed = speedFilters.isEmpty() || model.speed == null || speedFilters.any { it::class.isInstance(model.speed) } val hasKeyword = keywordFilter.isBlank() || model.name.contains(keywordFilter, true) || model.title.contains(keywordFilter, true) || (model.description?.let { appContext.getString(model.description) .contains(keywordFilter, true) || appContext.getStringLocalized(model.description, Locale.ENGLISH) .contains(keywordFilter, true) } == true) hasType && hasSpeed && hasKeyword } } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelFilterSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material.icons.outlined.FilterAlt import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggleByClass import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel @Composable internal fun NeuralModelFilterSheet( visible: Boolean, onDismiss: (Boolean) -> Unit, typeFilters: List, speedFilters: List, keywordFilter: String, onTypeFiltersChange: (List) -> Unit, onSpeedFiltersChange: (List) -> Unit, onKeywordFilterChange: (String) -> Unit ) { EnhancedModalBottomSheet( visible = visible, onDismiss = onDismiss, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onDismiss(false) } ) { Text(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(id = R.string.filter), icon = Icons.Outlined.FilterAlt ) } ) { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(16.dp) .clearFocusOnTap(), horizontalAlignment = Alignment.CenterHorizontally ) { Column( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp, color = EnhancedBottomSheetDefaults.contentContainerColor ), horizontalAlignment = Alignment.CenterHorizontally ) { TitleItem( text = stringResource(R.string.type), modifier = Modifier .padding( start = 4.dp, top = 4.dp, end = 4.dp ) ) Spacer(Modifier.height(8.dp)) FlowRow( horizontalArrangement = Arrangement.spacedBy( space = 4.dp, alignment = Alignment.CenterHorizontally ), verticalArrangement = Arrangement.spacedBy(4.dp) ) { NeuralModel.Type.entries.forEach { type -> NeuralModelTypeBadge( type = type, isInverted = type in typeFilters, onClick = { onTypeFiltersChange(typeFilters.toggle(type)) }, height = 36.dp, endPadding = 12.dp, startPadding = 6.dp, style = MaterialTheme.typography.labelMedium ) } } } Spacer(Modifier.height(8.dp)) Column( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp, color = EnhancedBottomSheetDefaults.contentContainerColor ), horizontalAlignment = Alignment.CenterHorizontally ) { TitleItem( text = stringResource(R.string.speed), modifier = Modifier .padding( start = 4.dp, top = 4.dp, end = 4.dp ) ) Spacer(Modifier.height(8.dp)) FlowRow( horizontalArrangement = Arrangement.spacedBy( space = 4.dp, alignment = Alignment.CenterHorizontally ), verticalArrangement = Arrangement.spacedBy(4.dp) ) { NeuralModel.Speed.entries.forEach { speed -> NeuralModelSpeedBadge( speed = speed, isInverted = speedFilters.any { it::class.isInstance(speed) }, onClick = { onSpeedFiltersChange(speedFilters.toggleByClass(speed)) }, height = 36.dp, endPadding = 12.dp, startPadding = 6.dp, style = MaterialTheme.typography.labelMedium, showTitle = true ) } } } Spacer(Modifier.height(8.dp)) RoundedTextField( value = keywordFilter, onValueChange = onKeywordFilterChange, label = stringResource(R.string.keyword), modifier = Modifier .fillMaxWidth() .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp, color = EnhancedBottomSheetDefaults.contentContainerColor ), singleLine = false, endIcon = { AnimatedVisibility(keywordFilter.isNotBlank()) { EnhancedIconButton( onClick = { onKeywordFilterChange("") }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.Cancel, contentDescription = stringResource(R.string.cancel) ) } } }, maxLines = 4 ) } } } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelectionSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FilterAlt import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Neurology import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel @Composable internal fun NeuralModelSelectionSheet( visible: Boolean, onDismiss: (Boolean) -> Unit, selectedModel: NeuralModel?, onSelectModel: (NeuralModel) -> Unit, onDownloadModel: (NeuralModel) -> Unit, onDeleteModel: (NeuralModel) -> Unit, downloadedModels: List, notDownloadedModels: List, onImportModel: (Uri) -> Unit, downloadProgresses: Map, occupiedStorageSize: Long, onCancelDownload: (NeuralModel) -> Unit ) { var typeFilters by rememberSaveable(stateSaver = TypeFiltersSaver) { mutableStateOf(emptyList()) } var speedFilters by rememberSaveable(stateSaver = SpeedFiltersSaver) { mutableStateOf(emptyList()) } var keywordFilter by rememberSaveable { mutableStateOf("") } var showFilterSheet by rememberSaveable { mutableStateOf(false) } EnhancedModalBottomSheet( visible = visible, onDismiss = onDismiss, confirmButton = { Row( verticalAlignment = Alignment.CenterVertically ) { EnhancedIconButton( onClick = { showFilterSheet = true }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Box { Icon( imageVector = Icons.Rounded.FilterAlt, contentDescription = "more" ) BoxAnimatedVisibility( visible = typeFilters.isNotEmpty() || speedFilters.isNotEmpty() || keywordFilter.isNotBlank(), modifier = Modifier .size(6.dp) .align(Alignment.TopEnd) ) { EnhancedBadge() } } } NeuralModelFilterSheet( visible = showFilterSheet, onDismiss = { showFilterSheet = it }, typeFilters = typeFilters, speedFilters = speedFilters, keywordFilter = keywordFilter, onTypeFiltersChange = { typeFilters = it }, onSpeedFiltersChange = { speedFilters = it }, onKeywordFilterChange = { keywordFilter = it } ) EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onDismiss(false) } ) { Text(stringResource(R.string.close)) } } }, title = { TitleItem( text = stringResource(id = R.string.models), icon = Icons.Outlined.Neurology ) }, sheetContent = { val (filteredDownloadedModels, filteredNotDownloadedModels) = filteredModels( downloadedModels = downloadedModels, notDownloadedModels = notDownloadedModels, typeFilters = typeFilters, speedFilters = speedFilters, keywordFilter = keywordFilter ).value NeuralModelsColumn( selectedModel = selectedModel, downloadedModels = filteredDownloadedModels, notDownloadedModels = filteredNotDownloadedModels, onSelectModel = onSelectModel, onDownloadModel = onDownloadModel, onDeleteModel = onDeleteModel, onImportModel = onImportModel, downloadProgresses = downloadProgresses, occupiedStorageSize = occupiedStorageSize, onCancelDownload = onCancelDownload ) } ) } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.Neurology import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel @Composable internal fun NeuralModelSelector( value: NeuralModel?, onSelectModel: (NeuralModel) -> Unit, onDownloadModel: (NeuralModel) -> Unit, onDeleteModel: (NeuralModel) -> Unit, downloadedModels: List, notDownloadedModels: List, onImportModel: (Uri) -> Unit, downloadProgresses: Map, occupiedStorageSize: Long, onCancelDownload: (NeuralModel) -> Unit ) { var showSelectionSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( modifier = Modifier.fillMaxWidth(), title = stringResource(id = R.string.active_model), subtitle = value?.title ?: stringResource(R.string.select_one_to_start), onClick = { showSelectionSheet = true }, containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, shape = ShapeDefaults.extraLarge, startIcon = Icons.Outlined.Neurology, endIcon = Icons.Rounded.MiniEdit, placeBottomContentInside = true, bottomContent = value?.type?.let { type -> { FlowRow( modifier = Modifier.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { NeuralModelTypeBadge( type = type, isInverted = null ) value.speed?.let { speed -> NeuralModelSpeedBadge( speed = speed, isInverted = null ) } NeuralModelSizeBadge( model = value, isInverted = false ) } } } ) NeuralModelSelectionSheet( visible = showSelectionSheet, onDismiss = { showSelectionSheet = it }, selectedModel = value, onSelectModel = onSelectModel, onDownloadModel = onDownloadModel, onDeleteModel = onDeleteModel, downloadedModels = downloadedModels, notDownloadedModels = notDownloadedModels, onImportModel = onImportModel, downloadProgresses = downloadProgresses, occupiedStorageSize = occupiedStorageSize, onCancelDownload = onCancelDownload ) } @Preview @Composable private fun Preview() = ImageToolboxThemeForPreview( isDarkTheme = false, keyColor = Color.Green ) { NeuralModelSelector( value = NeuralModel.entries.first(), onSelectModel = {}, onDownloadModel = {}, onDeleteModel = {}, downloadedModels = emptyList(), notDownloadedModels = emptyList(), onImportModel = { _ -> }, downloadProgresses = emptyMap(), occupiedStorageSize = 0, onCancelDownload = {} ) } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelTypeResources.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.DirectionsWalk import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material.icons.rounded.Bolt import androidx.compose.material.icons.rounded.Cloud import androidx.compose.material.icons.rounded.HighQuality import androidx.compose.material.icons.rounded.Scanner import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.resources.icons.Eraser import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper import com.t8rin.imagetoolbox.core.resources.icons.Jpg import com.t8rin.imagetoolbox.core.resources.icons.Manga import com.t8rin.imagetoolbox.core.resources.icons.NoiseAlt import com.t8rin.imagetoolbox.core.resources.icons.Rabbit import com.t8rin.imagetoolbox.core.resources.icons.Snail import com.t8rin.imagetoolbox.core.resources.icons.Tortoise import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralConstants import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import java.io.File import kotlin.random.Random fun NeuralModel.Type.title(): Int = when (this) { NeuralModel.Type.DE_JPEG -> R.string.type_dejpeg NeuralModel.Type.DENOISE -> R.string.type_denoise NeuralModel.Type.COLORIZE -> R.string.type_colorize NeuralModel.Type.ARTIFACTS -> R.string.type_artifacts NeuralModel.Type.ENHANCE -> R.string.type_enhance NeuralModel.Type.ANIME -> R.string.type_anime NeuralModel.Type.SCANS -> R.string.type_scans NeuralModel.Type.UPSCALE -> R.string.type_upscale NeuralModel.Type.REMOVE_BG -> R.string.type_removebg } fun NeuralModel.Type.icon(): ImageVector = when (this) { NeuralModel.Type.DE_JPEG -> Icons.Outlined.Jpg NeuralModel.Type.DENOISE -> Icons.Outlined.NoiseAlt NeuralModel.Type.COLORIZE -> Icons.Outlined.Eyedropper NeuralModel.Type.ARTIFACTS -> Icons.Rounded.BrokenImageAlt NeuralModel.Type.ENHANCE -> Icons.Rounded.AutoFixHigh NeuralModel.Type.ANIME -> Icons.Rounded.Manga NeuralModel.Type.SCANS -> Icons.Rounded.Scanner NeuralModel.Type.UPSCALE -> Icons.Rounded.HighQuality NeuralModel.Type.REMOVE_BG -> Icons.Rounded.Eraser } fun NeuralModel.Speed.icon(): ImageVector = when (this) { is NeuralModel.Speed.VeryFast -> Icons.Rounded.Bolt is NeuralModel.Speed.Fast -> Icons.Rounded.Rabbit is NeuralModel.Speed.Normal -> Icons.AutoMirrored.Rounded.DirectionsWalk is NeuralModel.Speed.Slow -> Icons.Rounded.Tortoise is NeuralModel.Speed.VerySlow -> Icons.Rounded.Snail } fun NeuralModel.Speed.title(): Int = when (this) { is NeuralModel.Speed.VeryFast -> R.string.very_fast is NeuralModel.Speed.Fast -> R.string.fast is NeuralModel.Speed.Normal -> R.string.normal is NeuralModel.Speed.Slow -> R.string.slow is NeuralModel.Speed.VerySlow -> R.string.very_slow } @Composable fun NeuralModelTypeBadge( type: NeuralModel.Type, isInverted: Boolean?, modifier: Modifier = Modifier, height: Dp = 22.dp, endPadding: Dp = 6.dp, startPadding: Dp = 2.dp, onClick: (() -> Unit)? = null, style: TextStyle = MaterialTheme.typography.labelSmall ) { val interactionSource = remember { MutableInteractionSource() } Row( modifier = modifier .height(height) .container( color = takeColorFromScheme { when (isInverted) { true -> tertiary.blend( color = secondary, fraction = 0.3f ) false -> tertiaryContainer.blend( color = secondaryContainer, fraction = 0.3f ) null -> surfaceVariant.blend( color = secondaryContainer, fraction = 0.3f ) } }, shape = shapeByInteraction( shape = ShapeDefaults.circle, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ), resultPadding = 0.dp ) .then( if (onClick != null) { Modifier.hapticsClickable( indication = LocalIndication.current, onClick = onClick, interactionSource = interactionSource ) } else { Modifier } ) .padding(start = startPadding, end = endPadding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(2.dp) ) { val contentColor = takeColorFromScheme { when (isInverted) { true -> onTertiary.blend( color = onSecondary, fraction = 0.65f ) false -> onTertiaryContainer.blend( color = onSecondaryContainer, fraction = 0.65f ) null -> onSurfaceVariant.blend( color = onSecondaryContainer, fraction = 0.65f ) } } Box( modifier = Modifier.size((height - 2.dp).coerceAtMost(24.dp)), contentAlignment = Alignment.Center ) { Icon( imageVector = type.icon(), contentDescription = null, tint = contentColor, modifier = Modifier .fillMaxSize() .padding(2.dp) ) } Text( text = stringResource(type.title()), color = contentColor, style = style ) } } @Composable fun NeuralModelSpeedBadge( speed: NeuralModel.Speed, isInverted: Boolean?, modifier: Modifier = Modifier, height: Dp = 22.dp, endPadding: Dp = 6.dp, startPadding: Dp = 2.dp, onClick: (() -> Unit)? = null, showTitle: Boolean = false, style: TextStyle = MaterialTheme.typography.labelSmall ) { val hasValue = showTitle || speed.speedValue > 0f val interactionSource = remember { MutableInteractionSource() } Row( modifier = modifier .then( if (hasValue) { Modifier.height(height) } else { Modifier.size(height) } ) .container( color = takeColorFromScheme { when (isInverted) { true -> primary false -> primaryContainer null -> surfaceVariant.blend( color = primaryContainer, fraction = 0.2f ) } }, shape = shapeByInteraction( shape = ShapeDefaults.circle, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ), resultPadding = 0.dp ) .then( if (onClick != null) { Modifier.hapticsClickable( indication = LocalIndication.current, onClick = onClick, interactionSource = interactionSource ) } else { Modifier } ) .then( if (hasValue) { Modifier.padding(start = startPadding, end = endPadding) } else Modifier ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterHorizontally) ) { val contentColor = takeColorFromScheme { when (isInverted) { true -> onPrimary false -> onPrimaryContainer null -> onSurfaceVariant.blend( color = onPrimaryContainer, fraction = 0.3f ) } } Box( modifier = Modifier.size((height - 2.dp).coerceAtMost(24.dp)), contentAlignment = Alignment.Center ) { Icon( imageVector = speed.icon(), contentDescription = null, tint = contentColor, modifier = Modifier .fillMaxSize() .padding(2.dp) ) } if (hasValue) { val speedValue by remember(speed.speedValue) { derivedStateOf { speed.speedValue.roundTo( when { speed.speedValue > 10f -> 1 speed.speedValue > 5f -> 2 else -> 3 } ).toString().trimTrailingZero() } } Text( text = if (showTitle) { stringResource(speed.title()) } else { speedValue }, color = contentColor, style = style ) } } } @Composable fun NeuralModelSizeBadge( model: NeuralModel, isInverted: Boolean, modifier: Modifier = Modifier ) { Row( modifier = modifier .height(22.dp) .container( color = takeColorFromScheme { if (isInverted) surface else surfaceVariant }, shape = ShapeDefaults.circle, resultPadding = 0.dp ) .padding(start = 4.dp, end = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(2.dp) ) { val contentColor = takeColorFromScheme { if (isInverted) onSurface else onSurfaceVariant } val modelFile by remember(model.name) { derivedStateOf { File(File(appContext.filesDir, NeuralConstants.DIR), model.name) } } val size by remember(model.downloadSize, modelFile) { derivedStateOf { modelFile.length().takeIf { it > 0 }?.let(::humanFileSize) ?: humanFileSize(model.downloadSize) } } Box( modifier = Modifier.size(20.dp), contentAlignment = Alignment.Center ) { Icon( imageVector = if (modelFile.exists()) { Icons.AutoMirrored.Rounded.InsertDriveFile } else { Icons.Rounded.Cloud }, contentDescription = null, tint = contentColor, modifier = Modifier.size(16.dp) ) } Text( text = size, color = contentColor, style = MaterialTheme.typography.labelSmall ) } } @Preview @Composable private fun PreviewSpeed() = ImageToolboxThemeForPreview( isDarkTheme = true, keyColor = Color.Green ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .background(MaterialTheme.colorScheme.surface) .padding(8.dp) ) { NeuralModel.Speed.entries.forEach { NeuralModelSpeedBadge( speed = it.clone(12.21f), isInverted = Random.nextBoolean(), height = 36.dp, endPadding = 12.dp, startPadding = 6.dp, style = MaterialTheme.typography.labelMedium ) } } } @Preview @Composable private fun PreviewType() = ImageToolboxThemeForPreview( isDarkTheme = true, keyColor = Color.Green ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .background(MaterialTheme.colorScheme.surface) .padding(8.dp) ) { NeuralModel.Type.entries.forEach { NeuralModelTypeBadge( type = it, isInverted = Random.nextBoolean(), height = 36.dp, endPadding = 12.dp, startPadding = 6.dp, style = MaterialTheme.typography.labelMedium ) } } } @Preview @Composable private fun PreviewMixed() = ImageToolboxThemeForPreview( isDarkTheme = true, keyColor = Color.Green ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .background(MaterialTheme.colorScheme.surfaceContainer) .padding(8.dp) ) { NeuralModel.Type.entries.forEach { Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { val inv = Random.nextBoolean() NeuralModelTypeBadge(it, inv) NeuralModelSpeedBadge( speed = (NeuralModel.Speed.entries.getOrNull(it.ordinal) ?: NeuralModel.Speed.entries.random()) .clone(Random.nextFloat() * 20), inv ) NeuralModelSizeBadge( model = NeuralModel.entries.first(), isInverted = inv ) } } } } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralModelsColumn.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.rounded.Download import androidx.compose.material.icons.rounded.DownloadDone import androidx.compose.material.icons.rounded.DownloadForOffline import androidx.compose.material.icons.rounded.ModelTraining import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.FileImport import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCancellableCircularProgressIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.RevealDirection import com.t8rin.imagetoolbox.core.ui.widget.other.RevealValue import com.t8rin.imagetoolbox.core.ui.widget.other.SwipeToReveal import com.t8rin.imagetoolbox.core.ui.widget.other.rememberRevealState import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.saver.OneTimeEffect import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable internal fun NeuralModelsColumn( selectedModel: NeuralModel?, downloadedModels: List, notDownloadedModels: List, onSelectModel: (NeuralModel) -> Unit, onDownloadModel: (NeuralModel) -> Unit, onDeleteModel: (NeuralModel) -> Unit, onImportModel: (Uri) -> Unit, downloadProgresses: Map, occupiedStorageSize: Long, onCancelDownload: (NeuralModel) -> Unit ) { val scope = rememberCoroutineScope() val listState = rememberLazyListState() val filePicker = rememberFilePicker { uri: Uri -> val name = uri.filename().orEmpty() if (name.endsWith(".onnx") || name.endsWith(".ort")) { onImportModel(uri) } else { AppToastHost.showFailureToast(R.string.only_onnx_models) } } val (importedModels, downloadedModels) = remember(downloadedModels) { derivedStateOf { downloadedModels.partition { it.isImported } } }.value val scrollToSelected = suspend { downloadedModels.indexOf(selectedModel).takeIf { it != -1 }.let { listState.animateScrollToItem(it ?: 0) } } var isSelectRequested by remember { mutableStateOf(false) } OneTimeEffect { scrollToSelected() } LaunchedEffect(downloadedModels) { delay(250) scrollToSelected() } LaunchedEffect(selectedModel?.name) { delay(250) if (isSelectRequested) { isSelectRequested = false return@LaunchedEffect } scrollToSelected() } var deleteDialogData by remember { mutableStateOf(null) } DeleteModelDialog( model = deleteDialogData, onDismiss = { deleteDialogData = null }, onDeleteModel = onDeleteModel ) LazyColumn( state = listState, contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { item { PreferenceItem( title = stringResource(R.string.import_model), subtitle = stringResource(R.string.import_model_sub), onClick = filePicker::pickFile, startIcon = Icons.Rounded.ModelTraining, containerColor = EnhancedBottomSheetDefaults.contentContainerColor, shape = ShapeDefaults.default, modifier = Modifier.fillMaxWidth(), endIcon = Icons.Rounded.FileImport ) } if (importedModels.isNotEmpty()) { item { TitleItem( icon = Icons.AutoMirrored.Rounded.InsertDriveFile, text = stringResource(id = R.string.imported_models) ) } } itemsIndexed( items = importedModels, key = { _, m -> m.name } ) { index, model -> val selected = selectedModel?.name == model.name val state = rememberRevealState() val interactionSource = remember { MutableInteractionSource() } val isDragged by interactionSource.collectIsDraggedAsState() val shape = ShapeDefaults.byIndex( index = index, size = importedModels.size, forceDefault = isDragged ) SwipeToReveal( state = state, modifier = Modifier.animateItem(), revealedContentEnd = { Box( Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.errorContainer, shape = shape, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } deleteDialogData = model } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete), modifier = Modifier .padding(16.dp) .padding(end = 8.dp) .align(Alignment.CenterEnd), tint = MaterialTheme.colorScheme.onErrorContainer ) } }, directions = setOf(RevealDirection.EndToStart), swipeableContent = { PreferenceItemOverload( shape = shape, containerColor = animateColorAsState( if (selected) { MaterialTheme .colorScheme .mixedContainer .copy(0.8f) } else EnhancedBottomSheetDefaults.contentContainerColor ).value, onLongClick = { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } }, onClick = { isSelectRequested = true onSelectModel(model) }, title = model.title, subtitle = model.description?.let { stringResource(it) }, modifier = Modifier.fillMaxWidth(), endIcon = { Icon( imageVector = if (selected) { Icons.Rounded.RadioButtonChecked } else { Icons.Rounded.RadioButtonUnchecked }, contentDescription = null ) }, placeBottomContentInside = true, bottomContent = { NeuralModelSizeBadge( model = model, isInverted = selected, modifier = Modifier.padding(top = 8.dp) ) } ) }, interactionSource = interactionSource ) } if (downloadedModels.isNotEmpty()) { item { TitleItem( icon = Icons.Rounded.DownloadDone, text = stringResource(id = R.string.downloaded_models), endContent = { EnhancedBadge( containerColor = MaterialTheme.colorScheme.tertiary ) { Text( rememberHumanFileSize( byteCount = occupiedStorageSize ) ) } } ) } } itemsIndexed( items = downloadedModels, key = { _, m -> m.name } ) { index, model -> val selected = selectedModel?.name == model.name val state = rememberRevealState() val interactionSource = remember { MutableInteractionSource() } val isDragged by interactionSource.collectIsDraggedAsState() val shape = ShapeDefaults.byIndex( index = index, size = downloadedModels.size, forceDefault = isDragged ) SwipeToReveal( state = state, modifier = Modifier.animateItem(), revealedContentEnd = { Box( Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.errorContainer, shape = shape, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } deleteDialogData = model } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete), modifier = Modifier .padding(16.dp) .padding(end = 8.dp) .align(Alignment.CenterEnd), tint = MaterialTheme.colorScheme.onErrorContainer ) } }, revealedContentStart = { val uriHandler = LocalUriHandler.current Box( Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.tertiaryContainer.copy( 0.5f ), shape = shape, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } uriHandler.openUri(model.pointerLink) } ) { Icon( imageVector = Icons.Outlined.Link, contentDescription = "link", modifier = Modifier .padding(16.dp) .padding(start = 8.dp) .align(Alignment.CenterStart), tint = MaterialTheme.colorScheme.onTertiaryContainer ) } }, directions = setOf( RevealDirection.StartToEnd, RevealDirection.EndToStart ), swipeableContent = { PreferenceItemOverload( shape = shape, containerColor = animateColorAsState( if (selected) { MaterialTheme .colorScheme .mixedContainer .copy(0.8f) } else EnhancedBottomSheetDefaults.contentContainerColor ).value, onLongClick = { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } }, onClick = { isSelectRequested = true onSelectModel(model) }, title = model.title, subtitle = model.description?.let { stringResource(it) }, modifier = Modifier.fillMaxWidth(), placeBottomContentInside = true, endIcon = { Icon( imageVector = if (selected) { Icons.Rounded.RadioButtonChecked } else { Icons.Rounded.RadioButtonUnchecked }, contentDescription = null ) }, bottomContent = model.type?.let { type -> { FlowRow( modifier = Modifier.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { NeuralModelTypeBadge( type = type, isInverted = selected ) model.speed?.let { speed -> NeuralModelSpeedBadge( speed = speed, isInverted = selected ) } NeuralModelSizeBadge( model = model, isInverted = selected ) } } } ) }, interactionSource = interactionSource ) } if (notDownloadedModels.isNotEmpty()) { item { TitleItem( icon = Icons.Rounded.Download, text = stringResource(id = R.string.available_models) ) } } itemsIndexed( items = notDownloadedModels, key = { _, m -> m.name + "not" } ) { index, model -> val state = rememberRevealState() val interactionSource = remember { MutableInteractionSource() } val isDragged by interactionSource.collectIsDraggedAsState() val shape = ShapeDefaults.byIndex( index = index, size = notDownloadedModels.size, forceDefault = isDragged ) SwipeToReveal( state = state, modifier = Modifier.animateItem(), revealedContentStart = { val uriHandler = LocalUriHandler.current Box( Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.tertiaryContainer.copy( 0.5f ), shape = shape, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } uriHandler.openUri(model.pointerLink) } ) { Icon( imageVector = Icons.Outlined.Link, contentDescription = "link", modifier = Modifier .padding(16.dp) .padding(start = 8.dp) .align(Alignment.CenterStart), tint = MaterialTheme.colorScheme.onTertiaryContainer ) } }, directions = setOf(RevealDirection.StartToEnd), swipeableContent = { PreferenceItemOverload( shape = shape, title = model.title, subtitle = model.description?.let { stringResource(it) }, onClick = { onDownloadModel(model) }, onLongClick = { scope.launch { state.animateTo(RevealValue.FullyRevealedEnd) } }, containerColor = EnhancedBottomSheetDefaults.contentContainerColor, modifier = Modifier .animateItem() .fillMaxWidth(), endIcon = { AnimatedContent( targetState = downloadProgresses[model.name], contentKey = { key -> key?.currentTotalSize?.let { it > 0 } } ) { progress -> if (progress != null) { Row( modifier = Modifier.container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surface, resultPadding = 8.dp ), verticalAlignment = Alignment.CenterVertically ) { Text( text = if (progress.currentTotalSize > 0) { rememberHumanFileSize(progress.currentTotalSize) } else { stringResource(R.string.preparing) }, style = MaterialTheme.typography.bodySmall ) Spacer(Modifier.width(8.dp)) EnhancedCancellableCircularProgressIndicator( progress = { progress.currentPercent }, modifier = Modifier.size(24.dp), trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), strokeWidth = 3.dp, onCancel = { onCancelDownload(model) } ) } } else { Icon( imageVector = Icons.Rounded.DownloadForOffline, contentDescription = null ) } } }, placeBottomContentInside = true, bottomContent = model.type?.let { type -> { FlowRow( modifier = Modifier.padding(top = 8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { NeuralModelTypeBadge( type = type, isInverted = false ) model.speed?.let { speed -> NeuralModelSpeedBadge( speed = speed, isInverted = false ) } AnimatedVisibility( visible = downloadProgresses[model.name] == null ) { NeuralModelSizeBadge( model = model, isInverted = false ) } } } } ) }, interactionSource = interactionSource ) } if (downloadedModels.isEmpty() && notDownloadedModels.isEmpty()) { item { Column( modifier = Modifier .fillMaxWidth() .padding( top = 8.dp ) .container( shape = ShapeDefaults.large, resultPadding = 0.dp, color = EnhancedBottomSheetDefaults.contentContainerColor ) .height(256.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 24.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) } } } } } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgress.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components data class NeuralSaveProgress( val doneImages: Int, val totalImages: Int, val doneChunks: Int, val totalChunks: Int ) { val chunkProgress = if (totalChunks > 0) { doneChunks / totalChunks.toFloat() } else { 0f } val totalProgress = if (totalImages > 0) { (doneImages.toFloat() + chunkProgress) / totalImages.toFloat() } else { 0f } val isZero = doneImages == 0 && (totalImages < 2) && doneChunks == 0 && totalChunks == 0 } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/NeuralSaveProgressDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.width import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.feature.ai_tools.presentation.screenLogic.AiToolsComponent @Composable internal fun NeuralSaveProgressDialog( component: AiToolsComponent ) { component.saveProgress?.let { saveProgress -> LoadingDialog( visible = true, onCancelLoading = component::cancelSaving, progress = { saveProgress.totalProgress }, switchToIndicator = saveProgress.isZero, loaderSize = 72.dp, isLayoutSwappable = false, additionalContent = { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { AutoSizeText( text = if (saveProgress.totalImages > 1) { "${saveProgress.doneImages} / ${saveProgress.totalImages}" } else { "${saveProgress.doneChunks} / ${saveProgress.totalChunks}" }, maxLines = 1, fontWeight = FontWeight.Medium, modifier = Modifier.width(it * 0.7f), textAlign = TextAlign.Center ) if (saveProgress.totalImages > 1 && saveProgress.totalChunks > 0) { AutoSizeText( text = "${saveProgress.doneChunks} / ${saveProgress.totalChunks}", maxLines = 1, style = LocalTextStyle.current.copy( fontSize = 12.sp, lineHeight = 12.sp ), color = LocalContentColor.current.copy(0.5f), fontWeight = FontWeight.Normal, modifier = Modifier.width(it * 0.45f), textAlign = TextAlign.Center ) } } } ) } } ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/components/Savers.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.components import androidx.compose.runtime.saveable.listSaver import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel internal val SpeedFiltersSaver = listSaver( save = { state -> state.map { speed -> NeuralModel.Speed.entries.indexOfFirst { it::class.isInstance(speed) } } }, restore = { list -> list.map { NeuralModel.Speed.entries[it] } } ) internal val TypeFiltersSaver = listSaver( save = { state -> state.map { it.name } }, restore = { list -> list.map { NeuralModel.Type.valueOf(it) } } ) ================================================ FILE: feature/ai-tools/src/main/java/com/t8rin/imagetoolbox/feature/ai_tools/presentation/screenLogic/AiToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ai_tools.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.Info import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.utils.state.updateNotNull import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.ai_tools.domain.AiProgressListener import com.t8rin.imagetoolbox.feature.ai_tools.domain.AiToolsRepository import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralModel import com.t8rin.imagetoolbox.feature.ai_tools.domain.model.NeuralParams import com.t8rin.imagetoolbox.feature.ai_tools.presentation.components.NeuralSaveProgress import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.stateIn class AiToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val aiToolsRepository: AiToolsRepository, private val shareProvider: ImageShareProvider, private val imageGetter: ImageGetter, private val fileController: FileController, private val imageCompressor: ImageCompressor, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::updateUris) } } private val _uris = mutableStateOf?>(null) val uris by _uris private val _saveProgress: MutableState = mutableStateOf(null) val saveProgress by _saveProgress private var savingJob: Job? by smartJob { _saveProgress.update { null } } private var downloadJobs: MutableMap = mutableMapOf() val occupiedStorageSize: StateFlow = aiToolsRepository.occupiedStorageSize val downloadedModels: StateFlow> = aiToolsRepository.downloadedModels val notDownloadedModels: StateFlow> = downloadedModels.map { NeuralModel.entries - it.toSet() }.stateIn( scope = componentScope, started = SharingStarted.Eagerly, initialValue = NeuralModel.entries ) val selectedModel: StateFlow = aiToolsRepository.selectedModel private val _downloadProgresses: SnapshotStateMap = mutableStateMapOf() val downloadProgresses: Map = _downloadProgresses private val _params = fileController.savable( scope = componentScope, initial = NeuralParams.Default ) val params by _params private val _imageFormat: MutableState = mutableStateOf(null) val imageFormat by _imageFormat private val aiProgressListener = object : AiProgressListener { override fun onError(error: String) { AppToastHost.showFailureToast(error) } override fun onProgress( currentChunkIndex: Int, totalChunks: Int ) { _saveProgress.updateNotNull { it.copy( doneChunks = currentChunkIndex, totalChunks = totalChunks ) } } } fun selectModel(model: NeuralModel) { componentScope.launch { aiToolsRepository.selectModel(model) registerChanges() } } fun downloadModel(model: NeuralModel) { if (downloadJobs.contains(model.name)) return downloadJobs[model.name] = componentScope.launch { aiToolsRepository .downloadModel(model) .onCompletion { _downloadProgresses.remove(model.name) downloadJobs.remove(model.name) } .catch { _downloadProgresses.remove(model.name) downloadJobs.remove(model.name) } .collect { progress -> _downloadProgresses[model.name] = progress } } } fun cancelDownload(model: NeuralModel) { downloadJobs.remove(model.name)?.cancel() } fun importModel(uri: Uri) { componentScope.launch { _isImageLoading.update { true } when (val result = aiToolsRepository.importModel(uri.toString())) { SaveResult.Skipped -> { AppToastHost.showToast( message = getString(R.string.model_already_downloaded), icon = Icons.Outlined.Info ) } is SaveResult.Success -> { AppToastHost.showToast( message = getString(R.string.model_successfully_imported), icon = Icons.Outlined.CheckCircle ) } else -> parseFileSaveResult(result) } _isImageLoading.update { false } } } fun deleteModel(model: NeuralModel) { componentScope.launch { aiToolsRepository.deleteModel(model) } } fun updateParams( action: NeuralParams.() -> NeuralParams ) { _params.update { action(it) } registerChanges() } fun updateUris(uris: List?) { _uris.value = null _uris.value = uris } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { if (selectedModel.value?.type == NeuralModel.Type.REMOVE_BG && imageFormat == null) { setImageFormat(ImageFormat.Png.Lossless) } delay(400) _saveProgress.update { NeuralSaveProgress( doneImages = 0, totalImages = uris.orEmpty().size, doneChunks = 0, totalChunks = 0 ) } val results = mutableListOf() uris?.forEach { uri -> runSuspendCatching { val (image, imageInfo) = imageGetter.getImage(uri.toString()) ?: return@runSuspendCatching null aiToolsRepository.processImage( image = image, listener = aiProgressListener, params = params )?.let { it to imageInfo.copy( width = it.width, height = it.height, originalUri = uri.toString(), imageFormat = imageFormat ?: imageInfo.imageFormat ) } }.onFailure { results.add( SaveResult.Error.Exception(it) ) }.getOrNull()?.let { (image, imageInfo) -> results.add( fileController.save( ImageSaveTarget( imageInfo = imageInfo, originalUri = uri.toString(), sequenceNumber = _saveProgress.value?.doneImages?.plus(1), data = imageCompressor.compressAndTransform( image = image, imageInfo = imageInfo ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } _saveProgress.updateNotNull { it.copy( doneImages = it.doneImages + 1 ) } } parseSaveResults(results.onSuccess(::registerSave)) _saveProgress.update { null } aiToolsRepository.cleanup() } } fun cancelSaving() { savingJob?.cancel() savingJob = null _saveProgress.update { null } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { if (selectedModel.value?.type == NeuralModel.Type.REMOVE_BG && imageFormat == null) { setImageFormat(ImageFormat.Png.Lossless) } delay(400) _saveProgress.update { NeuralSaveProgress( doneImages = 0, totalImages = uris.orEmpty().size, doneChunks = 0, totalChunks = 0 ) } val list = mutableListOf() uris?.forEach { uri -> runSuspendCatching { val (image, imageInfo) = imageGetter.getImage(uri.toString()) ?: return@runSuspendCatching null aiToolsRepository.processImage( image = image, listener = aiProgressListener, params = params )?.let { it to imageInfo.copy( width = it.width, height = it.height, originalUri = uri.toString(), imageFormat = imageFormat ?: imageInfo.imageFormat ) } }.getOrNull()?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo )?.let { uri -> list.add(uri.toUri()) } } _saveProgress.updateNotNull { it.copy( doneImages = it.doneImages + 1 ) } } onComplete(list) _saveProgress.update { null } aiToolsRepository.cleanup() } } fun shareBitmaps() { cacheImages { uris -> componentScope.launch { shareProvider.shareUris(uris.map { it.toString() }) AppToastHost.showConfetti() } } } fun removeUri(uri: Uri) { _uris.update { it.orEmpty() - uri } } fun addUris(uris: List) { _uris.update { it.orEmpty() + uris } } fun setImageFormat(imageFormat: ImageFormat?) { _imageFormat.update { imageFormat } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): AiToolsComponent } } ================================================ FILE: feature/apng-tools/.gitignore ================================================ /build ================================================ FILE: feature/apng-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.apng_tools" dependencies { implementation(libs.toolbox.apng) implementation(libs.jxl.coder) } ================================================ FILE: feature/apng-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/data/AndroidApngConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.apng_tools.data import android.content.Context import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri import com.awxkee.jxlcoder.JxlCoder import com.awxkee.jxlcoder.JxlDecodingSpeed import com.awxkee.jxlcoder.JxlEffort import com.t8rin.imagetoolbox.core.data.utils.outputStream import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.feature.apng_tools.domain.ApngConverter import com.t8rin.imagetoolbox.feature.apng_tools.domain.ApngParams import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.cancel import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import oupson.apng.decoder.ApngDecoder import oupson.apng.encoder.ApngEncoder import javax.inject.Inject internal class AndroidApngConverter @Inject constructor( private val imageGetter: ImageGetter, private val shareProvider: ImageShareProvider, private val imageScaler: ImageScaler, @ApplicationContext private val context: Context, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ApngConverter { override fun extractFramesFromApng( apngUri: String, imageFormat: ImageFormat, quality: Quality ): Flow = channelFlow { ApngDecoder( context = context, uri = apngUri.toUri() ).decodeAsync(defaultDispatcher) { frame -> if (!currentCoroutineContext().isActive) { currentCoroutineContext().cancel(null) return@decodeAsync } shareProvider.cacheImage( image = frame, imageInfo = ImageInfo( width = frame.width, height = frame.height, imageFormat = imageFormat, quality = quality ) )?.let { send(it) } } } override suspend fun createApngFromImageUris( imageUris: List, params: ApngParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): String? = withContext(defaultDispatcher) { val size = params.size ?: imageGetter.getImage(data = imageUris[0])!!.run { IntegerSize(width, height) } if (size.width <= 0 || size.height <= 0) { onFailure(IllegalArgumentException("Width and height must be > 0")) return@withContext null } shareProvider.cacheData( writeData = { writeable -> val encoder = ApngEncoder( outputStream = writeable.outputStream(), width = size.width, height = size.height, numberOfFrames = imageUris.size ).apply { setOptimiseApng(false) setRepetitionCount(params.repeatCount) setCompressionLevel(params.quality.qualityValue) } imageUris.forEach { uri -> imageGetter.getImage( data = uri, size = size )?.let { encoder.writeFrame( btm = imageScaler.scaleImage( image = imageScaler.scaleImage( image = it, width = size.width, height = size.height, resizeType = ResizeType.Flexible ), width = size.width, height = size.height, resizeType = ResizeType.CenterCrop( canvasColor = Color.Transparent.toArgb() ) ), delay = params.delay.toFloat() ) } onProgress() } encoder.writeEnd() }, filename = "temp_apng.png" ) } override suspend fun convertApngToJxl( apngUris: List, quality: Quality.Jxl, onProgress: suspend (String, ByteArray) -> Unit ) = withContext(defaultDispatcher) { apngUris.forEach { uri -> uri.bytes?.let { apngData -> runSuspendCatching { JxlCoder.Convenience.apng2JXL( apngData = apngData, quality = quality.qualityValue, effort = JxlEffort.entries.first { it.ordinal == quality.effort }, decodingSpeed = JxlDecodingSpeed.entries.first { it.ordinal == quality.speed } ).let { onProgress(uri, it) } } } } } private val String.bytes: ByteArray? get() = context .contentResolver .openInputStream(toUri())?.use { it.readBytes() } } ================================================ FILE: feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/di/ApngToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.apng_tools.di import com.t8rin.imagetoolbox.feature.apng_tools.data.AndroidApngConverter import com.t8rin.imagetoolbox.feature.apng_tools.domain.ApngConverter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ApngToolsModule { @Binds @Singleton fun provideConverter( converter: AndroidApngConverter ): ApngConverter } ================================================ FILE: feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.apng_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import kotlinx.coroutines.flow.Flow interface ApngConverter { fun extractFramesFromApng( apngUri: String, imageFormat: ImageFormat, quality: Quality ): Flow suspend fun createApngFromImageUris( imageUris: List, params: ApngParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): String? suspend fun convertApngToJxl( apngUris: List, quality: Quality.Jxl, onProgress: suspend (String, ByteArray) -> Unit ) } ================================================ FILE: feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/domain/ApngParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.apng_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize data class ApngParams( val size: IntegerSize?, val repeatCount: Int, val delay: Int, val quality: Quality ) { companion object { val Default by lazy { ApngParams( size = null, repeatCount = 1, delay = 1000, quality = Quality.Base(5) ) } } } ================================================ FILE: feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/ApngToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.apng_tools.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Gif import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Apng import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.ImagesPreviewWithSelection import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.image.urisPreview import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.core.utils.isApng import com.t8rin.imagetoolbox.feature.apng_tools.presentation.components.ApngParamsSelector import com.t8rin.imagetoolbox.feature.apng_tools.presentation.screenLogic.ApngToolsComponent @Composable fun ApngToolsContent( component: ApngToolsComponent ) { val imagePicker = rememberImagePicker(onSuccess = component::setImageUris) val pickSingleApngLauncher = rememberFilePicker( mimeType = MimeType.Png, onSuccess = { uri: Uri -> if (uri.isApng()) { component.setApngUri(uri) } else { AppToastHost.showToast( message = getString(R.string.select_apng_image_to_start), icon = Icons.Rounded.Apng ) } } ) val pickMultipleApngLauncher = rememberFilePicker( mimeType = MimeType.Png, onSuccess = { list: List -> list.filter { it.isApng() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_apng_image_to_start), icon = Icons.Rounded.Apng ) } else { component.setType( Screen.ApngTools.Type.ApngToJxl(uris) ) } } } ) val addApngLauncher = rememberFilePicker( mimeType = MimeType.Png, onSuccess = { list: List -> list.filter { it.isApng() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_gif_image_to_start), icon = Icons.Filled.Gif ) } else { component.setType( Screen.ApngTools.Type.ApngToJxl( (component.type as? Screen.ApngTools.Type.ApngToJxl)?.apngUris?.plus( uris )?.distinct() ) ) } } } ) val saveApngLauncher = rememberFileCreator( mimeType = MimeType.Apng, onSuccess = component::saveApngTo ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val isPortrait by isPortraitOrientationAsState() AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = when (val type = component.type) { null -> stringResource(R.string.apng_tools) else -> stringResource(type.title) }, input = component.type, isLoading = component.isLoading, size = null ) }, onGoBack = onBack, topAppBarPersistentActions = { if (component.type == null) TopAppBarEmoji() val pagesSize by remember(component.imageFrames, component.convertedImageUris) { derivedStateOf { component.imageFrames.getFramePositions(component.convertedImageUris.size).size } } val isApngToImage = component.type is Screen.ApngTools.Type.ApngToImage AnimatedVisibility( visible = isApngToImage && pagesSize != component.convertedImageUris.size, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = component::selectAllConvertedImages ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = isApngToImage && pagesSize != 0 ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { pagesSize.takeIf { it != 0 }?.let { Spacer(Modifier.width(8.dp)) Text( text = it.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) } EnhancedIconButton( onClick = component::clearConvertedImagesSelection ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } }, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = !component.isLoading && component.type != null, onShare = component::performSharing, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, imagePreview = { AnimatedContent( targetState = component.isLoading to component.type ) { (loading, type) -> Box( contentAlignment = Alignment.Center, modifier = if (loading) { Modifier.padding(32.dp) } else Modifier ) { if (loading || type == null) { EnhancedLoadingIndicator() } else { when (type) { is Screen.ApngTools.Type.ApngToImage -> { ImagesPreviewWithSelection( imageUris = component.convertedImageUris, imageFrames = component.imageFrames, onFrameSelectionChange = component::updateApngFrames, isPortrait = isPortrait, isLoadingImages = component.isLoadingApngImages ) } is Screen.ApngTools.Type.ApngToJxl -> { UrisPreview( modifier = Modifier.urisPreview(), uris = type.apngUris ?: emptyList(), isPortrait = true, onRemoveUri = { component.setType( Screen.ApngTools.Type.ApngToJxl(type.apngUris?.minus(it)) ) }, onAddUris = addApngLauncher::pickFile ) } is Screen.ApngTools.Type.ImageToApng -> Unit } } } } }, placeImagePreview = component.type !is Screen.ApngTools.Type.ImageToApng, showImagePreviewAsStickyHeader = false, autoClearFocus = false, controls = { when (val type = component.type) { is Screen.ApngTools.Type.ApngToImage -> { Spacer(modifier = Modifier.height(16.dp)) ImageFormatSelector( value = component.imageFormat, quality = if (component.type is Screen.ApngTools.Type.ApngToJxl) { component.jxlQuality } else { component.params.quality }, onValueChange = component::setImageFormat, entries = ImageFormatGroup.alphaContainedEntries ) Spacer(modifier = Modifier.height(8.dp)) QualitySelector( imageFormat = component.imageFormat, quality = component.params.quality, onQualityChange = component::setQuality ) Spacer(modifier = Modifier.height(16.dp)) } is Screen.ApngTools.Type.ImageToApng -> { val addImagesToPdfPicker = rememberImagePicker(onSuccess = component::addImageToUris) Spacer(modifier = Modifier.height(16.dp)) ImageReorderCarousel( images = type.imageUris, onReorder = component::reorderImageUris, onNeedToAddImage = addImagesToPdfPicker::pickImage, onNeedToRemoveImageAt = component::removeImageAt, onNavigate = component.onNavigate ) Spacer(modifier = Modifier.height(8.dp)) ApngParamsSelector( value = component.params, onValueChange = component::updateParams ) Spacer(modifier = Modifier.height(16.dp)) } is Screen.ApngTools.Type.ApngToJxl -> { QualitySelector( imageFormat = ImageFormat.Jxl.Lossy, quality = component.jxlQuality, onQualityChange = component::setJxlQuality ) } null -> Unit } }, contentPadding = animateDpAsState( if (component.type == null) 12.dp else 20.dp ).value, buttons = { actions -> val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it, onApngSaveResult = saveApngLauncher::make ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.type == null, onSecondaryButtonClick = { when (component.type) { is Screen.ApngTools.Type.ApngToImage -> pickSingleApngLauncher.pickFile() is Screen.ApngTools.Type.ApngToJxl -> pickMultipleApngLauncher.pickFile() else -> imagePicker.pickImage() } }, isPrimaryButtonVisible = component.canSave, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { if (component.type is Screen.ApngTools.Type.ImageToApng) { saveBitmaps(null) } else showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, showNullDataButtonAsContainer = true, onSecondaryButtonLongClick = if (component.type is Screen.ApngTools.Type.ImageToApng || component.type == null) { { showOneTimeImagePickingDialog = true } } else null ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, insetsForNoData = WindowInsets(0), noDataControls = { val types = remember { Screen.ApngTools.Type.entries } val preference1 = @Composable { PreferenceItem( title = stringResource(types[0].title), subtitle = stringResource(types[0].subtitle), startIcon = types[0].icon, modifier = Modifier.fillMaxWidth(), onClick = imagePicker::pickImage ) } val preference2 = @Composable { PreferenceItem( title = stringResource(types[1].title), subtitle = stringResource(types[1].subtitle), startIcon = types[1].icon, modifier = Modifier.fillMaxWidth(), onClick = pickSingleApngLauncher::pickFile ) } val preference3 = @Composable { PreferenceItem( title = stringResource(types[2].title), subtitle = stringResource(types[2].subtitle), startIcon = types[2].icon, modifier = Modifier.fillMaxWidth(), onClick = pickMultipleApngLauncher::pickFile ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() Spacer(modifier = Modifier.height(8.dp)) preference3() } } else { val direction = LocalLayoutDirection.current Column( horizontalAlignment = Alignment.CenterHorizontally ) { Row( modifier = Modifier.padding( WindowInsets.displayCutout.asPaddingValues().let { PaddingValues( start = it.calculateStartPadding(direction), end = it.calculateEndPadding(direction) ) } ) ) { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } Spacer(modifier = Modifier.height(8.dp)) preference3.withModifier(modifier = Modifier.fillMaxWidth(0.5f)) } } }, canShowScreenData = component.type != null ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = component::clearAll, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/components/ApngParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.apng_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PhotoSizeSelectLarge import androidx.compose.material.icons.outlined.RepeatOne import androidx.compose.material.icons.outlined.Timelapse import androidx.compose.material.icons.rounded.Stream import androidx.compose.material3.LocalContentColor import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.apng_tools.domain.ApngParams import kotlin.math.roundToInt @Composable fun ApngParamsSelector( value: ApngParams, onValueChange: (ApngParams) -> Unit ) { Column { val size = value.size ?: IntegerSize.Undefined AnimatedVisibility(size.isDefined()) { ResizeImageField( imageInfo = ImageInfo(size.width, size.height), originalSize = null, onWidthChange = { onValueChange( value.copy( size = size.copy(width = it) ) ) }, onHeightChange = { onValueChange( value.copy( size = size.copy(height = it) ) ) } ) } Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.use_size_of_first_frame), subtitle = stringResource(id = R.string.use_size_of_first_frame_sub), checked = value.size == null, onClick = { onValueChange( value.copy(size = if (it) null else IntegerSize(1000, 1000)) ) }, startIcon = Icons.Outlined.PhotoSizeSelectLarge, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.quality.qualityValue, title = stringResource(R.string.effort), icon = Icons.Rounded.Stream, valueRange = 0f..9f, steps = 9, internalStateTransformation = { it.toInt().coerceIn(0..9).toFloat() }, onValueChange = { onValueChange( value.copy( quality = Quality.Base(it.toInt()) ) ) } ) { Text( text = stringResource( R.string.effort_sub, 0, 9 ), fontSize = 12.sp, textAlign = TextAlign.Center, lineHeight = 12.sp, color = LocalContentColor.current.copy(0.5f), modifier = Modifier .padding(4.dp) .container(ShapeDefaults.large) .padding(4.dp) ) } Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.repeatCount, icon = Icons.Outlined.RepeatOne, title = stringResource(id = R.string.repeat_count), valueRange = 1f..10f, steps = 9, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( repeatCount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.delay, icon = Icons.Outlined.Timelapse, title = stringResource(id = R.string.frame_delay), valueRange = 1f..4000f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( delay = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) } } ================================================ FILE: feature/apng-tools/src/main/java/com/t8rin/imagetoolbox/feature/apng_tools/presentation/screenLogic/ApngToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.feature.apng_tools.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.FileSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.apng_tools.domain.ApngConverter import com.t8rin.imagetoolbox.feature.apng_tools.domain.ApngParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.flow.onCompletion class ApngToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted initialType: Screen.ApngTools.Type?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val fileController: FileController, private val filenameCreator: FilenameCreator, private val apngConverter: ApngConverter, private val shareProvider: ShareProvider, defaultDispatchersHolder: DispatchersHolder ) : BaseComponent(defaultDispatchersHolder, componentContext) { init { debounce { initialType?.let(::setType) } } private val _type: MutableState = mutableStateOf(null) val type by _type private val _isLoading: MutableState = mutableStateOf(false) val isLoading by _isLoading private val _isLoadingApngImages: MutableState = mutableStateOf(false) val isLoadingApngImages by _isLoadingApngImages private val _params: MutableState = mutableStateOf(ApngParams.Default) val params by _params private val _convertedImageUris: MutableState> = mutableStateOf(emptyList()) val convertedImageUris by _convertedImageUris private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat by _imageFormat private val _imageFrames: MutableState = mutableStateOf(ImageFrames.All) val imageFrames by _imageFrames private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _jxlQuality: MutableState = mutableStateOf(Quality.Jxl()) val jxlQuality by _jxlQuality private var _outputApngUri: String? = null fun setType(type: Screen.ApngTools.Type) { when (type) { is Screen.ApngTools.Type.ApngToImage -> { type.apngUri?.let { setApngUri(it) } ?: _type.update { null } } is Screen.ApngTools.Type.ImageToApng -> { _type.update { type } } is Screen.ApngTools.Type.ApngToJxl -> { _type.update { type } } } } fun setImageUris(uris: List) { clearAll() _type.update { Screen.ApngTools.Type.ImageToApng(uris) } } private var collectionJob: Job? by smartJob { _isLoading.update { false } } fun setApngUri(uri: Uri) { clearAll() _type.update { Screen.ApngTools.Type.ApngToImage(uri) } updateApngFrames(ImageFrames.All) collectionJob = componentScope.launch { _isLoading.update { true } _isLoadingApngImages.update { true } apngConverter.extractFramesFromApng( apngUri = uri.toString(), imageFormat = imageFormat, quality = params.quality ).onCompletion { _isLoading.update { false } _isLoadingApngImages.update { false } }.collect { nextUri -> if (isLoading) { _isLoading.update { false } } _convertedImageUris.update { it + nextUri } } } } fun clearAll() { collectionJob = null _type.update { null } _convertedImageUris.update { emptyList() } _outputApngUri = null savingJob = null updateParams(ApngParams.Default) registerChangesCleared() } fun updateApngFrames(imageFrames: ImageFrames) { _imageFrames.update { imageFrames } registerChanges() } fun clearConvertedImagesSelection() = updateApngFrames(ImageFrames.ManualSelection(emptyList())) fun selectAllConvertedImages() = updateApngFrames(ImageFrames.All) private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveApngTo(uri: Uri) { savingJob = trackProgress { _isSaving.value = true _outputApngUri?.let { apngUri -> fileController.transferBytes( fromUri = apngUri, toUri = uri.toString(), ).also(::parseFileSaveResult).onSuccess(::registerSave) } _isSaving.value = false _outputApngUri = null } } fun saveBitmaps( oneTimeSaveLocationUri: String?, onApngSaveResult: (String) -> Unit ) { _isSaving.value = false savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.ApngTools.Type.ApngToImage -> { val results = mutableListOf() type.apngUri?.toString()?.also { apngUri -> _left.value = 0 apngConverter.extractFramesFromApng( apngUri = apngUri, imageFormat = imageFormat, quality = params.quality ).onCompletion { parseSaveResults(results.onSuccess(::registerSave)) }.collect { uri -> imageGetter.getImage( data = uri, originalSize = true )?.let { localBitmap -> if ((done + 1) in imageFrames.getFramePositions(convertedImageUris.size + 10)) { val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = uri, sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( imageFormat = imageFormat, quality = params.quality, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value++ } } } is Screen.ApngTools.Type.ImageToApng -> { _left.value = type.imageUris?.size ?: -1 _outputApngUri = type.imageUris?.map { it.toString() }?.let { list -> apngConverter.createApngFromImageUris( imageUris = list, params = params, onProgress = { _done.update { it + 1 } }, onFailure = { parseSaveResults(listOf(SaveResult.Error.Exception(it))) } )?.also { onApngSaveResult("APNG_${timestamp()}.png") registerSave() } } } is Screen.ApngTools.Type.ApngToJxl -> { val results = mutableListOf() val apngUris = type.apngUris?.map { it.toString() } ?: emptyList() _left.value = apngUris.size apngConverter.convertApngToJxl( apngUris = apngUris, quality = jxlQuality ) { uri, jxlBytes -> results.add( fileController.save( saveTarget = JxlSaveTarget(uri, jxlBytes), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.update { it + 1 } } parseSaveResults(results.onSuccess(::registerSave)) } null -> Unit } _isSaving.value = false } } private fun JxlSaveTarget( uri: String, jxlBytes: ByteArray ): SaveTarget = FileSaveTarget( originalUri = uri, filename = jxlFilename(uri), data = jxlBytes, imageFormat = ImageFormat.Jxl.Lossless ) private fun jxlFilename( uri: String ): String = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = ImageInfo( imageFormat = ImageFormat.Jxl.Lossless, originalUri = uri ), originalUri = uri, sequenceNumber = done + 1, metadata = null, data = ByteArray(0) ), forceNotAddSizeInFilename = true ) fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun reorderImageUris(uris: List?) { if (type is Screen.ApngTools.Type.ImageToApng) { _type.update { Screen.ApngTools.Type.ImageToApng(uris) } } registerChanges() } fun addImageToUris(uris: List) { val type = _type.value if (type is Screen.ApngTools.Type.ImageToApng) { _type.update { val newUris = type.imageUris?.plus(uris)?.toSet()?.toList() Screen.ApngTools.Type.ImageToApng(newUris) } } registerChanges() } fun removeImageAt(index: Int) { val type = _type.value if (type is Screen.ApngTools.Type.ImageToApng) { _type.update { val newUris = type.imageUris?.toMutableList()?.apply { removeAt(index) } Screen.ApngTools.Type.ImageToApng(newUris) } } registerChanges() } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } registerChanges() } fun setQuality(quality: Quality) { updateParams(params.copy(quality = quality)) } fun updateParams(params: ApngParams) { _params.update { params } registerChanges() } fun performSharing() { cacheImages { uris -> componentScope.launch { shareProvider.shareUris(uris.map { it.toString() }) AppToastHost.showConfetti() } } } fun setJxlQuality(quality: Quality) { _jxlQuality.update { (quality as? Quality.Jxl) ?: Quality.Jxl() } registerChanges() } fun cacheImages( onComplete: (List) -> Unit ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.ApngTools.Type.ApngToImage -> { _left.value = -1 val positions = imageFrames.getFramePositions(convertedImageUris.size).map { it - 1 } val uris = convertedImageUris.filterIndexed { index, _ -> index in positions } onComplete(uris.map { it.toUri() }) } is Screen.ApngTools.Type.ImageToApng -> { _left.value = type.imageUris?.size ?: -1 type.imageUris?.map { it.toString() }?.let { list -> apngConverter.createApngFromImageUris( imageUris = list, params = params, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) }, onFailure = {} )?.also { uri -> onComplete(listOf(uri.toUri())) } } } is Screen.ApngTools.Type.ApngToJxl -> { val results = mutableListOf() val apngUris = type.apngUris?.map { it.toString() } ?: emptyList() _left.value = apngUris.size apngConverter.convertApngToJxl( apngUris = apngUris, quality = jxlQuality ) { uri, jxlBytes -> results.add( shareProvider.cacheByteArray( byteArray = jxlBytes, filename = jxlFilename(uri) ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } onComplete(results.mapNotNull { it?.toUri() }) } null -> Unit } _isSaving.value = false } } val canSave: Boolean get() = (imageFrames == ImageFrames.All) .or(type is Screen.ApngTools.Type.ImageToApng) .or((imageFrames as? ImageFrames.ManualSelection)?.framePositions?.isNotEmpty() == true) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialType: Screen.ApngTools.Type?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ApngToolsComponent } } ================================================ FILE: feature/ascii-art/.gitignore ================================================ /build ================================================ FILE: feature/ascii-art/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.ascii_art" dependencies { implementation(projects.feature.filters) implementation(projects.lib.ascii) } ================================================ FILE: feature/ascii-art/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/data/AndroidAsciiConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ascii_art.data import android.graphics.Bitmap import com.t8rin.ascii.ASCIIConverter import com.t8rin.ascii.Gradient import com.t8rin.ascii.toMapper import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.feature.ascii_art.domain.AsciiConverter import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidAsciiConverter @Inject constructor( dispatchersHolder: DispatchersHolder ) : AsciiConverter, DispatchersHolder by dispatchersHolder { override suspend fun imageToAscii( image: Bitmap, fontSize: Float, gradient: String ): String = withContext(defaultDispatcher) { ASCIIConverter( fontSize = fontSize, mapper = Gradient(gradient).toMapper() ).convertToAscii(image) } } ================================================ FILE: feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/di/AsciiArtModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ascii_art.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.ascii_art.data.AndroidAsciiConverter import com.t8rin.imagetoolbox.feature.ascii_art.domain.AsciiConverter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface AsciiArtModule { @Binds @Singleton fun converter(impl: AndroidAsciiConverter): AsciiConverter } ================================================ FILE: feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/domain/AsciiConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ascii_art.domain interface AsciiConverter { suspend fun imageToAscii( image: Image, fontSize: Float, gradient: String ): String } ================================================ FILE: feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/AsciiArtContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ascii_art.presentation import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.utils.safeAspectRatio import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.shareText import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.ascii_art.presentation.components.AsciiArtControls import com.t8rin.imagetoolbox.feature.ascii_art.presentation.screenLogic.AsciiArtComponent @Composable fun AsciiArtContent( component: AsciiArtComponent ) { AutoContentBasedColors(component.uri) val imagePicker = rememberImagePicker(onSuccess = component::setUri) val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialUri != null ) val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } val transformations by remember( component.uri, component.gradient, component.fontSize, component.isInvertImage ) { derivedStateOf { component.getAsciiTransformations() } } ZoomModalSheet( data = component.uri, visible = showZoomSheet, onDismiss = { showZoomSheet = false }, transformations = transformations ) AdaptiveLayoutScreen( shouldDisableBackHandler = true, title = { TopAppBarTitle( title = stringResource(R.string.ascii_art), input = component.uri.takeIf { it != Uri.EMPTY }, isLoading = component.isImageLoading, size = null ) }, onGoBack = component.onGoBack, topAppBarPersistentActions = { if (component.uri == Uri.EMPTY) { TopAppBarEmoji() } ZoomButton( onClick = { showZoomSheet = true }, visible = component.uri != Uri.EMPTY ) }, actions = { ShareButton( enabled = component.uri != Uri.EMPTY, onShare = { component.convertToAsciiString { appContext.shareText(it) } }, ) }, imagePreview = { Box( contentAlignment = Alignment.Center ) { var aspectRatio by remember { mutableFloatStateOf(1f) } Picture( model = component.uri, modifier = Modifier .container(MaterialTheme.shapes.medium) .aspectRatio(aspectRatio), onSuccess = { aspectRatio = it.result.image.toBitmap().safeAspectRatio }, isLoadingFromDifferentPlace = component.isImageLoading, shape = MaterialTheme.shapes.medium, contentScale = ContentScale.FillBounds, transformations = transformations ) if (component.isImageLoading) EnhancedLoadingIndicator() } }, controls = { AsciiArtControls(component) }, buttons = { var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uri == Uri.EMPTY, onSecondaryButtonClick = pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, primaryButtonIcon = Icons.Rounded.ContentCopy, onPrimaryButtonClick = { component.convertToAsciiString(Clipboard::copy) }, actions = { if (isPortrait) it() } ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, canShowScreenData = component.uri != Uri.EMPTY, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } } ) } ================================================ FILE: feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/components/AsciiArtControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ascii_art.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Build import androidx.compose.material.icons.rounded.InvertColors import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.widget.filterItem.AsciiParamsSelector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.ascii_art.presentation.screenLogic.AsciiArtComponent @Composable internal fun AsciiArtControls( component: AsciiArtComponent ) { Column( modifier = Modifier .container( shape = ShapeDefaults.large, resultPadding = 0.dp ) ) { TitleItem( text = stringResource(R.string.params), icon = Icons.Rounded.Build, ) Box( modifier = Modifier .padding( horizontal = 12.dp ) ) { AsciiParamsSelector( value = component.asciiParams, onValueChange = { params -> component.setGradient(params.gradient) component.setFontSize(params.fontSize) }, itemShapes = { ShapeDefaults.byIndex( index = it, size = 3 ) } ) } Spacer(Modifier.height(4.dp)) PreferenceRowSwitch( title = stringResource(R.string.invert_colors), subtitle = stringResource(R.string.invert_colors_ascii_sub), startIcon = Icons.Rounded.InvertColors, checked = component.isInvertImage, onClick = { component.toggleIsInvertImage() }, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp) ) Spacer(Modifier.height(12.dp)) } } ================================================ FILE: feature/ascii-art/src/main/java/com/t8rin/imagetoolbox/feature/ascii_art/presentation/screenLogic/AsciiArtComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.ascii_art.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import coil3.transform.Transformation import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.params.AsciiParams import com.t8rin.imagetoolbox.core.filters.presentation.model.UiAsciiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiNegativeFilter import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.core.settings.presentation.model.asFontType import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.toCoil import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.ascii_art.domain.AsciiConverter import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach class AsciiArtComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, private val imageGetter: ImageGetter, private val asciiConverter: AsciiConverter, private val filterProvider: FilterProvider, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _uri: MutableState = mutableStateOf(Uri.EMPTY) val uri: Uri by _uri private val _asciiParams: MutableState = mutableStateOf(AsciiParams.Default.copy(isGrayscale = true)) val asciiParams: AsciiParams by _asciiParams val gradient: String get() = asciiParams.gradient val fontSize: Float get() = asciiParams.fontSize private val _isInvertImage: MutableState = mutableStateOf(false) val isInvertImage: Boolean by _isInvertImage init { debounce { initialUri?.let(::setUri) } settingsProvider.settingsState.onEach { settings -> _asciiParams.update { it.copy(font = settings.font.asFontType()) } }.launchIn(componentScope) } fun setUri(uri: Uri) { _uri.update { uri } } fun convertToAsciiString( onSuccess: (String) -> Unit ) { debouncedImageCalculation { imageGetter.getImage(uri)?.let { image -> onSuccess( asciiConverter.imageToAscii( image = image, fontSize = fontSize, gradient = gradient ) ) } } } fun setGradient(gradient: String) { _asciiParams.update { it.copy(gradient = gradient) } } fun setFontSize(fontSize: Float) { _asciiParams.update { it.copy(fontSize = fontSize) } } fun toggleIsInvertImage() { _isInvertImage.update { !it } } fun getAsciiTransformations(): List = buildList { if (isInvertImage) add(UiNegativeFilter()) add(UiAsciiFilter(asciiParams)) }.map { filterProvider.filterToTransformation(it).toCoil() } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, ): AsciiArtComponent } } ================================================ FILE: feature/audio-cover-extractor/.gitignore ================================================ /build ================================================ FILE: feature/audio-cover-extractor/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.audio_cover_extractor" ================================================ FILE: feature/audio-cover-extractor/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/data/AndroidAudioCoverRetriever.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.audio_cover_extractor.data import android.content.Context import android.graphics.Bitmap import android.media.MediaMetadataRetriever import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain.AudioCoverRetriever import com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain.model.AudioCoverResult import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidAudioCoverRetriever @Inject constructor( @ApplicationContext private val context: Context, private val imageCompressor: ImageCompressor, private val shareProvider: ShareProvider, private val imageGetter: ImageGetter, dispatchersHolder: DispatchersHolder, resourceManager: ResourceManager ) : AudioCoverRetriever, DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { override suspend fun loadCover( audioUri: String ): AudioCoverResult = withContext(defaultDispatcher) { val pictureData = runCatching { MediaMetadataRetriever().run { setDataSource( context, audioUri.toUri() ) embeddedPicture ?: frameAtTime } }.onFailure { return@withContext AudioCoverResult.Failure(it.message.orEmpty()) }.getOrNull() ensureActive() val coverUri = pictureData?.let { imageGetter.getImage( data = pictureData, originalSize = true )?.let { bitmap -> val originalName = audioUri.toUri().filename(context)?.substringBeforeLast('.') ?: "AUDIO_${System.currentTimeMillis()}" shareProvider.cacheData( writeData = { it.writeBytes( imageCompressor.compress( image = bitmap, imageFormat = ImageFormat.Png.Lossless, quality = Quality.Base() ) ) }, filename = "$originalName.png" ) } } if (coverUri.isNullOrEmpty()) { AudioCoverResult.Failure(getString(R.string.no_image)) } else { AudioCoverResult.Success(coverUri) } } override suspend fun loadCover( audioData: ByteArray ): AudioCoverResult { val audioUri = shareProvider.cacheData( writeData = { it.writeBytes(audioData) }, filename = "Audio_data_${System.currentTimeMillis()}.mp3" ) ?: return AudioCoverResult.Failure((getString(R.string.filename_is_not_set))) return loadCover( audioUri = audioUri ) } } ================================================ FILE: feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/di/AudioCoverExtractorModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.audio_cover_extractor.di import com.t8rin.imagetoolbox.feature.audio_cover_extractor.data.AndroidAudioCoverRetriever import com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain.AudioCoverRetriever import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface AudioCoverExtractorModule { @Binds @Singleton fun extractor( impl: AndroidAudioCoverRetriever ): AudioCoverRetriever } ================================================ FILE: feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/AudioCoverRetriever.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain import com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain.model.AudioCoverResult interface AudioCoverRetriever { suspend fun loadCover( audioUri: String ): AudioCoverResult suspend fun loadCover( audioData: ByteArray ): AudioCoverResult } ================================================ FILE: feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/domain/model/AudioCoverResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain.model sealed interface AudioCoverResult { data class Success(val coverUri: String) : AudioCoverResult data class Failure(val message: String) : AudioCoverResult } ================================================ FILE: feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/AudioCoverExtractorContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Album import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MusicAdd import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCircularProgressIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.FileNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.image.urisPreview import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui.screenLogic.AudioCoverExtractorComponent import kotlinx.coroutines.delay @Composable fun AudioCoverExtractorContent( component: AudioCoverExtractorComponent ) { LaunchedEffect(component.initialUris, component.covers) { delay(500) if (component.initialUris != null && component.covers.isEmpty()) { AppToastHost.showFailureToast(R.string.no_covers_found) } } var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val audioPicker = rememberFilePicker( mimeType = MimeType.Audio, onSuccess = component::updateCovers ) AutoFilePicker( onAutoPick = audioPicker::pickFile, isPickedAlready = !component.initialUris.isNullOrEmpty() ) val addAudioPicker = rememberFilePicker( mimeType = MimeType.Audio, onSuccess = component::addCovers ) val isPortrait by isPortraitOrientationAsState() val covers = component.covers AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { Text( text = stringResource(R.string.audio_cover_extractor), modifier = Modifier.marquee() ) }, topAppBarPersistentActions = { if (isPortrait) { TopAppBarEmoji() } }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( onShare = { component.performSharing( onComplete = AppToastHost::showConfetti ) }, onEdit = { component.cacheImages { editSheetData = it } }, enabled = !component.isSaving && covers.isNotEmpty() ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, imagePreview = { val uris by remember(covers) { derivedStateOf { covers.map { it.imageCoverUri ?: it.audioUri } + Uri.EMPTY } } UrisPreview( modifier = Modifier.urisPreview(), uris = uris, isPortrait = true, onRemoveUri = component::removeCover, onAddUris = addAudioPicker::pickFile, errorContent = { index, width -> Box( modifier = Modifier .background(MaterialTheme.colorScheme.scrim.copy(0.5f)) .fillMaxSize(), contentAlignment = Alignment.Center ) { val cover = covers[index] val size = (width + 16.dp) / 3f Icon( imageVector = Icons.Rounded.Album, contentDescription = null, modifier = Modifier .size(size) .padding(8.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant ) AnimatedVisibility( visible = cover.isLoading, enter = fadeIn(), exit = fadeOut() ) { EnhancedCircularProgressIndicator( modifier = Modifier.size(size) ) } } }, showScrimForNonSuccess = false, showTransparencyChecker = false, filenameSource = { index -> covers[index].audioUri }, onNavigate = component.onNavigate ) }, showImagePreviewAsStickyHeader = false, noDataControls = { FileNotPickedWidget( onPickFile = audioPicker::pickFile, text = stringResource(R.string.pick_audio_to_start) ) }, controls = { ImageFormatSelector( value = component.imageFormat, quality = component.quality, onValueChange = component::setImageFormat ) if (component.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = component.imageFormat, quality = component.quality, onQualityChange = component::setQuality ) }, buttons = { val save: (oneTimeSaveLocationUri: String?) -> Unit = { uri -> component.save( oneTimeSaveLocationUri = uri ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } val canSave by remember(covers) { derivedStateOf { covers.none { cover -> cover.isLoading } } } BottomButtonsBlock( isNoData = covers.isEmpty(), isPrimaryButtonEnabled = canSave, onSecondaryButtonClick = audioPicker::pickFile, isPrimaryButtonVisible = covers.isNotEmpty(), secondaryButtonIcon = Icons.Rounded.MusicAdd, secondaryButtonText = stringResource(R.string.pick_audio), onPrimaryButtonClick = { save(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() }, ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = save ) }, canShowScreenData = covers.isNotEmpty() ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/components/AudioWithCover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui.components import android.net.Uri data class AudioWithCover( val audioUri: Uri, val imageCoverUri: Uri?, val isLoading: Boolean ) ================================================ FILE: feature/audio-cover-extractor/src/main/java/com/t8rin/imagetoolbox/feature/audio_cover_extractor/ui/screenLogic/AudioCoverExtractorComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain.AudioCoverRetriever import com.t8rin.imagetoolbox.feature.audio_cover_extractor.domain.model.AudioCoverResult import com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui.components.AudioWithCover import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.ensureActive import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock class AudioCoverExtractorComponent @AssistedInject constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val shareProvider: ImageShareProvider, private val audioCoverRetriever: AudioCoverRetriever, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::updateCovers) } } private val _covers: MutableState> = mutableStateOf(emptyList()) val covers by _covers private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat by _imageFormat private val _quality: MutableState = mutableStateOf(Quality.Base()) val quality by _quality private var savingJob: Job? by smartJob { _isSaving.update { false } } private val mutex = Mutex() private fun Bitmap.imageInfo() = ImageInfo( width = width, height = height, quality = quality, imageFormat = imageFormat ) fun updateCovers(uris: List) { componentScope.launch { val audioUris = uris.distinct() mutex.withLock { _covers.value = audioUris.map { AudioWithCover( audioUri = it, imageCoverUri = null, isLoading = true ) } } audioUris.forEach { audioUri -> launch { val result = audioCoverRetriever.loadCover(audioUri.toString()) val success = result as? AudioCoverResult.Success ensureActive() val newCover = AudioWithCover( audioUri = audioUri, imageCoverUri = success?.coverUri?.toUri(), isLoading = false ) mutex.withLock { _covers.update { covers -> covers.toMutableList().apply { val index = indexOfFirst { it.audioUri == audioUri }.takeIf { it >= 0 } ?: return@update covers + newCover set(index, newCover) } } } } } } } fun addCovers(uris: List) { componentScope.launch { val audioUris = uris.distinct() mutex.withLock { _covers.update { covers -> val newList = covers + audioUris.map { AudioWithCover( audioUri = it, imageCoverUri = null, isLoading = true ) } newList.distinctBy { it.audioUri } } } covers.filter { it.imageCoverUri == null }.forEach { (audioUri) -> launch { val result = audioCoverRetriever.loadCover(audioUri.toString()) val success = result as? AudioCoverResult.Success ensureActive() val newCover = AudioWithCover( audioUri = audioUri, imageCoverUri = success?.coverUri?.toUri(), isLoading = false ) mutex.withLock { _covers.update { covers -> covers.toMutableList().apply { val index = indexOfFirst { it.audioUri == audioUri }.takeIf { it >= 0 } ?: return@update covers + newCover set(index, newCover) } } } } } } } fun performSharing(onComplete: () -> Unit) { cacheImages { uris -> componentScope.launch { shareProvider.shareUris(uris.map { it.toString() }) onComplete() } } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.update { true } val targetUris = covers.mapNotNull { it.imageCoverUri?.toString() } _done.update { 0 } _left.update { targetUris.size } val list = targetUris.mapNotNull { uri -> imageGetter.getImage(data = uri)?.let { image -> shareProvider.cacheImage( image = image, imageInfo = image.imageInfo() )?.toUri() }.also { _done.value += 1 updateProgress( done = done, total = left ) } } _isSaving.update { false } onComplete(list) } } fun removeCover(coverUri: Uri) { _covers.update { list -> list.filter { it.imageCoverUri != coverUri && it.audioUri != coverUri } } } fun save( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { val results = mutableListOf() val coverUris = covers.mapNotNull { it.imageCoverUri?.toString() } _done.value = 0 _left.value = coverUris.size coverUris.forEach { coverUri -> runSuspendCatching { imageGetter.getImage(data = coverUri) }.getOrNull()?.let { bitmap -> results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = bitmap.imageInfo(), metadata = null, originalUri = coverUri, sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = bitmap, imageInfo = bitmap.imageInfo() ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun setQuality(quality: Quality) { _quality.update { quality } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): AudioCoverExtractorComponent } } ================================================ FILE: feature/base64-tools/.gitignore ================================================ /build ================================================ FILE: feature/base64-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.base64_tools" ================================================ FILE: feature/base64-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/data/AndroidBase64Converter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.base64_tools.data import android.graphics.Bitmap import android.util.Base64 import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.feature.base64_tools.domain.Base64Converter import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidBase64Converter @Inject constructor( private val imageGetter: ImageGetter, private val fileController: FileController, private val shareProvider: ShareProvider, private val imageCompressor: ImageCompressor, dispatchersHolder: DispatchersHolder ) : Base64Converter, DispatchersHolder by dispatchersHolder { override suspend fun decode( base64: String ): String? = withContext(ioDispatcher) { val decoded = runCatching { Base64.decode(base64, Base64.DEFAULT or Base64.NO_WRAP) }.getOrNull() ?: return@withContext null imageGetter.getImage(decoded)?.let { bitmap -> shareProvider.cacheData( writeData = { it.writeBytes( imageCompressor.compress( image = bitmap, imageFormat = ImageFormat.Png.Lossless, quality = Quality.Base() ) ) }, filename = "Base64_decoded_${System.currentTimeMillis()}.png" ) } } override suspend fun encode( uri: String ): String = withContext(ioDispatcher) { Base64.encodeToString(fileController.readBytes(uri), Base64.DEFAULT or Base64.NO_WRAP) } } ================================================ FILE: feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/di/Base64ToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.base64_tools.di import com.t8rin.imagetoolbox.feature.base64_tools.data.AndroidBase64Converter import com.t8rin.imagetoolbox.feature.base64_tools.domain.Base64Converter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface Base64ToolsModule { @Binds @Singleton fun provideConverter( impl: AndroidBase64Converter ): Base64Converter } ================================================ FILE: feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/domain/Base64Converter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.base64_tools.domain interface Base64Converter { /** * @param base64 - string to decode to uri * @return uri **/ suspend fun decode(base64: String): String? /** * @param uri - uri to decode * @return base64 **/ suspend fun encode(uri: String): String } ================================================ FILE: feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/Base64ToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.base64_tools.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.toBitmap import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.theme.takeUnless import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.base64_tools.presentation.components.Base64ToolsTiles import com.t8rin.imagetoolbox.feature.base64_tools.presentation.screenLogic.Base64ToolsComponent @Composable fun Base64ToolsContent( component: Base64ToolsComponent ) { AutoContentBasedColors(component.uri) val isPortrait by isPortraitOrientationAsState() val imagePicker = rememberImagePicker(onSuccess = component::setUri) val pickImage = imagePicker::pickImage val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } AdaptiveLayoutScreen( shouldDisableBackHandler = true, title = { TopAppBarTitle( title = stringResource(R.string.base_64_tools), input = component.uri, isLoading = component.isImageLoading, size = null ) }, onGoBack = component.onGoBack, topAppBarPersistentActions = { if (component.uri == null) { TopAppBarEmoji() } var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomButton( onClick = { showZoomSheet = true }, visible = component.uri != null ) ZoomModalSheet( data = component.base64String, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) }, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.uri != null, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { uri -> editSheetData = listOf(uri) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, imagePreview = { AnimatedContent(component.base64String.isEmpty()) { isEmpty -> if (isEmpty) { ImageNotPickedWidget( onPickImage = pickImage, modifier = Modifier.padding(20.dp), text = stringResource(R.string.pick_image_or_base64), containerColor = MaterialTheme .colorScheme .surfaceContainerLowest .takeUnless(isPortrait) ) } else { Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip( alignment = Alignment.Center ), contentAlignment = Alignment.Center ) { var aspectRatio by remember { mutableFloatStateOf(1f) } Picture( model = component.base64String, contentScale = ContentScale.FillBounds, modifier = Modifier.aspectRatio(aspectRatio), onSuccess = { aspectRatio = it.result.image.toBitmap().safeAspectRatio }, error = { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, modifier = Modifier.background(MaterialTheme.colorScheme.surface) ) }, shape = MaterialTheme.shapes.medium, isLoadingFromDifferentPlace = component.isImageLoading ) } } } }, showImagePreviewAsStickyHeader = component.base64String.isNotEmpty(), controls = { if (isPortrait) Spacer(Modifier.height(8.dp)) Base64ToolsTiles(component) Spacer(Modifier.height(8.dp)) if (component.uri != null) { if (component.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = component.imageFormat, quality = component.quality, onQualityChange = component::setQuality ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = component.imageFormat, quality = component.quality, onValueChange = component::setImageFormat ) } else { InfoContainer( modifier = Modifier.padding(8.dp), text = stringResource(R.string.base_64_tips), ) } }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.base64String.isEmpty() && isPortrait, isPrimaryButtonVisible = isPortrait || component.base64String.isNotEmpty(), onSecondaryButtonClick = pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, canShowScreenData = true ) LoadingDialog( visible = component.isSaving, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/components/Base64ToolsTiles.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.base64_tools.presentation.components import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FileOpen import androidx.compose.material.icons.outlined.Save import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.rounded.ContentPaste import androidx.compose.material.icons.rounded.CopyAll import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.utils.isBase64 import com.t8rin.imagetoolbox.core.domain.utils.trimToBase64 import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Base64 import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.base64_tools.presentation.screenLogic.Base64ToolsComponent @Composable internal fun Base64ToolsTiles(component: Base64ToolsComponent) { val pasteTile: @Composable RowScope.(shape: Shape) -> Unit = { shape -> Tile( onClick = { Clipboard.getText { raw -> val text = raw.trimToBase64() if (text.isBase64()) { component.setBase64(text) } else { AppToastHost.showToast( message = getString(R.string.not_a_valid_base_64), icon = Icons.Rounded.Base64 ) } } }, shape = shape, icon = Icons.Rounded.ContentPaste, textRes = R.string.paste_base_64, isButton = component.uri != null ) } val importTile: @Composable RowScope.(shape: Shape) -> Unit = { shape -> val pickLauncher = rememberLauncherForActivityResult( ActivityResultContracts.OpenDocument() ) { uri -> uri?.let { component.setBase64FromUri( uri = uri, onFailure = { AppToastHost.showToast( message = getString(R.string.not_a_valid_base_64), icon = Icons.Rounded.Base64 ) } ) } } Tile( onClick = { pickLauncher.launch(arrayOf("text/plain")) }, shape = shape, icon = Icons.Outlined.FileOpen, textRes = R.string.import_base_64, isButton = component.uri != null ) } AnimatedContent(component.uri == null) { isNoUri -> if (isNoUri) { Row( modifier = Modifier .fillMaxWidth() .height(IntrinsicSize.Max), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { pasteTile(ShapeDefaults.start) importTile(ShapeDefaults.end) } } else { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .container( shape = ShapeDefaults.extremeLarge, resultPadding = 0.dp ) .padding( horizontal = 12.dp ) .padding( top = 4.dp, bottom = 8.dp ) ) { Text( text = stringResource(R.string.base_64_actions), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium, modifier = Modifier.padding(8.dp) ) Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { pasteTile(ShapeDefaults.circle) importTile(ShapeDefaults.circle) Tile( onClick = { val text = buildAnnotatedString { append(component.base64String) } if (component.base64String.isBase64()) { Clipboard.copy(text) } else { AppToastHost.showToast( message = getString(R.string.copy_not_a_valid_base_64), icon = Icons.Rounded.Base64 ) } }, icon = Icons.Rounded.CopyAll, textRes = R.string.copy_base_64 ) Tile( onClick = component::shareText, icon = Icons.Outlined.Share, textRes = R.string.share_base_64 ) val saveLauncher = rememberFileCreator( mimeType = MimeType.Txt, onSuccess = component::saveContentToTxt ) Tile( onClick = { saveLauncher.make(component.generateTextFilename()) }, icon = Icons.Outlined.Save, textRes = R.string.save_base_64 ) } } } } } @Composable private fun RowScope.Tile( onClick: () -> Unit, shape: Shape = ShapeDefaults.circle, icon: ImageVector, textRes: Int, isButton: Boolean = true ) { if (isButton) { val tooltipState = rememberTooltipState() Box( modifier = Modifier.weight(1f) ) { TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider( TooltipAnchorPosition.Above ), tooltip = { PlainTooltip { Text(stringResource(id = textRes)) } }, state = tooltipState ) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, onClick = onClick, modifier = Modifier.fillMaxWidth() ) { Icon( imageVector = icon, contentDescription = null ) } } } } else { PreferenceRow( title = stringResource(id = textRes), onClick = onClick, shape = shape, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCenteredSmall.copy( fontSize = 13.sp ), startIcon = icon, drawStartIconContainer = false, modifier = Modifier .weight(1f) .fillMaxHeight(), color = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) } } ================================================ FILE: feature/base64-tools/src/main/java/com/t8rin/imagetoolbox/feature/base64_tools/presentation/screenLogic/Base64ToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.base64_tools.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.isBase64 import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.domain.utils.trimToBase64 import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.base64_tools.domain.Base64Converter import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class Base64ToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val imageGetter: ImageGetter, private val shareProvider: ImageShareProvider, private val fileController: FileController, private val converter: Base64Converter, private val imageCompressor: ImageCompressor, dispatchersHolder: DispatchersHolder, ) : BaseComponent(dispatchersHolder, componentContext) { private val _imageFormat = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat private val _quality = mutableStateOf(Quality.Base()) val quality by _quality private val _uri = mutableStateOf(null) val uri by _uri private val _base64String = mutableStateOf("") val base64String by _base64String private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private var savingJob: Job? by smartJob { _isSaving.update { false } } init { debounce { initialUri?.let(::setUri) } } fun setUri(uri: Uri) { _uri.value = uri updateBase64() } private fun updateBase64() { debouncedImageCalculation { uri?.let { imageGetter.getImage(it) }?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = imageFormat, quality = quality, originalUri = uri.toString() ) )?.let { _base64String.value = converter.encode(it) } } } } fun setBase64(base64: String) { _base64String.value = base64 debouncedImageCalculation { _uri.value = converter.decode(base64)?.toUri() } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } updateBase64() } fun setQuality(quality: Quality) { _quality.update { quality } updateBase64() } fun getFormatForFilenameSelection(): ImageFormat = imageFormat fun shareBitmap() { savingJob = trackProgress { _isSaving.update { true } uri?.let { imageGetter.getImage(it) }?.let { image -> shareProvider.shareImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = imageFormat, quality = quality, originalUri = uri.toString() ), onComplete = AppToastHost::showConfetti ) } _isSaving.update { false } } } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.update { true } uri?.let { imageGetter.getImage(it) }?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = imageFormat, quality = quality, originalUri = uri.toString() ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.update { false } } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.update { false } } fun saveBitmap( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } uri?.let { imageGetter.getImage(it) }?.let { image -> val imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = imageFormat, quality = quality, originalUri = uri.toString() ) parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = uri.toString(), sequenceNumber = null, data = imageCompressor.compressAndTransform( image = image, imageInfo = imageInfo.copy( originalUri = uri.toString() ) ) ), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.update { false } } } fun saveContentToTxt(uri: Uri) { base64String.takeIf { it.isNotEmpty() }?.let { data -> componentScope.launch { fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(data.encodeToByteArray()) } ).also(::parseFileSaveResult).onSuccess(::registerSave) } } } fun generateTextFilename(): String = "Base64_${timestamp()}.txt" fun shareText() { base64String.takeIf { it.isNotEmpty() }?.let { data -> componentScope.launch { shareProvider.shareData( writeData = { it.writeBytes(data.encodeToByteArray()) }, filename = generateTextFilename() ) AppToastHost.showConfetti() } } } fun setBase64FromUri( uri: Uri, onFailure: () -> Unit ) { componentScope.launch { val text = fileController.readBytes(uri.toString()).decodeToString().trimToBase64() if (text.isBase64()) { setBase64(text) } else { onFailure() } } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): Base64ToolsComponent } } ================================================ FILE: feature/checksum-tools/.gitignore ================================================ /build ================================================ FILE: feature/checksum-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.checksum_tools" ================================================ FILE: feature/checksum-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/data/AndroidChecksumManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.data import android.content.Context import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.saving.io.StringReadable import com.t8rin.imagetoolbox.core.data.saving.io.UriReadable import com.t8rin.imagetoolbox.core.data.utils.computeFromReadable import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.saving.io.Readable import com.t8rin.imagetoolbox.feature.checksum_tools.domain.ChecksumManager import com.t8rin.imagetoolbox.feature.checksum_tools.domain.ChecksumSource import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidChecksumManager @Inject constructor( @ApplicationContext private val context: Context, dispatchersHolder: DispatchersHolder ) : ChecksumManager, DispatchersHolder by dispatchersHolder { override suspend fun calculateChecksum( type: HashingType, source: ChecksumSource ): String = withContext(defaultDispatcher) { runCatching { type.computeFromReadable(source.toReadable()) }.getOrDefault("") } override suspend fun compareChecksum( checksum: String, type: HashingType, source: ChecksumSource ): Boolean = coroutineScope { calculateChecksum(type, source) == checksum } private fun ChecksumSource.toReadable(): Readable = when (this) { is ChecksumSource.Text -> StringReadable(data) is ChecksumSource.Uri -> UriReadable(data.toUri(), context) } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/di/ChecksumToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.di import com.t8rin.imagetoolbox.feature.checksum_tools.data.AndroidChecksumManager import com.t8rin.imagetoolbox.feature.checksum_tools.domain.ChecksumManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ChecksumToolsModule { @Binds @Singleton fun checksumManager( impl: AndroidChecksumManager ): ChecksumManager } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.domain import com.t8rin.imagetoolbox.core.domain.model.HashingType interface ChecksumManager { suspend fun calculateChecksum( type: HashingType, source: ChecksumSource ): String suspend fun compareChecksum( checksum: String, type: HashingType, source: ChecksumSource ): Boolean } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/domain/ChecksumSource.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.domain sealed class ChecksumSource( val data: String ) { class Uri( data: String ) : ChecksumSource(data) class Text( data: String ) : ChecksumSource(data) } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/ChecksumToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.union import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Tag import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumPage import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumToolsTabs import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages.CalculateFromTextPage import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages.CalculateFromUriPage import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages.CompareWithUriPage import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages.CompareWithUrisPage import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent @Composable fun ChecksumToolsContent( component: ChecksumToolsComponent ) { val pagerState = rememberPagerState { ChecksumPage.ENTRIES_COUNT } AdaptiveLayoutScreen( shouldDisableBackHandler = true, onGoBack = component.onGoBack, title = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.checksum_tools) ) EnhancedBadge( content = { Text( text = HashingType.entries.size.toString() ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } }, actions = {}, topAppBarPersistentActions = { TopAppBarEmoji() }, imagePreview = {}, placeImagePreview = false, addHorizontalCutoutPaddingIfNoPreview = false, showImagePreviewAsStickyHeader = false, canShowScreenData = true, underTopAppBarContent = { ChecksumToolsTabs(pagerState) }, contentPadding = 0.dp, controls = { val insets = WindowInsets.navigationBars.union( WindowInsets.displayCutout ).only( WindowInsetsSides.Horizontal ).asPaddingValues() DataSelector( modifier = Modifier .padding(top = 20.dp) .padding(horizontal = 20.dp) .padding(insets), value = component.hashingType, containerColor = Color.Unspecified, selectedItemColor = MaterialTheme.colorScheme.secondary, onValueChange = component::updateChecksumType, entries = HashingType.entries, title = stringResource(R.string.algorithms), titleIcon = Icons.Rounded.Tag, itemContentText = { it.name } ) val direction = LocalLayoutDirection.current HorizontalPager( state = pagerState, beyondViewportPageCount = 3, contentPadding = insets, pageSpacing = remember(insets, direction) { 20.dp + insets.calculateStartPadding(direction) + insets.calculateEndPadding( direction ) }, verticalAlignment = Alignment.Top ) { pageIndex -> Column( modifier = Modifier .fillMaxWidth() .padding(20.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { when (pageIndex) { ChecksumPage.CalculateFromUri.INDEX -> { CalculateFromUriPage( component = component ) } ChecksumPage.CalculateFromText.INDEX -> { CalculateFromTextPage( component = component ) } ChecksumPage.CompareWithUri.INDEX -> { CompareWithUriPage( component = component ) } ChecksumPage.CompareWithUris.INDEX -> { CompareWithUrisPage( component = component ) } } } } }, buttons = {} ) } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumEnterField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField @Composable internal fun ChecksumEnterField( value: String, onValueChange: (String) -> Unit, label: String = stringResource(R.string.checksum_to_compare) ) { RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Text ), onValueChange = onValueChange, endIcon = { AnimatedVisibility(value.isNotBlank()) { EnhancedIconButton( onClick = { onValueChange("") }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.Cancel, contentDescription = stringResource(R.string.cancel) ) } } }, singleLine = false, value = value, label = label ) } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components import android.net.Uri sealed interface ChecksumPage { data class CalculateFromUri( val uri: Uri?, val checksum: String ) : ChecksumPage { companion object { const val INDEX = 0 val Empty: CalculateFromUri by lazy { CalculateFromUri( uri = null, checksum = "" ) } } } data class CalculateFromText( val text: String, val checksum: String ) : ChecksumPage { companion object { const val INDEX = 1 val Empty: CalculateFromText by lazy { CalculateFromText( text = "", checksum = "" ) } } } data class CompareWithUri( val uri: Uri?, val checksum: String, val targetChecksum: String, val isCorrect: Boolean ) : ChecksumPage { companion object { const val INDEX = 2 val Empty: CompareWithUri by lazy { CompareWithUri( uri = null, checksum = "", targetChecksum = "", isCorrect = false ) } } } data class CompareWithUris( val uris: List, val targetChecksum: String ) : ChecksumPage { companion object { const val INDEX = 3 val Empty: CompareWithUris by lazy { CompareWithUris( uris = emptyList(), targetChecksum = "" ) } } } companion object { const val ENTRIES_COUNT: Int = 4 } } data class UriWithHash( val uri: Uri, val checksum: String ) ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumPreviewField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextFieldColors @Composable internal fun ChecksumPreviewField( value: String, onCopyText: (String) -> Unit, label: String = stringResource(R.string.source_checksum) ) { RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp, color = MaterialTheme.colorScheme.tertiaryContainer.copy( 0.2f ) ), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Text ), onValueChange = {}, singleLine = false, readOnly = true, value = value, endIcon = { AnimatedVisibility(value.isNotBlank()) { EnhancedIconButton( onClick = { onCopyText(value) }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.ContentCopy, contentDescription = stringResource( R.string.copy ) ) } } }, colors = RoundedTextFieldColors( isError = false, containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(0.3f), focusedIndicatorColor = MaterialTheme.colorScheme.tertiary, unfocusedIndicatorColor = MaterialTheme.colorScheme.onTertiaryContainer.copy( 0.5f ) ), label = label ) } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumResultCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.Green import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.Red import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContainerColor import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContentColor import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable internal fun ChecksumResultCard( isCorrect: Boolean ) { val containerColor = animateColorAsState( when { isCorrect -> Green else -> Red } ).value.blend( color = Color.Black, fraction = 0.25f ) val contentColor = containerColor.inverse( fraction = { 1f }, darkMode = true ) CompositionLocalProvider( LocalIconShapeContentColor provides contentColor, LocalIconShapeContainerColor provides containerColor.blend( color = Color.Black, fraction = 0.15f ) ) { PreferenceItem( title = if (isCorrect) { stringResource(R.string.match) } else { stringResource(R.string.difference) }, subtitle = if (isCorrect) { stringResource(R.string.match_sub) } else { stringResource(R.string.difference_sub) }, startIcon = if (isCorrect) { Icons.Outlined.CheckCircle } else { Icons.Outlined.WarningAmber }, contentColor = contentColor, containerColor = containerColor, overrideIconShapeContentColor = true, modifier = Modifier.fillMaxWidth(), onClick = null ) } } @Composable @Preview private fun Preview() = ImageToolboxThemeForPreview(false) { CompositionLocalProvider( LocalSettingsState provides LocalSettingsState.current.copy( drawContainerShadows = false ) ) { ChecksumResultCard(false) } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/ChecksumToolsTabs.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.pager.PagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Calculate import androidx.compose.material.icons.rounded.TextFields import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PrimaryScrollableTabRow import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Tab import androidx.compose.material3.TabIndicatorScope import androidx.compose.material3.TabRowDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.CompareArrows import com.t8rin.imagetoolbox.core.resources.icons.FolderMatch import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import kotlinx.coroutines.launch @Composable internal fun ChecksumToolsTabs( pagerState: PagerState ) { val scope = rememberCoroutineScope() val haptics = LocalHapticFeedback.current val topAppBarColor = MaterialTheme.colorScheme.surfaceContainer Row( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke() .background(topAppBarColor) .drawWithContent { drawContent() drawRect( color = topAppBarColor, size = size.copy(height = 6.dp.toPx()), topLeft = Offset( x = 0f, y = -4.dp.toPx() ) ) }, horizontalArrangement = Arrangement.Center ) { val modifier = Modifier.windowInsetsPadding( WindowInsets.statusBars.union( WindowInsets.displayCutout ).only( WindowInsetsSides.Horizontal ) ) val indicator: @Composable TabIndicatorScope.() -> Unit = { TabRowDefaults.PrimaryIndicator( modifier = Modifier.tabIndicatorOffset( selectedTabIndex = pagerState.currentPage, matchContentSize = true ), width = Dp.Unspecified, height = 4.dp, shape = AutoCornersShape( topStart = 100f, topEnd = 100f ) ) } val tabs: @Composable () -> Unit = { repeat(pagerState.pageCount) { index -> val selected = pagerState.currentPage == index val color by animateColorAsState( if (selected) { MaterialTheme.colorScheme.primary } else MaterialTheme.colorScheme.onSurface ) val (icon, textRes) = when (index) { 0 -> Icons.Rounded.Calculate to R.string.calculate 1 -> Icons.Rounded.TextFields to R.string.text_hash 2 -> Icons.Rounded.CompareArrows to R.string.compare 3 -> Icons.Rounded.FolderMatch to R.string.batch_compare else -> throw IllegalArgumentException("Not presented index $index of ChecksumPage") } val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = AutoCornersShape(42.dp), pressedShape = ShapeDefaults.default, interactionSource = interactionSource ) Tab( unselectedContentColor = MaterialTheme.colorScheme.onSurface, interactionSource = interactionSource, modifier = Modifier .padding(vertical = 8.dp) .clip(shape), selected = selected, onClick = { haptics.longPress() scope.launch { pagerState.animateScrollToPage(index) } }, icon = { Icon( imageVector = icon, contentDescription = null, tint = color ) }, text = { Text( text = stringResource(textRes), color = color ) } ) } } var disableScroll by remember { mutableStateOf(false) } if (disableScroll) { PrimaryTabRow( modifier = modifier.padding(horizontal = 8.dp), divider = {}, containerColor = Color.Transparent, selectedTabIndex = pagerState.currentPage, indicator = indicator, tabs = tabs ) } else { val screenWidth = LocalScreenSize.current.widthPx PrimaryScrollableTabRow( modifier = modifier.layout { measurable, constraints -> val placeable = measurable.measure(constraints) if (!disableScroll) { disableScroll = screenWidth > placeable.measuredWidth } layout(placeable.measuredWidth, placeable.measuredHeight) { placeable.placeWithLayer(x = 0, y = 0) } }, edgePadding = 8.dp, divider = {}, containerColor = Color.Transparent, selectedTabIndex = pagerState.currentPage, indicator = indicator, tabs = tabs ) } } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/UriWithHashItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FilePresent import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload @Composable internal fun UriWithHashItem( uriWithHash: UriWithHash, onCopyText: (String) -> Unit ) { val (uri, checksum) = uriWithHash val filename = rememberFilename(uri) ?: stringResource(R.string.filename) val fileSize = rememberHumanFileSize(uri) Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { PreferenceItemOverload( title = filename, subtitle = fileSize, startIcon = { Icon( imageVector = Icons.Outlined.FilePresent, contentDescription = null, modifier = Modifier .size(48.dp) .clip(CloverShape) .background( MaterialTheme.colorScheme.secondaryContainer.copy( 0.5f ) ) .padding(8.dp) ) }, modifier = Modifier.fillMaxWidth(), drawStartIconContainer = false ) ChecksumPreviewField( value = checksum, onCopyText = onCopyText ) } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromTextPage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumEnterField import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumPreviewField import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent @Composable internal fun ColumnScope.CalculateFromTextPage( component: ChecksumToolsComponent ) { val page = component.calculateFromTextPage var text by remember { mutableStateOf(page.text) } ChecksumEnterField( value = text, onValueChange = { text = it component.setText(it) }, label = stringResource(R.string.text) ) ChecksumPreviewField( value = page.checksum, onCopyText = Clipboard::copy, label = stringResource(R.string.checksum) ) AnimatedVisibility(page.checksum.isEmpty()) { InfoContainer( text = stringResource(R.string.enter_text_to_checksum), modifier = Modifier.padding(8.dp), ) } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CalculateFromUriPage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FileSelector import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumPreviewField import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent @Composable internal fun ColumnScope.CalculateFromUriPage( component: ChecksumToolsComponent ) { val page = component.calculateFromUriPage FileSelector( value = page.uri?.toString(), onValueChange = component::setUri, subtitle = null ) AnimatedContent(page.checksum) { checksum -> if (checksum.isNotEmpty()) { ChecksumPreviewField( value = checksum, onCopyText = Clipboard::copy, label = stringResource(R.string.checksum) ) } else { InfoContainer( text = stringResource(R.string.pick_file_to_checksum), modifier = Modifier.padding(8.dp), ) } } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUriPage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FileSelector import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumEnterField import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumPreviewField import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumResultCard import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent @Composable internal fun ColumnScope.CompareWithUriPage( component: ChecksumToolsComponent, ) { val page = component.compareWithUriPage FileSelector( value = page.uri?.toString(), onValueChange = component::setDataForComparison, subtitle = null ) AnimatedContent(page.checksum) { checksum -> if (checksum.isNotEmpty()) { ChecksumPreviewField( value = checksum, onCopyText = Clipboard::copy ) } else { InfoContainer( text = stringResource(R.string.pick_file_to_checksum), modifier = Modifier.padding(8.dp), ) } } ChecksumEnterField( value = page.targetChecksum, onValueChange = { component.setDataForComparison( targetChecksum = it ) } ) AnimatedVisibility( page.targetChecksum.isNotEmpty() && !page.uri?.toString() .isNullOrEmpty() ) { ChecksumResultCard(page.isCorrect) } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/components/pages/CompareWithUrisPage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.pages import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FileCopy import androidx.compose.material.icons.outlined.FolderOpen import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.FileType import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFolderPicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.buttons.PagerScrollPanel import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.negativePadding import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumEnterField import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumResultCard import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.UriWithHashItem import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent @Composable internal fun ColumnScope.CompareWithUrisPage( component: ChecksumToolsComponent ) { var previousFolder by rememberSaveable { mutableStateOf(null) } val isFilesLoading = component.filesLoadingProgress >= 0 val openDirectoryLauncher = rememberFolderPicker( onSuccess = { uri -> previousFolder = uri component.setDataForBatchComparisonFromTree(uri) } ) val filePicker = rememberFilePicker( type = FileType.Multiple, onSuccess = component::setDataForBatchComparison ) val page = component.compareWithUrisPage Row( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.height(IntrinsicSize.Max) ) { PreferenceRow( title = stringResource(R.string.pick_files), onClick = filePicker::pickFile, shape = ShapeDefaults.start, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCenteredSmall, startIcon = Icons.Outlined.FileCopy, drawStartIconContainer = false, modifier = Modifier .weight(1f) .fillMaxHeight(), color = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) PreferenceRow( title = stringResource(R.string.pick_directory), onClick = { openDirectoryLauncher.pickFolder(previousFolder) }, shape = ShapeDefaults.end, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCenteredSmall, startIcon = Icons.Outlined.FolderOpen, drawStartIconContainer = false, modifier = Modifier .weight(1f) .fillMaxHeight(), color = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) } val nestedPagerState = rememberPagerState { page.uris.size } AnimatedContent( targetState = page.uris.isNotEmpty() to isFilesLoading, modifier = Modifier.padding(vertical = 4.dp) ) { (isNotEmpty, isLoading) -> if (isLoading) { Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator( progress = component.filesLoadingProgress ) } } else if (isNotEmpty) { HorizontalPager( state = nestedPagerState, pageSpacing = 16.dp, modifier = Modifier .fillMaxWidth() .negativePadding(horizontal = 20.dp) .fadingEdges(nestedPagerState), contentPadding = PaddingValues(horizontal = 20.dp), beyondViewportPageCount = 10 ) { nestedPage -> UriWithHashItem( uriWithHash = page.uris[nestedPage], onCopyText = Clipboard::copy ) } } else { InfoContainer( text = stringResource(R.string.pick_files_to_checksum), modifier = Modifier.padding(8.dp), ) } } AnimatedVisibility(page.uris.isNotEmpty()) { Column( verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { if (page.uris.size > 1) { PagerScrollPanel(nestedPagerState) } ChecksumEnterField( value = page.targetChecksum, onValueChange = { component.setDataForBatchComparison( targetChecksum = it ) } ) } } AnimatedVisibility(page.targetChecksum.isNotEmpty() && page.uris.isNotEmpty()) { val isCorrect = page.targetChecksum == page.uris[nestedPagerState.currentPage].checksum ChecksumResultCard(isCorrect) } } ================================================ FILE: feature/checksum-tools/src/main/java/com/t8rin/imagetoolbox/feature/checksum_tools/presentation/screenLogic/ChecksumToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.checksum_tools.domain.ChecksumManager import com.t8rin.imagetoolbox.feature.checksum_tools.domain.ChecksumSource import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.ChecksumPage import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.components.UriWithHash import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay class ChecksumToolsComponent @AssistedInject constructor( @Assisted componentContext: ComponentContext, @Assisted initialUri: Uri?, @Assisted val onGoBack: () -> Unit, private val checksumManager: ChecksumManager, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _hashingType = fileController.savable( scope = componentScope, initial = HashingType.entries.first() ) val hashingType: HashingType by _hashingType private val _calculateFromUriPage: MutableState = mutableStateOf(ChecksumPage.CalculateFromUri.Empty) val calculateFromUriPage: ChecksumPage.CalculateFromUri by _calculateFromUriPage private val _calculateFromTextPage: MutableState = mutableStateOf(ChecksumPage.CalculateFromText.Empty) val calculateFromTextPage: ChecksumPage.CalculateFromText by _calculateFromTextPage private val _compareWithUriPage: MutableState = mutableStateOf(ChecksumPage.CompareWithUri.Empty) val compareWithUriPage: ChecksumPage.CompareWithUri by _compareWithUriPage private val _compareWithUrisPage: MutableState = mutableStateOf(ChecksumPage.CompareWithUris.Empty) val compareWithUrisPage: ChecksumPage.CompareWithUris by _compareWithUrisPage init { debounce { initialUri?.let(::setUri) } } fun setUri(uri: Uri) { _calculateFromUriPage.update { it.copy( uri = uri ) } componentScope.launch { _calculateFromUriPage.update { it.copy( uri = uri, checksum = checksumManager.calculateChecksum( type = hashingType, source = ChecksumSource.Uri(uri.toString()) ) ) } } } fun setText(text: String) { _calculateFromTextPage.update { it.copy( text = text ) } componentScope.launch { _calculateFromTextPage.update { it.copy( text = text, checksum = if (text.isNotEmpty()) { checksumManager.calculateChecksum( type = hashingType, source = ChecksumSource.Text(text) ) } else "" ) } } } fun setDataForComparison( uri: Uri? = compareWithUriPage.uri, targetChecksum: String = compareWithUriPage.targetChecksum ) { _compareWithUriPage.update { it.copy( uri = uri, targetChecksum = targetChecksum ) } componentScope.launch { _compareWithUriPage.update { it.copy( uri = uri, targetChecksum = targetChecksum, checksum = checksumManager.calculateChecksum( type = hashingType, source = ChecksumSource.Uri(uri.toString()) ), isCorrect = checksumManager.compareChecksum( checksum = targetChecksum, type = hashingType, source = ChecksumSource.Uri(uri.toString()) ) ) } } } fun updateChecksumType(type: HashingType) { _hashingType.update { type } calculateFromUriPage.uri?.let(::setUri) calculateFromTextPage.text.let(::setText) compareWithUriPage.uri?.let(::setDataForComparison) setDataForBatchComparison(forceReload = true) } private var treeJob: Job? by smartJob { _filesLoadingProgress.update { -1f } } fun setDataForBatchComparison( uris: List = compareWithUrisPage.uris.map { it.uri }, targetChecksum: String = compareWithUrisPage.targetChecksum, forceReload: Boolean = false ) { _compareWithUrisPage.update { it.copy( targetChecksum = targetChecksum ) } val targetUris = compareWithUrisPage.uris.map { it.uri } if (targetUris != uris || forceReload) { treeJob = componentScope.launch { delay(500) _filesLoadingProgress.update { 0f } var done = 0 val urisWithHash = uris.map { uri -> val checksum = checksumManager.calculateChecksum( type = hashingType, source = ChecksumSource.Uri(uri.toString()) ) _filesLoadingProgress.update { ((done++) / uris.size.toFloat()).takeIf { it.isFinite() } ?: 0f } UriWithHash( uri = uri, checksum = checksum ) } _compareWithUrisPage.update { it.copy( uris = urisWithHash, targetChecksum = targetChecksum ) } _filesLoadingProgress.update { -1f } } } } private val _filesLoadingProgress = mutableFloatStateOf(-1f) val filesLoadingProgress by _filesLoadingProgress fun setDataForBatchComparisonFromTree(uri: Uri) { treeJob = componentScope.launch { delay(500) _filesLoadingProgress.update { 0f } fileController.listFilesInDirectory(uri.toString()) .map { it.toUri() } .let(::setDataForBatchComparison) } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, ): ChecksumToolsComponent } } ================================================ FILE: feature/cipher/.gitignore ================================================ /build ================================================ FILE: feature/cipher/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.cipher" ================================================ FILE: feature/cipher/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidCryptographyManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("PrivatePropertyName", "SpellCheckingInspection") package com.t8rin.imagetoolbox.feature.cipher.data import com.t8rin.imagetoolbox.core.data.saving.io.StringReadable import com.t8rin.imagetoolbox.core.data.utils.computeBytesFromReadable import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.CipherType import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.feature.cipher.domain.CryptographyManager import com.t8rin.imagetoolbox.feature.cipher.domain.WrongKeyException import kotlinx.coroutines.withContext import java.security.Key import java.security.SecureRandom import javax.crypto.Cipher import javax.crypto.SecretKeyFactory import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEParameterSpec import javax.crypto.spec.SecretKeySpec import javax.inject.Inject internal class AndroidCryptographyManager @Inject constructor( private val dispatchersHolder: DispatchersHolder ) : CryptographyManager, DispatchersHolder by dispatchersHolder { private val CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" private fun createKey( password: String, type: CipherType ): Key = if ("PBE" in type.name) { SecretKeyFactory .getInstance(type.cipher) .generateSecret( PBEKeySpec(password.toCharArray()) ) } else { hashingType(type.name) .computeBytesFromReadable(StringReadable(password)) .let { key -> SecretKeySpec(key, type.cipher) } } override fun generateRandomString(length: Int): String { val sr = SecureRandom() return buildString { repeat(length) { append(CHARS[sr.nextInt(CHARS.length)]) } } } override suspend fun decrypt( data: ByteArray, key: String, type: CipherType ): ByteArray = withContext(defaultDispatcher) { val cipher = type.toCipher( keySpec = createKey( password = key, type = type ), isEncrypt = false ) cipher.doOrThrow(data) } override suspend fun encrypt( data: ByteArray, key: String, type: CipherType ): ByteArray = withContext(defaultDispatcher) { val cipher = type.toCipher( keySpec = createKey( password = key, type = type ), isEncrypt = true ) cipher.doOrThrow(data) } private fun Cipher.init( keySpec: Key, isEncrypt: Boolean, type: CipherType ) { val mode = if (isEncrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE try { val encodedKey = keySpec.encoded when { "PBE" in type.name -> { init( mode, keySpec, PBEParameterSpec(encodedKey, encodedKey.size) ) } else -> { init( mode, keySpec, IvParameterSpec(encodedKey, 0, blockSize) ) } } } catch (_: Throwable) { runCatching { init( mode, keySpec, generateIV(ivSize(type.name)) ) }.getOrElse { init( mode, keySpec ) } } } private fun CipherType.toCipher( keySpec: Key, isEncrypt: Boolean ): Cipher = Cipher.getInstance(cipher).apply { init( keySpec = keySpec, isEncrypt = isEncrypt, type = this@toCipher ) } private fun Cipher.doOrThrow(data: ByteArray): ByteArray { return try { doFinal(data) } catch (e: Throwable) { throw if (e.message?.contains("mac") == true && e.message?.contains("failed") == true) { WrongKeyException() } else e } } private fun generateIV(size: Int): IvParameterSpec { val iv = ByteArray(size) SecureRandom().nextBytes(iv) return IvParameterSpec(iv) } private fun ivSize(name: String): Int = when { name == "AESRFC5649WRAP" || name == "AESWRAPPAD" || name == "AES_128/KWP/NoPadding" || name == "AES_192/KWP/NoPadding" || name == "AES_256/KWP/NoPadding" || name == "ARIAWRAPPAD" -> 4 name == "AESWRAP" || name == "AES_128/KW/NoPadding" || name == "AES_192/KW/NoPadding" || name == "CAMELLIAWRAP" || name == "AES_256/KW/NoPadding" || name == "ARIAWRAP" || name == "CHACHA" || "CBC" in name && name != "SEED/CBC" || name == "DESEDEWRAP" || name == "GRAINV1" || name == "SALSA20" || name.contains("wrap", true) -> 8 name == "AES/ECB/PKCS7PADDING" || name == "AES/GCM-SIV/NOPADDING" || name == "AES128/CCM" || name == "AES192/CCM" || name == "AES256/CCM" || "CCM" in name || "CHACHA" in name || name == "AES_256/GCM-SIV/NOPADDING" || name == "AES_256/GCM/NOPADDING" || name == "GRAIN128" || name == "AES_128/CBC/NOPADDING" || name == "AES_128/CBC/PKCS5PADDING" || name == "AES_128/GCM/NOPADDING" -> 12 name == "XSALSA20" -> 24 name == "ZUC-256" -> 25 "DSTU7624" in name && "512" in name -> 64 else -> 16 } private val MD5List = listOf( "SEED", "NOEKEON", "HC128", "AES_128/CBC/NOPADDING", "AES_128/CBC/PKCS5PADDING", "AES_128/GCM/NOPADDING", "DESEDE", "GRAIN128", "SM4", "TEA", "ZUC-128", "AES_128/ECB/NOPADDING", "AES_128/ECB/PKCS5PADDING", "AES_128/GCM/NOPADDING" ) private fun hashingType(name: String): HashingType = when { MD5List.any { name.contains(it, true) } -> HashingType.MD5 else -> HashingType.SHA_256 } } ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/data/AndroidRandomStringGenerator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.data import com.t8rin.imagetoolbox.core.domain.saving.RandomStringGenerator import com.t8rin.imagetoolbox.feature.cipher.domain.CryptographyManager import javax.inject.Inject internal class AndroidRandomStringGenerator @Inject constructor( private val cryptographyManager: CryptographyManager ) : RandomStringGenerator { override fun generate(length: Int): String = cryptographyManager.generateRandomString(length) } ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/di/CipherModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.di import com.t8rin.imagetoolbox.core.domain.saving.RandomStringGenerator import com.t8rin.imagetoolbox.feature.cipher.data.AndroidCryptographyManager import com.t8rin.imagetoolbox.feature.cipher.data.AndroidRandomStringGenerator import com.t8rin.imagetoolbox.feature.cipher.domain.CryptographyManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface CipherModule { @Singleton @Binds fun cryptographyManager( impl: AndroidCryptographyManager ): CryptographyManager @Singleton @Binds fun provideRandomStringGenerator( impl: AndroidRandomStringGenerator ): RandomStringGenerator } ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/CryptographyManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.domain import com.t8rin.imagetoolbox.core.domain.model.CipherType interface CryptographyManager { fun generateRandomString( length: Int ): String suspend fun decrypt( data: ByteArray, key: String, type: CipherType ): ByteArray suspend fun encrypt( data: ByteArray, key: String, type: CipherType ): ByteArray } ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/domain/WrongKeyException.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.domain internal class WrongKeyException : Throwable() ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/CipherContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.presentation import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.material.icons.rounded.Lock import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.CipherType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.KeyVertical import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.FileNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.cipher.domain.WrongKeyException import com.t8rin.imagetoolbox.feature.cipher.presentation.components.CipherControls import com.t8rin.imagetoolbox.feature.cipher.presentation.components.CipherTipSheet import com.t8rin.imagetoolbox.feature.cipher.presentation.screenLogic.CipherComponent @Composable fun CipherContent( component: CipherComponent ) { val showTip = component.showTip var showExitDialog by rememberSaveable { mutableStateOf(false) } val canGoBack = component.canGoBack val key = component.key val onBack = { if (!canGoBack) showExitDialog = true else component.onGoBack() } val filePicker = rememberFilePicker(onSuccess = component::setUri) AutoFilePicker( onAutoPick = filePicker::pickFile, isPickedAlready = component.initialUri != null ) val isPortrait by isPortraitOrientationAsState() AdaptiveLayoutScreen( title = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { AnimatedContent( targetState = component.uri to component.isEncrypt, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { (uri, isEncrypt) -> Text( if (uri == null) { stringResource(R.string.cipher) } else { listOf( stringResource(R.string.encryption), stringResource(R.string.decryption) )[if (isEncrypt) 0 else 1] }, textAlign = TextAlign.Center ) } EnhancedBadge( content = { Text( text = CipherType.entries.size.toString() ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } }, onGoBack = onBack, shouldDisableBackHandler = canGoBack, topAppBarPersistentActions = { TopAppBarEmoji() }, actions = {}, buttons = { BottomButtonsBlock( isNoData = component.uri == null, onSecondaryButtonClick = filePicker::pickFile, secondaryButtonIcon = Icons.Rounded.FileOpen, secondaryButtonText = stringResource(R.string.pick_file), onPrimaryButtonClick = { component.startCryptography { if (it is WrongKeyException) { AppToastHost.showFailureToast(R.string.invalid_password_or_not_encrypted) } else if (it != null) { AppToastHost.showFailureToast( throwable = it ) } } }, primaryButtonIcon = if (component.isEncrypt) { Icons.Rounded.Lock } else { Icons.Rounded.KeyVertical }, primaryButtonText = if (isPortrait) { if (component.isEncrypt) { stringResource(R.string.encrypt) } else { stringResource(R.string.decrypt) } } else "", isPrimaryButtonEnabled = key.isNotEmpty(), actions = {} ) }, canShowScreenData = component.uri != null, noDataControls = { FileNotPickedWidget(onPickFile = filePicker::pickFile) }, controls = { CipherControls(component) }, imagePreview = {}, showImagePreviewAsStickyHeader = false, placeImagePreview = false, showActionsInTopAppBar = false, addHorizontalCutoutPaddingIfNoPreview = false, ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) CipherTipSheet( visible = showTip, onDismiss = component::hideTip ) LoadingDialog( visible = component.isSaving, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.HelpOutline import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material.icons.rounded.CheckCircle import androidx.compose.material.icons.rounded.FileDownload import androidx.compose.material.icons.rounded.Key import androidx.compose.material.icons.rounded.Share import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.model.CipherType import com.t8rin.imagetoolbox.core.domain.utils.toInt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.Green import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.cipher.presentation.screenLogic.CipherComponent import kotlin.random.Random @Composable internal fun CipherControls(component: CipherComponent) { val settingsState = LocalSettingsState.current val isPortrait by isPortraitOrientationAsState() val saveLauncher = rememberFileCreator( onSuccess = component::saveCryptographyTo ) val filename = component.uri?.let { rememberFilename(it) } Column(horizontalAlignment = Alignment.CenterHorizontally) { if (isPortrait) Spacer(Modifier.height(20.dp)) Row( modifier = Modifier .container(ShapeDefaults.circle) .padding(horizontal = 8.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { val items = listOf( stringResource(R.string.encryption), stringResource(R.string.decryption) ) EnhancedButtonGroup( enabled = true, itemCount = items.size, selectedIndex = (!component.isEncrypt).toInt(), onIndexChange = { component.setIsEncrypt(it == 0) }, itemContent = { Text( text = items[it], fontSize = 12.sp ) }, isScrollable = false, modifier = Modifier.weight(1f) ) EnhancedIconButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = component::showTip ) { Icon( imageVector = Icons.AutoMirrored.Rounded.HelpOutline, contentDescription = "Info" ) } } Spacer(Modifier.height(16.dp)) PreferenceItem( modifier = Modifier, title = filename ?: stringResource(R.string.something_went_wrong), onClick = null, titleFontStyle = LocalTextStyle.current.copy( lineHeight = 16.sp, fontSize = 15.sp ), subtitle = component.uri?.let { stringResource( id = R.string.size, rememberHumanFileSize(it) ) }, startIcon = Icons.AutoMirrored.Rounded.InsertDriveFile ) Spacer(Modifier.height(16.dp)) RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = component.key, startIcon = { EnhancedIconButton( onClick = { component.updateKey(component.generateRandomPassword()) }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.Rounded.Shuffle, contentDescription = stringResource(R.string.shuffle), tint = MaterialTheme.colorScheme.onSecondaryContainer ) } }, endIcon = { EnhancedIconButton( onClick = { component.updateKey("") }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.Cancel, contentDescription = stringResource(R.string.cancel), tint = MaterialTheme.colorScheme.onSecondaryContainer ) } }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), singleLine = false, onValueChange = component::updateKey, label = { Text(stringResource(R.string.key)) } ) AnimatedVisibility(visible = component.byteArray != null) { Column( modifier = Modifier .fillMaxWidth() .padding(top = 24.dp) .container( shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme .colorScheme .surfaceContainerHighest, resultPadding = 0.dp ) .padding(16.dp) ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = Icons.Rounded.CheckCircle, contentDescription = null, tint = Green, modifier = Modifier .size(36.dp) .background( color = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.circle ) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant(), shape = ShapeDefaults.circle ) .padding(4.dp) ) Spacer(modifier = Modifier.width(16.dp)) Text( stringResource(R.string.file_proceed), fontSize = 17.sp, fontWeight = FontWeight.Medium ) } Text( text = stringResource(R.string.store_file_desc), fontSize = 13.sp, color = LocalContentColor.current.copy(alpha = 0.7f), lineHeight = 14.sp, modifier = Modifier.padding(vertical = 16.dp) ) var name by rememberSaveable(component.byteArray, filename) { mutableStateOf( if (component.isEncrypt) { "enc-" } else { "dec-" } + (filename ?: Random.nextInt()) ) } RoundedTextField( modifier = Modifier .padding(top = 8.dp) .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = name, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), singleLine = false, onValueChange = { name = it }, label = { Text(stringResource(R.string.filename)) } ) Row( modifier = Modifier .padding(top = 24.dp) .fillMaxWidth() ) { EnhancedButton( onClick = { saveLauncher.make(name) }, modifier = Modifier .padding(end = 8.dp) .fillMaxWidth(0.5f) .height(50.dp), containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Icon( imageVector = Icons.Rounded.FileDownload, contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) AutoSizeText( text = stringResource(id = R.string.save), maxLines = 1 ) } } EnhancedButton( onClick = { component.byteArray?.let { component.shareFile( it = it, filename = name ) } }, modifier = Modifier .padding(start = 8.dp) .fillMaxWidth() .height(50.dp), containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Icon( imageVector = Icons.Rounded.Share, contentDescription = stringResource( R.string.share ) ) Spacer(modifier = Modifier.width(8.dp)) AutoSizeText( text = stringResource(id = R.string.share), maxLines = 1 ) } } } } } Spacer(Modifier.height(24.dp)) DataSelector( modifier = Modifier, value = component.cipherType, containerColor = Color.Unspecified, spanCount = 5, selectedItemColor = MaterialTheme.colorScheme.secondary, onValueChange = component::updateCipherType, entries = CipherType.entries, title = stringResource(R.string.algorithms), titleIcon = Icons.Rounded.Key, itemContentText = { it.name }, initialExpanded = true ) } } ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/components/CipherTipSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.InsertDriveFile import androidx.compose.material.icons.rounded.Functions import androidx.compose.material.icons.rounded.Security import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Interface import com.t8rin.imagetoolbox.core.resources.icons.Puzzle import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun CipherTipSheet( visible: Boolean, onDismiss: () -> Unit ) { EnhancedModalBottomSheet( sheetContent = { Box { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(12.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) { Column( modifier = Modifier .container( shape = ShapeDefaults.top ) .fillMaxWidth() ) { TitleItem( text = stringResource(R.string.features), icon = Icons.Rounded.Functions ) Text( text = stringResource(R.string.features_sub), modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), fontSize = 14.sp, lineHeight = 18.sp ) } Column( modifier = Modifier .container( shape = ShapeDefaults.center ) .fillMaxWidth() ) { TitleItem( text = stringResource(R.string.implementation), icon = Icons.Filled.Interface ) Text( text = stringResource(id = R.string.implementation_sub), modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), fontSize = 14.sp, lineHeight = 18.sp ) } Column( modifier = Modifier .container( shape = ShapeDefaults.center ) .fillMaxWidth() ) { TitleItem( text = stringResource(R.string.file_size), icon = Icons.AutoMirrored.Outlined.InsertDriveFile ) Text( text = stringResource(id = R.string.file_size_sub), modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), fontSize = 14.sp, lineHeight = 18.sp ) } Column( modifier = Modifier .container( shape = ShapeDefaults.bottom ) .fillMaxWidth() ) { TitleItem( text = stringResource(R.string.compatibility), icon = Icons.Outlined.Puzzle ) Text( text = stringResource(id = R.string.compatibility_sub), modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), fontSize = 14.sp, lineHeight = 18.sp ) } } } }, visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.cipher), icon = Icons.Rounded.Security ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } } ) } ================================================ FILE: feature/cipher/src/main/java/com/t8rin/imagetoolbox/feature/cipher/presentation/screenLogic/CipherComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.cipher.presentation.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.model.CipherType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.cipher.domain.CryptographyManager import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class CipherComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, private val cryptographyManager: CryptographyManager, private val shareProvider: ShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _cipherType = fileController.savable( scope = componentScope, initial = CipherType.entries.first() ) val cipherType: CipherType by _cipherType private val _showTip: MutableState = mutableStateOf(false) val showTip by _showTip private val _key: MutableState = mutableStateOf("") val key by _key val canGoBack: Boolean get() = uri == null || (key.isEmpty() && byteArray == null) fun showTip() = _showTip.update { true } fun hideTip() = _showTip.update { false } init { debounce { initialUri?.let(::setUri) } } fun updateKey(newKey: String) { _key.update { newKey } resetCalculatedData() } private val _uri = mutableStateOf(null) val uri by _uri private val _isEncrypt = mutableStateOf(true) val isEncrypt by _isEncrypt private val _byteArray = mutableStateOf(null) val byteArray by _byteArray private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving fun setUri(newUri: Uri) { _uri.value = newUri resetCalculatedData() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun startCryptography( onComplete: (Throwable?) -> Unit ) { savingJob = trackProgress { _isSaving.value = true val uri = uri if (uri == null) { onComplete(null) return@trackProgress } runSuspendCatching { _byteArray.update { fileController.readBytes(uri.toString()).let { file -> if (isEncrypt) { cryptographyManager.encrypt( data = file, key = key, type = cipherType ) } else { cryptographyManager.decrypt( data = file, key = key, type = cipherType ) } } } }.exceptionOrNull().let(onComplete) _isSaving.value = false } } fun updateCipherType(type: CipherType) { _cipherType.update { type } resetCalculatedData() } fun setIsEncrypt(isEncrypt: Boolean) { _isEncrypt.value = isEncrypt resetCalculatedData() } fun resetCalculatedData() { _byteArray.value = null } fun saveCryptographyTo(uri: Uri) { savingJob = trackProgress { _isSaving.value = true byteArray?.let { byteArray -> fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(byteArray) } ).also(::parseFileSaveResult).onSuccess(::registerSave) } _isSaving.value = false } } fun generateRandomPassword(): String = cryptographyManager.generateRandomString(18) fun shareFile( it: ByteArray, filename: String, ) { savingJob = trackProgress { _isSaving.value = true shareProvider.shareByteArray( byteArray = it, filename = filename, onComplete = { _isSaving.value = false AppToastHost.showConfetti() } ) } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, ): CipherComponent } } ================================================ FILE: feature/collage-maker/.gitignore ================================================ /build ================================================ FILE: feature/collage-maker/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.collage_maker" dependencies { implementation(projects.lib.collages) } ================================================ FILE: feature/collage-maker/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/CollageMakerContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.collage_maker.presentation import android.content.pm.ActivityInfo import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable import android.net.Uri import androidx.appcompat.content.res.AppCompatResources import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AutoAwesomeMosaic import androidx.compose.material.icons.outlined.PinEnd import androidx.compose.material.icons.outlined.SwipeVertical import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.FormatLineSpacing import androidx.compose.material.icons.rounded.PhotoSizeSelectSmall import androidx.compose.material.icons.rounded.RoundedCorner import androidx.compose.material.icons.rounded.SwapHoriz import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.graphics.drawable.DrawableCompat import com.t8rin.collages.Collage import com.t8rin.collages.CollageTypeSelection import com.t8rin.collages.public.CollageConstants import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.collage_maker.presentation.screenLogic.CollageMakerComponent import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveBottomScaffoldLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.image.AspectRatioSelector import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.other.LockScreenOrientation import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.core.utils.getString import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable fun CollageMakerContent( component: CollageMakerComponent ) { LockScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) var isLoading by rememberSaveable { mutableStateOf(true) } LaunchedEffect(component.initialUris) { component.initialUris?.takeIf { it.isNotEmpty() }?.let { if (it.size in 1..CollageConstants.MAX_IMAGE_COUNT) { component.updateUris(it) } else { AppToastHost.showToast( message = getString( R.string.pick_up_to_n_collage_images, CollageConstants.MAX_IMAGE_COUNT, ), icon = Icons.Outlined.AutoAwesomeMosaic ) } } } val imagePicker = rememberImagePicker { uris: List -> if (uris.size in 1..CollageConstants.MAX_IMAGE_COUNT) { isLoading = true component.updateUris(uris) } else { AppToastHost.showToast( message = if (uris.size > CollageConstants.MAX_IMAGE_COUNT) { getString( R.string.pick_up_to_n_collage_images, CollageConstants.MAX_IMAGE_COUNT, ) } else { getString(R.string.pick_at_least_two_images) }, icon = Icons.Outlined.AutoAwesomeMosaic ) } } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } var resettingTrigger by rememberSaveable { mutableIntStateOf(0) } val settings = LocalSettingsState.current LaunchedEffect( settings.isNightMode, settings.appColorTuple, settings.isDynamicColors ) { delay(500) resettingTrigger++ } val scope = rememberCoroutineScope() AdaptiveBottomScaffoldLayoutScreen( title = { AnimatedContent( targetState = component.uris.isNullOrEmpty() ) { noData -> if (noData) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.collage_maker) ) EnhancedBadge( content = { Text( text = CollageConstants.layoutCount.toString() ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } } else { TopAppBarTitle( title = stringResource(R.string.collage_maker), input = component.uris, isLoading = component.isImageLoading, size = null ) } } }, onGoBack = onBack, shouldDisableBackHandler = !component.haveChanges, actions = { scaffoldState -> var editSheetData by remember { mutableStateOf(listOf()) } EnhancedIconButton( onClick = { scope.launch { if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) { scaffoldState.bottomSheetState.partialExpand() } else { scaffoldState.bottomSheetState.expand() } } }, ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = stringResource(R.string.properties) ) } ShareButton( onShare = component::performSharing, onCopy = { component.cacheImage(Clipboard::copy) }, onEdit = { component.cacheImage { editSheetData = listOf(it) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, topAppBarPersistentActions = { if (component.uris.isNullOrEmpty()) { TopAppBarEmoji() } else { var showResetDialog by rememberSaveable { mutableStateOf(false) } EnhancedIconButton( onClick = { showResetDialog = true } ) { Icon( imageVector = Icons.Rounded.ImageReset, contentDescription = stringResource(R.string.reset_image) ) } ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, onReset = { resettingTrigger++ } ) } }, mainContent = { LaunchedEffect(component.uris) { if (!component.uris.isNullOrEmpty()) { delay(200) isLoading = false } } Box( modifier = Modifier .fillMaxSize() .padding(20.dp) ) { var bottomPadding by remember { mutableStateOf(0.dp) } var infoHeight by remember { mutableStateOf(0.dp) } var tapIndex by remember { mutableIntStateOf(-1) } var showItemMenu by remember { mutableStateOf(false) } val singlePicker = rememberImagePicker { uri: Uri -> component.replaceImageAt( index = tapIndex, uri = uri ) } val addPicker = rememberImagePicker { uri: Uri -> component.addImage(uri) } Box( modifier = Modifier .align(Alignment.Center) .padding( bottom = bottomPadding ) ) { AnimatedContent( targetState = resettingTrigger, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { trigger -> key(trigger) { Box( modifier = Modifier .zoomable(rememberZoomState()) .container( shape = ShapeDefaults.extraSmall, resultPadding = 0.dp ) .shimmer(visible = isLoading), contentAlignment = Alignment.Center ) { Collage( modifier = Modifier .padding(4.dp) .clip(ShapeDefaults.extraSmall) .transparencyChecker(), images = component.uris ?: emptyList(), collageType = component.collageType, collageCreationTrigger = component.collageCreationTrigger, onCollageCreated = { component.updateCollageBitmap(it) isLoading = false }, backgroundColor = component.backgroundColor, spacing = component.params.spacing, cornerRadius = component.params.cornerRadius, aspectRatio = component.aspectRatio.value, outputScaleRatio = component.params.outputScaleRatio, disableRotation = component.params.disableRotation, enableSnapToBorders = component.params.enableSnapToBorders, onImageTap = { index -> if (index >= 0) { tapIndex = index showItemMenu = true } else { showItemMenu = false } }, handleDrawable = rememberHandleDrawable() ) } } } } val density = LocalDensity.current InfoContainer( modifier = Modifier .align(Alignment.BottomCenter) .onSizeChanged { val height = with(density) { it.height.toDp() } infoHeight = height bottomPadding = height + 20.dp }, containerColor = MaterialTheme.colorScheme.surfaceContainerLow, text = stringResource(R.string.collages_info) ) AnimatedVisibility( visible = showItemMenu, modifier = Modifier .align(Alignment.BottomCenter) .padding(bottom = infoHeight + 12.dp), enter = fadeIn(), exit = fadeOut() ) { Row( modifier = Modifier .container( color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = ShapeDefaults.large, resultPadding = 0.dp ), horizontalArrangement = Arrangement.spacedBy(2.dp) ) { EnhancedIconButton( onClick = { showItemMenu = false singlePicker.pickImage() }, enabled = tapIndex >= 0 ) { Icon( imageVector = Icons.Rounded.SwapHoriz, contentDescription = "Swap" ) } EnhancedIconButton( onClick = { showItemMenu = false addPicker.pickImage() } ) { Icon( imageVector = Icons.Rounded.Add, contentDescription = "Add" ) } EnhancedIconButton( onClick = { showItemMenu = false component.removeImageAt(tapIndex) }, enabled = tapIndex >= 0 ) { Icon( imageVector = Icons.Rounded.Delete, contentDescription = "Delete" ) } } } } }, controls = { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { val canChangeCollage = (component.uris?.size ?: 0) > 1 Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .then( if (canChangeCollage) { Modifier.container( resultPadding = 0.dp, shape = ShapeDefaults.extraLarge ) } else Modifier ) ) { if (canChangeCollage) { Text( fontWeight = FontWeight.Medium, text = stringResource(R.string.collage_type), modifier = Modifier.padding(top = 16.dp), fontSize = 18.sp ) } val state = rememberLazyListState() CollageTypeSelection( state = state, imagesCount = component.uris?.size ?: 0, value = component.collageType, onValueChange = component::setCollageType, modifier = Modifier .fillMaxWidth() .height(100.dp) .fadingEdges(state), contentPadding = PaddingValues(16.dp), shape = ShapeDefaults.small, itemModifierFactory = { isSelected -> Modifier .container( resultPadding = 0.dp, color = animateColorAsState( targetValue = if (isSelected) { MaterialTheme.colorScheme.secondaryContainer } else MaterialTheme.colorScheme.surfaceContainerLowest, ).value, shape = ShapeDefaults.small ) .padding(8.dp) .clip(ShapeDefaults.extremeSmall) } ) } ColorRowSelector( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.extraLarge ), icon = Icons.Outlined.BackgroundColor, value = component.backgroundColor, onValueChange = component::setBackgroundColor ) AspectRatioSelector( selectedAspectRatio = component.aspectRatio, onAspectRatioChange = { aspect, _ -> component.setAspectRatio(aspect) }, unselectedCardColor = MaterialTheme.colorScheme.surfaceContainerLowest, aspectRatios = remember { DomainAspectRatio.defaultList - setOf( DomainAspectRatio.Free, DomainAspectRatio.Original ) } ) EnhancedSliderItem( modifier = Modifier.fillMaxWidth(), value = component.params.spacing, title = stringResource(R.string.spacing), valueRange = 0f..50f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = component::setSpacing, sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.FormatLineSpacing, shape = ShapeDefaults.extraLarge ) EnhancedSliderItem( modifier = Modifier.fillMaxWidth(), value = component.params.cornerRadius, title = stringResource(R.string.corners), valueRange = 0f..50f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = component::setCornerRadius, sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.RoundedCorner, shape = ShapeDefaults.extraLarge ) EnhancedSliderItem( modifier = Modifier.fillMaxWidth(), value = component.params.outputScaleRatio, title = stringResource(R.string.output_image_scale), valueRange = 0.5f..4f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = component::setOutputScaleRatio, sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.PhotoSizeSelectSmall, shape = ShapeDefaults.extraLarge ) PreferenceRowSwitch( title = stringResource(id = R.string.disable_rotation), subtitle = stringResource(id = R.string.disable_rotation_sub), checked = component.params.disableRotation, startIcon = Icons.Outlined.SwipeVertical, onClick = component::setDisableRotation ) PreferenceRowSwitch( title = stringResource(id = R.string.enable_snapping_to_borders), subtitle = stringResource(id = R.string.enable_snapping_to_borders_sub), checked = component.params.enableSnapToBorders, startIcon = Icons.Outlined.PinEnd, onClick = component::setEnableSnapToBorders ) QualitySelector( imageFormat = component.imageFormat, quality = component.quality, onQualityChange = component::setQuality ) ImageFormatSelector( modifier = Modifier.navigationBarsPadding(), value = component.imageFormat, quality = component.quality, onValueChange = component::setImageFormat, entries = if (component.backgroundColor.alpha != 1f) { ImageFormatGroup.alphaContainedEntries } else ImageFormatGroup.entries, forceEnabled = true ) } }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } }, canShowScreenData = !component.uris.isNullOrEmpty() ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving || component.isImageLoading, onCancelLoading = component::cancelSaving ) } @Composable private fun rememberHandleDrawable(): Drawable { val context = LocalContext.current val width = with(LocalDensity.current) { 42.dp.roundToPx() } val height = width / 2 val backgroundColor = MaterialTheme.colorScheme.tertiary val iconColor = MaterialTheme.colorScheme.onTertiary return remember(width, backgroundColor, iconColor, context) { val container = GradientDrawable().apply { shape = GradientDrawable.RECTANGLE setColor(Color.Transparent.toArgb()) setSize(width, width) } val box = GradientDrawable().apply { mutate() shape = GradientDrawable.RECTANGLE cornerRadius = height / 2f setColor(backgroundColor.toArgb()) setSize(width, height) } val icon = AppCompatResources .getDrawable(context, R.drawable.outline_drag_handle_24)!! .mutate() DrawableCompat.setTint(icon, iconColor.toArgb()) LayerDrawable(arrayOf(container, box, icon)).apply { val insetH = 0 val insetV = (width - height) / 2 val inset = (width * 0.2f).toInt() setLayerInset(0, 0, 0, 0, 0) setLayerInset(1, insetH, insetV, insetH, insetV) setLayerInset(2, inset, inset, inset, inset) setBounds(0, 0, width, height) } } } ================================================ FILE: feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/components/CollageParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.collage_maker.presentation.components data class CollageParams( val spacing: Float = 10f, val cornerRadius: Float = 0f, val outputScaleRatio: Float = 2f, val disableRotation: Boolean = true, val enableSnapToBorders: Boolean = true ) ================================================ FILE: feature/collage-maker/src/main/java/com/t8rin/imagetoolbox/collage_maker/presentation/screenLogic/CollageMakerComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.collage_maker.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.collages.CollageType import com.t8rin.collages.public.CollageConstants import com.t8rin.imagetoolbox.collage_maker.presentation.components.CollageParams import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class CollageMakerComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val shareProvider: ImageShareProvider, private val settingsManager: SettingsManager, dispatchersHolder: DispatchersHolder, ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::updateUris) _imageFormat.value = settingsManager.settingsState.value.defaultImageFormat ?: imageFormat } } private val _aspectRatio: MutableState = mutableStateOf(DomainAspectRatio.Numeric(1f, 1f)) val aspectRatio by _aspectRatio private val _backgroundColor = mutableStateOf(Color.White) val backgroundColor: Color by _backgroundColor private val _collageCreationTrigger = mutableStateOf(false) val collageCreationTrigger by _collageCreationTrigger private val _collageType: MutableState = mutableStateOf(CollageType.Empty) val collageType by _collageType private val _collageBitmap = mutableStateOf(null) private val collageBitmap by _collageBitmap private val _params = fileController.savable( scope = componentScope, initial = CollageParams() ) val params by _params private val _uris = mutableStateOf?>(null) val uris by _uris private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat: ImageFormat by _imageFormat private val _quality: MutableState = mutableStateOf(Quality.Base()) val quality: Quality by _quality private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private var requestedOperation: () -> Unit = {} fun setCollageType(collageType: CollageType) { _collageType.update { collageType } registerChanges() } fun updateCollageBitmap(bitmap: Bitmap) { _collageCreationTrigger.update { false } _collageBitmap.update { bitmap } requestedOperation() } fun updateUris(uris: List?) { componentScope.launch { _isImageLoading.update { true } _uris.update { uris } _isImageLoading.update { false } } } fun replaceImageAt(index: Int, uri: Uri) { _uris.update { current -> val list = current?.toMutableList() ?: return@update current if (index in list.indices) { list[index] = uri list } else current } registerChanges() } fun addImage(uri: Uri) { _uris.update { current -> val list = current ?: emptyList() if (list.size >= CollageConstants.MAX_IMAGE_COUNT) list else list + uri } registerChanges() } fun removeImageAt(index: Int) { _uris.update { current -> val list = current?.toMutableList() ?: return@update current if (index in list.indices) { list.removeAt(index) list } else current } registerChanges() } fun setQuality(quality: Quality) { _quality.update { quality } registerChanges() } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } registerChanges() } fun setOutputScaleRatio(ratio: Float) { _params.update { it.copy(outputScaleRatio = ratio) } registerChanges() } fun setDisableRotation(value: Boolean) { _params.update { it.copy(disableRotation = value) } registerChanges() } fun setEnableSnapToBorders(value: Boolean) { _params.update { it.copy(enableSnapToBorders = value) } registerChanges() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmap( oneTimeSaveLocationUri: String? ) { _isSaving.update { true } _collageCreationTrigger.update { true } requestedOperation = { savingJob = trackProgress { collageBitmap?.let { image -> _isSaving.update { true } val imageInfo = ImageInfo( width = image.width, height = image.height, quality = quality, imageFormat = imageFormat ) val result = fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = "", sequenceNumber = null, data = imageCompressor.compress( image = image, imageFormat = imageFormat, quality = quality ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) parseSaveResult(result.onSuccess(::registerSave)) _isSaving.update { false } } } } } fun performSharing() { _isSaving.update { true } _collageCreationTrigger.update { true } requestedOperation = { collageBitmap?.let { image -> savingJob = trackProgress { _isSaving.update { true } shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, quality = quality, imageFormat = imageFormat ) )?.let { uri -> shareProvider.shareUri( uri = uri, onComplete = AppToastHost::showConfetti ) } _isSaving.update { false } } } } } fun cacheImage( onComplete: (Uri) -> Unit, ) { _isSaving.update { true } _collageCreationTrigger.update { true } requestedOperation = { collageBitmap?.let { image -> savingJob = trackProgress { _isSaving.update { true } shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, quality = quality, imageFormat = imageFormat ) )?.let { uri -> onComplete(uri.toUri()) } _isSaving.update { false } } } } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.update { false } } fun setBackgroundColor(color: Color) { _backgroundColor.update { color } registerChanges() } fun setSpacing(value: Float) { _params.update { it.copy(spacing = value) } registerChanges() } fun setCornerRadius(value: Float) { _params.update { it.copy(cornerRadius = value) } registerChanges() } fun getFormatForFilenameSelection(): ImageFormat = imageFormat fun setAspectRatio(aspect: DomainAspectRatio) { _aspectRatio.update { aspect } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): CollageMakerComponent } } ================================================ FILE: feature/color-library/.gitignore ================================================ /build ================================================ FILE: feature/color-library/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.color_library" ================================================ FILE: feature/color-library/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/ColorLibraryContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_library.presentation import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.plus import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.colors.util.ColorUtil import com.t8rin.imagetoolbox.color_library.presentation.screenLogic.ColorLibraryComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.other.ColorWithNameItem import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.isKeyboardVisibleAsState import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable fun ColorLibraryContent( component: ColorLibraryComponent ) { val isKeyboardVisible by isKeyboardVisibleAsState() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val colors = component.colors val searchKeyword = component.searchKeyword val favoriteColors = component.favoriteColors val settingsState = LocalSettingsState.current val copyColor: (Color) -> Unit = { color -> Clipboard.copy( text = if (color.alpha == 1f) { ColorUtil.colorToHex(color) } else { ColorUtil.colorToHexAlpha(color) }.uppercase(), message = R.string.color_copied ) } val focus = LocalFocusManager.current var isSearching by rememberSaveable { mutableStateOf(false) } Scaffold( modifier = Modifier .clearFocusOnTap() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { EnhancedTopAppBar( type = EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior, title = { Text( text = stringResource(R.string.color_library), modifier = Modifier.marquee() ) }, navigationIcon = { EnhancedIconButton( onClick = component.onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, actions = { TopAppBarEmoji() } ) }, bottomBar = { val insets = WindowInsets.navigationBars.union( WindowInsets.displayCutout.only( WindowInsetsSides.Horizontal ) ) AnimatedContent( targetState = isSearching, modifier = Modifier.fillMaxWidth() ) { isSearch -> if (isSearch) { Box( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke(true) .background( MaterialTheme.colorScheme.surfaceContainer ) .pointerInput(Unit) { detectTapGestures { } } .windowInsetsPadding(insets) .padding(16.dp) ) { ProvideTextStyle(value = MaterialTheme.typography.bodyLarge) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = searchKeyword, onValueChange = component::updateSearch, endIcon = { EnhancedIconButton( onClick = { if (searchKeyword.isNotBlank()) { component.clearSearch() } else { isSearching = false focus.clearFocus() } }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface.copy( if (it) 1f else 0.5f ) ) } }, shape = ShapeDefaults.circle ) } } } else { Box( modifier = Modifier .windowInsetsPadding(insets) .padding(16.dp) .fillMaxWidth() ) { EnhancedFloatingActionButton( onClick = { isSearching = true }, modifier = Modifier.align( settingsState.fabAlignment.takeIf { it != Alignment.BottomCenter } ?: Alignment.BottomEnd ) ) { Icon( imageVector = Icons.Rounded.Search, contentDescription = null ) } } } } } ) { contentPadding -> AnimatedContent( targetState = colors.isNotEmpty(), modifier = Modifier.fillMaxSize() ) { isNotEmpty -> if (isNotEmpty) { val reverseLayout = searchKeyword.isNotEmpty() && isKeyboardVisible LazyVerticalGrid( contentPadding = contentPadding + PaddingValues(12.dp), verticalArrangement = Arrangement.spacedBy( space = 4.dp, alignment = if (reverseLayout) { Alignment.Bottom } else { Alignment.Top } ), horizontalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior(), columns = GridCells.Adaptive(180.dp), modifier = Modifier.fillMaxSize() ) { items( items = colors, key = { it.toString() } ) { colorWithName -> ColorWithNameItem( isFavorite = colorWithName.name in favoriteColors, color = colorWithName.color, name = colorWithName.name, onToggleFavorite = { component.toggleFavoriteColor(colorWithName) }, onCopy = { copyColor(colorWithName.color) }, modifier = Modifier.animateItem() ) } } } else if (isSearching) { Column( modifier = Modifier .padding(contentPadding) .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } else { Box( modifier = Modifier .padding(contentPadding) .fillMaxSize(), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } } ================================================ FILE: feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/components/FavoriteColors.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_library.presentation.components data class FavoriteColors( val colors: Set = emptySet() ) ================================================ FILE: feature/color-library/src/main/java/com/t8rin/imagetoolbox/color_library/presentation/screenLogic/ColorLibraryComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_library.presentation.screenLogic import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshotFlow import com.arkivanov.decompose.ComponentContext import com.t8rin.colors.parser.ColorNameParser import com.t8rin.colors.parser.ColorWithName import com.t8rin.imagetoolbox.color_library.presentation.components.FavoriteColors import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.utils.state.savable import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.collectLatest class ColorLibraryComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private var baseColors: List = emptyList() private val _colors: MutableState> = mutableStateOf(emptyList()) val colors by _colors private val _searchKeyword: MutableState = mutableStateOf("") val searchKeyword by _searchKeyword private val _favoriteColors = fileController.savable( scope = componentScope, initial = FavoriteColors(), ) val favoriteColors get() = _favoriteColors.get().colors init { componentScope.launch { setColors(ColorNameParser.colorNames.values.sortedBy { it.name }.toList()) snapshotFlow { favoriteColors } .collectLatest { favorite -> setColors( baseColors.sortedBy { it.name !in favorite } ) if (searchKeyword.isNotBlank()) { updateSearch() } } } } private fun setColors(newColors: List) { baseColors = newColors _colors.value = newColors } fun updateSearch(keyword: String) { _searchKeyword.value = keyword if (keyword.isBlank()) { updateSearch() return } debouncedImageCalculation( delay = 350, action = { updateSearch() } ) } private fun updateSearch() { if (searchKeyword.isBlank()) { _colors.value = baseColors } else { _colors.value = baseColors.filter { it.name.contains( other = searchKeyword, ignoreCase = true ) || searchKeyword.contains( other = it.name, ignoreCase = true ) || it.color.toHex().contains( other = searchKeyword, ignoreCase = true ) } } } fun clearSearch() { updateSearch("") } fun toggleFavoriteColor(color: ColorWithName) { _favoriteColors.update { it.copy(colors = it.colors.toggle(color.name)) } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, ): ColorLibraryComponent } } ================================================ FILE: feature/color-tools/.gitignore ================================================ /build ================================================ FILE: feature/color-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.color_tools" ================================================ FILE: feature/color-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/ColorToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PushPin import androidx.compose.material.icons.rounded.Palette import androidx.compose.material.icons.rounded.PushPin import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.gigamole.composefadingedges.FadingEdgesGravity import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.color_tools.presentation.components.ColorHarmonies import com.t8rin.imagetoolbox.color_tools.presentation.components.ColorHistogram import com.t8rin.imagetoolbox.color_tools.presentation.components.ColorInfo import com.t8rin.imagetoolbox.color_tools.presentation.components.ColorMixing import com.t8rin.imagetoolbox.color_tools.presentation.components.ColorShading import com.t8rin.imagetoolbox.color_tools.presentation.screenLogic.ColorToolsComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberAppColorTuple import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.negativePadding import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable fun ColorToolsContent( component: ColorToolsComponent ) { val themeState = LocalDynamicThemeState.current val settingsState = LocalSettingsState.current val allowChangeColor = settingsState.allowChangeColorByImage val appColorTuple = rememberAppColorTuple() val selectedColor = component.selectedColor.takeOrElse { appColorTuple.primary } LaunchedEffect(selectedColor) { if (allowChangeColor) { themeState.updateColor(selectedColor) } } val isPortrait by isPortraitOrientationAsState() val isPinned = component.isPinned val colorSelector = @Composable { ColorRowSelector( value = selectedColor, onValueChange = component::updateSelectedColor, modifier = Modifier .fillMaxWidth() .container( color = if (isPinned && !isPortrait) { MaterialTheme.colorScheme.surface } else { Color.Unspecified }, shape = ShapeDefaults.large ), icon = Icons.Rounded.Palette, title = stringResource(R.string.selected_color), topEndIcon = { EnhancedIconButton( onClick = { component.updateIsPinned(!isPinned) } ) { Icon( imageVector = if (isPinned) Icons.Rounded.PushPin else Icons.Outlined.PushPin, contentDescription = null ) } } ) } key(isPinned) { AdaptiveLayoutScreen( title = { Text( text = stringResource(R.string.color_tools), modifier = Modifier.marquee() ) }, shouldDisableBackHandler = true, onGoBack = component.onGoBack, actions = {}, topAppBarPersistentActions = { TopAppBarEmoji() }, imagePreview = { Column( modifier = if (isPortrait) { Modifier .negativePadding(horizontal = 20.dp) .fadingEdges( scrollableState = null, isVertical = true, length = 16.dp, gravity = FadingEdgesGravity.End ) .background(MaterialTheme.colorScheme.surface) .padding(20.dp) } else { Modifier } ) { colorSelector() } }, useRegularStickyHeader = true, controls = { listState -> LaunchedEffect(isPinned) { if (isPinned) { listState.scrollToItem(0) } } Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { if (!isPinned) { if (isPortrait) { Spacer(modifier = Modifier.height(12.dp)) } colorSelector() Spacer(modifier = Modifier.fillMaxWidth()) } ColorInfo( selectedColor = selectedColor, onColorChange = component::updateSelectedColor ) ColorMixing( selectedColor = selectedColor, appColorTuple = appColorTuple ) ColorHarmonies( selectedColor = selectedColor ) ColorShading( selectedColor = selectedColor ) ColorHistogram() } }, buttons = {}, placeImagePreview = isPinned, canShowScreenData = true ) } } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmonies.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BarChart import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.ColorWithNameItem import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun ColorHarmonies( selectedColor: Color, ) { var selectedHarmony by rememberSaveable { mutableStateOf(HarmonyType.COMPLEMENTARY) } val harmonies by remember(selectedColor, selectedHarmony) { derivedStateOf { selectedColor.applyHarmony(selectedHarmony) } } ExpandableItem( visibleContent = { TitleItem( text = stringResource(R.string.color_harmonies), icon = Icons.Rounded.BarChart, modifier = Modifier.padding(12.dp) ) }, expandableContent = { Column( modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 8.dp ), ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.fillMaxWidth() ) { HarmonyType.entries.forEach { EnhancedChip( selected = it == selectedHarmony, onClick = { selectedHarmony = it }, selectedColor = MaterialTheme.colorScheme.secondaryContainer, modifier = Modifier.weight(1f) ) { Icon( imageVector = it.icon(), contentDescription = null ) } } } Spacer(Modifier.height(8.dp)) AnimatedContent( targetState = selectedHarmony, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { Text(it.title()) } Spacer(Modifier.height(8.dp)) AnimatedContent( targetState = harmonies, modifier = Modifier.fillMaxWidth(), transitionSpec = { fadeIn() togetherWith fadeOut() } ) { harmonies -> Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { harmonies.forEachIndexed { index, color -> val shape = ShapeDefaults.byIndex( index = index, size = harmonies.size, vertical = false ) ColorWithNameItem( color = color, containerShape = shape, onCopy = { Clipboard.copy( text = getFormattedColor(color), message = R.string.color_copied ) }, modifier = Modifier .heightIn(min = 120.dp) .weight(1f) ) } } } } }, shape = ShapeDefaults.extraLarge, initialState = false ) } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHarmoniesUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Analogous import com.t8rin.imagetoolbox.core.resources.icons.AnalogousComplementary import com.t8rin.imagetoolbox.core.resources.icons.Complementary import com.t8rin.imagetoolbox.core.resources.icons.SplitComplementary import com.t8rin.imagetoolbox.core.resources.icons.SquareHarmony import com.t8rin.imagetoolbox.core.resources.icons.Tetradic import com.t8rin.imagetoolbox.core.resources.icons.Triadic import com.t8rin.imagetoolbox.core.ui.theme.toColor import android.graphics.Color as AndroidColor fun Color.applyHarmony( type: HarmonyType ): List { val (h, s, v) = toHSV() return when (type) { HarmonyType.COMPLEMENTARY -> listOf( hsvToColor(h, s, v), hsvToColor((h + 180) % 360, s, v) ) HarmonyType.ANALOGOUS -> listOf( hsvToColor(h, s, v), hsvToColor((h + 30) % 360, s, v), hsvToColor((h - 30 + 360) % 360, s, v) ) HarmonyType.ANALOGOUS_COMPLEMENTARY -> listOf( hsvToColor(h, s, v), hsvToColor((h + 30) % 360, s, v), hsvToColor((h - 30 + 360) % 360, s, v), hsvToColor((h + 180) % 360, s, v) ) HarmonyType.TRIADIC -> listOf( hsvToColor(h, s, v), hsvToColor((h + 120) % 360, s, v), hsvToColor((h - 120 + 360) % 360, s, v) ) HarmonyType.SPLIT_COMPLEMENTARY -> listOf( hsvToColor(h, s, v), hsvToColor((h + 150) % 360, s, v), hsvToColor((h - 150 + 360) % 360, s, v) ) HarmonyType.TETRADIC -> listOf( hsvToColor(h, s, v), hsvToColor((h + 30) % 360, s, v), hsvToColor((h + 180) % 360, s, v), hsvToColor((h + 210) % 360, s, v) ) HarmonyType.SQUARE -> listOf( hsvToColor(h, s, v), hsvToColor((h + 90) % 360, s, v), hsvToColor((h + 180) % 360, s, v), hsvToColor((h + 270) % 360, s, v) ) }.map { it.copy(alpha) } } enum class HarmonyType { COMPLEMENTARY, ANALOGOUS, ANALOGOUS_COMPLEMENTARY, TRIADIC, SPLIT_COMPLEMENTARY, TETRADIC, SQUARE } @Composable fun HarmonyType.title(): String = when (this) { HarmonyType.COMPLEMENTARY -> stringResource(R.string.harmony_complementary) HarmonyType.ANALOGOUS -> stringResource(R.string.harmony_analogous) HarmonyType.TRIADIC -> stringResource(R.string.harmony_triadic) HarmonyType.SPLIT_COMPLEMENTARY -> stringResource(R.string.harmony_split_complementary) HarmonyType.TETRADIC -> stringResource(R.string.harmony_tetradic) HarmonyType.SQUARE -> stringResource(R.string.harmony_square) HarmonyType.ANALOGOUS_COMPLEMENTARY -> stringResource(R.string.harmony_analogous_complementary) } fun HarmonyType.icon(): ImageVector = when (this) { HarmonyType.COMPLEMENTARY -> Icons.Filled.Complementary HarmonyType.ANALOGOUS -> Icons.Filled.Analogous HarmonyType.ANALOGOUS_COMPLEMENTARY -> Icons.Filled.AnalogousComplementary HarmonyType.TRIADIC -> Icons.Filled.Triadic HarmonyType.SPLIT_COMPLEMENTARY -> Icons.Filled.SplitComplementary HarmonyType.TETRADIC -> Icons.Filled.Tetradic HarmonyType.SQUARE -> Icons.Filled.SquareHarmony } fun Color.toHSV(): FloatArray { val hsv = FloatArray(3) AndroidColor.colorToHSV(toArgb(), hsv) return hsv } fun hsvToColor( h: Float, s: Float, v: Float ): Color { return AndroidColor.HSVToColor(floatArrayOf(h, s, v)).toColor() } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorHistogram.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.histogram.HistogramType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AreaChart import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.image.HistogramChart import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun ColorHistogram() { var imageUri by rememberSaveable { mutableStateOf(null) } ExpandableItem( visibleContent = { TitleItem( text = stringResource(R.string.histogram), icon = Icons.Rounded.AreaChart, modifier = Modifier.padding(12.dp) ) }, expandableContent = { Column( modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 8.dp ) ) { ImageSelector( value = imageUri, onValueChange = { imageUri = it }, subtitle = stringResource(R.string.image_for_histogram), shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ) Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { HistogramChart( model = imageUri, modifier = Modifier .padding(top = 16.dp) .fillMaxWidth() .height(250.dp) .background( color = MaterialTheme.colorScheme.background, shape = ShapeDefaults.extraSmall ), initialType = HistogramType.RGB, onSwapType = null, linesThickness = 1.dp, bordersShape = ShapeDefaults.pressed ) HistogramChart( model = imageUri, modifier = Modifier .fillMaxWidth() .height(250.dp) .background( color = MaterialTheme.colorScheme.background, shape = ShapeDefaults.extraSmall ), initialType = HistogramType.Brightness, onSwapType = null, linesThickness = 1.dp, bordersShape = ShapeDefaults.pressed ) HistogramChart( model = imageUri, modifier = Modifier .fillMaxWidth() .height(250.dp) .background( color = MaterialTheme.colorScheme.background, shape = ShapeDefaults.extraSmall ), initialType = HistogramType.Camera, onSwapType = null, linesThickness = 1.dp, bordersShape = ShapeDefaults.pressed ) } } }, shape = ShapeDefaults.extraLarge, initialState = false ) } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Info import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.ColorWithNameItem import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable internal fun ColorInfo( selectedColor: Color, onColorChange: (Color) -> Unit, ) { val scope = rememberCoroutineScope() ExpandableItem( visibleContent = { TitleItem( text = stringResource(R.string.color_info), icon = Icons.Rounded.Info, modifier = Modifier.padding(12.dp) ) }, expandableContent = { Column( modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 8.dp ), ) { ColorWithNameItem( color = selectedColor, onCopy = { Clipboard.copy( text = getFormattedColor(selectedColor), message = R.string.color_copied ) } ) Spacer(modifier = Modifier.height(16.dp)) var wasNull by rememberSaveable { mutableStateOf(false) } var resetJob by remember { mutableStateOf(null) } ColorInfoDisplay( value = selectedColor, onValueChange = { wasNull = it == null onColorChange(it ?: selectedColor) }, onCopy = { Clipboard.copy( text = it, message = R.string.color_copied ) }, onLoseFocus = { resetJob?.cancel() resetJob = scope.launch { delay(100) if (wasNull) { selectedColor.let { onColorChange(Color.White) delay(100) onColorChange(it) } } } } ) } }, shape = ShapeDefaults.extraLarge, initialState = true ) } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorInfoDisplay.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.core.graphics.ColorUtils import com.t8rin.colors.parser.ColorNameParser import com.t8rin.colors.util.ColorUtil import com.t8rin.colors.util.HexUtil import com.t8rin.colors.util.HexVisualTransformation import com.t8rin.colors.util.hexRegexSingleChar import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import kotlinx.coroutines.delay import kotlin.math.roundToInt import android.graphics.Color as AndroidColor @Composable fun ColorInfoDisplay( value: Color, onValueChange: (Color?) -> Unit, onCopy: (String) -> Unit, onLoseFocus: () -> Unit ) { var hexColor by remember(value) { mutableStateOf(value.toHex()) } var rgb by remember(value) { mutableStateOf(value.toRGB()) } var hsv by remember(value) { mutableStateOf(value.toHSVString()) } var hsl by remember(value) { mutableStateOf(value.toHSL()) } var cmyk by remember(value) { mutableStateOf(value.toCMYK()) } Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { ColorEditableField( label = "HEX", value = hexColor.removePrefix("#"), onCopy = onCopy, visualTransformation = HexVisualTransformation(false), onValueChange = { newHex -> val newHex = newHex.replace("#", "") if (newHex.length <= 8) { var validHex = true for (index in newHex.indices) { validHex = hexRegexSingleChar.matches(newHex[index].toString()) if (!validHex) break } if (validHex) { hexColor = "#${newHex.uppercase()}" val color = newHex.toColor() onValueChange(color) } } }, modifier = Modifier.weight(1f), onLoseFocus = onLoseFocus ) ColorEditableField( label = "RGB", value = rgb, onValueChange = { newRgb -> rgb = newRgb val color = rgbToColor(newRgb) onValueChange(color) }, onCopy = onCopy, modifier = Modifier.weight(1f), onLoseFocus = onLoseFocus ) } Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { ColorEditableField( label = "HSV", value = hsv, onValueChange = { newHsv -> hsv = newHsv val color = hsvToColor(newHsv) onValueChange(color) }, onCopy = onCopy, modifier = Modifier.weight(1f), onLoseFocus = onLoseFocus ) ColorEditableField( label = "HSL", value = hsl, onValueChange = { newHsl -> hsl = newHsl val color = hslToColor(newHsl) onValueChange(color) }, onCopy = onCopy, modifier = Modifier.weight(1f), onLoseFocus = onLoseFocus ) } ColorEditableField( label = "CMYK", value = cmyk, onValueChange = { newCmyk -> cmyk = newCmyk val color = cmykToColor(newCmyk) onValueChange(color) }, onCopy = onCopy, modifier = Modifier.fillMaxWidth(), onLoseFocus = onLoseFocus ) var name by remember { mutableStateOf(ColorNameParser.parseColorName(color = value)) } var isFocused by remember { mutableStateOf(false) } LaunchedEffect(value, isFocused) { if (!isFocused) { delay(200) name = ColorNameParser.parseColorName(value) } } ColorEditableField( label = stringResource(R.string.name), value = name, onValueChange = { newName -> name = newName onValueChange( ColorNameParser.parseColorFromNameSingle(newName) ) }, onCopy = onCopy, modifier = Modifier .fillMaxWidth() .onFocusChanged { focusState -> isFocused = focusState.isFocused }, onLoseFocus = onLoseFocus ) } } internal fun getFormattedColor(color: Color): String { return if (color.alpha == 1f) { ColorUtil.colorToHex(color) } else { ColorUtil.colorToHexAlpha(color) }.uppercase() } @Composable private fun ColorEditableField( label: String, value: String, onLoseFocus: () -> Unit, visualTransformation: VisualTransformation = VisualTransformation.None, onValueChange: (String) -> Unit, onCopy: (String) -> Unit, modifier: Modifier ) { RoundedTextField( modifier = modifier, value = value, visualTransformation = visualTransformation, onValueChange = onValueChange, onLoseFocusTransformation = { onLoseFocus() this }, label = { Text(label) }, singleLine = true, endIcon = { EnhancedIconButton( onClick = { onCopy(value) }, forceMinimumInteractiveComponentSize = false, modifier = Modifier.size(36.dp) ) { Icon( imageVector = Icons.Rounded.ContentCopy, contentDescription = null ) } } ) } fun Color.toHex(): String { val red = (red * 255).roundToInt() val green = (green * 255).roundToInt() val blue = (blue * 255).roundToInt() return String.format("#%02X%02X%02X", red, green, blue) } fun String.toColor(): Color? { return runCatching { HexUtil.hexToColor(this) }.getOrNull() } fun Color.toRGB(): String { val r = (red * 255).roundToInt() val g = (green * 255).roundToInt() val b = (blue * 255).roundToInt() return "$r, $g, $b" } fun rgbToColor(rgb: String): Color? = runCatching { val (r, g, b) = rgb.split(",").map { it.trim().toInt() } Color(r / 255f, g / 255f, b / 255f) }.getOrNull() fun Color.toHSVString(): String { val hsv = FloatArray(3) AndroidColor.colorToHSV(this.toArgb(), hsv) return "${hsv[0].roundToInt()}, ${(hsv[1] * 100).roundToInt()}, ${(hsv[2] * 100).roundToInt()}" } fun hsvToColor(hsv: String): Color? = runCatching { val (h, s, v) = hsv.split(",") .map { it.trim().toInt() } val colorInt = AndroidColor.HSVToColor(floatArrayOf(h.toFloat(), s / 100f, v / 100f)) Color(colorInt) }.getOrNull() fun Color.toHSL(): String { val hsl = FloatArray(3) ColorUtils.colorToHSL(this.toArgb(), hsl) return "${hsl[0].roundToInt()}, ${(hsl[1] * 100).roundToInt()}, ${(hsl[2] * 100).roundToInt()}" } fun hslToColor(hsl: String): Color? = runCatching { val (h, s, l) = hsl.split(",") .map { it.trim().toInt() } val colorInt = ColorUtils.HSLToColor(floatArrayOf(h.toFloat(), s / 100f, l / 100f)) Color(colorInt) }.getOrNull() fun Color.toCMYK(): String { val k = (1 - maxOf(red, green, blue)) val c = ((1 - red - k) / (1 - k)).takeIf { it.isFinite() } ?: 0f val m = ((1 - green - k) / (1 - k)).takeIf { it.isFinite() } ?: 0f val y = ((1 - blue - k) / (1 - k)).takeIf { it.isFinite() } ?: 0f return "${(c * 100).roundToInt()}, ${(m * 100).roundToInt()}, ${(y * 100).roundToInt()}, ${(k * 100).roundToInt()}" } fun cmykToColor(cmyk: String): Color? = runCatching { val (c, m, y, k) = cmyk.split(",").map { it.trim().toInt() / 100f } val r = 255 * (1 - c) * (1 - k) val g = 255 * (1 - m) * (1 - k) val b = 255 * (1 - y) * (1 - k) Color(r / 255f, g / 255f, b / 255f) }.getOrNull() ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixing.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Blender import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.ColorWithNameItem import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlin.math.roundToInt @Composable internal fun ColorMixing( selectedColor: Color, appColorTuple: ColorTuple, ) { var mixingVariation by rememberSaveable { mutableIntStateOf(3) } var colorToMix by rememberSaveable( stateSaver = ColorSaver ) { mutableStateOf(appColorTuple.tertiary ?: Color.Yellow) } val mixedColors by remember(selectedColor, mixingVariation, colorToMix) { derivedStateOf { selectedColor.mixWith( color = colorToMix, variations = mixingVariation, maxPercent = 1f ) } } ExpandableItem( visibleContent = { TitleItem( text = stringResource(R.string.color_mixing), icon = Icons.Rounded.Blender, modifier = Modifier.padding(12.dp) ) }, expandableContent = { Column( modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 8.dp ), ) { ColorRowSelector( value = colorToMix, onValueChange = { colorToMix = it }, modifier = Modifier .fillMaxWidth() .container( color = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.top ), title = stringResource(R.string.color_to_mix) ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = mixingVariation, title = stringResource(R.string.variation), valueRange = 2f..20f, onValueChange = { mixingVariation = it.roundToInt() }, internalStateTransformation = { it.roundToInt() }, shape = ShapeDefaults.bottom, behaveAsContainer = true, containerColor = MaterialTheme.colorScheme.surface, steps = 17 ) Spacer(modifier = Modifier.height(16.dp)) Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.CenterHorizontally ) { mixedColors.forEachIndexed { index, color -> ColorWithNameItem( color = color, containerShape = ShapeDefaults.byIndex( index = index, size = mixedColors.size ), onCopy = { Clipboard.copy( text = getFormattedColor(color), message = R.string.color_copied ) } ) } } } }, shape = ShapeDefaults.extraLarge, initialState = false ) } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorMixingUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.ui.theme.blend fun Color.mixWith( color: Color, variations: Int, maxPercent: Float = 1f ): List = List(variations) { val percent = it / ((variations + (1f - maxPercent) * 10) - 1) this.blend(color, percent) } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/components/ColorShading.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Swatch import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.ColorWithNameItem import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlin.math.roundToInt @Composable internal fun ColorShading( selectedColor: Color ) { var shadingVariation by rememberSaveable { mutableIntStateOf(5) } val shades by remember(selectedColor, shadingVariation) { derivedStateOf { selectedColor.mixWith( color = Color.Black, variations = shadingVariation, maxPercent = 0.9f ) } } val tones by remember(selectedColor, shadingVariation) { derivedStateOf { selectedColor.mixWith( color = Color(0xff8e918f), variations = shadingVariation, maxPercent = 0.9f ) } } val tints by remember(selectedColor, shadingVariation) { derivedStateOf { selectedColor.mixWith( color = Color.White, variations = shadingVariation, maxPercent = 0.8f ) } } ExpandableItem( visibleContent = { TitleItem( text = stringResource(R.string.color_shading), icon = Icons.Rounded.Swatch, modifier = Modifier.padding(12.dp) ) }, expandableContent = { Column( modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 8.dp ), ) { EnhancedSliderItem( value = shadingVariation, title = stringResource(R.string.variation), valueRange = 2f..20f, onValueChange = { shadingVariation = it.roundToInt() }, internalStateTransformation = { it.roundToInt() }, behaveAsContainer = true, containerColor = MaterialTheme.colorScheme.surface, steps = 17 ) Spacer(modifier = Modifier.height(16.dp)) Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { listOf( tints to R.string.tints, tones to R.string.tones, shades to R.string.shades ).forEach { (data, title) -> Column( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = stringResource(title)) data.forEachIndexed { index, color -> ColorWithNameItem( color = color, containerShape = ShapeDefaults.byIndex( index = index, size = data.size ), onCopy = { Clipboard.copy( text = getFormattedColor(color), message = R.string.color_copied ) }, modifier = Modifier.heightIn(min = 100.dp) ) } } } } } }, shape = ShapeDefaults.extraLarge, initialState = false ) } ================================================ FILE: feature/color-tools/src/main/java/com/t8rin/imagetoolbox/color_tools/presentation/screenLogic/ColorToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.color_tools.presentation.screenLogic import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class ColorToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _selectedColor: MutableState = mutableStateOf(Color.Unspecified) val selectedColor: Color by _selectedColor private val _isPinned = fileController.savable( delay = 750, scope = componentScope, initial = false, key = "ColorToolsComponent" ) val isPinned: Boolean by _isPinned fun updateIsPinned(isPinned: Boolean) { _isPinned.update { isPinned } } fun updateSelectedColor(newColor: Color) { _selectedColor.update { newColor } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, ): ColorToolsComponent } } ================================================ FILE: feature/compare/.gitignore ================================================ /build ================================================ FILE: feature/compare/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.compare" dependencies { implementation(projects.lib.opencvTools) } ================================================ FILE: feature/compare/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/CompareContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.dynamic.theme.extractPrimaryColor import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareScreenContent import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareScreenTopAppBar import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareShareSheet import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareType import com.t8rin.imagetoolbox.feature.compare.presentation.components.model.ifNotEmpty import com.t8rin.imagetoolbox.feature.compare.presentation.screenLogic.CompareComponent import kotlinx.coroutines.delay @Composable fun CompareContent( component: CompareComponent ) { val settingsState = LocalSettingsState.current val themeState = LocalDynamicThemeState.current val allowChangeColor = settingsState.allowChangeColorByImage LaunchedEffect(component.bitmapData) { component.bitmapData?.ifNotEmpty { before, after -> if (allowChangeColor) { delay(100L) //delay to perform screen rotation themeState.updateColor( after.image.extractPrimaryColor() .blend(before.image.extractPrimaryColor(), 0.5f) ) } } } val imagePicker = rememberImagePicker { uris: List -> if (uris.size != 2) { AppToastHost.showFailureToast(R.string.pick_two_images) } else { component.updateUris( uris = uris[0] to uris[1], onFailure = { AppToastHost.showFailureToast(R.string.something_went_wrong) } ) } } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialComparableUris != null ) val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val isPortrait by isPortraitOrientationAsState() var showShareSheet by rememberSaveable { mutableStateOf(false) } var isLabelsEnabled by rememberSaveable { mutableStateOf(true) } Box { Scaffold( modifier = Modifier .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { CompareScreenTopAppBar( imageNotPicked = component.bitmapData == null, scrollBehavior = scrollBehavior, onNavigationIconClick = component.onGoBack, onShareButtonClick = { showShareSheet = true }, onSwapImagesClick = component::swap, onRotateImagesClick = component::rotate, isShareButtonVisible = component.compareType == CompareType.Slide || component.compareType == CompareType.PixelByPixel, isImagesRotated = component.rotation == 90f, titleWhenBitmapsPicked = stringResource(component.compareType.title), isLabelsEnabled = isLabelsEnabled, onToggleLabelsEnabled = { isLabelsEnabled = it }, isLabelsButtonVisible = component.compareType != CompareType.PixelByPixel ) }, contentWindowInsets = WindowInsets() ) { contentPadding -> CompareScreenContent( bitmapData = component.bitmapData, compareType = component.compareType, onCompareTypeSelected = component::setCompareType, isPortrait = isPortrait, compareProgress = component.compareProgress, onCompareProgressChange = component::setCompareProgress, imagePicker = imagePicker, isLabelsEnabled = isLabelsEnabled, pixelByPixelCompareState = component.pixelByPixelCompareState, onPixelByPixelCompareStateChange = component::updatePixelByPixelCompareState, createPixelByPixelTransformation = component::createPixelByPixelTransformation, contentPadding = contentPadding ) } if (component.bitmapData == null) { var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } EnhancedFloatingActionButton( onClick = pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, modifier = Modifier .navigationBarsPadding() .padding(16.dp) .align(settingsState.fabAlignment), content = { Spacer(Modifier.width(16.dp)) Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_image_alt) ) Spacer(Modifier.width(16.dp)) Text(stringResource(R.string.pick_image_alt)) Spacer(Modifier.width(16.dp)) } ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) } } val previewBitmap by remember(component.bitmapData) { derivedStateOf { component.getImagePreview() } } val transformations = remember( component.bitmapData, component.compareProgress, component.pixelByPixelCompareState, component.compareType, showShareSheet ) { if (component.compareType == CompareType.PixelByPixel && showShareSheet) { listOf(component.createPixelByPixelTransformation()) } else emptyList() } CompareShareSheet( visible = showShareSheet, onVisibleChange = { showShareSheet = it }, onSaveBitmap = { imageFormat, oneTimeSaveLocationUri -> component.saveBitmap( imageFormat = imageFormat, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) showShareSheet = false }, onShare = { imageFormat -> component.shareBitmap( imageFormat = imageFormat ) showShareSheet = false }, onCopy = component::cacheCurrentImage, previewData = previewBitmap, transformations = transformations ) LoadingDialog( visible = component.isImageLoading, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareLabel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename @Composable internal fun BoxScope.CompareLabel( uri: Uri?, alignment: Alignment, enabled: Boolean, shape: Shape, modifier: Modifier = Modifier ) { AnimatedVisibility( visible = enabled && uri != null, modifier = modifier.align(alignment) ) { Text( text = uri?.let { rememberFilename(it) } ?: stringResource(R.string.filename), modifier = Modifier .background( color = MaterialTheme.colorScheme.scrim.copy(0.4f), shape = shape ) .padding(horizontal = 8.dp, vertical = 2.dp), color = Color.White, fontSize = 13.sp ) } } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Highlight import androidx.compose.material.icons.rounded.Pix import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSlider import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.compare.presentation.components.model.CompareData import net.engawapg.lib.zoomable.ZoomableDefaults.defaultZoomOnDoubleTap import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable internal fun CompareScreenContent( bitmapData: CompareData?, compareType: CompareType, onCompareTypeSelected: (CompareType) -> Unit, isPortrait: Boolean, compareProgress: Float, onCompareProgressChange: (Float) -> Unit, pixelByPixelCompareState: PixelByPixelCompareState, onPixelByPixelCompareStateChange: (PixelByPixelCompareState) -> Unit, imagePicker: ImagePicker, isLabelsEnabled: Boolean, createPixelByPixelTransformation: () -> Transformation, contentPadding: PaddingValues ) { AnimatedContent( targetState = bitmapData == null, modifier = Modifier .fillMaxSize() .padding(contentPadding) ) { noData -> bitmapData.takeIf { !noData }?.let { bitmapPair -> var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } val zoomEnabled = compareType != CompareType.SideBySide val zoomState = rememberZoomState(30f, key = compareType) val zoomModifier = Modifier .clipToBounds() .zoomable( zoomState = zoomState, onDoubleTap = { if (zoomEnabled) { zoomState.defaultZoomOnDoubleTap(it) } }, enableOneFingerZoom = zoomEnabled, zoomEnabled = zoomEnabled ) val tuneButton: @Composable BoxScope.() -> Unit = { BoxAnimatedVisibility( visible = compareType == CompareType.PixelByPixel, modifier = Modifier.align(Alignment.BottomEnd) ) { var openTuneMenu by rememberSaveable { mutableStateOf(false) } EnhancedIconButton( onClick = { openTuneMenu = true }, contentColor = MaterialTheme.colorScheme.onPrimary, containerColor = MaterialTheme.colorScheme.primary.copy(0.85f), modifier = Modifier.padding(8.dp) ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = null ) } EnhancedModalBottomSheet( visible = openTuneMenu, onDismiss = { openTuneMenu = it }, title = { TitleItem( icon = Icons.Rounded.Tune, text = stringResource(compareType.title) ) }, confirmButton = { EnhancedButton( onClick = { openTuneMenu = false } ) { Text(stringResource(R.string.close)) } } ) { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp) ) { ColorRowSelector( value = pixelByPixelCompareState.highlightColor, onValueChange = { onPixelByPixelCompareStateChange( pixelByPixelCompareState.copy( highlightColor = it ) ) }, allowAlpha = false, modifier = Modifier.container( shape = ShapeDefaults.top ), title = stringResource(R.string.highlight_color), icon = Icons.Rounded.Highlight ) Spacer(Modifier.height(4.dp)) DataSelector( value = pixelByPixelCompareState.comparisonType, onValueChange = { onPixelByPixelCompareStateChange( pixelByPixelCompareState.copy( comparisonType = it ) ) }, entries = ComparisonType.entries, title = stringResource(R.string.pixel_comparison_type), titleIcon = Icons.Rounded.Pix, spanCount = 1, shape = ShapeDefaults.bottom, itemContentText = { it.name }, containerColor = Color.Unspecified ) } } } } if (isPortrait) { Column { Box( modifier = Modifier .weight(1f) .fillMaxWidth(), contentAlignment = Alignment.Center, ) { Box( modifier = Modifier .fillMaxSize() .then(zoomModifier), contentAlignment = Alignment.Center, ) { CompareScreenContentImpl( compareType = compareType, bitmapPair = bitmapPair, compareProgress = compareProgress, onCompareProgressChange = onCompareProgressChange, isPortrait = true, isLabelsEnabled = isLabelsEnabled, pixelByPixelCompareState = pixelByPixelCompareState, createPixelByPixelTransformation = createPixelByPixelTransformation ) } tuneButton() } val showButtonsAtTheTop by remember(compareType) { derivedStateOf { compareType != CompareType.Tap && compareType != CompareType.SideBySide } } AnimatedVisibility( visible = showButtonsAtTheTop ) { Row( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke( top = true ) .container( color = MaterialTheme.colorScheme.surfaceContainer, shape = RectangleShape, borderColor = Color.Transparent ) .padding(top = 4.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { CompareSelectionButtons( value = compareType, onValueChange = onCompareTypeSelected, isPortrait = true ) } } BottomAppBar( modifier = Modifier .drawHorizontalStroke( top = true, enabled = !showButtonsAtTheTop ), floatingActionButton = { EnhancedFloatingActionButton( onClick = imagePicker::pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, ) { Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_image_alt) ) } }, actions = { AnimatedContent( targetState = !showButtonsAtTheTop ) { showButtons -> if (showButtons) { Row( modifier = Modifier .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically ) { CompareSelectionButtons( value = compareType, onValueChange = onCompareTypeSelected, isPortrait = true ) } } else { EnhancedSlider( modifier = Modifier .padding(horizontal = 16.dp) .weight(100f, true) .offset(y = (-2).dp), value = compareProgress, onValueChange = onCompareProgressChange, valueRange = 0f..100f ) } } } ) } } else { Row { val direction = LocalLayoutDirection.current Box( modifier = Modifier.weight(0.8f), contentAlignment = Alignment.Center ) { Box( modifier = Modifier .fillMaxSize() .then(zoomModifier) .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ), contentAlignment = Alignment.Center ) { CompareScreenContentImpl( compareType = compareType, bitmapPair = bitmapPair, compareProgress = compareProgress, onCompareProgressChange = onCompareProgressChange, isPortrait = false, isLabelsEnabled = isLabelsEnabled, pixelByPixelCompareState = pixelByPixelCompareState, createPixelByPixelTransformation = createPixelByPixelTransformation ) } tuneButton() } val showButtonsAtTheStart by remember(compareType) { derivedStateOf { compareType != CompareType.Tap && compareType != CompareType.SideBySide } } AnimatedVisibility( visible = showButtonsAtTheStart ) { Row { LocalSettingsState.current.borderWidth.takeIf { it > 0.dp }?.let { VerticalDivider(thickness = it) } Column( modifier = Modifier .fillMaxHeight() .container( shape = RectangleShape, borderColor = Color.Transparent ) .padding(start = 4.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { CompareSelectionButtons( value = compareType, onValueChange = onCompareTypeSelected, isPortrait = false, modifier = Modifier.padding(start = 8.dp) ) } } } Column( Modifier .container( shape = RectangleShape, borderColor = if (showButtonsAtTheStart) Color.Transparent else null ) .padding(horizontal = 20.dp) .fillMaxHeight() .navigationBarsPadding() .padding( end = WindowInsets.displayCutout .asPaddingValues() .calculateEndPadding(direction) ), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { AnimatedContent( targetState = !showButtonsAtTheStart ) { showButtons -> if (showButtons) { Column( horizontalAlignment = Alignment.CenterHorizontally ) { CompareSelectionButtons( value = compareType, onValueChange = onCompareTypeSelected, isPortrait = false, modifier = Modifier.padding(16.dp) ) } } else { val modifier = Modifier .padding(16.dp) .graphicsLayer { rotationZ = 270f transformOrigin = TransformOrigin(0f, 0f) } .layout { measurable, constraints -> val placeable = measurable.measure( Constraints( minWidth = constraints.minHeight, maxWidth = constraints.maxHeight, minHeight = constraints.minWidth, maxHeight = constraints.maxHeight, ) ) layout(placeable.height, placeable.width) { placeable.place(-placeable.width, 0) } } .width(LocalScreenSize.current.height / 2f) EnhancedSlider( modifier = modifier, value = compareProgress, onValueChange = onCompareProgressChange, valueRange = 0f..100f ) } } EnhancedFloatingActionButton( onClick = imagePicker::pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, ) { Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_image_alt) ) } } } } OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) } ?: Column( modifier = Modifier .fillMaxWidth() .enhancedVerticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { ImageNotPickedWidget( onPickImage = imagePicker::pickImage, modifier = Modifier .padding(bottom = 88.dp, top = 20.dp, start = 20.dp, end = 20.dp) .navigationBarsPadding(), text = stringResource(R.string.pick_two_images) ) } } } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenContentImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.CornerSides import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.only import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter.BeforeAfterLayout import com.t8rin.imagetoolbox.feature.compare.presentation.components.model.CompareData import com.t8rin.imagetoolbox.feature.compare.presentation.components.model.ifNotEmpty import kotlinx.coroutines.delay import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable internal fun CompareScreenContentImpl( compareType: CompareType, bitmapPair: CompareData, pixelByPixelCompareState: PixelByPixelCompareState, compareProgress: Float, onCompareProgressChange: (Float) -> Unit, isPortrait: Boolean, isLabelsEnabled: Boolean, createPixelByPixelTransformation: () -> Transformation ) { val modifier = Modifier .padding(16.dp) .container(ShapeDefaults.default) .padding(4.dp) .clip(ShapeDefaults.small) .transparencyChecker() AnimatedContent(targetState = compareType) { type -> when (type) { CompareType.Slide -> { AnimatedContent(targetState = bitmapPair) { data -> data.ifNotEmpty { beforeData, afterData -> val before = remember(data) { beforeData.image.asImageBitmap() } val after = remember(data) { afterData.image.asImageBitmap() } BeforeAfterLayout( modifier = modifier, progress = animateFloatAsState(targetValue = compareProgress).value, onProgressChange = onCompareProgressChange, beforeContent = { Picture( model = before, modifier = Modifier.aspectRatio(before.safeAspectRatio) ) }, afterContent = { Picture( model = after, modifier = Modifier.aspectRatio(after.safeAspectRatio) ) }, beforeLabel = { Box( modifier = Modifier.matchParentSize() ) { CompareLabel( uri = beforeData.uri, alignment = Alignment.TopStart, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.BottomEnd ) ) } }, afterLabel = { Box( modifier = Modifier.matchParentSize() ) { CompareLabel( uri = afterData.uri, alignment = Alignment.BottomEnd, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.TopStart ) ) } } ) } } } CompareType.SideBySide -> { val first = bitmapPair.first?.image val second = bitmapPair.second?.image val zoomState = rememberZoomState(30f) val zoomModifier = Modifier .clipToBounds() .zoomable( zoomState = zoomState ) Box(modifier) { if (isPortrait) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { if (first != null) { Picture( model = first, contentDescription = null, modifier = Modifier .fillMaxWidth() .weight(1f) .then(zoomModifier) ) HorizontalDivider() } if (second != null) { Picture( model = second, contentDescription = null, modifier = Modifier .fillMaxWidth() .weight(1f) .then(zoomModifier) ) } } CompareLabel( uri = bitmapPair.first?.uri, alignment = Alignment.TopStart, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.BottomEnd ) ) CompareLabel( uri = bitmapPair.second?.uri, alignment = Alignment.BottomStart, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.TopEnd ) ) } else { Row( modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically ) { if (first != null) { Picture( model = first, contentDescription = null, modifier = Modifier .fillMaxHeight() .weight(1f) .then(zoomModifier) ) VerticalDivider() } if (second != null) { Picture( model = second, contentDescription = null, modifier = Modifier .fillMaxHeight() .weight(1f) .then(zoomModifier) ) } } CompareLabel( uri = bitmapPair.first?.uri, alignment = Alignment.TopStart, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.BottomEnd ) ) CompareLabel( uri = bitmapPair.second?.uri, alignment = Alignment.TopEnd, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.BottomStart ) ) } } } CompareType.Tap -> { var showSecondImage by rememberSaveable { mutableStateOf(false) } Box( modifier = modifier.tappable { showSecondImage = !showSecondImage } ) { val first = bitmapPair.first?.image val second = bitmapPair.second?.image if (!showSecondImage && first != null) { Picture( model = first, contentDescription = null, contentScale = ContentScale.Inside ) } if (showSecondImage && second != null) { Picture( model = second, contentDescription = null, contentScale = ContentScale.Inside ) } Box( modifier = Modifier.matchParentSize() ) { CompareLabel( uri = if (showSecondImage) bitmapPair.second?.uri else bitmapPair.first?.uri, alignment = if (showSecondImage) Alignment.BottomEnd else Alignment.TopStart, enabled = isLabelsEnabled, shape = if (showSecondImage) { ShapeDefaults.default.only( CornerSides.TopStart ) } else { ShapeDefaults.default.only( CornerSides.BottomEnd ) } ) } } } CompareType.Transparency -> { Box( modifier = modifier ) { val first = bitmapPair.first?.image val second = bitmapPair.second?.image if (first != null) { Picture( model = first, contentDescription = null, contentScale = ContentScale.Inside ) } if (second != null) { Picture( model = second, contentDescription = null, contentScale = ContentScale.Inside, modifier = Modifier.alpha(compareProgress / 100f) ) } Box( modifier = Modifier.matchParentSize() ) { CompareLabel( uri = bitmapPair.first?.uri, alignment = Alignment.TopStart, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.BottomEnd ) ) } Box( modifier = Modifier.matchParentSize() ) { CompareLabel( uri = bitmapPair.second?.uri, modifier = Modifier.alpha(compareProgress / 100f), alignment = Alignment.BottomEnd, enabled = isLabelsEnabled, shape = ShapeDefaults.default.only( CornerSides.TopStart ) ) } } } CompareType.PixelByPixel -> { var isLoading by remember { mutableStateOf(false) } val first = bitmapPair.first?.image val second = bitmapPair.second?.image Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Box( modifier = modifier ) { if (first != null) { var transformations: List by remember { mutableStateOf(emptyList()) } LaunchedEffect( first, second, compareProgress, pixelByPixelCompareState ) { delay(300) transformations = listOf( createPixelByPixelTransformation() ) } Picture( model = first, transformations = transformations, onSuccess = { isLoading = false }, onLoading = { isLoading = true }, contentDescription = null, contentScale = ContentScale.Inside ) } } AnimatedVisibility( visible = isLoading && first != null, enter = fadeIn(), exit = fadeOut() ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } } } } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareScreenTopAppBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.Label import androidx.compose.material.icons.automirrored.rounded.RotateLeft import androidx.compose.material.icons.automirrored.rounded.RotateRight import androidx.compose.material.icons.rounded.SwapHoriz import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable fun CompareScreenTopAppBar( imageNotPicked: Boolean, scrollBehavior: TopAppBarScrollBehavior, onNavigationIconClick: () -> Unit, onShareButtonClick: () -> Unit, onSwapImagesClick: () -> Unit, onRotateImagesClick: () -> Unit, isShareButtonVisible: Boolean, isImagesRotated: Boolean, titleWhenBitmapsPicked: String, onToggleLabelsEnabled: (Boolean) -> Unit, isLabelsEnabled: Boolean, isLabelsButtonVisible: Boolean ) { if (imageNotPicked) { EnhancedTopAppBar( type = EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior, navigationIcon = { EnhancedIconButton( onClick = onNavigationIconClick ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, title = { Text( text = stringResource(R.string.compare), modifier = Modifier.marquee() ) }, actions = { TopAppBarEmoji() } ) } else { EnhancedTopAppBar( navigationIcon = { EnhancedIconButton( onClick = onNavigationIconClick ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, actions = { AnimatedVisibility(visible = isShareButtonVisible) { ShareButton(onShare = onShareButtonClick) } EnhancedIconButton( onClick = onSwapImagesClick ) { Icon( imageVector = Icons.Rounded.SwapHoriz, contentDescription = "Swap" ) } EnhancedIconButton( onClick = onRotateImagesClick ) { AnimatedContent(isImagesRotated) { rotated -> Icon( imageVector = if (rotated) Icons.AutoMirrored.Rounded.RotateLeft else Icons.AutoMirrored.Rounded.RotateRight, contentDescription = "Rotate" ) } } AnimatedVisibility(visible = isLabelsButtonVisible) { EnhancedIconButton( onClick = { onToggleLabelsEnabled(!isLabelsEnabled) }, containerColor = animateColorAsState( if (isLabelsEnabled) MaterialTheme.colorScheme.secondary else Color.Transparent ).value ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Label, contentDescription = "Label" ) } } }, title = { AnimatedContent( targetState = titleWhenBitmapsPicked, modifier = Modifier.marquee() ) { text -> Text(text) } } ) } } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSelectionButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedHorizontalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges @Composable fun CompareSelectionButtons( value: CompareType, onValueChange: (CompareType) -> Unit, isPortrait: Boolean, modifier: Modifier = Modifier ) { val buttonsContent = @Composable { CompareType.entries.forEach { compareType -> val selected by remember(compareType, value) { derivedStateOf { compareType == value } } val containerColor by animateColorAsState( if (selected) MaterialTheme.colorScheme.secondaryContainer else Color.Transparent ) EnhancedIconButton( containerColor = containerColor, onClick = { onValueChange(compareType) } ) { Icon( imageVector = compareType.icon, contentDescription = stringResource(compareType.title) ) } } } val scrollState = rememberScrollState() val internalModifier = modifier .container( color = MaterialTheme.colorScheme.surfaceContainerLowest, shape = ShapeDefaults.circle, resultPadding = 0.dp ) .fadingEdges( scrollableState = scrollState, isVertical = !isPortrait ) .then( if (isPortrait) Modifier.enhancedHorizontalScroll(scrollState) else Modifier.enhancedVerticalScroll(scrollState) ) if (isPortrait) { Row( modifier = internalModifier ) { buttonsContent() } } else { Column( modifier = internalModifier ) { buttonsContent() } } } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareShareSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material.icons.rounded.IosShare import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Share import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.transform.Transformation import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults.bottom import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults.center import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults.top import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun CompareShareSheet( visible: Boolean, onVisibleChange: (Boolean) -> Unit, onSaveBitmap: (ImageFormat, String?) -> Unit, onShare: (ImageFormat) -> Unit, onCopy: (ImageFormat) -> Unit, previewData: Any?, transformations: List ) { EnhancedModalBottomSheet( sheetContent = { var imageFormat by remember { mutableStateOf(ImageFormat.Png.Lossless) } Box { Column( modifier = Modifier.enhancedVerticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { Box( Modifier .padding( bottom = 8.dp, start = 4.dp, end = 4.dp, top = 16.dp ) .height(100.dp) .width(120.dp) .container( shape = MaterialTheme.shapes.extraLarge, resultPadding = 0.dp ) ) { Picture( model = previewData, transformations = transformations, shape = RectangleShape, modifier = Modifier.fillMaxSize() ) } Spacer(Modifier.height(16.dp)) ImageFormatSelector( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), value = imageFormat, forceEnabled = true, onValueChange = { imageFormat = it } ) Spacer(Modifier.height(8.dp)) var showFolderSelectionDialog by rememberSaveable(visible) { mutableStateOf(false) } PreferenceItem( title = stringResource(id = R.string.save), onClick = { onSaveBitmap(imageFormat, null) }, onLongClick = { showFolderSelectionDialog = true }, shape = top, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), containerColor = MaterialTheme.colorScheme.primaryContainer, endIcon = Icons.Rounded.Save ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = { onSaveBitmap(imageFormat, it) }, formatForFilenameSelection = imageFormat ) Spacer(Modifier.height(4.dp)) PreferenceItem( title = stringResource(id = R.string.copy), modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), shape = center, onClick = { onCopy(imageFormat) }, containerColor = MaterialTheme.colorScheme.secondaryContainer, endIcon = Icons.Rounded.ContentCopy ) Spacer(Modifier.height(4.dp)) PreferenceItem( title = stringResource(id = R.string.share), modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), shape = bottom, onClick = { onShare(imageFormat) }, containerColor = MaterialTheme.colorScheme.tertiaryContainer, endIcon = Icons.Rounded.Share ) Spacer(Modifier.height(16.dp)) } } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onVisibleChange(false) } ) { AutoSizeText(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(id = R.string.share), icon = Icons.Rounded.IosShare ) }, onDismiss = onVisibleChange, visible = visible ) } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import android.graphics.Bitmap import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Compare import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalSheetDragHandle import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter.BeforeAfterLayout import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable fun CompareSheet( data: Pair?, visible: Boolean, onDismiss: () -> Unit ) { var progress by rememberSaveable(visible) { mutableFloatStateOf(50f) } if (data != null) { EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier.navigationBarsPadding() ) { Box( modifier = Modifier .fillMaxWidth() .weight(1f) .padding(horizontal = 16.dp) .container( shape = ShapeDefaults.extraSmall, color = MaterialTheme.colorScheme .outlineVariant() .copy(alpha = 0.1f), resultPadding = 0.dp ) .transparencyChecker() .clipToBounds() .zoomable(rememberZoomState(maxScale = 15f)), contentAlignment = Alignment.Center ) { data.let { (b, a) -> val before = remember(data) { b?.asImageBitmap() } val after = remember(data) { a?.asImageBitmap() } if (before != null && after != null) { BeforeAfterLayout( modifier = Modifier.clip(ShapeDefaults.extraSmall), progress = animateFloatAsState(targetValue = progress).value, onProgressChange = { progress = it }, beforeContent = { Picture( model = before, modifier = Modifier.aspectRatio(before.safeAspectRatio) ) }, afterContent = { Picture( model = after, modifier = Modifier.aspectRatio(after.safeAspectRatio) ) }, beforeLabel = { }, afterLabel = { } ) } } } Row( modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { TitleItem( text = stringResource(R.string.compare), icon = Icons.Outlined.Compare ) Spacer(Modifier.weight(1f)) EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss, modifier = Modifier.padding(horizontal = 12.dp) ) { AutoSizeText(stringResource(R.string.close)) } } } }, visible = visible, onDismiss = { if (!it) onDismiss() }, dragHandle = { EnhancedModalSheetDragHandle( color = Color.Transparent, drawStroke = false, heightWhenDisabled = 20.dp ) } ) } } @Composable fun CompareSheet( beforeContent: @Composable () -> Unit, afterContent: @Composable () -> Unit, visible: Boolean, onDismiss: () -> Unit, shape: Shape = ShapeDefaults.extraSmall ) { var progress by rememberSaveable(visible) { mutableFloatStateOf(50f) } EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier.navigationBarsPadding() ) { Box( modifier = Modifier .fillMaxWidth() .weight(1f) .padding(horizontal = 16.dp) .container( shape = ShapeDefaults.extraSmall, color = MaterialTheme.colorScheme .outlineVariant() .copy(alpha = 0.1f), resultPadding = 0.dp ) .transparencyChecker() .clipToBounds() .zoomable(rememberZoomState(maxScale = 15f)), contentAlignment = Alignment.Center ) { BeforeAfterLayout( modifier = Modifier.clip(shape), progress = animateFloatAsState(targetValue = progress).value, onProgressChange = { progress = it }, beforeContent = beforeContent, afterContent = afterContent, beforeLabel = { }, afterLabel = { } ) } Row( modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { TitleItem( text = stringResource(R.string.compare), icon = Icons.Outlined.Compare ) Spacer(Modifier.weight(1f)) EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss, modifier = Modifier.padding(horizontal = 12.dp) ) { AutoSizeText(stringResource(R.string.close)) } } } }, visible = visible, onDismiss = { if (!it) onDismiss() }, dragHandle = { EnhancedModalSheetDragHandle( color = Color.Transparent, drawStroke = false, heightWhenDisabled = 20.dp ) } ) } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/CompareType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import androidx.annotation.StringRes import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.TouchApp import androidx.compose.material.icons.rounded.ZoomIn import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Compare import com.t8rin.imagetoolbox.core.resources.icons.Cube import com.t8rin.imagetoolbox.core.resources.icons.Transparency sealed class CompareType( val icon: ImageVector, @StringRes val title: Int ) { data object Slide : CompareType( icon = Icons.Outlined.Compare, title = R.string.slide ) data object SideBySide : CompareType( icon = Icons.Rounded.ZoomIn, title = R.string.side_by_side ) data object Tap : CompareType( icon = Icons.Rounded.TouchApp, title = R.string.toggle_tap ) data object Transparency : CompareType( icon = Icons.Filled.Transparency, title = R.string.transparency ) data object PixelByPixel : CompareType( icon = Icons.Outlined.Cube, title = R.string.pixel_by_pixel ) companion object { val entries by lazy { listOf(Slide, SideBySide, Tap, Transparency, PixelByPixel) } } } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/PixelByPixelCompareState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components import androidx.compose.ui.graphics.Color data class PixelByPixelCompareState( val highlightColor: Color, val comparisonType: ComparisonType ) { companion object { val Default by lazy { PixelByPixelCompareState( highlightColor = Color.Red, comparisonType = ComparisonType.AE ) } } } enum class ComparisonType { SSIM, AE, MAE, NCC, PSNR, RMSE } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayout.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter import androidx.annotation.FloatRange import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.DpSize /** * A composable that lays out and draws a given [beforeContent] and [afterContent] * based on [contentOrder]. This overload uses [DefaultOverlay] to draw vertical slider and thumb. * * @param enableProgressWithTouch flag to enable drag and change progress with touch * @param contentOrder order of composables to be drawn * @param overlayStyle styling values for [DefaultOverlay] to set divier color, thumb shape, size, * elevation and other properties * @param beforeContent content to be drawn as before Composable * @param afterContent content to be drawn as after Composable * @param beforeLabel label for [beforeContent]. It's [BeforeLabel] by default * @param afterLabel label for [afterContent]. It's [AfterLabel] by default * */ @Composable internal fun BeforeAfterLayout( modifier: Modifier = Modifier, enableProgressWithTouch: Boolean = true, contentOrder: ContentOrder = ContentOrder.BeforeAfter, overlayStyle: OverlayStyle = OverlayStyle(), beforeContent: @Composable () -> Unit, afterContent: @Composable () -> Unit, beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) }, afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) }, ) { var progress by remember { mutableFloatStateOf(50f) } Layout( modifier = modifier, beforeContent = beforeContent, afterContent = afterContent, beforeLabel = beforeLabel, afterLabel = afterLabel, progress = progress, onProgressChange = { progress = it }, contentOrder = contentOrder, enableProgressWithTouch = enableProgressWithTouch, overlay = { dpSize: DpSize, offset: Offset -> DefaultOverlay( width = dpSize.width, height = dpSize.height, position = offset, overlayStyle = overlayStyle ) } ) } /** * A composable that lays out and draws a given [beforeContent] and [afterContent] * based on [contentOrder]. This overload uses [DefaultOverlay] to draw vertical slider and thumb * and has [progress] and [onProgressChange] which makes it eligible to animate by changing * [progress] value. * * @param enableProgressWithTouch flag to enable drag and change progress with touch * @param contentOrder order of composables to be drawn * @param progress current position or progress of before/after * @param onProgressChange current position or progress of before/after * @param overlayStyle styling values for [DefaultOverlay] to set divier color, thumb shape, size, * elevation and other properties * @param beforeContent content to be drawn as before Composable * @param afterContent content to be drawn as after Composable * @param beforeLabel label for [beforeContent]. It's [BeforeLabel] by default * @param afterLabel label for [afterContent]. It's [AfterLabel] by default */ @Composable internal fun BeforeAfterLayout( modifier: Modifier = Modifier, enableProgressWithTouch: Boolean = true, contentOrder: ContentOrder = ContentOrder.BeforeAfter, @FloatRange(from = 0.0, to = 100.0) progress: Float = 50f, onProgressChange: ((Float) -> Unit)? = null, overlayStyle: OverlayStyle = OverlayStyle(), beforeContent: @Composable () -> Unit, afterContent: @Composable () -> Unit, beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) }, afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) }, ) { Layout( modifier = modifier, beforeContent = beforeContent, afterContent = afterContent, beforeLabel = beforeLabel, afterLabel = afterLabel, progress = progress, onProgressChange = onProgressChange, contentOrder = contentOrder, enableProgressWithTouch = enableProgressWithTouch, overlay = { dpSize: DpSize, offset: Offset -> DefaultOverlay( width = dpSize.width, height = dpSize.height, position = offset, overlayStyle = overlayStyle ) } ) } /** * A composable that lays out and draws a given [beforeContent] and [afterContent] * based on [contentOrder]. * * @param enableProgressWithTouch flag to enable drag and change progress with touch * @param contentOrder order of composables to be drawn * It's between [0f-100f] to set thumb's vertical position in layout * @param beforeContent content to be drawn as before Composable * @param afterContent content to be drawn as after Composable * @param beforeLabel label for [beforeContent]. It's [BeforeLabel] by default * @param afterLabel label for [afterContent]. It's [AfterLabel] by default * @param overlay Composable for drawing overlay over this Composable. It returns dimensions * of ancestor and touch position */ @Composable internal fun BeforeAfterLayout( modifier: Modifier = Modifier, enableProgressWithTouch: Boolean = true, contentOrder: ContentOrder = ContentOrder.BeforeAfter, beforeContent: @Composable () -> Unit, afterContent: @Composable () -> Unit, beforeLabel: @Composable BoxScope.() -> Unit = { BeforeLabel(contentOrder = contentOrder) }, afterLabel: @Composable BoxScope.() -> Unit = { AfterLabel(contentOrder = contentOrder) }, overlay: @Composable ((DpSize, Offset) -> Unit)? ) { var progress by remember { mutableFloatStateOf(50f) } Layout( modifier = modifier, beforeContent = beforeContent, afterContent = afterContent, beforeLabel = beforeLabel, afterLabel = afterLabel, progress = progress, onProgressChange = { progress = it }, contentOrder = contentOrder, enableProgressWithTouch = enableProgressWithTouch, overlay = overlay ) } /** * A composable that lays out and draws a given [beforeContent] and [afterContent] * based on [contentOrder]. * * @param enableProgressWithTouch flag to enable drag and change progress with touch * @param contentOrder order of composables to be drawn * @param progress current position or progress of before/after * @param onProgressChange current position or progress of before/after * It's between [0f-100f] to set thumb's vertical position in layout * @param beforeContent content to be drawn as before Composable * @param afterContent content to be drawn as after Composable * @param beforeLabel label for [beforeContent]. It's [BeforeLabel] by default * @param afterLabel label for [afterContent]. It's [AfterLabel] by default * @param overlay Composable for drawing overlay over this Composable. It returns dimensions * of ancestor and touch position */ @Composable internal fun BeforeAfterLayout( modifier: Modifier = Modifier, @FloatRange(from = 0.0, to = 100.0) progress: Float = 50f, onProgressChange: ((Float) -> Unit)? = null, enableProgressWithTouch: Boolean = true, contentOrder: ContentOrder = ContentOrder.BeforeAfter, beforeContent: @Composable () -> Unit, afterContent: @Composable () -> Unit, beforeLabel: @Composable (BoxScope.() -> Unit)? = { BeforeLabel(contentOrder = contentOrder) }, afterLabel: @Composable (BoxScope.() -> Unit)? = { AfterLabel(contentOrder = contentOrder) }, overlay: @Composable ((DpSize, Offset) -> Unit)? ) { Layout( modifier = modifier, beforeContent = beforeContent, afterContent = afterContent, beforeLabel = beforeLabel, afterLabel = afterLabel, progress = progress, onProgressChange = onProgressChange, contentOrder = contentOrder, enableProgressWithTouch = enableProgressWithTouch, overlay = overlay ) } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/BeforeAfterLayoutImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter import androidx.annotation.FloatRange import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.GenericShape import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.LayoutDirection import com.t8rin.gesture.detectMotionEvents @Composable internal fun Layout( modifier: Modifier = Modifier, @FloatRange(from = 0.0, to = 100.0) progress: Float = 50f, onProgressChange: ((Float) -> Unit)? = null, enableProgressWithTouch: Boolean = true, contentOrder: ContentOrder = ContentOrder.BeforeAfter, beforeContent: @Composable () -> Unit, afterContent: @Composable () -> Unit, beforeLabel: @Composable (BoxScope.() -> Unit)?, afterLabel: @Composable (BoxScope.() -> Unit)?, overlay: @Composable ((DpSize, Offset) -> Unit)? ) { DimensionSubcomposeLayout( modifier = modifier, placeMainContent = false, mainContent = { beforeContent() }, dependentContent = { contentSize: Size -> val boxWidth = contentSize.width val boxHeight = contentSize.height val boxWidthInDp: Dp val boxHeightInDp: Dp with(LocalDensity.current) { boxWidthInDp = boxWidth.toDp() boxHeightInDp = boxHeight.toDp() } // Sales and interpolates from offset from dragging to user value in valueRange fun scaleToUserValue(offset: Float) = scale(0f, boxWidth, offset, 0f, 100f) // Scales user value using valueRange to position on x axis on screen fun scaleToOffset(userValue: Float) = scale(0f, 100f, userValue, 0f, boxWidth) var rawOffset by remember { mutableStateOf( Offset( x = scaleToOffset(progress), y = boxHeight / 2f, ) ) } rawOffset = rawOffset.copy(x = scaleToOffset(progress)) var isHandleTouched by remember { mutableStateOf(false) } val touchModifier = Modifier.pointerInput(Unit) { detectMotionEvents( onDown = { val position = it.position val xPos = position.x isHandleTouched = ((rawOffset.x - xPos) * (rawOffset.x - xPos) < 5000) }, onMove = { if (isHandleTouched) { rawOffset = it.position onProgressChange?.invoke( scaleToUserValue(rawOffset.x) ) it.consume() } }, onUp = { isHandleTouched = false } ) } val handlePosition = rawOffset.x val shapeBefore by remember(handlePosition) { mutableStateOf( GenericShape { size: Size, _: LayoutDirection -> moveTo(0f, 0f) lineTo(handlePosition, 0f) lineTo(handlePosition, size.height) lineTo(0f, size.height) close() } ) } val shapeAfter by remember(handlePosition) { mutableStateOf( GenericShape { size: Size, _: LayoutDirection -> moveTo(handlePosition, 0f) lineTo(size.width, 0f) lineTo(size.width, size.height) lineTo(handlePosition, size.height) close() } ) } val parentModifier = Modifier .size(boxWidthInDp, boxHeightInDp) .clipToBounds() .then(if (enableProgressWithTouch) touchModifier else Modifier) val beforeModifier = Modifier .fillMaxSize() .graphicsLayer { this.clip = true this.shape = shapeBefore } val afterModifier = Modifier .fillMaxSize() .graphicsLayer { this.clip = true this.shape = shapeAfter } LayoutImpl( modifier = parentModifier, beforeModifier = beforeModifier, afterModifier = afterModifier, graphicsModifier = Modifier, beforeContent = beforeContent, afterContent = afterContent, beforeLabel = beforeLabel, afterLabel = afterLabel, overlay = overlay, contentOrder = contentOrder, boxWidthInDp = boxWidthInDp, boxHeightInDp = boxHeightInDp, rawOffset = rawOffset ) } ) } @Composable private fun LayoutImpl( modifier: Modifier, beforeModifier: Modifier, afterModifier: Modifier, graphicsModifier: Modifier, beforeContent: @Composable () -> Unit, afterContent: @Composable () -> Unit, beforeLabel: @Composable (BoxScope.() -> Unit)? = null, afterLabel: @Composable (BoxScope.() -> Unit)? = null, overlay: @Composable ((DpSize, Offset) -> Unit)? = null, contentOrder: ContentOrder, boxWidthInDp: Dp, boxHeightInDp: Dp, rawOffset: Offset, ) { Box(modifier = modifier) { // BEFORE val before = @Composable { Box(if (contentOrder == ContentOrder.BeforeAfter) beforeModifier else afterModifier) { Box( modifier = Modifier.then(graphicsModifier) ) { beforeContent() } beforeLabel?.invoke(this) } } // AFTER val after = @Composable { Box(if (contentOrder == ContentOrder.BeforeAfter) afterModifier else beforeModifier) { Box( modifier = Modifier.then(graphicsModifier) ) { afterContent() } afterLabel?.invoke(this) } } if (contentOrder == ContentOrder.BeforeAfter) { before() after() } else { after() before() } } overlay?.invoke( DpSize(boxWidthInDp, boxHeightInDp), rawOffset ) } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/ContentOrder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter /** * Enum class to determine in which order before and after content should be drawn */ internal enum class ContentOrder { BeforeAfter, AfterBefore } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DefaultOverlay.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp /** * Default overlay for and [BeforeAfterLayout] that draws line and * thumb with properties provided. * * @param width of the or [BeforeAfterLayout]. You should get width from * scope of these Composables and pass to calculate bounds correctly * @param height of the or [BeforeAfterLayout]. You should get height from * scope of these Composables and pass to calculate bounds correctly * @param position current position or progress of before/after */ @Composable internal fun DefaultOverlay( width: Dp, height: Dp, position: Offset, overlayStyle: OverlayStyle ) { CompositionLocalProvider( LocalLayoutDirection provides LayoutDirection.Ltr ) { val verticalThumbMove = overlayStyle.verticalThumbMove val dividerWidth = overlayStyle.dividerWidth val dividerColor = overlayStyle.dividerColor.takeOrElse { MaterialTheme.colorScheme.primary } val secondDividerColor = overlayStyle.secondDividerColor.takeOrElse { MaterialTheme.colorScheme.primaryContainer } val thumbBackgroundColor = overlayStyle.thumbBackgroundColor.takeOrElse { MaterialTheme.colorScheme.primary } val thumbTintColor = overlayStyle.thumbTintColor.takeOrElse { MaterialTheme.colorScheme.primaryContainer } val thumbShape = overlayStyle.thumbShape val thumbElevation = overlayStyle.thumbElevation val thumbResource = overlayStyle.thumbResource val thumbHeight = overlayStyle.thumbHeight val thumbWidth = overlayStyle.thumbWidth val thumbPositionPercent = overlayStyle.thumbPositionPercent var thumbPosX = position.x var thumbPosY = position.y val linePosition: Float val density = LocalDensity.current with(density) { val thumbRadius = (maxOf(thumbHeight, thumbWidth) / 2).toPx() val imageWidthInPx = width.toPx() val imageHeightInPx = height.toPx() val horizontalOffset = imageWidthInPx / 2 val verticalOffset = imageHeightInPx / 2 linePosition = thumbPosX.coerceIn(0f, imageWidthInPx) thumbPosX = linePosition - horizontalOffset thumbPosY = if (verticalThumbMove) { (thumbPosY - verticalOffset) .coerceIn( -verticalOffset + thumbRadius, verticalOffset - thumbRadius ) } else { ((imageHeightInPx * thumbPositionPercent / 100f - thumbRadius) - verticalOffset) } } Box( modifier = Modifier.size(width, height), contentAlignment = Alignment.Center ) { Canvas(modifier = Modifier.fillMaxSize()) { drawLine( color = dividerColor, strokeWidth = dividerWidth.toPx(), start = Offset(linePosition, 0f), end = Offset(linePosition, size.height) ) drawLine( color = secondDividerColor, strokeWidth = dividerWidth.toPx(), start = Offset(linePosition, 0f), end = Offset(linePosition, size.height), pathEffect = PathEffect.dashPathEffect( intervals = floatArrayOf( overlayStyle.dash.toPx(), overlayStyle.gap.toPx() ) ) ) } Icon( imageVector = thumbResource, contentDescription = "compare thumb", tint = thumbTintColor, modifier = Modifier .offset { IntOffset(thumbPosX.toInt(), thumbPosY.toInt()) } .shadow(thumbElevation, thumbShape) .background(thumbBackgroundColor) .size( width = thumbWidth, height = thumbHeight, ) .rotate(overlayStyle.iconRotation) .scale(overlayStyle.iconScale) ) } } } /** * Values for styling [DefaultOverlay] * @param verticalThumbMove when true thumb can move vertically based on user touch * @param dividerColor color if divider line * @param dividerWidth width if divider line * @param thumbBackgroundColor background color of thumb [Icon] * @param thumbTintColor tint color of thumb [Icon] * @param thumbShape shape of thumb [Icon] * @param thumbElevation elevation of thumb [Icon] * @param thumbResource drawable resource that should be used with thumb * thumbSize size of the thumb in dp * @param thumbPositionPercent vertical position of thumb if [verticalThumbMove] is false * It's between [0f-100f] to set thumb's vertical position in layout */ @Immutable internal class OverlayStyle( val dividerColor: Color = Color.Unspecified, val secondDividerColor: Color = Color.Unspecified, val dash: Dp = 24.dp, val gap: Dp = 24.dp, val dividerWidth: Dp = 2.dp, val verticalThumbMove: Boolean = true, val thumbBackgroundColor: Color = Color.Unspecified, val thumbTintColor: Color = Color.Unspecified, val thumbShape: Shape = CircleShape, val thumbElevation: Dp = 2.dp, val thumbResource: ImageVector = Icons.Rounded.DragHandle, val iconRotation: Float = 90f, val iconScale: Float = 1.2f, val thumbHeight: Dp = 36.dp, val thumbWidth: Dp = 18.dp, val thumbPositionPercent: Float = 85f, ) ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionSubcomposeLayout.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.SubcomposeLayout import androidx.compose.ui.layout.SubcomposeMeasureScope import androidx.compose.ui.unit.Constraints /** * SubcomposeLayout that [SubcomposeMeasureScope.subcompose]s [mainContent] * and gets total size of [mainContent] and passes this size to [dependentContent]. * This layout passes exact size of content unlike * BoxWithConstraints which returns [Constraints] that doesn't match Composable dimensions under * some circumstances * * @param placeMainContent when set to true places main content. Set this flag to false * when dimensions of content is required for inside [mainContent]. Just measure it then pass * its dimensions to any child composable * * @param mainContent Composable is used for calculating size and pass it * to Composables that depend on it * * @param dependentContent Composable requires dimensions of [mainContent] to set its size. * One example for this is overlay over Composable that should match [mainContent] size. * */ @Composable internal fun DimensionSubcomposeLayout( modifier: Modifier = Modifier, placeMainContent: Boolean = true, mainContent: @Composable () -> Unit, dependentContent: @Composable (Size) -> Unit ) { SubcomposeLayout( modifier = modifier ) { constraints: Constraints -> // Subcompose(compose only a section) main content and get Placeable val mainPlaceables: List = subcompose(SlotsEnum.Main, mainContent) .map { it.measure(constraints) } // Get max width and height of main component var maxWidth = 0 var maxHeight = 0 mainPlaceables.forEach { placeable: Placeable -> maxWidth += placeable.width maxHeight = placeable.height } val dependentPlaceables: List = subcompose(SlotsEnum.Dependent) { dependentContent(Size(maxWidth.toFloat(), maxHeight.toFloat())) } .map { measurable: Measurable -> measurable.measure(constraints) } layout(maxWidth, maxHeight) { if (placeMainContent) { mainPlaceables.forEach { placeable: Placeable -> placeable.placeRelative(0, 0) } } dependentPlaceables.forEach { placeable: Placeable -> placeable.placeRelative(0, 0) } } } } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/DimensionUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter /** * [Linear Interpolation](https://en.wikipedia.org/wiki/Linear_interpolation) function that moves * amount from it's current position to start and amount * @param start of interval * @param end of interval * @param amount e closed unit interval [0, 1] */ internal fun lerp(start: Float, end: Float, amount: Float): Float { return (1 - amount) * start + amount * end } /** * Scale x1 from start1..end1 range to start2..end2 range */ internal fun scale(start1: Float, end1: Float, pos: Float, start2: Float, end2: Float) = lerp(start2, end2, calculateFraction(start1, end1, pos)) /** * Scale x.start, x.endInclusive from a1..b1 range to a2..b2 range */ internal fun scale( start1: Float, end1: Float, range: ClosedFloatingPointRange, start2: Float, end2: Float ) = scale(start1, end1, range.start, start2, end2)..scale( start1, end1, range.endInclusive, start2, end2 ) /** * Calculate fraction for value between a range [end] and [start] coerced into 0f-1f range */ fun calculateFraction(start: Float, end: Float, pos: Float) = (if (end - start == 0f) 0f else (pos - start) / (end - start)).coerceIn(0f, 1f) ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/Label.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter import androidx.compose.foundation.background import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp /** * Black transparent label to display before or after text */ @Composable internal fun Label( modifier: Modifier = Modifier, textColor: Color = Color.White, fontWeight: FontWeight = FontWeight.Bold, fontSize: TextUnit = 14.sp, text: String ) { Text( text = text, color = textColor, fontSize = fontSize, fontWeight = fontWeight, modifier = modifier ) } internal val labelModifier = Modifier .background(Color.Black.copy(alpha = .5f), RoundedCornerShape(50)) .padding(horizontal = 12.dp, vertical = 8.dp) @Composable internal fun BoxScope.BeforeLabel( text: String = "Before", textColor: Color = Color.White, fontWeight: FontWeight = FontWeight.Bold, fontSize: TextUnit = 14.sp, contentOrder: ContentOrder = ContentOrder.BeforeAfter ) { Label( text = text, textColor = textColor, fontWeight = fontWeight, fontSize = fontSize, modifier = Modifier .padding(8.dp) .align( if (contentOrder == ContentOrder.BeforeAfter) Alignment.TopStart else Alignment.TopEnd ) .then(labelModifier) ) } @Composable internal fun BoxScope.AfterLabel( text: String = "After", textColor: Color = Color.White, fontWeight: FontWeight = FontWeight.Bold, fontSize: TextUnit = 14.sp, contentOrder: ContentOrder = ContentOrder.BeforeAfter ) { Label( text = text, textColor = textColor, fontWeight = fontWeight, fontSize = fontSize, modifier = Modifier .padding(8.dp) .align( if (contentOrder == ContentOrder.BeforeAfter) Alignment.TopEnd else Alignment.TopStart ) .then(labelModifier) ) } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/beforeafter/SlotsEnum.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.beforeafter /** * Enum class for SubcomposeLayouts with main and dependent Composables */ internal enum class SlotsEnum { Main, Dependent } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/components/model/CompareData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.components.model import android.graphics.Bitmap import android.net.Uri data class CompareEntry( val uri: Uri, val image: Bitmap ) data class CompareData( val first: CompareEntry?, val second: CompareEntry? ) { fun swap() = CompareData( first = second, second = first ) } inline fun CompareData.ifNotEmpty( action: (CompareEntry, CompareEntry) -> R ): R? = run { if (first != null && second != null) action(first, second) else null } ================================================ FILE: feature/compare/src/main/java/com/t8rin/imagetoolbox/feature/compare/presentation/screenLogic/CompareComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.compare.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.applyCanvas import androidx.core.net.toUri import coil3.transform.Transformation import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.asDomain import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.transformation.GenericTransformation import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.createScaledBitmap import com.t8rin.imagetoolbox.core.ui.utils.helper.toCoil import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareType import com.t8rin.imagetoolbox.feature.compare.presentation.components.PixelByPixelCompareState import com.t8rin.imagetoolbox.feature.compare.presentation.components.model.CompareData import com.t8rin.imagetoolbox.feature.compare.presentation.components.model.CompareEntry import com.t8rin.imagetoolbox.feature.compare.presentation.components.model.ifNotEmpty import com.t8rin.opencv_tools.image_comparison.ImageDiffTool import com.t8rin.opencv_tools.image_comparison.model.ComparisonType import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlin.math.roundToInt class CompareComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialComparableUris: Pair?, @Assisted val onGoBack: () -> Unit, private val imageCompressor: ImageCompressor, private val imageTransformer: ImageTransformer, private val imageGetter: ImageGetter, private val fileController: FileController, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialComparableUris?.let { updateUris( uris = it, onFailure = {}, ) } } } private val _compareProgress: MutableState = mutableFloatStateOf(50f) val compareProgress by _compareProgress fun setCompareProgress(progress: Float) { _compareProgress.update { progress } } private val _bitmapData: MutableState = mutableStateOf(null) val bitmapData by _bitmapData private val _rotation: MutableState = mutableFloatStateOf(0f) val rotation by _rotation private val _compareType: MutableState = mutableStateOf(CompareType.Slide) val compareType by _compareType private val _pixelByPixelCompareState: MutableState = mutableStateOf( PixelByPixelCompareState.Default ) val pixelByPixelCompareState: PixelByPixelCompareState by _pixelByPixelCompareState fun updatePixelByPixelCompareState(state: PixelByPixelCompareState) { _pixelByPixelCompareState.update { state } } fun rotate() { val old = _rotation.value _rotation.value = _rotation.value.let { if (it == 90f) 0f else 90f } componentScope.launch { bitmapData?.ifNotEmpty { first, second -> _isImageLoading.value = true _bitmapData.value = with(imageTransformer) { CompareData( first = first.copy( image = rotate( image = rotate( image = first.image, degrees = 180f - old ), degrees = rotation ) ), second = second.copy( image = rotate( image = rotate( image = second.image, degrees = 180f - old ), degrees = rotation ) ) ) } _isImageLoading.value = false } } } fun swap() { componentScope.launch { _isImageLoading.value = true _bitmapData.value = bitmapData?.swap() _isImageLoading.value = false } } fun updateUris( uris: Pair, onFailure: () -> Unit, ) { componentScope.launch { val data = getBitmapByUri(uris.first) to getBitmapByUri(uris.second) if (data.first == null || data.second == null) onFailure() else { _bitmapData.value = CompareData( first = CompareEntry( uri = uris.first, image = data.first!! ), second = CompareEntry( uri = uris.second, image = data.second!! ) ) setCompareProgress( if (compareType == CompareType.PixelByPixel) 4f else 50f ) } } } private suspend fun getBitmapByUri( uri: Uri ): Bitmap? = imageGetter.getImage( data = uri.toString(), size = 4000 ) private var savingJob: Job? by smartJob { _isImageLoading.update { false } } fun shareBitmap( imageFormat: ImageFormat ) { savingJob = trackProgress { _isImageLoading.value = true getResultImage()?.let { shareProvider.shareImage( image = it, imageInfo = ImageInfo( imageFormat = imageFormat, width = it.width, height = it.height ), onComplete = AppToastHost::showConfetti ) } ?: AppToastHost.showConfetti() _isImageLoading.value = false } } fun saveBitmap( imageFormat: ImageFormat, oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isImageLoading.value = true getResultImage()?.let { localBitmap -> parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ), originalUri = "", sequenceNumber = null, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _isImageLoading.value = false } } } private fun Bitmap.overlay( overlay: Bitmap, percent: Float ): Bitmap = overlay.copy(overlay.safeConfig, true) .apply { setHasAlpha(true) } .applyCanvas { runCatching { val image = createScaledBitmap( width = width, height = height ) drawBitmap( Bitmap.createBitmap( image, 0, 0, (image.width * percent / 100).roundToInt(), image.height ) ) } } private fun getOverlappedImage(): Bitmap? { return bitmapData?.ifNotEmpty { before, after -> before.image.overlay( overlay = after.image, percent = compareProgress ) } } private suspend fun getResultImage(): Bitmap? = coroutineScope { when (compareType) { CompareType.PixelByPixel -> imageTransformer.transform( image = bitmapData?.first?.image ?: return@coroutineScope null, transformations = listOf(createPixelByPixelTransformation().asDomain()) ) CompareType.Slide -> getOverlappedImage() else -> null } } fun getImagePreview(): Bitmap? = when (compareType) { CompareType.PixelByPixel -> bitmapData?.first?.image CompareType.Slide -> getOverlappedImage() else -> null } fun cancelSaving() { savingJob?.cancel() savingJob = null _isImageLoading.value = false } fun setCompareType(value: CompareType) { _compareType.update { value } if (value == CompareType.PixelByPixel) { setCompareProgress(4f) } } fun cacheCurrentImage( imageFormat: ImageFormat, ) { savingJob = trackProgress { _isImageLoading.value = true getResultImage()?.let { shareProvider.cacheImage( image = it, imageInfo = ImageInfo( imageFormat = imageFormat, width = it.width, height = it.height ) ) }?.let { uri -> Clipboard.copy(uri.toUri()) } _isImageLoading.value = false } } fun createPixelByPixelTransformation(): Transformation = GenericTransformation { first -> ImageDiffTool.highlightDifferences( input = first, other = bitmapData?.second?.image ?: return@GenericTransformation first, comparisonType = ComparisonType.valueOf(pixelByPixelCompareState.comparisonType.name), highlightColor = pixelByPixelCompareState.highlightColor.toArgb(), threshold = compareProgress ) }.toCoil() @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialComparableUris: Pair?, onGoBack: () -> Unit, ): CompareComponent } } ================================================ FILE: feature/crop/.gitignore ================================================ /build ================================================ FILE: feature/crop/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.crop" dependencies { implementation(projects.lib.opencvTools) } ================================================ FILE: feature/crop/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/CropContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation import android.net.Uri import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetValue import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.cropper.model.OutlineType import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.icons.CropSmall import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveBottomScaffoldLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.MagnifierEnabledSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AspectRatioSelector import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.crop.presentation.components.CoercePointsToImageBoundsToggle import com.t8rin.imagetoolbox.feature.crop.presentation.components.CropMaskSelection import com.t8rin.imagetoolbox.feature.crop.presentation.components.CropRotationSelector import com.t8rin.imagetoolbox.feature.crop.presentation.components.CropType import com.t8rin.imagetoolbox.feature.crop.presentation.components.Cropper import com.t8rin.imagetoolbox.feature.crop.presentation.components.FreeCornersCropToggle import com.t8rin.imagetoolbox.feature.crop.presentation.screenLogic.CropComponent import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun CropContent( component: CropComponent ) { val scope = rememberCoroutineScope() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.bitmap != null) showExitDialog = true else component.onGoBack() } AutoContentBasedColors(component.bitmap) var coercePointsToImageArea by rememberSaveable { mutableStateOf(true) } val rotationState = rememberSaveable { mutableFloatStateOf(0f) } val imagePicker = rememberImagePicker { uri: Uri -> rotationState.floatValue = 0f component.setUri(uri) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialUri != null ) val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showResetDialog by rememberSaveable { mutableStateOf(false) } var crop by remember { mutableStateOf(false) } val actions = @Composable { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.bitmap != null, onShare = component::shareBitmap, onEdit = { component.cacheCurrentImage { uri -> editSheetData = listOf(uri) } }, onCopy = { component.cacheCurrentImage(Clipboard::copy) } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } AdaptiveBottomScaffoldLayoutScreen( title = { TopAppBarTitle( title = stringResource(R.string.crop), input = component.bitmap, isLoading = component.isImageLoading, size = null, originalSize = null ) }, onGoBack = onBack, shouldDisableBackHandler = component.bitmap == null, actions = { actions() }, topAppBarPersistentActions = { scaffoldState -> if (component.bitmap == null) TopAppBarEmoji() else { if (isPortrait) { EnhancedIconButton( onClick = { scope.launch { if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) { scaffoldState.bottomSheetState.partialExpand() } else { scaffoldState.bottomSheetState.expand() } } }, ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = stringResource(R.string.properties) ) } } EnhancedIconButton( onClick = { showResetDialog = true }, enabled = component.bitmap != null && component.isBitmapChanged ) { Icon( imageVector = Icons.Rounded.ImageReset, contentDescription = stringResource(R.string.reset_image) ) } if (!isPortrait) actions() } }, mainContent = { component.bitmap?.let { bitmap -> Cropper( bitmap = bitmap, crop = crop, onImageCropStarted = component::imageCropStarted, onImageCropFinished = { component.imageCropFinished() if (it != null) { component.updateBitmap(it) } crop = false }, rotationState = rotationState, cropProperties = component.cropProperties, cropType = component.cropType, addVerticalInsets = !isPortrait, coercePointsToImageArea = coercePointsToImageArea ) } }, controls = { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { FreeCornersCropToggle( modifier = Modifier.fillMaxWidth(), value = component.cropType == CropType.FreeCorners, onClick = component::toggleFreeCornersCrop ) BoxAnimatedVisibility( visible = component.cropType == CropType.Default, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { CropRotationSelector( value = rotationState.floatValue, onValueChange = { rotationState.floatValue = it } ) } BoxAnimatedVisibility( visible = component.cropType == CropType.FreeCorners, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { CoercePointsToImageBoundsToggle( value = coercePointsToImageArea, onValueChange = { coercePointsToImageArea = it }, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(8.dp)) MagnifierEnabledSelector( modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.extraLarge, ) } } BoxAnimatedVisibility( visible = component.cropType != CropType.FreeCorners, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { AspectRatioSelector( modifier = Modifier.fillMaxWidth(), selectedAspectRatio = component.selectedAspectRatio, onAspectRatioChange = component::setCropAspectRatio ) Spacer(modifier = Modifier.height(8.dp)) CropMaskSelection( onCropMaskChange = component::setCropMask, selectedItem = component.cropProperties.cropOutlineProperty, loadImage = { component.loadImage(it)?.asImageBitmap() } ) } } ImageFormatSelector( modifier = Modifier.navigationBarsPadding(), entries = if (component.cropProperties.cropOutlineProperty.outlineType == OutlineType.Rect) { ImageFormatGroup.entries } else ImageFormatGroup.alphaContainedEntries, value = component.imageFormat, onValueChange = { component.setImageFormat(it) } ) } }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } var job by remember { mutableStateOf(null) } BottomButtonsBlock( isNoData = component.bitmap == null, onSecondaryButtonClick = { if (component.bitmap == null) { pickImage() } else { job?.cancel() job = scope.launch { delay(500) crop = true } } }, onSecondaryButtonLongClick = if (component.bitmap == null) { { showOneTimeImagePickingDialog = true } } else null, secondaryButtonIcon = if (component.bitmap == null) Icons.Rounded.AddPhotoAlt else Icons.Rounded.CropSmall, onPrimaryButtonClick = { saveBitmap(null) }, isPrimaryButtonVisible = component.isBitmapChanged, middleFab = { EnhancedFloatingActionButton( onClick = pickImage, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.add_image) ) } }, showMiddleFabInRow = component.bitmap != null, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, noDataControls = { ImageNotPickedWidget(onPickImage = pickImage) }, canShowScreenData = component.bitmap != null, showActionsInTopAppBar = false ) ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, onReset = component::resetBitmap ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving || component.isImageLoading, onCancelLoading = component::cancelSaving, canCancel = component.isSaving ) } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CoercePointsToImageBoundsToggle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Rectangle import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun CoercePointsToImageBoundsToggle( value: Boolean, onValueChange: (Boolean) -> Unit, modifier: Modifier = Modifier ) { PreferenceRowSwitch( checked = value, onClick = onValueChange, title = stringResource(R.string.coerce_points_to_image_bounds), subtitle = stringResource(R.string.coerce_points_to_image_bounds_sub), startIcon = Icons.Outlined.Rectangle, modifier = modifier, shape = ShapeDefaults.extraLarge ) } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropMaskSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.cropper.model.CornerRadiusProperties import com.t8rin.cropper.model.CutCornerCropShape import com.t8rin.cropper.model.ImageMaskOutline import com.t8rin.cropper.model.OutlineType import com.t8rin.cropper.model.RoundedCornerCropShape import com.t8rin.cropper.settings.CropOutlineProperty import com.t8rin.cropper.widget.CropFrameDisplayCard import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable fun CropMaskSelection( modifier: Modifier = Modifier, selectedItem: CropOutlineProperty, loadImage: suspend (Uri) -> ImageBitmap?, onCropMaskChange: (CropOutlineProperty) -> Unit, color: Color = Color.Unspecified, shape: Shape = ShapeDefaults.extraLarge ) { var cornerRadius by rememberSaveable { mutableIntStateOf(20) } val outlineProperties = DefaultOutlineProperties val scope = rememberCoroutineScope() val maskLauncher = rememberImagePicker { uri: Uri -> scope.launch { loadImage(uri)?.let { onCropMaskChange( outlineProperties.last().run { copy( cropOutline = (cropOutline as ImageMaskOutline).copy(image = it) ) } ) } } } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.container( color = color, shape = shape ) ) { Text( text = stringResource(id = R.string.crop_mask), modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 16.dp), fontWeight = FontWeight.Medium ) val listState = rememberLazyListState() LazyRow( horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), contentPadding = PaddingValues( start = 16.dp, top = 4.dp, bottom = 4.dp, end = 16.dp + WindowInsets .navigationBars .asPaddingValues() .calculateEndPadding(LocalLayoutDirection.current) ), state = listState, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fadingEdges(listState), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = outlineProperties, key = { _, o -> o.cropOutline.id } ) { _, item -> val selected = selectedItem.cropOutline.id == item.cropOutline.id CropFrameDisplayCard( modifier = Modifier .padding(vertical = 8.dp) .height(100.dp) .container( resultPadding = 0.dp, color = animateColorAsState( targetValue = if (selected) { MaterialTheme.colorScheme.primaryContainer } else MaterialTheme.colorScheme.surfaceContainerLowest, ).value, borderColor = if (selected) MaterialTheme.colorScheme.onPrimaryContainer.copy( 0.7f ) else MaterialTheme.colorScheme.outlineVariant() ) .hapticsClickable { if (item.cropOutline is ImageMaskOutline) { maskLauncher.pickImage() } else { onCropMaskChange(item) } cornerRadius = 20 } .padding(16.dp), editable = false, scale = 1f, outlineColor = MaterialTheme.colorScheme.secondary, title = "", cropOutline = item.cropOutline ) } } EnhancedSliderItem( visible = selectedItem.cropOutline.id == 1 || selectedItem.cropOutline.id == 2, modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), shape = ShapeDefaults.default, value = cornerRadius, title = stringResource(R.string.radius), icon = null, internalStateTransformation = { it.roundToInt() }, containerColor = MaterialTheme.colorScheme.surface, onValueChange = { cornerRadius = it.roundToInt() if (selectedItem.cropOutline is CutCornerCropShape) { onCropMaskChange( selectedItem.copy( cropOutline = CutCornerCropShape( id = selectedItem.cropOutline.id, title = selectedItem.cropOutline.title, cornerRadius = CornerRadiusProperties( topStartPercent = cornerRadius, topEndPercent = cornerRadius, bottomStartPercent = cornerRadius, bottomEndPercent = cornerRadius ) ) ) ) } else if (selectedItem.cropOutline is RoundedCornerCropShape) { onCropMaskChange( selectedItem.copy( cropOutline = RoundedCornerCropShape( id = selectedItem.cropOutline.id, title = selectedItem.cropOutline.title, cornerRadius = CornerRadiusProperties( topStartPercent = cornerRadius, topEndPercent = cornerRadius, bottomStartPercent = cornerRadius, bottomEndPercent = cornerRadius ) ) ) ) } }, valueRange = 0f..50f, steps = 50 ) AnimatedVisibility( selectedItem.cropOutline.title == OutlineType.ImageMask.name ) { Column( modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .container(shape = ShapeDefaults.extraLarge) .padding(8.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = stringResource(id = R.string.image_crop_mask_sub), textAlign = TextAlign.Center, color = LocalContentColor.current.copy(0.5f), fontSize = 14.sp, lineHeight = 16.sp ) } } } } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropRotationSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ScreenRotationAlt import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem @Composable fun CropRotationSelector( value: Float, onValueChange: (Float) -> Unit, modifier: Modifier = Modifier ) { EnhancedSliderItem( value = value, title = stringResource(R.string.rotation), modifier = modifier, icon = Icons.Rounded.ScreenRotationAlt, valueRange = -45f..45f, internalStateTransformation = { it.roundTo(1) }, onValueChange = onValueChange ) } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/CropType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.components enum class CropType { Default, NoRotation, FreeCorners } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/Cropper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.components import android.graphics.Bitmap import android.graphics.BitmapFactory import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableFloatState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.core.net.toFile import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.cropper.ImageCropper import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.settings.CropDefaults import com.t8rin.cropper.settings.CropProperties import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.opencv_tools.free_corners_crop.compose.FreeCornersCropper import com.yalantis.ucrop.compose.UCropper import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable fun Cropper( modifier: Modifier = Modifier, bitmap: Bitmap, crop: Boolean, onImageCropStarted: () -> Unit, onImageCropFinished: (Bitmap?) -> Unit, cropType: CropType, coercePointsToImageArea: Boolean, rotationState: MutableFloatState, cropProperties: CropProperties, addVerticalInsets: Boolean ) { LaunchedEffect(crop) { if (crop) onImageCropStarted() } AnimatedContent( targetState = cropType, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { type -> when (type) { CropType.Default -> { val scope = rememberCoroutineScope() UCropper( imageModel = bitmap, aspectRatio = if (cropProperties.aspectRatio != AspectRatio.Original) { cropProperties.aspectRatio.value } else null, modifier = Modifier.transparencyChecker(), containerModifier = Modifier.fillMaxSize(), hapticsStrength = LocalSettingsState.current.hapticsStrength, croppingTrigger = crop, onCropped = { scope.launch { BitmapFactory.decodeFile(it.toFile().absolutePath) .apply(onImageCropFinished) } }, isOverlayDraggable = true, rotationAngleState = rotationState, onLoadingStateChange = { if (it) onImageCropStarted() else onImageCropFinished(null) }, contentPadding = WindowInsets.systemBars.union(WindowInsets.displayCutout).let { if (addVerticalInsets) it.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) else it.only(WindowInsetsSides.Horizontal) }.asPaddingValues() ) } CropType.NoRotation -> { AnimatedContent( targetState = (cropProperties.aspectRatio != AspectRatio.Original) to bitmap, transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = modifier.fillMaxSize(), ) { (fixedAspectRatio, bitmap) -> Box { var zoomLevel by remember { mutableFloatStateOf(1f) } val bmp = remember(bitmap) { bitmap.asImageBitmap() } val minDimension = remember(cropProperties.handleSize) { val size = (cropProperties.handleSize * 3).roundToInt() IntSize(size, size) } ImageCropper( backgroundModifier = Modifier.transparencyChecker(), imageBitmap = bmp, contentDescription = null, cropProperties = cropProperties.copy( fixedAspectRatio = fixedAspectRatio, minDimension = minDimension ), onCropStart = onImageCropStarted, crop = crop, cropStyle = CropDefaults.style( overlayColor = MaterialTheme.colorScheme.surfaceVariant ), onZoomChange = { newZoom -> zoomLevel = newZoom }, onCropSuccess = { image -> onImageCropFinished(image.asAndroidBitmap()) } ) BoxAnimatedVisibility( visible = zoomLevel > 1f, modifier = Modifier .padding(16.dp) .align(Alignment.TopStart), enter = scaleIn() + fadeIn(), exit = scaleOut() + fadeOut() ) { Text( text = stringResource(R.string.zoom) + " ${zoomLevel.roundToTwoDigits()}x", modifier = Modifier .background( color = MaterialTheme.colorScheme.scrim.copy(0.4f), shape = ShapeDefaults.circle ) .padding(horizontal = 8.dp, vertical = 4.dp), color = Color.White ) } } } } CropType.FreeCorners -> { val settingsState = LocalSettingsState.current FreeCornersCropper( bitmap = bitmap, croppingTrigger = crop, onCropped = { onImageCropFinished(it) }, coercePointsToImageArea = coercePointsToImageArea, modifier = Modifier.transparencyChecker(), contentPadding = WindowInsets.systemBars.union(WindowInsets.displayCutout) .let { if (addVerticalInsets) it.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) else it.only(WindowInsetsSides.Horizontal) } .union( WindowInsets( left = 16.dp, top = 16.dp, right = 16.dp, bottom = 16.dp ) ) .asPaddingValues(), showMagnifier = settingsState.magnifierEnabled ) } } } } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/DefaultOutlineProperties.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.components import androidx.compose.material3.MaterialShapes import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Shape import com.t8rin.cropper.model.CornerRadiusProperties import com.t8rin.cropper.model.CropShape import com.t8rin.cropper.model.CustomPathOutline import com.t8rin.cropper.model.CutCornerCropShape import com.t8rin.cropper.model.ImageMaskOutline import com.t8rin.cropper.model.OutlineType import com.t8rin.cropper.model.RectCropShape import com.t8rin.cropper.model.RoundedCornerCropShape import com.t8rin.cropper.settings.CropOutlineProperty import com.t8rin.cropper.settings.Paths import com.t8rin.imagetoolbox.core.resources.shapes.ArrowShape import com.t8rin.imagetoolbox.core.resources.shapes.BookmarkShape import com.t8rin.imagetoolbox.core.resources.shapes.BurgerShape import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.resources.shapes.DropletShape import com.t8rin.imagetoolbox.core.resources.shapes.EggShape import com.t8rin.imagetoolbox.core.resources.shapes.ExplosionShape import com.t8rin.imagetoolbox.core.resources.shapes.KotlinShape import com.t8rin.imagetoolbox.core.resources.shapes.MapShape import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.resources.shapes.OctagonShape import com.t8rin.imagetoolbox.core.resources.shapes.OvalShape import com.t8rin.imagetoolbox.core.resources.shapes.PentagonShape import com.t8rin.imagetoolbox.core.resources.shapes.PillShape import com.t8rin.imagetoolbox.core.resources.shapes.ShieldShape import com.t8rin.imagetoolbox.core.resources.shapes.ShurikenShape import com.t8rin.imagetoolbox.core.resources.shapes.SmallMaterialStarShape import com.t8rin.imagetoolbox.core.resources.shapes.SquircleShape import com.t8rin.imagetoolbox.core.settings.presentation.utils.toShape val DefaultOutlineProperties = listOf( CropOutlineProperty( outlineType = OutlineType.Rect, cropOutline = RectCropShape( id = 0, title = OutlineType.Rect.name ) ), CropOutlineProperty( outlineType = OutlineType.RoundedRect, cropOutline = RoundedCornerCropShape( id = 1, title = OutlineType.RoundedRect.name, cornerRadius = CornerRadiusProperties() ) ), CropOutlineProperty( outlineType = OutlineType.CutCorner, cropOutline = CutCornerCropShape( id = 2, title = OutlineType.CutCorner.name, cornerRadius = CornerRadiusProperties() ) ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = OvalShape override val id: Int = 3 override val title: String = "Oval" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = SquircleShape override val id: Int = 4 override val title: String = "Squircle" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = OctagonShape override val id: Int = 5 override val title: String = "Octagon" }, ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = PentagonShape override val id: Int = 6 override val title: String = "Pentagon" } ), CropOutlineProperty( OutlineType.Custom, object : CropShape { override val shape: Shape = CloverShape override val id: Int = 7 override val title: String = "Clover" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = PillShape override val id: Int = 8 override val title: String = "Pill" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = BookmarkShape override val id: Int = 9 override val title: String = "Bookmark" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = BurgerShape override val id: Int = 10 override val title: String = "Burger" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = ShurikenShape override val id: Int = 11 override val title: String = "Shuriken" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = ExplosionShape override val id: Int = 12 override val title: String = "Explosion" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = MapShape override val id: Int = 13 override val title: String = "Map" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = MaterialStarShape override val id: Int = 14 override val title: String = "MaterialStar" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = SmallMaterialStarShape override val id: Int = 15 override val title: String = "SmallMaterialStar" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = ShieldShape override val id: Int = 16 override val title: String = "Shield" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = EggShape override val id: Int = 17 override val title: String = "Egg" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = DropletShape override val id: Int = 18 override val title: String = "Droplet" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = ArrowShape override val id: Int = 19 override val title: String = "Arrow" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = KotlinShape override val id: Int = 20 override val title: String = "Kotlin" } ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = CustomPathOutline( id = 21, title = "Heart", path = Paths.Favorite ) ), CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = CustomPathOutline( id = 22, title = "Star", path = Paths.Star ) ), ).toMutableList().apply { val shapes = listOf( MaterialShapes.Slanted, MaterialShapes.Arch, MaterialShapes.Fan, MaterialShapes.Arrow, MaterialShapes.SemiCircle, MaterialShapes.Oval, MaterialShapes.Triangle, MaterialShapes.Diamond, MaterialShapes.ClamShell, MaterialShapes.Gem, MaterialShapes.Sunny, MaterialShapes.VerySunny, MaterialShapes.Cookie4Sided, MaterialShapes.Cookie6Sided, MaterialShapes.Cookie9Sided, MaterialShapes.Cookie12Sided, MaterialShapes.Ghostish, MaterialShapes.Clover4Leaf, MaterialShapes.Clover8Leaf, MaterialShapes.Burst, MaterialShapes.SoftBurst, MaterialShapes.Boom, MaterialShapes.SoftBoom, MaterialShapes.Flower, MaterialShapes.Puffy, MaterialShapes.PuffyDiamond, MaterialShapes.PixelCircle, MaterialShapes.PixelTriangle, MaterialShapes.Bun, MaterialShapes.Heart ).mapIndexed { index, polygon -> CropOutlineProperty( outlineType = OutlineType.Custom, cropOutline = object : CropShape { override val shape: Shape = polygon.toShape() override val id: Int = index + 500 override val title: String = (index + 500).toString() } ) } addAll(shapes) add( CropOutlineProperty( outlineType = OutlineType.ImageMask, cropOutline = ImageMaskOutline( id = 23, title = OutlineType.ImageMask.name, image = ImageBitmap( width = 1, height = 1 ) ) ) ) } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/components/FreeCornersCropToggle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Perspective import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun FreeCornersCropToggle( value: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ) { PreferenceRowSwitch( modifier = modifier, shape = ShapeDefaults.extraLarge, startIcon = Icons.Outlined.Perspective, title = stringResource(R.string.free_corners), subtitle = stringResource(R.string.free_corners_sub), checked = value, onClick = { onClick() } ) } ================================================ FILE: feature/crop/src/main/java/com/t8rin/imagetoolbox/feature/crop/presentation/screenLogic/CropComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.crop.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.model.OutlineType import com.t8rin.cropper.model.RectCropShape import com.t8rin.cropper.settings.CropDefaults import com.t8rin.cropper.settings.CropOutlineProperty import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.crop.presentation.components.CropType import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class CropComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } } private val _selectedAspectRatio: MutableState = mutableStateOf(DomainAspectRatio.Free) val selectedAspectRatio by _selectedAspectRatio private val defaultOutline = CropOutlineProperty( OutlineType.Rect, RectCropShape( id = 0, title = OutlineType.Rect.name ) ) private val _cropProperties = mutableStateOf( CropDefaults.properties( cropOutlineProperty = defaultOutline, fling = true ) ) val cropProperties by _cropProperties private val _cropType: MutableState = mutableStateOf(CropType.Default) val cropType: CropType by _cropType private val _uri = mutableStateOf(Uri.EMPTY) private var internalBitmap = mutableStateOf(null) private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap val isBitmapChanged get() = internalBitmap.value != _bitmap.value private val _imageFormat = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat by _imageFormat private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving fun updateBitmap( bitmap: Bitmap?, newBitmap: Boolean = false ) { componentScope.launch { _isImageLoading.value = true val bmp = imageScaler.scaleUntilCanShow(bitmap) if (newBitmap) { internalBitmap.value = bmp } _bitmap.value = bmp _isImageLoading.value = false } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.value = imageFormat } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmap( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true bitmap?.let { localBitmap -> val byteArray = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) ) val decoded = imageGetter.getImage(data = byteArray) _bitmap.value = decoded parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ), originalUri = _uri.value.toString(), sequenceNumber = null, data = byteArray ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } _isSaving.value = false } } fun setCropAspectRatio( domainAspectRatio: DomainAspectRatio, aspectRatio: AspectRatio ) { _cropProperties.update { properties -> properties.copy( aspectRatio = aspectRatio.takeIf { domainAspectRatio != DomainAspectRatio.Original } ?: _bitmap.value?.let { AspectRatio(it.safeAspectRatio) } ?: aspectRatio, fixedAspectRatio = domainAspectRatio != DomainAspectRatio.Free ) } _selectedAspectRatio.update { domainAspectRatio } } fun setCropMask(cropOutlineProperty: CropOutlineProperty) { _cropProperties.update { it.copy(cropOutlineProperty = cropOutlineProperty) } if (cropOutlineProperty.cropOutline.id == 0) { _cropType.update { CropType.Default } } else { _cropType.update { CropType.NoRotation } } } fun toggleFreeCornersCrop() { _cropType.update { if (it != CropType.FreeCorners) { CropType.FreeCorners } else if (cropProperties.cropOutlineProperty.cropOutline.id != defaultOutline.cropOutline.id) { CropType.NoRotation } else { CropType.Default } } } fun resetBitmap() { _bitmap.value = internalBitmap.value } fun imageCropStarted() { _isImageLoading.value = true } fun imageCropFinished() { _isImageLoading.value = false } fun setUri( uri: Uri ) { _uri.value = uri imageGetter.getImageAsync( uri = uri.toString(), originalSize = true, onGetImage = { updateBitmap(it.image, true) setImageFormat(it.imageInfo.imageFormat) }, onFailure = AppToastHost::showFailureToast ) } fun shareBitmap() { savingJob = trackProgress { _isSaving.value = true bitmap?.let { localBitmap -> shareProvider.shareImage( imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ), image = localBitmap, onComplete = { _isSaving.value = false AppToastHost.showConfetti() } ) } } } suspend fun loadImage(uri: Uri): Bitmap? = imageGetter.getImage(data = uri) fun cancelSaving() { _isSaving.value = false savingJob?.cancel() savingJob = null } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true bitmap?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = image.width, height = image.height ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun getFormatForFilenameSelection(): ImageFormat = imageFormat @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): CropComponent } } ================================================ FILE: feature/crop/src/main/res/values/dimens.xml ================================================ 50dp ================================================ FILE: feature/delete-exif/.gitignore ================================================ /build ================================================ FILE: feature/delete-exif/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.delete_exif" ================================================ FILE: feature/delete-exif/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/DeleteExifContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.delete_exif.presentation import android.net.Uri import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.localizedName import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.sheets.AddExifSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.delete_exif.presentation.screenLogic.DeleteExifComponent @Composable fun DeleteExifContent( component: DeleteExifComponent ) { AutoContentBasedColors(component.bitmap) val imagePicker = rememberImagePicker { uris: List -> component.updateUris( uris = uris ) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } var showExifSelection by rememberSaveable { mutableStateOf(false) } ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.delete_exif), input = component.bitmap, isLoading = component.isImageLoading, size = rememberFileSize(component.selectedUri) ) }, onGoBack = { if (component.uris?.isNotEmpty() == true) showExitDialog = true else component.onGoBack() }, actions = { if (component.previewBitmap != null) { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( onShare = component::shareBitmaps, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } ZoomButton( onClick = { showZoomSheet = true }, visible = component.bitmap != null, ) }, topAppBarPersistentActions = { if (component.bitmap == null) { TopAppBarEmoji() } }, imagePreview = { ImageContainer( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), imageInside = isPortrait, showOriginal = false, previewBitmap = component.previewBitmap, originalBitmap = component.bitmap, isLoading = component.isImageLoading, shouldShowPreview = true ) }, controls = { val selectedTags = component.selectedTags val subtitle by remember(selectedTags) { derivedStateOf { if (selectedTags.isEmpty()) getString(R.string.all) else selectedTags.joinToString(", ") { it.localizedName(appContext) } } } ImageCounter( imageCount = component.uris?.size?.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) Spacer(modifier = Modifier.height(8.dp)) PreferenceItem( onClick = { showExifSelection = true }, modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.tags_to_remove), subtitle = subtitle, shape = ShapeDefaults.extraLarge, startIcon = Icons.Rounded.Exif, endIcon = Icons.Rounded.MiniEdit ) }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, noDataControls = { ImageNotPickedWidget(onPickImage = pickImage) }, canShowScreenData = !component.uris.isNullOrEmpty() ) AddExifSheet( visible = showExifSelection, onDismiss = { showExifSelection = it }, selectedTags = component.selectedTags, onTagSelected = component::addTag, isTagsRemovable = true ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris?.size ?: 1, onCancelLoading = component::cancelSaving ) PickImageFromUrisSheet( visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, uris = component.uris, selectedUri = component.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = { uri -> component.updateUrisSilently(removedUri = uri) }, columns = if (isPortrait) 2 else 4, ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/delete-exif/src/main/java/com/t8rin/imagetoolbox/feature/delete_exif/presentation/screenLogic/DeleteExifComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.delete_exif.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.clearAttributes import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay class DeleteExifComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val shareProvider: ShareProvider, private val filenameCreator: FilenameCreator, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::updateUris) } } private val _uris = mutableStateOf?>(null) val uris by _uris private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _selectedUri: MutableState = mutableStateOf(null) val selectedUri by _selectedUri private val _selectedTags: MutableState> = mutableStateOf(emptyList()) val selectedTags by _selectedTags fun updateUris( uris: List? ) { _uris.value = null _uris.value = uris uris?.firstOrNull()?.let(::updateSelectedUri) } fun updateUrisSilently(removedUri: Uri) { componentScope.launch { _uris.value = uris if (_selectedUri.value == removedUri) { val index = uris?.indexOf(removedUri) ?: -1 if (index == 0) { uris?.getOrNull(1)?.let { _selectedUri.value = it _bitmap.value = imageGetter.getImage(it.toString(), originalSize = false)?.image } } else { uris?.getOrNull(index - 1)?.let { _selectedUri.value = it _bitmap.value = imageGetter.getImage(it.toString(), originalSize = false)?.image } } } val u = _uris.value?.toMutableList()?.apply { remove(removedUri) } _uris.value = u } } private fun updateBitmap(bitmap: Bitmap?) { componentScope.launch { _isImageLoading.value = true _bitmap.value = bitmap _previewBitmap.value = imageScaler.scaleUntilCanShow(bitmap) delay(500) _isImageLoading.value = false } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 uris?.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString()) }.getOrNull()?.let { val metadata = if (selectedTags.isNotEmpty()) { it.metadata?.clearAttributes(selectedTags) } else null results.add( fileController.save( ImageSaveTarget( imageInfo = it.imageInfo, originalUri = uri.toString(), sequenceNumber = _done.value, metadata = metadata, data = ByteArray(0), readFromUriInsteadOfData = true ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun updateSelectedUri( uri: Uri ) = componentScope.launch { _isImageLoading.value = true _selectedUri.value = uri imageGetter.getImageAsync( uri = uri.toString(), originalSize = false, onGetImage = { updateBitmap(it.image) }, onFailure = { _isImageLoading.value = false AppToastHost.showFailureToast(it) } ) } fun shareBitmaps() { _isSaving.update { true } cacheImages { uris -> savingJob = trackProgress { shareProvider.shareUris(uris.map(Uri::toString)) AppToastHost.showConfetti() _isSaving.update { false } } } } fun cancelSaving() { _isSaving.update { false } savingJob?.cancel() savingJob = null } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { cacheImages( uris = listOfNotNull(selectedUri), onComplete = { if (it.isNotEmpty()) onComplete(it.first()) } ) } fun addTag(tag: MetadataTag) { if (tag in selectedTags) { _selectedTags.update { it - tag } } else { _selectedTags.update { it + tag } } } fun cacheImages( uris: List? = this.uris, onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 val list = mutableListOf() uris?.forEach { uri -> imageGetter.getImage( uri.toString() )?.let { val metadata = if (selectedTags.isNotEmpty()) { it.metadata?.clearAttributes(selectedTags) } else null shareProvider.cacheData( writeData = { w -> w.writeBytes( fileController.readBytes(uri.toString()) ) }, filename = filenameCreator.constructImageFilename( saveTarget = ImageSaveTarget( imageInfo = it.imageInfo.copy(originalUri = uri.toString()), originalUri = uri.toString(), sequenceNumber = done, metadata = metadata, data = ByteArray(0) ) ) )?.let { uri -> fileController.writeMetadata( imageUri = uri, metadata = metadata ) list.add(uri.toUri()) } } _done.value++ updateProgress( done = done, total = uris.size ) } onComplete(list) _isSaving.value = false } } fun selectLeftUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.rightFrom(it) } ?.let(::updateSelectedUri) } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): DeleteExifComponent } } ================================================ FILE: feature/document-scanner/.gitignore ================================================ /build ================================================ FILE: feature/document-scanner/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.document_scanner" dependencies { implementation(projects.feature.pdfTools) } ================================================ FILE: feature/document-scanner/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/DocumentScannerContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.document_scanner.presentation import android.net.Uri import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.rounded.DocumentScanner import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.icons.Pdf import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberDocumentScanner import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.FileNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.ImagePager import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.document_scanner.presentation.screenLogic.DocumentScannerComponent @Composable fun DocumentScannerContent( component: DocumentScannerComponent ) { var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val savePdfLauncher = rememberFileCreator( mimeType = MimeType.Pdf, onSuccess = component::savePdfTo ) val documentScanner = rememberDocumentScanner { component.parseScanResult(it) } val additionalDocumentScanner = rememberDocumentScanner { component.addScanResult(it) } AutoFilePicker( onAutoPick = documentScanner::scan, isPickedAlready = false ) val isPortrait by isPortraitOrientationAsState() val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { Text( text = stringResource(R.string.document_scanner), modifier = Modifier.marquee() ) }, topAppBarPersistentActions = { TopAppBarEmoji() }, onGoBack = onBack, actions = {}, imagePreview = {}, showImagePreviewAsStickyHeader = false, placeImagePreview = false, addHorizontalCutoutPaddingIfNoPreview = false, noDataControls = { FileNotPickedWidget( onPickFile = documentScanner::scan, text = stringResource(R.string.click_to_start_scanning) ) }, controls = { var selectedUriForPreview by remember { mutableStateOf(null) } ImagePager( visible = selectedUriForPreview != null, selectedUri = selectedUriForPreview, uris = component.uris, onNavigate = { selectedUriForPreview = null component.onNavigate(it) }, onUriSelected = { selectedUriForPreview = it }, onShare = component::shareUri, onDismiss = { selectedUriForPreview = null } ) Spacer(modifier = Modifier.height(24.dp)) UrisPreview( uris = component.uris, isPortrait = isPortrait, onRemoveUri = component::removeImageUri, onAddUris = additionalDocumentScanner::scan, addUrisContent = { width -> Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.add), modifier = Modifier.size(width / 3f) ) }, onClickUri = { uri -> selectedUriForPreview = uri } ) Spacer(modifier = Modifier.height(12.dp)) Column( modifier = Modifier .fillMaxWidth() .container( resultPadding = 0.dp, shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surfaceContainerLow ) .padding(8.dp), ) { EnhancedButton( onClick = component::sharePdf, containerColor = MaterialTheme.colorScheme.secondary, contentPadding = PaddingValues( top = 8.dp, bottom = 8.dp, start = 12.dp, end = 20.dp ), shape = ShapeDefaults.top, modifier = Modifier.fillMaxWidth() ) { Icon( imageVector = Icons.Outlined.Share, contentDescription = stringResource(R.string.share_as_pdf) ) Spacer(Modifier.width(8.dp)) AutoSizeText( text = stringResource(id = R.string.share_as_pdf), maxLines = 2 ) } Spacer(Modifier.height(4.dp)) EnhancedButton( onClick = { savePdfLauncher.make(component.generatePdfFilename()) }, contentPadding = PaddingValues( top = 8.dp, bottom = 8.dp, start = 16.dp, end = 20.dp ), shape = ShapeDefaults.bottom, modifier = Modifier.fillMaxWidth() ) { Icon( imageVector = Icons.Outlined.Pdf, contentDescription = stringResource(R.string.save_as_pdf) ) Spacer(Modifier.width(8.dp)) AutoSizeText( text = stringResource(id = R.string.save_as_pdf), maxLines = 2 ) } } Spacer(modifier = Modifier.height(24.dp)) InfoContainer( modifier = Modifier.padding(8.dp), text = stringResource(R.string.options_below_is_for_images) ) if (component.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = component.imageFormat, quality = component.quality, onQualityChange = component::setQuality ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, quality = component.quality, ) }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isEmpty(), onSecondaryButtonClick = documentScanner::scan, secondaryButtonIcon = Icons.Rounded.DocumentScanner, secondaryButtonText = stringResource(R.string.start_scanning), onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { ShareButton( enabled = component.uris.isNotEmpty(), onShare = component::shareBitmaps ) } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) }, canShowScreenData = component.uris.isNotEmpty() ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/document-scanner/src/main/java/com/t8rin/imagetoolbox/feature/document_scanner/presentation/screenLogic/DocumentScannerComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.document_scanner.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ScanResult import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.navigation.coroutineScope import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCreationParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlin.random.Random class DocumentScannerComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val shareProvider: ImageShareProvider, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val fileController: FileController, private val pdfManager: PdfManager, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _uris = mutableStateOf>(emptyList()) val uris by _uris private val _imageFormat = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat private val _quality = mutableStateOf(Quality.Base()) val quality by _quality private val _pdfUris = mutableStateOf>(emptyList()) private suspend fun getPdfUri(): Uri? = if (_pdfUris.value.size > 1 || _pdfUris.value.isEmpty()) { createPdfUri() } else _pdfUris.value.firstOrNull() private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left fun parseScanResult(scanResult: ScanResult) { if (scanResult.imageUris.isNotEmpty()) { _uris.update { scanResult.imageUris } } if (scanResult.pdfUri != null) { _pdfUris.update { listOfNotNull(scanResult.pdfUri) } } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 uris.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString())?.image }.getOrNull()?.let { bitmap -> val imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = imageFormat, quality = quality, originalUri = uri.toString() ) results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = bitmap, imageInfo = imageInfo ) ), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun savePdfTo( uri: Uri ) { savingJob = trackProgress { _isSaving.value = true getPdfUri()?.let { pdfUri -> fileController.transferBytes( fromUri = pdfUri.toString(), toUri = uri.toString(), ).also(::parseFileSaveResult).onSuccess(::registerSave) _isSaving.value = false } } } private suspend fun createPdfUri(): Uri? { _done.update { 0 } _left.update { 0 } return runSuspendCatching { pdfManager.createPdf( imageUris = uris.map { it.toString() }, params = PdfCreationParams() ) }.getOrNull()?.toUri() } fun generatePdfFilename(): String { val timeStamp = "${timestamp()}_${Random(Random.nextInt()).hashCode().toString().take(4)}" return "PDF_$timeStamp.pdf" } fun sharePdf() { savingJob = trackProgress { _isSaving.value = true getPdfUri()?.let { pdfUri -> _done.update { 0 } _left.update { 0 } runSuspendCatching { shareProvider.shareUri( uri = pdfUri.toString(), onComplete = AppToastHost::showConfetti ) }.onFailure { val bytes = fileController.readBytes(pdfUri.toString()) shareProvider.shareByteArray( byteArray = bytes, filename = generatePdfFilename(), onComplete = AppToastHost::showConfetti ) } } _isSaving.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun removeImageUri(uri: Uri) { _uris.update { it - uri } _pdfUris.update { emptyList() } } fun addScanResult(scanResult: ScanResult) { _uris.update { (it + scanResult.imageUris).distinct() } _pdfUris.update { (it + scanResult.pdfUri).distinct().filterNotNull() } } fun shareBitmaps() { savingJob = trackProgress { _isSaving.value = true shareProvider.shareImages( uris = uris.map { it.toString() }, imageLoader = { uri -> imageGetter.getImage(uri)?.image?.let { bmp -> bmp to ImageInfo( width = bmp.width, height = bmp.height, imageFormat = imageFormat, quality = quality, originalUri = uri ) } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _isSaving.value = false _done.value = 0 } else { _done.value = it } updateProgress( done = done, total = left ) } ) } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } } fun setQuality(quality: Quality) { _quality.update { quality } } fun getFormatForFilenameSelection(): ImageFormat = imageFormat fun shareUri(uri: Uri) { coroutineScope.launch { shareProvider.shareUri( uri = uri.toString(), onComplete = {} ) } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): DocumentScannerComponent } } ================================================ FILE: feature/draw/.gitignore ================================================ /build ================================================ FILE: feature/draw/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.draw" dependencies { implementation(libs.trickle) implementation(projects.core.filters) implementation(projects.feature.pickColor) } ================================================ FILE: feature/draw/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/AndroidImageDrawApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.data import android.content.Context import android.graphics.Bitmap import android.graphics.BlurMaskFilter import android.graphics.Matrix import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageShader import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.PaintingStyle import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.StampedPathEffectStyle import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.addOutline import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.nativePaint import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.LayoutDirection import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.density import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.data.utils.toSoftware import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.max import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.createFilter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.SpotHealMode import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.utils.toImageModel import com.t8rin.imagetoolbox.core.utils.toTypeface import com.t8rin.imagetoolbox.feature.draw.data.utils.drawRepeatedBitmapOnPath import com.t8rin.imagetoolbox.feature.draw.data.utils.drawRepeatedTextOnPath import com.t8rin.imagetoolbox.feature.draw.domain.DrawBehavior import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.domain.ImageDrawApplier import com.t8rin.imagetoolbox.feature.draw.domain.PathPaint import com.t8rin.trickle.WarpBrush import com.t8rin.trickle.WarpEngine import com.t8rin.trickle.WarpMode import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import kotlin.math.roundToInt import android.graphics.Paint as AndroidPaint import android.graphics.Path as NativePath internal class AndroidImageDrawApplier @Inject constructor( @ApplicationContext private val context: Context, private val imageTransformer: ImageTransformer, private val imageGetter: ImageGetter, private val filterProvider: FilterProvider, ) : ImageDrawApplier { override suspend fun applyDrawToImage( drawBehavior: DrawBehavior, pathPaints: List>, imageUri: String ): Bitmap? { val image: Bitmap? = when (drawBehavior) { is DrawBehavior.Image -> { imageGetter.getImage(data = imageUri) } is DrawBehavior.Background -> { drawBehavior.color.toDrawable().toBitmap( width = drawBehavior.width, height = drawBehavior.height ) } else -> null } val drawImage = image?.let { createBitmap(it.width, it.height, it.safeConfig).apply { setHasAlpha(true) } } drawImage?.let { bitmap -> bitmap.applyCanvas { val canvasSize = IntegerSize(width, height) (drawBehavior as? DrawBehavior.Background)?.apply { drawColor(color) } pathPaints.forEach { (nonScaledPath, nonScaledStroke, radius, drawColor, isErasing, drawMode, size, drawPathMode, drawLineStyle) -> val stroke = drawPathMode.convertStrokeWidth( strokeWidth = nonScaledStroke, canvasSize = canvasSize ) val path = nonScaledPath.scaleToFitCanvas( currentSize = canvasSize, oldSize = size ) val isSharpEdge = drawPathMode.isSharpEdge val isFilled = drawPathMode.isFilled if (drawMode is DrawMode.PathEffect && !isErasing) { val paint = Paint().apply { if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke this.strokeWidth = stroke if (isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } color = Color.Transparent blendMode = BlendMode.Clear } val shaderSource = imageTransformer.transform( image = image.overlay(bitmap), transformations = transformationsForMode( canvasSize = canvasSize, drawMode = drawMode ) )?.asImageBitmap()?.clipBitmap( path = path, paint = paint ) if (shaderSource != null) { drawBitmap(shaderSource.asAndroidBitmap()) } } else if (drawMode is DrawMode.SpotHeal && !isErasing) { val paint = Paint().apply { if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke this.strokeWidth = stroke if (isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } color = Color.White } val filter = filterProvider.filterToTransformation( createFilter, Filter.SpotHeal>( Pair( createBitmap( canvasSize.width, canvasSize.height ).applyCanvas { drawColor(Color.Black.toArgb()) drawPath( path.asAndroidPath(), paint.nativePaint ) }.toImageModel(), drawMode.mode ) ) ) imageTransformer.transform( image = image.overlay(bitmap), transformations = listOf(filter) )?.let { drawBitmap( it.asImageBitmap().clipBitmap( path = path, paint = paint.apply { blendMode = BlendMode.Clear } ).asAndroidBitmap() ) } } else if (drawMode is DrawMode.Warp && !isErasing) { val engine = WarpEngine(image.overlay(bitmap)) try { drawMode.strokes.forEach { warpStroke -> val warp = warpStroke.scaleToFitCanvas( currentSize = canvasSize, oldSize = size ) engine.applyStroke( fromX = warp.fromX, fromY = warp.fromY, toX = warp.toX, toY = warp.toY, brush = WarpBrush( radius = stroke, strength = drawMode.strength, hardness = drawMode.hardness ), mode = WarpMode.valueOf(drawMode.warpMode.name) ) } drawBitmap(engine.render()) } finally { engine.release() } } else { val paint = Paint().apply { if (isErasing) { blendMode = BlendMode.Clear style = PaintingStyle.Stroke this.strokeWidth = stroke strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } else { if (drawMode !is DrawMode.Text) { pathEffect = drawLineStyle.asPathEffect( canvasSize = canvasSize, strokeWidth = stroke ) if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke strokeWidth = stroke if (drawMode is DrawMode.Highlighter || isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } } } color = drawColor alpha = drawColor.alpha }.nativePaint.apply { if (drawMode is DrawMode.Neon && !isErasing) { this.color = Color.White.toArgb() setShadowLayer( radius.toPx(canvasSize), 0f, 0f, drawColor .copy(alpha = .8f) .toArgb() ) } else if (radius.value > 0f) { maskFilter = BlurMaskFilter( radius.toPx(canvasSize), BlurMaskFilter.Blur.NORMAL ) } if (drawMode is DrawMode.Text && !isErasing) { isAntiAlias = true textSize = stroke typeface = drawMode.font.toTypeface() } } val androidPath = path.asAndroidPath() if (drawMode is DrawMode.Text && !isErasing) { if (drawMode.isRepeated) { drawRepeatedTextOnPath( text = drawMode.text, path = androidPath, paint = paint, interval = drawMode.repeatingInterval.toPx(canvasSize) ) } else { drawTextOnPath(drawMode.text, androidPath, 0f, 0f, paint) } } else if (drawMode is DrawMode.Image && !isErasing) { imageGetter.getImage( data = drawMode.imageData, size = stroke.roundToInt() )?.let { drawRepeatedBitmapOnPath( bitmap = it, path = androidPath, paint = paint, interval = drawMode.repeatingInterval.toPx(canvasSize) ) } } else if (drawPathMode is DrawPathMode.Outlined) { drawPathMode.fillColor?.let { fillColor -> val filledPaint = AndroidPaint().apply { set(paint) style = AndroidPaint.Style.FILL color = fillColor.colorInt if (Color(fillColor.colorInt).alpha == 1f) { alpha = (drawColor.alpha * 255).roundToInt().coerceIn(0, 255) } pathEffect = null } drawPath(androidPath, filledPaint) } drawPath(androidPath, paint) } else { drawPath(androidPath, paint) } } } } } return drawImage?.let { image.overlay(it) } } override suspend fun applyEraseToImage( pathPaints: List>, imageUri: String ): Bitmap? = applyEraseToImage( pathPaints = pathPaints, image = imageGetter.getImage(data = imageUri), shaderSourceUri = imageUri ) override suspend fun applyEraseToImage( pathPaints: List>, image: Bitmap?, shaderSourceUri: String ): Bitmap? = image?.let { it.copy(it.safeConfig, true) }?.let { bitmap -> bitmap.applyCanvas { val canvasSize = IntegerSize(width, height) drawBitmap(bitmap) val recoveryShader = imageGetter.getImage( data = shaderSourceUri )?.asImageBitmap()?.let { bmp -> ImageShader(bmp) } pathPaints.forEach { (nonScaledPath, stroke, radius, _, isRecoveryOn, _, size, mode) -> val path = nonScaledPath.scaleToFitCanvas( currentSize = canvasSize, oldSize = size ) drawPath( path.asAndroidPath(), Paint().apply { if (mode.isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke this.strokeWidth = mode.convertStrokeWidth( strokeWidth = stroke, canvasSize = canvasSize ) if (mode.isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } if (isRecoveryOn) { shader = recoveryShader } else { blendMode = BlendMode.Clear } }.nativePaint.apply { if (radius.value > 0f) { maskFilter = BlurMaskFilter( radius.toPx(canvasSize), BlurMaskFilter.Blur.NORMAL ) } } ) } } } private fun transformationsForMode( canvasSize: IntegerSize, drawMode: DrawMode ): List> = when (drawMode) { is DrawMode.PathEffect.PrivacyBlur -> { listOf( createFilter( drawMode.blurRadius.toFloat() / 1000 * max(canvasSize) ) ) } is DrawMode.PathEffect.Pixelation -> { listOf( createFilter( 20.toFloat() / 1000 * max(canvasSize) ), createFilter( drawMode.pixelSize / 1000 * max(canvasSize) ) ) } is DrawMode.PathEffect.Custom -> drawMode.filter?.let { listOf(it) } ?: emptyList() else -> emptyList() }.map { filterProvider.filterToTransformation(it) } private fun DrawLineStyle.asPathEffect( canvasSize: IntegerSize, strokeWidth: Float ): PathEffect? = when (this) { is DrawLineStyle.Dashed -> { PathEffect.dashPathEffect( intervals = floatArrayOf( size.toPx(canvasSize), gap.toPx(canvasSize) + strokeWidth ), phase = 0f ) } DrawLineStyle.DotDashed -> { val dashOnInterval1 = strokeWidth * 4 val dashOffInterval1 = strokeWidth * 2 val dashOnInterval2 = strokeWidth / 4 val dashOffInterval2 = strokeWidth * 2 PathEffect.dashPathEffect( intervals = floatArrayOf( dashOnInterval1, dashOffInterval1, dashOnInterval2, dashOffInterval2 ), phase = 0f ) } is DrawLineStyle.Stamped<*> -> { fun Shape.toPath(): Path = Path().apply { addOutline( createOutline( size = Size(strokeWidth, strokeWidth), layoutDirection = LayoutDirection.Ltr, density = context.density ) ) } val path: Path? = when (shape) { is Shape -> shape.toPath() is NativePath -> shape.asComposePath() is Path -> shape null -> MaterialStarShape.toPath() else -> null } path?.let { PathEffect.stampedPathEffect( shape = it, advance = spacing.toPx(canvasSize) + strokeWidth, phase = 0f, style = StampedPathEffectStyle.Morph ) } } is DrawLineStyle.ZigZag -> { val zigZagPath = Path().apply { val zigZagLineWidth = strokeWidth / heightRatio val shapeVerticalOffset = (strokeWidth / 2) / 2 val shapeHorizontalOffset = (strokeWidth / 2) / 2 moveTo(0f, 0f) lineTo(strokeWidth / 2, strokeWidth / 2) lineTo(strokeWidth, 0f) lineTo(strokeWidth, 0f + zigZagLineWidth) lineTo(strokeWidth / 2, strokeWidth / 2 + zigZagLineWidth) lineTo(0f, 0f + zigZagLineWidth) translate(Offset(-shapeHorizontalOffset, -shapeVerticalOffset)) } PathEffect.stampedPathEffect( shape = zigZagPath, advance = strokeWidth, phase = 0f, style = StampedPathEffectStyle.Morph ) } DrawLineStyle.None -> null } private fun ImageBitmap.clipBitmap( path: Path, paint: Paint, ): ImageBitmap { val newPath = NativePath(path.asAndroidPath()) return asAndroidBitmap().copy(Bitmap.Config.ARGB_8888, true).applyCanvas { drawPath( newPath.apply { fillType = NativePath.FillType.INVERSE_WINDING }, paint.nativePaint ) }.asImageBitmap() } private fun Bitmap.overlay(overlay: Bitmap): Bitmap { val image = this return createBitmap( width = image.width, height = image.height, config = safeConfig.toSoftware() ).applyCanvas { drawBitmap(image) drawBitmap(overlay.toSoftware()) } } private fun Path.scaleToFitCanvas( currentSize: IntegerSize, oldSize: IntegerSize, onGetScale: (Float, Float) -> Unit = { _, _ -> } ): Path { val sx = currentSize.width.toFloat() / oldSize.width val sy = currentSize.height.toFloat() / oldSize.height onGetScale(sx, sy) return NativePath(this.asAndroidPath()).apply { transform( Matrix().apply { setScale(sx, sy) } ) }.asComposePath() } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/data/utils/DrawRepeatedPath.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.data.utils import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Matrix import android.graphics.Paint import android.graphics.Path import android.graphics.PathMeasure import androidx.core.graphics.withSave import kotlin.math.atan2 fun Canvas.drawRepeatedTextOnPath( text: String, path: Path, paint: Paint, interval: Float = 0f ) { val pathMeasure = PathMeasure(path, false) val pathLength = pathMeasure.length val textWidth = paint.measureText(text) val fullRepeats = (pathLength / (textWidth + interval)).toInt() val remainingLength = pathLength - fullRepeats * (textWidth + interval) var distance = 0f repeat(fullRepeats) { drawTextOnPath(text, path, distance, 0f, paint) distance += (textWidth + interval) } if (remainingLength > 0f) { val ratio = (textWidth + interval - (remainingLength)) / (textWidth + interval) val endOffset = (text.length - (text.length * ratio).toInt()).coerceAtLeast(0) drawTextOnPath(text.substring(0, endOffset), path, distance, 0f, paint) } } fun Canvas.drawRepeatedBitmapOnPath( bitmap: Bitmap, path: Path, paint: Paint, interval: Float = 0f ) { val pathMeasure = PathMeasure(path, false) val pathLength = pathMeasure.length val bitmapWidth = bitmap.width.toFloat() val bitmapHeight = bitmap.height.toFloat() var distance = 0f val matrix = Matrix() while (distance < pathLength) { val pos = FloatArray(2) val tan = FloatArray(2) pathMeasure.getPosTan(distance, pos, tan) val degree = Math.toDegrees(atan2(tan[1].toDouble(), tan[0].toDouble())).toFloat() withSave { translate(pos[0], pos[1]) rotate(degree) matrix.reset() matrix.postTranslate(-bitmapWidth / 2, -bitmapHeight / 2) matrix.postRotate(degree) matrix.postTranslate(bitmapWidth / 2, bitmapHeight / 2) drawBitmap(bitmap, matrix, paint) } if (interval < 0 && distance + bitmapWidth < 0) break else { distance += (bitmapWidth + interval) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/di/DrawModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.di import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import com.t8rin.imagetoolbox.feature.draw.data.AndroidImageDrawApplier import com.t8rin.imagetoolbox.feature.draw.domain.ImageDrawApplier import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface DrawModule { @Singleton @Binds fun provideImageDrawApplier( applier: AndroidImageDrawApplier ): ImageDrawApplier } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawBehavior.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain sealed class DrawBehavior( open val orientation: Int ) { data object None : DrawBehavior(2) data class Image( override val orientation: Int ) : DrawBehavior(orientation = orientation) data class Background( override val orientation: Int, val width: Int, val height: Int, val color: Int ) : DrawBehavior(orientation = orientation) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawLineStyle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.pt // Works only for Basic draw modes (excludes DrawMode.PathEffect, DrawMode.SpotHeal, DrawMode.Text, DrawMode.Image) sealed class DrawLineStyle( val ordinal: Int ) { data object None : DrawLineStyle(0) data class Dashed( val size: Pt = 10.pt, val gap: Pt = 20.pt ) : DrawLineStyle(1) data class ZigZag( val heightRatio: Float = 4f ) : DrawLineStyle(2) data class Stamped( val shape: Shape? = null, val spacing: Pt = 20.pt ) : DrawLineStyle(3) data object DotDashed : DrawLineStyle(4) companion object { val entries by lazy { listOf( None, Dashed(), DotDashed, ZigZag(), Stamped(), ) } fun fromOrdinal( ordinal: Int ): DrawLineStyle = entries.find { it.ordinal == ordinal } ?: None } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.SpotHealMode import com.t8rin.imagetoolbox.core.settings.domain.model.FontType sealed class DrawMode(open val ordinal: Int) { data object Neon : DrawMode(2) data object Highlighter : DrawMode(3) data object Pen : DrawMode(0) sealed class PathEffect(override val ordinal: Int) : DrawMode(ordinal) { data class PrivacyBlur( val blurRadius: Int = 20 ) : PathEffect(1) data class Pixelation( val pixelSize: Float = 35f ) : PathEffect(4) data class Custom( val filter: Filter<*>? = null ) : PathEffect(5) } data class Text( val text: String = "Text", val font: FontType? = null, val isRepeated: Boolean = false, val repeatingInterval: Pt = Pt.Zero ) : DrawMode(6) data class Image( val imageData: Any = "file:///android_asset/svg/emotions/aasparkles.svg", val repeatingInterval: Pt = Pt.Zero ) : DrawMode(7) data class SpotHeal( val mode: SpotHealMode = SpotHealMode.OpenCV ) : DrawMode(8) data class Warp( val warpMode: WarpMode = WarpMode.MOVE, val strength: Float = 0.25f, val hardness: Float = 0.5f, val strokes: List = emptyList(), val previewClearToken: Long = 0L ) : DrawMode(9) companion object { val entries by lazy { listOf( Pen, PathEffect.PrivacyBlur(), SpotHeal(), Warp(), Neon, Highlighter, Text(), Image(), PathEffect.Pixelation(), PathEffect.Custom() ) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawOnBackgroundParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain data class DrawOnBackgroundParams( val width: Int, val height: Int, val color: Int?, ) { companion object { val Default by lazy { DrawOnBackgroundParams( width = -1, height = -1, color = null ) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/DrawPathMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.FloodFill import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.Lasso import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.OutlinedOval import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.OutlinedRect import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.Oval import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.Polygon import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.Rect import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.Spray import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.Star import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode.Triangle sealed class DrawPathMode( val ordinal: Int ) { sealed class Outlined(ordinal: Int) : DrawPathMode(ordinal) { abstract val fillColor: ColorModel? } data object Free : DrawPathMode(0) data object Line : DrawPathMode(1) data class PointingArrow( val sizeScale: Float = 3f, val angle: Float = 150f ) : DrawPathMode(2) data class DoublePointingArrow( val sizeScale: Float = 3f, val angle: Float = 150f ) : DrawPathMode(3) data class LinePointingArrow( val sizeScale: Float = 3f, val angle: Float = 150f ) : DrawPathMode(4) data class DoubleLinePointingArrow( val sizeScale: Float = 3f, val angle: Float = 150f ) : DrawPathMode(5) data object Lasso : DrawPathMode(6) data class OutlinedRect( val rotationDegrees: Int = 0, val cornerRadius: Float = 0f, override val fillColor: ColorModel? = null ) : Outlined(7) data class OutlinedOval( override val fillColor: ColorModel? = null ) : Outlined(8) data class Rect( val rotationDegrees: Int = 0, val cornerRadius: Float = 0f ) : DrawPathMode(9) data object Oval : DrawPathMode(10) data object Triangle : DrawPathMode(11) data class OutlinedTriangle( override val fillColor: ColorModel? = null ) : Outlined(12) data class Polygon( val vertices: Int = 5, val rotationDegrees: Int = 0, val isRegular: Boolean = false ) : DrawPathMode(13) data class OutlinedPolygon( val vertices: Int = 5, val rotationDegrees: Int = 0, val isRegular: Boolean = false, override val fillColor: ColorModel? = null ) : Outlined(14) data class Star( val vertices: Int = 5, val rotationDegrees: Int = 0, val innerRadiusRatio: Float = 0.5f, val isRegular: Boolean = false ) : DrawPathMode(15) data class OutlinedStar( val vertices: Int = 5, val rotationDegrees: Int = 0, val innerRadiusRatio: Float = 0.5f, val isRegular: Boolean = false, override val fillColor: ColorModel? = null ) : Outlined(16) data class FloodFill( val tolerance: Float = 0.25f ) : DrawPathMode(17) { companion object { val StrokeSize = 2f.pt } } data class Spray( val density: Int = 50, val pixelSize: Float = 1f, val isSquareShaped: Boolean = false ) : DrawPathMode(18) val canChangeStrokeWidth: Boolean get() = this !is FloodFill && (!isFilled || this is Spray) val isFilled: Boolean get() = filled.any { this::class.isInstance(it) } val outlinedFillColor: ColorModel? get() = this.safeCast()?.fillColor val isSharpEdge: Boolean get() = sharp.any { this::class.isInstance(it) } companion object { val entries by lazy { listOf( Free, FloodFill(), Spray(), Line, PointingArrow(), DoublePointingArrow(), LinePointingArrow(), DoubleLinePointingArrow(), Lasso, OutlinedRect(), OutlinedOval(), OutlinedTriangle(), OutlinedPolygon(), OutlinedStar(), Rect(), Oval, Triangle, Polygon(), Star() ) } val outlinedEntries by lazy { listOf( OutlinedRect(), OutlinedOval(), OutlinedTriangle(), OutlinedPolygon(), OutlinedStar() ) } fun fromOrdinal( ordinal: Int ): DrawPathMode = entries.find { it.ordinal == ordinal } ?: Free } fun convertStrokeWidth( strokeWidth: Pt, canvasSize: IntegerSize ): Float = when (this) { is FloodFill -> FloodFill.StrokeSize.toPx(canvasSize) else -> strokeWidth.toPx(canvasSize) } } private val filled = listOf( Lasso, Rect(), Oval, Triangle, Polygon(), Star(), Spray() ) private val sharp = listOf( OutlinedRect(), OutlinedOval(), Rect(), Oval, Lasso, FloodFill(), Spray() ) ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/ImageDrawApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain interface ImageDrawApplier { suspend fun applyDrawToImage( drawBehavior: DrawBehavior, pathPaints: List>, imageUri: String ): Image? suspend fun applyEraseToImage( pathPaints: List>, imageUri: String ): Image? suspend fun applyEraseToImage( pathPaints: List>, image: Image?, shaderSourceUri: String ): Image? } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/PathPaint.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt interface PathPaint { operator fun component1() = path operator fun component2() = strokeWidth operator fun component3() = brushSoftness operator fun component4() = drawColor operator fun component5() = isErasing operator fun component6() = drawMode operator fun component7() = canvasSize operator fun component8() = drawPathMode operator fun component9() = drawLineStyle val path: Path val strokeWidth: Pt val brushSoftness: Pt val drawColor: Color val isErasing: Boolean val drawMode: DrawMode val canvasSize: IntegerSize val drawPathMode: DrawPathMode val drawLineStyle: DrawLineStyle } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/domain/Warp.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.domain import com.t8rin.imagetoolbox.core.domain.model.IntegerSize enum class WarpMode { MOVE, GROW, SHRINK, SWIRL_CW, SWIRL_CCW, MIXING } data class WarpStroke( val fromX: Float, val fromY: Float, val toX: Float, val toY: Float ) { fun scaleToFitCanvas( currentSize: IntegerSize, oldSize: IntegerSize ): WarpStroke { val sx = currentSize.width.toFloat() / oldSize.width val sy = currentSize.height.toFloat() / oldSize.height return copy( fromX = fromX * sx, fromY = fromY * sy, toX = toX * sx, toY = toY * sy ) } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/DrawContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation import android.graphics.Bitmap import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.Icon import androidx.compose.material3.SheetValue import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.core.domain.model.coerceIn import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberAppColorTuple import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveBottomScaffoldLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.other.DrawLockScreenOrientation import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.saver.PtSaver import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.draw.domain.DrawBehavior import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.BitmapDrawer import com.t8rin.imagetoolbox.feature.draw.presentation.components.controls.DrawContentControls import com.t8rin.imagetoolbox.feature.draw.presentation.components.controls.DrawContentNoDataControls import com.t8rin.imagetoolbox.feature.draw.presentation.components.controls.DrawContentSecondaryControls import com.t8rin.imagetoolbox.feature.draw.presentation.screenLogic.DrawComponent import kotlinx.coroutines.launch @Composable fun DrawContent( component: DrawComponent, ) { val settingsState = LocalSettingsState.current val themeState = LocalDynamicThemeState.current val appColorTuple = rememberAppColorTuple() val scope = rememberCoroutineScope() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { when (component.drawBehavior) { !is DrawBehavior.None if component.haveChanges -> showExitDialog = true !is DrawBehavior.None -> { component.resetDrawBehavior() themeState.updateColorTuple(appColorTuple) } else -> component.onGoBack() } } AutoContentBasedColors(component.bitmap) val imagePicker = rememberImagePicker { uri: Uri -> component.setUri(uri) } val pickImage = imagePicker::pickImage val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } val screenSize = LocalScreenSize.current val isPortrait by isPortraitOrientationAsState() var panEnabled by rememberSaveable(component.drawBehavior) { mutableStateOf(false) } var strokeWidth by rememberSaveable( component.drawBehavior, stateSaver = PtSaver ) { mutableStateOf(settingsState.defaultDrawLineWidth.pt) } var drawColor by rememberSaveable( component.drawBehavior, stateSaver = ColorSaver ) { mutableStateOf(settingsState.defaultDrawColor) } var isEraserOn by rememberSaveable(component.drawBehavior) { mutableStateOf(false) } val drawMode = component.drawMode var alpha by rememberSaveable(component.drawBehavior, drawMode) { mutableFloatStateOf(if (drawMode is DrawMode.Highlighter) 0.4f else 1f) } var brushSoftness by rememberSaveable(component.drawBehavior, drawMode, stateSaver = PtSaver) { mutableStateOf(if (drawMode is DrawMode.Neon) 35.pt else 0.pt) } val drawPathMode = component.drawPathMode val drawLineStyle = component.drawLineStyle LaunchedEffect(drawMode, strokeWidth) { strokeWidth = if (drawMode is DrawMode.Image) { strokeWidth.coerceIn(10.pt, 120.pt) } else { strokeWidth.coerceIn(1.pt, 100.pt) } } val secondaryControls = @Composable { DrawContentSecondaryControls( component = component, panEnabled = panEnabled, onTogglePanEnabled = { panEnabled = !panEnabled }, isEraserOn = isEraserOn, onToggleIsEraserOn = { isEraserOn = !isEraserOn } ) } val bitmap = component.bitmap ?: (component.drawBehavior as? DrawBehavior.Background)?.run { remember { ImageBitmap(width, height).asAndroidBitmap() } } ?: remember { ImageBitmap( screenSize.widthPx, screenSize.heightPx ).asAndroidBitmap() } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } AdaptiveBottomScaffoldLayoutScreen( title = { TopAppBarTitle( title = stringResource(R.string.draw), input = component.drawBehavior.takeIf { it !is DrawBehavior.None }, isLoading = component.isImageLoading, size = null, originalSize = null ) }, onGoBack = onBack, shouldDisableBackHandler = component.drawBehavior is DrawBehavior.None, actions = { secondaryControls() }, topAppBarPersistentActions = { scaffoldState -> if (component.drawBehavior == DrawBehavior.None) TopAppBarEmoji() else { if (isPortrait) { EnhancedIconButton( onClick = { scope.launch { if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) { scaffoldState.bottomSheetState.partialExpand() } else { scaffoldState.bottomSheetState.expand() } } }, ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = stringResource(R.string.properties) ) } } var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.drawBehavior !is DrawBehavior.None, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { uri -> editSheetData = listOf(uri) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) EnhancedIconButton( onClick = component::clearDrawing, enabled = component.drawBehavior !is DrawBehavior.None && component.havePaths ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete) ) } } }, mainContent = { AnimatedContent( targetState = remember(bitmap) { derivedStateOf { bitmap.copy(Bitmap.Config.ARGB_8888, true).asImageBitmap() } }.value, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { imageBitmap -> val direction = LocalLayoutDirection.current val aspectRatio = imageBitmap.width / imageBitmap.height.toFloat() BitmapDrawer( imageBitmap = imageBitmap, paths = component.paths, strokeWidth = strokeWidth, brushSoftness = brushSoftness, drawColor = drawColor.copy(alpha), onAddPath = component::addPath, isEraserOn = isEraserOn, drawMode = drawMode, modifier = Modifier .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ) .padding(16.dp) .aspectRatio(aspectRatio, isPortrait) .fillMaxSize(), panEnabled = panEnabled, onRequestFiltering = component::filter, drawPathMode = drawPathMode, backgroundColor = component.backgroundColor, drawLineStyle = drawLineStyle, helperGridParams = component.helperGridParams ) } }, controls = { DrawContentControls( component = component, secondaryControls = secondaryControls, drawColor = drawColor, onDrawColorChange = { drawColor = it }, strokeWidth = strokeWidth, onStrokeWidthChange = { strokeWidth = it }, brushSoftness = brushSoftness, onBrushSoftnessChange = { brushSoftness = it }, alpha = alpha, onAlphaChange = { alpha = it } ) }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.drawBehavior is DrawBehavior.None, onSecondaryButtonClick = pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, isSecondaryButtonVisible = component.drawBehavior !is DrawBehavior.Background, onPrimaryButtonClick = { saveBitmap(null) }, isPrimaryButtonVisible = component.drawBehavior !is DrawBehavior.None, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() }, showNullDataButtonAsContainer = true ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, enableNoDataScroll = false, noDataControls = { DrawContentNoDataControls( component = component, onPickImage = pickImage ) }, canShowScreenData = component.drawBehavior !is DrawBehavior.None, showActionsInTopAppBar = false, mainContentWeight = 0.65f ) LoadingDialog( visible = component.isSaving || component.isImageLoading, onCancelLoading = component::cancelSaving, canCancel = component.isSaving ) ExitWithoutSavingDialog( onExit = { if (component.drawBehavior !is DrawBehavior.None) { component.resetDrawBehavior() themeState.updateColorTuple(appColorTuple) } else component.onGoBack() }, onDismiss = { showExitDialog = false }, visible = showExitDialog ) DrawLockScreenOrientation() } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BitmapDrawer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.feature.draw.presentation.components import android.annotation.SuppressLint import android.graphics.Bitmap import android.graphics.PorterDuff import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.takeOrElse import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathMeasure import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.createScaledBitmap import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.domain.WarpStroke import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.BitmapDrawerPreview import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.DrawPathEffectPreview import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.MotionEvent import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.copy import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.drawRepeatedImageOnPath import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.drawRepeatedTextOnPath import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.floodFill import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.handle import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.overlay import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.pointerDrawObserver import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.rememberPaint import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.rememberPathHelper import com.t8rin.trickle.WarpBrush import com.t8rin.trickle.WarpEngine import com.t8rin.trickle.WarpMode import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch import net.engawapg.lib.zoomable.ZoomState import net.engawapg.lib.zoomable.rememberZoomState import kotlin.math.roundToInt import android.graphics.Canvas as AndroidCanvas import android.graphics.Paint as AndroidPaint @SuppressLint("CoroutineCreationDuringComposition") @Composable fun BitmapDrawer( imageBitmap: ImageBitmap, onRequestFiltering: suspend (Bitmap, List>) -> Bitmap?, paths: List, brushSoftness: Pt, zoomState: ZoomState = rememberZoomState(maxScale = 30f), onAddPath: (UiPathPaint) -> Unit, strokeWidth: Pt, isEraserOn: Boolean, drawMode: DrawMode, modifier: Modifier, drawPathMode: DrawPathMode = DrawPathMode.Free, onDrawStart: (() -> Unit)? = null, onDraw: ((Bitmap) -> Unit)? = null, onDrawFinish: (() -> Unit)? = null, backgroundColor: Color, panEnabled: Boolean, drawColor: Color, drawLineStyle: DrawLineStyle = DrawLineStyle.None, helperGridParams: HelperGridParams = remember { HelperGridParams() }, ) { val scope = rememberCoroutineScope() val settingsState = LocalSettingsState.current val magnifierEnabled by remember(zoomState.scale, settingsState.magnifierEnabled) { derivedStateOf { zoomState.scale <= 3f && !panEnabled && settingsState.magnifierEnabled } } val globalTouchPointersCount = remember { mutableIntStateOf(0) } var currentDrawPosition by remember { mutableStateOf(Offset.Unspecified) } Box( modifier = Modifier.pointerDrawObserver( magnifierEnabled = magnifierEnabled, currentDrawPosition = currentDrawPosition, zoomState = zoomState, globalTouchPointersCount = globalTouchPointersCount, panEnabled = panEnabled ), contentAlignment = Alignment.Center ) { BoxWithConstraints(modifier) { val motionEvent = remember { mutableStateOf(MotionEvent.Idle) } var previousDrawPosition by remember { mutableStateOf(Offset.Unspecified) } var drawDownPosition by remember { mutableStateOf(Offset.Unspecified) } val imageWidth = constraints.maxWidth val imageHeight = constraints.maxHeight val drawImageBitmap by remember(imageWidth, imageHeight, backgroundColor) { derivedStateOf { imageBitmap.asAndroidBitmap().createScaledBitmap( width = imageWidth, height = imageHeight ).apply { val canvas = AndroidCanvas(this) val paint = android.graphics.Paint().apply { color = backgroundColor.toArgb() } canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint) }.asImageBitmap() } } val drawBitmap: ImageBitmap by remember(imageWidth, imageHeight) { derivedStateOf { createBitmap(imageWidth, imageHeight).asImageBitmap() } } var invalidations by remember { mutableIntStateOf(0) } val drawPathBitmap: ImageBitmap by remember(imageWidth, imageHeight, invalidations) { derivedStateOf { createBitmap(imageWidth, imageHeight).asImageBitmap() } } LaunchedEffect( paths, drawMode, backgroundColor, drawPathMode, imageWidth, imageHeight ) { invalidations++ } val outputImage by remember(invalidations) { derivedStateOf { drawImageBitmap.overlay(drawBitmap) } } val canvas: Canvas by remember(drawBitmap, imageHeight, imageWidth) { derivedStateOf { Canvas(drawBitmap) } } val drawPathCanvas: Canvas by remember(drawPathBitmap, imageWidth, imageHeight) { derivedStateOf { Canvas(drawPathBitmap) } } val canvasSize by remember(canvas.nativeCanvas) { derivedStateOf { IntegerSize( width = canvas.nativeCanvas.width, height = canvas.nativeCanvas.height ) } } val drawPaint by rememberPaint( strokeWidth = strokeWidth, isEraserOn = isEraserOn, drawColor = drawColor, brushSoftness = brushSoftness, drawMode = drawMode, canvasSize = canvasSize, drawPathMode = drawPathMode, drawLineStyle = drawLineStyle ) var drawPath by remember( drawMode, strokeWidth, isEraserOn, drawColor, brushSoftness, drawPathMode ) { mutableStateOf(Path()) } var pathWithoutTransformations by remember( drawMode, strokeWidth, isEraserOn, drawColor, brushSoftness, drawPathMode ) { mutableStateOf(Path()) } var warpRuntimeStrokes by remember(drawMode) { mutableStateOf(emptyList()) } var warpClearTrigger by remember { mutableIntStateOf(0) } var warpPreviewToken by remember { mutableLongStateOf(0L) } var pendingWarpCommitToken by remember { mutableLongStateOf(-1L) } var previousPathsCount by remember { mutableIntStateOf(paths.size) } LaunchedEffect(paths.size) { if (paths.isEmpty() || paths.size < previousPathsCount) { warpClearTrigger++ pendingWarpCommitToken = -1L } previousPathsCount = paths.size } LaunchedEffect(drawMode, isEraserOn) { if (drawMode !is DrawMode.Warp || isEraserOn) { pendingWarpCommitToken = -1L } } val isWarpInputLocked by remember(drawMode, isEraserOn, pendingWarpCommitToken) { derivedStateOf { drawMode is DrawMode.Warp && !isEraserOn && pendingWarpCommitToken >= 0L } } with(canvas) { with(nativeCanvas) { drawColor(Color.Transparent.toArgb(), PorterDuff.Mode.CLEAR) drawColor(backgroundColor.toArgb()) paths.forEach { uiPathPaint -> UiPathPaintCanvasAction( uiPathPaint = uiPathPaint, invalidations = invalidations, onInvalidate = { invalidations++ }, pathsCount = paths.size, backgroundColor = backgroundColor, drawImageBitmap = drawImageBitmap, drawBitmap = drawBitmap, onClearDrawPath = { drawPath = Path() warpClearTrigger++ warpRuntimeStrokes = emptyList() }, onClearWarpDrawPath = { token -> if (token == pendingWarpCommitToken) { drawPath = Path() warpClearTrigger++ warpRuntimeStrokes = emptyList() pendingWarpCommitToken = -1L } }, onRequestFiltering = onRequestFiltering, canvasSize = canvasSize ) } if ((drawMode !is DrawMode.PathEffect && drawMode !is DrawMode.Warp) || isEraserOn) { val androidPath by remember(drawPath) { derivedStateOf { drawPath.asAndroidPath() } } if (drawMode is DrawMode.Text && !isEraserOn) { if (drawMode.isRepeated) { drawRepeatedTextOnPath( text = drawMode.text, path = androidPath, paint = drawPaint, interval = drawMode.repeatingInterval.toPx(canvasSize) ) } else { drawTextOnPath(drawMode.text, androidPath, 0f, 0f, drawPaint) } } else if (drawMode is DrawMode.Image && !isEraserOn) { drawRepeatedImageOnPath( drawMode = drawMode, strokeWidth = strokeWidth, canvasSize = canvasSize, path = androidPath, paint = drawPaint, invalidations = invalidations ) } else if (drawMode is DrawMode.SpotHeal && !isEraserOn) { with(drawPathCanvas.nativeCanvas) { drawColor(Color.Transparent.toArgb(), PorterDuff.Mode.CLEAR) drawPath( androidPath, drawPaint.apply { color = Color.Red.copy(0.5f).toArgb() } ) } } else if (drawPathMode is DrawPathMode.Outlined) { drawPathMode.fillColor?.let { fillColor -> val filledPaint = remember(fillColor, drawPaint) { AndroidPaint().apply { set(drawPaint) style = AndroidPaint.Style.FILL color = fillColor.colorInt if (Color(fillColor.colorInt).alpha == 1f) { alpha = (drawColor.alpha * 255).roundToInt() .coerceIn(0, 255) } pathEffect = null } } drawPath(androidPath, filledPaint) } drawPath(androidPath, drawPaint) } else { drawPath(androidPath, drawPaint) } } } val drawHelper by rememberPathHelper( drawDownPosition = drawDownPosition, currentDrawPosition = currentDrawPosition, onPathChange = { drawPath = it }, strokeWidth = strokeWidth, canvasSize = canvasSize, drawPathMode = drawPathMode, isEraserOn = isEraserOn, drawMode = drawMode ) motionEvent.value.handle( onDown = { if (drawMode is DrawMode.Warp && !isEraserOn) { warpPreviewToken++ warpRuntimeStrokes = emptyList() drawPath = Path() pathWithoutTransformations = Path() } else { warpClearTrigger++ warpRuntimeStrokes = emptyList() } if (currentDrawPosition.isSpecified) { onDrawStart?.invoke() drawPath.moveTo(currentDrawPosition.x, currentDrawPosition.y) previousDrawPosition = currentDrawPosition pathWithoutTransformations = drawPath.copy() } else { drawPath = Path() pathWithoutTransformations = Path() } motionEvent.value = MotionEvent.Idle }, onMove = { if (drawMode is DrawMode.Warp && !isEraserOn) { if ( previousDrawPosition.isSpecified && currentDrawPosition.isSpecified ) { warpRuntimeStrokes += WarpStroke( fromX = previousDrawPosition.x, fromY = previousDrawPosition.y, toX = currentDrawPosition.x, toY = currentDrawPosition.y ) } } drawHelper.drawPath( currentDrawPath = drawPath, onDrawFreeArrow = { if (previousDrawPosition.isUnspecified && currentDrawPosition.isSpecified) { drawPath = Path().apply { moveTo( currentDrawPosition.x, currentDrawPosition.y ) } pathWithoutTransformations = drawPath.copy() previousDrawPosition = currentDrawPosition } if (previousDrawPosition.isSpecified && currentDrawPosition.isSpecified) { drawPath = pathWithoutTransformations drawPath.quadraticTo( previousDrawPosition.x, previousDrawPosition.y, (previousDrawPosition.x + currentDrawPosition.x) / 2, (previousDrawPosition.y + currentDrawPosition.y) / 2 ) previousDrawPosition = currentDrawPosition pathWithoutTransformations = drawPath.copy() drawArrowsIfNeeded(drawPath) } }, onBaseDraw = { if (previousDrawPosition.isUnspecified && currentDrawPosition.isSpecified) { drawPath.moveTo(currentDrawPosition.x, currentDrawPosition.y) previousDrawPosition = currentDrawPosition } if (currentDrawPosition.isSpecified && previousDrawPosition.isSpecified) { drawPath.quadraticTo( previousDrawPosition.x, previousDrawPosition.y, (previousDrawPosition.x + currentDrawPosition.x) / 2, (previousDrawPosition.y + currentDrawPosition.y) / 2 ) } previousDrawPosition = currentDrawPosition }, ) motionEvent.value = MotionEvent.Idle }, onUp = { if (currentDrawPosition.isSpecified && drawDownPosition.isSpecified) { if (drawMode is DrawMode.Warp && warpRuntimeStrokes.isNotEmpty() && !isEraserOn) { PathMeasure().apply { setPath(drawPath, false) }.let { it.getPosition(it.length) }.takeOrElse { currentDrawPosition }.let { lastPoint -> warpRuntimeStrokes += WarpStroke( fromX = lastPoint.x, fromY = lastPoint.y, toX = currentDrawPosition.x, toY = currentDrawPosition.y ) } onAddPath( UiPathPaint( path = drawPath, strokeWidth = strokeWidth, brushSoftness = 0.pt, drawColor = Color.Transparent, isErasing = false, drawMode = drawMode.copy( strokes = warpRuntimeStrokes.toList(), previewClearToken = warpPreviewToken ), canvasSize = canvasSize, drawPathMode = DrawPathMode.Free, drawLineStyle = DrawLineStyle.None ) ) pendingWarpCommitToken = warpPreviewToken } else { drawHelper.drawPath( currentDrawPath = null, onDrawFreeArrow = { drawPath = pathWithoutTransformations PathMeasure().apply { setPath(drawPath, false) }.let { it.getPosition(it.length) }.let { lastPoint -> if (!lastPoint.isSpecified) { drawPath.moveTo( currentDrawPosition.x, currentDrawPosition.y ) } drawPath.lineTo( currentDrawPosition.x, currentDrawPosition.y ) } drawArrowsIfNeeded(drawPath) }, onBaseDraw = { PathMeasure().apply { setPath(drawPath, false) }.let { it.getPosition(it.length) }.takeOrElse { currentDrawPosition }.let { lastPoint -> drawPath.moveTo(lastPoint.x, lastPoint.y) drawPath.lineTo( currentDrawPosition.x, currentDrawPosition.y ) } }, onFloodFill = { tolerance -> outputImage .floodFill( offset = currentDrawPosition, tolerance = tolerance ) ?.let { drawPath = it } } ) onAddPath( UiPathPaint( path = drawPath, strokeWidth = strokeWidth, brushSoftness = brushSoftness, drawColor = drawColor, isErasing = isEraserOn, drawMode = drawMode, canvasSize = canvasSize, drawPathMode = drawPathMode, drawLineStyle = drawLineStyle ) ) } } currentDrawPosition = Offset.Unspecified previousDrawPosition = Offset.Unspecified motionEvent.value = MotionEvent.Idle scope.launch { if ((drawMode is DrawMode.PathEffect || drawMode is DrawMode.SpotHeal || drawMode is DrawMode.Warp) && !isEraserOn) Unit else drawPath = Path() pathWithoutTransformations = Path() } onDrawFinish?.invoke() } ) } if (drawMode is DrawMode.PathEffect && !isEraserOn) { DrawPathEffectPreview( drawPathCanvas = drawPathCanvas, drawMode = drawMode, canvasSize = canvasSize, imageWidth = imageWidth, imageHeight = imageHeight, outputImage = outputImage, onRequestFiltering = onRequestFiltering, paths = paths, drawPath = drawPath, backgroundColor = backgroundColor, strokeWidth = strokeWidth, drawPathMode = drawPathMode ) } var warpEngine by remember { mutableStateOf( WarpEngine( src = outputImage.asAndroidBitmap() ) ) } LaunchedEffect(warpClearTrigger, drawMode) { warpEngine.release() warpEngine = WarpEngine( src = outputImage.asAndroidBitmap() ) } LaunchedEffect(warpEngine) { snapshotFlow { warpRuntimeStrokes.lastOrNull() } .filterNotNull() .collect { if (drawMode is DrawMode.Warp) { warpEngine.applyStroke( fromX = it.fromX, fromY = it.fromY, toX = it.toX, toY = it.toY, brush = WarpBrush( radius = strokeWidth.toPx(canvasSize), strength = drawMode.strength, hardness = drawMode.hardness ), mode = WarpMode.valueOf(drawMode.warpMode.name) ) invalidations++ } } } val warpedImage by remember(invalidations, warpEngine) { derivedStateOf { if (warpRuntimeStrokes.isNotEmpty()) { warpEngine.render().asImageBitmap().also { it.prepareToDraw() } } else { outputImage.overlay(drawPathBitmap) } } } val previewBitmap by remember(invalidations) { derivedStateOf { if (drawMode is DrawMode.Warp) { warpedImage } else { outputImage.overlay(drawPathBitmap) } } } onDraw?.let { LaunchedEffect(invalidations) { onDraw(previewBitmap.asAndroidBitmap()) } } BitmapDrawerPreview( preview = previewBitmap, globalTouchPointersCount = globalTouchPointersCount, onReceiveMotionEvent = { motionEvent.value = it }, onInvalidate = { invalidations++ }, onUpdateCurrentDrawPosition = { currentDrawPosition = it }, onUpdateDrawDownPosition = { drawDownPosition = it }, drawEnabled = !panEnabled && !isWarpInputLocked, helperGridParams = helperGridParams ) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/BrushSoftnessSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Dots import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun BrushSoftnessSelector( modifier: Modifier, value: Float, color: Color = Color.Unspecified, onValueChange: (Float) -> Unit ) { EnhancedSliderItem( modifier = modifier, value = value, title = stringResource(R.string.brush_softness), valueRange = 0f..100f, onValueChange = { onValueChange(it.roundToTwoDigits()) }, valueSuffix = " Pt", sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.Dots, shape = ShapeDefaults.extraLarge, containerColor = color ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawColorSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrushColor import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun DrawColorSelector( modifier: Modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), value: Color, onValueChange: (Color) -> Unit, color: Color = Color.Unspecified, titleText: String = stringResource(R.string.paint_color), defaultColors: List = ColorSelectionRowDefaults.colorList, ) { ColorRowSelector( value = value, onValueChange = onValueChange, modifier = modifier .container( shape = ShapeDefaults.extraLarge, color = color ), title = titleText, allowAlpha = false, icon = Icons.Outlined.BrushColor, defaultColors = defaultColors ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawLineStyleSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BoldLine import com.t8rin.imagetoolbox.core.resources.icons.DashedLine import com.t8rin.imagetoolbox.core.resources.icons.DotDashedLine import com.t8rin.imagetoolbox.core.resources.icons.StampedLine import com.t8rin.imagetoolbox.core.resources.icons.ZigzagLine import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.presentation.model.IconShape import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle @Composable fun DrawLineStyleSelector( modifier: Modifier, value: DrawLineStyle, onValueChange: (DrawLineStyle) -> Unit, values: List = DrawLineStyle.entries ) { var isSheetVisible by rememberSaveable { mutableStateOf(false) } LaunchedEffect(values, value) { if (values.filterIsInstance(value::class.java).isEmpty() && values.isNotEmpty()) { onValueChange(values.first()) } } Column( modifier = modifier .container(ShapeDefaults.extraLarge), horizontalAlignment = Alignment.CenterHorizontally ) { EnhancedButtonGroup( enabled = true, itemCount = values.size, title = { Text( text = stringResource(R.string.line_style), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.width(8.dp)) SupportingButton( onClick = { isSheetVisible = true } ) }, selectedIndex = values.indexOfFirst { value::class.isInstance(it) }, itemContent = { Icon( imageVector = values[it].getIcon(), contentDescription = null ) }, onIndexChange = { onValueChange(values[it]) }, activeButtonColor = MaterialTheme.colorScheme.secondaryContainer ) var lineStyle by remember { mutableStateOf(value) } AnimatedVisibility( visible = value is DrawLineStyle.Dashed, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { SideEffect { if (value is DrawLineStyle.Dashed) { lineStyle = value } } DisposableEffect(Unit) { onDispose { lineStyle = null } } val style = lineStyle as? DrawLineStyle.Dashed ?: return@AnimatedVisibility Column { EnhancedSliderItem( value = style.size.value, title = stringResource(R.string.dash_size), valueRange = 0f..100f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( style.copy( size = it.pt ) ) }, valueSuffix = " Pt", containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = style.gap.value, title = stringResource(R.string.gap_size), valueRange = 0f..100f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( style.copy( gap = it.pt ) ) }, valueSuffix = " Pt", containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.bottom ) Spacer(modifier = Modifier.height(8.dp)) } } AnimatedVisibility( visible = value is DrawLineStyle.ZigZag, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { SideEffect { if (value is DrawLineStyle.ZigZag) { lineStyle = value } } DisposableEffect(Unit) { onDispose { lineStyle = null } } val style = lineStyle as? DrawLineStyle.ZigZag ?: return@AnimatedVisibility Column { var ratio by remember { mutableFloatStateOf(style.heightRatio) } EnhancedSliderItem( value = ratio, title = stringResource(R.string.zigzag_ratio), valueRange = 0.5f..20f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { ratio = it onValueChange( style.copy( heightRatio = 20.5f - it ) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.default ) Spacer(modifier = Modifier.height(8.dp)) } } AnimatedVisibility( visible = value is DrawLineStyle.Stamped<*>, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { SideEffect { if (value is DrawLineStyle.Stamped<*>) { lineStyle = value } } DisposableEffect(Unit) { onDispose { lineStyle = null } } val style = lineStyle as? DrawLineStyle.Stamped<*> ?: return@AnimatedVisibility val shape = if (style.shape is Shape) style.shape else MaterialStarShape Column { val shapes = IconShape.entriesNoRandom Column( modifier = Modifier .padding(horizontal = 8.dp) .container( shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp ) ) { val state = rememberLazyGridState() LazyHorizontalGrid( state = state, rows = GridCells.Adaptive(48.dp), modifier = Modifier .fillMaxWidth() .height(240.dp) .fadingEdges( scrollableState = state, spanCount = 4, length = 32.dp ), verticalArrangement = Arrangement.spacedBy( space = 6.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 6.dp, alignment = Alignment.CenterHorizontally ), contentPadding = PaddingValues(8.dp), flingBehavior = enhancedFlingBehavior() ) { items(shapes) { iconShape -> val selected by remember(iconShape, shape) { derivedStateOf { iconShape.shape == shape } } val color by animateColorAsState( if (selected) MaterialTheme.colorScheme.primaryContainer else Color.Unspecified ) val borderColor by animateColorAsState( if (selected) { MaterialTheme.colorScheme.onPrimaryContainer.copy(0.7f) } else MaterialTheme.colorScheme.onSecondaryContainer.copy( alpha = 0.1f ) ) Box( modifier = Modifier .aspectRatio(1f) .container( color = color, shape = CloverShape, borderColor = borderColor, resultPadding = 0.dp ) .hapticsClickable { onValueChange( DrawLineStyle.Stamped( shape = iconShape.shape, spacing = style.spacing ) ) }, contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(30.dp) .container( borderWidth = 2.dp, borderColor = MaterialTheme.colorScheme.onSurfaceVariant, color = Color.Transparent, shape = iconShape.shape ) ) } } } } Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = style.spacing.value, title = stringResource(R.string.spacing), valueRange = 0f..100f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( style.copy( spacing = it.pt ) ) }, valueSuffix = " Pt", containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.bottom ) Spacer(modifier = Modifier.height(8.dp)) } } } EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { values.forEachIndexed { index, item -> Column( Modifier .fillMaxWidth() .container( shape = ShapeDefaults.byIndex( index = index, size = values.size ), resultPadding = 0.dp ) ) { TitleItem( text = stringResource(item.getTitle()), icon = item.getIcon() ) Text( text = stringResource(item.getSubtitle()), modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 16.dp ), fontSize = 14.sp, lineHeight = 18.sp ) } } } }, visible = isSheetVisible, onDismiss = { isSheetVisible = it }, title = { TitleItem(text = stringResource(R.string.draw_mode)) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { isSheetVisible = false } ) { AutoSizeText(stringResource(R.string.close)) } } ) } private fun DrawLineStyle.getSubtitle(): Int = when (this) { is DrawLineStyle.Dashed -> R.string.dashed_sub DrawLineStyle.DotDashed -> R.string.dot_dashed_sub DrawLineStyle.None -> R.string.defaultt_sub is DrawLineStyle.Stamped<*> -> R.string.stamped_sub is DrawLineStyle.ZigZag -> R.string.zigzag_sub } private fun DrawLineStyle.getTitle(): Int = when (this) { is DrawLineStyle.Dashed -> R.string.dashed DrawLineStyle.DotDashed -> R.string.dot_dashed DrawLineStyle.None -> R.string.defaultt is DrawLineStyle.Stamped<*> -> R.string.stamped is DrawLineStyle.ZigZag -> R.string.zigzag } private fun DrawLineStyle.getIcon(): ImageVector = when (this) { is DrawLineStyle.Dashed -> Icons.Rounded.DashedLine DrawLineStyle.DotDashed -> Icons.Rounded.DotDashedLine DrawLineStyle.None -> Icons.Rounded.BoldLine is DrawLineStyle.Stamped<*> -> Icons.Rounded.StampedLine is DrawLineStyle.ZigZag -> Icons.Rounded.ZigzagLine } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AutoFixHigh import androidx.compose.material.icons.outlined.Healing import androidx.compose.material.icons.outlined.Image import androidx.compose.material.icons.rounded.BlurCircular import androidx.compose.material.icons.rounded.TextFormat import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Cube import com.t8rin.imagetoolbox.core.resources.icons.Highlighter import com.t8rin.imagetoolbox.core.resources.icons.MeshGradient import com.t8rin.imagetoolbox.core.resources.icons.NeonBrush import com.t8rin.imagetoolbox.core.resources.icons.Pen import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.CustomPathEffectParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.ImageParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.PixelationParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.PrivacyBlurParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.SpotHealParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.TextParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.WarpParamsSelector @Composable fun DrawModeSelector( addFiltersSheetComponent: AddFiltersSheetComponent, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent, modifier: Modifier, value: DrawMode, strokeWidth: Pt, onValueChange: (DrawMode) -> Unit, values: List = DrawMode.entries ) { var isSheetVisible by rememberSaveable { mutableStateOf(false) } LaunchedEffect(values, value) { if (values.find { it::class.isInstance(value) } == null) { values.firstOrNull()?.let { onValueChange(it) } } } Column( modifier = modifier .container(ShapeDefaults.extraLarge), horizontalAlignment = Alignment.CenterHorizontally ) { EnhancedButtonGroup( enabled = true, itemCount = values.size, title = { Text( text = stringResource(R.string.draw_mode), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.width(8.dp)) SupportingButton( onClick = { isSheetVisible = true } ) }, selectedIndex = values.indexOfFirst { value::class.isInstance(it) }, itemContent = { Icon( imageVector = values[it].getIcon(), contentDescription = null ) }, onIndexChange = { onValueChange(values[it]) } ) WarpParamsSelector( value = value, onValueChange = onValueChange ) SpotHealParamsSelector( value = value, onValueChange = onValueChange ) PrivacyBlurParamsSelector( value = value, onValueChange = onValueChange ) PixelationParamsSelector( value = value, onValueChange = onValueChange ) TextParamsSelector( value = value, onValueChange = onValueChange ) ImageParamsSelector( value = value, onValueChange = onValueChange, strokeWidth = strokeWidth ) CustomPathEffectParamsSelector( value = value, onValueChange = onValueChange, addFiltersSheetComponent = addFiltersSheetComponent, filterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent ) } EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { values.forEachIndexed { index, item -> Column( Modifier .fillMaxWidth() .container( shape = ShapeDefaults.byIndex( index = index, size = values.size ), resultPadding = 0.dp ) ) { TitleItem( text = stringResource(item.getTitle()), icon = item.getIcon() ) Text( text = stringResource(item.getSubtitle()), modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 16.dp ), fontSize = 14.sp, lineHeight = 18.sp ) } } } }, visible = isSheetVisible, onDismiss = { isSheetVisible = it }, title = { TitleItem(text = stringResource(R.string.draw_mode)) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { isSheetVisible = false } ) { AutoSizeText(stringResource(R.string.close)) } } ) } private fun DrawMode.getSubtitle(): Int = when (this) { is DrawMode.Highlighter -> R.string.highlighter_sub is DrawMode.Neon -> R.string.neon_sub is DrawMode.Pen -> R.string.pen_sub is DrawMode.PathEffect.PrivacyBlur -> R.string.privacy_blur_sub is DrawMode.PathEffect.Pixelation -> R.string.pixelation_sub is DrawMode.Text -> R.string.draw_text_sub is DrawMode.Image -> R.string.draw_mode_image_sub is DrawMode.PathEffect.Custom -> R.string.draw_filter_sub is DrawMode.SpotHeal -> R.string.spot_heal_sub is DrawMode.Warp -> R.string.warp_sub } private fun DrawMode.getTitle(): Int = when (this) { is DrawMode.Highlighter -> R.string.highlighter is DrawMode.Neon -> R.string.neon is DrawMode.Pen -> R.string.pen is DrawMode.PathEffect.PrivacyBlur -> R.string.privacy_blur is DrawMode.PathEffect.Pixelation -> R.string.pixelation is DrawMode.Text -> R.string.text is DrawMode.Image -> R.string.image is DrawMode.PathEffect.Custom -> R.string.filter is DrawMode.SpotHeal -> R.string.spot_heal is DrawMode.Warp -> R.string.warp } private fun DrawMode.getIcon(): ImageVector = when (this) { is DrawMode.Highlighter -> Icons.Outlined.Highlighter is DrawMode.Neon -> Icons.Outlined.NeonBrush is DrawMode.Pen -> Icons.Outlined.Pen is DrawMode.PathEffect.PrivacyBlur -> Icons.Rounded.BlurCircular is DrawMode.PathEffect.Pixelation -> Icons.Outlined.Cube is DrawMode.Text -> Icons.Rounded.TextFormat is DrawMode.Image -> Icons.Outlined.Image is DrawMode.PathEffect.Custom -> Icons.Outlined.AutoFixHigh is DrawMode.SpotHeal -> Icons.Outlined.Healing is DrawMode.Warp -> Icons.Outlined.MeshGradient } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/DrawPathModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.ArrowParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.DrawPathModeInfoSheet import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.FloodFillParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.OvalParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.PolygonParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.RectParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.SprayParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.StarParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.element.TriangleParamsSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.getIcon import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.saveState @Composable fun DrawPathModeSelector( modifier: Modifier, values: List = DrawPathMode.entries, value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit, drawMode: DrawMode, containerColor: Color = Color.Unspecified ) { var isSheetVisible by rememberSaveable { mutableStateOf(false) } LaunchedEffect(value, values) { if (values.find { it::class.isInstance(value) } == null) { values.firstOrNull()?.let { onValueChange(it) } } } Column( modifier = modifier .container( shape = ShapeDefaults.extraLarge, color = containerColor ), horizontalAlignment = Alignment.CenterHorizontally ) { EnhancedButtonGroup( enabled = true, itemCount = values.size, title = { Text( text = stringResource(R.string.draw_path_mode), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.width(8.dp)) SupportingButton( onClick = { isSheetVisible = true } ) }, selectedIndex = remember(values, value) { derivedStateOf { values.indexOfFirst { value::class.isInstance(it) } } }.value, activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest, itemContent = { Icon( imageVector = values[it].getIcon(), contentDescription = null ) }, onIndexChange = { onValueChange(values[it].saveState(value)) } ) val canChangeFillColor = value is DrawPathMode.Outlined && (drawMode is DrawMode.Pen || drawMode is DrawMode.Highlighter || drawMode is DrawMode.Neon) PolygonParamsSelector( value = value, onValueChange = onValueChange, canChangeFillColor = canChangeFillColor ) StarParamsSelector( value = value, onValueChange = onValueChange, canChangeFillColor = canChangeFillColor ) RectParamsSelector( value = value, onValueChange = onValueChange, canChangeFillColor = canChangeFillColor ) OvalParamsSelector( value = value, onValueChange = onValueChange, canChangeFillColor = canChangeFillColor ) TriangleParamsSelector( value = value, onValueChange = onValueChange, canChangeFillColor = canChangeFillColor ) ArrowParamsSelector( value = value, onValueChange = onValueChange ) FloodFillParamsSelector( value = value, onValueChange = onValueChange ) SprayParamsSelector( value = value, onValueChange = onValueChange ) } DrawPathModeInfoSheet( visible = isSheetVisible, onDismiss = { isSheetVisible = false }, values = values ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/LineWidthSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.LineWeight import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun LineWidthSelector( modifier: Modifier, value: Float, title: String = stringResource(R.string.line_width), valueRange: ClosedFloatingPointRange = 1f..100f, color: Color = Color.Unspecified, onValueChange: (Float) -> Unit ) { EnhancedSliderItem( modifier = modifier, value = value, containerColor = color, icon = Icons.Rounded.LineWeight, title = title, valueSuffix = " Pt", sliderModifier = Modifier .padding(top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp), valueRange = valueRange, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(it.roundToTwoDigits()) }, shape = ShapeDefaults.extraLarge ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/OpenColorPickerCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun OpenColorPickerCard( onOpen: () -> Unit, modifier: Modifier = Modifier .padding(horizontal = 16.dp) ) { Row( modifier = modifier .container( resultPadding = 0.dp, color = MaterialTheme.colorScheme.mixedContainer.copy(0.7f), shape = ShapeDefaults.extraLarge ) .hapticsClickable(onClick = onOpen) .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = stringResource(id = R.string.pipette), modifier = Modifier.weight(1f), color = MaterialTheme.colorScheme.onMixedContainer ) Icon( imageVector = Icons.Outlined.Eyedropper, contentDescription = stringResource(R.string.pipette), tint = MaterialTheme.colorScheme.onMixedContainer ) } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/PixelSizeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Cube import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun PixelSizeSelector( modifier: Modifier, value: Float, onValueChange: (Float) -> Unit, color: Color = Color.Unspecified ) { EnhancedSliderItem( modifier = modifier, value = value, title = stringResource(R.string.pixel_size), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Outlined.Cube, valueRange = 10f..75f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(it.roundToTwoDigits()) }, shape = ShapeDefaults.default, containerColor = color ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaint.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.domain.PathPaint data class UiPathPaint( override val path: Path, override val strokeWidth: Pt, override val brushSoftness: Pt, override val drawColor: Color = Color.Transparent, override val isErasing: Boolean, override val drawMode: DrawMode = DrawMode.Pen, override val canvasSize: IntegerSize, override val drawPathMode: DrawPathMode = DrawPathMode.Free, override val drawLineStyle: DrawLineStyle = DrawLineStyle.None ) : PathPaint fun PathPaint.toUiPathPaint() = UiPathPaint( path = path, strokeWidth = strokeWidth, brushSoftness = brushSoftness, drawColor = drawColor, isErasing = isErasing, drawMode = drawMode, canvasSize = canvasSize, drawPathMode = drawPathMode, drawLineStyle = drawLineStyle ) ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/UiPathPaintCanvasAction.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components import android.annotation.SuppressLint import android.graphics.Bitmap import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.PaintingStyle import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePaint import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.nativePaint import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.createFilter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.SpotHealMode import com.t8rin.imagetoolbox.core.ui.utils.helper.scaleToFitCanvas import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.utils.toImageModel import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.clipBitmap import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.drawRepeatedImageOnPath import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.drawRepeatedTextOnPath import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.overlay import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.pathEffectPaint import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.rememberPaint import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.transformationsForMode import com.t8rin.trickle.WarpBrush import com.t8rin.trickle.WarpEngine import com.t8rin.trickle.WarpMode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.math.roundToInt import android.graphics.Paint as AndroidPaint @SuppressLint("ComposableNaming") @Composable internal fun Canvas.UiPathPaintCanvasAction( uiPathPaint: UiPathPaint, invalidations: Int, onInvalidate: () -> Unit, canvasSize: IntegerSize, pathsCount: Int, backgroundColor: Color, drawImageBitmap: ImageBitmap, drawBitmap: ImageBitmap, onClearDrawPath: () -> Unit, onClearWarpDrawPath: (Long) -> Unit, onRequestFiltering: suspend (Bitmap, List>) -> Bitmap?, ) = with(nativeCanvas) { val (nonScaledPath, strokeWidth, brushSoftness, drawColor, isEraserOn, drawMode, size, drawPathMode, drawLineStyle) = uiPathPaint val path by remember(nonScaledPath, canvasSize, size) { derivedStateOf { nonScaledPath.scaleToFitCanvas( currentSize = canvasSize, oldSize = size ).asAndroidPath() } } if (drawMode is DrawMode.PathEffect && !isEraserOn) { var shaderSource by remember(backgroundColor) { mutableStateOf(null) } LaunchedEffect(shaderSource, invalidations) { withContext(Dispatchers.Default) { if (shaderSource == null || invalidations <= pathsCount) { shaderSource = onRequestFiltering( drawImageBitmap.overlay(drawBitmap) .asAndroidBitmap(), transformationsForMode( drawMode = drawMode, canvasSize = canvasSize ) )?.asImageBitmap()?.clipBitmap( path = path.asComposePath(), paint = pathEffectPaint( strokeWidth = strokeWidth, drawPathMode = drawPathMode, canvasSize = canvasSize ).asComposePaint() )?.also { it.prepareToDraw() onInvalidate() } } } } if (shaderSource != null) { LaunchedEffect(shaderSource) { onClearDrawPath() } val imagePaint = remember { Paint() } drawImage( image = shaderSource!!, topLeftOffset = Offset.Zero, paint = imagePaint ) } } else if (drawMode is DrawMode.SpotHeal && !isEraserOn) { val paint = remember(uiPathPaint, canvasSize) { val isSharpEdge = drawPathMode.isSharpEdge val isFilled = drawPathMode.isFilled val stroke = strokeWidth.toPx(canvasSize) Paint().apply { if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke this.strokeWidth = stroke if (isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } color = Color.White } } var isLoading by remember { mutableStateOf(false) } var progress by remember { mutableFloatStateOf(0f) } var shaderSource by remember(backgroundColor) { mutableStateOf(null) } LaunchedEffect(shaderSource, invalidations) { withContext(Dispatchers.Default) { if (shaderSource == null || invalidations <= pathsCount) { isLoading = true val job = launch { while (progress < 0.5f && isActive && isLoading) { progress += 0.01f delay(100) } while (progress < 0.75f && isActive && isLoading) { progress += 0.0025f delay(100) } while (progress < 1f && isActive && isLoading) { progress += 0.0025f delay(500) } } shaderSource = withContext(Dispatchers.IO) { onRequestFiltering( drawImageBitmap.overlay(drawBitmap).asAndroidBitmap(), listOf( createFilter, Filter.SpotHeal>( Pair( createBitmap( width = canvasSize.width, height = canvasSize.height ).applyCanvas { drawColor(Color.Black.toArgb()) drawPath( path, paint.nativePaint ) }.toImageModel(), drawMode.mode ) ) ) )?.asImageBitmap()?.clipBitmap( path = path.asComposePath(), paint = paint.apply { blendMode = BlendMode.Clear } )?.also { it.prepareToDraw() onInvalidate() } } isLoading = false job.cancel() progress = 0f } } } if (shaderSource != null) { LaunchedEffect(shaderSource) { onClearDrawPath() onInvalidate() } val imagePaint = remember { Paint() } drawImage( image = shaderSource!!, topLeftOffset = Offset.Zero, paint = imagePaint ) } LoadingDialog( visible = isLoading, canCancel = false, progress = { progress }, loaderSize = 72.dp, additionalContent = { AutoSizeText( text = "${(progress * 100).roundToInt()}%", maxLines = 1, fontWeight = FontWeight.Medium, modifier = Modifier.width(it * 0.8f), textAlign = TextAlign.Center ) } ) } else if (drawMode is DrawMode.Warp && !isEraserOn) { var warpedBitmap by remember(uiPathPaint, canvasSize) { mutableStateOf(null) } LaunchedEffect(warpedBitmap, invalidations) { if (warpedBitmap == null || invalidations <= pathsCount) { val source = drawImageBitmap .overlay(drawBitmap) .asAndroidBitmap() withContext(Dispatchers.Default) { val engine = WarpEngine(source) try { drawMode.strokes.forEach { warp -> val stroke = warp.scaleToFitCanvas( currentSize = canvasSize, oldSize = size ) engine.applyStroke( fromX = stroke.fromX, fromY = stroke.fromY, toX = stroke.toX, toY = stroke.toY, brush = WarpBrush( radius = strokeWidth.toPx(canvasSize), strength = drawMode.strength, hardness = drawMode.hardness ), mode = WarpMode.valueOf(drawMode.warpMode.name) ) } warpedBitmap = engine .render() .asImageBitmap().also { it.prepareToDraw() onInvalidate() } } finally { engine.release() } } } } warpedBitmap?.let { LaunchedEffect(warpedBitmap) { onClearWarpDrawPath(drawMode.previewClearToken) } val imagePaint = remember { Paint() } drawImage( image = warpedBitmap!!, topLeftOffset = Offset.Zero, paint = imagePaint ) } } else { val pathPaint by rememberPaint( strokeWidth = strokeWidth, isEraserOn = isEraserOn, drawColor = drawColor, brushSoftness = brushSoftness, drawMode = drawMode, canvasSize = canvasSize, drawPathMode = drawPathMode, drawLineStyle = drawLineStyle ) if (drawMode is DrawMode.Text && !isEraserOn) { if (drawMode.isRepeated) { drawRepeatedTextOnPath( text = drawMode.text, path = path, paint = pathPaint, interval = drawMode.repeatingInterval.toPx(canvasSize) ) } else { drawTextOnPath(drawMode.text, path, 0f, 0f, pathPaint) } } else if (drawMode is DrawMode.Image && !isEraserOn) { drawRepeatedImageOnPath( drawMode = drawMode, strokeWidth = strokeWidth, canvasSize = canvasSize, path = path, paint = pathPaint, invalidations = invalidations ) } else if (drawPathMode is DrawPathMode.Outlined) { drawPathMode.fillColor?.let { fillColor -> val filledPaint = remember(fillColor, pathPaint) { AndroidPaint().apply { set(pathPaint) style = AndroidPaint.Style.FILL color = fillColor.colorInt if (Color(fillColor.colorInt).alpha == 1f) { alpha = (drawColor.alpha * 255).roundToInt().coerceIn(0, 255) } pathEffect = null } } drawPath(path, filledPaint) } drawPath(path, pathPaint) } else { drawPath(path, pathPaint) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.controls import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.HelperGridParamsSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.MagnifierEnabledSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.feature.draw.domain.DrawBehavior import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.BrushSoftnessSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawColorSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawLineStyleSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawModeSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawPathModeSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.LineWidthSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.OpenColorPickerCard import com.t8rin.imagetoolbox.feature.draw.presentation.screenLogic.DrawComponent import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageSheet @Composable internal fun DrawContentControls( component: DrawComponent, secondaryControls: @Composable () -> Unit, drawColor: Color, onDrawColorChange: (Color) -> Unit, strokeWidth: Pt, onStrokeWidthChange: (Pt) -> Unit, brushSoftness: Pt, onBrushSoftnessChange: (Pt) -> Unit, alpha: Float, onAlphaChange: (Float) -> Unit ) { var showPickColorSheet by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() val drawMode = component.drawMode val drawPathMode = component.drawPathMode val drawLineStyle = component.drawLineStyle Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { if (!isPortrait) { Row( modifier = Modifier .padding(vertical = 8.dp) .container(shape = ShapeDefaults.circle) ) { secondaryControls() } } AnimatedVisibility( visible = drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { OpenColorPickerCard( modifier = Modifier.fillMaxWidth(), onOpen = { component.openColorPicker() showPickColorSheet = true } ) } AnimatedVisibility( visible = drawMode !is DrawMode.PathEffect && drawMode !is DrawMode.Image && drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { DrawColorSelector( modifier = Modifier.fillMaxWidth(), value = drawColor, onValueChange = onDrawColorChange ) } AnimatedVisibility( visible = drawPathMode.canChangeStrokeWidth, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { LineWidthSelector( modifier = Modifier.fillMaxWidth(), title = if (drawMode is DrawMode.Text) { stringResource(R.string.font_size) } else stringResource(R.string.line_width), valueRange = if (drawMode is DrawMode.Image) { 10f..120f } else 1f..100f, value = strokeWidth.value, onValueChange = { onStrokeWidthChange(it.pt) } ) } AnimatedVisibility( visible = drawMode !is DrawMode.Highlighter && drawMode !is DrawMode.PathEffect && drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { BrushSoftnessSelector( modifier = Modifier.fillMaxWidth(), value = brushSoftness.value, onValueChange = { onBrushSoftnessChange(it.pt) } ) } if (component.drawBehavior is DrawBehavior.Background) { ColorRowSelector( value = component.backgroundColor, onValueChange = component::updateBackgroundColor, icon = Icons.Outlined.BackgroundColor, modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.extraLarge ) ) } AnimatedVisibility( visible = drawMode !is DrawMode.Neon && drawMode !is DrawMode.PathEffect && drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { AlphaSelector( value = alpha, onValueChange = onAlphaChange, modifier = Modifier.fillMaxWidth() ) } DrawModeSelector( modifier = Modifier.fillMaxWidth(), value = drawMode, strokeWidth = strokeWidth, onValueChange = component::updateDrawMode, values = remember(drawLineStyle) { derivedStateOf { if (drawLineStyle == DrawLineStyle.None) { DrawMode.entries } else { listOf( DrawMode.Pen, DrawMode.Highlighter, DrawMode.Neon ) } } }.value, addFiltersSheetComponent = component.addFiltersSheetComponent, filterTemplateCreationSheetComponent = component.filterTemplateCreationSheetComponent ) AnimatedVisibility( visible = drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { DrawPathModeSelector( modifier = Modifier.fillMaxWidth(), value = drawPathMode, onValueChange = component::updateDrawPathMode, values = remember(drawMode, drawLineStyle) { derivedStateOf { if (drawMode !is DrawMode.Text && drawMode !is DrawMode.Image) { when (drawLineStyle) { DrawLineStyle.None -> DrawPathMode.entries !is DrawLineStyle.Stamped<*> -> listOf( DrawPathMode.Free, DrawPathMode.Line, DrawPathMode.LinePointingArrow(), DrawPathMode.PointingArrow(), DrawPathMode.DoublePointingArrow(), DrawPathMode.DoubleLinePointingArrow(), ) + DrawPathMode.outlinedEntries else -> listOf( DrawPathMode.Free, DrawPathMode.Line ) + DrawPathMode.outlinedEntries } } else { listOf( DrawPathMode.Free, DrawPathMode.Line ) + DrawPathMode.outlinedEntries } } }.value, drawMode = drawMode ) } AnimatedVisibility( visible = drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { DrawLineStyleSelector( modifier = Modifier.fillMaxWidth(), value = drawLineStyle, onValueChange = component::updateDrawLineStyle ) } HelperGridParamsSelector( value = component.helperGridParams, onValueChange = component::updateHelperGridParams, modifier = Modifier.fillMaxWidth() ) MagnifierEnabledSelector( modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.extraLarge, ) SaveExifWidget( modifier = Modifier.fillMaxWidth(), checked = component.saveExif, imageFormat = component.imageFormat, onCheckedChange = component::setSaveExif ) ImageFormatSelector( modifier = Modifier.navigationBarsPadding(), forceEnabled = component.drawBehavior is DrawBehavior.Background, value = component.imageFormat, onValueChange = component::setImageFormat ) } var colorPickerColor by rememberSaveable(stateSaver = ColorSaver) { mutableStateOf(Color.Black) } PickColorFromImageSheet( visible = showPickColorSheet, onDismiss = { showPickColorSheet = false }, bitmap = component.colorPickerBitmap, onColorChange = { colorPickerColor = it }, color = colorPickerColor ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentNoDataControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.controls import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor import com.t8rin.imagetoolbox.core.resources.icons.ImagesMode import com.t8rin.imagetoolbox.core.resources.icons.ImagesearchRoller import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.restrict import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.draw.presentation.screenLogic.DrawComponent @Composable internal fun DrawContentNoDataControls( component: DrawComponent, onPickImage: () -> Unit ) { var showBackgroundDrawingSetup by rememberSaveable { mutableStateOf(false) } val cutout = WindowInsets.displayCutout.asPaddingValues() LazyVerticalStaggeredGrid( modifier = Modifier.fillMaxHeight(), columns = StaggeredGridCells.Adaptive(300.dp), horizontalArrangement = Arrangement.spacedBy( space = 12.dp, alignment = Alignment.CenterHorizontally ), verticalItemSpacing = 12.dp, contentPadding = PaddingValues( bottom = 12.dp + WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding(), top = 12.dp, end = 12.dp + cutout.calculateEndPadding( LocalLayoutDirection.current ), start = 12.dp + cutout.calculateStartPadding( LocalLayoutDirection.current ) ), flingBehavior = enhancedFlingBehavior() ) { item { PreferenceItem( onClick = onPickImage, startIcon = Icons.Outlined.ImagesMode, title = stringResource(R.string.draw_on_image), subtitle = stringResource(R.string.draw_on_image_sub), modifier = Modifier.fillMaxWidth() ) } item { PreferenceItem( onClick = { showBackgroundDrawingSetup = true }, startIcon = Icons.Outlined.ImagesearchRoller, title = stringResource(R.string.draw_on_background), subtitle = stringResource(R.string.draw_on_background_sub), modifier = Modifier.fillMaxWidth() ) } } val drawOnBackgroundParams = component.drawOnBackgroundParams val screenSize = LocalScreenSize.current val screenWidth = screenSize.widthPx val screenHeight = screenSize.heightPx var width by remember( showBackgroundDrawingSetup, screenWidth, drawOnBackgroundParams ) { mutableIntStateOf(drawOnBackgroundParams.width.takeIf { it > -1 } ?: screenWidth) } var height by remember( showBackgroundDrawingSetup, screenHeight, drawOnBackgroundParams ) { mutableIntStateOf(drawOnBackgroundParams.height.takeIf { it > -1 } ?: screenHeight) } var sheetBackgroundColor by rememberSaveable( showBackgroundDrawingSetup, drawOnBackgroundParams, stateSaver = ColorSaver ) { mutableStateOf(drawOnBackgroundParams.color?.toColor() ?: Color.White) } EnhancedModalBottomSheet( title = { TitleItem( text = stringResource(R.string.draw), icon = Icons.Rounded.ImagesearchRoller ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showBackgroundDrawingSetup = false component.startDrawOnBackground( reqWidth = width, reqHeight = height, color = sheetBackgroundColor ) } ) { AutoSizeText(stringResource(R.string.ok)) } }, sheetContent = { Box { Column(Modifier.enhancedVerticalScroll(rememberScrollState())) { Row( Modifier .padding(16.dp) .container(shape = ShapeDefaults.extraLarge) ) { RoundedTextField( value = width.takeIf { it != 0 }?.toString() ?: "", onValueChange = { width = it.restrict(8192).toIntOrNull() ?: 0 }, shape = ShapeDefaults.smallStart, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = { Text(stringResource(R.string.width, " ")) }, modifier = Modifier .weight(1f) .padding( start = 8.dp, top = 8.dp, bottom = 8.dp, end = 2.dp ) ) RoundedTextField( value = height.takeIf { it != 0 }?.toString() ?: "", onValueChange = { height = it.restrict(8192).toIntOrNull() ?: 0 }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), shape = ShapeDefaults.smallEnd, label = { Text(stringResource(R.string.height, " ")) }, modifier = Modifier .weight(1f) .padding( start = 2.dp, top = 8.dp, bottom = 8.dp, end = 8.dp ), ) } ColorRowSelector( value = sheetBackgroundColor, onValueChange = { sheetBackgroundColor = it }, icon = Icons.Outlined.BackgroundColor, modifier = Modifier .padding( start = 16.dp, end = 16.dp, bottom = 16.dp ) .container(ShapeDefaults.extraLarge) ) } } }, visible = showBackgroundDrawingSetup, onDismiss = { showBackgroundDrawingSetup = it } ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/controls/DrawContentSecondaryControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.controls import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Redo import androidx.compose.material.icons.automirrored.rounded.Undo import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import com.t8rin.imagetoolbox.core.ui.widget.buttons.EraseModeButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.feature.draw.presentation.screenLogic.DrawComponent @Composable internal fun DrawContentSecondaryControls( component: DrawComponent, panEnabled: Boolean, onTogglePanEnabled: () -> Unit, isEraserOn: Boolean, onToggleIsEraserOn: () -> Unit ) { PanModeButton( selected = panEnabled, onClick = onTogglePanEnabled ) EnhancedIconButton( onClick = component::undo, enabled = component.lastPaths.isNotEmpty() || component.paths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Undo, contentDescription = "Undo" ) } EnhancedIconButton( onClick = component::redo, enabled = component.undonePaths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Redo, contentDescription = "Redo" ) } EraseModeButton( selected = isEraserOn, enabled = !panEnabled, onClick = onToggleIsEraserOn ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ArrowParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.angle import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isArrow import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.sizeScale import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateArrow @Composable internal fun ArrowParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit ) { AnimatedVisibility( visible = value.isArrow(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { EnhancedSliderItem( value = value.sizeScale(), title = stringResource(R.string.head_length_scale), valueRange = 0.5f..8f, internalStateTransformation = { it.roundTo(1) }, onValueChange = { onValueChange( value.updateArrow(sizeScale = it) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = value.angle() - 90f, title = stringResource(R.string.angle), valueRange = 0f..90f, internalStateTransformation = { it.roundTo(1) }, onValueChange = { onValueChange( value.updateArrow(angle = it + 90f) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.bottom ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/CustomPathEffectParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AutoFixNormal import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.AddFilterButton import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode @Composable internal fun CustomPathEffectParamsSelector( value: DrawMode, onValueChange: (DrawMode) -> Unit, addFiltersSheetComponent: AddFiltersSheetComponent, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent ) { AnimatedVisibility( visible = value is DrawMode.PathEffect.Custom, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val filter by remember(value) { derivedStateOf { (value as? DrawMode.PathEffect.Custom)?.filter?.toUiFilter() } } var showFilterSelection by rememberSaveable { mutableStateOf(false) } AnimatedContent( targetState = filter, contentKey = { it != null } ) { filter -> Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(horizontal = 8.dp) ) { if (filter != null) { FilterItem( filter = filter, showDragHandle = false, onRemove = { onValueChange( DrawMode.PathEffect.Custom() ) }, onFilterChange = { value -> onValueChange( DrawMode.PathEffect.Custom(filter.copy(value)) ) }, backgroundColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.default, canHide = false, onCreateTemplate = null ) Spacer(modifier = Modifier.height(8.dp)) EnhancedButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = { showFilterSelection = true } ) { Icon( imageVector = Icons.Rounded.AutoFixNormal, contentDescription = stringResource(R.string.replace_filter) ) Spacer(Modifier.width(8.dp)) Text(stringResource(id = R.string.replace_filter)) } Spacer(modifier = Modifier.height(8.dp)) } else { InfoContainer( containerColor = MaterialTheme.colorScheme.surfaceContainerLow, text = stringResource(R.string.pick_filter_info) ) Spacer(modifier = Modifier.height(8.dp)) AddFilterButton( onClick = { showFilterSelection = true } ) Spacer(modifier = Modifier.height(8.dp)) } } AddFiltersSheet( component = addFiltersSheetComponent, filterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent, visible = showFilterSelection, onVisibleChange = { showFilterSelection = it }, canAddTemplates = false, previewBitmap = null, onFilterPicked = { onValueChange( DrawMode.PathEffect.Custom(it.newInstance()) ) }, onFilterPickedWithParams = { onValueChange( DrawMode.PathEffect.Custom(it) ) } ) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/DrawPathModeInfoSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.getIcon import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.getSubtitle import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.getTitle @Composable internal fun DrawPathModeInfoSheet( visible: Boolean, onDismiss: () -> Unit, values: List ) { EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { values.forEachIndexed { index, item -> Column( Modifier .fillMaxWidth() .container( shape = ShapeDefaults.byIndex( index = index, size = values.size ), resultPadding = 0.dp ) ) { TitleItem(text = stringResource(item.getTitle()), icon = item.getIcon()) Text( text = stringResource(item.getSubtitle()), modifier = Modifier.padding( start = 16.dp, end = 16.dp, bottom = 16.dp ), fontSize = 14.sp, lineHeight = 18.sp ) } } } }, visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem(text = stringResource(R.string.draw_path_mode)) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } } ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/FloodFillParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isFloodFill import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.tolerance import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateFloodFill @Composable internal fun FloodFillParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit ) { AnimatedVisibility( visible = value.isFloodFill(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { EnhancedSliderItem( value = value.tolerance(), title = stringResource(R.string.tolerance), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.updateFloodFill(tolerance = it.roundToTwoDigits()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.default ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/ImageParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.coerceIn import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode @Composable internal fun ImageParamsSelector( value: DrawMode, onValueChange: (DrawMode) -> Unit, strokeWidth: Pt ) { AnimatedVisibility( visible = value is DrawMode.Image, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { ImageSelector( modifier = Modifier .padding(horizontal = 8.dp), value = (value as? DrawMode.Image)?.imageData ?: "", onValueChange = { onValueChange( (value as? DrawMode.Image)?.copy( imageData = it ) ?: value ) }, subtitle = stringResource(id = R.string.draw_image_sub), shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) val dashMinimum = -((strokeWidth.value * 0.9f) / 2).toInt().toFloat() LaunchedEffect(dashMinimum, value) { if (value is DrawMode.Image && value.repeatingInterval < dashMinimum.pt) { onValueChange( value.copy( repeatingInterval = value.repeatingInterval.coerceIn( dashMinimum.pt, 100.pt ) ) ) } } EnhancedSliderItem( value = (value as? DrawMode.Image)?.repeatingInterval?.value ?: 0f, title = stringResource(R.string.dash_size), valueRange = dashMinimum..100f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( (value as? DrawMode.Image)?.copy( repeatingInterval = it.pt.coerceIn(dashMinimum.pt, 100.pt) ) ?: value ) }, containerColor = MaterialTheme.colorScheme.surface, valueSuffix = " Pt", modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.bottom ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OutlinedFillColorSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FormatColorFill import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun OutlinedFillColorSelector( value: Color?, onValueChange: (Color?) -> Unit, modifier: Modifier = Modifier, shape: Shape = ShapeDefaults.default, containerColor: Color = Color.Unspecified ) { ColorRowSelector( value = value, onValueChange = onValueChange, onNullClick = { onValueChange(null) }, title = stringResource(R.string.fill_color), icon = Icons.Outlined.FormatColorFill, allowAlpha = true, modifier = modifier .fillMaxWidth() .container( color = containerColor, shape = shape ) ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/OvalParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateOutlined @Composable internal fun OvalParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit, canChangeFillColor: Boolean ) { AnimatedVisibility( visible = value is DrawPathMode.OutlinedOval && canChangeFillColor, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { OutlinedFillColorSelector( value = value.outlinedFillColor?.toColor(), onValueChange = { onValueChange(value.updateOutlined(it)) }, shape = ShapeDefaults.default, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), containerColor = MaterialTheme.colorScheme.surface, ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PixelationParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.PixelSizeSelector @Composable internal fun PixelationParamsSelector( value: DrawMode, onValueChange: (DrawMode) -> Unit ) { AnimatedVisibility( visible = value is DrawMode.PathEffect.Pixelation, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { PixelSizeSelector( modifier = Modifier.padding(8.dp), value = (value as? DrawMode.PathEffect.Pixelation)?.pixelSize ?: 0f, onValueChange = { onValueChange(DrawMode.PathEffect.Pixelation(it)) }, color = MaterialTheme.colorScheme.surface ) } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PolygonParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SquareFoot import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isPolygon import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isRegular import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.rotationDegrees import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateOutlined import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updatePolygon import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.vertices import kotlin.math.roundToInt @Composable internal fun PolygonParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit, canChangeFillColor: Boolean ) { AnimatedVisibility( visible = value.isPolygon(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { AnimatedVisibility( visible = canChangeFillColor, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { OutlinedFillColorSelector( value = value.outlinedFillColor?.toColor(), onValueChange = { onValueChange(value.updateOutlined(it)) }, shape = ShapeDefaults.top, modifier = Modifier .padding(bottom = 4.dp) .fillMaxWidth() .padding(horizontal = 8.dp), containerColor = MaterialTheme.colorScheme.surface, ) } EnhancedSliderItem( value = value.vertices(), title = stringResource(R.string.vertices), valueRange = 3f..24f, steps = 20, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.updatePolygon(vertices = it.toInt()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = if (canChangeFillColor) ShapeDefaults.center else ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = value.rotationDegrees(), title = stringResource(R.string.angle), valueRange = 0f..360f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.updatePolygon(rotationDegrees = it.toInt()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) PreferenceRowSwitch( title = stringResource(R.string.draw_regular_polygon), subtitle = stringResource(R.string.draw_regular_polygon_sub), checked = value.isRegular(), onClick = { onValueChange( value.updatePolygon(isRegular = it) ) }, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.bottom, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), startIcon = Icons.Rounded.SquareFoot, ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/PrivacyBlurParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group.components.BlurRadiusSelector import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode @Composable internal fun PrivacyBlurParamsSelector( value: DrawMode, onValueChange: (DrawMode) -> Unit ) { AnimatedVisibility( visible = value is DrawMode.PathEffect.PrivacyBlur, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { BlurRadiusSelector( modifier = Modifier.padding(8.dp), value = (value as? DrawMode.PathEffect.PrivacyBlur)?.blurRadius ?: 0, valueRange = 5f..50f, onValueChange = { onValueChange(DrawMode.PathEffect.PrivacyBlur(it)) }, color = MaterialTheme.colorScheme.surface ) } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/RectParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.cornerRadius import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isRect import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.rotationDegrees import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateOutlined import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateRect import kotlin.math.roundToInt @Composable internal fun RectParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit, canChangeFillColor: Boolean ) { AnimatedVisibility( visible = value.isRect(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { AnimatedVisibility( visible = canChangeFillColor, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { OutlinedFillColorSelector( value = value.outlinedFillColor?.toColor(), onValueChange = { onValueChange(value.updateOutlined(it)) }, shape = ShapeDefaults.top, modifier = Modifier .padding(bottom = 4.dp) .fillMaxWidth() .padding(horizontal = 8.dp), containerColor = MaterialTheme.colorScheme.surface, ) } EnhancedSliderItem( value = value.rotationDegrees(), title = stringResource(R.string.angle), valueRange = 0f..360f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.updateRect(rotationDegrees = it.toInt()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = if (canChangeFillColor) ShapeDefaults.center else ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = value.cornerRadius(), title = stringResource(R.string.radius), valueRange = 0f..0.5f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.updateRect(cornerRadius = it.roundToTwoDigits()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.bottom ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SpotHealParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.retain.retain import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.saving.track import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.throttleLatest import com.t8rin.imagetoolbox.core.filters.domain.model.enums.SpotHealMode import com.t8rin.imagetoolbox.core.filters.presentation.utils.LamaLoader import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalKeepAliveService import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCancellableCircularProgressIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable internal fun SpotHealParamsSelector( value: DrawMode, onValueChange: (DrawMode) -> Unit ) { AnimatedVisibility( visible = value is DrawMode.SpotHeal, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val keepAliveService = LocalKeepAliveService.current val settingsState = LocalSettingsState.current val scope = retain { CoroutineScope(Dispatchers.IO) } val simpleSettingsInteractor = LocalSimpleSettingsInteractor.current var downloadJob by retain { mutableStateOf(null) } var downloadProgress by remember(LamaLoader.isDownloaded) { mutableStateOf(null) } var useLama by remember(settingsState.spotHealMode) { mutableStateOf(settingsState.spotHealMode == 1) } LaunchedEffect(LamaLoader.isDownloaded) { if (!LamaLoader.isDownloaded) { useLama = false simpleSettingsInteractor.setSpotHealMode(0) } } LaunchedEffect(useLama) { onValueChange( DrawMode.SpotHeal( if (useLama) SpotHealMode.LaMa else SpotHealMode.OpenCV ) ) } PreferenceRowSwitch( title = stringResource(R.string.generative_inpaint), subtitle = stringResource( if (LamaLoader.isDownloaded) R.string.generative_inpaint_ready_sub else R.string.generative_inpaint_sub ), checked = useLama, onClick = { new -> if (downloadJob == null) { useLama = new scope.launch { simpleSettingsInteractor.setSpotHealMode(if (useLama) 1 else 0) } if (useLama && !LamaLoader.isDownloaded) { downloadJob?.cancel() downloadJob = scope.launch { keepAliveService.track( initial = { updateOrStart( title = getString(R.string.downloading) ) } ) { LamaLoader.download() .onStart { downloadProgress = DownloadProgress( currentPercent = 0f, currentTotalSize = 0 ) } .onCompletion { downloadProgress = null downloadJob = null } .catch { simpleSettingsInteractor.setSpotHealMode(0) AppToastHost.showFailureToast(it) useLama = false downloadProgress = null downloadJob = null } .onEach { downloadProgress = it } .throttleLatest(50) .collect { updateProgress( title = getString(R.string.downloading), done = (it.currentPercent * 100).roundToInt(), total = 100 ) } } } } } }, contentInsteadOfSwitch = downloadProgress?.let { progress -> { EnhancedCancellableCircularProgressIndicator( progress = { progress.currentPercent }, modifier = Modifier.size(24.dp), trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), strokeWidth = 3.dp, onCancel = { downloadJob?.cancel() downloadProgress = null downloadJob = null useLama = false scope.launch { simpleSettingsInteractor.setSpotHealMode(0) } } ) } }, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.default, modifier = Modifier .fillMaxWidth() .padding(8.dp), resultModifier = Modifier.padding(16.dp), applyHorizontalPadding = false ) } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/SprayParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CropSquare import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.density import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isSpray import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isSquareShaped import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.pixelSize import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateSpray import kotlin.math.roundToInt @Composable internal fun SprayParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit ) { AnimatedVisibility( visible = value.isSpray(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { EnhancedSliderItem( value = value.density(), title = stringResource(R.string.density), valueRange = 1f..500f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.updateSpray(density = it.roundToInt()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = value.pixelSize(), title = stringResource(R.string.pixel_size), valueRange = 0.5f..20f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.updateSpray(pixelSize = it.roundToTwoDigits()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) PreferenceRowSwitch( title = stringResource(R.string.square_particles), subtitle = stringResource(R.string.square_particles_sub), checked = value.isSquareShaped(), onClick = { onValueChange( value.updateSpray(isSquareShaped = it) ) }, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.bottom, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), resultModifier = Modifier.padding(16.dp), startIcon = Icons.Rounded.CropSquare ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/StarParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SquareFoot import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.innerRadiusRatio import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isRegular import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.isStar import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.rotationDegrees import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateOutlined import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateStar import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.vertices import kotlin.math.roundToInt @Composable internal fun StarParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit, canChangeFillColor: Boolean ) { AnimatedVisibility( visible = value.isStar(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { AnimatedVisibility( visible = canChangeFillColor, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { OutlinedFillColorSelector( value = value.outlinedFillColor?.toColor(), onValueChange = { onValueChange(value.updateOutlined(it)) }, shape = ShapeDefaults.top, modifier = Modifier .padding(bottom = 4.dp) .fillMaxWidth() .padding(horizontal = 8.dp), containerColor = MaterialTheme.colorScheme.surface, ) } EnhancedSliderItem( value = value.vertices(), title = stringResource(R.string.vertices), valueRange = 3f..24f, steps = 20, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.updateStar(vertices = it.toInt()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = if (canChangeFillColor) ShapeDefaults.center else ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = value.rotationDegrees(), title = stringResource(R.string.angle), valueRange = 0f..360f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.updateStar(rotationDegrees = it.toInt()) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = value.innerRadiusRatio(), title = stringResource(R.string.inner_radius_ratio), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.updateStar(innerRadiusRatio = it) ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) PreferenceRowSwitch( title = stringResource(R.string.draw_regular_star), subtitle = stringResource(R.string.draw_regular_star_sub), checked = value.isRegular(), onClick = { onValueChange( value.updateStar(isRegular = it) ) }, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.bottom, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), startIcon = Icons.Rounded.SquareFoot, ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TextParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiFont import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FontSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode @Composable internal fun TextParamsSelector( value: DrawMode, onValueChange: (DrawMode) -> Unit ) { AnimatedVisibility( visible = value is DrawMode.Text, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { RoundedTextField( modifier = Modifier .padding(horizontal = 8.dp) .container( shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface, resultPadding = 8.dp ), value = (value as? DrawMode.Text)?.text ?: "", singleLine = false, onValueChange = { onValueChange( (value as? DrawMode.Text)?.copy( text = it ) ?: value ) }, label = { Text(stringResource(R.string.text)) } ) Spacer(modifier = Modifier.height(4.dp)) FontSelector( value = (value as? DrawMode.Text)?.font.toUiFont(), onValueChange = { onValueChange( (value as? DrawMode.Text)?.copy( font = it.type ) ?: value ) }, modifier = Modifier .padding(horizontal = 8.dp), shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) val isDashSizeControlVisible = (value as? DrawMode.Text)?.isRepeated == true PreferenceRowSwitch( title = stringResource(R.string.repeat_text), subtitle = stringResource(R.string.repeat_text_sub), checked = (value as? DrawMode.Text)?.isRepeated == true, onClick = { onValueChange( (value as? DrawMode.Text)?.copy( isRepeated = it ) ?: value ) }, containerColor = MaterialTheme.colorScheme.surface, shape = animateShape( if (isDashSizeControlVisible) ShapeDefaults.center else ShapeDefaults.bottom ), modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), resultModifier = Modifier.padding(16.dp), applyHorizontalPadding = false ) Spacer( modifier = Modifier.height( if (isDashSizeControlVisible) 4.dp else 8.dp ) ) AnimatedVisibility( visible = isDashSizeControlVisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { EnhancedSliderItem( value = (value as? DrawMode.Text)?.repeatingInterval?.value ?: 0f, title = stringResource(R.string.dash_size), valueRange = 0f..100f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( (value as? DrawMode.Text)?.copy( repeatingInterval = it.pt ) ?: value ) }, containerColor = MaterialTheme.colorScheme.surface, valueSuffix = " Pt", modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .padding(bottom = 8.dp), shape = ShapeDefaults.bottom ) } } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/TriangleParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.toColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.updateOutlined @Composable internal fun TriangleParamsSelector( value: DrawPathMode, onValueChange: (DrawPathMode) -> Unit, canChangeFillColor: Boolean ) { AnimatedVisibility( visible = value is DrawPathMode.OutlinedTriangle && canChangeFillColor, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { OutlinedFillColorSelector( value = value.outlinedFillColor?.toColor(), onValueChange = { onValueChange(value.updateOutlined(it)) }, shape = ShapeDefaults.default, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), containerColor = MaterialTheme.colorScheme.surface, ) Spacer(Modifier.height(8.dp)) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/element/WarpParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.element import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.WarpMode @Composable internal fun WarpParamsSelector( value: DrawMode, onValueChange: (DrawMode) -> Unit ) { AnimatedVisibility( visible = value is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { EnhancedSliderItem( value = value.safeCast()?.strength ?: 0f, title = stringResource(R.string.strength), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.safeCast()?.copy( strength = it.roundToTwoDigits() ) ?: value ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = value.safeCast()?.hardness ?: 0f, title = stringResource(R.string.hardness), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.safeCast()?.copy( hardness = it.roundToTwoDigits() ) ?: value ) }, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) EnhancedButtonGroup( enabled = true, modifier = Modifier .padding(horizontal = 8.dp) .container( shape = ShapeDefaults.bottom, color = MaterialTheme.colorScheme.surface ), itemCount = WarpMode.entries.size, title = { Text( text = stringResource(R.string.warp_mode), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium, modifier = Modifier.padding(top = 4.dp) ) }, selectedIndex = WarpMode.entries.indexOfFirst { value.safeCast()?.warpMode == it }, itemContent = { Text( text = stringResource(WarpMode.entries[it].title()) ) }, onIndexChange = { onValueChange( value.safeCast()?.copy( warpMode = WarpMode.entries[it] ) ?: value ) }, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer ) Spacer(modifier = Modifier.height(8.dp)) } } } private fun WarpMode.title(): Int = when (this) { WarpMode.MOVE -> R.string.warp_mode_move WarpMode.GROW -> R.string.warp_mode_grow WarpMode.SHRINK -> R.string.warp_mode_shrink WarpMode.SWIRL_CW -> R.string.warp_mode_swirl_cw WarpMode.SWIRL_CCW -> R.string.warp_mode_swirl_ccw WarpMode.MIXING -> R.string.mix } @Composable @Preview private fun Preview() = ImageToolboxThemeForPreview(true) { Surface( color = MaterialTheme.colorScheme.surfaceContainer ) { WarpParamsSelector( value = DrawMode.Warp(), onValueChange = {} ) } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/BitmapDrawerPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import androidx.compose.foundation.border import androidx.compose.foundation.layout.BoxScope import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableIntState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHelperGrid import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable fun BoxScope.BitmapDrawerPreview( preview: ImageBitmap, globalTouchPointersCount: MutableIntState, onReceiveMotionEvent: (MotionEvent) -> Unit, onInvalidate: () -> Unit, onUpdateCurrentDrawPosition: (Offset) -> Unit, onUpdateDrawDownPosition: (Offset) -> Unit, drawEnabled: Boolean, helperGridParams: HelperGridParams, beforeHelperGridModifier: Modifier = Modifier, ) { Picture( model = preview, modifier = Modifier .matchParentSize() .pointerDrawHandler( globalTouchPointersCount = globalTouchPointersCount, onReceiveMotionEvent = onReceiveMotionEvent, onInvalidate = onInvalidate, onUpdateCurrentDrawPosition = onUpdateCurrentDrawPosition, onUpdateDrawDownPosition = onUpdateDrawDownPosition, enabled = drawEnabled ) .clip(ShapeDefaults.extremeSmall) .transparencyChecker() .then(beforeHelperGridModifier) .drawHelperGrid(helperGridParams) .border( width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant(), shape = ShapeDefaults.extremeSmall ), contentDescription = null, contentScale = ContentScale.FillBounds ) } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathEffectPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import android.graphics.Bitmap import android.graphics.PorterDuff import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.createScaledBitmap import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import android.graphics.Path as NativePath @Composable internal fun DrawPathEffectPreview( drawPathCanvas: Canvas, drawMode: DrawMode.PathEffect, canvasSize: IntegerSize, imageWidth: Int, imageHeight: Int, outputImage: ImageBitmap, onRequestFiltering: suspend (Bitmap, List>) -> Bitmap?, paths: List, drawPath: Path, backgroundColor: Color, strokeWidth: Pt, drawPathMode: DrawPathMode ) { var shaderBitmap by remember { mutableStateOf(null) } LaunchedEffect(outputImage, paths, backgroundColor, drawMode) { shaderBitmap = onRequestFiltering( outputImage.asAndroidBitmap(), transformationsForMode( drawMode = drawMode, canvasSize = canvasSize ) )?.createScaledBitmap( width = imageWidth, height = imageHeight )?.asImageBitmap() } shaderBitmap?.let { with(drawPathCanvas) { with(nativeCanvas) { drawColor(Color.Transparent.toArgb(), PorterDuff.Mode.CLEAR) val paint by rememberPathEffectPaint( strokeWidth = strokeWidth, drawPathMode = drawPathMode, canvasSize = canvasSize ) val newPath = drawPath.copyAsAndroidPath().apply { fillType = NativePath.FillType.INVERSE_WINDING } val imagePaint = remember { Paint() } drawImage( image = it, topLeftOffset = Offset.Zero, paint = imagePaint ) drawPath(newPath, paint) } } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawPathModeUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank import androidx.compose.material.icons.rounded.Circle import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarOutline import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toColor import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FloodFill import com.t8rin.imagetoolbox.core.resources.icons.FreeArrow import com.t8rin.imagetoolbox.core.resources.icons.FreeDoubleArrow import com.t8rin.imagetoolbox.core.resources.icons.FreeDraw import com.t8rin.imagetoolbox.core.resources.icons.Lasso import com.t8rin.imagetoolbox.core.resources.icons.Line import com.t8rin.imagetoolbox.core.resources.icons.LineArrow import com.t8rin.imagetoolbox.core.resources.icons.LineDoubleArrow import com.t8rin.imagetoolbox.core.resources.icons.Polygon import com.t8rin.imagetoolbox.core.resources.icons.Spray import com.t8rin.imagetoolbox.core.resources.icons.Square import com.t8rin.imagetoolbox.core.resources.icons.Triangle import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode internal fun DrawPathMode.saveState( value: DrawPathMode ): DrawPathMode = when (value) { is DrawPathMode.Rect if this is DrawPathMode.OutlinedRect -> { copy( rotationDegrees = value.rotationDegrees, cornerRadius = value.cornerRadius ) } is DrawPathMode.OutlinedRect if this is DrawPathMode.Rect -> { copy( rotationDegrees = value.rotationDegrees, cornerRadius = value.cornerRadius ) } is DrawPathMode.Polygon if this is DrawPathMode.OutlinedPolygon -> { copy( vertices = value.vertices, rotationDegrees = value.rotationDegrees, isRegular = value.isRegular ) } is DrawPathMode.OutlinedPolygon if this is DrawPathMode.Polygon -> { copy( vertices = value.vertices, rotationDegrees = value.rotationDegrees, isRegular = value.isRegular ) } is DrawPathMode.Star if this is DrawPathMode.OutlinedStar -> { copy( vertices = value.vertices, innerRadiusRatio = innerRadiusRatio, rotationDegrees = value.rotationDegrees, isRegular = value.isRegular ) } is DrawPathMode.OutlinedStar if this is DrawPathMode.Star -> { copy( vertices = value.vertices, innerRadiusRatio = innerRadiusRatio, rotationDegrees = value.rotationDegrees, isRegular = value.isRegular ) } is DrawPathMode.PointingArrow if this is DrawPathMode.LinePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.LinePointingArrow if this is DrawPathMode.PointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.PointingArrow if this is DrawPathMode.DoublePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.DoublePointingArrow if this is DrawPathMode.PointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.PointingArrow if this is DrawPathMode.DoubleLinePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.DoubleLinePointingArrow if this is DrawPathMode.PointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.LinePointingArrow if this is DrawPathMode.DoublePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.DoublePointingArrow if this is DrawPathMode.LinePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.LinePointingArrow if this is DrawPathMode.DoubleLinePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.DoubleLinePointingArrow if this is DrawPathMode.LinePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.DoublePointingArrow if this is DrawPathMode.DoubleLinePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.DoubleLinePointingArrow if this is DrawPathMode.DoublePointingArrow -> { copy( sizeScale = value.sizeScale, angle = value.angle ) } is DrawPathMode.FloodFill if this is DrawPathMode.FloodFill -> { copy( tolerance = value.tolerance ) } is DrawPathMode.Spray if this is DrawPathMode.Spray -> { copy( density = value.density, pixelSize = value.pixelSize, isSquareShaped = value.isSquareShaped ) } else -> this }.run { if (value is DrawPathMode.Outlined && this is DrawPathMode.Outlined) { updateOutlined( fillColor = value.fillColor?.toColor() ) } else this } internal fun DrawPathMode.density(): Int = when (this) { is DrawPathMode.Spray -> density else -> 0 } internal fun DrawPathMode.pixelSize(): Float = when (this) { is DrawPathMode.Spray -> pixelSize else -> 0f } internal fun DrawPathMode.isSquareShaped(): Boolean = when (this) { is DrawPathMode.Spray -> isSquareShaped else -> false } internal fun DrawPathMode.tolerance(): Float = when (this) { is DrawPathMode.FloodFill -> tolerance else -> 0f } internal fun DrawPathMode.sizeScale(): Float = when (this) { is DrawPathMode.PointingArrow -> sizeScale is DrawPathMode.LinePointingArrow -> sizeScale is DrawPathMode.DoublePointingArrow -> sizeScale is DrawPathMode.DoubleLinePointingArrow -> sizeScale else -> 1f } internal fun DrawPathMode.angle(): Float = when (this) { is DrawPathMode.PointingArrow -> angle is DrawPathMode.LinePointingArrow -> angle is DrawPathMode.DoublePointingArrow -> angle is DrawPathMode.DoubleLinePointingArrow -> angle else -> 0f } internal fun DrawPathMode.vertices(): Int = when (this) { is DrawPathMode.Polygon -> vertices is DrawPathMode.OutlinedPolygon -> vertices is DrawPathMode.Star -> vertices is DrawPathMode.OutlinedStar -> vertices else -> 0 } internal fun DrawPathMode.rotationDegrees(): Int = when (this) { is DrawPathMode.Polygon -> rotationDegrees is DrawPathMode.OutlinedPolygon -> rotationDegrees is DrawPathMode.Star -> rotationDegrees is DrawPathMode.OutlinedStar -> rotationDegrees is DrawPathMode.Rect -> rotationDegrees is DrawPathMode.OutlinedRect -> rotationDegrees else -> 0 } internal fun DrawPathMode.cornerRadius(): Float = when (this) { is DrawPathMode.Rect -> cornerRadius is DrawPathMode.OutlinedRect -> cornerRadius else -> 0f } internal fun DrawPathMode.isRegular(): Boolean = when (this) { is DrawPathMode.Polygon -> isRegular is DrawPathMode.OutlinedPolygon -> isRegular is DrawPathMode.Star -> isRegular is DrawPathMode.OutlinedStar -> isRegular else -> false } internal fun DrawPathMode.innerRadiusRatio(): Float = when (this) { is DrawPathMode.Star -> innerRadiusRatio is DrawPathMode.OutlinedStar -> innerRadiusRatio else -> 0.5f } internal fun DrawPathMode.updateOutlined( fillColor: Color? ) = when (this) { is DrawPathMode.Outlined -> { when (this) { is DrawPathMode.OutlinedOval -> copy( fillColor = fillColor?.toModel() ) is DrawPathMode.OutlinedPolygon -> copy( fillColor = fillColor?.toModel() ) is DrawPathMode.OutlinedRect -> copy( fillColor = fillColor?.toModel() ) is DrawPathMode.OutlinedStar -> copy( fillColor = fillColor?.toModel() ) is DrawPathMode.OutlinedTriangle -> copy( fillColor = fillColor?.toModel() ) } } else -> this } internal fun DrawPathMode.updatePolygon( vertices: Int? = null, rotationDegrees: Int? = null, isRegular: Boolean? = null ) = when (this) { is DrawPathMode.Polygon -> { copy( vertices = vertices ?: this.vertices, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) } is DrawPathMode.OutlinedPolygon -> { copy( vertices = vertices ?: this.vertices, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) } else -> this } internal fun DrawPathMode.updateStar( vertices: Int? = null, innerRadiusRatio: Float? = null, rotationDegrees: Int? = null, isRegular: Boolean? = null ) = when (this) { is DrawPathMode.Star -> { copy( vertices = vertices ?: this.vertices, innerRadiusRatio = innerRadiusRatio ?: this.innerRadiusRatio, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) } is DrawPathMode.OutlinedStar -> { copy( vertices = vertices ?: this.vertices, innerRadiusRatio = innerRadiusRatio ?: this.innerRadiusRatio, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) } else -> this } internal fun DrawPathMode.updateRect( rotationDegrees: Int? = null, cornerRadius: Float? = null ) = when (this) { is DrawPathMode.Rect -> { copy( rotationDegrees = rotationDegrees ?: this.rotationDegrees, cornerRadius = cornerRadius ?: this.cornerRadius ) } is DrawPathMode.OutlinedRect -> { copy( rotationDegrees = rotationDegrees ?: this.rotationDegrees, cornerRadius = cornerRadius ?: this.cornerRadius ) } else -> this } internal fun DrawPathMode.updateArrow( sizeScale: Float? = null, angle: Float? = null ) = when (this) { is DrawPathMode.PointingArrow -> { copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) } is DrawPathMode.LinePointingArrow -> { copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) } is DrawPathMode.DoublePointingArrow -> { copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) } is DrawPathMode.DoubleLinePointingArrow -> { copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) } else -> this } internal fun DrawPathMode.updateFloodFill( tolerance: Float? = null, ) = when (this) { is DrawPathMode.FloodFill -> { copy( tolerance = tolerance ?: this.tolerance ) } else -> this } internal fun DrawPathMode.updateSpray( density: Int? = null, pixelSize: Float? = null, isSquareShaped: Boolean? = null, ) = when (this) { is DrawPathMode.Spray -> { copy( density = density ?: this.density, pixelSize = pixelSize ?: this.pixelSize, isSquareShaped = isSquareShaped ?: this.isSquareShaped ) } else -> this } internal fun DrawPathMode.isArrow(): Boolean = this is DrawPathMode.PointingArrow || this is DrawPathMode.LinePointingArrow || this is DrawPathMode.DoublePointingArrow || this is DrawPathMode.DoubleLinePointingArrow internal fun DrawPathMode.isRect(): Boolean = this is DrawPathMode.Rect || this is DrawPathMode.OutlinedRect internal fun DrawPathMode.isPolygon(): Boolean = this is DrawPathMode.Polygon || this is DrawPathMode.OutlinedPolygon internal fun DrawPathMode.isStar(): Boolean = this is DrawPathMode.Star || this is DrawPathMode.OutlinedStar internal fun DrawPathMode.isFloodFill(): Boolean = this is DrawPathMode.FloodFill internal fun DrawPathMode.isSpray(): Boolean = this is DrawPathMode.Spray internal fun DrawPathMode.getSubtitle(): Int = when (this) { is DrawPathMode.DoubleLinePointingArrow -> R.string.double_line_arrow_sub is DrawPathMode.DoublePointingArrow -> R.string.double_arrow_sub DrawPathMode.Free -> R.string.free_drawing_sub DrawPathMode.Line -> R.string.line_sub is DrawPathMode.LinePointingArrow -> R.string.line_arrow_sub is DrawPathMode.PointingArrow -> R.string.arrow_sub is DrawPathMode.OutlinedOval -> R.string.outlined_oval_sub is DrawPathMode.OutlinedRect -> R.string.outlined_rect_sub DrawPathMode.Oval -> R.string.oval_sub is DrawPathMode.Rect -> R.string.rect_sub DrawPathMode.Lasso -> R.string.lasso_sub is DrawPathMode.OutlinedTriangle -> R.string.outlined_triangle_sub DrawPathMode.Triangle -> R.string.triangle_sub is DrawPathMode.Polygon -> R.string.polygon_sub is DrawPathMode.OutlinedPolygon -> R.string.outlined_polygon_sub is DrawPathMode.OutlinedStar -> R.string.outlined_star_sub is DrawPathMode.Star -> R.string.star_sub is DrawPathMode.FloodFill -> R.string.flood_fill_sub is DrawPathMode.Spray -> R.string.spray_sub } internal fun DrawPathMode.getTitle(): Int = when (this) { is DrawPathMode.DoubleLinePointingArrow -> R.string.double_line_arrow is DrawPathMode.DoublePointingArrow -> R.string.double_arrow DrawPathMode.Free -> R.string.free_drawing DrawPathMode.Line -> R.string.line is DrawPathMode.LinePointingArrow -> R.string.line_arrow is DrawPathMode.PointingArrow -> R.string.arrow is DrawPathMode.OutlinedOval -> R.string.outlined_oval is DrawPathMode.OutlinedRect -> R.string.outlined_rect DrawPathMode.Oval -> R.string.oval is DrawPathMode.Rect -> R.string.rect DrawPathMode.Lasso -> R.string.lasso is DrawPathMode.OutlinedTriangle -> R.string.outlined_triangle DrawPathMode.Triangle -> R.string.triangle is DrawPathMode.Polygon -> R.string.polygon is DrawPathMode.OutlinedPolygon -> R.string.outlined_polygon is DrawPathMode.OutlinedStar -> R.string.outlined_star is DrawPathMode.Star -> R.string.star is DrawPathMode.FloodFill -> R.string.flood_fill is DrawPathMode.Spray -> R.string.spray } internal fun DrawPathMode.getIcon(): ImageVector = when (this) { is DrawPathMode.DoubleLinePointingArrow -> Icons.Rounded.LineDoubleArrow is DrawPathMode.DoublePointingArrow -> Icons.Rounded.FreeDoubleArrow DrawPathMode.Free -> Icons.Rounded.FreeDraw DrawPathMode.Line -> Icons.Rounded.Line is DrawPathMode.LinePointingArrow -> Icons.Rounded.LineArrow is DrawPathMode.PointingArrow -> Icons.Rounded.FreeArrow is DrawPathMode.OutlinedOval -> Icons.Rounded.RadioButtonUnchecked is DrawPathMode.OutlinedRect -> Icons.Rounded.CheckBoxOutlineBlank DrawPathMode.Oval -> Icons.Rounded.Circle is DrawPathMode.Rect -> Icons.Rounded.Square DrawPathMode.Lasso -> Icons.Rounded.Lasso DrawPathMode.Triangle -> Icons.Rounded.Triangle is DrawPathMode.OutlinedTriangle -> Icons.Outlined.Triangle is DrawPathMode.Polygon -> Icons.Rounded.Polygon is DrawPathMode.OutlinedPolygon -> Icons.Outlined.Polygon is DrawPathMode.OutlinedStar -> Icons.Rounded.StarOutline is DrawPathMode.Star -> Icons.Rounded.Star is DrawPathMode.FloodFill -> Icons.Rounded.FloodFill is DrawPathMode.Spray -> Icons.Outlined.Spray } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawRepeatedPath.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Matrix import android.graphics.Paint import android.graphics.Path import android.graphics.PathMeasure import androidx.core.graphics.withSave import kotlin.math.atan2 fun Canvas.drawRepeatedTextOnPath( text: String, path: Path, paint: Paint, interval: Float = 0f ) { val pathMeasure = PathMeasure(path, false) val pathLength = pathMeasure.length val textWidth = paint.measureText(text) val fullRepeats = (pathLength / (textWidth + interval)).toInt() val remainingLength = pathLength - fullRepeats * (textWidth + interval) var distance = 0f repeat(fullRepeats) { drawTextOnPath(text, path, distance, 0f, paint) distance += (textWidth + interval) } if (remainingLength > 0f) { val ratio = (textWidth + interval - (remainingLength)) / (textWidth + interval) val endOffset = (text.length - (text.length * ratio).toInt()).coerceAtLeast(0) drawTextOnPath(text.substring(0, endOffset), path, distance, 0f, paint) } } fun Canvas.drawRepeatedBitmapOnPath( bitmap: Bitmap, path: Path, paint: Paint, interval: Float = 0f ) { val pathMeasure = PathMeasure(path, false) val pathLength = pathMeasure.length val bitmapWidth = bitmap.width.toFloat() val bitmapHeight = bitmap.height.toFloat() var distance = 0f val matrix = Matrix() while (distance < pathLength) { val pos = FloatArray(2) val tan = FloatArray(2) pathMeasure.getPosTan(distance, pos, tan) val degree = Math.toDegrees(atan2(tan[1].toDouble(), tan[0].toDouble())).toFloat() withSave { translate(pos[0], pos[1]) rotate(degree) matrix.reset() matrix.postTranslate(-bitmapWidth / 2, -bitmapHeight / 2) matrix.postRotate(degree) matrix.postTranslate(bitmapWidth / 2, bitmapHeight / 2) drawBitmap(bitmap, matrix, paint) } if (interval < 0 && distance + bitmapWidth < 0) break else { distance += (bitmapWidth + interval) } } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/DrawUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.graphics.BlurMaskFilter import android.graphics.Canvas import android.graphics.Matrix import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.PaintingStyle import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.StampedPathEffectStyle import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.addOutline import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.nativePaint import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.LayoutDirection import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import coil3.imageLoader import coil3.request.ImageRequest import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.max import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiNativeStackBlurFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiPixelationFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.density import com.t8rin.imagetoolbox.core.ui.widget.modifier.Line import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.toTypeface import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import kotlin.math.roundToInt import kotlin.math.sqrt import android.graphics.Canvas as NativeCanvas import android.graphics.Paint as NativePaint import android.graphics.Path as NativePath /** * Needed to trigger recomposition **/ fun ImageBitmap.copy(): ImageBitmap = asAndroidBitmap().asImageBitmap() internal fun Path.copy(): Path = copyAsAndroidPath().asComposePath() internal fun Path.copyAsAndroidPath(): NativePath = NativePath(this.asAndroidPath()) internal fun NativePath.mirror( x: Float, y: Float, x1: Float, y1: Float ): NativePath { val dx = x1 - x val dy = y1 - y val lengthSq = dx * dx + dy * dy val matrix = Matrix().apply { setValues( floatArrayOf( (dx * dx - dy * dy) / lengthSq, (2 * dx * dy) / lengthSq, (2 * x * dy * dy - 2 * y * dx * dy) / lengthSq, (2 * dx * dy) / lengthSq, (dy * dy - dx * dx) / lengthSq, (2 * y * dx * dx - 2 * x * dx * dy) / lengthSq, 0f, 0f, 1f ) ) } val mirroredPath = NativePath() this.transform(matrix, mirroredPath) return mirroredPath } @Suppress("unused") internal fun Path.mirrorIfNeeded( canvasSize: IntegerSize, mirroringLines: List ): Path = asAndroidPath().mirrorIfNeeded( canvasSize = canvasSize, mirroringLines = mirroringLines ).asComposePath() internal fun NativePath.mirrorIfNeeded( canvasSize: IntegerSize, mirroringLines: List ): NativePath = if (mirroringLines.isNotEmpty()) { NativePath(this).apply { mirroringLines.forEach { mirroringLine -> addPath( mirror( x = mirroringLine.startX * canvasSize.width, y = mirroringLine.startY * canvasSize.height, x1 = mirroringLine.endX * canvasSize.width, y1 = mirroringLine.endY * canvasSize.height ) ) } } } else { this } @Suppress("unused") internal fun Path.mirror( x: Float, y: Float, x1: Float, y1: Float ): Path = asAndroidPath().mirror( x = x, y = y, x1 = x1, y1 = y1 ).asComposePath() @Suppress("unused") fun Canvas.drawInfiniteLine( line: Line, paint: NativePaint = NativePaint().apply { color = Color.Red.toArgb() style = NativePaint.Style.STROKE strokeWidth = 5f } ) { val width = width.toFloat() val height = height.toFloat() val startX = line.startX * width val startY = line.startY * height val endX = line.endX * width val endY = line.endY * height val dx = endX - startX val dy = endY - startY if (dx == 0f) { drawLine(startX, 0f, startX, height, paint) return } if (dy == 0f) { drawLine(0f, startY, width, startY, paint) return } val directionX = dx / sqrt(dx * dx + dy * dy) val directionY = dy / sqrt(dx * dx + dy * dy) val scale = maxOf(width, height) * 2 val extendedStartX = startX - directionX * scale val extendedStartY = startY - directionY * scale val extendedEndX = endX + directionX * scale val extendedEndY = endY + directionY * scale drawLine(extendedStartX, extendedStartY, extendedEndX, extendedEndY, paint) } internal fun ImageBitmap.clipBitmap( path: Path, paint: Paint, ): ImageBitmap = asAndroidBitmap() .let { it.copy(it.safeConfig, true) } .applyCanvas { drawPath( NativePath(path.asAndroidPath()).apply { fillType = NativePath.FillType.INVERSE_WINDING }, paint.nativePaint ) }.asImageBitmap() internal fun ImageBitmap.overlay(overlay: ImageBitmap): ImageBitmap { val image = this.asAndroidBitmap() return createBitmap( width = image.width, height = image.height, config = image.safeConfig ).applyCanvas { drawBitmap(image) drawBitmap(overlay.asAndroidBitmap()) }.asImageBitmap() } @Composable internal fun rememberPaint( strokeWidth: Pt, isEraserOn: Boolean, drawColor: Color, brushSoftness: Pt, drawMode: DrawMode, canvasSize: IntegerSize, drawPathMode: DrawPathMode, drawLineStyle: DrawLineStyle ): State { val context = LocalContext.current return remember( strokeWidth, isEraserOn, drawColor, brushSoftness, drawMode, canvasSize, drawPathMode, context, drawLineStyle ) { derivedStateOf { val isSharpEdge = drawPathMode.isSharpEdge val isFilled = drawPathMode.isFilled Paint().apply { if (drawMode !is DrawMode.Text && drawMode !is DrawMode.Image) { pathEffect = drawLineStyle.asPathEffect( canvasSize = canvasSize, strokeWidth = strokeWidth.toPx(canvasSize), context = context ) } blendMode = if (!isEraserOn) blendMode else BlendMode.Clear if (isEraserOn) { style = PaintingStyle.Stroke this.strokeWidth = strokeWidth.toPx(canvasSize) strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } else { if (drawMode !is DrawMode.Text) { if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke this.strokeWidth = drawPathMode.convertStrokeWidth( strokeWidth = strokeWidth, canvasSize = canvasSize ) if (drawMode is DrawMode.Highlighter || isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } } } color = if (drawMode is DrawMode.PathEffect) { Color.Transparent } else drawColor alpha = drawColor.alpha }.nativePaint.apply { if (drawMode is DrawMode.Neon && !isEraserOn) { this.color = Color.White.toArgb() setShadowLayer( brushSoftness.toPx(canvasSize), 0f, 0f, drawColor .copy(alpha = .8f) .toArgb() ) } else if (brushSoftness.value > 0f) { maskFilter = BlurMaskFilter( brushSoftness.toPx(canvasSize), BlurMaskFilter.Blur.NORMAL ) } if (drawMode is DrawMode.Text && !isEraserOn) { isAntiAlias = true textSize = strokeWidth.toPx(canvasSize) typeface = drawMode.font.toTypeface() } } } } } fun pathEffectPaint( strokeWidth: Pt, drawPathMode: DrawPathMode, canvasSize: IntegerSize, ): NativePaint { val isSharpEdge = drawPathMode.isSharpEdge val isFilled = drawPathMode.isFilled return Paint().apply { if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke this.strokeWidth = strokeWidth.toPx(canvasSize) if (isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } color = Color.Transparent blendMode = BlendMode.Clear }.nativePaint } @Composable fun rememberPathEffectPaint( strokeWidth: Pt, drawPathMode: DrawPathMode, canvasSize: IntegerSize, ): State = remember( strokeWidth, drawPathMode, canvasSize ) { derivedStateOf { pathEffectPaint( strokeWidth = strokeWidth, drawPathMode = drawPathMode, canvasSize = canvasSize ) } } internal fun DrawLineStyle.asPathEffect( canvasSize: IntegerSize, strokeWidth: Float, context: Context ): PathEffect? = when (this) { is DrawLineStyle.Dashed -> { PathEffect.dashPathEffect( intervals = floatArrayOf( size.toPx(canvasSize), gap.toPx(canvasSize) + strokeWidth ), phase = 0f ) } DrawLineStyle.DotDashed -> { val dashOnInterval1 = strokeWidth * 4 val dashOffInterval1 = strokeWidth * 2 val dashOnInterval2 = strokeWidth / 4 val dashOffInterval2 = strokeWidth * 2 PathEffect.dashPathEffect( intervals = floatArrayOf( dashOnInterval1, dashOffInterval1, dashOnInterval2, dashOffInterval2 ), phase = 0f ) } is DrawLineStyle.Stamped<*> -> { fun Shape.toPath(): Path = Path().apply { addOutline( createOutline( size = Size(strokeWidth, strokeWidth), layoutDirection = LayoutDirection.Ltr, density = context.density ) ) } val path: Path? = when (shape) { is Shape -> shape.toPath() is NativePath -> shape.asComposePath() is Path -> shape null -> MaterialStarShape.toPath() else -> null } path?.let { PathEffect.stampedPathEffect( shape = it, advance = spacing.toPx(canvasSize) + strokeWidth, phase = 0f, style = StampedPathEffectStyle.Morph ) } } is DrawLineStyle.ZigZag -> { val zigZagPath = Path().apply { val zigZagLineWidth = strokeWidth / heightRatio val shapeVerticalOffset = (strokeWidth / 2) / 2 val shapeHorizontalOffset = (strokeWidth / 2) / 2 moveTo(0f, 0f) lineTo(strokeWidth / 2, strokeWidth / 2) lineTo(strokeWidth, 0f) lineTo(strokeWidth, 0f + zigZagLineWidth) lineTo(strokeWidth / 2, strokeWidth / 2 + zigZagLineWidth) lineTo(0f, 0f + zigZagLineWidth) translate(Offset(-shapeHorizontalOffset, -shapeVerticalOffset)) } PathEffect.stampedPathEffect( shape = zigZagPath, advance = strokeWidth, phase = 0f, style = StampedPathEffectStyle.Morph ) } DrawLineStyle.None -> null } @SuppressLint("ComposableNaming") @Composable internal fun NativeCanvas.drawRepeatedImageOnPath( drawMode: DrawMode.Image, strokeWidth: Pt, canvasSize: IntegerSize, path: NativePath, paint: NativePaint, invalidations: Int ) { var pathImage by remember(strokeWidth, canvasSize, drawMode.imageData) { mutableStateOf(null) } LaunchedEffect(pathImage, drawMode.imageData, strokeWidth, canvasSize, invalidations) { if (pathImage == null) { pathImage = appContext.imageLoader.execute( ImageRequest.Builder(appContext) .data(drawMode.imageData) .size(strokeWidth.toPx(canvasSize).roundToInt()) .build() ).image?.toBitmap() } } pathImage?.let { bitmap -> drawRepeatedBitmapOnPath( bitmap = bitmap, path = path, paint = paint, interval = drawMode.repeatingInterval.toPx(canvasSize) ) } } internal fun transformationsForMode( drawMode: DrawMode, canvasSize: IntegerSize ): List> = when (drawMode) { is DrawMode.PathEffect.PrivacyBlur -> { listOf( UiNativeStackBlurFilter( value = drawMode.blurRadius.toFloat() / 1000 * max(canvasSize) ) ) } is DrawMode.PathEffect.Pixelation -> { listOf( UiNativeStackBlurFilter( value = 20f / 1000 * max(canvasSize) ), UiPixelationFilter( value = drawMode.pixelSize / 1000 * max(canvasSize) ) ) } is DrawMode.PathEffect.Custom -> { drawMode.filter?.let { listOf(it.toUiFilter()) } ?: emptyList() } else -> emptyList() } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/FloodFill.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import android.graphics.Bitmap import android.graphics.Color import android.graphics.Path import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asComposePath import com.t8rin.logger.makeLog import java.util.LinkedList import java.util.Queue import kotlin.math.roundToInt import androidx.compose.ui.graphics.Path as ComposePath internal class FloodFill(image: Bitmap) { private val path = Path() private val width: Int = image.width private val height: Int = image.height private val pixels: IntArray = IntArray(width * height) private lateinit var pixelsChecked: BooleanArray private lateinit var ranges: Queue private var tolerance = 0 private var startColorRed = 0 private var startColorGreen = 0 private var startColorBlue = 0 private var startColorAlpha = 0 init { image.getPixels(pixels, 0, width, 0, 0, width, height) } private fun prepare() { // Called before starting flood-fill pixelsChecked = BooleanArray(pixels.size) ranges = LinkedList() } // Fills the specified point on the bitmap with the currently selected fill color. // int x, int y: The starting coordinates for the fill fun performFloodFill( x: Int, y: Int, tolerance: Float ): Path? { if (x >= width || y >= height || x < 0 || y < 0) return null path.rewind() this.tolerance = (tolerance * 255).roundToInt().coerceIn(0, 255) // Setup prepare() // Get starting color. val startPixel = pixels.getOrNull(width * y + x) ?: return null startColorRed = Color.red(startPixel) startColorGreen = Color.green(startPixel) startColorBlue = Color.blue(startPixel) startColorAlpha = Color.alpha(startPixel) // Do first call to flood-fill. linearFill(x, y) // Call flood-fill routine while flood-fill ranges still exist on the queue var range: FloodFillRange while (ranges.isNotEmpty()) { // Get Next Range Off the Queue range = ranges.remove() // Check Above and Below Each Pixel in the flood-fill Range var downPxIdx = width * (range.Y + 1) + range.startX var upPxIdx = width * (range.Y - 1) + range.startX val upY = range.Y - 1 // so we can pass the y coordinate by ref val downY = range.Y + 1 for (i in range.startX..range.endX) { // Start Fill Upwards // if we're not above the top of the bitmap and the pixel above this one is within the color tolerance if (range.Y > 0 && !pixelsChecked[upPxIdx] && isPixelColorWithinTolerance(upPxIdx)) { linearFill(i, upY) } // Start Fill Downwards // if we're not below the bottom of the bitmap and the pixel below this one is within the color tolerance if ( range.Y < height - 1 && !pixelsChecked[downPxIdx] && isPixelColorWithinTolerance(downPxIdx) ) { linearFill(i, downY) } downPxIdx++ upPxIdx++ } } return path } // Finds the furthermost left and right boundaries of the fill area // on a given y coordinate, starting from a given x coordinate, filling as it goes. // Adds the resulting horizontal range to the queue of flood-fill ranges, // to be processed in the main loop. // // int x, int y: The starting coordinates private fun linearFill(x: Int, y: Int) { // Find Left Edge of Color Area var lFillLoc = x // the location to check/fill on the left var pxIdx = width * y + x path.moveTo(x.toFloat(), y.toFloat()) while (true) { pixelsChecked[pxIdx] = true lFillLoc-- pxIdx-- // exit loop if we're at edge of bitmap or color area if (lFillLoc < 0 || pixelsChecked[pxIdx] || !isPixelColorWithinTolerance(pxIdx)) { break } } vectorFill(pxIdx + 1) lFillLoc++ // Find Right Edge of Color Area var rFillLoc = x // the location to check/fill on the left pxIdx = width * y + x while (true) { pixelsChecked[pxIdx] = true rFillLoc++ pxIdx++ if (rFillLoc >= width || pixelsChecked[pxIdx] || !isPixelColorWithinTolerance(pxIdx)) { break } } vectorFill(pxIdx - 1) rFillLoc-- // add range to queue val r = FloodFillRange(lFillLoc, rFillLoc, y) ranges.offer(r) } // vector fill pixels with color private fun vectorFill(pxIndex: Int) { val x = (pxIndex % width).toFloat() val y = (pxIndex - x) / width path.lineTo(x, y) } // Sees if a pixel is within the color tolerance range. private fun isPixelColorWithinTolerance(px: Int): Boolean { val alpha = pixels[px] ushr 24 and 0xff val red = pixels[px] ushr 16 and 0xff val green = pixels[px] ushr 8 and 0xff val blue = pixels[px] and 0xff return alpha >= startColorAlpha - tolerance && alpha <= startColorAlpha + tolerance && red >= startColorRed - tolerance && red <= startColorRed + tolerance && green >= startColorGreen - tolerance && green <= startColorGreen + tolerance && blue >= startColorBlue - tolerance && blue <= startColorBlue + tolerance } // Represents a linear range to be filled and branched from. private data class FloodFillRange( val startX: Int, val endX: Int, val Y: Int ) } fun ImageBitmap.floodFill( offset: Offset, tolerance: Float ): ComposePath? = runCatching { FloodFill( asAndroidBitmap().copy(Bitmap.Config.ARGB_8888, false) ).performFloodFill( x = offset.x.roundToInt().coerceIn(0, width - 1), y = offset.y.roundToInt().coerceIn(0, height - 1), tolerance = tolerance )?.asComposePath() }.onFailure { it.makeLog("floodFill") }.getOrNull() ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/MotionEvent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils enum class MotionEvent { Idle, Down, Move, Up; } inline fun MotionEvent.handle( onDown: () -> Unit, onMove: () -> Unit, onUp: () -> Unit ) { when (this) { MotionEvent.Down -> onDown() MotionEvent.Move -> onMove() MotionEvent.Up -> onUp() else -> Unit } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PathHelper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import android.graphics.Matrix import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.takeOrElse import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathMeasure import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.ui.utils.helper.rotate import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import kotlin.math.abs import kotlin.math.cos import kotlin.math.max import kotlin.math.min import kotlin.math.sin import kotlin.math.sqrt import kotlin.random.Random data class PathHelper( val drawDownPosition: Offset, val currentDrawPosition: Offset, val onPathChange: (Path) -> Unit, val strokeWidth: Pt, val canvasSize: IntegerSize, val drawPathMode: DrawPathMode, val isEraserOn: Boolean, val drawMode: DrawMode ) { private val strokeWidthSized = strokeWidth.toPx(canvasSize) private val drawArrowsScope by lazy { object : DrawArrowsScope { override fun drawArrowsIfNeeded( drawPath: Path, ) { fun drawStartEndArrows( sizeScale: Float = 3f, angle: Float = 150f ) { drawEndArrow( drawPath = drawPath, arrowSize = sizeScale, arrowAngle = angle.toDouble() ) drawStartArrow( drawPath = drawPath, arrowSize = sizeScale, arrowAngle = angle.toDouble() ) } when (drawPathMode) { is DrawPathMode.DoublePointingArrow -> { drawStartEndArrows( sizeScale = drawPathMode.sizeScale, angle = drawPathMode.angle ) } is DrawPathMode.DoubleLinePointingArrow -> { drawStartEndArrows( sizeScale = drawPathMode.sizeScale, angle = drawPathMode.angle ) } is DrawPathMode.PointingArrow -> { drawEndArrow( drawPath = drawPath, arrowSize = drawPathMode.sizeScale, arrowAngle = drawPathMode.angle.toDouble() ) } is DrawPathMode.LinePointingArrow -> { drawEndArrow( drawPath = drawPath, arrowSize = drawPathMode.sizeScale, arrowAngle = drawPathMode.angle.toDouble() ) } else -> Unit } } private fun drawEndArrow( drawPath: Path, arrowSize: Float = 3f, arrowAngle: Double = 150.0 ) { val (preLastPoint, lastPoint) = PathMeasure().apply { setPath(drawPath, false) }.let { Pair( it.getPosition(it.length - strokeWidthSized * arrowSize) .takeOrElse { Offset.Zero }, it.getPosition(it.length).takeOrElse { Offset.Zero } ) } val arrowVector = lastPoint - preLastPoint fun drawArrow() { val (rx1, ry1) = arrowVector.rotate(arrowAngle) val (rx2, ry2) = arrowVector.rotate(360 - arrowAngle) drawPath.apply { relativeLineTo(rx1, ry1) moveTo(lastPoint.x, lastPoint.y) relativeLineTo(rx2, ry2) } } if (abs(arrowVector.x) < arrowSize * strokeWidthSized && abs(arrowVector.y) < arrowSize * strokeWidthSized && preLastPoint != Offset.Zero ) { drawArrow() } } private fun drawStartArrow( drawPath: Path, arrowSize: Float = 3f, arrowAngle: Double = 150.0 ) { val (firstPoint, secondPoint) = PathMeasure().apply { setPath(drawPath, false) }.let { Pair( it.getPosition(0f).takeOrElse { Offset.Zero }, it.getPosition(strokeWidthSized * arrowSize) .takeOrElse { Offset.Zero } ) } val arrowVector = firstPoint - secondPoint fun drawArrow() { val (rx1, ry1) = arrowVector.rotate(arrowAngle) val (rx2, ry2) = arrowVector.rotate(360 - arrowAngle) drawPath.apply { moveTo(firstPoint.x, firstPoint.y) relativeLineTo(rx1, ry1) moveTo(firstPoint.x, firstPoint.y) relativeLineTo(rx2, ry2) } } if (abs(arrowVector.x) < arrowSize * strokeWidthSized && abs(arrowVector.y) < arrowSize * strokeWidthSized && secondPoint != Offset.Zero ) { drawArrow() } } } } fun drawPolygon( vertices: Int, rotationDegrees: Int, isRegular: Boolean, ) { if (drawDownPosition.isSpecified && currentDrawPosition.isSpecified) { val top = max(drawDownPosition.y, currentDrawPosition.y) val left = min(drawDownPosition.x, currentDrawPosition.x) val bottom = min(drawDownPosition.y, currentDrawPosition.y) val right = max(drawDownPosition.x, currentDrawPosition.x) val width = right - left val height = bottom - top val centerX = (left + right) / 2f val centerY = (top + bottom) / 2f val radius = min(width, height) / 2f val newPath = Path().apply { if (isRegular) { val angleStep = 360f / vertices val startAngle = rotationDegrees - 270.0 moveTo( centerX + radius * cos(Math.toRadians(startAngle)).toFloat(), centerY + radius * sin(Math.toRadians(startAngle)).toFloat() ) for (i in 1 until vertices) { val angle = startAngle + i * angleStep lineTo( centerX + radius * cos(Math.toRadians(angle)).toFloat(), centerY + radius * sin(Math.toRadians(angle)).toFloat() ) } } else { for (i in 0 until vertices) { val angle = i * (360f / vertices) + rotationDegrees - 270.0 val x = centerX + width / 2f * cos(Math.toRadians(angle)).toFloat() val y = centerY + height / 2f * sin(Math.toRadians(angle)).toFloat() if (i == 0) { moveTo(x, y) } else { lineTo(x, y) } } } close() } onPathChange(newPath) } } fun drawStar( vertices: Int, innerRadiusRatio: Float, rotationDegrees: Int, isRegular: Boolean, ) { if (drawDownPosition.isSpecified && currentDrawPosition.isSpecified) { val top = max(drawDownPosition.y, currentDrawPosition.y) val left = min(drawDownPosition.x, currentDrawPosition.x) val bottom = min(drawDownPosition.y, currentDrawPosition.y) val right = max(drawDownPosition.x, currentDrawPosition.x) val centerX = (left + right) / 2f val centerY = (top + bottom) / 2f val width = right - left val height = bottom - top val newPath = Path().apply { if (isRegular) { val outerRadius = min(width, height) / 2f val innerRadius = outerRadius * innerRadiusRatio val angleStep = 360f / (2 * vertices) val startAngle = rotationDegrees - 270.0 for (i in 0 until (2 * vertices)) { val radius = if (i % 2 == 0) outerRadius else innerRadius val angle = startAngle + i * angleStep val x = centerX + radius * cos(Math.toRadians(angle)).toFloat() val y = centerY + radius * sin(Math.toRadians(angle)).toFloat() if (i == 0) { moveTo(x, y) } else { lineTo(x, y) } } } else { for (i in 0 until (2 * vertices)) { val angle = i * (360f / (2 * vertices)) + rotationDegrees - 270.0 val radiusX = (if (i % 2 == 0) width else width * innerRadiusRatio) / 2f val radiusY = (if (i % 2 == 0) height else height * innerRadiusRatio) / 2f val x = centerX + radiusX * cos(Math.toRadians(angle)).toFloat() val y = centerY + radiusY * sin(Math.toRadians(angle)).toFloat() if (i == 0) { moveTo(x, y) } else { lineTo(x, y) } } } close() } onPathChange(newPath) } } fun drawTriangle() { if (drawDownPosition.isSpecified && currentDrawPosition.isSpecified) { val newPath = Path().apply { moveTo(drawDownPosition.x, drawDownPosition.y) lineTo(currentDrawPosition.x, drawDownPosition.y) lineTo( (drawDownPosition.x + currentDrawPosition.x) / 2, currentDrawPosition.y ) lineTo(drawDownPosition.x, drawDownPosition.y) close() } onPathChange(newPath) } } fun drawRect( rotationDegrees: Int, cornerRadius: Float ) { if (!drawDownPosition.isSpecified || !currentDrawPosition.isSpecified) return val left = min(drawDownPosition.x, currentDrawPosition.x) val right = max(drawDownPosition.x, currentDrawPosition.x) val top = min(drawDownPosition.y, currentDrawPosition.y) val bottom = max(drawDownPosition.y, currentDrawPosition.y) val width = right - left val height = bottom - top if (width <= 0f || height <= 0f) return val radius = min(width, height) * cornerRadius.coerceIn(0f, 0.5f) val centerX = (left + right) / 2f val centerY = (top + bottom) / 2f val path = Path().apply { addRoundRect( RoundRect( rect = Rect(left, top, right, bottom), radius, radius ) ) } val matrix = Matrix().apply { setRotate(rotationDegrees.toFloat(), centerX, centerY) } onPathChange( path.asAndroidPath().apply { transform(matrix) }.asComposePath() ) } fun drawOval() { if (drawDownPosition.isSpecified && currentDrawPosition.isSpecified) { val newPath = Path().apply { addOval( Rect( top = max( drawDownPosition.y, currentDrawPosition.y ), left = min( drawDownPosition.x, currentDrawPosition.x ), bottom = min( drawDownPosition.y, currentDrawPosition.y ), right = max( drawDownPosition.x, currentDrawPosition.x ), ) ) } onPathChange(newPath) } } fun drawLine() { if (drawDownPosition.isSpecified && currentDrawPosition.isSpecified) { val newPath = Path().apply { moveTo(drawDownPosition.x, drawDownPosition.y) lineTo(currentDrawPosition.x, currentDrawPosition.y) } drawArrowsScope.drawArrowsIfNeeded(newPath) onPathChange(newPath) } } fun drawPath( currentDrawPath: Path? = null, onDrawFreeArrow: DrawArrowsScope.() -> Unit = {}, onBaseDraw: () -> Unit = {}, onFloodFill: (tolerance: Float) -> Unit = {} ) { if (!isEraserOn && drawMode !is DrawMode.Warp) { when (drawPathMode) { is DrawPathMode.PointingArrow, is DrawPathMode.DoublePointingArrow -> onDrawFreeArrow(drawArrowsScope) is DrawPathMode.DoubleLinePointingArrow, DrawPathMode.Line, is DrawPathMode.LinePointingArrow -> drawLine() is DrawPathMode.Rect -> drawRect( rotationDegrees = drawPathMode.rotationDegrees, cornerRadius = drawPathMode.cornerRadius ) is DrawPathMode.OutlinedRect -> drawRect( rotationDegrees = drawPathMode.rotationDegrees, cornerRadius = drawPathMode.cornerRadius ) is DrawPathMode.Triangle, is DrawPathMode.OutlinedTriangle -> drawTriangle() is DrawPathMode.Polygon -> { drawPolygon( vertices = drawPathMode.vertices, rotationDegrees = drawPathMode.rotationDegrees, isRegular = drawPathMode.isRegular ) } is DrawPathMode.OutlinedPolygon -> { drawPolygon( vertices = drawPathMode.vertices, rotationDegrees = drawPathMode.rotationDegrees, isRegular = drawPathMode.isRegular ) } is DrawPathMode.Star -> { drawStar( vertices = drawPathMode.vertices, innerRadiusRatio = drawPathMode.innerRadiusRatio, rotationDegrees = drawPathMode.rotationDegrees, isRegular = drawPathMode.isRegular ) } is DrawPathMode.OutlinedStar -> { drawStar( vertices = drawPathMode.vertices, innerRadiusRatio = drawPathMode.innerRadiusRatio, rotationDegrees = drawPathMode.rotationDegrees, isRegular = drawPathMode.isRegular ) } is DrawPathMode.Oval, is DrawPathMode.OutlinedOval -> drawOval() is DrawPathMode.Free, is DrawPathMode.Lasso -> onBaseDraw() is DrawPathMode.FloodFill -> onFloodFill(drawPathMode.tolerance) is DrawPathMode.Spray -> { currentDrawPath?.let { val path = currentDrawPath.copy().apply { repeat(drawPathMode.density) { val angle = Random.nextFloat() * PI_2 val radius = sqrt(Random.nextFloat()) * strokeWidthSized val x = currentDrawPosition.x + radius * cos(angle) val y = currentDrawPosition.y + radius * sin(angle) val rect = Rect( left = x, top = y, right = x + drawPathMode.pixelSize, bottom = y + drawPathMode.pixelSize ) if (drawPathMode.isSquareShaped) addRect(rect) else addOval(rect) } } onPathChange(path) } } } } else onBaseDraw() } } private const val PI_2 = (Math.PI * 2).toFloat() interface DrawArrowsScope { fun drawArrowsIfNeeded( drawPath: Path, ) } @Composable fun rememberPathHelper( drawDownPosition: Offset, currentDrawPosition: Offset, onPathChange: (Path) -> Unit, strokeWidth: Pt, canvasSize: IntegerSize, drawPathMode: DrawPathMode, isEraserOn: Boolean, drawMode: DrawMode ): State = remember( drawDownPosition, currentDrawPosition, onPathChange, strokeWidth, canvasSize, drawPathMode, isEraserOn, drawMode ) { derivedStateOf { PathHelper( drawDownPosition = drawDownPosition, currentDrawPosition = currentDrawPosition, onPathChange = onPathChange, strokeWidth = strokeWidth, canvasSize = canvasSize, drawPathMode = drawPathMode, isEraserOn = isEraserOn, drawMode = drawMode ) } } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/components/utils/PointerDraw.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.components.utils import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.magnifier import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import com.t8rin.gesture.pointerMotionEvents import com.t8rin.imagetoolbox.core.ui.widget.modifier.observePointersCountWithOffset import com.t8rin.imagetoolbox.core.ui.widget.modifier.smartDelayAfterDownInMillis import net.engawapg.lib.zoomable.ZoomState import net.engawapg.lib.zoomable.ZoomableDefaults.defaultZoomOnDoubleTap import net.engawapg.lib.zoomable.zoomable fun Modifier.pointerDrawObserver( magnifierEnabled: Boolean, currentDrawPosition: Offset, zoomState: ZoomState, globalTouchPointersCount: MutableIntState, panEnabled: Boolean ) = this.composed { var globalTouchPosition by remember { mutableStateOf(Offset.Unspecified) } Modifier .fillMaxSize() .observePointersCountWithOffset { size, offset -> globalTouchPointersCount.intValue = size globalTouchPosition = offset } .then( if (magnifierEnabled) { Modifier.magnifier( sourceCenter = { if (currentDrawPosition.isSpecified) { globalTouchPosition } else Offset.Unspecified }, magnifierCenter = { globalTouchPosition - Offset(0f, 100.dp.toPx()) }, size = DpSize(height = 100.dp, width = 100.dp), cornerRadius = 50.dp, elevation = 2.dp ) } else Modifier ) .clipToBounds() .zoomable( zoomState = zoomState, zoomEnabled = (globalTouchPointersCount.intValue >= 2 || panEnabled), enableOneFingerZoom = panEnabled, onDoubleTap = { pos -> if (panEnabled) zoomState.defaultZoomOnDoubleTap(pos) } ) } fun Modifier.pointerDrawHandler( globalTouchPointersCount: MutableIntState, onReceiveMotionEvent: (MotionEvent) -> Unit, onInvalidate: () -> Unit, onUpdateCurrentDrawPosition: (Offset) -> Unit, onUpdateDrawDownPosition: (Offset) -> Unit, enabled: Boolean ) = if (enabled) { this.composed { var drawStartedWithOnePointer by remember { mutableStateOf(false) } Modifier.pointerMotionEvents( onDown = { pointerInputChange -> drawStartedWithOnePointer = globalTouchPointersCount.intValue <= 1 if (drawStartedWithOnePointer) { onReceiveMotionEvent(MotionEvent.Down) onUpdateCurrentDrawPosition(pointerInputChange.position) onUpdateDrawDownPosition(pointerInputChange.position) pointerInputChange.consume() onInvalidate() } }, onMove = { pointerInputChange -> if (drawStartedWithOnePointer) { onReceiveMotionEvent(MotionEvent.Move) onUpdateCurrentDrawPosition(pointerInputChange.position) pointerInputChange.consume() onInvalidate() } }, onUp = { pointerInputChange -> if (drawStartedWithOnePointer) { onReceiveMotionEvent(MotionEvent.Up) pointerInputChange.consume() onInvalidate() } drawStartedWithOnePointer = false }, delayAfterDownInMillis = smartDelayAfterDownInMillis(globalTouchPointersCount.intValue) ) } } else { this } ================================================ FILE: feature/draw/src/main/java/com/t8rin/imagetoolbox/feature/draw/presentation/screenLogic/DrawComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.draw.presentation.screenLogic import android.content.pm.ActivityInfo import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.childContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.feature.draw.domain.DrawBehavior import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawOnBackgroundParams import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.domain.ImageDrawApplier import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.withContext class DrawComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageTransformer: ImageTransformer, private val imageCompressor: ImageCompressor, private val imageDrawApplier: ImageDrawApplier, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val shareProvider: ImageShareProvider, private val filterProvider: FilterProvider, private val settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder, addFiltersSheetComponentFactory: AddFiltersSheetComponent.Factory, filterTemplateCreationSheetComponentFactory: FilterTemplateCreationSheetComponent.Factory ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } } val addFiltersSheetComponent: AddFiltersSheetComponent = addFiltersSheetComponentFactory( componentContext = componentContext.childContext( key = "addFilters" ) ) val filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent = filterTemplateCreationSheetComponentFactory( componentContext = componentContext.childContext( key = "filterTemplateCreationSheetComponentDraw" ) ) private val _drawOnBackgroundParams = fileController.savable( scope = componentScope, initial = DrawOnBackgroundParams.Default ) val drawOnBackgroundParams: DrawOnBackgroundParams by _drawOnBackgroundParams private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _backgroundColor: MutableState = mutableStateOf(Color.Transparent) val backgroundColor by _backgroundColor private val _colorPickerBitmap: MutableState = mutableStateOf(null) val colorPickerBitmap by _colorPickerBitmap private val _drawBehavior: MutableState = mutableStateOf(DrawBehavior.None) val drawBehavior: DrawBehavior by _drawBehavior private val _drawMode: MutableState = mutableStateOf(DrawMode.Pen) val drawMode: DrawMode by _drawMode private val _drawPathMode: MutableState = mutableStateOf(DrawPathMode.Free) val drawPathMode: DrawPathMode by _drawPathMode private val _drawLineStyle: MutableState = mutableStateOf(DrawLineStyle.None) val drawLineStyle: DrawLineStyle by _drawLineStyle private val _uri = mutableStateOf(Uri.EMPTY) val uri: Uri by _uri private val _paths = mutableStateOf(listOf()) val paths: List by _paths private val _lastPaths = mutableStateOf(listOf()) val lastPaths: List by _lastPaths private val _undonePaths = mutableStateOf(listOf()) val undonePaths: List by _undonePaths val havePaths: Boolean get() = paths.isNotEmpty() || lastPaths.isNotEmpty() || undonePaths.isNotEmpty() private val _imageFormat = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _saveExif: MutableState = mutableStateOf(false) val saveExif: Boolean by _saveExif private val _helperGridParams = fileController.savable( scope = componentScope, initial = HelperGridParams() ) val helperGridParams: HelperGridParams by _helperGridParams init { componentScope.launch { val settingsState = settingsProvider.getSettingsState() _drawPathMode.update { DrawPathMode.fromOrdinal(settingsState.defaultDrawPathMode) } } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmap( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true getDrawingBitmap()?.let { localBitmap -> parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ), originalUri = _uri.value.toString(), sequenceNumber = null, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = _saveExif.value, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.value = false } } private suspend fun calculateScreenOrientationBasedOnUri(uri: Uri): Int { val bmp = imageGetter.getImage(uri = uri.toString(), originalSize = false)?.image val imageRatio = (bmp?.width ?: 0) / (bmp?.height?.toFloat() ?: 1f) return if (imageRatio <= 1.05f) { ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } else { ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.value = imageFormat registerChanges() } fun setSaveExif(bool: Boolean) { _saveExif.value = bool registerChanges() } private fun updateBitmap(bitmap: Bitmap?) { componentScope.launch { _isImageLoading.value = true _bitmap.value = imageScaler.scaleUntilCanShow(bitmap) _isImageLoading.value = false } } fun setUri( uri: Uri ) { componentScope.launch { _paths.value = listOf() _lastPaths.value = listOf() _undonePaths.value = listOf() _isImageLoading.value = true _uri.value = uri if (drawBehavior !is DrawBehavior.Image) { _drawBehavior.update { DrawBehavior.Image(calculateScreenOrientationBasedOnUri(uri)) } } imageGetter.getImageData( uri = uri.toString(), size = 2500, onFailure = AppToastHost::showFailureToast )?.let { data -> updateBitmap(data.image) _imageFormat.update { data.imageInfo.imageFormat } } } } private suspend fun getDrawingBitmap(): Bitmap? = withContext(defaultDispatcher) { imageDrawApplier.applyDrawToImage( drawBehavior = drawBehavior.let { if (it is DrawBehavior.Background) it.copy(color = backgroundColor.toArgb()) else it }, pathPaints = paths, imageUri = _uri.value.toString() ) } fun openColorPicker() { componentScope.launch { _colorPickerBitmap.value = getDrawingBitmap() } } fun resetDrawBehavior() { _paths.value = listOf() _lastPaths.value = listOf() _undonePaths.value = listOf() _bitmap.value = null _drawBehavior.update { DrawBehavior.None } _drawPathMode.update { DrawPathMode.Free } _uri.value = Uri.EMPTY _backgroundColor.value = Color.Transparent registerChangesCleared() } fun startDrawOnBackground( reqWidth: Int, reqHeight: Int, color: Color, ) { val width = reqWidth.takeIf { it > 0 } ?: 1 val height = reqHeight.takeIf { it > 0 } ?: 1 val imageRatio = width / height.toFloat() _drawBehavior.update { DrawBehavior.Background( orientation = if (imageRatio <= 1f) { ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } else { ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE }, width = width, height = height, color = color.toArgb() ) } _backgroundColor.value = color componentScope.launch { val newValue = DrawOnBackgroundParams( width = width, height = height, color = color.toArgb() ) _drawOnBackgroundParams.update { newValue } } } fun shareBitmap() { savingJob = trackProgress { _isSaving.value = true getDrawingBitmap()?.let { shareProvider.shareImage( image = it, imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = it.width, height = it.height ), onComplete = AppToastHost::showConfetti ) } _isSaving.value = false } } fun updateBackgroundColor(color: Color) { _backgroundColor.value = color registerChanges() } fun clearDrawing() { if (paths.isNotEmpty()) { _lastPaths.value = paths _paths.value = listOf() _undonePaths.value = listOf() registerChanges() } } fun undo() { if (paths.isEmpty() && lastPaths.isNotEmpty()) { _paths.value = lastPaths _lastPaths.value = listOf() return } if (paths.isEmpty()) return val lastPath = paths.last() _paths.update { it - lastPath } _undonePaths.update { it + lastPath } registerChanges() } fun redo() { if (undonePaths.isEmpty()) return val lastPath = undonePaths.last() _paths.update { it + lastPath } _undonePaths.update { it - lastPath } registerChanges() } fun addPath(pathPaint: UiPathPaint) { _paths.update { it + pathPaint } _undonePaths.value = listOf() registerChanges() } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } suspend fun filter( bitmap: Bitmap, filters: List>, ): Bitmap? = imageTransformer.transform( image = bitmap, transformations = filters.map { filterProvider.filterToTransformation(it) } ) fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true getDrawingBitmap()?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = image.width, height = image.height ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun updateDrawMode(drawMode: DrawMode) { _drawMode.update { drawMode } if (drawMode is DrawMode.Warp) { _drawPathMode.update { DrawPathMode.Free } _drawLineStyle.update { DrawLineStyle.None } } } fun updateDrawPathMode(drawPathMode: DrawPathMode) { _drawPathMode.update { drawPathMode } } fun getFormatForFilenameSelection(): ImageFormat = imageFormat fun updateDrawLineStyle(style: DrawLineStyle) { _drawLineStyle.update { style } } fun updateHelperGridParams(params: HelperGridParams) { _helperGridParams.update { params } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): DrawComponent } } ================================================ FILE: feature/easter-egg/.gitignore ================================================ /build ================================================ FILE: feature/easter-egg/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.compose) alias(libs.plugins.image.toolbox.hilt) } android.namespace = "com.t8rin.imagetoolbox.feature.easter_egg" ================================================ FILE: feature/easter-egg/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/EasterEggContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("KotlinConstantConditions", "COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.feature.easter_egg.presentation import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min import androidx.compose.ui.unit.sp import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.core.resources.BuildConfig import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.emoji.Emoji import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.AppVersion import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.EmojiItem import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.easter_egg.presentation.screenLogic.EasterEggComponent import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlin.math.roundToInt import kotlin.random.Random @Composable fun EasterEggContent( component: EasterEggComponent ) { val themeState = LocalDynamicThemeState.current val allEmojis = Emoji.allIcons() val emojiData = remember { mutableStateListOf().apply { addAll( List(11) { allEmojis.random().toString() } ) } } val counter: MutableState = remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { while (isActive) { delay(700) if (counter.value > 10) counter.value = 0 emojiData[counter.value] = allEmojis.random().toString() emojiData[10 - counter.value] = allEmojis.random().toString() counter.value++ } } val painter = painterResource(R.drawable.ic_launcher_foreground) Scaffold( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surface), topBar = { EnhancedTopAppBar( title = { Row(modifier = Modifier.marquee()) { emojiData.forEach { emoji -> EmojiItem( emoji = emoji, fontScale = 1f ) } } }, navigationIcon = { EnhancedIconButton( onClick = component.onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, type = EnhancedTopAppBarType.Center ) }, contentWindowInsets = WindowInsets() ) { contentPadding -> BoxWithConstraints( modifier = Modifier .fillMaxSize() .padding(contentPadding) ) { val density = LocalDensity.current val width = constraints.maxWidth val height = constraints.maxHeight val ballSize = remember(maxWidth, maxHeight) { (min(maxWidth, maxHeight) * 0.3f).coerceAtMost(180.dp) } val ballSizePx = remember(density, ballSize) { with(density) { ballSize.toPx().roundToInt() } } var speed by remember { mutableFloatStateOf(0.2f) } var x by remember { mutableFloatStateOf((width - ballSizePx) * 1f) } var y by remember { mutableFloatStateOf((height - ballSizePx) * 1f) } val animatedX = animateFloatAsState(x) val animatedY = animateFloatAsState(y) var xSpeed by rememberSaveable { mutableFloatStateOf(10f) } var ySpeed by rememberSaveable { mutableFloatStateOf(10f) } val rotation = rememberInfiniteTransition().animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( animation = tween(2500), repeatMode = RepeatMode.Reverse ) ) var bounces by remember { mutableIntStateOf(0) } LaunchedEffect(bounces) { if (bounces % 10 == 0) { themeState.updateColorTuple(ColorTuple(Color(Random.nextInt()))) } } LaunchedEffect(speed) { while (isActive) { x += xSpeed * speed y += ySpeed * speed val rightBounce = x > width - ballSizePx val leftBounce = x < 0 val bottomBounce = y > height - ballSizePx val topBounce = y < 0 if (rightBounce || leftBounce) { xSpeed = -xSpeed bounces++ } if (topBounce || bottomBounce) { ySpeed = -ySpeed bounces++ } delay(10) } } val icons = remember { Screen.entries.mapNotNull { it.icon }.shuffled() } val tint = MaterialTheme.colorScheme.secondary.copy(0.8f) val minIconSize = remember(density) { with(density) { 22.dp.roundToPx() } } Column( modifier = Modifier.fillMaxSize() ) { repeat(height / minIconSize) { Row( modifier = Modifier.weight(1f) ) { repeat(width / minIconSize) { val icon = remember(it) { icons.random() } Icon( imageVector = icon, contentDescription = null, modifier = Modifier .weight(1f) .aspectRatio(1f) .padding(1.dp), tint = tint ) } } } } Box( modifier = Modifier .graphicsLayer { translationX = animatedX.value translationY = animatedY.value rotationZ = rotation.value }, contentAlignment = Alignment.Center ) { rememberCoroutineScope() Column( modifier = Modifier .container( shape = MaterialStarShape, color = MaterialTheme.colorScheme.tertiaryContainer ) .hapticsClickable { speed = if (speed == 0.2f) { Random.nextFloat() } else 0.2f AppToastHost.showConfetti() } .size(ballSize) .graphicsLayer { rotationZ = -rotation.value }, verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( painter = painter, contentDescription = stringResource(R.string.version), tint = MaterialTheme.colorScheme.onTertiaryContainer, modifier = Modifier .size(ballSize * 0.6f) .weight(1f, false) ) Column( modifier = Modifier.offset( y = -ballSize * 0.15f ), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = stringResource(R.string.version), style = LocalTextStyle.current.copy( lineHeight = 18.sp, color = MaterialTheme.colorScheme.onTertiaryContainer.copy(0.9f) ), maxLines = 1 ) AutoSizeText( text = "$AppVersion\n(${BuildConfig.VERSION_CODE})", style = LocalTextStyle.current.copy( fontSize = 12.sp, fontWeight = FontWeight.Normal, lineHeight = 14.sp, textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onTertiaryContainer.copy(0.5f) ), maxLines = 2 ) } } } } } } ================================================ FILE: feature/easter-egg/src/main/java/com/t8rin/imagetoolbox/feature/easter_egg/presentation/screenLogic/EasterEggComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.easter_egg.presentation.screenLogic import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class EasterEggComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, ): EasterEggComponent } } ================================================ FILE: feature/edit-exif/.gitignore ================================================ /build ================================================ FILE: feature/edit-exif/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.edit_exif" ================================================ FILE: feature/edit-exif/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/EditExifContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.edit_exif.presentation import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.utils.safeAspectRatio import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exif import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.FormatExifWarning import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.sheets.EditExifSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.edit_exif.presentation.screenLogic.EditExifComponent @Composable fun EditExifContent( component: EditExifComponent, ) { AutoContentBasedColors(component.uri) var showExitDialog by rememberSaveable { mutableStateOf(false) } val imagePicker = rememberImagePicker(onSuccess = component::setUri) val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialUri != null ) val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomModalSheet( data = component.uri, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } var showEditExifDialog by rememberSaveable { mutableStateOf(false) } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.edit_exif_screen), input = component.uri.takeIf { it != Uri.EMPTY }, isLoading = component.isImageLoading, size = rememberFileSize(component.uri) ) }, onGoBack = onBack, topAppBarPersistentActions = { if (component.uri == Uri.EMPTY) { TopAppBarEmoji() } ZoomButton( onClick = { showZoomSheet = true }, visible = component.uri != Uri.EMPTY ) }, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.uri != Uri.EMPTY, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { uri -> editSheetData = listOf(uri) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, imagePreview = { Box( contentAlignment = Alignment.Center ) { var aspectRatio by remember { mutableFloatStateOf(1f) } Picture( model = component.uri, modifier = Modifier .container(MaterialTheme.shapes.medium) .aspectRatio(aspectRatio), onSuccess = { aspectRatio = it.result.image.toBitmap().safeAspectRatio }, isLoadingFromDifferentPlace = component.isImageLoading, shape = MaterialTheme.shapes.medium, contentScale = ContentScale.FillBounds ) if (component.isImageLoading) EnhancedLoadingIndicator() } }, controls = { PreferenceItem( onClick = { showEditExifDialog = true }, modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.edit_exif), subtitle = stringResource(R.string.edit_exif_tag), shape = ShapeDefaults.extraLarge, enabled = component.imageFormat.canWriteExif, onDisabledClick = { AppToastHost.showToast( getString( R.string.image_exif_warning, component.imageFormat.title ) ) }, startIcon = Icons.Rounded.Exif, endIcon = Icons.Rounded.MiniEdit ) Spacer(Modifier.height(8.dp)) FormatExifWarning(component.imageFormat) }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uri == Uri.EMPTY, onSecondaryButtonClick = pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, canShowScreenData = component.uri != Uri.EMPTY, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } } ) EditExifSheet( visible = showEditExifDialog, onDismiss = { showEditExifDialog = false }, exif = component.exif, onClearExif = component::clearExif, onUpdateTag = component::updateExifByTag, onRemoveTag = component::removeExifTag ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/edit-exif/src/main/java/com/t8rin/imagetoolbox/feature/edit_exif/presentation/screenLogic/EditExifComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.edit_exif.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.clearAllAttributes import com.t8rin.imagetoolbox.core.domain.image.clearAttribute import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class EditExifComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageGetter: ImageGetter, private val shareProvider: ShareProvider, private val filenameCreator: FilenameCreator, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } } private val _exif: MutableState = mutableStateOf(null) val exif by _exif private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat private val _uri: MutableState = mutableStateOf(Uri.EMPTY) val uri: Uri by _uri private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmap( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } runSuspendCatching { imageGetter.getImage(uri.toString()) }.getOrNull()?.let { val result = fileController.save( ImageSaveTarget( imageInfo = it.imageInfo, originalUri = uri.toString(), sequenceNumber = null, metadata = exif, data = ByteArray(0), readFromUriInsteadOfData = true ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) parseSaveResult(result.onSuccess(::registerSave)) } _isSaving.update { false } } } fun setUri(uri: Uri) { _uri.update { uri } componentScope.launch { imageGetter.getImage(uri.toString())?.let { _exif.value = it.metadata _imageFormat.value = it.imageInfo.imageFormat } } } fun shareBitmap() { cacheCurrentImage { componentScope.launch { shareProvider.shareUris(listOf(it.toString())) AppToastHost.showConfetti() } } } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.update { true } imageGetter.getImage( uri.toString() )?.let { shareProvider.cacheData( writeData = { w -> w.writeBytes( fileController.readBytes(uri.toString()) ) }, filename = filenameCreator.constructImageFilename( saveTarget = ImageSaveTarget( imageInfo = it.imageInfo.copy(originalUri = uri.toString()), originalUri = uri.toString(), metadata = exif, sequenceNumber = null, data = ByteArray(0) ) ) )?.let { uri -> fileController.writeMetadata( imageUri = uri, metadata = exif ) onComplete(uri.toUri()) } } _isSaving.update { false } } } fun clearExif() { updateExif(_exif.value?.clearAllAttributes()) } private fun updateExif(metadata: Metadata?) { _exif.update { metadata } registerChanges() } fun removeExifTag(tag: MetadataTag) { updateExif(_exif.value?.clearAttribute(tag)) } fun updateExifByTag( tag: MetadataTag, value: String, ) { updateExif(_exif.value?.setAttribute(tag, value)) } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.update { false } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): EditExifComponent } } ================================================ FILE: feature/erase-background/.gitignore ================================================ /build ================================================ FILE: feature/erase-background/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.erase_background" dependencies { "marketImplementation"(libs.mlkit.subject.segmentation) "marketImplementation"(libs.mlkit.segmentation.selfie) implementation(projects.lib.neuralTools) implementation(libs.trickle) implementation(projects.feature.draw) } ================================================ FILE: feature/erase-background/src/foss/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.data.backend import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemoverBackend import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType internal object MlKitBackgroundRemoverBackend : AutoBackgroundRemoverBackend by GenericBackgroundRemoverBackend(modelType = BgModelType.U2NetP) ================================================ FILE: feature/erase-background/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.data import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.data.image.utils.healAlpha import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemover import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemoverBackendFactory import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType import com.t8rin.logger.makeLog import com.t8rin.neural_tools.bgremover.BgRemover import com.t8rin.trickle.TrickleUtils import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import javax.inject.Inject internal class AndroidAutoBackgroundRemover @Inject constructor( private val backendFactory: AutoBackgroundRemoverBackendFactory, private val appScope: AppScope ) : AutoBackgroundRemover { override suspend fun trimEmptyParts( image: Bitmap, emptyColor: Int? ): Bitmap = coroutineScope { val transparent = emptyColor ?: Color.Transparent.toArgb() runCatching { TrickleUtils.trimEmptyParts( bitmap = image, transparent = transparent ) }.onFailure { "trimEmptyParts".makeLog("Failed to crop image ${it.message}") }.getOrNull() ?: image } override fun removeBackgroundFromImage( image: Bitmap, modelType: BgModelType, onSuccess: (Bitmap) -> Unit, onFailure: (Throwable) -> Unit ) { appScope.launch { backendFactory.create(modelType) .performBackgroundRemove(image) .map { it.healAlpha(image) } .onSuccess(onSuccess) .onFailure { onFailure(it.makeLog()) } } } override fun cleanup() = BgRemover.closeAll() } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/AndroidAutoBackgroundRemoverBackendFactory.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.data import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.erase_background.data.backend.GenericBackgroundRemoverBackend import com.t8rin.imagetoolbox.feature.erase_background.data.backend.MlKitBackgroundRemoverBackend import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemoverBackend import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemoverBackendFactory import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType import javax.inject.Inject internal class AndroidAutoBackgroundRemoverBackendFactory @Inject constructor() : AutoBackgroundRemoverBackendFactory { override fun create( modelType: BgModelType ): AutoBackgroundRemoverBackend = when (modelType) { BgModelType.MlKit -> MlKitBackgroundRemoverBackend else -> GenericBackgroundRemoverBackend(modelType) } } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/GenericBackgroundRemoverBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.data.backend import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemoverBackend import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType import com.t8rin.neural_tools.bgremover.BgRemover internal class GenericBackgroundRemoverBackend( private val modelType: BgModelType ) : AutoBackgroundRemoverBackend { override suspend fun performBackgroundRemove( image: Bitmap ): Result = runCatching { BgRemover.removeBackground( image = image, type = when (modelType) { BgModelType.MlKit, BgModelType.U2NetP -> BgRemover.Type.U2NetP BgModelType.U2Net -> BgRemover.Type.U2Net BgModelType.RMBG -> BgRemover.Type.RMBG1_4 BgModelType.InSPyReNet -> BgRemover.Type.InSPyReNet BgModelType.BiRefNetTiny -> BgRemover.Type.BiRefNetTiny BgModelType.ISNet -> BgRemover.Type.ISNet } )!! } } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/di/EraseBackgroundModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.erase_background.data.AndroidAutoBackgroundRemover import com.t8rin.imagetoolbox.feature.erase_background.data.AndroidAutoBackgroundRemoverBackendFactory import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemover import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemoverBackendFactory import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface EraseBackgroundModule { @Binds @Singleton fun provideBackgroundRemover( remover: AndroidAutoBackgroundRemover ): AutoBackgroundRemover @Binds @Singleton fun provideBackendFactory( backend: AndroidAutoBackgroundRemoverBackendFactory ): AutoBackgroundRemoverBackendFactory } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.domain import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType interface AutoBackgroundRemover { fun removeBackgroundFromImage( image: I, modelType: BgModelType, onSuccess: (I) -> Unit, onFailure: (Throwable) -> Unit ) suspend fun trimEmptyParts( image: I, emptyColor: Int? = null ): I fun cleanup() } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.domain internal interface AutoBackgroundRemoverBackend { suspend fun performBackgroundRemove(image: I): Result } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/AutoBackgroundRemoverBackendFactory.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.domain import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType internal interface AutoBackgroundRemoverBackendFactory { fun create(modelType: BgModelType): AutoBackgroundRemoverBackend } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/domain/model/BgModelType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.domain.model import com.t8rin.imagetoolbox.core.domain.utils.Flavor enum class BgModelType( val title: String ) { MlKit("MlKit"), U2NetP("U2NetP"), U2Net("U2Net"), RMBG("RMBG"), InSPyReNet("InSPyReNet"), BiRefNetTiny("BiRefNet"), ISNet("ISNet"); companion object { val Default = if (Flavor.isFoss()) U2NetP else MlKit } } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/EraseBackgroundContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.presentation import android.graphics.Bitmap import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Redo import androidx.compose.material.icons.automirrored.rounded.Undo import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetValue import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveBottomScaffoldLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.HelperGridParamsSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.MagnifierEnabledSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.other.DrawLockScreenOrientation import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.saver.PtSaver import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.BrushSoftnessSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawPathModeSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.LineWidthSelector import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.AutoEraseBackgroundCard import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.BitmapEraser import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.OriginalImagePreviewAlphaSelector import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.RecoverModeButton import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.RecoverModeCard import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.TrimImageToggle import com.t8rin.imagetoolbox.feature.erase_background.presentation.screenLogic.EraseBackgroundComponent import kotlinx.coroutines.launch @Composable fun EraseBackgroundContent( component: EraseBackgroundComponent, ) { val settingsState = LocalSettingsState.current val scope = rememberCoroutineScope() AutoContentBasedColors(component.bitmap) var showExitDialog by rememberSaveable { mutableStateOf(false) } val imagePicker = rememberImagePicker { uri: Uri -> component.setUri(uri) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialUri != null ) val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } var strokeWidth by rememberSaveable(stateSaver = PtSaver) { mutableStateOf( settingsState.defaultDrawLineWidth.pt ) } var brushSoftness by rememberSaveable(stateSaver = PtSaver) { mutableStateOf( 0.pt ) } val drawPathMode = component.drawPathMode var originalImagePreviewAlpha by rememberSaveable { mutableFloatStateOf(0.2f) } val screenSize = LocalScreenSize.current val isPortrait by isPortraitOrientationAsState() var panEnabled by rememberSaveable { mutableStateOf(false) } val secondaryControls = @Composable { PanModeButton( selected = panEnabled, onClick = { panEnabled = !panEnabled } ) EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = { component.undo() }, enabled = component.lastPaths.isNotEmpty() || component.paths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Undo, contentDescription = "Undo" ) } EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = { component.redo() }, enabled = component.undonePaths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Redo, contentDescription = "Redo" ) } } AdaptiveBottomScaffoldLayoutScreen( title = { TopAppBarTitle( title = stringResource(R.string.background_remover), input = component.bitmap, isLoading = component.isImageLoading, size = null, originalSize = null ) }, onGoBack = onBack, shouldDisableBackHandler = !component.haveChanges, actions = { secondaryControls() RecoverModeButton( selected = component.isRecoveryOn, enabled = !panEnabled, onClick = component::toggleEraser ) }, topAppBarPersistentActions = { scaffoldState -> if (component.bitmap == null) TopAppBarEmoji() else { if (isPortrait) { EnhancedIconButton( onClick = { scope.launch { if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) { scaffoldState.bottomSheetState.partialExpand() } else { scaffoldState.bottomSheetState.expand() } } }, ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = stringResource(R.string.properties) ) } } var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.bitmap != null, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { uri -> editSheetData = listOf(uri) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) EnhancedIconButton( onClick = { component.clearDrawing() }, enabled = component.paths.isNotEmpty() ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete) ) } } }, mainContent = { AnimatedContent( targetState = remember(component.bitmap) { derivedStateOf { component.bitmap?.copy( Bitmap.Config.ARGB_8888, true )?.asImageBitmap() ?: ImageBitmap( screenSize.widthPx, screenSize.heightPx ) } }.value, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { imageBitmap -> val direction = LocalLayoutDirection.current val aspectRatio = imageBitmap.width / imageBitmap.height.toFloat() BitmapEraser( imageBitmapForShader = component.internalBitmap?.asImageBitmap(), imageBitmap = imageBitmap, paths = component.paths, strokeWidth = strokeWidth, brushSoftness = brushSoftness, onAddPath = component::addPath, isRecoveryOn = component.isRecoveryOn, modifier = Modifier .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ) .padding(16.dp) .aspectRatio( ratio = aspectRatio, matchHeightConstraintsFirst = isPortrait ) .fillMaxSize(), panEnabled = panEnabled, originalImagePreviewAlpha = originalImagePreviewAlpha, drawPathMode = drawPathMode, helperGridParams = component.helperGridParams ) } }, controls = { scaffoldState -> Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { if (!isPortrait) { Row( modifier = Modifier .padding(vertical = 8.dp) .container(shape = ShapeDefaults.circle) ) { secondaryControls() } } RecoverModeCard( modifier = Modifier.fillMaxWidth(), selected = component.isRecoveryOn, enabled = !panEnabled, onClick = component::toggleEraser ) AutoEraseBackgroundCard( modifier = Modifier.fillMaxWidth(), onClick = { modelType -> scope.launch { scaffoldState.bottomSheetState.partialExpand() component.autoEraseBackground( modelType = modelType ) } }, onReset = component::resetImage ) OriginalImagePreviewAlphaSelector( value = originalImagePreviewAlpha, onValueChange = { originalImagePreviewAlpha = it }, modifier = Modifier.fillMaxWidth() ) DrawPathModeSelector( modifier = Modifier.fillMaxWidth(), value = drawPathMode, onValueChange = component::updateDrawPathMode, values = remember { listOf( DrawPathMode.Free, DrawPathMode.FloodFill(), DrawPathMode.Spray(), DrawPathMode.Line, DrawPathMode.Lasso, DrawPathMode.Rect(), DrawPathMode.Oval ) }, drawMode = DrawMode.Pen ) BoxAnimatedVisibility(drawPathMode.canChangeStrokeWidth) { LineWidthSelector( modifier = Modifier.fillMaxWidth(), value = strokeWidth.value, onValueChange = { strokeWidth = it.pt } ) } BrushSoftnessSelector( modifier = Modifier.fillMaxWidth(), value = brushSoftness.value, onValueChange = { brushSoftness = it.pt } ) TrimImageToggle( checked = component.trimImage, onCheckedChange = { component.setTrimImage(it) }, modifier = Modifier.fillMaxWidth() ) HelperGridParamsSelector( value = component.helperGridParams, onValueChange = component::updateHelperGridParams, modifier = Modifier.fillMaxWidth() ) MagnifierEnabledSelector( modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.extraLarge, ) SaveExifWidget( imageFormat = component.imageFormat, checked = component.saveExif, onCheckedChange = component::setSaveExif, modifier = Modifier.fillMaxWidth() ) ImageFormatSelector( modifier = Modifier.navigationBarsPadding(), entries = ImageFormatGroup.alphaContainedEntries, value = component.imageFormat, onValueChange = component::setImageFormat ) } }, buttons = { var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.bitmap == null, onSecondaryButtonClick = pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, noDataControls = { ImageNotPickedWidget( onPickImage = pickImage ) }, canShowScreenData = component.bitmap != null, showActionsInTopAppBar = false, mainContentWeight = 0.65f ) LoadingDialog( visible = component.isSaving || component.isImageLoading || component.isErasingBG, onCancelLoading = component::cancelSaving, canCancel = component.isSaving ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) DrawLockScreenOrientation() } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/AutoEraseBackgroundCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("KotlinConstantConditions") package com.t8rin.imagetoolbox.feature.erase_background.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material.icons.rounded.DownloadForOffline import androidx.compose.material.icons.rounded.SettingsBackupRestore import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.retain.retain import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.t8rin.imagetoolbox.core.domain.saving.track import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.Flavor import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.throttleLatest import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalKeepAliveService import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCancellableCircularProgressIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType import com.t8rin.neural_tools.DownloadProgress import com.t8rin.neural_tools.bgremover.BgRemover import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable fun AutoEraseBackgroundCard( modifier: Modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp), onClick: (BgModelType) -> Unit, onReset: () -> Unit ) { var selectedModel by rememberSaveable { mutableStateOf(flavoredEntries.first()) } val downloadedModelsRaw by BgRemover.downloadedModels.collectAsStateWithLifecycle() val downloadedModels by remember(downloadedModelsRaw) { derivedStateOf { downloadedModelsRaw.mapNotNull { it.toDomain() } } } val downloadProgresses = remember(downloadedModels) { mutableStateMapOf() } LaunchedEffect(Unit) { flavoredEntries.forEach { BgRemover.getRemover(it.toLib()).checkModel() delay(100) } } LaunchedEffect(downloadedModels) { if (!downloadedModels.contains(selectedModel)) { selectedModel = BgModelType.Default } } val scope = retain { CoroutineScope(Dispatchers.IO) } var downloadJob by retain { mutableStateOf(null) } val keepAliveService = LocalKeepAliveService.current Column( Modifier .then(modifier) .container( resultPadding = 8.dp, shape = ShapeDefaults.extraLarge ) ) { if (flavoredEntries.size > 1) { EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .height(IntrinsicSize.Min), entries = flavoredEntries, value = selectedModel, title = null, onValueChange = { type -> if (downloadJob == null) { if (selectedModel != type) { BgRemover.getRemover(selectedModel.toLib()).close() } selectedModel = type if (type in downloadedModels) return@EnhancedButtonGroup downloadJob?.cancel() downloadJob = scope.launch { keepAliveService.track( initial = { updateOrStart( title = getString(R.string.downloading) ) } ) { BgRemover.downloadModel(type.toLib()) .onStart { downloadProgresses[type] = DownloadProgress( currentPercent = 0f, currentTotalSize = 0 ) } .onCompletion { downloadProgresses.remove(type) downloadJob = null delay(100) BgRemover.getRemover(type.toLib()).checkModel() } .catch { selectedModel = BgModelType.Default downloadProgresses.remove(type) downloadJob = null } .onEach { downloadProgresses[type] = it } .throttleLatest(50) .collect { updateProgress( title = getString(R.string.downloading), done = (it.currentPercent * 100).roundToInt(), total = 100 ) } } } } }, itemContent = { type -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxHeight() ) { Text( text = type.title ) if (type != BgModelType.MlKit) { AnimatedVisibility(type !in downloadedModels) { downloadProgresses[type]?.let { progress -> EnhancedCancellableCircularProgressIndicator( progress = { progress.currentPercent }, modifier = Modifier .padding(start = 8.dp) .size(24.dp), trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), strokeWidth = 3.dp, onCancel = { downloadJob?.cancel() selectedModel = BgModelType.Default downloadProgresses.remove(type) downloadJob = null } ) } ?: Icon( imageVector = Icons.Rounded.DownloadForOffline, contentDescription = null, modifier = Modifier.padding(start = 8.dp) ) } } } }, isScrollable = true, contentPadding = PaddingValues(0.dp), activeButtonColor = MaterialTheme.colorScheme.secondaryContainer ) Spacer(modifier = Modifier.height(8.dp)) } Row( modifier = Modifier .container( resultPadding = 0.dp, color = MaterialTheme.colorScheme.mixedContainer.copy(0.7f) ) .hapticsClickable( onClick = { onClick(selectedModel) } ) .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Text( stringResource(id = R.string.auto_erase_background), modifier = Modifier.weight(1f), color = MaterialTheme.colorScheme.onMixedContainer ) Icon( imageVector = Icons.Rounded.AutoFixHigh, contentDescription = stringResource(R.string.auto_erase_background), tint = MaterialTheme.colorScheme.onMixedContainer ) } Spacer(modifier = Modifier.height(8.dp)) EnhancedButton( containerColor = MaterialTheme.colorScheme.errorContainer.copy(0.2f), contentColor = MaterialTheme.colorScheme.onErrorContainer, onClick = onReset, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.default, isShadowClip = true ) { Icon( imageVector = Icons.Rounded.SettingsBackupRestore, contentDescription = null ) Spacer(Modifier.width(8.dp)) Text(stringResource(id = R.string.restore_image)) } } } private val flavoredEntries: List = BgModelType.entries.let { if (Flavor.isFoss()) it.toggle(BgModelType.MlKit) else it } private fun BgModelType.toLib(): BgRemover.Type = when (this) { BgModelType.MlKit, BgModelType.U2NetP -> BgRemover.Type.U2NetP BgModelType.U2Net -> BgRemover.Type.U2Net BgModelType.RMBG -> BgRemover.Type.RMBG1_4 BgModelType.InSPyReNet -> BgRemover.Type.InSPyReNet BgModelType.BiRefNetTiny -> BgRemover.Type.BiRefNetTiny BgModelType.ISNet -> BgRemover.Type.ISNet } private fun BgRemover.Type.toDomain(): BgModelType? = when (this) { BgRemover.Type.U2NetP -> BgModelType.U2NetP BgRemover.Type.U2Net -> BgModelType.U2Net BgRemover.Type.RMBG1_4 -> BgModelType.RMBG BgRemover.Type.InSPyReNet -> BgModelType.InSPyReNet BgRemover.Type.BiRefNetTiny -> BgModelType.BiRefNetTiny BgRemover.Type.BiRefNet -> null BgRemover.Type.ISNet -> BgModelType.ISNet } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/BitmapEraser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.feature.erase_background.presentation.components import android.annotation.SuppressLint import android.graphics.Bitmap import android.graphics.BlurMaskFilter import android.graphics.PorterDuff import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.takeOrElse import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageShader import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.PaintingStyle import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathMeasure import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.nativePaint import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.IntSize import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.createScaledBitmap import com.t8rin.imagetoolbox.core.ui.utils.helper.scaleToFitCanvas import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.BitmapDrawerPreview import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.MotionEvent import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.copy import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.floodFill import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.handle import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.pointerDrawObserver import com.t8rin.imagetoolbox.feature.draw.presentation.components.utils.rememberPathHelper import kotlinx.coroutines.launch import net.engawapg.lib.zoomable.rememberZoomState @SuppressLint("CoroutineCreationDuringComposition") @Composable fun BitmapEraser( imageBitmap: ImageBitmap, imageBitmapForShader: ImageBitmap?, paths: List, brushSoftness: Pt, originalImagePreviewAlpha: Float, onAddPath: (UiPathPaint) -> Unit, drawPathMode: DrawPathMode = DrawPathMode.Lasso, strokeWidth: Pt, isRecoveryOn: Boolean = false, modifier: Modifier, onErased: (Bitmap) -> Unit = {}, panEnabled: Boolean, helperGridParams: HelperGridParams = remember { HelperGridParams() }, ) { val zoomState = rememberZoomState(maxScale = 30f) val scope = rememberCoroutineScope() val settingsState = LocalSettingsState.current val magnifierEnabled by remember(zoomState.scale, settingsState.magnifierEnabled) { derivedStateOf { zoomState.scale <= 3f && !panEnabled && settingsState.magnifierEnabled } } val globalTouchPointersCount = remember { mutableIntStateOf(0) } var currentDrawPosition by remember { mutableStateOf(Offset.Unspecified) } Box( modifier = Modifier.pointerDrawObserver( magnifierEnabled = magnifierEnabled, currentDrawPosition = currentDrawPosition, zoomState = zoomState, globalTouchPointersCount = globalTouchPointersCount, panEnabled = panEnabled ), contentAlignment = Alignment.Center ) { BoxWithConstraints(modifier) { var invalidations by remember { mutableIntStateOf(0) } LaunchedEffect(paths, constraints) { invalidations++ } val motionEvent = remember { mutableStateOf(MotionEvent.Idle) } var previousDrawPosition by remember { mutableStateOf(Offset.Unspecified) } var drawDownPosition by remember { mutableStateOf(Offset.Unspecified) } val imageWidth = constraints.maxWidth val imageHeight = constraints.maxHeight val drawImageBitmap by remember(imageWidth, imageHeight) { derivedStateOf { imageBitmap .asAndroidBitmap() .createScaledBitmap( width = imageWidth, height = imageHeight ) .asImageBitmap() } } val shaderBitmap by remember(imageBitmapForShader, imageWidth, imageHeight) { derivedStateOf { imageBitmapForShader ?.asAndroidBitmap() ?.createScaledBitmap( imageWidth, imageHeight ) ?.asImageBitmap() } } val erasedBitmap: ImageBitmap by remember(imageWidth, imageHeight) { derivedStateOf { createBitmap(imageWidth, imageHeight).asImageBitmap() } } val outputImage by remember(invalidations) { derivedStateOf { erasedBitmap.copy() } } LaunchedEffect(invalidations) { onErased(outputImage.asAndroidBitmap()) } val canvas: Canvas by remember(imageHeight, imageWidth, erasedBitmap) { derivedStateOf { Canvas(erasedBitmap) } } val canvasSize by remember(canvas.nativeCanvas) { derivedStateOf { IntegerSize(canvas.nativeCanvas.width, canvas.nativeCanvas.height) } } val drawPaint by remember( drawPathMode, strokeWidth, isRecoveryOn, brushSoftness, canvasSize ) { derivedStateOf { Paint().apply { style = if (drawPathMode.isFilled) { PaintingStyle.Fill } else PaintingStyle.Stroke blendMode = if (isRecoveryOn) blendMode else BlendMode.Clear shader = if (isRecoveryOn) shaderBitmap?.let { ImageShader(it) } else shader this.strokeWidth = drawPathMode.convertStrokeWidth( strokeWidth = strokeWidth, canvasSize = canvasSize ) if (drawPathMode.isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } isAntiAlias = true }.nativePaint.apply { if (brushSoftness.value > 0f) maskFilter = BlurMaskFilter( brushSoftness.toPx(canvasSize), BlurMaskFilter.Blur.NORMAL ) } } } var drawPath by remember( strokeWidth, brushSoftness ) { mutableStateOf(Path()) } with(canvas) { with(nativeCanvas) { drawColor(Color.Transparent.toArgb(), PorterDuff.Mode.CLEAR) val imagePaint = remember { Paint() } drawImageRect( image = drawImageBitmap, dstSize = IntSize(canvasSize.width, canvasSize.height), paint = imagePaint ) paths.forEach { (nonScaledPath, stroke, radius, _, isRecoveryOn, _, size, drawPathMode) -> val path by remember(nonScaledPath, size, canvasSize) { derivedStateOf { nonScaledPath.scaleToFitCanvas( currentSize = canvasSize, oldSize = size ).asAndroidPath() } } val paint by remember( drawPathMode, isRecoveryOn, shaderBitmap, stroke, canvasSize, radius ) { derivedStateOf { Paint().apply { style = if (drawPathMode.isFilled) { PaintingStyle.Fill } else PaintingStyle.Stroke blendMode = if (isRecoveryOn) blendMode else BlendMode.Clear if (isRecoveryOn) shader = shaderBitmap?.let { ImageShader(it) } this.strokeWidth = drawPathMode.convertStrokeWidth( strokeWidth = stroke, canvasSize = canvasSize ) if (drawPathMode.isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } isAntiAlias = true }.nativePaint.apply { if (radius.value > 0f) { maskFilter = BlurMaskFilter( radius.toPx(canvasSize), BlurMaskFilter.Blur.NORMAL ) } } } } drawPath(path, paint) } drawPath( drawPath.asAndroidPath(), drawPaint ) } val drawHelper by rememberPathHelper( drawDownPosition = drawDownPosition, currentDrawPosition = currentDrawPosition, onPathChange = { drawPath = it }, strokeWidth = strokeWidth, canvasSize = canvasSize, drawPathMode = drawPathMode, isEraserOn = false, drawMode = DrawMode.Pen ) motionEvent.value.handle( onDown = { if (currentDrawPosition.isSpecified) { drawPath.moveTo(currentDrawPosition.x, currentDrawPosition.y) previousDrawPosition = currentDrawPosition } else { drawPath = Path() } motionEvent.value = MotionEvent.Idle }, onMove = { drawHelper.drawPath( onBaseDraw = { if (previousDrawPosition.isUnspecified && currentDrawPosition.isSpecified) { drawPath.moveTo(currentDrawPosition.x, currentDrawPosition.y) previousDrawPosition = currentDrawPosition } if (currentDrawPosition.isSpecified && previousDrawPosition.isSpecified) { drawPath.quadraticTo( previousDrawPosition.x, previousDrawPosition.y, (previousDrawPosition.x + currentDrawPosition.x) / 2, (previousDrawPosition.y + currentDrawPosition.y) / 2 ) } previousDrawPosition = currentDrawPosition }, currentDrawPath = drawPath ) motionEvent.value = MotionEvent.Idle }, onUp = { drawHelper.drawPath( onBaseDraw = { PathMeasure().apply { setPath(drawPath, false) }.let { it.getPosition(it.length) }.takeOrElse { currentDrawPosition }.let { lastPoint -> drawPath.moveTo(lastPoint.x, lastPoint.y) drawPath.lineTo( currentDrawPosition.x, currentDrawPosition.y ) } }, onFloodFill = { tolerance -> erasedBitmap.floodFill( offset = currentDrawPosition, tolerance = tolerance )?.let { drawPath = it } }, currentDrawPath = null ) onAddPath( UiPathPaint( path = drawPath, strokeWidth = strokeWidth, brushSoftness = brushSoftness, isErasing = isRecoveryOn, canvasSize = canvasSize, drawPathMode = drawPathMode ) ) currentDrawPosition = Offset.Unspecified previousDrawPosition = Offset.Unspecified motionEvent.value = MotionEvent.Idle scope.launch { drawPath = Path() } } ) } BitmapDrawerPreview( preview = outputImage, globalTouchPointersCount = globalTouchPointersCount, onReceiveMotionEvent = { motionEvent.value = it }, onInvalidate = { invalidations++ }, onUpdateCurrentDrawPosition = { currentDrawPosition = it }, onUpdateDrawDownPosition = { drawDownPosition = it }, drawEnabled = !panEnabled, helperGridParams = helperGridParams, beforeHelperGridModifier = Modifier.drawBehind { if (originalImagePreviewAlpha != 0f) { drawContext.canvas.apply { drawImageRect( image = imageBitmapForShader ?: drawImageBitmap, dstSize = IntSize(canvasSize.width, canvasSize.height), paint = Paint().apply { alpha = originalImagePreviewAlpha } ) } } } ) } } } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/OriginalImagePreviewAlphaSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ImageSearch import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector @Composable fun OriginalImagePreviewAlphaSelector( value: Float, onValueChange: (Float) -> Unit, modifier: Modifier = Modifier ) { AlphaSelector( value = value, onValueChange = onValueChange, modifier = modifier, title = stringResource(id = R.string.original_image_preview_alpha), icon = Icons.Rounded.ImageSearch ) } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/RecoverModeCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Healing import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun RecoverModeCard( modifier: Modifier = Modifier .padding(start = 16.dp, end = 16.dp, top = 8.dp), selected: Boolean, enabled: Boolean, onClick: () -> Unit, ) { PreferenceRowSwitch( modifier = modifier, shape = ShapeDefaults.extraLarge, enabled = enabled, title = stringResource(R.string.restore_background), subtitle = stringResource(R.string.restore_background_sub), startIcon = Icons.Rounded.Healing, checked = selected, onClick = { onClick() } ) } @Composable fun RecoverModeButton( selected: Boolean, enabled: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { EnhancedIconButton( modifier = modifier, enabled = enabled, containerColor = animateColorAsState( if (selected) MaterialTheme.colorScheme.mixedContainer else Color.Transparent ).value, contentColor = animateColorAsState( if (selected) MaterialTheme.colorScheme.onMixedContainer else MaterialTheme.colorScheme.onSurface ).value, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = onClick ) { Icon( imageVector = Icons.Rounded.Healing, contentDescription = "Brush" ) } } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/components/TrimImageToggle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ScissorsSmall import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun TrimImageToggle( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, color: Color = Color.Unspecified ) { PreferenceRowSwitch( modifier = modifier, title = stringResource(R.string.trim_image), subtitle = stringResource(R.string.trim_image_sub), checked = checked, containerColor = color, shape = ShapeDefaults.extraLarge, onClick = onCheckedChange, startIcon = Icons.Outlined.ScissorsSmall ) } ================================================ FILE: feature/erase-background/src/main/java/com/t8rin/imagetoolbox/feature/erase_background/presentation/screenLogic/EraseBackgroundComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.arkivanov.essenty.lifecycle.doOnDestroy import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.domain.ImageDrawApplier import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemover import com.t8rin.imagetoolbox.feature.erase_background.domain.model.BgModelType import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class EraseBackgroundComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val imageScaler: ImageScaler, private val imageGetter: ImageGetter, private val imageCompressor: ImageCompressor, private val fileController: FileController, private val imageDrawApplier: ImageDrawApplier, private val autoBackgroundRemover: AutoBackgroundRemover, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder, ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } doOnDestroy { autoBackgroundRemover.cleanup() } } private val _internalBitmap: MutableState = mutableStateOf(null) val internalBitmap: Bitmap? by _internalBitmap private val _isRecoveryOn: MutableState = mutableStateOf(false) val isRecoveryOn: Boolean by _isRecoveryOn private val _saveExif: MutableState = mutableStateOf(false) val saveExif: Boolean by _saveExif private val _trimImage: MutableState = mutableStateOf(true) val trimImage: Boolean by _trimImage private val _paths = mutableStateOf(listOf()) val paths: List by _paths private val _lastPaths = mutableStateOf(listOf()) val lastPaths: List by _lastPaths private val _undonePaths = mutableStateOf(listOf()) val undonePaths: List by _undonePaths private val _drawPathMode: MutableState = mutableStateOf(DrawPathMode.Free) val drawPathMode: DrawPathMode by _drawPathMode private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _isErasingBG: MutableState = mutableStateOf(false) val isErasingBG: Boolean by _isErasingBG private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Default) val imageFormat: ImageFormat by _imageFormat private val _uri: MutableState = mutableStateOf(Uri.EMPTY) private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _helperGridParams = fileController.savable( scope = componentScope, initial = HelperGridParams() ) val helperGridParams: HelperGridParams by _helperGridParams private fun updateBitmap(bitmap: Bitmap?) { componentScope.launch { _isImageLoading.value = true _bitmap.value = imageScaler.scaleUntilCanShow(bitmap) _internalBitmap.value = _bitmap.value _isImageLoading.value = false } } fun setUri( uri: Uri ) { _uri.value = uri autoEraseCount = 0 _isImageLoading.value = true componentScope.launch { _paths.value = listOf() _lastPaths.value = listOf() _undonePaths.value = listOf() imageGetter.getImageAsync( uri = uri.toString(), originalSize = true, onGetImage = { data -> updateBitmap(data.image) _imageFormat.update { data.imageInfo.imageFormat } }, onFailure = AppToastHost::showFailureToast ) } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.value = imageFormat registerChanges() } private var savingJob: Job? = null fun saveBitmap( oneTimeSaveLocationUri: String? ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true getErasedBitmap(true)?.let { localBitmap -> parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ), originalUri = _uri.value.toString(), sequenceNumber = null, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = _saveExif.value, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.value = false } } private suspend fun getErasedBitmap(canTrim: Boolean): Bitmap? { return if (autoEraseCount == 0) { imageDrawApplier.applyEraseToImage( pathPaints = _paths.value, imageUri = _uri.value.toString() ) } else { imageDrawApplier.applyEraseToImage( pathPaints = _paths.value, image = _bitmap.value, shaderSourceUri = _uri.value.toString() ) }?.let { if (trimImage && canTrim) autoBackgroundRemover.trimEmptyParts(it) else it } } fun shareBitmap() { componentScope.launch { getErasedBitmap(true)?.let { _isSaving.value = true shareProvider.shareImage( imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = it.width, height = it.height ), image = it, onComplete = AppToastHost::showConfetti ) } ?: AppToastHost.showConfetti() _isSaving.value = false }.also { _isSaving.value = false savingJob?.cancel() savingJob = it } } fun undo() { if (paths.isEmpty() && lastPaths.isNotEmpty()) { _paths.value = lastPaths _lastPaths.value = listOf() return } if (paths.isEmpty()) return val lastPath = paths.last() _paths.update { it - lastPath } _undonePaths.update { it + lastPath } registerChanges() } fun redo() { if (undonePaths.isEmpty()) return val lastPath = undonePaths.last() _paths.update { it + lastPath } _undonePaths.update { it - lastPath } registerChanges() } fun addPath(pathPaint: UiPathPaint) { _paths.update { it + pathPaint } _undonePaths.value = listOf() registerChanges() } fun clearDrawing() { if (paths.isNotEmpty()) { _lastPaths.value = paths _paths.value = listOf() _undonePaths.value = listOf() registerChanges() } } fun setSaveExif(bool: Boolean) { _saveExif.value = bool registerChanges() } fun setTrimImage(boolean: Boolean) { _trimImage.value = boolean registerChanges() } private var autoEraseCount: Int = 0 fun autoEraseBackground( modelType: BgModelType, ) { componentScope.launch { getErasedBitmap(false)?.let { image -> _isErasingBG.value = true autoBackgroundRemover.removeBackgroundFromImage( image = image, modelType = modelType, onSuccess = { _bitmap.value = it _paths.value = listOf() _lastPaths.value = listOf() _undonePaths.value = listOf() _isErasingBG.value = false AppToastHost.showConfetti() autoEraseCount++ registerChanges() }, onFailure = { _isErasingBG.value = false AppToastHost.showFailureToast(it) } ) } } } fun resetImage() { componentScope.launch { autoEraseCount = 0 _bitmap.value = _internalBitmap.value _paths.value = listOf() _lastPaths.value = listOf() } } fun toggleEraser() { _isRecoveryOn.update { !it } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true getErasedBitmap(true)?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( originalUri = _uri.value.toString(), imageFormat = imageFormat, width = image.width, height = image.height ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun updateDrawPathMode(drawPathMode: DrawPathMode) { _drawPathMode.update { drawPathMode } } fun getFormatForFilenameSelection(): ImageFormat = imageFormat fun updateHelperGridParams(params: HelperGridParams) { _helperGridParams.update { params } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): EraseBackgroundComponent } } ================================================ FILE: feature/erase-background/src/market/java/com/t8rin/imagetoolbox/feature/erase_background/data/backend/MlKitBackgroundRemoverBackend.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.erase_background.data.backend import android.annotation.SuppressLint import android.graphics.Bitmap import android.os.Build import androidx.annotation.RequiresApi import androidx.core.graphics.set import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.segmentation.Segmentation import com.google.mlkit.vision.segmentation.Segmenter import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions import com.google.mlkit.vision.segmentation.subject.SubjectSegmentation import com.google.mlkit.vision.segmentation.subject.SubjectSegmenter import com.google.mlkit.vision.segmentation.subject.SubjectSegmenterOptions import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemoverBackend import kotlinx.coroutines.suspendCancellableCoroutine import java.nio.ByteBuffer import kotlin.coroutines.resume internal object MlKitBackgroundRemoverBackend : AutoBackgroundRemoverBackend { override suspend fun performBackgroundRemove( image: Bitmap ): Result = suspendCancellableCoroutine { continuation -> runCatching { autoRemove( image = image, onFinish = { continuation.resume(it) } ) }.onFailure { continuation.resume(Result.failure(it)) } } @SuppressLint("NewApi") private fun autoRemove( type: ApiType = ApiType.Best, image: Bitmap, onFinish: (Result) -> Unit ) { val old = { MlKitBackgroundRemover.removeBackground( bitmap = image, onFinish = onFinish ) } val new = { MlKitSubjectBackgroundRemover.removeBackground( bitmap = image, onFinish = { if (it.isFailure) old() else onFinish(it) } ) } when (type) { ApiType.Old -> old() ApiType.New -> new() } } private enum class ApiType { Old, New; companion object Companion { val Best: ApiType get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) New else Old } } } private object MlKitBackgroundRemover { private var segment: Segmenter? = null private var buffer = ByteBuffer.allocate(0) private var width = 0 private var height = 0 init { runCatching { val segmentOptions = SelfieSegmenterOptions.Builder() .setDetectorMode(SelfieSegmenterOptions.SINGLE_IMAGE_MODE) .build() segment = Segmentation.getClient(segmentOptions) } } /** * Process the image to get buffer and image height and width * @param bitmap Bitmap which you want to remove background. * @param onFinish listener for success and failure callback. **/ fun removeBackground( bitmap: Bitmap, onFinish: (Result) -> Unit ) { //Generate a copy of bitmap just in case the if the bitmap is immutable. val copyBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) val input = InputImage.fromBitmap(copyBitmap, 0) val segmenter = segment ?: return onFinish(Result.failure(NoClassDefFoundError())) segmenter.process(input) .addOnSuccessListener { segmentationMask -> buffer = segmentationMask.buffer width = segmentationMask.width height = segmentationMask.height val resultBitmap = removeBackgroundFromImage(copyBitmap) onFinish(Result.success(resultBitmap)) } .addOnFailureListener { e -> onFinish(Result.failure(e)) } } /** * Change the background pixels color to transparent. * */ private fun removeBackgroundFromImage( image: Bitmap ): Bitmap { image.setHasAlpha(true) for (y in 0 until height) { for (x in 0 until width) { val bgConfidence = ((1.0 - buffer.float) * 255).toInt() if (bgConfidence >= 100) { image[x, y] = 0 } } } buffer.rewind() return image } } @RequiresApi(api = Build.VERSION_CODES.N) private object MlKitSubjectBackgroundRemover { private var segment: SubjectSegmenter? = null init { runCatching { val segmentOptions = SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build() segment = SubjectSegmentation.getClient(segmentOptions) } } /** * Process the image to get buffer and image height and width * @param bitmap Bitmap which you want to remove background. * @param onFinish listener for success and failure callback. **/ @RequiresApi(api = Build.VERSION_CODES.N) fun removeBackground( bitmap: Bitmap, onFinish: (Result) -> Unit ) { //Generate a copy of bitmap just in case the if the bitmap is immutable. val copyBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) val input = InputImage.fromBitmap(copyBitmap, 0) val segmenter = segment ?: return onFinish(Result.failure(NoClassDefFoundError())) segmenter.process(input) .addOnSuccessListener { onFinish(Result.success(it?.foregroundBitmap ?: bitmap)) } .addOnFailureListener { e -> onFinish(Result.failure(e)) } } } ================================================ FILE: feature/filters/.gitignore ================================================ /build ================================================ FILE: feature/filters/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.filters" dependencies { api(projects.core.filters) ksp(projects.core.ksp) implementation(projects.core.ksp) implementation(projects.feature.draw) implementation(projects.feature.pickColor) implementation(projects.feature.compare) implementation(libs.kotlin.reflect) implementation(libs.aire) implementation(libs.trickle) implementation(libs.toolbox.gpuimage) implementation(projects.lib.opencvTools) implementation(projects.lib.neuralTools) implementation(projects.lib.curves) implementation(libs.toolbox.jhlabs) implementation(projects.lib.ascii) } ================================================ FILE: feature/filters/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterMaskApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data import android.graphics.Bitmap import android.graphics.BlurMaskFilter import android.graphics.Matrix import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.PaintingStyle import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.nativePaint import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.data.utils.toSoftware import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.feature.draw.domain.PathPaint import com.t8rin.imagetoolbox.feature.filters.domain.FilterMask import com.t8rin.imagetoolbox.feature.filters.domain.FilterMaskApplier import javax.inject.Inject import android.graphics.Paint as NativePaint internal class AndroidFilterMaskApplier @Inject constructor( private val imageGetter: ImageGetter, private val imageTransformer: ImageTransformer, private val filterProvider: FilterProvider, ) : FilterMaskApplier { override suspend fun filterByMask( filterMask: FilterMask, imageUri: String, ): Bitmap? = imageGetter.getImage(uri = imageUri)?.let { filterByMask(filterMask = filterMask, image = it.image) } override suspend fun filterByMask( filterMask: FilterMask, image: Bitmap, ): Bitmap? { if (filterMask.filters.isEmpty()) return image val filteredBitmap = imageTransformer.transform( image = image, transformations = filterMask.filters.map { filterProvider.filterToTransformation(it) } )?.clipBitmap( pathPaints = filterMask.maskPaints, inverse = filterMask.isInverseFillType ) return filteredBitmap?.let { image.let { bitmap -> if (filterMask.filters.any { it is Filter.RemoveColor }) { bitmap.clipBitmap( pathPaints = filterMask.maskPaints, inverse = !filterMask.isInverseFillType ) } else bitmap }.overlay(filteredBitmap) } } private fun Bitmap.clipBitmap( pathPaints: List>, inverse: Boolean, ): Bitmap = createBitmap( width = this.width, height = this.height, config = this.safeConfig ).apply { setHasAlpha(true) }.applyCanvas { val canvasSize = IntegerSize(width, height) pathPaints.forEach { pathPaint -> val path = pathPaint.path.scaleToFitCanvas( currentSize = canvasSize, oldSize = pathPaint.canvasSize ) val drawPathMode = pathPaint.drawPathMode val isSharpEdge = drawPathMode.isSharpEdge val isFilled = drawPathMode.isFilled drawPath( path, Paint().apply { if (pathPaint.isErasing) { style = PaintingStyle.Stroke this.strokeWidth = pathPaint.strokeWidth.toPx(canvasSize) strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } else { if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke if (isSharpEdge) { strokeCap = StrokeCap.Square } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } this.strokeWidth = pathPaint.strokeWidth.toPx(canvasSize) } } color = pathPaint.drawColor if (pathPaint.isErasing) { blendMode = BlendMode.Clear } }.nativePaint.apply { if (pathPaint.brushSoftness.value > 0f) { maskFilter = BlurMaskFilter( pathPaint.brushSoftness.toPx(canvasSize), BlurMaskFilter.Blur.NORMAL ) } } ) } drawBitmap( this@clipBitmap, 0f, 0f, NativePaint() .apply { xfermode = if (!inverse) { PorterDuffXfermode(PorterDuff.Mode.SRC_IN) } else { PorterDuffXfermode(PorterDuff.Mode.SRC_OUT) } } ) } private fun Bitmap.overlay(overlay: Bitmap): Bitmap { val image = this return createBitmap( width = image.width, height = image.height, config = image.safeConfig.toSoftware() ).applyCanvas { drawBitmap(image) drawBitmap(overlay.toSoftware()) } } private fun Path.scaleToFitCanvas( currentSize: IntegerSize, oldSize: IntegerSize, onGetScale: (Float, Float) -> Unit = { _, _ -> }, ): android.graphics.Path { val sx = currentSize.width.toFloat() / oldSize.width val sy = currentSize.height.toFloat() / oldSize.height onGetScale(sx, sy) return android.graphics.Path(this.asAndroidPath()).apply { transform( Matrix().apply { setScale(sx, sy) } ) } } override suspend fun filterByMasks( filterMasks: List>, imageUri: String, ): Bitmap? = imageGetter.getImage(uri = imageUri)?.let { filterByMasks(filterMasks, it.image) } override suspend fun filterByMasks( filterMasks: List>, image: Bitmap, ): Bitmap? = filterMasks.fold, Bitmap?>( initial = image, operation = { bmp, mask -> bmp?.let { filterByMask( filterMask = mask, image = bmp ) } } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterParamsInteractor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UNCHECKED_CAST") package com.t8rin.imagetoolbox.feature.filters.data import android.content.Context import android.graphics.Bitmap import androidx.core.net.toUri import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.filters.domain.FilterParamsInteractor import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.toImageModel import com.t8rin.imagetoolbox.feature.filters.data.utils.serialization.PACKAGE_ALIAS import com.t8rin.imagetoolbox.feature.filters.data.utils.serialization.toDatastoreString import com.t8rin.imagetoolbox.feature.filters.data.utils.serialization.toFiltersList import com.t8rin.imagetoolbox.feature.filters.data.utils.serialization.toTemplateFiltersList import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import java.io.File import javax.inject.Inject internal class AndroidFilterParamsInteractor @Inject constructor( @ApplicationContext private val context: Context, private val dataStore: DataStore, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter ) : FilterParamsInteractor { override fun getFavoriteFilters(): Flow>> = dataStore.data.map { prefs -> prefs[FAVORITE_FILTERS]?.toFiltersList(false, context) ?: emptyList() } override suspend fun toggleFavorite(filter: Filter<*>) { val currentFilters = getFavoriteFilters().first() if (currentFilters.filterIsInstance(filter::class.java).isEmpty()) { dataStore.edit { prefs -> prefs[FAVORITE_FILTERS] = (listOf(filter) + currentFilters).toDatastoreString(context = context) } } else { dataStore.edit { prefs -> prefs[FAVORITE_FILTERS] = currentFilters .filter { !it::class.java.isInstance(filter) } .toDatastoreString(context = context) } } } override fun getTemplateFilters(): Flow> = dataStore.data.map { prefs -> prefs[TEMPLATE_FILTERS]?.takeIf { it.isNotEmpty() }?.toTemplateFiltersList(context) ?: emptyList() } override suspend fun addTemplateFilterFromString( string: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit, ) { runSuspendCatching { if (isValidTemplateFilter(string)) { runSuspendCatching { string.removePrefix(LINK_HEADER).toTemplateFiltersList(context).firstOrNull() }.getOrNull()?.let { addTemplateFilter(it) onSuccess(it.name, it.filters.size) } ?: onFailure() } else onFailure() }.onFailure { onFailure() } } override fun isValidTemplateFilter( string: String, ): Boolean = (context.applicationInfo.packageName in string || PACKAGE_ALIAS in string) && "Filter" in string && LINK_HEADER in string override suspend fun reorderFavoriteFilters(newOrder: List>) { dataStore.edit { prefs -> prefs[FAVORITE_FILTERS] = newOrder.toDatastoreString(context = context) } } override fun getFilterPreviewModel(): Flow = dataStore.data.map { prefs -> prefs[PREVIEW_MODEL]?.let { when (it) { "0" -> R.drawable.filter_preview_source "1" -> R.drawable.filter_preview_source_3 else -> it }.toImageModel() } ?: R.drawable.filter_preview_source.toImageModel() } override suspend fun setFilterPreviewModel(uri: String) { if (uri.any { !it.isDigit() }) { imageGetter.getImage( data = uri, size = 300 )?.let { image -> fileController.writeBytes( File(context.filesDir, "filterPreview.png").apply { createNewFile() }.toUri().toString() ) { writeable -> writeable.writeBytes( imageCompressor.compress( image = image, imageFormat = ImageFormat.Png.Lossless, quality = Quality.Base(100) ) ) } } } dataStore.edit { it[PREVIEW_MODEL] = uri } } override suspend fun addTemplateFilterFromUri( uri: String, onSuccess: suspend (filterName: String, filtersCount: Int) -> Unit, onFailure: suspend () -> Unit, ) { addTemplateFilterFromString( string = fileController.readBytes(uri).decodeToString(), onSuccess = onSuccess, onFailure = onFailure ) } override suspend fun removeTemplateFilter(templateFilter: TemplateFilter) { val currentFilters = getTemplateFilters().first() dataStore.edit { prefs -> prefs[TEMPLATE_FILTERS] = currentFilters.filter { convertTemplateFilterToString(it) != convertTemplateFilterToString(templateFilter) }.toDatastoreString(context) } } override suspend fun convertTemplateFilterToString( templateFilter: TemplateFilter, ): String = "$LINK_HEADER${listOf(templateFilter).toDatastoreString(context)}" override suspend fun addTemplateFilter(templateFilter: TemplateFilter) { val currentFilters = getTemplateFilters().first() dataStore.edit { prefs -> prefs[TEMPLATE_FILTERS] = currentFilters.let { filters -> val index = filters.indexOfFirst { convertTemplateFilterToString(it) == convertTemplateFilterToString( templateFilter ) } if (index != -1) filters else filters + templateFilter }.toDatastoreString(context) } } override fun getCanSetDynamicFilterPreview(): Flow = dataStore.data.map { it[CAN_SET_DYNAMIC_FILTER_PREVIEW] == true } override suspend fun setCanSetDynamicFilterPreview(value: Boolean) { dataStore.edit { prefs -> prefs[CAN_SET_DYNAMIC_FILTER_PREVIEW] = value } } } private const val LINK_HEADER: String = "https://github.com/T8RIN/ImageToolbox?" private val FAVORITE_FILTERS = stringPreferencesKey("FAVORITE_FILTERS") private val TEMPLATE_FILTERS = stringPreferencesKey("TEMPLATE_FILTERS") private val PREVIEW_MODEL = stringPreferencesKey("PREVIEW_MODEL") private val CAN_SET_DYNAMIC_FILTER_PREVIEW = booleanPreferencesKey("CAN_SET_DYNAMIC_FILTER_PREVIEW") ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/AndroidFilterProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ifVisible import com.t8rin.imagetoolbox.core.domain.transformation.EmptyTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.generated.mapFilter import javax.inject.Inject internal class AndroidFilterProvider @Inject constructor() : FilterProvider { override fun filterToTransformation( filter: Filter<*>, ): Transformation = filter.ifVisible(::mapFilter) ?: EmptyTransformation() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesFilmicToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class AcesFilmicToneMappingFilter( override val value: Float = 1f, ) : Transformation, Filter.AcesFilmicToneMapping { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.acesFilmicToneMapping( bitmap = input, exposure = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AcesHillToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class AcesHillToneMappingFilter( override val value: Float = 1f, ) : Transformation, Filter.AcesHillToneMapping { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.acesHillToneMapping( bitmap = input, exposure = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class AchromatomalyFilter( override val value: Unit = Unit ) : Transformation, Filter.Achromatomaly { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.ACHROMATOMALY).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AchromatopsiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class AchromatopsiaFilter( override val value: Unit = Unit ) : Transformation, Filter.Achromatopsia { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.ACHROMATOPSIA).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AldridgeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class AldridgeFilter( override val value: Pair = 1f to 0.025f ) : Transformation, Filter.Aldridge { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.aldridge( bitmap = input, exposure = value.first, cutoff = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AmatorkaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class AmatorkaFilter( override val value: Float = 1f, ) : ChainTransformation, Filter.Amatorka { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_amatorka)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnaglyphFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.glitch.GlitchTool @FilterInject internal class AnaglyphFilter( override val value: Float = 20f ) : Transformation, Filter.Anaglyph { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = GlitchTool.anaglyph(input, value.toInt()) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AnisotropicDiffusionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class AnisotropicDiffusionFilter( override val value: Triple = Triple(20f, 0.6f, 0.5f) ) : Transformation, Filter.AnisotropicDiffusion { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.anisotropicDiffusion( bitmap = input, numOfSteps = value.first.roundToInt(), conduction = value.second, diffusion = value.third ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ArcFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.jhlabs.ArcFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ArcParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation import kotlin.math.max @FilterInject internal class ArcFilter( override val value: ArcParams = ArcParams.Default ) : JhFilterTransformation(), Filter.Arc { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = ArcFilter() override fun createFilter(image: Bitmap): JhFilter = ArcFilter().apply { radius = value.radius * (max(image.width, image.height) / 2f) height = value.height * (max(image.width, image.height) / 2f) angle = Math.toRadians(value.angle.toDouble()).toFloat() spreadAngle = Math.toRadians(value.spreadAngle.toDouble()).toFloat() centreX = value.centreX centreY = value.centreY } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AsciiFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import android.graphics.Typeface import com.t8rin.ascii.ASCIIConverter import com.t8rin.ascii.Gradient import com.t8rin.ascii.toMapper import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.AsciiParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.utils.toTypeface @FilterInject internal class AsciiFilter( override val value: AsciiParams = AsciiParams.Default ) : Transformation, Filter.Ascii { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ASCIIConverter( fontSize = value.fontSize, mapper = Gradient(value.gradient).toMapper() ).convertToAsciiBitmap( bitmap = input, typeface = value.font.toTypeface() ?: Typeface.DEFAULT, backgroundColor = value.backgroundColor.colorInt, isGrayscale = value.isGrayscale ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AtkinsonDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class AtkinsonDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.AtkinsonDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Atkinson, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoCropFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.autocrop.AutoCropper import kotlin.math.roundToInt @FilterInject internal class AutoCropFilter( override val value: Float = 5f ) : Transformation, Filter.AutoCrop { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = AutoCropper.crop( bitmap = input, sensitivity = value.roundToInt() ) ?: input } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoPerspectiveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.auto_straight.AutoStraighten import com.t8rin.opencv_tools.auto_straight.model.StraightenMode @FilterInject internal class AutoPerspectiveFilter( override val value: Unit = Unit ) : Transformation, Filter.AutoPerspective { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = AutoStraighten.process( input = input, mode = StraightenMode.Perspective ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutoRemoveRedEyesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.red_eye.RedEyeRemover @FilterInject internal class AutoRemoveRedEyesFilter( override val value: Float = 150f, ) : Transformation, Filter.AutoRemoveRedEyes { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = RedEyeRemover.removeRedEyes( bitmap = input, minEyeSize = 35.0, redThreshold = value.toDouble() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AutumnTonesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class AutumnTonesFilter( override val value: Unit = Unit ) : Transformation, Filter.AutumnTones { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.AUTUMN_TONES).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/AverageDistanceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.forensics.ImageForensics @FilterInject internal class AverageDistanceFilter( override val value: Unit = Unit ) : Transformation, Filter.AverageDistance { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ImageForensics.averageDistance( input = input ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerEightDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class BayerEightDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.BayerEightDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.BayerEight, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerFourDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class BayerFourDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.BayerFourDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.BayerFour, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerThreeDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class BayerThreeDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.BayerThreeDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.BayerThree, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BayerTwoDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class BayerTwoDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.BayerTwoDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.BayerTwo, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BilaterialBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.BilaterialBlurParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode @FilterInject internal class BilaterialBlurFilter( override val value: BilaterialBlurParams = BilaterialBlurParams.Default ) : Transformation, Filter.BilaterialBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.bilateralBlur( bitmap = input, spatialSigma = value.spatialSigma, rangeSigma = value.rangeSigma, edgeMode = value.edgeMode.toEdgeMode(), borderScalar = Scalar.ZEROS, kernelSize = 2 * value.radius + 1 ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackAndWhiteFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageLuminanceFilter @FilterInject internal class BlackAndWhiteFilter( override val value: Unit = Unit, ) : GPUFilterTransformation(), Filter.BlackAndWhite { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageLuminanceFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlackHatFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphKernels import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha @FilterInject internal class BlackHatFilter( override val value: Pair = 25f to true ) : Transformation, Filter.BlackHat { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = if (value.second) { MorphKernels.circle(value.first.toInt()) } else { MorphKernels.box(value.first.toInt()) }, morphOp = MorphOp.BLACKHAT, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.toInt(), kernelWidth = value.first.toInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BleachBypassFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class BleachBypassFilter( override val value: Float = 1f ) : ChainTransformation, Filter.BleachBypass { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_bleach_bypass)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BlockGlitchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class BlockGlitchFilter( override val value: Pair = 0.02f to 0.5f, ) : Transformation, Filter.BlockGlitch { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.blockGlitch( src = input, blockSizeFraction = value.first, strength = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BloomFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.BloomParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class BloomFilter( override val value: BloomParams = BloomParams.Default ) : Transformation, Filter.Bloom { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.bloom( src = input, threshold = value.threshold, intensity = value.intensity, radius = value.radius, softKnee = value.softKnee, exposure = value.exposure, gamma = value.gamma ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BokehFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha import kotlin.math.roundToInt @FilterInject internal class BokehFilter( override val value: Pair = 6f to 6f ) : Transformation, Filter.Bokeh { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = Aire.getBokehKernel( kernelSize = value.first.roundToInt(), sides = value.second.roundToInt() ), morphOp = MorphOp.DILATE, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.roundToInt(), kernelWidth = value.first.roundToInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BorderFrameFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class BorderFrameFilter( override val value: Triple = Triple(20f, 40f, Color.White.toModel()) ) : Transformation, Filter.BorderFrame { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap { val horizontal = value.first.roundToInt() val vertical = value.second.roundToInt() return createBitmap( width = input.width + horizontal * 2, height = input.height + vertical * 2 ).applyCanvas { drawColor(value.third.colorInt) drawBitmap( bitmap = input, position = Position.Center ) } } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class BoxBlurFilter( override val value: Float = 10f, ) : Transformation, Filter.BoxBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.boxBlur( bitmap = input, kernelSize = 2 * value.toInt() + 1 ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrightnessFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class BrightnessFilter( override val value: Float = 0.5f, ) : Transformation, Filter.Brightness { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.brightness( bitmap = input, bias = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BrowniFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class BrowniFilter( override val value: Unit = Unit ) : Transformation, Filter.Browni { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.BROWNI).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BulgeDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.PointF import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageBulgeDistortionFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class BulgeDistortionFilter( override val value: Pair = 0.25f to 0.5f, ) : GPUFilterTransformation(), Filter.BulgeDistortion { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageBulgeDistortionFilter( /* radius = */ value.first, /* scale = */value.second, /* center = */PointF(0.5f, 0.5f) ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/BurkesDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class BurkesDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.BurkesDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Burkes, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CGAColorSpaceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageCGAColorspaceFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class CGAColorSpaceFilter( override val value: Unit = Unit, ) : GPUFilterTransformation(), Filter.CGAColorSpace { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageCGAColorspaceFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CandlelightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class CandlelightFilter( override val value: Float = 1f ) : ChainTransformation, Filter.Candlelight { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_candlelight)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CannyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.image_processing.ImageProcessing @FilterInject internal class CannyFilter( override val value: Pair = 100f to 200f ) : Transformation, Filter.Canny { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ImageProcessing.canny( bitmap = input, thresholdOne = value.first, thresholdTwo = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CaramelDarknessFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class CaramelDarknessFilter( override val value: Unit = Unit ) : Transformation, Filter.CaramelDarkness { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.CARAMEL_DARKNESS).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CelluloidFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class CelluloidFilter( override val value: Float = 1f ) : ChainTransformation, Filter.Celluloid { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_celluloid)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ChannelMixFilter.kt ================================================ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.ChannelMixFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ChannelMixParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class ChannelMixFilter( override val value: ChannelMixParams = ChannelMixParams.Default ) : JhFilterTransformation(), Filter.ChannelMix { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = ChannelMixFilter().apply { blueGreen = value.blueGreen redBlue = value.redBlue greenRed = value.greenRed intoR = value.intoR intoG = value.intoG intoB = value.intoB } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CircleBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.ConvolveKernels import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.convolution.convolve2D @FilterInject internal class CircleBlurFilter( override val value: Float = 25f, ) : Transformation, Filter.CircleBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.convolve2D( input = input, kernelProducer = ConvolveKernels::circle, size = value.roundTo(NEAREST_ODD_ROUNDING).toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CirclePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class CirclePixelationFilter( override val value: Float = 24f, ) : Transformation, Filter.CirclePixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { circle(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class ClaheFilter( override val value: Triple = Triple(0.5f, 8f, 8f) ) : Transformation, Filter.Clahe { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.clahe( bitmap = input, threshold = value.first, gridSizeHorizontal = value.second.roundToInt(), gridSizeVertical = value.third.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSLFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ClaheHSLFilter( override val value: ClaheParams = ClaheParams.Default ) : Transformation, Filter.ClaheHSL { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.claheHSL( bitmap = input, threshold = value.threshold, gridSizeHorizontal = value.gridSizeHorizontal, gridSizeVertical = value.gridSizeVertical, binsCount = value.binsCount ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheHSVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ClaheHSVFilter( override val value: ClaheParams = ClaheParams.Default ) : Transformation, Filter.ClaheHSV { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.claheHSV( bitmap = input, threshold = value.threshold, gridSizeHorizontal = value.gridSizeHorizontal, gridSizeVertical = value.gridSizeVertical, binsCount = value.binsCount ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheJzazbzFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ClaheJzazbzFilter( override val value: ClaheParams = ClaheParams.Default ) : Transformation, Filter.ClaheJzazbz { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.claheJzazbz( bitmap = input, threshold = value.threshold, gridSizeHorizontal = value.gridSizeHorizontal, gridSizeVertical = value.gridSizeVertical, binsCount = value.binsCount ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLABFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ClaheLABFilter( override val value: ClaheParams = ClaheParams.Default ) : Transformation, Filter.ClaheLAB { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.claheLAB( bitmap = input, threshold = value.threshold, gridSizeHorizontal = value.gridSizeHorizontal, gridSizeVertical = value.gridSizeVertical, binsCount = value.binsCount ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheLUVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ClaheLUVFilter( override val value: ClaheParams = ClaheParams.Default ) : Transformation, Filter.ClaheLUV { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.claheLUV( bitmap = input, threshold = value.threshold, gridSizeHorizontal = value.gridSizeHorizontal, gridSizeVertical = value.gridSizeVertical, binsCount = value.binsCount ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklabFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ClaheOklabFilter( override val value: ClaheParams = ClaheParams.Default ) : Transformation, Filter.ClaheOklab { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.claheOklab( bitmap = input, threshold = value.threshold, gridSizeHorizontal = value.gridSizeHorizontal, gridSizeVertical = value.gridSizeVertical, binsCount = value.binsCount ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClaheOklchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ClaheOklchFilter( override val value: ClaheParams = ClaheParams.Default ) : Transformation, Filter.ClaheOklch { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.claheOklch( bitmap = input, threshold = value.threshold, gridSizeHorizontal = value.gridSizeHorizontal, gridSizeVertical = value.gridSizeVertical, binsCount = value.binsCount ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ClosingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphKernels import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha @FilterInject internal class ClosingFilter( override val value: Pair = 25f to true ) : Transformation, Filter.Closing { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = if (value.second) { MorphKernels.circle(value.first.toInt()) } else { MorphKernels.box(value.first.toInt()) }, morphOp = MorphOp.CLOSING, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.toInt(), kernelWidth = value.first.toInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered2x2DitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class Clustered2x2DitheringFilter( override val value: Boolean = false, ) : Transformation, Filter.Clustered2x2Dithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Clustered2x2, isGrayScale = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered4x4DitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class Clustered4x4DitheringFilter( override val value: Boolean = false, ) : Transformation, Filter.Clustered4x4Dithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Clustered4x4, isGrayScale = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Clustered8x8DitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class Clustered8x8DitheringFilter( override val value: Boolean = false, ) : Transformation, Filter.Clustered8x8Dithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Clustered8x8, isGrayScale = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CodaChromeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class CodaChromeFilter( override val value: Unit = Unit ) : Transformation, Filter.CodaChrome { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.CODA_CHROME).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoffeeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class CoffeeFilter( override val value: Float = 1f ) : ChainTransformation, Filter.Coffee { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_coffee)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorAnomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ColorAnomalyFilter( override val value: Float = 0.56f ) : Transformation, Filter.ColorAnomaly { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = AldridgeFilter(0.5f to value).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorBalanceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageColorBalanceFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class ColorBalanceFilter( override val value: FloatArray = floatArrayOf( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ), ) : GPUFilterTransformation(), Filter.ColorBalance { override val cacheKey: String get() = value.contentHashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageColorBalanceFilter().apply { setHighlights(value.take(3).toFloatArray()) setMidtones(floatArrayOf(value[3], value[4], value[6])) setShowdows(value.takeLast(3).toFloatArray()) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorExplosionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ColorExplosionFilter( override val value: Unit = Unit ) : Transformation, Filter.ColorExplosion { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.COLOR_EXPLOSION).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorHalftoneFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.ColorHalftoneFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class ColorHalftoneFilter( override val value: Quad = Quad( first = 2f, second = 108f, third = 162f, fourth = 90f ) ) : JhFilterTransformation(), Filter.ColorHalftone { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = ColorHalftoneFilter().apply { setdotRadius(value.first) cyanScreenAngle = Math.toRadians(value.second.toDouble()).toFloat() magentaScreenAngle = Math.toRadians(value.third.toDouble()).toFloat() yellowScreenAngle = Math.toRadians(value.fourth.toDouble()).toFloat() } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMapFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.ColorMapType import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.ColorMapTransformation @FilterInject internal class AutumnFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.AUTUMN), Filter.Autumn @FilterInject internal class BoneFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.BONE), Filter.Bone @FilterInject internal class JetFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.JET), Filter.Jet @FilterInject internal class WinterFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.WINTER), Filter.Winter @FilterInject internal class RainbowFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.RAINBOW), Filter.Rainbow @FilterInject internal class OceanFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.OCEAN), Filter.Ocean @FilterInject internal class SummerFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.SUMMER), Filter.Summer @FilterInject internal class SpringFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.SPRING), Filter.Spring @FilterInject internal class CoolVariantFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.COOL), Filter.CoolVariant @FilterInject internal class HsvFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.HSV), Filter.Hsv @FilterInject internal class PinkFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.PINK), Filter.Pink @FilterInject internal class HotFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.HOT), Filter.Hot @FilterInject internal class ParulaFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.PARULA), Filter.Parula @FilterInject internal class MagmaFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.MAGMA), Filter.Magma @FilterInject internal class InfernoFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.INFERNO), Filter.Inferno @FilterInject internal class PlasmaFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.PLASMA), Filter.Plasma @FilterInject internal class ViridisFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.VIRIDIS), Filter.Viridis @FilterInject internal class CividisFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.CIVIDIS), Filter.Cividis @FilterInject internal class TwilightFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.TWILIGHT), Filter.Twilight @FilterInject internal class TwilightShiftedFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.TWILIGHT_SHIFTED), Filter.TwilightShifted @FilterInject internal class TurboFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.TURBO), Filter.Turbo @FilterInject internal class DeepGreenFilter( override val value: Unit = Unit ) : ColorMapTransformation(ColorMapType.DEEPGREEN), Filter.DeepGreen ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix3x3Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ColorMatrix3x3Filter( override val value: FloatArray = floatArrayOf( 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, ), ) : Transformation, Filter.ColorMatrix3x3 { override val cacheKey: String get() = value.contentHashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.colorMatrix( bitmap = input, colorMatrix = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorMatrix4x4Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageColorMatrixFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class ColorMatrix4x4Filter( override val value: FloatArray = floatArrayOf( 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ), ) : GPUFilterTransformation(), Filter.ColorMatrix4x4 { override val cacheKey: String get() = value.contentHashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageColorMatrixFilter(1f, value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorOverlayFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterValueWrapper import com.t8rin.imagetoolbox.core.filters.domain.model.wrap import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class ColorOverlayFilter( override val value: FilterValueWrapper = Color.Yellow.copy(0.3f).toModel().wrap(), ) : Transformation, Filter.ColorOverlay { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.drawColorAbove( input = input, color = value.wrapped.colorInt ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorPosterFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle import com.t8rin.trickle.TrickleUtils import kotlin.math.roundToInt @FilterInject internal class ColorPosterFilter( override val value: Pair = 0.5f to Color(0xFF4DFFE4).toModel() ) : Transformation, Filter.ColorPoster { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.colorPosterize( input = input, colors = TrickleUtils.generateShades( color = value.second.colorInt, shadeStep = (50 * value.first).coerceAtLeast(1f).roundToInt() ).toIntArray() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ColorfulSwirlFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ColorfulSwirlFilter( override val value: Unit = Unit ) : Transformation, Filter.ColorfulSwirl { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.COLORFUL_SWIRL).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContourFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import androidx.compose.ui.graphics.Color import com.jhlabs.ContourFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class ContourFilter( override val value: Quad = Quad( first = 5f, second = 1f, third = 0f, fourth = Color(0xff000000).toModel() ) ) : JhFilterTransformation(), Filter.Contour { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = ContourFilter().apply { levels = value.first scale = value.second offset = value.third contourColor = value.fourth.colorInt } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ContrastFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ContrastFilter( override val value: Float = 2f, ) : Transformation, Filter.Contrast { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.contrast( bitmap = input, gain = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ConvexFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ConvexFilter( override val value: Float = 3f, ) : Transformation, Filter.Convex { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.convex( bitmap = input, strength = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/Convolution3x3Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.convolution.convolve2D @FilterInject internal class Convolution3x3Filter( override val value: FloatArray = floatArrayOf( 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f ), ) : Transformation, Filter.Convolution3x3 { override val cacheKey: String get() = value.contentHashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.convolve2D( input = input, kernelProducer = { value }, size = 3 ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CoolFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class CoolFilter( override val value: Unit = Unit ) : Transformation, Filter.Cool { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.COOL).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CopyMoveDetectionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.forensics.ImageForensics import kotlin.math.roundToInt @FilterInject internal class CopyMoveDetectionFilter( override val value: Pair = 4f to 1f ) : Transformation, Filter.CopyMoveDetection { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ImageForensics.detectCopyMove( input = input, retain = value.first.roundToInt(), qCoefficent = value.second.toDouble() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropOrPerspectiveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.CropOrPerspectiveParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.auto_straight.AutoStraighten import com.t8rin.opencv_tools.auto_straight.model.Corners import com.t8rin.opencv_tools.auto_straight.model.PointD import com.t8rin.opencv_tools.auto_straight.model.StraightenMode @FilterInject internal class CropOrPerspectiveFilter( override val value: CropOrPerspectiveParams = CropOrPerspectiveParams.Default ) : Transformation, Filter.CropOrPerspective { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = AutoStraighten.process( input = input, mode = StraightenMode.Manual( corners = Corners( topLeft = PointD( x = value.topLeft.first.toDouble(), y = value.topLeft.second.toDouble() ), topRight = PointD( x = value.topRight.first.toDouble(), y = value.topRight.second.toDouble() ), bottomLeft = PointD( x = value.bottomLeft.first.toDouble(), y = value.bottomLeft.second.toDouble() ), bottomRight = PointD( x = value.bottomRight.first.toDouble(), y = value.bottomRight.second.toDouble() ), isAbsolute = value.isAbsolute ) ) ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CropToContentFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class CropToContentFilter( override val value: Pair = 0f to Color.Black.toModel() ) : Transformation, Filter.CropToContent { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.cropToContent( input = input, colorToIgnore = value.second.colorInt, tolerance = value.first ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.ConvolveKernels import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.convolution.convolve2D @FilterInject internal class CrossBlurFilter( override val value: Float = 25f, ) : Transformation, Filter.CrossBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.convolve2D( input = input, kernelProducer = ConvolveKernels::cross, size = value.roundTo(NEAREST_ODD_ROUNDING).toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrossPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class CrossPixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.CrossPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { cross(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrosshatchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageCrosshatchFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class CrosshatchFilter( override val value: Pair = 0.01f to 0.003f, ) : GPUFilterTransformation(), Filter.Crosshatch { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageCrosshatchFilter(value.first, value.second) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrtCurvatureFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class CrtCurvatureFilter( override val value: Triple = Triple(0.25f, 0.65f, 0.015f), ) : Transformation, Filter.CrtCurvature { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.crtCurvature( src = input, curvature = value.first, vignette = value.second, chroma = value.third ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CrystallizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class CrystallizeFilter( override val value: Pair = 1f to Color.Transparent.toModel() ) : Transformation, Filter.Crystallize { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.crystallize( bitmap = input, numClusters = (input.width * input.height * 0.01f * value.first).toInt(), strokeColor = value.second.colorInt ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CubeLutFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.trickle.Trickle import com.t8rin.trickle.TrickleUtils @FilterInject internal class CubeLutFilter( override val value: Pair = 1f to FileModel(""), ) : Transformation, Filter.CubeLut { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap { if (value.second.uri.isEmpty()) return input val lutPath = TrickleUtils.getAbsolutePath( uri = value.second.uri.toUri(), context = appContext ) return Trickle.applyCubeLut( input = input, cubeLutPath = lutPath, intensity = value.first ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/CyberpunkFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class CyberpunkFilter( override val value: Unit = Unit ) : Transformation, Filter.Cyberpunk { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.CYBERPUNK).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeepPurpleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class DeepPurpleFilter( override val value: Unit = Unit ) : Transformation, Filter.DeepPurple { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.DEEP_PURPLE).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DehazeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class DehazeFilter( override val value: Pair = 17f to 0.45f ) : Transformation, Filter.Dehaze { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.dehaze( bitmap = input, radius = value.first.roundToInt(), omega = value.second, ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeskewFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.auto_straight.AutoStraighten import com.t8rin.opencv_tools.auto_straight.model.StraightenMode @FilterInject internal class DeskewFilter( override val value: Pair = 15f to true ) : Transformation, Filter.Deskew { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = AutoStraighten.process( input = input, mode = StraightenMode.Deskew( maxSkew = value.first.toInt(), allowCrop = value.second ) ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DespeckleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.DespeckleFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class DespeckleFilter( override val value: Unit = Unit, ) : JhFilterTransformation(), Filter.Despeckle { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = DespeckleFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeutaromalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class DeutaromalyFilter( override val value: Unit = Unit ) : Transformation, Filter.Deutaromaly { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.DEUTAROMALY).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DeutaronotopiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class DeutaronotopiaFilter( override val value: Unit = Unit ) : Transformation, Filter.Deutaronotopia { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.DEUTARONOPIA).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiamondPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class DiamondPixelationFilter( override val value: Float = 24f, ) : Transformation, Filter.DiamondPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { diamond(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DiffuseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.DiffuseFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class DiffuseFilter( override val value: Float = 4f ) : JhFilterTransformation(), Filter.Diffuse { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = DiffuseFilter().apply { scale = value } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DigitalCodeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class DigitalCodeFilter( override val value: Unit = Unit ) : Transformation, Filter.DigitalCode { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.DIGITAL_CODE).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DilationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphKernels import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha @FilterInject internal class DilationFilter( override val value: Pair = 25f to true ) : Transformation, Filter.Dilation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = if (value.second) { MorphKernels.circle(value.first.toInt()) } else { MorphKernels.box(value.first.toInt()) }, morphOp = MorphOp.DILATE, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.toInt(), kernelWidth = value.first.toInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DoGFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.DoGFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class DoGFilter( override val value: Pair = 1f to 2f ) : JhFilterTransformation(), Filter.DoG { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = DoGFilter().apply { radius1 = value.first radius2 = value.second } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DragoFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class DragoFilter( override val value: Pair = 1f to 250f ) : Transformation, Filter.Drago { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.drago( bitmap = input, exposure = value.first, sdrWhitePoint = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/DropBluesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class DropBluesFilter( override val value: Float = 1f ) : ChainTransformation, Filter.DropBlues { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_drop_blues)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EdgyAmberFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class EdgyAmberFilter( override val value: Float = 1f ) : ChainTransformation, Filter.EdgyAmber { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_edgy_amber)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ElectricGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ElectricGradientFilter( override val value: Unit = Unit ) : Transformation, Filter.ElectricGradient { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.ELECTRIC_GRADIENT).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EmbossFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class EmbossFilter( override val value: Float = 1f, ) : Transformation, Filter.Emboss { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.emboss( bitmap = input, intensity = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedCirclePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class EnhancedCirclePixelationFilter( override val value: Float = 32f, ) : Transformation, Filter.EnhancedCirclePixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { enhancedCircle(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedDiamondPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class EnhancedDiamondPixelationFilter( override val value: Float = 48f, ) : Transformation, Filter.EnhancedDiamondPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { enhancedDiamond(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedGlitchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.GlitchParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class EnhancedGlitchFilter( override val value: GlitchParams = GlitchParams() ) : Transformation, Filter.EnhancedGlitch { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.glitch( bitmap = input, channelsShiftX = value.channelsShiftX, channelsShiftY = value.corruptionShiftY, corruptionSize = value.corruptionSize, corruptionCount = value.corruptionCount, corruptionShiftX = value.channelsShiftX, corruptionShiftY = value.corruptionShiftY ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedOilFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class EnhancedOilFilter( override val value: Float = 10f ) : Transformation, Filter.EnhancedOil { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.oil( input = input, oilRange = value.toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class EnhancedPixelationFilter( override val value: Float = 48f, ) : Transformation, Filter.EnhancedPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { enhancedSquare(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EnhancedZoomBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class EnhancedZoomBlurFilter( override val value: EnhancedZoomBlurParams = EnhancedZoomBlurParams.Default, ) : Transformation, Filter.EnhancedZoomBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.zoomBlur( bitmap = input, kernelSize = value.radius * 2 + 1, sigma = value.sigma, centerX = value.centerX, centerY = value.centerY, strength = value.strength, angle = value.angle * Math.PI.toFloat() / 180f ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.EqualizeFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class EqualizeFilter( override val value: Unit = Unit ) : JhFilterTransformation(), Filter.Equalize { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = EqualizeFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class EqualizeHistogramAdaptiveFilter( override val value: Pair = 3f to 3f ) : Transformation, Filter.EqualizeHistogramAdaptive { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHistAdaptive( bitmap = input, gridSizeHorizontal = value.first.roundToInt(), gridSizeVertical = value.second.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSLFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class EqualizeHistogramAdaptiveHSLFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : Transformation, Filter.EqualizeHistogramAdaptiveHSL { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHistAdaptiveHSL( bitmap = input, gridSizeHorizontal = value.first.roundToInt(), gridSizeVertical = value.second.roundToInt(), binsCount = value.third.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveHSVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class EqualizeHistogramAdaptiveHSVFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : Transformation, Filter.EqualizeHistogramAdaptiveHSV { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHistAdaptiveHSV( bitmap = input, gridSizeHorizontal = value.first.roundToInt(), gridSizeVertical = value.second.roundToInt(), binsCount = value.third.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLABFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class EqualizeHistogramAdaptiveLABFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : Transformation, Filter.EqualizeHistogramAdaptiveLAB { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHistAdaptiveLAB( bitmap = input, gridSizeHorizontal = value.first.roundToInt(), gridSizeVertical = value.second.roundToInt(), binsCount = value.third.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramAdaptiveLUVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class EqualizeHistogramAdaptiveLUVFilter( override val value: Triple = Triple(3f, 3f, 128f) ) : Transformation, Filter.EqualizeHistogramAdaptiveLUV { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHistAdaptiveLUV( bitmap = input, gridSizeHorizontal = value.first.roundToInt(), gridSizeVertical = value.second.roundToInt(), binsCount = value.third.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class EqualizeHistogramFilter( override val value: Unit = Unit ) : Transformation, Filter.EqualizeHistogram { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHist( bitmap = input ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramHSVFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class EqualizeHistogramHSVFilter( override val value: Float = 128f ) : Transformation, Filter.EqualizeHistogramHSV { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHistHSV( bitmap = input, binsCount = value.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/EqualizeHistogramPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class EqualizeHistogramPixelationFilter( override val value: Pair = 50f to 50f ) : Transformation, Filter.EqualizeHistogramPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.equalizeHistSquares( bitmap = input, gridSizeHorizontal = value.first.roundToInt(), gridSizeVertical = value.second.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErodeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphKernels import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha @FilterInject internal class ErodeFilter( override val value: Pair = 25f to true ) : Transformation, Filter.Erode { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = if (value.second) { MorphKernels.circle(value.first.toInt()) } else { MorphKernels.box(value.first.toInt()) }, morphOp = MorphOp.ERODE, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.toInt(), kernelWidth = value.first.toInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ErrorLevelAnalysisFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.forensics.ImageForensics import kotlin.math.roundToInt @FilterInject internal class ErrorLevelAnalysisFilter( override val value: Float = 90f ) : Transformation, Filter.ErrorLevelAnalysis { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ImageForensics.errorLevelAnalysis( input = input, quality = value.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ExposureFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageExposureFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class ExposureFilter( override val value: Float = 1f, ) : GPUFilterTransformation(), Filter.Exposure { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageExposureFilter(value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FallColorsFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class FallColorsFilter( override val value: Float = 1f ) : ChainTransformation, Filter.FallColors { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_fall_colors)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseColorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.blue import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.green import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.red import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFalseColorFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class FalseColorFilter( override val value: Pair = Color.Yellow.toModel() to Color.Magenta.toModel(), ) : GPUFilterTransformation(), Filter.FalseColor { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageFalseColorFilter( value.first.red, value.first.green, value.first.blue, value.second.red, value.second.green, value.second.blue ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FalseFloydSteinbergDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class FalseFloydSteinbergDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.FalseFloydSteinbergDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.FalseFloydSteinberg, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FantasyLandscapeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class FantasyLandscapeFilter( override val value: Unit = Unit ) : Transformation, Filter.FantasyLandscape { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.FANTASY_LANDSCAPE).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBilaterialBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class FastBilaterialBlurFilter( override val value: Triple = Triple(11f, 10f, 3f), ) : Transformation, Filter.FastBilaterialBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.fastBilateralBlur( bitmap = input, spatialSigma = value.second, rangeSigma = value.third, kernelSize = value.first.roundTo(NEAREST_ODD_ROUNDING).toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle import kotlin.math.roundToInt @FilterInject internal class FastBlurFilter( override val value: Pair = 0.5f to 5f, ) : Transformation, Filter.FastBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.fastBlur( bitmap = input, scale = value.first, radius = value.second.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur2DFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode import kotlin.math.roundToInt @FilterInject internal class FastGaussianBlur2DFilter( override val value: Pair = 10f to BlurEdgeMode.Reflect101 ) : Transformation, Filter.FastGaussianBlur2D { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.fastGaussian2Degree( bitmap = input, verticalRadius = value.first.roundToInt(), horizontalRadius = value.first.roundToInt(), edgeMode = value.second.toEdgeMode() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur3DFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode import kotlin.math.roundToInt @FilterInject internal class FastGaussianBlur3DFilter( override val value: Pair = 10f to BlurEdgeMode.Reflect101 ) : Transformation, Filter.FastGaussianBlur3D { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.fastGaussian3Degree( bitmap = input, verticalRadius = value.first.roundToInt(), horizontalRadius = value.first.roundToInt(), edgeMode = value.second.toEdgeMode() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FastGaussianBlur4DFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class FastGaussianBlur4DFilter( override val value: Float = 10f ) : Transformation, Filter.FastGaussianBlur4D { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.fastGaussian4Degree( bitmap = input, radius = value.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FilmStock50Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class FilmStock50Filter( override val value: Float = 1f ) : ChainTransformation, Filter.FilmStock50 { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_filmstock_50)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FloydSteinbergDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class FloydSteinbergDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.FloydSteinbergDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.FloydSteinberg, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FoggyNightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class FoggyNightFilter( override val value: Float = 1f ) : ChainTransformation, Filter.FoggyNight { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_foggy_night)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FractalGlassFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class FractalGlassFilter( override val value: Pair = 0.02f to 0.02f ) : Transformation, Filter.FractalGlass { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.fractalGlass( bitmap = input, glassSize = value.first, amplitude = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/FuturisticGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class FuturisticGradientFilter( override val value: Unit = Unit ) : Transformation, Filter.FuturisticGradient { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.FUTURISTIC_GRADIENT).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GammaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class GammaFilter( override val value: Float = 1.5f, ) : Transformation, Filter.Gamma { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.gamma( bitmap = input, gamma = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.GaussianPreciseLevel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode @FilterInject internal class GaussianBlurFilter( override val value: Triple = Triple( 25f, 10f, BlurEdgeMode.Reflect101 ), ) : Transformation, Filter.GaussianBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.gaussianBlur( bitmap = input, verticalKernelSize = 2 * value.first.toInt() + 1, horizontalKernelSize = 2 * value.first.toInt() + 1, verticalSigma = value.second, horizontalSigma = value.second, edgeMode = value.third.toEdgeMode(), gaussianPreciseLevel = GaussianPreciseLevel.INTEGRAL ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GaussianBoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class GaussianBoxBlurFilter( override val value: Float = 10f ) : Transformation, Filter.GaussianBoxBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.gaussianBoxBlur( bitmap = input, sigma = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlassSphereRefractionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.PointF import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageGlassSphereFilter @FilterInject internal class GlassSphereRefractionFilter( override val value: Pair = 0.25f to 0.71f, ) : GPUFilterTransformation(), Filter.GlassSphereRefraction { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageGlassSphereFilter( PointF(0.5f, 0.5f), value.first, value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.glitch.GlitchTool @FilterInject internal class GlitchFilter( override val value: Triple = Triple(20f, 15f, 9f), ) : Transformation, Filter.Glitch { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = GlitchTool.jpegGlitch( input = input, amount = value.first.toInt(), seed = value.second.toInt(), iterations = value.third.toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlitchVariantFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle import kotlin.math.roundToInt @FilterInject internal class GlitchVariantFilter( override val value: Triple = Triple(30f, 0.25f, 0.3f), ) : Transformation, Filter.GlitchVariant { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.glitchVariant( src = input, iterations = value.first.roundToInt(), maxOffsetFraction = value.second, channelShiftFraction = value.third ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GlowFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.GlowFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class GlowFilter( override val value: Float = 0.5f ) : JhFilterTransformation(), Filter.Glow { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = GlowFilter().apply { amount = value } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenForestFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class GoldenForestFilter( override val value: Float = 1f ) : ChainTransformation, Filter.GoldenForest { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_golden_forest)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GoldenHourFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class GoldenHourFilter( override val value: Unit = Unit ) : Transformation, Filter.GoldenHour { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.GOLDEN_HOUR).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GothamFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class GothamFilter( override val value: Unit = Unit, ) : Transformation, Filter.Gotham { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Trickle.gotham(input) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrainFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class GrainFilter( override val value: Float = 0.75f ) : Transformation, Filter.Grain { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.grain( bitmap = input, intensity = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GrayscaleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class GrayscaleFilter( override val value: Triple = Triple(0.299f, 0.587f, 0.114f) ) : Transformation, Filter.Grayscale { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.grayscale( bitmap = input, rPrimary = value.first, gPrimary = value.second, bPrimary = value.third ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenSunFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class GreenSunFilter( override val value: Unit = Unit ) : Transformation, Filter.GreenSun { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.GREEN_SUN).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/GreenishFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class GreenishFilter( override val value: Float = 1f ) : ChainTransformation, Filter.Greenish { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_greenish)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HDRFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class HDRFilter( override val value: Unit = Unit ) : Transformation, Filter.HDR { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.hdr(input) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HableFilmicToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class HableFilmicToneMappingFilter( override val value: Float = 1f, ) : Transformation, Filter.HableFilmicToneMapping { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.hableFilmicToneMapping( bitmap = input, exposure = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HalftoneFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageHalftoneFilter @FilterInject internal class HalftoneFilter( override val value: Float = 0.005f, ) : GPUFilterTransformation(), Filter.Halftone { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageHalftoneFilter(value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HazeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageHazeFilter @FilterInject internal class HazeFilter( override val value: Pair = 0.2f to 0f, ) : GPUFilterTransformation(), Filter.Haze { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageHazeFilter(value.first, value.second) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HejlBurgessToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class HejlBurgessToneMappingFilter( override val value: Float = 1f, ) : Transformation, Filter.HejlBurgessToneMapping { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.hejlBurgessToneMapping( bitmap = input, exposure = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HighlightsAndShadowsFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import com.t8rin.imagetoolbox.feature.filters.data.utils.gpu.GPUImageHighlightShadowWideRangeFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class HighlightsAndShadowsFilter( override val value: Float = 0.25f, ) : GPUFilterTransformation(), Filter.HighlightsAndShadows { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageHighlightShadowWideRangeFilter(value, 1f) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HorizontalWindStaggerFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toAbgr import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.ui.theme.toColor @FilterInject internal class HorizontalWindStaggerFilter( override val value: Triple = Triple( first = 0.2f, second = 90, third = Color.Transparent.toModel() ) ) : Transformation, Filter.HorizontalWindStagger { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.horizontalWindStagger( bitmap = input, windStrength = value.first, streamsCount = value.second, clearColor = value.third.colorInt.toColor().toAbgr() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HotSummerFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class HotSummerFilter( override val value: Unit = Unit ) : Transformation, Filter.HotSummer { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.HOT_SUMMER).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/HueFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageHueFilter @FilterInject internal class HueFilter( override val value: Float = 90f, ) : GPUFilterTransformation(), Filter.Hue { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageHueFilter(value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/JarvisJudiceNinkeDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class JarvisJudiceNinkeDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.JarvisJudiceNinkeDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.JarvisJudiceNinke, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KaleidoscopeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.JhFilter import com.jhlabs.KaleidoscopeFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.KaleidoscopeParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class KaleidoscopeFilter( override val value: KaleidoscopeParams = KaleidoscopeParams.Default ) : JhFilterTransformation(), Filter.Kaleidoscope { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = KaleidoscopeFilter().apply { angle = value.angle angle2 = value.angle2 centreX = value.centreX centreY = value.centreY sides = value.sides radius = value.radius } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KodakFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class KodakFilter( override val value: Float = 1f ) : ChainTransformation, Filter.Kodak { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_kodak)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/KuwaharaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageKuwaharaFilter @FilterInject internal class KuwaharaFilter( override val value: Float = 9f, ) : GPUFilterTransformation(), Filter.Kuwahara { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageKuwaharaFilter(value.toInt()) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LUT512x512Filter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.core.graphics.scale import com.t8rin.imagetoolbox.core.data.utils.safeAspectRatio import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.feature.filters.data.utils.image.loadBitmap import com.t8rin.trickle.Trickle @FilterInject internal class LUT512x512Filter( override val value: Pair = 1f to ImageModel(R.drawable.lookup), ) : Transformation, Filter.LUT512x512 { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap { val lutBitmap = value.second.data.loadBitmap(512)?.takeIf { it.safeAspectRatio == 1f } ?: return input return Trickle.applyLut( input = input, lutBitmap = lutBitmap.scale( width = 512, height = 512 ), intensity = value.first ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageLaplacianFilter @FilterInject internal class LaplacianFilter( override val value: Unit = Unit, ) : GPUFilterTransformation(), Filter.Laplacian { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageLaplacianFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LaplacianSimpleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class LaplacianSimpleFilter( override val value: Unit = Unit, ) : Transformation, Filter.LaplacianSimple { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.laplacian( bitmap = input, edgeMode = EdgeMode.REFLECT_101, scalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LavenderDreamFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class LavenderDreamFilter( override val value: Unit = Unit ) : Transformation, Filter.LavenderDream { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.LAVENDER_DREAM).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LeftToRightDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class LeftToRightDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.LeftToRightDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.LeftToRight, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LemonadeLightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class LemonadeLightFilter( override val value: Unit = Unit ) : Transformation, Filter.LemonadeLight { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.LEMONADE_LIGHT).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LensCorrectionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.model.FileModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.lens_correction.LensCorrection import com.t8rin.opencv_tools.lens_correction.model.SAMPLE_LENS_PROFILE @FilterInject internal class LensCorrectionFilter( override val value: Pair = 1f to FileModel(""), ) : Transformation, Filter.LensCorrection { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = value.second.uri.let { uri -> if (uri.isEmpty()) { LensCorrection.undistort( bitmap = input, lensDataJson = LensCorrection.SAMPLE_LENS_PROFILE, intensity = value.first.toDouble() ) } else { LensCorrection.undistort( bitmap = input, lensDataUri = uri.toUri(), intensity = value.first.toDouble() ) } } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearBoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toFunc @FilterInject internal class LinearBoxBlurFilter( override val value: Pair = 10 to TransferFunc.SRGB ) : Transformation, Filter.LinearBoxBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.linearBoxBlur( bitmap = input, kernelSize = 2 * value.first + 1, transferFunction = value.second.toFunc() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode import com.t8rin.imagetoolbox.feature.filters.data.utils.toFunc @FilterInject internal class LinearFastGaussianBlurFilter( override val value: Triple = Triple( first = 10, second = TransferFunc.SRGB, third = BlurEdgeMode.Reflect101 ) ) : Transformation, Filter.LinearFastGaussianBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.linearFastGaussian( bitmap = input, verticalRadius = value.first, horizontalRadius = value.first, transferFunction = value.second.toFunc(), edgeMode = value.third.toEdgeMode() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearFastGaussianBlurNextFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode import com.t8rin.imagetoolbox.feature.filters.data.utils.toFunc @FilterInject internal class LinearFastGaussianBlurNextFilter( override val value: Triple = Triple( first = 10, second = TransferFunc.SRGB, third = BlurEdgeMode.Reflect101 ) ) : Transformation, Filter.LinearFastGaussianBlurNext { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.linearFastGaussianNext( bitmap = input, verticalRadius = value.first, horizontalRadius = value.first, transferFunction = value.second.toFunc(), edgeMode = value.third.toEdgeMode() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearGaussianParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode import com.t8rin.imagetoolbox.feature.filters.data.utils.toFunc @FilterInject internal class LinearGaussianBlurFilter( override val value: LinearGaussianParams = LinearGaussianParams.Default ) : Transformation, Filter.LinearGaussianBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.linearGaussianBlur( bitmap = input, verticalKernelSize = value.kernelSize.toFloat().roundTo(NEAREST_ODD_ROUNDING).toInt(), horizontalKernelSize = value.kernelSize.toFloat().roundTo(NEAREST_ODD_ROUNDING).toInt(), verticalSigma = value.sigma, horizontalSigma = value.sigma, edgeMode = value.edgeMode.toEdgeMode(), transferFunction = value.transferFunction.toFunc() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearGaussianBoxBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toFunc @FilterInject internal class LinearGaussianBoxBlurFilter( override val value: Pair = 10f to TransferFunc.SRGB ) : Transformation, Filter.LinearGaussianBoxBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.linearGaussianBoxBlur( bitmap = input, sigma = value.first, transferFunction = value.second.toFunc() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearStackBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toFunc @FilterInject internal class LinearStackBlurFilter( override val value: Pair = 10 to TransferFunc.SRGB ) : Transformation, Filter.LinearStackBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.linearStackBlur( bitmap = input, verticalRadius = value.first, horizontalRadius = value.first, transferFunction = value.second.toFunc() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTentBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toFunc @FilterInject internal class LinearTentBlurFilter( override val value: Pair = 11f to TransferFunc.SRGB, ) : Transformation, Filter.LinearTentBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.linearTentBlur( bitmap = input, sigma = value.first.roundTo(NEAREST_ODD_ROUNDING), transferFunction = value.second.toFunc() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LinearTiltShiftFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearTiltShiftParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class LinearTiltShiftFilter( override val value: LinearTiltShiftParams = LinearTiltShiftParams.Default ) : Transformation, Filter.LinearTiltShift { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.horizontalTiltShift( bitmap = input, radius = value.blurRadius.roundToInt(), sigma = value.sigma, anchorX = value.anchorX, anchorY = value.anchorY, tiltRadius = value.holeRadius, angle = value.angle * Math.PI.toFloat() / 180f ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LogarithmicToneMappingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class LogarithmicToneMappingFilter( override val value: Float = 1f, ) : Transformation, Filter.LogarithmicToneMapping { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.logarithmicToneMapping( bitmap = input, exposure = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LookupFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageLookupFilter @FilterInject internal class LookupFilter( override val value: Float = -1f, ) : GPUFilterTransformation(), Filter.Lookup { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageLookupFilter(value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LowPolyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle import kotlin.math.roundToInt @FilterInject internal class LowPolyFilter( override val value: Pair = 2000f to true ) : Transformation, Filter.LowPoly { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.lowPoly( input = input, alphaOrPointCount = value.first.roundToInt().toFloat(), fill = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/LuminanceGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.forensics.ImageForensics @FilterInject internal class LuminanceGradientFilter( override val value: Unit = Unit ) : Transformation, Filter.LuminanceGradient { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ImageForensics.luminanceGradient( input = input ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MarbleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class MarbleFilter( override val value: Triple = Triple(0.02f, 1f, 1f) ) : Transformation, Filter.Marble { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.marble( bitmap = input, intensity = value.first, turbulence = value.second, amplitude = value.third ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MedianBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class MedianBlurFilter( override val value: Float = 10f ) : Transformation, Filter.MedianBlur { override val cacheKey: String get() = value.hashCode() .toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.medianBlur( bitmap = input, kernelSize = 2 * value.roundToInt() + 1 ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MicroMacroPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class MicroMacroPixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.MicroMacroPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { microMacro(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MirrorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.mirror @FilterInject internal class MirrorFilter( override val value: Pair = 0.5f to MirrorSide.LeftToRight, ) : Transformation, Filter.Mirror { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = input.mirror( value = value.first, side = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MissEtikateFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class MissEtikateFilter( override val value: Float = 1f ) : ChainTransformation, Filter.MissEtikate { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_miss_etikate)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MobiusFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class MobiusFilter( override val value: Triple = Triple(1f, 0.9f, 1f) ) : Transformation, Filter.Mobius { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.mobius( bitmap = input, exposure = value.first, transition = value.second, peak = value.third ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MoireFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.moire.Moire @FilterInject internal class MoireFilter( override val value: Unit = Unit ) : Transformation, Filter.Moire { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Moire.remove(input) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MonochromeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.blue import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.green import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.red import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class MonochromeFilter( override val value: Pair = 1f to Color( red = 0.6f, green = 0.45f, blue = 0.3f, alpha = 1.0f ).toModel(), ) : Transformation, Filter.Monochrome { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.monochrome( bitmap = input, color = floatArrayOf( value.second.red, value.second.green, value.second.blue, value.first ), exposure = 1f ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MorphologicalGradientFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphKernels import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha @FilterInject internal class MorphologicalGradientFilter( override val value: Pair = 25f to true ) : Transformation, Filter.MorphologicalGradient { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = if (value.second) { MorphKernels.circle(value.first.toInt()) } else { MorphKernels.box(value.first.toInt()) }, morphOp = MorphOp.GRADIENT, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.toInt(), kernelWidth = value.first.toInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/MotionBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toEdgeMode @FilterInject internal class MotionBlurFilter( override val value: Triple = Triple(25, 45f, BlurEdgeMode.Reflect101), ) : Transformation, Filter.MotionBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.motionBlur( bitmap = input, kernelSize = value.first.toFloat().roundTo(NEAREST_ODD_ROUNDING).toInt(), angle = value.second, borderMode = value.third.toEdgeMode(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NativeStackBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class NativeStackBlurFilter( override val value: Float = 25f, ) : Transformation, Filter.NativeStackBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.stackBlur( bitmap = input, verticalRadius = value.toInt(), horizontalRadius = value.toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NegativeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageColorInvertFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class NegativeFilter( override val value: Unit = Unit, ) : GPUFilterTransformation(), Filter.Negative { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageColorInvertFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NeonFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.wrap import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class NeonFilter( override val value: Triple = Triple( first = 1f, second = 0.26f, third = Color.Magenta.toModel() ) ) : ChainTransformation, Filter.Neon { override val cacheKey: String get() = value.hashCode().toString() override fun getTransformations(): List> = listOf( SharpenFilter(value.second), SobelEdgeDetectionFilter(value.first), RGBFilter(value.third.wrap()) ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightMagicFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class NightMagicFilter( override val value: Unit = Unit ) : Transformation, Filter.NightMagic { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.NIGHT_MAGIC).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NightVisionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class NightVisionFilter( override val value: Unit = Unit ) : Transformation, Filter.NightVision { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.NIGHT_VISION).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NoiseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class NoiseFilter( override val value: Float = 128f ) : Transformation, Filter.Noise { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.noise( input = input, threshold = value.toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NonMaximumSuppressionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageNonMaximumSuppressionFilter @FilterInject internal class NonMaximumSuppressionFilter( override val value: Unit = Unit, ) : GPUFilterTransformation(), Filter.NonMaximumSuppression { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageNonMaximumSuppressionFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/NucleusPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class NucleusPixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.NucleusPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { nucleus(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OffsetFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.jhlabs.JhFilter import com.jhlabs.OffsetFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation import kotlin.math.roundToInt @FilterInject internal class OffsetFilter( override val value: Pair = 0.25f to 0.25f ) : JhFilterTransformation(), Filter.Offset { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = OffsetFilter() override fun createFilter(image: Bitmap): JhFilter = OffsetFilter().apply { xOffset = (value.first * image.width).roundToInt() yOffset = (value.second * image.height).roundToInt() } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OilFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class OilFilter( override val value: Pair = 4f to 1f ) : Transformation, Filter.Oil { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.oil( bitmap = input, radius = value.first.roundToInt(), levels = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OldTvFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class OldTvFilter( override val value: Unit ) : ChainTransformation, Filter.OldTv { override val cacheKey: String get() = value.hashCode().toString() override fun getTransformations(): List> = listOf( GrainFilter(0.36f), HazeFilter(0f to 0.3f), MonochromeFilter(1f to Color(0xFF1C3A00).toModel()) ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpacityFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageOpacityFilter @FilterInject internal class OpacityFilter( override val value: Float = 0.5f, ) : GPUFilterTransformation(), Filter.Opacity { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageOpacityFilter(value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OpeningFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphKernels import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha @FilterInject internal class OpeningFilter( override val value: Pair = 25f to true ) : Transformation, Filter.Opening { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = if (value.second) { MorphKernels.circle(value.first.toInt()) } else { MorphKernels.box(value.first.toInt()) }, morphOp = MorphOp.OPENING, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.toInt(), kernelWidth = value.first.toInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrangeHazeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class OrangeHazeFilter( override val value: Unit = Unit ) : Transformation, Filter.OrangeHaze { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.ORANGE_HAZE).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/OrbitalPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class OrbitalPixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.OrbitalPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { orbital(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.feature.filters.data.utils.image.loadBitmap import com.t8rin.trickle.Trickle @FilterInject internal class PaletteTransferFilter( override val value: Pair = 1f to ImageModel(R.drawable.filter_preview_source_2), ) : Transformation, Filter.PaletteTransfer { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap { val reference = value.second.data.loadBitmap(1000) ?: return input return Trickle.transferPalette( source = reference, target = input, intensity = value.first ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PaletteTransferVariantFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PaletteTransferSpace import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.feature.filters.data.utils.image.loadBitmap import com.t8rin.imagetoolbox.feature.filters.data.utils.toSpace @FilterInject internal class PaletteTransferVariantFilter( override val value: Triple = Triple( first = 1f, second = PaletteTransferSpace.OKLAB, third = ImageModel(R.drawable.filter_preview_source_2) ) ) : Transformation, Filter.PaletteTransferVariant { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap { val reference = value.third.data.loadBitmap(1000) ?: return input return Aire.copyPalette( source = reference, destination = input, colorSpace = value.second.toSpace(), intensity = value.first ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PastelFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class PastelFilter( override val value: Unit = Unit ) : Transformation, Filter.Pastel { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.PASTEL).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PerlinDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class PerlinDistortionFilter( override val value: Triple = Triple(0.02f, 1f, 1f) ) : Transformation, Filter.PerlinDistortion { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.perlinDistortion( bitmap = input, intensity = value.first, turbulence = value.second, amplitude = value.third ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.jhlabs.JhFilter import com.jhlabs.PinchFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.PinchParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation import kotlin.math.max @FilterInject internal class PinchFilter( override val value: PinchParams = PinchParams.Default ) : JhFilterTransformation(), Filter.Pinch { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = PinchFilter() override fun createFilter(image: Bitmap): JhFilter = PinchFilter().apply { angle = Math.toRadians(value.angle.toDouble()).toFloat() centreX = value.centreX centreY = value.centreY radius = value.radius * (max(image.width, image.height) / 2f) amount = value.amount } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PinkDreamFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class PinkDreamFilter( override val value: Unit = Unit ) : Transformation, Filter.PinkDream { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.PINK_DREAM).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelMeltFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle import kotlin.math.roundToInt @FilterInject internal class PixelMeltFilter( override val value: Pair = 20f to 0.5f, ) : Transformation, Filter.PixelMelt { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.pixelMelt( src = input, maxDrop = value.first.roundToInt(), strength = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class PixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.Pixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { square(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PointillizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.JhFilter import com.jhlabs.PointillizeFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class PointillizeFilter( override val value: VoronoiCrystallizeParams = VoronoiCrystallizeParams.Companion.Default ) : JhFilterTransformation(), Filter.Pointillize { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = PointillizeFilter().apply { edgeThickness = value.borderThickness edgeColor = value.color.colorInt scale = value.scale randomness = value.randomness gridType = value.shape turbulence = value.turbulence angle = Math.toRadians(value.angle.toDouble()).toFloat() stretch = value.stretch amount = value.amount } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PoissonBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class PoissonBlurFilter( override val value: Float = 10f, ) : Transformation, Filter.PoissonBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.poissonBlur( bitmap = input, kernelSize = 2 * value.toInt() + 1 ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolarCoordinatesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.JhFilter import com.jhlabs.PolarFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PolarCoordinatesType import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class PolarCoordinatesFilter( override val value: PolarCoordinatesType = PolarCoordinatesType.RECT_TO_POLAR ) : JhFilterTransformation(), Filter.PolarCoordinates { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = PolarFilter(value.ordinal) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolaroidFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class PolaroidFilter( override val value: Unit = Unit ) : Transformation, Filter.Polaroid { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.POLAROID).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PolkaDotFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.trickle.Trickle @FilterInject internal class PolkaDotFilter( override val value: Triple = Triple( first = 10, second = 8, third = Color.Black.toModel() ) ) : Transformation, Filter.PolkaDot { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = if (value.third.colorInt == Color.Transparent.toArgb()) { Trickle.polkaDot( input = input, dotRadius = value.first, spacing = value.second ) } else { Trickle.drawColorBehind( input = Trickle.polkaDot( input = input, dotRadius = value.first, spacing = value.second ), color = value.third.colorInt ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PopArtFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.toMode import com.t8rin.trickle.Trickle @FilterInject internal class PopArtFilter( override val value: Triple = Triple( first = 1f, second = Color.Red.toModel(), third = PopArtBlendingMode.MULTIPLY ) ) : Transformation, Filter.PopArt { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.popArt( input = input, color = value.second.colorInt, strength = value.first, blendMode = value.third.toMode() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PosterizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImagePosterizeFilter @FilterInject internal class PosterizeFilter( override val value: Float = 5f, ) : GPUFilterTransformation(), Filter.Posterize { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImagePosterizeFilter(value.toInt()) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtanopiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ProtanopiaFilter( override val value: Unit = Unit ) : Transformation, Filter.Protanopia { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.PROTANOPIA).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ProtonomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ProtonomalyFilter( override val value: Unit = Unit ) : Transformation, Filter.Protonomaly { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.PROTANOMALY).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PulseGridPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class PulseGridPixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.PulseGridPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { pulseGrid(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/PurpleMistFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class PurpleMistFilter( override val value: Unit = Unit ) : Transformation, Filter.PurpleMist { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.PURPLE_MIST).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/QuantizierFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.AireColorMapper import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class QuantizierFilter( override val value: Float = 256f ) : Transformation, Filter.Quantizier { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.palette( bitmap = input, maxColors = value.toInt(), colorMapper = AireColorMapper.COVER_TREE ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RGBFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.blue import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.green import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.red import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.FilterValueWrapper import com.t8rin.imagetoolbox.core.filters.domain.model.wrap import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageRGBFilter @FilterInject internal class RGBFilter( override val value: FilterValueWrapper = Color.Green.toModel().wrap(), ) : GPUFilterTransformation(), Filter.RGB { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageRGBFilter( value.wrapped.red, value.wrapped.green, value.wrapped.blue ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialTiltShiftFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.RadialTiltShiftParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class RadialTiltShiftFilter( override val value: RadialTiltShiftParams = RadialTiltShiftParams.Default ) : Transformation, Filter.RadialTiltShift { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.tiltShift( bitmap = input, radius = value.blurRadius.roundToInt(), sigma = value.sigma, anchorX = value.anchorX, anchorY = value.anchorY, tiltRadius = value.holeRadius ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RadialWeavePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class RadialWeavePixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.RadialWeavePixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { radialWeave(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RainbowWorldFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class RainbowWorldFilter( override val value: Unit = Unit ) : Transformation, Filter.RainbowWorld { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.RAINBOW_WORLD).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RandomDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class RandomDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.RandomDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Random, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RedSwirlFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class RedSwirlFilter( override val value: Unit = Unit ) : Transformation, Filter.RedSwirl { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.RED_SWIRL).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReduceNoiseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.JhFilter import com.jhlabs.ReduceNoiseFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class ReduceNoiseFilter( override val value: Unit = Unit ) : JhFilterTransformation(), Filter.ReduceNoise { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = ReduceNoiseFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RemoveColorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class RemoveColorFilter( override val value: Pair = 0f to Color(0xFF000000).toModel(), ) : Transformation, Filter.RemoveColor { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ReplaceColorFilter( value = Triple( first = value.first, second = value.second, third = Color.Transparent.toModel() ) ).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ReplaceColorFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class ReplaceColorFilter( override val value: Triple = Triple( first = 0f, second = Color(red = 0.0f, green = 0.0f, blue = 0.0f, alpha = 1.0f).toModel(), third = Color(red = 1.0f, green = 1.0f, blue = 1.0f, alpha = 1.0f).toModel() ), ) : Transformation, Filter.ReplaceColor { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.replaceColor( input = input, sourceColor = value.second.colorInt, targetColor = value.third.colorInt, tolerance = value.first ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RetroYellowFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class RetroYellowFilter( override val value: Float = 1f ) : ChainTransformation, Filter.RetroYellow { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_retro_yellow)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RingBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.ConvolveKernels import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.convolution.convolve2D @FilterInject internal class RingBlurFilter( override val value: Float = 25f, ) : Transformation, Filter.RingBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.convolve2D( input = input, kernelProducer = ConvolveKernels::ring, size = value.roundTo(NEAREST_ODD_ROUNDING).toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/RubberStampFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.jhlabs.JhFilter import com.jhlabs.StampFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.RubberStampParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation import kotlin.math.max @FilterInject internal class RubberStampFilter( override val value: RubberStampParams = RubberStampParams.Default ) : JhFilterTransformation(), Filter.RubberStamp { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = StampFilter() override fun createFilter(image: Bitmap): JhFilter = StampFilter().apply { threshold = value.threshold softness = value.softness radius = value.radius * (max(image.width, image.height) / 64f) white = value.firstColor.colorInt black = value.secondColor.colorInt } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SandPaintingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class SandPaintingFilter( override val value: Triple = Triple(5000, 50, Color.Black.toModel()) ) : Transformation, Filter.SandPainting { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.drawColorBehind( input = Trickle.sandPainting( input = input, alphaOrPointCount = value.first.toFloat(), threshold = value.second ), color = value.third.colorInt ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SaturationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SaturationFilter( override val value: Pair = 2f to true, ) : Transformation, Filter.Saturation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.saturation( bitmap = input, saturation = value.first, tonemap = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SeamCarvingFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.opencv_tools.seam_carving.SeamCarver @FilterInject internal class SeamCarvingFilter( override val value: IntegerSize = IntegerSize.Zero ) : Transformation, Filter.SeamCarving { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = if (value.isZero()) { input } else { SeamCarver.carve( bitmap = input, desiredWidth = value.width, desiredHeight = value.height ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SepiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SepiaFilter( override val value: Unit = Unit ) : Transformation, Filter.Sepia { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.SEPIA).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SharpenFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageSharpenFilter @FilterInject internal class SharpenFilter( override val value: Float = 1f, ) : GPUFilterTransformation(), Filter.Sharpen { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageSharpenFilter(value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ShuffleBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle import kotlin.math.roundToInt @FilterInject internal class ShuffleBlurFilter( override val value: Pair = 35f to 1f ) : Transformation, Filter.ShuffleBlur { private val radiusMapping = listOf(0f, RAD_1) + List(200) { RAD_2 + STEP * it } override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.shuffleBlur( input = input, threshold = value.second, strength = radiusMapping.getOrNull(value.first.roundToInt()) ?: 0f ) private companion object { private const val RAD_1 = 0.001f private const val RAD_2 = 0.005f private const val STEP = 0.005f } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SideFadeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.core.graphics.applyCanvas import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.data.getPaint import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide import com.t8rin.imagetoolbox.core.filters.domain.model.params.SideFadeParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import kotlin.math.roundToInt @FilterInject internal class SideFadeFilter( override val value: SideFadeParams = SideFadeParams.Relative(FadeSide.Start, 0.5f), ) : Transformation, Filter.SideFade { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap { val bitmap = input.copy(input.safeConfig, true).apply { setHasAlpha(true) } val fadeSize: Int = when (value) { is SideFadeParams.Absolute -> value.size is SideFadeParams.Relative -> { when (value.side) { FadeSide.Start, FadeSide.End -> { bitmap.width * value.scale } FadeSide.Bottom, FadeSide.Top -> { bitmap.height * value.scale } }.roundToInt() } } val strength = when (value) { is SideFadeParams.Absolute -> value.strength is SideFadeParams.Relative -> 1f } return bitmap.applyCanvas { drawPaint( value.side.getPaint( bmp = input, length = fadeSize, strength = strength ) ) } } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class SierraDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.SierraDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Sierra, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SierraLiteDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class SierraLiteDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.SierraLiteDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.SierraLite, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleOldTvFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class SimpleOldTvFilter( override val value: Unit = Unit ) : Transformation, Filter.SimpleOldTv { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.tv(input) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSketchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class SimpleSketchFilter( override val value: Unit = Unit ) : Transformation, Filter.SimpleSketch { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.sketch(input) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleSolarizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.JhFilter import com.jhlabs.SolarizeFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class SimpleSolarizeFilter( override val value: Unit = Unit ) : JhFilterTransformation(), Filter.SimpleSolarize { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = SolarizeFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleThresholdDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class SimpleThresholdDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.SimpleThresholdDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.SimpleThreshold, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SimpleWeavePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class SimpleWeavePixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.SimpleWeavePixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { simpleWeave(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SketchFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SketchFilter( override val value: Float = 5f, ) : Transformation, Filter.Sketch { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.removeShadows( bitmap = input, kernelSize = value.toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmearFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.JhFilter import com.jhlabs.SmearFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.SmearParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class SmearFilter( override val value: SmearParams = SmearParams.Default ) : JhFilterTransformation(), Filter.Smear { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = SmearFilter().apply { angle = Math.toRadians(value.angle.toDouble()).toFloat() density = value.density mix = value.mix distance = value.distance shape = value.shape } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SmoothToonFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageSmoothToonFilter @FilterInject internal class SmoothToonFilter( override val value: Triple = Triple(0.5f, 0.2f, 10f), ) : GPUFilterTransformation(), Filter.SmoothToon { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageSmoothToonFilter().apply { setBlurSize(value.first) setThreshold(value.second) setQuantizationLevels(value.third) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelEdgeDetectionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageSobelEdgeDetectionFilter @FilterInject internal class SobelEdgeDetectionFilter( override val value: Float = 1f, ) : GPUFilterTransformation(), Filter.SobelEdgeDetection { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageSobelEdgeDetectionFilter().apply { setLineSize(value) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SobelSimpleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SobelSimpleFilter( override val value: Unit = Unit, ) : Transformation, Filter.SobelSimple { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.sobel( bitmap = input, edgeMode = EdgeMode.REFLECT_101, scalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class SoftEleganceFilter( override val value: Float = 1f, ) : ChainTransformation, Filter.SoftElegance { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_soft_elegance_1)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftEleganceVariantFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.transformation.ChainTransformation import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.core.resources.R @FilterInject internal class SoftEleganceVariantFilter( override val value: Float = 1f ) : ChainTransformation, Filter.SoftEleganceVariant { override fun getTransformations(): List> = listOf( LUT512x512Filter(value to ImageModel(R.drawable.lookup_soft_elegance_2)) ) override val cacheKey: String get() = value.hashCode().toString() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SoftSpringLightFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SoftSpringLightFilter( override val value: Unit = Unit ) : Transformation, Filter.SoftSpringLight { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.SOFT_SPRING_LIGHT).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SolarizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageSolarizeFilter @FilterInject internal class SolarizeFilter( override val value: Float = 0.5f, ) : GPUFilterTransformation(), Filter.Solarize { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageSolarizeFilter(value) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpacePortalFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SpacePortalFilter( override val value: Unit = Unit ) : Transformation, Filter.SpacePortal { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.SPACE_PORTAL).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SparkleFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.jhlabs.JhFilter import com.jhlabs.SparkleFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.SparkleParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation import kotlin.math.max import kotlin.math.roundToInt @FilterInject internal class SparkleFilter( override val value: SparkleParams = SparkleParams.Default ) : JhFilterTransformation(), Filter.Sparkle { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = SparkleFilter(1, 1) override fun createFilter(image: Bitmap): JhFilter = SparkleFilter( (value.centreX * image.width).roundToInt(), (value.centreY * image.height).roundToInt() ).apply { amount = value.amount rays = value.rays radius = (value.radius * (max(image.width, image.height) / 2f)).roundToInt() randomness = value.randomness color = value.color.colorInt } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpectralFireFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SpectralFireFilter( override val value: Unit = Unit ) : Transformation, Filter.SpectralFire { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.SPECTRAL_FIRE).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereLensDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.jhlabs.JhFilter import com.jhlabs.SphereLensDistortionFilter import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation import kotlin.math.max @FilterInject internal class SphereLensDistortionFilter( override val value: Quad = 1.5f to 0.5f qto (0.5f to 0.5f) ) : JhFilterTransformation(), Filter.SphereLensDistortion { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = SphereLensDistortionFilter() override fun createFilter(image: Bitmap): JhFilter = SphereLensDistortionFilter().apply { refractionIndex = value.first radius = value.second * (max(image.width, image.height) / 2f) centreX = value.third centreY = value.fourth } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SphereRefractionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.PointF import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageSphereRefractionFilter @FilterInject internal class SphereRefractionFilter( override val value: Pair = 0.25f to 0.71f, ) : GPUFilterTransformation(), Filter.SphereRefraction { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageSphereRefractionFilter( PointF(0.5f, 0.5f), value.first, value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SpotHealFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.SpotHealMode import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.image.loadBitmap import com.t8rin.neural_tools.inpaint.LaMaProcessor import com.t8rin.opencv_tools.spot_heal.SpotHealer import com.t8rin.opencv_tools.spot_heal.model.HealType @FilterInject internal class SpotHealFilter( override val value: Pair, ) : Transformation, Filter.SpotHeal { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap { val mask = value.first.data.loadBitmap() ?: return input return when (value.second) { SpotHealMode.OpenCV -> openCV( input = input, mask = mask ) SpotHealMode.LaMa -> lama( input = input, mask = mask ) } } private fun openCV( input: Bitmap, mask: Bitmap ) = SpotHealer.heal( image = input, mask = mask, radius = 3f, type = HealType.TELEA ) private fun lama( input: Bitmap, mask: Bitmap ) = LaMaProcessor.inpaint( image = input, mask = mask ) ?: openCV( input = input, mask = mask ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StackBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle import kotlin.math.roundToInt @FilterInject internal class StackBlurFilter( override val value: Pair = 0.5f to 10f, ) : Transformation, Filter.StackBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.stackBlur( bitmap = input, scale = value.first, radius = value.second.roundToInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StaggeredPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class StaggeredPixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.StaggeredPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { staggered(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StarBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.ConvolveKernels import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.convolution.convolve2D @FilterInject internal class StarBlurFilter( override val value: Float = 25f, ) : Transformation, Filter.StarBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.convolve2D( input = input, kernelProducer = ConvolveKernels::star, size = value.roundTo(NEAREST_ODD_ROUNDING).toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StrokePixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool import com.t8rin.trickle.Trickle @FilterInject internal class StrokePixelationFilter( override val value: Pair = 20f to Color.Black.toModel(), ) : Transformation, Filter.StrokePixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { stroke(value.first) } ).let { Trickle.drawColorBehind( input = it, color = value.second.colorInt ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/StuckiDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class StuckiDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.StuckiDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Stucki, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SunriseFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class SunriseFilter( override val value: Unit = Unit ) : Transformation, Filter.Sunrise { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.SUNRISE).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/SwirlDistortionFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.PointF import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageSwirlFilter @FilterInject internal class SwirlDistortionFilter( override val value: Quad = 0.5f to 1f qto (0.5f to 0.5f) ) : GPUFilterTransformation(), Filter.SwirlDistortion { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageSwirlFilter(value.first, value.second, PointF(value.third, value.fourth)) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TentBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.NEAREST_ODD_ROUNDING import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class TentBlurFilter( override val value: Float = 15f, ) : Transformation, Filter.TentBlur { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize, ): Bitmap = Aire.tentBlur( bitmap = input, sigma = value.roundTo(NEAREST_ODD_ROUNDING) ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ThresholdFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class ThresholdFilter( override val value: Float = 128f ) : Transformation, Filter.Threshold { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.threshold( bitmap = input, level = value.toInt() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToneCurvesFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.curves.GPUImageToneCurveFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.ToneCurvesParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter @FilterInject internal class ToneCurvesFilter( override val value: ToneCurvesParams = ToneCurvesParams.Default, ) : GPUFilterTransformation(), Filter.ToneCurves { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageToneCurveFilter(value.controlPoints) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ToonFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageToonFilter @FilterInject internal class ToonFilter( override val value: Pair = 0.2f to 10f, ) : GPUFilterTransformation(), Filter.Toon { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageToonFilter(value.first, value.second) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TopHatFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.MorphKernels import com.awxkee.aire.MorphOp import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.TrickleUtils.checkHasAlpha @FilterInject internal class TopHatFilter( override val value: Pair = 25f to true ) : Transformation, Filter.TopHat { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.morphology( bitmap = input, kernel = if (value.second) { MorphKernels.circle(value.first.toInt()) } else { MorphKernels.box(value.first.toInt()) }, morphOp = MorphOp.TOPHAT, morphOpMode = if (input.checkHasAlpha()) MorphOpMode.RGBA else MorphOpMode.RGB, borderMode = EdgeMode.REFLECT_101, kernelHeight = value.first.toInt(), kernelWidth = value.first.toInt(), borderScalar = Scalar.ZEROS ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TriToneFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class TriToneFilter( override val value: Triple = Triple( first = Color(0xFFFF003B).toModel(), second = Color(0xFF831111).toModel(), third = Color(0xFFFF0099).toModel() ) ) : Transformation, Filter.TriTone { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.tritone( input = input, shadowsColor = value.first.colorInt, middleColor = value.second.colorInt, highlightsColor = value.third.colorInt ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritanopiaFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class TritanopiaFilter( override val value: Unit = Unit ) : Transformation, Filter.Tritanopia { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.TRITANOPIA).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TritonomalyFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class TritonomalyFilter( override val value: Unit = Unit ) : Transformation, Filter.Tritonomaly { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.Assistance.TRITONOMALY).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwirlFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.jhlabs.JhFilter import com.jhlabs.TwirlFilter import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation import kotlin.math.max @FilterInject internal class TwirlFilter( override val value: Quad = 45f to 0.5f qto (0.5f to 0.5f) ) : JhFilterTransformation(), Filter.Twirl { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = TwirlFilter() override fun createFilter(image: Bitmap): JhFilter = TwirlFilter().apply { angle = Math.toRadians(value.first.toDouble()).toFloat() centreX = value.second centreY = value.third radius = value.fourth * (max(image.width, image.height) / 2f) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/TwoRowSierraDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class TwoRowSierraDitheringFilter( override val value: Pair = 200f to false, ) : Transformation, Filter.TwoRowSierraDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.TwoRowSierra, threshold = value.first.toInt(), isGrayScale = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UchimuraFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class UchimuraFilter( override val value: Float = 1f ) : Transformation, Filter.Uchimura { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.uchimura( bitmap = input, exposure = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/UnsharpFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class UnsharpFilter( override val value: Float = 0.5f, ) : Transformation, Filter.Unsharp { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.unsharp( bitmap = input, intensity = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VHSFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.Trickle @FilterInject internal class VHSFilter( override val value: Pair = 2f to 3f, ) : Transformation, Filter.VHS { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.vhsGlitch( src = input, time = value.first, strength = value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VibranceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class VibranceFilter( override val value: Float = 3f, ) : Transformation, Filter.Vibrance { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.vibrance( bitmap = input, vibrance = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VignetteFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.PointF import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.blue import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.green import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.red import com.t8rin.imagetoolbox.core.data.image.utils.ColorUtils.toModel import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageVignetteFilter @FilterInject internal class VignetteFilter( override val value: Triple = Triple( first = 0.3f, second = 0.75f, third = Color.Black.toModel() ) ) : GPUFilterTransformation(), Filter.Vignette { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageVignetteFilter( PointF(0.5f, 0.5f), floatArrayOf(value.third.red, value.third.green, value.third.blue), value.first, value.second ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VintageFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class VintageFilter( override val value: Unit = Unit ) : Transformation, Filter.Vintage { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.VINTAGE).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VoronoiCrystallizeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.CrystallizeFilter import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class VoronoiCrystallizeFilter( override val value: VoronoiCrystallizeParams = VoronoiCrystallizeParams.Default ) : JhFilterTransformation(), Filter.VoronoiCrystallize { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = CrystallizeFilter().apply { edgeThickness = value.borderThickness edgeColor = value.color.colorInt scale = value.scale randomness = value.randomness gridType = value.shape turbulence = value.turbulence angle = Math.toRadians(value.angle.toDouble()).toFloat() stretch = value.stretch amount = value.amount } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/VortexPixelationFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.PixelationTool @FilterInject internal class VortexPixelationFilter( override val value: Float = 25f, ) : Transformation, Filter.VortexPixelation { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = PixelationTool.pixelate( input = input, layers = { vortex(value) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WarmFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.ColorMatrices import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class WarmFilter( override val value: Unit = Unit ) : Transformation, Filter.Warm { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMatrix3x3Filter(ColorMatrices.WARM).transform(input, size) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WaterEffectFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterParams import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject @FilterInject internal class WaterEffectFilter( override val value: WaterParams = WaterParams() ) : Transformation, Filter.WaterEffect { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Aire.waterEffect( bitmap = input, fractionSize = 0.2f * value.fractionSize, frequencyX = value.frequencyX, frequencyY = value.frequencyY, amplitudeX = value.amplitudeX, amplitudeY = value.amplitudeY ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeakPixelFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageWeakPixelInclusionFilter @FilterInject internal class WeakPixelFilter( override val value: Unit = Unit, ) : GPUFilterTransformation(), Filter.WeakPixel { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageWeakPixelInclusionFilter() } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WeaveFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.jhlabs.JhFilter import com.jhlabs.WeaveFilter import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.qto import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.JhFilterTransformation @FilterInject internal class WeaveFilter( override val value: Quad = 16f to 16f qto (6f to 6f) ) : JhFilterTransformation(), Filter.Weave { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): JhFilter = WeaveFilter().apply { xWidth = value.first yWidth = value.second xGap = value.third yGap = value.fourth } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/WhiteBalanceFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageWhiteBalanceFilter @FilterInject internal class WhiteBalanceFilter( override val value: Pair = 7000.0f to 100f, ) : GPUFilterTransformation(), Filter.WhiteBalance { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageWhiteBalanceFilter(value.first, value.second) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/YililomaDitheringFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.trickle.DitheringType import com.t8rin.trickle.Trickle @FilterInject internal class YililomaDitheringFilter( override val value: Boolean = false, ) : Transformation, Filter.YililomaDithering { override val cacheKey: String get() = value.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = Trickle.dithering( input = input, type = DitheringType.Yililoma, isGrayScale = value ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/model/ZoomBlurFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.model import android.graphics.PointF import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.ksp.annotations.FilterInject import com.t8rin.imagetoolbox.feature.filters.data.transformation.GPUFilterTransformation import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import jp.co.cyberagent.android.gpuimage.filter.GPUImageZoomBlurFilter @FilterInject internal class ZoomBlurFilter( override val value: Triple = Triple(0.5f, 0.5f, 5f), ) : GPUFilterTransformation(), Filter.ZoomBlur { override val cacheKey: String get() = value.hashCode().toString() override fun createFilter(): GPUImageFilter = GPUImageZoomBlurFilter(PointF(value.first, value.second), value.third) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/ColorMapTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.transformation import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.enums.ColorMapType import com.t8rin.opencv_tools.color_map.ColorMap import com.t8rin.opencv_tools.color_map.model.ColorMapType as NativeColorMapType internal abstract class ColorMapTransformation( val type: ColorMapType ) : Transformation { override val cacheKey: String get() = type.hashCode().toString() override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = ColorMap.apply( bitmap = input, map = NativeColorMapType.valueOf(type.name) ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/GPUFilterTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.transformation import android.graphics.Bitmap import coil3.size.Size import com.t8rin.imagetoolbox.core.data.utils.asCoil import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.filters.data.utils.flexible import jp.co.cyberagent.android.gpuimage.GPUImage import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter import coil3.transform.Transformation as CoilTransformation internal abstract class GPUFilterTransformation : CoilTransformation(), Transformation { /** * Create the [GPUImageFilter] to apply to this [Transformation] */ abstract fun createFilter(): GPUImageFilter override suspend fun transform( input: Bitmap, size: Size ): Bitmap = GPUImage(appContext).apply { setImage(input.flexible(size)) setFilter(createFilter()) }.bitmapWithFilterApplied override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = transform(input, size.asCoil()) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/transformation/JhFilterTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.transformation import android.graphics.Bitmap import coil3.size.Size import com.jhlabs.JhFilter import com.t8rin.imagetoolbox.core.data.utils.asCoil import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.feature.filters.data.utils.flexible import coil3.transform.Transformation as CoilTransformation internal abstract class JhFilterTransformation : CoilTransformation(), Transformation { abstract fun createFilter(): JhFilter open fun createFilter(image: Bitmap): JhFilter = createFilter() override suspend fun transform( input: Bitmap, size: Size ): Bitmap = input.flexible(size).let { createFilter(it).filter(it) } override suspend fun transform( input: Bitmap, size: IntegerSize ): Bitmap = transform(input, size.asCoil()) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/EnumMappings.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils import com.awxkee.aire.EdgeMode import com.awxkee.aire.PaletteTransferColorspace import com.awxkee.aire.TransferFunction import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PaletteTransferSpace import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.trickle.PopArtBlendMode fun BlurEdgeMode.toEdgeMode(): EdgeMode = when (this) { BlurEdgeMode.Clamp -> EdgeMode.CLAMP BlurEdgeMode.Reflect101 -> EdgeMode.REFLECT_101 BlurEdgeMode.Wrap -> EdgeMode.WRAP BlurEdgeMode.Reflect -> EdgeMode.REFLECT } fun TransferFunc.toFunc(): TransferFunction = when (this) { TransferFunc.SRGB -> TransferFunction.SRGB TransferFunc.REC709 -> TransferFunction.REC709 TransferFunc.GAMMA2P2 -> TransferFunction.GAMMA2P2 TransferFunc.GAMMA2P8 -> TransferFunction.GAMMA2P8 } fun PaletteTransferSpace.toSpace(): PaletteTransferColorspace = when (this) { PaletteTransferSpace.LALPHABETA -> PaletteTransferColorspace.LALPHABETA PaletteTransferSpace.LAB -> PaletteTransferColorspace.LAB PaletteTransferSpace.OKLAB -> PaletteTransferColorspace.OKLAB PaletteTransferSpace.LUV -> PaletteTransferColorspace.LUV } fun PopArtBlendingMode.toMode(): PopArtBlendMode = when (this) { PopArtBlendingMode.MULTIPLY -> PopArtBlendMode.MULTIPLY PopArtBlendingMode.COLOR_BURN -> PopArtBlendMode.COLOR_BURN PopArtBlendingMode.SOFT_LIGHT -> PopArtBlendMode.SOFT_LIGHT PopArtBlendingMode.HSL_COLOR -> PopArtBlendMode.HSL_COLOR PopArtBlendingMode.HSL_HUE -> PopArtBlendMode.HSL_HUE PopArtBlendingMode.DIFFERENCE -> PopArtBlendMode.DIFFERENCE } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/TransformationUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils import android.graphics.Bitmap import android.graphics.Matrix import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.scale import coil3.size.Size import coil3.size.pxOrElse import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.aspectRatio import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import java.lang.Integer.max internal fun Bitmap.flexible(size: Size): Bitmap = flexibleResize( image = this, max = max( size.height.pxOrElse { height }, size.width.pxOrElse { width } ) ) internal fun Bitmap.mirror( value: Float = 0.5f, side: MirrorSide = MirrorSide.LeftToRight ): Bitmap { val input = this if (value <= 0f || value >= 1f) return input val width = input.width val height = input.height return when (side) { MirrorSide.LeftToRight, MirrorSide.RightToLeft -> { val centerX = (width * value).toInt().coerceIn(1, width - 1) val leftWidth = centerX val rightWidth = width - centerX val halfWidth = minOf(leftWidth, rightWidth) val outputWidth = halfWidth * 2 createBitmap( width = outputWidth, height = height, config = input.safeConfig ).applyCanvas { if (side == MirrorSide.LeftToRight) { val leftPart = Bitmap.createBitmap(input, centerX - halfWidth, 0, halfWidth, height) val flipped = Bitmap.createBitmap(leftPart, 0, 0, halfWidth, height, Matrix().apply { preScale(-1f, 1f) }, true) drawBitmap(leftPart) drawBitmap(flipped, halfWidth.toFloat(), 0f) } else { val rightPart = Bitmap.createBitmap(input, centerX, 0, halfWidth, height) val flipped = Bitmap.createBitmap(rightPart, 0, 0, halfWidth, height, Matrix().apply { preScale(-1f, 1f) }, true) drawBitmap(flipped) drawBitmap(rightPart, halfWidth.toFloat(), 0f) } } } MirrorSide.TopToBottom, MirrorSide.BottomToTop -> { val centerY = (height * value).toInt().coerceIn(1, height - 1) val topHeight = centerY val bottomHeight = height - centerY val halfHeight = minOf(topHeight, bottomHeight) val outputHeight = halfHeight * 2 createBitmap( width = width, height = outputHeight, config = input.safeConfig ).applyCanvas { if (side == MirrorSide.TopToBottom) { val topPart = Bitmap.createBitmap(input, 0, centerY - halfHeight, width, halfHeight) val flipped = Bitmap.createBitmap(topPart, 0, 0, width, halfHeight, Matrix().apply { preScale(1f, -1f) }, true) drawBitmap(topPart) drawBitmap(flipped, 0f, halfHeight.toFloat()) } else { val bottomPart = Bitmap.createBitmap(input, 0, centerY, width, halfHeight) val flipped = Bitmap.createBitmap(bottomPart, 0, 0, width, halfHeight, Matrix().apply { preScale(1f, -1f) }, true) drawBitmap(flipped) drawBitmap(bottomPart, 0f, halfHeight.toFloat()) } } } } } private fun flexibleResize( image: Bitmap, max: Int ): Bitmap { return runCatching { if (image.height >= image.width) { val aspectRatio = image.aspectRatio val targetWidth = (max * aspectRatio).toInt() image.scale(targetWidth, max) } else { val aspectRatio = 1f / image.aspectRatio val targetHeight = (max * aspectRatio).toInt() image.scale(max, targetHeight) } }.getOrNull() ?: image } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/convolution/AireConvolution.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnusedReceiverParameter") package com.t8rin.imagetoolbox.feature.filters.data.utils.convolution import android.graphics.Bitmap import com.awxkee.aire.Aire import com.awxkee.aire.EdgeMode import com.awxkee.aire.KernelShape import com.awxkee.aire.MorphOpMode import com.awxkee.aire.Scalar internal inline fun Aire.convolve2D( input: Bitmap, kernelProducer: (Int) -> FloatArray, size: Int ): Bitmap = Aire.convolve2D( bitmap = input, kernel = kernelProducer(size), kernelShape = KernelShape(size, size), edgeMode = EdgeMode.REFLECT_101, scalar = Scalar.ZEROS, mode = MorphOpMode.RGBA ) ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/GlitchTool.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.glitch import com.t8rin.imagetoolbox.feature.filters.data.utils.glitch.tools.Anaglyph import com.t8rin.imagetoolbox.feature.filters.data.utils.glitch.tools.JpegGlitch internal object GlitchTool : Anaglyph, JpegGlitch ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/Anaglyph.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.glitch.tools import android.graphics.Bitmap import android.graphics.BitmapShader import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter import android.graphics.Matrix import android.graphics.Paint import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Shader import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import kotlinx.coroutines.coroutineScope internal interface Anaglyph { suspend fun anaglyph( image: Bitmap, percentage: Int ): Bitmap = coroutineScope { val anaglyphPaint = Paint() val anaglyphShader = BitmapShader(image, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT) anaglyphPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.ADD) anaglyphPaint.shader = anaglyphShader val w = image.width val h = image.height val transX = (percentage) val transY = 0 val colorMatrix = ColorMatrix() createBitmap( width = w, height = h ).applyCanvas { drawColor(0, PorterDuff.Mode.CLEAR) //left val matrix = Matrix() matrix.setTranslate((-transX).toFloat(), (transY).toFloat()) anaglyphShader.setLocalMatrix(matrix) colorMatrix.set(leftArray) anaglyphPaint.colorFilter = ColorMatrixColorFilter(colorMatrix) drawRect(0.0f, 0.0f, w.toFloat(), h.toFloat(), anaglyphPaint) //right val matrix2 = Matrix() matrix2.setTranslate((transX).toFloat(), transY.toFloat()) anaglyphShader.setLocalMatrix(matrix2) colorMatrix.set(rightArray) anaglyphPaint.colorFilter = ColorMatrixColorFilter(colorMatrix) drawRect(0.0f, 0.0f, w.toFloat(), h.toFloat(), anaglyphPaint) drawBitmap(image, 0f, 0f, anaglyphPaint) } } companion object { private val leftArray = floatArrayOf( 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ) private val rightArray = floatArrayOf( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/glitch/tools/JpegGlitch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.glitch.tools import android.graphics.Bitmap import android.graphics.BitmapFactory import kotlinx.coroutines.coroutineScope import java.io.ByteArrayOutputStream import kotlin.math.floor internal interface JpegGlitch { suspend fun jpegGlitch( input: Bitmap, amount: Int = 20, seed: Int = 15, iterations: Int = 9 ): Bitmap = coroutineScope { val imageByteArray = ByteArrayOutputStream().use { input.compress(Bitmap.CompressFormat.JPEG, 100, it) it.toByteArray() } val jpgHeaderLength = getJpegHeaderSize(imageByteArray) repeat( times = iterations ) { glitchJpegBytes( pos = it, imageByteArray = imageByteArray, jpgHeaderLength = jpgHeaderLength, amount = amount, seed = seed, iterations = iterations ) } BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.size) } companion object { private fun glitchJpegBytes( pos: Int, imageByteArray: ByteArray, jpgHeaderLength: Int, amount: Int = 20, seed: Int = 15, iterations: Int = 9 ) { val maxIndex = imageByteArray.size - jpgHeaderLength - 4f val pxMin = maxIndex / iterations * pos val pxMax = maxIndex / iterations * (pos + 1) val delta = pxMax - pxMin var pxIndex = pxMin + delta * seed / 100f if (pxIndex > maxIndex) { pxIndex = maxIndex } val index = floor((jpgHeaderLength + pxIndex).toDouble()).toInt() imageByteArray[index] = floor((amount / 100f * 256f).toDouble()).toInt().toByte() } private fun getJpegHeaderSize(imageByteArray: ByteArray): Int { var result = 417 var i = 0 val len = imageByteArray.size while (i < len) { if (imageByteArray[i].toInt() == 255 && imageByteArray[i + 1].toInt() == 218) { result = i + 2 break } i++ } return result } } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/gpu/GPUImageHighlightShadowWideRangeFilter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.gpu import android.opengl.GLES20 import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter internal class GPUImageHighlightShadowWideRangeFilter @JvmOverloads constructor( private var shadows: Float = 1.0f, private var highlights: Float = 1.0f ) : GPUImageFilter(NO_FILTER_VERTEX_SHADER, HIGHLIGHT_SHADOW_FRAGMENT_SHADER) { private var shadowsLocation = 0 private var highlightsLocation = 0 override fun onInit() { super.onInit() highlightsLocation = GLES20.glGetUniformLocation(program, "highlights") shadowsLocation = GLES20.glGetUniformLocation(program, "shadows") } override fun onInitialized() { super.onInitialized() setHighlights(highlights) setShadows(shadows) } fun setHighlights(highlights: Float) { this.highlights = highlights setFloat(highlightsLocation, this.highlights) } fun setShadows(shadows: Float) { this.shadows = shadows setFloat(shadowsLocation, this.shadows) } companion object { const val HIGHLIGHT_SHADOW_FRAGMENT_SHADER: String = "" + " uniform sampler2D inputImageTexture;\n" + " varying highp vec2 textureCoordinate;\n" + " \n" + " uniform lowp float shadows;\n" + " uniform lowp float highlights;\n" + " \n" + " const mediump vec3 luminanceWeighting = vec3(0.3, 0.3, 0.3);\n" + " \n" + " void main()\n" + " {\n" + " lowp vec4 source = texture2D(inputImageTexture, textureCoordinate);\n" + " mediump float luminance = dot(source.rgb, luminanceWeighting);\n" + " \n" + " mediump float shadow = clamp((pow(luminance, 1.0/shadows) + (-0.76)*pow(luminance, 2.0/shadows)) - luminance, 0.0, 1.0);\n" + " mediump float highlight = clamp((1.0 - (pow(1.0-luminance, 1.0/(2.0-highlights)) + (-0.8)*pow(1.0-luminance, 2.0/(2.0-highlights)))) - luminance, -1.0, 0.0);\n" + " lowp vec3 result = vec3(0.0, 0.0, 0.0) + ((luminance + shadow + highlight) - 0.0) * ((source.rgb - vec3(0.0, 0.0, 0.0))/(luminance - 0.0));\n" + " \n" + " mediump float contrastedLuminance = ((luminance - 0.5) * 1.5) + 0.5;\n" + " mediump float whiteInterp = contrastedLuminance*contrastedLuminance*contrastedLuminance;\n" + " mediump float whiteTarget = clamp(highlights, 1.0, 2.0) - 1.0;\n" + " result = mix(result, vec3(1.0), whiteInterp*whiteTarget);\n" + " \n" + " mediump float invContrastedLuminance = 1.0 - contrastedLuminance;\n" + " mediump float blackInterp = invContrastedLuminance*invContrastedLuminance*invContrastedLuminance;\n" + " mediump float blackTarget = 1.0 - clamp(shadows, 0.0, 1.0);\n" + " result = mix(result, vec3(0.0), blackInterp*blackTarget);\n" + " \n" + " gl_FragColor = vec4(result, source.a);\n" + " }" } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/image/ImageLoader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.image import coil3.imageLoader import coil3.request.ImageRequest import coil3.toBitmap import com.t8rin.imagetoolbox.core.utils.appContext internal suspend fun Any.loadBitmap(size: Int? = null) = appContext.imageLoader.execute( ImageRequest.Builder(appContext) .data(this) .apply { if (size != null) size(size) } .build() ).image?.toBitmap() ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/PixelationTool.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("MemberVisibilityCanBePrivate") package com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.get import androidx.core.graphics.withClip import androidx.core.graphics.withSave import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.tool.PixelationCommands import com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.tool.PixelationLayer import kotlin.math.sqrt internal object PixelationTool { fun pixelate( input: Bitmap, inBounds: Rect? = null, outBounds: Rect? = null, layers: PixelationCommands.() -> Array, ): Bitmap = pixelate( input = input, inBounds = inBounds, outBounds = outBounds, layers = layers(PixelationCommands) ) fun pixelate( input: Bitmap, inBounds: Rect? = null, outBounds: Rect? = null, vararg layers: PixelationLayer, ): Bitmap { val bounds = outBounds ?: Rect(0, 0, input.width, input.height) return createBitmap( width = bounds.width(), height = bounds.height() ).applyCanvas { render( input = input, inBounds = inBounds, outBounds = bounds, layers = layers ) } } private fun Canvas.render( input: Bitmap, inBounds: Rect?, outBounds: Rect, vararg layers: PixelationLayer, ) { val inWidth = inBounds?.width() ?: input.width val inHeight = inBounds?.height() ?: input.height val inX = inBounds?.left ?: 0 val inY = inBounds?.top ?: 0 val scaleX = outBounds.width().toFloat() / inWidth val scaleY = outBounds.height().toFloat() / inHeight withClip(outBounds) { translate( outBounds.left.toFloat(), outBounds.top.toFloat() ) scale(scaleX, scaleY) for (layer in layers) { // option defaults val size: Float = layer.size ?: layer.resolution val cols = (inWidth / layer.resolution + 1).toInt() val rows = (inHeight / layer.resolution + 1).toInt() val halfSize = size / 2f val diamondSize = size / SQRT2 val halfDiamondSize = diamondSize / 2f for (row in 0..rows) { val y: Float = (row - 0.5f) * layer.resolution + layer.offsetY // normalize y so shapes around edges get color val pixelY = inY + y.coerceAtMost((inHeight - 1).toFloat()).coerceAtLeast(0f) for (col in 0..cols) { val x: Float = (col - 0.5f) * layer.resolution + layer.offsetX // normalize y so shapes around edges get color val pixelX = inX + x.coerceAtMost((inWidth - 1).toFloat()).coerceAtLeast(0f) paint.color = getPixelColor(input, pixelX.toInt(), pixelY.toInt(), layer) when (layer.shape) { PixelationLayer.Shape.Circle -> drawCircle(x, y, halfSize, paint) PixelationLayer.Shape.Diamond -> { withSave { translate(x, y) rotate(45f) drawRect( -halfDiamondSize, -halfDiamondSize, halfDiamondSize, halfDiamondSize, paint ) } } PixelationLayer.Shape.Square -> drawRect( x - halfSize, y - halfSize, x + halfSize, y + halfSize, paint ) } } // col } // row } } } /** * Returns the color of the cluster. If options.enableDominantColor is true, return the * dominant color around the provided point. Return the color of the point itself otherwise. * The dominant color algorithm is based on simple counting search, so use with caution. * * @param pixels the bitmap * @param pixelX the x coordinate of the reference point * @param pixelY the y coordinate of the reference point * @param opts additional options * @return the color of the cluster */ private fun getPixelColor( pixels: Bitmap, pixelX: Int, pixelY: Int, opts: PixelationLayer ): Int { var pixel = pixels[pixelX, pixelY] if (opts.enableDominantColor) { val colorCounter: MutableMap = HashMap(100) for (x in 0.coerceAtLeast((pixelX - opts.resolution).toInt()) until pixels.width.coerceAtMost( (pixelX + opts.resolution).toInt() )) { for (y in 0.coerceAtLeast((pixelY - opts.resolution).toInt()) until pixels.height.coerceAtMost( (pixelY + opts.resolution).toInt() )) { val currentRGB = pixels[x, y] val count = if (colorCounter.containsKey(currentRGB)) colorCounter[currentRGB]!! else 0 colorCounter[currentRGB] = count + 1 } } var max: Int? = null var dominantRGB: Int? = null for ((key, value) in colorCounter) { if (max == null || value > max) { max = value dominantRGB = key } } pixel = dominantRGB!! } val red = Color.red(pixel) val green = Color.green(pixel) val blue = Color.blue(pixel) val alpha = (opts.alpha * Color.alpha(pixel)).toInt() return Color.argb(alpha, red, green, blue) } private val SQRT2 = sqrt(2.0).toFloat() private val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG or Paint.FILTER_BITMAP_FLAG) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationCommands.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.tool internal object PixelationCommands { fun enhancedDiamond(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value) .setOffset(value / 4) .setAlpha(0.5f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value) .setOffset(value) .setAlpha(0.5f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value / 3) .setSize(value / 6) .setOffset(value / 12) .build() ) fun circle(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 3f) .setOffset(value / 2) .build() ) fun diamond(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value) .setSize(value + 1) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value) .setOffset(value / 2) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .setAlpha(0.6f) .build() ) fun enhancedCircle(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setOffset(value / 2) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 1.2f) .setOffset(value / 2.5f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 1.8f) .setOffset(value / 3) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 2.7f) .setOffset(value / 4) .build() ) fun enhancedSquare(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value / 4) .setSize(value / 6) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value / 4) .setSize(value / 6) .setOffset(value / 8) .build() ) fun square(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value - 4f) .setSize(value) .build() ) fun stroke(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 5) .setOffset(value / 4) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 4) .setOffset(value / 2) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 3) .setOffset(value / 1.3f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value / 4) .setOffset(0f) .build() ) fun simpleWeave(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .setSize(value / 2) .setOffset(value / 2) .setAlpha(0.5f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value) .setSize(value / 3) .setOffset(value / 4) .setAlpha(0.4f) .build() ) fun staggered(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .setOffset(value / 2) .setSize(value * 0.85f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value * 2) .setAlpha(0.35f) .build() ) fun cross(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value) .setSize(value * 0.7f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value / 2) .setSize(value / 3) .setAlpha(0.45f) .build() ) fun microMacro(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value * 2) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value / 2) .setSize(value / 3) .build() ) fun orbital(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value * 0.9f) .setOffset(value / 3) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value * 0.6f) .setOffset(value / 1.5f) .build() ) fun vortex(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value * 0.85f) .setOffset(value / 3) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value * 1.4f) .setSize(value * 0.6f) .setOffset(value / 1.1f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value / 2) .setSize(value / 3) .build() ) fun pulseGrid(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value * 1.5f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value * 0.7f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value / 1.8f) .setSize(value / 2.8f) .setOffset(value / 2) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value / 2.5f) .setAlpha(0.35f) .build() ) fun nucleus(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value * 0.6f) .setOffset(value / 2) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value / 2) .setSize(value / 3) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value * 2) .setAlpha(0.25f) .build() ) fun radialWeave(value: Float): Array = arrayOf( PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value * 0.9f) .setOffset(value / 3) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value) .setSize(value * 0.6f) .setOffset(value / 1.5f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Circle) .setResolution(value / 2) .setSize(value / 3) .setOffset(value / 4) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Diamond) .setResolution(value / 3) .setSize(value / 5) .setOffset(value / 6) .setAlpha(0.35f) .build(), PixelationLayer.Builder(PixelationLayer.Shape.Square) .setResolution(value / 4) .setAlpha(0.25f) .build() ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/pixelation/tool/PixelationLayer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.feature.filters.data.utils.pixelation.tool @ConsistentCopyVisibility internal data class PixelationLayer private constructor( val shape: Shape, val enableDominantColor: Boolean = false, val resolution: Float = 16f, val size: Float? = null, val alpha: Float = 1f, val offsetX: Float = 0f, val offsetY: Float = 0f ) { class Builder(shape: Shape) { private var layer: PixelationLayer = PixelationLayer(shape) private inline fun mutate( action: PixelationLayer.() -> PixelationLayer ): Builder = apply { layer = layer.action() } fun setResolution(resolution: Float): Builder = mutate { copy(resolution = resolution) } fun setSize(size: Float): Builder = mutate { copy( size = size ) } fun setOffset(size: Float): Builder = mutate { copy( offsetX = size, offsetY = size ) } fun setAlpha(alpha: Float): Builder = mutate { copy( alpha = alpha ) } fun setEnableDominantColors(enable: Boolean): Builder = mutate { copy( enableDominantColor = enable ) } fun build(): PixelationLayer = layer } enum class Shape { Circle, Diamond, Square } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/FilterSerializationUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.serialization import android.content.Context import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import kotlin.reflect.full.primaryConstructor internal fun List>.toDatastoreString( includeValue: Boolean = false, context: Context ): String = joinToString(separator = FILTERS_SEPARATOR) { filter -> filter::class.qualifiedName!!.replace( context.applicationInfo.packageName, PACKAGE_ALIAS ) + if (includeValue) { VALUE_SEPARATOR + filter.value.toPair() ?.let { it.first + VALUE_SEPARATOR + it.second } } else "" }.trim() @Suppress("UNCHECKED_CAST") internal fun String.toFiltersList( includeValue: Boolean, context: Context ): List> = split(FILTERS_SEPARATOR).mapNotNull { line -> if (line.trim().isEmpty()) return@mapNotNull null val (name, value) = if (includeValue) { runCatching { val splitData = line.split(VALUE_SEPARATOR) val className = splitData[1] val valueString = splitData[2] splitData[0].trim() to (className to valueString).fromPair() }.getOrElse { line.trim() to Unit } } else line.trim() to Unit runCatching { val filterClass = Class.forName( name.replace( PACKAGE_ALIAS, context.applicationInfo.packageName ) ) as Class> filterClass.kotlin.primaryConstructor?.run { try { if (includeValue && value != null) { callBy(mapOf(parameters[0] to value)) } else callBy(emptyMap()) } catch (_: Throwable) { callBy(emptyMap()) } } }.getOrNull() } internal fun String.toTemplateFiltersList(context: Context): List = split(TEMPLATES_SEPARATOR).map { val splitData = it.split(TEMPLATE_CONTENT_SEPARATOR) val name = splitData[0] val filters = splitData[1].toFiltersList(true, context) TemplateFilter( name = name, filters = filters ) } internal fun List.toDatastoreString(context: Context): String = joinToString(separator = TEMPLATES_SEPARATOR) { it.name + TEMPLATE_CONTENT_SEPARATOR + it.filters.toDatastoreString(true, context) } private const val FILTERS_SEPARATOR = "," private const val TEMPLATES_SEPARATOR = "\\" private const val TEMPLATE_CONTENT_SEPARATOR = "+" private const val VALUE_SEPARATOR = ":" internal const val PACKAGE_ALIAS = "^^" ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/data/utils/serialization/Mappings.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.data.utils.serialization import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.toColorModel import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.component6 import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.component7 import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.component8 import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.component9 import com.t8rin.imagetoolbox.core.domain.utils.Quad import com.t8rin.imagetoolbox.core.domain.utils.simpleName import com.t8rin.imagetoolbox.core.filters.domain.model.FilterValueWrapper import com.t8rin.imagetoolbox.core.filters.domain.model.enums.BlurEdgeMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide import com.t8rin.imagetoolbox.core.filters.domain.model.enums.MirrorSide import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PolarCoordinatesType import com.t8rin.imagetoolbox.core.filters.domain.model.enums.PopArtBlendingMode import com.t8rin.imagetoolbox.core.filters.domain.model.enums.TransferFunc import com.t8rin.imagetoolbox.core.filters.domain.model.params.ArcParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.AsciiParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.BilaterialBlurParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.BloomParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ChannelMixParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ClaheParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.CropOrPerspectiveParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.EnhancedZoomBlurParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.GlitchParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.KaleidoscopeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearGaussianParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.LinearTiltShiftParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.PinchParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.RadialTiltShiftParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.RubberStampParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SideFadeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SmearParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.SparkleParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.ToneCurvesParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.VoronoiCrystallizeParams import com.t8rin.imagetoolbox.core.filters.domain.model.params.WaterParams import com.t8rin.imagetoolbox.core.settings.domain.model.DomainFontFamily import com.t8rin.imagetoolbox.core.settings.presentation.model.asDomain import com.t8rin.imagetoolbox.core.settings.presentation.model.asFontType import kotlin.io.encoding.Base64 internal fun Any.toPair(): Pair? { return when (this) { is Int -> Int::class.simpleName() to toString() is Float -> Float::class.simpleName() to toString() is Unit -> Unit::class.simpleName() to "Unit" is PolarCoordinatesType -> PolarCoordinatesType::class.simpleName() to name is FloatArray -> FloatArray::class.simpleName() to joinToString(separator = PROPERTIES_SEPARATOR) { it.toString() } is FilterValueWrapper<*> -> { when (wrapped) { is ColorModel -> "${FilterValueWrapper::class.simpleName()}{${ColorModel::class.simpleName}}" to (wrapped as ColorModel).colorInt .toString() else -> null } } is Pair<*, *> -> { val firstPart = first!!.toPart() val secondPart = second!!.toPart() "${Pair::class.simpleName}{${first!!::class.simpleName}$PROPERTIES_SEPARATOR${second!!::class.simpleName}}" to listOf( firstPart, secondPart ).joinToString(PROPERTIES_SEPARATOR) } is Triple<*, *, *> -> { val firstPart = first!!.toPart() val secondPart = second!!.toPart() val thirdPart = third!!.toPart() "${Triple::class.simpleName}{${first!!::class.simpleName}$PROPERTIES_SEPARATOR${second!!::class.simpleName}$PROPERTIES_SEPARATOR${third!!::class.simpleName}}" to listOf( firstPart, secondPart, thirdPart ).joinToString(PROPERTIES_SEPARATOR) } is Quad<*, *, *, *> -> { val firstPart = first!!.toPart() val secondPart = second!!.toPart() val thirdPart = third!!.toPart() val fourthPart = fourth!!.toPart() "${Quad::class.simpleName}{${first!!::class.simpleName}$PROPERTIES_SEPARATOR${second!!::class.simpleName}$PROPERTIES_SEPARATOR${third!!::class.simpleName}$PROPERTIES_SEPARATOR${fourth!!::class.simpleName}}" to listOf( firstPart, secondPart, thirdPart, fourthPart ).joinToString(PROPERTIES_SEPARATOR) } is GlitchParams -> { GlitchParams::class.simpleName() to listOf( channelsShiftX, channelsShiftY, corruptionSize, corruptionCount, corruptionShiftX, corruptionShiftY ).joinToString(PROPERTIES_SEPARATOR) } is LinearTiltShiftParams -> { LinearTiltShiftParams::class.simpleName() to listOf( blurRadius, sigma, anchorX, anchorY, holeRadius, angle ).joinToString(PROPERTIES_SEPARATOR) } is RadialTiltShiftParams -> { RadialTiltShiftParams::class.simpleName() to listOf( blurRadius, sigma, anchorX, anchorY, holeRadius ).joinToString(PROPERTIES_SEPARATOR) } is EnhancedZoomBlurParams -> { EnhancedZoomBlurParams::class.simpleName() to listOf( radius, sigma, centerX, centerY, strength, angle ).joinToString(PROPERTIES_SEPARATOR) } is SideFadeParams.Relative -> { SideFadeParams::class.simpleName() to listOf( side.name, scale ).joinToString(PROPERTIES_SEPARATOR) } is WaterParams -> { WaterParams::class.simpleName() to listOf( fractionSize, frequencyX, frequencyY, amplitudeX, amplitudeY ).joinToString(PROPERTIES_SEPARATOR) } is ClaheParams -> { ClaheParams::class.simpleName() to listOf( threshold, gridSizeHorizontal, gridSizeVertical, binsCount ).joinToString(PROPERTIES_SEPARATOR) } is LinearGaussianParams -> { LinearGaussianParams::class.simpleName() to listOf( kernelSize, sigma, edgeMode.name, transferFunction.name ).joinToString(PROPERTIES_SEPARATOR) } is ToneCurvesParams -> { ToneCurvesParams::class.simpleName() to controlPoints.joinToString(PROPERTIES_SEPARATOR) { it.joinToString(ADDITIONAL_PROPERTIES_SEPARATOR) } } is BilaterialBlurParams -> { BilaterialBlurParams::class.simpleName() to listOf( radius, spatialSigma, rangeSigma, edgeMode.name ).joinToString(PROPERTIES_SEPARATOR) } is KaleidoscopeParams -> { KaleidoscopeParams::class.simpleName() to listOf( angle, angle2, centreX, centreY, sides, radius ).joinToString(PROPERTIES_SEPARATOR) } is ChannelMixParams -> { ChannelMixParams::class.simpleName() to listOf( blueGreen, redBlue, greenRed, intoR, intoG, intoB ).joinToString(PROPERTIES_SEPARATOR) } is VoronoiCrystallizeParams -> { VoronoiCrystallizeParams::class.simpleName!! to listOf( borderThickness, scale, randomness, shape, turbulence, angle, stretch, amount, color.colorInt ).joinToString(PROPERTIES_SEPARATOR) } is PinchParams -> { PinchParams::class.simpleName!! to listOf( angle, centreX, centreY, radius, amount ).joinToString(PROPERTIES_SEPARATOR) } is RubberStampParams -> { RubberStampParams::class.simpleName!! to listOf( threshold, softness, radius, firstColor.colorInt, secondColor.colorInt, ).joinToString(PROPERTIES_SEPARATOR) } is SmearParams -> { SmearParams::class.simpleName!! to listOf( angle, density, mix, distance, shape, ).joinToString(PROPERTIES_SEPARATOR) } is ArcParams -> { ArcParams::class.simpleName!! to listOf( radius, height, angle, spreadAngle, centreX, centreY ).joinToString(PROPERTIES_SEPARATOR) } is SparkleParams -> { SparkleParams::class.simpleName!! to listOf( amount, rays, radius, randomness, centreX, centreY, color.colorInt ).joinToString(PROPERTIES_SEPARATOR) } is AsciiParams -> { val font = font?.asDomain()?.takeIf { it !is DomainFontFamily.Custom } ?: DomainFontFamily.System AsciiParams::class.simpleName!! to listOf( Base64.encode(gradient.toByteArray(Charsets.UTF_8)), fontSize, backgroundColor.colorInt, isGrayscale, font.asString() ).joinToString(PROPERTIES_SEPARATOR) } is BloomParams -> { BloomParams::class.simpleName!! to listOf( threshold, intensity, radius, softKnee, exposure, gamma ).joinToString(PROPERTIES_SEPARATOR) } is CropOrPerspectiveParams -> { CropOrPerspectiveParams::class.simpleName!! to listOf( topLeft.join(), topRight.join(), bottomLeft.join(), bottomRight.join(), isAbsolute ).joinToString(PROPERTIES_SEPARATOR) } is IntegerSize -> { IntegerSize::class.simpleName!! to listOf( width, height ).joinToString(PROPERTIES_SEPARATOR) } else -> null } } internal fun Pair.fromPair(): Any? { val name = first.trim() val value = second.trim() return when { name == Int::class.simpleName -> value.toInt() name == Float::class.simpleName -> value.toFloat() name == Boolean::class.simpleName -> value.toBoolean() name == Unit::class.simpleName -> Unit name == PolarCoordinatesType::class.simpleName -> PolarCoordinatesType.valueOf(value) name == FloatArray::class.simpleName -> value.split(PROPERTIES_SEPARATOR) .map { it.toFloat() } .toFloatArray() "${FilterValueWrapper::class.simpleName}{" in name -> { when (name.getTypeFromBraces()) { ColorModel::class.simpleName -> FilterValueWrapper(ColorModel(value.toInt())) else -> null } } "${Pair::class.simpleName}{" in name -> { val (firstType, secondType) = name.getTypeFromBraces().split(PROPERTIES_SEPARATOR) val (firstPart, secondPart) = value.split(PROPERTIES_SEPARATOR) firstPart.fromPart(firstType) to secondPart.fromPart(secondType) } "${Triple::class.simpleName}{" in name -> { val (firstType, secondType, thirdType) = name.getTypeFromBraces() .split(PROPERTIES_SEPARATOR) val (firstPart, secondPart, thirdPart) = value.split(PROPERTIES_SEPARATOR) Triple( firstPart.fromPart(firstType), secondPart.fromPart(secondType), thirdPart.fromPart(thirdType) ) } "${Quad::class.simpleName}{" in name -> { val (firstType, secondType, thirdType, fourthType) = name.getTypeFromBraces() .split(PROPERTIES_SEPARATOR) val (firstPart, secondPart, thirdPart, fourthPart) = value.split(PROPERTIES_SEPARATOR) Quad( firstPart.fromPart(firstType), secondPart.fromPart(secondType), thirdPart.fromPart(thirdType), fourthPart.fromPart(fourthType) ) } name == GlitchParams::class.simpleName -> { val ( channelsShiftX, channelsShiftY, corruptionSize, corruptionCount, corruptionShiftX, corruptionShiftY, ) = value.split(PROPERTIES_SEPARATOR) GlitchParams( channelsShiftX = channelsShiftX.toFloat(), channelsShiftY = channelsShiftY.toFloat(), corruptionSize = corruptionSize.toFloat(), corruptionCount = corruptionCount.toInt(), corruptionShiftX = corruptionShiftX.toFloat(), corruptionShiftY = corruptionShiftY.toFloat() ) } name == LinearTiltShiftParams::class.simpleName -> { val (blurRadius, sigma, anchorX, anchorY, holeRadius, angle) = value.split( PROPERTIES_SEPARATOR ) LinearTiltShiftParams( blurRadius = blurRadius.toFloat(), sigma = sigma.toFloat(), anchorX = anchorX.toFloat(), anchorY = anchorY.toFloat(), holeRadius = holeRadius.toFloat(), angle = angle.toFloat() ) } name == RadialTiltShiftParams::class.simpleName -> { val (blurRadius, sigma, anchorX, anchorY, holeRadius) = value.split( PROPERTIES_SEPARATOR ) RadialTiltShiftParams( blurRadius = blurRadius.toFloat(), sigma = sigma.toFloat(), anchorX = anchorX.toFloat(), anchorY = anchorY.toFloat(), holeRadius = holeRadius.toFloat() ) } name == EnhancedZoomBlurParams::class.simpleName -> { val (radius, sigma, centerX, centerY, strength, angle) = value.split( PROPERTIES_SEPARATOR ) EnhancedZoomBlurParams( radius = radius.toInt(), sigma = sigma.toFloat(), centerX = centerX.toFloat(), centerY = centerY.toFloat(), strength = strength.toFloat(), angle = angle.toFloat() ) } name == SideFadeParams::class.simpleName -> { val (sideName, scale) = value.split(PROPERTIES_SEPARATOR) SideFadeParams.Relative( side = FadeSide.valueOf(sideName), scale = scale.toFloat() ) } name == WaterParams::class.simpleName -> { val (fractionSize, frequencyX, frequencyY, amplitudeX, amplitudeY) = value.split( PROPERTIES_SEPARATOR ) WaterParams( fractionSize = fractionSize.toFloat(), frequencyX = frequencyX.toFloat(), frequencyY = frequencyY.toFloat(), amplitudeX = amplitudeX.toFloat(), amplitudeY = amplitudeY.toFloat() ) } name == ClaheParams::class.simpleName -> { val (threshold, gridSizeHorizontal, gridSizeVertical, binsCount) = value.split( PROPERTIES_SEPARATOR ) ClaheParams( threshold = threshold.toFloat(), gridSizeHorizontal = gridSizeHorizontal.toInt(), gridSizeVertical = gridSizeVertical.toInt(), binsCount = binsCount.toInt() ) } name == LinearGaussianParams::class.simpleName -> { val (kernelSize, sigma, edgeModeName, transferFunctionName) = value.split( PROPERTIES_SEPARATOR ) LinearGaussianParams( kernelSize = kernelSize.toInt(), sigma = sigma.toFloat(), edgeMode = BlurEdgeMode.valueOf(edgeModeName), transferFunction = TransferFunc.valueOf(transferFunctionName) ) } name == ToneCurvesParams::class.simpleName -> { val controlPoints = value.split(PROPERTIES_SEPARATOR).map { valueString -> valueString.split(ADDITIONAL_PROPERTIES_SEPARATOR).map { it.toFloatOrNull() ?: 0f } } ToneCurvesParams( controlPoints = controlPoints ) } name == BilaterialBlurParams::class.simpleName -> { val (radius, spatialSigma, rangeSigma, edgeMode) = value.split( PROPERTIES_SEPARATOR ) BilaterialBlurParams( radius = radius.toInt(), spatialSigma = spatialSigma.toFloat(), rangeSigma = rangeSigma.toFloat(), edgeMode = BlurEdgeMode.valueOf(edgeMode) ) } name == KaleidoscopeParams::class.simpleName -> { val (angle, angle2, centreX, centreY, sides, radius) = value.split( PROPERTIES_SEPARATOR ) KaleidoscopeParams( angle = angle.toFloat(), angle2 = angle2.toFloat(), centreX = centreX.toFloat(), centreY = centreY.toFloat(), sides = sides.toInt(), radius = radius.toFloat() ) } name == ChannelMixParams::class.simpleName -> { val (blueGreen, redBlue, greenRed, intoR, intoG, intoB) = value.split( PROPERTIES_SEPARATOR ).map { it.toInt() } ChannelMixParams( blueGreen = blueGreen, redBlue = redBlue, greenRed = greenRed, intoR = intoR, intoG = intoG, intoB = intoB ) } name == VoronoiCrystallizeParams::class.simpleName -> { val (borderThickness, scale, randomness, shape, turbulence, angle, stretch, amount, color) = value.split( PROPERTIES_SEPARATOR ) VoronoiCrystallizeParams( borderThickness = borderThickness.toFloat(), scale = scale.toFloat(), randomness = randomness.toFloat(), shape = shape.toInt(), turbulence = turbulence.toFloat(), angle = angle.toFloat(), stretch = stretch.toFloat(), amount = amount.toFloat(), color = color.toInt().toColorModel() ) } name == PinchParams::class.simpleName -> { val (angle, centreX, centreY, radius, amount) = value.split( PROPERTIES_SEPARATOR ).map { it.toFloat() } PinchParams( angle = angle, centreX = centreX, centreY = centreY, radius = radius, amount = amount ) } name == RubberStampParams::class.simpleName -> { val (threshold, softness, radius, firstColor, secondColor) = value.split( PROPERTIES_SEPARATOR ).map { it.toFloat() } RubberStampParams( threshold = threshold, softness = softness, radius = radius, firstColor = firstColor.toInt().toColorModel(), secondColor = secondColor.toInt().toColorModel() ) } name == SmearParams::class.simpleName -> { val (angle, density, mix, distance, shape) = value.split( PROPERTIES_SEPARATOR ).map { it.toFloat() } SmearParams( angle = angle, density = density, mix = mix, distance = distance.toInt(), shape = shape.toInt() ) } name == ArcParams::class.simpleName -> { val (radius, height, angle, spreadAngle, centreX, centreY) = value.split( PROPERTIES_SEPARATOR ).map { it.toFloat() } ArcParams( radius = radius, height = height, angle = angle, spreadAngle = spreadAngle, centreX = centreX, centreY = centreY ) } name == SparkleParams::class.simpleName -> { val (amount, rays, radius, randomness, centreX, centreY, color) = value.split( PROPERTIES_SEPARATOR ) SparkleParams( amount = amount.toInt(), rays = rays.toInt(), radius = radius.toFloat(), randomness = randomness.toInt(), centreX = centreX.toFloat(), centreY = centreY.toFloat(), color = color.toInt().toColorModel() ) } name == AsciiParams::class.simpleName -> { val (gradient, fontSize, backgroundColor, isGrayscale, font) = value.split( PROPERTIES_SEPARATOR ) AsciiParams( gradient = Base64.decode(gradient).toString(Charsets.UTF_8), fontSize = fontSize.toFloat(), backgroundColor = backgroundColor.toInt().toColorModel(), isGrayscale = isGrayscale.toBoolean(), font = DomainFontFamily.fromString(font).asFontType() ) } name == BloomParams::class.simpleName -> { val (threshold, intensity, radius, softKnee, exposure, gamma) = value.split( PROPERTIES_SEPARATOR ) BloomParams( threshold = threshold.toFloat(), intensity = intensity.toFloat(), radius = radius.toInt(), softKnee = softKnee.toFloat(), exposure = exposure.toFloat(), gamma = gamma.toFloat() ) } name == CropOrPerspectiveParams::class.simpleName -> { val (topLeft, topRight, bottomLeft, bottomRight, isAbsolute) = value.split( PROPERTIES_SEPARATOR ) CropOrPerspectiveParams( topLeft = topLeft.toFloatPair(), topRight = topRight.toFloatPair(), bottomLeft = bottomLeft.toFloatPair(), bottomRight = bottomRight.toFloatPair(), isAbsolute = isAbsolute.toBoolean() ) } name == IntegerSize::class.simpleName -> { val (width, height) = value.split(PROPERTIES_SEPARATOR) IntegerSize( width = width.toInt(), height = height.toInt() ) } else -> null } } internal fun String.getTypeFromBraces(): String = removeSuffix("}").split("{")[1] internal fun Any.toPart(): String { return when (this) { is Int -> toString() is Float -> toString() is ColorModel -> colorInt.toString() is Boolean -> toString() is BlurEdgeMode -> name is TransferFunc -> name is FadeSide -> name is PopArtBlendingMode -> name is MirrorSide -> name is PolarCoordinatesType -> name else -> "" } } internal fun String.fromPart(type: String): Any { return when (type) { Int::class.simpleName() -> toInt() Float::class.simpleName() -> toFloat() ColorModel::class.simpleName() -> ColorModel(toInt()) Boolean::class.simpleName() -> toBoolean() BlurEdgeMode::class.simpleName() -> BlurEdgeMode.valueOf(this) TransferFunc::class.simpleName() -> TransferFunc.valueOf(this) FadeSide::class.simpleName() -> FadeSide.valueOf(this) PopArtBlendingMode::class.simpleName() -> PopArtBlendingMode.valueOf(this) MirrorSide::class.simpleName() -> MirrorSide.valueOf(this) PolarCoordinatesType::class.simpleName() -> PolarCoordinatesType.valueOf(this) else -> "" } } private fun Pair<*, *>.join() = "$first$ADDITIONAL_PROPERTIES_SEPARATOR$second" private fun String.toFloatPair() = split(ADDITIONAL_PROPERTIES_SEPARATOR).let { it[0].toFloat() to it[1].toFloat() } private const val PROPERTIES_SEPARATOR = "$" private const val ADDITIONAL_PROPERTIES_SEPARATOR = "*" ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/di/FilterModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.di import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import com.t8rin.imagetoolbox.core.filters.domain.FilterParamsInteractor import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.feature.filters.data.AndroidFilterMaskApplier import com.t8rin.imagetoolbox.feature.filters.data.AndroidFilterParamsInteractor import com.t8rin.imagetoolbox.feature.filters.data.AndroidFilterProvider import com.t8rin.imagetoolbox.feature.filters.domain.FilterMaskApplier import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface FilterModule { @Singleton @Binds fun filterProvider( provider: AndroidFilterProvider ): FilterProvider @Singleton @Binds fun filterMaskApplier( applier: AndroidFilterMaskApplier ): FilterMaskApplier @Singleton @Binds fun favoriteFiltersInteractor( interactor: AndroidFilterParamsInteractor ): FilterParamsInteractor } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMask.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.domain import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.feature.draw.domain.PathPaint interface FilterMask { val maskPaints: List> val filters: List> val isInverseFillType: Boolean } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/domain/FilterMaskApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.domain interface FilterMaskApplier { suspend fun filterByMask( filterMask: FilterMask, imageUri: String ): Image? suspend fun filterByMask( filterMask: FilterMask, image: Image ): Image? suspend fun filterByMasks( filterMasks: List>, imageUri: String ): Image? suspend fun filterByMasks( filterMasks: List>, image: Image ): Image? } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/FiltersContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvideFilterPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.CompareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShowOriginalButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareSheet import com.t8rin.imagetoolbox.feature.filters.presentation.components.FiltersContentActionButtons import com.t8rin.imagetoolbox.feature.filters.presentation.components.FiltersContentControls import com.t8rin.imagetoolbox.feature.filters.presentation.components.FiltersContentNoData import com.t8rin.imagetoolbox.feature.filters.presentation.components.FiltersContentSheets import com.t8rin.imagetoolbox.feature.filters.presentation.components.FiltersContentTopAppBarActions import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent @Composable fun FiltersContent( component: FiltersComponent ) { AutoContentBasedColors(component.previewBitmap) val imagePicker = rememberImagePicker(onSuccess = component::setBasicFilter) val pickSingleImagePicker = rememberImagePicker(onSuccess = component::setMaskFilter) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else if (component.filterType != null) { component.clearType() } else component.onGoBack() } val isPortrait by isPortraitOrientationAsState() var showOriginal by remember { mutableStateOf(false) } val actions: @Composable RowScope.() -> Unit = { Spacer(modifier = Modifier.width(8.dp)) if (component.bitmap != null) { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.canSave, onShare = component::performSharing, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) ShowOriginalButton( canShow = component.canShow(), onStateChange = { showOriginal = it } ) } var showCompareSheet by rememberSaveable { mutableStateOf(false) } CompareButton( onClick = { showCompareSheet = true }, visible = component.previewBitmap != null ) CompareSheet( data = component.bitmap to component.previewBitmap, visible = showCompareSheet, onDismiss = { showCompareSheet = false } ) if (component.bitmap != null && (component.basicFilterState.filters.size >= 2 || component.maskingFilterState.masks.size >= 2)) { EnhancedIconButton( onClick = component::showReorderSheet ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = stringResource(R.string.properties) ) } } } var tempSelectionUris by rememberSaveable { mutableStateOf?>( null ) } LaunchedEffect(component.isSelectionFilterPickerVisible) { if (!component.isSelectionFilterPickerVisible) tempSelectionUris = null } val selectionFilterPicker = rememberImagePicker { uris: List -> tempSelectionUris = uris if (uris.size > 1) { component.setBasicFilter(tempSelectionUris) } else { component.showSelectionFilterPicker() } } AutoFilePicker( onAutoPick = selectionFilterPicker::pickImage, isPickedAlready = component.initialType != null ) AdaptiveLayoutScreen( shouldDisableBackHandler = !(component.haveChanges || component.filterType != null), onGoBack = onBack, title = { AnimatedContent( targetState = component.filterType?.let { stringResource(it.title) } ) { title -> if (title == null) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.filter) ) EnhancedBadge( content = { Text( text = UiFilter.count.toString() ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } } else { TopAppBarTitle( title = title, input = component.bitmap, isLoading = component.isImageLoading, size = component.imageInfo.sizeInBytes.toLong() ) } } }, topAppBarPersistentActions = { FiltersContentTopAppBarActions( component = component, actions = actions ) }, actions = actions, showActionsInTopAppBar = false, canShowScreenData = component.filterType != null, imagePreview = { ImageContainer( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), imageInside = isPortrait, showOriginal = showOriginal, previewBitmap = component.previewBitmap, originalBitmap = component.bitmap, isLoading = component.isImageLoading, shouldShowPreview = true, animatePreviewChange = false ) }, forceImagePreviewToMax = showOriginal, controls = { FiltersContentControls(component) }, buttons = { bottomActions -> FiltersContentActionButtons( component = component, actions = bottomActions, imagePicker = imagePicker, pickSingleImagePicker = pickSingleImagePicker, selectionFilterPicker = selectionFilterPicker ) }, insetsForNoData = WindowInsets(0), noDataControls = { FiltersContentNoData( component = component, imagePicker = imagePicker, pickSingleImagePicker = pickSingleImagePicker, tempSelectionUris = tempSelectionUris ) }, contentPadding = animateDpAsState( if (component.filterType == null) 12.dp else 20.dp ).value, ) ProvideFilterPreview(component.previewBitmap) FiltersContentSheets(component) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = { if (component.filterType != null) { component.clearType() } else { component.onGoBack() } }, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterPreference.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun BasicFilterPreference( onClick: () -> Unit, modifier: Modifier = Modifier, color: Color = Color.Unspecified ) { PreferenceItem( onClick = onClick, startIcon = Icons.Rounded.AutoFixHigh, title = stringResource(R.string.filter), subtitle = stringResource(R.string.filter_sub), containerColor = color, modifier = modifier ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/BasicFilterState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import android.net.Uri import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter data class BasicFilterState( val uris: List? = null, val filters: List> = emptyList(), val selectedUri: Uri? = null ) ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentActionButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.foundation.layout.RowScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material.icons.rounded.Texture import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent @Composable internal fun FiltersContentActionButtons( component: FiltersComponent, actions: @Composable RowScope.() -> Unit, imagePicker: ImagePicker, pickSingleImagePicker: ImagePicker, selectionFilterPicker: ImagePicker, ) { val isPortrait by isPortraitOrientationAsState() val filterType = component.filterType val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { when (filterType) { is Screen.Filter.Type.Basic -> { component.saveBitmaps( oneTimeSaveLocationUri = it ) } is Screen.Filter.Type.Masking -> { component.saveMaskedBitmap( oneTimeSaveLocationUri = it ) } else -> Unit } } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.basicFilterState.uris.isNullOrEmpty() && component.maskingFilterState.uri == null, onSecondaryButtonClick = { when (filterType) { is Screen.Filter.Type.Basic -> imagePicker.pickImage() is Screen.Filter.Type.Masking -> pickSingleImagePicker.pickImage() null -> selectionFilterPicker.pickImage() } }, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, isPrimaryButtonVisible = component.canSave, middleFab = { EnhancedFloatingActionButton( onClick = component::showAddFiltersSheet, containerColor = MaterialTheme.colorScheme.mixedContainer ) { when (filterType) { is Screen.Filter.Type.Basic -> { Icon( imageVector = Icons.Rounded.AutoFixHigh, contentDescription = null ) } is Screen.Filter.Type.Masking -> { Icon( imageVector = Icons.Rounded.Texture, contentDescription = null ) } null -> Unit } } }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, showNullDataButtonAsContainer = true ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = if (filterType !is Screen.Filter.Type.Masking) { Picker.Multiple } else { Picker.Single }, imagePicker = selectionFilterPicker, visible = showOneTimeImagePickingDialog ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Texture import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.AddFilterButton import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheet import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.HistogramChart import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent @Composable internal fun FiltersContentControls( component: FiltersComponent ) { val filterType = component.filterType var showTemplateCreationSheet by rememberSaveable(filterType) { mutableStateOf(false) } val histogramItem = @Composable { PreferenceItemOverload( title = stringResource(R.string.histogram), subtitle = stringResource(R.string.histogram_sub), endIcon = { AnimatedContent(component.previewBitmap != null) { if (it) { HistogramChart( model = component.previewBitmap, modifier = Modifier .width(100.dp) .height(65.dp) .background(MaterialTheme.colorScheme.background) ) } else { Box(modifier = Modifier.size(56.dp)) { EnhancedLoadingIndicator() } } } }, shape = ShapeDefaults.extraLarge, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.size(8.dp)) } when (filterType) { is Screen.Filter.Type.Basic -> { val filterList = component.basicFilterState.filters if (component.bitmap != null) { ImageCounter( imageCount = component.basicFilterState.uris?.size?.takeIf { it > 1 }, onRepick = component::showPickImageFromUrisSheet ) if (filterList.isNotEmpty()) histogramItem() AnimatedContent( targetState = filterList.isNotEmpty(), transitionSpec = { fadeIn() + expandVertically() togetherWith fadeOut() + shrinkVertically() } ) { notEmpty -> if (notEmpty) { Column(Modifier.container(MaterialTheme.shapes.extraLarge)) { TitleItem(text = stringResource(R.string.filters)) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( 8.dp ), modifier = Modifier.padding(8.dp) ) { filterList.forEachIndexed { index, filter -> FilterItem( backgroundColor = MaterialTheme.colorScheme.surface, filter = filter, onFilterChange = { newValue -> component.updateFilter( value = newValue, index = index ) }, onLongPress = component::showReorderSheet, showDragHandle = false, onRemove = { component.removeFilterAtIndex(index) }, onCreateTemplate = { showTemplateCreationSheet = true component.filterTemplateCreationSheetComponent.setInitialTemplateFilter( TemplateFilter( name = getString(filter.title), filters = listOf(filter) ) ) } ) } AddFilterButton( modifier = Modifier.padding(horizontal = 16.dp), onClick = component::showAddFiltersSheet, onCreateTemplate = { showTemplateCreationSheet = true component.filterTemplateCreationSheetComponent.setInitialTemplateFilter( TemplateFilter( name = getString( filterList.firstOrNull()?.title ?: R.string.template_filter ), filters = filterList ) ) } ) } } } else { AddFilterButton( onClick = component::showAddFiltersSheet, modifier = Modifier.padding( horizontal = 16.dp ) ) } } Spacer(Modifier.size(8.dp)) if (filterList.isEmpty()) histogramItem() SaveExifWidget( imageFormat = component.imageInfo.imageFormat, checked = component.keepExif, onCheckedChange = component::setKeepExif ) if (component.imageInfo.imageFormat.canChangeCompressionValue) Spacer( Modifier.size(8.dp) ) QualitySelector( imageFormat = component.imageInfo.imageFormat, quality = component.imageInfo.quality, onQualityChange = component::setQuality ) Spacer(Modifier.size(8.dp)) ImageFormatSelector( value = component.imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = component.imageInfo.quality, ) } Spacer(Modifier.height(8.dp)) } is Screen.Filter.Type.Masking -> { val maskList = component.maskingFilterState.masks if (component.bitmap != null) { if (maskList.isNotEmpty()) histogramItem() AnimatedContent( targetState = maskList.isNotEmpty(), transitionSpec = { fadeIn() + expandVertically() togetherWith fadeOut() + shrinkVertically() } ) { notEmpty -> if (notEmpty) { Column(Modifier.container(MaterialTheme.shapes.extraLarge)) { TitleItem(text = stringResource(R.string.masks)) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( 4.dp ), modifier = Modifier.padding(8.dp) ) { maskList.forEachIndexed { index, mask -> MaskItem( backgroundColor = MaterialTheme.colorScheme.surface, imageUri = component.maskingFilterState.uri, previousMasks = maskList.take(index), mask = mask, titleText = stringResource( R.string.mask_indexed, index + 1 ), onMaskChange = { filterMask -> component.updateMask( value = filterMask, index = index ) }, onLongPress = component::showReorderSheet, showDragHandle = false, onRemove = { component.removeMaskAtIndex(index) }, addMaskSheetComponent = component.addMaskSheetComponent, onCreateTemplate = { showTemplateCreationSheet = true component.filterTemplateCreationSheetComponent.setInitialTemplateFilter( TemplateFilter( name = getString( mask.filters.firstOrNull() ?.toUiFilter()?.title ?: R.string.template_filter ), filters = mask.filters ) ) } ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = component::showAddFiltersSheet, modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 4.dp ) ) { Icon( imageVector = Icons.Rounded.Texture, contentDescription = stringResource(R.string.add_mask) ) Spacer(Modifier.width(8.dp)) Text(stringResource(id = R.string.add_mask)) } } } } else { EnhancedButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = component::showAddFiltersSheet, modifier = Modifier.padding( horizontal = 16.dp ) ) { Icon( imageVector = Icons.Rounded.Texture, contentDescription = stringResource(R.string.add_mask) ) Spacer(Modifier.width(8.dp)) Text(stringResource(id = R.string.add_mask)) } } } Spacer(Modifier.size(8.dp)) if (maskList.isEmpty()) histogramItem() SaveExifWidget( imageFormat = component.imageInfo.imageFormat, checked = component.keepExif, onCheckedChange = component::setKeepExif ) if (component.imageInfo.imageFormat.canChangeCompressionValue) Spacer( Modifier.size(8.dp) ) QualitySelector( imageFormat = component.imageInfo.imageFormat, quality = component.imageInfo.quality, onQualityChange = component::setQuality ) Spacer(Modifier.size(8.dp)) ImageFormatSelector( value = component.imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = component.imageInfo.quality ) } Spacer(Modifier.height(8.dp)) } else -> Unit } FilterTemplateCreationSheet( component = component.filterTemplateCreationSheetComponent, visible = showTemplateCreationSheet, onDismiss = { showTemplateCreationSheet = false } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentNoData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent @Composable internal fun FiltersContentNoData( component: FiltersComponent, imagePicker: ImagePicker, pickSingleImagePicker: ImagePicker, tempSelectionUris: List? ) { val isPortrait by isPortraitOrientationAsState() val preference1 = @Composable { BasicFilterPreference( onClick = imagePicker::pickImage, modifier = Modifier.fillMaxWidth() ) } val preference2 = @Composable { MaskFilterPreference( onClick = pickSingleImagePicker::pickImage, modifier = Modifier.fillMaxWidth() ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() } } else { val direction = LocalLayoutDirection.current Row( modifier = Modifier.padding( WindowInsets.displayCutout.asPaddingValues() .let { PaddingValues( start = it.calculateStartPadding(direction), end = it.calculateEndPadding(direction) ) } ) ) { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } } EnhancedModalBottomSheet( visible = component.isSelectionFilterPickerVisible, onDismiss = { if (!it) component.hideSelectionFilterPicker() }, confirmButton = { EnhancedButton( onClick = component::hideSelectionFilterPicker, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(id = R.string.close)) } }, sheetContent = { SideEffect { if (tempSelectionUris == null) { component.hideSelectionFilterPicker() } } LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(250.dp), horizontalArrangement = Arrangement.spacedBy( space = 12.dp, alignment = Alignment.CenterHorizontally ), verticalItemSpacing = 12.dp, contentPadding = PaddingValues(12.dp), flingBehavior = enhancedFlingBehavior() ) { item { BasicFilterPreference( onClick = { component.setBasicFilter(tempSelectionUris) component.hideSelectionFilterPicker() }, modifier = Modifier.fillMaxWidth() ) } item { MaskFilterPreference( onClick = { component.setMaskFilter(tempSelectionUris?.firstOrNull()) component.hideSelectionFilterPicker() }, modifier = Modifier.fillMaxWidth() ) } } }, title = { TitleItem( text = stringResource(id = R.string.pick_file), icon = Icons.Rounded.FileOpen ) } ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentSheets.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterReorderSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheet import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet.AddEditMaskSheet import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent @Composable internal fun FiltersContentSheets( component: FiltersComponent ) { val isPortrait by isPortraitOrientationAsState() if (component.filterType is Screen.Filter.Type.Basic) { val transformations by remember(component.basicFilterState, component.imageInfo) { derivedStateOf(component::getFiltersTransformation) } PickImageFromUrisSheet( transformations = transformations, visible = component.isPickImageFromUrisSheetVisible, onDismiss = component::hidePickImageFromUrisSheet, uris = component.basicFilterState.uris, selectedUri = component.basicFilterState.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = component::updateUrisSilently, columns = if (isPortrait) 2 else 4, ) AddFiltersSheet( visible = component.isAddFiltersSheetVisible, onDismiss = component::hideAddFiltersSheet, previewBitmap = component.previewBitmap, onFilterPicked = component::addFilterNewInstance, onFilterPickedWithParams = component::addFilter, component = component.addFiltersSheetComponent, filterTemplateCreationSheetComponent = component.filterTemplateCreationSheetComponent ) FilterReorderSheet( filterList = component.basicFilterState.filters, visible = component.isReorderSheetVisible, onDismiss = component::hideReorderSheet, onReorder = component::updateFiltersOrder ) } else if (component.filterType is Screen.Filter.Type.Masking) { AddEditMaskSheet( visible = component.isAddFiltersSheetVisible, targetBitmapUri = component.maskingFilterState.uri, onMaskPicked = component::addMask, onDismiss = component::hideAddFiltersSheet, masks = component.maskingFilterState.masks, component = component.addMaskSheetComponent ) MaskReorderSheet( maskList = component.maskingFilterState.masks, visible = component.isReorderSheetVisible, onDismiss = component::hideReorderSheet, onReorder = component::updateMasksOrder ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/FiltersContentTopAppBarActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.foundation.layout.RowScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material.icons.rounded.Texture import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageSheet @Composable internal fun RowScope.FiltersContentTopAppBarActions( component: FiltersComponent, actions: @Composable RowScope.() -> Unit ) { val isPortrait by isPortraitOrientationAsState() if (component.previewBitmap != null) { var showColorPicker by rememberSaveable { mutableStateOf(false) } var tempColor by rememberSaveable( showColorPicker, stateSaver = ColorSaver ) { mutableStateOf(Color.Black) } EnhancedIconButton( onClick = { showColorPicker = true }, enabled = component.previewBitmap != null ) { Icon( imageVector = Icons.Outlined.Eyedropper, contentDescription = stringResource(R.string.pipette) ) } PickColorFromImageSheet( visible = showColorPicker, onDismiss = { showColorPicker = false }, bitmap = component.previewBitmap, onColorChange = { tempColor = it }, color = tempColor ) var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomButton( onClick = { showZoomSheet = true }, visible = component.bitmap != null, ) ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) } if (component.bitmap == null) { TopAppBarEmoji() } else { if (isPortrait) { when (component.filterType) { is Screen.Filter.Type.Basic -> { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = component::showAddFiltersSheet ) { Icon( imageVector = Icons.Rounded.AutoFixHigh, contentDescription = stringResource(R.string.add_filter) ) } } is Screen.Filter.Type.Masking -> { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = component::showAddFiltersSheet ) { Icon( imageVector = Icons.Rounded.Texture, contentDescription = stringResource(R.string.add_mask) ) } } null -> Unit } } else { actions() } } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskFilterPreference.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Texture import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun MaskFilterPreference( onClick: () -> Unit, modifier: Modifier = Modifier, color: Color = Color.Unspecified ) { PreferenceItem( onClick = onClick, startIcon = Icons.Rounded.Texture, title = stringResource(R.string.mask_filter), subtitle = stringResource(R.string.mask_filter_sub), containerColor = color, modifier = modifier ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.AddFilterButton import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheet import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.EditAlt import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet.AddEditMaskSheet import com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet.AddMaskSheetComponent @Composable fun MaskItem( addMaskSheetComponent: AddMaskSheetComponent?, mask: UiFilterMask, modifier: Modifier = Modifier, titleText: String, onMaskChange: (UiFilterMask) -> Unit, previewOnly: Boolean = false, backgroundColor: Color = Color.Unspecified, showDragHandle: Boolean, onLongPress: (() -> Unit)? = null, onCreateTemplate: (() -> Unit)?, onRemove: () -> Unit, imageUri: Uri? = null, previousMasks: List = emptyList(), shape: Shape = MaterialTheme.shapes.extraLarge ) { var showMaskRemoveDialog by rememberSaveable { mutableStateOf(false) } var showAddFilterSheet by rememberSaveable { mutableStateOf(false) } var showEditMaskSheet by rememberSaveable { mutableStateOf(false) } val settingsState = LocalSettingsState.current Box { Row( modifier = modifier .container( color = backgroundColor, shape = shape ) .animateContentSizeNoClip() .then( onLongPress?.let { Modifier.pointerInput(Unit) { detectTapGestures( onLongPress = { it() } ) } } ?: Modifier ), verticalAlignment = Alignment.CenterVertically ) { if (showDragHandle) { Spacer(Modifier.width(8.dp)) Icon( imageVector = Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.drag_handle_width) ) Spacer(Modifier.width(8.dp)) Box( Modifier .height(32.dp) .width(settingsState.borderWidth.coerceAtLeast(0.25.dp)) .background(MaterialTheme.colorScheme.outlineVariant()) .padding(start = 20.dp) ) } Column( Modifier .weight(1f) .alpha(if (previewOnly) 0.5f else 1f) ) { Row(verticalAlignment = Alignment.CenterVertically) { Row( modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically ) { PathPaintPreview( modifier = Modifier .padding(start = 12.dp) .sizeIn(maxHeight = 30.dp, maxWidth = 30.dp), pathPaints = mask.maskPaints ) Text( text = titleText, fontWeight = FontWeight.SemiBold, modifier = Modifier .padding( end = 8.dp, start = 16.dp ) ) Spacer(Modifier.weight(1f)) EnhancedIconButton( onClick = { showMaskRemoveDialog = true } ) { Icon( imageVector = Icons.Rounded.RemoveCircleOutline, contentDescription = stringResource(R.string.remove) ) } EnhancedIconButton( onClick = { showEditMaskSheet = true } ) { Icon( imageVector = Icons.Rounded.EditAlt, contentDescription = stringResource(R.string.edit) ) } } EnhancedAlertDialog( visible = showMaskRemoveDialog, onDismissRequest = { showMaskRemoveDialog = false }, confirmButton = { EnhancedButton( onClick = { showMaskRemoveDialog = false } ) { Text(stringResource(R.string.cancel)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showMaskRemoveDialog = false onRemove() } ) { Text(stringResource(R.string.delete)) } }, title = { Text(stringResource(R.string.delete_mask)) }, icon = { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete) ) }, text = { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { PathPaintPreview( pathPaints = mask.maskPaints, modifier = Modifier.sizeIn( maxHeight = 80.dp, maxWidth = 80.dp ) ) Spacer(modifier = Modifier.height(8.dp)) Text(stringResource(R.string.delete_mask_warn)) } } ) } AnimatedVisibility(mask.filters.isNotEmpty()) { ExpandableItem( modifier = Modifier.padding(8.dp), visibleContent = { TitleItem(text = stringResource(id = R.string.filters) + " (${mask.filters.size})") }, expandableContent = { Column( verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(horizontal = 8.dp) ) { mask.filters.forEachIndexed { index, filter -> val uiFilter by remember(filter) { derivedStateOf { filter.toUiFilter() } } FilterItem( backgroundColor = MaterialTheme.colorScheme.surface, filter = uiFilter, showDragHandle = false, onRemove = { onMaskChange( mask.copy(filters = mask.filters - filter) ) }, onFilterChange = { value -> onMaskChange( mask.copy( filters = mask.filters.toMutableList() .apply { this[index] = uiFilter.copy(value) } ) ) }, onCreateTemplate = onCreateTemplate ) } AddFilterButton( containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, onClick = { showAddFilterSheet = true } ) } } ) } } } if (previewOnly) { Surface( color = Color.Transparent, modifier = modifier.matchParentSize() ) {} } } addMaskSheetComponent?.let { AddFiltersSheet( visible = showAddFilterSheet, onVisibleChange = { showAddFilterSheet = it }, previewBitmap = null, onFilterPicked = { filter -> onMaskChange( mask.copy( filters = mask.filters + filter.newInstance() ) ) }, onFilterPickedWithParams = { filter -> onMaskChange( mask.copy( filters = mask.filters + filter ) ) }, component = addMaskSheetComponent.addFiltersSheetComponent, filterTemplateCreationSheetComponent = addMaskSheetComponent.filterTemplateCreationSheetComponent ) AddEditMaskSheet( mask = mask, visible = showEditMaskSheet, targetBitmapUri = imageUri, masks = previousMasks, onDismiss = { showEditMaskSheet = false }, onMaskPicked = onMaskChange, component = addMaskSheetComponent ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskReorderSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Reorder import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState @Composable fun MaskReorderSheet( maskList: List, visible: Boolean, onDismiss: () -> Unit, onReorder: (List) -> Unit ) { EnhancedModalBottomSheet( sheetContent = { if (maskList.size < 2) onDismiss() Box { val data = remember { mutableStateOf(maskList) } val listState = rememberLazyListState() val haptics = LocalHapticFeedback.current val state = rememberReorderableLazyListState( lazyListState = listState, onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } } ) LazyColumn( state = listState, contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data.value, key = { _, it -> it.hashCode() } ) { index, mask -> ReorderableItem( state = state, key = mask.hashCode() ) { isDragging -> MaskItem( mask = mask, modifier = Modifier .fillMaxWidth() .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { onReorder(data.value) } ) .scale( animateFloatAsState( if (isDragging) 1.05f else 1f ).value ), onMaskChange = {}, previewOnly = true, shape = ShapeDefaults.byIndex( index = index, size = data.value.size ), titleText = stringResource(R.string.mask_indexed, index + 1), showDragHandle = maskList.size >= 2, onRemove = {}, addMaskSheetComponent = null, onCreateTemplate = null ) } } } } }, visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.reorder), icon = Icons.Rounded.Reorder ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } }, ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/MaskingFilterState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import android.net.Uri data class MaskingFilterState( val uri: Uri? = null, val masks: List = emptyList() ) ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/PathPaintPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import android.graphics.BlurMaskFilter import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.PaintingStyle import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.nativePaint import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.scaleToFitCanvas import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.feature.draw.domain.PathPaint @Composable fun PathPaintPreview( modifier: Modifier = Modifier, pathPaints: List> ) { val visuals = Modifier .border( width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant(), shape = ShapeDefaults.extraSmall ) .clip(ShapeDefaults.extraSmall) .transparencyChecker( checkerHeight = 2.dp, checkerWidth = 2.dp ) val first = pathPaints.firstOrNull() if (first != null) { Box( modifier = modifier .aspectRatio(first.canvasSize.aspectRatio) .then(visuals) .alpha(0.99f) .drawBehind { val currentSize = IntegerSize( size.width.toInt(), size.height.toInt() ) drawIntoCanvas { composeCanvas -> val canvas = composeCanvas.nativeCanvas pathPaints.forEach { pathPaint -> val stroke = pathPaint .strokeWidth .toPx(currentSize) val drawPathMode = pathPaint.drawPathMode val isSharpEdge = drawPathMode.isSharpEdge val isFilled = drawPathMode.isFilled canvas.drawPath( pathPaint.path .scaleToFitCanvas( currentSize = currentSize, oldSize = pathPaint.canvasSize ) .asAndroidPath(), Paint() .apply { if (pathPaint.isErasing) { blendMode = BlendMode.Clear style = PaintingStyle.Stroke strokeWidth = stroke strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } else { color = pathPaint.drawColor if (isFilled) { style = PaintingStyle.Fill } else { style = PaintingStyle.Stroke strokeWidth = stroke if (isSharpEdge) { style = PaintingStyle.Stroke } else { strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } } } } .nativePaint .apply { if (pathPaint.brushSoftness.value > 0f) { maskFilter = BlurMaskFilter( pathPaint.brushSoftness.toPx(currentSize), BlurMaskFilter.Blur.NORMAL ) } } ) } } } ) } else { val color = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.6f) val textMeasurer = rememberTextMeasurer() Box( modifier = modifier .aspectRatio(1f) .then(visuals) .drawBehind { drawText( textMeasurer = textMeasurer, text = "N", topLeft = Offset( size.width / 2, size.height / 2 ), style = TextStyle(color = color) ) } ) } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/UiFilterMask.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.feature.draw.domain.PathPaint import com.t8rin.imagetoolbox.feature.filters.domain.FilterMask data class UiFilterMask( override val filters: List>, override val maskPaints: List>, override val isInverseFillType: Boolean ) : FilterMask ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Texture import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.ImageHeaderState import com.t8rin.imagetoolbox.core.ui.widget.image.imageStickyHeader import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.saver.PtSaver import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.utils.rememberAvailableHeight import com.t8rin.imagetoolbox.feature.filters.presentation.components.UiFilterMask @Composable fun AddEditMaskSheet( component: AddMaskSheetComponent, mask: UiFilterMask? = null, visible: Boolean, onDismiss: () -> Unit, targetBitmapUri: Uri? = null, masks: List = emptyList(), onMaskPicked: (UiFilterMask) -> Unit, ) { var invalidations by remember { mutableIntStateOf(0) } LaunchedEffect(mask, masks, targetBitmapUri, invalidations) { component.setMask( mask = mask, bitmapUri = targetBitmapUri, masks = masks ) } val isPortrait by isPortraitOrientationAsState() var showExitDialog by remember { mutableStateOf(false) } val settingsState = LocalSettingsState.current var isEraserOn by rememberSaveable { mutableStateOf(false) } var strokeWidth by rememberSaveable(stateSaver = PtSaver) { mutableStateOf(settingsState.defaultDrawLineWidth.pt) } var brushSoftness by rememberSaveable(stateSaver = PtSaver) { mutableStateOf(20.pt) } var panEnabled by rememberSaveable { mutableStateOf(false) } val canSave = component.paths.isNotEmpty() && component.filterList.isNotEmpty() EnhancedModalBottomSheet( visible = visible, onDismiss = { if (component.paths.isEmpty() && component.filterList.isEmpty()) onDismiss() else showExitDialog = true }, cancelable = false, title = { TitleItem( text = stringResource(id = R.string.add_mask), icon = Icons.Rounded.Texture ) }, confirmButton = { EnhancedButton( enabled = canSave, containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onMaskPicked(component.getUiMask()) onDismiss() } ) { Text(stringResource(id = R.string.save)) } }, dragHandle = { Column( modifier = Modifier .fillMaxWidth() .drawHorizontalStroke(autoElevation = 3.dp) .zIndex(Float.MAX_VALUE) .background(EnhancedBottomSheetDefaults.barContainerColor) .padding(8.dp) ) { EnhancedIconButton( onClick = { if (component.paths.isEmpty() && component.filterList.isEmpty()) onDismiss() else showExitDialog = true } ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } } }, enableBackHandler = component.paths.isEmpty() && component.filterList.isEmpty() ) { component.AttachLifecycle() LaunchedEffect(Unit) { invalidations++ } var imageState by remember { mutableStateOf(ImageHeaderState(2)) } if (visible) { BackHandler( enabled = !(component.paths.isEmpty() && component.filterList.isEmpty()) ) { showExitDialog = true } } val drawPreview: @Composable () -> Unit = { AddMaskSheetBitmapPreview( component = component, imageState = imageState, strokeWidth = strokeWidth, brushSoftness = brushSoftness, isEraserOn = isEraserOn, panEnabled = panEnabled ) } Row { val backgroundColor = MaterialTheme.colorScheme.surfaceContainerLow if (!isPortrait) { Box(modifier = Modifier.weight(1.3f)) { drawPreview() } } val internalHeight = rememberAvailableHeight(imageState = imageState) LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f), flingBehavior = enhancedFlingBehavior() ) { imageStickyHeader( visible = isPortrait, internalHeight = internalHeight, imageState = imageState, onStateChange = { imageState = it }, isControlsVisibleIndefinitely = true, padding = 0.dp, backgroundColor = backgroundColor, imageBlock = drawPreview ) item { AddEditMaskSheetControls( component = component, imageState = imageState, strokeWidth = strokeWidth, onStrokeWidthChange = { strokeWidth = it }, brushSoftness = brushSoftness, onBrushSoftnessChange = { brushSoftness = it }, panEnabled = panEnabled, onTogglePanEnabled = { panEnabled = !panEnabled }, isEraserOn = isEraserOn, onToggleIsEraserOn = { isEraserOn = !isEraserOn } ) } } } } ExitWithoutSavingDialog( onExit = onDismiss, onDismiss = { showExitDialog = false }, visible = showExitDialog, placeAboveAll = true ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddEditMaskSheetControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Redo import androidx.compose.material.icons.automirrored.rounded.Undo import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.AddFilterButton import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterReorderSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheet import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Preview import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.EraseModeButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.HistogramChart import com.t8rin.imagetoolbox.core.ui.widget.image.ImageHeaderState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.BrushSoftnessSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawColorSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawPathModeSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.LineWidthSelector @Composable internal fun AddEditMaskSheetControls( component: AddMaskSheetComponent, imageState: ImageHeaderState, strokeWidth: Pt, onStrokeWidthChange: (Pt) -> Unit, brushSoftness: Pt, onBrushSoftnessChange: (Pt) -> Unit, panEnabled: Boolean, onTogglePanEnabled: () -> Unit, isEraserOn: Boolean, onToggleIsEraserOn: () -> Unit ) { var showAddFilterSheet by rememberSaveable { mutableStateOf(false) } var showReorderSheet by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() val canSave = component.paths.isNotEmpty() && component.filterList.isNotEmpty() Row( Modifier .then( if (imageState.isBlocked && isPortrait) { Modifier.padding( start = 16.dp, end = 16.dp, bottom = 16.dp ) } else Modifier.padding(16.dp) ) .container(shape = ShapeDefaults.circle) ) { PanModeButton( selected = panEnabled, onClick = onTogglePanEnabled ) Spacer(Modifier.width(4.dp)) EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = component::undo, enabled = (component.lastPaths.isNotEmpty() || component.paths.isNotEmpty()) ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Undo, contentDescription = "Undo" ) } EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = component::redo, enabled = component.undonePaths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Redo, contentDescription = "Redo" ) } EraseModeButton( selected = isEraserOn, enabled = !panEnabled, onClick = onToggleIsEraserOn ) } AnimatedVisibility(visible = canSave) { Column { BoxAnimatedVisibility(component.maskPreviewModeEnabled) { PreferenceItemOverload( title = stringResource(R.string.histogram), subtitle = stringResource(R.string.histogram_sub), endIcon = { AnimatedContent(component.previewBitmap != null) { if (it) { HistogramChart( model = component.previewBitmap, modifier = Modifier .width(100.dp) .height(65.dp) .background(MaterialTheme.colorScheme.background) ) } else { Box(modifier = Modifier.size(56.dp)) { EnhancedLoadingIndicator() } } } }, shape = ShapeDefaults.extraLarge, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) .padding(bottom = 8.dp) ) } PreferenceRowSwitch( title = stringResource(id = R.string.mask_preview), subtitle = stringResource(id = R.string.mask_preview_sub), containerColor = animateColorAsState( if (component.maskPreviewModeEnabled) MaterialTheme.colorScheme.onPrimary else Color.Unspecified, ).value, modifier = Modifier.padding(horizontal = 16.dp), shape = ShapeDefaults.extraLarge, contentColor = animateColorAsState( if (component.maskPreviewModeEnabled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface ).value, onClick = component::togglePreviewMode, checked = component.maskPreviewModeEnabled, startIcon = Icons.Outlined.Preview ) } } Column( horizontalAlignment = Alignment.CenterHorizontally ) { DrawColorSelector( color = Color.Unspecified, titleText = stringResource(id = R.string.mask_color), defaultColors = remember { listOf( Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Cyan, Color.Magenta ) }, value = component.maskColor, onValueChange = component::updateMaskColor, modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 8.dp ) ) DrawPathModeSelector( modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 8.dp ), values = remember { listOf( DrawPathMode.Free, DrawPathMode.FloodFill(), DrawPathMode.Spray(), DrawPathMode.Lasso, DrawPathMode.Rect(), DrawPathMode.Oval, DrawPathMode.Triangle, DrawPathMode.Polygon(), DrawPathMode.Star() ) }, value = component.drawPathMode, onValueChange = component::setDrawPathMode, drawMode = DrawMode.Pen ) LineWidthSelector( modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 8.dp ), color = Color.Unspecified, value = strokeWidth.value, onValueChange = { onStrokeWidthChange(it.pt) } ) BrushSoftnessSelector( modifier = Modifier .padding(top = 8.dp, end = 16.dp, start = 16.dp), color = Color.Unspecified, value = brushSoftness.value, onValueChange = { onBrushSoftnessChange(it.pt) } ) } PreferenceRowSwitch( title = stringResource(id = R.string.inverse_fill_type), subtitle = stringResource(id = R.string.inverse_fill_type_sub), checked = component.isInverseFillType, modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 8.dp ), containerColor = Color.Unspecified, resultModifier = Modifier.padding(16.dp), applyHorizontalPadding = false, shape = ShapeDefaults.extraLarge, onClick = { component.toggleIsInverseFillType() } ) AnimatedContent( targetState = component.filterList.isNotEmpty(), transitionSpec = { fadeIn() + expandVertically() togetherWith fadeOut() + shrinkVertically() } ) { notEmpty -> if (notEmpty) { var showTemplateCreationSheet by rememberSaveable { mutableStateOf(false) } Column( modifier = Modifier .padding(horizontal = 16.dp, vertical = 8.dp) .container(MaterialTheme.shapes.extraLarge) ) { TitleItem(text = stringResource(R.string.filters)) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(8.dp) ) { component.filterList.forEachIndexed { index, filter -> FilterItem( backgroundColor = MaterialTheme.colorScheme.surface, filter = filter, onFilterChange = { value -> component.updateFilter( value = value, index = index ) }, onLongPress = { showReorderSheet = true }, showDragHandle = false, onRemove = { component.removeFilterAtIndex(index) }, onCreateTemplate = { showTemplateCreationSheet = true component.filterTemplateCreationSheetComponent.setInitialTemplateFilter( TemplateFilter( name = getString(filter.title), filters = listOf(filter) ) ) } ) } AddFilterButton( modifier = Modifier.padding(horizontal = 16.dp), onClick = { showAddFilterSheet = true }, onCreateTemplate = { showTemplateCreationSheet = true component.filterTemplateCreationSheetComponent.setInitialTemplateFilter( TemplateFilter( name = getString( component.filterList.firstOrNull()?.title ?: R.string.template_filter ), filters = component.filterList ) ) } ) } } FilterTemplateCreationSheet( component = component.filterTemplateCreationSheetComponent, visible = showTemplateCreationSheet, onDismiss = { showTemplateCreationSheet = false } ) } else { AddFilterButton( onClick = { showAddFilterSheet = true }, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) } } AddFiltersSheet( visible = showAddFilterSheet, onVisibleChange = { showAddFilterSheet = it }, previewBitmap = component.previewBitmap, onFilterPicked = { component.addFilter(it.newInstance()) }, onFilterPickedWithParams = { component.addFilter(it) }, component = component.addFiltersSheetComponent, filterTemplateCreationSheetComponent = component.filterTemplateCreationSheetComponent ) FilterReorderSheet( filterList = component.filterList, visible = showReorderSheet, onDismiss = { showReorderSheet = false }, onReorder = component::updateFiltersOrder ) } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetBitmapPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.Pt import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.ImageHeaderState import com.t8rin.imagetoolbox.core.ui.widget.modifier.CornerSides import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.only import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.BitmapDrawer import net.engawapg.lib.zoomable.rememberZoomState @Composable internal fun AddMaskSheetBitmapPreview( component: AddMaskSheetComponent, imageState: ImageHeaderState, strokeWidth: Pt, brushSoftness: Pt, isEraserOn: Boolean, panEnabled: Boolean ) { val zoomState = rememberZoomState(maxScale = 30f, key = imageState) val isPortrait by isPortraitOrientationAsState() AnimatedContent( targetState = Triple( first = remember(component.previewBitmap) { derivedStateOf { component.previewBitmap?.asImageBitmap() } }.value, second = component.maskPreviewModeEnabled, third = component.isImageLoading ), transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier .fillMaxSize() .clip( if (isPortrait) { ShapeDefaults.extraLarge.only( CornerSides.Bottom ) } else RectangleShape ) .background( color = MaterialTheme.colorScheme .surfaceContainer .copy(0.8f) ) ) { (imageBitmap, preview, loading) -> if (loading || imageBitmap == null) { Box( modifier = Modifier .fillMaxSize() .padding(16.dp), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } else { val aspectRatio = imageBitmap.width / imageBitmap.height.toFloat() var drawing by remember { mutableStateOf(false) } BitmapDrawer( zoomState = zoomState, imageBitmap = imageBitmap, paths = if (!preview || drawing || component.isImageLoading) component.paths else emptyList(), strokeWidth = strokeWidth, brushSoftness = brushSoftness, drawColor = component.maskColor, onAddPath = component::addPath, isEraserOn = isEraserOn, drawMode = DrawMode.Pen, modifier = Modifier .padding(16.dp) .aspectRatio(aspectRatio, isPortrait) .fillMaxSize(), panEnabled = panEnabled, onDrawStart = { drawing = true }, onDrawFinish = { drawing = false }, onRequestFiltering = component::filter, drawPathMode = component.drawPathMode, backgroundColor = Color.Transparent ) } } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/components/addEditMaskSheet/AddMaskSheetComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.childContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.toUiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import com.t8rin.imagetoolbox.feature.draw.presentation.components.toUiPathPaint import com.t8rin.imagetoolbox.feature.filters.domain.FilterMaskApplier import com.t8rin.imagetoolbox.feature.filters.presentation.components.UiFilterMask import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class AddMaskSheetComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, private val imageTransformer: ImageTransformer, private val imageGetter: ImageGetter, private val filterMaskApplier: FilterMaskApplier, private val imagePreviewCreator: ImagePreviewCreator, private val filterProvider: FilterProvider, dispatchersHolder: DispatchersHolder, addFiltersSheetComponentFactory: AddFiltersSheetComponent.Factory, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent.Factory ) : BaseComponent(dispatchersHolder, componentContext) { val addFiltersSheetComponent: AddFiltersSheetComponent = addFiltersSheetComponentFactory( componentContext = componentContext.childContext( key = "addFiltersMask" ) ) val filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent( componentContext = componentContext.childContext( key = "filterTemplateCreationSheetComponentMask" ) ) private val _maskColor = mutableStateOf(Color.Red) val maskColor by _maskColor private val _paths: MutableState> = mutableStateOf(emptyList()) val paths by _paths private val _lastPaths = mutableStateOf(listOf()) val lastPaths: List by _lastPaths private val _undonePaths = mutableStateOf(listOf()) val undonePaths: List by _undonePaths private val _filterList: MutableState>> = mutableStateOf(emptyList()) val filterList by _filterList private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap by _previewBitmap private val _maskPreviewModeEnabled: MutableState = mutableStateOf(false) val maskPreviewModeEnabled by _maskPreviewModeEnabled private val _isInverseFillType: MutableState = mutableStateOf(false) val isInverseFillType by _isInverseFillType private val _drawPathMode: MutableState = mutableStateOf(DrawPathMode.Line) val drawPathMode by _drawPathMode private var bitmapUri: Uri? = null private var initialMasks: List = emptyList() private var initialMask: UiFilterMask? = null private fun updatePreview() { debouncedImageCalculation { if (filterList.isEmpty() || paths.isEmpty()) { _maskPreviewModeEnabled.update { false } } if (maskPreviewModeEnabled) { imageGetter.getImage( data = bitmapUri.toString(), originalSize = false )?.let { bmp -> _previewBitmap.value = filterMaskApplier.filterByMasks( filterMasks = initialMasks.takeWhile { it != initialMask }.let { it + getUiMask() }, image = bmp )?.let { imagePreviewCreator.createPreview( image = it, imageInfo = ImageInfo( width = it.width, height = it.height, imageFormat = ImageFormat.Png.Lossless ), onGetByteCount = {} ) } } } else { _previewBitmap.value = imageGetter.getImage( data = bitmapUri.toString(), originalSize = false )?.let { bmp -> filterMaskApplier.filterByMasks( filterMasks = initialMasks.takeWhile { it != initialMask }, image = bmp ) } } } } fun togglePreviewMode(value: Boolean) { _maskPreviewModeEnabled.update { value } updatePreview() } fun removeFilterAtIndex(index: Int) { _filterList.update { it.toMutableList().apply { removeAt(index) } } updatePreview() } fun updateFilter( value: T, index: Int ) { val list = _filterList.value.toMutableList() runCatching { list[index] = list[index].copy(value) _filterList.update { list } }.exceptionOrNull()?.let { throwable -> AppToastHost.showFailureToast(throwable) list[index] = list[index].newInstance() _filterList.update { list } } updatePreview() } fun updateFiltersOrder(uiFilters: List>) { _filterList.update { uiFilters } } fun addFilter(filter: UiFilter<*>) { _filterList.update { it + filter } updatePreview() } fun getUiMask(): UiFilterMask = UiFilterMask( filters = filterList, maskPaints = paths, isInverseFillType = isInverseFillType ) fun addPath(pathPaint: UiPathPaint) { _paths.update { it + pathPaint } _undonePaths.value = listOf() if (maskPreviewModeEnabled) updatePreview() } fun undo() { if (paths.isEmpty() && lastPaths.isNotEmpty()) { _paths.value = lastPaths _lastPaths.value = listOf() if (maskPreviewModeEnabled) updatePreview() return } if (paths.isEmpty()) return val lastPath = paths.last() _paths.update { it - lastPath } _undonePaths.update { it + lastPath } if (maskPreviewModeEnabled || paths.isEmpty()) updatePreview() } fun redo() { if (undonePaths.isEmpty()) return val lastPath = undonePaths.last() _paths.update { it + lastPath } _undonePaths.update { it - lastPath } if (maskPreviewModeEnabled) updatePreview() } fun updateMaskColor(color: Color) { _maskColor.update { color } _paths.update { paintList -> paintList.map { it.copy(drawColor = color) } } } fun setMask( mask: UiFilterMask?, bitmapUri: Uri?, masks: List, ) { mask?.let { _paths.update { mask.maskPaints.map { it.toUiPathPaint() } } _filterList.update { mask.filters.map { it.toUiFilter() } } _maskColor.update { initial -> val color = mask.maskPaints.map { it.drawColor }.toSet().firstOrNull() color ?: initial } _isInverseFillType.update { mask.isInverseFillType } } this.initialMask = mask this.bitmapUri = bitmapUri this.initialMasks = masks updatePreview() } fun toggleIsInverseFillType() { _isInverseFillType.update { !it } updatePreview() } suspend fun filter( bitmap: Bitmap, filters: List>, size: IntegerSize? = null, ): Bitmap? = size?.let { intSize -> imageTransformer.transform( image = bitmap, transformations = filters.map { filterProvider.filterToTransformation(it) }, size = intSize ) } ?: imageTransformer.transform( image = bitmap, transformations = filters.map { filterProvider.filterToTransformation(it) } ) fun setDrawPathMode(mode: DrawPathMode) { _drawPathMode.update { mode } } override fun resetState() { _maskColor.update { Color.Red } _paths.update { emptyList() } _undonePaths.update { emptyList() } _lastPaths.update { emptyList() } _filterList.update { emptyList() } cancelImageLoading() _previewBitmap.update { null } filterTemplateCreationSheetComponent.resetState() addFiltersSheetComponent.resetState() } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext ): AddMaskSheetComponent } } ================================================ FILE: feature/filters/src/main/java/com/t8rin/imagetoolbox/feature/filters/presentation/screenLogic/FiltersComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.core.net.toUri import coil3.transform.Transformation import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.childContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.ui.transformation.ImageInfoTransformation import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.filters.domain.FilterMaskApplier import com.t8rin.imagetoolbox.feature.filters.presentation.components.BasicFilterState import com.t8rin.imagetoolbox.feature.filters.presentation.components.MaskingFilterState import com.t8rin.imagetoolbox.feature.filters.presentation.components.UiFilterMask import com.t8rin.imagetoolbox.feature.filters.presentation.components.addEditMaskSheet.AddMaskSheetComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay class FiltersComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialType: Screen.Filter.Type?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imagePreviewCreator: ImagePreviewCreator, private val imageCompressor: ImageCompressor, private val filterMaskApplier: FilterMaskApplier, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val filterProvider: FilterProvider, private val imageInfoTransformationFactory: ImageInfoTransformation.Factory, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder, addFiltersSheetComponentFactory: AddFiltersSheetComponent.Factory, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent.Factory, addMaskSheetComponentFactory: AddMaskSheetComponent.Factory, ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialType?.let(::setType) } } val addFiltersSheetComponent: AddFiltersSheetComponent = addFiltersSheetComponentFactory( componentContext = componentContext.childContext( key = "addFiltersFilters" ) ) val filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent( componentContext = componentContext.childContext( key = "filterTemplateCreationSheetComponentFilters" ) ) val addMaskSheetComponent: AddMaskSheetComponent = addMaskSheetComponentFactory( componentContext = componentContext.childContext( key = "addMaskSheetComponentFactoryFilters" ) ) fun getFiltersTransformation(): List = listOf( imageInfoTransformationFactory( imageInfo = imageInfo, transformations = basicFilterState.filters.map( filterProvider::filterToTransformation ) ) ) private val _isPickImageFromUrisSheetVisible = mutableStateOf(false) val isPickImageFromUrisSheetVisible by _isPickImageFromUrisSheetVisible fun showPickImageFromUrisSheet() { _isPickImageFromUrisSheetVisible.update { true } } fun hidePickImageFromUrisSheet() { _isPickImageFromUrisSheetVisible.update { false } } private val _isAddFiltersSheetVisible = mutableStateOf(false) val isAddFiltersSheetVisible by _isAddFiltersSheetVisible fun showAddFiltersSheet() { _isAddFiltersSheetVisible.update { true } } fun hideAddFiltersSheet() { _isAddFiltersSheetVisible.update { false } } private val _isReorderSheetVisible = mutableStateOf(false) val isReorderSheetVisible by _isReorderSheetVisible fun showReorderSheet() { _isReorderSheetVisible.update { true } } fun hideReorderSheet() { _isReorderSheetVisible.update { false } } private val _isSelectionFilterPickerVisible = mutableStateOf(false) val isSelectionFilterPickerVisible by _isSelectionFilterPickerVisible fun showSelectionFilterPicker() { _isSelectionFilterPickerVisible.update { true } } fun hideSelectionFilterPicker() { _isSelectionFilterPickerVisible.update { false } } private val _canSave = mutableStateOf(false) val canSave by _canSave private val _basicFilterState: MutableState = mutableStateOf(BasicFilterState()) val basicFilterState by _basicFilterState private val _maskingFilterState: MutableState = mutableStateOf(MaskingFilterState()) val maskingFilterState by _maskingFilterState private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _keepExif = mutableStateOf(false) val keepExif by _keepExif private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(1) val left by _left private val _imageInfo = mutableStateOf(ImageInfo()) val imageInfo by _imageInfo private val _filterType: MutableState = mutableStateOf(null) val filterType: Screen.Filter.Type? by _filterType fun setImageFormat(imageFormat: ImageFormat) { _imageInfo.value = _imageInfo.value.copy(imageFormat = imageFormat) updatePreview() registerChanges() } fun setBasicFilter(uris: List?) { _filterType.update { it as? Screen.Filter.Type.Basic ?: Screen.Filter.Type.Basic(uris) } _basicFilterState.update { it.copy( uris = uris, selectedUri = uris?.firstOrNull()?.also(::updateSelectedUri) ) } } fun updateUrisSilently(removedUri: Uri) { componentScope.launch { val state = _basicFilterState.value if (state.selectedUri == removedUri) { val index = state.uris?.indexOf(removedUri) ?: -1 if (index == 0) { state.uris?.getOrNull(1)?.let { _basicFilterState.update { f -> f.copy(selectedUri = it) } updateSelectedUri(it) } } else { state.uris?.getOrNull(index - 1)?.let { _basicFilterState.update { f -> f.copy(selectedUri = it) } updateSelectedUri(it) } } } _basicFilterState.update { it.copy( uris = it.uris?.toMutableList()?.apply { remove(removedUri) } ) } } } fun setKeepExif(boolean: Boolean) { _keepExif.value = boolean registerChanges() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 _left.value = _basicFilterState.value.uris?.size ?: 1 _basicFilterState.value.uris?.forEach { uri -> runSuspendCatching { imageGetter.getImageWithTransformations( uri = uri.toString(), transformations = _basicFilterState.value.filters.map { filterProvider.filterToTransformation(it) } )?.image }.getOrNull()?.let { bitmap -> val localBitmap = bitmap results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = imageInfo.copy( width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun updateSelectedUri( uri: Uri, onFailure: (Throwable) -> Unit = {} ) { runCatching { componentScope.launch { _isImageLoading.update { true } val req = imageGetter.getImage(uri = uri.toString()) val tempBitmap = req?.image val size = tempBitmap?.let { it.width to it.height } _bitmap.update { imageScaler.scaleUntilCanShow(tempBitmap) } _imageInfo.update { it.copy( width = size?.first ?: 0, height = size?.second ?: 0, imageFormat = req?.imageInfo?.imageFormat ?: ImageFormat.Default ) } updatePreview() _basicFilterState.update { it.copy(selectedUri = uri) } _isImageLoading.update { false } } }.onFailure(onFailure) } private fun updateCanSave() { _canSave.value = _bitmap.value != null && ((_filterType.value is Screen.Filter.Type.Basic && _basicFilterState.value.filters.isNotEmpty()) || (_filterType.value is Screen.Filter.Type.Masking && _maskingFilterState.value.masks.isNotEmpty())) registerChanges() } private var filterJob: Job? by smartJob() fun updateFilter( value: T, index: Int ) { val list = _basicFilterState.value.filters.toMutableList() runCatching { list[index] = list[index].copy(value) _basicFilterState.update { it.copy(filters = list) } }.onFailure { throwable -> AppToastHost.showFailureToast(throwable) list[index] = list[index].newInstance() _basicFilterState.update { it.copy(filters = list) } } updateCanSave() updatePreview() } fun updateFiltersOrder(value: List>) { _basicFilterState.update { it.copy(filters = value) } filterJob = null updateCanSave() updatePreview() } fun addFilterNewInstance(filter: UiFilter<*>) { addFilter(filter.newInstance()) } fun addFilter(filter: UiFilter<*>) { _basicFilterState.update { it.copy(filters = it.filters + filter) } updateCanSave() filterJob = null updatePreview() } fun removeFilterAtIndex(index: Int) { _basicFilterState.update { it.copy( filters = it.filters.toMutableList().apply { removeAt(index) } ) } updateCanSave() filterJob = null updatePreview() } fun canShow(): Boolean = bitmap?.let { imagePreviewCreator.canShow(it) } == true fun performSharing() { savingJob = trackProgress { _isSaving.value = true _done.value = 0 when (filterType) { is Screen.Filter.Type.Basic -> { _left.value = _basicFilterState.value.uris?.size ?: 1 shareProvider.shareImages( uris = _basicFilterState.value.uris?.map { it.toString() } ?: emptyList(), imageLoader = { uri -> imageGetter.getImageWithTransformations( uri = uri, transformations = _basicFilterState.value.filters.map { filterProvider.filterToTransformation(it) } )?.let { it.image to it.imageInfo } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _isSaving.value = false _done.value = 0 } else { _done.value = it } updateProgress( done = done, total = left ) } ) } is Screen.Filter.Type.Masking -> { _left.value = maskingFilterState.masks.size maskingFilterState.uri?.toString()?.let { imageGetter.getImage(uri = it) }?.let { maskingFilterState.masks.fold( initial = it.image, operation = { bmp, mask -> bmp?.let { filterMaskApplier.filterByMask( filterMask = mask, image = bmp ) }?.also { _done.value++ updateProgress( done = done, total = left ) } } )?.let { bitmap -> shareProvider.shareImage( image = bitmap, imageInfo = imageInfo.copy( width = bitmap.width, height = bitmap.height ), onComplete = { _isSaving.value = false AppToastHost.showConfetti() } ) } } } null -> Unit } } } fun setQuality(quality: Quality) { _imageInfo.update(onValueChanged = ::updatePreview) { it.copy(quality = quality) } } private fun updatePreview() { _bitmap.value?.let { bitmap -> filterJob = componentScope.launch { delay(200L) _isImageLoading.value = true when (filterType) { is Screen.Filter.Type.Basic -> { _previewBitmap.value = imagePreviewCreator.createPreview( image = bitmap, imageInfo = imageInfo, transformations = _basicFilterState.value.filters.map { filterProvider.filterToTransformation(it) }, onGetByteCount = { size -> _imageInfo.update { it.copy(sizeInBytes = size) } } ) } is Screen.Filter.Type.Masking -> { _previewBitmap.value = filterMaskApplier.filterByMasks( filterMasks = _maskingFilterState.value.masks, image = bitmap )?.let { bmp -> imagePreviewCreator.createPreview( image = bmp, imageInfo = imageInfo, onGetByteCount = { size -> _imageInfo.update { it.copy(sizeInBytes = size) } } ) } } null -> Unit } _isImageLoading.value = false } } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun setType(type: Screen.Filter.Type) { when (type) { is Screen.Filter.Type.Basic -> setBasicFilter(type.uris) is Screen.Filter.Type.Masking -> setMaskFilter(type.uri) } } fun setMaskFilter(uri: Uri?) { _filterType.update { it as? Screen.Filter.Type.Masking ?: Screen.Filter.Type.Masking(uri) } uri?.let { updateSelectedUri(it) } _maskingFilterState.value = MaskingFilterState(uri) updatePreview() updateCanSave() } fun clearType() { _filterType.update { null } _basicFilterState.update { BasicFilterState() } _maskingFilterState.update { MaskingFilterState() } _bitmap.value = null _previewBitmap.value = null _imageInfo.update { ImageInfo() } updateCanSave() registerChangesCleared() } fun saveMaskedBitmap( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 _left.value = maskingFilterState.masks.size maskingFilterState.uri?.toString()?.let { imageGetter.getImage(uri = it) }?.let { maskingFilterState.masks.fold( initial = it.image, operation = { bmp, mask -> bmp?.let { filterMaskApplier.filterByMask( filterMask = mask, image = bmp ) }?.also { _done.value++ updateProgress( done = done, total = left ) } } )?.let { localBitmap -> parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo.copy( width = localBitmap.width, height = localBitmap.height ), originalUri = maskingFilterState.uri.toString(), sequenceNumber = null, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = imageInfo.copy( width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } } _isSaving.value = false } } fun updateMasksOrder(uiFilterMasks: List) { _maskingFilterState.update { it.copy(masks = uiFilterMasks) } updatePreview() updateCanSave() } fun updateMask( value: UiFilterMask, index: Int ) { runCatching { _maskingFilterState.update { it.copy( masks = it.masks.toMutableList().apply { this[index] = value } ) } updatePreview() updateCanSave() }.onFailure(AppToastHost::showFailureToast) } fun removeMaskAtIndex(index: Int) { _maskingFilterState.update { it.copy( masks = it.masks.toMutableList().apply { removeAt(index) } ) } updatePreview() updateCanSave() } fun addMask(value: UiFilterMask) { _maskingFilterState.update { it.copy( masks = it.masks + value ) } updatePreview() updateCanSave() } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true when (filterType) { is Screen.Filter.Type.Basic -> { imageGetter.getImageWithTransformations( uri = _basicFilterState.value.selectedUri.toString(), transformations = _basicFilterState.value.filters.map { filterProvider.filterToTransformation(it) } )?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo )?.let { onComplete(it.toUri()) } } } is Screen.Filter.Type.Masking -> { _left.value = maskingFilterState.masks.size maskingFilterState.uri?.toString()?.let { imageGetter.getImage(uri = it) }?.let { imageData -> maskingFilterState.masks.fold( initial = imageData.image, operation = { bmp, mask -> bmp?.let { filterMaskApplier.filterByMask( filterMask = mask, image = bmp ) }?.also { _done.value++ updateProgress( done = done, total = left ) } } )?.let { bitmap -> shareProvider.cacheImage( image = bitmap, imageInfo = imageInfo.copy( width = bitmap.width, height = bitmap.height ) )?.let { onComplete(it.toUri()) } } } } null -> Unit } _isSaving.value = false } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true val list = mutableListOf() when (filterType) { is Screen.Filter.Type.Basic -> { _done.value = 0 _left.value = basicFilterState.uris?.size ?: 0 basicFilterState.uris?.forEach { uri -> imageGetter.getImageWithTransformations( uri = uri.toString(), transformations = _basicFilterState.value.filters.map { filterProvider.filterToTransformation(it) } )?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo )?.let { list.add(it.toUri()) } } _done.value++ updateProgress( done = done, total = left ) } } is Screen.Filter.Type.Masking -> { _done.value = 0 _left.value = maskingFilterState.masks.size maskingFilterState.uri?.toString()?.let { imageGetter.getImage(uri = it) }?.let { imageData -> maskingFilterState.masks.fold( initial = imageData.image, operation = { bmp, mask -> bmp?.let { filterMaskApplier.filterByMask( filterMask = mask, image = bmp ) }?.also { _done.value++ updateProgress( done = done, total = left ) } } )?.let { bitmap -> shareProvider.cacheImage( image = bitmap, imageInfo = imageInfo.copy( width = bitmap.width, height = bitmap.height ) )?.let { list.add(it.toUri()) } } } } null -> Unit } onComplete(list) _isSaving.value = false } } fun selectLeftUri() { basicFilterState.uris ?.indexOf(basicFilterState.selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { basicFilterState.uris?.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { basicFilterState.uris ?.indexOf(basicFilterState.selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { basicFilterState.uris?.rightFrom(it) } ?.let(::updateSelectedUri) } fun getFormatForFilenameSelection(): ImageFormat? = when { basicFilterState.uris?.size == 1 -> imageInfo.imageFormat maskingFilterState.uri != null -> imageInfo.imageFormat else -> null } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialType: Screen.Filter.Type?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): FiltersComponent } } ================================================ FILE: feature/format-conversion/.gitignore ================================================ /build ================================================ FILE: feature/format-conversion/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.format_conversion" dependencies { implementation(projects.feature.compare) } ================================================ FILE: feature/format-conversion/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/FormatConversionContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.format_conversion.presentation import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.CompareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.core.utils.fileSize import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareSheet import com.t8rin.imagetoolbox.feature.format_conversion.presentation.screenLogic.FormatConversionComponent @Composable fun FormatConversionContent( component: FormatConversionComponent ) { AutoContentBasedColors(component.bitmap) val imagePicker = rememberImagePicker { uris: List -> component.setUris( uris = uris ) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } var showZoomSheet by rememberSaveable { mutableStateOf(false) } var showCompareSheet by rememberSaveable { mutableStateOf(false) } CompareSheet( data = component.bitmap to component.previewBitmap, visible = showCompareSheet, onDismiss = { showCompareSheet = false } ) ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.format_conversion), input = component.bitmap, isLoading = component.isImageLoading, size = component.imageInfo.sizeInBytes.toLong(), originalSize = component.selectedUri?.fileSize() ) }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.bitmap != null, onShare = component::shareBitmaps, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, imagePreview = { ImageContainer( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), imageInside = isPortrait, showOriginal = false, previewBitmap = component.previewBitmap, originalBitmap = component.bitmap, isLoading = component.isImageLoading, shouldShowPreview = component.shouldShowPreview ) }, controls = { val imageInfo = component.imageInfo ImageCounter( imageCount = component.uris?.size?.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) Spacer(Modifier.size(8.dp)) AnimatedVisibility( visible = component.uris?.size != 1, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { SaveExifWidget( imageFormat = component.imageInfo.imageFormat, checked = component.keepExif, onCheckedChange = component::setKeepExif ) Spacer(Modifier.size(8.dp)) } } Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = imageInfo.quality ) if (imageInfo.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = imageInfo.imageFormat, quality = imageInfo.quality, onQualityChange = component::setQuality ) }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, topAppBarPersistentActions = { if (component.bitmap == null) TopAppBarEmoji() CompareButton( onClick = { showCompareSheet = true }, visible = component.previewBitmap != null && component.bitmap != null && component.shouldShowPreview ) ZoomButton( onClick = { showZoomSheet = true }, visible = component.previewBitmap != null && component.shouldShowPreview ) }, canShowScreenData = component.bitmap != null, forceImagePreviewToMax = false, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } } ) val transformations by remember(component.imageInfo) { derivedStateOf(component::getConversionTransformation) } PickImageFromUrisSheet( transformations = transformations, visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, uris = component.uris, selectedUri = component.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = component::updateUrisSilently, columns = if (isPortrait) 2 else 4, ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris?.size ?: 1, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/format-conversion/src/main/java/com/t8rin/imagetoolbox/feature/format_conversion/presentation/screenLogic/FormatConversionComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.format_conversion.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageData import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.transformation.ImageInfoTransformation import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class FormatConversionComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageTransformer: ImageTransformer, private val imagePreviewCreator: ImagePreviewCreator, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val shareProvider: ImageShareProvider, private val imageInfoTransformationFactory: ImageInfoTransformation.Factory, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) } } private val _originalSize: MutableState = mutableStateOf(null) private val _uris = mutableStateOf?>(null) val uris by _uris private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _keepExif = mutableStateOf(false) val keepExif by _keepExif private val _imageInfo: MutableState = mutableStateOf(ImageInfo()) val imageInfo: ImageInfo by _imageInfo private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _shouldShowPreview: MutableState = mutableStateOf(true) val shouldShowPreview by _shouldShowPreview private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _selectedUri: MutableState = mutableStateOf(null) val selectedUri by _selectedUri private var job: Job? by smartJob { _isImageLoading.update { false } } fun setUris( uris: List? ) { _uris.value = null _uris.value = uris _selectedUri.value = uris?.firstOrNull() uris?.firstOrNull()?.let { uri -> _imageInfo.update { it.copy(originalUri = uri.toString()) } imageGetter.getImageAsync( uri = uri.toString(), originalSize = false, onGetImage = ::setImageData, onFailure = AppToastHost::showFailureToast ) } } fun updateUrisSilently( removedUri: Uri ) { componentScope.launch { _uris.value = uris if (_selectedUri.value == removedUri) { val index = uris?.indexOf(removedUri) ?: -1 if (index == 0) { uris?.getOrNull(1)?.let { updateSelectedUri(it) } } else { uris?.getOrNull(index - 1)?.let { updateSelectedUri(it) } } } val u = _uris.value?.toMutableList()?.apply { remove(removedUri) } _uris.value = u registerChanges() } } private suspend fun checkBitmapAndUpdate() { _bitmap.value?.let { bmp -> val preview = updatePreview(bmp) _previewBitmap.value = null _shouldShowPreview.value = imagePreviewCreator.canShow(preview) if (shouldShowPreview) _previewBitmap.value = preview } } private suspend fun updatePreview( bitmap: Bitmap ): Bitmap? = imagePreviewCreator.createPreview( image = bitmap, imageInfo = imageInfo, onGetByteCount = { size -> _imageInfo.update { it.copy(sizeInBytes = size) } } ) private fun resetValues() { _imageInfo.value = ImageInfo( width = _originalSize.value?.width ?: 0, height = _originalSize.value?.height ?: 0, imageFormat = imageInfo.imageFormat, originalUri = selectedUri?.toString() ) debouncedImageCalculation { checkBitmapAndUpdate() } } private fun setImageData(imageData: ImageData) { job = componentScope.launch { _isImageLoading.update { true } val bitmap = imageData.image val size = bitmap.width to bitmap.height _originalSize.update { size.run { IntegerSize(width = first, height = second) } } _bitmap.update { imageScaler.scaleUntilCanShow(bitmap) } resetValues() _imageInfo.update { imageData.imageInfo.copy( width = size.first, height = size.second ) } checkBitmapAndUpdate() _isImageLoading.update { false } } } fun setQuality(quality: Quality) { if (_imageInfo.value.quality != quality) { _imageInfo.value = _imageInfo.value.copy(quality = quality) debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } } fun setImageFormat(imageFormat: ImageFormat) { if (_imageInfo.value.imageFormat != imageFormat) { _imageInfo.value = _imageInfo.value.copy(imageFormat = imageFormat) debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } } fun setKeepExif(boolean: Boolean) { _keepExif.value = boolean registerChanges() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 uris?.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString())?.image }.getOrNull()?.let { bitmap -> imageInfo.copy( originalUri = uri.toString() ).let { imageTransformer.applyPresetBy( image = bitmap, preset = Preset.Original, currentInfo = it ) }.let { imageInfo -> results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = bitmap, imageInfo = imageInfo ), canSkipIfLarger = true ), keepOriginalMetadata = keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun updateSelectedUri( uri: Uri ) { _selectedUri.value = uri componentScope.launch { runSuspendCatching { _isImageLoading.update { true } val bitmap = imageGetter.getImage( uri = uri.toString(), originalSize = true )?.image val size = bitmap?.let { it.width to it.height } _originalSize.value = size?.run { IntegerSize(width = first, height = second) } _bitmap.value = imageScaler.scaleUntilCanShow(bitmap) _imageInfo.value = _imageInfo.value.copy( width = size?.first ?: 0, height = size?.second ?: 0, originalUri = uri.toString() ) _imageInfo.value = imageTransformer.applyPresetBy( image = _bitmap.value, preset = Preset.Original, currentInfo = _imageInfo.value ) checkBitmapAndUpdate() _isImageLoading.update { false } }.onFailure { _isImageLoading.update { false } AppToastHost.showFailureToast(it) } } } fun shareBitmaps() { savingJob = trackProgress { _isSaving.value = true shareProvider.shareImages( uris = uris?.map { it.toString() } ?: emptyList(), imageLoader = { uri -> imageGetter.getImage(uri)?.image?.let { bmp -> bmp to imageInfo.copy( originalUri = uri ).let { imageTransformer.applyPresetBy( image = bitmap, preset = Preset.Original, currentInfo = it ) } } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _isSaving.value = false _done.value = 0 } else { _done.value = it } updateProgress( done = done, total = uris.orEmpty().size ) } ) } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true imageGetter.getImage(selectedUri.toString())?.image?.let { bmp -> bmp to imageInfo.copy( originalUri = selectedUri.toString() ).let { imageTransformer.applyPresetBy( image = bitmap, preset = Preset.Original, currentInfo = it ) } }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = selectedUri.toString()) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 val list = mutableListOf() uris?.forEach { imageGetter.getImage(it.toString())?.image?.let { bmp -> bmp to imageInfo.copy( originalUri = it.toString() ).let { info -> imageTransformer.applyPresetBy( image = bitmap, preset = Preset.Original, currentInfo = info ) } }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = it.toString()) )?.let { uri -> list.add(uri.toUri()) } } _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } onComplete(list) _isSaving.value = false } } fun selectLeftUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.rightFrom(it) } ?.let(::updateSelectedUri) } fun getFormatForFilenameSelection(): ImageFormat? = if (uris?.size == 1) imageInfo.imageFormat else null fun getConversionTransformation() = listOf( imageInfoTransformationFactory( imageInfo = imageInfo, preset = Preset.Original ) ) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): FormatConversionComponent } } ================================================ FILE: feature/gif-tools/.gitignore ================================================ /build ================================================ FILE: feature/gif-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.gif_tools" dependencies { implementation(libs.toolbox.awebp) implementation(libs.toolbox.gifConverter) implementation(libs.jxl.coder) } ================================================ FILE: feature/gif-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/data/AndroidGifConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.data import android.content.Context import android.graphics.Bitmap import android.graphics.PorterDuff import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.net.toUri import com.awxkee.jxlcoder.JxlCoder import com.awxkee.jxlcoder.JxlDecodingSpeed import com.awxkee.jxlcoder.JxlEffort import com.t8rin.awebp.encoder.AnimatedWebpEncoder import com.t8rin.gif_converter.GifDecoder import com.t8rin.gif_converter.GifEncoder import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.data.utils.toSoftware import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifConverter import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifParams import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.cancel import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import java.io.InputStream import javax.inject.Inject internal class AndroidGifConverter @Inject constructor( private val imageGetter: ImageGetter, private val imageShareProvider: ImageShareProvider, @ApplicationContext private val context: Context, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, GifConverter { override fun extractFramesFromGif( gifUri: String, imageFormat: ImageFormat, imageFrames: ImageFrames, quality: Quality, onGetFramesCount: (frames: Int) -> Unit ): Flow = extractFramesFromGif( gifUri = gifUri, imageFrames = imageFrames, onGetFramesCount = onGetFramesCount ).mapNotNull { (frame) -> imageShareProvider.cacheImage( image = frame, imageInfo = ImageInfo( width = frame.width, height = frame.height, imageFormat = imageFormat, quality = quality ) ) } override suspend fun createGifFromImageUris( imageUris: List, params: GifParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): ByteArray? = withContext(defaultDispatcher) { val out = ByteArrayOutputStream() val encoder = GifEncoder().apply { params.size?.let { size -> if (size.width <= 0 || size.height <= 0) { onFailure(IllegalArgumentException("Width and height must be > 0")) return@withContext null } setSize( size.width, size.height ) } setRepeat(params.repeatCount) setQuality( (100 - ((params.quality.qualityValue - 1) * (100 / 19f))).toInt() ) setFrameRate(params.fps.toFloat()) setDispose( if (params.dontStack) 2 else 0 ) setTransparent(Color.Transparent.toArgb()) start(out) } imageUris.forEachIndexed { index, uri -> imageGetter.getImage( data = uri, size = params.size )?.apply { setHasAlpha(true) }?.let { frame -> encoder.addFrame(frame) if (params.crossfadeCount > 1) { val list = mutableSetOf(0, 255) for (a in 0..255 step (255 / params.crossfadeCount)) { list.add(a) } val alphas = list.sortedDescending() imageGetter.getImage( data = imageUris.getOrNull(index + 1) ?: Unit, size = params.size )?.let { next -> alphas.forEach { alpha -> encoder.addFrame( next.overlay( frame.copy(frame.safeConfig, true).applyCanvas { drawColor( Color.Black.copy(alpha / 255f).toArgb(), PorterDuff.Mode.DST_IN ) } ) ) } } } } onProgress() } encoder.finish() out.toByteArray() } private fun Bitmap.overlay(overlay: Bitmap): Bitmap { return createBitmap(width, height, safeConfig.toSoftware()).applyCanvas { drawBitmap(this@overlay) drawBitmap(overlay.toSoftware()) } } override suspend fun convertGifToJxl( gifUris: List, quality: Quality.Jxl, onProgress: suspend (String, ByteArray) -> Unit ) = withContext(defaultDispatcher) { gifUris.forEach { uri -> uri.bytes?.let { gifData -> runSuspendCatching { JxlCoder.Convenience.gif2JXL( gifData = gifData, quality = quality.qualityValue.coerceIn(0, 99), effort = JxlEffort.entries.first { it.ordinal == quality.effort }, decodingSpeed = JxlDecodingSpeed.entries.first { it.ordinal == quality.speed } ).let { onProgress(uri, it) } } } } } override suspend fun convertGifToWebp( gifUris: List, quality: Quality.Base, onProgress: suspend (String, ByteArray) -> Unit ) = withContext(defaultDispatcher) { gifUris.forEach { uri -> runSuspendCatching { val encoder = AnimatedWebpEncoder( quality = quality.qualityValue, loopCount = 1, backgroundColor = Color.Transparent.toArgb() ) extractFramesFromGif( gifUri = uri, imageFrames = ImageFrames.All, onGetFramesCount = {} ).collect { (frame, duration) -> encoder.addFrame( bitmap = frame.copy(frame.safeConfig, false), duration = duration ) } onProgress(uri, encoder.encode()) } } } private fun extractFramesFromGif( gifUri: String, imageFrames: ImageFrames, onGetFramesCount: (frames: Int) -> Unit ): Flow> = flow { val bytes = gifUri.bytes val decoder = GifDecoder().apply { read(bytes) } onGetFramesCount(decoder.frameCount) val indexes = imageFrames .getFramePositions(decoder.frameCount) .map { it - 1 } repeat(decoder.frameCount) { pos -> if (!currentCoroutineContext().isActive) { currentCoroutineContext().cancel(null) return@repeat } decoder.advance() decoder.nextFrame?.let { frame -> frame to decoder.nextDelay }?.takeIf { pos in indexes }?.let { emit(it) } } } private val String.inputStream: InputStream? get() = context .contentResolver .openInputStream(toUri()) private val String.bytes: ByteArray? get() = inputStream?.use { it.readBytes() } } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/di/GifToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.di import com.t8rin.imagetoolbox.feature.gif_tools.data.AndroidGifConverter import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifConverter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface GifToolsModule { @Binds @Singleton fun provideConverter( converter: AndroidGifConverter ): GifConverter } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.Quality import kotlinx.coroutines.flow.Flow interface GifConverter { fun extractFramesFromGif( gifUri: String, imageFormat: ImageFormat, imageFrames: ImageFrames, quality: Quality, onGetFramesCount: (frames: Int) -> Unit = {} ): Flow suspend fun createGifFromImageUris( imageUris: List, params: GifParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): ByteArray? suspend fun convertGifToJxl( gifUris: List, quality: Quality.Jxl, onProgress: suspend (String, ByteArray) -> Unit ) suspend fun convertGifToWebp( gifUris: List, quality: Quality.Base, onProgress: suspend (String, ByteArray) -> Unit ) } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/domain/GifParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize data class GifParams( val size: IntegerSize?, val repeatCount: Int, val fps: Int, val quality: Quality, val dontStack: Boolean, val crossfadeCount: Int ) { companion object { val Default by lazy { GifParams( size = null, repeatCount = 0, fps = 12, quality = Quality.Base(50), dontStack = false, crossfadeCount = 0 ) } } } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/GifToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.presentation import android.net.Uri import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Gif import androidx.compose.material.icons.rounded.Gif import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.core.utils.isGif import com.t8rin.imagetoolbox.feature.gif_tools.presentation.components.GifToolsControls import com.t8rin.imagetoolbox.feature.gif_tools.presentation.components.GifToolsImagePreview import com.t8rin.imagetoolbox.feature.gif_tools.presentation.components.GifToolsNoDataControls import com.t8rin.imagetoolbox.feature.gif_tools.presentation.components.GifToolsTopAppBarActions import com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic.GifToolsComponent @Composable fun GifToolsContent( component: GifToolsComponent ) { val imagePicker = rememberImagePicker(onSuccess = component::setImageUris) val pickSingleGifLauncher = rememberFilePicker( mimeType = MimeType.Gif, onSuccess = { uri: Uri -> if (uri.isGif()) { component.setGifUri(uri) } else { AppToastHost.showToast( message = getString(R.string.select_gif_image_to_start), icon = Icons.Rounded.Gif ) } } ) val pickMultipleGifToJxlLauncher = rememberFilePicker( mimeType = MimeType.Gif, onSuccess = { list: List -> list.filter { it.isGif() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_gif_image_to_start), icon = Icons.Filled.Gif ) } else { component.setType( Screen.GifTools.Type.GifToJxl(uris) ) } } } ) val pickMultipleGifToWebpLauncher = rememberFilePicker( mimeType = MimeType.Gif, onSuccess = { list: List -> list.filter { it.isGif() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_gif_image_to_start), icon = Icons.Filled.Gif ) } else { component.setType( Screen.GifTools.Type.GifToWebp(uris) ) } } } ) val addGifsToJxlLauncher = rememberFilePicker( mimeType = MimeType.Gif, onSuccess = { list: List -> list.filter { it.isGif() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_gif_image_to_start), icon = Icons.Filled.Gif ) } else { component.setType( Screen.GifTools.Type.GifToJxl( (component.type as? Screen.GifTools.Type.GifToJxl)?.gifUris?.plus(uris) ?.distinct() ) ) } } } ) val addGifsToWebpLauncher = rememberFilePicker( mimeType = MimeType.Gif, onSuccess = { list: List -> list.filter { it.isGif() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_gif_image_to_start), icon = Icons.Filled.Gif ) } else { component.setType( Screen.GifTools.Type.GifToWebp( (component.type as? Screen.GifTools.Type.GifToWebp)?.gifUris?.plus(uris) ?.distinct() ) ) } } } ) val saveGifLauncher = rememberFileCreator( mimeType = MimeType.Gif, onSuccess = component::saveGifTo ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val isPortrait by isPortraitOrientationAsState() AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = when (val type = component.type) { null -> stringResource(R.string.gif_tools) else -> stringResource(type.title) }, input = component.type, isLoading = component.isLoading, size = null ) }, onGoBack = onBack, topAppBarPersistentActions = { GifToolsTopAppBarActions(component) }, actions = { ShareButton( enabled = !component.isLoading && component.type != null, onShare = component::performSharing ) }, imagePreview = { GifToolsImagePreview( component = component, onAddGifsToJxl = addGifsToJxlLauncher::pickFile, onAddGifsToWebp = addGifsToWebpLauncher::pickFile ) }, placeImagePreview = component.type !is Screen.GifTools.Type.ImageToGif, showImagePreviewAsStickyHeader = false, autoClearFocus = false, controls = { GifToolsControls(component) }, contentPadding = animateDpAsState( if (component.type == null) 12.dp else 20.dp ).value, buttons = { actions -> val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it, onGifSaveResult = saveGifLauncher::make ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.type == null, onSecondaryButtonClick = { when (component.type) { is Screen.GifTools.Type.GifToImage -> pickSingleGifLauncher.pickFile() is Screen.GifTools.Type.GifToJxl -> pickMultipleGifToJxlLauncher.pickFile() is Screen.GifTools.Type.GifToWebp -> pickMultipleGifToWebpLauncher.pickFile() else -> imagePicker.pickImage() } }, isPrimaryButtonVisible = component.canSave, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { if (component.type is Screen.GifTools.Type.ImageToGif) { saveBitmaps(null) } else showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, showNullDataButtonAsContainer = true, onSecondaryButtonLongClick = if (component.type is Screen.GifTools.Type.ImageToGif || component.type == null) { { showOneTimeImagePickingDialog = true } } else null ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, insetsForNoData = WindowInsets(0), noDataControls = { GifToolsNoDataControls( onClickType = { type -> when (type) { is Screen.GifTools.Type.GifToImage -> pickSingleGifLauncher.pickFile() is Screen.GifTools.Type.GifToJxl -> pickMultipleGifToJxlLauncher.pickFile() is Screen.GifTools.Type.GifToWebp -> pickMultipleGifToWebpLauncher.pickFile() is Screen.GifTools.Type.ImageToGif -> imagePicker.pickImage() } } ) }, canShowScreenData = component.type != null ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = component::resetState, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } private val GifToolsComponent.canSave: Boolean get() = (gifFrames == ImageFrames.All) .or(type is Screen.GifTools.Type.ImageToGif) .or((gifFrames as? ImageFrames.ManualSelection)?.framePositions?.isNotEmpty() == true) ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FilterFrames import androidx.compose.material.icons.outlined.Opacity import androidx.compose.material.icons.outlined.PhotoSizeSelectLarge import androidx.compose.material.icons.outlined.RepeatOne import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Counter import com.t8rin.imagetoolbox.core.resources.icons.Stacks import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifParams import kotlinx.collections.immutable.persistentMapOf import kotlin.math.roundToInt @Composable fun GifParamsSelector( value: GifParams, onValueChange: (GifParams) -> Unit ) { Column { val size = value.size ?: IntegerSize.Undefined AnimatedVisibility(size.isDefined()) { ResizeImageField( imageInfo = ImageInfo(size.width, size.height), originalSize = null, onWidthChange = { onValueChange( value.copy( size = size.copy(width = it) ) ) }, onHeightChange = { onValueChange( value.copy( size = size.copy(height = it) ) ) } ) } Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.use_size_of_first_frame), subtitle = stringResource(id = R.string.use_size_of_first_frame_sub), checked = value.size == null, onClick = { onValueChange( value.copy(size = if (it) null else IntegerSize(1000, 1000)) ) }, startIcon = Icons.Outlined.PhotoSizeSelectLarge, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.dont_stack_frames), subtitle = stringResource(id = R.string.dont_stack_frames_sub), checked = value.dontStack, onClick = { onValueChange( value.copy(dontStack = it) ) }, startIcon = Icons.Outlined.Stacks, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) QualitySelector( imageFormat = ImageFormat.Jpeg, quality = value.quality, onQualityChange = { onValueChange( value.copy( quality = it ) ) } ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.repeatCount, icon = Icons.Outlined.RepeatOne, title = stringResource(id = R.string.repeat_count), valueRange = 0f..10f, steps = 10, valuesPreviewMapping = remember { persistentMapOf( 0f to "∞" ) }, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( repeatCount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.fps, icon = Icons.Outlined.FilterFrames, title = stringResource(id = R.string.fps), valueRange = 1f..120f, steps = 119, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( fps = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.crossfade), subtitle = stringResource(id = R.string.crossfade_sub), checked = value.crossfadeCount > 1, onClick = { onValueChange( value.copy( crossfadeCount = if (it) 2 else 0 ) ) }, startIcon = Icons.Outlined.Opacity, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) AnimatedVisibility(value.crossfadeCount > 1) { EnhancedSliderItem( value = value.crossfadeCount, icon = Icons.Outlined.Counter, title = stringResource(id = R.string.crossfade_count), valueRange = 2f..20f, steps = 18, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( crossfadeCount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge, modifier = Modifier.padding(top = 8.dp) ) } } } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.presentation.components import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic.GifToolsComponent @Composable internal fun GifToolsControls(component: GifToolsComponent) { when (val type = component.type) { is Screen.GifTools.Type.GifToImage -> { Spacer(modifier = Modifier.height(16.dp)) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, quality = component.params.quality ) Spacer(modifier = Modifier.height(8.dp)) QualitySelector( imageFormat = component.imageFormat, quality = component.params.quality, onQualityChange = component::setQuality ) Spacer(modifier = Modifier.height(16.dp)) } is Screen.GifTools.Type.ImageToGif -> { val addImagesToGifPicker = rememberImagePicker(onSuccess = component::addImageToUris) Spacer(modifier = Modifier.height(16.dp)) ImageReorderCarousel( images = type.imageUris, onReorder = component::reorderImageUris, onNeedToAddImage = addImagesToGifPicker::pickImage, onNeedToRemoveImageAt = component::removeImageAt, onNavigate = component.onNavigate ) Spacer(modifier = Modifier.height(8.dp)) GifParamsSelector( value = component.params, onValueChange = component::updateParams ) Spacer(modifier = Modifier.height(16.dp)) } is Screen.GifTools.Type.GifToJxl -> { QualitySelector( imageFormat = ImageFormat.Jxl.Lossy, quality = component.jxlQuality, onQualityChange = component::setJxlQuality ) } is Screen.GifTools.Type.GifToWebp -> { QualitySelector( imageFormat = ImageFormat.Jpg, quality = component.webpQuality, onQualityChange = component::setWebpQuality ) } null -> Unit } } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsImagePreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.ImagesPreviewWithSelection import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.image.urisPreview import com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic.GifToolsComponent @Composable internal fun GifToolsImagePreview( component: GifToolsComponent, onAddGifsToJxl: () -> Unit, onAddGifsToWebp: () -> Unit, ) { val isPortrait by isPortraitOrientationAsState() AnimatedContent( targetState = component.isLoading to component.type ) { (loading, type) -> Box( contentAlignment = Alignment.Center, modifier = if (loading) { Modifier.padding(32.dp) } else Modifier ) { if (loading || type == null) { EnhancedLoadingIndicator() } else { when (type) { is Screen.GifTools.Type.GifToImage -> { ImagesPreviewWithSelection( imageUris = component.convertedImageUris, imageFrames = component.gifFrames, onFrameSelectionChange = component::updateGifFrames, isPortrait = isPortrait, isLoadingImages = component.isLoadingGifImages ) } is Screen.GifTools.Type.GifToJxl -> { UrisPreview( modifier = Modifier.urisPreview(), uris = type.gifUris ?: emptyList(), isPortrait = true, onRemoveUri = { component.setType( Screen.GifTools.Type.GifToJxl(type.gifUris?.minus(it)) ) }, onAddUris = onAddGifsToJxl ) } is Screen.GifTools.Type.GifToWebp -> { UrisPreview( modifier = Modifier.urisPreview(), uris = type.gifUris ?: emptyList(), isPortrait = true, onRemoveUri = { component.setType( Screen.GifTools.Type.GifToWebp(type.gifUris?.minus(it)) ) }, onAddUris = onAddGifsToWebp ) } is Screen.GifTools.Type.ImageToGif -> Unit } } } } } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsNoDataControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable internal fun GifToolsNoDataControls( onClickType: (Screen.GifTools.Type) -> Unit ) { val isPortrait by isPortraitOrientationAsState() val types = remember { Screen.GifTools.Type.entries } val preference1 = @Composable { PreferenceItem( title = stringResource(types[0].title), subtitle = stringResource(types[0].subtitle), startIcon = types[0].icon, modifier = Modifier.fillMaxWidth(), onClick = { onClickType(types[0]) } ) } val preference2 = @Composable { PreferenceItem( title = stringResource(types[1].title), subtitle = stringResource(types[1].subtitle), startIcon = types[1].icon, modifier = Modifier.fillMaxWidth(), onClick = { onClickType(types[1]) } ) } val preference3 = @Composable { PreferenceItem( title = stringResource(types[2].title), subtitle = stringResource(types[2].subtitle), startIcon = types[2].icon, modifier = Modifier.fillMaxWidth(), onClick = { onClickType(types[2]) } ) } val preference4 = @Composable { PreferenceItem( title = stringResource(types[3].title), subtitle = stringResource(types[3].subtitle), startIcon = types[3].icon, modifier = Modifier.fillMaxWidth(), onClick = { onClickType(types[3]) } ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() Spacer(modifier = Modifier.height(8.dp)) preference3() Spacer(modifier = Modifier.height(8.dp)) preference4() } } else { val direction = LocalLayoutDirection.current val cutout = WindowInsets.displayCutout.asPaddingValues().let { PaddingValues( start = it.calculateStartPadding(direction), end = it.calculateEndPadding(direction) ) } Column { Row( modifier = Modifier.padding(cutout) ) { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.padding(cutout) ) { preference3.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference4.withModifier(modifier = Modifier.weight(1f)) } } } } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/components/GifToolsTopAppBarActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gif_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic.GifToolsComponent @Composable internal fun RowScope.GifToolsTopAppBarActions(component: GifToolsComponent) { if (component.type == null) TopAppBarEmoji() val pagesSize by remember(component.gifFrames, component.convertedImageUris) { derivedStateOf { component.gifFrames.getFramePositions(component.convertedImageUris.size).size } } val isGifToImage = component.type is Screen.GifTools.Type.GifToImage AnimatedVisibility( visible = isGifToImage && pagesSize != component.convertedImageUris.size, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = component::selectAllConvertedImages ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = isGifToImage && pagesSize != 0 ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { pagesSize.takeIf { it != 0 }?.let { Spacer(Modifier.width(8.dp)) Text( text = it.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) } EnhancedIconButton( onClick = component::clearConvertedImagesSelection ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } } ================================================ FILE: feature/gif-tools/src/main/java/com/t8rin/imagetoolbox/feature/gif_tools/presentation/screenLogic/GifToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.FileSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifConverter import com.t8rin.imagetoolbox.feature.gif_tools.domain.GifParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.flow.onCompletion class GifToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialType: Screen.GifTools.Type?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val fileController: FileController, private val filenameCreator: FilenameCreator, private val gifConverter: GifConverter, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialType?.let(::setType) } } private val _type: MutableState = mutableStateOf(null) val type by _type private val _isLoading: MutableState = mutableStateOf(false) val isLoading by _isLoading private val _isLoadingGifImages: MutableState = mutableStateOf(false) val isLoadingGifImages by _isLoadingGifImages private val _params: MutableState = mutableStateOf(GifParams.Default) val params by _params private val _convertedImageUris: MutableState> = mutableStateOf(emptyList()) val convertedImageUris by _convertedImageUris private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat private val _imageFrames: MutableState = mutableStateOf(ImageFrames.All) val gifFrames by _imageFrames private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _jxlQuality: MutableState = mutableStateOf(Quality.Jxl()) val jxlQuality by _jxlQuality private val _webpQuality: MutableState = mutableStateOf(Quality.Base()) val webpQuality by _webpQuality private var gifData: ByteArray? = null fun setType(type: Screen.GifTools.Type) { when (type) { is Screen.GifTools.Type.GifToImage -> { type.gifUri?.let { setGifUri(it) } ?: _type.update { null } } is Screen.GifTools.Type.ImageToGif -> { _type.update { type } } is Screen.GifTools.Type.GifToJxl -> { _type.update { type } } is Screen.GifTools.Type.GifToWebp -> { _type.update { type } } } } fun setImageUris(uris: List) { resetState() _type.update { Screen.GifTools.Type.ImageToGif(uris) } } private var collectionJob: Job? by smartJob { _isLoading.update { false } } fun setGifUri(uri: Uri) { resetState() _type.update { Screen.GifTools.Type.GifToImage(uri) } updateGifFrames(ImageFrames.All) collectionJob = componentScope.launch { _isLoading.update { true } _isLoadingGifImages.update { true } gifConverter.extractFramesFromGif( gifUri = uri.toString(), imageFormat = imageFormat, imageFrames = ImageFrames.All, quality = params.quality ).onCompletion { _isLoading.update { false } _isLoadingGifImages.update { false } }.collect { nextUri -> if (isLoading) { _isLoading.update { false } } _convertedImageUris.update { it + nextUri } } } } override fun resetState() { collectionJob?.cancel() collectionJob = null _type.update { null } _convertedImageUris.update { emptyList() } gifData = null savingJob?.cancel() savingJob = null updateParams(GifParams.Default) registerChangesCleared() } fun updateGifFrames(imageFrames: ImageFrames) { _imageFrames.update { imageFrames } registerChanges() } fun clearConvertedImagesSelection() = updateGifFrames(ImageFrames.ManualSelection(emptyList())) fun selectAllConvertedImages() = updateGifFrames(ImageFrames.All) private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveGifTo(uri: Uri) { savingJob = trackProgress { _isSaving.value = true gifData?.let { byteArray -> fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(byteArray) } ).also(::parseFileSaveResult).onSuccess(::registerSave) } _isSaving.value = false gifData = null } } fun saveBitmaps( oneTimeSaveLocationUri: String?, onGifSaveResult: (filename: String) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.GifTools.Type.GifToImage -> { val results = mutableListOf() type.gifUri?.toString()?.also { gifUri -> gifConverter.extractFramesFromGif( gifUri = gifUri, imageFormat = imageFormat, imageFrames = gifFrames, quality = params.quality, onGetFramesCount = { if (it == 0) { _isSaving.value = false savingJob?.cancel() parseSaveResults( listOf(SaveResult.Error.MissingPermissions) ) } _left.value = gifFrames.getFramePositions(it).size updateProgress( done = done, total = left ) } ).onCompletion { parseSaveResults(results.onSuccess(::registerSave)) }.collect { uri -> imageGetter.getImage( data = uri, originalSize = true )?.let { localBitmap -> val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = uri, sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( imageFormat = imageFormat, quality = params.quality, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value++ updateProgress( done = done, total = left ) } } } is Screen.GifTools.Type.ImageToGif -> { _left.value = type.imageUris?.size ?: -1 gifData = type.imageUris?.map { it.toString() }?.let { list -> gifConverter.createGifFromImageUris( imageUris = list, params = params, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) }, onFailure = { parseSaveResults(listOf(SaveResult.Error.Exception(it))) } )?.also { onGifSaveResult("GIF_${timestamp()}.gif") } } } is Screen.GifTools.Type.GifToJxl -> { val results = mutableListOf() val gifUris = type.gifUris?.map { it.toString() } ?: emptyList() _left.value = gifUris.size gifConverter.convertGifToJxl( gifUris = gifUris, quality = jxlQuality ) { uri, jxlBytes -> results.add( fileController.save( saveTarget = JxlSaveTarget(uri, jxlBytes), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) } is Screen.GifTools.Type.GifToWebp -> { val results = mutableListOf() val gifUris = type.gifUris?.map { it.toString() } ?: emptyList() _left.value = gifUris.size gifConverter.convertGifToWebp( gifUris = gifUris, quality = webpQuality ) { uri, webpBytes -> results.add( fileController.save( saveTarget = WebpSaveTarget(uri, webpBytes), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) } null -> Unit } _isSaving.value = false } } private fun JxlSaveTarget( uri: String, jxlBytes: ByteArray ): SaveTarget = FileSaveTarget( originalUri = uri, filename = jxlFilename(uri), data = jxlBytes, imageFormat = ImageFormat.Jxl.Lossless ) private fun WebpSaveTarget( uri: String, webpBytes: ByteArray ): SaveTarget = FileSaveTarget( originalUri = uri, filename = webpFilename(uri), data = webpBytes, imageFormat = ImageFormat.Webp.Lossless ) private fun webpFilename( uri: String ): String = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = ImageInfo( imageFormat = ImageFormat.Webp.Lossless, originalUri = uri ), originalUri = uri, sequenceNumber = done + 1, metadata = null, data = ByteArray(0) ), forceNotAddSizeInFilename = true ) private fun jxlFilename( uri: String ): String = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = ImageInfo( imageFormat = ImageFormat.Jxl.Lossless, originalUri = uri ), originalUri = uri, sequenceNumber = done + 1, metadata = null, data = ByteArray(0) ), forceNotAddSizeInFilename = true ) fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun reorderImageUris(uris: List?) { if (type is Screen.GifTools.Type.ImageToGif) { _type.update { Screen.GifTools.Type.ImageToGif(uris) } registerChanges() } } fun addImageToUris(uris: List) { val type = _type.value if (type is Screen.GifTools.Type.ImageToGif) { _type.update { val newUris = type.imageUris?.plus(uris)?.toSet()?.toList() Screen.GifTools.Type.ImageToGif(newUris) } registerChanges() } } fun removeImageAt(index: Int) { val type = _type.value if (type is Screen.GifTools.Type.ImageToGif) { _type.update { val newUris = type.imageUris?.toMutableList()?.apply { removeAt(index) } Screen.GifTools.Type.ImageToGif(newUris) } registerChanges() } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } registerChanges() } fun setQuality(quality: Quality) { updateParams(params.copy(quality = quality)) } fun updateParams(params: GifParams) { _params.update { params } registerChanges() } fun performSharing() { savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.GifTools.Type.GifToImage -> { _left.value = -1 val positions = gifFrames.getFramePositions(convertedImageUris.size).map { it - 1 } val uris = convertedImageUris.filterIndexed { index, _ -> index in positions } shareProvider.shareUris(uris) AppToastHost.showConfetti() } is Screen.GifTools.Type.ImageToGif -> { _left.value = type.imageUris?.size ?: -1 type.imageUris?.map { it.toString() }?.let { list -> gifConverter.createGifFromImageUris( imageUris = list, params = params, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) }, onFailure = { } )?.also { byteArray -> shareProvider.shareByteArray( byteArray = byteArray, filename = "GIF_${timestamp()}.gif", onComplete = AppToastHost::showConfetti ) } } } is Screen.GifTools.Type.GifToJxl -> { val results = mutableListOf() val gifUris = type.gifUris?.map { it.toString() } ?: emptyList() _left.value = gifUris.size gifConverter.convertGifToJxl( gifUris = gifUris, quality = jxlQuality ) { uri, jxlBytes -> results.add( shareProvider.cacheByteArray( byteArray = jxlBytes, filename = jxlFilename(uri) ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } shareProvider.shareUris(results.filterNotNull()) } is Screen.GifTools.Type.GifToWebp -> { val results = mutableListOf() val gifUris = type.gifUris?.map { it.toString() } ?: emptyList() _left.value = gifUris.size gifConverter.convertGifToWebp( gifUris = gifUris, quality = webpQuality ) { uri, webpBytes -> results.add( shareProvider.cacheByteArray( byteArray = webpBytes, filename = webpFilename(uri) ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } shareProvider.shareUris(results.filterNotNull()) } null -> Unit } _isSaving.value = false } } fun setJxlQuality(quality: Quality) { _jxlQuality.update { (quality as? Quality.Jxl) ?: Quality.Jxl() } registerChanges() } fun setWebpQuality(quality: Quality) { _webpQuality.update { (quality as? Quality.Base) ?: Quality.Base() } registerChanges() } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialType: Screen.GifTools.Type?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): GifToolsComponent } } ================================================ FILE: feature/gradient-maker/.gitignore ================================================ /build ================================================ FILE: feature/gradient-maker/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.gradient_maker" dependencies { implementation(projects.feature.compare) } ================================================ FILE: feature/gradient-maker/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/data/AndroidGradientMaker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.data import android.graphics.Bitmap import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathMeasure import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.VertexMode import androidx.compose.ui.graphics.Vertices import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.lerp import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientMaker import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientState import com.t8rin.imagetoolbox.feature.gradient_maker.domain.MeshGradientState import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidGradientMaker @Inject constructor( dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, GradientMaker { override suspend fun createGradient( integerSize: IntegerSize, gradientState: GradientState ): Bitmap? = createGradient( src = integerSize.toSize().run { createBitmap( width = width.toInt(), height = height.toInt() ) }, gradientState = gradientState, gradientAlpha = 1f ) override suspend fun createGradient( src: Bitmap, gradientState: GradientState, gradientAlpha: Float ): Bitmap? = withContext(defaultDispatcher) { val size = IntegerSize( src.width, src.height ).toSize() gradientState.getBrush(size)?.let { brush -> src.copy(src.safeConfig, true).apply { setHasAlpha(true) Canvas(asImageBitmap()).apply { drawImage(asImageBitmap(), Offset.Zero, Paint()) drawRect( paint = Paint().apply { shader = brush.createShader(size) alpha = gradientAlpha }, rect = Rect(offset = Offset.Zero, size = size) ) } } } } override suspend fun createMeshGradient( integerSize: IntegerSize, gradientState: MeshGradientState ): Bitmap? = createMeshGradient( src = integerSize.toSize().run { createBitmap( width = width.toInt(), height = height.toInt() ) }, gradientState = gradientState, gradientAlpha = 1f ) override suspend fun createMeshGradient( src: Bitmap, gradientState: MeshGradientState, gradientAlpha: Float ): Bitmap? = withContext(defaultDispatcher) { src.copy(src.safeConfig, true).apply { setHasAlpha(true) val paint = Paint().apply { alpha = gradientAlpha } Canvas(asImageBitmap()).apply { drawImage(asImageBitmap(), Offset.Zero, Paint()) drawMeshGradient( pointData = PointData( points = gradientState.points, stepsX = gradientState.resolutionX, stepsY = gradientState.resolutionY ), size = Size(width.toFloat(), height.toFloat()), paint = paint ) } } } private fun IntegerSize.toSize(): Size = Size( width.coerceAtLeast(1).toFloat(), height.coerceAtLeast(1).toFloat(), ) private fun Canvas.drawMeshGradient( pointData: PointData, indicesModifier: (List) -> List = { it }, size: Size, paint: Paint ) { CanvasDrawScope().apply { drawContext.canvas = this@drawMeshGradient drawContext.size = size with(drawContext.canvas) { scale( scaleX = size.width, scaleY = size.height, pivot = Offset.Zero ) { drawVertices( vertices = Vertices( vertexMode = VertexMode.Triangles, positions = pointData.offsets, textureCoordinates = pointData.offsets, colors = pointData.colors, indices = indicesModifier(pointData.indices) ), blendMode = BlendMode.Dst, paint = paint, ) } } } } } internal class PointData( private val points: List>>, private val stepsX: Int, private val stepsY: Int ) { val offsets: MutableList val colors: MutableList val indices: List private val xLength: Int = (points[0].size * stepsX) - (stepsX - 1) private val yLength: Int = (points.size * stepsY) - (stepsY - 1) private val measure = PathMeasure() private val indicesBlocks: List init { offsets = buildList { repeat((xLength - 0) * (yLength - 0)) { add(Offset(0f, 0f)) } }.toMutableList() colors = buildList { repeat((xLength - 0) * (yLength - 0)) { add(Color.Transparent) } }.toMutableList() indicesBlocks = buildList { for (y in 0..yLength - 2) { for (x in 0..xLength - 2) { val a = (y * xLength) + x val b = a + 1 val c = ((y + 1) * xLength) + x val d = c + 1 add( IndicesBlock( indices = buildList { add(a) add(c) add(d) add(a) add(b) add(d) }, x = x, y = y ) ) } } } indices = indicesBlocks.flatMap { it.indices } generateInterpolatedOffsets() } private fun generateInterpolatedOffsets() { for (y in 0..points.lastIndex) { for (x in 0..points[y].lastIndex) { this[x * stepsX, y * stepsY] = points[y][x].first this[x * stepsX, y * stepsY] = points[y][x].second if (x != points[y].lastIndex) { val path = cubicPathX( point1 = points[y][x].first, point2 = points[y][x + 1].first, when (x) { 0 -> 0 points[y].lastIndex - 1 -> 2 else -> 1 } ) measure.setPath(path, false) for (i in 1.. 0 points[y].lastIndex - 1 -> 2 else -> 1 } ) measure.setPath(path, false) for (i in (1.., val x: Int, val y: Int ) operator fun get( x: Int, y: Int ): Offset { val index = (y * xLength) + x return offsets[index] } private fun getColor( x: Int, y: Int ): Color { val index = (y * xLength) + x return colors[index] } private operator fun set( x: Int, y: Int, offset: Offset ) { val index = (y * xLength) + x offsets[index] = Offset(offset.x, offset.y) } private operator fun set( x: Int, y: Int, color: Color ) { val index = (y * xLength) + x colors[index] = color } } private fun cubicPathX( point1: Offset, point2: Offset, position: Int ): Path { val path = Path().apply { moveTo(point1.x, point1.y) val delta = (point2.x - point1.x) * .5f when (position) { 0 -> cubicTo( point1.x, point1.y, point2.x - delta, point2.y, point2.x, point2.y ) 2 -> cubicTo( point1.x + delta, point1.y, point2.x, point2.y, point2.x, point2.y ) else -> cubicTo( point1.x + delta, point1.y, point2.x - delta, point2.y, point2.x, point2.y ) } lineTo(point2.x, point2.y) } return path } private fun cubicPathY( point1: Offset, point2: Offset, position: Int ): Path { val path = Path().apply { moveTo(point1.x, point1.y) val delta = (point2.y - point1.y) * .5f when (position) { 0 -> cubicTo( point1.x, point1.y, point2.x, point2.y - delta, point2.x, point2.y ) 2 -> cubicTo( point1.x, point1.y + delta, point2.x, point2.y, point2.x, point2.y ) else -> cubicTo( point1.x, point1.y + delta, point2.x, point2.y - delta, point2.x, point2.y ) } lineTo(point2.x, point2.y) } return path } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/di/GradientMakerModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.di import android.graphics.Bitmap import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode import com.t8rin.imagetoolbox.feature.gradient_maker.data.AndroidGradientMaker import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientMaker import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface GradientMakerModule { @Singleton @Binds fun provideGradientMaker( maker: AndroidGradientMaker ): GradientMaker } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientMaker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.domain import com.t8rin.imagetoolbox.core.domain.model.IntegerSize interface GradientMaker { suspend fun createGradient( integerSize: IntegerSize, gradientState: GradientState ): Image? suspend fun createGradient( src: Image, gradientState: GradientState, gradientAlpha: Float ): Image? suspend fun createMeshGradient( integerSize: IntegerSize, gradientState: MeshGradientState, ): Image? suspend fun createMeshGradient( src: Image, gradientState: MeshGradientState, gradientAlpha: Float ): Image? } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.domain interface GradientState { var size: Size val brush: Brush? get() = getBrush(size) fun getBrush(size: Size): Brush? var gradientType: GradientType val colorStops: List> var tileMode: TileMode var linearGradientAngle: Float var centerFriction: Offset var radiusFriction: Float } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/GradientType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.domain enum class GradientType { Linear, Radial, Sweep } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/domain/MeshGradientState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.domain interface MeshGradientState { val points: List>> var resolutionX: Int var resolutionY: Int } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/GradientMakerContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation import android.net.Uri import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.WindowInsets import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShowOriginalButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.GradientMakerAppColorSchemeHandler import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.GradientMakerBottomButtons import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.GradientMakerCompareButton import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.GradientMakerControls import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.GradientMakerImagePreview import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.GradientMakerNoDataControls import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.GradientMakerType import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent @Composable fun GradientMakerContent( component: GradientMakerComponent ) { val screenType = component.screenType GradientMakerAppColorSchemeHandler(component) LaunchedEffect(screenType) { if (screenType == null) { component.resetState() } } val goBack = { if (screenType != null) { component.resetState() } else { component.onGoBack() } } var showExitDialog by rememberSaveable { mutableStateOf(false) } val imagePicker = rememberImagePicker { uris: List -> component.setUris(uris) component.updateGradientAlpha(0.5f) } AdaptiveLayoutScreen( shouldDisableBackHandler = screenType == null, canShowScreenData = screenType != null, title = { TopAppBarTitle( title = when (screenType) { null, GradientMakerType.Default -> stringResource(R.string.gradient_maker) GradientMakerType.Overlay -> stringResource(R.string.gradient_maker_type_image) GradientMakerType.Mesh -> stringResource(R.string.mesh_gradients) GradientMakerType.MeshOverlay -> stringResource(R.string.gradient_maker_type_image_mesh) }, input = Unit, isLoading = false, size = null ) }, onGoBack = { if (component.haveChanges) showExitDialog = true else goBack() }, actions = { if (component.uris.isNotEmpty()) { ShowOriginalButton( onStateChange = component::setShowOriginal ) } var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.brush != null, onShare = component::shareBitmaps, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, topAppBarPersistentActions = { if (screenType == null) { TopAppBarEmoji() } GradientMakerCompareButton(component) }, imagePreview = { GradientMakerImagePreview(component) }, controls = { GradientMakerControls(component) }, insetsForNoData = WindowInsets(0), noDataControls = { GradientMakerNoDataControls(component) }, buttons = { actions -> GradientMakerBottomButtons( component = component, actions = actions, imagePicker = imagePicker ) }, forceImagePreviewToMax = component.showOriginal, contentPadding = animateDpAsState( if (screenType == null) 12.dp else 20.dp ).value ) LoadingDialog( visible = component.isSaving || component.isImageLoading, done = component.done, left = component.left, onCancelLoading = component::cancelSaving, canCancel = component.isSaving, ) ExitWithoutSavingDialog( onExit = goBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/ColorStopSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorInfo import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelection import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSlider import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.other.RevealDirection import com.t8rin.imagetoolbox.core.ui.widget.other.RevealValue import com.t8rin.imagetoolbox.core.ui.widget.other.SwipeToReveal import com.t8rin.imagetoolbox.core.ui.widget.other.rememberRevealState import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.value.ValueDialog import com.t8rin.imagetoolbox.core.ui.widget.value.ValueText import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable fun ColorStopSelection( colorStops: List>, onRemoveClick: (Int) -> Unit, onValueChange: (Int, Pair) -> Unit, onAddColorStop: (Pair) -> Unit ) { var showColorPicker by rememberSaveable { mutableStateOf(false) } ExpandableItem( initialState = true, modifier = Modifier.padding(1.dp), shape = ShapeDefaults.extraLarge, color = MaterialTheme.colorScheme.surfaceContainer, visibleContent = { TitleItem(text = stringResource(R.string.color_stops)) }, expandableContent = { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(8.dp) ) { colorStops.forEachIndexed { index, (value, color) -> ColorStopSelectionItem( value = value, color = color, onRemoveClick = { onRemoveClick(index) }, onValueChange = { onValueChange(index, it) }, canDelete = colorStops.size > 2 ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = { showColorPicker = true }, modifier = Modifier.padding( horizontal = 16.dp ) ) { Icon( imageVector = Icons.Rounded.Palette, contentDescription = null ) Spacer(Modifier.width(8.dp)) Text(stringResource(id = R.string.add_color)) } } } ) var color by rememberSaveable(stateSaver = ColorSaver) { mutableStateOf(Color.Red) } EnhancedModalBottomSheet( sheetContent = { Box { Column( Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(start = 36.dp, top = 36.dp, end = 36.dp, bottom = 24.dp) ) { ColorSelection( value = color, onValueChange = { color = it }, withAlpha = true ) } } }, visible = showColorPicker, onDismiss = { showColorPicker = it }, title = { TitleItem( text = stringResource(R.string.color), icon = Icons.Rounded.Palette ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onAddColorStop(1f to color) showColorPicker = false } ) { AutoSizeText(stringResource(R.string.ok)) } } ) } @Composable private fun ColorStopSelectionItem( value: Float, color: Color, onRemoveClick: () -> Unit, onValueChange: (Pair) -> Unit, canDelete: Boolean ) { var showColorPicker by rememberSaveable { mutableStateOf(false) } val scope = rememberCoroutineScope() val state = rememberRevealState() SwipeToReveal( state = state, enableSwipe = canDelete, revealedContentEnd = { Box( Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.errorContainer, shape = MaterialTheme.shapes.large, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } onRemoveClick() } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete), modifier = Modifier .padding(16.dp) .padding(end = 8.dp) .align(Alignment.CenterEnd), tint = MaterialTheme.colorScheme.onErrorContainer ) } }, directions = setOf(RevealDirection.EndToStart), swipeableContent = { Column( modifier = Modifier .fillMaxWidth() .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ) .then( if (canDelete) { Modifier.pointerInput(Unit) { detectTapGestures( onPress = { val time = System.currentTimeMillis() awaitRelease() if (System.currentTimeMillis() - time >= 200) { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } } } ) } } else Modifier ) ) { ColorInfo( color = color, onColorChange = { onValueChange(value to it) }, supportButtonIcon = Icons.Rounded.MiniEdit, onSupportButtonClick = { showColorPicker = true } ) Spacer(modifier = Modifier.height(8.dp)) Row( verticalAlignment = Alignment.CenterVertically ) { EnhancedSlider( value = value, onValueChange = { onValueChange(it to color) }, valueRange = 0f..1f, modifier = Modifier.weight(1f) ) var showValueDialog by rememberSaveable { mutableStateOf(false) } ValueText( modifier = Modifier, value = (value * 100).toInt(), onClick = { showValueDialog = true } ) ValueDialog( roundTo = 0, valueRange = 0f..100f, valueState = (value * 100).toInt().toString(), expanded = showValueDialog, onDismiss = { showValueDialog = false }, onValueUpdate = { onValueChange(it.roundToInt() / 100f to color) showValueDialog = false } ) } } } ) EnhancedModalBottomSheet( sheetContent = { Box { Column( Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(start = 36.dp, top = 36.dp, end = 36.dp, bottom = 24.dp) ) { ColorSelection( value = color, onValueChange = { onValueChange(value to it) }, withAlpha = true ) } } }, visible = showColorPicker, onDismiss = { showColorPicker = it }, title = { TitleItem( text = stringResource(R.string.color), icon = Icons.Rounded.Palette ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showColorPicker = false } ) { AutoSizeText(stringResource(R.string.close)) } } ) } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerAppColorSchemeHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent @Composable internal fun GradientMakerAppColorSchemeHandler(component: GradientMakerComponent) { AutoContentBasedColors( model = Triple(component.brush, component.meshPoints, component.selectedUri), selector = { (_, uri) -> component.createGradientBitmap( data = uri, integerSize = IntegerSize(1000, 1000) ) }, allowChangeColor = component.screenType != null ) val colorScheme = MaterialTheme.colorScheme LaunchedEffect(component.colorStops) { if (component.colorStops.isEmpty()) { colorScheme.apply { component.addColorStop( pair = 0f to primary.blend(primaryContainer, 0.5f), isInitial = true ) component.addColorStop( pair = 0.5f to secondary.blend(secondaryContainer, 0.5f), isInitial = true ) component.addColorStop( pair = 1f to tertiary.blend(tertiaryContainer, 0.5f), isInitial = true ) } } } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerBottomButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.foundation.layout.RowScope import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.canPickImage import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent @Composable internal fun GradientMakerBottomButtons( component: GradientMakerComponent, actions: @Composable RowScope.() -> Unit, imagePicker: ImagePicker ) { val isPortrait by isPortraitOrientationAsState() val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { if (component.brush != null) { component.saveBitmaps( oneTimeSaveLocationUri = it ) } } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.screenType == null, onSecondaryButtonClick = imagePicker::pickImage, isSecondaryButtonVisible = component.screenType.canPickImage(), isPrimaryButtonVisible = component.brush != null, showNullDataButtonAsContainer = true, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerCompareButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import android.net.Uri import androidx.compose.foundation.layout.aspectRatio import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.t8rin.imagetoolbox.core.ui.widget.buttons.CompareButton import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareSheet import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.canPickImage import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.isMesh import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent @Composable internal fun GradientMakerCompareButton(component: GradientMakerComponent) { var showCompareSheet by rememberSaveable { mutableStateOf(false) } val screenType = component.screenType CompareButton( onClick = { showCompareSheet = true }, visible = component.brush != null && screenType.canPickImage() && component.selectedUri != Uri.EMPTY ) CompareSheet( beforeContent = { Picture( model = component.selectedUri, modifier = Modifier.aspectRatio( component.imageAspectRatio ) ) }, afterContent = { if (screenType.isMesh()) { MeshGradientPreview( meshGradientState = component.meshGradientState, gradientAlpha = component.gradientAlpha, allowPickingImage = screenType.canPickImage(), gradientSize = component.gradientSize, selectedUri = component.selectedUri, imageAspectRatio = component.imageAspectRatio ) } else { val gradientState = rememberGradientState() LaunchedEffect(component.brush) { gradientState.gradientType = component.gradientType gradientState.linearGradientAngle = component.angle gradientState.centerFriction = component.centerFriction gradientState.radiusFriction = component.radiusFriction gradientState.colorStops.apply { clear() addAll(component.colorStops) } gradientState.tileMode = component.tileMode } GradientPreview( brush = gradientState.brush, gradientAlpha = component.gradientAlpha, allowPickingImage = screenType.canPickImage(), gradientSize = component.gradientSize, onSizeChanged = { gradientState.size = it }, selectedUri = component.selectedUri, imageAspectRatio = component.imageAspectRatio ) } }, visible = showCompareSheet, onDismiss = { showCompareSheet = false } ) } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Build import androidx.compose.material.icons.rounded.DensitySmall import androidx.compose.material.icons.rounded.GridOn import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.canPickImage import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.isMesh import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent import kotlin.math.roundToInt @Composable internal fun GradientMakerControls(component: GradientMakerComponent) { var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } val screenType = component.screenType Column( horizontalAlignment = Alignment.CenterHorizontally ) { ImageCounter( imageCount = component.uris.size.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) AnimatedContent( screenType != null && !screenType.canPickImage() ) { canChangeSize -> if (canChangeSize) { GradientSizeSelector( value = component.gradientSize, onWidthChange = component::updateWidth, onHeightChange = component::updateHeight ) } else { AlphaSelector( value = component.gradientAlpha, onValueChange = component::updateGradientAlpha, modifier = Modifier ) } } Spacer(Modifier.height(8.dp)) if (screenType.isMesh()) { Column( modifier = Modifier.container( resultPadding = 0.dp ) ) { Spacer(Modifier.height(16.dp)) TitleItem( text = stringResource(R.string.points_customization), icon = Icons.Rounded.Build, modifier = Modifier.padding( horizontal = 16.dp ) ) MeshGradientEditor( state = component.meshGradientState, modifier = Modifier .fillMaxWidth() .aspectRatio(1f) .padding(16.dp) ) } Spacer(Modifier.height(8.dp)) EnhancedSliderItem( value = component.meshGradientState.gridSize, title = stringResource(R.string.grid_size), icon = Icons.Rounded.GridOn, valueRange = 2f..6f, internalStateTransformation = { it.roundToInt() }, onValueChange = { value -> if (value.roundToInt() != component.meshGradientState.gridSize) { val size = value.roundToInt() component.setResolution(lerp(1f, 16f, 2f / size)) component.meshGradientState.points.apply { clear() addAll(generateMesh(size)) } } } ) Spacer(Modifier.height(8.dp)) EnhancedSliderItem( value = component.meshResolutionX, title = stringResource(R.string.resolution), icon = Icons.Rounded.DensitySmall, valueRange = 1f..64f, internalStateTransformation = { it.roundToInt() }, onValueChange = component::setResolution ) } else { GradientTypeSelector( value = component.gradientType, onValueChange = component::setGradientType ) { GradientPropertiesSelector( gradientType = component.gradientType, linearAngle = component.angle, onLinearAngleChange = component::updateLinearAngle, centerFriction = component.centerFriction, radiusFriction = component.radiusFriction, onRadialDimensionsChange = component::setRadialProperties ) } Spacer(Modifier.height(8.dp)) ColorStopSelection( colorStops = component.colorStops, onRemoveClick = component::removeColorStop, onValueChange = component::updateColorStop, onAddColorStop = component::addColorStop ) Spacer(Modifier.height(8.dp)) TileModeSelector( value = component.tileMode, onValueChange = component::setTileMode ) } if (screenType.canPickImage()) { Spacer(Modifier.height(8.dp)) SaveExifWidget( checked = component.keepExif, imageFormat = component.imageFormat, onCheckedChange = component::toggleKeepExif ) } Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = component.imageFormat, forceEnabled = screenType != null && !screenType.canPickImage(), onValueChange = component::setImageFormat ) } val transformations by remember( component.brush, screenType.isMesh(), component.meshPoints, component.meshResolutionX, component.meshResolutionY, component.gradientAlpha ) { derivedStateOf { listOf( component.getGradientTransformation() ) } } val isPortrait by isPortraitOrientationAsState() PickImageFromUrisSheet( transformations = transformations, visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, uris = component.uris, selectedUri = component.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = component::updateUrisSilently, columns = if (isPortrait) 2 else 4, ) } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerImagePreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.canPickImage import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.isMesh import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent @Composable internal fun GradientMakerImagePreview(component: GradientMakerComponent) { val screenType = component.screenType Box( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ) .container() .padding(4.dp), contentAlignment = Alignment.Center ) { if (screenType.isMesh()) { MeshGradientPreview( meshGradientState = component.meshGradientState, gradientAlpha = if (component.showOriginal) 0f else component.gradientAlpha, allowPickingImage = screenType.canPickImage(), gradientSize = component.gradientSize, selectedUri = component.selectedUri, imageAspectRatio = component.imageAspectRatio ) } else { GradientPreview( brush = component.brush, gradientAlpha = if (component.showOriginal) 0f else component.gradientAlpha, allowPickingImage = screenType.canPickImage(), gradientSize = component.gradientSize, onSizeChanged = component::setPreviewSize, selectedUri = component.selectedUri, imageAspectRatio = component.imageAspectRatio ) } } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientMakerNoDataControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import android.net.Uri import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageOverlay import com.t8rin.imagetoolbox.core.resources.icons.ImagesMode import com.t8rin.imagetoolbox.core.resources.icons.MeshDownload import com.t8rin.imagetoolbox.core.resources.icons.MeshGradient import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.GradientMakerType import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent @Composable internal fun GradientMakerNoDataControls( component: GradientMakerComponent ) { val isPortrait by isPortraitOrientationAsState() var requestedType by rememberSaveable(component.screenType) { mutableStateOf(null) } val imagePicker = rememberImagePicker { uris: List -> component.setScreenType(requestedType) component.setUris(uris) component.updateGradientAlpha(0.5f) } val preference1 = @Composable { val screen = remember { Screen.GradientMaker() } PreferenceItem( title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), startIcon = screen.icon, modifier = Modifier.fillMaxWidth(), onClick = { component.setScreenType(GradientMakerType.Default) } ) } val preference2 = @Composable { PreferenceItem( title = stringResource(R.string.gradient_maker_type_image), subtitle = stringResource(R.string.gradient_maker_type_image_sub), startIcon = Icons.Outlined.ImagesMode, modifier = Modifier.fillMaxWidth(), onClick = { requestedType = GradientMakerType.Overlay imagePicker.pickImage() } ) } val preference3 = @Composable { PreferenceItem( title = stringResource(R.string.mesh_gradients), subtitle = stringResource(R.string.mesh_gradients_sub), startIcon = Icons.Outlined.MeshGradient, modifier = Modifier.fillMaxWidth(), onClick = { component.setScreenType(GradientMakerType.Mesh) } ) } val preference4 = @Composable { PreferenceItem( title = stringResource(R.string.gradient_maker_type_image_mesh), subtitle = stringResource(R.string.gradient_maker_type_image_mesh_sub), startIcon = Icons.Outlined.ImageOverlay, modifier = Modifier.fillMaxWidth(), onClick = { requestedType = GradientMakerType.MeshOverlay imagePicker.pickImage() } ) } val preference5 = @Composable { PreferenceItem( title = stringResource(R.string.collection_mesh_gradients), subtitle = stringResource(R.string.collection_mesh_gradients_sub), startIcon = Icons.Outlined.MeshDownload, modifier = Modifier.fillMaxWidth(), onClick = { component.onNavigate(Screen.MeshGradients) } ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() Spacer(modifier = Modifier.height(8.dp)) preference3() Spacer(modifier = Modifier.height(8.dp)) preference4() Spacer(modifier = Modifier.height(8.dp)) preference5() } } else { Column( horizontalAlignment = Alignment.CenterHorizontally ) { Row( modifier = Modifier.windowInsetsPadding( WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal) ) ) { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.windowInsetsPadding( WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal) ) ) { preference3.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference4.withModifier(modifier = Modifier.weight(1f)) } Spacer(modifier = Modifier.height(8.dp)) preference5.withModifier(modifier = Modifier.fillMaxWidth(0.5f)) } } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.zIndex import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable internal fun GradientPreview( brush: ShaderBrush?, gradientAlpha: Float, allowPickingImage: Boolean?, gradientSize: IntegerSize, onSizeChanged: (Size) -> Unit, imageAspectRatio: Float, selectedUri: Uri ) { val alpha by animateFloatAsState( if (brush == null) 1f else gradientAlpha ) val solidBrush = SolidColor(MaterialTheme.colorScheme.surfaceContainer) AnimatedContent( targetState = if (allowPickingImage == true) { imageAspectRatio } else { gradientSize .aspectRatio .roundToTwoDigits() .coerceIn(0.01f..100f) } ) { aspectRatio -> Box { Box( modifier = Modifier .aspectRatio(aspectRatio) .clip(MaterialTheme.shapes.medium) .then( if (allowPickingImage != true) { Modifier.transparencyChecker() } else Modifier ) .drawBehind { drawRect( brush = brush ?: solidBrush, alpha = alpha ) } .onGloballyPositioned { onSizeChanged( Size( it.size.width.toFloat(), it.size.height.toFloat() ) ) } .zIndex(2f), contentAlignment = Alignment.Center ) { Spacer( modifier = Modifier .fillMaxSize() .shimmer(visible = brush == null) ) } if (allowPickingImage == true) { Picture( model = selectedUri, modifier = Modifier.matchParentSize(), shape = MaterialTheme.shapes.medium ) } } } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientPropertiesSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.res.stringResource import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientType import kotlin.math.roundToInt @Composable fun GradientPropertiesSelector( gradientType: GradientType, linearAngle: Float, centerFriction: Offset, radiusFriction: Float, onLinearAngleChange: (Float) -> Unit, onRadialDimensionsChange: (Offset, Float) -> Unit, modifier: Modifier = Modifier ) { AnimatedContent( targetState = gradientType, modifier = modifier ) { type -> when (type) { GradientType.Linear -> { EnhancedSliderItem( behaveAsContainer = false, value = linearAngle, title = stringResource(id = R.string.angle), valueRange = 0f..360f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onLinearAngleChange(it.roundToInt().toFloat()) } ) } GradientType.Radial, GradientType.Sweep -> { var centerX by remember { mutableFloatStateOf(centerFriction.x) } var centerY by remember { mutableFloatStateOf(centerFriction.y) } var radius by remember { mutableFloatStateOf(radiusFriction) } onRadialDimensionsChange(Offset(centerX, centerY), radius) Column { EnhancedSliderItem( value = centerX, title = stringResource(id = R.string.center_x), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { centerX = it }, valueRange = 0f..1f, behaveAsContainer = false ) EnhancedSliderItem( value = centerY, title = stringResource(id = R.string.center_y), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { centerY = it }, valueRange = 0f..1f, behaveAsContainer = false ) AnimatedVisibility( visible = type != GradientType.Sweep ) { EnhancedSliderItem( value = radius, title = stringResource(id = R.string.radius), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { radius = it }, valueRange = 0f..1f, behaveAsContainer = false ) } } } } } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientSizeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.restrict import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextFieldColors @Composable fun GradientSizeSelector( value: IntegerSize, onWidthChange: (Int) -> Unit, onHeightChange: (Int) -> Unit, modifier: Modifier = Modifier ) { Row( modifier = modifier.container( shape = ShapeDefaults.extraLarge ) ) { val (width, height) = value RoundedTextField( value = width.takeIf { it != 0 }?.toString() ?: "", onValueChange = { onWidthChange(it.restrict(8192).toIntOrNull() ?: 0) }, shape = ShapeDefaults.smallStart, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = { Text(stringResource(R.string.width, " ")) }, modifier = Modifier .weight(1f) .padding( start = 8.dp, top = 8.dp, bottom = 8.dp, end = 2.dp ), colors = RoundedTextFieldColors( isError = false, containerColor = MaterialTheme.colorScheme.surfaceContainer ) ) RoundedTextField( value = height.takeIf { it != 0 }?.toString() ?: "", onValueChange = { onHeightChange(it.restrict(8192).toIntOrNull() ?: 0) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), shape = ShapeDefaults.smallEnd, label = { Text(stringResource(R.string.height, " ")) }, modifier = Modifier .weight(1f) .padding( start = 2.dp, top = 8.dp, bottom = 8.dp, end = 8.dp ), colors = RoundedTextFieldColors( isError = false, containerColor = MaterialTheme.colorScheme.surfaceContainer ) ) } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/GradientTypeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Tune import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientType @Composable fun GradientTypeSelector( value: GradientType, onValueChange: (GradientType) -> Unit, modifier: Modifier = Modifier, propertiesContent: @Composable () -> Unit ) { Column( modifier = modifier .container( shape = ShapeDefaults.extraLarge ) .animateContentSizeNoClip(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { EnhancedButtonGroup( modifier = Modifier.padding(8.dp), enabled = true, items = GradientType.entries.map { it.translatedName }, selectedIndex = GradientType.entries.indexOf(value), title = stringResource(id = R.string.gradient_type), onIndexChange = { onValueChange(GradientType.entries[it]) } ) ExpandableItem( visibleContent = { TitleItem( text = stringResource(id = R.string.properties), icon = Icons.Outlined.Tune ) }, expandableContent = { Column( modifier = Modifier.padding(horizontal = 8.dp) ) { propertiesContent() } }, modifier = Modifier.padding(end = 8.dp, start = 8.dp, bottom = 8.dp), color = MaterialTheme.colorScheme.surface ) } } private val GradientType.translatedName: String @Composable get() = when (this) { GradientType.Linear -> stringResource(id = R.string.gradient_type_linear) GradientType.Radial -> stringResource(id = R.string.gradient_type_radial) GradientType.Sweep -> stringResource(id = R.string.gradient_type_sweep) } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientEditor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.SaverScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.asComposePaint import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.nativePaint import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.input.pointer.pointerInput import com.t8rin.imagetoolbox.core.resources.icons.EditAlt import com.t8rin.imagetoolbox.core.ui.theme.inverseByLuma import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorPickerSheet import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.meshGradient import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.saver.OffsetSaver import kotlin.math.pow import kotlin.math.sqrt @Composable internal fun MeshGradientEditor( state: UiMeshGradientState, modifier: Modifier = Modifier ) { val selectedPoint = rememberSaveable( stateSaver = PairOffsetColorSaver ) { mutableStateOf(null) } val dragOffset = rememberSaveable( stateSaver = OffsetSaver ) { mutableStateOf(null) } val showColorPicker = rememberSaveable { mutableStateOf(false) } val isDragging = rememberSaveable { mutableStateOf(false) } Box( modifier = modifier .clip(ShapeDefaults.extraSmall) .transparencyChecker() .meshGradient( points = state.points, resolutionX = state.resolutionX, resolutionY = state.resolutionY ) .tappable { tapOffset -> val tappedPoint = state.points.flatten() .firstOrNull { (offset, _) -> Offset(offset.x * size.width, offset.y * size.height).getDistance( tapOffset ) < 60f } showColorPicker.value = tappedPoint == selectedPoint.value selectedPoint.value = tappedPoint if (tappedPoint == null) dragOffset.value = null } ) { val painter = rememberVectorPainter(Icons.Rounded.EditAlt) Canvas( modifier = Modifier .fillMaxSize() .then( if (selectedPoint.value != null) { Modifier.pointerInput(Unit) { detectDragGestures( onDragStart = { startOffset -> val tappedPoint = state.points.flatten() .firstOrNull { (offset, _) -> Offset( offset.x * size.width, offset.y * size.height ).getDistance(startOffset) < 60f } if (tappedPoint != null) { selectedPoint.value = tappedPoint dragOffset.value = tappedPoint.first isDragging.value = true showColorPicker.value = false } }, onDrag = { _, dragAmount -> selectedPoint.value?.let { (oldOffset, color) -> val newOffset = Offset( ((oldOffset.x * size.width + dragAmount.x) / size.width).coerceIn( 0f, 1f ), ((oldOffset.y * size.height + dragAmount.y) / size.height).coerceIn( 0f, 1f ) ) state.updatePointPosition(oldOffset, newOffset) selectedPoint.value = newOffset to color } }, onDragEnd = { isDragging.value = false } ) } } else Modifier ) ) { state.points.flatten().forEach { (offset, color) -> val scaledOffset = Offset(offset.x * size.width, offset.y * size.height) val isSelected = selectedPoint.value?.first == offset val inverseColor = color.inverseByLuma() drawContext.canvas.apply { drawCircle( radius = if (isSelected) 32f else 27f, center = scaledOffset, paint = Paint().nativePaint.apply { setShadowLayer( if (isSelected) 36f else 31f, 0f, 0f, Color.Black.copy(alpha = if (isSelected) .8f else .4f) .toArgb() ) }.asComposePaint() ) } if (isSelected) { drawCircle( color = inverseColor, radius = 40f, center = scaledOffset ) } drawCircle( color = color, radius = if (isSelected) 35f else 30f, center = scaledOffset ) if (isSelected) { translate( scaledOffset.x - 25f, scaledOffset.y - 25f ) { with(painter) { draw( size = Size(width = 50f, height = 50f), colorFilter = ColorFilter.tint(inverseColor) ) } } } } } ColorPickerSheet( visible = showColorPicker.value, onDismiss = { showColorPicker.value = false }, color = selectedPoint.value?.second, onColorSelected = { newColor -> selectedPoint.value?.let { (offset) -> state.updatePointColor(offset, newColor) } }, allowAlpha = true ) } } private fun UiMeshGradientState.updatePointPosition( oldOffset: Offset, newOffset: Offset ) { var found = false points.replaceAll { row -> row.map { if (it.first == oldOffset && !found) { found = true newOffset to it.second } else { it } } } } private fun UiMeshGradientState.updatePointColor( offset: Offset, newColor: Color ) { var found = false points.replaceAll { row -> row.map { if (it.first == offset && !found) { found = true it.first to newColor } else { it } } } } private fun Offset.getDistance(other: Offset): Float { return sqrt((x - other.x).pow(2) + (y - other.y).pow(2)) } private val PairOffsetColorSaver: Saver?, List> = object : Saver?, List> { override fun restore(value: List): Pair? { return if (value.size == 5) { val offset = Offset(value[0], value[1]) val color = Color(value[2], value[3], value[4]) offset to color } else { null } } override fun SaverScope.save(value: Pair?): List { return if (value == null) emptyList() else listOf( value.first.x, value.first.y, value.second.red, value.second.green, value.second.blue ) } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/MeshGradientPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.zIndex import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.meshGradient import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable internal fun MeshGradientPreview( meshGradientState: UiMeshGradientState, gradientAlpha: Float, allowPickingImage: Boolean?, gradientSize: IntegerSize, imageAspectRatio: Float, selectedUri: Uri ) { val alpha by animateFloatAsState(gradientAlpha) AnimatedContent( targetState = if (allowPickingImage == true) { imageAspectRatio } else { gradientSize .aspectRatio .roundToTwoDigits() .coerceIn(0.01f..100f) } ) { aspectRatio -> Box { Spacer( modifier = Modifier .aspectRatio(aspectRatio) .clip(MaterialTheme.shapes.medium) .then( if (allowPickingImage != true) { Modifier.transparencyChecker() } else Modifier ) .meshGradient( points = meshGradientState.points, resolutionX = meshGradientState.resolutionX, resolutionY = meshGradientState.resolutionY, alpha = alpha ) .zIndex(2f) ) if (allowPickingImage == true) { Picture( model = selectedUri, modifier = Modifier.matchParentSize(), shape = MaterialTheme.shapes.medium, size = 1500 ) } } } } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/TileModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun TileModeSelector( value: TileMode, onValueChange: (TileMode) -> Unit, modifier: Modifier = Modifier ) { val entries = remember { listOf( TileMode.Clamp, TileMode.Repeated, TileMode.Mirror, TileMode.Decal ) } Box( modifier = modifier .container( shape = ShapeDefaults.extraLarge ) .animateContentSizeNoClip(), contentAlignment = Alignment.Center ) { EnhancedButtonGroup( modifier = Modifier.padding(8.dp), enabled = true, items = entries.map { it.translatedName }, selectedIndex = entries.indexOf(value), title = stringResource(id = R.string.tile_mode), onIndexChange = { onValueChange(entries[it]) } ) } } private val TileMode.translatedName: String @Composable get() = when (this) { TileMode.Repeated -> stringResource(id = R.string.tile_mode_repeated) TileMode.Mirror -> stringResource(id = R.string.tile_mode_mirror) TileMode.Decal -> stringResource(id = R.string.tile_mode_decal) else -> stringResource(id = R.string.tile_mode_clamp) } ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/UiGradientState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.center import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.DpSize import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientState import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientType import com.t8rin.imagetoolbox.feature.gradient_maker.domain.MeshGradientState import kotlin.math.PI import kotlin.math.cos import kotlin.math.min import kotlin.math.pow import kotlin.math.sin import kotlin.math.sqrt import kotlin.random.Random @Composable fun rememberGradientState( size: DpSize = DpSize.Zero ): UiGradientState { val density = LocalDensity.current return remember { val sizePx = if (size == DpSize.Zero) { Size.Zero } else { with(density) { Size( size.width.toPx(), size.height.toPx() ) } } UiGradientState(sizePx) } } class UiGradientState( size: Size = Size.Zero ) : GradientState { override var size by mutableStateOf(size) override val brush: ShaderBrush? get() = getBrush(size) override fun getBrush(size: Size): ShaderBrush? { if (colorStops.isEmpty()) return null val colorStops = colorStops.sortedBy { it.first }.let { pairs -> if (gradientType != GradientType.Sweep) { pairs.distinctBy { it.first } } else pairs }.let { if (it.size == 1) { listOf(it.first(), it.first()) } else it }.toTypedArray() val brush = runCatching { when (gradientType) { GradientType.Linear -> { val angleRad = linearGradientAngle / 180f * PI val x = cos(angleRad).toFloat() val y = sin(angleRad).toFloat() val radius = sqrt(size.width.pow(2) + size.height.pow(2)) / 2f val offset = size.center + Offset(x * radius, y * radius) val exactOffset = Offset( x = min(offset.x.coerceAtLeast(0f), size.width), y = size.height - min(offset.y.coerceAtLeast(0f), size.height) ) Brush.linearGradient( colorStops = colorStops, start = Offset(size.width, size.height) - exactOffset, end = exactOffset, tileMode = tileMode ) } GradientType.Radial -> Brush.radialGradient( colorStops = colorStops, center = Offset( x = size.width * centerFriction.x, y = size.height * centerFriction.y ), radius = ((size.width.coerceAtLeast(size.height)) / 2 * radiusFriction) .coerceAtLeast(0.01f), tileMode = tileMode ) GradientType.Sweep -> Brush.sweepGradient( colorStops = colorStops, center = Offset( x = size.width * centerFriction.x, y = size.height * centerFriction.y ) ) } as ShaderBrush }.getOrNull() return brush } override var gradientType: GradientType by mutableStateOf(GradientType.Linear) override val colorStops = mutableStateListOf>() override var tileMode by mutableStateOf(TileMode.Clamp) override var linearGradientAngle by mutableFloatStateOf(0f) override var centerFriction by mutableStateOf(Offset(.5f, .5f)) override var radiusFriction by mutableFloatStateOf(.5f) } class UiMeshGradientState : MeshGradientState { override val points = mutableStateListOf>>().apply { addAll(generateMesh(2)) } val gridSize: Int get() = points.firstOrNull()?.size ?: 0 override var resolutionX: Int by mutableIntStateOf(16) override var resolutionY: Int by mutableIntStateOf(16) } fun generateMesh(size: Int): List>> { return List(size) { y -> List(size) { x -> Offset(x / (size - 1f), y / (size - 1f)) to Color.random() } } } private fun Color.Companion.random(): Color = Color(Random.nextInt()).copy(1f) ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/components/model/GradientMakerType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model enum class GradientMakerType { Default, Overlay, Mesh, MeshOverlay } fun GradientMakerType?.isMesh() = this == GradientMakerType.Mesh || this == GradientMakerType.MeshOverlay fun GradientMakerType?.canPickImage() = this == GradientMakerType.Overlay || this == GradientMakerType.MeshOverlay ================================================ FILE: feature/gradient-maker/src/main/java/com/t8rin/imagetoolbox/feature/gradient_maker/presentation/screenLogic/GradientMakerComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode import androidx.core.net.toUri import coil3.transform.Transformation import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.data.utils.toCoil import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.transformation.GenericTransformation import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientMaker import com.t8rin.imagetoolbox.feature.gradient_maker.domain.GradientType import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.UiGradientState import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.UiMeshGradientState import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.GradientMakerType import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.components.model.isMesh import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlin.math.roundToInt class GradientMakerComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val shareProvider: ImageShareProvider, private val imageGetter: ImageGetter, private val gradientMaker: GradientMaker, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) resetState() } } private val _screenType: MutableState = mutableStateOf(null) val screenType by _screenType private val _showOriginal: MutableState = mutableStateOf(false) val showOriginal by _showOriginal private var _gradientState = UiGradientState() private val gradientState: UiGradientState get() = _gradientState private var _meshGradientState = UiMeshGradientState() val meshGradientState: UiMeshGradientState get() = _meshGradientState val meshResolutionX: Int get() = meshGradientState.resolutionX val meshResolutionY: Int get() = meshGradientState.resolutionY val meshPoints: List>> get() = meshGradientState.points val brush: ShaderBrush? get() = gradientState.brush val gradientType: GradientType get() = gradientState.gradientType val colorStops: List> get() = gradientState.colorStops val tileMode: TileMode get() = gradientState.tileMode val angle: Float get() = gradientState.linearGradientAngle val centerFriction: Offset get() = gradientState.centerFriction val radiusFriction: Float get() = gradientState.radiusFriction private var _gradientAlpha: MutableState = mutableFloatStateOf(1f) val gradientAlpha by _gradientAlpha private val _keepExif = mutableStateOf(false) val keepExif by _keepExif private val _selectedUri = mutableStateOf(Uri.EMPTY) val selectedUri: Uri by _selectedUri private val _uris = mutableStateOf(emptyList()) val uris by _uris private val _imageAspectRatio: MutableState = mutableFloatStateOf(1f) val imageAspectRatio by _imageAspectRatio private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _imageFormat = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat private val _gradientSize: MutableState = mutableStateOf(IntegerSize(1000, 1000)) val gradientSize by _gradientSize suspend fun createGradientBitmap( data: Any, integerSize: IntegerSize = gradientSize, useBitmapOriginalSizeIfAvailable: Boolean = false ): Bitmap? { return if (selectedUri == Uri.EMPTY) { if (screenType.isMesh()) { gradientMaker.createMeshGradient( integerSize = integerSize, gradientState = meshGradientState ) } else { gradientMaker.createGradient( integerSize = integerSize, gradientState = gradientState ) } } else { imageGetter.getImage( data = data, originalSize = useBitmapOriginalSizeIfAvailable )?.let { if (screenType.isMesh()) { gradientMaker.createMeshGradient( src = it, gradientState = meshGradientState, gradientAlpha = gradientAlpha ) } else { gradientMaker.createGradient( src = it, gradientState = gradientState, gradientAlpha = gradientAlpha ) } } } } private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _left.value = -1 _isSaving.value = true if (uris.isEmpty()) { createGradientBitmap( data = Unit, useBitmapOriginalSizeIfAvailable = true )?.let { localBitmap -> val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = "Gradient", sequenceNumber = null, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = imageInfo ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } } else { val results = mutableListOf() _done.value = 0 _left.value = uris.size uris.forEach { uri -> createGradientBitmap( data = uri, useBitmapOriginalSizeIfAvailable = true )?.let { localBitmap -> val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height, originalUri = uri.toString() ) results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = imageInfo ) ), keepOriginalMetadata = keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) } _isSaving.value = false } } fun shareBitmaps() { savingJob = trackProgress { _left.value = -1 _isSaving.value = true if (uris.isEmpty()) { createGradientBitmap( data = Unit, useBitmapOriginalSizeIfAvailable = true )?.let { shareProvider.shareImage( image = it, imageInfo = ImageInfo( imageFormat = imageFormat, width = it.width, height = it.height ), onComplete = AppToastHost::showConfetti ) } } else { _done.value = 0 _left.value = uris.size shareProvider.shareImages( uris.map { it.toString() }, imageLoader = { uri -> createGradientBitmap( data = uri, useBitmapOriginalSizeIfAvailable = true )?.let { it to ImageInfo( width = it.width, height = it.height, imageFormat = imageFormat ) } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _isSaving.value = false _done.value = 0 } else { _done.value = it } updateProgress( done = done, total = left ) } ) } _isSaving.value = false _left.value = -1 } } fun cancelSaving() { savingJob = null _isSaving.value = false _left.value = -1 } fun updateHeight(value: Int) { _gradientSize.update { it.copy(height = value) } registerChanges() } fun updateWidth(value: Int) { _gradientSize.update { it.copy(width = value) } registerChanges() } fun setGradientType(gradientType: GradientType) { gradientState.gradientType = gradientType registerChanges() } fun setPreviewSize(size: Size) { gradientState.size = size } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } registerChanges() } fun updateLinearAngle(angle: Float) { gradientState.linearGradientAngle = angle registerChanges() } fun setRadialProperties( center: Offset, radius: Float ) { gradientState.centerFriction = center gradientState.radiusFriction = radius registerChanges() } fun setTileMode(tileMode: TileMode) { gradientState.tileMode = tileMode registerChanges() } fun setResolution(resolution: Float) { meshGradientState.resolutionX = resolution.roundToInt() meshGradientState.resolutionY = resolution.roundToInt() registerChanges() } fun setScreenType( type: GradientMakerType? ) { _screenType.update { type } } fun addColorStop( pair: Pair, isInitial: Boolean = false ) { gradientState.colorStops.add(pair) if (!isInitial) { registerChanges() } } fun updateColorStop( index: Int, pair: Pair ) { gradientState.colorStops[index] = pair.copy() registerChanges() } fun removeColorStop(index: Int) { if (gradientState.colorStops.size > 2) { gradientState.colorStops.removeAt(index) registerChanges() } } fun updateSelectedUri(uri: Uri) { componentScope.launch { _selectedUri.value = uri _isImageLoading.value = true imageGetter.getImageAsync( uri = uri.toString(), originalSize = false, onGetImage = { imageData -> _imageAspectRatio.update { imageData.image.safeAspectRatio } _isImageLoading.value = false setImageFormat(imageData.imageInfo.imageFormat) }, onFailure = { _isImageLoading.value = false } ) } } fun updateGradientAlpha(value: Float) { _gradientAlpha.update { value } registerChanges() } override fun resetState() { _selectedUri.update { Uri.EMPTY } _uris.update { emptyList() } _gradientAlpha.update { 1f } _gradientState = UiGradientState() _meshGradientState = UiMeshGradientState() setScreenType(null) registerChangesCleared() } fun updateUrisSilently( removedUri: Uri ) = componentScope.launch { if (selectedUri == removedUri) { val index = uris.indexOf(removedUri) if (index == 0) { uris.getOrNull(1)?.let(::updateSelectedUri) } else { uris.getOrNull(index - 1)?.let(::updateSelectedUri) } } _uris.update { it.toMutableList().apply { remove(removedUri) } } } fun setUris(uris: List) { _uris.update { uris } uris.firstOrNull()?.let(::updateSelectedUri) } fun getGradientTransformation(): Transformation = GenericTransformation( Triple(brush, meshPoints, screenType.isMesh()) ) { input -> createGradientBitmap( data = input, useBitmapOriginalSizeIfAvailable = false ) ?: input }.toCoil() fun toggleKeepExif(value: Boolean) { _keepExif.update { value } registerChanges() } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true createGradientBitmap( data = selectedUri, useBitmapOriginalSizeIfAvailable = true )?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( imageFormat = imageFormat, width = image.width, height = image.height ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { val list = mutableListOf() _left.value = -1 _isSaving.value = true if (uris.isEmpty()) { createGradientBitmap( data = Unit, useBitmapOriginalSizeIfAvailable = true )?.let { localBitmap -> val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) shareProvider.cacheImage( image = localBitmap, imageInfo = imageInfo )?.toUri()?.let(list::add) } } else { _done.value = 0 _left.value = uris.size uris.forEach { uri -> createGradientBitmap( data = uri, useBitmapOriginalSizeIfAvailable = true )?.let { localBitmap -> val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height, originalUri = uri.toString() ) shareProvider.cacheImage( image = localBitmap, imageInfo = imageInfo )?.toUri()?.let(list::add) } _done.value += 1 updateProgress( done = done, total = left ) } } _isSaving.value = false onComplete(list) _isSaving.value = false } } fun selectLeftUri() { uris .indexOf(selectedUri) .takeIf { it >= 0 } ?.let { uris.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris .indexOf(selectedUri) .takeIf { it >= 0 } ?.let { uris.rightFrom(it) } ?.let(::updateSelectedUri) } fun getFormatForFilenameSelection(): ImageFormat? = if (uris.size < 2) imageFormat else null fun setShowOriginal(value: Boolean) { _showOriginal.update { value } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): GradientMakerComponent } } ================================================ FILE: feature/image-cutting/.gitignore ================================================ /build ================================================ FILE: feature/image-cutting/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.image_cutting" dependencies { implementation(projects.feature.compare) } ================================================ FILE: feature/image-cutting/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/data/AndroidImageCutter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.data import android.graphics.Bitmap import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.image_cutting.domain.CutParams import com.t8rin.imagetoolbox.image_cutting.domain.ImageCutter import com.t8rin.imagetoolbox.image_cutting.domain.PivotPair import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.math.roundToInt internal class AndroidImageCutter @Inject constructor( private val imageGetter: ImageGetter, dispatchersHolder: DispatchersHolder ) : ImageCutter, DispatchersHolder by dispatchersHolder { override suspend fun cutAndMerge( imageUri: String, params: CutParams ): Bitmap? { return cutAndMerge( image = imageGetter.getImage( data = imageUri, originalSize = true ) ?: return null, params = params ) } override suspend fun cutAndMerge( image: Bitmap, params: CutParams ): Bitmap = withContext(defaultDispatcher) { runSuspendCatching { val (verticalStart, verticalEnd) = params.vertical.toCutBounds(image.width) val (horizontalStart, horizontalEnd) = params.horizontal.toCutBounds(image.height) image.cutAndMerge( verticalStart = verticalStart, verticalEnd = verticalEnd, horizontalStart = horizontalStart, horizontalEnd = horizontalEnd, inverseVertical = params.inverseVertical, inverseHorizontal = params.inverseHorizontal ) }.getOrNull() ?: image } private fun PivotPair?.toCutBounds( size: Int ): Pair { val bounds = this ?.takeIf { it != PivotPair(0f, 1f) } ?.let { (it.startRtlAdjusted * size).roundToInt() to (it.endRtlAdjusted * size).roundToInt() } ?: return null to null val (start, end) = bounds return if (start in 0..size && end in 0..size && start < end) { start to end } else { null to null } } private suspend fun Bitmap.cutAndMerge( verticalStart: Int? = null, verticalEnd: Int? = null, horizontalStart: Int? = null, horizontalEnd: Int? = null, inverseVertical: Boolean = false, inverseHorizontal: Boolean = false ): Bitmap = coroutineScope { if (inverseVertical && inverseHorizontal) { Bitmap.createBitmap( this@cutAndMerge, verticalStart ?: 0, horizontalStart ?: 0, (verticalEnd ?: width) - (verticalStart ?: 0), (horizontalEnd ?: height) - (horizontalStart ?: 0) ) } else { cutVertically( start = verticalStart, end = verticalEnd, inverse = inverseVertical ).cutHorizontally( start = horizontalStart, end = horizontalEnd, inverse = inverseHorizontal ) } } private suspend fun Bitmap.cutHorizontally( start: Int?, end: Int?, inverse: Boolean ): Bitmap = coroutineScope { val source = this@cutHorizontally if (inverse) { if (start != null && end != null) { return@coroutineScope Bitmap.createBitmap( source, 0, start, source.width, end - start ) } else if (start == null && end != null) { return@coroutineScope Bitmap.createBitmap( source, 0, 0, source.width, end ) } else if (start != null) { return@coroutineScope Bitmap.createBitmap( source, 0, start, source.width, source.height - start ) } } val parts = mutableListOf() if (start != null || end != null) { if (start != null && start > 0) { parts.add( Bitmap.createBitmap( source, 0, 0, source.width, start ) ) } if (end != null && end < source.height) { parts.add( Bitmap.createBitmap( source, 0, end, source.width, source.height - end ) ) } } else { parts.add(source.copy(source.safeConfig, true)) } val mergedWidth = parts.maxOf { it.width } val mergedHeight = parts.sumOf { it.height } createBitmap(mergedWidth, mergedHeight, source.safeConfig) .applyCanvas { var offsetY = 0f for (part in parts) { drawBitmap(part, 0f, offsetY) offsetY += part.height part.recycle() } } } private suspend fun Bitmap.cutVertically( start: Int?, end: Int?, inverse: Boolean ): Bitmap = coroutineScope { val source = this@cutVertically if (inverse) { if (start != null && end != null) { return@coroutineScope Bitmap.createBitmap( source, start, 0, end - start, source.height ) } else if (start == null && end != null) { return@coroutineScope Bitmap.createBitmap( source, 0, 0, end, source.height ) } else if (start != null) { return@coroutineScope Bitmap.createBitmap( source, start, 0, source.width - start, source.height ) } } val parts = mutableListOf() if (start != null || end != null) { if (start != null && start > 0) { parts.add( Bitmap.createBitmap( source, 0, 0, start, source.height ) ) } if (end != null && end < source.width) { parts.add( Bitmap.createBitmap( source, end, 0, source.width - end, source.height ) ) } } else { parts.add(source.copy(source.safeConfig, true)) } val mergedWidth = parts.sumOf { it.width } val mergedHeight = parts.maxOf { it.height } createBitmap(mergedWidth, mergedHeight, source.safeConfig) .applyCanvas { var offsetX = 0f for (part in parts) { drawBitmap(part, offsetX, 0f) offsetX += part.width part.recycle() } } } } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/di/ImageCutterModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.image_cutting.data.AndroidImageCutter import com.t8rin.imagetoolbox.image_cutting.domain.ImageCutter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ImageCutterModule { @Binds @Singleton fun cutter( impl: AndroidImageCutter ): ImageCutter } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/CutParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.domain data class CutParams( val vertical: PivotPair?, val horizontal: PivotPair?, val inverseVertical: Boolean, val inverseHorizontal: Boolean ) { companion object { val Default by lazy { CutParams( vertical = null, horizontal = null, inverseVertical = false, inverseHorizontal = false ) } } } class PivotPair( val start: Float, val end: Float, val isRtl: Boolean = false ) { val startRtlAdjusted = if (isRtl) 1f - end else start val endRtlAdjusted = if (isRtl) 1f - start else end override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as PivotPair if (start != other.start) return false if (end != other.end) return false return true } override fun hashCode(): Int { var result = start.hashCode() result = 31 * result + end.hashCode() return result } } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/domain/ImageCutter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.domain interface ImageCutter { suspend fun cutAndMerge( imageUri: String, params: CutParams ): Image? suspend fun cutAndMerge( image: Image, params: CutParams ): Image? } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/ImageCutterContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.presentation import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.toBitmap import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.CompareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareSheet import com.t8rin.imagetoolbox.image_cutting.presentation.components.CutParamsSelector import com.t8rin.imagetoolbox.image_cutting.presentation.components.CutPreview import com.t8rin.imagetoolbox.image_cutting.presentation.components.rememberCutTransformations import com.t8rin.imagetoolbox.image_cutting.presentation.screenLogic.ImageCutterComponent @Composable fun ImageCutterContent( component: ImageCutterComponent ) { val imagePicker = rememberImagePicker(onSuccess = component::updateUris) val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.image_cutting), input = component.uris, isLoading = component.isImageLoading, size = null ) }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.uris != null, onShare = component::shareBitmaps, onEdit = { component.cacheImages { editSheetData = it } }, onCopy = { component.cacheCurrentImage(Clipboard::copy) } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, imagePreview = { Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip( alignment = Alignment.Center ) .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), contentAlignment = Alignment.Center ) { CutPreview( uri = component.selectedUri, params = component.params, isLoadingFromDifferentPlace = component.isImageLoading ) } }, controls = { ImageCounter( imageCount = component.uris?.size?.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) Spacer(modifier = Modifier.height(8.dp)) val params = component.params CutParamsSelector( value = params, onValueChange = component::updateParams ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, quality = component.quality, ) if (component.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = component.imageFormat, quality = component.quality, onQualityChange = component::setQuality ) }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, topAppBarPersistentActions = { if (component.uris.isNullOrEmpty()) { TopAppBarEmoji() } var showZoomSheet by rememberSaveable { mutableStateOf(false) } var showCompareSheet by rememberSaveable { mutableStateOf(false) } CompareButton( visible = !component.uris.isNullOrEmpty(), onClick = { showCompareSheet = true } ) ZoomButton( visible = !component.uris.isNullOrEmpty(), onClick = { showZoomSheet = true } ) CompareSheet( beforeContent = { var aspectRatio by remember { mutableFloatStateOf(1f) } Picture( model = component.selectedUri, modifier = Modifier.aspectRatio(aspectRatio), onSuccess = { aspectRatio = it.result.image.toBitmap().safeAspectRatio } ) }, afterContent = { var aspectRatio by remember { mutableFloatStateOf(1f) } Picture( model = component.selectedUri, transformations = component.rememberCutTransformations(component.selectedUri), modifier = Modifier.aspectRatio(aspectRatio), onSuccess = { aspectRatio = it.result.image.toBitmap().safeAspectRatio } ) }, visible = showCompareSheet, onDismiss = { showCompareSheet = false } ) ZoomModalSheet( data = component.selectedUri, transformations = component.rememberCutTransformations(component.selectedUri), visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) }, canShowScreenData = !component.uris.isNullOrEmpty(), noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } } ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) PickImageFromUrisSheet( visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, transformations = component.rememberCutTransformations(component.uris), uris = component.uris, selectedUri = component.selectedUri, onUriPicked = { uri -> component.updateSelectedUri(uri) }, onUriRemoved = { uri -> component.updateUrisSilently(removedUri = uri) }, columns = if (isPortrait) 2 else 4, ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris?.size ?: -1, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BorderHorizontal import androidx.compose.material.icons.rounded.BorderVertical import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SelectInverse import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedRangeSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.image_cutting.domain.CutParams import com.t8rin.imagetoolbox.image_cutting.domain.PivotPair @Composable internal fun CutParamsSelector( value: CutParams, onValueChange: (CutParams) -> Unit ) { val params by rememberUpdatedState(value) val layoutDirection = LocalLayoutDirection.current Column { EnhancedRangeSliderItem( value = params.vertical?.let { it.start..it.end } ?: 0f..1f, valueRange = 0f..1f, icon = Icons.Rounded.BorderVertical, title = stringResource(R.string.vertical_pivot_line), internalStateTransformation = { it.start.roundTo(3)..it.endInclusive.roundTo(3) }, onValueChange = { onValueChange( params.copy( vertical = PivotPair( start = it.start, end = it.endInclusive, isRtl = layoutDirection == LayoutDirection.Rtl ) ) ) }, additionalContent = { PreferenceRowSwitch( title = stringResource(R.string.inverse_selection), subtitle = stringResource(R.string.inverse_vertical_selection_sub), startIcon = Icons.Rounded.SelectInverse, checked = params.inverseVertical, onClick = { onValueChange( params.copy( inverseVertical = it ) ) }, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.small, modifier = Modifier.padding( start = 4.dp, end = 4.dp, bottom = 4.dp ) ) } ) Spacer(Modifier.height(8.dp)) EnhancedRangeSliderItem( value = params.horizontal?.let { it.start..it.end } ?: 0f..1f, valueRange = 0f..1f, icon = Icons.Rounded.BorderHorizontal, title = stringResource(R.string.horizontal_pivot_line), internalStateTransformation = { it.start.roundTo(3)..it.endInclusive.roundTo(3) }, onValueChange = { onValueChange( params.copy( horizontal = PivotPair( start = it.start, end = it.endInclusive, isRtl = layoutDirection == LayoutDirection.Rtl ) ) ) }, additionalContent = { PreferenceRowSwitch( title = stringResource(R.string.inverse_selection), subtitle = stringResource(R.string.inverse_horizontal_selection_sub), startIcon = Icons.Rounded.SelectInverse, checked = params.inverseHorizontal, onClick = { onValueChange( params.copy( inverseHorizontal = it ) ) }, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.small, modifier = Modifier.padding( start = 4.dp, end = 4.dp, bottom = 4.dp ) ) } ) } } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/CutPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.presentation.components import android.net.Uri import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.core.net.toUri import coil3.toBitmap import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.Black import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.other.rememberAnimatedBorderPhase import com.t8rin.imagetoolbox.image_cutting.domain.CutParams import com.t8rin.imagetoolbox.image_cutting.domain.PivotPair import kotlin.math.abs import kotlin.math.hypot @Composable internal fun CutPreview( uri: Uri?, params: CutParams, modifier: Modifier = Modifier, isLoadingFromDifferentPlace: Boolean = false ) { var aspectRatio by rememberSaveable(uri) { mutableFloatStateOf(1f) } Box( modifier = modifier .aspectRatio(aspectRatio) .clip(MaterialTheme.shapes.medium) ) { Picture( model = uri, size = 1500, contentScale = ContentScale.FillBounds, modifier = Modifier.fillMaxSize(), onSuccess = { aspectRatio = it.result.image.toBitmap().safeAspectRatio }, shape = RectangleShape, isLoadingFromDifferentPlace = isLoadingFromDifferentPlace ) CutFrameBorder( modifier = Modifier.fillMaxSize(), params = params ) } } @Composable private fun CutFrameBorder( modifier: Modifier = Modifier, params: CutParams ) { val keptRects = remember(params) { params.toPreviewRects() } ?: return val meaningfulEdgeGroups = remember(keptRects) { keptRects.map(Rect::toMeaningfulEdges).filter(List::isNotEmpty) } val isNightMode = LocalSettingsState.current.isNightMode val colorScheme = MaterialTheme.colorScheme val overlayColor = Black.copy(alpha = if (isNightMode) 0.5f else 0.3f) val animatedBorderPhase = rememberAnimatedBorderPhase() Canvas( modifier = modifier.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } ) { val strokeWidth = 1.5.dp.toPx() drawRect( color = overlayColor, size = size ) keptRects.forEach { rect -> val topLeft = Offset( x = rect.left * size.width, y = rect.top * size.height ) val rectSize = Size( width = rect.width * size.width, height = rect.height * size.height ) drawRect( color = Color.Transparent, blendMode = BlendMode.Clear, topLeft = topLeft, size = rectSize ) } meaningfulEdgeGroups.forEach { edges -> var accumulatedLength = 0f edges.forEach { edge -> val start = Offset( x = edge.start.x * size.width, y = edge.start.y * size.height ) val end = Offset( x = edge.end.x * size.width, y = edge.end.y * size.height ) val lineLength = hypot(end.x - start.x, end.y - start.y) drawLine( color = colorScheme.primary, start = start, end = end, strokeWidth = strokeWidth ) drawLine( color = colorScheme.primaryContainer, start = start, end = end, strokeWidth = strokeWidth, pathEffect = PathEffect.dashPathEffect( intervals = DashIntervals, phase = animatedBorderPhase - accumulatedLength ) ) accumulatedLength += lineLength } } } } private fun CutParams.toPreviewRects(): List? { val vertical = vertical.toPreviewRangeOrNull() val horizontal = horizontal.toPreviewRangeOrNull() if (vertical == null && horizontal == null) return null val xSegments = vertical.toPreviewSegments(inverse = inverseVertical) val ySegments = horizontal.toPreviewSegments(inverse = inverseHorizontal) return buildList { xSegments.forEach { xRange -> ySegments.forEach { yRange -> Rect( left = xRange.start, top = yRange.start, right = xRange.endInclusive, bottom = yRange.endInclusive ).takeIf { it.width > 0f && it.height > 0f }?.let(::add) } } } } private fun PivotPair?.toPreviewRangeOrNull(): ClosedFloatingPointRange? { return this ?.takeIf { it != PivotPair(0f, 1f) } ?.let { it.startRtlAdjusted.coerceIn(0f, 1f)..it.endRtlAdjusted.coerceIn(0f, 1f) } } private fun ClosedFloatingPointRange?.toPreviewSegments( inverse: Boolean ): List> { return when { this == null -> listOf(0f..1f) inverse -> listOf(this) else -> buildList { if (start > 0f) add(0f..start) if (endInclusive < 1f) add(endInclusive..1f) } } } private data class PreviewEdge( val start: Offset, val end: Offset ) private val DashIntervals = floatArrayOf(20f, 20f) private fun Rect.toMeaningfulEdges(): List { return buildList { if (!top.isCloseTo(0f)) { add( PreviewEdge( start = Offset(left, top), end = Offset(right, top) ) ) } if (!right.isCloseTo(1f)) { add( PreviewEdge( start = Offset(right, top), end = Offset(right, bottom) ) ) } if (!bottom.isCloseTo(1f)) { add( PreviewEdge( start = Offset(right, bottom), end = Offset(left, bottom) ) ) } if (!left.isCloseTo(0f)) { add( PreviewEdge( start = Offset(left, bottom), end = Offset(left, top) ) ) } } } private fun Float.isCloseTo( other: Float, epsilon: Float = 0.001f ): Boolean = abs(this - other) <= epsilon @EnPreview @Composable private fun Preview() = ImageToolboxThemeForPreview(false) { CutPreview( uri = "111".toUri(), params = CutParams( vertical = PivotPair(start = 0.2f, end = 0.8f), horizontal = PivotPair(start = 0.3f, end = 0.65f), inverseVertical = false, inverseHorizontal = true ) ) } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/components/Utils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.presentation.components import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import coil3.transform.Transformation import com.t8rin.imagetoolbox.image_cutting.presentation.screenLogic.ImageCutterComponent @Composable internal fun ImageCutterComponent.rememberCutTransformations( key: Any? ): List { return remember( params, imageFormat, quality, key ) { derivedStateOf { getCutTransformation() } }.value } ================================================ FILE: feature/image-cutting/src/main/java/com/t8rin/imagetoolbox/image_cutting/presentation/screenLogic/ImageCutterComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_cutting.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import coil3.transform.Transformation import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.data.utils.toCoil import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.transformation.GenericTransformation import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.image_cutting.domain.CutParams import com.t8rin.imagetoolbox.image_cutting.domain.ImageCutter import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class ImageCutterComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val shareProvider: ImageShareProvider, private val imageCutter: ImageCutter, private val imagePreviewCreator: ImagePreviewCreator, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::updateUris) } } private val _uris = mutableStateOf?>(null) val uris by _uris private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _selectedUri: MutableState = mutableStateOf(null) val selectedUri by _selectedUri private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Default) val imageFormat: ImageFormat by _imageFormat private val _quality: MutableState = mutableStateOf(Quality.Base(100)) val quality: Quality by _quality private val _params: MutableState = mutableStateOf(CutParams.Default) val params by _params fun updateParams(cutParams: CutParams) { _params.update { cutParams } registerChanges() } fun updateUris(uris: List?) { _uris.value = null _uris.value = uris _selectedUri.value = uris?.firstOrNull() } fun updateUrisSilently(removedUri: Uri) { componentScope.launch { _uris.value = uris if (_selectedUri.value == removedUri) { val index = uris?.indexOf(removedUri) ?: -1 if (index == 0) { uris?.getOrNull(1)?.let { updateSelectedUri(it) } } else { uris?.getOrNull(index - 1)?.let { updateSelectedUri(it) } } } val u = _uris.value?.toMutableList()?.apply { remove(removedUri) } _uris.value = u registerChanges() } } fun setQuality(quality: Quality) { if (_quality.value != quality) { _quality.value = quality registerChanges() } } fun setImageFormat(imageFormat: ImageFormat) { if (_imageFormat.value != imageFormat) { _imageFormat.value = imageFormat registerChanges() } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 uris?.forEach { uri -> runSuspendCatching { imageCutter.cutAndMerge( imageUri = uri.toString(), params = params ) }.getOrNull()?.let { bitmap -> ImageInfo( width = bitmap.width, height = bitmap.height, originalUri = uri.toString(), quality = quality, imageFormat = imageFormat ).let { imageInfo -> results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = imageCompressor.compress( image = bitmap, imageFormat = imageFormat, quality = quality ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun updateSelectedUri(uri: Uri) { _selectedUri.value = uri } fun shareBitmaps() { savingJob = trackProgress { _isSaving.value = true shareProvider.shareImages( uris = uris?.map { it.toString() } ?: emptyList(), imageLoader = { uri -> imageGetter.getImage(uri)?.image?.let { bmp -> bmp to ImageInfo( width = bmp.width, height = bmp.height, originalUri = uri, quality = quality, imageFormat = imageFormat ) } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _isSaving.value = false _done.value = 0 } else { _done.value = it } updateProgress( done = done, total = uris.orEmpty().size ) } ) } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true imageCutter.cutAndMerge( imageUri = selectedUri.toString(), params = params )?.let { bmp -> bmp to ImageInfo( width = bmp.width, height = bmp.height, originalUri = selectedUri.toString(), quality = quality, imageFormat = imageFormat ) }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = selectedUri.toString()) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 val list = mutableListOf() uris?.forEach { uri -> imageCutter.cutAndMerge( imageUri = uri.toString(), params = params )?.let { bmp -> bmp to ImageInfo( width = bmp.width, height = bmp.height, originalUri = uri.toString(), quality = quality, imageFormat = imageFormat ) }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = uri.toString()) )?.let { uri -> list.add(uri.toUri()) } } _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } onComplete(list) _isSaving.value = false } } fun selectLeftUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.rightFrom(it) } ?.let(::updateSelectedUri) } fun getFormatForFilenameSelection(): ImageFormat? = if (uris?.size == 1) imageFormat else null fun getCutTransformation(): List = listOf( GenericTransformation { image: Bitmap -> val bitmap = imageCutter.cutAndMerge( image = image, params = params ) ?: image imagePreviewCreator.createPreview( image = bitmap, imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, quality = quality, imageFormat = imageFormat ), transformations = emptyList(), onGetByteCount = {} ) ?: image }.toCoil() ) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ImageCutterComponent } } ================================================ FILE: feature/image-preview/.gitignore ================================================ /build ================================================ FILE: feature/image-preview/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.image_preview" ================================================ FILE: feature/image-preview/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/ImagePreviewContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_preview.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.outlined.ImageSearch import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Share import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.icons.FolderOpened import com.t8rin.imagetoolbox.core.resources.icons.ImageEdit import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFolderPicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.controls.SortButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitBackHandler import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButtonType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.ImagePreviewGrid import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.core.utils.sortedByType import com.t8rin.imagetoolbox.feature.image_preview.presentation.screenLogic.ImagePreviewComponent @Composable fun ImagePreviewContent( component: ImagePreviewComponent ) { var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.uris.isNullOrEmpty()) component.onGoBack() else showExitDialog = true } val initialShowImagePreviewDialog = !component.initialUris.isNullOrEmpty() val settingsState = LocalSettingsState.current val imagePicker = rememberImagePicker(onSuccess = component::updateUris) val isLoadingImages = component.isImageLoading var previousFolder by rememberSaveable { mutableStateOf(null) } val openDirectoryLauncher = rememberFolderPicker( onSuccess = { uri -> previousFolder = uri component.updateUrisFromTree(uri) } ) val pickImage = imagePicker::pickImage val selectedUris by remember(component.uris, component.imageFrames) { derivedStateOf { component.getSelectedUris() ?: emptyList() } } var wantToEdit by rememberSaveable(selectedUris.isNotEmpty()) { mutableStateOf(false) } var gridInvalidations by remember { mutableIntStateOf(0) } Surface( color = MaterialTheme.colorScheme.background ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Box( modifier = Modifier .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection) ) { var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } Scaffold( modifier = Modifier.fillMaxSize(), topBar = { EnhancedTopAppBar( type = EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior, title = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.image_preview) ) AnimatedVisibility(!component.uris.isNullOrEmpty()) { EnhancedBadge( content = { val prefix = if (isLoadingImages) "~" else "" Text( text = "$prefix${component.uris.orEmpty().size}" ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } } }, navigationIcon = { EnhancedIconButton( onClick = component.onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, actions = { val isCanClear = selectedUris.isNotEmpty() val isCanSelectAll = component.uris?.size != selectedUris.size && component.uris != null AnimatedContent( targetState = (!isCanSelectAll && !isCanClear) to selectedUris.isEmpty(), modifier = Modifier.size(40.dp) ) { (notSelection, haveUris) -> if (notSelection && haveUris) { TopAppBarEmoji() } else if (haveUris) { SortButton( modifier = Modifier.size(40.dp), iconSize = 24.dp, containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onSurface, onSortTypeSelected = { sortType -> component.asyncUpdateUris( onFinish = { gridInvalidations++ }, action = { it.orEmpty().sortedByType( sortType = sortType ) } ) } ) } } AnimatedVisibility( visible = isCanSelectAll && isCanClear, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = { component.updateImageFrames(ImageFrames.All) } ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = isCanClear ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Spacer(Modifier.width(8.dp)) Text( text = selectedUris.size.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) EnhancedIconButton( onClick = { component.updateImageFrames( ImageFrames.ManualSelection(emptyList()) ) } ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } } ) }, contentWindowInsets = WindowInsets() ) { contentPadding -> AnimatedContent( targetState = !component.uris.isNullOrEmpty() || isLoadingImages, transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier .fillMaxSize() .padding(contentPadding) ) { canShowGrid -> if (canShowGrid) { Crossfade(gridInvalidations) { key -> key(key) { ImagePreviewGrid( data = component.uris, onAddImages = component::updateUris, onShareImage = { component.shareImages( uriList = listOf(element = it) ) }, onRemove = component::removeUri, initialShowImagePreviewDialog = initialShowImagePreviewDialog, onNavigate = component.onNavigate, imageFrames = component.imageFrames, onFrameSelectionChange = component::updateImageFrames ) } } } else { Column( modifier = Modifier .fillMaxSize() .enhancedVerticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { ImageNotPickedWidget( onPickImage = pickImage, modifier = Modifier.padding( PaddingValues( bottom = 88.dp + WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding(), top = 12.dp, end = 12.dp, start = 12.dp ) ) ) } } } } Row( modifier = Modifier .navigationBarsPadding() .padding(16.dp) .align(settingsState.fabAlignment) ) { AnimatedContent(targetState = selectedUris.isNotEmpty()) { isFramesSelected -> if (isFramesSelected) { EnhancedFloatingActionButton( onClick = { wantToEdit = true }, content = { Spacer(Modifier.width(16.dp)) Icon( imageVector = Icons.Outlined.ImageEdit, contentDescription = stringResource(R.string.edit) ) Spacer(Modifier.width(16.dp)) Text(stringResource(R.string.edit)) Spacer(Modifier.width(16.dp)) } ) } else { EnhancedFloatingActionButton( onClick = pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, content = { Spacer(Modifier.width(16.dp)) Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_image_alt) ) Spacer(Modifier.width(16.dp)) Text(stringResource(R.string.pick_image_alt)) Spacer(Modifier.width(16.dp)) } ) } } Spacer(modifier = Modifier.width(8.dp)) AnimatedContent(targetState = selectedUris.isNotEmpty()) { isFramesSelected -> if (isFramesSelected) { EnhancedFloatingActionButton( onClick = { component.shareImages( uriList = null ) }, containerColor = MaterialTheme.colorScheme.secondaryContainer, type = EnhancedFloatingActionButtonType.SecondaryHorizontal, content = { Icon( imageVector = Icons.Rounded.Share, contentDescription = stringResource(R.string.share) ) } ) } else { EnhancedFloatingActionButton( onClick = { openDirectoryLauncher.pickFolder(previousFolder) }, containerColor = MaterialTheme.colorScheme.secondaryContainer, type = EnhancedFloatingActionButtonType.SecondaryHorizontal, content = { Icon( imageVector = Icons.Rounded.FolderOpened, contentDescription = stringResource(R.string.folder) ) } ) } } } OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) ProcessImagesPreferenceSheet( uris = selectedUris, visible = wantToEdit, onDismiss = { wantToEdit = false }, onNavigate = component.onNavigate ) ExitBackHandler( enabled = !component.uris.isNullOrEmpty(), onBack = onBack ) } } LoadingDialog( visible = isLoadingImages, onCancelLoading = component::cancelImageLoading, isForSaving = false ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog, title = stringResource(id = R.string.image_preview), text = stringResource(id = R.string.preview_closing), icon = Icons.Outlined.ImageSearch ) } ================================================ FILE: feature/image-preview/src/main/java/com/t8rin/imagetoolbox/feature/image_preview/presentation/screenLogic/ImagePreviewComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_preview.presentation.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.utils.appContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.withContext class ImagePreviewComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val shareProvider: ShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::updateUris) } } private val _uris = mutableStateOf?>(null) val uris by _uris private val _imageFrames: MutableState = mutableStateOf( ImageFrames.ManualSelection( emptyList() ) ) val imageFrames by _imageFrames fun updateUris(uris: List?) { _uris.value = null _uris.value = uris } fun shareImages( uriList: List? ) = componentScope.launch { uris?.let { shareProvider.shareUris( if (uriList.isNullOrEmpty()) { getSelectedUris()!! } else { uriList }.map { it.toString() } ) AppToastHost.showConfetti() } } fun getSelectedUris(): List? { val targetUris = uris ?: return null val positions = imageFrames.getFramePositions(targetUris.size) return targetUris.mapIndexedNotNull { index, uri -> if (index + 1 in positions) uri else null } } fun removeUri( uri: Uri ) = _uris.update { it?.minus(uri) } fun updateImageFrames(imageFrames: ImageFrames) { _imageFrames.update { imageFrames } } fun updateUrisFromTree(uri: Uri) { asyncUpdateUris { _ -> withContext(ioDispatcher) { val buffer = SnapshotStateList() _uris.update { buffer } buildList { fileController.listFilesInDirectoryAsFlow(uri.toString()) .mapNotNull { uri -> if (EXCLUDED.any { uri.endsWith(".$it", true) }) return@mapNotNull null uri.toUri().also { val mime = appContext.contentResolver.getType(it).orEmpty() if ("audio" in mime || "video" in mime) return@mapNotNull null } } .collect { uri -> add(uri) buffer.add(uri) } } } } } fun asyncUpdateUris( onFinish: suspend () -> Unit = {}, action: suspend (List?) -> List ) { debouncedImageCalculation(delay = 100, onFinish = onFinish) { _uris.value = action(_uris.value) } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ImagePreviewComponent } } private val EXCLUDED = listOf( "xml", "mov", "zip", "apk", "mp4", "mp3", "pdf", "ldb", "ttf", "gz", "rar" ) ================================================ FILE: feature/image-splitting/.gitignore ================================================ /build ================================================ FILE: feature/image-splitting/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.image_splitting" ================================================ FILE: feature/image-splitting/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_splitting.data import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.image_splitting.domain.ImageSplitter import com.t8rin.imagetoolbox.image_splitting.domain.SplitParams import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidImageSplitter @Inject constructor( private val imageGetter: ImageGetter, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder ) : ImageSplitter, DispatchersHolder by dispatchersHolder { override suspend fun split( imageUri: String, params: SplitParams ): List = withContext(defaultDispatcher) { if (params.columnsCount <= 1 && params.rowsCount <= 1) { return@withContext listOf(imageUri) } val image = imageGetter.getImage( data = imageUri, originalSize = true ) ?: return@withContext emptyList() if (params.rowsCount <= 1) { splitForColumns( image = image, count = params.columnsCount, imageFormat = params.imageFormat, quality = params.quality, columnPercentages = params.columnPercentages, ) } else if (params.columnsCount <= 1) { splitForRows( image = image, count = params.rowsCount, imageFormat = params.imageFormat, quality = params.quality, rowPercentages = params.rowPercentages ) } else { splitBoth( image = image, rowsCount = params.rowsCount, columnsCount = params.columnsCount, imageFormat = params.imageFormat, quality = params.quality, rowPercentages = params.rowPercentages, columnPercentages = params.columnPercentages ) } } private suspend fun splitBoth( image: Bitmap, rowsCount: Int, columnsCount: Int, imageFormat: ImageFormat, quality: Quality, rowPercentages: List = emptyList(), columnPercentages: List = emptyList() ): List = withContext(defaultDispatcher) { val rowHeights = calculatePartSizes(image.height, rowPercentages, rowsCount) val uris = mutableListOf>>() var currentY = 0 for (row in 0 until rowsCount) { val height = rowHeights[row] val rowBitmap = Bitmap.createBitmap(image, 0, currentY, image.width, height) val rowUris = async { splitForColumns( image = rowBitmap, count = columnsCount, imageFormat = imageFormat, quality = quality, columnPercentages = columnPercentages ) } uris.add(rowUris) currentY += height } uris.flatMap { it.await() } } private suspend fun splitForRows( image: Bitmap, count: Int, imageFormat: ImageFormat, quality: Quality, rowPercentages: List = emptyList() ): List = withContext(defaultDispatcher) { val rowHeights = calculatePartSizes(image.height, rowPercentages, count) val uris = mutableListOf() var currentY = 0 for (i in 0 until count) { val height = rowHeights[i] val cell = Bitmap.createBitmap(image, 0, currentY, image.width, height) uris.add( shareProvider.cacheImage( image = cell, imageInfo = ImageInfo( height = cell.height, width = cell.width, imageFormat = imageFormat, quality = quality.coerceIn(imageFormat) ) ) ) currentY += height } uris.filterNotNull() } private suspend fun splitForColumns( image: Bitmap, count: Int, imageFormat: ImageFormat, quality: Quality, columnPercentages: List = emptyList() ): List = withContext(defaultDispatcher) { val columnWidths = calculatePartSizes(image.width, columnPercentages, count) val uris = mutableListOf() var currentX = 0 for (i in 0 until count) { val width = columnWidths[i] val cell = Bitmap.createBitmap(image, currentX, 0, width, image.height) uris.add( shareProvider.cacheImage( image = cell, imageInfo = ImageInfo( height = cell.height, width = cell.width, imageFormat = imageFormat, quality = quality.coerceIn(imageFormat) ) ) ) currentX += width } uris.filterNotNull() } private fun calculatePartSizes( totalSize: Int, percentages: List, count: Int ): List { if (percentages.isEmpty()) { val partSize = totalSize / count return List(count) { index -> if (index == count - 1) { totalSize - (partSize * (count - 1)) } else { partSize } } } val normalizedPercentages = if (percentages.size < count) { val remainingPercentage = 1f - percentages.sum() val remainingParts = count - percentages.size val equalPercentage = remainingPercentage / remainingParts percentages + List(remainingParts) { equalPercentage } } else if (percentages.size > count) { percentages.take(count) } else { percentages } val totalPercentage = normalizedPercentages.sum() val normalized = normalizedPercentages.map { it / totalPercentage } return normalized.map { percentage -> (totalSize * percentage).toInt() }.let { sizes -> val calculatedTotal = sizes.sum() if (calculatedTotal != totalSize) { sizes.dropLast(1) + (totalSize - sizes.dropLast(1).sum()) } else { sizes } } } } ================================================ FILE: feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/di/ImageSplitterModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_splitting.di import com.t8rin.imagetoolbox.image_splitting.data.AndroidImageSplitter import com.t8rin.imagetoolbox.image_splitting.domain.ImageSplitter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ImageSplitterModule { @Binds @Singleton fun splitter( impl: AndroidImageSplitter ): ImageSplitter } ================================================ FILE: feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_splitting.domain interface ImageSplitter { suspend fun split( imageUri: String, params: SplitParams ): List } ================================================ FILE: feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_splitting.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality data class SplitParams( val rowsCount: Int, val columnsCount: Int, val rowPercentages: List, val columnPercentages: List, val imageFormat: ImageFormat, val quality: Quality, ) { fun withAspectRatio( targetAspectRatio: Float, maxRows: Int = rowsCount, maxColumns: Int = columnsCount ): SplitParams { require(targetAspectRatio > 0f) { "aspectRatio must be > 0" } var bestRows = 1 var bestCols = 1 for (rows in 1..maxRows) { val tileHeight = 1f / rows val tileWidth = tileHeight * targetAspectRatio val cols = (1f / tileWidth).toInt() if (cols in 1..maxColumns) { bestRows = rows bestCols = cols } } val rowsCount = bestRows val columnsCount = bestCols val rowPercentages = List(rowsCount) { 1f / rowsCount }.let { rows -> rows.dropLast(1) + (1f - rows.dropLast(1).sum()) } val columnPercentages = List(columnsCount) { 1f / columnsCount }.let { cols -> cols.dropLast(1) + (1f - cols.dropLast(1).sum()) } return this.copy( rowsCount = rowsCount, columnsCount = columnsCount, rowPercentages = rowPercentages, columnPercentages = columnPercentages ) } companion object { val Default by lazy { SplitParams( rowsCount = 2, columnsCount = 2, rowPercentages = emptyList(), columnPercentages = emptyList(), imageFormat = ImageFormat.Default, quality = Quality.Base() ) } } } ================================================ FILE: feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/ImageSplitterContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_splitting.presentation import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.toBitmap import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.image_splitting.presentation.components.SplitParamsSelector import com.t8rin.imagetoolbox.image_splitting.presentation.screenLogic.ImageSplitterComponent @Composable fun ImageSplitterContent( component: ImageSplitterComponent ) { val imagePicker = rememberImagePicker(onSuccess = component::updateUri) val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialUri != null ) val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } @Composable fun ImagePreview(showUrisPreview: Boolean) { if (showUrisPreview) { var aspectRatio by remember { mutableFloatStateOf(2f / 1f) } Picture( model = component.uri, contentDescription = null, modifier = Modifier .padding(top = if (isPortrait) 20.dp else 0.dp) .heightIn(max = 200.dp) .aspectRatio(aspectRatio) .container() .padding(4.dp) .clip(MaterialTheme.shapes.medium), onSuccess = { aspectRatio = it.result.image.toBitmap().safeAspectRatio } ) } else { UrisPreview( uris = component.uris.ifEmpty { listOf(Uri.EMPTY, Uri.EMPTY) }, modifier = Modifier .animateContentSizeNoClip() .padding(2.dp), isPortrait = true, onRemoveUri = null, onAddUris = null, isAddUrisVisible = component.isImageLoading, addUrisContent = { width -> Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator(modifier = Modifier.size(width / 2)) } }, onNavigate = component.onNavigate ) } } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.image_splitting), input = component.uri, isLoading = component.isImageLoading, size = null ) }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.uri != null, onShare = component::shareBitmaps, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, showImagePreviewAsStickyHeader = false, imagePreview = { ImagePreview(isPortrait) }, controls = { Spacer(Modifier.height(8.dp)) ImagePreview(!isPortrait) Spacer(Modifier.height(8.dp)) val params = component.params SplitParamsSelector( value = params, onValueChange = component::updateParams ) }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, topAppBarPersistentActions = { if (component.uri == null) TopAppBarEmoji() }, canShowScreenData = component.uri != null, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } } ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris.size, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_splitting.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.rounded.Percent import androidx.compose.material.icons.rounded.TableRows import androidx.compose.material.icons.rounded.ViewColumn import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.image_splitting.domain.SplitParams import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable internal fun SplitParamsSelector( value: SplitParams, onValueChange: (SplitParams) -> Unit ) { var recomputeTrigger by remember { mutableIntStateOf(0) } var rowsCount by remember(recomputeTrigger) { mutableIntStateOf(value.rowsCount) } var columnsCount by remember(recomputeTrigger) { mutableIntStateOf(value.columnsCount) } Column( modifier = Modifier.container( shape = ShapeDefaults.top ) ) { Row( modifier = Modifier.fillMaxWidth() ) { TitleItem( text = stringResource(R.string.compute_percents), icon = Icons.Rounded.Percent, modifier = Modifier.padding( start = 12.dp, top = 8.dp, bottom = 4.dp, end = 12.dp ) ) } var aspectRatio by rememberSaveable { mutableStateOf("") } val focus = LocalFocusManager.current val computed by remember(aspectRatio) { derivedStateOf { aspectRatio .replace(',', '.') .filter { it.isDigit() || it == '.' } .takeIf { it.isNotEmpty() } ?.toFloatOrNull() ?: 0f } } val scope = rememberCoroutineScope() RoundedTextField( value = aspectRatio, onValueChange = { text -> aspectRatio = text }, hint = { Text(1f.toString()) }, label = { Text(stringResource(R.string.aspect_ratio)) }, endIcon = { AnimatedVisibility( visible = computed > 0f, enter = scaleIn() + fadeIn(), exit = scaleOut() + fadeOut() ) { EnhancedIconButton( onClick = { onValueChange( value.withAspectRatio(computed) ) aspectRatio = "" scope.launch { delay(200) recomputeTrigger++ focus.clearFocus() } } ) { Icon( imageVector = Icons.Outlined.CheckCircle, contentDescription = null ) } } }, modifier = Modifier.padding(12.dp) ) } Spacer(Modifier.height(4.dp)) EnhancedSliderItem( value = rowsCount, title = stringResource(R.string.rows_count), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.TableRows, valueRange = 1f..20f, steps = 18, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( value.copy(rowsCount = it.roundToInt()) ) rowsCount = it.roundToInt() }, onValueChange = {}, shape = ShapeDefaults.center, additionalContent = if (rowsCount > 1) { { PercentagesField( totalSize = rowsCount, percentageValues = value.rowPercentages, onValueChange = { onValueChange( value.copy( rowPercentages = it ) ) } ) } } else null ) Spacer(Modifier.height(4.dp)) EnhancedSliderItem( value = columnsCount, title = stringResource(R.string.columns_count), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), steps = 18, icon = Icons.Rounded.ViewColumn, valueRange = 1f..20f, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( value.copy(columnsCount = it.roundToInt()) ) columnsCount = it.roundToInt() }, onValueChange = {}, shape = ShapeDefaults.bottom, additionalContent = if (columnsCount > 1) { { PercentagesField( totalSize = columnsCount, percentageValues = value.columnPercentages, onValueChange = { onValueChange( value.copy( columnPercentages = it ) ) } ) } } else null ) if (value.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = value.imageFormat, quality = value.quality, onQualityChange = { onValueChange( value.copy(quality = it) ) } ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = value.imageFormat, onValueChange = { onValueChange( value.copy(imageFormat = it) ) }, quality = value.quality, ) } @Composable private fun PercentagesField( totalSize: Int, percentageValues: List, onValueChange: (List) -> Unit ) { val default by remember(totalSize) { derivedStateOf { List(totalSize) { 1f / totalSize }.joinToString("/") { it.roundToTwoDigits().toString() } } } var percentages by remember { mutableStateOf( percentageValues.joinToString("/") { it.roundToTwoDigits().toString() } ) } LaunchedEffect(percentageValues, totalSize) { if (percentageValues.size > totalSize) { percentages = percentageValues.take(totalSize).joinToString("/") { it.roundToTwoDigits().toString() } } } LaunchedEffect(percentages) { onValueChange( percentages.split("/").mapNotNull { it.toFloatOrNull() } ) } Row( modifier = Modifier.fillMaxWidth() ) { TitleItem( text = stringResource(R.string.part_percents), icon = Icons.Rounded.Percent, modifier = Modifier.padding( start = 12.dp, top = 8.dp, bottom = 4.dp, end = 12.dp ) ) } RoundedTextField( value = percentages, onValueChange = { percentages = it }, hint = { Text(text = default) }, modifier = Modifier.padding(12.dp) ) } ================================================ FILE: feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.image_splitting.presentation.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.image_splitting.domain.ImageSplitter import com.t8rin.imagetoolbox.image_splitting.domain.SplitParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class ImageSplitterComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageSplitter: ImageSplitter, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::updateUri) } } private val _uri = mutableStateOf(null) val uri by _uri private val _uris = mutableStateOf>(emptyList()) val uris by _uris private val _params: MutableState = mutableStateOf(SplitParams.Default) val params: SplitParams by _params private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _done: MutableState = mutableIntStateOf(0) val done by _done private fun updateUris() { if (uri == null) return debouncedImageCalculation { _uris.update { imageSplitter.split( imageUri = uri!!.toString(), params = params ).map { it.toUri() } } } } fun updateUri(uri: Uri?) { _uri.value = null _uri.value = uri updateUris() } fun updateParams(params: SplitParams) { if (params != this.params) { _params.update { params } registerChanges() updateUris() } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 uris.forEach { uri -> results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( imageFormat = params.imageFormat, quality = params.quality, originalUri = uri.toString() ), metadata = null, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = fileController.readBytes(uri.toString()) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.value += 1 updateProgress( done = done, total = uris.size ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun shareBitmaps() { savingJob = trackProgress { _isSaving.value = true _done.value = 0 shareProvider.shareUris( uris = uris.map { it.toString() } ) AppToastHost.showConfetti() _isSaving.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 onComplete(uris) _isSaving.value = false } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ImageSplitterComponent } } ================================================ FILE: feature/image-stacking/.gitignore ================================================ /build ================================================ FILE: feature/image-stacking/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.image_stacking" ================================================ FILE: feature/image-stacking/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/data/AndroidImageStacker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.data import android.graphics.Bitmap import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.image.utils.toPaint import com.t8rin.imagetoolbox.core.data.utils.getSuitableConfig import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeAnchor import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.feature.image_stacking.domain.ImageStacker import com.t8rin.imagetoolbox.feature.image_stacking.domain.StackImage import com.t8rin.imagetoolbox.feature.image_stacking.domain.StackingParams import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidImageStacker @Inject constructor( private val imageGetter: ImageGetter, private val imagePreviewCreator: ImagePreviewCreator, private val imageScaler: ImageScaler, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ImageStacker { override suspend fun stackImages( stackImages: List, stackingParams: StackingParams, onFailure: (Throwable) -> Unit, onProgress: (Int) -> Unit ): Bitmap? = withContext(defaultDispatcher) { val resultSize = stackingParams.size ?: imageGetter.getImage( data = stackImages.firstOrNull()?.uri ?: "", originalSize = true )?.let { IntegerSize(it.width, it.height) } ?: IntegerSize(0, 0) if (resultSize.width <= 0 || resultSize.height <= 0) { onFailure(IllegalArgumentException("Width and height must be > 0")) return@withContext null } createBitmap( width = resultSize.width, height = resultSize.height, config = getSuitableConfig() ).applyCanvas { stackImages.forEachIndexed { index, stackImage -> val bitmap = imageGetter.getImage( data = stackImage.uri )?.let { bitmap -> bitmap.setHasAlpha(true) val resizeType = when (stackImage.scale) { StackImage.Scale.None -> null StackImage.Scale.Fill -> ResizeType.Explicit StackImage.Scale.Fit -> ResizeType.Flexible(ResizeAnchor.Min) StackImage.Scale.FitWidth -> ResizeType.Flexible(ResizeAnchor.Width) StackImage.Scale.FitHeight -> ResizeType.Flexible(ResizeAnchor.Height) StackImage.Scale.Crop -> ResizeType.CenterCrop(0x00000000) } resizeType?.let { imageScaler.scaleImage( image = bitmap, width = resultSize.width, height = resultSize.height, resizeType = resizeType ) } ?: bitmap } bitmap?.let { drawBitmap( bitmap = it, position = stackImage.position, paint = stackImage.blendingMode.toPaint().apply { alpha = (stackImage.alpha * 255).toInt() } ) } onProgress(index + 1) } } } override suspend fun stackImagesPreview( stackImages: List, stackingParams: StackingParams, imageFormat: ImageFormat, quality: Quality, onGetByteCount: (Int) -> Unit ): Bitmap? = withContext(defaultDispatcher) { stackImages( stackImages = stackImages, stackingParams = stackingParams, onProgress = {}, onFailure = {} )?.let { image -> val imageSize = IntegerSize( width = image.width, height = image.height ) return@let imagePreviewCreator.createPreview( image = image, imageInfo = ImageInfo( width = imageSize.width, height = imageSize.height, imageFormat = imageFormat, quality = quality ), transformations = emptyList(), onGetByteCount = onGetByteCount ) } } } ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/di/ImageStackingModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.image_stacking.data.AndroidImageStacker import com.t8rin.imagetoolbox.feature.image_stacking.domain.ImageStacker import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ImageStackingModule { @Binds @Singleton fun provideImageStacker(stackerImpl: AndroidImageStacker): ImageStacker } ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/ImageStacker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality interface ImageStacker { suspend fun stackImages( stackImages: List, stackingParams: StackingParams, onFailure: (Throwable) -> Unit, onProgress: (Int) -> Unit ): I? suspend fun stackImagesPreview( stackImages: List, stackingParams: StackingParams, imageFormat: ImageFormat, quality: Quality, onGetByteCount: (Int) -> Unit ): I? } ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.domain import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.model.Position data class StackImage( val uri: String, val alpha: Float, val blendingMode: BlendingMode, val position: Position, val scale: Scale ) { enum class Scale { None, Fill, Fit, FitWidth, FitHeight, Crop } } fun String.toStackImage() = StackImage( uri = this, alpha = 1f, blendingMode = BlendingMode.SrcOver, position = Position.TopLeft, scale = StackImage.Scale.Fit ) ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/domain/StackingParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.domain import com.t8rin.imagetoolbox.core.domain.model.IntegerSize data class StackingParams( val size: IntegerSize? ) { companion object { val Default by lazy { StackingParams(size = null) } } } ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/ImageStackingContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.presentation import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AddCircleOutline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.image_stacking.domain.StackImage import com.t8rin.imagetoolbox.feature.image_stacking.presentation.components.StackImageItem import com.t8rin.imagetoolbox.feature.image_stacking.presentation.components.StackingParamsSelector import com.t8rin.imagetoolbox.feature.image_stacking.presentation.screenLogic.ImageStackingComponent @Composable fun ImageStackingContent( component: ImageStackingComponent ) { AutoContentBasedColors(component.previewBitmap) val imagePicker = rememberImagePicker(onSuccess = component::updateUris) val addImagesImagePicker = rememberImagePicker(onSuccess = component::addUrisToEnd) val addImages = addImagesImagePicker::pickImage val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.image_stacking), input = component.stackImages, isLoading = component.isImageLoading, size = component.imageByteSize?.toLong(), updateOnSizeChange = false ) }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.previewBitmap != null, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { editSheetData = listOf(it) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) ZoomButton( onClick = { showZoomSheet = true }, visible = component.previewBitmap != null, ) }, imagePreview = { ImageContainer( imageInside = isPortrait, showOriginal = false, previewBitmap = component.previewBitmap, originalBitmap = null, isLoading = component.isImageLoading, shouldShowPreview = true ) }, topAppBarPersistentActions = { if (component.stackImages.isEmpty()) { TopAppBarEmoji() } }, controls = { Column( verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { ImageReorderCarousel( images = remember(component.stackImages) { derivedStateOf { component.stackImages.map { it.uri.toUri() } } }.value, onReorder = component::reorderUris, onNeedToAddImage = addImages, onNeedToRemoveImageAt = component::removeImageAt, onNavigate = component.onNavigate ) StackingParamsSelector( value = component.stackingParams, onValueChange = component::updateParams ) Column(Modifier.container(MaterialTheme.shapes.extraLarge)) { TitleItem( text = stringResource( R.string.images, component.stackImages.size ) ) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(8.dp) ) { component.stackImages.forEachIndexed { index, stackImage -> StackImageItem( backgroundColor = MaterialTheme.colorScheme.surface, stackImage = stackImage, index = index, onStackImageChange = { image: StackImage -> component.updateStackImage( value = image, index = index ) }, isRemoveVisible = component.stackImages.size > 2, onRemove = { component.removeImageAt(index) }, shape = ShapeDefaults.byIndex( index = index, size = component.stackImages.size ) ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = addImages, modifier = Modifier .padding(top = 4.dp) .padding( horizontal = 16.dp ) ) { Icon( imageVector = Icons.Rounded.AddCircleOutline, contentDescription = stringResource(R.string.add_image) ) Spacer(Modifier.width(8.dp)) Text(stringResource(id = R.string.add_image)) } } } QualitySelector( imageFormat = component.imageInfo.imageFormat, quality = component.imageInfo.quality, onQualityChange = component::setQuality ) ImageFormatSelector( value = component.imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = component.imageInfo.quality, ) } }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.stackImages.isEmpty(), isPrimaryButtonVisible = component.stackImages.isNotEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } }, canShowScreenData = component.stackImages.isNotEmpty() ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.stackImages.size, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackImageItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PhotoSizeSelectLarge import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material.icons.rounded.RemoveCircleOutline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.BlendingModeSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PositionSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.image_stacking.domain.StackImage @Composable fun StackImageItem( stackImage: StackImage, index: Int, onRemove: () -> Unit, modifier: Modifier = Modifier, onStackImageChange: (StackImage) -> Unit, isRemoveVisible: Boolean, backgroundColor: Color = Color.Unspecified, shape: Shape = MaterialTheme.shapes.extraLarge ) { var isControlsExpanded by rememberSaveable { mutableStateOf(true) } Row( modifier = modifier .container(color = backgroundColor, shape = shape) .animateContentSizeNoClip(), verticalAlignment = Alignment.CenterVertically ) { Column( modifier = Modifier.weight(1f) ) { Row(verticalAlignment = Alignment.CenterVertically) { if (!isRemoveVisible) { EnhancedIconButton( onClick = onRemove ) { Icon( imageVector = Icons.Rounded.RemoveCircleOutline, contentDescription = stringResource(R.string.remove) ) } } Row( modifier = Modifier .weight(1f) .padding( top = 8.dp, end = 8.dp, start = 16.dp, bottom = 8.dp ), verticalAlignment = Alignment.CenterVertically ) { Box( contentAlignment = Alignment.Center, modifier = Modifier.clip(MaterialTheme.shapes.small) ) { Picture( model = stackImage.uri, shape = RectangleShape, modifier = Modifier .height(56.dp) .width(112.dp) ) Box( Modifier .matchParentSize() .background( color = MaterialTheme.colorScheme.surfaceContainer.copy( alpha = 0.3f ) ), contentAlignment = Alignment.Center ) { Text( text = "${index + 1}", color = MaterialTheme.colorScheme.onSurface, fontSize = 20.sp, fontWeight = FontWeight.Bold ) } } } EnhancedIconButton( onClick = { isControlsExpanded = !isControlsExpanded } ) { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate( animateFloatAsState( if (isControlsExpanded) 180f else 0f ).value ) ) } } AnimatedVisibility( visible = isControlsExpanded ) { Column( modifier = Modifier.padding(8.dp) ) { AlphaSelector( value = stackImage.alpha, onValueChange = { onStackImageChange( stackImage.copy(alpha = it) ) }, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) BlendingModeSelector( value = stackImage.blendingMode, onValueChange = { onStackImageChange( stackImage.copy(blendingMode = it) ) }, color = Color.Unspecified, shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) PositionSelector( value = stackImage.position, onValueChange = { onStackImageChange( stackImage.copy(position = it) ) }, color = Color.Unspecified, shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) DataSelector( value = stackImage.scale, onValueChange = { onStackImageChange( stackImage.copy(scale = it) ) }, entries = StackImage.Scale.entries, spanCount = 1, title = stringResource(R.string.scale), titleIcon = Icons.Outlined.PhotoSizeSelectLarge, itemContentText = { it.title() }, containerColor = Color.Unspecified, shape = ShapeDefaults.bottom ) } } } } } @Composable private fun StackImage.Scale.title(): String = when (this) { StackImage.Scale.None -> stringResource(R.string.none) StackImage.Scale.Fill -> stringResource(R.string.fill) StackImage.Scale.Fit -> stringResource(R.string.fit) StackImage.Scale.FitWidth -> stringResource(R.string.fit_width) StackImage.Scale.FitHeight -> stringResource(R.string.fit_height) StackImage.Scale.Crop -> stringResource(R.string.crop) } ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/components/StackingParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PhotoSizeSelectLarge import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.image_stacking.domain.StackingParams @Composable internal fun StackingParamsSelector( value: StackingParams, onValueChange: (StackingParams) -> Unit, ) { Column { val size = value.size ?: IntegerSize.Undefined AnimatedVisibility(size.isDefined()) { ResizeImageField( imageInfo = ImageInfo(size.width, size.height), originalSize = null, onWidthChange = { onValueChange( value.copy( size = size.copy(width = it) ) ) }, onHeightChange = { onValueChange( value.copy( size = size.copy(height = it) ) ) } ) } Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.use_size_of_first_frame), subtitle = stringResource(id = R.string.use_size_of_first_frame_sub), checked = value.size == null, onClick = { onValueChange( value.copy(size = if (it) null else IntegerSize(1000, 1000)) ) }, startIcon = Icons.Outlined.PhotoSizeSelectLarge, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) } } ================================================ FILE: feature/image-stacking/src/main/java/com/t8rin/imagetoolbox/feature/image_stacking/presentation/screenLogic/ImageStackingComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stacking.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.image_stacking.domain.ImageStacker import com.t8rin.imagetoolbox.feature.image_stacking.domain.StackImage import com.t8rin.imagetoolbox.feature.image_stacking.domain.StackingParams import com.t8rin.imagetoolbox.feature.image_stacking.domain.toStackImage import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay class ImageStackingComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val shareProvider: ImageShareProvider, private val imageStacker: ImageStacker, private val fileController: FileController, private val imageCompressor: ImageCompressor, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::updateUris) } } private val _stackImages: MutableState> = mutableStateOf(emptyList()) val stackImages by _stackImages private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving fun updateUris(uris: List) { _stackImages.value = uris.map { it.toString().toStackImage() } if (uris.isNotEmpty()) { calculatePreview() } } private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _stackingParams: MutableState = mutableStateOf(StackingParams.Default) val stackingParams by _stackingParams private val _imageInfo = mutableStateOf(ImageInfo(imageFormat = ImageFormat.Png.Lossless)) val imageInfo by _imageInfo private val _imageByteSize: MutableState = mutableStateOf(null) val imageByteSize by _imageByteSize private val _done: MutableState = mutableIntStateOf(0) val done by _done fun setImageFormat(imageFormat: ImageFormat) { _imageInfo.value = _imageInfo.value.copy(imageFormat = imageFormat) calculatePreview() } private var calculationPreviewJob: Job? by smartJob { _isImageLoading.update { false } } private fun calculatePreview() { calculationPreviewJob = componentScope.launch { delay(300L) _isImageLoading.value = true stackImages.takeIf { it.isNotEmpty() }?.let { registerChanges() imageStacker.stackImagesPreview( stackImages = stackImages, stackingParams = stackingParams, imageFormat = imageInfo.imageFormat, quality = imageInfo.quality, onGetByteCount = { _imageByteSize.update { it } } ).let { image -> _previewBitmap.value = image } } _isImageLoading.value = false } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 imageStacker.stackImages( stackImages = stackImages, stackingParams = stackingParams, onFailure = { parseSaveResult(SaveResult.Error.Exception(it)) }, onProgress = { _done.value = it updateProgress( done = done, total = stackImages.size ) } )?.let { image -> val imageInfo = ImageInfo( height = image.height, width = image.width, quality = imageInfo.quality, imageFormat = imageInfo.imageFormat ) parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = "Stacked", sequenceNumber = null, data = imageCompressor.compressAndTransform( image = image, imageInfo = imageInfo ) ), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.value = false } } fun shareBitmap() { savingJob = trackProgress { _isSaving.value = true _done.value = 0 imageStacker.stackImages( stackImages = stackImages, stackingParams = stackingParams, onProgress = { _done.value = it updateProgress( done = done, total = stackImages.size ) }, onFailure = AppToastHost::showFailureToast )?.let { image -> val imageInfo = ImageInfo( height = image.height, width = image.width, quality = imageInfo.quality, imageFormat = imageInfo.imageFormat ) shareProvider.shareImage( image = image, imageInfo = imageInfo, onComplete = AppToastHost::showConfetti ) } _isSaving.value = false } } fun setQuality(quality: Quality) { _imageInfo.value = _imageInfo.value.copy(quality = quality) calculatePreview() } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun addUrisToEnd(uris: List) { _stackImages.update { list -> list + uris.map { it.toString().toStackImage() }.filter { it !in list } } calculatePreview() } fun removeImageAt(index: Int) { _stackImages.update { list -> list.toMutableList().apply { removeAt(index) }.takeIf { it.size >= 2 }.also { if (it == null) _previewBitmap.value = null } ?: emptyList() } calculatePreview() } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 imageStacker.stackImages( stackImages = stackImages, stackingParams = stackingParams, onProgress = { _done.value = it updateProgress( done = done, total = stackImages.size ) }, onFailure = {} )?.let { image -> val imageInfo = ImageInfo( height = image.height, width = image.width, quality = imageInfo.quality, imageFormat = imageInfo.imageFormat ) shareProvider.cacheImage( image = image, imageInfo = imageInfo )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun updateParams( newParams: StackingParams ) { _stackingParams.update { newParams } calculatePreview() } fun updateStackImage( value: StackImage, index: Int ) { val list = stackImages.toMutableList() runCatching { list[index] = value _stackImages.update { list } }.onFailure(AppToastHost::showFailureToast) calculatePreview() } fun reorderUris(uris: List) { if (stackImages.map { it.uri } != uris) { _stackImages.update { stack -> val stackOrder = uris.map { it.toString() } val data = stack.associateBy { it.uri } val leftStack = stack.filter { it.uri !in stackOrder } (leftStack + stackOrder.mapNotNull { data[it] }).distinct() } calculatePreview() } } fun getFormatForFilenameSelection(): ImageFormat = imageInfo.imageFormat @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ImageStackingComponent } } ================================================ FILE: feature/image-stitch/.gitignore ================================================ /build ================================================ FILE: feature/image-stitch/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.image_stitch" dependencies { implementation(projects.core.filters) implementation(projects.lib.opencvTools) implementation(libs.trickle) } ================================================ FILE: feature/image-stitch/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/AndroidImageCombiner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.data import android.graphics.Bitmap import android.graphics.Paint import android.graphics.PorterDuff import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.image.utils.toPaint import com.t8rin.imagetoolbox.core.data.utils.aspectRatio import com.t8rin.imagetoolbox.core.data.utils.getSuitableConfig import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageWithSize import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.withSize import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.domain.model.createFilter import com.t8rin.imagetoolbox.core.filters.domain.model.enums.FadeSide import com.t8rin.imagetoolbox.core.filters.domain.model.params.SideFadeParams import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.feature.image_stitch.domain.CombiningParams import com.t8rin.imagetoolbox.feature.image_stitch.domain.ImageCombiner import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchAlignment import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchFadeSide import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchMode import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.math.absoluteValue import kotlin.math.max import kotlin.math.roundToInt import kotlin.math.roundToLong internal class AndroidImageCombiner @Inject constructor( private val imageScaler: ImageScaler, private val imageGetter: ImageGetter, private val imageTransformer: ImageTransformer, private val shareProvider: ImageShareProvider, private val filterProvider: FilterProvider, private val imagePreviewCreator: ImagePreviewCreator, private val cvStitchHelper: CvStitchHelper, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ImageCombiner { private val _settingsState = settingsProvider.settingsState private val settingsState get() = _settingsState.value override suspend fun combineImages( imageUris: List, combiningParams: CombiningParams, onProgress: (Int) -> Unit ): Pair = withContext(defaultDispatcher) { if (combiningParams.stitchMode is StitchMode.Auto) { return@withContext cvStitchHelper.cvCombine( imageUris = imageUris, combiningParams = combiningParams ) } suspend fun getImageData( imagesUris: List, isHorizontal: Boolean ): Pair { val (size, images) = calculateCombinedImageDimensionsAndBitmaps( imageUris = imagesUris, isHorizontal = isHorizontal, scaleSmallImagesToLarge = combiningParams.scaleSmallImagesToLarge, imageSpacing = combiningParams.spacing, imageScale = combiningParams.outputScale ) val bitmaps = images.map { image -> if ( combiningParams.scaleSmallImagesToLarge && image.shouldUpscale( isHorizontal = isHorizontal, size = size ) ) { image.upscale(isHorizontal, size) } else image } val bitmap = createBitmap( width = size.width, height = size.height, config = getSuitableConfig() ).applyCanvas { drawColor(Color.Transparent.toArgb(), PorterDuff.Mode.CLEAR) drawColor(combiningParams.backgroundColor) var pos = 0 val fullSpace = combiningParams.spacing.absoluteValue val halfSpace = fullSpace / 2 val strength = combiningParams.fadeStrength for (i in imagesUris.indices) { var bmp = bitmaps[i] combiningParams.spacing.takeIf { it < 0 && combiningParams.fadingEdgesMode != StitchFadeSide.None } ?.let { val filters = when (combiningParams.fadingEdgesMode) { StitchFadeSide.Start -> { when (i) { 0 -> emptyList() else -> listOf( createFilter( SideFadeParams.Absolute( side = if (isHorizontal) FadeSide.Start else FadeSide.Top, size = fullSpace, strength = strength ) ) ) } } StitchFadeSide.End -> { when (i) { imagesUris.lastIndex -> emptyList() else -> listOf( createFilter( SideFadeParams.Absolute( side = if (isHorizontal) FadeSide.End else FadeSide.Bottom, size = fullSpace, strength = strength ) ) ) } } StitchFadeSide.Both -> { when (i) { 0 -> listOf( createFilter( SideFadeParams.Absolute( side = if (isHorizontal) FadeSide.End else FadeSide.Bottom, size = halfSpace, strength = strength ) ) ) imagesUris.lastIndex -> listOf( createFilter( SideFadeParams.Absolute( side = if (isHorizontal) FadeSide.Start else FadeSide.Top, size = halfSpace, strength = strength ) ) ) else -> listOf( createFilter( SideFadeParams.Absolute( side = if (isHorizontal) FadeSide.Start else FadeSide.Top, size = halfSpace, strength = strength ) ), createFilter( SideFadeParams.Absolute( side = if (isHorizontal) FadeSide.End else FadeSide.Bottom, size = halfSpace, strength = strength ) ) ) } } else -> emptyList() } imageTransformer.transform( image = bmp, transformations = filters.map(filterProvider::filterToTransformation) )?.let { bmp = it } } if (isHorizontal) { drawBitmap( bitmap = bmp, left = pos.toFloat(), top = when (combiningParams.alignment) { StitchAlignment.Start -> 0f StitchAlignment.Center -> (height - bmp.height) / 2f StitchAlignment.End -> (height - bmp.height).toFloat() }, paint = if (pos > 0) combiningParams.blendingMode.toPaint() else Paint() ) } else { drawBitmap( bitmap = bmp, left = when (combiningParams.alignment) { StitchAlignment.Start -> 0f StitchAlignment.Center -> (width - bmp.width) / 2f StitchAlignment.End -> (width - bmp.width).toFloat() }, top = pos.toFloat(), paint = if (pos > 0) combiningParams.blendingMode.toPaint() else Paint() ) } pos += if (isHorizontal) { (bmp.width + combiningParams.spacing).coerceAtLeast(1) } else (bmp.height + combiningParams.spacing).coerceAtLeast(1) onProgress(i + 1) } } return bitmap.createScaledBitmap( width = size.width, height = size.height ) to ImageInfo( width = size.width, height = size.height, imageFormat = ImageFormat.Png.Lossless ) } if (combiningParams.stitchMode.gridCellsCount().let { !(it == 0 || it > imageUris.size) }) { combineImages( imageUris = distributeImages( images = imageUris, cellCount = combiningParams.stitchMode.gridCellsCount() ).mapNotNull { images -> val data = getImageData( imagesUris = images, isHorizontal = combiningParams.stitchMode.isHorizontal() ) shareProvider.cacheImage( image = data.first, imageInfo = data.second ) }, combiningParams = combiningParams.copy( stitchMode = when (combiningParams.stitchMode) { is StitchMode.Grid.Horizontal -> StitchMode.Vertical else -> StitchMode.Horizontal } ), onProgress = onProgress ) } else { getImageData( imagesUris = imageUris, isHorizontal = combiningParams.stitchMode.isHorizontal() ) } } override suspend fun calculateCombinedImageDimensions( imageUris: List, combiningParams: CombiningParams ): IntegerSize { return if (combiningParams.stitchMode.gridCellsCount() .let { it == 0 || it > imageUris.size } ) { calculateCombinedImageDimensionsAndBitmaps( imageUris = imageUris, isHorizontal = combiningParams.stitchMode.isHorizontal(), scaleSmallImagesToLarge = combiningParams.scaleSmallImagesToLarge, imageSpacing = combiningParams.spacing, imageScale = combiningParams.outputScale ).first } else { val isHorizontalGrid = combiningParams.stitchMode.isHorizontal() var size = IntegerSize(0, 0) distributeImages( images = imageUris, cellCount = combiningParams.stitchMode.gridCellsCount() ).forEach { images -> calculateCombinedImageDimensionsAndBitmaps( imageUris = images, isHorizontal = !isHorizontalGrid, scaleSmallImagesToLarge = combiningParams.scaleSmallImagesToLarge, imageSpacing = combiningParams.spacing, imageScale = combiningParams.outputScale ).first.let { newSize -> size = if (isHorizontalGrid) { size.copy( height = size.height + newSize.height, width = max(newSize.width, size.width) ) } else { size.copy( height = max(newSize.height, size.height), width = size.width + newSize.width, ) } } } IntegerSize( width = size.width.coerceAtLeast(1), height = size.height.coerceAtLeast(1) ) } } private suspend fun calculateCombinedImageDimensionsAndBitmaps( imageUris: List, isHorizontal: Boolean, scaleSmallImagesToLarge: Boolean, imageSpacing: Int, imageScale: Float ): Pair> = withContext(defaultDispatcher) { var w = 0 var h = 0 var maxHeight = 0 var maxWidth = 0 val drawables = imageUris.mapNotNull { uri -> imageGetter.getImage( data = uri, originalSize = true )?.let { it.createScaledBitmap( width = (it.width * imageScale).roundToInt(), height = (it.height * imageScale).roundToInt() ) }?.apply { maxWidth = max(maxWidth, width) maxHeight = max(maxHeight, height) } } drawables.forEachIndexed { index, image -> val width = image.width val height = image.height val spacing = if (index != drawables.lastIndex) imageSpacing else 0 if (scaleSmallImagesToLarge && image.shouldUpscale( isHorizontal = isHorizontal, size = IntegerSize(maxWidth, maxHeight) ) ) { val targetHeight: Int val targetWidth: Int if (isHorizontal) { targetHeight = maxHeight targetWidth = (targetHeight * image.aspectRatio).toInt() } else { targetWidth = maxWidth targetHeight = (targetWidth / image.aspectRatio).toInt() } if (isHorizontal) { w += (targetWidth + spacing).coerceAtLeast(1) } else { h += (targetHeight + spacing).coerceAtLeast(1) } } else { if (isHorizontal) { w += (width + spacing).coerceAtLeast(1) } else { h += (height + spacing).coerceAtLeast(1) } } } if (isHorizontal) { h = maxHeight } else { w = maxWidth } IntegerSize( width = w.coerceAtLeast(1), height = h.coerceAtLeast(1) ) to drawables } private fun distributeImages( images: List, cellCount: Int ): List> { val imageCount = images.size val imagesPerRow = imageCount / cellCount val remainingImages = imageCount % cellCount val result = MutableList(cellCount) { imagesPerRow } for (i in 0 until remainingImages) { result[i] += 1 } var offset = 0 return result.map { count -> images.subList( fromIndex = offset, toIndex = offset + count ).also { offset += count } } } override suspend fun createCombinedImagesPreview( imageUris: List, combiningParams: CombiningParams, imageFormat: ImageFormat, quality: Quality, onGetByteCount: (Long) -> Unit ): ImageWithSize = withContext(defaultDispatcher) { val imageSize = calculateCombinedImageDimensions( imageUris = imageUris, combiningParams = combiningParams ).let { it.copy( width = (it.width / combiningParams.outputScale).roundToInt(), height = (it.height / combiningParams.outputScale).roundToInt() ) } if (!settingsState.generatePreviews) return@withContext null withSize imageSize val scale = 0.2f combineImages( imageUris = imageUris, combiningParams = combiningParams.copy( outputScale = scale ), onProgress = {} ).let { (image, imageInfo) -> return@let imagePreviewCreator.createPreview( image = image, imageInfo = imageInfo.copy( imageFormat = imageFormat, quality = quality ), transformations = emptyList(), onGetByteCount = { val original = it / (scale * scale) onGetByteCount(original.roundToLong()) } ) withSize imageSize } } private fun Bitmap.shouldUpscale( isHorizontal: Boolean, size: IntegerSize ): Boolean { return if (isHorizontal) this.height != size.height else this.width != size.width } private suspend fun Bitmap.upscale( isHorizontal: Boolean, size: IntegerSize ): Bitmap { return if (isHorizontal) { createScaledBitmap( width = (size.height * aspectRatio).toInt(), height = size.height ) } else { createScaledBitmap( width = size.width, height = (size.width / aspectRatio).toInt() ) } } private suspend fun Bitmap.createScaledBitmap( width: Int, height: Int ): Bitmap = imageScaler.scaleImage( image = this, width = width, height = height ) } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/data/CvStitchHelper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.data import android.graphics.Bitmap import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.feature.image_stitch.domain.CombiningParams import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchMode import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import com.t8rin.trickle.Trickle import org.opencv.calib3d.Calib3d import org.opencv.core.Core import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.core.MatOfDMatch import org.opencv.core.MatOfKeyPoint import org.opencv.core.MatOfPoint2f import org.opencv.core.Point import org.opencv.core.Rect import org.opencv.core.Scalar import org.opencv.core.Size import org.opencv.core.times import org.opencv.features2d.FlannBasedMatcher import org.opencv.features2d.SIFT import org.opencv.imgproc.Imgproc import javax.inject.Inject import kotlin.math.max import kotlin.math.roundToInt internal class CvStitchHelper @Inject constructor( private val imageGetter: ImageGetter, private val imageScaler: ImageScaler ) : OpenCV() { private val kernel = Mat(3, 3, CvType.CV_8SC1).apply { put(0, 0, 1.0, 1.0, 1.0) put(1, 0, 1.0, -8.0, 1.0) put(2, 0, 1.0, 1.0, 1.0) } fun stitchBitmaps( mat0: Mat, mat1: Mat, homo: Boolean = true, diff: Boolean = true ): Mat? { return if (homo) { stitchHomography(mat0, mat1, diff) } else { stitchPhaseCorrelate(mat0, mat1, diff) } } private fun stitchHomography(mat0: Mat, mat1: Mat, diff: Boolean): Mat? { val mat0Proc = if (diff) { val tmp = Mat() Imgproc.filter2D(mat0, tmp, CvType.CV_8U, kernel) tmp } else mat0 val mat1Proc = if (diff) { val tmp = Mat() Imgproc.filter2D(mat1, tmp, CvType.CV_8U, kernel) tmp } else mat1 val sift = SIFT.create() val kp0 = MatOfKeyPoint() val kp1 = MatOfKeyPoint() val desc0 = Mat() val desc1 = Mat() sift.detectAndCompute(mat0Proc, Mat(), kp0, desc0) sift.detectAndCompute(mat1Proc, Mat(), kp1, desc1) if (kp0.empty() || kp1.empty()) return null val matcher = FlannBasedMatcher.create() val knnMatches = mutableListOf() matcher.knnMatch(desc0, desc1, knnMatches, 2) val queryPoints = mutableListOf() val trainPoints = mutableListOf() val kp0a = kp0.toArray() val kp1a = kp1.toArray() for (m in knnMatches) { val matches = m.toArray() if (matches.size < 2) continue if (matches[0].distance > 0.7 * matches[1].distance) continue queryPoints.add(kp0a[matches[0].queryIdx].pt) trainPoints.add(kp1a[matches[0].trainIdx].pt) } if (queryPoints.size < 10) return null val homoMat = Calib3d.findHomography( MatOfPoint2f(*trainPoints.toTypedArray()), MatOfPoint2f(*queryPoints.toTypedArray()), Calib3d.RANSAC ) val corners = arrayOf( Point(0.0, 0.0), Point(mat1.cols().toDouble(), 0.0), Point(mat1.cols().toDouble(), mat1.rows().toDouble()), Point(0.0, mat1.rows().toDouble()) ) val transformedCorners = MatOfPoint2f(*corners).let { src -> val dst = MatOfPoint2f() Core.perspectiveTransform(src, dst, homoMat) dst.toArray() } val allX = transformedCorners.map { it.x } + listOf(0.0, mat0.cols().toDouble()) val allY = transformedCorners.map { it.y } + listOf(0.0, mat0.rows().toDouble()) val minX = allX.minOrNull() ?: 0.0 val minY = allY.minOrNull() ?: 0.0 val maxX = allX.maxOrNull() ?: mat0.cols().toDouble() val maxY = allY.maxOrNull() ?: mat0.rows().toDouble() val width = (maxX - minX).toInt() val height = (maxY - minY).toInt() val offset = Mat.eye(3, 3, CvType.CV_64F) offset.put(0, 2, -minX) offset.put(1, 2, -minY) val adjustedHomo = offset * homoMat val canvas = Mat(Size(width.toDouble(), height.toDouble()), mat0.type()) Imgproc.warpPerspective(mat1, canvas, adjustedHomo, canvas.size()) val roi0 = Rect((-minX).toInt(), (-minY).toInt(), mat0.cols(), mat0.rows()) mat0.copyTo(canvas.submat(roi0)) return canvas } private fun stitchPhaseCorrelate(mat0: Mat, mat1: Mat, diff: Boolean): Mat? { if (mat0.size() != mat1.size()) { val targetSize = Size( minOf(mat0.cols(), mat1.cols()).toDouble(), minOf(mat0.rows(), mat1.rows()).toDouble() ) val resized0 = Mat() val resized1 = Mat() Imgproc.resize(mat0, resized0, targetSize) Imgproc.resize(mat1, resized1, targetSize) return stitchPhaseCorrelate(resized0, resized1, diff) } val mat0Gray = Mat() val mat1Gray = Mat() if (diff) { val grad0 = Mat() val grad1 = Mat() Imgproc.filter2D(mat0, grad0, CvType.CV_8U, kernel) Imgproc.filter2D(mat1, grad1, CvType.CV_8U, kernel) val diffMat = Mat() Core.absdiff(grad0, grad1, diffMat) Core.bitwise_and(grad0, diffMat, grad0) Core.bitwise_and(grad1, diffMat, grad1) Imgproc.cvtColor(grad0, mat0Gray, Imgproc.COLOR_RGBA2GRAY) Imgproc.cvtColor(grad1, mat1Gray, Imgproc.COLOR_RGBA2GRAY) } else { Imgproc.cvtColor(mat0, mat0Gray, Imgproc.COLOR_RGBA2GRAY) Imgproc.cvtColor(mat1, mat1Gray, Imgproc.COLOR_RGBA2GRAY) } val matchResult = Mat() Imgproc.matchTemplate(mat0Gray, mat1Gray, matchResult, Imgproc.TM_CCORR_NORMED) val mmr = Core.minMaxLoc(matchResult) val dx = mmr.maxLoc.x.toInt() val dy = mmr.maxLoc.y.toInt() val width = max(mat0.cols(), mat1.cols() + dx) val height = max(mat0.rows(), mat1.rows() + dy) val canvas = Mat(Size(width.toDouble(), height.toDouble()), mat0.type()) canvas.setTo(Scalar.all(0.0)) mat0.copyTo(canvas.submat(Rect(0, 0, mat0.cols(), mat0.rows()))) mat1.copyTo(canvas.submat(Rect(dx, dy, mat1.cols(), mat1.rows()))) return canvas } suspend fun cvCombine( imageUris: List, combiningParams: CombiningParams, ): Pair { val result = cvStitch( uris = imageUris, imageScale = combiningParams.outputScale, stitchMode = combiningParams.stitchMode as StitchMode.Auto ) ?: imageUris.first().toBitmap( imageScale = combiningParams.outputScale, stitchMode = combiningParams.stitchMode ) ?: createBitmap(1, 1) return Trickle.drawColorBehind( input = result, color = combiningParams.backgroundColor ) to ImageInfo( width = result.width, height = result.height, imageFormat = ImageFormat.Png.Lossless ) } private suspend fun cvStitch( uris: List, imageScale: Float, stitchMode: StitchMode.Auto ): Bitmap? { if (uris.size < 2) return null var current = uris.first().toBitmap( imageScale = imageScale, stitchMode = stitchMode )?.toMat() ?: return null for (i in 1 until uris.size) { val next = uris[i].toBitmap( imageScale = imageScale, stitchMode = stitchMode )?.toMat() ?: continue val stitched = stitchBitmaps( mat0 = current, mat1 = next ) current.release() next.release() stitched ?: return null current = stitched } return current.toBitmap().also { current.release() } } private suspend fun String.toBitmap( imageScale: Float, stitchMode: StitchMode.Auto ): Bitmap? = imageGetter.getImage( data = this, originalSize = true )?.let { val scaled = it.createScaledBitmap( width = (it.width * imageScale).roundToInt(), height = (it.height * imageScale).roundToInt() ) val newWidth = scaled.width - stitchMode.startDrop - stitchMode.endDrop val newHeight = scaled.height - stitchMode.topDrop - stitchMode.bottomDrop if (newWidth < 1 || newHeight < 1) { scaled } else { Bitmap.createBitmap( scaled, stitchMode.startDrop, stitchMode.topDrop, newWidth.coerceAtLeast(1), newHeight.coerceAtLeast(1) ) } } private suspend fun Bitmap.createScaledBitmap( width: Int, height: Int ): Bitmap = imageScaler.scaleImage( image = this, width = width, height = height ) } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/di/ImageStitchModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.image_stitch.data.AndroidImageCombiner import com.t8rin.imagetoolbox.feature.image_stitch.domain.ImageCombiner import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ImageStitchModule { @Singleton @Binds fun provideImageCombiner( combiner: AndroidImageCombiner ): ImageCombiner } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/CombiningParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.domain import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode data class CombiningParams( val stitchMode: StitchMode = StitchMode.Horizontal, val spacing: Int = 0, val scaleSmallImagesToLarge: Boolean = false, val backgroundColor: Int = 0x00000000, val fadingEdgesMode: StitchFadeSide = StitchFadeSide.Start, val alignment: StitchAlignment = StitchAlignment.Start, val outputScale: Float = 0.5f, val blendingMode: BlendingMode = BlendingMode.SrcOver, val fadeStrength: Float = 1f ) ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/ImageCombiner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageWithSize import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize interface ImageCombiner { suspend fun combineImages( imageUris: List, combiningParams: CombiningParams, onProgress: (Int) -> Unit ): Pair suspend fun calculateCombinedImageDimensions( imageUris: List, combiningParams: CombiningParams ): IntegerSize suspend fun createCombinedImagesPreview( imageUris: List, combiningParams: CombiningParams, imageFormat: ImageFormat, quality: Quality, onGetByteCount: (Long) -> Unit ): ImageWithSize } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/SavableCombiningParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.domain import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.ui.utils.helper.entries data class SavableCombiningParams( val stitchMode: String, val spacing: Int, val scaleSmallImagesToLarge: Boolean, val backgroundColor: Int, val fadingEdgesMode: StitchFadeSide, val alignment: StitchAlignment, val outputScale: Float, val blendingMode: Int, val fadeStrength: Float ) fun CombiningParams.toSavable() = SavableCombiningParams( stitchMode = "${stitchMode.ordinal}_${stitchMode.gridCellsCount()}_${ stitchMode.drops().joinToString(separator = "_") }", spacing = spacing, scaleSmallImagesToLarge = scaleSmallImagesToLarge, backgroundColor = backgroundColor, fadingEdgesMode = fadingEdgesMode, alignment = alignment, outputScale = outputScale, blendingMode = blendingMode.value, fadeStrength = fadeStrength ) fun SavableCombiningParams.toParams() = CombiningParams( stitchMode = stitchMode.split("_").let { if (it.size < 2) StitchMode.Horizontal else { val mode = StitchMode.fromOrdinal(it.getOrNull(0)?.toIntOrNull() ?: 0) val cells = it.getOrNull(1)?.toIntOrNull() ?: 0 when (mode) { is StitchMode.Grid.Horizontal -> mode.copy(rows = cells) is StitchMode.Grid.Vertical -> mode.copy(columns = cells) is StitchMode.Auto -> StitchMode.Auto(it.drop(2).map { s -> s.toIntOrNull() ?: 0 }) else -> mode } } }, spacing = spacing, scaleSmallImagesToLarge = scaleSmallImagesToLarge, backgroundColor = backgroundColor, fadingEdgesMode = fadingEdgesMode, alignment = alignment, outputScale = outputScale, blendingMode = BlendingMode.entries.find { it.value == blendingMode } ?: BlendingMode.SrcOver, fadeStrength = fadeStrength ) ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchAlignment.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.domain enum class StitchAlignment { Start, Center, End } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchFadeSide.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.domain enum class StitchFadeSide { None, Start, End, Both } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/domain/StitchMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.domain import com.t8rin.imagetoolbox.core.domain.utils.safeCast sealed class StitchMode(val ordinal: Int) { fun drops(): List = safeCast()?.drops ?: emptyList() data class Auto( val topDrop: Int = 0, val bottomDrop: Int = 0, val startDrop: Int = 0, val endDrop: Int = 0 ) : StitchMode(-1) { val drops = listOf(topDrop, bottomDrop, startDrop, endDrop) constructor(drops: List) : this( topDrop = drops.getOrNull(0) ?: 0, bottomDrop = drops.getOrNull(1) ?: 0, startDrop = drops.getOrNull(2) ?: 0, endDrop = drops.getOrNull(3) ?: 0 ) } data object Horizontal : StitchMode(0) data object Vertical : StitchMode(1) sealed class Grid(ordinal: Int) : StitchMode(ordinal) { data class Horizontal(val rows: Int = 2) : Grid(2) data class Vertical(val columns: Int = 2) : Grid(3) } fun gridCellsCount(): Int { return when (this) { is Grid.Horizontal -> this.rows is Grid.Vertical -> this.columns else -> 0 } } fun isHorizontal(): Boolean = this is Horizontal || this is Grid.Horizontal companion object { fun fromOrdinal(ordinal: Int) = when (ordinal) { -1 -> Auto() 0 -> Horizontal 1 -> Vertical 2 -> Grid.Horizontal() 3 -> Grid.Vertical() else -> Horizontal } val entries by lazy { listOf( Horizontal, Vertical, Grid.Horizontal(), Grid.Vertical(), Auto() ) } } } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/ImageStitchingContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel import com.t8rin.imagetoolbox.core.ui.widget.controls.ScaleSmallImagesToLargeToggle import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.BlendingModeSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchFadeSide import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchMode import com.t8rin.imagetoolbox.feature.image_stitch.presentation.components.FadeStrengthSelector import com.t8rin.imagetoolbox.feature.image_stitch.presentation.components.ImageFadingEdgesSelector import com.t8rin.imagetoolbox.feature.image_stitch.presentation.components.ImageScaleSelector import com.t8rin.imagetoolbox.feature.image_stitch.presentation.components.SpacingSelector import com.t8rin.imagetoolbox.feature.image_stitch.presentation.components.StitchAlignmentSelector import com.t8rin.imagetoolbox.feature.image_stitch.presentation.components.StitchModeSelector import com.t8rin.imagetoolbox.feature.image_stitch.presentation.screenLogic.ImageStitchingComponent import kotlin.math.pow import kotlin.math.roundToLong @Composable fun ImageStitchingContent( component: ImageStitchingComponent ) { AutoContentBasedColors(component.previewBitmap) val imagePicker = rememberImagePicker(onSuccess = component::updateUris) val addImagesImagePicker = rememberImagePicker(onSuccess = component::addUrisToEnd) val addImages = addImagesImagePicker::pickImage val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.image_stitching), input = component.uris, isLoading = component.isImageLoading, size = component .imageByteSize?.times(component.combiningParams.outputScale.pow(2)) ?.roundToLong(), updateOnSizeChange = false ) }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.previewBitmap != null, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { editSheetData = listOf(it) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) ZoomButton( onClick = { showZoomSheet = true }, visible = component.previewBitmap != null, ) }, imagePreview = { ImageContainer( imageInside = isPortrait, showOriginal = false, previewBitmap = component.previewBitmap, originalBitmap = null, isLoading = component.isImageLoading, shouldShowPreview = true ) }, topAppBarPersistentActions = { if (component.uris.isNullOrEmpty()) { TopAppBarEmoji() } }, controls = { Column( verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { ImageReorderCarousel( images = component.uris, onReorder = component::updateUris, onNeedToAddImage = addImages, onNeedToRemoveImageAt = component::removeImageAt, onNavigate = component.onNavigate ) ImageScaleSelector( modifier = Modifier.padding(top = 8.dp), value = component.combiningParams.outputScale, onValueChange = component::updateImageScale, approximateImageSize = component.imageSize ) StitchModeSelector( value = component.combiningParams.stitchMode, onValueChange = component::setStitchMode ) AnimatedVisibility( visible = component.combiningParams.stitchMode !is StitchMode.Auto, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { SpacingSelector( value = component.combiningParams.spacing, onValueChange = component::updateImageSpacing ) } AnimatedVisibility( visible = component.combiningParams.spacing < 0 && component.combiningParams.stitchMode !is StitchMode.Auto, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { ImageFadingEdgesSelector( value = component.combiningParams.fadingEdgesMode, onValueChange = component::setFadingEdgesMode ) } AnimatedVisibility( visible = component.combiningParams.spacing < 0 && component.combiningParams.fadingEdgesMode != StitchFadeSide.None && component.combiningParams.stitchMode !is StitchMode.Auto, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { FadeStrengthSelector( value = component.combiningParams.fadeStrength, onValueChange = component::setFadeStrength ) } AnimatedVisibility( visible = component.combiningParams.stitchMode !is StitchMode.Auto, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { ScaleSmallImagesToLargeToggle( checked = component.combiningParams.scaleSmallImagesToLarge, onCheckedChange = component::toggleScaleSmallImagesToLarge ) } AnimatedVisibility( visible = component.combiningParams.spacing < 0 && component.combiningParams.stitchMode !is StitchMode.Auto, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { BlendingModeSelector( value = component.combiningParams.blendingMode, onValueChange = component::setBlendingMode, color = Color.Unspecified ) } AnimatedVisibility( visible = !component.combiningParams.scaleSmallImagesToLarge && component.combiningParams.stitchMode !is StitchMode.Auto, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { StitchAlignmentSelector( value = component.combiningParams.alignment, onValueChange = component::setStitchAlignment ) } ColorRowSelector( value = Color(component.combiningParams.backgroundColor), onValueChange = { component.updateBackgroundSelector(it.toArgb()) }, modifier = Modifier.container( shape = ShapeDefaults.extraLarge ) ) QualitySelector( imageFormat = component.imageInfo.imageFormat, quality = component.imageInfo.quality, onQualityChange = component::setQuality ) ImageFormatSelector( value = component.imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = component.imageInfo.quality, ) } }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), isPrimaryButtonVisible = component.previewBitmap != null, onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } }, canShowScreenData = !component.uris.isNullOrEmpty() ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris?.size ?: 1, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/FadeStrengthSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exercise import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun FadeStrengthSelector( modifier: Modifier = Modifier, value: Float, onValueChange: (Float) -> Unit ) { EnhancedSliderItem( modifier = modifier, value = value, title = stringResource(R.string.fade_strength), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(it.roundToTwoDigits()) }, sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Outlined.Exercise, shape = ShapeDefaults.extraLarge ) } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageFadingEdgesSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation.components import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchFadeSide @Composable fun ImageFadingEdgesSelector( modifier: Modifier = Modifier, value: StitchFadeSide, onValueChange: (StitchFadeSide) -> Unit ) { EnhancedButtonGroup( modifier = modifier .container(shape = ShapeDefaults.extraLarge), title = stringResource(id = R.string.fading_edges), entries = StitchFadeSide.entries, value = value, onValueChange = onValueChange, itemContent = { Text(it.title()) } ) } @Composable private fun StitchFadeSide.title() = when (this) { StitchFadeSide.None -> stringResource(R.string.disabled) StitchFadeSide.Start -> stringResource(R.string.start) StitchFadeSide.End -> stringResource(R.string.end) StitchFadeSide.Both -> stringResource(R.string.both) } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/ImageScaleSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.PhotoSizeSelectSmall import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.OOMWarning import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun ImageScaleSelector( modifier: Modifier, value: Float, approximateImageSize: IntegerSize, onValueChange: (Float) -> Unit ) { val scaledSize by remember(approximateImageSize, value) { derivedStateOf { val s = approximateImageSize * value if (s.isZero()) null else s } } val showWarning by remember(scaledSize) { derivedStateOf { scaledSize!!.width * scaledSize!!.height * 4L >= 7_000 * 7_000 * 3L } } EnhancedSliderItem( modifier = modifier, value = value.roundToTwoDigits(), title = stringResource(R.string.output_image_scale), valueRange = 0.1f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(it.roundToTwoDigits()) }, sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.PhotoSizeSelectSmall, shape = ShapeDefaults.extraLarge ) { AnimatedContent( targetState = scaledSize != null, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { notNull -> if (notNull) { Column { Row( modifier = Modifier .padding(4.dp) .container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) .padding(4.dp) ) { Column( modifier = Modifier .weight(1f) .container( autoShadowElevation = 0.2.dp, color = MaterialTheme.colorScheme.surfaceContainerLow ) .padding(4.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(stringResource(R.string.width, "")) Spacer(Modifier.height(8.dp)) Text( text = scaledSize!!.width.toString(), fontSize = 16.sp, textAlign = TextAlign.Center, color = LocalContentColor.current.copy(0.5f), ) } Spacer(Modifier.width(8.dp)) Column( modifier = Modifier .weight(1f) .container( autoShadowElevation = 0.2.dp, color = MaterialTheme.colorScheme.surfaceContainerLow ) .padding(4.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(stringResource(R.string.height, "")) Spacer(Modifier.height(8.dp)) Text( text = scaledSize!!.height.toString(), fontSize = 16.sp, textAlign = TextAlign.Center, color = LocalContentColor.current.copy(0.5f), ) } } OOMWarning( visible = showWarning, modifier = Modifier.padding(4.dp) ) } } else { Text( text = stringResource(R.string.loading), modifier = Modifier .padding(4.dp) .fillMaxWidth() .container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) .padding(6.dp), color = LocalContentColor.current.copy(0.5f), textAlign = TextAlign.Center ) } } } } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/SpacingSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FormatLineSpacing import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlin.math.roundToInt @Composable fun SpacingSelector( modifier: Modifier = Modifier, value: Int, onValueChange: (Int) -> Unit ) { EnhancedSliderItem( modifier = modifier, value = value, title = stringResource(R.string.spacing), valueRange = -512f..256f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange(it.roundToInt()) }, sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.FormatLineSpacing, shape = ShapeDefaults.extraLarge ) } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchAlignmentSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.state.derivedValueOf import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchAlignment @Composable fun StitchAlignmentSelector( modifier: Modifier = Modifier, value: StitchAlignment, onValueChange: (StitchAlignment) -> Unit ) { Column( modifier = modifier .container(shape = ShapeDefaults.extraLarge) ) { EnhancedButtonGroup( modifier = Modifier.padding(start = 3.dp, end = 2.dp), enabled = true, title = { Column { Spacer(modifier = Modifier.height(8.dp)) Text(stringResource(id = R.string.alignment)) Spacer(modifier = Modifier.height(8.dp)) } }, itemCount = StitchAlignment.entries.size, selectedIndex = derivedValueOf(value) { StitchAlignment.entries.indexOfFirst { it == value } }, onIndexChange = { onValueChange(StitchAlignment.entries[it]) }, itemContent = { val text = when (StitchAlignment.entries[it]) { StitchAlignment.Start -> R.string.start StitchAlignment.Center -> R.string.center StitchAlignment.End -> R.string.end } Text(stringResource(text)) } ) } } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/components/StitchModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BorderBottom import androidx.compose.material.icons.rounded.BorderLeft import androidx.compose.material.icons.rounded.BorderRight import androidx.compose.material.icons.rounded.BorderTop import androidx.compose.material.icons.rounded.TableRows import androidx.compose.material.icons.rounded.ViewColumn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchMode import kotlin.math.roundToInt @Composable fun StitchModeSelector( modifier: Modifier = Modifier, value: StitchMode, onValueChange: (StitchMode) -> Unit ) { Column( modifier = modifier .container(shape = ShapeDefaults.extraLarge) ) { EnhancedButtonGroup( modifier = Modifier.padding(start = 3.dp, end = 2.dp), title = stringResource(id = R.string.stitch_mode), entries = StitchMode.entries, value = value, onValueChange = onValueChange, itemContent = { Text(it.title()) }, useClassFinding = true ) AnimatedVisibility( visible = value is StitchMode.Grid.Horizontal, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { EnhancedSliderItem( modifier = Modifier.padding(8.dp), value = value.gridCellsCount(), title = stringResource(R.string.rows_count), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.TableRows, valueRange = 2f..6f, steps = 3, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( StitchMode.Grid.Horizontal(it.roundToInt()) ) }, onValueChange = {}, shape = ShapeDefaults.default, containerColor = MaterialTheme.colorScheme.surface ) } AnimatedVisibility( visible = value is StitchMode.Grid.Vertical, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { EnhancedSliderItem( modifier = Modifier.padding(8.dp), value = value.gridCellsCount(), title = stringResource(R.string.columns_count), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.ViewColumn, valueRange = 2f..6f, steps = 3, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( StitchMode.Grid.Vertical(it.roundToInt()) ) }, onValueChange = {}, shape = ShapeDefaults.default, containerColor = MaterialTheme.colorScheme.surface ) } AnimatedVisibility( visible = value is StitchMode.Auto, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { Spacer(Modifier.height(8.dp)) EnhancedSliderItem( modifier = Modifier.padding(horizontal = 8.dp), value = value.safeCast()?.topDrop ?: 0, title = stringResource(R.string.top_drop), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.BorderTop, valueRange = 0f..512f, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( value.safeCast()?.copy( topDrop = it.roundToInt() ) ?: value ) }, onValueChange = {}, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surface ) Spacer(Modifier.height(4.dp)) EnhancedSliderItem( modifier = Modifier.padding(horizontal = 8.dp), value = value.safeCast()?.bottomDrop ?: 0, title = stringResource(R.string.bottom_drop), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.BorderBottom, valueRange = 0f..512f, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( value.safeCast()?.copy( bottomDrop = it.roundToInt() ) ?: value ) }, onValueChange = {}, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surface ) Spacer(Modifier.height(4.dp)) EnhancedSliderItem( modifier = Modifier.padding(horizontal = 8.dp), value = value.safeCast()?.startDrop ?: 0, title = stringResource(R.string.start_drop), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.BorderLeft, valueRange = 0f..512f, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( value.safeCast()?.copy( startDrop = it.roundToInt() ) ?: value ) }, onValueChange = {}, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surface ) Spacer(Modifier.height(4.dp)) EnhancedSliderItem( modifier = Modifier.padding(horizontal = 8.dp), value = value.safeCast()?.endDrop ?: 0, title = stringResource(R.string.end_drop), sliderModifier = Modifier .padding( top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp ), icon = Icons.Rounded.BorderRight, valueRange = 0f..512f, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange( value.safeCast()?.copy( endDrop = it.roundToInt() ) ?: value ) }, onValueChange = {}, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface ) Spacer(Modifier.height(8.dp)) } } } } @Composable private fun StitchMode.title(): String = when (this) { is StitchMode.Auto -> stringResource(R.string.auto) is StitchMode.Grid.Horizontal -> stringResource(R.string.horizontal_grid) is StitchMode.Grid.Vertical -> stringResource(R.string.vertical_grid) StitchMode.Horizontal -> stringResource(R.string.horizontal) StitchMode.Vertical -> stringResource(R.string.vertical) } ================================================ FILE: feature/image-stitch/src/main/java/com/t8rin/imagetoolbox/feature/image_stitch/presentation/screenLogic/ImageStitchingComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.image_stitch.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.image_stitch.domain.CombiningParams import com.t8rin.imagetoolbox.feature.image_stitch.domain.ImageCombiner import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchAlignment import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchFadeSide import com.t8rin.imagetoolbox.feature.image_stitch.domain.StitchMode import com.t8rin.imagetoolbox.feature.image_stitch.domain.toParams import com.t8rin.imagetoolbox.feature.image_stitch.domain.toSavable import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay class ImageStitchingComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val imageCombiner: ImageCombiner, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _imageSize: MutableState = mutableStateOf(IntegerSize(0, 0)) val imageSize by _imageSize private val _uris = mutableStateOf?>(null) val uris by _uris private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _imageInfo = mutableStateOf(ImageInfo(imageFormat = ImageFormat.Png.Lossless)) val imageInfo by _imageInfo private val _combiningParams = fileController.savable( scope = componentScope, initial = CombiningParams().toSavable() ) val combiningParams: CombiningParams get() = _combiningParams.get().toParams() private val _imageByteSize: MutableState = mutableStateOf(null) val imageByteSize by _imageByteSize private val _done: MutableState = mutableIntStateOf(0) val done by _done init { debounce { initialUris?.let(::updateUris) } } fun setImageFormat(imageFormat: ImageFormat) { _imageInfo.update { it.copy(imageFormat = imageFormat) } calculatePreview(true) } fun updateUris(uris: List?) { if (uris != _uris.value) { _uris.value = uris calculatePreview(true) } } private var calculationPreviewJob: Job? by smartJob { _isImageLoading.update { false } } private var previousParams: CombiningParams? = null private fun calculatePreview(force: Boolean = false) { if (previousParams == combiningParams && !force) return calculationPreviewJob = componentScope.launch { delay(300L) _isImageLoading.value = true uris?.let { uris -> registerChanges() imageCombiner.createCombinedImagesPreview( imageUris = uris.map { it.toString() }, combiningParams = combiningParams, imageFormat = imageInfo.imageFormat, quality = imageInfo.quality, onGetByteCount = { _imageByteSize.value = it } ).let { (image, size) -> _previewBitmap.value = image _imageSize.value = size previousParams = combiningParams } } _isImageLoading.value = false } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 imageCombiner.combineImages( imageUris = uris?.map { it.toString() } ?: emptyList(), combiningParams = combiningParams, onProgress = { _done.value = it updateProgress( done = done, total = uris.orEmpty().size ) } ).let { (image, info) -> val imageInfo = info.copy( quality = imageInfo.quality, imageFormat = imageInfo.imageFormat ) parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = "Combined", sequenceNumber = null, data = imageCompressor.compressAndTransform( image = image, imageInfo = imageInfo ) ), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.value = false } } fun shareBitmap() { savingJob = trackProgress { _isSaving.value = true _done.value = 0 imageCombiner.combineImages( imageUris = uris?.map { it.toString() } ?: emptyList(), combiningParams = combiningParams, onProgress = { _done.value = it updateProgress( done = done, total = uris.orEmpty().size ) } ).let { it.copy( second = it.second.copy( quality = imageInfo.quality, imageFormat = imageInfo.imageFormat ) ) }.let { (image, imageInfo) -> shareProvider.shareImage( image = image, imageInfo = imageInfo, onComplete = AppToastHost::showConfetti ) } _isSaving.value = false } } fun setQuality(quality: Quality) { _imageInfo.update { it.copy(quality = quality) } calculatePreview(true) } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun updateImageScale(newScale: Float) { _combiningParams.update { it.copy(outputScale = newScale) } registerChanges() } fun setStitchMode(value: StitchMode) { updateCombiningParams( combiningParams.copy( stitchMode = value, scaleSmallImagesToLarge = false ) ) calculatePreview() } fun setFadingEdgesMode(mode: StitchFadeSide) { updateCombiningParams( combiningParams.copy(fadingEdgesMode = mode) ) calculatePreview() } fun setFadeStrength(value: Float) { updateCombiningParams(combiningParams.copy(fadeStrength = value)) calculatePreview() } fun updateImageSpacing(spacing: Int) { updateCombiningParams( combiningParams.copy(spacing = spacing) ) calculatePreview() } fun toggleScaleSmallImagesToLarge(checked: Boolean) { updateCombiningParams( combiningParams.copy(scaleSmallImagesToLarge = checked) ) calculatePreview() } fun updateBackgroundSelector(color: Int) { updateCombiningParams( combiningParams.copy(backgroundColor = color) ) calculatePreview() } fun addUrisToEnd(uris: List) { _uris.update { list -> list?.plus( uris.filter { it !in list } ) } calculatePreview(true) } fun removeImageAt(index: Int) { _uris.update { list -> list?.toMutableList()?.apply { removeAt(index) }?.takeIf { it.size >= 2 }.also { if (it == null) _previewBitmap.value = null } } calculatePreview(true) } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 imageCombiner.combineImages( imageUris = uris?.map { it.toString() } ?: emptyList(), combiningParams = combiningParams, onProgress = { _done.value = it updateProgress( done = done, total = uris.orEmpty().size ) } ).let { it.copy( second = it.second.copy( quality = imageInfo.quality, imageFormat = imageInfo.imageFormat ) ) }.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun setStitchAlignment(stitchAlignment: StitchAlignment) { updateCombiningParams(combiningParams.copy(alignment = stitchAlignment)) calculatePreview() } fun setBlendingMode(blendingMode: BlendingMode) { updateCombiningParams(combiningParams.copy(blendingMode = blendingMode)) calculatePreview() } private fun updateCombiningParams(params: CombiningParams) { _combiningParams.update { params.toSavable() } } fun getFormatForFilenameSelection(): ImageFormat = imageInfo.imageFormat @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ImageStitchingComponent } } ================================================ FILE: feature/jxl-tools/.gitignore ================================================ /build ================================================ FILE: feature/jxl-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.jxl_tools" dependencies { implementation(libs.jxl.coder) } ================================================ FILE: feature/jxl-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/data/AndroidJxlConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.data import android.content.Context import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri import com.awxkee.jxlcoder.JxlAnimatedEncoder import com.awxkee.jxlcoder.JxlAnimatedImage import com.awxkee.jxlcoder.JxlChannelsConfiguration import com.awxkee.jxlcoder.JxlCoder import com.awxkee.jxlcoder.JxlCompressionOption import com.awxkee.jxlcoder.JxlDecodingSpeed import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.feature.jxl_tools.domain.AnimatedJxlParams import com.t8rin.imagetoolbox.feature.jxl_tools.domain.JxlConverter import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.cancel import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidJxlConverter @Inject constructor( @ApplicationContext private val context: Context, private val imageGetter: ImageGetter, private val imageShareProvider: ImageShareProvider, private val imageScaler: ImageScaler, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, JxlConverter { override suspend fun jpegToJxl( jpegUris: List, onFailure: (Throwable) -> Unit, onProgress: suspend (String, ByteArray) -> Unit ) = withContext(defaultDispatcher) { jpegUris.forEach { uri -> runSuspendCatching { uri.jxl?.let { onProgress(uri, it) } }.onFailure(onFailure) } } override suspend fun jxlToJpeg( jxlUris: List, onFailure: (Throwable) -> Unit, onProgress: suspend (String, ByteArray) -> Unit ) = withContext(defaultDispatcher) { jxlUris.forEach { uri -> runSuspendCatching { uri.jpeg?.let { onProgress(uri, it) } }.onFailure(onFailure) } } override suspend fun createJxlAnimation( imageUris: List, params: AnimatedJxlParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): ByteArray? = withContext(defaultDispatcher) { val jxlQuality = params.quality as? Quality.Jxl if (jxlQuality == null) { onFailure(IllegalArgumentException("Quality Must be Jxl")) return@withContext null } runSuspendCatching { val size = params.size ?: imageGetter.getImage(data = imageUris[0])!!.run { IntegerSize(width, height) } if (size.width <= 0 || size.height <= 0) { onFailure(IllegalArgumentException("Width and height must be > 0")) return@withContext null } val (quality, compressionOption) = if (params.isLossy) { params.quality.qualityValue to JxlCompressionOption.LOSSY } else 100 to JxlCompressionOption.LOSSLESS val encoder = JxlAnimatedEncoder( width = size.width, height = size.height, numLoops = params.repeatCount, channelsConfiguration = when (params.quality.channels) { Quality.Channels.RGBA -> JxlChannelsConfiguration.RGBA Quality.Channels.RGB -> JxlChannelsConfiguration.RGB Quality.Channels.Monochrome -> JxlChannelsConfiguration.MONOCHROME }, compressionOption = compressionOption, effort = params.quality.effort.coerceAtLeast(1), quality = quality, decodingSpeed = JxlDecodingSpeed.entries.first { it.ordinal == params.quality.speed } ) imageUris.forEach { uri -> imageGetter.getImage( data = uri, size = params.size )?.let { encoder.addFrame( bitmap = imageScaler.scaleImage( image = imageScaler.scaleImage( image = it, width = size.width, height = size.height, resizeType = ResizeType.Flexible ), width = size.width, height = size.height, resizeType = ResizeType.CenterCrop( canvasColor = Color.Transparent.toArgb() ) ).apply { setHasAlpha(true) }, duration = params.delay ) } onProgress() } encoder.encode() }.onFailure { onFailure(it) return@withContext null }.getOrNull() } override fun extractFramesFromJxl( jxlUri: String, imageFormat: ImageFormat, imageFrames: ImageFrames, quality: Quality, onFailure: (Throwable) -> Unit, onGetFramesCount: (frames: Int) -> Unit ): Flow = flow { val bytes = jxlUri.bytes ?: return@flow val decoder = JxlAnimatedImage(bytes) onGetFramesCount(decoder.numberOfFrames) val indexes = imageFrames .getFramePositions(decoder.numberOfFrames) .map { it - 1 } repeat(decoder.numberOfFrames) { pos -> if (!currentCoroutineContext().isActive) { currentCoroutineContext().cancel(null) return@repeat } decoder.getFrame(pos).let { frame -> imageShareProvider.cacheImage( image = frame, imageInfo = ImageInfo( width = frame.width, height = frame.height, imageFormat = imageFormat, quality = quality ) ) }?.takeIf { pos in indexes }?.let { emit(it) } } }.catch { onFailure(it) } private val String.jxl: ByteArray? get() = bytes?.let { JxlCoder.Convenience.construct(it) } private val String.jpeg: ByteArray? get() = bytes?.let { JxlCoder.Convenience.reconstructJPEG(it) } private val String.bytes: ByteArray? get() = context .contentResolver .openInputStream(toUri())?.use { it.readBytes() } } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/di/JxlToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.di import com.t8rin.imagetoolbox.feature.jxl_tools.data.AndroidJxlConverter import com.t8rin.imagetoolbox.feature.jxl_tools.domain.JxlConverter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface JxlToolsModule { @Binds @Singleton fun provideTranscoder( converter: AndroidJxlConverter ): JxlConverter } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/AnimatedJxlParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize data class AnimatedJxlParams( val size: IntegerSize?, val repeatCount: Int, val delay: Int, val quality: Quality, val isLossy: Boolean ) { companion object { val Default by lazy { AnimatedJxlParams( size = null, repeatCount = 1, delay = 1000, quality = Quality.Jxl(), isLossy = true ) } } } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/domain/JxlConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.Quality import kotlinx.coroutines.flow.Flow interface JxlConverter { suspend fun jpegToJxl( jpegUris: List, onFailure: (Throwable) -> Unit, onProgress: suspend (originalUri: String, data: ByteArray) -> Unit ) suspend fun jxlToJpeg( jxlUris: List, onFailure: (Throwable) -> Unit, onProgress: suspend (originalUri: String, data: ByteArray) -> Unit ) suspend fun createJxlAnimation( imageUris: List, params: AnimatedJxlParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): ByteArray? fun extractFramesFromJxl( jxlUri: String, imageFormat: ImageFormat, imageFrames: ImageFrames, quality: Quality, onFailure: (Throwable) -> Unit, onGetFramesCount: (frames: Int) -> Unit = {} ): Flow } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/JxlToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.presentation import android.net.Uri import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Jxl import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.core.utils.isJxl import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components.JxlToolsBitmapPreview import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components.JxlToolsButtons import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components.JxlToolsControls import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components.JxlToolsNoDataControls import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components.JxlToolsTopAppBarActions import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic.JxlToolsComponent @Composable fun JxlToolsContent( component: JxlToolsComponent ) { val pickJpegsLauncher = rememberFilePicker( mimeType = MimeType.JpgAll, onSuccess = { list: List -> list.let { uris -> component.setType( type = Screen.JxlTools.Type.JpegToJxl(uris) ) } } ) val pickJxlsLauncher = rememberFilePicker { list: List -> list.filter { it.isJxl() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_jxl_image_to_start), icon = Icons.Filled.Jxl ) } else { component.setType( type = Screen.JxlTools.Type.JxlToJpeg(uris) ) } } } val pickSingleJxlLauncher = rememberFilePicker { uri: Uri -> if (uri.isJxl()) { component.setType( type = Screen.JxlTools.Type.JxlToImage(uri) ) } else { AppToastHost.showToast( message = getString(R.string.select_jxl_image_to_start), icon = Icons.Filled.Jxl ) } } val imagePicker = rememberImagePicker { uris: List -> component.setType( type = Screen.JxlTools.Type.ImageToJxl(uris) ) } val addImagesImagePicker = rememberImagePicker { uris: List -> component.setType( type = Screen.JxlTools.Type.ImageToJxl( (component.type as? Screen.JxlTools.Type.ImageToJxl)?.imageUris?.plus(uris) ?.distinct() ) ) } val addJpegsLauncher = rememberFilePicker( mimeType = MimeType.JpgAll, onSuccess = { list: List -> component.setType( type = (component.type as? Screen.JxlTools.Type.JpegToJxl)?.let { it.copy(jpegImageUris = it.jpegImageUris?.plus(list)?.distinct()) } ) } ) val addJxlsLauncher = rememberFilePicker { list: List -> list.filter { it.isJxl() }.let { uris -> if (uris.isEmpty()) { AppToastHost.showToast( message = getString(R.string.select_jxl_image_to_start), icon = Icons.Filled.Jxl ) } else { component.setType( type = (component.type as? Screen.JxlTools.Type.JxlToJpeg)?.let { it.copy(jxlImageUris = it.jxlImageUris?.plus(uris)?.distinct()) } ) } } } fun pickImage(type: Screen.JxlTools.Type? = null) { when (type ?: component.type) { is Screen.JxlTools.Type.ImageToJxl -> imagePicker.pickImage() is Screen.JxlTools.Type.JpegToJxl -> pickJpegsLauncher.pickFile() is Screen.JxlTools.Type.JxlToImage -> pickSingleJxlLauncher.pickFile() else -> pickJxlsLauncher.pickFile() } } val addImages: () -> Unit = { when (component.type) { is Screen.JxlTools.Type.ImageToJxl -> addImagesImagePicker.pickImage() is Screen.JxlTools.Type.JpegToJxl -> addJpegsLauncher.pickFile() else -> addJxlsLauncher.pickFile() } } var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = when (val type = component.type) { null -> stringResource(R.string.jxl_tools) else -> stringResource(type.title) }, input = component.type, isLoading = component.isLoading, size = null ) }, onGoBack = onBack, topAppBarPersistentActions = { JxlToolsTopAppBarActions(component = component) }, actions = { if (component.type is Screen.JxlTools.Type.JxlToImage) { ShareButton( enabled = !component.isLoading && component.type != null, onShare = component::performSharing ) } }, imagePreview = { JxlToolsBitmapPreview( component = component, onAddImages = addImages ) }, placeImagePreview = component.type is Screen.JxlTools.Type.JxlToImage || component.type is Screen.JxlTools.Type.ImageToJxl, showImagePreviewAsStickyHeader = false, autoClearFocus = false, controls = { JxlToolsControls( component = component, onAddImages = addImages ) }, contentPadding = animateDpAsState( if (component.type == null) 12.dp else 20.dp ).value, buttons = { actions -> JxlToolsButtons( component = component, actions = actions, onPickImage = ::pickImage, imagePicker = imagePicker ) }, insetsForNoData = WindowInsets(0), noDataControls = { JxlToolsNoDataControls(::pickImage) }, canShowScreenData = component.type != null ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = component::clearAll, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/AnimatedJxlParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.EnergySavingsLeaf import androidx.compose.material.icons.outlined.PhotoSizeSelectLarge import androidx.compose.material.icons.outlined.RepeatOne import androidx.compose.material.icons.outlined.Timelapse import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.jxl_tools.domain.AnimatedJxlParams import kotlin.math.roundToInt @Composable fun AnimatedJxlParamsSelector( value: AnimatedJxlParams, onValueChange: (AnimatedJxlParams) -> Unit ) { Column { val size = value.size ?: IntegerSize.Undefined AnimatedVisibility(size.isDefined()) { ResizeImageField( imageInfo = ImageInfo(size.width, size.height), originalSize = null, onWidthChange = { onValueChange( value.copy( size = size.copy(width = it) ) ) }, onHeightChange = { onValueChange( value.copy( size = size.copy(height = it) ) ) } ) } Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.use_size_of_first_frame), subtitle = stringResource(id = R.string.use_size_of_first_frame_sub), checked = value.size == null, onClick = { onValueChange( value.copy(size = if (it) null else IntegerSize(1000, 1000)) ) }, startIcon = Icons.Outlined.PhotoSizeSelectLarge, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.lossy_compression), subtitle = stringResource(id = R.string.lossy_compression_sub), checked = value.isLossy, onClick = { onValueChange( value.copy( isLossy = it ) ) }, startIcon = Icons.Outlined.EnergySavingsLeaf, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) QualitySelector( imageFormat = if (value.isLossy) { ImageFormat.Jxl.Lossy } else ImageFormat.Jxl.Lossless, quality = value.quality, onQualityChange = { onValueChange( value.copy( quality = it ) ) } ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.repeatCount, icon = Icons.Outlined.RepeatOne, title = stringResource(id = R.string.repeat_count), valueRange = 1f..10f, steps = 9, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( repeatCount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.delay, icon = Icons.Outlined.Timelapse, title = stringResource(id = R.string.frame_delay), valueRange = 1f..4000f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( delay = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) } } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsBitmapPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.ImagesPreviewWithSelection import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic.JxlToolsComponent @Composable internal fun JxlToolsBitmapPreview( component: JxlToolsComponent, onAddImages: () -> Unit ) { val uris = when (val type = component.type) { is Screen.JxlTools.Type.JpegToJxl -> type.jpegImageUris is Screen.JxlTools.Type.JxlToJpeg -> type.jxlImageUris is Screen.JxlTools.Type.ImageToJxl -> type.imageUris is Screen.JxlTools.Type.JxlToImage -> listOfNotNull(type.jxlUri) null -> null } ?: emptyList() val isPortrait by isPortraitOrientationAsState() AnimatedContent( targetState = component.isLoading to component.type ) { (loading, type) -> Box( contentAlignment = Alignment.Center, modifier = if (loading) { Modifier.padding(32.dp) } else Modifier ) { if (loading || type == null) { EnhancedLoadingIndicator() } else { when (type) { is Screen.JxlTools.Type.JxlToImage -> { ImagesPreviewWithSelection( imageUris = component.convertedImageUris, imageFrames = component.imageFrames, onFrameSelectionChange = component::updateJxlFrames, isPortrait = isPortrait, isLoadingImages = component.isLoadingJxlImages ) } is Screen.JxlTools.Type.ImageToJxl -> { ImageReorderCarousel( images = uris, modifier = Modifier .padding(top = if (isPortrait) 24.dp else 0.dp) .container( shape = ShapeDefaults.extraLarge, color = if (isPortrait) { Color.Unspecified } else MaterialTheme.colorScheme.surface ), onReorder = { component.setType( Screen.JxlTools.Type.ImageToJxl(it) ) }, onNeedToAddImage = onAddImages, onNeedToRemoveImageAt = { component.setType( Screen.JxlTools.Type.ImageToJxl( (component.type as Screen.JxlTools.Type.ImageToJxl) .imageUris?.toMutableList() ?.apply { removeAt(it) } ) ) }, onNavigate = component.onNavigate ) Spacer(modifier = Modifier.height(12.dp)) } else -> Unit } } } } } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic.JxlToolsComponent @Composable internal fun JxlToolsButtons( component: JxlToolsComponent, actions: @Composable RowScope.() -> Unit, onPickImage: () -> Unit, imagePicker: ImagePicker ) { val uris = when (val type = component.type) { is Screen.JxlTools.Type.JpegToJxl -> type.jpegImageUris is Screen.JxlTools.Type.JxlToJpeg -> type.jxlImageUris is Screen.JxlTools.Type.ImageToJxl -> type.imageUris is Screen.JxlTools.Type.JxlToImage -> listOfNotNull(type.jxlUri) null -> null } ?: emptyList() val save: (oneTimeSaveLocationUri: String?) -> Unit = { component.save( oneTimeSaveLocationUri = it ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.type == null, onSecondaryButtonClick = onPickImage, isPrimaryButtonVisible = component.canSave, onPrimaryButtonClick = { save(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (component.type is Screen.JxlTools.Type.JxlToImage) { actions() } else { EnhancedChip( selected = true, onClick = null, selectedColor = MaterialTheme.colorScheme.secondaryContainer, modifier = Modifier.padding(8.dp) ) { Text(uris.size.toString()) } } }, showNullDataButtonAsContainer = true, onSecondaryButtonLongClick = if (component.type is Screen.JxlTools.Type.ImageToJxl || component.type == null) { { showOneTimeImagePickingDialog = true } } else null ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = save ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic.JxlToolsComponent @Composable internal fun JxlToolsControls( component: JxlToolsComponent, onAddImages: () -> Unit ) { val isPortrait by isPortraitOrientationAsState() when (component.type) { is Screen.JxlTools.Type.JxlToImage -> { Spacer(modifier = Modifier.height(16.dp)) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, quality = component.params.quality, ) Spacer(modifier = Modifier.height(8.dp)) QualitySelector( imageFormat = component.imageFormat, quality = component.params.quality, onQualityChange = { component.updateParams( component.params.copy( quality = it ) ) } ) Spacer(modifier = Modifier.height(16.dp)) } is Screen.JxlTools.Type.JpegToJxl, is Screen.JxlTools.Type.JxlToJpeg -> { val uris = when (val type = component.type) { is Screen.JxlTools.Type.JpegToJxl -> type.jpegImageUris is Screen.JxlTools.Type.JxlToJpeg -> type.jxlImageUris is Screen.JxlTools.Type.ImageToJxl -> type.imageUris is Screen.JxlTools.Type.JxlToImage -> listOfNotNull(type.jxlUri) null -> null } ?: emptyList() UrisPreview( modifier = Modifier .padding( vertical = if (isPortrait) 24.dp else 8.dp ), uris = uris, isPortrait = true, onRemoveUri = component::removeUri, onAddUris = onAddImages ) } is Screen.JxlTools.Type.ImageToJxl -> { AnimatedJxlParamsSelector( value = component.params, onValueChange = component::updateParams ) } else -> Unit } } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsNoDataControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable internal fun JxlToolsNoDataControls( onPickImage: (Screen.JxlTools.Type) -> Unit ) { val isPortrait by isPortraitOrientationAsState() val types = remember { Screen.JxlTools.Type.entries } val preference1 = @Composable { PreferenceItem( title = stringResource(types[0].title), subtitle = stringResource(types[0].subtitle), startIcon = types[0].icon, modifier = Modifier.fillMaxWidth(), onClick = { onPickImage(types[0]) } ) } val preference2 = @Composable { PreferenceItem( title = stringResource(types[1].title), subtitle = stringResource(types[1].subtitle), startIcon = types[1].icon, modifier = Modifier.fillMaxWidth(), onClick = { onPickImage(types[1]) } ) } val preference3 = @Composable { PreferenceItem( title = stringResource(types[2].title), subtitle = stringResource(types[2].subtitle), startIcon = types[2].icon, modifier = Modifier.fillMaxWidth(), onClick = { onPickImage(types[2]) } ) } val preference4 = @Composable { PreferenceItem( title = stringResource(types[3].title), subtitle = stringResource(types[3].subtitle), startIcon = types[3].icon, modifier = Modifier.fillMaxWidth(), onClick = { onPickImage(types[3]) } ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() Spacer(modifier = Modifier.height(8.dp)) preference3() Spacer(modifier = Modifier.height(8.dp)) preference4() } } else { val direction = LocalLayoutDirection.current val cutout = WindowInsets.displayCutout.asPaddingValues().let { PaddingValues( start = it.calculateStartPadding(direction), end = it.calculateEndPadding(direction) ) } Column { Row( modifier = Modifier.padding(cutout) ) { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.padding(cutout) ) { preference3.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference4.withModifier(modifier = Modifier.weight(1f)) } } } } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/components/JxlToolsTopAppBarActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.jxl_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic.JxlToolsComponent @Composable internal fun RowScope.JxlToolsTopAppBarActions( component: JxlToolsComponent ) { val isJxlToImage = component.type is Screen.JxlTools.Type.JxlToImage if (component.type == null) TopAppBarEmoji() else if (!isJxlToImage) { ShareButton( enabled = !component.isLoading && component.type != null, onShare = component::performSharing ) } val pagesSize by remember(component.imageFrames, component.convertedImageUris) { derivedStateOf { component.imageFrames.getFramePositions(component.convertedImageUris.size).size } } AnimatedVisibility( visible = isJxlToImage && pagesSize != component.convertedImageUris.size, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = component::selectAllConvertedImages ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = isJxlToImage && pagesSize != 0 ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { pagesSize.takeIf { it != 0 }?.let { Spacer(Modifier.width(8.dp)) Text( text = it.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) } EnhancedIconButton( onClick = component::clearConvertedImagesSelection ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } } ================================================ FILE: feature/jxl-tools/src/main/java/com/t8rin/imagetoolbox/feature/jxl_tools/presentation/screenLogic/JxlToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.FileSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.jxl_tools.domain.AnimatedJxlParams import com.t8rin.imagetoolbox.feature.jxl_tools.domain.JxlConverter import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.flow.onCompletion class JxlToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialType: Screen.JxlTools.Type?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val jxlConverter: JxlConverter, private val fileController: FileController, private val filenameCreator: FilenameCreator, private val shareProvider: ShareProvider, private val imageGetter: ImageGetter, private val imageCompressor: ImageCompressor, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialType?.let(::setType) } } private val _type: MutableState = mutableStateOf(null) val type by _type private val _isLoading: MutableState = mutableStateOf(false) val isLoading by _isLoading private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat by _imageFormat private val _imageFrames: MutableState = mutableStateOf(ImageFrames.All) val imageFrames by _imageFrames private val _isLoadingJxlImages: MutableState = mutableStateOf(false) val isLoadingJxlImages by _isLoadingJxlImages private val _params: MutableState = mutableStateOf(AnimatedJxlParams.Default) val params by _params private val _convertedImageUris: MutableState> = mutableStateOf(emptyList()) val convertedImageUris by _convertedImageUris fun setType( type: Screen.JxlTools.Type? ) { when (type) { is Screen.JxlTools.Type.JpegToJxl -> { if (!type.jpegImageUris.isNullOrEmpty()) _type.update { type } else _type.update { null } } is Screen.JxlTools.Type.JxlToJpeg -> { if (!type.jxlImageUris.isNullOrEmpty()) _type.update { type } else _type.update { null } } is Screen.JxlTools.Type.JxlToImage -> { type.jxlUri?.let(::setJxlUri) ?: _type.update { null } } else -> _type.update { type } } registerChanges() if (_type.value == null) { clearAll() } else if (!_type.value!!::class.isInstance(type)) { clearAll() } } private var collectionJob: Job? by smartJob { _isLoading.update { false } } private fun setJxlUri(uri: Uri) { clearAll() _type.update { Screen.JxlTools.Type.JxlToImage(uri) } updateJxlFrames(ImageFrames.All) collectionJob = componentScope.launch { _isLoading.update { true } _isLoadingJxlImages.update { true } jxlConverter.extractFramesFromJxl( jxlUri = uri.toString(), imageFormat = imageFormat, quality = params.quality, imageFrames = imageFrames, onFailure = AppToastHost::showFailureToast ).onCompletion { _isLoading.update { false } _isLoadingJxlImages.update { false } }.collect { nextUri -> if (isLoading) { _isLoading.update { false } } _convertedImageUris.update { it + nextUri } } } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun save( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.JxlTools.Type.JpegToJxl -> { val results = mutableListOf() val jpegUris = type.jpegImageUris?.map { it.toString() } ?: emptyList() _left.value = jpegUris.size jxlConverter.jpegToJxl( jpegUris = jpegUris, onFailure = { results.add(SaveResult.Error.Exception(it)) } ) { uri, jxlBytes -> results.add( fileController.save( saveTarget = JxlSaveTarget(uri, jxlBytes), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) } is Screen.JxlTools.Type.JxlToJpeg -> { val results = mutableListOf() val jxlUris = type.jxlImageUris?.map { it.toString() } ?: emptyList() _left.value = jxlUris.size jxlConverter.jxlToJpeg( jxlUris = jxlUris, onFailure = { results.add(SaveResult.Error.Exception(it)) } ) { uri, jpegBytes -> results.add( fileController.save( saveTarget = JpegSaveTarget(uri, jpegBytes), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) } is Screen.JxlTools.Type.JxlToImage -> { val results = mutableListOf() type.jxlUri?.toString()?.also { jxlUri -> _left.value = 0 jxlConverter.extractFramesFromJxl( jxlUri = jxlUri, imageFormat = imageFormat, quality = params.quality, imageFrames = imageFrames, onFailure = { results.add(SaveResult.Error.Exception(it)) }, onGetFramesCount = { if (it == 0) { _isSaving.value = false savingJob?.cancel() parseSaveResults( listOf(SaveResult.Error.MissingPermissions) ) } _left.value = imageFrames.getFramePositions(it).size updateProgress( done = done, total = left ) } ).onCompletion { parseSaveResults(results.onSuccess(::registerSave)) }.collect { uri -> imageGetter.getImage( data = uri, originalSize = true )?.let { localBitmap -> val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = uri, sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( imageFormat = imageFormat, quality = params.quality, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value++ updateProgress( done = done, total = left ) } } } is Screen.JxlTools.Type.ImageToJxl -> { _left.value = type.imageUris?.size ?: -1 type.imageUris?.map { it.toString() }?.let { list -> jxlConverter.createJxlAnimation( imageUris = list, params = params, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) }, onFailure = { parseSaveResults( listOf( SaveResult.Error.Exception(it) ) ) }, )?.also { jxlBytes -> val result = fileController.save( saveTarget = JxlSaveTarget("", jxlBytes), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) parseSaveResults(listOf(result)) } } } null -> Unit } _isSaving.value = false } } private fun JpegSaveTarget( uri: String, jpegBytes: ByteArray ): SaveTarget = FileSaveTarget( originalUri = uri, filename = filename( uri = uri, format = ImageFormat.Jpg ), data = jpegBytes, imageFormat = ImageFormat.Jpg ) private fun JxlSaveTarget( uri: String, jxlBytes: ByteArray ): SaveTarget = FileSaveTarget( originalUri = uri, filename = filename( uri = uri, format = ImageFormat.Jxl.Lossless ), data = jxlBytes, imageFormat = ImageFormat.Jxl.Lossless ) private fun filename( uri: String, format: ImageFormat ): String = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = ImageInfo( imageFormat = format, originalUri = uri ), originalUri = uri, sequenceNumber = done + 1, metadata = null, data = ByteArray(0) ), forceNotAddSizeInFilename = true ) fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun performSharing() { savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.JxlTools.Type.JpegToJxl -> { val results = mutableListOf() val jpegUris = type.jpegImageUris?.map { it.toString() } ?: emptyList() _left.value = jpegUris.size jxlConverter.jpegToJxl( jpegUris = jpegUris, onFailure = AppToastHost::showFailureToast ) { uri, jxlBytes -> results.add( shareProvider.cacheByteArray( byteArray = jxlBytes, filename = filename(uri, ImageFormat.Jxl.Lossless) ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } shareProvider.shareUris(results.filterNotNull()) AppToastHost.showConfetti() } is Screen.JxlTools.Type.JxlToJpeg -> { val results = mutableListOf() val jxlUris = type.jxlImageUris?.map { it.toString() } ?: emptyList() _left.value = jxlUris.size jxlConverter.jxlToJpeg( jxlUris = jxlUris, onFailure = AppToastHost::showFailureToast ) { uri, jpegBytes -> results.add( shareProvider.cacheByteArray( byteArray = jpegBytes, filename = filename(uri, ImageFormat.Jpg) ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } shareProvider.shareUris(results.filterNotNull()) AppToastHost.showConfetti() } is Screen.JxlTools.Type.JxlToImage -> { _left.value = -1 val positions = imageFrames.getFramePositions(convertedImageUris.size).map { it - 1 } val uris = convertedImageUris.filterIndexed { index, _ -> index in positions } shareProvider.shareUris(uris) AppToastHost.showConfetti() } is Screen.JxlTools.Type.ImageToJxl -> { _left.value = type.imageUris?.size ?: -1 type.imageUris?.map { it.toString() }?.let { list -> jxlConverter.createJxlAnimation( imageUris = list, params = params, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) }, onFailure = { _isSaving.value = false savingJob?.cancel() AppToastHost.showFailureToast(it) } )?.also { byteArray -> shareProvider.shareByteArray( byteArray = byteArray, filename = "JXL_${timestamp()}.jxl", onComplete = AppToastHost::showConfetti ) } } } null -> Unit } _isSaving.value = false } } fun clearAll() { collectionJob?.cancel() collectionJob = null _type.update { null } _convertedImageUris.update { emptyList() } savingJob?.cancel() savingJob = null updateParams(AnimatedJxlParams.Default) registerChangesCleared() } fun removeUri(uri: Uri) { setType( when (val type = _type.value) { is Screen.JxlTools.Type.JpegToJxl -> type.copy( jpegImageUris = type.jpegImageUris?.minus(uri) ) is Screen.JxlTools.Type.JxlToJpeg -> type.copy( jxlImageUris = type.jxlImageUris?.minus(uri) ) is Screen.JxlTools.Type.ImageToJxl -> type.copy( imageUris = type.imageUris?.minus(uri) ) is Screen.JxlTools.Type.JxlToImage -> type null -> null } ) } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } registerChanges() } fun updateJxlFrames(imageFrames: ImageFrames) { _imageFrames.update { imageFrames } registerChanges() } fun updateParams(params: AnimatedJxlParams) { _params.update { params } registerChanges() } fun clearConvertedImagesSelection() = updateJxlFrames(ImageFrames.ManualSelection(emptyList())) fun selectAllConvertedImages() = updateJxlFrames(ImageFrames.All) val canSave: Boolean get() = (imageFrames == ImageFrames.All) .or(type !is Screen.JxlTools.Type.JxlToImage) .or((imageFrames as? ImageFrames.ManualSelection)?.framePositions?.isNotEmpty() == true) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialType: Screen.JxlTools.Type?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): JxlToolsComponent } } ================================================ FILE: feature/libraries-info/.gitignore ================================================ /build ================================================ FILE: feature/libraries-info/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.libraries_info" dependencies { implementation(libs.aboutlibraries.m3) } ================================================ FILE: feature/libraries-info/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/LibrariesInfoContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.libraries_info.presentation import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.plus import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults import com.mikepenz.aboutlibraries.ui.compose.m3.chipColors import com.mikepenz.aboutlibraries.ui.compose.m3.libraryColors import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent import com.mikepenz.aboutlibraries.util.withContext import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.libraries_info.presentation.components.LibrariesContainer import com.t8rin.imagetoolbox.feature.libraries_info.presentation.components.link import com.t8rin.imagetoolbox.feature.libraries_info.presentation.screenLogic.LibrariesInfoComponent import kotlinx.collections.immutable.toPersistentList @Composable fun LibrariesInfoContent( component: LibrariesInfoComponent ) { val context = LocalContext.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( modifier = Modifier .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { EnhancedTopAppBar( title = { Text( text = stringResource(id = R.string.open_source_licenses), modifier = Modifier.marquee() ) }, navigationIcon = { EnhancedIconButton( onClick = component.onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = null ) } }, actions = { TopAppBarEmoji() }, type = EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior ) } ) { contentPadding -> val linkHandler = LocalUriHandler.current val libraries = remember(context) { Libs.Builder() .withContext(context) .build().let { libs -> libs.copy( libraries = libs.libraries.distinctBy { it.name }.filter { it.licenses.isNotEmpty() }.sortedWith( compareBy( { !it.name.contains("T8RIN", true) }, { it.name } ), ).toPersistentList() ) } } LibrariesContainer( libraries = libraries, modifier = Modifier.fillMaxSize(), contentPadding = contentPadding + PaddingValues(12.dp), dimensions = LibraryDefaults.libraryDimensions( itemSpacing = 4.dp ), colors = LibraryDefaults.libraryColors( versionChipColors = LibraryDefaults.chipColors( containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) ), onLibraryClick = { library -> val license = library.licenses.firstOrNull() val url = library.link() if (!license?.htmlReadyLicenseContent.isNullOrBlank()) { component.selectLibrary(library) } else if (!url.isNullOrBlank()) { linkHandler.openUri(url) } } ) } } ================================================ FILE: feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibrariesContainer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.libraries_info.presentation.components import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRowScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.Typography import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Funding import com.mikepenz.aboutlibraries.entity.Library import com.mikepenz.aboutlibraries.entity.License import com.mikepenz.aboutlibraries.ui.compose.LibraryColors import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults import com.mikepenz.aboutlibraries.ui.compose.LibraryDimensions import com.mikepenz.aboutlibraries.ui.compose.LibraryPadding import com.mikepenz.aboutlibraries.ui.compose.LibraryShapes import com.mikepenz.aboutlibraries.ui.compose.LibraryTextStyles import com.mikepenz.aboutlibraries.ui.compose.m3.component.LibraryChip import com.mikepenz.aboutlibraries.ui.compose.m3.libraryColors import com.mikepenz.aboutlibraries.ui.compose.util.author import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @Composable internal fun LibrariesContainer( libraries: Libs?, modifier: Modifier = Modifier, libraryModifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(0.dp), showAuthor: Boolean = true, showDescription: Boolean = true, showVersion: Boolean = true, showLicenseBadges: Boolean = true, showFundingBadges: Boolean = true, typography: Typography = MaterialTheme.typography, colors: LibraryColors = LibraryDefaults.libraryColors(), padding: LibraryPadding = LibraryDefaults.libraryPadding(), dimensions: LibraryDimensions = LibraryDefaults.libraryDimensions(), textStyles: LibraryTextStyles = LibraryDefaults.libraryTextStyles(), shapes: LibraryShapes = LibraryDefaults.libraryShapes(), onLibraryClick: ((Library) -> Unit)? = null, onFundingClick: ((Funding) -> Unit)? = null, name: @Composable BoxScope.(name: String) -> Unit = { DefaultLibraryName( it, textStyles, colors, typography ) }, version: (@Composable BoxScope.(version: String) -> Unit)? = { version -> if (showVersion) DefaultLibraryVersion( version, textStyles, colors, typography, padding, dimensions, shapes ) }, author: (@Composable BoxScope.(authors: String) -> Unit)? = { author -> if (showAuthor && author.isNotBlank()) DefaultLibraryAuthor( author, textStyles, colors, typography ) }, description: (@Composable BoxScope.(description: String) -> Unit)? = { description -> if (showDescription) DefaultLibraryDescription(description, textStyles, colors, typography) }, license: (@Composable FlowRowScope.(license: License) -> Unit)? = { license -> if (showLicenseBadges) DefaultLibraryLicense( license, textStyles, colors, padding, dimensions, shapes ) }, funding: (@Composable FlowRowScope.(funding: Funding) -> Unit)? = { funding -> if (showFundingBadges) DefaultLibraryFunding( funding, textStyles, colors, padding, dimensions, shapes, onFundingClick ) }, actions: (@Composable FlowRowScope.(library: Library) -> Unit)? = null, header: (LazyListScope.() -> Unit)? = null, divider: (@Composable LazyItemScope.() -> Unit)? = null, footer: (LazyListScope.() -> Unit)? = null, ) { val libs = remember(libraries) { libraries?.libraries?.toPersistentList() ?: persistentListOf() } LibrariesScaffold( libraries = libs, modifier = modifier, libraryModifier = libraryModifier.background(colors.libraryBackgroundColor), lazyListState = lazyListState, contentPadding = contentPadding, padding = padding, dimensions = dimensions, name = name, version = version, author = author, description = description, license = license, funding = funding, actions = actions, header = header, divider = divider, footer = footer, onLibraryClick = { library -> if (onLibraryClick != null) { onLibraryClick(library) true } else { false } }, ) } private val DefaultLibraryName: @Composable BoxScope.(name: String, textStyles: LibraryTextStyles, colors: LibraryColors, typography: Typography) -> Unit = { libraryName, textStyles, colors, typography -> Text( text = libraryName, style = textStyles.nameTextStyle ?: typography.titleLarge, color = colors.libraryContentColor, maxLines = textStyles.nameMaxLines, overflow = textStyles.nameOverflow, ) } private val DefaultLibraryVersion: @Composable BoxScope.(version: String, textStyles: LibraryTextStyles, colors: LibraryColors, typography: Typography, padding: LibraryPadding, dimensions: LibraryDimensions, shapes: LibraryShapes) -> Unit = { version, textStyles, colors, typography, padding, dimensions, shapes -> LibraryChip( modifier = Modifier.padding(padding.versionPadding.containerPadding), minHeight = dimensions.chipMinHeight, containerColor = colors.versionChipColors.containerColor, contentColor = colors.versionChipColors.contentColor, shape = shapes.chipShape, ) { Text( modifier = Modifier.padding(padding.versionPadding.contentPadding), text = version, style = textStyles.versionTextStyle ?: typography.bodyMedium, maxLines = textStyles.versionMaxLines, textAlign = TextAlign.Center, overflow = textStyles.defaultOverflow, ) } } private val DefaultLibraryAuthor: @Composable BoxScope.(author: String, textStyles: LibraryTextStyles, colors: LibraryColors, typography: Typography) -> Unit = { author, textStyles, colors, typography -> Text( text = author, style = textStyles.authorTextStyle ?: typography.bodyMedium, color = colors.libraryContentColor, maxLines = textStyles.authorMaxLines, overflow = textStyles.defaultOverflow, textAlign = TextAlign.End, modifier = Modifier .fillMaxWidth() .marquee() ) } private val DefaultLibraryDescription: @Composable BoxScope.(description: String, textStyles: LibraryTextStyles, colors: LibraryColors, typography: Typography) -> Unit = { description, textStyles, colors, typography -> Text( text = description, style = textStyles.descriptionTextStyle ?: typography.bodySmall, color = colors.libraryContentColor, maxLines = textStyles.descriptionMaxLines, overflow = textStyles.defaultOverflow, ) } private val DefaultLibraryLicense: @Composable FlowRowScope.(license: License, textStyles: LibraryTextStyles, colors: LibraryColors, padding: LibraryPadding, dimensions: LibraryDimensions, shapes: LibraryShapes) -> Unit = { license, textStyles, colors, padding, dimensions, shapes -> LibraryChip( modifier = Modifier.padding(padding.licensePadding.containerPadding), minHeight = dimensions.chipMinHeight, containerColor = colors.licenseChipColors.containerColor, contentColor = colors.licenseChipColors.contentColor, shape = shapes.chipShape, ) { Text( modifier = Modifier.padding(padding.licensePadding.contentPadding), maxLines = 1, text = license.name, style = textStyles.licensesTextStyle ?: LocalTextStyle.current, textAlign = TextAlign.Center, overflow = textStyles.defaultOverflow, ) } } private val DefaultLibraryFunding: @Composable FlowRowScope.(funding: Funding, textStyles: LibraryTextStyles, colors: LibraryColors, padding: LibraryPadding, dimensions: LibraryDimensions, shapes: LibraryShapes, onFundingClick: ((Funding) -> Unit)?) -> Unit = { funding, textStyles, colors, padding, dimensions, shapes, onFundingClick -> val uriHandler = LocalUriHandler.current LibraryChip( modifier = Modifier .padding(padding.fundingPadding.containerPadding) .pointerHoverIcon(PointerIcon.Hand), onClick = { if (onFundingClick != null) { onFundingClick(funding) } else { try { uriHandler.openUri(funding.url) } catch (t: Throwable) { println("Failed to open funding url: ${funding.url} // ${t.message}") } } }, minHeight = dimensions.chipMinHeight, containerColor = colors.fundingChipColors.containerColor, contentColor = colors.fundingChipColors.contentColor, shape = shapes.chipShape, ) { Text( modifier = Modifier.padding(padding.fundingPadding.contentPadding), maxLines = 1, text = funding.platform, style = textStyles.fundingTextStyle ?: LocalTextStyle.current, textAlign = TextAlign.Center, overflow = textStyles.defaultOverflow, ) } } @Composable private fun LibrariesScaffold( libraries: ImmutableList, modifier: Modifier = Modifier, libraryModifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(0.dp), padding: LibraryPadding = LibraryDefaults.libraryPadding(), dimensions: LibraryDimensions = LibraryDefaults.libraryDimensions(), name: @Composable BoxScope.(name: String) -> Unit = {}, version: (@Composable BoxScope.(version: String) -> Unit)? = null, author: (@Composable BoxScope.(authors: String) -> Unit)? = null, description: (@Composable BoxScope.(description: String) -> Unit)? = null, license: (@Composable FlowRowScope.(license: License) -> Unit)? = null, funding: (@Composable FlowRowScope.(funding: Funding) -> Unit)? = null, actions: (@Composable FlowRowScope.(library: Library) -> Unit)? = null, header: (LazyListScope.() -> Unit)? = null, divider: (@Composable LazyItemScope.() -> Unit)? = null, footer: (LazyListScope.() -> Unit)? = null, onLibraryClick: ((Library) -> Boolean)? = { false }, ) { val uriHandler = LocalUriHandler.current LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(dimensions.itemSpacing), state = lazyListState, contentPadding = contentPadding, flingBehavior = enhancedFlingBehavior() ) { header?.invoke(this) itemsIndexed(libraries) { index, library -> val interactionSource = remember { MutableInteractionSource() } LibraryScaffoldLayout( modifier = libraryModifier .container( shape = shapeByInteraction( shape = ShapeDefaults.byIndex( index = index, size = libraries.size, ), pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ), resultPadding = 0.dp ) .hapticsClickable( indication = LocalIndication.current, interactionSource = interactionSource ) { val license = library.licenses.firstOrNull() val handled = onLibraryClick?.invoke(library) ?: false if (!handled && !license?.url.isNullOrBlank()) { license.url?.also { try { uriHandler.openUri(it) } catch (t: Throwable) { println("Failed to open url: $it // ${t.message}") } } } }, libraryPadding = padding, name = { name(library.name) }, version = { val artifactVersion = library.artifactVersion if (version != null && artifactVersion != null) { version(artifactVersion) } }, author = { val authors = library.author if (author != null && authors.isNotBlank()) { author(authors) } }, description = { val desc = library.description if (description != null && !desc.isNullOrBlank()) { description(desc) } }, licenses = { if (license != null && library.licenses.isNotEmpty()) { library.licenses.forEach { license(it) } } }, actions = { if (funding != null && library.funding.isNotEmpty()) { library.funding.forEach { funding(it) } } if (actions != null) { actions(library) } } ) if (divider != null && index < libraries.lastIndex) { divider.invoke(this) } } footer?.invoke(this) } } @OptIn(ExperimentalLayoutApi::class) @Composable private fun LibraryScaffoldLayout( name: @Composable BoxScope.() -> Unit, version: @Composable BoxScope.() -> Unit, author: @Composable BoxScope.() -> Unit, description: @Composable BoxScope.() -> Unit, licenses: @Composable FlowRowScope.() -> Unit, actions: @Composable FlowRowScope.() -> Unit, modifier: Modifier = Modifier, libraryPadding: LibraryPadding = LibraryDefaults.libraryPadding(), ) { Column( modifier = modifier.padding(libraryPadding.contentPadding) ) { Row( verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .padding(libraryPadding.namePadding) .weight(1f), content = name ) Box(content = version) } Spacer(Modifier.height(4.dp)) Box(content = description) Spacer(Modifier.height(4.dp)) Row( verticalAlignment = Alignment.Bottom ) { FlowRow( modifier = Modifier .weight(1f) .padding(end = 8.dp) ) { licenses() actions() } Box( modifier = Modifier.weight(1.2f), content = author ) } } } ================================================ FILE: feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/components/LibraryLink.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.libraries_info.presentation.components import com.mikepenz.aboutlibraries.entity.Library fun Library.link(): String? = (scm?.url ?: website ?: licenses.firstOrNull()?.url)?.replace("git://", "") ================================================ FILE: feature/libraries-info/src/main/java/com/t8rin/imagetoolbox/feature/libraries_info/presentation/screenLogic/LibrariesInfoComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.libraries_info.presentation.screenLogic import com.arkivanov.decompose.ComponentContext import com.mikepenz.aboutlibraries.entity.Library import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.feature.libraries_info.presentation.components.link import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class LibrariesInfoComponent @AssistedInject constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, @Assisted private val onNavigate: (Screen) -> Unit, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { fun selectLibrary(library: Library) { onNavigate( Screen.LibraryDetails( name = library.name, htmlDescription = library.licenses.joinToString("\n\n") { it.htmlReadyLicenseContent .orEmpty() .trimIndent() }, link = library.link() ) ) } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit ): LibrariesInfoComponent } } ================================================ FILE: feature/library-details/.gitignore ================================================ /build ================================================ FILE: feature/library-details/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.library_details" ================================================ FILE: feature/library-details/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/LibraryDetailsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.library_details.presentation import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.OpenInNew import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.HtmlText import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.library_details.presentation.screenLogic.LibraryDetailsComponent @Composable fun LibraryDetailsContent( component: LibraryDetailsComponent ) { val childScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val linkHandler = LocalUriHandler.current Scaffold( modifier = Modifier .fillMaxSize() .nestedScroll(childScrollBehavior.nestedScrollConnection), topBar = { EnhancedTopAppBar( title = { Text( text = component.libraryName, modifier = Modifier.marquee() ) }, navigationIcon = { EnhancedIconButton( onClick = component.onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = null ) } }, actions = { if (component.libraryLink.isNullOrBlank()) { TopAppBarEmoji() } else { EnhancedIconButton( onClick = { linkHandler.openUri(component.libraryLink) } ) { Icon( imageVector = Icons.AutoMirrored.Rounded.OpenInNew, contentDescription = component.libraryLink ) } } }, type = EnhancedTopAppBarType.Large, scrollBehavior = childScrollBehavior ) } ) { contentPadding -> SelectionContainer { HtmlText( modifier = Modifier .fillMaxWidth() .enhancedVerticalScroll(rememberScrollState()) .padding(contentPadding) .padding(12.dp) .container( resultPadding = 12.dp ), html = component.libraryDescription ) } } } ================================================ FILE: feature/library-details/src/main/java/com/t8rin/imagetoolbox/library_details/presentation/screenLogic/LibraryDetailsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.library_details.presentation.screenLogic import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class LibraryDetailsComponent @AssistedInject constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, @Assisted("libraryName") val libraryName: String, @Assisted("libraryDescription") val libraryDescription: String, @Assisted("libraryLink") val libraryLink: String?, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { @AssistedFactory fun interface Factory { operator fun invoke( @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted("libraryName") libraryName: String, @Assisted("libraryDescription") libraryDescription: String, @Assisted("libraryLink") libraryLink: String?, ): LibraryDetailsComponent } } ================================================ FILE: feature/limits-resize/.gitignore ================================================ /build ================================================ FILE: feature/limits-resize/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.limits_resize" ================================================ FILE: feature/limits-resize/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/data/AndroidLimitsImageScaler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.data import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.utils.aspectRatio import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.feature.limits_resize.domain.LimitsImageScaler import com.t8rin.imagetoolbox.feature.limits_resize.domain.LimitsResizeType import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidLimitsImageScaler @Inject constructor( private val imageScaler: ImageScaler, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, LimitsImageScaler, ImageScaler by imageScaler { override suspend fun scaleImage( image: Bitmap, width: Int, height: Int, resizeType: LimitsResizeType, imageScaleMode: ImageScaleMode ): Bitmap? = withContext(defaultDispatcher) { val widthInternal = width.takeIf { it > 0 } ?: Int.MAX_VALUE val heightInternal = height.takeIf { it > 0 } ?: Int.MAX_VALUE resizeType.resizeWithLimits( image = image, width = widthInternal, height = heightInternal, imageScaleMode = imageScaleMode ) } private suspend fun LimitsResizeType.resizeWithLimits( image: Bitmap, width: Int, height: Int, imageScaleMode: ImageScaleMode ): Bitmap? { val limitWidth: Int val limitHeight: Int if (autoRotateLimitBox && image.aspectRatio < 1f) { limitWidth = height limitHeight = width } else { limitWidth = width limitHeight = height } val limitAspectRatio = limitWidth / limitHeight.toFloat() if (image.height > limitHeight || image.width > limitWidth) { return when { image.aspectRatio > limitAspectRatio -> { scaleImage( image = image, width = limitWidth, height = (limitWidth / image.aspectRatio).toInt(), imageScaleMode = imageScaleMode ) } image.aspectRatio < limitAspectRatio -> { scaleImage( image = image, width = (limitHeight * image.aspectRatio).toInt(), height = limitHeight, imageScaleMode = imageScaleMode ) } else -> { scaleImage( image = image, width = limitWidth, height = limitHeight, imageScaleMode = imageScaleMode ) } } } else { return when (this) { is LimitsResizeType.Recode -> image is LimitsResizeType.Zoom -> { when { limitHeight == Int.MAX_VALUE -> { val newHeight = (limitWidth / image.aspectRatio).toInt() scaleImage( image = image, width = limitWidth, height = newHeight, imageScaleMode = imageScaleMode ) } limitWidth == Int.MAX_VALUE -> { val newWidth = (limitHeight * image.aspectRatio).toInt() scaleImage( image = image, width = newWidth, height = limitHeight, imageScaleMode = imageScaleMode ) } else -> { val widthRatio = limitWidth.toDouble() / image.width val heightRatio = limitHeight.toDouble() / image.height val ratio = minOf(widthRatio, heightRatio) val newWidth = (image.width * ratio).toInt() val newHeight = (image.height * ratio).toInt() scaleImage( image = image, width = newWidth, height = newHeight, imageScaleMode = imageScaleMode ) } } } is LimitsResizeType.Skip -> null } } } } ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/di/LimitsResizeModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.limits_resize.data.AndroidLimitsImageScaler import com.t8rin.imagetoolbox.feature.limits_resize.domain.LimitsImageScaler import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface LimitsResizeModule { @Binds @Singleton fun provideScaler( scaler: AndroidLimitsImageScaler ): LimitsImageScaler } ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsImageScaler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.domain import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode interface LimitsImageScaler : ImageScaler { suspend fun scaleImage( image: I, width: Int, height: Int, resizeType: LimitsResizeType, imageScaleMode: ImageScaleMode ): I? } ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/domain/LimitsResizeType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.domain sealed class LimitsResizeType( val autoRotateLimitBox: Boolean ) { fun copy(autoRotateLimitBox: Boolean) = when (this) { is Recode -> Recode(autoRotateLimitBox) is Skip -> Skip(autoRotateLimitBox) is Zoom -> Zoom(autoRotateLimitBox) } class Skip( autoRotateLimitBox: Boolean = false ) : LimitsResizeType(autoRotateLimitBox) class Recode( autoRotateLimitBox: Boolean = false ) : LimitsResizeType(autoRotateLimitBox) class Zoom( autoRotateLimitBox: Boolean = false ) : LimitsResizeType(autoRotateLimitBox) } ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/LimitsResizeContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.presentation import android.net.Uri import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ScaleModeSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.limits_resize.presentation.components.AutoRotateLimitBoxToggle import com.t8rin.imagetoolbox.feature.limits_resize.presentation.components.LimitsResizeSelector import com.t8rin.imagetoolbox.feature.limits_resize.presentation.screenLogic.LimitsResizeComponent @Composable fun LimitsResizeContent( component: LimitsResizeComponent ) { AutoContentBasedColors(component.bitmap) val imagePicker = rememberImagePicker { uris: List -> component.setUris(uris) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.limits_resize), input = component.bitmap, isLoading = component.isImageLoading, size = rememberFileSize(component.selectedUri) ) }, onGoBack = onBack, actions = { if (component.previewBitmap != null) { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.canSave, onShare = component::shareBitmaps, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } ZoomButton( onClick = { showZoomSheet = true }, visible = component.bitmap != null, ) }, imagePreview = { ImageContainer( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), imageInside = isPortrait, showOriginal = false, previewBitmap = component.previewBitmap, originalBitmap = component.bitmap, isLoading = component.isImageLoading, shouldShowPreview = true ) }, controls = { ImageCounter( imageCount = component.uris?.size?.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) ResizeImageField( imageInfo = component.imageInfo, originalSize = component.originalSize, onWidthChange = component::updateWidth, onHeightChange = component::updateHeight ) Spacer(Modifier.size(8.dp)) SaveExifWidget( imageFormat = component.imageInfo.imageFormat, checked = component.keepExif, onCheckedChange = component::setKeepExif ) if (component.imageInfo.imageFormat.canChangeCompressionValue) Spacer( Modifier.size(8.dp) ) QualitySelector( imageFormat = component.imageInfo.imageFormat, quality = component.imageInfo.quality, onQualityChange = component::setQuality ) Spacer(Modifier.size(8.dp)) ImageFormatSelector( value = component.imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = component.imageInfo.quality ) Spacer(Modifier.size(8.dp)) AutoRotateLimitBoxToggle( value = component.resizeType.autoRotateLimitBox, onClick = component::toggleAutoRotateLimitBox ) Spacer(Modifier.size(8.dp)) LimitsResizeSelector( enabled = component.bitmap != null, value = component.resizeType, onValueChange = component::setResizeType ) Spacer(Modifier.height(8.dp)) ScaleModeSelector( value = component.imageInfo.imageScaleMode, onValueChange = component::setImageScaleMode ) }, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), isPrimaryButtonVisible = component.canSave, onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, topAppBarPersistentActions = { if (component.bitmap == null) { TopAppBarEmoji() } }, canShowScreenData = component.bitmap != null ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris?.size ?: 1, onCancelLoading = component::cancelSaving ) PickImageFromUrisSheet( visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, uris = component.uris, selectedUri = component.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = component::updateUrisSilently, columns = if (isPortrait) 2 else 4, ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/AutoRotateLimitBoxToggle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.MotionPhotosAuto import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AutoRotateLimitBoxToggle( value: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { PreferenceRowSwitch( modifier = modifier, title = stringResource(R.string.auto_rotate_limits), subtitle = stringResource(R.string.auto_rotate_limits_sub), checked = value, shape = ShapeDefaults.extraLarge, onClick = { onClick() }, startIcon = Icons.Rounded.MotionPhotosAuto ) } ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/components/LimitResizeGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.limits_resize.domain.LimitsResizeType @Composable fun LimitsResizeSelector( enabled: Boolean, value: LimitsResizeType, onValueChange: (LimitsResizeType) -> Unit ) { EnhancedButtonGroup( modifier = Modifier .container(shape = ShapeDefaults.extraLarge) .padding(start = 3.dp, end = 2.dp), enabled = enabled, title = { Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( text = stringResource(R.string.fallback_option), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) } Spacer(modifier = Modifier.height(8.dp)) }, items = listOf( stringResource(R.string.skip), stringResource(R.string.recode), stringResource(R.string.zoom) ), selectedIndex = when (value) { is LimitsResizeType.Skip -> 0 is LimitsResizeType.Recode -> 1 is LimitsResizeType.Zoom -> 2 }, onIndexChange = { onValueChange( when (it) { 0 -> LimitsResizeType.Skip(value.autoRotateLimitBox) 1 -> LimitsResizeType.Recode(value.autoRotateLimitBox) else -> LimitsResizeType.Zoom(value.autoRotateLimitBox) } ) } ) } ================================================ FILE: feature/limits-resize/src/main/java/com/t8rin/imagetoolbox/feature/limits_resize/presentation/screenLogic/LimitsResizeComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.limits_resize.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.limits_resize.domain.LimitsImageScaler import com.t8rin.imagetoolbox.feature.limits_resize.domain.LimitsResizeType import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class LimitsResizeComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val imageScaler: LimitsImageScaler, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) } } private val _originalSize: MutableState = mutableStateOf(null) val originalSize by _originalSize private val _canSave: MutableState = mutableStateOf(false) val canSave by _canSave private val _uris: MutableState?> = mutableStateOf(null) val uris by _uris private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _keepExif: MutableState = mutableStateOf(false) val keepExif by _keepExif private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _selectedUri: MutableState = mutableStateOf(null) val selectedUri by _selectedUri private val _imageInfo: MutableState = mutableStateOf(ImageInfo()) val imageInfo by _imageInfo private val _resizeType: MutableState = mutableStateOf(LimitsResizeType.Recode()) val resizeType by _resizeType fun setImageFormat(imageFormat: ImageFormat) { _imageInfo.value = _imageInfo.value.copy(imageFormat = imageFormat) } fun setUris( uris: List? ) { _uris.value = null _uris.value = uris _selectedUri.value = uris?.firstOrNull() if (uris != null) { componentScope.launch { imageGetter.getImageAsync( uri = uris[0].toString(), originalSize = true, onGetImage = { updateBitmap(it.image) setImageFormat(it.imageInfo.imageFormat) }, onFailure = AppToastHost::showFailureToast ) } } } fun updateUrisSilently(removedUri: Uri) { componentScope.launch { _uris.value = uris if (_selectedUri.value == removedUri) { val index = uris?.indexOf(removedUri) ?: -1 if (index == 0) { uris?.getOrNull(1)?.let { _selectedUri.value = it _bitmap.value = imageGetter.getImage(it.toString())?.image } } else { uris?.getOrNull(index - 1)?.let { _selectedUri.value = it _bitmap.value = imageGetter.getImage(it.toString())?.image } } } val u = _uris.value?.toMutableList()?.apply { remove(removedUri) } _uris.value = u } } private fun updateBitmap( bitmap: Bitmap?, preview: Bitmap? = null ) { componentScope.launch { _isImageLoading.value = true val size = bitmap?.let { it.width to it.height } _originalSize.value = size?.run { IntegerSize(width = first, height = second) } _bitmap.value = imageScaler.scaleUntilCanShow(bitmap) _previewBitmap.value = preview ?: _bitmap.value _isImageLoading.value = false } } fun setKeepExif(boolean: Boolean) { _keepExif.value = boolean registerChanges() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 uris?.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString())?.image }.getOrNull()?.let { bitmap -> imageScaler.scaleImage( image = bitmap, width = imageInfo.width, height = imageInfo.height, resizeType = resizeType, imageScaleMode = imageInfo.imageScaleMode ) }?.let { localBitmap -> results.add( fileController.save( ImageSaveTarget( imageInfo = imageInfo.copy( width = localBitmap.width, height = localBitmap.height ), originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = imageInfo.copy( width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun updateSelectedUri( uri: Uri ) { runCatching { componentScope.launch { _isImageLoading.value = true updateBitmap(imageGetter.getImage(uri.toString())?.image) _selectedUri.value = uri _isImageLoading.value = false } }.onFailure(AppToastHost::showFailureToast) } private fun updateCanSave() { _canSave.update { _bitmap.value != null && (_imageInfo.value.height != 0 || _imageInfo.value.width != 0) } registerChanges() } fun updateWidth(i: Int) { _imageInfo.value = _imageInfo.value.copy(width = i) updateCanSave() } fun updateHeight(i: Int) { _imageInfo.value = _imageInfo.value.copy(height = i) updateCanSave() } fun shareBitmaps() { _isSaving.value = false savingJob = trackProgress { _isSaving.value = true shareProvider.shareImages( uris = uris?.map { it.toString() } ?: emptyList(), imageLoader = { uri -> imageGetter.getImage(uri)?.image?.let { bitmap: Bitmap -> imageScaler.scaleImage( image = bitmap, width = imageInfo.width, height = imageInfo.height, resizeType = resizeType, imageScaleMode = imageInfo.imageScaleMode ) }?.let { it to imageInfo.copy( width = it.width, height = it.height ) } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _done.value = 0 _isSaving.value = false } else { _done.value = it } updateProgress( done = done, total = uris.orEmpty().size ) } ) } } fun setQuality(quality: Quality) { _imageInfo.value = _imageInfo.value.copy(quality = quality) registerChanges() } fun setResizeType(resizeType: LimitsResizeType) { _resizeType.value = resizeType registerChanges() } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun toggleAutoRotateLimitBox() { _resizeType.update { it.copy(!it.autoRotateLimitBox) } registerChanges() } fun setImageScaleMode(imageScaleMode: ImageScaleMode) { _imageInfo.update { it.copy( imageScaleMode = imageScaleMode ) } registerChanges() } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true imageGetter.getImage( uri = selectedUri.toString() )?.image?.let { bitmap -> imageScaler.scaleImage( image = bitmap, width = imageInfo.width, height = imageInfo.height, resizeType = resizeType, imageScaleMode = imageInfo.imageScaleMode ) }?.let { it to imageInfo.copy( width = it.width, height = it.height ) }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = selectedUri.toString()) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 val list = mutableListOf() uris?.forEach { uri -> imageGetter.getImage( uri = uri.toString() )?.image?.let { bitmap -> imageScaler.scaleImage( image = bitmap, width = imageInfo.width, height = imageInfo.height, resizeType = resizeType, imageScaleMode = imageInfo.imageScaleMode ) }?.let { it to imageInfo.copy( width = it.width, height = it.height ) }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = uri.toString()) )?.let { uri -> list.add(uri.toUri()) } } _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } onComplete(list) _isSaving.value = false } } fun selectLeftUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.rightFrom(it) } ?.let(::updateSelectedUri) } fun getFormatForFilenameSelection(): ImageFormat? = if (uris?.size == 1) imageInfo.imageFormat else null @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): LimitsResizeComponent } } ================================================ FILE: feature/load-net-image/.gitignore ================================================ /build ================================================ FILE: feature/load-net-image/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.load_net_image" dependencies { implementation(libs.jsoup) } ================================================ FILE: feature/load-net-image/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/data/AndroidHtmlImageParser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.data import android.content.Context import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.USER_AGENT import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.feature.load_net_image.domain.HtmlImageParser import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import org.jsoup.Jsoup import java.net.UnknownHostException import javax.inject.Inject internal class AndroidHtmlImageParser @Inject constructor( @ApplicationContext private val context: Context, private val shareProvider: ImageShareProvider, private val imageGetter: ImageGetter, dispatchersHolder: DispatchersHolder ) : HtmlImageParser, DispatchersHolder by dispatchersHolder { override suspend fun parseImagesSrc( url: String, onFailure: (message: String) -> Unit ): List = withContext(defaultDispatcher) { val trimmedUrl = url.trim() val realUrl = if (trimmedUrl.isMalformed()) { "https://$trimmedUrl" } else trimmedUrl val baseImage = loadImage(realUrl) val parsedImages = if (realUrl.isNotEmpty()) { runSuspendCatching { val parsed = Jsoup .connect(realUrl) .userAgent(USER_AGENT) .execute() .parse() val list = parsed.getElementsByTag("img") .mapNotNull { element -> element.absUrl("src").takeIf { it.isNotEmpty() }?.substringBefore("?") } val content = parsed.getElementsByTag("meta") .mapNotNull { element -> when (element.attr("property")) { "og:image" -> element.attr("content") else -> null } } val favIcon = loadImage( parsed.head() .select("link[href~=.*\\.ico]") .firstOrNull() ?.attr("href") ?: "" ).ifEmpty { loadImage(realUrl.removeSuffix("/") + "/favicon.ico") } content + list + favIcon }.onFailure { if (it is UnknownHostException) onFailure(context.getString(R.string.unknown_host)) }.getOrNull() ?: emptyList() } else { emptyList() } baseImage + parsedImages } private suspend fun loadImage( url: String ): List = imageGetter.getImage(data = url)?.let { shareProvider.cacheImage( image = it, imageInfo = ImageInfo( width = it.width, height = it.height, imageFormat = ImageFormat.Png.Lossless ) ) }.let(::listOfNotNull) private fun String.isMalformed(): Boolean = !(startsWith("https://") || startsWith("http://")) } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/di/LoadNetImageModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.di import com.t8rin.imagetoolbox.feature.load_net_image.data.AndroidHtmlImageParser import com.t8rin.imagetoolbox.feature.load_net_image.domain.HtmlImageParser import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface LoadNetImageModule { @Binds @Singleton fun parser( impl: AndroidHtmlImageParser ): HtmlImageParser } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/domain/HtmlImageParser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.domain interface HtmlImageParser { suspend fun parseImagesSrc( url: String, onFailure: (message: String) -> Unit ): List } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/LoadNetImageContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.takeUnless import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.load_net_image.presentation.components.LoadNetImageActionButtons import com.t8rin.imagetoolbox.feature.load_net_image.presentation.components.LoadNetImageAdaptiveActions import com.t8rin.imagetoolbox.feature.load_net_image.presentation.components.LoadNetImageTopAppBarActions import com.t8rin.imagetoolbox.feature.load_net_image.presentation.components.LoadNetImageUrlTextField import com.t8rin.imagetoolbox.feature.load_net_image.presentation.components.ParsedImagePreview import com.t8rin.imagetoolbox.feature.load_net_image.presentation.components.ParsedImagesSelection import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent @Composable fun LoadNetImageContent( component: LoadNetImageComponent ) { val isPortrait by isPortraitOrientationAsState() AutoContentBasedColors(component.bitmap) AdaptiveLayoutScreen( shouldDisableBackHandler = true, title = { TopAppBarTitle( title = stringResource(R.string.load_image_from_net), input = component.bitmap, isLoading = component.isImageLoading, size = null ) }, onGoBack = component.onGoBack, actions = { LoadNetImageAdaptiveActions(component) }, topAppBarPersistentActions = { LoadNetImageTopAppBarActions(component) }, imagePreview = { AnimatedContent(component.targetUrl.isEmpty()) { isEmpty -> if (isEmpty) { ImageNotPickedWidget( onPickImage = { Clipboard.getText(component::updateTargetUrl) }, modifier = Modifier.padding(20.dp), text = stringResource(R.string.type_image_link), containerColor = MaterialTheme .colorScheme .surfaceContainerLowest .takeUnless(isPortrait) ) } else { ParsedImagePreview(component) } } }, controls = { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { LoadNetImageUrlTextField(component) ParsedImagesSelection(component) } }, buttons = { actions -> LoadNetImageActionButtons( component = component, actions = actions ) }, showImagePreviewAsStickyHeader = component.targetUrl.isNotEmpty(), canShowScreenData = true ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageActionButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation.components import android.net.Uri import androidx.compose.foundation.layout.RowScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ContentPaste import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageEdit import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent @Composable internal fun LoadNetImageActionButtons( component: LoadNetImageComponent, actions: @Composable RowScope.() -> Unit ) { val isPortrait by isPortraitOrientationAsState() val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var editSheetData by remember { mutableStateOf(listOf()) } val noData = component.parsedImages.isEmpty() BottomButtonsBlock( isNoData = noData, isPrimaryButtonVisible = !noData, isSecondaryButtonVisible = !noData, secondaryButtonIcon = if (noData) Icons.Rounded.ContentPaste else Icons.Outlined.ImageEdit, secondaryButtonText = if (noData) stringResource(R.string.paste_link) else stringResource(R.string.edit), showNullDataButtonAsContainer = true, isScreenHaveNoDataContent = true, onSecondaryButtonClick = { if (noData) { Clipboard.getText(component::updateTargetUrl) } else { component.cacheImages { editSheetData = it } } }, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageAdaptiveActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.RowScope import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent @Composable internal fun RowScope.LoadNetImageAdaptiveActions( component: LoadNetImageComponent ) { AnimatedVisibility(component.parsedImages.isNotEmpty()) { ShareButton( onShare = component::performSharing, onCopy = { component.cacheCurrentImage(Clipboard::copy) } ) } var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomButton( onClick = { showZoomSheet = true }, visible = component.bitmap != null, ) ZoomModalSheet( data = component.bitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageTopAppBarActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent @Composable internal fun RowScope.LoadNetImageTopAppBarActions( component: LoadNetImageComponent ) { if (component.bitmap == null) { TopAppBarEmoji() } else { AnimatedVisibility(component.parsedImages.size > 1) { Row( verticalAlignment = Alignment.CenterVertically ) { val pagesSize by remember(component.imageFrames, component.parsedImages) { derivedStateOf { component.imageFrames.getFramePositions(component.parsedImages.size).size } } AnimatedVisibility( visible = pagesSize != component.parsedImages.size, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = component::selectAllImages ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = pagesSize != 0 ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { pagesSize.takeIf { it != 0 }?.let { Spacer(Modifier.width(8.dp)) Text( text = it.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) } EnhancedIconButton( onClick = component::clearImagesSelection ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } } } } } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/LoadNetImageUrlTextField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material.icons.rounded.WifiTetheringError import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent @Composable internal fun LoadNetImageUrlTextField( component: LoadNetImageComponent ) { RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = component.targetUrl, onValueChange = { component.updateTargetUrl( newUrl = it, onFailure = { AppToastHost.showToast( message = it, icon = Icons.Rounded.WifiTetheringError ) } ) }, singleLine = false, label = { Text(stringResource(id = R.string.image_link)) }, endIcon = { AnimatedVisibility(component.targetUrl.isNotBlank()) { EnhancedIconButton( onClick = { component.updateTargetUrl("") }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.Cancel, contentDescription = stringResource(R.string.cancel) ) } } } ) } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagePreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.data.utils.safeAspectRatio import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent @Composable internal fun ParsedImagePreview( component: LoadNetImageComponent ) { Picture( allowHardware = false, model = component.targetUrl, modifier = Modifier .container( resultPadding = 8.dp ) .then( if (component.bitmap == null) { Modifier .fillMaxWidth() .height(140.dp) } else { Modifier.aspectRatio(component.bitmap?.safeAspectRatio ?: 2f) } ), isLoadingFromDifferentPlace = component.isImageLoading, contentScale = ContentScale.FillBounds, shape = MaterialTheme.shapes.small, error = { if (component.bitmap != null) { Picture( modifier = Modifier.fillMaxSize(), model = component.bitmap, contentDescription = contentDescription, contentScale = ContentScale.FillBounds ) } else { Column( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.surfaceContainer), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, modifier = Modifier .padding(vertical = 8.dp, horizontal = 16.dp) .size(64.dp) ) Text(stringResource(id = R.string.no_image)) Spacer(Modifier.height(8.dp)) } } } ) } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/components/ParsedImagesSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.image.ImagesPreviewWithSelection import com.t8rin.imagetoolbox.core.ui.widget.modifier.negativePadding import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent @Composable internal fun ParsedImagesSelection( component: LoadNetImageComponent ) { AnimatedVisibility(component.parsedImages.size > 1) { ImagesPreviewWithSelection( imageUris = component.parsedImages, imageFrames = component.imageFrames, onFrameSelectionChange = component::updateImageFrames, isPortrait = true, isLoadingImages = component.isImageLoading, contentScale = ContentScale.Fit, contentPadding = PaddingValues(20.dp), modifier = Modifier .fillMaxWidth() .height( (130.dp * component.parsedImages.size).coerceAtMost(420.dp) ) .negativePadding(horizontal = 20.dp), showExtension = false ) } } ================================================ FILE: feature/load-net-image/src/main/java/com/t8rin/imagetoolbox/feature/load_net_image/presentation/screenLogic/LoadNetImageComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.load_net_image.domain.HtmlImageParser import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class LoadNetImageComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted initialUrl: String, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageGetter: ImageGetter, private val shareProvider: ImageShareProvider, private val imageCompressor: ImageCompressor, private val htmlImageParser: HtmlImageParser, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { updateTargetUrl(initialUrl) } } private val _targetUrl: MutableState = mutableStateOf("") val targetUrl: String by _targetUrl private val _bitmap = mutableStateOf(null) val bitmap by _bitmap private val _parsedImages: MutableState> = mutableStateOf(emptyList()) val parsedImages: List by _parsedImages private val _imageFrames: MutableState = mutableStateOf(ImageFrames.All) val imageFrames by _imageFrames private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left fun updateTargetUrl( newUrl: String, onFailure: (String) -> Unit = {} ) { _targetUrl.update( onValueChanged = { debouncedImageCalculation { val newImages = htmlImageParser.parseImagesSrc( url = newUrl, onFailure = onFailure ) newImages.firstOrNull().let { src -> _bitmap.update { src?.let { imageGetter.getImage(data = src) } } } _parsedImages.update { newImages } } }, transform = { newUrl } ) } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } val results = mutableListOf() val positions = imageFrames.getFramePositions(parsedImages.size) _done.value = 0 _left.value = positions.size parsedImages.forEachIndexed { index, url -> if ((index + 1) in positions) { imageGetter.getImage(data = url)?.let { bitmap -> fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = ImageFormat.Png.Lossless ), originalUri = "_", sequenceNumber = null, data = imageCompressor.compress( image = bitmap, imageFormat = ImageFormat.Png.Lossless, quality = Quality.Base(100) ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) }?.let(results::add) ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value++ } } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.update { false } } } fun performSharing() { cacheImages { uris -> componentScope.launch { shareProvider.shareUris(uris.map { it.toString() }) AppToastHost.showConfetti() } } } fun cacheImages( onComplete: (List) -> Unit ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true val positions = imageFrames.getFramePositions(parsedImages.size).map { it - 1 } _done.value = 0 _left.value = positions.size val uris = parsedImages.filterIndexed { index, _ -> index in positions } onComplete( uris.mapNotNull { val image = imageGetter.getImage(data = it) ?: return@mapNotNull null shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = ImageFormat.Png.Lossless ) )?.toUri() } ) _isSaving.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { _done.value = 0 _left.value = 1 _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true imageFrames.getFramePositions(parsedImages.size).firstOrNull()?.let { imageGetter.getImage(data = parsedImages[it - 1]) }?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = ImageFormat.Png.Lossless ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun getFormatForFilenameSelection(): ImageFormat = ImageFormat.Png.Lossless fun updateImageFrames(imageFrames: ImageFrames) { _imageFrames.update { imageFrames } registerChanges() } fun clearImagesSelection() = updateImageFrames(ImageFrames.ManualSelection(emptyList())) fun selectAllImages() = updateImageFrames(ImageFrames.All) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUrl: String, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): LoadNetImageComponent } } ================================================ FILE: feature/main/.gitignore ================================================ /build ================================================ FILE: feature/main/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.main" dependencies { implementation(projects.feature.settings) } ================================================ FILE: feature/main/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/MainContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.MenuOpen import androidx.compose.material.icons.rounded.FileDownloadOff import androidx.compose.material3.DrawerDefaults import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.rememberDrawerState import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalWindowSizeClass import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.main.presentation.components.MainContentImpl import com.t8rin.imagetoolbox.feature.main.presentation.components.MainDrawerContent import com.t8rin.imagetoolbox.feature.main.presentation.screenLogic.MainComponent import com.t8rin.imagetoolbox.feature.settings.presentation.SettingsContent import com.t8rin.snowfall.snowfall import com.t8rin.snowfall.types.FlakeType import kotlinx.coroutines.delay @Composable fun MainContent( component: MainComponent ) { val isUpdateAvailable by component.isUpdateAvailable.subscribeAsState() val settingsState = LocalSettingsState.current val isGrid = LocalWindowSizeClass.current.widthSizeClass != WindowWidthSizeClass.Compact val sideSheetState = rememberDrawerState(initialValue = DrawerValue.Closed) val isSheetSlideable = (isGrid && !settingsState.showSettingsInLandscape) || !isGrid val layoutDirection = LocalLayoutDirection.current var sheetExpanded by rememberSaveable { mutableStateOf(false) } val drawerContent = remember(isSheetSlideable) { movableContentOf { MainDrawerContent( sideSheetState = sideSheetState, isSheetSlideable = isSheetSlideable, sheetExpanded = sheetExpanded, layoutDirection = layoutDirection, settingsBlockContent = { SettingsContent( component = component.settingsComponent, appBarNavigationIcon = { showSettingsSearch, onCloseSearch -> AnimatedContent( targetState = !isSheetSlideable to showSettingsSearch, transitionSpec = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() } ) { (expanded, searching) -> if (searching) { EnhancedIconButton(onClick = onCloseSearch) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } } else if (expanded) { EnhancedIconButton( onClick = { sheetExpanded = !sheetExpanded } ) { Icon( imageVector = Icons.AutoMirrored.Rounded.MenuOpen, contentDescription = "Expand", modifier = Modifier.rotate( animateFloatAsState(if (!sheetExpanded) 0f else 180f).value ) ) } } } } ) } ) } } var showFeaturesFall by rememberSaveable { mutableStateOf(false) } val content = remember { movableContentOf { MainContentImpl( layoutDirection = layoutDirection, isSheetSlideable = isSheetSlideable, sideSheetState = sideSheetState, sheetExpanded = sheetExpanded, isGrid = isGrid, onShowFeaturesFall = { showFeaturesFall = true }, onGetClipList = component::parseClipList, onTryGetUpdate = { component.tryGetUpdate( isNewRequest = true, onNoUpdates = { AppToastHost.showToast( icon = Icons.Rounded.FileDownloadOff, message = getString(R.string.no_updates) ) } ) }, isUpdateAvailable = isUpdateAvailable, onNavigate = component.onNavigate, onToggleFavorite = component::toggleFavoriteScreen ) } } Box( modifier = Modifier .fillMaxSize() .clipToBounds() ) { if (settingsState.useFullscreenSettings) { content() } else { if (isSheetSlideable) { LocalLayoutDirection.ProvidesValue( if (layoutDirection == LayoutDirection.Ltr) LayoutDirection.Rtl else LayoutDirection.Ltr ) { ModalNavigationDrawer( drawerState = sideSheetState, drawerContent = drawerContent, content = content ) } } else { Row { content.withModifier( modifier = Modifier.weight(1f) ) if (settingsState.borderWidth > 0.dp) { Spacer( modifier = Modifier .fillMaxHeight() .width(settingsState.borderWidth) .background( MaterialTheme.colorScheme.outlineVariant( 0.3f, DrawerDefaults.standardContainerColor ) ) ) } drawerContent.withModifier( modifier = Modifier.container( shape = RectangleShape, borderColor = MaterialTheme.colorScheme.outlineVariant( 0.3f, DrawerDefaults.standardContainerColor ), autoShadowElevation = 2.dp, resultPadding = 0.dp ) ) } } } AnimatedVisibility( visible = showFeaturesFall, modifier = Modifier .fillMaxSize(), enter = fadeIn(tween(1000)) + slideInVertically(tween(1000)) { -it / 4 }, exit = fadeOut(tween(1000)) + slideOutVertically(tween(1000)) { it / 4 } ) { val snowFallList = Screen.entries.mapNotNull { screen -> screen.icon?.let { rememberVectorPainter(image = it) } } val color = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.5f) Box( modifier = Modifier .fillMaxSize() .snowfall( type = FlakeType.Custom(snowFallList), color = color ) ) LaunchedEffect(showFeaturesFall) { if (showFeaturesFall) { delay(5000) showFeaturesFall = false } } DisposableEffect(Unit) { onDispose { showFeaturesFall = false } } } } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/FilteredScreenListFor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getStringLocalized import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.utils.appContext import java.util.Locale @Composable internal fun filteredScreenListFor( screenSearchKeyword: String, selectedNavigationItem: Int, showScreenSearch: Boolean ): State> { val settingsState = LocalSettingsState.current val canSearchScreens = settingsState.screensSearchEnabled val screenList by remember(settingsState.screenList) { derivedStateOf { settingsState.screenList.mapNotNull { Screen.entries.find { s -> s.id == it } }.takeIf { it.isNotEmpty() } ?: Screen.entries } } return remember( settingsState.groupOptionsByTypes, settingsState.favoriteScreenList, screenSearchKeyword, screenList, selectedNavigationItem, showScreenSearch ) { derivedStateOf { when { settingsState.groupOptionsByTypes && (screenSearchKeyword.isEmpty() && !showScreenSearch) -> { Screen.typedEntries[selectedNavigationItem].entries } !settingsState.groupOptionsByTypes && (screenSearchKeyword.isEmpty() && !showScreenSearch) -> { if (selectedNavigationItem == 0) { screenList.filter { it.id in settingsState.favoriteScreenList } } else screenList } else -> screenList }.let { screens -> if (screenSearchKeyword.isNotEmpty() && canSearchScreens) { screens.filter { val string = appContext.getString(it.title) + " " + appContext.getString(it.subtitle) val stringEn = appContext.getStringLocalized(it.title, Locale.ENGLISH) .plus(" ") .plus(appContext.getStringLocalized(it.subtitle, Locale.ENGLISH)) stringEn.contains(other = screenSearchKeyword, ignoreCase = true).or( string.contains(other = screenSearchKeyword, ignoreCase = true) ) } } else screens } } } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/LauncherScreenSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.SizeTransform import androidx.compose.animation.animateColorAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BookmarkBorder import androidx.compose.material3.BadgedBox import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RichTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove import com.t8rin.imagetoolbox.core.settings.presentation.model.IconShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility @Composable internal fun LauncherScreenSelector( screenList: List, onNavigateToScreenWithPopUpTo: (Screen) -> Unit, contentPadding: PaddingValues, onToggleFavorite: (Screen) -> Unit, ) { val settingsState = LocalSettingsState.current LazyVerticalGrid( columns = GridCells.Adaptive(80.dp), contentPadding = contentPadding, verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), flingBehavior = enhancedFlingBehavior() ) { items(screenList) { screen -> val containerColor by animateColorAsState( if (settingsState.isNightMode) { MaterialTheme.colorScheme.secondaryContainer.blend( color = Color.Black, fraction = 0.3f ) } else { MaterialTheme.colorScheme.primaryContainer } ) Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.animateItem() ) { TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider( TooltipAnchorPosition.Below ), tooltip = { RichTooltip( title = { Text( text = stringResource(screen.title), textAlign = TextAlign.Start ) }, text = { Text( text = stringResource(screen.subtitle), textAlign = TextAlign.Start ) }, colors = TooltipDefaults.richTooltipColors( containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer.copy( 0.5f ), titleContentColor = MaterialTheme.colorScheme.onTertiaryContainer ), ) }, state = rememberTooltipState() ) { BadgedBox( badge = { BoxAnimatedVisibility( visible = !settingsState.groupOptionsByTypes, modifier = Modifier .size(34.dp) .offset(x = (-8).dp, y = 8.dp), ) { val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = AutoCircleShape(), pressedShape = ShapeDefaults.smallMini, interactionSource = interactionSource ) EnhancedIconButton( onClick = { onToggleFavorite(screen) }, modifier = Modifier .fillMaxSize() .background( shape = shape, color = MaterialTheme.colorScheme.surface ) .padding(2.dp) .padding(bottom = 0.5.dp), containerColor = containerColor.copy(0.5f), contentColor = LocalContentColor.current, interactionSource = interactionSource ) { val inFavorite by remember( settingsState.favoriteScreenList, screen ) { derivedStateOf { settingsState.favoriteScreenList.find { it == screen.id } != null } } AnimatedContent( targetState = inFavorite, transitionSpec = { (fadeIn() + scaleIn(initialScale = 0.85f)) .togetherWith( fadeOut() + scaleOut( targetScale = 0.85f ) ) }, modifier = Modifier.fillMaxSize(0.6f) ) { isInFavorite -> val icon by remember(isInFavorite) { derivedStateOf { if (isInFavorite) Icons.Rounded.BookmarkRemove else Icons.Rounded.BookmarkBorder } } Icon( imageVector = icon, contentDescription = null ) } } } } ) { val iconShape by remember(settingsState.iconShape) { derivedStateOf { settingsState.iconShape?.takeOrElseFrom(IconShape.entries) } } Box( modifier = Modifier .size(64.dp) .container( resultPadding = 0.dp, color = containerColor, borderColor = MaterialTheme.colorScheme.outlineVariant(), shape = iconShape?.shape ?: ShapeDefaults.circle ) .hapticsClickable { onNavigateToScreenWithPopUpTo(screen) } .padding(iconShape?.padding ?: 0.dp), contentAlignment = Alignment.Center ) { AnimatedContent( targetState = screen.icon!!, modifier = Modifier.fillMaxSize(0.6f), transitionSpec = { (slideInVertically() + fadeIn() + scaleIn()) .togetherWith(slideOutVertically { it / 2 } + fadeOut() + scaleOut()) .using(SizeTransform(false)) } ) { icon -> Icon( imageVector = icon, contentDescription = null, tint = animateColorAsState( if (settingsState.isNightMode) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.primary.blend(Color.Black) } ).value, modifier = Modifier.fillMaxSize() ) } } } } Spacer(Modifier.height(8.dp)) AnimatedContent( targetState = screen.title, transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier.fillMaxWidth() ) { Text( text = stringResource(it), fontSize = 12.sp, lineHeight = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) ) } } } } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainContentImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.shrinkVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.DrawerState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection import com.t8rin.imagetoolbox.core.settings.domain.model.SnowfallMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberCurrentLifecycleEvent import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.modifier.realisticSnowfall import kotlinx.coroutines.delay import java.time.LocalDate @Composable internal fun MainContentImpl( layoutDirection: LayoutDirection, isSheetSlideable: Boolean, sideSheetState: DrawerState, sheetExpanded: Boolean, isGrid: Boolean, onGetClipList: (List) -> Unit, onNavigate: (Screen) -> Unit, onToggleFavorite: (Screen) -> Unit, onShowFeaturesFall: () -> Unit, onTryGetUpdate: () -> Unit, isUpdateAvailable: Boolean ) { val settingsState = LocalSettingsState.current var selectedNavigationItem by rememberSaveable { mutableIntStateOf(0) } val canSearchScreens = settingsState.screensSearchEnabled var showScreenSearch by rememberSaveable(canSearchScreens) { mutableStateOf(false) } var screenSearchKeyword by rememberSaveable(canSearchScreens) { mutableStateOf("") } val currentScreenList by filteredScreenListFor( screenSearchKeyword = screenSearchKeyword, selectedNavigationItem = selectedNavigationItem, showScreenSearch = showScreenSearch ) LocalLayoutDirection.ProvidesValue(layoutDirection) { val snowfallMode = settingsState.snowfallMode val event = rememberCurrentLifecycleEvent() val showSnowfall by remember(snowfallMode, event) { derivedStateOf { when (snowfallMode) { SnowfallMode.Auto -> { LocalDate.now().run { (monthValue == 12 && dayOfMonth >= 22) || (monthValue == 1 && dayOfMonth <= 11) } } SnowfallMode.Enabled -> true SnowfallMode.Disabled -> false } } } val scrollBehavior = if (showSnowfall) { TopAppBarDefaults.pinnedScrollBehavior() } else { TopAppBarDefaults.exitUntilCollapsedScrollBehavior() } val topBar: @Composable () -> Unit = { MainTopAppBar( scrollBehavior = scrollBehavior, onShowFeaturesFall = onShowFeaturesFall, sideSheetState = sideSheetState, isSheetSlideable = isSheetSlideable, onNavigate = onNavigate, type = if (showSnowfall) { EnhancedTopAppBarType.Medium } else { EnhancedTopAppBarType.Large }, modifier = Modifier.realisticSnowfall( enabled = showSnowfall ) ) } Scaffold( modifier = Modifier .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { val colorScheme = MaterialTheme.colorScheme var key by remember { mutableStateOf(colorScheme.primary) } LaunchedEffect(colorScheme) { delay(200) key = colorScheme.primary } if (showSnowfall) { key(key) { topBar() } } else { topBar() } }, bottomBar = { AnimatedVisibility( visible = !isGrid || sheetExpanded || (showScreenSearch && canSearchScreens), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { AnimatedContent( targetState = settingsState.groupOptionsByTypes to (showScreenSearch && canSearchScreens), transitionSpec = { fadeIn() togetherWith fadeOut() } ) { (groupOptionsByTypes, searching) -> if (groupOptionsByTypes && !searching) { MainNavigationBar( selectedIndex = selectedNavigationItem, onValueChange = { selectedNavigationItem = it } ) } else if (!searching) { MainNavigationBarForFavorites( selectedIndex = selectedNavigationItem, onValueChange = { selectedNavigationItem = it } ) } else { SearchableBottomBar( searching = true, updateAvailable = isUpdateAvailable, onTryGetUpdate = onTryGetUpdate, screenSearchKeyword = screenSearchKeyword, onUpdateSearch = { screenSearchKeyword = it }, onCloseSearch = { showScreenSearch = false } ) } } } }, contentWindowInsets = WindowInsets() ) { contentPadding -> Row( modifier = Modifier .fillMaxSize() .padding(contentPadding) ) { val showNavRail = isGrid && screenSearchKeyword.isEmpty() && !sheetExpanded AnimatedVisibility( visible = showNavRail, enter = fadeIn() + expandHorizontally(), exit = fadeOut() + shrinkHorizontally() ) { if (settingsState.groupOptionsByTypes) { MainNavigationRail( selectedIndex = selectedNavigationItem, onValueChange = { selectedNavigationItem = it } ) } else { MainNavigationRailForFavorites( selectedIndex = selectedNavigationItem, onValueChange = { selectedNavigationItem = it } ) } } ScreenPreferenceSelection( currentScreenList = currentScreenList, showScreenSearch = showScreenSearch, screenSearchKeyword = screenSearchKeyword, isGrid = isGrid, isSheetSlideable = isSheetSlideable, showNavRail = showNavRail, onChangeShowScreenSearch = { showScreenSearch = it }, onGetClipList = onGetClipList, onNavigateToScreenWithPopUpTo = onNavigate, onNavigationBarItemChange = { selectedNavigationItem = it }, onToggleFavorite = onToggleFavorite ) } } } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainDrawerContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.width import androidx.compose.material3.DrawerDefaults import androidx.compose.material3.DrawerState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.GraphicsLayerScope import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.coerceAtLeast import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.modifier.CornerSides import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.autoElevatedBorder import com.t8rin.imagetoolbox.core.ui.widget.modifier.only import kotlinx.coroutines.delay @Composable internal fun MainDrawerContent( sideSheetState: DrawerState, isSheetSlideable: Boolean, sheetExpanded: Boolean, layoutDirection: LayoutDirection, settingsBlockContent: @Composable () -> Unit ) { val settingsState = LocalSettingsState.current val settingsBlock = remember { movableContentOf { settingsBlockContent() } } val screenSize = LocalScreenSize.current val widthState by remember(sheetExpanded, screenSize) { derivedStateOf { if (isSheetSlideable) { min( screenSize.width * 0.85f, DrawerDefaults.MaximumDrawerWidth ) } else { if (sheetExpanded) screenSize.width * 0.55f else min( screenSize.width * 0.4f, DrawerDefaults.MaximumDrawerWidth ) }.coerceAtLeast(1.dp) } } var predictiveBackProgress by remember { mutableFloatStateOf(0f) } val animatedPredictiveBackProgress by animateFloatAsState(predictiveBackProgress) val clean = { predictiveBackProgress = 0f } val shape = if (isSheetSlideable) { ShapeDefaults.extraLarge.only( CornerSides.End ) } else RectangleShape LaunchedEffect(sideSheetState.isOpen, isSheetSlideable) { if (!sideSheetState.isOpen || isSheetSlideable) { delay(300L) clean() } } PredictiveBackObserver( onProgress = { predictiveBackProgress = it / 6f }, onClean = { isCompleted -> if (isCompleted) { sideSheetState.close() } clean() }, enabled = (sideSheetState.isOpen || sideSheetState.isAnimationRunning) && isSheetSlideable ) val autoElevation by animateDpAsState( if (settingsState.drawContainerShadows) 16.dp else 0.dp ) ModalDrawerSheet( modifier = Modifier .width(animateDpAsState(targetValue = widthState).value) .then( if (isSheetSlideable) { Modifier .graphicsLayer { val sheetOffset = 0f val sheetHeight = size.height if (!sheetOffset.isNaN() && !sheetHeight.isNaN() && sheetHeight != 0f) { val progress = animatedPredictiveBackProgress scaleX = calculatePredictiveBackScaleX(progress) scaleY = calculatePredictiveBackScaleY(progress) transformOrigin = TransformOrigin((sheetOffset + sheetHeight) / sheetHeight, 0.5f) } } .offset(-((settingsState.borderWidth + 1.dp))) .autoElevatedBorder( shape = shape, autoElevation = autoElevation ) .autoElevatedBorder( height = 0.dp, shape = shape, autoElevation = autoElevation ) .clip(shape) } else Modifier ), drawerContainerColor = MaterialTheme.colorScheme.surfaceContainerLowest, drawerShape = shape, windowInsets = WindowInsets(0) ) { LocalLayoutDirection.ProvidesValue(layoutDirection) { settingsBlock() } } } fun GraphicsLayerScope.calculatePredictiveBackScaleX(progress: Float): Float { val width = size.width return if (width.isNaN() || width == 0f) { 1f } else { (1f - progress).coerceAtLeast(0.85f) } } fun GraphicsLayerScope.calculatePredictiveBackScaleY(progress: Float): Float { val height = size.height return if (height.isNaN() || height == 0f) { 1f } else { (1f - progress).coerceAtLeast(0.85f) } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedNavigationBarItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable internal fun MainNavigationBar( selectedIndex: Int, onValueChange: (Int) -> Unit ) { NavigationBar( modifier = Modifier.drawHorizontalStroke(top = true) ) { Screen.typedEntries.forEachIndexed { index, group -> val selected = index == selectedIndex val haptics = LocalHapticFeedback.current EnhancedNavigationBarItem( modifier = Modifier.weight(1f), selected = selected, onClick = { onValueChange(index) haptics.longPress() }, icon = { AnimatedContent( targetState = selected, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { selected -> Icon( imageVector = group.icon(selected), contentDescription = stringResource(group.title) ) } }, label = { Text( text = stringResource(group.title), modifier = Modifier.marquee() ) } ) } } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationBarForFavorites.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.material.icons.rounded.BookmarkBorder import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ServiceToolbox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedNavigationBarItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable internal fun MainNavigationBarForFavorites( selectedIndex: Int, onValueChange: (Int) -> Unit ) { NavigationBar( modifier = Modifier.drawHorizontalStroke(top = true) ) { val haptics = LocalHapticFeedback.current EnhancedNavigationBarItem( modifier = Modifier.weight(1f), selected = selectedIndex == 0, onClick = { onValueChange(0) haptics.longPress() }, icon = { AnimatedContent( targetState = selectedIndex == 0, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { selected -> Icon( imageVector = if (selected) Icons.Rounded.Bookmark else Icons.Rounded.BookmarkBorder, contentDescription = null ) } }, label = { Text( text = stringResource(R.string.favorite), modifier = Modifier.marquee() ) } ) EnhancedNavigationBarItem( modifier = Modifier.weight(1f), selected = selectedIndex == 1, onClick = { onValueChange(1) haptics.longPress() }, icon = { AnimatedContent( targetState = selectedIndex == 1, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { selected -> Icon( imageVector = if (selected) Icons.Rounded.ServiceToolbox else Icons.Outlined.ServiceToolbox, contentDescription = null ) } }, label = { Text( text = stringResource(R.string.tools), modifier = Modifier.marquee() ) } ) } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRail.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.DrawerDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedNavigationRailItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun MainNavigationRail( selectedIndex: Int, onValueChange: (Int) -> Unit ) { val settingsState = LocalSettingsState.current Row { Box( modifier = Modifier .fillMaxHeight() .widthIn(min = 80.dp) .container( shape = RectangleShape, autoShadowElevation = 10.dp, resultPadding = 0.dp ) ) { Column( modifier = Modifier .fillMaxHeight() .padding(horizontal = 8.dp) .enhancedVerticalScroll(rememberScrollState()) .navigationBarsPadding() .padding( start = WindowInsets .statusBars .asPaddingValues() .calculateStartPadding(LocalLayoutDirection.current) ) .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(LocalLayoutDirection.current) ), verticalArrangement = Arrangement.spacedBy( 4.dp, Alignment.CenterVertically ), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(Modifier.height(8.dp)) Screen.typedEntries.forEachIndexed { index, group -> val selected = index == selectedIndex val haptics = LocalHapticFeedback.current EnhancedNavigationRailItem( modifier = Modifier.width(100.dp), selected = selected, onClick = { onValueChange(index) haptics.longPress() }, icon = { AnimatedContent( targetState = selected, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { selected -> Icon( imageVector = group.icon(selected), contentDescription = stringResource(group.title) ) } }, label = { Text(stringResource(group.title)) } ) } Spacer(Modifier.height(8.dp)) } } Spacer( Modifier .fillMaxHeight() .width(settingsState.borderWidth) .background( MaterialTheme.colorScheme.outlineVariant( 0.3f, DrawerDefaults.standardContainerColor ) ) ) } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainNavigationRailForFavorites.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.material.icons.rounded.BookmarkBorder import androidx.compose.material3.DrawerDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ServiceToolbox import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable internal fun MainNavigationRailForFavorites( selectedIndex: Int, onValueChange: (Int) -> Unit ) { val settingsState = LocalSettingsState.current Row { Box( modifier = Modifier .fillMaxHeight() .widthIn(min = 80.dp) .container( shape = RectangleShape, autoShadowElevation = 10.dp, resultPadding = 0.dp ) ) { Column( modifier = Modifier .fillMaxHeight() .padding(horizontal = 8.dp) .enhancedVerticalScroll(rememberScrollState()) .navigationBarsPadding() .padding( start = WindowInsets .statusBars .asPaddingValues() .calculateStartPadding(LocalLayoutDirection.current) ) .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(LocalLayoutDirection.current) ), verticalArrangement = Arrangement.spacedBy( 4.dp, Alignment.CenterVertically ), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(Modifier.height(8.dp)) val haptics = LocalHapticFeedback.current NavigationRailItem( modifier = Modifier .height(height = 56.dp) .width(100.dp), selected = selectedIndex == 0, onClick = { onValueChange(0) haptics.longPress() }, icon = { AnimatedContent( targetState = selectedIndex == 0, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { selected -> Icon( imageVector = if (selected) Icons.Rounded.Bookmark else Icons.Rounded.BookmarkBorder, contentDescription = null ) } }, label = { Text( text = stringResource(R.string.favorite), modifier = Modifier.marquee() ) } ) NavigationRailItem( modifier = Modifier .height(height = 56.dp) .width(100.dp), selected = selectedIndex == 1, onClick = { onValueChange(1) haptics.longPress() }, icon = { AnimatedContent( targetState = selectedIndex == 1, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { selected -> Icon( imageVector = if (selected) Icons.Rounded.ServiceToolbox else Icons.Outlined.ServiceToolbox, contentDescription = null ) } }, label = { Text( text = stringResource(R.string.tools), modifier = Modifier.marquee() ) } ) Spacer(Modifier.height(8.dp)) } } Box( Modifier .fillMaxHeight() .width(settingsState.borderWidth) .background( MaterialTheme.colorScheme.outlineVariant( 0.3f, DrawerDefaults.standardContainerColor ) ) ) } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/MainTopAppBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("KotlinConstantConditions") package com.t8rin.imagetoolbox.feature.main.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.PushPin import androidx.compose.material.icons.rounded.Settings import androidx.compose.material.icons.twotone.BugReport import androidx.compose.material3.DrawerState import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.BuildConfig import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MobileArrowUpRight import com.t8rin.imagetoolbox.core.resources.icons.PhotoPrints import com.t8rin.imagetoolbox.core.settings.presentation.model.isFirstLaunch import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.AppVersionPreReleaseFlavored import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.canPinShortcuts import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.createScreenShortcut import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelection import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.pulsate import com.t8rin.imagetoolbox.core.ui.widget.modifier.rotateAnimation import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import kotlinx.coroutines.launch import kotlin.time.ExperimentalTime @OptIn(ExperimentalTime::class) @Composable internal fun MainTopAppBar( scrollBehavior: TopAppBarScrollBehavior, onShowFeaturesFall: () -> Unit, onNavigate: (Screen) -> Unit, sideSheetState: DrawerState, isSheetSlideable: Boolean, type: EnhancedTopAppBarType = EnhancedTopAppBarType.Large, modifier: Modifier = Modifier ) { EnhancedTopAppBar( type = type, title = { MainTitle(onShowSnowfall = onShowFeaturesFall) }, actions = { PinShortcutButton() EmbeddedPickerButton( onNavigate = onNavigate ) SettingsButton( onNavigate = onNavigate, sideSheetState = sideSheetState, isSheetSlideable = isSheetSlideable ) }, scrollBehavior = scrollBehavior, modifier = modifier ) } @Composable private fun PinShortcutButton() { val context = LocalContext.current if (context.canPinShortcuts()) { val settingsState = LocalSettingsState.current val scope = rememberCoroutineScope() var showShortcutAddingSheet by rememberSaveable { mutableStateOf(false) } EnhancedIconButton( onClick = { showShortcutAddingSheet = true }, forceMinimumInteractiveComponentSize = false ) { Icon( imageVector = Icons.Outlined.MobileArrowUpRight, contentDescription = null ) } EnhancedModalBottomSheet( visible = showShortcutAddingSheet, onDismiss = { showShortcutAddingSheet = it }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.primaryContainer, onClick = { showShortcutAddingSheet = false } ) { Text(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(R.string.create_shortcut), icon = Icons.Rounded.MobileArrowUpRight, ) } ) { val screenList by remember(settingsState.screenList) { derivedStateOf { settingsState.screenList.mapNotNull { Screen.entries.find { s -> s.id == it } }.distinctBy { it.id }.ifEmpty { Screen.entries } } } var showShortcutPreviewDialog by rememberSaveable { mutableStateOf(false) } var selectedScreenId by rememberSaveable { mutableIntStateOf(-1) } val colorScheme = MaterialTheme.colorScheme var shortcutColor by rememberSaveable( selectedScreenId, stateSaver = ColorSaver ) { mutableStateOf(colorScheme.primaryContainer) } val selectedScreen by remember(screenList, selectedScreenId) { derivedStateOf { screenList.find { it.id == selectedScreenId } } } EnhancedAlertDialog( visible = showShortcutPreviewDialog, onDismissRequest = { showShortcutPreviewDialog = false }, confirmButton = { EnhancedButton( onClick = { selectedScreen?.let { screen -> scope.launch { context.createScreenShortcut( screen = screen, tint = shortcutColor, onFailure = AppToastHost::showFailureToast ) } } } ) { Text(stringResource(R.string.add)) } }, dismissButton = { EnhancedButton( onClick = { showShortcutPreviewDialog = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } }, icon = { Icon( imageVector = Icons.Rounded.MobileArrowUpRight, contentDescription = null ) }, title = { Text( text = stringResource(R.string.create_shortcut) ) }, text = { val state = rememberScrollState() Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fadingEdges( scrollableState = state, isVertical = true ) .enhancedVerticalScroll(state) ) { Box( modifier = Modifier .background(Color.White, ShapeDefaults.circle) .padding(8.dp) ) { Icon( imageVector = selectedScreen?.icon ?: Icons.Rounded.MobileArrowUpRight, contentDescription = null, tint = shortcutColor, modifier = Modifier.size(36.dp) ) } Spacer(Modifier.height(16.dp)) ColorSelection( value = shortcutColor, onValueChange = { shortcutColor = it } ) } } ) LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(250.dp), contentPadding = PaddingValues(16.dp), verticalItemSpacing = 8.dp, horizontalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior() ) { item( span = StaggeredGridItemSpan.FullLine ) { PreferenceItem( title = stringResource(R.string.create_shortcut_title), subtitle = stringResource(R.string.create_shortcut_subtitle), startIcon = Icons.Rounded.PushPin, containerColor = MaterialTheme.colorScheme.secondaryContainer, modifier = Modifier.padding(bottom = 8.dp), shape = ShapeDefaults.extremeLarge ) } items( items = screenList, key = { it.id } ) { screen -> PreferenceItem( onClick = { showShortcutPreviewDialog = true selectedScreenId = screen.id }, startIcon = screen.icon, title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), modifier = Modifier.fillMaxWidth() ) } } } } } @Composable private fun EmbeddedPickerButton( onNavigate: (Screen) -> Unit ) { var editSheetData by remember { mutableStateOf(listOf()) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } val imagePicker = rememberImagePicker { uris: List -> editSheetData = uris } EnhancedIconButton( onClick = imagePicker::pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, forceMinimumInteractiveComponentSize = false ) { Icon( imageVector = Icons.Outlined.PhotoPrints, contentDescription = "Embedded Picker Button" ) } OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = onNavigate ) } @Composable private fun SettingsButton( onNavigate: (Screen) -> Unit, sideSheetState: DrawerState, isSheetSlideable: Boolean ) { val scope = rememberCoroutineScope() val settingsState = LocalSettingsState.current if (isSheetSlideable || settingsState.useFullscreenSettings) { EnhancedIconButton( onClick = { if (settingsState.useFullscreenSettings) { onNavigate(Screen.Settings()) } else { scope.launch { sideSheetState.open() } } }, modifier = Modifier .pulsate( range = 0.95f..1.2f, enabled = settingsState.isFirstLaunch() ) .rotateAnimation(enabled = settingsState.isFirstLaunch()) ) { Icon( imageVector = Icons.Rounded.Settings, contentDescription = stringResource(R.string.settings) ) } } } @Composable private fun MainTitle( onShowSnowfall: () -> Unit ) { val settingsState = LocalSettingsState.current LocalLayoutDirection.ProvidesValue(LayoutDirection.Ltr) { val badgeText = remember { "${Screen.FEATURES_COUNT} $AppVersionPreReleaseFlavored".trim() } Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { AnimatedContent(settingsState.mainScreenTitle) { title -> Text(title) } if (BuildConfig.DEBUG) { Icon( imageVector = Icons.TwoTone.BugReport, contentDescription = null, modifier = Modifier .offset(x = 2.dp) .size( with(LocalDensity.current) { LocalTextStyle.current.fontSize.toDp() * 1.05f } ) ) } EnhancedBadge( content = { Text(badgeText) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { onShowSnowfall() } ) Spacer(Modifier.width(12.dp)) TopAppBarEmoji() } } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/ScreenPreferenceSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import android.content.ClipboardManager import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.SizeTransform import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ContentPasteOff import androidx.compose.material.icons.rounded.BookmarkBorder import androidx.compose.material.icons.rounded.ContentPaste import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material3.BadgedBox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.getSystemService import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BookmarkOff import com.t8rin.imagetoolbox.core.resources.icons.BookmarkRemove import com.t8rin.imagetoolbox.core.resources.icons.LayersSearchOutline import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.clipList import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberClipboardData import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButtonType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.utils.getString @Composable internal fun RowScope.ScreenPreferenceSelection( currentScreenList: List, showScreenSearch: Boolean, screenSearchKeyword: String, isGrid: Boolean, isSheetSlideable: Boolean, onGetClipList: (List) -> Unit, onNavigationBarItemChange: (Int) -> Unit, onNavigateToScreenWithPopUpTo: (Screen) -> Unit, onChangeShowScreenSearch: (Boolean) -> Unit, onToggleFavorite: (Screen) -> Unit, showNavRail: Boolean, ) { val settingsState = LocalSettingsState.current val cutout = WindowInsets.displayCutout.asPaddingValues() val canSearchScreens = settingsState.screensSearchEnabled val isSearching = showScreenSearch && screenSearchKeyword.isNotEmpty() && canSearchScreens val isScreenSelectionLauncherMode = settingsState.isScreenSelectionLauncherMode AnimatedContent( modifier = Modifier .weight(1f) .widthIn(min = 1.dp), targetState = remember(currentScreenList, isSearching, settingsState.favoriteScreenList) { Triple( currentScreenList.isNotEmpty(), isSearching, settingsState.favoriteScreenList.isEmpty() ) }, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { (hasScreens, isSearching, noFavorites) -> if (hasScreens) { Box( modifier = Modifier.fillMaxSize() ) { val clipboardData by rememberClipboardData() val allowAutoPaste = settingsState.allowAutoClipboardPaste val showClipButton = (clipboardData.isNotEmpty() && allowAutoPaste) || !allowAutoPaste val showSearchButton = !showScreenSearch && canSearchScreens val layoutDirection = LocalLayoutDirection.current val navBarsPadding = WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding() val contentPadding by remember( isGrid, navBarsPadding, showClipButton, showSearchButton, isSheetSlideable, layoutDirection, cutout, showNavRail, isScreenSelectionLauncherMode ) { derivedStateOf { val vertical = if (isScreenSelectionLauncherMode) 12.dp else 0.dp val firstBottomPart = if (isGrid) { navBarsPadding } else { 0.dp } val secondBottomPart = if (showClipButton && showSearchButton) { 76.dp + 48.dp } else if (showClipButton || showSearchButton) { 76.dp } else { 0.dp } PaddingValues( bottom = 12.dp + firstBottomPart + secondBottomPart + vertical, top = 12.dp + vertical, end = 12.dp + if (isSheetSlideable) { cutout.calculateEndPadding(layoutDirection) } else 0.dp, start = 12.dp + if (!showNavRail) { cutout.calculateStartPadding(layoutDirection) } else 0.dp ) } } AnimatedContent( targetState = isScreenSelectionLauncherMode, modifier = Modifier.fillMaxSize() ) { isLauncherMode -> if (isLauncherMode) { LauncherScreenSelector( screenList = currentScreenList, onNavigateToScreenWithPopUpTo = onNavigateToScreenWithPopUpTo, contentPadding = contentPadding, onToggleFavorite = onToggleFavorite ) } else { LazyVerticalStaggeredGrid( reverseLayout = showScreenSearch && screenSearchKeyword.isNotEmpty() && canSearchScreens, modifier = Modifier.fillMaxSize(), columns = StaggeredGridCells.Adaptive(220.dp), verticalItemSpacing = 12.dp, horizontalArrangement = Arrangement.spacedBy( space = 12.dp, alignment = Alignment.CenterHorizontally ), contentPadding = contentPadding, flingBehavior = enhancedFlingBehavior(), content = { items(currentScreenList) { screen -> PreferenceItemOverload( onClick = { onNavigateToScreenWithPopUpTo(screen) }, containerColor = MaterialTheme.colorScheme.surfaceContainerLow, modifier = Modifier .widthIn(min = 1.dp) .fillMaxWidth() .animateItem(), shape = ShapeDefaults.default, title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), badge = { AnimatedVisibility( visible = screen.isBetaFeature, modifier = Modifier .align(Alignment.CenterVertically) .padding( start = 4.dp, bottom = 2.dp, top = 2.dp ), enter = fadeIn(), exit = fadeOut() ) { EnhancedBadge( content = { Text(stringResource(R.string.beta)) }, containerColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.onSecondary ) } }, endIcon = { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { if (!settingsState.groupOptionsByTypes) { EnhancedIconButton( onClick = { onToggleFavorite(screen) }, modifier = Modifier.offset(8.dp) ) { val inFavorite by remember( settingsState.favoriteScreenList, screen ) { derivedStateOf { settingsState.favoriteScreenList.find { it == screen.id } != null } } AnimatedContent( targetState = inFavorite, transitionSpec = { (fadeIn() + scaleIn(initialScale = 0.85f)) .togetherWith( fadeOut() + scaleOut( targetScale = 0.85f ) ) } ) { isInFavorite -> val icon by remember(isInFavorite) { derivedStateOf { if (isInFavorite) Icons.Rounded.BookmarkRemove else Icons.Rounded.BookmarkBorder } } Icon( imageVector = icon, contentDescription = null ) } } } } }, startIcon = { AnimatedContent( targetState = screen.icon, transitionSpec = { (slideInVertically() + fadeIn() + scaleIn()) .togetherWith(slideOutVertically { it / 2 } + fadeOut() + scaleOut()) .using(SizeTransform(false)) } ) { icon -> icon?.let { Icon( imageVector = icon, contentDescription = null ) } } } ) } } ) } } val context = LocalContext.current val clipboardManager = remember(context) { context.getSystemService() } BoxAnimatedVisibility( visible = showClipButton, modifier = Modifier .align(Alignment.BottomEnd) .padding(16.dp) .then( if (showNavRail) { Modifier.navigationBarsPadding() } else Modifier ), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { BadgedBox( badge = { if (clipboardData.isNotEmpty()) { EnhancedBadge( containerColor = MaterialTheme.colorScheme.primary ) { Text(clipboardData.size.toString()) } } } ) { EnhancedFloatingActionButton( onClick = { if (!allowAutoPaste) { val list = clipboardManager.clipList() if (list.isEmpty()) { AppToastHost.showToast( message = getString(R.string.clipboard_paste_invalid_empty), icon = Icons.Outlined.ContentPasteOff ) } else onGetClipList(list) } else onGetClipList(clipboardData) }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Icon( imageVector = Icons.Rounded.ContentPaste, contentDescription = stringResource(R.string.copy) ) } } } BoxAnimatedVisibility( visible = showSearchButton, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut(), modifier = Modifier .align(Alignment.BottomEnd) .then( if (showClipButton) { Modifier.padding(start = 16.dp, end = 16.dp, bottom = 4.dp) } else Modifier.padding(16.dp) ) .then( if (showNavRail) { Modifier.navigationBarsPadding() } else Modifier ) .then( if (showClipButton) { Modifier.padding(bottom = 76.dp) } else Modifier ) ) { EnhancedFloatingActionButton( containerColor = if (showClipButton) { MaterialTheme.colorScheme.secondaryContainer } else MaterialTheme.colorScheme.tertiaryContainer, type = if (showClipButton) { EnhancedFloatingActionButtonType.Small } else EnhancedFloatingActionButtonType.Primary, onClick = { onChangeShowScreenSearch(canSearchScreens) } ) { Icon( imageVector = Icons.Outlined.LayersSearchOutline, contentDescription = stringResource(R.string.search_here) ) } } } } else { if (!isSearching && noFavorites) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.no_favorite_options_selected), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Outlined.BookmarkOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.height(16.dp)) EnhancedButton( onClick = { onNavigationBarItemChange(1) } ) { Text(stringResource(R.string.add_favorites)) } Spacer(Modifier.weight(1f)) } } else { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } } } } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/components/SearchableBottomBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.components import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.APP_GITHUB_LINK import com.t8rin.imagetoolbox.core.resources.BuildConfig import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Github import com.t8rin.imagetoolbox.core.resources.icons.GooglePlay import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.AppVersion import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore import com.t8rin.imagetoolbox.core.ui.utils.helper.asUnsafe import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.modifier.pulsate import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import kotlinx.coroutines.delay @Composable internal fun SearchableBottomBar( searching: Boolean, updateAvailable: Boolean, onTryGetUpdate: () -> Unit, screenSearchKeyword: String, onUpdateSearch: (String) -> Unit, onCloseSearch: () -> Unit ) { BottomAppBar( modifier = Modifier.drawHorizontalStroke(top = true), actions = { if (!searching) { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, contentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy( alpha = 0.5f ), borderColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ), modifier = Modifier .padding(horizontal = 16.dp) .pulsate(enabled = updateAvailable), onClick = onTryGetUpdate ) { Text( stringResource(R.string.version) + " $AppVersion (${BuildConfig.VERSION_CODE})" ) } } else { val focus = remember { FocusRequester() } LaunchedEffect(Unit) { delay(100) focus.requestFocus() } BackHandler { onUpdateSearch("") onCloseSearch() } ProvideTextStyle(value = MaterialTheme.typography.bodyLarge) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, modifier = Modifier .focusRequester(focus) .padding(start = 6.dp) .offset(2.dp, (-2).dp), keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = screenSearchKeyword, onValueChange = { onUpdateSearch(it) }, startIcon = { EnhancedIconButton( onClick = { onUpdateSearch("") onCloseSearch() }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = MaterialTheme.colorScheme.onSurface ) } }, endIcon = { AnimatedVisibility( visible = screenSearchKeyword.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = { onUpdateSearch("") }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface ) } } }, shape = ShapeDefaults.circle ) } } }, floatingActionButton = { val context = LocalContext.current val linkHandler = LocalUriHandler.current.asUnsafe() if (!searching) { EnhancedFloatingActionButton( onClick = { if (context.isInstalledFromPlayStore()) { runCatching { linkHandler.openUri("market://details?id=${context.packageName}") }.onFailure { linkHandler.openUri("https://play.google.com/store/apps/details?id=${context.packageName}") } } else { linkHandler.openUri(APP_GITHUB_LINK) } }, modifier = Modifier.requiredSize(size = 56.dp), content = { if (context.isInstalledFromPlayStore()) { Icon( imageVector = Icons.Rounded.GooglePlay, contentDescription = "Google Play", modifier = Modifier.offset(1.5.dp) ) } else { Icon( imageVector = Icons.Rounded.Github, contentDescription = stringResource(R.string.github) ) } } ) } } ) } ================================================ FILE: feature/main/src/main/java/com/t8rin/imagetoolbox/feature/main/presentation/screenLogic/MainComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.main.presentation.screenLogic import android.net.Uri import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.childContext import com.arkivanov.decompose.value.Value import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class MainComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted private val onTryGetUpdate: (Boolean, () -> Unit) -> Unit, @Assisted private val onGetClipList: (List) -> Unit, @Assisted val onNavigate: (Screen) -> Unit, @Assisted val isUpdateAvailable: Value, dispatchersHolder: DispatchersHolder, private val settingsManager: SettingsManager, settingsComponentFactory: SettingsComponent.Factory ) : BaseComponent(dispatchersHolder, componentContext) { val settingsComponent = settingsComponentFactory( componentContext = childContext("mainSettings"), onTryGetUpdate = onTryGetUpdate, onNavigate = onNavigate, isUpdateAvailable = isUpdateAvailable, onGoBack = null, initialSearchQuery = "" ) fun tryGetUpdate( isNewRequest: Boolean = false, onNoUpdates: () -> Unit = {} ) = onTryGetUpdate(isNewRequest, onNoUpdates) fun toggleFavoriteScreen(screen: Screen) { componentScope.launch { settingsManager.toggleFavoriteScreen(screen.id) } } fun parseClipList( list: List ) = onGetClipList(list) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onTryGetUpdate: (Boolean, () -> Unit) -> Unit, onGetClipList: (List) -> Unit, onNavigate: (Screen) -> Unit, isUpdateAvailable: Value, ): MainComponent } } ================================================ FILE: feature/markup-layers/.gitignore ================================================ /build ================================================ FILE: feature/markup-layers/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.markup_layers" ================================================ FILE: feature/markup-layers/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/AndroidMarkupLayersApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data import android.content.Context import android.graphics.Bitmap import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.utils.outputStream import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.json.JsonParser import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.createZip import com.t8rin.imagetoolbox.core.utils.putEntry import com.t8rin.imagetoolbox.feature.markup_layers.data.project.AssetRegistry import com.t8rin.imagetoolbox.feature.markup_layers.data.project.MarkupMapper import com.t8rin.imagetoolbox.feature.markup_layers.data.project.MarkupProjectFile import com.t8rin.imagetoolbox.feature.markup_layers.data.project.MarkupProjectJsonEntry import com.t8rin.imagetoolbox.feature.markup_layers.data.project.ProjectArchive import com.t8rin.imagetoolbox.feature.markup_layers.data.project.ProjectFileLoadResult import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.LayersRenderer import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayersApplier import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProject import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProjectResult import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream import java.util.UUID import java.util.zip.ZipEntry import java.util.zip.ZipException import java.util.zip.ZipInputStream import javax.inject.Inject internal class AndroidMarkupLayersApplier @Inject constructor( @ApplicationContext private val context: Context, dispatchersHolder: DispatchersHolder, private val mapper: MarkupMapper, private val renderer: LayersRenderer, private val jsonParser: JsonParser, ) : MarkupLayersApplier, DispatchersHolder by dispatchersHolder { private val projectCacheRoot by lazy { File(context.filesDir, "markup-projects").apply(File::mkdirs) } override suspend fun applyToImage( image: Bitmap, layers: List ): Bitmap = withContext(defaultDispatcher) { renderer.render( backgroundImage = image, layers = layers ) } override suspend fun saveProject( destination: Writeable, project: MarkupProject ) = withContext(ioDispatcher) { writeProjectArchive( destination = destination, archive = project.toArchive() ?: return@withContext ) } override suspend fun openProject(uri: String): MarkupProjectResult = withContext(ioDispatcher) { clearProjectCache() try { val extractionDir = File(projectCacheRoot, UUID.randomUUID().toString()).apply(File::mkdirs) val loadResult = loadProjectFile( uri = uri, extractionDir = extractionDir ) when (loadResult) { is ProjectFileLoadResult.Error -> loadResult.error is ProjectFileLoadResult.Success -> mapper.map( project = loadResult.projectFile, extractionDir = extractionDir ) } } catch (t: Throwable) { clearProjectCache() if (t is ZipException) { invalidArchiveError() } else { MarkupProjectResult.Error.Exception( throwable = t, message = t.localizedMessage ?: context.getString(R.string.something_went_wrong) ) } } } override fun clearProjectCache() { projectCacheRoot.listFiles().orEmpty().forEach(File::deleteRecursively) } private fun MarkupProject.toArchive(): ProjectArchive? { val assetRegistry = AssetRegistry() val projectJson = jsonParser.toJson( obj = mapper.map( project = this, registry = assetRegistry ), type = MarkupProjectFile::class.java ) ?: return null return ProjectArchive( projectJson = projectJson, assets = assetRegistry.entries() ) } private fun loadProjectFile( uri: String, extractionDir: File ): ProjectFileLoadResult { val projectJson = extractProjectArchive( uri = uri, extractionDir = extractionDir ) ?: return ProjectFileLoadResult.Error(invalidArchiveError()) if (projectJson.isBlank()) { return ProjectFileLoadResult.Error( MarkupProjectResult.Error.MissingProjectFile( message = context.getString(R.string.markup_project_missing_data) ) ) } val projectFile = jsonParser.fromJson( json = projectJson, type = MarkupProjectFile::class.java ) ?: return ProjectFileLoadResult.Error( MarkupProjectResult.Error.InvalidProjectFile( message = context.getString(R.string.markup_project_corrupted) ) ) return ProjectFileLoadResult.Success(projectFile) } private fun invalidArchiveError(): MarkupProjectResult.Error.InvalidArchive { return MarkupProjectResult.Error.InvalidArchive( message = context.getString(R.string.markup_project_open_failed) ) } private fun writeProjectArchive( destination: Writeable, archive: ProjectArchive ) { destination.outputStream().createZip { zip -> zip.putEntry(MarkupProjectJsonEntry) { it.write(archive.projectJson.toByteArray()) } archive.assets.forEach { asset -> zip.putEntry(asset.entryName) { entry -> openSourceStream(asset.source)?.use { input -> input.copyTo(entry) } } } } } private fun extractProjectArchive( uri: String, extractionDir: File ): String? { val extractionDirPath = extractionDir.canonicalPath + File.separator return context.contentResolver.openInputStream(uri.toUri())?.use { inputStream -> ZipInputStream(inputStream).use { zipIn -> var projectJson: String? = null var entry: ZipEntry? while (zipIn.nextEntry.also { entry = it } != null) { entry?.let { zipEntry -> if (!zipEntry.isDirectory && zipEntry.name == MarkupProjectJsonEntry) { projectJson = zipIn.readBytes().decodeToString() zipIn.closeEntry() return@let } val output = File(extractionDir, zipEntry.name).canonicalFile if (!output.path.startsWith(extractionDirPath)) { throw ZipException("Invalid zip entry path: ${zipEntry.name}") } if (zipEntry.isDirectory) { output.mkdirs() } else { output.parentFile?.mkdirs() FileOutputStream(output).use { fos -> zipIn.copyTo(fos) } } zipIn.closeEntry() } } projectJson } } } private fun openSourceStream(source: String) = when { source.startsWith("content://") || source.startsWith("file://") -> { context.contentResolver.openInputStream(source.toUri()) } else -> File(source).takeIf(File::exists)?.inputStream() ?: context.contentResolver.openInputStream(source.toUri()) } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/AssetRegistry.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.project import androidx.core.net.toUri import java.io.File internal class AssetRegistry { private val entryBySource = linkedMapOf() fun register( source: String, proposedEntryName: String ): String { val key = sourceKey(source) return entryBySource[key]?.entryName ?: proposedEntryName.also { entryBySource[key] = AssetSource( entryName = proposedEntryName, source = source ) } } private fun sourceKey( source: String ): String = when { source.startsWith("android.resource://") -> source source.startsWith("content://") -> source source.startsWith("file://") -> { source.toUri().path ?.let(::File) ?.canonicalPath ?.let { "file:$it" } ?: source } else -> runCatching { File(source).canonicalPath } .getOrNull() ?.let { "path:$it" } ?: source } fun entries(): List = entryBySource.values.toList() } internal data class AssetSource( val entryName: String, val source: String ) internal data class ProjectArchive( val projectJson: String, val assets: List ) ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/Mapping.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.project import android.net.Uri import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Outline import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.DomainFontFamily import com.t8rin.imagetoolbox.core.settings.domain.model.FontType import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.settings.presentation.model.asFontType import com.t8rin.imagetoolbox.core.settings.presentation.model.asUi import com.t8rin.imagetoolbox.core.ui.utils.helper.entries import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.extension import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.markup_layers.domain.DropShadow import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerPosition import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProject import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProjectHistorySnapshot import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProjectResult import com.t8rin.imagetoolbox.feature.markup_layers.domain.ProjectBackground import com.t8rin.imagetoolbox.feature.markup_layers.domain.TextGeometricTransform import com.t8rin.imagetoolbox.feature.markup_layers.domain.arrowAngle import com.t8rin.imagetoolbox.feature.markup_layers.domain.arrowSizeScale import com.t8rin.imagetoolbox.feature.markup_layers.domain.cornerRadius import com.t8rin.imagetoolbox.feature.markup_layers.domain.innerRadiusRatio import com.t8rin.imagetoolbox.feature.markup_layers.domain.isRegular import com.t8rin.imagetoolbox.feature.markup_layers.domain.layerCornerRadiusPercent import com.t8rin.imagetoolbox.feature.markup_layers.domain.ordinal import com.t8rin.imagetoolbox.feature.markup_layers.domain.outlinedFillColorInt import com.t8rin.imagetoolbox.feature.markup_layers.domain.resolveMarkupLayerShapeMode import com.t8rin.imagetoolbox.feature.markup_layers.domain.rotationDegrees import com.t8rin.imagetoolbox.feature.markup_layers.domain.updateArrow import com.t8rin.imagetoolbox.feature.markup_layers.domain.updatePolygon import com.t8rin.imagetoolbox.feature.markup_layers.domain.updateRect import com.t8rin.imagetoolbox.feature.markup_layers.domain.updateStar import com.t8rin.imagetoolbox.feature.markup_layers.domain.vertices import com.t8rin.imagetoolbox.feature.markup_layers.domain.withOutlinedFillColor import com.t8rin.logger.makeLog import java.io.File import javax.inject.Inject internal class MarkupMapper @Inject constructor( private val settingsManager: SettingsManager ) { fun map(project: MarkupProject, registry: AssetRegistry) = project.toSnapshot(registry) suspend fun map( project: MarkupProjectFile, extractionDir: File ) = project.toDomain(extractionDir) private fun MarkupProject.toSnapshot( assetRegistry: AssetRegistry ): MarkupProjectFile = MarkupProjectFile( version = MarkupProjectVersion, background = background.toSnapshot(assetRegistry), layers = layers.toSnapshotList(assetRegistry, prefix = "layer"), lastLayers = lastLayers.toSnapshotList(assetRegistry, prefix = "last"), undoneLayers = undoneLayers.toSnapshotList(assetRegistry, prefix = "undone"), history = history.toEditorSnapshots(assetRegistry, prefix = "history"), redoHistory = redoHistory.toEditorSnapshots(assetRegistry, prefix = "redo") ) private suspend fun MarkupProjectFile.toDomain( extractionDir: File ): MarkupProjectResult { if (version != MarkupProjectVersion) { return MarkupProjectResult.Error.UnsupportedVersion( version = version, message = getString(R.string.unsupported_markup_project_version, version) ) } return MarkupProjectResult.Success( project = MarkupProject( background = background.toDomain(extractionDir), layers = layers.toDomainLayers(extractionDir), lastLayers = lastLayers.toDomainLayers(extractionDir), undoneLayers = undoneLayers.toDomainLayers(extractionDir), history = history.toDomainSnapshots(extractionDir), redoHistory = redoHistory.toDomainSnapshots(extractionDir) ) ) } private fun List.toEditorSnapshots( assetRegistry: AssetRegistry, prefix: String ): List = mapIndexed { index, snapshot -> snapshot.toSnapshot( assetRegistry = assetRegistry, prefix = "$prefix-$index" ) } private fun MarkupProjectHistorySnapshot.toSnapshot( assetRegistry: AssetRegistry, prefix: String ): EditorSnapshot = EditorSnapshot( background = background.toSnapshot(assetRegistry), layers = layers.toSnapshotList( assetRegistry = assetRegistry, prefix = prefix ) ) private fun List.toSnapshotList( assetRegistry: AssetRegistry, prefix: String ): List = mapIndexed { index, layer -> layer.toSnapshot( index = index, assetRegistry = assetRegistry, prefix = prefix ) } private fun ProjectBackground.toSnapshot( assetRegistry: AssetRegistry ): BackgroundSnapshot = when (this) { is ProjectBackground.Color -> BackgroundSnapshot( type = BackgroundType.Color, width = width, height = height, color = color ) is ProjectBackground.Image -> { val source = uri val entryName = assetRegistry.register( source = source, proposedEntryName = assetEntryName( prefix = "background", extension = sourceExtension(source) ) ) BackgroundSnapshot( type = BackgroundType.Image, assetPath = entryName ) } ProjectBackground.None -> BackgroundSnapshot(type = BackgroundType.None) } private fun MarkupLayer.toSnapshot( index: Int, assetRegistry: AssetRegistry, prefix: String ): LayerSnapshot { val layerType = type return LayerSnapshot( type = layerType.toSnapshotType(), position = position.toSnapshot(), contentWidth = contentSize.width, contentHeight = contentSize.height, visibleLineCount = visibleLineCount, cornerRadiusPercent = layerType.layerCornerRadiusPercent(cornerRadiusPercent), isLocked = isLocked, blendingMode = blendingMode.value, text = (layerType as? LayerType.Text)?.toSnapshot( assetRegistry = assetRegistry, fontPrefix = "$prefix-$index-font" ), picture = layerType.toPictureSnapshot( assetRegistry = assetRegistry, prefix = "$prefix-$index" ), shape = (layerType as? LayerType.Shape)?.toSnapshot(), groupedLayers = groupedLayers.toSnapshotList( assetRegistry = assetRegistry, prefix = "$prefix-$index-group" ) ) } private fun LayerPosition.toSnapshot(): PositionSnapshot = PositionSnapshot( scale = scale, rotation = rotation, isFlippedHorizontally = isFlippedHorizontally, isFlippedVertically = isFlippedVertically, offsetX = offsetX, offsetY = offsetY, alpha = alpha, canvasWidth = currentCanvasSize.width, canvasHeight = currentCanvasSize.height, coerceToBounds = coerceToBounds, isVisible = isVisible ) private fun LayerType.toSnapshotType(): LayerSnapshotType = when (this) { is LayerType.Text -> LayerSnapshotType.Text is LayerType.Picture.Image -> LayerSnapshotType.Image is LayerType.Picture.Sticker -> LayerSnapshotType.Sticker is LayerType.Shape -> LayerSnapshotType.Shape } private fun LayerType.Text.toSnapshot( assetRegistry: AssetRegistry, fontPrefix: String ): TextSnapshot = TextSnapshot( color = color, size = size, font = font?.toSnapshot( assetRegistry = assetRegistry, prefix = fontPrefix ), backgroundColor = backgroundColor, text = text, decorations = decorations.map(Enum<*>::name), outline = outline?.toSnapshot(), alignment = alignment.name, geometricTransform = geometricTransform?.toSnapshot(), shadow = shadow?.toSnapshot() ) private fun LayerType.toPictureSnapshot( assetRegistry: AssetRegistry, prefix: String ): PictureSnapshot? = when (this) { is LayerType.Picture.Image -> { val source = imageData.toPersistableSource() val entryName = assetRegistry.register( source = source, proposedEntryName = assetEntryName( prefix = prefix, extension = sourceExtension(source) ) ) PictureSnapshot( assetPath = entryName, shadow = shadow?.toSnapshot() ) } is LayerType.Picture.Sticker -> PictureSnapshot( value = imageData.toString(), shadow = shadow?.toSnapshot() ) is LayerType.Text, is LayerType.Shape -> null } private fun LayerType.Shape.toSnapshot(): ShapeSnapshot = ShapeSnapshot( modeName = shapeMode.kind.name, modeOrdinal = shapeMode.ordinal, color = color, strokeWidth = strokeWidth, widthRatio = widthRatio, heightRatio = heightRatio, fillColor = shapeMode.outlinedFillColorInt(), rotationDegrees = shapeMode.rotationDegrees(), cornerRadius = shapeMode.cornerRadius(), vertices = shapeMode.vertices(), isRegular = shapeMode.isRegular(), innerRadiusRatio = shapeMode.innerRadiusRatio(), sizeScale = shapeMode.arrowSizeScale(), angle = shapeMode.arrowAngle(), shadow = shadow?.toSnapshot() ) private fun Outline.toSnapshot(): OutlineSnapshot = OutlineSnapshot( color = color, width = width ) private fun TextGeometricTransform.toSnapshot(): TextGeometricTransformSnapshot = TextGeometricTransformSnapshot( scaleX = scaleX, skewX = skewX ) private fun DropShadow.toSnapshot(): DropShadowSnapshot = DropShadowSnapshot( color = color, offsetX = offsetX, offsetY = offsetY, blurRadius = blurRadius ) private suspend fun List.toDomainLayers( extractionDir: File ): List = map { it.toDomain(extractionDir) } private suspend fun List.toDomainSnapshots( extractionDir: File ): List = map { snapshot -> MarkupProjectHistorySnapshot( background = snapshot.background.toDomain(extractionDir), layers = snapshot.layers.toDomainLayers(extractionDir) ) } private fun BackgroundSnapshot.toDomain( extractionDir: File ): ProjectBackground = when (type) { BackgroundType.Image -> ProjectBackground.Image( uri = File(extractionDir, assetPath.orEmpty()).toUri().toString() ) BackgroundType.Color -> ProjectBackground.Color( width = width ?: 1, height = height ?: 1, color = color ?: 0 ) BackgroundType.None -> ProjectBackground.None } private suspend fun LayerSnapshot.toDomain( extractionDir: File ): MarkupLayer { val layerType = toDomainType(extractionDir) return MarkupLayer( type = layerType, position = position.toDomain(), contentSize = IntegerSize( width = (contentWidth ?: 0).coerceAtLeast(0), height = (contentHeight ?: 0).coerceAtLeast(0) ), visibleLineCount = visibleLineCount, cornerRadiusPercent = layerType.layerCornerRadiusPercent(cornerRadiusPercent), isLocked = isLocked, blendingMode = BlendingMode.entries.find { it.value == blendingMode } ?: BlendingMode.SrcOver, groupedLayers = groupedLayers.toDomainLayers(extractionDir) ) } private suspend fun LayerSnapshot.toDomainType( extractionDir: File ): LayerType = when (type) { LayerSnapshotType.Text -> { val value = text ?: error("Missing text layer data") value.toDomain(extractionDir) } LayerSnapshotType.Image -> LayerType.Picture.Image( imageData = File(extractionDir, picture?.assetPath.orEmpty()).toUri().toString(), shadow = picture?.shadow?.toDomain() ) LayerSnapshotType.Sticker -> LayerType.Picture.Sticker( imageData = picture?.value.orEmpty(), shadow = picture?.shadow?.toDomain() ) LayerSnapshotType.Shape -> { val value = shape ?: error("Missing shape layer data") value.toDomain() } } private suspend fun TextSnapshot.toDomain( extractionDir: File ): LayerType.Text = LayerType.Text( color = color, size = size, font = font?.toDomain(extractionDir), backgroundColor = backgroundColor, text = text, decorations = decorations.toDomainDecorations(), outline = outline?.toDomain(), alignment = alignment.toDomainAlignment(), geometricTransform = geometricTransform?.toDomain(), shadow = shadow?.toDomain() ) private fun ShapeSnapshot.toDomain(): LayerType.Shape { val baseMode = resolveMarkupLayerShapeMode( modeName = modeName, modeOrdinal = modeOrdinal ) val shapeMode = baseMode .updateArrow( sizeScale = sizeScale, angle = angle ) .updateRect( rotationDegrees = rotationDegrees, cornerRadius = cornerRadius ) .updatePolygon( vertices = vertices, rotationDegrees = rotationDegrees, isRegular = isRegular ) .updateStar( vertices = vertices, innerRadiusRatio = innerRadiusRatio, rotationDegrees = rotationDegrees, isRegular = isRegular ) .withOutlinedFillColor(fillColor) return LayerType.Shape( shapeMode = shapeMode, color = color, strokeWidth = strokeWidth, widthRatio = widthRatio, heightRatio = heightRatio, shadow = shadow?.toDomain() ) } private fun List.toDomainDecorations(): List { return mapNotNull { value -> runCatching { LayerType.Text.Decoration.valueOf(value) }.onFailure(Throwable::makeLog).getOrNull() } } private fun String.toDomainAlignment(): LayerType.Text.Alignment { return runCatching { LayerType.Text.Alignment.valueOf(this) }.getOrDefault(LayerType.Text.Alignment.Start) } private fun PositionSnapshot.toDomain(): LayerPosition = LayerPosition( scale = scale, rotation = rotation, isFlippedHorizontally = isFlippedHorizontally, isFlippedVertically = isFlippedVertically, offsetX = offsetX, offsetY = offsetY, alpha = alpha, currentCanvasSize = IntegerSize( width = canvasWidth, height = canvasHeight ), coerceToBounds = coerceToBounds, isVisible = isVisible ) private fun OutlineSnapshot.toDomain(): Outline = Outline( color = color, width = width ) private fun TextGeometricTransformSnapshot.toDomain(): TextGeometricTransform = TextGeometricTransform( scaleX = scaleX, skewX = skewX ) private fun DropShadowSnapshot.toDomain(): DropShadow = DropShadow( color = color, offsetX = offsetX, offsetY = offsetY, blurRadius = blurRadius ) private fun FontType.toSnapshot( assetRegistry: AssetRegistry, prefix: String ): FontSnapshot = when (this) { is FontType.File -> { val source = path val fileName = File(path).name.takeIf(String::isNotBlank) ?: "$prefix.ttf" val entryName = assetRegistry.register( source = source, proposedEntryName = "assets/fonts/$prefix/$fileName" ) FontSnapshot( type = FontSnapshotType.File, path = path, assetPath = entryName, filename = fileName ) } is FontType.Resource -> { val source = "android.resource://${appContext.packageName}/$resId" val entryName = assetRegistry.register( source = source, proposedEntryName = "assets/fonts/$prefix/resource-$resId.font" ) FontSnapshot( type = FontSnapshotType.Resource, resourceId = resId, resourceName = runCatching { appContext.resources.getResourceEntryName(resId) }.getOrNull(), familyKey = asUi() .takeIf { (it.type as? FontType.Resource)?.resId == resId } ?.asDomain() ?.asString(), assetPath = entryName, filename = "resource-$resId.font" ) } } private suspend fun FontSnapshot.toDomain( extractionDir: File ): FontType? = when (type) { FontSnapshotType.File -> path ?.takeIf { File(it).exists() } ?.let { FontType.File(it) } ?: restoreFontFromAsset(extractionDir) FontSnapshotType.Resource -> familyKey ?.let { DomainFontFamily.fromString(familyKey)?.asFontType() } ?: resourceName ?.let(::resolveResourceByKnownFonts) ?.let { FontType.Resource(it) } ?: resourceId ?.takeIf(::isValidFontResource) ?.let { FontType.Resource(it) } ?: restoreFontFromAsset(extractionDir) } private fun Any.toPersistableSource(): String = when (this) { is Uri -> toString() is File -> toUri().toString() else -> toString() } private fun assetEntryName( prefix: String, extension: String ): String = "assets/$prefix.$extension" private fun sourceExtension( source: String ): String = source.toUri().extension() ?.takeIf(String::isNotBlank) ?: source.substringAfterLast('.', "") .takeIf(String::isNotBlank) ?: "png" private suspend fun FontSnapshot.restoreFontFromAsset( extractionDir: File ): FontType.File? { val fontAsset = assetPath ?.let { File(extractionDir, it) } ?.takeIf(File::exists) ?: return null val preferredFilename = filename ?.let { File(it).name } ?.takeIf(String::isNotBlank) val existingFont = settingsManager.settingsState.value.customFonts .firstOrNull { custom -> val file = File(custom.filePath) file.exists() && preferredFilename != null && file.name.equals( preferredFilename, ignoreCase = true ) } ?.filePath ?.let(FontType::File) if (existingFont != null) return existingFont return settingsManager.importCustomFont(fontAsset.toUri().toString()) ?.let { imported -> FontType.File(imported.filePath) } ?: FontType.File(fontAsset.absolutePath) } private fun resolveResourceByKnownFonts( resourceName: String ): Int? = UiFontFamily.defaultEntries .mapNotNull { (it.type as? FontType.Resource)?.resId } .firstOrNull { resId -> runCatching { appContext.resources.getResourceEntryName(resId) == resourceName }.getOrDefault(false) } private fun isValidFontResource( resourceId: Int ): Boolean = runCatching { appContext.resources.getResourceTypeName(resourceId) == "font" }.getOrDefault(false) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectConstants.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.project const val MarkupProjectExtension = "itp" const val MarkupProjectJsonEntry = "project.json" const val MarkupProjectVersion = 1 ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectExtensions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.project import android.net.Uri import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.filename fun Uri.isMarkupProject(): Boolean { val name = filename(appContext).orEmpty() val uri = toString() return name.isMarkupProjectFilename() || uri.isMarkupProjectFilename() } private fun String.isMarkupProjectFilename(): Boolean { val value = lowercase() return value.endsWith(".$MarkupProjectExtension") || value.endsWith(".$MarkupProjectExtension.zip") } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/MarkupProjectFile.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.project import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode data class MarkupProjectFile( val version: Int = MarkupProjectVersion, val background: BackgroundSnapshot, val layers: List, val lastLayers: List, val undoneLayers: List, val history: List = emptyList(), val redoHistory: List = emptyList(), ) data class EditorSnapshot( val background: BackgroundSnapshot, val layers: List, ) data class BackgroundSnapshot( val type: BackgroundType, val assetPath: String? = null, val width: Int? = null, val height: Int? = null, val color: Int? = null, ) data class LayerSnapshot( val type: LayerSnapshotType, val position: PositionSnapshot, val contentWidth: Int? = null, val contentHeight: Int? = null, val visibleLineCount: Int? = null, val cornerRadiusPercent: Int = 0, val isLocked: Boolean = false, val blendingMode: Int = BlendingMode.SrcOver.value, val text: TextSnapshot? = null, val picture: PictureSnapshot? = null, val shape: ShapeSnapshot? = null, val groupedLayers: List = emptyList(), ) data class PositionSnapshot( val scale: Float, val rotation: Float, val isFlippedHorizontally: Boolean = false, val isFlippedVertically: Boolean = false, val offsetX: Float, val offsetY: Float, val alpha: Float, val canvasWidth: Int, val canvasHeight: Int, val coerceToBounds: Boolean, val isVisible: Boolean, ) data class TextSnapshot( val color: Int, val size: Float, val font: FontSnapshot?, val backgroundColor: Int, val text: String, val decorations: List, val outline: OutlineSnapshot?, val alignment: String, val geometricTransform: TextGeometricTransformSnapshot? = null, val shadow: DropShadowSnapshot? = null, ) data class PictureSnapshot( val assetPath: String? = null, val value: String? = null, val shadow: DropShadowSnapshot? = null, ) data class ShapeSnapshot( val modeName: String? = null, val modeOrdinal: Int = 0, val color: Int, val strokeWidth: Float, val widthRatio: Float, val heightRatio: Float, val fillColor: Int? = null, val rotationDegrees: Int? = null, val cornerRadius: Float? = null, val vertices: Int? = null, val isRegular: Boolean? = null, val innerRadiusRatio: Float? = null, val sizeScale: Float? = null, val angle: Float? = null, val shadow: DropShadowSnapshot? = null, ) data class FontSnapshot( val type: FontSnapshotType, val resourceId: Int? = null, val path: String? = null, val resourceName: String? = null, val familyKey: String? = null, val assetPath: String? = null, val filename: String? = null, ) data class OutlineSnapshot( val color: Int, val width: Float, ) data class TextGeometricTransformSnapshot( val scaleX: Float = 1f, val skewX: Float = 0f, ) data class DropShadowSnapshot( val color: Int, val offsetX: Float = 0f, val offsetY: Float = 0f, val blurRadius: Float = 0f, ) enum class BackgroundType { None, Image, Color } enum class LayerSnapshotType { Text, Image, Sticker, Shape } enum class FontSnapshotType { File, Resource } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/project/ProjectFileLoadResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.project import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProjectResult internal sealed interface ProjectFileLoadResult { data class Success( val projectFile: MarkupProjectFile ) : ProjectFileLoadResult data class Error( val error: MarkupProjectResult.Error ) : ProjectFileLoadResult } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/LayersRenderer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.utils import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.graphics.Path import android.graphics.RectF import android.text.Layout import android.text.StaticLayout import android.text.TextPaint import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.withSave import coil3.ImageLoader import coil3.request.ImageRequest import coil3.request.allowHardware import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.image.utils.static import com.t8rin.imagetoolbox.core.data.image.utils.toPaint import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayer import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.math.absoluteValue import kotlin.math.ceil import kotlin.math.min import kotlin.math.roundToInt internal class LayersRenderer @Inject constructor( @ApplicationContext private val context: Context, private val imageLoader: ImageLoader, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder { suspend fun render( backgroundImage: Bitmap, layers: List ): Bitmap = withContext(defaultDispatcher) { val resultBitmap = backgroundImage.copy(Bitmap.Config.ARGB_8888, true) val visibleLayers = layers.filter { it.position.isVisible } if (visibleLayers.isEmpty()) return@withContext resultBitmap val targetWidth = resultBitmap.width.toFloat() val targetHeight = resultBitmap.height.toFloat() val authorSize = visibleLayers .asSequence() .map { it.position.currentCanvasSize } .firstOrNull { it.width > 0 && it.height > 0 } val authorWidth = (authorSize?.width ?: resultBitmap.width).toFloat().coerceAtLeast(1f) val authorHeight = (authorSize?.height ?: resultBitmap.height).toFloat().coerceAtLeast(1f) val ratio = min(targetWidth / authorWidth, targetHeight / authorHeight) val canvasOffsetX = (targetWidth - authorWidth * ratio) / 2f val canvasOffsetY = (targetHeight - authorHeight * ratio) / 2f val textFullSize = min(authorWidth, authorHeight).roundToInt().coerceAtLeast(1) val shapeContentInsetPx = context.resources.displayMetrics.density * 4f val pictureCache = mutableMapOf() val textCache = mutableMapOf() val shapeCache = mutableMapOf() resultBitmap.applyCanvas { withSave { translate(canvasOffsetX, canvasOffsetY) scale(ratio, ratio) layers.forEach { layer -> if (!layer.position.isVisible) return@forEach val centerX = authorWidth / 2f + layer.position.offsetX val centerY = authorHeight / 2f + layer.position.offsetY when (val type = layer.type) { is LayerType.Picture -> { val contentBitmap = pictureCache.getOrPut(type.imageData) { loadPictureBitmap( imageData = type.imageData ) } ?: return@forEach val pictureData = resolvePictureRenderData( bitmap = contentBitmap, shadow = type.shadow, contentSize = layer.contentSize, maxWidth = authorWidth / 2f, maxHeight = authorHeight / 2f ) ?: return@forEach val shadowRenderData = buildPictureShadowRenderData( sourceBitmap = contentBitmap, shadow = type.shadow, targetWidth = pictureData.contentWidth, targetHeight = pictureData.contentHeight, cornerRadiusPercent = layer.cornerRadiusPercent, rasterScale = resolveShadowRasterScale( layerScale = layer.position.scale ) ) drawPictureLayer( bitmap = contentBitmap, data = pictureData, shadowRenderData = shadowRenderData, centerX = centerX, centerY = centerY, rotation = layer.position.rotation, scale = layer.position.scale, isFlippedHorizontally = layer.position.isFlippedHorizontally, isFlippedVertically = layer.position.isFlippedVertically, cornerRadiusPercent = layer.cornerRadiusPercent, blendingMode = layer.blendingMode, alpha = (layer.position.alpha * 255).roundToInt().coerceIn(0, 255) ) } is LayerType.Text -> { val shadowRasterScale = resolveShadowRasterScale( layerScale = layer.position.scale ) val textData = textCache.getOrPut( TextLayerCacheKey( type = type, textFullSize = textFullSize, contentSize = layer.contentSize, maxLines = layer.visibleLineCount, shadowRasterScaleKey = (shadowRasterScale * 100f).roundToInt() ) ) { buildTextLayerRenderData( type = type, textFullSize = textFullSize, contentSize = layer.contentSize, fallbackMaxTextBoxWidth = authorWidth, maxLines = layer.visibleLineCount, shadowRasterScale = shadowRasterScale ) } drawTextLayer( data = textData, centerX = centerX, centerY = centerY, rotation = layer.position.rotation, scale = layer.position.scale, isFlippedHorizontally = layer.position.isFlippedHorizontally, isFlippedVertically = layer.position.isFlippedVertically, cornerRadiusPercent = layer.cornerRadiusPercent, blendingMode = layer.blendingMode, alpha = (layer.position.alpha * 255).roundToInt().coerceIn(0, 255) ) } is LayerType.Shape -> { val shadowRasterScale = resolveShadowRasterScale( layerScale = layer.position.scale ) val shapeValue = shapeCache.getOrPut( ShapeLayerCacheKey( type = type, referenceSize = textFullSize, contentSize = layer.contentSize, shadowRasterScaleKey = (shadowRasterScale * 100f).roundToInt(), contentInsetKey = shapeContentInsetPx.roundToInt() ) ) { val data = resolveShapeLayerRenderData( type = type, referenceSize = textFullSize.toFloat(), contentSize = layer.contentSize, maxWidth = authorWidth / 2f, maxHeight = authorHeight / 2f, contentInsetPx = shapeContentInsetPx ) ShapeLayerCacheValue( data = data, shadowRenderData = buildShapeShadowRenderData( type = type, data = data, rasterScale = shadowRasterScale ) ) } drawShapeLayerItem( type = type, data = shapeValue.data, shadowRenderData = shapeValue.shadowRenderData, centerX = centerX, centerY = centerY, rotation = layer.position.rotation, scale = layer.position.scale, isFlippedHorizontally = layer.position.isFlippedHorizontally, isFlippedVertically = layer.position.isFlippedVertically, blendingMode = layer.blendingMode, alpha = (layer.position.alpha * 255).roundToInt().coerceIn(0, 255) ) } } } } } resultBitmap } private suspend fun loadPictureBitmap( imageData: Any ): Bitmap? = imageLoader.execute( ImageRequest.Builder(appContext) .data(imageData) .static() .allowHardware(false) .size(1600) .build() ).image?.toBitmap() private fun resolvePictureRenderData( bitmap: Bitmap, shadow: com.t8rin.imagetoolbox.feature.markup_layers.domain.DropShadow?, contentSize: IntegerSize, maxWidth: Float, maxHeight: Float ): PictureLayerRenderData? { val shadowPadding = calculateShadowPadding(shadow) val horizontalShadowPadding = shadowPadding.leftPx + shadowPadding.rightPx val verticalShadowPadding = shadowPadding.topPx + shadowPadding.bottomPx contentSize.takeIf { it.width > 0 && it.height > 0 }?.let { val width = it.width.toFloat().coerceAtLeast(horizontalShadowPadding + 1f) val height = it.height.toFloat().coerceAtLeast(verticalShadowPadding + 1f) return PictureLayerRenderData( width = width, height = height, contentLeft = shadowPadding.leftPx, contentTop = shadowPadding.topPx, contentWidth = (width - horizontalShadowPadding).coerceAtLeast(1f), contentHeight = (height - verticalShadowPadding).coerceAtLeast(1f) ) } val width = bitmap.width.takeIf { it > 0 } ?: return null val height = bitmap.height.takeIf { it > 0 } ?: return null val availableContentWidth = (maxWidth - horizontalShadowPadding).coerceAtLeast(1f) val availableContentHeight = (maxHeight - verticalShadowPadding).coerceAtLeast(1f) val fitScale = min( 1f, min(availableContentWidth / width, availableContentHeight / height) ) val contentWidth = (width * fitScale).roundToInt().coerceAtLeast(1).toFloat() val contentHeight = (height * fitScale).roundToInt().coerceAtLeast(1).toFloat() return PictureLayerRenderData( width = contentWidth + horizontalShadowPadding, height = contentHeight + verticalShadowPadding, contentLeft = shadowPadding.leftPx, contentTop = shadowPadding.topPx, contentWidth = contentWidth, contentHeight = contentHeight ) } private fun buildTextLayerRenderData( type: LayerType.Text, textFullSize: Int, contentSize: IntegerSize, fallbackMaxTextBoxWidth: Float, maxLines: Int?, shadowRasterScale: Float ): TextLayerRenderData { val textMetrics = context.calculateTextLayerMetrics( type = type, textFullSize = textFullSize ) val outlineWidth = type.outline?.width ?: 0f val layoutText = type.text.ifEmpty { " " } val resolvedContentSize = contentSize.takeIf { it.width > 0 && it.height > 0 } val availableLayoutWidth = ( (resolvedContentSize?.width?.toFloat() ?: fallbackMaxTextBoxWidth) - textMetrics.padding.leftPx - textMetrics.padding.rightPx - outlineWidth * 2f ).roundToInt().coerceAtLeast(1) val fillPaint = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG).apply { color = type.color textSize = textMetrics.fontSizePx hinting = Paint.HINTING_ON isLinearText = true isUnderlineText = type.decorations.any { it == LayerType.Text.Decoration.Underline } isStrikeThruText = type.decorations.any { it == LayerType.Text.Decoration.LineThrough } typeface = textMetrics.typeface textScaleX = type.geometricTransform?.scaleX?.coerceAtLeast(0.01f) ?: 1f textSkewX = type.geometricTransform?.skewX ?: 0f } val desiredLayoutWidth = maxLineWidth( text = layoutText, paint = fillPaint ).coerceAtLeast(1) val layoutWidth = resolvedContentSize?.let { availableLayoutWidth } ?: min(desiredLayoutWidth, availableLayoutWidth) val alignment = when (type.alignment) { LayerType.Text.Alignment.Start -> Layout.Alignment.ALIGN_NORMAL LayerType.Text.Alignment.Center -> Layout.Alignment.ALIGN_CENTER LayerType.Text.Alignment.End -> Layout.Alignment.ALIGN_OPPOSITE } val fillLayout = createTextStaticLayout( text = layoutText, paint = fillPaint, width = layoutWidth, alignment = alignment, lineHeightPx = textMetrics.lineHeightPx, maxLines = maxLines ) val shadowRenderData = buildTextShadowRenderData( type = type, textMetrics = textMetrics, layoutWidth = layoutWidth, maxLines = maxLines, rasterScale = shadowRasterScale ) val bitmapWidth = resolvedContentSize?.width?.coerceAtLeast(1) ?: ceil( layoutWidth + textMetrics.padding.leftPx + textMetrics.padding.rightPx + outlineWidth * 2f ).toInt().coerceAtLeast(1) val bitmapHeight = resolvedContentSize?.height?.coerceAtLeast(1) ?: ceil( fillLayout.height + textMetrics.padding.topPx + textMetrics.padding.bottomPx + outlineWidth * 2f ).toInt().coerceAtLeast(1) val outlineLayout = type.outline?.takeIf { it.width > 0f }?.let { outline -> val outlinePaint = TextPaint(fillPaint).apply { color = outline.color style = Paint.Style.STROKE strokeWidth = outline.width strokeJoin = Paint.Join.ROUND strokeCap = Paint.Cap.ROUND clearShadowLayer() } createTextStaticLayout( text = layoutText, paint = outlinePaint, width = layoutWidth, alignment = alignment, lineHeightPx = textMetrics.lineHeightPx, maxLines = maxLines ) } return TextLayerRenderData( width = bitmapWidth.toFloat(), height = bitmapHeight.toFloat(), backgroundPaint = type.backgroundColor.takeIf { it != 0 }?.let { backgroundColor -> Paint(Paint.ANTI_ALIAS_FLAG).apply { color = backgroundColor } }, textLeft = outlineWidth + textMetrics.padding.leftPx, textTop = outlineWidth + textMetrics.padding.topPx, shadowRenderData = shadowRenderData, outlineLayout = outlineLayout, fillLayout = fillLayout ) } private fun Canvas.drawTextLayer( data: TextLayerRenderData, centerX: Float, centerY: Float, rotation: Float, scale: Float, isFlippedHorizontally: Boolean, isFlippedVertically: Boolean, cornerRadiusPercent: Int, blendingMode: BlendingMode, alpha: Int ) { withSave { translate(centerX, centerY) rotate(rotation) scale( scale * if (isFlippedHorizontally) -1f else 1f, scale * if (isFlippedVertically) -1f else 1f ) val destination = RectF( -data.width / 2f, -data.height / 2f, data.width / 2f, data.height / 2f ) clipToRoundedBounds( bounds = destination, cornerRadiusPx = cornerRadiusPx( cornerRadiusPercent = cornerRadiusPercent, width = data.width, height = data.height ) ) if (blendingMode == BlendingMode.SrcOver && alpha >= 255) { drawTextLayerContent( data = data, bounds = destination ) } else { drawBitmap( renderTextLayerBitmap( data = data, cornerRadiusPercent = cornerRadiusPercent ), null, destination, blendingMode.toPaint().apply { this.alpha = alpha isFilterBitmap = true } ) } } } private fun Canvas.drawTextLayerContent( data: TextLayerRenderData, bounds: RectF ) { withSave { translate(bounds.left, bounds.top) data.backgroundPaint?.let { drawRect( 0f, 0f, data.width, data.height, it ) } data.shadowRenderData?.let { shadow -> withSave { val rasterScale = shadow.rasterScale.coerceAtLeast(1f) scale(1f / rasterScale, 1f / rasterScale) drawBitmap( shadow.bitmap, data.textLeft * rasterScale + shadow.left, data.textTop * rasterScale + shadow.top, Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } ) } } withSave { translate(data.textLeft, data.textTop) data.outlineLayout?.draw(this@drawTextLayerContent) data.fillLayout.draw(this@drawTextLayerContent) } } } private fun Canvas.drawPictureLayer( bitmap: Bitmap, data: PictureLayerRenderData, shadowRenderData: PictureShadowRenderData?, centerX: Float, centerY: Float, rotation: Float, scale: Float, isFlippedHorizontally: Boolean, isFlippedVertically: Boolean, cornerRadiusPercent: Int, blendingMode: BlendingMode, alpha: Int ) { withSave { translate(centerX, centerY) rotate(rotation) scale( scale * if (isFlippedHorizontally) -1f else 1f, scale * if (isFlippedVertically) -1f else 1f ) val destination = RectF( -data.width / 2f, -data.height / 2f, data.width / 2f, data.height / 2f ) if (blendingMode == BlendingMode.SrcOver && alpha >= 255) { drawPictureLayerContent( bitmap = bitmap, data = data, bounds = destination, shadowRenderData = shadowRenderData, cornerRadiusPercent = cornerRadiusPercent ) } else { drawBitmap( renderPictureLayerBitmap( bitmap = bitmap, data = data, shadowRenderData = shadowRenderData, cornerRadiusPercent = cornerRadiusPercent ), null, destination, blendingMode.toPaint().apply { this.alpha = alpha isFilterBitmap = true } ) } } } private fun Canvas.drawPictureLayerContent( bitmap: Bitmap, data: PictureLayerRenderData, bounds: RectF, shadowRenderData: PictureShadowRenderData?, cornerRadiusPercent: Int ) { withSave { translate(bounds.left, bounds.top) shadowRenderData?.let { shadow -> withSave { val rasterScale = shadow.rasterScale.coerceAtLeast(1f) scale(1f / rasterScale, 1f / rasterScale) drawBitmap( shadow.bitmap, data.contentLeft * rasterScale + shadow.left, data.contentTop * rasterScale + shadow.top, Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } ) } } val imageBounds = RectF( data.contentLeft, data.contentTop, data.contentLeft + data.contentWidth, data.contentTop + data.contentHeight ) withSave { clipToRoundedBounds( bounds = imageBounds, cornerRadiusPx = cornerRadiusPx( cornerRadiusPercent = cornerRadiusPercent, width = imageBounds.width(), height = imageBounds.height() ) ) drawBitmap( bitmap, null, imageBounds, Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } ) } } } private fun Canvas.drawShapeLayerItem( type: LayerType.Shape, data: ShapeLayerRenderData, shadowRenderData: PictureShadowRenderData?, centerX: Float, centerY: Float, rotation: Float, scale: Float, isFlippedHorizontally: Boolean, isFlippedVertically: Boolean, blendingMode: BlendingMode, alpha: Int ) { withSave { translate(centerX, centerY) rotate(rotation) scale( scale * if (isFlippedHorizontally) -1f else 1f, scale * if (isFlippedVertically) -1f else 1f ) val destination = RectF( -data.width / 2f, -data.height / 2f, data.width / 2f, data.height / 2f ) if (blendingMode == BlendingMode.SrcOver && alpha >= 255) { drawShapeLayerContent( type = type, data = data, bounds = destination, shadowRenderData = shadowRenderData ) } else { drawBitmap( renderShapeLayerBitmap( type = type, data = data, shadowRenderData = shadowRenderData ), null, destination, blendingMode.toPaint().apply { this.alpha = alpha isFilterBitmap = true } ) } } } private fun Canvas.drawShapeLayerContent( type: LayerType.Shape, data: ShapeLayerRenderData, bounds: RectF, shadowRenderData: PictureShadowRenderData? ) { withSave { translate(bounds.left, bounds.top) shadowRenderData?.let { shadow -> withSave { val rasterScale = shadow.rasterScale.coerceAtLeast(1f) scale(1f / rasterScale, 1f / rasterScale) drawBitmap( shadow.bitmap, data.contentLeft * rasterScale + shadow.left, data.contentTop * rasterScale + shadow.top, Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } ) } } withSave { translate(data.contentLeft, data.contentTop) drawShapeLayer( type = type, data = data.copy( width = data.contentWidth, height = data.contentHeight, contentLeft = 0f, contentTop = 0f ) ) } } } private fun renderTextLayerBitmap( data: TextLayerRenderData, cornerRadiusPercent: Int ): Bitmap = createLayerBitmap( width = data.width, height = data.height ) { canvas -> val bounds = RectF( 0f, 0f, data.width, data.height ) canvas.withSave { clipToRoundedBounds( bounds = bounds, cornerRadiusPx = cornerRadiusPx( cornerRadiusPercent = cornerRadiusPercent, width = data.width, height = data.height ) ) drawTextLayerContent( data = data, bounds = bounds ) } } private fun renderPictureLayerBitmap( bitmap: Bitmap, data: PictureLayerRenderData, shadowRenderData: PictureShadowRenderData?, cornerRadiusPercent: Int ): Bitmap = createLayerBitmap( width = data.width, height = data.height ) { canvas -> canvas.drawPictureLayerContent( bitmap = bitmap, data = data, bounds = RectF( 0f, 0f, data.width, data.height ), shadowRenderData = shadowRenderData, cornerRadiusPercent = cornerRadiusPercent ) } private fun renderShapeLayerBitmap( type: LayerType.Shape, data: ShapeLayerRenderData, shadowRenderData: PictureShadowRenderData? ): Bitmap = createLayerBitmap( width = data.width, height = data.height ) { canvas -> canvas.drawShapeLayerContent( type = type, data = data, bounds = RectF( 0f, 0f, data.width, data.height ), shadowRenderData = shadowRenderData ) } private inline fun createLayerBitmap( width: Float, height: Float, draw: (Canvas) -> Unit ): Bitmap = createBitmap( ceil(width.toDouble()).toInt().coerceAtLeast(1), ceil(height.toDouble()).toInt().coerceAtLeast(1) ).apply { draw(Canvas(this)) } private fun resolveShadowRasterScale( layerScale: Float ): Float = layerScale .absoluteValue .coerceAtLeast(1f) .coerceAtMost(MAX_SHADOW_RASTER_SCALE) private companion object { const val MAX_SHADOW_RASTER_SCALE = 4f } private fun maxLineWidth( text: String, paint: TextPaint ): Int = text .split('\n') .maxOfOrNull { line -> ceil(Layout.getDesiredWidth(line.ifEmpty { " " }, paint).toDouble()).toInt() } ?.coerceAtLeast(1) ?: 1 private fun cornerRadiusPx( cornerRadiusPercent: Int, width: Float, height: Float ): Float { val normalizedPercent = cornerRadiusPercent.coerceIn(0, 50) if (normalizedPercent == 0) return 0f return min(width, height) * (normalizedPercent / 100f) } private fun Canvas.clipToRoundedBounds( bounds: RectF, cornerRadiusPx: Float ) { if (cornerRadiusPx <= 0f) return clipPath( Path().apply { addRoundRect( bounds, cornerRadiusPx, cornerRadiusPx, Path.Direction.CW ) } ) } } private data class TextLayerRenderData( val width: Float, val height: Float, val backgroundPaint: Paint?, val textLeft: Float, val textTop: Float, val shadowRenderData: TextShadowRenderData?, val outlineLayout: StaticLayout?, val fillLayout: StaticLayout ) private data class TextLayerCacheKey( val type: LayerType.Text, val textFullSize: Int, val contentSize: IntegerSize, val maxLines: Int?, val shadowRasterScaleKey: Int ) private data class PictureLayerRenderData( val width: Float, val height: Float, val contentLeft: Float, val contentTop: Float, val contentWidth: Float, val contentHeight: Float ) private data class ShapeLayerCacheKey( val type: LayerType.Shape, val referenceSize: Int, val contentSize: IntegerSize, val shadowRasterScaleKey: Int, val contentInsetKey: Int ) private data class ShapeLayerCacheValue( val data: ShapeLayerRenderData, val shadowRenderData: PictureShadowRenderData? ) ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/PictureLayerShadowRenderer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.utils import android.graphics.Bitmap import android.graphics.BlurMaskFilter import android.graphics.Paint import android.graphics.Path import android.graphics.PorterDuff import android.graphics.RectF import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.withSave import com.t8rin.imagetoolbox.feature.markup_layers.domain.DropShadow import kotlin.math.min import kotlin.math.roundToInt internal data class PictureShadowRenderData( val bitmap: Bitmap, val left: Float, val top: Float, val rasterScale: Float ) internal fun buildPictureShadowRenderData( sourceBitmap: Bitmap, shadow: DropShadow?, targetWidth: Float, targetHeight: Float, cornerRadiusPercent: Int = 0, rasterScale: Float = 1f ): PictureShadowRenderData? { shadow ?: return null val safeRasterScale = rasterScale.coerceAtLeast(1f) val safeTargetWidth = (targetWidth .coerceAtLeast(1f) * safeRasterScale) .roundToInt() .coerceAtLeast(1) val safeTargetHeight = (targetHeight .coerceAtLeast(1f) * safeRasterScale) .roundToInt() .coerceAtLeast(1) val targetRect = RectF(0f, 0f, safeTargetWidth.toFloat(), safeTargetHeight.toFloat()) val cornerRadiusPx = calculatePictureCornerRadiusPx( cornerRadiusPercent = cornerRadiusPercent, width = targetRect.width(), height = targetRect.height() ) val contentBitmap = createBitmap( width = safeTargetWidth, height = safeTargetHeight ).applyCanvas { withSave { if (cornerRadiusPx > 0f) { clipPath( Path().apply { addRoundRect( targetRect, cornerRadiusPx, cornerRadiusPx, Path.Direction.CW ) } ) } drawBitmap( sourceBitmap, null, targetRect, Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } ) } } val blurRadius = shadow.blurRadius.coerceAtLeast(0f) * safeRasterScale val offset = IntArray(2) val alphaBitmap = contentBitmap.extractAlpha( Paint(Paint.ANTI_ALIAS_FLAG).apply { if (blurRadius > 0f) { maskFilter = BlurMaskFilter( blurRadius, BlurMaskFilter.Blur.NORMAL ) } }, offset ) val tintedBitmap = createBitmap( width = alphaBitmap.width.coerceAtLeast(1), height = alphaBitmap.height.coerceAtLeast(1) ).applyCanvas { drawBitmap( alphaBitmap, 0f, 0f, Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } ) drawColor(shadow.color, PorterDuff.Mode.SRC_IN) } contentBitmap.recycle() alphaBitmap.recycle() return PictureShadowRenderData( bitmap = tintedBitmap, left = offset[0].toFloat() + shadow.offsetX * safeRasterScale, top = offset[1].toFloat() + shadow.offsetY * safeRasterScale, rasterScale = safeRasterScale ) } private fun calculatePictureCornerRadiusPx( cornerRadiusPercent: Int, width: Float, height: Float ): Float { val normalizedPercent = cornerRadiusPercent.coerceIn(0, 50) if (normalizedPercent == 0) return 0f return min(width, height) * (normalizedPercent / 100f) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/ShapeLayerRenderer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.utils import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Matrix import android.graphics.Paint import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.graphics.drawscope.Stroke import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.domain.ShapeMode import com.t8rin.imagetoolbox.feature.markup_layers.domain.arrowAngle import com.t8rin.imagetoolbox.feature.markup_layers.domain.arrowSizeScale import com.t8rin.imagetoolbox.feature.markup_layers.domain.cornerRadius import com.t8rin.imagetoolbox.feature.markup_layers.domain.innerRadiusRatio import com.t8rin.imagetoolbox.feature.markup_layers.domain.isFilledShapeMode import com.t8rin.imagetoolbox.feature.markup_layers.domain.isOutlinedShapeMode import com.t8rin.imagetoolbox.feature.markup_layers.domain.isRegular import com.t8rin.imagetoolbox.feature.markup_layers.domain.outlinedFillColorInt import com.t8rin.imagetoolbox.feature.markup_layers.domain.rotationDegrees import com.t8rin.imagetoolbox.feature.markup_layers.domain.usesStrokeWidth import com.t8rin.imagetoolbox.feature.markup_layers.domain.vertices import kotlin.math.abs import kotlin.math.cos import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt import kotlin.math.sin internal data class ShapeLayerRenderData( val width: Float, val height: Float, val contentLeft: Float, val contentTop: Float, val contentWidth: Float, val contentHeight: Float, val shapeWidth: Float, val shapeHeight: Float, val shapeTranslateX: Float, val shapeTranslateY: Float ) internal fun resolveShapeLayerRenderData( type: LayerType.Shape, referenceSize: Float, contentSize: IntegerSize = IntegerSize.Zero, maxWidth: Float = Float.POSITIVE_INFINITY, maxHeight: Float = Float.POSITIVE_INFINITY, contentInsetPx: Float = 0f ): ShapeLayerRenderData { val shadowPadding = calculateShadowPadding(type.shadow) val horizontalShadowPadding = shadowPadding.leftPx + shadowPadding.rightPx val verticalShadowPadding = shadowPadding.topPx + shadowPadding.bottomPx val desiredShapeWidth = (referenceSize * type.widthRatio).coerceAtLeast(1f) val desiredShapeHeight = (referenceSize * type.heightRatio).coerceAtLeast(1f) val constrainedSize = contentSize.takeIf { it.width > 0 && it.height > 0 } val availableContentWidth = constrainedSize?.width?.toFloat()?.let { (it - horizontalShadowPadding).coerceAtLeast(1f) } ?: maxWidth .takeIf(Float::isFinite) ?.let { (it - horizontalShadowPadding).coerceAtLeast(1f) } ?: Float.POSITIVE_INFINITY val availableContentHeight = constrainedSize?.height?.toFloat()?.let { (it - verticalShadowPadding).coerceAtLeast(1f) } ?: maxHeight .takeIf(Float::isFinite) ?.let { (it - verticalShadowPadding).coerceAtLeast(1f) } ?: Float.POSITIVE_INFINITY val desiredPlacement = resolveShapePlacement( type = type, shapeWidth = desiredShapeWidth, shapeHeight = desiredShapeHeight, contentInsetPx = contentInsetPx ) val fitScale = min( 1f, min( availableContentWidth / desiredPlacement.contentWidth, availableContentHeight / desiredPlacement.contentHeight ) ).coerceAtLeast(0.01f) val shapeWidth = desiredShapeWidth * fitScale val shapeHeight = desiredShapeHeight * fitScale val placement = if (fitScale == 1f) desiredPlacement else resolveShapePlacement( type = type, shapeWidth = shapeWidth, shapeHeight = shapeHeight, contentInsetPx = contentInsetPx ) val resolvedContentWidth = constrainedSize?.let { (it.width.toFloat() - horizontalShadowPadding).coerceAtLeast(1f) } ?: placement.contentWidth val resolvedContentHeight = constrainedSize?.let { (it.height.toFloat() - verticalShadowPadding).coerceAtLeast(1f) } ?: placement.contentHeight val extraContentLeft = ((resolvedContentWidth - placement.contentWidth) / 2f).coerceAtLeast(0f) val extraContentTop = ((resolvedContentHeight - placement.contentHeight) / 2f).coerceAtLeast(0f) val totalWidth = constrainedSize?.width?.toFloat()?.coerceAtLeast(horizontalShadowPadding + 1f) ?: (placement.contentWidth + horizontalShadowPadding) val totalHeight = constrainedSize?.height?.toFloat()?.coerceAtLeast(verticalShadowPadding + 1f) ?: (placement.contentHeight + verticalShadowPadding) return ShapeLayerRenderData( width = totalWidth, height = totalHeight, contentLeft = shadowPadding.leftPx, contentTop = shadowPadding.topPx, contentWidth = resolvedContentWidth, contentHeight = resolvedContentHeight, shapeWidth = shapeWidth, shapeHeight = shapeHeight, shapeTranslateX = placement.shapeTranslateX + extraContentLeft, shapeTranslateY = placement.shapeTranslateY + extraContentTop ) } internal fun buildShapeShadowRenderData( type: LayerType.Shape, data: ShapeLayerRenderData, rasterScale: Float = 1f ): PictureShadowRenderData? { if (type.shadow == null) return null val scaledData = data.scaleContent(rasterScale) val scaledWidth = scaledData.contentWidth.roundToInt().coerceAtLeast(1) val scaledHeight = scaledData.contentHeight.roundToInt().coerceAtLeast(1) val bitmap = renderShapeBitmap( type = type.copy(strokeWidth = type.strokeWidth * rasterScale), data = scaledData, width = scaledWidth, height = scaledHeight ) return buildPictureShadowRenderData( sourceBitmap = bitmap, shadow = type.shadow, targetWidth = data.contentWidth, targetHeight = data.contentHeight, cornerRadiusPercent = 0, rasterScale = rasterScale ) } internal fun DrawScope.drawShapeLayer( type: LayerType.Shape, data: ShapeLayerRenderData ) { val path = buildPlacedShapePath( type = type, data = data ) if (type.shapeMode.isFilledShapeMode()) { drawPath( path = path, color = type.color.toColor(), style = Fill ) return } if (type.shapeMode.isOutlinedShapeMode()) { type.shapeMode.outlinedFillColorInt()?.let { drawPath( path = path, color = Color(it), style = Fill ) } } drawPath( path = path, color = type.color.toColor(), style = Stroke( width = type.strokeWidth.coerceAtLeast(1f), cap = StrokeCap.Round, join = StrokeJoin.Round ) ) } internal fun Canvas.drawShapeLayer( type: LayerType.Shape, data: ShapeLayerRenderData ) { val path = buildPlacedShapePath( type = type, data = data ).asAndroidPath() if (type.shapeMode.isFilledShapeMode()) { drawPath( path, Paint(Paint.ANTI_ALIAS_FLAG).apply { color = type.color style = Paint.Style.FILL } ) return } if (type.shapeMode.isOutlinedShapeMode()) { type.shapeMode.outlinedFillColorInt()?.let { drawPath( path, Paint(Paint.ANTI_ALIAS_FLAG).apply { color = it style = Paint.Style.FILL } ) } } drawPath( path, Paint(Paint.ANTI_ALIAS_FLAG).apply { color = type.color style = Paint.Style.STROKE strokeWidth = type.strokeWidth.coerceAtLeast(1f) strokeJoin = Paint.Join.ROUND strokeCap = Paint.Cap.ROUND } ) } private fun renderShapeBitmap( type: LayerType.Shape, data: ShapeLayerRenderData, width: Int, height: Int ): Bitmap = createBitmap( width = width.coerceAtLeast(1), height = height.coerceAtLeast(1) ).apply { Canvas(this).drawShapeLayer( type = type, data = data ) } private data class ShapePlacement( val contentWidth: Float, val contentHeight: Float, val shapeTranslateX: Float, val shapeTranslateY: Float ) private fun resolveShapePlacement( type: LayerType.Shape, shapeWidth: Float, shapeHeight: Float, contentInsetPx: Float ): ShapePlacement { val path = buildShapePath( type = type, width = shapeWidth, height = shapeHeight ) val bounds = path.getBounds() val drawPadding = if (type.shapeMode.usesStrokeWidth()) { type.strokeWidth.coerceAtLeast(1f) / 2f + 1f } else { 1f } val visibleLeft = bounds.left - drawPadding val visibleTop = bounds.top - drawPadding val visibleRight = bounds.right + drawPadding val visibleBottom = bounds.bottom + drawPadding return ShapePlacement( contentWidth = (visibleRight - visibleLeft + contentInsetPx * 2f).coerceAtLeast(1f), contentHeight = (visibleBottom - visibleTop + contentInsetPx * 2f).coerceAtLeast(1f), shapeTranslateX = -visibleLeft + contentInsetPx, shapeTranslateY = -visibleTop + contentInsetPx ) } private fun buildPlacedShapePath( type: LayerType.Shape, data: ShapeLayerRenderData ): Path { val path = buildShapePath( type = type, width = data.shapeWidth, height = data.shapeHeight ) if (data.shapeTranslateX == 0f && data.shapeTranslateY == 0f) return path val matrix = Matrix().apply { setTranslate(data.shapeTranslateX, data.shapeTranslateY) } return path.asAndroidPath().apply { transform(matrix) }.asComposePath() } private fun buildShapePath( type: LayerType.Shape, width: Float, height: Float ): Path { val strokeInset = if (type.shapeMode.usesStrokeWidth()) { type.strokeWidth / 2f + 1f } else { 1f } val left = strokeInset.coerceAtMost(width / 2f) val top = strokeInset.coerceAtMost(height / 2f) val right = max(left + 1f, width - strokeInset) val bottom = max(top + 1f, height - strokeInset) return when (val mode = type.shapeMode) { is ShapeMode.Rect, is ShapeMode.OutlinedRect -> { buildRotatedRoundRectPath( left = left, top = top, right = right, bottom = bottom, rotationDegrees = mode.rotationDegrees(), cornerRadius = mode.cornerRadius() ) } ShapeMode.Oval, is ShapeMode.OutlinedOval -> Path().apply { addOval(Rect(left, top, right, bottom)) } ShapeMode.Triangle, is ShapeMode.OutlinedTriangle -> Path().apply { moveTo((left + right) / 2f, top) lineTo(right, bottom) lineTo(left, bottom) close() } is ShapeMode.Polygon, is ShapeMode.OutlinedPolygon -> { buildPolygonPath( left = left, top = top, right = right, bottom = bottom, vertices = mode.vertices(), rotationDegrees = mode.rotationDegrees(), isRegular = mode.isRegular() ) } is ShapeMode.Star, is ShapeMode.OutlinedStar -> { buildStarPath( left = left, top = top, right = right, bottom = bottom, vertices = mode.vertices(), rotationDegrees = mode.rotationDegrees(), innerRadiusRatio = mode.innerRadiusRatio(), isRegular = mode.isRegular() ) } is ShapeMode.Arrow -> { buildFilledArrowPath( left = left, top = top, right = right, bottom = bottom, doubleHeaded = false, sizeScale = mode.arrowSizeScale(), angle = mode.arrowAngle() ) } is ShapeMode.DoubleArrow -> { buildFilledArrowPath( left = left, top = top, right = right, bottom = bottom, doubleHeaded = true, sizeScale = mode.arrowSizeScale(), angle = mode.arrowAngle() ) } ShapeMode.Line, is ShapeMode.LineArrow, is ShapeMode.DoubleLineArrow -> { buildLineArrowPath( left = left, top = top, right = right, bottom = bottom, drawStartArrow = mode is ShapeMode.DoubleLineArrow, drawEndArrow = mode is ShapeMode.LineArrow || mode is ShapeMode.DoubleLineArrow, sizeScale = mode.arrowSizeScale(), angle = mode.arrowAngle() ) } } } private fun buildRotatedRoundRectPath( left: Float, top: Float, right: Float, bottom: Float, rotationDegrees: Int, cornerRadius: Float ): Path { val width = right - left val height = bottom - top val radius = min(width, height) * cornerRadius.coerceIn(0f, 0.5f) val path = Path().apply { addRoundRect( RoundRect( rect = Rect(left, top, right, bottom), radiusX = radius, radiusY = radius ) ) } if (rotationDegrees == 0) return path val matrix = Matrix().apply { setRotate( rotationDegrees.toFloat(), (left + right) / 2f, (top + bottom) / 2f ) } return path.asAndroidPath().apply { transform(matrix) }.asComposePath() } private fun buildPolygonPath( left: Float, top: Float, right: Float, bottom: Float, vertices: Int, rotationDegrees: Int, isRegular: Boolean ): Path { val centerX = (left + right) / 2f val centerY = (top + bottom) / 2f val width = right - left val height = bottom - top val safeVertices = vertices.coerceAtLeast(3) return Path().apply { if (isRegular) { val radius = min(width, height) / 2f val step = 360f / safeVertices val startAngle = rotationDegrees - 90f repeat(safeVertices) { index -> val angle = Math.toRadians((startAngle + index * step).toDouble()) val x = centerX + radius * cos(angle).toFloat() val y = centerY + radius * sin(angle).toFloat() if (index == 0) moveTo(x, y) else lineTo(x, y) } } else { repeat(safeVertices) { index -> val angle = Math.toRadians( (rotationDegrees - 90f + index * (360f / safeVertices)).toDouble() ) val x = centerX + width / 2f * cos(angle).toFloat() val y = centerY + height / 2f * sin(angle).toFloat() if (index == 0) moveTo(x, y) else lineTo(x, y) } } close() } } private fun buildStarPath( left: Float, top: Float, right: Float, bottom: Float, vertices: Int, rotationDegrees: Int, innerRadiusRatio: Float, isRegular: Boolean ): Path { val safeVertices = vertices.coerceAtLeast(3) val centerX = (left + right) / 2f val centerY = (top + bottom) / 2f val width = right - left val height = bottom - top val safeInnerRadiusRatio = innerRadiusRatio.coerceIn(0f, 1f) return Path().apply { if (isRegular) { val outerRadius = min(width, height) / 2f val innerRadius = outerRadius * safeInnerRadiusRatio val step = 360f / (safeVertices * 2) val startAngle = rotationDegrees - 90f repeat(safeVertices * 2) { index -> val radius = if (index % 2 == 0) outerRadius else innerRadius val angle = Math.toRadians((startAngle + index * step).toDouble()) val x = centerX + radius * cos(angle).toFloat() val y = centerY + radius * sin(angle).toFloat() if (index == 0) moveTo(x, y) else lineTo(x, y) } } else { val step = 360f / (safeVertices * 2) val startAngle = rotationDegrees - 90f repeat(safeVertices * 2) { index -> val radiusX = if (index % 2 == 0) width / 2f else width / 2f * safeInnerRadiusRatio val radiusY = if (index % 2 == 0) height / 2f else height / 2f * safeInnerRadiusRatio val angle = Math.toRadians((startAngle + index * step).toDouble()) val x = centerX + radiusX * cos(angle).toFloat() val y = centerY + radiusY * sin(angle).toFloat() if (index == 0) moveTo(x, y) else lineTo(x, y) } } close() } } private fun buildLineArrowPath( left: Float, top: Float, right: Float, bottom: Float, drawStartArrow: Boolean, drawEndArrow: Boolean, sizeScale: Float, angle: Float ): Path { val centerY = (top + bottom) / 2f val length = right - left val headLength = min( length / if (drawStartArrow && drawEndArrow) 3f else 2f, max(bottom - top, 1f) * sizeScale.coerceAtLeast(0.5f) ).coerceAtLeast(1f) return Path().apply { moveTo(left, centerY) lineTo(right, centerY) if (drawEndArrow) { addArrowHead( tip = Offset(right, centerY), directionAngleDegrees = 0f, headLength = headLength, angle = angle ) } if (drawStartArrow) { addArrowHead( tip = Offset(left, centerY), directionAngleDegrees = 180f, headLength = headLength, angle = angle ) } } } private fun Path.addArrowHead( tip: Offset, directionAngleDegrees: Float, headLength: Float, angle: Float ) { val first = directionAngleDegrees + angle val second = directionAngleDegrees - angle moveTo(tip.x, tip.y) lineTo( tip.x + cos(Math.toRadians(first.toDouble())).toFloat() * headLength, tip.y + sin(Math.toRadians(first.toDouble())).toFloat() * headLength ) moveTo(tip.x, tip.y) lineTo( tip.x + cos(Math.toRadians(second.toDouble())).toFloat() * headLength, tip.y + sin(Math.toRadians(second.toDouble())).toFloat() * headLength ) } private fun buildFilledArrowPath( left: Float, top: Float, right: Float, bottom: Float, doubleHeaded: Boolean, sizeScale: Float, angle: Float ): Path { val width = right - left val height = bottom - top val centerY = (top + bottom) / 2f val angleRadians = Math.toRadians(angle.coerceIn(100f, 175f).toDouble()) val normalizedScale = (sizeScale.coerceIn(0.5f, 8f) - 0.5f) / 7.5f val desiredHeadLength = width * (0.22f + normalizedScale * 0.28f) val headBackOffset = abs(cos(angleRadians)).toFloat() * desiredHeadLength val tipHalfHeight = min(height / 2f, abs(sin(angleRadians)).toFloat() * desiredHeadLength) val shaftHalfHeight = min(height * 0.24f, tipHalfHeight * 0.55f).coerceAtLeast(height * 0.12f) return Path().apply { if (doubleHeaded) { val safeHeadOffset = min(headBackOffset, width / 2.5f) val leftBackX = left + safeHeadOffset val rightBackX = right - safeHeadOffset moveTo(left, centerY) lineTo(leftBackX, centerY - tipHalfHeight) lineTo(leftBackX, centerY - shaftHalfHeight) lineTo(rightBackX, centerY - shaftHalfHeight) lineTo(rightBackX, centerY - tipHalfHeight) lineTo(right, centerY) lineTo(rightBackX, centerY + tipHalfHeight) lineTo(rightBackX, centerY + shaftHalfHeight) lineTo(leftBackX, centerY + shaftHalfHeight) lineTo(leftBackX, centerY + tipHalfHeight) } else { val safeHeadOffset = min(headBackOffset, width * 0.6f) val backX = right - safeHeadOffset moveTo(left, centerY - shaftHalfHeight) lineTo(backX, centerY - shaftHalfHeight) lineTo(backX, centerY - tipHalfHeight) lineTo(right, centerY) lineTo(backX, centerY + tipHalfHeight) lineTo(backX, centerY + shaftHalfHeight) lineTo(left, centerY + shaftHalfHeight) } close() } } private fun ShapeLayerRenderData.scaleContent( scale: Float ): ShapeLayerRenderData = copy( width = width * scale, height = height * scale, contentLeft = contentLeft * scale, contentTop = contentTop * scale, contentWidth = contentWidth * scale, contentHeight = contentHeight * scale, shapeWidth = shapeWidth * scale, shapeHeight = shapeHeight * scale, shapeTranslateX = shapeTranslateX * scale, shapeTranslateY = shapeTranslateY * scale ) ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerMetrics.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.utils import android.content.Context import android.graphics.Paint import android.graphics.Typeface import android.text.TextPaint import android.util.TypedValue import com.t8rin.imagetoolbox.core.utils.toTypeface import com.t8rin.imagetoolbox.feature.markup_layers.domain.DropShadow import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import kotlin.math.abs import kotlin.math.ceil internal data class TextLayerPadding( val leftPx: Float, val topPx: Float, val rightPx: Float, val bottomPx: Float ) { companion object { val Zero = TextLayerPadding( leftPx = 0f, topPx = 0f, rightPx = 0f, bottomPx = 0f ) } } internal data class TextLayerMetrics( val fontSizePx: Float, val lineHeightPx: Float, val padding: TextLayerPadding, val typeface: Typeface ) internal fun Context.calculateTextLayerMetrics( type: LayerType.Text, textFullSize: Int ): TextLayerMetrics { val displayMetrics = resources.displayMetrics val baseTextSize = textFullSize.coerceAtLeast(1) * type.size val fontSizePx = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, baseTextSize / 12.5f, displayMetrics ).coerceAtLeast(1f) val typeface = createTextLayerTypeface(type) val lineHeightPx = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG).apply { textSize = fontSizePx hinting = Paint.HINTING_ON isLinearText = true isUnderlineText = type.decorations.any { it == LayerType.Text.Decoration.Underline } isStrikeThruText = type.decorations.any { it == LayerType.Text.Decoration.LineThrough } this.typeface = typeface }.fontMetrics.run { ceil((descent - ascent + leading.coerceAtLeast(0f)).toDouble()) .toFloat() .coerceAtLeast(fontSizePx) } val baseHorizontalPaddingPx = baseTextSize / 10f val baseVerticalPaddingPx = baseTextSize / 12f val shadowPadding = calculateShadowPadding(type.shadow) val geometricTransformPaddingPx = lineHeightPx * abs(type.geometricTransform?.skewX ?: 0f) return TextLayerMetrics( fontSizePx = fontSizePx, lineHeightPx = lineHeightPx, padding = TextLayerPadding( leftPx = baseHorizontalPaddingPx + geometricTransformPaddingPx + shadowPadding.leftPx, topPx = baseVerticalPaddingPx + shadowPadding.topPx, rightPx = baseHorizontalPaddingPx + geometricTransformPaddingPx + shadowPadding.rightPx, bottomPx = baseVerticalPaddingPx + shadowPadding.bottomPx ), typeface = typeface ) } internal fun createTextLayerTypeface(type: LayerType.Text): Typeface { val isBold = type.decorations.any { it == LayerType.Text.Decoration.Bold } val isItalic = type.decorations.any { it == LayerType.Text.Decoration.Italic } val style = when { isBold && isItalic -> Typeface.BOLD_ITALIC isBold -> Typeface.BOLD isItalic -> Typeface.ITALIC else -> Typeface.NORMAL } return Typeface.create(type.font.toTypeface() ?: Typeface.DEFAULT, style) } internal fun calculateShadowPadding( shadow: DropShadow? ): TextLayerPadding { shadow ?: return TextLayerPadding.Zero val blurRadius = shadow.blurRadius.coerceAtLeast(0f) return TextLayerPadding( leftPx = (blurRadius - shadow.offsetX).coerceAtLeast(0f), topPx = (blurRadius - shadow.offsetY).coerceAtLeast(0f), rightPx = (blurRadius + shadow.offsetX).coerceAtLeast(0f), bottomPx = (blurRadius + shadow.offsetY).coerceAtLeast(0f) ) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/data/utils/TextLayerShadowRenderer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.data.utils import android.graphics.Bitmap import android.graphics.BlurMaskFilter import android.graphics.Paint import android.graphics.PorterDuff import android.text.Layout import android.text.StaticLayout import android.text.TextPaint import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.withSave import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import kotlin.math.ceil import kotlin.math.roundToInt internal data class TextShadowRenderData( val bitmap: Bitmap, val left: Float, val top: Float, val rasterScale: Float ) internal fun buildTextShadowRenderData( type: LayerType.Text, textMetrics: TextLayerMetrics, layoutWidth: Int, maxLines: Int?, rasterScale: Float = 1f ): TextShadowRenderData? { val shadow = type.shadow ?: return null val safeRasterScale = rasterScale.coerceAtLeast(1f) val safeLayoutWidth = (layoutWidth.coerceAtLeast(1) * safeRasterScale) .roundToInt() .coerceAtLeast(1) val text = type.text.ifEmpty { " " } val alignment = when (type.alignment) { LayerType.Text.Alignment.Start -> Layout.Alignment.ALIGN_NORMAL LayerType.Text.Alignment.Center -> Layout.Alignment.ALIGN_CENTER LayerType.Text.Alignment.End -> Layout.Alignment.ALIGN_OPPOSITE } val paint = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG).apply { color = android.graphics.Color.BLACK textSize = textMetrics.fontSizePx * safeRasterScale hinting = Paint.HINTING_ON isLinearText = true isUnderlineText = type.decorations.any { it == LayerType.Text.Decoration.Underline } isStrikeThruText = type.decorations.any { it == LayerType.Text.Decoration.LineThrough } typeface = textMetrics.typeface textScaleX = type.geometricTransform?.scaleX?.coerceAtLeast(0.01f) ?: 1f textSkewX = type.geometricTransform?.skewX ?: 0f } val layout = createTextStaticLayout( text = text, paint = paint, width = safeLayoutWidth, alignment = alignment, lineHeightPx = textMetrics.lineHeightPx * safeRasterScale, maxLines = maxLines ) val sourcePadding = shadowSourcePadding( textMetrics = textMetrics, rasterScale = safeRasterScale ) val sourceBitmap = createBitmap( width = ceil(layout.width + sourcePadding.leftPx + sourcePadding.rightPx) .toInt() .coerceAtLeast(1), height = ceil(layout.height + sourcePadding.topPx + sourcePadding.bottomPx) .toInt() .coerceAtLeast(1) ).applyCanvas { withSave { translate(sourcePadding.leftPx, sourcePadding.topPx) layout.draw(this) } } val blurRadius = shadow.blurRadius.coerceAtLeast(0f) * safeRasterScale val offset = IntArray(2) val alphaBitmap = sourceBitmap.extractAlpha( Paint(Paint.ANTI_ALIAS_FLAG).apply { if (blurRadius > 0f) { maskFilter = BlurMaskFilter( blurRadius, BlurMaskFilter.Blur.NORMAL ) } }, offset ) val tintedBitmap = createBitmap( width = alphaBitmap.width.coerceAtLeast(1), height = alphaBitmap.height.coerceAtLeast(1) ).applyCanvas { drawBitmap( alphaBitmap, 0f, 0f, Paint(Paint.ANTI_ALIAS_FLAG).apply { isFilterBitmap = true } ) drawColor(shadow.color, PorterDuff.Mode.SRC_IN) } sourceBitmap.recycle() alphaBitmap.recycle() return TextShadowRenderData( bitmap = tintedBitmap, left = offset[0].toFloat() - sourcePadding.leftPx + shadow.offsetX * safeRasterScale, top = offset[1].toFloat() - sourcePadding.topPx + shadow.offsetY * safeRasterScale, rasterScale = safeRasterScale ) } private fun shadowSourcePadding( textMetrics: TextLayerMetrics, rasterScale: Float ): TextLayerPadding = TextLayerPadding( leftPx = ceil(textMetrics.padding.leftPx * rasterScale), topPx = ceil(textMetrics.padding.topPx * rasterScale), rightPx = ceil(textMetrics.padding.rightPx * rasterScale), bottomPx = ceil(textMetrics.padding.bottomPx * rasterScale) ) internal fun createTextStaticLayout( text: String, paint: TextPaint, width: Int, alignment: Layout.Alignment, lineHeightPx: Float, maxLines: Int? ): StaticLayout { val boundedText = maxLines ?.takeIf { it > 0 } ?.let { linesLimit -> val fullLayout = StaticLayout.Builder .obtain(text, 0, text.length, paint, width) .setAlignment(alignment) .setIncludePad(false) .build() if (fullLayout.lineCount <= linesLimit) { text } else { text.substring(0, fullLayout.getLineEnd(linesLimit - 1)) } } ?: text val naturalLineHeightPx = ceil( (paint.fontMetrics.descent - paint.fontMetrics.ascent).toDouble() ).toFloat() return StaticLayout.Builder .obtain(boundedText, 0, boundedText.length, paint, width) .setAlignment(alignment) .setIncludePad(false) .setLineSpacing((lineHeightPx - naturalLineHeightPx).coerceAtLeast(0f), 1f) .build() } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/di/MarkupLayersModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.markup_layers.data.AndroidMarkupLayersApplier import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayersApplier import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface MarkupLayersModule { @Binds @Singleton fun applier( impl: AndroidMarkupLayersApplier ): MarkupLayersApplier } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.domain import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Outline import com.t8rin.imagetoolbox.core.settings.domain.model.FontType data class MarkupLayer( val type: LayerType, val position: LayerPosition, val contentSize: IntegerSize = IntegerSize.Zero, val visibleLineCount: Int? = null, val cornerRadiusPercent: Int = 0, val isLocked: Boolean = false, val blendingMode: BlendingMode = BlendingMode.SrcOver, val groupedLayers: List = emptyList() ) data class LayerPosition( val scale: Float = 1f, val rotation: Float = 0f, val isFlippedHorizontally: Boolean = false, val isFlippedVertically: Boolean = false, val offsetX: Float = 0f, val offsetY: Float = 0f, val alpha: Float = 1f, val currentCanvasSize: IntegerSize, val coerceToBounds: Boolean, val isVisible: Boolean ) typealias DomainTextDecoration = LayerType.Text.Decoration data class TextGeometricTransform( val scaleX: Float = 1f, val skewX: Float = 0f ) data class DropShadow( val color: Int = 0xFF000000.toInt(), val offsetX: Float = 0f, val offsetY: Float = 6f, val blurRadius: Float = 12f ) { companion object { val Default = DropShadow() val BlurRadiusRange: ClosedFloatingPointRange get() = 0f..100f val OffsetXRange: ClosedFloatingPointRange get() = -64f..64f val OffsetYRange: ClosedFloatingPointRange get() = -64f..64f } } sealed interface LayerType { data class Text( val color: Int, val size: Float, val font: FontType?, val backgroundColor: Int, val text: String, val decorations: List, val outline: Outline?, val alignment: Alignment, val geometricTransform: TextGeometricTransform? = null, val shadow: DropShadow? = null ) : LayerType { enum class Decoration { Bold, Italic, Underline, LineThrough } enum class Alignment { Start, Center, End } companion object { val Default by lazy { Text( color = -16777216, size = 0.5f, font = null, backgroundColor = 0, text = "Text", decorations = listOf(), outline = null, alignment = Alignment.Start, geometricTransform = null, shadow = null ) } } } sealed class Picture( open val imageData: Any, open val shadow: DropShadow? = null ) : LayerType { data class Image( override val imageData: Any, override val shadow: DropShadow? = null ) : Picture( imageData = imageData, shadow = shadow ) data class Sticker( override val imageData: Any, override val shadow: DropShadow? = null ) : Picture( imageData = imageData, shadow = shadow ) } data class Shape( val shapeMode: ShapeMode, val color: Int, val strokeWidth: Float = 16f, val widthRatio: Float = 0.35f, val heightRatio: Float = 0.35f, val shadow: DropShadow? = null ) : LayerType { companion object { val Default by lazy { Shape( shapeMode = ShapeMode.Star(), color = -16777216, strokeWidth = 16f, widthRatio = 0.35f, heightRatio = 0.35f, shadow = null ) } } } } internal fun LayerType.layerCornerRadiusPercent(value: Int): Int = when (this) { is LayerType.Shape -> 0 else -> value.coerceIn(0, 50) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupLayersApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.domain import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable interface MarkupLayersApplier { suspend fun applyToImage( image: I, layers: List ): I suspend fun saveProject( destination: Writeable, project: MarkupProject ) suspend fun openProject( uri: String ): MarkupProjectResult fun clearProjectCache() } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProject.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.domain data class MarkupProject( val background: ProjectBackground, val layers: List, val lastLayers: List, val undoneLayers: List, val history: List = emptyList(), val redoHistory: List = emptyList(), ) data class MarkupProjectHistorySnapshot( val background: ProjectBackground, val layers: List, ) ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/MarkupProjectResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.domain sealed interface MarkupProjectResult { data class Success( val project: MarkupProject ) : MarkupProjectResult sealed class Error( open val message: String ) : MarkupProjectResult { data class InvalidArchive( override val message: String ) : Error(message) data class MissingProjectFile( override val message: String ) : Error(message) data class InvalidProjectFile( override val message: String ) : Error(message) data class UnsupportedVersion( val version: Int, override val message: String ) : Error(message) data class Exception( val throwable: Throwable, override val message: String ) : Error(message) } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ProjectBackground.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.domain sealed class ProjectBackground { data object None : ProjectBackground() data class Image( val uri: String ) : ProjectBackground() data class Color( val width: Int, val height: Int, val color: Int ) : ProjectBackground() } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/domain/ShapeLayerModeExt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.domain sealed interface ShapeMode { val kind: Kind enum class Kind { Line, Arrow, DoubleArrow, LineArrow, DoubleLineArrow, Rect, OutlinedRect, Oval, OutlinedOval, Triangle, OutlinedTriangle, Polygon, OutlinedPolygon, Star, OutlinedStar } object Line : ShapeMode { override val kind: Kind = Kind.Line } data class Arrow( val sizeScale: Float = DEFAULT_ARROW_SIZE_SCALE, val angle: Float = DEFAULT_ARROW_ANGLE ) : ShapeMode { override val kind: Kind = Kind.Arrow } data class DoubleArrow( val sizeScale: Float = DEFAULT_ARROW_SIZE_SCALE, val angle: Float = DEFAULT_ARROW_ANGLE ) : ShapeMode { override val kind: Kind = Kind.DoubleArrow } data class LineArrow( val sizeScale: Float = DEFAULT_ARROW_SIZE_SCALE, val angle: Float = DEFAULT_ARROW_ANGLE ) : ShapeMode { override val kind: Kind = Kind.LineArrow } data class DoubleLineArrow( val sizeScale: Float = DEFAULT_ARROW_SIZE_SCALE, val angle: Float = DEFAULT_ARROW_ANGLE ) : ShapeMode { override val kind: Kind = Kind.DoubleLineArrow } data class Rect( val rotationDegrees: Int = 0, val cornerRadius: Float = DEFAULT_RECT_CORNER_RADIUS ) : ShapeMode { override val kind: Kind = Kind.Rect } data class OutlinedRect( val rotationDegrees: Int = 0, val cornerRadius: Float = DEFAULT_RECT_CORNER_RADIUS, val fillColor: Int? = null ) : ShapeMode { override val kind: Kind = Kind.OutlinedRect } object Oval : ShapeMode { override val kind: Kind = Kind.Oval } data class OutlinedOval( val fillColor: Int? = null ) : ShapeMode { override val kind: Kind = Kind.OutlinedOval } object Triangle : ShapeMode { override val kind: Kind = Kind.Triangle } data class OutlinedTriangle( val fillColor: Int? = null ) : ShapeMode { override val kind: Kind = Kind.OutlinedTriangle } data class Polygon( val vertices: Int = DEFAULT_VERTICES, val rotationDegrees: Int = 0, val isRegular: Boolean = false ) : ShapeMode { override val kind: Kind = Kind.Polygon } data class OutlinedPolygon( val vertices: Int = DEFAULT_VERTICES, val rotationDegrees: Int = 0, val isRegular: Boolean = false, val fillColor: Int? = null ) : ShapeMode { override val kind: Kind = Kind.OutlinedPolygon } data class Star( val vertices: Int = DEFAULT_VERTICES, val rotationDegrees: Int = 0, val innerRadiusRatio: Float = DEFAULT_INNER_RADIUS_RATIO, val isRegular: Boolean = false ) : ShapeMode { override val kind: Kind = Kind.Star } data class OutlinedStar( val vertices: Int = DEFAULT_VERTICES, val rotationDegrees: Int = 0, val innerRadiusRatio: Float = DEFAULT_INNER_RADIUS_RATIO, val isRegular: Boolean = false, val fillColor: Int? = null ) : ShapeMode { override val kind: Kind = Kind.OutlinedStar } companion object { val entries by lazy { listOf( Line, Arrow(), DoubleArrow(), LineArrow(), DoubleLineArrow(), Rect(), OutlinedRect(), Oval, OutlinedOval(), Triangle, OutlinedTriangle(), Polygon(), OutlinedPolygon(), Star(), OutlinedStar() ) } } } private data class ShapeSizePreset( val widthRatio: Float, val heightRatio: Float ) private enum class ShapeGeometryFamily { Line, FilledArrow, RectLike, Triangle, Polygon, Star } internal val ShapeMode.ordinal: Int get() = ShapeMode.entries.indexOfFirst { it.kind == kind } .takeIf { it >= 0 } ?: 0 internal fun ShapeMode.Kind.defaultMode(): ShapeMode = when (this) { ShapeMode.Kind.Line -> ShapeMode.Line ShapeMode.Kind.Arrow -> ShapeMode.Arrow() ShapeMode.Kind.DoubleArrow -> ShapeMode.DoubleArrow() ShapeMode.Kind.LineArrow -> ShapeMode.LineArrow() ShapeMode.Kind.DoubleLineArrow -> ShapeMode.DoubleLineArrow() ShapeMode.Kind.Rect -> ShapeMode.Rect() ShapeMode.Kind.OutlinedRect -> ShapeMode.OutlinedRect() ShapeMode.Kind.Oval -> ShapeMode.Oval ShapeMode.Kind.OutlinedOval -> ShapeMode.OutlinedOval() ShapeMode.Kind.Triangle -> ShapeMode.Triangle ShapeMode.Kind.OutlinedTriangle -> ShapeMode.OutlinedTriangle() ShapeMode.Kind.Polygon -> ShapeMode.Polygon() ShapeMode.Kind.OutlinedPolygon -> ShapeMode.OutlinedPolygon() ShapeMode.Kind.Star -> ShapeMode.Star() ShapeMode.Kind.OutlinedStar -> ShapeMode.OutlinedStar() } internal fun resolveMarkupLayerShapeMode( modeName: String?, modeOrdinal: Int? ): ShapeMode { val modeByName = modeName ?.let { raw -> ShapeMode.Kind.entries.firstOrNull { it.name == raw } } ?.defaultMode() return modeByName ?: modeOrdinal?.let(ShapeMode.entries::getOrNull) ?: ShapeMode.Kind.OutlinedRect.defaultMode() } internal fun ShapeMode.isOutlinedShapeMode(): Boolean = when (this) { is ShapeMode.OutlinedRect, is ShapeMode.OutlinedOval, is ShapeMode.OutlinedTriangle, is ShapeMode.OutlinedPolygon, is ShapeMode.OutlinedStar -> true else -> false } internal fun ShapeMode.isFilledShapeMode(): Boolean = when (this) { is ShapeMode.Rect, ShapeMode.Oval, ShapeMode.Triangle, is ShapeMode.Polygon, is ShapeMode.Star, is ShapeMode.Arrow, is ShapeMode.DoubleArrow -> true else -> false } internal fun ShapeMode.usesStrokeWidth(): Boolean = when (this) { ShapeMode.Line, is ShapeMode.LineArrow, is ShapeMode.DoubleLineArrow, is ShapeMode.OutlinedRect, is ShapeMode.OutlinedOval, is ShapeMode.OutlinedTriangle, is ShapeMode.OutlinedPolygon, is ShapeMode.OutlinedStar -> true else -> false } internal fun ShapeMode.outlinedFillColorInt(): Int? = when (this) { is ShapeMode.OutlinedRect -> fillColor is ShapeMode.OutlinedOval -> fillColor is ShapeMode.OutlinedTriangle -> fillColor is ShapeMode.OutlinedPolygon -> fillColor is ShapeMode.OutlinedStar -> fillColor else -> null } internal fun ShapeMode.withOutlinedFillColor(color: Int?): ShapeMode = when (this) { is ShapeMode.OutlinedRect -> copy(fillColor = color) is ShapeMode.OutlinedOval -> copy(fillColor = color) is ShapeMode.OutlinedTriangle -> copy(fillColor = color) is ShapeMode.OutlinedPolygon -> copy(fillColor = color) is ShapeMode.OutlinedStar -> copy(fillColor = color) else -> this } internal fun ShapeMode.arrowSizeScale(): Float = when (this) { is ShapeMode.Arrow -> sizeScale is ShapeMode.DoubleArrow -> sizeScale is ShapeMode.LineArrow -> sizeScale is ShapeMode.DoubleLineArrow -> sizeScale else -> DEFAULT_ARROW_SIZE_SCALE } internal fun ShapeMode.arrowAngle(): Float = when (this) { is ShapeMode.Arrow -> angle is ShapeMode.DoubleArrow -> angle is ShapeMode.LineArrow -> angle is ShapeMode.DoubleLineArrow -> angle else -> DEFAULT_ARROW_ANGLE } internal fun ShapeMode.vertices(): Int = when (this) { is ShapeMode.Polygon -> vertices is ShapeMode.OutlinedPolygon -> vertices is ShapeMode.Star -> vertices is ShapeMode.OutlinedStar -> vertices else -> DEFAULT_VERTICES } internal fun ShapeMode.rotationDegrees(): Int = when (this) { is ShapeMode.Rect -> rotationDegrees is ShapeMode.OutlinedRect -> rotationDegrees is ShapeMode.Polygon -> rotationDegrees is ShapeMode.OutlinedPolygon -> rotationDegrees is ShapeMode.Star -> rotationDegrees is ShapeMode.OutlinedStar -> rotationDegrees else -> 0 } internal fun ShapeMode.cornerRadius(): Float = when (this) { is ShapeMode.Rect -> cornerRadius is ShapeMode.OutlinedRect -> cornerRadius else -> DEFAULT_RECT_CORNER_RADIUS } internal fun ShapeMode.isRegular(): Boolean = when (this) { is ShapeMode.Polygon -> isRegular is ShapeMode.OutlinedPolygon -> isRegular is ShapeMode.Star -> isRegular is ShapeMode.OutlinedStar -> isRegular else -> false } internal fun ShapeMode.innerRadiusRatio(): Float = when (this) { is ShapeMode.Star -> innerRadiusRatio is ShapeMode.OutlinedStar -> innerRadiusRatio else -> DEFAULT_INNER_RADIUS_RATIO } internal fun ShapeMode.updateArrow( sizeScale: Float? = null, angle: Float? = null ): ShapeMode = when (this) { is ShapeMode.Arrow -> copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) is ShapeMode.DoubleArrow -> copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) is ShapeMode.LineArrow -> copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) is ShapeMode.DoubleLineArrow -> copy( sizeScale = sizeScale ?: this.sizeScale, angle = angle ?: this.angle ) else -> this } internal fun ShapeMode.updateRect( rotationDegrees: Int? = null, cornerRadius: Float? = null ): ShapeMode = when (this) { is ShapeMode.Rect -> copy( rotationDegrees = rotationDegrees ?: this.rotationDegrees, cornerRadius = cornerRadius ?: this.cornerRadius ) is ShapeMode.OutlinedRect -> copy( rotationDegrees = rotationDegrees ?: this.rotationDegrees, cornerRadius = cornerRadius ?: this.cornerRadius ) else -> this } internal fun ShapeMode.updatePolygon( vertices: Int? = null, rotationDegrees: Int? = null, isRegular: Boolean? = null ): ShapeMode = when (this) { is ShapeMode.Polygon -> copy( vertices = vertices ?: this.vertices, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) is ShapeMode.OutlinedPolygon -> copy( vertices = vertices ?: this.vertices, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) else -> this } internal fun ShapeMode.updateStar( vertices: Int? = null, innerRadiusRatio: Float? = null, rotationDegrees: Int? = null, isRegular: Boolean? = null ): ShapeMode = when (this) { is ShapeMode.Star -> copy( vertices = vertices ?: this.vertices, innerRadiusRatio = innerRadiusRatio ?: this.innerRadiusRatio, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) is ShapeMode.OutlinedStar -> copy( vertices = vertices ?: this.vertices, innerRadiusRatio = innerRadiusRatio ?: this.innerRadiusRatio, rotationDegrees = rotationDegrees ?: this.rotationDegrees, isRegular = isRegular ?: this.isRegular ) else -> this } internal fun ShapeMode.withSavedStateFrom(previous: ShapeMode): ShapeMode { val previousArrow = previous.takeIf { it is ShapeMode.Arrow || it is ShapeMode.DoubleArrow || it is ShapeMode.LineArrow || it is ShapeMode.DoubleLineArrow } if (previousArrow != null) { val current = updateArrow( sizeScale = previousArrow.arrowSizeScale(), angle = previousArrow.arrowAngle() ) if (current !== this) return current } return when (previous) { is ShapeMode.Rect, is ShapeMode.OutlinedRect -> updateRect( rotationDegrees = previous.rotationDegrees(), cornerRadius = previous.cornerRadius() ) is ShapeMode.Polygon, is ShapeMode.OutlinedPolygon -> updatePolygon( vertices = previous.vertices(), rotationDegrees = previous.rotationDegrees(), isRegular = previous.isRegular() ) is ShapeMode.Star, is ShapeMode.OutlinedStar -> updateStar( vertices = previous.vertices(), innerRadiusRatio = previous.innerRadiusRatio(), rotationDegrees = previous.rotationDegrees(), isRegular = previous.isRegular() ) else -> this } } internal fun LayerType.Shape.withPreferredGeometryFor( newMode: ShapeMode ): LayerType.Shape { val previousKind = shapeMode.kind val updatedType = copy(shapeMode = newMode) if (previousKind.geometryFamily() == newMode.kind.geometryFamily()) { return updatedType } val preset = newMode.kind.preferredSizePreset() return updatedType.copy( widthRatio = preset.widthRatio, heightRatio = preset.heightRatio ) } internal fun LayerType.Shape.withPreferredInitialGeometryFor( mode: ShapeMode ): LayerType.Shape { val preset = mode.kind.preferredSizePreset() return copy( shapeMode = mode, widthRatio = preset.widthRatio, heightRatio = preset.heightRatio ) } private fun ShapeMode.Kind.geometryFamily(): ShapeGeometryFamily = when (this) { ShapeMode.Kind.Line, ShapeMode.Kind.LineArrow, ShapeMode.Kind.DoubleLineArrow -> ShapeGeometryFamily.Line ShapeMode.Kind.Arrow, ShapeMode.Kind.DoubleArrow -> ShapeGeometryFamily.FilledArrow ShapeMode.Kind.Rect, ShapeMode.Kind.OutlinedRect, ShapeMode.Kind.Oval, ShapeMode.Kind.OutlinedOval -> ShapeGeometryFamily.RectLike ShapeMode.Kind.Triangle, ShapeMode.Kind.OutlinedTriangle -> ShapeGeometryFamily.Triangle ShapeMode.Kind.Polygon, ShapeMode.Kind.OutlinedPolygon -> ShapeGeometryFamily.Polygon ShapeMode.Kind.Star, ShapeMode.Kind.OutlinedStar -> ShapeGeometryFamily.Star } private fun ShapeMode.Kind.preferredSizePreset(): ShapeSizePreset = when (this) { ShapeMode.Kind.Line, ShapeMode.Kind.LineArrow, ShapeMode.Kind.DoubleLineArrow -> ShapeSizePreset( widthRatio = 0.5f, heightRatio = 0.1f ) ShapeMode.Kind.Arrow, ShapeMode.Kind.DoubleArrow -> ShapeSizePreset( widthRatio = 0.5f, heightRatio = 0.2f ) ShapeMode.Kind.Rect, ShapeMode.Kind.OutlinedRect, ShapeMode.Kind.Oval, ShapeMode.Kind.OutlinedOval -> ShapeSizePreset( widthRatio = 0.42f, heightRatio = 0.42f ) ShapeMode.Kind.Triangle, ShapeMode.Kind.OutlinedTriangle -> ShapeSizePreset( widthRatio = 0.36f, heightRatio = 0.32f ) ShapeMode.Kind.Polygon, ShapeMode.Kind.OutlinedPolygon -> ShapeSizePreset( widthRatio = 0.36f, heightRatio = 0.36f ) ShapeMode.Kind.Star, ShapeMode.Kind.OutlinedStar -> ShapeSizePreset( widthRatio = 0.38f, heightRatio = 0.38f ) } private const val DEFAULT_ARROW_SIZE_SCALE = 3f private const val DEFAULT_ARROW_ANGLE = 150f private const val DEFAULT_VERTICES = 5 private const val DEFAULT_INNER_RADIUS_RATIO = 0.5f private const val DEFAULT_RECT_CORNER_RADIUS = 0f ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/MarkupLayersContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.feature.markup_layers.presentation import android.graphics.Bitmap import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastAny import androidx.compose.ui.zIndex import androidx.core.graphics.applyCanvas import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Archive import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberAppColorTuple import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveBottomScaffoldLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.Layer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.MarkupLayersActions import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.MarkupLayersNoDataControls import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.MarkupLayersSideMenu import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.MarkupLayersTopAppBarActions import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.MarkupLayersUndoRedo import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.activeLayerGestures import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.BackgroundBehavior import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable fun MarkupLayersContent( component: MarkupLayersComponent ) { AutoContentBasedColors(component.bitmap) val themeState = LocalDynamicThemeState.current val appColorTuple = rememberAppColorTuple() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { when (component.backgroundBehavior) { !is BackgroundBehavior.None if component.haveChanges -> showExitDialog = true !is BackgroundBehavior.None -> { component.resetState() themeState.updateColorTuple(appColorTuple) } else -> component.onGoBack() } } AutoContentBasedColors(component.bitmap) val imagePicker = rememberImagePicker { uri: Uri -> component.setUri( uri = uri ) } val pickImage = imagePicker::pickImage val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } val projectOpener = rememberFilePicker( mimeType = MimeType.MarkupProjectList, onSuccess = component::setUri ) val activeLayer by remember(component) { derivedStateOf { component.layers.firstOrNull { it.state.isActive } } } val projectSaver = rememberFileCreator( mimeType = MimeType.MarkupProject, onSuccess = component::saveProject ) val screenSize = LocalScreenSize.current val isPortrait by isPortraitOrientationAsState() val bitmap = component.bitmap ?: (component.backgroundBehavior as? BackgroundBehavior.Color)?.run { remember(width, height, color) { ImageBitmap(width, height).asAndroidBitmap() .applyCanvas { drawColor(color) } } } ?: remember { ImageBitmap( screenSize.widthPx, screenSize.heightPx ).asAndroidBitmap() } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } var showLayersSelection by rememberSaveable { mutableStateOf(false) } var isContextOptionsVisible by rememberSaveable { mutableStateOf(false) } var shouldOpenContextOptions by rememberSaveable { mutableStateOf(false) } val closeLayersSelection = { showLayersSelection = false isContextOptionsVisible = false shouldOpenContextOptions = false component.cancelGroupingSelection() } val toggleLayersSelection = { if (showLayersSelection) { closeLayersSelection() } else { showLayersSelection = true } } val requestContextOptions = { waitForActiveLayer: Boolean -> showLayersSelection = true if (waitForActiveLayer || activeLayer != null) { shouldOpenContextOptions = true } } LaunchedEffect(showLayersSelection, activeLayer, shouldOpenContextOptions) { if (showLayersSelection && shouldOpenContextOptions && activeLayer != null) { withFrameNanos { } isContextOptionsVisible = true shouldOpenContextOptions = false } } AdaptiveBottomScaffoldLayoutScreen( autoClearFocus = false, modifier = Modifier .clearFocusOnTap() .tappable { component.deactivateAllLayers() }, title = { TopAppBarTitle( title = stringResource(R.string.markup_layers), input = component.backgroundBehavior.takeIf { it !is BackgroundBehavior.None }, isLoading = component.isImageLoading, size = null ) }, onGoBack = onBack, shouldDisableBackHandler = component.backgroundBehavior is BackgroundBehavior.None, actions = { MarkupLayersActions( component = component, showLayersSelection = showLayersSelection, onToggleLayersSection = toggleLayersSelection, onToggleLayersSectionQuick = { requestContextOptions(false) } ) }, topAppBarPersistentActions = { scaffoldState -> MarkupLayersTopAppBarActions( component = component, scaffoldState = scaffoldState ) }, mainContent = { val imageBitmap by remember(bitmap) { derivedStateOf { bitmap.copy(Bitmap.Config.ARGB_8888, true).asImageBitmap() } } val direction = LocalLayoutDirection.current val aspectRatio = imageBitmap.width / imageBitmap.height.toFloat() Box( modifier = Modifier .fillMaxSize() .clipToBounds() .zoomable( zoomState = rememberZoomState(maxScale = 10f), zoomEnabled = !component.layers.fastAny { it.state.isActive } ), contentAlignment = Alignment.Center ) { Box { Box( modifier = Modifier .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ) .padding(16.dp) .aspectRatio(aspectRatio, isPortrait) .fillMaxSize() .clip(ShapeDefaults.extremeSmall) .border( width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant(), shape = ShapeDefaults.extremeSmall ) .background(MaterialTheme.colorScheme.surfaceContainerLow), contentAlignment = Alignment.Center ) { Box( modifier = Modifier .zIndex(-1f) .matchParentSize() .clipToBounds() .transparencyChecker() ) BoxWithConstraints( modifier = Modifier .matchParentSize() .activeLayerGestures( component = component, activeLayer = activeLayer ) .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }, contentAlignment = Alignment.Center ) { Picture( model = imageBitmap, contentDescription = null, contentScale = ContentScale.FillBounds, modifier = Modifier .matchParentSize() .clipToBounds(), showTransparencyChecker = false ) component.layers.forEachIndexed { index, layer -> Layer( component = component, layer = layer, onActivate = { component.activateLayer(layer) }, onUpdateLayer = { updatedLayer, commitToHistory -> component.updateLayerAt( index = index, layer = updatedLayer, commitToHistory = commitToHistory ) }, onShowContextOptions = { requestContextOptions(true) } ) } } } } } }, controls = { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { if (!isPortrait) { MarkupLayersUndoRedo( component = component, color = Color.Unspecified, removePadding = false ) Spacer(Modifier.height(4.dp)) } val behavior = component.backgroundBehavior if (behavior is BackgroundBehavior.Color) { ColorRowSelector( value = behavior.color.toColor(), onValueChange = component::updateBackgroundColor, icon = Icons.Outlined.BackgroundColor, modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.extraLarge ) ) } SaveExifWidget( modifier = Modifier.fillMaxWidth(), checked = component.saveExif, imageFormat = component.imageFormat, onCheckedChange = component::setSaveExif ) PreferenceItem( onClick = { projectSaver.make(component.createProjectFilename()) }, startIcon = Icons.Outlined.Archive, title = stringResource(R.string.save_markup_project), subtitle = stringResource(R.string.save_markup_project_sub), modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.large, ) ImageFormatSelector( modifier = Modifier.navigationBarsPadding(), forceEnabled = component.backgroundBehavior is BackgroundBehavior.Color, value = component.imageFormat, onValueChange = component::setImageFormat ) } }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.backgroundBehavior is BackgroundBehavior.None, onSecondaryButtonClick = pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, isSecondaryButtonVisible = component.backgroundBehavior !is BackgroundBehavior.Color, onPrimaryButtonClick = { saveBitmap(null) }, isPrimaryButtonVisible = component.backgroundBehavior !is BackgroundBehavior.None, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() }, showNullDataButtonAsContainer = true ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, enableNoDataScroll = false, noDataControls = { MarkupLayersNoDataControls( component = component, onPickImage = pickImage, onOpenProject = projectOpener::pickFile ) }, canShowScreenData = component.backgroundBehavior !is BackgroundBehavior.None, mainContentWeight = 0.65f ) MarkupLayersSideMenu( component = component, visible = showLayersSelection, onDismiss = closeLayersSelection, isContextOptionsVisible = isContextOptionsVisible, onContextOptionsVisibleChange = { isContextOptionsVisible = it } ) LoadingDialog( visible = component.isSaving || component.isImageLoading, onCancelLoading = component::cancelSaving, canCancel = component.isSaving ) ExitWithoutSavingDialog( onExit = { if (component.backgroundBehavior !is BackgroundBehavior.None) { component.resetState() themeState.updateColorTuple(appColorTuple) } else component.onGoBack() }, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ActiveLayerGestureModifier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.calculateCentroidSize import androidx.compose.foundation.gestures.calculatePan import androidx.compose.foundation.gestures.calculateRotation import androidx.compose.foundation.gestures.calculateZoom import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.positionChanged import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.applyGroupGlobalChanges import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.uiCornerRadiusPercent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent import kotlin.math.PI import kotlin.math.abs internal fun Modifier.activeLayerGestures( component: MarkupLayersComponent, activeLayer: UiMarkupLayer? ): Modifier = pointerInput(activeLayer) { activeLayer ?: return@pointerInput awaitEachGesture { activeLayer.state var totalZoom = 1f var totalRotation = 0f var totalPan = Offset.Zero var pastTouchSlop = false val touchSlop = viewConfiguration.touchSlop var down = awaitFirstDown( pass = PointerEventPass.Initial, requireUnconsumed = false ) var activePointerId = down.id do { val event = awaitPointerEvent(pass = PointerEventPass.Initial) val pressedChanges = event.changes.filter(PointerInputChange::pressed) if (pressedChanges.isEmpty()) break val activePointer = pressedChanges.firstOrNull { it.id == activePointerId } ?: pressedChanges.first().also { activePointerId = it.id } val isMultiTouch = pressedChanges.size > 1 val panChange = if (isMultiTouch) { event.calculatePan() } else { activePointer.positionChange() } val zoomChange = if (isMultiTouch) event.calculateZoom() else 1f val rotationChange = if (isMultiTouch) event.calculateRotation() else 0f if (!pastTouchSlop) { totalPan += panChange totalZoom *= zoomChange totalRotation += rotationChange val centroidSize = if (isMultiTouch) { event.calculateCentroidSize(useCurrent = false) } else { 0f } val zoomMotion = abs(1 - totalZoom) * centroidSize val rotationMotion = abs(totalRotation * PI.toFloat() * centroidSize / 180f) val panMotion = totalPan.getDistance() if (zoomMotion > touchSlop || rotationMotion > touchSlop || panMotion > touchSlop ) { component.beginHistoryTransaction() pastTouchSlop = true } } if (pastTouchSlop) { component.updateLayerState( layer = activeLayer, commitToHistory = false ) { if (activeLayer.isGroup) { activeLayer.applyGroupGlobalChanges( zoomChange = zoomChange, offsetChange = panChange, rotationChange = rotationChange ) } else { val contentSize = contentSize if (contentSize.width > 0 && contentSize.height > 0) { val canvasWidth = canvasSize.width.takeIf { it > 0 } ?: size.width val canvasHeight = canvasSize.height.takeIf { it > 0 } ?: size.height applyGlobalChanges( parentMaxWidth = canvasWidth, parentMaxHeight = canvasHeight, contentSize = contentSize, cornerRadiusPercent = activeLayer.uiCornerRadiusPercent(), zoomChange = zoomChange, offsetChange = panChange, rotationChange = rotationChange ) } } } event.changes.forEach { change -> if (change.positionChanged()) { change.consume() } } } down = activePointer activePointerId = down.id } while (true) if (pastTouchSlop) { component.commitHistoryTransaction() } } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddShapeLayerDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.StarSticky import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.feature.markup_layers.domain.ShapeMode import com.t8rin.imagetoolbox.feature.markup_layers.domain.defaultMode import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.icon @Composable internal fun AddShapeLayerDialog( visible: Boolean, onDismiss: () -> Unit, onShapePicked: (ShapeMode) -> Unit ) { var selectedKindName by rememberSaveable(visible) { mutableStateOf(null) } val selectedKind = selectedKindName?.let { raw -> ShapeMode.Kind.entries.firstOrNull { it.name == raw } } EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, icon = { Icon( imageVector = Icons.Outlined.StarSticky, contentDescription = null ) }, title = { Text(stringResource(R.string.shape)) }, text = { val state = rememberScrollState() FlowRow( horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterHorizontally ), verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), modifier = Modifier .fillMaxWidth() .verticalScroll( state = state, flingBehavior = enhancedFlingBehavior() ) .fadingEdges( scrollableState = state, isVertical = true ) ) { ShapeMode.Kind.entries.forEach { kind -> EnhancedChip( selected = selectedKind == kind, onClick = { selectedKindName = kind.name }, selectedColor = MaterialTheme.colorScheme.tertiaryContainer, unselectedColor = MaterialTheme.colorScheme.surface, contentPadding = PaddingValues( horizontal = 12.dp, vertical = 8.dp ), label = { Icon( imageVector = kind.icon, contentDescription = null ) } ) } } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.close)) } }, confirmButton = { EnhancedButton( enabled = selectedKind != null, onClick = { selectedKind?.let { onShapePicked(it.defaultMode()) } onDismiss() } ) { Text(stringResource(R.string.add)) } } ) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/AddTextLayerDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.TextSticky import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer @Composable internal fun AddTextLayerDialog( visible: Boolean, onDismiss: () -> Unit, onAddLayer: (UiMarkupLayer) -> Unit ) { var dialogText by rememberSaveable(visible) { mutableStateOf("") } EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, icon = { Icon( imageVector = Icons.Outlined.TextSticky, contentDescription = null ) }, title = { Text(stringResource(R.string.text)) }, text = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { OutlinedTextField( shape = ShapeDefaults.default, value = dialogText, textStyle = MaterialTheme.typography.titleMedium.copy( textAlign = TextAlign.Center ), onValueChange = { dialogText = it } ) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss, ) { Text(stringResource(R.string.close)) } }, confirmButton = { EnhancedButton( enabled = dialogText.isNotEmpty(), onClick = { onAddLayer( UiMarkupLayer( type = LayerType.Text.Default.copy( text = dialogText ) ) ) onDismiss() }, ) { Text(stringResource(R.string.add)) } } ) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ClickableTile.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun ClickableTile( onClick: () -> Unit, icon: ImageVector, text: String?, enabled: Boolean = true, containerColor: Color = MaterialTheme.colorScheme.surfaceContainerLow, onHoldStep: (() -> Unit)? = null, modifier: Modifier = Modifier ) { if (text != null) { val tooltipState = rememberTooltipState() TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider( TooltipAnchorPosition.Above ), tooltip = { PlainTooltip { Text(text) } }, state = tooltipState ) { ClickableTile( containerColor = containerColor, onClick = onClick, enabled = enabled, onHoldStep = onHoldStep, modifier = modifier ) { Icon( imageVector = icon, contentDescription = null ) } } } else { ClickableTile( containerColor = containerColor, onClick = onClick, enabled = enabled, onHoldStep = onHoldStep, modifier = modifier ) { Icon( imageVector = icon, contentDescription = null ) } } } @Composable internal fun ClickableTile( onClick: () -> Unit, enabled: Boolean = true, containerColor: Color = MaterialTheme.colorScheme.surfaceContainerLow, onHoldStep: (() -> Unit)? = null, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit ) { val haptics = LocalHapticFeedback.current val interactionModifier = if (!enabled) { Modifier } else if (onHoldStep != null) { Modifier.pointerInput(onClick, onHoldStep) { awaitEachGesture { val down = awaitFirstDown(requireUnconsumed = false) down.consume() haptics.longPress() onClick() var repeatDelayMs = 140L val minRepeatDelayMs = 45L var up = withTimeoutOrNull(240L) { waitForUpOrCancellation() } if (up != null) { up.consume() return@awaitEachGesture } val holdStartNanos = System.nanoTime() var stepsAccumulator = 0f while (up == null) { val holdDurationMs = ((System.nanoTime() - holdStartNanos) / 1_000_000L) // Smoothly ramps average speed from 1 to 10 steps per tick. val progress = (holdDurationMs / 3000f).coerceIn(0f, 1f) val smoothStepsPerTick = 1f + 9f * progress stepsAccumulator += smoothStepsPerTick val stepsPerTick = stepsAccumulator.toInt().coerceIn(1, 20).also { stepsAccumulator -= it } repeat(stepsPerTick) { onHoldStep() } repeatDelayMs = (repeatDelayMs * 0.94f).toLong() .coerceAtLeast(minRepeatDelayMs) up = withTimeoutOrNull(repeatDelayMs) { waitForUpOrCancellation() } } up.consume() } } } else { Modifier.hapticsClickable(onClick = onClick) } Column( modifier = Modifier .then( if (modifier == Modifier) { Modifier.size( width = 100.dp, height = 72.dp ) } else { modifier } ) .container( shape = ShapeDefaults.extraSmall, color = containerColor, resultPadding = 0.dp ) .alpha(if (enabled) 1f else 0.5f) .then(interactionModifier) .padding(6.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, content = content ) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/DropShadowSection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Shadow import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.markup_layers.domain.DropShadow @Composable internal fun DropShadowSection( shadow: DropShadow?, onShadowChange: (DropShadow?) -> Unit, onShadowChangeContinuously: (DropShadow) -> Unit, onContinuousEditFinished: () -> Unit, shape: androidx.compose.ui.graphics.Shape = ShapeDefaults.large ) { var haveShadow by rememberSaveable(shadow != null) { mutableStateOf(shadow != null) } LaunchedEffect(haveShadow, shadow) { val desiredShadow = if (haveShadow) { shadow ?: DropShadow.Default } else null if (shadow != desiredShadow) { onShadowChange(desiredShadow) } } PreferenceRowSwitch( title = stringResource(R.string.add_shadow), subtitle = stringResource(R.string.add_shadow_sub), shape = shape, containerColor = MaterialTheme.colorScheme.surface, startIcon = Icons.Outlined.Shadow, checked = haveShadow, onClick = { haveShadow = it }, additionalContent = { AnimatedVisibility( visible = shadow != null, modifier = Modifier.fillMaxWidth() ) { val resolvedShadow = shadow ?: DropShadow.Default Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(top = 16.dp) ) { ColorRowSelector( value = resolvedShadow.color.toColor(), onValueChange = { onShadowChange( resolvedShadow.copy( color = it.toArgb() ) ) }, title = stringResource(R.string.shadow_color), modifier = Modifier.container( shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surfaceContainerLow ) ) EnhancedSliderItem( value = resolvedShadow.blurRadius, title = stringResource(R.string.blur_radius), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onShadowChangeContinuously( resolvedShadow.copy( blurRadius = it ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = DropShadow.BlurRadiusRange, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surfaceContainerLow ) EnhancedSliderItem( value = resolvedShadow.offsetX, title = stringResource(R.string.offset_x), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onShadowChangeContinuously( resolvedShadow.copy( offsetX = it ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = DropShadow.OffsetXRange, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surfaceContainerLow ) EnhancedSliderItem( value = resolvedShadow.offsetY, title = stringResource(R.string.offset_y), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onShadowChangeContinuously( resolvedShadow.copy( offsetY = it ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = DropShadow.OffsetYRange, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surfaceContainerLow ) } } } ) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBox.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateIntAsState import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.toRect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asComposePaint import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.IntSize import com.t8rin.imagetoolbox.core.data.image.utils.toPaint import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.other.AnimatedBorder import kotlinx.coroutines.launch @Composable fun BoxWithConstraintsScope.EditBox( state: EditBoxState, onTap: () -> Unit, modifier: Modifier = Modifier, onLongTap: (() -> Unit)? = null, cornerRadiusPercent: Int = 0, blendingMode: BlendingMode = BlendingMode.SrcOver, isInteractive: Boolean = true, showSelectionBackground: Boolean = true, content: @Composable BoxScope.() -> Unit ) { val parentSize by remember(constraints) { derivedStateOf { IntegerSize( constraints.maxWidth, constraints.maxHeight ) } } EditBox( modifier = modifier, onTap = onTap, onLongTap = onLongTap, state = state, parentSize = parentSize, cornerRadiusPercent = cornerRadiusPercent, blendingMode = blendingMode, isInteractive = isInteractive, showSelectionBackground = showSelectionBackground, content = content ) } @Composable fun EditBox( state: EditBoxState, onTap: () -> Unit, parentSize: IntegerSize, modifier: Modifier = Modifier, onLongTap: (() -> Unit)? = null, cornerRadiusPercent: Int = 0, blendingMode: BlendingMode = BlendingMode.SrcOver, isInteractive: Boolean = true, showSelectionBackground: Boolean = true, content: @Composable BoxScope.() -> Unit ) { if (!state.isVisible) return var contentSize by remember { mutableStateOf(IntSize.Zero) } val parentMaxWidth = parentSize.width val parentMaxHeight = parentSize.height SideEffect { state.canvasSize = parentSize } var needRecalculations by rememberSaveable(state.coerceToBounds, contentSize) { mutableStateOf(state.coerceToBounds && contentSize != IntSize.Zero) } LaunchedEffect(needRecalculations) { if (needRecalculations) { state.applyChanges( parentMaxWidth = parentMaxWidth, parentMaxHeight = parentMaxHeight, contentSize = contentSize, cornerRadiusPercent = cornerRadiusPercent, zoomChange = 1f, offsetChange = Offset.Zero, rotationChange = 0f ) needRecalculations = false } } val tapScale = remember { Animatable(1f) } val scope = rememberCoroutineScope() val haptics = LocalHapticFeedback.current val animateTap = { haptics.longPress() scope.launch { tapScale.animateTo(0.98f) tapScale.animateTo(1.02f) tapScale.animateTo(1f) } } val borderAlpha by animateFloatAsState(if (state.isActive) 1f else 0f) val shape = AutoCornersShape( animateIntAsState(cornerRadiusPercent).value ) val selectionBackgroundColor = MaterialTheme.colorScheme.primary.copy( alpha = 0.2f * borderAlpha ) val interactionModifier = if (isInteractive) { Modifier.pointerInput(onTap, animateTap) { detectTapGestures( onLongPress = onLongTap?.let { { it() animateTap() } } ) { onTap() if (state.isActive) animateTap() } } } else { Modifier } Box( modifier = modifier .onSizeChanged { contentSize = it state.contentSize = it } .graphicsLayer( scaleX = state.scale * if (state.isFlippedHorizontally) -1f else 1f, scaleY = state.scale * if (state.isFlippedVertically) -1f else 1f, rotationZ = state.rotation, translationX = state.offset.x, translationY = state.offset.y ) .scale(tapScale.value) .clip(shape) .drawWithCache { onDrawWithContent { if (showSelectionBackground && state.isActive && blendingMode == BlendingMode.SrcOver) { drawRect(selectionBackgroundColor) } drawContent() } } .then(interactionModifier), contentAlignment = Alignment.Center ) { Box( Modifier .layerBlendingMode( mode = blendingMode, alpha = state.alpha ) ) { content() } AnimatedBorder( modifier = Modifier.matchParentSize(), alpha = borderAlpha, scale = state.scale, shape = shape ) if (state.isActive) { Surface( color = Color.Transparent, modifier = Modifier.matchParentSize() ) { } } } } private fun Modifier.layerBlendingMode( mode: BlendingMode, alpha: Float ): Modifier { val coercedAlpha = alpha.coerceIn(0f, 1f) if (mode == BlendingMode.SrcOver) { return if (coercedAlpha >= 0.999f) this else graphicsLayer { this.alpha = coercedAlpha } } return drawWithCache { val paint = mode.toPaint().asComposePaint().apply { this.alpha = coercedAlpha } onDrawWithContent { drawContext.canvas.saveLayer(size.toRect(), paint) drawContent() drawContext.canvas.restore() } } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditBoxState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastCoerceIn import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.math.cos import kotlin.math.max import kotlin.math.min import kotlin.math.sin class EditBoxState( scale: Float = 1f, rotation: Float = 0f, isFlippedHorizontally: Boolean = false, isFlippedVertically: Boolean = false, offset: Offset = Offset.Zero, alpha: Float = 1f, isActive: Boolean = false, canvasSize: IntegerSize = IntegerSize.Zero, contentSize: IntSize = IntSize.Zero, isVisible: Boolean = true, coerceToBounds: Boolean = true, isInEditMode: Boolean = false, ) { fun copy( scale: Float = this.scale, rotation: Float = this.rotation, isFlippedHorizontally: Boolean = this.isFlippedHorizontally, isFlippedVertically: Boolean = this.isFlippedVertically, offset: Offset = this.offset, alpha: Float = this.alpha, isActive: Boolean = this.isActive, canvasSize: IntegerSize = this.canvasSize, contentSize: IntSize = this.contentSize, isVisible: Boolean = this.isVisible, coerceToBounds: Boolean = this.coerceToBounds, isInEditMode: Boolean = this.isInEditMode, ): EditBoxState = EditBoxState( scale = scale, rotation = rotation, isFlippedHorizontally = isFlippedHorizontally, isFlippedVertically = isFlippedVertically, offset = offset, alpha = alpha, isActive = isActive, canvasSize = canvasSize, contentSize = contentSize, isVisible = isVisible, coerceToBounds = coerceToBounds, isInEditMode = isInEditMode ) var isActive by mutableStateOf(isActive) internal set var isInEditMode by mutableStateOf(isInEditMode) internal set private val _isVisible = mutableStateOf(isVisible) var isVisible: Boolean get() = _isVisible.value internal set(value) { if (!value) { isActive = false } _isVisible.value = value } fun activate() { isActive = true } fun deactivate() { isActive = false } internal fun applyChanges( parentMaxWidth: Int, parentMaxHeight: Int, contentSize: IntSize, cornerRadiusPercent: Int, zoomChange: Float, offsetChange: Offset, rotationChange: Float ) { applyChangeSet( parentMaxWidth = parentMaxWidth, parentMaxHeight = parentMaxHeight, contentSize = contentSize, cornerRadiusPercent = cornerRadiusPercent, zoomChange = zoomChange, offsetChange = (offsetChange * scale).rotateBy(rotation), rotationChange = rotationChange ) } internal fun applyGlobalChanges( parentMaxWidth: Int, parentMaxHeight: Int, contentSize: IntSize, cornerRadiusPercent: Int, zoomChange: Float, offsetChange: Offset, rotationChange: Float ) { applyChangeSet( parentMaxWidth = parentMaxWidth, parentMaxHeight = parentMaxHeight, contentSize = contentSize, cornerRadiusPercent = cornerRadiusPercent, zoomChange = zoomChange, offsetChange = offsetChange, rotationChange = rotationChange ) } private fun applyChangeSet( parentMaxWidth: Int, parentMaxHeight: Int, contentSize: IntSize, cornerRadiusPercent: Int, zoomChange: Float, offsetChange: Offset, rotationChange: Float ) { rotation += rotationChange val currentScale = scale scale = coerceInteractiveScale( currentScale = currentScale, targetScale = currentScale * zoomChange, minimumValue = SCALE_RANGE.start, maximumValue = SCALE_RANGE.endInclusive ) val halfExtents = contentSize.rotatedHalfExtents( degrees = rotation, cornerRadiusPercent = cornerRadiusPercent ) val halfParentWidth = parentMaxWidth / 2f val halfParentHeight = parentMaxHeight / 2f val maxX = (halfParentWidth - halfExtents.x * scale).absoluteValue val maxY = (halfParentHeight - halfExtents.y * scale).absoluteValue offset = Offset( x = (offset.x + offsetChange.x).coerceIn(-maxX, maxX, coerceToBounds), y = (offset.y + offsetChange.y).coerceIn(-maxY, maxY, coerceToBounds), ) } private fun Float.coerceIn( minimumValue: Float, maximumValue: Float, enable: Boolean ) = if (enable) coerceIn(minimumValue, maximumValue) else this var scale by mutableFloatStateOf(scale) internal set var rotation by mutableFloatStateOf(rotation) internal set var isFlippedHorizontally by mutableStateOf(isFlippedHorizontally) internal set var isFlippedVertically by mutableStateOf(isFlippedVertically) internal set var offset by mutableStateOf(offset) internal set var alpha by mutableFloatStateOf(alpha) internal set var coerceToBounds by mutableStateOf(coerceToBounds) internal set var contentSize by mutableStateOf(contentSize) internal set private val _canvasSize = mutableStateOf(IntegerSize.Zero) init { adjustByCanvasSize(canvasSize) } var canvasSize: IntegerSize get() = _canvasSize.value set(value) { adjustByCanvasSize(value) } internal fun syncCanvasSize( value: IntegerSize, forceScaleAdjustment: Boolean = false ) { adjustByCanvasSize( value = value, forceScaleAdjustment = forceScaleAdjustment ) } private fun adjustByCanvasSize( value: IntegerSize, forceScaleAdjustment: Boolean = false ) { val previousCanvasSize = _canvasSize.value if (previousCanvasSize == value) { _canvasSize.value = value return } if (previousCanvasSize != IntegerSize.Zero) { val sx = value.width.toFloat() / previousCanvasSize.width val sy = value.height.toFloat() / previousCanvasSize.height offset = Offset( x = offset.x * sx, y = offset.y * sy ) // Layers whose measured content did not depend on the canvas bounds // need an explicit scale adjustment to preserve their visual size // relative to the preview after canvas resize. Layers already capped // by `sizeIn(max = canvas / 2)` will remeasure on their own. if ( contentSize.isSpecified() && ( forceScaleAdjustment || !contentSize.isBoundedByCanvas(previousCanvasSize) ) ) { val scaledValue = scale * min(sx, sy) scale = if (forceScaleAdjustment) { scaledValue.coerceAtMost(SCALE_RANGE.endInclusive) } else { scaledValue.fastCoerceIn( minimumValue = SCALE_RANGE.start, maximumValue = SCALE_RANGE.endInclusive ) } } } _canvasSize.value = value } internal fun normalizedPosition( cornerRadiusPercent: Int ): Offset? { val contentSize = contentSize val canvasWidth = canvasSize.width.takeIf { it > 0 } ?: return null val canvasHeight = canvasSize.height.takeIf { it > 0 } ?: return null if (contentSize.width <= 0 || contentSize.height <= 0) return null val halfExtents = contentSize.rotatedHalfExtents( degrees = rotation, cornerRadiusPercent = cornerRadiusPercent ) val halfWidth = halfExtents.x * scale val halfHeight = halfExtents.y * scale val layerWidth = halfWidth * 2f val layerHeight = halfHeight * 2f val left = canvasWidth / 2f + offset.x - halfWidth val top = canvasHeight / 2f + offset.y - halfHeight return Offset( x = left.normalizeEdgeAware( axisSize = canvasWidth.toFloat(), layerSize = layerWidth ), y = top.normalizeEdgeAware( axisSize = canvasHeight.toFloat(), layerSize = layerHeight ) ) } internal fun setNormalizedPosition( x: Float? = null, y: Float? = null, cornerRadiusPercent: Int ) { if (x == null && y == null) return val contentSize = contentSize val canvasWidth = canvasSize.width.takeIf { it > 0 } ?: return val canvasHeight = canvasSize.height.takeIf { it > 0 } ?: return if (contentSize.width <= 0 || contentSize.height <= 0) return val halfExtents = contentSize.rotatedHalfExtents( degrees = rotation, cornerRadiusPercent = cornerRadiusPercent ) val halfWidth = halfExtents.x * scale val halfHeight = halfExtents.y * scale val layerWidth = halfWidth * 2f val layerHeight = halfHeight * 2f val currentLeft = canvasWidth / 2f + offset.x - halfWidth val currentTop = canvasHeight / 2f + offset.y - halfHeight val targetLeft = x?.denormalizeEdgeAware( axisSize = canvasWidth.toFloat(), layerSize = layerWidth ) ?: currentLeft val targetTop = y?.denormalizeEdgeAware( axisSize = canvasHeight.toFloat(), layerSize = layerHeight ) ?: currentTop val targetOffset = Offset( x = targetLeft - canvasWidth / 2f + halfWidth, y = targetTop - canvasHeight / 2f + halfHeight ) applyGlobalChanges( parentMaxWidth = canvasWidth, parentMaxHeight = canvasHeight, contentSize = contentSize, cornerRadiusPercent = cornerRadiusPercent, zoomChange = 1f, offsetChange = Offset( x = targetOffset.x - offset.x, y = targetOffset.y - offset.y ), rotationChange = 0f ) } internal fun setScalePrecisely( targetScale: Float, cornerRadiusPercent: Int ) { val contentSize = contentSize val scale = coerceInteractiveScale( currentScale = this.scale, targetScale = targetScale, minimumValue = SCALE_RANGE.start, maximumValue = SCALE_RANGE.endInclusive ) if (contentSize.width <= 0 || contentSize.height <= 0) { this.scale = scale return } val canvasWidth = canvasSize.width.takeIf { it > 0 } ?: contentSize.width val canvasHeight = canvasSize.height.takeIf { it > 0 } ?: contentSize.height applyGlobalChanges( parentMaxWidth = canvasWidth, parentMaxHeight = canvasHeight, contentSize = contentSize, cornerRadiusPercent = cornerRadiusPercent, zoomChange = scale / this.scale.coerceAtLeast(0.0001f), offsetChange = Offset.Zero, rotationChange = 0f ) } } private fun Float.normalizeEdgeAware( axisSize: Float, layerSize: Float ): Float { val safeAxisSize = axisSize.coerceAtLeast(1f) val safeLayerSize = layerSize.coerceAtLeast(1f) val maxInsideStart = safeAxisSize - safeLayerSize return when { maxInsideStart > 0f -> when { this < 0f -> this / safeLayerSize this + safeLayerSize > safeAxisSize -> 1f + (this - maxInsideStart) / safeLayerSize else -> this / maxInsideStart } maxInsideStart == 0f -> when { this < 0f -> this / safeLayerSize this > 0f -> 1f + this / safeLayerSize else -> 0f } else -> when { this > 0f -> -this / safeLayerSize this < maxInsideStart -> 1f + (maxInsideStart - this) / safeLayerSize else -> this / maxInsideStart } } } private fun Float.denormalizeEdgeAware( axisSize: Float, layerSize: Float ): Float { val safeAxisSize = axisSize.coerceAtLeast(1f) val safeLayerSize = layerSize.coerceAtLeast(1f) val maxInsideStart = safeAxisSize - safeLayerSize return when { maxInsideStart > 0f -> when { this < 0f -> this * safeLayerSize this > 1f -> maxInsideStart + (this - 1f) * safeLayerSize else -> this * maxInsideStart } maxInsideStart == 0f -> when { this < 0f -> this * safeLayerSize this > 1f -> (this - 1f) * safeLayerSize else -> 0f } else -> when { this < 0f -> -this * safeLayerSize this > 1f -> maxInsideStart - (this - 1f) * safeLayerSize else -> this * maxInsideStart } } } private fun IntSize.rotatedHalfExtents( degrees: Float, cornerRadiusPercent: Int ): Offset { val halfWidth = width / 2f val halfHeight = height / 2f val cornerRadiusPx = ( min(width, height) * (cornerRadiusPercent.coerceIn(0, 50) / 100f) ).coerceIn(0f, min(halfWidth, halfHeight)) // Rounded rectangle can be represented as an inner rectangle expanded by a circle. // This gives accurate support extents for collision checks in any rotation. val innerHalfWidth = max(0f, halfWidth - cornerRadiusPx) val innerHalfHeight = max(0f, halfHeight - cornerRadiusPx) val radians = Math.toRadians(degrees.toDouble()) val cos = abs(cos(radians)).toFloat() val sin = abs(sin(radians)).toFloat() return Offset( x = innerHalfWidth * cos + innerHalfHeight * sin + cornerRadiusPx, y = innerHalfWidth * sin + innerHalfHeight * cos + cornerRadiusPx ) } private fun IntSize.isSpecified(): Boolean = width > 0 && height > 0 private fun IntSize.isBoundedByCanvas( canvasSize: IntegerSize, tolerancePx: Int = 1 ): Boolean { val maxWidth = canvasSize.width / 2 val maxHeight = canvasSize.height / 2 return width >= maxWidth - tolerancePx || height >= maxHeight - tolerancePx } private fun Offset.rotateBy( angle: Float ): Offset { val angleInRadians = ROTATION_CONST * angle val newX = x * cos(angleInRadians) - y * sin(angleInRadians) val newY = x * sin(angleInRadians) + y * cos(angleInRadians) return Offset(newX, newY) } private fun coerceInteractiveScale( currentScale: Float, targetScale: Float, minimumValue: Float, maximumValue: Float ): Float { val safeTargetScale = targetScale.coerceAtLeast(MIN_SCALE_EPSILON) return when { safeTargetScale >= minimumValue -> safeTargetScale.coerceAtMost(maximumValue) currentScale < minimumValue -> safeTargetScale.coerceAtMost(maximumValue) else -> minimumValue } } private const val ROTATION_CONST = (Math.PI / 180f).toFloat() private const val MIN_SCALE_EPSILON = 0.0001f private val SCALE_RANGE = 0.1f..10f ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/EditLayerSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.FormatAlignLeft import androidx.compose.material.icons.automirrored.rounded.FormatAlignRight import androidx.compose.material.icons.outlined.BorderColor import androidx.compose.material.icons.outlined.BorderStyle import androidx.compose.material.icons.outlined.Percent import androidx.compose.material.icons.outlined.Rectangle import androidx.compose.material.icons.rounded.FormatAlignCenter import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.Outline import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.emoji.Emoji import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.MiniEditLarge import com.t8rin.imagetoolbox.core.resources.icons.SkewMore import com.t8rin.imagetoolbox.core.resources.icons.StackSticky import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiFont import com.t8rin.imagetoolbox.core.ui.theme.inverseByLuma import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.BlendingModeSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FontSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.sheets.EmojiSelectionSheet import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextFieldColors import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.markup_layers.domain.DomainTextDecoration import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType.Text.Alignment import com.t8rin.imagetoolbox.feature.markup_layers.domain.TextGeometricTransform import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.icon import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.titleRes import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.withCoerceToBoundsRecursively import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent import kotlin.math.roundToInt @Composable internal fun EditLayerSheet( component: MarkupLayersComponent, visible: Boolean, onDismiss: (Boolean) -> Unit, onUpdateLayer: (UiMarkupLayer, Boolean) -> Unit, layer: UiMarkupLayer ) { val updateLayerWithHistory: (UiMarkupLayer) -> Unit = { onUpdateLayer(it, true) } val updateLayerContinuously: (UiMarkupLayer) -> Unit = { component.beginHistoryTransaction() onUpdateLayer(it, false) } val finishContinuousEdit = component::commitHistoryTransaction EnhancedModalBottomSheet( visible = visible, onDismiss = onDismiss, title = { if (layer.isGroup) { TitleItem( icon = Icons.Outlined.StackSticky, text = stringResource(R.string.edit_layer) ) } else when (val type = layer.type) { is LayerType.Picture -> { TitleItem( icon = Icons.Rounded.MiniEditLarge, text = stringResource(R.string.edit_layer) ) } is LayerType.Text -> { Row { DomainTextDecoration.entries.forEach { decoration -> EnhancedIconButton( onClick = { updateLayerWithHistory( layer.copy( type = type.copy( decorations = type.decorations.toggle(decoration) ) ) ) }, containerColor = takeColorFromScheme { if (decoration in type.decorations) secondaryContainer else surface }, contentColor = takeColorFromScheme { if (decoration in type.decorations) onSecondaryContainer else onSurface } ) { Icon( imageVector = decoration.icon, contentDescription = null ) } } } } is LayerType.Shape -> { TitleItem( icon = type.shapeMode.kind.icon, text = stringResource(type.shapeMode.kind.titleRes) ) } } }, confirmButton = { EnhancedButton( onClick = { onDismiss(false) } ) { Text(stringResource(R.string.close)) } } ) { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(16.dp) ) { if (!layer.isGroup) { when (val type = layer.type) { is LayerType.Text -> { RoundedTextField( value = type.text, onValueChange = { updateLayerWithHistory( layer.copy( type = type.copy( text = it ) ) ) }, hint = stringResource(R.string.text), colors = RoundedTextFieldColors( isError = false, containerColor = SafeLocalContainerColor ), modifier = Modifier.container( shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surface, resultPadding = 8.dp ), keyboardOptions = KeyboardOptions(), singleLine = false ) Spacer(Modifier.height(4.dp)) EnhancedButtonGroup( modifier = Modifier .container( shape = ShapeDefaults.bottom, color = MaterialTheme.colorScheme.surface ), title = stringResource(id = R.string.alignment), entries = Alignment.entries, value = type.alignment, onValueChange = { updateLayerWithHistory( layer.copy( type = type.copy(alignment = it) ) ) }, itemContent = { Icon( imageVector = when (it) { Alignment.Start -> Icons.AutoMirrored.Rounded.FormatAlignLeft Alignment.Center -> Icons.Rounded.FormatAlignCenter Alignment.End -> Icons.AutoMirrored.Rounded.FormatAlignRight }, contentDescription = null ) }, activeButtonColor = MaterialTheme.colorScheme.secondaryContainer, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh, isScrollable = false ) Spacer(Modifier.height(8.dp)) FontSelector( value = type.font.toUiFont(), onValueChange = { updateLayerWithHistory( layer.copy( type = type.copy( font = it.type ) ) ) }, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = type.size, title = stringResource(R.string.font_scale), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { updateLayerContinuously( layer.copy( type = type.copy(size = it) ) ) }, onValueChangeFinished = { _ -> finishContinuousEdit() }, valueRange = 0.01f..1f, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) ColorRowSelector( value = type.backgroundColor.toColor(), onValueChange = { updateLayerWithHistory( layer.copy( type = type.copy( backgroundColor = it.toArgb() ) ) ) }, title = stringResource(R.string.background_color), icon = Icons.Outlined.BackgroundColor, modifier = Modifier.container( shape = ShapeDefaults.center, color = MaterialTheme.colorScheme.surface ) ) Spacer(modifier = Modifier.height(4.dp)) ColorRowSelector( value = type.color.toColor(), onValueChange = { updateLayerWithHistory( layer.copy( type = type.copy( color = it.toArgb() ) ) ) }, title = stringResource(R.string.text_color), modifier = Modifier.container( shape = ShapeDefaults.center, color = MaterialTheme.colorScheme.surface ) ) Spacer(modifier = Modifier.height(4.dp)) var haveTextGeometry by remember { mutableStateOf(type.geometricTransform != null) } LaunchedEffect(haveTextGeometry, type.geometricTransform) { val desiredGeometricTransform = if (haveTextGeometry) { type.geometricTransform ?: TextGeometricTransform() } else null if (type.geometricTransform != desiredGeometricTransform) { updateLayerWithHistory( layer.copy( type = type.copy( geometricTransform = desiredGeometricTransform ) ) ) } } PreferenceRowSwitch( title = stringResource(R.string.text_geometry), subtitle = stringResource(R.string.text_geometry_sub), shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surface, startIcon = Icons.Outlined.SkewMore, checked = haveTextGeometry, onClick = { haveTextGeometry = it }, additionalContent = { AnimatedVisibility( visible = type.geometricTransform != null, modifier = Modifier.fillMaxWidth() ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(top = 16.dp) ) { EnhancedSliderItem( value = type.geometricTransform?.scaleX ?: 1f, title = stringResource(R.string.scale_x), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { updateLayerContinuously( layer.copy( type = type.copy( geometricTransform = type.geometricTransform?.copy( scaleX = it ) ) ) ) }, onValueChangeFinished = { _ -> finishContinuousEdit() }, valueRange = 0.25f..3f, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surfaceContainerLow ) EnhancedSliderItem( value = type.geometricTransform?.skewX ?: 0f, title = stringResource(R.string.skew_x), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { updateLayerContinuously( layer.copy( type = type.copy( geometricTransform = type.geometricTransform?.copy( skewX = it ) ) ) ) }, onValueChangeFinished = { _ -> finishContinuousEdit() }, valueRange = -1.5f..1.5f, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surfaceContainerLow ) } } } ) Spacer(modifier = Modifier.height(4.dp)) DropShadowSection( shadow = type.shadow, onShadowChange = { shadow -> updateLayerWithHistory( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onShadowChangeContinuously = { shadow -> updateLayerContinuously( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onContinuousEditFinished = finishContinuousEdit, shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) var haveOutline by remember { mutableStateOf(type.outline != null) } LaunchedEffect(haveOutline, type.outline, type.color) { val desiredOutline = if (haveOutline) { type.outline ?: Outline( color = type.color.toColor() .inverseByLuma() .toArgb(), width = 4f ) } else null if (type.outline != desiredOutline) { updateLayerWithHistory( layer.copy( type = type.copy( outline = desiredOutline ) ) ) } } PreferenceRowSwitch( title = stringResource(R.string.add_outline), subtitle = stringResource(R.string.add_outline_sub), shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface, startIcon = Icons.Outlined.BorderStyle, checked = haveOutline, onClick = { haveOutline = it }, additionalContent = { AnimatedVisibility( visible = type.outline != null, modifier = Modifier.fillMaxWidth() ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(top = 16.dp) ) { ColorRowSelector( value = type.outline?.color?.toColor() ?: Color.Transparent, onValueChange = { updateLayerWithHistory( layer.copy( type = type.copy( outline = type.outline?.copy( color = it.toArgb() ) ) ) ) }, title = stringResource(R.string.outline_color), modifier = Modifier.container( shape = ShapeDefaults.top, color = MaterialTheme.colorScheme.surfaceContainerLow ), icon = Icons.Outlined.BorderColor ) EnhancedSliderItem( value = type.outline?.width ?: 0.2f, title = stringResource(R.string.outline_size), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { updateLayerContinuously( layer.copy( type = type.copy( outline = type.outline?.copy( width = it ) ) ) ) }, onValueChangeFinished = { _ -> finishContinuousEdit() }, valueRange = 0.01f..10f, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surfaceContainerLow ) } } } ) } is LayerType.Shape -> { ShapeLayerParamsSelector( layer = layer, type = type, onUpdateLayer = updateLayerWithHistory, onUpdateLayerContinuously = updateLayerContinuously, onContinuousEditFinished = finishContinuousEdit ) } is LayerType.Picture.Image -> { ImageSelector( value = type.imageData, onValueChange = { updateLayerWithHistory( layer.copy( type = type.copy( imageData = it ) ) ) }, subtitle = null, color = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) DropShadowSection( shadow = type.shadow, onShadowChange = { shadow -> updateLayerWithHistory( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onShadowChangeContinuously = { shadow -> updateLayerContinuously( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onContinuousEditFinished = finishContinuousEdit ) } is LayerType.Picture.Sticker -> { var showEmojiPicker by rememberSaveable { mutableStateOf(false) } PreferenceItemOverload( title = stringResource(R.string.change_sticker), subtitle = null, onClick = { showEmojiPicker = true }, startIcon = { Picture( model = type.imageData, contentPadding = PaddingValues(8.dp), shape = MaterialStarShape, modifier = Modifier.size(48.dp), error = { Icon( imageVector = Icons.Outlined.AddPhotoAlt, contentDescription = null, modifier = Modifier .fillMaxSize() .clip(MaterialStarShape) .background( color = MaterialTheme.colorScheme.secondaryContainer.copy( 0.5f ) ) .padding(8.dp) ) } ) }, endIcon = { Icon( imageVector = Icons.Rounded.MiniEdit, contentDescription = stringResource(R.string.edit) ) }, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface, drawStartIconContainer = false ) Spacer(modifier = Modifier.height(4.dp)) DropShadowSection( shadow = type.shadow, onShadowChange = { shadow -> updateLayerWithHistory( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onShadowChangeContinuously = { shadow -> updateLayerContinuously( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onContinuousEditFinished = finishContinuousEdit ) val allEmojis = Emoji.allIcons() EmojiSelectionSheet( selectedEmojiIndex = null, allEmojis = allEmojis, onEmojiPicked = { updateLayerWithHistory( layer.copy( type = type.copy( imageData = allEmojis[it] ) ) ) showEmojiPicker = false }, visible = showEmojiPicker, onDismiss = { showEmojiPicker = false } ) } } Spacer(modifier = Modifier.height(8.dp)) } PreferenceRowSwitch( title = stringResource(R.string.coerce_points_to_image_bounds), subtitle = stringResource(R.string.coerce_points_to_image_bounds_sub), startIcon = Icons.Outlined.Rectangle, checked = layer.state.coerceToBounds, onClick = { updateLayerWithHistory( layer.withCoerceToBoundsRecursively(it) ) }, shape = if (layer.isGroup) ShapeDefaults.large else ShapeDefaults.top, modifier = Modifier.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.surface ) if (!layer.isGroup) { Spacer(modifier = Modifier.height(4.dp)) AlphaSelector( value = layer.state.alpha, onValueChange = { component.beginHistoryTransaction() component.updateLayerState( layer = layer, commitToHistory = false ) { alpha = it } }, onValueChangeFinished = { _ -> finishContinuousEdit() }, modifier = Modifier.fillMaxWidth(), title = stringResource(R.string.layer_alpha), color = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) BlendingModeSelector( value = layer.blendingMode, onValueChange = { updateLayerWithHistory( layer.copy(blendingMode = it) ) }, modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surface, shape = if (layer.type is LayerType.Shape) { ShapeDefaults.bottom } else { ShapeDefaults.center } ) if (layer.type !is LayerType.Shape) { Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = layer.cornerRadiusPercent, title = stringResource(R.string.corners_size), icon = Icons.Outlined.Percent, internalStateTransformation = { it.roundToInt() }, onValueChange = { updateLayerContinuously( layer.copy( cornerRadiusPercent = it.roundToInt().coerceIn(0, 50) ) ) }, onValueChangeFinished = { _ -> finishContinuousEdit() }, valueRange = 0f..50f, steps = 49, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface ) } } } } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/Layer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.sizeIn import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntSize import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.canvasLeafLayers import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.groupContentSize import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.renderCopy import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.uiCornerRadiusPercent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent @Composable internal fun BoxWithConstraintsScope.Layer( component: MarkupLayersComponent?, layer: UiMarkupLayer, onActivate: (() -> Unit)?, onShowContextOptions: (() -> Unit)?, onUpdateLayer: ((UiMarkupLayer, Boolean) -> Unit)?, referenceSizeOverride: Int? = null ) { val canEditLayer = onUpdateLayer != null && component != null if (layer.isGroup) { GroupLayer( component = component, layer = layer, onActivate = onActivate, onShowContextOptions = onShowContextOptions, onUpdateLayer = onUpdateLayer, canEditLayer = canEditLayer ) return } val type = layer.type val cornerRadiusPercent = layer.uiCornerRadiusPercent() val isInteractive = !layer.isLocked && onActivate != null EditBox( state = layer.state, cornerRadiusPercent = cornerRadiusPercent, blendingMode = layer.blendingMode, isInteractive = isInteractive, onTap = { if (layer.state.isActive && canEditLayer) { layer.state.isInEditMode = true } else { onActivate?.invoke() } }, onLongTap = { if (!layer.state.isActive) { onActivate?.invoke() } onShowContextOptions?.invoke() }, content = { val measuredContentSize = layer.state.contentSize val density = LocalDensity.current val contentModifier = when { measuredContentSize.width > 0 && measuredContentSize.height > 0 && (layer.isGroup || onUpdateLayer == null) -> Modifier.requiredSize( width = with(density) { measuredContentSize.width.toDp() }, height = with(density) { measuredContentSize.height.toDp() } ) layer.isGroup || type is LayerType.Text -> Modifier.sizeIn( maxWidth = this@Layer.maxWidth, maxHeight = this@Layer.maxHeight ) else -> { Modifier.sizeIn( maxWidth = this@Layer.maxWidth / 2, maxHeight = this@Layer.maxHeight / 2 ) } } LayerContent( modifier = contentModifier, type = type, groupedLayers = layer.groupedLayers, textFullSize = referenceSizeOverride ?: this@Layer.constraints.run { minOf(maxWidth, maxHeight) }, cornerRadiusPercent = cornerRadiusPercent, onTextLayout = if (layer.type is LayerType.Text && onUpdateLayer != null) { { result -> val visibleLineCount = if (result.didOverflowHeight) { (0 until result.lineCount).count { lineIndex -> result.getLineBottom(lineIndex) <= result.size.height } } else { result.lineCount } if (visibleLineCount > 0 && layer.visibleLineCount != visibleLineCount) { onUpdateLayer( layer.copy(visibleLineCount = visibleLineCount), false ) } } } else null ) } ) if (canEditLayer) { EditLayerSheet( component = component, visible = layer.state.isInEditMode && !layer.isLocked, onDismiss = { layer.state.isInEditMode = it }, onUpdateLayer = onUpdateLayer, layer = layer ) } } @Composable private fun BoxWithConstraintsScope.GroupLayer( component: MarkupLayersComponent?, layer: UiMarkupLayer, onActivate: (() -> Unit)?, onShowContextOptions: (() -> Unit)?, onUpdateLayer: ((UiMarkupLayer, Boolean) -> Unit)?, canEditLayer: Boolean ) { val density = LocalDensity.current val parentCanvasSize = IntegerSize( width = constraints.maxWidth, height = constraints.maxHeight ) val activateGroup = if (!layer.isLocked && onActivate != null) { { if (layer.state.isActive && canEditLayer) { layer.state.isInEditMode = true } else { onActivate() } } } else null val leafLayers = layer.canvasLeafLayers(canvasSize = parentCanvasSize) leafLayers.forEach { child -> val renderedChild = child.renderCopy().let { renderCopy -> if (layer.state.isActive) { renderCopy.copy( state = renderCopy.state.copy( isActive = true ) ) } else renderCopy } Layer( component = null, layer = renderedChild, onActivate = null, onShowContextOptions = null, onUpdateLayer = null ) } if (!layer.isLocked && (activateGroup != null || onShowContextOptions != null)) { leafLayers.forEach { child -> val hitContentSize = child.state.contentSize .takeIf(IntSize::isSpecified) ?: IntSize(1, 1) EditBox( state = child.state.copy( isActive = false, isInEditMode = false ), cornerRadiusPercent = child.uiCornerRadiusPercent(), isInteractive = true, showSelectionBackground = false, onTap = { activateGroup?.invoke() }, onLongTap = { if (!layer.state.isActive) { activateGroup?.invoke() } onShowContextOptions?.invoke() } ) { Box( modifier = Modifier.requiredSize( width = with(density) { hitContentSize.width.toDp() }, height = with(density) { hitContentSize.height.toDp() } ) ) } } } val measuredContentSize = layer.groupContentSize() ?.takeIf { it.isSpecified() } ?: layer.state.contentSize.takeIf { it.isSpecified() } ?: IntSize(1, 1) SideEffect { layer.state.syncCanvasSize( value = parentCanvasSize, forceScaleAdjustment = true ) if (layer.state.contentSize != measuredContentSize) { layer.state.contentSize = measuredContentSize } } if (canEditLayer && component != null && onUpdateLayer != null) { EditLayerSheet( component = component, visible = layer.state.isInEditMode && !layer.isLocked, onDismiss = { layer.state.isInEditMode = it }, onUpdateLayer = onUpdateLayer, layer = layer ) } } private fun IntSize.isSpecified(): Boolean = width > 0 && height > 0 ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/LayerContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import android.graphics.Bitmap import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.core.graphics.withSave import coil3.request.ImageRequest import coil3.request.allowHardware import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.image.utils.static import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiFont import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.text.OutlineParams import com.t8rin.imagetoolbox.core.ui.widget.text.OutlinedText import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.buildPictureShadowRenderData import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.buildShapeShadowRenderData import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.buildTextShadowRenderData import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.calculateShadowPadding import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.calculateTextLayerMetrics import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.drawShapeLayer import com.t8rin.imagetoolbox.feature.markup_layers.data.utils.resolveShapeLayerRenderData import com.t8rin.imagetoolbox.feature.markup_layers.domain.DomainTextDecoration import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.renderCopy import kotlin.math.roundToInt import androidx.compose.ui.text.style.TextGeometricTransform as ComposeTextGeometricTransform @Composable internal fun LayerContent( modifier: Modifier = Modifier, type: LayerType, groupedLayers: List = emptyList(), textFullSize: Int, maxLines: Int = Int.MAX_VALUE, cornerRadiusPercent: Int = 0, onTextLayout: ((TextLayoutResult) -> Unit)? = null ) { if (groupedLayers.isNotEmpty()) { GroupLayerContent( modifier = modifier, groupedLayers = groupedLayers, referenceSize = textFullSize ) } else { when (type) { is LayerType.Picture -> PictureLayerContent( modifier = modifier, type = type, cornerRadiusPercent = cornerRadiusPercent ) is LayerType.Text -> TextLayerContent( modifier = modifier, type = type, textFullSize = textFullSize, maxLines = maxLines, onTextLayout = onTextLayout ) is LayerType.Shape -> ShapeLayerContent( modifier = modifier, type = type, textFullSize = textFullSize ) } } } @Composable private fun GroupLayerContent( modifier: Modifier, groupedLayers: List, referenceSize: Int ) { val renderLayers = remember(groupedLayers) { groupedLayers.map(UiMarkupLayer::renderCopy) } BoxWithConstraints( modifier = modifier, contentAlignment = Alignment.Center ) { renderLayers.forEach { layer -> Layer( component = null, layer = layer, onActivate = null, onShowContextOptions = null, onUpdateLayer = null, referenceSizeOverride = referenceSize ) } } } @Composable private fun ShapeLayerContent( modifier: Modifier, type: LayerType.Shape, textFullSize: Int ) { val density = LocalDensity.current val shapeContentInsetPx = with(density) { 4.dp.toPx() } BoxWithConstraints( modifier = modifier, contentAlignment = Alignment.Center ) { val renderData = remember( type, textFullSize, constraints.maxWidth, constraints.maxHeight, shapeContentInsetPx ) { resolveShapeLayerRenderData( type = type, referenceSize = textFullSize.toFloat(), maxWidth = constraints.maxWidth.toFloat(), maxHeight = constraints.maxHeight.toFloat(), contentInsetPx = shapeContentInsetPx ) } Box( modifier = Modifier .requiredSize( width = with(density) { renderData.width.toDp() }, height = with(density) { renderData.height.toDp() } ) .drawWithCache { val shadow = buildShapeShadowRenderData( type = type, data = renderData ) onDrawWithContent { shadow?.let { shadowData -> drawContext.canvas.nativeCanvas.drawBitmap( shadowData.bitmap, renderData.contentLeft + shadowData.left, renderData.contentTop + shadowData.top, null ) } drawContent() } }, contentAlignment = Alignment.Center ) { Canvas( modifier = Modifier .fillMaxSize() .padding( start = with(density) { renderData.contentLeft.toDp() }, top = with(density) { renderData.contentTop.toDp() }, end = with(density) { (renderData.width - renderData.contentLeft - renderData.contentWidth).toDp() }, bottom = with(density) { (renderData.height - renderData.contentTop - renderData.contentHeight).toDp() } ) ) { drawShapeLayer( type = type, data = renderData ) } } } } @Composable private fun PictureLayerContent( modifier: Modifier, type: LayerType.Picture, cornerRadiusPercent: Int ) { val density = LocalDensity.current var previewBitmap by remember(type.imageData) { mutableStateOf(null) } val shadowPadding = remember(type.shadow) { calculateShadowPadding(type.shadow) } Box( modifier = modifier .drawWithCache { val contentWidth = ( size.width - shadowPadding.leftPx - shadowPadding.rightPx ) .coerceAtLeast(1f) val contentHeight = ( size.height - shadowPadding.topPx - shadowPadding.bottomPx ) .coerceAtLeast(1f) val shadow = previewBitmap?.let { bitmap -> buildPictureShadowRenderData( sourceBitmap = bitmap, shadow = type.shadow, targetWidth = contentWidth, targetHeight = contentHeight, cornerRadiusPercent = cornerRadiusPercent ) } onDrawWithContent { shadow?.let { shadowData -> drawContext.canvas.nativeCanvas.drawBitmap( shadowData.bitmap, shadowPadding.leftPx + shadowData.left, shadowPadding.topPx + shadowData.top, null ) } drawContent() } }, contentAlignment = Alignment.Center ) { Picture( model = remember(type.imageData) { ImageRequest.Builder(appContext) .data(type.imageData) .static() .allowHardware(false) .size(1600) .build() }, contentScale = ContentScale.Fit, modifier = Modifier .padding( start = with(density) { shadowPadding.leftPx.toDp() }, top = with(density) { shadowPadding.topPx.toDp() }, end = with(density) { shadowPadding.rightPx.toDp() }, bottom = with(density) { shadowPadding.bottomPx.toDp() } ) .clip(AutoCornersShape(cornerRadiusPercent)), showTransparencyChecker = false, allowHardware = false, onSuccess = { previewBitmap = it.result.image.toBitmap() }, onError = { previewBitmap = null } ) } } @Composable private fun TextLayerContent( modifier: Modifier, type: LayerType.Text, textFullSize: Int, maxLines: Int = Int.MAX_VALUE, onTextLayout: ((TextLayoutResult) -> Unit)? = null ) { val context = LocalContext.current val density = LocalDensity.current val style = LocalTextStyle.current val fontFamily = type.font.toUiFont().fontFamily val textMetrics = remember(type, textFullSize, density) { context.calculateTextLayerMetrics( type = type, textFullSize = textFullSize ) } val mergedStyle = remember( style, type, fontFamily, textMetrics, density ) { style.copy( color = type.color.toColor(), fontSize = with(density) { textMetrics.fontSizePx.toSp() }, lineHeight = with(density) { textMetrics.lineHeightPx.toSp() }, fontFamily = fontFamily, textDecoration = TextDecoration.combine( type.decorations.mapNotNull { when (it) { DomainTextDecoration.LineThrough -> TextDecoration.LineThrough DomainTextDecoration.Underline -> TextDecoration.Underline else -> null } } ), fontWeight = if (type.decorations.any { it == DomainTextDecoration.Bold }) { FontWeight.Bold } else { style.fontWeight }, fontStyle = if (type.decorations.any { it == DomainTextDecoration.Italic }) { FontStyle.Italic } else { FontStyle.Normal }, textGeometricTransform = type.geometricTransform?.let { ComposeTextGeometricTransform( scaleX = it.scaleX, skewX = it.skewX ) }, textAlign = when (type.alignment) { LayerType.Text.Alignment.Start -> TextAlign.Start LayerType.Text.Alignment.Center -> TextAlign.Center LayerType.Text.Alignment.End -> TextAlign.End } ) } val outlineParams = remember(type) { type.outline?.let { OutlineParams( color = Color(it.color), stroke = Stroke( width = it.width, cap = StrokeCap.Round, join = StrokeJoin.Round ) ) } } Box( modifier = modifier, contentAlignment = Alignment.Center ) { OutlinedText( text = type.text, style = mergedStyle, outlineParams = outlineParams, maxLines = maxLines, onTextLayout = onTextLayout, modifier = Modifier .drawWithCache { val textLeft = textMetrics.padding.leftPx val textTop = textMetrics.padding.topPx val layoutWidth = (size.width - textMetrics.padding.leftPx - textMetrics.padding.rightPx) .roundToInt() .coerceAtLeast(1) val shadow = buildTextShadowRenderData( type = type, textMetrics = textMetrics, layoutWidth = layoutWidth, maxLines = maxLines.takeIf { it != Int.MAX_VALUE } ) onDrawWithContent { drawRect(type.backgroundColor.toColor()) shadow?.let { shadowData -> drawContext.canvas.nativeCanvas.apply { withSave { scale( 1f / shadowData.rasterScale, 1f / shadowData.rasterScale ) drawBitmap( bitmap = shadowData.bitmap, top = textTop * shadowData.rasterScale + shadowData.top, left = textLeft * shadowData.rasterScale + shadowData.left ) } } } drawContent() } } .padding( start = with(density) { textMetrics.padding.leftPx.toDp() }, top = with(density) { textMetrics.padding.topPx.toDp() }, end = with(density) { textMetrics.padding.rightPx.toDp() }, bottom = with(density) { textMetrics.padding.bottomPx.toDp() } ) ) } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Redo import androidx.compose.material.icons.automirrored.rounded.Undo import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.emoji.Emoji import com.t8rin.imagetoolbox.core.resources.icons.AddSticky import com.t8rin.imagetoolbox.core.resources.icons.EmojiSticky import com.t8rin.imagetoolbox.core.resources.icons.ImageSticky import com.t8rin.imagetoolbox.core.resources.icons.Layers import com.t8rin.imagetoolbox.core.resources.icons.StarSticky import com.t8rin.imagetoolbox.core.resources.icons.TextSticky import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedHorizontalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.sheets.EmojiSelectionSheet import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.domain.withPreferredInitialGeometryFor import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent @Composable internal fun MarkupLayersActions( component: MarkupLayersComponent, showLayersSelection: Boolean, onToggleLayersSection: () -> Unit, onToggleLayersSectionQuick: () -> Unit ) { val layerImagePicker = rememberImagePicker { uri: Uri -> component.addLayer( UiMarkupLayer( type = LayerType.Picture.Image(uri) ) ) } var showTextEnteringDialog by rememberSaveable { mutableStateOf(false) } var showEmojiPicker by rememberSaveable { mutableStateOf(false) } var showShapePicker by rememberSaveable { mutableStateOf(false) } val state = rememberScrollState() Row( modifier = Modifier .fadingEdges(state) .enhancedHorizontalScroll(state) .padding(bottom = 2.dp), verticalAlignment = Alignment.CenterVertically ) { EnhancedIconButton( containerColor = takeColorFromScheme { if (showLayersSelection) tertiary else Color.Transparent }, onLongClick = onToggleLayersSectionQuick, onClick = onToggleLayersSection, enabled = component.layers.isNotEmpty() ) { Icon( imageVector = Icons.Rounded.Layers, contentDescription = null ) } Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.container( shape = ShapeDefaults.circle, color = takeColorFromScheme { if (component.isOptionsExpanded) surface else Color.Transparent }, composeColorOnTopOfBackground = false, clip = false, resultPadding = 0.dp ) ) { EnhancedIconButton( onClick = component::toggleExpandOptions, containerColor = takeColorFromScheme { if (component.isOptionsExpanded) secondaryContainer else Color.Transparent }, contentColor = takeColorFromScheme { if (component.isOptionsExpanded) onSecondaryContainer else LocalContentColor.current } ) { Icon( imageVector = Icons.Outlined.AddSticky, contentDescription = null ) } AnimatedVisibility( visible = component.isOptionsExpanded, enter = fadeIn() + expandHorizontally(), exit = fadeOut() + shrinkHorizontally() ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(end = 4.dp) ) { EnhancedIconButton( onClick = { showTextEnteringDialog = true }, forceMinimumInteractiveComponentSize = false ) { Icon( imageVector = Icons.Outlined.TextSticky, contentDescription = null ) } EnhancedIconButton( onClick = layerImagePicker::pickImage, forceMinimumInteractiveComponentSize = false ) { Icon( imageVector = Icons.Outlined.ImageSticky, contentDescription = null ) } EnhancedIconButton( onClick = { showEmojiPicker = true }, forceMinimumInteractiveComponentSize = false ) { Icon( imageVector = Icons.Outlined.EmojiSticky, contentDescription = null ) } EnhancedIconButton( onClick = { showShapePicker = true }, forceMinimumInteractiveComponentSize = false ) { Icon( imageVector = Icons.Outlined.StarSticky, contentDescription = null ) } } } } val isPortrait by isPortraitOrientationAsState() if (isPortrait) { Spacer(Modifier.width(8.dp)) MarkupLayersUndoRedo( component = component, color = MaterialTheme.colorScheme.surface, removePadding = true ) Spacer(Modifier.width(8.dp)) } } val allEmojis = Emoji.allIcons() EmojiSelectionSheet( selectedEmojiIndex = null, allEmojis = allEmojis, onEmojiPicked = { component.addLayer( UiMarkupLayer( type = LayerType.Picture.Sticker(allEmojis[it]) ) ) showEmojiPicker = false }, visible = showEmojiPicker, onDismiss = { showEmojiPicker = false }, icon = Icons.Outlined.EmojiSticky ) AddShapeLayerDialog( visible = showShapePicker, onDismiss = { showShapePicker = false }, onShapePicked = { mode -> component.addLayer( UiMarkupLayer( type = LayerType.Shape.Default.withPreferredInitialGeometryFor(mode) ) ) showShapePicker = false } ) AddTextLayerDialog( visible = showTextEnteringDialog, onDismiss = { showTextEnteringDialog = false }, onAddLayer = component::addLayer ) } @Composable internal fun MarkupLayersUndoRedo( component: MarkupLayersComponent, color: Color, removePadding: Boolean ) { Row( modifier = Modifier.container( shape = ShapeDefaults.circle, color = color, resultPadding = if (removePadding) 0.dp else 4.dp ) ) { EnhancedIconButton( onClick = component::undo, enabled = component.canUndo ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Undo, contentDescription = "Undo" ) } EnhancedIconButton( onClick = component::redo, enabled = component.canRedo ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Redo, contentDescription = "Redo" ) } } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersContextActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnusedReceiverParameter") package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowLeft import androidx.compose.material.icons.automirrored.rounded.ArrowRight import androidx.compose.material.icons.outlined.FitScreen import androidx.compose.material.icons.rounded.ArrowDropDown import androidx.compose.material.icons.rounded.ArrowDropUp import androidx.compose.material.icons.rounded.CenterFocusStrong import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material.icons.rounded.Lock import androidx.compose.material.icons.rounded.LockOpen import androidx.compose.material.icons.rounded.ScreenRotationAlt import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.Deselect import com.t8rin.imagetoolbox.core.resources.icons.Flip import com.t8rin.imagetoolbox.core.resources.icons.FlipVertical import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.widget.buttons.SupportingButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedDropdownMenu import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSlider import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.value.ValueDialog import com.t8rin.imagetoolbox.core.ui.widget.value.ValueText @Composable internal fun BoxScope.MarkupLayersContextActions( visible: Boolean, onDismiss: () -> Unit, onCopyLayer: () -> Unit, onToggleEditMode: () -> Unit, onRemoveLayer: () -> Unit, onActivateLayer: () -> Unit, isLayerLocked: Boolean, onToggleLayerLock: () -> Unit, isGroupingSelectionMode: Boolean, groupingSelectionCount: Int, onFlipLayerHorizontally: () -> Unit, onFlipLayerVertically: () -> Unit, onMoveLayerBy: (Float, Float) -> Unit, onResetLayerPosition: () -> Unit, onNormalizedPositionXChange: (Float) -> Unit, onNormalizedPositionYChange: (Float) -> Unit, normalizedPositionX: Float?, normalizedPositionY: Float?, scale: Float?, onScaleChange: (Float) -> Unit, onScaleChangeFinished: () -> Unit, rotationDegrees: Float?, onRotationDegreesChange: (Float) -> Unit, onRotationDegreesChangeFinished: () -> Unit ) { var expandedAdjustableAction by rememberSaveable { mutableStateOf(AdjustableActionType.None) } var valueDialogType by rememberSaveable { mutableStateOf(ValueDialogType.None) } val transformActionsEnabled = !isLayerLocked && !isGroupingSelectionMode EnhancedDropdownMenu( expanded = visible, onDismissRequest = { if (expandedAdjustableAction != AdjustableActionType.None) { expandedAdjustableAction = AdjustableActionType.None } else { onDismiss() } }, containerColor = MaterialTheme.colorScheme.surface ) { Column( modifier = Modifier.padding(horizontal = 8.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { ClickableTile( onClick = { onToggleEditMode() onDismiss() }, enabled = transformActionsEnabled, icon = Icons.Rounded.MiniEdit, text = stringResource(R.string.edit), modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) ClickableTile( onClick = onCopyLayer, enabled = !isGroupingSelectionMode, icon = Icons.Rounded.ContentCopy, text = stringResource(R.string.copy), modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) ClickableTile( onClick = onRemoveLayer, enabled = !isGroupingSelectionMode, icon = Icons.Rounded.Delete, text = stringResource(R.string.delete), modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) } Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { ClickableTile( onClick = onActivateLayer, enabled = !isGroupingSelectionMode || groupingSelectionCount > 0, icon = Icons.Outlined.Deselect, text = stringResource(R.string.clear_selection), modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) ClickableTile( onClick = onFlipLayerHorizontally, enabled = transformActionsEnabled, icon = Icons.Outlined.Flip, text = stringResource(R.string.horizontal_flip), modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) ClickableTile( onClick = onFlipLayerVertically, enabled = transformActionsEnabled, icon = Icons.Outlined.FlipVertical, text = stringResource(R.string.vertical_flip), modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) } val activeActionContainerColor = takeColorFromScheme { surfaceContainerLow.blend( color = tertiaryContainer, fraction = 0.7f ) } Row( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { ClickableTile( onClick = { onToggleLayerLock() onDismiss() }, enabled = !isGroupingSelectionMode, icon = if (isLayerLocked) Icons.Rounded.LockOpen else Icons.Rounded.Lock, text = stringResource( if (isLayerLocked) R.string.unlock else R.string.lock ), modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) ClickableTile( onClick = { if (transformActionsEnabled) { expandedAdjustableAction = expandedAdjustableAction.toggle(AdjustableActionType.Rotation) } }, enabled = transformActionsEnabled, icon = Icons.Rounded.ScreenRotationAlt, text = stringResource(R.string.rotation), containerColor = if (expandedAdjustableAction == AdjustableActionType.Rotation) { activeActionContainerColor } else { MaterialTheme.colorScheme.surfaceContainerLow }, modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) ClickableTile( onClick = { if (transformActionsEnabled) { expandedAdjustableAction = expandedAdjustableAction.toggle(AdjustableActionType.Scale) } }, enabled = transformActionsEnabled, icon = Icons.Outlined.FitScreen, text = stringResource(R.string.scale), containerColor = if (expandedAdjustableAction == AdjustableActionType.Scale) { activeActionContainerColor } else { MaterialTheme.colorScheme.surfaceContainerLow }, modifier = Modifier.size( width = 66.dp, height = 50.dp ) ) } AnimatedContent( targetState = expandedAdjustableAction, modifier = Modifier.fillMaxWidth() ) { action -> when (action) { AdjustableActionType.Rotation -> AdjustableActionCard( modifier = Modifier .fillMaxWidth() .heightIn(min = 50.dp), title = stringResource(R.string.rotation), value = rotationDegrees ?: 0f, valueRange = 0f..360f, enabled = transformActionsEnabled, sliderEnabled = rotationDegrees != null, onValueClick = { expandedAdjustableAction = AdjustableActionType.None onDismiss() valueDialogType = ValueDialogType.Rotation }, onValueChange = onRotationDegreesChange, onValueChangeFinished = onRotationDegreesChangeFinished ) AdjustableActionType.Scale -> AdjustableActionCard( modifier = Modifier .fillMaxWidth() .heightIn(min = 50.dp), title = stringResource(R.string.scale), value = scale ?: 1f, valueRange = 0.1f..10f, enabled = transformActionsEnabled, sliderEnabled = scale != null, onValueClick = { expandedAdjustableAction = AdjustableActionType.None onDismiss() valueDialogType = ValueDialogType.Scale }, onValueChange = onScaleChange, onValueChangeFinished = onScaleChangeFinished ) AdjustableActionType.None -> Unit } } CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .fillMaxWidth() .height(100.dp) ) { val buttonContainerColor = takeColorFromScheme { surfaceContainerLow.blend( color = primaryContainer, fraction = 0.2f ) } ClickableTile( onClick = { onMoveLayerBy(-1f, 0f) }, onHoldStep = { onMoveLayerBy(-1f, 0f) }, enabled = transformActionsEnabled, icon = Icons.AutoMirrored.Rounded.ArrowLeft, text = null, containerColor = buttonContainerColor, modifier = Modifier .width(66.dp) .fillMaxHeight() ) Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .width(66.dp) .fillMaxHeight() ) { ClickableTile( onClick = { onMoveLayerBy(0f, -1f) }, onHoldStep = { onMoveLayerBy(0f, -1f) }, enabled = transformActionsEnabled, icon = Icons.Rounded.ArrowDropUp, text = null, modifier = Modifier .weight(1f) .fillMaxWidth(), containerColor = buttonContainerColor ) ClickableTile( onClick = { onMoveLayerBy(0f, 1f) }, onHoldStep = { onMoveLayerBy(0f, 1f) }, enabled = transformActionsEnabled, icon = Icons.Rounded.ArrowDropDown, text = null, modifier = Modifier .weight(1f) .fillMaxWidth(), containerColor = buttonContainerColor ) } ClickableTile( onClick = { onMoveLayerBy(1f, 0f) }, onHoldStep = { onMoveLayerBy(1f, 0f) }, enabled = transformActionsEnabled, icon = Icons.AutoMirrored.Rounded.ArrowRight, text = null, containerColor = buttonContainerColor, modifier = Modifier .width(66.dp) .fillMaxHeight() ) } } if (normalizedPositionX != null && normalizedPositionY != null) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.container( shape = ShapeDefaults.extraSmall, color = takeColorFromScheme { surfaceContainerLow.blend(tertiaryContainer, 0.3f) }, resultPadding = 0.dp ) ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.weight(1f) ) { val x = normalizedPositionX.roundTo(3) val y = normalizedPositionY.roundTo(3) AutoSizeText( text = "X: $x", style = MaterialTheme.typography.labelSmall.copy( fontSize = 12.sp ), textAlign = TextAlign.Center, modifier = Modifier .then( if (transformActionsEnabled) { Modifier.hapticsClickable { onDismiss() valueDialogType = ValueDialogType.PositionX } } else Modifier ) .alpha(if (transformActionsEnabled) 1f else 0.5f) .padding( start = 8.dp, top = 8.dp, bottom = 8.dp, end = 4.dp ) ) AutoSizeText( text = "Y: $y", style = MaterialTheme.typography.labelSmall.copy( fontSize = 12.sp ), textAlign = TextAlign.Center, modifier = Modifier .then( if (transformActionsEnabled) { Modifier.hapticsClickable { onDismiss() valueDialogType = ValueDialogType.PositionY } } else Modifier ) .alpha(if (transformActionsEnabled) 1f else 0.5f) .padding( start = 4.dp, top = 8.dp, bottom = 8.dp, end = 8.dp ) ) } SupportingButton( icon = Icons.Rounded.CenterFocusStrong, onClick = { if (transformActionsEnabled) { onResetLayerPosition() } }, containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer, shape = ShapeDefaults.extremeSmall, modifier = Modifier .padding(4.dp) .alpha(if (transformActionsEnabled) 1f else 0.5f) ) } } } } val activeValueDialogType = valueDialogType ValueDialog( roundTo = when (activeValueDialogType) { ValueDialogType.Rotation -> null ValueDialogType.Scale -> 3 ValueDialogType.PositionX, ValueDialogType.PositionY -> 3 ValueDialogType.None -> null }, valueRange = when (activeValueDialogType) { ValueDialogType.Rotation -> 0f..360f ValueDialogType.Scale -> 0.1f..10f ValueDialogType.PositionX, ValueDialogType.PositionY -> -2f..2f ValueDialogType.None -> 0f..1f }, valueState = when (activeValueDialogType) { ValueDialogType.Rotation -> (rotationDegrees ?: 0f).toString() ValueDialogType.Scale -> (scale ?: 1f).toString() ValueDialogType.PositionX -> (normalizedPositionX?.roundTo(3) ?: 0f).toString() ValueDialogType.PositionY -> (normalizedPositionY?.roundTo(3) ?: 0f).toString() ValueDialogType.None -> "0" }, expanded = activeValueDialogType != ValueDialogType.None, onDismiss = { valueDialogType = ValueDialogType.None }, onValueUpdate = { when (activeValueDialogType) { ValueDialogType.Rotation -> { onRotationDegreesChange(it) onRotationDegreesChangeFinished() } ValueDialogType.Scale -> { onScaleChange(it) onScaleChangeFinished() } ValueDialogType.PositionX -> { onNormalizedPositionXChange(it) } ValueDialogType.PositionY -> { onNormalizedPositionYChange(it) } ValueDialogType.None -> Unit } } ) } @Composable private fun AdjustableActionCard( title: String, value: Float, valueRange: ClosedFloatingPointRange, enabled: Boolean, sliderEnabled: Boolean, onValueClick: () -> Unit, onValueChange: (Float) -> Unit, onValueChangeFinished: () -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier .container( shape = ShapeDefaults.extraSmall, color = MaterialTheme.colorScheme.surfaceContainerLow, resultPadding = 0.dp ) .alpha(if (enabled) 1f else 0.5f) .padding(6.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = title, style = MaterialTheme.typography.labelLarge, modifier = Modifier.weight(1f) ) ValueText( value = value, onClick = if (enabled) { onValueClick } else null, modifier = Modifier ) } EnhancedSlider( value = value, enabled = enabled && sliderEnabled, onValueChange = onValueChange, onValueChangeFinished = onValueChangeFinished, valueRange = valueRange, drawContainer = false, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) ) } } private enum class AdjustableActionType { None, Rotation, Scale } private fun AdjustableActionType.toggle(target: AdjustableActionType): AdjustableActionType { return if (this == target) AdjustableActionType.None else target } private enum class ValueDialogType { None, Rotation, Scale, PositionX, PositionY } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersNoDataControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BackgroundColor import com.t8rin.imagetoolbox.core.resources.icons.ImagesMode import com.t8rin.imagetoolbox.core.resources.icons.ImagesearchRoller import com.t8rin.imagetoolbox.core.resources.icons.Stacks import com.t8rin.imagetoolbox.core.resources.icons.Unarchive import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.restrict import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent @Composable internal fun MarkupLayersNoDataControls( component: MarkupLayersComponent, onPickImage: () -> Unit, onOpenProject: () -> Unit ) { var showBackgroundDrawingSetup by rememberSaveable { mutableStateOf(false) } val cutout = WindowInsets.displayCutout.asPaddingValues() LazyVerticalStaggeredGrid( modifier = Modifier.fillMaxHeight(), columns = StaggeredGridCells.Adaptive(300.dp), horizontalArrangement = Arrangement.spacedBy( space = 12.dp, alignment = Alignment.CenterHorizontally ), verticalItemSpacing = 12.dp, contentPadding = PaddingValues( bottom = 12.dp + WindowInsets .navigationBars .asPaddingValues() .calculateBottomPadding(), top = 12.dp, end = 12.dp + cutout.calculateEndPadding( LocalLayoutDirection.current ), start = 12.dp + cutout.calculateStartPadding( LocalLayoutDirection.current ) ), flingBehavior = enhancedFlingBehavior() ) { item { PreferenceItem( onClick = onPickImage, startIcon = Icons.Outlined.ImagesMode, title = stringResource(R.string.layers_on_image), subtitle = stringResource(R.string.layers_on_image_sub), modifier = Modifier.fillMaxWidth() ) } item { PreferenceItem( onClick = { showBackgroundDrawingSetup = true }, startIcon = Icons.Outlined.ImagesearchRoller, title = stringResource(R.string.layers_on_background), subtitle = stringResource(R.string.layers_on_background_sub), modifier = Modifier.fillMaxWidth() ) } item { PreferenceItem( onClick = onOpenProject, startIcon = Icons.Outlined.Unarchive, title = stringResource(R.string.open_markup_project), subtitle = stringResource(R.string.open_markup_project_sub), modifier = Modifier.fillMaxWidth() ) } } val screenSize = LocalScreenSize.current val screenWidth = screenSize.widthPx val screenHeight = screenSize.heightPx var width by remember( showBackgroundDrawingSetup, screenWidth ) { mutableIntStateOf(screenWidth) } var height by remember( showBackgroundDrawingSetup, screenHeight ) { mutableIntStateOf(screenHeight) } var sheetBackgroundColor by rememberSaveable( showBackgroundDrawingSetup, stateSaver = ColorSaver ) { mutableStateOf(Color.White) } EnhancedModalBottomSheet( title = { TitleItem( text = stringResource(R.string.markup_layers), icon = Icons.Rounded.Stacks ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showBackgroundDrawingSetup = false component.startDrawOnBackground( reqWidth = width, reqHeight = height, color = sheetBackgroundColor ) } ) { AutoSizeText(stringResource(R.string.ok)) } }, sheetContent = { Column(Modifier.enhancedVerticalScroll(rememberScrollState())) { Row( Modifier .padding(16.dp) .container(shape = ShapeDefaults.extraLarge) ) { RoundedTextField( value = width.takeIf { it != 0 }?.toString() ?: "", onValueChange = { width = it.restrict(8192).toIntOrNull() ?: 0 }, shape = ShapeDefaults.smallStart, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = { Text(stringResource(R.string.width, " ")) }, modifier = Modifier .weight(1f) .padding( start = 8.dp, top = 8.dp, bottom = 8.dp, end = 2.dp ) ) RoundedTextField( value = height.takeIf { it != 0 }?.toString() ?: "", onValueChange = { height = it.restrict(8192).toIntOrNull() ?: 0 }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), shape = ShapeDefaults.smallEnd, label = { Text(stringResource(R.string.height, " ")) }, modifier = Modifier .weight(1f) .padding( start = 2.dp, top = 8.dp, bottom = 8.dp, end = 8.dp ), ) } ColorRowSelector( value = sheetBackgroundColor, onValueChange = { sheetBackgroundColor = it }, icon = Icons.Outlined.BackgroundColor, modifier = Modifier .padding( start = 16.dp, end = 16.dp, bottom = 16.dp ) .container(ShapeDefaults.extraLarge) ) } }, visible = showBackgroundDrawingSetup, onDismiss = { showBackgroundDrawingSetup = it } ) } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenu.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Build import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.StackSticky import com.t8rin.imagetoolbox.core.resources.icons.StackStickyOff import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSlider import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.uiCornerRadiusPercent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent import com.t8rin.modalsheet.FullscreenPopup @Composable internal fun MarkupLayersSideMenu( component: MarkupLayersComponent, visible: Boolean, onDismiss: () -> Unit, isContextOptionsVisible: Boolean, onContextOptionsVisibleChange: (Boolean) -> Unit ) { val layers = component.layers FullscreenPopup { BoxWithConstraints( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd ) { if (visible) { BackHandler(onBack = onDismiss) Box( Modifier .fillMaxSize() .tappable { onDismiss() } ) } val maxHeightFull = this.maxHeight AnimatedVisibility( visible = visible, enter = fadeIn() + expandHorizontally(), exit = fadeOut() + shrinkHorizontally() ) { Surface( color = Color.Transparent ) { Column( modifier = Modifier .padding(8.dp) .padding( WindowInsets.systemBars .union(WindowInsets.displayCutout) .asPaddingValues() ) .height( minOf(maxHeightFull, 480.dp) ) .width(168.dp) .container( color = MaterialTheme.colorScheme.surfaceContainer.copy(0.9f), composeColorOnTopOfBackground = false, resultPadding = 0.dp ), horizontalAlignment = Alignment.CenterHorizontally ) { val activeLayer by remember(layers) { derivedStateOf { layers.find { it.state.isActive } } } val normalizedPosition by remember(activeLayer) { derivedStateOf { activeLayer?.let { layer -> layer.state.normalizedPosition( cornerRadiusPercent = layer.uiCornerRadiusPercent() ) } } } val scale by remember(activeLayer) { derivedStateOf { activeLayer?.state?.scale?.roundTo(3) } } val normalizedRotationDegrees by remember(activeLayer) { derivedStateOf { activeLayer?.state?.rotation ?.normalizeForUi() ?.roundTo(1) } } Scaffold( topBar = { Column( modifier = Modifier.container( shape = RectangleShape, color = MaterialTheme.colorScheme.surfaceContainerHigh ) ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { EnhancedIconButton( onClick = { activeLayer?.let(component::removeLayer) }, enabled = activeLayer != null && !component.isGroupingSelectionMode ) { Icon( imageVector = Icons.Rounded.Delete, contentDescription = null ) } Spacer(Modifier.weight(1f)) AnimatedContent( targetState = (component.groupingSelectionCount >= 2) to (activeLayer?.isGroup == true && !component.isGroupingSelectionMode) ) { (canGroupLayers, canUngroupLayer) -> if (canGroupLayers) { EnhancedIconButton( onClick = component::groupSelectedLayers ) { Icon( imageVector = Icons.Outlined.StackSticky, contentDescription = null ) } } else if (canUngroupLayer) { EnhancedIconButton( onClick = { activeLayer?.let(component::ungroupLayer) } ) { Icon( imageVector = Icons.Outlined.StackStickyOff, contentDescription = null ) } } else { Spacer(Modifier.height(48.dp)) } } Box { EnhancedIconButton( onClick = { onContextOptionsVisibleChange(true) }, enabled = activeLayer != null || component.isGroupingSelectionMode ) { Icon( imageVector = Icons.Rounded.Build, contentDescription = null ) } MarkupLayersContextActions( visible = isContextOptionsVisible && (activeLayer != null || component.isGroupingSelectionMode), onDismiss = { onContextOptionsVisibleChange(false) }, onCopyLayer = { activeLayer?.let(component::copyLayer) }, onToggleEditMode = { activeLayer ?.takeIf { !it.isLocked } ?.state ?.isInEditMode = true }, onRemoveLayer = { activeLayer?.let(component::removeLayer) }, onActivateLayer = { component.clearSelections() }, isLayerLocked = activeLayer?.isLocked == true, onToggleLayerLock = { activeLayer?.let(component::toggleLayerLock) }, isGroupingSelectionMode = component.isGroupingSelectionMode, groupingSelectionCount = component.groupingSelectionCount, onFlipLayerHorizontally = { activeLayer?.let { layer -> component.updateLayerState(layer) { isFlippedHorizontally = !isFlippedHorizontally } } }, onFlipLayerVertically = { activeLayer?.let { layer -> component.updateLayerState(layer) { isFlippedVertically = !isFlippedVertically } } }, onMoveLayerBy = { dx, dy -> activeLayer?.let { layer -> component.moveLayerBy( layer = layer, offsetChange = Offset(dx, dy) ) } }, onResetLayerPosition = { activeLayer?.let(component::resetLayerPosition) }, onNormalizedPositionXChange = { x -> activeLayer?.let { layer -> component.setLayerNormalizedPosition( layer = layer, x = x ) } }, onNormalizedPositionYChange = { y -> activeLayer?.let { layer -> component.setLayerNormalizedPosition( layer = layer, y = y ) } }, normalizedPositionX = normalizedPosition?.x, normalizedPositionY = normalizedPosition?.y, scale = scale, onScaleChange = { component.beginHistoryTransaction() activeLayer?.let { layer -> component.setLayerScale( layer = layer, scale = it, commitToHistory = false ) } }, onScaleChangeFinished = { component.commitHistoryTransaction() }, rotationDegrees = normalizedRotationDegrees, onRotationDegreesChange = { component.beginHistoryTransaction() activeLayer?.let { layer -> component.updateLayerState( layer = layer, commitToHistory = false ) { rotation = it.roundTo(1) } } }, onRotationDegreesChangeFinished = { component.commitHistoryTransaction() } ) } } EnhancedSlider( value = activeLayer?.state?.alpha ?: 1f, enabled = activeLayer != null && activeLayer?.isLocked != true && !component.isGroupingSelectionMode, onValueChange = { component.beginHistoryTransaction() activeLayer?.let { layer -> component.updateLayerState( layer = layer, commitToHistory = false ) { alpha = it } } }, onValueChangeFinished = component::commitHistoryTransaction, valueRange = 0f..1f, drawContainer = false, modifier = Modifier.padding(horizontal = 8.dp) ) } }, contentWindowInsets = WindowInsets(0) ) { contentPadding -> MarkupLayersSideMenuColumn( modifier = Modifier.fillMaxSize(), contentPadding = contentPadding, layers = layers, onReorderLayers = component::reorderLayers, onActivateLayer = component::activateLayer, isGroupingSelectionMode = component.isGroupingSelectionMode, groupingSelectionIds = component.groupingSelectionIds, onStartGroupingSelection = component::startGroupingSelection, onToggleGroupingSelection = component::toggleGroupingSelection, onToggleLayerVisibility = { layer -> component.updateLayerState( layer = layer, allowLocked = true ) { isVisible = !isVisible } }, onUnlockLayer = component::toggleLayerLock ) } } } } } } } private fun Float.normalizeForUi(): Float { val normalized = this % 360f return when { normalized < 0f -> normalized + 360f normalized == 0f && this > 0f -> 360f else -> normalized } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersSideMenuColumn.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.plus import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.material.icons.rounded.Lock import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material.icons.rounded.VisibilityOff import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.icons.EmojiSticky import com.t8rin.imagetoolbox.core.resources.icons.ImageSticky import com.t8rin.imagetoolbox.core.resources.icons.StackSticky import com.t8rin.imagetoolbox.core.resources.icons.StarSticky import com.t8rin.imagetoolbox.core.resources.icons.TextSticky import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.other.AnimatedBorder import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.toPreviewGroupData import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.uiCornerRadiusPercent import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState import kotlin.math.abs import kotlin.math.cos import kotlin.math.min import kotlin.math.sin @Composable internal fun MarkupLayersSideMenuColumn( modifier: Modifier, contentPadding: PaddingValues, layers: List, onReorderLayers: (List) -> Unit, onActivateLayer: (UiMarkupLayer) -> Unit, isGroupingSelectionMode: Boolean, groupingSelectionIds: Set, onStartGroupingSelection: (UiMarkupLayer) -> Unit, onToggleGroupingSelection: (UiMarkupLayer) -> Unit, onToggleLayerVisibility: (UiMarkupLayer) -> Unit, onUnlockLayer: (UiMarkupLayer) -> Unit ) { val haptics = LocalHapticFeedback.current val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState( lazyListState = lazyListState ) { from, to -> if (isGroupingSelectionMode) return@rememberReorderableLazyListState haptics.press() val data = layers.toMutableList().apply { add(to.index, removeAt(from.index)) } onReorderLayers(data) } LaunchedEffect(Unit) { val index = layers.indexOfFirst { it.state.isActive } .takeIf { it >= 0 } ?: return@LaunchedEffect lazyListState.scrollToItem(index) } LazyColumn( state = lazyListState, modifier = modifier, contentPadding = contentPadding + PaddingValues( top = 12.dp, bottom = 12.dp, start = 8.dp, end = 4.dp ), verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.Bottom), reverseLayout = true, flingBehavior = enhancedFlingBehavior() ) { items( items = layers, key = { it.id } ) { layer -> ReorderableItem( state = reorderableLazyListState, key = layer.id ) { val type = layer.type val state = layer.state val isSelectedForGrouping = layer.id in groupingSelectionIds val density = LocalDensity.current val previewData by remember(layer) { derivedStateOf { layer.takeIf(UiMarkupLayer::isGroup)?.toPreviewGroupData() } } val previewContentSize = remember(state.contentSize, previewData) { previewData?.contentSize ?: state.contentSize.takeIf(IntSize::isSpecified) } val previewTextFullSize by remember(state.canvasSize) { derivedStateOf { min(state.canvasSize.width, state.canvasSize.height) .coerceAtLeast(1) } } val previewReferenceSize = remember(previewData, previewTextFullSize) { previewData?.referenceSize ?: previewTextFullSize } val boxSize = 92.dp Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Icon( imageVector = if (layer.state.isVisible) { Icons.Rounded.Visibility } else { Icons.Rounded.VisibilityOff }, contentDescription = null, modifier = Modifier .hapticsClickable( indication = null, interactionSource = null ) { onToggleLayerVisibility(layer) } ) Spacer(Modifier.width(8.dp)) Box( modifier = Modifier .size(boxSize) .clip(ShapeDefaults.extraSmall) .transparencyChecker() .hapticsCombinedClickable( onLongClick = { onStartGroupingSelection(layer) } ) { if (isGroupingSelectionMode) { onToggleGroupingSelection(layer) } else if (!layer.isLocked) { onActivateLayer(layer) } } ) { val borderAlpha by animateFloatAsState( if (state.isActive || isSelectedForGrouping) 1f else 0f ) BoxWithConstraints( modifier = Modifier .fillMaxSize() .background( MaterialTheme.colorScheme.primary.copy( 0.16f * borderAlpha ) ) .padding(6.dp), contentAlignment = Alignment.Center ) { val scope = this val previewContainerSize = remember(scope.constraints) { IntSize( width = scope.constraints.maxWidth, height = scope.constraints.maxHeight ) } val previewFitScale by remember( previewContentSize, previewContainerSize, state.rotation ) { derivedStateOf { previewContentSize?.let { contentSize -> calculatePreviewFitScale( contentSize = contentSize, containerSize = previewContainerSize, rotation = if (layer.isGroup) 0f else state.rotation ) } ?: 1f } } val previewModifier = remember( previewContentSize, density, scope.maxWidth, scope.maxHeight ) { previewContentSize?.let { contentSize -> Modifier.requiredSize( width = with(density) { contentSize.width.toDp() }, height = with(density) { contentSize.height.toDp() } ) } ?: Modifier.sizeIn( maxWidth = scope.maxWidth, maxHeight = scope.maxHeight ) } Box( modifier = Modifier .fillMaxSize(0.96f) .graphicsLayer { scaleX = if (layer.isGroup) { previewFitScale } else { previewFitScale * if (state.isFlippedHorizontally) -1f else 1f } scaleY = if (layer.isGroup) { previewFitScale } else { previewFitScale * if (state.isFlippedVertically) -1f else 1f } rotationZ = if (layer.isGroup) 0f else state.rotation alpha = if (layer.isGroup) 1f else state.alpha compositingStrategy = if ((if (layer.isGroup) 1f else state.alpha) >= 1f) { CompositingStrategy.Auto } else { CompositingStrategy.ModulateAlpha } } .padding(4.dp), contentAlignment = Alignment.Center ) { LayerContent( modifier = previewModifier, type = type, groupedLayers = previewData?.layers ?: layer.groupedLayers, textFullSize = previewReferenceSize, maxLines = layer.visibleLineCount ?: Int.MAX_VALUE, cornerRadiusPercent = layer.uiCornerRadiusPercent() ) } } AnimatedBorder( modifier = Modifier.matchParentSize(), alpha = borderAlpha, scale = 1f, shape = ShapeDefaults.extraSmall ) Box( modifier = Modifier .align(Alignment.TopStart) .padding(3.dp) .clip(ShapeDefaults.extraSmall) .background(MaterialTheme.colorScheme.primaryContainer.copy(0.8f)) .padding(2.dp) ) { Icon( imageVector = if (layer.isGroup) { Icons.Outlined.StackSticky } else { when (layer.type) { is LayerType.Picture.Image -> Icons.Outlined.ImageSticky is LayerType.Picture.Sticker -> Icons.Outlined.EmojiSticky is LayerType.Text -> Icons.Outlined.TextSticky is LayerType.Shape -> Icons.Outlined.StarSticky } }, contentDescription = null, tint = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.size(13.dp) ) } if (layer.isLocked) { Box( modifier = Modifier .align(Alignment.TopEnd) .padding(6.dp) .clip(ShapeDefaults.extraSmall) .background(MaterialTheme.colorScheme.surfaceContainerHigh) .hapticsClickable { onUnlockLayer(layer) } .padding(4.dp) ) { Icon( imageVector = Icons.Rounded.Lock, contentDescription = null, modifier = Modifier.size(16.dp) ) } } } Spacer(Modifier.width(8.dp)) Icon( imageVector = Icons.Rounded.DragHandle, contentDescription = null, modifier = if (isGroupingSelectionMode) { Modifier.graphicsLayer { alpha = 0.35f } } else { Modifier.longPressDraggableHandle( onDragStarted = { haptics.longPress() } ) } ) } } } } } private fun calculatePreviewFitScale( contentSize: IntSize, containerSize: IntSize, rotation: Float ): Float { if (!contentSize.isSpecified() || !containerSize.isSpecified()) return 1f val radians = Math.toRadians(rotation.toDouble()) val cos = abs(cos(radians)).toFloat() val sin = abs(sin(radians)).toFloat() val rotatedWidth = contentSize.width * cos + contentSize.height * sin val rotatedHeight = contentSize.width * sin + contentSize.height * cos return min( containerSize.width / rotatedWidth.coerceAtLeast(1f), containerSize.height / rotatedHeight.coerceAtLeast(1f) ).coerceIn(0f, 1f) } private fun IntSize.isSpecified(): Boolean = width > 0 && height > 0 ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/MarkupLayersTopAppBarActions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import android.net.Uri import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.Icon import androidx.compose.material3.SheetValue import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.BackgroundBehavior import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent import kotlinx.coroutines.launch @Composable internal fun MarkupLayersTopAppBarActions( component: MarkupLayersComponent, scaffoldState: BottomSheetScaffoldState ) { val isPortrait by isPortraitOrientationAsState() val scope = rememberCoroutineScope() if (component.backgroundBehavior == BackgroundBehavior.None) TopAppBarEmoji() else { if (isPortrait) { EnhancedIconButton( onClick = { scope.launch { if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) { scaffoldState.bottomSheetState.partialExpand() } else { scaffoldState.bottomSheetState.expand() } } }, ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = stringResource(R.string.properties) ) } } EnhancedIconButton( onClick = component::clearLayers, enabled = component.layers.isNotEmpty() ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.clear) ) } var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.backgroundBehavior !is BackgroundBehavior.None, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { uri -> editSheetData = listOf(uri) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/ShapeLayerParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FormatColorFill import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SquareFoot import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.domain.ShapeMode import com.t8rin.imagetoolbox.feature.markup_layers.domain.arrowAngle import com.t8rin.imagetoolbox.feature.markup_layers.domain.arrowSizeScale import com.t8rin.imagetoolbox.feature.markup_layers.domain.cornerRadius import com.t8rin.imagetoolbox.feature.markup_layers.domain.innerRadiusRatio import com.t8rin.imagetoolbox.feature.markup_layers.domain.isOutlinedShapeMode import com.t8rin.imagetoolbox.feature.markup_layers.domain.isRegular import com.t8rin.imagetoolbox.feature.markup_layers.domain.ordinal import com.t8rin.imagetoolbox.feature.markup_layers.domain.outlinedFillColorInt import com.t8rin.imagetoolbox.feature.markup_layers.domain.rotationDegrees import com.t8rin.imagetoolbox.feature.markup_layers.domain.updateArrow import com.t8rin.imagetoolbox.feature.markup_layers.domain.updatePolygon import com.t8rin.imagetoolbox.feature.markup_layers.domain.updateRect import com.t8rin.imagetoolbox.feature.markup_layers.domain.updateStar import com.t8rin.imagetoolbox.feature.markup_layers.domain.usesStrokeWidth import com.t8rin.imagetoolbox.feature.markup_layers.domain.vertices import com.t8rin.imagetoolbox.feature.markup_layers.domain.withOutlinedFillColor import com.t8rin.imagetoolbox.feature.markup_layers.domain.withPreferredGeometryFor import com.t8rin.imagetoolbox.feature.markup_layers.domain.withSavedStateFrom import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.icon import kotlin.math.roundToInt @Composable internal fun ShapeLayerParamsSelector( layer: UiMarkupLayer, type: LayerType.Shape, onUpdateLayer: (UiMarkupLayer) -> Unit, onUpdateLayerContinuously: (UiMarkupLayer) -> Unit, onContinuousEditFinished: () -> Unit ) { Column( modifier = Modifier.container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface, ) ) { EnhancedButtonGroup( enabled = true, itemCount = ShapeMode.entries.size, title = { Text( text = stringResource(R.string.shape), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) }, selectedIndex = type.shapeMode.ordinal, activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, itemContent = { Icon( imageVector = ShapeMode.entries[it].kind.icon, contentDescription = null ) }, onIndexChange = { val mode = ShapeMode.entries[it] .withSavedStateFrom(type.shapeMode) .let { candidate -> if (candidate.isOutlinedShapeMode() && candidate.outlinedFillColorInt() == null) { candidate.withOutlinedFillColor( color = type.shapeMode.outlinedFillColorInt() ) } else { candidate } } onUpdateLayer( layer.copy( cornerRadiusPercent = 0, type = type.withPreferredGeometryFor(mode) ) ) } ) } Spacer(modifier = Modifier.height(8.dp)) ShapeAppearanceSection( layer = layer, type = type, onUpdateLayer = onUpdateLayer, onUpdateLayerContinuously = onUpdateLayerContinuously, onContinuousEditFinished = onContinuousEditFinished ) Spacer(modifier = Modifier.height(8.dp)) ShapeSizeSection( layer = layer, type = type, onUpdateLayerContinuously = onUpdateLayerContinuously, onContinuousEditFinished = onContinuousEditFinished ) AnimatedContent( targetState = type, contentKey = { it.shapeMode.kind }, modifier = Modifier.fillMaxWidth() ) { animatedType -> Column { ShapeSpecificControls( layer = layer, type = animatedType, onUpdateLayer = onUpdateLayer, onUpdateLayerContinuously = onUpdateLayerContinuously, onContinuousEditFinished = onContinuousEditFinished ) } } Spacer(modifier = Modifier.height(4.dp)) DropShadowSection( shadow = type.shadow, onShadowChange = { shadow -> onUpdateLayer( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onShadowChangeContinuously = { shadow -> onUpdateLayerContinuously( layer.copy( type = type.copy( shadow = shadow ) ) ) }, onContinuousEditFinished = onContinuousEditFinished ) } @Composable private fun ShapeAppearanceSection( layer: UiMarkupLayer, type: LayerType.Shape, onUpdateLayer: (UiMarkupLayer) -> Unit, onUpdateLayerContinuously: (UiMarkupLayer) -> Unit, onContinuousEditFinished: () -> Unit ) { val mode = type.shapeMode val showFillColor = mode.isOutlinedShapeMode() val showStrokeWidth = mode.usesStrokeWidth() val singleItemShape = ShapeDefaults.large ColorRowSelector( value = type.color.toColor(), onValueChange = { onUpdateLayer( layer.copy( type = type.copy(color = it.toArgb()) ) ) }, title = stringResource(R.string.color), modifier = Modifier.container( shape = when { showFillColor || showStrokeWidth -> ShapeDefaults.top else -> singleItemShape }, color = MaterialTheme.colorScheme.surface ) ) AnimatedVisibility( visible = showFillColor, modifier = Modifier.fillMaxWidth() ) { Column { Spacer(modifier = Modifier.height(4.dp)) ColorRowSelector( value = mode.outlinedFillColorInt()?.toColor(), onValueChange = { onUpdateLayer( layer.copy( type = type.copy( shapeMode = mode.withOutlinedFillColor(it.toArgb()) ) ) ) }, onNullClick = { onUpdateLayer( layer.copy( type = type.copy( shapeMode = mode.withOutlinedFillColor(null) ) ) ) }, title = stringResource(R.string.fill_color), icon = Icons.Outlined.FormatColorFill, allowAlpha = true, modifier = Modifier.container( shape = if (showStrokeWidth) ShapeDefaults.center else ShapeDefaults.bottom, color = MaterialTheme.colorScheme.surface ) ) } } AnimatedVisibility( visible = showStrokeWidth, modifier = Modifier.fillMaxWidth() ) { Column { Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = type.strokeWidth, title = stringResource(R.string.line_width), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy(strokeWidth = it) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 1f..64f, shape = if (showFillColor) ShapeDefaults.bottom else ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface ) } } } @Composable private fun ShapeSizeSection( layer: UiMarkupLayer, type: LayerType.Shape, onUpdateLayerContinuously: (UiMarkupLayer) -> Unit, onContinuousEditFinished: () -> Unit ) { EnhancedSliderItem( value = type.widthRatio, title = stringResource(R.string.width, "").trim(), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy(widthRatio = it.roundToTwoDigits()) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0.05f..0.5f, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = type.heightRatio, title = stringResource(R.string.height, "").trim(), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy(heightRatio = it.roundToTwoDigits()) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0.05f..0.5f, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface ) } @Composable private fun ShapeSpecificControls( layer: UiMarkupLayer, type: LayerType.Shape, onUpdateLayer: (UiMarkupLayer) -> Unit, onUpdateLayerContinuously: (UiMarkupLayer) -> Unit, onContinuousEditFinished: () -> Unit ) { when (val mode = type.shapeMode) { is ShapeMode.Arrow, is ShapeMode.DoubleArrow, is ShapeMode.LineArrow, is ShapeMode.DoubleLineArrow -> { Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = mode.arrowSizeScale(), title = stringResource(R.string.head_length_scale), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updateArrow(sizeScale = it) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0.5f..8f, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = (mode.arrowAngle() - 90f).coerceAtLeast(0f), title = stringResource(R.string.angle), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updateArrow(angle = it + 90f) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0f..90f, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface ) } is ShapeMode.Rect, is ShapeMode.OutlinedRect -> { Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = mode.rotationDegrees().toFloat(), title = stringResource(R.string.angle), internalStateTransformation = { it.roundToInt() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updateRect(rotationDegrees = it.roundToInt()) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0f..360f, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = mode.cornerRadius(), title = stringResource(R.string.radius), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updateRect(cornerRadius = it.roundToTwoDigits()) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0f..0.5f, shape = ShapeDefaults.bottom, containerColor = MaterialTheme.colorScheme.surface ) } is ShapeMode.Polygon, is ShapeMode.OutlinedPolygon -> { Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = mode.vertices().toFloat(), title = stringResource(R.string.vertices), internalStateTransformation = { it.roundToInt() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updatePolygon(vertices = it.roundToInt()) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 3f..24f, steps = 20, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = mode.rotationDegrees().toFloat(), title = stringResource(R.string.angle), internalStateTransformation = { it.roundToInt() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updatePolygon(rotationDegrees = it.roundToInt()) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0f..360f, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) PreferenceRowSwitch( title = stringResource(R.string.draw_regular_polygon), subtitle = stringResource(R.string.draw_regular_polygon_sub), checked = mode.isRegular(), onClick = { onUpdateLayer( layer.copy( type = type.copy( shapeMode = mode.updatePolygon(isRegular = it) ) ) ) }, shape = ShapeDefaults.bottom, modifier = Modifier.fillMaxWidth(), startIcon = Icons.Rounded.SquareFoot, containerColor = MaterialTheme.colorScheme.surface ) } is ShapeMode.Star, is ShapeMode.OutlinedStar -> { Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = mode.vertices().toFloat(), title = stringResource(R.string.vertices), internalStateTransformation = { it.roundToInt() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updateStar(vertices = it.roundToInt()) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 3f..24f, steps = 20, shape = ShapeDefaults.top, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = mode.rotationDegrees().toFloat(), title = stringResource(R.string.angle), internalStateTransformation = { it.roundToInt() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updateStar(rotationDegrees = it.roundToInt()) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0f..360f, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = mode.innerRadiusRatio(), title = stringResource(R.string.inner_radius_ratio), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onUpdateLayerContinuously( layer.copy( type = type.copy( shapeMode = mode.updateStar(innerRadiusRatio = it.roundToTwoDigits()) ) ) ) }, onValueChangeFinished = { _ -> onContinuousEditFinished() }, valueRange = 0f..1f, shape = ShapeDefaults.center, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) PreferenceRowSwitch( title = stringResource(R.string.draw_regular_star), subtitle = stringResource(R.string.draw_regular_star_sub), checked = mode.isRegular(), onClick = { onUpdateLayer( layer.copy( type = type.copy( shapeMode = mode.updateStar(isRegular = it) ) ) ) }, shape = ShapeDefaults.bottom, modifier = Modifier.fillMaxWidth(), startIcon = Icons.Rounded.SquareFoot, containerColor = MaterialTheme.colorScheme.surface ) } else -> Unit } } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/BackgroundBehavior.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model sealed class BackgroundBehavior { data object None : BackgroundBehavior() data object Image : BackgroundBehavior() data class Color( val width: Int, val height: Int, val color: Int ) : BackgroundBehavior() } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/ShapeLayerModeUiExt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank import androidx.compose.material.icons.rounded.Circle import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarOutline import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FreeArrow import com.t8rin.imagetoolbox.core.resources.icons.FreeDoubleArrow import com.t8rin.imagetoolbox.core.resources.icons.Line import com.t8rin.imagetoolbox.core.resources.icons.LineArrow import com.t8rin.imagetoolbox.core.resources.icons.LineDoubleArrow import com.t8rin.imagetoolbox.core.resources.icons.Polygon import com.t8rin.imagetoolbox.core.resources.icons.Square import com.t8rin.imagetoolbox.core.resources.icons.Triangle import com.t8rin.imagetoolbox.feature.markup_layers.domain.ShapeMode internal val ShapeMode.Kind.titleRes: Int get() = when (this) { ShapeMode.Kind.Line -> R.string.line ShapeMode.Kind.Arrow -> R.string.arrow ShapeMode.Kind.DoubleArrow -> R.string.double_arrow ShapeMode.Kind.LineArrow -> R.string.line_arrow ShapeMode.Kind.DoubleLineArrow -> R.string.double_line_arrow ShapeMode.Kind.Rect -> R.string.rect ShapeMode.Kind.OutlinedRect -> R.string.outlined_rect ShapeMode.Kind.Oval -> R.string.oval ShapeMode.Kind.OutlinedOval -> R.string.outlined_oval ShapeMode.Kind.Triangle -> R.string.triangle ShapeMode.Kind.OutlinedTriangle -> R.string.outlined_triangle ShapeMode.Kind.Polygon -> R.string.polygon ShapeMode.Kind.OutlinedPolygon -> R.string.outlined_polygon ShapeMode.Kind.Star -> R.string.star ShapeMode.Kind.OutlinedStar -> R.string.outlined_star } internal val ShapeMode.Kind.icon: ImageVector get() = when (this) { ShapeMode.Kind.Line -> Icons.Rounded.Line ShapeMode.Kind.Arrow -> Icons.Rounded.FreeArrow ShapeMode.Kind.DoubleArrow -> Icons.Rounded.FreeDoubleArrow ShapeMode.Kind.LineArrow -> Icons.Rounded.LineArrow ShapeMode.Kind.DoubleLineArrow -> Icons.Rounded.LineDoubleArrow ShapeMode.Kind.Rect -> Icons.Rounded.Square ShapeMode.Kind.OutlinedRect -> Icons.Rounded.CheckBoxOutlineBlank ShapeMode.Kind.Oval -> Icons.Rounded.Circle ShapeMode.Kind.OutlinedOval -> Icons.Rounded.RadioButtonUnchecked ShapeMode.Kind.Triangle -> Icons.Rounded.Triangle ShapeMode.Kind.OutlinedTriangle -> Icons.Outlined.Triangle ShapeMode.Kind.Polygon -> Icons.Rounded.Polygon ShapeMode.Kind.OutlinedPolygon -> Icons.Outlined.Polygon ShapeMode.Kind.Star -> Icons.Rounded.Star ShapeMode.Kind.OutlinedStar -> Icons.Rounded.StarOutline } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FormatBold import androidx.compose.material.icons.rounded.FormatItalic import androidx.compose.material.icons.rounded.FormatStrikethrough import androidx.compose.material.icons.rounded.FormatUnderlined import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.IntSize import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.feature.markup_layers.domain.DomainTextDecoration import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerPosition import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.domain.layerCornerRadiusPercent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.EditBoxState import java.util.concurrent.atomic.AtomicLong data class UiMarkupLayer( val id: Long = nextUiLayerId(), val type: LayerType, val visibleLineCount: Int? = null, val cornerRadiusPercent: Int = 0, val blendingMode: BlendingMode = BlendingMode.SrcOver, val isLocked: Boolean = false, val groupedLayers: List = emptyList(), val state: EditBoxState = EditBoxState(isActive = true) ) { val isGroup: Boolean get() = groupedLayers.isNotEmpty() fun copy( isActive: Boolean = state.isActive, coerceToBounds: Boolean = state.coerceToBounds ) = UiMarkupLayer( id = id, type = type, visibleLineCount = visibleLineCount, cornerRadiusPercent = cornerRadiusPercent, blendingMode = blendingMode, isLocked = isLocked, groupedLayers = groupedLayers, state = state.copy( isActive = isActive, coerceToBounds = coerceToBounds ) ) } fun UiMarkupLayer.asDomain(): MarkupLayer = MarkupLayer( type = if (isGroup) defaultGroupPlaceholderType() else type, position = LayerPosition( scale = state.scale, rotation = state.rotation, isFlippedHorizontally = state.isFlippedHorizontally, isFlippedVertically = state.isFlippedVertically, offsetX = state.offset.x, offsetY = state.offset.y, alpha = state.alpha, currentCanvasSize = state.canvasSize, coerceToBounds = state.coerceToBounds, isVisible = state.isVisible ), contentSize = state.contentSize.toIntegerSize(), visibleLineCount = visibleLineCount, cornerRadiusPercent = type.layerCornerRadiusPercent(cornerRadiusPercent), isLocked = isLocked, blendingMode = blendingMode, groupedLayers = groupedLayers.map(UiMarkupLayer::asDomain) ) fun MarkupLayer.asUi(): UiMarkupLayer = UiMarkupLayer( type = type, visibleLineCount = visibleLineCount, cornerRadiusPercent = type.layerCornerRadiusPercent(cornerRadiusPercent), blendingMode = blendingMode, isLocked = isLocked, groupedLayers = groupedLayers.map(MarkupLayer::asUi), state = EditBoxState( scale = position.scale, rotation = position.rotation, isFlippedHorizontally = position.isFlippedHorizontally, isFlippedVertically = position.isFlippedVertically, offset = Offset( x = position.offsetX, y = position.offsetY ), alpha = position.alpha, isActive = false, canvasSize = position.currentCanvasSize, contentSize = contentSize.toIntSize(), isVisible = position.isVisible, coerceToBounds = position.coerceToBounds ) ) private fun IntSize.toIntegerSize(): IntegerSize = IntegerSize( width = width.coerceAtLeast(0), height = height.coerceAtLeast(0) ) private fun IntegerSize.toIntSize(): IntSize = IntSize( width = width.coerceAtLeast(0), height = height.coerceAtLeast(0) ) internal fun defaultGroupPlaceholderType(): LayerType = LayerType.Shape.Default.copy( color = 0, shadow = null ) internal fun noteUiLayerId(id: Long) { uiLayerIdCounter.updateAndGet { current -> maxOf(current, id + 1) } } private fun nextUiLayerId(): Long = uiLayerIdCounter.getAndIncrement() private val uiLayerIdCounter = AtomicLong(1L) val DomainTextDecoration.icon: ImageVector get() = when (this) { LayerType.Text.Decoration.Bold -> Icons.Rounded.FormatBold LayerType.Text.Decoration.Italic -> Icons.Rounded.FormatItalic LayerType.Text.Decoration.Underline -> Icons.Rounded.FormatUnderlined LayerType.Text.Decoration.LineThrough -> Icons.Rounded.FormatStrikethrough } ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/components/model/UiMarkupLayerGrouping.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerPosition import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.domain.layerCornerRadiusPercent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.EditBoxState import kotlin.math.PI import kotlin.math.abs import kotlin.math.ceil import kotlin.math.cos import kotlin.math.roundToInt import kotlin.math.sin import kotlin.math.sqrt internal fun UiMarkupLayer.uiCornerRadiusPercent(): Int = if (isGroup) { 0 } else { type.layerCornerRadiusPercent(cornerRadiusPercent) } internal fun UiMarkupLayer.deepDuplicate(): UiMarkupLayer = UiMarkupLayer( type = type, visibleLineCount = visibleLineCount, cornerRadiusPercent = cornerRadiusPercent, blendingMode = blendingMode, isLocked = isLocked, groupedLayers = groupedLayers.map(UiMarkupLayer::deepDuplicate), state = state.copy( isActive = false, isInEditMode = false ) ) internal fun UiMarkupLayer.renderCopy(): UiMarkupLayer = UiMarkupLayer( id = id, type = type, visibleLineCount = visibleLineCount, cornerRadiusPercent = cornerRadiusPercent, blendingMode = blendingMode, isLocked = false, groupedLayers = groupedLayers.map(UiMarkupLayer::renderCopy), state = state.copy( isActive = false, isInEditMode = false ) ) internal fun UiMarkupLayer.groupChildAt( center: Offset ): UiMarkupLayer = copy( state = state.copy( offset = state.offset - center, isActive = false, isInEditMode = false ) ) internal fun UiMarkupLayer.effectiveCoerceToBounds(): Boolean = state.coerceToBounds && groupedLayers.all(UiMarkupLayer::effectiveCoerceToBounds) internal fun UiMarkupLayer.withCoerceToBoundsRecursively( value: Boolean ): UiMarkupLayer = copy( groupedLayers = groupedLayers.map { child -> child.withCoerceToBoundsRecursively(value) }, state = state.copy( coerceToBounds = value ) ) internal data class GroupPreviewData( val layers: List, val contentSize: IntSize, val referenceSize: Int ) internal fun UiMarkupLayer.groupContentSize(): IntSize? = localLeafLayers() .combinedBounds() ?.toIntSize() internal fun UiMarkupLayer.canvasLeafLayers( canvasSize: IntegerSize = state.canvasSize, coerceScale: Boolean = false ): List { val rootState = state.adjustedToCanvasSize( canvasSize = canvasSize, forceScaleAdjustment = true ).let { state -> if (coerceScale) { state.copy( scale = 1f ) } else { state } } return groupedLayers.flatMap { child -> child.flattenLeafLayers( parentTransform = rootState.toLayerTransform(), inheritedAlpha = rootState.alpha, inheritedVisible = rootState.isVisible, rootCanvasSize = rootState.canvasSize ) } } internal fun UiMarkupLayer.coerceGroupToBounds() { if (!isGroup || !state.coerceToBounds) return val canvasSize = state.canvasSize.takeIf { it.width > 0 && it.height > 0 } ?: return val bounds = canvasLeafLayers(canvasSize = canvasSize).combinedBounds() ?: return val constrainedOffset = state.offset + bounds.offsetCorrection(canvasSize) if (constrainedOffset != state.offset) { state.offset = constrainedOffset } } internal fun UiMarkupLayer.applyGroupGlobalChanges( zoomChange: Float = 1f, offsetChange: Offset = Offset.Zero, rotationChange: Float = 0f ) { if (!isGroup) return val currentScale = state.scale val targetScale = coerceGroupInteractiveScale( currentScale = currentScale, targetScale = currentScale * zoomChange ) val proposedState = state.copy( scale = targetScale, rotation = state.rotation + rotationChange, offset = state.offset + offsetChange ) val targetOffset = if (proposedState.coerceToBounds) { proposedState.withOffsetCorrection( layer = this, canvasSize = proposedState.canvasSize ) } else { proposedState.offset } state.scale = targetScale state.rotation = proposedState.rotation state.offset = targetOffset } internal fun UiMarkupLayer.setGroupScalePrecisely( targetScale: Float ) { val resolvedScale = coerceGroupInteractiveScale( currentScale = state.scale, targetScale = targetScale ) val currentScale = state.scale.coerceAtLeast(MIN_GROUP_SCALE_EPSILON) applyGroupGlobalChanges( zoomChange = resolvedScale / currentScale ) } private fun EditBoxState.withOffsetCorrection( layer: UiMarkupLayer, canvasSize: IntegerSize ): Offset { if (canvasSize.width <= 0 || canvasSize.height <= 0) return offset val bounds = layer.copy(state = this) .canvasLeafLayers(canvasSize = canvasSize) .combinedBounds() ?: return offset return offset + bounds.offsetCorrection(canvasSize) } private fun LayerBounds.offsetCorrection( canvasSize: IntegerSize ): Offset { val halfCanvasWidth = canvasSize.width / 2f val halfCanvasHeight = canvasSize.height / 2f val dx = axisCoerceDelta( minEdge = -halfCanvasWidth, maxEdge = halfCanvasWidth, start = left, end = right ) val dy = axisCoerceDelta( minEdge = -halfCanvasHeight, maxEdge = halfCanvasHeight, start = top, end = bottom ) return Offset(dx, dy) } internal fun UiMarkupLayer.flattenToDomain(): List = flattenToDomain( parentTransform = null, inheritedAlpha = 1f, inheritedVisible = true, rootCanvasSize = state.canvasSize ) internal fun UiMarkupLayer.toPreviewGroupData(): GroupPreviewData { val previewLayers = previewLeafLayers() val bounds = previewLayers.combinedBounds() ?: LayerBounds(-0.5f, -0.5f, 0.5f, 0.5f) val previewSize = bounds.toIntSize() val previewCanvasSize = previewSize.toIntegerSize() val centeredLayers = previewLayers.map { layer -> layer.renderCopy().copy( state = layer.state.copy( offset = layer.state.offset - bounds.center, canvasSize = previewCanvasSize, isActive = false, isInEditMode = false ) ) } return GroupPreviewData( layers = centeredLayers, contentSize = previewSize, referenceSize = minOf(state.canvasSize.width, state.canvasSize.height).coerceAtLeast(1) ) } internal fun UiMarkupLayer.composeToParentSpace( parent: UiMarkupLayer ): UiMarkupLayer { val rootCanvasSize = parent.state.canvasSize val detachedLayer = detachedSubtree() val composedTransform = parent.state.toLayerTransform().compose( detachedLayer.state.toLayerTransform() ) val decomposition = composedTransform.matrix.decompose() return detachedLayer.copy( state = detachedLayer.state.copy( scale = decomposition.scale, rotation = decomposition.rotation, isFlippedHorizontally = decomposition.isFlippedHorizontally, isFlippedVertically = decomposition.isFlippedVertically, offset = composedTransform.offset, alpha = (parent.state.alpha * detachedLayer.state.alpha).coerceIn(0f, 1f), isActive = false, canvasSize = rootCanvasSize, isVisible = parent.state.isVisible && detachedLayer.state.isVisible, coerceToBounds = detachedLayer.state.coerceToBounds, isInEditMode = false ) ) } internal fun UiMarkupLayer.visualBounds(): LayerBounds { val size = state.contentSize.takeIf(IntSize::isSpecified) ?: IntSize(1, 1) val halfExtents = size.rotatedHalfExtents( degrees = state.rotation, cornerRadiusPercent = uiCornerRadiusPercent() ) val scaledHalfWidth = halfExtents.x * state.scale val scaledHalfHeight = halfExtents.y * state.scale return LayerBounds( left = state.offset.x - scaledHalfWidth, top = state.offset.y - scaledHalfHeight, right = state.offset.x + scaledHalfWidth, bottom = state.offset.y + scaledHalfHeight ) } internal data class UiMarkupLayerSnapshot( val id: Long, val type: com.t8rin.imagetoolbox.feature.markup_layers.domain.LayerType, val visibleLineCount: Int?, val cornerRadiusPercent: Int, val blendingMode: com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode, val isLocked: Boolean, val groupedLayers: List, val state: EditBoxStateSnapshot ) internal data class EditBoxStateSnapshot( val scale: Float, val rotation: Float, val isFlippedHorizontally: Boolean, val isFlippedVertically: Boolean, val offsetX: Float, val offsetY: Float, val alpha: Float, val isActive: Boolean, val canvasWidth: Int, val canvasHeight: Int, val contentWidth: Int, val contentHeight: Int, val isVisible: Boolean, val coerceToBounds: Boolean, val isInEditMode: Boolean ) internal fun UiMarkupLayer.toSnapshot(): UiMarkupLayerSnapshot = UiMarkupLayerSnapshot( id = id, type = type, visibleLineCount = visibleLineCount, cornerRadiusPercent = cornerRadiusPercent, blendingMode = blendingMode, isLocked = isLocked, groupedLayers = groupedLayers.map(UiMarkupLayer::toSnapshot), state = state.toSnapshot() ) internal fun UiMarkupLayerSnapshot.toUi(): UiMarkupLayer { noteUiLayerId(id) return UiMarkupLayer( id = id, type = type, visibleLineCount = visibleLineCount, cornerRadiusPercent = cornerRadiusPercent, blendingMode = blendingMode, isLocked = isLocked, groupedLayers = groupedLayers.map(UiMarkupLayerSnapshot::toUi), state = state.toUi() ) } private fun EditBoxState.toSnapshot(): EditBoxStateSnapshot = EditBoxStateSnapshot( scale = scale, rotation = rotation, isFlippedHorizontally = isFlippedHorizontally, isFlippedVertically = isFlippedVertically, offsetX = offset.x, offsetY = offset.y, alpha = alpha, isActive = isActive, canvasWidth = canvasSize.width, canvasHeight = canvasSize.height, contentWidth = contentSize.width, contentHeight = contentSize.height, isVisible = isVisible, coerceToBounds = coerceToBounds, isInEditMode = isInEditMode ) private fun EditBoxStateSnapshot.toUi(): EditBoxState = EditBoxState( scale = scale, rotation = rotation, isFlippedHorizontally = isFlippedHorizontally, isFlippedVertically = isFlippedVertically, offset = Offset(offsetX, offsetY), alpha = alpha, isActive = isActive, canvasSize = IntegerSize(canvasWidth, canvasHeight), contentSize = IntSize(contentWidth, contentHeight), isVisible = isVisible, coerceToBounds = coerceToBounds, isInEditMode = isInEditMode ) private fun UiMarkupLayer.flattenToDomain( parentTransform: LayerTransform?, inheritedAlpha: Float, inheritedVisible: Boolean, rootCanvasSize: IntegerSize ): List { val currentTransform = parentTransform?.compose(state.toLayerTransform()) ?: state.toLayerTransform() val combinedAlpha = (inheritedAlpha * state.alpha).coerceIn(0f, 1f) val combinedVisible = inheritedVisible && state.isVisible if (isGroup) { return groupedLayers.flatMap { child -> child.flattenToDomain( parentTransform = currentTransform, inheritedAlpha = combinedAlpha, inheritedVisible = combinedVisible, rootCanvasSize = rootCanvasSize ) } } val decomposition = currentTransform.matrix.decompose() return listOf( MarkupLayer( type = type, position = LayerPosition( scale = decomposition.scale, rotation = decomposition.rotation, isFlippedHorizontally = decomposition.isFlippedHorizontally, isFlippedVertically = decomposition.isFlippedVertically, offsetX = currentTransform.offset.x, offsetY = currentTransform.offset.y, alpha = combinedAlpha, currentCanvasSize = rootCanvasSize, coerceToBounds = state.coerceToBounds, isVisible = combinedVisible ), contentSize = state.contentSize.toIntegerSize(), visibleLineCount = visibleLineCount, cornerRadiusPercent = type.layerCornerRadiusPercent(cornerRadiusPercent), isLocked = isLocked, blendingMode = blendingMode ) ) } private data class LayerTransform( val matrix: TransformMatrix, val offset: Offset ) { fun compose( child: LayerTransform ): LayerTransform = LayerTransform( matrix = matrix * child.matrix, offset = offset + matrix.transform(child.offset) ) } private data class TransformMatrix( val m00: Float, val m01: Float, val m10: Float, val m11: Float ) { operator fun times( other: TransformMatrix ): TransformMatrix = TransformMatrix( m00 = m00 * other.m00 + m01 * other.m10, m01 = m00 * other.m01 + m01 * other.m11, m10 = m10 * other.m00 + m11 * other.m10, m11 = m10 * other.m01 + m11 * other.m11 ) fun transform( offset: Offset ): Offset = Offset( x = m00 * offset.x + m01 * offset.y, y = m10 * offset.x + m11 * offset.y ) fun decompose(): DecomposedTransform { val scale = sqrt(m00 * m00 + m10 * m10).coerceAtLeast(0.0001f) val determinant = m00 * m11 - m01 * m10 return if (determinant < 0f) { DecomposedTransform( scale = scale, rotation = radiansToDegrees( kotlin.math.atan2(-m10, -m00) ), isFlippedHorizontally = true, isFlippedVertically = false ) } else { DecomposedTransform( scale = scale, rotation = radiansToDegrees( kotlin.math.atan2(m10, m00) ), isFlippedHorizontally = false, isFlippedVertically = false ) } } } private data class DecomposedTransform( val scale: Float, val rotation: Float, val isFlippedHorizontally: Boolean, val isFlippedVertically: Boolean ) internal data class LayerBounds( val left: Float, val top: Float, val right: Float, val bottom: Float ) { val center: Offset get() = Offset( x = (left + right) / 2f, y = (top + bottom) / 2f ) fun plus( other: LayerBounds ): LayerBounds = LayerBounds( left = minOf(left, other.left), top = minOf(top, other.top), right = maxOf(right, other.right), bottom = maxOf(bottom, other.bottom) ) fun toIntSize(): IntSize = IntSize( width = ceil(right - left).roundToInt().coerceAtLeast(1), height = ceil(bottom - top).roundToInt().coerceAtLeast(1) ) fun contains(point: Offset): Boolean = point.x in left..right && point.y in top..bottom fun axisCoerceDelta( minEdge: Float, maxEdge: Float, start: Float, end: Float ): Float { val size = end - start val availableSize = maxEdge - minEdge return if (size <= availableSize) { when { start < minEdge -> minEdge - start end > maxEdge -> maxEdge - end else -> 0f } } else { when { start > minEdge -> minEdge - start end < maxEdge -> maxEdge - end else -> 0f } } } } internal fun List.combinedBounds(): LayerBounds? = map(UiMarkupLayer::visualBounds) .reduceOrNull(LayerBounds::plus) private fun UiMarkupLayer.previewLeafLayers(): List = canvasLeafLayers( coerceScale = true ) private fun UiMarkupLayer.detachedSubtree(): UiMarkupLayer = copy( groupedLayers = groupedLayers.map(UiMarkupLayer::detachedSubtree), state = state.copy( isActive = false, isInEditMode = false ) ) private fun UiMarkupLayer.localLeafLayers(): List = groupedLayers.flatMap { child -> child.flattenLeafLayers() } private fun EditBoxState.adjustedToCanvasSize( canvasSize: IntegerSize, forceScaleAdjustment: Boolean = false ): EditBoxState = copy().also { it.syncCanvasSize( value = canvasSize, forceScaleAdjustment = forceScaleAdjustment ) } private fun UiMarkupLayer.flattenLeafLayers( parentTransform: LayerTransform? = null, inheritedAlpha: Float = 1f, inheritedVisible: Boolean = true, rootCanvasSize: IntegerSize = state.canvasSize, includeScale: Boolean = true ): List { val currentTransform = parentTransform?.compose(state.toLayerTransform(includeScale = includeScale)) ?: state.toLayerTransform(includeScale = includeScale) val combinedAlpha = (inheritedAlpha * state.alpha).coerceIn(0f, 1f) val combinedVisible = inheritedVisible && state.isVisible if (isGroup) { return groupedLayers.flatMap { child -> child.flattenLeafLayers( parentTransform = currentTransform, inheritedAlpha = combinedAlpha, inheritedVisible = combinedVisible, rootCanvasSize = rootCanvasSize, includeScale = includeScale ) } } val decomposition = currentTransform.matrix.decompose() return listOf( copy( groupedLayers = emptyList(), state = state.copy( scale = decomposition.scale, rotation = decomposition.rotation, isFlippedHorizontally = decomposition.isFlippedHorizontally, isFlippedVertically = decomposition.isFlippedVertically, offset = currentTransform.offset, alpha = combinedAlpha, canvasSize = rootCanvasSize, isVisible = combinedVisible, isActive = false, isInEditMode = false ) ) ) } private fun EditBoxState.toLayerTransform( includeScale: Boolean = true ): LayerTransform { val angle = rotation * DEGREES_TO_RADIANS val cos = cos(angle) val sin = sin(angle) val appliedScale = if (includeScale) scale else 1f val scaleX = appliedScale * if (isFlippedHorizontally) -1f else 1f val scaleY = appliedScale * if (isFlippedVertically) -1f else 1f return LayerTransform( matrix = TransformMatrix( m00 = cos * scaleX, m01 = -sin * scaleY, m10 = sin * scaleX, m11 = cos * scaleY ), offset = offset ) } private fun IntSize.toIntegerSize(): IntegerSize = IntegerSize( width = width.coerceAtLeast(0), height = height.coerceAtLeast(0) ) private fun IntSize.rotatedHalfExtents( degrees: Float, cornerRadiusPercent: Int ): Offset { val halfWidth = width / 2f val halfHeight = height / 2f val cornerRadiusPx = ( minOf(width, height) * (cornerRadiusPercent.coerceIn(0, 50) / 100f) ).coerceIn(0f, minOf(halfWidth, halfHeight)) val innerHalfWidth = (halfWidth - cornerRadiusPx).coerceAtLeast(0f) val innerHalfHeight = (halfHeight - cornerRadiusPx).coerceAtLeast(0f) val radians = degrees * DEGREES_TO_RADIANS val absCos = abs(cos(radians)) val absSin = abs(sin(radians)) return Offset( x = innerHalfWidth * absCos + innerHalfHeight * absSin + cornerRadiusPx, y = innerHalfWidth * absSin + innerHalfHeight * absCos + cornerRadiusPx ) } private fun IntSize.isSpecified(): Boolean = width > 0 && height > 0 private fun radiansToDegrees( radians: Float ): Float = radians * 180f / PI.toFloat() private fun coerceGroupInteractiveScale( currentScale: Float, targetScale: Float ): Float { val minimumValue = GROUP_SCALE_RANGE.start val maximumValue = GROUP_SCALE_RANGE.endInclusive val safeTargetScale = targetScale.coerceAtLeast(MIN_GROUP_SCALE_EPSILON) return when { safeTargetScale >= minimumValue -> safeTargetScale.coerceAtMost(maximumValue) currentScale < minimumValue -> safeTargetScale.coerceAtMost(maximumValue) else -> minimumValue } } private const val DEGREES_TO_RADIANS = (PI / 180f).toFloat() private const val MIN_GROUP_SCALE_EPSILON = 0.0001f private val GROUP_SCALE_RANGE = 0.1f..10f ================================================ FILE: feature/markup-layers/src/main/java/com/t8rin/imagetoolbox/feature/markup_layers/presentation/screenLogic/MarkupLayersComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.markup_layers.data.project.MarkupProjectExtension import com.t8rin.imagetoolbox.feature.markup_layers.data.project.isMarkupProject import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupLayersApplier import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProject import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProjectHistorySnapshot import com.t8rin.imagetoolbox.feature.markup_layers.domain.MarkupProjectResult import com.t8rin.imagetoolbox.feature.markup_layers.domain.ProjectBackground import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.EditBoxState import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.BackgroundBehavior import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayer import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.UiMarkupLayerSnapshot import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.applyGroupGlobalChanges import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.asDomain import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.asUi import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.coerceGroupToBounds import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.combinedBounds import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.composeToParentSpace import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.deepDuplicate import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.defaultGroupPlaceholderType import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.effectiveCoerceToBounds import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.flattenToDomain import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.groupChildAt import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.setGroupScalePrecisely import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.toSnapshot import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.toUi import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.uiCornerRadiusPercent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.components.model.withCoerceToBoundsRecursively import com.t8rin.logger.makeLog import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.withContext class MarkupLayersComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, dispatchersHolder: DispatchersHolder, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val shareProvider: ImageShareProvider, private val markupLayersApplier: MarkupLayersApplier, ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } } private val _isOptionsExpanded = fileController.savable( scope = componentScope, initial = false ) val isOptionsExpanded: Boolean get() = _isOptionsExpanded.get() private val _backgroundBehavior: MutableState = mutableStateOf(BackgroundBehavior.None) val backgroundBehavior: BackgroundBehavior by _backgroundBehavior private val _layers: MutableState> = mutableStateOf(emptyList()) val layers: List by _layers private val _groupingSelectionIds: MutableState> = mutableStateOf(emptySet()) val groupingSelectionIds: Set by _groupingSelectionIds val isGroupingSelectionMode: Boolean get() = groupingSelectionIds.isNotEmpty() val groupingSelectionCount: Int get() = groupingSelectionIds.size private val _history: MutableState> = mutableStateOf( listOf(HistorySnapshot()) ) private val history: List by _history private val _redoHistory: MutableState> = mutableStateOf(emptyList()) private val redoHistory: List by _redoHistory private var pendingHistorySnapshot: HistorySnapshot? = null val canUndo: Boolean get() = history.size > 1 val canRedo: Boolean get() = redoHistory.isNotEmpty() fun toggleExpandOptions() { _isOptionsExpanded.update { !it } } fun undo() { finalizePendingHistoryTransaction() if (!canUndo) return val current = history.last() val previous = history[history.lastIndex - 1] _history.value = history.dropLast(1) _redoHistory.update { (it + current).takeLast(MAX_HISTORY_SIZE) } applyHistorySnapshot(previous) registerChanges() } fun redo() { finalizePendingHistoryTransaction() if (!canRedo) return val snapshot = redoHistory.last() _redoHistory.value = redoHistory.dropLast(1) _history.update { (it + snapshot).takeLast(MAX_HISTORY_SIZE) } applyHistorySnapshot(snapshot) registerChanges() } fun clearLayers() { if (layers.isEmpty()) return cancelGroupingSelection() runEditorChange { _layers.value = emptyList() } } fun addLayer(layer: UiMarkupLayer) { cancelGroupingSelection() deactivateAllLayers() runEditorChange { _layers.update { it + layer } } } fun deactivateAllLayers() { _layers.value.forEach { it.state.deactivate() } } fun activateLayer(layer: UiMarkupLayer) { if (layer.isLocked) return deactivateAllLayers() layer.state.activate() } fun copyLayer(layer: UiMarkupLayer) { cancelGroupingSelection() runEditorChange { val copied = layer.deepDuplicate() _layers.update { it.toMutableList().apply { add(indexOf(layer), copied) } } activateLayer(copied) } } fun updateLayerAt( index: Int, layer: UiMarkupLayer, commitToHistory: Boolean = true ) { val currentLayer = layers.getOrNull(index) ?: return if (currentLayer == layer) return val metadataOnlyUpdate = currentLayer.copy( visibleLineCount = layer.visibleLineCount ) == layer if (currentLayer.isLocked && !metadataOnlyUpdate) return val replaceLayer: () -> Unit = { _layers.update { it.toMutableList().apply { set(index, layer) } } } val shouldTrackHistory = commitToHistory && !metadataOnlyUpdate if (shouldTrackHistory) { runEditorChange(replaceLayer) } else { replaceLayer() } } fun updateLayerState( layer: UiMarkupLayer, commitToHistory: Boolean = true, allowLocked: Boolean = false, block: EditBoxState.() -> Unit ) { if (layer.isLocked && !allowLocked) return if (commitToHistory) { runEditorChange { layer.state.block() layer.coerceGroupToBounds() } } else { layer.state.block() layer.coerceGroupToBounds() } } fun toggleLayerLock(layer: UiMarkupLayer) { cancelGroupingSelection() runEditorChange { val copied = layer.copy( isLocked = !layer.isLocked, state = layer.state.copy( isActive = false, isInEditMode = false ) ) _layers.update { it.toMutableList().apply { set( index = indexOf(layer), element = copied ) } } } } fun removeLayer(layer: UiMarkupLayer) { cancelGroupingSelection() runEditorChange { _layers.update { it - layer } } } fun reorderLayers(layers: List) { runEditorChange { _layers.update { layers } } } fun beginHistoryTransaction() { if (pendingHistorySnapshot == null) { pendingHistorySnapshot = currentHistorySnapshot() } } fun commitHistoryTransaction() { pendingHistorySnapshot?.let(::commitHistoryFrom) } fun moveLayerBy( layer: UiMarkupLayer, offsetChange: Offset, commitToHistory: Boolean = true ) { updateLayerState( layer = layer, commitToHistory = commitToHistory ) { if (layer.isGroup) { layer.applyGroupGlobalChanges( offsetChange = offsetChange ) } else { moveBy( offsetChange = offsetChange, cornerRadiusPercent = layer.uiCornerRadiusPercent() ) } } } fun setLayerScale( layer: UiMarkupLayer, scale: Float, commitToHistory: Boolean = true ) { updateLayerState( layer = layer, commitToHistory = commitToHistory ) { if (layer.isGroup) { layer.setGroupScalePrecisely(scale) } else { setScalePrecisely( targetScale = scale, cornerRadiusPercent = layer.uiCornerRadiusPercent() ) } } } fun resetLayerPosition(layer: UiMarkupLayer) { updateLayerState(layer = layer) { resetPosition() } } fun setLayerNormalizedPosition( layer: UiMarkupLayer, x: Float? = null, y: Float? = null, commitToHistory: Boolean = true ) { updateLayerState( layer = layer, commitToHistory = commitToHistory ) { setNormalizedPosition( x = x, y = y, cornerRadiusPercent = layer.uiCornerRadiusPercent() ) } } fun toggleGroupingSelection(layer: UiMarkupLayer) { if (layer.isLocked) return if (layers.none { it.id == layer.id }) return deactivateAllLayers() _groupingSelectionIds.update { ids -> if (layer.id in ids) ids - layer.id else ids + layer.id } } fun startGroupingSelection(layer: UiMarkupLayer) { if (layer.isLocked) return if (layers.none { it.id == layer.id }) return val activeLayerId = layers.firstOrNull { it.state.isActive && !it.isLocked }?.id deactivateAllLayers() _groupingSelectionIds.update { ids -> val seededIds = ids.ifEmpty { buildSet { activeLayerId?.let(::add) add(layer.id) } } when { seededIds.isEmpty() -> setOf(layer.id) ids.isEmpty() -> seededIds layer.id in seededIds -> seededIds - layer.id else -> seededIds + layer.id } } } fun cancelGroupingSelection() { _groupingSelectionIds.value = emptySet() } fun clearSelections() { cancelGroupingSelection() deactivateAllLayers() } fun groupSelectedLayers() { val selectedEntries = layers.withIndex() .filter { it.value.id in groupingSelectionIds } if (selectedEntries.size < 2) return val selectedLayers = selectedEntries.map { it.value } val bounds = selectedLayers.combinedBounds() ?: return val center = bounds.center val canvasSize = selectedLayers.firstNotNullOfOrNull { layer -> layer.state.canvasSize.takeIf { it.width > 0 && it.height > 0 } } ?: return val groupCoerceToBounds = selectedLayers.all(UiMarkupLayer::effectiveCoerceToBounds) val groupedLayer = UiMarkupLayer( type = defaultGroupPlaceholderType(), groupedLayers = selectedLayers.map { layer -> layer.groupChildAt(center) }, state = EditBoxState( isActive = true, canvasSize = canvasSize, contentSize = bounds.toIntSize(), offset = center, coerceToBounds = groupCoerceToBounds ) ).withCoerceToBoundsRecursively(groupCoerceToBounds) runEditorChange { val selectedIds = selectedEntries.map { it.value.id }.toSet() val firstSelectedIndex = selectedEntries.minOf { it.index } _layers.update { current -> buildList { current.forEachIndexed { index, currentLayer -> if (index == firstSelectedIndex) add(groupedLayer) if (currentLayer.id !in selectedIds) add(currentLayer) } } } } cancelGroupingSelection() } fun ungroupLayer(layer: UiMarkupLayer) { if (!layer.isGroup) return cancelGroupingSelection() runEditorChange { val restoredLayers = layer.groupedLayers.map { child -> child.composeToParentSpace(layer) } _layers.update { current -> current.toMutableList().apply { val index = indexOf(layer) if (index >= 0) { removeAt(index) addAll(index, restoredLayers) } } } restoredLayers.firstOrNull()?.let(::activateLayer) } } private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _uri = mutableStateOf(Uri.EMPTY) private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat by _imageFormat private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _saveExif: MutableState = mutableStateOf(false) val saveExif: Boolean by _saveExif private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmap( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true renderLayers()?.let { localBitmap -> parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ), originalUri = _uri.value.toString(), sequenceNumber = null, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = _saveExif.value, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.value = false } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.value = imageFormat registerChanges() } fun setSaveExif(bool: Boolean) { _saveExif.value = bool registerChanges() } private fun updateBitmap(bitmap: Bitmap?) { componentScope.launch { updateBitmapSync(bitmap) } } private suspend fun updateBitmapSync(bitmap: Bitmap?) { _isImageLoading.value = true _bitmap.value = imageScaler.scaleUntilCanShow(bitmap) _isImageLoading.value = false } fun setUri(uri: Uri) { if (uri.isMarkupProject()) { loadProject(uri) } else { setImageUri(uri) } } fun saveProject(uri: Uri) { savingJob = trackProgress { _isSaving.value = true finalizePendingHistoryTransaction() fileController.writeBytes(uri.toString()) { output -> markupLayersApplier.saveProject( destination = output, project = createProject() ) }.onSuccess { registerSave() AppToastHost.showConfetti() }.let(::parseSaveResult) _isSaving.value = false } } fun createProjectFilename(): String { val baseName = when (backgroundBehavior) { is BackgroundBehavior.Image -> { _uri.value.filename()?.substringBeforeLast('.')?.takeIf(String::isNotBlank) } is BackgroundBehavior.Color -> "Markup" BackgroundBehavior.None -> null } ?: "Markup" return "${baseName}_${timestamp()}.$MarkupProjectExtension" } private fun setImageUri(uri: Uri) { componentScope.launch { cancelGroupingSelection() _layers.update { emptyList() } _isImageLoading.value = true _uri.value = uri imageGetter.getImageAsync( uri = uri.toString(), originalSize = false, onGetImage = { data -> _backgroundBehavior.update { BackgroundBehavior.Image } updateBitmap(data.image) _imageFormat.update { data.imageInfo.imageFormat } resetHistory() registerChangesCleared() }, onFailure = { _isImageLoading.value = false if (bitmap == null) resetState() AppToastHost.showFailureToast(it) } ) } } private fun loadProject(uri: Uri) { componentScope.launch { _isImageLoading.value = true when (val result = markupLayersApplier.openProject(uri.toString())) { is MarkupProjectResult.Success -> { applyProject( project = result.project ) registerChangesCleared() } is MarkupProjectResult.Error -> { AppToastHost.showFailureToast(result.message) } } _isImageLoading.value = false } } private suspend fun renderLayers(): Bitmap? = withContext(defaultDispatcher) { deactivateAllLayers() runCatching { markupLayersApplier.applyToImage( image = imageGetter.getImage(data = _uri.value) ?: (backgroundBehavior as? BackgroundBehavior.Color)?.run { color.toDrawable().toBitmap(width, height) } ?: run { val w = layers.firstOrNull()?.state?.canvasSize?.width?.takeIf { it > 0 } ?: 1 val h = layers.firstOrNull()?.state?.canvasSize?.height?.takeIf { it > 0 } ?: 1 ImageBitmap(w, h).asAndroidBitmap() }, layers = flattenLayers(layers) ) }.onFailure { it.makeLog() }.getOrNull() } override fun resetState() { markupLayersApplier.clearProjectCache() _bitmap.value = null _backgroundBehavior.update { BackgroundBehavior.None } _uri.value = Uri.EMPTY _layers.update { emptyList() } cancelGroupingSelection() resetHistory() registerChangesCleared() } fun startDrawOnBackground( reqWidth: Int, reqHeight: Int, color: Color, ) { val width = reqWidth.takeIf { it > 0 } ?: 1 val height = reqHeight.takeIf { it > 0 } ?: 1 width / height.toFloat() _backgroundBehavior.update { BackgroundBehavior.Color( width = width, height = height, color = color.toArgb() ) } _uri.value = Uri.EMPTY _layers.value = emptyList() cancelGroupingSelection() updateBitmap(null) resetHistory() registerChangesCleared() } fun shareBitmap() { savingJob = trackProgress { _isSaving.value = true renderLayers()?.let { shareProvider.shareImage( image = it, imageInfo = ImageInfo( imageFormat = imageFormat, width = it.width, height = it.height ), onComplete = AppToastHost::showConfetti ) } _isSaving.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true renderLayers()?.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( imageFormat = imageFormat, width = image.width, height = image.height ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun getFormatForFilenameSelection(): ImageFormat = imageFormat fun updateBackgroundColor(color: Color) { runEditorChange { _backgroundBehavior.update { if (it is BackgroundBehavior.Color) { it.copy(color = color.toArgb()) } else it } } } private fun createProject(): MarkupProject = MarkupProject( background = when (val behavior = backgroundBehavior) { is BackgroundBehavior.Color -> ProjectBackground.Color( width = behavior.width, height = behavior.height, color = behavior.color ) is BackgroundBehavior.Image -> ProjectBackground.Image( uri = _uri.value.toString() ) BackgroundBehavior.None -> ProjectBackground.None }, layers = layers.toProjectLayers(), lastLayers = history.dropLast(1).lastOrNull()?.projectLayers() ?: emptyList(), undoneLayers = redoHistory.lastOrNull()?.projectLayers() ?: emptyList(), history = history.map { it.toProjectHistorySnapshot() }, redoHistory = redoHistory.map { it.toProjectHistorySnapshot() } ) private suspend fun applyProject( project: MarkupProject ) { cancelGroupingSelection() _layers.value = emptyList() when (val background = project.background) { is ProjectBackground.Image -> { _uri.value = background.uri.toUri() _backgroundBehavior.value = BackgroundBehavior.Image updateBitmapSync( bitmap = imageGetter.getImage( data = background.uri, originalSize = false ) ) } is ProjectBackground.Color -> { _uri.value = Uri.EMPTY _backgroundBehavior.value = BackgroundBehavior.Color( width = background.width, height = background.height, color = background.color ) updateBitmapSync(null) } ProjectBackground.None -> { _uri.value = Uri.EMPTY _backgroundBehavior.value = BackgroundBehavior.None updateBitmapSync(null) } } _layers.value = project.layers.map { it.asUi() } if (project.history.isNotEmpty() || project.redoHistory.isNotEmpty()) { restoreHistory( historySnapshots = project.history.map { it.toInternalHistorySnapshot() }, redoSnapshots = project.redoHistory.map { it.toInternalHistorySnapshot() } ) } else { restoreHistory( previousLayers = project.lastLayers, redoneLayers = project.undoneLayers ) } } private fun runEditorChange( block: () -> Unit ) { val beforeSnapshot = pendingHistorySnapshot ?: currentHistorySnapshot() pendingHistorySnapshot = null block() normalizeGroupingSelection() commitHistoryFrom(beforeSnapshot) } private fun currentHistorySnapshot(): HistorySnapshot = HistorySnapshot( backgroundBehavior = backgroundBehavior, layers = layers.map(UiMarkupLayer::toSnapshot) ) private fun commitHistoryFrom(beforeSnapshot: HistorySnapshot) { pendingHistorySnapshot = null val afterSnapshot = currentHistorySnapshot() if (afterSnapshot == beforeSnapshot) return _history.update { states -> val normalizedStates = when { states.isEmpty() -> listOf(beforeSnapshot) states.last() == beforeSnapshot -> states else -> (states + beforeSnapshot).takeLast(MAX_HISTORY_SIZE) } (normalizedStates + afterSnapshot).takeLast(MAX_HISTORY_SIZE) } _redoHistory.value = emptyList() registerChanges() } private fun finalizePendingHistoryTransaction() { pendingHistorySnapshot?.let(::commitHistoryFrom) } private fun resetHistory() { restoreHistory() } private fun restoreHistory( previousLayers: List = emptyList(), redoneLayers: List = emptyList(), historySnapshots: List = emptyList(), redoSnapshots: List = emptyList() ) { pendingHistorySnapshot = null val currentSnapshot = currentHistorySnapshot() if (historySnapshots.isNotEmpty() || redoSnapshots.isNotEmpty()) { val restoredHistory = historySnapshots .ifEmpty { listOf(currentSnapshot) } .let { snapshots -> if (snapshots.lastOrNull() == currentSnapshot) { snapshots } else { (snapshots + currentSnapshot).takeLast(MAX_HISTORY_SIZE) } } _history.value = restoredHistory.takeLast(MAX_HISTORY_SIZE) _redoHistory.value = redoSnapshots.takeLast(MAX_HISTORY_SIZE) return } val previousSnapshot = HistorySnapshot( backgroundBehavior = currentSnapshot.backgroundBehavior, layers = previousLayers.map { it.asUi().toSnapshot() } ).takeIf { it != currentSnapshot } val redoneSnapshot = HistorySnapshot( backgroundBehavior = currentSnapshot.backgroundBehavior, layers = redoneLayers.map { it.asUi().toSnapshot() } ).takeIf { it != currentSnapshot } _history.value = listOfNotNull(previousSnapshot, currentSnapshot) _redoHistory.value = listOfNotNull(redoneSnapshot) } private fun applyHistorySnapshot(snapshot: HistorySnapshot) { _backgroundBehavior.value = snapshot.backgroundBehavior _layers.value = snapshot.layers.map(UiMarkupLayerSnapshot::toUi) cancelGroupingSelection() } private data class HistorySnapshot( val backgroundBehavior: BackgroundBehavior = BackgroundBehavior.None, val layers: List = emptyList() ) private fun HistorySnapshot.toProjectHistorySnapshot(): MarkupProjectHistorySnapshot = MarkupProjectHistorySnapshot( background = backgroundBehavior.toProjectBackground(), layers = projectLayers() ) private fun MarkupProjectHistorySnapshot.toInternalHistorySnapshot(): HistorySnapshot = HistorySnapshot( backgroundBehavior = background.toBackgroundBehavior(), layers = layers.map { it.asUi().toSnapshot() } ) private fun HistorySnapshot.projectLayers(): List = layers.map( UiMarkupLayerSnapshot::toUi ).toProjectLayers() private fun BackgroundBehavior.toProjectBackground(): ProjectBackground = when (this) { is BackgroundBehavior.Color -> ProjectBackground.Color( width = width, height = height, color = color ) BackgroundBehavior.Image -> ProjectBackground.Image( uri = _uri.value.toString() ) BackgroundBehavior.None -> ProjectBackground.None } private fun ProjectBackground.toBackgroundBehavior(): BackgroundBehavior = when (this) { is ProjectBackground.Color -> BackgroundBehavior.Color( width = width, height = height, color = color ) is ProjectBackground.Image -> BackgroundBehavior.Image ProjectBackground.None -> BackgroundBehavior.None } private fun flattenLayers( layers: List ): List = layers.flatMap(UiMarkupLayer::flattenToDomain) private fun List.toProjectLayers(): List = map( UiMarkupLayer::asDomain ) private fun normalizeGroupingSelection() { if (groupingSelectionIds.isEmpty()) return val validIds = layers.mapTo(mutableSetOf()) { it.id } _groupingSelectionIds.update { ids -> ids.filterTo(mutableSetOf()) { it in validIds } } } private companion object { const val MAX_HISTORY_SIZE = 50 } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): MarkupLayersComponent } } private fun EditBoxState.moveBy( offsetChange: Offset, cornerRadiusPercent: Int ) { val contentSize = contentSize if (contentSize.width <= 0 || contentSize.height <= 0) { offset += offsetChange return } val canvasWidth = canvasSize.width.takeIf { it > 0 } ?: contentSize.width val canvasHeight = canvasSize.height.takeIf { it > 0 } ?: contentSize.height applyGlobalChanges( parentMaxWidth = canvasWidth, parentMaxHeight = canvasHeight, contentSize = contentSize, cornerRadiusPercent = cornerRadiusPercent, zoomChange = 1f, offsetChange = offsetChange, rotationChange = 0f ) } private fun EditBoxState.resetPosition() { offset = Offset.Zero } ================================================ FILE: feature/media-picker/.gitignore ================================================ /build ================================================ FILE: feature/media-picker/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.media_picker" dependencies { implementation(projects.core.crash) } ================================================ FILE: feature/media-picker/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/AndroidMediaRetriever.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.data import android.content.ContentResolver import android.content.Context import android.os.Bundle import android.provider.MediaStore import androidx.annotation.RequiresApi import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.feature.media_picker.data.utils.Query import com.t8rin.imagetoolbox.feature.media_picker.data.utils.contentFlowObserver import com.t8rin.imagetoolbox.feature.media_picker.data.utils.getAlbums import com.t8rin.imagetoolbox.feature.media_picker.data.utils.getMedia import com.t8rin.imagetoolbox.feature.media_picker.data.utils.getSupportedFileSequence import com.t8rin.imagetoolbox.feature.media_picker.domain.MediaRetriever import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Album import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaOrder import com.t8rin.imagetoolbox.feature.media_picker.domain.model.OrderType import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import javax.inject.Inject @RequiresApi(26) internal class AndroidMediaRetriever @Inject constructor( @ApplicationContext private val context: Context, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, MediaRetriever { override fun getAlbumsWithType( allowedMedia: AllowedMedia ): Flow>> = context.retrieveAlbums { val query = Query.AlbumQuery().copy( bundle = Bundle().apply { val mimeType = when (allowedMedia) { is AllowedMedia.Photos -> "image%" AllowedMedia.Videos -> "video%" AllowedMedia.Both -> "%/%" } putString( ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.MIME_TYPE + " like ?" ) putStringArray( ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(mimeType) ) } ) val fileQuery = Query.AlbumQuery().copy( bundle = Bundle().apply { val extensions = getSupportedFileSequence(allowedMedia).toList() putString( ContentResolver.QUERY_ARG_SQL_SELECTION, extensions.asSequence().map { MediaStore.MediaColumns.DATA + " LIKE ?" } .joinToString(" OR ") ) putStringArray( ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, extensions.toTypedArray() ) } ) it.getAlbums( query = query, fileQuery = fileQuery, mediaOrder = MediaOrder.Date(OrderType.Descending) ) } override fun mediaFlowWithType( albumId: Long, allowedMedia: AllowedMedia ): Flow>> = if (albumId != -1L) { getMediaByAlbumIdWithType(albumId, allowedMedia) } else { getMediaByType(allowedMedia) }.flowOn(defaultDispatcher).conflate() override fun getMediaByAlbumIdWithType( albumId: Long, allowedMedia: AllowedMedia ): Flow>> = context.retrieveMedia { val query = Query.MediaQuery().copy( bundle = Bundle().apply { val mimeType = when (allowedMedia) { is AllowedMedia.Photos -> "image%" AllowedMedia.Videos -> "video%" AllowedMedia.Both -> "%/%" } putString( ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.BUCKET_ID + "= ? and (" + MediaStore.MediaColumns.MIME_TYPE + " like ? OR ${MediaStore.MediaColumns.DATA} LIKE ? OR ${MediaStore.MediaColumns.DATA} LIKE ?)" ) putStringArray( ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(albumId.toString(), mimeType, "%.jxl", "%.qoi") ) } ) val fileQuery = Query.MediaQuery().copy( bundle = Bundle().apply { val extensions = getSupportedFileSequence(allowedMedia).toList() putString( ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.BUCKET_ID + "= ? and (" + extensions.asSequence() .map { MediaStore.MediaColumns.DATA + " LIKE ?" } .joinToString(" OR ") + ")" ) putStringArray( ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(albumId.toString(), *extensions.toTypedArray()) ) } ) /** return@retrieveMedia */ it.getMedia( mediaQuery = query, fileQuery = fileQuery ) } override fun getMediaByType( allowedMedia: AllowedMedia ): Flow>> = context.retrieveMedia { val query = when (allowedMedia) { is AllowedMedia.Photos -> Query.PhotoQuery() AllowedMedia.Videos -> Query.VideoQuery() AllowedMedia.Both -> Query.MediaQuery() } val fileQuery = Query.FileQuery(getSupportedFileSequence(allowedMedia).toList()) it.getMedia( mediaQuery = query, fileQuery = fileQuery ) } private val uris = arrayOf( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, MediaStore.Files.getContentUri("external") ) private fun Context.retrieveMedia( dataBody: suspend (ContentResolver) -> List ) = contentFlowObserver( uris = uris, coroutineContext = ioDispatcher ).map { runSuspendCatching { dataBody.invoke(contentResolver) } }.conflate() private fun Context.retrieveAlbums( dataBody: suspend (ContentResolver) -> List ) = contentFlowObserver( uris = uris, coroutineContext = ioDispatcher ).map { runSuspendCatching { dataBody.invoke(contentResolver) } }.conflate() } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/DateExt.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.data.utils import android.text.format.DateFormat import com.t8rin.imagetoolbox.feature.media_picker.domain.model.DEFAULT_DATE_FORMAT import com.t8rin.imagetoolbox.feature.media_picker.domain.model.EXTENDED_DATE_FORMAT import com.t8rin.imagetoolbox.feature.media_picker.domain.model.WEEKLY_DATE_FORMAT import java.text.ParseException import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale import java.util.concurrent.TimeUnit data class DateExt( val month: String, val day: Int, val year: Int ) fun Long.getDateExt(): DateExt { val mediaDate = Calendar.getInstance(Locale.US) mediaDate.timeInMillis = this * 1000L return DateExt( month = mediaDate.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US)!!, day = mediaDate.get(Calendar.DAY_OF_MONTH), year = mediaDate.get(Calendar.YEAR) ) } fun Long.getDate( format: CharSequence = DEFAULT_DATE_FORMAT, ): String { val mediaDate = Calendar.getInstance(Locale.US) mediaDate.timeInMillis = this * 1000L return DateFormat.format(format, mediaDate).toString() } fun Long.getDate( format: CharSequence = DEFAULT_DATE_FORMAT, weeklyFormat: CharSequence = WEEKLY_DATE_FORMAT, extendedFormat: CharSequence = EXTENDED_DATE_FORMAT, stringToday: String, stringYesterday: String ): String { val currentDate = Calendar.getInstance(Locale.US) currentDate.timeInMillis = System.currentTimeMillis() val mediaDate = Calendar.getInstance(Locale.US) mediaDate.timeInMillis = this * 1000L val different: Long = System.currentTimeMillis() - mediaDate.timeInMillis val secondsInMilli: Long = 1000 val minutesInMilli = secondsInMilli * 60 val hoursInMilli = minutesInMilli * 60 val daysInMilli = hoursInMilli * 24 val daysDifference = different / daysInMilli return when (daysDifference.toInt()) { 0 -> { if (currentDate.get(Calendar.DATE) != mediaDate.get(Calendar.DATE)) { stringYesterday } else { stringToday } } 1 -> { stringYesterday } else -> { if (daysDifference.toInt() in 2..5) { DateFormat.format(weeklyFormat, mediaDate).toString() } else { if (currentDate.get(Calendar.YEAR) > mediaDate.get(Calendar.YEAR)) { DateFormat.format(extendedFormat, mediaDate).toString() } else DateFormat.format(format, mediaDate).toString() } } } } fun Long.getMonth(): String { val currentDate = Calendar.getInstance(Locale.US).apply { timeInMillis = System.currentTimeMillis() } val mediaDate = Calendar.getInstance(Locale.US).apply { timeInMillis = this@getMonth * 1000L } val month = mediaDate.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US)!! val year = mediaDate.get(Calendar.YEAR) return if (currentDate.get(Calendar.YEAR) != mediaDate.get(Calendar.YEAR)) "$month $year" else month } fun getMonth(date: String): String { return try { val dateFormatExtended = SimpleDateFormat(EXTENDED_DATE_FORMAT, Locale.US).parse(date) val cal = Calendar.getInstance(Locale.US).apply { timeInMillis = dateFormatExtended!!.time } val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US)!! val year = cal.get(Calendar.YEAR) "$month $year" } catch (e: ParseException) { try { val dateFormat = SimpleDateFormat(DEFAULT_DATE_FORMAT, Locale.US).parse(date) val cal = Calendar.getInstance(Locale.US).apply { timeInMillis = dateFormat!!.time } cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US)!! } catch (e: ParseException) { "" } } } fun getDateHeader( startDate: DateExt, endDate: DateExt ): String { return if (startDate.year == endDate.year) { if (startDate.month == endDate.month) { if (startDate.day == endDate.day) { "${startDate.month} ${startDate.day}, ${startDate.year}" } else "${startDate.month} ${startDate.day} - ${endDate.day}, ${startDate.year}" } else "${startDate.month} ${startDate.day} - ${endDate.month} ${endDate.day}, ${startDate.year}" } else { "${startDate.month} ${startDate.day}, ${startDate.year} - ${endDate.month} ${endDate.day}, ${endDate.year}" } } fun Long.formatMinSec(): String { return if (this == 0L) { "00:00" } else { String.format( locale = Locale.getDefault(), format = "%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(this), TimeUnit.MILLISECONDS.toSeconds(this) - TimeUnit.MINUTES.toSeconds( TimeUnit.MILLISECONDS.toMinutes(this) ) ) } } fun String?.formatMinSec(): String { return when (val value = this?.toLong()) { null -> "" else -> value.formatMinSec() } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaObserver.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.data.utils import android.content.ContentResolver import android.content.ContentUris import android.content.Context import android.database.ContentObserver import android.database.Cursor import android.database.MergeCursor import android.net.Uri import android.os.Build import android.provider.MediaStore import com.t8rin.imagetoolbox.feature.media_picker.domain.model.FULL_DATE_FORMAT import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaOrder import com.t8rin.imagetoolbox.feature.media_picker.domain.model.OrderType import kotlinx.coroutines.Job import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlin.coroutines.CoroutineContext import kotlin.io.path.Path import kotlin.io.path.extension private var observerJob: Job? = null /** * Register an observer class that gets callbacks when data identified by a given content URI * changes. */ fun Context.contentFlowObserver( uris: Array, coroutineContext: CoroutineContext ) = callbackFlow { val observer = object : ContentObserver(null) { override fun onChange(selfChange: Boolean) { observerJob?.cancel() observerJob = launch(coroutineContext) { send(false) } } } for (uri in uris) { contentResolver.registerContentObserver(uri, true, observer) } // trigger first. observerJob = launch(coroutineContext) { send(true) } awaitClose { contentResolver.unregisterContentObserver(observer) } }.conflate().onEach { if (!it) delay(1000) } suspend fun ContentResolver.getMedia( mediaQuery: Query = Query.MediaQuery(), fileQuery: Query = Query.MediaQuery(), mediaOrder: MediaOrder = MediaOrder.Date(OrderType.Descending) ): List { return coroutineScope { val media = mutableListOf() query(mediaQuery, fileQuery).use { cursor -> while (cursor.moveToNext()) { try { media.add(cursor.getMediaFromCursor()) } catch (e: Throwable) { e.printStackTrace() } } } mediaOrder.sortMedia(media) } } fun Cursor.getMediaFromCursor(): Media { val id: Long = getLong(getColumnIndexOrThrow(MediaStore.MediaColumns._ID)) val path: String = getString(getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)) val relativePath: String = getString( getColumnIndexOrThrow( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.RELATIVE_PATH } else MediaStore.MediaColumns.DATA ) ) val title: String = getString(getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)) val albumID: Long = getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.BUCKET_ID)) val albumLabel: String = try { getString( getColumnIndexOrThrow( MediaStore.MediaColumns.BUCKET_DISPLAY_NAME ) ) } catch (_: Throwable) { Build.MODEL } val takenTimestamp: Long? = try { getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN)) } catch (_: Throwable) { null } val modifiedTimestamp: Long = getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)) val duration: String? = try { getString(getColumnIndexOrThrow(MediaStore.MediaColumns.DURATION)) } catch (_: Throwable) { null } val expiryTimestamp: Long? = try { getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_EXPIRES)) } catch (_: Throwable) { null } val (mimeType, contentUri) = SUPPORTED_FILES[Path(path).extension]?.let { (mimeType, _) -> Pair(mimeType, MediaStore.Files.getContentUri("external")) } ?: run { val mimeType: String = getString(getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)) val contentUri = if (mimeType.contains("image")) MediaStore.Images.Media.EXTERNAL_CONTENT_URI else MediaStore.Video.Media.EXTERNAL_CONTENT_URI Pair(mimeType, contentUri) } val uri = ContentUris.withAppendedId(contentUri, id) val formattedDate = modifiedTimestamp.getDate(FULL_DATE_FORMAT) return Media( id = id, label = title, uri = uri.toString(), path = path, relativePath = relativePath, albumID = albumID, albumLabel = albumLabel, timestamp = modifiedTimestamp, takenTimestamp = takenTimestamp, expiryTimestamp = expiryTimestamp, fullDate = formattedDate, duration = duration, mimeType = mimeType, ) } suspend fun ContentResolver.query( mediaQuery: Query, fileQuery: Query ): Cursor = coroutineScope { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { MergeCursor( arrayOf( query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediaQuery.projection, mediaQuery.bundle, null ), query( MediaStore.Files.getContentUri("external"), fileQuery.projection, fileQuery.bundle, null ), query( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, mediaQuery.projection, mediaQuery.bundle, null ) ) ) } else { MergeCursor( arrayOf( query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediaQuery.projection, null, null, null ), query( MediaStore.Files.getContentUri("external"), fileQuery.projection, null, null, null ), query( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, mediaQuery.projection, null, null, null ) ) ) } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/MediaQuery.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.data.utils import android.content.ContentResolver import android.content.ContentUris import android.os.Build import android.os.Bundle import android.provider.MediaStore import androidx.annotation.RequiresApi import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Album import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaOrder import com.t8rin.imagetoolbox.feature.media_picker.domain.model.OrderType import com.t8rin.logger.makeLog import kotlinx.coroutines.coroutineScope @RequiresApi(26) sealed class Query( var projection: Array, var bundle: Bundle? = null ) { class MediaQuery : Query( projection = listOfNotNull( MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.RELATIVE_PATH } else MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.BUCKET_ID, MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.DATE_TAKEN, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.DURATION } else null, MediaStore.MediaColumns.MIME_TYPE ).toTypedArray(), ) class PhotoQuery : Query( projection = listOfNotNull( MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.RELATIVE_PATH } else MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.BUCKET_ID, MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.DATE_TAKEN, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.DURATION } else null, MediaStore.MediaColumns.MIME_TYPE ).toTypedArray(), bundle = defaultBundle.apply { putString( ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.MIME_TYPE + " like ?" ) putStringArray( ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf("image%") ) } ) class VideoQuery : Query( projection = listOfNotNull( MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.RELATIVE_PATH } else MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.BUCKET_ID, MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.DURATION } else null, MediaStore.MediaColumns.MIME_TYPE, MediaStore.MediaColumns.ORIENTATION ).toTypedArray(), bundle = defaultBundle.apply { putString( ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.MIME_TYPE + " LIKE ?" ) putStringArray( ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf("video%") ) } ) class AlbumQuery : Query( projection = arrayOf( MediaStore.MediaColumns.BUCKET_ID, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.DATA, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.RELATIVE_PATH } else MediaStore.MediaColumns.DATA, MediaStore.MediaColumns._ID, MediaStore.MediaColumns.MIME_TYPE, MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.DATE_TAKEN ) ) class FileQuery(fileExtensions: List) : Query( projection = listOfNotNull( MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.RELATIVE_PATH } else MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.BUCKET_ID, MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.DATE_TAKEN, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.DURATION } else null, MediaStore.MediaColumns.MIME_TYPE ).toTypedArray(), bundle = defaultBundle.deepCopy().apply { putString( ContentResolver.QUERY_ARG_SQL_SELECTION, fileExtensions.indices.asSequence().map { "${MediaStore.MediaColumns.DATA} like ?" } .joinToString(" OR ") ) putStringArray( ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, fileExtensions.toTypedArray() ) } ) fun copy( projection: Array = this.projection, bundle: Bundle? = this.bundle, ): Query { this.projection = projection this.bundle = bundle return this } companion object { val defaultBundle = Bundle().apply { putStringArray( ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(MediaStore.MediaColumns.DATE_MODIFIED) ) putInt( ContentResolver.QUERY_ARG_SQL_SORT_ORDER, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING ) } } } @RequiresApi(26) private fun Query.copyAsAlbum(): Query { val bundle = this.bundle ?: Bundle() return this.copy( bundle = bundle.apply { putInt( ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING ) putStringArray( ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(MediaStore.MediaColumns.DATE_MODIFIED) ) } ) } @RequiresApi(26) suspend fun ContentResolver.getAlbums( query: Query = Query.AlbumQuery(), fileQuery: Query = Query.AlbumQuery(), mediaOrder: MediaOrder = MediaOrder.Date(OrderType.Descending) ): List = coroutineScope { val timeStart = System.currentTimeMillis() val albums = mutableListOf() val albumQuery = query.copyAsAlbum() val albumFileQuery = fileQuery.copyAsAlbum() query( mediaQuery = albumQuery, fileQuery = albumFileQuery ).use { with(it) { while (moveToNext()) { runCatching { val albumId = getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.BUCKET_ID)) val id = getLong(getColumnIndexOrThrow(MediaStore.MediaColumns._ID)) val label: String? = try { getString( getColumnIndexOrThrow( MediaStore.MediaColumns.BUCKET_DISPLAY_NAME ) ) } catch (_: Throwable) { Build.MODEL } val thumbnailPath = getString(getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)) val thumbnailRelativePath = getString( getColumnIndexOrThrow( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.MediaColumns.RELATIVE_PATH } else MediaStore.MediaColumns.DATA ) ) val thumbnailDate = getLong(getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)) val mimeType = getString(getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)) val contentUri = if (mimeType.contains("image")) MediaStore.Images.Media.EXTERNAL_CONTENT_URI else MediaStore.Video.Media.EXTERNAL_CONTENT_URI val album = Album( id = albumId, label = label ?: Build.MODEL, uri = ContentUris.withAppendedId(contentUri, id).toString(), pathToThumbnail = thumbnailPath, relativePath = thumbnailRelativePath, timestamp = thumbnailDate, count = 1 ) val currentAlbum = albums.find { a -> a.id == albumId } if (currentAlbum == null) albums.add(album) else { val i = albums.indexOf(currentAlbum) albums[i] = albums[i].let { a -> a.copy(count = a.count + 1) } if (albums[i].timestamp <= thumbnailDate) { albums[i] = album.copy(count = albums[i].count) } } } } } } mediaOrder.sortAlbums(albums).also { "Album parsing took: ${System.currentTimeMillis() - timeStart}ms".makeLog() } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/data/utils/SupportedFiles.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.data.utils import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia enum class FileType { Photo, Video } val SUPPORTED_FILES = mapOf( "jxl" to Pair("image/jxl", FileType.Photo), "qoi" to Pair("image/qoi", FileType.Photo) ) fun getSupportedFileSequence(allowedMedia: AllowedMedia) = when (allowedMedia) { AllowedMedia.Both -> SUPPORTED_FILES.asSequence() is AllowedMedia.Photos -> SUPPORTED_FILES.asSequence() .filter { (_, p) -> p.second == FileType.Photo } AllowedMedia.Videos -> SUPPORTED_FILES.asSequence() .filter { (_, p) -> p.second == FileType.Video } }.map { (ext, _) -> "%.$ext" } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/di/MediaPickerModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.di import com.t8rin.imagetoolbox.feature.media_picker.data.AndroidMediaRetriever import com.t8rin.imagetoolbox.feature.media_picker.domain.MediaRetriever import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface MediaPickerModule { @Binds @Singleton fun mediaRetriever( impl: AndroidMediaRetriever ): MediaRetriever } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/MediaRetriever.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.domain import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Album import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media import kotlinx.coroutines.flow.Flow interface MediaRetriever { fun getAlbumsWithType( allowedMedia: AllowedMedia ): Flow>> fun mediaFlowWithType( albumId: Long, allowedMedia: AllowedMedia ): Flow>> fun getMediaByAlbumIdWithType( albumId: Long, allowedMedia: AllowedMedia ): Flow>> fun getMediaByType( allowedMedia: AllowedMedia ): Flow>> } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Album.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.domain.model data class Album( val id: Long = 0, val label: String, val uri: String, val pathToThumbnail: String, val relativePath: String, val timestamp: Long, val count: Long = 0, val selected: Boolean = false, val isPinned: Boolean = false, ) { val volume: String = pathToThumbnail.substringBeforeLast("/").removeSuffix(relativePath.removeSuffix("/")) val isOnSdcard: Boolean = volume.lowercase().matches(".*[0-9a-f]{4}-[0-9a-f]{4}".toRegex()) companion object { val NewAlbum = Album( id = -200, label = "New Album", uri = "", pathToThumbnail = "", relativePath = "", timestamp = 0, count = 0, ) } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/AllowedMedia.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.domain.model sealed class AllowedMedia { data class Photos(val ext: String?) : AllowedMedia() data object Videos : AllowedMedia() data object Both : AllowedMedia() } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/Media.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.imagetoolbox.feature.media_picker.domain.model import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize import com.t8rin.imagetoolbox.core.utils.fileSize data class Media( val id: Long = 0, val label: String, val uri: String, val path: String, val relativePath: String, val albumID: Long, val albumLabel: String, val timestamp: Long, val expiryTimestamp: Long? = null, val takenTimestamp: Long? = null, val fullDate: String, val mimeType: String, val duration: String? = null, ) { val fileSize: Long by lazy { uri.toUri().fileSize() ?: 0 } val humanFileSize: String by lazy { humanFileSize(fileSize) } val isVideo: Boolean = mimeType.startsWith("video/") && duration != null val isImage: Boolean = mimeType.startsWith("image/") /** * Used to determine if the Media object is not accessible * via MediaStore. * This happens when the user tries to open media from an app * using external sources (in our case, Gallery Media Viewer), but * the specific media is only available internally in that app * (Android/data(OR media)/com.package.name/) * * If it's readUriOnly then we know that we should expect a barebone * Media object with limited functionality (no favorites, trash, timestamp etc) */ val readUriOnly: Boolean = albumID == -99L && albumLabel == "" /** * Determine if the current media is a raw format * * Checks if [mimeType] starts with "image/x-" or "image/vnd." * * Most used formats: * - ARW: image/x-sony-arw * - CR2: image/x-canon-cr2 * - CRW: image/x-canon-crw * - DCR: image/x-kodak-dcr * - DNG: image/x-adobe-dng * - ERF: image/x-epson-erf * - K25: image/x-kodak-k25 * - KDC: image/x-kodak-kdc * - MRW: image/x-minolta-mrw * - NEF: image/x-nikon-nef * - ORF: image/x-olympus-orf * - PEF: image/x-pentax-pef * - RAF: image/x-fuji-raf * - RAW: image/x-panasonic-raw * - SR2: image/x-sony-sr2 * - SRF: image/x-sony-srf * - X3F: image/x-sigma-x3f * * Other proprietary image types in the standard: * image/vnd.manufacturer.filename_extension for instance for NEF by Nikon and .mrv for Minolta: * - NEF: image/vnd.nikon.nef * - Minolta: image/vnd.minolta.mrw */ val isRaw: Boolean = mimeType.isNotBlank() && (mimeType.startsWith("image/x-") || mimeType.startsWith("image/vnd.")) val fileExtension: String = label.substringAfterLast(".").removePrefix(".") val volume: String = path.substringBeforeLast("/").removeSuffix(relativePath.removeSuffix("/")) } const val WEEKLY_DATE_FORMAT = "EEEE" const val DEFAULT_DATE_FORMAT = "EEE, MMMM d" const val EXTENDED_DATE_FORMAT = "EEE, MMM d, yyyy" const val FULL_DATE_FORMAT = "EEEE, MMMM d, yyyy, hh:mm a" const val HEADER_DATE_FORMAT = "MMMM d, yyyy\n" + "h:mm a" const val EXIF_DATE_FORMAT = "MMMM d, yyyy • h:mm a" ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.domain.model sealed class MediaItem { abstract val key: String data class Header( override val key: String, val text: String, val data: List ) : MediaItem() data class MediaViewItem( override val key: String, val media: Media ) : MediaItem() } val Any.isHeaderKey: Boolean get() = this is String && this.startsWith("header_") val Any.isBigHeaderKey: Boolean get() = this is String && this.startsWith("header_big_") val Any.isIgnoredKey: Boolean get() = this is String && this == "aboveGrid" ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaOrder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.domain.model import kotlinx.coroutines.coroutineScope sealed class MediaOrder(private val orderType: OrderType) { class Label(orderType: OrderType) : MediaOrder(orderType) class Date(orderType: OrderType) : MediaOrder(orderType) class Expiry(orderType: OrderType = OrderType.Descending) : MediaOrder(orderType) fun copy(orderType: OrderType): MediaOrder { return when (this) { is Date -> Date(orderType) is Label -> Label(orderType) is Expiry -> Expiry(orderType) } } suspend fun sortMedia(media: List): List = coroutineScope { when (orderType) { OrderType.Ascending -> { when (this@MediaOrder) { is Date -> media.sortedBy { it.timestamp } is Label -> media.sortedBy { it.label.lowercase() } is Expiry -> media.sortedBy { it.expiryTimestamp ?: it.timestamp } } } OrderType.Descending -> { when (this@MediaOrder) { is Date -> media.sortedByDescending { it.timestamp } is Label -> media.sortedByDescending { it.label.lowercase() } is Expiry -> media.sortedByDescending { it.expiryTimestamp ?: it.timestamp } } } } } fun sortAlbums(albums: List): List { return when (orderType) { OrderType.Ascending -> { when (this) { is Date -> albums.sortedBy { it.timestamp } is Label -> albums.sortedBy { it.label.lowercase() } else -> albums } } OrderType.Descending -> { when (this) { is Date -> albums.sortedByDescending { it.timestamp } is Label -> albums.sortedByDescending { it.label.lowercase() } else -> albums } } } } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/MediaState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.domain.model data class MediaState( val media: List = emptyList(), val mappedMedia: List = emptyList(), val dateHeader: String = "", val error: String = "", val isLoading: Boolean = true ) data class AlbumState( val albums: List = emptyList(), val error: String = "" ) ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/domain/model/OrderType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.domain.model sealed class OrderType { data object Ascending : OrderType() data object Descending : OrderType() } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/MediaPickerActivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation import androidx.compose.runtime.Composable import com.arkivanov.decompose.retainedComponent import com.t8rin.imagetoolbox.core.ui.utils.ComposeActivity import com.t8rin.imagetoolbox.feature.media_picker.presentation.components.MediaPickerRootContent import com.t8rin.imagetoolbox.feature.media_picker.presentation.screenLogic.MediaPickerComponent import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint class MediaPickerActivity : ComposeActivity() { @Inject lateinit var componentFactory: MediaPickerComponent.Factory private val component: MediaPickerComponent by lazy { retainedComponent(factory = componentFactory::invoke) } @Composable override fun Content() = MediaPickerRootContent(component) } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ManageExternalStorageWarning.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun ManageExternalStorageWarning( onRequestManagePermission: () -> Unit ) { Row( modifier = Modifier .container( color = MaterialTheme.colorScheme.errorContainer, resultPadding = 0.dp, shape = RectangleShape ) .padding( start = 16.dp, end = 16.dp, top = 16.dp, bottom = 16.dp ), verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { Row( verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Outlined.WarningAmber, contentDescription = null, tint = MaterialTheme.colorScheme.error ) Spacer(modifier = Modifier.width(8.dp)) Text( text = stringResource(R.string.manage_storage_extra_types), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyLarge, lineHeight = 18.sp, modifier = Modifier.weight(1f, false) ) } Spacer(modifier = Modifier.height(8.dp)) Text( text = stringResource(R.string.manage_storage_extra_types_sub), color = MaterialTheme.colorScheme.onBackground, style = MaterialTheme.typography.bodyMedium, lineHeight = 16.sp, textAlign = TextAlign.Start ) } Spacer(modifier = Modifier.width(8.dp)) EnhancedButton( containerColor = MaterialTheme.colorScheme.error, contentColor = MaterialTheme.colorScheme.onError, onClick = onRequestManagePermission ) { Text(text = stringResource(R.string.request)) } } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaExtensionHeader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.widget.modifier.advancedShadow import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media @Composable fun MediaExtensionHeader( modifier: Modifier = Modifier, media: Media ) { Row( modifier = modifier .padding(8.dp) .padding(vertical = 2.dp) .advancedShadow( cornersRadius = 4.dp, shadowBlurRadius = 6.dp, alpha = 0.4f ) .padding(horizontal = 2.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier, text = media.fileExtension.uppercase(), style = MaterialTheme.typography.labelMedium, color = White ) } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.animation.animateColor import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Error import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import coil3.request.ImageRequest import coil3.request.allowHardware import coil3.size.Precision import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.widget.buttons.MediaCheckBox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media @Composable fun MediaImage( modifier: Modifier = Modifier, media: Media, isInSelection: Boolean = true, isSelected: Boolean, selectionIndex: Int, canClick: Boolean, onItemClick: (Media) -> Unit, onItemLongClick: (Media) -> Unit, ) { val transition = updateTransition(isSelected) val selectedSize = transition.animateDp { if (it) 12.dp else 0.dp } val scale = transition.animateFloat { if (it) 0.5f else 1f } var isImageError by remember { mutableStateOf(false) } val strokeColor = takeColorFromScheme { if (isSelected) { if (isImageError) errorContainer else primaryContainer } else Color.Transparent } Box( modifier = modifier .clip(ShapeDefaults.extraSmall) .background(MaterialTheme.colorScheme.surfaceContainer) .then( if (canClick) { Modifier.hapticsCombinedClickable( onClick = { onItemClick(media) }, onLongClick = { onItemLongClick(media) }, ) } else Modifier ) .aspectRatio(1f) ) { val shape = animateShape( if (isSelected) { AutoCornersShape(16.dp) } else { AutoCornersShape(4.dp) } ) Box( modifier = Modifier .align(Alignment.Center) .aspectRatio(1f) .padding(selectedSize.value) .clip(shape) .border( width = if (isSelected) 2.dp else 0.dp, shape = shape, color = strokeColor ) .background( color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = shape ) ) { Picture( modifier = Modifier.fillMaxSize(), model = remember(media.uri) { ImageRequest.Builder(appContext) .data(media.uri) .size(384) .precision(Precision.INEXACT) .memoryCacheKey(media.uri) .diskCacheKey(media.uri) .allowHardware(true) .build() }, contentDescription = media.label, contentScale = ContentScale.Crop, onSuccess = { isImageError = false }, onError = { isImageError = true }, error = { Box( contentAlignment = Alignment.Center, modifier = Modifier.background( takeColorFromScheme { isNightMode -> errorContainer.copy( if (isNightMode) 0.25f else 1f ).compositeOver(surface) } ) ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, modifier = Modifier.fillMaxSize(0.5f), tint = MaterialTheme.colorScheme.onErrorContainer.copy(0.8f) ) } }, filterQuality = FilterQuality.High ) } Box( modifier = Modifier.align(Alignment.TopEnd) ) { if (media.duration != null) { MediaVideoDurationHeader( modifier = Modifier .padding(selectedSize.value / 2) .graphicsLayer { scaleX = scale.value scaleY = scale.value }, media = media, ) } else { MediaExtensionHeader( modifier = Modifier .padding(selectedSize.value / 2) .graphicsLayer { scaleX = scale.value scaleY = scale.value }, media = media ) } } if (media.fileSize > 0) { MediaSizeFooter( modifier = Modifier .align(Alignment.BottomStart) .padding(selectedSize.value / 2) .graphicsLayer { scaleX = scale.value scaleY = scale.value transformOrigin = TransformOrigin(0.3f, 0.5f) }, media = media, ) } if (isInSelection) { Box( modifier = Modifier .fillMaxWidth() .padding(4.dp) ) { MediaCheckBox( isChecked = isSelected, uncheckedColor = White.copy(0.8f), checkedColor = if (isImageError) { MaterialTheme.colorScheme.error } else MaterialTheme.colorScheme.primary, checkedIcon = if (isImageError) { Icons.Filled.Error } else Icons.Filled.CheckCircle, selectionIndex = selectionIndex, modifier = Modifier .clip(ShapeDefaults.circle) .background( transition.animateColor { if (it) MaterialTheme.colorScheme.surfaceContainer else Color.Transparent }.value ) ) } } } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaImagePager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.gestures.AnchoredDraggableDefaults import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.snapTo import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Error import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.onPrimaryContainerFixed import com.t8rin.imagetoolbox.core.ui.theme.primaryContainerFixed import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.buttons.MediaCheckBox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.image.HistogramChart import com.t8rin.imagetoolbox.core.ui.widget.image.MetadataPreviewButton import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.toShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.withLayoutCorners import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media import com.t8rin.modalsheet.FullscreenPopup import kotlinx.coroutines.delay import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.toggleScale import net.engawapg.lib.zoomable.zoomable import kotlin.math.roundToInt @Composable internal fun MediaImagePager( imagePreviewUri: String?, onDismiss: () -> Unit, media: List, selectedMedia: SnapshotStateList, onMediaClick: (Media) -> Unit ) { val visible = imagePreviewUri != null FullscreenPopup { var predictiveBackProgress by remember { mutableFloatStateOf(0f) } val animatedPredictiveBackProgress by animateFloatAsState(predictiveBackProgress) val scale = (1f - animatedPredictiveBackProgress).coerceAtLeast(0.75f) LaunchedEffect(predictiveBackProgress, visible) { if (!visible && predictiveBackProgress != 0f) { delay(600) predictiveBackProgress = 0f } } AnimatedVisibility( visible = visible, modifier = Modifier.fillMaxSize(), enter = fadeIn(tween(500)), exit = fadeOut(tween(500)) ) { val density = LocalDensity.current val screenHeight = LocalScreenSize.current.height + WindowInsets.systemBars.asPaddingValues() .let { it.calculateTopPadding() + it.calculateBottomPadding() } val anchors = with(density) { DraggableAnchors { true at 0f false at -screenHeight.toPx() } } val draggableState = remember(anchors) { AnchoredDraggableState( initialValue = true, anchors = anchors ) } LaunchedEffect(draggableState.settledValue) { if (!draggableState.settledValue) { onDismiss() delay(600) draggableState.snapTo(true) } } val initialPage by remember(imagePreviewUri, media) { derivedStateOf { imagePreviewUri?.let { media.indexOfFirst { it.uri == imagePreviewUri } }?.takeIf { it >= 0 } ?: 0 } } val pagerState = rememberPagerState( initialPage = initialPage, pageCount = { media.size } ) val progress by remember(draggableState) { derivedStateOf { draggableState.progress( from = false, to = true ) } } Box( modifier = Modifier .fillMaxSize() .withLayoutCorners { corners -> graphicsLayer { scaleX = scale scaleY = scale shape = corners.toShape(animatedPredictiveBackProgress) clip = true } } .background( MaterialTheme.colorScheme.scrim.copy(alpha = 0.6f * progress) ) ) { val currentMedia = media.getOrNull(pagerState.currentPage) val imageErrorPages = remember { mutableStateListOf() } var hideControls by remember(animatedPredictiveBackProgress) { mutableStateOf(false) } HorizontalPager( state = pagerState, modifier = Modifier.fillMaxSize(), beyondViewportPageCount = 5, pageSpacing = if (pagerState.pageCount > 1) 16.dp else 0.dp ) { page -> Box( modifier = Modifier.fillMaxSize() ) { val zoomState = rememberZoomState(20f) Picture( showTransparencyChecker = false, model = media.getOrNull(page)?.uri, modifier = Modifier .fillMaxSize() .clipToBounds() .systemBarsPadding() .displayCutoutPadding() .offset { IntOffset( x = 0, y = -draggableState .requireOffset() .roundToInt(), ) } .anchoredDraggable( state = draggableState, enabled = zoomState.scale < 1.01f && !pagerState.isScrollInProgress, orientation = Orientation.Vertical, reverseDirection = true, flingBehavior = AnchoredDraggableDefaults.flingBehavior( animationSpec = tween(500), state = draggableState ) ) .zoomable( zoomEnabled = !imageErrorPages.contains(page), zoomState = zoomState, onTap = { hideControls = !hideControls }, onDoubleTap = { zoomState.toggleScale( targetScale = 5f, position = it ) } ), enableUltraHDRSupport = true, contentScale = ContentScale.Fit, shape = RectangleShape, onSuccess = { imageErrorPages.remove(page) }, onError = { imageErrorPages.add(page) }, error = { Box( contentAlignment = Alignment.Center, modifier = Modifier.background( takeColorFromScheme { isNightMode -> errorContainer.copy( if (isNightMode) 0.25f else 1f ).compositeOver(surface) } ) ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, modifier = Modifier.fillMaxSize(0.5f), tint = MaterialTheme.colorScheme.onErrorContainer.copy(0.8f) ) } } ) } } val showTopBar by remember(hideControls, draggableState) { derivedStateOf { draggableState.offset == 0f && !hideControls } } val showBottomHist = pagerState.currentPage !in imageErrorPages val showBottomBar by remember(draggableState, showBottomHist, hideControls) { derivedStateOf { draggableState.offset == 0f && showBottomHist && !hideControls } } AnimatedVisibility( visible = showTopBar, modifier = Modifier.fillMaxWidth(), enter = fadeIn() + slideInVertically(), exit = fadeOut() + slideOutVertically() ) { EnhancedTopAppBar( colors = EnhancedTopAppBarDefaults.colors( containerColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.5f) ), type = EnhancedTopAppBarType.Center, drawHorizontalStroke = false, title = { media.size.takeIf { it > 1 }?.let { Text( text = "${pagerState.currentPage + 1}/$it", modifier = Modifier .padding(vertical = 4.dp, horizontal = 12.dp), color = White ) } }, actions = { val isImageError = imageErrorPages.contains(pagerState.currentPage) AnimatedVisibility( visible = media.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { val isChecked = selectedMedia.contains(currentMedia) MediaCheckBox( isChecked = isChecked, onCheck = { currentMedia?.let(onMediaClick) }, uncheckedColor = White, checkedColor = if (isImageError) { MaterialTheme.colorScheme.error } else MaterialTheme.colorScheme.primary, checkedIcon = if (isImageError) { Icons.Filled.Error } else Icons.Filled.CheckCircle, addContainer = isChecked ) } }, navigationIcon = { AnimatedVisibility(media.isNotEmpty()) { EnhancedIconButton( onClick = onDismiss ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = White ) } } }, ) } AnimatedVisibility( visible = showBottomBar, modifier = Modifier.align(Alignment.BottomEnd), enter = fadeIn() + slideInVertically { it / 2 }, exit = fadeOut() + slideOutVertically { it / 2 } ) { Row( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.scrim.copy(0.5f)) .navigationBarsPadding() .padding( WindowInsets.displayCutout .only( WindowInsetsSides.Horizontal ) .asPaddingValues() ) .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Row( modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically ) { currentMedia?.label?.let { Text( text = it, modifier = Modifier .animateContentSizeNoClip() .weight(1f, false), color = White, style = MaterialTheme.typography.labelLarge, fontSize = 13.sp ) } currentMedia?.humanFileSize ?.takeIf { currentMedia.fileSize > 0 } ?.let { size -> Spacer(Modifier.width(8.dp)) Text( text = size, modifier = Modifier .animateContentSizeNoClip() .background( color = MaterialTheme.colorScheme.primaryContainerFixed, shape = ShapeDefaults.circle ) .padding(horizontal = 8.dp, vertical = 4.dp), color = MaterialTheme.colorScheme.onPrimaryContainerFixed, style = MaterialTheme.typography.labelMedium ) } MetadataPreviewButton( uri = currentMedia?.uri?.toUri(), dateModified = { currentMedia?.timestamp?.times(1000) }, path = { currentMedia?.path }, name = { currentMedia?.label }, fileSize = { currentMedia?.humanFileSize } ) } Spacer(Modifier.width(16.dp)) HistogramChart( model = currentMedia?.uri ?: Uri.EMPTY, modifier = Modifier .height(50.dp) .width(90.dp), bordersColor = Color.White ) } } } PredictiveBackObserver( onProgress = { predictiveBackProgress = it / 6f }, onClean = { isCompleted -> if (isCompleted) { onDismiss() delay(400) } predictiveBackProgress = 0f }, enabled = visible ) } } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGrid.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.modifier.dragHandler import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaItem import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaState import com.t8rin.imagetoolbox.feature.media_picker.domain.model.isHeaderKey import kotlinx.coroutines.launch @Composable internal fun MediaPickerGrid( state: MediaState, isSelectionOfAll: Boolean, selectedMedia: SnapshotStateList, allowMultiple: Boolean, isButtonVisible: Boolean, onRequestManagePermission: () -> Unit, isManagePermissionAllowed: Boolean ) { val scope = rememberCoroutineScope() val stringToday = stringResource(id = R.string.header_today) val stringYesterday = stringResource(id = R.string.header_yesterday) val gridState = rememberLazyGridState() val isCheckVisible = rememberSaveable { mutableStateOf(allowMultiple) } val hapticFeedback = LocalHapticFeedback.current LaunchedEffect(state.media) { gridState.scrollToItem(0) } var imagePreviewUri by rememberSaveable { mutableStateOf(null) } val onMediaClick: (Media) -> Unit = { if (allowMultiple) { if (selectedMedia.contains(it)) selectedMedia.remove(it) else selectedMedia.add(it) } else { if (selectedMedia.contains(it)) selectedMedia.remove(it) else { if (selectedMedia.isNotEmpty()) selectedMedia[0] = it else selectedMedia.add(it) } } } val layoutDirection = LocalLayoutDirection.current val privateSelection = remember { mutableStateOf(emptySet()) } LaunchedEffect(state.mappedMedia, isSelectionOfAll, selectedMedia.size) { if (isSelectionOfAll) { privateSelection.value = state.mappedMedia.mapIndexedNotNull { index, item -> if (item is MediaItem.MediaViewItem && item.media in selectedMedia) { index } else null }.toSet() } } LaunchedEffect(selectedMedia.size) { if (selectedMedia.isEmpty() && isSelectionOfAll) { privateSelection.value = emptySet() } } val cutout = WindowInsets.displayCutout.asPaddingValues() val navBar = WindowInsets.navigationBars.asPaddingValues() LazyVerticalGrid( state = gridState, modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surface) .padding( start = cutout.calculateStartPadding(layoutDirection), end = cutout.calculateEndPadding(layoutDirection) ) .dragHandler( enabled = isSelectionOfAll && allowMultiple, key = state.mappedMedia, lazyGridState = gridState, isVertical = true, selectedItems = privateSelection, onSelectionChange = { indices -> val order: MutableList = indices.toMutableList() state.mappedMedia.forEachIndexed { index, mediaItem -> if (index in indices && mediaItem is MediaItem.MediaViewItem) { order.indexOf(index).takeIf { it >= 0 }?.let { order[it] = mediaItem.media } } } selectedMedia.clear() selectedMedia.addAll(order.mapNotNull(Any::safeCast)) }, onLongTap = { if (selectedMedia.isEmpty()) { imagePreviewUri = (state.mappedMedia[it + 1] as? MediaItem.MediaViewItem)?.media?.uri } }, shouldHandleLongTap = selectedMedia.isNotEmpty() ), columns = GridCells.Adaptive(100.dp), horizontalArrangement = Arrangement.spacedBy(1.dp), verticalArrangement = Arrangement.spacedBy(1.dp), contentPadding = remember( navBar, layoutDirection, isButtonVisible, selectedMedia.isNotEmpty() ) { PaddingValues( bottom = navBar.calculateBottomPadding().plus( if (isButtonVisible) 80.dp else 0.dp ).plus( if (selectedMedia.isNotEmpty()) 52.dp else 0.dp ) ) }, flingBehavior = enhancedFlingBehavior(FlingType.IOS_STYLE) ) { if (!isManagePermissionAllowed) { item( span = { GridItemSpan(maxLineSpan) } ) { ManageExternalStorageWarning(onRequestManagePermission) } } itemsIndexed( items = state.mappedMedia, key = { index, item -> "${item.key}-$index" }, contentType = { _, item -> item.key.startsWith("media_") }, span = { _, item -> GridItemSpan(if (item.key.isHeaderKey) maxLineSpan else 1) } ) { _, item -> when (item) { is MediaItem.Header -> { val isChecked = rememberSaveable { mutableStateOf(false) } if (allowMultiple) { LaunchedEffect(selectedMedia.size) { // Partial check of media items should not check the header isChecked.value = selectedMedia.containsAll(item.data) } } MediaStickyHeader( date = remember(item.text) { item.text .replace("Today", stringToday) .replace("Yesterday", stringYesterday) }, isCheckVisible = isCheckVisible, isChecked = isChecked.value, onChecked = { if (allowMultiple) { hapticFeedback.longPress() scope.launch { isChecked.value = !isChecked.value if (isChecked.value) { val toAdd = item.data.toMutableList().apply { // Avoid media from being added twice to selection removeIf { selectedMedia.contains(it) } } selectedMedia.addAll(toAdd) } else selectedMedia.removeAll(item.data) } } } ) } is MediaItem.MediaViewItem -> { val selectionIndex = selectedMedia.indexOf(item.media) MediaImage( media = item.media, canClick = !isSelectionOfAll || !allowMultiple, onItemClick = { hapticFeedback.longPress() onMediaClick(it) }, onItemLongClick = { imagePreviewUri = it.uri }, selectionIndex = if (selectedMedia.size > 1) selectionIndex else -1, isSelected = selectionIndex >= 0 ) } } } } MediaImagePager( imagePreviewUri = imagePreviewUri, onDismiss = { imagePreviewUri = null }, media = state.media, selectedMedia = selectedMedia, onMediaClick = onMediaClick ) } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerGridWithOverlays.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TaskAlt import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material.icons.twotone.ImageNotSupported import androidx.compose.material3.BadgedBox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButtonType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia import com.t8rin.imagetoolbox.feature.media_picker.presentation.screenLogic.MediaPickerComponent @Composable internal fun MediaPickerGridWithOverlays( component: MediaPickerComponent, isSearching: Boolean, allowedMedia: AllowedMedia, allowMultiple: Boolean, onRequestManagePermission: () -> Unit, isManagePermissionAllowed: Boolean, selectedAlbumIndex: Long, onSearchingChange: (Boolean) -> Unit, onPicked: (List) -> Unit, modifier: Modifier = Modifier ) { val albumsState by component.albumsState.collectAsState() val mediaState by component.mediaState.collectAsState() val selectedMedia = component.selectedMedia var searchKeyword by rememberSaveable(isSearching) { mutableStateOf("") } val filterMedia = { component.filterMedia( searchKeyword = searchKeyword.trim(), isForceReset = !isSearching || searchKeyword.trim() .isBlank() || mediaState.media.isEmpty() ) } val filteredMediaState by component.filteredMediaState.collectAsState() Box( modifier = modifier.fillMaxSize() ) { Box( modifier = Modifier.fillMaxSize() ) { val isButtonVisible = (!allowMultiple || selectedMedia.isNotEmpty()) && !isSearching MediaPickerGrid( state = filteredMediaState, isSelectionOfAll = selectedAlbumIndex == -1L, selectedMedia = selectedMedia, allowMultiple = allowMultiple, isButtonVisible = isButtonVisible, isManagePermissionAllowed = isManagePermissionAllowed, onRequestManagePermission = onRequestManagePermission ) BoxAnimatedVisibility( modifier = Modifier .align(Alignment.BottomEnd) .padding(16.dp) .safeDrawingPadding(), visible = isButtonVisible, enter = slideInVertically { it * 2 }, exit = slideOutVertically { it * 2 } ) { val enabled = selectedMedia.isNotEmpty() val containerColor by animateColorAsState( targetValue = if (enabled) { MaterialTheme.colorScheme.primaryContainer } else MaterialTheme.colorScheme.surfaceVariant ) val contentColor by animateColorAsState( targetValue = if (enabled) { MaterialTheme.colorScheme.onPrimaryContainer } else MaterialTheme.colorScheme.onSurfaceVariant ) Column( horizontalAlignment = Alignment.End ) { AnimatedVisibility(visible = selectedMedia.isNotEmpty()) { EnhancedFloatingActionButton( type = EnhancedFloatingActionButtonType.Small, onClick = selectedMedia::clear, containerColor = MaterialTheme.colorScheme.secondaryContainer, contentColor = MaterialTheme.colorScheme.onSecondaryContainer, content = { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) }, modifier = Modifier.padding(bottom = 8.dp) ) } BadgedBox( badge = { if (selectedMedia.isNotEmpty() && allowMultiple) { EnhancedBadge( containerColor = MaterialTheme.colorScheme.primary ) { Text(selectedMedia.size.toString()) } } } ) { EnhancedFloatingActionButton( content = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp) ) { Icon( imageVector = Icons.Outlined.TaskAlt, contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) Text(stringResource(R.string.pick)) } }, containerColor = containerColor, contentColor = contentColor, onClick = { if (enabled) { onPicked(selectedMedia.map { it.uri.toUri() }) } }, modifier = Modifier .semantics { contentDescription = "Add media" } ) } } BackHandler(selectedMedia.isNotEmpty()) { selectedMedia.clear() } } } val isHaveNoData = mediaState.media.isEmpty() && !mediaState.isLoading val showLoading = (mediaState.isLoading || filteredMediaState.isLoading) && !isHaveNoData val backgroundColor by animateColorAsState( MaterialTheme.colorScheme.scrim.copy( if (showLoading && filteredMediaState.media.isNotEmpty()) 0.5f else 0f ) ) BoxAnimatedVisibility( visible = showLoading, modifier = Modifier .fillMaxSize() .imePadding() .background(backgroundColor), enter = scaleIn() + fadeIn(), exit = scaleOut() + fadeOut() ) { Box( modifier = Modifier .fillMaxSize() .windowInsetsPadding( WindowInsets.displayCutout .union(WindowInsets.navigationBars) ), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } BoxAnimatedVisibility( visible = filteredMediaState.media.isEmpty() && !filteredMediaState.isLoading && isSearching, enter = scaleIn() + fadeIn(), exit = scaleOut() + fadeOut(), modifier = Modifier .fillMaxSize() .imePadding() ) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } BoxAnimatedVisibility( visible = isHaveNoData, enter = scaleIn() + fadeIn(), exit = scaleOut() + fadeOut(), modifier = Modifier .fillMaxSize() .imePadding() ) { val errorMessage = albumsState.error + "\n" + mediaState.error Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Icon( imageVector = Icons.TwoTone.ImageNotSupported, contentDescription = null, modifier = Modifier.size(108.dp) ) Spacer(modifier = Modifier.height(8.dp)) Text( text = errorMessage.trim().ifEmpty { stringResource(id = R.string.no_data) }, fontWeight = FontWeight.SemiBold, fontSize = 16.sp ) Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp)) EnhancedButton( onClick = { component.init(allowedMedia) } ) { Text(stringResource(id = R.string.try_again)) } Spacer(Modifier.weight(1f)) } } BoxAnimatedVisibility( visible = !mediaState.isLoading && !isHaveNoData, modifier = Modifier.fillMaxSize(), enter = fadeIn(), exit = fadeOut() ) { AnimatedContent( modifier = Modifier .fillMaxSize() .padding(16.dp) .safeDrawingPadding(), targetState = isSearching, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { searchMode -> Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomStart ) { if (searchMode) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = searchKeyword, onValueChange = { searchKeyword = it filterMedia() }, startIcon = { EnhancedIconButton( onClick = { searchKeyword = "" onSearchingChange(false) filterMedia() }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.exit), tint = MaterialTheme.colorScheme.onSurface ) } }, endIcon = { BoxAnimatedVisibility( visible = searchKeyword.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = { searchKeyword = "" filterMedia() }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface ) } } }, shape = ShapeDefaults.circle ) } else { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer, modifier = Modifier .padding(bottom = 6.dp) .size(44.dp), onClick = { onSearchingChange(true) filterMedia() }, shape = MaterialStarShape, pressedShape = MaterialStarShape ) { Icon( imageVector = Icons.Rounded.Search, contentDescription = null ) } } } } } } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerHavePermissions.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.coerceAtLeast import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Album import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia import com.t8rin.imagetoolbox.feature.media_picker.presentation.screenLogic.MediaPickerComponent @Composable internal fun MediaPickerHavePermissions( component: MediaPickerComponent, allowedMedia: AllowedMedia, allowMultiple: Boolean, onRequestManagePermission: () -> Unit, isManagePermissionAllowed: Boolean, onPicked: (List) -> Unit ) { var selectedAlbumIndex by rememberSaveable { mutableLongStateOf(-1) } val albumsState by component.albumsState.collectAsState() var isSearching by rememberSaveable { mutableStateOf(false) } BackHandler(selectedAlbumIndex != -1L) { selectedAlbumIndex = -1L component.getAlbum(selectedAlbumIndex) } BackHandler(isSearching) { isSearching = false } Scaffold( topBar = { AnimatedVisibility( modifier = Modifier.fillMaxWidth(), visible = albumsState.albums.size > 1 ) { val layoutDirection = LocalLayoutDirection.current var showAlbumThumbnail by rememberSaveable { mutableStateOf(false) } val listState = rememberLazyListState() Row( modifier = Modifier .drawHorizontalStroke() .background(MaterialTheme.colorScheme.surfaceContainer) ) { LazyRow( modifier = Modifier .weight(1f) .fadingEdges(listState) .padding(vertical = 8.dp), horizontalArrangement = Arrangement.spacedBy( space = 8.dp ), contentPadding = PaddingValues( start = WindowInsets.displayCutout .asPaddingValues() .calculateStartPadding(layoutDirection) + 8.dp, end = WindowInsets.displayCutout .asPaddingValues() .calculateEndPadding(layoutDirection) + 8.dp ), state = listState, flingBehavior = enhancedFlingBehavior() ) { items( items = albumsState.albums, key = Album::toString ) { album -> val selected = selectedAlbumIndex == album.id val isImageVisible = showAlbumThumbnail && album.uri.isNotEmpty() EnhancedChip( selected = selected, selectedColor = MaterialTheme.colorScheme.secondaryContainer, unselectedColor = MaterialTheme.colorScheme.surfaceContainerHigh, unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant, onClick = { selectedAlbumIndex = album.id component.getAlbum(selectedAlbumIndex) }, contentPadding = PaddingValues( horizontal = animateDpAsState( if (isImageVisible) 8.dp else 12.dp ).value, vertical = animateDpAsState( if (isImageVisible) 8.dp else 0.dp ).value ), label = { val title = if (album.id == -1L) stringResource(R.string.all) else album.label Column( modifier = Modifier .animateContentSizeNoClip( alignment = Alignment.Center ) .then( if (showAlbumThumbnail && album.uri.isEmpty()) { Modifier.height(140.dp) } else Modifier ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { var width by remember { mutableStateOf(1.dp) } val density = LocalDensity.current Text( text = title, modifier = Modifier.onSizeChanged { width = with(density) { it.width.toDp().coerceAtLeast(100.dp) } } ) BoxAnimatedVisibility( visible = isImageVisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Box { BoxAnimatedVisibility( visible = width > 1.dp, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { Picture( model = album.uri, modifier = Modifier .padding(top = 8.dp) .height(100.dp) .width(width), shape = ShapeDefaults.small ) } Box( modifier = Modifier .padding(top = 8.dp) .height(100.dp) .width(width) .clip(ShapeDefaults.small) .background( MaterialTheme .colorScheme .surfaceContainer .copy(0.6f) ), contentAlignment = Alignment.Center ) { AutoSizeText( text = album.count.toString(), style = MaterialTheme.typography.headlineLarge.copy( fontSize = 20.sp, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold ) ) } } } } }, defaultMinSize = 32.dp, shape = ShapeDefaults.default ) } } EnhancedIconButton( onClick = { showAlbumThumbnail = !showAlbumThumbnail } ) { val rotation by animateFloatAsState(if (showAlbumThumbnail) 180f else 0f) Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate(rotation) ) } } } }, contentWindowInsets = WindowInsets() ) { contentPadding -> MediaPickerGridWithOverlays( component = component, isSearching = isSearching, allowedMedia = allowedMedia, allowMultiple = allowMultiple, onRequestManagePermission = onRequestManagePermission, isManagePermissionAllowed = isManagePermissionAllowed, selectedAlbumIndex = selectedAlbumIndex, onSearchingChange = { isSearching = it }, onPicked = onPicked, modifier = Modifier.padding(contentPadding) ) } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import android.content.Intent import androidx.compose.runtime.Composable import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiState import com.t8rin.imagetoolbox.core.ui.utils.provider.ImageToolboxCompositionLocals import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia import com.t8rin.imagetoolbox.feature.media_picker.presentation.screenLogic.MediaPickerComponent @Composable internal fun MediaPickerRootContent(component: MediaPickerComponent) { val context = LocalComponentActivity.current ImageToolboxCompositionLocals( settingsState = component.settingsState.toUiState() ) { MediaPickerRootContentEmbeddable( component = component, allowedMedia = context.intent.type.allowedMedia, allowMultiple = context.intent.allowMultiple, onPicked = context::sendMediaAsResult, onBack = context::finish ) ObserveColorSchemeExtra() } } private val Intent.allowMultiple get() = getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) private val String?.pickImage: Boolean get() = this?.startsWith("image") == true private val String?.pickVideo: Boolean get() = this?.startsWith("video") == true private val String?.allowedMedia: AllowedMedia get() = if (pickImage) AllowedMedia.Photos(this?.takeLastWhile { it != '/' }) else if (pickVideo) AllowedMedia.Videos else AllowedMedia.Both ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaPickerRootContentEmbeddable.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import android.Manifest import android.net.Uri import android.os.Build import android.os.Environment import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.app.ActivityCompat import com.t8rin.imagetoolbox.core.domain.utils.tryAll import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.appSettingsIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.manageAllFilesIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.manageAppAllFilesIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.requestPermissions import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionStatus import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionUtils.checkPermissions import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberCurrentLifecycleEvent import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia import com.t8rin.imagetoolbox.feature.media_picker.presentation.screenLogic.MediaPickerComponent @Composable fun MediaPickerRootContentEmbeddable( component: MediaPickerComponent, onPicked: (List) -> Unit, modifier: Modifier = Modifier, allowedMedia: AllowedMedia = AllowedMedia.Photos(null), allowMultiple: Boolean = true, onBack: (() -> Unit)? = null ) { val context = LocalComponentActivity.current var isPermissionAllowed by remember { mutableStateOf(true) } var isManagePermissionAllowed by remember { mutableStateOf(true) } var invalidator by remember { mutableIntStateOf(0) } val launcher = rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult() ) { invalidator++ } val requestManagePermission = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { tryAll( { launcher.launch(context.manageAppAllFilesIntent()) }, { launcher.launch(manageAllFilesIntent()) }, { launcher.launch(context.appSettingsIntent()) } ) } } val lifecycleEvent = rememberCurrentLifecycleEvent() LaunchedEffect(lifecycleEvent, invalidator) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val permission = Manifest.permission.READ_MEDIA_IMAGES isManagePermissionAllowed = Environment.isExternalStorageManager() || context.isInstalledFromPlayStore() when (context.checkPermissions(listOf(permission)).finalStatus) { PermissionStatus.ALLOWED -> { isPermissionAllowed = true component.init(allowedMedia) } PermissionStatus.NOT_GIVEN -> { ActivityCompat.requestPermissions( context, arrayOf(permission), 0 ) } PermissionStatus.DENIED_PERMANENTLY -> Unit } } } val content: @Composable (PaddingValues) -> Unit = { AnimatedContent( targetState = isPermissionAllowed, modifier = Modifier .fillMaxSize() .padding(top = it.calculateTopPadding()) ) { havePermissions -> if (havePermissions) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { MediaPickerHavePermissions( allowedMedia = allowedMedia, allowMultiple = allowMultiple, component = component, isManagePermissionAllowed = isManagePermissionAllowed, onRequestManagePermission = requestManagePermission, onPicked = onPicked ) LaunchedEffect(Unit) { component.init(allowedMedia = allowedMedia) } } } else { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, modifier = Modifier.size(108.dp) ) Spacer(modifier = Modifier.height(8.dp)) Text( text = stringResource(id = R.string.no_permissions), fontWeight = FontWeight.SemiBold, fontSize = 16.sp, textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) Spacer(modifier = Modifier.height(16.dp)) EnhancedButton( onClick = { val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Manifest.permission.READ_MEDIA_IMAGES } else { Manifest.permission.READ_EXTERNAL_STORAGE } context.requestPermissions(listOf(permission)) } ) { Text(stringResource(id = R.string.request)) } } } } } Box(modifier = modifier) { if (onBack == null) { content(PaddingValues()) } else { Scaffold( topBar = { EnhancedTopAppBar( title = { Text( text = if (allowMultiple) { stringResource(R.string.pick_multiple_media) } else { stringResource(R.string.pick_single_media) }, modifier = Modifier.marquee() ) }, navigationIcon = { EnhancedIconButton( onClick = onBack, containerColor = Color.Transparent ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.close) ) } }, actions = { TopAppBarEmoji() }, drawHorizontalStroke = component.albumsState.collectAsState().value.albums.size <= 1 ) }, content = content ) } } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaSizeFooter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.widget.modifier.advancedShadow import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media @Composable fun MediaSizeFooter( modifier: Modifier = Modifier, media: Media ) { Row( modifier = modifier .padding(8.dp) .padding(vertical = 2.dp) .advancedShadow( cornersRadius = 4.dp, shadowBlurRadius = 6.dp, alpha = 0.4f ) .padding(horizontal = 2.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier, text = media.humanFileSize, style = MaterialTheme.typography.labelMedium, color = White ) } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaStickyHeader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.buttons.MediaCheckBox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable @Composable fun MediaStickyHeader( modifier: Modifier = Modifier, date: String, isCheckVisible: MutableState, isChecked: Boolean, onChecked: (() -> Unit)? = null ) { Row( modifier = modifier .padding( horizontal = 16.dp, vertical = 24.dp ) .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( text = date, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.hapticsCombinedClickable( interactionSource = remember { MutableInteractionSource() }, indication = null, onLongClick = onChecked, onClick = { if (isCheckVisible.value) onChecked?.invoke() } ) ) if (onChecked != null) { AnimatedVisibility( visible = isCheckVisible.value, enter = enterAnimation, exit = exitAnimation ) { MediaCheckBox( isChecked = isChecked, onCheck = onChecked, modifier = Modifier.size(20.dp) ) } } } } private val enterAnimation = fadeIn(tween(150)) private val exitAnimation = fadeOut(tween(150)) ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/MediaVideoDurationHeader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.PlayCircle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.widget.modifier.advancedShadow import com.t8rin.imagetoolbox.feature.media_picker.data.utils.formatMinSec import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media @Composable fun MediaVideoDurationHeader( modifier: Modifier = Modifier, media: Media ) { Row( modifier = modifier .padding(all = 8.dp) .advancedShadow( cornersRadius = 8.dp, shadowBlurRadius = 6.dp, alpha = 0.3f ), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier, text = media.duration.formatMinSec(), style = MaterialTheme.typography.labelSmall, color = White ) Spacer(modifier = Modifier.size(2.dp)) Icon( modifier = Modifier .size(16.dp) .advancedShadow( cornersRadius = 2.dp, shadowBlurRadius = 6.dp, alpha = 0.1f, offsetY = (-2).dp ), imageVector = Icons.Rounded.PlayCircle, tint = White, contentDescription = "Video" ) } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/ObserveColorSchemeExtra.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.core.ui.utils.helper.ColorSchemeName import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable internal fun ObserveColorSchemeExtra() { val context = LocalComponentActivity.current val dynamicTheme = LocalDynamicThemeState.current val scope = rememberCoroutineScope() SideEffect { context.intent.getIntExtra(ColorSchemeName, Color.Transparent.toArgb()).takeIf { it != Color.Transparent.toArgb() }?.let { scope.launch { while (dynamicTheme.colorTuple.value.primary != Color(it)) { dynamicTheme.updateColorTuple(ColorTuple(Color(it))) delay(500L) } } } } } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/components/SendMediaAsResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.components import android.content.Intent import android.net.Uri import androidx.activity.ComponentActivity import androidx.appcompat.app.AppCompatActivity.RESULT_OK import com.t8rin.imagetoolbox.core.ui.utils.helper.toClipData internal fun ComponentActivity.sendMediaAsResult(selectedMedia: List) { val newIntent = Intent( if (selectedMedia.size == 1) Intent.ACTION_SEND else Intent.ACTION_SEND_MULTIPLE ).apply { if (selectedMedia.size == 1) { data = selectedMedia.first() clipData = selectedMedia.toClipData() putExtra( Intent.EXTRA_STREAM, selectedMedia.first() ) } else { clipData = selectedMedia.toClipData() putParcelableArrayListExtra( Intent.EXTRA_STREAM, ArrayList(selectedMedia) ) } addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } setResult(RESULT_OK, newIntent) finish() } ================================================ FILE: feature/media-picker/src/main/java/com/t8rin/imagetoolbox/feature/media_picker/presentation/screenLogic/MediaPickerComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.media_picker.presentation.screenLogic import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.feature.media_picker.data.utils.DateExt import com.t8rin.imagetoolbox.feature.media_picker.data.utils.getDate import com.t8rin.imagetoolbox.feature.media_picker.data.utils.getDateExt import com.t8rin.imagetoolbox.feature.media_picker.data.utils.getDateHeader import com.t8rin.imagetoolbox.feature.media_picker.data.utils.getMonth import com.t8rin.imagetoolbox.feature.media_picker.domain.MediaRetriever import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Album import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AlbumState import com.t8rin.imagetoolbox.feature.media_picker.domain.model.AllowedMedia import com.t8rin.imagetoolbox.feature.media_picker.domain.model.Media import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaItem import com.t8rin.imagetoolbox.feature.media_picker.domain.model.MediaState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext class MediaPickerComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, private val settingsManager: SettingsManager, private val mediaRetriever: MediaRetriever, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _settingsState = mutableStateOf(SettingsState.Default) val settingsState: SettingsState by _settingsState val selectedMedia = mutableStateListOf() private val _mediaState = MutableStateFlow(MediaState()) val mediaState = _mediaState.asStateFlow() private val _filteredMediaState = MutableStateFlow(MediaState()) val filteredMediaState = _filteredMediaState.asStateFlow() private val _albumsState = MutableStateFlow(AlbumState()) val albumsState = _albumsState.asStateFlow() fun init(allowedMedia: AllowedMedia) { this.allowedMedia = allowedMedia getMedia(selectedAlbumId, allowedMedia) getAlbums(allowedMedia) } fun getAlbum(albumId: Long) { this.selectedAlbumId = albumId getMedia(albumId, allowedMedia) getAlbums(allowedMedia) } private var allowedMedia: AllowedMedia = AllowedMedia.Photos(null) private var selectedAlbumId: Long = -1L private val emptyAlbum = Album( id = -1, label = "All", uri = "", pathToThumbnail = "", timestamp = 0, relativePath = "" ) private var albumJob: Job? by smartJob() private fun getAlbums(allowedMedia: AllowedMedia) { albumJob = componentScope.launch { mediaRetriever.getAlbumsWithType(allowedMedia) .flowOn(defaultDispatcher) .collectLatest { result -> val data = result.getOrNull() ?: emptyList() val error = if (result.isFailure) result.exceptionOrNull()?.message ?: "An error occurred" else "" if (data.isEmpty()) { return@collectLatest _albumsState.emit( AlbumState( albums = listOf(emptyAlbum), error = error ) ) } val albums = mutableListOf().apply { add(emptyAlbum) addAll(data) } _albumsState.emit(AlbumState(albums = albums, error = error)) } } } private var mediaGettingJob: Job? by smartJob() private fun getMedia( albumId: Long, allowedMedia: AllowedMedia ) { mediaGettingJob = componentScope.launch { _mediaState.emit(mediaState.value.copy(isLoading = true)) mediaRetriever.mediaFlowWithType(albumId, allowedMedia) .flowOn(defaultDispatcher) .collectLatest { result -> val data = if (allowedMedia is AllowedMedia.Photos && allowedMedia.ext != null && allowedMedia.ext != "*") { result.getOrNull()?.filter { it.uri.endsWith(allowedMedia.ext) } } else { result.getOrNull() }?.distinctBy { it.id } ?: emptyList() val error = if (result.isFailure) result.exceptionOrNull()?.message ?: "An error occurred" else "" if (data.isEmpty()) { return@collectLatest _mediaState.emit(MediaState(isLoading = false)) } _mediaState.collectMedia(data, error, albumId) _filteredMediaState.emit(mediaState.value) } } } private suspend fun MutableStateFlow.collectMedia( data: List, error: String, albumId: Long, groupByMonth: Boolean = false ) { val mappedData = mutableListOf() val monthHeaderList: MutableSet = mutableSetOf() withContext(defaultDispatcher) { val groupedData = data.groupBy { if (groupByMonth) { it.timestamp.getMonth() } else { it.timestamp.getDate( stringToday = "Today", stringYesterday = "Yesterday" ) } } groupedData.forEach { (date, data) -> val dateHeader = MediaItem.Header("header_$date", date, data) val groupedMedia = data.map { MediaItem.MediaViewItem("media_${it.id}_${it.label}", it) } if (groupByMonth) { mappedData.add(dateHeader) mappedData.addAll(groupedMedia) } else { val month = getMonth(date) if (month.isNotEmpty() && !monthHeaderList.contains(month)) { monthHeaderList.add(month) } mappedData.add(dateHeader) mappedData.addAll(groupedMedia) } } } withContext(uiDispatcher) { tryEmit( MediaState( isLoading = false, error = error, media = data, mappedMedia = mappedData, dateHeader = data.dateHeader(albumId) ) ) } } private fun List.dateHeader(albumId: Long): String = if (albumId != -1L && isNotEmpty()) { val startDate: DateExt = last().timestamp.getDateExt() val endDate: DateExt = first().timestamp.getDateExt() getDateHeader(startDate, endDate) } else "" private var mediaFilterJob: Job? by smartJob() fun filterMedia( searchKeyword: String, isForceReset: Boolean ) { mediaFilterJob = componentScope.launch { if (isForceReset) { _filteredMediaState.emit(mediaState.value) } else { _filteredMediaState.emit(mediaState.value.copy(isLoading = true)) _filteredMediaState.collectMedia( data = mediaState.value.media.filter { if (searchKeyword.startsWith("*")) { it.label.endsWith( suffix = searchKeyword.drop(1), ignoreCase = true ) } else if (searchKeyword.endsWith("*")) { it.label.startsWith( prefix = searchKeyword.dropLast(1), ignoreCase = true ) } else { it.label.contains( other = searchKeyword, ignoreCase = true ) } }.distinctBy { it.id }, error = mediaState.value.error, albumId = selectedAlbumId ) } } } init { runBlocking { _settingsState.value = settingsManager.getSettingsState() } settingsManager.settingsState.onEach { _settingsState.value = it }.launchIn(componentScope) } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext ): MediaPickerComponent } } ================================================ FILE: feature/mesh-gradients/.gitignore ================================================ /build ================================================ FILE: feature/mesh-gradients/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.mesh_gradients" ================================================ FILE: feature/mesh-gradients/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/MeshGradientsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.mesh_gradients.presentation import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.image.ImagePreviewGrid import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.mesh_gradients.presentation.screenLogic.MeshGradientsComponent @Composable fun MeshGradientsContent( component: MeshGradientsComponent ) { val childScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( modifier = Modifier .fillMaxSize() .nestedScroll(childScrollBehavior.nestedScrollConnection), topBar = { EnhancedTopAppBar( title = { Text( text = stringResource(R.string.collection_mesh_gradients), modifier = Modifier.marquee() ) }, navigationIcon = { EnhancedIconButton( onClick = component.onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = null ) } }, actions = { TopAppBarEmoji() }, type = EnhancedTopAppBarType.Large, scrollBehavior = childScrollBehavior ) } ) { contentPadding -> AnimatedContent( modifier = Modifier.fillMaxSize(), targetState = component.meshGradientUris ) { uris -> if (uris.isNotEmpty()) { ImagePreviewGrid( data = uris, onAddImages = null, onShareImage = { component.shareImages( uriList = listOf(element = it), ) }, onRemove = null, onNavigate = component.onNavigate, imageFrames = null, onFrameSelectionChange = {}, contentPadding = PaddingValues(12.dp), isSelectable = false, modifier = Modifier.padding(contentPadding) ) } else { val meshGradientDownloadProgress = component.meshGradientDownloadProgress Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { val currentPercent = meshGradientDownloadProgress?.currentPercent ?: 0f if (currentPercent > 0f) { EnhancedLoadingIndicator( progress = currentPercent, loaderSize = 72.dp ) { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() .padding(8.dp) ) { Text( text = rememberHumanFileSize( meshGradientDownloadProgress?.currentTotalSize ?: 0 ), maxLines = 1, textAlign = TextAlign.Center, fontSize = 10.sp, lineHeight = 10.sp ) } } } else { EnhancedLoadingIndicator() } } } } } } ================================================ FILE: feature/mesh-gradients/src/main/java/com/t8rin/imagetoolbox/feature/mesh_gradients/presentation/screenLogic/MeshGradientsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.mesh_gradients.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.remote.RemoteResources import com.t8rin.imagetoolbox.core.domain.remote.RemoteResourcesStore import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.delay class MeshGradientsComponent @AssistedInject constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val shareProvider: ImageShareProvider, private val imageGetter: ImageGetter, dispatchersHolder: DispatchersHolder, remoteResourcesStore: RemoteResourcesStore ) : BaseComponent(dispatchersHolder, componentContext) { private val _meshGradientUris = mutableStateOf(emptyList()) val meshGradientUris by _meshGradientUris private val _meshGradientDownloadProgress: MutableState = mutableStateOf(null) val meshGradientDownloadProgress by _meshGradientDownloadProgress init { componentScope.launch { delay(200) val resources = remoteResourcesStore .getResources( name = RemoteResources.MESH_GRADIENTS, forceUpdate = true, onDownloadRequest = { launch { remoteResourcesStore.downloadResources( name = RemoteResources.MESH_GRADIENTS, onProgress = { _meshGradientDownloadProgress.value = it }, onFailure = {}, downloadOnlyNewData = true ) } null } ) _meshGradientUris.value = resources?.list?.map { it.uri.toUri() } ?: emptyList() } } fun shareImages( uriList: List ) = componentScope.launch { shareProvider.shareImages( uris = uriList.map { it.toString() }, imageLoader = { imageGetter.getImage(it)?.run { image to imageInfo } }, onProgressChange = {} ) AppToastHost.showConfetti() } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit ): MeshGradientsComponent } } ================================================ FILE: feature/noise-generation/.gitignore ================================================ /build ================================================ FILE: feature/noise-generation/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.noise_generation" dependencies { implementation(libs.toolbox.fastNoise) } ================================================ FILE: feature/noise-generation/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/data/AndroidNoiseGenerator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.data import android.graphics.Bitmap import com.t8rin.fast_noise.FastNoise import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.noise_generation.domain.NoiseGenerator import com.t8rin.imagetoolbox.noise_generation.domain.model.NoiseParams import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidNoiseGenerator @Inject constructor( dispatchersHolder: DispatchersHolder ) : NoiseGenerator, DispatchersHolder by dispatchersHolder { override suspend fun generateNoise( width: Int, height: Int, noiseParams: NoiseParams, onFailure: (Throwable) -> Unit ): Bitmap? = withContext(defaultDispatcher) { runCatching { with(noiseParams) { FastNoise.generateNoiseImage( width = width, height = height, seed = seed, frequency = frequency, noiseType = noiseType.ordinal, rotationType3D = rotationType3D.ordinal, fractalType = fractalType.ordinal, fractalOctaves = fractalOctaves, fractalLacunarity = fractalLacunarity, fractalGain = fractalGain, fractalWeightedStrength = fractalWeightedStrength, fractalPingPongStrength = fractalPingPongStrength, cellularDistanceFunction = cellularDistanceFunction.ordinal, cellularReturnType = cellularReturnType.ordinal, cellularJitter = cellularJitter, domainWarpType = domainWarpType.ordinal, domainWarpAmp = domainWarpAmp, ) } }.onFailure(onFailure).getOrNull() } } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/di/NoiseGenerationModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.noise_generation.data.AndroidNoiseGenerator import com.t8rin.imagetoolbox.noise_generation.domain.NoiseGenerator import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) internal interface NoiseGenerationModule { @Binds fun provideGenerator( impl: AndroidNoiseGenerator ): NoiseGenerator } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/NoiseGenerator.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain import com.t8rin.imagetoolbox.noise_generation.domain.model.NoiseParams interface NoiseGenerator { suspend fun generateNoise( width: Int, height: Int, noiseParams: NoiseParams, onFailure: (Throwable) -> Unit = {} ): Image? } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularDistanceFunction.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain.model enum class CellularDistanceFunction { Euclidean, EuclideanSq, Manhattan, Hybrid } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/CellularReturnType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain.model enum class CellularReturnType { CellValue, Distance, Distance2, Distance2Add, Distance2Sub, Distance2Mul, Distance2Div } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/DomainWarpType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain.model enum class DomainWarpType { OpenSimplex2, OpenSimplex2Reduced, BasicGrid } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/FractalType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain.model enum class FractalType { None, FBm, Ridged, PingPong, DomainWarpProgressive, DomainWarpIndependent } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain.model data class NoiseParams( val seed: Int, val frequency: Float, val noiseType: NoiseType, val rotationType3D: RotationType3D, val fractalType: FractalType, val fractalOctaves: Int, val fractalLacunarity: Float, val fractalGain: Float, val fractalWeightedStrength: Float, val fractalPingPongStrength: Float, val cellularDistanceFunction: CellularDistanceFunction, val cellularReturnType: CellularReturnType, val cellularJitter: Float, val domainWarpType: DomainWarpType, val domainWarpAmp: Float ) { companion object { val Default by lazy { NoiseParams( seed = 1337, frequency = 0.01f, noiseType = NoiseType.OpenSimplex2, rotationType3D = RotationType3D.None, fractalType = FractalType.None, fractalOctaves = 3, fractalLacunarity = 2f, fractalGain = 0.5f, fractalWeightedStrength = 0f, fractalPingPongStrength = 2f, cellularDistanceFunction = CellularDistanceFunction.EuclideanSq, cellularReturnType = CellularReturnType.Distance, cellularJitter = 1f, domainWarpType = DomainWarpType.OpenSimplex2, domainWarpAmp = 1f ) } } } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/NoiseType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain.model enum class NoiseType { OpenSimplex2, OpenSimplex2S, Cellular, Perlin, ValueCubic, Value } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/domain/model/RotationType3D.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.domain.model enum class RotationType3D { None, ImproveXYPlanes, ImproveXZPlanes } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/NoiseGenerationContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.presentation import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.animation.animate import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.state.derivedValueOf import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.noise_generation.presentation.components.NoiseParamsSelection import com.t8rin.imagetoolbox.noise_generation.presentation.screenLogic.NoiseGenerationComponent @Composable fun NoiseGenerationContent( component: NoiseGenerationComponent ) { val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveNoise( oneTimeSaveLocationUri = it ) } val shareButton: @Composable () -> Unit = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( onShare = component::shareNoise, onCopy = { component.cacheCurrentNoise(Clipboard::copy) }, onEdit = { component.cacheCurrentNoise { editSheetData = listOf(it) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } AdaptiveLayoutScreen( shouldDisableBackHandler = true, title = { Text( text = stringResource(R.string.noise_generation), textAlign = TextAlign.Center, modifier = Modifier.marquee() ) }, onGoBack = component.onGoBack, actions = {}, topAppBarPersistentActions = { TopAppBarEmoji() }, imagePreview = { Box( contentAlignment = Alignment.Center ) { Picture( model = component.previewBitmap, modifier = Modifier .container(MaterialTheme.shapes.medium) .aspectRatio(component.noiseSize.safeAspectRatio.animate()), shape = MaterialTheme.shapes.medium, contentScale = ContentScale.FillBounds ) if (component.isImageLoading) EnhancedLoadingIndicator() } }, controls = { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { ResizeImageField( imageInfo = derivedValueOf(component.noiseSize) { ImageInfo(component.noiseSize.width, component.noiseSize.height) }, originalSize = null, onWidthChange = component::setNoiseWidth, onHeightChange = component::setNoiseHeight ) NoiseParamsSelection( value = component.noiseParams, onValueChange = component::updateParams ) Spacer(Modifier.height(4.dp)) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, forceEnabled = true, quality = component.quality, ) QualitySelector( quality = component.quality, imageFormat = component.imageFormat, onQualityChange = component::setQuality ) } }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = false, isSecondaryButtonVisible = false, onSecondaryButtonClick = {}, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { shareButton() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) }, canShowScreenData = true ) LoadingDialog( visible = component.isSaving, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/components/NoiseParamsSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.RampLeft import androidx.compose.material.icons.outlined.SettingsEthernet import androidx.compose.material.icons.outlined.Waves import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.noise_generation.domain.model.CellularDistanceFunction import com.t8rin.imagetoolbox.noise_generation.domain.model.CellularReturnType import com.t8rin.imagetoolbox.noise_generation.domain.model.DomainWarpType import com.t8rin.imagetoolbox.noise_generation.domain.model.FractalType import com.t8rin.imagetoolbox.noise_generation.domain.model.NoiseParams import com.t8rin.imagetoolbox.noise_generation.domain.model.NoiseType import kotlin.math.roundToInt @Composable fun NoiseParamsSelection( value: NoiseParams, onValueChange: (NoiseParams) -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { EnhancedSliderItem( value = value.seed, icon = Icons.Outlined.SettingsEthernet, title = stringResource(R.string.seed), valueRange = -10000f..10000f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange(value.copy(seed = it.toInt())) }, shape = ShapeDefaults.extraLarge ) EnhancedSliderItem( value = value.frequency, icon = Icons.Outlined.Waves, title = stringResource(R.string.frequency), valueRange = -0.5f..0.5f, internalStateTransformation = { it.roundTo(3) }, onValueChange = { onValueChange(value.copy(frequency = it)) }, shape = ShapeDefaults.extraLarge ) DataSelector( value = value.noiseType, onValueChange = { onValueChange(value.copy(noiseType = it)) }, entries = NoiseType.entries, title = stringResource(R.string.noise_type), titleIcon = null, itemContentText = { it.name }, spanCount = 2, containerColor = Color.Unspecified ) DataSelector( value = value.fractalType, onValueChange = { onValueChange(value.copy(fractalType = it)) }, entries = FractalType.entries, title = stringResource(R.string.fractal_type), titleIcon = null, itemContentText = { it.name }, spanCount = 2, containerColor = Color.Unspecified ) AnimatedVisibility(value.fractalType != FractalType.None) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { EnhancedSliderItem( value = value.fractalOctaves, title = stringResource(R.string.octaves), valueRange = 1f..5f, steps = 3, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange(value.copy(fractalOctaves = it.toInt())) }, shape = ShapeDefaults.extraLarge ) EnhancedSliderItem( value = value.fractalLacunarity, title = stringResource(R.string.lacunarity), valueRange = -50f..50f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(value.copy(fractalLacunarity = it)) }, shape = ShapeDefaults.extraLarge ) EnhancedSliderItem( value = value.fractalGain, title = stringResource(R.string.gain), valueRange = -10f..10f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(value.copy(fractalGain = it)) }, shape = ShapeDefaults.extraLarge ) EnhancedSliderItem( value = value.fractalWeightedStrength, title = stringResource(R.string.weighted_strength), valueRange = -3f..3f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(value.copy(fractalWeightedStrength = it)) }, shape = ShapeDefaults.extraLarge ) AnimatedVisibility(value.fractalType == FractalType.PingPong) { EnhancedSliderItem( value = value.fractalPingPongStrength, title = stringResource(R.string.ping_pong_strength), valueRange = 0f..20f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(value.copy(fractalPingPongStrength = it)) }, shape = ShapeDefaults.extraLarge ) } AnimatedVisibility(value.noiseType == NoiseType.Cellular) { Column { DataSelector( value = value.cellularDistanceFunction, onValueChange = { onValueChange(value.copy(cellularDistanceFunction = it)) }, entries = CellularDistanceFunction.entries, title = stringResource(R.string.distance_function), titleIcon = null, itemContentText = { it.name }, spanCount = 2, containerColor = Color.Unspecified ) Spacer(Modifier.height(8.dp)) DataSelector( value = value.cellularReturnType, onValueChange = { onValueChange(value.copy(cellularReturnType = it)) }, entries = CellularReturnType.entries, title = stringResource(R.string.return_type), titleIcon = null, itemContentText = { it.name }, spanCount = 2, containerColor = Color.Unspecified ) Spacer(Modifier.height(8.dp)) EnhancedSliderItem( value = value.cellularJitter, title = stringResource(R.string.jitter), valueRange = -10f..10f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(value.copy(cellularJitter = it)) }, shape = ShapeDefaults.extraLarge ) } } } } DataSelector( value = value.domainWarpType, onValueChange = { onValueChange(value.copy(domainWarpType = it)) }, entries = DomainWarpType.entries, title = stringResource(R.string.domain_warp), titleIcon = null, itemContentText = { it.name }, spanCount = 2, containerColor = Color.Unspecified ) EnhancedSliderItem( value = value.domainWarpAmp, icon = Icons.Outlined.RampLeft, title = stringResource(R.string.amplitude), valueRange = -2000f..2000f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(value.copy(domainWarpAmp = it)) }, shape = ShapeDefaults.extraLarge ) } } ================================================ FILE: feature/noise-generation/src/main/java/com/t8rin/imagetoolbox/noise_generation/presentation/screenLogic/NoiseGenerationComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.noise_generation.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.noise_generation.domain.NoiseGenerator import com.t8rin.imagetoolbox.noise_generation.domain.model.NoiseParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class NoiseGenerationComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, dispatchersHolder: DispatchersHolder, private val noiseGenerator: NoiseGenerator, private val fileController: FileController, private val shareProvider: ImageShareProvider, private val imageCompressor: ImageCompressor, private val imageScaler: ImageScaler ) : BaseComponent(dispatchersHolder, componentContext) { init { updatePreview() } private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _noiseParams: MutableState = mutableStateOf(NoiseParams.Default) val noiseParams: NoiseParams by _noiseParams private val _noiseSize: MutableState = mutableStateOf(IntegerSize(1000, 1000)) val noiseSize: IntegerSize by _noiseSize private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Default) val imageFormat: ImageFormat by _imageFormat private val _quality: MutableState = mutableStateOf(Quality.Base(100)) val quality: Quality by _quality private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveNoise( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } noiseGenerator.generateNoise( width = noiseSize.width, height = noiseSize.height, noiseParams = noiseParams, onFailure = { parseSaveResult(SaveResult.Error.Exception(it)) } )?.let { bitmap -> val imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, quality = quality, imageFormat = imageFormat ) parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = "", sequenceNumber = null, data = imageCompressor.compress( image = bitmap, imageFormat = imageFormat, quality = quality ) ), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.update { false } } } fun cacheCurrentNoise(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.update { true } noiseGenerator.generateNoise( width = noiseSize.width, height = noiseSize.height, noiseParams = noiseParams )?.let { image -> val imageInfo = ImageInfo( width = image.width, height = image.height, quality = quality, imageFormat = imageFormat ) shareProvider.cacheImage( image = image, imageInfo = imageInfo )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.update { false } } } fun shareNoise() { cacheCurrentNoise { uri -> componentScope.launch { shareProvider.shareUri( uri = uri.toString(), onComplete = AppToastHost::showConfetti ) } } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.update { false } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } } fun setQuality(quality: Quality) { _quality.update { quality } } fun updateParams(params: NoiseParams) { _noiseParams.update( onValueChanged = ::updatePreview, transform = { params } ) } fun setNoiseWidth(width: Int) { _noiseSize.update( onValueChanged = ::updatePreview, transform = { it.copy(width = width.coerceAtMost(8192)) } ) } fun setNoiseHeight(height: Int) { _noiseSize.update( onValueChanged = ::updatePreview, transform = { it.copy(height = height.coerceAtMost(8192)) } ) } private fun updatePreview() { componentScope.launch { _isImageLoading.update { true } _previewBitmap.update { null } debouncedImageCalculation { noiseGenerator.generateNoise( width = noiseSize.width, height = noiseSize.height, noiseParams = noiseParams )?.let { imageScaler.scaleImage( image = it, width = 512, height = 512, resizeType = ResizeType.Flexible ) }.also { bitmap -> _previewBitmap.update { bitmap } _isImageLoading.update { false } } } } } fun getFormatForFilenameSelection(): ImageFormat = imageFormat @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): NoiseGenerationComponent } } ================================================ FILE: feature/palette-tools/.gitignore ================================================ /build ================================================ FILE: feature/palette-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.palette_tools" dependencies { implementation(projects.feature.pickColor) implementation(projects.lib.palette) } ================================================ FILE: feature/palette-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/PaletteToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation import android.net.Uri import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.icons.ContractEdit import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper import com.t8rin.imagetoolbox.core.resources.icons.PaletteBox import com.t8rin.imagetoolbox.core.resources.icons.PaletteSwatch import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.SimplePicture import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.PaletteToolsScreenControls import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.PaletteType import com.t8rin.imagetoolbox.feature.palette_tools.presentation.screenLogic.PaletteToolsComponent import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageSheet @Composable fun PaletteToolsContent( component: PaletteToolsComponent ) { val paletteType = component.paletteType var showPreferencePicker by rememberSaveable(component.initialUri) { mutableStateOf(component.initialUri != null && paletteType == PaletteType.Default || paletteType == PaletteType.MaterialYou) } var showExitDialog by remember { mutableStateOf(false) } AutoContentBasedColors( model = component.bitmap, allowChangeColor = paletteType == PaletteType.Default ) val imagePicker = rememberImagePicker { uri: Uri -> showPreferencePicker = true component.setUri(uri) } AutoFilePicker( onAutoPick = imagePicker::pickImage, isPickedAlready = component.initialUri != null ) val paletteImageLauncher = rememberImagePicker { uri: Uri -> component.setPaletteType(PaletteType.Default) component.setUri(uri) } val materialYouImageLauncher = rememberImagePicker { uri: Uri -> component.setPaletteType(PaletteType.MaterialYou) component.setUri(uri) } val paletteFormatPicker = rememberFilePicker { uri: Uri -> component.setPaletteType(PaletteType.Edit) component.setUri(uri) } val pickImage = when (paletteType) { PaletteType.MaterialYou -> materialYouImageLauncher::pickImage PaletteType.Default -> paletteImageLauncher::pickImage PaletteType.Edit -> paletteFormatPicker::pickFile null -> imagePicker::pickImage } val paletteSaver = rememberFileCreator(onSuccess = component::savePaletteTo) val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomModalSheet( data = component.bitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) var showColorPickerSheet by rememberSaveable { mutableStateOf(false) } val preferences: @Composable () -> Unit = { val preference1 = @Composable { PreferenceItem( title = stringResource(R.string.generate_palette), subtitle = stringResource(R.string.palette_sub), startIcon = Icons.Outlined.PaletteSwatch, modifier = Modifier.fillMaxWidth(), onClick = { if (component.bitmap == null) { paletteImageLauncher.pickImage() } else { component.setPaletteType(PaletteType.Default) } showPreferencePicker = false } ) } val preference2 = @Composable { PreferenceItem( title = stringResource(R.string.material_you), subtitle = stringResource(R.string.material_you_sub), startIcon = Icons.Outlined.PaletteBox, modifier = Modifier.fillMaxWidth(), onClick = { if (component.bitmap == null) { materialYouImageLauncher.pickImage() } else { component.setPaletteType(PaletteType.MaterialYou) } showPreferencePicker = false } ) } val preference3 = @Composable { PreferenceItem( title = stringResource(R.string.edit_palette), subtitle = stringResource(R.string.edit_palette_sub), startIcon = Icons.Outlined.ContractEdit, modifier = Modifier.fillMaxWidth(), onClick = { component.setPaletteType(PaletteType.Edit) showPreferencePicker = false } ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() Spacer(modifier = Modifier.height(8.dp)) preference3() } } else { Column( Modifier.windowInsetsPadding(WindowInsets.displayCutout.only(WindowInsetsSides.End)) ) { Row { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } Spacer(modifier = Modifier.height(8.dp)) Row { preference3.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) Spacer(Modifier.weight(1f)) } } } } AdaptiveLayoutScreen( shouldDisableBackHandler = paletteType == null, title = { TopAppBarTitle( title = when (paletteType) { PaletteType.MaterialYou -> stringResource(R.string.material_you) PaletteType.Default -> stringResource(R.string.generate_palette) PaletteType.Edit -> { val base = stringResource(R.string.edit_palette) val end = component.palette.colors.size.takeIf { it > 0 }?.let { ": " + pluralStringResource(R.plurals.color_count, it, it) }.orEmpty() base + end } null -> stringResource(R.string.palette_tools) }, input = component.bitmap, isLoading = component.isImageLoading, size = null ) }, onGoBack = { when (paletteType) { PaletteType.Edit if component.palette.isNotEmpty() -> showExitDialog = true null -> component.onGoBack() else -> component.setUri(null) } }, actions = { ZoomButton( onClick = { showZoomSheet = true }, visible = component.bitmap != null, ) if (component.bitmap != null) { EnhancedIconButton( onClick = { showColorPickerSheet = true } ) { Icon( imageVector = Icons.Outlined.Eyedropper, contentDescription = stringResource(R.string.pipette) ) } } if (paletteType == PaletteType.Edit && component.palette.isNotEmpty()) { ShareButton( onShare = component::sharePalette ) } }, topAppBarPersistentActions = { if (component.bitmap == null) { TopAppBarEmoji() } }, imagePreview = { SimplePicture(bitmap = component.bitmap) }, showImagePreviewAsStickyHeader = paletteType == PaletteType.Default, placeImagePreview = paletteType == PaletteType.Default, controls = { PaletteToolsScreenControls( component = component ) }, buttons = { actions -> var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = if (paletteType == PaletteType.Edit) { isPortrait } else { paletteType == null || component.bitmap == null }, onSecondaryButtonClick = pickImage, isPrimaryButtonVisible = paletteType == PaletteType.Edit && component.palette.isNotEmpty(), secondaryButtonIcon = if (paletteType == PaletteType.Edit) Icons.Rounded.FileOpen else Icons.Rounded.AddPhotoAlt, secondaryButtonText = stringResource( if (paletteType == PaletteType.Edit) R.string.pick_file else R.string.pick_image_alt ), onPrimaryButtonClick = { paletteSaver.make(component.createPaletteFilename()) }, showNullDataButtonAsContainer = true, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }.takeIf { paletteType != PaletteType.Edit } ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, contentPadding = animateDpAsState( if (paletteType == null) 12.dp else 20.dp ).value, insetsForNoData = WindowInsets(0), noDataControls = { preferences() }, canShowScreenData = paletteType != null && (paletteType == PaletteType.Edit || component.bitmap != null) ) var colorPickerValue by rememberSaveable(stateSaver = ColorSaver) { mutableStateOf(Color.Black) } PickColorFromImageSheet( visible = showColorPickerSheet, onDismiss = { showColorPickerSheet = false }, bitmap = component.bitmap, onColorChange = { colorPickerValue = it }, color = colorPickerValue ) EnhancedModalBottomSheet( visible = showPreferencePicker, onDismiss = { showPreferencePicker = it }, confirmButton = { EnhancedButton( onClick = { showPreferencePicker = false } ) { Text(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(id = R.string.palette), icon = Icons.Rounded.Palette ) } ) { Column( modifier = Modifier.padding(12.dp) ) { preferences() } } ExitWithoutSavingDialog( onExit = { if (paletteType != null) { component.setUri(null) } else { component.onGoBack() } }, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isImageLoading || component.isSaving, canCancel = false ) } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/DefaultPaletteControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import android.graphics.Bitmap import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.rememberImageColorPaletteState import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FileExport import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.NamedColor @Composable internal fun DefaultPaletteControls( bitmap: Bitmap, onOpenExport: (List) -> Unit ) { var count by rememberSaveable { mutableIntStateOf(32) } val state = rememberImageColorPaletteState( imageBitmap = bitmap.asImageBitmap(), maximumColorCount = count ) PaletteColorsCountSelector( value = count, onValueChange = { count = it } ) Spacer(modifier = Modifier.height(16.dp)) PreferenceItem( title = stringResource(R.string.export), subtitle = stringResource(R.string.export_palette_sub), onClick = { onOpenExport( state.paletteData.map { NamedColor( color = it.colorData.color, name = it.colorData.name ) } ) }, endIcon = Icons.Outlined.FileExport, shape = ShapeDefaults.top, modifier = Modifier.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.mixedContainer.copy(0.5f), contentColor = MaterialTheme.colorScheme.onMixedContainer ) Spacer(modifier = Modifier.height(4.dp)) ImageColorPalette( paletteDataList = state.paletteData, modifier = Modifier .fillMaxSize() .container(ShapeDefaults.bottom) .padding(4.dp), onColorClick = { Clipboard.copy( text = it.color.toHex(), message = R.string.color_copied ) } ) } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/EditPaletteControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile import androidx.compose.material.icons.outlined.AddCircleOutline import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.luminance import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.replaceAt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.Swatch import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorPickerSheet import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.NamedColor import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.NamedPalette import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.PaletteFormatHelper import com.t8rin.palette.PaletteFormat @Composable internal fun EditPaletteControls( paletteFormat: PaletteFormat?, onPaletteFormatChange: (PaletteFormat) -> Unit, palette: NamedPalette, onPaletteChange: (NamedPalette) -> Unit ) { Spacer(modifier = Modifier.height(16.dp)) val entries = PaletteFormatHelper.entries val format = paletteFormat ?: entries.first() AnimatedContent( targetState = format, contentKey = { it.withPaletteName }, modifier = Modifier.fillMaxWidth() ) { formatAnimated -> RoundedTextField( value = palette.name, onValueChange = { onPaletteChange( palette.copy( name = it ) ) }, enabled = formatAnimated.withPaletteName, modifier = Modifier .container( shape = ShapeDefaults.top, resultPadding = 8.dp ), isError = !formatAnimated.withPaletteName, supportingText = if (!formatAnimated.withPaletteName) { { Text( stringResource( R.string.palette_name_not_supported, formatAnimated.name.uppercase().replace("_", " ") ) ) } } else null, label = { Text(stringResource(R.string.palette_name)) }, startIcon = { Icon( imageVector = Icons.Rounded.Swatch, contentDescription = null ) } ) } Spacer(modifier = Modifier.height(4.dp)) DataSelector( shape = ShapeDefaults.bottom, value = format, onValueChange = onPaletteFormatChange, entries = entries, title = stringResource(R.string.palette_format), titleIcon = Icons.AutoMirrored.Rounded.InsertDriveFile, itemContentText = { it.name.uppercase().replace("_", " ") }, badgeContent = { Text(entries.size.toString()) } ) Spacer(modifier = Modifier.height(12.dp)) Column( modifier = Modifier.container(resultPadding = 8.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { (palette.colors + null).forEachIndexed { index, data -> var showColorPicker by remember { mutableStateOf(false) } ColorPickerSheet( visible = showColorPicker, onDismiss = { showColorPicker = false }, color = data?.color, onColorSelected = { onPaletteChange( palette.copy( colors = if (data == null) { palette.colors + NamedColor( color = it, name = "" ) } else { palette.colors.replaceAt(index) { item -> item.copy( color = it.copy(1f) ) } } ) ) }, allowAlpha = false ) if (data == null) { PreferenceItem( onClick = { showColorPicker = true }, title = stringResource(R.string.add_color), subtitle = stringResource(R.string.add_color_palette_sub), startIcon = Icons.Rounded.Palette, endIcon = Icons.Outlined.AddCircleOutline, modifier = Modifier .fillMaxWidth() .padding(top = 4.dp), shape = ShapeDefaults.default, containerColor = MaterialTheme.colorScheme.surface ) } else { val baseShape = ShapeDefaults.byIndex( index = index, size = palette.colors.size ) val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = baseShape, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) Row( modifier = Modifier .container( shape = shape, color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp ) .fillMaxWidth() .padding(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { val colorInteractionSource = remember { MutableInteractionSource() } Row( modifier = Modifier .container( shape = ShapeDefaults.circle, color = data.color.inverse( fraction = { 0.8f }, darkMode = data.color.luminance() < 0.3f ) ) .hapticsClickable( interactionSource = colorInteractionSource, indication = LocalIndication.current ) { showColorPicker = true }, verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(32.dp) .container( shape = ShapeDefaults.circle, color = data.color ) ) Box( contentAlignment = Alignment.Center, modifier = Modifier.padding( horizontal = 8.dp ) ) { Text( text = "#FFFFFF", fontSize = 15.sp, modifier = Modifier.alpha(0f) ) Text( text = remember(data.color) { data.color.toHex().uppercase() }, color = data.color, fontSize = 15.sp ) } } PaletteColorNameField( value = data.name, onValueChange = { onPaletteChange( palette.copy( colors = palette.colors.replaceAt(index) { item -> item.copy( name = it ) } ) ) }, modifier = Modifier .weight(1f) .heightIn(min = 40.dp) ) EnhancedIconButton( onClick = { onPaletteChange( palette.copy( colors = palette.colors - data ) ) }, modifier = Modifier.size(28.dp, 40.dp), forceMinimumInteractiveComponentSize = false, contentColor = MaterialTheme.colorScheme.onErrorContainer, containerColor = MaterialTheme.colorScheme.errorContainer.copy(0.5f) ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null, modifier = Modifier.size(20.dp) ) } } } } } Spacer(modifier = Modifier.height(16.dp)) } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/ImageColorPalette.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.colors.PaletteData import com.t8rin.colors.model.ColorData import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.PaletteSwatch import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.SourceNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction @Composable internal fun ImageColorPalette( modifier: Modifier, paletteDataList: List, onColorClick: (ColorData) -> Unit, ) { AnimatedContent( targetState = paletteDataList.isEmpty(), modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { isEmpty -> if (isEmpty) { SourceNotPickedWidget( onClick = null, text = stringResource(R.string.no_palette), icon = Icons.Rounded.PaletteSwatch, containerColor = MaterialTheme.colorScheme.surface ) } else { Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { paletteDataList.forEachIndexed { index, paletteData: PaletteData -> val colorData = paletteData.colorData val baseShape = ShapeDefaults.byIndex( index = index, size = paletteDataList.size ) val interactionSource = remember { MutableInteractionSource() } val shape = shapeByInteraction( shape = baseShape, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) Row( modifier = Modifier .container( shape = shape, color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp ) .fillMaxWidth() .hapticsClickable( indication = LocalIndication.current, interactionSource = interactionSource ) { onColorClick(colorData) } .padding(top = 8.dp, bottom = 8.dp, start = 8.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(38.dp) .container( shape = ShapeDefaults.circle, color = colorData.color ) ) Spacer(modifier = Modifier.width(12.dp)) Text( text = colorData.name, fontSize = 17.sp, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) Spacer(modifier = Modifier.width(12.dp)) Text( text = colorData.hexText.uppercase(), fontSize = 16.sp ) } } } } } } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPalette.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material3.ColorScheme import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.dynamic.theme.getColorScheme import com.t8rin.dynamic.theme.rememberColorScheme import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Cube import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import kotlinx.coroutines.delay @Composable internal fun MaterialYouPalette( keyColor: Color, paletteStyle: PaletteStyle, isDarkTheme: Boolean, isInvertColors: Boolean, contrastLevel: Float ) { val colorScheme = rememberColorScheme( isDarkTheme = isDarkTheme, amoledMode = false, colorTuple = ColorTuple(keyColor), style = paletteStyle, contrastLevel = contrastLevel.toDouble(), dynamicColor = false, isInvertColors = isInvertColors ) val context = LocalContext.current val themeState = LocalDynamicThemeState.current LaunchedEffect(colorScheme.primary) { delay(200L) themeState.updateColorTuple( ColorTuple( primary = colorScheme.primary, secondary = colorScheme.secondary, tertiary = colorScheme.tertiary, surface = colorScheme.surface ) ) } MaterialYouPaletteGroup( colorScheme = colorScheme, onCopy = { color -> Clipboard.copy( text = color.toHex(), message = R.string.color_copied ) } ) Spacer(modifier = Modifier.height(16.dp)) EnhancedButton( onClick = { val light = context.getColorScheme( isDarkTheme = false, amoledMode = false, colorTuple = ColorTuple(keyColor), style = paletteStyle, contrastLevel = contrastLevel.toDouble(), dynamicColor = false, isInvertColors = isInvertColors ).asCodeString(false) val dark = context.getColorScheme( isDarkTheme = true, amoledMode = false, colorTuple = ColorTuple(keyColor), style = paletteStyle, contrastLevel = contrastLevel.toDouble(), dynamicColor = false, isInvertColors = isInvertColors ).asCodeString(true) Clipboard.copy(light + "\n\n" + dark) }, containerColor = MaterialTheme.colorScheme.tertiary ) { Icon( imageVector = Icons.Outlined.Cube, contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) Text(stringResource(R.string.copy_as_compose_code)) } } private fun ColorScheme.asCodeString( isDarkTheme: Boolean ): String = """ val ${isDarkTheme.schemeName} = ColorScheme( background = ${background.colorCode}, error = ${error.colorCode}, errorContainer = ${errorContainer.colorCode}, inverseOnSurface = ${inverseOnSurface.colorCode}, inversePrimary = ${inversePrimary.colorCode}, inverseSurface = ${inverseSurface.colorCode}, onBackground = ${onBackground.colorCode}, onError = ${onError.colorCode}, onErrorContainer = ${onErrorContainer.colorCode}, onPrimary = ${onPrimary.colorCode}, onPrimaryContainer = ${onPrimaryContainer.colorCode}, onSecondary = ${onSecondary.colorCode}, onSecondaryContainer = ${onSecondaryContainer.colorCode}, onSurface = ${onSurface.colorCode}, onSurfaceVariant = ${onSurfaceVariant.colorCode}, onTertiary = ${onTertiary.colorCode}, onTertiaryContainer = ${onTertiaryContainer.colorCode}, outline = ${outline.colorCode}, outlineVariant = ${outlineVariant.colorCode}, primary = ${primary.colorCode}, primaryContainer = ${primaryContainer.colorCode}, scrim = ${scrim.colorCode}, secondary = ${secondary.colorCode}, secondaryContainer = ${secondaryContainer.colorCode}, surface = ${surface.colorCode}, surfaceTint = ${surfaceTint.colorCode}, surfaceVariant = ${surfaceVariant.colorCode}, tertiary = ${tertiary.colorCode}, tertiaryContainer = ${tertiaryContainer.colorCode}, surfaceBright = ${surfaceBright.colorCode}, surfaceDim = ${surfaceDim.colorCode}, surfaceContainer = ${surfaceContainer.colorCode}, surfaceContainerHigh = ${surfaceContainerHigh.colorCode}, surfaceContainerHighest = ${surfaceContainerHighest.colorCode}, surfaceContainerLow = ${surfaceContainerLow.colorCode}, surfaceContainerLowest = ${surfaceContainerLowest.colorCode}, ) """.trim() private val Boolean.schemeName: String get() = "${if (this) "dark" else "light"}ColorScheme" private val Color.colorCode get() = "Color(0xff${this.toHex().drop(1).lowercase()})" ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import android.graphics.Bitmap import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Contrast import androidx.compose.material.icons.rounded.DarkMode import androidx.compose.material.icons.rounded.InvertColors import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.dynamic.theme.extractPrimaryColor import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.Swatch import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorInfo import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelection import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.palette_selection.getTitle import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun MaterialYouPaletteControls(bitmap: Bitmap) { val context = LocalContext.current val settingsState = LocalSettingsState.current var showColorPicker by rememberSaveable { mutableStateOf(false) } var paletteStyle by rememberSaveable { mutableStateOf(PaletteStyle.TonalSpot) } var isDarkTheme by rememberSaveable { mutableStateOf(settingsState.isNightMode) } var invertColors by rememberSaveable { mutableStateOf(false) } var contrast by rememberSaveable { mutableFloatStateOf(0f) } var keyColor by rememberSaveable(bitmap, stateSaver = ColorSaver) { mutableStateOf(bitmap.extractPrimaryColor()) } Spacer(modifier = Modifier.height(16.dp)) Row( verticalAlignment = Alignment.CenterVertically ) { ColorInfo( color = keyColor, onColorChange = { keyColor = it }, supportButtonIcon = Icons.Rounded.MiniEdit, onSupportButtonClick = { showColorPicker = true }, modifier = Modifier.weight(1f) ) Spacer(modifier = Modifier.width(16.dp)) Picture( model = bitmap, shape = RectangleShape, modifier = Modifier .size(56.dp) .container( shape = ShapeDefaults.mini, resultPadding = 0.dp ) ) } Spacer(modifier = Modifier.height(16.dp)) MaterialYouPalette( keyColor = keyColor, paletteStyle = paletteStyle, isDarkTheme = isDarkTheme, isInvertColors = invertColors, contrastLevel = contrast ) Spacer(modifier = Modifier.height(16.dp)) PreferenceRowSwitch( title = stringResource(R.string.dark_colors), subtitle = stringResource(R.string.dark_colors_sub), checked = isDarkTheme, startIcon = Icons.Rounded.DarkMode, onClick = { isDarkTheme = it }, containerColor = Color.Unspecified, shape = ShapeDefaults.top ) Spacer(modifier = Modifier.height(4.dp)) PreferenceRowSwitch( title = stringResource(R.string.invert_colors), subtitle = stringResource(R.string.invert_colors_sub), checked = invertColors, startIcon = Icons.Rounded.InvertColors, onClick = { invertColors = it }, containerColor = Color.Unspecified, shape = ShapeDefaults.center ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( containerColor = Color.Unspecified, value = contrast.roundToTwoDigits(), icon = Icons.Rounded.Contrast, title = stringResource(id = R.string.contrast), valueRange = -1f..1f, shape = ShapeDefaults.center, onValueChange = { }, internalStateTransformation = { it.roundToTwoDigits() }, steps = 198, onValueChangeFinished = { contrast = it } ) Spacer(modifier = Modifier.height(4.dp)) Column( modifier = Modifier.container( shape = ShapeDefaults.bottom ) ) { Row( verticalAlignment = Alignment.CenterVertically ) { IconShapeContainer( modifier = Modifier.padding(top = 16.dp, start = 16.dp) ) { Icon( imageVector = Icons.Rounded.Swatch, contentDescription = null ) } Text( fontWeight = FontWeight.Medium, text = stringResource(R.string.palette_style), modifier = Modifier.padding(top = 16.dp, start = 16.dp) ) } Spacer(modifier = Modifier.height(8.dp)) val listState = rememberLazyListState() LazyRow( state = listState, modifier = Modifier .fillMaxWidth() .fadingEdges(listState), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, contentPadding = PaddingValues(8.dp), flingBehavior = enhancedFlingBehavior() ) { items(PaletteStyle.entries) { EnhancedChip( selected = it == paletteStyle, onClick = { paletteStyle = it }, selectedColor = MaterialTheme.colorScheme.secondary, contentPadding = PaddingValues( horizontal = 12.dp, vertical = 8.dp ) ) { Text(it.getTitle(context)) } } } } EnhancedModalBottomSheet( sheetContent = { Box { Column( Modifier .enhancedVerticalScroll(rememberScrollState()) .padding( start = 36.dp, top = 36.dp, end = 36.dp, bottom = 24.dp ) ) { ColorSelection( value = keyColor, onValueChange = { keyColor = it } ) } } }, visible = showColorPicker, onDismiss = { showColorPicker = it }, title = { TitleItem( text = stringResource(R.string.color), icon = Icons.Rounded.Palette ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showColorPicker = false } ) { AutoSizeText(stringResource(R.string.close)) } } ) } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @Composable fun MaterialYouPaletteGroup( colorScheme: ColorScheme, onCopy: (Color) -> Unit ) { Column( modifier = Modifier.clip(MaterialTheme.shapes.large), verticalArrangement = Arrangement.spacedBy(4.dp) ) { Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.primary, colorScheme = colorScheme, name = "Primary", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onPrimary, colorScheme = colorScheme, name = "On Primary", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.primaryContainer, colorScheme = colorScheme, name = "Primary Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onPrimaryContainer, colorScheme = colorScheme, name = "On Primary Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.secondary, colorScheme = colorScheme, name = "Secondary", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onSecondary, colorScheme = colorScheme, name = "On Secondary", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.secondaryContainer, colorScheme = colorScheme, name = "Secondary Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onSecondaryContainer, colorScheme = colorScheme, name = "On Secondary Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.tertiary, colorScheme = colorScheme, name = "Tertiary", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onTertiary, colorScheme = colorScheme, name = "On Tertiary", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.tertiaryContainer, colorScheme = colorScheme, name = "Tertiary Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onTertiaryContainer, colorScheme = colorScheme, name = "On Tertiary Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.error, colorScheme = colorScheme, name = "Error", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onError, colorScheme = colorScheme, name = "On Error", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.errorContainer, colorScheme = colorScheme, name = "Error Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onErrorContainer, colorScheme = colorScheme, name = "On Error Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.inversePrimary, colorScheme = colorScheme, name = "Inverse Primary", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surfaceTint, colorScheme = colorScheme, name = "Surface Tint", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surfaceDim, colorScheme = colorScheme, name = "Surface Dim", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surfaceBright, colorScheme = colorScheme, name = "Surface Bright", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.surfaceVariant, colorScheme = colorScheme, name = "Surface Variant", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.onSurfaceVariant, colorScheme = colorScheme, name = "On Surface Variant", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.inverseSurface, colorScheme = colorScheme, name = "Inverse Surface", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.inverseOnSurface, colorScheme = colorScheme, name = "Inverse On Surface", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.onSurface, colorScheme = colorScheme, name = "On Surface", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surface, colorScheme = colorScheme, name = "Surface", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surfaceContainerLowest, colorScheme = colorScheme, name = "Surface Lowest", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surfaceContainerLow, colorScheme = colorScheme, name = "Surface Low", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.surfaceContainer, colorScheme = colorScheme, name = "Surface Container", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surfaceContainerHigh, colorScheme = colorScheme, name = "Surface High", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.surfaceContainerHighest, colorScheme = colorScheme, name = "Surface Highest", onCopy = onCopy, modifier = Modifier.weight(1f) ) } Row( modifier = Modifier.height(IntrinsicSize.Max) ) { MaterialYouPaletteItem( color = colorScheme.outline, colorScheme = colorScheme, name = "Outline", onCopy = onCopy, modifier = Modifier.weight(1f) ) MaterialYouPaletteItem( color = colorScheme.outlineVariant, colorScheme = colorScheme, name = "Outline Variant", onCopy = onCopy, modifier = Modifier.weight(1f) ) } } } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/MaterialYouPaletteItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material3.ColorScheme import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun MaterialYouPaletteItem( color: Color, colorScheme: ColorScheme, name: String, onCopy: (Color) -> Unit, modifier: Modifier = Modifier ) { val containerColor by animateColorAsState(color) val contentColor = colorScheme.contentColorFor(containerColor) LocalContentColor.ProvidesValue(contentColor) { Column( modifier = modifier .container( shape = RectangleShape, color = containerColor, resultPadding = 0.dp ) .hapticsClickable { onCopy(containerColor) } .padding(12.dp) ) { AutoSizeText( text = name, maxLines = if (name.length < 11) 1 else 2, style = LocalTextStyle.current.copy( lineHeight = 16.sp ) ) Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.height(6.dp)) SelectionContainer { AutoSizeText( text = containerColor.toHex(), modifier = Modifier .fillMaxWidth() .offset(y = 2.dp), style = LocalTextStyle.current.copy( textAlign = TextAlign.End, fontSize = 12.sp, color = LocalContentColor.current.copy(0.5f) ) ) } } } } @Stable private fun ColorScheme.contentColorFor( color: Color ): Color = when (color) { primary -> onPrimary secondary -> onSecondary tertiary -> onTertiary background -> onBackground error -> onError primaryContainer -> onPrimaryContainer secondaryContainer -> onSecondaryContainer tertiaryContainer -> onTertiaryContainer errorContainer -> onErrorContainer inverseSurface -> inverseOnSurface surface -> onSurface inversePrimary -> primary surfaceVariant -> onSurfaceVariant surfaceBright -> onSurface surfaceContainer -> onSurface surfaceContainerHigh -> onSurface surfaceContainerHighest -> onSurface surfaceContainerLow -> onSurface surfaceContainerLowest -> onSurface onPrimary -> primary onSecondary -> secondary onTertiary -> tertiary onBackground -> background onError -> error onPrimaryContainer -> primaryContainer onSecondaryContainer -> secondaryContainer onTertiaryContainer -> tertiaryContainer onErrorContainer -> errorContainer inverseOnSurface -> inverseSurface onSurface -> surface outline -> surfaceContainerLow outlineVariant -> onSurfaceVariant onSurfaceVariant -> surfaceVariant else -> Color.Unspecified } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorNameField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextFieldColors import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextFieldColors @Composable internal fun PaletteColorNameField( value: String, onValueChange: (String) -> Unit, shape: Shape = ShapeDefaults.large, colors: TextFieldColors = RoundedTextFieldColors( isError = false, unfocusedIndicatorColor = MaterialTheme.colorScheme.surfaceVariant.inverse({ 0.2f }) .copy(0.5f) ), modifier: Modifier = Modifier ) { var isFocused by remember { mutableStateOf(false) } BasicTextField( value = value, onValueChange = onValueChange, textStyle = LocalTextStyle.current.copy( fontSize = 17.sp, fontWeight = FontWeight.Bold, color = colors.textColor( enabled = true, isError = false, focused = true ) ), modifier = modifier .background( color = colors.containerColor( enabled = true, isError = false, focused = isFocused ), shape = shape ) .border( width = animateDpAsState( if (isFocused) 2.dp else 1.dp ).value, color = if (isFocused) { colors.focusedIndicatorColor } else { colors.unfocusedIndicatorColor }, shape = shape ) .onFocusChanged { isFocused = it.isFocused }, maxLines = 3, cursorBrush = SolidColor(colors.focusedIndicatorColor) ) { inner -> Box( modifier = Modifier.padding( horizontal = 16.dp, vertical = 8.dp ) ) { inner() if (value.isEmpty()) { Text( text = stringResource(id = R.string.color_name), color = MaterialTheme.colorScheme.outline ) } } } } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteColorsCountSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Palette import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlin.math.roundToInt @Composable fun PaletteColorsCountSelector( modifier: Modifier = Modifier, value: Int, onValueChange: (Int) -> Unit ) { EnhancedSliderItem( modifier = modifier, value = value, icon = Icons.Rounded.Palette, title = stringResource(R.string.max_colors_count), onValueChange = {}, internalStateTransformation = { it.roundToInt() }, onValueChangeFinished = { onValueChange(it.roundToInt()) }, valueRange = 1f..128f, steps = 127, shape = ShapeDefaults.extraLarge ) } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteToolsScreenControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.NamedPalette import com.t8rin.imagetoolbox.feature.palette_tools.presentation.screenLogic.PaletteToolsComponent @Composable internal fun PaletteToolsScreenControls( component: PaletteToolsComponent ) { val paletteType = component.paletteType ?: return val bitmap = component.bitmap AnimatedContent( targetState = paletteType ) { type -> Column( horizontalAlignment = Alignment.CenterHorizontally ) { when (type) { PaletteType.Default -> { DefaultPaletteControls( bitmap = bitmap ?: return@AnimatedContent, onOpenExport = { colors -> component.setPaletteType(PaletteType.Edit) component.updatePalette( NamedPalette( name = "", colors = colors ) ) } ) } PaletteType.MaterialYou -> { MaterialYouPaletteControls( bitmap = bitmap ?: return@AnimatedContent ) } PaletteType.Edit -> { EditPaletteControls( paletteFormat = component.paletteFormat, onPaletteFormatChange = component::updatePaletteFormat, palette = component.palette, onPaletteChange = component::updatePalette ) } } } } } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/PaletteType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components enum class PaletteType { Default, MaterialYou, Edit } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/NamedColor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model import androidx.compose.ui.graphics.Color import com.t8rin.palette.ColorGroup import com.t8rin.palette.Palette import com.t8rin.palette.PaletteColor data class NamedColor( val color: Color, val name: String ) data class NamedColorGroup( val name: String, val colors: List ) data class NamedPalette( val name: String = "", val colors: List = emptyList(), val groups: List = emptyList() ) { fun isNotEmpty() = name.isNotBlank() || colors.isNotEmpty() || groups.isNotEmpty() } fun NamedPalette.toPalette(): Palette { return Palette( name = name, colors = colors.map { PaletteColor( color = it.color, name = it.name ) }, groups = groups.map { group -> ColorGroup( name = group.name, colors = group.colors.map { PaletteColor( color = it.color, name = it.name ) } ) }.distinct(), ) } fun Palette.toNamed(): NamedPalette? { if (name.isEmpty() && colors.isEmpty() && groups.isEmpty()) return null return NamedPalette( name = name, colors = colors.map { it.toNamed() }.filter { it.color.alpha > 0f }.distinct(), groups = groups.map { group -> NamedColorGroup( name = group.name, colors = group.colors.map { it.toNamed() }.filter { it.color.alpha > 0f } ) }.distinct() ) } fun PaletteColor.toNamed(): NamedColor = NamedColor( color = toComposeColor(), name = name ) ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/components/model/PaletteFormatHelper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model import com.t8rin.palette.PaletteFormat object PaletteFormatHelper { val entries: List = PaletteFormat.entries.toSet().minus( setOf( PaletteFormat.CSV, PaletteFormat.HEX_RGBA ) ).plus( setOf( PaletteFormat.HEX_RGBA, PaletteFormat.CSV ) ).toList() fun entriesFor(filename: String): Set = buildSet { PaletteFormat.fromFilename(filename)?.let { add(it) addAll(entries - it) } ?: addAll(entries) } } ================================================ FILE: feature/palette-tools/src/main/java/com/t8rin/imagetoolbox/feature/palette_tools/presentation/screenLogic/PaletteToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.palette_tools.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.PaletteType import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.NamedPalette import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.PaletteFormatHelper import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.toNamed import com.t8rin.imagetoolbox.feature.palette_tools.presentation.components.model.toPalette import com.t8rin.palette.PaletteFormat import com.t8rin.palette.decode import com.t8rin.palette.encode import com.t8rin.palette.getCoder import com.t8rin.palette.use import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.withContext class PaletteToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, private val imageScaler: ImageScaler, private val imageGetter: ImageGetter, private val fileController: FileController, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } } private val _paletteType: MutableState = mutableStateOf(null) val paletteType by _paletteType private val _paletteFormat: MutableState = mutableStateOf(null) val paletteFormat by _paletteFormat private val _palette: MutableState = mutableStateOf(NamedPalette()) val palette by _palette private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _uri = mutableStateOf(null) private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private var savingJob by smartJob() fun setUri(uri: Uri?) { _uri.value = uri if (uri == null) { _paletteType.update { null } _paletteFormat.update { null } _palette.update { NamedPalette() } _bitmap.value = null return } componentScope.launch { _isImageLoading.value = true _bitmap.value = imageScaler.scaleUntilCanShow( imageGetter.getImage( data = uri.toString(), originalSize = false ) ) if (bitmap == null || paletteType == PaletteType.Edit) { _bitmap.update { null } withContext(defaultDispatcher) { val data = fileController.readBytes(uri.toString()) val entries = PaletteFormatHelper.entriesFor(uri.filename() ?: uri.toString()) for (format in entries) { format.getCoder().use { decode(data) }.onSuccess { palette -> palette.toNamed()?.let { named -> _palette.update { named } updatePaletteFormat(format) break } } } } } _isImageLoading.value = false } } fun savePaletteTo(uri: Uri) { val format = paletteFormat ?: PaletteFormatHelper.entries.first() savingJob = trackProgress { _isSaving.value = true fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes( format.getCoder().encode(palette.toPalette()) ) } ).also(::parseFileSaveResult).onSuccess(::registerSave) _isSaving.value = false } } fun sharePalette() { val format = paletteFormat ?: PaletteFormatHelper.entries.first() savingJob = trackProgress { _isSaving.value = true val data = withContext(defaultDispatcher) { format.getCoder().use { encode(palette.toPalette()) }.getOrNull() } if (data == null) { _isSaving.update { false } return@trackProgress } shareProvider.shareByteArray( byteArray = data, filename = createPaletteFilename(), onComplete = { _isSaving.value = false AppToastHost.showConfetti() } ) } } fun updatePalette(palette: NamedPalette) { _palette.update { palette } } fun updatePaletteFormat(format: PaletteFormat) { _paletteFormat.update { format } } fun setPaletteType(type: PaletteType) { _paletteType.update { type } if (type != PaletteType.Edit) { _palette.update { NamedPalette() } _paletteFormat.update { null } } } fun createPaletteFilename(): String { val name = palette.name.ifBlank { "Palette_Export" } val format = paletteFormat ?: PaletteFormatHelper.entries.first() val extension = format.fileExtension.maxBy { it.length } return "${name}_${timestamp()}.$extension" } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, ): PaletteToolsComponent } } ================================================ FILE: feature/pdf-tools/.gitignore ================================================ /build ================================================ FILE: feature/pdf-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.pdf_tools" dependencies { implementation(libs.androidx.pdfviewer.fragment) implementation(libs.androidx.fragment.compose) implementation(libs.trickle) implementation(libs.aire) implementation(libs.pdfbox) } ================================================ FILE: feature/pdf-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfHelper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.data import android.content.Context import android.graphics.Bitmap import androidx.core.net.toFile import androidx.core.net.toUri import androidx.core.text.HtmlCompat import com.awxkee.aire.Aire import com.awxkee.aire.ResizeFunction import com.awxkee.aire.ScaleColorSpace import com.t8rin.imagetoolbox.core.data.utils.aspectRatio import com.t8rin.imagetoolbox.core.data.utils.observeHasChanges import com.t8rin.imagetoolbox.core.domain.PDF import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.HocrData import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.HocrPageBox import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.HocrWord import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.PdfRenderer import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.asXObject import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.createPage import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.createPdf import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.pageIndices import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.safeOpenPdf import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.save import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.setMetadata import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfHelper import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PageSize import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCheckResult import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCreationParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfMetadata import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PrintPdfParams import com.t8rin.logger.makeLog import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDPage import com.tom_roush.pdfbox.pdmodel.PDPageContentStream import com.tom_roush.pdfbox.pdmodel.common.PDRectangle import com.tom_roush.pdfbox.pdmodel.encryption.InvalidPasswordException import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import javax.inject.Inject import kotlin.math.max import kotlin.math.roundToInt import kotlin.random.Random internal class AndroidPdfHelper @Inject constructor( @ApplicationContext private val context: Context, private val appScope: AppScope, private val shareProvider: ShareProvider, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, dispatchersHolder: DispatchersHolder ) : PdfHelper, DispatchersHolder by dispatchersHolder { companion object { private const val SIGNATURES_LIMIT = 20 val HOCR_PAGE_REGEX = Regex( pattern = "<(?:div|span)[^>]*class=[\"'][^\"']*ocr_page[^\"']*[\"'][^>]*title=[\"'][^\"']*bbox\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)[^\"']*[\"'][^>]*>", options = setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL) ) val HOCR_WORD_REGEX = Regex( pattern = "]*class=[\"'][^\"']*ocrx_word[^\"']*[\"'][^>]*title=[\"'][^\"']*bbox\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)[^\"']*[\"'][^>]*>(.*?)", options = setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL) ) val HOCR_TAG_REGEX = Regex("<[^>]+>") val PDF_CONTROL_REGEX = Regex("[\\p{Cntrl}&&[^\\n\\r\\t]]") } private val pagesCache = hashMapOf>() private val signaturesDir: File get() = File(context.filesDir, "signatures").apply(File::mkdirs) private val updateFlow: MutableSharedFlow = MutableSharedFlow() private var _masterPassword: String? = null val masterPassword: String? get() = _masterPassword override val savedSignatures: StateFlow> = merge( updateFlow, signaturesDir.observeHasChanges().debounce(100) ).map { signaturesDir .listFiles() .orEmpty() .sortedByDescending { it.lastModified() } .map { it.toUri().toString() } }.stateIn( scope = appScope, started = SharingStarted.Eagerly, initialValue = emptyList() ) override suspend fun saveSignature(signature: Any): Boolean { return runSuspendCatching { val currentSignatures = savedSignatures.value if (currentSignatures.size + 1 > SIGNATURES_LIMIT) { currentSignatures.last().toUri().toFile().delete() } File(signaturesDir, "signature_${System.currentTimeMillis()}.png") .outputStream() .use { out -> imageGetter.getImage(signature)?.compress( Bitmap.CompressFormat.PNG, 100, out ) } updateFlow.emit(Unit) }.onFailure { it.makeLog("saveSignature") }.isSuccess } override fun setMasterPassword(password: String?) { _masterPassword = password } override fun createTempName(key: String, uri: String?): String = tempName( key = key, uri = uri ).removePrefix(PDF) override fun clearPdfCache(uri: String?) { appScope.launch { runCatching { if (uri.isNullOrBlank()) { File(context.cacheDir, "pdf").deleteRecursively() } else { context.contentResolver.delete(uri.toUri(), null, null) } }.onFailure { "failed to delete $uri".makeLog("delete") } } } override suspend fun checkPdf(uri: String): PdfCheckResult = withContext(defaultDispatcher) { try { usePdf(uri) { document -> if (masterPassword.isNullOrBlank()) { PdfCheckResult.Open } else { PdfCheckResult.Protected.Unlocked( document.save( filename = uri.toUri().filename()?.let { "$PDF$it" } ?: tempName( key = "unlocked", uri = uri ) ) ) } } } catch (_: InvalidPasswordException) { PdfCheckResult.Protected.NeedsPassword } catch (t: Throwable) { ensureActive() PdfCheckResult.Failure(t) }.makeLog("checkPdf") } override suspend fun getPdfPages( uri: String ): List = withContext(decodingDispatcher) { try { usePdf( uri = uri, password = masterPassword, action = PDDocument::pageIndices ) } catch (_: Throwable) { emptyList() } } override suspend fun getPdfPageSizes( uri: String ): List = withContext(decodingDispatcher) { pagesCache[uri]?.takeIf { it.isNotEmpty() }?.let { return@withContext it } try { PdfRenderer( uri = uri, password = masterPassword )?.use { renderer -> List(renderer.pageCount) { renderer.openPage(it).run { IntegerSize(width, height) } } }.orEmpty() } catch (_: Throwable) { emptyList() }.also { pagesCache[uri] = it } } internal fun tempName( key: String, uri: String? = null ): String { val keyFixed = if (key.isBlank()) "_" else "_${key}_" return PDF + (uri?.toUri()?.filename()?.substringBeforeLast('.')?.let { "${it}${keyFixed.removeSuffix("_")}.pdf" } ?: "PDF$keyFixed${timestamp()}_${ Random(Random.nextInt()).hashCode().toString().take(4) }.pdf") } internal inline fun usePdf( uri: String, password: String? = masterPassword, action: (PDDocument) -> T ): T = openPdf( uri = uri, password = password ).use(action) internal fun openPdf( uri: String, password: String? = masterPassword ): PDDocument = safeOpenPdf( uri = uri, password = password ) internal inline fun useRenderer( uri: String, password: String? = masterPassword, action: (PdfRenderer) -> T ) = PdfRenderer( uri = uri, password = password, onFailure = { throw it } )?.use(action) internal suspend fun PDDocument.save( filename: String, password: String? = null, metadata: PdfMetadata? = PdfMetadata.Empty ): String { if (metadata != PdfMetadata.Empty) { setMetadata(metadata) } return shareProvider.cacheDataOrThrow( filename = filename, writeData = { save( writeable = it, password = password ) } ) } internal fun PrintPdfParams.calculatePageSize( firstPageOnSheet: PDPage ) = pageSizeFinal ?: copy( pageSize = firstPageOnSheet.cropBox.run { PageSize( width = width.roundToInt(), height = height.roundToInt(), name = "Auto" ) } ).pageSizeFinal internal fun parseHocrData(hocr: String): HocrData { val pageBox = HOCR_PAGE_REGEX.find(hocr)?.let { match -> val left = match.groupValues.getOrNull(1)?.toFloatOrNull() ?: return@let null val top = match.groupValues.getOrNull(2)?.toFloatOrNull() ?: return@let null val right = match.groupValues.getOrNull(3)?.toFloatOrNull() ?: return@let null val bottom = match.groupValues.getOrNull(4)?.toFloatOrNull() ?: return@let null HocrPageBox( width = (right - left).coerceAtLeast(1f), height = (bottom - top).coerceAtLeast(1f) ) } val words = HOCR_WORD_REGEX .findAll(hocr) .mapNotNull { match -> val left = match.groupValues.getOrNull(1)?.toFloatOrNull() ?: return@mapNotNull null val top = match.groupValues.getOrNull(2)?.toFloatOrNull() ?: return@mapNotNull null val right = match.groupValues.getOrNull(3)?.toFloatOrNull() ?: return@mapNotNull null val bottom = match.groupValues.getOrNull(4)?.toFloatOrNull() ?: return@mapNotNull null val rawText = match.groupValues.getOrNull(5).orEmpty() val text = HtmlCompat .fromHtml(rawText.replace(HOCR_TAG_REGEX, ""), HtmlCompat.FROM_HTML_MODE_LEGACY) .toString() .trim() .takeIf { it.isNotBlank() } ?: return@mapNotNull null HocrWord( left = left, top = top, right = right, bottom = bottom, text = text ) } .toList() return HocrData( pageBox = pageBox, words = words ) } internal fun String.cleanPdfText(): String = replace(PDF_CONTROL_REGEX, "").take(2000) internal suspend fun prepareImagesForPdf( imageUris: List, params: PdfCreationParams ): List { val scale = params.preset.value / 100f return imageUris.mapNotNull { uri -> imageGetter.getImage(data = uri)?.let { imageScaler.scaleImage( image = it, width = (it.width * scale).roundToInt(), height = (it.height * scale).roundToInt(), resizeType = ResizeType.Flexible ) } } } internal suspend fun createPdfFromPreparedImages( images: List, quality: Float, scaleSmallImagesToLarge: Boolean, addTextLayer: (PDPageContentStream.(pageIndex: Int, pageWidth: Float, pageHeight: Float, document: PDDocument) -> Unit)? ): String { if (images.isEmpty()) error("No PDF created") return createPdf { newDoc -> var h = 0 var maxWidth = 0 images.forEach { maxWidth = max(maxWidth, it.width) } for (image in images) { h += if (scaleSmallImagesToLarge && image.width != maxWidth) { (maxWidth / image.aspectRatio).toInt().coerceAtLeast(1) } else { image.height.coerceAtLeast(1) } } val size = IntegerSize(maxWidth, h) images.forEachIndexed { index, image -> val bitmap = if (scaleSmallImagesToLarge && image.width != size.width) { Aire.scale( bitmap = image, dstWidth = size.width, dstHeight = (size.width / image.aspectRatio).toInt(), scaleMode = ResizeFunction.Bicubic, colorSpace = ScaleColorSpace.SRGB ) } else image val pageHeight = bitmap.height.toFloat() val pageWidth = bitmap.width.toFloat() newDoc.createPage( PDPage( PDRectangle( pageWidth, pageHeight ) ) ) { drawImage( bitmap.asXObject(newDoc, quality), 0f, 0f, pageWidth, pageHeight ) addTextLayer?.invoke(this, index, pageWidth, pageHeight, newDoc) } } shareProvider.cacheData( writeData = newDoc::save, filename = createTempName(key = "") ) ?: error("No PDF created") } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/AndroidPdfManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.data import android.content.Context import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri import com.awxkee.aire.Aire import com.t8rin.imagetoolbox.core.data.saving.io.ByteArrayReadable import com.t8rin.imagetoolbox.core.data.saving.io.StreamWriteable import com.t8rin.imagetoolbox.core.data.saving.io.UriReadable import com.t8rin.imagetoolbox.core.data.saving.io.shielded import com.t8rin.imagetoolbox.core.data.utils.computeFromReadable import com.t8rin.imagetoolbox.core.data.utils.outputStream import com.t8rin.imagetoolbox.core.domain.PDF import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.createZip import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.core.utils.putEntry import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.HocrWord import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.PdfRenderer import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.asXObject import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.createPage import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.createPdf import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.crop import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.defaultFont import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.getAllImages import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.getPageSafe import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.metadata import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.orAll import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.pageIndices import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.save import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.setAlpha import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.setColor import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.transformImages import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.writePage import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfHelper import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.ExtractPagesAction import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfAnnotationType import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCreationParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCropParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfExtractPagesParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfMetadata import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfPageNumbersParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfRemoveAnnotationParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfSignatureParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfWatermarkParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PrintPdfParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.SearchablePdfPage import com.t8rin.logger.makeLog import com.t8rin.trickle.Trickle import com.tom_roush.pdfbox.io.MemoryUsageSetting import com.tom_roush.pdfbox.multipdf.PDFMergerUtility import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDPage import com.tom_roush.pdfbox.pdmodel.common.PDRectangle import com.tom_roush.pdfbox.pdmodel.encryption.InvalidPasswordException import com.tom_roush.pdfbox.pdmodel.graphics.state.RenderingMode import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationPopup import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationRubberStamp import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationSquareCircle import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationText import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationUnknown import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget import com.tom_roush.pdfbox.text.PDFTextStripper import com.tom_roush.pdfbox.util.Matrix import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import javax.inject.Inject internal class AndroidPdfManager @Inject constructor( @ApplicationContext private val context: Context, private val shareProvider: ImageShareProvider, private val imageGetter: ImageGetter, private val helper: AndroidPdfHelper, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, PdfManager, PdfHelper by helper { override fun extractPages( uri: String, params: PdfExtractPagesParams ): Flow = channelFlow { val scale = params.preset.value / 100f val dpi = 72f * scale catchPdf { useRenderer(uri) { renderer -> params.pages.orAll(renderer.pDocument).also { send(ExtractPagesAction.PagesCount(it.size)) }.forEach { pageIndex -> send( ExtractPagesAction.Progress( index = pageIndex, image = renderer.safeRenderDpi( pageIndex = pageIndex, dpi = dpi ).whiteBg() ) ) } } } close() }.flowOn(defaultDispatcher) override suspend fun createPdf( imageUris: List, params: PdfCreationParams ): String = catchPdf { createPdfFromPreparedImages( images = prepareImagesForPdf( imageUris = imageUris, params = params ), quality = params.quality / 100f, scaleSmallImagesToLarge = params.scaleSmallImagesToLarge, addTextLayer = null ) } override suspend fun createSearchablePdf( pages: List, params: PdfCreationParams ): String = catchPdf { createPdfFromPreparedImages( images = prepareImagesForPdf( imageUris = pages.map(SearchablePdfPage::imageUri), params = params ), quality = params.quality / 100f, scaleSmallImagesToLarge = params.scaleSmallImagesToLarge, addTextLayer = { pageIndex, pageWidth, pageHeight, document -> val page = pages.getOrNull(pageIndex) ?: return@createPdfFromPreparedImages val hocrData = page.hocr.let(::parseHocrData) val sourcePageWidth = hocrData.pageBox?.width?.takeIf { it > 0f } ?: pageWidth val sourcePageHeight = hocrData.pageBox?.height?.takeIf { it > 0f } ?: pageHeight val scaleX = (pageWidth / sourcePageWidth).coerceAtLeast(0.0001f) val scaleY = (pageHeight / sourcePageHeight).coerceAtLeast(0.0001f) val words = hocrData.words .ifEmpty { page.text .lineSequence() .map(String::trim) .filter(String::isNotBlank) .take(300) .mapIndexed { index, line -> HocrWord( left = 8f, top = (index * 14f), right = 8f + 1000f, bottom = (index * 14f) + 12f, text = line ) } .toList() } words.forEach { word -> val text = word.text.cleanPdfText() if (text.isBlank()) return@forEach val left = word.left * scaleX val right = word.right * scaleX val top = word.top * scaleY val bottom = word.bottom * scaleY val boxHeight = (bottom - top).coerceAtLeast(1f) val targetWidth = (right - left).coerceAtLeast(1f) val x = left.coerceIn(0f, pageWidth - 1f) val glyphWidthEm = (document.defaultFont.getStringWidth(text) / 1000f) .coerceAtLeast(0.001f) val fontByHeight = (boxHeight * 0.84f).coerceAtLeast(1f) val fontByWidth = (targetWidth / glyphWidthEm).coerceAtLeast(1f) val fontSize = (fontByHeight * 0.72f + fontByWidth * 0.28f) .coerceIn(1f, pageHeight.coerceAtLeast(1f)) val sourceWidth = (glyphWidthEm * fontSize).coerceAtLeast(0.1f) val horizontalScale = (targetWidth / sourceWidth * 100f).coerceIn(80f, 125f) val y = (pageHeight - bottom + ((boxHeight - fontSize).coerceAtLeast(0f) * 0.5f) + (fontSize * 0.10f) ).coerceIn(0f, pageHeight - 1f) beginText() setRenderingMode(RenderingMode.NEITHER) setFont(document.defaultFont, fontSize) setHorizontalScaling(horizontalScale) newLineAtOffset(x, y) showText(text) endText() } } ) } override suspend fun mergePdfs(uris: List): String = catchPdf { PDFMergerUtility().run { uris.forEach { uri -> addSource(UriReadable(uri.toUri(), context).stream) } shareProvider.cacheDataOrThrow(filename = tempName("merged")) { output -> destinationStream = output.outputStream() mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()) } } } override suspend fun splitPdf( uri: String, pages: List? ): String = catchPdf { usePdf(uri) { document -> createPdf { newDoc -> pages.orAll(document).forEach { index -> newDoc.addPage(document.getPageSafe(index)) } newDoc.save( filename = tempName( key = "split", uri = uri ) ) } } } override suspend fun removePdfPages( uri: String, pages: List ): String = catchPdf { usePdf(uri) { document -> createPdf { newDoc -> document.pageIndices.forEach { index -> if (index !in pages) newDoc.addPage(document.getPage(index)) } if (newDoc.numberOfPages <= 0) { error(getString(R.string.cant_remove_all)) } newDoc.save( filename = tempName( key = "removed", uri = uri ) ) } } } override suspend fun rotatePdf( uri: String, rotations: List ): String = catchPdf { usePdf(uri) { document -> document.pages.forEachIndexed { idx, page -> val angle = rotations.getOrNull(idx) ?: 0 page.rotation = (page.rotation + angle) % 360 } document.save( filename = tempName( key = "rotated", uri = uri ) ) } } override suspend fun rearrangePdf( uri: String, newOrder: List ): String = catchPdf { usePdf(uri) { document -> createPdf { newDoc -> newOrder.forEach { pageIndex -> newDoc.addPage(document.getPageSafe(pageIndex)) } newDoc.save( filename = tempName( key = "rearranged", uri = uri ) ) } } } override suspend fun addPageNumbers( uri: String, params: PdfPageNumbersParams ): String = catchPdf { usePdf(uri) { document -> val font = document.defaultFont val totalPages = document.numberOfPages val label = params.labelFormat .replace("{total}", totalPages.toString()) document.pages.forEachIndexed { idx, page -> val text = label.replace("{n}", (idx + 1).toString()) val cropBox = page.cropBox val pageWidth = cropBox.width val pageHeight = cropBox.height val originX = cropBox.lowerLeftX val originY = cropBox.lowerLeftY val textWidth = font.getStringWidth(text) / 1000f * 12f val baseX = when (params.position) { Position.TopLeft, Position.CenterLeft, Position.BottomLeft -> 10f Position.TopCenter, Position.Center, Position.BottomCenter -> pageWidth / 2f Position.TopRight, Position.CenterRight, Position.BottomRight -> pageWidth - 10f } val baseY = when (params.position) { Position.TopLeft, Position.TopCenter, Position.TopRight -> pageHeight - 20f Position.CenterLeft, Position.Center, Position.CenterRight -> pageHeight / 2f Position.BottomLeft, Position.BottomCenter, Position.BottomRight -> 20f } val adjustedX = when (params.position) { Position.TopCenter, Position.Center, Position.BottomCenter -> baseX - textWidth / 2f Position.TopRight, Position.CenterRight, Position.BottomRight -> baseX - textWidth else -> baseX } val adjustedY = when (params.position) { Position.CenterLeft, Position.Center, Position.CenterRight -> baseY - 6f else -> baseY } val adjustedXWithOrigin = adjustedX + originX val adjustedYWithOrigin = adjustedY + originY document.writePage(page) { beginText() setFont(font, 12f) setColor(params.color) newLineAtOffset(adjustedXWithOrigin, adjustedYWithOrigin) showText(text) endText() } } document.save( filename = tempName( key = "numbered", uri = uri ) ) } } override suspend fun addWatermark( uri: String, params: PdfWatermarkParams ): String = catchPdf { val color = Color(params.color) usePdf(uri) { document -> val font = document.defaultFont params.pages.orAll(document).forEach { pageIndex -> val page = document.getPageSafe(pageIndex) val textWidth = font.getStringWidth(params.text) / 1000f * params.fontSize val radians = Math.toRadians(-params.rotation.toDouble()) val cropBox = page.cropBox val originX = cropBox.lowerLeftX val originY = cropBox.lowerLeftY val centerX = originX + cropBox.width / 2f val centerY = originY + cropBox.height / 2f val matrix = Matrix.getRotateInstance( radians, centerX, centerY ) document.writePage(page) { beginText() setFont(font, params.fontSize) setColor(color.copy(params.opacity)) setTextMatrix(matrix) newLineAtOffset(-textWidth / 2f, 0f) showText(params.text) endText() } } document.save( filename = tempName( key = "watermarked", uri = uri ) ) } } override suspend fun addSignature( uri: String, params: PdfSignatureParams ): String = catchPdf { usePdf(uri) { document -> val signatureImage = imageGetter.getImage(data = params.signatureImage)!!.asXObject( document = document, quality = 1f ) val imageAspect = signatureImage.width.toFloat() / signatureImage.height.toFloat() params.pages.orAll(document).forEach { pageIndex -> val page = document.getPageSafe(pageIndex) val crop = page.cropBox val pageWidth = crop.width val pageHeight = crop.height val originX = crop.lowerLeftX val originY = crop.lowerLeftY val targetWidth = pageWidth * params.size val targetHeight = targetWidth / imageAspect val centerX = pageWidth * params.x val centerY = pageHeight * (1f - params.y) var x = centerX - targetWidth / 2f var y = centerY - targetHeight / 2f x = x.coerceIn(0f, pageWidth - targetWidth) y = y.coerceIn(0f, pageHeight - targetHeight) x += originX y += originY document.writePage(page) { setAlpha(params.opacity) saveGraphicsState() transform( Matrix( targetWidth, 0f, 0f, -targetHeight, x, y + targetHeight ) ) drawImage(signatureImage, 0f, 0f, 1f, 1f) restoreGraphicsState() } } document.save( filename = tempName( key = "signed", uri = uri ) ) } } override suspend fun protectPdf( uri: String, password: String ): String = catchPdf { usePdf(uri) { document -> document.save( filename = tempName( key = "protected", uri = uri ), password = password ) } } override suspend fun unlockPdf( uri: String, password: String ): String = catchPdf { usePdf( uri = uri, password = password, action = { document -> document.save( filename = tempName( key = "unlocked", uri = uri ) ) } ) } override suspend fun extractPagesFromPdf(uri: String): List = catchPdf { useRenderer(uri) { renderer -> renderer.pageIndices.mapNotNull { pageIndex -> val bitmap = renderer.safeRenderDpi( pageIndex = pageIndex, dpi = 72f ).whiteBg() shareProvider.cacheImage( image = bitmap, imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = ImageFormat.Png.Lossless ) ) } } ?: emptyList() } override suspend fun compressPdf( uri: String, quality: Float ): String = catchPdf { usePdf(uri) { document -> document.transformImages( quality = quality, transform = { it } ) document.save( filename = tempName( key = "compressed", uri = uri ) ) } } override suspend fun convertToGrayscale(uri: String): String = catchPdf { usePdf(uri) { document -> document.transformImages( quality = 0.8f, transform = { Aire.saturation( bitmap = it, saturation = 0f, tonemap = false ) } ) document.save( filename = tempName( key = "grayscale", uri = uri ) ) } } override suspend fun repairPdf(uri: String): String = catchPdf { usePdf(uri) { document -> document.save( filename = tempName( key = "repaired", uri = uri ) ) } } override suspend fun changePdfMetadata( uri: String, metadata: PdfMetadata? ): String = catchPdf { usePdf(uri) { document -> document.save( metadata = metadata, filename = tempName( key = "metadata", uri = uri ) ) } } override suspend fun getPdfMetadata(uri: String): PdfMetadata = catchPdf { usePdf( uri = uri, action = PDDocument::metadata ) } override suspend fun stripText(uri: String): List = catchPdf { usePdf(uri) { document -> PDFTextStripper().run { document.pageIndices.map { pageIndex -> startPage = pageIndex + 1 endPage = pageIndex + 1 getText(document).trim() } } } } override suspend fun cropPdf( uri: String, params: PdfCropParams ): String = catchPdf { usePdf(uri) { document -> params.pages.orAll(document).forEach { pageIndex -> document.getPageSafe(pageIndex).let { page -> page.cropBox = page.cropBox.crop( rotation = page.rotation, rect = params.rect ) } } document.save( filename = tempName( key = "cropped", uri = uri ) ) } } override suspend fun flattenPdf( uri: String, quality: Float ): String = catchPdf { val dpi = 72f + (228f * quality) usePdf(uri) { document -> val renderer = PdfRenderer(document) createPdf { newDoc -> document.pages.forEachIndexed { index, page -> val cropBox = page.cropBox val pdImage = renderer .safeRenderDpi(index, dpi) .whiteBg() .asXObject(newDoc, quality) newDoc.createPage(PDPage(cropBox)) { drawImage( pdImage, 0f, 0f, cropBox.width, cropBox.height ) } } newDoc.save( filename = createTempName( key = "flattened", uri = uri ) ) } } } override suspend fun detectPdfAutoRotations( uri: String ): List = catchPdf { usePdf(uri) { document -> val rotations = document.pages.map { page -> ((page.rotation % 360) + 360) % 360 } val majority = rotations .groupingBy { it } .eachCount() .maxByOrNull { it.value } ?.key ?: 0 rotations.map { rotation -> ((majority - rotation) + 360) % 360 } } } override suspend fun extractImagesFromPdf( uri: String ): String? = catchPdf { var hasImages = false val prefix = uri.toUri().filename()?.substringBeforeLast('.') ?: timestamp() val filename = "$PDF${prefix}_extracted.zip" val zipPath = usePdf(uri) { document -> shareProvider.cacheDataOrThrow( filename = filename ) { output -> val seen = mutableSetOf() var index = 0 output.outputStream().createZip { zip -> for (xObject in document.getAllImages()) { if (!seen.add(xObject.cosObject)) continue val suffix = xObject.suffix?.lowercase() ?: "png" val stream = if (suffix == "jpg" || suffix == "jp2" || suffix == "tiff") { xObject.stream.createInputStream() } else { val data = ByteArrayOutputStream().apply { use { xObject.image.compress( Bitmap.CompressFormat.PNG, 100, it ) } }.toByteArray() if (!seen.add(HashingType.MD5.computeFromReadable(ByteArrayReadable(data)))) continue data.inputStream() } zip.putEntry( name = "extracted_${index++}.$suffix", input = stream ) hasImages = true } } } } if (!hasImages) { clearPdfCache(zipPath) null } else { zipPath } } override suspend fun convertToZip( uri: String, interval: Int ): String = catchPdf { val prefix = uri.toUri().filename()?.substringBeforeLast('.') ?: timestamp() val filename = "$PDF${prefix}.zip" usePdf(uri) { document -> shareProvider.cacheDataOrThrow( filename = filename ) { output -> var index = 0 output.outputStream().createZip { zip -> document.pageIndices .chunked(interval.coerceAtLeast(1)) .forEach { pages -> createPdf { newDoc -> pages.forEach { pageIndex -> newDoc.addPage(document.getPageSafe(pageIndex)) } zip.putEntry( name = "${prefix}_${index++}.pdf", write = { newDoc.save(StreamWriteable(it).shielded()) } ) } } } } } } override suspend fun printPdf( uri: String, params: PrintPdfParams ): String = catchPdf { val dpi = 72f + (228f * params.quality) usePdf(uri) { document -> val renderer = PdfRenderer(document) createPdf { newDoc -> val pagesPerSheet = params.pagesPerSheet.coerceIn(PrintPdfParams.pageRange) val gridSize = params.gridSize val totalPages = document.numberOfPages val sheetsNeeded = (totalPages + pagesPerSheet - 1) / pagesPerSheet for (sheetIndex in 0 until sheetsNeeded) { val startPageIndex = sheetIndex * pagesPerSheet val firstPageOnSheet = document.getPage(startPageIndex) val cropBox = params.calculatePageSize(firstPageOnSheet)?.let { size -> PDRectangle(size.width.toFloat(), size.height.toFloat()) } ?: firstPageOnSheet.cropBox newDoc.createPage(PDPage(cropBox)) { val pageWidth = cropBox.width val pageHeight = cropBox.height val rows = gridSize.first val cols = gridSize.second val cellWidth = pageWidth / cols val cellHeight = pageHeight / rows val margin = if (params.marginPercent > 0) { (minOf( pageWidth, pageHeight ) * params.marginPercent / 100f).coerceAtLeast(0f) } else 0f val availableContentWidth = if (margin > 0) { (pageWidth - (cols + 1) * margin) / cols } else cellWidth val availableContentHeight = if (margin > 0) { (pageHeight - (rows + 1) * margin) / rows } else cellHeight for (i in 0 until pagesPerSheet) { val pageIndex = startPageIndex + i if (pageIndex >= totalPages) break val sourcePage = document.getPage(pageIndex) val sourceWidth = sourcePage.cropBox.width val sourceHeight = sourcePage.cropBox.height val scale = minOf( availableContentWidth / sourceWidth, availableContentHeight / sourceHeight ).coerceAtMost(1f) val scaledWidth = sourceWidth * scale val scaledHeight = sourceHeight * scale val col = i % cols val row = i / cols val cellLeft = col * cellWidth val cellBottom = pageHeight - (row + 1) * cellHeight val x: Float val y: Float if (margin > 0) { val contentLeft = cellLeft + margin val contentBottom = cellBottom + margin val contentCenterX = contentLeft + availableContentWidth / 2 val contentCenterY = contentBottom + availableContentHeight / 2 x = contentCenterX - scaledWidth / 2 y = contentCenterY - scaledHeight / 2 } else { x = cellLeft + (cellWidth - scaledWidth) / 2 y = cellBottom + (cellHeight - scaledHeight) / 2 } val pdImage = Trickle .drawColorBehind( input = renderer.safeRenderDpi(pageIndex, dpi), color = Color.White.toArgb() ) .asXObject( document = newDoc, quality = params.quality ) drawImage(pdImage, x, y, scaledWidth, scaledHeight) } } } newDoc.save( filename = createTempName( key = "printed", uri = uri ) ) } } } override suspend fun removeAnnotations( uri: String, params: PdfRemoveAnnotationParams ): String = catchPdf { usePdf(uri) { document -> val removeAll = params.types == PdfAnnotationType.setEntries params.pages.orAll(document).forEach { pageIndex -> val page = document.getPageSafe(pageIndex) if (removeAll) { page.annotations = emptyList() } else { page.annotations = page.annotations.filterNot { annotation -> params.types.any { type -> when (type) { PdfAnnotationType.Link -> annotation is PDAnnotationLink PdfAnnotationType.FileAttachment -> annotation is PDAnnotationFileAttachment PdfAnnotationType.Line -> annotation is PDAnnotationLine PdfAnnotationType.Popup -> annotation is PDAnnotationPopup PdfAnnotationType.Stamp -> annotation is PDAnnotationRubberStamp PdfAnnotationType.SquareCircle -> annotation is PDAnnotationSquareCircle PdfAnnotationType.Text -> annotation is PDAnnotationText PdfAnnotationType.TextMarkup -> annotation is PDAnnotationTextMarkup PdfAnnotationType.Widget -> annotation is PDAnnotationWidget PdfAnnotationType.Markup -> annotation is PDAnnotationMarkup PdfAnnotationType.Unknown -> annotation is PDAnnotationUnknown } } } } } document.save( filename = tempName( key = "annotations_removed", uri = uri ) ) } } private suspend inline fun catchPdf( crossinline action: suspend AndroidPdfHelper.() -> T ): T = withContext(defaultDispatcher) { try { helper.action() } catch (k: InvalidPasswordException) { throw SecurityException(k.message) } catch (e: Throwable) { e.makeLog("catchPdf") throw e } } private fun Bitmap.whiteBg(): Bitmap = Trickle.drawColorBehind( input = this, color = Color.White.toArgb() ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/Hocr.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.data.utils internal data class HocrWord( val left: Float, val top: Float, val right: Float, val bottom: Float, val text: String ) internal data class HocrPageBox( val width: Float, val height: Float ) internal data class HocrData( val pageBox: HocrPageBox?, val words: List ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfContentStreamEditor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.data.utils import android.graphics.Path import android.graphics.PointF import com.tom_roush.pdfbox.contentstream.PDFGraphicsStreamEngine import com.tom_roush.pdfbox.contentstream.operator.Operator import com.tom_roush.pdfbox.cos.COSBase import com.tom_roush.pdfbox.cos.COSName import com.tom_roush.pdfbox.pdfwriter.ContentStreamWriter import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDPage import com.tom_roush.pdfbox.pdmodel.common.PDStream import com.tom_roush.pdfbox.pdmodel.font.PDFont import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImage import com.tom_roush.pdfbox.util.Matrix import com.tom_roush.pdfbox.util.Vector import java.io.IOException import java.io.OutputStream internal open class PdfContentStreamEditor(val document: PDDocument, page: PDPage?) : PDFGraphicsStreamEngine(page) { /** * * * This method retrieves the next operation before its registered * listener is called. The default does nothing. * * * * Override this method to retrieve state information from before the * operation execution. * */ protected fun nextOperation(operator: Operator?, operands: List) { } /** * * * This method writes content stream operations to the target canvas. The default * implementation writes them as they come, so it essentially generates identical * copies of the original instructions [.processOperator] * forwards to it. * * * * Override this method to achieve some fancy editing effect. * */ @Throws(IOException::class) protected open fun write( contentStreamWriter: ContentStreamWriter, operator: Operator, operands: List ) { contentStreamWriter.writeTokens(operands) contentStreamWriter.writeToken(operator) } // stub implementation of PDFGraphicsStreamEngine abstract methods override fun appendRectangle( p0: PointF?, p1: PointF?, p2: PointF?, p3: PointF? ) { } @Throws(IOException::class) override fun drawImage(pdImage: PDImage?) { } override fun clip(windingRule: Path.FillType?) { } @Throws(IOException::class) override fun moveTo(x: Float, y: Float) { } @Throws(IOException::class) override fun lineTo(x: Float, y: Float) { } @Throws(IOException::class) override fun curveTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) { } @Throws(IOException::class) override fun getCurrentPoint(): PointF? { return PointF() } @Throws(IOException::class) override fun closePath() { } @Throws(IOException::class) override fun endPath() { } @Throws(IOException::class) override fun strokePath() { } override fun fillPath(windingRule: Path.FillType?) { } override fun fillAndStrokePath(windingRule: Path.FillType?) { } @Throws(IOException::class) override fun shadingFill(shadingName: COSName?) { } // Actual editing methods @Throws(IOException::class) override fun processPage(page: PDPage) { val stream = PDStream(document) replacement = ContentStreamWriter( stream.createOutputStream(COSName.FLATE_DECODE).also { replacementStream = it }) super.processPage(page) replacementStream!!.close() page.setContents(stream) replacement = null replacementStream = null } @Throws(IOException::class) fun processFormXObject(formXObject: PDFormXObject, page: PDPage?) { val stream = PDStream(document) replacement = ContentStreamWriter( stream.createOutputStream(COSName.FLATE_DECODE).also { replacementStream = it }) super.processChildStream(formXObject, page) replacementStream!!.close() try { formXObject.cosObject.createOutputStream().use { outputStream -> stream.createInputStream().copyTo(outputStream) } } finally { replacement = null replacementStream = null } } // PDFStreamEngine overrides to allow editing @Throws(IOException::class) override fun showForm(form: PDFormXObject?) { // DON'T descend into XObjects // super.showForm(form); } @Throws(IOException::class) protected override fun processOperator(operator: Operator, operands: List) { if (inOperator) { super.processOperator(operator, operands) } else { inOperator = true nextOperation(operator, operands) super.processOperator(operator, operands) write(replacement!!, operator, operands) inOperator = false } } var replacementStream: OutputStream? = null var replacement: ContentStreamWriter? = null var inOperator: Boolean = false } internal fun PDDocument.removeText(linesToRemove: Set) { for (page in pages) { val editor = object : PdfContentStreamEditor(this, page) { val recentChars = StringBuilder() val TEXT_SHOWING_OPERATORS = listOf("Tj", "'", "\"", "TJ") @Suppress("DEPRECATION") @Deprecated("Deprecated in Java") override fun showGlyph( textRenderingMatrix: Matrix, font: PDFont, code: Int, unicode: String, displacement: Vector ) { recentChars.append(unicode) super.showGlyph(textRenderingMatrix, font, code, unicode, displacement) } override fun write( contentStreamWriter: ContentStreamWriter, operator: Operator, operands: List ) { val recentText = recentChars.toString() recentChars.setLength(0) if (TEXT_SHOWING_OPERATORS.contains(operator.name) && linesToRemove.any { recentText.contains(it, ignoreCase = true) } ) { return } super.write(contentStreamWriter, operator, operands) } } editor.processPage(page) } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfRenderer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.data.utils import android.graphics.Bitmap import android.os.Build import android.os.ext.SdkExtensions import androidx.annotation.ChecksSdkIntAtLeast import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.logger.makeLog import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.encryption.InvalidPasswordException import com.tom_roush.pdfbox.rendering.PDFRenderer import java.lang.AutoCloseable import kotlin.math.roundToInt class PdfRenderer( val pDocument: PDDocument ) : PDFRenderer(pDocument), AutoCloseable { val pageCount: Int get() = pDocument.numberOfPages val pageIndices: List get() = pDocument.pageIndices fun openPage(index: Int): Page = pDocument.getPage(index).let { page -> page.cropBox.run { Page( width = width.roundToInt(), height = height.roundToInt() ) } } fun safeRenderDpi( pageIndex: Int, dpi: Float ): Bitmap = try { System.gc() if (openPage(pageIndex).run { width * height * 4 <= 4096 * 4096 * 4 }) { renderImageWithDPI(pageIndex, dpi) } else { renderImage(pageIndex) } } catch (t1: Throwable) { t1.makeLog("safeRenderDpi") System.gc() try { renderImage(pageIndex) } catch (t2: Throwable) { t2.makeLog("safeRenderDpi") System.gc() renderImage(pageIndex, 0.5f) } } finally { System.gc() } override fun close() = pDocument.close() class Page( val width: Int, val height: Int ) { val size = IntegerSize(width, height) } } fun PdfRenderer( uri: String, password: String? = null, onFailure: (Throwable) -> Unit = {}, onPasswordRequest: (() -> Unit)? = null ): PdfRenderer? = runCatching { safeOpenPdf( uri = uri, password = password ).let(::PdfRenderer) }.onFailure { throwable -> when (throwable) { is InvalidPasswordException -> onPasswordRequest?.invoke() ?: onFailure(throwable) else -> onFailure(throwable) } }.getOrNull() @ChecksSdkIntAtLeast(api = 13, extension = Build.VERSION_CODES.S) fun canUseNewPdf(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM || Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 13 ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/data/utils/PdfUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.data.utils import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.saving.io.UriReadable import com.t8rin.imagetoolbox.core.data.utils.outputStream import com.t8rin.imagetoolbox.core.domain.model.RectModel import com.t8rin.imagetoolbox.core.domain.saving.io.Writeable import com.t8rin.imagetoolbox.core.domain.utils.applyUse import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfMetadata import com.t8rin.logger.makeLog import com.tom_roush.harmony.awt.AWTColor import com.tom_roush.pdfbox.io.MemoryUsageSetting import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDDocumentInformation import com.tom_roush.pdfbox.pdmodel.PDPage import com.tom_roush.pdfbox.pdmodel.PDPageContentStream import com.tom_roush.pdfbox.pdmodel.PDResources import com.tom_roush.pdfbox.pdmodel.common.PDRectangle import com.tom_roush.pdfbox.pdmodel.encryption.AccessPermission import com.tom_roush.pdfbox.pdmodel.encryption.InvalidPasswordException import com.tom_roush.pdfbox.pdmodel.encryption.StandardProtectionPolicy import com.tom_roush.pdfbox.pdmodel.font.PDType0Font import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory import com.tom_roush.pdfbox.pdmodel.graphics.image.LosslessFactory import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject import com.tom_roush.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState import java.util.Calendar import kotlin.math.roundToInt internal fun PDDocument.save( writeable: Writeable, password: String? = null ) { if (password.isNullOrBlank()) { isAllSecurityToBeRemoved = true } else { protect( StandardProtectionPolicy(password, password, AccessPermission()).apply { encryptionKeyLength = 128 } ) } save(writeable.outputStream()) } internal var PDDocument.metadata: PdfMetadata get() = documentInformation.run { PdfMetadata( title = title, author = author, subject = subject, keywords = keywords, creator = creator, producer = producer ) } set(value) { documentInformation.apply { title = value.title ?: title author = value.author ?: author subject = value.subject ?: subject keywords = value.keywords ?: keywords creator = value.creator ?: creator producer = value.producer ?: producer } } @JvmName("setMetadataNullable") internal fun PDDocument.setMetadata(value: PdfMetadata?) { if (value == null) { documentInformation = PDDocumentInformation().apply { creationDate = Calendar.getInstance() modificationDate = Calendar.getInstance() } } else { metadata = value } } internal val PDDocument.defaultFont get() = PDType0Font.load( this, appContext.resources.openRawResource(R.raw.roboto_bold) ) internal fun PDDocument.getPageSafe(index: Int): PDPage = getPage( index.coerceIn( minimumValue = 0, maximumValue = numberOfPages - 1 ) ) internal val PDDocument.pageIndices: List get() = List(numberOfPages) { it } internal fun PDPageContentStream.setAlpha(alpha: Float) { val gs = PDExtendedGraphicsState().apply { nonStrokingAlphaConstant = alpha } setGraphicsStateParameters(gs) } internal fun PDPageContentStream.setColor(color: Color) { if (color.alpha < 1f) { setAlpha(color.alpha) } setNonStrokingColor( AWTColor( (color.red * 255).roundToInt(), (color.green * 255).roundToInt(), (color.blue * 255).roundToInt(), (color.alpha * 255).roundToInt() ) ) } internal fun PDPageContentStream.setColor(color: Int) = setColor(Color(color)) internal fun PDDocument.createPage( page: PDPage, graphics: PDPageContentStream.() -> T ) { addPage(page) writePage( page = page, overwrite = true, graphics = graphics ) } internal fun PDDocument.writePage( page: PDPage, overwrite: Boolean = false, graphics: PDPageContentStream.() -> T ) = if (overwrite) { PDPageContentStream(this, page) } else { PDPageContentStream( this, page, PDPageContentStream.AppendMode.APPEND, true ) }.applyUse(graphics) internal fun safeOpenPdf( uri: String, password: String? ): PDDocument { val stream = UriReadable(uri.toUri(), appContext).stream return try { PDDocument.load( stream, password.orEmpty() ) } catch (t: Throwable) { if (t is InvalidPasswordException) throw t "failed to open pdf from $uri - trying again".makeLog("openPdf") PDDocument.load( stream, password.orEmpty(), MemoryUsageSetting.setupTempFileOnly() ) } } internal fun Bitmap.asXObject( document: PDDocument, quality: Float ): PDImageXObject = if (quality >= 1f) { LosslessFactory.createFromImage(document, this) } else { JPEGFactory.createFromImage(document, this, quality.coerceAtLeast(0f)) } internal fun PDDocument.getAllImages(): List = pages.flatMap { it.getResources().getImages() } internal inline fun createPdf(action: (PDDocument) -> T) = PDDocument().use(action) internal fun List?.orAll(document: PDDocument) = orEmpty().ifEmpty { document.pageIndices } internal inline fun PDDocument.transformImages( quality: Float, transform: (Bitmap) -> Bitmap ) { pages.forEach { page -> page.resources.apply { for (name in xObjectNames) { val image = getXObject(name) .safeCast() ?.image ?.let(transform) ?.asXObject( document = this@transformImages, quality = quality ) ?: continue put(name, image) } } } } internal fun PDRectangle.crop( rotation: Int, rect: RectModel ): PDRectangle { val width = width val height = height val originX = lowerLeftX val originY = lowerLeftY return when (rotation) { 90 -> PDRectangle( originX + rect.top * width, originY + rect.left * height, (rect.bottom - rect.top) * width, (rect.right - rect.left) * height ) 180 -> PDRectangle( originX + (1f - rect.right) * width, originY + rect.top * height, (rect.right - rect.left) * width, (rect.bottom - rect.top) * height ) 270 -> PDRectangle( originX + (1f - rect.bottom) * width, originY + (1f - rect.right) * height, (rect.bottom - rect.top) * width, (rect.right - rect.left) * height ) else -> PDRectangle( originX + rect.left * width, originY + (1f - rect.bottom) * height, (rect.right - rect.left) * width, (rect.bottom - rect.top) * height ) } } private fun PDResources.getImages(): List { val images: MutableList = mutableListOf() for (xObjectName in xObjectNames) { val xObject = getXObject(xObjectName) if (xObject is PDFormXObject) { images.addAll(xObject.getResources().getImages()) } else if (xObject is PDImageXObject) { images.add(xObject) } } return images } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/di/PdfToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.di import com.t8rin.imagetoolbox.feature.pdf_tools.data.AndroidPdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface PdfToolsModule { @Singleton @Binds fun providePdfManager( manager: AndroidPdfManager ): PdfManager } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfHelper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCheckResult import kotlinx.coroutines.flow.StateFlow interface PdfHelper { val savedSignatures: StateFlow> suspend fun saveSignature(signature: Any): Boolean fun setMasterPassword( password: String? ) fun createTempName( key: String, uri: String? = null ): String fun clearPdfCache(uri: String?) suspend fun checkPdf( uri: String ): PdfCheckResult suspend fun getPdfPages( uri: String ): List suspend fun getPdfPageSizes( uri: String ): List } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/PdfManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.ExtractPagesAction import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCreationParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCropParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfExtractPagesParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfMetadata import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfPageNumbersParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfRemoveAnnotationParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfSignatureParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfWatermarkParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PrintPdfParams import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.SearchablePdfPage import kotlinx.coroutines.flow.Flow interface PdfManager : PdfHelper { fun extractPages( uri: String, params: PdfExtractPagesParams ): Flow suspend fun createPdf( imageUris: List, params: PdfCreationParams ): String suspend fun createSearchablePdf( pages: List, params: PdfCreationParams = PdfCreationParams(quality = 100) ): String suspend fun mergePdfs( uris: List ): String suspend fun splitPdf( uri: String, pages: List? ): String suspend fun removePdfPages( uri: String, pages: List ): String suspend fun rotatePdf( uri: String, rotations: List ): String suspend fun rearrangePdf( uri: String, newOrder: List ): String suspend fun addPageNumbers( uri: String, params: PdfPageNumbersParams ): String suspend fun addWatermark( uri: String, params: PdfWatermarkParams ): String suspend fun addSignature( uri: String, params: PdfSignatureParams ): String suspend fun protectPdf( uri: String, password: String ): String suspend fun unlockPdf( uri: String, password: String ): String suspend fun extractPagesFromPdf( uri: String ): List suspend fun compressPdf( uri: String, quality: Float ): String suspend fun convertToGrayscale( uri: String ): String suspend fun repairPdf( uri: String ): String suspend fun changePdfMetadata( uri: String, metadata: PdfMetadata? ): String suspend fun getPdfMetadata( uri: String ): PdfMetadata suspend fun stripText( uri: String ): List suspend fun cropPdf( uri: String, params: PdfCropParams ): String suspend fun flattenPdf( uri: String, quality: Float ): String suspend fun detectPdfAutoRotations( uri: String ): List suspend fun extractImagesFromPdf( uri: String ): String? suspend fun convertToZip( uri: String, interval: Int ): String suspend fun printPdf( uri: String, params: PrintPdfParams ): String suspend fun removeAnnotations( uri: String, params: PdfRemoveAnnotationParams ): String } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/ExtractPagesAction.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model sealed interface ExtractPagesAction { data class PagesCount(val count: Int) : ExtractPagesAction data class Progress(val index: Int, val image: Any) : ExtractPagesAction } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageOrientation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model enum class PageOrientation { ORIGINAL, VERTICAL, HORIZONTAL } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PageSize.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model data class PageSize( val width: Int, val height: Int, val name: String? ) { companion object { val Auto by lazy { PageSize( width = -100, height = -100, name = null ) } val entries: List by lazy { listOf( // ISO A Series PageSize(2384, 3370, "A0"), PageSize(1684, 2384, "A1"), PageSize(1191, 1684, "A2"), PageSize(842, 1191, "A3"), PageSize(595, 842, "A4"), PageSize(420, 595, "A5"), PageSize(297, 420, "A6"), PageSize(210, 297, "A7"), PageSize(148, 210, "A8"), PageSize(105, 148, "A9"), PageSize(74, 105, "A10"), // ISO B Series PageSize(2835, 4008, "B0"), PageSize(2004, 2835, "B1"), PageSize(1417, 2004, "B2"), PageSize(1001, 1417, "B3"), PageSize(709, 1001, "B4"), PageSize(499, 709, "B5"), PageSize(354, 499, "B6"), PageSize(249, 354, "B7"), PageSize(176, 249, "B8"), PageSize(125, 176, "B9"), PageSize(88, 125, "B10"), // ISO C Series (envelopes) PageSize(2599, 3677, "C0"), PageSize(1837, 2599, "C1"), PageSize(1298, 1837, "C2"), PageSize(918, 1298, "C3"), PageSize(649, 918, "C4"), PageSize(459, 649, "C5"), PageSize(323, 459, "C6"), PageSize(230, 323, "C7"), PageSize(162, 230, "C8"), PageSize(113, 162, "C9"), PageSize(79, 113, "C10"), // US/ANSI Series PageSize(612, 792, "Letter"), // 8.5 x 11 in PageSize(612, 1008, "Legal"), // 8.5 x 14 in PageSize(792, 1224, "Tabloid"), // 11 x 17 in PageSize(612, 936, "Statement"), // 5.5 x 8.5 in PageSize(396, 612, "Executive"), // 7.25 x 10.5 in // ANSI Series PageSize(612, 792, "ANSI A"), // Letter PageSize(792, 1224, "ANSI B"), // Tabloid/Ledger PageSize(1224, 1584, "ANSI C"), PageSize(1584, 2448, "ANSI D"), PageSize(2448, 3168, "ANSI E"), // Architectural Series PageSize(432, 576, "Arch A"), // 9 x 12 in PageSize(576, 864, "Arch B"), // 12 x 18 in PageSize(864, 1152, "Arch C"), // 18 x 24 in PageSize(1152, 1728, "Arch D"), // 24 x 36 in PageSize(1728, 2304, "Arch E"), // 36 x 48 in PageSize(2304, 3240, "Arch E1"), // 30 x 42 in // Other Common Sizes PageSize(280, 416, "Business Card"), // 2.91 x 4.33 in (ISO) PageSize(255, 408, "Business Card US"), // 2 x 3.5 in PageSize(499, 709, "A5+"), PageSize(595, 984, "A4+"), PageSize(842, 1338, "A3+"), // Photo Sizes PageSize(300, 450, "Photo 4x6"), // 4 x 6 in PageSize(450, 600, "Photo 5x7"), // 5 x 7 in PageSize(600, 720, "Photo 6x8"), // 6 x 8 in PageSize(720, 960, "Photo 8x10"), // 8 x 10 in PageSize(900, 1200, "Photo 10x12"), // 10 x 12 in PageSize(1200, 1800, "Photo 12x18"), // 12 x 18 in // Other International PageSize(700, 1000, "F4"), // 8.27 x 13 in PageSize(827, 1169, "Quarto"), // 8.46 x 10.83 in PageSize(649, 918, "C5 Envelope"), PageSize(461, 648, "DL Envelope"), // 110 x 220 mm PageSize(413, 610, "Japanese Postcard"), // 100 x 148 mm PageSize(630, 882, "ISO B6"), // Square formats PageSize(420, 420, "Square 15cm"), // 15 x 15 cm PageSize(595, 595, "Square 21cm"), // 21 x 21 cm PageSize(842, 842, "Square 30cm"), // 30 x 30 cm // Digital formats PageSize(768, 1024, "iPad"), PageSize(600, 800, "E-reader"), PageSize(1080, 1920, "HD Video"), // Posters PageSize(1191, 1684, "A2 Poster"), PageSize(842, 1191, "A3 Poster"), PageSize(1684, 2384, "A1 Poster"), PageSize(1224, 1584, "ANSI C Poster"), PageSize(1584, 2448, "ANSI D Poster"), // Index cards PageSize(300, 500, "Index Card 3x5"), // 3 x 5 in PageSize(400, 600, "Index Card 4x6"), // 4 x 6 in PageSize(500, 800, "Index Card 5x8") // 5 x 8 in ) } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfAnnotationType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model enum class PdfAnnotationType { Link, FileAttachment, Line, Popup, Stamp, SquareCircle, Text, TextMarkup, Widget, Markup, Unknown; companion object { val setEntries by lazy { entries.toSet() } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCheckResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model sealed interface PdfCheckResult { data class Failure(val throwable: Throwable) : PdfCheckResult data object Open : PdfCheckResult sealed interface Protected : PdfCheckResult { data object NeedsPassword : Protected data class Unlocked(val decryptedUri: String) : Protected } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCreationParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model import com.t8rin.imagetoolbox.core.domain.image.model.Preset data class PdfCreationParams( val scaleSmallImagesToLarge: Boolean = false, val preset: Preset.Percentage = Preset.Original, val quality: Int = 85 ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfCropParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model import com.t8rin.imagetoolbox.core.domain.model.RectModel data class PdfCropParams( val pages: List? = null, val rect: RectModel = RectModel( left = 0.1f, right = 0.9f, top = 0.1f, bottom = 0.9f ) ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfExtractPagesParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model import com.t8rin.imagetoolbox.core.domain.image.model.Preset data class PdfExtractPagesParams( val pages: List? = null, val preset: Preset.Percentage = Preset.Original ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfMetadata.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model data class PdfMetadata( val title: String? = null, val author: String? = null, val subject: String? = null, val keywords: String? = null, val creator: String? = null, val producer: String? = null ) { companion object { val Empty = PdfMetadata() } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfPageNumbersParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model import com.t8rin.imagetoolbox.core.domain.model.Position data class PdfPageNumbersParams( val labelFormat: String = "Page {n} of {total}", val position: Position = Position.BottomCenter, val color: Int = -7829368 ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfRemoveAnnotationParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model data class PdfRemoveAnnotationParams( val pages: List? = null, val types: Set = setOf(PdfAnnotationType.Link) ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfSignatureParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model data class PdfSignatureParams( val x: Float = 0.1f, val y: Float = 0.1f, val size: Float = 0.25f, val pages: List = emptyList(), val opacity: Float = 0.3f, val signatureImage: Any = "file:///android_asset/svg/emotions/aasparkles.svg" ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PdfWatermarkParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model data class PdfWatermarkParams( val color: Int = 0x000000, val fontSize: Float = 50f, val rotation: Float = 315f, val opacity: Float = 0.3f, val pages: List = emptyList(), val text: String = "Watermark", ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/PrintPdfParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model data class PrintPdfParams( val orientation: PageOrientation = PageOrientation.ORIGINAL, val pageSize: PageSize = PageSize.Auto, val pagesPerSheet: Int = 1, val marginPercent: Float = 0f, val quality: Float = 0.85f, ) { val pageSizeFinal = if (pageSize == PageSize.Auto) { null } else { when (orientation) { PageOrientation.ORIGINAL -> null PageOrientation.VERTICAL -> pageSize PageOrientation.HORIZONTAL -> pageSize.run { copy(width = height, height = width) } } } val gridSize = pagesMapping.getOrDefault(pagesPerSheet, 1 to 1).let { when (orientation) { PageOrientation.ORIGINAL, PageOrientation.VERTICAL -> it PageOrientation.HORIZONTAL -> it.second to it.first } } companion object { val pagesMapping by lazy { mapOf( 1 to (1 to 1), 2 to (2 to 1), 4 to (2 to 2), 6 to (3 to 2), 8 to (4 to 2), 9 to (3 to 3), 12 to (4 to 3), 16 to (4 to 4) ) } val pageRange by lazy { pagesMapping.keys.sorted().run { first()..last() } } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/domain/model/SearchablePdfPage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.domain.model data class SearchablePdfPage( val imageUri: String, val text: String, val hocr: String ) ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.arkivanov.essenty.lifecycle.doOnDestroy import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCheckResult import com.t8rin.logger.makeLog import kotlinx.coroutines.Job abstract class BasePdfToolComponent( val onGoBack: () -> Unit, val onNavigate: (Screen) -> Unit, dispatchersHolder: DispatchersHolder, componentContext: ComponentContext, private val pdfManager: PdfManager, ) : BaseComponent(dispatchersHolder, componentContext) { private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving protected val _showPasswordRequestDialog: MutableState = mutableStateOf(false) val showPasswordRequestDialog by _showPasswordRequestDialog protected var isRtl = false open val extraDataType: ExtraDataType? = ExtraDataType.Pdf open val mimeType: MimeType.Single = MimeType.Pdf init { doOnDestroy { pdfManager.setMasterPassword(null) } } protected var savingJob: Job? by smartJob { _isSaving.update { false } } open fun setPassword(password: String) { _showPasswordRequestDialog.update { false } pdfManager.setMasterPassword(password) } fun hidePasswordRequestDialog() { _showPasswordRequestDialog.update { false } } fun updateIsRtl(isRtl: Boolean) { this.isRtl = isRtl } fun checkPdf( uri: Uri?, onDecrypted: (Uri) -> Unit, onSuccess: (Uri) -> Unit = {} ) { if (uri == null) return componentScope.launch { when (val result = pdfManager.checkPdf(uri.toString())) { is PdfCheckResult.Open -> onSuccess(uri) is PdfCheckResult.Protected.NeedsPassword -> _showPasswordRequestDialog.update { true } is PdfCheckResult.Protected.Unlocked -> { pdfManager.setMasterPassword(null) onDecrypted(result.decryptedUri.toUri()) onSuccess(result.decryptedUri.toUri()) } is PdfCheckResult.Failure -> result.throwable.makeLog("checkPdf") } } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } open fun createTargetFilename(): String = getKey().let { pdfManager.createTempName( key = it?.first.orEmpty(), uri = it?.second?.toString() ) } protected open fun getKey(): Pair? = null abstract fun saveTo(uri: Uri) abstract fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) abstract fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) protected fun doSaving( action: suspend KeepAliveService.() -> SaveResult ) { savingJob = trackProgress { runSuspendCatching { _isSaving.value = true parseFileSaveResult(action()) }.onFailure { if (it is SecurityException) { _showPasswordRequestDialog.update { true } } else { parseFileSaveResult(SaveResult.Error.Exception(it)) } } _isSaving.value = false } } protected fun doSharing( action: suspend KeepAliveService.() -> T, onSuccess: (T) -> Unit = {}, onFailure: (Throwable) -> Unit ) { savingJob = trackProgress { runSuspendCatching { _isSaving.value = true onSuccess(action()) }.onFailure { if (it is SecurityException) { _showPasswordRequestDialog.update { true } } else { onFailure(it) } } _isSaving.value = false } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/BasePdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common import android.net.Uri import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Pdf import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ResultLauncher import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitBackHandler import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.PasswordRequestDialog import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.FileNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle @Composable internal fun BasePdfToolContent( component: BasePdfToolComponent, contentPicker: ResultLauncher, isPickedAlready: Boolean, canShowScreenData: Boolean, title: String, actions: @Composable RowScope.() -> Unit = {}, topAppBarPersistentActions: @Composable RowScope.() -> Unit = {}, imagePreview: @Composable () -> Unit = {}, placeImagePreview: Boolean = false, showImagePreviewAsStickyHeader: Boolean = false, controls: (@Composable ColumnScope.(LazyListState) -> Unit)?, canSave: Boolean = true, canShare: Boolean = canSave, onFilledPassword: () -> Unit = {}, forceImagePreviewToMax: Boolean = false, placeControlsSeparately: Boolean = false, addHorizontalCutoutPaddingIfNoPreview: Boolean = placeImagePreview && showImagePreviewAsStickyHeader, secondaryButtonIcon: ImageVector = Icons.Rounded.FileOpen, secondaryButtonText: String = stringResource(R.string.pick_file), noDataText: String = stringResource(R.string.pick_file_to_start), onPrimaryButtonClick: (() -> Unit)? = null, onPrimaryButtonLongClick: (() -> Unit)? = null, drawBottomShadow: Boolean = true, shareDialogTitle: String = "PDF", shareDialogIcon: ImageVector = Icons.Outlined.Pdf ) { val saveLauncher = rememberFileCreator( mimeType = component.mimeType, onSuccess = component::saveTo ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } AutoFilePicker( onAutoPick = contentPicker::launch, isPickedAlready = isPickedAlready ) val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl LaunchedEffect(component) { snapshotFlow { isRtl } .collect { component.updateIsRtl(it) } } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, placeImagePreview = placeImagePreview, addHorizontalCutoutPaddingIfNoPreview = addHorizontalCutoutPaddingIfNoPreview, showImagePreviewAsStickyHeader = showImagePreviewAsStickyHeader, title = { TopAppBarTitle( title = title, input = null, isLoading = component.isImageLoading, size = null ) }, forceImagePreviewToMax = forceImagePreviewToMax, placeControlsSeparately = placeControlsSeparately, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = canShare, onShare = { component.performSharing( onSuccess = AppToastHost::showConfetti, onFailure = AppToastHost::showFailureToast ) }, onEdit = { component.prepareForSharing( onSuccess = { editSheetData = it }, onFailure = AppToastHost::showFailureToast ) }, dialogTitle = shareDialogTitle, dialogIcon = shareDialogIcon ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, extraDataType = component.extraDataType, onNavigate = component.onNavigate ) actions() }, topAppBarPersistentActions = { if (!canShowScreenData) { TopAppBarEmoji() } else { topAppBarPersistentActions() } }, imagePreview = imagePreview, controls = controls?.let { { Column( modifier = if (!placeImagePreview && !isPortrait) { Modifier.windowInsetsPadding( WindowInsets.displayCutout.only(WindowInsetsSides.Start) ) } else { Modifier }, horizontalAlignment = Alignment.CenterHorizontally ) { controls(this, it) } } }, buttons = { actions -> BottomButtonsBlock( isNoData = !canShowScreenData, isPrimaryButtonVisible = canSave, secondaryButtonIcon = secondaryButtonIcon, secondaryButtonText = secondaryButtonText, onSecondaryButtonClick = contentPicker::launch, onPrimaryButtonClick = onPrimaryButtonClick ?: { saveLauncher.make(component.createTargetFilename()) }, onPrimaryButtonLongClick = onPrimaryButtonLongClick, actions = { if (isPortrait) actions() }, enableHorizontalStroke = drawBottomShadow ) }, noDataControls = { FileNotPickedWidget( text = noDataText, onPickFile = contentPicker::launch ) }, portraitTopPadding = 20.dp, canShowScreenData = canShowScreenData ) LoadingDialog( visible = component.isSaving, onCancelLoading = component::cancelSaving ) ExitBackHandler( enabled = component.haveChanges, onBack = onBack ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) PasswordRequestDialog( isVisible = component.showPasswordRequestDialog, onDismiss = { component.hidePasswordRequestDialog() component.onGoBack() }, onFillPassword = { component.setPassword(it) onFilledPassword() } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PageSwitcher.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes @Composable internal fun PageSwitcher( pageCount: Int, activePages: List? = null, modifier: Modifier = Modifier, content: @Composable (page: Int) -> Unit ) { var page by rememberSaveable { mutableIntStateOf(0) } val indices = remember(pageCount) { List(pageCount) { it } } Column( modifier = modifier .detectSwipes( key = indices, onSwipeLeft = { page = indices.rightFrom(page) }, onSwipeRight = { page = indices.leftFrom(page) } ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { val targetPage = if (indices.isEmpty()) 0 else indices[page] Box(modifier = Modifier.weight(1f, false)) { content(targetPage) } if (pageCount > 1) { val isActive = activePages == null || targetPage in activePages Spacer(Modifier.height(6.dp)) Row( horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth() ) { EnhancedBadge( contentColor = takeColorFromScheme { if (isActive) { onSecondary } else { outline } }, containerColor = takeColorFromScheme { if (isActive) { secondary } else { outlineVariant } } ) { Text("${targetPage + 1} / $pageCount") } AnimatedVisibility( visible = !isActive ) { Icon( imageVector = Icons.Outlined.VisibilityOff, contentDescription = null, tint = MaterialTheme.colorScheme.outline, modifier = Modifier .padding(start = 4.dp) .size(16.dp) .background( color = MaterialTheme.colorScheme.outlineVariant, shape = CircleShape ) .padding(2.dp) ) } } } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/common/PdfPreviewItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common import android.net.Uri import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberHumanFileSize import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults @Composable internal fun PdfPreviewItem( uri: Uri, onRemove: () -> Unit, modifier: Modifier = Modifier ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxWidth() .container( resultPadding = 8.dp ) ) { Picture( model = uri, modifier = Modifier .height(80.dp) .defaultMinSize(minWidth = 60.dp) .widthIn(max = 120.dp) .container( shape = ShapeDefaults.small, color = Color.Transparent, resultPadding = 0.dp ), shape = RectangleShape, contentScale = ContentScale.Fit ) Spacer(Modifier.width(16.dp)) Column( modifier = Modifier.weight(1f) ) { Text( text = rememberFilename(uri) ?: uri.toString(), style = PreferenceItemDefaults.TitleFontStyle ) Spacer(Modifier.height(4.dp)) val size = rememberHumanFileSize(uri) val pages by rememberPdfPages(uri) Text( text = "$size • $pages ${stringResource(R.string.pages_short)}", fontSize = 12.sp, textAlign = TextAlign.Start, fontWeight = FontWeight.Normal, lineHeight = 14.sp, color = LocalContentColor.current.copy(alpha = 0.5f) ) } Spacer(Modifier.width(16.dp)) EnhancedIconButton( onClick = onRemove, containerColor = MaterialTheme.colorScheme.errorContainer.copy( 0.4f ), contentColor = MaterialTheme.colorScheme.onErrorContainer, modifier = Modifier.padding(top = 4.dp) ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null ) } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/CompressPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.compress import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.compress.screenLogic.CompressPdfToolComponent import kotlin.math.roundToInt @Composable fun CompressPdfToolContent( component: CompressPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.compress_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } QualitySelector( imageFormat = ImageFormat.Jpg, quality = Quality.Base((component.quality * 100).roundToInt()), onQualityChange = { component.updateQuality(it.qualityValue / 100f) }, autoCoerce = false ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/compress/screenLogic/CompressPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.compress.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class CompressPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _quality: MutableState = mutableFloatStateOf(0.8f) val quality by _quality override fun getKey(): Pair = "compressed" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateQuality(quality: Float) { registerChanges() _quality.update { quality } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.compressPdf( uri = _uri.value.toString(), quality = quality ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.compressPdf( uri = _uri.value.toString(), quality = quality ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): CompressPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/CropPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BorderHorizontal import androidx.compose.material.icons.rounded.BorderVertical import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.utils.roundTo import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.controls.page.PageSelectionItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedRangeSliderItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop.components.CropPreview import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop.screenLogic.CropPdfToolComponent @Composable fun CropPdfToolContent( component: CropPdfToolComponent ) { val pageCount by rememberPdfPages(component.uri) val params = component.params LaunchedEffect(pageCount, params.pages) { if (params.pages == null && pageCount > 0) { component.updateParams( params.copy( pages = List(pageCount) { it } ) ) } } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canSave = !params.rect.isEmpty, canShowScreenData = component.uri != null, title = stringResource(R.string.crop_pdf), imagePreview = { CropPreview( uri = component.uri, params = params, pageCount = pageCount ) }, placeImagePreview = true, showImagePreviewAsStickyHeader = true, controls = { PageSelectionItem( value = params.pages, onValueChange = { component.updateParams(params.copy(pages = it)) }, pageCount = pageCount ) Spacer(Modifier.height(16.dp)) EnhancedRangeSliderItem( value = params.rect.let { it.left..it.right }, valueRange = 0f..1f, icon = Icons.Rounded.BorderVertical, title = stringResource(R.string.vertical_pivot_line), internalStateTransformation = { it.start.roundTo(3)..it.endInclusive.roundTo(3) }, onValueChange = { component.updateParams( params.copy( rect = params.rect.copy( left = it.start, right = it.endInclusive ) ) ) } ) Spacer(Modifier.height(8.dp)) EnhancedRangeSliderItem( value = params.rect.let { it.top..it.bottom }, valueRange = 0f..1f, icon = Icons.Rounded.BorderHorizontal, title = stringResource(R.string.horizontal_pivot_line), internalStateTransformation = { it.start.roundTo(3)..it.endInclusive.roundTo(3) }, onValueChange = { component.updateParams( params.copy( rect = params.rect.copy( top = it.start, bottom = it.endInclusive ) ) ) } ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/components/CropPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop.components import android.net.Uri import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.domain.model.RectModel import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.Black import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.rememberAnimatedBorder import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCropParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PageSwitcher @Composable internal fun CropPreview( uri: Uri?, params: PdfCropParams, pageCount: Int ) { PageSwitcher( activePages = params.pages, pageCount = pageCount ) { page -> Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip( alignment = Alignment.Center ), contentAlignment = Alignment.Center ) { var aspectRatio by rememberSaveable { mutableFloatStateOf(1f) } Box( modifier = Modifier .aspectRatio(aspectRatio) .clip(MaterialTheme.shapes.small) ) { Picture( model = remember(uri, page) { PdfImageRequest( data = uri, pdfPage = page ) }, contentScale = ContentScale.FillBounds, modifier = Modifier.matchParentSize(), onSuccess = { aspectRatio = it.result.image.safeAspectRatio }, shape = RectangleShape ) if (params.pages == null || page in params.pages) { CropFrameBorder( modifier = Modifier.matchParentSize(), cropRect = remember(params.rect) { Rect( left = params.rect.left, top = params.rect.top, right = params.rect.right, bottom = params.rect.bottom ) } ) } } } } } @Composable private fun CropFrameBorder( modifier: Modifier = Modifier, cropRect: Rect ) { val isNightMode = LocalSettingsState.current.isNightMode val black = Black val colorScheme = MaterialTheme.colorScheme val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl val cropRect = remember(cropRect, isRtl) { if (isRtl) { cropRect.copy( left = 1f - cropRect.left, right = 1f - cropRect.right ) } else { cropRect } } val pathEffect = rememberAnimatedBorder() Canvas( modifier = modifier.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } ) { val canvasWidth = size.width val canvasHeight = size.height drawRect( color = black.copy(alpha = if (isNightMode) 0.5f else 0.3f), size = size ) val topLeft = Offset( x = cropRect.left * canvasWidth, y = cropRect.top * canvasHeight ) val size = Size( width = (cropRect.right - cropRect.left) * canvasWidth, height = (cropRect.bottom - cropRect.top) * canvasHeight ) drawRect( color = Color.Transparent, blendMode = BlendMode.Clear, topLeft = topLeft, size = size ) drawRect( color = colorScheme.primary, style = Stroke( width = 1.5.dp.toPx() ), topLeft = topLeft, size = size ) drawRect( color = colorScheme.primaryContainer, style = Stroke( width = 1.5.dp.toPx(), pathEffect = pathEffect ), topLeft = topLeft, size = size ) } } @EnPreview @Composable private fun Preview() = ImageToolboxThemeForPreview(false) { LocalLayoutDirection.ProvidesValue(LayoutDirection.Ltr) { CropPreview( uri = "111".toUri(), pageCount = 100, params = PdfCropParams( rect = RectModel( left = 0.4f, top = 0.4f, right = 0.9f, bottom = 0.9f ), pages = null ), ) } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/crop/screenLogic/CropPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshotFlow import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCropParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.distinctUntilChanged class CropPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _params: MutableState = mutableStateOf(PdfCropParams()) val params by _params override fun getKey(): Pair = "cropped" to uri init { componentScope.launch { snapshotFlow { uri } .distinctUntilChanged() .collect { _params.update { it.copy(pages = null) } } } } private val adjustedParams: PdfCropParams get() = params.copy( rect = if (isRtl) { params.rect.copy( left = 1f - params.rect.left, right = 1f - params.rect.right ) } else { params.rect } ) fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateParams(params: PdfCropParams) { _params.update { params } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.cropPdf( uri = _uri.value.toString(), params = adjustedParams ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.cropPdf( uri = _uri.value.toString(), params = adjustedParams ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): CropPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/ExtractImagesPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_images import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_images.screenLogic.ExtractImagesPdfToolComponent @Composable fun ExtractImagesPdfToolContent( component: ExtractImagesPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.extract_images), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } InfoContainer( text = stringResource(R.string.extract_images_info) ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_images/screenLogic/ExtractImagesPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_images.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class ExtractImagesPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val fileController: FileController, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges override val extraDataType: ExtraDataType = ExtraDataType.File override val mimeType: MimeType.Single = MimeType.Zip private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } override fun createTargetFilename(): String = "${uri?.filename()?.substringBeforeLast('.') ?: timestamp()}_extracted.zip" override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.extractImagesFromPdf( uri = _uri.value.toString() ) ?: return@doSaving SaveResult.Error.Exception(Throwable(getString(R.string.pdf_no_embedded))) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { val processed = pdfManager.extractImagesFromPdf( uri = _uri.value.toString() ) ?: return@doSharing onFailure(Throwable(getString(R.string.pdf_no_embedded))) onSuccess( listOf( processed.toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ExtractImagesPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/ExtractPagesPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_pages import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Image import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.controls.page.PageSelectionItem import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PresetSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.ImagesPreviewWithSelection import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_pages.screenLogic.ExtractPagesPdfToolComponent @Composable fun ExtractPagesPdfToolContent( component: ExtractPagesPdfToolComponent ) { val pagesCount by rememberPdfPages(component.uri) val params = component.params val selectedPagesSize = params.pages?.size ?: 0 val isPortrait by isPortraitOrientationAsState() var trigger by remember { mutableIntStateOf(0) } val savePdfToImages: (oneTimeSaveLocationUri: String?) -> Unit = { component.save( oneTimeSaveLocationUri = it ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.pdf_to_images), topAppBarPersistentActions = { AnimatedVisibility( visible = selectedPagesSize != pagesCount, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = { component.updatePages( List(pagesCount) { it } ) trigger++ } ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = selectedPagesSize != 0 ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { selectedPagesSize.takeIf { it != 0 }?.let { Spacer(Modifier.width(8.dp)) Text( text = selectedPagesSize.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) } EnhancedIconButton( onClick = { component.updatePages(emptyList()) trigger++ } ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } }, canSave = !params.pages.isNullOrEmpty(), imagePreview = { key(trigger, pagesCount, component.uri) { ImagesPreviewWithSelection( imageUris = remember(pagesCount, component.uri) { List(pagesCount) { PdfImageRequest( data = component.uri, pdfPage = it ) } }, imageFrames = remember(params.pages) { ImageFrames.ManualSelection( params.pages.orEmpty().map { it + 1 } ) }, onFrameSelectionChange = { frames -> component.updatePages( frames.getFramePositions(pagesCount).map { it - 1 } ) }, isPortrait = isPortrait, isLoadingImages = false ) } }, placeImagePreview = true, controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } PageSelectionItem( value = params.pages, onValueChange = { component.updatePages(it) trigger++ }, pageCount = pagesCount ) Spacer(Modifier.height(8.dp)) PresetSelector( value = params.preset, includeTelegramOption = false, onValueChange = { if (it is Preset.Percentage) { component.selectPreset(it) } } ) if (component.imageInfo.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = component.imageInfo.imageFormat, quality = component.imageInfo.quality, onQualityChange = component::setQuality ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = component.imageInfo.imageFormat, onValueChange = component::updateImageFormat, quality = component.imageInfo.quality, ) }, onFilledPassword = { component.setUri(component.uri) }, onPrimaryButtonClick = { savePdfToImages(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, shareDialogTitle = stringResource(R.string.image), shareDialogIcon = Icons.Outlined.Image ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = savePdfToImages ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/extract_pages/screenLogic/ExtractPagesPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_pages.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshotFlow import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.KeepAliveService import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.ExtractPagesAction import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfExtractPagesParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onCompletion class ExtractPagesPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val imageGetter: ImageGetter, private val imageTransformer: ImageTransformer, private val imageCompressor: ImageCompressor, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges override val extraDataType: ExtraDataType? = null private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _params: MutableState = mutableStateOf(PdfExtractPagesParams()) val params by _params private val _imageInfo = mutableStateOf(ImageInfo()) val imageInfo by _imageInfo init { componentScope.launch { snapshotFlow { uri } .distinctUntilChanged() .collect { _params.update { it.copy(pages = null) } } } } fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updatePages(pages: List) { registerChanges() _params.update { it.copy( pages = pages ) } } fun selectPreset(preset: Preset.Percentage) { preset.value()?.takeIf { it <= 100f }?.let { quality -> _imageInfo.update { it.copy( quality = when (val q = it.quality) { is Quality.Base -> q.copy(qualityValue = quality) is Quality.Jxl -> q.copy(qualityValue = quality) else -> q } ) } } _params.update { it.copy( preset = preset ) } registerChanges() } fun updateImageFormat(imageFormat: ImageFormat) { _imageInfo.update { it.copy(imageFormat = imageFormat) } registerChanges() } fun setQuality(quality: Quality) { _imageInfo.update { it.copy(quality = quality) } registerChanges() } fun save( oneTimeSaveLocationUri: String? ) { doSharing( action = { extractPages( onPage = { bitmap, imageInfo -> fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = null, originalUri = uri?.toString().orEmpty(), sequenceNumber = null, data = imageCompressor.compressAndTransform( image = bitmap, imageInfo = imageInfo ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) }, onSuccess = { parseSaveResults(it.onSuccess(::registerSave)) }, onFailure = { parseSaveResults(listOf(SaveResult.Error.Exception(it))) } ) }, onFailure = { parseSaveResults(listOf(SaveResult.Error.Exception(it))) } ) } override fun saveTo( uri: Uri ) = Unit override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { extractPages( onPage = { bitmap, imageInfo -> shareProvider.cacheImage( imageInfo = imageInfo, image = bitmap )?.toUri() }, onSuccess = onSuccess, onFailure = onFailure ) }, onFailure = onFailure ) } private suspend fun KeepAliveService.extractPages( onPage: suspend (Bitmap, ImageInfo) -> T?, onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { var done = 0 var left = 1 val results = mutableListOf() pdfManager.extractPages( uri = uri.toString(), params = params ).onCompletion { onSuccess(results) registerSave() }.catch { onFailure(it) }.collect { action -> when (action) { is ExtractPagesAction.PagesCount -> left = action.count is ExtractPagesAction.Progress -> { val bitmap = imageGetter.getImage(action.image) ?: return@collect val imageInfo = imageTransformer.applyPresetBy( image = bitmap, preset = params.preset, currentInfo = imageInfo ).copy( originalUri = uri?.toString() ) onPage(bitmap, imageInfo)?.let(results::add) done += 1 updateProgress( done = done, total = left ) } } } } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ExtractPagesPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/FlattenPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.flatten import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.flatten.screenLogic.FlattenPdfToolComponent import kotlin.math.roundToInt @Composable fun FlattenPdfToolContent( component: FlattenPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.flatten_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } QualitySelector( imageFormat = ImageFormat.Jpg, quality = Quality.Base((component.quality * 100).roundToInt()), onQualityChange = { component.updateQuality(it.qualityValue / 100f) }, autoCoerce = false ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/flatten/screenLogic/FlattenPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.flatten.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class FlattenPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _quality: MutableState = mutableFloatStateOf(0.85f) val quality by _quality override fun getKey(): Pair = "flattened" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateQuality(quality: Float) { registerChanges() _quality.update { quality } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.flattenPdf( uri = _uri.value.toString(), quality = quality ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.flattenPdf( uri = _uri.value.toString(), quality = quality ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): FlattenPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/GrayscalePdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.grayscale import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.grayscale.screenLogic.GrayscalePdfToolComponent @Composable fun GrayscalePdfToolContent( component: GrayscalePdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.grayscale), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } InfoContainer( text = stringResource(R.string.grayscale_info) ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/grayscale/screenLogic/GrayscalePdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.grayscale.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class GrayscalePdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri override fun getKey(): Pair = "grayscale" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.convertToGrayscale( uri = _uri.value.toString() ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.convertToGrayscale( uri = _uri.value.toString() ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): GrayscalePdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/ImagesToPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.images_to_pdf import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel import com.t8rin.imagetoolbox.core.ui.widget.controls.ScaleSmallImagesToLargeToggle import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PresetSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.images_to_pdf.screenLogic.ImagesToPdfToolComponent @Composable fun ImagesToPdfToolContent( component: ImagesToPdfToolComponent ) { val addImagesToPdfPicker = rememberImagePicker(onSuccess = component::addUris) BasePdfToolContent( component = component, contentPicker = rememberImagePicker( onSuccess = component::setUris ), secondaryButtonIcon = Icons.Rounded.AddPhotoAlt, secondaryButtonText = stringResource(R.string.pick_image_alt), noDataText = stringResource(R.string.pick_image), isPickedAlready = component.initialUris != null, canShowScreenData = !component.uris.isNullOrEmpty(), title = stringResource(R.string.images_to_pdf), controls = { ImageReorderCarousel( images = component.uris, onReorder = component::setUris, onNeedToAddImage = addImagesToPdfPicker::pickImage, onNeedToRemoveImageAt = component::removeAt, onNavigate = component.onNavigate ) Spacer(Modifier.height(16.dp)) PresetSelector( value = component.presetSelected, includeTelegramOption = false, onValueChange = { if (it is Preset.Percentage) { component.selectPreset(it) } } ) Spacer(Modifier.height(8.dp)) QualitySelector( imageFormat = ImageFormat.Jpg, quality = Quality.Base(component.quality), onQualityChange = { component.setQuality(it.qualityValue) }, autoCoerce = false ) Spacer(Modifier.height(8.dp)) ScaleSmallImagesToLargeToggle( checked = component.scaleSmallImagesToLarge, onCheckedChange = { component.toggleScaleSmallImagesToLarge() } ) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/images_to_pdf/screenLogic/ImagesToPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.images_to_pdf.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfCreationParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class ImagesToPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUris: List?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUris != null) override val haveChanges: Boolean by _haveChanges private val _uris: MutableState?> = mutableStateOf(initialUris) val uris by _uris private val _presetSelected: MutableState = mutableStateOf(Preset.Percentage(100)) val presetSelected by _presetSelected private val _quality: MutableState = mutableIntStateOf(85) val quality by _quality private val _scaleSmallImagesToLarge: MutableState = mutableStateOf(false) val scaleSmallImagesToLarge by _scaleSmallImagesToLarge private val pdfCreationParams: PdfCreationParams get() = PdfCreationParams( scaleSmallImagesToLarge = _scaleSmallImagesToLarge.value, preset = _presetSelected.value, quality = _quality.value ) fun setUris(uris: List?) { if (uris == null) { registerChangesCleared() } else { registerChanges() } _uris.update { uris } } fun addUris(uris: List) { setUris(this.uris.orEmpty().plus(uris).distinct()) } fun removeAt(index: Int) { runCatching { _uris.update { it?.toMutableList()?.apply { removeAt(index) } } registerChanges() } } fun toggleScaleSmallImagesToLarge() { _scaleSmallImagesToLarge.update { !it } registerChanges() } fun setQuality(quality: Int) { _quality.update { quality } registerChanges() } fun selectPreset(preset: Preset.Percentage) { _presetSelected.update { preset } registerChanges() } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.createPdf( imageUris = uris?.map { it.toString() } ?: emptyList(), params = pdfCreationParams ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.createPdf( imageUris = uris?.map { it.toString() } ?: emptyList(), params = pdfCreationParams ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUris: List?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ImagesToPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/MergePdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.merge import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.controls.FileReorderVerticalList import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.merge.screenLogic.MergePdfToolComponent @Composable fun MergePdfToolContent( component: MergePdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUris ), isPickedAlready = !component.initialUris.isNullOrEmpty(), canShowScreenData = component.uris.isNotEmpty(), title = stringResource(R.string.merge_pdf), controls = { val addFilesPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::addUris ) FileReorderVerticalList( files = component.uris, onReorder = component::setUris, onNeedToAddFile = addFilesPicker::pickFile, onNeedToRemoveFileAt = component::removeAt ) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/merge/screenLogic/MergePdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.merge.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class MergePdfToolComponent @AssistedInject internal constructor( @Assisted val initialUris: List?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(!initialUris.isNullOrEmpty()) override val haveChanges: Boolean by _haveChanges private val _uris: MutableState> = mutableStateOf(initialUris.orEmpty()) val uris by _uris fun setUris(uris: List) { registerChanges() _uris.update { uris } } fun addUris(uris: List) { _uris.update { (it + uris).distinct() } } fun removeAt(index: Int) { _uris.update { it.toMutableList().apply { removeAt(index) } } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.mergePdfs(uris.map(Uri::toString)) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess(listOf(pdfManager.mergePdfs(uris.map(Uri::toString)).toUri())) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUris: List?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): MergePdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/MetadataPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata.components.MetadataEditor import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata.screenLogic.MetadataPdfToolComponent @Composable fun MetadataPdfToolContent( component: MetadataPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.metadata), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } MetadataEditor( value = component.metadata, onValueChange = component::updateMetadata, deepClean = component.deepClean, onReset = component::resetMetadata, onUpdateDeepClean = component::updateDeepClean ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/components/MetadataEditor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material.icons.rounded.Restore import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Mop import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfMetadata @Composable internal fun MetadataEditor( value: PdfMetadata, onValueChange: (PdfMetadata) -> Unit, onReset: () -> Unit, deepClean: Boolean, onUpdateDeepClean: (Boolean) -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier.container( shape = ShapeDefaults.extraLarge, clip = false ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .padding( top = 16.dp, bottom = 8.dp, start = 16.dp, end = 8.dp ) ) { Text( fontWeight = FontWeight.Medium, text = stringResource(R.string.tags), modifier = Modifier.weight(1f), fontSize = 18.sp, ) Spacer(Modifier.width(16.dp)) EnhancedButton( onClick = onReset, containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, contentPadding = PaddingValues( start = 8.dp, end = 12.dp ), modifier = Modifier .padding(start = 8.dp, end = 8.dp) .height(30.dp), ) { Row { Icon( imageVector = Icons.Rounded.Restore, contentDescription = "Restore", modifier = Modifier.size(20.dp) ) Spacer(Modifier.width(4.dp)) Text( stringResource(R.string.reset) ) } } } PreferenceRowSwitch( title = stringResource(R.string.privacy_deep_clean), subtitle = stringResource(R.string.privacy_deep_clean_sub), checked = deepClean, onClick = onUpdateDeepClean, startIcon = Icons.Rounded.Mop, shape = ShapeDefaults.default, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier.padding(8.dp) ) val previewValue = value.takeIf { !deepClean } Box { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .padding(8.dp) .alpha( animateFloatAsState( if (deepClean) 0.5f else 1f ).value ) ) { MetadataField( shape = ShapeDefaults.top, value = previewValue?.title.orEmpty(), onValueChange = { onValueChange(value.copy(title = it)) }, label = stringResource(R.string.title) ) MetadataField( shape = ShapeDefaults.center, value = previewValue?.author.orEmpty(), onValueChange = { onValueChange(value.copy(author = it)) }, label = stringResource(R.string.author) ) MetadataField( shape = ShapeDefaults.center, value = previewValue?.subject.orEmpty(), onValueChange = { onValueChange(value.copy(subject = it)) }, label = stringResource(R.string.subject) ) MetadataField( shape = ShapeDefaults.center, value = previewValue?.keywords.orEmpty(), onValueChange = { onValueChange(value.copy(keywords = it)) }, label = stringResource(R.string.keywords) ) MetadataField( shape = ShapeDefaults.center, value = previewValue?.creator.orEmpty(), onValueChange = { onValueChange(value.copy(creator = it)) }, label = stringResource(R.string.creator) ) MetadataField( shape = ShapeDefaults.bottom, value = previewValue?.producer.orEmpty(), onValueChange = { onValueChange(value.copy(producer = it)) }, label = stringResource(R.string.producer) ) } if (deepClean) { Surface( modifier = Modifier.matchParentSize(), color = Color.Transparent ) { } } } } } @Composable private fun MetadataField( shape: Shape, value: String, onValueChange: (String) -> Unit, label: String ) { RoundedTextField( modifier = Modifier .container( shape = shape, color = MaterialTheme.colorScheme.surface, resultPadding = 8.dp ), value = value, endIcon = { if (value.isNotEmpty()) { EnhancedIconButton( onClick = { onValueChange("") }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.Cancel, contentDescription = stringResource(R.string.cancel), tint = MaterialTheme.colorScheme.onSecondaryContainer ) } } }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), singleLine = false, onValueChange = onValueChange, label = { Text(label) } ) } @Composable @EnPreview private fun Preview() = ImageToolboxThemeForPreview(true) { MetadataEditor( value = PdfMetadata(), onValueChange = {}, onReset = {}, deepClean = false, onUpdateDeepClean = {}, modifier = Modifier.padding(16.dp) ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/metadata/screenLogic/MetadataPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfMetadata import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class MetadataPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _metadata: MutableState = mutableStateOf(PdfMetadata()) val metadata by _metadata private val _deepClean: MutableState = mutableStateOf(false) val deepClean by _deepClean override fun getKey(): Pair = "metadata" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it }, onSuccess = { newUri -> doSharing( action = { pdfManager.getPdfMetadata(newUri.toString()) }, onSuccess = { metadata -> _metadata.update { metadata } }, onFailure = { _metadata.update { PdfMetadata() } } ) _deepClean.update { false } } ) } fun updateMetadata(metadata: PdfMetadata) { registerChanges() _metadata.update { metadata } } fun updateDeepClean(value: Boolean) { registerChanges() _deepClean.update { value } } fun resetMetadata() { setUri(uri) } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.changePdfMetadata( uri = _uri.value.toString(), metadata = metadata.takeIf { !deepClean }?.copy( producer = metadata.producer.orEmpty().ifEmpty { "ImageToolbox" } ) ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.changePdfMetadata( uri = _uri.value.toString(), metadata = metadata.takeIf { !deepClean }?.copy( producer = metadata.producer.orEmpty().ifEmpty { "ImageToolbox" } ) ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): MetadataPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/OCRPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.ocr import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FilePresent import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.ocr.screenLogic.OCRPdfToolComponent @Composable fun OCRPdfToolContent( component: OCRPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.pdf_to_text), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } PreferenceItem( title = stringResource(R.string.deep_ocr), subtitle = stringResource(R.string.deep_ocr_sub), startIcon = Icons.Outlined.FilePresent, modifier = Modifier.fillMaxWidth(), onClick = component::navigateToOcr ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/ocr/screenLogic/OCRPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.ocr.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class OCRPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val fileController: FileController, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges override val extraDataType: ExtraDataType = ExtraDataType.File override val mimeType: MimeType.Single = MimeType.Txt private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } override fun createTargetFilename(): String = "${uri?.filename()?.substringBeforeLast('.') ?: timestamp()}_extracted.txt" fun navigateToOcr() { doSharing( action = { pdfManager.extractPagesFromPdf(uri.toString()) }, onSuccess = { uris -> onNavigate( Screen.RecognizeText(Screen.RecognizeText.Type.WriteToFile(uris.map { it.toUri() })) ) }, onFailure = { AppToastHost.showFailureToast(it) _uri.value = null registerChangesCleared() } ) } override fun saveTo( uri: Uri ) { doSaving { val processed = stripText() fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(processed) } ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { val processed = stripText() shareProvider.cacheData( filename = createTargetFilename(), writeData = { it.writeBytes(processed) } )?.toUri()?.let { onSuccess(listOf(it)) registerSave() } }, onFailure = onFailure ) } private suspend fun stripText(): ByteArray = pdfManager.stripText( uri = _uri.value.toString() ).mapIndexed { index, text -> "--- ${getString(R.string.page)} ${index + 1} ---\n$text\n\n" }.joinToString("").encodeToByteArray() @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): OCRPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/PageNumbersPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PositionSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers.components.PageNumbersPreview import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers.screenLogic.PageNumbersPdfToolComponent @Composable fun PageNumbersPdfToolContent( component: PageNumbersPdfToolComponent ) { val pageCount by rememberPdfPages(component.uri) val params = component.params BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.page_numbers), actions = {}, imagePreview = { PageNumbersPreview( uri = component.uri, params = params, pageCount = pageCount ) }, placeImagePreview = true, showImagePreviewAsStickyHeader = true, controls = { RoundedTextField( modifier = Modifier .padding(top = 8.dp) .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = params.labelFormat, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), singleLine = false, onValueChange = { component.updateParams( params.copy( labelFormat = it ) ) }, label = { Text(stringResource(R.string.label_format)) } ) Spacer(Modifier.height(8.dp)) PositionSelector( value = params.position, onValueChange = { component.updateParams( params.copy( position = it ) ) }, color = Color.Unspecified ) Spacer(Modifier.height(8.dp)) ColorRowSelector( value = Color(params.color), onValueChange = { component.updateParams( params.copy( color = it.toArgb() ) ) }, title = stringResource(R.string.text_color), modifier = Modifier.container( shape = ShapeDefaults.large ) ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/components/PageNumbersPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers.components import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfPageNumbersParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PageSwitcher @Composable internal fun PageNumbersPreview( uri: Uri?, params: PdfPageNumbersParams, pageCount: Int ) { PageSwitcher( activePages = null, pageCount = pageCount ) { page -> Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip( alignment = Alignment.Center ), contentAlignment = Alignment.Center ) { var aspectRatio by rememberSaveable { mutableFloatStateOf(1f) } val previewText = params.labelFormat .replace("{n}", (page + 1).toString()) .replace("{total}", pageCount.toString()) val previewAlignment = when (params.position) { Position.TopLeft -> Alignment.TopStart Position.TopCenter -> Alignment.TopCenter Position.TopRight -> Alignment.TopEnd Position.CenterLeft -> Alignment.CenterStart Position.Center -> Alignment.Center Position.CenterRight -> Alignment.CenterEnd Position.BottomLeft -> Alignment.BottomStart Position.BottomCenter -> Alignment.BottomCenter Position.BottomRight -> Alignment.BottomEnd } Box( modifier = Modifier.aspectRatio(aspectRatio) ) { Picture( model = remember(uri, page) { PdfImageRequest( data = uri, pdfPage = page ) }, contentScale = ContentScale.FillBounds, modifier = Modifier.matchParentSize(), onSuccess = { aspectRatio = it.result.image.safeAspectRatio }, shape = MaterialTheme.shapes.medium ) BoxWithConstraints( modifier = Modifier.matchParentSize() ) { AutoSizeText( key = { maxWidth }, text = previewText, modifier = Modifier .align(previewAlignment) .padding(8.dp), style = MaterialTheme.typography.labelSmall, lineHeight = 11.sp, color = Color(params.color) ) } } } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/page_numbers/screenLogic/PageNumbersPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfPageNumbersParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class PageNumbersPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _params: MutableState = mutableStateOf(PdfPageNumbersParams()) val params by _params override fun getKey(): Pair = "numbered" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateParams(params: PdfPageNumbersParams) { registerChanges() _params.update { params } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.addPageNumbers( uri = _uri.value.toString(), params = params ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.addPageNumbers( uri = _uri.value.toString(), params = params ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): PageNumbersPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/PreviewPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.preview import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Search import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.rememberFilename import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.canUseNewPdf import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.preview.screenLogic.PreviewPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.components.PdfViewer @Composable fun PreviewPdfToolContent( component: PreviewPdfToolComponent ) { val isPortrait by isPortraitOrientationAsState() var isSearching by remember(component.uri) { mutableStateOf(false) } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = component.uri?.let { rememberFilename(it) }.orEmpty().ifEmpty { stringResource(R.string.preview_pdf) }, actions = {}, forceImagePreviewToMax = component.uri != null, placeControlsSeparately = true, canShare = true, canSave = false, topAppBarPersistentActions = { if (component.uri != null && canUseNewPdf()) { EnhancedIconButton( onClick = { isSearching = !isSearching } ) { Icon( imageVector = Icons.Outlined.Search, contentDescription = stringResource(R.string.search_here) ) } } }, drawBottomShadow = !isSearching, controls = { if (rememberPdfPages(component.uri).value > 0) { PdfViewer( modifier = Modifier .fillMaxSize() .padding(bottom = if (isPortrait) 104.dp else 0.dp), uri = component.uri, contentPadding = PaddingValues(), isSearching = isSearching ) } }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/preview/screenLogic/PreviewPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.preview.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class PreviewPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri fun setUri(uri: Uri?) { registerChangesCleared() _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } override fun saveTo( uri: Uri ) = Unit override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { shareProvider.cacheData( writeData = { writeable -> fileController.transferBytes( fromUri = _uri.value.toString(), to = writeable ) }, filename = _uri.value?.filename() ?: createTargetFilename() )?.let { onSuccess(listOf(it.toUri())) registerSave() } }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): PreviewPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/PrintPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.print import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Receipt import androidx.compose.material.icons.outlined.ViewWeek import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.derivative.OnlyAllowedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PageOrientation import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PageSize import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PrintPdfParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.print.screenLogic.PrintPdfToolComponent import kotlin.math.roundToInt @Composable fun PrintPdfToolContent( component: PrintPdfToolComponent ) { val params = component.params BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.print_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } QualitySelector( imageFormat = ImageFormat.Jpg, quality = Quality.Base((params.quality * 100).roundToInt()), onQualityChange = { component.updateParams( params.copy(quality = it.qualityValue / 100f) ) }, autoCoerce = false ) Spacer(Modifier.height(8.dp)) OnlyAllowedSliderItem( label = stringResource(id = R.string.pages_per_sheet), icon = Icons.Outlined.ViewWeek, value = component.params.pagesPerSheet, allowed = PrintPdfParams.pagesMapping.keys, onValueChange = { component.updateParams(params.copy(pagesPerSheet = it)) }, valueSuffix = "" ) Spacer(Modifier.height(8.dp)) DataSelector( value = component.params.pageSize, onValueChange = { component.updateParams(params.copy(pageSize = it)) }, entries = remember { listOf(PageSize.Auto) + PageSize.entries }, spanCount = 3, initialExpanded = true, title = stringResource(R.string.page_size), titleIcon = Icons.Outlined.Receipt, itemContentText = { value -> value.name.orEmpty().ifBlank { stringResource(R.string.auto) } }, shape = ShapeDefaults.large ) Spacer(Modifier.height(8.dp)) EnhancedButtonGroup( modifier = Modifier .container(ShapeDefaults.large), title = stringResource(id = R.string.orientation), entries = PageOrientation.entries, value = component.params.orientation, onValueChange = { component.updateParams(params.copy(orientation = it)) }, itemContent = { value -> Text( stringResource( when (value) { PageOrientation.ORIGINAL -> R.string.original PageOrientation.VERTICAL -> R.string.vertical PageOrientation.HORIZONTAL -> R.string.horizontal } ) ) } ) Spacer(Modifier.height(8.dp)) EnhancedSliderItem( value = params.marginPercent, title = stringResource(id = R.string.margin), internalStateTransformation = { it.roundToInt() }, onValueChange = { component.updateParams(params.copy(marginPercent = it)) }, valueRange = 0f..50f, shape = ShapeDefaults.large, valueSuffix = "%" ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/print/screenLogic/PrintPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.print.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PrintPdfParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class PrintPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _params: MutableState = mutableStateOf(PrintPdfParams()) val params by _params override fun getKey(): Pair = "printed" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateParams(params: PrintPdfParams) { registerChanges() _params.update { params } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.printPdf( uri = _uri.value.toString(), params = params ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.printPdf( uri = _uri.value.toString(), params = params ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): PrintPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/ProtectPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.protect import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.protect.screenLogic.ProtectPdfToolComponent @Composable fun ProtectPdfToolContent( component: ProtectPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.protect_pdf), canSave = component.password.isNotEmpty(), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = component.password, startIcon = { EnhancedIconButton( onClick = { component.updatePassword(component.generateRandomPassword()) }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.Rounded.Shuffle, contentDescription = stringResource(R.string.shuffle), tint = MaterialTheme.colorScheme.onSecondaryContainer ) } }, endIcon = { EnhancedIconButton( onClick = { component.updatePassword("") }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Outlined.Cancel, contentDescription = stringResource(R.string.cancel), tint = MaterialTheme.colorScheme.onSecondaryContainer ) } }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), singleLine = false, onValueChange = component::updatePassword, label = { Text(stringResource(R.string.password)) } ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/protect/screenLogic/ProtectPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.protect.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.RandomStringGenerator import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class ProtectPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, private val randomStringGenerator: RandomStringGenerator, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _password: MutableState = mutableStateOf("") val password by _password override fun getKey(): Pair = "protected" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updatePassword(password: String) { registerChanges() _password.update { password } } fun generateRandomPassword(): String = randomStringGenerator.generate(18) override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.protectPdf( uri = _uri.value.toString(), password = password ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.protectPdf( uri = _uri.value.toString(), password = password ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ProtectPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/RearrangePdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.components.PageData import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.components.PdfPagesRearrangeGrid import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.screenLogic.RearrangePdfToolComponent @Composable fun RearrangePdfToolContent( component: RearrangePdfToolComponent ) { val pagesCount by rememberPdfPages(component.uri) LaunchedEffect(pagesCount, component.pages) { if (pagesCount > 0 && (component.pages.isEmpty() || component.pages.size != pagesCount)) { component.updatePages( List(pagesCount) { PageData( index = it, request = PdfImageRequest( data = component.uri, pdfPage = it ) ) } ) } } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.rearrange_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } PdfPagesRearrangeGrid( pages = component.pages, onReorder = component::updatePages ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/components/PdfPagesRearrangeGrid.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Restore import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import coil3.request.ImageRequest import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.utils.appContext import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyGridState @Composable internal fun PdfPagesRearrangeGrid( pages: List?, modifier: Modifier = Modifier .container( shape = ShapeDefaults.extraLarge, clip = false ), onReorder: (List) -> Unit, title: String = stringResource(R.string.hold_drag_drop), coerceHeight: Boolean = true ) { val data = remember(pages) { mutableStateOf(pages ?: emptyList()) } val haptics = LocalHapticFeedback.current val listState = rememberLazyGridState() val state = rememberReorderableLazyGridState( lazyGridState = listState, onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } } ) Column( modifier = modifier .then( if (coerceHeight) { Modifier .heightIn( max = LocalWindowInfo.current.containerDpSize.height * 0.7f ) } else { Modifier } ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .padding( top = 16.dp, bottom = 8.dp, start = 16.dp, end = 8.dp ) ) { Text( fontWeight = FontWeight.Medium, text = title, modifier = Modifier.weight(1f), fontSize = 18.sp, ) Spacer(Modifier.width(16.dp)) EnhancedButton( onClick = { onReorder(data.value.sortedBy { it.index }) }, containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, contentPadding = PaddingValues( start = 8.dp, end = 12.dp ), modifier = Modifier .padding(start = 8.dp, end = 8.dp) .height(30.dp), ) { Row { Icon( imageVector = Icons.Rounded.Restore, contentDescription = "Restore", modifier = Modifier.size(20.dp) ) Spacer(Modifier.width(4.dp)) Text( stringResource(R.string.reset) ) } } } Box( modifier = Modifier.weight(1f, false) ) { LazyVerticalGrid( state = listState, modifier = Modifier .fadingEdges( scrollableState = listState, isVertical = true ) .animateContentSizeNoClip(), contentPadding = PaddingValues(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior(), columns = GridCells.Adaptive(minSize = 150.dp) ) { items( items = data.value, key = { uri -> uri.toString() + uri.hashCode() } ) { uri -> ReorderableItem( state = state, key = uri.toString() + uri.hashCode(), ) { isDragging -> val alpha by animateFloatAsState(if (isDragging) 0.3f else 0.6f) Box( modifier = Modifier .fillMaxWidth() .aspectRatio(1f) .container( color = MaterialTheme.colorScheme.surface, resultPadding = 8.dp ) .container( shape = ShapeDefaults.small, color = Color.Transparent, resultPadding = 0.dp ) .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { onReorder(data.value) } ) ) { Picture( model = uri.request, modifier = Modifier.fillMaxSize(), shape = RectangleShape, contentScale = ContentScale.Inside ) Box( modifier = Modifier .matchParentSize() .background( MaterialTheme.colorScheme.surfaceContainer.copy(alpha) ), contentAlignment = Alignment.Center ) { Text( text = "${uri.index + 1}", color = MaterialTheme.colorScheme.onSurface, fontSize = 20.sp, fontWeight = FontWeight.Bold ) } } } } } } } } internal data class PageData( val index: Int, val request: ImageRequest ) @Composable @EnPreview private fun Preview() = ImageToolboxThemeForPreview(true) { var files by remember { mutableStateOf( List(15) { PageData( index = it, request = ImageRequest.Builder(appContext).data("file:///uri_$it.pdf".toUri()) .build() ) } ) } LazyColumn { item { PdfPagesRearrangeGrid( pages = files, onReorder = { files = it } ) } items(30) { Text("TEST $it") } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rearrange/screenLogic/RearrangePdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.components.PageData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class RearrangePdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _pages: MutableState> = mutableStateOf(emptyList()) internal val pages by _pages override fun getKey(): Pair = "rearranged" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } internal fun updatePages(pages: List) { registerChanges() _pages.update { pages } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.rearrangePdf( uri = _uri.value.toString(), newOrder = pages.map { it.index } ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.rearrangePdf( uri = _uri.value.toString(), newOrder = pages.map { it.index } ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): RearrangePdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/RemoveAnnotationsPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.controls.page.PageSelectionItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.components.PdfAnnotationTypeSelector import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.components.RemoveAnnotationsPreview import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.screenLogic.RemoveAnnotationsPdfToolComponent @Composable fun RemoveAnnotationsPdfToolContent( component: RemoveAnnotationsPdfToolComponent ) { val pageCount by rememberPdfPages(component.uri) val params = component.params LaunchedEffect(pageCount, params.pages) { if (params.pages == null && pageCount > 0) { component.updateParams( params.copy( pages = List(pageCount) { it } ) ) } } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canSave = params.types.isNotEmpty(), canShowScreenData = component.uri != null, title = stringResource(R.string.remove_annotations), imagePreview = { RemoveAnnotationsPreview( uri = component.uri, params = params, pageCount = pageCount ) }, placeImagePreview = true, showImagePreviewAsStickyHeader = true, controls = { PageSelectionItem( value = params.pages, onValueChange = { component.updateParams(params.copy(pages = it)) }, pageCount = pageCount ) Spacer(Modifier.height(16.dp)) PdfAnnotationTypeSelector( values = params.types, onValueChange = { newTypes -> component.updateParams( params.copy( types = newTypes ) ) } ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/PdfAnnotationTypeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfAnnotationType @Composable internal fun PdfAnnotationTypeSelector( values: Set, onValueChange: (Set) -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier .fillMaxWidth() .container( shape = ShapeDefaults.extraLarge ), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) { Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.annotations), modifier = Modifier .fillMaxWidth() .padding(bottom = 4.dp), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) FlowRow( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterHorizontally ), modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ) .padding(horizontal = 8.dp, vertical = 12.dp) ) { PdfAnnotationType.entries.forEach { type -> val selected = type in values EnhancedChip( selected = selected, onClick = { onValueChange(values.toggle(type)) }, selectedColor = MaterialTheme.colorScheme.primary, contentPadding = PaddingValues( horizontal = 12.dp, vertical = 8.dp ), modifier = Modifier.height(36.dp) ) { Text(stringResource(type.title())) } } } Spacer(Modifier.height(4.dp)) } } private fun PdfAnnotationType.title(): Int = when (this) { PdfAnnotationType.Link -> R.string.annotation_link PdfAnnotationType.FileAttachment -> R.string.annotation_file_attachment PdfAnnotationType.Line -> R.string.annotation_line PdfAnnotationType.Popup -> R.string.annotation_popup PdfAnnotationType.Stamp -> R.string.annotation_stamp PdfAnnotationType.SquareCircle -> R.string.annotation_shapes PdfAnnotationType.Text -> R.string.annotation_text PdfAnnotationType.TextMarkup -> R.string.annotation_text_markup PdfAnnotationType.Widget -> R.string.annotation_widget PdfAnnotationType.Markup -> R.string.annotation_markup PdfAnnotationType.Unknown -> R.string.annotation_unknown } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/components/RemoveAnnotationsPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.components import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfRemoveAnnotationParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PageSwitcher @Composable internal fun RemoveAnnotationsPreview( uri: Uri?, params: PdfRemoveAnnotationParams, pageCount: Int ) { PageSwitcher( activePages = params.pages, pageCount = pageCount ) { page -> Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip( alignment = Alignment.Center ), contentAlignment = Alignment.Center ) { var aspectRatio by rememberSaveable { mutableFloatStateOf(1f) } Box( modifier = Modifier .aspectRatio(aspectRatio) .clip(MaterialTheme.shapes.small) ) { Picture( model = remember(uri, page) { PdfImageRequest( data = uri, pdfPage = page ) }, contentScale = ContentScale.FillBounds, modifier = Modifier.matchParentSize(), onSuccess = { aspectRatio = it.result.image.safeAspectRatio }, shape = RectangleShape ) } } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_annotations/screenLogic/RemoveAnnotationsPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshotFlow import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfRemoveAnnotationParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.distinctUntilChanged class RemoveAnnotationsPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _params: MutableState = mutableStateOf(PdfRemoveAnnotationParams()) val params by _params override fun getKey(): Pair = "annotations_removed" to uri init { componentScope.launch { snapshotFlow { uri } .distinctUntilChanged() .collect { _params.update { it.copy(pages = null) } } } } fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateParams(params: PdfRemoveAnnotationParams) { _params.update { params } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.removeAnnotations( uri = _uri.value.toString(), params = params ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.removeAnnotations( uri = _uri.value.toString(), params = params ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): RemoveAnnotationsPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/RemovePagesPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages.components.PdfPagesRemoveGrid import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages.screenLogic.RemovePagesPdfToolComponent @Composable fun RemovePagesPdfToolContent( component: RemovePagesPdfToolComponent ) { val pagesCount by rememberPdfPages(component.uri) BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.remove_pages_pdf), canSave = component.pagesToDelete.size < pagesCount, controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } PdfPagesRemoveGrid( pages = remember(pagesCount, component.uri) { List(pagesCount) { PdfImageRequest( data = component.uri, pdfPage = it ) } }, pagesToDelete = component.pagesToDelete, onClearAll = { component.updatePages(emptyList()) }, onClickPage = { component.updatePages( component.pagesToDelete.toggle(it) ) }, onUpdatePages = component::updatePages, pagesCount = pagesCount ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/components/PdfPagesRemoveGrid.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages.components import androidx.compose.animation.animateColor import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.rounded.Pages import androidx.compose.material.icons.rounded.Restore import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.widget.buttons.MediaCheckBox import com.t8rin.imagetoolbox.core.ui.widget.controls.page.PageInputDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable internal fun PdfPagesRemoveGrid( pages: List, modifier: Modifier = Modifier .container( shape = ShapeDefaults.extraLarge, clip = false ), onUpdatePages: (List) -> Unit, onClearAll: () -> Unit, pagesToDelete: List, onClickPage: (Int) -> Unit, title: String = stringResource(R.string.pages_selection), coerceHeight: Boolean = true, pagesCount: Int ) { var showPageSelector by rememberSaveable { mutableStateOf(false) } Column( modifier = modifier .then( if (coerceHeight) { Modifier .heightIn( max = LocalWindowInfo.current.containerDpSize.height * 0.7f ) } else { Modifier } ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .padding( top = 16.dp, bottom = 8.dp, start = 16.dp, end = 8.dp ) ) { Text( fontWeight = FontWeight.Medium, text = title, modifier = Modifier.weight(1f), fontSize = 18.sp, ) Spacer(Modifier.width(16.dp)) EnhancedButton( onClick = { showPageSelector = true }, containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, contentColor = MaterialTheme.colorScheme.onSurfaceVariant, contentPadding = PaddingValues( start = 8.dp, end = 10.dp ), modifier = Modifier .padding(start = 8.dp) .height(30.dp), ) { Row( verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Rounded.Pages, contentDescription = "manually", modifier = Modifier.size(20.dp) ) Spacer(Modifier.width(4.dp)) Text( stringResource(R.string.manually) ) } } EnhancedButton( onClick = onClearAll, containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, contentPadding = PaddingValues( start = 8.dp, end = 12.dp ), modifier = Modifier .padding(start = 8.dp, end = 8.dp) .height(30.dp), ) { Row { Icon( imageVector = Icons.Rounded.Restore, contentDescription = "Restore", modifier = Modifier.size(20.dp) ) Spacer(Modifier.width(4.dp)) Text( stringResource(R.string.reset) ) } } } Box( modifier = Modifier.weight(1f, false) ) { val listState = rememberLazyGridState() LazyVerticalGrid( state = listState, modifier = Modifier .fadingEdges( scrollableState = listState, isVertical = true ) .animateContentSizeNoClip(), contentPadding = PaddingValues(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior(), columns = GridCells.Adaptive(minSize = 150.dp) ) { itemsIndexed( items = pages, key = { _, uri -> uri.toString() + uri.hashCode() } ) { index, uri -> Box( modifier = Modifier .fillMaxWidth() .aspectRatio(1f) .container( color = MaterialTheme.colorScheme.errorContainer, resultPadding = 0.dp ) ) { val transition = updateTransition(index in pagesToDelete) Box( modifier = Modifier .padding( transition.animateDp { if (it) 12.dp else 0.dp }.value ) .container( color = MaterialTheme.colorScheme.surface, shape = AutoCornersShape( transition.animateDp { if (it) 8.dp else 16.dp }.value ), resultPadding = 8.dp ) .container( shape = AutoCornersShape( transition.animateDp { if (it) 4.dp else 12.dp }.value ), color = Color.Transparent, resultPadding = 0.dp ) .hapticsClickable { onClickPage(index) } ) { Picture( model = uri, modifier = Modifier .fillMaxSize() .transparencyChecker(), showTransparencyChecker = false, shape = RectangleShape, contentScale = ContentScale.Inside ) Box( modifier = Modifier .matchParentSize() .background( MaterialTheme.colorScheme.surfaceContainer.copy(0.6f) ), contentAlignment = Alignment.Center ) { Text( text = "${index + 1}", color = MaterialTheme.colorScheme.onSurface, fontSize = 20.sp, fontWeight = FontWeight.Bold ) } } Box( modifier = Modifier .fillMaxWidth() .padding( transition.animateDp { if (it) 6.dp else 12.dp }.value ) ) { MediaCheckBox( isChecked = transition.targetState, uncheckedColor = White.copy(0.8f), checkedColor = MaterialTheme.colorScheme.error, checkedIcon = Icons.Filled.CheckCircle, modifier = Modifier .clip(ShapeDefaults.circle) .background( transition.animateColor { if (it) MaterialTheme.colorScheme.errorContainer else Color.Transparent }.value ) ) } } } } } } PageInputDialog( visible = showPageSelector, onDismiss = { showPageSelector = false }, value = pagesToDelete, onValueChange = onUpdatePages, pagesCount = pagesCount ) } @Composable @EnPreview private fun Preview() = ImageToolboxThemeForPreview(true) { var files by remember { mutableStateOf( List(15) { "file:///uri_$it.pdf".toUri() } ) } var rotations by remember(files) { mutableStateOf( emptyList() ) } LazyColumn { item { PdfPagesRemoveGrid( pages = files, pagesToDelete = rotations, onClearAll = { rotations = emptyList() }, onClickPage = { rotations = rotations.toggle(it) }, onUpdatePages = { rotations = it }, pagesCount = 100 ) } items(30) { Text("TEST $it") } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/remove_pages/screenLogic/RemovePagesPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class RemovePagesPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _pagesToDelete: MutableState> = mutableStateOf(emptyList()) val pagesToDelete by _pagesToDelete override fun getKey(): Pair = "removed" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updatePages(pages: List) { registerChanges() _pagesToDelete.update { pages } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.removePdfPages( uri = _uri.value.toString(), pages = pagesToDelete ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.removePdfPages( uri = _uri.value.toString(), pages = pagesToDelete ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): RemovePagesPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/RepairPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.repair import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.repair.screenLogic.RepairPdfToolComponent @Composable fun RepairPdfToolContent( component: RepairPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.repair_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } InfoContainer( text = stringResource(R.string.repair_info) ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/repair/screenLogic/RepairPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.repair.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class RepairPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri override fun getKey(): Pair = "repaired" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.repairPdf( uri = _uri.value.toString() ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.repairPdf( uri = _uri.value.toString() ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): RepairPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/RootPdfToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.plus import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.screenLogic.RootPdfToolsComponent @Composable fun RootPdfToolsContent( component: RootPdfToolsComponent ) { var tempSelectionUri by rememberSaveable { mutableStateOf(null) } var showSelectionPdfPicker by rememberSaveable { mutableStateOf(false) } LaunchedEffect(showSelectionPdfPicker) { if (!showSelectionPdfPicker) tempSelectionUri = null } val selectionPdfPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = { uri: Uri -> tempSelectionUri = uri showSelectionPdfPicker = true } ) AdaptiveLayoutScreen( title = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.pdf_tools), textAlign = TextAlign.Center ) EnhancedBadge( content = { Text( text = Screen.PdfTools.options.size.toString() ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } }, topAppBarPersistentActions = { TopAppBarEmoji() }, onGoBack = component.onGoBack, shouldDisableBackHandler = true, buttons = {}, controls = { Scaffold( bottomBar = { BottomButtonsBlock( isNoData = true, showNullDataButtonAsContainer = true, isPrimaryButtonVisible = false, onSecondaryButtonClick = selectionPdfPicker::pickFile, onPrimaryButtonClick = { }, actions = { }, secondaryButtonIcon = Icons.Rounded.FileOpen, secondaryButtonText = stringResource(R.string.pick_file) ) }, contentWindowInsets = WindowInsets.systemBars .union(WindowInsets.displayCutout) .only(WindowInsetsSides.Start) ) { contentPadding -> LazyVerticalStaggeredGrid( modifier = Modifier.fillMaxHeight(), columns = StaggeredGridCells.Adaptive(300.dp), horizontalArrangement = Arrangement.spacedBy( space = 12.dp, alignment = Alignment.CenterHorizontally ), verticalItemSpacing = 12.dp, contentPadding = contentPadding + PaddingValues(16.dp), flingBehavior = enhancedFlingBehavior() ) { items(Screen.PdfTools.options) { screen -> PreferenceItem( title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), startIcon = screen.icon, modifier = Modifier.fillMaxWidth(), onClick = { component.onNavigate(screen) } ) } } } }, imagePreview = {}, placeImagePreview = false, showImagePreviewAsStickyHeader = false, placeControlsSeparately = true, canShowScreenData = true, noDataControls = {}, actions = {} ) EnhancedModalBottomSheet( visible = showSelectionPdfPicker, onDismiss = { showSelectionPdfPicker = it }, confirmButton = { EnhancedButton( onClick = { showSelectionPdfPicker = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(id = R.string.close)) } }, sheetContent = { if (tempSelectionUri == null) showSelectionPdfPicker = false LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(250.dp), horizontalArrangement = Arrangement.spacedBy( space = 12.dp, alignment = Alignment.CenterHorizontally ), verticalItemSpacing = 12.dp, contentPadding = PaddingValues(12.dp), flingBehavior = enhancedFlingBehavior() ) { items(Screen.PdfTools.options) { screen -> PreferenceItem( title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), startIcon = screen.icon, modifier = Modifier.fillMaxWidth(), onClick = { showSelectionPdfPicker = false component.navigate( screen = screen, tempSelectionUri = tempSelectionUri ) } ) } } }, title = { TitleItem( text = stringResource(id = R.string.pick_file), icon = Icons.Rounded.FileOpen ) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/PdfViewer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.components import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.canUseNewPdf import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.components.viewer.LegacyPdfViewer import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.components.viewer.ModernPdfViewer @Composable fun PdfViewer( uri: Uri?, modifier: Modifier, spacing: Dp = 8.dp, contentPadding: PaddingValues = PaddingValues(start = 20.dp, end = 20.dp), isSearching: Boolean = false ) { AnimatedContent( targetState = uri ) { uri -> if (uri != null) { if (canUseNewPdf()) { ModernPdfViewer( uri = uri, isSearching = isSearching, modifier = modifier ) } else { LegacyPdfViewer( uri = uri, spacing = spacing, contentPadding = contentPadding, modifier = modifier ) } } else { Box( modifier = modifier .fillMaxSize() .animateContentSizeNoClip(), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/LegacyPdfViewer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.components.viewer import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import coil3.Image import coil3.asImage import coil3.imageLoader import coil3.memory.MemoryCache import coil3.request.ImageRequest import coil3.toBitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.flexibleResize import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.pdf_tools.data.utils.PdfRenderer import com.t8rin.logger.makeLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import my.nanihadesuka.compose.LazyColumnScrollbar import my.nanihadesuka.compose.ScrollbarSelectionMode import my.nanihadesuka.compose.ScrollbarSettings import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable import kotlin.math.sqrt @Composable internal fun LegacyPdfViewer( uri: Uri, spacing: Dp = 8.dp, contentPadding: PaddingValues = PaddingValues(start = 20.dp, end = 20.dp), modifier: Modifier = Modifier ) { val showError: (Throwable) -> Unit = { it.makeLog("PdfViewer") AppToastHost.showFailureToast(it) } val listState = rememberLazyListState() BoxWithConstraints(modifier = modifier.animateContentSizeNoClip()) { val density = LocalDensity.current val width = with(density) { this@BoxWithConstraints.maxWidth.toPx() }.toInt() val height = (width * sqrt(2f)).toInt() val rendererScope = rememberCoroutineScope() val mutex = remember { Mutex() } val pagesSize = remember { mutableStateListOf() } val renderer by produceState(null, uri) { rendererScope.launch(Dispatchers.IO) { runCatching { mutex.withLock { pagesSize.clear() val renderer = PdfRenderer( uri = uri.toString(), onFailure = showError )?.also { repeat(it.pageCount) { index -> it.openPage(index).let { page -> val size = IntegerSize( width = page.width, height = page.height ).flexibleResize(width, height) pagesSize.add(size) } } } value = renderer } }.onFailure(showError) } awaitDispose { val currentRenderer = value rendererScope.launch(Dispatchers.IO) { mutex.withLock { currentRenderer?.close() } } } } val pageCount by remember(renderer) { derivedStateOf { renderer?.pageCount ?: 0 } } LazyColumnScrollbar( state = listState, settings = ScrollbarSettings( thumbUnselectedColor = MaterialTheme.colorScheme.primary, thumbSelectedColor = MaterialTheme.colorScheme.primary, scrollbarPadding = 0.dp, thumbThickness = 10.dp, selectionMode = ScrollbarSelectionMode.Full, thumbShape = AutoCornersShape( topStartPercent = 100, bottomStartPercent = 100 ), hideDelayMillis = 1500 ), indicatorContent = { index, _ -> val text by remember(index, pageCount, listState) { derivedStateOf { val first = listState.layoutInfo.visibleItemsInfo.firstOrNull() val last = listState.layoutInfo.visibleItemsInfo.lastOrNull() first?.takeIf { it.index == 0 }?.let { 1 } ?: ((last?.index ?: index) + 1) } } Text( text = "$text / $pageCount", modifier = Modifier .padding(6.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.secondaryContainer ) .padding( start = 6.dp, end = 6.dp, top = 2.dp, bottom = 2.dp ), color = MaterialTheme.colorScheme.onSecondaryContainer ) } ) { LazyColumn( state = listState, modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surfaceContainerLow) .clipToBounds() .zoomable( rememberZoomState(10f) ), contentPadding = contentPadding, horizontalAlignment = Alignment.CenterHorizontally, flingBehavior = enhancedFlingBehavior() ) { items( count = pageCount, key = { index -> "$uri-$index" } ) { index -> if (index == 0) { Spacer(Modifier.height(16.dp)) } else Spacer(Modifier.height(spacing)) val cacheKey = MemoryCache.Key("$uri-${pagesSize[index]}-$index") PdfPage( contentScale = ContentScale.Fit, renderWidth = pagesSize[index].width, renderHeight = pagesSize[index].height, index = index, mutex = mutex, renderer = renderer, cacheKey = cacheKey ) if (index == pageCount - 1) { Spacer(Modifier.height(16.dp)) } } } } if (pageCount == 0) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } @Composable private fun PdfPage( contentScale: ContentScale = ContentScale.Crop, modifier: Modifier = Modifier, index: Int, renderWidth: Int, renderHeight: Int, zoom: Float = 1f, mutex: Mutex, renderer: PdfRenderer?, cacheKey: MemoryCache.Key, ) { val imageLoadingScope = rememberCoroutineScope() val cacheValue: Image? = appContext.imageLoader.memoryCache?.get(cacheKey)?.image var bitmap: Image? by remember { mutableStateOf(cacheValue) } if (bitmap == null) { DisposableEffect(cacheKey, index) { val job = imageLoadingScope.launch(Dispatchers.IO) { mutex.withLock { if (!coroutineContext.isActive) return@launch try { renderer?.let { it.openPage(index).let { page -> val originalWidth = page.width val originalHeight = page.height val targetSize = IntegerSize( width = originalWidth, height = originalHeight ).flexibleResize( w = renderWidth, h = renderHeight ) val scaleX = targetSize.width / originalWidth.toFloat() val scaleY = targetSize.height / originalHeight.toFloat() val scale = minOf(scaleX, scaleY) * 1.2f bitmap = renderer.renderImage( index, scale.coerceAtMost(3f).makeLog("PdfDecoder, scale") ).asImage() } } } catch (_: Throwable) { //Just catch and return in case the renderer is being closed return@launch } } } onDispose { job.cancel() } } } val request = remember(renderWidth, renderHeight, bitmap) { ImageRequest.Builder(appContext) .size(renderWidth, renderHeight) .memoryCacheKey(cacheKey) .data(bitmap?.toBitmap()) .build() } val bgColor = MaterialTheme.colorScheme.secondaryContainer val density = LocalDensity.current Box( modifier .clip(ShapeDefaults.extraSmall) .background(bgColor) ) { Picture( modifier = Modifier .then( if (contentScale == ContentScale.Crop) Modifier.matchParentSize() else Modifier ) .width(with(density) { renderWidth.toDp() * zoom }) .aspectRatio(renderWidth / renderHeight.toFloat()) .background(Color.White), shape = RectangleShape, contentScale = contentScale, showTransparencyChecker = false, model = request ) } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.components.viewer import android.net.Uri import android.os.Build import android.os.Bundle import androidx.annotation.RequiresExtension import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.fragment.compose.AndroidFragment import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13) @Composable internal fun ModernPdfViewer( uri: Uri, isSearching: Boolean, modifier: Modifier = Modifier ) { var fragmentReference by remember { mutableStateOf(null) } val loadingState = fragmentReference?.loadingState?.collectAsState()?.value val colorScheme = MaterialTheme.colorScheme LaunchedEffect(colorScheme, fragmentReference) { fragmentReference?.setScheme(colorScheme) } LaunchedEffect(fragmentReference, isSearching) { fragmentReference?.isTextSearchActive = isSearching } AndroidFragment( arguments = Bundle().apply { putParcelable("documentUri", uri) }, modifier = modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surface), onUpdate = { fragmentReference = it } ) if (loadingState == true) { Box( modifier = modifier .fillMaxSize() .animateContentSizeNoClip(), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/components/viewer/ModernPdfViewerDelegate.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.components.viewer import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.ColorFilter import android.graphics.Paint import android.graphics.Path import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build import android.text.TextPaint import android.view.View import android.view.ViewGroup import androidx.annotation.RequiresExtension import androidx.compose.material3.ColorScheme import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.content.ContextCompat import androidx.core.graphics.withScale import androidx.core.graphics.withTranslation import androidx.core.view.updateLayoutParams import androidx.core.widget.ImageViewCompat import androidx.pdf.ExperimentalPdfApi import androidx.pdf.PdfDocument import androidx.pdf.R import androidx.pdf.view.PdfView import androidx.pdf.view.ToolBoxView import androidx.pdf.viewer.fragment.PdfViewerFragment import com.google.android.material.floatingactionbutton.FloatingActionButton import com.t8rin.logger.makeLog import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlin.math.min @OptIn(ExperimentalPdfApi::class) @SuppressLint("RestrictedApi", "VisibleForTests", "PrivateResource") @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13) internal class ModernPdfViewerDelegate : PdfViewerFragment() { private val _loadingState = MutableStateFlow(true) val loadingState: StateFlow = _loadingState override fun onLoadDocumentSuccess(document: PdfDocument) { super.onLoadDocumentSuccess(document) _loadingState.value = false } override fun onPdfViewCreated(pdfView: PdfView) { super.onPdfViewCreated(pdfView) pdfContainer.updateLayoutParams { leftMargin = 0 rightMargin = 0 } } override fun onLoadDocumentError(error: Throwable) { super.onLoadDocumentError(error) _loadingState.value = null } fun setScheme(colorScheme: ColorScheme) { toolboxView.getEditFab()?.let { fab -> fab.backgroundTintList = ColorStateList.valueOf(colorScheme.tertiaryContainer.toArgb()) fab.imageTintList = ColorStateList.valueOf(colorScheme.onTertiaryContainer.toArgb()) fab.invalidate() } pdfSearchView.apply { background = GradientDrawable().apply { shape = GradientDrawable.RECTANGLE cornerRadii = floatArrayOf( 36f.dp, 36f.dp, 36f.dp, 36f.dp, 0f, 0f, 0f, 0f ) setColor(colorScheme.surfaceContainer.toArgb()) } } pdfSearchView.findViewById(R.id.searchViewContainer).apply { background = GradientDrawable().apply { shape = GradientDrawable.RECTANGLE cornerRadius = 28f.dp setColor(colorScheme.surfaceBright.toArgb()) } invalidate() } pdfSearchView.searchQueryBox.apply { setTextColor(colorScheme.onSurface.toArgb()) setHintTextColor(colorScheme.onSurfaceVariant.toArgb()) highlightColor = colorScheme.primary.copy(alpha = 0.3f).toArgb() invalidate() } pdfSearchView.matchStatusTextView.apply { setTextColor( colorScheme.onSurfaceVariant.toArgb() ) invalidate() } val iconTint = ColorStateList.valueOf( colorScheme.onSurfaceVariant.toArgb() ) val buttonBgTint = ColorStateList.valueOf( Color.Transparent.toArgb() ) listOf( pdfSearchView.findPrevButton, pdfSearchView.findNextButton, pdfSearchView.closeButton ).forEach { button -> ImageViewCompat.setImageTintList(button, iconTint) button.backgroundTintList = buttonBgTint button.invalidate() } pdfSearchView.invalidate() pdfView.fastScrollVerticalThumbDrawable = createFastScrollDrawable( backgroundColor = colorScheme.surfaceContainerHigh.toArgb(), indicatorColor = colorScheme.onSurfaceVariant.toArgb() ) ContextCompat.getDrawable(requireContext(), R.drawable.page_indicator_background) ?.mutate()?.let { pageIndicatorDrawable -> pageIndicatorDrawable.setTint(colorScheme.surfaceContainerHigh.toArgb()) pdfView.fastScrollPageIndicatorBackgroundDrawable = pageIndicatorDrawable } CoroutineScope(Dispatchers.Main.immediate).launch { repeat(20) { i -> pdfView.fastScroller?.fastScrollDrawer?.let { try { it.javaClass.getDeclaredField("textPaint").apply { isAccessible = true set( it, TextPaint(get(it) as TextPaint).apply { color = colorScheme.onSurface.toArgb() } ) } } catch (t: Throwable) { if (i == 0) t.makeLog("textPaint") } } pdfView.fastScrollVisibility = PdfView.FastScrollVisibility.ALWAYS_SHOW pdfView.invalidate() delay(100) } } } private fun createFastScrollDrawable( backgroundColor: Int, indicatorColor: Int ): Drawable { val density = requireContext().resources.displayMetrics.density val widthDp = 36f val heightDp = 48f return object : Drawable() { private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = backgroundColor style = Paint.Style.FILL } private val indicatorPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = indicatorColor style = Paint.Style.FILL } private val indicatorPath = Path().apply { moveTo(480f, 880f) lineTo(240f, 640f) lineTo(297f, 583f) lineTo(480f, 766f) lineTo(663f, 583f) lineTo(720f, 640f) lineTo(480f, 880f) close() moveTo(298f, 376f) lineTo(240f, 320f) lineTo(480f, 80f) lineTo(720f, 320f) lineTo(662f, 376f) lineTo(480f, 194f) lineTo(298f, 376f) close() } override fun draw(canvas: Canvas) { val sizePx = 24 * density val offsetX = 8 * density val left = bounds.left + offsetX val top = bounds.top + (bounds.height() - sizePx) / 2 val scale = sizePx / 960f canvas.withTranslation(left, top) { canvas.withTranslation(-12f, -sizePx / 2) { val cx = bounds.width() / 2f val cy = bounds.height() / 2f val radius = min(bounds.width(), bounds.height()) / 2f canvas.drawCircle(cx, cy, radius + 16, bgPaint) } canvas.withScale(scale, scale) { drawPath(indicatorPath, indicatorPaint) } } } override fun setAlpha(alpha: Int) { bgPaint.alpha = alpha indicatorPaint.alpha = alpha invalidateSelf() } override fun getAlpha(): Int = bgPaint.alpha override fun setColorFilter(colorFilter: ColorFilter?) { bgPaint.colorFilter = colorFilter indicatorPaint.colorFilter = colorFilter invalidateSelf() } @Deprecated("Deprecated in Java") override fun getOpacity(): Int = PixelFormat.TRANSLUCENT override fun getIntrinsicWidth(): Int = (widthDp * density).toInt() override fun getIntrinsicHeight(): Int = (heightDp * density).toInt() } } private fun ToolBoxView.getEditFab(): FloatingActionButton? { return try { val field = ToolBoxView::class.java.getDeclaredField("editButton") field.isAccessible = true field.get(this) as? FloatingActionButton } catch (t: Throwable) { t.makeLog("getEditFab") null } } private val Float.dp: Float get() = this * requireContext().resources.displayMetrics.density } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/root/screenLogic/RootPdfToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.screenLogic import android.net.Uri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class RootPdfToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { fun navigate(screen: Screen, tempSelectionUri: Uri?) { onNavigate( when (screen) { is Screen.PdfTools.ExtractPages -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Merge -> screen.copy(uris = tempSelectionUri?.let(::listOf)) is Screen.PdfTools.RemovePages -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Split -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Rotate -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Rearrange -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Crop -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.PageNumbers -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Watermark -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Signature -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Compress -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.RemoveAnnotations -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Flatten -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Print -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Grayscale -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Repair -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Protect -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Unlock -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Metadata -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.ExtractImages -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.OCR -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.ZipConvert -> screen.copy(uri = tempSelectionUri) is Screen.PdfTools.Preview -> screen.copy(uri = tempSelectionUri) else -> screen } ) } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit ): RootPdfToolsComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/RotatePdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate.components.PdfPagesRotationGrid import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate.screenLogic.RotatePdfToolComponent @Composable fun RotatePdfToolContent( component: RotatePdfToolComponent ) { val pagesCount by rememberPdfPages(component.uri) LaunchedEffect(pagesCount, component.rotations) { if (pagesCount > 0 && (component.rotations.isEmpty() || component.rotations.size != pagesCount)) { component.updateRotations(List(pagesCount) { 0 }) } } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.rotate_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } PdfPagesRotationGrid( pages = remember(pagesCount, component.uri) { List(pagesCount) { PdfImageRequest( data = component.uri, pdfPage = it ) } }, rotations = component.rotations, onRotateAll = { component.updateRotations( component.rotations.map { (it + 90) % 360 } ) }, onClearAll = { component.updateRotations(List(pagesCount) { 0 }) }, onAutoClick = component::autoRotate, onRotateAt = { component.updateRotations( component.rotations.mapIndexed { index, rotation -> if (index == it) (rotation + 90) % 360 else rotation } ) } ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/components/PdfPagesRotationGrid.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.RotateRight import androidx.compose.material.icons.rounded.AutoMode import androidx.compose.material.icons.rounded.Restore import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.utils.animation.lessSpringySpec import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable internal fun PdfPagesRotationGrid( pages: List, modifier: Modifier = Modifier .container( shape = ShapeDefaults.extraLarge, clip = false ), onAutoClick: () -> Unit, onRotateAll: () -> Unit, onClearAll: () -> Unit, rotations: List, onRotateAt: (Int) -> Unit, title: String = stringResource(R.string.pages), coerceHeight: Boolean = true ) { Column( modifier = modifier .then( if (coerceHeight) { Modifier .heightIn( max = LocalWindowInfo.current.containerDpSize.height * 0.7f ) } else { Modifier } ), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .padding( top = 16.dp, bottom = 8.dp, start = 16.dp, end = 8.dp ) ) { Text( fontWeight = FontWeight.Medium, text = title, modifier = Modifier.weight(1f), fontSize = 18.sp, ) Spacer(Modifier.width(16.dp)) EnhancedButton( onClick = onAutoClick, containerColor = MaterialTheme.colorScheme.secondaryContainer, contentColor = MaterialTheme.colorScheme.onSecondaryContainer, contentPadding = PaddingValues( start = 8.dp, end = 12.dp ), modifier = Modifier .padding(start = 8.dp) .height(30.dp), ) { Row( verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Rounded.AutoMode, contentDescription = "auto", modifier = Modifier.size(18.dp) ) Spacer(Modifier.width(4.dp)) Text( stringResource(R.string.auto) ) } } EnhancedButton( onClick = onRotateAll, containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, contentColor = MaterialTheme.colorScheme.onSurfaceVariant, contentPadding = PaddingValues( start = 8.dp, end = 12.dp ), modifier = Modifier .padding(start = 8.dp) .height(30.dp), ) { Row( verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.AutoMirrored.Rounded.RotateRight, contentDescription = "Rotate 90", modifier = Modifier.size(20.dp) ) Spacer(Modifier.width(4.dp)) Text( stringResource(R.string.all) ) } } EnhancedButton( onClick = onClearAll, containerColor = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, contentPadding = PaddingValues( start = 8.dp, end = 12.dp ), modifier = Modifier .padding(start = 8.dp, end = 8.dp) .height(30.dp), ) { Row { Icon( imageVector = Icons.Rounded.Restore, contentDescription = "Restore", modifier = Modifier.size(20.dp) ) Spacer(Modifier.width(4.dp)) Text( stringResource(R.string.reset) ) } } } Box( modifier = Modifier.weight(1f, false) ) { val listState = rememberLazyGridState() LazyVerticalGrid( state = listState, modifier = Modifier .fadingEdges( scrollableState = listState, isVertical = true ) .animateContentSizeNoClip(), contentPadding = PaddingValues(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior(), columns = GridCells.Adaptive(minSize = 150.dp) ) { itemsIndexed( items = pages, key = { _, uri -> uri.toString() + uri.hashCode() } ) { index, uri -> Box( modifier = Modifier .fillMaxWidth() .aspectRatio(1f) .container( color = MaterialTheme.colorScheme.surface, resultPadding = 8.dp ) .container( shape = ShapeDefaults.small, color = Color.Transparent, resultPadding = 0.dp ) .hapticsClickable { onRotateAt(index) } ) { Picture( model = uri, modifier = Modifier .fillMaxSize() .transparencyChecker() .rotate( animateFloatAsState( targetValue = rotations.getOrNull(index)?.toFloat() ?: 0f, animationSpec = lessSpringySpec() ).value ), showTransparencyChecker = false, shape = RectangleShape, contentScale = ContentScale.Inside ) Box( modifier = Modifier .matchParentSize() .background( MaterialTheme.colorScheme.surfaceContainer.copy(0.6f) ), contentAlignment = Alignment.Center ) { Text( text = "${index + 1}", color = MaterialTheme.colorScheme.onSurface, fontSize = 20.sp, fontWeight = FontWeight.Bold ) } } } } } } } @Composable @EnPreview private fun Preview() = ImageToolboxThemeForPreview(true) { var files by remember { mutableStateOf( List(15) { "file:///uri_$it.pdf".toUri() } ) } var rotations by remember(files) { mutableStateOf( List(files.size) { 0 } ) } LazyColumn { item { PdfPagesRotationGrid( pages = files, rotations = rotations, onRotateAll = { rotations = rotations.map { (it + 90) % 360 } }, onClearAll = { rotations = rotations.map { 0 } }, onRotateAt = { rotations = rotations.toMutableList().apply { this[it] = (this[it] + 90) % 360 } }, onAutoClick = {} ) } items(30) { Text("TEST $it") } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/rotate/screenLogic/RotatePdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class RotatePdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _rotations: MutableState> = mutableStateOf(emptyList()) val rotations by _rotations override fun getKey(): Pair = "rotated" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateRotations(rotations: List) { registerChanges() _rotations.update { rotations } } fun autoRotate() { doSharing( action = { updateRotations( pdfManager.detectPdfAutoRotations( uri?.toString() ?: return@doSharing ) ) }, onFailure = {} ) } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.rotatePdf( uri = _uri.value.toString(), rotations = rotations ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.rotatePdf( uri = _uri.value.toString(), rotations = rotations ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): RotatePdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/SignaturePdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.controls.page.PageSelectionItem import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.components.SignaturePreview import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.components.SignatureSelector import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.screenLogic.SignaturePdfToolComponent @Composable fun SignaturePdfToolContent( component: SignaturePdfToolComponent ) { val params = component.params val pagesCount by rememberPdfPages(component.uri) LaunchedEffect(pagesCount, params.pages) { if (pagesCount > 0 && params.pages.isEmpty()) { component.updateParams( params.copy( pages = List(pagesCount) { it } ) ) } } val savedSignatures by component.savedSignatures.collectAsStateWithLifecycle() BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.signature), actions = {}, imagePreview = { SignaturePreview( uri = component.uri, params = component.params, pageCount = pagesCount ) }, placeImagePreview = true, showImagePreviewAsStickyHeader = true, controls = { ImageSelector( value = params.signatureImage, onValueChange = component::updateSignature, subtitle = stringResource(R.string.will_be_for_signature) ) Spacer(Modifier.height(16.dp)) PageSelectionItem( value = params.pages, onValueChange = { component.updateParams(params.copy(pages = it)) }, pageCount = pagesCount ) Spacer(Modifier.height(8.dp)) SignatureSelector( savedSignatures = savedSignatures, onSelect = { component.updateParams(params.copy(opacity = 1f)) component.updateSignature(it) }, onAdd = { component.updateSignature( data = it, save = true ) } ) Spacer(Modifier.height(8.dp)) AlphaSelector( value = params.opacity, onValueChange = { component.updateParams(params.copy(opacity = it)) }, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.large ) Spacer(Modifier.height(8.dp)) EnhancedSliderItem( value = params.x, title = stringResource(id = R.string.offset_x), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { component.updateParams(params.copy(x = it)) }, valueRange = 0f..1f, shape = ShapeDefaults.large ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = params.y, title = stringResource(id = R.string.offset_y), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { component.updateParams(params.copy(y = it)) }, valueRange = 0f..1f, shape = ShapeDefaults.large ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = params.size, title = stringResource(R.string.just_size), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { component.updateParams(params.copy(size = it)) }, valueRange = 0.01f..1f, shape = ShapeDefaults.large ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.components import android.graphics.Bitmap import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.LineWeight import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.retain.retain import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.PaintingStyle import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrushColor import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.Signature import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreviewLandscape import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.helper.scaleToFitCanvas import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.saver.PtSaver import kotlin.math.abs @Composable fun SignatureDialog( visible: Boolean, onDismiss: () -> Unit, onDone: (bitmap: Bitmap) -> Unit ) { val path = retain(visible) { Path() } var redraw by remember { mutableIntStateOf(0) } var lastPoint by remember { mutableStateOf(null) } var lastMid by remember { mutableStateOf(null) } val borderColor = MaterialTheme.colorScheme.outlineVariant() var canvasSize by remember { mutableStateOf(IntegerSize.Zero) } var strokeWidth by rememberSaveable(stateSaver = PtSaver) { mutableStateOf(15.pt) } var drawColor by rememberSaveable(stateSaver = ColorSaver) { mutableStateOf(Color.Black) } var showTuneDialog by remember { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() val screenHeight = LocalWindowInfo.current.containerDpSize.height val showIconAndTitle = isPortrait || screenHeight > 500.dp EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, icon = if (showIconAndTitle) { { Icon( imageVector = Icons.Outlined.Signature, contentDescription = null ) } } else null, title = if (showIconAndTitle) { { Text(stringResource(R.string.draw_signature)) } } else null, confirmButton = { EnhancedButton( onClick = { val size = IntegerSize(1024, 1024) val bitmap = ImageBitmap(size.width, size.height) val canvas = Canvas(bitmap) canvas.drawPath( path = path.scaleToFitCanvas( oldSize = canvasSize, currentSize = size ), paint = Paint().apply { color = drawColor this.strokeWidth = strokeWidth.toPx(size) style = PaintingStyle.Stroke isAntiAlias = true strokeCap = StrokeCap.Round strokeJoin = StrokeJoin.Round } ) onDone(bitmap.asAndroidBitmap()) path.reset() redraw++ onDismiss() } ) { Text(stringResource(R.string.confirm)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.close)) } }, text = { BoxWithConstraints( modifier = Modifier.fillMaxWidth() ) { val min = minOf(maxWidth, maxHeight) val shape = ShapeDefaults.mini Box( modifier = Modifier .size(min) .align(Alignment.Center) ) { Canvas( modifier = Modifier .fillMaxSize() .onSizeChanged { canvasSize = IntegerSize(it.width, it.height) } .border( width = 1.dp, color = borderColor, shape = shape ) .clip(shape) .transparencyChecker() .background(Color.White.copy(0.6f)) .pointerInput(Unit) { awaitEachGesture { val down = awaitFirstDown() path.moveTo(down.position.x, down.position.y) lastPoint = down.position lastMid = down.position redraw++ while (true) { val event = awaitPointerEvent() val change = event.changes.first() if (!change.pressed) break val point = change.position val prev = lastPoint ?: point if ( abs(point.x - prev.x) < 1f && abs(point.y - prev.y) < 1f ) continue val mid = Offset( (prev.x + point.x) / 2f, (prev.y + point.y) / 2f ) val lastMidPoint = lastMid ?: prev path.cubicTo( lastMidPoint.x, lastMidPoint.y, prev.x, prev.y, mid.x, mid.y ) lastPoint = point lastMid = mid redraw++ change.consume() } } } ) { redraw drawPath( path = path, color = drawColor, style = Stroke( width = strokeWidth.toPx(canvasSize), cap = StrokeCap.Round, join = StrokeJoin.Round ) ) } EnhancedIconButton( modifier = Modifier .align(Alignment.BottomStart) .offset( x = (-2).dp, y = 2.dp ), containerColor = MaterialTheme.colorScheme.surfaceContainerHigh.copy(0.8f), enableAutoShadowAndBorder = false, onClick = { showTuneDialog = true } ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = null ) } EnhancedIconButton( modifier = Modifier .align(Alignment.BottomEnd) .offset( x = 2.dp, y = 2.dp ), containerColor = MaterialTheme.colorScheme.surfaceContainerHigh.copy(0.8f), enableAutoShadowAndBorder = false, onClick = { path.reset() lastPoint = null lastMid = null redraw++ } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null ) } } } }, modifier = if (canvasSize != IntegerSize.Zero) { Modifier.width( with(LocalDensity.current) { canvasSize.width.toDp() + 48.dp }.coerceAtLeast(300.dp) ) } else Modifier ) EnhancedAlertDialog( visible = showTuneDialog, onDismissRequest = { showTuneDialog = false }, icon = { Icon(Icons.Rounded.Tune, null) }, title = { Text(stringResource(R.string.pen_params)) }, confirmButton = { EnhancedButton( onClick = { showTuneDialog = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } }, text = { Column { ColorRowSelector( value = drawColor, onValueChange = { drawColor = it }, modifier = Modifier .container( shape = ShapeDefaults.top ), title = stringResource(R.string.paint_color), allowAlpha = false, icon = Icons.Outlined.BrushColor ) Spacer(Modifier.height(4.dp)) EnhancedSliderItem( modifier = Modifier.fillMaxWidth(), value = strokeWidth.value, icon = Icons.Rounded.LineWeight, title = stringResource(R.string.line_width), valueSuffix = " Pt", sliderModifier = Modifier .padding(top = 14.dp, start = 12.dp, end = 12.dp, bottom = 10.dp), valueRange = 1f..50f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { strokeWidth = it.roundToTwoDigits().pt }, shape = ShapeDefaults.bottom ) } } ) } @EnPreview @EnPreviewLandscape @Composable private fun Preview() = ImageToolboxThemeForPreview(false) { Surface { Spacer(Modifier.fillMaxSize()) var image by remember { mutableStateOf(null) } var visible by remember { mutableStateOf(true) } if (visible) { SignatureDialog( visible = true, onDismiss = { visible = false }, onDone = { bmp -> image = bmp } ) } image?.let { Image(it.asImageBitmap(), null, modifier = Modifier.background(Color.White)) } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignaturePreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.components import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.helper.toDp import com.t8rin.imagetoolbox.core.ui.utils.helper.toPx import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfSignatureParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PageSwitcher import kotlin.math.roundToInt @Composable internal fun SignaturePreview( uri: Uri?, params: PdfSignatureParams, pageCount: Int ) { PageSwitcher( activePages = params.pages, pageCount = pageCount ) { page -> Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip(alignment = Alignment.Center), contentAlignment = Alignment.Center ) { var aspectRatio by rememberSaveable { mutableFloatStateOf(1f) } Box( modifier = Modifier.aspectRatio(aspectRatio) ) { Picture( model = remember(uri, page) { PdfImageRequest( data = uri, pdfPage = page ) }, contentScale = ContentScale.FillBounds, modifier = Modifier.matchParentSize(), onSuccess = { aspectRatio = it.result.image.safeAspectRatio }, shape = MaterialTheme.shapes.medium ) if (page in params.pages) { BoxWithConstraints( modifier = Modifier.matchParentSize() ) { var imageAspect by remember { mutableFloatStateOf(1f) } val boxWidthPx = maxWidth.toPx() val boxHeightPx = maxHeight.toPx() val targetWidthPx = boxWidthPx * params.size val targetHeightPx = targetWidthPx / imageAspect val centerXPx = boxWidthPx * params.x val centerYPx = boxHeightPx * params.y var offsetXPx = centerXPx - targetWidthPx / 2f var offsetYPx = centerYPx - targetHeightPx / 2f offsetXPx = offsetXPx.coerceIn(0f, boxWidthPx - targetWidthPx) offsetYPx = offsetYPx.coerceIn(0f, boxHeightPx - targetHeightPx) AsyncImage( model = params.signatureImage, contentDescription = null, modifier = Modifier .offset { IntOffset( x = offsetXPx.roundToInt(), y = (boxHeightPx - offsetYPx - targetHeightPx).roundToInt() ) } .width(targetWidthPx.toDp()) .aspectRatio(imageAspect) .graphicsLayer { alpha = params.opacity }, contentScale = ContentScale.Fit, onSuccess = { imageAspect = it.result.image.safeAspectRatio } ) } } } } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/components/SignatureSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.components import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AddCircleOutline import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Signature import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.EnPreview import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun SignatureSelector( savedSignatures: List, onAdd: (Any) -> Unit, onSelect: (Any) -> Unit ) { var isDrawingVisible by remember { mutableStateOf(false) } PreferenceItem( shape = ShapeDefaults.large, onClick = { isDrawingVisible = true }, title = stringResource(R.string.draw_signature), subtitle = stringResource(R.string.draw_signature_sub), startIcon = Icons.Outlined.Signature, endIcon = Icons.Rounded.AddCircleOutline, modifier = Modifier.fillMaxWidth(), bottomContent = if (savedSignatures.isNotEmpty()) { { val shape = ShapeDefaults.mini LazyRow( contentPadding = PaddingValues( start = 16.dp, end = 16.dp, bottom = 16.dp ), horizontalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { items(savedSignatures) { signature -> Picture( model = signature, modifier = Modifier .size(40.dp) .border( width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant(), shape = shape ) .clip(shape) .transparencyChecker( checkerWidth = 6.dp, checkerHeight = 6.dp ) .background(Color.White.copy(0.4f)) .hapticsClickable { onSelect(signature) }, showTransparencyChecker = false, shape = RectangleShape ) } } } } else null ) SignatureDialog( visible = isDrawingVisible, onDismiss = { isDrawingVisible = false }, onDone = onAdd ) } @Composable @EnPreview private fun Preview() = ImageToolboxThemeForPreview(false) { SignatureSelector( savedSignatures = List(10) { "qwqw" }, onSelect = {}, onAdd = {} ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/signature/screenLogic/SignaturePdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshotFlow import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfSignatureParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.distinctUntilChanged class SignaturePdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _params: MutableState = mutableStateOf(PdfSignatureParams()) val params by _params val savedSignatures = pdfManager.savedSignatures override fun getKey(): Pair = "signed" to uri init { componentScope.launch { snapshotFlow { uri } .distinctUntilChanged() .collect { _params.update { it.copy(pages = emptyList()) } } } } fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateParams(params: PdfSignatureParams) { registerChanges() _params.update { params } } fun updateSignature( data: Any, save: Boolean = false ) { registerChanges() _params.update { it.copy( signatureImage = data ) } if (save) { updateParams(params.copy(opacity = 1f)) componentScope.launch { pdfManager.saveSignature(data) } } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.addSignature( uri = _uri.value.toString(), params = params ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.addSignature( uri = _uri.value.toString(), params = params ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): SignaturePdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/SplitPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.split import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.controls.page.PageSelectionItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.ImagesPreviewWithSelection import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.split.screenLogic.SplitPdfToolComponent @Composable fun SplitPdfToolContent( component: SplitPdfToolComponent ) { val pagesCount by rememberPdfPages(component.uri) val selectedPagesSize = component.pages?.size ?: 0 val isPortrait by isPortraitOrientationAsState() var trigger by remember { mutableIntStateOf(0) } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.split_pdf), topAppBarPersistentActions = { AnimatedVisibility( visible = selectedPagesSize != pagesCount, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = { component.updatePages( List(pagesCount) { it } ) trigger++ } ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = selectedPagesSize != 0 ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { selectedPagesSize.takeIf { it != 0 }?.let { Spacer(Modifier.width(8.dp)) Text( text = selectedPagesSize.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) } EnhancedIconButton( onClick = { component.updatePages(emptyList()) trigger++ } ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } }, canSave = !component.pages.isNullOrEmpty(), actions = {}, imagePreview = { key(trigger, pagesCount, component.uri) { ImagesPreviewWithSelection( imageUris = remember(pagesCount, component.uri) { List(pagesCount) { PdfImageRequest( data = component.uri, pdfPage = it ) } }, imageFrames = remember(component.pages) { ImageFrames.ManualSelection( component.pages.orEmpty().map { it + 1 } ) }, onFrameSelectionChange = { frames -> component.updatePages( frames.getFramePositions(pagesCount).map { it - 1 } ) }, isPortrait = isPortrait, isLoadingImages = false ) } }, placeImagePreview = true, showImagePreviewAsStickyHeader = false, controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } PageSelectionItem( value = component.pages, onValueChange = { component.updatePages(it) trigger++ }, pageCount = pagesCount ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/split/screenLogic/SplitPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.split.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshotFlow import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.distinctUntilChanged class SplitPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _pages: MutableState?> = mutableStateOf(null) val pages by _pages init { componentScope.launch { snapshotFlow { uri } .distinctUntilChanged() .collect { _pages.update { null } } } } override fun getKey(): Pair = "split" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updatePages(pages: List) { registerChanges() _pages.update { pages } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.splitPdf( uri = _uri.value.toString(), pages = pages ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.splitPdf( uri = _uri.value.toString(), pages = pages ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): SplitPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/UnlockPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.unlock import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.Green import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContainerColor import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContentColor import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.unlock.screenLogic.UnlockPdfToolComponent @Composable fun UnlockPdfToolContent( component: UnlockPdfToolComponent ) { BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.unlock_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } AnimatedVisibility( visible = rememberPdfPages(component.uri).value != 0, modifier = Modifier.fillMaxWidth() ) { val containerColor = Green.blend( color = Color.Black, fraction = 0.4f ) val contentColor = containerColor.inverse( fraction = { 1f }, darkMode = true ) CompositionLocalProvider( LocalIconShapeContentColor provides contentColor, LocalIconShapeContainerColor provides containerColor.blend( color = Color.Black, fraction = 0.15f ) ) { PreferenceItem( title = stringResource(R.string.success), subtitle = stringResource(R.string.pdf_unlocked), startIcon = Icons.Outlined.CheckCircle, contentColor = contentColor, containerColor = containerColor, overrideIconShapeContentColor = true, modifier = Modifier.fillMaxWidth(), onClick = null ) } } }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/unlock/screenLogic/UnlockPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.unlock.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class UnlockPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _password: MutableState = mutableStateOf("") val password by _password override fun getKey(): Pair = "unlocked" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } override fun setPassword(password: String) { _password.update { password } _showPasswordRequestDialog.update { false } pdfManager.setMasterPassword(password) } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.unlockPdf( uri = _uri.value.toString(), password = password ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.unlockPdf( uri = _uri.value.toString(), password = password ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): UnlockPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/WatermarkPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TextRotationAngleup import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.controls.page.PageSelectionItem import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark.components.WatermarkPreview import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark.screenLogic.WatermarkPdfToolComponent import kotlin.math.roundToInt @Composable fun WatermarkPdfToolContent( component: WatermarkPdfToolComponent ) { val params = component.params val pagesCount by rememberPdfPages(component.uri) LaunchedEffect(pagesCount, params.pages) { if (pagesCount > 0 && params.pages.isEmpty()) { component.updateParams( params.copy( pages = List(pagesCount) { it } ) ) } } BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.watermarking), actions = {}, imagePreview = { WatermarkPreview( uri = component.uri, params = params, pageCount = pagesCount ) }, placeImagePreview = true, showImagePreviewAsStickyHeader = true, controls = { RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = params.text, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), singleLine = false, onValueChange = { component.updateParams(params.copy(text = it)) }, label = { Text(stringResource(R.string.text)) } ) Spacer(Modifier.height(16.dp)) PageSelectionItem( value = params.pages, onValueChange = { component.updateParams(params.copy(pages = it)) }, pageCount = pagesCount ) Spacer(Modifier.height(8.dp)) EnhancedSliderItem( value = params.rotation, icon = Icons.Outlined.TextRotationAngleup, title = stringResource(id = R.string.angle), valueRange = 0f..360f, internalStateTransformation = Float::roundToInt, onValueChange = { component.updateParams(params.copy(rotation = it)) }, shape = ShapeDefaults.large ) Spacer(Modifier.height(8.dp)) AlphaSelector( value = params.opacity, onValueChange = { component.updateParams(params.copy(opacity = it)) }, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.large ) Spacer(Modifier.height(8.dp)) EnhancedSliderItem( value = params.fontSize, title = stringResource(R.string.watermark_size), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { component.updateParams(params.copy(fontSize = it)) }, valueRange = 5f..100f, shape = ShapeDefaults.large ) Spacer(modifier = Modifier.height(8.dp)) ColorRowSelector( value = params.color.toColor(), onValueChange = { component.updateParams(params.copy(color = it.toArgb())) }, title = stringResource(R.string.text_color), modifier = Modifier.container( shape = ShapeDefaults.large ), allowAlpha = false ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/components/WatermarkPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark.components import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.data.coil.PdfImageRequest import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfWatermarkParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PageSwitcher @Composable internal fun WatermarkPreview( uri: Uri?, params: PdfWatermarkParams, pageCount: Int ) { PageSwitcher( activePages = params.pages, pageCount = pageCount ) { page -> Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip( alignment = Alignment.Center ), contentAlignment = Alignment.Center ) { var aspectRatio by rememberSaveable { mutableFloatStateOf(1f) } Box( modifier = Modifier.aspectRatio(aspectRatio) ) { Picture( model = remember(uri, page) { PdfImageRequest( data = uri, pdfPage = page ) }, contentScale = ContentScale.FillBounds, modifier = Modifier.matchParentSize(), onSuccess = { aspectRatio = it.result.image.safeAspectRatio }, shape = MaterialTheme.shapes.medium ) if (page in params.pages) { BoxWithConstraints( modifier = Modifier.matchParentSize() ) { val scaledFontSize = remember( params.fontSize, maxWidth ) { val scaleFactor = maxWidth.value / 300f (params.fontSize * scaleFactor).sp } AutoSizeText( key = { maxWidth }, text = params.text, modifier = Modifier .align(Alignment.Center) .graphicsLayer { rotationZ = params.rotation alpha = params.opacity }, color = Color(params.color), style = MaterialTheme.typography.bodyMedium.copy( fontSize = scaledFontSize ) ) } } } } } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/watermark/screenLogic/WatermarkPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.PdfWatermarkParams import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.distinctUntilChanged class WatermarkPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val shareProvider: ImageShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _params: MutableState = mutableStateOf(PdfWatermarkParams(color = Color.Gray.toArgb())) val params by _params init { componentScope.launch { snapshotFlow { uri } .distinctUntilChanged() .collect { _params.update { it.copy(pages = emptyList()) } } } } override fun getKey(): Pair = "watermarked" to uri fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateParams(params: PdfWatermarkParams) { registerChanges() _params.update { params } } override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.addWatermark( uri = _uri.value.toString(), params = params ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.addWatermark( uri = _uri.value.toString(), params = params ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): WatermarkPdfToolComponent } } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/ZipConvertPdfToolContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.zip_convert import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceAtLeast import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.rememberPdfPages import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.PdfPreviewItem import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.zip_convert.screenLogic.ZipConvertPdfToolComponent import kotlin.math.roundToInt @Composable fun ZipConvertPdfToolContent( component: ZipConvertPdfToolComponent ) { val pagesCount by rememberPdfPages(component.uri) BasePdfToolContent( component = component, contentPicker = rememberFilePicker( mimeType = MimeType.Pdf, onSuccess = component::setUri ), isPickedAlready = component.initialUri != null, canShowScreenData = component.uri != null, title = stringResource(R.string.zip_pdf), controls = { component.uri?.let { PdfPreviewItem( uri = it, onRemove = { component.setUri(null) } ) Spacer(Modifier.height(16.dp)) } EnhancedSliderItem( value = component.interval, title = stringResource(R.string.interval), internalStateTransformation = { it.roundToInt() }, onValueChange = { component.updateInterval(it.roundToInt()) }, valueRange = 1f..pagesCount.fastCoerceAtLeast(1).toFloat(), shape = ShapeDefaults.large ) }, onFilledPassword = { component.setUri(component.uri) } ) } ================================================ FILE: feature/pdf-tools/src/main/java/com/t8rin/imagetoolbox/feature/pdf_tools/presentation/zip_convert/screenLogic/ZipConvertPdfToolComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pdf_tools.presentation.zip_convert.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.common.BasePdfToolComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class ZipConvertPdfToolComponent @AssistedInject internal constructor( @Assisted val initialUri: Uri?, @Assisted componentContext: ComponentContext, @Assisted onGoBack: () -> Unit, @Assisted onNavigate: (Screen) -> Unit, private val pdfManager: PdfManager, private val fileController: FileController, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : BasePdfToolComponent( onGoBack = onGoBack, onNavigate = onNavigate, dispatchersHolder = dispatchersHolder, componentContext = componentContext, pdfManager = pdfManager ) { override val _haveChanges: MutableState = mutableStateOf(initialUri != null) override val haveChanges: Boolean by _haveChanges override val extraDataType: ExtraDataType = ExtraDataType.File override val mimeType: MimeType.Single = MimeType.Zip private val _uri: MutableState = mutableStateOf(initialUri) val uri by _uri private val _interval: MutableState = mutableIntStateOf(1) val interval by _interval fun setUri(uri: Uri?) { if (uri == null) { registerChangesCleared() } else { registerChanges() } _uri.update { uri } checkPdf( uri = uri, onDecrypted = { _uri.value = it } ) } fun updateInterval(interval: Int) { registerChanges() _interval.update { interval } } override fun createTargetFilename(): String = "${uri?.filename()?.substringBeforeLast('.') ?: timestamp()}.zip" override fun saveTo( uri: Uri ) { doSaving { val processed = pdfManager.convertToZip( uri = _uri.value.toString(), interval = interval ) fileController.transferBytes( fromUri = processed, toUri = uri.toString() ).onSuccess(::registerSave) } } override fun performSharing( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) { prepareForSharing( onSuccess = { shareProvider.shareUris(it.map(Uri::toString)) registerSave() onSuccess() }, onFailure = onFailure ) } override fun prepareForSharing( onSuccess: suspend (List) -> Unit, onFailure: (Throwable) -> Unit ) { doSharing( action = { onSuccess( listOf( pdfManager.convertToZip( uri = _uri.value.toString(), interval = interval ).toUri() ) ) registerSave() }, onFailure = onFailure ) } @AssistedFactory fun interface Factory { operator fun invoke( initialUri: Uri?, componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ZipConvertPdfToolComponent } } ================================================ FILE: feature/pick-color/.gitignore ================================================ /build ================================================ FILE: feature/pick-color/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.pick_color" ================================================ FILE: feature/pick-color/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/PickColorFromImageContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pick_color.presentation import android.net.Uri import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ZoomIn import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageBottomAppBar import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageContentImpl import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageTopAppBar import com.t8rin.imagetoolbox.feature.pick_color.presentation.screenLogic.PickColorFromImageComponent import kotlinx.coroutines.launch @Composable fun PickColorFromImageContent( component: PickColorFromImageComponent ) { val settingsState = LocalSettingsState.current val scope = rememberCoroutineScope() var panEnabled by rememberSaveable { mutableStateOf(false) } AutoContentBasedColors(component.bitmap) AutoContentBasedColors(component.color) val imagePicker = rememberImagePicker { uri: Uri -> component.setUri( uri = uri ) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialUri != null ) val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val isPortrait by isPortraitOrientationAsState() val switch = @Composable { PanModeButton( selected = panEnabled, onClick = { panEnabled = !panEnabled } ) } val magnifierButton = @Composable { val settingsInteractor = LocalSimpleSettingsInteractor.current EnhancedIconButton( containerColor = takeColorFromScheme { if (settingsState.magnifierEnabled) { secondary } else surfaceContainer }, contentColor = takeColorFromScheme { if (settingsState.magnifierEnabled) { onSecondary } else onSurface }, enableAutoShadowAndBorder = false, onClick = { scope.launch { settingsInteractor.toggleMagnifierEnabled() } }, modifier = Modifier.statusBarsPadding() ) { Icon( imageVector = Icons.Outlined.ZoomIn, contentDescription = stringResource(R.string.magnifier) ) } } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } Box { Scaffold( modifier = Modifier .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { PickColorFromImageTopAppBar( bitmap = component.bitmap, scrollBehavior = scrollBehavior, onGoBack = component.onGoBack, isPortrait = isPortrait, magnifierButton = magnifierButton, color = component.color ) }, bottomBar = { PickColorFromImageBottomAppBar( bitmap = component.bitmap, isPortrait = isPortrait, switch = switch, color = component.color, onPickImage = pickImage, onOneTimePickImage = { showOneTimeImagePickingDialog = true }, ) }, contentWindowInsets = WindowInsets() ) { contentPadding -> PickColorFromImageContentImpl( bitmap = component.bitmap, isPortrait = isPortrait, panEnabled = panEnabled, onColorChange = component::updateColor, onPickImage = pickImage, onOneTimePickImage = { showOneTimeImagePickingDialog = true }, magnifierButton = magnifierButton, switch = switch, color = component.color, contentPadding = contentPadding ) } if (component.bitmap == null) { EnhancedFloatingActionButton( onClick = pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, modifier = Modifier .navigationBarsPadding() .padding(16.dp) .align(settingsState.fabAlignment) ) { Spacer(Modifier.width(16.dp)) Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_image_alt) ) Spacer(Modifier.width(16.dp)) Text(stringResource(R.string.pick_image_alt)) Spacer(Modifier.width(16.dp)) } } } OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) LoadingDialog( visible = component.isImageLoading, canCancel = false ) } ================================================ FILE: feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageBottomAppBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pick_color.presentation.components import android.graphics.Bitmap import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.colors.parser.ColorNameParser import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke @Composable internal fun PickColorFromImageBottomAppBar( bitmap: Bitmap?, isPortrait: Boolean, switch: @Composable () -> Unit, onOneTimePickImage: () -> Unit, onPickImage: () -> Unit, color: Color, ) { AnimatedVisibility( visible = bitmap != null && isPortrait, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val text by remember(color) { derivedStateOf { ColorNameParser.parseColorName(color) } } BottomAppBar( modifier = Modifier .drawHorizontalStroke(true), actions = { switch() Spacer(Modifier.width(16.dp)) Text( modifier = Modifier .weight(1f) .padding(2.dp), text = text, textAlign = TextAlign.Center ) }, floatingActionButton = { EnhancedFloatingActionButton( onClick = onPickImage, onLongClick = onOneTimePickImage ) { Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_image_alt) ) } } ) } } ================================================ FILE: feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageContentImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pick_color.presentation.components import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.ImageColorDetector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable internal fun PickColorFromImageContentImpl( bitmap: Bitmap?, isPortrait: Boolean, panEnabled: Boolean, onColorChange: (Color) -> Unit, onPickImage: () -> Unit, magnifierButton: @Composable () -> Unit, switch: @Composable () -> Unit, onOneTimePickImage: () -> Unit, color: Color, contentPadding: PaddingValues ) { val settingsState = LocalSettingsState.current Box( modifier = Modifier .fillMaxSize() .padding(contentPadding) ) { bitmap?.let { if (isPortrait) { AnimatedContent( targetState = it ) { bitmap -> ImageColorDetector( panEnabled = panEnabled, imageBitmap = bitmap.asImageBitmap(), color = color, modifier = Modifier .fillMaxSize() .padding(16.dp) .windowInsetsPadding( WindowInsets.navigationBars .only(WindowInsetsSides.Bottom) .union(WindowInsets.displayCutout) ) .container(resultPadding = 8.dp) .clip( ShapeDefaults.small ) .transparencyChecker(), isMagnifierEnabled = settingsState.magnifierEnabled, onColorChange = onColorChange ) } } else { Row { Box( modifier = Modifier.weight(0.8f) ) { Box(Modifier.align(Alignment.Center)) { AnimatedContent( targetState = it ) { bitmap -> val direction = LocalLayoutDirection.current ImageColorDetector( panEnabled = panEnabled, imageBitmap = bitmap.asImageBitmap(), color = color, modifier = Modifier .fillMaxSize() .padding(20.dp) .windowInsetsPadding( WindowInsets.navigationBars .only(WindowInsetsSides.Bottom) .union(WindowInsets.displayCutout) ) .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ) .container(resultPadding = 8.dp) .clip(ShapeDefaults.small) .transparencyChecker(), isMagnifierEnabled = settingsState.magnifierEnabled, onColorChange = onColorChange ) } } } val direction = LocalLayoutDirection.current Column( modifier = Modifier .container( shape = RectangleShape, resultPadding = 0.dp ) .fillMaxHeight() .padding(horizontal = 20.dp) .padding( end = WindowInsets.displayCutout .asPaddingValues() .calculateEndPadding(direction) ) .navigationBarsPadding(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { magnifierButton() Spacer(modifier = Modifier.height(8.dp)) switch() Spacer(modifier = Modifier.height(8.dp)) EnhancedFloatingActionButton( onClick = onPickImage, onLongClick = onOneTimePickImage ) { Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = stringResource(R.string.pick_image_alt) ) } } } } } ?: Column( modifier = Modifier .fillMaxWidth() .enhancedVerticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { ImageNotPickedWidget( onPickImage = onPickImage, modifier = Modifier .padding(bottom = 88.dp, top = 20.dp, start = 20.dp, end = 20.dp) .navigationBarsPadding() ) } } } ================================================ FILE: feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pick_color.presentation.components import android.graphics.Bitmap import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.luminance import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.t8rin.colors.ImageColorDetector import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalSheetDragHandle import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shimmer import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker @Composable fun PickColorFromImageSheet( visible: Boolean, onDismiss: () -> Unit, bitmap: Bitmap?, onColorChange: (Color) -> Unit, color: Color ) { val settingsState = LocalSettingsState.current var panEnabled by rememberSaveable { mutableStateOf(false) } EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier.navigationBarsPadding() ) { remember(bitmap) { bitmap?.asImageBitmap() }?.let { ImageColorDetector( panEnabled = panEnabled, color = color, imageBitmap = it, onColorChange = onColorChange, isMagnifierEnabled = settingsState.magnifierEnabled, boxModifier = Modifier .weight(1f) .fillMaxWidth() .padding(horizontal = 16.dp) .container( shape = ShapeDefaults.extraSmall, color = MaterialTheme.colorScheme .outlineVariant() .copy(alpha = 0.1f), resultPadding = 0.dp ) .transparencyChecker() ) } ?: Box( modifier = Modifier .weight(1f) .fillMaxWidth() .padding(horizontal = 16.dp) .container( shape = ShapeDefaults.extraSmall, color = MaterialTheme.colorScheme .outlineVariant() .copy(alpha = 0.1f), resultPadding = 0.dp ) .shimmer(true) ) Row( modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Row( verticalAlignment = Alignment.CenterVertically ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .padding(end = 16.dp) .container( color = MaterialTheme.colorScheme.surfaceContainerHighest ) .padding(4.dp) ) { Box( modifier = Modifier .padding(end = 16.dp) .background( color = animateColorAsState(color).value, shape = ShapeDefaults.small ) .size(40.dp) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant( onTopOf = animateColorAsState(color).value ), shape = ShapeDefaults.small ) .clip(ShapeDefaults.small) .hapticsClickable { Clipboard.copy( text = color.toHex(), message = R.string.color_copied ) }, contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.ContentCopy, contentDescription = stringResource(R.string.copy), tint = animateColorAsState( color.inverse( fraction = { cond -> if (cond) 0.8f else 0.5f }, darkMode = color.luminance() < 0.3f ) ).value, modifier = Modifier.size(20.dp) ) } Text( modifier = Modifier .clip(ShapeDefaults.mini) .hapticsClickable { Clipboard.copy( text = color.toHex(), message = R.string.color_copied ) } .background(MaterialTheme.colorScheme.secondaryContainer) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ), shape = ShapeDefaults.mini ) .padding(horizontal = 8.dp), text = color.toHex(), style = LocalTextStyle.current.copy( fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSecondaryContainer ) ) } } Spacer(Modifier.weight(1f)) PanModeButton( selected = panEnabled, onClick = { panEnabled = !panEnabled } ) } } }, dragHandle = { EnhancedModalSheetDragHandle( color = Color.Transparent, drawStroke = false, heightWhenDisabled = 20.dp ) }, visible = visible, onDismiss = { if (!it) onDismiss() } ) } ================================================ FILE: feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/components/PickColorFromImageTopAppBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pick_color.presentation.components import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.t8rin.colors.parser.ColorNameParser import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.toHex import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable internal fun PickColorFromImageTopAppBar( bitmap: Bitmap?, scrollBehavior: TopAppBarScrollBehavior, onGoBack: () -> Unit, isPortrait: Boolean, magnifierButton: @Composable () -> Unit, color: Color, ) { val settingsState = LocalSettingsState.current AnimatedContent( modifier = Modifier.drawHorizontalStroke(), targetState = bitmap == null, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { noBmp -> if (noBmp) { EnhancedTopAppBar( type = EnhancedTopAppBarType.Large, scrollBehavior = scrollBehavior, drawHorizontalStroke = false, navigationIcon = { EnhancedIconButton( onClick = onGoBack ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } }, title = { Text( text = stringResource(R.string.pick_color), modifier = Modifier.marquee() ) }, actions = { if (bitmap == null) { TopAppBarEmoji() } } ) } else { Surface( color = MaterialTheme.colorScheme.surfaceContainer, modifier = Modifier.animateContentSizeNoClip(), ) { Column { Column( modifier = Modifier.windowInsetsPadding( WindowInsets.navigationBars .only(WindowInsetsSides.End) .union(WindowInsets.displayCutout) ) ) { Spacer(modifier = Modifier.height(8.dp)) Row( verticalAlignment = Alignment.CenterVertically ) { Row { EnhancedIconButton( onClick = onGoBack, modifier = Modifier.statusBarsPadding() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } if (isPortrait) { Spacer(modifier = Modifier.weight(1f)) magnifierButton() Spacer(modifier = Modifier.width(4.dp)) } } if (!isPortrait) { ProvideTextStyle( value = LocalTextStyle.current.merge( MaterialTheme.typography.headlineSmall ) ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .padding( start = 16.dp, end = 16.dp ) .statusBarsPadding() ) { Text(stringResource(R.string.color)) Text( modifier = Modifier .padding(horizontal = 8.dp) .clip(ShapeDefaults.mini) .hapticsClickable { Clipboard.copy( text = color.toHex(), message = R.string.color_copied ) } .background(MaterialTheme.colorScheme.secondaryContainer) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ), shape = ShapeDefaults.mini ) .padding(horizontal = 6.dp), text = color.toHex(), style = LocalTextStyle.current.copy( fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSecondaryContainer ) ) Text( modifier = Modifier .weight(1f) .padding(2.dp), text = remember(color) { derivedStateOf { ColorNameParser.parseColorName(color) } }.value ) Box( Modifier .padding( vertical = 4.dp, horizontal = 16.dp ) .background( color = animateColorAsState(color).value, shape = ShapeDefaults.small ) .height(40.dp) .width(72.dp) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant( onTopOf = animateColorAsState(color).value ), shape = ShapeDefaults.small ) .clip(ShapeDefaults.small) .hapticsClickable { Clipboard.copy( text = color.toHex(), message = R.string.color_copied ) } ) } } } Spacer( Modifier .weight(1f) .padding(start = 8.dp) ) } if (isPortrait) { Spacer(modifier = Modifier.height(8.dp)) ProvideTextStyle( value = LocalTextStyle.current.merge( MaterialTheme.typography.headlineSmall ) ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) ) { Text(stringResource(R.string.color)) Text( modifier = Modifier .padding(horizontal = 8.dp) .clip(ShapeDefaults.mini) .hapticsClickable { Clipboard.copy( text = color.toHex(), message = R.string.color_copied ) } .background(MaterialTheme.colorScheme.secondaryContainer) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ), shape = ShapeDefaults.mini ) .padding(horizontal = 6.dp), text = color.toHex(), style = LocalTextStyle.current.copy( fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSecondaryContainer ) ) Spacer( modifier = Modifier .weight(1f) .padding(2.dp) ) Box( Modifier .padding(vertical = 4.dp) .background( color = animateColorAsState(color).value, shape = ShapeDefaults.small ) .height(40.dp) .width(72.dp) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant( onTopOf = animateColorAsState(color).value ), shape = ShapeDefaults.small ) .clip(ShapeDefaults.small) .hapticsClickable { Clipboard.copy( text = color.toHex(), message = R.string.color_copied ) } ) } } } else { Spacer(modifier = Modifier.height(8.dp)) } } } } } } } ================================================ FILE: feature/pick-color/src/main/java/com/t8rin/imagetoolbox/feature/pick_color/presentation/screenLogic/PickColorFromImageComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.pick_color.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject class PickColorFromImageComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, private val imageGetter: ImageGetter, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } } private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _color: MutableState = mutableStateOf(Color.Unspecified) val color: Color by _color private val _uri = mutableStateOf(null) fun setUri( uri: Uri ) { _uri.value = uri componentScope.launch { runSuspendCatching { _bitmap.value = imageGetter.getImage( data = uri, size = 4000 ) }.onFailure(AppToastHost::showFailureToast) } } fun updateColor(color: Color) { _color.value = color } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, ): PickColorFromImageComponent } } ================================================ FILE: feature/quick-tiles/.gitignore ================================================ /build ================================================ FILE: feature/quick-tiles/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) } android.namespace = "com.t8rin.imagetoolbox.feature.quick_tiles" dependencies { implementation(projects.feature.eraseBackground) } ================================================ FILE: feature/quick-tiles/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/Contants.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.quick_tiles.screenshot internal const val SCREENSHOT_ACTION = "shot" internal const val DATA_EXTRA = "data" internal const val RESULT_CODE_EXTRA = "resultCode" ================================================ FILE: feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotLauncher.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.quick_tiles.screenshot import android.media.projection.MediaProjectionManager import android.os.Build import android.os.Bundle import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.content.getSystemService import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.buildIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getScreenExtra import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.postToast import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.putScreenExtra class ScreenshotLauncher : AppCompatActivity() { @RequiresApi(Build.VERSION_CODES.N) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val projectionManager = getSystemService() val captureIntent = projectionManager?.createScreenCaptureIntent() if (captureIntent == null) { onFailure(NullPointerException("No projection manager")) } registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> runCatching { val resultCode = result.resultCode val data = result.data if (resultCode == RESULT_OK) { val serviceIntent = buildIntent(ScreenshotService::class.java) { putExtra(DATA_EXTRA, data) putExtra(RESULT_CODE_EXTRA, resultCode) action = intent.action putScreenExtra(intent.getScreenExtra()) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent) } else { startService(serviceIntent) } finish() } else throw SecurityException() }.onFailure(::onFailure) }.launch(captureIntent!!) } private fun onFailure(throwable: Throwable) { postToast( textRes = R.string.smth_went_wrong, isLong = true, throwable.localizedMessage ?: "" ) finish() } } ================================================ FILE: feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotMaker.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.quick_tiles.screenshot import android.content.Context import android.graphics.Bitmap import android.graphics.PixelFormat import android.graphics.Point import android.graphics.Rect import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.media.ImageReader import android.media.ImageReader.OnImageAvailableListener import android.media.projection.MediaProjection import android.os.Build import android.view.WindowManager import androidx.core.content.getSystemService import androidx.core.graphics.createBitmap import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.ui.utils.helper.mainLooperDelayedAction import com.t8rin.logger.makeLog class ScreenshotMaker( private val mediaProjection: MediaProjection, private val context: Context, private val onSuccess: (Bitmap) -> Unit ) : OnImageAvailableListener { private var virtualDisplay: VirtualDisplay? = null private var imageReader: ImageReader? = null @Suppress("DEPRECATION") private val screenSize: IntegerSize = run { val wm = context.getSystemService() ?: return@run context.resources.displayMetrics.run { IntegerSize( width = widthPixels, height = heightPixels ) } val bounds = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { wm.currentWindowMetrics.bounds } else { val display = wm.defaultDisplay val size = Point() display.getRealSize(size) Rect(0, 0, size.x, size.y) } IntegerSize(bounds.width(), bounds.height()) }.also { it.makeLog("Acquired screen size for screenshot") } fun takeScreenshot(delay: Long) { mainLooperDelayedAction(delay) { imageReader = ImageReader.newInstance( screenSize.width, screenSize.height, PixelFormat.RGBA_8888, 1 ) runCatching { virtualDisplay = mediaProjection.createVirtualDisplay( "screenshot", screenSize.width, screenSize.height, context.resources.displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader?.surface, null, null ) imageReader?.setOnImageAvailableListener(this@ScreenshotMaker, null) } } } override fun onImageAvailable(reader: ImageReader) { val image = reader.acquireLatestImage() ?: return takeScreenshot(300) val planes = image.planes val buffer = planes[0].buffer.rewind() val pixelStride = planes[0].pixelStride val rowStride = planes[0].rowStride val rowPadding = rowStride - pixelStride * screenSize.width val bitmap = createBitmap( width = screenSize.width + rowPadding / pixelStride, height = screenSize.height ) bitmap.copyPixelsFromBuffer(buffer) finish() image.close() onSuccess(bitmap) } private fun finish() { virtualDisplay?.release() mediaProjection.stop() imageReader = null } } ================================================ FILE: feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/screenshot/ScreenshotService.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.quick_tiles.screenshot import android.app.Activity.RESULT_CANCELED import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.ClipData import android.content.ClipboardManager import android.content.Intent import android.content.pm.ServiceInfo import android.graphics.Bitmap import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.net.Uri import android.os.Build import android.os.Handler import android.os.IBinder import android.os.Looper import androidx.annotation.RequiresApi import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.content.getSystemService import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.utils.safeConfig import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.FileSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getScreenExtra import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.postToast import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.putScreenExtra import com.t8rin.imagetoolbox.core.ui.utils.helper.IntentUtils.parcelable import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemover import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @RequiresApi(Build.VERSION_CODES.N) @AndroidEntryPoint class ScreenshotService : Service() { @Inject lateinit var fileController: FileController @Inject lateinit var shareProvider: ImageShareProvider @Inject lateinit var imageCompressor: ImageCompressor @Inject lateinit var autoBackgroundRemover: AutoBackgroundRemover @Inject lateinit var dispatchersHolder: DispatchersHolder private val coroutineScope by lazy { CoroutineScope(dispatchersHolder.defaultDispatcher) } private val clipboardManager get() = getSystemService() private val mediaProjectionManager get() = getSystemService() private val screenshotChannel = Channel(Channel.BUFFERED) private var screenshotJob by smartJob() private var timeoutJob by smartJob() override fun onStartCommand( intent: Intent?, flags: Int, startId: Int ): Int = runCatching { startListening() val resultCode = intent?.getIntExtra(RESULT_CODE_EXTRA, RESULT_CANCELED) ?: RESULT_CANCELED val data = intent?.parcelable(DATA_EXTRA) val channelId = 1 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { getSystemService() ?.createNotificationChannel( NotificationChannel( channelId.toString(), "screenshot", NotificationManager.IMPORTANCE_MIN ) ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground( channelId, Notification.Builder(applicationContext, channelId.toString()) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle(getString(R.string.processing_screenshot)) .build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION ) } else { startForeground( channelId, Notification.Builder(applicationContext, channelId.toString()) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle(getString(R.string.processing_screenshot)) .build() ) } } val callback = object : MediaProjection.Callback() {} mediaProjectionManager?.getMediaProjection(resultCode, data!!)?.apply { registerCallback( callback, Handler(Looper.getMainLooper()) ) val screenshotMaker = buildScreenshotMaker( mediaProjection = this, intent = intent ) screenshotMaker.takeScreenshot(1000) } START_REDELIVER_INTENT }.getOrNull() ?: START_REDELIVER_INTENT override fun onDestroy() { screenshotJob?.cancel() timeoutJob?.cancel() super.onDestroy() } override fun onBind(intent: Intent): IBinder? = null private fun startListening() { screenshotJob = coroutineScope.launch { screenshotChannel .receiveAsFlow() .debounce(500.milliseconds) .collectLatest { (intent, output) -> val bitmap = autoBackgroundRemover.trimEmptyParts(output) val resultBitmap = createBitmap( width = bitmap.width, height = bitmap.height, config = bitmap.safeConfig ).applyCanvas { drawColor(Color.Black.toArgb()) drawBitmap(bitmap) } val uri: Uri? = shareProvider.cacheImage( image = resultBitmap, imageInfo = ImageInfo( width = resultBitmap.width, height = resultBitmap.height, imageFormat = ImageFormat.Png.Lossless ) )?.toUri() if (intent?.action != SCREENSHOT_ACTION) { startActivity( Intent(Intent.ACTION_SEND).apply { setPackage(applicationContext.packageName) type = "image/png" putScreenExtra(intent.getScreenExtra()) putExtra(Intent.EXTRA_STREAM, uri) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + Intent.FLAG_GRANT_READ_URI_PERMISSION) } ) } else { fileController.save( saveTarget = FileSaveTarget( filename = "screenshot-${timestamp()}.png", originalUri = "screenshot", imageFormat = ImageFormat.Png.Lossless, data = imageCompressor.compress( image = resultBitmap, imageFormat = ImageFormat.Png.Lossless, quality = Quality.Base() ) ), keepOriginalMetadata = true ) uri?.let { uri -> clipboardManager?.setPrimaryClip( ClipData.newUri( contentResolver, "IMAGE", uri ) ) } postToast( textRes = R.string.saved_to_without_filename, fileController.defaultSavingPath ) } stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() } } timeoutJob = coroutineScope.launch { delay(5.seconds) if (screenshotChannel.isEmpty) { postToast( textRes = R.string.screenshot_not_captured_try_again, isLong = true ) stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() } } } private fun buildScreenshotMaker( mediaProjection: MediaProjection, intent: Intent? ) = ScreenshotMaker( mediaProjection = mediaProjection, context = this, onSuccess = { screenshotChannel.trySend( Screenshot( intent = intent, output = it ) ) } ) private data class Screenshot( val intent: Intent?, val output: Bitmap ) } ================================================ FILE: feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/QuickTile.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.quick_tiles.tiles import android.annotation.SuppressLint import android.app.PendingIntent import android.service.quicksettings.TileService import androidx.core.service.quicksettings.PendingIntentActivityWrapper import androidx.core.service.quicksettings.TileServiceCompat @SuppressLint("NewApi") sealed class QuickTile( private val tileAction: TileAction ) : TileService() { override fun onClick() { super.onClick() runCatching { TileServiceCompat.startActivityAndCollapse( this, PendingIntentActivityWrapper( applicationContext, 0, tileAction.toIntent(this), PendingIntent.FLAG_UPDATE_CURRENT, false ) ) } } } ================================================ FILE: feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/TileAction.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.quick_tiles.tiles import android.content.Context import android.content.Intent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppActivityClass import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.SHORTCUT_OPEN_ACTION import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.buildIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.putScreenExtra import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.feature.quick_tiles.screenshot.SCREENSHOT_ACTION import com.t8rin.imagetoolbox.feature.quick_tiles.screenshot.ScreenshotLauncher internal sealed class TileAction(val clazz: Class<*>) { data class ScreenshotAndOpenScreen(val screen: Screen?) : TileAction(ScreenshotClass) data class OpenScreen(val screen: Screen?) : TileAction(AppActivityClass) data object Screenshot : TileAction(ScreenshotClass) data object OpenApp : TileAction(AppActivityClass) fun toIntent(context: Context): Intent = context.buildIntent(clazz) { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK when (this@TileAction) { OpenApp -> setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) Screenshot -> action = SCREENSHOT_ACTION is ScreenshotAndOpenScreen -> { action = SHORTCUT_OPEN_ACTION putScreenExtra(screen) } is OpenScreen -> { action = SHORTCUT_OPEN_ACTION putScreenExtra(screen) } } } companion object { private val ScreenshotClass = ScreenshotLauncher::class.java } } ================================================ FILE: feature/quick-tiles/src/main/java/com/t8rin/imagetoolbox/feature/quick_tiles/tiles/Tiles.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.quick_tiles.tiles import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.feature.quick_tiles.tiles.TileAction.OpenApp import com.t8rin.imagetoolbox.feature.quick_tiles.tiles.TileAction.OpenScreen import com.t8rin.imagetoolbox.feature.quick_tiles.tiles.TileAction.Screenshot import com.t8rin.imagetoolbox.feature.quick_tiles.tiles.TileAction.ScreenshotAndOpenScreen class ImageToolboxTile : QuickTile( tileAction = OpenApp ) class TakeScreenshotTile : QuickTile( tileAction = Screenshot ) class EditScreenshotTile : QuickTile( tileAction = ScreenshotAndOpenScreen(null) ) class GeneratePaletteTile : QuickTile( tileAction = ScreenshotAndOpenScreen(Screen.PaletteTools()) ) class ColorPickerTile : QuickTile( tileAction = ScreenshotAndOpenScreen(Screen.PickColorFromImage()) ) class QrTile : QuickTile( tileAction = OpenScreen(Screen.ScanQrCode()) ) class DocumentScannerTile : QuickTile( tileAction = OpenScreen(Screen.DocumentScanner) ) class TextRecognitionTile : QuickTile( tileAction = OpenScreen(Screen.RecognizeText()) ) class ResizeAndConvertTile : QuickTile( tileAction = OpenScreen(Screen.ResizeAndConvert()) ) ================================================ FILE: feature/recognize-text/.gitignore ================================================ /build ================================================ FILE: feature/recognize-text/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.recognize.text" dependencies { implementation(projects.core.filters) implementation(projects.feature.pdfTools) implementation(projects.feature.singleEdit) implementation(libs.tesseract) } ================================================ FILE: feature/recognize-text/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/data/AndroidImageTextReader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.data import android.content.Context import android.graphics.Bitmap import androidx.core.net.toUri import com.googlecode.tesseract.android.TessBaseAPI import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.remote.DownloadManager import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.createZip import com.t8rin.imagetoolbox.core.utils.putEntry import com.t8rin.imagetoolbox.feature.recognize.text.domain.DownloadData import com.t8rin.imagetoolbox.feature.recognize.text.domain.ImageTextReader import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.OcrEngineMode import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionData import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType import com.t8rin.imagetoolbox.feature.recognize.text.domain.SegmentationMode import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessParams import com.t8rin.imagetoolbox.feature.recognize.text.domain.TextRecognitionResult import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.util.Locale import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import javax.inject.Inject internal class AndroidImageTextReader @Inject constructor( private val imageGetter: ImageGetter, @ApplicationContext private val context: Context, private val shareProvider: ShareProvider, private val downloadManager: DownloadManager, resourceManager: ResourceManager, dispatchersHolder: DispatchersHolder, appScope: AppScope, ) : ImageTextReader, DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { init { appScope.launch { RecognitionType.entries.forEach { File(getPathFromMode(it), "tessdata").mkdirs() } } } override suspend fun getTextFromImage( type: RecognitionType, languageCode: String, segmentationMode: SegmentationMode, ocrEngineMode: OcrEngineMode, parameters: TessParams, model: Any?, onProgress: (Int) -> Unit ): TextRecognitionResult = withContext(defaultDispatcher) { val empty = TextRecognitionResult.Success(RecognitionData.Empty) if (model == null) return@withContext empty val image = model as? Bitmap ?: (imageGetter.getImage(model) ?: return@withContext empty) val needToDownload = getNeedToDownloadLanguages(type, languageCode) if (needToDownload.isNotEmpty()) { return@withContext TextRecognitionResult.NoData(needToDownload) } return@withContext runCatching { val api = TessBaseAPI { if (isActive) onProgress(it.percent) else return@TessBaseAPI }.apply { val success = init( getPathFromMode(type), languageCode, ocrEngineMode.ordinal ) if (!success) { return@withContext TextRecognitionResult.NoData( getNeedToDownloadLanguages( type = type, languageCode = languageCode ) ).also { it.data.forEach { data -> getModelFile( type = type, languageCode = data.languageCode ).delete() } } } pageSegMode = segmentationMode.ordinal parameters.tessParamList.forEach { param -> setVariable(param.key, param.stringValue) } runCatching { parameters.tessCustomParams.trim().removePrefix("--").split("--").forEach { s -> val (key, value) = s.trim().split(" ").filter { it.isNotEmpty() } setVariable(key, value) } } setImage(image.copy(Bitmap.Config.ARGB_8888, false)) } val hocr = api.getHOCRText(0) val text = api.utF8Text val accuracy = api.meanConfidence() TextRecognitionResult.Success( RecognitionData( text = text, accuracy = if (text.isEmpty()) 0 else accuracy, hocr = hocr ) ) }.let { if (it.isSuccess) { it.getOrNull()!! } else { languageCode.split("+").forEach { code -> getModelFile( type = type, languageCode = code ).delete() } TextRecognitionResult.Error(it.exceptionOrNull()!!) } } } private fun getNeedToDownloadLanguages( type: RecognitionType, languageCode: String ): List { val needToDownload = mutableListOf() languageCode.split("+").filter { it.isNotBlank() }.forEach { code -> if (!isLanguageDataExists(type, code)) { needToDownload.add( DownloadData( type = type, languageCode = code, name = getDisplayName(code, false), localizedName = getDisplayName(code, true) ) ) } } return needToDownload } override fun isLanguageDataExists( type: RecognitionType, languageCode: String ): Boolean = getModelFile( type = type, languageCode = languageCode ).exists() override suspend fun getLanguages( type: RecognitionType ): List = withContext(ioDispatcher) { val codes = context.resources.getStringArray(R.array.key_ocr_engine_language_value) return@withContext codes.mapNotNull { code -> val name = getDisplayName(code, false) val localizedName = getDisplayName(code, true) if (name.isBlank() || localizedName.isBlank()) return@mapNotNull null OCRLanguage( name = name, code = code, downloaded = RecognitionType.entries.filter { isLanguageDataExists( type = it, languageCode = code ) }, localizedName = localizedName ) }.toList() } override fun getLanguageForCode( code: String ): OCRLanguage = OCRLanguage( name = getDisplayName(code, false), code = code, downloaded = RecognitionType.entries.filter { isLanguageDataExists(it, code) }, localizedName = getDisplayName(code, true) ) override suspend fun deleteLanguage( language: OCRLanguage, types: List ) = withContext(ioDispatcher) { types.forEach { type -> getModelFile( type = type, languageCode = language.code ).delete() } } override suspend fun downloadTrainingData( type: RecognitionType, languageCode: String, onProgress: (DownloadProgress) -> Unit ) { val needToDownloadLanguages = getNeedToDownloadLanguages(type, languageCode) if (needToDownloadLanguages.isNotEmpty()) { downloadTrainingDataImpl( type = type, needToDownloadLanguages = needToDownloadLanguages, onProgress = onProgress ) } } private suspend fun downloadTrainingDataImpl( type: RecognitionType, needToDownloadLanguages: List, onProgress: (DownloadProgress) -> Unit ) = needToDownloadLanguages.map { downloadTrainingDataForCode( type = type, lang = it.languageCode, onProgress = onProgress ) } private suspend fun downloadTrainingDataForCode( type: RecognitionType, lang: String, onProgress: (DownloadProgress) -> Unit ) { channelFlow { downloadManager.download( url = when (type) { RecognitionType.Best -> TessConstants.TESSERACT_DATA_DOWNLOAD_URL_BEST RecognitionType.Standard -> TessConstants.TESSERACT_DATA_DOWNLOAD_URL_STANDARD RecognitionType.Fast -> TessConstants.TESSERACT_DATA_DOWNLOAD_URL_FAST }.format(lang), destinationPath = getModelFile( type = type, languageCode = lang ).absolutePath, onProgress = ::trySend, onFinish = { close() } ) }.collect { progress -> onProgress(progress) } } private fun getPathFromMode( type: RecognitionType ): String = File( File(context.filesDir, "tesseract").apply(File::mkdirs), type.displayName ).absolutePath private fun getModelFile( type: RecognitionType, languageCode: String ) = File( "${getPathFromMode(type)}/tessdata", TessConstants.LANGUAGE_CODE.format(languageCode) ) private fun getDisplayName( lang: String?, useDefaultLocale: Boolean ): String { if (lang.isNullOrEmpty()) { return "" } val locale = Locale.forLanguageTag( if (lang.contains("chi_sim")) "zh-CN" else if (lang.contains("chi_tra")) "zh-TW" else lang ) return locale.getDisplayName( if (useDefaultLocale) Locale.getDefault() else locale ).replaceFirstChar { it.uppercase(locale) } } override suspend fun exportLanguagesToZip(): String? = withContext(ioDispatcher) { val zipBytes = ByteArrayOutputStream().apply { createZip { zip -> RecognitionType.entries.forEach { type -> File(getPathFromMode(type), "tessdata").listFiles()?.forEach { file -> zip.putEntry( name = "${type.displayName}/tessdata/${file.name}", input = FileInputStream(file) ) } } } }.toByteArray() shareProvider.cacheByteArray( byteArray = zipBytes, filename = "exported_languages.zip" ) } override suspend fun importLanguagesFromUri( zipUri: String ): Result = withContext(ioDispatcher) { val zipInput = context.contentResolver.openInputStream( zipUri.toUri() ) ?: return@withContext Result.failure(NullPointerException()) runCatching { zipInput.use { inputStream -> ZipInputStream(inputStream).use { zipIn -> var entry: ZipEntry? while (zipIn.nextEntry.also { entry = it } != null) { entry?.let { zipEntry -> val outFile = File( File(context.filesDir, "tesseract").apply(File::mkdirs), zipEntry.name ) outFile.parentFile?.mkdirs() FileOutputStream(outFile).use { fos -> zipIn.copyTo(fos) } zipIn.closeEntry() } } } } } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/di/RecognizeTextModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.di import com.t8rin.imagetoolbox.feature.recognize.text.data.AndroidImageTextReader import com.t8rin.imagetoolbox.feature.recognize.text.domain.ImageTextReader import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface RecognizeTextModule { @Singleton @Binds fun provideImageTextReader( reader: AndroidImageTextReader ): ImageTextReader } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/DownloadData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain data class DownloadData( val type: RecognitionType, val languageCode: String, val name: String, val localizedName: String ) ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/ImageTextReader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress interface ImageTextReader { suspend fun getTextFromImage( type: RecognitionType, languageCode: String, segmentationMode: SegmentationMode, ocrEngineMode: OcrEngineMode, parameters: TessParams, model: Any?, onProgress: (Int) -> Unit ): TextRecognitionResult suspend fun downloadTrainingData( type: RecognitionType, languageCode: String, onProgress: (DownloadProgress) -> Unit ) fun isLanguageDataExists( type: RecognitionType, languageCode: String ): Boolean suspend fun getLanguages( type: RecognitionType ): List fun getLanguageForCode( code: String ): OCRLanguage suspend fun deleteLanguage( language: OCRLanguage, types: List ) suspend fun exportLanguagesToZip(): String? suspend fun importLanguagesFromUri( zipUri: String ): Result } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OCRLanguage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain data class OCRLanguage( val name: String, val localizedName: String, val code: String, val downloaded: List ) { companion object { val Default by lazy { OCRLanguage( name = "English", localizedName = "English", code = "eng", downloaded = emptyList() ) } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/OcrEngineMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain enum class OcrEngineMode { TESSERACT_ONLY, LSTM_ONLY, TESSERACT_LSTM_COMBINED, DEFAULT } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain data class RecognitionData( val text: String, val accuracy: Int, val hocr: String ) { companion object { val Empty = RecognitionData( text = "", accuracy = 0, hocr = "" ) } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/RecognitionType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain enum class RecognitionType(val displayName: String) { Fast("fast"), Standard("standard"), Best("best") } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/SegmentationMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain enum class SegmentationMode { PSM_OSD_ONLY, PSM_AUTO_OSD, PSM_AUTO_ONLY, PSM_AUTO, PSM_SINGLE_COLUMN, PSM_SINGLE_BLOCK_VERT_TEXT, PSM_SINGLE_BLOCK, PSM_SINGLE_LINE, PSM_SINGLE_WORD, PSM_CIRCLE_WORD, PSM_SINGLE_CHAR, PSM_SPARSE_TEXT, PSM_SPARSE_TEXT_OSD, PSM_RAW_LINE } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessConstants.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain internal object TessConstants { const val LANGUAGE_CODE = "%s.traineddata" const val TESSERACT_DATA_DOWNLOAD_URL_BEST = "https://github.com/tesseract-ocr/tessdata_best/raw/refs/heads/main/%s.traineddata" const val TESSERACT_DATA_DOWNLOAD_URL_STANDARD = "https://github.com/tesseract-ocr/tessdata/raw/refs/heads/main/%s.traineddata" const val TESSERACT_DATA_DOWNLOAD_URL_FAST = "https://github.com/tesseract-ocr/tessdata_fast/raw/refs/heads/main/%s.traineddata" const val KEY_PRESERVE_INTERWORD_SPACES = "preserve_interword_spaces" const val KEY_CHOP_ENABLE = "chop_enable" const val KEY_USE_NEW_STATE_COST = "use_new_state_cost" const val KEY_SEGMENT_SEGCOST_RATING = "segment_segcost_rating" const val KEY_ENABLE_NEW_SEGSEARCH = "enable_new_segsearch" const val KEY_LANGUAGE_MODEL_NGRAM_ON = "language_model_ngram_on" const val KEY_TEXTORD_FORCE_MAKE_PROP_WORDS = "textord_force_make_prop_words" const val KEY_EDGES_MAX_CHILDREN_PER_OUTLINE = "edges_max_children_per_outline" } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TessParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_CHOP_ENABLE import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_EDGES_MAX_CHILDREN_PER_OUTLINE import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_ENABLE_NEW_SEGSEARCH import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_LANGUAGE_MODEL_NGRAM_ON import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_PRESERVE_INTERWORD_SPACES import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_SEGMENT_SEGCOST_RATING import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_TEXTORD_FORCE_MAKE_PROP_WORDS import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessConstants.KEY_USE_NEW_STATE_COST import kotlinx.collections.immutable.toImmutableList class TessParam( val key: String, val value: Any ) { val stringValue: String get() = when (value) { is Boolean -> if (value) "1" else "0" else -> value.toString() } fun copy(value: Any) = TessParam( key = key, value = value ) override fun equals(other: Any?): Boolean { if (other !is TessParam) return false if (this.key != other.key) return false return this.value == other.value } override fun hashCode(): Int { var result = key.hashCode() result = 31 * result + value.hashCode() return result } operator fun component1(): String = key operator fun component2(): Any = value } class TessParams private constructor( val tessParamList: List, val tessCustomParams: String = "" ) { fun update( key: String, transform: (Any) -> Any ): TessParams = TessParams( tessParamList = tessParamList.toMutableList().apply { val index = indexOfFirst { it.key == key }.takeIf { it >= 0 } ?: return this@TessParams this[index] = this[index].let { it.copy(value = transform(it.value)) } }.toImmutableList(), tessCustomParams = tessCustomParams ) fun update( newCustomParams: String ): TessParams = TessParams( tessParamList = tessParamList, tessCustomParams = newCustomParams ) companion object { val Default by lazy { TessParams( tessParamList = listOf( KEY_PRESERVE_INTERWORD_SPACES.disabled(), KEY_CHOP_ENABLE.enabled(), KEY_USE_NEW_STATE_COST.disabled(), KEY_SEGMENT_SEGCOST_RATING.disabled(), KEY_ENABLE_NEW_SEGSEARCH.disabled(), KEY_LANGUAGE_MODEL_NGRAM_ON.disabled(), KEY_TEXTORD_FORCE_MAKE_PROP_WORDS.disabled(), KEY_EDGES_MAX_CHILDREN_PER_OUTLINE.int(40) ) ) } private fun String.enabled() = TessParam( key = this, value = true ) private fun String.disabled() = TessParam( key = this, value = false ) private fun String.int(value: Int) = TessParam( key = this, value = value ) } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/domain/TextRecognitionResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.domain sealed interface TextRecognitionResult { data class Error(val throwable: Throwable) : TextRecognitionResult data class Success(val data: RecognitionData) : TextRecognitionResult data class NoData(val data: List) : TextRecognitionResult } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/RecognizeTextContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Save import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.image.urisPreview import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.recognize.text.presentation.components.RecognizeTextButtons import com.t8rin.imagetoolbox.feature.recognize.text.presentation.components.RecognizeTextControls import com.t8rin.imagetoolbox.feature.recognize.text.presentation.components.RecognizeTextDownloadDataDialog import com.t8rin.imagetoolbox.feature.recognize.text.presentation.components.RecognizeTextNoDataControls import com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent import com.t8rin.imagetoolbox.feature.single_edit.presentation.components.CropEditOption @Composable fun RecognizeTextContent( component: RecognizeTextComponent ) { val type = component.type val isExtraction = type is Screen.RecognizeText.Type.Extraction val isHaveText = component.editedText.orEmpty().isNotEmpty() AutoContentBasedColors( model = (type as? Screen.RecognizeText.Type.Extraction)?.uri ) LaunchedEffect(component.previewBitmap, component.filtersAdded) { if (component.previewBitmap != null) component.startRecognition() } val multipleImagePicker = rememberImagePicker { uris: List -> when { isExtraction || (uris.size == 1) -> { component.updateType( type = Screen.RecognizeText.Type.Extraction(uris.firstOrNull()) ) } type is Screen.RecognizeText.Type.WriteToFile -> { component.updateType( type = Screen.RecognizeText.Type.WriteToFile(uris) ) } type is Screen.RecognizeText.Type.WriteToMetadata -> { component.updateType( type = Screen.RecognizeText.Type.WriteToMetadata(uris) ) } type is Screen.RecognizeText.Type.WriteToSearchablePdf -> { component.updateType( type = Screen.RecognizeText.Type.WriteToSearchablePdf(uris) ) } type == null -> { component.showSelectionTypeSheet(uris) } } } val addImagesImagePicker = rememberImagePicker { uris: List -> when (type) { is Screen.RecognizeText.Type.WriteToFile -> { component.updateType( type = Screen.RecognizeText.Type.WriteToFile( type.uris?.plus(uris)?.distinct() ) ) } is Screen.RecognizeText.Type.WriteToMetadata -> { component.updateType( type = Screen.RecognizeText.Type.WriteToMetadata( type.uris?.plus(uris)?.distinct() ) ) } is Screen.RecognizeText.Type.WriteToSearchablePdf -> { component.updateType( type = Screen.RecognizeText.Type.WriteToSearchablePdf( type.uris?.plus(uris)?.distinct() ) ) } else -> Unit } } AutoFilePicker( onAutoPick = multipleImagePicker::pickImage, isPickedAlready = component.initialType != null ) val isPortrait by isPortraitOrientationAsState() val saveLauncher = rememberFileCreator( mimeType = MimeType.Txt, onSuccess = component::saveContentToTxt ) var showCropper by rememberSaveable { mutableStateOf(false) } AdaptiveLayoutScreen( shouldDisableBackHandler = true, title = { AnimatedContent( targetState = component.recognitionData to type ) { (data, type) -> TopAppBarTitle( title = if (data == null) { when (type) { null -> stringResource(R.string.recognize_text) else -> stringResource(type.title) } } else { stringResource( R.string.accuracy, data.accuracy ) }, input = type, isLoading = component.isTextLoading, size = null ) } }, onGoBack = component.onGoBack, topAppBarPersistentActions = { if (type == null) TopAppBarEmoji() if (type is Screen.RecognizeText.Type.Extraction) { var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomButton( onClick = { showZoomSheet = true }, visible = true ) ZoomModalSheet( data = type.uri, visible = showZoomSheet, onDismiss = { showZoomSheet = false }, transformations = component.getTransformations() ) } }, actions = { ShareButton( onShare = { if (isExtraction) { component.shareEditedText() } else { component.shareData() } }, enabled = isHaveText || !isExtraction ) if (isExtraction) { EnhancedIconButton( onClick = { saveLauncher.make(component.generateTextFilename()) }, enabled = !component.text.isNullOrEmpty() ) { Icon( imageVector = Icons.Outlined.Save, contentDescription = null ) } } }, imagePreview = { if (isExtraction) { Box( modifier = Modifier .container() .padding(4.dp) .animateContentSizeNoClip( alignment = Alignment.Center ), contentAlignment = Alignment.Center ) { Picture( model = component.previewBitmap, contentScale = ContentScale.FillBounds, modifier = Modifier.aspectRatio( component.previewBitmap?.safeAspectRatio ?: 1f ), transformations = component.getTransformations(), shape = MaterialTheme.shapes.medium, isLoadingFromDifferentPlace = component.isImageLoading ) } } else { UrisPreview( modifier = Modifier.urisPreview(), uris = component.uris, isPortrait = true, onRemoveUri = component::removeUri, onAddUris = addImagesImagePicker::pickImage ) } }, showImagePreviewAsStickyHeader = isExtraction, controls = { RecognizeTextControls( component = component, onShowCropper = { showCropper = true } ) }, buttons = { actions -> RecognizeTextButtons( component = component, multipleImagePicker = multipleImagePicker, actions = actions ) }, noDataControls = { RecognizeTextNoDataControls(component) }, insetsForNoData = WindowInsets(0), contentPadding = animateDpAsState( if (component.type == null) 12.dp else 20.dp ).value, canShowScreenData = type != null ) RecognizeTextDownloadDataDialog(component) LoadingDialog( visible = component.isExporting || component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving, canCancel = component.isSaving ) CropEditOption( visible = showCropper, onDismiss = { showCropper = false }, useScaffold = isPortrait, bitmap = component.previewBitmap, onGetBitmap = component::updateBitmap, cropProperties = component.cropProperties, setCropAspectRatio = component::setCropAspectRatio, setCropMask = component::setCropMask, selectedAspectRatio = component.selectedAspectRatio, loadImage = component::loadImage ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DeleteLanguageDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType @Composable internal fun DeleteLanguageDialog( languageToDelete: OCRLanguage?, onDismiss: () -> Unit, onDeleteLanguage: (OCRLanguage, List) -> Unit, currentRecognitionType: RecognitionType ) { EnhancedAlertDialog( visible = languageToDelete != null, icon = { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null ) }, title = { Text(stringResource(id = R.string.delete)) }, text = { Text( stringResource( id = R.string.delete_language_sub, languageToDelete?.name ?: "", currentRecognitionType.displayName ) ) }, onDismissRequest = onDismiss, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.error, onClick = { languageToDelete?.let { onDeleteLanguage(it, listOf(currentRecognitionType)) } onDismiss() } ) { Text(stringResource(R.string.current)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.errorContainer, onClick = { languageToDelete?.let { onDeleteLanguage(it, RecognitionType.entries) } onDismiss() } ) { Text(stringResource(R.string.all)) } } ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadLanguageDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Download import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isNetworkAvailable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.BasicEnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun DownloadLanguageDialog( downloadDialogData: List, onDownloadRequest: (List) -> Unit, downloadProgress: Float, dataRemaining: String, onNoConnection: () -> Unit, onDismiss: () -> Unit ) { val context = LocalContext.current var downloadStarted by rememberSaveable(downloadDialogData) { mutableStateOf(false) } EnhancedAlertDialog( visible = !downloadStarted, icon = { Icon( imageVector = Icons.Outlined.Download, contentDescription = null ) }, title = { Text(stringResource(id = R.string.no_data)) }, text = { Text( stringResource( id = R.string.download_description, downloadDialogData.firstOrNull()?.type?.displayName ?: "", downloadDialogData.joinToString(separator = ", ") { it.localizedName } ) ) }, onDismissRequest = {}, confirmButton = { EnhancedButton( onClick = { if (context.isNetworkAvailable()) { downloadDialogData.let { downloadData -> onDownloadRequest(downloadData) downloadStarted = true } } else onNoConnection() } ) { Text(stringResource(R.string.download)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { Text(stringResource(R.string.close)) } } ) BasicEnhancedAlertDialog( onDismissRequest = {}, visible = downloadStarted, modifier = Modifier.fillMaxSize() ) { EnhancedLoadingIndicator( progress = downloadProgress, loaderSize = 64.dp ) { AutoSizeText( text = dataRemaining, maxLines = 1, fontWeight = FontWeight.Medium, modifier = Modifier.width(it * 0.8f), textAlign = TextAlign.Center, style = LocalTextStyle.current.copy( fontSize = 12.sp, lineHeight = 12.sp ) ) } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/DownloadedLanguageItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Cancel import androidx.compose.material.icons.rounded.CheckCircle import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.Green import com.t8rin.imagetoolbox.core.ui.theme.Red import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCheckbox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsCombinedClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.RevealDirection import com.t8rin.imagetoolbox.core.ui.widget.other.RevealValue import com.t8rin.imagetoolbox.core.ui.widget.other.SwipeToReveal import com.t8rin.imagetoolbox.core.ui.widget.other.rememberRevealState import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType import kotlinx.coroutines.launch @Composable internal fun LazyItemScope.DownloadedLanguageItem( index: Int, value: List, lang: OCRLanguage, downloadedLanguages: List, onWantDelete: (OCRLanguage) -> Unit, onValueChange: (Boolean, OCRLanguage) -> Unit, onValueChangeForced: (List, RecognitionType) -> Unit, currentRecognitionType: RecognitionType ) { val settingsState = LocalSettingsState.current val selected by remember(value, lang) { derivedStateOf { lang in value } } val scope = rememberCoroutineScope() val state = rememberRevealState() val interactionSource = remember { MutableInteractionSource() } val isDragged by interactionSource.collectIsDraggedAsState() val shape = ShapeDefaults.byIndex( index = index, size = downloadedLanguages.size, forceDefault = isDragged ) SwipeToReveal( state = state, modifier = Modifier.animateItem(), revealedContentEnd = { Box( Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.errorContainer, shape = shape, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } onWantDelete(lang) } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete), modifier = Modifier .padding(16.dp) .padding(end = 8.dp) .align(Alignment.CenterEnd), tint = MaterialTheme.colorScheme.onErrorContainer ) } }, directions = setOf(RevealDirection.EndToStart), swipeableContent = { Row( modifier = Modifier .fillMaxWidth() .container( shape = shape, color = animateColorAsState( if (selected) { MaterialTheme .colorScheme .mixedContainer .copy(0.8f) } else EnhancedBottomSheetDefaults.contentContainerColor ).value, resultPadding = 0.dp ) .hapticsCombinedClickable( onLongClick = { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } }, onClick = { onValueChange(selected, lang) } ) .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { AnimatedVisibility(visible = value.size > 1) { LocalMinimumInteractiveComponentSize.ProvidesValue( Dp.Unspecified ) { EnhancedCheckbox( checked = selected, onCheckedChange = { onValueChange(selected, lang) }, modifier = Modifier.padding(end = 8.dp) ) } } Column { Text( text = lang.name, style = LocalTextStyle.current.copy( fontSize = 16.sp, fontWeight = FontWeight.Medium, lineHeight = 18.sp ) ) if (lang.name != lang.localizedName) { Spacer(modifier = Modifier.height(2.dp)) Text( text = lang.localizedName, fontSize = 12.sp, fontWeight = FontWeight.Normal, lineHeight = 14.sp, color = LocalContentColor.current.copy(alpha = 0.5f) ) } } Spacer(modifier = Modifier.weight(1f)) Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.container() ) { RecognitionType.entries.forEach { type -> Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier .clip(ShapeDefaults.circle) .hapticsClickable { onValueChangeForced(value, type) } ) { val notDownloaded by remember( type, lang.downloaded ) { derivedStateOf { type !in lang.downloaded } } val displayName by remember(type) { derivedStateOf { type.displayName.first().uppercase() } } val green = Green val red = Red val color by remember( currentRecognitionType, red, green, lang.downloaded ) { derivedStateOf { when (type) { currentRecognitionType -> if (type in lang.downloaded) { green } else red !in lang.downloaded -> red.copy( 0.3f ) else -> green.copy(0.3f) } } } Text( text = displayName, fontSize = 12.sp ) Spacer(modifier = Modifier.height(4.dp)) Icon( imageVector = if (notDownloaded) { Icons.Rounded.Cancel } else Icons.Rounded.CheckCircle, contentDescription = null, tint = animateColorAsState(color).value, modifier = Modifier .size(28.dp) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant(), shape = ShapeDefaults.circle ) ) } } } } }, interactionSource = interactionSource ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FillableButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.padding import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCircleShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun FillableButton( modifier: Modifier, onClick: () -> Unit, content: @Composable RowScope.() -> Unit ) { Row( modifier = modifier .container( color = MaterialTheme.colorScheme.secondaryContainer.copy(0.5f), shape = AutoCircleShape(), resultPadding = 0.dp ) .hapticsClickable(onClick = onClick) .padding(ButtonDefaults.ContentPadding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { CompositionLocalProvider( LocalContentColor provides MaterialTheme.colorScheme.onSecondaryContainer ) { content() } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/FilterSelectionBar.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.then import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText @Composable fun FilterSelectionBar( addedFilters: List>, onContrastClick: () -> Unit, onThresholdClick: () -> Unit, onSharpnessClick: () -> Unit, modifier: Modifier = Modifier ) { val selectedIndices = remember(addedFilters) { derivedStateOf { setOfNotNull( addedFilters.filterIsInstance().isNotEmpty().then(0), addedFilters.filterIsInstance().isNotEmpty().then(1), addedFilters.filterIsInstance().isNotEmpty().then(2), ) } }.value EnhancedButtonGroup( itemCount = 3, modifier = modifier.container( ShapeDefaults.extraLarge ), activeButtonColor = MaterialTheme.colorScheme.secondaryContainer, selectedIndices = selectedIndices, onIndexChange = { when (it) { 0 -> onContrastClick() 1 -> onSharpnessClick() 2 -> onThresholdClick() } }, title = { Text( text = stringResource(id = R.string.transformations), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium, modifier = Modifier.padding(vertical = 8.dp) ) }, itemContent = { val text = when (it) { 0 -> stringResource(id = R.string.contrast) 1 -> stringResource(id = R.string.sharpen) else -> stringResource(id = R.string.threshold) } AutoSizeText( text = text, maxLines = 1 ) }, isScrollable = false ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/ModelTypeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Segment import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.recognize.text.domain.SegmentationMode @Composable fun ModelTypeSelector( value: SegmentationMode, onValueChange: (SegmentationMode) -> Unit ) { var showSelectionSheet by remember { mutableStateOf(false) } PreferenceItem( modifier = Modifier.fillMaxWidth(), title = stringResource(id = R.string.segmentation_mode), subtitle = stringResource(id = value.title), onClick = { showSelectionSheet = true }, shape = ShapeDefaults.extraLarge, startIcon = Icons.AutoMirrored.Outlined.Segment, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showSelectionSheet, onDismiss = { showSelectionSheet = it }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showSelectionSheet = false } ) { Text(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(id = R.string.segmentation_mode), icon = Icons.AutoMirrored.Outlined.Segment ) } ) { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = SegmentationMode.entries, key = { _, e -> e.name } ) { index, mode -> PreferenceItem( modifier = Modifier.fillMaxWidth(), title = stringResource(id = mode.title), onClick = { onValueChange(mode) }, containerColor = animateColorAsState( if (value == mode) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surfaceContainer ).value, shape = ShapeDefaults.byIndex( index = index, size = SegmentationMode.entries.size ) ) } } } } private inline val SegmentationMode.title: Int get() = when (this) { SegmentationMode.PSM_OSD_ONLY -> R.string.segmentation_mode_osd_only SegmentationMode.PSM_AUTO_OSD -> R.string.segmentation_mode_auto_osd SegmentationMode.PSM_AUTO_ONLY -> R.string.segmentation_mode_auto_only SegmentationMode.PSM_AUTO -> R.string.segmentation_mode_auto SegmentationMode.PSM_SINGLE_COLUMN -> R.string.segmentation_mode_single_column SegmentationMode.PSM_SINGLE_BLOCK_VERT_TEXT -> R.string.segmentation_mode_single_block_vert_text SegmentationMode.PSM_SINGLE_BLOCK -> R.string.segmentation_mode_single_block SegmentationMode.PSM_SINGLE_LINE -> R.string.segmentation_mode_single_line SegmentationMode.PSM_SINGLE_WORD -> R.string.segmentation_mode_single_word SegmentationMode.PSM_CIRCLE_WORD -> R.string.segmentation_mode_circle_word SegmentationMode.PSM_SINGLE_CHAR -> R.string.segmentation_mode_single_char SegmentationMode.PSM_SPARSE_TEXT -> R.string.segmentation_mode_sparse_text SegmentationMode.PSM_SPARSE_TEXT_OSD -> R.string.segmentation_mode_sparse_text_osd SegmentationMode.PSM_RAW_LINE -> R.string.segmentation_mode_raw_line } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguageColumnForSearch.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.MaterialTheme import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType @Composable internal fun OCRLanguageColumnForSearch( languagesForSearch: List, value: List, currentRecognitionType: RecognitionType, onValueChange: (List, RecognitionType) -> Unit, allowMultipleLanguagesSelection: Boolean ) { fun onValueChangeImpl( selected: Boolean, type: RecognitionType, lang: OCRLanguage ) { if (allowMultipleLanguagesSelection) { if (selected) { onValueChange( (value - lang).distinct(), type ) } else onValueChange( (value + lang).distinct(), type ) } else onValueChange(listOf(lang), type) } LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = languagesForSearch, key = { _, l -> l.code } ) { index, lang -> val selected by remember(value, lang) { derivedStateOf { lang in value } } PreferenceItem( title = lang.name, subtitle = lang.localizedName.takeIf { it != lang.name }, onClick = { onValueChangeImpl( selected = selected, type = currentRecognitionType, lang = lang ) }, containerColor = animateColorAsState( if (selected) { MaterialTheme.colorScheme.surfaceColorAtElevation( 20.dp ) } else EnhancedBottomSheetDefaults.contentContainerColor ).value, shape = ShapeDefaults.byIndex( index = index, size = languagesForSearch.size ), modifier = Modifier .animateItem() .fillMaxWidth() ) } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRLanguagesColumn.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.UploadFile import androidx.compose.material.icons.rounded.Download import androidx.compose.material.icons.rounded.DownloadDone import androidx.compose.material.icons.rounded.MultipleStop import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.DownloadFile import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.negativePadding import com.t8rin.imagetoolbox.core.ui.widget.other.GradientEdge import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType @Composable internal fun OCRLanguagesColumn( listState: LazyListState, allowMultipleLanguagesSelection: Boolean, value: List, currentRecognitionType: RecognitionType, onValueChange: (List, RecognitionType) -> Unit, onImportLanguages: () -> Unit, onExportLanguages: () -> Unit, downloadedLanguages: List, notDownloadedLanguages: List, onDeleteLanguage: (OCRLanguage, List) -> Unit, onToggleAllowMultipleLanguagesSelection: () -> Unit ) { fun onValueChangeImpl( selected: Boolean, type: RecognitionType, lang: OCRLanguage ) { if (allowMultipleLanguagesSelection) { if (selected) { onValueChange( (value - lang).distinct(), type ) } else onValueChange( (value + lang).distinct(), type ) } else onValueChange(listOf(lang), type) } var deleteDialogData by remember { mutableStateOf(null) } DeleteLanguageDialog( languageToDelete = deleteDialogData, onDismiss = { deleteDialogData = null }, onDeleteLanguage = onDeleteLanguage, currentRecognitionType = currentRecognitionType ) LazyColumn( state = listState, contentPadding = PaddingValues( start = 16.dp, bottom = 16.dp, end = 16.dp ), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { stickyHeader { Column( modifier = Modifier .negativePadding(horizontal = 16.dp) .background(EnhancedBottomSheetDefaults.containerColor) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(20.dp)) PreferenceRowSwitch( title = stringResource(R.string.allow_multiple_languages), containerColor = animateColorAsState( if (allowMultipleLanguagesSelection) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainer ).value, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.extremeLarge, checked = allowMultipleLanguagesSelection, startIcon = Icons.Rounded.MultipleStop, onClick = { if (!it) { onValueChange( value.take(1), currentRecognitionType ) } onToggleAllowMultipleLanguagesSelection() } ) Spacer(modifier = Modifier.height(8.dp)) } GradientEdge( modifier = Modifier .fillMaxWidth() .height(16.dp), startColor = EnhancedBottomSheetDefaults.containerColor, endColor = Color.Transparent ) } item { Column( modifier = Modifier .container( shape = ShapeDefaults.large, color = EnhancedBottomSheetDefaults.contentContainerColor, resultPadding = 0.dp ) .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(modifier = Modifier.height(8.dp)) Text( text = stringResource(R.string.backup_ocr_models), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.height(16.dp)) Row( modifier = Modifier.fillMaxWidth() ) { FillableButton( onClick = onImportLanguages, modifier = Modifier.weight(1f) ) { Icon( imageVector = Icons.Outlined.DownloadFile, contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) Text(text = stringResource(R.string.import_word)) } Spacer(modifier = Modifier.width(4.dp)) FillableButton( onClick = onExportLanguages, modifier = Modifier.weight(1f) ) { Icon( imageVector = Icons.Outlined.UploadFile, contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) Text(text = stringResource(R.string.export)) } } } } if (downloadedLanguages.isNotEmpty()) { item { TitleItem( icon = Icons.Rounded.DownloadDone, text = stringResource(id = R.string.downloaded_languages) ) } } itemsIndexed( items = downloadedLanguages, key = { _, l -> l.code } ) { index, lang -> DownloadedLanguageItem( index = index, value = value, lang = lang, downloadedLanguages = downloadedLanguages, onWantDelete = { deleteDialogData = it }, onValueChange = { selected, language -> onValueChangeImpl( selected = selected, type = currentRecognitionType, lang = language ) }, onValueChangeForced = onValueChange, currentRecognitionType = currentRecognitionType ) } if (notDownloadedLanguages.isNotEmpty()) { item { TitleItem( icon = Icons.Rounded.Download, text = stringResource(id = R.string.available_languages) ) } } itemsIndexed( items = notDownloadedLanguages, key = { _, l -> l.code } ) { index, lang -> val selected by remember(value, lang) { derivedStateOf { lang in value } } PreferenceItem( title = lang.name, subtitle = lang.localizedName.takeIf { it != lang.name }, onClick = { onValueChangeImpl( selected = selected, type = currentRecognitionType, lang = lang ) }, containerColor = animateColorAsState( if (selected) { MaterialTheme.colorScheme.surfaceColorAtElevation(20.dp) } else EnhancedBottomSheetDefaults.contentContainerColor ).value, shape = ShapeDefaults.byIndex( index = index, size = notDownloadedLanguages.size ), modifier = Modifier .animateItem() .fillMaxWidth() ) } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OCRTextPreviewItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.KeyboardArrowDown import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun OCRTextPreviewItem( text: String?, onTextEdit: (String) -> Unit, isLoading: Boolean, loadingProgress: Int, accuracy: Int ) { var expanded by rememberSaveable { mutableStateOf(true) } AnimatedContent(targetState = isLoading) { loading -> Box( modifier = Modifier .fillMaxWidth() .container(shape = ShapeDefaults.extraLarge) .padding(8.dp), contentAlignment = Alignment.Center ) { if (loading) { Box( modifier = Modifier .fillMaxWidth() .border( width = 2.dp, color = MaterialTheme.colorScheme.outline, shape = ShapeDefaults.small ), contentAlignment = Alignment.Center ) { Box( modifier = Modifier .padding(24.dp) .size(72.dp) ) { EnhancedLoadingIndicator( progress = loadingProgress / 100f, loaderSize = 36.dp ) } } } else { Column( Modifier .fillMaxWidth() .border( width = 2.dp, color = MaterialTheme.colorScheme.outline, shape = ShapeDefaults.small ) .padding(16.dp) .animateContentSizeNoClip() ) { Row( verticalAlignment = Alignment.Top ) { Text( text = stringResource(R.string.accuracy, accuracy), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Start, color = MaterialTheme.colorScheme.outline, modifier = Modifier.weight(1f) ) if ((text?.length ?: 0) >= 100) { val rotation by animateFloatAsState( if (expanded) 180f else 0f ) EnhancedIconButton( forceMinimumInteractiveComponentSize = false, containerColor = Color.Transparent, onClick = { expanded = !expanded }, modifier = Modifier.size(24.dp) ) { Icon( imageVector = Icons.Rounded.KeyboardArrowDown, contentDescription = "Expand", modifier = Modifier.rotate(rotation) ) } } } AnimatedContent( targetState = expanded, transitionSpec = { fadeIn(tween(200)) togetherWith fadeOut(tween(200)) } ) { showFull -> SelectionContainer { if (showFull) { BasicTextField( value = text ?: stringResource(R.string.picture_has_no_text), onValueChange = onTextEdit, enabled = text != null, textStyle = LocalTextStyle.current.copy( color = LocalContentColor.current ), cursorBrush = SolidColor(MaterialTheme.colorScheme.primary) ) } else { Text( text = text ?: stringResource(R.string.picture_has_no_text), maxLines = if (text == null) Int.MAX_VALUE else 3, overflow = TextOverflow.Ellipsis, style = LocalTextStyle.current ) } } } } } } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/OcrEngineModeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Engineering import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.recognize.text.domain.OcrEngineMode @Composable fun OcrEngineModeSelector( value: OcrEngineMode, onValueChange: (OcrEngineMode) -> Unit ) { var showSelectionSheet by remember { mutableStateOf(false) } PreferenceItem( modifier = Modifier.fillMaxWidth(), title = stringResource(id = R.string.engine_mode), subtitle = stringResource(id = value.title), onClick = { showSelectionSheet = true }, shape = ShapeDefaults.extraLarge, startIcon = Icons.Outlined.Engineering, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showSelectionSheet, onDismiss = { showSelectionSheet = it }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showSelectionSheet = false } ) { Text(stringResource(R.string.close)) } }, title = { TitleItem( text = stringResource(id = R.string.engine_mode), icon = Icons.Outlined.Engineering ) } ) { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed(OcrEngineMode.entries) { index, mode -> PreferenceItem( modifier = Modifier.fillMaxWidth(), title = stringResource(id = mode.title), onClick = { onValueChange(mode) }, containerColor = animateColorAsState( if (value == mode) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surfaceContainer ).value, shape = ShapeDefaults.byIndex( index = index, size = OcrEngineMode.entries.size ) ) } } } } private inline val OcrEngineMode.title: Int get() = when (this) { OcrEngineMode.TESSERACT_ONLY -> R.string.legacy OcrEngineMode.LSTM_ONLY -> R.string.lstm_network OcrEngineMode.TESSERACT_LSTM_COMBINED -> R.string.legacy_and_lstm OcrEngineMode.DEFAULT -> R.string.defaultt } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognitionTypeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType @Composable fun RecognitionTypeSelector( value: RecognitionType, onValueChange: (RecognitionType) -> Unit, modifier: Modifier = Modifier ) { Box( modifier = modifier .container(shape = ShapeDefaults.extraLarge) .animateContentSizeNoClip(), contentAlignment = Alignment.Center ) { EnhancedButtonGroup( modifier = Modifier.padding(8.dp), enabled = true, items = RecognitionType.entries.map { it.translatedName }, selectedIndex = RecognitionType.entries.indexOf(value), title = stringResource(id = R.string.recognition_type), onIndexChange = { onValueChange(RecognitionType.entries[it]) } ) } } private val RecognitionType.translatedName: String @Composable get() = when (this) { RecognitionType.Best -> stringResource(id = R.string.best) RecognitionType.Fast -> stringResource(id = R.string.fast) RecognitionType.Standard -> stringResource(id = R.string.standard) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.outlined.Language import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Search import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType @Composable fun RecognizeLanguageSelector( currentRecognitionType: RecognitionType, value: List, availableLanguages: List, onValueChange: (List, RecognitionType) -> Unit, onDeleteLanguage: (OCRLanguage, List) -> Unit, onImportLanguages: () -> Unit, onExportLanguages: () -> Unit ) { var showDetailedLanguageSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( modifier = Modifier.fillMaxWidth(), title = stringResource(id = R.string.language), subtitle = value.joinToString(separator = ", ") { it.localizedName }, onClick = { showDetailedLanguageSheet = true }, containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, shape = ShapeDefaults.extraLarge, startIcon = Icons.Outlined.Language, endIcon = Icons.Rounded.MiniEdit ) var isSearching by rememberSaveable { mutableStateOf(false) } var searchKeyword by rememberSaveable { mutableStateOf("") } var allowMultipleLanguagesSelection by rememberSaveable { mutableStateOf(value.isNotEmpty()) } EnhancedModalBottomSheet( visible = showDetailedLanguageSheet, onDismiss = { showDetailedLanguageSheet = it }, enableBottomContentWeight = false, confirmButton = {}, title = { AnimatedContent( targetState = isSearching ) { searching -> if (searching) { BackHandler { searchKeyword = "" isSearching = false } ProvideTextStyle(value = MaterialTheme.typography.bodyLarge) { RoundedTextField( maxLines = 1, hint = { Text(stringResource(id = R.string.search_here)) }, keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, autoCorrectEnabled = null ), value = searchKeyword, onValueChange = { searchKeyword = it }, startIcon = { EnhancedIconButton( onClick = { searchKeyword = "" isSearching = false }, modifier = Modifier.padding(start = 4.dp) ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit), tint = MaterialTheme.colorScheme.onSurface ) } }, endIcon = { AnimatedVisibility( visible = searchKeyword.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( onClick = { searchKeyword = "" }, modifier = Modifier.padding(end = 4.dp) ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close), tint = MaterialTheme.colorScheme.onSurface ) } } }, shape = ShapeDefaults.circle ) } } else { Row( verticalAlignment = Alignment.CenterVertically ) { TitleItem( text = stringResource(id = R.string.language), icon = Icons.Outlined.Language ) Spacer(modifier = Modifier.weight(1f)) EnhancedIconButton( onClick = { isSearching = true }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Icon( imageVector = Icons.Rounded.Search, contentDescription = stringResource(R.string.search_here) ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showDetailedLanguageSheet = false } ) { Text(stringResource(R.string.close)) } } } } }, sheetContent = { RecognizeLanguageSelectorSheetContent( value = value, currentRecognitionType = currentRecognitionType, onValueChange = onValueChange, onImportLanguages = onImportLanguages, onExportLanguages = onExportLanguages, isSearching = isSearching, searchKeyword = searchKeyword, availableLanguages = availableLanguages, onDeleteLanguage = onDeleteLanguage, allowMultipleLanguagesSelection = allowMultipleLanguagesSelection, onToggleAllowMultipleLanguagesSelection = { allowMultipleLanguagesSelection = !allowMultipleLanguagesSelection } ) } ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeLanguageSelectorSheetContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType import kotlinx.coroutines.delay @Composable internal fun RecognizeLanguageSelectorSheetContent( value: List, currentRecognitionType: RecognitionType, onValueChange: (List, RecognitionType) -> Unit, onImportLanguages: () -> Unit, onExportLanguages: () -> Unit, isSearching: Boolean, searchKeyword: String, availableLanguages: List, onDeleteLanguage: (OCRLanguage, List) -> Unit, allowMultipleLanguagesSelection: Boolean, onToggleAllowMultipleLanguagesSelection: () -> Unit ) { val downloadedLanguages by remember(availableLanguages) { derivedStateOf { availableLanguages.filter { it.downloaded.isNotEmpty() }.sortedByDescending { it.downloaded.size } } } val notDownloadedLanguages by remember(availableLanguages, value) { derivedStateOf { availableLanguages.filter { it.downloaded.isEmpty() }.sortedByDescending { it in value } } } var languagesForSearch by remember { mutableStateOf( downloadedLanguages + notDownloadedLanguages ) } LaunchedEffect(searchKeyword) { delay(400L) // Debounce calculations if (searchKeyword.isEmpty()) { languagesForSearch = downloadedLanguages + notDownloadedLanguages return@LaunchedEffect } languagesForSearch = (downloadedLanguages + notDownloadedLanguages).filter { it.name.contains( other = searchKeyword, ignoreCase = true ).or( it.localizedName.contains( other = searchKeyword, ignoreCase = true ) ) }.sortedBy { it.name } } AnimatedContent(targetState = value.isEmpty()) { loading -> if (loading) { Box( modifier = Modifier .fillMaxWidth() .padding(16.dp), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } else { val listState = rememberLazyListState() LaunchedEffect(downloadedLanguages) { downloadedLanguages.indexOf(value.firstOrNull()).takeIf { it != -1 }.let { listState.scrollToItem(it ?: 0) } } AnimatedContent( targetState = isSearching to languagesForSearch.isNotEmpty() ) { (searching, haveData) -> if (searching) { if (haveData) { OCRLanguageColumnForSearch( languagesForSearch = languagesForSearch, value = value, currentRecognitionType = currentRecognitionType, onValueChange = onValueChange, allowMultipleLanguagesSelection = allowMultipleLanguagesSelection ) } else { Column( modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.5f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } } else { OCRLanguagesColumn( listState = listState, allowMultipleLanguagesSelection = allowMultipleLanguagesSelection, value = value, currentRecognitionType = currentRecognitionType, onValueChange = onValueChange, onImportLanguages = onImportLanguages, onExportLanguages = onExportLanguages, downloadedLanguages = downloadedLanguages, notDownloadedLanguages = notDownloadedLanguages, onDeleteLanguage = onDeleteLanguage, onToggleAllowMultipleLanguagesSelection = onToggleAllowMultipleLanguagesSelection ) } } } } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.foundation.layout.RowScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CopyAll import androidx.compose.material.icons.rounded.Save import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent @Composable internal fun RecognizeTextButtons( component: RecognizeTextComponent, multipleImagePicker: ImagePicker, actions: @Composable RowScope.() -> Unit ) { val isPortrait by isPortraitOrientationAsState() val type = component.type val isExtraction = type is Screen.RecognizeText.Type.Extraction val isHaveText = component.editedText.orEmpty().isNotEmpty() val copyText: () -> Unit = { component.editedText?.let(Clipboard::copy) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } val saveSearchablePdfLauncher = rememberFileCreator( mimeType = MimeType.Pdf, onSuccess = component::saveSearchablePdfTo ) val save: (oneTimeSaveLocationUri: String?) -> Unit = { component.save( oneTimeSaveLocationUri = it ) } BottomButtonsBlock( isNoData = type == null, onSecondaryButtonClick = multipleImagePicker::pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, onPrimaryButtonClick = { if (isExtraction) { copyText() } else if (type is Screen.RecognizeText.Type.WriteToSearchablePdf) { saveSearchablePdfLauncher.make(component.generateSearchablePdfFilename()) } else { save(null) } }, onPrimaryButtonLongClick = { if (isExtraction) { copyText() } else { showFolderSelectionDialog = true } }, primaryButtonIcon = if (isExtraction) { Icons.Rounded.CopyAll } else { Icons.Rounded.Save }, isPrimaryButtonVisible = if (isExtraction) isHaveText else type != null, actions = { if (isPortrait) actions() }, showNullDataButtonAsContainer = true ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = save ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = multipleImagePicker, visible = showOneTimeImagePickingDialog ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import android.net.Uri import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CameraAlt import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.CropSmall import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ImagePickerMode import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.localImagePickerMode import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageTransformBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.other.LinkPreviewList import com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent @Composable internal fun RecognizeTextControls( component: RecognizeTextComponent, onShowCropper: () -> Unit ) { val type = component.type val isExtraction = type is Screen.RecognizeText.Type.Extraction val imagePickerMode = localImagePickerMode(Picker.Single) val editedText = component.editedText val captureImageLauncher = rememberImagePicker(ImagePickerMode.CameraCapture) { list -> component.updateType( type = Screen.RecognizeText.Type.Extraction(list.firstOrNull()) ) } val captureImage = captureImageLauncher::pickImage val exportLanguagesPicker = rememberFileCreator( mimeType = MimeType.Zip, onSuccess = component::exportLanguagesTo ) val importLanguagesPicker = rememberFilePicker( mimeType = MimeType.Zip, onSuccess = { uri: Uri -> component.importLanguagesFrom( uri = uri, onFailure = AppToastHost::showFailureToast ) } ) val onExportLanguages: () -> Unit = { exportLanguagesPicker.make(component.generateExportFilename()) } val onImportLanguages: () -> Unit = importLanguagesPicker::pickFile if (isExtraction) { ImageTransformBar( onRotateLeft = component::rotateBitmapLeft, onFlip = component::flipImage, onRotateRight = component::rotateBitmapRight ) { if (imagePickerMode != ImagePickerMode.CameraCapture) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer, onClick = captureImage ) { Icon( imageVector = Icons.Rounded.CameraAlt, contentDescription = stringResource(R.string.camera) ) } Spacer(Modifier.weight(1f)) } EnhancedIconButton( containerColor = MaterialTheme.colorScheme.mixedContainer, contentColor = MaterialTheme.colorScheme.onMixedContainer, onClick = onShowCropper ) { Icon( imageVector = Icons.Rounded.CropSmall, contentDescription = stringResource(R.string.crop) ) } } Spacer(modifier = Modifier.height(8.dp)) } FilterSelectionBar( addedFilters = component.filtersAdded, onContrastClick = component::toggleContrastFilter, onThresholdClick = component::toggleThresholdFilter, onSharpnessClick = component::toggleSharpnessFilter ) Spacer(modifier = Modifier.height(16.dp)) RecognizeLanguageSelector( currentRecognitionType = component.recognitionType, value = component.selectedLanguages, availableLanguages = component.languages, onValueChange = { codeList, recognitionType -> component.onLanguagesSelected(codeList) component.setRecognitionType(recognitionType) component.startRecognition() }, onDeleteLanguage = { language, types -> component.deleteLanguage( language = language, types = types ) }, onImportLanguages = onImportLanguages, onExportLanguages = onExportLanguages ) if (isExtraction) { LinkPreviewList( text = editedText ?: "", modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) ) Spacer(modifier = Modifier.height(8.dp)) OCRTextPreviewItem( text = editedText, onTextEdit = { newText -> if (editedText != null) { component.updateEditedText(newText) } }, isLoading = component.isTextLoading, loadingProgress = component.textLoadingProgress, accuracy = component.recognitionData?.accuracy ?: 0 ) } Spacer(modifier = Modifier.height(8.dp)) RecognitionTypeSelector( value = component.recognitionType, onValueChange = component::setRecognitionType ) Spacer(modifier = Modifier.height(8.dp)) ModelTypeSelector( value = component.segmentationMode, onValueChange = component::setSegmentationMode ) Spacer(modifier = Modifier.height(8.dp)) OcrEngineModeSelector( value = component.ocrEngineMode, onValueChange = component::setOcrEngineMode ) Spacer(modifier = Modifier.height(8.dp)) TessParamsSelector( value = component.params, onValueChange = component::updateParams, modifier = Modifier.fillMaxWidth() ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextDownloadDataDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.SignalCellularConnectedNoInternet0Bar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType import com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent @Composable internal fun RecognizeTextDownloadDataDialog(component: RecognizeTextComponent) { val downloadDialogData = component.downloadDialogData if (downloadDialogData.isNotEmpty()) { var progress by rememberSaveable(downloadDialogData) { mutableFloatStateOf(0f) } var dataRemaining by rememberSaveable(downloadDialogData) { mutableStateOf("") } DownloadLanguageDialog( downloadDialogData = downloadDialogData, onDownloadRequest = { downloadData -> component.downloadTrainData( type = downloadData.firstOrNull()?.type ?: RecognitionType.Standard, languageCode = downloadDialogData.joinToString(separator = "+") { it.languageCode }, onProgress = { (p, size) -> dataRemaining = humanFileSize(size) progress = p }, onComplete = { component.clearDownloadDialogData() AppToastHost.showConfetti() component.startRecognition() } ) }, downloadProgress = progress, dataRemaining = dataRemaining, onNoConnection = { component.clearDownloadDialogData() AppToastHost.showToast( message = getString(R.string.no_connection), icon = Icons.Outlined.SignalCellularConnectedNoInternet0Bar, duration = ToastDuration.Long ) }, onDismiss = component::clearDownloadDialogData ) } } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/RecognizeTextNoDataControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.localImagePickerMode import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent @Composable internal fun RecognizeTextNoDataControls(component: RecognizeTextComponent) { val isPortrait by isPortraitOrientationAsState() val imagePickerMode = localImagePickerMode(Picker.Single) val imagePicker = rememberImagePicker(imagePickerMode) { list -> component.updateType( type = Screen.RecognizeText.Type.Extraction(list.firstOrNull()) ) } val writeToFilePicker = rememberImagePicker { uris: List -> component.updateType( type = Screen.RecognizeText.Type.WriteToFile(uris) ) } val writeToMetadataPicker = rememberImagePicker { uris: List -> component.updateType( type = Screen.RecognizeText.Type.WriteToMetadata(uris) ) } val writeToSearchablePdfPicker = rememberImagePicker { uris: List -> component.updateType( type = Screen.RecognizeText.Type.WriteToSearchablePdf(uris) ) } val types = remember { Screen.RecognizeText.Type.entries } val preference1 = @Composable { PreferenceItem( title = stringResource(types[0].title), subtitle = stringResource(types[0].subtitle), startIcon = types[0].icon, modifier = Modifier.fillMaxWidth(), onClick = imagePicker::pickImage ) } val preference2 = @Composable { PreferenceItem( title = stringResource(types[1].title), subtitle = stringResource(types[1].subtitle), startIcon = types[1].icon, modifier = Modifier.fillMaxWidth(), onClick = { if (component.selectionSheetData.isNotEmpty()) { component.updateType( type = Screen.RecognizeText.Type.WriteToFile(component.selectionSheetData) ) component.hideSelectionTypeSheet() } else { writeToFilePicker.pickImage() } } ) } val preference3 = @Composable { PreferenceItem( title = stringResource(types[2].title), subtitle = stringResource(types[2].subtitle), startIcon = types[2].icon, modifier = Modifier.fillMaxWidth(), onClick = { if (component.selectionSheetData.isNotEmpty()) { component.updateType( type = Screen.RecognizeText.Type.WriteToMetadata(component.selectionSheetData) ) component.hideSelectionTypeSheet() } else { writeToMetadataPicker.pickImage() } } ) } val preference4 = @Composable { PreferenceItem( title = stringResource(types[3].title), subtitle = stringResource(types[3].subtitle), startIcon = types[3].icon, modifier = Modifier.fillMaxWidth(), onClick = { if (component.selectionSheetData.isNotEmpty()) { component.updateType( type = Screen.RecognizeText.Type.WriteToSearchablePdf(component.selectionSheetData) ) component.hideSelectionTypeSheet() } else { writeToSearchablePdfPicker.pickImage() } } ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() Spacer(modifier = Modifier.height(8.dp)) preference3() Spacer(modifier = Modifier.height(8.dp)) preference4() } } else { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding( WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal) .asPaddingValues() ) ) { Row { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } Spacer(modifier = Modifier.height(8.dp)) Row { preference3.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference4.withModifier(modifier = Modifier.weight(1f)) } } } EnhancedModalBottomSheet( visible = component.selectionSheetData.isNotEmpty(), onDismiss = { if (!it) component.hideSelectionTypeSheet() }, confirmButton = { EnhancedButton( onClick = component::hideSelectionTypeSheet, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(id = R.string.close)) } }, sheetContent = { LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(250.dp), horizontalArrangement = Arrangement.spacedBy( space = 12.dp, alignment = Alignment.CenterHorizontally ), verticalItemSpacing = 12.dp, contentPadding = PaddingValues(12.dp), flingBehavior = enhancedFlingBehavior() ) { item { preference2() } item { preference3() } item { preference4() } } }, title = { TitleItem( text = stringResource(id = R.string.pick_file), icon = Icons.Rounded.FileOpen ) } ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/TessParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Tune import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessParams import kotlin.math.roundToInt @Composable fun TessParamsSelector( value: TessParams, onValueChange: (TessParams) -> Unit, modifier: Modifier = Modifier ) { ExpandableItem( modifier = modifier, visibleContent = { TitleItem( text = stringResource(R.string.tesseract_options), subtitle = stringResource(R.string.tesseract_options_sub), icon = Icons.Outlined.Tune, iconEndPadding = 16.dp, modifier = Modifier.padding(8.dp) ) }, expandableContent = { val params by rememberUpdatedState(value) Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(horizontal = 8.dp) ) { val size = params.tessParamList.size params.tessParamList.forEachIndexed { index, (key, paramValue) -> if (paramValue is Int) { EnhancedSliderItem( value = paramValue, onValueChange = { newValue -> onValueChange( params.update(key) { newValue.roundToInt() } ) }, title = key, valueRange = 0f..100f, steps = 98, internalStateTransformation = { it.roundToInt() }, shape = ShapeDefaults.byIndex(index, size), containerColor = MaterialTheme.colorScheme.surface ) } else if (paramValue is Boolean) { PreferenceRowSwitch( title = key, checked = paramValue, onClick = { checked -> onValueChange( params.update(key) { checked } ) }, shape = ShapeDefaults.byIndex(index, size), modifier = Modifier.fillMaxWidth(), applyHorizontalPadding = false, containerColor = MaterialTheme.colorScheme.surface ) } } var tempTessParams by rememberSaveable(params.tessCustomParams) { mutableStateOf(params.tessCustomParams) } Column( modifier = Modifier .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ) .padding(8.dp) ) { RoundedTextField( modifier = Modifier.fillMaxWidth(), value = tempTessParams, singleLine = false, onValueChange = { tempTessParams = it }, label = { Text(stringResource(R.string.custom_options)) }, onLoseFocusTransformation = { tempTessParams.trim().also { onValueChange( params.update(newCustomParams = it) ) tempTessParams = it } }, endIcon = { AnimatedVisibility(tempTessParams.isNotEmpty()) { EnhancedIconButton( onClick = { onValueChange( params.update(newCustomParams = tempTessParams) ) } ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } } }, shape = ShapeDefaults.small ) Spacer(Modifier.height(8.dp)) InfoContainer( containerColor = MaterialTheme.colorScheme.surfaceContainerLow, text = stringResource(R.string.custom_params_info) ) } } }, shape = ShapeDefaults.extraLarge ) } ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/components/UiDownloadData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.recognize.text.presentation.components import android.os.Parcelable import com.t8rin.imagetoolbox.feature.recognize.text.domain.DownloadData import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType import kotlinx.parcelize.Parcelize @Parcelize data class UiDownloadData( val type: RecognitionType, val languageCode: String, val name: String, val localizedName: String ) : Parcelable fun DownloadData.toUi() = UiDownloadData(type, languageCode, name, localizedName) ================================================ FILE: feature/recognize-text/src/main/java/com/t8rin/imagetoolbox/feature/recognize/text/presentation/screenLogic/RecognizeTextComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Language import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.model.OutlineType import com.t8rin.cropper.model.RectCropShape import com.t8rin.cropper.settings.CropDefaults import com.t8rin.cropper.settings.CropOutlineProperty import com.t8rin.imagetoolbox.core.data.utils.asDomain import com.t8rin.imagetoolbox.core.data.utils.toCoil import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.remote.DownloadProgress import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.FileSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiContrastFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiSharpenFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiThresholdFilter import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.pdf_tools.domain.PdfManager import com.t8rin.imagetoolbox.feature.pdf_tools.domain.model.SearchablePdfPage import com.t8rin.imagetoolbox.feature.recognize.text.domain.DownloadData import com.t8rin.imagetoolbox.feature.recognize.text.domain.ImageTextReader import com.t8rin.imagetoolbox.feature.recognize.text.domain.OCRLanguage import com.t8rin.imagetoolbox.feature.recognize.text.domain.OcrEngineMode import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionData import com.t8rin.imagetoolbox.feature.recognize.text.domain.RecognitionType import com.t8rin.imagetoolbox.feature.recognize.text.domain.SegmentationMode import com.t8rin.imagetoolbox.feature.recognize.text.domain.TessParams import com.t8rin.imagetoolbox.feature.recognize.text.domain.TextRecognitionResult import com.t8rin.imagetoolbox.feature.recognize.text.presentation.components.UiDownloadData import com.t8rin.imagetoolbox.feature.recognize.text.presentation.components.toUi import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import coil3.transform.Transformation as CoilTransformation class RecognizeTextComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialType: Screen.RecognizeText.Type?, @Assisted onGoBack: () -> Unit, private val imageGetter: ImageGetter, private val imageTextReader: ImageTextReader, private val settingsManager: SettingsManager, private val imageTransformer: ImageTransformer, private val filterProvider: FilterProvider, private val imageScaler: ImageScaler, private val shareProvider: ShareProvider, private val fileController: FileController, private val pdfManager: PdfManager, private val filenameCreator: FilenameCreator, resourceManager: ResourceManager, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext), ResourceManager by resourceManager { init { debounce { initialType?.let(::updateType) } } private val _segmentationMode: MutableState = mutableStateOf(SegmentationMode.PSM_AUTO_OSD) val segmentationMode by _segmentationMode private val _ocrEngineMode: MutableState = mutableStateOf(OcrEngineMode.DEFAULT) val ocrEngineMode by _ocrEngineMode private val _params: MutableState = mutableStateOf(TessParams.Default) val params by _params private val _selectedLanguages = mutableStateOf(listOf(OCRLanguage.Default)) val selectedLanguages by _selectedLanguages private var isRecognitionTypeSet = false private val _recognitionType = mutableStateOf(RecognitionType.Standard) val recognitionType by _recognitionType private val _type = mutableStateOf(null) val type by _type val uris: List get() = when (val target = type) { is Screen.RecognizeText.Type.WriteToFile -> target.uris ?: emptyList() is Screen.RecognizeText.Type.WriteToMetadata -> target.uris ?: emptyList() is Screen.RecognizeText.Type.WriteToSearchablePdf -> target.uris ?: emptyList() else -> emptyList() } val onGoBack: () -> Unit = { if (type == null) onGoBack() else { _recognitionData.update { null } _type.update { null } } } private val _recognitionData = mutableStateOf(null) val recognitionData by _recognitionData val text: String? get() = recognitionData?.text?.takeIf { it.isNotEmpty() } private val _editedText = mutableStateOf(text) val editedText by _editedText private val _textLoadingProgress: MutableState = mutableIntStateOf(-1) val textLoadingProgress by _textLoadingProgress private val _languages: MutableState> = mutableStateOf(emptyList()) val languages by _languages private val contrastFilterInstance = UiContrastFilter() private val sharpenFilterInstance = UiSharpenFilter() private val thresholdFilterInstance = UiThresholdFilter() private val filtersOrder = listOf( contrastFilterInstance, sharpenFilterInstance, thresholdFilterInstance ) private val _filtersAdded: MutableState>> = mutableStateOf(emptyList()) val filtersAdded by _filtersAdded private val internalBitmap: MutableState = mutableStateOf(null) private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _rotation: MutableState = mutableFloatStateOf(0f) private val _isFlipped: MutableState = mutableStateOf(false) private val _selectedAspectRatio: MutableState = mutableStateOf(DomainAspectRatio.Free) val selectedAspectRatio by _selectedAspectRatio private val _cropProperties = mutableStateOf( CropDefaults.properties( cropOutlineProperty = CropOutlineProperty( OutlineType.Rect, RectCropShape( id = 0, title = OutlineType.Rect.name ) ), fling = true ) ) val cropProperties by _cropProperties private val _isExporting: MutableState = mutableStateOf(false) val isExporting by _isExporting private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private var languagesJob: Job? by smartJob { _isExporting.update { false } } val isTextLoading: Boolean get() = textLoadingProgress in 0..100 private var loadingJob: Job? by smartJob() private val _selectionSheetData = mutableStateOf(emptyList()) val selectionSheetData by _selectionSheetData private val _downloadDialogData = mutableStateOf>(emptyList()) val downloadDialogData by _downloadDialogData fun clearDownloadDialogData() { _downloadDialogData.update { emptyList() } } fun showSelectionTypeSheet(uris: List) { _selectionSheetData.update { uris } } fun hideSelectionTypeSheet() { _selectionSheetData.update { emptyList() } } private fun loadLanguages( onComplete: suspend () -> Unit = {} ) { loadingJob = componentScope.launch { delay(200L) if (!isRecognitionTypeSet) { _recognitionType.update { RecognitionType.entries[settingsManager.getSettingsState().initialOcrMode] } isRecognitionTypeSet = true } val data = imageTextReader.getLanguages(recognitionType) _selectedLanguages.update { ocrLanguages -> ocrLanguages.toMutableList().also { oldList -> data.forEach { ocrLanguage -> ocrLanguages.indexOfFirst { it.code == ocrLanguage.code }.takeIf { it != -1 }?.let { index -> oldList[index] = ocrLanguage } } }.ifEmpty { listOf(OCRLanguage.Default) } } _languages.update { data } onComplete() } } init { loadLanguages() componentScope.launch { val languageCodes = settingsManager .getSettingsState() .initialOcrCodes .filter { it.isNotBlank() } .map(imageTextReader::getLanguageForCode) _selectedLanguages.update { languageCodes.ifEmpty { listOf(OCRLanguage.Default) } } } } fun getTransformations(): List = filtersOrder.filter { it in filtersAdded }.map { filterProvider.filterToTransformation(it).toCoil() } fun updateType( type: Screen.RecognizeText.Type? ) { type?.let { componentScope.launch { _isImageLoading.value = true _type.update { type } if (type is Screen.RecognizeText.Type.Extraction) { imageGetter.getImage( data = type.uri ?: "", originalSize = false )?.let { updateBitmap( bitmap = it, onComplete = ::startRecognition ) } } _isImageLoading.value = false } } } fun save( oneTimeSaveLocationUri: String? ) { recognitionJob = componentScope.launch { delay(400) _isSaving.update { true } when (type) { is Screen.RecognizeText.Type.WriteToFile -> { val txtString = StringBuilder() _left.update { uris.size } uris.forEach { uri -> uri.readText().appendToStringBuilder( builder = txtString, uri = uri, onRequestDownload = { data -> _downloadDialogData.update { data.map(DownloadData::toUi) } return@launch } ) _done.update { it + 1 } } parseSaveResults( listOf( fileController.save( saveTarget = TxtSaveTarget( txtBytes = txtString.toString().toByteArray() ), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) ) } is Screen.RecognizeText.Type.WriteToMetadata -> { val results = mutableListOf() _left.update { uris.size } uris.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString()) }.getOrNull()?.let { data -> val txtString = when (val result = data.image.readText()) { is TextRecognitionResult.Error -> { result.throwable.message ?: "" } is TextRecognitionResult.NoData -> { _downloadDialogData.update { result.data.map(DownloadData::toUi) } return@launch } is TextRecognitionResult.Success -> { result.data.text.ifEmpty { getString(R.string.picture_has_no_text) } } } results.add( fileController.save( ImageSaveTarget( imageInfo = data.imageInfo, originalUri = uri.toString(), sequenceNumber = null, metadata = data.metadata?.apply { setAttribute( MetadataTag.UserComment, txtString.takeIf { it.isNotEmpty() } ) }, data = ByteArray(0), readFromUriInsteadOfData = true ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.update { it + 1 } } } parseSaveResults(results.onSuccess(::registerSave)) } else -> return@launch } }.apply { invokeOnCompletion { _isSaving.update { false } } } } private fun TxtSaveTarget( txtBytes: ByteArray ): SaveTarget = FileSaveTarget( originalUri = "", filename = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = ImageInfo(), originalUri = "", sequenceNumber = null, metadata = null, data = ByteArray(0), extension = "txt" ), oneTimePrefix = "OCR_images(${uris.size})", forceNotAddSizeInFilename = true ), data = txtBytes, mimeType = MimeType.Txt, extension = "txt" ) fun removeUri(uri: Uri) { when (type) { is Screen.RecognizeText.Type.WriteToFile -> { updateType( type = Screen.RecognizeText.Type.WriteToFile(uris - uri) ) } is Screen.RecognizeText.Type.WriteToMetadata -> { updateType( type = Screen.RecognizeText.Type.WriteToMetadata(uris - uri), ) } is Screen.RecognizeText.Type.WriteToSearchablePdf -> { updateType( type = Screen.RecognizeText.Type.WriteToSearchablePdf(uris - uri), ) } else -> Unit } } fun updateBitmap( bitmap: Bitmap, onComplete: () -> Unit = {} ) { componentScope.launch { _isImageLoading.value = true _previewBitmap.value = imageScaler.scaleUntilCanShow(bitmap) internalBitmap.update { previewBitmap } _isImageLoading.value = false onComplete() } } private var recognitionJob: Job? by smartJob { _textLoadingProgress.update { -1 } _isSaving.update { false } } fun startRecognition() { recognitionJob = componentScope.launch { val type = _type.value if (type !is Screen.RecognizeText.Type.Extraction) return@launch delay(400L) _textLoadingProgress.update { 0 } (previewBitmap ?: type.uri)?.readText()?.also { result -> when (result) { is TextRecognitionResult.Error -> { AppToastHost.showFailureToast(result.throwable) } is TextRecognitionResult.NoData -> { _downloadDialogData.update { result.data.map(DownloadData::toUi) } } is TextRecognitionResult.Success -> { _recognitionData.update { result.data } _editedText.update { text } } } } _textLoadingProgress.update { -1 } } } fun setRecognitionType(recognitionType: RecognitionType) { _recognitionType.update { recognitionType } componentScope.launch { settingsManager.setInitialOcrMode(recognitionType.ordinal) } loadLanguages() startRecognition() } private val downloadMutex = Mutex() fun downloadTrainData( type: RecognitionType, languageCode: String, onProgress: (DownloadProgress) -> Unit, onComplete: () -> Unit ) { componentScope.launch { downloadMutex.withLock { imageTextReader.downloadTrainingData( type = type, languageCode = languageCode, onProgress = onProgress ) loadLanguages { settingsManager.setInitialOCRLanguageCodes( selectedLanguages.filter { it.downloaded.isNotEmpty() }.map { it.code }.ifEmpty { listOf(OCRLanguage.Default.code) } ) } onComplete() } } } fun onLanguagesSelected(ocrLanguages: List) { componentScope.launch { settingsManager.setInitialOCRLanguageCodes( ocrLanguages.filter { it.downloaded.isNotEmpty() && it.code.isNotBlank() }.map { it.code }.ifEmpty { listOf(OCRLanguage.Default.code) } ) } _selectedLanguages.update { ocrLanguages.ifEmpty { listOf(OCRLanguage.Default) } } _recognitionData.update { null } _editedText.update { null } recognitionJob?.cancel() _textLoadingProgress.update { -1 } } fun setSegmentationMode(segmentationMode: SegmentationMode) { _segmentationMode.update { segmentationMode } startRecognition() } fun deleteLanguage( language: OCRLanguage, types: List ) { componentScope.launch { imageTextReader.deleteLanguage(language, types) onLanguagesSelected(selectedLanguages - language) val availableTypes = language.downloaded - types.toSet() availableTypes.firstOrNull()?.let(::setRecognitionType) ?: loadLanguages() startRecognition() } } fun rotateBitmapLeft() { _rotation.update { it - 90f } debouncedImageCalculation { checkBitmapAndUpdate() } } fun rotateBitmapRight() { _rotation.update { it + 90f } debouncedImageCalculation { checkBitmapAndUpdate() } } fun flipImage() { _isFlipped.update { !it } debouncedImageCalculation { checkBitmapAndUpdate() } } private suspend fun checkBitmapAndUpdate() { _previewBitmap.value = internalBitmap.value?.let { imageTransformer.flip( image = imageTransformer.rotate( image = it, degrees = _rotation.value ), isFlipped = _isFlipped.value ) } } fun setCropAspectRatio( domainAspectRatio: DomainAspectRatio, aspectRatio: AspectRatio ) { _cropProperties.update { properties -> properties.copy( aspectRatio = aspectRatio.takeIf { domainAspectRatio != DomainAspectRatio.Original } ?: _previewBitmap.value?.let { AspectRatio(it.safeAspectRatio) } ?: aspectRatio, fixedAspectRatio = domainAspectRatio != DomainAspectRatio.Free ) } _selectedAspectRatio.update { domainAspectRatio } } fun setCropMask(cropOutlineProperty: CropOutlineProperty) { _cropProperties.update { it.copy(cropOutlineProperty = cropOutlineProperty) } } suspend fun loadImage(uri: Uri): Bitmap? = imageGetter.getImage(data = uri) fun toggleContrastFilter() { _filtersAdded.update { it.toggle(contrastFilterInstance) } } fun toggleThresholdFilter() { _filtersAdded.update { it.toggle(thresholdFilterInstance) } } fun toggleSharpnessFilter() { _filtersAdded.update { it.toggle(sharpenFilterInstance) } } fun setOcrEngineMode(mode: OcrEngineMode) { _ocrEngineMode.update { mode } startRecognition() } fun shareEditedText() { editedText?.let { shareProvider.shareText( value = it, onComplete = AppToastHost::showConfetti ) } } fun updateEditedText(text: String) { _editedText.update { text } } fun shareData() { recognitionJob = componentScope.launch { delay(400) _isSaving.update { true } when (type) { is Screen.RecognizeText.Type.WriteToFile -> { val txtString = StringBuilder() _left.update { uris.size } uris.forEach { uri -> uri.readText().also { result -> result.appendToStringBuilder( builder = txtString, uri = uri, onRequestDownload = { data -> _downloadDialogData.update { data.map(DownloadData::toUi) } return@launch } ) _done.update { it + 1 } } } val saveTarget = TxtSaveTarget( txtBytes = txtString.toString().toByteArray() ) shareProvider.shareByteArray( byteArray = saveTarget.data, filename = saveTarget.filename ?: "", onComplete = AppToastHost::showConfetti ) } is Screen.RecognizeText.Type.WriteToMetadata -> { val cachedUris = mutableListOf() _left.update { uris.size } uris.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString()) }.getOrNull()?.let { data -> data.image.readText().also { result -> val txtString = when (result) { is TextRecognitionResult.Error -> { result.throwable.message ?: "" } is TextRecognitionResult.NoData -> { _downloadDialogData.update { result.data.map(DownloadData::toUi) } return@launch } is TextRecognitionResult.Success -> { result.data.text.ifEmpty { getString(R.string.picture_has_no_text) } } } val exif = data.metadata?.apply { setAttribute( MetadataTag.UserComment, txtString.takeIf { it.isNotEmpty() } ) } shareProvider.cacheData( writeData = { w -> w.writeBytes( fileController.readBytes(uri.toString()) ) }, filename = filenameCreator.constructImageFilename( saveTarget = ImageSaveTarget( imageInfo = data.imageInfo.copy(originalUri = uri.toString()), originalUri = uri.toString(), metadata = exif, sequenceNumber = null, data = ByteArray(0) ) ) )?.let { uri -> fileController.writeMetadata( imageUri = uri, metadata = exif ) cachedUris.add(uri) } _done.update { it + 1 } } } } shareProvider.shareUris(cachedUris) } is Screen.RecognizeText.Type.WriteToSearchablePdf -> { val pdfUri = createSearchablePdfUri( onRequestDownload = { data -> _downloadDialogData.update { data.map(DownloadData::toUi) } } ) ?: return@launch shareProvider.shareUri( uri = pdfUri, type = MimeType.Pdf, onComplete = AppToastHost::showConfetti ) } else -> return@launch } }.apply { invokeOnCompletion { _isSaving.update { false } } } } private inline fun TextRecognitionResult.appendToStringBuilder( builder: StringBuilder, uri: Uri, onRequestDownload: (List) -> Unit ) { when (this) { is TextRecognitionResult.Error -> { builder.apply { append("${done + 1} - ") append("[${filenameCreator.getFilename(uri.toString())}]") append("\n\n") append(throwable.message) append("\n\n") } } is TextRecognitionResult.NoData -> onRequestDownload(data) is TextRecognitionResult.Success -> { builder.apply { append("${done + 1} - ") append("[${filenameCreator.getFilename(uri.toString())}]") append(" ") append(getString(R.string.accuracy, data.accuracy)) append("\n\n") append(data.text.ifEmpty { getString(R.string.picture_has_no_text) }) append("\n\n") } } } } fun exportLanguagesTo(uri: Uri) { languagesJob = componentScope.launch { _isExporting.value = true imageTextReader.exportLanguagesToZip()?.let { zipUri -> fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(fileController.readBytes(zipUri)) } ).also(::parseFileSaveResult).onSuccess(::registerSave) } _isExporting.value = false } } fun generateExportFilename(): String = "image_toolbox_ocr_languages_${timestamp()}.zip" fun generateTextFilename(): String = "OCR_${timestamp()}.txt" fun generateSearchablePdfFilename(): String = "OCR_searchable_${timestamp()}.pdf" fun importLanguagesFrom( uri: Uri, onFailure: (Throwable) -> Unit ) { languagesJob = componentScope.launch { _isExporting.value = true imageTextReader.importLanguagesFromUri(uri.toString()) .onSuccess { loadLanguages { AppToastHost.showConfetti() AppToastHost.showToast( message = getString(R.string.languages_imported), icon = Icons.Outlined.Language ) startRecognition() } } .onFailure(onFailure) _isExporting.value = false } } fun saveContentToTxt(uri: Uri) { recognitionData?.text?.takeIf { it.isNotEmpty() }?.let { data -> componentScope.launch { fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(data.encodeToByteArray()) } ).also(::parseFileSaveResult).onSuccess(::registerSave) } } } fun saveSearchablePdfTo(uri: Uri) { recognitionJob = componentScope.launch { _isSaving.update { true } val pdfUri = createSearchablePdfUri( onRequestDownload = { data -> _downloadDialogData.update { data.map(DownloadData::toUi) } } ) ?: return@launch fileController.transferBytes( fromUri = pdfUri, toUri = uri.toString() ).also(::parseFileSaveResult).onSuccess(::registerSave) }.apply { invokeOnCompletion { _isSaving.update { false } } } } fun updateParams(newParams: TessParams) { _params.update { newParams } startRecognition() } fun cancelSaving() { recognitionJob?.cancel() recognitionJob = null _isSaving.update { false } } private suspend fun createSearchablePdfUri( onRequestDownload: (List) -> Unit ): String? { val pages = mutableListOf() _left.update { uris.size } _done.update { 0 } uris.forEachIndexed { index, uri -> val data = when (val result = uri.readText()) { is TextRecognitionResult.Error -> return null is TextRecognitionResult.NoData -> { onRequestDownload(result.data) return null } is TextRecognitionResult.Success -> result.data } pages.add( SearchablePdfPage( imageUri = uri.toString(), text = data.text, hocr = data.hocr ) ) _done.update { index + 1 } } return pdfManager.createSearchablePdf( pages = pages ) } private suspend fun Any.readText(): TextRecognitionResult { return imageTextReader.getTextFromImage( type = recognitionType, languageCode = selectedLanguages.joinToString("+") { it.code }, segmentationMode = segmentationMode, model = imageGetter.getImage(this)?.let { bitmap -> imageTransformer.transform( transformations = getTransformations().map(CoilTransformation::asDomain), image = bitmap ) }, parameters = params, ocrEngineMode = ocrEngineMode, onProgress = { progress -> _textLoadingProgress.update { progress } } ).also { _textLoadingProgress.update { -1 } } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialType: Screen.RecognizeText.Type?, onGoBack: () -> Unit, ): RecognizeTextComponent } } ================================================ FILE: feature/resize-convert/.gitignore ================================================ /build ================================================ FILE: feature/resize-convert/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.resize_convert" dependencies { implementation(projects.feature.compare) } ================================================ FILE: feature/resize-convert/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/ResizeAndConvertContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.resize_convert.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.History import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.CompareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShowOriginalButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageTransformBar import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group.ResizeTypeSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PresetSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ScaleModeSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.EditExifSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.core.utils.fileSize import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareSheet import com.t8rin.imagetoolbox.feature.resize_convert.presentation.screenLogic.ResizeAndConvertComponent @Composable fun ResizeAndConvertContent( component: ResizeAndConvertComponent ) { AutoContentBasedColors(component.bitmap) val imagePicker = rememberImagePicker { uris: List -> component.setUris( uris = uris ) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } var showResetDialog by rememberSaveable { mutableStateOf(false) } var showOriginal by rememberSaveable { mutableStateOf(false) } var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } var showEditExifDialog by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } var showZoomSheet by rememberSaveable { mutableStateOf(false) } var showCompareSheet by rememberSaveable { mutableStateOf(false) } CompareSheet( data = component.bitmap to component.previewBitmap, visible = showCompareSheet, onDismiss = { showCompareSheet = false } ) ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.resize_and_convert), input = component.bitmap, isLoading = component.isImageLoading, size = component.imageInfo.sizeInBytes.toLong(), originalSize = component.selectedUri?.fileSize() ) }, onGoBack = onBack, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.bitmap != null, onShare = component::performSharing, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) EnhancedIconButton( enabled = component.bitmap != null, onClick = { showResetDialog = true } ) { Icon( imageVector = Icons.Rounded.ImageReset, contentDescription = stringResource(R.string.reset_image) ) } if (component.bitmap != null) { ShowOriginalButton( canShow = component.canShow(), onStateChange = { showOriginal = it } ) } else { EnhancedIconButton( enabled = false, onClick = {} ) { Icon( imageVector = Icons.Rounded.History, contentDescription = stringResource(R.string.original) ) } } }, imagePreview = { ImageContainer( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), imageInside = isPortrait, showOriginal = showOriginal, previewBitmap = component.previewBitmap, originalBitmap = component.bitmap, isLoading = component.isImageLoading, shouldShowPreview = component.shouldShowPreview ) }, controls = { val imageInfo = component.imageInfo ImageCounter( imageCount = component.uris?.size?.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) AnimatedContent( targetState = component.uris?.size == 1 ) { oneUri -> val preset = component.presetSelected if (oneUri) { ImageTransformBar( onEditExif = { showEditExifDialog = true }, onRotateLeft = component::rotateLeft, onFlip = component::flip, imageFormat = component.imageInfo.imageFormat, onRotateRight = component::rotateRight, canRotate = !(preset is Preset.AspectRatio && preset.ratio != 1f) ) } else { LaunchedEffect(Unit) { showEditExifDialog = false component.updateExif(null) } ImageTransformBar( onRotateLeft = component::rotateLeft, onFlip = component::flip, onRotateRight = component::rotateRight, canRotate = !(preset is Preset.AspectRatio && preset.ratio != 1f) ) } } Spacer(Modifier.size(8.dp)) PresetSelector( value = component.presetSelected, includeTelegramOption = true, includeAspectRatioOption = true, onValueChange = component::updatePreset ) Spacer(Modifier.size(8.dp)) AnimatedVisibility( visible = component.uris?.size != 1, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { SaveExifWidget( imageFormat = component.imageInfo.imageFormat, checked = component.keepExif, onCheckedChange = component::setKeepExif ) Spacer(Modifier.size(8.dp)) } } ResizeImageField( imageInfo = imageInfo, originalSize = component.originalSize, onHeightChange = component::updateHeight, onWidthChange = component::updateWidth, showWarning = component.showWarning ) if (imageInfo.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } QualitySelector( imageFormat = imageInfo.imageFormat, quality = imageInfo.quality, onQualityChange = component::setQuality ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = imageInfo.quality, ) Spacer(Modifier.height(8.dp)) ResizeTypeSelector( enabled = component.bitmap != null, value = imageInfo.resizeType, onValueChange = component::setResizeType ) Spacer(Modifier.height(8.dp)) ScaleModeSelector( value = imageInfo.imageScaleMode, onValueChange = component::setImageScaleMode ) }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, topAppBarPersistentActions = { if (component.bitmap == null) TopAppBarEmoji() CompareButton( onClick = { showCompareSheet = true }, visible = component.previewBitmap != null && component.bitmap != null && component.shouldShowPreview ) ZoomButton( onClick = { showZoomSheet = true }, visible = component.previewBitmap != null && component.shouldShowPreview ) }, canShowScreenData = component.bitmap != null, forceImagePreviewToMax = showOriginal, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } } ) ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, onReset = component::resetValues ) val transformations by remember(component.imageInfo, component.presetSelected) { derivedStateOf(component::getTransformations) } PickImageFromUrisSheet( transformations = transformations, visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, uris = component.uris, selectedUri = component.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = component::updateUrisSilently, columns = if (isPortrait) 2 else 4, ) EditExifSheet( visible = showEditExifDialog, onDismiss = { showEditExifDialog = false }, exif = component.exif, onClearExif = component::clearExif, onUpdateTag = component::updateExifByTag, onRemoveTag = component::removeExifTag ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris?.size ?: 1, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/resize-convert/src/main/java/com/t8rin/imagetoolbox/feature/resize_convert/presentation/screenLogic/ResizeAndConvertComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.resize_convert.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.clearAllAttributes import com.t8rin.imagetoolbox.core.domain.image.clearAttribute import com.t8rin.imagetoolbox.core.domain.image.model.ImageData import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.core.ui.transformation.ImageInfoTransformation import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.withContext class ResizeAndConvertComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageTransformer: ImageTransformer, private val imagePreviewCreator: ImagePreviewCreator, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val shareProvider: ImageShareProvider, private val imageInfoTransformationFactory: ImageInfoTransformation.Factory, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) } } private val _originalSize: MutableState = mutableStateOf(null) val originalSize by _originalSize private val _exif: MutableState = mutableStateOf(null) val exif by _exif private val _uris = mutableStateOf?>(null) val uris by _uris private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _keepExif = mutableStateOf(false) val keepExif by _keepExif private val _imageInfo: MutableState = mutableStateOf(ImageInfo()) val imageInfo: ImageInfo by _imageInfo private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _shouldShowPreview: MutableState = mutableStateOf(true) val shouldShowPreview by _shouldShowPreview private val _showWarning: MutableState = mutableStateOf(false) val showWarning: Boolean by _showWarning private val _presetSelected: MutableState = mutableStateOf(Preset.None) val presetSelected by _presetSelected private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _selectedUri: MutableState = mutableStateOf(null) val selectedUri by _selectedUri init { componentScope.launch { val settingsState = settingsProvider.getSettingsState() _imageInfo.update { it.copy(resizeType = settingsState.defaultResizeType) } } } private var job: Job? by smartJob { _isImageLoading.update { false } } fun setUris( uris: List? ) { _uris.update { null } _uris.update { uris } _selectedUri.update { uris?.firstOrNull() } _presetSelected.update { Preset.None } uris?.firstOrNull()?.let { uri -> componentScope.launch { _imageInfo.update { it.copy(originalUri = uri.toString()) } imageGetter.getImageAsync( uri = uri.toString(), originalSize = true, onGetImage = ::setImageData, onFailure = AppToastHost::showFailureToast ) } } } fun updateUrisSilently(removedUri: Uri) { componentScope.launch { _uris.update { uris } if (_selectedUri.value == removedUri) { val index = uris?.indexOf(removedUri) ?: -1 if (index == 0) { uris?.getOrNull(1)?.let { updateSelectedUri(it) } } else { uris?.getOrNull(index - 1)?.let { updateSelectedUri(it) } } } _uris.update { it?.toMutableList()?.apply { remove(removedUri) } } } } private suspend fun checkBitmapAndUpdate( resetPreset: Boolean = false ) { if (resetPreset) { _presetSelected.update { Preset.None } } _bitmap.value?.let { bmp -> val preview = updatePreview(bmp) _previewBitmap.update { null } _shouldShowPreview.update { imagePreviewCreator.canShow(preview) } if (shouldShowPreview) _previewBitmap.update { preview } } } private suspend fun updatePreview( bitmap: Bitmap ): Bitmap? = withContext(defaultDispatcher) { return@withContext imageInfo.run { _showWarning.update { width * height * 4L >= 10_000 * 10_000 * 3L } imagePreviewCreator.createPreview( image = bitmap, imageInfo = this, onGetByteCount = { sizeInBytes -> _imageInfo.update { it.copy(sizeInBytes = sizeInBytes) } } ) } } private fun setBitmapInfo(newInfo: ImageInfo) { if (_imageInfo.value != newInfo) { _imageInfo.update { newInfo } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } } fun resetValues( saveFormat: Boolean = false, resetPreset: Boolean = true ) { _imageInfo.update { ImageInfo( width = _originalSize.value?.width ?: 0, height = _originalSize.value?.height ?: 0, imageFormat = if (saveFormat) { imageInfo.imageFormat } else ImageFormat.Default, originalUri = selectedUri?.toString() ) } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = resetPreset ) } } private fun setImageData(imageData: ImageData) { job = componentScope.launch { _isImageLoading.update { true } _exif.update { imageData.metadata } val bitmap = imageData.image val size = bitmap.width to bitmap.height _originalSize.update { size.run { IntegerSize(width = first, height = second) } } _bitmap.update { imageScaler.scaleUntilCanShow(bitmap) } resetValues(true) _imageInfo.update { imageData.imageInfo.copy( width = size.first, height = size.second ) } checkBitmapAndUpdate( resetPreset = _presetSelected.value == Preset.Telegram && imageData.imageInfo.imageFormat != ImageFormat.Png.Lossless ) _isImageLoading.update { false } } } fun rotateLeft() { _imageInfo.update { it.copy( rotationDegrees = it.rotationDegrees - 90f, height = it.width, width = it.height ) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } fun rotateRight() { _imageInfo.update { it.copy( rotationDegrees = it.rotationDegrees + 90f, height = it.width, width = it.height ) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } fun flip() { _imageInfo.update { it.copy(isFlipped = !it.isFlipped) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } fun updateWidth(width: Int) { if (imageInfo.width != width) { _imageInfo.update { it.copy(width = width) } debouncedImageCalculation { checkBitmapAndUpdate(true) } registerChanges() } } fun updateHeight(height: Int) { if (imageInfo.height != height) { _imageInfo.update { it.copy(height = height) } debouncedImageCalculation { checkBitmapAndUpdate(true) } registerChanges() } } fun setQuality(quality: Quality) { if (imageInfo.quality != quality) { _imageInfo.update { it.copy(quality = quality) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } } fun setImageFormat(imageFormat: ImageFormat) { if (imageInfo.imageFormat != imageFormat) { _imageInfo.update { it.copy(imageFormat = imageFormat) } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = _presetSelected.value == Preset.Telegram && imageFormat != ImageFormat.Png.Lossless ) } registerChanges() } } fun setResizeType(type: ResizeType) { if (imageInfo.resizeType != type) { _imageInfo.update { it.copy( resizeType = type.withOriginalSizeIfCrop(originalSize) ) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } } fun setKeepExif(boolean: Boolean) { _keepExif.update { boolean } registerChanges() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } val results = mutableListOf() _done.update { 0 } uris?.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString())?.image }.getOrNull()?.let { bitmap -> imageInfo.copy( originalUri = uri.toString() ).let { imageTransformer.applyPresetBy( image = bitmap, preset = presetSelected, currentInfo = it ) }.let { imageInfo -> results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = if (uris!!.size == 1) exif else null, originalUri = uri.toString(), sequenceNumber = done + 1, data = imageCompressor.compressAndTransform( image = bitmap, imageInfo = imageInfo ), presetInfo = presetSelected, canSkipIfLarger = true ), keepOriginalMetadata = if (uris!!.size == 1) true else keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.update { false } } } fun updateSelectedUri(uri: Uri) { runCatching { _selectedUri.update { uri } componentScope.launch { _isImageLoading.update { true } val bitmap = imageGetter.getImage( uri = uri.toString(), originalSize = true )?.image val size = bitmap?.let { it.width to it.height } _originalSize.update { size?.run { IntegerSize( width = first, height = second ) } } _bitmap.update { imageScaler.scaleUntilCanShow(bitmap) } _imageInfo.update { it.copy( width = size?.first ?: 0, height = size?.second ?: 0, originalUri = uri.toString() ) } _imageInfo.update { imageTransformer.applyPresetBy( image = _bitmap.value, preset = presetSelected, currentInfo = it ) } checkBitmapAndUpdate() _isImageLoading.update { false } } }.onFailure(AppToastHost::showFailureToast) } fun updatePreset(preset: Preset) { componentScope.launch { if (preset is Preset.AspectRatio && preset.ratio != 1f) { _imageInfo.update { it.copy(rotationDegrees = 0f) } } setBitmapInfo( imageTransformer.applyPresetBy( image = bitmap, preset = preset, currentInfo = imageInfo.copy( originalUri = selectedUri?.toString() ) ) ) _presetSelected.update { preset } } } fun selectLeftUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.rightFrom(it) } ?.let(::updateSelectedUri) } fun performSharing() { cacheImages { uris -> componentScope.launch { shareProvider.shareUris(uris.map { it.toString() }) AppToastHost.showConfetti() } } } fun canShow(): Boolean = bitmap?.let { imagePreviewCreator.canShow(it) } == true fun clearExif() { updateExif(_exif.value?.clearAllAttributes()) } fun updateExif(metadata: Metadata?) { _exif.update { metadata } registerChanges() } fun removeExifTag(tag: MetadataTag) { updateExif(_exif.value?.clearAttribute(tag)) } fun updateExifByTag( tag: MetadataTag, value: String ) { updateExif(_exif.value?.setAttribute(tag, value)) } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.update { false } } fun setImageScaleMode(imageScaleMode: ImageScaleMode) { _imageInfo.update { it.copy( imageScaleMode = imageScaleMode ) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.update { true } imageGetter.getImage(selectedUri.toString())?.image?.let { bmp -> bmp to imageInfo.copy( originalUri = selectedUri.toString() ).let { imageTransformer.applyPresetBy( image = bitmap, preset = presetSelected, currentInfo = it ) } }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = selectedUri.toString()) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.update { false } } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.update { true } _done.update { 0 } val list = mutableListOf() uris?.forEach { uri -> imageGetter.getImage(uri.toString())?.image?.let { bmp -> bmp to imageInfo.copy( originalUri = uri.toString() ).let { imageTransformer.applyPresetBy( image = bitmap, preset = presetSelected, currentInfo = it ) } }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = uri.toString()) )?.let { uri -> list.add(uri.toUri()) } } _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } onComplete(list) _isSaving.update { false } } } fun getFormatForFilenameSelection(): ImageFormat? = if (uris?.size == 1) imageInfo.imageFormat else null fun getTransformations() = listOf( imageInfoTransformationFactory( imageInfo = imageInfo, preset = presetSelected ) ) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): ResizeAndConvertComponent } } ================================================ FILE: feature/root/.gitignore ================================================ /build ================================================ FILE: feature/root/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.root" dependencies { implementation(projects.feature.main) implementation(projects.feature.loadNetImage) implementation(projects.feature.crop) implementation(projects.feature.limitsResize) implementation(projects.feature.cipher) implementation(projects.feature.imagePreview) implementation(projects.feature.weightResize) implementation(projects.feature.compare) implementation(projects.feature.deleteExif) implementation(projects.feature.paletteTools) implementation(projects.feature.resizeConvert) implementation(projects.feature.pdfTools) implementation(projects.feature.singleEdit) implementation(projects.feature.eraseBackground) implementation(projects.feature.draw) implementation(projects.feature.filters) implementation(projects.feature.imageStitch) implementation(projects.feature.pickColor) implementation(projects.feature.recognizeText) implementation(projects.feature.gradientMaker) implementation(projects.feature.watermarking) implementation(projects.feature.gifTools) implementation(projects.feature.apngTools) implementation(projects.feature.zip) implementation(projects.feature.jxlTools) implementation(projects.feature.settings) implementation(projects.feature.easterEgg) implementation(projects.feature.svgMaker) implementation(projects.feature.formatConversion) implementation(projects.feature.documentScanner) implementation(projects.feature.scanQrCode) implementation(projects.feature.imageStacking) implementation(projects.feature.imageSplitting) implementation(projects.feature.colorTools) implementation(projects.feature.webpTools) implementation(projects.feature.noiseGeneration) implementation(projects.feature.collageMaker) implementation(projects.feature.librariesInfo) implementation(projects.feature.markupLayers) implementation(projects.feature.base64Tools) implementation(projects.feature.checksumTools) implementation(projects.feature.meshGradients) implementation(projects.feature.editExif) implementation(projects.feature.imageCutting) implementation(projects.feature.audioCoverExtractor) implementation(projects.feature.libraryDetails) implementation(projects.feature.wallpapersExport) implementation(projects.feature.asciiArt) implementation(projects.feature.aiTools) implementation(projects.feature.colorLibrary) } ================================================ FILE: feature/root/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/RootContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.ImageToolboxCompositionLocals import com.t8rin.imagetoolbox.feature.root.presentation.components.RootDialogs import com.t8rin.imagetoolbox.feature.root.presentation.components.ScreenSelector import com.t8rin.imagetoolbox.feature.root.presentation.components.utils.uiSettingsState import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent @Composable fun RootContent( component: RootComponent ) { val stack by component.childStack.subscribeAsState() ImageToolboxCompositionLocals( settingsState = component.uiSettingsState(), filterPreviewModel = component.filterPreviewModel, canSetDynamicFilterPreview = component.canSetDynamicFilterPreview, currentScreen = stack.items.lastOrNull()?.configuration ) { ScreenSelector(component) RootDialogs(component) } } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/RootDialogs.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components import androidx.compose.runtime.Composable import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalEditPresetsController import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ReviewHandler import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.UpdateSheet import com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs.AppExitDialog import com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs.EditPresetsSheet import com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs.FirstLaunchSetupDialog import com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs.GithubReviewDialog import com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs.PermissionDialog import com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs.TelegramGroupDialog import com.t8rin.imagetoolbox.feature.root.presentation.components.utils.HandleLookForUpdates import com.t8rin.imagetoolbox.feature.root.presentation.components.utils.SuccessRestoreBackupToastHandler import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.DonateDialog @Composable internal fun RootDialogs(component: RootComponent) { val editPresetsController = LocalEditPresetsController.current AppExitDialog(component) EditPresetsSheet( visible = editPresetsController.isVisible, onDismiss = editPresetsController::close, onUpdatePresets = component::setPresets ) ProcessImagesPreferenceSheet( uris = component.uris ?: emptyList(), extraDataType = component.extraDataType, visible = component.showSelectDialog, onDismiss = component::hideSelectDialog, onNavigate = { screen -> component.navigateTo(screen) Clipboard.clear() } ) UpdateSheet( tag = component.tag, changelog = component.changelog, visible = component.showUpdateDialog, onDismiss = component::cancelledUpdate ) FirstLaunchSetupDialog( toggleShowUpdateDialog = component::toggleShowUpdateDialog, toggleAllowBetas = component::toggleAllowBetas, adjustPerformance = component::adjustPerformance ) DonateDialog( onRegisterDonateDialogOpen = component::registerDonateDialogOpen, onNotShowDonateDialogAgain = component::notShowDonateDialogAgain ) PermissionDialog() GithubReviewDialog( visible = component.showGithubReviewDialog, onDismiss = component::hideReviewDialog, onNotShowAgain = ReviewHandler.current::notShowReviewAgain, isNotShowAgainButtonVisible = ReviewHandler.current.showNotShowAgainButton ) TelegramGroupDialog( visible = component.showTelegramGroupDialog, onDismiss = component::hideTelegramGroupDialog, onRedirected = component::registerTelegramGroupOpen ) SuccessRestoreBackupToastHandler(component) HandleLookForUpdates(component) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/ScreenSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.arkivanov.decompose.extensions.compose.stack.Children import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.t8rin.imagetoolbox.core.ui.utils.animation.toolboxPredictiveBackAnimation import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalCurrentScreen import com.t8rin.imagetoolbox.feature.root.presentation.components.utils.ResetThemeOnGoBack import com.t8rin.imagetoolbox.feature.root.presentation.components.utils.ScreenBasedMaxBrightnessEnforcement import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent @Composable internal fun ScreenSelector( component: RootComponent ) { ResetThemeOnGoBack(component) val childStack by component.childStack.subscribeAsState() val currentScreen = LocalCurrentScreen.current SettingsBackdropWrapper( currentScreen = currentScreen, concealBackdropFlow = component.concealBackdropFlow, settingsComponent = component.settingsComponent, children = { Children( stack = childStack, modifier = Modifier.fillMaxSize(), animation = remember(component) { toolboxPredictiveBackAnimation( backHandler = component.backHandler, onBack = component::navigateBack ) }, content = { child -> child.instance.Content() } ) } ) ScreenBasedMaxBrightnessEnforcement(currentScreen) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsBackdropWrapper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.material.BackdropScaffold import androidx.compose.material.BackdropValue import androidx.compose.material.Surface import androidx.compose.material.rememberBackdropScaffoldState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import com.t8rin.gesture.detectPointerTransformGestures import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.ui.utils.animation.FancyTransitionEasing import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalSheetDragHandle import com.t8rin.imagetoolbox.feature.settings.presentation.SettingsContent import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce @Composable internal fun SettingsBackdropWrapper( currentScreen: Screen?, concealBackdropFlow: Flow, settingsComponent: SettingsComponent, children: @Composable () -> Unit ) { val scaffoldState = rememberBackdropScaffoldState( initialValue = BackdropValue.Concealed, animationSpec = tween( durationMillis = 400, easing = FancyTransitionEasing ) ) val canExpandSettings = ((currentScreen?.id ?: -1) >= 0) .and(settingsComponent.settingsState.fastSettingsSide != FastSettingsSide.None) var predictiveBackProgress by remember { mutableFloatStateOf(0f) } val animatedPredictiveBackProgress by animateFloatAsState(predictiveBackProgress) val clean = { predictiveBackProgress = 0f } LaunchedEffect(canExpandSettings) { if (!canExpandSettings) { clean() scaffoldState.conceal() } } LaunchedEffect(concealBackdropFlow) { concealBackdropFlow .debounce(200) .collectLatest { if (it) { clean() scaffoldState.conceal() } } } val isTargetRevealed = scaffoldState.targetValue == BackdropValue.Revealed BackdropScaffold( scaffoldState = scaffoldState, appBar = {}, frontLayerContent = { val alpha by animateFloatAsState( if (isTargetRevealed) 1f else 0f ) val color = MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha / 2f) var isWantOpenSettings by remember { mutableStateOf(false) } Box( modifier = Modifier .fillMaxSize() .drawWithContent { drawContent() drawRect(color) } ) { Box( modifier = Modifier.pointerInput(isWantOpenSettings) { detectPointerTransformGestures( consume = false, onGestureEnd = {}, onGestureStart = { isWantOpenSettings = false }, onGesture = { _, _, _, _, _, _ -> } ) }, content = { children() if (isTargetRevealed || scaffoldState.isRevealed) { Surface( modifier = Modifier.fillMaxSize(), color = Color.Transparent ) {} } } ) SettingsOpenButton( isWantOpenSettings = isWantOpenSettings, onStateChange = { isWantOpenSettings = it }, scaffoldState = scaffoldState, canExpandSettings = canExpandSettings ) val progress = scaffoldState.progress( from = BackdropValue.Revealed, to = BackdropValue.Concealed ) * 20f EnhancedModalSheetDragHandle( color = Color.Transparent, drawStroke = false, bendAngle = (-15f * (1f - progress)).coerceAtMost(0f), modifier = Modifier.alpha(alpha) ) } }, backLayerContent = { if (canExpandSettings && (scaffoldState.isRevealed || isTargetRevealed)) { PredictiveBackObserver( onProgress = { predictiveBackProgress = it * 1.3f }, onClean = { isCompleted -> if (isCompleted) scaffoldState.conceal() clean() }, enabled = isTargetRevealed ) Box( modifier = Modifier .fillMaxWidth() .height(LocalScreenSize.current.height) .alpha(1f - animatedPredictiveBackProgress) ) { SettingsContent( component = settingsComponent ) } } }, peekHeight = 0.dp, headerHeight = 48.dp + WindowInsets.navigationBars.asPaddingValues() .calculateBottomPadding(), persistentAppBar = false, frontLayerElevation = 0.dp, backLayerBackgroundColor = MaterialTheme.colorScheme.surface, frontLayerBackgroundColor = MaterialTheme.colorScheme.surface, frontLayerScrimColor = Color.Transparent, frontLayerShape = RectangleShape, gesturesEnabled = scaffoldState.isRevealed ) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/SettingsOpenButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.VisibilityThreshold import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.BackdropScaffoldState import androidx.compose.material.Icon import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Settings import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.animation.springySpec import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberRipple import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import kotlinx.coroutines.launch @Composable internal fun BoxScope.SettingsOpenButton( isWantOpenSettings: Boolean, onStateChange: (Boolean) -> Unit, scaffoldState: BackdropScaffoldState, canExpandSettings: Boolean ) { val scope = rememberCoroutineScope() val fastSettingsSide = LocalSettingsState.current.fastSettingsSide val alignment = if (fastSettingsSide == FastSettingsSide.CenterStart) { Alignment.CenterStart } else { Alignment.CenterEnd } val direction = LocalLayoutDirection.current val paddingValues = WindowInsets.safeDrawing.asPaddingValues() val (startPadding, endPadding) = remember(paddingValues, direction) { derivedStateOf { paddingValues.calculateStartPadding(direction) to paddingValues.calculateEndPadding( direction ) } }.value val expandedPart = if (isWantOpenSettings) 12.dp else 42.dp val cornerRadius = expandedPart.coerceAtLeast(4.dp) val shape = animateShape( targetValue = key(cornerRadius) { if (fastSettingsSide == FastSettingsSide.CenterStart) { if (startPadding == 0.dp) { AutoCornersShape( topEnd = cornerRadius, bottomEnd = cornerRadius ) } else { AutoCornersShape(cornerRadius) } } else { if (endPadding == 0.dp) { AutoCornersShape( topStart = cornerRadius, bottomStart = cornerRadius ) } else { AutoCornersShape(cornerRadius) } } }, animationSpec = spring( dampingRatio = 0.5f, stiffness = Spring.StiffnessLow ) ) val height by animateDpAsState( targetValue = if (isWantOpenSettings) 64.dp else 104.dp ) val width by animateDpAsState( targetValue = if (isWantOpenSettings) 48.dp else 24.dp, animationSpec = springySpec() ) val xOffset by animateDpAsState( targetValue = if (!canExpandSettings) { if (fastSettingsSide == FastSettingsSide.CenterStart) { -width } else { width } } else { 0.dp }, animationSpec = spring( visibilityThreshold = Dp.VisibilityThreshold, dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow ) ) val alpha by animateFloatAsState( targetValue = if (canExpandSettings) 1f else 0f, animationSpec = tween(650) ) val interactionSource = remember { MutableInteractionSource() } Surface( color = Color.Transparent, modifier = Modifier .align(alignment) .padding( start = startPadding, end = endPadding ) .size( height = height, width = width ) .hapticsClickable( enabled = canExpandSettings, indication = null, interactionSource = interactionSource ) { if (isWantOpenSettings) { scope.launch { scaffoldState.reveal() onStateChange(false) } } else { onStateChange(true) } } .alpha(alpha) .offset { IntOffset( x = xOffset.roundToPx(), y = 0 ) } ) { Box { val width by animateDpAsState( targetValue = if (isWantOpenSettings) 48.dp else 4.dp, animationSpec = spring( dampingRatio = 0.35f, stiffness = Spring.StiffnessLow ) ) val containerColor = takeColorFromScheme { tertiary.blend(primary, 0.65f) } val contentColor = takeColorFromScheme { onTertiary.blend(onPrimary, 0.8f) } Box( modifier = Modifier .align(alignment) .width(width) .height(64.dp) .container( shape = shape, resultPadding = 0.dp, color = containerColor ) .indication( interactionSource = interactionSource, indication = rememberRipple(contentColor = contentColor) ), contentAlignment = Alignment.Center ) { AnimatedVisibility( visible = isWantOpenSettings, enter = fadeIn() + scaleIn( animationSpec = spring( dampingRatio = 0.35f, stiffness = Spring.StiffnessLow ) ), exit = fadeOut() + scaleOut() ) { Icon( imageVector = Icons.Rounded.Settings, contentDescription = null, tint = contentColor ) } } } } } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/AppExitDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs import android.os.Build import androidx.activity.compose.BackHandler import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.DoorBack import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalCurrentScreen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent @Composable internal fun AppExitDialog(component: RootComponent) { val currentScreen = LocalCurrentScreen.current if (currentScreen is Screen.Main) { val context = LocalComponentActivity.current var showExitDialog by rememberSaveable { mutableStateOf(false) } val tiramisu = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU BackHandler(enabled = !tiramisu) { if (component.shouldShowDialog) showExitDialog = true else context.finishAffinity() } AppExitDialogImpl( onDismiss = { showExitDialog = false }, visible = showExitDialog && !tiramisu ) } } @Composable private fun AppExitDialogImpl( onDismiss: () -> Unit, visible: Boolean ) { val activity = LocalComponentActivity.current EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = activity::finishAffinity ) { Text(stringResource(R.string.close)) } }, confirmButton = { EnhancedButton(onClick = onDismiss) { Text(stringResource(R.string.stay)) } }, title = { Text(stringResource(R.string.app_closing)) }, text = { Text( stringResource(R.string.app_closing_sub), textAlign = TextAlign.Center ) }, icon = { Icon( imageVector = Icons.Outlined.DoorBack, contentDescription = null ) } ) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/EditPresetsSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PhotoSizeSelectSmall import androidx.compose.material.icons.rounded.AddCircle import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.text.isDigitsOnly import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.LabelPercent import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun EditPresetsSheet( visible: Boolean, onDismiss: () -> Unit, onUpdatePresets: (List) -> Unit ) { val settingsState = LocalSettingsState.current EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.presets), icon = Icons.Rounded.LabelPercent ) }, sheetContent = { val data = settingsState.presets Box { AnimatedContent( targetState = data, transitionSpec = { fadeIn() togetherWith fadeOut() }, modifier = Modifier .fillMaxWidth() .enhancedVerticalScroll(rememberScrollState()) ) { list -> var expanded by remember { mutableStateOf(false) } FlowRow( modifier = Modifier .align(Alignment.Center) .fillMaxWidth() .padding(16.dp), horizontalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterHorizontally ), verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically) ) { list.forEach { EnhancedChip( onClick = { onUpdatePresets(list - it) }, selected = false, selectedColor = MaterialTheme.colorScheme.primary, shape = MaterialTheme.shapes.medium ) { AutoSizeText(it.toString()) } } EnhancedChip( onClick = { expanded = true }, selected = false, selectedColor = MaterialTheme.colorScheme.primary, shape = MaterialTheme.shapes.medium, label = { Icon( imageVector = Icons.Rounded.AddCircle, contentDescription = stringResource(R.string.add) ) } ) } var value by remember(expanded) { mutableStateOf("") } EnhancedAlertDialog( visible = expanded, onDismissRequest = { expanded = false }, icon = { Icon( imageVector = Icons.Outlined.PhotoSizeSelectSmall, contentDescription = null ) }, title = { Text(stringResource(R.string.presets)) }, text = { OutlinedTextField( modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.default, value = value, textStyle = MaterialTheme.typography.titleMedium.copy( textAlign = TextAlign.Center ), maxLines = 1, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), onValueChange = { if (it.isDigitsOnly()) { value = it.toIntOrNull()?.coerceAtMost(500)?.toString() ?: "" } }, placeholder = { Text( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.enter_percent), style = MaterialTheme.typography.titleMedium.copy( textAlign = TextAlign.Center ), color = LocalContentColor.current.copy(0.5f) ) } ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { onUpdatePresets( list + (value.toIntOrNull() ?: 0) ) expanded = false }, enabled = value.isNotEmpty() ) { Text(stringResource(R.string.add)) } } ) } } }, confirmButton = { EnhancedButton( onClick = onDismiss, borderColor = MaterialTheme.colorScheme.outlineVariant(), containerColor = MaterialTheme.colorScheme.secondaryContainer, ) { AutoSizeText(stringResource(R.string.close)) } } ) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/FirstLaunchSetupDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.NewReleases import androidx.compose.material.icons.rounded.SystemSecurityUpdate import androidx.compose.material.icons.rounded.Webhook import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.PerformanceClass import com.t8rin.imagetoolbox.core.domain.utils.Flavor import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Beta import com.t8rin.imagetoolbox.core.settings.presentation.model.isFirstLaunch import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.performanceClass import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.saver.OneTimeEffect import com.t8rin.imagetoolbox.core.utils.appContext @Composable internal fun FirstLaunchSetupDialog( toggleAllowBetas: () -> Unit, toggleShowUpdateDialog: () -> Unit, adjustPerformance: (PerformanceClass) -> Unit ) { val settingsState = LocalSettingsState.current var updateOnFirstOpen by rememberSaveable { mutableStateOf(false) } OneTimeEffect { updateOnFirstOpen = settingsState.isFirstLaunch(false) if (updateOnFirstOpen) { adjustPerformance(appContext.performanceClass) } } EnhancedAlertDialog( visible = updateOnFirstOpen, onDismissRequest = {}, icon = { Icon( imageVector = Icons.Rounded.SystemSecurityUpdate, contentDescription = null ) }, title = { Text(stringResource(R.string.updates)) }, text = { val state = rememberScrollState() ProvideTextStyle(value = LocalTextStyle.current.copy(textAlign = TextAlign.Left)) { Column( modifier = Modifier .fadingEdges( isVertical = true, scrollableState = state, scrollFactor = 1.1f ) .enhancedVerticalScroll(state) .padding(2.dp) ) { if (Flavor.isFoss()) { PreferenceItem( title = stringResource(id = R.string.attention), subtitle = stringResource(R.string.foss_update_checker_warning), startIcon = Icons.Rounded.Webhook, shape = ShapeDefaults.default, modifier = Modifier.padding(bottom = 8.dp), containerColor = MaterialTheme.colorScheme.surfaceContainerHighest ) } PreferenceRowSwitch( shape = if (!appContext.isInstalledFromPlayStore()) { ShapeDefaults.top } else ShapeDefaults.default, modifier = Modifier, title = stringResource(R.string.check_updates), subtitle = stringResource(R.string.check_updates_sub), checked = settingsState.showUpdateDialogOnStartup, onClick = { toggleShowUpdateDialog() }, startIcon = Icons.Rounded.NewReleases ) if (!appContext.isInstalledFromPlayStore()) { Spacer(Modifier.height(4.dp)) PreferenceRowSwitch( modifier = Modifier, shape = ShapeDefaults.bottom, title = stringResource(R.string.allow_betas), subtitle = stringResource(R.string.allow_betas_sub), checked = settingsState.allowBetas, onClick = { toggleAllowBetas() }, startIcon = Icons.Rounded.Beta ) } } } }, confirmButton = { EnhancedButton( onClick = { updateOnFirstOpen = false } ) { Text(stringResource(id = R.string.ok)) } } ) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/GithubReviewDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.StarRate import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.domain.APP_GITHUB_LINK import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton @Composable internal fun GithubReviewDialog( visible: Boolean, onDismiss: () -> Unit, isNotShowAgainButtonVisible: Boolean, onNotShowAgain: () -> Unit ) { val linkHandler = LocalUriHandler.current EnhancedAlertDialog( visible = visible, onDismissRequest = onDismiss, confirmButton = { EnhancedButton( onClick = { linkHandler.openUri(APP_GITHUB_LINK) } ) { Text(text = stringResource(id = R.string.rate)) } }, dismissButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { if (isNotShowAgainButtonVisible) onNotShowAgain() onDismiss() } ) { Text(stringResource(id = R.string.close)) } }, title = { Text(stringResource(R.string.rate_app)) }, icon = { Icon( imageVector = Icons.Rounded.StarRate, contentDescription = null ) }, text = { Text(stringResource(R.string.rate_app_sub)) } ) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/PermissionDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs import android.Manifest import android.os.Build import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Storage import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.core.app.ActivityCompat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.needToShowStoragePermissionRequest import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.requestStoragePermission import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionUtils.hasPermissionAllowed import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberCurrentLifecycleEvent import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import kotlinx.coroutines.delay @Composable internal fun PermissionDialog() { val context = LocalComponentActivity.current val settingsState = LocalSettingsState.current var showDialog by remember { mutableStateOf(false) } val currentLifecycleEvent = rememberCurrentLifecycleEvent() LaunchedEffect( showDialog, context, settingsState, currentLifecycleEvent ) { showDialog = context.needToShowStoragePermissionRequest() == true while (showDialog) { showDialog = context.needToShowStoragePermissionRequest() == true delay(100) } } var requestedOnce by rememberSaveable { mutableStateOf(false) } LaunchedEffect(Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !requestedOnce) { val notAllowed = listOf( Manifest.permission.ACCESS_MEDIA_LOCATION, Manifest.permission.POST_NOTIFICATIONS ).filter { !context.hasPermissionAllowed(it) } if (notAllowed.isNotEmpty()) { ActivityCompat.requestPermissions( context, buildList { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { add(Manifest.permission.READ_MEDIA_IMAGES) } addAll(notAllowed) }.toTypedArray(), 0 ) } requestedOnce = true } } EnhancedAlertDialog( visible = showDialog, onDismissRequest = { }, icon = { Icon( imageVector = Icons.Rounded.Storage, contentDescription = null ) }, title = { Text(stringResource(R.string.permission)) }, text = { Text(stringResource(R.string.permission_sub)) }, confirmButton = { EnhancedButton( onClick = { context.requestStoragePermission() } ) { Text(stringResource(id = R.string.grant)) } } ) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/dialogs/TelegramGroupDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.dialogs import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Cancel import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.domain.TELEGRAM_CHANNEL_LINK import com.t8rin.imagetoolbox.core.domain.TELEGRAM_GROUP_LINK import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Telegram import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton @Composable fun TelegramGroupDialog( visible: Boolean, onDismiss: () -> Unit, onRedirected: () -> Unit ) { val linkHandler = LocalUriHandler.current EnhancedAlertDialog( visible = visible, icon = { Icon( imageVector = Icons.Rounded.Telegram, contentDescription = "Telegram" ) }, title = { Text(stringResource(R.string.image_toolbox_in_telegram)) }, text = { Text(stringResource(R.string.image_toolbox_in_telegram_sub)) }, confirmButton = { EnhancedButton( onClick = { onRedirected() linkHandler.openUri(TELEGRAM_GROUP_LINK) } ) { Text(stringResource(R.string.group)) } }, dismissButton = { EnhancedIconButton( onClick = { onRedirected() }, containerColor = MaterialTheme.colorScheme.errorContainer, contentColor = MaterialTheme.colorScheme.onErrorContainer ) { Icon( imageVector = Icons.Rounded.Cancel, contentDescription = null ) } EnhancedButton( onClick = { onRedirected() linkHandler.openUri(TELEGRAM_CHANNEL_LINK) }, containerColor = MaterialTheme.colorScheme.tertiaryContainer ) { Text(stringResource(R.string.ci_channel)) } }, onDismissRequest = onDismiss ) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/ChildProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.navigation import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.collage_maker.presentation.screenLogic.CollageMakerComponent import com.t8rin.imagetoolbox.color_library.presentation.screenLogic.ColorLibraryComponent import com.t8rin.imagetoolbox.color_tools.presentation.screenLogic.ColorToolsComponent import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.feature.ai_tools.presentation.screenLogic.AiToolsComponent import com.t8rin.imagetoolbox.feature.apng_tools.presentation.screenLogic.ApngToolsComponent import com.t8rin.imagetoolbox.feature.ascii_art.presentation.screenLogic.AsciiArtComponent import com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui.screenLogic.AudioCoverExtractorComponent import com.t8rin.imagetoolbox.feature.base64_tools.presentation.screenLogic.Base64ToolsComponent import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent import com.t8rin.imagetoolbox.feature.cipher.presentation.screenLogic.CipherComponent import com.t8rin.imagetoolbox.feature.compare.presentation.screenLogic.CompareComponent import com.t8rin.imagetoolbox.feature.crop.presentation.screenLogic.CropComponent import com.t8rin.imagetoolbox.feature.delete_exif.presentation.screenLogic.DeleteExifComponent import com.t8rin.imagetoolbox.feature.document_scanner.presentation.screenLogic.DocumentScannerComponent import com.t8rin.imagetoolbox.feature.draw.presentation.screenLogic.DrawComponent import com.t8rin.imagetoolbox.feature.easter_egg.presentation.screenLogic.EasterEggComponent import com.t8rin.imagetoolbox.feature.edit_exif.presentation.screenLogic.EditExifComponent import com.t8rin.imagetoolbox.feature.erase_background.presentation.screenLogic.EraseBackgroundComponent import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent import com.t8rin.imagetoolbox.feature.format_conversion.presentation.screenLogic.FormatConversionComponent import com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic.GifToolsComponent import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent import com.t8rin.imagetoolbox.feature.image_preview.presentation.screenLogic.ImagePreviewComponent import com.t8rin.imagetoolbox.feature.image_stacking.presentation.screenLogic.ImageStackingComponent import com.t8rin.imagetoolbox.feature.image_stitch.presentation.screenLogic.ImageStitchingComponent import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic.JxlToolsComponent import com.t8rin.imagetoolbox.feature.libraries_info.presentation.screenLogic.LibrariesInfoComponent import com.t8rin.imagetoolbox.feature.limits_resize.presentation.screenLogic.LimitsResizeComponent import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent import com.t8rin.imagetoolbox.feature.main.presentation.screenLogic.MainComponent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent import com.t8rin.imagetoolbox.feature.mesh_gradients.presentation.screenLogic.MeshGradientsComponent import com.t8rin.imagetoolbox.feature.palette_tools.presentation.screenLogic.PaletteToolsComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.compress.screenLogic.CompressPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop.screenLogic.CropPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_images.screenLogic.ExtractImagesPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_pages.screenLogic.ExtractPagesPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.flatten.screenLogic.FlattenPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.grayscale.screenLogic.GrayscalePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.images_to_pdf.screenLogic.ImagesToPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.merge.screenLogic.MergePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata.screenLogic.MetadataPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.ocr.screenLogic.OCRPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers.screenLogic.PageNumbersPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.preview.screenLogic.PreviewPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.print.screenLogic.PrintPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.protect.screenLogic.ProtectPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.screenLogic.RearrangePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.screenLogic.RemoveAnnotationsPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages.screenLogic.RemovePagesPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.repair.screenLogic.RepairPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.screenLogic.RootPdfToolsComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate.screenLogic.RotatePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.screenLogic.SignaturePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.split.screenLogic.SplitPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.unlock.screenLogic.UnlockPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark.screenLogic.WatermarkPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.zip_convert.screenLogic.ZipConvertPdfToolComponent import com.t8rin.imagetoolbox.feature.pick_color.presentation.screenLogic.PickColorFromImageComponent import com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent import com.t8rin.imagetoolbox.feature.resize_convert.presentation.screenLogic.ResizeAndConvertComponent import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.AiTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ApngTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.AsciiArt import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.AudioCoverExtractor import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Base64Tools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ChecksumTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Cipher import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.CollageMaker import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ColorLibrary import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ColorTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Compare import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.CompressPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Crop import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.CropPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.DeleteExif import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.DocumentScanner import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Draw import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.EasterEgg import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.EditExif import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.EraseBackground import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ExtractImagesPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ExtractPagesPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Filter import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.FlattenPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.FormatConversion import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.GifTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.GradientMaker import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.GrayscalePdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ImageCutter import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ImagePreview import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ImageSplitting import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ImageStacking import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ImageStitching import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ImagesToPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.JxlTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.LibrariesInfo import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.LibraryDetails import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.LimitResize import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.LoadNetImage import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Main import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.MarkupLayers import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.MergePdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.MeshGradients import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.MetadataPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.NoiseGeneration import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.OCRPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.PageNumbersPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.PaletteTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.PickColorFromImage import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.PreviewPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.PrintPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ProtectPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.RearrangePdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.RecognizeText import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.RemoveAnnotationsPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.RemovePagesPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.RepairPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ResizeAndConvert import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.RootPdfTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.RotatePdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ScanQrCode import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Settings import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.SignaturePdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.SingleEdit import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.SplitPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.SvgMaker import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.UnlockPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.WallpapersExport import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.WatermarkPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Watermarking import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.WebpTools import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.WeightResize import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.Zip import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild.ZipConvertPdfTool import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.screenLogic.ScanQrCodeComponent import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent import com.t8rin.imagetoolbox.feature.single_edit.presentation.screenLogic.SingleEditComponent import com.t8rin.imagetoolbox.feature.svg_maker.presentation.screenLogic.SvgMakerComponent import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.screenLogic.WallpapersExportComponent import com.t8rin.imagetoolbox.feature.watermarking.presentation.screenLogic.WatermarkingComponent import com.t8rin.imagetoolbox.feature.webp_tools.presentation.screenLogic.WebpToolsComponent import com.t8rin.imagetoolbox.feature.weight_resize.presentation.screenLogic.WeightResizeComponent import com.t8rin.imagetoolbox.feature.zip.presentation.screenLogic.ZipComponent import com.t8rin.imagetoolbox.image_cutting.presentation.screenLogic.ImageCutterComponent import com.t8rin.imagetoolbox.image_splitting.presentation.screenLogic.ImageSplitterComponent import com.t8rin.imagetoolbox.library_details.presentation.screenLogic.LibraryDetailsComponent import com.t8rin.imagetoolbox.noise_generation.presentation.screenLogic.NoiseGenerationComponent import javax.inject.Inject internal class ChildProvider @Inject constructor( private val apngToolsComponentFactory: ApngToolsComponent.Factory, private val cipherComponentFactory: CipherComponent.Factory, private val collageMakerComponentFactory: CollageMakerComponent.Factory, private val compareComponentFactory: CompareComponent.Factory, private val cropComponentFactory: CropComponent.Factory, private val deleteExifComponentFactory: DeleteExifComponent.Factory, private val documentScannerComponentFactory: DocumentScannerComponent.Factory, private val drawComponentFactory: DrawComponent.Factory, private val eraseBackgroundComponentFactory: EraseBackgroundComponent.Factory, private val filtersComponentFactory: FiltersComponent.Factory, private val formatConversionComponentFactory: FormatConversionComponent.Factory, private val paletteToolsComponentFactory: PaletteToolsComponent.Factory, private val gifToolsComponentFactory: GifToolsComponent.Factory, private val gradientMakerComponentFactory: GradientMakerComponent.Factory, private val imagePreviewComponentFactory: ImagePreviewComponent.Factory, private val imageSplittingComponentFactory: ImageSplitterComponent.Factory, private val imageStackingComponentFactory: ImageStackingComponent.Factory, private val imageStitchingComponentFactory: ImageStitchingComponent.Factory, private val jxlToolsComponentFactory: JxlToolsComponent.Factory, private val limitResizeComponentFactory: LimitsResizeComponent.Factory, private val loadNetImageComponentFactory: LoadNetImageComponent.Factory, private val noiseGenerationComponentFactory: NoiseGenerationComponent.Factory, private val rootPdfToolsComponentFactory: RootPdfToolsComponent.Factory, private val pickColorFromImageComponentFactory: PickColorFromImageComponent.Factory, private val recognizeTextComponentFactory: RecognizeTextComponent.Factory, private val resizeAndConvertComponentFactory: ResizeAndConvertComponent.Factory, private val scanQrCodeComponentFactory: ScanQrCodeComponent.Factory, private val settingsComponentFactory: SettingsComponent.Factory, private val singleEditComponentFactory: SingleEditComponent.Factory, private val svgMakerComponentFactory: SvgMakerComponent.Factory, private val watermarkingComponentFactory: WatermarkingComponent.Factory, private val webpToolsComponentFactory: WebpToolsComponent.Factory, private val weightResizeComponentFactory: WeightResizeComponent.Factory, private val zipComponentFactory: ZipComponent.Factory, private val easterEggComponentFactory: EasterEggComponent.Factory, private val colorToolsComponentFactory: ColorToolsComponent.Factory, private val librariesInfoComponentFactory: LibrariesInfoComponent.Factory, private val mainComponentFactory: MainComponent.Factory, private val markupLayersComponentFactory: MarkupLayersComponent.Factory, private val base64ToolsComponentFactory: Base64ToolsComponent.Factory, private val checksumToolsComponentFactory: ChecksumToolsComponent.Factory, private val meshGradientsComponentFactory: MeshGradientsComponent.Factory, private val editExifComponentFactory: EditExifComponent.Factory, private val imageCutterComponentFactory: ImageCutterComponent.Factory, private val audioCoverExtractorComponentFactory: AudioCoverExtractorComponent.Factory, private val libraryDetailsComponentFactory: LibraryDetailsComponent.Factory, private val wallpapersExportComponentFactory: WallpapersExportComponent.Factory, private val asciiArtComponentFactory: AsciiArtComponent.Factory, private val aiToolsComponentFactory: AiToolsComponent.Factory, private val colorLibraryComponentFactory: ColorLibraryComponent.Factory, private val mergePdfToolComponentFactory: MergePdfToolComponent.Factory, private val splitPdfToolComponentFactory: SplitPdfToolComponent.Factory, private val rotatePdfToolComponentFactory: RotatePdfToolComponent.Factory, private val rearrangePdfToolComponentFactory: RearrangePdfToolComponent.Factory, private val pageNumbersPdfToolComponentFactory: PageNumbersPdfToolComponent.Factory, private val ocrPdfToolComponentFactory: OCRPdfToolComponent.Factory, private val watermarkPdfToolComponentFactory: WatermarkPdfToolComponent.Factory, private val signaturePdfToolComponentFactory: SignaturePdfToolComponent.Factory, private val protectPdfToolComponentFactory: ProtectPdfToolComponent.Factory, private val unlockPdfToolComponentFactory: UnlockPdfToolComponent.Factory, private val compressPdfToolComponentFactory: CompressPdfToolComponent.Factory, private val grayscalePdfToolComponentFactory: GrayscalePdfToolComponent.Factory, private val repairPdfToolComponentFactory: RepairPdfToolComponent.Factory, private val metadataPdfToolComponentFactory: MetadataPdfToolComponent.Factory, private val removePagesPdfToolComponentFactory: RemovePagesPdfToolComponent.Factory, private val cropPdfToolComponentFactory: CropPdfToolComponent.Factory, private val flattenPdfToolComponentFactory: FlattenPdfToolComponent.Factory, private val extractImagesPdfToolComponentFactory: ExtractImagesPdfToolComponent.Factory, private val zipConvertPdfToolComponentFactory: ZipConvertPdfToolComponent.Factory, private val printPdfToolComponentFactory: PrintPdfToolComponent.Factory, private val previewPdfToolComponentFactory: PreviewPdfToolComponent.Factory, private val imagesToPdfToolComponentFactory: ImagesToPdfToolComponent.Factory, private val extractPagesPdfToolComponentFactory: ExtractPagesPdfToolComponent.Factory, private val removeAnnotationsPdfToolComponentFactory: RemoveAnnotationsPdfToolComponent.Factory, ) { fun RootComponent.createChild( config: Screen, componentContext: ComponentContext ): NavigationChild = when (config) { Screen.ColorTools -> ColorTools( colorToolsComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack ) ) Screen.EasterEgg -> EasterEgg( easterEggComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack ) ) Screen.Main -> Main( mainComponentFactory( componentContext = componentContext, onTryGetUpdate = ::tryGetUpdate, onGetClipList = ::updateUris, onNavigate = ::navigateToNew, isUpdateAvailable = isUpdateAvailable ) ) is Screen.ApngTools -> ApngTools( apngToolsComponentFactory( componentContext = componentContext, initialType = config.type, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.Cipher -> Cipher( cipherComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack ) ) is Screen.CollageMaker -> CollageMaker( collageMakerComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.Compare -> Compare( compareComponentFactory( componentContext = componentContext, initialComparableUris = config.uris ?.takeIf { it.size == 2 } ?.let { it[0] to it[1] }, onGoBack = ::navigateBack ) ) is Screen.Crop -> Crop( cropComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.DeleteExif -> DeleteExif( deleteExifComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) Screen.DocumentScanner -> DocumentScanner( documentScannerComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.Draw -> Draw( drawComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.EraseBackground -> EraseBackground( eraseBackgroundComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.Filter -> Filter( filtersComponentFactory( componentContext = componentContext, initialType = config.type, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.FormatConversion -> FormatConversion( formatConversionComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.PaletteTools -> PaletteTools( paletteToolsComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack ) ) is Screen.GifTools -> GifTools( gifToolsComponentFactory( componentContext = componentContext, initialType = config.type, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.GradientMaker -> GradientMaker( gradientMakerComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ImagePreview -> ImagePreview( imagePreviewComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ImageSplitting -> ImageSplitting( imageSplittingComponentFactory( componentContext = componentContext, initialUris = config.uri, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ImageStacking -> ImageStacking( imageStackingComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ImageStitching -> ImageStitching( imageStitchingComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.JxlTools -> JxlTools( jxlToolsComponentFactory( componentContext = componentContext, initialType = config.type, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.LimitResize -> LimitResize( limitResizeComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.LoadNetImage -> LoadNetImage( loadNetImageComponentFactory( componentContext = componentContext, initialUrl = config.url, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) Screen.NoiseGeneration -> NoiseGeneration( noiseGenerationComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::navigateTo, ) ) is Screen.PdfTools -> RootPdfTools( rootPdfToolsComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.PickColorFromImage -> PickColorFromImage( pickColorFromImageComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack ) ) is Screen.RecognizeText -> RecognizeText( recognizeTextComponentFactory( componentContext = componentContext, initialType = config.type, onGoBack = ::navigateBack ) ) is Screen.ResizeAndConvert -> ResizeAndConvert( resizeAndConvertComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ScanQrCode -> ScanQrCode( scanQrCodeComponentFactory( componentContext = componentContext, initialQrCodeContent = config.qrCodeContent, uriToAnalyze = config.uriToAnalyze, onGoBack = ::navigateBack ) ) is Screen.Settings -> Settings( settingsComponentFactory( componentContext = componentContext, onTryGetUpdate = ::tryGetUpdate, onNavigate = ::navigateToNew, isUpdateAvailable = isUpdateAvailable, onGoBack = ::navigateBack, initialSearchQuery = config.searchQuery ) ) is Screen.SingleEdit -> SingleEdit( singleEditComponentFactory( componentContext = componentContext, initialUri = config.uri, onNavigate = ::navigateTo, onGoBack = ::navigateBack ) ) is Screen.SvgMaker -> SvgMaker( svgMakerComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack ) ) is Screen.Watermarking -> Watermarking( watermarkingComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.WebpTools -> WebpTools( webpToolsComponentFactory( componentContext = componentContext, initialType = config.type, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.WeightResize -> WeightResize( weightResizeComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.Zip -> Zip( zipComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack ) ) Screen.LibrariesInfo -> LibrariesInfo( librariesInfoComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.MarkupLayers -> MarkupLayers( markupLayersComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.Base64Tools -> Base64Tools( base64ToolsComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ChecksumTools -> ChecksumTools( checksumToolsComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack ) ) is Screen.MeshGradients -> MeshGradients( meshGradientsComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.EditExif -> EditExif( editExifComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ImageCutter -> ImageCutter( imageCutterComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.AudioCoverExtractor -> AudioCoverExtractor( audioCoverExtractorComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.LibraryDetails -> LibraryDetails( libraryDetailsComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack, libraryName = config.name, libraryDescription = config.htmlDescription, libraryLink = config.link ) ) is Screen.WallpapersExport -> WallpapersExport( wallpapersExportComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.AsciiArt -> AsciiArt( asciiArtComponentFactory( componentContext = componentContext, initialUri = config.uri, onGoBack = ::navigateBack ) ) is Screen.AiTools -> AiTools( aiToolsComponentFactory( componentContext = componentContext, initialUris = config.uris, onGoBack = ::navigateBack, onNavigate = ::navigateTo ) ) is Screen.ColorLibrary -> ColorLibrary( colorLibraryComponentFactory( componentContext = componentContext, onGoBack = ::navigateBack ) ) is Screen.PdfTools.Merge -> MergePdfTool( mergePdfToolComponentFactory( initialUris = config.uris, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Split -> SplitPdfTool( splitPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Rotate -> RotatePdfTool( rotatePdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Rearrange -> RearrangePdfTool( rearrangePdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.PageNumbers -> PageNumbersPdfTool( pageNumbersPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.OCR -> OCRPdfTool( ocrPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Watermark -> WatermarkPdfTool( watermarkPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Signature -> SignaturePdfTool( signaturePdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Protect -> ProtectPdfTool( protectPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Unlock -> UnlockPdfTool( unlockPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Compress -> CompressPdfTool( compressPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Grayscale -> GrayscalePdfTool( grayscalePdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Repair -> RepairPdfTool( repairPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Metadata -> MetadataPdfTool( metadataPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.RemovePages -> RemovePagesPdfTool( removePagesPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Crop -> CropPdfTool( cropPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Flatten -> FlattenPdfTool( flattenPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.ExtractImages -> ExtractImagesPdfTool( extractImagesPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.ZipConvert -> ZipConvertPdfTool( zipConvertPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Print -> PrintPdfTool( printPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.Preview -> PreviewPdfTool( previewPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.ImagesToPdf -> ImagesToPdfTool( imagesToPdfToolComponentFactory( initialUris = config.uris, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.ExtractPages -> ExtractPagesPdfTool( extractPagesPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) is Screen.PdfTools.RemoveAnnotations -> RemoveAnnotationsPdfTool( removeAnnotationsPdfToolComponentFactory( initialUri = config.uri, componentContext = componentContext, onGoBack = ::navigateBack, onNavigate = ::replaceTo ) ) } } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/navigation/NavigationChild.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.navigation import androidx.compose.runtime.Composable import com.t8rin.imagetoolbox.collage_maker.presentation.CollageMakerContent import com.t8rin.imagetoolbox.collage_maker.presentation.screenLogic.CollageMakerComponent import com.t8rin.imagetoolbox.color_library.presentation.ColorLibraryContent import com.t8rin.imagetoolbox.color_library.presentation.screenLogic.ColorLibraryComponent import com.t8rin.imagetoolbox.color_tools.presentation.ColorToolsContent import com.t8rin.imagetoolbox.color_tools.presentation.screenLogic.ColorToolsComponent import com.t8rin.imagetoolbox.feature.ai_tools.presentation.AiToolsContent import com.t8rin.imagetoolbox.feature.ai_tools.presentation.screenLogic.AiToolsComponent import com.t8rin.imagetoolbox.feature.apng_tools.presentation.ApngToolsContent import com.t8rin.imagetoolbox.feature.apng_tools.presentation.screenLogic.ApngToolsComponent import com.t8rin.imagetoolbox.feature.ascii_art.presentation.AsciiArtContent import com.t8rin.imagetoolbox.feature.ascii_art.presentation.screenLogic.AsciiArtComponent import com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui.AudioCoverExtractorContent import com.t8rin.imagetoolbox.feature.audio_cover_extractor.ui.screenLogic.AudioCoverExtractorComponent import com.t8rin.imagetoolbox.feature.base64_tools.presentation.Base64ToolsContent import com.t8rin.imagetoolbox.feature.base64_tools.presentation.screenLogic.Base64ToolsComponent import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.ChecksumToolsContent import com.t8rin.imagetoolbox.feature.checksum_tools.presentation.screenLogic.ChecksumToolsComponent import com.t8rin.imagetoolbox.feature.cipher.presentation.CipherContent import com.t8rin.imagetoolbox.feature.cipher.presentation.screenLogic.CipherComponent import com.t8rin.imagetoolbox.feature.compare.presentation.CompareContent import com.t8rin.imagetoolbox.feature.compare.presentation.screenLogic.CompareComponent import com.t8rin.imagetoolbox.feature.crop.presentation.CropContent import com.t8rin.imagetoolbox.feature.crop.presentation.screenLogic.CropComponent import com.t8rin.imagetoolbox.feature.delete_exif.presentation.DeleteExifContent import com.t8rin.imagetoolbox.feature.delete_exif.presentation.screenLogic.DeleteExifComponent import com.t8rin.imagetoolbox.feature.document_scanner.presentation.DocumentScannerContent import com.t8rin.imagetoolbox.feature.document_scanner.presentation.screenLogic.DocumentScannerComponent import com.t8rin.imagetoolbox.feature.draw.presentation.DrawContent import com.t8rin.imagetoolbox.feature.draw.presentation.screenLogic.DrawComponent import com.t8rin.imagetoolbox.feature.easter_egg.presentation.EasterEggContent import com.t8rin.imagetoolbox.feature.easter_egg.presentation.screenLogic.EasterEggComponent import com.t8rin.imagetoolbox.feature.edit_exif.presentation.EditExifContent import com.t8rin.imagetoolbox.feature.edit_exif.presentation.screenLogic.EditExifComponent import com.t8rin.imagetoolbox.feature.erase_background.presentation.EraseBackgroundContent import com.t8rin.imagetoolbox.feature.erase_background.presentation.screenLogic.EraseBackgroundComponent import com.t8rin.imagetoolbox.feature.filters.presentation.FiltersContent import com.t8rin.imagetoolbox.feature.filters.presentation.screenLogic.FiltersComponent import com.t8rin.imagetoolbox.feature.format_conversion.presentation.FormatConversionContent import com.t8rin.imagetoolbox.feature.format_conversion.presentation.screenLogic.FormatConversionComponent import com.t8rin.imagetoolbox.feature.gif_tools.presentation.GifToolsContent import com.t8rin.imagetoolbox.feature.gif_tools.presentation.screenLogic.GifToolsComponent import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.GradientMakerContent import com.t8rin.imagetoolbox.feature.gradient_maker.presentation.screenLogic.GradientMakerComponent import com.t8rin.imagetoolbox.feature.image_preview.presentation.ImagePreviewContent import com.t8rin.imagetoolbox.feature.image_preview.presentation.screenLogic.ImagePreviewComponent import com.t8rin.imagetoolbox.feature.image_stacking.presentation.ImageStackingContent import com.t8rin.imagetoolbox.feature.image_stacking.presentation.screenLogic.ImageStackingComponent import com.t8rin.imagetoolbox.feature.image_stitch.presentation.ImageStitchingContent import com.t8rin.imagetoolbox.feature.image_stitch.presentation.screenLogic.ImageStitchingComponent import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.JxlToolsContent import com.t8rin.imagetoolbox.feature.jxl_tools.presentation.screenLogic.JxlToolsComponent import com.t8rin.imagetoolbox.feature.libraries_info.presentation.LibrariesInfoContent import com.t8rin.imagetoolbox.feature.libraries_info.presentation.screenLogic.LibrariesInfoComponent import com.t8rin.imagetoolbox.feature.limits_resize.presentation.LimitsResizeContent import com.t8rin.imagetoolbox.feature.limits_resize.presentation.screenLogic.LimitsResizeComponent import com.t8rin.imagetoolbox.feature.load_net_image.presentation.LoadNetImageContent import com.t8rin.imagetoolbox.feature.load_net_image.presentation.screenLogic.LoadNetImageComponent import com.t8rin.imagetoolbox.feature.main.presentation.MainContent import com.t8rin.imagetoolbox.feature.main.presentation.screenLogic.MainComponent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.MarkupLayersContent import com.t8rin.imagetoolbox.feature.markup_layers.presentation.screenLogic.MarkupLayersComponent import com.t8rin.imagetoolbox.feature.mesh_gradients.presentation.MeshGradientsContent import com.t8rin.imagetoolbox.feature.mesh_gradients.presentation.screenLogic.MeshGradientsComponent import com.t8rin.imagetoolbox.feature.palette_tools.presentation.PaletteToolsContent import com.t8rin.imagetoolbox.feature.palette_tools.presentation.screenLogic.PaletteToolsComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.compress.CompressPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.compress.screenLogic.CompressPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop.CropPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.crop.screenLogic.CropPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_images.ExtractImagesPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_images.screenLogic.ExtractImagesPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_pages.ExtractPagesPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.extract_pages.screenLogic.ExtractPagesPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.flatten.FlattenPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.flatten.screenLogic.FlattenPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.grayscale.GrayscalePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.grayscale.screenLogic.GrayscalePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.images_to_pdf.ImagesToPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.images_to_pdf.screenLogic.ImagesToPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.merge.MergePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.merge.screenLogic.MergePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata.MetadataPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.metadata.screenLogic.MetadataPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.ocr.OCRPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.ocr.screenLogic.OCRPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers.PageNumbersPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.page_numbers.screenLogic.PageNumbersPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.preview.PreviewPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.preview.screenLogic.PreviewPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.print.PrintPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.print.screenLogic.PrintPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.protect.ProtectPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.protect.screenLogic.ProtectPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.RearrangePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rearrange.screenLogic.RearrangePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.RemoveAnnotationsPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_annotations.screenLogic.RemoveAnnotationsPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages.RemovePagesPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.remove_pages.screenLogic.RemovePagesPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.repair.RepairPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.repair.screenLogic.RepairPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.RootPdfToolsContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.root.screenLogic.RootPdfToolsComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate.RotatePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.rotate.screenLogic.RotatePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.SignaturePdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.signature.screenLogic.SignaturePdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.split.SplitPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.split.screenLogic.SplitPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.unlock.UnlockPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.unlock.screenLogic.UnlockPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark.WatermarkPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.watermark.screenLogic.WatermarkPdfToolComponent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.zip_convert.ZipConvertPdfToolContent import com.t8rin.imagetoolbox.feature.pdf_tools.presentation.zip_convert.screenLogic.ZipConvertPdfToolComponent import com.t8rin.imagetoolbox.feature.pick_color.presentation.PickColorFromImageContent import com.t8rin.imagetoolbox.feature.pick_color.presentation.screenLogic.PickColorFromImageComponent import com.t8rin.imagetoolbox.feature.recognize.text.presentation.RecognizeTextContent import com.t8rin.imagetoolbox.feature.recognize.text.presentation.screenLogic.RecognizeTextComponent import com.t8rin.imagetoolbox.feature.resize_convert.presentation.ResizeAndConvertContent import com.t8rin.imagetoolbox.feature.resize_convert.presentation.screenLogic.ResizeAndConvertComponent import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.ScanQrCodeContent import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.screenLogic.ScanQrCodeComponent import com.t8rin.imagetoolbox.feature.settings.presentation.SettingsContent import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent import com.t8rin.imagetoolbox.feature.single_edit.presentation.SingleEditContent import com.t8rin.imagetoolbox.feature.single_edit.presentation.screenLogic.SingleEditComponent import com.t8rin.imagetoolbox.feature.svg_maker.presentation.SvgMakerContent import com.t8rin.imagetoolbox.feature.svg_maker.presentation.screenLogic.SvgMakerComponent import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.WallpapersExportContent import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.screenLogic.WallpapersExportComponent import com.t8rin.imagetoolbox.feature.watermarking.presentation.WatermarkingContent import com.t8rin.imagetoolbox.feature.watermarking.presentation.screenLogic.WatermarkingComponent import com.t8rin.imagetoolbox.feature.webp_tools.presentation.WebpToolsContent import com.t8rin.imagetoolbox.feature.webp_tools.presentation.screenLogic.WebpToolsComponent import com.t8rin.imagetoolbox.feature.weight_resize.presentation.WeightResizeContent import com.t8rin.imagetoolbox.feature.weight_resize.presentation.screenLogic.WeightResizeComponent import com.t8rin.imagetoolbox.feature.zip.presentation.ZipContent import com.t8rin.imagetoolbox.feature.zip.presentation.screenLogic.ZipComponent import com.t8rin.imagetoolbox.image_cutting.presentation.ImageCutterContent import com.t8rin.imagetoolbox.image_cutting.presentation.screenLogic.ImageCutterComponent import com.t8rin.imagetoolbox.image_splitting.presentation.ImageSplitterContent import com.t8rin.imagetoolbox.image_splitting.presentation.screenLogic.ImageSplitterComponent import com.t8rin.imagetoolbox.library_details.presentation.LibraryDetailsContent import com.t8rin.imagetoolbox.library_details.presentation.screenLogic.LibraryDetailsComponent import com.t8rin.imagetoolbox.noise_generation.presentation.NoiseGenerationContent import com.t8rin.imagetoolbox.noise_generation.presentation.screenLogic.NoiseGenerationComponent internal sealed interface NavigationChild { @Composable fun Content() class ApngTools(private val component: ApngToolsComponent) : NavigationChild { @Composable override fun Content() = ApngToolsContent(component) } class Cipher(private val component: CipherComponent) : NavigationChild { @Composable override fun Content() = CipherContent(component) } class CollageMaker(private val component: CollageMakerComponent) : NavigationChild { @Composable override fun Content() = CollageMakerContent(component) } class ColorTools(private val component: ColorToolsComponent) : NavigationChild { @Composable override fun Content() = ColorToolsContent(component) } class Compare(private val component: CompareComponent) : NavigationChild { @Composable override fun Content() = CompareContent(component) } class Crop(private val component: CropComponent) : NavigationChild { @Composable override fun Content() = CropContent(component) } class DeleteExif(private val component: DeleteExifComponent) : NavigationChild { @Composable override fun Content() = DeleteExifContent(component) } class DocumentScanner(private val component: DocumentScannerComponent) : NavigationChild { @Composable override fun Content() = DocumentScannerContent(component) } class Draw(private val component: DrawComponent) : NavigationChild { @Composable override fun Content() = DrawContent(component) } class EasterEgg(private val component: EasterEggComponent) : NavigationChild { @Composable override fun Content() = EasterEggContent(component) } class EraseBackground(private val component: EraseBackgroundComponent) : NavigationChild { @Composable override fun Content() = EraseBackgroundContent(component) } class Filter(private val component: FiltersComponent) : NavigationChild { @Composable override fun Content() = FiltersContent(component) } class FormatConversion(private val component: FormatConversionComponent) : NavigationChild { @Composable override fun Content() = FormatConversionContent(component) } class PaletteTools(private val component: PaletteToolsComponent) : NavigationChild { @Composable override fun Content() = PaletteToolsContent(component) } class GifTools(private val component: GifToolsComponent) : NavigationChild { @Composable override fun Content() = GifToolsContent(component) } class GradientMaker(private val component: GradientMakerComponent) : NavigationChild { @Composable override fun Content() = GradientMakerContent(component) } class ImagePreview(private val component: ImagePreviewComponent) : NavigationChild { @Composable override fun Content() = ImagePreviewContent(component) } class ImageSplitting(private val component: ImageSplitterComponent) : NavigationChild { @Composable override fun Content() = ImageSplitterContent(component) } class ImageStacking(private val component: ImageStackingComponent) : NavigationChild { @Composable override fun Content() = ImageStackingContent(component) } class ImageStitching(private val component: ImageStitchingComponent) : NavigationChild { @Composable override fun Content() = ImageStitchingContent(component) } class JxlTools(private val component: JxlToolsComponent) : NavigationChild { @Composable override fun Content() = JxlToolsContent(component) } class LimitResize(private val component: LimitsResizeComponent) : NavigationChild { @Composable override fun Content() = LimitsResizeContent(component) } class LoadNetImage(private val component: LoadNetImageComponent) : NavigationChild { @Composable override fun Content() = LoadNetImageContent(component) } class Main(private val component: MainComponent) : NavigationChild { @Composable override fun Content() = MainContent(component) } class NoiseGeneration(private val component: NoiseGenerationComponent) : NavigationChild { @Composable override fun Content() = NoiseGenerationContent(component) } class RootPdfTools(private val component: RootPdfToolsComponent) : NavigationChild { @Composable override fun Content() = RootPdfToolsContent(component) } class PickColorFromImage(private val component: PickColorFromImageComponent) : NavigationChild { @Composable override fun Content() = PickColorFromImageContent(component) } class RecognizeText(private val component: RecognizeTextComponent) : NavigationChild { @Composable override fun Content() = RecognizeTextContent(component) } class ResizeAndConvert(private val component: ResizeAndConvertComponent) : NavigationChild { @Composable override fun Content() = ResizeAndConvertContent(component) } class ScanQrCode(private val component: ScanQrCodeComponent) : NavigationChild { @Composable override fun Content() = ScanQrCodeContent(component) } class Settings(private val component: SettingsComponent) : NavigationChild { @Composable override fun Content() = SettingsContent(component) } class SingleEdit(private val component: SingleEditComponent) : NavigationChild { @Composable override fun Content() = SingleEditContent(component) } class SvgMaker(private val component: SvgMakerComponent) : NavigationChild { @Composable override fun Content() = SvgMakerContent(component) } class Watermarking(private val component: WatermarkingComponent) : NavigationChild { @Composable override fun Content() = WatermarkingContent(component) } class WebpTools(private val component: WebpToolsComponent) : NavigationChild { @Composable override fun Content() = WebpToolsContent(component) } class WeightResize(private val component: WeightResizeComponent) : NavigationChild { @Composable override fun Content() = WeightResizeContent(component) } class Zip(private val component: ZipComponent) : NavigationChild { @Composable override fun Content() = ZipContent(component) } class LibrariesInfo(private val component: LibrariesInfoComponent) : NavigationChild { @Composable override fun Content() = LibrariesInfoContent(component) } class MarkupLayers(private val component: MarkupLayersComponent) : NavigationChild { @Composable override fun Content() = MarkupLayersContent(component) } class Base64Tools(private val component: Base64ToolsComponent) : NavigationChild { @Composable override fun Content() = Base64ToolsContent(component) } class ChecksumTools(private val component: ChecksumToolsComponent) : NavigationChild { @Composable override fun Content() = ChecksumToolsContent(component) } class MeshGradients(private val component: MeshGradientsComponent) : NavigationChild { @Composable override fun Content() = MeshGradientsContent(component) } class EditExif(private val component: EditExifComponent) : NavigationChild { @Composable override fun Content() = EditExifContent(component) } class ImageCutter(private val component: ImageCutterComponent) : NavigationChild { @Composable override fun Content() = ImageCutterContent(component) } class AudioCoverExtractor( private val component: AudioCoverExtractorComponent ) : NavigationChild { @Composable override fun Content() = AudioCoverExtractorContent(component) } class LibraryDetails(private val component: LibraryDetailsComponent) : NavigationChild { @Composable override fun Content() = LibraryDetailsContent(component) } class WallpapersExport(private val component: WallpapersExportComponent) : NavigationChild { @Composable override fun Content() = WallpapersExportContent(component) } class AsciiArt(private val component: AsciiArtComponent) : NavigationChild { @Composable override fun Content() = AsciiArtContent(component) } class AiTools(private val component: AiToolsComponent) : NavigationChild { @Composable override fun Content() = AiToolsContent(component) } class ColorLibrary(private val component: ColorLibraryComponent) : NavigationChild { @Composable override fun Content() = ColorLibraryContent(component) } class MergePdfTool(private val component: MergePdfToolComponent) : NavigationChild { @Composable override fun Content() = MergePdfToolContent(component) } class SplitPdfTool(private val component: SplitPdfToolComponent) : NavigationChild { @Composable override fun Content() = SplitPdfToolContent(component) } class RotatePdfTool(private val component: RotatePdfToolComponent) : NavigationChild { @Composable override fun Content() = RotatePdfToolContent(component) } class RearrangePdfTool(private val component: RearrangePdfToolComponent) : NavigationChild { @Composable override fun Content() = RearrangePdfToolContent(component) } class PageNumbersPdfTool(private val component: PageNumbersPdfToolComponent) : NavigationChild { @Composable override fun Content() = PageNumbersPdfToolContent(component) } class OCRPdfTool(private val component: OCRPdfToolComponent) : NavigationChild { @Composable override fun Content() = OCRPdfToolContent(component) } class WatermarkPdfTool(private val component: WatermarkPdfToolComponent) : NavigationChild { @Composable override fun Content() = WatermarkPdfToolContent(component) } class SignaturePdfTool(private val component: SignaturePdfToolComponent) : NavigationChild { @Composable override fun Content() = SignaturePdfToolContent(component) } class ProtectPdfTool(private val component: ProtectPdfToolComponent) : NavigationChild { @Composable override fun Content() = ProtectPdfToolContent(component) } class UnlockPdfTool(private val component: UnlockPdfToolComponent) : NavigationChild { @Composable override fun Content() = UnlockPdfToolContent(component) } class CompressPdfTool(private val component: CompressPdfToolComponent) : NavigationChild { @Composable override fun Content() = CompressPdfToolContent(component) } class GrayscalePdfTool(private val component: GrayscalePdfToolComponent) : NavigationChild { @Composable override fun Content() = GrayscalePdfToolContent(component) } class RepairPdfTool(private val component: RepairPdfToolComponent) : NavigationChild { @Composable override fun Content() = RepairPdfToolContent(component) } class MetadataPdfTool(private val component: MetadataPdfToolComponent) : NavigationChild { @Composable override fun Content() = MetadataPdfToolContent(component) } class RemovePagesPdfTool(private val component: RemovePagesPdfToolComponent) : NavigationChild { @Composable override fun Content() = RemovePagesPdfToolContent(component) } class CropPdfTool(private val component: CropPdfToolComponent) : NavigationChild { @Composable override fun Content() = CropPdfToolContent(component) } class FlattenPdfTool(private val component: FlattenPdfToolComponent) : NavigationChild { @Composable override fun Content() = FlattenPdfToolContent(component) } class ExtractImagesPdfTool(private val component: ExtractImagesPdfToolComponent) : NavigationChild { @Composable override fun Content() = ExtractImagesPdfToolContent(component) } class ZipConvertPdfTool(private val component: ZipConvertPdfToolComponent) : NavigationChild { @Composable override fun Content() = ZipConvertPdfToolContent(component) } class PrintPdfTool(private val component: PrintPdfToolComponent) : NavigationChild { @Composable override fun Content() = PrintPdfToolContent(component) } class PreviewPdfTool(private val component: PreviewPdfToolComponent) : NavigationChild { @Composable override fun Content() = PreviewPdfToolContent(component) } class ImagesToPdfTool(private val component: ImagesToPdfToolComponent) : NavigationChild { @Composable override fun Content() = ImagesToPdfToolContent(component) } class ExtractPagesPdfTool(private val component: ExtractPagesPdfToolComponent) : NavigationChild { @Composable override fun Content() = ExtractPagesPdfToolContent(component) } class RemoveAnnotationsPdfTool(private val component: RemoveAnnotationsPdfToolComponent) : NavigationChild { @Composable override fun Content() = RemoveAnnotationsPdfToolContent(component) } } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/BackEventObserver.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.utils import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen fun interface BackEventObserver { fun onBack(closedScreen: Screen?) } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ResetThemeOnGoBack.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.utils import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import com.t8rin.dynamic.theme.LocalDynamicThemeState import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberAppColorTuple import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent @Composable internal fun ResetThemeOnGoBack( component: RootComponent ) { val appColorTuple = rememberAppColorTuple() val themeState = LocalDynamicThemeState.current DisposableEffect(component, themeState, appColorTuple) { val observer = BackEventObserver { themeState.updateColorTuple(appColorTuple) } component.addBackEventsObserver(observer) onDispose { component.removeBackEventsObserver(observer) } } } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/ScreenBasedMaxBrightnessEnforcement.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.utils import android.view.WindowManager import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.util.fastAny import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity @Composable internal fun ScreenBasedMaxBrightnessEnforcement( currentScreen: Screen? ) { val context = LocalComponentActivity.current val listToForceBrightness = LocalSettingsState.current.screenListWithMaxBrightnessEnforcement DisposableEffect(currentScreen) { if (listToForceBrightness.fastAny { it == currentScreen?.id }) { context.window.apply { attributes.apply { screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL } addFlags(WindowManager.LayoutParams.SCREEN_BRIGHTNESS_CHANGED) } } onDispose { context.window.apply { attributes.apply { screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE } addFlags(WindowManager.LayoutParams.SCREEN_BRIGHTNESS_CHANGED) } } } } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SettingsUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.utils import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.value.Value import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.presentation.model.UiSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent import kotlinx.coroutines.delay @Composable internal fun RootComponent.uiSettingsState(): UiSettingsState = settingsState.toUiState( randomEmojiKey = childStack.randomEmojiKey() ) @Composable internal fun HandleLookForUpdates(component: RootComponent) { if (component.settingsState.shouldTryGetUpdate) { LaunchedEffect(Unit) { delay(500) component.tryGetUpdate() } } } private val SettingsState.shouldTryGetUpdate: Boolean get() = appOpenCount >= 2 && showUpdateDialogOnStartup @Composable private fun Value>.randomEmojiKey(): Any { val randomEmojiKey = remember { mutableIntStateOf(0) } val currentDestination = subscribeAsState().value.items LaunchedEffect(currentDestination) { delay(200L) // Delay for transition randomEmojiKey.intValue++ } return randomEmojiKey.intValue } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/components/utils/SuccessRestoreBackupToastHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.components.utils import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Save import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.feature.root.presentation.screenLogic.RootComponent import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @Composable internal fun SuccessRestoreBackupToastHandler(component: RootComponent) { val context = LocalComponentActivity.current LaunchedEffect(component) { component.backupRestoredEvents.collectLatest { restored -> if (restored) { launch { AppToastHost.showConfetti() //Wait for confetti to appear, then trigger font scale adjustment delay(300L) context.recreate() } AppToastHost.showToast( message = context.getString(R.string.settings_restored), icon = Icons.Rounded.Save ) } } } } ================================================ FILE: feature/root/src/main/java/com/t8rin/imagetoolbox/feature/root/presentation/screenLogic/RootComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.root.presentation.screenLogic import android.content.Intent import android.net.Uri import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ErrorOutline import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.childContext import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.router.stack.items import com.arkivanov.decompose.router.stack.navigate import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pushNew import com.arkivanov.decompose.value.MutableValue import com.arkivanov.decompose.value.Value import com.t8rin.imagetoolbox.core.domain.APP_CHANGELOG import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.model.ExtraDataType import com.t8rin.imagetoolbox.core.domain.model.ImageModel import com.t8rin.imagetoolbox.core.domain.model.PerformanceClass import com.t8rin.imagetoolbox.core.domain.remote.AnalyticsManager import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.filters.domain.FilterParamsInteractor import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.handleDeeplinks import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.widget.other.ToastDuration import com.t8rin.imagetoolbox.core.utils.isNeedUpdate import com.t8rin.imagetoolbox.core.utils.parseChangelog import com.t8rin.imagetoolbox.core.utils.toImageModel import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.ChildProvider import com.t8rin.imagetoolbox.feature.root.presentation.components.navigation.NavigationChild import com.t8rin.imagetoolbox.feature.root.presentation.components.utils.BackEventObserver import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent import com.t8rin.logger.makeLog import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.client.statement.bodyAsChannel import io.ktor.utils.io.jvm.javaio.toInputStream import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext class RootComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, private val settingsManager: SettingsManager, private val childProvider: ChildProvider, private val analyticsManager: AnalyticsManager, private val client: HttpClient, filterParamsInteractor: FilterParamsInteractor, fileController: FileController, dispatchersHolder: DispatchersHolder, settingsComponentFactory: SettingsComponent.Factory, resourceManager: ResourceManager, ) : BaseComponent(dispatchersHolder, componentContext), ResourceManager by resourceManager { private var updatesJob: Job? by smartJob() private val _backupRestoredEvents: Channel = Channel(Channel.BUFFERED) val backupRestoredEvents: Flow = _backupRestoredEvents.receiveAsFlow() private val _isUpdateAvailable: MutableValue = MutableValue(false) val isUpdateAvailable: Value = _isUpdateAvailable private val _concealBackdropChannel: Channel = Channel(Channel.BUFFERED) val concealBackdropFlow: Flow = _concealBackdropChannel.receiveAsFlow() val settingsComponent = settingsComponentFactory( componentContext = childContext("rootSettings"), onTryGetUpdate = ::tryGetUpdate, onNavigate = ::navigateTo, isUpdateAvailable = isUpdateAvailable, onGoBack = { _concealBackdropChannel.trySend(true) }, initialSearchQuery = "" ) private val _settingsState = mutableStateOf(SettingsState.Default) val settingsState: SettingsState by _settingsState private val navController = StackNavigation() internal val childStack: Value> by lazy { childStack( source = navController, initialConfiguration = Screen.Main, serializer = Screen.serializer(), handleBackButton = true, childFactory = { screen, context -> with(childProvider) { createChild( config = screen, componentContext = context ) } } ) } private val _uris = mutableStateOf?>(null) val uris by _uris private val _extraDataType = mutableStateOf(null) val extraDataType: ExtraDataType? by _extraDataType private val _showSelectDialog = mutableStateOf(false) val showSelectDialog by _showSelectDialog private val _showUpdateDialog = mutableStateOf(false) val showUpdateDialog by _showUpdateDialog private val _isUpdateCancelled = mutableStateOf(false) private val _shouldShowExitDialog = mutableStateOf(true) val shouldShowDialog by _shouldShowExitDialog private val _showGithubReviewDialog = mutableStateOf(false) val showGithubReviewDialog by _showGithubReviewDialog private val _showTelegramGroupDialog = mutableStateOf(false) val showTelegramGroupDialog by _showTelegramGroupDialog private val _tag = mutableStateOf("") val tag by _tag private val _changelog = mutableStateOf("") val changelog by _changelog private val _filterPreviewModel: MutableState = mutableStateOf(R.drawable.filter_preview_source.toImageModel()) val filterPreviewModel by _filterPreviewModel private val _canSetDynamicFilterPreview: MutableState = mutableStateOf(false) val canSetDynamicFilterPreview by _canSetDynamicFilterPreview init { runBlocking { _settingsState.value = settingsManager.getSettingsState() if (settingsState.clearCacheOnLaunch && fileController.getCacheSize() > 20 * 1024 * 1024) { fileController.clearCache() } } settingsManager .settingsState .onEach { state -> _showTelegramGroupDialog.update { state.appOpenCount % 6 == 0 && state.appOpenCount != 0 && !state.isTelegramGroupOpened } _settingsState.value = state } .launchIn(componentScope) if (settingsState.screenList.size != Screen.entries.size) { componentScope.launch { val currentList = settingsState.screenList val neededList = Screen.entries.filter { it.id !in currentList }.map { it.id } settingsManager.setScreenOrder( (currentList + neededList).joinToString("/") ) } } filterParamsInteractor .getFilterPreviewModel().onEach { data -> _filterPreviewModel.update { data } }.launchIn(componentScope) filterParamsInteractor .getCanSetDynamicFilterPreview().onEach { value -> _canSetDynamicFilterPreview.update { value } }.launchIn(componentScope) } fun toggleShowUpdateDialog() { componentScope.launch { settingsManager.toggleShowUpdateDialogOnStartup() } } fun setPresets(newPresets: List) { componentScope.launch { settingsManager.setPresets(newPresets) } } fun cancelledUpdate(showAgain: Boolean = false) { if (!showAgain) _isUpdateCancelled.value = true _showUpdateDialog.value = false } fun tryGetUpdate( isNewRequest: Boolean = false, onNoUpdates: () -> Unit = {} ) { if (settingsState.appOpenCount < 2 && !isNewRequest) return val isInstalledFromMarket = settingsManager.isInstalledFromPlayStore() val showDialog = settingsState.showUpdateDialogOnStartup if (isInstalledFromMarket) { if (showDialog) { _showUpdateDialog.value = isNewRequest } } else { if (!_isUpdateCancelled.value || isNewRequest) { updatesJob = componentScope.launch { checkForUpdates( showDialog = showDialog, onNoUpdates = onNoUpdates ) } } } } private suspend fun checkForUpdates( showDialog: Boolean, onNoUpdates: () -> Unit ) = withContext(defaultDispatcher) { "start updates check".makeLog("checkForUpdates") runCatching { val (tag, changelog) = client .get(APP_CHANGELOG).bodyAsChannel().toInputStream() .use { it.parseChangelog() } _tag.update { tag } _changelog.update { changelog } val isNeedUpdate = isNeedUpdate( updateName = tag, allowBetas = settingsState.allowBetas ).makeLog("checkForUpdates") { "isNeedUpdate = $it" } if (isNeedUpdate) { _isUpdateAvailable.value = true _showUpdateDialog.value = showDialog } else { onNoUpdates() } }.onFailure { it.makeLog("checkForUpdates") onNoUpdates() } } fun hideSelectDialog() { _showSelectDialog.value = false _uris.update { null } } fun updateUris(uris: List) { _uris.value = uris if (uris.isNotEmpty() || extraDataType != null) { _showSelectDialog.value = true } } fun updateExtraDataType(type: ExtraDataType?) { type.makeLog("updateExtraDataType") if (type is ExtraDataType.Backup) { componentScope.launch { settingsManager.restoreFromBackupFile( backupFileUri = type.uri.trim(), onSuccess = { _backupRestoredEvents.trySend(true) }, onFailure = { throwable -> AppToastHost.showToast( message = getString( R.string.smth_went_wrong, throwable.localizedMessage ?: "" ), icon = Icons.Rounded.ErrorOutline, duration = ToastDuration.Long ) _backupRestoredEvents.trySend(false) } ) } return } _extraDataType.update { null } _extraDataType.update { type } } fun cancelShowingExitDialog() { _shouldShowExitDialog.update { false } } fun toggleAllowBetas() { componentScope.launch { settingsManager.toggleAllowBetas() } } fun onWantGithubReview() { _showGithubReviewDialog.update { true } } fun hideReviewDialog() { _showGithubReviewDialog.update { false } } fun hideTelegramGroupDialog() { _showTelegramGroupDialog.update { false } } fun adjustPerformance(performanceClass: PerformanceClass) { componentScope.launch { settingsManager.adjustPerformance(performanceClass) } } fun registerDonateDialogOpen() { componentScope.launch { settingsManager.registerDonateDialogOpen() } } fun notShowDonateDialogAgain() { componentScope.launch { settingsManager.setNotShowDonateDialogAgain() } } fun registerTelegramGroupOpen() { componentScope.launch { settingsManager.registerTelegramGroupOpen() } } fun navigateTo(screen: Screen) { componentScope.launch { delay(100) screen.simpleName.makeLog("Navigator").also(analyticsManager::registerScreenOpen) navController.pushNew(screen) hideSelectDialog() } } fun replaceTo(screen: Screen) { componentScope.launch { delay(100) screen.simpleName.makeLog("Navigator").also(analyticsManager::registerScreenOpen) navController.navigate( transformer = { stack -> stack.dropLastWhile { it !is Screen.PdfTools } + screen } ) hideSelectDialog() } } fun navigateToNew(screen: Screen) { if (childStack.items.lastOrNull()?.configuration != Screen.Main) { navigateBack() } screen.simpleName.makeLog("Navigator").also(analyticsManager::registerScreenOpen) navController.pushNew(screen) } private val backEventsObservers: MutableList = mutableListOf() fun navigateBack() { val closedScreen = childStack.items.lastOrNull()?.configuration backEventsObservers.forEach { observer -> observer.onBack(closedScreen) } hideSelectDialog() "Pop ${closedScreen?.simpleName}".makeLog("Navigator") navController.pop() } fun addBackEventsObserver( observer: BackEventObserver ) { backEventsObservers.add(observer) } fun removeBackEventsObserver( observer: BackEventObserver ) { backEventsObservers.remove(observer) } fun handleDeeplinks(intent: Intent?) { intent.handleDeeplinks( onStart = ::hideSelectDialog, onHasExtraDataType = ::updateExtraDataType, onColdStart = ::cancelShowingExitDialog, onGetUris = ::updateUris, onNavigate = ::navigateTo, isHasUris = !uris.isNullOrEmpty(), onWantGithubReview = ::onWantGithubReview, isOpenEditInsteadOfPreview = settingsState.openEditInsteadOfPreview ) } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext ): RootComponent } } ================================================ FILE: feature/scan-qr-code/.gitignore ================================================ /build ================================================ FILE: feature/scan-qr-code/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.scan_qr_code" dependencies { implementation(projects.core.filters) "marketImplementation"(libs.quickie.bundled) "fossImplementation"(libs.quickie.foss) } ================================================ FILE: feature/scan-qr-code/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/data/AndroidImageBarcodeReader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.data import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.data.utils.toSoftware import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.utils.toQrType import com.t8rin.imagetoolbox.feature.scan_qr_code.domain.ImageBarcodeReader import io.github.g00fy2.quickie.extensions.readQrCode import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.coroutines.resume internal class AndroidImageBarcodeReader @Inject constructor( private val imageGetter: ImageGetter, resourceManager: ResourceManager, dispatchersHolder: DispatchersHolder ) : ImageBarcodeReader, DispatchersHolder by dispatchersHolder, ResourceManager by resourceManager { override suspend fun readBarcode( image: Any ): Result = withContext(defaultDispatcher) { val bitmap = image as? Bitmap ?: imageGetter.getImage( data = image, originalSize = false ) if (bitmap == null) { return@withContext Result.failure(NullPointerException(getString(R.string.something_went_wrong))) } suspendCancellableCoroutine { continuation -> bitmap.toSoftware().readQrCode( barcodeFormats = IntArray(0), onSuccess = { continuation.resume(Result.success(it.toQrType())) }, onFailure = { continuation.resume(Result.failure(it)) } ) } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/di/ScanQrCodeModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.di import com.t8rin.imagetoolbox.feature.scan_qr_code.data.AndroidImageBarcodeReader import com.t8rin.imagetoolbox.feature.scan_qr_code.domain.ImageBarcodeReader import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ScanQrCodeModule { @Binds @Singleton fun reader( impl: AndroidImageBarcodeReader ): ImageBarcodeReader } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/domain/ImageBarcodeReader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.domain import com.t8rin.imagetoolbox.core.domain.model.QrType interface ImageBarcodeReader { suspend fun readBarcode( image: Any ): Result } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/ScanQrCodeContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation import android.annotation.SuppressLint import android.graphics.Bitmap import android.net.Uri import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AutoFixHigh import androidx.compose.material.icons.rounded.ImageSearch import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BarcodeScanner import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.capturable.rememberCaptureController import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberBarcodeScanner import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.BarcodeType import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.other.renderAsQr import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.QrCodePreview import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.ScanQrCodeControls import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.screenLogic.ScanQrCodeComponent import kotlinx.coroutines.delay import kotlinx.coroutines.launch @SuppressLint("StringFormatInvalid") @Composable fun ScanQrCodeContent( component: ScanQrCodeComponent ) { val params = component.params val scanner = rememberBarcodeScanner { component.updateParams( params = params.copy( content = it ) ) } val isNotScannable = params.content.raw.isNotEmpty() && component.mayBeNotScannable val isSaveEnabled = params.content.raw.isNotEmpty() && component.isSaveEnabled val analyzerImagePicker = rememberImagePicker { uri: Uri -> component.readBarcodeFromImage( image = uri, onFailure = { AppToastHost.showFailureToast( Throwable(getString(R.string.no_barcode_found), it) ) } ) } val captureController = rememberCaptureController() LaunchedEffect(params) { if (params.content.raw.isEmpty()) return@LaunchedEffect delay(500) component.syncReadBarcodeFromImage( image = params.qrParams.renderAsQr( content = params.content.raw, type = params.type ) ) } LaunchedEffect(params.content) { component.processFilterTemplateFromQrContent( onSuccess = { filterName, filtersCount -> AppToastHost.showToast( message = getString( R.string.added_filter_template, filterName, filtersCount ), icon = Icons.Outlined.AutoFixHigh ) } ) } val saveBitmap: (oneTimeSaveLocationUri: String?, bitmap: Bitmap) -> Unit = { oneTimeSaveLocationUri, bitmap -> component.saveBitmap( bitmap = bitmap, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) } val isPortrait by isPortraitOrientationAsState() val scope = rememberCoroutineScope() AdaptiveLayoutScreen( shouldDisableBackHandler = true, title = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.qr_code) ) EnhancedBadge( content = { Text( text = BarcodeType.entries.size.toString() ) }, containerColor = MaterialTheme.colorScheme.tertiary, contentColor = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .padding(horizontal = 2.dp) .padding(bottom = 12.dp) .scaleOnTap { AppToastHost.showConfetti() } ) } }, onGoBack = component.onGoBack, actions = { ShareButton( enabled = params.content.raw.isNotEmpty(), onShare = { scope.launch { component.shareImage( bitmap = captureController.bitmap() ) } }, onCopy = { scope.launch { component.cacheImage( bitmap = captureController.bitmap(), onComplete = Clipboard::copy ) } } ) }, topAppBarPersistentActions = { TopAppBarEmoji() }, showImagePreviewAsStickyHeader = false, imagePreview = { if (!isPortrait) { QrCodePreview( captureController = captureController, isLandscape = true, params = params, onStartScan = scanner::scan ) } }, controls = { if (isPortrait) { Spacer(modifier = Modifier.height(20.dp)) QrCodePreview( captureController = captureController, isLandscape = false, params = params, onStartScan = scanner::scan ) Spacer(modifier = Modifier.height(16.dp)) } ScanQrCodeControls( component = component ) }, buttons = { actions -> var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = params.content.raw.isEmpty() && isPortrait, secondaryButtonIcon = Icons.Outlined.BarcodeScanner, secondaryButtonText = stringResource(R.string.start_scanning), onSecondaryButtonClick = scanner::scan, isPrimaryButtonEnabled = isSaveEnabled, onPrimaryButtonClick = { scope.launch { saveBitmap(null, captureController.bitmap()) } }, primaryButtonContainerColor = takeColorFromScheme { if (isNotScannable) error else primaryContainer }, primaryButtonContentColor = takeColorFromScheme { if (isNotScannable) onError else onPrimaryContainer }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, isPrimaryButtonVisible = isPortrait || params.content.raw.isNotEmpty(), actions = { if (isPortrait) actions() }, showMiddleFabInRow = true, middleFab = { EnhancedFloatingActionButton( onClick = analyzerImagePicker::pickImage, onLongClick = { showOneTimeImagePickingDialog = true }, containerColor = takeColorFromScheme { if (params.content.raw.isEmpty()) tertiaryContainer else secondaryContainer } ) { Icon( imageVector = Icons.Rounded.ImageSearch, contentDescription = null ) } } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = { scope.launch { saveBitmap(it, captureController.bitmap()) } }, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = analyzerImagePicker, visible = showOneTimeImagePickingDialog ) }, canShowScreenData = true ) LoadingDialog( visible = component.isSaving, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrCodePreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateIntAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min import coil3.request.ImageRequest import coil3.size.Precision import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.theme.ProvideTypography import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.theme.takeIf import com.t8rin.imagetoolbox.core.ui.utils.capturable.CaptureController import com.t8rin.imagetoolbox.core.ui.utils.capturable.capturable import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.rememberPrevious import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.other.QrCode import com.t8rin.imagetoolbox.core.utils.appContext @Composable internal fun QrCodePreview( captureController: CaptureController, isLandscape: Boolean, params: QrPreviewParams, onStartScan: () -> Unit ) { Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center ) { Column(Modifier.capturable(captureController)) { if (params.imageUri != null) { Spacer(modifier = Modifier.height(32.dp)) } BoxWithConstraints( modifier = Modifier .then( if ((params.imageUri != null || params.description.isNotEmpty()) && params.content.raw.isNotEmpty()) { Modifier .background( color = takeColorFromScheme { if (isLandscape) { surfaceContainerLowest } else surfaceContainerLow }, shape = ShapeDefaults.default ) .padding(16.dp) } else Modifier ) ) { val targetSize = min(min(this.maxWidth, this.maxHeight), 400.dp) Column( horizontalAlignment = Alignment.CenterHorizontally ) { val previous = rememberPrevious(params) AnimatedContent( targetState = params.content.raw.isEmpty(), modifier = Modifier .padding( top = if (params.imageUri != null) 36.dp else 0.dp, bottom = if (params.description.isNotEmpty()) 16.dp else 0.dp ) .then( if (isLandscape) { Modifier .weight(1f, false) .aspectRatio(1f) } else Modifier ) ) { isEmpty -> if (isEmpty) { ImageNotPickedWidget( onPickImage = onStartScan, text = stringResource(R.string.generated_barcode_will_be_here), containerColor = MaterialTheme .colorScheme .surfaceContainerLowest .takeIf(isLandscape) ) } else { QrCode( content = params.content.raw, modifier = Modifier.width(targetSize), heightRatio = params.heightRatio, type = params.type, qrParams = params.qrParams, cornerRadius = animateIntAsState(params.cornersSize).value.dp, onSuccess = AppToastHost::dismissToasts, onFailure = { AppToastHost.dismissToasts() if (previous != params) AppToastHost.showFailureToast(it) } ) } } BoxAnimatedVisibility(visible = params.description.isNotEmpty() && params.content.raw.isNotEmpty()) { ProvideTypography(params.descriptionFont) { Text( text = params.description, style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center, modifier = Modifier.width(targetSize) ) } } } if (params.imageUri != null && params.content.raw.isNotEmpty()) { Picture( modifier = Modifier .align(Alignment.TopCenter) .offset(y = (-48).dp) .size(64.dp), model = remember(params.imageUri) { ImageRequest.Builder(appContext) .data(params.imageUri) .size(1000, 1000) .precision(Precision.INEXACT) .build() }, contentScale = ContentScale.Crop, filterQuality = FilterQuality.High, contentDescription = null, shape = MaterialTheme.shapes.medium ) } } } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import android.content.Intent import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.StickyNote2 import androidx.compose.material.icons.automirrored.rounded.ShortText import androidx.compose.material.icons.outlined.Badge import androidx.compose.material.icons.outlined.Business import androidx.compose.material.icons.outlined.Description import androidx.compose.material.icons.outlined.Event import androidx.compose.material.icons.outlined.Flag import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Phone import androidx.compose.material.icons.outlined.Place import androidx.compose.material.icons.outlined.RecordVoiceOver import androidx.compose.material.icons.outlined.Start import androidx.compose.material.icons.outlined.Topic import androidx.compose.material.icons.rounded.AlternateEmail import androidx.compose.material.icons.rounded.Numbers import androidx.compose.material.icons.rounded.Password import androidx.compose.material.icons.rounded.Public import androidx.compose.material.icons.rounded.Security import androidx.compose.material.icons.rounded.TextFields import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.model.QrType.Wifi.EncryptionType import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Latitude import com.t8rin.imagetoolbox.core.resources.icons.Longitude import com.t8rin.imagetoolbox.core.utils.getString import java.text.DateFormat internal data class InfoEntry( val icon: ImageVector, val text: String, val canCopy: Boolean, ) internal data class QrInfo( val title: String, val icon: ImageVector, val intent: Intent?, val data: List, ) { companion object } @Composable internal fun rememberQrInfo(qrType: QrType.Complex): QrInfo { return when (qrType) { is QrType.Wifi -> wifiQrInfo(qrType) is QrType.Email -> emailQrInfo(qrType) is QrType.Geo -> geoQrInfo(qrType) is QrType.Phone -> phoneQrInfo(qrType) is QrType.Sms -> smsQrInfo(qrType) is QrType.Contact -> contactQrInfo(qrType) is QrType.Calendar -> calendarQrInfo(qrType) } } @Composable private fun calendarQrInfo( qrType: QrType.Calendar ): QrInfo = qrInfoBuilder(qrType) { entry( InfoEntry( icon = Icons.Outlined.Event, text = qrType.summary.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.summary.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Description, text = qrType.description.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.description.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Place, text = qrType.location.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.location.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Person, text = qrType.organizer.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.organizer.isNotBlank() ) ) val start = runCatching { qrType.start?.let { DateFormat.getDateTimeInstance().format(it) }?.removeSuffix(":00") }.getOrNull().orEmpty() entry( InfoEntry( icon = Icons.Outlined.Start, text = start.ifBlank { getString(R.string.not_specified) }, canCopy = start.isNotBlank() ) ) val end = runCatching { qrType.end?.let { DateFormat.getDateTimeInstance().format(it) }?.removeSuffix(":00") }.getOrNull().orEmpty() entry( InfoEntry( icon = Icons.Outlined.Flag, text = end.ifBlank { getString(R.string.not_specified) }, canCopy = end.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Info, text = qrType.status.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.status.isNotBlank() ) ) } @Composable private fun contactQrInfo( qrType: QrType.Contact ): QrInfo = qrInfoBuilder(qrType) { val formattedName = qrType.name.formattedName.replace("\\", "") entry( InfoEntry( icon = Icons.Outlined.Person, text = formattedName.ifBlank { getString(R.string.not_specified) }, canCopy = formattedName.isNotBlank() ) ) if (qrType.name.pronunciation.isNotBlank()) { entry( InfoEntry( icon = Icons.Outlined.RecordVoiceOver, text = qrType.name.pronunciation, canCopy = true ) ) } entry( InfoEntry( icon = Icons.Outlined.Business, text = qrType.organization.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.organization.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Badge, text = qrType.title.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.title.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Phone, text = qrType.phones.joinToString("\n") { it.number } .ifBlank { getString(R.string.not_specified) }, canCopy = qrType.phones.isNotEmpty() ) ) entry( InfoEntry( icon = Icons.Rounded.AlternateEmail, text = qrType.emails.joinToString("\n") { it.address } .ifBlank { getString(R.string.not_specified) }, canCopy = qrType.emails.isNotEmpty() ) ) entry( InfoEntry( icon = Icons.Outlined.Link, text = qrType.urls.joinToString("\n") .ifBlank { getString(R.string.not_specified) }, canCopy = qrType.urls.isNotEmpty() ) ) entry( InfoEntry( icon = Icons.Outlined.Home, text = qrType.addresses.joinToString("\n") { it.addressLines.joinToString(" ") } .ifBlank { getString(R.string.not_specified) }, canCopy = qrType.addresses.isNotEmpty() ) ) } @Composable private fun smsQrInfo( qrType: QrType.Sms ): QrInfo = qrInfoBuilder(qrType) { entry( InfoEntry( icon = Icons.AutoMirrored.Outlined.StickyNote2, text = qrType.message.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.message.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Phone, text = qrType.phoneNumber.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.phoneNumber.isNotBlank() ) ) } @Composable private fun phoneQrInfo( qrType: QrType.Phone ): QrInfo = qrInfoBuilder(qrType) { entry( InfoEntry( icon = Icons.Rounded.Numbers, text = qrType.number.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.number.isNotBlank() ) ) } @Composable private fun geoQrInfo( qrType: QrType.Geo ): QrInfo = qrInfoBuilder(qrType) { val latitude = qrType.latitude?.toString()?.trimTrailingZero().orEmpty() val longitude = qrType.longitude?.toString()?.trimTrailingZero().orEmpty() entry( InfoEntry( icon = Icons.Outlined.Latitude, text = latitude.ifBlank { getString(R.string.not_specified) }, canCopy = latitude.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Longitude, text = longitude.ifBlank { getString(R.string.not_specified) }, canCopy = longitude.isNotBlank() ) ) } @Composable private fun emailQrInfo( qrType: QrType.Email ): QrInfo = qrInfoBuilder(qrType) { entry( InfoEntry( icon = Icons.Rounded.AlternateEmail, text = qrType.address.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.address.isNotBlank() ) ) entry( InfoEntry( icon = Icons.Outlined.Topic, text = qrType.subject.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.subject.isNotBlank() ) ) entry( InfoEntry( icon = Icons.AutoMirrored.Rounded.ShortText, text = qrType.body.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.body.isNotBlank() ) ) } @Composable private fun wifiQrInfo( qrType: QrType.Wifi ): QrInfo = qrInfoBuilder(qrType) { val ssid = InfoEntry( icon = Icons.Rounded.TextFields, text = qrType.ssid.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.ssid.isNotBlank() ) when (qrType.encryptionType) { EncryptionType.OPEN -> { entry( InfoEntry( icon = Icons.Rounded.Public, text = getString(R.string.open_network), canCopy = false ) ) entry(ssid) } else -> { entry( InfoEntry( icon = Icons.Rounded.Security, text = qrType.encryptionType.toString(), canCopy = false ) ) entry(ssid) entry( InfoEntry( icon = Icons.Rounded.Password, text = qrType.password.ifBlank { getString(R.string.not_specified) }, canCopy = qrType.password.isNotBlank() ) ) } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrInfoBuilder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import android.content.Intent import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.graphics.vector.ImageVector import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.utils.getString internal interface QrInfoBuilderScope { fun title(title: String): QrInfoBuilderScope fun icon(icon: ImageVector): QrInfoBuilderScope fun intent(intent: Intent?): QrInfoBuilderScope fun entry(infoEntry: InfoEntry): QrInfoBuilderScope fun build(): QrInfo } internal operator fun QrInfo.Companion.invoke( builder: QrInfoBuilderScope.() -> Unit ): QrInfo = QrInfoBuilderScopeImpl().apply(builder).build() @Composable internal fun qrInfoBuilder( qrType: QrType, builder: QrInfoBuilderScope.() -> Unit ): QrInfo = remember(qrType) { derivedStateOf { QrInfo { title(getString(qrType.name)) icon(qrType.icon) if (qrType is QrType.Complex) intent(qrType.toIntent()) builder() } } }.value private class QrInfoBuilderScopeImpl : QrInfoBuilderScope { private var title: String? = null private var icon: ImageVector? = null private var intent: Intent? = null private var data: List = emptyList() override fun title(title: String) = apply { this.title = title } override fun icon(icon: ImageVector) = apply { this.icon = icon } override fun intent(intent: Intent?) = apply { this.intent = intent } override fun entry(infoEntry: InfoEntry) = apply { data += infoEntry } override fun build(): QrInfo = QrInfo( title = requireNotNull(title), icon = requireNotNull(icon), intent = intent, data = data ) } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.DarkMode import androidx.compose.material.icons.outlined.LightMode import androidx.compose.material.icons.outlined.Padding import androidx.compose.material.icons.outlined.PhotoSizeSelectLarge import androidx.compose.material.icons.outlined.RoundedCorner import androidx.compose.material.icons.rounded.Circle import androidx.compose.material.icons.rounded.RoundedCorner import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material.icons.rounded.TableRows import androidx.compose.material.icons.rounded.ViewColumn import androidx.compose.material.icons.sharp.Square import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.TopLeft import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams.BallShape import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams.ErrorCorrectionLevel import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams.FrameShape import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams.FrameShape.Corners.CornerSide import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams.MaskPattern import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams.PixelShape import com.t8rin.imagetoolbox.core.ui.widget.other.defaultQrColors import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlin.math.roundToInt @Composable internal fun QrParamsSelector( isQrType: Boolean, value: QrCodeParams, onValueChange: (QrCodeParams) -> Unit ) { Column( modifier = Modifier .container( shape = ShapeDefaults.large, resultPadding = 12.dp ), verticalArrangement = Arrangement.spacedBy(8.dp) ) { TitleItem( text = stringResource(R.string.code_customization), icon = Icons.Outlined.Code, modifier = Modifier.padding(bottom = 8.dp) ) Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { val (bg, fg) = defaultQrColors() ColorRowSelector( value = value.foregroundColor ?: fg, onValueChange = { onValueChange( value.copy( foregroundColor = it ) ) }, modifier = Modifier .fillMaxWidth() .container( color = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.top ), title = stringResource(R.string.dark_color), icon = Icons.Outlined.DarkMode ) ColorRowSelector( value = value.backgroundColor ?: bg, onValueChange = { onValueChange( value.copy( backgroundColor = it ) ) }, modifier = Modifier .fillMaxWidth() .container( color = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.bottom ), title = stringResource(R.string.light_color), icon = Icons.Outlined.LightMode ) } AnimatedVisibility( visible = isQrType, modifier = Modifier.fillMaxWidth() ) { Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { val logoParamsSize = 4 Row( modifier = Modifier.height(intrinsicSize = IntrinsicSize.Max) ) { ImageSelector( value = value.logo, title = stringResource(R.string.logo), subtitle = stringResource(R.string.qr_logo_image), onValueChange = { onValueChange( value.copy( logo = it ) ) }, modifier = Modifier .fillMaxHeight() .weight(1f), shape = if (value.logo == null) { ShapeDefaults.default } else { ShapeDefaults.topStart }, color = MaterialTheme.colorScheme.surface ) BoxAnimatedVisibility(visible = value.logo != null) { val interactionSource = remember { MutableInteractionSource() } Box( modifier = Modifier .fillMaxHeight() .padding(start = 4.dp) .container( color = MaterialTheme.colorScheme.errorContainer, resultPadding = 0.dp, shape = shapeByInteraction( shape = ShapeDefaults.topEnd, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) ) .hapticsClickable( interactionSource = interactionSource, indication = LocalIndication.current ) { onValueChange( value.copy( logo = null ) ) } .padding(horizontal = 8.dp), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onErrorContainer ) } } } BoxAnimatedVisibility( visible = value.logo != null, modifier = Modifier.fillMaxWidth() ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { var logoPadding by remember { mutableFloatStateOf(value.logoPadding) } EnhancedSliderItem( value = logoPadding, title = stringResource(R.string.logo_padding), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { logoPadding = it.roundToTwoDigits() onValueChange( value.copy( logoPadding = logoPadding ) ) }, containerColor = MaterialTheme.colorScheme.surface, icon = Icons.Outlined.Padding, shape = ShapeDefaults.byIndex( index = 1, size = logoParamsSize ) ) var logoSize by remember { mutableFloatStateOf(value.logoSize) } EnhancedSliderItem( value = logoSize, title = stringResource(R.string.logo_size), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { logoSize = it.roundToTwoDigits() onValueChange( value.copy( logoSize = logoSize ) ) }, icon = Icons.Outlined.PhotoSizeSelectLarge, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.byIndex( index = 2, size = logoParamsSize ) ) var logoCorners by remember { mutableFloatStateOf(value.logoCorners) } EnhancedSliderItem( value = logoCorners, title = stringResource(R.string.logo_corners), valueRange = 0f..1f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { logoCorners = it.roundToTwoDigits() onValueChange( value.copy( logoCorners = logoCorners ) ) }, icon = Icons.Outlined.RoundedCorner, containerColor = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.byIndex( index = 3, size = logoParamsSize ) ) } } } EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ), entries = PixelShape.entries, value = value.pixelShape, itemContent = { it.Content() }, onValueChange = { onValueChange( value.copy( pixelShape = it ) ) }, title = stringResource(R.string.pixel_shape), inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, activeButtonColor = MaterialTheme.colorScheme.primary ) val frameShape = value.frameShape val frameShapes by remember(frameShape) { derivedStateOf { FrameShape.entries.map { if (it is FrameShape.Corners && frameShape is FrameShape.Corners) { it.copy( sides = frameShape.sides ) } else it } } } EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ), entries = frameShapes, value = value.frameShape, itemContent = { it.Content() }, onValueChange = { onValueChange( value.copy( frameShape = if (it is FrameShape.Corners && it.percent <= 0f) { it.copy(sides = CornerSide.entries) } else it ) ) }, title = stringResource(R.string.frame_shape), inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, activeButtonColor = MaterialTheme.colorScheme.primary ) AnimatedVisibility( visible = frameShape is FrameShape.Corners && frameShape.percent > 0f, modifier = Modifier.fillMaxWidth() ) { val shape = frameShape.safeCast() EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ), entries = CornerSide.entries, values = shape?.sides ?: emptyList(), itemContent = { it.Content() }, onValueChange = { shape?.apply { val newCorners = shape.sides.toggle(it) if (newCorners.isNotEmpty()) { onValueChange( value.copy( frameShape = shape.copy( sides = newCorners ) ) ) } } }, title = stringResource(R.string.corners), inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, activeButtonColor = MaterialTheme.colorScheme.tertiaryContainer ) } EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ), entries = BallShape.entries, value = value.ballShape, itemContent = { it.Content() }, onValueChange = { onValueChange( value.copy( ballShape = it ) ) }, title = stringResource(R.string.ball_shape), inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, activeButtonColor = MaterialTheme.colorScheme.primary ) EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ), entries = ErrorCorrectionLevel.entries, value = value.errorCorrectionLevel, itemContent = { Text( text = when (it) { ErrorCorrectionLevel.Auto -> stringResource(R.string.auto) else -> it.name } ) }, onValueChange = { onValueChange( value.copy( errorCorrectionLevel = it ) ) }, title = stringResource(R.string.error_correction_level), inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, activeButtonColor = MaterialTheme.colorScheme.secondaryContainer ) EnhancedButtonGroup( modifier = Modifier .fillMaxWidth() .container( shape = ShapeDefaults.default, color = MaterialTheme.colorScheme.surface ), entries = MaskPattern.entries, value = value.maskPattern, itemContent = { Text( text = when (it) { MaskPattern.Auto -> stringResource(R.string.auto) else -> it.name.removePrefix("P_") } ) }, onValueChange = { onValueChange( value.copy( maskPattern = it ) ) }, title = stringResource(R.string.mask_pattern), inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, activeButtonColor = MaterialTheme.colorScheme.secondaryContainer ) } } } } @Composable private fun CornerSide.Content() { Icon( imageVector = Icons.Outlined.TopLeft, contentDescription = null, modifier = Modifier .size(24.dp) .rotate(90f * ordinal) ) } @Composable private fun PixelShape.Content() { when (this) { is PixelShape.Predefined -> { Icon( imageVector = when (this) { PixelShape.Square -> Icons.Sharp.Square PixelShape.RoundSquare -> Icons.Rounded.RoundedCorner PixelShape.Circle -> Icons.Rounded.Circle PixelShape.Vertical -> Icons.Rounded.ViewColumn PixelShape.Horizontal -> Icons.Rounded.TableRows }, contentDescription = null, modifier = Modifier.size(24.dp) ) } is PixelShape.Random -> { Icon( imageVector = Icons.Rounded.Shuffle, contentDescription = null, modifier = Modifier.size(24.dp) ) } is PixelShape.Shaped -> { Spacer( modifier = Modifier .size(20.dp) .background( color = LocalContentColor.current, shape = shape, ) ) } } } @Composable private fun FrameShape.Content() { when (this) { is FrameShape.Corners -> { val percent = (percent * 100).roundToInt() Spacer( modifier = Modifier .size(20.dp) .border( width = 2.dp, color = LocalContentColor.current, shape = if (isCut) { CutCornerShape( topStartPercent = if (topLeft) percent else 0, topEndPercent = if (topRight) percent else 0, bottomStartPercent = if (bottomLeft) percent else 0, bottomEndPercent = if (bottomRight) percent else 0 ) } else { RoundedCornerShape( topStartPercent = if (topLeft) percent else 0, topEndPercent = if (topRight) percent else 0, bottomStartPercent = if (bottomLeft) percent else 0, bottomEndPercent = if (bottomRight) percent else 0 ) } ) ) } } } @Composable private fun BallShape.Content() { when (this) { is BallShape.Predefined -> { Icon( imageVector = when (this) { BallShape.Square -> Icons.Sharp.Square BallShape.Circle -> Icons.Rounded.Circle }, contentDescription = null, modifier = Modifier.size(24.dp) ) } is BallShape.Shaped -> { Spacer( modifier = Modifier .size(20.dp) .background( color = LocalContentColor.current, shape = shape, ) ) } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrPreviewParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import android.net.Uri import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.ui.widget.other.BarcodeType import com.t8rin.imagetoolbox.core.ui.widget.other.QrCodeParams data class QrPreviewParams( val imageUri: Uri?, val description: String, val content: QrType, val cornersSize: Int, val descriptionFont: UiFontFamily, val heightRatio: Float, val type: BarcodeType, val qrParams: QrCodeParams, ) { companion object { val Default by lazy { QrPreviewParams( imageUri = null, description = "", content = QrType.Empty, cornersSize = 4, descriptionFont = UiFontFamily.System, heightRatio = 2f, type = BarcodeType.QR_CODE, qrParams = QrCodeParams() ) } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeEditSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.QrCode import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.retain.retain import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor.QrEditField @Composable internal fun QrTypeEditSheet( qrType: QrType.Complex?, onSave: (QrType.Complex) -> Unit, onDismiss: () -> Unit, visible: Boolean ) { var edited by retain(visible, qrType) { mutableStateOf(qrType ?: QrType.Wifi()) } EnhancedModalBottomSheet( visible = visible, onDismiss = { onDismiss() }, title = { TitleItem( text = stringResource( if (qrType == null) R.string.create_barcode else R.string.edit_barcode ), icon = Icons.Rounded.QrCode ) }, confirmButton = { EnhancedButton( onClick = { onSave(edited.updateRaw()) onDismiss() }, containerColor = MaterialTheme.colorScheme.primary, enabled = !edited.isEmpty() ) { Text(stringResource(R.string.save)) } } ) { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(16.dp) .clearFocusOnTap(), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { DataSelector( value = edited, onValueChange = { edited = if (qrType != null && it::class.isInstance(qrType)) qrType else it }, itemEqualityDelegate = { t, o -> t::class.isInstance(o) }, entries = QrType.complexEntries, title = null, titleIcon = null, itemContentText = { stringResource(it.name) }, itemContentIcon = { item, _ -> item.icon }, canExpand = false ) QrEditField( value = edited, onValueChange = { edited = it }, modifier = Modifier .fillMaxWidth() .padding(top = 4.dp) ) } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeInfoItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import androidx.activity.compose.LocalActivity import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.OpenInNew import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun QrTypeInfoItem( qrType: QrType, modifier: Modifier = Modifier ) { AnimatedContent( targetState = qrType, transitionSpec = { if (initialState::class.isInstance(targetState)) { fadeIn(tween(150)) togetherWith fadeOut(tween(150)) } else if (targetState !is QrType.Complex) { slideInVertically { it } + fadeIn() togetherWith slideOutVertically { -it } + fadeOut() } else { slideInVertically { -it } + fadeIn() togetherWith slideOutVertically { it } + fadeOut() } }, modifier = Modifier.fillMaxWidth() ) { type -> when (type) { is QrType.Complex -> { QrInfoItem( qrInfo = rememberQrInfo(type), modifier = modifier ) } else -> Spacer(Modifier.fillMaxWidth()) } } } @Composable private fun QrInfoItem( qrInfo: QrInfo, modifier: Modifier, ) { if (qrInfo.data.isEmpty()) return val activity = LocalActivity.current Column( modifier = Modifier .then(modifier) .container( shape = ShapeDefaults.large, resultPadding = 0.dp ) .padding(8.dp) ) { TitleItem( text = qrInfo.title, icon = qrInfo.icon, modifier = Modifier.padding(8.dp), endContent = { qrInfo.intent?.let { EnhancedIconButton( modifier = Modifier.size(32.dp), onClick = { runCatching { activity?.startActivity(qrInfo.intent) }.onFailure(AppToastHost::showFailureToast) } ) { Icon( imageVector = Icons.AutoMirrored.Rounded.OpenInNew, contentDescription = "open", modifier = Modifier.size(20.dp) ) } } } ) Spacer(modifier = Modifier.padding(4.dp)) Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { qrInfo.data.forEachIndexed { index, (icon, text, canCopy) -> val interactionSource = remember(index) { MutableInteractionSource() } val shape = shapeByInteraction( shape = ShapeDefaults.byIndex( index = index, size = qrInfo.data.size ), pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) Row( modifier = Modifier .fillMaxWidth() .container( shape = shape, color = MaterialTheme.colorScheme.surface, resultPadding = 0.dp ) .hapticsClickable( enabled = canCopy, onClick = { Clipboard.copy(text) }, interactionSource = interactionSource, indication = LocalIndication.current ) .padding( vertical = 10.dp, horizontal = 12.dp ), verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = icon, contentDescription = null, tint = MaterialTheme.colorScheme.onSurface ) Spacer(Modifier.width(8.dp)) Text( text = text, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f) ) if (canCopy) { Spacer(Modifier.width(16.dp)) Icon( imageVector = Icons.Rounded.ContentCopy, contentDescription = null, tint = MaterialTheme.colorScheme.onSurface.copy(0.5f), modifier = Modifier.size(18.dp) ) } } } } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/QrTypeUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import android.content.ContentValues import android.content.Intent import android.provider.CalendarContract import android.provider.ContactsContract import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CalendarMonth import androidx.compose.material.icons.rounded.Contacts import androidx.compose.material.icons.rounded.Email import androidx.compose.material.icons.rounded.Link import androidx.compose.material.icons.rounded.LocationOn import androidx.compose.material.icons.rounded.Phone import androidx.compose.material.icons.rounded.Sms import androidx.compose.material.icons.rounded.TextFields import androidx.compose.material.icons.rounded.Wifi import androidx.compose.ui.graphics.vector.ImageVector import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.model.copy import com.t8rin.imagetoolbox.core.domain.model.ifNotEmpty import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Contact import ezvcard.Ezvcard import ezvcard.VCard import ezvcard.parameter.EmailType import ezvcard.parameter.TelephoneType import ezvcard.property.FormattedName import ezvcard.property.Organization import ezvcard.property.RawProperty import ezvcard.property.StructuredName import ezvcard.property.Telephone import ezvcard.property.Title import io.github.g00fy2.quickie.extensions.DataType import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import java.util.TimeZone internal val QrType.name: Int get() = when (this) { is QrType.Calendar -> R.string.qr_type_calendar_event is QrType.Contact -> R.string.qr_type_contact_info is QrType.Email -> R.string.qr_type_email is QrType.Geo -> R.string.qr_type_geo_point is QrType.Phone -> R.string.qr_type_phone is QrType.Plain -> R.string.qr_type_plain is QrType.Sms -> R.string.qr_type_sms is QrType.Url -> R.string.qr_type_url is QrType.Wifi -> R.string.qr_type_wifi } internal val QrType.icon: ImageVector get() = when (this) { is QrType.Calendar -> Icons.Rounded.CalendarMonth is QrType.Contact -> Icons.Rounded.Contacts is QrType.Email -> Icons.Rounded.Email is QrType.Geo -> Icons.Rounded.LocationOn is QrType.Phone -> Icons.Rounded.Phone is QrType.Sms -> Icons.Rounded.Sms is QrType.Wifi -> Icons.Rounded.Wifi is QrType.Plain -> Icons.Rounded.TextFields is QrType.Url -> Icons.Rounded.Link } internal fun QrType.toIntent(): Intent? = ifNotEmpty { when (this) { is QrType.Plain -> Intent(Intent.ACTION_SEND).setType("text/plain") .putExtra(Intent.EXTRA_TEXT, raw) is QrType.Url -> Intent(Intent.ACTION_VIEW, url.toUri()) is QrType.Email -> Intent(Intent.ACTION_SENDTO, raw.toUri()) is QrType.Phone -> Intent(Intent.ACTION_DIAL, raw.toUri()) is QrType.Sms -> { val cleanNumber = phoneNumber.removePrefix("smsto:").removePrefix("sms:") return Intent(Intent.ACTION_SENDTO, "smsto:$cleanNumber".toUri()).apply { if (message.isNotBlank()) { putExtra("sms_body", message) } } } is QrType.Geo -> Intent(Intent.ACTION_VIEW, raw.toUri()) is QrType.Wifi -> null is QrType.Contact -> { Intent(Intent.ACTION_INSERT).apply { type = ContactsContract.Contacts.CONTENT_TYPE if (organization.isNotBlank()) { putExtra(ContactsContract.Intents.Insert.COMPANY, organization) } if (title.isNotBlank()) { putExtra(ContactsContract.Intents.Insert.JOB_TITLE, title) } if (name.pronunciation.isNotBlank()) { putExtra(ContactsContract.Intents.Insert.PHONETIC_NAME, name.pronunciation) } val data = arrayListOf() val nameCv = ContentValues().apply { put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE ) if (name.first.isNotBlank()) put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name.first) if (name.middle.isNotBlank()) put( ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, name.middle ) if (name.last.isNotBlank()) put(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, name.last) if (name.prefix.isNotBlank()) put(ContactsContract.CommonDataKinds.StructuredName.PREFIX, name.prefix) if (name.suffix.isNotBlank()) put(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, name.suffix) if (name.formattedName.isNotBlank()) put( ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name.formattedName ) } data.add(nameCv) phones.forEach { phone -> val cv = ContentValues() cv.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ) cv.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone.number) cv.put( ContactsContract.CommonDataKinds.Phone.TYPE, when (phone.type) { DataType.TYPE_HOME -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME DataType.TYPE_WORK -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK DataType.TYPE_FAX -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK DataType.TYPE_MOBILE -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE else -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER } ) data.add(cv) } emails.forEach { email -> val cv = ContentValues() cv.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ) cv.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email.address) cv.put( ContactsContract.CommonDataKinds.Email.TYPE, when (email.type) { DataType.TYPE_HOME -> ContactsContract.CommonDataKinds.Email.TYPE_HOME DataType.TYPE_WORK -> ContactsContract.CommonDataKinds.Email.TYPE_WORK else -> ContactsContract.CommonDataKinds.Email.TYPE_OTHER } ) data.add(cv) } addresses.forEach { addr -> val cv = ContentValues().apply { put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE ) put( ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, addr.addressLines.joinToString(" ") ) put( ContactsContract.CommonDataKinds.StructuredPostal.TYPE, when (addr.type) { DataType.TYPE_HOME -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME DataType.TYPE_WORK -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK else -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER } ) } data.add(cv) } urls.forEach { url -> val cv = ContentValues().apply { put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE ) put(ContactsContract.CommonDataKinds.Website.URL, url) put( ContactsContract.CommonDataKinds.Website.TYPE, ContactsContract.CommonDataKinds.Website.TYPE_OTHER ) } data.add(cv) } putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, data) } } is QrType.Calendar -> { Intent(Intent.ACTION_INSERT).apply { data = CalendarContract.Events.CONTENT_URI putExtra(CalendarContract.Events.TITLE, summary) putExtra(CalendarContract.Events.DESCRIPTION, description) putExtra(CalendarContract.Events.EVENT_LOCATION, location) putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start?.time) putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end?.time) } } }?.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } internal fun QrType.Complex.createRaw(): String = runCatching { when (this) { is QrType.Wifi -> buildString { append("WIFI:") append("S:").append(ssid).append(";") if (encryptionType != QrType.Wifi.EncryptionType.OPEN) { append("T:").append(encryptionType.name).append(";") append("P:").append(password).append(";") } append(";") } is QrType.Sms -> buildString { append("SMSTO:").append(phoneNumber).append(":").append(message) } is QrType.Geo -> buildString { if (latitude != null && longitude != null) { append("geo:").append(latitude).append(",").append(longitude) } } is QrType.Email -> buildString { append("MATMSG:TO:").append(address).append(";") append("SUB:").append(subject).append(";") append("BODY:").append(body).append(";;") } is QrType.Phone -> "tel:$number" is QrType.Contact -> { val vcard = VCard() val structuredName = StructuredName().apply { given = name.first family = name.last additionalNames.add(name.middle) prefixes.add(name.prefix) suffixes.add(name.suffix) } if (name.pronunciation.isNotBlank()) { vcard.addProperty(RawProperty("X-PHONETIC-FIRST-NAME", name.pronunciation)) vcard.addProperty(RawProperty("X-PHONETIC-LAST-NAME", name.pronunciation)) vcard.addProperty(RawProperty("X-PHONETIC-MIDDLE-NAME", name.pronunciation)) } vcard.formattedName = FormattedName(name.formattedName) vcard.structuredName = structuredName vcard.organization = Organization().apply { values.add(organization) } vcard.titles.add(Title(title)) phones.forEach { vcard.telephoneNumbers.add( Telephone(it.number).apply { types.add(mapPhoneType(it.type)) } ) } emails.forEach { vcard.emails.add( ezvcard.property.Email(it.address).apply { types.add(mapEmailType(it.type)) } ) } addresses.forEach { vcard.addresses.add( ezvcard.property.Address().apply { streetAddress = it.addressLines.joinToString(", ") types.add(mapAddressType(it.type)) } ) } urls.forEach { vcard.urls.add(ezvcard.property.Url(it)) } Ezvcard.write(vcard).go() } is QrType.Calendar -> buildString { val dateFormat = SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") } append("BEGIN:VEVENT\n") if (summary.isNotBlank()) append("SUMMARY:").append(summary).append("\n") if (description.isNotBlank()) append("DESCRIPTION:").append(description) .append("\n") if (location.isNotBlank()) append("LOCATION:").append(location).append("\n") if (organizer.isNotBlank()) append("ORGANIZER:").append(organizer).append("\n") if (status.isNotBlank()) append("STATUS:").append(status).append("\n") append("DTSTART:").append(dateFormat.format(start ?: Date())).append("\n") append("DTEND:").append(dateFormat.format(end ?: Date())).append("\n") append("END:VEVENT") } } }.getOrDefault(raw) internal fun QrType.Complex.updateRaw(): QrType.Complex = copy(createRaw().trim()) internal fun QrType.Contact.updateFormattedName(): QrType.Contact { val formatted = buildString { if (name.prefix.isNotBlank()) append(name.prefix.trim()).append(' ') if (name.first.isNotBlank()) append(name.first.trim()).append(' ') if (name.middle.isNotBlank()) append(name.middle.trim()).append(' ') if (name.last.isNotBlank()) append(name.last.trim()).append(' ') if (name.suffix.isNotBlank()) append(", ${name.suffix.trim()}") }.trim() return copy( name = name.copy( formattedName = formatted ) ) } internal fun Contact.toQrType(raw: String = ""): QrType.Contact { return QrType.Contact( raw = raw, addresses = addresses.map { QrType.Contact.Address( addressLines = it.addressLines, type = it.type ) }, emails = emails.map { QrType.Email( raw = "", address = it.address, body = it.body, subject = it.subject, type = it.type ) }, name = QrType.Contact.PersonName( first = name.first, formattedName = name.formattedName, last = name.last, middle = name.middle, prefix = name.prefix, pronunciation = name.pronunciation, suffix = name.suffix ), organization = organization, phones = phones.map { QrType.Phone( raw = "", number = it.number, type = it.type ) }, title = title, urls = urls ) } private fun mapPhoneType(type: Int): TelephoneType = when (type) { DataType.TYPE_WORK -> TelephoneType.WORK DataType.TYPE_HOME -> TelephoneType.HOME DataType.TYPE_FAX -> TelephoneType.FAX DataType.TYPE_MOBILE -> TelephoneType.CELL else -> TelephoneType.VOICE } private fun mapEmailType(type: Int): EmailType = when (type) { DataType.TYPE_WORK -> EmailType.WORK DataType.TYPE_HOME -> EmailType.HOME else -> EmailType.INTERNET } private fun mapAddressType(type: Int): ezvcard.parameter.AddressType = when (type) { DataType.TYPE_WORK -> ezvcard.parameter.AddressType.WORK DataType.TYPE_HOME -> ezvcard.parameter.AddressType.HOME else -> ezvcard.parameter.AddressType.HOME } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/ScanQrCodeControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.RoundedCorner import androidx.compose.material.icons.rounded.QrCode2 import androidx.compose.material.icons.rounded.WarningAmber import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.model.copy import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FontSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.shapeByInteraction import com.t8rin.imagetoolbox.core.ui.widget.other.BarcodeType import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.other.LinkPreviewList import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.screenLogic.ScanQrCodeComponent import kotlin.math.roundToInt @Composable internal fun ScanQrCodeControls(component: ScanQrCodeComponent) { val params by rememberUpdatedState(component.params) LinkPreviewList( text = params.content.raw, externalLinks = remember(params.content) { (params.content as? QrType.Url)?.let { listOf(it.url) } }, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp) ) QrTypeInfoItem( qrType = params.content, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp) ) val noContent = params.content.raw.isEmpty() val isNotScannable = !noContent && component.mayBeNotScannable AnimatedVisibility( visible = isNotScannable, modifier = Modifier.fillMaxWidth() ) { InfoContainer( text = stringResource(R.string.code_may_be_not_scannable), modifier = Modifier.padding(8.dp), containerColor = MaterialTheme.colorScheme.errorContainer.copy(0.4f), contentColor = MaterialTheme.colorScheme.onErrorContainer.copy(0.7f), icon = Icons.Rounded.WarningAmber ) } Column( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 0.dp ) ) { RoundedTextField( modifier = Modifier .padding( top = 8.dp, start = 8.dp, end = 8.dp, bottom = if (noContent) 4.dp else 6.dp ), shape = animateShape( if (noContent) ShapeDefaults.smallTop else ShapeDefaults.small ), value = params.content.raw, onValueChange = { component.updateParams( params.copy( content = params.content.copy(it) ) ) }, maxSymbols = 2500, singleLine = false, supportingText = if (!noContent) { { AnimatedContent( targetState = params.content, contentKey = { it::class.simpleName }, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { content -> Text( text = stringResource(content.name), color = MaterialTheme.colorScheme.onMixedContainer, modifier = Modifier .background( color = MaterialTheme.colorScheme.mixedContainer, shape = ShapeDefaults.small ) .padding(horizontal = 5.dp, vertical = 1.dp) ) } } } else null, label = { Text(stringResource(id = R.string.code_content)) }, keyboardOptions = KeyboardOptions() ) var showEditField by rememberSaveable { mutableStateOf(false) } EnhancedButton( onClick = { showEditField = true }, shape = if (noContent) ShapeDefaults.smallBottom else ShapeDefaults.small, modifier = Modifier .fillMaxWidth() .padding( start = 8.dp, end = 8.dp, bottom = 8.dp ), containerColor = MaterialTheme.colorScheme.surfaceContainerHighest ) { Row( verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Rounded.MiniEdit, contentDescription = null ) Spacer(Modifier.width(4.dp)) Text( text = stringResource( if (params.content is QrType.Complex) R.string.edit_barcode else R.string.create_barcode ) ) } } QrTypeEditSheet( qrType = params.content.safeCast(), onSave = { component.updateParams(params.copy(content = it)) }, onDismiss = { showEditField = false }, visible = showEditField ) } Spacer(modifier = Modifier.height(8.dp)) InfoContainer( text = stringResource(R.string.scan_qr_code_to_replace_content), modifier = Modifier.padding(8.dp) ) Spacer(modifier = Modifier.height(8.dp)) AnimatedVisibility(visible = params.content.raw.isNotEmpty()) { Column { DataSelector( value = params.type, onValueChange = { component.updateParams( params.copy( type = it ) ) }, spanCount = 2, entries = BarcodeType.entries, title = stringResource(R.string.barcode_type), titleIcon = Icons.Rounded.QrCode2, itemContentText = { remember { it.name.replace("_", " ") } } ) Spacer(modifier = Modifier.height(8.dp)) BoxAnimatedVisibility( visible = !params.type.isSquare || params.type == BarcodeType.DATA_MATRIX, modifier = Modifier.fillMaxWidth() ) { EnhancedSliderItem( value = params.heightRatio, title = stringResource(R.string.height_ratio), valueRange = 1f..4f, onValueChange = {}, onValueChangeFinished = { component.updateParams( params.copy( heightRatio = it ) ) }, internalStateTransformation = { it.roundToTwoDigits() }, modifier = Modifier.padding(bottom = 8.dp) ) } Spacer(modifier = Modifier.height(8.dp)) QrParamsSelector( isQrType = params.type == BarcodeType.QR_CODE, value = params.qrParams, onValueChange = { component.updateParams( params.copy( qrParams = it ) ) } ) Spacer(modifier = Modifier.height(16.dp)) Row( modifier = Modifier.height(intrinsicSize = IntrinsicSize.Max) ) { ImageSelector( value = params.imageUri, subtitle = stringResource(id = R.string.qr_code_top_image), onValueChange = { component.updateParams( params.copy( imageUri = it ) ) }, modifier = Modifier .fillMaxHeight() .weight(1f), shape = ShapeDefaults.extraLarge ) BoxAnimatedVisibility(visible = params.imageUri != null) { val interactionSource = remember { MutableInteractionSource() } Box( modifier = Modifier .fillMaxHeight() .padding(start = 8.dp) .container( color = MaterialTheme.colorScheme.errorContainer, resultPadding = 0.dp, shape = shapeByInteraction( shape = ShapeDefaults.default, pressedShape = ShapeDefaults.pressed, interactionSource = interactionSource ) ) .hapticsClickable( interactionSource = interactionSource, indication = LocalIndication.current ) { component.updateParams( params.copy( imageUri = null ) ) } .padding(horizontal = 8.dp), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.onErrorContainer ) } } } Spacer(modifier = Modifier.height(8.dp)) RoundedTextField( modifier = Modifier.container( shape = animateShape( if (params.description.isNotEmpty()) ShapeDefaults.top else ShapeDefaults.default ), resultPadding = 8.dp ), value = params.description, onValueChange = { component.updateParams( params.copy( description = it ) ) }, singleLine = false, label = { Text(stringResource(id = R.string.qr_description)) } ) Spacer(modifier = Modifier.height(8.dp)) BoxAnimatedVisibility( visible = params.description.isNotEmpty(), modifier = Modifier.fillMaxWidth() ) { FontSelector( value = params.descriptionFont, onValueChange = { component.updateParams( params.copy( descriptionFont = it ) ) }, containerColor = Color.Unspecified, shape = ShapeDefaults.bottom, modifier = Modifier.padding(bottom = 8.dp) ) } EnhancedSliderItem( value = params.cornersSize, title = stringResource(R.string.corners), valueRange = 0f..36f, onValueChange = { component.updateParams( params.copy( cornersSize = it.toInt() ) ) }, internalStateTransformation = { it.roundToInt() }, icon = Icons.Outlined.RoundedCorner, steps = 22 ) Spacer(modifier = Modifier.height(8.dp)) } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrCalendarEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.DateRange import androidx.compose.material.icons.outlined.Description import androidx.compose.material.icons.outlined.Event import androidx.compose.material.icons.outlined.Flag import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Place import androidx.compose.material.icons.outlined.Start import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.rememberDateRangePickerState import androidx.compose.material3.rememberTimePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.TimerEdit import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedDateRangePickerDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTimePickerDialog import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import java.text.DateFormat import java.util.Calendar import java.util.Date @Composable internal fun QrCalendarEditField( value: QrType.Calendar, onValueChange: (QrType.Calendar) -> Unit ) { var isDateDialogVisible by rememberSaveable { mutableStateOf(false) } var showStartTimePicker by rememberSaveable { mutableStateOf(false) } var showEndTimePicker by rememberSaveable { mutableStateOf(false) } val startDate = remember(value.start) { value.start ?: Date() } val endDate = remember(value.end) { value.end ?: Calendar.getInstance() .apply { add(Calendar.DAY_OF_YEAR, 1) }.time } val startCalendar = remember(startDate) { Calendar.getInstance().apply { time = startDate } } val endCalendar = remember(endDate) { Calendar.getInstance().apply { time = endDate } } val dateState = rememberDateRangePickerState( initialSelectedStartDateMillis = startDate.time, initialSelectedEndDateMillis = endDate.time ) val startTimeState = rememberTimePickerState( initialHour = startCalendar.get(Calendar.HOUR_OF_DAY), initialMinute = startCalendar.get(Calendar.MINUTE) ) val endTimeState = rememberTimePickerState( initialHour = endCalendar.get(Calendar.HOUR_OF_DAY), initialMinute = endCalendar.get(Calendar.MINUTE) ) Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { RoundedTextField( value = value.summary, onValueChange = { onValueChange(value.copy(summary = it)) }, label = { Text(stringResource(R.string.summary)) }, startIcon = { Icon( imageVector = Icons.Outlined.Event, contentDescription = null ) } ) RoundedTextField( value = value.description, onValueChange = { onValueChange(value.copy(description = it)) }, label = { Text(stringResource(R.string.description)) }, startIcon = { Icon( imageVector = Icons.Outlined.Description, contentDescription = null ) }, singleLine = false ) RoundedTextField( value = value.location, onValueChange = { onValueChange(value.copy(location = it)) }, label = { Text(stringResource(R.string.location)) }, startIcon = { Icon( imageVector = Icons.Outlined.Place, contentDescription = null ) } ) RoundedTextField( value = value.organizer, onValueChange = { onValueChange(value.copy(organizer = it)) }, label = { Text(stringResource(R.string.organizer)) }, startIcon = { Icon( imageVector = Icons.Outlined.Person, contentDescription = null ) } ) val startText = remember(startDate) { runCatching { DateFormat.getDateTimeInstance().format(startDate).removeSuffix(":00") }.getOrDefault("") } val endText = remember(endDate) { runCatching { DateFormat.getDateTimeInstance().format(endDate).removeSuffix(":00") }.getOrDefault("") } Row( modifier = Modifier .fillMaxWidth() .height(IntrinsicSize.Max), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { Column( modifier = Modifier .fillMaxHeight() .weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp) ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.weight(1f) ) { RoundedTextField( modifier = Modifier.weight(1f), value = startText, onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.start_date)) }, startIcon = { Icon( imageVector = Icons.Outlined.Start, contentDescription = null ) }, shape = ShapeDefaults.smallStart ) EnhancedIconButton( onClick = { showStartTimePicker = true }, modifier = Modifier.fillMaxHeight(), containerColor = MaterialTheme.colorScheme.secondaryContainer, forceMinimumInteractiveComponentSize = false, shape = ShapeDefaults.smallEnd ) { Icon( imageVector = Icons.Outlined.TimerEdit, contentDescription = null ) } } Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.weight(1f) ) { RoundedTextField( modifier = Modifier.weight(1f), value = endText, onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.end_date)) }, startIcon = { Icon( imageVector = Icons.Outlined.Flag, contentDescription = null ) }, shape = ShapeDefaults.smallStart ) EnhancedIconButton( onClick = { showEndTimePicker = true }, modifier = Modifier.fillMaxHeight(), containerColor = MaterialTheme.colorScheme.secondaryContainer, forceMinimumInteractiveComponentSize = false, shape = ShapeDefaults.smallEnd ) { Icon( imageVector = Icons.Outlined.TimerEdit, contentDescription = null ) } } } EnhancedIconButton( onClick = { isDateDialogVisible = true }, modifier = Modifier.fillMaxHeight(), containerColor = MaterialTheme.colorScheme.secondaryContainer, forceMinimumInteractiveComponentSize = false, shape = ShapeDefaults.small ) { Icon( imageVector = Icons.Outlined.DateRange, contentDescription = null ) } } RoundedTextField( value = value.status, onValueChange = { onValueChange(value.copy(status = it)) }, label = { Text(stringResource(R.string.status)) }, startIcon = { Icon( imageVector = Icons.Outlined.Info, contentDescription = null ) } ) EnhancedDateRangePickerDialog( visible = isDateDialogVisible, onDismissRequest = { isDateDialogVisible = false }, state = dateState, onDatePicked = { start, end -> onValueChange( value.copy( start = startCalendar.apply { time = Date(start) set(Calendar.HOUR_OF_DAY, startTimeState.hour) set(Calendar.MINUTE, startTimeState.minute) }.time, end = endCalendar.apply { time = Date(end) set(Calendar.HOUR_OF_DAY, endTimeState.hour) set(Calendar.MINUTE, endTimeState.minute) }.time ) ) } ) EnhancedTimePickerDialog( visible = showStartTimePicker, onDismissRequest = { showStartTimePicker = false }, state = startTimeState, onTimePicked = { hour, minute -> onValueChange( value.copy( start = startCalendar.apply { set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, minute) }.time ) ) } ) EnhancedTimePickerDialog( visible = showEndTimePicker, onDismissRequest = { showEndTimePicker = false }, state = endTimeState, onTimePicked = { hour, minute -> onValueChange( value.copy( end = endCalendar.apply { set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, minute) }.time ) ) } ) LaunchedEffect(startCalendar, endCalendar) { startTimeState.hour = startCalendar.get(Calendar.HOUR_OF_DAY) startTimeState.minute = startCalendar.get(Calendar.MINUTE) endTimeState.hour = endCalendar.get(Calendar.HOUR_OF_DAY) endTimeState.minute = endCalendar.get(Calendar.MINUTE) } LaunchedEffect(Unit) { val start = dateState.selectedStartDateMillis val end = dateState.selectedEndDateMillis if (start != null && end != null) { onValueChange( value.copy( start = Date(start), end = Date(end) ) ) } } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrContactEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnusedReceiverParameter") package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.annotation.StringRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.AlternateEmail import androidx.compose.material.icons.outlined.Badge import androidx.compose.material.icons.outlined.Business import androidx.compose.material.icons.outlined.Call import androidx.compose.material.icons.outlined.Email import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Place import androidx.compose.material.icons.outlined.Public import androidx.compose.material.icons.outlined.RecordVoiceOver import androidx.compose.material.icons.outlined.RemoveCircleOutline import androidx.compose.material.icons.outlined.SupervisedUserCircle import androidx.compose.material.icons.rounded.Numbers import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Prefix import com.t8rin.imagetoolbox.core.resources.icons.Suffix import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ContactPickerButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.toQrType import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.updateFormattedName @Composable internal fun QrContactEditField( value: QrType.Contact, onValueChange: (QrType.Contact) -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { ContactPickerButton( onPicked = { contact -> onValueChange(contact.toQrType()) } ) ContactInfoEnterBlock( value = value, onValueChange = onValueChange ) PhonesEnterBlock( value = value, onValueChange = onValueChange ) EmailsEnterBlock( value = value, onValueChange = onValueChange ) AddressesEnterBlock( value = value, onValueChange = onValueChange ) UrlsEnterBlock( value = value, onValueChange = onValueChange ) } } @Composable private fun ColumnScope.ContactInfoEnterBlock( value: QrType.Contact, onValueChange: (QrType.Contact) -> Unit ) { TitleItem( text = stringResource(R.string.contact_info), icon = Icons.Outlined.Person, modifier = Modifier.padding(vertical = 8.dp) ) NameEditField( value = value, onValueChange = { onValueChange(it.updateFormattedName()) } ) RoundedTextField( value = value.organization, onValueChange = { onValueChange(value.copy(organization = it)) }, label = { Text(stringResource(R.string.organization)) }, startIcon = { Icon(Icons.Outlined.Business, null) } ) RoundedTextField( value = value.title, onValueChange = { onValueChange(value.copy(title = it)) }, label = { Text(stringResource(R.string.title)) }, startIcon = { Icon(Icons.Outlined.Badge, null) } ) } @Composable private fun ColumnScope.NameEditField( value: QrType.Contact, onValueChange: (QrType.Contact) -> Unit ) { RoundedTextField( value = value.name.first, onValueChange = { onValueChange(value.copy(name = value.name.copy(first = it))) }, label = { Text(stringResource(R.string.first_name)) }, startIcon = { Icon(Icons.Outlined.SupervisedUserCircle, null) } ) RoundedTextField( value = value.name.middle, onValueChange = { onValueChange(value.copy(name = value.name.copy(middle = it))) }, label = { Text(stringResource(R.string.middle_name)) }, startIcon = { Icon(Icons.Outlined.SupervisedUserCircle, null) } ) RoundedTextField( value = value.name.last, onValueChange = { onValueChange(value.copy(name = value.name.copy(last = it))) }, label = { Text(stringResource(R.string.last_name)) }, startIcon = { Icon(Icons.Outlined.SupervisedUserCircle, null) } ) RoundedTextField( value = value.name.prefix, onValueChange = { onValueChange(value.copy(name = value.name.copy(prefix = it))) }, label = { Text(stringResource(R.string.prefix)) }, startIcon = { Icon(Icons.Filled.Prefix, null) } ) RoundedTextField( value = value.name.suffix, onValueChange = { onValueChange(value.copy(name = value.name.copy(suffix = it))) }, label = { Text(stringResource(R.string.suffix)) }, startIcon = { Icon(Icons.Filled.Suffix, null) } ) RoundedTextField( value = value.name.pronunciation, onValueChange = { onValueChange(value.copy(name = value.name.copy(pronunciation = it))) }, label = { Text(stringResource(R.string.pronunciation)) }, startIcon = { Icon(Icons.Outlined.RecordVoiceOver, null) } ) } @Composable private fun ColumnScope.UrlsEnterBlock( value: QrType.Contact, onValueChange: (QrType.Contact) -> Unit ) { TitleItem( text = stringResource(R.string.urls), icon = Icons.Outlined.Public, modifier = Modifier.padding(vertical = 8.dp) ) value.urls.forEachIndexed { index, url -> RemovableTextField( value = url, onValueChange = { val updated = value.urls.toMutableList() updated[index] = it onValueChange(value.copy(urls = updated)) }, startIcon = Icons.Outlined.Link, label = "${stringResource(R.string.website)} ${index + 1}", onRemove = { val updated = value.urls.toMutableList() updated.removeAt(index) onValueChange(value.copy(urls = updated)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri) ) } AddButton( onClick = { onValueChange(value.copy(urls = value.urls + "")) }, title = R.string.add_website ) } @Composable private fun ColumnScope.AddressesEnterBlock( value: QrType.Contact, onValueChange: (QrType.Contact) -> Unit ) { TitleItem( text = stringResource(R.string.addresses), icon = Icons.Outlined.Home, modifier = Modifier.padding(vertical = 8.dp) ) value.addresses.forEachIndexed { index, address -> RemovableTextField( value = address.addressLines.joinToString(", "), onValueChange = { val updated = value.addresses.toMutableList() updated[index] = address.copy(addressLines = it.split(",").map(String::trim)) onValueChange(value.copy(addresses = updated)) }, startIcon = Icons.Outlined.Place, label = "${stringResource(R.string.address)} ${index + 1}", onRemove = { val updated = value.addresses.toMutableList() updated.removeAt(index) onValueChange(value.copy(addresses = updated)) }, keyboardOptions = KeyboardOptions() ) } AddButton( onClick = { onValueChange( value.copy( addresses = value.addresses + QrType.Contact.Address() ) ) }, title = R.string.add_address ) } @Composable private fun ColumnScope.EmailsEnterBlock( value: QrType.Contact, onValueChange: (QrType.Contact) -> Unit ) { TitleItem( text = stringResource(R.string.emails), icon = Icons.Outlined.Email, modifier = Modifier.padding(vertical = 8.dp) ) value.emails.forEachIndexed { index, email -> RemovableTextField( value = email.address, onValueChange = { val updated = value.emails.toMutableList() updated[index] = email.copy(address = it) onValueChange(value.copy(emails = updated)) }, startIcon = Icons.Outlined.AlternateEmail, label = "${stringResource(R.string.email)} ${index + 1}", onRemove = { val updated = value.emails.toMutableList() updated.removeAt(index) onValueChange(value.copy(emails = updated)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email) ) } AddButton( onClick = { onValueChange(value.copy(emails = value.emails + QrType.Email())) }, title = R.string.add_email ) } @Composable private fun ColumnScope.PhonesEnterBlock( value: QrType.Contact, onValueChange: (QrType.Contact) -> Unit ) { TitleItem( text = stringResource(R.string.phones), icon = Icons.Outlined.Call, modifier = Modifier.padding(vertical = 8.dp) ) value.phones.forEachIndexed { index, phone -> RemovableTextField( value = phone.number, onValueChange = { val updated = value.phones.toMutableList() updated[index] = phone.copy(number = it) onValueChange(value.copy(phones = updated)) }, startIcon = Icons.Rounded.Numbers, label = "${stringResource(R.string.phone)} ${index + 1}", onRemove = { val updated = value.phones.toMutableList() updated.removeAt(index) onValueChange(value.copy(phones = updated)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone) ) } AddButton( onClick = { onValueChange(value.copy(phones = value.phones + QrType.Phone())) }, title = R.string.add_phone ) } @Composable private fun RemovableTextField( value: String, onValueChange: (String) -> Unit, onRemove: () -> Unit, startIcon: ImageVector, label: String, keyboardOptions: KeyboardOptions ) { Row( verticalAlignment = Alignment.CenterVertically ) { RoundedTextField( value = value, onValueChange = onValueChange, label = { Text(text = label) }, startIcon = { Icon( imageVector = startIcon, contentDescription = null ) }, keyboardOptions = keyboardOptions, modifier = Modifier.weight(1f) ) EnhancedIconButton( onClick = onRemove ) { Icon( imageVector = Icons.Outlined.RemoveCircleOutline, contentDescription = null, tint = MaterialTheme.colorScheme.error ) } } } @Composable private fun AddButton( onClick: () -> Unit, @StringRes title: Int ) { EnhancedButton( onClick = onClick, modifier = Modifier.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Icon(Icons.Outlined.Add, null) Spacer(Modifier.width(4.dp)) Text(stringResource(title)) } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.compose.animation.AnimatedContent import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun QrEditField( value: QrType.Complex, onValueChange: (QrType.Complex) -> Unit, modifier: Modifier = Modifier ) { AnimatedContent( targetState = value, contentKey = { it::class.simpleName }, modifier = modifier .container( shape = ShapeDefaults.large, resultPadding = 12.dp ) ) { qrType -> when (qrType) { is QrType.Wifi -> QrWifiEditField( value = qrType, onValueChange = onValueChange ) is QrType.Phone -> QrPhoneEditField( value = qrType, onValueChange = onValueChange ) is QrType.Sms -> QrSmsEditField( value = qrType, onValueChange = onValueChange ) is QrType.Email -> QrEmailEditField( value = qrType, onValueChange = onValueChange ) is QrType.Geo -> QrGeoEditField( value = qrType, onValueChange = onValueChange ) is QrType.Calendar -> QrCalendarEditField( value = qrType, onValueChange = onValueChange ) is QrType.Contact -> QrContactEditField( value = qrType, onValueChange = onValueChange ) } } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrEmailEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ShortText import androidx.compose.material.icons.outlined.Topic import androidx.compose.material.icons.rounded.AlternateEmail import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField @Composable internal fun QrEmailEditField( value: QrType.Email, onValueChange: (QrType.Email) -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { RoundedTextField( value = value.address, onValueChange = { onValueChange(value.copy(address = it)) }, label = { Text(stringResource(R.string.address)) }, startIcon = { Icon( imageVector = Icons.Rounded.AlternateEmail, contentDescription = null ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Email ) ) RoundedTextField( value = value.subject, onValueChange = { onValueChange(value.copy(subject = it)) }, label = { Text(stringResource(R.string.subject)) }, startIcon = { Icon( imageVector = Icons.Outlined.Topic, contentDescription = null ) }, singleLine = false ) RoundedTextField( value = value.body, onValueChange = { onValueChange(value.copy(body = it)) }, label = { Text(stringResource(R.string.body)) }, startIcon = { Icon( imageVector = Icons.AutoMirrored.Rounded.ShortText, contentDescription = null ) }, singleLine = false ) } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrGeoEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.utils.trimTrailingZero import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Latitude import com.t8rin.imagetoolbox.core.resources.icons.Longitude import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.value.filterDecimal @Composable internal fun QrGeoEditField( value: QrType.Geo, onValueChange: (QrType.Geo) -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { var latitude by remember { mutableStateOf(value.latitude?.toString().orEmpty().trimTrailingZero()) } var longitude by remember { mutableStateOf(value.longitude?.toString().orEmpty().trimTrailingZero()) } RoundedTextField( value = latitude, onValueChange = { latitude = it.filterDecimal() latitude.toDoubleOrNull()?.coerceIn(LatitudeRange)?.let { new -> onValueChange(value.copy(latitude = new)) } }, label = { Text(stringResource(R.string.latitude)) }, startIcon = { Icon( imageVector = Icons.Outlined.Latitude, contentDescription = null ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Decimal ) ) RoundedTextField( value = longitude, onValueChange = { longitude = it.filterDecimal() longitude.toDoubleOrNull()?.coerceIn(LongitudeRange)?.let { new -> onValueChange(value.copy(longitude = new)) } }, label = { Text(stringResource(R.string.longitude)) }, startIcon = { Icon( imageVector = Icons.Outlined.Longitude, contentDescription = null ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Decimal ) ) } } private val LatitudeRange = -90.0..90.0 private val LongitudeRange = -180.0..180.0 ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrPhoneEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Numbers import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ContactPickerButton import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField @Composable internal fun QrPhoneEditField( value: QrType.Phone, onValueChange: (QrType.Phone) -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { RoundedTextField( value = value.number, onValueChange = { onValueChange(value.copy(number = it)) }, label = { Text(stringResource(R.string.phone)) }, startIcon = { Icon( imageVector = Icons.Rounded.Numbers, contentDescription = null ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Phone ) ) ContactPickerButton( onPicked = { onValueChange(value.copy(number = it.phones.firstOrNull()?.number.orEmpty())) } ) } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrSmsEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.StickyNote2 import androidx.compose.material.icons.rounded.Numbers import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.ContactPickerButton import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField @Composable internal fun QrSmsEditField( value: QrType.Sms, onValueChange: (QrType.Sms) -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { RoundedTextField( value = value.phoneNumber, onValueChange = { onValueChange(value.copy(phoneNumber = it)) }, label = { Text(stringResource(R.string.phone)) }, startIcon = { Icon( imageVector = Icons.Rounded.Numbers, contentDescription = null ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Phone ) ) RoundedTextField( value = value.message, onValueChange = { onValueChange(value.copy(message = it)) }, label = { Text(stringResource(R.string.message)) }, startIcon = { Icon( imageVector = Icons.AutoMirrored.Outlined.StickyNote2, contentDescription = null ) }, singleLine = false ) ContactPickerButton( onPicked = { onValueChange(value.copy(phoneNumber = it.phones.firstOrNull()?.number.orEmpty())) } ) } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/components/editor/QrWifiEditField.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.editor import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CheckCircleOutline import androidx.compose.material.icons.rounded.Password import androidx.compose.material.icons.rounded.Security import androidx.compose.material.icons.rounded.TextFields import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.model.QrType.Wifi.EncryptionType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Build import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun QrWifiEditField( value: QrType.Wifi, onValueChange: (QrType.Wifi) -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { TitleItem( text = stringResource(R.string.wifi_configuration), icon = Icons.Outlined.Build, modifier = Modifier.padding(bottom = 8.dp) ) RoundedTextField( value = value.ssid, onValueChange = { onValueChange(value.copy(ssid = it)) }, label = { Text(stringResource(R.string.ssid)) }, startIcon = { Icon( imageVector = Icons.Rounded.TextFields, contentDescription = null ) }, singleLine = false ) AnimatedVisibility( visible = value.encryptionType != EncryptionType.OPEN, modifier = Modifier.fillMaxWidth() ) { RoundedTextField( value = value.password, onValueChange = { onValueChange(value.copy(password = it)) }, label = { Text(stringResource(R.string.password)) }, startIcon = { Icon( imageVector = Icons.Rounded.Password, contentDescription = null ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password ) ) } DataSelector( value = value.encryptionType, onValueChange = { onValueChange(value.copy(encryptionType = it)) }, entries = EncryptionType.entries, title = stringResource(R.string.security), titleIcon = Icons.Rounded.Security, itemContentText = { it.name }, itemContentIcon = { _, selected -> if (selected) Icons.Rounded.CheckCircleOutline else null }, spanCount = 1, behaveAsContainer = false, titlePadding = PaddingValues(top = 8.dp, bottom = 16.dp), contentPadding = PaddingValues(), canExpand = false, selectedItemColor = MaterialTheme.colorScheme.secondaryContainer ) } } ================================================ FILE: feature/scan-qr-code/src/main/java/com/t8rin/imagetoolbox/feature/scan_qr_code/presentation/screenLogic/ScanQrCodeComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.QrType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.utils.onResult import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.filters.domain.FilterParamsInteractor import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiFont import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.scan_qr_code.domain.ImageBarcodeReader import com.t8rin.imagetoolbox.feature.scan_qr_code.presentation.components.QrPreviewParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach class ScanQrCodeComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted initialQrCodeContent: String?, @Assisted uriToAnalyze: Uri?, @Assisted val onGoBack: () -> Unit, private val fileController: FileController, private val shareProvider: ImageShareProvider, private val imageCompressor: ImageCompressor, private val filterParamsInteractor: FilterParamsInteractor, private val imageBarcodeReader: ImageBarcodeReader, settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _params: MutableState = mutableStateOf(QrPreviewParams.Default) val params by _params private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private var savingJob: Job? by smartJob { _isSaving.update { false } } private var settingsState: SettingsState = SettingsState.Default private val _mayBeNotScannable = mutableStateOf(false) val mayBeNotScannable by _mayBeNotScannable private val _isSaveEnabled = mutableStateOf(false) val isSaveEnabled by _isSaveEnabled init { settingsProvider.settingsState.onEach { state -> settingsState = state _params.update { it.copy( descriptionFont = settingsState.font.toUiFont() ) } }.launchIn(componentScope) initialQrCodeContent?.let { content -> updateParams( params.copy( content = QrType.Plain(content) ) ) } uriToAnalyze?.let(::readBarcodeFromImage) } fun saveBitmap( bitmap: Bitmap, oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height ), originalUri = "_", sequenceNumber = null, data = imageCompressor.compress( image = bitmap, imageFormat = ImageFormat.Png.Lossless, quality = Quality.Base(100) ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _isSaving.update { false } } } fun shareImage( bitmap: Bitmap ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true bitmap.let { image -> shareProvider.shareImage( imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = ImageFormat.Png.Lossless ), image = image, onComplete = { _isSaving.value = false AppToastHost.showConfetti() } ) } } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun cacheImage( bitmap: Bitmap, onComplete: (Uri) -> Unit ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true bitmap.let { image -> shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = ImageFormat.Png.Lossless ) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun processFilterTemplateFromQrContent( onSuccess: (filterName: String, filtersCount: Int) -> Unit ) { componentScope.launch { if (filterParamsInteractor.isValidTemplateFilter(params.content.raw)) { filterParamsInteractor.addTemplateFilterFromString( string = params.content.raw, onSuccess = onSuccess, onFailure = {} ) } } } fun getFormatForFilenameSelection(): ImageFormat = ImageFormat.Png.Lossless fun updateParams(params: QrPreviewParams) { _params.update { params } } fun readBarcodeFromImage( image: Any, onFailure: (Throwable) -> Unit = {} ) { componentScope.launch { syncReadBarcodeFromImage(image).onFailure(onFailure) } } suspend fun syncReadBarcodeFromImage( image: Any? ): Result { _isSaveEnabled.value = image != null if (image == null) return Result.failure(Throwable("Barcode not rendered")) return imageBarcodeReader .readBarcode(image) .onResult { isSuccess -> _mayBeNotScannable.value = !isSuccess } .onSuccess { updateParams( params.copy( content = it ) ) } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialQrCodeContent: String?, uriToAnalyze: Uri?, onGoBack: () -> Unit, ): ScanQrCodeComponent } } ================================================ FILE: feature/settings/.gitignore ================================================ /build ================================================ FILE: feature/settings/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.settings" ================================================ FILE: feature/settings/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/AndroidSettingsManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.data import android.content.Context import android.graphics.Typeface import androidx.core.net.toFile import androidx.core.net.toUri import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import com.t8rin.imagetoolbox.core.data.utils.isInstalledFromPlayStore import com.t8rin.imagetoolbox.core.data.utils.outputStream import com.t8rin.imagetoolbox.core.domain.BACKUP_FILE_EXT import com.t8rin.imagetoolbox.core.domain.GLOBAL_STORAGE_NAME import com.t8rin.imagetoolbox.core.domain.coroutines.AppScope import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.json.JsonParser import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.model.PerformanceClass import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.ColorHarmonizer import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.domain.model.DomainFontFamily import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.settings.domain.model.NightMode import com.t8rin.imagetoolbox.core.settings.domain.model.OneTimeSaveLocation import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.domain.model.SnowfallMode import com.t8rin.imagetoolbox.core.settings.domain.model.SwitchType import com.t8rin.imagetoolbox.core.utils.createZip import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.core.utils.putEntry import com.t8rin.imagetoolbox.feature.settings.data.keys.ADD_ORIGINAL_NAME_TO_FILENAME import com.t8rin.imagetoolbox.feature.settings.data.keys.ADD_PRESET_TO_FILENAME import com.t8rin.imagetoolbox.feature.settings.data.keys.ADD_SCALE_MODE_TO_FILENAME import com.t8rin.imagetoolbox.feature.settings.data.keys.ADD_SEQ_NUM_TO_FILENAME import com.t8rin.imagetoolbox.feature.settings.data.keys.ADD_SIZE_TO_FILENAME import com.t8rin.imagetoolbox.feature.settings.data.keys.ADD_TIMESTAMP_TO_FILENAME import com.t8rin.imagetoolbox.feature.settings.data.keys.ALLOW_ANALYTICS import com.t8rin.imagetoolbox.feature.settings.data.keys.ALLOW_AUTO_PASTE import com.t8rin.imagetoolbox.feature.settings.data.keys.ALLOW_BETAS import com.t8rin.imagetoolbox.feature.settings.data.keys.ALLOW_CRASHLYTICS import com.t8rin.imagetoolbox.feature.settings.data.keys.ALLOW_IMAGE_MONET import com.t8rin.imagetoolbox.feature.settings.data.keys.ALLOW_SKIP_IF_LARGER import com.t8rin.imagetoolbox.feature.settings.data.keys.AMOLED_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.APP_COLOR_TUPLE import com.t8rin.imagetoolbox.feature.settings.data.keys.APP_OPEN_COUNT import com.t8rin.imagetoolbox.feature.settings.data.keys.ASCII_CUSTOM_GRADIENTS import com.t8rin.imagetoolbox.feature.settings.data.keys.AUTO_CACHE_CLEAR import com.t8rin.imagetoolbox.feature.settings.data.keys.BACKGROUND_COLOR_FOR_NA_FORMATS import com.t8rin.imagetoolbox.feature.settings.data.keys.BORDER_WIDTH import com.t8rin.imagetoolbox.feature.settings.data.keys.CAN_ENTER_PRESETS_BY_TEXT_FIELD import com.t8rin.imagetoolbox.feature.settings.data.keys.CENTER_ALIGN_DIALOG_BUTTONS import com.t8rin.imagetoolbox.feature.settings.data.keys.COLOR_BLIND_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.COLOR_TUPLES import com.t8rin.imagetoolbox.feature.settings.data.keys.CONFETTI_ENABLED import com.t8rin.imagetoolbox.feature.settings.data.keys.CONFETTI_HARMONIZATION_LEVEL import com.t8rin.imagetoolbox.feature.settings.data.keys.CONFETTI_HARMONIZER import com.t8rin.imagetoolbox.feature.settings.data.keys.CONFETTI_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.COPY_TO_CLIPBOARD_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.CUSTOM_FONTS import com.t8rin.imagetoolbox.feature.settings.data.keys.DEFAULT_DRAW_COLOR import com.t8rin.imagetoolbox.feature.settings.data.keys.DEFAULT_DRAW_LINE_WIDTH import com.t8rin.imagetoolbox.feature.settings.data.keys.DEFAULT_DRAW_PATH_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.DEFAULT_IMAGE_FORMAT import com.t8rin.imagetoolbox.feature.settings.data.keys.DEFAULT_QUALITY import com.t8rin.imagetoolbox.feature.settings.data.keys.DEFAULT_RESIZE_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.DONATE_DIALOG_OPEN_COUNT import com.t8rin.imagetoolbox.feature.settings.data.keys.DRAG_HANDLE_WIDTH import com.t8rin.imagetoolbox.feature.settings.data.keys.DRAW_APPBAR_SHADOWS import com.t8rin.imagetoolbox.feature.settings.data.keys.DRAW_BUTTON_SHADOWS import com.t8rin.imagetoolbox.feature.settings.data.keys.DRAW_CONTAINER_SHADOWS import com.t8rin.imagetoolbox.feature.settings.data.keys.DRAW_FAB_SHADOWS import com.t8rin.imagetoolbox.feature.settings.data.keys.DRAW_SLIDER_SHADOWS import com.t8rin.imagetoolbox.feature.settings.data.keys.DRAW_SWITCH_SHADOWS import com.t8rin.imagetoolbox.feature.settings.data.keys.DYNAMIC_COLORS import com.t8rin.imagetoolbox.feature.settings.data.keys.EMOJI_COUNT import com.t8rin.imagetoolbox.feature.settings.data.keys.ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS import com.t8rin.imagetoolbox.feature.settings.data.keys.ENABLE_TOOL_EXIT_CONFIRMATION import com.t8rin.imagetoolbox.feature.settings.data.keys.EXIF_WIDGET_INITIAL_STATE import com.t8rin.imagetoolbox.feature.settings.data.keys.FAB_ALIGNMENT import com.t8rin.imagetoolbox.feature.settings.data.keys.FAST_SETTINGS_SIDE import com.t8rin.imagetoolbox.feature.settings.data.keys.FAVORITE_COLORS import com.t8rin.imagetoolbox.feature.settings.data.keys.FAVORITE_SCREENS import com.t8rin.imagetoolbox.feature.settings.data.keys.FILENAME_BEHAVIOR import com.t8rin.imagetoolbox.feature.settings.data.keys.FILENAME_PATTERN import com.t8rin.imagetoolbox.feature.settings.data.keys.FILENAME_PREFIX import com.t8rin.imagetoolbox.feature.settings.data.keys.FILENAME_SUFFIX import com.t8rin.imagetoolbox.feature.settings.data.keys.FLING_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.FONT_SCALE import com.t8rin.imagetoolbox.feature.settings.data.keys.GENERATE_PREVIEWS import com.t8rin.imagetoolbox.feature.settings.data.keys.GROUP_OPTIONS_BY_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.HIDDEN_FOR_SHARE_SCREENS import com.t8rin.imagetoolbox.feature.settings.data.keys.ICON_SHAPE import com.t8rin.imagetoolbox.feature.settings.data.keys.IMAGE_PICKER_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.IMAGE_SCALE_COLOR_SPACE import com.t8rin.imagetoolbox.feature.settings.data.keys.IMAGE_SCALE_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.INITIAL_OCR_CODES import com.t8rin.imagetoolbox.feature.settings.data.keys.INITIAL_OCR_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.INVERT_THEME import com.t8rin.imagetoolbox.feature.settings.data.keys.IS_LAUNCHER_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.IS_LINK_PREVIEW_ENABLED import com.t8rin.imagetoolbox.feature.settings.data.keys.IS_SYSTEM_BARS_VISIBLE_BY_SWIPE import com.t8rin.imagetoolbox.feature.settings.data.keys.IS_TELEGRAM_GROUP_OPENED import com.t8rin.imagetoolbox.feature.settings.data.keys.KEEP_DATE_TIME import com.t8rin.imagetoolbox.feature.settings.data.keys.LOCK_DRAW_ORIENTATION import com.t8rin.imagetoolbox.feature.settings.data.keys.MAGNIFIER_ENABLED import com.t8rin.imagetoolbox.feature.settings.data.keys.MAIN_SCREEN_TITLE import com.t8rin.imagetoolbox.feature.settings.data.keys.NIGHT_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.ONE_TIME_SAVE_LOCATIONS import com.t8rin.imagetoolbox.feature.settings.data.keys.OPEN_EDIT_INSTEAD_OF_PREVIEW import com.t8rin.imagetoolbox.feature.settings.data.keys.PRESETS import com.t8rin.imagetoolbox.feature.settings.data.keys.RECENT_COLORS import com.t8rin.imagetoolbox.feature.settings.data.keys.SAVE_FOLDER_URI import com.t8rin.imagetoolbox.feature.settings.data.keys.SCREENS_WITH_BRIGHTNESS_ENFORCEMENT import com.t8rin.imagetoolbox.feature.settings.data.keys.SCREEN_ORDER import com.t8rin.imagetoolbox.feature.settings.data.keys.SCREEN_SEARCH_ENABLED import com.t8rin.imagetoolbox.feature.settings.data.keys.SECURE_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.SELECTED_EMOJI_INDEX import com.t8rin.imagetoolbox.feature.settings.data.keys.SELECTED_FONT import com.t8rin.imagetoolbox.feature.settings.data.keys.SETTINGS_GROUP_VISIBILITY import com.t8rin.imagetoolbox.feature.settings.data.keys.SHAPES_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.SHOW_SETTINGS_IN_LANDSCAPE import com.t8rin.imagetoolbox.feature.settings.data.keys.SHOW_UPDATE_DIALOG import com.t8rin.imagetoolbox.feature.settings.data.keys.SKIP_IMAGE_PICKING import com.t8rin.imagetoolbox.feature.settings.data.keys.SLIDER_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.SNOWFALL_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.SPOT_HEAL_MODE import com.t8rin.imagetoolbox.feature.settings.data.keys.SWITCH_TYPE import com.t8rin.imagetoolbox.feature.settings.data.keys.SYSTEM_BARS_VISIBILITY import com.t8rin.imagetoolbox.feature.settings.data.keys.THEME_CONTRAST_LEVEL import com.t8rin.imagetoolbox.feature.settings.data.keys.THEME_STYLE import com.t8rin.imagetoolbox.feature.settings.data.keys.USE_COMPACT_SELECTORS_LAYOUT import com.t8rin.imagetoolbox.feature.settings.data.keys.USE_EMOJI_AS_PRIMARY_COLOR import com.t8rin.imagetoolbox.feature.settings.data.keys.USE_FORMATTED_TIMESTAMP import com.t8rin.imagetoolbox.feature.settings.data.keys.USE_FULLSCREEN_SETTINGS import com.t8rin.imagetoolbox.feature.settings.data.keys.USE_RANDOM_EMOJIS import com.t8rin.imagetoolbox.feature.settings.data.keys.VIBRATION_STRENGTH import com.t8rin.imagetoolbox.feature.settings.data.keys.toSettingsState import com.t8rin.logger.Logger import com.t8rin.logger.makeLog import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.ByteArrayInputStream import java.io.File import java.io.FileInputStream import javax.inject.Inject import kotlin.random.Random internal class AndroidSettingsManager @Inject constructor( @ApplicationContext private val context: Context, private val dataStore: DataStore, private val shareProvider: Lazy, private val jsonParser: JsonParser, dispatchersHolder: DispatchersHolder, appScope: AppScope, ) : DispatchersHolder by dispatchersHolder, SettingsManager { init { appScope.launch { registerAppOpen() } } private val default = SettingsState.Default private val currentSettings: SettingsState get() = settingsState.value override suspend fun getSettingsState(): SettingsState = rawFlow().first() private fun rawFlow(): Flow = dataStore.data.map { it.toSettingsState( default = default, jsonParser = jsonParser ) } override val settingsState: StateFlow = rawFlow().stateIn( scope = appScope, started = SharingStarted.Eagerly, initialValue = default ) override suspend fun toggleAddSequenceNumber() = toggle( key = ADD_SEQ_NUM_TO_FILENAME, defaultValue = default.addSequenceNumber ) override suspend fun toggleAddOriginalFilename() = toggle( key = ADD_ORIGINAL_NAME_TO_FILENAME, defaultValue = default.addOriginalFilename ) override suspend fun setEmojisCount(count: Int) = edit { it[EMOJI_COUNT] = count } override suspend fun setImagePickerMode(mode: Int) = edit { it[IMAGE_PICKER_MODE] = mode } override suspend fun toggleAddFileSize() = toggle( key = ADD_SIZE_TO_FILENAME, defaultValue = default.addSizeInFilename ) override suspend fun setEmoji(emoji: Int) = edit { it[SELECTED_EMOJI_INDEX] = emoji } override suspend fun setFilenamePrefix(name: String) = edit { it[FILENAME_PREFIX] = name } override suspend fun toggleShowUpdateDialogOnStartup() = toggle( key = SHOW_UPDATE_DIALOG, defaultValue = default.showUpdateDialogOnStartup ) override suspend fun setColorTuple(colorTuple: String) = edit { it[APP_COLOR_TUPLE] = colorTuple } override suspend fun setPresets(newPresets: List) = edit { preferences -> if (newPresets.size > 3) { preferences[PRESETS] = newPresets .map { it.coerceIn(10..500) } .toSortedSet() .toList() .reversed() .joinToString("*") } } override suspend fun toggleDynamicColors() = edit { it.toggle( key = DYNAMIC_COLORS, defaultValue = default.isDynamicColors ) if (it[DYNAMIC_COLORS] == true) { it[ALLOW_IMAGE_MONET] = false } } override suspend fun setBorderWidth(width: Float) = edit { it[BORDER_WIDTH] = if (width > 0) width else -1f } override suspend fun toggleAllowImageMonet() = toggle( key = ALLOW_IMAGE_MONET, defaultValue = default.allowChangeColorByImage ) override suspend fun toggleAmoledMode() = toggle( key = AMOLED_MODE, defaultValue = default.isAmoledMode ) override suspend fun setNightMode(nightMode: NightMode) = edit { it[NIGHT_MODE] = nightMode.ordinal } override suspend fun setSaveFolderUri(uri: String?) = edit { it[SAVE_FOLDER_URI] = uri ?: "" } override suspend fun setColorTuples(colorTuples: String) = edit { it[COLOR_TUPLES] = colorTuples } override suspend fun setAlignment(align: Int) = edit { it[FAB_ALIGNMENT] = align } override suspend fun setScreenOrder(data: String) = edit { it[SCREEN_ORDER] = data } override suspend fun toggleClearCacheOnLaunch() = toggle( key = AUTO_CACHE_CLEAR, defaultValue = default.clearCacheOnLaunch ) override suspend fun toggleGroupOptionsByTypes() = toggle( key = GROUP_OPTIONS_BY_TYPE, defaultValue = default.groupOptionsByTypes ) override suspend fun toggleRandomizeFilename() = toggleFilenameBehavior( behavior = FilenameBehavior.Random() ) override suspend fun createBackupFile(): ByteArray = context.obtainDatastoreData(GLOBAL_STORAGE_NAME) override suspend fun restoreFromBackupFile( backupFileUri: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit, ) = withContext(ioDispatcher) { context.restoreDatastore( fileName = GLOBAL_STORAGE_NAME, backupUri = backupFileUri.toUri(), onFailure = onFailure, onSuccess = { onSuccess() setSaveFolderUri(null) } ) toggleClearCacheOnLaunch() toggleClearCacheOnLaunch() } override suspend fun resetSettings() = withContext(defaultDispatcher) { context.resetDatastore(GLOBAL_STORAGE_NAME) registerAppOpen() } override fun createBackupFilename(): String = "image_toolbox_${timestamp()}.$BACKUP_FILE_EXT" override suspend fun setFont(font: DomainFontFamily) = edit { it[SELECTED_FONT] = font.asString() } override suspend fun setFontScale(scale: Float) = edit { it[FONT_SCALE] = scale } override suspend fun toggleAllowCrashlytics() = toggle( key = ALLOW_CRASHLYTICS, defaultValue = default.allowCollectCrashlytics ) override suspend fun toggleAllowAnalytics() = toggle( key = ALLOW_ANALYTICS, defaultValue = default.allowCollectAnalytics ) override suspend fun toggleAllowBetas() = toggle( key = ALLOW_BETAS, defaultValue = default.allowBetas ) override suspend fun toggleDrawContainerShadows() = toggle( key = DRAW_CONTAINER_SHADOWS, defaultValue = default.drawContainerShadows ) override suspend fun toggleDrawButtonShadows() = toggle( key = DRAW_BUTTON_SHADOWS, defaultValue = default.drawButtonShadows ) override suspend fun toggleDrawSliderShadows() = toggle( key = DRAW_SLIDER_SHADOWS, defaultValue = default.drawSliderShadows ) override suspend fun toggleDrawSwitchShadows() = toggle( key = DRAW_SWITCH_SHADOWS, defaultValue = default.drawSwitchShadows ) override suspend fun toggleDrawFabShadows() = toggle( key = DRAW_FAB_SHADOWS, defaultValue = default.drawFabShadows ) private suspend fun registerAppOpen() = edit { val v = it[APP_OPEN_COUNT] ?: default.appOpenCount it[APP_OPEN_COUNT] = v + 1 } override suspend fun toggleLockDrawOrientation() = toggle( key = LOCK_DRAW_ORIENTATION, defaultValue = default.lockDrawOrientation ) override suspend fun setThemeStyle(value: Int) = edit { it[THEME_STYLE] = value } override suspend fun setThemeContrast(value: Double) = edit { it[THEME_CONTRAST_LEVEL] = value } override suspend fun toggleInvertColors() = toggle( key = INVERT_THEME, defaultValue = default.isInvertThemeColors ) override suspend fun toggleScreensSearchEnabled() = toggle( key = SCREEN_SEARCH_ENABLED, defaultValue = default.screensSearchEnabled ) override suspend fun toggleDrawAppBarShadows() = toggle( key = DRAW_APPBAR_SHADOWS, defaultValue = default.drawAppBarShadows ) override suspend fun setCopyToClipboardMode( copyToClipboardMode: CopyToClipboardMode ) = edit { it[COPY_TO_CLIPBOARD_MODE] = copyToClipboardMode.value } override suspend fun setVibrationStrength(strength: Int) = edit { it[VIBRATION_STRENGTH] = strength } override suspend fun toggleOverwriteFiles() = toggleFilenameBehavior( behavior = FilenameBehavior.Overwrite() ) override suspend fun setSpotHealMode(mode: Int) = edit { it[SPOT_HEAL_MODE] = mode } override suspend fun setFilenameSuffix(name: String) = edit { it[FILENAME_SUFFIX] = name } override suspend fun setDefaultImageScaleMode(imageScaleMode: ImageScaleMode) = edit { it[IMAGE_SCALE_MODE] = imageScaleMode.value it[IMAGE_SCALE_COLOR_SPACE] = imageScaleMode.scaleColorSpace.ordinal } override suspend fun toggleMagnifierEnabled() = toggle( key = MAGNIFIER_ENABLED, defaultValue = default.magnifierEnabled ) override suspend fun toggleExifWidgetInitialState() = toggle( key = EXIF_WIDGET_INITIAL_STATE, defaultValue = default.exifWidgetInitialState ) override suspend fun setInitialOCRLanguageCodes(list: List) = edit { it[INITIAL_OCR_CODES] = list.joinToString(separator = "+") } override suspend fun createLogsExport(): String = withContext(ioDispatcher) { "Start Logs Export".makeLog("SettingsManager") val logsFile = Logger.getLogsFile().toFile() val settingsFile = createBackupFile() shareProvider.get().cacheData( writeData = { writeable -> writeable.outputStream().createZip { zip -> zip.putEntry( name = logsFile.name, input = FileInputStream(logsFile) ) zip.putEntry( name = createBackupFilename(), input = ByteArrayInputStream(settingsFile) ) } }, filename = "image_toolbox_logs_${timestamp()}.zip" ) ?: "" } override suspend fun toggleAddPresetInfoToFilename() = toggle( key = ADD_PRESET_TO_FILENAME, defaultValue = default.addPresetInfoToFilename ) override suspend fun toggleAddImageScaleModeInfoToFilename() = toggle( key = ADD_SCALE_MODE_TO_FILENAME, defaultValue = default.addImageScaleModeInfoToFilename ) override suspend fun toggleAllowSkipIfLarger() = toggle( key = ALLOW_SKIP_IF_LARGER, defaultValue = default.allowSkipIfLarger ) override suspend fun toggleIsScreenSelectionLauncherMode() = toggle( key = IS_LAUNCHER_MODE, defaultValue = default.isScreenSelectionLauncherMode ) override suspend fun setScreensWithBrightnessEnforcement(data: List) = edit { preferences -> preferences[SCREENS_WITH_BRIGHTNESS_ENFORCEMENT] = data.joinToString("/") { it.toString() } } override suspend fun toggleConfettiEnabled() = toggle( key = CONFETTI_ENABLED, defaultValue = default.isConfettiEnabled ) override suspend fun toggleSecureMode() = toggle( key = SECURE_MODE, defaultValue = default.isSecureMode ) override suspend fun toggleUseRandomEmojis() = toggle( key = USE_RANDOM_EMOJIS, defaultValue = default.useRandomEmojis ) override suspend fun setIconShape(iconShape: Int) = edit { it[ICON_SHAPE] = iconShape } override suspend fun toggleUseEmojiAsPrimaryColor() = toggle( key = USE_EMOJI_AS_PRIMARY_COLOR, defaultValue = default.useEmojiAsPrimaryColor ) override suspend fun setDragHandleWidth(width: Int) = edit { it[DRAG_HANDLE_WIDTH] = width } override suspend fun setConfettiType(type: Int) = edit { it[CONFETTI_TYPE] = type } override suspend fun toggleAllowAutoClipboardPaste() = toggle( key = ALLOW_AUTO_PASTE, defaultValue = default.allowAutoClipboardPaste ) override suspend fun setConfettiHarmonizer(colorHarmonizer: ColorHarmonizer) = edit { it[CONFETTI_HARMONIZER] = colorHarmonizer.ordinal } override suspend fun setConfettiHarmonizationLevel(level: Float) = edit { it[CONFETTI_HARMONIZATION_LEVEL] = level } override suspend fun toggleGeneratePreviews() = toggle( key = GENERATE_PREVIEWS, defaultValue = default.generatePreviews ) override suspend fun toggleSkipImagePicking() = toggle( key = SKIP_IMAGE_PICKING, defaultValue = default.skipImagePicking ) override suspend fun toggleShowSettingsInLandscape() = toggle( key = SHOW_SETTINGS_IN_LANDSCAPE, defaultValue = default.showSettingsInLandscape ) override suspend fun toggleUseFullscreenSettings() = toggle( key = USE_FULLSCREEN_SETTINGS, defaultValue = default.useFullscreenSettings ) override suspend fun setSwitchType(type: SwitchType) = edit { it[SWITCH_TYPE] = type.ordinal } override suspend fun setDefaultDrawLineWidth(value: Float) = edit { it[DEFAULT_DRAW_LINE_WIDTH] = value } override suspend fun setOneTimeSaveLocations( value: List ) = edit { preferences -> preferences[ONE_TIME_SAVE_LOCATIONS] = value.filter { it.uri.isNotEmpty() && it.date != null }.distinctBy { it.uri }.joinToString(", ") } override suspend fun toggleRecentColor( color: ColorModel, forceExclude: Boolean, ) = edit { preferences -> val current = currentSettings.recentColors val newColors = if (color in current) { if (forceExclude) { current - color } else { listOf(color) + (current - color) } } else { listOf(color) + current } preferences[RECENT_COLORS] = newColors.take(30).map { it.colorInt.toString() }.toSet() } override suspend fun toggleFavoriteColor( color: ColorModel, forceExclude: Boolean ) = edit { preferences -> val current = currentSettings.favoriteColors val newColors = if (color in current) { if (forceExclude) { current - color } else { listOf(color) + (current - color) } } else { listOf(color) + current } preferences[FAVORITE_COLORS] = newColors.joinToString("/") { it.colorInt.toString() } } override suspend fun toggleOpenEditInsteadOfPreview() = toggle( key = OPEN_EDIT_INSTEAD_OF_PREVIEW, defaultValue = default.openEditInsteadOfPreview ) override suspend fun toggleCanEnterPresetsByTextField() = toggle( key = CAN_ENTER_PRESETS_BY_TEXT_FIELD, defaultValue = default.canEnterPresetsByTextField ) override suspend fun adjustPerformance(performanceClass: PerformanceClass) = edit { when (performanceClass) { PerformanceClass.Low -> { it[CONFETTI_ENABLED] = false it[DRAW_BUTTON_SHADOWS] = false it[DRAW_SWITCH_SHADOWS] = false it[DRAW_SLIDER_SHADOWS] = false it[DRAW_CONTAINER_SHADOWS] = false it[DRAW_APPBAR_SHADOWS] = false } PerformanceClass.Average -> { it[CONFETTI_ENABLED] = true it[DRAW_BUTTON_SHADOWS] = false it[DRAW_SWITCH_SHADOWS] = true it[DRAW_SLIDER_SHADOWS] = false it[DRAW_CONTAINER_SHADOWS] = false it[DRAW_APPBAR_SHADOWS] = true } PerformanceClass.High -> { it[CONFETTI_ENABLED] = true it[DRAW_BUTTON_SHADOWS] = true it[DRAW_SWITCH_SHADOWS] = true it[DRAW_SLIDER_SHADOWS] = true it[DRAW_CONTAINER_SHADOWS] = true it[DRAW_APPBAR_SHADOWS] = true } } } override suspend fun registerDonateDialogOpen() = edit { val value = it[DONATE_DIALOG_OPEN_COUNT] ?: default.donateDialogOpenCount if (value != -1) { it[DONATE_DIALOG_OPEN_COUNT] = value + 1 } } override suspend fun setNotShowDonateDialogAgain() = edit { it[DONATE_DIALOG_OPEN_COUNT] = -1 } override suspend fun setColorBlindType(value: Int?) = edit { it[COLOR_BLIND_TYPE] = value ?: -1 } override suspend fun toggleFavoriteScreen(screenId: Int) = edit { val current = currentSettings.favoriteScreenList val newScreens = if (screenId in current) { current - screenId } else { current + screenId } it[FAVORITE_SCREENS] = newScreens.joinToString("/") } override suspend fun toggleIsLinkPreviewEnabled() = toggle( key = IS_LINK_PREVIEW_ENABLED, defaultValue = default.isLinkPreviewEnabled ) override suspend fun setDefaultDrawColor(color: ColorModel) = edit { it[DEFAULT_DRAW_COLOR] = color.colorInt } override suspend fun setDefaultDrawPathMode(modeOrdinal: Int) = edit { it[DEFAULT_DRAW_PATH_MODE] = modeOrdinal } override suspend fun toggleAddTimestampToFilename() = toggle( key = ADD_TIMESTAMP_TO_FILENAME, defaultValue = default.addTimestampToFilename ) override suspend fun toggleUseFormattedFilenameTimestamp() = toggle( key = USE_FORMATTED_TIMESTAMP, defaultValue = default.useFormattedFilenameTimestamp ) override suspend fun registerTelegramGroupOpen() = edit { it[IS_TELEGRAM_GROUP_OPENED] = true } override suspend fun setDefaultResizeType(resizeType: ResizeType) = edit { preferences -> preferences[DEFAULT_RESIZE_TYPE] = ResizeType.entries.indexOfFirst { it::class.isInstance(resizeType) } } override suspend fun setSystemBarsVisibility( systemBarsVisibility: SystemBarsVisibility ) = edit { it[SYSTEM_BARS_VISIBILITY] = systemBarsVisibility.ordinal } override suspend fun toggleIsSystemBarsVisibleBySwipe() = toggle( key = IS_SYSTEM_BARS_VISIBLE_BY_SWIPE, defaultValue = default.isSystemBarsVisibleBySwipe ) override suspend fun setInitialOcrMode(mode: Int) = edit { it[INITIAL_OCR_MODE] = mode } override suspend fun toggleUseCompactSelectorsLayout() = toggle( key = USE_COMPACT_SELECTORS_LAYOUT, defaultValue = default.isCompactSelectorsLayout ) override suspend fun setMainScreenTitle(title: String) = edit { it[MAIN_SCREEN_TITLE] = title } override suspend fun setSliderType(type: SliderType) = edit { it[SLIDER_TYPE] = type.ordinal } override suspend fun toggleIsCenterAlignDialogButtons() = toggle( key = CENTER_ALIGN_DIALOG_BUTTONS, defaultValue = default.isCenterAlignDialogButtons ) override fun isInstalledFromPlayStore(): Boolean = context.isInstalledFromPlayStore() override suspend fun toggleSettingsGroupVisibility( key: Int, value: Boolean ) = edit { preferences -> preferences[SETTINGS_GROUP_VISIBILITY] = currentSettings.settingGroupsInitialVisibility.toMutableMap().run { this[key] = value map { "${it.key}:${it.value}" }.toSet() } } override suspend fun clearRecentColors() = edit { it[RECENT_COLORS] = emptySet() } override suspend fun updateFavoriteColors( colors: List ) = edit { preferences -> preferences[FAVORITE_COLORS] = colors.joinToString("/") { it.colorInt.toString() } } override suspend fun setBackgroundColorForNoAlphaFormats( color: ColorModel ) = edit { it[BACKGROUND_COLOR_FOR_NA_FORMATS] = color.colorInt } override suspend fun setFastSettingsSide(side: FastSettingsSide) = edit { it[FAST_SETTINGS_SIDE] = side.ordinal } override suspend fun setChecksumTypeForFilename(type: HashingType?) = toggleFilenameBehavior( behavior = type?.let { FilenameBehavior.Checksum(type) } ?: FilenameBehavior.None() ) override suspend fun setCustomFonts(fonts: List) = edit { it[CUSTOM_FONTS] = fonts.map(DomainFontFamily::asString).toSet() } override suspend fun importCustomFont( uri: String ): DomainFontFamily.Custom? = withContext(ioDispatcher) { val font = context.contentResolver.openInputStream(uri.toUri())?.use { it.buffered().readBytes() } ?: ByteArray(0) val filename = uri.toUri().filename(context) ?: "font${Random.nextInt()}.ttf" val directory = File(context.filesDir, "customFonts").apply { mkdir() } val file = File(directory, filename).apply { if (exists()) { val fontToRemove = DomainFontFamily.Custom( name = nameWithoutExtension.replace("[:\\-_.,]".toRegex(), " "), filePath = absolutePath ) removeCustomFont(fontToRemove) } delete() createNewFile() outputStream().use { writeBytes(font) } } val typeface = runCatching { Typeface.createFromFile(file) }.getOrNull() if (typeface == null) { file.delete() return@withContext null } DomainFontFamily.Custom( name = file.nameWithoutExtension.replace("[:\\-_.,]".toRegex(), " "), filePath = file.absolutePath ).also { setCustomFonts(currentSettings.customFonts + it) } } override suspend fun removeCustomFont( font: DomainFontFamily.Custom ) = withContext(ioDispatcher) { File(font.filePath).delete() setCustomFonts(currentSettings.customFonts - font) } override suspend fun createCustomFontsExport(): String? = withContext(ioDispatcher) { shareProvider.get().cacheData( writeData = { writeable -> writeable.outputStream().createZip { zip -> File(context.filesDir, "customFonts").listFiles()?.forEach { file -> zip.putEntry( name = file.name, input = FileInputStream(file) ) } } }, filename = "fonts_export.zip" ) } override suspend fun toggleEnableToolExitConfirmation() = toggle( key = ENABLE_TOOL_EXIT_CONFIRMATION, defaultValue = default.enableToolExitConfirmation ) override suspend fun toggleCustomAsciiGradient(gradient: String) = edit { it[ASCII_CUSTOM_GRADIENTS] = (it[ASCII_CUSTOM_GRADIENTS] ?: emptySet()).toggle(gradient) } override suspend fun setSnowfallMode(snowfallMode: SnowfallMode) = edit { it[SNOWFALL_MODE] = snowfallMode.ordinal } override suspend fun setDefaultImageFormat(imageFormat: ImageFormat?) = edit { if (imageFormat == null) { it[DEFAULT_IMAGE_FORMAT] = "" } else { it[DEFAULT_IMAGE_FORMAT] = imageFormat.title } } override suspend fun setDefaultQuality(quality: Quality) = edit { jsonParser.toJson(quality, Quality::class.java)?.apply { it[DEFAULT_QUALITY] = this } } override suspend fun setShapesType(shapeType: ShapeType) = edit { jsonParser.toJson(shapeType, ShapeType::class.java)?.apply { it[SHAPES_TYPE] = this } } override suspend fun setFilenamePattern(pattern: String?) = edit { it[FILENAME_PATTERN] = pattern.orEmpty() } override suspend fun setFlingType(type: FlingType) = edit { it[FLING_TYPE] = type.ordinal } override suspend fun setHiddenForShareScreens(data: List) = edit { preferences -> preferences[HIDDEN_FOR_SHARE_SCREENS] = data.joinToString("/") { it.toString() } } override suspend fun toggleKeepDateTime() = toggle( key = KEEP_DATE_TIME, defaultValue = default.keepDateTime ) override suspend fun toggleEnableBackgroundColorForAlphaFormats() = toggle( key = ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS, defaultValue = default.enableBackgroundColorForAlphaFormats ) private suspend fun toggleFilenameBehavior( behavior: FilenameBehavior ) = edit { val useToggle = behavior is FilenameBehavior.Checksum || !currentSettings.filenameBehavior::class.isInstance(behavior) if (useToggle) { it[FILENAME_BEHAVIOR] = jsonParser.toJson(behavior, FilenameBehavior::class.java).orEmpty() } else { it[FILENAME_BEHAVIOR] = "" } } private fun MutablePreferences.toggle( key: Preferences.Key, defaultValue: Boolean, ) { val value = this[key] ?: defaultValue this[key] = !value } private suspend fun toggle( key: Preferences.Key, defaultValue: Boolean, ) = edit { it.toggle( key = key, defaultValue = defaultValue ) } private suspend fun edit( transform: suspend (MutablePreferences) -> Unit ) { dataStore.edit(transform) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/ContextUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.data import android.content.Context import android.net.Uri import androidx.datastore.preferences.core.PreferencesSerializer import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.resources.R import kotlinx.coroutines.coroutineScope import okio.buffer import okio.source import java.io.ByteArrayInputStream import java.io.File internal suspend fun Context.restoreDatastore( fileName: String, backupUri: Uri, onFailure: (Throwable) -> Unit, onSuccess: suspend () -> Unit ) = coroutineScope { runSuspendCatching { contentResolver.openInputStream(backupUri)?.use { input -> val bytes = input.readBytes() restoreDatastore( fileName = fileName, backupData = bytes, onFailure = onFailure, onSuccess = onSuccess ) } }.onFailure(onFailure).onSuccess { onSuccess() } } internal suspend fun Context.restoreDatastore( fileName: String, backupData: ByteArray, onFailure: (Throwable) -> Unit, onSuccess: suspend () -> Unit ) = coroutineScope { runSuspendCatching { runSuspendCatching { PreferencesSerializer.readFrom(ByteArrayInputStream(backupData).source().buffer()) }.onFailure { onFailure(Throwable(getString(R.string.corrupted_file_or_not_a_backup))) return@coroutineScope } File( filesDir, "datastore/${fileName}.preferences_pb" ).outputStream().use { ByteArrayInputStream(backupData).copyTo(it) } }.onFailure(onFailure).onSuccess { onSuccess() } } internal suspend fun Context.obtainDatastoreData( fileName: String ) = coroutineScope { File(filesDir, "datastore/${fileName}.preferences_pb").readBytes() } internal suspend fun Context.resetDatastore( fileName: String ) = coroutineScope { File( filesDir, "datastore/${fileName}.preferences_pb" ).apply { delete() createNewFile() } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/MapToSettingsState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.data.keys import androidx.datastore.preferences.core.Preferences import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.image.model.ScaleColorSpace import com.t8rin.imagetoolbox.core.domain.json.JsonParser import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.settings.domain.model.ColorHarmonizer import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.domain.model.DomainFontFamily import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.settings.domain.model.NightMode import com.t8rin.imagetoolbox.core.settings.domain.model.OneTimeSaveLocation import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.domain.model.SnowfallMode import com.t8rin.imagetoolbox.core.settings.domain.model.SwitchType internal fun Preferences.toSettingsState( default: SettingsState, jsonParser: JsonParser ): SettingsState = SettingsState( nightMode = NightMode.fromOrdinal(this[NIGHT_MODE]) ?: default.nightMode, isDynamicColors = this[DYNAMIC_COLORS] ?: default.isDynamicColors, isAmoledMode = this[AMOLED_MODE] ?: default.isAmoledMode, appColorTuple = this[APP_COLOR_TUPLE] ?: default.appColorTuple, borderWidth = this[BORDER_WIDTH] ?: default.borderWidth, showUpdateDialogOnStartup = this[SHOW_UPDATE_DIALOG] ?: default.showUpdateDialogOnStartup, selectedEmoji = this[SELECTED_EMOJI_INDEX] ?: default.selectedEmoji, screenList = this[SCREEN_ORDER]?.split("/")?.mapNotNull { it.toIntOrNull() }?.takeIf { it.isNotEmpty() } ?: default.screenList, emojisCount = this[EMOJI_COUNT] ?: default.emojisCount, clearCacheOnLaunch = this[AUTO_CACHE_CLEAR] ?: default.clearCacheOnLaunch, groupOptionsByTypes = this[GROUP_OPTIONS_BY_TYPE] ?: default.groupOptionsByTypes, addSequenceNumber = this[ADD_SEQ_NUM_TO_FILENAME] ?: default.addSequenceNumber, saveFolderUri = this[SAVE_FOLDER_URI], presets = Preset.createListFromInts(this[PRESETS]) ?: default.presets, colorTupleList = this[COLOR_TUPLES], allowChangeColorByImage = this[ALLOW_IMAGE_MONET] ?: default.allowChangeColorByImage, picturePickerModeInt = this[IMAGE_PICKER_MODE] ?: default.picturePickerModeInt, fabAlignment = this[FAB_ALIGNMENT] ?: default.fabAlignment, filenamePrefix = this[FILENAME_PREFIX] ?: default.filenamePrefix, addSizeInFilename = this[ADD_SIZE_TO_FILENAME] ?: default.addSizeInFilename, addOriginalFilename = this[ADD_ORIGINAL_NAME_TO_FILENAME] ?: default.addOriginalFilename, font = DomainFontFamily.fromString(this[SELECTED_FONT]) ?: default.font, fontScale = (this[FONT_SCALE] ?: 1f).takeIf { it > 0f }, allowCollectCrashlytics = this[ALLOW_CRASHLYTICS] ?: default.allowCollectCrashlytics, allowCollectAnalytics = this[ALLOW_ANALYTICS] ?: default.allowCollectAnalytics, allowBetas = this[ALLOW_BETAS] ?: default.allowBetas, drawContainerShadows = this[DRAW_CONTAINER_SHADOWS] ?: default.drawContainerShadows, drawFabShadows = this[DRAW_FAB_SHADOWS] ?: default.drawFabShadows, drawSwitchShadows = this[DRAW_SWITCH_SHADOWS] ?: default.drawSwitchShadows, drawSliderShadows = this[DRAW_SLIDER_SHADOWS] ?: default.drawSliderShadows, drawButtonShadows = this[DRAW_BUTTON_SHADOWS] ?: default.drawButtonShadows, drawAppBarShadows = this[DRAW_APPBAR_SHADOWS] ?: default.drawAppBarShadows, appOpenCount = this[APP_OPEN_COUNT] ?: default.appOpenCount, aspectRatios = default.aspectRatios, lockDrawOrientation = this[LOCK_DRAW_ORIENTATION] ?: default.lockDrawOrientation, themeContrastLevel = this[THEME_CONTRAST_LEVEL] ?: default.themeContrastLevel, themeStyle = this[THEME_STYLE] ?: default.themeStyle, isInvertThemeColors = this[INVERT_THEME] ?: default.isInvertThemeColors, screensSearchEnabled = this[SCREEN_SEARCH_ENABLED] ?: default.screensSearchEnabled, copyToClipboardMode = this[COPY_TO_CLIPBOARD_MODE]?.let { CopyToClipboardMode.fromInt(it) } ?: default.copyToClipboardMode, hapticsStrength = this[VIBRATION_STRENGTH] ?: default.hapticsStrength, filenameSuffix = this[FILENAME_SUFFIX] ?: default.filenameSuffix, defaultImageScaleMode = this.toDefaultImageScaleMode(default), magnifierEnabled = this[MAGNIFIER_ENABLED] ?: default.magnifierEnabled, exifWidgetInitialState = this[EXIF_WIDGET_INITIAL_STATE] ?: default.exifWidgetInitialState, initialOcrCodes = this[INITIAL_OCR_CODES]?.split("+") ?: default.initialOcrCodes, screenListWithMaxBrightnessEnforcement = this[SCREENS_WITH_BRIGHTNESS_ENFORCEMENT]?.split( "/" )?.mapNotNull { it.toIntOrNull() } ?: default.screenListWithMaxBrightnessEnforcement, isConfettiEnabled = this[CONFETTI_ENABLED] ?: default.isConfettiEnabled, isSecureMode = this[SECURE_MODE] ?: default.isSecureMode, useRandomEmojis = this[USE_RANDOM_EMOJIS] ?: default.useRandomEmojis, iconShape = (this[ICON_SHAPE] ?: default.iconShape)?.takeIf { it >= 0 }, useEmojiAsPrimaryColor = this[USE_EMOJI_AS_PRIMARY_COLOR] ?: default.useEmojiAsPrimaryColor, dragHandleWidth = this[DRAG_HANDLE_WIDTH] ?: default.dragHandleWidth, confettiType = this[CONFETTI_TYPE] ?: default.confettiType, allowAutoClipboardPaste = this[ALLOW_AUTO_PASTE] ?: default.allowAutoClipboardPaste, confettiColorHarmonizer = this[CONFETTI_HARMONIZER]?.let { ColorHarmonizer.fromInt(it) } ?: default.confettiColorHarmonizer, confettiHarmonizationLevel = this[CONFETTI_HARMONIZATION_LEVEL] ?: default.confettiHarmonizationLevel, skipImagePicking = this[SKIP_IMAGE_PICKING] ?: default.skipImagePicking, generatePreviews = this[GENERATE_PREVIEWS] ?: default.generatePreviews, showSettingsInLandscape = this[SHOW_SETTINGS_IN_LANDSCAPE] ?: default.showSettingsInLandscape, useFullscreenSettings = this[USE_FULLSCREEN_SETTINGS] ?: default.useFullscreenSettings, switchType = this[SWITCH_TYPE]?.let { SwitchType.fromInt(it) } ?: default.switchType, defaultDrawLineWidth = this[DEFAULT_DRAW_LINE_WIDTH] ?: default.defaultDrawLineWidth, oneTimeSaveLocations = this[ONE_TIME_SAVE_LOCATIONS]?.split(", ") ?.mapNotNull { string -> OneTimeSaveLocation.fromString(string)?.takeIf { it.uri.isNotEmpty() && it.date != null } } ?.sortedWith(compareBy(OneTimeSaveLocation::count, OneTimeSaveLocation::date)) ?.reversed() ?: default.oneTimeSaveLocations, openEditInsteadOfPreview = this[OPEN_EDIT_INSTEAD_OF_PREVIEW] ?: default.openEditInsteadOfPreview, canEnterPresetsByTextField = this[CAN_ENTER_PRESETS_BY_TEXT_FIELD] ?: default.canEnterPresetsByTextField, donateDialogOpenCount = this[DONATE_DIALOG_OPEN_COUNT] ?: default.donateDialogOpenCount, colorBlindType = this[COLOR_BLIND_TYPE]?.let { if (it < 0) null else it } ?: default.colorBlindType, favoriteScreenList = this[FAVORITE_SCREENS]?.split("/")?.mapNotNull { it.toIntOrNull() }?.takeIf { it.isNotEmpty() } ?: default.favoriteScreenList, isLinkPreviewEnabled = this[IS_LINK_PREVIEW_ENABLED] ?: default.isLinkPreviewEnabled, defaultDrawColor = this[DEFAULT_DRAW_COLOR]?.let { ColorModel(it) } ?: default.defaultDrawColor, defaultDrawPathMode = this[DEFAULT_DRAW_PATH_MODE] ?: default.defaultDrawPathMode, addTimestampToFilename = this[ADD_TIMESTAMP_TO_FILENAME] ?: default.addTimestampToFilename, useFormattedFilenameTimestamp = this[USE_FORMATTED_TIMESTAMP] ?: default.useFormattedFilenameTimestamp, favoriteColors = this[FAVORITE_COLORS]?.split("/")?.mapNotNull { color -> color.toIntOrNull()?.let { ColorModel(it) } } ?: default.favoriteColors, defaultResizeType = this[DEFAULT_RESIZE_TYPE]?.let { ResizeType.entries.getOrNull(it) } ?: default.defaultResizeType, systemBarsVisibility = SystemBarsVisibility.fromOrdinal(this[SYSTEM_BARS_VISIBILITY]) ?: default.systemBarsVisibility, isSystemBarsVisibleBySwipe = this[IS_SYSTEM_BARS_VISIBLE_BY_SWIPE] ?: default.isSystemBarsVisibleBySwipe, isCompactSelectorsLayout = this[USE_COMPACT_SELECTORS_LAYOUT] ?: default.isCompactSelectorsLayout, mainScreenTitle = this[MAIN_SCREEN_TITLE] ?: default.mainScreenTitle, sliderType = this[SLIDER_TYPE]?.let { SliderType.fromInt(it) } ?: default.sliderType, isCenterAlignDialogButtons = this[CENTER_ALIGN_DIALOG_BUTTONS] ?: default.isCenterAlignDialogButtons, fastSettingsSide = this[FAST_SETTINGS_SIDE]?.let { FastSettingsSide.fromOrdinal(it) } ?: default.fastSettingsSide, settingGroupsInitialVisibility = this[SETTINGS_GROUP_VISIBILITY].toSettingGroupsInitialVisibility( default ), customFonts = this[CUSTOM_FONTS].toCustomFonts(), enableToolExitConfirmation = this[ENABLE_TOOL_EXIT_CONFIRMATION] ?: default.enableToolExitConfirmation, recentColors = this[RECENT_COLORS]?.mapNotNull { color -> color.toIntOrNull()?.let { ColorModel(it) } } ?: default.recentColors, backgroundForNoAlphaImageFormats = this[BACKGROUND_COLOR_FOR_NA_FORMATS]?.let { ColorModel(it) } ?: default.backgroundForNoAlphaImageFormats, addPresetInfoToFilename = this[ADD_PRESET_TO_FILENAME] ?: default.addPresetInfoToFilename, addImageScaleModeInfoToFilename = this[ADD_SCALE_MODE_TO_FILENAME] ?: default.addImageScaleModeInfoToFilename, allowSkipIfLarger = this[ALLOW_SKIP_IF_LARGER] ?: default.allowSkipIfLarger, customAsciiGradients = this[ASCII_CUSTOM_GRADIENTS] ?: default.customAsciiGradients, isScreenSelectionLauncherMode = this[IS_LAUNCHER_MODE] ?: default.isScreenSelectionLauncherMode, isTelegramGroupOpened = this[IS_TELEGRAM_GROUP_OPENED] ?: default.isTelegramGroupOpened, initialOcrMode = this[INITIAL_OCR_MODE] ?: default.initialOcrMode, spotHealMode = this[SPOT_HEAL_MODE] ?: default.spotHealMode, snowfallMode = this[SNOWFALL_MODE]?.let { SnowfallMode.entries[it] } ?: default.snowfallMode, defaultImageFormat = this[DEFAULT_IMAGE_FORMAT].let { title -> if (title.isNullOrBlank()) { null } else { ImageFormat.entries.find { it.title == title } ?: default.defaultImageFormat } }, defaultQuality = this[DEFAULT_QUALITY]?.let { jsonParser.fromJson( json = it, type = Quality::class.java ) } ?: default.defaultQuality, shapesType = this[SHAPES_TYPE]?.let { jsonParser.fromJson( json = it, type = ShapeType::class.java ) } ?: default.shapesType, filenamePattern = this[FILENAME_PATTERN]?.takeIf { it.isNotBlank() } ?: default.filenamePattern, filenameBehavior = this[FILENAME_BEHAVIOR]?.let { jsonParser.fromJson( json = it, type = FilenameBehavior::class.java ) } ?: default.filenameBehavior, flingType = this[FLING_TYPE]?.let { FlingType.entries[it] } ?: default.flingType, hiddenForShareScreens = this[HIDDEN_FOR_SHARE_SCREENS]?.split( "/" )?.mapNotNull { it.toIntOrNull() } ?: default.hiddenForShareScreens, keepDateTime = this[KEEP_DATE_TIME] ?: default.keepDateTime, enableBackgroundColorForAlphaFormats = this[ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS] ?: default.enableBackgroundColorForAlphaFormats ) private fun Preferences.toDefaultImageScaleMode(default: SettingsState): ImageScaleMode { val scaleMode = this[IMAGE_SCALE_MODE]?.let { ImageScaleMode.fromInt(it) } ?: default.defaultImageScaleMode val scaleColorSpace = this[IMAGE_SCALE_COLOR_SPACE]?.let { ScaleColorSpace.fromOrdinal(it) } ?: default.defaultImageScaleMode.scaleColorSpace return scaleMode.copy(scaleColorSpace) } private fun Set?.toSettingGroupsInitialVisibility( default: SettingsState ): Map = this?.associate { key -> key.split(":").let { it[0].toInt() to it[1].toBoolean() } } ?: default.settingGroupsInitialVisibility private fun Set?.toCustomFonts(): List = this?.map { val split = it.split(":") DomainFontFamily.Custom( name = split[0], filePath = split[1] ) } ?: emptyList() ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/data/keys/SettingKeys.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.data.keys import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.doublePreferencesKey import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringSetPreferencesKey internal val SAVE_FOLDER_URI = stringPreferencesKey("saveFolder") internal val NIGHT_MODE = intPreferencesKey("nightMode") internal val DYNAMIC_COLORS = booleanPreferencesKey("dynamicColors") internal val ALLOW_IMAGE_MONET = booleanPreferencesKey("imageMonet") internal val AMOLED_MODE = booleanPreferencesKey("amoledMode") internal val APP_COLOR_TUPLE = stringPreferencesKey("appColorTuple") internal val BORDER_WIDTH = floatPreferencesKey("borderWidth") internal val PRESETS = stringPreferencesKey("presets") internal val COLOR_TUPLES = stringPreferencesKey("color_tuples") internal val FAB_ALIGNMENT = intPreferencesKey("alignment") internal val SHOW_UPDATE_DIALOG = booleanPreferencesKey("showDialog") internal val FILENAME_PREFIX = stringPreferencesKey("filename") internal val SELECTED_EMOJI_INDEX = intPreferencesKey("emoji") internal val ADD_SIZE_TO_FILENAME = booleanPreferencesKey("add_size") internal val IMAGE_PICKER_MODE = intPreferencesKey("picker_mode") internal val SCREEN_ORDER = stringPreferencesKey("order") internal val EMOJI_COUNT = intPreferencesKey("em_count") internal val ADD_ORIGINAL_NAME_TO_FILENAME = booleanPreferencesKey("ADD_ORIGINAL_NAME") internal val ADD_SEQ_NUM_TO_FILENAME = booleanPreferencesKey("ADD_SEQ_NUM") internal val AUTO_CACHE_CLEAR = booleanPreferencesKey("auto_clear") internal val GROUP_OPTIONS_BY_TYPE = booleanPreferencesKey("group_options") internal val SELECTED_FONT = stringPreferencesKey("SELECTED_FONT") internal val FONT_SCALE = floatPreferencesKey("font_scale") internal val ALLOW_CRASHLYTICS = booleanPreferencesKey("allow_crashlytics") internal val ALLOW_ANALYTICS = booleanPreferencesKey("allow_analytics") internal val ALLOW_BETAS = booleanPreferencesKey("allow_betas") internal val DRAW_CONTAINER_SHADOWS = booleanPreferencesKey("ALLOW_SHADOWS_INSTEAD_OF_BORDERS") internal val APP_OPEN_COUNT = intPreferencesKey("APP_OPEN_COUNT") internal val LOCK_DRAW_ORIENTATION = booleanPreferencesKey("LOCK_DRAW_ORIENTATION") internal val THEME_CONTRAST_LEVEL = doublePreferencesKey("THEME_CONTRAST_LEVEL") internal val THEME_STYLE = intPreferencesKey("THEME_STYLE") internal val INVERT_THEME = booleanPreferencesKey("INVERT_THEME") internal val SCREEN_SEARCH_ENABLED = booleanPreferencesKey("SCREEN_SEARCH_ENABLED") internal val DRAW_BUTTON_SHADOWS = booleanPreferencesKey("DRAW_BUTTON_SHADOWS") internal val DRAW_FAB_SHADOWS = booleanPreferencesKey("DRAW_FAB_SHADOWS") internal val DRAW_SWITCH_SHADOWS = booleanPreferencesKey("DRAW_SWITCH_SHADOWS") internal val DRAW_SLIDER_SHADOWS = booleanPreferencesKey("DRAW_SLIDER_SHADOWS") internal val DRAW_APPBAR_SHADOWS = booleanPreferencesKey("DRAW_APPBAR_SHADOWS") internal val COPY_TO_CLIPBOARD_MODE = intPreferencesKey("COPY_TO_CLIPBOARD_MODE") internal val VIBRATION_STRENGTH = intPreferencesKey("VIBRATION_STRENGTH") internal val FILENAME_SUFFIX = stringPreferencesKey("FILENAME_SUFFIX") internal val IMAGE_SCALE_MODE = intPreferencesKey("IMAGE_SCALE_MODE") internal val MAGNIFIER_ENABLED = booleanPreferencesKey("MAGNIFIER_ENABLED") internal val EXIF_WIDGET_INITIAL_STATE = booleanPreferencesKey("EXIF_WIDGET_INITIAL_STATE") internal val INITIAL_OCR_CODES = stringPreferencesKey("INITIAL_OCR_CODES") internal val SCREENS_WITH_BRIGHTNESS_ENFORCEMENT = stringPreferencesKey("SCREENS_WITH_BRIGHTNESS_ENFORCEMENT") internal val CONFETTI_ENABLED = booleanPreferencesKey("CONFETTI_ENABLED") internal val SECURE_MODE = booleanPreferencesKey("SECURE_MODE") internal val USE_RANDOM_EMOJIS = booleanPreferencesKey("USE_RANDOM_EMOJIS") internal val ICON_SHAPE = intPreferencesKey("ICON_SHAPE") internal val USE_EMOJI_AS_PRIMARY_COLOR = booleanPreferencesKey("USE_EMOJI_AS_PRIMARY_COLOR") internal val DRAG_HANDLE_WIDTH = intPreferencesKey("DRAG_HANDLE_WIDTH") internal val CONFETTI_TYPE = intPreferencesKey("CONFETTI_TYPE") internal val ALLOW_AUTO_PASTE = booleanPreferencesKey("ALLOW_AUTO_PASTE") internal val CONFETTI_HARMONIZER = intPreferencesKey("CONFETTI_HARMONIZER") internal val CONFETTI_HARMONIZATION_LEVEL = floatPreferencesKey("CONFETTI_HARMONIZATION_LEVEL") internal val SKIP_IMAGE_PICKING = booleanPreferencesKey("SKIP_IMAGE_PICKER") internal val GENERATE_PREVIEWS = booleanPreferencesKey("GENERATE_PREVIEWS") internal val SHOW_SETTINGS_IN_LANDSCAPE = booleanPreferencesKey("SHOW_SETTINGS_IN_LANDSCAPE") internal val USE_FULLSCREEN_SETTINGS = booleanPreferencesKey("USE_FULLSCREEN_SETTINGS") internal val SWITCH_TYPE = intPreferencesKey("SWITCH_TYPE") internal val DEFAULT_DRAW_LINE_WIDTH = floatPreferencesKey("DEFAULT_DRAW_LINE_WIDTH") internal val ONE_TIME_SAVE_LOCATIONS = stringPreferencesKey("ONE_TIME_SAVE_LOCATIONS") internal val OPEN_EDIT_INSTEAD_OF_PREVIEW = booleanPreferencesKey("OPEN_EDIT_INSTEAD_OF_PREVIEW") internal val CAN_ENTER_PRESETS_BY_TEXT_FIELD = booleanPreferencesKey("CAN_ENTER_PRESETS_BY_TEXT_FIELD") internal val DONATE_DIALOG_OPEN_COUNT = intPreferencesKey("DONATE_DIALOG_OPEN_COUNT") internal val COLOR_BLIND_TYPE = intPreferencesKey("COLOR_BLIND_TYPE") internal val FAVORITE_SCREENS = stringPreferencesKey("FAVORITE_SCREENS") internal val IS_LINK_PREVIEW_ENABLED = booleanPreferencesKey("IS_LINK_PREVIEW_ENABLED") internal val DEFAULT_DRAW_COLOR = intPreferencesKey("DEFAULT_DRAW_COLOR") internal val DEFAULT_DRAW_PATH_MODE = intPreferencesKey("DEFAULT_DRAW_PATH_MODE") internal val ADD_TIMESTAMP_TO_FILENAME = booleanPreferencesKey("ADD_TIMESTAMP_TO_FILENAME") internal val USE_FORMATTED_TIMESTAMP = booleanPreferencesKey("USE_FORMATTED_TIMESTAMP") internal val IS_TELEGRAM_GROUP_OPENED = booleanPreferencesKey("IS_TELEGRAM_GROUP_OPENED") internal val DEFAULT_RESIZE_TYPE = intPreferencesKey("DEFAULT_RESIZE_TYPE") internal val SYSTEM_BARS_VISIBILITY = intPreferencesKey("SYSTEM_BARS_VISIBILITY") internal val IS_SYSTEM_BARS_VISIBLE_BY_SWIPE = booleanPreferencesKey("IS_SYSTEM_BARS_VISIBLE_BY_SWIPE") internal val INITIAL_OCR_MODE = intPreferencesKey("INITIAL_OCR_MODE") internal val USE_COMPACT_SELECTORS_LAYOUT = booleanPreferencesKey("USE_COMPACT_SELECTORS_LAYOUT") internal val MAIN_SCREEN_TITLE = stringPreferencesKey("MAIN_SCREEN_TITLE") internal val SLIDER_TYPE = intPreferencesKey("SLIDER_TYPE") internal val CENTER_ALIGN_DIALOG_BUTTONS = booleanPreferencesKey("CENTER_ALIGN_DIALOG_BUTTONS") internal val FAST_SETTINGS_SIDE = intPreferencesKey("FAST_SETTINGS_SIDE") internal val SETTINGS_GROUP_VISIBILITY = stringSetPreferencesKey("SETTINGS_GROUP_VISIBILITY") internal val CUSTOM_FONTS = stringSetPreferencesKey("CUSTOM_FONTS") internal val ENABLE_TOOL_EXIT_CONFIRMATION = booleanPreferencesKey("ENABLE_TOOL_EXIT_CONFIRMATION") internal val RECENT_COLORS = stringSetPreferencesKey("RECENT_COLORS") internal val FAVORITE_COLORS = stringPreferencesKey("FAVORITE_COLORS_KEY") internal val BACKGROUND_COLOR_FOR_NA_FORMATS = intPreferencesKey("BACKGROUND_COLOR_FOR_NA_FORMATS") internal val IMAGE_SCALE_COLOR_SPACE = intPreferencesKey("IMAGE_SCALE_COLOR_SPACE") internal val ADD_PRESET_TO_FILENAME = booleanPreferencesKey("ADD_PRESET_TO_FILENAME") internal val ADD_SCALE_MODE_TO_FILENAME = booleanPreferencesKey("ADD_SCALE_MODE_TO_FILENAME") internal val ALLOW_SKIP_IF_LARGER = booleanPreferencesKey("ALLOW_SKIP_IF_LARGER") internal val ASCII_CUSTOM_GRADIENTS = stringSetPreferencesKey("ASCII_CUSTOM_GRADIENTS") internal val IS_LAUNCHER_MODE = booleanPreferencesKey("IS_LAUNCHER_MODE") internal val SPOT_HEAL_MODE = intPreferencesKey("SPOT_HEAL_MODE") internal val SNOWFALL_MODE = intPreferencesKey("SNOWFALL_MODE") internal val DEFAULT_QUALITY = stringPreferencesKey("DEFAULT_QUALITY") internal val DEFAULT_IMAGE_FORMAT = stringPreferencesKey("DEFAULT_IMAGE_FORMAT") internal val SHAPES_TYPE = stringPreferencesKey("SHAPES_TYPE_NEW") internal val FILENAME_PATTERN = stringPreferencesKey("FILENAME_PATTERN") internal val FILENAME_BEHAVIOR = stringPreferencesKey("FILENAME_BEHAVIOR") internal val FLING_TYPE = intPreferencesKey("FLING_TYPE") internal val HIDDEN_FOR_SHARE_SCREENS = stringPreferencesKey("HIDDEN_FOR_SHARE_SCREENS") internal val KEEP_DATE_TIME = booleanPreferencesKey("KEEP_DATE_TIME") internal val ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS = booleanPreferencesKey("ENABLE_BACKGROUND_COLOR_FOR_ALPHA_FORMATS") ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/di/SettingsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.di import com.t8rin.imagetoolbox.core.settings.domain.SettingsInteractor import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.feature.settings.data.AndroidSettingsManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface SettingsModule { @Singleton @Binds fun provideSettingsManager( repository: AndroidSettingsManager ): SettingsManager @Singleton @Binds fun provideSettingsProvider( repository: SettingsManager ): SettingsProvider @Singleton @Binds fun provideSettingsInteractor( repository: SettingsManager ): SettingsInteractor } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/SettingsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("KotlinConstantConditions") package com.t8rin.imagetoolbox.feature.settings.presentation import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.SearchOff import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.SettingsGroup import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.other.SearchBar import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.text.isKeyboardVisibleAsState import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.settings.presentation.components.SearchableSettingItem import com.t8rin.imagetoolbox.feature.settings.presentation.components.SettingGroupItem import com.t8rin.imagetoolbox.feature.settings.presentation.components.SettingItem import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent @Composable fun SettingsContent( component: SettingsComponent, disableBottomInsets: Boolean = false, appBarNavigationIcon: (@Composable (Boolean, () -> Unit) -> Unit)? = null ) { val isStandaloneScreen = appBarNavigationIcon == null val isUpdateAvailable by component.isUpdateAvailable.subscribeAsState() val settingsState = LocalSettingsState.current val layoutDirection = LocalLayoutDirection.current val initialSettingGroups = SettingsGroup.entries val searchKeyword = component.searchKeyword var showSearch by rememberSaveable { mutableStateOf(false) } val settings = component.filteredSettings val loading = component.isFilteringSettings val padding = WindowInsets.navigationBars .union(WindowInsets.displayCutout) .let { insets -> if (disableBottomInsets) { insets.only( WindowInsetsSides.Horizontal + WindowInsetsSides.Top ) } else { insets } } .asPaddingValues() .run { PaddingValues( top = 8.dp, bottom = calculateBottomPadding() + 8.dp, end = calculateEndPadding(layoutDirection) + 8.dp, start = if (isStandaloneScreen) calculateStartPadding(layoutDirection) + 8.dp else 8.dp ) } val focus = LocalFocusManager.current val isKeyboardVisible by isKeyboardVisibleAsState() DisposableEffect(Unit) { onDispose { if (!isKeyboardVisible) { focus.clearFocus() } } } val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( modifier = if (isStandaloneScreen) { Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surface) .nestedScroll( scrollBehavior.nestedScrollConnection ) } else Modifier, topBar = { EnhancedTopAppBar( type = if (isStandaloneScreen) EnhancedTopAppBarType.Large else EnhancedTopAppBarType.Normal, title = { AnimatedContent( targetState = showSearch ) { searching -> if (!searching) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.marquee() ) { Text( text = stringResource(R.string.settings), style = if (!isStandaloneScreen) { MaterialTheme.typography.titleLarge } else LocalTextStyle.current ) if (isStandaloneScreen) { Spacer(modifier = Modifier.width(8.dp)) TopAppBarEmoji() } } } else { BackHandler { component.updateSearchKeyword("") showSearch = false } SearchBar( searchString = searchKeyword, onValueChange = component::updateSearchKeyword ) } } }, actions = { AnimatedContent( targetState = showSearch to searchKeyword.isNotEmpty(), transitionSpec = { fadeIn() + scaleIn() togetherWith fadeOut() + scaleOut() } ) { (searching, hasSearchKey) -> EnhancedIconButton( onClick = { if (!showSearch) { showSearch = true } else { component.updateSearchKeyword("") } } ) { if (searching && hasSearchKey) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } else if (!searching) { Icon( imageVector = Icons.Rounded.Search, contentDescription = stringResource(R.string.search_here) ) } } } }, navigationIcon = { if (appBarNavigationIcon != null) { appBarNavigationIcon(showSearch) { showSearch = false component.updateSearchKeyword("") } } else if (component.onGoBack != null || showSearch) { EnhancedIconButton( onClick = { if (showSearch) { showSearch = false component.updateSearchKeyword("") } else { component.onGoBack?.invoke() } }, containerColor = Color.Transparent ) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = stringResource(R.string.exit) ) } } }, windowInsets = if (isStandaloneScreen) { EnhancedTopAppBarDefaults.windowInsets } else { EnhancedTopAppBarDefaults.windowInsets.only( WindowInsetsSides.End + WindowInsetsSides.Top ) }, colors = if (isStandaloneScreen) { EnhancedTopAppBarDefaults.colors() } else { EnhancedTopAppBarDefaults.colors( containerColor = MaterialTheme.colorScheme.surfaceContainerHigh.blend( color = MaterialTheme.colorScheme.surfaceContainer, fraction = 0.5f ) ) }, scrollBehavior = if (isStandaloneScreen) { scrollBehavior } else null ) }, contentWindowInsets = WindowInsets() ) { contentPadding -> Box( modifier = Modifier.padding(contentPadding) ) { AnimatedContent( targetState = settings, modifier = Modifier .fillMaxSize() .clearFocusOnTap(), transitionSpec = { fadeIn() + scaleIn(initialScale = 0.95f) togetherWith fadeOut() + scaleOut( targetScale = 0.8f ) } ) { settingsAnimated -> val oneColumn = LocalScreenSize.current.width < 600.dp val spacing = if (searchKeyword.isNotEmpty()) 4.dp else if (isStandaloneScreen) 8.dp else 2.dp if (settingsAnimated == null) { LazyVerticalStaggeredGrid( contentPadding = padding, columns = StaggeredGridCells.Adaptive(300.dp), verticalItemSpacing = spacing, horizontalArrangement = Arrangement.spacedBy(spacing), flingBehavior = enhancedFlingBehavior() ) { items( items = initialSettingGroups, key = { it.id } ) { group -> BoxAnimatedVisibility( visible = if (group is SettingsGroup.Shadows) { settingsState.borderWidth <= 0.dp } else true ) { if (isStandaloneScreen) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = if (!oneColumn) { Modifier.container( shape = ShapeDefaults.large, resultPadding = 0.dp, color = MaterialTheme.colorScheme.surfaceContainerLowest ) } else Modifier ) { TitleItem( modifier = Modifier.padding( start = 8.dp, end = 8.dp, top = 12.dp, bottom = 12.dp ), icon = group.icon, text = stringResource(group.titleId), iconContainerColor = takeColorFromScheme { primary.blend(tertiary, 0.5f) }, iconContentColor = takeColorFromScheme { onPrimary.blend(onTertiary, 0.5f) } ) Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(bottom = 8.dp) ) { group.settingsList.forEach { setting -> SettingItem( setting = setting, component = component, isUpdateAvailable = isUpdateAvailable, containerColor = MaterialTheme.colorScheme.surfaceContainerLow, onNavigateToEasterEgg = { component.onNavigate(Screen.EasterEgg) }, onNavigateToSettings = { component.onNavigate(Screen.Settings()) }, onNavigateToLibrariesInfo = { component.onNavigate(Screen.LibrariesInfo) } ) } } } } else { SettingGroupItem( groupKey = group.id, icon = group.icon, text = stringResource(group.titleId), initialState = group.initialState ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { group.settingsList.forEach { setting -> SettingItem( setting = setting, component = component, isUpdateAvailable = isUpdateAvailable, onNavigateToEasterEgg = { component.onNavigate(Screen.EasterEgg) }, onNavigateToSettings = { component.onNavigate(Screen.Settings()) }, onNavigateToLibrariesInfo = { component.onNavigate(Screen.LibrariesInfo) } ) } } } } } } } } else if (settingsAnimated.isNotEmpty()) { LazyVerticalStaggeredGrid( contentPadding = padding, columns = StaggeredGridCells.Adaptive(300.dp), verticalItemSpacing = spacing, horizontalArrangement = Arrangement.spacedBy(spacing), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = settingsAnimated, key = { _, v -> v.hashCode() } ) { index, (group, setting) -> SearchableSettingItem( shape = ShapeDefaults.byIndex( index = if (oneColumn) index else -1, size = settingsAnimated.size ), group = group, setting = setting, component = component, isUpdateAvailable = isUpdateAvailable, onNavigateToEasterEgg = { component.onNavigate(Screen.EasterEgg) }, onNavigateToSettings = { component.onNavigate(Screen.Settings()) }, onNavigateToLibrariesInfo = { component.onNavigate(Screen.LibrariesInfo) } ) } } } else { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Spacer(Modifier.weight(1f)) Text( text = stringResource(R.string.nothing_found_by_search), fontSize = 18.sp, textAlign = TextAlign.Center, modifier = Modifier.padding( start = 24.dp, end = 24.dp, top = 8.dp, bottom = 8.dp ) ) Icon( imageVector = Icons.Rounded.SearchOff, contentDescription = null, modifier = Modifier .weight(2f) .sizeIn(maxHeight = 140.dp, maxWidth = 140.dp) .fillMaxSize() ) Spacer(Modifier.weight(1f)) } } } BoxAnimatedVisibility( visible = loading, modifier = Modifier.fillMaxSize(), enter = fadeIn(), exit = fadeOut() ) { Box( modifier = Modifier .fillMaxSize() .background( MaterialTheme.colorScheme.surfaceDim.copy(0.7f) ), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddFileSizeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ScaleUnbalanced import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AddFileSizeSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, enabled = settingsState.filenameBehavior is FilenameBehavior.None, startIcon = Icons.Outlined.ScaleUnbalanced, onClick = { onClick() }, title = stringResource(R.string.add_file_size), subtitle = stringResource(R.string.add_file_size_sub), checked = settingsState.addSizeInFilename ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddImageScaleModeToFilenameSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FitScreen import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AddImageScaleModeToFilenameSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.Companion.padding(horizontal = 8.dp), ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, enabled = settingsState.filenameBehavior is FilenameBehavior.None, title = stringResource(R.string.add_image_scale_mode_to_filename), subtitle = stringResource(R.string.add_image_scale_mode_to_filename_sub), checked = settingsState.addImageScaleModeInfoToFilename, startIcon = Icons.Outlined.FitScreen ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddOriginalFilenameSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Difference import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AddOriginalFilenameSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, enabled = settingsState.filenameBehavior is FilenameBehavior.None, modifier = modifier, startIcon = Icons.Outlined.Difference, onClick = { onClick() }, title = stringResource(R.string.add_original_filename), subtitle = stringResource(R.string.add_original_filename_sub), checked = settingsState.addOriginalFilename ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddPresetToFilenameSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.LabelPercent import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AddPresetToFilenameSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.Companion.padding(horizontal = 8.dp), ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, enabled = settingsState.filenameBehavior is FilenameBehavior.None, title = stringResource(R.string.add_preset_to_filename), subtitle = stringResource(R.string.add_preset_to_filename_sub), checked = settingsState.addPresetInfoToFilename, startIcon = Icons.Outlined.LabelPercent ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AddTimestampToFilenameSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Timer import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AddTimestampToFilenameSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp), ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, enabled = settingsState.filenameBehavior is FilenameBehavior.None, title = stringResource(R.string.add_timestamp), subtitle = stringResource(R.string.add_timestamp_sub), checked = settingsState.addTimestampToFilename, startIcon = Icons.Outlined.Timer ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowAutoClipboardPasteSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ContentPasteGo import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AllowAutoClipboardPasteSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.auto_paste), subtitle = stringResource(R.string.auto_paste_sub), checked = settingsState.allowAutoClipboardPaste, onClick = { onClick() }, startIcon = Icons.Outlined.ContentPasteGo ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowBetasSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Beta import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AllowBetasSettingItem( onClick: (Boolean) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.allow_betas), subtitle = stringResource(R.string.allow_betas_sub), checked = settingsState.allowBetas, onClick = onClick, startIcon = Icons.Rounded.Beta ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowImageMonetSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.WaterDrop import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.utils.getString @Composable fun AllowImageMonetSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, enabled = !settingsState.isDynamicColors, onDisabledClick = { AppToastHost.showToast( icon = Icons.Outlined.WaterDrop, message = getString(R.string.cannot_use_monet_while_dynamic_colors_applied) ) }, title = stringResource(R.string.allow_image_monet), subtitle = stringResource(R.string.allow_image_monet_sub), checked = settingsState.allowChangeColorByImage, onClick = { onClick() }, startIcon = Icons.Outlined.WaterDrop ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AllowSkipIfLargerSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.NextPlan import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AllowSkipIfLargerSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.Companion.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.allow_skip_if_larger), subtitle = stringResource(R.string.allow_skip_if_larger_sub), checked = settingsState.allowSkipIfLarger, onClick = { onClick() }, startIcon = Icons.Outlined.NextPlan ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AmoledModeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Brightness4 import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AmoledModeSettingItem( onClick: () -> Unit, modifier: Modifier = Modifier.padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.center ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, startIcon = Icons.Outlined.Brightness4, title = stringResource(R.string.amoled_mode), subtitle = stringResource(R.string.amoled_mode_sub), checked = settingsState.isAmoledMode, onClick = { onClick() } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AnalyticsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Analytics import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AnalyticsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, title = stringResource(R.string.analytics), subtitle = stringResource(id = R.string.analytics_sub), startIcon = Icons.Rounded.Analytics, checked = settingsState.allowCollectAnalytics, onClick = { onClick() } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AppBarShadowsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.HorizontalSplit import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AppBarShadowsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, enabled = settingsState.borderWidth <= 0.dp, shape = shape, title = stringResource(R.string.app_bars_shadow), subtitle = stringResource(R.string.app_bars_shadow_sub), checked = settingsState.drawAppBarShadows, onClick = { onClick() }, startIcon = Icons.Outlined.HorizontalSplit ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AuthorSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Forum import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.AuthorLinksSheet @Composable fun AuthorSettingItem( shape: Shape = ShapeDefaults.top ) { var showAuthorSheet by rememberSaveable { mutableStateOf(false) } PreferenceRow( modifier = Modifier.padding(horizontal = 8.dp), color = MaterialTheme.colorScheme.secondaryContainer, title = stringResource(R.string.app_developer), subtitle = stringResource(R.string.app_developer_nick), shape = shape, startIcon = Icons.Outlined.Forum, endContent = { Picture( model = painterResource(id = R.drawable.avatar), modifier = Modifier .padding(end = 8.dp) .size(64.dp) .container( shape = MaterialStarShape, resultPadding = 0.dp ), contentDescription = null ) }, onClick = { showAuthorSheet = true } ) AuthorLinksSheet( visible = showAuthorSheet, onDismiss = { showAuthorSheet = false } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCacheClearSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AutoDelete import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AutoCacheClearSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, title = stringResource(R.string.auto_cache_clearing), subtitle = stringResource(R.string.auto_cache_clearing_sub), checked = settingsState.clearCacheOnLaunch, startIcon = Icons.Outlined.AutoDelete, onClick = { onClick() } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoCheckUpdatesSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AutoCheckUpdatesSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, title = stringResource(R.string.check_updates), subtitle = stringResource(R.string.check_updates_sub), checked = settingsState.showUpdateDialogOnStartup, onClick = { onClick() }, startIcon = Icons.Outlined.NewReleases ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardOnlyClipSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.SaveAs import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AutoPinClipboardOnlyClipSettingItem( onClick: (Boolean) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, enabled = settingsState.copyToClipboardMode is CopyToClipboardMode.Enabled, shape = shape, title = stringResource(R.string.only_clip), subtitle = stringResource(R.string.only_clip_sub), checked = settingsState.copyToClipboardMode is CopyToClipboardMode.Enabled.WithoutSaving, onClick = onClick, startIcon = Icons.Outlined.SaveAs ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/AutoPinClipboardSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PushPin import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun AutoPinClipboardSettingItem( onClick: (Boolean) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.auto_pin), subtitle = stringResource(R.string.auto_pin_sub), checked = settingsState.copyToClipboardMode is CopyToClipboardMode.Enabled, onClick = onClick, startIcon = Icons.Outlined.PushPin ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BackupSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import android.net.Uri import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.UploadFile import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun BackupSettingItem( onCreateBackupFilename: () -> String, onCreateBackup: (Uri) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val backupSavingLauncher = rememberFileCreator(onSuccess = onCreateBackup) PreferenceItem( onClick = { backupSavingLauncher.make(onCreateBackupFilename()) }, shape = shape, modifier = modifier, title = stringResource(R.string.backup), subtitle = stringResource(R.string.backup_sub), startIcon = Icons.Outlined.UploadFile ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BorderThicknessSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.BorderStyle import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlinx.coroutines.delay import kotlin.math.roundToInt @Composable fun BorderThicknessSettingItem( onValueChange: (Float) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var value by remember { mutableFloatStateOf(settingsState.borderWidth.value.coerceAtLeast(0f)) } LaunchedEffect(value) { delay(500) onValueChange(value) } EnhancedSliderItem( modifier = modifier, shape = shape, valueSuffix = " Dp", value = value, title = stringResource(R.string.border_thickness), icon = Icons.Outlined.BorderStyle, onValueChange = { value = (it * 10).roundToInt() / 10f }, internalStateTransformation = { (it * 10).roundToInt() / 10f }, valueRange = 0f..1.5f, steps = 14 ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/BrightnessEnforcementSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.BrightnessHigh import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastAny import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCheckbox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun BrightnessEnforcementSettingItem( onValueChange: (Screen) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val settingsScreenList = settingsState.screenListWithMaxBrightnessEnforcement val screenList by remember(settingsScreenList) { derivedStateOf { settingsScreenList.mapNotNull { Screen.entries.find { s -> s.id == it } } } } var showPickerSheet by rememberSaveable { mutableStateOf(false) } val context = LocalResourceManager.current val subtitle by remember(screenList, context) { derivedStateOf { screenList.joinToString(separator = ", ") { context.getString(it.title) }.ifEmpty { context.getString(R.string.disabled) } } } PreferenceItem( shape = shape, modifier = modifier, onClick = { showPickerSheet = true }, startIcon = Icons.Outlined.BrightnessHigh, title = stringResource(R.string.brightness_enforcement), subtitle = subtitle, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showPickerSheet, onDismiss = { showPickerSheet = it }, title = { TitleItem( text = stringResource(R.string.brightness), icon = Icons.Outlined.BrightnessHigh ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showPickerSheet = false } ) { AutoSizeText(stringResource(R.string.close)) } }, sheetContent = { Box { LazyColumn( contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior() ) { items( items = Screen.entries, key = { it.id } ) { screen -> val checked by remember(screen, screenList) { derivedStateOf { screenList.fastAny { it::class.isInstance(screen) } } } PreferenceItemOverload( modifier = Modifier.fillMaxWidth(), title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), startIcon = { screen.icon?.let { Icon( imageVector = it, contentDescription = null ) } }, endIcon = { EnhancedCheckbox( checked = checked, onCheckedChange = { onValueChange(screen) } ) }, onClick = { onValueChange(screen) } ) } } } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ButtonShadowsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Gamepad import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ButtonShadowsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, enabled = settingsState.borderWidth <= 0.dp, shape = shape, title = stringResource(R.string.buttons_shadow), subtitle = stringResource(R.string.buttons_shadow_sub), checked = settingsState.drawButtonShadows, onClick = { onClick() }, startIcon = Icons.Outlined.Gamepad ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CanEnterPresetsByTextFieldSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.WrapText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun CanEnterPresetsByTextFieldSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.allow_enter_by_text_field), subtitle = stringResource(R.string.allow_enter_by_text_field_sub), checked = settingsState.canEnterPresetsByTextField, onClick = { onClick() }, startIcon = Icons.AutoMirrored.Outlined.WrapText ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CenterAlignDialogButtonsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.PictureInPictureCenter import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun CenterAlignDialogButtonsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, title = stringResource(R.string.center_align_dialog_buttons), subtitle = stringResource(R.string.center_align_dialog_buttons_sub), checked = settingsState.isCenterAlignDialogButtons, startIcon = Icons.Outlined.PictureInPictureCenter ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeFontSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import android.net.Uri import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FontFamily import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.PickFontFamilySheet @Composable fun ChangeFontSettingItem( onValueChange: (UiFontFamily) -> Unit, onAddFont: (Uri) -> Unit, onRemoveFont: (UiFontFamily.Custom) -> Unit, onExportFonts: (Uri) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showFontSheet by rememberSaveable { mutableStateOf(false) } val exportFontsLauncher = rememberFileCreator( mimeType = MimeType.Zip, onSuccess = onExportFonts ) PreferenceItem( shape = shape, onClick = { showFontSheet = true }, title = stringResource(R.string.font), subtitle = settingsState.font.name ?: stringResource(R.string.system), startIcon = Icons.Rounded.FontFamily, endIcon = Icons.Rounded.MiniEdit, modifier = modifier ) PickFontFamilySheet( visible = showFontSheet, onDismiss = { showFontSheet = false }, onFontSelected = onValueChange, onAddFont = onAddFont, onRemoveFont = onRemoveFont, onExportFonts = { exportFontsLauncher.make("FONTS_EXPORT_${timestamp()}.zip") } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChangeLanguageSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import android.app.LocaleManager import android.content.Context import android.content.Intent import android.content.res.Resources import android.os.Build import android.os.LocaleList import android.provider.Settings import androidx.appcompat.app.AppCompatDelegate import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Language import androidx.compose.material.icons.rounded.Language import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.core.os.LocaleListCompat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getCurrentLocaleString import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getDisplayName import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.getLanguages import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedRadioButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.logger.makeLog import java.util.Locale @Composable fun ChangeLanguageSettingItem( modifier: Modifier = Modifier.padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.top ) { val context = LocalContext.current var showEmbeddedLanguagePicker by rememberSaveable { mutableStateOf(false) } Column(Modifier.animateContentSizeNoClip()) { PreferenceItem( shape = shape, modifier = modifier.padding(bottom = 1.dp), title = stringResource(R.string.language), subtitle = remember { context.getCurrentLocaleString() }, startIcon = Icons.Outlined.Language, endIcon = Icons.Rounded.MiniEdit, onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { try { context.startActivity( Intent( Settings.ACTION_APP_LOCALE_SETTINGS, "package:${context.packageName}".toUri() ) ) } catch (e: Throwable) { e.makeLog("LocaleSelect") showEmbeddedLanguagePicker = true } } else { showEmbeddedLanguagePicker = true } } ) } PickLanguageSheet( entries = remember { context.getLanguages() }, selected = remember { context.getCurrentLocaleString() }, onSelect = { tag -> context.setGlobalLocale( tag.takeIf { it.isNotBlank() }?.let(Locale::forLanguageTag) ) }, visible = showEmbeddedLanguagePicker, onDismiss = { showEmbeddedLanguagePicker = false } ) } @Composable private fun PickLanguageSheet( entries: Map, selected: String, onSelect: (String) -> Unit, visible: Boolean, onDismiss: () -> Unit ) { EnhancedModalBottomSheet( onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.language), icon = Icons.Rounded.Language ) }, sheetContent = { Box { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(12.dp) ) { entries.entries.forEachIndexed { index, locale -> val isSelected = selected == locale.value || (selected.isEmpty() && index == 0) PreferenceItemOverload( modifier = Modifier.fillMaxWidth(), onClick = { onSelect(locale.key) }, resultModifier = Modifier.padding( start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp ), containerColor = animateColorAsState( if (isSelected) MaterialTheme .colorScheme .secondaryContainer else EnhancedBottomSheetDefaults.contentContainerColor ).value, shape = ShapeDefaults.byIndex( index = index, size = entries.size ), endIcon = { EnhancedRadioButton( selected = isSelected, onClick = { onSelect(locale.key) } ) }, title = locale.value, subtitle = remember(locale) { getDisplayName( lang = locale.key, useDefaultLocale = true ) }.takeIf { locale.value != it } ) } } } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss ) { AutoSizeText(stringResource(R.string.close)) } }, visible = visible ) } @Suppress("DEPRECATION") private fun Context.setGlobalLocale(locale: Locale?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { getSystemService(LocaleManager::class.java).applicationLocales = locale?.let { LocaleList.forLanguageTags(locale.toLanguageTag()) } ?: LocaleList.getEmptyLocaleList() } else { val newLocale = locale ?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Resources.getSystem().configuration.locales[0] } else { Resources.getSystem().configuration.locale } Locale.setDefault(newLocale) val configuration = resources.configuration configuration.setLocale(newLocale) resources.updateConfiguration( configuration, resources.displayMetrics ) } AppCompatDelegate.setApplicationLocales( locale?.let { LocaleListCompat.forLanguageTags(locale.toLanguageTag()) } ?: LocaleListCompat.getEmptyLocaleList() ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CheckUpdatesButtonSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SyncArrowDown import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun CheckUpdatesButtonSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { PreferenceItem( title = stringResource(R.string.check_for_updates), containerColor = MaterialTheme.colorScheme.mixedContainer, contentColor = MaterialTheme.colorScheme.onMixedContainer, modifier = modifier, shape = shape, startIcon = Icons.Outlined.SyncArrowDown, overrideIconShapeContentColor = true, onClick = onClick ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ChecksumAsFilenameSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Tag import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.utils.safeCast import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.drop @Composable fun ChecksumAsFilenameSettingItem( onValueChange: (HashingType?) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var checkedState by remember { mutableStateOf(settingsState.filenameBehavior is FilenameBehavior.Checksum) } LaunchedEffect(onValueChange, settingsState) { snapshotFlow { checkedState } .drop(1) .collectLatest { onValueChange( if (it) { settingsState.filenameBehavior.safeCast()?.hashingType ?: HashingType.entries.first() } else { null } ) } } PreferenceRowSwitch( shape = shape, modifier = modifier, enabled = settingsState.filenameBehavior is FilenameBehavior.None || settingsState.filenameBehavior is FilenameBehavior.Checksum, onClick = { checkedState = it }, title = stringResource(R.string.checksum_as_filename), subtitle = stringResource(R.string.checksum_as_filename_sub), checked = checkedState, startIcon = Icons.Rounded.Tag, additionalContent = { AnimatedVisibility( visible = checkedState, modifier = Modifier.fillMaxWidth() ) { DataSelector( modifier = Modifier .padding(top = 16.dp), value = settingsState.filenameBehavior.safeCast()?.hashingType ?: HashingType.entries.first(), onValueChange = onValueChange, entries = HashingType.entries, containerColor = MaterialTheme.colorScheme.surfaceContainerLowest, shape = shape, title = stringResource(R.string.algorithms), titleIcon = null, badgeContent = { Text(HashingType.entries.size.toString()) }, itemContentText = { it.name } ) } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ClearCacheSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Memory import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun ClearCacheSettingItem( onClearCache: ((String) -> Unit) -> Unit, value: String, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { var cache by remember(value) { mutableStateOf(value) } PreferenceItem( shape = shape, onClick = { onClearCache { cache = it } }, modifier = modifier, title = stringResource(R.string.cache_size), subtitle = stringResource(R.string.found_s, cache), endIcon = Icons.Outlined.Delete, startIcon = Icons.Outlined.Memory ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorBlindSchemeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.RemoveRedEye import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material.icons.rounded.RemoveRedEye import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.ColorBlindType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun ColorBlindSchemeSettingItem( onValueChange: (Int?) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var isShowSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( shape = shape, onClick = { isShowSheet = true }, title = stringResource(R.string.color_blind_scheme), subtitle = settingsState.colorBlindType?.localizedTitle ?: stringResource(R.string.disabled), startIcon = Icons.Outlined.RemoveRedEye, endIcon = Icons.Rounded.MiniEdit, modifier = modifier ) EnhancedModalBottomSheet( visible = isShowSheet, onDismiss = { isShowSheet = false }, title = { TitleItem( text = stringResource(R.string.color_blind_scheme), icon = Icons.Rounded.RemoveRedEye ) }, confirmButton = { EnhancedButton( onClick = { isShowSheet = false } ) { Text(text = stringResource(R.string.close)) } }, ) { val entries = remember { listOf(null) + ColorBlindType.entries } LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(250.dp), contentPadding = PaddingValues(16.dp), verticalItemSpacing = 8.dp, horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), flingBehavior = enhancedFlingBehavior() ) { items(entries) { type -> ColorBlindTypeSelectionItem( type = type, onClick = { onValueChange(type?.ordinal) } ) } } } } @Composable private fun ColorBlindTypeSelectionItem( type: ColorBlindType?, onClick: () -> Unit ) { val settingsState = LocalSettingsState.current val selected = settingsState.colorBlindType == type PreferenceItem( onClick = onClick, title = type?.localizedTitle ?: stringResource(R.string.not_use_color_blind_scheme), subtitle = type?.localizedDescription ?: stringResource(R.string.not_use_color_blind_scheme_sub), containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor }, modifier = Modifier .fillMaxWidth() .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme.colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = ShapeDefaults.default ), endIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else Icons.Rounded.RadioButtonUnchecked ) } private val ColorBlindType.localizedTitle: String @Composable get() = stringResource( when (this) { ColorBlindType.Protanomaly -> R.string.protonomaly ColorBlindType.Deuteranomaly -> R.string.deutaromaly ColorBlindType.Tritanomaly -> R.string.tritonomaly ColorBlindType.Protanopia -> R.string.protanopia ColorBlindType.Deuteranopia -> R.string.deutaronotopia ColorBlindType.Tritanopia -> R.string.tritanopia ColorBlindType.Achromatomaly -> R.string.achromatomaly ColorBlindType.Achromatopsia -> R.string.achromatopsia } ) private val ColorBlindType.localizedDescription: String @Composable get() = stringResource( when (this) { ColorBlindType.Protanomaly -> R.string.protanomaly_sub ColorBlindType.Deuteranomaly -> R.string.deuteranomaly_sub ColorBlindType.Tritanomaly -> R.string.tritanomaly_sub ColorBlindType.Protanopia -> R.string.protanopia_sub ColorBlindType.Deuteranopia -> R.string.deuteranopia_sub ColorBlindType.Tritanopia -> R.string.tritanopia_sub ColorBlindType.Achromatomaly -> R.string.achromatomaly_sub ColorBlindType.Achromatopsia -> R.string.achromatopsia_sub } ) ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ColorSchemeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.luminance import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.ColorTupleItem import com.t8rin.dynamic.theme.PaletteStyle import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.PaletteBox import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.rememberAppColorTuple import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.color_picker.AvailableColorTuplesSheet import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorTuplePicker import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.core.utils.getString @Composable fun ColorSchemeSettingItem( onToggleInvertColors: () -> Unit, onSetThemeStyle: (Int) -> Unit, onUpdateThemeContrast: (Float) -> Unit, onUpdateColorTuple: (ColorTuple) -> Unit, onUpdateColorTuples: (List) -> Unit, onToggleUseEmojiAsPrimaryColor: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp), ) { val settingsState = LocalSettingsState.current val enabled = !settingsState.isDynamicColors var showPickColorSheet by rememberSaveable { mutableStateOf(false) } PreferenceRow( modifier = modifier, enabled = enabled, shape = shape, title = stringResource(R.string.color_scheme), startIcon = Icons.Outlined.PaletteBox, subtitle = stringResource(R.string.pick_accent_color), onClick = { showPickColorSheet = true }, onDisabledClick = { AppToastHost.showToast( icon = Icons.Rounded.Palette, message = getString(R.string.cannot_change_palette_while_dynamic_colors_applied) ) }, endContent = { val colorTuple by remember( settingsState.themeStyle, settingsState.appColorTuple ) { derivedStateOf { if (settingsState.themeStyle == PaletteStyle.TonalSpot) { settingsState.appColorTuple } else settingsState.appColorTuple.run { copy(secondary = primary, tertiary = primary) } } } Box( modifier = Modifier .padding(end = 8.dp) .size(72.dp) .container( shape = MaterialStarShape, color = MaterialTheme.colorScheme .surfaceVariant .copy(alpha = 0.5f), borderColor = MaterialTheme.colorScheme.outlineVariant( 0.2f ), resultPadding = 5.dp ) ) { ColorTupleItem( modifier = Modifier .clip(ShapeDefaults.circle), colorTuple = colorTuple, backgroundColor = Color.Transparent ) { Box( modifier = Modifier .size(28.dp) .background( color = animateColorAsState( settingsState.appColorTuple.primary.inverse( fraction = { if (it) 0.8f else 0.5f }, darkMode = settingsState.appColorTuple.primary.luminance() < 0.3f ) ).value, shape = ShapeDefaults.circle ) ) Icon( imageVector = Icons.Rounded.MiniEdit, contentDescription = stringResource(R.string.edit), tint = settingsState.appColorTuple.primary ) } } } ) var showColorPicker by rememberSaveable { mutableStateOf(false) } AvailableColorTuplesSheet( visible = showPickColorSheet, colorTupleList = settingsState.colorTupleList, currentColorTuple = rememberAppColorTuple(), onToggleInvertColors = onToggleInvertColors, onThemeStyleSelected = { onSetThemeStyle(it.ordinal) }, onUpdateThemeContrast = onUpdateThemeContrast, onOpenColorPicker = { showColorPicker = true }, colorPicker = { ColorTuplePicker( visible = showColorPicker, colorTuple = settingsState.appColorTuple, onDismiss = { showColorPicker = false }, onColorChange = { onUpdateColorTuple(it) onUpdateColorTuples(settingsState.colorTupleList + it) } ) }, onUpdateColorTuples = onUpdateColorTuples, onToggleUseEmojiAsPrimaryColor = onToggleUseEmojiAsPrimaryColor, onDismiss = { showPickColorSheet = false }, onPickTheme = onUpdateColorTuple ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationColorSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.rounded.ColorLens import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.ColorHarmonizer import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelection import com.t8rin.imagetoolbox.core.ui.widget.color_picker.RecentAndFavoriteColorsCard import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun ConfettiHarmonizationColorSettingItem( onValueChange: (ColorHarmonizer) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val items = remember { ColorHarmonizer.entries } val enabled = settingsState.isConfettiEnabled val scope = rememberCoroutineScope() var showColorPicker by remember { mutableStateOf(false) } Box { Column( modifier = modifier .container( shape = shape ) .alpha( animateFloatAsState( if (enabled) 1f else 0.5f ).value ) ) { TitleItem( modifier = Modifier.padding( top = 12.dp, end = 12.dp, bottom = 16.dp, start = 12.dp ), iconEndPadding = 14.dp, text = stringResource(R.string.harmonization_color), icon = Icons.Outlined.ColorLens ) FlowRow( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterHorizontally ), modifier = Modifier .fillMaxWidth() .padding(start = 8.dp, bottom = 8.dp, end = 8.dp) ) { val value = settingsState.confettiColorHarmonizer items.forEach { harmonizer -> val colorScheme = MaterialTheme.colorScheme val selectedColor = when (harmonizer) { is ColorHarmonizer.Custom -> Color(value.ordinal) .blend( color = colorScheme.surface, fraction = 0.1f ) ColorHarmonizer.Primary -> colorScheme.primary ColorHarmonizer.Secondary -> colorScheme.secondary ColorHarmonizer.Tertiary -> colorScheme.tertiary } EnhancedChip( onClick = { if (harmonizer !is ColorHarmonizer.Custom) { AppToastHost.dismissToasts() onValueChange(harmonizer) scope.launch { delay(200L) AppToastHost.showConfetti() } } else { showColorPicker = true } }, selected = harmonizer::class.isInstance(value), label = { Text(text = harmonizer.title) }, contentPadding = PaddingValues(horizontal = 16.dp, vertical = 6.dp), selectedColor = selectedColor, selectedContentColor = when (harmonizer) { is ColorHarmonizer.Custom -> selectedColor.inverse( fraction = { if (it) 0.9f else 0.6f }, darkMode = selectedColor.luminance() < 0.3f ) else -> contentColorFor(backgroundColor = selectedColor) }, unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant ) } } } if (!enabled) { Surface( color = Color.Transparent, modifier = Modifier.matchParentSize() ) {} } } var tempColor by remember(settingsState.confettiColorHarmonizer) { mutableStateOf( (settingsState.confettiColorHarmonizer as? ColorHarmonizer.Custom)?.color?.toColor() ?: Color.Black ) } EnhancedModalBottomSheet( sheetContent = { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(24.dp) ) { RecentAndFavoriteColorsCard( onRecentColorClick = { tempColor = it }, onFavoriteColorClick = { tempColor = it } ) ColorSelection( value = tempColor, onValueChange = { tempColor = it } ) } }, visible = showColorPicker, onDismiss = { showColorPicker = it }, title = { TitleItem( text = stringResource(R.string.color), icon = Icons.Rounded.ColorLens ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { AppToastHost.dismissToasts() onValueChange(ColorHarmonizer.Custom(tempColor.toArgb())) scope.launch { delay(200L) AppToastHost.showConfetti() } showColorPicker = false } ) { AutoSizeText(stringResource(R.string.ok)) } } ) } private val ColorHarmonizer.title: String @Composable get() = when (this) { is ColorHarmonizer.Custom -> stringResource(R.string.custom) ColorHarmonizer.Primary -> stringResource(R.string.primary) ColorHarmonizer.Secondary -> stringResource(R.string.secondary) ColorHarmonizer.Tertiary -> stringResource(R.string.tertiary) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiHarmonizationLevelSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exercise import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun ConfettiHarmonizationLevelSettingItem( onValueChange: (Float) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var value by remember { mutableFloatStateOf(settingsState.confettiHarmonizationLevel) } EnhancedSliderItem( modifier = modifier, shape = shape, value = value, title = stringResource(R.string.harmonization_level), enabled = settingsState.isConfettiEnabled, icon = Icons.Outlined.Exercise, onValueChange = { value = it.roundToTwoDigits() onValueChange(value) }, internalStateTransformation = { it.roundToTwoDigits() }, valueRange = 0f..1f ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Celebration import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun ConfettiSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val scope = rememberCoroutineScope() val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.confetti), subtitle = stringResource(R.string.confetti_sub), checked = settingsState.isConfettiEnabled, onClick = { isEnabled -> onClick() if (isEnabled) { scope.launch { //Wait for setting to be applied delay(200L) AppToastHost.showConfetti() } } }, startIcon = Icons.Outlined.Celebration ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ConfettiTypeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Emergency import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.confetti.Particles import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun ConfettiTypeSettingItem( onValueChange: (Int) -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val items = remember { Particles.Type.entries } val enabled = settingsState.isConfettiEnabled Box { Column( modifier = modifier .container( shape = shape ) .alpha( animateFloatAsState( if (enabled) 1f else 0.5f ).value ) ) { TitleItem( modifier = Modifier.padding( top = 12.dp, end = 12.dp, bottom = 16.dp, start = 12.dp ), iconEndPadding = 14.dp, text = stringResource(R.string.confetti_type), icon = Icons.Outlined.Emergency ) FlowRow( verticalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterHorizontally ), modifier = Modifier .fillMaxWidth() .padding(start = 8.dp, bottom = 8.dp, end = 8.dp) ) { val scope = rememberCoroutineScope() val value = settingsState.confettiType items.forEach { EnhancedChip( onClick = { AppToastHost.dismissToasts() onValueChange(it.ordinal) scope.launch { delay(200L) AppToastHost.showConfetti() } }, selected = it.ordinal == value, label = { Text(text = it.title) }, contentPadding = PaddingValues(horizontal = 16.dp, vertical = 6.dp), selectedColor = MaterialTheme.colorScheme.outlineVariant( 0.2f, MaterialTheme.colorScheme.tertiary ), selectedContentColor = MaterialTheme.colorScheme.onTertiary, unselectedContentColor = MaterialTheme.colorScheme.onSurface ) } } } if (!enabled) { Surface( color = Color.Transparent, modifier = Modifier.matchParentSize() ) {} } } } private val Particles.Type.title: String @Composable get() = when (this) { Particles.Type.Default -> stringResource(R.string.defaultt) Particles.Type.Festive -> stringResource(R.string.festive) Particles.Type.Explode -> stringResource(R.string.explode) Particles.Type.Rain -> stringResource(R.string.rain) Particles.Type.Side -> stringResource(R.string.side) Particles.Type.Corners -> stringResource(R.string.corners) Particles.Type.Toolbox -> stringResource(R.string.app_name) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ContainerShadowsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ViewComfy import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ContainerShadowsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, enabled = settingsState.borderWidth <= 0.dp, shape = shape, title = stringResource(R.string.containers_shadow), subtitle = stringResource(R.string.containers_shadow_sub), checked = settingsState.drawContainerShadows, onClick = { onClick() }, startIcon = Icons.Outlined.ViewComfy ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CornersSizeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Percent import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.drop @Composable fun CornersSizeSettingItem( onValueChange: (ShapeType) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val settingsInteractor = LocalSimpleSettingsInteractor.current var value by remember { mutableFloatStateOf(settingsState.shapesType.strength) } var isSwapped by rememberSaveable { mutableStateOf(false) } var trigger by remember { mutableIntStateOf(0) } LaunchedEffect(settingsState.shapesType) { snapshotFlow { trigger } .drop(1) .collectLatest { if (settingsState.shapesType is ShapeType.Smooth && settingsState.drawContainerShadows && value > 1f) { settingsInteractor.setBorderWidth(0.2f) isSwapped = true } else if (isSwapped && settingsState.borderWidth.value == 0.2f) { settingsInteractor.setBorderWidth(0f) isSwapped = false } onValueChange(settingsState.shapesType.copy(value)) } } EnhancedSliderItem( modifier = modifier, shape = shape, value = value, title = stringResource(R.string.corners_size), icon = Icons.Outlined.Percent, onValueChange = { value = it.roundToTwoDigits() }, onValueChangeFinished = { trigger++ }, internalStateTransformation = { it.roundToTwoDigits() }, valueRange = 0f..2f ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CrashlyticsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Crashlytics import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun CrashlyticsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, title = stringResource(R.string.crashlytics), subtitle = stringResource(id = R.string.crashlytics_sub), startIcon = Icons.Rounded.Crashlytics, checked = settingsState.allowCollectCrashlytics, onClick = { onClick() } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/CurrentVersionCodeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("KotlinConstantConditions") package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Verified import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.BuildConfig import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.shapes.MaterialStarShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.AppVersion import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.pulsate import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow @Composable fun CurrentVersionCodeSettingItem( isUpdateAvailable: Boolean, onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRow( shape = shape, modifier = Modifier .pulsate( enabled = isUpdateAvailable, range = 0.98f..1.02f ) .then(modifier), title = stringResource(R.string.version), subtitle = remember { "$AppVersion (${BuildConfig.VERSION_CODE})" }, startIcon = Icons.Outlined.Verified, endContent = { Icon( painter = painterResource(R.drawable.ic_launcher_foreground), contentDescription = stringResource(R.string.version), tint = animateColorAsState( if (settingsState.isNightMode) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.primary.blend(Color.Black) } ).value, modifier = Modifier .padding(horizontal = 8.dp) .size(64.dp) .container( resultPadding = 0.dp, color = animateColorAsState( if (settingsState.isNightMode) { MaterialTheme.colorScheme.secondaryContainer.blend( color = Color.Black, fraction = 0.3f ) } else { MaterialTheme.colorScheme.primaryContainer } ).value, borderColor = MaterialTheme.colorScheme.outlineVariant(), shape = MaterialStarShape ) .scale(1.25f) ) }, onClick = onClick ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultColorSpaceSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.ScaleColorSpace import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.DataSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.title import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun DefaultColorSpaceSettingItem( onValueChange: (ImageScaleMode) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current AnimatedVisibility( visible = settingsState.defaultImageScaleMode != ImageScaleMode.Base, modifier = Modifier.fillMaxWidth() ) { val items = remember { ScaleColorSpace.entries } DataSelector( value = settingsState.defaultImageScaleMode.scaleColorSpace, onValueChange = { onValueChange( settingsState.defaultImageScaleMode.copy(it) ) }, initialExpanded = true, spanCount = 2, entries = items, title = stringResource(R.string.tag_color_space), titleIcon = Icons.Outlined.ColorLens, itemContentText = { it.title }, containerColor = Color.Unspecified, shape = shape, modifier = modifier, selectedItemColor = MaterialTheme.colorScheme.secondary ) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawColorSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BrushColor import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.toModel import com.t8rin.imagetoolbox.core.ui.widget.color_picker.ColorSelectionRowDefaults import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable fun DefaultDrawColorSettingItem( onValueChange: (ColorModel) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp), ) { val settingsState = LocalSettingsState.current ColorRowSelector( modifier = modifier.container(shape = shape), value = settingsState.defaultDrawColor, onValueChange = { onValueChange(it.toModel()) }, icon = Icons.Outlined.BrushColor, title = stringResource(R.string.default_draw_color), defaultColors = ColorSelectionRowDefaults.colorList ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawLineWidthSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.LineWeight import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun DefaultDrawLineWidthSettingItem( onValueChange: (Float) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var value by remember { mutableFloatStateOf(settingsState.defaultDrawLineWidth) } EnhancedSliderItem( modifier = modifier, shape = shape, value = value, title = stringResource(R.string.default_line_width), icon = Icons.Rounded.LineWeight, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { value = it onValueChange(it) }, valueSuffix = " Pt", valueRange = 1f..100f ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultDrawPathModeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank import androidx.compose.material.icons.rounded.Circle import androidx.compose.material.icons.rounded.HourglassEmpty import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarOutline import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FloodFill import com.t8rin.imagetoolbox.core.resources.icons.FreeArrow import com.t8rin.imagetoolbox.core.resources.icons.FreeDoubleArrow import com.t8rin.imagetoolbox.core.resources.icons.FreeDraw import com.t8rin.imagetoolbox.core.resources.icons.Lasso import com.t8rin.imagetoolbox.core.resources.icons.Line import com.t8rin.imagetoolbox.core.resources.icons.LineArrow import com.t8rin.imagetoolbox.core.resources.icons.LineDoubleArrow import com.t8rin.imagetoolbox.core.resources.icons.Polygon import com.t8rin.imagetoolbox.core.resources.icons.Spray import com.t8rin.imagetoolbox.core.resources.icons.Square import com.t8rin.imagetoolbox.core.resources.icons.Triangle import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun DefaultDrawPathModeSettingItem( onValueChange: (Int) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current Column(modifier = modifier.container(shape = shape)) { TitleItem( text = stringResource(R.string.default_draw_path_mode), icon = Icons.Outlined.TouchApp, iconEndPadding = 14.dp, modifier = Modifier .padding(horizontal = 12.dp) .padding(top = 12.dp) ) EnhancedButtonGroup( enabled = true, itemCount = ordinals.size, title = {}, selectedIndex = ordinals.indexOf(settingsState.defaultDrawPathMode), activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, itemContent = { Icon( imageVector = ordinals[it].getIcon(), contentDescription = null ) }, onIndexChange = { onValueChange(ordinals[it]) } ) } } private val ordinals = listOf( 0, 17, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ) private fun Int.getIcon(): ImageVector = when (this) { 5 -> Icons.Rounded.LineDoubleArrow 3 -> Icons.Rounded.FreeDoubleArrow 0 -> Icons.Rounded.FreeDraw 1 -> Icons.Rounded.Line 4 -> Icons.Rounded.LineArrow 2 -> Icons.Rounded.FreeArrow 8 -> Icons.Rounded.RadioButtonUnchecked 7 -> Icons.Rounded.CheckBoxOutlineBlank 10 -> Icons.Rounded.Circle 9 -> Icons.Rounded.Square 6 -> Icons.Rounded.Lasso 11 -> Icons.Rounded.Triangle 12 -> Icons.Outlined.Triangle 13 -> Icons.Rounded.Polygon 14 -> Icons.Outlined.Polygon 16 -> Icons.Rounded.StarOutline 15 -> Icons.Rounded.Star 17 -> Icons.Rounded.FloodFill 18 -> Icons.Outlined.Spray else -> Icons.Rounded.HourglassEmpty } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultImageFormatSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Png import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults @Composable fun DefaultImageFormatSettingItem( onValueChange: (ImageFormat?) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current ImageFormatSelector( modifier = modifier, shape = shape, quality = settingsState.defaultQuality, backgroundColor = Color.Unspecified, value = settingsState.defaultImageFormat, onValueChange = onValueChange, enableItemsCardBackground = false, onAutoClick = { onValueChange(null) }, title = { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp) .padding(top = 4.dp), verticalAlignment = Alignment.CenterVertically ) { IconShapeContainer( content = { Icon( imageVector = Icons.Outlined.Png, contentDescription = null ) } ) Spacer(modifier = Modifier.width(8.dp)) Text( text = stringResource(R.string.image_format), style = PreferenceItemDefaults.TitleFontStyle, modifier = Modifier.weight(1f, false) ) } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultQualitySettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.resources.icons.ShineDiamond import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults @Composable fun DefaultQualitySettingItem( onValueChange: (Quality) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var quality by remember { mutableStateOf(settingsState.defaultQuality) } QualitySelector( modifier = modifier, shape = shape, imageFormat = settingsState.defaultImageFormat ?: ImageFormat.Default, quality = quality, onQualityChange = { quality = it onValueChange(quality) }, icon = Icons.Outlined.ShineDiamond, activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultResizeTypeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectSmall import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.state.derivedValueOf import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun DefaultResizeTypeSettingItem( onValueChange: (ResizeType) -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val value = settingsState.defaultResizeType val entries = remember { ResizeType.entries } Column(modifier = modifier.container(shape = shape)) { TitleItem( text = stringResource(R.string.resize_type), icon = Icons.Outlined.PhotoSizeSelectSmall, modifier = Modifier .padding(horizontal = 12.dp) .padding(top = 12.dp) ) EnhancedButtonGroup( enabled = true, itemCount = entries.size, title = {}, selectedIndex = derivedValueOf(value) { entries.indexOfFirst { it::class.isInstance(value) } }, activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, itemContent = { Text(stringResource(entries[it].getTitle())) }, onIndexChange = { onValueChange(entries[it]) } ) } } private fun ResizeType.getTitle(): Int = when (this) { is ResizeType.CenterCrop -> R.string.crop is ResizeType.Explicit -> R.string.explicit is ResizeType.Flexible -> R.string.flexible is ResizeType.Fit -> R.string.fit } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DefaultScaleModeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FitScreen import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ScaleModeSelector import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults @Composable fun DefaultScaleModeSettingItem( onValueChange: (ImageScaleMode) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current ScaleModeSelector( modifier = modifier, shape = shape, backgroundColor = Color.Unspecified, value = settingsState.defaultImageScaleMode, onValueChange = onValueChange, titlePadding = PaddingValues( top = 12.dp, start = 12.dp, end = 12.dp ), titleArrangement = Arrangement.Start, enableItemsCardBackground = false, title = { IconShapeContainer( content = { Icon( imageVector = Icons.Outlined.FitScreen, contentDescription = null ) } ) Spacer(modifier = Modifier.width(8.dp)) Text( text = stringResource(R.string.scale_mode), style = PreferenceItemDefaults.TitleFontStyle, modifier = Modifier.weight(1f, false) ) } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DonateSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.VolunteerActivism import com.t8rin.imagetoolbox.core.settings.presentation.model.isFirstLaunch import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContainerColor import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContentColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.pulsate import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.DonateSheet @Composable fun DonateSettingItem( shape: Shape = ShapeDefaults.bottom ) { val settingsState = LocalSettingsState.current var showDonateSheet by rememberSaveable { mutableStateOf(false) } CompositionLocalProvider( LocalIconShapeContentColor provides MaterialTheme.colorScheme.onTertiaryContainer, LocalIconShapeContainerColor provides MaterialTheme.colorScheme.tertiaryContainer.blend( color = MaterialTheme.colorScheme.tertiary, fraction = if (settingsState.isNightMode) 0.2f else 0.1f ) ) { PreferenceItem( modifier = Modifier .pulsate( range = 0.98f..1.02f, enabled = settingsState.isFirstLaunch() ) .padding(horizontal = 8.dp), shape = shape, containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer, title = stringResource(R.string.donation), subtitle = stringResource(R.string.donation_sub), startIcon = Icons.Outlined.VolunteerActivism, onClick = { showDonateSheet = true }, overrideIconShapeContentColor = true ) } DonateSheet( visible = showDonateSheet, onDismiss = { showDonateSheet = false } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DragHandleWidthSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlin.math.roundToInt @Composable fun DragHandleWidthSettingItem( onValueChange: (Int) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var value by remember { mutableFloatStateOf(settingsState.dragHandleWidth.value) } EnhancedSliderItem( modifier = modifier, shape = shape, valueSuffix = " Dp", value = value, title = stringResource(R.string.drag_handle_width), icon = Icons.Rounded.DragHandle, onValueChange = { value = it.roundToInt().toFloat() onValueChange(it.roundToInt()) }, internalStateTransformation = { it.roundToInt() }, valueRange = 0f..128f ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/DynamicColorsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FormatColorFill import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun DynamicColorsSettingItem( onClick: () -> Unit, modifier: Modifier = Modifier.padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.center ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, startIcon = Icons.Outlined.FormatColorFill, title = stringResource(R.string.dynamic_colors), subtitle = stringResource(R.string.dynamic_colors_sub), checked = settingsState.isDynamicColors, onClick = { onClick() } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojiSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Block import androidx.compose.material.icons.rounded.Casino import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Cool import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.scaleOnTap import com.t8rin.imagetoolbox.core.ui.widget.other.EmojiItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.core.ui.widget.sheets.EmojiSelectionSheet import com.t8rin.imagetoolbox.core.utils.getString @Composable fun EmojiSettingItem( selectedEmojiIndex: Int, onAddColorTupleFromEmoji: (String) -> Unit, onUpdateEmoji: (Int) -> Unit, modifier: Modifier = Modifier.padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.top ) { val settingsState = LocalSettingsState.current var showSecretDescriptionDialog by rememberSaveable { mutableStateOf("") } var showShoeDescriptionDialog by rememberSaveable { mutableStateOf("") } var showEmojiDialog by rememberSaveable { mutableStateOf(false) } PreferenceRow( modifier = modifier, shape = shape, title = stringResource(R.string.emoji), subtitle = stringResource(R.string.emoji_sub), onClick = { showEmojiDialog = true }, startIcon = Icons.Outlined.Cool, enabled = !settingsState.useRandomEmojis, onDisabledClick = { AppToastHost.showToast( message = getString(R.string.emoji_selection_error), icon = Icons.Rounded.Casino ) }, endContent = { val emoji = LocalSettingsState.current.selectedEmoji Box( modifier = Modifier .padding(end = 8.dp) .size(64.dp) .container( shape = CloverShape, color = MaterialTheme.colorScheme .surfaceVariant .copy(alpha = 0.5f), borderColor = MaterialTheme.colorScheme.outlineVariant( 0.2f ) ), contentAlignment = Alignment.Center ) { EmojiItem( emoji = emoji?.toString(), modifier = Modifier.then( if (emoji != null) { Modifier.scaleOnTap( onRelease = { time -> if (time > 500) { onAddColorTupleFromEmoji(emoji.toString()) if (emoji.toString().contains("frog", true)) { showSecretDescriptionDialog = emoji.toString() } else if (emoji.toString().contains("shoe", true)) { showShoeDescriptionDialog = emoji.toString() } } } ) } else Modifier ), fontScale = 1f, fontSize = MaterialTheme.typography.headlineLarge.fontSize, onNoEmoji = { size -> Icon( imageVector = Icons.Rounded.Block, contentDescription = null, modifier = Modifier.size(size) ) } ) } } ) EmojiSelectionSheet( selectedEmojiIndex = selectedEmojiIndex, onEmojiPicked = onUpdateEmoji, visible = showEmojiDialog, onDismiss = { showEmojiDialog = false } ) EnhancedAlertDialog( visible = showShoeDescriptionDialog.isNotEmpty(), icon = { EmojiItem( emoji = showShoeDescriptionDialog, fontScale = 1f, fontSize = MaterialTheme.typography.headlineLarge.fontSize, ) }, title = { Text(text = "Shoe") }, text = { Text(text = "15.07.1981 - Shoe, (ShoeUnited since 1998)") }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showShoeDescriptionDialog = "" } ) { Text(stringResource(R.string.close)) } }, onDismissRequest = { showShoeDescriptionDialog = "" } ) EnhancedAlertDialog( visible = showSecretDescriptionDialog.isNotEmpty(), icon = { EmojiItem( emoji = showSecretDescriptionDialog, fontScale = 1f, fontSize = MaterialTheme.typography.headlineLarge.fontSize, ) }, text = { Text( text = "\uD83D\uDC49 \uD83D\uDC46, \uD83D\uDC47 \uD83D\uDE4B \uD83D\uDC70 ❗ \uD83D\uDC64 \uD83D\uDC96 \uD83D\uDCF6 \uD83C\uDF05", modifier = Modifier.fillMaxWidth() ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showSecretDescriptionDialog = "" } ) { Text(stringResource(R.string.close)) } }, onDismissRequest = { showSecretDescriptionDialog = "" } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EmojisCountSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.EmojiMultiple import com.t8rin.imagetoolbox.core.resources.icons.Robot import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.utils.getString @Composable fun EmojisCountSettingItem( onValueChange: (Int) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier .padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current EnhancedSliderItem( modifier = modifier.then( if (settingsState.selectedEmoji == null) { Modifier .clip(ShapeDefaults.extraSmall) .hapticsClickable { AppToastHost.showToast( message = getString(R.string.random_emojis_error), icon = Icons.Rounded.Robot ) } } else Modifier ), shape = shape, value = settingsState.emojisCount.coerceAtLeast(1), title = stringResource(R.string.emojis_count), icon = Icons.Outlined.EmojiMultiple, valueRange = 1f..5f, steps = 3, enabled = settingsState.selectedEmoji != null, onValueChange = {}, internalStateTransformation = { it.toInt() }, onValueChangeFinished = { onValueChange(it.toInt()) } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableBackgroundColorForAlphaFormatsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Transparency import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun EnableBackgroundColorForAlphaFormatsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.background_color_for_alpha_formats), subtitle = stringResource(R.string.background_color_for_alpha_formats_sub), checked = settingsState.enableBackgroundColorForAlphaFormats, onClick = { onClick() }, startIcon = Icons.Filled.Transparency ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLauncherModeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Apps import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun EnableLauncherModeSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.Companion.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.launcher_mode), subtitle = stringResource(R.string.launcher_mode_sub), checked = settingsState.isScreenSelectionLauncherMode, onClick = { onClick() }, startIcon = Icons.Outlined.Apps ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableLinksPreviewSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Link import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun EnableLinksPreviewSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.links_preview), subtitle = stringResource(R.string.links_preview_sub), checked = settingsState.isLinkPreviewEnabled, onClick = { onClick() }, startIcon = Icons.Outlined.Link ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/EnableToolExitConfirmationSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SaveConfirm import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun EnableToolExitConfirmationSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.tool_exit_confirmation), subtitle = stringResource(R.string.tool_exit_confirmation_sub), checked = settingsState.enableToolExitConfirmation, onClick = { onClick() }, startIcon = Icons.Outlined.SaveConfirm ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ExifWidgetInitialStateSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.DataSaverOff import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ExifWidgetInitialStateSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.force_exif_widget_initial_value), subtitle = stringResource(R.string.force_exif_widget_initial_value_sub), checked = settingsState.exifWidgetInitialState, onClick = { onClick() }, startIcon = Icons.Outlined.DataSaverOff ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabAlignmentSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AlignVerticalCenter import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateContentSizeNoClip import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.settings.presentation.components.additional.FabPreview @Composable fun FabAlignmentSettingItem( onValueChange: (Float) -> Unit, modifier: Modifier = Modifier .padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.bottom ) { val settingsState = LocalSettingsState.current Row( modifier .height(IntrinsicSize.Max) .container( shape = shape ) .animateContentSizeNoClip() .padding( start = 4.dp, top = 4.dp, bottom = 4.dp, end = 4.dp ), verticalAlignment = Alignment.CenterVertically ) { val derivedValue by remember(settingsState) { derivedStateOf { when (settingsState.fabAlignment) { Alignment.BottomStart -> 0 Alignment.BottomCenter -> 1 else -> 2 } } } Column( modifier = Modifier .weight(1f) .fillMaxHeight() .padding(end = 12.dp) ) { TitleItem( text = stringResource(R.string.fab_alignment), icon = Icons.Rounded.AlignVerticalCenter, modifier = Modifier.padding( start = 8.dp, top = 6.dp ) ) Spacer(modifier = Modifier.weight(1f)) EnhancedButtonGroup( modifier = Modifier.padding(horizontal = 4.dp), itemCount = 3, itemContent = { Text( stringResource( when (it) { 0 -> R.string.start_position 1 -> R.string.center_position else -> R.string.end_position } ) ) }, onIndexChange = { onValueChange(it.toFloat()) }, selectedIndex = derivedValue, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer ) } FabPreview( alignment = settingsState.fabAlignment, modifier = Modifier .width(75.dp) .fillMaxHeight() ) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FabShadowsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FabCorner import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun FabShadowsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, enabled = settingsState.borderWidth <= 0.dp, shape = shape, title = stringResource(R.string.fabs_shadow), subtitle = stringResource(R.string.fabs_shadow_sub), checked = settingsState.drawFabShadows, onClick = { onClick() }, startIcon = Icons.Outlined.FabCorner ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FastSettingsSideSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SettingsTimelapse import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerColor import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun FastSettingsSideSettingItem( onValueChange: (FastSettingsSide) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp), ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { if (it) { onValueChange(FastSettingsSide.CenterEnd) } else { onValueChange(FastSettingsSide.None) } }, title = stringResource(R.string.fast_settings_side), subtitle = stringResource(R.string.fast_settings_side_sub), checked = settingsState.fastSettingsSide != FastSettingsSide.None, startIcon = Icons.Outlined.SettingsTimelapse, additionalContent = { AnimatedVisibility( visible = settingsState.fastSettingsSide != FastSettingsSide.None, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { ProvideContainerDefaults( shape = null, color = LocalContainerColor.current ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier .padding(top = 16.dp) .height(IntrinsicSize.Max) ) { val entries = remember { listOf(FastSettingsSide.CenterStart, FastSettingsSide.CenterEnd) } entries.forEachIndexed { index, side -> val selected = settingsState.fastSettingsSide == side PreferenceRow( title = when (side) { FastSettingsSide.CenterEnd -> stringResource(R.string.end) FastSettingsSide.CenterStart -> stringResource(R.string.start) FastSettingsSide.None -> "" }, onClick = { onValueChange(side) }, shape = ShapeDefaults.byIndex( index = index, size = entries.size, vertical = false ), titleFontStyle = PreferenceItemDefaults.TitleFontStyleCenteredSmall, startIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else { Icons.Rounded.RadioButtonUnchecked }, drawStartIconContainer = false, modifier = Modifier .weight(1f) .fillMaxHeight(), color = takeColorFromScheme { if (selected) tertiaryContainer.copy(0.5f) else surfaceContainer }, contentColor = takeColorFromScheme { if (selected) onTertiaryContainer.copy(0.8f) else onSurface }, ) } } } } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePatternSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Description import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.JAVA_FORMAT_SPECIFICATION import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Date import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Extension import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Height import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.OriginalName import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Prefix import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.PresetInfo import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Rand import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.ScaleMode import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Sequence import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Suffix import com.t8rin.imagetoolbox.core.domain.saving.model.FilenamePattern.Companion.Width import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.ImageToolboxThemeForPreview import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBadge import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.PatternHighlightTransformation import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun FilenamePatternSettingItem( onValueChange: (String) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp), filenameCreator: FilenameCreator ) { val settingsState = LocalSettingsState.current var showEditDialog by rememberSaveable { mutableStateOf(false) } val exampleSaveTarget = remember { val originalUri = "file:///android_asset/svg/emotions/aasparkles.svg" ImageSaveTarget( imageInfo = ImageInfo( width = 123, height = 456, originalUri = originalUri ), originalUri = originalUri, sequenceNumber = 1, data = ByteArray(1), presetInfo = Preset.Percentage(95) ) } var value by remember(showEditDialog, settingsState) { mutableStateOf( settingsState.filenamePattern?.takeIf { it.isNotBlank() } ?: if (settingsState.addOriginalFilename) { FilenamePattern.ForOriginal } else { FilenamePattern.Default } ) } val exampleFilename by remember(filenameCreator, value, settingsState) { derivedStateOf { filenameCreator.constructImageFilename( saveTarget = exampleSaveTarget, oneTimePrefix = null, forceNotAddSizeInFilename = false, pattern = value ) } } PreferenceItem( shape = shape, onClick = { showEditDialog = true }, enabled = settingsState.filenameBehavior is FilenameBehavior.None, title = stringResource(R.string.filename_format), subtitle = exampleFilename, endIcon = Icons.Rounded.MiniEdit, startIcon = Icons.Outlined.Description, modifier = modifier.fillMaxWidth() ) EnhancedModalBottomSheet( visible = showEditDialog, onDismiss = { showEditDialog = it }, title = { TitleItem( icon = Icons.Outlined.Description, text = stringResource(R.string.filename_format) ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer.copy( alpha = if (settingsState.isNightMode) 0.5f else 1f ), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, onClick = { onValueChange(value.trim()) showEditDialog = false }, borderColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ) ) { Text(stringResource(R.string.save)) } } ) { Column( modifier = Modifier .fillMaxWidth() .clearFocusOnTap() .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( space = 4.dp, alignment = Alignment.CenterVertically ), ) { PreferenceItem( title = stringResource(R.string.filename), subtitle = exampleFilename, modifier = Modifier ) RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = value, textStyle = MaterialTheme.typography.titleMedium, onValueChange = { value = it }, maxLines = Int.MAX_VALUE, singleLine = false, hint = { Text(stringResource(R.string.format_pattern)) }, label = null, visualTransformation = PatternHighlightTransformation.default() ) Spacer(Modifier.height(4.dp)) Column( verticalArrangement = Arrangement.spacedBy( space = 4.dp, alignment = Alignment.CenterVertically ), ) { val linkHandler = LocalUriHandler.current FilenamePattern.entries.forEachIndexed { index, pattern -> PreferenceItemOverload( title = when (pattern) { Date -> pattern.value + "{pattern}" Rand -> pattern.value + "{count}" else -> pattern.value }, subtitle = when (pattern) { Prefix -> stringResource(R.string.prefix_pattern_description) OriginalName -> stringResource(R.string.original_filename_pattern_description) Width -> stringResource(R.string.width_pattern_description) Height -> stringResource(R.string.height_pattern_description) Date -> stringResource(R.string.formatted_timestamp_pattern_description) Rand -> stringResource(R.string.random_numbers_pattern_description) Sequence -> stringResource(R.string.sequence_number_pattern_description) PresetInfo -> stringResource(R.string.preset_info_pattern_description) ScaleMode -> stringResource(R.string.scale_mode_pattern_description) Suffix -> stringResource(R.string.suffix_pattern_description) Extension -> stringResource(R.string.extension_pattern_description) else -> null }, badge = if (pattern.hasUpper()) { { EnhancedBadge( modifier = Modifier .align(Alignment.CenterVertically) .padding(horizontal = 4.dp, vertical = 2.dp), content = { Text("A/a") }, containerColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.onSecondary ) } } else null, containerColor = takeColorFromScheme { if (pattern.value in value) { secondaryContainer } else { SafeLocalContainerColor } }, contentColor = takeColorFromScheme { if (pattern.value in value) { onSecondaryContainer } else { onSurface } }, endIcon = if (pattern == Date) { { Icon( imageVector = Icons.Outlined.Info, contentDescription = null ) } } else null, onClick = if (pattern == Date) { { linkHandler.openUri(JAVA_FORMAT_SPECIFICATION) } } else null, shape = ShapeDefaults.byIndex( index = index, size = FilenamePattern.entries.size ), modifier = Modifier ) } } } } } @Composable @Preview private fun Preview() = ImageToolboxThemeForPreview( isDarkTheme = true ) { Surface { FilenamePatternSettingItem( onValueChange = {}, filenameCreator = object : FilenameCreator { override fun constructImageFilename( saveTarget: ImageSaveTarget, oneTimePrefix: String?, forceNotAddSizeInFilename: Boolean, pattern: String? ): String = "Not yet implemented" override fun constructRandomFilename( extension: String, length: Int ): String = "Not yet implemented" override fun getFilename(uri: String): String = "Not yet implemented" } ) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenamePrefixSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.Prefix import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun FilenamePrefixSettingItem( onValueChange: (String) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showChangeFilenameDialog by rememberSaveable { mutableStateOf(false) } PreferenceItem( shape = shape, onClick = { showChangeFilenameDialog = true }, enabled = settingsState.filenameBehavior is FilenameBehavior.None, title = stringResource(R.string.prefix), subtitle = (settingsState.filenamePrefix.takeIf { it.isNotEmpty() } ?: stringResource(R.string.empty)), endIcon = Icons.Rounded.MiniEdit, startIcon = Icons.Filled.Prefix, modifier = modifier ) var value by remember(showChangeFilenameDialog) { mutableStateOf( settingsState.filenamePrefix ) } EnhancedAlertDialog( visible = showChangeFilenameDialog, onDismissRequest = { showChangeFilenameDialog = false }, icon = { Icon( imageVector = Icons.Filled.Prefix, contentDescription = null ) }, title = { Text(stringResource(R.string.prefix)) }, text = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { OutlinedTextField( placeholder = { Text( text = stringResource(R.string.default_prefix), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) }, shape = ShapeDefaults.default, value = value, textStyle = MaterialTheme.typography.titleMedium.copy( textAlign = TextAlign.Center ), onValueChange = { value = it } ) } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer.copy( alpha = if (settingsState.isNightMode) 0.5f else 1f ), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, onClick = { onValueChange(value.trim()) showChangeFilenameDialog = false }, borderColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ), ) { Text(stringResource(R.string.ok)) } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FilenameSuffixSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.Suffix import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun FilenameSuffixSettingItem( onValueChange: (String) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showChangeFilenameDialog by rememberSaveable { mutableStateOf(false) } PreferenceItem( shape = shape, onClick = { showChangeFilenameDialog = true }, enabled = settingsState.filenameBehavior is FilenameBehavior.None, title = stringResource(R.string.suffix), subtitle = (settingsState.filenameSuffix.takeIf { it.isNotEmpty() } ?: stringResource(R.string.empty)), endIcon = Icons.Rounded.MiniEdit, startIcon = Icons.Filled.Suffix, modifier = modifier.fillMaxWidth() ) var value by remember(showChangeFilenameDialog) { mutableStateOf( settingsState.filenameSuffix ) } EnhancedAlertDialog( visible = showChangeFilenameDialog, onDismissRequest = { showChangeFilenameDialog = false }, icon = { Icon( imageVector = Icons.Filled.Suffix, contentDescription = null ) }, title = { Text(stringResource(R.string.suffix)) }, text = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { OutlinedTextField( shape = ShapeDefaults.default, value = value, textStyle = MaterialTheme.typography.titleMedium.copy( textAlign = TextAlign.Center ), onValueChange = { value = it } ) } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer.copy( alpha = if (settingsState.isNightMode) 0.5f else 1f ), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, onClick = { onValueChange(value.trim()) showChangeFilenameDialog = false }, borderColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ), ) { Text(stringResource(R.string.ok)) } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FlingTypeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Animation import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Animation import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun FlingTypeSettingItem( onValueChange: (FlingType) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( modifier = modifier, title = stringResource(id = R.string.fling_type), startIcon = Icons.Outlined.Animation, subtitle = settingsState.flingType.title, onClick = { showSheet = true }, shape = shape, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showSheet, onDismiss = { showSheet = it }, title = { TitleItem( text = stringResource(id = R.string.fling_type), icon = Icons.Rounded.Animation ) }, confirmButton = { EnhancedButton( onClick = { showSheet = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } } ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp) ) { val entries = FlingType.entries entries.forEachIndexed { index, type -> val selected = type == settingsState.flingType PreferenceItem( onClick = { onValueChange(type) }, title = type.title, subtitle = type.subtitle, containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor }, shape = ShapeDefaults.byIndex(index, entries.size), modifier = Modifier .fillMaxWidth() .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme.colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = ShapeDefaults.byIndex(index, entries.size) ), endIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else Icons.Rounded.RadioButtonUnchecked ) } } } } private val FlingType.title: String @Composable get() = when (this) { FlingType.DEFAULT -> stringResource(R.string.android_native) FlingType.SMOOTH -> stringResource(R.string.smooth) FlingType.IOS_STYLE -> stringResource(R.string.ios_style) FlingType.SMOOTH_CURVE -> stringResource(R.string.smooth_curve) FlingType.QUICK_STOP -> stringResource(R.string.quick_stop) FlingType.BOUNCY -> stringResource(R.string.bouncy) FlingType.FLOATY -> stringResource(R.string.floaty) FlingType.SNAPPY -> stringResource(R.string.snappy) FlingType.ULTRA_SMOOTH -> stringResource(R.string.ultra_smooth) FlingType.ADAPTIVE -> stringResource(R.string.adaptive) FlingType.ACCESSIBILITY_AWARE -> stringResource(R.string.accessibility_aware) FlingType.REDUCED_MOTION -> stringResource(R.string.reduced_motion) } private val FlingType.subtitle: String @Composable get() = when (this) { FlingType.DEFAULT -> stringResource(R.string.android_native_sub) FlingType.SMOOTH -> stringResource(R.string.smooth_sub) FlingType.IOS_STYLE -> stringResource(R.string.ios_style_sub) FlingType.SMOOTH_CURVE -> stringResource(R.string.smooth_curve_sub) FlingType.QUICK_STOP -> stringResource(R.string.quick_stop_sub) FlingType.BOUNCY -> stringResource(R.string.bouncy_sub) FlingType.FLOATY -> stringResource(R.string.floaty_sub) FlingType.SNAPPY -> stringResource(R.string.snappy_sub) FlingType.ULTRA_SMOOTH -> stringResource(R.string.ultra_smooth_sub) FlingType.ADAPTIVE -> stringResource(R.string.adaptive_sub) FlingType.ACCESSIBILITY_AWARE -> stringResource(R.string.accessibility_aware_sub) FlingType.REDUCED_MOTION -> stringResource(R.string.reduced_motion_sub) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FontScaleSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.TextFields import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import kotlinx.collections.immutable.persistentMapOf @Composable fun FontScaleSettingItem( onValueChange: (Float) -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val resources = LocalResourceManager.current var sliderValue by remember(settingsState.fontScale) { mutableFloatStateOf(settingsState.fontScale ?: 0.45f) } EnhancedSliderItem( modifier = modifier, shape = shape, value = sliderValue, title = stringResource(R.string.font_scale), icon = Icons.Outlined.TextFields, onValueChange = { sliderValue = it.roundToTwoDigits() }, internalStateTransformation = { it.roundToTwoDigits() }, onValueChangeFinished = { onValueChange( if (sliderValue < 0.5f) 0f else sliderValue ) }, valueRange = 0.45f..1.5f, steps = 20, valuesPreviewMapping = remember { persistentMapOf(0.45f to resources.getString(R.string.defaultt)) }, valueTextTapEnabled = false, additionalContent = { AnimatedVisibility( visible = sliderValue > 1.2f, modifier = Modifier.fillMaxWidth() ) { InfoContainer( text = stringResource(R.string.using_large_fonts_warn), textAlign = TextAlign.Start, containerColor = MaterialTheme.colorScheme.errorContainer.copy(0.4f), contentColor = MaterialTheme.colorScheme.onErrorContainer.copy(0.7f), modifier = Modifier.padding(4.dp) ) } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/FreeSoftwarePartnerSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.PARTNER_FREE_SOFTWARE import com.t8rin.imagetoolbox.core.domain.utils.Flavor import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.HandshakeAlt import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import java.util.Locale @Composable fun FreeSoftwarePartnerSettingItem( shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { if (Flavor.isFoss() || Locale.getDefault().language != "ru") return val linkHandler = LocalUriHandler.current PreferenceRow( shape = shape, onClick = { linkHandler.openUri(PARTNER_FREE_SOFTWARE) }, startIcon = Icons.Outlined.HandshakeAlt, title = stringResource(R.string.free_software_partner), subtitle = stringResource(R.string.free_software_partner_sub), color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.35f) .compositeOver(MaterialTheme.colorScheme.surface), contentColor = MaterialTheme.colorScheme.onSecondaryContainer.copy(0.9f), modifier = modifier ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GeneratePreviewsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Preview import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun GeneratePreviewsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.generate_previews), subtitle = stringResource(R.string.generate_previews_sub), checked = settingsState.generatePreviews, onClick = { onClick() }, startIcon = Icons.Outlined.Preview ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/GroupOptionsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BatchPrediction import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun GroupOptionsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, startIcon = Icons.Outlined.BatchPrediction, title = stringResource(R.string.group_tools_by_type), subtitle = stringResource(R.string.group_tools_by_type_sub), checked = settingsState.groupOptionsByTypes, onClick = { onClick() } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/HelpTranslateSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Translate import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.WEBLATE_LINK import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun HelpTranslateSettingItem( shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val linkHandler = LocalUriHandler.current PreferenceItem( shape = shape, modifier = modifier, title = stringResource(R.string.help_translate), subtitle = stringResource(R.string.help_translate_sub), startIcon = Icons.Rounded.Translate, onClick = { linkHandler.openUri(WEBLATE_LINK) } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IconShapeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FormatShapes import androidx.compose.material.icons.rounded.Block import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.settings.presentation.model.IconShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun IconShapeSettingItem( value: Int?, onValueChange: (Int) -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { var showPickerSheet by rememberSaveable { mutableStateOf(false) } PreferenceRow( modifier = modifier, shape = shape, title = stringResource(R.string.icon_shape), subtitle = stringResource(R.string.icon_shape_sub), onClick = { showPickerSheet = true }, startIcon = Icons.Outlined.FormatShapes, endContent = { Box( modifier = Modifier .padding(end = 8.dp) .size(64.dp) .container( shape = CloverShape, color = MaterialTheme.colorScheme .surfaceVariant .copy(alpha = 0.5f), borderColor = MaterialTheme.colorScheme.outlineVariant( 0.2f ) ), contentAlignment = Alignment.Center ) { IconShapePreview() } } ) EnhancedModalBottomSheet( visible = showPickerSheet, onDismiss = { showPickerSheet = false }, title = { TitleItem( icon = Icons.Outlined.FormatShapes, text = stringResource(R.string.icon_shape) ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showPickerSheet = false } ) { Text(stringResource(R.string.close)) } } ) { LazyVerticalGrid( columns = GridCells.Adaptive(55.dp), modifier = Modifier .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy( space = 6.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 6.dp, alignment = Alignment.CenterHorizontally ), contentPadding = PaddingValues(16.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed(IconShape.entries) { index, iconShape -> val selected by remember(index, value) { derivedStateOf { index == value } } val color by animateColorAsState( if (selected) MaterialTheme.colorScheme.primaryContainer else SafeLocalContainerColor ) val borderColor by animateColorAsState( if (selected) { MaterialTheme.colorScheme.onPrimaryContainer.copy(0.7f) } else MaterialTheme.colorScheme.onSecondaryContainer.copy( alpha = 0.1f ) ) Box( modifier = Modifier .aspectRatio(1f) .container( color = color, shape = CloverShape, borderColor = borderColor, resultPadding = 0.dp ) .hapticsClickable { onValueChange(index) }, contentAlignment = Alignment.Center ) { IconShapePreview(iconShape) } } item { val selected by remember(value) { derivedStateOf { value == null } } val color by animateColorAsState( if (selected) MaterialTheme.colorScheme.primaryContainer else SafeLocalContainerColor ) val borderColor by animateColorAsState( if (selected) { MaterialTheme.colorScheme.onPrimaryContainer.copy(0.7f) } else MaterialTheme.colorScheme.onSecondaryContainer.copy( alpha = 0.1f ) ) Box( modifier = Modifier .aspectRatio(1f) .container( color = color, shape = CloverShape, borderColor = borderColor, resultPadding = 0.dp ) .hapticsClickable { onValueChange(-1) }, contentAlignment = Alignment.Center ) { IconShapePreview(iconShape = null) } } } } } @Composable private fun IconShapePreview( iconShape: IconShape? = LocalSettingsState.current.iconShape ) { val color = MaterialTheme.colorScheme.onSurfaceVariant when (iconShape) { null -> { Icon( imageVector = Icons.Rounded.Block, contentDescription = null, tint = color, modifier = Modifier.size(30.dp) ) } IconShape.Random -> { Icon( imageVector = Icons.Rounded.Shuffle, contentDescription = null, tint = color, modifier = Modifier.size(30.dp) ) } else -> { Box( modifier = Modifier .size(30.dp) .container( borderWidth = 2.dp, borderColor = color, color = Color.Transparent, shape = iconShape.shape ) ) } } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ImagePickerModeSettingItemGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.settings.presentation.model.PicturePickerMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun ImagePickerModeSettingItemGroup( onValueChange: (Int) -> Unit, modifier: Modifier = Modifier ) { val settingsState = LocalSettingsState.current Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp) ) { val data = remember { PicturePickerMode.entries } data.forEachIndexed { index, mode -> val selected = settingsState.picturePickerMode.ordinal == mode.ordinal val shape = ShapeDefaults.byIndex( index = index, size = data.size ) PreferenceItem( shape = shape, onClick = { onValueChange(mode.ordinal) }, title = stringResource(mode.title), startIcon = mode.icon, subtitle = stringResource(mode.subtitle), containerColor = takeColorFromScheme { if (selected) secondaryContainer.copy(0.7f) else SafeLocalContainerColor }, contentColor = takeColorFromScheme { if (selected) onSecondaryContainer else MaterialTheme.colorScheme.onBackground }, endIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else { Icons.Rounded.RadioButtonUnchecked }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) { MaterialTheme.colorScheme.onSecondaryContainer.copy(0.5f) } else Color.Transparent ).value, shape = shape ) ) } } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/IssueTrackerSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.BugReport import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.ISSUE_TRACKER import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun IssueTrackerSettingItem( shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val linkHandler = LocalUriHandler.current PreferenceItem( shape = shape, modifier = modifier, title = stringResource(R.string.issue_tracker), subtitle = stringResource(R.string.issue_tracker_sub), startIcon = Icons.Outlined.BugReport, onClick = { linkHandler.openUri(ISSUE_TRACKER) } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/KeepDateTimeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AccessTime import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun KeepDateTimeSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.keep_date_time), subtitle = stringResource(R.string.keep_date_time_sub), checked = settingsState.keepDateTime, onClick = { onClick() }, startIcon = Icons.Outlined.AccessTime ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/LockDrawOrientationSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MobileRotateLock import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun LockDrawOrientationSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, title = stringResource(R.string.lock_draw_orientation), subtitle = stringResource(R.string.lock_draw_orientation_sub), checked = settingsState.lockDrawOrientation, startIcon = Icons.Outlined.MobileRotateLock ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MagnifierSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ZoomIn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun MagnifierSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.magnifier), subtitle = stringResource(R.string.magnifier_sub), checked = settingsState.magnifierEnabled, onClick = { onClick() }, startIcon = Icons.Outlined.ZoomIn ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/MainScreenTitleSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Title import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun MainScreenTitleSettingItem( onValueChange: (String) -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showDialog by rememberSaveable { mutableStateOf(false) } PreferenceItem( shape = shape, onClick = { showDialog = true }, title = stringResource(R.string.main_screen_title), subtitle = settingsState.mainScreenTitle, endIcon = Icons.Rounded.MiniEdit, startIcon = Icons.Rounded.Title, modifier = modifier ) var value by remember(showDialog) { mutableStateOf( settingsState.mainScreenTitle ) } EnhancedAlertDialog( visible = showDialog, onDismissRequest = { showDialog = false }, icon = { Icon( imageVector = Icons.Rounded.Title, contentDescription = null ) }, title = { Text(stringResource(R.string.main_screen_title)) }, text = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { OutlinedTextField( placeholder = { Text( text = stringResource(R.string.app_name), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) }, shape = ShapeDefaults.default, value = value, textStyle = MaterialTheme.typography.titleMedium.copy( textAlign = TextAlign.Center ), onValueChange = { value = it } ) } }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer.copy( alpha = if (settingsState.isNightMode) 0.5f else 1f ), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, onClick = { onValueChange(value.trim()) showDialog = false }, borderColor = MaterialTheme.colorScheme.outlineVariant( onTopOf = MaterialTheme.colorScheme.secondaryContainer ), ) { Text(stringResource(R.string.ok)) } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/NightModeSettingItemGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.DarkMode import androidx.compose.material.icons.outlined.LightMode import androidx.compose.material.icons.outlined.SettingsSuggest import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.NightMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun NightModeSettingItemGroup( value: NightMode, onValueChange: (NightMode) -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp) ) { val settingsState = LocalSettingsState.current listOf( Triple( stringResource(R.string.dark), Icons.Outlined.DarkMode, NightMode.Dark ), Triple( stringResource(R.string.light), Icons.Outlined.LightMode, NightMode.Light ), Triple( stringResource(R.string.system), Icons.Outlined.SettingsSuggest, NightMode.System ), ).forEachIndexed { index, (title, icon, nightMode) -> val selected = nightMode == value val shape = ShapeDefaults.byIndex(index, 3) PreferenceItem( onClick = { onValueChange(nightMode) }, title = title, containerColor = takeColorFromScheme { if (selected) secondaryContainer.copy(0.7f) else SafeLocalContainerColor }, shape = shape, startIcon = icon, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme.colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = shape ), endIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else Icons.Rounded.RadioButtonUnchecked ) } } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OneTimeSaveLocationSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.LocationSearching import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun OneTimeSaveLocationSettingItem( shape: Shape = ShapeDefaults.default, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { var showDialog by rememberSaveable { mutableStateOf(false) } PreferenceItem( shape = shape, onClick = { showDialog = true }, title = stringResource(R.string.one_time_save_location), subtitle = stringResource(R.string.one_time_save_location_sub), startIcon = Icons.Rounded.LocationSearching, endIcon = Icons.Rounded.MiniEdit, modifier = modifier ) OneTimeSaveLocationSelectionDialog( visible = showDialog, onDismiss = { showDialog = false }, onSaveRequest = null ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenEditInsteadOfPreviewSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.EditAlt import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun OpenEditInsteadOfPreviewSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.open_edit_instead_of_preview), subtitle = stringResource(R.string.open_edit_instead_of_preview_sub), checked = settingsState.openEditInsteadOfPreview, onClick = { onClick() }, startIcon = Icons.Rounded.EditAlt ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OpenSourceLicensesSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.License import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun OpenSourceLicensesSettingItem( shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp), onClick: () -> Unit ) { PreferenceItem( shape = shape, modifier = modifier, title = stringResource(R.string.open_source_licenses), subtitle = stringResource(R.string.open_source_licenses_sub), startIcon = Icons.Outlined.License, onClick = onClick ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/OverwriteFilesSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FileReplace import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun OverwriteFilesSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, enabled = settingsState.filenameBehavior is FilenameBehavior.None || settingsState.filenameBehavior is FilenameBehavior.Overwrite, title = stringResource(R.string.overwrite_files), subtitle = stringResource(R.string.overwrite_files_sub), checked = settingsState.filenameBehavior is FilenameBehavior.Overwrite, startIcon = Icons.Outlined.FileReplace ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/PresetsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Numbers import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalEditPresetsController import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun PresetsSettingItem( shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val editPresetsController = LocalEditPresetsController.current val settingsState = LocalSettingsState.current PreferenceItem( shape = shape, onClick = editPresetsController::open, title = stringResource(R.string.values), subtitle = settingsState.presets.joinToString(", "), startIcon = Icons.Outlined.Numbers, endIcon = Icons.Rounded.MiniEdit, modifier = modifier ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RandomizeFilenameSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Symbol import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun RandomizeFilenameSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, enabled = settingsState.filenameBehavior is FilenameBehavior.None || settingsState.filenameBehavior is FilenameBehavior.Random, onClick = { onClick() }, title = stringResource(R.string.randomize_filename), subtitle = stringResource(R.string.randomize_filename_sub), checked = settingsState.filenameBehavior is FilenameBehavior.Random, startIcon = Icons.Rounded.Symbol ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ReplaceSequenceNumberSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Numeric import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ReplaceSequenceNumberSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, enabled = settingsState.filenameBehavior is FilenameBehavior.None, title = stringResource(R.string.replace_sequence_number), subtitle = stringResource(R.string.replace_sequence_number_sub), checked = settingsState.addSequenceNumber, startIcon = Icons.Filled.Numeric ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ResetSettingsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.RestartAlt import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContainerColor import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContentColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun ResetSettingsSettingItem( onReset: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { var showResetDialog by remember { mutableStateOf(false) } CompositionLocalProvider( LocalIconShapeContentColor provides MaterialTheme.colorScheme.onErrorContainer, LocalIconShapeContainerColor provides MaterialTheme.colorScheme.errorContainer ) { PreferenceItem( onClick = { showResetDialog = true }, shape = shape, modifier = modifier, containerColor = MaterialTheme.colorScheme .errorContainer .copy(alpha = 0.5f), title = stringResource(R.string.reset), subtitle = stringResource(R.string.reset_settings_sub), startIcon = Icons.Rounded.RestartAlt, overrideIconShapeContentColor = true ) } ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, onReset = { showResetDialog = false onReset() }, title = stringResource(R.string.reset), text = stringResource(R.string.reset_settings_sub), icon = Icons.Rounded.RestartAlt ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/RestoreSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import android.net.Uri import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.DownloadFile import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun RestoreSettingItem( onObtainBackupFile: (uri: Uri) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val filePicker = rememberFilePicker(onSuccess = onObtainBackupFile) PreferenceItem( onClick = filePicker::pickFile, shape = shape, modifier = modifier, title = stringResource(R.string.restore), subtitle = stringResource(R.string.restore_sub), startIcon = Icons.Outlined.DownloadFile ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SavingFolderSettingItemGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import android.net.Uri import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FolderShared import androidx.compose.material.icons.outlined.FolderSpecial import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFolderPicker import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.utils.uiPath @Composable fun SavingFolderSettingItemGroup( modifier: Modifier = Modifier, onValueChange: (Uri?) -> Unit ) { Column(modifier) { val settingsState = LocalSettingsState.current val currentFolderUri = settingsState.saveFolderUri val launcher = rememberFolderPicker( onSuccess = onValueChange ) PreferenceItem( shape = ShapeDefaults.top, onClick = { onValueChange(null) }, title = stringResource(R.string.def), subtitle = stringResource(R.string.default_folder), containerColor = takeColorFromScheme { if (currentFolderUri == null) secondaryContainer.copy(0.7f) else SafeLocalContainerColor }, startIcon = Icons.Outlined.FolderSpecial, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .border( width = settingsState.borderWidth, color = animateColorAsState( if (currentFolderUri == null) { MaterialTheme.colorScheme.onSecondaryContainer.copy(0.5f) } else Color.Transparent ).value, shape = ShapeDefaults.top ) ) Spacer(modifier = Modifier.height(4.dp)) PreferenceItem( shape = ShapeDefaults.bottom, onClick = { launcher.pickFolder(currentFolderUri) }, title = stringResource(R.string.custom), subtitle = currentFolderUri.uiPath( default = stringResource(R.string.unspecified) ), containerColor = takeColorFromScheme { if (currentFolderUri != null) secondaryContainer.copy(0.7f) else Color.Unspecified }, startIcon = Icons.Outlined.FolderShared, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) .border( width = settingsState.borderWidth, color = animateColorAsState( if (currentFolderUri != null) { MaterialTheme.colorScheme.onSecondaryContainer.copy(0.5f) } else Color.Transparent ).value, shape = ShapeDefaults.bottom ) ) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenOrderSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FormatLineSpacing import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.BatchPrediction import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.Stacks import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.enhanced.longPress import com.t8rin.imagetoolbox.core.ui.widget.enhanced.press import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.getString import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyListState @Composable fun ScreenOrderSettingItem( onValueChange: (List) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val screenList by remember(settingsState.screenList, settingsState.favoriteScreenList) { derivedStateOf { val fav = settingsState.favoriteScreenList.mapNotNull { Screen.entries.find { s -> s.id == it } } val other = settingsState.screenList.mapNotNull { Screen.entries.find { s -> s.id == it } }.ifEmpty { Screen.entries }.filter { it !in fav } fav.plus(other).distinctBy { it.id } } } var showArrangementSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( shape = shape, modifier = modifier, onClick = { showArrangementSheet = true }, onDisabledClick = { AppToastHost.showToast( icon = Icons.Outlined.BatchPrediction, message = getString(R.string.cannot_change_arrangement_while_options_grouping_enabled) ) }, enabled = !settingsState.groupOptionsByTypes, startIcon = Icons.Outlined.FormatLineSpacing, title = stringResource(R.string.order), subtitle = stringResource(R.string.order_sub), endIcon = Icons.Rounded.MiniEdit, ) EnhancedModalBottomSheet( visible = showArrangementSheet, onDismiss = { showArrangementSheet = it }, title = { TitleItem( text = stringResource(R.string.order), icon = Icons.Rounded.Stacks ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showArrangementSheet = false } ) { AutoSizeText(stringResource(R.string.close)) } }, sheetContent = { Box { val data = remember(screenList) { mutableStateOf(screenList) } val listState = rememberLazyListState() val haptics = LocalHapticFeedback.current val state = rememberReorderableLazyListState( lazyListState = listState, onMove = { from, to -> haptics.press() data.value = data.value.toMutableList().apply { add(to.index, removeAt(from.index)) } } ) LazyColumn( state = listState, contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp), flingBehavior = enhancedFlingBehavior() ) { itemsIndexed( items = data.value, key = { _, s -> s.id } ) { index, screen -> ReorderableItem( state = state, key = screen.id ) { isDragging -> PreferenceItem( modifier = Modifier .fillMaxWidth() .longPressDraggableHandle( onDragStarted = { haptics.longPress() }, onDragStopped = { onValueChange(data.value) } ) .scale( animateFloatAsState( if (isDragging) 1.05f else 1f ).value ), title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), startIcon = screen.icon, endIcon = Icons.Rounded.DragHandle, containerColor = if (screen.id in settingsState.favoriteScreenList) { MaterialTheme.colorScheme.secondaryContainer } else Color.Unspecified, shape = ShapeDefaults.byIndex( index = index, size = data.value.size ) ) } } } } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ScreenSearchSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.LayersSearchOutline import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ScreenSearchSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, title = stringResource(R.string.search_option), startIcon = Icons.Outlined.LayersSearchOutline, subtitle = stringResource(R.string.search_option_sub), checked = settingsState.screensSearchEnabled, onClick = { onClick() } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SearchableSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.settings.presentation.model.IconShape import com.t8rin.imagetoolbox.core.settings.presentation.model.Setting import com.t8rin.imagetoolbox.core.settings.presentation.model.SettingsGroup import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.IconShapeContainer import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent @Composable internal fun SearchableSettingItem( modifier: Modifier = Modifier, group: SettingsGroup, setting: Setting, shape: Shape, component: SettingsComponent, onNavigateToEasterEgg: () -> Unit, onNavigateToSettings: () -> Unit, onNavigateToLibrariesInfo: () -> Unit, isUpdateAvailable: Boolean ) { Column( modifier = modifier.container( resultPadding = 0.dp, shape = shape ) ) { val settingState = LocalSettingsState.current val iconShape = remember(settingState.iconShape) { derivedStateOf { settingState.iconShape?.takeOrElseFrom(IconShape.entries) } }.value Row( modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { IconShapeContainer( iconShape = iconShape?.copy( iconSize = 16.dp ) ) { Icon( imageVector = group.icon, contentDescription = null, modifier = Modifier.size(16.dp) ) } Spacer(Modifier.width(8.dp)) Text( text = stringResource(id = group.titleId), fontSize = 12.sp ) } val itemShape = when (setting) { is Setting.ImagePickerMode -> null is Setting.NightMode -> null else -> ShapeDefaults.small } ProvideContainerDefaults(itemShape) { SettingItem( setting = setting, component = component, isUpdateAvailable = isUpdateAvailable, onNavigateToEasterEgg = onNavigateToEasterEgg, onNavigateToSettings = onNavigateToSettings, onNavigateToLibrariesInfo = onNavigateToLibrariesInfo ) } Spacer(Modifier.height(8.dp)) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SecureModeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Security import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun SecureModeSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.secure_mode), subtitle = stringResource(R.string.secure_mode_sub), checked = settingsState.isSecureMode, onClick = { onClick() }, startIcon = Icons.Outlined.Security ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SendLogsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MobileShare import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.utils.animation.springySpec import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCircularProgressIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import kotlinx.coroutines.delay @Composable fun SendLogsSettingItem( onClick: (onComplete: () -> Unit) -> Unit, modifier: Modifier = Modifier.padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.center, color: Color = MaterialTheme.colorScheme.mixedContainer.copy(0.9f), contentColor: Color = MaterialTheme.colorScheme.onMixedContainer ) { var isLoading by rememberSaveable { mutableStateOf(false) } val progressAnimatable = remember { Animatable(if (isLoading) 1f else 0f) } val progress = progressAnimatable.value LaunchedEffect(isLoading) { delay(400) if (isLoading) { progressAnimatable.animateTo( targetValue = 1f, animationSpec = springySpec() ) } else { progressAnimatable.animateTo( targetValue = 0f, animationSpec = tween(200) ) } } PreferenceItemOverload( contentColor = contentColor, shape = shape, onClick = { isLoading = true onClick { isLoading = false } }, startIcon = { Icon( imageVector = Icons.Outlined.MobileShare, contentDescription = null ) }, title = stringResource(R.string.send_logs), subtitle = stringResource(R.string.send_logs_sub), containerColor = color, modifier = modifier, endIcon = if (progress > 0f) { { EnhancedCircularProgressIndicator( modifier = Modifier.size(24.dp * progress), trackColor = MaterialTheme.colorScheme.primary.copy(0.2f), strokeWidth = 3.dp ) } } else null ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingGroupItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Settings import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSimpleSettingsInteractor import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.getString import kotlinx.coroutines.launch @Composable fun SettingGroupItem( groupKey: Int, icon: ImageVector, text: String, initialState: Boolean = false, modifier: Modifier = Modifier .fillMaxWidth() .padding(2.dp), content: @Composable ColumnScope.(Boolean) -> Unit ) { val settingsState = LocalSettingsState.current val initialState = settingsState.settingGroupsInitialVisibility[groupKey] ?: initialState val simpleSettingsInteractor = LocalSimpleSettingsInteractor.current val scope = rememberCoroutineScope() ExpandableItem( modifier = modifier, visibleContent = { TitleItem( modifier = Modifier.padding(start = 8.dp), icon = icon, text = text ) }, color = takeColorFromScheme { surfaceContainer.blend( surfaceContainerLowest, 0.4f ) }, onLongClick = { scope.launch { simpleSettingsInteractor.toggleSettingsGroupVisibility( key = groupKey, value = !initialState ) AppToastHost.showToast( message = getString( if (initialState) { R.string.settings_group_visibility_hidden } else { R.string.settings_group_visibility_visible }, text ), icon = Icons.Outlined.Settings ) } }, expandableContent = content, initialState = initialState ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.activity.compose.LocalActivity import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileDownloadOff import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.TextFields import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.Setting import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalContainerShape import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberCurrentLifecycleEvent import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic.SettingsComponent import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable internal fun SettingItem( setting: Setting, component: SettingsComponent, onNavigateToEasterEgg: () -> Unit, onNavigateToSettings: () -> Unit, onNavigateToLibrariesInfo: () -> Unit, isUpdateAvailable: Boolean, containerColor: Color = MaterialTheme.colorScheme.surface, ) { ProvideContainerDefaults( color = containerColor, shape = LocalContainerShape.current ) { when (setting) { Setting.AddFileSize -> { AddFileSizeSettingItem(onClick = component::toggleAddFileSize) } Setting.AddOriginalFilename -> { AddOriginalFilenameSettingItem(onClick = component::toggleAddOriginalFilename) } Setting.AllowBetas -> { if (!appContext.isInstalledFromPlayStore()) { AllowBetasSettingItem( onClick = { component.toggleAllowBetas() component.tryGetUpdate() } ) } } Setting.AllowImageMonet -> { AllowImageMonetSettingItem(onClick = component::toggleAllowImageMonet) } Setting.AmoledMode -> { AmoledModeSettingItem(onClick = component::toggleAmoledMode) } Setting.Analytics -> { AnalyticsSettingItem(onClick = component::toggleAllowCollectAnalytics) } Setting.Author -> { AuthorSettingItem() } Setting.AutoCacheClear -> { AutoCacheClearSettingItem(onClick = component::toggleClearCacheOnLaunch) } Setting.AutoCheckUpdates -> { AutoCheckUpdatesSettingItem(onClick = component::toggleShowUpdateDialog) } Setting.Backup -> { BackupSettingItem( onCreateBackupFilename = component::createBackupFilename, onCreateBackup = component::createBackup ) } Setting.BorderThickness -> { BorderThicknessSettingItem(onValueChange = component::setBorderWidth) } Setting.ChangeFont -> { val context = LocalComponentActivity.current ChangeFontSettingItem( onValueChange = { font -> component.setFont(font.asDomain()) context.recreate() }, onAddFont = { component.importCustomFont( uri = it, onSuccess = AppToastHost::showConfetti, onFailure = { AppToastHost.showToast( message = getString(R.string.wrong_font), icon = Icons.Rounded.TextFields ) } ) }, onRemoveFont = component::removeCustomFont, onExportFonts = component::exportFonts ) } Setting.ChangeLanguage -> { ChangeLanguageSettingItem() } Setting.ClearCache -> { val lifecycleEvent = rememberCurrentLifecycleEvent() ClearCacheSettingItem( value = remember(lifecycleEvent) { component.getReadableCacheSize() }, onClearCache = component::clearCache ) } Setting.ColorScheme -> { ColorSchemeSettingItem( onToggleInvertColors = component::toggleInvertColors, onSetThemeStyle = component::setThemeStyle, onUpdateThemeContrast = component::setThemeContrast, onUpdateColorTuple = component::setColorTuple, onUpdateColorTuples = component::setColorTuples, onToggleUseEmojiAsPrimaryColor = component::toggleUseEmojiAsPrimaryColor ) } Setting.Crashlytics -> { CrashlyticsSettingItem(onClick = component::toggleAllowCollectCrashlytics) } Setting.CurrentVersionCode -> { var clicks by rememberSaveable { mutableIntStateOf(0) } LaunchedEffect(clicks) { if (clicks >= 3) { onNavigateToEasterEgg() clicks = 0 } delay(500L) //debounce if (clicks == 0) return@LaunchedEffect AppToastHost.dismissToasts() if (clicks == 1) { component.tryGetUpdate(true) { AppToastHost.showToast( icon = Icons.Rounded.FileDownloadOff, message = getString(R.string.no_updates) ) } } } CurrentVersionCodeSettingItem( isUpdateAvailable = isUpdateAvailable, onClick = { clicks++ } ) } Setting.Donate -> { DonateSettingItem() } Setting.DynamicColors -> { DynamicColorsSettingItem(onClick = component::toggleDynamicColors) } Setting.Emoji -> { EmojiSettingItem( onAddColorTupleFromEmoji = component::addColorTupleFromEmoji, selectedEmojiIndex = component.settingsState.selectedEmoji ?: 0, onUpdateEmoji = component::setEmoji ) } Setting.EmojisCount -> { EmojisCountSettingItem(onValueChange = component::setEmojisCount) } Setting.FabAlignment -> { FabAlignmentSettingItem(onValueChange = component::setAlignment) } Setting.FilenamePrefix -> { FilenamePrefixSettingItem(onValueChange = component::setFilenamePrefix) } Setting.FilenameSuffix -> { FilenameSuffixSettingItem(onValueChange = component::setFilenameSuffix) } Setting.FontScale -> { val context = LocalComponentActivity.current FontScaleSettingItem( onValueChange = { component.setFontScale(it) context.recreate() } ) } Setting.GroupOptions -> { GroupOptionsSettingItem(onClick = component::toggleGroupOptionsByType) } Setting.HelpTranslate -> { HelpTranslateSettingItem() } Setting.ImagePickerMode -> { ImagePickerModeSettingItemGroup(onValueChange = component::setImagePickerMode) } Setting.IssueTracker -> { IssueTrackerSettingItem() } Setting.LockDrawOrientation -> { LockDrawOrientationSettingItem(onClick = component::toggleLockDrawOrientation) } Setting.NightMode -> { NightModeSettingItemGroup( value = component.settingsState.nightMode, onValueChange = component::setNightMode ) } Setting.Presets -> { PresetsSettingItem() } Setting.RandomizeFilename -> { RandomizeFilenameSettingItem(onClick = component::toggleRandomizeFilename) } Setting.ReplaceSequenceNumber -> { ReplaceSequenceNumberSettingItem(onClick = component::toggleAddSequenceNumber) } Setting.OverwriteFiles -> { OverwriteFilesSettingItem(onClick = component::toggleOverwriteFiles) } Setting.Reset -> { ResetSettingsSettingItem(onReset = component::resetSettings) } Setting.Restore -> { val scope = rememberCoroutineScope() val context = LocalActivity.current RestoreSettingItem( onObtainBackupFile = { uri -> component.restoreBackupFrom( uri = uri, onSuccess = { scope.launch { AppToastHost.showConfetti() //Wait for confetti to appear, then trigger font scale adjustment delay(300L) context?.recreate() } AppToastHost.showToast( message = getString(R.string.settings_restored), icon = Icons.Rounded.Save ) }, onFailure = AppToastHost::showFailureToast ) } ) } Setting.SavingFolder -> { SavingFolderSettingItemGroup(onValueChange = component::setSaveFolderUri) } Setting.ScreenOrder -> { ScreenOrderSettingItem(onValueChange = component::setScreenOrder) } Setting.ScreenSearch -> { ScreenSearchSettingItem(onClick = component::toggleScreenSearchEnabled) } Setting.SourceCode -> { SourceCodeSettingItem() } Setting.TelegramGroup -> { TelegramGroupSettingItem() } Setting.TelegramChannel -> { TelegramChannelSettingItem() } Setting.FreeSoftwarePartner -> { FreeSoftwarePartnerSettingItem() } Setting.CheckUpdatesButton -> { CheckUpdatesButtonSettingItem( onClick = { component.tryGetUpdate(true) { AppToastHost.showToast( icon = Icons.Rounded.FileDownloadOff, message = getString(R.string.no_updates) ) } } ) } Setting.ContainerShadows -> { ContainerShadowsSettingItem(onClick = component::toggleDrawContainerShadows) } Setting.ButtonShadows -> { ButtonShadowsSettingItem(onClick = component::toggleDrawButtonShadows) } Setting.FABShadows -> { FabShadowsSettingItem(onClick = component::toggleDrawFabShadows) } Setting.SliderShadows -> { SliderShadowsSettingItem(onClick = component::toggleDrawSliderShadows) } Setting.SwitchShadows -> { SwitchShadowsSettingItem(onClick = component::toggleDrawSwitchShadows) } Setting.AppBarShadows -> { AppBarShadowsSettingItem(onClick = component::toggleDrawAppBarShadows) } Setting.AutoPinClipboard -> { AutoPinClipboardSettingItem(onClick = component::toggleAutoPinClipboard) } Setting.AutoPinClipboardOnlyClip -> { AutoPinClipboardOnlyClipSettingItem(onClick = component::toggleAutoPinClipboardOnlyClip) } Setting.VibrationStrength -> { VibrationStrengthSettingItem(onValueChange = component::setVibrationStrength) } Setting.DefaultScaleMode -> { DefaultScaleModeSettingItem(onValueChange = component::setDefaultImageScaleMode) } Setting.DefaultColorSpace -> { DefaultColorSpaceSettingItem(onValueChange = component::setDefaultImageScaleMode) } Setting.SwitchType -> { SwitchTypeSettingItem(onValueChange = component::setSwitchType) } Setting.Magnifier -> { MagnifierSettingItem(onClick = component::toggleMagnifierEnabled) } Setting.ExifWidgetInitialState -> { ExifWidgetInitialStateSettingItem(onClick = component::toggleExifWidgetInitialState) } Setting.BrightnessEnforcement -> { BrightnessEnforcementSettingItem(onValueChange = component::setScreensWithBrightnessEnforcement) } Setting.Confetti -> { ConfettiSettingItem(onClick = component::toggleConfettiEnabled) } Setting.SecureMode -> { SecureModeSettingItem(onClick = component::toggleSecureMode) } Setting.UseRandomEmojis -> { UseRandomEmojisSettingItem(onClick = component::toggleUseRandomEmojis) } Setting.IconShape -> { IconShapeSettingItem( value = component.settingsState.iconShape, onValueChange = component::setIconShape ) } Setting.DragHandleWidth -> { DragHandleWidthSettingItem(onValueChange = component::setDragHandleWidth) } Setting.ConfettiType -> { ConfettiTypeSettingItem(onValueChange = component::setConfettiType) } Setting.AllowAutoClipboardPaste -> { AllowAutoClipboardPasteSettingItem(onClick = component::toggleAllowAutoClipboardPaste) } Setting.ConfettiHarmonizer -> { ConfettiHarmonizationColorSettingItem(onValueChange = component::setConfettiHarmonizer) } Setting.ConfettiHarmonizationLevel -> { ConfettiHarmonizationLevelSettingItem(onValueChange = component::setConfettiHarmonizationLevel) } Setting.GeneratePreviews -> { GeneratePreviewsSettingItem(onClick = component::toggleGeneratePreviews) } Setting.SkipFilePicking -> { SkipImagePickingSettingItem(onClick = component::toggleSkipImagePicking) } Setting.ShowSettingsInLandscape -> { ShowSettingsInLandscapeSettingItem(onClick = component::toggleShowSettingsInLandscape) } Setting.UseFullscreenSettings -> { UseFullscreenSettingsSettingItem( onClick = component::toggleUseFullscreenSettings, onNavigateToSettings = onNavigateToSettings ) } Setting.DefaultDrawLineWidth -> { DefaultDrawLineWidthSettingItem(onValueChange = component::setDefaultDrawLineWidth) } Setting.OpenEditInsteadOfPreview -> { OpenEditInsteadOfPreviewSettingItem(onClick = component::toggleOpenEditInsteadOfPreview) } Setting.CanEnterPresetsByTextField -> { CanEnterPresetsByTextFieldSettingItem(onClick = component::toggleCanEnterPresetsByTextField) } Setting.ColorBlindScheme -> { ColorBlindSchemeSettingItem(onValueChange = component::setColorBlindScheme) } Setting.EnableLinksPreview -> { EnableLinksPreviewSettingItem(onClick = component::toggleIsLinksPreviewEnabled) } Setting.DefaultDrawColor -> { DefaultDrawColorSettingItem(onValueChange = component::setDefaultDrawColor) } Setting.DefaultDrawPathMode -> { DefaultDrawPathModeSettingItem(onValueChange = component::setDefaultDrawPathMode) } Setting.AddTimestampToFilename -> { AddTimestampToFilenameSettingItem(onClick = component::toggleAddTimestampToFilename) } Setting.UseFormattedFilenameTimestamp -> { UseFormattedFilenameTimestampSettingItem(onClick = component::toggleUseFormattedFilenameTimestamp) } Setting.OneTimeSaveLocation -> { OneTimeSaveLocationSettingItem() } Setting.DefaultResizeType -> { DefaultResizeTypeSettingItem(onValueChange = component::setDefaultResizeType) } Setting.SystemBarsVisibility -> { SystemBarsVisibilitySettingItem(onValueChange = component::setSystemBarsVisibility) } Setting.ShowSystemBarsBySwipe -> { ShowSystemBarsBySwipeSettingItem(onClick = component::toggleIsSystemBarsVisibleBySwipe) } Setting.UseCompactSelectors -> { UseCompactSelectorsSettingItem(onClick = component::toggleUseCompactSelectors) } Setting.MainScreenTitle -> { MainScreenTitleSettingItem(onValueChange = component::setMainScreenTitle) } Setting.SliderType -> { SliderTypeSettingItem(onValueChange = component::setSliderType) } Setting.CenterAlignDialogButtons -> { CenterAlignDialogButtonsSettingItem(onClick = component::toggleIsCenterAlignDialogButtons) } Setting.OpenSourceLicenses -> { OpenSourceLicensesSettingItem(onClick = onNavigateToLibrariesInfo) } Setting.FastSettingsSide -> { FastSettingsSideSettingItem(onValueChange = component::setFastSettingsSide) } Setting.ChecksumAsFilename -> { ChecksumAsFilenameSettingItem(onValueChange = component::setChecksumTypeForFilename) } Setting.EnableToolExitConfirmation -> { EnableToolExitConfirmationSettingItem(onClick = component::toggleEnableToolExitConfirmation) } Setting.SendLogs -> { SendLogsSettingItem(onClick = component::shareLogs) } Setting.AddPresetToFilename -> { AddPresetToFilenameSettingItem(onClick = component::toggleAddPresetInfoToFilename) } Setting.AddImageScaleModeToFilename -> { AddImageScaleModeToFilenameSettingItem(onClick = component::toggleAddImageScaleModeInfoToFilename) } Setting.AllowSkipIfLarger -> { AllowSkipIfLargerSettingItem(onClick = component::toggleAllowSkipIfLarger) } Setting.EnableLauncherMode -> { EnableLauncherModeSettingItem(onClick = component::toggleIsScreenSelectionLauncherMode) } Setting.SnowfallMode -> { SnowfallModeSettingItem(onValueChange = component::setSnowfallMode) } Setting.DefaultImageFormat -> { DefaultImageFormatSettingItem(onValueChange = component::setDefaultImageFormat) } Setting.DefaultQuality -> { DefaultQualitySettingItem(onValueChange = component::setDefaultQuality) } Setting.ShapeType -> { ShapeTypeSettingItem(onValueChange = component::setShapesType) } Setting.CornersSize -> { CornersSizeSettingItem(onValueChange = component::setShapesType) } Setting.FilenamePattern -> { FilenamePatternSettingItem( onValueChange = component::setFilenamePattern, filenameCreator = component ) } Setting.FlingType -> { FlingTypeSettingItem(onValueChange = component::setFlingType) } Setting.ToolsHiddenForShare -> { ToolsHiddenForShareSettingItem(onValueChange = component::setHiddenForShareScreens) } Setting.KeepDateTime -> { KeepDateTimeSettingItem(onClick = component::toggleKeepDateTime) } Setting.EnableBackgroundColorForAlphaFormats -> { EnableBackgroundColorForAlphaFormatsSettingItem(onClick = component::toggleEnableBackgroundColorForAlphaFormats) } } } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShapeTypeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material.icons.rounded.RoundedCorner import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.AutoCornersShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun ShapeTypeSettingItem( onValueChange: (ShapeType) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( modifier = modifier, title = stringResource(id = R.string.shapes_type), startIcon = Icons.Rounded.RoundedCorner, subtitle = stringResource(settingsState.shapesType.title()), onClick = { showSheet = true }, shape = shape, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showSheet, onDismiss = { showSheet = it }, title = { TitleItem( text = stringResource(id = R.string.shape_type), icon = Icons.Rounded.RoundedCorner ) }, confirmButton = { EnhancedButton( onClick = { showSheet = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } } ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp) ) { val entries = ShapeType.entries entries.forEachIndexed { index, type -> val selected = type::class.isInstance(settingsState.shapesType) PreferenceItemOverload( onClick = { onValueChange(type.copy(settingsState.shapesType.strength)) }, title = stringResource(type.title()), subtitle = stringResource(type.subtitle()), containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor }, shape = ShapeDefaults.byIndex(index, entries.size), startIcon = { Spacer( modifier = Modifier .size(24.dp) .padding(2.dp) .border( width = 2.dp, color = LocalContentColor.current, shape = AutoCornersShape( size = when (type) { is ShapeType.Smooth -> 8.dp is ShapeType.Squircle -> 24.dp else -> 6.dp }, shapesType = type ) ) ) }, modifier = Modifier .fillMaxWidth() .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme.colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = ShapeDefaults.byIndex(index, entries.size) ), endIcon = { Icon( imageVector = if (selected) { Icons.Rounded.RadioButtonChecked } else Icons.Rounded.RadioButtonUnchecked, contentDescription = null ) } ) } } } } private fun ShapeType.title() = when (this) { is ShapeType.Cut -> R.string.cut is ShapeType.Rounded -> R.string.rounded is ShapeType.Smooth -> R.string.smooth is ShapeType.Squircle -> R.string.squircle } private fun ShapeType.subtitle() = when (this) { is ShapeType.Cut -> R.string.cut_shapes_sub is ShapeType.Rounded -> R.string.rounded_shapes_sub is ShapeType.Smooth -> R.string.smooth_shapes_sub is ShapeType.Squircle -> R.string.squircle_shapes_sub } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSettingsInLandscapeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MobileLandscape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ShowSettingsInLandscapeSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, enabled = !settingsState.useFullscreenSettings, title = stringResource(R.string.show_settings_in_landscape), subtitle = stringResource(R.string.show_settings_in_landscape_sub), checked = settingsState.showSettingsInLandscape, onClick = { onClick() }, startIcon = Icons.Outlined.MobileLandscape ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ShowSystemBarsBySwipeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.SwipeVertical import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun ShowSystemBarsBySwipeSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, title = stringResource(R.string.show_system_bars_by_swipe), subtitle = stringResource(R.string.show_system_bars_by_swipe_sub), checked = settingsState.isSystemBarsVisibleBySwipe, startIcon = Icons.Outlined.SwipeVertical ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SkipImagePickingSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.SkipNext import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun SkipImagePickingSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.skip_file_picking), subtitle = stringResource(R.string.skip_file_picking_sub), checked = settingsState.skipImagePicking, onClick = { onClick() }, startIcon = Icons.Outlined.SkipNext ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderShadowsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Slider import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun SliderShadowsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, enabled = settingsState.borderWidth <= 0.dp, shape = shape, title = stringResource(R.string.sliders_shadow), subtitle = stringResource(R.string.sliders_shadow_sub), checked = settingsState.drawSliderShadows, onClick = { onClick() }, startIcon = Icons.Rounded.Slider ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SliderTypeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Android import androidx.compose.material.icons.rounded.AutoAwesome import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.HyperOS import com.t8rin.imagetoolbox.core.resources.icons.MaterialDesign import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.Slider import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun SliderTypeSettingItem( onValueChange: (SliderType) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( modifier = modifier, title = stringResource(id = R.string.slider_type), startIcon = Icons.Rounded.Slider, subtitle = settingsState.sliderType.title, onClick = { showSheet = true }, shape = shape, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showSheet, onDismiss = { showSheet = it }, title = { TitleItem( text = stringResource(id = R.string.slider_type), icon = Icons.Rounded.Slider ) }, confirmButton = { EnhancedButton( onClick = { showSheet = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } } ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp) ) { val entries = remember { SliderType.entries } entries.forEachIndexed { index, type -> val selected = type == settingsState.sliderType PreferenceItem( onClick = { onValueChange(type) }, title = type.title, subtitle = type.subtitle, containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor }, shape = ShapeDefaults.byIndex(index, entries.size), startIcon = type.icon, modifier = Modifier .fillMaxWidth() .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme.colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = ShapeDefaults.byIndex(index, entries.size) ), endIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else Icons.Rounded.RadioButtonUnchecked ) } } } } private val SliderType.title: String @Composable get() = when (this) { SliderType.Fancy -> stringResource(R.string.fancy) SliderType.Material -> stringResource(R.string.material_2) SliderType.MaterialYou -> stringResource(R.string.material_you) SliderType.HyperOS -> stringResource(R.string.hyper_os) } private val SliderType.subtitle: String @Composable get() = when (this) { SliderType.Fancy -> stringResource(R.string.fancy_sub) SliderType.Material -> stringResource(R.string.material_2_sub) SliderType.MaterialYou -> stringResource(R.string.material_you_slider_sub) SliderType.HyperOS -> stringResource(R.string.hyper_os_sub) } private val SliderType.icon: ImageVector get() = when (this) { SliderType.Fancy -> Icons.Rounded.AutoAwesome SliderType.Material -> Icons.Rounded.Android SliderType.MaterialYou -> Icons.Outlined.MaterialDesign SliderType.HyperOS -> Icons.Outlined.HyperOS } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SnowfallModeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Snowflake import com.t8rin.imagetoolbox.core.settings.domain.model.SnowfallMode import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.state.derivedValueOf import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun SnowfallModeSettingItem( onValueChange: (SnowfallMode) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val value = settingsState.snowfallMode val entries = SnowfallMode.entries Column( modifier = modifier.container(shape = shape) ) { TitleItem( text = stringResource(R.string.snowfall_mode), icon = Icons.Outlined.Snowflake, iconEndPadding = 14.dp, modifier = Modifier .padding(horizontal = 12.dp) .padding(top = 8.dp) ) EnhancedButtonGroup( enabled = true, itemCount = entries.size, title = {}, modifier = Modifier.fillMaxWidth(), isScrollable = false, selectedIndex = derivedValueOf(value) { entries.indexOfFirst { it::class.isInstance(value) } }, activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer, itemContent = { Text(stringResource(entries[it].getTitle())) }, onIndexChange = { onValueChange(entries[it]) } ) } } private fun SnowfallMode.getTitle(): Int = when (this) { SnowfallMode.Auto -> R.string.auto SnowfallMode.Enabled -> R.string.enabled SnowfallMode.Disabled -> R.string.disabled } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SourceCodeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.APP_GITHUB_LINK import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Github import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContainerColor import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContentColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem @Composable fun SourceCodeSettingItem( modifier: Modifier = Modifier.padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.bottom, ) { val linkHandler = LocalUriHandler.current CompositionLocalProvider( LocalIconShapeContentColor provides MaterialTheme.colorScheme.onTertiaryContainer, LocalIconShapeContainerColor provides MaterialTheme.colorScheme.tertiaryContainer.blend( color = MaterialTheme.colorScheme.tertiary, fraction = 0.1f ) ) { PreferenceItem( contentColor = MaterialTheme.colorScheme.onTertiaryContainer, shape = shape, onClick = { linkHandler.openUri(APP_GITHUB_LINK) }, startIcon = Icons.Rounded.Github, title = stringResource(R.string.check_source_code), subtitle = stringResource(R.string.check_source_code_sub), containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(0.7f), modifier = modifier, overrideIconShapeContentColor = true ) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchShadowsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ToggleOff import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun SwitchShadowsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, enabled = settingsState.borderWidth <= 0.dp, shape = shape, title = stringResource(R.string.switches_shadow), subtitle = stringResource(R.string.switches_shadow_sub), checked = settingsState.drawSwitchShadows, onClick = { onClick() }, startIcon = Icons.Outlined.ToggleOff ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SwitchTypeSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ToggleOff import androidx.compose.material.icons.outlined.ToggleOn import androidx.compose.material.icons.rounded.Android import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material.icons.rounded.Water import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Cube import com.t8rin.imagetoolbox.core.resources.icons.HyperOS import com.t8rin.imagetoolbox.core.resources.icons.IOS import com.t8rin.imagetoolbox.core.resources.icons.MaterialDesign import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.SamsungLetter import com.t8rin.imagetoolbox.core.resources.icons.Windows import com.t8rin.imagetoolbox.core.settings.domain.model.SwitchType import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.provider.SafeLocalContainerColor import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun SwitchTypeSettingItem( onValueChange: (SwitchType) -> Unit, shape: Shape = ShapeDefaults.top, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current var showSheet by rememberSaveable { mutableStateOf(false) } PreferenceItem( modifier = modifier, title = stringResource(id = R.string.switch_type), startIcon = Icons.Outlined.ToggleOn, subtitle = settingsState.switchType.title, onClick = { showSheet = true }, shape = shape, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showSheet, onDismiss = { showSheet = it }, title = { TitleItem( text = stringResource(id = R.string.switch_type), icon = Icons.Outlined.ToggleOff ) }, confirmButton = { EnhancedButton( onClick = { showSheet = false }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Text(stringResource(R.string.close)) } } ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(8.dp) ) { val entries = remember { SwitchType.entries } entries.forEachIndexed { index, type -> val selected = type == settingsState.switchType PreferenceItem( onClick = { onValueChange(type) }, title = type.title, subtitle = type.subtitle, containerColor = takeColorFromScheme { if (selected) secondaryContainer else SafeLocalContainerColor }, shape = ShapeDefaults.byIndex(index, entries.size), startIcon = type.icon, modifier = Modifier .fillMaxWidth() .border( width = settingsState.borderWidth, color = animateColorAsState( if (selected) MaterialTheme.colorScheme .onSecondaryContainer .copy(alpha = 0.5f) else Color.Transparent ).value, shape = ShapeDefaults.byIndex(index, entries.size) ), endIcon = if (selected) { Icons.Rounded.RadioButtonChecked } else Icons.Rounded.RadioButtonUnchecked ) } } } } private val SwitchType.title: String @Composable get() = when (this) { SwitchType.MaterialYou -> stringResource(R.string.material_you) SwitchType.Compose -> stringResource(R.string.compose) SwitchType.Pixel -> stringResource(R.string.pixel_switch) SwitchType.Fluent -> stringResource(R.string.fluent_switch) SwitchType.Cupertino -> stringResource(R.string.cupertino_switch) SwitchType.LiquidGlass -> stringResource(R.string.liquid_glass) SwitchType.HyperOS -> stringResource(R.string.hyper_os) SwitchType.OneUI -> stringResource(R.string.one_ui) } private val SwitchType.subtitle: String @Composable get() = when (this) { SwitchType.MaterialYou -> stringResource(R.string.material_you_switch_sub) SwitchType.Compose -> stringResource(R.string.compose_switch_sub) SwitchType.Pixel -> stringResource(R.string.use_pixel_switch_sub) SwitchType.Fluent -> stringResource(R.string.fluent_switch_sub) SwitchType.Cupertino -> stringResource(R.string.cupertino_switch_sub) SwitchType.LiquidGlass -> stringResource(R.string.liquid_glass_sub) SwitchType.HyperOS -> stringResource(R.string.hyper_os_sub) SwitchType.OneUI -> stringResource(R.string.one_ui_sub) } private val SwitchType.icon: ImageVector get() = when (this) { SwitchType.MaterialYou -> Icons.Outlined.MaterialDesign SwitchType.Compose -> Icons.Outlined.Cube SwitchType.Pixel -> Icons.Rounded.Android SwitchType.Fluent -> Icons.Rounded.Windows SwitchType.Cupertino -> Icons.Rounded.IOS SwitchType.LiquidGlass -> Icons.Rounded.Water SwitchType.HyperOS -> Icons.Outlined.HyperOS SwitchType.OneUI -> Icons.Outlined.SamsungLetter } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/SystemBarsVisibilitySettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.TelevisionAmbientLight import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun SystemBarsVisibilitySettingItem( onValueChange: (SystemBarsVisibility) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val items = remember { SystemBarsVisibility.entries } Column( modifier = modifier.container( shape = shape ) ) { TitleItem( modifier = Modifier.padding( top = 12.dp, end = 12.dp, bottom = 16.dp, start = 12.dp ), iconEndPadding = 14.dp, text = stringResource(R.string.system_bars_visibility), icon = Icons.Outlined.TelevisionAmbientLight ) FlowRow( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterHorizontally ), modifier = Modifier .fillMaxWidth() .padding(start = 8.dp, bottom = 8.dp, end = 8.dp) ) { val value = settingsState.systemBarsVisibility items.forEach { item -> EnhancedChip( onClick = { onValueChange(item) }, selected = item::class.isInstance(value), label = { Text(text = item.title) }, contentPadding = PaddingValues(horizontal = 16.dp, vertical = 6.dp), selectedColor = MaterialTheme.colorScheme.tertiary, unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant ) } } } } private val SystemBarsVisibility.title: String @Composable get() = stringResource( when (this) { SystemBarsVisibility.Auto -> R.string.auto SystemBarsVisibility.HideAll -> R.string.hide_all SystemBarsVisibility.HideNavigationBar -> R.string.hide_nav_bar SystemBarsVisibility.HideStatusBar -> R.string.hide_status_bar SystemBarsVisibility.ShowAll -> R.string.show_all } ) ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramChannelSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.RssFeed import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.TELEGRAM_CHANNEL_LINK import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow @Composable fun TelegramChannelSettingItem( shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val linkHandler = LocalUriHandler.current PreferenceRow( shape = shape, onClick = { linkHandler.openUri(TELEGRAM_CHANNEL_LINK) }, startIcon = Icons.Outlined.RssFeed, title = stringResource(R.string.ci_channel), subtitle = stringResource(R.string.ci_channel_sub), color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.7f), contentColor = MaterialTheme.colorScheme.onPrimaryContainer, modifier = modifier ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/TelegramGroupSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.TELEGRAM_GROUP_LINK import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Telegram import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow @Composable fun TelegramGroupSettingItem( shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val linkHandler = LocalUriHandler.current PreferenceRow( shape = shape, onClick = { linkHandler.openUri(TELEGRAM_GROUP_LINK) }, startIcon = Icons.Rounded.Telegram, title = stringResource(R.string.tg_chat), subtitle = stringResource(R.string.tg_chat_sub), color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f), contentColor = MaterialTheme.colorScheme.onSecondaryContainer, modifier = modifier ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/ToolsHiddenForShareSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastAny import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.MiniEdit import com.t8rin.imagetoolbox.core.resources.icons.ShareOff import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedCheckbox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun ToolsHiddenForShareSettingItem( onValueChange: (Screen) -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current val settingsScreenList = settingsState.hiddenForShareScreens val screenList by remember(settingsScreenList) { derivedStateOf { settingsScreenList.mapNotNull { Screen.entries.find { s -> s.id == it } } } } var showPickerSheet by rememberSaveable { mutableStateOf(false) } val context = LocalResourceManager.current val subtitle by remember(screenList, context) { derivedStateOf { screenList.joinToString(separator = ", ") { context.getString(it.title) }.ifEmpty { context.getString(R.string.disabled) } } } PreferenceItem( shape = shape, modifier = modifier, onClick = { showPickerSheet = true }, startIcon = Icons.Outlined.ShareOff, title = stringResource(R.string.hidden_for_share), subtitle = subtitle, endIcon = Icons.Rounded.MiniEdit ) EnhancedModalBottomSheet( visible = showPickerSheet, onDismiss = { showPickerSheet = it }, title = { TitleItem( text = stringResource(R.string.brightness), icon = Icons.Rounded.ShareOff ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { showPickerSheet = false } ) { AutoSizeText(stringResource(R.string.close)) } }, sheetContent = { Box { LazyColumn( contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior() ) { items( items = Screen.entries, key = { it.id } ) { screen -> val checked by remember(screen, screenList) { derivedStateOf { screenList.fastAny { it::class.isInstance(screen) } } } PreferenceItemOverload( modifier = Modifier.fillMaxWidth(), title = stringResource(screen.title), subtitle = stringResource(screen.subtitle), startIcon = { screen.icon?.let { Icon( imageVector = it, contentDescription = null ) } }, endIcon = { EnhancedCheckbox( checked = checked, onCheckedChange = { onValueChange(screen) } ) }, onClick = { onValueChange(screen) } ) } } } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseCompactSelectorsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FullscreenExit import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch @Composable fun UseCompactSelectorsSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp), ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, title = stringResource(R.string.compact_selectors), subtitle = stringResource(R.string.compact_selectors_sub), checked = settingsState.isCompactSelectorsLayout, startIcon = Icons.Rounded.FullscreenExit ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFormattedFilenameTimestampSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.FormatTextdirectionLToR import androidx.compose.material.icons.outlined.Timer import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.domain.model.FilenameBehavior import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.utils.getString @Composable fun UseFormattedFilenameTimestampSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( shape = shape, modifier = modifier, onClick = { onClick() }, enabled = settingsState.filenameBehavior is FilenameBehavior.None && settingsState.addTimestampToFilename, onDisabledClick = { AppToastHost.showToast( message = getString(R.string.enable_timestamps_to_format_them), icon = Icons.Outlined.Timer ) }, title = stringResource(R.string.formatted_timestamp), subtitle = stringResource(R.string.formatted_timestamp_sub), checked = settingsState.useFormattedFilenameTimestamp, startIcon = Icons.AutoMirrored.Outlined.FormatTextdirectionLToR ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseFullscreenSettingsSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Fullscreen import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun UseFullscreenSettingsSettingItem( onClick: () -> Unit, onNavigateToSettings: () -> Unit, shape: Shape = ShapeDefaults.center, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.fullscreen_settings), subtitle = stringResource(R.string.fullscreen_settings_sub), checked = settingsState.useFullscreenSettings, onClick = { if (it) { onNavigateToSettings() GlobalScope.launch { delay(1000) onClick() } } else onClick() }, startIcon = Icons.Outlined.Fullscreen ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/UseRandomEmojisSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Casino import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.utils.getString @Composable fun UseRandomEmojisSettingItem( onClick: () -> Unit, shape: Shape = ShapeDefaults.bottom, modifier: Modifier = Modifier.padding(horizontal = 8.dp) ) { val settingsState = LocalSettingsState.current PreferenceRowSwitch( modifier = modifier, shape = shape, title = stringResource(R.string.random_emojis), subtitle = stringResource(R.string.random_emojis_sub), checked = settingsState.useRandomEmojis, enabled = settingsState.selectedEmoji != null, onClick = { onClick() }, onDisabledClick = { AppToastHost.showToast( message = getString(R.string.random_emojis_error), icon = Icons.Outlined.Casino ) }, startIcon = Icons.Outlined.Casino ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/VibrationStrengthSettingItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Exercise import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalResourceManager import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import kotlinx.collections.immutable.persistentMapOf import kotlin.math.roundToInt @Composable fun VibrationStrengthSettingItem( onValueChange: (Int) -> Unit, modifier: Modifier = Modifier .padding(horizontal = 8.dp), shape: Shape = ShapeDefaults.default ) { val settingsState = LocalSettingsState.current val resources = LocalResourceManager.current EnhancedSliderItem( modifier = modifier, shape = shape, value = settingsState.hapticsStrength, title = stringResource(R.string.vibration_strength), icon = Icons.Outlined.Exercise, onValueChange = { onValueChange(it.roundToInt()) }, internalStateTransformation = { it.roundToInt() }, valueRange = 0f..2f, valuesPreviewMapping = remember { persistentMapOf(0f to resources.getString(R.string.disabled)) }, steps = 1, valueTextTapEnabled = false ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/AuthorLinksSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components.additional import android.content.Intent import androidx.activity.compose.LocalActivity import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AlternateEmail import androidx.compose.material.icons.rounded.Link import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.domain.AUTHOR_GITHUB import com.t8rin.imagetoolbox.core.domain.AUTHOR_TELEGRAM import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Forum import com.t8rin.imagetoolbox.core.resources.icons.Github import com.t8rin.imagetoolbox.core.resources.icons.Telegram import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.shareText import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContainerColor import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContentColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults.bottom import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults.center import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults.top import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.core.utils.getString @Composable fun AuthorLinksSheet( visible: Boolean, onDismiss: () -> Unit ) { EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.app_developer_nick), icon = Icons.Rounded.Forum ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss, ) { AutoSizeText(stringResource(R.string.close)) } }, sheetContent = { val activity = LocalActivity.current val linkHandler = LocalUriHandler.current val settingsState = LocalSettingsState.current Box { Column(Modifier.enhancedVerticalScroll(rememberScrollState())) { Spacer(Modifier.height(16.dp)) CompositionLocalProvider( LocalIconShapeContentColor provides MaterialTheme.colorScheme.onTertiaryContainer, LocalIconShapeContainerColor provides MaterialTheme.colorScheme.tertiaryContainer.blend( color = MaterialTheme.colorScheme.tertiary, fraction = if (settingsState.isNightMode) 0.2f else 0.1f ) ) { PreferenceItem( containerColor = MaterialTheme.colorScheme.tertiaryContainer, onClick = { linkHandler.openUri(AUTHOR_TELEGRAM) }, endIcon = Icons.Rounded.Link, shape = top, title = stringResource(R.string.telegram), startIcon = Icons.Rounded.Telegram, subtitle = stringResource(R.string.app_developer_nick), overrideIconShapeContentColor = true ) } Spacer(Modifier.height(4.dp)) PreferenceItem( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = { val mail = getString(R.string.developer_email) runCatching { activity!!.startActivity( Intent(Intent.ACTION_SENDTO).apply { data = "mailto:$mail".toUri() } ) }.onFailure { appContext.shareText(mail) } }, shape = center, endIcon = Icons.Rounded.Link, title = stringResource(R.string.email), startIcon = Icons.Rounded.AlternateEmail, subtitle = stringResource(R.string.developer_email) ) Spacer(Modifier.height(4.dp)) PreferenceItem( containerColor = MaterialTheme.colorScheme.primaryContainer, onClick = { linkHandler.openUri(AUTHOR_GITHUB) }, endIcon = Icons.Rounded.Link, shape = bottom, title = stringResource(R.string.github), startIcon = Icons.Rounded.Github, subtitle = stringResource(R.string.app_developer_nick) ) Spacer(Modifier.height(16.dp)) } } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateContainerContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components.additional import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material.icons.rounded.Link import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.BOOSTY_LINK import com.t8rin.imagetoolbox.core.domain.BTC_WALLET import com.t8rin.imagetoolbox.core.domain.TON_SPACE_WALLET import com.t8rin.imagetoolbox.core.domain.TON_WALLET import com.t8rin.imagetoolbox.core.domain.USDT_WALLET import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Bitcoin import com.t8rin.imagetoolbox.core.resources.icons.Boosty import com.t8rin.imagetoolbox.core.resources.icons.Ton import com.t8rin.imagetoolbox.core.resources.icons.USDT import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.BitcoinColor import com.t8rin.imagetoolbox.core.ui.theme.BoostyColor import com.t8rin.imagetoolbox.core.ui.theme.TONColor import com.t8rin.imagetoolbox.core.ui.theme.TONSpaceColor import com.t8rin.imagetoolbox.core.ui.theme.USDTColor import com.t8rin.imagetoolbox.core.ui.theme.blend import com.t8rin.imagetoolbox.core.ui.theme.inverse import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.icon_shape.LocalIconShapeContainerColor import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults @Composable fun DonateContainerContent( modifier: Modifier = Modifier ) { Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp) ) { InfoContainer( text = stringResource(R.string.donation_sub), modifier = Modifier .padding( start = 16.dp, top = 16.dp, end = 16.dp, bottom = 12.dp ), containerColor = MaterialTheme.colorScheme.surfaceVariant, contentColor = MaterialTheme.colorScheme.onSurfaceVariant, ) val options = DonationOption.entries options.forEachIndexed { index, option -> CompositionLocalProvider( LocalIconShapeContainerColor provides option.containerColor().blend( color = Color.White, fraction = 0.1f ) ) { PreferenceItem( containerColor = option.containerColor(), overrideIconShapeContentColor = true, contentColor = option.contentColor(), shape = ShapeDefaults.byIndex( index = index, size = options.size ), onClick = option.onClick, endIcon = option.endIcon, startIcon = option.startIcon, title = option.title(), subtitle = option.subtitle, titleFontStyle = PreferenceItemDefaults.TitleFontStyle.copy( textAlign = TextAlign.Start ) ) } } Spacer(Modifier.height(12.dp)) } } private data class DonationOption( val containerColor: @Composable () -> Color, val contentColor: @Composable () -> Color, val onClick: () -> Unit, val endIcon: ImageVector, val startIcon: ImageVector, val title: @Composable () -> String, val subtitle: String ) { companion object Companion { val entries: List @Composable get() { val linkHandler = LocalUriHandler.current val darkMode = !LocalSettingsState.current.isNightMode return remember(linkHandler, darkMode) { listOf( DonationOption( containerColor = { BoostyColor }, contentColor = { BoostyColor.inverse( fraction = { 1f }, darkMode = true ) }, onClick = { linkHandler.openUri(BOOSTY_LINK) }, endIcon = Icons.Rounded.Link, startIcon = Icons.Rounded.Boosty, title = { stringResource(R.string.boosty) }, subtitle = BOOSTY_LINK ), DonationOption( containerColor = { BitcoinColor }, contentColor = { BitcoinColor.inverse( fraction = { 1f }, darkMode = darkMode ) }, onClick = { Clipboard.copy(BTC_WALLET) }, endIcon = Icons.Rounded.ContentCopy, title = { stringResource(R.string.bitcoin) }, startIcon = Icons.Filled.Bitcoin, subtitle = BTC_WALLET ), DonationOption( containerColor = { USDTColor }, contentColor = { USDTColor.inverse( fraction = { 1f }, darkMode = darkMode ) }, onClick = { Clipboard.copy(BTC_WALLET) }, endIcon = Icons.Rounded.ContentCopy, title = { stringResource(R.string.usdt) }, startIcon = Icons.Filled.USDT, subtitle = USDT_WALLET ), DonationOption( containerColor = { TONColor }, contentColor = { TONColor.inverse( fraction = { 1f }, darkMode = darkMode ) }, onClick = { Clipboard.copy(TON_WALLET) }, endIcon = Icons.Rounded.ContentCopy, startIcon = Icons.Rounded.Ton, title = { stringResource(R.string.ton) }, subtitle = TON_WALLET ), DonationOption( containerColor = { TONSpaceColor }, contentColor = { TONSpaceColor.inverse( fraction = { 1f }, darkMode = true ) }, onClick = { Clipboard.copy(TON_SPACE_WALLET) }, endIcon = Icons.Rounded.ContentCopy, startIcon = Icons.Rounded.Ton, title = { stringResource(R.string.ton_space) }, subtitle = TON_SPACE_WALLET ) ) } } } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateDialog.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components.additional import androidx.compose.foundation.layout.Column import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.VolunteerActivism import com.t8rin.imagetoolbox.core.settings.presentation.model.isFirstLaunch import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedAlertDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import kotlinx.coroutines.delay @Composable fun DonateDialog( onNotShowDonateDialogAgain: () -> Unit, onRegisterDonateDialogOpen: () -> Unit ) { val settings = LocalSettingsState.current var isClosed by rememberSaveable { mutableStateOf(false) } val showDialog = settings.appOpenCount % 12 == 0 && !settings.isFirstLaunch(false) && !isClosed && settings.donateDialogOpenCount != null var isOpenRegistered by rememberSaveable(showDialog) { mutableStateOf(false) } if (showDialog) { LaunchedEffect(isOpenRegistered) { if (!isOpenRegistered) { delay(1000) onRegisterDonateDialogOpen() isOpenRegistered = true } } } val isNotShowAgainButtonVisible = (settings.donateDialogOpenCount ?: 0) > 2 EnhancedAlertDialog( visible = showDialog, onDismissRequest = { }, icon = { Icon( imageVector = Icons.Rounded.VolunteerActivism, contentDescription = null ) }, title = { Text(stringResource(R.string.donation)) }, text = { val scrollState = rememberScrollState() Column( modifier = Modifier .fadingEdges( isVertical = true, scrollableState = scrollState ) .enhancedVerticalScroll(scrollState) ) { DonateContainerContent() } }, dismissButton = { if (isNotShowAgainButtonVisible) { EnhancedButton( onClick = { onNotShowDonateDialogAgain() isClosed = true }, containerColor = MaterialTheme.colorScheme.errorContainer ) { Text(stringResource(id = R.string.dismiss_forever)) } } }, confirmButton = { EnhancedButton( onClick = { isClosed = true }, containerColor = takeColorFromScheme { if (isNotShowAgainButtonVisible) tertiaryContainer else secondaryContainer } ) { Text(stringResource(id = R.string.close)) } } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/DonateSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components.additional import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.VolunteerActivism import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable fun DonateSheet( visible: Boolean, onDismiss: () -> Unit ) { EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!it) onDismiss() }, title = { TitleItem( text = stringResource(R.string.donation), icon = Icons.Rounded.VolunteerActivism ) }, confirmButton = { EnhancedButton( containerColor = MaterialTheme.colorScheme.secondaryContainer, onClick = onDismiss, ) { AutoSizeText(stringResource(R.string.close)) } }, sheetContent = { DonateContainerContent( modifier = Modifier.enhancedVerticalScroll(rememberScrollState()) ) } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FabPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components.additional import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Image import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.shapes import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvidesValue import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.autoElevatedBorder import com.t8rin.imagetoolbox.core.ui.widget.modifier.containerFabBorder @Composable fun FabPreview( modifier: Modifier = Modifier, alignment: Alignment, ) { val shadowEnabled = LocalSettingsState.current.drawContainerShadows val elevation by animateDpAsState(if (shadowEnabled) 2.dp else 0.dp) Column( modifier = modifier .padding(4.dp) .autoElevatedBorder( shape = ShapeDefaults.small, autoElevation = elevation ) .clip(ShapeDefaults.small) .background(colorScheme.surfaceContainerLowest), verticalArrangement = Arrangement.SpaceBetween ) { Column( modifier = Modifier .padding(horizontal = 8.dp, vertical = 8.dp) .autoElevatedBorder(shape = shapes.small, autoElevation = elevation) .clip(shapes.small) .background(colorScheme.surfaceContainer) .fillMaxWidth(1f), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(Modifier.height(4.dp)) EnhancedIconButton( onClick = {}, modifier = Modifier .size(25.dp) .aspectRatio(1f), shape = CloverShape, containerColor = colorScheme.surfaceColorAtElevation(6.dp), contentColor = colorScheme.onSurfaceVariant ) { Icon( imageVector = Icons.TwoTone.Image, contentDescription = null, modifier = Modifier .fillMaxSize() .padding(3.dp), tint = colorScheme.onSurfaceVariant ) } Text( text = stringResource(R.string.pick_image), modifier = Modifier.padding(4.dp), fontSize = 3.sp, lineHeight = 4.sp, textAlign = TextAlign.Center, color = colorScheme.onSurfaceVariant ) } val weight by animateFloatAsState( targetValue = when (alignment) { Alignment.BottomStart -> 0f Alignment.BottomCenter -> 0.5f else -> 1f } ) val settingsState = LocalSettingsState.current LocalContentColor.ProvidesValue(colorScheme.onPrimaryContainer) { Row( modifier = Modifier.fillMaxWidth(), ) { Spacer(modifier = Modifier.weight(0.01f + weight)) Box( modifier = Modifier .padding(8.dp) .size(22.dp) .aspectRatio(1f) .containerFabBorder( shape = ShapeDefaults.mini, autoElevation = animateDpAsState( if (settingsState.drawFabShadows) 3.dp else 0.dp ).value ) .background( color = colorScheme.primaryContainer, shape = ShapeDefaults.mini, ) .clip(ShapeDefaults.mini) .hapticsClickable { }, contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.AddPhotoAlt, contentDescription = null, modifier = Modifier.size(10.dp) ) } Spacer(modifier = Modifier.weight(1.01f - weight)) } } } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/FontItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components.additional import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsDraggedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Delete import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.animateShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.FontSelectionItem import com.t8rin.imagetoolbox.core.ui.widget.other.RevealDirection import com.t8rin.imagetoolbox.core.ui.widget.other.RevealValue import com.t8rin.imagetoolbox.core.ui.widget.other.SwipeToReveal import com.t8rin.imagetoolbox.core.ui.widget.other.rememberRevealState import kotlinx.coroutines.launch @Composable internal fun LazyItemScope.FontItem( font: UiFontFamily, onFontSelected: (UiFontFamily) -> Unit, onRemoveFont: (UiFontFamily.Custom) -> Unit ) { if (font is UiFontFamily.Custom) { val scope = rememberCoroutineScope() val state = rememberRevealState() val interactionSource = remember { MutableInteractionSource() } val isDragged by interactionSource.collectIsDraggedAsState() val shape = animateShape( if (isDragged) ShapeDefaults.extraSmall else ShapeDefaults.default ) SwipeToReveal( state = state, modifier = Modifier.animateItem(), revealedContentEnd = { Box( Modifier .fillMaxSize() .container( color = MaterialTheme.colorScheme.errorContainer, shape = shape, autoShadowElevation = 0.dp, resultPadding = 0.dp ) .hapticsClickable { scope.launch { state.animateTo(RevealValue.Default) } onRemoveFont(font) } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.delete), modifier = Modifier .padding(16.dp) .padding(end = 8.dp) .align(Alignment.CenterEnd), tint = MaterialTheme.colorScheme.onErrorContainer ) } }, directions = setOf(RevealDirection.EndToStart), swipeableContent = { FontSelectionItem( font = font, onClick = { onFontSelected(font) }, onLongClick = { scope.launch { state.animateTo(RevealValue.FullyRevealedStart) } }, modifier = Modifier.fillMaxWidth(), shape = shape ) }, interactionSource = interactionSource ) } else { FontSelectionItem( font = font, onClick = { onFontSelected(font) } ) } } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/components/additional/PickFontFamilySheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.components.additional import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Extension import androidx.compose.material.icons.rounded.FontDownload import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FileExport import com.t8rin.imagetoolbox.core.resources.icons.FileImport import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedModalBottomSheet import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.negativePadding import com.t8rin.imagetoolbox.core.ui.widget.other.GradientEdge import com.t8rin.imagetoolbox.core.ui.widget.other.InfoContainer import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRow import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem @Composable internal fun PickFontFamilySheet( visible: Boolean, onDismiss: () -> Unit, onFontSelected: (UiFontFamily) -> Unit, onAddFont: (Uri) -> Unit, onRemoveFont: (UiFontFamily.Custom) -> Unit, onExportFonts: () -> Unit ) { EnhancedModalBottomSheet( visible = visible, onDismiss = { if (!it) onDismiss() }, sheetContent = { val defaultEntries = UiFontFamily.defaultEntries val customEntries = UiFontFamily.customEntries LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), flingBehavior = enhancedFlingBehavior() ) { stickyHeader { Column( modifier = Modifier .negativePadding(horizontal = 16.dp) .background(EnhancedBottomSheetDefaults.containerColor) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(20.dp)) Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.height(IntrinsicSize.Max) ) { val pickFileLauncher = rememberFilePicker( mimeType = MimeType.Font, onSuccess = onAddFont ) PreferenceRow( title = stringResource(R.string.import_font), onClick = pickFileLauncher::pickFile, shape = ShapeDefaults.start, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered, startIcon = Icons.Rounded.FileImport, modifier = Modifier .weight(1f) .fillMaxHeight(), color = MaterialTheme.colorScheme.primaryContainer ) val canExport = customEntries.isNotEmpty() PreferenceRow( title = stringResource(R.string.export_fonts), onClick = onExportFonts, shape = ShapeDefaults.end, enabled = canExport, startIcon = Icons.Rounded.FileExport, modifier = Modifier .weight(1f) .fillMaxHeight(), color = takeColorFromScheme { if (canExport) primaryContainer else surfaceVariant }, titleFontStyle = PreferenceItemDefaults.TitleFontStyleCentered ) } Spacer(modifier = Modifier.height(8.dp)) } GradientEdge( modifier = Modifier .fillMaxWidth() .height(16.dp), startColor = EnhancedBottomSheetDefaults.containerColor, endColor = Color.Transparent ) } items( items = defaultEntries, key = { it.name ?: "sys" } ) { font -> FontItem( font = font, onFontSelected = onFontSelected, onRemoveFont = onRemoveFont ) } if (customEntries.isNotEmpty()) { item { InfoContainer( text = stringResource(R.string.imported_fonts), icon = Icons.Outlined.Extension, modifier = Modifier .fillMaxWidth() .padding(8.dp), contentColor = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.6f), containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(0.4f) ) } items( items = customEntries, key = { it.name ?: "sys" } ) { font -> FontItem( font = font, onFontSelected = onFontSelected, onRemoveFont = onRemoveFont ) } } } }, confirmButton = { Row( verticalAlignment = Alignment.CenterVertically ) { EnhancedButton( onClick = onDismiss, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { AutoSizeText(stringResource(R.string.close)) } } }, title = { TitleItem( icon = Icons.Rounded.FontDownload, text = stringResource(R.string.font), ) } ) } ================================================ FILE: feature/settings/src/main/java/com/t8rin/imagetoolbox/feature/settings/presentation/screenLogic/SettingsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.settings.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.Value import com.t8rin.dynamic.theme.ColorTuple import com.t8rin.dynamic.theme.extractPrimaryColor import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.ColorModel import com.t8rin.imagetoolbox.core.domain.model.HashingType import com.t8rin.imagetoolbox.core.domain.model.SystemBarsVisibility import com.t8rin.imagetoolbox.core.domain.resource.ResourceManager import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.humanFileSize import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.settings.domain.SettingsManager import com.t8rin.imagetoolbox.core.settings.domain.model.ColorHarmonizer import com.t8rin.imagetoolbox.core.settings.domain.model.CopyToClipboardMode import com.t8rin.imagetoolbox.core.settings.domain.model.DomainFontFamily import com.t8rin.imagetoolbox.core.settings.domain.model.FastSettingsSide import com.t8rin.imagetoolbox.core.settings.domain.model.FlingType import com.t8rin.imagetoolbox.core.settings.domain.model.NightMode import com.t8rin.imagetoolbox.core.settings.domain.model.SettingsState import com.t8rin.imagetoolbox.core.settings.domain.model.ShapeType import com.t8rin.imagetoolbox.core.settings.domain.model.SliderType import com.t8rin.imagetoolbox.core.settings.domain.model.SnowfallMode import com.t8rin.imagetoolbox.core.settings.domain.model.SwitchType import com.t8rin.imagetoolbox.core.settings.presentation.model.Setting import com.t8rin.imagetoolbox.core.settings.presentation.model.SettingsGroup import com.t8rin.imagetoolbox.core.settings.presentation.model.UiFontFamily import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach import java.util.Locale class SettingsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted private val onTryGetUpdate: (Boolean, () -> Unit) -> Unit, @Assisted val onNavigate: (Screen) -> Unit, @Assisted val isUpdateAvailable: Value, @Assisted val onGoBack: (() -> Unit)?, @Assisted initialSearchQuery: String, private val imageGetter: ImageGetter, private val fileController: FileController, private val settingsManager: SettingsManager, private val resourceManager: ResourceManager, private val shareProvider: ShareProvider, filenameCreator: FilenameCreator, dispatchersHolder: DispatchersHolder, ) : BaseComponent(dispatchersHolder, componentContext), FilenameCreator by filenameCreator { private val _settingsState = mutableStateOf(SettingsState.Default) val settingsState: SettingsState by _settingsState private val _searchKeyword = mutableStateOf("") val searchKeyword by _searchKeyword private val _filteredSettings: MutableState>?> = mutableStateOf(null) val filteredSettings by _filteredSettings private val _isFilteringSettings = mutableStateOf(false) val isFilteringSettings by _isFilteringSettings private var filterJob by smartJob() private fun filterSettings() { filterJob = componentScope.launch { delay(150) _isFilteringSettings.update { searchKeyword.isNotEmpty() } _filteredSettings.update { searchKeyword.takeIf { it.trim().isNotEmpty() }?.let { val newList = mutableListOf, Int>>() SettingsGroup.entries.forEach { group -> group.settingsList.forEach { setting -> val keywords = mutableListOf().apply { add(resourceManager.getString(group.titleId)) add(resourceManager.getString(setting.title)) add( resourceManager.getStringLocalized( group.titleId, Locale.ENGLISH.language ) ) add( resourceManager.getStringLocalized( setting.title, Locale.ENGLISH.language ) ) setting.subtitle?.let { add(resourceManager.getString(it)) add( resourceManager.getStringLocalized( it, Locale.ENGLISH.language ) ) } } val substringStart = keywords .joinToString() .indexOf( string = searchKeyword, ignoreCase = true ).takeIf { it != -1 } substringStart?.plus(searchKeyword.length)?.let { substringEnd -> newList.add(group to setting to (substringEnd - substringStart)) } } } newList.sortedBy { it.second }.map { it.first } } } _isFilteringSettings.update { false } } } fun updateSearchKeyword(value: String) { _searchKeyword.update { value } filterSettings() } fun tryGetUpdate( isNewRequest: Boolean = false, onNoUpdates: () -> Unit = {} ) = onTryGetUpdate(isNewRequest, onNoUpdates) init { settingsScope { settingsState.onEach { _settingsState.value = it }.collect() } debounce { updateSearchKeyword(initialSearchQuery) } } fun getReadableCacheSize(): String = humanFileSize(fileController.getCacheSize()) fun clearCache(onComplete: (String) -> Unit = {}) = fileController.clearCache { onComplete(getReadableCacheSize()) } fun toggleAddSequenceNumber() = settingsScope { toggleAddSequenceNumber() } fun toggleAddOriginalFilename() = settingsScope { toggleAddOriginalFilename() } fun setEmojisCount(count: Int) = settingsScope { setEmojisCount(count) } fun setImagePickerMode(mode: Int) = settingsScope { setImagePickerMode(mode) } fun toggleAddFileSize() = settingsScope { toggleAddFileSize() } fun setEmoji(emoji: Int) = settingsScope { setEmoji(emoji) } fun setFilenamePrefix(name: String) = settingsScope { setFilenamePrefix(name) } fun setFilenameSuffix(name: String) = settingsScope { setFilenameSuffix(name) } fun toggleShowUpdateDialog() = settingsScope { toggleShowUpdateDialogOnStartup() } fun setColorTuple(colorTuple: ColorTuple) = settingsScope { setColorTuple( colorTuple.run { "${primary.toArgb()}*${secondary?.toArgb()}*${tertiary?.toArgb()}*${surface?.toArgb()}" } ) } fun toggleDynamicColors() = settingsScope { toggleDynamicColors() } fun toggleLockDrawOrientation() = settingsScope { toggleLockDrawOrientation() } fun setBorderWidth(width: Float) = settingsScope { setBorderWidth(width) } fun toggleAllowImageMonet() = settingsScope { toggleAllowImageMonet() } fun toggleAmoledMode() = settingsScope { toggleAmoledMode() } fun setNightMode(nightMode: NightMode) = settingsScope { setNightMode(nightMode) } fun setSaveFolderUri(uri: Uri?) = settingsScope { setSaveFolderUri(uri?.toString()) } private fun List.asString(): String = joinToString(separator = "*") { "${it.primary.toArgb()}/${it.secondary?.toArgb()}/${it.tertiary?.toArgb()}/${it.surface?.toArgb()}" } fun setColorTuples(colorTuples: List) = settingsScope { setColorTuples(colorTuples.asString()) } fun setAlignment(align: Float) = settingsScope { setAlignment(align.toInt()) } fun setScreenOrder(data: List) = settingsScope { setScreenOrder(data.joinToString("/") { it.id.toString() }) } fun toggleClearCacheOnLaunch() = settingsScope { toggleClearCacheOnLaunch() } fun toggleGroupOptionsByType() = settingsScope { toggleGroupOptionsByTypes() } fun toggleRandomizeFilename() = settingsScope { toggleRandomizeFilename() } fun createBackup( uri: Uri ) = settingsScope { fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(createBackupFile()) } ).also(::parseFileSaveResult) } fun exportFonts(uri: Uri) = settingsScope { fileController.transferBytes( fromUri = createCustomFontsExport().toString(), toUri = uri.toString() ).also(::parseFileSaveResult) } fun restoreBackupFrom( uri: Uri, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit, ) = settingsScope { restoreFromBackupFile( backupFileUri = uri.toString(), onSuccess = onSuccess, onFailure = onFailure ) } fun resetSettings() = settingsScope { resetSettings() } fun createBackupFilename(): String = settingsManager.createBackupFilename() fun setFont(font: DomainFontFamily) = settingsScope { setFont(font) } fun setFontScale(scale: Float) = settingsScope { setFontScale(scale) } fun toggleAllowCollectCrashlytics() = settingsScope { toggleAllowCrashlytics() } fun toggleAllowCollectAnalytics() = settingsScope { toggleAllowAnalytics() } fun toggleAllowBetas() = settingsScope { toggleAllowBetas() } fun toggleDrawContainerShadows() = settingsScope { toggleDrawContainerShadows() } fun toggleDrawSwitchShadows() = settingsScope { toggleDrawSwitchShadows() } fun toggleDrawSliderShadows() = settingsScope { toggleDrawSliderShadows() } fun toggleDrawButtonShadows() = settingsScope { toggleDrawButtonShadows() } fun toggleDrawFabShadows() = settingsScope { toggleDrawFabShadows() } fun addColorTupleFromEmoji(emoji: String) = settingsScope { if (emoji.contains("shoe", true)) { setFont(DomainFontFamily.DejaVu) val colorTuple = ColorTuple( primary = Color(0xFF6D216D), secondary = Color(0xFF240A95), tertiary = Color(0xFFFFFFA0), surface = Color(0xFF1D2D3D) ) val colorTupleS = listOf(colorTuple).asString() setColorTuple(colorTuple) setColorTuples(this@SettingsComponent.settingsState.colorTupleList + "*" + colorTupleS) setThemeContrast(0f) setThemeStyle(0) if (this@SettingsComponent.settingsState.useEmojiAsPrimaryColor) toggleUseEmojiAsPrimaryColor() if (this@SettingsComponent.settingsState.isInvertThemeColors) toggleInvertColors() } else { imageGetter.getImage(data = emoji) ?.extractPrimaryColor() ?.let { primary -> val colorTuple = ColorTuple(primary) setColorTuple(colorTuple) settingsManager.setColorTuples( this@SettingsComponent.settingsState.colorTupleList + "*" + listOf( colorTuple ).asString() ) } } if (this@SettingsComponent.settingsState.isDynamicColors) toggleDynamicColors() } fun setThemeContrast(value: Float) = settingsScope { setThemeContrast(value.toDouble()) } fun setThemeStyle(value: Int) = settingsScope { setThemeStyle(value) } fun toggleInvertColors() = settingsScope { toggleInvertColors() } fun toggleScreenSearchEnabled() = settingsScope { toggleScreensSearchEnabled() } fun toggleDrawAppBarShadows() = settingsScope { toggleDrawAppBarShadows() } private fun setCopyToClipboardMode(copyToClipboardMode: CopyToClipboardMode) = settingsScope { setCopyToClipboardMode(copyToClipboardMode) } fun toggleAutoPinClipboard(value: Boolean) { val mode = if (value) { CopyToClipboardMode.Enabled.WithSaving } else { CopyToClipboardMode.Disabled } setCopyToClipboardMode(mode) } fun toggleAutoPinClipboardOnlyClip(value: Boolean) { val mode = if (value) { CopyToClipboardMode.Enabled.WithoutSaving } else { CopyToClipboardMode.Enabled.WithSaving } setCopyToClipboardMode(mode) } fun setVibrationStrength(strength: Int) = settingsScope { setVibrationStrength(strength) } fun toggleOverwriteFiles() = settingsScope { toggleOverwriteFiles() } fun setDefaultImageScaleMode(imageScaleMode: ImageScaleMode) = settingsScope { setDefaultImageScaleMode(imageScaleMode) } fun setSwitchType(type: SwitchType) = settingsScope { setSwitchType(type) } fun toggleMagnifierEnabled() = settingsScope { toggleMagnifierEnabled() } fun toggleExifWidgetInitialState() = settingsScope { toggleExifWidgetInitialState() } fun setScreensWithBrightnessEnforcement(screen: Screen) = settingsScope { val screens = this@SettingsComponent.settingsState.screenListWithMaxBrightnessEnforcement .toggle(screen.id) setScreensWithBrightnessEnforcement(screens) } fun toggleConfettiEnabled() = settingsScope { toggleConfettiEnabled() } fun toggleSecureMode() = settingsScope { toggleSecureMode() } fun toggleUseEmojiAsPrimaryColor() = settingsScope { toggleUseEmojiAsPrimaryColor() } fun toggleUseRandomEmojis() = settingsScope { toggleUseRandomEmojis() } fun setIconShape(iconShape: Int) = settingsScope { setIconShape(iconShape) } fun setDragHandleWidth(width: Int) = settingsScope { setDragHandleWidth(width) } fun setConfettiType(type: Int) = settingsScope { setConfettiType(type) } fun toggleAllowAutoClipboardPaste() = settingsScope { toggleAllowAutoClipboardPaste() } fun setConfettiHarmonizer(colorHarmonizer: ColorHarmonizer) = settingsScope { setConfettiHarmonizer(colorHarmonizer) } fun setConfettiHarmonizationLevel(level: Float) = settingsScope { setConfettiHarmonizationLevel(level) } fun toggleGeneratePreviews() = settingsScope { toggleGeneratePreviews() } fun toggleSkipImagePicking() = settingsScope { toggleSkipImagePicking() } fun toggleShowSettingsInLandscape() = settingsScope { toggleShowSettingsInLandscape() } fun toggleUseFullscreenSettings() = settingsScope { toggleUseFullscreenSettings() } fun setDefaultDrawLineWidth(value: Float) = settingsScope { setDefaultDrawLineWidth(value) } fun toggleOpenEditInsteadOfPreview() = settingsScope { toggleOpenEditInsteadOfPreview() } fun toggleCanEnterPresetsByTextField() = settingsScope { toggleCanEnterPresetsByTextField() } fun setColorBlindScheme(value: Int?) = settingsScope { setColorBlindType(value) } fun toggleIsLinksPreviewEnabled() = settingsScope { toggleIsLinkPreviewEnabled() } fun setDefaultDrawColor(colorModel: ColorModel) = settingsScope { setDefaultDrawColor(colorModel) } fun setDefaultDrawPathMode(mode: Int) = settingsScope { setDefaultDrawPathMode(mode) } fun toggleAddTimestampToFilename() = settingsScope { toggleAddTimestampToFilename() } fun toggleUseFormattedFilenameTimestamp() = settingsScope { toggleUseFormattedFilenameTimestamp() } fun setDefaultResizeType(resizeType: ResizeType) = settingsScope { setDefaultResizeType(resizeType) } fun setSystemBarsVisibility(systemBarsVisibility: SystemBarsVisibility) = settingsScope { setSystemBarsVisibility(systemBarsVisibility) } fun toggleIsSystemBarsVisibleBySwipe() = settingsScope { toggleIsSystemBarsVisibleBySwipe() } fun toggleUseCompactSelectors() = settingsScope { toggleUseCompactSelectorsLayout() } fun setMainScreenTitle(title: String) = settingsScope { setMainScreenTitle(title) } fun setSliderType(sliderType: SliderType) = settingsScope { setSliderType(sliderType) } fun toggleIsCenterAlignDialogButtons() = settingsScope { toggleIsCenterAlignDialogButtons() } fun setFastSettingsSide(side: FastSettingsSide) = settingsScope { setFastSettingsSide(side) } fun setChecksumTypeForFilename(type: HashingType?) = settingsScope { setChecksumTypeForFilename(type) } fun importCustomFont( uri: Uri, onSuccess: () -> Unit, onFailure: () -> Unit ) = settingsScope { importCustomFont(uri.toString())?.let { font -> setFont(font) onSuccess() } ?: onFailure() } fun removeCustomFont( font: UiFontFamily.Custom ) = settingsScope { removeCustomFont(font.asDomain() as DomainFontFamily.Custom) setFont(DomainFontFamily.System) } fun toggleEnableToolExitConfirmation() = settingsScope { toggleEnableToolExitConfirmation() } fun shareLogs(onComplete: () -> Unit) = settingsScope { shareProvider.shareUri( uri = settingsManager.createLogsExport(), onComplete = onComplete ) } fun toggleAddPresetInfoToFilename() = settingsScope { toggleAddPresetInfoToFilename() } fun toggleAddImageScaleModeInfoToFilename() = settingsScope { toggleAddImageScaleModeInfoToFilename() } fun toggleAllowSkipIfLarger() = settingsScope { toggleAllowSkipIfLarger() } fun toggleIsScreenSelectionLauncherMode() = settingsScope { toggleIsScreenSelectionLauncherMode() } fun setSnowfallMode(snowfallMode: SnowfallMode) = settingsScope { setSnowfallMode(snowfallMode) } fun setDefaultImageFormat(imageFormat: ImageFormat?) = settingsScope { setDefaultImageFormat(imageFormat) } fun setDefaultQuality(quality: Quality) = settingsScope { setDefaultQuality(quality) } fun setShapesType(shapeType: ShapeType) = settingsScope { setShapesType(shapeType) } fun setFilenamePattern(value: String) = settingsScope { setFilenamePattern(value) } fun setFlingType(type: FlingType) = settingsScope { setFlingType(type) } fun setHiddenForShareScreens(screen: Screen) = settingsScope { val screens = this@SettingsComponent.settingsState.hiddenForShareScreens .toggle(screen.id) setHiddenForShareScreens(screens) } fun toggleKeepDateTime() = settingsScope { toggleKeepDateTime() } fun toggleEnableBackgroundColorForAlphaFormats() = settingsScope { toggleEnableBackgroundColorForAlphaFormats() } private inline fun settingsScope( crossinline action: suspend SettingsManager.() -> Unit ) { componentScope.launch { settingsManager.action() } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onTryGetUpdate: (Boolean, () -> Unit) -> Unit, onNavigate: (Screen) -> Unit, isUpdateAvailable: Value, onGoBack: (() -> Unit)?, initialSearchQuery: String ): SettingsComponent } } ================================================ FILE: feature/single-edit/.gitignore ================================================ /build ================================================ FILE: feature/single-edit/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.single_edit" dependencies { implementation(projects.feature.crop) implementation(projects.feature.eraseBackground) implementation(projects.feature.draw) implementation(projects.feature.filters) implementation(projects.feature.pickColor) implementation(projects.feature.compare) implementation(projects.lib.curves) } ================================================ FILE: feature/single-edit/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/SingleEditContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation import android.net.Uri import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.History import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.CompareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShowOriginalButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageExtraTransformBar import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageTransformBar import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.controls.resize_group.ResizeTypeSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PresetSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ScaleModeSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.EditExifSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.core.utils.fileSize import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareSheet import com.t8rin.imagetoolbox.feature.single_edit.presentation.components.CropEditOption import com.t8rin.imagetoolbox.feature.single_edit.presentation.components.DrawEditOption import com.t8rin.imagetoolbox.feature.single_edit.presentation.components.EraseBackgroundEditOption import com.t8rin.imagetoolbox.feature.single_edit.presentation.components.FilterEditOption import com.t8rin.imagetoolbox.feature.single_edit.presentation.components.ToneCurvesEditOption import com.t8rin.imagetoolbox.feature.single_edit.presentation.screenLogic.SingleEditComponent @Composable fun SingleEditContent( component: SingleEditComponent, ) { AutoContentBasedColors(component.bitmap) var showResetDialog by rememberSaveable { mutableStateOf(false) } var showOriginal by rememberSaveable { mutableStateOf(false) } var showExitDialog by rememberSaveable { mutableStateOf(false) } val imageInfo = component.imageInfo val imagePicker = rememberImagePicker(onSuccess = component::setUri) val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = component.initialUri != null ) val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmap( oneTimeSaveLocationUri = it ) } val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } var showCompareSheet by rememberSaveable { mutableStateOf(false) } CompareSheet( data = component.initialBitmap to component.previewBitmap, visible = showCompareSheet, onDismiss = { showCompareSheet = false } ) ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } var showCropper by rememberSaveable { mutableStateOf(false) } var showFiltering by rememberSaveable { mutableStateOf(false) } var showDrawing by rememberSaveable { mutableStateOf(false) } var showEraseBackground by rememberSaveable { mutableStateOf(false) } var showApplyCurves by rememberSaveable { mutableStateOf(false) } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { val originalSize = component.uri.fileSize() ?: 0 val compressedSize = component.imageInfo.sizeInBytes.toLong() TopAppBarTitle( title = stringResource(R.string.single_edit), input = component.bitmap, isLoading = component.isImageLoading, size = compressedSize, originalSize = originalSize ) }, onGoBack = onBack, topAppBarPersistentActions = { if (component.bitmap == null) { TopAppBarEmoji() } CompareButton( onClick = { showCompareSheet = true }, visible = component.previewBitmap != null && component.bitmap != null && component.shouldShowPreview ) ZoomButton( onClick = { showZoomSheet = true }, visible = component.previewBitmap != null && component.shouldShowPreview ) }, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.bitmap != null, onShare = component::shareBitmap, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheCurrentImage { uri -> editSheetData = listOf(uri) } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) EnhancedIconButton( enabled = component.bitmap != null, onClick = { showResetDialog = true } ) { Icon( imageVector = Icons.Rounded.ImageReset, contentDescription = stringResource(R.string.reset_image) ) } if (component.bitmap != null) { ShowOriginalButton( canShow = component.canShow(), onStateChange = { showOriginal = it } ) } else { EnhancedIconButton( enabled = false, onClick = {} ) { Icon( imageVector = Icons.Rounded.History, contentDescription = stringResource(R.string.original) ) } } }, imagePreview = { ImageContainer( imageInside = isPortrait, showOriginal = showOriginal, previewBitmap = component.previewBitmap, originalBitmap = component.initialBitmap, isLoading = component.isImageLoading, shouldShowPreview = component.shouldShowPreview ) }, controls = { var showEditExifDialog by rememberSaveable { mutableStateOf(false) } val preset = component.presetSelected ImageTransformBar( onEditExif = { showEditExifDialog = true }, imageFormat = component.imageInfo.imageFormat, onRotateLeft = component::rotateBitmapLeft, onFlip = component::flipImage, onRotateRight = component::rotateBitmapRight, canRotate = !(preset is Preset.AspectRatio && preset.ratio != 1f) ) Spacer(Modifier.size(8.dp)) ImageExtraTransformBar( onCrop = { showCropper = true }, onFilter = { showFiltering = true }, onDraw = { showDrawing = true }, onEraseBackground = { showEraseBackground = true }, onApplyCurves = { showApplyCurves = true } ) Spacer(Modifier.size(16.dp)) PresetSelector( value = component.presetSelected, includeTelegramOption = true, includeAspectRatioOption = true, onValueChange = component::setPreset ) Spacer(Modifier.size(8.dp)) ResizeImageField( imageInfo = imageInfo, originalSize = component.originalSize, onHeightChange = component::updateHeight, onWidthChange = component::updateWidth, showWarning = component.showWarning ) if (imageInfo.imageFormat.canChangeCompressionValue) Spacer( Modifier.height(8.dp) ) QualitySelector( imageFormat = imageInfo.imageFormat, quality = imageInfo.quality, onQualityChange = component::setQuality ) Spacer(Modifier.height(8.dp)) ImageFormatSelector( value = imageInfo.imageFormat, onValueChange = component::setImageFormat, quality = imageInfo.quality, ) Spacer(Modifier.height(8.dp)) ResizeTypeSelector( enabled = component.bitmap != null, value = imageInfo.resizeType, onValueChange = component::setResizeType ) Spacer(Modifier.height(8.dp)) ScaleModeSelector( value = imageInfo.imageScaleMode, onValueChange = component::setImageScaleMode ) EditExifSheet( visible = showEditExifDialog, onDismiss = { showEditExifDialog = false }, exif = component.exif, onClearExif = component::clearExif, onUpdateTag = component::updateExifByTag, onRemoveTag = component::removeExifTag ) }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uri == Uri.EMPTY, onSecondaryButtonClick = pickImage, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true }, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Single, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, canShowScreenData = component.bitmap != null, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } }, forceImagePreviewToMax = showOriginal ) ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, onReset = { component.resetValues(true) } ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, onCancelLoading = component::cancelSaving ) CropEditOption( visible = showCropper, onDismiss = { showCropper = false }, useScaffold = isPortrait, bitmap = component.previewBitmap, onGetBitmap = component::updateBitmapAfterEditing, cropProperties = component.cropProperties, setCropAspectRatio = component::setCropAspectRatio, setCropMask = component::setCropMask, selectedAspectRatio = component.selectedAspectRatio, loadImage = component::loadImage ) FilterEditOption( visible = showFiltering, onDismiss = { showFiltering = false component.clearFilterList() }, useScaffold = isPortrait, bitmap = component.previewBitmap, onGetBitmap = { component.updateBitmapAfterEditing(it, true) }, onRequestMappingFilters = component::mapFilters, filterList = component.filterList, updateOrder = component::updateOrder, updateFilter = component::updateFilter, removeAt = component::removeFilterAtIndex, addFilter = component::addFilter, filterTemplateCreationSheetComponent = component.filterTemplateCreationSheetComponent, addFilterSheetComponent = component.addFiltersSheetComponent ) DrawEditOption( addFiltersSheetComponent = component.addFiltersSheetComponent, filterTemplateCreationSheetComponent = component.filterTemplateCreationSheetComponent, onRequestFiltering = component::filter, visible = showDrawing, onDismiss = { showDrawing = false component.clearDrawing() }, useScaffold = isPortrait, bitmap = component.previewBitmap, onGetBitmap = { component.updateBitmapAfterEditing(it, true) component.clearDrawing() }, undo = component::undoDraw, redo = component::redoDraw, paths = component.drawPaths, lastPaths = component.drawLastPaths, undonePaths = component.drawUndonePaths, addPath = component::addPathToDrawList, drawMode = component.drawMode, onUpdateDrawMode = component::updateDrawMode, drawPathMode = component.drawPathMode, onUpdateDrawPathMode = component::updateDrawPathMode, drawLineStyle = component.drawLineStyle, onUpdateDrawLineStyle = component::updateDrawLineStyle, helperGridParams = component.helperGridParams, onUpdateHelperGridParams = component::updateHelperGridParams ) EraseBackgroundEditOption( visible = showEraseBackground, onDismiss = { showEraseBackground = false component.clearErasing() }, useScaffold = isPortrait, bitmap = component.previewBitmap, onGetBitmap = { bitmap, saveSize -> component.updateBitmapAfterEditing( bitmap = bitmap, saveOriginalSize = saveSize ) }, clearErasing = component::clearErasing, undo = component::undoErase, redo = component::redoErase, paths = component.erasePaths, lastPaths = component.eraseLastPaths, undonePaths = component.eraseUndonePaths, addPath = component::addPathToEraseList, drawPathMode = component.drawPathMode, onUpdateDrawPathMode = component::updateDrawPathMode, autoBackgroundRemover = component.getBackgroundRemover(), helperGridParams = component.helperGridParams, onUpdateHelperGridParams = component::updateHelperGridParams ) ToneCurvesEditOption( visible = showApplyCurves, onDismiss = { showApplyCurves = false }, useScaffold = isPortrait, bitmap = component.previewBitmap, editorState = component.imageCurvesEditorState, onResetState = component::resetImageCurvesEditorState, onGetBitmap = { component.updateBitmapAfterEditing(it, true) } ) } ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/CropEditOption.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation.components import android.graphics.Bitmap import android.net.Uri import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.settings.CropOutlineProperty import com.t8rin.cropper.settings.CropProperties import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.utils.notNullAnd import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.CropSmall import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.MagnifierEnabledSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.image.AspectRatioSelector import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.crop.presentation.components.CoercePointsToImageBoundsToggle import com.t8rin.imagetoolbox.feature.crop.presentation.components.CropMaskSelection import com.t8rin.imagetoolbox.feature.crop.presentation.components.CropRotationSelector import com.t8rin.imagetoolbox.feature.crop.presentation.components.CropType import com.t8rin.imagetoolbox.feature.crop.presentation.components.Cropper import com.t8rin.imagetoolbox.feature.crop.presentation.components.FreeCornersCropToggle import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun CropEditOption( visible: Boolean, onDismiss: () -> Unit, useScaffold: Boolean, bitmap: Bitmap?, onGetBitmap: (Bitmap) -> Unit, cropProperties: CropProperties, selectedAspectRatio: DomainAspectRatio, setCropAspectRatio: (DomainAspectRatio, AspectRatio) -> Unit, setCropMask: (CropOutlineProperty) -> Unit, loadImage: suspend (Uri) -> Bitmap? ) { val rotationState = rememberSaveable { mutableFloatStateOf(0f) } var coercePointsToImageArea by rememberSaveable { mutableStateOf(true) } var cropType by rememberSaveable { mutableStateOf(CropType.Default) } LaunchedEffect(cropProperties.cropOutlineProperty) { cropType = if (cropProperties.cropOutlineProperty.cropOutline.id != 0) { CropType.NoRotation } else { CropType.Default } } val toggleFreeCornersCrop: () -> Unit = { cropType = if (cropType != CropType.FreeCorners) { CropType.FreeCorners } else if (cropProperties.cropOutlineProperty.cropOutline.id != 0) { CropType.NoRotation } else { CropType.Default } } val scope = rememberCoroutineScope() bitmap?.let { var crop by remember(visible) { mutableStateOf(false) } var stateBitmap by remember(bitmap, visible) { mutableStateOf(bitmap) } FullscreenEditOption( canGoBack = stateBitmap == bitmap, visible = visible, onDismiss = onDismiss, useScaffold = useScaffold, controls = { scaffoldState -> val focus = LocalFocusManager.current LaunchedEffect(scaffoldState?.bottomSheetState?.currentValue, focus) { val current = scaffoldState?.bottomSheetState?.currentValue if (current.notNullAnd { it != SheetValue.Expanded }) { focus.clearFocus() } } Spacer(modifier = Modifier.height(16.dp)) FreeCornersCropToggle( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), value = cropType == CropType.FreeCorners, onClick = toggleFreeCornersCrop ) BoxAnimatedVisibility( visible = cropType == CropType.Default, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { CropRotationSelector( value = rotationState.floatValue, onValueChange = { rotationState.floatValue = it }, modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) .padding(horizontal = 16.dp), ) } BoxAnimatedVisibility( visible = cropType == CropType.FreeCorners, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column( modifier = Modifier .padding(top = 8.dp) .padding(horizontal = 16.dp) ) { CoercePointsToImageBoundsToggle( value = coercePointsToImageArea, onValueChange = { coercePointsToImageArea = it }, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(8.dp)) MagnifierEnabledSelector( modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.extraLarge, ) } } BoxAnimatedVisibility( visible = cropType != CropType.FreeCorners, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { Spacer(modifier = Modifier.height(8.dp)) AspectRatioSelector( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), selectedAspectRatio = selectedAspectRatio, onAspectRatioChange = setCropAspectRatio ) Spacer(modifier = Modifier.height(8.dp)) CropMaskSelection( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), onCropMaskChange = setCropMask, selectedItem = cropProperties.cropOutlineProperty, loadImage = { loadImage(it)?.asImageBitmap() } ) } } Spacer(modifier = Modifier.height(16.dp)) }, fabButtons = { var job by remember { mutableStateOf(null) } EnhancedFloatingActionButton( onClick = { job?.cancel() job = scope.launch { delay(500) crop = true } }, containerColor = MaterialTheme.colorScheme.primaryContainer ) { Icon( imageVector = Icons.Rounded.CropSmall, contentDescription = stringResource(R.string.crop) ) } }, actions = {}, topAppBar = { closeButton -> EnhancedTopAppBar( type = EnhancedTopAppBarType.Center, navigationIcon = closeButton, actions = { AnimatedVisibility(visible = stateBitmap != bitmap) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, onClick = { onGetBitmap(stateBitmap) onDismiss() } ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } } }, title = { Text( text = stringResource(R.string.crop), modifier = Modifier.marquee() ) } ) } ) { var loading by remember { mutableStateOf(false) } Box(contentAlignment = Alignment.Center) { Cropper( bitmap = stateBitmap, crop = crop, onImageCropStarted = { loading = true }, onImageCropFinished = { if (it != null) { stateBitmap = it scope.launch { delay(500) loading = false } } else { loading = false } crop = false }, rotationState = rotationState, cropProperties = cropProperties, cropType = cropType, addVerticalInsets = !useScaffold, coercePointsToImageArea = coercePointsToImageArea ) AnimatedVisibility( visible = loading, modifier = Modifier.fillMaxSize(), enter = fadeIn(), exit = fadeOut() ) { Box( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.scrim.copy(0.5f)) ) { EnhancedLoadingIndicator() } } } } } } ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/DrawEditOption.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation.components import android.graphics.Bitmap import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Redo import androidx.compose.material.icons.automirrored.rounded.Undo import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.coerceIn import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.domain.utils.notNullAnd import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.widget.buttons.EraseModeButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.HelperGridParamsSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.MagnifierEnabledSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.DrawLockScreenOrientation import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.saver.PtSaver import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.BitmapDrawer import com.t8rin.imagetoolbox.feature.draw.presentation.components.BrushSoftnessSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawColorSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawLineStyleSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawModeSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawPathModeSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.LineWidthSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.OpenColorPickerCard import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageSheet @Composable fun DrawEditOption( visible: Boolean, onRequestFiltering: suspend (Bitmap, List>) -> Bitmap?, drawMode: DrawMode, onUpdateDrawMode: (DrawMode) -> Unit, drawPathMode: DrawPathMode, onUpdateDrawPathMode: (DrawPathMode) -> Unit, drawLineStyle: DrawLineStyle, onUpdateDrawLineStyle: (DrawLineStyle) -> Unit, onDismiss: () -> Unit, useScaffold: Boolean, bitmap: Bitmap?, onGetBitmap: (Bitmap) -> Unit, undo: () -> Unit, redo: () -> Unit, paths: List, lastPaths: List, undonePaths: List, addPath: (UiPathPaint) -> Unit, helperGridParams: HelperGridParams, onUpdateHelperGridParams: (HelperGridParams) -> Unit, addFiltersSheetComponent: AddFiltersSheetComponent, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent ) { bitmap?.let { var panEnabled by rememberSaveable { mutableStateOf(false) } val switch = @Composable { PanModeButton( selected = panEnabled, onClick = { panEnabled = !panEnabled } ) } var showPickColorSheet by rememberSaveable { mutableStateOf(false) } var isEraserOn by rememberSaveable { mutableStateOf(false) } val settingsState = LocalSettingsState.current var strokeWidth by rememberSaveable(stateSaver = PtSaver) { mutableStateOf(settingsState.defaultDrawLineWidth.pt) } var drawColor by rememberSaveable(stateSaver = ColorSaver) { mutableStateOf(settingsState.defaultDrawColor) } var alpha by rememberSaveable(drawMode) { mutableFloatStateOf(if (drawMode is DrawMode.Highlighter) 0.4f else 1f) } var brushSoftness by rememberSaveable(drawMode, stateSaver = PtSaver) { mutableStateOf(if (drawMode is DrawMode.Neon) 35.pt else 0.pt) } LaunchedEffect(drawMode, strokeWidth) { strokeWidth = if (drawMode is DrawMode.Image) { strokeWidth.coerceIn(10.pt, 120.pt) } else { strokeWidth.coerceIn(1.pt, 100.pt) } } val secondaryControls = @Composable { Row( modifier = Modifier.then( if (!useScaffold) { Modifier .padding(16.dp) .container(shape = ShapeDefaults.circle) } else Modifier ) ) { switch() Spacer(Modifier.width(8.dp)) EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = undo, enabled = lastPaths.isNotEmpty() || paths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Undo, contentDescription = "Undo" ) } EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = redo, enabled = undonePaths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Redo, contentDescription = "Redo" ) } EraseModeButton( selected = isEraserOn, enabled = !panEnabled, onClick = { isEraserOn = !isEraserOn } ) } } var stateBitmap by remember(bitmap, visible) { mutableStateOf(bitmap) } FullscreenEditOption( canGoBack = paths.isEmpty(), visible = visible, onDismiss = onDismiss, useScaffold = useScaffold, controls = { scaffoldState -> Column( modifier = Modifier.padding(vertical = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { val focus = LocalFocusManager.current LaunchedEffect(scaffoldState?.bottomSheetState?.currentValue, focus) { val current = scaffoldState?.bottomSheetState?.currentValue if (current.notNullAnd { it != SheetValue.Expanded }) { focus.clearFocus() } } if (!useScaffold) secondaryControls() AnimatedVisibility( visible = drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { OpenColorPickerCard( onOpen = { showPickColorSheet = true } ) } AnimatedVisibility( visible = drawMode !is DrawMode.PathEffect && drawMode !is DrawMode.Image && drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { DrawColorSelector( value = drawColor, onValueChange = { drawColor = it }, modifier = Modifier.padding(horizontal = 16.dp) ) } AnimatedVisibility( visible = drawPathMode.canChangeStrokeWidth, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { LineWidthSelector( modifier = Modifier.padding(horizontal = 16.dp), title = if (drawMode is DrawMode.Text) { stringResource(R.string.font_size) } else stringResource(R.string.line_width), valueRange = if (drawMode is DrawMode.Image) { 10f..120f } else 1f..100f, value = strokeWidth.value, onValueChange = { strokeWidth = it.pt } ) } AnimatedVisibility( visible = drawMode !is DrawMode.Highlighter && drawMode !is DrawMode.PathEffect && drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { BrushSoftnessSelector( modifier = Modifier.padding(horizontal = 16.dp), value = brushSoftness.value, onValueChange = { brushSoftness = it.pt } ) } AnimatedVisibility( visible = drawMode !is DrawMode.Neon && drawMode !is DrawMode.PathEffect && drawMode !is DrawMode.SpotHeal && drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { AlphaSelector( value = alpha, onValueChange = { alpha = it }, modifier = Modifier.padding(horizontal = 16.dp) ) } DrawModeSelector( addFiltersSheetComponent = addFiltersSheetComponent, filterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent, modifier = Modifier.padding(horizontal = 16.dp), value = drawMode, strokeWidth = strokeWidth, onValueChange = { if (it is DrawMode.Warp) { onUpdateDrawPathMode(DrawPathMode.Free) onUpdateDrawLineStyle(DrawLineStyle.None) } onUpdateDrawMode(it) }, values = remember(drawLineStyle) { derivedStateOf { if (drawLineStyle == DrawLineStyle.None) { DrawMode.entries } else { listOf( DrawMode.Pen, DrawMode.Highlighter, DrawMode.Neon ) } } }.value ) AnimatedVisibility( visible = drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { DrawPathModeSelector( modifier = Modifier.padding(horizontal = 16.dp), value = drawPathMode, onValueChange = onUpdateDrawPathMode, values = remember(drawMode, drawLineStyle) { derivedStateOf { if (drawMode !is DrawMode.Text && drawMode !is DrawMode.Image) { when (drawLineStyle) { DrawLineStyle.None -> DrawPathMode.entries !is DrawLineStyle.Stamped<*> -> listOf( DrawPathMode.Free, DrawPathMode.Line, DrawPathMode.LinePointingArrow(), DrawPathMode.PointingArrow(), DrawPathMode.DoublePointingArrow(), DrawPathMode.DoubleLinePointingArrow(), ) + DrawPathMode.outlinedEntries else -> listOf( DrawPathMode.Free, DrawPathMode.Line ) + DrawPathMode.outlinedEntries } } else { listOf( DrawPathMode.Free, DrawPathMode.Line ) + DrawPathMode.outlinedEntries } } }.value, drawMode = drawMode ) } AnimatedVisibility( visible = drawMode !is DrawMode.Warp, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically(), modifier = Modifier.fillMaxWidth() ) { DrawLineStyleSelector( modifier = Modifier.padding(horizontal = 16.dp), value = drawLineStyle, onValueChange = onUpdateDrawLineStyle ) } HelperGridParamsSelector( value = helperGridParams, onValueChange = onUpdateHelperGridParams, modifier = Modifier.padding(horizontal = 16.dp) ) MagnifierEnabledSelector( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), shape = ShapeDefaults.extraLarge, ) } }, fabButtons = null, actions = { if (useScaffold) { secondaryControls() Spacer(Modifier.weight(1f)) } }, topAppBar = { closeButton -> EnhancedTopAppBar( type = EnhancedTopAppBarType.Center, navigationIcon = closeButton, actions = { AnimatedVisibility( visible = paths.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, onClick = { onGetBitmap(stateBitmap) onDismiss() } ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } } }, title = { Text( text = stringResource(R.string.draw), modifier = Modifier.marquee() ) } ) } ) { val direction = LocalLayoutDirection.current Box(contentAlignment = Alignment.Center) { remember(bitmap) { derivedStateOf { bitmap.copy(Bitmap.Config.ARGB_8888, true).asImageBitmap() } }.value.let { imageBitmap -> val aspectRatio = imageBitmap.width / imageBitmap.height.toFloat() BitmapDrawer( imageBitmap = imageBitmap, paths = paths, onRequestFiltering = onRequestFiltering, strokeWidth = strokeWidth, brushSoftness = brushSoftness, drawColor = drawColor.copy(alpha), onAddPath = addPath, isEraserOn = isEraserOn, drawMode = drawMode, modifier = Modifier .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ) .padding(16.dp) .aspectRatio(aspectRatio, !useScaffold) .fillMaxSize(), panEnabled = panEnabled, onDraw = { stateBitmap = it }, drawPathMode = drawPathMode, backgroundColor = Color.Transparent, drawLineStyle = drawLineStyle, helperGridParams = helperGridParams ) } } } var color by rememberSaveable(stateSaver = ColorSaver) { mutableStateOf(Color.Black) } PickColorFromImageSheet( visible = showPickColorSheet, onDismiss = { showPickColorSheet = false }, bitmap = stateBitmap, onColorChange = { color = it }, color = color ) if (visible) { DrawLockScreenOrientation() } } } ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/EraseBackgroundEditOption.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation.components import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.Redo import androidx.compose.material.icons.automirrored.rounded.Undo import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.model.pt import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.HelperGridParamsSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.MagnifierEnabledSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.other.BoxAnimatedVisibility import com.t8rin.imagetoolbox.core.ui.widget.other.DrawLockScreenOrientation import com.t8rin.imagetoolbox.core.ui.widget.saver.PtSaver import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.BrushSoftnessSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.DrawPathModeSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.LineWidthSelector import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemover import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.AutoEraseBackgroundCard import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.BitmapEraser import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.OriginalImagePreviewAlphaSelector import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.RecoverModeButton import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.RecoverModeCard import com.t8rin.imagetoolbox.feature.erase_background.presentation.components.TrimImageToggle import kotlinx.coroutines.launch @Composable fun EraseBackgroundEditOption( visible: Boolean, onDismiss: () -> Unit, useScaffold: Boolean, bitmap: Bitmap?, onGetBitmap: (Bitmap, Boolean) -> Unit, clearErasing: (Boolean) -> Unit, undo: () -> Unit, redo: () -> Unit, paths: List, lastPaths: List, undonePaths: List, drawPathMode: DrawPathMode, onUpdateDrawPathMode: (DrawPathMode) -> Unit, addPath: (UiPathPaint) -> Unit, autoBackgroundRemover: AutoBackgroundRemover, helperGridParams: HelperGridParams, onUpdateHelperGridParams: (HelperGridParams) -> Unit, ) { val scope = rememberCoroutineScope() bitmap?.let { var panEnabled by rememberSaveable { mutableStateOf(false) } val switch = @Composable { PanModeButton( selected = panEnabled, onClick = { panEnabled = !panEnabled } ) } var isRecoveryOn by rememberSaveable { mutableStateOf(false) } val settingsState = LocalSettingsState.current var strokeWidth by rememberSaveable(stateSaver = PtSaver) { mutableStateOf(settingsState.defaultDrawLineWidth.pt) } var brushSoftness by rememberSaveable(stateSaver = PtSaver) { mutableStateOf(0.pt) } var originalImagePreviewAlpha by rememberSaveable { mutableFloatStateOf(0.2f) } var trimImage by rememberSaveable { mutableStateOf(true) } val secondaryControls = @Composable { Row( modifier = Modifier .then( if (!useScaffold) { Modifier .padding(16.dp) .container(shape = ShapeDefaults.circle) } else Modifier ) ) { switch() Spacer(Modifier.width(8.dp)) EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = undo, enabled = lastPaths.isNotEmpty() || paths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Undo, contentDescription = "Undo" ) } EnhancedIconButton( containerColor = Color.Transparent, borderColor = MaterialTheme.colorScheme.outlineVariant( luminance = 0.1f ), onClick = redo, enabled = undonePaths.isNotEmpty() ) { Icon( imageVector = Icons.AutoMirrored.Rounded.Redo, contentDescription = "Redo" ) } RecoverModeButton( selected = isRecoveryOn, enabled = !panEnabled, onClick = { isRecoveryOn = !isRecoveryOn } ) } } var bitmapState by remember(bitmap, visible) { mutableStateOf(bitmap) } var erasedBitmap by remember(bitmap, visible) { mutableStateOf(bitmap) } var loading by remember { mutableStateOf(false) } var autoErased by remember { mutableStateOf(false) } FullscreenEditOption( canGoBack = paths.isEmpty() && !autoErased, visible = visible, onDismiss = onDismiss, useScaffold = useScaffold, controls = { scaffoldState -> Column( horizontalAlignment = Alignment.CenterHorizontally ) { if (!useScaffold) secondaryControls() Spacer(modifier = Modifier.height(8.dp)) RecoverModeCard( selected = isRecoveryOn, enabled = !panEnabled, onClick = { isRecoveryOn = !isRecoveryOn } ) AutoEraseBackgroundCard( onClick = { modelType -> scope.launch { scaffoldState?.bottomSheetState?.partialExpand() } loading = true autoBackgroundRemover.removeBackgroundFromImage( image = erasedBitmap, modelType = modelType, onSuccess = { loading = false bitmapState = it clearErasing(false) autoErased = true AppToastHost.showConfetti() }, onFailure = { loading = false AppToastHost.showFailureToast(it) } ) }, onReset = { bitmapState = bitmap autoErased = true } ) OriginalImagePreviewAlphaSelector( value = originalImagePreviewAlpha, onValueChange = { originalImagePreviewAlpha = it }, modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp) ) DrawPathModeSelector( modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 8.dp ), value = drawPathMode, onValueChange = onUpdateDrawPathMode, values = remember { listOf( DrawPathMode.Free, DrawPathMode.FloodFill(), DrawPathMode.Spray(), DrawPathMode.Line, DrawPathMode.Lasso, DrawPathMode.Rect(), DrawPathMode.Oval ) }, drawMode = DrawMode.Pen ) BoxAnimatedVisibility(drawPathMode.canChangeStrokeWidth) { LineWidthSelector( modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp), value = strokeWidth.value, onValueChange = { strokeWidth = it.pt } ) } BrushSoftnessSelector( modifier = Modifier .padding(top = 8.dp, end = 16.dp, start = 16.dp), value = brushSoftness.value, onValueChange = { brushSoftness = it.pt } ) TrimImageToggle( checked = trimImage, onCheckedChange = { trimImage = it }, modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 8.dp, ) ) HelperGridParamsSelector( value = helperGridParams, onValueChange = onUpdateHelperGridParams, modifier = Modifier.padding( start = 16.dp, end = 16.dp, top = 8.dp, ) ) MagnifierEnabledSelector( modifier = Modifier .fillMaxWidth() .padding( start = 16.dp, end = 16.dp, top = 8.dp, bottom = 16.dp ), shape = ShapeDefaults.extraLarge ) } }, fabButtons = null, actions = { if (useScaffold) { secondaryControls() Spacer(Modifier.weight(1f)) } }, topAppBar = { closeButton -> EnhancedTopAppBar( type = EnhancedTopAppBarType.Center, navigationIcon = closeButton, actions = { AnimatedVisibility( visible = paths.isNotEmpty() || autoErased, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, onClick = { scope.launch { val trimmed = if (trimImage) { autoBackgroundRemover.trimEmptyParts( erasedBitmap ) } else { erasedBitmap } onGetBitmap( trimmed, trimmed.width == erasedBitmap.width && trimmed.height == erasedBitmap.height ) clearErasing(false) } onDismiss() } ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } } }, title = { Text( text = stringResource(R.string.erase_background), modifier = Modifier.marquee() ) } ) } ) { Box(contentAlignment = Alignment.Center) { AnimatedContent( targetState = remember(bitmapState) { derivedStateOf { bitmapState.copy(Bitmap.Config.ARGB_8888, true).asImageBitmap() } }.value, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { imageBitmap -> val direction = LocalLayoutDirection.current val aspectRatio = imageBitmap.width / imageBitmap.height.toFloat() BitmapEraser( imageBitmapForShader = bitmap.asImageBitmap(), imageBitmap = imageBitmap, paths = paths, strokeWidth = strokeWidth, brushSoftness = brushSoftness, onAddPath = addPath, isRecoveryOn = isRecoveryOn, modifier = Modifier .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ) .padding(16.dp) .aspectRatio(aspectRatio, !useScaffold) .fillMaxSize(), panEnabled = panEnabled, drawPathMode = drawPathMode, originalImagePreviewAlpha = originalImagePreviewAlpha, onErased = { erasedBitmap = it }, helperGridParams = helperGridParams ) } AnimatedVisibility( visible = loading, modifier = Modifier.fillMaxSize(), enter = fadeIn(), exit = fadeOut() ) { Box( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.scrim.copy(0.5f)) ) { EnhancedLoadingIndicator() } } } } if (visible) { DrawLockScreenOrientation() } } } ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FilterEditOption.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation.components import android.graphics.Bitmap import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AutoFixHigh import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil3.toBitmap import com.t8rin.imagetoolbox.core.data.utils.toCoil import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.filters.domain.model.TemplateFilter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterItem import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterReorderSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheet import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.utils.helper.ProvideFilterPreview import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedFloatingActionButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.tappable import com.t8rin.imagetoolbox.core.ui.widget.modifier.transparencyChecker import com.t8rin.imagetoolbox.core.ui.widget.saver.ColorSaver import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.feature.draw.presentation.components.OpenColorPickerCard import com.t8rin.imagetoolbox.feature.pick_color.presentation.components.PickColorFromImageSheet import kotlinx.coroutines.launch import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable fun FilterEditOption( addFilterSheetComponent: AddFiltersSheetComponent, filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent, visible: Boolean, onDismiss: () -> Unit, useScaffold: Boolean, bitmap: Bitmap?, onGetBitmap: (Bitmap) -> Unit, onRequestMappingFilters: (List>) -> List>, filterList: List>, updateFilter: (Any, Int) -> Unit, removeAt: (Int) -> Unit, addFilter: (UiFilter<*>) -> Unit, updateOrder: (List>) -> Unit ) { var stateBitmap by remember(bitmap, visible) { mutableStateOf(if (!visible) null else bitmap) } ProvideFilterPreview(stateBitmap) bitmap?.let { val scaffoldState = rememberBottomSheetScaffoldState() var showFilterSheet by rememberSaveable { mutableStateOf(false) } var showReorderSheet by rememberSaveable { mutableStateOf(false) } var showColorPicker by rememberSaveable { mutableStateOf(false) } var tempColor by rememberSaveable( showColorPicker, stateSaver = ColorSaver ) { mutableStateOf(Color.Black) } LaunchedEffect(visible) { if (visible && filterList.isEmpty()) { showFilterSheet = true } } var showTemplateCreationSheet by rememberSaveable { mutableStateOf(false) } FullscreenEditOption( showControls = filterList.isNotEmpty(), canGoBack = stateBitmap == bitmap, visible = visible, modifier = Modifier.heightIn(max = LocalScreenSize.current.height / 1.5f), onDismiss = onDismiss, useScaffold = useScaffold, controls = { Column( modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { if (!useScaffold) { OpenColorPickerCard( onOpen = { showColorPicker = true }, modifier = Modifier .fillMaxWidth() .padding(bottom = 16.dp) ) } Column(Modifier.container(MaterialTheme.shapes.extraLarge)) { TitleItem(text = stringResource(R.string.filters)) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(8.dp) ) { filterList.forEachIndexed { index, filter -> FilterItem( filter = filter, onFilterChange = { filterChange -> updateFilter( filterChange, index ) }, onLongPress = { showReorderSheet = true }, backgroundColor = MaterialTheme.colorScheme.surface, showDragHandle = false, onRemove = { removeAt(index) }, onCreateTemplate = { showTemplateCreationSheet = true filterTemplateCreationSheetComponent.setInitialTemplateFilter( TemplateFilter( name = getString(filter.title), filters = listOf(filter) ) ) } ) } EnhancedButton( containerColor = MaterialTheme.colorScheme.mixedContainer, onClick = { showFilterSheet = true }, modifier = Modifier.padding(horizontal = 16.dp) ) { Icon( imageVector = Icons.Rounded.AutoFixHigh, contentDescription = null ) Spacer(Modifier.width(8.dp)) Text(stringResource(R.string.add_filter)) } } FilterTemplateCreationSheet( component = filterTemplateCreationSheetComponent, visible = showTemplateCreationSheet, onDismiss = { showTemplateCreationSheet = false } ) } } }, fabButtons = { EnhancedFloatingActionButton( onClick = { showFilterSheet = true }, containerColor = MaterialTheme.colorScheme.primaryContainer, ) { Icon( imageVector = Icons.Rounded.AutoFixHigh, contentDescription = stringResource(R.string.add_filter) ) } }, scaffoldState = scaffoldState, actions = { if (filterList.isEmpty()) { Text( text = stringResource(id = R.string.add_filter), modifier = Modifier .padding(horizontal = 16.dp) .tappable { showFilterSheet = true } ) } else { EnhancedIconButton( onClick = { showColorPicker = true }, ) { Icon( imageVector = Icons.Outlined.Eyedropper, contentDescription = stringResource(R.string.pipette) ) } } }, topAppBar = { closeButton -> EnhancedTopAppBar( type = EnhancedTopAppBarType.Center, navigationIcon = closeButton, actions = { AnimatedVisibility( visible = stateBitmap != bitmap && stateBitmap != null, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, onClick = { stateBitmap?.let(onGetBitmap) onDismiss() } ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } } }, title = { Text( text = stringResource(R.string.filter), modifier = Modifier.marquee() ) } ) } ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { val direction = LocalLayoutDirection.current Picture( model = bitmap, shape = RectangleShape, transformations = remember(filterList) { derivedStateOf { onRequestMappingFilters(filterList).map { it.toCoil() } } }.value, onSuccess = { stateBitmap = it.result.image.toBitmap() }, showTransparencyChecker = false, modifier = Modifier .fillMaxSize() .clipToBounds() .transparencyChecker() .clipToBounds() .zoomable(rememberZoomState()) .padding( start = WindowInsets .displayCutout .asPaddingValues() .calculateStartPadding(direction) ), contentScale = ContentScale.Fit, ) } } val scope = rememberCoroutineScope() AddFiltersSheet( visible = showFilterSheet, onVisibleChange = { showFilterSheet = it }, previewBitmap = stateBitmap, onFilterPicked = { scope.launch { scaffoldState.bottomSheetState.expand() } addFilter(it.newInstance()) }, onFilterPickedWithParams = { scope.launch { scaffoldState.bottomSheetState.expand() } addFilter(it) }, component = addFilterSheetComponent, filterTemplateCreationSheetComponent = filterTemplateCreationSheetComponent ) FilterReorderSheet( filterList = filterList, visible = showReorderSheet, onDismiss = { showReorderSheet = false }, onReorder = updateOrder ) PickColorFromImageSheet( visible = showColorPicker, onDismiss = { showColorPicker = false }, bitmap = stateBitmap, onColorChange = { tempColor = it }, color = tempColor ) } } ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/FullscreenEditOption.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Tune import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.BottomSheetScaffoldState import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SheetValue import androidx.compose.material3.Surface import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.PredictiveBackObserver import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.utils.provider.ProvideContainerDefaults import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitBackHandler import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedBottomSheetDefaults import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedVerticalScroll import com.t8rin.imagetoolbox.core.ui.widget.modifier.clearFocusOnTap import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.drawHorizontalStroke import com.t8rin.imagetoolbox.core.ui.widget.modifier.onSwipeDown import com.t8rin.imagetoolbox.core.ui.widget.modifier.toShape import com.t8rin.imagetoolbox.core.ui.widget.modifier.withLayoutCorners import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun FullscreenEditOption( visible: Boolean, canGoBack: Boolean, onDismiss: () -> Unit, useScaffold: Boolean, modifier: Modifier = Modifier, showControls: Boolean = true, controls: @Composable (BottomSheetScaffoldState?) -> Unit, fabButtons: (@Composable () -> Unit)?, actions: @Composable RowScope.() -> Unit, topAppBar: @Composable (closeButton: @Composable () -> Unit) -> Unit, scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(), content: @Composable () -> Unit ) { var predictiveBackProgress by remember { mutableFloatStateOf(0f) } LaunchedEffect(predictiveBackProgress, visible) { if (!visible && predictiveBackProgress != 0f) { delay(600) predictiveBackProgress = 0f } } AnimatedVisibility( visible = visible, modifier = Modifier.fillMaxSize(), enter = fadeIn(tween(600)), exit = fadeOut(tween(600)) ) { var showExitDialog by remember(visible) { mutableStateOf(false) } val internalOnDismiss = { if (!canGoBack) showExitDialog = true else onDismiss() } val direction = LocalLayoutDirection.current val animatedPredictiveBackProgress by animateFloatAsState(predictiveBackProgress) val scale = (1f - animatedPredictiveBackProgress).coerceAtLeast(0.75f) Box( Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.scrim.copy(0.5f * scale)) ) Surface( modifier = Modifier .fillMaxSize() .withLayoutCorners { corners -> graphicsLayer { scaleX = scale scaleY = scale shape = corners.toShape(animatedPredictiveBackProgress) clip = true } } ) { Column { if (useScaffold) { val screenHeight = LocalScreenSize.current.height val sheetSwipeEnabled = scaffoldState.bottomSheetState.currentValue == SheetValue.PartiallyExpanded && !scaffoldState.bottomSheetState.isAnimationRunning BottomSheetScaffold( topBar = { topAppBar { EnhancedIconButton( onClick = internalOnDismiss ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } }, scaffoldState = scaffoldState, sheetPeekHeight = 80.dp + WindowInsets.navigationBars.asPaddingValues() .calculateBottomPadding(), sheetDragHandle = null, sheetShape = RectangleShape, sheetSwipeEnabled = sheetSwipeEnabled, sheetContent = { Scaffold( modifier = modifier .heightIn(max = screenHeight * 0.7f) .clearFocusOnTap(), topBar = { val scope = rememberCoroutineScope() Box( modifier = Modifier.onSwipeDown(!sheetSwipeEnabled) { scope.launch { scaffoldState.bottomSheetState.partialExpand() } } ) { BottomAppBar( modifier = Modifier.drawHorizontalStroke(true), actions = { actions() if (showControls) { EnhancedIconButton( onClick = { scope.launch { if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) { scaffoldState.bottomSheetState.partialExpand() } else { scaffoldState.bottomSheetState.expand() } } } ) { Icon( imageVector = Icons.Rounded.Tune, contentDescription = stringResource(R.string.properties) ) } } }, floatingActionButton = { Row( horizontalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterHorizontally ), verticalAlignment = Alignment.CenterVertically ) { if (fabButtons != null) { fabButtons() } } } ) } } ) { contentPadding -> if (showControls) { Column( modifier = Modifier .enhancedVerticalScroll(rememberScrollState()) .padding(contentPadding), horizontalAlignment = Alignment.CenterHorizontally ) { ProvideContainerDefaults( color = EnhancedBottomSheetDefaults.contentContainerColor ) { controls(scaffoldState) } } } } }, content = { Box(Modifier.padding(it)) { content() } } ) } else { Scaffold( topBar = { topAppBar { EnhancedIconButton( onClick = internalOnDismiss ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } }, contentWindowInsets = WindowInsets() ) { contentPadding -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(contentPadding) ) { Box( Modifier .container( shape = RectangleShape, resultPadding = 0.dp ) .weight(0.8f) .fillMaxHeight() .clipToBounds() ) { content() } if (showControls) { Column( modifier = Modifier .weight(0.7f) .clearFocusOnTap() .enhancedVerticalScroll(rememberScrollState()) .then( if (fabButtons == null) { Modifier.padding( end = WindowInsets.displayCutout .asPaddingValues() .calculateEndPadding(direction) ) } else Modifier ), horizontalAlignment = Alignment.CenterHorizontally ) { ProvideContainerDefaults( color = MaterialTheme.colorScheme.surfaceContainerLowest ) { controls(null) } } } fabButtons?.let { Column( Modifier .container( shape = RectangleShape, resultPadding = 0.dp ) .padding(horizontal = 20.dp) .padding( end = WindowInsets.displayCutout .asPaddingValues() .calculateEndPadding(direction) ) .fillMaxHeight() .navigationBarsPadding(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterVertically ) ) { it() } } } } } } } if (visible) { if (canGoBack) { PredictiveBackObserver( onProgress = { predictiveBackProgress = it / 6f }, onClean = { isCompleted -> if (isCompleted) { internalOnDismiss() delay(400) } predictiveBackProgress = 0f } ) } else { ExitBackHandler(onBack = internalOnDismiss) } ExitWithoutSavingDialog( onExit = onDismiss, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } } } ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/components/ToneCurvesEditOption.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation.components import android.graphics.Bitmap import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.curves.ImageCurvesEditor import com.t8rin.curves.ImageCurvesEditorState import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShowOriginalButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBar import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedTopAppBarType import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee @Composable fun ToneCurvesEditOption( visible: Boolean, onDismiss: () -> Unit, useScaffold: Boolean, bitmap: Bitmap?, onGetBitmap: (Bitmap) -> Unit, editorState: ImageCurvesEditorState, onResetState: () -> Unit ) { bitmap?.let { val scaffoldState = rememberBottomSheetScaffoldState() var showOriginal by remember { mutableStateOf(false) } var imageObtainingTrigger by remember { mutableStateOf(false) } var showResetDialog by rememberSaveable { mutableStateOf(false) } val actions = @Composable { EnhancedIconButton( onClick = { showResetDialog = true } ) { Icon( imageVector = Icons.Rounded.ImageReset, contentDescription = stringResource(R.string.reset_image) ) } ShowOriginalButton( onStateChange = { showOriginal = it } ) } var isDefault by remember(editorState) { mutableStateOf(editorState.isDefault()) } FullscreenEditOption( showControls = false, canGoBack = isDefault, visible = visible, modifier = Modifier.heightIn(max = LocalScreenSize.current.height / 1.5f), onDismiss = { onDismiss() onResetState() }, useScaffold = useScaffold, controls = { }, fabButtons = if (useScaffold) { { Box( modifier = Modifier .size(48.dp) .offset(y = 8.dp), contentAlignment = Alignment.Center ) { TopAppBarEmoji() } } } else { { actions() } }, scaffoldState = scaffoldState, actions = { actions() }, topAppBar = { closeButton -> EnhancedTopAppBar( type = EnhancedTopAppBarType.Center, navigationIcon = closeButton, actions = { EnhancedIconButton( containerColor = MaterialTheme.colorScheme.tertiaryContainer, onClick = { imageObtainingTrigger = true }, enabled = !showOriginal ) { Icon( imageVector = Icons.Rounded.Done, contentDescription = "Done" ) } }, title = { Text( text = stringResource(R.string.tone_curves), modifier = Modifier.marquee() ) } ) } ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { ImageCurvesEditor( bitmap = bitmap, state = editorState, curvesSelectionText = { Text( text = when (it) { 0 -> stringResource(R.string.all) 1 -> stringResource(R.string.color_red) 2 -> stringResource(R.string.color_green) 3 -> stringResource(R.string.color_blue) else -> "" }, style = MaterialTheme.typography.bodySmall ) }, placeControlsAtTheEnd = !useScaffold, imageObtainingTrigger = imageObtainingTrigger, onImageObtained = { imageObtainingTrigger = false onGetBitmap(it) onResetState() onDismiss() }, contentPadding = WindowInsets.systemBars.union(WindowInsets.displayCutout) .let { if (!useScaffold) it.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) else it.only(WindowInsetsSides.Horizontal) } .union( WindowInsets( left = 16.dp, top = 16.dp, right = 16.dp, bottom = 16.dp ) ) .asPaddingValues(), containerModifier = Modifier.align(Alignment.Center), showOriginal = showOriginal, onStateChange = { isDefault = it.isDefault() } ) } } ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, title = stringResource(R.string.reset_curves), text = stringResource(R.string.reset_curves_sub), onReset = onResetState ) } } ================================================ FILE: feature/single-edit/src/main/java/com/t8rin/imagetoolbox/feature/single_edit/presentation/screenLogic/SingleEditComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.single_edit.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.childContext import com.arkivanov.essenty.lifecycle.doOnDestroy import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.model.OutlineType import com.t8rin.cropper.model.RectCropShape import com.t8rin.cropper.settings.CropDefaults import com.t8rin.cropper.settings.CropOutlineProperty import com.t8rin.curves.ImageCurvesEditorState import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImagePreviewCreator import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.Metadata import com.t8rin.imagetoolbox.core.domain.image.clearAllAttributes import com.t8rin.imagetoolbox.core.domain.image.clearAttribute import com.t8rin.imagetoolbox.core.domain.image.model.ImageData import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.MetadataTag import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.DomainAspectRatio import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.transformation.Transformation import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.update import com.t8rin.imagetoolbox.core.filters.domain.FilterProvider import com.t8rin.imagetoolbox.core.filters.domain.model.Filter import com.t8rin.imagetoolbox.core.filters.presentation.model.UiFilter import com.t8rin.imagetoolbox.core.filters.presentation.widget.FilterTemplateCreationSheetComponent import com.t8rin.imagetoolbox.core.filters.presentation.widget.addFilters.AddFiltersSheetComponent import com.t8rin.imagetoolbox.core.settings.domain.SettingsProvider import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.safeAspectRatio import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.savable import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.core.ui.widget.modifier.HelperGridParams import com.t8rin.imagetoolbox.feature.draw.domain.DrawLineStyle import com.t8rin.imagetoolbox.feature.draw.domain.DrawMode import com.t8rin.imagetoolbox.feature.draw.domain.DrawPathMode import com.t8rin.imagetoolbox.feature.draw.presentation.components.UiPathPaint import com.t8rin.imagetoolbox.feature.erase_background.domain.AutoBackgroundRemover import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.withContext class SingleEditComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUri: Uri?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageTransformer: ImageTransformer, private val imagePreviewCreator: ImagePreviewCreator, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val autoBackgroundRemover: AutoBackgroundRemover, private val shareProvider: ImageShareProvider, private val filterProvider: FilterProvider, private val settingsProvider: SettingsProvider, dispatchersHolder: DispatchersHolder, addFiltersSheetComponentFactory: AddFiltersSheetComponent.Factory, filterTemplateCreationSheetComponentFactory: FilterTemplateCreationSheetComponent.Factory, ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUri?.let(::setUri) } doOnDestroy { autoBackgroundRemover.cleanup() } } val addFiltersSheetComponent: AddFiltersSheetComponent = addFiltersSheetComponentFactory( componentContext = componentContext.childContext( key = "addFiltersSingle" ) ) val filterTemplateCreationSheetComponent: FilterTemplateCreationSheetComponent = filterTemplateCreationSheetComponentFactory( componentContext = componentContext.childContext( key = "filterTemplateCreationSheetComponentSingle" ) ) private val _originalSize: MutableState = mutableStateOf(null) val originalSize by _originalSize private val _erasePaths = mutableStateOf(listOf()) val erasePaths: List by _erasePaths private val _eraseLastPaths = mutableStateOf(listOf()) val eraseLastPaths: List by _eraseLastPaths private val _eraseUndonePaths = mutableStateOf(listOf()) val eraseUndonePaths: List by _eraseUndonePaths private val _drawPaths = mutableStateOf(listOf()) val drawPaths: List by _drawPaths private val _drawLastPaths = mutableStateOf(listOf()) val drawLastPaths: List by _drawLastPaths private val _drawUndonePaths = mutableStateOf(listOf()) val drawUndonePaths: List by _drawUndonePaths private val _filterList = mutableStateOf(listOf>()) val filterList by _filterList private val _selectedAspectRatio: MutableState = mutableStateOf(DomainAspectRatio.Free) val selectedAspectRatio by _selectedAspectRatio private val _cropProperties = mutableStateOf( CropDefaults.properties( cropOutlineProperty = CropOutlineProperty( outlineType = OutlineType.Rect, cropOutline = RectCropShape( id = 0, title = OutlineType.Rect.name ) ), fling = true ) ) val cropProperties by _cropProperties private val _imageCurvesEditorState: MutableState = mutableStateOf(ImageCurvesEditorState.Default) val imageCurvesEditorState: ImageCurvesEditorState by _imageCurvesEditorState private val _exif: MutableState = mutableStateOf(null) val exif by _exif private val _uri: MutableState = mutableStateOf(Uri.EMPTY) val uri: Uri by _uri private val _internalBitmap: MutableState = mutableStateOf(null) val initialBitmap by _internalBitmap private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _imageInfo: MutableState = mutableStateOf(ImageInfo()) val imageInfo: ImageInfo by _imageInfo private val _showWarning: MutableState = mutableStateOf(false) val showWarning: Boolean by _showWarning private val _shouldShowPreview: MutableState = mutableStateOf(true) val shouldShowPreview by _shouldShowPreview private val _presetSelected: MutableState = mutableStateOf(Preset.None) val presetSelected by _presetSelected private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private val _drawMode: MutableState = mutableStateOf(DrawMode.Pen) val drawMode: DrawMode by _drawMode private val _drawPathMode: MutableState = mutableStateOf(DrawPathMode.Free) val drawPathMode: DrawPathMode by _drawPathMode private val _drawLineStyle: MutableState = mutableStateOf(DrawLineStyle.None) val drawLineStyle: DrawLineStyle by _drawLineStyle private val _helperGridParams = fileController.savable( scope = componentScope, initial = HelperGridParams() ) val helperGridParams: HelperGridParams by _helperGridParams init { componentScope.launch { val settingsState = settingsProvider.getSettingsState() _drawPathMode.update { DrawPathMode.fromOrdinal(settingsState.defaultDrawPathMode) } _imageInfo.update { it.copy(resizeType = settingsState.defaultResizeType) } } } private var job: Job? by smartJob { _isImageLoading.update { false } } private suspend fun checkBitmapAndUpdate(resetPreset: Boolean = false) { if (resetPreset) { _presetSelected.update { Preset.None } } _bitmap.value?.let { bmp -> val preview = updatePreview(bmp) _previewBitmap.update { null } _shouldShowPreview.update { imagePreviewCreator.canShow(preview) } if (shouldShowPreview) _previewBitmap.update { preview } } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmap( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } bitmap?.let { bitmap -> parseSaveResult( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, metadata = exif, originalUri = uri.toString(), sequenceNumber = null, data = imageCompressor.compressAndTransform( image = bitmap, imageInfo = imageInfo.copy( originalUri = uri.toString() ) ), presetInfo = presetSelected ), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ).onSuccess(::registerSave) ) } _isSaving.update { false } } } private suspend fun updatePreview( bitmap: Bitmap, ): Bitmap? = withContext(defaultDispatcher) { return@withContext imageInfo.run { _showWarning.update { width * height * 4L >= 10_000 * 10_000 * 3L } imagePreviewCreator.createPreview( image = bitmap, imageInfo = this, onGetByteCount = { sizeInBytes -> _imageInfo.update { it.copy(sizeInBytes = sizeInBytes) } } ) } } private fun setBitmapInfo(newInfo: ImageInfo) { if (imageInfo != newInfo) { _imageInfo.update { newInfo } debouncedImageCalculation { checkBitmapAndUpdate() } } } fun resetValues(newBitmapComes: Boolean = false) { _imageInfo.update { ImageInfo( width = _originalSize.value?.width ?: 0, height = _originalSize.value?.height ?: 0, imageFormat = it.imageFormat, originalUri = uri.toString() ) } if (newBitmapComes) { _bitmap.update { _internalBitmap.value } } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = true ) } } fun updateBitmapAfterEditing( bitmap: Bitmap?, saveOriginalSize: Boolean = false, ) { componentScope.launch { if (!saveOriginalSize) { val size = bitmap?.let { it.width to it.height } _originalSize.update { size?.run { IntegerSize(width = first, height = second) } } } _bitmap.update { imageScaler.scaleUntilCanShow(bitmap) } _imageInfo.update { it.copy( rotationDegrees = 0f ) } if (!saveOriginalSize) { _imageInfo.update { it.copy( width = bitmap?.width ?: 0, height = bitmap?.height ?: 0 ) } } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = true ) } registerChanges() } } fun rotateBitmapLeft() { _imageInfo.update { it.copy( rotationDegrees = it.rotationDegrees - 90f, height = it.width, width = it.height ) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } fun rotateBitmapRight() { _imageInfo.update { it.copy( rotationDegrees = it.rotationDegrees + 90f, height = it.width, width = it.height ) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } fun flipImage() { _imageInfo.update { it.copy(isFlipped = !it.isFlipped) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } fun updateWidth(width: Int) { if (imageInfo.width != width) { _imageInfo.update { it.copy(width = width) } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = true ) } registerChanges() } } fun updateHeight(height: Int) { if (imageInfo.height != height) { _imageInfo.update { it.copy(height = height) } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = true ) } registerChanges() } } fun setQuality(quality: Quality) { if (imageInfo.quality != quality) { _imageInfo.update { it.copy(quality = quality) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } } fun setImageFormat(imageFormat: ImageFormat) { if (imageInfo.imageFormat != imageFormat) { _imageInfo.update { it.copy(imageFormat = imageFormat) } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = _presetSelected.value == Preset.Telegram && imageFormat != ImageFormat.Png.Lossless ) } registerChanges() } } fun setResizeType(type: ResizeType) { if (imageInfo.resizeType != type) { _imageInfo.update { it.copy( resizeType = type.withOriginalSizeIfCrop(originalSize) ) } debouncedImageCalculation { checkBitmapAndUpdate( resetPreset = false ) } registerChanges() } } fun setUri(uri: Uri) { _uri.update { uri } decodeBitmapByUri(uri) } private fun decodeBitmapByUri(uri: Uri) { _isImageLoading.update { true } _imageInfo.update { it.copy(originalUri = uri.toString()) } imageGetter.getImageAsync( uri = uri.toString(), originalSize = true, onGetImage = ::setImageData, onFailure = { _isImageLoading.update { false } } ) } private fun setImageData(imageData: ImageData) { job = componentScope.launch { _isImageLoading.update { true } _exif.update { imageData.metadata } val bitmap = imageData.image val size = bitmap.width to bitmap.height _originalSize.update { size.run { IntegerSize(width = first, height = second) } } _bitmap.update { _internalBitmap.update { imageScaler.scaleUntilCanShow(bitmap) } } resetValues(true) _imageInfo.update { imageData.imageInfo.copy( width = size.first, height = size.second ) } checkBitmapAndUpdate( resetPreset = _presetSelected.value == Preset.Telegram && imageData.imageInfo.imageFormat != ImageFormat.Png.Lossless ) _isImageLoading.update { false } } } fun shareBitmap() { savingJob = trackProgress { _isSaving.update { true } bitmap?.let { image -> shareProvider.shareImage( image = image, imageInfo = imageInfo.copy(originalUri = uri.toString()), onComplete = AppToastHost::showConfetti ) } _isSaving.update { false } } } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.update { true } bitmap?.let { image -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = uri.toString()) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.update { false } } } fun canShow(): Boolean = bitmap?.let { imagePreviewCreator.canShow(it) } == true fun setPreset(preset: Preset) { componentScope.launch { if (preset is Preset.AspectRatio && preset.ratio != 1f) { _imageInfo.update { it.copy(rotationDegrees = 0f) } } setBitmapInfo( imageTransformer.applyPresetBy( image = bitmap, preset = preset, currentInfo = imageInfo.copy( originalUri = uri.toString() ) ) ) _presetSelected.update { preset } registerChanges() } } fun clearExif() { updateExif(_exif.value?.clearAllAttributes()) } private fun updateExif(metadata: Metadata?) { _exif.update { metadata } registerChanges() } fun removeExifTag(tag: MetadataTag) { updateExif(_exif.value?.clearAttribute(tag)) } fun updateExifByTag( tag: MetadataTag, value: String, ) { updateExif(_exif.value?.setAttribute(tag, value)) } fun setCropAspectRatio( domainAspectRatio: DomainAspectRatio, aspectRatio: AspectRatio, ) { _cropProperties.update { properties -> properties.copy( aspectRatio = aspectRatio.takeIf { domainAspectRatio != DomainAspectRatio.Original } ?: _bitmap.value?.let { AspectRatio(it.safeAspectRatio) } ?: aspectRatio, fixedAspectRatio = domainAspectRatio != DomainAspectRatio.Free ) } _selectedAspectRatio.update { domainAspectRatio } } fun setCropMask(cropOutlineProperty: CropOutlineProperty) { _cropProperties.value = _cropProperties.value.copy(cropOutlineProperty = cropOutlineProperty) } suspend fun loadImage(uri: Uri): Bitmap? = imageGetter.getImage(data = uri) fun getBackgroundRemover(): AutoBackgroundRemover = autoBackgroundRemover fun updateFilter( value: T, index: Int ) { val list = _filterList.value.toMutableList() runCatching { list[index] = list[index].copy(value) _filterList.update { list } }.onFailure { AppToastHost.showFailureToast(it) list[index] = list[index].newInstance() _filterList.update { list } } } fun updateOrder(value: List>) { _filterList.update { value } } fun addFilter(filter: UiFilter<*>) { _filterList.update { it + filter } } fun removeFilterAtIndex(index: Int) { _filterList.update { it.toMutableList().apply { removeAt(index) } } } fun clearFilterList() { _filterList.update { listOf() } } fun clearDrawing(canUndo: Boolean = false) { componentScope.launch { delay(500L) _drawLastPaths.update { if (canUndo) drawPaths else listOf() } _drawPaths.update { listOf() } _drawUndonePaths.update { listOf() } _drawMode.update { DrawMode.Pen } _drawPathMode.update { DrawPathMode.Free } } } fun undoDraw() { if (drawPaths.isEmpty() && drawLastPaths.isNotEmpty()) { _drawPaths.update { drawLastPaths } _drawLastPaths.update { listOf() } return } if (drawPaths.isEmpty()) return val lastPath = drawPaths.last() _drawPaths.update { it - lastPath } _drawUndonePaths.update { it + lastPath } } fun redoDraw() { if (drawUndonePaths.isEmpty()) return val lastPath = drawUndonePaths.last() _drawPaths.update { it + lastPath } _drawUndonePaths.update { it - lastPath } } fun addPathToDrawList(pathPaint: UiPathPaint) { _drawPaths.update { it + pathPaint } _drawUndonePaths.update { listOf() } } fun clearErasing(canUndo: Boolean = false) { componentScope.launch { delay(250L) _eraseLastPaths.update { if (canUndo) erasePaths else listOf() } _erasePaths.update { listOf() } _eraseUndonePaths.update { listOf() } _drawPathMode.update { DrawPathMode.Free } } } fun undoErase() { if (erasePaths.isEmpty() && eraseLastPaths.isNotEmpty()) { _erasePaths.update { eraseLastPaths } _eraseLastPaths.update { listOf() } return } if (erasePaths.isEmpty()) return val lastPath = erasePaths.last() _erasePaths.update { it - lastPath } _eraseUndonePaths.update { it + lastPath } } fun redoErase() { if (eraseUndonePaths.isEmpty()) return val lastPath = eraseUndonePaths.last() _erasePaths.update { it + lastPath } _eraseUndonePaths.update { it - lastPath } } fun addPathToEraseList(pathPaint: UiPathPaint) { _erasePaths.update { it + pathPaint } _eraseUndonePaths.update { listOf() } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.update { false } } fun setImageScaleMode(imageScaleMode: ImageScaleMode) { _imageInfo.update { it.copy(imageScaleMode = imageScaleMode) } debouncedImageCalculation { checkBitmapAndUpdate() } registerChanges() } suspend fun filter( bitmap: Bitmap, filters: List>, ): Bitmap? = imageTransformer.transform( image = bitmap, transformations = mapFilters(filters) ) fun mapFilters( filters: List>, ): List> = filters.map { filterProvider.filterToTransformation(it) } fun updateDrawMode(drawMode: DrawMode) { _drawMode.update { drawMode } } fun updateDrawPathMode(drawPathMode: DrawPathMode) { _drawPathMode.update { drawPathMode } } fun getFormatForFilenameSelection(): ImageFormat = imageInfo.imageFormat fun resetImageCurvesEditorState() { _imageCurvesEditorState.update { ImageCurvesEditorState.Default } } fun updateDrawLineStyle(style: DrawLineStyle) { _drawLineStyle.update { style } } fun updateHelperGridParams(params: HelperGridParams) { _helperGridParams.update { params } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUri: Uri?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): SingleEditComponent } } ================================================ FILE: feature/svg-maker/.gitignore ================================================ /build ================================================ FILE: feature/svg-maker/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.svg_maker" dependencies { implementation(libs.toolbox.svg) } ================================================ FILE: feature/svg-maker/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/data/AndroidSvgManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.feature.svg_maker.data import android.content.Context import android.graphics.Bitmap import com.t8rin.image.toolbox.svg.ImageTracerAndroid import com.t8rin.image.toolbox.svg.ImageTracerAndroid.SvgListener import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.saving.RandomStringGenerator import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.feature.svg_maker.domain.SvgManager import com.t8rin.imagetoolbox.feature.svg_maker.domain.SvgParams import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import java.io.File import javax.inject.Inject internal class AndroidSvgManager @Inject constructor( @ApplicationContext private val context: Context, private val randomStringGenerator: RandomStringGenerator, private val imageGetter: ImageGetter, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, SvgManager { override suspend fun convertToSvg( imageUris: List, params: SvgParams, onFailure: (Throwable) -> Unit, onProgress: suspend (originalUri: String, data: ByteArray) -> Unit ) = withContext(defaultDispatcher) { imageUris.forEach { uri -> runSuspendCatching { val folder = File(context.cacheDir, "svg").apply { mkdirs() } val file = File(folder, "${randomStringGenerator.generate(10)}.svg") withContext(ioDispatcher) { file.bufferedWriter().use { writer -> ImageTracerAndroid.imageToSVG( imageGetter.getImage( data = uri, size = if (params.isImageSampled) { IntegerSize(1000, 1000) } else null )!!, params.toOptions(), null, SvgTracer { writer.write(it) } ) } } onProgress(uri, file.readBytes()) }.onFailure(onFailure) } } private fun SvgTracer( onProgress: (String) -> Unit ): SvgListener = object : SvgListener { override fun onProgress( part: String? ): SvgListener = apply { onProgress(part ?: "") } override fun onProgress( part: Double ): SvgListener = apply { onProgress(part.toString()) } } private fun SvgParams.toOptions(): HashMap = HashMap().apply { put("numberofcolors", colorsCount.toFloat()) put("colorquantcycles", quantizationCyclesCount.toFloat()) put("colorsampling", if (isPaletteSampled) 1f else 0f) put("blurradius", blurRadius.toFloat()) put("blurdelta", blurDelta.toFloat()) put("pathomit", pathOmit.toFloat()) put("ltres", linesThreshold) put("qtres", quadraticThreshold) put("roundcoords", coordinatesRoundingAmount.toFloat()) put("mincolorratio", minColorRatio) put("scale", svgPathsScale) } } ================================================ FILE: feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/di/SvgMakerModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.svg_maker.di import com.t8rin.imagetoolbox.feature.svg_maker.data.AndroidSvgManager import com.t8rin.imagetoolbox.feature.svg_maker.domain.SvgManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface SvgMakerModule { @Singleton @Binds fun provideSvgManager( manager: AndroidSvgManager ): SvgManager } ================================================ FILE: feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.svg_maker.domain interface SvgManager { suspend fun convertToSvg( imageUris: List, params: SvgParams, onFailure: (Throwable) -> Unit, onProgress: suspend (originalUri: String, data: ByteArray) -> Unit ) } ================================================ FILE: feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/domain/SvgParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.svg_maker.domain data class SvgParams( val colorsCount: Int, val isPaletteSampled: Boolean, val quantizationCyclesCount: Int, val blurRadius: Int, val blurDelta: Int, val pathOmit: Int, val linesThreshold: Float, val quadraticThreshold: Float, val minColorRatio: Float, val coordinatesRoundingAmount: Int, val svgPathsScale: Float, // 0.01f, 100f val isImageSampled: Boolean ) { companion object { val Default by lazy { SvgParams( colorsCount = 16, isPaletteSampled = true, quantizationCyclesCount = 3, blurRadius = 0, blurDelta = 20, pathOmit = 8, linesThreshold = 1f, quadraticThreshold = 1f, minColorRatio = 0.02f, coordinatesRoundingAmount = 1, svgPathsScale = 1f, isImageSampled = true ) } val Detailed by lazy { Default.copy( pathOmit = 0, linesThreshold = 0.5f, quadraticThreshold = 0.5f, coordinatesRoundingAmount = 3, colorsCount = 64, quantizationCyclesCount = 1 ) } val Grayscale by lazy { Default.copy( isPaletteSampled = false, quantizationCyclesCount = 1, colorsCount = 7 ) } val presets by lazy { listOf(Default, Detailed, Grayscale) } } } ================================================ FILE: feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/SvgMakerContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.svg_maker.presentation import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.image.urisPreview import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.svg_maker.domain.SvgParams import com.t8rin.imagetoolbox.feature.svg_maker.presentation.components.SvgParamsSelector import com.t8rin.imagetoolbox.feature.svg_maker.presentation.screenLogic.SvgMakerComponent @Composable fun SvgMakerContent( component: SvgMakerComponent ) { var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val imagePicker = rememberImagePicker(onSuccess = component::setUris) AutoFilePicker( onAutoPick = imagePicker::pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) val addImagesImagePicker = rememberImagePicker(onSuccess = component::addUris) val isPortrait by isPortraitOrientationAsState() var showResetDialog by rememberSaveable { mutableStateOf(false) } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { Text( text = stringResource(R.string.images_to_svg), modifier = Modifier.marquee() ) }, topAppBarPersistentActions = { if (isPortrait) { TopAppBarEmoji() } }, onGoBack = onBack, actions = { ShareButton( onShare = component::performSharing, enabled = !component.isSaving && component.uris.isNotEmpty() ) EnhancedIconButton( enabled = component.params != SvgParams.Default, onClick = { showResetDialog = true } ) { Icon( imageVector = Icons.Rounded.ImageReset, contentDescription = stringResource(R.string.reset_image) ) } }, imagePreview = { UrisPreview( modifier = Modifier.urisPreview(), uris = component.uris, isPortrait = true, onRemoveUri = component::removeUri, onAddUris = addImagesImagePicker::pickImage ) }, showImagePreviewAsStickyHeader = false, noDataControls = { ImageNotPickedWidget(onPickImage = imagePicker::pickImage) }, controls = { SvgParamsSelector( value = component.params, onValueChange = component::updateParams ) }, buttons = { actions -> val save: (oneTimeSaveLocationUri: String?) -> Unit = { component.save( oneTimeSaveLocationUri = it ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isEmpty(), onSecondaryButtonClick = imagePicker::pickImage, isPrimaryButtonVisible = component.uris.isNotEmpty(), onPrimaryButtonClick = { save(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = save ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, canShowScreenData = component.uris.isNotEmpty() ) ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, title = stringResource(R.string.reset_properties), text = stringResource(R.string.reset_properties_sub), onReset = { component.updateParams(SvgParams.Default) } ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/components/SvgParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.svg_maker.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Calculate import androidx.compose.material.icons.outlined.ChangeHistory import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.outlined.FormatColorFill import androidx.compose.material.icons.outlined.LinearScale import androidx.compose.material.icons.outlined.RepeatOne import androidx.compose.material.icons.outlined.Upcoming import androidx.compose.material.icons.rounded.BlurCircular import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.Eyedropper import com.t8rin.imagetoolbox.core.resources.icons.FreeDraw import com.t8rin.imagetoolbox.core.resources.icons.Line import com.t8rin.imagetoolbox.core.resources.icons.PhotoSizeSelectSmall import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.enhanced.enhancedFlingBehavior import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.fadingEdges import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.feature.svg_maker.domain.SvgParams import kotlin.math.pow import kotlin.math.roundToInt @Composable fun SvgParamsSelector( value: SvgParams, onValueChange: (SvgParams) -> Unit ) { Column( horizontalAlignment = Alignment.CenterHorizontally ) { Column( modifier = Modifier .container(shape = ShapeDefaults.extraLarge), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(Modifier.height(8.dp)) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( text = stringResource(R.string.presets), textAlign = TextAlign.Center, fontWeight = FontWeight.Medium ) } Spacer(Modifier.height(12.dp)) Box( contentAlignment = Alignment.Center, modifier = Modifier.padding(bottom = 8.dp) ) { val listState = rememberLazyListState() LazyRow( state = listState, modifier = Modifier .fadingEdges(listState) .padding(vertical = 1.dp), horizontalArrangement = Arrangement.spacedBy( 8.dp, Alignment.CenterHorizontally ), contentPadding = PaddingValues(horizontal = 8.dp), flingBehavior = enhancedFlingBehavior() ) { items(SvgParams.presets) { val selected = value == it EnhancedChip( selected = selected, onClick = { onValueChange(it) }, selectedColor = MaterialTheme.colorScheme.primary, shape = MaterialTheme.shapes.medium ) { AutoSizeText(it.name) } } } } } Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.downscale_image), subtitle = stringResource(id = R.string.downscale_image_sub), checked = value.isImageSampled, onClick = { onValueChange( value.copy(isImageSampled = it) ) }, startIcon = Icons.Outlined.PhotoSizeSelectSmall, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.extraLarge ) AnimatedVisibility( visible = !value.isImageSampled ) { Box( modifier = Modifier .padding(top = 8.dp) .fillMaxWidth() .container( shape = ShapeDefaults.large, borderColor = MaterialTheme.colorScheme.onErrorContainer.copy( 0.4f ), color = MaterialTheme.colorScheme.errorContainer.copy( alpha = 0.7f ) ), contentAlignment = Alignment.Center ) { Text( text = stringResource(R.string.svg_warning), fontSize = 12.sp, modifier = Modifier.padding(8.dp), textAlign = TextAlign.Center, fontWeight = FontWeight.SemiBold, lineHeight = 14.sp, color = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.5f) ) } } Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.colorsCount, title = stringResource(R.string.max_colors_count), icon = Icons.Outlined.ColorLens, valueRange = 2f..64f, steps = 61, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( colorsCount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.minColorRatio, title = stringResource(R.string.min_color_ratio), icon = Icons.Outlined.Eyedropper, valueRange = 0f..0.1f, internalStateTransformation = { it.roundTo(3) }, onValueChange = { onValueChange( value.copy( minColorRatio = it ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.quantizationCyclesCount, icon = Icons.Outlined.RepeatOne, title = stringResource(id = R.string.repeat_count), valueRange = 1f..10f, steps = 8, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( quantizationCyclesCount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.use_sampled_palette), subtitle = stringResource(id = R.string.use_sampled_palette_sub), checked = value.isPaletteSampled, onClick = { onValueChange( value.copy(isPaletteSampled = it) ) }, startIcon = Icons.Outlined.FormatColorFill, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.svgPathsScale, icon = Icons.Outlined.LinearScale, title = stringResource(R.string.path_scale), valueRange = 0.01f..100f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.copy( svgPathsScale = it ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.blurRadius, title = stringResource(R.string.blur_radius), icon = Icons.Rounded.BlurCircular, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( blurRadius = it.roundToInt() ) ) }, containerColor = Color.Unspecified, valueRange = 0f..5f, steps = 4, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.blurDelta, icon = Icons.Outlined.ChangeHistory, title = stringResource(id = R.string.blur_size), valueRange = 0f..255f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( blurDelta = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.pathOmit, icon = Icons.Outlined.Upcoming, title = stringResource(id = R.string.path_omit), valueRange = 0f..64f, steps = 63, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( pathOmit = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.linesThreshold, icon = Icons.Rounded.Line, title = stringResource(R.string.lines_threshold), valueRange = 0f..10f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.copy( linesThreshold = it ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.quadraticThreshold, icon = Icons.Rounded.FreeDraw, title = stringResource(R.string.quadratic_threshold), valueRange = 0f..10f, internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( value.copy( quadraticThreshold = it ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.coordinatesRoundingAmount, icon = Icons.Outlined.Calculate, title = stringResource(R.string.coordinates_rounding_tolerance), valueRange = 0f..8f, steps = 7, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( coordinatesRoundingAmount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) } } private fun Float.roundTo( digits: Int? = 2 ) = digits?.let { (this * 10f.pow(digits)).roundToInt() / (10f.pow(digits)) } ?: this private val SvgParams.name: String @Composable get() = when (this) { SvgParams.Default -> stringResource(R.string.defaultt) SvgParams.Detailed -> stringResource(R.string.detailed) SvgParams.Grayscale -> stringResource(R.string.gray_scale) else -> "" } ================================================ FILE: feature/svg-maker/src/main/java/com/t8rin/imagetoolbox/feature/svg_maker/presentation/screenLogic/SvgMakerComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.imagetoolbox.feature.svg_maker.presentation.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.FileSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.SaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.svg_maker.domain.SvgManager import com.t8rin.imagetoolbox.feature.svg_maker.domain.SvgParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class SvgMakerComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, private val svgManager: SvgManager, private val shareProvider: ShareProvider, private val fileController: FileController, private val filenameCreator: FilenameCreator, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) } } private val _uris = mutableStateOf>(emptyList()) val uris by _uris private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _params = mutableStateOf(SvgParams.Default) val params by _params private var savingJob: Job? by smartJob { _isSaving.update { false } } fun save( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { val results = mutableListOf() _isSaving.value = true _done.update { 0 } _left.value = uris.size svgManager.convertToSvg( imageUris = uris.map { it.toString() }, params = params, onFailure = { results.add( SaveResult.Error.Exception(it) ) } ) { uri, svgBytes -> results.add( fileController.save( saveTarget = SvgSaveTarget(uri, svgBytes), keepOriginalMetadata = true, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } _isSaving.value = false parseSaveResults(results.onSuccess(::registerSave)) } } fun performSharing() { savingJob = trackProgress { _done.update { 0 } _left.update { uris.size } _isSaving.value = true val results = mutableListOf() svgManager.convertToSvg( imageUris = uris.map { it.toString() }, params = params, onFailure = AppToastHost::showFailureToast ) { uri, jxlBytes -> results.add( shareProvider.cacheByteArray( byteArray = jxlBytes, filename = filename(uri) ) ) _done.update { it + 1 } updateProgress( done = done, total = left ) } shareProvider.shareUris(results.filterNotNull()) _isSaving.value = false AppToastHost.showConfetti() } } private fun filename( uri: String ): String = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = ImageInfo( originalUri = uri ), originalUri = uri, sequenceNumber = done + 1, metadata = null, data = ByteArray(0), extension = "svg" ), forceNotAddSizeInFilename = true ) private fun SvgSaveTarget( uri: String, svgBytes: ByteArray ): SaveTarget = FileSaveTarget( originalUri = uri, filename = filename(uri), data = svgBytes, mimeType = MimeType.Svg, extension = "svg" ) fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun setUris(newUris: List) { _uris.update { newUris.distinct() } } fun removeUri(uri: Uri) { _uris.update { it - uri } registerChanges() } fun addUris(list: List) { setUris(uris + list) registerChanges() } fun updateParams(newParams: SvgParams) { _params.update { newParams } registerChanges() } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, ): SvgMakerComponent } } ================================================ FILE: feature/wallpapers-export/.gitignore ================================================ /build ================================================ FILE: feature/wallpapers-export/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.wallpapers_export" ================================================ FILE: feature/wallpapers-export/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/data/AndroidWallpapersProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.data import android.Manifest import android.annotation.SuppressLint import android.app.WallpaperManager import android.app.WallpaperManager.FLAG_LOCK import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.Drawable import android.os.Build import android.os.Environment import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.permission.PermissionUtils.hasPermissionAllowed import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.WallpapersProvider import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.Permission import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.Wallpaper import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.WallpapersResult import com.t8rin.logger.makeLog import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidWallpapersProvider @Inject constructor( @ApplicationContext private val context: Context, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder ) : WallpapersProvider, DispatchersHolder by dispatchersHolder { private val wallpaperManager = WallpaperManager.getInstance(context) override suspend fun getWallpapers(): WallpapersResult = withContext(defaultDispatcher) { val missingPermissions = mutableListOf() if (!context.hasPermissionAllowed(Manifest.permission.READ_MEDIA_IMAGES) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { missingPermissions.add(Permission.ReadMediaImages) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { missingPermissions.add(Permission.ManageExternalStorage) } if (missingPermissions.isNotEmpty()) { return@withContext WallpapersResult.Failed.NoPermissions(missingPermissions) } val wallpapers = loadWallpapers() return@withContext WallpapersResult.Success( wallpapers.mapIndexed { index, drawable -> val imageUri = drawable?.let { shareProvider.cacheImage( image = drawable.toBitmap(), imageInfo = ImageInfo( width = drawable.intrinsicWidth, height = drawable.intrinsicHeight, imageFormat = ImageFormat.Png.Lossless ) ) } Wallpaper( imageUri = imageUri, nameRes = nameByIndex(index), resolution = drawable?.let { IntegerSize( width = it.intrinsicWidth, height = it.intrinsicHeight ) } ?: IntegerSize.Zero ) } ) } @SuppressLint("MissingPermission") private fun loadWallpapers(): List { val home = safe { wallpaperManager.drawable } val builtIn = safe { wallpaperManager.getBuiltInDrawable(30000, 30000, false, 0.5f, 0.5f) } val lock = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { safe { wallpaperManager.getWallpaperFile(FLAG_LOCK)?.use { BitmapFactory.decodeFileDescriptor(it.fileDescriptor) .toDrawable(context.resources) } } ?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { safe { wallpaperManager.getDrawable(FLAG_LOCK) } ?: safe { wallpaperManager.getBuiltInDrawable(FLAG_LOCK) } } else { safe { wallpaperManager.getBuiltInDrawable(FLAG_LOCK) } } } else { null } return listOf(home, lock, builtIn) } private inline fun safe(action: () -> T): T? = runCatching { action() } .onFailure { it.makeLog("AndroidWallpapersProvider") }.getOrNull() private fun nameByIndex(index: Int): Int = when (index) { 0 -> R.string.home_screen 1 -> R.string.lock_screen else -> R.string.built_in } } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/di/WallpapersExportModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.di import com.t8rin.imagetoolbox.feature.wallpapers_export.data.AndroidWallpapersProvider import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.WallpapersProvider import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface WallpapersExportModule { @Binds @Singleton fun provider( impl: AndroidWallpapersProvider ): WallpapersProvider } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/WallpapersProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.domain import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.WallpapersResult interface WallpapersProvider { suspend fun getWallpapers(): WallpapersResult } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Permission.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model sealed interface Permission { data object ManageExternalStorage : Permission data object ReadMediaImages : Permission } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/Wallpaper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model import com.t8rin.imagetoolbox.core.domain.model.IntegerSize data class Wallpaper( val imageUri: String?, val nameRes: Int, val resolution: IntegerSize ) ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/domain/model/WallpapersResult.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model sealed interface WallpapersResult { data object Loading : WallpapersResult data class Success( val wallpapers: List ) : WallpapersResult sealed interface Failed : WallpapersResult { data class NoPermissions(val missingPermissions: List) : Failed } } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/WallpapersExportContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.presentation import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.rememberCurrentLifecycleEvent import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.other.FeatureNotAvailableContent import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.core.utils.appContext import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.WallpapersResult import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.components.WallpapersActionButtons import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.components.WallpapersControls import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.components.WallpapersPreview import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.screenLogic.WallpapersExportComponent @Composable fun WallpapersExportContent( component: WallpapersExportComponent ) { if (appContext.isInstalledFromPlayStore()) { FeatureNotAvailableContent( title = { Text( text = stringResource(R.string.wallpapers_export), modifier = Modifier.marquee() ) }, onGoBack = component.onGoBack ) return } val isPortrait by isPortraitOrientationAsState() val lifecycleEvent = rememberCurrentLifecycleEvent() LaunchedEffect(lifecycleEvent) { component.loadWallpapers() } AutoContentBasedColors(component.wallpapers.firstOrNull()?.imageUri) AdaptiveLayoutScreen( shouldDisableBackHandler = true, title = { TopAppBarTitle( title = stringResource(R.string.wallpapers_export), input = component.wallpapers.takeIf { it.isNotEmpty() }, isLoading = component.isImageLoading, size = null ) }, onGoBack = component.onGoBack, actions = { ShareButton( enabled = component.selectedImages.isNotEmpty(), onShare = component::performSharing, onCopy = if (component.wallpapers.size == 1) { { component.cacheImages { it.firstOrNull()?.let(Clipboard::copy) } } } else null ) }, topAppBarPersistentActions = { if (isPortrait) { TopAppBarEmoji() } }, imagePreview = { WallpapersPreview(component) }, controls = { WallpapersControls(component) }, buttons = { actions -> WallpapersActionButtons( component = component, actions = actions ) }, placeImagePreview = component.wallpapersState is WallpapersResult.Success, showImagePreviewAsStickyHeader = false, canShowScreenData = true ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving, canCancel = component.isSaving ) } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersActionButtons.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.components import android.net.Uri import androidx.compose.foundation.layout.RowScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Refresh import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageEdit import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.WallpapersResult import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.screenLogic.WallpapersExportComponent @Composable internal fun WallpapersActionButtons( component: WallpapersExportComponent, actions: @Composable RowScope.() -> Unit ) { if (component.wallpapersState is WallpapersResult.Loading) return val isPortrait by isPortraitOrientationAsState() val saveBitmap: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var editSheetData by remember { mutableStateOf(listOf()) } val noData = component.wallpapers.isEmpty() val canRefresh = if (noData) true else component.selectedImages.isNotEmpty() BottomButtonsBlock( isNoData = noData, isPrimaryButtonVisible = !noData, isPrimaryButtonEnabled = component.selectedImages.isNotEmpty(), isSecondaryButtonVisible = canRefresh, secondaryButtonIcon = if (noData) Icons.Rounded.Refresh else Icons.Outlined.ImageEdit, secondaryButtonText = if (noData) stringResource(R.string.refresh) else stringResource(R.string.edit), showNullDataButtonAsContainer = true, isScreenHaveNoDataContent = true, onSecondaryButtonClick = { if (canRefresh && noData) { component.loadWallpapers() } else { component.cacheImages { editSheetData = it } } }, onPrimaryButtonClick = { saveBitmap(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmap, formatForFilenameSelection = component.getFormatForFilenameSelection() ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.components import android.Manifest import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowCircleRight import androidx.compose.material.icons.rounded.ImageSearch import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.utils.tryAll import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.FolderOpened import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.appSettingsIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.manageAllFilesIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.manageAppAllFilesIntent import com.t8rin.imagetoolbox.core.ui.utils.helper.ContextUtils.requestPermissions import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalComponentActivity import com.t8rin.imagetoolbox.core.ui.utils.provider.LocalScreenSize import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.Permission import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.WallpapersResult import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.screenLogic.WallpapersExportComponent @Composable fun WallpapersControls(component: WallpapersExportComponent) { val isPortrait by isPortraitOrientationAsState() AnimatedContent( targetState = component.wallpapersState, contentKey = { it::class.simpleName }, modifier = Modifier.fillMaxWidth() ) { state -> when (state) { is WallpapersResult.Success -> { Column { if (isPortrait) { Spacer(Modifier.height(16.dp)) } QualitySelector( imageFormat = component.imageFormat, quality = component.quality, onQualityChange = component::setQuality ) if (component.imageFormat.canChangeCompressionValue) { Spacer(Modifier.height(8.dp)) } ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, quality = component.quality, ) } } is WallpapersResult.Failed.NoPermissions -> { Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { if (isPortrait) { Spacer(Modifier.height(12.dp)) } state.missingPermissions.forEachIndexed { index, permission -> PermissionItem( permission = permission, shape = ShapeDefaults.byIndex( index = index, size = state.missingPermissions.size ) ) } } } is WallpapersResult.Loading -> { val screenHeight = LocalScreenSize.current.height Box( modifier = Modifier .fillMaxWidth() .padding(vertical = screenHeight * 0.3f), contentAlignment = Alignment.Center ) { EnhancedLoadingIndicator() } } } } } @Composable private fun PermissionItem( permission: Permission, shape: Shape ) { val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult(), onResult = {} ) val context = LocalComponentActivity.current PreferenceItem( title = permission.title, subtitle = permission.subtitle, modifier = Modifier.fillMaxWidth(), onClick = { when (permission) { Permission.ManageExternalStorage -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { tryAll( { launcher.launch(context.manageAppAllFilesIntent()) }, { launcher.launch(manageAllFilesIntent()) }, { launcher.launch(context.appSettingsIntent()) } ) } } Permission.ReadMediaImages -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { context.requestPermissions(listOf(Manifest.permission.READ_MEDIA_IMAGES)) } } } }, shape = shape, containerColor = MaterialTheme.colorScheme.errorContainer, endIcon = Icons.Rounded.ArrowCircleRight, startIcon = permission.icon ) } private val Permission.icon: ImageVector get() = when (this) { Permission.ManageExternalStorage -> Icons.Rounded.FolderOpened Permission.ReadMediaImages -> Icons.Rounded.ImageSearch } private val Permission.subtitle: String @Composable get() = when (this) { Permission.ManageExternalStorage -> stringResource(R.string.allow_access_to_all_files_for_wp) Permission.ReadMediaImages -> stringResource(R.string.allow_read_media_images_for_wp) } private val Permission.title: String get() = when (this) { Permission.ManageExternalStorage -> "MANAGE_EXTERNAL_STORAGE" Permission.ReadMediaImages -> "READ_MEDIA_IMAGES" } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/components/WallpapersPreview.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import com.t8rin.imagetoolbox.core.resources.icons.BrokenImageAlt import com.t8rin.imagetoolbox.core.ui.theme.White import com.t8rin.imagetoolbox.core.ui.theme.mixedContainer import com.t8rin.imagetoolbox.core.ui.theme.onMixedContainer import com.t8rin.imagetoolbox.core.ui.theme.takeColorFromScheme import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.buttons.MediaCheckBox import com.t8rin.imagetoolbox.core.ui.widget.enhanced.hapticsClickable import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.Wallpaper import com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.screenLogic.WallpapersExportComponent @Composable internal fun WallpapersPreview(component: WallpapersExportComponent) { val wallpapers = component.wallpapers val isPortrait by isPortraitOrientationAsState() AnimatedVisibility( visible = wallpapers.isNotEmpty(), modifier = Modifier.fillMaxWidth() ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically, modifier = Modifier .then( if (isPortrait) Modifier.padding(top = 20.dp) else Modifier ) .fillMaxWidth() .height(IntrinsicSize.Max) ) { wallpapers.forEachIndexed { index, wallpaper -> val isSelected by remember(index, component.selectedImages) { derivedStateOf { index in component.selectedImages } } WallpaperItem( wallpaper = wallpaper, onClick = { component.toggleSelection(index) }, isSelected = isSelected, shape = ShapeDefaults.byIndex( index = index, size = wallpapers.size, vertical = false ) ) } } } } @Composable private fun RowScope.WallpaperItem( wallpaper: Wallpaper, onClick: () -> Unit, isSelected: Boolean, shape: Shape ) { Box( modifier = Modifier .weight(1f) .container( shape = shape, resultPadding = 0.dp ) ) { Column( horizontalAlignment = Alignment.CenterHorizontally ) { if (wallpaper.imageUri.isNullOrBlank()) { Box( modifier = Modifier .weight(1f) .fillMaxWidth() .background(MaterialTheme.colorScheme.errorContainer), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Rounded.BrokenImageAlt, contentDescription = null, tint = MaterialTheme.colorScheme.onErrorContainer, modifier = Modifier .fillMaxWidth(0.5f) .padding(vertical = 16.dp) ) } } else { Box( modifier = Modifier .weight(1f) .fillMaxWidth() ) { val padding by animateDpAsState( if (isSelected) 8.dp else 0.dp ) val borderColor = takeColorFromScheme { if (isSelected) primary else Color.Transparent } AsyncImage( model = wallpaper.imageUri, contentDescription = null, modifier = Modifier .fillMaxSize() .padding(padding) .border( width = 2.dp, color = borderColor, shape = ShapeDefaults.small ) .clip(ShapeDefaults.small) .hapticsClickable(onClick = onClick), filterQuality = FilterQuality.None, contentScale = ContentScale.Crop ) Box( modifier = Modifier .fillMaxWidth() .padding(4.dp) ) { MediaCheckBox( isChecked = isSelected, uncheckedColor = White.copy(0.8f), checkedColor = MaterialTheme.colorScheme.primary, checkedIcon = Icons.Filled.CheckCircle, modifier = Modifier .clip(ShapeDefaults.circle) .background( animateColorAsState( if (isSelected) MaterialTheme.colorScheme.surfaceContainer else Color.Transparent ).value ) ) } } } Spacer(Modifier.height(4.dp)) AutoSizeText( key = { wallpaper.imageUri }, text = stringResource(wallpaper.nameRes), textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onMixedContainer, modifier = Modifier .padding(horizontal = 4.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.mixedContainer ) .padding(horizontal = 4.dp, vertical = 2.dp), style = TextStyle(fontSize = 12.sp, lineHeight = 13.sp) ) Spacer(Modifier.height(4.dp)) if (!wallpaper.imageUri.isNullOrBlank()) { AutoSizeText( key = { wallpaper.imageUri }, text = "${wallpaper.resolution.width}x${wallpaper.resolution.height}", textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onSecondaryContainer, modifier = Modifier .padding(horizontal = 4.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.secondaryContainer ) .padding(horizontal = 4.dp, vertical = 2.dp), style = TextStyle(fontSize = 11.sp, lineHeight = 12.sp) ) Spacer(Modifier.height(4.dp)) } Spacer(Modifier.height(4.dp)) } } } ================================================ FILE: feature/wallpapers-export/src/main/java/com/t8rin/imagetoolbox/feature/wallpapers_export/presentation/screenLogic/WallpapersExportComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.wallpapers_export.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.toggle import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.WallpapersProvider import com.t8rin.imagetoolbox.feature.wallpapers_export.domain.model.WallpapersResult import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class WallpapersExportComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageGetter: ImageGetter, private val shareProvider: ImageShareProvider, private val imageCompressor: ImageCompressor, private val wallpapersProvider: WallpapersProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { private val _imageFormat = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat by _imageFormat private val _quality = mutableStateOf(Quality.Base()) val quality by _quality private val _wallpapersState: MutableState = mutableStateOf(WallpapersResult.Loading) val wallpapersState: WallpapersResult by _wallpapersState private var switchToLoading = true val wallpapers get() = (wallpapersState as? WallpapersResult.Success)?.wallpapers.orEmpty() private val _selectedImages: MutableState> = mutableStateOf(emptyList()) val selectedImages: List by _selectedImages private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private var savingJob: Job? by smartJob { _isSaving.update { false } } private var wallpapersJob: Job? by smartJob() fun loadWallpapers() { wallpapersJob = componentScope.launch { if (switchToLoading) _wallpapersState.value = WallpapersResult.Loading _wallpapersState.value = wallpapersProvider.getWallpapers().also { result -> val success = result is WallpapersResult.Success if (!success) { _selectedImages.update { emptyList() } } if (switchToLoading && success) { _selectedImages.update { result.wallpapers.mapIndexedNotNull { index, wallpaper -> index.takeIf { wallpaper.imageUri != null } } } } switchToLoading = !success } } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.update { true } val results = mutableListOf() val uris = wallpapers.mapIndexedNotNull { index, wallpaper -> wallpaper.imageUri?.takeIf { index in selectedImages } } _done.value = 0 _left.value = uris.size uris.forEach { url -> imageGetter.getImage(data = url)?.let { bitmap -> fileController.save( saveTarget = ImageSaveTarget( imageInfo = ImageInfo( width = bitmap.width, height = bitmap.height, imageFormat = imageFormat, quality = quality ), originalUri = "_", sequenceNumber = null, data = imageCompressor.compress( image = bitmap, imageFormat = imageFormat, quality = quality ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) }?.let(results::add) ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value++ updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.update { false } } } fun performSharing() { cacheImages { uris -> componentScope.launch { shareProvider.shareUris(uris.map { it.toString() }) AppToastHost.showConfetti() } } } fun cacheImages( onComplete: (List) -> Unit ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true val uris = wallpapers.mapIndexedNotNull { index, wallpaper -> wallpaper.imageUri?.takeIf { index in selectedImages } } _done.value = 0 _left.value = uris.size onComplete( uris.mapNotNull { val image = imageGetter.getImage(data = it) ?: return@mapNotNull null shareProvider.cacheImage( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = imageFormat, quality = quality ) )?.toUri().also { _done.value++ updateProgress( done = done, total = left ) } } ) _isSaving.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun getFormatForFilenameSelection(): ImageFormat = imageFormat fun toggleSelection(position: Int) { _selectedImages.update { it.toggle(position) } } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } } fun setQuality(quality: Quality) { _quality.update { quality } } @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): WallpapersExportComponent } } ================================================ FILE: feature/watermarking/.gitignore ================================================ /build ================================================ FILE: feature/watermarking/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.watermarking" dependencies { implementation(projects.feature.compare) implementation(libs.toolbox.androidwm) } ================================================ FILE: feature/watermarking/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/data/AndroidWatermarkApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnnecessaryVariable") package com.t8rin.imagetoolbox.feature.watermarking.data import android.content.Context import android.graphics.Bitmap import android.graphics.Paint import android.graphics.PorterDuffXfermode import android.os.Build import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.applyCanvas import coil3.transform.RoundedCornersTransformation import com.t8rin.imagetoolbox.core.data.image.utils.drawBitmap import com.t8rin.imagetoolbox.core.data.image.utils.toAndroidBlendMode import com.t8rin.imagetoolbox.core.data.image.utils.toPorterDuffMode import com.t8rin.imagetoolbox.core.data.utils.asDomain import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageTransformer import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.utils.toTypeface import com.t8rin.imagetoolbox.feature.watermarking.domain.DigitalParams import com.t8rin.imagetoolbox.feature.watermarking.domain.HiddenWatermark import com.t8rin.imagetoolbox.feature.watermarking.domain.TextParams import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkApplier import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkingType import com.watermark.androidwm.WatermarkBuilder import com.watermark.androidwm.WatermarkDetector import com.watermark.androidwm.bean.WatermarkImage import com.watermark.androidwm.bean.WatermarkText import com.watermark.androidwm.listener.BuildFinishListener import com.watermark.androidwm.listener.DetectFinishListener import com.watermark.androidwm.task.DetectionReturnValue import com.watermark.androidwm.utils.BitmapUtils import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.coroutines.resume import kotlin.math.roundToInt internal class AndroidWatermarkApplier @Inject constructor( @ApplicationContext private val context: Context, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val imageTransformer: ImageTransformer, dispatchersHolder: DispatchersHolder, ) : DispatchersHolder by dispatchersHolder, WatermarkApplier { override suspend fun applyWatermark( image: Bitmap, originalSize: Boolean, params: WatermarkParams ): Bitmap? = withContext(defaultDispatcher) { when (val type = params.watermarkingType) { is WatermarkingType.Text -> { WatermarkBuilder .create(context, image, !originalSize) .loadWatermarkText( WatermarkText(type.text) .setPositionX(params.positionX.toDouble()) .setPositionY(params.positionY.toDouble()) .setRotation(params.rotation.toDouble()) .setTextAlpha( (params.alpha * 255).roundToInt() ) .setTextSize( type.params.size.toDouble() ) .setBackgroundColor(type.params.backgroundColor) .setTextColor(type.params.color) .apply { type.params.font.toTypeface()?.let(::setTextTypeface) } .setTextStyle( Paint.Style.FILL ) ) .setTileMode(params.isRepeated) .apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { setBlendMode( params.overlayMode.toAndroidBlendMode() ) } else { setPorterDuffMode( params.overlayMode.toPorterDuffMode() ) } } .generateImage(type.digitalParams) } is WatermarkingType.Image -> { imageGetter.getImage( data = type.imageData, size = IntegerSize( (image.width * type.size).toInt(), (image.height * type.size).toInt() ) )?.let { watermarkSource -> WatermarkBuilder .create(context, image, !originalSize) .loadWatermarkImage( WatermarkImage(watermarkSource) .setPositionX(params.positionX.toDouble()) .setPositionY(params.positionY.toDouble()) .setRotation(params.rotation.toDouble()) .setImageAlpha( (params.alpha * 255).roundToInt() ) .setSize(type.size.toDouble()) ) .setTileMode(params.isRepeated) .apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { setBlendMode( params.overlayMode.toAndroidBlendMode() ) } else { setPorterDuffMode( params.overlayMode.toPorterDuffMode() ) } } .generateImage(type.digitalParams) } } is WatermarkingType.Stamp.Text -> { drawStamp( image = image, alpha = params.alpha, overlayMode = params.overlayMode, position = type.position, params = type.params, text = type.text, padding = type.padding ) } is WatermarkingType.Stamp.Time -> { drawStamp( image = image, alpha = params.alpha, overlayMode = params.overlayMode, position = type.position, params = type.params, text = timestamp(type.format), padding = type.padding ) } } } @OptIn(InternalCoroutinesApi::class) override suspend fun checkHiddenWatermark( image: Bitmap ): HiddenWatermark? = runSuspendCatching { suspendCancellableCoroutine { continuation -> WatermarkDetector .create(image, true) .detect( object : DetectFinishListener { override fun onSuccess( returnValue: DetectionReturnValue ) = continuation.resume( returnValue.watermarkBitmap ?.let(HiddenWatermark::Image) ?: returnValue.watermarkString?.takeIf { it.isNotEmpty() } ?.let(HiddenWatermark::Text) ) override fun onFailure(message: String?) = continuation.resume(null) } ) } }.getOrNull() @OptIn(InternalCoroutinesApi::class) private suspend fun WatermarkBuilder.generateImage( params: DigitalParams ): Bitmap? = runSuspendCatching { if (params.isInvisible) { suspendCancellableCoroutine { continuation -> setInvisibleWMListener( params.isLSB, object : BuildFinishListener { override fun onSuccess(image: Bitmap) = continuation.resume(image) override fun onFailure(reason: String) = continuation.resume(null) } ) } } else { watermark?.outputImage } }.getOrNull() private suspend fun drawStamp( image: Bitmap, alpha: Float, overlayMode: BlendingMode, position: Position, padding: Float, params: TextParams, text: String, ): Bitmap = coroutineScope { image.copy(Bitmap.Config.ARGB_8888, true).applyCanvas { val watermark = WatermarkText(text) .setTextAlpha( (alpha * 255).roundToInt() ) .setTextSize( params.size.toDouble() ) .setBackgroundColor(params.backgroundColor) .setTextColor(params.color) .apply { params.font.toTypeface()?.let(::setTextTypeface) } .setTextStyle( Paint.Style.FILL ) val verticalPadding = padding - (6f * padding / 20f) val horizontalPadding = padding val processedText = BitmapUtils.textAsBitmap(context, watermark, image) val scaled = imageScaler.scaleImage( image = processedText, width = (processedText.width - 2 * horizontalPadding).roundToInt(), height = (processedText.height - 2 * verticalPadding).roundToInt(), resizeType = ResizeType.Flexible ) val textBitmap = if (params.backgroundColor != Color.Transparent.toArgb()) { imageTransformer.transform( image = scaled, transformations = listOf( RoundedCornersTransformation(12f).asDomain() ) ) ?: scaled } else { scaled } drawBitmap( bitmap = textBitmap, position = position, paint = Paint().apply { if (Build.VERSION.SDK_INT >= 29) { blendMode = overlayMode.toAndroidBlendMode() } else { xfermode = PorterDuffXfermode(overlayMode.toPorterDuffMode()) } }, verticalPadding = verticalPadding, horizontalPadding = horizontalPadding ) } } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/di/WatermarkingModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.watermarking.data.AndroidWatermarkApplier import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkApplier import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface WatermarkingModule { @Singleton @Binds fun provideWatermarkApplier( applier: AndroidWatermarkApplier ): WatermarkApplier } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/HiddenWatermark.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.domain sealed interface HiddenWatermark { data class Image(val image: Any) : HiddenWatermark data class Text(val text: String) : HiddenWatermark } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkApplier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.domain interface WatermarkApplier { suspend fun applyWatermark( image: I, originalSize: Boolean, params: WatermarkParams ): I? suspend fun checkHiddenWatermark( image: I ): HiddenWatermark? } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/domain/WatermarkParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.domain import com.t8rin.imagetoolbox.core.domain.image.model.BlendingMode import com.t8rin.imagetoolbox.core.domain.model.Position import com.t8rin.imagetoolbox.core.settings.domain.model.FontType data class WatermarkParams( val positionX: Float, val positionY: Float, val rotation: Int, val alpha: Float, val isRepeated: Boolean, val overlayMode: BlendingMode, val watermarkingType: WatermarkingType ) { companion object { val Default by lazy { WatermarkParams( positionX = 0f, positionY = 0f, rotation = 45, alpha = 0.5f, isRepeated = true, overlayMode = BlendingMode.SrcOver, watermarkingType = WatermarkingType.Text.Default ) } } } sealed interface WatermarkingType { data class Text( val params: TextParams, val text: String, val digitalParams: DigitalParams ) : WatermarkingType { companion object { val Default by lazy { Text( params = TextParams.Default, text = "Watermark", digitalParams = DigitalParams.Default ) } } } data class Image( val imageData: Any, val size: Float, val digitalParams: DigitalParams ) : WatermarkingType { companion object { val Default by lazy { Image( size = 0.1f, imageData = "file:///android_asset/svg/emotions/aasparkles.svg", digitalParams = DigitalParams.Default ) } } } sealed interface Stamp : WatermarkingType { val position: Position val padding: Float val params: TextParams data class Text( override val position: Position, override val padding: Float, override val params: TextParams, val text: String, ) : Stamp { companion object { val Default by lazy { Text( params = TextParams.Default.copy(size = 0.2f), padding = 20f, position = Position.BottomRight, text = "Stamp" ) } } } data class Time( override val position: Position, override val padding: Float, override val params: TextParams, val format: String, ) : Stamp { companion object { val Default by lazy { Time( params = TextParams.Default.copy(size = 0.2f), padding = 20f, position = Position.BottomRight, format = "dd/MM/yyyy HH:mm" ) } } } } companion object { val entries by lazy { listOf( Text.Default, Image.Default, Stamp.Text.Default, Stamp.Time.Default ) } } } fun WatermarkingType.Stamp.copy( position: Position = this.position, padding: Float = this.padding, params: TextParams = this.params, ): WatermarkingType.Stamp = when (this) { is WatermarkingType.Stamp.Text -> { copy( position = position, padding = padding, params = params, text = this.text ) } is WatermarkingType.Stamp.Time -> { copy( position = position, padding = padding, params = params, format = this.format ) } } data class TextParams( val color: Int, val size: Float, val font: FontType?, val backgroundColor: Int, ) { companion object { val Default by lazy { TextParams( color = -16777216, size = 0.1f, font = null, backgroundColor = 0, ) } } } data class DigitalParams( val isInvisible: Boolean, val isLSB: Boolean ) { companion object { val Default by lazy { DigitalParams( isInvisible = false, isLSB = true ) } } } fun WatermarkingType.isStamp() = this is WatermarkingType.Stamp fun WatermarkingType.digitalParams(): DigitalParams? = when (this) { is WatermarkingType.Image -> digitalParams is WatermarkingType.Text -> digitalParams else -> null } fun WatermarkParams.copy( digitalParams: DigitalParams? = this.watermarkingType.digitalParams() ): WatermarkParams = when (watermarkingType) { is WatermarkingType.Image -> copy( watermarkingType = watermarkingType.copy( digitalParams = digitalParams ?: watermarkingType.digitalParams ) ) is WatermarkingType.Text -> copy( watermarkingType = watermarkingType.copy( digitalParams = digitalParams ?: watermarkingType.digitalParams ) ) else -> this } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/WatermarkingContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation import android.net.Uri import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.ImageReset import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.CompareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShowOriginalButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ResetDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.compare.presentation.components.CompareSheet import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.HiddenWatermarkInfo import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.WatermarkDataSelector import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.WatermarkParamsSelectionGroup import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.WatermarkingTypeSelector import com.t8rin.imagetoolbox.feature.watermarking.presentation.screenLogic.WatermarkingComponent @Composable fun WatermarkingContent( component: WatermarkingComponent ) { AutoContentBasedColors( model = component.selectedUri ) val imagePicker = rememberImagePicker { uris: List -> component.setUris( uris = uris ) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } var showExitDialog by rememberSaveable { mutableStateOf(false) } var showOriginal by rememberSaveable { mutableStateOf(false) } var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } var showCompareSheet by rememberSaveable { mutableStateOf(false) } var showResetDialog by rememberSaveable { mutableStateOf(false) } AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.watermarking), input = component.selectedUri.takeIf { it != Uri.EMPTY }, isLoading = component.isImageLoading, size = null ) }, onGoBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() }, topAppBarPersistentActions = { if (component.previewBitmap == null) TopAppBarEmoji() CompareButton( onClick = { showCompareSheet = true }, visible = component.previewBitmap != null && component.internalBitmap != null ) ZoomButton( onClick = { showZoomSheet = true }, visible = component.previewBitmap != null ) }, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.previewBitmap != null, onShare = component::shareBitmaps, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) EnhancedIconButton( enabled = component.internalBitmap != null, onClick = { showResetDialog = true } ) { Icon( imageVector = Icons.Rounded.ImageReset, contentDescription = stringResource(R.string.reset_image) ) } if (component.internalBitmap != null) { ShowOriginalButton( onStateChange = { showOriginal = it } ) } }, forceImagePreviewToMax = showOriginal, imagePreview = { ImageContainer( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), imageInside = isPortrait, showOriginal = showOriginal, previewBitmap = component.previewBitmap, originalBitmap = component.internalBitmap, isLoading = component.isImageLoading, shouldShowPreview = true ) }, controls = { Column( verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { ImageCounter( imageCount = component.uris.size.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) HiddenWatermarkInfo( hiddenWatermark = component.currentHiddenWatermark ) WatermarkingTypeSelector( value = component.watermarkParams, onValueChange = component::updateWatermarkParams ) WatermarkDataSelector( value = component.watermarkParams, onValueChange = component::updateWatermarkParams ) WatermarkParamsSelectionGroup( value = component.watermarkParams, onValueChange = component::updateWatermarkParams ) SaveExifWidget( checked = component.keepExif, imageFormat = component.imageFormat, onCheckedChange = component::toggleKeepExif ) QualitySelector( imageFormat = component.imageFormat, quality = component.quality, onQualityChange = component::setQuality ) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, quality = component.quality, ) } }, buttons = { val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { oneTimeSaveLocationUri -> component.saveBitmaps( oneTimeSaveLocationUri = oneTimeSaveLocationUri ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, actions = { if (isPortrait) it() }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, noDataControls = { ImageNotPickedWidget(onPickImage = pickImage) }, canShowScreenData = component.uris.isNotEmpty() ) val transformations by remember(component.previewBitmap) { derivedStateOf { listOf( component.getWatermarkTransformation() ) } } PickImageFromUrisSheet( transformations = transformations, visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, uris = component.uris, selectedUri = component.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = component::updateUrisSilently, columns = if (isPortrait) 2 else 4, ) ResetDialog( visible = showResetDialog, onDismiss = { showResetDialog = false }, title = stringResource(R.string.reset_properties), text = stringResource(R.string.reset_properties_sub), onReset = { component.updateWatermarkParams(WatermarkParams.Default) } ) CompareSheet( data = component.internalBitmap to component.previewBitmap, visible = showCompareSheet, onDismiss = { showCompareSheet = false } ) ZoomModalSheet( data = component.selectedUri, visible = showZoomSheet, transformations = transformations, onDismiss = { showZoomSheet = false } ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/HiddenWatermarkInfo.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.rounded.ContentCopy import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.AddPhotoAlt import com.t8rin.imagetoolbox.core.resources.shapes.CloverShape import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.widget.image.Picture import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItemOverload import com.t8rin.imagetoolbox.feature.watermarking.domain.HiddenWatermark @Composable internal fun HiddenWatermarkInfo( hiddenWatermark: HiddenWatermark? ) { AnimatedContent( targetState = hiddenWatermark, modifier = Modifier.fillMaxWidth() ) { hidden -> hidden?.let { PreferenceItemOverload( title = stringResource( when (hidden) { is HiddenWatermark.Text -> R.string.hidden_watermark_text_detected is HiddenWatermark.Image -> R.string.hidden_watermark_image_detected } ), subtitle = when (hidden) { is HiddenWatermark.Text -> hidden.text is HiddenWatermark.Image -> stringResource(R.string.this_image_was_hidden) }, onClick = if (hidden is HiddenWatermark.Text) { { Clipboard.copy(hidden.text) } } else null, contentColor = MaterialTheme.colorScheme.onSecondaryContainer, containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.35f), titleFontStyle = PreferenceItemDefaults.TitleFontStyleSmall, startIcon = { Icon( imageVector = Icons.Outlined.Info, contentDescription = null ) }, endIcon = { when (hidden) { is HiddenWatermark.Text -> { Icon( imageVector = Icons.Rounded.ContentCopy, contentDescription = null ) } is HiddenWatermark.Image -> { Picture( model = hidden.image, shape = CloverShape, modifier = Modifier.size(48.dp), error = { Icon( imageVector = Icons.TwoTone.AddPhotoAlt, contentDescription = null, modifier = Modifier .fillMaxSize() .clip(CloverShape) .background( color = MaterialTheme.colorScheme.secondaryContainer .copy(0.5f) .compositeOver(MaterialTheme.colorScheme.surfaceContainer) ) .padding(8.dp) ) } ) } } }, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.large ) } } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkDataSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.JAVA_FORMAT_SPECIFICATION import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkingType @Composable fun WatermarkDataSelector( value: WatermarkParams, onValueChange: (WatermarkParams) -> Unit, modifier: Modifier = Modifier ) { Column { AnimatedContent( targetState = value.watermarkingType::class.qualifiedName, transitionSpec = { fadeIn() + slideInVertically() togetherWith fadeOut() + slideOutVertically() } ) { qualifiedName -> when (qualifiedName) { WatermarkingType.Image::class.qualifiedName -> { val type = value.watermarkingType as? WatermarkingType.Image ?: return@AnimatedContent ImageSelector( value = type.imageData, subtitle = stringResource(id = R.string.watermarking_image_sub), onValueChange = { onValueChange( value.copy( watermarkingType = type.copy(imageData = it) ) ) }, modifier = modifier.fillMaxWidth() ) } WatermarkingType.Text::class.qualifiedName -> { val type = value.watermarkingType as? WatermarkingType.Text ?: return@AnimatedContent RoundedTextField( modifier = modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = type.text, singleLine = false, onValueChange = { onValueChange( value.copy( watermarkingType = type.copy(text = it) ) ) }, label = { Text(stringResource(R.string.text)) } ) } WatermarkingType.Stamp.Text::class.qualifiedName -> { val type = value.watermarkingType as? WatermarkingType.Stamp.Text ?: return@AnimatedContent RoundedTextField( modifier = modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = type.text, singleLine = false, onValueChange = { onValueChange( value.copy( watermarkingType = type.copy(text = it) ) ) }, label = { Text(stringResource(R.string.text)) } ) } WatermarkingType.Stamp.Time::class.qualifiedName -> { val type = value.watermarkingType as? WatermarkingType.Stamp.Time ?: return@AnimatedContent RoundedTextField( modifier = modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = type.format, singleLine = false, onValueChange = { onValueChange( value.copy( watermarkingType = type.copy(format = it) ) ) }, label = { Text(stringResource(R.string.format_pattern)) }, endIcon = { val linkHandler = LocalUriHandler.current EnhancedIconButton( onClick = { linkHandler.openUri(JAVA_FORMAT_SPECIFICATION) } ) { Icon( imageVector = Icons.Outlined.Info, contentDescription = null ) } } ) } } } } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkParamsSelectionGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Tune import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.other.ExpandableItem import com.t8rin.imagetoolbox.core.ui.widget.text.TitleItem import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors.CommonParamsContent import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors.DigitalParamsContent import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors.ImageParamsContent import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors.StampParamsContent import com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors.TextParamsContent @Composable fun WatermarkParamsSelectionGroup( value: WatermarkParams, onValueChange: (WatermarkParams) -> Unit, modifier: Modifier = Modifier ) { ExpandableItem( modifier = modifier, color = MaterialTheme.colorScheme.surfaceContainer, visibleContent = { TitleItem( text = stringResource(id = R.string.properties), icon = Icons.Outlined.Tune ) }, expandableContent = { Column( modifier = Modifier.padding(horizontal = 8.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) { val params by rememberUpdatedState(value) CommonParamsContent( params = params, onValueChange = onValueChange ) TextParamsContent( params = params, onValueChange = onValueChange ) ImageParamsContent( params = params, onValueChange = onValueChange ) DigitalParamsContent( params = params, onValueChange = onValueChange ) StampParamsContent( params = params, onValueChange = onValueChange ) } }, shape = ShapeDefaults.extraLarge ) } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/WatermarkingTypeSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButtonGroup import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkingType @Composable fun WatermarkingTypeSelector( value: WatermarkParams, onValueChange: (WatermarkParams) -> Unit, modifier: Modifier = Modifier ) { val selectedIndex by remember(value.watermarkingType) { derivedStateOf { WatermarkingType .entries .indexOfFirst { value.watermarkingType::class.java.isInstance(it) } } } EnhancedButtonGroup( modifier = modifier .container( shape = ShapeDefaults.large ), enabled = true, items = WatermarkingType.entries.map { it.translatedName }, selectedIndex = selectedIndex, title = stringResource(id = R.string.watermark_type), onIndexChange = { onValueChange(value.copy(watermarkingType = WatermarkingType.entries[it])) }, inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainerHigh ) } private val WatermarkingType.translatedName: String @Composable get() = when (this) { is WatermarkingType.Text -> stringResource(R.string.text) is WatermarkingType.Image -> stringResource(R.string.image) is WatermarkingType.Stamp.Text -> stringResource(R.string.stamp) is WatermarkingType.Stamp.Time -> stringResource(R.string.timestamp) } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/CommonParamsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TextRotationAngleup import androidx.compose.material.icons.rounded.Repeat import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.AlphaSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.BlendingModeSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.digitalParams import com.t8rin.imagetoolbox.feature.watermarking.domain.isStamp import kotlin.math.roundToInt @Composable internal fun CommonParamsContent( params: WatermarkParams, onValueChange: (WatermarkParams) -> Unit ) { val digitalParams = params.watermarkingType.digitalParams() val isInvisible = digitalParams?.isInvisible == true val isNotStampAndInvisible = !params.watermarkingType.isStamp() && !isInvisible AnimatedVisibility( visible = !params.isRepeated && isNotStampAndInvisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { EnhancedSliderItem( value = params.positionX, title = stringResource(id = R.string.offset_x), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(params.copy(positionX = it)) }, valueRange = 0f..1f, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = params.positionY, title = stringResource(id = R.string.offset_y), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange(params.copy(positionY = it)) }, valueRange = 0f..1f, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier.padding(bottom = 4.dp) ) } } AnimatedVisibility( visible = isNotStampAndInvisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { EnhancedSliderItem( value = params.rotation, icon = Icons.Outlined.TextRotationAngleup, title = stringResource(id = R.string.angle), valueRange = 0f..360f, internalStateTransformation = Float::roundToInt, onValueChange = { onValueChange(params.copy(rotation = it.roundToInt())) }, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) } AnimatedVisibility( visible = !isInvisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { AlphaSelector( value = params.alpha, onValueChange = { onValueChange(params.copy(alpha = it)) }, modifier = Modifier.fillMaxWidth(), shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) } AnimatedVisibility( visible = isNotStampAndInvisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { PreferenceRowSwitch( title = stringResource(id = R.string.repeat_watermark), subtitle = stringResource(id = R.string.repeat_watermark_sub), checked = params.isRepeated, startIcon = Icons.Rounded.Repeat, onClick = { onValueChange(params.copy(isRepeated = it)) }, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) Spacer(Modifier.height(4.dp)) BlendingModeSelector( value = params.overlayMode, onValueChange = { onValueChange( params.copy(overlayMode = it) ) } ) } } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/DigitalParamsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2025 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DisabledVisible import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.copy import com.t8rin.imagetoolbox.feature.watermarking.domain.digitalParams import com.t8rin.imagetoolbox.feature.watermarking.domain.isStamp @Composable internal fun DigitalParamsContent( params: WatermarkParams, onValueChange: (WatermarkParams) -> Unit ) { val digitalParams = params.watermarkingType.digitalParams() val isInvisible = digitalParams?.isInvisible == true AnimatedVisibility( visible = !params.watermarkingType.isStamp(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { Column { PreferenceRowSwitch( title = stringResource(id = R.string.invisible_mode), subtitle = stringResource(id = R.string.invisible_mode_sub), checked = isInvisible, startIcon = Icons.Rounded.DisabledVisible, onClick = { onValueChange( params.copy( digitalParams = digitalParams?.copy( isInvisible = !isInvisible ) ) ) }, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) // AnimatedVisibility(visible = isInvisible) { // PreferenceRowSwitch( // title = stringResource(id = R.string.use_lsb), // subtitle = stringResource(id = R.string.use_lsb_sub), // checked = digitalParams?.isLSB ?: false, // startIcon = Icons.Rounded.GraphicEq, // onClick = { // onValueChange( // params.copy( // digitalParams = digitalParams?.copy( // isLSB = !digitalParams.isLSB // ) // ) // ) // }, // shape = ShapeDefaults.large, // containerColor = MaterialTheme.colorScheme.surface, // modifier = Modifier.padding(top = 4.dp) // ) // } } } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/ImageParamsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkingType import com.t8rin.imagetoolbox.feature.watermarking.domain.digitalParams @Composable internal fun ImageParamsContent( params: WatermarkParams, onValueChange: (WatermarkParams) -> Unit ) { val digitalParams = params.watermarkingType.digitalParams() val isInvisible = digitalParams?.isInvisible == true AnimatedVisibility( visible = params.watermarkingType is WatermarkingType.Image && !isInvisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val type = params.watermarkingType as? WatermarkingType.Image ?: return@AnimatedVisibility EnhancedSliderItem( value = type.size, title = stringResource(R.string.watermark_size), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( params.copy( watermarkingType = type.copy(size = it) ) ) }, valueRange = 0.01f..1f, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/StampParamsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiFont import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FontSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PositionSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkingType import com.t8rin.imagetoolbox.feature.watermarking.domain.copy import com.t8rin.imagetoolbox.feature.watermarking.domain.isStamp @Composable internal fun StampParamsContent( params: WatermarkParams, onValueChange: (WatermarkParams) -> Unit ) { AnimatedVisibility( visible = params.watermarkingType.isStamp(), enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val type = params.watermarkingType as? WatermarkingType.Stamp ?: return@AnimatedVisibility Column { FontSelector( value = type.params.font.toUiFont(), onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy( font = it.type ) ) ) ) } ) Spacer(modifier = Modifier.height(4.dp)) PositionSelector( value = type.position, onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( position = it ) ) ) }, selectedItemColor = MaterialTheme.colorScheme.primary ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = type.padding, title = stringResource(R.string.padding), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( padding = it ) ) ) }, valueRange = 0f..50f, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = type.params.size, title = stringResource(R.string.watermark_size), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy( size = it ) ) ) ) }, valueRange = 0.01f..1f, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) ColorRowSelector( value = type.params.color.toColor(), onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy( color = it.toArgb() ) ) ) ) }, title = stringResource(R.string.text_color), modifier = Modifier.container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) ) Spacer(modifier = Modifier.height(4.dp)) ColorRowSelector( value = type.params.backgroundColor.toColor(), onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy( backgroundColor = it.toArgb() ) ) ) ) }, title = stringResource(R.string.background_color), modifier = Modifier.container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) ) } } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/components/selectors/TextParamsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.components.selectors import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.colors.util.roundToTwoDigits import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.model.toUiFont import com.t8rin.imagetoolbox.core.ui.theme.toColor import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ColorRowSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.FontSelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkingType import com.t8rin.imagetoolbox.feature.watermarking.domain.digitalParams @Composable internal fun TextParamsContent( params: WatermarkParams, onValueChange: (WatermarkParams) -> Unit ) { val digitalParams = params.watermarkingType.digitalParams() val isInvisible = digitalParams?.isInvisible == true AnimatedVisibility( visible = params.watermarkingType is WatermarkingType.Text && !isInvisible, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { val type = params.watermarkingType as? WatermarkingType.Text ?: return@AnimatedVisibility Column { FontSelector( value = type.params.font.toUiFont(), onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy(font = it.type) ) ) ) } ) Spacer(modifier = Modifier.height(4.dp)) EnhancedSliderItem( value = type.params.size, title = stringResource(R.string.watermark_size), internalStateTransformation = { it.roundToTwoDigits() }, onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy(size = it) ) ) ) }, valueRange = 0.01f..1f, shape = ShapeDefaults.large, containerColor = MaterialTheme.colorScheme.surface ) Spacer(modifier = Modifier.height(4.dp)) ColorRowSelector( value = type.params.color.toColor(), onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy(color = it.toArgb()) ) ) ) }, title = stringResource(R.string.text_color), modifier = Modifier.container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) ) Spacer(modifier = Modifier.height(4.dp)) ColorRowSelector( value = type.params.backgroundColor.toColor(), onValueChange = { onValueChange( params.copy( watermarkingType = type.copy( params = type.params.copy(backgroundColor = it.toArgb()) ) ) ) }, title = stringResource(R.string.background_color), modifier = Modifier.container( shape = ShapeDefaults.large, color = MaterialTheme.colorScheme.surface ) ) } } } ================================================ FILE: feature/watermarking/src/main/java/com/t8rin/imagetoolbox/feature/watermarking/presentation/screenLogic/WatermarkingComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.watermarking.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import coil3.transform.Transformation import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.data.utils.toCoil import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.transformation.GenericTransformation import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.watermarking.domain.HiddenWatermark import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkApplier import com.t8rin.imagetoolbox.feature.watermarking.domain.WatermarkParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.withContext class WatermarkingComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val imageCompressor: ImageCompressor, private val shareProvider: ImageShareProvider, private val imageGetter: ImageGetter, private val imageScaler: ImageScaler, private val watermarkApplier: WatermarkApplier, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) } } private val _internalBitmap: MutableState = mutableStateOf(null) val internalBitmap: Bitmap? by _internalBitmap private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _keepExif = mutableStateOf(false) val keepExif by _keepExif private val _selectedUri = mutableStateOf(Uri.EMPTY) val selectedUri: Uri by _selectedUri private val _uris = mutableStateOf>(emptyList()) val uris by _uris private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _watermarkParams = mutableStateOf(WatermarkParams.Default) val watermarkParams by _watermarkParams private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat private val _quality: MutableState = mutableStateOf(Quality.Base()) val quality by _quality private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _currentHiddenWatermark: MutableState = mutableStateOf(null) val currentHiddenWatermark by _currentHiddenWatermark private fun updateBitmap( bitmap: Bitmap, onComplete: () -> Unit = {} ) { componentScope.launch { _currentHiddenWatermark.update { watermarkApplier.checkHiddenWatermark(bitmap) } } componentScope.launch { _isImageLoading.value = true _internalBitmap.value = imageScaler.scaleUntilCanShow(bitmap) checkBitmapAndUpdate() _isImageLoading.value = false onComplete() } } private fun checkBitmapAndUpdate() { debouncedImageCalculation { _previewBitmap.value = _internalBitmap.value?.let { getWatermarkedBitmap(it) } } } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _left.value = -1 _isSaving.value = true val results = mutableListOf() _done.value = 0 _left.value = uris.size uris.forEach { uri -> getWatermarkedBitmap( data = uri.toString(), originalSize = true )?.let { localBitmap -> val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height, quality = quality ) results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = imageInfo ) ), keepOriginalMetadata = keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = left ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } private suspend fun getWatermarkedBitmap( data: Any, originalSize: Boolean = false ): Bitmap? = withContext(defaultDispatcher) { imageGetter.getImage(data, originalSize)?.let { image -> watermarkApplier.applyWatermark( image = image, originalSize = originalSize, params = watermarkParams ) } } fun shareBitmaps() { _left.value = -1 savingJob = trackProgress { _isSaving.value = true _done.value = 0 _left.value = uris.size shareProvider.shareImages( uris.map { it.toString() }, imageLoader = { uri -> getWatermarkedBitmap( data = uri, originalSize = true )?.let { it to ImageInfo( width = it.width, height = it.height, imageFormat = imageFormat, quality = quality ) } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _isSaving.value = false _done.value = 0 } else { _done.value = it } updateProgress( done = done, total = left ) } ) _isSaving.value = false _left.value = -1 } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false _left.value = -1 } fun setQuality(quality: Quality) { _quality.update { quality } registerChanges() } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } registerChanges() } fun updateWatermarkParams(watermarkParams: WatermarkParams) { _watermarkParams.update { watermarkParams } registerChanges() checkBitmapAndUpdate() } fun updateSelectedUri( uri: Uri ) { componentScope.launch { _selectedUri.value = uri _isImageLoading.value = true imageGetter.getImageAsync( uri = uri.toString(), originalSize = false, onGetImage = { imageData -> updateBitmap(imageData.image) _isImageLoading.value = false setImageFormat(imageData.imageInfo.imageFormat) }, onFailure = { _isImageLoading.value = false AppToastHost.showFailureToast(it) } ) } } fun updateUrisSilently(removedUri: Uri) { componentScope.launch { if (selectedUri == removedUri) { val index = uris.indexOf(removedUri) if (index == 0) { uris.getOrNull(1)?.let(::updateSelectedUri) } else { uris.getOrNull(index - 1)?.let(::updateSelectedUri) } } _uris.update { it.toMutableList().apply { remove(removedUri) } } } } fun setUris( uris: List, ) { _uris.update { uris } uris.firstOrNull()?.let(::updateSelectedUri) } fun toggleKeepExif(value: Boolean) { _keepExif.update { value } registerChanges() } fun getWatermarkTransformation(): Transformation { return GenericTransformation(watermarkParams) { input, size -> imageScaler.scaleImage( image = getWatermarkedBitmap(input) ?: input, width = size.width, height = size.height, resizeType = ResizeType.Flexible ) }.toCoil() } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true getWatermarkedBitmap( data = selectedUri, originalSize = true )?.let { it to ImageInfo( width = it.width, height = it.height, imageFormat = imageFormat, quality = quality ) }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = selectedUri.toString()) )?.let { uri -> onComplete(uri.toUri()) } } _isSaving.value = false } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 _left.value = uris.size val list = mutableListOf() uris.forEach { uri -> getWatermarkedBitmap( data = uri, originalSize = true )?.let { it to ImageInfo( width = it.width, height = it.height, imageFormat = imageFormat, quality = quality ) }?.let { (image, imageInfo) -> shareProvider.cacheImage( image = image, imageInfo = imageInfo.copy(originalUri = uri.toString()) )?.let { uri -> list.add(uri.toUri()) } } _done.value += 1 updateProgress( done = done, total = left ) } onComplete(list) _isSaving.value = false } } fun selectLeftUri() { uris .indexOf(selectedUri) .takeIf { it >= 0 } ?.let { uris.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris .indexOf(selectedUri) .takeIf { it >= 0 } ?.let { uris.rightFrom(it) } ?.let(::updateSelectedUri) } fun getFormatForFilenameSelection(): ImageFormat? = if (uris.size == 1) imageFormat else null @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): WatermarkingComponent } } ================================================ FILE: feature/webp-tools/.gitignore ================================================ /build ================================================ FILE: feature/webp-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.webp_tools" dependencies { implementation(libs.toolbox.awebp) } ================================================ FILE: feature/webp-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/data/AndroidWebpConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.webp_tools.data import android.content.Context import android.graphics.Bitmap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.net.toUri import com.t8rin.awebp.decoder.AnimatedWebpDecoder import com.t8rin.awebp.encoder.AnimatedWebpEncoder import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.image.model.ResizeType import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.feature.webp_tools.domain.WebpConverter import com.t8rin.imagetoolbox.feature.webp_tools.domain.WebpParams import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream import java.io.InputStream import javax.inject.Inject internal class AndroidWebpConverter @Inject constructor( private val imageGetter: ImageGetter, private val imageShareProvider: ImageShareProvider, private val imageScaler: ImageScaler, @ApplicationContext private val context: Context, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, WebpConverter { override fun extractFramesFromWebp( webpUri: String, imageFormat: ImageFormat, quality: Quality ): Flow = AnimatedWebpDecoder( sourceFile = webpUri.file, coroutineScope = CoroutineScope(decodingDispatcher) ).frames().mapNotNull { frame -> imageShareProvider.cacheImage( image = frame.bitmap, imageInfo = ImageInfo( width = frame.bitmap.width, height = frame.bitmap.height, imageFormat = imageFormat, quality = quality ) ).also { frame.bitmap.recycle() } } override suspend fun createWebpFromImageUris( imageUris: List, params: WebpParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): ByteArray? = withContext(defaultDispatcher) { val size = params.size ?: imageGetter.getImage(data = imageUris[0])!!.run { IntegerSize(width, height) } if (size.width <= 0 || size.height <= 0) { onFailure(IllegalArgumentException("Width and height must be > 0")) return@withContext null } val encoder = AnimatedWebpEncoder( quality = params.quality.qualityValue, loopCount = params.repeatCount, backgroundColor = Color.Transparent.toArgb() ) imageUris.forEach { uri -> imageGetter.getImage( data = uri, size = size )?.let { encoder.addFrame( bitmap = imageScaler.scaleImage( image = imageScaler.scaleImage( image = it, width = size.width, height = size.height, resizeType = ResizeType.Flexible ), width = size.width, height = size.height, resizeType = ResizeType.CenterCrop( canvasColor = Color.Transparent.toArgb() ) ), duration = params.delay ) } onProgress() } runCatching { encoder.encode() }.onFailure(onFailure).getOrNull() } private val String.inputStream: InputStream? get() = context .contentResolver .openInputStream(toUri()) private val String.file: File get() { val gifFile = File(context.cacheDir, "temp.webp") inputStream?.use { gifStream -> gifStream.copyTo(FileOutputStream(gifFile)) } return gifFile } } ================================================ FILE: feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/di/WebpToolsModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.webp_tools.di import com.t8rin.imagetoolbox.feature.webp_tools.data.AndroidWebpConverter import com.t8rin.imagetoolbox.feature.webp_tools.domain.WebpConverter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface WebpToolsModule { @Binds @Singleton fun provideConverter( converter: AndroidWebpConverter ): WebpConverter } ================================================ FILE: feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.webp_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.Quality import kotlinx.coroutines.flow.Flow interface WebpConverter { fun extractFramesFromWebp( webpUri: String, imageFormat: ImageFormat, quality: Quality ): Flow suspend fun createWebpFromImageUris( imageUris: List, params: WebpParams, onFailure: (Throwable) -> Unit, onProgress: () -> Unit ): ByteArray? } ================================================ FILE: feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/domain/WebpParams.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.webp_tools.domain import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize data class WebpParams( val size: IntegerSize?, val repeatCount: Int, val delay: Int, val quality: Quality ) { companion object { val Default by lazy { WebpParams( size = null, repeatCount = 1, delay = 1000, quality = Quality.Base(100) ) } } } ================================================ FILE: feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/WebpToolsContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.webp_tools.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.resources.icons.SelectAll import com.t8rin.imagetoolbox.core.resources.icons.Webp import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.controls.ImageReorderCarousel import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedIconButton import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedLoadingIndicator import com.t8rin.imagetoolbox.core.ui.widget.image.ImagesPreviewWithSelection import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.withModifier import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceItem import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.utils.getString import com.t8rin.imagetoolbox.core.utils.isWebp import com.t8rin.imagetoolbox.feature.webp_tools.presentation.components.WebpParamsSelector import com.t8rin.imagetoolbox.feature.webp_tools.presentation.screenLogic.WebpToolsComponent @Composable fun WebpToolsContent( component: WebpToolsComponent ) { val imagePicker = rememberImagePicker(onSuccess = component::setImageUris) val pickSingleWebpLauncher = rememberFilePicker( mimeType = MimeType.Webp, onSuccess = { uri: Uri -> if (uri.isWebp()) { component.setWebpUri(uri) } else { AppToastHost.showToast( message = getString(R.string.select_webp_image_to_start), icon = Icons.Rounded.Webp ) } } ) val saveWebpLauncher = rememberFileCreator( mimeType = MimeType.Webp, onSuccess = component::saveWebpTo ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val isPortrait by isPortraitOrientationAsState() AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = when (val type = component.type) { null -> stringResource(R.string.webp_tools) else -> stringResource(type.title) }, input = component.type, isLoading = component.isLoading, size = null ) }, onGoBack = onBack, topAppBarPersistentActions = { if (component.type == null) TopAppBarEmoji() val pagesSize by remember(component.imageFrames, component.convertedImageUris) { derivedStateOf { component.imageFrames.getFramePositions(component.convertedImageUris.size).size } } val isWebpToImage = component.type is Screen.WebpTools.Type.WebpToImage AnimatedVisibility( visible = isWebpToImage && pagesSize != component.convertedImageUris.size, enter = fadeIn() + scaleIn() + expandHorizontally(), exit = fadeOut() + scaleOut() + shrinkHorizontally() ) { EnhancedIconButton( onClick = component::selectAllConvertedImages ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = "Select All" ) } } AnimatedVisibility( modifier = Modifier .padding(8.dp) .container( shape = ShapeDefaults.circle, color = MaterialTheme.colorScheme.surfaceContainerHighest, resultPadding = 0.dp ), visible = isWebpToImage && pagesSize != 0 ) { Row( modifier = Modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { pagesSize.takeIf { it != 0 }?.let { Spacer(Modifier.width(8.dp)) Text( text = it.toString(), fontSize = 20.sp, fontWeight = FontWeight.Medium ) } EnhancedIconButton( onClick = component::clearConvertedImagesSelection ) { Icon( imageVector = Icons.Rounded.Close, contentDescription = stringResource(R.string.close) ) } } } }, actions = { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = !component.isLoading && component.type != null, onShare = component::performSharing, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) }, imagePreview = { AnimatedContent( targetState = component.isLoading to component.type ) { (loading, type) -> Box( contentAlignment = Alignment.Center, modifier = if (loading) { Modifier.padding(32.dp) } else Modifier ) { if (loading || type == null) { EnhancedLoadingIndicator() } else { when (type) { is Screen.WebpTools.Type.WebpToImage -> { ImagesPreviewWithSelection( imageUris = component.convertedImageUris, imageFrames = component.imageFrames, onFrameSelectionChange = component::updateWebpFrames, isPortrait = isPortrait, isLoadingImages = component.isLoadingWebpImages ) } is Screen.WebpTools.Type.ImageToWebp -> Unit } } } } }, placeImagePreview = component.type !is Screen.WebpTools.Type.ImageToWebp, showImagePreviewAsStickyHeader = false, autoClearFocus = false, controls = { when (val type = component.type) { is Screen.WebpTools.Type.WebpToImage -> { Spacer(modifier = Modifier.height(16.dp)) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, entries = ImageFormatGroup.alphaContainedEntries ) Spacer(modifier = Modifier.height(8.dp)) QualitySelector( imageFormat = component.imageFormat, quality = component.params.quality, onQualityChange = component::setQuality ) Spacer(modifier = Modifier.height(16.dp)) } is Screen.WebpTools.Type.ImageToWebp -> { val addImagesToPdfPicker = rememberImagePicker(onSuccess = component::addImageToUris) Spacer(modifier = Modifier.height(16.dp)) ImageReorderCarousel( images = type.imageUris, onReorder = component::reorderImageUris, onNeedToAddImage = addImagesToPdfPicker::pickImage, onNeedToRemoveImageAt = component::removeImageAt, onNavigate = component.onNavigate ) Spacer(modifier = Modifier.height(8.dp)) WebpParamsSelector( value = component.params, onValueChange = component::updateParams ) Spacer(modifier = Modifier.height(16.dp)) } null -> Unit } }, contentPadding = animateDpAsState( if (component.type == null) 12.dp else 20.dp ).value, buttons = { actions -> val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it, onWebpSaveResult = saveWebpLauncher::make ) } var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.type == null, onSecondaryButtonClick = { when (component.type) { is Screen.WebpTools.Type.WebpToImage -> pickSingleWebpLauncher.pickFile() else -> imagePicker.pickImage() } }, isPrimaryButtonVisible = component.canSave, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { if (component.type is Screen.WebpTools.Type.ImageToWebp) { saveBitmaps(null) } else showFolderSelectionDialog = true }, actions = { if (isPortrait) actions() }, showNullDataButtonAsContainer = true, onSecondaryButtonLongClick = if (component.type is Screen.WebpTools.Type.ImageToWebp || component.type == null) { { showOneTimeImagePickingDialog = true } } else null ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, insetsForNoData = WindowInsets(0), noDataControls = { val types = remember { Screen.WebpTools.Type.entries } val preference1 = @Composable { PreferenceItem( title = stringResource(types[0].title), subtitle = stringResource(types[0].subtitle), startIcon = types[0].icon, modifier = Modifier.fillMaxWidth(), onClick = imagePicker::pickImage ) } val preference2 = @Composable { PreferenceItem( title = stringResource(types[1].title), subtitle = stringResource(types[1].subtitle), startIcon = types[1].icon, modifier = Modifier.fillMaxWidth(), onClick = pickSingleWebpLauncher::pickFile ) } if (isPortrait) { Column { preference1() Spacer(modifier = Modifier.height(8.dp)) preference2() } } else { val direction = LocalLayoutDirection.current Column( horizontalAlignment = Alignment.CenterHorizontally ) { Row( modifier = Modifier.padding( WindowInsets.displayCutout.asPaddingValues().let { PaddingValues( start = it.calculateStartPadding(direction), end = it.calculateEndPadding(direction) ) } ) ) { preference1.withModifier(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.width(8.dp)) preference2.withModifier(modifier = Modifier.weight(1f)) } } } }, canShowScreenData = component.type != null ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) ExitWithoutSavingDialog( onExit = component::clearAll, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/components/WebpParamsSelector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.webp_tools.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PhotoSizeSelectLarge import androidx.compose.material.icons.outlined.RepeatOne import androidx.compose.material.icons.outlined.Timelapse import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.controls.ResizeImageField import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.preferences.PreferenceRowSwitch import com.t8rin.imagetoolbox.feature.webp_tools.domain.WebpParams import kotlin.math.roundToInt @Composable fun WebpParamsSelector( value: WebpParams, onValueChange: (WebpParams) -> Unit ) { Column { val size = value.size ?: IntegerSize.Undefined AnimatedVisibility(size.isDefined()) { ResizeImageField( imageInfo = ImageInfo(size.width, size.height), originalSize = null, onWidthChange = { onValueChange( value.copy( size = size.copy(width = it) ) ) }, onHeightChange = { onValueChange( value.copy( size = size.copy(height = it) ) ) } ) } Spacer(modifier = Modifier.height(8.dp)) PreferenceRowSwitch( title = stringResource(id = R.string.use_size_of_first_frame), subtitle = stringResource(id = R.string.use_size_of_first_frame_sub), checked = value.size == null, onClick = { onValueChange( value.copy(size = if (it) null else IntegerSize(1000, 1000)) ) }, startIcon = Icons.Outlined.PhotoSizeSelectLarge, modifier = Modifier.fillMaxWidth(), containerColor = Color.Unspecified, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) QualitySelector( imageFormat = ImageFormat.Jpg, quality = value.quality, onQualityChange = { onValueChange( value.copy( quality = Quality.Base(it.qualityValue) ) ) } ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.repeatCount, icon = Icons.Outlined.RepeatOne, title = stringResource(id = R.string.repeat_count), valueRange = 1f..10f, steps = 9, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( repeatCount = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) Spacer(modifier = Modifier.height(8.dp)) EnhancedSliderItem( value = value.delay, icon = Icons.Outlined.Timelapse, title = stringResource(id = R.string.frame_delay), valueRange = 1f..4000f, internalStateTransformation = { it.roundToInt() }, onValueChange = { onValueChange( value.copy( delay = it.roundToInt() ) ) }, shape = ShapeDefaults.extraLarge ) } } ================================================ FILE: feature/webp-tools/src/main/java/com/t8rin/imagetoolbox/feature/webp_tools/presentation/screenLogic/WebpToolsComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.webp_tools.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFrames import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.webp_tools.domain.WebpConverter import com.t8rin.imagetoolbox.feature.webp_tools.domain.WebpParams import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.flow.onCompletion class WebpToolsComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialType: Screen.WebpTools.Type?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val imageCompressor: ImageCompressor, private val imageGetter: ImageGetter, private val fileController: FileController, private val webpConverter: WebpConverter, private val shareProvider: ShareProvider, defaultDispatchersHolder: DispatchersHolder ) : BaseComponent(defaultDispatchersHolder, componentContext) { init { debounce { initialType?.let(::setType) } } private val _type: MutableState = mutableStateOf(null) val type by _type private val _isLoading: MutableState = mutableStateOf(false) val isLoading by _isLoading private val _isLoadingWebpImages: MutableState = mutableStateOf(false) val isLoadingWebpImages by _isLoadingWebpImages private val _params: MutableState = mutableStateOf(WebpParams.Default) val params by _params private val _convertedImageUris: MutableState> = mutableStateOf(emptyList()) val convertedImageUris by _convertedImageUris private val _imageFormat: MutableState = mutableStateOf(ImageFormat.Png.Lossless) val imageFormat by _imageFormat private val _imageFrames: MutableState = mutableStateOf(ImageFrames.All) val imageFrames by _imageFrames private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private var webpData: ByteArray? = null fun setType(type: Screen.WebpTools.Type) { when (type) { is Screen.WebpTools.Type.WebpToImage -> { type.webpUri?.let { setWebpUri(it) } ?: _type.update { null } } is Screen.WebpTools.Type.ImageToWebp -> { _type.update { type } } } } fun setImageUris(uris: List) { clearAll() _type.update { Screen.WebpTools.Type.ImageToWebp(uris) } } private var collectionJob: Job? by smartJob { _isLoading.update { false } } fun setWebpUri(uri: Uri) { clearAll() _type.update { Screen.WebpTools.Type.WebpToImage(uri) } updateWebpFrames(ImageFrames.All) collectionJob = componentScope.launch { _isLoading.update { true } _isLoadingWebpImages.update { true } webpConverter.extractFramesFromWebp( webpUri = uri.toString(), imageFormat = imageFormat, quality = params.quality ).onCompletion { _isLoading.update { false } _isLoadingWebpImages.update { false } }.collect { nextUri -> if (isLoading) { _isLoading.update { false } } _convertedImageUris.update { it + nextUri } } } } fun clearAll() { collectionJob = null _type.update { null } _convertedImageUris.update { emptyList() } webpData = null savingJob = null updateParams(WebpParams.Default) registerChangesCleared() } fun updateWebpFrames(imageFrames: ImageFrames) { _imageFrames.update { imageFrames } registerChanges() } fun clearConvertedImagesSelection() = updateWebpFrames(ImageFrames.ManualSelection(emptyList())) fun selectAllConvertedImages() = updateWebpFrames(ImageFrames.All) private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveWebpTo(uri: Uri) { savingJob = trackProgress { _isSaving.value = true webpData?.let { byteArray -> fileController.writeBytes( uri = uri.toString(), block = { it.writeBytes(byteArray) } ).also(::parseFileSaveResult).onSuccess(::registerSave) } _isSaving.value = false webpData = null } } fun saveBitmaps( oneTimeSaveLocationUri: String?, onWebpSaveResult: (String) -> Unit ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.WebpTools.Type.WebpToImage -> { val results = mutableListOf() type.webpUri?.toString()?.also { webpUri -> _left.value = 0 webpConverter.extractFramesFromWebp( webpUri = webpUri, imageFormat = imageFormat, quality = params.quality ).onCompletion { parseSaveResults(results.onSuccess(::registerSave)) }.collect { uri -> imageGetter.getImage( data = uri, originalSize = true )?.let { localBitmap -> if ((done + 1) in imageFrames.getFramePositions(convertedImageUris.size + 10)) { val imageInfo = ImageInfo( imageFormat = imageFormat, width = localBitmap.width, height = localBitmap.height ) results.add( fileController.save( saveTarget = ImageSaveTarget( imageInfo = imageInfo, originalUri = uri, sequenceNumber = _done.value + 1, data = imageCompressor.compressAndTransform( image = localBitmap, imageInfo = ImageInfo( imageFormat = imageFormat, quality = params.quality, width = localBitmap.width, height = localBitmap.height ) ) ), keepOriginalMetadata = false, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value++ updateProgress( done = done, total = left ) } } } is Screen.WebpTools.Type.ImageToWebp -> { _left.value = type.imageUris?.size ?: -1 webpData = type.imageUris?.map { it.toString() }?.let { list -> webpConverter.createWebpFromImageUris( imageUris = list, params = params, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) }, onFailure = { parseSaveResults(listOf(SaveResult.Error.Exception(it))) } )?.also { onWebpSaveResult("WEBP_${timestamp()}.webp") registerSave() } } } null -> Unit } _isSaving.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun reorderImageUris(uris: List?) { if (type is Screen.WebpTools.Type.ImageToWebp) { _type.update { Screen.WebpTools.Type.ImageToWebp(uris) } } registerChanges() } fun addImageToUris(uris: List) { val type = _type.value if (type is Screen.WebpTools.Type.ImageToWebp) { _type.update { val newUris = type.imageUris?.plus(uris)?.toSet()?.toList() Screen.WebpTools.Type.ImageToWebp(newUris) } } registerChanges() } fun removeImageAt(index: Int) { val type = _type.value if (type is Screen.WebpTools.Type.ImageToWebp) { _type.update { val newUris = type.imageUris?.toMutableList()?.apply { removeAt(index) } Screen.WebpTools.Type.ImageToWebp(newUris) } } registerChanges() } fun setImageFormat(imageFormat: ImageFormat) { _imageFormat.update { imageFormat } registerChanges() } fun setQuality(quality: Quality) { updateParams(params.copy(quality = quality)) } fun updateParams(params: WebpParams) { _params.update { params } registerChanges() } fun performSharing() { cacheImages { uris -> componentScope.launch { shareProvider.shareUris(uris.map { it.toString() }) AppToastHost.showConfetti() } } } fun cacheImages( onComplete: (List) -> Unit ) { _isSaving.value = false savingJob?.cancel() savingJob = trackProgress { _isSaving.value = true _left.value = 1 _done.value = 0 when (val type = _type.value) { is Screen.WebpTools.Type.WebpToImage -> { _left.value = -1 val positions = imageFrames.getFramePositions(convertedImageUris.size).map { it - 1 } val uris = convertedImageUris.filterIndexed { index, _ -> index in positions } onComplete(uris.map { it.toUri() }) } is Screen.WebpTools.Type.ImageToWebp -> { _left.value = type.imageUris?.size ?: -1 type.imageUris?.map { it.toString() }?.let { list -> webpConverter.createWebpFromImageUris( imageUris = list, params = params, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) }, onFailure = {} )?.also { byteArray -> shareProvider.cacheByteArray( byteArray = byteArray, filename = "WEBP_${timestamp()}.webp", )?.let { onComplete(listOf(it.toUri())) } } } } null -> Unit } _isSaving.value = false } } val canSave: Boolean get() = (imageFrames == ImageFrames.All) .or(type is Screen.WebpTools.Type.ImageToWebp) .or((imageFrames as? ImageFrames.ManualSelection)?.framePositions?.isNotEmpty() == true) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialType: Screen.WebpTools.Type?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): WebpToolsComponent } } ================================================ FILE: feature/weight-resize/.gitignore ================================================ /build ================================================ FILE: feature/weight-resize/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.weight_resize" ================================================ FILE: feature/weight-resize/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/data/AndroidWeightImageScaler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.weight_resize.data import android.graphics.Bitmap import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Quality import com.t8rin.imagetoolbox.core.domain.model.IntegerSize import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.feature.weight_resize.domain.WeightImageScaler import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.math.roundToInt internal class AndroidWeightImageScaler @Inject constructor( imageScaler: ImageScaler, private val imageCompressor: ImageCompressor, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ImageScaler by imageScaler, WeightImageScaler { override suspend fun scaleByMaxBytes( image: Bitmap, imageFormat: ImageFormat, imageScaleMode: ImageScaleMode, maxBytes: Long ): Pair? = withContext(defaultDispatcher) { runSuspendCatching { val initialSize = imageCompressor.calculateImageSize( image = image, imageInfo = ImageInfo( width = image.width, height = image.height, imageFormat = imageFormat ) ) val normalization = 2048 * (initialSize / (5 * 1024 * 1024)).coerceAtLeast(1) val targetSize = (maxBytes - normalization).coerceAtLeast(1024) if (initialSize > targetSize) { var outArray = ByteArray(initialSize.toInt()) var compressQuality = 100 var newSize = image.size() if (imageFormat.canChangeCompressionValue) { while (outArray.size > targetSize) { ensureActive() outArray = imageCompressor.compressAndTransform( image = image, imageInfo = ImageInfo( width = newSize.width, height = newSize.height, quality = Quality.Base(compressQuality), imageFormat = imageFormat ) ) compressQuality -= 1 if (compressQuality < 15) break } compressQuality = 15 } while (outArray.size > targetSize) { ensureActive() newSize = scaleImage( image = image, width = (newSize.width * 0.93f).roundToInt(), height = (newSize.height * 0.93f).roundToInt(), imageScaleMode = imageScaleMode ).size() outArray = imageCompressor.compressAndTransform( image = image, imageInfo = ImageInfo( quality = Quality.Base(compressQuality), imageFormat = imageFormat, width = newSize.width, height = newSize.height ) ) } outArray to ImageInfo( width = newSize.width, height = newSize.height, imageFormat = imageFormat ) } else null }.getOrNull() } private fun Bitmap.size(): IntegerSize = IntegerSize(width, height) } ================================================ FILE: feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/di/WeightResizeModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.weight_resize.di import android.graphics.Bitmap import com.t8rin.imagetoolbox.feature.weight_resize.data.AndroidWeightImageScaler import com.t8rin.imagetoolbox.feature.weight_resize.domain.WeightImageScaler import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface WeightResizeModule { @Singleton @Binds fun provideScaler( scaler: AndroidWeightImageScaler ): WeightImageScaler } ================================================ FILE: feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/domain/WeightImageScaler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.weight_resize.domain import com.t8rin.imagetoolbox.core.domain.image.ImageScaler import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode interface WeightImageScaler : ImageScaler { suspend fun scaleByMaxBytes( image: I, imageFormat: ImageFormat, imageScaleMode: ImageScaleMode, maxBytes: Long ): Pair? } ================================================ FILE: feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/WeightResizeContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.weight_resize.presentation import android.net.Uri import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormatGroup import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.Picker import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberImagePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.Clipboard import com.t8rin.imagetoolbox.core.ui.utils.helper.ImageUtils.restrict import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.buttons.PanModeButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ShareButton import com.t8rin.imagetoolbox.core.ui.widget.buttons.ZoomButton import com.t8rin.imagetoolbox.core.ui.widget.controls.SaveExifWidget import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.PresetSelector import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ScaleModeSelector import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeImagePickingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.OneTimeSaveLocationSelectionDialog import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.ImageContainer import com.t8rin.imagetoolbox.core.ui.widget.image.ImageCounter import com.t8rin.imagetoolbox.core.ui.widget.image.ImageNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.modifier.detectSwipes import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.sheets.PickImageFromUrisSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ProcessImagesPreferenceSheet import com.t8rin.imagetoolbox.core.ui.widget.sheets.ZoomModalSheet import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.core.ui.widget.text.TopAppBarTitle import com.t8rin.imagetoolbox.core.ui.widget.utils.AutoContentBasedColors import com.t8rin.imagetoolbox.feature.weight_resize.presentation.components.ImageFormatAlert import com.t8rin.imagetoolbox.feature.weight_resize.presentation.screenLogic.WeightResizeComponent @Composable fun WeightResizeContent( component: WeightResizeComponent ) { AutoContentBasedColors(component.bitmap) val imagePicker = rememberImagePicker { uris: List -> component.setUris( uris = uris ) } val pickImage = imagePicker::pickImage AutoFilePicker( onAutoPick = pickImage, isPickedAlready = !component.initialUris.isNullOrEmpty() ) var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.haveChanges) showExitDialog = true else component.onGoBack() } val saveBitmaps: (oneTimeSaveLocationUri: String?) -> Unit = { component.saveBitmaps( oneTimeSaveLocationUri = it ) } var showPickImageFromUrisSheet by rememberSaveable { mutableStateOf(false) } val isPortrait by isPortraitOrientationAsState() var showZoomSheet by rememberSaveable { mutableStateOf(false) } ZoomModalSheet( data = component.previewBitmap, visible = showZoomSheet, onDismiss = { showZoomSheet = false } ) AdaptiveLayoutScreen( shouldDisableBackHandler = !component.haveChanges, title = { TopAppBarTitle( title = stringResource(R.string.by_bytes_resize), input = component.bitmap, isLoading = component.isImageLoading, size = component.imageSize ) }, onGoBack = onBack, actions = {}, topAppBarPersistentActions = { if (component.bitmap == null) { TopAppBarEmoji() } ZoomButton( onClick = { showZoomSheet = true }, visible = component.bitmap != null, ) if (component.previewBitmap != null) { var editSheetData by remember { mutableStateOf(listOf()) } ShareButton( enabled = component.canSave, onShare = component::shareBitmaps, onCopy = { component.cacheCurrentImage(Clipboard::copy) }, onEdit = { component.cacheImages { editSheetData = it } } ) ProcessImagesPreferenceSheet( uris = editSheetData, visible = editSheetData.isNotEmpty(), onDismiss = { editSheetData = emptyList() }, onNavigate = component.onNavigate ) } }, imagePreview = { ImageContainer( modifier = Modifier .detectSwipes( onSwipeRight = component::selectLeftUri, onSwipeLeft = component::selectRightUri ), imageInside = isPortrait, showOriginal = false, previewBitmap = component.previewBitmap, originalBitmap = component.bitmap, isLoading = component.isImageLoading, shouldShowPreview = true ) }, controls = { ImageCounter( imageCount = component.uris?.size?.takeIf { it > 1 }, onRepick = { showPickImageFromUrisSheet = true } ) AnimatedContent( targetState = component.handMode, transitionSpec = { if (!targetState) { slideInVertically { it } + fadeIn() togetherWith slideOutVertically { -it } + fadeOut() } else { slideInVertically { -it } + fadeIn() togetherWith slideOutVertically { it } + fadeOut() } } ) { handMode -> if (handMode) { RoundedTextField( modifier = Modifier .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), enabled = component.bitmap != null, value = (component.maxBytes / 1024).toString() .takeIf { it != "0" } ?: "", onValueChange = { component.updateMaxBytes( it.restrict(1_000_000) ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number ), label = stringResource(R.string.max_bytes) ) } else { PresetSelector( value = component.presetSelected.takeIf { it > 0 }?.let { Preset.Percentage(it) } ?: Preset.None, includeTelegramOption = false, onValueChange = component::selectPreset, isBytesResize = true ) } } Spacer(Modifier.height(8.dp)) SaveExifWidget( imageFormat = component.imageFormat, checked = component.keepExif, onCheckedChange = component::setKeepExif ) AnimatedVisibility( visible = component.imageFormat.canChangeCompressionValue ) { Spacer(Modifier.height(8.dp)) } ImageFormatAlert( format = component.imageFormat, modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp) ) ImageFormatSelector( value = component.imageFormat, onValueChange = component::setImageFormat, entries = remember { ImageFormatGroup.entries .minus(ImageFormatGroup.Png) .plus( ImageFormatGroup.Custom( title = "PNG Lossless", formats = listOf( ImageFormat.Png.Lossless ) ) ) } ) Spacer(Modifier.height(8.dp)) ScaleModeSelector( value = component.imageScaleMode, onValueChange = component::setImageScaleMode ) }, buttons = { var showFolderSelectionDialog by rememberSaveable { mutableStateOf(false) } var showOneTimeImagePickingDialog by rememberSaveable { mutableStateOf(false) } BottomButtonsBlock( isNoData = component.uris.isNullOrEmpty(), onSecondaryButtonClick = pickImage, onPrimaryButtonClick = { saveBitmaps(null) }, onPrimaryButtonLongClick = { showFolderSelectionDialog = true }, isPrimaryButtonVisible = component.canSave, actions = { PanModeButton( selected = component.handMode, onClick = component::updateHandMode ) }, onSecondaryButtonLongClick = { showOneTimeImagePickingDialog = true } ) OneTimeSaveLocationSelectionDialog( visible = showFolderSelectionDialog, onDismiss = { showFolderSelectionDialog = false }, onSaveRequest = saveBitmaps, formatForFilenameSelection = component.getFormatForFilenameSelection() ) OneTimeImagePickingDialog( onDismiss = { showOneTimeImagePickingDialog = false }, picker = Picker.Multiple, imagePicker = imagePicker, visible = showOneTimeImagePickingDialog ) }, canShowScreenData = component.bitmap != null, noDataControls = { if (!component.isImageLoading) { ImageNotPickedWidget(onPickImage = pickImage) } } ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.uris?.size ?: 1, onCancelLoading = component::cancelSaving ) PickImageFromUrisSheet( visible = showPickImageFromUrisSheet, onDismiss = { showPickImageFromUrisSheet = false }, uris = component.uris, selectedUri = component.selectedUri, onUriPicked = component::updateSelectedUri, onUriRemoved = component::updateUrisSilently, columns = if (isPortrait) 2 else 4, ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) } ================================================ FILE: feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/components/ImageFormatAlert.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.weight_resize.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container @Composable internal fun ImageFormatAlert( format: ImageFormat, modifier: Modifier = Modifier ) { AnimatedVisibility(!format.canChangeCompressionValue) { Box( modifier = modifier .container( shape = ShapeDefaults.large, borderColor = MaterialTheme.colorScheme.onErrorContainer.copy( 0.4f ), color = MaterialTheme.colorScheme.errorContainer.copy( alpha = 0.7f ) ), contentAlignment = Alignment.Center ) { Text( text = stringResource(R.string.warning_bytes, format.title), fontSize = 12.sp, modifier = Modifier.padding(8.dp), textAlign = TextAlign.Center, fontWeight = FontWeight.SemiBold, lineHeight = 14.sp, color = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.5f) ) } } } ================================================ FILE: feature/weight-resize/src/main/java/com/t8rin/imagetoolbox/feature/weight_resize/presentation/screenLogic/WeightResizeComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.weight_resize.presentation.screenLogic import android.graphics.Bitmap import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ImageCompressor import com.t8rin.imagetoolbox.core.domain.image.ImageGetter import com.t8rin.imagetoolbox.core.domain.image.ImageShareProvider import com.t8rin.imagetoolbox.core.domain.image.model.ImageData import com.t8rin.imagetoolbox.core.domain.image.model.ImageFormat import com.t8rin.imagetoolbox.core.domain.image.model.ImageInfo import com.t8rin.imagetoolbox.core.domain.image.model.ImageScaleMode import com.t8rin.imagetoolbox.core.domain.image.model.Preset import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.FilenameCreator import com.t8rin.imagetoolbox.core.domain.saving.model.ImageSaveTarget import com.t8rin.imagetoolbox.core.domain.saving.model.SaveResult import com.t8rin.imagetoolbox.core.domain.saving.model.onSuccess import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.leftFrom import com.t8rin.imagetoolbox.core.domain.utils.ListUtils.rightFrom import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.navigation.Screen import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.weight_resize.domain.WeightImageScaler import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class WeightResizeComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, @Assisted val onNavigate: (Screen) -> Unit, private val fileController: FileController, private val filenameCreator: FilenameCreator, private val imageGetter: ImageGetter, private val imageCompressor: ImageCompressor, private val imageScaler: WeightImageScaler, private val shareProvider: ImageShareProvider, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) } } private val _imageScaleMode: MutableState = mutableStateOf(ImageScaleMode.Default) val imageScaleMode by _imageScaleMode private val _imageSize: MutableState = mutableLongStateOf(0L) val imageSize by _imageSize private val _canSave = mutableStateOf(false) val canSave by _canSave private val _presetSelected: MutableState = mutableIntStateOf(-1) val presetSelected by _presetSelected private val _handMode = mutableStateOf(true) val handMode by _handMode private val _uris = mutableStateOf?>(null) val uris by _uris private val _bitmap: MutableState = mutableStateOf(null) val bitmap: Bitmap? by _bitmap private val _keepExif = mutableStateOf(false) val keepExif by _keepExif private val _isSaving: MutableState = mutableStateOf(false) val isSaving: Boolean by _isSaving private val _previewBitmap: MutableState = mutableStateOf(null) val previewBitmap: Bitmap? by _previewBitmap private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _selectedUri: MutableState = mutableStateOf(null) val selectedUri by _selectedUri private val _maxBytes: MutableState = mutableLongStateOf(0L) val maxBytes by _maxBytes private val _imageFormat = mutableStateOf(ImageFormat.Default) val imageFormat by _imageFormat fun setImageFormat(imageFormat: ImageFormat) { if (_imageFormat.value != imageFormat) { _imageFormat.value = imageFormat componentScope.launch { _bitmap.value?.let { _isImageLoading.value = true _imageSize.value = imageCompressor.calculateImageSize( image = it, imageInfo = ImageInfo(imageFormat = imageFormat) ) _isImageLoading.value = false } } registerChanges() } } fun setUris( uris: List? ) { _uris.value = null _uris.value = uris _selectedUri.value = uris?.firstOrNull() if (uris != null) { _isImageLoading.value = true imageGetter.getImageAsync( uri = uris[0].toString(), originalSize = true, onGetImage = ::setImageData, onFailure = { _isImageLoading.value = false AppToastHost.showFailureToast(it) } ) } } fun updateUrisSilently(removedUri: Uri) { componentScope.launch { _uris.value = uris if (_selectedUri.value == removedUri) { val index = uris?.indexOf(removedUri) ?: -1 if (index == 0) { uris?.getOrNull(1)?.let { _selectedUri.value = it _bitmap.value = imageGetter.getImage(it.toString())?.image } } else { uris?.getOrNull(index - 1)?.let { _selectedUri.value = it _bitmap.value = imageGetter.getImage(it.toString())?.image } } } val u = _uris.value?.toMutableList()?.apply { remove(removedUri) } _uris.value = u } } private fun updateBitmap(bitmap: Bitmap?) { componentScope.launch { _isImageLoading.value = true _bitmap.value = bitmap _previewBitmap.value = imageScaler.scaleUntilCanShow(bitmap) _isImageLoading.value = false } } fun setKeepExif(boolean: Boolean) { _keepExif.value = boolean registerChanges() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun saveBitmaps( oneTimeSaveLocationUri: String? ) { savingJob = trackProgress { _isSaving.value = true val results = mutableListOf() _done.value = 0 uris?.forEach { uri -> runSuspendCatching { imageGetter.getImage(uri.toString()) }.getOrNull()?.image?.let { bitmap -> runSuspendCatching { if (handMode) { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = maxBytes, imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } else { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = (fileController.getSize(uri.toString()) ?: 0) .times(presetSelected / 100f) .toLong(), imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } }.getOrNull()?.let { (data, imageInfo) -> results.add( fileController.save( ImageSaveTarget( imageInfo = imageInfo, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = data ), keepOriginalMetadata = keepExif, oneTimeSaveLocationUri = oneTimeSaveLocationUri ) ) } } ?: results.add( SaveResult.Error.Exception(Throwable()) ) _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } parseSaveResults(results.onSuccess(::registerSave)) _isSaving.value = false } } fun updateSelectedUri( uri: Uri ) { runCatching { componentScope.launch { updateBitmap( imageGetter.getImage( uri = uri.toString(), originalSize = false )?.image ) _selectedUri.value = uri } }.onFailure(AppToastHost::showFailureToast) } private fun updateCanSave() { _canSave.value = _bitmap.value != null && (_maxBytes.value != 0L && _handMode.value || !_handMode.value && _presetSelected.value != -1) registerChanges() } fun updateMaxBytes(newBytes: String) { val b = newBytes.toLongOrNull() ?: 0 _maxBytes.value = b * 1024 updateCanSave() } fun selectPreset(preset: Preset) { preset.value()?.let { _presetSelected.value = it } updateCanSave() } fun updateHandMode() { _handMode.value = !_handMode.value updateCanSave() } fun shareBitmaps() { savingJob = trackProgress { _isSaving.value = true shareProvider.shareImages( uris = uris?.map { it.toString() } ?: emptyList(), imageLoader = { uri -> imageGetter.getImage(uri)?.image?.let { bitmap -> if (handMode) { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = maxBytes, imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } else { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = (fileController.getSize(uri) ?: 0) .times(presetSelected / 100f) .toLong(), imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } }?.let { (data, imageInfo) -> imageGetter.getImage(data)!! to imageInfo } }, onProgressChange = { if (it == -1) { AppToastHost.showConfetti() _isSaving.value = false _done.value = 0 } else { _done.value = it } updateProgress( done = done, total = uris.orEmpty().size ) } ) } } private var job: Job? by smartJob { _isImageLoading.update { false } } private fun setImageData(imageData: ImageData) { job = componentScope.launch { _isImageLoading.value = true imageScaler.scaleUntilCanShow(imageData.image)?.let { _bitmap.value = imageData.image _previewBitmap.value = it _imageFormat.value = imageData.imageInfo.imageFormat _imageSize.value = imageCompressor.calculateImageSize( image = imageData.image, imageInfo = ImageInfo( width = imageData.image.width, height = imageData.image.height, imageFormat = imageFormat ) ) } _isImageLoading.value = false } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun setImageScaleMode(imageScaleMode: ImageScaleMode) { _imageScaleMode.update { imageScaleMode } updateCanSave() } fun cacheCurrentImage(onComplete: (Uri) -> Unit) { savingJob = trackProgress { _isSaving.value = true selectedUri?.toString()?.let { uri -> imageGetter.getImage(uri)?.image?.let { bitmap -> if (handMode) { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = maxBytes, imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } else { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = (fileController.getSize(uri) ?: 0) .times(presetSelected / 100f) .toLong(), imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } }?.let { scaled -> scaled.first to scaled.second.copy(imageFormat = imageFormat) }?.let { (image, imageInfo) -> shareProvider.cacheByteArray( byteArray = image, filename = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = imageInfo, originalUri = uri, sequenceNumber = _done.value + 1, data = image ) ) )?.let { uri -> onComplete(uri.toUri()) } } } _isSaving.value = false } } fun cacheImages( onComplete: (List) -> Unit ) { savingJob = trackProgress { _isSaving.value = true _done.value = 0 val list = mutableListOf() uris?.forEach { uri -> imageGetter.getImage(uri.toString())?.image?.let { bitmap -> if (handMode) { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = maxBytes, imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } else { imageScaler.scaleByMaxBytes( image = bitmap, maxBytes = (fileController.getSize(uri.toString()) ?: 0) .times(presetSelected / 100f) .toLong(), imageFormat = imageFormat, imageScaleMode = imageScaleMode ) } }?.let { scaled -> scaled.first to scaled.second.copy(imageFormat = imageFormat) }?.let { (image, imageInfo) -> shareProvider.cacheByteArray( byteArray = image, filename = filenameCreator.constructImageFilename( ImageSaveTarget( imageInfo = imageInfo, originalUri = uri.toString(), sequenceNumber = _done.value + 1, data = image ) ) )?.let { uri -> list.add(uri.toUri()) } } _done.value += 1 updateProgress( done = done, total = uris.orEmpty().size ) } onComplete(list) _isSaving.value = false } } fun selectLeftUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.leftFrom(it) } ?.let(::updateSelectedUri) } fun selectRightUri() { uris ?.indexOf(selectedUri ?: Uri.EMPTY) ?.takeIf { it >= 0 } ?.let { uris?.rightFrom(it) } ?.let(::updateSelectedUri) } fun getFormatForFilenameSelection(): ImageFormat? = if (uris?.size == 1) imageFormat else null @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, onNavigate: (Screen) -> Unit, ): WeightResizeComponent } } ================================================ FILE: feature/zip/.gitignore ================================================ /build ================================================ FILE: feature/zip/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.feature) alias(libs.plugins.image.toolbox.hilt) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.imagetoolbox.feature.zip" ================================================ FILE: feature/zip/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/data/AndroidZipManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.zip.data import android.content.Context import androidx.core.net.toUri import com.t8rin.imagetoolbox.core.data.saving.io.UriReadable import com.t8rin.imagetoolbox.core.data.utils.outputStream import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.utils.createZip import com.t8rin.imagetoolbox.core.utils.filename import com.t8rin.imagetoolbox.core.utils.putEntry import com.t8rin.imagetoolbox.feature.zip.domain.ZipManager import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext import javax.inject.Inject internal class AndroidZipManager @Inject constructor( @ApplicationContext private val context: Context, private val shareProvider: ShareProvider, dispatchersHolder: DispatchersHolder ) : DispatchersHolder by dispatchersHolder, ZipManager { override suspend fun zip( files: List, onProgress: () -> Unit ): String = withContext(defaultDispatcher) { shareProvider.cacheData( writeData = { writeable -> writeable.outputStream().createZip { output -> for (file in files) { output.putEntry( name = file.toUri().filename(context) ?: continue, input = UriReadable(file.toUri(), context).stream ) onProgress() } } }, filename = files.firstOrNull()?.toUri()?.filename() ?: "temp.zip" ) ?: throw IllegalArgumentException("Cached to null file") } } ================================================ FILE: feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/di/ZipModule.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.zip.di import com.t8rin.imagetoolbox.feature.zip.data.AndroidZipManager import com.t8rin.imagetoolbox.feature.zip.domain.ZipManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal interface ZipModule { @Singleton @Binds fun provideZipManager( manager: AndroidZipManager ): ZipManager } ================================================ FILE: feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/domain/ZipManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.zip.domain interface ZipManager { suspend fun zip( files: List, onProgress: () -> Unit ): String } ================================================ FILE: feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/ZipContent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.zip.presentation import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FileOpen import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.widget.AdaptiveLayoutScreen import com.t8rin.imagetoolbox.core.ui.widget.buttons.BottomButtonsBlock import com.t8rin.imagetoolbox.core.ui.widget.dialogs.ExitWithoutSavingDialog import com.t8rin.imagetoolbox.core.ui.widget.dialogs.LoadingDialog import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedChip import com.t8rin.imagetoolbox.core.ui.widget.image.AutoFilePicker import com.t8rin.imagetoolbox.core.ui.widget.image.FileNotPickedWidget import com.t8rin.imagetoolbox.core.ui.widget.other.TopAppBarEmoji import com.t8rin.imagetoolbox.core.ui.widget.text.marquee import com.t8rin.imagetoolbox.feature.zip.presentation.components.ZipControls import com.t8rin.imagetoolbox.feature.zip.presentation.screenLogic.ZipComponent @Composable fun ZipContent( component: ZipComponent ) { var showExitDialog by rememberSaveable { mutableStateOf(false) } val onBack = { if (component.uris.isNotEmpty() && component.compressedArchiveUri != null) { showExitDialog = true } else component.onGoBack() } val filePicker = rememberFilePicker(onSuccess = component::setUris) AutoFilePicker( onAutoPick = filePicker::pickFile, isPickedAlready = !component.initialUris.isNullOrEmpty() ) AdaptiveLayoutScreen( shouldDisableBackHandler = !(component.uris.isNotEmpty() && component.compressedArchiveUri != null), title = { Text( text = stringResource(R.string.zip), modifier = Modifier.marquee() ) }, topAppBarPersistentActions = { TopAppBarEmoji() }, onGoBack = onBack, actions = {}, imagePreview = {}, showImagePreviewAsStickyHeader = false, placeImagePreview = false, addHorizontalCutoutPaddingIfNoPreview = false, noDataControls = { FileNotPickedWidget(onPickFile = filePicker::pickFile) }, controls = { ZipControls( component = component, lazyListState = it ) }, buttons = { BottomButtonsBlock( isNoData = component.uris.isEmpty(), onSecondaryButtonClick = filePicker::pickFile, secondaryButtonIcon = Icons.Rounded.FileOpen, secondaryButtonText = stringResource(R.string.pick_file), isPrimaryButtonVisible = component.uris.isNotEmpty(), onPrimaryButtonClick = component::startCompression, actions = { EnhancedChip( selected = true, onClick = null, selectedColor = MaterialTheme.colorScheme.secondaryContainer, modifier = Modifier.padding(8.dp) ) { Text(component.uris.size.toString()) } } ) }, canShowScreenData = component.uris.isNotEmpty() ) ExitWithoutSavingDialog( onExit = component.onGoBack, onDismiss = { showExitDialog = false }, visible = showExitDialog ) LoadingDialog( visible = component.isSaving, done = component.done, left = component.left, onCancelLoading = component::cancelSaving ) } ================================================ FILE: feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/components/ZipControls.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.zip.presentation.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CheckCircle import androidx.compose.material.icons.rounded.FileDownload import androidx.compose.material.icons.rounded.Share import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.imagetoolbox.core.domain.model.MimeType import com.t8rin.imagetoolbox.core.domain.utils.timestamp import com.t8rin.imagetoolbox.core.resources.R import com.t8rin.imagetoolbox.core.settings.presentation.provider.LocalSettingsState import com.t8rin.imagetoolbox.core.ui.theme.Green import com.t8rin.imagetoolbox.core.ui.theme.outlineVariant import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFileCreator import com.t8rin.imagetoolbox.core.ui.utils.content_pickers.rememberFilePicker import com.t8rin.imagetoolbox.core.ui.utils.helper.isPortraitOrientationAsState import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedButton import com.t8rin.imagetoolbox.core.ui.widget.image.UrisPreview import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults import com.t8rin.imagetoolbox.core.ui.widget.modifier.container import com.t8rin.imagetoolbox.core.ui.widget.text.AutoSizeText import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField import com.t8rin.imagetoolbox.feature.zip.presentation.screenLogic.ZipComponent @Composable internal fun ColumnScope.ZipControls( component: ZipComponent, lazyListState: LazyListState ) { val isPortrait by isPortraitOrientationAsState() val settingsState = LocalSettingsState.current val saveLauncher = rememberFileCreator( mimeType = MimeType.Zip, onSuccess = component::saveResultTo ) val additionalFilePicker = rememberFilePicker(onSuccess = component::addUris) AnimatedVisibility(visible = component.compressedArchiveUri != null) { LaunchedEffect(lazyListState) { lazyListState.animateScrollToItem(0) } Column( modifier = Modifier .fillMaxWidth() .padding(top = 24.dp) .container( shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme .colorScheme .surfaceContainerHighest, resultPadding = 0.dp ) .padding(16.dp) ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = Icons.Rounded.CheckCircle, contentDescription = null, tint = Green, modifier = Modifier .size(36.dp) .background( color = MaterialTheme.colorScheme.surface, shape = ShapeDefaults.circle ) .border( width = settingsState.borderWidth, color = MaterialTheme.colorScheme.outlineVariant(), shape = ShapeDefaults.circle ) .padding(4.dp) ) Spacer(modifier = Modifier.width(16.dp)) Text( stringResource(R.string.file_proceed), fontSize = 17.sp, fontWeight = FontWeight.Medium ) } Text( text = stringResource(R.string.store_file_desc), fontSize = 13.sp, color = LocalContentColor.current.copy(alpha = 0.7f), lineHeight = 14.sp, modifier = Modifier.padding(vertical = 16.dp) ) var name by rememberSaveable(component.compressedArchiveUri, component.uris) { val count = component.uris.size.let { if (it > 1) "($it)" else "" } mutableStateOf("ZIP${count}_${timestamp()}.zip") } RoundedTextField( modifier = Modifier .padding(top = 8.dp) .container( shape = MaterialTheme.shapes.large, resultPadding = 8.dp ), value = name, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), singleLine = false, onValueChange = { name = it }, label = { Text(stringResource(R.string.filename)) } ) Row( modifier = Modifier .padding(top = 24.dp) .fillMaxWidth() ) { EnhancedButton( onClick = { saveLauncher.make(name) }, modifier = Modifier .padding(end = 8.dp) .fillMaxWidth(0.5f) .height(50.dp), containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Icon( imageVector = Icons.Rounded.FileDownload, contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) AutoSizeText( text = stringResource(id = R.string.save), maxLines = 1 ) } } EnhancedButton( onClick = component::shareFile, modifier = Modifier .padding(start = 8.dp) .fillMaxWidth() .height(50.dp), containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Icon( imageVector = Icons.Rounded.Share, contentDescription = stringResource(R.string.share) ) Spacer(modifier = Modifier.width(8.dp)) AutoSizeText( text = stringResource(id = R.string.share), maxLines = 1 ) } } } } } Spacer(modifier = Modifier.height(24.dp)) UrisPreview( uris = component.uris, isPortrait = isPortrait, onRemoveUri = component::removeUri, onAddUris = additionalFilePicker::pickFile ) } ================================================ FILE: feature/zip/src/main/java/com/t8rin/imagetoolbox/feature/zip/presentation/screenLogic/ZipComponent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.imagetoolbox.feature.zip.presentation.screenLogic import android.net.Uri import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import com.arkivanov.decompose.ComponentContext import com.t8rin.imagetoolbox.core.domain.coroutines.DispatchersHolder import com.t8rin.imagetoolbox.core.domain.image.ShareProvider import com.t8rin.imagetoolbox.core.domain.saving.FileController import com.t8rin.imagetoolbox.core.domain.saving.updateProgress import com.t8rin.imagetoolbox.core.domain.utils.runSuspendCatching import com.t8rin.imagetoolbox.core.domain.utils.smartJob import com.t8rin.imagetoolbox.core.ui.utils.BaseComponent import com.t8rin.imagetoolbox.core.ui.utils.helper.AppToastHost import com.t8rin.imagetoolbox.core.ui.utils.state.update import com.t8rin.imagetoolbox.feature.zip.domain.ZipManager import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Job class ZipComponent @AssistedInject internal constructor( @Assisted componentContext: ComponentContext, @Assisted val initialUris: List?, @Assisted val onGoBack: () -> Unit, private val zipManager: ZipManager, private val shareProvider: ShareProvider, private val fileController: FileController, dispatchersHolder: DispatchersHolder ) : BaseComponent(dispatchersHolder, componentContext) { init { debounce { initialUris?.let(::setUris) } } private val _uris = mutableStateOf>(emptyList()) val uris by _uris private val _compressedArchiveUri = mutableStateOf(null) val compressedArchiveUri by _compressedArchiveUri private val _isSaving: MutableState = mutableStateOf(false) val isSaving by _isSaving private val _done: MutableState = mutableIntStateOf(0) val done by _done private val _left: MutableState = mutableIntStateOf(-1) val left by _left fun setUris(newUris: List) { _uris.update { newUris.distinct() } resetCalculatedData() } private var savingJob: Job? by smartJob { _isSaving.update { false } } fun startCompression() { savingJob = trackProgress { _isSaving.value = true if (uris.isEmpty()) { return@trackProgress } runSuspendCatching { _done.update { 0 } _left.update { uris.size } _compressedArchiveUri.value = zipManager.zip( files = uris.map { it.toString() }, onProgress = { _done.update { it + 1 } updateProgress( done = done, total = left ) } ) }.onFailure(AppToastHost::showFailureToast) _isSaving.value = false } } private fun resetCalculatedData() { _compressedArchiveUri.value = null } fun saveResultTo(uri: Uri) { savingJob = trackProgress { _isSaving.value = true _compressedArchiveUri.value?.let { byteArray -> fileController.transferBytes( fromUri = byteArray, toUri = uri.toString(), ).also(::parseFileSaveResult).onSuccess(::registerSave) } _isSaving.value = false } } fun shareFile() { compressedArchiveUri?.let { uri -> savingJob = trackProgress { _done.update { 0 } _left.update { 0 } _isSaving.value = true shareProvider.shareUri( uri = uri, onComplete = { _isSaving.value = false AppToastHost.showConfetti() } ) } } } fun cancelSaving() { savingJob?.cancel() savingJob = null _isSaving.value = false } fun removeUri(uri: Uri) { _uris.update { it - uri } } fun addUris(list: List) = setUris(uris + list) @AssistedFactory fun interface Factory { operator fun invoke( componentContext: ComponentContext, initialUris: List?, onGoBack: () -> Unit, ): ZipComponent } } ================================================ FILE: gradle/libs.versions.toml ================================================ [versions] androidMinSdk = "23" androidTargetSdk = "36" androidCompileSdk = "36" androidCompileSdkExtension = "19" versionName = "3.8.1-beta01" versionCode = "227" jvmTarget = "21" imageToolboxLibs = "6.2.0" trickle = "1.6.1" evaluator = "1.0.0" quickie = "1.18.1" fadingEdges = "1.0.4" logger = "1.0.2" avifCoder = "2.2.0" avifCoderCoil = "2.2.0" aire = "0.18.1" jxlCoder = "2.6.0" jxlCoderCoil = "2.6.0" jpegliCoder = "1.0.2" tesseract = "4.9.0" material3 = "1.5.0-alpha17" composeVersion = "1.11.0-rc01" materialIconsExtended = "1.7.8" dataStore = "1.3.0-alpha07" appUpdateKtx = "2.1.0" appUpdate = "2.1.0" shadowGadgets = "2.4.0" konfettiCompose = "2.0.5" shadowsPlus = "1.0.4" google-segmentationSelfie = "16.0.0-beta6" google-subjectSegmentation = "16.0.0-beta1" detekt = "1.23.8" detektCompose = "0.5.7" decompose = "3.5.0" kotlin = "2.3.20" agp = "9.1.1" hilt = "2.59.2" gms = "4.4.4" ktor = "3.4.2" coil = "3.4.0" appCompat = "1.7.1" androidxCore = "1.18.0" desugaring = "2.1.5" activityCompose = "1.13.0" kotlinxCollectionsImmutable = "0.4.0" scrollbar = "2.2.0" reorderable = "3.0.0" reviewKtx = "2.0.2" splashScreen = "1.2.0" espresso = "3.7.0" ksp = "2.3.6" androidx-test-ext-junit = "1.3.0" documentfile = "1.1.0" uiautomator = "2.3.0" androidxMacroBenchmark = "1.5.0-alpha05" material = "1.14.0-beta01" jsoup = "1.22.1" androidliquidglass = "1.0.6" capsule = "2.1.3" squircle-shape = "5.2.0" mlkitDocumentScanner = "16.0.0" moshi = "1.15.2" bouncycastle = "1.84" pdfviewer = "1.0.0-alpha17" fragmentCompose = "1.8.9" aboutlibraries = "14.0.1" flinger = "2.0.2" zxingCore = "3.5.4" pdfbox = "2.0.27.0" onnx = "1.24.3" opencv = "4.13.0" tiffdecoder = "1.0.5" materialKolor = "5.0.0-alpha07" paletteKtx = "1.0.0" kotlinxSerializationJson = "1.11.0" lifecycle = "2.10.0" firebaseBom = "34.12.0" firebaseCrashlyticsGradle = "3.0.7" junit = "4.13.2" [libraries] firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } firebase-analytics = { module = "com.google.firebase:firebase-analytics" } junit = { module = "junit:junit", version.ref = "junit" } lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } materialKolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" } androidx-palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "paletteKtx" } opencv = { module = "org.opencv:opencv", version.ref = "opencv" } onnx-runtime = { module = "com.microsoft.onnxruntime:onnxruntime-android", version.ref = "onnx" } pdfbox = { module = "com.tom-roush:pdfbox-android", version.ref = "pdfbox" } zxing-core = { module = "com.google.zxing:core", version.ref = "zxingCore" } flinger = { module = "com.github.iamjosephmj.flinger:flinger", version.ref = "flinger" } squircle-shape = { group = "com.stoyanvuchev", name = "squircle-shape-android", version.ref = "squircle-shape" } capsule = { module = "io.github.kyant0:capsule", version.ref = "capsule" } androidliquidglass = { module = "io.github.kyant0:backdrop", version.ref = "androidliquidglass" } androidx-fragment-compose = { module = "androidx.fragment:fragment-compose", version.ref = "fragmentCompose" } androidx-pdfviewer-fragment = { module = "androidx.pdf:pdf-viewer-fragment", version.ref = "pdfviewer" } evaluator = { module = "com.github.T8RIN:KotlinEvaluator", version.ref = "evaluator" } aboutlibraries-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlibraries" } moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", version.ref = "moshi" } symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } trickle = { module = "com.github.T8RIN:Trickle", version.ref = "trickle" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } mlkit-document-scanner = { module = "com.google.android.gms:play-services-mlkit-document-scanner", version.ref = "mlkitDocumentScanner" } quickie-bundled = { module = "com.github.T8RIN.QuickieExtended:quickie-bundled", version.ref = "quickie" } quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickie" } logger = { module = "com.github.T8RIN:Logger", version.ref = "logger" } toolbox-gpuimage = { module = "com.github.T8RIN.ImageToolboxLibs:gpuimage", version.ref = "imageToolboxLibs" } toolbox-androidwm = { module = "com.github.T8RIN.ImageToolboxLibs:androidwm", version.ref = "imageToolboxLibs" } toolbox-gifConverter = { module = "com.github.T8RIN.ImageToolboxLibs:gif-converter", version.ref = "imageToolboxLibs" } toolbox-apng = { module = "com.github.T8RIN.ImageToolboxLibs:apng", version.ref = "imageToolboxLibs" } toolbox-svg = { module = "com.github.T8RIN.ImageToolboxLibs:svg", version.ref = "imageToolboxLibs" } toolbox-jp2decoder = { module = "com.github.T8RIN.ImageToolboxLibs:jp2decoder", version.ref = "imageToolboxLibs" } toolbox-qoiCoder = { module = "com.github.T8RIN.ImageToolboxLibs:qoi-coder", version.ref = "imageToolboxLibs" } toolbox-awebp = { module = "com.github.T8RIN.ImageToolboxLibs:awebp", version.ref = "imageToolboxLibs" } toolbox-psd = { module = "com.github.T8RIN.ImageToolboxLibs:psd", version.ref = "imageToolboxLibs" } toolbox-djvuCoder = { module = "com.github.T8RIN.ImageToolboxLibs:djvu-coder", version.ref = "imageToolboxLibs" } toolbox-fastNoise = { module = "com.github.T8RIN.ImageToolboxLibs:fast-noise", version.ref = "imageToolboxLibs" } toolbox-histogram = { module = "com.github.T8RIN.ImageToolboxLibs:histogram", version.ref = "imageToolboxLibs" } toolbox-uCrop = { module = "com.github.T8RIN.ImageToolboxLibs:ucrop", version.ref = "imageToolboxLibs" } toolbox-exif = { module = "com.github.T8RIN.ImageToolboxLibs:exif", version.ref = "imageToolboxLibs" } toolbox-jhlabs = { module = "com.github.T8RIN.ImageToolboxLibs:jhlabs", version.ref = "imageToolboxLibs" } tiffdecoder = { module = "com.github.T8RIN:TiffDecoder", version.ref = "tiffdecoder" } aire = { module = "com.github.awxkee:aire", version.ref = "aire" } jpegli-coder = { module = "com.github.awxkee:jpegli-coder", version.ref = "jpegliCoder" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics", version.ref = "composeVersion" } app-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "appUpdateKtx" } app-update = { module = "com.google.android.play:app-update", version.ref = "appUpdate" } avif-coder = { module = "io.github.awxkee:avif-coder", version.ref = "avifCoder" } avif-coder-coil = { module = "io.github.awxkee:avif-coder-coil", version.ref = "avifCoderCoil" } shadowGadgets = { module = "com.github.zed-alpha.shadow-gadgets:compose", version.ref = "shadowGadgets" } datastore-preferences-android = { module = "androidx.datastore:datastore-preferences-android", version.ref = "dataStore" } datastore-core-android = { module = "androidx.datastore:datastore-core-android", version.ref = "dataStore" } fadingEdges = { module = "com.github.t8rin:ComposeFadingEdges", version.ref = "fadingEdges" } konfetti-compose = { module = "nl.dionsegijn:konfetti-compose", version.ref = "konfettiCompose" } decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" } decomposeExtensions = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" } shadowsPlus = { module = "com.github.GIGAMOLE:ComposeShadowsPlus", version.ref = "shadowsPlus" } ktor = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } ktor-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } coilNetwork = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } coilCompose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coilGif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } coilSvg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } jxl-coder-coil = { module = "io.github.awxkee:jxl-coder-coil", version.ref = "jxlCoderCoil" } jxl-coder = { module = "io.github.awxkee:jxl-coder", version.ref = "jxlCoder" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } appCompat = { module = "androidx.appcompat:appcompat", version.ref = "appCompat" } androidxCore = { module = "androidx.core:core-ktx", version.ref = "androidxCore" } scrollbar = { module = "com.github.nanihadesuka:LazyColumnScrollbar", version.ref = "scrollbar" } mlkit-segmentation-selfie = { module = "com.google.mlkit:segmentation-selfie", version.ref = "google-segmentationSelfie" } mlkit-subject-segmentation = { module = "com.google.android.gms:play-services-mlkit-subject-segmentation", version.ref = "google-subjectSegmentation" } reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" } review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "reviewKtx" } splashScreen = { module = "androidx.core:core-splashscreen", version.ref = "splashScreen" } activityCompose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" } benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" } tesseract = { module = "com.github.adaptech-cz.Tesseract4Android:tesseract4android-openmp", version.ref = "tesseract" } bouncycastle-pkix = { module = "org.bouncycastle:bcpkix-jdk15to18", version.ref = "bouncycastle" } bouncycastle-provider = { module = "org.bouncycastle:bcprov-jdk15to18", version.ref = "bouncycastle" } firebase-crashlytics-gradle = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsGradle" } baselineprofile-gradle = { group = "androidx.benchmark", name = "benchmark-baseline-profile-gradle-plugin", version.ref = "androidxMacroBenchmark" } kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } hilt-gradle = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "hilt" } gms-gradle = { module = "com.google.gms:google-services", version.ref = "gms" } kotlinx-serialization-gradle = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } ksp-gradle = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } agp-gradle = { module = "com.android.tools.build:gradle", version.ref = "agp" } detekt-gradle = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } aboutlibraries-gradle = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlibraries" } compose-compiler-gradle = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } # Used in convention plugins dagger-hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } dagger-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } detekt-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" } window-sizeclass = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "material3" } icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } androidx-material = { module = "androidx.compose.material:material", version.ref = "composeVersion" } androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } compose-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "composeVersion" } desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugaring" } [plugins] image-toolbox-library = { id = "image.toolbox.library", version = "unspecified" } image-toolbox-hilt = { id = "image.toolbox.hilt", version = "unspecified" } image-toolbox-feature = { id = "image.toolbox.feature", version = "unspecified" } image-toolbox-compose = { id = "image.toolbox.compose", version = "unspecified" } image-toolbox-application = { id = "image.toolbox.application", version = "unspecified" } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ # # ImageToolbox is an image editor for android # Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # # 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. # # You should have received a copy of the Apache License # along with this program. If not, see . # #Fri Mar 28 22:24:32 MSK 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # # ImageToolbox is an image editor for android # Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # # 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. # # You should have received a copy of the Apache License # along with this program. If not, see . # org.gradle.jvmargs=-Xmx8g android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true android.nonFinalResIds=true org.gradle.parallel=true kotlin.daemon.jvmargs=-Xmx16g org.gradle.configuration-cache=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or 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 UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$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 "$*" } die () { echo echo "$*" echo exit 1 } # 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 ;; 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 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: lib/ascii/.gitignore ================================================ /build ================================================ FILE: lib/ascii/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) } android.namespace = "com.t8rin.ascii" ================================================ FILE: lib/ascii/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/ascii/src/main/java/com/t8rin/ascii/ASCIIConverter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.ascii import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Typeface import androidx.core.graphics.alpha import androidx.core.graphics.applyCanvas import androidx.core.graphics.blue import androidx.core.graphics.createBitmap import androidx.core.graphics.get import androidx.core.graphics.green import androidx.core.graphics.red import androidx.core.graphics.scale import kotlinx.coroutines.coroutineScope import kotlin.math.min import kotlin.math.roundToInt typealias GradientMap = Map /** * Convert Bitmap into its ASCII equivalent [Bitmap] or [String]. */ class ASCIIConverter( private val fontSize: Float = 18.0f, private val reverseLuma: Boolean = false, private val mapper: AsciiMapper = AsciiMapper() ) { private var columns: Int = 0 suspend fun convertToAscii( bitmap: Bitmap, ): String = convert(bitmap) { grid -> grid.rotate90AndMirror().joinToString("\n") { row -> row.joinToString(" ") { it.luma()?.let(mapper::mapToAscii) ?: " " } } } suspend fun convertToAsciiBitmap( bitmap: Bitmap, typeface: Typeface = Typeface.DEFAULT, backgroundColor: Int = Color.TRANSPARENT, isGrayscale: Boolean = false, ): Bitmap = convert(bitmap) { grid -> prepareCanvas( bitmap = bitmap, backgroundColor = backgroundColor, typeface = typeface ) { paint -> grid.forEachIndexed { row, blocks -> blocks.forEachIndexed { col, color -> val luma = color.luma() if (isGrayscale && luma != null) { paint.alpha = (luma * 255.0f).toInt() paint.color = Color.GRAY } else { paint.color = color } drawText( luma?.let(mapper::mapToAscii) ?: " ", row * fontSize, col * fontSize, paint ) } } } } private fun Grid.transpose(): Grid = first().indices.map { y -> indices.map { x -> this[x][y] } } private fun Grid.rotate90AndMirror(): Grid = first().indices.map { x -> indices.map { y -> this[y][x] }.reversed() }.map { it.reversed() } private inline fun prepareCanvas( bitmap: Bitmap, backgroundColor: Int, typeface: Typeface, action: Canvas.(Paint) -> Unit ): Bitmap = createBitmap(bitmap.width, bitmap.height).applyCanvas { if (backgroundColor != Color.TRANSPARENT) drawColor(backgroundColor) action( Paint().apply { setTypeface(typeface) textSize = fontSize } ) } private suspend inline fun convert( bitmap: Bitmap, crossinline action: suspend (Grid) -> T ) = coroutineScope { checkColumns(bitmap) action(bitmap.resize().toGrid()) } private fun checkColumns(bitmap: Bitmap) { columns = bitmap.toColumns() require(columns >= 5) { "Columns count is very small. Font size needs to be reduced" } } private fun Bitmap.toColumns(): Int = if (columns < 5) (width / fontSize).roundToInt() else columns private fun Bitmap.toGrid(): Grid { require(width > 0 && height > 0) { "Width and height must be positive values." } return List(width) { x -> List(height) { y -> this[x, y] } } } private fun Bitmap.resize(): Bitmap { if (columns <= 1) return this val min = min(height, width) val scaleFactor = if (columns > min) min else columns val ratio = scaleFactor.toFloat() / width.toFloat() return copy(Bitmap.Config.ARGB_8888, false).scale( width = scaleFactor, height = (ratio * height).toInt() ) } private fun Int.luma(): Float? { if (alpha <= 0) return null val luminance = (0.2126 * (red / 255f)) .plus(0.7152 * (green / 255f)) .plus(0.0722 * (blue / 255f)) return (if (reverseLuma) 1 - luminance else luminance).toFloat() } } fun interface AsciiMapper { fun mapToAscii(luma: Float): String } @JvmInline value class Gradient(val value: String) { fun toMap(): GradientMap { val length = value.length return value.mapIndexed { index, char -> char.toString() to (1.0f - index.toFloat() / (length - 1).coerceAtLeast(1)) }.toMap() } companion object { val NORMAL = Gradient(".:-=+*#%@") val NORMAL2 = Gradient(" `.,-~+<>o=*%X@") val ARROWS = Gradient("↖←↙↓↘→↗↑") val OLD = Gradient("░▒▓█") val EXTENDED_HIGH = Gradient(".:-~=+*^><)(][}{#%@") val MINIMAL = Gradient(".-+#") val MATH = Gradient("π√∞≈≠=÷×-+") val NUMERICAL = Gradient("7132546980") } } fun String.toGradientMap(): GradientMap = Gradient(this).toMap() fun GradientMap.toMapper(): AsciiMapper = AsciiMapperImpl(map = this) fun Gradient.toMapper(): AsciiMapper = toMap().toMapper() fun AsciiMapper(map: GradientMap): AsciiMapper = map.toMapper() fun AsciiMapper(gradient: Gradient = Gradient.NORMAL): AsciiMapper = gradient.toMapper() private class AsciiMapperImpl(map: GradientMap) : AsciiMapper { private val metrics: List> = map.toList().sortedByDescending { it.second } override fun mapToAscii( luma: Float ): String = metrics.find { luma >= it.second }?.first ?: metrics.firstOrNull()?.first.orEmpty() } private typealias Grid = List> ================================================ FILE: lib/collages/.gitignore ================================================ /build ================================================ FILE: lib/collages/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.collages" dependencies { implementation(libs.appCompat) implementation(libs.coilCompose) } ================================================ FILE: lib/collages/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/Collage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex import com.t8rin.collages.utils.CollageLayoutFactory.createCollageLayouts import com.t8rin.collages.view.FramePhotoLayout import kotlin.math.min @Composable fun Collage( images: List, modifier: Modifier = Modifier, spacing: Float = 0f, cornerRadius: Float = 0f, backgroundColor: Color = Color.White, onCollageCreated: (Bitmap) -> Unit, collageCreationTrigger: Boolean, collageType: CollageType, userInteractionEnabled: Boolean = true, aspectRatio: Float = 1f, outputScaleRatio: Float = 1.5f, onImageTap: ((index: Int) -> Unit)? = null, handleDrawable: Drawable? = null, disableRotation: Boolean = false, enableSnapToBorders: Boolean = false ) { var previousSize by rememberSaveable { mutableIntStateOf(100) } var previousAspect by rememberSaveable { mutableFloatStateOf(aspectRatio) } var previousImages by rememberSaveable { mutableStateOf(listOf()) } var needToInvalidate by remember { mutableStateOf(false) } val ownedCollageLayout by remember(collageType.layout?.title) { mutableStateOf( collageType.layout?.let { template -> createCollageLayouts(template.title) } ) } LaunchedEffect(collageType.layout?.title) { needToInvalidate = true } AnimatedVisibility( visible = ownedCollageLayout != null, modifier = modifier, enter = fadeIn(), exit = fadeOut() ) { BoxWithConstraints { val size = this.constraints.run { min(maxWidth, maxHeight) } var viewInstance by remember { mutableStateOf(null) } var viewState by rememberSaveable { mutableStateOf(Bundle.EMPTY) } DisposableEffect(viewInstance) { viewInstance?.restoreInstanceState(viewState) onDispose { viewState = Bundle() viewInstance?.saveInstanceState(viewState) } } SideEffect { viewInstance?.setBackgroundColor(backgroundColor) viewInstance?.setSpace(spacing, cornerRadius) viewInstance?.setDisableRotation(disableRotation) viewInstance?.setEnableSnapToBorders(enableSnapToBorders) } CompositionLocalProvider( LocalLayoutDirection provides LayoutDirection.Ltr ) { AndroidView( factory = { FramePhotoLayout( context = it, mPhotoItems = ownedCollageLayout?.photoItemList ?: emptyList() ).apply { updateImages(images) previousImages = images setParamsManager(ownedCollageLayout?.paramsManager) val (width, height) = calculateDimensions( size, constraints, aspectRatio ) viewInstance = this previousSize = size previousAspect = aspectRatio setBackgroundColor(backgroundColor) setOnItemTapListener(onImageTap) setHandleDrawable(handleDrawable) setDisableRotation(disableRotation) setEnableSnapToBorders(enableSnapToBorders) build( viewWidth = width, viewHeight = height, space = spacing, corner = cornerRadius ) } }, update = { if (needToInvalidate) { //Full rebuild needToInvalidate = false it.mPhotoItems = ownedCollageLayout?.photoItemList ?: emptyList() it.updateImages(images) previousImages = images it.setParamsManager(ownedCollageLayout?.paramsManager) it.setOnItemTapListener(onImageTap) it.setHandleDrawable(handleDrawable) previousSize = size previousAspect = aspectRatio val (width, height) = calculateDimensions( size, constraints, aspectRatio ) it.build( viewWidth = width, viewHeight = height, space = spacing, corner = cornerRadius ) } else { //Readjustments if (previousSize != size || previousAspect != aspectRatio) { val (width, height) = calculateDimensions( size, constraints, aspectRatio ) it.resize(width, height) previousSize = size previousAspect = aspectRatio } if (previousImages != images) { it.updateImages(images) previousImages = images } } } ) } if (!userInteractionEnabled) { Surface( color = Color.Transparent, modifier = Modifier .matchParentSize() .zIndex(2f) ) { } } LaunchedEffect(viewInstance, collageCreationTrigger) { if (collageCreationTrigger) { viewInstance?.createImage(outputScaleRatio)?.let(onCollageCreated) } } } } } private fun calculateDimensions( size: Int, constraints: Constraints, aspectRatio: Float ): Pair { return if (size == constraints.maxWidth) { val targetHeight = (size / aspectRatio).toDouble().coerceAtMost(constraints.maxHeight.toDouble()).toInt() val targetWidth = (targetHeight * aspectRatio).toInt() targetWidth to targetHeight } else { val targetWidth = (size * aspectRatio).toDouble().coerceAtMost(constraints.maxWidth.toDouble()).toInt() val targetHeight = (targetWidth / aspectRatio).toInt() targetWidth to targetHeight } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/CollageType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages import com.t8rin.collages.model.CollageLayout @ConsistentCopyVisibility data class CollageType internal constructor( internal val layout: CollageLayout?, internal val index: Int? ) { companion object { val Empty by lazy { CollageType( layout = null, index = null ) } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/CollageTypeSelection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory.loadFrameImages @Composable fun CollageTypeSelection( imagesCount: Int, value: CollageType, onValueChange: (CollageType) -> Unit, modifier: Modifier = Modifier, shape: Shape = RectangleShape, itemModifierFactory: @Composable (isSelected: Boolean) -> Modifier = { Modifier }, state: LazyListState = rememberLazyListState(), previewColor: Color = MaterialTheme.colorScheme.secondary, contentPadding: PaddingValues = PaddingValues(16.dp) ) { var allFrames: List by remember { mutableStateOf(emptyList()) } val context = LocalContext.current LaunchedEffect(context) { allFrames = loadFrameImages(context) } val availableFrames by remember(allFrames, imagesCount) { derivedStateOf { allFrames.filter { it.photoItemList.size == imagesCount } } } LaunchedEffect(availableFrames) { if ( availableFrames.isNotEmpty() && (value == CollageType.Empty || (value.layout?.photoItemList?.size ?: 0) != imagesCount) ) { onValueChange( CollageType( layout = availableFrames.first(), index = 0 ) ) } } AnimatedVisibility( visible = availableFrames.size > 1 ) { LazyRow( state = state, modifier = modifier, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), contentPadding = contentPadding ) { itemsIndexed(availableFrames) { index, layout -> AsyncImage( model = layout.preview, contentDescription = null, contentScale = ContentScale.FillBounds, colorFilter = ColorFilter.tint(previewColor), modifier = Modifier .fillMaxHeight() .aspectRatio(1f) .clip(shape) .clickable { onValueChange( CollageType( layout = layout, index = index ) ) } .then(itemModifierFactory(value.index == index)) ) } } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/EightFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.view.PhotoItem internal object EightFrameImage { internal fun collage_8_16(): CollageLayout { val item = CollageLayoutFactory.collage("collage_8_16") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //sixth frame photoItem = PhotoItem() photoItem.index = 5 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //seventh frame photoItem = PhotoItem() photoItem.index = 6 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //eighth frame photoItem = PhotoItem() photoItem.index = 7 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_8_15(): CollageLayout { return CollageLayoutFactory.collage("collage_8_15") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_8_14(): CollageLayout { return CollageLayoutFactory.collage("collage_8_14") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_8_13(): CollageLayout { return CollageLayoutFactory.collage("collage_8_13") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], 1f, vs[y2]) } ) addBoxedItem( yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x1], vs[y3], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x2], vs[y3], 1f, 1f) } ) } } internal fun collage_8_12(): CollageLayout { return CollageLayoutFactory.collage("collage_8_12") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], vs[y3]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x1], vs[y3], 1f, 1f) } ) } } internal fun collage_8_11(): CollageLayout { return CollageLayoutFactory.collage("collage_8_11") { val xTop = param(0.5f) val xMid2 = param(0.6666f) val xBot = param(0.3333f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(xTop), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[xTop], vs[y1]) } ) addBoxedItem( xParams = listOf(xTop), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xTop], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(xTop), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[xTop], vs[y2]) } ) addBoxedItem( xParams = listOf(xTop), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xTop], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(xMid2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[xMid2], vs[y3]) } ) addBoxedItem( xParams = listOf(xMid2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[xMid2], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(xBot), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[xBot], 1f) } ) addBoxedItem( xParams = listOf(xBot), yParams = listOf(y3), boxParams = { vs -> RectF(vs[xBot], vs[y3], 1f, 1f) } ) } } internal fun collage_8_10(): CollageLayout { return CollageLayoutFactory.collage("collage_8_10") { val x = param(0.5f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(x), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x], vs[y1]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x], vs[y2]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[x], vs[y3]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x], 1f) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x], vs[y3], 1f, 1f) } ) } } internal fun collage_8_9(): CollageLayout { return CollageLayoutFactory.collage("collage_8_9") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.25f) val y2 = param(0.5f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_8_8(): CollageLayout { return CollageLayoutFactory.collage("collage_8_8") { val xR = param(0.6666f) val xM = param(0.3333f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) val yMid = param(0.7f) addBoxedItem( xParams = listOf(xM), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[xM], vs[y1]) } ) addBoxedItem( xParams = listOf(xM, xR), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xM], 0f, vs[xR], vs[y1]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y1, yMid), boxParams = { vs -> RectF(0f, vs[y1], vs[xR], vs[yMid]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(yMid), boxParams = { vs -> RectF(0f, vs[yMid], vs[xR], 1f) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xR], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xR], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[xR], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y3), boxParams = { vs -> RectF(vs[xR], vs[y3], 1f, 1f) } ) } } internal fun collage_8_7(): CollageLayout { return CollageLayoutFactory.collage("collage_8_7") { val x1 = param(0.3f) val x2 = param(0.6f) val y1 = param(0.2f) val y2 = param(0.6f) val yR1 = param(0.3333f) val yR2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yR1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[yR1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yR1, yR2), boxParams = { vs -> RectF(vs[x2], vs[yR1], 1f, vs[yR2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yR2), boxParams = { vs -> RectF(vs[x2], vs[yR2], 1f, 1f) } ) } } internal fun collage_8_6(): CollageLayout { return CollageLayoutFactory.collage("collage_8_6") { val xMain = param(0.6f) val xSmall = param(0.3f) val yTop = param(0.5f) val yR1 = param(0.3333f) val yR2 = param(0.6666f) val ySmall = param(0.75f) addBoxedItem( xParams = listOf(xMain), yParams = listOf(yTop), boxParams = { vs -> RectF(0f, 0f, vs[xMain], vs[yTop]) } ) addBoxedItem( xParams = listOf(xMain), yParams = listOf(yR1), boxParams = { vs -> RectF(vs[xMain], 0f, 1f, vs[yR1]) } ) addBoxedItem( xParams = listOf(xMain), yParams = listOf(yR1, yR2), boxParams = { vs -> RectF(vs[xMain], vs[yR1], 1f, vs[yR2]) } ) addBoxedItem( xParams = listOf(xMain), yParams = listOf(yR2), boxParams = { vs -> RectF(vs[xMain], vs[yR2], 1f, 1f) } ) addBoxedItem( xParams = listOf(xSmall), yParams = listOf(yTop, ySmall), boxParams = { vs -> RectF(0f, vs[yTop], vs[xSmall], vs[ySmall]) } ) addBoxedItem( xParams = listOf(xSmall, xMain), yParams = listOf(yTop, ySmall), boxParams = { vs -> RectF(vs[xSmall], vs[yTop], vs[xMain], vs[ySmall]) } ) addBoxedItem( xParams = listOf(xSmall), yParams = listOf(ySmall), boxParams = { vs -> RectF(0f, vs[ySmall], vs[xSmall], 1f) } ) addBoxedItem( xParams = listOf(xSmall, xMain), yParams = listOf(ySmall), boxParams = { vs -> RectF(vs[xSmall], vs[ySmall], vs[xMain], 1f) } ) } } internal fun collage_8_5(): CollageLayout { return CollageLayoutFactory.collage("collage_8_5") { val x0 = param(0.25f) val x1 = param(0.5f) val x2 = param(0.75f) val y = param(0.5f) addBoxedItem( xParams = listOf(x0), yParams = listOf(y), boxParams = { vs -> RectF(0f, 0f, vs[x0], vs[y]) } ) addBoxedItem( xParams = listOf(x0, x1), yParams = listOf(y), boxParams = { vs -> RectF(vs[x0], 0f, vs[x1], vs[y]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y]) } ) addBoxedItem( xParams = listOf(x0), yParams = listOf(y), boxParams = { vs -> RectF(0f, vs[y], vs[x0], 1f) } ) addBoxedItem( xParams = listOf(x0, x1), yParams = listOf(y), boxParams = { vs -> RectF(vs[x0], vs[y], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x1], vs[y], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x2], vs[y], 1f, 1f) } ) } } internal fun collage_8_4(): CollageLayout { return CollageLayoutFactory.collage("collage_8_4") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val yTop = param(0.25f) val yMid = param(0.75f) addBoxedItem( xParams = listOf(x1), yParams = listOf(yTop), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[yTop]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(yTop), boxParams = { vs -> RectF(0f, vs[yTop], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yMid), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[yMid]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yMid), boxParams = { vs -> RectF(vs[x1], vs[yMid], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(yTop), boxParams = { vs -> RectF(vs[x2], 0f, vs[x3], vs[yTop]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(yTop), boxParams = { vs -> RectF(vs[x2], vs[yTop], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(yMid), boxParams = { vs -> RectF(vs[x3], 0f, 1f, vs[yMid]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(yMid), boxParams = { vs -> RectF(vs[x3], vs[yMid], 1f, 1f) } ) } } internal fun collage_8_3(): CollageLayout { return CollageLayoutFactory.collage("collage_8_3") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val y1 = param(0.25f) val y2 = param(0.75f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x3], vs[y2], 1f, 1f) } ) } } internal fun collage_8_2(): CollageLayout { return CollageLayoutFactory.collage("collage_8_2") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) val yMid = param(0.5f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yMid), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[yMid]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yMid), boxParams = { vs -> RectF(vs[x1], vs[yMid], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_8_1(): CollageLayout { return CollageLayoutFactory.collage("collage_8_1") { val x1 = param(0.3333f) val x2 = param(0.5f) val x3 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x3], vs[y1]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x3], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x3], vs[y2], 1f, 1f) } ) } } internal fun collage_8_0(): CollageLayout { return CollageLayoutFactory.collage("collage_8_0") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], vs[y3]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x3], vs[y2], 1f, 1f) } ) } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/ExtendedFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.utils.CollageLayoutFactory.collage import com.t8rin.collages.utils.ParamsManagerBuilder import com.t8rin.collages.view.PhotoItem internal object ExtendedFrameImage { private fun ParamsManagerBuilder.fixedRect( left: Float, top: Float, right: Float, bottom: Float, ) { addBoxedItem(boxParams = { RectF(left, top, right, bottom) }) } private fun ParamsManagerBuilder.uniformGridInRegion( rows: Int, cols: Int, regionLeft: Float = 0f, regionTop: Float = 0f, regionRight: Float = 1f, regionBottom: Float = 1f, ) { val w = regionRight - regionLeft val h = regionBottom - regionTop val cw = w / cols val rh = h / rows for (r in 0 until rows) { for (c in 0 until cols) { fixedRect( regionLeft + c * cw, regionTop + r * rh, regionLeft + (c + 1) * cw, regionTop + (r + 1) * rh, ) } } } private fun ParamsManagerBuilder.rowStrip( rowTop: Float, rowBottom: Float, cellCount: Int, regionLeft: Float = 0f, regionRight: Float = 1f, ) { val w = (regionRight - regionLeft) / cellCount for (i in 0 until cellCount) { fixedRect( regionLeft + i * w, rowTop, regionLeft + (i + 1) * w, rowBottom, ) } } private fun ParamsManagerBuilder.parametricStackedRowStrips( rowCounts: List, ) { val numRows = rowCounts.size if (numRows == 0) return val yRowParams = (1 until numRows).map { r -> param(r.toFloat() / numRows) } val xRowParamsList = rowCounts.map { k -> if (k <= 1) emptyList() else (1 until k).map { j -> param(j.toFloat() / k) } } for (r in rowCounts.indices) { val k = rowCounts[r] if (k <= 0) continue val xRowParams = xRowParamsList[r] repeat(k) { i -> val xp = buildList { if (i > 0) add(xRowParams[i - 1]) if (i < k - 1) add(xRowParams[i]) }.distinct() val yp = buildList { if (r > 0) add(yRowParams[r - 1]) if (r < numRows - 1) add(yRowParams[r]) }.distinct() addBoxedItem( xParams = xp, yParams = yp, boxParams = { vs -> val top = if (r == 0) 0f else vs[yRowParams[r - 1]] val bottom = if (r == numRows - 1) 1f else vs[yRowParams[r]] val left = if (i == 0) 0f else vs[xRowParams[i - 1]] val right = if (i == k - 1) 1f else vs[xRowParams[i]] RectF(left, top, right, bottom) }, ) } } } internal fun collage_1_0(): CollageLayout { return collage("collage_1_0").copy( photoItemList = listOf( PhotoItem().apply { bound.set(0f, 0f, 1f, 1f) index = 0 pointList.add(PointF(0f, 0f)) pointList.add(PointF(1f, 0f)) pointList.add(PointF(1f, 1f)) pointList.add(PointF(0f, 1f)) } ) ) } internal fun collage_2_12(): CollageLayout { return TwoFrameImage.collage_2_0("collage_2_12", 0.38f) } internal fun collage_2_13(): CollageLayout { return TwoFrameImage.collage_2_1("collage_2_13", 0.42f) } internal fun collage_3_48(): CollageLayout { return collage("collage_3_48") { val x1 = param(0.34f) val y1 = param(0.5f) addBoxedItem( xParams = listOf(x1), boxParams = { vs -> RectF(0f, 0f, vs[x1], 1f) }, ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, 1f, vs[y1]) }, ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], vs[y1], 1f, 1f) }, ) } } internal fun collage_4_26(): CollageLayout { return collage("collage_4_26") { val y1 = param(0.55f) addBoxedItem( yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, 1f, vs[y1]) }, ) val x1 = param(0.3333f) val x2 = param(0.6667f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], 1f) }, ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], 1f) }, ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, 1f) }, ) } } internal fun collage_4_27(): CollageLayout { return collage("collage_4_27") { fixedRect(0f, 0f, 0.55f, 1f) fixedRect(0.55f, 0f, 1f, 1f / 3f) fixedRect(0.55f, 1f / 3f, 1f, 2f / 3f) fixedRect(0.55f, 2f / 3f, 1f, 1f) } } internal fun collage_5_32(): CollageLayout { return collage("collage_5_32") { val x1 = param(0.28f) val x2 = param(0.72f) val y1 = param(0.28f) val y2 = param(0.72f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) }, ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) }, ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) }, ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) }, ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) }, ) } } internal fun collage_5_33(): CollageLayout { return collage("collage_5_33") { rowStrip(0f, 0.5f, 2) rowStrip(0.5f, 1f, 3) } } internal fun collage_6_15(): CollageLayout { return collage("collage_6_15") { rowStrip(0f, 1f / 3f, 1) rowStrip(1f / 3f, 2f / 3f, 2) rowStrip(2f / 3f, 1f, 3) } } internal fun collage_6_16(): CollageLayout { return collage("collage_6_16") { rowStrip(0f, 1f / 3f, 2) rowStrip(1f / 3f, 2f / 3f, 2) rowStrip(2f / 3f, 1f, 2) } } internal fun collage_6_17(): CollageLayout { return collage("collage_6_17") { rowStrip(0f, 1f / 3f, 3) rowStrip(1f / 3f, 2f / 3f, 2) rowStrip(2f / 3f, 1f, 1) } } internal fun collage_7_11(): CollageLayout { return collage("collage_7_11") { rowStrip(0f, 1f / 3f, 3) rowStrip(1f / 3f, 2f / 3f, 2) rowStrip(2f / 3f, 1f, 2) } } internal fun collage_7_12(): CollageLayout { return collage("collage_7_12") { rowStrip(0f, 0.32f, 3) rowStrip(0.32f, 0.55f, 1) rowStrip(0.55f, 1f, 3) } } internal fun collage_8_17(): CollageLayout { return collage("collage_8_17") { rowStrip(0f, 0.35f, 3) rowStrip(0.35f, 0.7f, 3) rowStrip(0.7f, 1f, 2) } } internal fun collage_8_18(): CollageLayout { return collage("collage_8_18") { rowStrip(0f, 0.25f, 2) rowStrip(0.25f, 0.5f, 2) rowStrip(0.5f, 0.75f, 2) rowStrip(0.75f, 1f, 2) } } internal fun collage_9_12(): CollageLayout { return collage("collage_9_12") { rowStrip(0f, 1f / 3f, 2) rowStrip(1f / 3f, 2f / 3f, 3) rowStrip(2f / 3f, 1f, 4) } } internal fun collage_9_13(): CollageLayout { return collage("collage_9_13") { rowStrip(0f, 1f / 3f, 3) rowStrip(1f / 3f, 2f / 3f, 3) rowStrip(2f / 3f, 1f, 3) } } internal fun collage_10_9(): CollageLayout { return collage("collage_10_9") { uniformGridInRegion(3, 3, 0f, 0f, 1f, 0.75f) fixedRect(0f, 0.75f, 1f, 1f) } } internal fun collage_10_10(): CollageLayout { return collage("collage_10_10") { uniformGridInRegion(2, 5, 0f, 0f, 1f, 1f) } } internal fun collage_11_0(): CollageLayout { return collage("collage_11_0") { parametricStackedRowStrips(listOf(3, 4, 4)) } } internal fun collage_11_1(): CollageLayout { return collage("collage_11_1") { parametricStackedRowStrips(listOf(5, 6)) } } internal fun collage_11_2(): CollageLayout { return collage("collage_11_2") { parametricStackedRowStrips(listOf(4, 4, 3)) } } internal fun collage_11_3(): CollageLayout { return collage("collage_11_3") { parametricStackedRowStrips(listOf(3, 3, 5)) } } internal fun collage_11_4(): CollageLayout { return collage("collage_11_4") { parametricStackedRowStrips(listOf(4, 3, 4)) } } internal fun collage_11_5(): CollageLayout { return collage("collage_11_5") { parametricStackedRowStrips(listOf(5, 3, 3)) } } internal fun collage_11_6(): CollageLayout { return collage("collage_11_6") { rowStrip(0f, 0.24f, 3) fixedRect(0f, 0.24f, 0.2f, 0.76f) fixedRect(0.2f, 0.24f, 0.34f, 0.76f) fixedRect(0.34f, 0.24f, 0.66f, 0.76f) fixedRect(0.66f, 0.24f, 0.8f, 0.76f) fixedRect(0.8f, 0.24f, 1f, 0.76f) rowStrip(0.76f, 1f, 3) } } internal fun collage_11_7(): CollageLayout { return collage("collage_11_7") { parametricStackedRowStrips(listOf(2, 4, 5)) } } internal fun collage_11_8(): CollageLayout { return collage("collage_11_8") { parametricStackedRowStrips(listOf(6, 3, 2)) } } internal fun collage_11_9(): CollageLayout { return collage("collage_11_9") { rowStrip(0f, 0.26f, 3) uniformGridInRegion(1, 5, 0f, 0.26f, 1f, 0.72f) rowStrip(0.72f, 1f, 3) } } internal fun collage_11_10(): CollageLayout { return collage("collage_11_10") { rowStrip(0f, 0.24f, 4) fixedRect(0f, 0.24f, 0.3f, 0.78f) uniformGridInRegion(1, 3, 0.3f, 0.24f, 1f, 0.51f) uniformGridInRegion(1, 2, 0.3f, 0.51f, 1f, 0.78f) rowStrip(0.78f, 1f, 1) } } internal fun collage_12_0(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_12_0", rows = 3, cols = 4) } internal fun collage_12_1(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_12_1", rows = 4, cols = 3) } internal fun collage_12_2(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_12_2", rows = 2, cols = 6) } internal fun collage_12_3(): CollageLayout { return collage("collage_12_3") { parametricStackedRowStrips(listOf(2, 3, 3, 4)) } } internal fun collage_12_4(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_12_4", rows = 6, cols = 2) } internal fun collage_12_5(): CollageLayout { return collage("collage_12_5") { parametricStackedRowStrips(listOf(4, 3, 3, 2)) } } internal fun collage_12_6(): CollageLayout { return collage("collage_12_6") { rowStrip(0f, 0.2f, 4) fixedRect(0f, 0.2f, 0.2f, 0.8f) fixedRect(0.8f, 0.2f, 1f, 0.8f) uniformGridInRegion(2, 2, 0.2f, 0.2f, 0.8f, 0.8f) rowStrip(0.8f, 1f, 2) } } internal fun collage_12_7(): CollageLayout { return collage("collage_12_7") { parametricStackedRowStrips(listOf(3, 5, 4)) } } internal fun collage_12_8(): CollageLayout { return collage("collage_12_8") { parametricStackedRowStrips(listOf(2, 5, 5)) } } internal fun collage_12_9(): CollageLayout { return collage("collage_12_9") { parametricStackedRowStrips(listOf(4, 2, 4, 2)) } } internal fun collage_12_10(): CollageLayout { return collage("collage_12_10") { rowStrip(0f, 0.22f, 4) fixedRect(0f, 0.22f, 0.18f, 0.8f) fixedRect(0.82f, 0.22f, 1f, 0.8f) uniformGridInRegion(2, 2, 0.18f, 0.22f, 0.82f, 0.8f) rowStrip(0.8f, 1f, 2) } } internal fun collage_13_0(): CollageLayout { return collage("collage_13_0") { parametricStackedRowStrips(listOf(4, 4, 5)) } } internal fun collage_13_1(): CollageLayout { return collage("collage_13_1") { parametricStackedRowStrips(listOf(3, 3, 3, 4)) } } internal fun collage_13_2(): CollageLayout { return collage("collage_13_2") { parametricStackedRowStrips(listOf(5, 4, 4)) } } internal fun collage_13_3(): CollageLayout { return collage("collage_13_3") { parametricStackedRowStrips(listOf(3, 4, 6)) } } internal fun collage_13_4(): CollageLayout { return collage("collage_13_4") { parametricStackedRowStrips(listOf(2, 5, 6)) } } internal fun collage_13_5(): CollageLayout { return collage("collage_13_5") { parametricStackedRowStrips(listOf(4, 5, 4)) } } internal fun collage_13_6(): CollageLayout { return collage("collage_13_6") { rowStrip(0f, 0.22f, 5) fixedRect(0f, 0.22f, 0.22f, 0.78f) fixedRect(0.78f, 0.22f, 1f, 0.78f) uniformGridInRegion(2, 2, 0.22f, 0.22f, 0.78f, 0.78f) rowStrip(0.78f, 1f, 2) } } internal fun collage_13_7(): CollageLayout { return collage("collage_13_7") { parametricStackedRowStrips(listOf(3, 5, 5)) } } internal fun collage_13_8(): CollageLayout { return collage("collage_13_8") { parametricStackedRowStrips(listOf(6, 4, 3)) } } internal fun collage_13_9(): CollageLayout { return collage("collage_13_9") { parametricStackedRowStrips(listOf(4, 3, 4, 2)) } } internal fun collage_13_10(): CollageLayout { return collage("collage_13_10") { rowStrip(0f, 0.2f, 4) fixedRect(0f, 0.2f, 0.22f, 0.82f) fixedRect(0.22f, 0.2f, 0.5f, 0.5f) fixedRect(0.5f, 0.2f, 0.78f, 0.5f) fixedRect(0.78f, 0.2f, 1f, 0.82f) fixedRect(0.22f, 0.5f, 0.5f, 0.82f) fixedRect(0.5f, 0.5f, 0.78f, 0.82f) rowStrip(0.82f, 1f, 3) } } internal fun collage_14_0(): CollageLayout { return collage("collage_14_0") { parametricStackedRowStrips(listOf(7, 7)) } } internal fun collage_14_1(): CollageLayout { return collage("collage_14_1") { parametricStackedRowStrips(listOf(4, 5, 5)) } } internal fun collage_14_2(): CollageLayout { return collage("collage_14_2") { parametricStackedRowStrips(listOf(3, 4, 4, 3)) } } internal fun collage_14_3(): CollageLayout { return collage("collage_14_3") { parametricStackedRowStrips(listOf(5, 5, 4)) } } internal fun collage_14_4(): CollageLayout { return collage("collage_14_4") { parametricStackedRowStrips(listOf(6, 4, 4)) } } internal fun collage_14_5(): CollageLayout { return collage("collage_14_5") { parametricStackedRowStrips(listOf(4, 4, 3, 3)) } } internal fun collage_14_6(): CollageLayout { return collage("collage_14_6") { rowStrip(0f, 0.18f, 4) fixedRect(0f, 0.18f, 0.18f, 0.82f) fixedRect(0.82f, 0.18f, 1f, 0.82f) fixedRect(0.18f, 0.18f, 0.41f, 0.41f) fixedRect(0.41f, 0.18f, 0.59f, 0.41f) fixedRect(0.59f, 0.18f, 0.82f, 0.41f) fixedRect(0.18f, 0.41f, 0.41f, 0.82f) fixedRect(0.41f, 0.41f, 0.59f, 0.82f) fixedRect(0.59f, 0.41f, 0.82f, 0.82f) rowStrip(0.82f, 1f, 2) } } internal fun collage_14_7(): CollageLayout { return collage("collage_14_7") { parametricStackedRowStrips(listOf(3, 6, 5)) } } internal fun collage_14_8(): CollageLayout { return collage("collage_14_8") { parametricStackedRowStrips(listOf(5, 4, 3, 2)) } } internal fun collage_14_9(): CollageLayout { return collage("collage_14_9") { parametricStackedRowStrips(listOf(2, 4, 4, 4)) } } internal fun collage_14_10(): CollageLayout { return collage("collage_14_10") { rowStrip(0f, 0.2f, 4) fixedRect(0f, 0.2f, 0.2f, 0.82f) fixedRect(0.2f, 0.2f, 0.4f, 0.51f) fixedRect(0.4f, 0.2f, 0.6f, 0.51f) fixedRect(0.6f, 0.2f, 0.8f, 0.51f) fixedRect(0.8f, 0.2f, 1f, 0.82f) fixedRect(0.2f, 0.51f, 0.4f, 0.82f) fixedRect(0.4f, 0.51f, 0.6f, 0.82f) fixedRect(0.6f, 0.51f, 0.8f, 0.82f) rowStrip(0.82f, 1f, 2) } } internal fun collage_15_0(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_15_0", rows = 3, cols = 5) } internal fun collage_15_1(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_15_1", rows = 5, cols = 3) } internal fun collage_15_2(): CollageLayout { return collage("collage_15_2") { parametricStackedRowStrips(listOf(2, 3, 3, 4, 5)) } } internal fun collage_15_3(): CollageLayout { return collage("collage_15_3") { parametricStackedRowStrips(listOf(4, 5, 6)) } } internal fun collage_15_4(): CollageLayout { return collage("collage_15_4") { parametricStackedRowStrips(listOf(3, 5, 7)) } } internal fun collage_15_5(): CollageLayout { return collage("collage_15_5") { parametricStackedRowStrips(listOf(4, 4, 4, 3)) } } internal fun collage_15_6(): CollageLayout { return collage("collage_15_6") { rowStrip(0f, 0.2f, 5) fixedRect(0f, 0.2f, 0.22f, 0.8f) fixedRect(0.22f, 0.2f, 0.39f, 0.8f) fixedRect(0.39f, 0.2f, 0.61f, 0.5f) fixedRect(0.61f, 0.2f, 0.78f, 0.8f) fixedRect(0.78f, 0.2f, 1f, 0.8f) fixedRect(0.39f, 0.5f, 0.61f, 0.8f) rowStrip(0.8f, 1f, 4) } } internal fun collage_15_7(): CollageLayout { return collage("collage_15_7") { parametricStackedRowStrips(listOf(3, 4, 5, 3)) } } internal fun collage_15_8(): CollageLayout { return collage("collage_15_8") { parametricStackedRowStrips(listOf(2, 5, 5, 3)) } } internal fun collage_15_9(): CollageLayout { return collage("collage_15_9") { parametricStackedRowStrips(listOf(6, 4, 3, 2)) } } internal fun collage_15_10(): CollageLayout { return collage("collage_15_10") { rowStrip(0f, 0.18f, 5) fixedRect(0f, 0.18f, 0.2f, 0.82f) fixedRect(0.2f, 0.18f, 0.4f, 0.5f) fixedRect(0.4f, 0.18f, 0.6f, 0.5f) fixedRect(0.6f, 0.18f, 0.8f, 0.5f) fixedRect(0.8f, 0.18f, 1f, 0.82f) fixedRect(0.2f, 0.5f, 0.4f, 0.82f) fixedRect(0.4f, 0.5f, 0.6f, 0.82f) fixedRect(0.6f, 0.5f, 0.8f, 0.82f) rowStrip(0.82f, 1f, 2) } } internal fun collage_16_0(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_16_0", rows = 4, cols = 4) } internal fun collage_16_1(): CollageLayout { return collage("collage_16_1") { parametricStackedRowStrips(listOf(5, 5, 6)) } } internal fun collage_16_2(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_16_2", rows = 8, cols = 2) } internal fun collage_16_3(): CollageLayout { return collage("collage_16_3") { parametricStackedRowStrips(listOf(3, 4, 5, 4)) } } internal fun collage_16_4(): CollageLayout { return collage("collage_16_4") { parametricStackedRowStrips(listOf(2, 4, 4, 6)) } } internal fun collage_16_5(): CollageLayout { return collage("collage_16_5") { parametricStackedRowStrips(listOf(6, 5, 5)) } } internal fun collage_16_6(): CollageLayout { return collage("collage_16_6") { parametricStackedRowStrips(listOf(3, 5, 5, 3)) } } internal fun collage_16_7(): CollageLayout { return collage("collage_16_7") { parametricStackedRowStrips(listOf(3, 5, 4, 4)) } } internal fun collage_16_8(): CollageLayout { return collage("collage_16_8") { parametricStackedRowStrips(listOf(2, 4, 6, 4)) } } internal fun collage_16_9(): CollageLayout { return collage("collage_16_9") { rowStrip(0f, 0.18f, 4) fixedRect(0f, 0.18f, 0.2f, 0.82f) uniformGridInRegion(2, 3, 0.2f, 0.18f, 0.8f, 0.82f) fixedRect(0.8f, 0.18f, 1f, 0.82f) rowStrip(0.82f, 1f, 4) } } internal fun collage_16_10(): CollageLayout { return collage("collage_16_10") { rowStrip(0f, 0.22f, 5) fixedRect(0f, 0.22f, 0.22f, 0.8f) fixedRect(0.22f, 0.22f, 0.39f, 0.5f) fixedRect(0.39f, 0.22f, 0.61f, 0.5f) fixedRect(0.61f, 0.22f, 0.78f, 0.5f) fixedRect(0.78f, 0.22f, 1f, 0.8f) fixedRect(0.22f, 0.5f, 0.39f, 0.8f) fixedRect(0.39f, 0.5f, 0.61f, 0.8f) fixedRect(0.61f, 0.5f, 0.78f, 0.8f) rowStrip(0.8f, 1f, 3) } } internal fun collage_17_0(): CollageLayout { return collage("collage_17_0") { parametricStackedRowStrips(listOf(4, 4, 4, 5)) } } internal fun collage_17_1(): CollageLayout { return collage("collage_17_1") { parametricStackedRowStrips(listOf(6, 6, 5)) } } internal fun collage_17_2(): CollageLayout { return collage("collage_17_2") { parametricStackedRowStrips(listOf(5, 6, 6)) } } internal fun collage_17_3(): CollageLayout { return collage("collage_17_3") { parametricStackedRowStrips(listOf(4, 4, 5, 4)) } } internal fun collage_17_4(): CollageLayout { return collage("collage_17_4") { parametricStackedRowStrips(listOf(7, 5, 5)) } } internal fun collage_17_5(): CollageLayout { return collage("collage_17_5") { parametricStackedRowStrips(listOf(3, 4, 4, 6)) } } internal fun collage_17_6(): CollageLayout { return collage("collage_17_6") { parametricStackedRowStrips(listOf(3, 5, 5, 4)) } } internal fun collage_17_7(): CollageLayout { return collage("collage_17_7") { parametricStackedRowStrips(listOf(2, 5, 6, 4)) } } internal fun collage_17_8(): CollageLayout { return collage("collage_17_8") { parametricStackedRowStrips(listOf(4, 4, 4, 3, 2)) } } internal fun collage_17_9(): CollageLayout { return collage("collage_17_9") { rowStrip(0f, 0.24f, 5) rowStrip(0.24f, 0.76f, 7) rowStrip(0.76f, 1f, 5) } } internal fun collage_17_10(): CollageLayout { return collage("collage_17_10") { rowStrip(0f, 0.2f, 4) fixedRect(0f, 0.2f, 0.2f, 0.8f) fixedRect(0.2f, 0.2f, 0.4f, 0.5f) fixedRect(0.4f, 0.2f, 0.6f, 0.5f) fixedRect(0.6f, 0.2f, 0.8f, 0.5f) fixedRect(0.8f, 0.2f, 1f, 0.8f) fixedRect(0.2f, 0.5f, 0.4f, 0.8f) fixedRect(0.4f, 0.5f, 0.6f, 0.8f) fixedRect(0.6f, 0.5f, 0.8f, 0.8f) rowStrip(0.8f, 1f, 5) } } internal fun collage_18_0(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_18_0", rows = 3, cols = 6) } internal fun collage_18_1(): CollageLayout { return CollageLayoutFactory.collageParametricGrid("collage_18_1", rows = 6, cols = 3) } internal fun collage_18_2(): CollageLayout { return collage("collage_18_2") { parametricStackedRowStrips(listOf(6, 6, 6)) } } internal fun collage_18_3(): CollageLayout { return collage("collage_18_3") { parametricStackedRowStrips(listOf(4, 5, 5, 4)) } } internal fun collage_18_4(): CollageLayout { return collage("collage_18_4") { parametricStackedRowStrips(listOf(3, 5, 4, 6)) } } internal fun collage_18_5(): CollageLayout { return collage("collage_18_5") { parametricStackedRowStrips(listOf(4, 4, 4, 3, 3)) } } internal fun collage_18_6(): CollageLayout { return collage("collage_18_6") { parametricStackedRowStrips(listOf(3, 5, 6, 4)) } } internal fun collage_18_7(): CollageLayout { return collage("collage_18_7") { parametricStackedRowStrips(listOf(2, 4, 6, 6)) } } internal fun collage_18_8(): CollageLayout { return collage("collage_18_8") { parametricStackedRowStrips(listOf(5, 5, 4, 4)) } } internal fun collage_18_9(): CollageLayout { return collage("collage_18_9") { rowStrip(0f, 0.18f, 4) fixedRect(0f, 0.18f, 0.18f, 0.82f) uniformGridInRegion(2, 4, 0.18f, 0.18f, 0.82f, 0.82f) fixedRect(0.82f, 0.18f, 1f, 0.82f) rowStrip(0.82f, 1f, 4) } } internal fun collage_18_10(): CollageLayout { return collage("collage_18_10") { rowStrip(0f, 0.2f, 5) fixedRect(0f, 0.2f, 0.2f, 0.8f) fixedRect(0.2f, 0.2f, 0.35f, 0.8f) fixedRect(0.35f, 0.2f, 0.65f, 0.5f) fixedRect(0.65f, 0.2f, 0.8f, 0.8f) fixedRect(0.8f, 0.2f, 1f, 0.8f) fixedRect(0.35f, 0.5f, 0.65f, 0.8f) rowStrip(0.8f, 1f, 7) } } internal fun collage_19_0(): CollageLayout { return collage("collage_19_0") { parametricStackedRowStrips(listOf(5, 5, 5, 4)) } } internal fun collage_19_1(): CollageLayout { return collage("collage_19_1") { parametricStackedRowStrips(listOf(4, 5, 5, 5)) } } internal fun collage_19_2(): CollageLayout { return collage("collage_19_2") { parametricStackedRowStrips(listOf(6, 6, 7)) } } internal fun collage_19_3(): CollageLayout { return collage("collage_19_3") { parametricStackedRowStrips(listOf(3, 4, 5, 7)) } } internal fun collage_19_4(): CollageLayout { return collage("collage_19_4") { parametricStackedRowStrips(listOf(2, 6, 6, 5)) } } internal fun collage_19_5(): CollageLayout { return collage("collage_19_5") { parametricStackedRowStrips(listOf(4, 4, 4, 4, 3)) } } internal fun collage_19_6(): CollageLayout { return collage("collage_19_6") { parametricStackedRowStrips(listOf(3, 5, 6, 5)) } } internal fun collage_19_7(): CollageLayout { return collage("collage_19_7") { parametricStackedRowStrips(listOf(4, 5, 5, 3, 2)) } } internal fun collage_19_8(): CollageLayout { return collage("collage_19_8") { parametricStackedRowStrips(listOf(2, 4, 6, 4, 3)) } } internal fun collage_19_9(): CollageLayout { return collage("collage_19_9") { rowStrip(0f, 0.16f, 5) fixedRect(0f, 0.16f, 0.16f, 0.84f) uniformGridInRegion(2, 4, 0.16f, 0.16f, 0.84f, 0.84f) fixedRect(0.84f, 0.16f, 1f, 0.84f) rowStrip(0.84f, 1f, 4) } } internal fun collage_19_10(): CollageLayout { return collage("collage_19_10") { rowStrip(0f, 0.2f, 5) fixedRect(0f, 0.2f, 0.2f, 0.8f) fixedRect(0.2f, 0.2f, 0.36f, 0.8f) fixedRect(0.36f, 0.2f, 0.5f, 0.5f) fixedRect(0.5f, 0.2f, 0.64f, 0.5f) fixedRect(0.64f, 0.2f, 0.8f, 0.8f) fixedRect(0.8f, 0.2f, 1f, 0.8f) fixedRect(0.36f, 0.5f, 0.5f, 0.8f) fixedRect(0.5f, 0.5f, 0.64f, 0.8f) rowStrip(0.8f, 1f, 6) } } internal fun collage_20_0(): CollageLayout { return collage("collage_20_0") { parametricStackedRowStrips(listOf(7, 6, 7)) } } internal fun collage_20_1(): CollageLayout { return collage("collage_20_1") { parametricStackedRowStrips(listOf(6, 5, 4, 3, 2)) } } internal fun collage_20_2(): CollageLayout { return collage("collage_20_2") { parametricStackedRowStrips(listOf(4, 4, 4, 4, 4)) } } internal fun collage_20_3(): CollageLayout { return collage("collage_20_3") { parametricStackedRowStrips(listOf(5, 5, 5, 5)) } } internal fun collage_20_4(): CollageLayout { return collage("collage_20_4") { parametricStackedRowStrips(listOf(3, 7, 7, 3)) } } internal fun collage_20_5(): CollageLayout { return collage("collage_20_5") { parametricStackedRowStrips(listOf(7, 6, 4, 3)) } } internal fun collage_20_6(): CollageLayout { return collage("collage_20_6") { parametricStackedRowStrips(listOf(4, 6, 6, 4)) } } internal fun collage_20_7(): CollageLayout { return collage("collage_20_7") { parametricStackedRowStrips(listOf(3, 5, 6, 4, 2)) } } internal fun collage_20_8(): CollageLayout { return collage("collage_20_8") { parametricStackedRowStrips(listOf(5, 5, 4, 3, 3)) } } internal fun collage_20_9(): CollageLayout { return collage("collage_20_9") { rowStrip(0f, 0.16f, 5) fixedRect(0f, 0.16f, 0.16f, 0.84f) uniformGridInRegion(2, 4, 0.16f, 0.16f, 0.84f, 0.84f) fixedRect(0.84f, 0.16f, 1f, 0.84f) rowStrip(0.84f, 1f, 5) } } internal fun collage_20_10(): CollageLayout { return collage("collage_20_10") { rowStrip(0f, 0.18f, 6) fixedRect(0f, 0.18f, 0.18f, 0.82f) fixedRect(0.18f, 0.18f, 0.34f, 0.82f) fixedRect(0.34f, 0.18f, 0.5f, 0.5f) fixedRect(0.5f, 0.18f, 0.66f, 0.5f) fixedRect(0.66f, 0.18f, 0.82f, 0.82f) fixedRect(0.82f, 0.18f, 1f, 0.82f) fixedRect(0.34f, 0.5f, 0.5f, 0.82f) fixedRect(0.5f, 0.5f, 0.66f, 0.82f) rowStrip(0.82f, 1f, 6) } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/FiveFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.Path import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.view.PhotoItem /** * Created by admin on 6/24/2016. */ internal object FiveFrameImage { internal fun collage_5_31(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_31") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.3333f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.3333f)) photoItem.pointList.add(PointF(1f, 0.6666f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.6666f, 1f)) photoItem.pointList.add(PointF(0.3333f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6666f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.3333f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.6666f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6666f, 1f, 1f) photoItem.pointList.add(PointF(0.3333f, 0f)) photoItem.pointList.add(PointF(0.6666f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.3333f, 0.3333f, 0.6666f, 0.6666f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_30(): CollageLayout { return CollageLayoutFactory.collage("collage_5_30") { val wallTopY = param(0.3333f) val wallBottomY = param(0.6666f) val wallLeftX = param(0.3333f) val wallRightX = param(0.6666f) addBoxedItem( yParams = listOf(wallTopY), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallLeftX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(0f, vs[wallTopY], vs[wallLeftX], vs[wallBottomY]) } ) addBoxedItem( xParams = listOf(wallLeftX, wallRightX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF( vs[wallLeftX], vs[wallTopY], vs[wallRightX], vs[wallBottomY] ) } ) addBoxedItem( xParams = listOf(wallRightX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(vs[wallRightX], vs[wallTopY], 1f, vs[wallBottomY]) } ) addBoxedItem( yParams = listOf(wallBottomY), boxParams = { vs -> RectF(0f, vs[wallBottomY], 1f, 1f) } ) } } internal fun collage_5_29(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_29") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.4444f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.75f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0f, 1f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.1666f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0f, 0.3333f, 1f, 0.6666f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6666f, 0.6667f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.8333f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5556f, 0.6666f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.25f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_28(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_28") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.4f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6667f, 0.2f, 1f, 0.8f) photoItem.pointList.add(PointF(0f, 0.1111f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.8888f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0.2667f, 0.6667f, 0.7333f) photoItem.pointList.add(PointF(0f, 0.1428f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.8571f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.3333f, 0.3333f, 0.6667f) photoItem.pointList.add(PointF(0f, 0.2f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.8f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_27(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_27") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.4f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.2f, 0.3333f, 0.8f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.1111f)) photoItem.pointList.add(PointF(1f, 0.8888f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0.2667f, 0.6667f, 0.7333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.1428f)) photoItem.pointList.add(PointF(1f, 0.8571f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6666f, 0.3333f, 1f, 0.6667f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.2f)) photoItem.pointList.add(PointF(1f, 0.8f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_26(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_26") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.6f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.8333f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0.2f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.3333f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.75f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.5f, 0.75f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.8333f, 1f)) photoItem.pointList.add(PointF(0.1666f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.6667f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.25f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_25(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_25") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.25f, 0f, 0.75f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_24(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_24") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.75f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.3333f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.75f) photoItem.pointList.add(PointF(0f, 0.3333f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0.6667f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.25f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.6667f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_23(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_23") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.6667f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6667f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.3333f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.3333f, 0.3333f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6667f, 0.6667f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.3333f, 0.3333f, 0.6667f, 0.6667f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_22(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_22") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 2f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_21(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_21") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_20(): CollageLayout { return collage_5_19( name = "collage_5_20", x1 = 0.5f, x2 = 0.5f, y1 = 0.3333f, y2 = 0.6666f ) } internal fun collage_5_19( name: String = "collage_5_19", x1: Float = 0.6f, x2: Float = 0.4f, y1: Float = 0.3333f, y2: Float = 0.6666f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wallTopSplitX = param(x1) val wallBottomSplitX = param(x2) val wallTopY = param(y1) val wallBottomY = param(y2) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(0f, 0f, vs[wallTopSplitX], vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(vs[wallTopSplitX], 0f, 1f, vs[wallTopY]) } ) addBoxedItem( yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(0f, vs[wallTopY], 1f, vs[wallBottomY]) } ) addBoxedItem( xParams = listOf(wallBottomSplitX), yParams = listOf(wallBottomY), boxParams = { vs -> RectF(0f, vs[wallBottomY], vs[wallBottomSplitX], 1f) } ) addBoxedItem( xParams = listOf(wallBottomSplitX), yParams = listOf(wallBottomY), boxParams = { vs -> RectF(vs[wallBottomSplitX], vs[wallBottomY], 1f, 1f) } ) } } internal fun collage_5_18(): CollageLayout { return CollageLayoutFactory.collage("collage_5_18") { val wallTopSplitX = param(0.6f) val wallMidSplitX = param(0.4f) val wallTopY = param(0.3333f) val wallBottomY = param(0.6666f) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(0f, 0f, vs[wallTopSplitX], vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(vs[wallTopSplitX], 0f, 1f, vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallMidSplitX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(0f, vs[wallTopY], vs[wallMidSplitX], vs[wallBottomY]) } ) addBoxedItem( xParams = listOf(wallMidSplitX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(vs[wallMidSplitX], vs[wallTopY], 1f, vs[wallBottomY]) } ) addBoxedItem( yParams = listOf(wallBottomY), boxParams = { vs -> RectF(0f, vs[wallBottomY], 1f, 1f) } ) } } internal fun collage_5_17(): CollageLayout { return CollageLayoutFactory.collage("collage_5_17") { val wallMidX = param(0.5f) val wallTopY = param(0.3333f) val wallBottomY = param(0.6666f) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(0f, 0f, vs[wallMidX], vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(vs[wallMidX], 0f, 1f, vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(0f, vs[wallTopY], vs[wallMidX], vs[wallBottomY]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(vs[wallMidX], vs[wallTopY], 1f, vs[wallBottomY]) } ) addBoxedItem( yParams = listOf(wallBottomY), boxParams = { vs -> RectF(0f, vs[wallBottomY], 1f, 1f) } ) } } internal fun collage_5_16(): CollageLayout { return CollageLayoutFactory.collage("collage_5_16") { val wallTopY = param(0.3333f) val wallBottomY = param(0.6666f) val wallMidX = param(0.5f) addBoxedItem( yParams = listOf(wallTopY), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(0f, vs[wallTopY], vs[wallMidX], vs[wallBottomY]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(vs[wallMidX], vs[wallTopY], 1f, vs[wallBottomY]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallBottomY), boxParams = { vs -> RectF(0f, vs[wallBottomY], vs[wallMidX], 1f) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallBottomY), boxParams = { vs -> RectF(vs[wallMidX], vs[wallBottomY], 1f, 1f) } ) } } internal fun collage_5_15(): CollageLayout { return CollageLayoutFactory.collage("collage_5_15") { val wallLeftX = param(0.6f) val wallY1 = param(0.25f) val wallY2 = param(0.5f) val wallY3 = param(0.75f) addBoxedItem( xParams = listOf(wallLeftX), boxParams = { vs -> RectF(0f, 0f, vs[wallLeftX], 1f) } ) addBoxedItem( xParams = listOf(wallLeftX), yParams = listOf(wallY1), boxParams = { vs -> RectF(vs[wallLeftX], 0f, 1f, vs[wallY1]) } ) addBoxedItem( xParams = listOf(wallLeftX), yParams = listOf(wallY1, wallY2), boxParams = { vs -> RectF(vs[wallLeftX], vs[wallY1], 1f, vs[wallY2]) } ) addBoxedItem( xParams = listOf(wallLeftX), yParams = listOf(wallY2, wallY3), boxParams = { vs -> RectF(vs[wallLeftX], vs[wallY2], 1f, vs[wallY3]) } ) addBoxedItem( xParams = listOf(wallLeftX), yParams = listOf(wallY3), boxParams = { vs -> RectF(vs[wallLeftX], vs[wallY3], 1f, 1f) } ) } } internal fun collage_5_14(): CollageLayout { return CollageLayoutFactory.collage("collage_5_14") { val wallX1 = param(0.3333f) val wallX2 = param(0.6666f) val wallY = param(0.4f) addBoxedItem( xParams = listOf(wallX1), yParams = listOf(wallY), boxParams = { vs -> RectF(0f, 0f, vs[wallX1], vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX1, wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX1], 0f, vs[wallX2], vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX2], 0f, 1f, vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(0f, vs[wallY], vs[wallX2], 1f) } ) addBoxedItem( xParams = listOf(wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX2], vs[wallY], 1f, 1f) } ) } } internal fun collage_5_13(): CollageLayout { return CollageLayoutFactory.collage("collage_5_13") { val wallX1 = param(0.3333f) val wallX2 = param(0.6666f) val wallY = param(0.5f) addBoxedItem( xParams = listOf(wallX1), boxParams = { vs -> RectF(0f, 0f, vs[wallX1], 1f) } ) addBoxedItem( xParams = listOf(wallX1, wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX1], 0f, vs[wallX2], vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX2], 0f, 1f, vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX1, wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX1], vs[wallY], vs[wallX2], 1f) } ) addBoxedItem( xParams = listOf(wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX2], vs[wallY], 1f, 1f) } ) } } internal fun collage_5_12(): CollageLayout { return CollageLayoutFactory.collage("collage_5_12") { val wallTopSplitX = param(0.4f) val wallMidSplitX = param(0.6f) val wallTopY = param(0.3333f) val wallBottomY = param(0.6666f) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(0f, 0f, vs[wallTopSplitX], vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(vs[wallTopSplitX], 0f, 1f, vs[wallTopY]) } ) addBoxedItem( xParams = listOf(wallMidSplitX), yParams = listOf(wallTopY), boxParams = { vs -> RectF(0f, vs[wallTopY], vs[wallMidSplitX], 1f) } ) addBoxedItem( xParams = listOf(wallMidSplitX), yParams = listOf(wallTopY, wallBottomY), boxParams = { vs -> RectF(vs[wallMidSplitX], vs[wallTopY], 1f, vs[wallBottomY]) } ) addBoxedItem( xParams = listOf(wallMidSplitX), yParams = listOf(wallBottomY), boxParams = { vs -> RectF(vs[wallMidSplitX], vs[wallBottomY], 1f, 1f) } ) } } internal fun collage_5_11(): CollageLayout { return collage_5_10(name = "collage_5_11", y1 = 0.3333f) } internal fun collage_5_10( name: String = "collage_5_10", x1: Float = 0.3333f, x2: Float = 0.6667f, y1: Float = 0.5f, y2: Float = 0.6667f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wallX1 = param(x1) val wallX2 = param(x2) val wallY1 = param(y1) val wallY2 = param(y2) addBoxedItem( xParams = listOf(wallX1), yParams = listOf(wallY1), boxParams = { vs -> RectF(0f, 0f, vs[wallX1], vs[wallY1]) } ) addBoxedItem( xParams = listOf(wallX1), yParams = listOf(wallY1), boxParams = { vs -> RectF(0f, vs[wallY1], vs[wallX1], 1f) } ) addBoxedItem( xParams = listOf(wallX1), yParams = listOf(wallY2), boxParams = { vs -> RectF(vs[wallX1], 0f, 1f, vs[wallY2]) } ) addBoxedItem( xParams = listOf(wallX1, wallX2), yParams = listOf(wallY2), boxParams = { vs -> RectF(vs[wallX1], vs[wallY2], vs[wallX2], 1f) } ) addBoxedItem( xParams = listOf(wallX2), yParams = listOf(wallY2), boxParams = { vs -> RectF(vs[wallX2], vs[wallY2], 1f, 1f) } ) } } internal fun collage_5_9(): CollageLayout { return collage_5_8(name = "collage_5_9", x1 = 0.3333f, x2 = 0.6667f) } internal fun collage_5_8( name: String = "collage_5_8", x1: Float = 0.6667f, x2: Float = 0.3333f, y1: Float = 0.3333f, y2: Float = 0.6666f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wallTopSplitX = param(x1) val wallBottomSplitX = param(x2) val wallY1 = param(y1) val wallY2 = param(y2) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallY1), boxParams = { vs -> RectF(0f, 0f, vs[wallTopSplitX], vs[wallY1]) } ) addBoxedItem( xParams = listOf(wallTopSplitX), yParams = listOf(wallY1), boxParams = { vs -> RectF(vs[wallTopSplitX], 0f, 1f, vs[wallY1]) } ) addBoxedItem( yParams = listOf(wallY1, wallY2), boxParams = { vs -> RectF(0f, vs[wallY1], 1f, vs[wallY2]) } ) addBoxedItem( xParams = listOf(wallBottomSplitX), yParams = listOf(wallY2), boxParams = { vs -> RectF(0f, vs[wallY2], vs[wallBottomSplitX], 1f) } ) addBoxedItem( xParams = listOf(wallBottomSplitX), yParams = listOf(wallY2), boxParams = { vs -> RectF(vs[wallBottomSplitX], vs[wallY2], 1f, 1f) } ) } } internal fun collage_5_6(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_6") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //five frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_7(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_7") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(0.5f, 0.5f, 1.5f, 1.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(-0.5f, 0.5f, 0.5f, 1.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(-0.5f, -0.5f, 0.5f, 0.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(0.5f, -0.5f, 1.5f, 0.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //five frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.path = Path() photoItem.path!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.pathRatioBound = RectF(0f, 0f, 1f, 1f) photoItem.pathInCenterHorizontal = true photoItem.pathInCenterVertical = true photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_5(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_5") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.clearPathRatioBound = RectF(0.5f, 0.5f, 1.5f, 1.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.clearPathRatioBound = RectF(-0.5f, 0.5f, 0.5f, 1.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.clearPathRatioBound = RectF(-0.5f, -0.5f, 0.5f, 0.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.clearPathRatioBound = RectF(0.5f, -0.5f, 1.5f, 0.5f) photoItem.centerInClearBound = true photoItemList.add(photoItem) //five frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.path = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.pathRatioBound = RectF(0f, 0f, 1f, 1f) photoItem.pathInCenterHorizontal = true photoItem.pathInCenterVertical = true photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_4(): CollageLayout { return CollageLayoutFactory.collage("collage_5_4") { val wallX1 = param(0.3333f) val wallX2 = param(0.6666f) val wallX3 = param(0.5f) val wallY = param(0.5f) addBoxedItem( xParams = listOf(wallX1), yParams = listOf(wallY), boxParams = { vs -> RectF(0f, 0f, vs[wallX1], vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX1, wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX1], 0f, vs[wallX2], vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX2), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX2], 0f, 1f, vs[wallY]) } ) addBoxedItem( xParams = listOf(wallX3), yParams = listOf(wallY), boxParams = { vs -> RectF(0f, vs[wallY], vs[wallX3], 1f) } ) addBoxedItem( xParams = listOf(wallX3), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wallX3], vs[wallY], 1f, 1f) } ) } } internal fun collage_5_3(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_3") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0.5f, 0.5f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(1f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(0.5f, 0.5f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(2f, 2f) photoItemList.add(photoItem) //five frame photoItem = PhotoItem() photoItem.index = 4 photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_2(): CollageLayout { return CollageLayoutFactory.collage("collage_5_2") { val wallMidX = param(0.5f) val wallLeftY = param(0.5f) val wallRightY1 = param(0.3333f) val wallRightY2 = param(0.6666f) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallLeftY), boxParams = { vs -> RectF(0f, 0f, vs[wallMidX], vs[wallLeftY]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallLeftY), boxParams = { vs -> RectF(0f, vs[wallLeftY], vs[wallMidX], 1f) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallRightY1), boxParams = { vs -> RectF(vs[wallMidX], 0f, 1f, vs[wallRightY1]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallRightY1, wallRightY2), boxParams = { vs -> RectF(vs[wallMidX], vs[wallRightY1], 1f, vs[wallRightY2]) } ) addBoxedItem( xParams = listOf(wallMidX), yParams = listOf(wallRightY2), boxParams = { vs -> RectF(vs[wallMidX], vs[wallRightY2], 1f, 1f) } ) } } internal fun collage_5_1(): CollageLayout { val item = CollageLayoutFactory.collage("collage_5_1") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 2f) photoItemList.add(photoItem) //five frame photoItem = PhotoItem() photoItem.index = 4 photoItem.disableShrink = true photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(0.625f, 0.375f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.625f, 0.625f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0.375f, 0.625f)) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(0.375f, 0.375f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_5_0(): CollageLayout { return CollageLayoutFactory.collage("collage_5_0") { val wall1X = param(0.25f) val wall2X = param(0.75f) val wall3Y = param(0.25f) val wall4Y = param(0.75f) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall4Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall4Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X, wall2X), yParams = listOf(wall3Y, wall4Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall3Y], vs[wall2X], vs[wall4Y]) } ) addBoxedItem( xParams = listOf(wall2X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall2X], vs[wall3Y], 1f, 1f) } ) addBoxedItem( xParams = listOf(wall2X), yParams = listOf(wall4Y), boxParams = { vs -> RectF(0f, vs[wall4Y], vs[wall2X], 1f) } ) } } internal fun collage_5_1_1(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_5_1_1", count = 5, isHorizontal = true ) } internal fun collage_5_1_2(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_5_1_2", count = 5, isHorizontal = false ) } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/FourFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.view.PhotoItem /** * Created by admin on 6/20/2016. */ internal object FourFrameImage { internal fun collage_4_25(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_25") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.6667f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.6667f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_24(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_24") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.3f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.6667f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.2f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.3333f)) photoItem.pointList.add(PointF(1f, 0.6667f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.4f, 1f, 0.8f) photoItem.pointList.add(PointF(0f, 0.25f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.75f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.7f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.3333f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_23(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_23") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.6f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.6667f, 0f)) photoItem.pointList.add(PointF(1f, 0.8333f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.6f)) photoItem.pointList.add(PointF(0.25f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.3f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.2857f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.3333f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.6667f, 1f) photoItem.pointList.add(PointF(0f, 0.2f)) photoItem.pointList.add(PointF(0.75f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_22(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_22") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.8f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.4f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.1666f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.2f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.6f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.8333f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_21(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_21") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.25f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0.3333f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.25f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.75f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.75f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.75f)) photoItem.pointList.add(PointF(0.6667f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_20(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_20") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.75f, 0.6667f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.6667f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.3333f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.3333f, 1f, 1f) photoItem.pointList.add(PointF(0.6667f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.3333f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6667f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_19(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_19") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_18(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_18") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_17(): CollageLayout { return CollageLayoutFactory.collage("collage_4_17") { val wall1X = param(0.5f) val wall2Y = param(0.25f) val wall3Y = param(0.75f) addBoxedItem( yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y, wall3Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y, wall3Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall2Y], 1f, vs[wall3Y]) } ) addBoxedItem( yParams = listOf(wall3Y), boxParams = { vs -> RectF(0f, vs[wall3Y], 1f, 1f) } ) } } internal fun collage_4_16(): CollageLayout { return CollageLayoutFactory.collage("collage_4_16") { val wall1X = param(0.3333f) val wall2Y = param(0.3333f) val wall3Y = param(0.6666f) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y, wall3Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall3Y]) } ) addBoxedItem( yParams = listOf(wall3Y), boxParams = { vs -> RectF(0f, vs[wall3Y], 1f, 1f) } ) } } internal fun collage_4_15(): CollageLayout { return CollageLayoutFactory.collage("collage_4_15") { val wall1X = param(0.6667f) val wall2Y = param(0.3333f) val wall3Y = param(0.6666f) addBoxedItem( yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y, wall3Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall2Y], 1f, vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall3Y], 1f, 1f) } ) } } internal fun collage_4_14(): CollageLayout { return collage_4_0("collage_4_14", 0.3333f, 0.6667f) } internal fun collage_4_13(): CollageLayout { return collage_4_0("collage_4_13", 0.6667f, 0.3333f) } internal fun collage_4_12(): CollageLayout { return CollageLayoutFactory.collage("collage_4_12") { val wall1X = param(0.5f) val wall2Y = param(0.3333f) val wall3Y = param(0.6666f) addBoxedItem( xParams = listOf(wall1X), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y, wall3Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(0f, vs[wall3Y], vs[wall1X], 1f) } ) } } internal fun collage_4_11(): CollageLayout { return CollageLayoutFactory.collage("collage_4_11") { val wall1X = param(0.5f) val wall2Y = param(0.3333f) val wall3Y = param(0.6666f) addBoxedItem( xParams = listOf(wall1X), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y, wall3Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall2Y], 1f, vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall3Y], 1f, 1f) } ) } } internal fun collage_4_10(): CollageLayout { return CollageLayoutFactory.collage("collage_4_10") { val wall1X = param(0.3333f) val wall2X = param(0.6666f) val wall3Y = param(0.5f) addBoxedItem( yParams = listOf(wall3Y), boxParams = { vs -> RectF(0f, vs[wall3Y], 1f, 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X, wall2X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], 0f, vs[wall2X], vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall2X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall2X], 0f, 1f, vs[wall3Y]) } ) } } internal fun collage_4_9(): CollageLayout { return CollageLayoutFactory.collage("collage_4_9") { val wall1X = param(0.3333f) val wall2X = param(0.6666f) val wallY = param(0.5f) addBoxedItem( yParams = listOf(wallY), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wallY]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wallY), boxParams = { vs -> RectF(0f, vs[wallY], vs[wall1X], 1f) } ) addBoxedItem( xParams = listOf(wall1X, wall2X), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wall1X], vs[wallY], vs[wall2X], 1f) } ) addBoxedItem( xParams = listOf(wall2X), yParams = listOf(wallY), boxParams = { vs -> RectF(vs[wall2X], vs[wallY], 1f, 1f) } ) } } internal fun collage_4_8(): CollageLayout { return CollageLayoutFactory.collage("collage_4_8") { val wall1X = param(0.4f) val wall2X = param(0.6f) val wall3Y = param(0.4f) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall2X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall2X], vs[wall3Y], 1f, 1f) } ) addBoxedItem( xParams = listOf(wall2X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(0f, vs[wall3Y], vs[wall2X], 1f) } ) } } internal fun collage_4_7(): CollageLayout { return CollageLayoutFactory.collage("collage_4_7") { val wall1X = param(0.5f) val wall2Y = param(0.3333f) val wall3Y = param(0.6667f) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall3Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall3Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall3Y], 1f, 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], 1f) } ) } } internal fun collage_4_6(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_6") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.3333f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0f, 1f, 0.6667f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0.6667f, 0.6667f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.3333f, 0.6667f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_5(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_5") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.6667f, 0.6667f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.6667f, 0f, 1f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0.3333f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.bound.set(0f, 0.6667f, 0.3333f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_4(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_4") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.6667f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0f, 1f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.1666f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.4444f, 0.3333f, 1f, 0.6666f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.2f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5555f, 0.6666f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.25f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_2(): CollageLayout { val item = CollageLayoutFactory.collage("collage_4_2") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.6667f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.75f, 1f)) photoItem.pointList.add(PointF(0f, 0.6667f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.6667f) photoItem.pointList.add(PointF(0.3333f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.75f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.25f, 0f)) photoItem.pointList.add(PointF(1f, 0.3333f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //four frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.3333f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.25f)) photoItem.pointList.add(PointF(0.6667f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_4_1(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_4_1", count = 4, isHorizontal = true ) } internal fun collage_4_1_1(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_4_1_1", count = 4, isHorizontal = false ) } internal fun collage_4_0( name: String = "collage_4_0", initialX: Float = 0.5f, initialY: Float = 0.5f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wall1X = param(initialX) val wall2Y = param(initialY) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall2Y], 1f, 1f) } ) } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/NineFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.view.PhotoItem /** * All points of polygon must be ordered by clockwise along

* Created by admin on 7/3/2016. */ internal object NineFrameImage { internal fun collage_9_11(): CollageLayout { val item = CollageLayoutFactory.collage("collage_9_11") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.2666f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.7519f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.2f, 0f, 0.8f, 0.3333f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.8889f, 1f)) photoItem.pointList.add(PointF(0.1111f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 8 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.7334f, 0f, 1f, 0.3333f) photoItem.pointList.add(PointF(0.2481f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.3333f, 0.3333f, 0.6666f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.8f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.2666f, 0.3333f, 0.7334f, 0.6666f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.8572f, 1f)) photoItem.pointList.add(PointF(0.1428f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //sixth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6666f, 0.3333f, 1f, 0.6666f) photoItem.pointList.add(PointF(0.2f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //seventh frame photoItem = PhotoItem() photoItem.index = 5 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6666f, 0.4f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.8333f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //eighth frame photoItem = PhotoItem() photoItem.index = 6 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.3333f, 0.6666f, 0.6666f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.8f, 1f)) photoItem.pointList.add(PointF(0.2f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //ninth frame photoItem = PhotoItem() photoItem.index = 7 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6f, 0.6666f, 1f, 1f) photoItem.pointList.add(PointF(0.1666f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_9_10(): CollageLayout { val item = CollageLayoutFactory.collage("collage_9_10") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.39645f, 0.39645f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.73881f, 0f)) photoItem.pointList.add(PointF(1f, 0.6306f)) photoItem.pointList.add(PointF(0.6306f, 1f)) photoItem.pointList.add(PointF(0f, 0.73881f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.2929f, 0f, 0.7071f, 0.25f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.75f, 1f)) photoItem.pointList.add(PointF(0.25f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 8 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.60355f, 0f, 1f, 0.39645f) photoItem.pointList.add(PointF(0.26119f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.73881f)) photoItem.pointList.add(PointF(0.3694f, 1f)) photoItem.pointList.add(PointF(0f, 0.6306f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.75f, 0.2929f, 1f, 0.7071f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.75f)) photoItem.pointList.add(PointF(0f, 0.25f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.60355f, 0.60355f, 1f, 1f) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.26199f, 1f)) photoItem.pointList.add(PointF(0f, 0.3694f)) photoItem.pointList.add(PointF(0.3694f, 0f)) photoItem.pointList.add(PointF(1f, 0.26199f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItemList.add(photoItem) //sixth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.2929f, 0.75f, 0.7071f, 1f) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0.25f, 0f)) photoItem.pointList.add(PointF(0.75f, 0f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //seventh frame photoItem = PhotoItem() photoItem.index = 5 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.60355f, 0.39645f, 1f) photoItem.pointList.add(PointF(0.6306f, 0f)) photoItem.pointList.add(PointF(1f, 0.3694f)) photoItem.pointList.add(PointF(0.73881f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.26199f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 1f) photoItemList.add(photoItem) //eighth frame photoItem = PhotoItem() photoItem.index = 6 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.2929f, 0.25f, 0.7071f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.25f)) photoItem.pointList.add(PointF(1f, 0.75f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //ninth frame photoItem = PhotoItem() photoItem.index = 7 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0.2929f, 0f)) photoItem.pointList.add(PointF(0.7071f, 0f)) photoItem.pointList.add(PointF(1f, 0.2929f)) photoItem.pointList.add(PointF(1f, 0.7071f)) photoItem.pointList.add(PointF(0.7071f, 1f)) photoItem.pointList.add(PointF(0.2929f, 1f)) photoItem.pointList.add(PointF(0f, 0.7071f)) photoItem.pointList.add(PointF(0f, 0.2929f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[6]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[7]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_9_9(): CollageLayout { val item = CollageLayoutFactory.collage("collage_9_9") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.3f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.6f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.3f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.6f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 8 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.3f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.4f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.7f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0.6f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.7f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.4f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //sixth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.7f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.4f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //seventh frame photoItem = PhotoItem() photoItem.index = 5 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.7f, 0.5f, 1f) photoItem.pointList.add(PointF(0.6f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //eighth frame photoItem = PhotoItem() photoItem.index = 6 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.3f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.4f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //ninth frame photoItem = PhotoItem() photoItem.index = 7 photoItem.bound.set(0.3f, 0.3f, 0.7f, 0.7f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_9_8(): CollageLayout { return CollageLayoutFactory.collage("collage_9_8") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val x4 = param(0.3333f) val x5 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, vs[x3], vs[y1]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x3], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x4), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x4], vs[y2]) } ) addBoxedItem( xParams = listOf(x4, x5), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x4], vs[y1], vs[x5], vs[y2]) } ) addBoxedItem( xParams = listOf(x5), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x5], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_9_7(): CollageLayout { return CollageLayoutFactory.collage("collage_9_7") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val x4 = param(0.3333f) val x5 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, vs[x3], vs[y1]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x3], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x4), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x4], 1f) } ) addBoxedItem( xParams = listOf(x4, x5), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x4], vs[y2], vs[x5], 1f) } ) addBoxedItem( xParams = listOf(x5), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x5], vs[y2], 1f, 1f) } ) } } internal fun collage_9_6(): CollageLayout { return CollageLayoutFactory.collage("collage_9_6") { val x1 = param(0.2f) val x2 = param(0.4f) val x3 = param(0.6f) val x4 = param(0.8f) val y1 = param(0.5f) addBoxedItem( xParams = listOf(x1), boxParams = { vs -> RectF(0f, 0f, vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, vs[x3], vs[y1]) } ) addBoxedItem( xParams = listOf(x3, x4), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x3], 0f, vs[x4], vs[y1]) } ) addBoxedItem( xParams = listOf(x4), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x4], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], vs[y1], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3, x4), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x3], vs[y1], vs[x4], 1f) } ) addBoxedItem( xParams = listOf(x4), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x4], vs[y1], 1f, 1f) } ) } } internal fun collage_9_5(): CollageLayout { return CollageLayoutFactory.collage("collage_9_5") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x2], vs[y3], 1f, 1f) } ) } } internal fun collage_9_4(): CollageLayout { return CollageLayoutFactory.collage("collage_9_4") { val xL = param(0.3333f) val xQ1 = param(0.25f) val xQ2 = param(0.5f) val xQ3 = param(0.75f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(xL), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[xL], vs[y1]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xL], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(xQ1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, vs[y1], vs[xQ1], 1f) } ) addBoxedItem( xParams = listOf(xQ1, xQ2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xQ1], vs[y1], vs[xQ2], vs[y2]) } ) addBoxedItem( xParams = listOf(xQ2, xQ3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xQ2], vs[y1], vs[xQ3], vs[y2]) } ) addBoxedItem( xParams = listOf(xQ3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xQ3], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(xQ1, xQ2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[xQ1], vs[y2], vs[xQ2], 1f) } ) addBoxedItem( xParams = listOf(xQ2, xQ3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[xQ2], vs[y2], vs[xQ3], 1f) } ) addBoxedItem( xParams = listOf(xQ3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[xQ3], vs[y2], 1f, 1f) } ) } } internal fun collage_9_3(): CollageLayout { return CollageLayoutFactory.collage("collage_9_3") { val x1 = param(0.2f) val x2 = param(0.4f) val x3 = param(0.6f) val x4 = param(0.8f) val y1 = param(0.2f) val y2 = param(0.4f) val y3 = param(0.6f) val y4 = param(0.8f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2, y4), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], vs[y4]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y4), boxParams = { vs -> RectF(0f, vs[y4], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2, x4), yParams = listOf(y4), boxParams = { vs -> RectF(vs[x2], vs[y4], vs[x4], 1f) } ) addBoxedItem( xParams = listOf(x4), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x4], vs[y3], 1f, 1f) } ) addBoxedItem( xParams = listOf(x4), yParams = listOf(y1, y3), boxParams = { vs -> RectF(vs[x4], vs[y1], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x1, x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x3], vs[y1]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x3], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x4), yParams = listOf(y1, y4), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x4], vs[y4]) } ) } } internal fun collage_9_2(): CollageLayout { return CollageLayoutFactory.collage("collage_9_2") { val xM = param(0.5f) val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(xM), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[xM], vs[y1]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xM], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[xM], vs[y2]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xM], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[xM], vs[y3]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[xM], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x1], vs[y3], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x2], vs[y3], 1f, 1f) } ) } } internal fun collage_9_1(): CollageLayout { return CollageLayoutFactory.collage("collage_9_1") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_9_0(): CollageLayout { return CollageLayoutFactory.collage("collage_9_0") { val x1 = param(0.25f) val x2 = param(0.75f) val y1 = param(0.25f) val y2 = param(0.75f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/SevenFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.view.PhotoItem /** * Created by admin on 6/30/2016. */ internal object SevenFrameImage { internal fun collage_7_10(): CollageLayout { val item = CollageLayoutFactory.collage("collage_7_10") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.25f, 0.6471f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.7727f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0f, 0.85f, 0.3529f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0.7f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.4118f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.75f, 0.3529f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.2273f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.15f, 0.6471f, 0.75f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //sixth frame photoItem = PhotoItem() photoItem.index = 5 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5882f)) photoItem.pointList.add(PointF(0.3f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //seventh frame photoItem = PhotoItem() photoItem.index = 6 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.2059f, 0.75f, 0.7941f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.25f)) photoItem.pointList.add(PointF(1f, 0.75f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.75f)) photoItem.pointList.add(PointF(0f, 0.25f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_7_9(): CollageLayout { return collage_7_8(name = "collage_7_9", y = 0.25f) } internal fun collage_7_8(name: String = "collage_7_8", y: Float = 0.75f): CollageLayout { return CollageLayoutFactory.collage(name) { val x1 = param(0.3f) val x2 = param(0.6f) val yLeft = param(y) val yR1 = param(0.3333f) val yR2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(yLeft), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[yLeft]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yLeft), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[yLeft]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(yLeft), boxParams = { vs -> RectF(0f, vs[yLeft], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yLeft), boxParams = { vs -> RectF(vs[x1], vs[yLeft], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yR1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[yR1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yR1, yR2), boxParams = { vs -> RectF(vs[x2], vs[yR1], 1f, vs[yR2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yR2), boxParams = { vs -> RectF(vs[x2], vs[yR2], 1f, 1f) } ) } } internal fun collage_7_7(): CollageLayout { return CollageLayoutFactory.collage("collage_7_7") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], 1f, 1f) } ) } } internal fun collage_7_6(): CollageLayout { return CollageLayoutFactory.collage("collage_7_6") { val x = param(0.6666f) val y0 = param(0.3333f) val y1 = param(0.6666f) val r1 = param(0.25f) val r2 = param(0.5f) val r3 = param(0.75f) addBoxedItem( xParams = listOf(x), yParams = listOf(y0), boxParams = { vs -> RectF(0f, 0f, vs[x], vs[y0]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y0, y1), boxParams = { vs -> RectF(0f, vs[y0], vs[x], vs[y1]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(y1), boxParams = { vs -> RectF(0f, vs[y1], vs[x], 1f) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(r1), boxParams = { vs -> RectF(vs[x], 0f, 1f, vs[r1]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(r1, r2), boxParams = { vs -> RectF(vs[x], vs[r1], 1f, vs[r2]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(r2, r3), boxParams = { vs -> RectF(vs[x], vs[r2], 1f, vs[r3]) } ) addBoxedItem( xParams = listOf(x), yParams = listOf(r3), boxParams = { vs -> RectF(vs[x], vs[r3], 1f, 1f) } ) } } internal fun collage_7_5(): CollageLayout { return CollageLayoutFactory.collage("collage_7_5") { val x1 = param(0.3333f) val x2 = param(0.6666f) val x3 = param(0.5f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x3], vs[y2]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x3], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x3], vs[y2], 1f, 1f) } ) } } internal fun collage_7_4(): CollageLayout { return CollageLayoutFactory.collage("collage_7_4") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_7_3(): CollageLayout { return CollageLayoutFactory.collage("collage_7_3") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_7_2(): CollageLayout { return CollageLayoutFactory.collage("collage_7_2") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, 1f) } ) } } internal fun collage_7_1(): CollageLayout { return CollageLayoutFactory.collage("collage_7_1") { val x0 = param(0.25f) val x1 = param(0.5f) val x2 = param(0.75f) val y = param(0.5f) addBoxedItem( xParams = listOf(x0), boxParams = { vs -> RectF(0f, 0f, vs[x0], 1f) } ) addBoxedItem( xParams = listOf(x0, x1), yParams = listOf(y), boxParams = { vs -> RectF(vs[x0], 0f, vs[x1], vs[y]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y]) } ) addBoxedItem( xParams = listOf(x0, x1), yParams = listOf(y), boxParams = { vs -> RectF(vs[x0], vs[y], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x1], vs[y], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y), boxParams = { vs -> RectF(vs[x2], vs[y], 1f, 1f) } ) } } internal fun collage_7_0(): CollageLayout { return CollageLayoutFactory.collage("collage_7_0") { val xL = param(0.3333f) val xTopSplit = param(0.5555f) val xRightSplit = param(0.7777f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(xL), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[xL], vs[y1]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[xL], vs[y2]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[xL], 1f) } ) addBoxedItem( xParams = listOf(xL, xTopSplit), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xL], 0f, vs[xTopSplit], vs[y1]) } ) addBoxedItem( xParams = listOf(xTopSplit), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xTopSplit], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(xL, xRightSplit), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xL], vs[y1], vs[xRightSplit], 1f) } ) addBoxedItem( xParams = listOf(xRightSplit), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xRightSplit], vs[y1], 1f, 1f) } ) } } internal fun collage_7_1_1(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_7_1_1", count = 7, isHorizontal = true ) } internal fun collage_7_1_2(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_7_1_2", count = 7, isHorizontal = false ) } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/SixFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.view.PhotoItem /** * Created by admin on 6/26/2016. */ internal object SixFrameImage { internal fun collage_6_14(): CollageLayout { val item = CollageLayoutFactory.collage("collage_6_14") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.4f, 0.6f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.625f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.4f, 0f, 1f, 0.3f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.3333f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6f, 0f, 1f, 0.6f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.375f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.6f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 2f) photoItemList.add(photoItem) //sixth frame photoItem = PhotoItem() photoItem.index = 5 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.25f, 0.3f, 0.75f, 0.8f) photoItem.pointList.add(PointF(0.3f, 0f)) photoItem.pointList.add(PointF(0.7f, 0f)) photoItem.pointList.add(PointF(1f, 0.6f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.6f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_6_13(): CollageLayout { val item = CollageLayoutFactory.collage("collage_6_13") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.35f, 0f, 1f, 0.4f) photoItem.pointList.add(PointF(0.2308f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.3846f, 1f)) photoItem.pointList.add(PointF(0f, 0.375f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //fourth frame photoItem = PhotoItem() photoItem.index = 3 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.15f, 0.6f, 1f) photoItem.pointList.add(PointF(0.5833f, 0f)) photoItem.pointList.add(PointF(1f, 0.2941f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.4118f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //fifth frame photoItem = PhotoItem() photoItem.index = 4 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6f, 0.65f, 1f) photoItem.pointList.add(PointF(0.6154f, 0f)) photoItem.pointList.add(PointF(1f, 0.625f)) photoItem.pointList.add(PointF(0.7692f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //sixth frame photoItem = PhotoItem() photoItem.index = 5 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.4f, 0f, 1f, 0.85f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5882f)) photoItem.pointList.add(PointF(0.4166f, 1f)) photoItem.pointList.add(PointF(0f, 0.7059f)) photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_6_12(): CollageLayout { return CollageLayoutFactory.collage("collage_6_12") { val topY = param(0.2f) val midY = param(0.7f) val x1 = param(0.3333f) val x2 = param(0.6666f) val bottomSplitX = param(0.5f) addBoxedItem( yParams = listOf(topY), boxParams = { vs -> RectF(0f, 0f, 1f, vs[topY]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(topY, midY), boxParams = { vs -> RectF(0f, vs[topY], vs[x1], vs[midY]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(topY, midY), boxParams = { vs -> RectF(vs[x1], vs[topY], vs[x2], vs[midY]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(topY, midY), boxParams = { vs -> RectF(vs[x2], vs[topY], 1f, vs[midY]) } ) addBoxedItem( xParams = listOf(bottomSplitX), yParams = listOf(midY), boxParams = { vs -> RectF(0f, vs[midY], vs[bottomSplitX], 1f) } ) addBoxedItem( xParams = listOf(bottomSplitX), yParams = listOf(midY), boxParams = { vs -> RectF(vs[bottomSplitX], vs[midY], 1f, 1f) } ) } } internal fun collage_6_11(): CollageLayout { return CollageLayoutFactory.collage("collage_6_11") { val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) val midX = param(0.5f) addBoxedItem( yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(midX), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[midX], vs[y2]) } ) addBoxedItem( xParams = listOf(midX), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[midX], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(midX), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[midX], vs[y3]) } ) addBoxedItem( xParams = listOf(midX), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[midX], vs[y2], 1f, vs[y3]) } ) addBoxedItem( yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], 1f, 1f) } ) } } internal fun collage_6_10(): CollageLayout { return CollageLayoutFactory.collage("collage_6_10") { val rightX = param(0.6666f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(rightX), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[rightX], vs[y1]) } ) addBoxedItem( xParams = listOf(rightX), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[rightX], vs[y2]) } ) addBoxedItem( xParams = listOf(rightX), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[rightX], vs[y3]) } ) addBoxedItem( xParams = listOf(rightX), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[rightX], 1f) } ) addBoxedItem( xParams = listOf(rightX), yParams = listOf(y1), boxParams = { vs -> RectF(vs[rightX], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(rightX), yParams = listOf(y1), boxParams = { vs -> RectF(vs[rightX], vs[y1], 1f, 1f) } ) } } internal fun collage_6_9(): CollageLayout { return CollageLayoutFactory.collage("collage_6_9") { val x1 = param(0.3333f) val x2 = param(0.6666f) val yTop = param(0.3333f) val yRightSplit = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(yTop), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[yTop]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yTop), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[yTop]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yTop), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[yTop]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yTop), boxParams = { vs -> RectF(0f, vs[yTop], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yTop, yRightSplit), boxParams = { vs -> RectF(vs[x2], vs[yTop], 1f, vs[yRightSplit]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yRightSplit), boxParams = { vs -> RectF(vs[x2], vs[yRightSplit], 1f, 1f) } ) } } internal fun collage_6_8(): CollageLayout { return CollageLayoutFactory.collage("collage_6_8") { val x1 = param(0.3333f) val x2 = param(0.6666f) val midY = param(0.5f) addBoxedItem( xParams = listOf(x1), yParams = listOf(midY), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[midY]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(midY), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[midY]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(midY), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[midY]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(midY), boxParams = { vs -> RectF(0f, vs[midY], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(midY), boxParams = { vs -> RectF(vs[x1], vs[midY], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(midY), boxParams = { vs -> RectF(vs[x2], vs[midY], 1f, 1f) } ) } } internal fun collage_6_7(): CollageLayout { return CollageLayoutFactory.collage("collage_6_7") { val x1 = param(0.3333f) val x2 = param(0.6666f) val y1 = param(0.3333f) val y2 = param(0.6666f) val yTop = param(0.4f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(yTop), boxParams = { vs -> RectF(vs[x1], vs[yTop], 1f, 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(yTop), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[yTop]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(yTop), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[yTop]) } ) } } internal fun collage_6_6(): CollageLayout { return CollageLayoutFactory.collage("collage_6_6") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(0f, vs[y1], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x2], vs[y2], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x3], vs[y2], 1f, 1f) } ) } } internal fun collage_6_5(): CollageLayout { return collage_6_1(name = "collage_6_5", x1 = 0.6667f, x2 = 0.3333f, x3 = 0.6667f) } internal fun collage_6_4(): CollageLayout { return collage_6_3(name = "collage_6_4", initial_x1 = 0.5f) } internal fun collage_6_3( name: String = "collage_6_3", initial_x1: Float = 0.3333f, initial_y1: Float = 0.3333f, initial_y2: Float = 0.6666f ): CollageLayout { return CollageLayoutFactory.collage(name) { val x1 = param(initial_x1) val y1 = param(initial_y1) val y2 = param(initial_y2) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], 1f, 1f) } ) } } internal fun collage_6_2(): CollageLayout { return collage_6_1(name = "collage_6_2", x3 = 0.3333f) } internal fun collage_6_1( name: String = "collage_6_1", x1: Float = 0.3333f, x2: Float = 0.5f, x3: Float = 0.6666f, y1: Float = 0.3333f, y2: Float = 0.6666f ): CollageLayout { return CollageLayoutFactory.collage(name) { val xTopSplit = param(x1) val y1 = param(y1) val midX = param(x2) val y2 = param(y2) val bottomSplitX = param(x3) addBoxedItem( xParams = listOf(xTopSplit), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[xTopSplit], vs[y1]) } ) addBoxedItem( xParams = listOf(xTopSplit), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xTopSplit], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(midX), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[midX], vs[y2]) } ) addBoxedItem( xParams = listOf(midX), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[midX], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(bottomSplitX), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[bottomSplitX], 1f) } ) addBoxedItem( xParams = listOf(bottomSplitX), yParams = listOf(y2), boxParams = { vs -> RectF(vs[bottomSplitX], vs[y2], 1f, 1f) } ) } } internal fun collage_6_0(): CollageLayout { return CollageLayoutFactory.collage("collage_6_0") { val leftX = param(0.3333f) val y1 = param(0.3333f) val y2 = param(0.6667f) val bottomSplitX = param(0.6667f) addBoxedItem( xParams = listOf(leftX), yParams = listOf(y2), boxParams = { vs -> RectF(0f, 0f, vs[leftX], vs[y2]) } ) addBoxedItem( xParams = listOf(leftX), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[leftX], 1f) } ) addBoxedItem( xParams = listOf(leftX), yParams = listOf(y1), boxParams = { vs -> RectF(vs[leftX], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(leftX), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[leftX], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(leftX, bottomSplitX), yParams = listOf(y2), boxParams = { vs -> RectF(vs[leftX], vs[y2], vs[bottomSplitX], 1f) } ) addBoxedItem( xParams = listOf(bottomSplitX), yParams = listOf(y2), boxParams = { vs -> RectF(vs[bottomSplitX], vs[y2], 1f, 1f) } ) } } internal fun collage_6_1_1(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_6_1_1", count = 6, isHorizontal = true ) } internal fun collage_6_1_2(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_6_1_2", count = 6, isHorizontal = false ) } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/TenFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory /** * Created by admin on 7/4/2016. */ internal object TenFrameImage { internal fun collage_10_8(): CollageLayout { return CollageLayoutFactory.collage("collage_10_8") { val x1 = param(0.2f) val x2 = param(0.5f) val x3 = param(0.8f) val y1 = param(0.2f) val y2 = param(0.5f) val y3 = param(0.8f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y3), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y3]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y1, y3), boxParams = { vs -> RectF(vs[x2], vs[y1], vs[x3], vs[y3]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], vs[y3]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x3], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x3], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x3], vs[y3], 1f, 1f) } ) } } internal fun collage_10_7(): CollageLayout { return CollageLayoutFactory.collage("collage_10_7") { val x1 = param(0.2f) val x2 = param(0.6f) val y1 = param(0.2f) val y2 = param(0.5f) val y3 = param(0.8f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], vs[y3]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], vs[y3]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x2], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x2], vs[y3], 1f, 1f) } ) } } internal fun collage_10_6(): CollageLayout { return CollageLayoutFactory.collage("collage_10_6") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val y1 = param(0.25f) val y2 = param(0.5f) val y3 = param(0.75f) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y3), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y3]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], vs[x3], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], vs[y3]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x2], vs[y2], vs[x3], vs[y3]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1, y3), boxParams = { vs -> RectF(vs[x3], vs[y1], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x2], vs[y3], 1f, 1f) } ) } } internal fun collage_10_5(): CollageLayout { return CollageLayoutFactory.collage("collage_10_5") { val xL = param(0.2f) val xM = param(0.5f) val xR = param(0.8f) val yT = param(0.25f) val yM = param(0.5f) val yB = param(0.75f) addBoxedItem( xParams = listOf(xM), yParams = listOf(yT), boxParams = { vs -> RectF(0f, 0f, vs[xM], vs[yT]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(yT), boxParams = { vs -> RectF(vs[xM], 0f, 1f, vs[yT]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(yT, yM), boxParams = { vs -> RectF(0f, vs[yT], vs[xL], vs[yM]) } ) addBoxedItem( xParams = listOf(xL, xR), yParams = listOf(yT, yM), boxParams = { vs -> RectF(vs[xL], vs[yT], vs[xR], vs[yM]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(yT, yM), boxParams = { vs -> RectF(vs[xR], vs[yT], 1f, vs[yM]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(yM, yB), boxParams = { vs -> RectF(0f, vs[yM], vs[xL], vs[yB]) } ) addBoxedItem( xParams = listOf(xL, xR), yParams = listOf(yM, yB), boxParams = { vs -> RectF(vs[xL], vs[yM], vs[xR], vs[yB]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(yM, yB), boxParams = { vs -> RectF(vs[xR], vs[yM], 1f, vs[yB]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(yB), boxParams = { vs -> RectF(0f, vs[yB], vs[xM], 1f) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(yB), boxParams = { vs -> RectF(vs[xM], vs[yB], 1f, 1f) } ) } } internal fun collage_10_4(): CollageLayout { return CollageLayoutFactory.collage("collage_10_4") { val xM = param(0.5f) val xR = param(0.8f) val y1 = param(0.2f) val y2 = param(0.5f) val y3 = param(0.8f) addBoxedItem( xParams = listOf(xM), yParams = listOf(y2), boxParams = { vs -> RectF(0f, 0f, vs[xM], vs[y2]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[xM], 1f) } ) addBoxedItem( xParams = listOf(xM, xR), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xM], 0f, vs[xR], vs[y1]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xR], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(xM, xR), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xM], vs[y1], vs[xR], vs[y2]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xR], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(xM, xR), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[xM], vs[y2], vs[xR], vs[y3]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[xR], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(xM, xR), yParams = listOf(y3), boxParams = { vs -> RectF(vs[xM], vs[y3], vs[xR], 1f) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y3), boxParams = { vs -> RectF(vs[xR], vs[y3], 1f, 1f) } ) } } internal fun collage_10_3(): CollageLayout { return CollageLayoutFactory.collage("collage_10_3") { val xL = param(0.7f) val xR = param(0.9f) val xS = param(0.3f) val y1 = param(0.3f) val y2 = param(0.7f) val y3 = param(0.9f) addBoxedItem( xParams = listOf(xL), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[xL], vs[y1]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(y1), boxParams = { vs -> RectF(vs[xL], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[xL], vs[y2]) } ) addBoxedItem( xParams = listOf(xL, xR), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xL], vs[y1], vs[xR], vs[y2]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[xR], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(xS), yParams = listOf(y2, y3), boxParams = { vs -> RectF(0f, vs[y2], vs[xS], vs[y3]) } ) addBoxedItem( xParams = listOf(xS), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[xS], 1f) } ) addBoxedItem( xParams = listOf(xS, xL), yParams = listOf(y2), boxParams = { vs -> RectF(vs[xS], vs[y2], vs[xL], 1f) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[xL], vs[y2], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(y3), boxParams = { vs -> RectF(vs[xL], vs[y3], 1f, 1f) } ) } } internal fun collage_10_2(): CollageLayout { return CollageLayoutFactory.collage("collage_10_2") { val xM = param(0.5f) val xL = param(0.2f) val xR = param(0.8f) val yM = param(0.5f) val yB = param(0.8f) addBoxedItem( xParams = listOf(xM), yParams = listOf(yM), boxParams = { vs -> RectF(0f, 0f, vs[xM], vs[yM]) } ) addBoxedItem( xParams = listOf(xM), yParams = listOf(yM), boxParams = { vs -> RectF(vs[xM], 0f, 1f, vs[yM]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(yM, yB), boxParams = { vs -> RectF(0f, vs[yM], vs[xL], vs[yB]) } ) addBoxedItem( xParams = listOf(xL, xM), yParams = listOf(yM, yB), boxParams = { vs -> RectF(vs[xL], vs[yM], vs[xM], vs[yB]) } ) addBoxedItem( xParams = listOf(xM, xR), yParams = listOf(yM, yB), boxParams = { vs -> RectF(vs[xM], vs[yM], vs[xR], vs[yB]) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(yM, yB), boxParams = { vs -> RectF(vs[xR], vs[yM], 1f, vs[yB]) } ) addBoxedItem( xParams = listOf(xL), yParams = listOf(yB), boxParams = { vs -> RectF(0f, vs[yB], vs[xL], 1f) } ) addBoxedItem( xParams = listOf(xL, xM), yParams = listOf(yB), boxParams = { vs -> RectF(vs[xL], vs[yB], vs[xM], 1f) } ) addBoxedItem( xParams = listOf(xM, xR), yParams = listOf(yB), boxParams = { vs -> RectF(vs[xM], vs[yB], vs[xR], 1f) } ) addBoxedItem( xParams = listOf(xR), yParams = listOf(yB), boxParams = { vs -> RectF(vs[xR], vs[yB], 1f, 1f) } ) } } internal fun collage_10_1(): CollageLayout { return CollageLayoutFactory.collage("collage_10_1") { val x1 = param(0.25f) val x2 = param(0.5f) val x3 = param(0.75f) val y1 = param(0.3333f) val y2 = param(0.6666f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x3], vs[y1]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x3], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y2), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x2, x3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x2], vs[y1], vs[x3], vs[y2]) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x3], vs[y1], 1f, vs[y2]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y2), boxParams = { vs -> RectF(0f, vs[y2], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x3], 1f) } ) addBoxedItem( xParams = listOf(x3), yParams = listOf(y2), boxParams = { vs -> RectF(vs[x3], vs[y2], 1f, 1f) } ) } } internal fun collage_10_0(): CollageLayout { return CollageLayoutFactory.collage("collage_10_0") { val x1 = param(0.2f) val x2 = param(0.8f) val y1 = param(0.2f) val y2 = param(0.5f) val y3 = param(0.8f) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1), boxParams = { vs -> RectF(0f, 0f, vs[x1], vs[y1]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x1], 0f, vs[x2], vs[y1]) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1), boxParams = { vs -> RectF(vs[x2], 0f, 1f, vs[y1]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y1, y3), boxParams = { vs -> RectF(0f, vs[y1], vs[x1], vs[y3]) } ) addBoxedItem( xParams = listOf(x1), yParams = listOf(y3), boxParams = { vs -> RectF(0f, vs[y3], vs[x1], 1f) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x1], vs[y3], vs[x2], 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y3), boxParams = { vs -> RectF(vs[x2], vs[y3], 1f, 1f) } ) addBoxedItem( xParams = listOf(x2), yParams = listOf(y1, y3), boxParams = { vs -> RectF(vs[x2], vs[y1], 1f, vs[y3]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y1, y2), boxParams = { vs -> RectF(vs[x1], vs[y1], vs[x2], vs[y2]) } ) addBoxedItem( xParams = listOf(x1, x2), yParams = listOf(y2, y3), boxParams = { vs -> RectF(vs[x1], vs[y2], vs[x2], vs[y3]) } ) } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/ThreeFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.Path import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.utils.GeometryUtils import com.t8rin.collages.view.PhotoItem /** * Created by admin on 5/9/2016. */ internal object ThreeFrameImage { internal fun collage_3_47(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_47") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.75f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(0.75f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_46(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_46") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.4167f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.6f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.25f, 0f, 0.75f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.8333f, 1f)) photoItem.pointList.add(PointF(0.3333f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0.6666f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0.25f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_45(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_45") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.4f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.2f, 0f, 0.8f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.6667f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.3333f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.6f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_44(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_44") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.4167f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.6f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.25f, 1f, 0.75f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.3333f)) photoItem.pointList.add(PointF(1f, 0.8333f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6666f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.25f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_43(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_43") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.4f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.2f, 1f, 0.8f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.6667f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.3333f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_42(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_42") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.6f, 0.8f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.6667f, 0f)) photoItem.pointList.add(PointF(1f, 0.75f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.4f, 0f, 1f, 0.7f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.3333f, 0.8571f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.6f, 1f, 1f) photoItem.pointList.add(PointF(0.6f, 0f)) photoItem.pointList.add(PointF(1f, 0.25f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_41(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_41") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.6666f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0.3333f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.3333f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.6667f, 1f) photoItem.pointList.add(PointF(0f, 0.6667f)) photoItem.pointList.add(PointF(0.75f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_40(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_40") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_39(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_39") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_38(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_38") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_37(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_37") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_36(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_36") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_35(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_35") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_34(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_34") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_33(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_33") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0.5f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_32(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_32") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0.5f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_31(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_31") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_30(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_30") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0.5f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_29(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_29") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_28(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_28") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_27(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_27") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_26(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_26") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 1f) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_25(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_25") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_24(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_24") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_23(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_23") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0.5f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(2f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(0.5f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_22(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_22") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_COMMON photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(1f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(2f, 1f) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_21(): CollageLayout { return collage_3_18("collage_3_21", 0.5f, 0.3333f) } internal fun collage_3_20(): CollageLayout { return collage_3_18("collage_3_20", 0.5f, 0.6666f) } internal fun collage_3_19(): CollageLayout { return collage_3_16("collage_3_19", 0.25f, 0.75f) } internal fun collage_3_18( name: String = "collage_3_18", initialX: Float = 0.5f, initialY: Float = 0.5f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wall1X = param(initialX) val wall2Y = param(initialY) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall2Y]) } ) addBoxedItem( yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, vs[wall2Y], 1f, 1f) } ) } } internal fun collage_3_17(): CollageLayout { return CollageLayoutFactory.collage("collage_3_17") { val wall1X = param(0.5f) val wall2Y = param(0.3333f) addBoxedItem( yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall2Y], 1f, 1f) } ) } } internal fun collage_3_16( name: String = "collage_3_16", initialY1: Float = 0.3333f, initialY2: Float = 0.6666f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wall1Y = param(initialY1) val wall2Y = param(initialY2) addBoxedItem( yParams = listOf(wall1Y), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wall1Y]) } ) addBoxedItem( yParams = listOf(wall1Y, wall2Y), boxParams = { vs -> RectF(0f, vs[wall1Y], 1f, vs[wall2Y]) } ) addBoxedItem( yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, vs[wall2Y], 1f, 1f) } ) } } internal fun collage_3_15(): CollageLayout { return collage_3_12("collage_3_15", 0.6667f, 0.5f) } internal fun collage_3_14(): CollageLayout { return collage_3_12("collage_3_14", 0.3333f, 0.5f) } internal fun collage_3_13(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_13") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.fitBound = true photoItem.clearPath = Path() photoItem.clearPath!!.addRect(0f, 0f, 512f, 512f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(0.5f, 0.25f, 1.5f, 0.75f) photoItem.clearPathInCenterVertical = true photoItem.cornerMethod = PhotoItem.CORNER_METHOD_3_13 photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.fitBound = true photoItem.clearPath = Path() photoItem.clearPath!!.addRect(0f, 0f, 512f, 512f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(-0.5f, 0.25f, 0.5f, 0.75f) photoItem.clearPathInCenterVertical = true photoItem.cornerMethod = PhotoItem.CORNER_METHOD_3_13 photoItemList.add(photoItem) //third frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0.25f, 0f, 0.75f, 1f) photoItem.path = Path() photoItem.path!!.addRect(0f, 0f, 512f, 512f, Path.Direction.CCW) photoItem.pathRatioBound = RectF(0f, 0.25f, 1f, 0.75f) photoItem.pathInCenterVertical = true photoItem.pathInCenterHorizontal = true photoItem.fitBound = true photoItem.cornerMethod = PhotoItem.CORNER_METHOD_3_13 photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_12( name: String = "collage_3_12", initialX: Float = 0.5f, initialY: Float = 0.5f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wall1X = param(initialX) val wall2Y = param(initialY) addBoxedItem( xParams = listOf(wall1X), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(vs[wall1X], vs[wall2Y], 1f, 1f) } ) } } internal fun collage_3_11(): CollageLayout { return collage_3_5("collage_3_11", 0.6667f, 0.5f) } internal fun collage_3_10(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_10") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_USING_MAP photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.75f, 1f)) photoItem.pointList.add(PointF(0.75f, 0.5f)) photoItem.pointList.add(PointF(0.25f, 0.5f)) photoItem.pointList.add(PointF(0.25f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 2f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(-2f, 2f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(-2f, -1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, -1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, -1f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(-1f, -1f) photoItem.shrinkMap!![photoItem.pointList[6]] = PointF(-1f, -1f) photoItem.shrinkMap!![photoItem.pointList[7]] = PointF(2f, -1f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_USING_MAP photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(0.25f, 0f)) photoItem.pointList.add(PointF(0.25f, 0.5f)) photoItem.pointList.add(PointF(0.75f, 0.5f)) photoItem.pointList.add(PointF(0.75f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) //shrink map photoItem.shrinkMap = HashMap() photoItem.shrinkMap!![photoItem.pointList[0]] = PointF(2f, 1f) photoItem.shrinkMap!![photoItem.pointList[1]] = PointF(-1f, 1f) photoItem.shrinkMap!![photoItem.pointList[2]] = PointF(-1f, 1f) photoItem.shrinkMap!![photoItem.pointList[3]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[4]] = PointF(1f, 1f) photoItem.shrinkMap!![photoItem.pointList[5]] = PointF(-2f, 1f) photoItem.shrinkMap!![photoItem.pointList[6]] = PointF(-2f, -2f) photoItem.shrinkMap!![photoItem.pointList[7]] = PointF(2f, -2f) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_9(): CollageLayout { return collage_3_5("collage_3_9", 0.5f, 0.6667f) } internal fun collage_3_8(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_8") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(0.5f, 0.25f, 1.5f, 0.75f) photoItem.clearPathInCenterVertical = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(-0.5f, 0.25f, 0.5f, 0.75f) photoItem.clearPathInCenterVertical = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_3_8 photoItem.bound.set(0.25f, 0f, 0.75f, 1f) photoItem.path = Path() photoItem.path!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.pathRatioBound = RectF(0f, 0.25f, 1f, 0.75f) photoItem.pathInCenterVertical = true photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_7(): CollageLayout { return collage_3_5("collage_3_7", 0.3333f, 0.5f) } internal fun collage_3_6(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_6") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_3_6 photoItem.cornerMethod = PhotoItem.CORNER_METHOD_3_6 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() GeometryUtils.createRegularPolygonPath(photoItem.clearPath!!, 512f, 6, 0f) photoItem.clearPathRatioBound = RectF(0.5f, 0.25f, 1.5f, 0.75f) photoItem.clearPathInCenterVertical = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_3_6 photoItem.cornerMethod = PhotoItem.CORNER_METHOD_3_6 photoItem.index = 1 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() GeometryUtils.createRegularPolygonPath(photoItem.clearPath!!, 512f, 6, 0f) photoItem.clearPathRatioBound = RectF(-0.5f, 0.25f, 0.5f, 0.75f) photoItem.clearPathInCenterVertical = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_3_6 photoItem.cornerMethod = PhotoItem.CORNER_METHOD_3_6 photoItem.bound.set(0.25f, 0f, 0.75f, 1f) photoItem.path = Path() GeometryUtils.createRegularPolygonPath(photoItem.path!!, 512f, 6, 0f) photoItem.pathRatioBound = RectF(0f, 0.25f, 1f, 0.75f) photoItem.pathInCenterVertical = true photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_5( name: String = "collage_3_5", initialX: Float = 0.5f, initialY: Float = 0.5f ): CollageLayout { return CollageLayoutFactory.collage(name) { val wall1X = param(initialX) val wall2Y = param(initialY) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], vs[wall2Y]) } ) addBoxedItem( xParams = listOf(wall1X), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, 1f) } ) addBoxedItem( xParams = listOf(wall1X), yParams = listOf(wall2Y), boxParams = { vs -> RectF(0f, vs[wall2Y], vs[wall1X], 1f) } ) } } internal fun collage_3_4(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_4") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.clearPathRatioBound = RectF(0.25f, 0.5f, 0.75f, 1.5f) photoItem.clearPathInCenterHorizontal = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.clearPathRatioBound = RectF(0.25f, -0.5f, 0.75f, 0.5f) photoItem.clearPathInCenterHorizontal = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0f, 0.25f, 1f, 0.75f) photoItem.path = CollageLayoutFactory.createHeartItem(0f, 512f) photoItem.pathRatioBound = RectF(0f, 0f, 1f, 1f) photoItem.pathInCenterHorizontal = true photoItem.pathInCenterVertical = true photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_3(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_3") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_3_3 photoItem.bound.set(0f, 0f, 0.5f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 0.25f)) photoItem.pointList.add(PointF(0.5f, 0.5f)) photoItem.pointList.add(PointF(1f, 0.75f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_3_3 photoItem.bound.set(0.5f, 0f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.pointList.add(PointF(0f, 0.75f)) photoItem.pointList.add(PointF(0.5f, 0.5f)) photoItem.pointList.add(PointF(0f, 0.25f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 2 photoItem.bound.set(0.25f, 0.25f, 0.75f, 0.75f) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(1f, 0.5f)) photoItem.pointList.add(PointF(0.5f, 1f)) photoItem.pointList.add(PointF(0f, 0.5f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_1(): CollageLayout { val item = CollageLayoutFactory.collage("collage_3_1") val photoItemList = mutableListOf() //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.5f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(0.25f, 0.5f, 0.75f, 1.5f) photoItem.clearPathInCenterHorizontal = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.5f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItem.clearPath = Path() photoItem.clearPath!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.clearPathRatioBound = RectF(0.25f, -0.5f, 0.75f, 0.5f) photoItem.clearPathInCenterHorizontal = true photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 2 photoItem.shrinkMethod = PhotoItem.SHRINK_METHOD_3_8 photoItem.bound.set(0f, 0.25f, 1f, 0.75f) photoItem.path = Path() photoItem.path!!.addCircle(256f, 256f, 256f, Path.Direction.CCW) photoItem.pathRatioBound = RectF(0.25f, 0f, 0.75f, 1f) photoItem.pathInCenterVertical = true photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } internal fun collage_3_0(): CollageLayout { return CollageLayoutFactory.collageLinear( name = "collage_3_0", count = 3, isHorizontal = true ) } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/frames/TwoFrameImage.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.frames import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory import com.t8rin.collages.view.PhotoItem /** * Created by admin on 5/8/2016. */ internal object TwoFrameImage { fun collage_2_0(name: String = "collage_2_0", initialPosition: Float = 0.5f): CollageLayout { return CollageLayoutFactory.collage(name) { val wall1X = param(initialPosition) addBoxedItem( xParams = listOf(wall1X), boxParams = { vs -> RectF(0f, 0f, vs[wall1X], 1f) } ) addBoxedItem( xParams = listOf(wall1X), boxParams = { vs -> RectF(vs[wall1X], 0f, 1f, 1f) } ) } } fun collage_2_1(name: String = "collage_2_1", initialPosition: Float = 0.5f): CollageLayout { return CollageLayoutFactory.collage(name) { val wall1Y = param(initialPosition) addBoxedItem( yParams = listOf(wall1Y), boxParams = { vs -> RectF(0f, 0f, 1f, vs[wall1Y]) } ) addBoxedItem( yParams = listOf(wall1Y), boxParams = { vs -> RectF(0f, vs[wall1Y], 1f, 1f) } ) } } fun collage_2_2(): CollageLayout { return collage_2_1("collage_2_2", 0.3333f) } fun collage_2_3(): CollageLayout { val item = CollageLayoutFactory.collage("collage_2_3") val photoItemList = mutableListOf() //first frame val photoItem1 = PhotoItem() photoItem1.index = 0 photoItem1.bound.set(0f, 0f, 1f, 0.667f) photoItem1.pointList.add(PointF(0f, 0f)) photoItem1.pointList.add(PointF(1f, 0f)) photoItem1.pointList.add(PointF(1f, 0.5f)) photoItem1.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem1) //second frame val photoItem2 = PhotoItem() photoItem2.index = 1 photoItem2.bound.set(0f, 0.333f, 1f, 1f) photoItem2.pointList.add(PointF(0f, 0.5f)) photoItem2.pointList.add(PointF(1f, 0f)) photoItem2.pointList.add(PointF(1f, 1f)) photoItem2.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem2) return item.copy(photoItemList = photoItemList) } fun collage_2_4(): CollageLayout { val item = CollageLayoutFactory.collage("collage_2_4") val photoItemList = mutableListOf() //first frame //first frame var photoItem = PhotoItem() photoItem.index = 0 photoItem.bound.set(0f, 0f, 1f, 0.5714f) photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0.8333f, 0.75f)) photoItem.pointList.add(PointF(0.6666f, 1f)) photoItem.pointList.add(PointF(0.5f, 0.75f)) photoItem.pointList.add(PointF(0.3333f, 1f)) photoItem.pointList.add(PointF(0.1666f, 0.75f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) //second frame photoItem = PhotoItem() photoItem.index = 1 photoItem.bound.set(0f, 0.4286f, 1f, 1f) photoItem.pointList.add(PointF(0f, 0.25f)) photoItem.pointList.add(PointF(0.1666f, 0f)) photoItem.pointList.add(PointF(0.3333f, 0.25f)) photoItem.pointList.add(PointF(0.5f, 0f)) photoItem.pointList.add(PointF(0.6666f, 0.25f)) photoItem.pointList.add(PointF(0.8333f, 0f)) photoItem.pointList.add(PointF(1f, 0.25f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem) return item.copy(photoItemList = photoItemList) } fun collage_2_5(): CollageLayout { return collage_2_1("collage_2_5", 0.6667f) } fun collage_2_6(): CollageLayout { val item = CollageLayoutFactory.collage("collage_2_6") val photoItemList = mutableListOf() //first frame val photoItem1 = PhotoItem() photoItem1.index = 0 photoItem1.bound.set(0f, 0f, 1f, 0.667f) photoItem1.pointList.add(PointF(0f, 0f)) photoItem1.pointList.add(PointF(1f, 0f)) photoItem1.pointList.add(PointF(1f, 1f)) photoItem1.pointList.add(PointF(0f, 0.5f)) photoItemList.add(photoItem1) //second frame val photoItem2 = PhotoItem() photoItem2.index = 1 photoItem2.bound.set(0f, 0.333f, 1f, 1f) photoItem2.pointList.add(PointF(0f, 0f)) photoItem2.pointList.add(PointF(1f, 0.5f)) photoItem2.pointList.add(PointF(1f, 1f)) photoItem2.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem2) return item.copy(photoItemList = photoItemList) } fun collage_2_7(): CollageLayout { val item = CollageLayoutFactory.collage("collage_2_7") val photoItemList = mutableListOf() //first frame val photoItem1 = PhotoItem() photoItem1.index = 0 photoItem1.bound.set(0f, 0f, 1f, 1f) photoItem1.pointList.add(PointF(0f, 0f)) photoItem1.pointList.add(PointF(1f, 0f)) photoItem1.pointList.add(PointF(1f, 1f)) photoItem1.pointList.add(PointF(0f, 1f)) //clear area photoItem1.clearAreaPoints = ArrayList() photoItem1.clearAreaPoints!!.add(PointF(0.6f, 0.6f)) photoItem1.clearAreaPoints!!.add(PointF(0.9f, 0.6f)) photoItem1.clearAreaPoints!!.add(PointF(0.9f, 0.9f)) photoItem1.clearAreaPoints!!.add(PointF(0.6f, 0.9f)) photoItemList.add(photoItem1) //second frame val photoItem2 = PhotoItem() photoItem2.index = 1 //photoItem2.hasBackground = true; photoItem2.bound.set(0.6f, 0.6f, 0.9f, 0.9f) photoItem2.pointList.add(PointF(0f, 0f)) photoItem2.pointList.add(PointF(1f, 0f)) photoItem2.pointList.add(PointF(1f, 1f)) photoItem2.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem2) return item.copy(photoItemList = photoItemList) } fun collage_2_8(): CollageLayout { return collage_2_0("collage_2_8", 0.3333f) } fun collage_2_9(): CollageLayout { val item = CollageLayoutFactory.collage("collage_2_9") val photoItemList = mutableListOf() //first frame val photoItem1 = PhotoItem() photoItem1.index = 0 photoItem1.bound.set(0f, 0f, 0.6667f, 1f) photoItem1.pointList.add(PointF(0f, 0f)) photoItem1.pointList.add(PointF(0.5f, 0f)) photoItem1.pointList.add(PointF(1f, 1f)) photoItem1.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem1) //second frame val photoItem2 = PhotoItem() photoItem2.index = 1 photoItem2.bound.set(0.3333f, 0f, 1f, 1f) photoItem2.pointList.add(PointF(0f, 0f)) photoItem2.pointList.add(PointF(1f, 0f)) photoItem2.pointList.add(PointF(1f, 1f)) photoItem2.pointList.add(PointF(0.5f, 1f)) photoItemList.add(photoItem2) return item.copy(photoItemList = photoItemList) } fun collage_2_10(): CollageLayout { return collage_2_0("collage_2_10", 0.6667f) } fun collage_2_11(): CollageLayout { val item = CollageLayoutFactory.collage("collage_2_11") val photoItemList = mutableListOf() //first frame val photoItem1 = PhotoItem() photoItem1.index = 0 photoItem1.bound.set(0f, 0f, 0.667f, 1f) photoItem1.pointList.add(PointF(0f, 0f)) photoItem1.pointList.add(PointF(1f, 0f)) photoItem1.pointList.add(PointF(0.5f, 1f)) photoItem1.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem1) //second frame val photoItem2 = PhotoItem() photoItem1.index = 1 photoItem2.bound.set(0.333f, 0f, 1f, 1f) photoItem2.pointList.add(PointF(0.5f, 0f)) photoItem2.pointList.add(PointF(1f, 0f)) photoItem2.pointList.add(PointF(1f, 1f)) photoItem2.pointList.add(PointF(0f, 1f)) photoItemList.add(photoItem2) return item.copy(photoItemList = photoItemList) } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/model/CollageLayout.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.model import android.net.Uri import com.t8rin.collages.utils.ParamsManager import com.t8rin.collages.view.PhotoItem internal data class CollageLayout( val preview: Uri, val title: String, val paramsManager: ParamsManager? = null, val photoItemList: List = emptyList() ) ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/public/CollageConstants.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.public import coil3.request.ImageRequest import com.t8rin.collages.utils.CollageLayoutFactory object CollageConstants { const val MAX_IMAGE_COUNT: Int = 20 val layoutCount: Int = CollageLayoutFactory.COLLAGE_MAP.keys.size internal var requestMapper: ImageRequest.Builder.() -> ImageRequest.Builder = { this } fun requestMapper(mapper: ImageRequest.Builder.() -> ImageRequest.Builder) { this.requestMapper = mapper } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/utils/CollageLayoutFactory.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.collages.utils import android.content.Context import android.graphics.Path import android.graphics.RectF import androidx.core.net.toUri import com.t8rin.collages.frames.EightFrameImage import com.t8rin.collages.frames.ExtendedFrameImage import com.t8rin.collages.frames.FiveFrameImage import com.t8rin.collages.frames.FourFrameImage import com.t8rin.collages.frames.NineFrameImage import com.t8rin.collages.frames.SevenFrameImage import com.t8rin.collages.frames.SixFrameImage import com.t8rin.collages.frames.TenFrameImage import com.t8rin.collages.frames.ThreeFrameImage import com.t8rin.collages.frames.TwoFrameImage import com.t8rin.collages.model.CollageLayout internal object CollageLayoutFactory { private const val FRAME_FOLDER = "frame" fun collage( frameName: String ): CollageLayout = CollageLayout( preview = ("file:///android_asset/$FRAME_FOLDER/$frameName.webp").toUri(), title = frameName ) fun collage( frameName: String, setup: ParamsManagerBuilder.() -> Unit ): CollageLayout { val (paramsManager, photoItemList) = ParamsManagerBuilder().apply(setup).build() return collage(frameName).copy(paramsManager = paramsManager, photoItemList = photoItemList) } fun createHeartItem(top: Float, size: Float): Path { return Path().apply { moveTo(top, top + size / 4) quadTo(top, top, top + size / 4, top) quadTo(top + size / 2, top, top + size / 2, top + size / 4) quadTo(top + size / 2, top, top + size * 3 / 4, top) quadTo(top + size, top, top + size, top + size / 4) quadTo(top + size, top + size / 2, top + size * 3 / 4, top + size * 3 / 4) lineTo(top + size / 2, top + size) lineTo(top + size / 4, top + size * 3 / 4) quadTo(top, top + size / 2, top, top + size / 4) } } fun loadFrameImages(context: Context): List { return runCatching { context.assets.list(FRAME_FOLDER).orEmpty().mapNotNull { createCollageLayouts(it.substringBeforeLast('.')) }.sortedWith { lhs, rhs -> lhs.photoItemList.size - rhs.photoItemList.size } }.onFailure { it.printStackTrace() }.getOrNull().orEmpty() } internal fun collageLinear( name: String, count: Int, isHorizontal: Boolean, ): CollageLayout { return collage(name) { if (count <= 0) return@collage val params = (1 until count).map { i -> param(i.toFloat() / count) } repeat(count) { index -> addBoxedItem( xParams = if (isHorizontal) params else emptyList(), yParams = if (!isHorizontal) params else emptyList(), boxParams = { vs -> if (isHorizontal) { val left = if (index == 0) 0f else vs[params[index - 1]] val right = if (index == count - 1) 1f else vs[params[index]] RectF(left, 0f, right, 1f) } else { val top = if (index == 0) 0f else vs[params[index - 1]] val bottom = if (index == count - 1) 1f else vs[params[index]] RectF(0f, top, 1f, bottom) } } ) } } } internal fun collageParametricGrid( name: String, rows: Int, cols: Int, ): CollageLayout { return collage(name) { if (rows <= 0 || cols <= 0) return@collage val xColParams = (1 until cols).map { c -> param(c.toFloat() / cols) } val yRowParams = (1 until rows).map { r -> param(r.toFloat() / rows) } for (r in 0 until rows) { for (c in 0 until cols) { val xp = buildList { if (c > 0) add(xColParams[c - 1]) if (c < cols - 1) add(xColParams[c]) }.distinct() val yp = buildList { if (r > 0) add(yRowParams[r - 1]) if (r < rows - 1) add(yRowParams[r]) }.distinct() addBoxedItem( xParams = xp, yParams = yp, boxParams = { vs -> RectF( if (c == 0) 0f else vs[xColParams[c - 1]], if (r == 0) 0f else vs[yRowParams[r - 1]], if (c == cols - 1) 1f else vs[xColParams[c]], if (r == rows - 1) 1f else vs[yRowParams[r]], ) }, ) } } } } internal fun createCollageLayouts(frameName: String): CollageLayout? { return COLLAGE_MAP[frameName]?.invoke() } internal val COLLAGE_MAP: Map CollageLayout> = mapOf( "collage_1_0" to { ExtendedFrameImage.collage_1_0() }, "collage_2_0" to { TwoFrameImage.collage_2_0() }, "collage_2_1" to { TwoFrameImage.collage_2_1() }, "collage_2_2" to { TwoFrameImage.collage_2_2() }, "collage_2_3" to { TwoFrameImage.collage_2_3() }, "collage_2_4" to { TwoFrameImage.collage_2_4() }, "collage_2_5" to { TwoFrameImage.collage_2_5() }, "collage_2_6" to { TwoFrameImage.collage_2_6() }, "collage_2_7" to { TwoFrameImage.collage_2_7() }, "collage_2_8" to { TwoFrameImage.collage_2_8() }, "collage_2_9" to { TwoFrameImage.collage_2_9() }, "collage_2_10" to { TwoFrameImage.collage_2_10() }, "collage_2_11" to { TwoFrameImage.collage_2_11() }, "collage_3_0" to { ThreeFrameImage.collage_3_0() }, "collage_3_1" to { ThreeFrameImage.collage_3_1() }, "collage_3_3" to { ThreeFrameImage.collage_3_3() }, "collage_3_4" to { ThreeFrameImage.collage_3_4() }, "collage_3_5" to { ThreeFrameImage.collage_3_5() }, "collage_3_6" to { ThreeFrameImage.collage_3_6() }, "collage_3_7" to { ThreeFrameImage.collage_3_7() }, "collage_3_8" to { ThreeFrameImage.collage_3_8() }, "collage_3_9" to { ThreeFrameImage.collage_3_9() }, "collage_3_10" to { ThreeFrameImage.collage_3_10() }, "collage_3_11" to { ThreeFrameImage.collage_3_11() }, "collage_3_12" to { ThreeFrameImage.collage_3_12() }, "collage_3_13" to { ThreeFrameImage.collage_3_13() }, "collage_3_14" to { ThreeFrameImage.collage_3_14() }, "collage_3_15" to { ThreeFrameImage.collage_3_15() }, "collage_3_16" to { ThreeFrameImage.collage_3_16() }, "collage_3_17" to { ThreeFrameImage.collage_3_17() }, "collage_3_18" to { ThreeFrameImage.collage_3_18() }, "collage_3_19" to { ThreeFrameImage.collage_3_19() }, "collage_3_20" to { ThreeFrameImage.collage_3_20() }, "collage_3_21" to { ThreeFrameImage.collage_3_21() }, "collage_3_22" to { ThreeFrameImage.collage_3_22() }, "collage_3_23" to { ThreeFrameImage.collage_3_23() }, "collage_3_24" to { ThreeFrameImage.collage_3_24() }, "collage_3_25" to { ThreeFrameImage.collage_3_25() }, "collage_3_26" to { ThreeFrameImage.collage_3_26() }, "collage_3_27" to { ThreeFrameImage.collage_3_27() }, "collage_3_28" to { ThreeFrameImage.collage_3_28() }, "collage_3_29" to { ThreeFrameImage.collage_3_29() }, "collage_3_30" to { ThreeFrameImage.collage_3_30() }, "collage_3_31" to { ThreeFrameImage.collage_3_31() }, "collage_3_32" to { ThreeFrameImage.collage_3_32() }, "collage_3_33" to { ThreeFrameImage.collage_3_33() }, "collage_3_34" to { ThreeFrameImage.collage_3_34() }, "collage_3_35" to { ThreeFrameImage.collage_3_35() }, "collage_3_36" to { ThreeFrameImage.collage_3_36() }, "collage_3_37" to { ThreeFrameImage.collage_3_37() }, "collage_3_38" to { ThreeFrameImage.collage_3_38() }, "collage_3_39" to { ThreeFrameImage.collage_3_39() }, "collage_3_40" to { ThreeFrameImage.collage_3_40() }, "collage_3_41" to { ThreeFrameImage.collage_3_41() }, "collage_3_42" to { ThreeFrameImage.collage_3_42() }, "collage_3_43" to { ThreeFrameImage.collage_3_43() }, "collage_3_44" to { ThreeFrameImage.collage_3_44() }, "collage_3_45" to { ThreeFrameImage.collage_3_45() }, "collage_3_46" to { ThreeFrameImage.collage_3_46() }, "collage_3_47" to { ThreeFrameImage.collage_3_47() }, "collage_4_0" to { FourFrameImage.collage_4_0() }, "collage_4_1" to { FourFrameImage.collage_4_1() }, "collage_4_1_1" to { FourFrameImage.collage_4_1_1() }, "collage_4_2" to { FourFrameImage.collage_4_2() }, "collage_4_4" to { FourFrameImage.collage_4_4() }, "collage_4_5" to { FourFrameImage.collage_4_5() }, "collage_4_6" to { FourFrameImage.collage_4_6() }, "collage_4_7" to { FourFrameImage.collage_4_7() }, "collage_4_8" to { FourFrameImage.collage_4_8() }, "collage_4_9" to { FourFrameImage.collage_4_9() }, "collage_4_10" to { FourFrameImage.collage_4_10() }, "collage_4_11" to { FourFrameImage.collage_4_11() }, "collage_4_12" to { FourFrameImage.collage_4_12() }, "collage_4_13" to { FourFrameImage.collage_4_13() }, "collage_4_14" to { FourFrameImage.collage_4_14() }, "collage_4_15" to { FourFrameImage.collage_4_15() }, "collage_4_16" to { FourFrameImage.collage_4_16() }, "collage_4_17" to { FourFrameImage.collage_4_17() }, "collage_4_18" to { FourFrameImage.collage_4_18() }, "collage_4_19" to { FourFrameImage.collage_4_19() }, "collage_4_20" to { FourFrameImage.collage_4_20() }, "collage_4_21" to { FourFrameImage.collage_4_21() }, "collage_4_22" to { FourFrameImage.collage_4_22() }, "collage_4_23" to { FourFrameImage.collage_4_23() }, "collage_4_24" to { FourFrameImage.collage_4_24() }, "collage_4_25" to { FourFrameImage.collage_4_25() }, "collage_5_1_1" to { FiveFrameImage.collage_5_1_1() }, "collage_5_1_2" to { FiveFrameImage.collage_5_1_2() }, "collage_5_0" to { FiveFrameImage.collage_5_0() }, "collage_5_1" to { FiveFrameImage.collage_5_1() }, "collage_5_2" to { FiveFrameImage.collage_5_2() }, "collage_5_3" to { FiveFrameImage.collage_5_3() }, "collage_5_4" to { FiveFrameImage.collage_5_4() }, "collage_5_5" to { FiveFrameImage.collage_5_5() }, "collage_5_6" to { FiveFrameImage.collage_5_6() }, "collage_5_7" to { FiveFrameImage.collage_5_7() }, "collage_5_8" to { FiveFrameImage.collage_5_8() }, "collage_5_9" to { FiveFrameImage.collage_5_9() }, "collage_5_10" to { FiveFrameImage.collage_5_10() }, "collage_5_11" to { FiveFrameImage.collage_5_11() }, "collage_5_12" to { FiveFrameImage.collage_5_12() }, "collage_5_13" to { FiveFrameImage.collage_5_13() }, "collage_5_14" to { FiveFrameImage.collage_5_14() }, "collage_5_15" to { FiveFrameImage.collage_5_15() }, "collage_5_16" to { FiveFrameImage.collage_5_16() }, "collage_5_17" to { FiveFrameImage.collage_5_17() }, "collage_5_18" to { FiveFrameImage.collage_5_18() }, "collage_5_19" to { FiveFrameImage.collage_5_19() }, "collage_5_20" to { FiveFrameImage.collage_5_20() }, "collage_5_21" to { FiveFrameImage.collage_5_21() }, "collage_5_22" to { FiveFrameImage.collage_5_22() }, "collage_5_23" to { FiveFrameImage.collage_5_23() }, "collage_5_24" to { FiveFrameImage.collage_5_24() }, "collage_5_25" to { FiveFrameImage.collage_5_25() }, "collage_5_26" to { FiveFrameImage.collage_5_26() }, "collage_5_27" to { FiveFrameImage.collage_5_27() }, "collage_5_28" to { FiveFrameImage.collage_5_28() }, "collage_5_29" to { FiveFrameImage.collage_5_29() }, "collage_5_30" to { FiveFrameImage.collage_5_30() }, "collage_5_31" to { FiveFrameImage.collage_5_31() }, "collage_6_1_1" to { SixFrameImage.collage_6_1_1() }, "collage_6_1_2" to { SixFrameImage.collage_6_1_2() }, "collage_6_0" to { SixFrameImage.collage_6_0() }, "collage_6_1" to { SixFrameImage.collage_6_1() }, "collage_6_2" to { SixFrameImage.collage_6_2() }, "collage_6_3" to { SixFrameImage.collage_6_3() }, "collage_6_4" to { SixFrameImage.collage_6_4() }, "collage_6_5" to { SixFrameImage.collage_6_5() }, "collage_6_6" to { SixFrameImage.collage_6_6() }, "collage_6_7" to { SixFrameImage.collage_6_7() }, "collage_6_8" to { SixFrameImage.collage_6_8() }, "collage_6_9" to { SixFrameImage.collage_6_9() }, "collage_6_10" to { SixFrameImage.collage_6_10() }, "collage_6_11" to { SixFrameImage.collage_6_11() }, "collage_6_12" to { SixFrameImage.collage_6_12() }, "collage_6_13" to { SixFrameImage.collage_6_13() }, "collage_6_14" to { SixFrameImage.collage_6_14() }, "collage_7_1_1" to { SevenFrameImage.collage_7_1_1() }, "collage_7_1_2" to { SevenFrameImage.collage_7_1_2() }, "collage_7_0" to { SevenFrameImage.collage_7_0() }, "collage_7_1" to { SevenFrameImage.collage_7_1() }, "collage_7_2" to { SevenFrameImage.collage_7_2() }, "collage_7_3" to { SevenFrameImage.collage_7_3() }, "collage_7_4" to { SevenFrameImage.collage_7_4() }, "collage_7_5" to { SevenFrameImage.collage_7_5() }, "collage_7_6" to { SevenFrameImage.collage_7_6() }, "collage_7_7" to { SevenFrameImage.collage_7_7() }, "collage_7_8" to { SevenFrameImage.collage_7_8() }, "collage_7_9" to { SevenFrameImage.collage_7_9() }, "collage_7_10" to { SevenFrameImage.collage_7_10() }, "collage_8_0" to { EightFrameImage.collage_8_0() }, "collage_8_1" to { EightFrameImage.collage_8_1() }, "collage_8_2" to { EightFrameImage.collage_8_2() }, "collage_8_3" to { EightFrameImage.collage_8_3() }, "collage_8_4" to { EightFrameImage.collage_8_4() }, "collage_8_5" to { EightFrameImage.collage_8_5() }, "collage_8_6" to { EightFrameImage.collage_8_6() }, "collage_8_7" to { EightFrameImage.collage_8_7() }, "collage_8_8" to { EightFrameImage.collage_8_8() }, "collage_8_9" to { EightFrameImage.collage_8_9() }, "collage_8_10" to { EightFrameImage.collage_8_10() }, "collage_8_11" to { EightFrameImage.collage_8_11() }, "collage_8_12" to { EightFrameImage.collage_8_12() }, "collage_8_13" to { EightFrameImage.collage_8_13() }, "collage_8_14" to { EightFrameImage.collage_8_14() }, "collage_8_15" to { EightFrameImage.collage_8_15() }, "collage_8_16" to { EightFrameImage.collage_8_16() }, "collage_9_0" to { NineFrameImage.collage_9_0() }, "collage_9_1" to { NineFrameImage.collage_9_1() }, "collage_9_2" to { NineFrameImage.collage_9_2() }, "collage_9_3" to { NineFrameImage.collage_9_3() }, "collage_9_4" to { NineFrameImage.collage_9_4() }, "collage_9_5" to { NineFrameImage.collage_9_5() }, "collage_9_6" to { NineFrameImage.collage_9_6() }, "collage_9_7" to { NineFrameImage.collage_9_7() }, "collage_9_8" to { NineFrameImage.collage_9_8() }, "collage_9_9" to { NineFrameImage.collage_9_9() }, "collage_9_10" to { NineFrameImage.collage_9_10() }, "collage_9_11" to { NineFrameImage.collage_9_11() }, "collage_10_0" to { TenFrameImage.collage_10_0() }, "collage_10_1" to { TenFrameImage.collage_10_1() }, "collage_10_2" to { TenFrameImage.collage_10_2() }, "collage_10_3" to { TenFrameImage.collage_10_3() }, "collage_10_4" to { TenFrameImage.collage_10_4() }, "collage_10_5" to { TenFrameImage.collage_10_5() }, "collage_10_6" to { TenFrameImage.collage_10_6() }, "collage_10_7" to { TenFrameImage.collage_10_7() }, "collage_10_8" to { TenFrameImage.collage_10_8() }, "collage_10_9" to { ExtendedFrameImage.collage_10_9() }, "collage_10_10" to { ExtendedFrameImage.collage_10_10() }, "collage_2_12" to { ExtendedFrameImage.collage_2_12() }, "collage_2_13" to { ExtendedFrameImage.collage_2_13() }, "collage_3_48" to { ExtendedFrameImage.collage_3_48() }, "collage_4_26" to { ExtendedFrameImage.collage_4_26() }, "collage_4_27" to { ExtendedFrameImage.collage_4_27() }, "collage_5_32" to { ExtendedFrameImage.collage_5_32() }, "collage_5_33" to { ExtendedFrameImage.collage_5_33() }, "collage_6_15" to { ExtendedFrameImage.collage_6_15() }, "collage_6_16" to { ExtendedFrameImage.collage_6_16() }, "collage_6_17" to { ExtendedFrameImage.collage_6_17() }, "collage_7_11" to { ExtendedFrameImage.collage_7_11() }, "collage_7_12" to { ExtendedFrameImage.collage_7_12() }, "collage_8_17" to { ExtendedFrameImage.collage_8_17() }, "collage_8_18" to { ExtendedFrameImage.collage_8_18() }, "collage_9_12" to { ExtendedFrameImage.collage_9_12() }, "collage_9_13" to { ExtendedFrameImage.collage_9_13() }, "collage_11_0" to { ExtendedFrameImage.collage_11_0() }, "collage_11_1" to { ExtendedFrameImage.collage_11_1() }, "collage_11_2" to { ExtendedFrameImage.collage_11_2() }, "collage_11_3" to { ExtendedFrameImage.collage_11_3() }, "collage_11_4" to { ExtendedFrameImage.collage_11_4() }, "collage_11_5" to { ExtendedFrameImage.collage_11_5() }, "collage_11_6" to { ExtendedFrameImage.collage_11_6() }, "collage_11_7" to { ExtendedFrameImage.collage_11_7() }, "collage_11_8" to { ExtendedFrameImage.collage_11_8() }, "collage_11_9" to { ExtendedFrameImage.collage_11_9() }, "collage_11_10" to { ExtendedFrameImage.collage_11_10() }, "collage_12_0" to { ExtendedFrameImage.collage_12_0() }, "collage_12_1" to { ExtendedFrameImage.collage_12_1() }, "collage_12_2" to { ExtendedFrameImage.collage_12_2() }, "collage_12_3" to { ExtendedFrameImage.collage_12_3() }, "collage_12_4" to { ExtendedFrameImage.collage_12_4() }, "collage_12_5" to { ExtendedFrameImage.collage_12_5() }, "collage_12_6" to { ExtendedFrameImage.collage_12_6() }, "collage_12_7" to { ExtendedFrameImage.collage_12_7() }, "collage_12_8" to { ExtendedFrameImage.collage_12_8() }, "collage_12_9" to { ExtendedFrameImage.collage_12_9() }, "collage_12_10" to { ExtendedFrameImage.collage_12_10() }, "collage_13_0" to { ExtendedFrameImage.collage_13_0() }, "collage_13_1" to { ExtendedFrameImage.collage_13_1() }, "collage_13_2" to { ExtendedFrameImage.collage_13_2() }, "collage_13_3" to { ExtendedFrameImage.collage_13_3() }, "collage_13_4" to { ExtendedFrameImage.collage_13_4() }, "collage_13_5" to { ExtendedFrameImage.collage_13_5() }, "collage_13_6" to { ExtendedFrameImage.collage_13_6() }, "collage_13_7" to { ExtendedFrameImage.collage_13_7() }, "collage_13_8" to { ExtendedFrameImage.collage_13_8() }, "collage_13_9" to { ExtendedFrameImage.collage_13_9() }, "collage_13_10" to { ExtendedFrameImage.collage_13_10() }, "collage_14_0" to { ExtendedFrameImage.collage_14_0() }, "collage_14_1" to { ExtendedFrameImage.collage_14_1() }, "collage_14_2" to { ExtendedFrameImage.collage_14_2() }, "collage_14_3" to { ExtendedFrameImage.collage_14_3() }, "collage_14_4" to { ExtendedFrameImage.collage_14_4() }, "collage_14_5" to { ExtendedFrameImage.collage_14_5() }, "collage_14_6" to { ExtendedFrameImage.collage_14_6() }, "collage_14_7" to { ExtendedFrameImage.collage_14_7() }, "collage_14_8" to { ExtendedFrameImage.collage_14_8() }, "collage_14_9" to { ExtendedFrameImage.collage_14_9() }, "collage_14_10" to { ExtendedFrameImage.collage_14_10() }, "collage_15_0" to { ExtendedFrameImage.collage_15_0() }, "collage_15_1" to { ExtendedFrameImage.collage_15_1() }, "collage_15_2" to { ExtendedFrameImage.collage_15_2() }, "collage_15_3" to { ExtendedFrameImage.collage_15_3() }, "collage_15_4" to { ExtendedFrameImage.collage_15_4() }, "collage_15_5" to { ExtendedFrameImage.collage_15_5() }, "collage_15_6" to { ExtendedFrameImage.collage_15_6() }, "collage_15_7" to { ExtendedFrameImage.collage_15_7() }, "collage_15_8" to { ExtendedFrameImage.collage_15_8() }, "collage_15_9" to { ExtendedFrameImage.collage_15_9() }, "collage_15_10" to { ExtendedFrameImage.collage_15_10() }, "collage_16_0" to { ExtendedFrameImage.collage_16_0() }, "collage_16_1" to { ExtendedFrameImage.collage_16_1() }, "collage_16_2" to { ExtendedFrameImage.collage_16_2() }, "collage_16_3" to { ExtendedFrameImage.collage_16_3() }, "collage_16_4" to { ExtendedFrameImage.collage_16_4() }, "collage_16_5" to { ExtendedFrameImage.collage_16_5() }, "collage_16_6" to { ExtendedFrameImage.collage_16_6() }, "collage_16_7" to { ExtendedFrameImage.collage_16_7() }, "collage_16_8" to { ExtendedFrameImage.collage_16_8() }, "collage_16_9" to { ExtendedFrameImage.collage_16_9() }, "collage_16_10" to { ExtendedFrameImage.collage_16_10() }, "collage_17_0" to { ExtendedFrameImage.collage_17_0() }, "collage_17_1" to { ExtendedFrameImage.collage_17_1() }, "collage_17_2" to { ExtendedFrameImage.collage_17_2() }, "collage_17_3" to { ExtendedFrameImage.collage_17_3() }, "collage_17_4" to { ExtendedFrameImage.collage_17_4() }, "collage_17_5" to { ExtendedFrameImage.collage_17_5() }, "collage_17_6" to { ExtendedFrameImage.collage_17_6() }, "collage_17_7" to { ExtendedFrameImage.collage_17_7() }, "collage_17_8" to { ExtendedFrameImage.collage_17_8() }, "collage_17_9" to { ExtendedFrameImage.collage_17_9() }, "collage_17_10" to { ExtendedFrameImage.collage_17_10() }, "collage_18_0" to { ExtendedFrameImage.collage_18_0() }, "collage_18_1" to { ExtendedFrameImage.collage_18_1() }, "collage_18_2" to { ExtendedFrameImage.collage_18_2() }, "collage_18_3" to { ExtendedFrameImage.collage_18_3() }, "collage_18_4" to { ExtendedFrameImage.collage_18_4() }, "collage_18_5" to { ExtendedFrameImage.collage_18_5() }, "collage_18_6" to { ExtendedFrameImage.collage_18_6() }, "collage_18_7" to { ExtendedFrameImage.collage_18_7() }, "collage_18_8" to { ExtendedFrameImage.collage_18_8() }, "collage_18_9" to { ExtendedFrameImage.collage_18_9() }, "collage_18_10" to { ExtendedFrameImage.collage_18_10() }, "collage_19_0" to { ExtendedFrameImage.collage_19_0() }, "collage_19_1" to { ExtendedFrameImage.collage_19_1() }, "collage_19_2" to { ExtendedFrameImage.collage_19_2() }, "collage_19_3" to { ExtendedFrameImage.collage_19_3() }, "collage_19_4" to { ExtendedFrameImage.collage_19_4() }, "collage_19_5" to { ExtendedFrameImage.collage_19_5() }, "collage_19_6" to { ExtendedFrameImage.collage_19_6() }, "collage_19_7" to { ExtendedFrameImage.collage_19_7() }, "collage_19_8" to { ExtendedFrameImage.collage_19_8() }, "collage_19_9" to { ExtendedFrameImage.collage_19_9() }, "collage_19_10" to { ExtendedFrameImage.collage_19_10() }, "collage_20_0" to { ExtendedFrameImage.collage_20_0() }, "collage_20_1" to { ExtendedFrameImage.collage_20_1() }, "collage_20_2" to { ExtendedFrameImage.collage_20_2() }, "collage_20_3" to { ExtendedFrameImage.collage_20_3() }, "collage_20_4" to { ExtendedFrameImage.collage_20_4() }, "collage_20_5" to { ExtendedFrameImage.collage_20_5() }, "collage_20_6" to { ExtendedFrameImage.collage_20_6() }, "collage_20_7" to { ExtendedFrameImage.collage_20_7() }, "collage_20_8" to { ExtendedFrameImage.collage_20_8() }, "collage_20_9" to { ExtendedFrameImage.collage_20_9() }, "collage_20_10" to { ExtendedFrameImage.collage_20_10() }, ) } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/utils/GeometryUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("LocalVariableName", "FunctionName") package com.t8rin.collages.utils import android.graphics.Path import android.graphics.PointF import android.graphics.RectF import java.util.Arrays import java.util.Objects import java.util.function.Supplier import kotlin.math.abs import kotlin.math.acos import kotlin.math.atan2 import kotlin.math.cos import kotlin.math.sin import kotlin.math.sqrt /** * @noinspection unused */ /* * All points of polygon must be ordered by clockwise along
* Created by admin on 5/4/2016. */ object GeometryUtils { fun isInCircle(center: PointF, radius: Float, p: PointF): Boolean { return (sqrt(((center.x - p.x) * (center.x - p.x) + (center.y - p.y) * (center.y - p.y)).toDouble()) <= radius) } /** * Return true if the given point is contained inside the boundary. * See: [Short_Notes](http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html) * * @param test The point to check * @return true if the point is inside the boundary, false otherwise */ fun contains(points: List, test: PointF): Boolean { var j: Int var result = false var i = 0 j = points.size - 1 while (i < points.size) { if ((points[i].y > test.y) != (points[j].y > test.y) && (test.x < (points[j].x - points[i].x) * (test.y - points[i].y) / (points[j].y - points[i].y) + points[i].x) ) { result = !result } j = i++ } return result } fun createRectanglePath(outPath: Path, width: Float, height: Float, corner: Float) { val pointList = ArrayList() pointList.add(PointF(0f, 0f)) pointList.add(PointF(width, 0f)) pointList.add(PointF(width, height)) pointList.add(PointF(0f, height)) createPathWithCircleCorner(outPath, pointList, corner) } fun createRegularPolygonPath(outPath: Path, size: Float, vertexCount: Int, corner: Float) { createRegularPolygonPath(outPath, size, size / 2, size / 2, vertexCount, corner) } fun createRegularPolygonPath( outPath: Path, size: Float, centerX: Float, centerY: Float, vertexCount: Int, corner: Float ) { val section = (2.0 * Math.PI / vertexCount).toFloat() val radius = size / 2 val pointList = ArrayList() pointList.add( PointF( (centerX + radius * cos(0.0)).toFloat(), (centerY + radius * sin(0.0)).toFloat() ) ) for (i in 1.., space: Float, map: HashMap ): List { val result = ArrayList() for (p in pointList) { val add: PointF = checkNotNull(map[p]) result.add(PointF(p.x + add.x * space, p.y + add.y * space)) } return result } /** * Resolve case frame collage 3_3 * * @param pointList list * @param space space * @param bound bound * @return shrink points */ fun shrinkPathCollage_3_3( pointList: List, centerPointIdx: Int, space: Float, bound: RectF? ): List { val result = ArrayList() val center = pointList[centerPointIdx] val left: PointF = if (centerPointIdx > 0) { pointList[centerPointIdx - 1] } else { pointList[pointList.size - 1] } val right: PointF = if (centerPointIdx < pointList.size - 1) { pointList[centerPointIdx + 1] } else { pointList[0] } var spaceX: Float var spaceY: Float for (p in pointList) { val pointF = PointF() spaceX = space spaceY = space if (bound != null) { if ((bound.left == 0f && p.x < center.x) || (bound.right == 1f && p.x >= center.x)) { spaceX = 2 * space } if ((bound.top == 0f && p.y < center.y) || (bound.bottom == 1f && p.y >= center.y)) { spaceY = 2 * space } } if (left.x == right.x) { if (left.x < center.x) { if (p.x <= center.x) { pointF.x = p.x + spaceX } else { pointF.x = p.x - spaceX } } else { if (p.x < center.x) { pointF.x = p.x + spaceX } else { pointF.x = p.x - spaceX } } if (p !== left && p !== right && p !== center) { if (p.y < center.y) { pointF.y = p.y + spaceY } else { pointF.y = p.y - spaceY } } else if (p === left || p === right) { if (p.y < center.y) { pointF.y = p.y - space } else { pointF.y = p.y + space } } else { pointF.y = p.y } } result.add(pointF) } return result } fun shrinkPath( pointList: List, space: Float, bound: RectF? ): List { val result = ArrayList() if (space == 0f) { result.addAll(pointList) } else { val center = PointF(0f, 0f) for (p in pointList) { center.x += p.x center.y += p.y } center.x /= pointList.size center.y /= pointList.size var spaceX: Float var spaceY: Float for (p in pointList) { val pointF = PointF() spaceX = space spaceY = space if (bound != null) { if ((bound.left == 0f && p.x < center.x) || (bound.right == 1f && p.x >= center.x)) { spaceX = 2 * space } if ((bound.top == 0f && p.y < center.y) || (bound.bottom == 1f && p.y >= center.y)) { spaceY = 2 * space } } if (abs(center.x - p.x) >= 1) { if (p.x < center.x) { pointF.x = p.x + spaceX } else if (p.x > center.x) { pointF.x = p.x - spaceX } } else { pointF.x = p.x } if (abs(center.y - p.y) >= 1) { if (p.y < center.y) { pointF.y = p.y + spaceY } else if (p.y > center.y) { pointF.y = p.y - spaceY } } else { pointF.y = p.y } result.add(pointF) } } return result } fun commonShrinkPath( pointList: List, space: Float, shrunkPointLeftRightDistances: Map ): List { val result = ArrayList() if (space == 0f) { result.addAll(pointList) } else { val convexHull = jarvis(pointList) for (i in pointList.indices) { val center = pointList[i] var concave = true for (point in convexHull) if (center === point) { concave = false break } val left: PointF = if (i == 0) { pointList[pointList.size - 1] } else { pointList[i - 1] } val right: PointF = if (i == pointList.size - 1) { pointList[0] } else { pointList[i + 1] } val leftRightDistance: PointF = checkNotNull(shrunkPointLeftRightDistances[center]) val pointF = shrinkPoint( center, left, right, leftRightDistance.x * space, leftRightDistance.y * space, !concave, !concave ) result.add( Objects.requireNonNullElseGet( pointF, Supplier { PointF(0f, 0f) } ) ) } } return result } fun createPathWithCubicCorner(path: Path, pointList: List, corner: Float) { path.reset() for (i in pointList.indices) { if (corner == 0f || pointList.size < 3) { if (i == 0) { path.moveTo(pointList[i].x, pointList[i].y) } else { path.lineTo(pointList[i].x, pointList[i].y) } } else { val center = PointF(pointList[i].x, pointList[i].y) val left = PointF() val right = PointF() if (i == 0) { left.x = pointList[pointList.size - 1].x left.y = pointList[pointList.size - 1].y } else { left.x = pointList[i - 1].x left.y = pointList[i - 1].y } if (i == pointList.size - 1) { right.x = pointList[0].x right.y = pointList[0].y } else { right.x = pointList[i + 1].x right.y = pointList[i + 1].y } val middleA = findPointOnSegment(center, left, corner.toDouble()) val middleB = findPointOnSegment(center, right, corner.toDouble()) val middle = findMiddlePoint(middleA, middleB, center) if (i == 0) { path.moveTo(middleA.x, middleA.y) } else { path.lineTo(middleA.x, middleA.y) } path.cubicTo(middleA.x, middleA.y, middle.x, middle.y, middleB.x, middleB.y) } } } private fun containPoint(points: List, p: PointF): Boolean { for (pointF in points) if ((pointF.x == p.x && pointF.y == p.y)) { return true } return false } fun createPathWithCircleCorner( path: Path, pointList: List, cornerPointList: List, corner: Float ): Map>? { if (pointList.isEmpty()) { return null } val cornerPointMap = HashMap>() path.reset() var firstPoints = arrayOf(pointList[0], pointList[0], pointList[0]) val convexHull = jarvis(pointList) for (i in pointList.indices) { if (corner == 0f || pointList.size < 3) { if (i == 0) { path.moveTo(pointList[i].x, pointList[i].y) } else { path.lineTo(pointList[i].x, pointList[i].y) } } else { var isCornerPoint = true if (cornerPointList.isNotEmpty()) { isCornerPoint = containPoint(cornerPointList, pointList[i]) } if (!isCornerPoint) { if (i == 0) { path.moveTo(pointList[i].x, pointList[i].y) } else { path.lineTo(pointList[i].x, pointList[i].y) } if (i == pointList.size - 1) { path.lineTo(firstPoints[1].x, firstPoints[1].y) } } else { var concave = true for (p in convexHull) if (p === pointList[i]) { concave = false break } val center = PointF(pointList[i].x, pointList[i].y) val left = PointF() val right = PointF() if (i == 0) { left.x = pointList[pointList.size - 1].x left.y = pointList[pointList.size - 1].y } else { left.x = pointList[i - 1].x left.y = pointList[i - 1].y } if (i == pointList.size - 1) { right.x = pointList[0].x right.y = pointList[0].y } else { right.x = pointList[i + 1].x right.y = pointList[i + 1].y } val pointFs = Array(3) { PointF() } val angles = DoubleArray(2) createArc(center, left, right, corner, angles, pointFs, concave) if (i == 0) { path.moveTo(pointFs[1].x, pointFs[1].y) } else { path.lineTo(pointFs[1].x, pointFs[1].y) } val oval = RectF( pointFs[0].x - corner, pointFs[0].y - corner, pointFs[0].x + corner, pointFs[0].y + corner ) path.arcTo(oval, angles[0].toFloat(), angles[1].toFloat(), false) if (i == 0) { firstPoints = pointFs } if (i == pointList.size - 1) { path.lineTo(firstPoints[1].x, firstPoints[1].y) } cornerPointMap[pointList[i]] = pointFs } } } return cornerPointMap } fun createPathWithCircleCorner(path: Path, pointList: List, corner: Float) { path.reset() var firstPoints: Array? = null val convexHull = jarvis(pointList) for (i in pointList.indices) { if (corner == 0f || pointList.size < 3) { if (i == 0) { path.moveTo(pointList[i].x, pointList[i].y) } else { path.lineTo(pointList[i].x, pointList[i].y) } } else { var concave = true for (p in convexHull) if (p === pointList[i]) { concave = false break } val center = PointF(pointList[i].x, pointList[i].y) val left = PointF() val right = PointF() if (i == 0) { left.x = pointList[pointList.size - 1].x left.y = pointList[pointList.size - 1].y } else { left.x = pointList[i - 1].x left.y = pointList[i - 1].y } if (i == pointList.size - 1) { right.x = pointList[0].x right.y = pointList[0].y } else { right.x = pointList[i + 1].x right.y = pointList[i + 1].y } val pointFs = Array(3) { PointF() } val angles = DoubleArray(2) createArc(center, left, right, corner, angles, pointFs, concave) if (i == 0) { path.moveTo(pointFs[1].x, pointFs[1].y) } else { path.lineTo(pointFs[1].x, pointFs[1].y) } val oval = RectF( pointFs[0].x - corner, pointFs[0].y - corner, pointFs[0].x + corner, pointFs[0].y + corner ) path.arcTo(oval, angles[0].toFloat(), angles[1].toFloat(), false) if (i == 0) { firstPoints = pointFs } if (i == pointList.size - 1) { checkNotNull(firstPoints) path.lineTo(firstPoints[1].x, firstPoints[1].y) } } } } fun findPointOnSegment(A: PointF, B: PointF, dA: Double): PointF { if (dA == 0.0) { return PointF(A.x, A.y) } else { val result = PointF() val dAB = (sqrt(((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y)).toDouble())).toFloat() val dx = abs(A.x - B.x) * dA / dAB val dy = abs(A.y - B.y) * dA / dAB if (A.x > B.x) { result.x = (A.x - dx).toFloat() } else { result.x = (A.x + dx).toFloat() } if (A.y > B.y) { result.y = (A.y - dy).toFloat() } else { result.y = (A.y + dy).toFloat() } return result } } fun findMiddlePoint(A: PointF, B: PointF, D: PointF): PointF { val d = (sqrt(((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y)).toDouble()) / 2).toFloat() return findMiddlePoint(A, B, d, D) } fun findMiddlePoint(A: PointF, B: PointF, d: Float, D: PointF): PointF { val a = B.y - A.y val b = A.x - B.x val c = B.x * A.y - A.x * B.y val middlePoints = findMiddlePoint(A, B, d) val f = a * D.x + b * D.y + c val f1 = a * middlePoints[0].x + b * middlePoints[0].y + c return if (f * f1 > Float.MIN_VALUE) { middlePoints[0] } else { middlePoints[1] } } fun createArc( A: PointF, B: PointF, C: PointF, dA: Float, outAngles: DoubleArray, outPoints: Array, isConcave: Boolean ) { outPoints[0] = findPointOnBisector(A, B, C, dA) ?: return var d = (((A.x - outPoints[0].x) * (A.x - outPoints[0].x) + (A.y - outPoints[0].y) * (A.y - outPoints[0].y)) - dA * dA).toDouble() d = sqrt(d) outPoints[1] = findPointOnSegment(A, B, d) outPoints[2] = findPointOnSegment(A, C, d) //find angles val dMA = sqrt(((A.x - outPoints[0].x) * (A.x - outPoints[0].x) + (A.y - outPoints[0].y) * (A.y - outPoints[0].y)).toDouble()) val halfSweepAngle = acos(dA / dMA) val startAngle = atan2( (outPoints[1].y - outPoints[0].y).toDouble(), (outPoints[1].x - outPoints[0].x).toDouble() ) val endAngle = atan2( (outPoints[2].y - outPoints[0].y).toDouble(), (outPoints[2].x - outPoints[0].x).toDouble() ) var sweepAngle = endAngle - startAngle if (!isConcave) { sweepAngle = 2 * halfSweepAngle } outAngles[0] = Math.toDegrees(startAngle) outAngles[1] = Math.toDegrees(sweepAngle) val tmp = Math.toDegrees(2 * halfSweepAngle) if (abs(tmp - abs(outAngles[1])) > 1) { outAngles[1] = -tmp } } /** * @param A point * @param B point * @param C point * @param dA point * @return null if does not have solution, return PointF(Float.MaxValue, Float.MaxValue) if have infinite solution, other return the solution */ fun findPointOnBisector(A: PointF, B: PointF, C: PointF, dA: Float): PointF? { val lineAB = getCoefficients(A, B) val lineAC = getCoefficients(A, C) val vB = lineAC[0] * B.x + lineAC[1] * B.y + lineAC[2] val vC = lineAB[0] * C.x + lineAB[1] * C.y + lineAB[2] val square1 = sqrt(lineAB[0] * lineAB[0] + lineAB[1] * lineAB[1]) val square2 = sqrt(lineAC[0] * lineAC[0] + lineAC[1] * lineAC[1]) if (vC > 0) { return if (vB > 0) { findIntersectPoint( lineAB[0], lineAB[1], dA * square1 - lineAB[2], lineAC[0], lineAC[1], dA * square2 - lineAC[2] ) } else { findIntersectPoint( lineAB[0], lineAB[1], dA * square1 - lineAB[2], -lineAC[0], -lineAC[1], dA * square2 + lineAC[2] ) } } else { return if (vB > 0) { findIntersectPoint( -lineAB[0], -lineAB[1], dA * square1 + lineAB[2], lineAC[0], lineAC[1], dA * square2 - lineAC[2] ) } else { findIntersectPoint( -lineAB[0], -lineAB[1], dA * square1 + lineAB[2], -lineAC[0], -lineAC[1], dA * square2 + lineAC[2] ) } } } fun distanceToLine(line: DoubleArray, P: PointF): Double { val bottom = sqrt(line[0] * line[0] + line[1] * line[1]) return abs((line[0] * P.x + line[1] * P.y + line[2]) / bottom) } /** * @param A point * @param B point * @param C point * @param dAB is the distance from shrunk point to AB line * @param dAC is the distance from shrunk point to AC line * @param b is true if shrunk point and point B located on same half-plane * @param c is true if shrunk point and point C located on same half-plane * @return shrunk point of point A */ fun shrinkPoint( A: PointF, B: PointF, C: PointF, dAB: Float, dAC: Float, b: Boolean, c: Boolean ): PointF? { val ab = getCoefficients(A, B) val ac = getCoefficients(A, C) val sqrt = sqrt(ab[0] * ab[0] + ab[1] * ab[1]) val m = dAB * sqrt - ab[2] val sqrt1 = sqrt(ac[0] * ac[0] + ac[1] * ac[1]) val n = dAC * sqrt1 - ac[2] val p = -dAB * sqrt - ab[2] val q = -dAC * sqrt1 - ac[2] val P1 = findIntersectPoint(ab[0], ab[1], m, ac[0], ac[1], n) val P2 = findIntersectPoint(ab[0], ab[1], m, ac[0], ac[1], q) val P3 = findIntersectPoint(ab[0], ab[1], p, ac[0], ac[1], n) val P4 = findIntersectPoint(ab[0], ab[1], p, ac[0], ac[1], q) return if (testShrunkPoint(ab, ac, B, C, P1, b, c)) { P1 } else if (testShrunkPoint(ab, ac, B, C, P2, b, c)) { P2 } else if (testShrunkPoint(ab, ac, B, C, P3, b, c)) { P3 } else if (testShrunkPoint(ab, ac, B, C, P4, b, c)) { P4 } else { null } } private fun testShrunkPoint( ab: DoubleArray, ac: DoubleArray, B: PointF, C: PointF, P: PointF?, b: Boolean, c: Boolean ): Boolean { if (P != null && P.x < Float.MAX_VALUE && P.y < Float.MAX_VALUE) { val signC = (ab[0] * P.x + ab[1] * P.y + ab[2]) * (ab[0] * C.x + ab[1] * C.y + ab[2]) val signB = (ac[0] * P.x + ac[1] * P.y + ac[2]) * (ac[0] * B.x + ac[1] * B.y + ac[2]) val testC = signC > Double.MIN_VALUE val testB = signB > Double.MIN_VALUE return testC == c && testB == b } return false } /** * Solve equations * ax + by = c * dx + ey = f * * @param a point * @param b point * @param c point * @param d point * @param e point * @param f point * @return null if this equations does not has solution. * return PointF(Float.MaxValue, Float.MaxValue) if this equations has infinite solutions * other return the solution of this equations. */ fun findIntersectPoint( a: Double, b: Double, c: Double, d: Double, e: Double, f: Double ): PointF? { val D: Double = a * e - b * d val Dx: Double = c * e - b * f val Dy: Double = a * f - c * d return when (D) { 0.0 if Dx == 0.0 -> { PointF(Float.MAX_VALUE, Float.MAX_VALUE) } 0.0 -> { null } else -> { PointF((Dx / D).toFloat(), (Dy / D).toFloat()) } } } /** * Find bisector of angle */ fun findBisector(A: PointF, B: PointF, C: PointF): DoubleArray { val ab = getCoefficients(A, B) val ac = getCoefficients(A, C) val sqrt1 = sqrt(ab[0] * ab[0] + ab[1] * ab[1]) val sqrt2 = sqrt(ac[0] * ac[0] + ac[1] * ac[1]) val a1 = ab[0] / sqrt1 + ac[0] / sqrt2 val b1 = ab[1] / sqrt1 + ac[1] / sqrt2 val c1 = ab[2] / sqrt1 + ac[2] / sqrt2 val a2 = ab[0] / sqrt1 - ac[0] / sqrt2 val b2 = ab[1] / sqrt1 - ac[1] / sqrt2 val c2 = ab[2] / sqrt1 - ac[2] / sqrt2 val fB = a1 * B.x + b1 * B.y + c1 val fC = a1 * C.x + b1 * C.y + c1 return if (fB * fC > Double.MIN_VALUE) { doubleArrayOf(a2, b2, c2) } else { doubleArrayOf(a1, b1, c1) } } fun getCoefficients(A: PointF, B: PointF): DoubleArray { val a = (B.y - A.y).toDouble() val b = (A.x - B.x).toDouble() val c = (B.x * A.y - A.x * B.y).toDouble() return doubleArrayOf(a, b, c) } fun findMiddlePoint(A: PointF, B: PointF, d: Float): Array { val result0: PointF val result1: PointF val dx = B.x - A.x val dy = B.y - A.y val sx = (B.x + A.x) / 2.0f val sy = (B.y + A.y) / 2.0f if (dx == 0f) { result0 = PointF(A.x + d, sy) result1 = PointF(A.x - d, sy) } else if (dy == 0f) { result0 = PointF(sx, A.y + d) result1 = PointF(sx, A.y - d) } else { val deltaY = (d / sqrt((1 + (dy * dy) / (dx * dx)).toDouble())).toFloat() result0 = PointF(sx - dy / dx * deltaY, sy + deltaY) result1 = PointF(sx + dy / dx * deltaY, sy - deltaY) } return arrayOf(result0, result1) } fun CCW(p: PointF, q: PointF, r: PointF): Boolean { val `val` = (q.y.toInt() - p.y.toInt()) * (r.x.toInt() - q.x.toInt()) - (q.x.toInt() - p.x.toInt()) * (r.y.toInt() - q.y.toInt()) return `val` < 0 } /** * Implement Jarvis Algorithm. Jarvis algorithm or the gift wrapping algorithm is an algorithm for computing the convex hull of a given set of points. * * @param points points * @return the convex hull of a given set of points */ fun jarvis(points: List): ArrayList { val result = ArrayList() val n = points.size /* if less than 3 points return **/ if (n < 3) { result.addAll(points) return result } val next = IntArray(n) Arrays.fill(next, -1) /* find the leftmost point **/ var leftMost = 0 for (i in 1... */ package com.t8rin.collages.utils import android.graphics.PointF import kotlin.math.atan2 internal interface Handle { fun getAngle(): Float fun draggablePoint(manager: ParamsManager): PointF fun tryDrag(point: PointF, manager: ParamsManager): PointF? companion object { fun horizontal( yProvider: (values: FloatArray) -> Float, managedParam: ParamT ): Handle = XHandle( yProvider = yProvider, managedParam = managedParam ) fun vertical( xProvider: (values: FloatArray) -> Float, managedParam: ParamT ): Handle = YHandle( xProvider = xProvider, managedParam = managedParam ) } } private abstract class LinearHandle( private val managedParam: ParamT, private val direction: PointF ) : Handle { override fun getAngle(): Float = Math.toDegrees(atan2(direction.y, direction.x).toDouble()).toFloat() protected abstract fun computeDraggablePoint(values: FloatArray): PointF protected abstract fun pointToValue(point: PointF): Float override fun draggablePoint(manager: ParamsManager): PointF = computeDraggablePoint(manager.valuesRef()) override fun tryDrag(point: PointF, manager: ParamsManager): PointF? { val values = manager.valuesRef() val initialPoint = computeDraggablePoint(values) val dx = point.x - initialPoint.x val dy = point.y - initialPoint.y val norm = direction.x * dx + direction.y * dy val clippedPoint = PointF( initialPoint.x + direction.x * norm, initialPoint.y + direction.y * norm ) val newValue = pointToValue(clippedPoint) return try { manager.updateParams(listOf(managedParam), floatArrayOf(newValue)) clippedPoint } catch (e: ParamsManager.InvalidValues) { e.printStackTrace() null } } } private class XHandle( private val yProvider: (values: FloatArray) -> Float, private val managedParam: ParamT ) : LinearHandle(managedParam, PointF(1f, 0f)) { override fun computeDraggablePoint(values: FloatArray): PointF = PointF(values[managedParam], yProvider(values)) override fun pointToValue(point: PointF): Float = point.x } private class YHandle( private val xProvider: (values: FloatArray) -> Float, private val managedParam: ParamT ) : LinearHandle(managedParam, PointF(0f, 1f)) { override fun computeDraggablePoint(values: FloatArray): PointF = PointF(xProvider(values), values[managedParam]) override fun pointToValue(point: PointF): Float = point.y } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/utils/ImageDecoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.utils import android.content.Context import android.graphics.Bitmap import android.net.Uri import coil3.imageLoader import coil3.memory.MemoryCache import coil3.request.ImageRequest import coil3.request.allowHardware import coil3.toBitmap import com.t8rin.collages.public.CollageConstants.requestMapper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext internal object ImageDecoder { var SAMPLER_SIZE = 1536 suspend fun decodeFileToBitmap( context: Context, pathName: Uri ): Bitmap? = withContext(Dispatchers.IO) { val stringKey = pathName.toString() + SAMPLER_SIZE + "ImageDecoder" val key = MemoryCache.Key(stringKey) context.imageLoader.memoryCache?.get(key)?.image?.toBitmap() ?: context.imageLoader.execute( ImageRequest.Builder(context) .allowHardware(false) .diskCacheKey(stringKey) .memoryCacheKey(key) .data(pathName) .size(SAMPLER_SIZE) .run(requestMapper) .build() ).image?.toBitmap()?.apply { if (config != Bitmap.Config.ARGB_8888) { setConfig(Bitmap.Config.ARGB_8888) } } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManager.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.utils import android.os.Bundle import com.t8rin.collages.view.PhotoItem typealias ParamT = Int internal typealias ItemUpdate = (photoItem: PhotoItem, values: FloatArray) -> Unit internal class ParamsManager( private val values: FloatArray, private val itemUpdateFunctions: List, private val itemHandles: List>, private val itemsByIndex: List, private val paramToDependentItems: Array ) { class InvalidValues : RuntimeException() companion object { const val minSize: Float = 0.05f } var onItemUpdated: (Int) -> Unit = {} fun getHandles(itemIndex: Int): List = if (itemIndex in itemHandles.indices) itemHandles[itemIndex] else emptyList() fun snapshotValues(): FloatArray = values.copyOf() // Only for handles registered with this manager internal fun valuesRef(): FloatArray = values fun updateParams(params: List, newValues: FloatArray, notify: Boolean = true) { val previous = FloatArray(params.size) { i -> values[params[i]] } try { for (i in params.indices) { values[params[i]] = newValues[i] } val affected: MutableSet = mutableSetOf() for (param in params) { val arr = if (param in paramToDependentItems.indices) paramToDependentItems[param] else intArrayOf() for (item in arr) affected.add(item) } for (itemIndex in affected) { val photoItem = if (itemIndex in itemsByIndex.indices) itemsByIndex[itemIndex] else null val update = if (itemIndex in itemUpdateFunctions.indices) itemUpdateFunctions[itemIndex] else null if (photoItem != null && update != null) update(photoItem, values) } } catch (e: InvalidValues) { //rollback updateParams(params, previous, false) throw e } if (!notify) return val notified: MutableSet = mutableSetOf() for (param in params) { val arr = if (param in paramToDependentItems.indices) paramToDependentItems[param] else intArrayOf() for (item in arr) if (notified.add(item)) onItemUpdated(item) } } fun saveInstanceState(outState: Bundle) { outState.putFloatArray("collage_params_values", values.copyOf()) } fun restoreInstanceState(savedInstanceState: Bundle) { val saved = savedInstanceState.getFloatArray("collage_params_values") ?: return val count = minOf(saved.size, values.size) if (count <= 0) return val indices = (0 until count).toList() val newValues = FloatArray(count) { i -> saved[i] } updateParams(indices, newValues, notify = false) } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/utils/ParamsManagerBuilder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.utils import android.graphics.PointF import android.graphics.RectF import com.t8rin.collages.view.PhotoItem internal class ParamsManagerBuilder { private companion object { const val MIN_DIMENSION_TOLERANCE: Float = 1e-4f } private val paramValues: MutableList = mutableListOf() private val items: MutableList = mutableListOf() private val itemUpdates: MutableMap = mutableMapOf() private val itemHandles: MutableMap> = mutableMapOf() private val paramToItems: MutableMap> = mutableMapOf() fun param(initial: Float): ParamT { paramValues.add(initial) return paramValues.lastIndex } fun addBoxedItem( photoItem: PhotoItem? = null, xParams: List = emptyList(), yParams: List = emptyList(), boxParams: (FloatArray) -> RectF, action: (PhotoItem, FloatArray, RectF) -> Unit = { _, _, _ -> } ): ParamsManagerBuilder { val photoItem = photoItem ?: PhotoItem() if (photoItem.pointList.isEmpty()) { photoItem.pointList.add(PointF(0f, 0f)) photoItem.pointList.add(PointF(1f, 0f)) photoItem.pointList.add(PointF(1f, 1f)) photoItem.pointList.add(PointF(0f, 1f)) } val handles = mutableListOf() for (xParam in xParams) { handles.add( Handle.horizontal( yProvider = { boxParams(it).run { (bottom + top) / 2f } }, managedParam = xParam ) ) } for (yParam in yParams) { handles.add( Handle.vertical( xProvider = { boxParams(it).run { (right + left) / 2f } }, managedParam = yParam ) ) } return add( photoItem = photoItem, params = xParams + yParams, listener = { p, vs -> val box = boxParams(vs) if (box.left < 0) throw ParamsManager.InvalidValues() if (box.top < 0) throw ParamsManager.InvalidValues() if (box.right > 1f) throw ParamsManager.InvalidValues() if (box.bottom > 1f) throw ParamsManager.InvalidValues() if (box.right - box.left + MIN_DIMENSION_TOLERANCE < ParamsManager.minSize) { throw ParamsManager.InvalidValues() } if (box.bottom - box.top + MIN_DIMENSION_TOLERANCE < ParamsManager.minSize) { throw ParamsManager.InvalidValues() } p.bound.set(box) action(p, vs, box) }, handles = handles ) } fun add( photoItem: PhotoItem, params: List = emptyList(), listener: ItemUpdate = { _, _ -> }, handles: List = emptyList() ): ParamsManagerBuilder { photoItem.index = items.size items.add(photoItem) itemUpdates[photoItem.index] = listener itemHandles[photoItem.index] = handles for (p in params) paramToItems.getOrPut(p) { mutableSetOf() }.add(photoItem.index) return this } fun build(): Pair> { val maxItemIndex = (items.maxOfOrNull { it.index } ?: -1) + 1 val valuesArray = paramValues.toFloatArray() val itemUpdateArray = ArrayList(maxItemIndex).apply { repeat(maxItemIndex) { add(null) } for ((i, upd) in itemUpdates) if (i in 0 until maxItemIndex) this[i] = upd } val itemHandlesArray = ArrayList>(maxItemIndex).apply { repeat(maxItemIndex) { add(emptyList()) } for ((i, hs) in itemHandles) if (i in 0 until maxItemIndex) this[i] = hs } val itemsByIndex = ArrayList(maxItemIndex).apply { repeat(maxItemIndex) { add(null) } for (it in items) if (it.index in 0 until maxItemIndex) this[it.index] = it } val dependents: Array = Array(paramValues.size) { intArrayOf() } for ((p, set) in paramToItems) { dependents[p] = set.sorted().toIntArray() } val manager = ParamsManager( values = valuesArray, itemUpdateFunctions = itemUpdateArray, itemHandles = itemHandlesArray, itemsByIndex = itemsByIndex, paramToDependentItems = dependents ) val values = manager.snapshotValues() // Apply initial updates for (photoItem in items) { val update = itemUpdateArray.getOrNull(photoItem.index) if (update != null) update(photoItem, values) } return manager to items.toList() } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/utils/PreviewCollageGeneration.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.utils import android.graphics.Bitmap import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.keepScreenOn import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.scale import androidx.core.net.toUri import com.t8rin.collages.Collage import com.t8rin.collages.CollageType import com.t8rin.collages.model.CollageLayout import com.t8rin.collages.utils.CollageLayoutFactory.COLLAGE_MAP import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.io.File import kotlin.math.pow import kotlin.math.sqrt @Composable fun PreviewCollageGeneration( startFrom: Int = 0 ) { fun Bitmap.replaceColor( fromColor: Color, targetColor: Color, tolerance: Float ): Bitmap { fun Color.distanceFrom(color: Color): Float { return sqrt( (red - color.red).pow(2) + (green - color.green).pow(2) + (blue - color.blue).pow( 2 ) ) } val width = width val height = height val pixels = IntArray(width * height) getPixels(pixels, 0, width, 0, 0, width, height) for (x in pixels.indices) { pixels[x] = if (Color(pixels[x]).distanceFrom(fromColor) <= tolerance) { targetColor.toArgb() } else pixels[x] } val result = createBitmap(width, height) result.setPixels(pixels, 0, width, 0, 0, width, height) return result } var allFrames: List by remember { mutableStateOf(emptyList()) } val context = LocalContext.current LaunchedEffect(context) { allFrames = COLLAGE_MAP.values.map { it.invoke() }.filter { it.photoItemList.size >= startFrom } } var previewImageUri by rememberSaveable { mutableStateOf(null) } LaunchedEffect(previewImageUri) { if (previewImageUri == null) { val file = File(context.cacheDir, "tmp.png") file.outputStream().use { createBitmap(200, 200).applyCanvas { drawColor(Color.Black.toArgb()) }.compress(Bitmap.CompressFormat.PNG, 100, it) } previewImageUri = file.toUri() } } val scope = rememberCoroutineScope { Dispatchers.IO } val dir = remember { File(context.cacheDir, "frames").apply { deleteRecursively() mkdirs() } } if (previewImageUri != null) { Row( modifier = Modifier .keepScreenOn() .horizontalScroll(rememberScrollState()) .padding(vertical = 16.dp) .systemBarsPadding() ) { val data = remember(allFrames) { allFrames.groupBy { it.photoItemList.size }.toList().sortedBy { it.first } } data.forEachIndexed { index, (count, templates) -> Text( count.toString(), color = Color.White, modifier = Modifier.background(Color.Black) ) templates.forEach { template -> val (_, title, _, photoItemList) = template val density = LocalDensity.current val spacing = with(density) { 1.5.dp.toPx() } var trigger by remember { mutableStateOf(false) } LaunchedEffect(Unit) { delay(500 + 10L * index) trigger = true } Collage( images = photoItemList.mapNotNull { previewImageUri }, modifier = Modifier.size(64.dp), spacing = spacing, cornerRadius = 0f, onCollageCreated = { image -> scope.launch { val file = File(dir, "$title.png") file.createNewFile() file.outputStream().use { image.scale(525, 525, false).replaceColor( fromColor = Color.Black, targetColor = Color.Transparent, tolerance = 0.1f ).compress(Bitmap.CompressFormat.PNG, 100, it) } println("DONE: $title") } }, outputScaleRatio = 10f, collageCreationTrigger = trigger, collageType = CollageType( layout = template, index = null ), userInteractionEnabled = false ) } } } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/view/FrameImageView.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.view import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Matrix import android.graphics.Paint import android.graphics.Path import android.graphics.PointF import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.graphics.RectF import android.os.Bundle import android.view.GestureDetector import android.view.MotionEvent import android.widget.RelativeLayout import androidx.appcompat.widget.AppCompatImageView import androidx.core.graphics.withClip import androidx.core.graphics.withSave import androidx.core.net.toUri import com.t8rin.collages.utils.GeometryUtils import com.t8rin.collages.utils.ImageDecoder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.math.abs import kotlin.math.min @SuppressLint("ViewConstructor") internal class FrameImageView( private val context: Context, val photoItem: PhotoItem ) : AppCompatImageView(context) { private val mGestureDetector: GestureDetector = GestureDetector( context, object : GestureDetector.SimpleOnGestureListener() { override fun onLongPress(e: MotionEvent) { if (mOnImageClickListener != null) { mOnImageClickListener!!.onLongClickImage(this@FrameImageView) } } override fun onDoubleTap(e: MotionEvent): Boolean { if (mOnImageClickListener != null) { mOnImageClickListener!!.onDoubleClickImage(this@FrameImageView) } return true } override fun onSingleTapUp(e: MotionEvent): Boolean { if (mOnImageClickListener != null) { mOnImageClickListener!!.onSingleTapImage(this@FrameImageView) } return super.onSingleTapUp(e) } } ) private var mTouchHandler: MultiTouchHandler? = null var image: Bitmap? = null private val mPaint: Paint = Paint() private val mImageMatrix: Matrix = Matrix() private var viewWidth: Float = 0.toFloat() private var viewHeight: Float = 0.toFloat() private var mOnImageClickListener: OnImageClickListener? = null private var mOriginalLayoutParams: RelativeLayout.LayoutParams? = null private var mEnableTouch = true private var corner = 0f private var space = 0f private val mPath = Path() private val mBackgroundPath = Path() private val mPolygon = ArrayList() private val mPathRect = Rect(0, 0, 0, 0) private var mSelected = true private val mConvertedPoints = ArrayList() private var mBackgroundColor = Color.WHITE //Clear area private val mClearPath = Path() private val mConvertedClearPoints = ArrayList() // If true, user allowed empty space via gestures; don't auto-zoom it away on frame updates private var userAllowedEmptySpace: Boolean = false // Feature flags private var rotationEnabled: Boolean = true private var snapToBordersEnabled: Boolean = false private var imageLoadJob: Job? = null private var imageLoadToken: String? = null fun setRotationEnabled(enabled: Boolean) { rotationEnabled = enabled mTouchHandler?.setEnableRotation(enabled) } fun setSnapToBordersEnabled(enabled: Boolean) { snapToBordersEnabled = enabled if (enabled) { // Snap immediately snapToBorders() } } var originalLayoutParams: RelativeLayout.LayoutParams get() { if (mOriginalLayoutParams != null) { val params = RelativeLayout.LayoutParams( mOriginalLayoutParams!!.width, mOriginalLayoutParams!!.height ) params.leftMargin = mOriginalLayoutParams!!.leftMargin params.topMargin = mOriginalLayoutParams!!.topMargin return params } else { return layoutParams as RelativeLayout.LayoutParams } } set(originalLayoutParams) { mOriginalLayoutParams = RelativeLayout.LayoutParams(originalLayoutParams.width, originalLayoutParams.height) mOriginalLayoutParams!!.leftMargin = originalLayoutParams.leftMargin mOriginalLayoutParams!!.topMargin = originalLayoutParams.topMargin } interface OnImageClickListener { fun onLongClickImage(view: FrameImageView) fun onDoubleClickImage(view: FrameImageView) fun onSingleTapImage(view: FrameImageView) } private var viewState: Bundle = Bundle.EMPTY init { reloadImageFromPhotoItem() mPaint.isFilterBitmap = true mPaint.isAntiAlias = true scaleType = ScaleType.MATRIX setLayerType(LAYER_TYPE_SOFTWARE, mPaint) } fun saveInstanceState(outState: Bundle) { val index = photoItem.index val values = FloatArray(9) mImageMatrix.getValues(values) outState.putFloatArray("mImageMatrix_$index", values) outState.putFloat("mViewWidth_$index", viewWidth) outState.putFloat("mViewHeight_$index", viewHeight) outState.putFloat("mCorner_$index", corner) outState.putFloat("mSpace_$index", space) outState.putInt("mBackgroundColor_$index", mBackgroundColor) } /** * Called after init() function * * @param savedInstanceState */ fun restoreInstanceState(savedInstanceState: Bundle) { viewState = savedInstanceState val index = photoItem.index val values = savedInstanceState.getFloatArray("mImageMatrix_$index") if (values != null) { mImageMatrix.setValues(values) } viewWidth = savedInstanceState.getFloat("mViewWidth_$index", 1f) viewHeight = savedInstanceState.getFloat("mViewHeight_$index", 1f) corner = savedInstanceState.getFloat("mCorner_$index", 0f) space = savedInstanceState.getFloat("mSpace_$index", 0f) mBackgroundColor = savedInstanceState.getInt("mBackgroundColor_$index", Color.WHITE) mTouchHandler!!.matrix = mImageMatrix setSpace(space, corner) } fun swapImage(view: FrameImageView) { if (image != null && view.image != null) { val temp = view.image view.image = image image = temp val tmpPath = view.photoItem.imagePath view.photoItem.imagePath = photoItem.imagePath photoItem.imagePath = tmpPath resetImageMatrix() view.resetImageMatrix() } } fun reloadImageFromPhotoItem() { val token = photoItem.imagePath?.toString()?.takeIf { it.isNotEmpty() } // Skip if we're already loading the same image if (token == imageLoadToken && imageLoadJob?.isActive == true) return imageLoadJob?.cancel() imageLoadToken = token imageLoadJob = CoroutineScope(Dispatchers.Main.immediate).launch { // Decode on IO if we have a token/uri; null otherwise val bmp = withContext(Dispatchers.IO) { token?.let { t -> runCatching { ImageDecoder.decodeFileToBitmap( context = context, pathName = t.toUri() ) }.getOrNull() } } // If another reload changed the token while we were decoding, bail out if (token != imageLoadToken) return@launch if (bmp == null) { image = null invalidate() } else { image = bmp resetImageMatrix() if (viewState != Bundle.EMPTY) { restoreInstanceState(viewState) viewState = Bundle.EMPTY } } // Clear the job if we're still the latest load for this token if (token == imageLoadToken) imageLoadJob = null } } fun setOnImageClickListener(onImageClickListener: OnImageClickListener) { mOnImageClickListener = onImageClickListener } override fun setBackgroundColor(backgroundColor: Int) { mBackgroundColor = backgroundColor invalidate() } override fun getImageMatrix(): Matrix { return mImageMatrix } @JvmOverloads fun init( viewWidth: Float, viewHeight: Float, space: Float = 0f, corner: Float = 0f ) { this.viewWidth = viewWidth this.viewHeight = viewHeight this.space = space this.corner = corner if (image != null) { mImageMatrix.set( createMatrixToDrawImageInCenterView( viewWidth = viewWidth, viewHeight = viewHeight, imageWidth = image!!.width.toFloat(), imageHeight = image!!.height.toFloat() ) ) } mTouchHandler = MultiTouchHandler() mTouchHandler!!.matrix = mImageMatrix mTouchHandler!!.setEnableRotation(rotationEnabled) setSpace(this.space, this.corner) } fun setSpace(space: Float, corner: Float) { this.space = space this.corner = corner setSpace( viewWidth, viewHeight, photoItem, mConvertedPoints, mConvertedClearPoints, mPath, mClearPath, mBackgroundPath, mPolygon, mPathRect, space, corner ) invalidate() } fun updateFrame(viewWidth: Float, viewHeight: Float) { // --- Save previous state we need once val oldW = this.viewWidth val oldH = this.viewHeight // Preserve center point mImageMatrix.postTranslate((viewWidth - oldW) * 0.5f, (viewHeight - oldH) * 0.5f) if (oldW > 0 && oldH > 0 && abs(viewWidth / oldW - viewHeight / oldH) < 0.00001f) { // Simple center rescale val scale = (viewWidth / oldW + viewHeight / oldH) / 2 mImageMatrix.postScale(scale, scale, viewWidth / 2, viewHeight / 2) } else { if (image != null) { val hasEmptyAfter = hasEmptySpace(mImageMatrix, viewWidth, viewHeight) // If empty space disappeared naturally due to frame change, clear the pin if (!hasEmptyAfter) { userAllowedEmptySpace = false } // If empty space would appear and the user didn't pin it, zoom just enough to cover. if (hasEmptyAfter && !userAllowedEmptySpace) { snapToBorders(onlyMatrixUpdate = true) } } } mTouchHandler?.matrix = mImageMatrix // --- Apply new bounds and rebuild geometry this.viewWidth = viewWidth this.viewHeight = viewHeight mConvertedPoints.clear() mConvertedClearPoints.clear() mPolygon.clear() mPath.reset() mClearPath.reset() mBackgroundPath.reset() // Invalidates the view setSpace(this.space, this.corner) } private fun resetImageMatrix() { if (image != null) { mImageMatrix.set( createMatrixToDrawImageInCenterView( viewWidth = viewWidth, viewHeight = viewHeight, imageWidth = image!!.width.toFloat(), imageHeight = image!!.height.toFloat() ) ) } mTouchHandler?.matrix = mImageMatrix invalidate() } fun isSelected(x: Float, y: Float): Boolean { return GeometryUtils.contains(mPolygon, PointF(x, y)) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) drawImage( canvas, mPath, mPaint, mPathRect, image, mImageMatrix, width.toFloat(), height.toFloat(), mBackgroundColor, mBackgroundPath, mClearPath, mPolygon ) } fun drawOutputImage(canvas: Canvas, outputScale: Float) { val viewWidth = this.viewWidth * outputScale val viewHeight = this.viewHeight * outputScale val path = Path() val clearPath = Path() val backgroundPath = Path() val pathRect = Rect() val polygon = ArrayList() setSpace( viewWidth, viewHeight, photoItem, ArrayList(), ArrayList(), path, clearPath, backgroundPath, polygon, pathRect, space * outputScale, corner * outputScale ) val exportMatrix = Matrix(mImageMatrix).apply { postScale(outputScale, outputScale) } drawImage( canvas, path, mPaint, pathRect, image, exportMatrix, viewWidth, viewHeight, mBackgroundColor, backgroundPath, clearPath, polygon ) } @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { if (!mEnableTouch) { return super.onTouchEvent(event) } else { if (event.action == MotionEvent.ACTION_DOWN) { mSelected = GeometryUtils.contains(mPolygon, PointF(event.x, event.y)) } if (mSelected) { if (event.action == MotionEvent.ACTION_UP) { mSelected = false } mGestureDetector.onTouchEvent(event) if (mTouchHandler != null && image != null && !image!!.isRecycled) { mTouchHandler!!.touch(event) mImageMatrix.set(mTouchHandler!!.matrix) if (event.action == MotionEvent.ACTION_UP) { if (snapToBordersEnabled) { snapToBorders() } // Update weather user allowed empty space based on current transform userAllowedEmptySpace = hasEmptySpace(mImageMatrix, viewWidth, viewHeight) } invalidate() } return true } else { return super.onTouchEvent(event) } } } private fun snapToBorders(onlyMatrixUpdate: Boolean = false) { val img = image ?: return if (viewWidth <= 0f || viewHeight <= 0f) return fun mappedRect(): RectF = RectF(0f, 0f, img.width.toFloat(), img.height.toFloat()).also { mImageMatrix.mapRect(it) } var rect = mappedRect() var changed = false // 1) Minimal translation to reduce single-edge gaps (always apply if it reduces gap) var dx = 0f var dy = 0f val leftGap = rect.left - space val rightGap = (viewWidth - space) - rect.right val topGap = rect.top - space val bottomGap = (viewHeight - space) - rect.bottom val leftNeeds = leftGap > 0f val rightNeeds = rightGap > 0f if (leftNeeds.xor(rightNeeds)) { dx = if (leftNeeds) -leftGap else rightGap } val topNeeds = topGap > 0f val bottomNeeds = bottomGap > 0f if (topNeeds.xor(bottomNeeds)) { dy = if (topNeeds) -topGap else bottomGap } if (dx != 0f || dy != 0f) { mImageMatrix.postTranslate(dx, dy) rect = mappedRect() changed = true } // 2) Minimal uniform scale about view center to cover remaining gaps (capped) val cx = viewWidth * 0.5f val cy = viewHeight * 0.5f var needed = 1f if (rect.left > space) { val denom = (cx - rect.left) if (denom > 0f) needed = kotlin.math.max(needed, (cx - space) / denom) } if (rect.top > space) { val denom = (cy - rect.top) if (denom > 0f) needed = kotlin.math.max(needed, (cy - space) / denom) } if (rect.right < viewWidth - space) { val denom = (rect.right - cx) if (denom > 0f) needed = kotlin.math.max(needed, (cx - space) / denom) } if (rect.bottom < viewHeight - space) { val denom = (rect.bottom - cy) if (denom > 0f) needed = kotlin.math.max(needed, (cy - space) / denom) } val maxStep = 4f val scale = min(needed, maxStep) if (scale > 1.0005f) { mImageMatrix.postScale(scale, scale, cx, cy) rect = mappedRect() changed = true } // 3) Final clamp to remove any residual tiny gaps var cdx = 0f var cdy = 0f if (rect.left > space) cdx = space - rect.left if (rect.top > space) cdy = space - rect.top if (rect.right < viewWidth - space) cdx = (viewWidth - space) - rect.right if (rect.bottom < viewHeight - space) cdy = (viewHeight - space) - rect.bottom if (cdx != 0f || cdy != 0f) { mImageMatrix.postTranslate(cdx, cdy) changed = true } if (onlyMatrixUpdate) return if (changed) { mTouchHandler?.matrix = mImageMatrix invalidate() } } private fun hasEmptySpace(matrix: Matrix, vw: Float, vh: Float): Boolean { if (image == null || vw <= 0f || vh <= 0f) return false val rect = RectF(0f, 0f, image!!.width.toFloat(), image!!.height.toFloat()) matrix.mapRect(rect) // if image rect does not fully cover the view rect, there is empty space return rect.left > space || rect.top > space || rect.right < vw - space || rect.bottom < vh - space } companion object { private fun setSpace( viewWidth: Float, viewHeight: Float, photoItem: PhotoItem, convertedPoints: MutableList, convertedClearPoints: MutableList, path: Path, clearPath: Path, backgroundPath: Path, polygon: MutableList, pathRect: Rect, space: Float, corner: Float ) { if (convertedPoints.isEmpty()) { for (p in photoItem.pointList) { val convertedPoint = PointF(p.x * viewWidth, p.y * viewHeight) convertedPoints.add(convertedPoint) if (photoItem.shrinkMap != null) { photoItem.shrinkMap!![convertedPoint] = photoItem.shrinkMap!![p]!! } } } if (photoItem.clearAreaPoints != null && photoItem.clearAreaPoints!!.isNotEmpty()) { clearPath.reset() if (convertedClearPoints.isEmpty()) for (p in photoItem.clearAreaPoints!!) { convertedClearPoints.add(PointF(p.x * viewWidth, p.y * viewHeight)) } GeometryUtils.createPathWithCircleCorner(clearPath, convertedClearPoints, corner) } else if (photoItem.clearPath != null) { clearPath.reset() buildRealClearPath(viewWidth, viewHeight, photoItem, clearPath, corner) } if (photoItem.path != null) { buildRealPath(viewWidth, viewHeight, photoItem, path, space, corner) polygon.clear() } else { val shrunkPoints: List when (photoItem.shrinkMethod) { PhotoItem.SHRINK_METHOD_3_3 -> { val centerPointIdx = findCenterPointIndex(photoItem) shrunkPoints = GeometryUtils.shrinkPathCollage_3_3( convertedPoints, centerPointIdx, space, photoItem.bound ) } PhotoItem.SHRINK_METHOD_USING_MAP if photoItem.shrinkMap != null -> { shrunkPoints = GeometryUtils.shrinkPathCollageUsingMap( convertedPoints, space, photoItem.shrinkMap!! ) } PhotoItem.SHRINK_METHOD_COMMON if photoItem.shrinkMap != null -> { shrunkPoints = GeometryUtils.commonShrinkPath( convertedPoints, space, photoItem.shrinkMap!! ) } else -> { shrunkPoints = if (photoItem.disableShrink) { GeometryUtils.shrinkPath(convertedPoints, 0f, photoItem.bound) } else { GeometryUtils.shrinkPath(convertedPoints, space, photoItem.bound) } } } polygon.clear() polygon.addAll(shrunkPoints) GeometryUtils.createPathWithCircleCorner(path, shrunkPoints, corner) if (photoItem.hasBackground) { backgroundPath.reset() GeometryUtils.createPathWithCircleCorner( backgroundPath, convertedPoints, corner ) } } pathRect.set(0, 0, 0, 0) } private fun findCenterPointIndex(photoItem: PhotoItem): Int { var centerPointIdx = 0 if (photoItem.bound.left == 0f && photoItem.bound.top == 0f) { var minX = 1f for (idx in photoItem.pointList.indices) { val p = photoItem.pointList[idx] if (p.x > 0 && p.x < 1 && p.y > 0 && p.y < 1 && p.x < minX) { centerPointIdx = idx minX = p.x } } } else { var maxX = 0f for (idx in photoItem.pointList.indices) { val p = photoItem.pointList[idx] if (p.x > 0 && p.x < 1 && p.y > 0 && p.y < 1 && p.x > maxX) { centerPointIdx = idx maxX = p.x } } } return centerPointIdx } private fun buildRealPath( viewWidth: Float, viewHeight: Float, photoItem: PhotoItem, outPath: Path, space: Float, corner: Float ) { var newSpace = space if (photoItem.path != null) { val rect = RectF() photoItem.path!!.computeBounds(rect, true) val pathWidthPixels = rect.width() val pathHeightPixels = rect.height() newSpace *= 2 outPath.set(photoItem.path!!) val m = Matrix() val ratioX: Float val ratioY: Float if (photoItem.fitBound) { ratioX = photoItem.pathScaleRatio * (viewWidth * photoItem.pathRatioBound!!.width() - 2 * newSpace) / pathWidthPixels ratioY = photoItem.pathScaleRatio * (viewHeight * photoItem.pathRatioBound!!.height() - 2 * newSpace) / pathHeightPixels } else { val ratio = min( photoItem.pathScaleRatio * (viewHeight - 2 * newSpace) / pathHeightPixels, photoItem.pathScaleRatio * (viewWidth - 2 * newSpace) / pathWidthPixels ) ratioX = ratio ratioY = ratio } m.postScale(ratioX, ratioY) outPath.transform(m) val bound = RectF() when (photoItem.cornerMethod) { PhotoItem.CORNER_METHOD_3_6 -> { outPath.computeBounds(bound, true) GeometryUtils.createRegularPolygonPath( outPath, min(bound.width(), bound.height()), 6, corner ) outPath.computeBounds(bound, true) } PhotoItem.CORNER_METHOD_3_13 -> { outPath.computeBounds(bound, true) GeometryUtils.createRectanglePath( outPath, bound.width(), bound.height(), corner ) outPath.computeBounds(bound, true) } else -> { outPath.computeBounds(bound, true) } } var x: Float var y: Float if (photoItem.shrinkMethod == PhotoItem.SHRINK_METHOD_3_6 || photoItem.shrinkMethod == PhotoItem.SHRINK_METHOD_3_8) { x = viewWidth / 2 - bound.width() / 2 y = viewHeight / 2 - bound.height() / 2 m.reset() m.postTranslate(x, y) outPath.transform(m) } else { if (photoItem.pathAlignParentRight) { x = photoItem.pathRatioBound!!.right * viewWidth - bound.width() - newSpace / ratioX y = photoItem.pathRatioBound!!.top * viewHeight + newSpace / ratioY } else { x = photoItem.pathRatioBound!!.left * viewWidth + newSpace / ratioX y = photoItem.pathRatioBound!!.top * viewHeight + newSpace / ratioY } if (photoItem.pathInCenterHorizontal) { x = viewWidth / 2.0f - bound.width() / 2.0f } if (photoItem.pathInCenterVertical) { y = viewHeight / 2.0f - bound.height() / 2.0f } m.reset() m.postTranslate(x, y) outPath.transform(m) } } } private fun buildRealClearPath( viewWidth: Float, viewHeight: Float, photoItem: PhotoItem, clearPath: Path, corner: Float ): Path? { if (photoItem.clearPath != null) { val rect = RectF() photoItem.clearPath!!.computeBounds(rect, true) val clearPathWidthPixels = rect.width() val clearPathHeightPixels = rect.height() clearPath.set(photoItem.clearPath!!) val m = Matrix() val ratioX: Float val ratioY: Float if (photoItem.fitBound) { ratioX = photoItem.clearPathScaleRatio * viewWidth * photoItem.clearPathRatioBound!!.width() / clearPathWidthPixels ratioY = photoItem.clearPathScaleRatio * viewHeight * photoItem.clearPathRatioBound!!.height() / clearPathHeightPixels } else { val ratio = min( photoItem.clearPathScaleRatio * viewHeight / clearPathHeightPixels, photoItem.clearPathScaleRatio * viewWidth / clearPathWidthPixels ) ratioX = ratio ratioY = ratio } m.postScale(ratioX, ratioY) clearPath.transform(m) val bound = RectF() when (photoItem.cornerMethod) { PhotoItem.CORNER_METHOD_3_6 -> { clearPath.computeBounds(bound, true) GeometryUtils.createRegularPolygonPath( clearPath, min(bound.width(), bound.height()), 6, corner ) clearPath.computeBounds(bound, true) } PhotoItem.CORNER_METHOD_3_13 -> { clearPath.computeBounds(bound, true) GeometryUtils.createRectanglePath( clearPath, bound.width(), bound.height(), corner ) clearPath.computeBounds(bound, true) } else -> { clearPath.computeBounds(bound, true) } } var x: Float var y: Float if (photoItem.shrinkMethod == PhotoItem.SHRINK_METHOD_3_6) { x = if (photoItem.clearPathRatioBound!!.left > 0) { viewWidth - bound.width() / 2 } else { -bound.width() / 2 } y = viewHeight / 2 - bound.height() / 2 } else { if (photoItem.centerInClearBound) { x = photoItem.clearPathRatioBound!!.left * viewWidth + (viewWidth / 2 - bound.width() / 2) y = photoItem.clearPathRatioBound!!.top * viewHeight + (viewHeight / 2 - bound.height() / 2) } else { x = photoItem.clearPathRatioBound!!.left * viewWidth y = photoItem.clearPathRatioBound!!.top * viewHeight if (photoItem.clearPathInCenterHorizontal) { x = viewWidth / 2.0f - bound.width() / 2.0f } if (photoItem.clearPathInCenterVertical) { y = viewHeight / 2.0f - bound.height() / 2.0f } } } m.reset() m.postTranslate(x, y) clearPath.transform(m) return clearPath } else { return null } } private fun drawImage( canvas: Canvas, path: Path, paint: Paint, pathRect: Rect, image: Bitmap?, imageMatrix: Matrix, viewWidth: Float, viewHeight: Float, color: Int, backgroundPath: Path?, clearPath: Path?, touchPolygon: MutableList? ) { if (image != null && !image.isRecycled) { canvas.drawBitmap(image, imageMatrix, paint) } //clip outside if (pathRect.left == pathRect.right) { canvas.withClip(path) { pathRect.set(clipBounds) } } canvas.save() paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) canvas.drawARGB(0x00, 0x00, 0x00, 0x00) paint.color = Color.BLACK paint.style = Paint.Style.FILL canvas.drawRect(0f, 0f, viewWidth, pathRect.top.toFloat(), paint) canvas.drawRect(0f, 0f, pathRect.left.toFloat(), viewHeight, paint) canvas.drawRect(pathRect.right.toFloat(), 0f, viewWidth, viewHeight, paint) canvas.drawRect(0f, pathRect.bottom.toFloat(), viewWidth, viewHeight, paint) paint.xfermode = null canvas.restore() //clip inside canvas.save() paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) canvas.drawARGB(0x00, 0x00, 0x00, 0x00) paint.color = Color.BLACK paint.style = Paint.Style.FILL val currentFillType = path.fillType path.fillType = Path.FillType.INVERSE_WINDING canvas.drawPath(path, paint) paint.xfermode = null canvas.restore() path.fillType = currentFillType //clear area if (clearPath != null) { canvas.withSave { paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) drawARGB(0x00, 0x00, 0x00, 0x00) paint.color = Color.BLACK paint.style = Paint.Style.FILL drawPath(clearPath, paint) paint.xfermode = null } } //draw out side if (backgroundPath != null) { canvas.withSave { paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER) drawARGB(0x00, 0x00, 0x00, 0x00) paint.color = color paint.style = Paint.Style.FILL drawPath(backgroundPath, paint) paint.xfermode = null } } //touch polygon if (touchPolygon != null && touchPolygon.isEmpty()) { touchPolygon.add(PointF(pathRect.left.toFloat(), pathRect.top.toFloat())) touchPolygon.add(PointF(pathRect.right.toFloat(), pathRect.top.toFloat())) touchPolygon.add(PointF(pathRect.right.toFloat(), pathRect.bottom.toFloat())) touchPolygon.add(PointF(pathRect.left.toFloat(), pathRect.bottom.toFloat())) } } } private fun createMatrixToDrawImageInCenterView( viewWidth: Float, viewHeight: Float, imageWidth: Float, imageHeight: Float ): Matrix { val ratioWidth = viewWidth / imageWidth val ratioHeight = viewHeight / imageHeight val ratio = ratioWidth.coerceAtLeast(ratioHeight) val dx = (viewWidth - imageWidth) / 2.0f val dy = (viewHeight - imageHeight) / 2.0f val result = Matrix() result.postTranslate(dx, dy) result.postScale(ratio, ratio, viewWidth / 2, viewHeight / 2) return result } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/view/FramePhotoLayout.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.view import android.annotation.SuppressLint import android.app.ActivityManager import android.app.ActivityManager.MemoryInfo import android.content.ClipData import android.content.ClipDescription import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.graphics.PointF import android.graphics.RectF import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.net.Uri import android.os.Build import android.os.Bundle import android.view.DragEvent import android.view.MotionEvent import android.widget.RelativeLayout import androidx.compose.ui.graphics.toArgb import androidx.core.content.getSystemService import androidx.core.graphics.createBitmap import androidx.core.graphics.withTranslation import com.t8rin.collages.utils.Handle import com.t8rin.collages.utils.ImageDecoder import com.t8rin.collages.utils.ParamsManager import kotlin.math.max import androidx.compose.ui.graphics.Color as ComposeColor @SuppressLint("ViewConstructor") internal class FramePhotoLayout( context: Context, var mPhotoItems: List ) : RelativeLayout(context), FrameImageView.OnImageClickListener { private data class FrameMetrics( val leftMargin: Int, val topMargin: Int, val width: Int, val height: Int ) private fun computeFrameMetrics(bound: RectF): FrameMetrics { val leftMargin = (mViewWidth * bound.left).toInt() val topMargin = (mViewHeight * bound.top).toInt() val frameWidth: Int = if (bound.right == 1f) { mViewWidth - leftMargin } else { (mViewWidth * bound.width() + 0.5f).toInt() } val frameHeight: Int = if (bound.bottom == 1f) { mViewHeight - topMargin } else { (mViewHeight * bound.height() + 0.5f).toInt() } return FrameMetrics( leftMargin = leftMargin, topMargin = topMargin, width = frameWidth, height = frameHeight ) } private var mOnDragListener: OnDragListener = OnDragListener { v, event -> if (event.action == DragEvent.ACTION_DROP) { var target: FrameImageView? = v as FrameImageView val selectedView = getSelectedFrameImageView(target!!, event) if (selectedView != null) { target = selectedView val dragged = event.localState as FrameImageView var targetPath: Uri? = target.photoItem.imagePath var draggedPath: Uri? = dragged.photoItem.imagePath if (targetPath == null) targetPath = Uri.EMPTY if (draggedPath == null) draggedPath = Uri.EMPTY if (targetPath != draggedPath) target.swapImage(dragged) } } true } private val mItemImageViews: MutableList = ArrayList() private var mViewWidth: Int = 0 private var mViewHeight: Int = 0 private var backgroundColor: ComposeColor = ComposeColor.White private var onItemTapListener: ((index: Int) -> Unit)? = null // Handle overlay state private var selectedItemIndex: Int? = null private var activeHandle: Handle? = null private var paramsManager: ParamsManager? = null private var handleDrawable: Drawable? = null // Flags propagated from upper layers private var disableRotation: Boolean = false private var enableSnapToBorders: Boolean = false private val isNotLargeThan1Gb: Boolean get() = getMemoryInfo().run { totalMem > 0 && totalMem / 1048576.0 <= 1024 } init { setLayerType(LAYER_TYPE_HARDWARE, null) setWillNotDraw(false) // Enable focus so we can detect focus loss and clear selection isFocusable = true isFocusableInTouchMode = true } fun setParamsManager(manager: ParamsManager?) { paramsManager = manager // Update item views whenever params change paramsManager?.onItemUpdated = { itemIndex -> resizeItem(itemIndex) } } fun setDisableRotation(disable: Boolean) { disableRotation = disable // Update existing children for (v in mItemImageViews) { v.setRotationEnabled(!disableRotation) } } fun setEnableSnapToBorders(enable: Boolean) { enableSnapToBorders = enable // Update existing children for (v in mItemImageViews) { v.setSnapToBordersEnabled(enableSnapToBorders) } } private fun getSelectedFrameImageView( target: FrameImageView, event: DragEvent ): FrameImageView? { val dragged = event.localState as FrameImageView val leftMargin = (mViewWidth * target.photoItem.bound.left).toInt() val topMargin = (mViewHeight * target.photoItem.bound.top).toInt() val globalX = leftMargin + event.x val globalY = topMargin + event.y for (idx in mItemImageViews.indices.reversed()) { val view = mItemImageViews[idx] val x = globalX - mViewWidth * view.photoItem.bound.left val y = globalY - mViewHeight * view.photoItem.bound.top if (view.isSelected(x, y)) { return if (view === dragged) { null } else { view } } } return null } fun saveInstanceState(outState: Bundle) { paramsManager?.saveInstanceState(outState) for (view in mItemImageViews) view.saveInstanceState(outState) } fun restoreInstanceState(savedInstanceState: Bundle) { paramsManager?.restoreInstanceState(savedInstanceState) for (view in mItemImageViews) view.restoreInstanceState(savedInstanceState) } @JvmOverloads fun build( viewWidth: Int, viewHeight: Int, space: Float = 0f, corner: Float = 0f ) { mItemImageViews.clear() removeAllViews() if (viewWidth < 1 || viewHeight < 1) { return } setBackgroundColor(backgroundColor.toArgb()) //add children views mViewWidth = viewWidth mViewHeight = viewHeight mItemImageViews.clear() //A circle view always is on top if (mPhotoItems.size > 4 || isNotLargeThan1Gb) { ImageDecoder.SAMPLER_SIZE = 1024 } else { ImageDecoder.SAMPLER_SIZE = 1600 } for (item in mPhotoItems) { val imageView = addPhotoItemView(item, space, corner) mItemImageViews.add(imageView) } } fun resize(width: Int, height: Int) { mViewWidth = width mViewHeight = height for (i in mItemImageViews.indices) { resizeItem(i) } } private fun resizeItem(index: Int) { val view = mItemImageViews[index] val item = mPhotoItems[index] val metrics = computeFrameMetrics(item.bound) val params = view.layoutParams as LayoutParams params.leftMargin = metrics.leftMargin params.topMargin = metrics.topMargin params.width = metrics.width params.height = metrics.height view.layoutParams = params view.updateFrame(metrics.width.toFloat(), metrics.height.toFloat()) } fun setBackgroundColor(color: ComposeColor) { backgroundColor = color setBackgroundColor(backgroundColor.toArgb()) invalidate() } fun setOnItemTapListener(listener: ((index: Int) -> Unit)?) { onItemTapListener = listener } fun setSpace(space: Float, corner: Float) { for (img in mItemImageViews) img.setSpace(space, corner) } fun setHandleDrawable(drawable: Drawable?) { handleDrawable = drawable ?: createDefaultHandleDrawable() invalidate() } fun updateImages(images: List) { val minSize = kotlin.math.min(images.size, mPhotoItems.size) for (i in 0 until minSize) { val newUri = images[i] val item = mPhotoItems[i] val oldUri = item.imagePath val changed = (oldUri?.toString() ?: "") != (newUri.toString()) if (changed) { item.imagePath = newUri if (i < mItemImageViews.size) { mItemImageViews[i].reloadImageFromPhotoItem() } } } } private fun createDefaultHandleDrawable(): Drawable { val diameterPx = 72 // equals previous 36px radius circle return GradientDrawable().apply { shape = GradientDrawable.OVAL setColor(android.graphics.Color.rgb(255, 165, 0)) setSize(diameterPx, diameterPx) } } @SuppressLint("ClickableViewAccessibility") private fun addPhotoItemView( item: PhotoItem, space: Float, corner: Float ): FrameImageView { val imageView = FrameImageView(context, item) val metrics = computeFrameMetrics(item.bound) imageView.setRotationEnabled(!disableRotation) imageView.setSnapToBordersEnabled(enableSnapToBorders) imageView.init(metrics.width.toFloat(), metrics.height.toFloat(), space, corner) imageView.setOnImageClickListener(this) imageView.setOnTouchListener { _, event -> // Intercept to support handle dragging overlay onTouchEvent(event) } if (mPhotoItems.size > 1) imageView.setOnDragListener(mOnDragListener) val params = LayoutParams(metrics.width, metrics.height) params.leftMargin = metrics.leftMargin params.topMargin = metrics.topMargin imageView.originalLayoutParams = params addView(imageView, params) return imageView } @Throws(OutOfMemoryError::class) fun createImage(outputScaleRatio: Float): Bitmap { try { val template = createBitmap( (outputScaleRatio * mViewWidth).toInt(), (outputScaleRatio * mViewHeight).toInt() ) val canvas = Canvas(template) canvas.drawColor(backgroundColor.toArgb()) for (view in mItemImageViews) if (view.image != null && !view.image!!.isRecycled) { val left = (view.left * outputScaleRatio).toInt() val top = (view.top * outputScaleRatio).toInt() val width = (view.width * outputScaleRatio).toInt() val height = (view.height * outputScaleRatio).toInt() //draw image canvas.saveLayer( left.toFloat(), top.toFloat(), (left + width).toFloat(), (top + height).toFloat(), Paint() ) canvas.translate(left.toFloat(), top.toFloat()) canvas.clipRect(0, 0, width, height) view.drawOutputImage(canvas, outputScaleRatio) canvas.restore() } return template } catch (error: OutOfMemoryError) { throw error } } override fun onLongClickImage(view: FrameImageView) { if (mPhotoItems.size > 1) { view.tag = """x=${0f},y=${0f},path=${view.photoItem.imagePath}""" val item = ClipData.Item(view.tag as CharSequence) val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN) val dragData = ClipData(view.tag.toString(), mimeTypes, item) val myShadow = DragShadowBuilder(view) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { view.startDragAndDrop(dragData, myShadow, view, 0) } else { @Suppress("DEPRECATION") view.startDrag(dragData, myShadow, view, 0) } } } override fun onDoubleClickImage(view: FrameImageView) { } fun clearSelection() { selectedItemIndex = null activeHandle = null onItemTapListener?.invoke(-1) invalidate() } override fun onSingleTapImage(view: FrameImageView) { if (selectedItemIndex == view.photoItem.index) { clearSelection() } else { onItemTapListener?.invoke(view.photoItem.index) selectedItemIndex = view.photoItem.index requestFocus() invalidate() } } override fun onWindowFocusChanged(hasWindowFocus: Boolean) { super.onWindowFocusChanged(hasWindowFocus) if (!hasWindowFocus) { // Clear selection when window focus is lost if (selectedItemIndex != null) { clearSelection() } } } override fun onFocusChanged( gainFocus: Boolean, direction: Int, previouslyFocusedRect: android.graphics.Rect? ) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect) if (!gainFocus) { // Clear selection when view focus is lost if (selectedItemIndex != null) { clearSelection() } } } override fun dispatchDraw(canvas: Canvas) { super.dispatchDraw(canvas) // draw handles for selected item val index = selectedItemIndex ?: return val handles = paramsManager?.getHandles(index) ?: emptyList() if (handles.isEmpty()) return val drawable = handleDrawable ?: return for (handle in handles) { val dp = handle.draggablePoint(paramsManager!!) val cx = mViewWidth * dp.x val cy = mViewHeight * dp.y val angle = handle.getAngle() + 90f val diameter = max( drawable.intrinsicWidth, drawable.intrinsicHeight ).takeIf { it > 0 }?.toFloat() ?: 72f val half = diameter / 2f canvas.withTranslation(cx, cy) { canvas.rotate(angle) drawable.setBounds((-half).toInt(), (-half).toInt(), half.toInt(), half.toInt()) drawable.draw(canvas) } } } @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { val index = selectedItemIndex ?: return super.onTouchEvent(event) mItemImageViews.firstOrNull { it.photoItem.index == index } ?: return super.onTouchEvent( event ) val manager = paramsManager ?: return super.onTouchEvent(event) val handles = manager.getHandles(index) if (handles.isEmpty()) return super.onTouchEvent(event) // Compute coordinates in this layout's local space regardless of source view val screenPos = IntArray(2) getLocationOnScreen(screenPos) val globalX = event.rawX - screenPos[0] val globalY = event.rawY - screenPos[1] when (event.action) { MotionEvent.ACTION_DOWN -> { val drawable = handleDrawable if (drawable == null) { activeHandle = null return super.onTouchEvent(event) } val diameter = max( drawable.intrinsicWidth, drawable.intrinsicHeight ).takeIf { it > 0 }?.toFloat() ?: 72f val radius = diameter / 2f activeHandle = handles.firstOrNull { handle -> val dp = handle.draggablePoint(manager) val hx = mViewWidth * dp.x val hy = mViewHeight * dp.y val dx = globalX - hx val dy = globalY - hy dx * dx + dy * dy <= radius * radius } return activeHandle != null || super.onTouchEvent(event) } MotionEvent.ACTION_MOVE -> { val handle = activeHandle ?: return super.onTouchEvent(event) val nx = (globalX / mViewWidth).coerceIn(0f, 1f) val ny = (globalY / mViewHeight).coerceIn(0f, 1f) handle.tryDrag(PointF(nx, ny), manager) ?: return true // Drag failed invalidate() return true } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { activeHandle = null return super.onTouchEvent(event) } } return super.onTouchEvent(event) } private fun getMemoryInfo(): MemoryInfo = MemoryInfo().apply { context.getSystemService()?.getMemoryInfo(this) } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/view/MultiTouchHandler.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("DEPRECATION") package com.t8rin.collages.view import android.graphics.Matrix import android.graphics.PointF import android.os.Parcel import android.os.Parcelable import android.view.MotionEvent import kotlin.math.atan2 import kotlin.math.sqrt internal class MultiTouchHandler : Parcelable { // these matrices will be used to move and zoom image private var mMatrix = Matrix() private var mSavedMatrix = Matrix() private var mMode = NONE // remember some things for zooming private var mStart = PointF() private var mMid = PointF() private var mOldDist = 1f private var mD = 0f private var mNewRot = 0f private var mLastEvent: FloatArray? = null private var mEnableRotation = false private var mEnableZoom = true private var mEnableTranslateX = true private var mEnableTranslateY = true private var mMaxPositionOffset = -1f private var mOldImagePosition = PointF(0f, 0f) private var mCheckingPosition = PointF(0f, 0f) var matrix: Matrix get() = mMatrix set(matrix) { this.mMatrix.set(matrix) mSavedMatrix.set(matrix) } constructor() fun reset() { this.mMatrix.reset() this.mSavedMatrix.reset() mMode = NONE mStart.set(0f, 0f) mMid.set(0f, 0f) mOldDist = 1f mD = 0f mNewRot = 0f mLastEvent = null mEnableRotation = false } fun setMaxPositionOffset(maxPositionOffset: Float) { mMaxPositionOffset = maxPositionOffset } fun touch(event: MotionEvent) { when (event.action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_DOWN -> { mSavedMatrix.set(mMatrix) mStart.set(event.x, event.y) mOldImagePosition.set(mCheckingPosition.x, mCheckingPosition.y) mMode = DRAG mLastEvent = null } MotionEvent.ACTION_POINTER_DOWN -> { mOldDist = spacing(event) if (mOldDist > 10f) { mSavedMatrix.set(mMatrix) midPoint(mMid, event) mMode = ZOOM } mLastEvent = FloatArray(4) mLastEvent!![0] = event.getX(0) mLastEvent!![1] = event.getX(1) mLastEvent!![2] = event.getY(0) mLastEvent!![3] = event.getY(1) mD = rotation(event) } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { mMode = NONE mLastEvent = null } MotionEvent.ACTION_MOVE -> if (mMode == DRAG) { mMatrix.set(mSavedMatrix) mCheckingPosition.set(mOldImagePosition.x, mOldImagePosition.y) var dx = event.x - mStart.x var dy = event.y - mStart.y mCheckingPosition.x += dx mCheckingPosition.y += dy if (!mEnableTranslateX) { dx = 0f if (mCheckingPosition.y > mMaxPositionOffset) { dy -= (mCheckingPosition.y - mMaxPositionOffset) mCheckingPosition.y = mMaxPositionOffset } else if (mCheckingPosition.y < -mMaxPositionOffset) { dy -= (mCheckingPosition.y + mMaxPositionOffset) mCheckingPosition.y = -mMaxPositionOffset } } if (!mEnableTranslateY) { dy = 0f if (mCheckingPosition.x > mMaxPositionOffset) { dx -= (mCheckingPosition.x - mMaxPositionOffset) mCheckingPosition.x = mMaxPositionOffset } else if (mCheckingPosition.x < -mMaxPositionOffset) { dx -= (mCheckingPosition.x + mMaxPositionOffset) mCheckingPosition.x = -mMaxPositionOffset } } mMatrix.postTranslate(dx, dy) } else if (mMode == ZOOM && mEnableZoom) { val newDist = spacing(event) if (newDist > 10f) { mMatrix.set(mSavedMatrix) val scale = newDist / mOldDist mMatrix.postScale(scale, scale, mMid.x, mMid.y) } if (mEnableRotation && mLastEvent != null && event.pointerCount == 2) { mNewRot = rotation(event) midPoint(mMid, event) val r = mNewRot - mD mMatrix.postRotate(r, mMid.x, mMid.y) } } } } fun setEnableRotation(enableRotation: Boolean) { mEnableRotation = enableRotation } fun setEnableZoom(enableZoom: Boolean) { mEnableZoom = enableZoom } fun setEnableTranslateX(enableTranslateX: Boolean) { mEnableTranslateX = enableTranslateX } fun setEnableTranslateY(enableTranslateY: Boolean) { mEnableTranslateY = enableTranslateY } /** * Determine the space between the first two fingers */ private fun spacing(event: MotionEvent): Float { val x = event.getX(0) - event.getX(1) val y = event.getY(0) - event.getY(1) return sqrt((x * x + y * y).toDouble()).toFloat() } /** * Calculate the mid point of the first two fingers */ private fun midPoint(point: PointF, event: MotionEvent) { val x = event.getX(0) + event.getX(1) val y = event.getY(0) + event.getY(1) point.set(x / 2, y / 2) } /** * Calculate the degree to be rotated by. * * @param event * @return Degrees */ private fun rotation(event: MotionEvent): Float { val deltaX = (event.getX(0) - event.getX(1)).toDouble() val deltaY = (event.getY(0) - event.getY(1)).toDouble() val radians = atan2(deltaY, deltaX) return Math.toDegrees(radians).toFloat() } override fun describeContents(): Int { return 0 } private constructor(`in`: Parcel) { var values = FloatArray(9) `in`.readFloatArray(values) mMatrix = Matrix() mMatrix.setValues(values) values = FloatArray(9) `in`.readFloatArray(values) mSavedMatrix = Matrix() mSavedMatrix.setValues(values) mMode = `in`.readInt() mStart = `in`.readParcelable(PointF::class.java.classLoader)!! mMid = `in`.readParcelable(PointF::class.java.classLoader)!! mOldDist = `in`.readFloat() mD = `in`.readFloat() mNewRot = `in`.readFloat() val b = BooleanArray(4) `in`.readBooleanArray(b) mEnableRotation = b[0] mEnableZoom = b[1] mEnableTranslateX = b[2] mEnableTranslateY = b[3] mMaxPositionOffset = `in`.readFloat() mOldImagePosition = `in`.readParcelable(PointF::class.java.classLoader)!! mCheckingPosition = `in`.readParcelable(PointF::class.java.classLoader)!! } override fun writeToParcel(dest: Parcel, flags: Int) { var values = FloatArray(9) mMatrix.getValues(values) dest.writeFloatArray(values) values = FloatArray(9) mSavedMatrix.getValues(values) dest.writeFloatArray(values) dest.writeInt(mMode) dest.writeParcelable(mStart, flags) dest.writeParcelable(mMid, flags) dest.writeFloat(mOldDist) dest.writeFloat(mD) dest.writeFloat(mNewRot) val b = booleanArrayOf(mEnableRotation, mEnableZoom, mEnableTranslateX, mEnableTranslateY) dest.writeBooleanArray(b) dest.writeFloat(mMaxPositionOffset) dest.writeParcelable(mOldImagePosition, flags) dest.writeParcelable(mCheckingPosition, flags) } companion object CREATOR : Parcelable.Creator { private const val NONE = 0 private const val DRAG = 1 private const val ZOOM = 2 override fun createFromParcel(parcel: Parcel): MultiTouchHandler { return MultiTouchHandler(parcel) } override fun newArray(size: Int): Array { return arrayOfNulls(size) } } } ================================================ FILE: lib/collages/src/main/java/com/t8rin/collages/view/PhotoItem.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.collages.view import android.graphics.Path import android.graphics.PointF import android.graphics.RectF import android.net.Uri internal data class PhotoItem( //Primary info var index: Int = 0, var imagePath: Uri? = null, //Using point list to construct view. All points and width, height are in [0, 1] range. var pointList: ArrayList = ArrayList(), var bound: RectF = RectF(), //Using path to create var path: Path? = null, var pathRatioBound: RectF? = null, var pathInCenterHorizontal: Boolean = false, var pathInCenterVertical: Boolean = false, var pathAlignParentRight: Boolean = false, var pathScaleRatio: Float = 1f, var fitBound: Boolean = false, //other info var hasBackground: Boolean = false, var shrinkMethod: Int = SHRINK_METHOD_DEFAULT, var cornerMethod: Int = CORNER_METHOD_DEFAULT, var disableShrink: Boolean = false, var shrinkMap: HashMap? = null, //Clear polygon or arc area var clearAreaPoints: ArrayList? = null, //Clear an area using path var clearPath: Path? = null, var clearPathRatioBound: RectF? = null, var clearPathInCenterHorizontal: Boolean = false, var clearPathInCenterVertical: Boolean = false, var clearPathScaleRatio: Float = 1f, var centerInClearBound: Boolean = false, ) { companion object { const val SHRINK_METHOD_DEFAULT = 0 const val SHRINK_METHOD_3_3 = 1 const val SHRINK_METHOD_USING_MAP = 2 const val SHRINK_METHOD_3_6 = 3 const val SHRINK_METHOD_3_8 = 4 const val SHRINK_METHOD_COMMON = 5 const val CORNER_METHOD_DEFAULT = 0 const val CORNER_METHOD_3_6 = 1 const val CORNER_METHOD_3_13 = 2 } } ================================================ FILE: lib/colors/.gitignore ================================================ /build ================================================ FILE: lib/colors/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.colors" dependencies { implementation(libs.androidx.palette.ktx) implementation(projects.lib.gesture) implementation(projects.lib.image) implementation(projects.lib.zoomable) } ================================================ FILE: lib/colors/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/colors/src/main/assets/color_names.json ================================================ {"#FF000000": "Black", "#FF000080": "Navy Blue", "#FF0000C8": "Dark Blue", "#FF0000FF": "Blue", "#FF000741": "Stratos", "#FF001B1C": "Swamp", "#FF002387": "Resolution Blue", "#FF002900": "Deep Fir", "#FF002E20": "Burnham", "#FF002FA7": "Klein Blue", "#FF003153": "Prussian Blue", "#FF003366": "Midnight Blue", "#FF003399": "Smalt", "#FF003532": "Deep Teal", "#FF003E40": "Cyprus", "#FF004620": "Kaitoke Green", "#FF0047AB": "Cobalt", "#FF004816": "Crusoe", "#FF004950": "Sherpa Blue", "#FF0056A7": "Endeavour", "#FF00581A": "Camarone", "#FF0066CC": "Science Blue", "#FF0066FF": "Blue Ribbon", "#FF00755E": "Tropical Rain Forest", "#FF0076A3": "Allports", "#FF007BA7": "Deep Cerulean", "#FF007EC7": "Lochmara", "#FF007FFF": "Azure Radiance", "#FF008080": "Teal", "#FF0095B6": "Bondi Blue", "#FF009DC4": "Pacific Blue", "#FF00A693": "Persian Green", "#FF00A86B": "Jade", "#FF00CC99": "Caribbean Green", "#FF00CCCC": "Robin's Egg Blue", "#FF00FF00": "Green", "#FF00FF7F": "Spring Green", "#FF00FFFF": "Cyan Aqua", "#FF010D1A": "Blue Charcoal", "#FF011635": "Midnight", "#FF011D13": "Holly", "#FF012731": "Daintree", "#FF01361C": "Cardin Green", "#FF01371A": "County Green", "#FF013E62": "Astronaut Blue", "#FF013F6A": "Regal Blue", "#FF014B43": "Aqua Deep", "#FF015E85": "Orient", "#FF016162": "Blue Stone", "#FF016D39": "Fun Green", "#FF01796F": "Pine Green", "#FF017987": "Blue Lagoon", "#FF01826B": "Deep Sea", "#FF01A368": "Green Haze", "#FF022D15": "English Holly", "#FF02402C": "Sherwood Green", "#FF02478E": "Congress Blue", "#FF024E46": "Evening Sea", "#FF026395": "Bahama Blue", "#FF02866F": "Observatory", "#FF02A4D3": "Cerulean", "#FF03163C": "Tangaroa", "#FF032B52": "Green Vogue", "#FF036A6E": "Mosque", "#FF041004": "Midnight Moss", "#FF041322": "Black Pearl", "#FF042E4C": "Blue Whale", "#FF044022": "Zuccini", "#FF044259": "Teal Blue", "#FF051040": "Deep Cove", "#FF051657": "Gulf Blue", "#FF055989": "Venice Blue", "#FF056F57": "Watercourse", "#FF062A78": "Catalina Blue", "#FF063537": "Tiber", "#FF069B81": "Gossamer", "#FF06A189": "Niagara", "#FF073A50": "Tarawera", "#FF080110": "Jaguar", "#FF081910": "Black Bean", "#FF082567": "Deep Sapphire", "#FF088370": "Elf Green", "#FF08E8DE": "Bright Turquoise", "#FF092256": "Downriver", "#FF09230F": "Palm Green", "#FF09255D": "Madison", "#FF093624": "Bottle Green", "#FF095859": "Deep Sea Green", "#FF097F4B": "Salem", "#FF0A001C": "Black Russian", "#FF0A480D": "Dark Fern", "#FF0A6906": "Japanese Laurel", "#FF0A6F75": "Atoll", "#FF0B0B0B": "Cod Gray", "#FF0B0F08": "Marshland", "#FF0B1107": "Gordons Green", "#FF0B1304": "Black Forest", "#FF0B6207": "San Felix", "#FF0BDA51": "Malachite", "#FF0C0B1D": "Ebony", "#FF0C0D0F": "Woodsmoke", "#FF0C1911": "Racing Green", "#FF0C7A79": "Surfie Green", "#FF0C8990": "Blue Chill", "#FF0D0332": "Black Rock", "#FF0D1117": "Bunker", "#FF0D1C19": "Aztec", "#FF0D2E1C": "Bush", "#FF0E0E18": "Cinder", "#FF0E2A30": "Firefly", "#FF0F2D9E": "Torea Bay", "#FF10121D": "Vulcan", "#FF101405": "Green Waterloo", "#FF105852": "Eden", "#FF110C6C": "Arapawa", "#FF120A8F": "Ultramarine", "#FF123447": "Elephant", "#FF126B40": "Jewel", "#FF130000": "Diesel", "#FF130A06": "Asphalt", "#FF13264D": "Blue Zodiac", "#FF134F19": "Parsley", "#FF140600": "Nero", "#FF1450AA": "Tory Blue", "#FF151F4C": "Bunting", "#FF1560BD": "Denim", "#FF15736B": "Genoa", "#FF161928": "Mirage", "#FF161D10": "Hunter Green", "#FF162A40": "Big Stone", "#FF163222": "Celtic", "#FF16322C": "Timber Green", "#FF163531": "Gable Green", "#FF171F04": "Pine Tree", "#FF175579": "Chathams Blue", "#FF182D09": "Deep Forest Green", "#FF18587A": "Blumine", "#FF19330E": "Palm Leaf", "#FF193751": "Nile Blue", "#FF1959A8": "Fun Blue", "#FF1A1A68": "Lucky Point", "#FF1AB385": "Mountain Meadow", "#FF1B0245": "Tolopea", "#FF1B1035": "Haiti", "#FF1B127B": "Deep Koamaru", "#FF1B1404": "Acadia", "#FF1B2F11": "Seaweed", "#FF1B3162": "Biscay", "#FF1B659D": "Matisse", "#FF1C1208": "Crowshead", "#FF1C1E13": "Rangoon Green", "#FF1C39BB": "Persian Blue", "#FF1C402E": "Everglade", "#FF1C7C7D": "Elm", "#FF1D6142": "Green Pea", "#FF1E0F04": "Creole", "#FF1E1609": "Karaka", "#FF1E1708": "El Paso", "#FF1E385B": "Cello", "#FF1E433C": "Te Papa Green", "#FF1E90FF": "Dodger Blue", "#FF1E9AB0": "Eastern Blue", "#FF1F120F": "Night Rider", "#FF1FC2C2": "Java", "#FF20208D": "Jacksons Purple", "#FF202E54": "Cloud Burst", "#FF204852": "Blue Dianne", "#FF211A0E": "Eternity", "#FF220878": "Deep Blue", "#FF228B22": "Forest Green", "#FF233418": "Mallard", "#FF240A40": "Violet", "#FF240C02": "Kilimanjaro", "#FF242A1D": "Log Cabin", "#FF242E16": "Black Olive", "#FF24500F": "Green House", "#FF251607": "Graphite", "#FF251706": "Cannon Black", "#FF251F4F": "Port Gore", "#FF25272C": "Shark", "#FF25311C": "Green Kelp", "#FF2596D1": "Curious Blue", "#FF260368": "Paua", "#FF26056A": "Paris M", "#FF261105": "Wood Bark", "#FF261414": "Gondola", "#FF262335": "Steel Gray", "#FF26283B": "Ebony Clay", "#FF273A81": "Bay of Many", "#FF27504B": "Plantation", "#FF278A5B": "Eucalyptus", "#FF281E15": "Oil", "#FF283A77": "Astronaut", "#FF286ACD": "Mariner", "#FF290C5E": "Violent Violet", "#FF292130": "Bastille", "#FF292319": "Zeus", "#FF292937": "Charade", "#FF297B9A": "Jelly Bean", "#FF29AB87": "Jungle Green", "#FF2A0359": "Cherry Pie", "#FF2A140E": "Coffee Bean", "#FF2A2630": "Baltic Sea", "#FF2A380B": "Turtle Green", "#FF2A52BE": "Cerulean Blue", "#FF2B0202": "Sepia Black", "#FF2B194F": "Valhalla", "#FF2B3228": "Heavy Metal", "#FF2C0E8C": "Blue Gem", "#FF2C1632": "Revolver", "#FF2C2133": "Bleached Cedar", "#FF2C8C84": "Lochinvar", "#FF2D2510": "Mikado", "#FF2D383A": "Outer Space", "#FF2D569B": "St Tropez", "#FF2E0329": "Jacaranda", "#FF2E1905": "Jacko Bean", "#FF2E3222": "Rangitoto", "#FF2E3F62": "Rhino", "#FF2E8B57": "Sea Green", "#FF2EBFD4": "Scooter", "#FF2F270E": "Onion", "#FF2F3CB3": "Governor Bay", "#FF2F519E": "Sapphire", "#FF2F5A57": "Spectra", "#FF2F6168": "Casal", "#FF300529": "Melanzane", "#FF301F1E": "Cocoa Brown", "#FF302A0F": "Woodrush", "#FF304B6A": "San Juan", "#FF30D5C8": "Turquoise", "#FF311C17": "Eclipse", "#FF314459": "Pickled Bluewood", "#FF315BA1": "Azure", "#FF31728D": "Calypso", "#FF317D82": "Paradiso", "#FF32127A": "Persian Indigo", "#FF32293A": "Blackcurrant", "#FF323232": "Mine Shaft", "#FF325D52": "Stromboli", "#FF327C14": "Bilbao", "#FF327DA0": "Astral", "#FF33036B": "Christalle", "#FF33292F": "Thunder", "#FF33CC99": "Shamrock", "#FF341515": "Tamarind", "#FF350036": "Mardi Gras", "#FF350E42": "Valentino", "#FF350E57": "Jagger", "#FF353542": "Tuna", "#FF354E8C": "Chambray", "#FF363050": "Martinique", "#FF363534": "Tuatara", "#FF363C0D": "Waiouru", "#FF36747D": "Ming", "#FF368716": "La Palma", "#FF370202": "Chocolate", "#FF371D09": "Clinker", "#FF37290E": "Brown Tumbleweed", "#FF373021": "Birch", "#FF377475": "Oracle", "#FF380474": "Blue Diamond", "#FF381A51": "Grape", "#FF383533": "Dune", "#FF384555": "Oxford Blue", "#FF384910": "Clover", "#FF394851": "Limed Spruce", "#FF396413": "Dell", "#FF3A0020": "Toledo", "#FF3A2010": "Sambuca", "#FF3A2A6A": "Jacarta", "#FF3A686C": "William", "#FF3A6A47": "Killarney", "#FF3AB09E": "Keppel", "#FF3B000B": "Temptress", "#FF3B0910": "Aubergine", "#FF3B1F1F": "Jon", "#FF3B2820": "Treehouse", "#FF3B7A57": "Amazon", "#FF3B91B4": "Boston Blue", "#FF3C0878": "Windsor", "#FF3C1206": "Rebel", "#FF3C1F76": "Meteorite", "#FF3C2005": "Dark Ebony", "#FF3C3910": "Camouflage", "#FF3C4151": "Bright Gray", "#FF3C4443": "Cape Cod", "#FF3C493A": "Lunar Green", "#FF3D0C02": "Bean", "#FF3D2B1F": "Bistre", "#FF3D7D52": "Goblin", "#FF3E0480": "Kingfisher Daisy", "#FF3E1C14": "Cedar", "#FF3E2B23": "English Walnut", "#FF3E2C1C": "Black Marlin", "#FF3E3A44": "Ship Gray", "#FF3EABBF": "Pelorous", "#FF3F2109": "Bronze", "#FF3F2500": "Cola", "#FF3F3002": "Madras", "#FF3F307F": "Minsk", "#FF3F4C3A": "Cabbage Pont", "#FF3F583B": "Tom Thumb", "#FF3F5D53": "Mineral Green", "#FF3FC1AA": "Puerto Rico", "#FF3FFF00": "Harlequin", "#FF401801": "Brown Pod", "#FF40291D": "Cork", "#FF403B38": "Masala", "#FF403D19": "Thatch Green", "#FF405169": "Fjord", "#FF40826D": "Viridian", "#FF40A860": "Chateau Green", "#FF410056": "Ripe Plum", "#FF411E10": "Paco", "#FF412010": "Deep Oak", "#FF413C37": "Merlin", "#FF414257": "Gun Powder", "#FF414C7D": "East Bay", "#FF4169E1": "Royal Blue", "#FF41AA78": "Ocean Green", "#FF420303": "Burnt Maroon", "#FF423921": "Lisbon Brown", "#FF427977": "Faded Jade", "#FF431560": "Scarlet Gum", "#FF433120": "Iroko", "#FF433E37": "Armadillo", "#FF434C59": "River Bed", "#FF436A0D": "Green Leaf", "#FF44012D": "Barossa", "#FF441D00": "Morocco Brown", "#FF444954": "Mako", "#FF454936": "Kelp", "#FF456CAC": "San Marino", "#FF45B1E8": "Picton Blue", "#FF460B41": "Loulou", "#FF462425": "Crater Brown", "#FF465945": "Gray Asparagus", "#FF4682B4": "Steel Blue", "#FF480404": "Rustic Red", "#FF480607": "Bulgarian Rose", "#FF480656": "Clairvoyant", "#FF481C1C": "Cocoa Bean", "#FF483131": "Woody Brown", "#FF483C32": "Taupe", "#FF49170C": "Van Cleef", "#FF492615": "Brown Derby", "#FF49371B": "Metallic Bronze", "#FF495400": "Verdun Green", "#FF496679": "Blue Bayoux", "#FF497183": "Bismark", "#FF4A2A04": "Bracken", "#FF4A3004": "Deep Bronze", "#FF4A3C30": "Mondo", "#FF4A4244": "Tundora", "#FF4A444B": "Gravel", "#FF4A4E5A": "Trout", "#FF4B0082": "Pigment Indigo", "#FF4B5D52": "Nandor", "#FF4C3024": "Saddle", "#FF4C4F56": "Abbey", "#FF4D0135": "Blackberry", "#FF4D0A18": "Cab Sav", "#FF4D1E01": "Indian Tan", "#FF4D282D": "Cowboy", "#FF4D282E": "Livid Brown", "#FF4D3833": "Rock", "#FF4D3D14": "Punga", "#FF4D400F": "Bronzetone", "#FF4D5328": "Woodland", "#FF4E0606": "Mahogany", "#FF4E2A5A": "Bossanova", "#FF4E3B41": "Matterhorn", "#FF4E420C": "Bronze Olive", "#FF4E4562": "Mulled Wine", "#FF4E6649": "Axolotl", "#FF4E7F9E": "Wedgewood", "#FF4EABD1": "Shakespeare", "#FF4F1C70": "Honey Flower", "#FF4F2398": "Daisy Bush", "#FF4F69C6": "Indigo", "#FF4F7942": "Fern Green", "#FF4F9D5D": "Fruit Salad", "#FF4FA83D": "Apple", "#FF504351": "Mortar", "#FF507096": "Kashmir Blue", "#FF507672": "Cutty Sark", "#FF50C878": "Emerald", "#FF514649": "Emperor", "#FF516E3D": "Chalet Green", "#FF517C66": "Como", "#FF51808F": "Smalt Blue", "#FF52001F": "Castro", "#FF520C17": "Maroon Oak", "#FF523C94": "Gigas", "#FF533455": "Voodoo", "#FF534491": "Victoria", "#FF53824B": "Hippie Green", "#FF541012": "Heath", "#FF544333": "Judge Gray", "#FF54534D": "Fuscous Gray", "#FF549019": "Vida Loca", "#FF55280C": "Cioccolato", "#FF555B10": "Saratoga", "#FF556D56": "Finlandia", "#FF5590D9": "Havelock Blue", "#FF56B4BE": "Fountain Blue", "#FF578363": "Spring Leaves", "#FF583401": "Saddle Brown", "#FF585562": "Scarpa Flow", "#FF587156": "Cactus", "#FF589AAF": "Hippie Blue", "#FF591D35": "Wine Berry", "#FF592804": "Brown Bramble", "#FF593737": "Congo Brown", "#FF594433": "Millbrook", "#FF5A6E9C": "Waikawa Gray", "#FF5A87A0": "Horizon", "#FF5B3013": "Jambalaya", "#FF5C0120": "Bordeaux", "#FF5C0536": "Mulberry Wood", "#FF5C2E01": "Carnaby Tan", "#FF5C5D75": "Comet", "#FF5D1E0F": "Redwood", "#FF5D4C51": "Don Juan", "#FF5D5C58": "Chicago", "#FF5D5E37": "Verdigris", "#FF5D7747": "Dingley", "#FF5DA19F": "Breaker Bay", "#FF5E483E": "Kabul", "#FF5E5D3B": "Hemlock", "#FF5F3D26": "Irish Coffee", "#FF5F5F6E": "Mid Gray", "#FF5F6672": "Shuttle Gray", "#FF5FA777": "Aqua Forest", "#FF5FB3AC": "Tradewind", "#FF604913": "Horses Neck", "#FF605B73": "Smoky", "#FF606E68": "Corduroy", "#FF6093D1": "Danube", "#FF612718": "Espresso", "#FF614051": "Eggplant", "#FF615D30": "Costa Del Sol", "#FF61845F": "Glade Green", "#FF622F30": "Buccaneer", "#FF623F2D": "Quincy", "#FF624E9A": "Butterfly Bush", "#FF625119": "West Coast", "#FF626649": "Finch", "#FF639A8F": "Patina", "#FF63B76C": "Fern", "#FF6456B7": "Blue Violet", "#FF646077": "Dolphin", "#FF646463": "Storm Dust", "#FF646A54": "Siam", "#FF646E75": "Nevada", "#FF6495ED": "Cornflower Blue", "#FF64CCDB": "Viking", "#FF65000B": "Rosewood", "#FF651A14": "Cherrywood", "#FF652DC1": "Purple Heart", "#FF657220": "Fern Frond", "#FF65745D": "Willow Grove", "#FF65869F": "Hoki", "#FF660045": "Pompadour", "#FF660099": "Purple", "#FF66023C": "Tyrian Purple", "#FF661010": "Dark Tan", "#FF66B58F": "Silver Tree", "#FF66FF00": "Bright Green", "#FF66FF66": "Screamin Green", "#FF67032D": "Black Rose", "#FF675FA6": "Scampi", "#FF676662": "Ironside Gray", "#FF678975": "Viridian Green", "#FF67A712": "Christi", "#FF683600": "Nutmeg Wood Finish", "#FF685558": "Zambezi", "#FF685E6E": "Salt Box", "#FF692545": "Tawny Port", "#FF692D54": "Finn", "#FF695F62": "Scorpion", "#FF697E9A": "Lynch", "#FF6A442E": "Spice", "#FF6A5D1B": "Himalaya", "#FF6A6051": "Soya Bean", "#FF6B2A14": "Hairy Heath", "#FF6B3FA0": "Royal Purple", "#FF6B4E31": "Shingle Fawn", "#FF6B5755": "Dorado", "#FF6B8BA2": "Bermuda Gray", "#FF6C3082": "Eminence", "#FF6CDAE7": "Turquoise Blue", "#FF6D0101": "Lonestar", "#FF6D5E54": "Pine Cone", "#FF6D6C6C": "Dove Gray", "#FF6D9292": "Juniper", "#FF6D92A1": "Gothic", "#FF6E0902": "Red Oxide", "#FF6E1D14": "Moccaccino", "#FF6E4826": "Pickled Bean", "#FF6E4B26": "Dallas", "#FF6E6D57": "Kokoda", "#FF6E7783": "Pale Sky", "#FF6F440C": "Cafe Royale", "#FF6F6A61": "Flint", "#FF6F8E63": "Highland", "#FF6F9D02": "Limeade", "#FF6FD0C5": "Downy", "#FF701C1C": "Persian Plum", "#FF704214": "Sepia", "#FF704A07": "Antique Bronze", "#FF704F50": "Ferra", "#FF706555": "Coffee", "#FF708090": "Slate Gray", "#FF711A00": "Cedar Wood Finish", "#FF71291D": "Metallic Copper", "#FF714693": "Affair", "#FF714AB2": "Studio", "#FF715D47": "Tobacco Brown", "#FF716338": "Yellow Metal", "#FF716B56": "Peat", "#FF716E10": "Olivetone", "#FF717486": "Storm Gray", "#FF718080": "Sirocco", "#FF71D9E2": "Aquamarine Blue", "#FF72010F": "Venetian Red", "#FF724A2F": "Old Copper", "#FF726D4E": "Go Ben", "#FF727B89": "Raven", "#FF731E8F": "Seance", "#FF734A12": "Raw Umber", "#FF736C9F": "Kimberly", "#FF736D58": "Crocodile", "#FF737829": "Crete", "#FF738678": "Xanadu", "#FF74640D": "Spicy Mustard", "#FF747D63": "Limed Ash", "#FF747D83": "Rolling Stone", "#FF748881": "Blue Smoke", "#FF749378": "Laurel", "#FF74C365": "Mantis", "#FF755A57": "Russett", "#FF7563A8": "Deluge", "#FF76395D": "Cosmic", "#FF7666C6": "Blue Marguerite", "#FF76BD17": "Lima", "#FF76D7EA": "Sky Blue", "#FF770F05": "Dark Burgundy", "#FF771F1F": "Crown of Thorns", "#FF773F1A": "Walnut", "#FF776F61": "Pablo", "#FF778120": "Pacifika", "#FF779E86": "Oxley", "#FF77DD77": "Pastel Green", "#FF780109": "Japanese Maple", "#FF782D19": "Mocha", "#FF782F16": "Peanut", "#FF78866B": "Camouflage Green", "#FF788A25": "Wasabi", "#FF788BBA": "Ship Cove", "#FF78A39C": "Sea Nymph", "#FF795D4C": "Roman Coffee", "#FF796878": "Old Lavender", "#FF796989": "Rum", "#FF796A78": "Fedora", "#FF796D62": "Sandstone", "#FF79DEEC": "Spray", "#FF7A013A": "Siren", "#FF7A58C1": "Fuchsia Blue", "#FF7A7A7A": "Boulder", "#FF7A89B8": "Wild Blue Yonder", "#FF7AC488": "De York", "#FF7B3801": "Red Beech", "#FF7B3F00": "Cinnamon", "#FF7B6608": "Yukon Gold", "#FF7B7874": "Tapa", "#FF7B7C94": "Waterloo", "#FF7B8265": "Flax Smoke", "#FF7B9F80": "Amulet", "#FF7BA05B": "Asparagus", "#FF7C1C05": "Kenyan Copper", "#FF7C7631": "Pesto", "#FF7C778A": "Topaz", "#FF7C7B7A": "Concord", "#FF7C7B82": "Jumbo", "#FF7C881A": "Trendy Green", "#FF7CA1A6": "Gumbo", "#FF7CB0A1": "Acapulco", "#FF7CB7BB": "Neptune", "#FF7D2C14": "Pueblo", "#FF7DA98D": "Bay Leaf", "#FF7DC8F7": "Malibu", "#FF7DD8C6": "Bermuda", "#FF7E3A15": "Copper Canyon", "#FF7F1734": "Claret", "#FF7F3A02": "Peru Tan", "#FF7F626D": "Falcon", "#FF7F7589": "Mobster", "#FF7F76D3": "Moody Blue", "#FF7FFF00": "Chartreuse", "#FF7FFFD4": "Aquamarine", "#FF800000": "Maroon", "#FF800B47": "Rose Bud Cherry", "#FF801818": "Falu Red", "#FF80341F": "Red Robin", "#FF803790": "Vivid Violet", "#FF80461B": "Russet", "#FF807E79": "Friar Gray", "#FF808000": "Olive", "#FF808080": "Gray", "#FF80B3AE": "Gulf Stream", "#FF80B3C4": "Glacier", "#FF80CCEA": "Seagull", "#FF81422C": "Nutmeg", "#FF816E71": "Spicy Pink", "#FF817377": "Empress", "#FF819885": "Spanish Green", "#FF826F65": "Sand Dune", "#FF828685": "Gunsmoke", "#FF828F72": "Battleship Gray", "#FF831923": "Merlot", "#FF837050": "Shadow", "#FF83AA5D": "Chelsea Cucumber", "#FF83D0C6": "Monte Carlo", "#FF843179": "Plum", "#FF84A0A0": "Granny Smith", "#FF8581D9": "Chetwode Blue", "#FF858470": "Bandicoot", "#FF859FAF": "Bali Hai", "#FF85C4CC": "Half Baked", "#FF860111": "Red Devil", "#FF863C3C": "Lotus", "#FF86483C": "Ironstone", "#FF864D1E": "Bull Shot", "#FF86560A": "Rusty Nail", "#FF868974": "Bitter", "#FF86949F": "Regent Gray", "#FF871550": "Disco", "#FF87756E": "Americano", "#FF877C7B": "Hurricane", "#FF878D91": "Oslo Gray", "#FF87AB39": "Sushi", "#FF885342": "Spicy Mix", "#FF886221": "Kumera", "#FF888387": "Suva Gray", "#FF888D65": "Avocado", "#FF893456": "Camelot", "#FF893843": "Solid Pink", "#FF894367": "Cannon Pink", "#FF897D6D": "Makara", "#FF8A3324": "Burnt Umber", "#FF8A73D6": "True V", "#FF8A8360": "Clay Creek", "#FF8A8389": "Monsoon", "#FF8A8F8A": "Stack", "#FF8AB9F1": "Jordy Blue", "#FF8B00FF": "Electric Violet", "#FF8B0723": "Monarch", "#FF8B6B0B": "Corn Harvest", "#FF8B8470": "Olive Haze", "#FF8B847E": "Schooner", "#FF8B8680": "Natural Gray", "#FF8B9C90": "Mantle", "#FF8B9FEE": "Portage", "#FF8BA690": "Envy", "#FF8BA9A5": "Cascade", "#FF8BE6D8": "Riptide", "#FF8C055E": "Cardinal Pink", "#FF8C472F": "Mule Fawn", "#FF8C5738": "Potters Clay", "#FF8C6495": "Trendy Pink", "#FF8D0226": "Paprika", "#FF8D3D38": "Sanguine Brown", "#FF8D3F3F": "Tosca", "#FF8D7662": "Cement", "#FF8D8974": "Granite Green", "#FF8D90A1": "Manatee", "#FF8DA8CC": "Polo Blue", "#FF8E0000": "Red Berry", "#FF8E4D1E": "Rope", "#FF8E6F70": "Opium", "#FF8E775E": "Domino", "#FF8E8190": "Mamba", "#FF8EABC1": "Nepal", "#FF8F021C": "Pohutukawa", "#FF8F3E33": "El Salva", "#FF8F4B0E": "Korma", "#FF8F8176": "Squirrel", "#FF8FD6B4": "Vista Blue", "#FF900020": "Burgundy", "#FF901E1E": "Old Brick", "#FF907874": "Hemp", "#FF907B71": "Almond Frost", "#FF908D39": "Sycamore", "#FF92000A": "Sangria", "#FF924321": "Cumin", "#FF926F5B": "Beaver", "#FF928573": "Stonewall", "#FF928590": "Venus", "#FF9370DB": "Medium Purple", "#FF93CCEA": "Cornflower", "#FF93DFB8": "Algae Green", "#FF944747": "Copper Rust", "#FF948771": "Arrowtown", "#FF950015": "Scarlett", "#FF956387": "Strikemaster", "#FF959396": "Mountain Mist", "#FF960018": "Carmine", "#FF964B00": "Brown", "#FF967059": "Leather", "#FF9678B6": "Purple Mountain", "#FF967BB6": "Lavender Purple", "#FF96A8A1": "Pewter", "#FF96BBAB": "Summer Green", "#FF97605D": "Au Chico", "#FF9771B5": "Wisteria", "#FF97CD2D": "Atlantis", "#FF983D61": "Vin Rouge", "#FF9874D3": "Lilac Bush", "#FF98777B": "Bazaar", "#FF98811B": "Hacienda", "#FF988D77": "Pale Oyster", "#FF98FF98": "Mint Green", "#FF990066": "Fresh Eggplant", "#FF991199": "Violet Eggplant", "#FF991613": "Tamarillo", "#FF991B07": "Totem Pole", "#FF996666": "Copper Rose", "#FF9966CC": "Amethyst", "#FF997A8D": "Mountbatten Pink", "#FF9999CC": "Blue Bell", "#FF9A3820": "Prairie Sand", "#FF9A6E61": "Toast", "#FF9A9577": "Gurkha", "#FF9AB973": "Olivine", "#FF9AC2B8": "Shadow Green", "#FF9B4703": "Oregon", "#FF9B9E8F": "Lemon Grass", "#FF9C3336": "Stiletto", "#FF9D5616": "Hawaiian Tan", "#FF9DACB7": "Gull Gray", "#FF9DC209": "Pistachio", "#FF9DE093": "Granny Smith Apple", "#FF9DE5FF": "Anakiwa", "#FF9E5302": "Chelsea Gem", "#FF9E5B40": "Sepia Skin", "#FF9EA587": "Sage", "#FF9EA91F": "Citron", "#FF9EB1CD": "Rock Blue", "#FF9EDEE0": "Morning Glory", "#FF9F381D": "Cognac", "#FF9F821C": "Reef Gold", "#FF9F9F9C": "Star Dust", "#FF9FA0B1": "Santas Gray", "#FF9FD7D3": "Sinbad", "#FF9FDD8C": "Feijoa", "#FFA02712": "Tabasco", "#FFA1750D": "Buttered Rum", "#FFA1ADB5": "Hit Gray", "#FFA1C50A": "Citrus", "#FFA1DAD7": "Aqua Island", "#FFA1E9DE": "Water Leaf", "#FFA2006D": "Flirt", "#FFA23B6C": "Rouge", "#FFA26645": "Cape Palliser", "#FFA2AAB3": "Gray Chateau", "#FFA2AEAB": "Edward", "#FFA3807B": "Pharlap", "#FFA397B4": "Amethyst Smoke", "#FFA3E3ED": "Blizzard Blue", "#FFA4A49D": "Delta", "#FFA4A6D3": "Wistful", "#FFA4AF6E": "Green Smoke", "#FFA50B5E": "Jazzberry Jam", "#FFA59B91": "Zorba", "#FFA5CB0C": "Bahia", "#FFA62F20": "Roof Terracotta", "#FFA65529": "Paarl", "#FFA68B5B": "Barley Corn", "#FFA69279": "Donkey Brown", "#FFA6A29A": "Dawn", "#FFA72525": "Mexican Red", "#FFA7882C": "Luxor Gold", "#FFA85307": "Rich Gold", "#FFA86515": "Reno Sand", "#FFA86B6B": "Coral Tree", "#FFA8989B": "Dusty Gray", "#FFA899E6": "Dull Lavender", "#FFA8A589": "Tallow", "#FFA8AE9C": "Bud", "#FFA8AF8E": "Locust", "#FFA8BD9F": "Norway", "#FFA8E3BD": "Chinook", "#FFA9A491": "Gray Olive", "#FFA9ACB6": "Aluminium", "#FFA9B2C3": "Cadet Blue", "#FFA9B497": "Schist", "#FFA9BDBF": "Tower Gray", "#FFA9BEF2": "Perano", "#FFA9C6C2": "Opal", "#FFAA375A": "Night Shadz", "#FFAA4203": "Fire", "#FFAA8B5B": "Muesli", "#FFAA8D6F": "Sandal", "#FFAAA5A9": "Shady Lady", "#FFAAA9CD": "Logan", "#FFAAABB7": "Spun Pearl", "#FFAAD6E6": "Regent St Blue", "#FFAAF0D1": "Magic Mint", "#FFAB0563": "Lipstick", "#FFAB3472": "Royal Heath", "#FFAB917A": "Sandrift", "#FFABA0D9": "Cold Purple", "#FFABA196": "Bronco", "#FFAC8A56": "Limed Oak", "#FFAC91CE": "East Side", "#FFAC9E22": "Lemon Ginger", "#FFACA494": "Napa", "#FFACA586": "Hillary", "#FFACA59F": "Cloudy", "#FFACACAC": "Silver Chalice", "#FFACB78E": "Swamp Green", "#FFACCBB1": "Spring Rain", "#FFACDD4D": "Conifer", "#FFACE1AF": "Celadon", "#FFAD781B": "Mandalay", "#FFADBED1": "Casper", "#FFADDFAD": "Moss Green", "#FFADE6C4": "Padua", "#FFADFF2F": "Green Yellow", "#FFAE4560": "Hippie Pink", "#FFAE6020": "Desert", "#FFAE809E": "Bouquet", "#FFAF4035": "Medium Carmine", "#FFAF4D43": "Apple Blossom", "#FFAF593E": "Brown Rust", "#FFAF8751": "Driftwood", "#FFAF8F2C": "Alpine", "#FFAF9F1C": "Lucky", "#FFAFA09E": "Martini", "#FFAFB1B8": "Bombay", "#FFAFBDD9": "Pigeon Post", "#FFB04C6A": "Cadillac", "#FFB05D54": "Matrix", "#FFB05E81": "Tapestry", "#FFB06608": "Mai Tai", "#FFB09A95": "Del Rio", "#FFB0E0E6": "Powder Blue", "#FFB0E313": "Inch Worm", "#FFB10000": "Bright Red", "#FFB14A0B": "Vesuvius", "#FFB1610B": "Pumpkin Skin", "#FFB16D52": "Santa Fe", "#FFB19461": "Teak", "#FFB1E2C1": "Fringy Flower", "#FFB1F4E7": "Ice Cold", "#FFB20931": "Shiraz", "#FFB2A1EA": "Biloba Flower", "#FFB32D29": "Tall Poppy", "#FFB35213": "Fiery Orange", "#FFB38007": "Hot Toddy", "#FFB3AF95": "Taupe Gray", "#FFB3C110": "La Rioja", "#FFB43332": "Well Read", "#FFB44668": "Blush", "#FFB4CFD3": "Jungle Mist", "#FFB57281": "Turkish Rose", "#FFB57EDC": "Lavender", "#FFB5A27F": "Mongoose", "#FFB5B35C": "Olive Green", "#FFB5D2CE": "Jet Stream", "#FFB5ECDF": "Cruise", "#FFB6316C": "Hibiscus", "#FFB69D98": "Thatch", "#FFB6B095": "Heathered Gray", "#FFB6BAA4": "Eagle", "#FFB6D1EA": "Spindle", "#FFB6D3BF": "Gum Leaf", "#FFB7410E": "Rust", "#FFB78E5C": "Muddy Waters", "#FFB7A214": "Sahara", "#FFB7A458": "Husk", "#FFB7B1B1": "Nobel", "#FFB7C3D0": "Heather", "#FFB7F0BE": "Madang", "#FFB81104": "Milano Red", "#FFB87333": "Copper", "#FFB8B56A": "Gimblet", "#FFB8C1B1": "Green Spring", "#FFB8C25D": "Celery", "#FFB8E0F9": "Sail", "#FFB94E48": "Chestnut", "#FFB95140": "Crail", "#FFB98D28": "Marigold", "#FFB9C46A": "Wild Willow", "#FFB9C8AC": "Rainee", "#FFBA0101": "Guardsman Red", "#FFBA450C": "Rock Spray", "#FFBA6F1E": "Bourbon", "#FFBA7F03": "Pirate Gold", "#FFBAB1A2": "Nomad", "#FFBAC7C9": "Submarine", "#FFBAEEF9": "Charlotte", "#FFBB3385": "Medium Red Violet", "#FFBB8983": "Brandy Rose", "#FFBBD009": "Rio Grande", "#FFBBD7C1": "Surf", "#FFBCC9C2": "Powder Ash", "#FFBD5E2E": "Tuscany", "#FFBD978E": "Quicksand", "#FFBDB1A8": "Silk", "#FFBDB2A1": "Malta", "#FFBDB3C7": "Chatelle", "#FFBDBBD7": "Lavender Gray", "#FFBDBDC6": "French Gray", "#FFBDC8B3": "Clay Ash", "#FFBDC9CE": "Loblolly", "#FFBDEDFD": "French Pass", "#FFBEA6C3": "London Hue", "#FFBEB5B7": "Pink Swan", "#FFBEDE0D": "Fuego", "#FFBF5500": "Rose of Sharon", "#FFBFB8B0": "Tide", "#FFBFBED8": "Blue Haze", "#FFBFC1C2": "Silver Sand", "#FFBFC921": "Key Lime Pie", "#FFBFDBE2": "Ziggurat", "#FFBFFF00": "Lime", "#FFC02B18": "Thunderbird", "#FFC04737": "Mojo", "#FFC08081": "Old Rose", "#FFC0C0C0": "Silver", "#FFC0D3B9": "Pale Leaf", "#FFC0D8B6": "Pixie Green", "#FFC1440E": "Tia Maria", "#FFC154C1": "Fuchsia Pink", "#FFC1A004": "Buddha Gold", "#FFC1B7A4": "Bison Hide", "#FFC1BAB0": "Tea", "#FFC1BECD": "Gray Suit", "#FFC1D7B0": "Sprout", "#FFC1F07C": "Sulu", "#FFC26B03": "Indochine", "#FFC2955D": "Twine", "#FFC2BDB6": "Cotton Seed", "#FFC2CAC4": "Pumice", "#FFC2E8E5": "Jagged Ice", "#FFC32148": "Maroon Flush", "#FFC3B091": "Indian Khaki", "#FFC3BFC1": "Pale Slate", "#FFC3C3BD": "Gray Nickel", "#FFC3CDE6": "Periwinkle Gray", "#FFC3D1D1": "Tiara", "#FFC3DDF9": "Tropical Blue", "#FFC41E3A": "Cardinal", "#FFC45655": "Fuzzy Wuzzy Brown", "#FFC45719": "Orange Roughy", "#FFC4C4BC": "Mist Gray", "#FFC4D0B0": "Coriander", "#FFC4F4EB": "Mint Tulip", "#FFC54B8C": "Mulberry", "#FFC59922": "Nugget", "#FFC5994B": "Tussock", "#FFC5DBCA": "Sea Mist", "#FFC5E17A": "Yellow Green", "#FFC62D42": "Brick Red", "#FFC6726B": "Contessa", "#FFC69191": "Oriental Pink", "#FFC6A84B": "Roti", "#FFC6C3B5": "Ash", "#FFC6C8BD": "Kangaroo", "#FFC6E610": "Las Palmas", "#FFC7031E": "Monza", "#FFC71585": "Red Violet", "#FFC7BCA2": "Coral Reef", "#FFC7C1FF": "Melrose", "#FFC7C4BF": "Cloud", "#FFC7C9D5": "Ghost", "#FFC7CD90": "Pine Glade", "#FFC7DDE5": "Botticelli", "#FFC88A65": "Antique Brass", "#FFC8A2C8": "Lilac", "#FFC8A528": "Hokey Pokey", "#FFC8AABF": "Lily", "#FFC8B568": "Laser", "#FFC8E3D7": "Edgewater", "#FFC96323": "Piper", "#FFC99415": "Pizza", "#FFC9A0DC": "Light Wisteria", "#FFC9B29B": "Rodeo Dust", "#FFC9B35B": "Sundance", "#FFC9B93B": "Earls Green", "#FFC9C0BB": "Silver Rust", "#FFC9D9D2": "Conch", "#FFC9FFA2": "Reef", "#FFC9FFE5": "Aero Blue", "#FFCA3435": "Flush Mahogany", "#FFCABB48": "Turmeric", "#FFCADCD4": "Paris White", "#FFCAE00D": "Bitter Lemon", "#FFCAE6DA": "Skeptic", "#FFCB8FA9": "Viola", "#FFCBCAB6": "Foggy Gray", "#FFCBD3B0": "Green Mist", "#FFCBDBD6": "Nebula", "#FFCC3333": "Persian Red", "#FFCC5501": "Burnt Orange", "#FFCC7722": "Ochre", "#FFCC8899": "Puce", "#FFCCCAA8": "Thistle Green", "#FFCCCCFF": "Periwinkle", "#FFCCFF00": "Electric Lime", "#FFCD5700": "Tenn", "#FFCD5C5C": "Chestnut Rose", "#FFCD8429": "Brandy Punch", "#FFCDF4FF": "Onahau", "#FFCEB98F": "Sorrell Brown", "#FFCEBABA": "Cold Turkey", "#FFCEC291": "Yuma", "#FFCEC7A7": "Chino", "#FFCFA39D": "Eunry", "#FFCFB53B": "Old Gold", "#FFCFDCCF": "Tasman", "#FFCFE5D2": "Surf Crest", "#FFCFF9F3": "Humming Bird", "#FFCFFAF4": "Scandal", "#FFD05F04": "Red Stage", "#FFD06DA1": "Hopbush", "#FFD07D12": "Meteor", "#FFD0BEF8": "Perfume", "#FFD0C0E5": "Prelude", "#FFD0F0C0": "Tea Green", "#FFD18F1B": "Geebung", "#FFD1BEA8": "Vanilla", "#FFD1C6B4": "Soft Amber", "#FFD1D2CA": "Celeste", "#FFD1D2DD": "Mischka", "#FFD1E231": "Pear", "#FFD2691E": "Hot Cinnamon", "#FFD27D46": "Raw Sienna", "#FFD29EAA": "Careys Pink", "#FFD2B48C": "Tan", "#FFD2DA97": "Deco", "#FFD2F6DE": "Blue Romance", "#FFD2F8B0": "Gossip", "#FFD3CBBA": "Sisal", "#FFD3CDC5": "Swirl", "#FFD47494": "Charm", "#FFD4B6AF": "Clam Shell", "#FFD4BF8D": "Straw", "#FFD4C4A8": "Akaroa", "#FFD4CD16": "Bird Flower", "#FFD4D7D9": "Iron", "#FFD4DFE2": "Geyser", "#FFD4E2FC": "Hawkes Blue", "#FFD54600": "Grenadier", "#FFD591A4": "Can Can", "#FFD59A6F": "Whiskey", "#FFD5D195": "Winter Hazel", "#FFD5F6E3": "Granny Apple", "#FFD69188": "My Pink", "#FFD6C562": "Tacha", "#FFD6CEF6": "Moon Raker", "#FFD6D6D1": "Quill Gray", "#FFD6FFDB": "Snowy Mint", "#FFD7837F": "New York Pink", "#FFD7C498": "Pavlova", "#FFD7D0FF": "Fog", "#FFD84437": "Valencia", "#FFD87C63": "Japonica", "#FFD8BFD8": "Thistle", "#FFD8C2D5": "Maverick", "#FFD8FCFA": "Foam", "#FFD94972": "Cabaret", "#FFD99376": "Burning Sand", "#FFD9B99B": "Cameo", "#FFD9D6CF": "Timberwolf", "#FFD9DCC1": "Tana", "#FFD9E4F5": "Link Water", "#FFD9F7FF": "Mabel", "#FFDA3287": "Cerise", "#FFDA5B38": "Flame Pea", "#FFDA6304": "Bamboo", "#FFDA6A41": "Red Damask", "#FFDA70D6": "Orchid", "#FFDA8A67": "Copperfield", "#FFDAA520": "Golden Grass", "#FFDAECD6": "Zanah", "#FFDAF4F0": "Iceberg", "#FFDAFAFF": "Oyster Bay", "#FFDB5079": "Cranberry", "#FFDB9690": "Petite Orchid", "#FFDB995E": "Di Serria", "#FFDBDBDB": "Alto", "#FFDBFFF8": "Frosted Mint", "#FFDC143C": "Crimson", "#FFDC4333": "Punch", "#FFDCB20C": "Galliano", "#FFDCB4BC": "Blossom", "#FFDCD747": "Wattle", "#FFDCD9D2": "Westar", "#FFDCDDCC": "Moon Mist", "#FFDCEDB4": "Caper", "#FFDCF0EA": "Swans Down", "#FFDDD6D5": "Swiss Coffee", "#FFDDF9F1": "White Ice", "#FFDE3163": "Cerise Red", "#FFDE6360": "Roman", "#FFDEA681": "Tumbleweed", "#FFDEBA13": "Gold Tips", "#FFDEC196": "Brandy", "#FFDECBC6": "Wafer", "#FFDED4A4": "Sapling", "#FFDED717": "Barberry", "#FFDEE5C0": "Beryl Green", "#FFDEF5FF": "Pattens Blue", "#FFDF73FF": "Heliotrope", "#FFDFBE6F": "Apache", "#FFDFCD6F": "Chenin", "#FFDFCFDB": "Lola", "#FFDFECDA": "Willow Brook", "#FFDFFF00": "Chartreuse Yellow", "#FFE0B0FF": "Mauve", "#FFE0B646": "Anzac", "#FFE0B974": "Harvest Gold", "#FFE0C095": "Calico", "#FFE0FFFF": "Baby Blue", "#FFE16865": "Sunglo", "#FFE1BC64": "Equator", "#FFE1C0C8": "Pink Flare", "#FFE1E6D6": "Periglacial Blue", "#FFE1EAD4": "Kidnapper", "#FFE1F6E8": "Tara", "#FFE25465": "Mandy", "#FFE2725B": "Terracotta", "#FFE28913": "Golden Bell", "#FFE292C0": "Shocking", "#FFE29418": "Dixie", "#FFE29CD2": "Light Orchid", "#FFE2D8ED": "Snuff", "#FFE2EBED": "Mystic", "#FFE2F3EC": "Apple Green", "#FFE30B5C": "Razzmatazz", "#FFE32636": "Alizarin Crimson", "#FFE34234": "Cinnabar", "#FFE3BEBE": "Cavern Pink", "#FFE3F5E1": "Peppermint", "#FFE3F988": "Mindaro", "#FFE47698": "Deep Blush", "#FFE49B0F": "Gamboge", "#FFE4C2D5": "Melanie", "#FFE4CFDE": "Twilight", "#FFE4D1C0": "Bone", "#FFE4D422": "Sunflower", "#FFE4D5B7": "Grain Brown", "#FFE4D69B": "Zombie", "#FFE4F6E7": "Frostee", "#FFE4FFD1": "Snow Flurry", "#FFE52B50": "Amaranth", "#FFE5841B": "Zest", "#FFE5CCC9": "Dust Storm", "#FFE5D7BD": "Stark White", "#FFE5D8AF": "Hampton", "#FFE5E0E1": "Bon Jour", "#FFE5E5E5": "Mercury", "#FFE5F9F6": "Polar", "#FFE64E03": "Trinidad", "#FFE6BE8A": "Gold Sand", "#FFE6BEA5": "Cashmere", "#FFE6D7B9": "Double Spanish White", "#FFE6E4D4": "Satin Linen", "#FFE6F2EA": "Harp", "#FFE6F8F3": "Off Green", "#FFE6FFE9": "Hint of Green", "#FFE6FFFF": "Tranquil", "#FFE77200": "Mango Tango", "#FFE7730A": "Christine", "#FFE79F8C": "Tony's Pink", "#FFE79FC4": "Kobi", "#FFE7BCB4": "Rose Fog", "#FFE7BF05": "Corn", "#FFE7CD8C": "Putty", "#FFE7ECE6": "Gray Nurse", "#FFE7F8FF": "Lily White", "#FFE7FEFF": "Bubbles", "#FFE89928": "Fire Bush", "#FFE8B9B3": "Shilo", "#FFE8E0D5": "Pearl Bush", "#FFE8EBE0": "Green White", "#FFE8F1D4": "Chrome White", "#FFE8F2EB": "Gin", "#FFE8F5F2": "Aqua Squeeze", "#FFE96E00": "Clementine", "#FFE97451": "Burnt Sienna", "#FFE97C07": "Tahiti Gold", "#FFE9CECD": "Oyster Pink", "#FFE9D75A": "Confetti", "#FFE9E3E3": "Ebb", "#FFE9F8ED": "Ottoman", "#FFE9FFFD": "Clear Day", "#FFEA88A8": "Carissma", "#FFEAAE69": "Porsche", "#FFEAB33B": "Tulip Tree", "#FFEAC674": "Rob Roy", "#FFEADAB8": "Raffia", "#FFEAE8D4": "White Rock", "#FFEAF6EE": "Panache", "#FFEAF6FF": "Solitude", "#FFEAF9F5": "Aqua Spring", "#FFEAFFFE": "Dew", "#FFEB9373": "Apricot", "#FFEBC2AF": "Zinnwaldite", "#FFECA927": "Fuel Yellow", "#FFECC54E": "Ronchi", "#FFECC7EE": "French Lilac", "#FFECCDB9": "Just Right", "#FFECE090": "Wild Rice", "#FFECEBBD": "Fall Green", "#FFECEBCE": "Aths Special", "#FFECF245": "Starship", "#FFED0A3F": "Red Ribbon", "#FFED7A1C": "Tango", "#FFED9121": "Carrot Orange", "#FFED989E": "Sea Pink", "#FFEDB381": "Tacao", "#FFEDC9AF": "Desert Sand", "#FFEDCDAB": "Pancho", "#FFEDDCB1": "Chamois", "#FFEDEA99": "Primrose", "#FFEDF5DD": "Frost", "#FFEDF5F5": "Aqua Haze", "#FFEDF6FF": "Zumthor", "#FFEDF9F1": "Narvik", "#FFEDFC84": "Honeysuckle", "#FFEE82EE": "Lavender Magenta", "#FFEEC1BE": "Beauty Bush", "#FFEED794": "Chalky", "#FFEED9C4": "Almond", "#FFEEDC82": "Flax", "#FFEEDEDA": "Bizarre", "#FFEEE3AD": "Double Colonial White", "#FFEEEEE8": "Cararra", "#FFEEEF78": "Manz", "#FFEEF0C8": "Tahuna Sands", "#FFEEF0F3": "Athens Gray", "#FFEEF3C3": "Tusk", "#FFEEF4DE": "Loafer", "#FFEEF6F7": "Catskill White", "#FFEEFDFF": "Twilight Blue", "#FFEEFF9A": "Jonquil", "#FFEEFFE2": "Rice Flower", "#FFEF863F": "Jaffa", "#FFEFEFEF": "Gallery", "#FFEFF2F3": "Porcelain", "#FFF091A9": "Mauvelous", "#FFF0D52D": "Golden Dream", "#FFF0DB7D": "Golden Sand", "#FFF0DC82": "Buff", "#FFF0E2EC": "Prim", "#FFF0E68C": "Khaki", "#FFF0EEFD": "Selago", "#FFF0EEFF": "Titan White", "#FFF0F8FF": "Alice Blue", "#FFF0FCEA": "Feta", "#FFF18200": "Gold Drop", "#FFF19BAB": "Wewak", "#FFF1E788": "Sahara Sand", "#FFF1E9D2": "Parchment", "#FFF1E9FF": "Blue Chalk", "#FFF1EEC1": "Mint Julep", "#FFF1F1F1": "Seashell", "#FFF1F7F2": "Saltpan", "#FFF1FFAD": "Tidal", "#FFF1FFC8": "Chiffon", "#FFF2552A": "Flamingo", "#FFF28500": "Tangerine", "#FFF2C3B2": "Mandy's Pink", "#FFF2F2F2": "Concrete", "#FFF2FAFA": "Black Squeeze", "#FFF34723": "Pomegranate", "#FFF3AD16": "Buttercup", "#FFF3D69D": "New Orleans", "#FFF3D9DF": "Vanilla Ice", "#FFF3E7BB": "Sidecar", "#FFF3E9E5": "Dawn Pink", "#FFF3EDCF": "Wheatfield", "#FFF3FB62": "Canary", "#FFF3FBD4": "Orinoco", "#FFF3FFD8": "Carla", "#FFF400A1": "Hollywood Cerise", "#FFF4A460": "Sandy brown", "#FFF4C430": "Saffron", "#FFF4D81C": "Ripe Lemon", "#FFF4EBD3": "Janna", "#FFF4F2EE": "Pampas", "#FFF4F4F4": "Wild Sand", "#FFF4F8FF": "Zircon", "#FFF57584": "Froly", "#FFF5C85C": "Cream Can", "#FFF5C999": "Manhattan", "#FFF5D5A0": "Maize", "#FFF5DEB3": "Wheat", "#FFF5E7A2": "Sandwisp", "#FFF5E7E2": "Pot Pourri", "#FFF5E9D3": "Albescent White", "#FFF5EDEF": "Soft Peach", "#FFF5F3E5": "Ecru White", "#FFF5F5DC": "Beige", "#FFF5FB3D": "Golden Fizz", "#FFF5FFBE": "Australian Mint", "#FFF64A8A": "French Rose", "#FFF653A6": "Brilliant Rose", "#FFF6A4C9": "Illusion", "#FFF6F0E6": "Merino", "#FFF6F7F7": "Black Haze", "#FFF6FFDC": "Spring Sun", "#FFF7468A": "Violet Red", "#FFF77703": "Chilean Fire", "#FFF77FBE": "Persian Pink", "#FFF7B668": "Rajah", "#FFF7C8DA": "Azalea", "#FFF7DBE6": "We Peep", "#FFF7F2E1": "Quarter Spanish White", "#FFF7F5FA": "Whisper", "#FFF7FAF7": "Snow Drift", "#FFF8B853": "Casablanca", "#FFF8C3DF": "Chantilly", "#FFF8D9E9": "Cherub", "#FFF8DB9D": "Marzipan", "#FFF8DD5C": "Energy Yellow", "#FFF8E4BF": "Givry", "#FFF8F0E8": "White Linen", "#FFF8F4FF": "Magnolia", "#FFF8F6F1": "Spring Wood", "#FFF8F7DC": "Coconut Cream", "#FFF8F7FC": "White Lilac", "#FFF8F8F7": "Desert Storm", "#FFF8F99C": "Texas", "#FFF8FACD": "Corn Field", "#FFF8FDD3": "Mimosa", "#FFF95A61": "Carnation", "#FFF9BF58": "Saffron Mango", "#FFF9E0ED": "Carousel Pink", "#FFF9E4BC": "Dairy Cream", "#FFF9E663": "Portica", "#FFF9EAF3": "Amour", "#FFF9F8E4": "Rum Swizzle", "#FFF9FF8B": "Dolly", "#FFF9FFF6": "Sugar Cane", "#FFFA7814": "Ecstasy", "#FFFA9D5A": "Tan Hide", "#FFFAD3A2": "Corvette", "#FFFADFAD": "Peach Yellow", "#FFFAE600": "Turbo", "#FFFAEAB9": "Astra", "#FFFAECCC": "Champagne", "#FFFAF0E6": "Linen", "#FFFAF3F0": "Fantasy", "#FFFAF7D6": "Citrine White", "#FFFAFAFA": "Alabaster", "#FFFAFDE4": "Hint of Yellow", "#FFFAFFA4": "Milan", "#FFFB607F": "Brink Pink", "#FFFB8989": "Geraldine", "#FFFBA0E3": "Lavender Rose", "#FFFBA129": "Sea Buckthorn", "#FFFBAC13": "Sun", "#FFFBAED2": "Lavender Pink", "#FFFBB2A3": "Rose Bud", "#FFFBBEDA": "Cupid", "#FFFBCCE7": "Classic Rose", "#FFFBCEB1": "Apricot Peach", "#FFFBE7B2": "Banana Mania", "#FFFBE870": "Marigold Yellow", "#FFFBE96C": "Festival", "#FFFBEA8C": "Sweet Corn", "#FFFBEC5D": "Candy Corn", "#FFFBF9F9": "Hint of Red", "#FFFBFFBA": "Shalimar", "#FFFC0FC0": "Shocking Pink", "#FFFC80A5": "Tickle Me Pink", "#FFFC9C1D": "Tree Poppy", "#FFFCC01E": "Lightning Yellow", "#FFFCD667": "Goldenrod", "#FFFCD917": "Candlelight", "#FFFCDA98": "Cherokee", "#FFFCF4D0": "Double Pearl Lusta", "#FFFCF4DC": "Pearl Lusta", "#FFFCF8F7": "Vista White", "#FFFCFBF3": "Bianca", "#FFFCFEDA": "Moon Glow", "#FFFCFFE7": "China Ivory", "#FFFCFFF9": "Ceramic", "#FFFD0E35": "Torch Red", "#FFFD5B78": "Wild Watermelon", "#FFFD7B33": "Crusta", "#FFFD7C07": "Sorbus", "#FFFD9FA2": "Sweet Pink", "#FFFDD5B1": "Light Apricot", "#FFFDD7E4": "Pig Pink", "#FFFDE1DC": "Cinderella", "#FFFDE295": "Golden Glow", "#FFFDE910": "Lemon", "#FFFDF5E6": "Old Lace", "#FFFDF6D3": "Half Colonial White", "#FFFDF7AD": "Drover", "#FFFDFEB8": "Pale Prim", "#FFFDFFD5": "Cumulus", "#FFFE28A2": "Persian Rose", "#FFFE4C40": "Sunset Orange", "#FFFE6F5E": "Bittersweet", "#FFFE9D04": "California", "#FFFEA904": "Yellow Sea", "#FFFEBAAD": "Melon", "#FFFED33C": "Bright Sun", "#FFFED85D": "Dandelion", "#FFFEDB8D": "Salomie", "#FFFEE5AC": "Cape Honey", "#FFFEEBF3": "Remy", "#FFFEEFCE": "Oasis", "#FFFEF0EC": "Bridesmaid", "#FFFEF2C7": "Beeswax", "#FFFEF3D8": "Bleach White", "#FFFEF4CC": "Pipi", "#FFFEF4DB": "Half Spanish White", "#FFFEF4F8": "Wisp Pink", "#FFFEF5F1": "Provincial Pink", "#FFFEF7DE": "Half Dutch White", "#FFFEF8E2": "Solitaire", "#FFFEF8FF": "White Pointer", "#FFFEF9E3": "Off Yellow", "#FFFEFCED": "Orange White", "#FFFF0000": "Red", "#FFFF007F": "Rose", "#FFFF00CC": "Purple Pizzazz", "#FFFF00FF": "Magenta / Fuchsia", "#FFFF2400": "Scarlet", "#FFFF3399": "Wild Strawberry", "#FFFF33CC": "Razzle Dazzle Rose", "#FFFF355E": "Radical Red", "#FFFF3F34": "Red Orange", "#FFFF4040": "Coral Red", "#FFFF4D00": "Vermilion", "#FFFF4F00": "International Orange", "#FFFF6037": "Outrageous Orange", "#FFFF6600": "Blaze Orange", "#FFFF66FF": "Pink Flamingo", "#FFFF681F": "Orange", "#FFFF69B4": "Hot Pink", "#FFFF6B53": "Persimmon", "#FFFF6FFF": "Blush Pink", "#FFFF7034": "Burning Orange", "#FFFF7518": "Pumpkin", "#FFFF7D07": "Flamenco", "#FFFF7F00": "Flush Orange", "#FFFF7F50": "Coral", "#FFFF8C69": "Salmon", "#FFFF9000": "Pizazz", "#FFFF910F": "West Side", "#FFFF91A4": "Pink Salmon", "#FFFF9933": "Neon Carrot", "#FFFF9966": "Atomic Tangerine", "#FFFF9980": "Vivid Tangerine", "#FFFF9E2C": "Sunshade", "#FFFFA000": "Orange Peel", "#FFFFA194": "Mona Lisa", "#FFFFA500": "Web Orange", "#FFFFA6C9": "Carnation Pink", "#FFFFAB81": "Hit Pink", "#FFFFAE42": "Yellow Orange", "#FFFFB0AC": "Cornflower Lilac", "#FFFFB1B3": "Sundown", "#FFFFB31F": "My Sin", "#FFFFB555": "Texas Rose", "#FFFFB7D5": "Cotton Candy", "#FFFFB97B": "Macaroni and Cheese", "#FFFFBA00": "Selective Yellow", "#FFFFBD5F": "Koromiko", "#FFFFBF00": "Amber", "#FFFFC0A8": "Wax Flower", "#FFFFC0CB": "Pink", "#FFFFC3C0": "Your Pink", "#FFFFC901": "Supernova", "#FFFFCBA4": "Flesh", "#FFFFCC33": "Sunglow", "#FFFFCC5C": "Golden Tainoi", "#FFFFCC99": "Peach Orange", "#FFFFCD8C": "Chardonnay", "#FFFFD1DC": "Pastel Pink", "#FFFFD2B7": "Romantic", "#FFFFD38C": "Grandis", "#FFFFD700": "Gold", "#FFFFD801": "School bus Yellow", "#FFFFD8D9": "Cosmos", "#FFFFDB58": "Mustard", "#FFFFDCD6": "Peach Schnapps", "#FFFFDDAF": "Caramel", "#FFFFDDCD": "Tuft Bush", "#FFFFDDCF": "Watusi", "#FFFFDDF4": "Pink Lace", "#FFFFDEAD": "Navajo White", "#FFFFDEB3": "Frangipani", "#FFFFE1DF": "Pippin", "#FFFFE1F2": "Pale Rose", "#FFFFE2C5": "Negroni", "#FFFFE5A0": "Cream Brulee", "#FFFFE5B4": "Peach", "#FFFFE6C7": "Tequila", "#FFFFE772": "Kournikova", "#FFFFEAC8": "Sandy Beach", "#FFFFEAD4": "Karry", "#FFFFEC13": "Broom", "#FFFFEDBC": "Colonial White", "#FFFFEED8": "Derby", "#FFFFEFA1": "Vis Vis", "#FFFFEFC1": "Egg White", "#FFFFEFD5": "Papaya Whip", "#FFFFEFEC": "Fair Pink", "#FFFFF0DB": "Peach Cream", "#FFFFF0F5": "Lavender blush", "#FFFFF14F": "Gorse", "#FFFFF1B5": "Buttermilk", "#FFFFF1D8": "Pink Lady", "#FFFFF1EE": "Forget Me Not", "#FFFFF1F9": "Tutu", "#FFFFF39D": "Picasso", "#FFFFF3F1": "Chardon", "#FFFFF46E": "Paris Daisy", "#FFFFF4CE": "Barley White", "#FFFFF4DD": "Egg Sour", "#FFFFF4E0": "Sazerac", "#FFFFF4E8": "Serenade", "#FFFFF4F3": "Chablis", "#FFFFF5EE": "Seashell Peach", "#FFFFF5F3": "Sauvignon", "#FFFFF6D4": "Milk Punch", "#FFFFF6DF": "Varden", "#FFFFF6F5": "Rose White", "#FFFFF8D1": "Baja White", "#FFFFF9E2": "Gin Fizz", "#FFFFF9E6": "Early Dawn", "#FFFFFACD": "Lemon Chiffon", "#FFFFFAF4": "Bridal Heath", "#FFFFFBDC": "Scotch Mist", "#FFFFFBF9": "Soapstone", "#FFFFFC99": "Witch Haze", "#FFFFFCEA": "Buttery White", "#FFFFFCEE": "Island Spice", "#FFFFFDD0": "Cream", "#FFFFFDE6": "Chilean Heath", "#FFFFFDE8": "Travertine", "#FFFFFDF3": "Orchid White", "#FFFFFDF4": "Quarter Pearl Lusta", "#FFFFFEE1": "Half and Half", "#FFFFFEEC": "Apricot White", "#FFFFFEF0": "Rice Cake", "#FFFFFEF6": "Black White", "#FFFFFEFD": "Romance", "#FFFFFF00": "Yellow", "#FFFFFF66": "Laser Lemon", "#FFFFFF99": "Pale Canary", "#FFFFFFB4": "Portafino", "#FFFFFFF0": "Ivory", "#FFFFFFFF": "White", "#FFFAEBD7": "Antique White", "#FFFFE4C4": "Bisque", "#FFFFEBCD": "Blanched Almond", "#FF8A2BE2": "Blue Violet Variant", "#FFDEB887": "Burly Wood", "#FF5F9EA0": "Cadet Blue Variant", "#FFFFF8DC": "Cornsilk", "#FF00008B": "Dark Blue Variant", "#FF008B8B": "Dark Cyan", "#FFB8860B": "Dark Goldenrod", "#FFA9A9A9": "Dark Gray", "#FF006400": "Dark Green", "#FFBDB76B": "Dark Khaki", "#FF8B008B": "Dark Magenta", "#FF556B2F": "Dark Olive Green", "#FFFF8C00": "Dark Orange", "#FF9932CC": "Dark Orchid", "#FF8B0000": "Dark Red", "#FFE9967A": "Dark Salmon", "#FF8FBC8F": "Dark Sea Green", "#FF483D8B": "Dark Slate Blue", "#FF2F4F4F": "Dark Slate Gray", "#FF00CED1": "Dark Turquoise", "#FF9400D3": "Dark Violet", "#FFFF1493": "Deep Pink", "#FF00BFFF": "Deep Sky Blue", "#FF696969": "Dim Gray", "#FFB22222": "Fire Brick", "#FFFFFAF0": "Floral White", "#FFDCDCDC": "Gainsboro", "#FFF8F8FF": "Ghost White", "#FFF0FFF0": "Honeydew", "#FF7CFC00": "Lawn Green", "#FFADD8E6": "Light Blue", "#FFF08080": "Light Coral", "#FFFAFAD2": "Light Goldenrod Yellow", "#FFD3D3D3": "Light Gray", "#FF90EE90": "Light Green", "#FFFFB6C1": "Light Pink", "#FFFFA07A": "Light Salmon", "#FF20B2AA": "Light Sea Green", "#FF87CEFA": "Light Sky Blue", "#FF778899": "Light Slate Gray", "#FFB0C4DE": "Light Steel Blue", "#FFFFFFE0": "Light Yellow", "#FF32CD32": "Lime Green", "#FF66CDAA": "Medium Aquamarine", "#FF0000CD": "Medium Blue", "#FFBA55D3": "Medium Orchid", "#FF3CB371": "Medium Sea Green", "#FF7B68EE": "Medium Slate Blue", "#FF00FA9A": "Medium Spring Green", "#FF48D1CC": "Medium Turquoise", "#FF191970": "Midnight Blue Variant", "#FFF5FFFA": "Mint Cream", "#FFFFE4E1": "Misty Rose", "#FFFFE4B5": "Navajo White Variant", "#FFFF4500": "Orange Red", "#FFEEE8AA": "Pale Goldenrod", "#FF98FB98": "Pale Green", "#FFAFEEEE": "Pale Turquoise", "#FFDB7093": "Pale Violet Red", "#FFFFDAB9": "Peach Puff", "#FF663399": "Rebecca Purple", "#FFBC8F8F": "Rosy Brown", "#FFFFFAFA": "Snow", "#FFF5F5F5": "White Smoke", "#FF9ACD32": "Yellow Green Variant", "#FF004225": "British Racing Green", "#FFFF2800": "Ferrari Red", "#FF00A550": "GO Green", "#FF2E5090": "Yale Blue", "#FFE0115F": "Raspberry", "#FF8000FF": "Electric Purple", "#FFFF8000": "Electric Orange", "#FF404040": "Dark Grey", "#FFFF3366": "Bright Pink", "#FF33FF66": "Bright Green Variant", "#FF3366FF": "Bright Blue", "#FFFFCC00": "Golden Yellow", "#FFCC6600": "Orange Brown", "#FF6600CC": "Purple Blue", "#FF00CC66": "Mint Green Variant", "#FF9900FF": "Violet Purple", "#FFFF9900": "Orange Yellow", "#FF00FF99": "Lime Variant", "#FF99FF00": "Yellow Green Tint", "#FF99FFFF": "Light Cyan", "#FF99FF99": "Light Green Variant", "#FFFF9999": "Light Pink Variant", "#FF9999FF": "Light Blue Variant", "#FF666666": "Medium Gray", "#FF111111": "Very Dark Gray", "#FF222222": "Charcoal", "#FF555555": "Dim Grey", "#FFAAAAAA": "Silver Gray", "#FFBBBBBB": "Light Silver", "#FFEEEEEE": "Almost White", "#FFFF6347": "Tomato", "#FF5D8AA8": "Air Force Blue", "#FFFF7E00": "Amber Sae Ece", "#FFFF033E": "American Rose", "#FFF2F3F4": "Anti Flash White", "#FF915C83": "Antique Fuchsia", "#FF7FFFD0": "Aquamarine1", "#FF4B5320": "Army Green", "#FF3B444B": "Arsenic", "#FFE9D66B": "Arylide Yellow", "#FFB2BEB5": "Ash Grey", "#FF6D351A": "Auburn", "#FFFDEE00": "Aureolin", "#FF6E7F80": "Aurometalsaurus", "#FFFF2052": "Awesome", "#FFA1CAF1": "Baby Blue Eyes", "#FFF4C2C2": "Baby Pink", "#FF21ABCD": "Ball Blue", "#FF848482": "Battleship Grey", "#FFBCD4E6": "Beau Blue", "#FF318CE7": "Bleu De France", "#FFFAF0BE": "Blond", "#FF6699CC": "Blue Gray", "#FF00DDDD": "Blue Green", "#FF333399": "Blue Pigment", "#FF0247FE": "Blue Ryb", "#FF79443B": "Bole", "#FFCC0000": "Boston University Red", "#FF0070FF": "Brandeis Blue", "#FF1DACD6": "Bright Cerulean", "#FFBF94E4": "Bright Lavender", "#FFD19FE8": "Bright Ube", "#FFF4BBFF": "Brilliant Lavender", "#FFFFC1CC": "Bubble Gum", "#FFBD33A4": "Byzantine", "#FF702963": "Byzantium", "#FF91A3B0": "Cadet Grey", "#FF006B3C": "Cadmium Green", "#FFED872D": "Cadmium Orange", "#FFE30022": "Cadmium Red", "#FFFFF600": "Cadmium Yellow", "#FF1E4D2B": "Cal Poly Pomona Green", "#FFA3C1AD": "Cambridge Blue", "#FFFFEF00": "Canary Yellow", "#FFFF0800": "Candy Apple Red", "#FFE4717A": "Candy Pink", "#FF592720": "Caput Mortuum", "#FFEB4C42": "Carmine Pink", "#FFFF0038": "Carmine Red", "#FFB31B1B": "Carnelian", "#FF99BADD": "Carolina Blue", "#FF92A1CF": "Ceil", "#FF4997D0": "Celestial Blue", "#FFEC3B83": "Cerise Pink", "#FFA0785A": "Chamoisee", "#FFFFB7C5": "Cherry Blossom Pink", "#FFFFA700": "Chrome Yellow", "#FF98817B": "Cinereous", "#FF9BDDFF": "Columbia Blue", "#FF002E63": "Cool Black", "#FF8C92AC": "Cool Grey", "#FFFF3800": "Coquelicot", "#FFF88379": "Coral Pink", "#FF893F45": "Cordovan", "#FFFFF8E7": "Cosmic Latte", "#FFBE0032": "Crimson Glory", "#FF00B7EB": "Cyan Process", "#FFFFFF31": "Daffodil", "#FF654321": "Dark Brown", "#FF5D3954": "Dark Byzantium", "#FFA40000": "Dark Candy Apple Red", "#FF08457E": "Dark Cerulean", "#FFC2B280": "Dark Champagne", "#FF986960": "Dark Chestnut", "#FFCD5B45": "Dark Coral", "#FF536878": "Dark Electric Blue", "#FF013220": "Dark Green1", "#FF1A2421": "Dark Jungle Green", "#FF734F96": "Dark Lavender", "#FF779ECB": "Dark Pastel Blue", "#FF03C03C": "Dark Pastel Green", "#FF966FD6": "Dark Pastel Purple", "#FFC23B22": "Dark Pastel Red", "#FFE75480": "Dark Pink", "#FF872657": "Dark Raspberry", "#FF560319": "Dark Scarlet", "#FF3C1414": "Dark Sienna", "#FF177245": "Dark Spring Green", "#FFFFA812": "Dark Tangerine", "#FFCC4E5C": "Dark Terra Cotta", "#FF00693E": "Dartmouth Green", "#FFD70A53": "Debian Red", "#FFA9203E": "Deep Carmine", "#FFEF3038": "Deep Carmine Pink", "#FFE9692C": "Deep Carrot Orange", "#FFFAD6A5": "Deep Champagne", "#FF004B49": "Deep Jungle Green", "#FF9955BB": "Deep Lilac", "#FFCC00CC": "Deep Magenta", "#FFD71868": "Dogwood Rose", "#FF85BB65": "Dollar Bill", "#FF00009C": "Duke Blue", "#FFE1A95F": "Earth Yellow", "#FFF0EAD6": "Eggshell", "#FF1034A6": "Egyptian Blue", "#FF7DF9FF": "Electric Blue", "#FFFF003F": "Electric Crimson", "#FF6F00FF": "Electric Indigo", "#FF3F00FF": "Electric Ultramarine", "#FF96C8A2": "Eton Blue", "#FFC19A6B": "Fallow", "#FFB53389": "Fandango", "#FF4D5D53": "Feldgrau", "#FF6C541E": "Field Drab", "#FFCE2029": "Fire Engine Red", "#FFFC8EAC": "Flamingo Pink", "#FFF7E98E": "Flavescent", "#FFFF004F": "Folly", "#FF014421": "Forest Green Traditional", "#FFA67B5B": "French Beige", "#FF0072BB": "French Blue", "#FFE48400": "Fulvous", "#FF6082B6": "Glaucous", "#FF996515": "Golden Brown", "#FFFCC200": "Golden Poppy", "#FFD4AF37": "Gold Metallic", "#FF66B032": "Green Ryb", "#FFA99A86": "Grullo", "#FF663854": "Halaya Ube", "#FF446CCF": "Han Blue", "#FF5218FA": "Han Purple", "#FFC90016": "Harvard Crimson", "#FF007000": "Hooker SGreen", "#FFFF1DCE": "Hot Magenta", "#FFFCF75E": "Icterine", "#FFB2EC5D": "Inchworm", "#FF138808": "India Green", "#FFE3A857": "Indian Yellow", "#FF00416A": "Indigo Dye", "#FFF4F0EC": "Isabelline", "#FF009000": "Islamic Green", "#FFD73B3E": "Jasper", "#FFBDDA57": "June Bud", "#FF4CBB17": "Kelly Green", "#FFD6CADD": "Languid Lavender", "#FF26619C": "Lapis Lazuli", "#FF087830": "La Salle Green", "#FFCF1020": "Lava", "#FF9457EB": "Lavender Indigo", "#FFB5651D": "Light Brown", "#FFE66771": "Light Carmine Pink", "#FFF984EF": "Light Fuchsia Pink", "#FFDCD0FF": "Light Mauve", "#FFB19CD9": "Light Pastel Purple", "#FFB38B6D": "Light Taupe", "#FFE68FAC": "Light Thulian Pink", "#FFFFFFED": "Light Yellow1", "#FF195905": "Lincoln Green", "#FF534B4F": "Liver", "#FFFFBD88": "Macaroni And Cheese", "#FFCA1F7B": "Magenta Dye", "#FFFF0090": "Magenta Process", "#FF6050DC": "Majorelle Blue", "#FFB03060": "Maroon X11", "#FF915F6D": "Mauve Taupe", "#FF73C2FB": "Maya Blue", "#FFE5B73B": "Meat Brown", "#FF66DDAA": "Medium Aquamarine1", "#FFE2062C": "Medium Candy Apple Red", "#FFF3E5AB": "Medium Champagne", "#FF035096": "Medium Electric Blue", "#FF1C352D": "Medium Jungle Green", "#FF0067A5": "Medium Persian Blue", "#FFC9DC87": "Medium Spring Bud", "#FF674C47": "Medium Taupe", "#FF004953": "Midnight Green Eagle Green", "#FFFFC40C": "Mikado Yellow", "#FF967117": "Mode Beige", "#FF73A9C2": "Moonstone Blue", "#FFAE0C00": "Mordant Red19", "#FF18453B": "Msu Green", "#FF21421E": "Myrtle", "#FFF6ADC6": "Nadeshiko Pink", "#FF2A8000": "Napier Green", "#FFFADA5E": "Naples Yellow", "#FFFE59C2": "Neon Fuchsia", "#FF39FF14": "Neon Green", "#FFA4DDED": "Non Photo Blue", "#FFCC7422": "Ocean Boat Blue", "#FF673147": "Old Mauve", "#FF0F0F0F": "Onyx", "#FFB784A7": "Opera Mauve", "#FFFB9902": "Orange Ryb", "#FF990000": "Ou Crimson Red", "#FF00421B": "Pakistan Green", "#FF273BE2": "Palatinate Blue", "#FF682860": "Palatinate Purple", "#FF987654": "Pale Brown", "#FF9BC4E2": "Pale Cerulean", "#FFDDADAF": "Pale Chestnut", "#FFABCDEF": "Pale Cornflower Blue", "#FFF984E5": "Pale Magenta", "#FFFADADD": "Pale Pink", "#FF96DED1": "Pale Robin Egg Blue", "#FFBC987E": "Pale Taupe", "#FF78184A": "Pansy Purple", "#FFAEC6CF": "Pastel Blue", "#FF836953": "Pastel Brown", "#FFCFCFC4": "Pastel Gray", "#FFF49AC2": "Pastel Magenta", "#FFFFB347": "Pastel Orange", "#FFB39EB5": "Pastel Purple", "#FFFF6961": "Pastel Red", "#FFCB99C9": "Pastel Violet", "#FFFDFD96": "Pastel Yellow", "#FF40404F": "Payne SGrey", "#FFE6E200": "Peridot", "#FFD99058": "Persian Orange", "#FFDF00FF": "Phlox", "#FF000F89": "Phthalo Blue", "#FF123524": "Phthalo Green", "#FFFDDDE6": "Piggy Pink", "#FFE7ACCF": "Pink Pearl", "#FFF78FA7": "Pink Sherbet", "#FFE5E4E2": "Platinum", "#FF8E4585": "Plum Traditional", "#FFFF5A36": "Portland Orange", "#FFFF8F00": "Princeton Orange", "#FF9F00C5": "Purple Munsell", "#FF50404D": "Purple Taupe", "#FFA020F0": "Purple X11", "#FFE25098": "Raspberry Pink", "#FFB3446C": "Raspberry Rose", "#FFF2003C": "Red Munsell", "#FFC40233": "Red Ncs", "#FFED1C24": "Red Pigment", "#FFFE2712": "Red Ryb", "#FF522D80": "Regalia", "#FF004040": "Rich Black", "#FFF1A7FE": "Rich Brilliant Lavender", "#FFD70040": "Rich Carmine", "#FF0892D0": "Rich Electric Blue", "#FFA76BCF": "Rich Lavender", "#FFB666D2": "Rich Lilac", "#FF414833": "Rifle Green", "#FFF9429E": "Rose Bonbon", "#FF674846": "Rose Ebony", "#FFB76E79": "Rose Gold", "#FFFF66CC": "Rose Pink", "#FFAA98A9": "Rose Quartz", "#FF905D5D": "Rose Taupe", "#FFAB4E52": "Rose Vale", "#FFD40000": "Rosso Corsa", "#FF0038A8": "Royal Azure", "#FF002366": "Royal Blue Traditional", "#FFCA2C92": "Royal Fuchsia", "#FFFF0028": "Ruddy", "#FFBB6528": "Ruddy Brown", "#FFE18E96": "Ruddy Pink", "#FFA81C07": "Rufous", "#FF00563F": "Sacramento State Green", "#FFFF6700": "Safety Orange Blaze Orange", "#FFECD540": "Sandstorm", "#FF507D2A": "Sap Green", "#FFCBA135": "Satin Sheen Gold", "#FFFFD800": "School Bus Yellow", "#FF321414": "Seal Brown", "#FF009E60": "Shamrock Green", "#FF882D17": "Sienna1", "#FFCB410B": "Sinopia", "#FF007474": "Skobeloff", "#FFCF71AF": "Sky Magenta", "#FF933D41": "Smokey Topaz", "#FF100C08": "Smoky Black", "#FF0FC0FC": "Spiro Disco Ball", "#FFFEFDFF": "Splashed White", "#FFA7FC00": "Spring Bud", "#FF23297A": "St.Patrick SBlue", "#FFF94D00": "Tangelo", "#FF006D5B": "Teal Green", "#FFDE6FA1": "Thulian Pink", "#FF0ABAB5": "Tiffany Blue", "#FFE08D3C": "Tiger SEye", "#FFEEE600": "Titanium Yellow", "#FF746CC0": "Toolbox", "#FF417DC1": "Tufts Blue", "#FFA0D6B4": "Turquoise Green", "#FF823535": "Tuscan Red", "#FF8A496B": "Twilight Lavender", "#FF0033AA": "Ua Blue", "#FFD9004C": "Ua Red", "#FF8878C3": "Ube", "#FF536895": "Ucla Blue", "#FFFFB300": "Ucla Gold", "#FF3CD070": "Ufo Green", "#FF4166F5": "Ultramarine Blue", "#FF5B92E5": "United Nations Blue", "#FF7B1113": "Up Maroon", "#FFAE2029": "Upsdell Red", "#FFE1AD21": "Urobilin", "#FFD3003F": "Utah Crimson", "#FFC5B358": "Vegas Gold", "#FF8F00FF": "Violet1", "#FF7F00FF": "Violet Color Wheel", "#FF8601AF": "Violet Ryb", "#FF922724": "Vivid Auburn", "#FF9F1D35": "Vivid Burgundy", "#FFDA1D81": "Vivid Cerise", "#FF004242": "Warm Black", "#FF645452": "Wenge", "#FFEFCC00": "Yellow Munsell", "#FFFFD300": "Yellow Ncs", "#FFFEFE33": "Yellow Ryb", "#FF0014A8": "Zaffre", "#FF2C1608": "Zinnwaldite Brown", "#FF0048BA": "Absolute Zero", "#FFB0BF1A": "Acid green", "#FF7CB9E8": "Aero", "#FFB284BE": "African violet", "#FF72A0C1": "Air superiority blue", "#FFDB2D43": "Alizarin", "#FFC46210": "Alloy orange", "#FF9F2B68": "Amaranth deep purple", "#FFF19CBB": "Amaranth pink", "#FFAB274F": "Amaranth purple", "#FF3DDC84": "Android green", "#FF665D1E": "Antique bronze", "#FF841B2D": "Antique ruby", "#FFD0FF14": "Arctic lime", "#FF4B6F44": "Artichoke green", "#FFF0FFFF": "Azure (X11/web color)", "#FF89CFF0": "Baby blue", "#FFFEFEFA": "Baby powder", "#FFFF91AF": "Baker-Miller pink", "#FFFAE7B5": "Banana Mania Variant", "#FFDA1884": "Barbie Pink", "#FF7C0A02": "Barn red", "#FF9F8170": "Beaver Variant", "#FF2E5894": "B'dazzled blue", "#FF9C2542": "Big dip o\u2019ruby", "#FF54626F": "Black coral", "#FF3B3C36": "Black olive", "#FFBFAFB2": "Black Shadows", "#FFA57164": "Blast-off bronze", "#FFACE5EE": "Blizzard blue", "#FF660000": "Blood red", "#FF1F75FE": "Blue (Crayola)", "#FF0093AF": "Blue (Munsell)", "#FF0087BD": "Blue (NCS)", "#FF0018A8": "Blue (Pantone)", "#FFA2A2D0": "Blue bell", "#FF5DADEC": "Blue jeans", "#FF126180": "Blue sapphire", "#FF5072A7": "Blue yonder", "#FF3C69E7": "Bluetiful", "#FFDE5D83": "Blush Variant", "#FFE3DAC9": "Bone Variant", "#FFCB4154": "Brick red", "#FFD891EF": "Bright lilac", "#FFFFAA1D": "Bright yellow (Crayola)", "#FFCD7F32": "Bronze Variant", "#FFAF6E4D": "Brown sugar", "#FF7BB661": "Bud green", "#FFFFC680": "Buff Variant", "#FF800020": "Burgundy Variant", "#FFA17A74": "Burnished brown", "#FFCC5500": "Burnt orange", "#FF4B3621": "Caf\u00e9 noir", "#FFEFBBCC": "Cameo pink", "#FF56A0D3": "Carolina blue", "#FF703642": "Catawba", "#FFC95A49": "Cedar Chest", "#FFB2FFFF": "Celeste Variant", "#FF6D9BC3": "Cerulean frost", "#FF0040FF": "Cerulean (RGB)", "#FFF7E7CE": "Champagne Variant", "#FFF1DDCF": "Champagne pink", "#FF36454F": "Charcoal Variant", "#FF80FF00": "Chartreuse (web)", "#FF954535": "Chestnut Variant", "#FFE23D28": "Chili red", "#FFAA381E": "Chinese red", "#FF856088": "Chinese violet", "#FFFFB200": "Chinese yellow", "#FFCD607E": "Cinnamon Satin", "#FFE4D00A": "Citrine", "#FF9FA91F": "Citron Variant", "#FF6F4E37": "Coffee Variant", "#FFB9D9EB": "Columbia Blue Variant", "#FFAD6F69": "Copper penny", "#FFCB6D51": "Copper red", "#FF2E2D88": "Cosmic cobalt", "#FF81613C": "Coyote brown", "#FFFFBCD9": "Cotton candy", "#FF9E1B32": "Crimson (UA)", "#FF58427C": "Cyber grape", "#FFF56FA1": "Cyclamen", "#FF543D37": "Dark liver (horses)", "#FF301934": "Dark purple", "#FF8CBED6": "Dark sky blue", "#FF4A646C": "Deep Space Sparkle", "#FF7E5E60": "Deep taupe", "#FF2243B6": "Denim blue", "#FF4A412A": "Drab dark brown", "#FFEFDFBB": "Dutch white", "#FF555D50": "Ebony Variant", "#FF1B1B1B": "Eerie black", "#FFBF00FF": "Electric purple", "#FFB48395": "English lavender", "#FFAB4B52": "English red", "#FFCC474B": "English vermillion", "#FF563C5C": "English violet", "#FF00FF40": "Erin", "#FFDE5285": "Fandango pink", "#FFE5AA70": "Fawn", "#FFFF5470": "Fiery rose", "#FF683068": "Finn Variant", "#FFE25822": "Flame", "#FF856D4D": "French bistre", "#FFFD3F92": "French fuchsia", "#FF86608E": "French lilac", "#FF9EFD38": "French lime", "#FFD473D4": "French mauve", "#FFFD6C9E": "French pink", "#FFC72C48": "French raspberry", "#FF77B5FE": "French sky blue", "#FF8806CE": "French violet", "#FFE936A7": "Frostbite", "#FF87421F": "Fuzzy Wuzzy", "#FF007F66": "Generic viridian", "#FFAB92B3": "Glossy grape", "#FF00AB66": "GO green", "#FF85754E": "Gold Fusion", "#FFFFDF00": "Golden yellow", "#FF00573F": "Gotham green", "#FF676767": "Granite gray", "#FFA8E4A0": "Granny Smith apple", "#FFBEBEBE": "Gray (X11 gray)", "#FF1CAC78": "Green (Crayola)", "#FF008000": "Green (web)", "#FF00A877": "Green (Munsell)", "#FF009F6B": "Green (NCS)", "#FF00AD43": "Green (Pantone)", "#FF1164B4": "Green-blue", "#FFA7F432": "Green Lizard", "#FF6EAEA1": "Green Sheen", "#FF2a3439": "Gunmetal", "#FFDA9100": "Harvest gold", "#FFFF7A00": "Heat Wave", "#FF006DB0": "Honolulu blue", "#FF49796B": "Hooker's green", "#FF355E3B": "Hunter green", "#FF71A6D2": "Iceberg Variant", "#FF319177": "Illuminating emerald", "#FFED2939": "Imperial red", "#FF4C516D": "Independence", "#FF6A5DFF": "Indigo Variant", "#FF130a8f": "International Klein Blue", "#FFBA160C": "International orange (engineering)", "#FFC0362C": "International orange (Golden Gate Bridge)", "#FF9D2933": "Japanese carmine", "#FF5B3256": "Japanese violet", "#FFF8DE7E": "Jasmine", "#FF343434": "Jet", "#FFF4CA16": "Jonquil Variant", "#FFE8F48C": "Key lime", "#FF6B4423": "Kobicha", "#FF512888": "KSU purple", "#FFA9BA9D": "Laurel green", "#FFE6E6FA": "Lavender (web)", "#FFC4C3D0": "Lavender gray", "#FFFFF700": "Lemon Variant", "#FFCCA01D": "Lemon curry", "#FFFDFF00": "Lemon glacier", "#FFF6EABE": "Lemon meringue", "#FFFFF44F": "Lemon yellow", "#FFFFFF9F": "Lemon yellow (Crayola)", "#FF545AA7": "Liberty", "#FFC8AD7F": "Light French beige", "#FFFED8B1": "Light orange", "#FFC5CBE1": "Light periwinkle", "#FFAE98AA": "Lilac Luster", "#FFDECC9C": "Lion", "#FF6CA0DC": "Little boy blue", "#FFB86D29": "Liver (dogs)", "#FF6C2E1F": "Liver (organ)", "#FF987456": "Liver chestnut", "#FFCC3336": "Madder Lake", "#FFD0417E": "Magenta (Pantone)", "#FF9F4576": "Magenta haze", "#FFF2E8D7": "Magnolia Variant", "#FFC04000": "Mahogany Variant", "#FFF2C649": "Maize (Crayola)", "#FF979AAA": "Manatee Variant", "#FFF37A48": "Mandarin", "#FFFDBE02": "Mango", "#FFFF8243": "Mango Tango Variant", "#FF880085": "Mardi Gras Variant", "#FFEAA221": "Marigold Variant", "#FF00488B": "Marian blue", "#FFEF98AA": "Mauvelous Variant", "#FF47ABCC": "Maximum blue", "#FF30BFBF": "Maximum blue green", "#FFACACE6": "Maximum blue purple", "#FF5E8C31": "Maximum green", "#FFD9E650": "Maximum green yellow", "#FF733380": "Maximum purple", "#FFD92121": "Maximum red", "#FFA63A79": "Maximum red purple", "#FFFAFA37": "Maximum yellow", "#FFF2BA49": "Maximum yellow red", "#FF4C9141": "May green", "#FFF8B878": "Mellow apricot", "#FFD3AF37": "Metallic gold", "#FF0A7E8C": "Metallic Seaweed", "#FF9C7C38": "Metallic Sunburst", "#FFE4007C": "Mexican pink", "#FF7ED4E6": "Middle blue", "#FF8DD9CC": "Middle blue green", "#FF8B72BE": "Middle blue purple", "#FF4D8C57": "Middle green", "#FFACBF60": "Middle green yellow", "#FFD982B5": "Middle purple", "#FFE58E73": "Middle red", "#FFA55353": "Middle red purple", "#FFFFEB00": "Middle yellow", "#FFECB176": "Middle yellow red", "#FF702670": "Midnight Variant", "#FFFFDAE9": "Mimi pink", "#FFF5E050": "Minion yellow", "#FF3EB489": "Mint", "#FFBBB477": "Misty moss", "#FFFF948E": "Mona Lisa Variant", "#FF8DA399": "Morning blue", "#FF8A9A5B": "Moss green", "#FF30BA8F": "Mountain Meadow Variant", "#FFC8509B": "Mulberry (Crayola)", "#FF317873": "Myrtle green", "#FFD65282": "Mystic Variant", "#FFAD4379": "Mystic maroon", "#FF1974D2": "Navy blue (Crayola)", "#FF4666FF": "Neon blue", "#FFFE4164": "Neon fuchsia", "#FF214FC6": "New Car", "#FF727472": "Nickel", "#FFE9FFDB": "Nyanza", "#FF43302E": "Old burgundy", "#FF353839": "Onyx Variant", "#FFA8C3BC": "Opal Variant", "#FFFF7538": "Orange (Crayola)", "#FFFF5800": "Orange (Pantone)", "#FFFF9F00": "Orange peel", "#FFFF5349": "Orange-red (Crayola)", "#FFFA5B3D": "Orange soda", "#FFF5BD1F": "Orange-yellow", "#FFF8D568": "Orange-yellow (Crayola)", "#FFF2BDCD": "Orchid pink", "#FFFF6E4A": "Outrageous Orange Variant", "#FF4A0000": "Oxblood", "#FF002147": "Oxford blue", "#FF841617": "OU Crimson red", "#FF1CA9C9": "Pacific blue", "#FF006600": "Pakistan green", "#FFBED3E5": "Pale aqua", "#FFED7A9B": "Pale Dogwood", "#FFFAE6FA": "Pale purple (Pantone)", "#FF009B7D": "Paolo Veronese green", "#FFE63E62": "Paradise pink", "#FFDEA5A4": "Pastel pink", "#FF800080": "Patriarch", "#FF1F005E": "Paua Variant", "#FFB768A2": "Pearly purple", "#FFE12C2C": "Permanent Geranium Lake", "#FFEC5800": "Persimmon Variant", "#FF470659": "Petunia", "#FF8BA8B7": "Pewter Blue", "#FF2E2787": "Picotee blue", "#FFC30B4E": "Pictorial carmine", "#FF2A2F23": "Pine green", "#FFD74894": "Pink (Pantone)", "#FFD8B2D1": "Pink lavender", "#FF93C572": "Pistachio Variant", "#FFDDA0DD": "Plum (web)", "#FF5946B2": "Plump Purple", "#FF5DA493": "Polished Pine", "#FFBE4F62": "Popstar", "#FFE1CA7A": "Prairie gold", "#FFF58025": "Princeton orange", "#FF00B9F2": "Process Cyan", "#FF644117": "Pullman Brown (UPS Brown)", "#FF6A0DAD": "Purple Variant", "#FF4E5180": "Purple navy", "#FFFE4EDA": "Purple pizzazz[broken anchor]", "#FF9C51B6": "Purple Plum", "#FF436B95": "Queen blue", "#FFE8CCD7": "Queen pink", "#FFA6A6A6": "Quick Silver", "#FF8E3A59": "Quinacridone magenta", "#FF242124": "Raisin black", "#FFFBAB60": "Rajah Variant", "#FFE30B5D": "Raspberry Variant", "#FFD68A59": "Raw sienna", "#FF826644": "Raw umber", "#FFE3256B": "Razzmatazz Variant", "#FF8D4E85": "Razzmic Berry", "#FFEE204D": "Red (Crayola)", "#FF913831": "Red ocher (Red ochre)[2]", "#FFE40078": "Red-purple", "#FFFD3A4A": "Red Salsa", "#FFC0448F": "Red-violet (Crayola)", "#FF922B3E": "Red-violet (Color wheel)", "#FFA45A52": "Redwood Variant", "#FF777696": "Rhythm", "#FF010B13": "Rich black (FOGRA29)", "#FF010203": "Rich black (FOGRA39)", "#FF444C38": "Rifle green", "#FF8A7F80": "Rocket metallic", "#FFA91101": "Rojo Spanish red", "#FF838996": "Roman silver", "#FFFF0080": "Rose Variant", "#FF9E5E6F": "Rose Dust", "#FFC21E56": "Rose red", "#FF7851A9": "Royal purple", "#FFCE4676": "Ruber", "#FFD10056": "Rubine red", "#FF9B111E": "Ruby red", "#FF679267": "Russian green", "#FF32174D": "Russian violet", "#FFDA2C43": "Rusty red", "#FF043927": "Sacramento State green", "#FF8B4513": "Saddle brown", "#FFFF7800": "Safety orange", "#FFEED202": "Safety yellow", "#FFBCB88A": "Sage Variant", "#FFFA8072": "Salmon Variant", "#FF0F52BA": "Sapphire Variant", "#FF2D5DA1": "Sapphire (Crayola)", "#FF00FFCD": "Sea green (Crayola)", "#FF612086": "Seance Variant", "#FF59260B": "Seal brown", "#FF764374": "Secret", "#FF8A795D": "Shadow Variant", "#FF778BA5": "Shadow blue", "#FF8FD400": "Sheen green", "#FFD98695": "Shimmering Blush", "#FF5FA778": "Shiny Shamrock", "#FFAAA9AD": "Silver (Metallic)", "#FFC4AEAD": "Silver pink", "#FFFF3855": "Sizzling Red", "#FFFFDB00": "Sizzling Sunrise", "#FF87CEEB": "Sky blue", "#FF6A5ACD": "Slate blue", "#FF299617": "Slimy green", "#FFC84186": "Smitten", "#FF757575": "Sonic silver", "#FF1D2951": "Space cadet", "#FF807532": "Spanish bistre", "#FF0070B8": "Spanish blue", "#FFD10047": "Spanish carmine", "#FF989898": "Spanish gray", "#FF009150": "Spanish green", "#FFE86100": "Spanish orange", "#FFF7BFBE": "Spanish pink", "#FFE60026": "Spanish red", "#FF00FFFE": "Spanish sky blue", "#FF4C2882": "Spanish violet", "#FF007F5C": "Spanish viridian", "#FF87FF2A": "Spring Frost", "#FF00FF80": "Spring green", "#FF007BB8": "Star command blue", "#FFCC33CC": "Steel pink", "#FFE4D96F": "Straw Variant", "#FFFA5053": "Strawberry", "#FFFF9361": "Strawberry Blonde", "#FF33CC33": "Strong Lime Green", "#FF914E75": "Sugar Plum", "#FFE3AB57": "Sunray", "#FFCF6BA9": "Super pink", "#FFA83731": "Sweet Brown", "#FFD44500": "Syracuse Orange", "#FFD99A6C": "Tan (Crayola)", "#FFFB4D46": "Tart Orange", "#FF8B8589": "Taupe gray", "#FF367588": "Teal blue", "#FF00FFBF": "Technobotanica", "#FFCF3476": "Telemagenta", "#FFFC89AC": "Tickle Me Pink Variant", "#FFDBD7D2": "Timberwolf Variant", "#FF86A1A9": "Tourmaline", "#FF2D68C4": "True Blue", "#FF1C05B3": "Trypan Blue", "#FF3E8EDE": "Tufts blue", "#FFDEAA88": "Tumbleweed Variant", "#FF40E0D0": "Turquoise Variant", "#FF00FFEF": "Turquoise blue", "#FF7C4848": "Tuscan red", "#FFC09999": "Tuscany Variant", "#FFFC6C85": "Ultra red", "#FF635147": "Umber", "#FFFFDDCA": "Unbleached silk", "#FF009EDB": "United Nations blue", "#FFA50021": "University of Pennsylvania red", "#FFAFDBF5": "Uranian blue", "#FF004F98": "USAFA blue", "#FF664228": "Van Dyke brown", "#FFF38FA9": "Vanilla ice", "#FF5271FF": "Vantg blue", "#FFC80815": "Venetian red", "#FF43B3AE": "Verdigris Variant", "#FFD9381E": "Vermilion Variant", "#FF963D7F": "Violet (crayola)", "#FF324AB2": "Violet-blue", "#FF766EC8": "Violet-blue (Crayola)", "#FFF75394": "Violet-red", "#FFF0599C": "Violet-red(PerBang)", "#FF009698": "Viridian green", "#FF00CCFF": "Vivid sky blue", "#FFFFA089": "Vivid tangerine", "#FF9F00FF": "Vivid violet", "#FFCEFF00": "Volt", "#FF189BCC": "Weezy Blue", "#FFA2ADD0": "Wild blue yonder", "#FFD470A2": "Wild orchid", "#FFFF43A4": "Wild Strawberry Variant", "#FFFD5800": "Willpower orange", "#FFA75502": "Windsor tan", "#FF722F37": "Wine", "#FFB11226": "Wine Red", "#FFFF007C": "Winter Sky", "#FF56887D": "Wintergreen Dream", "#FFEEED09": "Xanthic", "#FFF1B42F": "Xanthous", "#FF00356B": "Yale Blue Variant", "#FFFCE883": "Yellow (Crayola)", "#FFFEDF00": "Yellow (Pantone)", "#FFC5E384": "Yellow-green (Crayola)", "#FF30B21A": "Yellow-green (Color Wheel)", "#FFFF9505": "Yellow Orange (Color Wheel)", "#FFFFF000": "Yellow Rose", "#FFFDF8FF": "Zinc white", "#FF6C0277": "Zinzolin", "#FF39A78E": "Zomp", "#FFC93F38": "100 Mph", "#FFA59344": "18th Century Green", "#FF7B463B": "1975 Earth Red", "#FFDD3366": "1989 Miami Hotline", "#FF7FB9DD": "21st Century Blue", "#FFE56E24": "24 Carrot", "#FFDFC685": "24 Karat", "#FF330404": "3AM Breakup", "#FF225577": "3AM in Shibuya", "#FFC0A98E": "3AM Latte", "#FFD2D2C0": "400XT Film", "#FF9BAFAD": "5-Masted Preu\u00dfen", "#FF3D1C02": "90% Cocoa", "#FF000099": "99 Years Blue", "#FFFFAABB": "A Brand New Day", "#FFD1EDEE": "A Certain Shade of Green", "#FFD3DDE4": "A Dime a Dozen", "#FF9F9978": "A Frond in Need", "#FFF2850D": "\u00c0 l\u2019Orange", "#FFF6ECDE": "A la Mode", "#FFFFBCC5": "A Lot of Love", "#FFBCDDB3": "A Mann\u2019s Mint", "#FFB0B2BC": "A Month of Sundays", "#FFBFAF92": "A Pair of Brown Eyes", "#FFBB8AA7": "A Plum Job", "#FFF3E9D9": "A Smell of Bakery", "#FF88FFCC": "A State of Mint", "#FFBABCCF": "A Stitch in Time", "#FFA9BFC5": "A-List Blue", "#FF8BCECB": "Aaquatic Wonderland", "#FF00B89F": "Aare River", "#FF05A3AD": "Aare River Brienz", "#FF1150AF": "Aarhusian Sky", "#FF231F20": "Abaddon Black", "#FFF2F1E6": "Abaidh White", "#FFF8F3F6": "Abalone", "#FF94877E": "Abandoned Mansion", "#FF746E6A": "Abandoned Playground", "#FF747A8A": "Abandoned Spaceship", "#FFCD716B": "Abbey Pink", "#FFA79F92": "Abbey Road", "#FFABA798": "Abbey Stone", "#FFECE6D0": "Abbey White", "#FF4D3C2D": "Abbot", "#FF166461": "Abduction", "#FFF5ECDA": "Abel", "#FF5BA8FF": "\u00c2bi Blue", "#FFEAE3D2": "Abilene Lace", "#FFC04641": "Ablaze", "#FFF1CBCD": "Abloom", "#FF77AA77": "Abomination", "#FFE0DDBE": "Above Board", "#FF966165": "Abra Cadabra", "#FFEEC400": "Abra Goldenrod", "#FF462E6E": "Abra Purple", "#FF15151C": "Absence of Light", "#FF7F8B30": "Absinthe Dreams", "#FF76B583": "Absinthe Green", "#FF008A60": "Absinthe Turquoise", "#FFFF9944": "Absolute Apricot", "#FF877D65": "Absolute Olive", "#FFE4CB97": "Abstract", "#FFEDE9DD": "Abstract White", "#FF629763": "Abundance", "#FFA19361": "Abura Green", "#FF8F9E9D": "Abyss", "#FF404C57": "Abyssal", "#FF1B2632": "Abyssal Anchorfish Blue", "#FF00035B": "Abyssal Blue", "#FF10246A": "Abyssal Depths", "#FF005765": "Abyssal Waters", "#FF3D5758": "Abysse", "#FF000033": "Abyssopelagic Water", "#FFDBCD64": "Acacia", "#FF486241": "Acacia Green", "#FF969C92": "Acacia Haze", "#FFA69D64": "Acacia Tree", "#FF2C3E56": "Academic Blue", "#FF79888E": "Academy Grey", "#FF525367": "Academy Purple", "#FF35312C": "Acadia Variant", "#FFE5B7BE": "Acadia Bloom", "#FF48295B": "Acai", "#FF42314B": "Acai Berry", "#FF942193": "Acai Juice", "#FF4C2F27": "Acajou", "#FF9899A7": "Acanthus", "#FF90977A": "Acanthus Leaf", "#FF75AA94": "Acapulco Variant", "#FF7FA8A7": "Acapulco Aqua", "#FF4E9AA8": "Acapulco Cliffs", "#FF65A7DD": "Acapulco Dive", "#FFEB8A44": "Acapulco Sun", "#FF208468": "Accent Green Blue", "#FFE56D00": "Accent Orange", "#FFD2C7B7": "Accessible Beige", "#FF7C94B2": "Accolade", "#FF090807": "Accursed Black", "#FFC7CCE7": "Ace", "#FF727A5F": "Aceituna Picante", "#FF4E4F48": "Aceto Balsamico", "#FF00FF22": "Acid", "#FFEFEDD7": "Acid Blond", "#FFA8C74D": "Acid Candy", "#FF11FF22": "Acid Drop", "#FF8FFE09": "Acid Green", "#FFB9DF31": "Acid Lime", "#FF00EE22": "Acid Pool", "#FF33EE66": "Acid Pops", "#FF30FF21": "Acid Reflux", "#FF4FC172": "Acid Sleazebag", "#FF9E9991": "Acier", "#FF9CA7B5": "Acier Tint", "#FFFFD8B1": "Acini di Pepe", "#FF7249D6": "Aconite Purple", "#FF9C52F2": "Aconite Violet", "#FF7F5E50": "Acorn", "#FFD48948": "Acorn Nut", "#FFB87439": "Acorn Spice", "#FFEDA740": "Acorn Squash", "#FF766B69": "Acoustic Brown", "#FFEFECE1": "Acoustic White", "#FFF8E2CA": "Acropolis", "#FFB3E1E8": "Across the Bay", "#FFFF44EE": "Actinic Light", "#FF00504B": "Action Green", "#FF00A67E": "Active Green", "#FF006F72": "Active Turquoise", "#FFBB1133": "Active Volcano", "#FFA7A6A3": "Actor\u2019s Star", "#FF46ADF9": "Adamantine Blue", "#FF3B845E": "Adamite Green", "#FF661111": "Adana Kebab\u0131", "#FF867E70": "Adaptive Shade", "#FF545651": "Addo", "#FF293947": "Adept", "#FF7C8286": "Adeptus Battlegrey", "#FF9E9CAB": "Adhesion", "#FFB7C1BE": "Adieu", "#FFB0B9C1": "Adirondack", "#FF74858F": "Adirondack Blue", "#FFC4AA9B": "Adirondack Path", "#FF50647F": "Admiral Blue", "#FF404E61": "Admiralty", "#FFF6F3D3": "Admiration", "#FFBD6C48": "Adobe", "#FFFB9587": "Adobe Avenue", "#FFDCBFA6": "Adobe Beige", "#FFC5703F": "Adobe Dusk", "#FFC99F93": "Adobe Glow", "#FFBA9F99": "Adobe Rose", "#FFE8DEC5": "Adobe Sand", "#FFE5C1A7": "Adobe South", "#FFC3A998": "Adobe Straw", "#FFD6AE9A": "Adobe Village", "#FFE6DBC4": "Adobe White", "#FFA99681": "Adolescent Rodent", "#FF64B5BF": "Adonis", "#FFEFBF4D": "Adonis Rose Yellow", "#FF8D8DC9": "Adora", "#FFE3BEB0": "Adorable", "#FFB8CACE": "Adorbs", "#FF014A69": "Adriatic", "#FF5C899B": "Adriatic Blue", "#FF96C6CD": "Adriatic Haze", "#FFD3ECE4": "Adriatic Mist", "#FF016081": "Adriatic Sea", "#FF4B9099": "Adrift", "#FF758181": "Adrift at Sea", "#FF93B8E3": "Adrift on the Nile", "#FF20726A": "Advantageous", "#FF34788C": "Adventure", "#FFF87858": "Adventure Island Pink", "#FF6F9FB9": "Adventure Isle", "#FF3063AF": "Adventure of the Seas", "#FFEDA367": "Adventure Orange", "#FF72664F": "Adventurer", "#FF7CAC88": "Adventurine", "#FFD8CB4B": "Advertisement Green", "#FF0081A8": "Advertising Blue", "#FF53A079": "Advertising Green", "#FFE6D3B6": "Aebleskiver", "#FF1B416F": "Aegean Blue", "#FF4C8C72": "Aegean Green", "#FF9CBBE2": "Aegean Mist", "#FF508FA2": "Aegean Sea", "#FFE48B59": "Aegean Sky", "#FF9BA0A4": "Aegean Splendour", "#FFA0B2C8": "Aerial View", "#FFC0E8D5": "Aero Blue Variant", "#FFA2C348": "Aerobic Fix", "#FFCACECD": "Aerodynamic", "#FF2B3448": "Aeronautic", "#FF355376": "Aerostatics", "#FFE3DDD3": "Aesthetic White", "#FF745085": "Affair Variant", "#FFAAFFFF": "Affen Turquoise", "#FFFED2A5": "Affinity", "#FF905E26": "Afghan Carpet", "#FFE2D7B5": "Afghan Hound", "#FFD3A95C": "Afghan Sand", "#FF78A3C2": "Afloat", "#FFC7927A": "African Bubinga", "#FF939899": "African Grey", "#FFCD4A4A": "African Mahogany", "#FF826C68": "African Mud", "#FF86714A": "African Plain", "#FFB16B40": "African Safari", "#FFCCAA88": "African Sand", "#FFB085B7": "African Violet", "#FFFD8B60": "After Burn", "#FF3C3535": "After Dark", "#FFE3F5E5": "After Dinner Mint", "#FF3D2E24": "After Eight", "#FFD6EAE8": "After Eight Filling", "#FF38393F": "After Midnight", "#FFFEC65F": "After Shock", "#FF8BC4D1": "After the Rain", "#FF33616A": "After the Storm", "#FF24246D": "After Work Blue", "#FFC95EFB": "After-Party Pink", "#FF85C0CD": "Aftercare", "#FFF2E3C5": "Afterglow", "#FFD91FFF": "Afterlife", "#FFFBCB78": "Afternoon", "#FF6E544B": "Afternoon Coffee", "#FFB7B5A4": "Afternoon Nap", "#FFD9C5A1": "Afternoon Stroll", "#FF594E40": "Afternoon Tea", "#FFBBC5DE": "Agapanthus", "#FF956A60": "Agate Brown", "#FF5A5B74": "Agate Violet", "#FF879D99": "Agave", "#FF5A6E6A": "Agave Frond", "#FF70766E": "Agave Green", "#FF879C67": "Agave Plant", "#FF886B2E": "Aged Antics", "#FF846262": "Aged Beech", "#FFD7CFC0": "Aged Beige", "#FF986456": "Aged Bourbon", "#FF87413F": "Aged Brandy", "#FF5F4947": "Aged Chocolate", "#FFE0DCDA": "Aged Cotton", "#FF898253": "Aged Eucalyptus", "#FFDD9944": "Aged Gouda", "#FFF9D5AF": "Aged Ivory", "#FF6C6956": "Aged Jade", "#FF73343A": "Aged Merlot", "#FF7E7E7E": "Aged Moustache Grey", "#FF6E6E30": "Aged Mustard Green", "#FF7E7666": "Aged Olive", "#FFCEB588": "Aged Papyrus", "#FFE9DDCA": "Aged Parchment", "#FF889999": "Aged Pewter", "#FF363E31": "Aged Pine", "#FFC99F99": "Aged Pink", "#FFFFFA86": "Aged Plastic Casing", "#FFA442A0": "Aged Purple", "#FFCCB27A": "Aged Seagrass", "#FF7A4134": "Aged Teak", "#FFA58EA9": "Aged", "#FF9D7147": "Aged Whisky", "#FFE8DECD": "Aged White", "#FF895460": "Aged Wine", "#FF6A5B4E": "Ageing Barrel", "#FFECECDF": "Ageless", "#FFE7A995": "Ageless Beauty", "#FF6FFFFF": "Aggressive Baby Blue", "#FFFF7799": "Aggressive Salmon", "#FF393121": "Agrax Earthshade", "#FF8E9683": "Agreeable Green", "#FFA17C59": "Agrellan Earth", "#FF00FBFF": "Agressive Aqua", "#FFF0E2D3": "Agrodolce", "#FF9FC5CC": "Agua Fr\u00eda", "#FF00FA92": "Ahaetulla Prasina", "#FFC22147": "Ahmar Red", "#FF2A3149": "Ahoy", "#FF0082A1": "Ahoy! Blue", "#FF199EBD": "Ahriman Blue", "#FF274447": "Ai Indigo", "#FFECF7F7": "Aijiro White", "#FFEEE5E1": "Aimee", "#FF2E372E": "Aimiru Brown", "#FF69A3C1": "Air Blue", "#FFD7D1E9": "Air Castle", "#FFD8F2EE": "Air of Mint", "#FFF6DCD2": "Air-Kiss", "#FFA2C2D0": "Airborne", "#FFAA6C51": "Airbrushed Copper", "#FF354F58": "Aircraft Blue", "#FFD9E5E4": "Airflow", "#FF364D70": "Airforce", "#FFEDF2F8": "Airport", "#FFAEC1D4": "Airway", "#FFDAE6E9": "Airy", "#FF88CCEE": "Airy Blue", "#FFDBE0C4": "Airy Fields", "#FFFAECD9": "Ajo Lily", "#FFD3DE7B": "Ajwain Green", "#FFC3272B": "Akabeni", "#FFBC012E": "Akai Red", "#FFF07F5E": "Akak\u014d Red", "#FFC90B42": "Akari Red", "#FFBEB29A": "Akaroa Variant", "#FFCF3A24": "Ake Blood", "#FF983FB2": "Akebi Purple", "#FFFA7B62": "Akebono Dawn", "#FF601EF9": "Akihabara Arcade", "#FFE12120": "Akira Red", "#FFD63136": "Akuma", "#FF871646": "Akuma\u2019s Fury", "#FFB9D08B": "Al Green", "#FFA32638": "Alabama Crimson", "#FFF3E7DB": "Alabaster Variant", "#FFE9E3D2": "Alabaster Beauty", "#FFF0DEBD": "Alabaster Gleam", "#FF81585B": "Alaea", "#FF8E8C97": "Alaitoc Blue", "#FFFFAE52": "Alajuela Toad", "#FFCA9234": "Alameda Ochre", "#FF939B71": "Alamosa Green", "#FFEC0003": "Alarm", "#FF2CE335": "Alarming Slime", "#FFDADAD1": "Alaska", "#FF61A4CE": "Alaskan Blue", "#FFBCBEBC": "Alaskan Grey", "#FF7E9EC2": "Alaskan Ice", "#FFECF0E5": "Alaskan Mist", "#FF05472A": "Alaskan Moss", "#FFCDDCED": "Alaskan Skies", "#FFBAE3EB": "Alaskan Wind", "#FFCC0001": "Albanian Red", "#FF38546E": "Albeit", "#FF4F5845": "Albert Green", "#FFE1DACB": "Albescent", "#FFFBEEE5": "Albino", "#FFE7CF8C": "Alchemy", "#FFAAA492": "Aldabra", "#FFEFC1A6": "Alesan", "#FF4D7EAA": "Aleutian Isle", "#FFFF8F73": "Alexandria", "#FFFCEFC1": "Alexandria\u2019s Lighthouse", "#FFBCD9DC": "Alexandrian Sky", "#FF598C74": "Alexandrite Green", "#FF72999E": "Alexandrite Teal", "#FFA55232": "Alfajor Brown", "#FFB3B299": "Alfalfa", "#FF78AD6D": "Alfalfa Bug", "#FF546940": "Alfalfa Extract", "#FF80365A": "Alfonso Olive", "#FF8DA98D": "Alga Moss", "#FF54AC68": "Algae", "#FF983D53": "Algae Red", "#FF21C36F": "Algal Fuel", "#FFFC5A50": "Algerian Coral", "#FF008DB0": "Algiers Blue", "#FFC1DBEC": "Algodon Azul", "#FF00A094": "Alhambra", "#FF00A465": "Alhambra Green", "#FFD4CBC4": "Alibi", "#FFC2CED2": "Alice White", "#FF415764": "Alien", "#FF0CFF0C": "Alien Abduction", "#FF84DE02": "Alien Armpit", "#FFB9CC81": "Alien Breed", "#FFF0A3BC": "Alien Conspiracy Pink", "#FF55FF33": "Alien Parasite", "#FF490648": "Alien Purple", "#FF00CC55": "Alienated", "#FF9790A4": "Alienator Grey", "#FF00728D": "Align", "#FFFEDAB9": "Alishan Dawn", "#FF676C58": "All About Olive", "#FFE6999D": "All Dressed Up", "#FFEFD7E7": "All Made Up", "#FF455454": "All Nighter", "#FFB30103": "All Systems Red", "#FF994411": "All the Leaves Are Brown", "#FFC68886": "All\u2019s Ace", "#FF5A6A8C": "Allegiance", "#FFB4B2A9": "Allegory", "#FFB28959": "Allegro", "#FFB8C4D9": "Alley", "#FF656874": "Alley Cat", "#FF2B655F": "Alliance", "#FF886600": "Alligator", "#FFC5D17B": "Alligator Alley", "#FFEAEED7": "Alligator Egg", "#FF444411": "Alligator Gladiator", "#FFF1EAD4": "Allison Lace", "#FF9569A3": "Allium", "#FF908F92": "Alloy", "#FF1F6A7D": "Allports Variant", "#FFF8CDAA": "Allspice", "#FF8E443D": "Allspice Berry", "#FFED2E38": "Allura Red", "#FF7291B4": "Allure", "#FF9EC4CD": "Alluring Blue", "#FFF8DBC2": "Alluring Gesture", "#FFFFF7D8": "Alluring Light", "#FF977B4D": "Alluring Umber", "#FFEFE1D2": "Alluring White", "#FFBB934B": "Alluvial Inca", "#FFE8DEC9": "Almanack", "#FFC2A37E": "Almandine", "#FFF5E0C9": "Almeja", "#FFE8D6BD": "Almendra Tostada", "#FFEDDCC8": "Almond Variant", "#FFDFD5CA": "Almond Beige", "#FFE9C9A9": "Almond Biscuit", "#FFF2ACB8": "Almond Blossom", "#FFE5D3B9": "Almond Brittle", "#FFCCB590": "Almond Buff", "#FFD8C6A8": "Almond Butter", "#FFEEC87C": "Almond Cookie", "#FFF4C29F": "Almond Cream", "#FF9A8678": "Almond Frost Variant", "#FF595E4C": "Almond Green", "#FFEFE3D9": "Almond Icing", "#FFF6E3D4": "Almond Kiss", "#FFD6C0A4": "Almond Latte", "#FFD2C9B8": "Almond Milk", "#FFF4EFC1": "Almond Oil", "#FFE5DBC5": "Almond Paste", "#FFC8B960": "Almond Puff", "#FFF0E8E0": "Almond Roca", "#FFCC8888": "Almond Rose", "#FFE1CFB2": "Almond Silk", "#FFBF9E77": "Almond Toast", "#FF7D665B": "Almond Truffle", "#FFE6C9BC": "Almond Willow", "#FFD6CAB9": "Almond Wisp", "#FFFEDEBC": "Almondine", "#FFBFE5B1": "Almost Aloe", "#FFE0A787": "Almost Apricot", "#FF98DDC5": "Almost Aqua", "#FF6B7070": "Almost Charcoal", "#FF3A5457": "Almost Famous", "#FFE5D9D6": "Almost Mauve", "#FFF0E3DA": "Almost Pink", "#FFBEB0C2": "Almost Plum", "#FF6A2DED": "Almost Royal", "#FF817A60": "Aloe", "#FFC97863": "Aloe Blossom", "#FFDBE5B9": "Aloe Cream", "#FFECF1E2": "Aloe Essence", "#FF61643F": "Aloe Leaf", "#FFDCF2E3": "Aloe Mist", "#FFDFE2C9": "Aloe Nectar", "#FFB8BA87": "Aloe Plant", "#FF888B73": "Aloe Thorn", "#FF8A9480": "Aloe Tip", "#FF678779": "Aloe Vera", "#FF7E9B39": "Aloe Vera Green", "#FF848B71": "Aloe Vera Tea", "#FFD0D3B7": "Aloe Wash", "#FF6A432D": "Aloeswood", "#FF1DB394": "Aloha", "#FFE9AA91": "Aloha Sunset", "#FF000066": "Alone in the Dark", "#FFD4E2E6": "Aloof", "#FFC9C9C0": "Aloof Grey", "#FFD6C5A0": "Aloof Lama", "#FFF9EDE2": "Alpaca", "#FFD7B8A9": "Alpaca Mittens", "#FFF0BEB8": "Alpenglow", "#FF588BB4": "Alpha Blue", "#FF4D5778": "Alpha Centauri", "#FFAE8E5F": "Alpha Gold", "#FF715A45": "Alpha Male", "#FF628FB0": "Alpha Tango", "#FFFEDCBA": "Alphabet Soup", "#FFAD8A3B": "Alpine Variant", "#FFA9B4A9": "Alpine Air", "#FFBADBE6": "Alpine Alabaster", "#FFF7E0BA": "Alpine Berry Yellow", "#FFDBE4E5": "Alpine Blue", "#FF40464D": "Alpine Duck Grey", "#FF99EEFF": "Alpine Expedition", "#FF5F6D91": "Alpine Forget-Me-Not", "#FFE0DED2": "Alpine Frost", "#FFF1F2F8": "Alpine Goat", "#FF005F50": "Alpine Green", "#FFABBEC0": "Alpine Haze", "#FF449955": "Alpine Herbs", "#FF4598AB": "Alpine Lake", "#FF6AAE2E": "Alpine Meadow", "#FFDED3E6": "Alpine Moon", "#FFA6CCD8": "Alpine Morning Blue", "#FFD3DED5": "Alpine Peak", "#FF234162": "Alpine Race", "#FF051009": "Alpine Salamander", "#FF79B4CE": "Alpine Sky", "#FFA5A99A": "Alpine Summer", "#FF515A52": "Alpine Trail", "#FFFFAAA5": "Alright Then I Became a Princess", "#FFB1575F": "Alsike Clover Red", "#FFDFD5B1": "Alsot Olive", "#FF4D4C80": "Altar of Heaven", "#FF5F1A10": "Altar Wine", "#FF69656D": "Alter Ego", "#FFEFC7BE": "Altered Pink", "#FFCDC6C5": "Alto Variant", "#FF5A464B": "Alton Brown", "#FFDDBB00": "Alu Gobi", "#FF000055": "Alucard\u2019s Night", "#FF848789": "Aluminium Variant", "#FFD2D9DB": "Aluminium Foil", "#FF8C8D91": "Aluminium Silver", "#FFADAFAF": "Aluminium Sky", "#FFA5C970": "Alverda", "#FFEBE5D2": "Always Almond", "#FFA0A667": "Always Apple", "#FFA2BACB": "Always Blue", "#FF479532": "Always Greener", "#FF11AA00": "Always Greener Grass", "#FF66778C": "Always Indigo", "#FFDFD7CB": "Always Neutral", "#FFE79DB3": "Always Rosey", "#FFF4E2D6": "Alyssa", "#FFF2D5D7": "Alyssum", "#FF417086": "Am I Blue?", "#FF016E85": "Amalfi", "#FF297CBF": "Amalfi Coast", "#FF033B9A": "Amalfitan Azure", "#FFE86EAD": "Amaranth Variant", "#FF7B2331": "Amaranth Blossom", "#FF723F89": "Amaranth Purple", "#FFD3212D": "Amaranth Red", "#FF5F4053": "Amaranthine", "#FFAB6F60": "Amaretto", "#FFC09856": "Amaretto Sour", "#FFFFF1D4": "Amarillo Bebito", "#FFFBF1C3": "Amarillo Yellow", "#FF551199": "Amarklor Violet", "#FFB85045": "Amaryllis", "#FF806568": "Amazing Amethyst", "#FFA9A797": "Amazing Boulder", "#FFBEB5A9": "Amazing Grey", "#FF017F9D": "Amazing Sky", "#FF387B54": "Amazon Variant", "#FFEBEBD6": "Amazon Breeze", "#FF505338": "Amazon Depths", "#FF31837E": "Amazon Drift", "#FF606553": "Amazon Foliage", "#FF786A4A": "Amazon Green", "#FF686747": "Amazon Jungle", "#FFECECDC": "Amazon Mist", "#FF7E8C7A": "Amazon Moss", "#FF80E45F": "Amazon Parrot", "#FF948F54": "Amazon Queen", "#FF777462": "Amazon River", "#FFE6B2B8": "Amazon River Dolphin", "#FF7E7873": "Amazon Stone", "#FFABAA97": "Amazon Vine", "#FFAA6644": "Amazonian", "#FFA7819D": "Amazonian Orchid", "#FF00C4B0": "Amazonite", "#FF0D2F5A": "Ambassador Blue", "#FFC69C6A": "Amber Autumn", "#FFD7A361": "Amber Brew", "#FFB46A4D": "Amber Brown", "#FFF6BC77": "Amber Dawn", "#FFBA843C": "Amber Essence", "#FFC79958": "Amber Glass", "#FFF29A39": "Amber Glow", "#FFC19552": "Amber Gold", "#FFAC8A41": "Amber Green", "#FFD0A592": "Amber Grey", "#FFBA9971": "Amber Leaf", "#FFEED1A5": "Amber Moon", "#FFF9D975": "Amber Pearl", "#FFB18140": "Amber Romance", "#FFC0863F": "Amber Sienna", "#FFFF9988": "Amber Sun", "#FFFFAFA3": "Amber Tide", "#FFD78B55": "Amber Wave", "#FFFAB75A": "Amber Yellow", "#FFAA8559": "Amberized", "#FFE7E7E6": "Ambience White", "#FFF8EDE0": "Ambient Glow", "#FFC2E2E7": "Ambient Light", "#FF97653F": "Ambit", "#FFDFB77D": "Ambitious", "#FFF0CB97": "Ambitious Amber", "#FFE9687E": "Ambitious Rose", "#FFC6E1BC": "Ambrosia", "#FFEEE9D3": "Ambrosia Coffee Cake", "#FFFFF4EB": "Ambrosia Ivory", "#FFF4DED3": "Ambrosia Salad", "#FF47AE9C": "Ambrosial Oceanside", "#FFBECCC2": "Amelia", "#FFFEA7BD": "Am\u00e9lie\u2019s Tutu", "#FF34546D": "America\u2019s Cup", "#FF7595AB": "American Anthem", "#FFA73340": "American Beauty", "#FF3B3B6D": "American Blue", "#FF391802": "American Bronze", "#FF52352F": "American Mahogany", "#FF63403A": "American Milking Devon", "#FFFF8B00": "American Orange", "#FFFF9899": "American Pink", "#FF431C53": "American Purple", "#FFB32134": "American Red", "#FF626E71": "American River", "#FF995544": "American Roast", "#FF551B8C": "American Violet", "#FFEFDCD4": "American Yorkshire", "#FF0477B4": "Americana", "#FF463732": "Americano Variant", "#FFECEAEC": "Amethyst Cream", "#FF4F3C52": "Amethyst Dark Violet", "#FF776985": "Amethyst Gem", "#FF9085C4": "Amethyst Grey", "#FF9C89A1": "Amethyst Grey Violet", "#FFA0A0AA": "Amethyst Haze", "#FFD0C9C6": "Amethyst Ice", "#FFCFC2D1": "Amethyst Light Violet", "#FF926AA6": "Amethyst Orchid", "#FF9C8AA4": "Amethyst Paint", "#FF9B91A1": "Amethyst Phlox", "#FFBD97CF": "Amethyst Show", "#FF95879C": "Amethyst Smoke Variant", "#FFCDC7D5": "Amethyst Tint", "#FFDED1E0": "Ametrine Quartz", "#FF783E48": "Amfissa Olive", "#FFDF965B": "Amiable Orange", "#FFE6DDBE": "Amish Bread", "#FF3A5F4E": "Amish Green", "#FFA58D6D": "Ammonite Fossil", "#FFF8FBEB": "Amnesiac White", "#FFDDCC22": "Amok", "#FFEE3377": "Amor", "#FFBB22AA": "Amora Purple", "#FFAE2F48": "Amore", "#FF967D96": "Amorous", "#FFB1A7B7": "Amorphous Rose", "#FFEE5851": "Amour Variant", "#FFF5E6EA": "Amour Frais", "#FFC8C5D7": "Amourette", "#FFE0DFE8": "Amourette Eternelle", "#FF556CB5": "Amparo Blue", "#FF264C47": "Amphibian", "#FF384E47": "Amphitrite", "#FF9F8672": "Amphora", "#FF3F425A": "Amphystine", "#FF7D9D72": "Amulet Variant", "#FF01748E": "Amulet Gem", "#FF69045F": "Amygdala Purple", "#FF94568C": "\u00c0n Z\u01d0 Purple", "#FF00BB44": "Anaheim Pepper", "#FF8CCEEA": "Anakiwa Variant", "#FFBFB6A7": "Analytical Grey", "#FFDB304A": "Anarchist", "#FFDE0300": "Anarchy", "#FFD0C1C3": "Ancestral", "#FFDDCDA6": "Ancestral Gold", "#FFD8C7C2": "Ancestral Haze", "#FFD0D0D0": "Ancestral Water", "#FF9E90A7": "Ancestry Violet", "#FF7A5145": "Ancho Pepper", "#FF596062": "Anchor Grey", "#FF435D8B": "Anchor Point", "#FF2C3641": "Anchorman", "#FF9EBBCD": "Anchors Away", "#FF2B3441": "Anchors Aweigh", "#FF756F6B": "Anchovy", "#FFEFEEDC": "Ancient", "#FF73754C": "Ancient Bonsai", "#FFAA6611": "Ancient Brandy", "#FF9C5221": "Ancient Bronze", "#FF624147": "Ancient Burgundy", "#FF99522B": "Ancient Chest", "#FF9F543E": "Ancient Copper", "#FFDCC9A8": "Ancient Doeskin", "#FF746550": "Ancient Earth", "#FFBB9A71": "Ancient Fresco", "#FFA44769": "Ancient Fuchsia", "#FF73FDFF": "Ancient Ice", "#FFE3AF8E": "Ancient Inca", "#FFF1E6D1": "Ancient Ivory", "#FFD6D8CD": "Ancient Kingdom", "#FF837C6F": "Ancient Labyrinth", "#FF953D55": "Ancient Magenta", "#FFD1CCB9": "Ancient Marble", "#FF959651": "Ancient Maze", "#FF895B8A": "Ancient Murasaki Purple", "#FF6A5536": "Ancient Olive", "#FFDDD4CE": "Ancient Pages", "#FF898D91": "Ancient Pewter", "#FF444B43": "Ancient Pine", "#FF774411": "Ancient Planks", "#FF5A3D3F": "Ancient Prunus", "#FF922A31": "Ancient Red", "#FFCCC1AB": "Ancient Relic", "#FF70553D": "Ancient Root", "#FF843F5B": "Ancient Royal Banner", "#FFE0CAC0": "Ancient Ruins", "#FFF0E4D1": "Ancient Scroll", "#FF83696E": "Ancient Shelter", "#FF765640": "Ancient Spice", "#FFDED8D4": "Ancient Stone", "#FFE7D082": "Ancient Treasure", "#FFA09083": "Ancient Wonder", "#FFEECD00": "Ancient Yellow", "#FFAFCDC7": "Andean Opal Green", "#FF90B19D": "Andean Slate", "#FFC1A097": "Andes Ash", "#FF78D8D9": "Andes Sky", "#FF424036": "Andiron", "#FF633737": "Andorra", "#FFB58338": "Andouille", "#FFFAF0D3": "Andover Cream", "#FFA4C639": "Android Green", "#FFABCDEE": "Andromeda Blue", "#FF882D4C": "Anemone", "#FFF9EFE4": "Anemone White", "#FFBEB6AB": "Anew Grey", "#FFAFA8AE": "Angel Aura", "#FF83C5CD": "Angel Blue", "#FFDCAF9F": "Angel Breath", "#FFA3BDD3": "Angel Falls", "#FFB8ACB4": "Angel Finger", "#FFF0E8D9": "Angel Food", "#FFD7A14F": "Angel Food Cake", "#FFD2D6DB": "Angel Hair Silver", "#FFA17791": "Angel Heart", "#FFBBC6D9": "Angel in Blue Jeans", "#FFCEC7DC": "Angel Kiss", "#FFC6F0E7": "Angel of Death Victorious", "#FFE19640": "Angel Shark", "#FF9BC2D7": "Angel Sol", "#FFDDDFD8": "Angel Touch", "#FFF3DFD7": "Angel Wing", "#FFF3F1E6": "Angel\u2019s Feather", "#FFE2D9D3": "Angel\u2019s Sigh", "#FFF6DD34": "Angel\u2019s Trumpet", "#FFDBDFD4": "Angel\u2019s Whisper", "#FFF2DCD7": "Angelic", "#FFBBC6D6": "Angelic Blue", "#FFE9D9DC": "Angelic Choir", "#FFEECC33": "Angelic Descent", "#FFF4D9CB": "Angelic Pink", "#FFE3DFEA": "Angelic Sent", "#FFEBE9D8": "Angelic Starlet", "#FFF4EDE4": "Angelic White", "#FFF4EFEE": "Angelic Wings", "#FFF4DFA7": "Angelic Yellow", "#FFEACFC2": "Angelico", "#FFD8DEE7": "Ang\u00e9lique Grey", "#FFDD0055": "Anger", "#FFDACAB1": "Angora", "#FFEDE7DE": "Angora Goat", "#FFEBDFEA": "Angora Pink", "#FFD9D6C3": "Angora Whisper", "#FFF4F6EC": "Angraecum Orchid", "#FFF04E45": "Angry Flamingo", "#FF9799A6": "Angry Gargoyle", "#FFEEBBBB": "Angry Ghost", "#FF37503D": "Angry Gremlin", "#FFEE9911": "Angry Hornet", "#FF4E6665": "Angry Ocean", "#FFFFCC55": "Angry Pasta", "#FFD82029": "Angry Tomato", "#FFB9ABAD": "Aniline Mauve", "#FFF4E6CE": "Animal Cracker", "#FFBCC09E": "Animal Kingdom", "#FFED9080": "Animated Coral", "#FFCCC14D": "Anime", "#FFFF7A83": "Anime Blush", "#FFE5D5AE": "Anise Biscotti", "#FFF4E3B5": "Anise Flower", "#FFB0AC98": "Anise Grey Yellow", "#FFCDA741": "Aniseed", "#FF8CB684": "Aniseed Leaf Green", "#FF91A0B7": "Anita", "#FFCDCA9F": "Anjou Pear", "#FFF5D547": "Anna Banana", "#FF8C5341": "Annatto", "#FF6B475D": "Annis", "#FFE17861": "Annular", "#FF89A4CD": "Anode", "#FFBDBFC8": "Anon", "#FFDADCD3": "Anonymous", "#FFC7BBA4": "Another One Bites the Dust", "#FF016884": "Ansel", "#FFB05D4A": "Ant Red", "#FF4B789B": "Antarctic Blue", "#FF0000BB": "Antarctic Circle", "#FF35383F": "Antarctic Deep", "#FFEDDEE6": "Antarctic Love", "#FFBFD2D0": "Antarctica", "#FFB19664": "Antelope", "#FF7F684E": "Anthill", "#FF28282D": "Anthracite", "#FF3D475E": "Anthracite Blue", "#FF373F42": "Anthracite Grey", "#FF73293B": "Anthracite Red", "#FFBEBDBC": "Anti Rainbow Grey", "#FF256D73": "Antigua", "#FF06B1C4": "Antigua Blue", "#FF83C2CD": "Antigua Sand", "#FFFFE7C8": "Antigua Sunrise", "#FFBDDFD8": "Antiguan", "#FF3B5E8D": "Antilles Blue", "#FF8AA277": "Antilles Garden", "#FFC7C8C1": "Antimony", "#FF946644": "Antiquarian Brown", "#FFBA8A45": "Antiquarian Gold", "#FF8D8AA0": "Antiquate", "#FF8B846D": "Antique", "#FF9C867B": "Antique Bear", "#FF926B43": "Antique Bourbon", "#FF6C461F": "Antique Brass Variant", "#FF553F2D": "Antique Brown", "#FFE4D2BB": "Antique Buff", "#FF352126": "Antique Burgundy", "#FFF0BAA4": "Antique Cameo", "#FFF4E1D6": "Antique Candle Light", "#FFA7856D": "Antique Chest", "#FFFDF6E7": "Antique China", "#FFB5B8A8": "Antique Coin", "#FF9E6649": "Antique Copper", "#FFFFC7B0": "Antique Coral", "#FF7E6C5F": "Antique Earth", "#FF8E5E5E": "Antique Garnet", "#FFB59E5F": "Antique Gold", "#FF2C6E62": "Antique Green", "#FF69576D": "Antique Grey", "#FFCDBACB": "Antique Heather", "#FFB39355": "Antique Honey", "#FFB07F9E": "Antique Hot Pink", "#FF7B7062": "Antique Iron", "#FFF9ECD3": "Antique Ivory", "#FFC5BBA8": "Antique Kilim", "#FFFDF2DB": "Antique Lace", "#FF9E8E7E": "Antique Leather", "#FFFAEEDB": "Antique Linen", "#FFF1E9D7": "Antique Marble", "#FFBBB0B1": "Antique Mauve", "#FF7A973B": "Antique Moss", "#FFF4F0E8": "Antique Paper", "#FFEAD8C1": "Antique Parchment", "#FFBAC5BB": "Antique Patina", "#FFEBD7CB": "Antique Pearl", "#FF957747": "Antique Penny", "#FFE8E3E3": "Antique Petal", "#FFC27A74": "Antique Pink", "#FF98211A": "Antique Port Wine", "#FF7D4F51": "Antique Red", "#FF997165": "Antique Rose", "#FF72393F": "Antique Rosewood", "#FF978466": "Antique Royal Gold", "#FF918E8C": "Antique Silver", "#FF6E7173": "Antique Tin", "#FFBB9973": "Antique Treasure", "#FF004E4E": "Antique Turquoise", "#FF928BA6": "Antique Viola", "#FFECE6D5": "Antique White Variant", "#FFF3D3A1": "Antique Wicker Basket", "#FFB6A38D": "Antique Windmill", "#FFBDCCC1": "Antiqued Aqua", "#FF8A6C57": "Antiquities", "#FFC1A87C": "Antiquity", "#FF957A76": "Antler", "#FF864F3E": "Antler Moth", "#FFC0AD96": "Antler Velvet", "#FFB09391": "Antoinette", "#FFE7C2B4": "Antoinette Pink", "#FF312231": "Anubis Black", "#FFC68E3F": "Anzac Variant", "#FF00800C": "Ao", "#FF27B692": "Aoife\u2019s Green", "#FF006442": "Aotake Bamboo", "#FF31827B": "Apatite Blue", "#FFBBFF99": "Apatite Crystal Green", "#FF8A843B": "Apeland", "#FFE35A63": "Aphrodisiac", "#FF45E9C1": "Aphrodite Aqua", "#FFEEFFFF": "Aphrodite\u2019s Pearls", "#FFDD14AB": "Aphroditean Fuchsia", "#FFB5D0A2": "Apium", "#FF284FBD": "Apnea Dive", "#FFF4711E": "Apocalyptic Orange", "#FF99CCFF": "Apocyan", "#FFC8C4C2": "Apollo", "#FF748697": "Apollo Bay", "#FFE5E5E1": "Apollo Landing", "#FFDDFFFF": "Apollo\u2019s White", "#FFC6D6C4": "Apothecary Jar", "#FF848B80": "Appalachian Forest", "#FFCFB989": "Appalachian Trail", "#FF876E52": "Appaloosa Spots", "#FFC2BCA9": "Apparition", "#FF66AA00": "Appetising Asparagus", "#FFB1E5AA": "Appetite", "#FF858C9B": "Applause Please", "#FFDDBCA0": "Apple Blossom Variant", "#FFD5E69D": "Apple Bob", "#FF9C6757": "Apple Brown Betty", "#FF8E5151": "Apple Butter", "#FFF81404": "Apple Cherry", "#FFDA995F": "Apple Cider", "#FFA67950": "Apple Cinnamon", "#FFF4EED8": "Apple Core", "#FFB8D7A6": "Apple Cream", "#FFE19C55": "Apple Crisp", "#FFFEE5C9": "Apple Crunch", "#FFDBDBBC": "Apple Cucumber", "#FFFDDFAE": "Apple Custard", "#FFEDF4EB": "Apple Flower", "#FFCC9350": "Apple Fritter", "#FF4B4247": "Apple Herb Black", "#FFA69F8D": "Apple Hill", "#FFBDD0B1": "Apple Ice", "#FFBFCA87": "Apple II Beige", "#FF93D6BF": "Apple II Blue", "#FFDA680E": "Apple II Chocolate", "#FF04650D": "Apple II Green", "#FF25C40D": "Apple II Lime", "#FFDC41F1": "Apple II Magenta", "#FFAC667B": "Apple II Rose", "#FFDDAABB": "Apple Infusion", "#FF8B974E": "Apple Jack", "#FFBDAD13": "Apple Jade", "#FFF9FDD9": "Apple Martini", "#FF93C96A": "Apple Orchard", "#FFCAAB94": "Apple Pie", "#FF883E3F": "Apple Polish", "#FFF4EBD2": "Apple Sauce", "#FFC2A377": "Apple Sauce Cake", "#FFA77C53": "Apple Seed", "#FFF1F0BF": "Apple Slice", "#FFE8C194": "Apple Turnover", "#FFEA8386": "Apple Valley", "#FFB59F62": "Apple Wine", "#FF903F45": "Apple-a-Day", "#FF8AC479": "Applegate", "#FFCDEACD": "Applemint", "#FFF3F5E9": "Applemint Soda", "#FF929637": "Appletini", "#FF6EB478": "Appleton", "#FF8B97A5": "Approaching Dusk", "#FF039487": "Approval Green", "#FFCED5E4": "Apr\u00e8s-Ski", "#FFFFB16D": "Apricot Variant", "#FFFEC382": "Apricot Appeal", "#FFFEAEA5": "Apricot Blush", "#FFBF6553": "Apricot Brandy", "#FFCC7E5B": "Apricot Brown", "#FFCD7E4D": "Apricot Buff", "#FFFFC782": "Apricot Butter", "#FFDA8923": "Apricot Chicken", "#FFF1BD89": "Apricot Cream", "#FFFFBB80": "Apricot Flower", "#FFEEDED8": "Apricot Foam", "#FFFFD2A0": "Apricot Fool", "#FFF3CFB7": "Apricot Freeze", "#FFFFC7A0": "Apricot Froth", "#FFF5D7AF": "Apricot Gelato", "#FFEEAA22": "Apricot Glazed Chicken", "#FFFFCE79": "Apricot Glow", "#FFFFAAAA": "Apricot Haze", "#FFFFF6E9": "Apricot Ice", "#FFF8CC9C": "Apricot Ice Cream", "#FFFBBE99": "Apricot Iced Tea", "#FFE2C3A6": "Apricot Illusion", "#FFEEA771": "Apricot Jam", "#FFFECFB5": "Apricot Lily", "#FFB47756": "Apricot Mix", "#FFC37248": "Apricot Mocha", "#FFFCDFAF": "Apricot Mousse", "#FFECAA79": "Apricot Nectar", "#FFF8C4B4": "Apricot Obsession", "#FFC86B3C": "Apricot Orange", "#FFEEB192": "Apricot Preserves", "#FFE8917D": "Apricot Red", "#FFFBCD9F": "Apricot Sherbet", "#FFE8A760": "Apricot Sorbet", "#FFF1C095": "Apricot Souffl\u00e9", "#FFF1B393": "Apricot Spring", "#FFDA8C53": "Apricot Tan", "#FFFBA57D": "Apricot Wash", "#FFFAE0C2": "Apricot Whisper", "#FFF7F0DB": "Apricot White Variant", "#FFF7BD81": "Apricot Yellow", "#FFD8A48F": "Apricotta", "#FFF6D0D8": "April Blush", "#FF1FB57A": "April Fool\u2019s Red", "#FFA9B062": "April Green", "#FF909245": "April Hills", "#FF8B3D2F": "April Love", "#FFCCD9C9": "April Mist", "#FF9BADA8": "April Rain", "#FFDADEB5": "April Showers", "#FFFBE198": "April Sunshine", "#FFB4CBD4": "April Tears", "#FFB1C1B8": "April Thicket", "#FFC5CFB1": "April Wedding", "#FFD5E2E5": "April Winds", "#FF0FF0FE": "Aqua", "#FFB5DFC9": "Aqua Bay", "#FF7ACAD0": "Aqua Belt", "#FF96D3D8": "Aqua Bloom", "#FF79B6BC": "Aqua Blue", "#FFD8E8E4": "Aqua Breeze", "#FF01F1F1": "Aqua Cyan", "#FF3896A7": "Aqua Dance", "#FFB7C9B5": "Aqua Dream", "#FF85C7A6": "Aqua Eden", "#FF038E85": "Aqua Experience", "#FF96E2E1": "Aqua Fiesta", "#FFA1BAAA": "Aqua Foam", "#FF4A9FA3": "Aqua Fresco", "#FFA9D1D7": "Aqua Frost", "#FFD2E8E0": "Aqua Glass", "#FF9DD5C8": "Aqua Glow", "#FF12E193": "Aqua Green", "#FF889FA5": "Aqua Grey", "#FFD9DDD5": "Aqua Haze Variant", "#FF30949D": "Aqua Lake", "#FFA0C9CB": "Aqua Mist", "#FF08787F": "Aqua Nation", "#FFBCE8DD": "Aqua Oasis", "#FF05696B": "Aqua Obscura", "#FFDDF2EE": "Aqua Pura", "#FF63A39C": "Aqua Rapids", "#FF539F91": "Aqua Revival", "#FF61A1A9": "Aqua Sea", "#FF70BBBF": "Aqua Sky", "#FF8C9FA0": "Aqua Smoke", "#FFD3E4E6": "Aqua Sparkle", "#FF85CED1": "Aqua Splash", "#FFA5DDDB": "Aqua Spray", "#FFE8F3E8": "Aqua Spring Variant", "#FFDBE4DC": "Aqua Squeeze Variant", "#FFE5F1EE": "Aqua Tint", "#FF00A29E": "Aqua Velvet", "#FF56B3C3": "Aqua Verde", "#FF7BBDC7": "Aqua Vitale", "#FF00937D": "Aqua Waters", "#FFBFDFDF": "Aqua Whisper", "#FFA0E3D1": "Aqua Wish", "#FF7CD8D6": "Aqua Zing", "#FF9CB0B3": "Aqua-Sphere", "#FFE1F0EA": "Aquacade", "#FF006F49": "Aquadazzle", "#FF7B9F82": "Aquadulce", "#FFE3ECED": "Aquafir", "#FF57B7C5": "Aqualogic", "#FF2EE8BB": "Aquamarine Variant", "#FFB3C4BA": "Aquamarine Dream", "#FF82CDAD": "Aquamarine Ocean", "#FF00A800": "Aquamentus Green", "#FF61AAB1": "Aquarelle", "#FFBFE0E4": "Aquarelle Blue", "#FFE2F4E4": "Aquarelle Green", "#FFEDC8FF": "Aquarelle Lilac", "#FFDBF4D8": "Aquarelle Mint", "#FFFBE8E0": "Aquarelle Orange", "#FFFBE9DE": "Aquarelle Pink", "#FFD8E1F1": "Aquarelle Purple", "#FFFEDDDD": "Aquarelle Red", "#FFBCE4EB": "Aquarelle Sky", "#FFF4EEDA": "Aquarelle Yellow", "#FF356B6F": "Aquarium", "#FF0A98AC": "Aquarium Diver", "#FF2DB0CE": "Aquarius", "#FF4D5AF3": "Aquarius Mood Indigo", "#FF559999": "Aquarius Reef Base", "#FF89C6B7": "Aquastone", "#FF99C1CC": "Aquatic", "#FF41A0B4": "Aquatic Cool", "#FFBFD6D1": "Aquatic Edge", "#FF49999A": "Aquatic Green", "#FFADE6DB": "Aquatic Mist", "#FFA6B5A9": "Aquatone", "#FF60B3BC": "Aqueduct", "#FF59B6D9": "Aquella", "#FF388D95": "Aqueous", "#FFE2ECED": "Aquifer", "#FF88ABB4": "Aquitaine", "#FFADAD9C": "Ara Glen", "#FF82ACC4": "Arabella", "#FFCD5F42": "Arabesque", "#FFCD9945": "Arabian Bake", "#FF016C84": "Arabian Nights", "#FFA14C3F": "Arabian Red", "#FFDDC6B1": "Arabian Sands", "#FF786E97": "Arabian Silk", "#FF934C36": "Arabian Spice", "#FFC9FFFA": "Arabian Veil", "#FF6F4D3F": "Arabic Coffee", "#FFC0FFEE": "Arabica Mint", "#FF7A552E": "Arable Brown", "#FFB06455": "Aragon", "#FF47BA87": "Aragon Green", "#FFE4E0D4": "Aragonite", "#FF6A95B1": "Aragonite Blue", "#FF948E96": "Aragonite Grey", "#FFF3F1F3": "Aragonite White", "#FFEC8254": "Araigaki Orange", "#FF3F4635": "Arame Seaweed Green", "#FFFF7013": "Arancio", "#FF274A5D": "Arapawa Variant", "#FF93A344": "Arathi Highlands", "#FFADD8E1": "Araucana Egg", "#FFA18D71": "Arava", "#FFCDA182": "Arbol de Tamarindo", "#FFBBC3AD": "Arbor Vitae", "#FF70BA9F": "Arboretum", "#FFC1C2B4": "Arbour Hollow", "#FFCCDDFF": "Arc Light", "#FFEE3311": "Arcade Fire", "#FF0022CC": "Arcade Glow", "#FFEDEBE2": "Arcade White", "#FF00AC8D": "Arcadia", "#FFA3C893": "Arcadian Green", "#FF3B6C3F": "Arcala Green", "#FF98687E": "Arcane", "#FF5260E1": "Arcane Brew", "#FF6A2F2F": "Arcane Red", "#FF6A0002": "Arcavia Red", "#FF8E785C": "Archaeological Site", "#FF6E6A5E": "Archaeology", "#FFCAC69D": "Archisa", "#FF6F6D5F": "Architect", "#FF7195A6": "Architecture Blue", "#FF6B6A69": "Architecture Grey", "#FF9F8C73": "Archivist", "#FF648589": "Arctic", "#FFCBD8E5": "Arctic Air", "#FFD9E5EB": "Arctic Blizzard", "#FF95D6DC": "Arctic Blue", "#FFB2CCD9": "Arctic Circle", "#FFE6E3DF": "Arctic Cotton", "#FFEBE4BE": "Arctic Daisy", "#FFE3E5E8": "Arctic Dawn", "#FF816678": "Arctic Dusk", "#FFAFBEC1": "Arctic Feelings", "#FFDAEAE4": "Arctic Flow", "#FFE7E7E2": "Arctic Fox", "#FFC9D1E9": "Arctic Glow", "#FF45BCB3": "Arctic Green", "#FFBBCCDD": "Arctic Grey", "#FFB6BDD0": "Arctic Ice", "#FF6F7872": "Arctic Lichen Green", "#FF345C61": "Arctic Nights", "#FF66C3D0": "Arctic Ocean", "#FFB8DFF8": "Arctic Paradise", "#FFC7DAED": "Arctic Rain", "#FFB7ABB0": "Arctic Rose", "#FF9BBACB": "Arctic Sunrise", "#FF6D584C": "Arctic Tundra", "#FF00FCFC": "Arctic Water", "#FFE9EAE7": "Arctic White", "#FFE2DEDF": "Ardcoat", "#FFE5756A": "Ardent Coral", "#FF232F2C": "Ard\u00f3sia", "#FFDD2200": "Ares Red", "#FF62584C": "Ares Shadow", "#FF9D6646": "Argan Oil", "#FF888888": "Argent", "#FFCECAC3": "Argento", "#FFBDBDB7": "Argos", "#FF348A5D": "Argyle", "#FF895C79": "Argyle Purple", "#FFC48677": "Argyle Rose", "#FFE3E4E2": "Aria", "#FFF9E8D8": "Aria Ivory", "#FFDCD6C6": "Arid Landscape", "#FFB6B4A9": "Arid Plains", "#FFAED7EA": "Ariel", "#FFB2A5D3": "Ariel\u2019s Delight", "#FFFAF0DF": "Aristocrat Ivory", "#FFECCEB9": "Aristocrat Peach", "#FF457E93": "Aristocratic", "#FF354655": "Aristocratic Blue", "#FFDDAACC": "Aristocratic Pink", "#FF980B4A": "Aristocratic Velvet", "#FFEEB377": "Arizona", "#FFAD735A": "Arizona Clay", "#FFDDAB93": "Arizona Dust", "#FF00655A": "Arizona Stone", "#FFEBBCB9": "Arizona Sunrise", "#FF669264": "Arizona Tree Frog", "#FFE8DAC3": "Arizona White", "#FF9F7B35": "Arlington Bronze", "#FF536762": "Armada", "#FF484A46": "Armadillo Variant", "#FF7D4638": "Armadillo Egg", "#FF926A25": "Armageddon Dunes", "#FFD3A907": "Armageddon Dust", "#FFAD916C": "Armagnac", "#FF74857F": "Armour", "#FF030303": "Armour Wash", "#FF747769": "Armoured Steel", "#FF6A6B65": "Armoury", "#FF5B6F61": "Army Canvas", "#FF6C7735": "Army Golf", "#FF8A806B": "Army Issue", "#FF838254": "Army Issue Green", "#FFBF8F37": "Arnica", "#FFE59B00": "Arnica Yellow", "#FFD3C1C5": "Aroma", "#FF96D2D6": "Aroma Blue", "#FFA1C4A8": "Aroma Garden", "#FFF9970C": "Aromango", "#FF706986": "Aromatic", "#FFFFCECB": "Aromatic Breeze", "#FF98C945": "Aromatic Herbs", "#FFF2FF26": "Aromatic Lemon", "#FF879BA3": "Arona", "#FFA1B670": "Around the Gills", "#FF776600": "Arousing Alligator", "#FF5C546E": "Arraign", "#FFBB8246": "Arrakis Spice", "#FF5A3532": "Arresting Auburn", "#FF927257": "Arrow Creek", "#FFC7A998": "Arrow Quiver", "#FFA28440": "Arrow Rock", "#FF5C503A": "Arrow Shaft", "#FF514B40": "Arrowhead", "#FF58728A": "Arrowhead Lake", "#FFF9EAEB": "Arrowhead White", "#FFF8DECF": "Arrowroot", "#FFE4DECF": "Arrowroote", "#FF827A67": "Arrowtown Variant", "#FFB3861E": "Arrowwood", "#FF896956": "Art and Craft", "#FFCDACA0": "Art Deco Pink", "#FF623745": "Art Deco Red", "#FF94897C": "Art District", "#FFC06F70": "Art House Pink", "#FFA29AA0": "Art Nouveau Glass", "#FF9C932F": "Art Nouveau Green", "#FFA08994": "Art Nouveau Violet", "#FFCA9D8D": "Artefact", "#FF65A98F": "Artemesia Green", "#FFD2A96E": "Artemis", "#FFDDDDEE": "Artemis Silver", "#FFE3EBEA": "Artemisia", "#FF711518": "Arterial", "#FFA6BEE1": "Artesian Pool", "#FF007DB6": "Artesian Water", "#FF5EB2AA": "Artesian Well", "#FFA8B1AD": "Artful", "#FF91B4B3": "Artful Aqua", "#FF80505D": "Artful Magenta", "#FFCC6C82": "Artful Pink", "#FF8F9779": "Artichoke", "#FFA19676": "Artichoke Dip", "#FF517345": "Artichoke Green", "#FFE4D588": "Artichoke Heart", "#FFC19AA5": "Artichoke Mauve", "#FFD9CA93": "Artichoke Mist", "#FFE6E2D3": "Artifice", "#FFA1A1A1": "Artificial Intelligence Grey", "#FF41B45C": "Artificial Turf", "#FF746F67": "Artillery", "#FF8F5C45": "Artisan", "#FFAFBFC4": "Artisan Blue", "#FFB99779": "Artisan Crafts", "#FF117471": "Artisan Green", "#FFAC5B50": "Artisan Red", "#FFB09879": "Artisan Tan", "#FFDAC2AF": "Artisan Tea", "#FF845E40": "Artisan Tile", "#FFF2AB46": "Artisans Gold", "#FF01343A": "Artist Blue", "#FFEEE4D2": "Artist\u2019s Canvas", "#FF37393E": "Artist\u2019s Charcoal", "#FFA1969B": "Artist\u2019s Shadow", "#FF987387": "Artiste", "#FF434053": "Artistic Licence", "#FF5C6B65": "Artistic Stone", "#FFC3B1AC": "Artistic Taupe", "#FFD0D2E9": "Artistic Violet", "#FFF5C68B": "Arts & Crafts Gold", "#FF7D6549": "Arts and Crafts", "#FFCCA537": "Aru Ressha", "#FFD1DED3": "Aruba Aqua", "#FF7DD4D6": "Aruba Blue", "#FF54B490": "Aruba Green", "#FF75AD5B": "Arugula", "#FF48929B": "Asagi Blue", "#FF455559": "Asagi Koi", "#FFF7BB7D": "Asagi Yellow", "#FFFCEF01": "Asfar Yellow", "#FFBEBAA7": "Ash Variant", "#FFD7BEA5": "Ash Blonde", "#FF98623C": "Ash Brown", "#FFE8D3D1": "Ash Cherry Blossom", "#FF8C6F54": "Ash Gold", "#FFC1B5A9": "Ash Grey Variant", "#FFB9B3BF": "Ash Grove", "#FFA88E8B": "Ash Hollow", "#FFD9DDE5": "Ash in the Air", "#FF737486": "Ash Mauve", "#FF998E91": "Ash Pink", "#FFE8D3C7": "Ash Plum", "#FFB5817D": "Ash Rose", "#FFAABB99": "Ash Tree", "#FFCECFD6": "Ash Tree Bark", "#FF9695A4": "Ash Violet", "#FFE9E4D4": "Ash White", "#FFF0BD7E": "Ash Yellow", "#FFB495A4": "Ashberry", "#FFC9BFB2": "Ashen", "#FF994444": "Ashen Brown", "#FFB2A79D": "Ashen Grey", "#FF9B9092": "Ashen Plum", "#FFD3CABF": "Ashen Tan", "#FF8C7A7B": "Ashen Violet", "#FF646B7A": "Ashen Whisper", "#FF94A9B7": "Ashen Wind", "#FF104071": "Ashenvale Nights", "#FF45575E": "Asher Benjamin", "#FFB8B5AD": "Ashes", "#FFBBB3A2": "Ashes Variant", "#FFA3A1A5": "Ashland Heights", "#FF8398A9": "Ashley Blue", "#FFD2D9DA": "Ashlin Grey", "#FFA7A49F": "Ashlite", "#FF4A79BA": "Ashton Blue", "#FF7B8EB0": "Ashton Skies", "#FFEDD6AE": "Ashwood", "#FFDBD4C3": "Asiago", "#FFECE0CD": "Asian Fusion", "#FFE8E0CD": "Asian Ivory", "#FFD4B78F": "Asian Jute", "#FFAE9156": "Asian Pear", "#FF118822": "Asian Spice", "#FF8B818C": "Asian Violet", "#FFB8B0A5": "Ask Me Anything", "#FF88DDBB": "\u0100sm\u0101n\u012b Sky", "#FF70B2CC": "Aspara", "#FF77AB56": "Asparagus Variant", "#FF96AF54": "Asparagus Cream", "#FFB9CB5A": "Asparagus Fern", "#FFD2CBB4": "Asparagus Green", "#FF576F44": "Asparagus Sprig", "#FFDAC98E": "Asparagus Yellow", "#FF83A494": "Aspen Aura", "#FFC6BCAD": "Aspen Branch", "#FFFFD662": "Aspen Gold", "#FF72926B": "Aspen Green", "#FFA39B92": "Aspen Grey", "#FF6A8D88": "Aspen Hush", "#FFCFD7CB": "Aspen Mist", "#FFF0F0E7": "Aspen Snow", "#FF687F7A": "Aspen Valley", "#FFEDF1E3": "Aspen Whisper", "#FFF6DF9F": "Aspen Yellow", "#FF474C55": "Asphalt Blue", "#FF5E5E5D": "Asphalt Grey", "#FFA08A80": "Aspiration", "#FFA2C1C0": "Aspiring Blue", "#FF2D4F83": "Assassin", "#FFF60206": "Assassin\u2019s Red", "#FFE1D0B2": "Assateague Sand", "#FF1C4374": "Assault", "#FF867BA9": "Aster", "#FF9BACD8": "Aster Flower Blue", "#FFD4DAE2": "Aster Petal", "#FF8881B0": "Aster Purple", "#FF8F629A": "Aster Violetta", "#FFDD482B": "Astorath Red", "#FF7E7565": "Astoria Grey", "#FFEDD5A6": "Astra Variant", "#FF376F89": "Astral Variant", "#FF363151": "Astral Aura", "#FF203943": "Astral Nomad", "#FF8EC2E7": "Astral Spirit", "#FF77FF77": "Astro Arcade Green", "#FF899FB9": "Astro Bound", "#FF5383C3": "Astro Nautico", "#FF6D5ACF": "Astro Purple", "#FF937874": "Astro Sunset", "#FF797EB5": "Astro Zinger", "#FF757679": "Astrogranite", "#FF3B424C": "Astrogranite Debris", "#FF2D96CE": "Astrolabe Reef", "#FF445172": "Astronaut Variant", "#FF214559": "Astronaut Blue Variant", "#FF474B4A": "Astronomical", "#FF6B7C85": "Astronomicon Grey", "#FFAFB4B6": "Astroscopus Grey", "#FF67A159": "Astroturf", "#FF273E51": "Asurmen Blue Wash", "#FF17181C": "Aswad Black", "#FFE7EEE1": "At Ease", "#FF9E9985": "At Ease Soldier", "#FFE7D9B9": "At the Beach", "#FFA3ABB8": "Atelier", "#FF003A6C": "Ateneo Blue", "#FFDCD7CC": "Athena", "#FF66DDFF": "Athena Blue", "#FFE9B4C3": "Athena Pink", "#FF92A18A": "Athenian Green", "#FF3F74B1": "Athens", "#FFDCDDDD": "Athens Grey", "#FF6D8E44": "Athonian Camoshade", "#FFD5CBB2": "Aths Special Variant", "#FFFF7E02": "Ati-Ati Amber", "#FF008997": "Atlantic Blue", "#FFCBE1EE": "Atlantic Breeze", "#FF2B2F41": "Atlantic Charter", "#FF294F58": "Atlantic Deep", "#FF001166": "Atlantic Depths", "#FFD7CEB9": "Atlantic Fig Snail", "#FFA5B4AC": "Atlantic Foam", "#FF4B8EB0": "Atlantic Gull", "#FF00629A": "Atlantic Mystique", "#FF13336F": "Atlantic Navy", "#FFA7D8E4": "Atlantic Ocean", "#FFDCD5D2": "Atlantic Sand", "#FF3B5F83": "Atlantic Schooner", "#FF708189": "Atlantic Shoreline", "#FF3E586E": "Atlantic Tide", "#FFB598C3": "Atlantic Tulip", "#FF264243": "Atlantic Waves", "#FF336172": "Atlantis Variant", "#FF006477": "Atlantis Myth", "#FF5CA0A7": "Atlas Cedar", "#FF667A6E": "Atlas Cedar Green", "#FF82193A": "Atlas Red", "#FFEDE5CF": "Atlas White", "#FF0099DD": "Atmosphere", "#FF899697": "Atmospheric", "#FFC2D0E1": "Atmospheric Pressure", "#FFACE1F0": "Atmospheric Soft Blue", "#FF2B797A": "Atoll Variant", "#FFFFCF9E": "Atoll Sand", "#FF8F9CAC": "Atom Blue", "#FF3D4B52": "Atomic", "#FF0097C3": "Atomic Blue", "#FFB9FF03": "Atomic Lime", "#FFF88605": "Atomic Orange", "#FFFB7EFD": "Atomic Pink", "#FFF1EEE4": "Atrium White", "#FF994240": "Attar of Rose", "#FFCCBCA9": "Attic Linen", "#FFA1BCA9": "Attica", "#FFA48884": "Attitude", "#FF7C7D75": "Attitude Grey", "#FF3F4258": "Attorney", "#FF9E6759": "Au Chico Variant", "#FFF4F2E2": "Au Clair de la Lune", "#FFF6A694": "Au Contraire", "#FFFF9D45": "Au Gratin", "#FFB99D83": "Au Lait Ole", "#FFE5E1CE": "Au Natural", "#FFE8CAC0": "Au Naturel", "#FF3F3130": "Auberge", "#FF372528": "Aubergine Variant", "#FFF2E4DD": "Aubergine Flesh", "#FF8B762C": "Aubergine Green", "#FF6E5861": "Aubergine Grey", "#FF3B2741": "Aubergine Mauve", "#FF5500AA": "Aubergine Perl", "#FF712F2C": "Auburn Variant", "#FFB58271": "Auburn Glaze", "#FF78342F": "Auburn Lights", "#FFD8A394": "Auburn Wave", "#FFB5ACB7": "Audition", "#FFAE8087": "Audrey\u2019s Blush", "#FF9F9292": "Auger Shell", "#FFE6E1D6": "August Moon", "#FFFFD79D": "August Morning", "#FF90AA0B": "Augustus Asparagus", "#FF7C7469": "Aumbry", "#FF7C0087": "Aunt Violet", "#FFB2A8A1": "Aura", "#FFDEE2E4": "Aura White", "#FFC48919": "Auric", "#FFE8BC6D": "Auric Armour Gold", "#FFAA6A44": "Auric Copper", "#FF32FFDC": "Aurichalcite", "#FF533552": "Auricula Purple", "#FFEBD147": "Aurora", "#FF556B87": "Aurora Borealis", "#FF6ADC99": "Aurora Green", "#FFD3C5C4": "Aurora Grey", "#FF963B60": "Aurora Magenta", "#FFEC7042": "Aurora Orange", "#FFE881A6": "Aurora Pink", "#FFC13435": "Aurora Red", "#FF595682": "Aurora Splendour", "#FF72B2AF": "Aurora Teal", "#FF438273": "Aussie Surf", "#FF726848": "Austere", "#FFBEBFB2": "Austere Grey", "#FFF4C4A5": "Australian Apricot", "#FF84A194": "Australian Jade", "#FFEFF8AA": "Australian Mint Variant", "#FFCC9911": "Australien", "#FFE7B53B": "Australium Gold", "#FFDEE6E7": "Austrian Ice", "#FF6B5446": "Authentic Brown", "#FFEADDC6": "Authentic Tan", "#FFC38743": "Automn Fox", "#FFC6C7C5": "Autonomous", "#FFAF865B": "Autumn", "#FFD2A888": "Autumn Air", "#FFCDA449": "Autumn Apple Yellow", "#FFF9986F": "Autumn Arrival", "#FF816B68": "Autumn Ashes", "#FFE3AD59": "Autumn Avenue", "#FF9D6F46": "Autumn Bark", "#FFD9922E": "Autumn Blaze", "#FFECCCA6": "Autumn Blonde", "#FFFFE0CB": "Autumn Bloom", "#FFFBE6C1": "Autumn Child", "#FF447744": "Autumn Crocodile", "#FFEA6F5B": "Autumn Enchantment", "#FF67423B": "Autumn Fall", "#FF507B49": "Autumn Fern", "#FFBE7D33": "Autumn Fest", "#FFA28B36": "Autumn Festival", "#FFC44E4F": "Autumn Fire", "#FFAFB8BA": "Autumn Fog", "#FFB3573F": "Autumn Glaze", "#FFE9874E": "Autumn Glimmer", "#FFFF8812": "Autumn Glory", "#FFE5C382": "Autumn Glow", "#FF7D623C": "Autumn Gold", "#FFE6AE76": "Autumn Gourd", "#FFB2ABA7": "Autumn Grey", "#FF827B53": "Autumn Grove", "#FFD4C2B1": "Autumn Haze", "#FFB9674E": "Autumn Ivy", "#FFE47227": "Autumn Landscape", "#FF9D8D66": "Autumn Laurel", "#FFB56A4C": "Autumn Leaf", "#FF7A560E": "Autumn Leaf Brown", "#FFD07A04": "Autumn Leaf Orange", "#FF623836": "Autumn Leaf Red", "#FF6E4440": "Autumn Leaves", "#FFCEA48E": "Autumn Malt", "#FFD26F16": "Autumn Maple", "#FFF7B486": "Autumn Mist", "#FF3B5861": "Autumn Night", "#FFEE9950": "Autumn Orange", "#FF9D9093": "Autumn Orchid", "#FF158078": "Autumn Pine Green", "#FF99451F": "Autumn Red", "#FF9B423F": "Autumn Ridge", "#FFC2452D": "Autumn Robin", "#FFA4746E": "Autumn Russet", "#FFAEA26E": "Autumn Sage", "#FFD19957": "Autumn Spice", "#FFFE9A50": "Autumn Splendour", "#FFF38554": "Autumn Sunset", "#FFAB662E": "Autumn Surprise", "#FFAE704F": "Autumn Umber", "#FFFAE2CF": "Autumn White", "#FFFBD1B6": "Autumn Wind", "#FFE99700": "Autumn Yellow", "#FFBA7A61": "Autumn\u2019s Hill", "#FFAD5928": "Autumnal", "#FF106B21": "Avagddu Green", "#FF799B96": "Avalon", "#FFFF77EE": "Avant-Garde Pink", "#FF576E6A": "Aventurine", "#FFD2C2B0": "Avenue Tan", "#FFC6E3E8": "Aviary Blue", "#FF7D6049": "Aviator", "#FFF4C69F": "Avid Apricot", "#FFC5B47F": "Aviva", "#FF568203": "Avocado Variant", "#FFB7BF6B": "Avocado Cream", "#FF3E4826": "Avocado Dark Green", "#FF87A922": "Avocado Green", "#FF555337": "Avocado Pear", "#FF39373B": "Avocado Peel", "#FF4E3E1F": "Avocado Stone", "#FF90B134": "Avocado Toast", "#FFCDD6B1": "Avocado Whip", "#FFCCBDB4": "Avorio", "#FFA7A3BB": "Awaken", "#FFE3DAE9": "Awakened", "#FFBB9E9B": "Awakening", "#FF315886": "Award Blue", "#FF54617D": "Award Night", "#FFFEF0DE": "Award Winning White", "#FFE3EBB1": "Awareness", "#FFF0D6CF": "Awe", "#FFCCC1DA": "Awesome Aura", "#FFA7B2D4": "Awesome Violet", "#FFD208CC": "Awkward Purple", "#FF90413E": "Awning Red", "#FF6B4730": "Axe Handle", "#FF756050": "Axinite", "#FFBAB6CB": "Axis", "#FFFFF0DF": "Axolotl Variant", "#FF665500": "Ayahuasca Vine", "#FF763568": "Ayame Iris", "#FFA07254": "Ayrshire", "#FFD73B5D": "Azalea Variant", "#FFEFC0CB": "Azalea Flower", "#FF4A6871": "Azalea Leaf", "#FFF9C0C4": "Azalea Pink", "#FFBA7462": "Azalea Pot", "#FFA5B546": "Azeitona", "#FF7C968B": "Azores", "#FF0085A7": "Azores Blue", "#FF4C6CB3": "Azraq Blue", "#FFB13916": "Azshara Vein", "#FF293432": "Aztec Variant", "#FFFFEFBC": "Aztec Aura", "#FF9E8352": "Aztec Brick", "#FFC46943": "Aztec Copper", "#FFE7B347": "Aztec Glimmer", "#FFC39953": "Aztec Gold", "#FF33BB88": "Aztec Jade", "#FF4DB5D7": "Aztec Sky", "#FF84705B": "Aztec Temple", "#FF00D6E2": "Aztec Turquoise", "#FFBB0066": "Aztec Warrior", "#FF96514D": "Azuki Bean", "#FF672422": "Azuki Red", "#FF1D5DEC": "Azul", "#FF0089C4": "Azul Caribe", "#FFC9E3EB": "Azul Cielito Lindo", "#FF537FAF": "Azul Pavo Real", "#FFE2EFF2": "Azul Primavera", "#FFC0CFC7": "Azul Tequila", "#FF6ABAC4": "Azul Turquesa", "#FF211D49": "Azulado", "#FF4D91C6": "Azure Blue", "#FF053976": "Azure Dragon", "#FF006C81": "Azure Green Blue", "#FFDDDCE1": "Azure Hint", "#FF7BBBC8": "Azure Lake", "#FFF0FFF1": "Azure Mist", "#FF007F1F": "Azure Radiance Variant", "#FFB0E0F6": "Azure Sky", "#FF2B9890": "Azure Tide", "#FF59BAD9": "Azurean", "#FFDBE9F4": "Azureish White", "#FFCC81F0": "Azuremyst Isle", "#FF8FA0D5": "Azureno", "#FF497F73": "Azurite Water Green", "#FFEDB367": "Ba-Dum Ching!", "#FF610023": "Baal Red Wash", "#FFEEBB88": "Baba Ganoush", "#FFBECFCD": "Babbling Brook", "#FFA7BAD3": "Babbling Creek", "#FFDC7B7C": "Babe", "#FF876FA3": "Babiana", "#FFABCCC3": "Baby Aqua", "#FFE9E3CE": "Baby Artichoke", "#FFC3C3B8": "Baby Barn Owl", "#FF6F5944": "Baby Bear", "#FF9C4A62": "Baby Berries", "#FFFAEFE9": "Baby Blossom", "#FFA2CFFE": "Baby Blue Variant", "#FFBBB98A": "Baby Bok Choy", "#FFB7CADB": "Baby Boots", "#FFABCAEA": "Baby Bunting", "#FF8C665C": "Baby Burro", "#FF87BEA3": "Baby Cake", "#FFFFEDA2": "Baby Chick", "#FFF3ACB9": "Baby Fish Mouth", "#FFF0D0B0": "Baby Fragrance", "#FFC8BA63": "Baby Frog", "#FFFFDFE8": "Baby Girl", "#FF8ABD7B": "Baby Grass", "#FF8CFF9E": "Baby Green", "#FFD0A7A8": "Baby Jane", "#FFFFA468": "Baby Melon", "#FF8FCBDC": "Baby Motive", "#FFFFB7CE": "Baby Pink Variant", "#FFCA9BF7": "Baby Purple", "#FFA1A5A8": "Baby Seal", "#FF89A882": "Baby Spinach", "#FFA78B81": "Baby Sprout", "#FFF5C9DA": "Baby Steps", "#FFBABABA": "Baby Talk Grey", "#FF66B9D6": "Baby Tears", "#FFDCC2CB": "Baby Tone", "#FFEEFFDD": "Baby Tooth", "#FF5D6942": "Baby Vegetable", "#FF4D5588": "Baby Whale", "#FFFFAEC1": "Baby\u2019s Blanket", "#FFE8C1C2": "Baby\u2019s Booties", "#FFD8E4E8": "Baby\u2019s Breath", "#FFEECCBB": "Babyccino", "#FFC8E4E7": "Babymoon Blue", "#FF945759": "Baca Berry", "#FF8A3A3C": "Bacchanalia Red", "#FF95122C": "Bacchic Burgundy", "#FF8FAACA": "Bachelor Blue", "#FF4ABBD5": "Bachelor Button", "#FFFDDEA5": "Bachimitsu Gold", "#FF16141C": "Back in Black", "#FF015F7F": "Back of Beyond", "#FF6B625B": "Back Stage", "#FF726747": "Back", "#FFBDB98F": "Back Variant", "#FFC1853B": "Back Tint", "#FF7C725F": "Backcountry", "#FFA7A799": "Backdrop", "#FFFCF0E5": "Backlight", "#FFFFDF4F": "Backlit Lemon", "#FFA19250": "Backroom Ember", "#FF687078": "Backwater", "#FF4A6546": "Backwoods", "#FF879877": "Backyard", "#FFDF3F32": "Bacon Strips", "#FFF1C983": "Bad Hair Day", "#FFF2E5B4": "Bad Moon Yellow", "#FF0A0908": "Badab Black Wash", "#FFB4DA55": "Badass Grass", "#FFB5695A": "Badlands", "#FFFF6316": "Badlands Orange", "#FF936A5B": "Badlands Sunset", "#FFD5BCB3": "Badlands Taupe", "#FFD3A194": "Badshahi Brown", "#FFE1BD88": "Bag of Gold", "#FFF6CD9B": "Bagel", "#FF1C5544": "Bagpiper", "#FFB5936A": "Baguette", "#FF25597F": "Bahama Blue Variant", "#FFB9D1DA": "Bahama Breezes", "#FF3FA49B": "Bahaman Bliss", "#FF12ABBE": "Bahaman Sea", "#FF58C1CD": "Baharroth Blue", "#FFA9C01C": "Bahia Variant", "#FFC4C5AD": "Bahia Grass", "#FFECEFEF": "B\u00e1i S\u00e8 White", "#FF887938": "Baik\u014d Brown", "#FF8A8EC9": "Bailey Bells", "#FF8273FD": "Baingan\u012b", "#FF4B5445": "Baize", "#FFC7CDA8": "Baize Green", "#FFD2C1A8": "Baja", "#FF66A6D9": "Baja Blue", "#FFB34646": "Baked Apple", "#FF9C4856": "Baked Bahama", "#FFB2754D": "Baked Bean", "#FFDAD3CC": "Baked Biscotti", "#FFDACBA9": "Baked Bread", "#FFEDE9D7": "Baked Brie", "#FFA35445": "Baked Clay", "#FF89674A": "Baked Cookie", "#FFEEC8BC": "Baked Ham", "#FFB69E87": "Baked Potato", "#FFDF9876": "Baked Salmon", "#FFE5D3BC": "Baked Scone", "#FF9B775E": "Baked Sienna", "#FFE6D4A5": "Bakelite", "#FFD7995D": "Bakelite Gold", "#FFC6B788": "Bakelite Yellow", "#FFBF8284": "Baker Rose", "#FFFF92AE": "Baker-Miller Pink", "#FFD0B393": "Baker\u2019s Bread", "#FF5C3317": "Baker\u2019s Chocolate", "#FFCEB997": "Baker\u2019s Dozen", "#FFC98F70": "Baker\u2019s Dream", "#FFF0F4F2": "Bakery Box", "#FFAB9078": "Bakery Brown", "#FFEFB435": "Baklava", "#FF273F4B": "Bakos Blue", "#FFD1DBC2": "Balance", "#FFC3C5A7": "Balance Green", "#FFD7D2D1": "Balanced", "#FFC0B2A2": "Balanced Beige", "#FFAFD3DA": "Balboa", "#FFE2BCB8": "Balcony Rose", "#FFD78E6B": "Balcony Sunset", "#FF165A90": "Baleine Blue", "#FFCF994B": "Bales of Brown", "#FF6F5937": "Bali Batik", "#FF5E9EA0": "Bali Bliss", "#FF8A8E93": "Bali Deep", "#FF849CA9": "Bali Hai Variant", "#FFF6E8D5": "Bali Sand", "#FFF1A177": "Balinese Sunset", "#FF525661": "Ball Gown", "#FFCAB6C6": "Ballad", "#FFF2CFDC": "Ballerina", "#FFE8DED6": "Ballerina Beauty", "#FFF9EAEA": "Ballerina Gown", "#FFF7B6BA": "Ballerina Pink", "#FFF0DEE0": "Ballerina Silk", "#FFF2BBB1": "Ballerina Tears", "#FFC8647F": "Ballerina Tutu", "#FFF7D5D4": "Ballet", "#FFFC8258": "Ballet Cream", "#FFD3ADB1": "Ballet Rose", "#FFFFC5B3": "Ballet Skirt", "#FFFCA2AD": "Ballet Slippers", "#FFF2E7D8": "Ballet White", "#FFB2B29C": "Ballie Scott Sage", "#FF323477": "Ballpoint Indigo", "#FFDAD3C8": "Ballroom Belle", "#FFA6B3C9": "Ballroom Blue", "#FF85928E": "Ballroom Slippers", "#FF58A83B": "Ballyhoo", "#FFC5D8DE": "Balmy", "#FF5C6F64": "Balmy Palm Tree", "#FFB4DCD3": "Balmy Seas", "#FF9C6B08": "Balor Brown", "#FFCBBB92": "Balsa Stone", "#FF9A7550": "Balsa Wood", "#FFBEC4B7": "Balsam", "#FF36574E": "Balsam Branch", "#FFCBA874": "Balsam Brown", "#FF909E91": "Balsam Fir", "#FF120D07": "Balsam of Peru", "#FFB19338": "Balsam Pear", "#FF434340": "Balsamic Reduction", "#FF130D07": "Balsamico", "#FFA47552": "Balthasar Gold", "#FF279D9F": "Baltic", "#FFFBB782": "Baltic Amber", "#FF6C969A": "Baltic Blue", "#FF9FBBDA": "Baltic Bream", "#FF3AA098": "Baltic Green", "#FF135952": "Baltic Prince", "#FF3C3D3E": "Baltic Sea Variant", "#FF125761": "Baltic Trench", "#FF00A49A": "Baltic Turquoise", "#FF8EDACC": "Bambino", "#FFE3DEC6": "Bamboo Variant", "#FFC1ABA0": "Bamboo Beige", "#FFC87F00": "Bamboo Brown", "#FF454A48": "Bamboo Charcoal", "#FFB1A979": "Bamboo Forest", "#FF82994C": "Bamboo Grass Green", "#FF99B243": "Bamboo Leaf", "#FFE5DA9F": "Bamboo Mat", "#FFBCAB8C": "Bamboo Screen", "#FFA3B6A4": "Bamboo Shoot", "#FFC6CFAD": "Bamboo White", "#FFAE884B": "Bamboo Yellow", "#FF5A1991": "Banaf\u0161 Violet", "#FFFAF3A6": "Banan-Appeal", "#FFFFFC79": "Banana", "#FFEFE073": "Banana Ball", "#FFF8F739": "Banana Bandanna", "#FFFFDE7B": "Banana Biscuit", "#FF933E49": "Banana Blossom", "#FFFDC838": "Banana Boat", "#FFF7E82E": "Banana Bombshell", "#FFFFCF73": "Banana Bread", "#FFE8D82C": "Banana Brick", "#FFF7EAB9": "Banana Br\u00fbl\u00e9e", "#FFD6D963": "Banana Chalk", "#FFEEDD00": "Banana Clan", "#FFFFF49C": "Banana Cream", "#FFE7D3AD": "Banana Crepe", "#FFFCF3C5": "Banana Custard", "#FFF1D548": "Banana Drama", "#FFFFDF38": "Banana Farm", "#FFEEFE02": "Banana Flash", "#FFDDD5B6": "Banana Frapp\u00e9", "#FFF1D3B2": "Banana Ice Cream", "#FFFFFB08": "Banana King", "#FF9D8F3A": "Banana Leaf", "#FFFAFE4B": "Banana Mash", "#FFFFF7AD": "Banana Milk", "#FFEDE6CB": "Banana Milkshake", "#FF95A263": "Banana Palm", "#FFFFE774": "Banana Peel", "#FFFDD630": "Banana Pepper", "#FFF7EFD7": "Banana Pie", "#FFD0C101": "Banana Powder", "#FFF3DB00": "Banana Propaganda", "#FFF4EFC3": "Banana Pudding", "#FFB29705": "Banana Puree", "#FFFFE292": "Banana Republic", "#FFF6F5D7": "Banana Sparkes", "#FFF7EEC8": "Banana Split", "#FFFFE135": "Banana Yellow", "#FFE4D466": "Bananarama", "#FFDBBE97": "Bananas Foster", "#FF00A86C": "Banaue Jade", "#FF666A47": "Bancha", "#FF816E54": "Bancroft Village", "#FFD7A97C": "Band-Aid", "#FF01578F": "Bandana", "#FFE0D3BD": "Banded Tulip", "#FF878466": "Bandicoot Variant", "#FF871466": "Bane of Royalty", "#FF937F6D": "Baneblade Brown", "#FFBC393B": "Bang", "#FFBBAA88": "Bangalore", "#FF006A4F": "Bangladesh Green", "#FFD2B762": "Banh Bot Loc Dumpling", "#FF745E6F": "Banished Brown", "#FF3E4652": "Bank Blue", "#FF757374": "Bank Vault", "#FFA6B29A": "Banksia", "#FF4B5539": "Banksia Leaf", "#FF016876": "Bankson Lake", "#FFA28557": "Banner Gold", "#FF806B5D": "Bannister Brown", "#FFE1E0D6": "Bannister White", "#FFDAF0E6": "Banshee", "#FFAF6C5D": "Bantam Egg", "#FF98AB8C": "Banyan Serenity", "#FF8D793E": "Banyan Tree", "#FFE6490B": "Baptism by Fire", "#FFE9546B": "Bara Red", "#FF551100": "Baragon Brown", "#FF4B2D2A": "Barako Brew", "#FF3E6676": "Barbados", "#FF006665": "Barbados Bay", "#FFB8A983": "Barbados Beige", "#FF2766AC": "Barbados Blue", "#FFAF0A30": "Barbados Cherry", "#FFFF0FF3": "Barbara", "#FFF78C5A": "Barbarian", "#FFA17308": "Barbarian Leather", "#FFA84734": "Barbarossa", "#FFC26157": "Barbecue", "#FF471C0F": "Barbecue Sauce", "#FF8B031C": "Barbera", "#FFEE1133": "Barberry Variant", "#FFD2C61F": "Barberry Bush", "#FFE1D4BC": "Barberry Sand", "#FFF3BD32": "Barberry Yellow", "#FFFE46A5": "Barbie Pink Variant", "#FFE15ACB": "Barbiecore", "#FFC4B39C": "Barcelona Beige", "#FF926A46": "Barcelona Brown", "#FFFF9500": "Barcelona Orange", "#FF817E6D": "Bare", "#FFE8D3C9": "Bare Beige", "#FFEEDDCC": "Bare Bone", "#FFDCD4C3": "Bare Market", "#FFD6E3E7": "Bare Mintimum", "#FFEFC9B7": "Bare Necessity", "#FFF2E1DD": "Bare Pink", "#FFBAE9E0": "Barely Aqua", "#FFE4CCD4": "Barely Berry", "#FFDDAADD": "Barely Bloomed", "#FFDDE0DF": "Barely Blue", "#FFDD6655": "Barely Brown", "#FFF8E9C2": "Barely Butter", "#FFCCBDB9": "Barely Mauve", "#FFFFE9C7": "Barely Peach", "#FFEDEBDB": "Barely Pear", "#FFF8D7DD": "Barely Pink", "#FFFFE3CB": "Barely Ripe Apricot", "#FFEDE0E3": "Barely Rose", "#FFE1E3DD": "Barely White", "#FFD5D3C0": "Barest Celadon", "#FFC5D9DB": "Barest Hint of Blue", "#FFD5D3C7": "Barest Hush", "#FF94AC02": "Barf Green", "#FF68534A": "Bargeboard Brown", "#FFBCAFA2": "Barista", "#FFBB8D4E": "Barista\u2019s Favourite", "#FF9E7B5C": "Barite", "#FF708E95": "Baritone", "#FFF4E1C5": "Barium", "#FF8FFF9F": "Barium Green", "#FF5F5854": "Bark", "#FF73532A": "Bark Brown", "#FFAB9004": "Bark Sawdust", "#FFADC6DA": "Barking Creek", "#FFC5B497": "Barking Prairie Dog", "#FFD5B37E": "Barley", "#FFB6935C": "Barley Corn Variant", "#FFC7BCAE": "Barley Field", "#FFFBF2DB": "Barley Groats", "#FFF7E5B7": "Barley White Variant", "#FF8E5959": "Barn Door", "#FF8B4044": "Barn Red", "#FF4D332A": "Barn Swallow", "#FFAC1DB8": "Barney", "#FFA00498": "Barney Purple", "#FF9C9481": "Barnfloor", "#FF554D44": "Barnwood", "#FF87857E": "Barnwood Ash", "#FF9E9589": "Barnwood Grey", "#FF5DAC51": "Barnyard Grass", "#FF71000E": "Barolo", "#FF929899": "Barometer", "#FFA785A7": "Baroness", "#FF847098": "Baroness Mauve", "#FF5A4840": "Baronial Brown", "#FFDDAA22": "Baroque", "#FF95B6B5": "Baroque Blue", "#FFAECCCB": "Baroque Chalk Soft Blue", "#FF5F5D64": "Baroque Grey", "#FF7B4F5D": "Baroque Red", "#FFB35A66": "Baroque Rose", "#FF452E39": "Barossa Variant", "#FFF0B069": "Barrel", "#FF8B6945": "Barrel Aged", "#FF9E3C31": "Barrel Sponge", "#FF8E7E67": "Barrel Stove", "#FFB9ABA3": "Barren", "#FFF5D1B2": "Barrett Quince", "#FF84623E": "Barricade", "#FF009BB5": "Barrier Reef", "#FFA2A59E": "Barrister Grey", "#FF9F8E71": "Barro Verde", "#FF989998": "Basalt", "#FF4D423E": "Basalt Black", "#FFC15154": "Basashi Red", "#FF575C3A": "Base Camp", "#FFBB9955": "Base Sand", "#FFF4EADC": "Baseball Base", "#FFE3EDED": "Bashful", "#FF6994CF": "Bashful Blue", "#FFB2B0AC": "Bashful Emu", "#FFD0D2E3": "Bashful Lilac", "#FFD9CDE5": "Bashful Pansy", "#FFB88686": "Bashful Rose", "#FFDBC3B6": "Basic Coral", "#FFC3B69F": "Basic Khaki", "#FF879F84": "Basil", "#FF828249": "Basil Chiffonade", "#FF54622E": "Basil Green", "#FFE2E6DB": "Basil Icing", "#FF6C5472": "Basil Mauve", "#FF529D6E": "Basil Pesto", "#FFB7E1A1": "Basil Smash", "#FF4A9FA7": "Basilica Blue", "#FF9AB38D": "Basilisk", "#FFBCECAC": "Basilisk Lizard", "#FFB9DEE4": "Basin Blue", "#FFC0A98B": "Basket Beige", "#FFC8C0B8": "Basket of Bobbins", "#FFF4CC3C": "Basket of Gold", "#FFEE6730": "Basketball", "#FFBDA286": "Basketry", "#FFCAAD92": "Basketweave Beige", "#FFEBE1C9": "Basmati White", "#FF5F6033": "Basque Green", "#FFD3C1CB": "Bassinet", "#FFC9B196": "Basswood", "#FF839E83": "Basswood Green", "#FFFFCC88": "Bastard Amber", "#FF2C2C32": "Bastille Variant", "#FF4D4A4A": "Bastion Grey", "#FF7E7466": "Bat Wing", "#FFFEFF00": "Bat-Signal", "#FFEE3366": "Bat\u2019s Blood Soup", "#FF87B2C9": "Batch Blue", "#FF45392F": "Batch Brew", "#FF1B7598": "Bateau", "#FF7A5F5A": "Bateau Brown", "#FFD8E9DB": "Bath", "#FF0A696A": "Bath Green", "#FF411900": "Bath in Chocolate", "#FFBBDED7": "Bath Salt Green", "#FF62BAA8": "Bath Turquoise", "#FF88EEEE": "Bath Water", "#FFC2E0E3": "Bathe Blue", "#FF93C9D0": "Bathing", "#FF428267": "Bathing Beauty", "#FF7E738B": "Batik Lilac", "#FF9C657E": "Batik Pink", "#FF656E72": "Batman", "#FF866F5A": "Baton", "#FFB8292B": "Baton Rouge", "#FF1F1518": "Bats Cloak", "#FFEDE2D4": "Battered Sausage", "#FF1DACD4": "Battery Charged Blue", "#FF74828F": "Battle Blue", "#FF2B7414": "Battle Cat", "#FF7E8270": "Battle Dress", "#FF9C9C82": "Battle Harbour", "#FF9C9895": "Battleship", "#FF6F7476": "Battleship Grey Variant", "#FF11CC55": "Battletoad", "#FF595438": "Batu Cave", "#FF3F4040": "Bauhaus", "#FF006392": "Bauhaus Blue", "#FFCFB49E": "Bauhaus Buff", "#FFB0986F": "Bauhaus Gold", "#FFCCC4AE": "Bauhaus Tan", "#FF4D5E42": "Bavarian", "#FF1C3382": "Bavarian Blue", "#FFFFF9DD": "Bavarian Cream", "#FF20006D": "Bavarian Gentian", "#FF749A54": "Bavarian Green", "#FFACBF93": "Bavarian Hops", "#FF4D3113": "Bavarian Sweet Mustard", "#FFB3E2D3": "Bay", "#FFAFA490": "Bay Area", "#FF773300": "Bay Brown", "#FF9899B0": "Bay Fog", "#FF214048": "Bay Isle Pointe", "#FF86793D": "Bay Leaf Variant", "#FF85C5C8": "Bay Mist", "#FFBFC9D0": "Bay of Hope", "#FF353E64": "Bay of Many Variant", "#FFD2CDBC": "Bay Salt", "#FFC5BEAE": "Bay Sands", "#FFFBE6CD": "Bay Scallop", "#FF325F8A": "Bay Site", "#FF018486": "Bay Teal", "#FF6A819E": "Bay View", "#FFBFC2BF": "Bay Waves", "#FF747F89": "Bay Wharf", "#FF7B9AAD": "Bay\u2019s Water", "#FF275A5D": "Bayberry", "#FFD0D9C7": "Bayberry Frost", "#FFB6AA89": "Bayberry Wax", "#FF0098D4": "Bayern Blue", "#FF268483": "Bayou", "#FF017992": "Bayou Serenade", "#FFBEB48E": "Bayou Shade", "#FF89CEE0": "Bayshore", "#FF5FC9BF": "Bayside", "#FFC9D8E4": "Baywater Blue", "#FF8F7777": "Bazaar Variant", "#FFA35046": "BBQ", "#FFF4E3E7": "Be Mine", "#FFEC9DC3": "Be My Valentine", "#FFA5CB66": "Be Spontaneous", "#FF9B983D": "Be Yourself", "#FFEFE4BB": "Beach", "#FFADB864": "Beach Bag", "#FFEFC700": "Beach Ball", "#FFB2E4CD": "Beach Blanket", "#FF5F9CA2": "Beach Blue", "#FFCEAB90": "Beach Boardwalk", "#FF91CBBF": "Beach Breeze", "#FFA69081": "Beach Cabana", "#FF665A38": "Beach Casuarina", "#FFCAAB84": "Beach Coast Buff", "#FF94ADB0": "Beach Cottage", "#FFC6BB9C": "Beach Dune", "#FFCDE0E1": "Beach Foam", "#FF96DFCE": "Beach Glass", "#FFDCDDB8": "Beach Grass", "#FFEDD481": "Beach House", "#FFCCDCDE": "Beach House Blue", "#FFBDA2C4": "Beach Lilac", "#FFFBD05C": "Beach Party", "#FFFBB88B": "Beach Sand", "#FF71CBD2": "Beach Sparkle", "#FFFCE3B3": "Beach Towel", "#FFFEDECA": "Beach Trail", "#FF819AAA": "Beach Umbrella", "#FF4F7694": "Beach View", "#FFDCE1E2": "Beach Wind", "#FFCAC0B0": "Beach Woods", "#FFD8E3E5": "Beachcomber", "#FFE4C683": "Beachcombing", "#FFFBEDD7": "Beaches of Cancun", "#FFACDBDB": "Beachside Drive", "#FFC3B296": "Beachside Villa", "#FFD2B17A": "Beachwalk", "#FFE6D0B6": "Beachy Keen", "#FFEBE8B9": "Beacon", "#FF265C98": "Beacon Blue", "#FFF2C98A": "Beacon Yellow", "#FF494D8B": "Beaded Blue", "#FF8D6737": "Beagle Brown", "#FF33FFFF": "Beaming Blue", "#FFFFF8DF": "Beaming Sun", "#FF4A1207": "Bean Variant", "#FF68755D": "Bean Counter", "#FF685C27": "Bean Green", "#FF8B6B51": "Bean Pot", "#FF91923A": "Bean Shoot", "#FFF3F9E9": "Bean Sprout", "#FFEBF0E4": "Bean White", "#FF31AA74": "Beanstalk", "#FF766E65": "Bear", "#FFAE6B52": "Bear Claw", "#FF836452": "Bear Creek", "#FF796359": "Bear Hug", "#FF5B4A44": "Bear in Mind", "#FF5A4943": "Bear Rug", "#FF7D756D": "Bearsuit", "#FFB08F69": "Beast Hide", "#FF680C08": "Beastly Red", "#FF663300": "Beasty Brown", "#FF6E6A44": "Beat Around the Bush", "#FF73372D": "Beaten Copper", "#FF4E0550": "Beaten Purple", "#FFD1BE92": "Beaten Track", "#FF448844": "Beating Around the Bush", "#FF5F8748": "Beatnik", "#FF7DB39E": "Beau Monde", "#FF0C6064": "Beau Vert", "#FF80304C": "Beaujolais", "#FF92774C": "Beaumont Brown", "#FF553F44": "Beauport Aubergine", "#FF186DB6": "Beautiful Blue", "#FF686D70": "Beautiful Darkness", "#FFB6C7E3": "Beautiful Dream", "#FF8DA936": "Beautiful Mint", "#FF866B8D": "Beauty", "#FFC99680": "Beauty and the Beach", "#FFEBB9B3": "Beauty Bush Variant", "#FF834F44": "Beauty Patch", "#FFBE5C87": "Beauty Queen", "#FFC79EA2": "Beauty Secret", "#FF604938": "Beauty Spot", "#FF997867": "Beaver Fur", "#FF60564C": "Beaver Pelt", "#FFF4EEE0": "B\u00e9chamel", "#FF607879": "Becker Blue", "#FF7F7353": "Becker Gold", "#FF85A699": "Beckett", "#FF4BEC13": "Becquerel", "#FFB893AB": "Bed of Roses", "#FFD3B9CC": "Bedazzled", "#FF968775": "Bedbox", "#FFAA8880": "Bedford Brown", "#FF9E9D99": "Bedrock", "#FFC3ACB2": "Bedroom Plum", "#FFADB7C1": "Bedside Manner", "#FFE1B090": "Bedtime Story", "#FFF1BA55": "Bee", "#FFFFAA33": "Bee Cluster", "#FFF2CC64": "Bee Hall", "#FF735B3B": "Bee Master", "#FFEBCA70": "Bee Pollen", "#FFFEFF32": "Bee Yellow", "#FFE8D9D2": "Bee\u2019s Knees", "#FF5B4F3B": "Beech", "#FF574128": "Beech Brown", "#FF758067": "Beech Fern", "#FFD7B59A": "Beech-Nut", "#FF6E5955": "Beechwood", "#FFB64701": "Beef Bourguignon", "#FFA85D2E": "Beef Hotpot", "#FFA25768": "Beef Jerky", "#FF8A4512": "Beef Noodle Broth", "#FFBB5533": "Beef Patties", "#FFDEBEEF": "Beefy Pink", "#FFE1B781": "Beehive", "#FFF6E491": "Beekeeper", "#FFFCAA12": "Beer", "#FF449933": "Beer Garden", "#FF773311": "Beer Glazed Bacon", "#FFE9D7AB": "Beeswax Variant", "#FFBF7E41": "Beeswax Candle", "#FFF5D297": "Beeswing", "#FF7E203F": "Beet Red", "#FFD4265D": "Beet Wave", "#FF90306A": "Beetiful Magenta", "#FF55584C": "Beetle", "#FF663F44": "Beetroot", "#FFD33376": "Beetroot Purple", "#FFC58F9D": "Beetroot Rice", "#FF736A86": "Beets", "#FF96496D": "Befitting", "#FF4D6A77": "Before the Storm", "#FFBD6F56": "Before Winter", "#FF5A4D39": "Beggar", "#FFFA6E79": "Begonia", "#FFC3797F": "Begonia Rose", "#FF848E8A": "Beguile", "#FF5E6F8E": "Beguiling Blue", "#FFAFA7AC": "Beguiling Mauve", "#FFE6DAA6": "Beige Variant", "#FFBBC199": "Beige and Sage", "#FFD6C5B4": "Beige Chalk", "#FFCFB095": "Beige Ganesh", "#FFE0D8B0": "Beige Green", "#FFC5A88D": "Beige Intenso", "#FF987A5B": "Beige Intuition", "#FFE2DAC6": "Beige Linen", "#FFDE9408": "Beige Red", "#FFCFC8B8": "Beige Royal", "#FFFFC87C": "Beige Topaz", "#FFECCC9B": "Beignet", "#FF3E7DAA": "Beijing Blue", "#FFA9A2A3": "Beijing Moon", "#FF25A26F": "Bejewelled", "#FF819AC1": "Bel Air Blue", "#FF9BBCC3": "Bel Esprit", "#FF558D4F": "Belfast", "#FFBB937F": "Belfry Brick", "#FF9BA29E": "Belgian Block", "#FFF7EFD0": "Belgian Blonde", "#FFF9F1E2": "Belgian Cream", "#FF8D7560": "Belgian Sweet", "#FFF3DFB6": "Belgian Waffle", "#FFDBC7A8": "Believable Buff", "#FF7FD3D3": "Belize", "#FFB9C3B3": "Belize Green", "#FF618B97": "Bell Blue", "#FFA475B1": "Bell Heather", "#FFDAD0BB": "Bell Tower", "#FF574057": "Bella", "#FF93C3B1": "Bella Green", "#FFDAC5BD": "Bella Mia", "#FFE08194": "Bella Pink", "#FF40465D": "Bella Sera", "#FF0B695B": "Bella Vista", "#FF220011": "Belladonna", "#FFADC3A7": "Belladonna\u2019s Leaf", "#FFB7DFF3": "Bellagio Fountains", "#FFF2B400": "Bellagio Gold", "#FFE3CBC0": "Belle of the Ball", "#FF5D66AA": "Bellflower", "#FFE1E9EF": "Bellflower Blue", "#FFB2A5B7": "Bellflower Violet", "#FFF4C9B1": "Bellini", "#FFF5C78E": "Bellini Fizz", "#FFEDE3A1": "Bells and Whistles Gold", "#FF773B38": "Belly Fire", "#FF00817F": "Belly Flop", "#FF626A60": "Belmont Green", "#FFB3B4DD": "Beloved", "#FFE9D3D4": "Beloved Pink", "#FFFFBA24": "Beloved Sunflower", "#FF0A2F7C": "Below the Surface", "#FF87CDED": "Below Zero", "#FFEFF2F1": "Beluga", "#FFD7D2D2": "Beluga Song", "#FFE3DBC3": "Belvedere Cream", "#FFF0F1E1": "Belyi White", "#FF694977": "Benevolence", "#FFDD1188": "Benevolent Pink", "#FFCC974D": "Bengal", "#FF38738B": "Bengal Blue", "#FF8E773F": "Bengal Grass", "#FF8F2E14": "Bengala Red", "#FF913225": "Bengara Red", "#FFB85241": "Beni Shoga", "#FFBB7796": "Benifuji", "#FFF35336": "Benihi Red", "#FF5A4F74": "Benikakehana Purple", "#FF44312E": "Benikeshinezumi Purple", "#FF78779B": "Benimidori Purple", "#FF007BAA": "Benitoite", "#FFFB8136": "Beniukon Bronze", "#FF000011": "Benthic Black", "#FFCC363C": "Bento Box", "#FF00D973": "Benzol Green", "#FFD8CFB6": "Berber", "#FF95C703": "Bergamot", "#FFF59D59": "Bergamot Orange", "#FFB8C7DB": "Beribboned", "#FF4B596E": "Bering Sea", "#FF3D6D84": "Bering Wave", "#FF7E613F": "Berkeley Hills", "#FFF0E1CF": "Berkshire Lace", "#FF5588CC": "Berlin Blue", "#FF1B7D8D": "Bermuda Variant", "#FF8CB1C2": "Bermuda Blue", "#FF9D5A8F": "Bermuda Onion", "#FFDACBBF": "Bermuda Sand", "#FFF9EEE3": "Bermuda Shell", "#FFF0E9BE": "Bermuda Son", "#FF6F8C9F": "Bermuda Triangle", "#FF6BC271": "Bermudagrass", "#FF386171": "Bermudan Blue", "#FFE20909": "Bern Red", "#FFAB7CB4": "Berries Galore", "#FFF2B8CA": "Berries N\u2019 Cream", "#FF990F4B": "Berry", "#FF662277": "Berry Blackmail", "#FFFF017F": "Berry Blast", "#FF9E8295": "Berry Bliss", "#FF32607A": "Berry Blue", "#FF264B56": "Berry Blue Green", "#FFB88591": "Berry Blush", "#FFBB5588": "Berry Boost", "#FF7D5857": "Berry Brandy", "#FFA08497": "Berry Bright", "#FF544F4C": "Berry Brown", "#FFAC72AF": "Berry Burst", "#FF77424E": "Berry Bush", "#FFEFCEDC": "Berry Butter", "#FFA6AEBB": "Berry Chalk", "#FF4F4763": "Berry Charm", "#FFF8E3DD": "Berry Cheesecake", "#FF3F000F": "Berry Chocolate", "#FF765269": "Berry Conserve", "#FF9A8CA2": "Berry Cream", "#FFAA6772": "Berry Crush", "#FF9B1C5D": "Berry Curious", "#FFB3A1C6": "Berry Frapp\u00e9", "#FFEBDED7": "Berry Frost", "#FFEDC3C5": "Berry Good", "#FF655883": "Berry Jam", "#FF673B66": "Berry Light", "#FF555A90": "Berry Mix", "#FFB6CACA": "Berry Mojito", "#FF84395D": "Berry Patch", "#FF4F6D8E": "Berry Pie", "#FFD6A5CD": "Berry Popsicle", "#FFA0688D": "Berry Pretty", "#FFE5A2AB": "Berry Riche", "#FF992244": "Berry Rossi", "#FF895360": "Berry Smoothie", "#FF64537C": "Berry Syrup", "#FFECD5D9": "Berry Taffy", "#FFD78BAD": "Berry Twist", "#FF624D55": "Berry Wine", "#FFD75E6C": "Berrylicious", "#FF45DCFF": "Berta Blue", "#FFBFE4D4": "Beru", "#FF7082A4": "Berwick Berry", "#FF71DCB8": "Beryl", "#FF2B322D": "Beryl Black Green", "#FFBCBFA8": "Beryl Green Variant", "#FFE2E3DF": "Beryl Pearl", "#FFA16381": "Beryl Red", "#FFE9E5D7": "Beryllonite", "#FFD4BA9D": "Bespoke", "#FF685E5B": "Bessie", "#FFC6B49C": "Best Beige", "#FF5D513E": "Best Bronze", "#FFB9B7BD": "Best in Show", "#FF01809F": "Best of Both Worlds", "#FFF7F2D9": "Best of Summer", "#FFBD5442": "Best of the Bunch", "#FFCD343D": "Bestial Blood", "#FF6B3900": "Bestial Brown", "#FF992211": "Bestial Red", "#FFD38A57": "Bestigor", "#FF7D655C": "Betalain Red", "#FF352925": "Betel Nut Dye", "#FFCADBBD": "Bethany", "#FFEE0022": "Bethlehem Red", "#FFEAEEDA": "Bethlehem Superstar", "#FF73C9D9": "Betsy", "#FF3A6B66": "Betta Fish", "#FFEBE2CB": "Better Than Beige", "#FFEDE1BE": "Beurre Blanc", "#FF7ACCB8": "Bevelled Glass", "#FF75AC16": "Bevelled Grass", "#FF6A393C": "Bewitched", "#FF75495E": "Bewitching", "#FFBBD0E3": "Bewitching Blue", "#FFAAEEFF": "Beyond the Clouds", "#FFF7D6BA": "Beyond the Pale", "#FF688049": "Beyond the Pines", "#FF005784": "Beyond the Sea", "#FF0A3251": "Beyond the Stars", "#FFD7E0EB": "Beyond the Wall", "#FFDBB0D3": "Bff", "#FF70C0E2": "Bff Blue", "#FF947706": "Bh\u016br\u0101 Brown", "#FF1C5022": "Bia\u0142owie\u017ca Forest", "#FFF4EFE0": "Bianca Variant", "#FF3DCFC2": "Bianchi Green", "#FFFFE58C": "Bicycle Yellow", "#FF802C3A": "Bicyclette", "#FFA9B9B5": "Bidwell Blue", "#FFB19C8F": "Bidwell Brown", "#FF507CA0": "Biedermeier Blue", "#FF1BA169": "Biel-Tan Green", "#FFF0908D": "Bierwurst", "#FF908C84": "Big Apple", "#FFAFABA0": "Big Band", "#FFFF0099": "Big Bang Pink", "#FFFFDA8B": "Big Bus Yellow", "#FF7ECBE2": "Big Chill", "#FFB98675": "Big Cypress", "#FF5D6B75": "Big Daddy Blue", "#FF41494B": "Big Dipper", "#FF99A38E": "Big Fish", "#FFDADBE1": "Big Fish Variant", "#FFE88E5A": "Big Foot Feet", "#FFB79E94": "Big Horn Mountains", "#FF34708F": "Big Ocean Wave", "#FFCDE2DE": "Big Sky", "#FF85C4C9": "Big Sky Country", "#FFACDDAF": "Big Spender", "#FF334046": "Big Stone Variant", "#FF886E54": "Big Stone Beach", "#FFB3CADC": "Big Sur", "#FF3F6E8E": "Big Sur Blue Jade", "#FF96D0D1": "Big Surf", "#FFFFEE22": "Big Yellow Streak", "#FFFFFF33": "Big Yellow Taxi", "#FF715145": "Bigfoot", "#FF20120E": "Bighorn Sheep", "#FF4E5E7F": "Bijou Blue", "#FFA33D3B": "Bijou Red", "#FF676B55": "Bijoux Green", "#FF7B222A": "Biking Red", "#FFC3C0B1": "Biking Trail", "#FF3E8027": "Bilbao Variant", "#FF71777E": "Bilberry", "#FFB5C306": "Bile", "#FFE39F08": "Bilious Brown", "#FFA9D171": "Bilious Green", "#FF1B6F81": "Billabong", "#FFAD7C35": "Billet", "#FF00AF9F": "Billiard", "#FF276B40": "Billiard Ball", "#FF305A4A": "Billiard Green", "#FF50846E": "Billiard Room", "#FF155843": "Billiard Table", "#FF01B44C": "Billiards Cloth", "#FF83C0E5": "Billow", "#FFD8DEE3": "Billowing Clouds", "#FFD2E3E3": "Billowing Sail", "#FF6E726A": "Billowing Smoke", "#FFAFC7CD": "Billowy Breeze", "#FFF6F0E9": "Billowy Clouds", "#FFEFF0E9": "Billowy Down", "#FF4C77A4": "Billycart Blue", "#FFAE99D2": "Biloba Flower Variant", "#FFF4E4CD": "Biloxi", "#FF0075B8": "Biloxi Blue", "#FFE3C9A1": "Biltmore Buff", "#FF410200": "Biltong", "#FF54682B": "Bimi Green", "#FF007A91": "Bimini Blue", "#FF010101": "Binary Black", "#FF616767": "Binary Star", "#FF8B3439": "Bindi Dot", "#FFB0003C": "Bindi Red", "#FFAF4967": "Bing Cherry Pie", "#FF433D3C": "Binrouji Black", "#FF465F9E": "Bio Blue", "#FFFBFB4C": "Biohazard Suit", "#FF91A135": "Biology Experiments", "#FF55EEFF": "Bioluminescence", "#FF66FF55": "Biopunk", "#FF889900": "BioShock", "#FFEEEE44": "Biotic Grasp", "#FFEEDD55": "Biotic Orb", "#FF3F3726": "Birch Variant", "#FFD9C3A1": "Birch Beige", "#FF899A8B": "Birch Forest", "#FF637E1D": "Birch Leaf Green", "#FFDFB45F": "Birch Strain", "#FFF6EEDF": "Birch White", "#FFCCBEAC": "Birchwood", "#FF806843": "Birchy Woods", "#FFCDDFE7": "Bird Bath Blue", "#FF7B929E": "Bird Blue", "#FF7F92A0": "Bird Blue Grey", "#FFD0C117": "Bird Flower Variant", "#FFFFF1CF": "Bird\u2019s Child", "#FFAACCB9": "Bird\u2019s Egg Green", "#FFCFBB9B": "Bird\u2019s Nest", "#FFAB823D": "Bird\u2019s-Eye", "#FFE4C495": "Bird\u2019s-Eye Maple", "#FF6C483A": "Birdhouse Brown", "#FFE9E424": "Birdie", "#FF89ACDA": "Birdie Num Num", "#FFE2C28E": "Birdseed", "#FF2F3946": "Biro Blue", "#FF224634": "Bir\u014ddo Green", "#FFFCE9DF": "Birth of a Star", "#FFF6EBC2": "Birth of Venus", "#FFE9D2CC": "Birthday Cake", "#FFCFA2AD": "Birthday Candle", "#FF9BDCB9": "Birthday King", "#FFE2C7B6": "Birthday Suit", "#FF79547A": "Birthstone", "#FF2F3C53": "Biscay Variant", "#FF097988": "Biscay Bay", "#FF55C6A9": "Biscay Green", "#FFD8C3A7": "Biscotti", "#FFFEEDCA": "Biscuit", "#FFE6BFA6": "Biscuit Beige", "#FFF9CCB7": "Biscuit Cream", "#FFE3CFB8": "Biscuit Crumbs", "#FFE8DBBD": "Biscuit Dough", "#FFC473A9": "Bishop Red", "#FF486C7A": "Bismarck", "#FF6E4F3A": "Bison", "#FF9F9180": "Bison Beige", "#FF584941": "Bison Brown", "#FFB5AC94": "Bison Hide Variant", "#FFE5D2B0": "Bisque Tan", "#FF705950": "Bistro", "#FF395551": "Bistro Green", "#FFECE3DC": "Bistro Napkin", "#FFE3B8B7": "Bistro Pink", "#FFECE8E1": "Bistro White", "#FFDD5599": "Bit of Berry", "#FFD9E3E5": "Bit of Blue", "#FFCAD7DE": "Bit of Heaven", "#FFE1E5AC": "Bit of Lime", "#FFF4F2EC": "Bit of Sugar", "#FFFFBB11": "Bitcoin", "#FFD47D72": "Bite My Tongue", "#FF88896C": "Bitter Variant", "#FF8D7470": "Bitter Briar", "#FF4F2923": "Bitter Chocolate", "#FF769789": "Bitter Clover Green", "#FF6ECB3C": "Bitter Dandelion", "#FFD2DB32": "Bitter Lemon Variant", "#FFCFFF00": "Bitter Lime", "#FF31CD31": "Bitter Lime and Defeat", "#FF262926": "Bitter Liquorice", "#FFCFD1B2": "Bitter Melon", "#FFD5762B": "Bitter Orange", "#FF97A18D": "Bitter Sage", "#FF856D9E": "Bitter Violet", "#FFFEA051": "Bittersweet Variant", "#FF4F2B17": "Bittersweet Chocolate", "#FF5B5354": "Bittersweet Molasses", "#FFBF4F51": "Bittersweet Shimmer", "#FFCBB49A": "Bittersweet Stem", "#FFE7D2C8": "Bizarre Variant", "#FF5B5D53": "Black Bamboo", "#FF474A4E": "Black Bay", "#FF4E4B4A": "Black Bean Variant", "#FF23262B": "Black Beauty", "#FF2F2F48": "Black Blueberry", "#FF454749": "Black Boudoir", "#FF0F282F": "Black Box", "#FF2E2F31": "Black Cat", "#FF102C33": "Black Chasm", "#FF2C1620": "Black Cherry", "#FF252321": "Black Chestnut Oak", "#FF441100": "Black Chocolate", "#FF3E3231": "Black Coffee", "#FF4E434D": "Black Dahlia", "#FF8A779A": "Black Diamond Apple", "#FF545562": "Black Dragon\u2019s Cauldron", "#FF90ABD9": "Black Drop", "#FFA66E7A": "Black Elder", "#FF50484A": "Black Elegance", "#FF12221D": "Black Emerald", "#FF45524F": "Black Evergreen", "#FF112222": "Black Feather", "#FF484B5A": "Black Flame", "#FF5E6354": "Black Forest Variant", "#FF29485A": "Black Forest Blue", "#FF424740": "Black Forest Green", "#FF4F4842": "Black Fox", "#FF4E4444": "Black Garnet", "#FF001111": "Black Glaze", "#FF384E49": "Black Green", "#FF24272E": "Black Grey", "#FFE0DED7": "Black Haze Variant", "#FF9C856C": "Black Headed Gull", "#FF444647": "Black Heron", "#FFC89180": "Black Hills Gold", "#FF202030": "Black Howl", "#FF110033": "Black Htun", "#FF4D5051": "Black Ice", "#FF2B3042": "Black Iris", "#FF0F1519": "Black Is Back", "#FF74563D": "Black Jasmine Rice", "#FF351E1C": "Black Kite", "#FF3F3E3E": "Black Lacquer", "#FF474C4D": "Black Lead", "#FF253529": "Black Leather Jacket", "#FF3A3B3B": "Black Liquorice", "#FF646763": "Black Locust", "#FF4F4554": "Black Magic", "#FF858585": "Black Mana", "#FF222244": "Black Market", "#FF383740": "Black Marlin Variant", "#FF222211": "Black Mesa", "#FF060606": "Black Metal", "#FF4B4743": "Black Mocha", "#FF4E4F4E": "Black Oak", "#FF323639": "Black of Night", "#FF2A272C": "Black Onyx", "#FF525463": "Black Orchid", "#FF424242": "Black Panther", "#FF1E272C": "Black Pearl Variant", "#FF33654A": "Black Pine Green", "#FF77606F": "Black Plum", "#FF4F5552": "Black Pool", "#FF34342C": "Black Powder", "#FF654B37": "Black Power", "#FFA44A56": "Black Pudding", "#FF694D27": "Black Queen", "#FF16110D": "Black Raspberry", "#FF484C51": "Black Ribbon", "#FF343E54": "Black River Falls", "#FF2C2D3C": "Black Rock Variant", "#FF331111": "Black Rooster", "#FF532934": "Black Rose Variant", "#FF24252B": "Black Russian Variant", "#FF220022": "Black Sabbath", "#FF434B4D": "Black Sable", "#FF302833": "Black Safflower", "#FF5B4E4B": "Black Sand", "#FF434555": "Black Sapphire", "#FF052462": "Black Sea Night", "#FF0F0D0D": "Black Sheep", "#FF332211": "Black Slug", "#FF3E3E3F": "Black Smoke", "#FF19443C": "Black Soap", "#FF545354": "Black Space", "#FF4C5752": "Black Spruce", "#FFE5E6DF": "Black Squeeze Variant", "#FF0E191C": "Black Stallion", "#FF434342": "Black Suede", "#FF332200": "Black Swan", "#FF464647": "Black Tie", "#FF353235": "Black Tortoise", "#FF463D3E": "Black Truffle", "#FF2C4364": "Black Turmeric", "#FF1F1916": "Black Umber", "#FF222233": "Black Velvet", "#FF2B2C42": "Black Violet", "#FF5E4F46": "Black Walnut", "#FF0C0C0C": "Black Wash", "#FFE5E4DB": "Black White Variant", "#FFE5B6A2": "Black-Eyed Peach", "#FF3E1825": "Black-Hearted", "#FF292C2C": "Blackadder", "#FF43182F": "Blackberry Variant", "#FF2E2848": "Blackberry Black", "#FF4C3938": "Blackberry Burgundy", "#FF404D6A": "Blackberry Cobbler", "#FF4F3357": "Blackberry Cordial", "#FFD9D3DA": "Blackberry Cream", "#FF633654": "Blackberry Deep Red", "#FF62506B": "Blackberry Farm", "#FF504358": "Blackberry Harvest", "#FF87657E": "Blackberry Jam", "#FF507F6D": "Blackberry Leaf Green", "#FFA58885": "Blackberry Mocha", "#FF64242E": "Blackberry Pie", "#FFC1A3B9": "Blackberry Sorbet", "#FF563342": "Blackberry Tart", "#FF8F5973": "Blackberry Tint", "#FF5C3C55": "Blackberry Wine", "#FFE5BDDF": "Blackberry Yoghurt", "#FF3F444C": "Blackbird", "#FFFCE7E4": "Blackbird\u2019s Egg", "#FF274C43": "Blackboard Green", "#FF2E183B": "Blackcurrant Variant", "#FF52383D": "Blackcurrant Conserve", "#FF5C4F6A": "Blackcurrant Elixir", "#FF442200": "Blackened Brown", "#FF504D53": "Blackened Pearl", "#FF77150E": "Blackened Sun", "#FF662266": "Blackest Berry", "#FF403330": "Blackest Brown", "#FF7A5901": "Blackfire Earth", "#FF453B32": "Blackish Brown", "#FF5D6161": "Blackish Green", "#FF5B5C61": "Blackish Grey", "#FF51504D": "Blackjack", "#FF221133": "Blacklist", "#FF220066": "Blackmail", "#FF020F03": "Blackn\u2019t", "#FF0E0702": "Blackout", "#FFF7E856": "Blacksmith Fire", "#FF8470FF": "Blackthorn Berry", "#FF4C606B": "Blackthorn Blue", "#FF739C69": "Blackthorn Green", "#FF545663": "Blackwater", "#FF696268": "Blackwater Park", "#FF494E52": "Blackwood", "#FF6A9266": "Blade Green", "#FFD43C35": "Blade Runner Red", "#FF758269": "Bladed Grass", "#FF6A8561": "Bladerunner", "#FFA1BDE0": "Blair", "#FFD9D0C2": "Blanc", "#FFF1EEE2": "Blanc Cass\u00e9", "#FFE4E7E4": "Blanc de Blanc", "#FFF8F9F4": "Blanca Peak", "#FFF6DCD0": "Blanched", "#FFCCBEB6": "Blanched Driftwood", "#FF949579": "Blanched Thyme", "#FFEBEAE5": "Blanco", "#FFAFA88B": "Bland", "#FF74915F": "Bland Celery", "#FFFFEFD6": "Blank Canvas", "#FF505150": "Blank Space", "#FF8B9CAC": "Blank Stare", "#FF9CD33C": "Blanka Green", "#FF9E8574": "Blanket Brown", "#FF00C08E": "Blarney", "#FF027944": "Blarney Stone", "#FF3356AA": "Blasphemous Blue", "#FFE57E37": "Blast Burn", "#FF6C3550": "Blasted Lands Rocks", "#FFFA8C4F": "Blaze", "#FF420420": "Blaze It Dark Magenta", "#FFFE6700": "Blaze Orange Variant", "#FFB8524B": "Blazer", "#FFE94E41": "Blazing", "#FFF3AD63": "Blazing Autumn", "#FFFFA035": "Blazing Bonfire", "#FFFF0054": "Blazing Dragonfruit", "#FFFFA64F": "Blazing Orange", "#FFFEE715": "Blazing Yellow", "#FFE35F1C": "Blazon Skies", "#FFEBE1CE": "Bleach White Variant", "#FFF3EAD5": "Bleached Almond", "#FFFBCAAD": "Bleached Apricot", "#FFBCE3DF": "Bleached Aqua", "#FFD0C7C3": "Bleached Bare", "#FF8B7F78": "Bleached Bark", "#FFDFDDD0": "Bleached Beige", "#FFEFD9A8": "Bleached Bone", "#FFFFD6D1": "Bleached Coral", "#FF6D76A1": "Bleached Denim", "#FF788878": "Bleached Grey", "#FFE2E6D1": "Bleached Jade", "#FFF3ECE1": "Bleached Linen", "#FFC7A06C": "Bleached Maple", "#FFEAE5D5": "Bleached Meadow", "#FF55BB88": "Bleached Olive", "#FFD9D1BA": "Bleached Pebble", "#FFD5C3AA": "Bleached Sand", "#FFF6E5DA": "Bleached Shell", "#FFF3F3F2": "Bleached Silk", "#FFBAD7AE": "Bleached Spruce", "#FFFBE8A8": "Bleached Sunflower", "#FFDDD2A9": "Bleached Wheat", "#FFDFE3E8": "Bleached White", "#FFC7C7C3": "Bleaches", "#FF9B1414": "Bleeding Crimson", "#FFC02E4C": "Bleeding Heart", "#FFA9C4C4": "Blende Blue", "#FFF8E3A4": "Blended Fruit", "#FFFFFBE8": "Blended Light", "#FF4499CC": "Blessed Blue", "#FF007BA1": "Bleu Ciel", "#FFAAC0C4": "Bleu Clair", "#FF9CC2BF": "Bleu Nattier", "#FF4488FF": "Bleuch\u00e2tel Blue", "#FFBCAEA1": "Blind Date", "#FF223300": "Blind Forest", "#FF4A4F51": "Blindfold", "#FF5A5E61": "Blindfolded", "#FFEEF0CE": "Bling Bling", "#FF0033FF": "Blinking Blue", "#FF66CC00": "Blinking Terminal", "#FF7AC7E1": "Bliss Blue", "#FFDDC4D4": "Blissful", "#FFAA1188": "Blissful Berry", "#FFB2C8D8": "Blissful Blue", "#FFE5D2DD": "Blissful Light", "#FFD5DAEE": "Blissful Meditation", "#FFFFAC39": "Blissful Orange", "#FFEAEED8": "Blissful Serenity", "#FFDAB6CD": "Blissfully Mine", "#FFAAFFEE": "Blister Pearl", "#FFFF6C51": "Blistering Mars", "#FF0099D1": "Blithe", "#FF90BDBD": "Blithe Blue", "#FFC0C1B9": "Blithe Mood", "#FFE5EBED": "Blizzard", "#FFD6D8D1": "Blizzard Fog", "#FF1151B4": "Blizzy Blueberry", "#FFD0B8BF": "Blobfish", "#FFE8BC50": "Blockchain Gold", "#FFDCBD92": "Blonde", "#FFF2EFCD": "Blonde Beauty", "#FFEFE2C5": "Blonde Curl", "#FFEDC558": "Blonde Girl", "#FFD6B194": "Blonde Lace", "#FFF6EDCD": "Blonde Shell", "#FFAB7741": "Blonde Wood", "#FFE5D0B1": "Blonde Wool", "#FF770001": "Blood", "#FF770011": "Blood Brother", "#FFFF474C": "Blood Burst", "#FFEA1822": "Blood Donor", "#FF67080B": "Blood God", "#FFC30B0A": "Blood Kiss", "#FF543839": "Blood Mahogany", "#FFD83432": "Blood Moon", "#FF85323C": "Blood Oath", "#FFE0413A": "Blood of My Enemies", "#FF8A0303": "Blood Omen", "#FFD1001C": "Blood Orange", "#FFFE4B03": "Blood Orange Juice", "#FF630F0F": "Blood Organ", "#FF771111": "Blood Pact", "#FF73404D": "Blood Rose", "#FFAA2222": "Blood Rush", "#FFBB5511": "Bloodhound", "#FF882200": "Bloodline", "#FF6B1C1A": "Bloodlust", "#FFF02723": "Bloodmyst Isle", "#FFBA271A": "Bloodshed", "#FFB52F3A": "Bloodsport", "#FF413431": "Bloodstone", "#FF880011": "Bloodthirsty", "#FFF8D7D0": "Bloodthirsty Beige", "#FFC6101E": "Bloodthirsty Lips", "#FF9B0503": "Bloodthirsty Vampire", "#FFEC1837": "Bloodthirsty Warlock", "#FF703F00": "Bloodtracker Brown", "#FFBA0105": "Bloody Mary", "#FFAA1144": "Bloody Periphylla", "#FFFF004D": "Bloody Pico-8", "#FFCA1F1B": "Bloody Red", "#FFA81C1D": "Bloody Ruby", "#FFC53D43": "Bloody Safflower", "#FFCC4433": "Bloody Salmon", "#FFFFAF75": "Bloom", "#FFD7E2EE": "Blooming Aster", "#FFE77F71": "Blooming Dahlia", "#FFBA93AF": "Blooming Lilac", "#FFD89696": "Blooming Perfect", "#FF88777E": "Blooming Wisteria", "#FFA598C4": "Bloomsberry", "#FFFEE9D8": "Blossom Variant", "#FFAACCEE": "Blossom Blue", "#FFA3A7CC": "Blossom Mauve", "#FFC3B3B9": "Blossom Powder", "#FFE5D2C9": "Blossom Time", "#FFFBD6CA": "Blossom Tint", "#FFFFC9DB": "Blossom Tree", "#FFE1C77D": "Blossom Yellow", "#FFDE5346": "Blossoming Dynasty", "#FFE79ACB": "Blossoms in Spring", "#FF67B7C6": "Blouson Blue", "#FFF6DEE0": "Blowing Kisses", "#FFD2D1D0": "Blowing Smoke", "#FF658499": "Blowout", "#FF25415D": "Blue Accolade", "#FFB1C6C7": "Blue Agave", "#FF89A3AE": "Blue Alps", "#FF5A79BA": "Blue Android Base", "#FFF8B800": "Blue Angels Yellow", "#FFA7CFCB": "Blue Angora", "#FF0085A1": "Blue Arc", "#FFA9B7B8": "Blue Arrow", "#FF414654": "Blue Ash", "#FF406482": "Blue Ashes", "#FF50A7D9": "Blue Astro", "#FF00B3E1": "Blue Atoll", "#FF6C7386": "Blue Aura", "#FF7498BD": "Blue Ballad", "#FFB4C7DB": "Blue Ballerina", "#FF576B6B": "Blue Ballet", "#FF6976A3": "Blue Batik", "#FFABDEE3": "Blue Bauble", "#FF619AD6": "Blue Bay", "#FF2D5360": "Blue Bayberry", "#FFBEC4D3": "Blue Bayou", "#FF017CB8": "Blue Bazaar", "#FF5A809E": "Blue Beads", "#FF7498BF": "Blue Beauty", "#FF220099": "Blue Beetle", "#FF40638E": "Blue Beret", "#FF91B8D9": "Blue Beyond", "#FF00BBEE": "Blue Bikini", "#FF237FAC": "Blue Bird Day", "#FF86B8CB": "Blue Bird Morning", "#FF52593B": "Blue Black Crayfish", "#FF6B7F81": "Blue Blood", "#FF94A4B9": "Blue Blouse", "#FF2242C7": "Blue Blue", "#FFCBD1CF": "Blue Blush", "#FF6181A3": "Blue Boater", "#FF52B4CA": "Blue Bobbin", "#FF01A7AE": "Blue Bolero", "#FF00B9FB": "Blue Bolt", "#FFC8DDEE": "Blue Booties", "#FF0033EE": "Blue Bouquet", "#FFA4C3D7": "Blue Bows", "#FF70B8D0": "Blue Brocade", "#FFA6D7EB": "Blue Bubble", "#FF309CD0": "Blue Burst", "#FFA1A2BD": "Blue Buzz", "#FFA0B7BA": "Blue by You", "#FFA5CDE1": "Blue Calico", "#FF55A7B6": "Blue Calypso", "#FF2F36BA": "Blue Cardinal Flower", "#FF9CD0E4": "Blue Carpenter Bee", "#FF7B9EB0": "Blue Cascade", "#FF41788A": "Blue Catch", "#FF4B8CA9": "Blue Chaise", "#FF94C0CC": "Blue Chalk Variant", "#FF5671A7": "Blue Chamber", "#FF5599FF": "Blue Chaos", "#FF262B2F": "Blue Charcoal Variant", "#FF82C2DB": "Blue Charm", "#FF80693D": "Blue Cheese Olive", "#FF70A6B8": "Blue Chiffon", "#FF408F90": "Blue Chill Variant", "#FF1D5699": "Blue Chip", "#FF77B7D0": "Blue Chrysocolla", "#FF6B9194": "Blue Clay", "#FFA7D8E8": "Blue Click", "#FF627188": "Blue Cloud", "#FF2B3040": "Blue Coal", "#FF0088DC": "Blue Cola", "#FF4411DD": "Blue Copper Ore", "#FF1D5A6E": "Blue Coral", "#FF9EBDD6": "Blue Crab Escape", "#FF6591A8": "Blue Cruise", "#FF6FEBE3": "Blue Crystal Landscape", "#FF7EB4D1": "Blue Cuddle", "#FF84A5DC": "Blue Cue", "#FFCBDBD7": "Blue Cypress", "#FF44DDEE": "Blue Dacnis", "#FF415E9C": "Blue Dahlia", "#FFA2C6D3": "Blue Dam", "#FF2FA1DA": "Blue Damselfly", "#FF0094BB": "Blue Danube", "#FF0078F8": "Blue Darknut", "#FF518FD1": "Blue Dart", "#FF3A7A9B": "Blue Dart Frog", "#FF668DB7": "Blue Dazzle", "#FF4428BC": "Blue Depression", "#FF2C3A64": "Blue Depths", "#FF0B67BE": "Blue Diamond Variant", "#FF35514F": "Blue Dianne Variant", "#FFBCC5CF": "Blue Dolphin", "#FF76799E": "Blue Dove", "#FF879294": "Blue Driftwood", "#FF4A5C94": "Blue Dude", "#FF8C959D": "Blue Dusk", "#FF375673": "Blue Earth", "#FF8DBBC9": "Blue Echo", "#FF035E7B": "Blue Edge", "#FF97D5EA": "Blue Effervescence", "#FF6D9FD1": "Blue Electress", "#FF5588EE": "Blue Elemental", "#FF51529C": "Blue Ember", "#FF0F5A5E": "Blue Emerald", "#FFD1EDEF": "Blue Emulsion", "#FF0D6376": "Blue Enchantment", "#FF6DC1AC": "Blue Energy", "#FF384883": "Blue Estate", "#FF0652FF": "Blue et une Nuit", "#FF253F74": "Blue Expanse", "#FF2B2F43": "Blue Exult", "#FF75AEBD": "Blue Eye Samurai", "#FF7DABD0": "Blue Eyes", "#FF2C3B4D": "Blue Fantastic", "#FFAED9EC": "Blue Feather", "#FF324C61": "Blue Fedora", "#FFADB6AC": "Blue Fescue", "#FF577FAE": "Blue Fin", "#FF51645F": "Blue Fir", "#FF00AADD": "Blue Fire", "#FF007290": "Blue Fjord", "#FF3B506F": "Blue Flag", "#FF005E88": "Blue Flame", "#FFDDEBED": "Blue Flax", "#FFC8D2CD": "Blue Flower", "#FFB4CCC2": "Blue Fluorite", "#FF9BABBB": "Blue Fog", "#FFACB0A9": "Blue Fox", "#FF86D2C1": "Blue Frosting", "#FF2D4470": "Blue Funk", "#FFA2B8CE": "Blue Garter", "#FF4B3C8E": "Blue Gem Variant", "#FF6666FF": "Blue Genie", "#FFB8DCDC": "Blue Glass", "#FF56597C": "Blue Glaze", "#FF92C6D7": "Blue Glint", "#FFB2D4DD": "Blue Glow", "#FFCDD7DF": "Blue Gossamer", "#FF69A2D5": "Blue Gourami", "#FF76798D": "Blue Granite", "#FF3A383F": "Blue Graphite", "#FF007A7C": "Blue Grass", "#FF137E6D": "Blue Green Variant", "#FF7CCBC5": "Blue Green Gem", "#FFD8EEED": "Blue Green Rules", "#FF56B78F": "Blue Green Scene", "#FF758DA3": "Blue Grey", "#FF50A2CA": "Blue Grotto", "#FF9ABCDC": "Blue Grouse", "#FFBDBACE": "Blue Haze Variant", "#FF5566FF": "Blue Heath Butterfly", "#FFAEBBC1": "Blue Heather", "#FF5B7E98": "Blue Heaven", "#FF939CAB": "Blue Heeler", "#FF006384": "Blue Heist", "#FF6666EE": "Blue Hepatica", "#FF939EC5": "Blue Heron", "#FF324A8B": "Blue Highlight", "#FFD0EEFB": "Blue Hijab", "#FF1E454D": "Blue Hill", "#FF289DBE": "Blue Horizon", "#FFA2BAD2": "Blue Horror", "#FF2A6F73": "Blue Hosta", "#FF0034AB": "Blue Hour", "#FF394D60": "Blue Hue", "#FF8394C5": "Blue Hyacinth", "#FFBBC3DD": "Blue Hydrangea", "#FF539CCC": "Blue Iguana", "#FF535A7C": "Blue Indigo", "#FF566977": "Blue Insignia", "#FF7F809C": "Blue Intrigue", "#FF587EBE": "Blue Iolite", "#FF6264A6": "Blue Iris", "#FF22AAAA": "Blue Island", "#FF597193": "Blue Jacket", "#FF828596": "Blue Jasmine", "#FFC1EBFF": "Blue Java Banana", "#FF5588DD": "Blue Jay", "#FF01708A": "Blue Jay Crest", "#FF465383": "Blue Jewel", "#FF002D72": "Blue Jinn\u2019s", "#FFBCE6E8": "Blue Karma", "#FF1D7881": "Blue Kelp", "#FFE2E5E2": "Blue Kiss", "#FF00626F": "Blue Lagoon Variant", "#FF2E5169": "Blue Lava", "#FF006284": "Blue League", "#FF032A62": "Blue Leviathan", "#FFACDFDD": "Blue Light", "#FF7FCCE2": "Blue Limewash", "#FF5A5E6A": "Blue Linen", "#FFA6BCE2": "Blue Lips", "#FF28314D": "Blue Lobelia", "#FF0055AA": "Blue Lobster", "#FF486D83": "Blue Loneliness", "#FFC8D7D2": "Blue Lullaby", "#FFB2C4CD": "Blue Luna", "#FF012389": "Blue Lust", "#FF007593": "Blue Luxury", "#FF5F34E7": "Blue Magenta", "#FF553592": "Blue Magenta Violet", "#FF9AC0DE": "Blue Magpie", "#FF68C2F5": "Blue Mana", "#FF6594BC": "Blue Marble", "#FF6A5BB1": "Blue Marguerite Variant", "#FF1FCECB": "Blue Martina", "#FF52B4D3": "Blue Martini", "#FFC9DCE7": "Blue Me Away", "#FF67A6AC": "Blue Mercury", "#FF014C76": "Blue Meridian", "#FF5A6370": "Blue Metal", "#FF5C6D7C": "Blue Mirage", "#FF5BACC3": "Blue Mist", "#FF637983": "Blue Monday", "#FF7A808D": "Blue Mood", "#FF3992A8": "Blue Moon", "#FF588496": "Blue Moon Bay", "#FF21426B": "Blue Mosque", "#FF759DBE": "Blue Mountain", "#FF2539BF": "Blue Murder", "#FF1199FF": "Blue Nebula", "#FF414657": "Blue Nights", "#FF779FB9": "Blue Nile", "#FFD2DDE0": "Blue Nuance", "#FF29518C": "Blue Nude", "#FF88A4AE": "Blue Nuthatch", "#FF647E9C": "Blue Oar", "#FF296D93": "Blue Oasis", "#FF26428B": "Blue Oblivion", "#FF4F6997": "Blue Odyssey", "#FF015193": "Blue Olympus", "#FF124168": "Blue Opal", "#FF0020EF": "Blue Overdose", "#FF5577EE": "Blue \u00d6yster Cult", "#FF2282A8": "Blue Paisley", "#FF01546D": "Blue Palisade", "#FF5095C3": "Blue Paradise", "#FF8080FF": "Blue Party Parrot", "#FFC5D9E3": "Blue Pearl", "#FF2200FF": "Blue Pencil", "#FFBCD7DF": "Blue Perennial", "#FF075158": "Blue Period", "#FF5B92AC": "Blue Persia", "#FFD2E6E8": "Blue Phlox", "#FFB5A3C5": "Blue Pink", "#FF545E6A": "Blue Planet", "#FF5B7A9C": "Blue Plate", "#FF30363C": "Blue Plaza", "#FF95B9D6": "Blue Pointer", "#FFA1B1C2": "Blue Pot", "#FF64617B": "Blue Potato", "#FF6A808F": "Blue Prince", "#FFABC4DB": "Blue Prism", "#FF729CC2": "Blue Promise", "#FF5729CE": "Blue Purple", "#FF43505E": "Blue Quarry", "#FF335287": "Blue Quartz", "#FF4BA4A9": "Blue Racer", "#FF58CFD4": "Blue Radiance", "#FFC4D6E1": "Blue Raindrop", "#FF00177D": "Blue Ranger", "#FF0CBFE9": "Blue Raspberry", "#FF3AA2C6": "Blue Raspberry Seed", "#FFCCD7E1": "Blue Reflection", "#FFB0D8E7": "Blue Refrain", "#FF303048": "Blue Regal", "#FF376298": "Blue Regatta", "#FF285991": "Blue Regent", "#FF4E5878": "Blue Review", "#FF3E6490": "Blue Ribbon Beauty", "#FFB3D9F3": "Blue Rice", "#FF75A6BB": "Blue Ridge Mist", "#FFB7BDC6": "Blue Rinse", "#FF377D95": "Blue Rodeo", "#FFD8F0D2": "Blue Romance Variant", "#FF292D74": "Blue Rose", "#FF29217A": "Blue Royale", "#FF0066DD": "Blue Ruin", "#FF575F6A": "Blue Sabre", "#FF57747A": "Blue Sage", "#FF24549A": "Blue Sail", "#FF666A76": "Blue Sari", "#FF9AD6E8": "Blue Sarong", "#FF494D58": "Blue Sash", "#FF9EB6D0": "Blue Satin", "#FF0033BB": "Blue Screen of Death", "#FF546E77": "Blue Sentinel", "#FF293F54": "Blue Shade Wash", "#FF758CA4": "Blue Shadow", "#FFB9CACC": "Blue Shale", "#FFBACBC4": "Blue Shamrock", "#FF9BB3BC": "Blue Shell", "#FFB3DAE2": "Blue Shimmer", "#FF6B8C93": "Blue Shoal", "#FF7593CB": "Blue Shock", "#FF93BDE7": "Blue Shutters", "#FFD0DCE8": "Blue Silk", "#FF95AFDC": "Blue Skies Today", "#FF8CA6B5": "Blue Skylights", "#FF5A5F68": "Blue Slate", "#FF008793": "Blue Slushie", "#FF5786B4": "Blue Smart", "#FFD7E0E2": "Blue Smoke Variant", "#FF4A87CB": "Blue Sonki", "#FF404956": "Blue Sou\u2019wester", "#FF0077FF": "Blue Sparkle", "#FF3B5C6C": "Blue Spell", "#FF2E85B1": "Blue Splash", "#FFADC5C9": "Blue Spruce", "#FF508A9A": "Blue Square", "#FF577284": "Blue Stone Variant", "#FF2266BB": "Blue Streak", "#FF95CDD8": "Blue Stream", "#FF687B92": "Blue Suede", "#FF484B62": "Blue Suede Shoes", "#FF829D99": "Blue Surf", "#FF1B4556": "Blue Syzygy", "#FF2A4B6E": "Blue Tang", "#FF3FA4D2": "Blue Tango", "#FF475C62": "Blue Tapestry", "#FF849DA2": "Blue Tea", "#FF5885A2": "Blue Team Spirit", "#FFADC0D6": "Blue Thistle", "#FF677F86": "Blue Thunder", "#FF9FD9D7": "Blue Tint", "#FF4466FF": "Blue Titmouse", "#FF0190C0": "Blue Variant", "#FFBABFC5": "Blue Tint", "#FF2B4057": "Blue Tone Ink", "#FF65AECE": "Blue Topaz", "#FF042993": "Blue Torus", "#FFA9B8C8": "Blue Tribute", "#FF4376AB": "Blue Triumph", "#FF5C4671": "Blue Tulip", "#FFC9DBE5": "Blue Tulle", "#FF6F95C1": "Blue Tuna", "#FF50ABAE": "Blue Turquoise", "#FF1E7EAE": "Blue Vacation", "#FF4E83BD": "Blue Vault", "#FFAECBE5": "Blue Veil", "#FF0D6183": "Blue Velvet", "#FF397C80": "Blue Venus", "#FF4E32B2": "Blue Violet Tint", "#FF3D4457": "Blue Vortex", "#FF1E3442": "Blue Whale Variant", "#FFA8BBBA": "Blue Willow", "#FF2E4556": "Blue Wing Teal", "#FF00827C": "Blue Winged Teal", "#FF404664": "Blue Wonder", "#FF5A77A8": "Blue Yonder", "#FF5B6676": "Blue Zephyr", "#FF3C4354": "Blue Zodiac Variant", "#FF24313D": "Blue-Black", "#FF005F7A": "Blue-Collar", "#FF2277CC": "Blue-Eyed Boy", "#FF5C767F": "Blue-Eyed Cryptid", "#FF0000DD": "Bluealicious", "#FFABB5C4": "Bluebeard", "#FF464196": "Blueberry", "#FF836268": "Blueberry Blush", "#FF8C99B3": "Blueberry Buckle", "#FF586E84": "Blueberry Dream", "#FF4D8CB9": "Blueberry Festival", "#FFCC66DD": "Blueberry Glaze", "#FFC6D8E4": "Blueberry Ice", "#FFD01343": "Blueberry Lemonade", "#FFCBCCDF": "Blueberry Mist", "#FF5588AB": "Blueberry Muffin", "#FF627099": "Blueberry Patch", "#FF314D67": "Blueberry Pie", "#FF5488C0": "Blueberry Popover", "#FF8290A6": "Blueberry Soda", "#FF5E96C3": "Blueberry Soft Blue", "#FF3F4050": "Blueberry Tart", "#FF24547D": "Blueberry Twist", "#FFD1D4DB": "Blueberry Whip", "#FF00A9B8": "Bluebird", "#FF6F9DB3": "Bluebird Feather", "#FF7395B8": "Bluebird\u2019s Belly", "#FF1C1CF0": "Bluebonnet", "#FF8ECFE8": "Bluebottle", "#FF4F9297": "Bluebound", "#FF6ABCDA": "Bluebrite", "#FF535A61": "Blued Steel", "#FF324C64": "Bluedgeons", "#FF35637C": "Blueprint", "#FF6F8479": "Blueridge Fir", "#FF1F66FF": "Bluerocratic", "#FF296A9D": "Blues", "#FF2D47FF": "Blueshift", "#FF6081A2": "Bluestone Path", "#FF7C9AB5": "Bluesy Note", "#FF9EBED8": "Bluette", "#FFE2E6E0": "Bluewash", "#FF375978": "Bluey", "#FFD2BD9E": "Bluff Stone", "#FF2976BB": "Bluish", "#FF413F44": "Bluish Black", "#FF10A674": "Bluish Green", "#FF748B97": "Bluish Grey", "#FFD0D5D3": "Bluish Lilac Purple", "#FF703BE7": "Bluish Purple", "#FF6666BB": "Bluish Purple Anemone", "#FF89CFDB": "Bluish Water", "#FF305C71": "Blumine Variant", "#FFB5BBC7": "Blunt", "#FF8D6C7A": "Blunt Violet", "#FF5539CC": "Blurple", "#FFF29E8E": "Blush Tint", "#FFEDD5C7": "Blush Beige", "#FFDD99AA": "Blush Bomb", "#FFCC88DD": "Blush Essence", "#FFFF6F91": "Blush Hour", "#FFEABCC0": "Blush Kiss", "#FFD9E6E0": "Blush Mint", "#FFDCCBD1": "Blush of Hyacinth", "#FFDCD1D5": "Blush of Morn", "#FFF0BCBE": "Blush Rush", "#FFE2E0D8": "Blush Sand", "#FFDEE1ED": "Blush Sky", "#FFEE88CC": "Blushed Bombshell", "#FFF0E0D2": "Blushed Cotton", "#FFDEC5D3": "Blushed Velvet", "#FFF0D1C3": "Blushing", "#FFFBBCA7": "Blushing Apricot", "#FFEEDAD1": "Blushing Bride", "#FFDD9999": "Blushing Bud", "#FFFFCDAF": "Blushing Cherub", "#FFFFBF99": "Blushing Cinnamon", "#FFEBD5CA": "Blushing Coconut", "#FFD3D4E5": "Blushing Lilac", "#FFFFD79F": "Blushing Peach", "#FFE09B81": "Blushing Rose", "#FFF3CACB": "Blushing Senorita", "#FFD9B1D6": "Blushing Sky", "#FFE3A1B8": "Blushing Tulip", "#FF4A5A6F": "Bluster Blue", "#FF4411FF": "Blustering Blue", "#FFD6DFE7": "Blustery Day", "#FF6F848C": "Blustery Sky", "#FFB6C5C1": "Blustery Wind", "#FF1D5BD6": "Bnei Brak Bay", "#FF7F7755": "Boa", "#FFEDE7D5": "Board & Batten", "#FF757760": "Boardman", "#FFC7B6AA": "Boardwalk Sashay", "#FF6C6B6A": "Boat Anchor", "#FF2D5384": "Boat Blue", "#FF577190": "Boathouse", "#FF087170": "Boating Green", "#FF243256": "Boatswain", "#FF97C5DA": "Bobby Blue", "#FFEADFD0": "Bobcat Whiskers", "#FF22BB11": "Boboli Gardens", "#FF5D341A": "Bock", "#FFDF8F67": "Bockwurst", "#FFB2619D": "Bodacious", "#FF5E81C1": "Bodega Bay", "#FFB09870": "Bodhi Tree", "#FF3D4652": "Boeing Blue", "#FF973443": "Boerewors", "#FFBAB796": "Bog", "#FFBAC3B9": "Bog Fog", "#FF8B8274": "Bogart", "#FF116F26": "Bogey Green", "#FF663B3A": "Bogong Moth", "#FF3B373C": "Bohemian Black", "#FF0000AA": "Bohemian Blue", "#FF9D777C": "Bohemian Jazz", "#FFB8B3C8": "Bohemianism", "#FF7B684D": "Boho", "#FFE58787": "Boho Blush", "#FFB96033": "Boho Copper", "#FF00EE11": "Boiling Acid", "#FFFF3300": "Boiling Magma", "#FFA59C9B": "Boiling Mud", "#FFD7E9E8": "Boiling Point", "#FFBCCAB3": "Bok Choy", "#FF2A2725": "Bokara Grey", "#FF879550": "Bold Avocado", "#FF1D6575": "Bold Bolection", "#FF796660": "Bold Brandy", "#FF8C5E55": "Bold Brick", "#FF463D2F": "Bold Eagle", "#FFEAD56D": "Bold Gold", "#FF2A814D": "Bold Irish", "#FF7A4549": "Bold Sangria", "#FF88464A": "Bolero", "#FFDEBB32": "Bollywood", "#FFFFFBAB": "Bollywood Gold", "#FF9C6F6C": "Bologna", "#FFFFCFDC": "Bologna Sausage", "#FFBB4400": "Bolognese", "#FF393939": "Boltgun Metal", "#FFD8BABC": "Bombay Pink", "#FF5E496A": "Bon Vivant", "#FF8BAEB2": "Bon Voyage", "#FF304471": "Bona Fide", "#FFCBB9AB": "Bona Fide Beige", "#FFE6E2D7": "Bonaire", "#FF523B2C": "Bonanza", "#FF8C4268": "Bonbon Red", "#FF16698C": "Bondi", "#FFE0D7C6": "Bone Tint", "#FF9D7446": "Bone Brown", "#FFF3EDDE": "Bone China", "#FFD7D0C0": "Bone Trace", "#FFF1E1B0": "Bone White", "#FFE1F2F0": "Bone-Chilling", "#FFBB9977": "Boneyard", "#FFF78058": "Bonfire", "#FFD7951F": "Bonfire Glow", "#FFDE6A41": "Bonfire Night", "#FFD7AF7A": "Bonfire Toffee", "#FFD2C2B2": "Bongo Drum", "#FFDECE96": "Bongo Skin", "#FFDFD7D2": "Bonjour", "#FFF54D79": "Bonker Pink", "#FF3A4866": "Bonne Nuit", "#FF8DBBD1": "Bonnie Blue", "#FFFDEFD2": "Bonnie Cream", "#FFE4D1BC": "Bonnie Dune Beach", "#FF7C644A": "Bonnie\u2019s Bench", "#FFC58EAB": "Bonny Belle", "#FF787B54": "Bonsai", "#FF9E9E7C": "Bonsai Garden", "#FFB8B19A": "Bonsai Pot", "#FFC5D1B2": "Bonsai Tint", "#FF6C6D62": "Bonsai Trunk", "#FFFFA00A": "Bonus Level", "#FF5E6B44": "Bonza Green", "#FF84AFD5": "Boo Blue", "#FF9BB53C": "Booger", "#FF00FF77": "Booger Buster", "#FF119944": "Boogie Blast", "#FF805D5B": "Book Binder", "#FF8C3432": "Bookstone", "#FFEBE3DE": "Bookworm", "#FFAFC2CF": "Boot Cut", "#FFDDAF8E": "Boot Hill Ghost", "#FF793721": "Bootstrap Leather", "#FF7FC6BE": "Booty Bay", "#FF92D0D0": "Bora Bora Shore", "#FF507EA4": "Borage", "#FF5566CC": "Borage Blue", "#FF7B002C": "Bordeaux Variant", "#FFEFBCDE": "Bordeaux Hint", "#FF5C3944": "Bordeaux Leaf", "#FF6F2C4F": "Bordeaux Red", "#FFC69B58": "Borderline", "#FFEE1166": "Borderline Pink", "#FF717E73": "Boreal", "#FFDEDD98": "Bored Accent Green", "#FF8C9C9C": "Boredom", "#FFFF8E51": "Boredom Buster", "#FF06470C": "Borg Drone", "#FF054907": "Borg Queen", "#FF63B365": "Boring Green", "#FFD9B1AA": "Borlotti Bean", "#FFA76244": "Borneo", "#FF8C2C24": "Borscht", "#FFC09056": "Bosc Pear", "#FF76A0AF": "Bosco Blue", "#FF552C1C": "Boson Brown", "#FFE7DBE1": "B\u014ds\u014dzoku Pink", "#FF008468": "Bosphorus", "#FF015D75": "Bosporus", "#FF4C3D4E": "Bossa Nova", "#FF767C9E": "Bossa Nova Blue", "#FFCB8EB1": "Bossy-Pants Pink", "#FF438EAC": "Boston Blue Variant", "#FF87544E": "Boston Brick", "#FF614432": "Boston Brown Bread", "#FF90966D": "Boston Fern", "#FF685043": "Boston Legacy", "#FFCC0002": "Boston University Red Variant", "#FFA2345C": "B\u014dtan", "#FF4D6E2F": "Botanical", "#FF227700": "Botanical Beauty", "#FFA3BFB9": "Botanical Bliss", "#FF44AA11": "Botanical Garden", "#FF77976E": "Botanical Green", "#FF12403C": "Botanical Night", "#FFA7E6D4": "Botanical Tint", "#FF9AD28C": "Botanist", "#FFB70272": "Botticelli Variant", "#FFFBDFD6": "Botticelli Angel", "#FF238E50": "Bottle Glass", "#FF006A4E": "Bottle Green Variant", "#FFE8EDB0": "Bottlebrush Blossom", "#FF095BAF": "Bottled Sea", "#FF9B6944": "Bottled Ship", "#FFBFC4C9": "Bottlefly Wings", "#FF6A7074": "Bottlenose Dolphin", "#FFCC0077": "Bottom of My Heart", "#FFDAB27D": "Boudin", "#FFD9C0AB": "Boudoir Beige", "#FF7EA3D2": "Boudoir Blue", "#FF9884B9": "Bougainvillaea", "#FF576145": "Boughs of Pine", "#FF7C817C": "Boulder Variant", "#FF655E4E": "Boulder Brown", "#FF8C9496": "Boulder Creek", "#FFD40701": "Boulevardier", "#FF49A462": "Bouncy Ball Green", "#FF5B6D84": "Boundless", "#FF5A8299": "Bountiful Blue", "#FFE4C36C": "Bountiful Gold", "#FFA78199": "Bouquet Variant", "#FFAF6C3E": "Bourbon Variant", "#FFEC842F": "Bourbon Peach", "#FF6C5654": "Bourbon Truffle", "#FFEE0066": "Bourgeois", "#FF637A72": "Bournonite Green", "#FFE1CEAD": "Boutique Beige", "#FF8CC1D6": "Boutonniere", "#FF52585C": "Bovine", "#FFBE2633": "Bow Tie", "#FF126DA8": "Bowen Blue", "#FF006585": "Bowerbird Blue", "#FFBFDEAF": "Bowling Green", "#FFD7BD92": "Bowman Beige", "#FF587176": "Bowman Blue", "#FF536B1F": "Bowser Shell", "#FFD6D1C8": "Bowstring", "#FF898790": "Box Office", "#FF873D30": "Boxcar", "#FF707B71": "Boxwood", "#FFEFE4A5": "Boxwood Yellow", "#FF8CACD6": "Boy Blue", "#FFB3111D": "Boy Red", "#FF635C53": "Boycott", "#FF9F4E3E": "Boynton Canyon", "#FF873260": "Boysenberry", "#FFA1395D": "Boysenberry Pink", "#FFF1F3F9": "Boysenberry Shadow", "#FF2A96D5": "Boyzone", "#FF014182": "Bracing Blue", "#FF5B3D27": "Bracken Variant", "#FF31453B": "Bracken Fern", "#FF626F5D": "Bracken Green", "#FF84726C": "Bradford Brown", "#FF77675B": "Braid", "#FFE9B578": "Braided Mat", "#FFE1D0AF": "Braided Raffia", "#FF00EEFF": "Brain Freeze", "#FFF2AEB1": "Brain Pink", "#FFB5B5B5": "Brainstem Grey", "#FFD1D3C0": "Brainstorm", "#FF74685A": "Brainstorm Bronze", "#FF65635B": "Braintree", "#FFEE0033": "Brake Light Trails", "#FF503629": "Bramble Bush", "#FFC71581": "Bramble Jam", "#FF9BA29D": "Brampton Grey", "#FFA9704C": "Bran", "#FF9A7F55": "Branching-Out Olive", "#FF706F64": "Brandenburg Gate", "#FFA37C79": "Brandied Apple", "#FFC27275": "Brandied Apricot", "#FFCC7753": "Brandied Melon", "#FFEAE2D1": "Brandied Pears", "#FFDCB68A": "Brandy Variant", "#FFF3E2DC": "Brandy Alexander", "#FFAA5412": "Brandy Bear", "#FF73342A": "Brandy Brown", "#FFF3BB8F": "Brandy Butter", "#FFC07C40": "Brandy Punch Variant", "#FFB6857A": "Brandy Rose Variant", "#FFB58E8B": "Brandy Snaps", "#FF490206": "Brandywine", "#FF5555AA": "Brandywine Raspberry", "#FFE69DAD": "Brandywine Spritz", "#FFB5A642": "Brass", "#FFE7BD42": "Brass Balls", "#FFDFAC4C": "Brass Buttons", "#FFB9A70F": "Brass Knuckle", "#FFE1A84B": "Brass Mesh", "#FFDBBD76": "Brass Nail", "#FF773B2E": "Brass Scorpion", "#FFD3B280": "Brass Trumpet", "#FFB58735": "Brass Yellow", "#FFCFA743": "Brassed Off", "#FF788879": "Brassica", "#FFF3BC6B": "Brasso", "#FFD5AB2C": "Brassy", "#FF776022": "Brassy Brass", "#FFD8AB39": "Brassy Tint", "#FF454743": "Brattle Spruce", "#FF582F2B": "Bratwurst", "#FF897058": "Braun", "#FFA0524E": "Bravado Red", "#FF015B6D": "Brave New Teal", "#FFFF631C": "Brave Orange", "#FF968DB8": "Brave Purple", "#FFD3E7E9": "Bravo Blue", "#FF8C80B9": "Bravo!", "#FFA87F12": "Brazen", "#FF7B6623": "Brazen Brass", "#FFCE7850": "Brazen Orange", "#FF856765": "Brazil Nut", "#FF7F5131": "Brazilian Brown", "#FFAF915D": "Brazilian Citrine", "#FF296D23": "Brazilian Green", "#FFD8C6B4": "Brazilian Sand", "#FF31D652": "Brazilianite", "#FFFFD182": "Bread and Butter", "#FFAB8659": "Bread Basket", "#FFE4D4BE": "Bread Crumb", "#FFB78B43": "Bread Crust", "#FFDCD6D2": "Bread Flavour", "#FFE6BC89": "Bread Pudding", "#FFB48C56": "Breadstick", "#FFFFFABD": "Break of Day", "#FFB2E1EE": "Break the Ice", "#FFCEDAC3": "Breakaway", "#FF424D60": "Breakaway Blue", "#FFE5EDED": "Breaker", "#FF517B78": "Breaker Bay Variant", "#FFF6E3D3": "Breakfast Biscuit", "#FF6D5542": "Breakfast Blend", "#FFB8E4F5": "Breaking the Ice", "#FF00A0B0": "Breaking Wave", "#FFC4D9CE": "Breaktime", "#FFD1DEE4": "Breakwater", "#FFEBF1E9": "Breakwater White", "#FFDCE7CB": "Breath of Celery", "#FFEE0011": "Breath of Fire", "#FFC7DBE4": "Breath of Fresh Air", "#FFBDD1CE": "Breath of Green", "#FFE9E1A7": "Breath of Spring", "#FFD1D2B8": "Breathe", "#FF015348": "Breathe Deeply", "#FFDFDAE0": "Breathless", "#FF536193": "Breathtaking", "#FFC3ACB7": "Breathtaking Evening", "#FF809BAC": "Breathtaking View", "#FF5E9948": "Bredon Green", "#FF795D34": "Breen", "#FFAEC9EA": "Breeze", "#FFC4DFE8": "Breeze in June", "#FFF4706E": "Breeze of Chilli", "#FFCFFDBC": "Breeze of Green", "#FFD6DBC0": "Breezeway", "#FFC2DDE6": "Breezy", "#FFD9E4DE": "Breezy Aqua", "#FFF7F2D7": "Breezy Beige", "#FFBAD9E5": "Breezy Blue", "#FFBDC4B8": "Breezy Day", "#FFC1D9E9": "Breezy Touch", "#FFCECEDF": "Breezy Violet", "#FF2D567C": "Breonne Blue", "#FF0080FF": "Brescian Blue", "#FFAA5555": "Bretzel Brown", "#FF715243": "Brevity Brown", "#FFE68364": "Brewed Mustard", "#FF777788": "Brewing Storm", "#FF745443": "Briar", "#FFC07281": "Briar Rose", "#FF695451": "Briar Wood", "#FFA03623": "Brick", "#FF77603F": "Brick Brown", "#FFB22122": "Brick by Brick", "#FFAB685F": "Brick Dust", "#FFB38070": "Brick Fence", "#FF956159": "Brick Hearth", "#FFC14A09": "Brick Orange", "#FFC2977C": "Brick Path", "#FF93402F": "Brick Paver", "#FF8F1402": "Brick Red Variant", "#FFD2A161": "Brick Yellow", "#FFA75C3D": "Brick-A-Brack", "#FF864A36": "Brickhouse", "#FFDB5856": "Bricks of Hope", "#FF825943": "Bricktone", "#FF986971": "Brickwork Red", "#FFB33A22": "Bricky Brick", "#FFAB6A64": "Brickyard", "#FFEBBDB8": "Bridal Bouquet", "#FFF8EBDD": "Bridal Heath Variant", "#FFD1949C": "Bridal Rose", "#FFE5D3CC": "Bridal Scent", "#FFE7E1DE": "Bridal Veil", "#FFE5E7E5": "Bridal Wreath", "#FFEFE7EB": "Bride", "#FFFAE6DF": "Bridesmaid Variant", "#FF817F6E": "Bridge Troll Grey", "#FF004683": "Bridgeport", "#FF527065": "Bridgewater", "#FFBCD7E2": "Bridgewater Bay", "#FF575144": "Bridgewood", "#FF8F7D70": "Bridle Leather", "#FFA29682": "Bridle Path", "#FF545E4F": "Brierwood Green", "#FF4FA1C0": "Brig", "#FFDDCFBF": "Brig O\u2019doon", "#FF365D73": "Brigade", "#FF0063A0": "Brigadier Blue", "#FFB5DDEB": "Bright and Breezy", "#FF0BF9EA": "Bright Aqua", "#FF0165FC": "Bright Blue Variant", "#FF9DA7CF": "Bright Bluebell", "#FF90B3C2": "Bright Bluebonnet", "#FFA05822": "Bright Bronze", "#FF533B32": "Bright Brown", "#FFFFC42A": "Bright Bubble", "#FFADBFC8": "Bright Chambray", "#FFDFFF11": "Bright Chartreuse", "#FFFFC6A5": "Bright Citrus", "#FFEFCF9B": "Bright Clove", "#FF3C6098": "Bright Cobalt", "#FF41FDFE": "Bright Cyan", "#FFCD5B26": "Bright Delight", "#FFEEE9F9": "Bright Dusk", "#FFFEFFCA": "Bright Ecru", "#FF5A4E88": "Bright Eggplant", "#FF728A51": "Bright Forest", "#FFCF9F52": "Bright Gold", "#FF3844F4": "Bright Greek", "#FFEBECF0": "Bright Grey", "#FFFFD266": "Bright Halo", "#FFECBE63": "Bright Idea", "#FF6F00FE": "Bright Indigo", "#FFF1E78C": "Bright Khaki", "#FF9F3645": "Bright Lady", "#FFF0EDD1": "Bright Laughter", "#FF8DCE65": "Bright Lettuce", "#FF2DFE54": "Bright Light Green", "#FF87FD05": "Bright Lime", "#FF65FE08": "Bright Lime Green", "#FFC1B9AA": "Bright Loam", "#FFFF08E8": "Bright Magenta", "#FFFF8830": "Bright Mango", "#FFEB7E00": "Bright Marigold", "#FF011993": "Bright Midnight", "#FFF6F1E5": "Bright Moon", "#FF225869": "Bright Nautilus", "#FF2D5E22": "Bright Nori", "#FFF0E8DA": "Bright Ocarina", "#FF9CBB04": "Bright Olive", "#FF549C38": "Bright Parrot", "#FFBE03FD": "Bright Purple", "#FFFF000D": "Bright Red Variant", "#FFC51959": "Bright Rose", "#FFFFCF09": "Bright Saffron", "#FFD1CEB4": "Bright Sage", "#FFFC0E34": "Bright Scarlet", "#FF9FE2BF": "Bright Sea Green", "#FFB1AA9C": "Bright Sepia", "#FF02CCFE": "Bright Sky Blue", "#FF76C1E1": "Bright Spark", "#FFDDE2E6": "Bright Star", "#FFECBD2C": "Bright Sun Variant", "#FF01F9C6": "Bright Teal", "#FFAD0AFD": "Bright Violet", "#FFF6F2F1": "Bright White", "#FFF5EFE8": "Bright Winter Cloud", "#FFFACE6D": "Bright Yarrow", "#FFFFFE42": "Bright Yellow", "#FF9DFF00": "Bright Yellow Green", "#FF757CAE": "Bright Zenith", "#FFE2681B": "Brihaspati Orange", "#FFDAB77F": "Brik Dough", "#FFFDFDFD": "Brilliance", "#FF0094A7": "Brilliant", "#FF3399FF": "Brilliant Azure", "#FFEFC5B5": "Brilliant Beige", "#FF0075B3": "Brilliant Blue", "#FFAD548F": "Brilliant Carmine", "#FFF0DBAA": "Brilliant Gold", "#FF88B407": "Brilliant Green", "#FFEFC600": "Brilliant Impression", "#FF545454": "Brilliant Liquorice", "#FF8C6143": "Brilliant Oak", "#FF009CB7": "Brilliant Sea", "#FFA9B0B4": "Brilliant Silver", "#FF00A68B": "Brilliant Turquoise", "#FFE8EEFE": "Brilliant White", "#FFE8E5D8": "Brilliant Yellow", "#FFA3DAD4": "Brilliante", "#FF7A958C": "Brimming Over", "#FFFFBD2B": "Brimstone", "#FFC2C190": "Brimstone Butterfly", "#FF82776B": "Brindle", "#FF08808E": "Briny", "#FFDFCFC3": "Brioche", "#FFE15F65": "Briquette", "#FF515051": "Briquette Grey", "#FFD2E0EF": "Brisa de Mar", "#FF6D829D": "Brisk Blue", "#FFAAA97F": "Brisk Olive", "#FF6E4534": "Brisket", "#FFA28450": "Bristle Grass", "#FF93836F": "Bristol Beige", "#FF558F91": "Bristol Blue", "#FF83A492": "Bristol Green", "#FFA09073": "Britches", "#FFFEDE8F": "Brite Gold", "#FF7D7081": "British Grey Mauve", "#FFBCAF97": "British Khaki", "#FF35427B": "British Mauve", "#FFFF0015": "British Phone Booth", "#FF05480D": "British Racing Green Variant", "#FFF4C8DB": "British Rose", "#FF4C7E86": "Brittany Blue", "#FFF3D8E0": "Brittany\u2019s Bow", "#FFEAAE47": "Brittlebush", "#FF94975D": "Broad Bean", "#FFBBDDFF": "Broad Daylight", "#FF034A71": "Broadwater Blue", "#FF145775": "Broadway", "#FFFEE07C": "Broadway Lights", "#FF8C87C5": "Brocade", "#FF7B4D6B": "Brocade Violet", "#FF8FA277": "Broccoflower", "#FF87B364": "Broccoli", "#FF4B5338": "Broccoli Green", "#FF008833": "Broccoli Paradise", "#FF486262": "Brochantite Green", "#FFFFDD88": "Broiled Flounder", "#FF74BBFB": "Broken Blue", "#FF060310": "Broken Tube", "#FFEEEBE3": "Broken White", "#FFA79781": "Bronco Variant", "#FFA87900": "Bronze Tint", "#FF3A4856": "Bronze Blue", "#FFFBC378": "Bronze Blush", "#FF825E2F": "Bronze Brown", "#FFEB9552": "Bronze Cup", "#FF6E6654": "Bronze Fig", "#FF8D8752": "Bronze Green", "#FF585538": "Bronze Icon", "#FFAA8031": "Bronze Leaf", "#FF6D6240": "Bronze Medal", "#FFA37F44": "Bronze Mist", "#FF584C25": "Bronze Olive Variant", "#FFE6BE9C": "Bronze Sand", "#FFCC5533": "Bronze Satin", "#FFBC8040": "Bronze Storm", "#FF434C28": "Bronze Tone", "#FFB08D57": "Bronze Treasure", "#FF737000": "Bronze Yellow", "#FFDD6633": "Bronzed", "#FF9B7E4E": "Bronzed Brass", "#FFD78A6C": "Bronzed Orange", "#FF69605A": "Brood", "#FF5E6D6E": "Brooding Storm", "#FFAFDDCC": "Brook Green", "#FFDACECD": "Brook Trout", "#FFE7EEEE": "Brooklet", "#FF586766": "Brooklyn", "#FF6D4B3F": "Brooklyn Brownstone", "#FF5A7562": "Brookside", "#FF99B792": "Brookview", "#FFEECC24": "Broom Variant", "#FF74462D": "Broomstick", "#FFB0B7C6": "Brother Blue", "#FF653700": "Brown Variant", "#FF4A3F37": "Brown Bear", "#FF4A3832": "Brown Beauty", "#FFCC8833": "Brown Beige", "#FF53331E": "Brown Bramble Variant", "#FFB08F6A": "Brown Branch", "#FFD4C5A9": "Brown Bread", "#FFC2AE93": "Brown Bunny", "#FFAC7C00": "Brown Butter", "#FF876140": "Brown Button", "#FFC5C2AD": "Brown Buzz", "#FF995555": "Brown Cerberus", "#FF5F1933": "Brown Chocolate", "#FFC37C59": "Brown Clay", "#FF4A2C2A": "Brown Coffee", "#FF594537": "Brown Derby Variant", "#FF89491A": "Brown Eyed Girl", "#FF9E6B4A": "Brown Eyes", "#FF544A42": "Brown Fox", "#FF706C11": "Brown Green", "#FF8D8468": "Brown Grey", "#FFD3B793": "Brown Hare", "#FF5E442C": "Brown Hen", "#FFF485AC": "Brown Knapweed", "#FF97382C": "Brown Labrador", "#FF7B2039": "Brown Magenta", "#FF662211": "Brown Moelleux", "#FFD8CBB5": "Brown Mouse", "#FFDFAC59": "Brown Mustard", "#FFB96902": "Brown Orange", "#FFC2AA90": "Brown Owl", "#FF8A5640": "Brown Patina", "#FF4E403B": "Brown Pepper", "#FF3C241B": "Brown Pod Variant", "#FFAE8E65": "Brown Rabbit", "#FF922B05": "Brown Red", "#FFDABD84": "Brown Rice", "#FF735852": "Brown Ridge", "#FF8D736C": "Brown Rose", "#FFBC9B4E": "Brown Rum", "#FFF7945F": "Brown Sand", "#FF4A290D": "Brown Study", "#FF5B4F41": "Brown Suede", "#FFAB764E": "Brown Sugar", "#FFC8AE96": "Brown Sugar Coating", "#FFCF7A4B": "Brown Sugar Glaze", "#FFBCA792": "Brown Teepee", "#FF906151": "Brown Thrush", "#FF704E40": "Brown Velvet", "#FFB4674D": "Brown Wood", "#FFDD9966": "Brown Yellow", "#FFDDBDA3": "Brown-Bag It", "#FF79512C": "Brown-Noser", "#FFBB4433": "Browned Off", "#FF916D56": "Brownie Scout", "#FF9C6D57": "Brownish", "#FF413936": "Brownish Black", "#FF6A6E09": "Brownish Green", "#FF86775F": "Brownish Grey", "#FFCB7723": "Brownish Orange", "#FFC27E79": "Brownish Pink", "#FF76424E": "Brownish Purple", "#FF8D746F": "Brownish Purple Red", "#FF9E3623": "Brownish Red", "#FFC9B003": "Brownish Yellow", "#FF785441": "Brownstone", "#FF6E615F": "Browse Brown", "#FFD3B99B": "Bruin Spice", "#FF7E4071": "Bruise", "#FF5B4148": "Bruised Burgundy", "#FF3B1921": "Bruised Plum", "#FFC6C6C2": "Brume", "#FF664238": "Brunette", "#FF829E2C": "Bruni Green", "#FF2A1B0E": "Brunneophobia", "#FF5E4662": "Brunneous", "#FF9BA9CA": "Brunnera Blue", "#FF433430": "Bruno Brown", "#FF236649": "Brunswick", "#FF1B4D3E": "Brunswick Green", "#FFB2654E": "Bruschetta", "#FFB99684": "Brush", "#FFD4E1ED": "Brush Blue", "#FFDB9351": "Brushed Clay", "#FFC7C8C9": "Brushed Metal", "#FF7D7A79": "Brushed Nickel", "#FFF8A060": "Brushed Orange", "#FFF1DFBA": "Brushstroke", "#FF8C5939": "Brushwood", "#FFCC6611": "Brusque Brown", "#FFEE00FF": "Brusque Pink", "#FF6C7C6D": "Brussels", "#FF665E0D": "Brussels Sprout Green", "#FFAA9B78": "Brussels Sprouts", "#FFE61626": "Brutal Doom", "#FFFF00BB": "Brutal Pink", "#FF0022DD": "Brutally Blue", "#FFA6BEA6": "Bryophyte", "#FF9FE010": "Bryopsida Green", "#FFFFBADF": "Bubbelgum Heart", "#FF90E4C1": "Bubble Algae", "#FFE8E0E9": "Bubble Bath", "#FF00B800": "Bubble Bobble Green", "#FF0084FF": "Bubble Bobble P2", "#FFD3A49A": "Bubble Shell", "#FF43817A": "Bubble Turquoise", "#FFFF85FF": "Bubblegum", "#FFCC55EE": "Bubblegum Baby Girl", "#FFF887C7": "Bubblegum Beam", "#FFFF01B7": "Bubblegum Bright", "#FFEECCEE": "Bubblegum Crisis", "#FFF092D6": "Bubblegum Kisses", "#FFF6B0BA": "Bubblegum Pink", "#FFD3E3E5": "Bubbles in the Air", "#FFEAD8C0": "Bubbly", "#FF77CCFF": "Bubbly Barracuda", "#FFC68400": "Bubonic Brown", "#FFFDF5D7": "Bucatini Noodle", "#FF6E5150": "Buccaneer Variant", "#FF035B8D": "Buccaneer Blue", "#FFAA1111": "B\u00fcchel Cherry", "#FF674834": "Buckeye", "#FF996655": "Bucking Bronco", "#FF89A068": "Buckingham Gardens", "#FF6B5140": "Buckingham Palace", "#FFD9C3A6": "Buckram Binding", "#FFD4BA8C": "Buckskin", "#FFC9A169": "Buckskin Pony", "#FFA76F1F": "Buckthorn Brown", "#FFD4DCD6": "Buckwheat", "#FFEFE2CF": "Buckwheat Flour", "#FFE0D8A7": "Buckwheat Groats", "#FFB9A4B0": "Buckwheat Mauve", "#FF1B6634": "Bucolic", "#FF98ACB0": "Bucolic Blue", "#FFA5A88F": "Bud Variant", "#FF79B465": "Bud Green", "#FFE9E3D3": "Bud\u2019s Sails", "#FF553D3E": "Budapest Brown", "#FFE5E1E6": "Budapest Pastel", "#FFFCE2C4": "Budder Skin", "#FFBC9B1B": "Buddha Gold Variant", "#FF37B575": "Buddha Green", "#FFFFBB33": "Buddha\u2019s Love Handles", "#FFDEEABD": "Budding Bloom", "#FFEDECD4": "Budding Fern", "#FFC4D1BF": "Budding Green", "#FFEEF0D7": "Budding Leaf", "#FFF3D4BF": "Budding Peach", "#FF976538": "Buddy Buddy", "#FF84C9E1": "Budgie Blue", "#FF63424B": "Bud\u014dnezumi Grape", "#FFF4DCC1": "Buenos Aires", "#FFD9CFBE": "Buff It", "#FFAA7733": "Buff Leather", "#FFFFBB7C": "Buff Orange", "#FFE8D0B9": "Buff Tone", "#FFF0B967": "Buff Yellow", "#FFF25A1A": "Buffallo Sauce", "#FFAE9274": "Buffalo Bill", "#FF695645": "Buffalo Dance", "#FF705046": "Buffalo Herd", "#FFBB9F6A": "Buffalo Hide", "#FF95786C": "Buffalo Soldier", "#FFE2AC78": "Buffalo Trail", "#FFDD9475": "Buffed Copper", "#FFAEAFB9": "Buffed Plum", "#FFA79C81": "Buffhide", "#FF868929": "Buffy Citrine", "#FFBB8F4F": "Bugle Boy", "#FFD3973C": "Bugle Call", "#FFAFAFA7": "Building Block", "#FFE9E3DA": "Built on Sand", "#FF73A263": "Bulbasaur", "#FF94B1B6": "Bulfinch Blue", "#FF636153": "Bull Kelp", "#FF6B605B": "Bull Ring", "#FF75442B": "Bull Shot Variant", "#FFFAF1C8": "Bullet Hell", "#FFCD4646": "Bullfighters Red", "#FF8A966A": "Bullfrog", "#FFB9030A": "Bullseye", "#FF359E6B": "Bulma Hair", "#FF6D5837": "Bulrush", "#FF0777BC": "Bumangu\u00e9s Blue", "#FFF5F1DE": "Bumble Baby", "#FFFFC82A": "Bumblebee", "#FF674961": "Bunchberry", "#FFFFC58A": "Bundaberg Sand", "#FFE5B584": "Bundle of Wheat", "#FFCBBEAA": "Bungalow Beige", "#FFAD947B": "Bungalow Brown", "#FFAD8047": "Bungalow Gold", "#FFE4C590": "Bungalow Maple", "#FFCEBE9F": "Bungalow Taupe", "#FFE4D3C8": "Bungalow White", "#FF696156": "Bungee Cord", "#FF988F7B": "Bunglehouse Beige", "#FF46616E": "Bunglehouse Blue", "#FF292C2F": "Bunker Variant", "#FF6C4522": "Bunni Brown", "#FFF1B5CC": "Bunny Cake", "#FFF9D9D2": "Bunny Ears", "#FFFB8DA6": "Bunny Fluff", "#FFF3ECEA": "Bunny Hop", "#FFDEC3C9": "Bunny Pink", "#FFD3BFC4": "Bunny Soft", "#FFFFE3F4": "Bunny Tail", "#FFFAD9DD": "Bunny\u2019s Nose", "#FF2B3449": "Bunting Variant", "#FF35537C": "Bunting Blue", "#FF79B0B6": "Buoyancy", "#FF65707E": "Buoyant", "#FF84ADDB": "Buoyant Blue", "#FF717867": "Burdock", "#FF746C8F": "Bureaucracy", "#FFDADBA0": "Burgundy Grey", "#FF811D45": "Burgundy Orchid", "#FF7E7150": "Burgundy Snail", "#FF6C403E": "Burgundy Wine", "#FFDBBC4B": "Buried Gold", "#FF772200": "Buried Lust", "#FFD28B42": "Buried Treasure", "#FFD4DEE8": "Burj Khalifa Fountain", "#FF353E4F": "Burka Black", "#FF8B7753": "Burlap", "#FF81717E": "Burlap Grey", "#FF6E314F": "Burlat Red", "#FF8F4C3A": "Burled Redwood", "#FF695641": "Burley Wood", "#FFA17874": "Burlwood", "#FF94B1A0": "Burma Jade", "#FFBC8143": "Burmese Gold", "#FF520B00": "Burned", "#FF6F4B3E": "Burned Brown", "#FF234537": "Burnham Variant", "#FF884736": "Burning Brier", "#FFA0403E": "Burning Bush", "#FFF79D72": "Burning Coals", "#FFFF1166": "Burning Fireflies", "#FFFFB162": "Burning Flame", "#FFCCAA77": "Burning Gold", "#FF8F8B72": "Burning Idea", "#FFFF7124": "Burning Orange Variant", "#FFFF0599": "Burning Raspberry", "#FFD08363": "Burning Sand Variant", "#FF742100": "Burning Steppes", "#FFEB5030": "Burning Tomato", "#FFEE9922": "Burning Trail", "#FF150AEC": "Burning Ultrablue", "#FFC48D82": "Burnished Apricot", "#FF6A3D36": "Burnished Bark", "#FF8B664E": "Burnished Brandy", "#FF9C7E40": "Burnished Bronze", "#FFBE9167": "Burnished Caramel", "#FFD2CCC4": "Burnished Clay", "#FFBB8833": "Burnished Copper", "#FFFCE5BF": "Burnished Cream", "#FFAA9855": "Burnished Gold", "#FFC5AEB1": "Burnished Lilac", "#FF734842": "Burnished Mahogany", "#FFC8CBC8": "Burnished Metal", "#FF716A62": "Burnished Pewter", "#FF794029": "Burnished Russet", "#FF7B5847": "Burns Cave", "#FFD0A664": "Burnside", "#FFB0724A": "Burnt Almond", "#FF746572": "Burnt Ash", "#FF9A4E12": "Burnt Bagel", "#FF4D3B3C": "Burnt Bamboo", "#FFB45241": "Burnt Brick", "#FFA47C53": "Burnt Butter", "#FF846242": "Burnt Caramel", "#FF271B10": "Burnt Coffee", "#FFC56A39": "Burnt Copper", "#FFE57568": "Burnt Coral", "#FF582124": "Burnt Crimson", "#FF885533": "Burnt Crust", "#FF9D4531": "Burnt Earth", "#FF75625E": "Burnt Grape", "#FF8D4035": "Burnt Henna", "#FFA87F28": "Burnt Honey", "#FFBB4F35": "Burnt Ochre", "#FF736F54": "Burnt Olive", "#FF743D68": "Burnt Orchid", "#FFCA955C": "Burnt Pumpkin", "#FF9F2305": "Burnt Red", "#FF853C47": "Burnt Russet", "#FFA93400": "Burnt Sienna Variant", "#FF82634E": "Burnt Terra", "#FF774645": "Burnt Tile", "#FFAB7E5E": "Burnt Toffee", "#FFD5AB09": "Burnt Yellow", "#FF6832E3": "Burple", "#FFEED7C1": "Burrito", "#FF947764": "Burro", "#FFDEB368": "Burst of Gold", "#FFACD243": "Burst of Lime", "#FFFCE282": "Bursting Lemon", "#FFA28D82": "Bush Buck", "#FFA0BCD0": "Bush Viper", "#FF9FA993": "Bushel", "#FF7F7B73": "Bushland Grey", "#FFE5A1A0": "Bussell Lace", "#FF3E4B69": "Buster", "#FF3300CC": "Busty Blue", "#FFF4FF00": "Busy Bee", "#FFB69983": "Butcher Paper", "#FFFFFF81": "Butter", "#FFBA843A": "Butter & Syrup", "#FFC28A35": "Butter Base", "#FFC88849": "Butter Bronze", "#FFFDFF52": "Butter Cake", "#FFA67A4C": "Butter Caramel", "#FFF0E4B2": "Butter Cookie", "#FFFEE5BA": "Butter Creme", "#FFFFDD99": "Butter Cupcake", "#FFFCE9AD": "Butter Fingers", "#FFAA6600": "Butter Fudge", "#FFF5E5AB": "Butter Honey", "#FFF5E5DA": "Butter Icing", "#FFCFE7CB": "Butter Lettuce", "#FFF6DFB2": "Butter Muffin", "#FFF9E097": "Butter Ridge", "#FFC38650": "Butter Rum", "#FFFEE99F": "Butter Tart", "#FFF4E0BB": "Butter Up", "#FFFDDEBD": "Butter White", "#FFFFFD74": "Butter Yellow", "#FFFFF4C4": "Butterball", "#FFAF7934": "Butterbeer", "#FFF1C766": "Butterblond", "#FFC5AE7C": "Butterbrot", "#FFEFE0CD": "Buttercream", "#FFF5EDD7": "Buttercream Frosting", "#FFDA9429": "Buttercup Variant", "#FFF1F458": "Buttercup Glow", "#FFE3C2A3": "Buttercup Yellow", "#FFECE2B7": "Buttered", "#FFFFF0A4": "Buttered Popcorn", "#FF9D702E": "Buttered Rum Variant", "#FFF7F0D2": "Buttered Up", "#FFF7BE5B": "Butterfield", "#FFCADEA5": "Butterfly", "#FF2099BB": "Butterfly Blue", "#FF68578C": "Butterfly Bush Variant", "#FF908ABA": "Butterfly Garden", "#FF0B6863": "Butterfly Green", "#FFF0DEDC": "Butterfly Kisses", "#FFF8CFB4": "Butterfly Wing", "#FFFFF7DB": "Buttermelon", "#FFFFFEE4": "Buttermilk Variant", "#FFFFA177": "Butternut", "#FFD99E66": "Butternut Pie", "#FFE59752": "Butternut Pizazz", "#FFFC7604": "Butternut Squash", "#FFCF9A5D": "Butternut Tree", "#FF7E6F59": "Butternut Wood", "#FFFDB147": "Butterscotch", "#FFD3B090": "Butterscotch Amber", "#FFD7AD62": "Butterscotch Bliss", "#FFF1C882": "Butterscotch Cake", "#FFC48446": "Butterscotch Glaze", "#FFA97D54": "Butterscotch Mousse", "#FFB08843": "Butterscotch Ripple", "#FFDBB486": "Butterscotch Sundae", "#FFD9A05F": "Butterscotch Syrup", "#FFC68F65": "Butterum", "#FFFFC283": "Buttery", "#FFF6E19C": "Buttery Croissant", "#FFD4B185": "Buttery Leather", "#FFFFB19A": "Buttery Salmon", "#FFF1EBDA": "Buttery White Variant", "#FF24A0ED": "Button Blue", "#FF4F3A32": "Button Eyes", "#FFECE6C8": "Button Mushroom", "#FFF0C641": "Buzz", "#FFFFD756": "Buzz-In", "#FF5F563F": "Buzzard", "#FF017A79": "Buzzards Bay", "#FFE6B261": "Buzzworthy", "#FF816A38": "By Gum", "#FF8D999E": "By the Sea", "#FFA5BA93": "Byakuroku Green", "#FF918E8A": "Bygone", "#FFB6C4D2": "Bypass", "#FF31667D": "Byron Place", "#FFC5DCE0": "Byte Blue", "#FF006C6E": "Byzantine Blue", "#FFAB7141": "Byzantine Copper", "#FF6A79F7": "Byzantine Night Blue", "#FFC33140": "C-3PO", "#FF83BCE5": "C\u2019est la Vie", "#FF003AFF": "C64 Blue", "#FF4E7FFF": "C64 NTSC", "#FF6F6ED1": "C64 Purple", "#FF4A2E32": "Cab Sav Variant", "#FF7F6473": "Cabal", "#FF8EC1C0": "Cabana Bay", "#FF5B9099": "Cabana Blue", "#FFDCA901": "Cabana Glow", "#FFC88567": "Cabana Melon", "#FFCD526C": "Cabaret Variant", "#FF7C8EA6": "Cabaret Charm", "#FF87D7BE": "Cabbage", "#FF724C7B": "Cabbage Blossom Violet", "#FF807553": "Cabbage Green", "#FFDFE8D0": "Cabbage Leaf", "#FF93C460": "Cabbage Patch", "#FF4C5544": "Cabbage Pont Variant", "#FFC59F91": "Cabbage Rose", "#FF8E5B68": "Cabernet", "#FF6D3445": "Cabernet Craving", "#FF5E5349": "Cabin Fever", "#FF5D4D47": "Cabin in the Woods", "#FF574038": "Cabin Plank", "#FFCEC0AA": "Cabo", "#FFA8A4A1": "Caboose", "#FF6B5848": "Cacao", "#FF80442F": "Cacao Nibs", "#FFB5CEE0": "Cache Blue", "#FFD3C296": "Cache of Camel", "#FFF3D9BA": "Cachet Cream", "#FF9F0000": "Cacodemon Red", "#FF5B6F55": "Cactus Variant", "#FFF6C79D": "Cactus Blooms", "#FFD8E5DD": "Cactus Blossom", "#FFAF416B": "Cactus Flower", "#FF7B8370": "Cactus Garden", "#FF56603D": "Cactus Green", "#FFB1A386": "Cactus Hill", "#FF9C9369": "Cactus Sand", "#FFC1E0A3": "Cactus Spike", "#FF88976B": "Cactus Valley", "#FFD0F7E4": "Cactus Water", "#FF009977": "Cadaverous", "#FF3E354D": "Caddies Silk", "#FF536872": "Cadet", "#FF3B5964": "Cadet Song", "#FF90766E": "Cadian", "#FF984961": "Cadillac Variant", "#FF0A1195": "Cadmium Blue", "#FFB60C26": "Cadmium Purple", "#FF7F3E98": "Cadmium Violet", "#FFFFEE66": "Caduceus Gold", "#FFEEDD22": "Caduceus Staff", "#FFECD0B1": "Caen Stone", "#FF986860": "Caf\u00e9", "#FFAA8E74": "Caf\u00e9 Am\u00e9ricaine", "#FFA57C5B": "Caf\u00e9 au Lait", "#FF8A9B98": "Caf\u00e9 Blue", "#FFC79685": "Caf\u00e9 Cr\u00e8me", "#FF889944": "Caf\u00e9 de Paris", "#FF5E4C48": "Cafe Expreso", "#FFD6C6B4": "Cafe Latte", "#FFB98F6F": "Caf\u00e9 Miel", "#FF9A7F79": "Cafe Ole", "#FFECC1C2": "Cafe Pink", "#FFAE8774": "Caf\u00e9 Renvers\u00e9", "#FF6A4928": "Cafe Royale Variant", "#FF885511": "Caffeinated Cinnamon", "#FF8A796A": "Caffeine", "#FF26B7B5": "Caicos Turquoise", "#FF0A6B92": "Cairns", "#FFC46D29": "Cajeta", "#FF5F3E41": "Cajun Brown", "#FFA45A4A": "Cajun Red", "#FFC3705F": "Cajun Spice", "#FFF0EDDB": "Cake Batter", "#FFE8D4BB": "Cake Crumbs", "#FFFCE0A8": "Cake Dough", "#FFF9DFE5": "Cake Frosting", "#FFEDC9D0": "Cake Pop", "#FFF6CAC3": "Cake Pop Pink", "#FFF8C649": "Cakepop Sorbet", "#FF0AC2C2": "Cala Benirr\u00e1s Blue", "#FFF8EB97": "Calabash", "#FF728478": "Calabash Clash", "#FFF4A6A3": "Calabrese", "#FFFCFFA4": "Calamansi", "#FFC4CC7A": "Calamansi Green", "#FF80FFCC": "Calamine Blue", "#FFE7E1DD": "Calc Sinter", "#FFDDEEFF": "Calcareous Sinter", "#FF94B2B2": "Calcite Blue", "#FF52605F": "Calcite Grey Green", "#FFF2F4E8": "Calcium", "#FFEEE9D9": "Calcium Rock", "#FFA1CCB1": "Calculus", "#FF31639C": "Caledor Sky", "#FFC1A188": "Calfskin", "#FF0485D1": "Calgar Blue", "#FFA57E98": "Cali Lily", "#FF005726": "Caliban Green", "#FFD5B185": "Calico Variant", "#FFC48E36": "Calico Cat", "#FF3D4E67": "Calico Dress", "#FF9C9584": "Calico Rock", "#FFE5C1B3": "Calico Rose", "#FF95594A": "Caliente", "#FFE98C3A": "California Variant", "#FFE6B76C": "California Chamois", "#FFE3AA94": "California Coral", "#FF93807F": "California Dreamin\u2019", "#FFDEC569": "California Dreaming", "#FFFCA716": "California Girl", "#FF95743F": "California Gold Rush", "#FFBBC5E2": "California Lilac", "#FFFCBE6A": "California Peach", "#FFA83C3F": "California Poppy", "#FFA09574": "California Roll", "#FF959988": "California Sagebrush", "#FFC5AD9A": "California Stucco", "#FFCA1850": "California Sunset", "#FFCA4B65": "California Wine", "#FF42364C": "Call It a Night", "#FFF2DFB5": "Calla", "#FF747D3B": "Calla Green", "#FFE4EAED": "Calla Lily", "#FF59636A": "Calligraphy", "#FFC89A8D": "Calliope", "#FF798052": "Calliste Green", "#FFCACFD3": "Callisto", "#FFDFE9E6": "Calm", "#FFEED2AE": "Calm Air", "#FF5E9D47": "Calm Balm", "#FFE9ECE4": "Calm Breeze", "#FFD0D4CF": "Calm Cool and Collected", "#FFC6B0B9": "Calm Cupid", "#FF7CAACF": "Calm Day", "#FFA7B0D5": "Calm Interlude", "#FFDEE2EB": "Calm Iridescence", "#FFE5EDE2": "Calm Thoughts", "#FFEAE3E9": "Calm Tint", "#FFE7FAFA": "Calm Waters", "#FFCFD3A2": "Calming Effect", "#FFEEE0D1": "Calming Retreat", "#FFB2A2C1": "Calming Silver Lavender", "#FFAAB7C1": "Calming Space", "#FF68A895": "Calmness", "#FF6D5044": "Calthan Brown", "#FF3D7188": "Calypso Variant", "#FFC53A4B": "Calypso Berry", "#FFEC4A61": "Calypso Coral", "#FF2E5F60": "Calypso Green", "#FFD978F0": "Calypso Orchid", "#FFDE6B66": "Calypso Red", "#FFFE828C": "Camaron Pink", "#FF206937": "Camarone Variant", "#FF8C633C": "Cambridge Leather", "#FFACB8B4": "Cambridge Lily", "#FFC69F59": "Camel", "#FFA56639": "Camel Brown", "#FFCC9944": "Camel Cardinal", "#FFC5B39A": "Camel Coat", "#FFE0CB82": "Camel Cord", "#FFBB6600": "Camel Fur", "#FFDBB8A4": "Camel Hair", "#FFF5B784": "Camel Hair Coat", "#FFC1AA91": "Camel Hide", "#FFE5743B": "Camel Red", "#FFB68B45": "Camel Ride", "#FFAC8A2A": "Camel Toe", "#FFBAAE9D": "Camel Train", "#FF817667": "Camel\u2019s Hump", "#FFC5AA85": "Camelback", "#FFD3B587": "Camelback Mountain", "#FFE2AF60": "Cameleer", "#FFF6685A": "Camellia", "#FFCD739D": "Camellia Pink", "#FFE94E6D": "Camellia Rose", "#FF803A4B": "Camelot Variant", "#FFFBF3DF": "Camembert", "#FFF2DEBC": "Cameo Variant", "#FFDFC1C3": "Cameo Appearance", "#FF7097A2": "Cameo Blue", "#FFBE847D": "Cameo Brown", "#FFF3E2C3": "Cameo Cream", "#FFDCE6E5": "Cameo Green", "#FFEBCFC9": "Cameo Peach", "#FFDDCAAF": "Cameo Role", "#FFF7DFD7": "Cameo Rose", "#FFEBDFD8": "Cameo Stone", "#FFEED8BA": "Cameo White", "#FF60746D": "Cameroon Green", "#FFEEB5B4": "Camille Pink", "#FFFCD9C7": "Camisole", "#FF7F8F4E": "Camo", "#FF8C8475": "Camo Beige", "#FF747F71": "Camo Clay", "#FFA5A542": "Camo Green", "#FF4B6113": "Camouflage Green Variant", "#FFA28F5C": "Camouflage Olive", "#FFFCF7DB": "Campanelle Noodle", "#FF3473B7": "Campanula", "#FF6C6D94": "Campanula Purple", "#FFCE5F38": "Campfire", "#FFDDD9CE": "Campfire Ash", "#FFB67656": "Campfire Blaze", "#FFD5D1CB": "Campfire Smoke", "#FFD0A569": "Campground", "#FFB4C2A2": "Camping Grounds", "#FFB6AFA0": "Camping Tent", "#FF67786E": "Camping Trip", "#FFD08A9B": "Can Can Variant", "#FFEAE2DD": "Canada Goose Eggs", "#FF415A53": "Canadian Fir", "#FF8F9AA4": "Canadian Lake", "#FFCAB266": "Canadian Maple", "#FFEDD8C3": "Canadian Pancake", "#FF2E7B52": "Canadian Pine", "#FF579ACA": "Canadian Tuxedo", "#FF9CC2C5": "Canal Blue", "#FF969281": "Canal Street", "#FF818C72": "Canaletto", "#FFFDFF63": "Canary Variant", "#FFFFCE52": "Canary Diamond", "#FFEFDE75": "Canary Feather", "#FFD0CCA9": "Canary Grass", "#FFCCD6BC": "Canary Green", "#FFE9D4A9": "Canary Island", "#FF91A1B5": "Canary Wharf", "#FF27A0A8": "Cancer Seagreen Scarab", "#FFBAC4D5": "Candela", "#FFE1C161": "Candelabra", "#FF6CC3E0": "Candid Blue", "#FFC3BC90": "Candidate", "#FFB95B6D": "Candied Apple", "#FF331166": "Candied Blueberry", "#FFBFA387": "Candied Ginger", "#FF838252": "Candied Lime", "#FFD8FFF3": "Candied Snow", "#FFF9A765": "Candied Yams", "#FFC3BDAA": "Candle Bark", "#FFFFF4A1": "Candle Flame", "#FFFFE8C3": "Candle Glow", "#FFF9EBBF": "Candle in the Wind", "#FFF2EACF": "Candle Wax", "#FFE09B6E": "Candle Yellow", "#FFCEB3BE": "Candlelight Dinner", "#FFFCF4E2": "Candlelight Ivory", "#FFF8A39D": "Candlelight Peach", "#FFF7F0C7": "Candlelight Yellow", "#FFF1EDE0": "Candlelit Beige", "#FFFFF1D5": "Candlestick Point", "#FFF2EBD3": "Candlewick", "#FFFF9B87": "Candy", "#FFF7BFC2": "Candy Cane", "#FFEF9FAA": "Candy Coated", "#FFFCFC5D": "Candy Corn Variant", "#FFE9AEF2": "Candy Dreams", "#FFC25D6A": "Candy Drop", "#FFE8A7E2": "Candy Floss", "#FFFFF0DE": "Candy Floss Cupcake", "#FF7755EE": "Candy Grape Fizz", "#FF33AA00": "Candy Grass", "#FF33CC00": "Candy Green", "#FFF5A2A1": "Candy Heart Pink", "#FFFABEB5": "Candy Kisses", "#FFF3DFE3": "Candy Mix", "#FFFF63E9": "Candy Pink Variant", "#FF895D8B": "Candy Violet", "#FFFF9E76": "Candyman", "#FFEDC9D8": "Candytuft", "#FFE3B982": "Cane Sugar", "#FFDDBB99": "Cane Sugar Glaze", "#FF977042": "Cane Toad", "#FF009BB3": "Caneel Bay", "#FFD7B69A": "Canewood", "#FFF7E4CC": "Cannellini Beans", "#FFBCB09E": "Cannery Park", "#FFEDECDB": "Cannoli Cream", "#FF484335": "Cannon Ball", "#FF3C4142": "Cannon Barrel", "#FF646C64": "Cannon Grey", "#FFDDC49E": "Canoe", "#FF1D5671": "Canoe Blue", "#FFF7EB7A": "Canola Oil", "#FF728F02": "Canopy", "#FFAABFD8": "Canopy Bed", "#FFFFD479": "Cantaloupe", "#FFFEB079": "Cantaloupe Slice", "#FFFFB355": "Cantaloupe Smile", "#FFAC8D74": "Cantankerous Coyote", "#FF96887F": "Cantankerous Hippo", "#FF5E5347": "Canteen", "#FFF6D3BB": "Canter Peach", "#FFCEC5AF": "Cantera", "#FFB9C3E6": "Canterbury Bells", "#FFB2AB94": "Canterbury Cathedral", "#FFE7E1D1": "Canterbury Cream", "#FF649C97": "Canton", "#FFBAE7C7": "Canton Jade", "#FFBB8855": "Canvas", "#FFE6DFD2": "Canvas Cloth", "#FFE2D7C6": "Canvas Luggage", "#FFCCB88D": "Canvas Satchel", "#FFDDD6C6": "Canvas Tan", "#FF607B8E": "Canyon Blue", "#FFCB7D6F": "Canyon Clay", "#FFECE3D1": "Canyon Cliffs", "#FFAEAFBB": "Canyon Cloud", "#FFDDC3B7": "Canyon Dusk", "#FFB18575": "Canyon Earth", "#FFE5E1CC": "Canyon Echo", "#FF97987F": "Canyon Falls", "#FFF1B8AC": "Canyon Hush", "#FF49548F": "Canyon Iris", "#FFA7A4C0": "Canyon Mist", "#FFEEDACB": "Canyon Peach", "#FFB47571": "Canyon Rose", "#FFF2D6AA": "Canyon Sand", "#FF93625B": "Canyon Stone", "#FFDD8869": "Canyon Sunset", "#FFD6B8A9": "Canyon Trail", "#FF8A7E5C": "Canyon Verde", "#FFC3B39F": "Canyon View", "#FFA14935": "Canyon Wall", "#FFE3E5DF": "Canyon Wind", "#FFF5DED1": "Canyonville", "#FF1FA774": "C\u01ceo L\u01dc Grass", "#FF4E5552": "Cape Cod Variant", "#FF557080": "Cape Cod Bay", "#FF91A2A6": "Cape Cod Blue", "#FFFEE0A5": "Cape Honey Variant", "#FFD8D6D7": "Cape Hope", "#FFFFB95A": "Cape Jasmine", "#FF50818B": "Cape Lee", "#FF75482F": "Cape Palliser Variant", "#FF0092AD": "Cape Pond", "#FFF6CDB8": "Cape Sands", "#FF3C4754": "Cape Storm", "#FF01554F": "Cape Verde", "#FFD9CED2": "Capella", "#FF847640": "Caper Green", "#FF78728C": "Capercaillie Mauve", "#FF897A3E": "Capers", "#FFFCEBCE": "Capetown Cream", "#FF1A4157": "Capital Blue", "#FFDBD0A8": "Capital Grains", "#FFE6BA45": "Capital Yellow", "#FF008F4C": "Capitalino Cactus", "#FFD9544D": "Capocollo", "#FF822A10": "Caponata", "#FF704A3A": "Cappuccino", "#FFB4897D": "Cappuccino Bombe", "#FFE1DDCD": "Cappuccino Cosmico", "#FFE0D0C2": "Cappuccino Foam", "#FFC8B089": "Cappuccino Froth", "#FF0089A8": "Capri Breeze", "#FFF1F0D6": "Capri Cream", "#FFAC839C": "Capri Fashion Pink", "#FFABE2D6": "Capri Water Blue", "#FFF1CD9F": "Capricious", "#FFBB00DD": "Capricious Purple", "#FFFECB51": "Capricorn Golden Key", "#FF7E7A75": "Caps", "#FF6D8A74": "Capsella", "#FF76392E": "Capsicum Red", "#FF007EB0": "Capstan", "#FF005171": "Captain Blue", "#FF9B870C": "Captain Kirk", "#FF828080": "Captain Nemo", "#FF557088": "Captains Blue", "#FF947CAE": "Captivated", "#FFD8CACE": "Captivating", "#FFF4D9B1": "Captivating Cream", "#FF005B6A": "Captive", "#FF2CBAA3": "Capture", "#FF6E6D4A": "Capulet Olive", "#FF5D473A": "Carafe", "#FF795F4D": "Cara\u00efbe", "#FF552233": "Carambar", "#FFEFEBD1": "Carambola", "#FFAF6F09": "Caramel Variant", "#FFB87A59": "Caramel Apple", "#FFCC8654": "Caramel Bar", "#FFB18775": "Caramel Brown", "#FF8E5626": "Caramel Caf\u00e9", "#FFB3715D": "Caramel Candy", "#FFD4AF85": "Caramel Cloud", "#FFBB7711": "Caramel Coating", "#FFF4B58F": "Caramel Cream", "#FFC39355": "Caramel Crumb", "#FFB98C5D": "Caramel Cupcake", "#FFB8623B": "Caramel Dream", "#FFD9AD7F": "Caramel Drizzle", "#FFE3AF64": "Caramel Essence", "#FFFFD59A": "Caramel Finish", "#FFB1936D": "Caramel Gold", "#FFEEC9AA": "Caramel Ice", "#FFB08A61": "Caramel Kiss", "#FF9D652B": "Caramel Kisses", "#FF8C6342": "Caramel Latte", "#FFC58D4B": "Caramel Macchiato", "#FFE5CAA4": "Caramel Mousse", "#FFEEBB99": "Caramel Powder", "#FFB3804D": "Caramel Sauce", "#FFA78045": "Caramel Suede", "#FFA9876A": "Caramel Sundae", "#FF8F6A4F": "Caramel Swirl", "#FFC27E43": "Caramel Toffee", "#FFD58A37": "Caramelise", "#FFBA947F": "Caramelised", "#FFAD5A28": "Caramelised Maple", "#FFEF924A": "Caramelised Orange", "#FFC56F2B": "Caramelised Peach", "#FFE7D5AD": "Caramelised Pears", "#FFA17B4D": "Caramelised Pecan", "#FF6E564A": "Caramelised Walnut", "#FFD69E6B": "Caramelo Dulce", "#FF9C0013": "Caraquenian Crimson", "#FF8C6E54": "Caravel Brown", "#FFA19473": "Caraway", "#FF6D563C": "Caraway Brown", "#FFDFD5BB": "Caraway Seeds", "#FFAB9975": "Caraway Shield", "#FF316382": "Carbide", "#FF333333": "Carbon", "#FF373C4F": "Carbon Blue", "#FF545554": "Carbon Copy", "#FF565B58": "Carbon Dating", "#FF2E2E2E": "Carbon Fibre", "#FF7B808B": "Carbon Footprint", "#FF00512C": "Card Table Green", "#FFAAAA77": "Cardamom", "#FFD7E2BC": "Cardamom Fragrance", "#FF989057": "Cardamom Green", "#FF837165": "Cardamom Spice", "#FFC19A6C": "Cardboard", "#FFE4DDCE": "Carded Wool", "#FF1B3427": "Cardin Green Variant", "#FF2C284C": "Cardinal Mauve", "#FFD10929": "Cardinal Rage", "#FF9B365E": "Cardinal Red", "#FFB33125": "Cardinal\u2019s Cassock", "#FF9AAE8C": "Cardoon", "#FF957B38": "Cardueline Finch", "#FFDCE9E9": "Carefree", "#FFA6CDDE": "Carefree Sky", "#FFFAAFC8": "Caregiver Pink", "#FFC99AA0": "Careys Pink Variant", "#FF8F755B": "Cargo", "#FFC8C5A7": "Cargo Green", "#FFCDC4AE": "Cargo Pants", "#FFCFCDBB": "Cargo River", "#FFCAF0E5": "Caribbean", "#FF1AC1DD": "Caribbean Blue", "#FF93C5DD": "Caribbean Coast", "#FFC07761": "Caribbean Coral", "#FF3F9DA9": "Caribbean Cruise", "#FF006E6E": "Caribbean Current", "#FF007180": "Caribbean Dream", "#FFCADEEA": "Caribbean Mist", "#FFD5DCCE": "Caribbean Pleasure", "#FF0087A7": "Caribbean Sea", "#FF819ECB": "Caribbean Sky", "#FF00697C": "Caribbean Splash", "#FFF5DAAA": "Caribbean Sunrise", "#FF126366": "Caribbean Swim", "#FF009D94": "Caribbean Turquoise", "#FF147D87": "Caribe", "#FF816C5E": "Caribou", "#FFCDA563": "Caribou Herd", "#FFE68095": "Carissima", "#FFF5F9CB": "Carla Variant", "#FFA87376": "Carley\u2019s Rose", "#FF45867C": "Carlisle", "#FF915F3D": "Carmel", "#FF927F76": "Carmel Mission", "#FF8D6B3B": "Carmel Woods", "#FFB98970": "Carmelite", "#FFBB534C": "Carmelito", "#FF7C383F": "Carmen", "#FF903E2F": "Carmen Miranda", "#FFD60036": "Carmine Variant", "#FFAD4B53": "Carmine Carnation", "#FFE35B8F": "Carmine Rose", "#FFB31C45": "Carmoisine", "#FF5B3A24": "Carnaby Tan Variant", "#FF940008": "Carnage Red", "#FFBB8866": "Carnal Brown", "#FFEF9CB5": "Carnal Pink", "#FFFD798F": "Carnation Variant", "#FFF9C0BE": "Carnation Bloom", "#FFF5C0D0": "Carnation Bouquet", "#FFEDB9AD": "Carnation Coral", "#FF915870": "Carnation Festival", "#FFCE94C2": "Carnation Rose", "#FFEB882C": "Carnival", "#FF98BEB5": "Carnival Glass", "#FF006E7A": "Carnival Night", "#FF991111": "Carnivore", "#FFFFCAC3": "Caro", "#FF885C4E": "Carob Brown", "#FF5A484B": "Carob Chip", "#FF338DAE": "Carol", "#FF77A135": "Carol\u2019s Purr", "#FFCBEFCB": "Carolina", "#FF8AB8FE": "Carolina Blue Variant", "#FF008B6D": "Carolina Green", "#FFD8DF80": "Carolina Parakeet", "#FFFF1500": "Carolina Reaper", "#FFFFB850": "Carolling Candlelight", "#FFFBA52E": "Carona", "#FFFDB793": "Carotene", "#FFF8DBE0": "Carousel Pink Variant", "#FFDEE3C0": "Carpathian Dawn", "#FF905755": "Carpe Diem", "#FF00AA33": "Carpet Moss", "#FF905D36": "Carrageen Moss", "#FFEEEBE4": "Carrara", "#FFE8E7D7": "Carrara Marble", "#FF6C6358": "Carriage", "#FF958D79": "Carriage Door", "#FF254D48": "Carriage Green", "#FF8C403D": "Carriage Red", "#FF8A8DC4": "Carriage Ride", "#FF7E7265": "Carriage Stone", "#FF5D6367": "Carriage Wheel", "#FFFFB756": "Carriage Yellow", "#FF889398": "Carrier Pigeon Blue", "#FFA82A70": "Carroburg Crimson", "#FFFD6F3B": "Carrot", "#FFBF6F31": "Carrot Cake", "#FFFE7A04": "Carrot Curl", "#FFCBD3C1": "Carrot Flower", "#FFFC5A1F": "Carrot Lava", "#FFDF7836": "Carrot Stick", "#FFEEEEFF": "Carte Blanche", "#FF405978": "Carter\u2019s Scroll", "#FFBB9E7E": "Carton", "#FFD01722": "Cartoon Violence", "#FF665537": "Cartwheel", "#FF937A62": "Carved Wood", "#FFF0C39F": "Carving Party", "#FFCF6837": "Casa de Oro", "#FFCACFE6": "Casa del Mar", "#FFC49CA5": "Casa Talec", "#FFABB790": "Casa Verde", "#FFF0B253": "Casablanca Variant", "#FF3F545A": "Casal Variant", "#FFFECE5A": "Casandora Yellow", "#FF7C4549": "Casandra", "#FFD4EDE6": "Cascade Variant", "#FFE7DBCA": "Cascade Beige", "#FFA1C2B9": "Cascade Green", "#FF697F8E": "Cascade Tour", "#FF234893": "Cascade Twilight", "#FFECF2EC": "Cascade White", "#FFF7F5F6": "Cascading White", "#FFEE4433": "Cascara", "#FFA47149": "Cashew", "#FFFCF9BD": "Cashew Cheese", "#FFEDCCB3": "Cashew Nut", "#FFD1B399": "Cashmere Variant", "#FFA5B8D0": "Cashmere Blue", "#FFCDA291": "Cashmere Clay", "#FFCB8097": "Cashmere Rose", "#FFFEF2D2": "Cashmere Sweater", "#FFD6B484": "Cashmere Wrap", "#FFF9F2B3": "Casino Lights", "#FFA49186": "Casket", "#FFAAB5B8": "Casper Variant", "#FF4F6F91": "Caspian Sea", "#FFAEC7DB": "Caspian Tide", "#FFBB7700": "Cassandra\u2019s Curse", "#FFE7C084": "Cassava Cake", "#FFE0CDDA": "Cassia Buds", "#FFAED0C9": "Cassiopeia", "#FF623C1F": "Cassiterite Brown", "#FF64645A": "Cast Iron", "#FF6DBAC0": "Castaway", "#FFD0C19F": "Castaway Beach", "#FF7A9291": "Castaway Cove", "#FF607374": "Castaway Lagoon", "#FFCFAF83": "Castell Harlech", "#FF455440": "Castellan Green", "#FFA27040": "Castellina", "#FF677727": "Castelvetrano Olive", "#FFFFFFE8": "Caster Sugar", "#FFD4B3AA": "Castilian Pink", "#FF4586C7": "Casting Sea", "#FF9DA7A0": "Casting Shadow", "#FFE0D5CA": "Castle Beige", "#FF95827B": "Castle Hill", "#FFEFDCCA": "Castle in the Clouds", "#FFD1EAED": "Castle in the Sky", "#FFBDAEB7": "Castle Mist", "#FF8B6B47": "Castle Moat", "#FFC5BAAA": "Castle Path", "#FFEADEC7": "Castle Ridge", "#FF525746": "Castle Stone", "#FFC2BBA2": "Castle Wall", "#FFA0A5A5": "Castlegate", "#FF5F5E62": "Castlerock", "#FF00564F": "Castleton Green", "#FFA80020": "Castlevania Heart", "#FF676A64": "Castor Grey", "#FF44232F": "Castro Variant", "#FF498090": "Casual Blue", "#FF95BAC2": "Casual Day", "#FFDFD5C8": "Casual Elegance", "#FFA09D98": "Casual Grey", "#FFD3C5AF": "Casual Khaki", "#FF8FABD6": "Casual Water", "#FFECBAC0": "Cat Nose", "#FF636D70": "Cat Person", "#FFCDDFBB": "Cat Woman", "#FFE7E7E3": "Cat\u2019s Cream", "#FFD6A75D": "Cat\u2019s Eye Marble", "#FF0071A0": "Cat\u2019s Purr", "#FF475742": "Catachan Green", "#FFE2DCCC": "Catacomb Bone", "#FFDBD7D0": "Catacomb Walls", "#FF429395": "Catalan", "#FF72A49F": "Catalina", "#FF5C7884": "Catalina Coast", "#FF859475": "Catalina Green", "#FFEFAC73": "Catalina Tile", "#FFC5D0D5": "Catalyst Grey", "#FF90C4B4": "Catarina Green", "#FF634049": "Catawba Grape", "#FF6C685E": "Catch of the Day", "#FFB5DCD8": "Catch the Wave", "#FF66A545": "Caterpillar", "#FF657D82": "Catfish", "#FF9D632D": "Cathay Spice", "#FFACAAA7": "Cathedral", "#FF7A999C": "Cathedral Glass", "#FFABA9A7": "Cathedral Grey", "#FF80796E": "Cathedral Stone", "#FF015158": "Cathedral View", "#FF00FF55": "Cathode Green", "#FFCCA800": "Catkin Yellow", "#FF9E4D28": "Catlinite", "#FFC9A8CE": "Catmint", "#FF80AA95": "Catnip", "#FF6F6066": "Catnip Wood", "#FF8EA5B6": "Cats and Dogs", "#FF595452": "Catskill Brown", "#FFE0E4DC": "Catskill White Variant", "#FF917546": "Cattail Brown", "#FFB64925": "Cattail Red", "#FFBB7B51": "Cattle Drive", "#FF4A4649": "Catwalk", "#FFBE4236": "Caught Red-Handed", "#FF599C99": "Caulerpa Lentillifera", "#FFEBE5D0": "Cauliflower", "#FFF2E4C7": "Cauliflower Cream", "#FF6F788F": "Causeway", "#FF11DD00": "Caustic Green", "#FFD5DDE5": "Cautious Blue", "#FFDFD8D9": "Cautious Grey", "#FFDAE4DE": "Cautious Jade", "#FF3F4C5A": "Cavalry", "#FF990003": "Cavalry Brown", "#FFDCE2CE": "Cavan", "#FF52B7C6": "Cave Lake", "#FF86736E": "Cave of the Winds", "#FFAA1100": "Cave Painting", "#FFD6E5E2": "Cave Pearl", "#FF625C58": "Caveman", "#FFFED200": "Cavendish", "#FFA08D7A": "Cavern", "#FFB69981": "Cavern Clay", "#FFCEC3B3": "Cavern Echo", "#FF92987D": "Cavern Moss", "#FFE0B8B1": "Cavern Pink Variant", "#FF947054": "Cavern Sand", "#FF515252": "Cavernous", "#FF2B2C30": "Caviar", "#FF533E39": "Caviar Black", "#FF772244": "Caviar Couture", "#FF72939E": "Cavolo Nero", "#FFA6D0D6": "Cay", "#FF941100": "Cayenne", "#FF52798D": "Cayman Bay", "#FF495A44": "Cayman Green", "#FF9271A7": "Ce Soir", "#FF463430": "Cedar Variant", "#FF788078": "Cedar Forest", "#FF686647": "Cedar Glen", "#FF69743E": "Cedar Green", "#FFBF6955": "Cedar Grove", "#FFB7BCAD": "Cedar Mill", "#FF8B786F": "Cedar Plank", "#FFA96A50": "Cedar Plank Salmon", "#FF9B6663": "Cedar Ridge", "#FF91493E": "Cedar Staff", "#FFA97367": "Cedar Wood", "#FFDDA896": "Cedarville", "#FFE9EBE7": "Ceiling Bright White", "#FFCCD4CB": "Celadon Glaze", "#FF2F847C": "Celadon Green", "#FF7EBEA5": "Celadon Porcelain", "#FFB1DAC6": "Celadon Sorbet", "#FFC6CAB8": "Celadon Tint", "#FFEBE667": "Celandine", "#FFB8BFAF": "Celandine Green", "#FF9D86AD": "Celeb City", "#FFE6C17A": "Celebration", "#FF008BC4": "Celebration Blue", "#FFB4C04C": "Celery Variant", "#FFD4E0B3": "Celery Bunch", "#FFC5CC7B": "Celery Green", "#FFE1D792": "Celery Heart", "#FFEAEBD1": "Celery Ice", "#FFC1FD95": "Celery Mousse", "#FFC5BDA5": "Celery Powder", "#FFD4E4BA": "Celery Root", "#FFD0D8BE": "Celery Satin", "#FFE1DF9A": "Celery Sceptre", "#FF9ED686": "Celery Sprig", "#FFCAEDD0": "Celery Stick", "#FFCCEEC2": "Celery Victor", "#FFDBD9CD": "Celery White", "#FF406374": "Celeste Blue", "#FF007894": "Celestial", "#FF11CC00": "Celestial Alien", "#FF2C4D69": "Celestial Blue Variant", "#FF0A2538": "Celestial Canvas", "#FFDAEAF6": "Celestial Cathedral", "#FFDD4455": "Celestial Coral", "#FFEAD97C": "Celestial Crown", "#FF992266": "Celestial Dragon", "#FFEAEBE9": "Celestial Glow", "#FF2DDFC1": "Celestial Green", "#FF7C94B3": "Celestial Horizon", "#FF091F92": "Celestial Indigo", "#FFC7DAE8": "Celestial Light", "#FFE3D4B9": "Celestial Moon", "#FF9C004A": "Celestial Pink", "#FF3C7AC2": "Celestial Plum", "#FF85C1C4": "Celestine", "#FF24A4C8": "Celestine Spring", "#FF99A7AB": "Celestra Grey", "#FFB5C7D2": "Celestyn", "#FF826167": "Celine", "#FF75553F": "Cellar Door", "#FFDDB582": "Cellini Gold", "#FF3A4E5F": "Cello Variant", "#FF515153": "Celluloid", "#FFE76A35": "Celosia Orange", "#FF2B3F36": "Celtic Variant", "#FF246BCE": "Celtic Blue", "#FF006940": "Celtic Clover", "#FF1F6954": "Celtic Green", "#FFC5D4CE": "Celtic Grey", "#FFF5E5CE": "Celtic Linen", "#FF00886B": "Celtic Queen", "#FF2E4C5B": "Celtic Rush", "#FFAADEB2": "Celtic Spring", "#FF8BAB68": "Celuce", "#FF725671": "Cembra Blossom", "#FFA5A391": "Cement Variant", "#FF7B737B": "Cement Feet", "#FFB5ABA4": "Cement Greige", "#FFC0C7D0": "Cemetery Ash", "#FF3E7FA5": "Cendre Blue", "#FF327A68": "Census", "#FF90673F": "Centaur", "#FF8B6A4F": "Centaur Brown", "#FFB3A7A6": "Centennial Rose", "#FFF7E077": "Cente\u014dtl Yellow", "#FF008B56": "Center Field", "#FF6D2400": "Centipede Brown", "#FFC08F45": "Centra", "#FF685549": "Centre Earth", "#FF817A69": "Centre Ridge", "#FFC8C7CB": "Centre Stage", "#FF9C7B87": "Century\u2019s Last Sunset", "#FFEDD1AC": "Ceramic Beige", "#FF16A29A": "Ceramic Blue Turquoise", "#FFA05843": "Ceramic Brown", "#FFE8A784": "Ceramic Glaze", "#FF3BB773": "Ceramic Green", "#FF908268": "Ceramic Pot", "#FFFEFEE0": "Ceramite White", "#FFEFD7AB": "Cereal Flake", "#FFCCCBCD": "Cerebellum Grey", "#FFCCCCCC": "Cerebral Grey", "#FFD69E59": "Ceremonial Gold", "#FF91998E": "Ceremonial Grey", "#FFC38B82": "Ceremonial Ochre", "#FF2A2756": "Ceremonial Purple", "#FF997B00": "Cerignola Olive", "#FFAD134E": "Cerise Variant", "#FFF2BDA2": "Certain Peach", "#FF55AAEE": "Cerulean Variant", "#FF8CC6CB": "Cerulean Skies", "#FFACBFCD": "Cerulean Tint", "#FF337EFF": "Ceruleite", "#FF001440": "Cetacean Blue", "#FF33431E": "Ceylanite", "#FFF3E9D6": "Ceylon Cream", "#FFD4AE40": "Ceylon Yellow", "#FF756858": "Ceylonese", "#FF007AA5": "CG Blue", "#FFE03C31": "CG Red", "#FF56FFFF": "CGA Blue", "#FF77926F": "Ch\u00e1 L\u01dc Green", "#FFFFDB92": "Cha-Cha-Cha", "#FFEC7D2C": "Chaat Masala", "#FFFDE9E0": "Chablis Variant", "#FFF6E0CF": "Chafed Wheat", "#FF008B62": "Chagall Green", "#FFEBCFAE": "Chai", "#FFF9CBA0": "Chai Latte", "#FFBD7C4F": "Chai Spice", "#FFA97B2D": "Chai Tea", "#FFEFD7B3": "Chai Tea Latte", "#FF847E78": "Chain Link", "#FF81777F": "Chain Mail", "#FFA4A6A4": "Chain Reaction", "#FFC1B2B3": "Chaise Mauve", "#FF8B5E8F": "Chakra", "#FFDDDD99": "Chalcedony", "#FF4B6057": "Chalcedony Green", "#FF6770AE": "Chalcedony Violet", "#FFC29867": "Chalet", "#FF5A6E41": "Chalet Green Variant", "#FFEDEAE5": "Chalk", "#FFEAEBE6": "Chalk Dust", "#FFE0CEB7": "Chalkware", "#FFDFC281": "Chalky Variant", "#FFD0EBF1": "Chalky Blue White", "#FFCD7A50": "Challah Bread", "#FF475877": "Chambray Variant", "#FF93AECE": "Chambray Blue", "#FFCEDAAC": "Chameleon", "#FFC0C2A0": "Chameleon Tango", "#FFE8CD9A": "Chamois Variant", "#FFF0E1D0": "Chamois Cloth", "#FFAD8867": "Chamois Leather", "#FFB3A385": "Chamois Tan", "#FF986E19": "Chamois Yellow", "#FFE4C697": "Chamomile", "#FFDAC395": "Chamomile Tea", "#FFE9D2AC": "Champagne Tint", "#FFD4C49E": "Champagne Beige", "#FFF0E1C5": "Champagne Bliss", "#FFDDCEAD": "Champagne Bubbles", "#FFF1E4CB": "Champagne Burst", "#FFE3D7AE": "Champagne Cocktail", "#FFEBD3E4": "Champagne Elegance", "#FFF6ECE2": "Champagne Flute", "#FFD7C1B4": "Champagne Glee", "#FFE8D6B3": "Champagne Gold", "#FFC5B067": "Champagne Grape", "#FFF3E5DD": "Champagne Ice", "#FFFFDFB0": "Champagne Orange", "#FFFADDC4": "Champagne Peach", "#FFE3D6CC": "Champagne Rose", "#FFF8EEC4": "Champagne Tickle", "#FFF4E1C6": "Champagne Toast", "#FFEFD4AE": "Champagne Wishes", "#FF949089": "Champignon", "#FF7B5986": "Champion", "#FF606788": "Champion Blue", "#FF435572": "Champlain Blue", "#FFA0A6A9": "Chance of Rain", "#FFA5BCBA": "Chance of Showers", "#FFECBA5D": "Chandra Cream", "#FFF4AFCD": "Changeling Pink", "#FFB6927C": "Changing Seasons", "#FFF1C3C2": "Channel", "#FF04D8B2": "Channel Marker Green", "#FFA0928D": "Channel Seal Grey", "#FFEEE8D2": "Chanoyu", "#FFFFC66E": "Chanterelle", "#FFA28776": "Chanterelle Sauce", "#FF870000": "Chanticleer", "#FFEDB8C7": "Chantilly Variant", "#FFF1E2DE": "Chantilly Lace", "#FF740600": "Chaotic Red", "#FFBB2266": "Chaotic Roses", "#FFE5D0B0": "Chaparral", "#FFDEE5EC": "Chapeau Violet", "#FFB0D2E7": "Chapel Blue", "#FFBBD1E2": "Chapel Choir", "#FFEDE2AC": "Chapel Wall", "#FF644B41": "Chaps", "#FF9F9369": "Chapter", "#FFB79779": "Char Latte", "#FF394043": "Charade Variant", "#FF504D4C": "Charadon Granite", "#FF343837": "Charcoal Tint", "#FF5D625C": "Charcoal Briquette", "#FF595758": "Charcoal Dust", "#FF5D5B56": "Charcoal Sketch", "#FF474F43": "Charcoal Smoke", "#FF60605E": "Charcoal Smudge", "#FF949D8D": "Charcoal Tint", "#FF48553F": "Chard", "#FFF8EADF": "Chardon Variant", "#FFEFE8BC": "Chardonnay Variant", "#FF632A60": "Charisma", "#FFE7C180": "Charismatic", "#FFEE2244": "Charismatic Red", "#FF9AC1DC": "Charismatic Sky", "#FF9F414B": "Charleston Cherry", "#FFC09278": "Charleston Chocolate", "#FF232B2B": "Charleston Green", "#FF995500": "Charlie Brown", "#FF948263": "Charlie Horse", "#FFE2E483": "Charlock", "#FFA4DCE6": "Charlotte Variant", "#FFD0748B": "Charm Variant", "#FFEBCFC7": "Charmed", "#FFA1A1A0": "Charmed Chalice", "#FF007F3A": "Charmed Green", "#FFFF90A2": "Charming Cherry", "#FFD4E092": "Charming Green", "#FF11BB44": "Charming Nature", "#FFF5AD75": "Charming Peach", "#FFEDD3D2": "Charming Pink", "#FF8C7281": "Charming Violet", "#FF6A577F": "Charoite Violet", "#FFF1EBEA": "Charolais Cattle", "#FFA1A29C": "Charon", "#FF3E0007": "Charred Brown", "#FF553B3D": "Charred Chocolate", "#FF885132": "Charred Clay", "#FF5B4E4A": "Charred Hickory", "#FFC01205": "Charsiu", "#FFB2CCE1": "Charted", "#FF69B2CF": "Charter", "#FF546E91": "Charter Blue", "#FFC1F80A": "Chartreuse Variant", "#FFE4DCC6": "Chartreuse Frost", "#FFDAD000": "Chartreuse Shot", "#FFB5CC18": "Chartreuse Spark", "#FF16A3CB": "Charybdis", "#FF876044": "Chasm", "#FF63B521": "Chasm Green", "#FF9944EE": "Chaste Blossoms", "#FFF79A3E": "Chat Orange", "#FFB5A28A": "Chateau", "#FF5B4B44": "Chateau Brown", "#FF419F59": "Chateau Green Variant", "#FF7C583A": "Ch\u00e2teau Mantle", "#FFDBA3CE": "Chateau Rose", "#FFB3ABB6": "Chatelle Variant", "#FF2C5971": "Chathams Blue Variant", "#FFB0AB9C": "Chatroom", "#FF89B386": "Chatty Cricket", "#FFA09287": "Chatura Beige", "#FFC7E2C6": "Chayote", "#FFED214D": "Che Guevara Red", "#FFEEB15D": "Cheater", "#FFEE9A09": "Cheddar", "#FFD2AD87": "Cheddar Biscuit", "#FFF0843A": "Cheddar Cheese", "#FFF9C982": "Cheddar Chunk", "#FFF5D4B5": "Cheddar Corn", "#FFB67DAF": "Cheddar Pink Mauve", "#FFA55A55": "Cheek Red", "#FF7B4D3A": "Cheeky Chestnut", "#FFDCC7C0": "Cheerful Heart", "#FFFFE195": "Cheerful Hue", "#FFFDA471": "Cheerful Tangerine", "#FFD3D7E7": "Cheerful Whisper", "#FF7E4258": "Cheerful Wine", "#FFFFC723": "Cheerful Yellow", "#FFBCCB08": "Cheerly Kiwi", "#FFC09962": "Cheers!", "#FFF08A88": "Cheery", "#FFFFA600": "Cheese", "#FFFDDE45": "Cheese It Up", "#FFFF9613": "Cheese Please", "#FFFFE4BE": "Cheese Powder", "#FFFFB96F": "Cheese Puff", "#FFDFAD51": "Cheese Wiz", "#FFFFFCDA": "Cheesecake", "#FFFFCC77": "Cheesus", "#FFEEB033": "Cheesy Cheetah", "#FFF0E093": "Cheesy Frittata", "#FFFAE195": "Cheesy Grin", "#FFF3F4F5": "Chef\u2019s Hat", "#FFCC3B3B": "Chef\u2019s Kiss", "#FFA3D1E8": "Chefchaouen Blue", "#FF88A95B": "Chelsea Cucumber Variant", "#FF546D66": "Chelsea Garden", "#FF95532F": "Chelsea Gem Variant", "#FFB6B7B0": "Chelsea Grey", "#FFBEAC9F": "Chelsea Mauve", "#FFF94009": "Ch\u00e9ng H\u00f3ng S\u00e8 Orange", "#FFA6CD91": "Chenille", "#FFF1E7D6": "Chenille Spread", "#FFF9EFE2": "Chenille White", "#FFDEC371": "Chenin Variant", "#FF22BBFF": "Cherenkov Radiation", "#FFF4E3CB": "Cherish Cream", "#FFE6E4DA": "Cherish Is the Word", "#FFCCACD7": "Cherish the Moment", "#FFBA97B1": "Cherished", "#FFFC9293": "Cherished One", "#FFAC0132": "Chernobog", "#FFE3DCDA": "Chernobog Breath", "#FFF5CD82": "Cherokee Variant", "#FFDD7722": "Cherokee Dignity", "#FF824E4A": "Cherokee Red", "#FFA22452": "Cherries Jubilee", "#FFCF0234": "Cherry", "#FF908279": "Cherry Bark", "#FF9F4D65": "Cherry Berry", "#FFAD5344": "Cherry Blink", "#FFF5C1D5": "Cherry Blossom", "#FFFFC9DD": "Cherry Blush", "#FFB73D3F": "Cherry Bomb", "#FFE26B81": "Cherry Brandy", "#FFFFBBB4": "Cherry Chip", "#FF883F41": "Cherry Cobbler", "#FF8E5E65": "Cherry Cocoa", "#FF894C3B": "Cherry Cola", "#FFEBBED3": "Cherry Cordial", "#FFC71414": "Cherry Crush", "#FFBD6973": "Cherry Fizz", "#FFFBDAE8": "Cherry Flower", "#FFF392A0": "Cherry Foam", "#FFCB6276": "Cherry Fruit", "#FFCC5160": "Cherry Hill", "#FFDD98A6": "Cherry Ice", "#FFBD9095": "Cherry Juice", "#FF6C2C45": "Cherry Juice Red", "#FFA32E39": "Cherry Kiss", "#FFB15E67": "Cherry Kisses", "#FFC8385A": "Cherry Lolly", "#FF6A332D": "Cherry Mahogany", "#FFA57C7A": "Cherry Mocha", "#FFAC495C": "Cherry on Top", "#FFFE314B": "Cherry Paddle Pop", "#FFF9E7F4": "Cherry Pearl", "#FF620B15": "Cherry Picking", "#FFBD2C22": "Cherry Pie Variant", "#FFC7607B": "Cherry Pink", "#FFA10047": "Cherry Plum", "#FFA64137": "Cherry Race", "#FFF7022A": "Cherry Red", "#FFC92435": "Cherry Sangria", "#FFD81D26": "Cherry Shine", "#FFFF0044": "Cherry Soda", "#FFE76178": "Cherry Static", "#FF933D3E": "Cherry Tart", "#FFB7938F": "Cherry Taupe", "#FFF2013F": "Cherry Tomato", "#FFDFB7B4": "Cherry Tree", "#FFE10646": "Cherry Velvet", "#FFB04556": "Cherry Wine", "#FFB22743": "Cherryade", "#FFF79890": "Cherrystone", "#FF848182": "Chert", "#FFF5D7DC": "Cherub Variant", "#FFFFE6F1": "Cherubic", "#FFABBD90": "Chervil Leaves", "#FFFFE9C5": "Chess Ivory", "#FF876B4B": "Chester Brown", "#FF742802": "Chestnut Tint", "#FF9A6844": "Chestnut Beach", "#FFC19C86": "Chestnut Bisque", "#FF6D1008": "Chestnut Brown", "#FFBCA486": "Chestnut Butter", "#FF8E5637": "Chestnut Chest", "#FFEBC795": "Chestnut Dressing", "#FF874E2D": "Chestnut Glaze", "#FFAB8508": "Chestnut Gold", "#FF2A4F21": "Chestnut Green", "#FF60281E": "Chestnut Leather", "#FF6D3C32": "Chestnut Peel", "#FF852E19": "Chestnut Plum", "#FF6C333F": "Chestnut Red", "#FFCD5252": "Chestnut Rose Variant", "#FF995D3B": "Chestnut Stallion", "#FFEAF1E6": "Chestnut White", "#FF516FA0": "Chesty Bond", "#FF666FB4": "Chetwode Blue Variant", "#FFF6F2E8": "Cheviot", "#FFE6B0AF": "Chewing Gum", "#FFE292B6": "Chewing Gum Pink", "#FF977043": "Chewy Caramel", "#FF9F918A": "Cheyenne Rock", "#FFD52B2D": "Chi-Gong", "#FFEEECB5": "Chia", "#FF734342": "Chianti", "#FFA4725A": "Chic Brick", "#FFD8EBD6": "Chic Green", "#FFCFCCC5": "Chic Grey", "#FFEDE1C8": "Chic Magnet", "#FFF0D1C8": "Chic Peach", "#FF7C9270": "Chic Shade", "#FFAA9788": "Chic Taupe", "#FF5B5D56": "Chicago Variant", "#FFB6DBE9": "Chicago Blue", "#FFCAC2BD": "Chicago Fog", "#FF96ADBA": "Chicago Skyline", "#FFE2C555": "Chicco d\u2019Oro", "#FF7E6072": "Chicha Morada", "#FFBF7D80": "Chick Flick", "#FFFFCF65": "Chickadee", "#FFDD2222": "Chicken Comb", "#FFCC8822": "Chicken Masala", "#FFFBE98E": "Chickery Chick", "#FFEFE7DF": "Chickpea", "#FFD9DFE3": "Chickweed", "#FFD9EEB4": "Chicon", "#FFA78658": "Chicory", "#FF4D3730": "Chicory Coffee", "#FF66789A": "Chicory Flower", "#FFBBAB75": "Chicory Green", "#FF5F423F": "Chicory Root", "#FF6A5637": "Chieftain", "#FFF0F5BB": "Chiffon Variant", "#FFDBC963": "Chifle Yellow", "#FFEAE5C5": "Child of Heaven", "#FFF0F4F8": "Child of Light", "#FFC68D37": "Child of the Moon", "#FF220077": "Child of the Night", "#FFE7BCD4": "Child\u2019s Play", "#FFE26D68": "Childhood Crush", "#FFA5A8D6": "Childish Wonder", "#FFE8C0CF": "Childlike", "#FFA1CED7": "Children\u2019s Soft Blue", "#FFD05E34": "Chilean Fire Variant", "#FFF9F7DE": "Chilean Heath Variant", "#FFC9CBC1": "Chilean Sea Bass", "#FFD1D5E7": "Chill in the Air", "#FF256D8D": "Chill of the Night", "#FFEC4236": "Chilled Chilly", "#FFCBCDB2": "Chilled Cucumber", "#FFFFE696": "Chilled Lemonade", "#FFE4EFDE": "Chilled Mint", "#FF6D4052": "Chilled Wine", "#FFBE4B41": "Chilli", "#FF4B1C35": "Chilli Black Red", "#FFCC5544": "Chilli Cashew", "#FF985E2B": "Chilli Con Carne", "#FFE93A0E": "Chilli Crab", "#FFF0B692": "Chilli Dip", "#FF8D7040": "Chilli Green", "#FF9D453C": "Chilli Oil", "#FFAC1E3A": "Chilli Pepper", "#FFBC4E40": "Chilli Sauce", "#FFCA7C74": "Chilli Soda", "#FF8AAEC3": "Chilly Blue", "#FFFD9989": "Chilly Spice", "#FFE5F1ED": "Chilly White", "#FF74626D": "Chimaera", "#FFC89B75": "Chimaera Brown", "#FFB16355": "Chimayo Red", "#FFC7CA86": "Chimes", "#FF4A5257": "Chimney", "#FF3C4247": "Chimney Smoke", "#FF272F38": "Chimney Sweep", "#FFDD3355": "Chin-Chin Cherry", "#FF444C60": "China Aster", "#FF5A6C80": "China Blue", "#FF8A7054": "China Cinnamon", "#FF718B9A": "China Clay", "#FFF8F0E5": "China Cup", "#FFF3E4D5": "China Doll", "#FF3A6468": "China Green Blue", "#FFFBF3D3": "China Ivory Variant", "#FFBCC9C7": "China Light Green", "#FF3D5C77": "China Pattern", "#FFDF6EA1": "China Pink", "#FFAD2B10": "China Red", "#FFA8516E": "China Rose", "#FF034F7C": "China Seas", "#FFE3D1CC": "China Silk", "#FFEAE6D9": "China White", "#FF464960": "Chinaberry", "#FFD6C2D2": "Chinaberry Bloom", "#FF9C8E7B": "Chinchilla", "#FFD0BBA7": "Chinchilla Chenille", "#FF7F746E": "Chinchilla Grey", "#FF4D5AAF": "Chinese Bellflower", "#FF111100": "Chinese Black", "#FF365194": "Chinese Blue", "#FFCD8032": "Chinese Bronze", "#FFAB381F": "Chinese Brown", "#FFF1D7CB": "Chinese Cherry", "#FFCB5251": "Chinese Dragon", "#FF006967": "Chinese Garden", "#FFDDAA00": "Chinese Gold", "#FFD0DB61": "Chinese Green", "#FFEBDBCA": "Chinese Hamster", "#FFE09E87": "Chinese Ibis Brown", "#FF3F312B": "Chinese Ink", "#FFCBD1BA": "Chinese Jade", "#FF60C7C2": "Chinese Lacquer", "#FFF09056": "Chinese Lantern", "#FFCCD6B0": "Chinese Leaf", "#FFA4BE5C": "Chinese Money Plant", "#FFF37042": "Chinese Orange", "#FFDE70A1": "Chinese Pink", "#FF3A5F7D": "Chinese Porcelain", "#FF720B98": "Chinese Purple", "#FFCD071E": "Chinese Red", "#FFB94047": "Chinese Safflower", "#FFD8D3B2": "Chinese Silk", "#FFDDDCEF": "Chinese Silver", "#FFACAD98": "Chinese Tea Green", "#FF8FBFBD": "Chinese Tzu", "#FF92698F": "Chinese Violet", "#FFE2E5DE": "Chinese White", "#FFDBD2BB": "Chino Variant", "#FF7C8C87": "Chinois Green", "#FF9DD3A8": "Chinook Variant", "#FFC8987E": "Chinook Salmon", "#FF554747": "Chinotto", "#FFD5C7B9": "Chintz", "#FFECBCB6": "Chintz Rose", "#FFCFA14A": "Chipmunk", "#FFAA4433": "Chipolata", "#FF683E3B": "Chipotle Paste", "#FFDDD618": "Chips Provencale", "#FF026B67": "Chitin Green", "#FFAEB2C0": "Chivalrous", "#FFC7662A": "Chivalrous Fox", "#FF816558": "Chivalrous Walrus", "#FF5D99B1": "Chivalry", "#FFBF784E": "Chivalry Copper", "#FF4D5637": "Chive", "#FF4F3650": "Chive Bloom", "#FF86619F": "Chive Blossom", "#FFA193BF": "Chive Flower", "#FF56AE57": "Chlorella Green", "#FF93D8C2": "Chloride", "#FF5E8E82": "Chlorite", "#FF44891A": "Chlorophyll", "#FFB3D6C3": "Chlorophyll Cream", "#FF4AFF00": "Chlorophyll Green", "#FF75876E": "Chlorosis", "#FFB4835B": "Choco Biscuit", "#FF993311": "Choco Chic", "#FF63493E": "Choco Death", "#FF7D5F53": "Choco Loco", "#FFF9BC08": "Chocobo Feather", "#FF993300": "Chocoholic", "#FF773333": "Chocolate Bar", "#FF775130": "Chocolate Bells", "#FF782A2E": "Chocolate Bhut Jolokia", "#FF7F6054": "Chocolate Bliss", "#FF7E4F35": "Chocolate Bonbon", "#FF765841": "Chocolate Caliente", "#FF452207": "Chocolate Castle", "#FF8A4438": "Chocolate Cherry", "#FF928178": "Chocolate Chiffon", "#FFAB4231": "Chocolate Chilli", "#FF6E5F52": "Chocolate Chip", "#FF6B574A": "Chocolate Chunk", "#FF644D42": "Chocolate Coco", "#FF58111A": "Chocolate Cosmos", "#FF8B4121": "Chocolate Covered", "#FFA89581": "Chocolate Cream Pie", "#FF605647": "Chocolate Cupcake", "#FF916D5E": "Chocolate Curl", "#FF96786D": "Chocolate Delight", "#FF6B5947": "Chocolate Diamonds", "#FF674848": "Chocolate Eclair", "#FF623D2E": "Chocolate Escape", "#FF8E473B": "Chocolate Explosion", "#FF5C3612": "Chocolate Fantasies", "#FF603932": "Chocolate Fondant", "#FF9A3001": "Chocolate Fondue", "#FFDED5C8": "Chocolate Froth", "#FF8F786C": "Chocolate Heart", "#FF3C1421": "Chocolate Kiss", "#FF66433B": "Chocolate Lab", "#FF993322": "Chocolate Lust", "#FF7A463A": "Chocolate Magma", "#FF331100": "Chocolate Melange", "#FF976F4C": "Chocolate Milk", "#FF998069": "Chocolate Moment", "#FFB5A39C": "Chocolate Oatmeal", "#FFBB5544": "Chocolate Oatmeal Cookie", "#FF884400": "Chocolate Pancakes", "#FF3F2F31": "Chocolate Plum", "#FFA58C7B": "Chocolate Powder", "#FF66424D": "Chocolate Praline", "#FF60504B": "Chocolate Pretzel", "#FF6F6665": "Chocolate Pudding", "#FF714F29": "Chocolate Rain", "#FF4D3635": "Chocolate Red", "#FF76604E": "Chocolate Ripple", "#FF4E1B0B": "Chocolate Rush", "#FF5C4945": "Chocolate Soul", "#FF8C6C6F": "Chocolate Sparkle", "#FF6F4E43": "Chocolate Sprinkle", "#FF84563C": "Chocolate Stain", "#FF68574B": "Chocolate Swirl", "#FF956E5F": "Chocolate Temptation", "#FF5F4940": "Chocolate Therapy", "#FF403534": "Chocolate Torte", "#FF612E32": "Chocolate Truffle", "#FF7F7453": "Chocolate Velvet", "#FF937979": "Chocolaty", "#FFF2E1D1": "Choice Cream", "#FF8F583C": "Ch\u014djicha Brown", "#FF867578": "Choo Choo", "#FFC7BCA1": "Chopped Almonds", "#FF336B4B": "Chopped Chive", "#FFB6C2A1": "Chopped Dill", "#FFE0D1B8": "Chopsticks", "#FFB77795": "Choral Singer", "#FFAA0011": "Chorizo", "#FF8E8987": "Chorus of Elephants", "#FF8C9632": "Chorus of Frogs", "#FFFF6301": "Chos Garden Marigold", "#FFB95754": "Ch\u014dshun Red", "#FFEBCF7D": "Choux \u00e0 la Cr\u00e8me", "#FFE5D2B2": "Chowder Bowl", "#FF382161": "Christalle Variant", "#FF71A91D": "Christi Variant", "#FF009094": "Christina Brown", "#FF2A8FBD": "Christmas Blue", "#FF5D2B2C": "Christmas Brown", "#FFCAA906": "Christmas Gold", "#FF3C8D0D": "Christmas Green", "#FF68846A": "Christmas Holly", "#FF477266": "Christmas Ivy", "#FFD56C2B": "Christmas Orange", "#FF6E5A49": "Christmas Ornament", "#FFE34285": "Christmas Pink", "#FF564342": "Christmas Pudding", "#FF4D084B": "Christmas Purple", "#FFB01B2E": "Christmas Red", "#FFFFDDBB": "Christmas Rose", "#FFE1DFE0": "Christmas Silver", "#FFD4C5BA": "Christobel", "#FFF6BBCA": "Christy\u2019s Smile", "#FF292929": "Chromaphobic Black", "#FFE1DFE1": "Chrome", "#FFA8A9AD": "Chrome Aluminium", "#FFCDC8D2": "Chrome Chalice", "#FFE7EDDD": "Chrome Green", "#FFCAC7B7": "Chrome White Variant", "#FF82CAFC": "Chromis Damsel Blue", "#FF06B48B": "Chromophobia Green", "#FF3E4265": "Chronicle", "#FF72A8D1": "Chronus Blue", "#FFC35458": "Chrysanthemum", "#FF9DB8AB": "Chrysanthemum Leaf", "#FF004F39": "Chrysocolla Dark Green", "#FF378661": "Chrysocolla Green", "#FF006B57": "Chrysocolla Medium Green", "#FF8E9849": "Chrysolite", "#FF39334A": "Chrysomela Goettingensis", "#FF8FB2A3": "Chrysopal Light Green", "#FF4A2200": "Chrysophobia", "#FFADBA98": "Chrysoprase", "#FF613521": "Chubby Chocolate", "#FFB43548": "Chubby Kiss", "#FFBF413A": "Chuckles", "#FF91C1C6": "Chuff Blue", "#FF1559DB": "Chun-Li Blue", "#FFFFC84B": "Chunky Bee", "#FFCFCDCF": "Chupacabra Grey", "#FF3D4161": "Church Blue", "#FFB3B5AF": "Church Mouse", "#FF4D4D58": "Churchill", "#FFA49A85": "Churchill Downs", "#FF9F5E4E": "Chutney", "#FFA97765": "Chutney Brown", "#FF0F0809": "Chyornyi Black", "#FFDE9D7C": "Ciao", "#FF938A43": "Cider Mill", "#FF8A946F": "Cider Pear Green", "#FFAE8167": "Cider Spice", "#FFB98033": "Cider Toddy", "#FFE7D6AF": "Cider Yellow", "#FFA5CEE8": "Cielo", "#FF7D4E38": "Cigar", "#FF9C7351": "Cigar Box", "#FF78857A": "Cigar Smoke", "#FFEE5500": "Cigarette Glow", "#FF4A5C52": "Cilantro", "#FFCECBAE": "Cilantro Cream", "#FF6B3D38": "Cimarron", "#FF242A2E": "Cinder Variant", "#FF7C787B": "Cinder Fox", "#FFFBD7CC": "Cinderella Variant", "#FFFFC6C4": "Cinderella Pink", "#FF95878E": "Cinema Screen", "#FF730113": "Cinnabar Variant", "#FF634D45": "Cinnabark", "#FFD26911": "Cinnamon Variant", "#FFCF8D6C": "Cinnamon Brandy", "#FF9E6A19": "Cinnamon Brown", "#FFFFBF6E": "Cinnamon Buff", "#FFAC4F06": "Cinnamon Bun", "#FFE8DDCF": "Cinnamon Cake", "#FFB15D63": "Cinnamon Candle", "#FF794344": "Cinnamon Cherry", "#FFD1A79C": "Cinnamon Cocoa", "#FF705742": "Cinnamon Crumble", "#FFA37D5A": "Cinnamon Crunch", "#FFA97673": "Cinnamon Diamonds", "#FFEDBC9B": "Cinnamon Foam", "#FFD3B191": "Cinnamon Frost", "#FFDBBBA7": "Cinnamon Ice", "#FFEBDAB5": "Cinnamon Milk", "#FFD5B199": "Cinnamon Mocha", "#FFBB9988": "Cinnamon Roast", "#FFC0737A": "Cinnamon Roll", "#FFC2612C": "Cinnamon Rufous", "#FFB78153": "Cinnamon Sand", "#FFBA9275": "Cinnamon Scone", "#FF9C5736": "Cinnamon Sparkle", "#FF935F43": "Cinnamon Spice", "#FFB05127": "Cinnamon Stick", "#FFC9543A": "Cinnamon Stone", "#FFB64C2D": "Cinnamon Sunset", "#FFDEC0AD": "Cinnamon Tea", "#FF8D7D77": "Cinnamon Toast", "#FF9F7250": "Cinnamon Twist", "#FFDAB2A4": "Cinnamon Whip", "#FFA6646F": "Cinnapink", "#FFFFFF88": "Cinque Foil", "#FF5D3B2E": "Cioccolato Variant", "#FFAA7691": "Cipher", "#FFC8CEC3": "Cipollino", "#FF6258C4": "Circumorbital Ring", "#FFFC5E30": "Circus", "#FFAD835C": "Circus Peanut", "#FF954A4C": "Circus Red", "#FFD6E4E1": "Cirrus Blue", "#FFA9B0B6": "Cistern", "#FF6A7F8B": "Citadel", "#FF9EABAD": "Citadel Blue", "#FFF6B906": "Citra", "#FF933709": "Citrine Brown", "#FFE9E89B": "Citrino", "#FFD5C757": "Citron Tint", "#FFDEFF00": "Citron Goby", "#FF66BB77": "Citronella", "#FFB8AF23": "Citronelle", "#FFC4AA27": "Citronette", "#FFDBB239": "Citronite", "#FFCD9C2B": "Citronne", "#FF9FB70A": "Citrus Variant", "#FFE1793A": "Citrus Blast", "#FFE4DE8E": "Citrus Butter", "#FFD0D557": "Citrus Delight", "#FFF9A78D": "Citrus Hill", "#FFF6B96B": "Citrus Honey", "#FFB3D157": "Citrus Leaf", "#FFC3DC68": "Citrus Lime", "#FFF7EDDE": "Citrus Mist", "#FFD26643": "Citrus Notes", "#FFB7BB6B": "Citrus Peel", "#FFFDEA83": "Citrus Punch", "#FFEDE0AE": "Citrus Rind", "#FFF2C6A7": "Citrus Sachet", "#FFE2CD52": "Citrus Spice", "#FFFFC400": "Citrus Splash", "#FFE6D943": "Citrus Sugar", "#FF8BC34A": "Citrus Surge", "#FFD7C275": "Citrus Yellow", "#FFEDC85A": "Citrus Zest", "#FF6E674B": "City Arboretum", "#FF675C49": "City Bench", "#FFE0E0DC": "City Brume", "#FFC0B9AC": "City Dweller", "#FF0022AA": "City Hunter Blue", "#FFDFE6EA": "City Lights", "#FFA79B8A": "City Loft", "#FFB3ADA4": "City of Bridges", "#FFFAE6CB": "City of Diamonds", "#FFE6B4A6": "City of Pink Angels", "#FF525C61": "City Rain", "#FF663333": "City Roast", "#FFD8D3C3": "City Steam", "#FF888C8C": "City Storm", "#FFBAB2AB": "City Street", "#FFD1A67D": "City Sunrise", "#FFAEABA5": "City Tower", "#FFDAE3E7": "Cityscape", "#FFC56138": "Civara", "#FFDBE9DF": "Clair de Lune", "#FF838493": "Clairvoyance", "#FFDAD1C0": "Clam", "#FFF4D9AF": "Clam Chowder", "#FFEBDBC1": "Clam Up", "#FFE0D1BB": "Clambake", "#FFEDD0B6": "Clamshell", "#FF680018": "Claret Variant", "#FFC84C61": "Claret Red", "#FFE69C23": "Clarified Butter", "#FFFEA15B": "Clarified Orange", "#FF002255": "Clarinet", "#FFEAF0E0": "Clarity", "#FF684976": "Clary", "#FFC7C0CE": "Clary Sage", "#FFBBAAA1": "Classic", "#FF6E7042": "Classic Avocado", "#FF7C5261": "Classic Berry", "#FF0F4E81": "Classic Blue", "#FFA38BBF": "Classic Bouquet", "#FF6D624E": "Classic Bronze", "#FF6A493D": "Classic Brown", "#FF6B8885": "Classic Calm", "#FFF4F4F0": "Classic Chalk", "#FF974146": "Classic Cherry", "#FF9197A3": "Classic Cloud", "#FFB7B2AC": "Classic Cool", "#FF7A9494": "Classic Damask", "#FF888782": "Classic French Grey", "#FFC9A367": "Classic Gold", "#FF3EB753": "Classic Green", "#FFA39D93": "Classic Grey", "#FFACACA7": "Classic Greyscale", "#FFF2E0C3": "Classic Ivory", "#FFDBC79F": "Classic Khaki", "#FFF0EADC": "Classic Light Buff", "#FF728284": "Classic Movie", "#FF685E3F": "Classic Olive", "#FFC52534": "Classic Red", "#FFD6BCAA": "Classic Sand", "#FFB9B9B4": "Classic Silver", "#FFD3BCA4": "Classic Taupe", "#FFE4CEAE": "Classic Terra", "#FF71588D": "Classic Waltz", "#FFEBB875": "Classical Gold", "#FF8B7989": "Classical Violet", "#FFECE1CB": "Classical White", "#FFF8D492": "Classical Yellow", "#FFAEACAD": "Classy", "#FFBB99AA": "Classy Mauve", "#FF887E82": "Classy Plum", "#FF911F21": "Classy Red", "#FFB66A50": "Clay", "#FFDACEBE": "Clay Angel", "#FFE1C68F": "Clay Bake", "#FF8A7D69": "Clay Bath", "#FFD5D1C3": "Clay Beige", "#FFA9765D": "Clay Court", "#FF897E59": "Clay Creek Variant", "#FFA48374": "Clay Dusk", "#FFF8DCA3": "Clay Dust", "#FFAF604D": "Clay Figure", "#FFC8C1B6": "Clay Figurine", "#FFD8A686": "Clay Fire", "#FFBD856C": "Clay Ground", "#FFA68779": "Clay Marble", "#FFD37959": "Clay Mug", "#FFAE895D": "Clay Ochre", "#FFBDB298": "Clay Pebble", "#FFD9C8B7": "Clay Pipe", "#FF774433": "Clay Play", "#FFC3663F": "Clay Pot", "#FF956A66": "Clay Ridge", "#FFCDCACE": "Clay Slate Wacke", "#FFD4823C": "Clay Terrace", "#FF83756C": "Clayton", "#FF969283": "Claytone", "#FFD8DDB6": "Clean Air", "#FFF6E9D3": "Clean Canvas", "#FF8FE0C6": "Clean Green", "#FF4EC0ED": "Clean Pool", "#FF577396": "Clean Slate", "#FFCCBCB0": "Clean Sweep", "#FFC4EAE0": "Clear Aqua", "#FF247AFD": "Clear Blue", "#FF60949B": "Clear Brook", "#FFF6E6E4": "Clear Calamine", "#FFDAE8E1": "Clear Camouflage Green", "#FFDFDBD8": "Clear Cinnamon", "#FFBAB6B2": "Clear Concrete", "#FF98CECD": "Clear Cove", "#FFDFEFEA": "Clear Day Variant", "#FF12732B": "Clear Green", "#FFA3BBDA": "Clear Lake Trail", "#FF766CB0": "Clear Mauve", "#FFFAF6EA": "Clear Moon", "#FF214F86": "Clear Night Sky", "#FFEE8800": "Clear Orange", "#FF64005E": "Clear Plum", "#FFB4CCCB": "Clear Pond", "#FF412A7A": "Clear Purple", "#FFCE261C": "Clear Red", "#FFEAE7DA": "Clear Sand", "#FFE8F7FD": "Clear Skies", "#FF8ECCFE": "Clear Sky", "#FFE0DDD3": "Clear Stone", "#FFCAD6DA": "Clear", "#FF008A81": "Clear Turquoise", "#FFE2EAE7": "Clear View", "#FFE7F0F7": "Clear Vision", "#FFA3BEC4": "Clear Vista", "#FFAAD5DB": "Clear Water", "#FF66BBDD": "Clear Weather", "#FFF1F1E6": "Clear Yellow", "#FFC4DBCB": "Clearly Aqua", "#FF7E6596": "Clematis", "#FF3C3D8A": "Clematis Blue", "#FF98B652": "Clematis Green", "#FFE05AEC": "Clematis Magenta", "#FFFF9D0A": "Clementine Earring", "#FFFFAD01": "Clementine Jelly", "#FF00507F": "Cleo\u2019s Bath", "#FF007590": "Cleopatra", "#FF795088": "Cleopatra\u2019s Gown", "#FFF4E6E0": "Clerestory", "#FFF6EBEE": "Clichy White", "#FF5D8FBD": "Cliff Blue", "#FFD0AB8C": "Cliff Brown", "#FFC5AE80": "Cliff Ridge", "#FFB19475": "Cliff Rock", "#FFECDDD4": "Cliff Swallow", "#FFDDC5AA": "Cliff\u2019s View", "#FF6F8165": "Cliffside Park", "#FFE5E1CD": "Climate Change", "#FF466082": "Climate Control", "#FF58714A": "Climbing Ivy", "#FFB2CFD3": "Clinical Soft Blue", "#FF463623": "Clinker Variant", "#FF663145": "Clinker Red", "#FFA1B841": "Clipped Grass", "#FFA6937D": "Clippership Twill", "#FF550055": "Cloak and Dagger", "#FF605E63": "Cloak Grey", "#FF916660": "Cloak of Mystery", "#FF002211": "Clock Chimes Thirteen", "#FFE07630": "Clockwork Orange", "#FF72573D": "Clockworks", "#FF0773AF": "Cloisonn\u00e9", "#FFA58235": "Cloisonn\u00e9 Gold", "#FF99B090": "Cloistered Garden", "#FF5F6C84": "Clooney", "#FF361D0A": "Close but No Cigar", "#FFD5D6CF": "Close Knit", "#FF25252C": "Closed Shutter", "#FFE1DED9": "Closet Skeletons", "#FFF3EFCD": "Clotted Cream", "#FF991115": "Clotted Red", "#FFE4F0EF": "Cloud Variant", "#FFDFE7EB": "Cloud Abyss", "#FFF6F1FE": "Cloud Break", "#FFADB5BC": "Cloud Cover", "#FFF1EEE8": "Cloud Dancer", "#FFA4ABAB": "Cloud Mountain", "#FFF9CEC6": "Cloud Number Nine", "#FFF1E2C4": "Cloud of Cream", "#FFC2BCB1": "Cloud Over London", "#FFFAF9F8": "Cloud Petal", "#FFFFA168": "Cloudberry", "#FF7B7777": "Cloudburst", "#FF628468": "Clouded Pine", "#FF7D93A2": "Clouded Sky", "#FFD1D0D1": "Clouded Vision", "#FFD6EAFC": "Cloudless", "#FF9AB1BF": "Cloudless Day", "#FFD8D7D3": "Cloudy Variant", "#FFACC2D9": "Cloudy Blue", "#FF87715F": "Cloudy Cinnamon", "#FFDFE6DA": "Cloudy Day", "#FFB0A99F": "Cloudy Desert", "#FFECE3E1": "Cloudy Grey", "#FFE3BC6F": "Cloudy Honey", "#FF9D7AAC": "Cloudy Plum", "#FF6699AA": "Cloudy Sea", "#FFC2D5DA": "Cloudy Sky", "#FFE4BD76": "Cloudy Sunset", "#FFA6A096": "Cloudy Today", "#FFB1C6D6": "Cloudy Valley", "#FF4B5F56": "Cloudy Viridian", "#FF876155": "Clove", "#FF766051": "Clove Brown", "#FFC8805F": "Clove Bud", "#FFA96232": "Clove Dye", "#FF523F21": "Clove Yellow Brown", "#FFB0705D": "Clovedust", "#FF008F00": "Clover Variant", "#FF1C6A53": "Clover Brook", "#FF006C44": "Clover Green", "#FFF0E2BC": "Clover Honey", "#FF6FC288": "Clover Mist", "#FF4A9D5B": "Clover Patch", "#FFCD9BC4": "Clover Pink", "#FFC4D056": "Clown Green", "#FFE94257": "Clown Nose", "#FF8BC3E1": "Club Cruise", "#FF464159": "Club Grey", "#FF834370": "Club Mauve", "#FF6B977A": "Club Moss", "#FF3E4A54": "Club Navy", "#FFE2EDEB": "Club Soda", "#FF2B245A": "Cluedo Night", "#FFD3B683": "Clumsy Caramel", "#FFE8E2E0": "Clytemnestra", "#FF4978A9": "Co Pilot", "#FFCADFEC": "CO\u2082", "#FF003527": "Coach Green", "#FF3B3B3D": "Coal Hard Truth", "#FF220033": "Coal Mine", "#FF777872": "Coal Miner", "#FF53555E": "Coal Tipple", "#FF181B26": "Coarse Wool", "#FFF6E6DB": "Coast Cream", "#FFBDD4D1": "Coast", "#FFF0EBD9": "Coastal Beige", "#FFE0F6FB": "Coastal Breeze", "#FF538F94": "Coastal Calm", "#FFB4C0AF": "Coastal Crush", "#FF727B76": "Coastal Dusk", "#FF505D7E": "Coastal Fjord", "#FFB0E5C9": "Coastal Foam", "#FFE5E8E4": "Coastal Fog", "#FF80B9C0": "Coastal Fringe", "#FF006E7F": "Coastal Jetty", "#FFD2E8EC": "Coastal Mist", "#FF9FA694": "Coastal Plain", "#FFC9A985": "Coastal Sand", "#FF9EA4A6": "Coastal Sea Fog", "#FF7D807B": "Coastal Storm", "#FF2D4982": "Coastal Surf", "#FFBDFFCA": "Coastal Trim", "#FF9E9486": "Coastal Villa", "#FF8293A0": "Coastal Vista", "#FF7DB7DB": "Coastal Waters", "#FF4398BC": "Coastline Blue", "#FF6E6C5B": "Coastline Trail", "#FF2E2F30": "Coated", "#FF030AA7": "Cobalt Variant", "#FF02367D": "Cobalt Blue Tarantula", "#FF4D585B": "Cobalt Cannon", "#FF4E719D": "Cobalt Flame", "#FF0072B5": "Cobalt Glaze", "#FF648FC4": "Cobalt Glow", "#FF94FF94": "Cobalt Green", "#FF353739": "Cobalt Night", "#FF0264AE": "Cobalt Stone", "#FF7A6455": "Cobble Brown", "#FFC4AB7D": "Cobbler", "#FFB2967E": "Cobbler Shop", "#FFA89A8E": "Cobblestone", "#FF9E8779": "Cobblestone Path", "#FFCFC7B9": "Cobblestone Street", "#FFB08E08": "Cobra Leather", "#FFB56D5D": "Cobrizo", "#FFBD9D95": "Coca Mocha", "#FFF8B862": "Cochin Chicken", "#FFDDCDB3": "Cochise", "#FFFF88BB": "Cochonnet", "#FF58C8B6": "Cockatoo", "#FFA46422": "Cockatrice Brown", "#FFE3C6AF": "Cockleshell", "#FFBC5378": "Cockscomb Red", "#FF5A7AA2": "Cocktail Blue", "#FF8EB826": "Cocktail Green", "#FFFD9A52": "Cocktail Hour", "#FF9FA36C": "Cocktail Olive", "#FFDCE2AD": "Cocktail Onion", "#FFD1BBA1": "Coco", "#FFE4DCC9": "Coco Malt", "#FF994A25": "Coco Muck", "#FF9B7757": "Coco Rum", "#FFEEDD88": "Coco-Lemon Tart", "#FF1C1C1A": "Coco\u2019s Black", "#FF875F42": "Cocoa", "#FF4F3835": "Cocoa Bean Variant", "#FFA08882": "Cocoa Berry", "#FF35281E": "Cocoa Brown Variant", "#FFF5F4C1": "Cocoa Butter", "#FFB9A39A": "Cocoa Craving", "#FFDBC8B6": "Cocoa Cream", "#FF967859": "Cocoa Cupcake", "#FF8D725A": "Cocoa Delight", "#FFD0B7A4": "Cocoa Dust", "#FFC4AD96": "Cocoa Froth", "#FF7D675D": "Cocoa Milk", "#FFBC9F7E": "Cocoa Nib", "#FFA8816F": "Cocoa Nutmeg", "#FFDFCEC2": "Cocoa Parfait", "#FF967B5D": "Cocoa Pecan", "#FF766A5F": "Cocoa Powder", "#FF7E6657": "Cocoa Shell", "#FFA08E7E": "Cocoa Whip", "#FF784848": "Cocobolo", "#FFAA8F7A": "Cocoloco", "#FF965A3E": "Coconut", "#FFEBE8E7": "Coconut Agony", "#FFEEEEDD": "Coconut Aroma", "#FFF2EFE1": "Coconut Butter", "#FFE1DABB": "Coconut Cream Variant", "#FFF0E2CF": "Coconut Cream Pie", "#FFE2CEA6": "Coconut Crumble", "#FF676D43": "Coconut Grove", "#FF7D6044": "Coconut Husk", "#FFDDD4C7": "Coconut Ice", "#FFDACAC0": "Coconut Macaroon", "#FFEEEBE2": "Coconut Milk", "#FFFBF9E1": "Coconut Pulp", "#FFFEE4B6": "Coconut Scent", "#FF917A56": "Coconut Shell", "#FFF7F1E1": "Coconut Twist", "#FFE9EDF6": "Coconut White", "#FFDEDBCC": "Cocoon", "#FF2D3032": "Cod Grey", "#FF9C9C9C": "Codex Grey", "#FF524B2A": "Codium Fragile", "#FF8C4040": "Codman Claret", "#FF0E7F78": "Coelia Greenshade", "#FF497D93": "Coelin Blue", "#FF883300": "Coffee Addiction", "#FF775511": "Coffee Adept", "#FFDBD6D3": "Coffee Bag", "#FF825C43": "Coffee Bar", "#FF362D26": "Coffee Bean Variant", "#FF6F0C0D": "Coffee Brick", "#FFB5987F": "Coffee Cake", "#FFB7997C": "Coffee Clay", "#FFFFF2D7": "Coffee Cream", "#FFAB9B9C": "Coffee Custard", "#FFBEA88D": "Coffee Diva", "#FFBF9779": "Coffee Gelato", "#FF302010": "Coffee Grounds", "#FF6C5B4D": "Coffee House", "#FFB19576": "Coffee Kiss", "#FF6A513B": "Coffee Liqueur", "#FFA9898D": "Coffee Rose", "#FF725042": "Coffee Shop", "#FFDF9D5B": "Coffee Whip", "#FFA68966": "Coffee With Cream", "#FFD48C46": "Cognac Variant", "#FFB98563": "Cognac Brown", "#FFA17B49": "Cognac Tint", "#FF90534A": "Cogswell Cedar", "#FFE0D5E3": "Coin Purse", "#FFFF4411": "Coin Slot", "#FFC7DE88": "Coincidence", "#FF3C2F23": "Cola Variant", "#FF3C3024": "Cola Bubble", "#FFC1DCDB": "Cold Air Turquoise", "#FF154250": "Cold and Dark", "#FFBBEEEE": "Cold Blooded", "#FF88DDDD": "Cold Blue", "#FF785736": "Cold Brew Coffee", "#FFC75D42": "Cold Brew Tonic", "#FFDBFFFE": "Cold Canada", "#FF234272": "Cold Current", "#FFEFECE3": "Cold Foam", "#FF85B3B2": "Cold Front Green", "#FF008B3C": "Cold Green", "#FF9F9F9F": "Cold Grey", "#FF22DDEE": "Cold Heights", "#FFDDE3E6": "Cold Light", "#FF00EEEE": "Cold Light of Day", "#FF9BA0EF": "Cold Lips", "#FFE6E5E4": "Cold Morning", "#FF559C9B": "Cold North", "#FF0033DD": "Cold Pack", "#FFD09351": "Cold Pilsner", "#FFBCA5AD": "Cold Pink", "#FF6C2E09": "Cold Press Coffee", "#FF665B42": "Cold Pressed", "#FF9D8ABF": "Cold Purple Variant", "#FF32545E": "Cold Sea Currents", "#FFD4E0EF": "Cold Shoulder", "#FFACBAC5": "Cold Snap", "#FFFFF7FD": "Cold Snow", "#FFD9E7E6": "Cold Soft Blue", "#FF88BB66": "Cold Spring", "#FF7E8692": "Cold Trade Winds", "#FFCFE1EF": "Cold Turbulence", "#FFCAB5B2": "Cold Turkey Variant", "#FFA5D0CB": "Cold Turquoise", "#FFD9DFE0": "Cold Water", "#FF839FA3": "Cold Waterlogged Lab Coat", "#FFC2E2E3": "Cold Wave", "#FFC1E2E3": "Cold Well Water", "#FFEDFCFB": "Cold White", "#FFE1E3E4": "Cold Wind", "#FFB4BCD1": "Cold Winter\u2019s Morn", "#FFCEC8B6": "Coliseum Marble", "#FF536861": "Collard Green", "#FF9B8467": "Collectible", "#FFEBECDA": "Colleen Green", "#FFBDB7CD": "Collensia", "#FF75BFD2": "Cologne", "#FFEFC944": "Colombian Yellow", "#FFBA7AB3": "Colombo Red Mauve", "#FFB68238": "Colonel Mustard", "#FFB2B1AD": "Colonnade Grey", "#FFC6C0B6": "Colonnade Stone", "#FFEE7766": "Colorado Bronze", "#FFE09CAB": "Colorado Dawn", "#FFE6994C": "Colorado Peach", "#FF9C9BA7": "Colorado Peak", "#FF88AAC4": "Colorado Springs", "#FFB49375": "Colorado Trail", "#FF625C91": "Colossus", "#FFC6D2DE": "Colour Blind", "#FF7CB77B": "Colour Me Green", "#FFAA5C43": "Colourful Leaves", "#FF009792": "Columbia", "#FFBEB861": "Columbian Gold", "#FFF5DAE3": "Columbine", "#FFD0CBCE": "Columbo\u2019s Coat", "#FF5F758F": "Columbus", "#FF006F37": "Column of Oak Green", "#FF7F725C": "Colusa Wetlands", "#FFF4F0DE": "Combed Cotton", "#FF5C92C5": "Come Sail Away", "#FF636373": "Comet Variant", "#FFC1DAED": "Comet Tail", "#FFE3CEB8": "Comfort", "#FFBEC3BB": "Comfort Grey", "#FFD7C0AB": "Comforting", "#FFCC1144": "Comforting Cherry", "#FFD5E0CF": "Comforting Green", "#FFC5C3B4": "Comforting Grey", "#FF64856B": "Comfrey", "#FFE3D2B6": "Comfy Beige", "#FFF3D1C8": "Comical Coral", "#FFDE7485": "Coming up Roses", "#FF0B597C": "Commandes", "#FFEDECE6": "Commercial White", "#FF25476A": "Commodore", "#FF858F94": "Common Feldspar", "#FF946943": "Common Jasper", "#FF009193": "Common Teal", "#FFD0B997": "Community", "#FF4C785C": "Como Variant", "#FFCDCDCD": "Compact Disc Grey", "#FF8A877B": "Compass", "#FF35475B": "Compass Blue", "#FFE8C89E": "Compatible Cream", "#FF847975": "Complex Grey", "#FF9E91AE": "Compliment", "#FFBBC8B2": "Composed", "#FF7A6E7E": "Composer\u2019s Magic", "#FF55CC00": "Composite Artefact Green", "#FF6C3877": "Comtesse", "#FFB12845": "Con Brio", "#FF263130": "Concealed Green", "#FF405852": "Concealment", "#FFD5BDA3": "Concept Beige", "#FF7AC34F": "Conceptual", "#FF9E6B75": "Concerto", "#FFA0B1AE": "Conch Variant", "#FFDBA496": "Conch Pink", "#FFFC8F9B": "Conch Shell", "#FFABB9D7": "Conclave", "#FF827F79": "Concord Variant", "#FFE2CEB0": "Concord Buff", "#FF855983": "Concord Grape", "#FF695A82": "Concord Jam", "#FFD2D1CD": "Concrete Variant", "#FF999988": "Concrete Jungle", "#FF5C606E": "Concrete Landscape", "#FF8D8A81": "Concrete Sidewalk", "#FFB98142": "Condiment", "#FFFFFFCC": "Conditioner", "#FF4A6169": "Cone Green Blue", "#FF6D7E7D": "Coney Island", "#FFF4ECDA": "Confection", "#FFD4B4D5": "Confectionary", "#FFDDCB46": "Confetti Variant", "#FFA98A6B": "Confidence", "#FFE4DFCE": "Confident White", "#FFFFCC13": "Confident Yellow", "#FF01C08D": "C\u014dng L\u01dc Green", "#FFE8C3BE": "Congo", "#FF654D49": "Congo Brown Variant", "#FF776959": "Congo Capture", "#FF00A483": "Congo Green", "#FFF98379": "Congo Pink", "#FF100438": "Congressional Navy", "#FFB1DD52": "Conifer Variant", "#FFFFDD49": "Conifer Blossom", "#FF885555": "Conifer Cone", "#FF747767": "Conifer Green", "#FFB94E41": "Conker", "#FF552200": "Conker Brown", "#FF654E44": "Connaisseur", "#FFEB6651": "Connect Red", "#FF898473": "Connected Grey", "#FFCCBBEE": "Connecticut Lilac", "#FF175A6C": "Connor\u2019s Lakefront", "#FFF2D9B8": "Cono de Vainilla", "#FF796E54": "Conservation", "#FFD1D0C6": "Conservative Grey", "#FFE4CDAC": "Consomm\u00e9", "#FF57465D": "Conspiracy Velvet", "#FFCD8E7F": "Constant Coral", "#FFBCCEDB": "Constellation", "#FF3C4670": "Constellation Blue", "#FFEE8442": "Construction Zone", "#FFF5811A": "Consumed by Fire", "#FFBB4745": "Conte Crayon", "#FFBEC6BB": "Contemplation", "#FF70766A": "Contemplative", "#FFBDC0B3": "Contented", "#FFC16F68": "Contessa Variant", "#FF98C6CB": "Continental Waters", "#FFDEDEFF": "Contrail", "#FFF2C200": "Contrasting Yellow", "#FFE9D6B0": "Convivial Yellow", "#FFCD98A3": "Cooing Doves", "#FF014E83": "Cook\u2019s Bay", "#FFFFE2B7": "Cookie", "#FFB19778": "Cookie Crumb", "#FFE3B258": "Cookie Crust", "#FFAB7100": "Cookie Dough", "#FFEEE0B1": "Cookies and Cream", "#FF96B3B3": "Cool", "#FF28394D": "Cool Air of Debonair", "#FFA9D99C": "Cool Aloe", "#FFC6D86B": "Cool as a Cucumber", "#FF929291": "Cool Ashes", "#FFC4B47D": "Cool Avocado", "#FF18233D": "Cool Balaclavas Are Forever", "#FFC6B5A7": "Cool Beige", "#FF4984B8": "Cool Blue", "#FF6B927C": "Cool Bluegrass", "#FFAE996B": "Cool Camel", "#FF827566": "Cool Camo", "#FFF1D3CA": "Cool Cantaloupe", "#FF807B76": "Cool Charcoal", "#FFBA947B": "Cool Clay", "#FFD9D0C1": "Cool Concrete", "#FFAD8458": "Cool Copper", "#FFB0E6E3": "Cool Crayon", "#FFFBE5D9": "Cool Cream", "#FFB88035": "Cool Cream Spirit", "#FF283C44": "Cool Current", "#FF96AABA": "Cool Customer", "#FFFDFBF8": "Cool December", "#FF00606F": "Cool Dive", "#FF7B9DAD": "Cool Dusk", "#FFCFCFD0": "Cool Elegance", "#FFE7E6ED": "Cool Frost", "#FFABACA8": "Cool Granite", "#FF33B864": "Cool Green", "#FF8C93AD": "Cool Grey Variant", "#FFE1EEE6": "Cool Icicle", "#FFBEE7E0": "Cool Jazz", "#FF9ACABA": "Cool Juniper", "#FFE97C6B": "Cool Lava", "#FFB3A6A5": "Cool Lavender", "#FFEBD1CD": "Cool Melon", "#FFC6DAD5": "Cool Mint", "#FF384248": "Cool Operator\u2019s Overalls", "#FFCAE4B2": "Cool Peridot", "#FFACAD97": "Cool Pine", "#FFE5CCD1": "Cool Pink", "#FFAA23FF": "Cool Purple", "#FFCBB5C6": "Cool Quiet", "#FFEAF0EB": "Cool Reflection", "#FFD9DBCA": "Cool Runnings", "#FFB4B9AB": "Cool Sea Air", "#FFCFE0E4": "Cool Sky", "#FFD0CCC5": "Cool Slate", "#FFBBD9C3": "Cool Spring", "#FF0091B3": "Cool Tide", "#FF7295C9": "Cool Touch", "#FFD5D7D3": "Cool Vapour", "#FF9BD9E5": "Cool Water Lake", "#FF487678": "Cool Waters", "#FFDAE6E2": "Cool White", "#FFEAEFCE": "Cool Yellow", "#FF499C9D": "Coolbox Ice Turquoise", "#FF75B9AE": "Cooled Blue", "#FFFADC97": "Cooled Cream", "#FF77BBFF": "Cooler Than Ever", "#FFE6E2E4": "Cooling Trend", "#FF006C8D": "Copacabana", "#FFE5D68E": "Copacabana Sand", "#FF57748D": "Copen Blue", "#FFADC8C0": "Copenhagen", "#FF21638B": "Copenhagen Blue", "#FFD0851D": "Copious Caramel", "#FFB1715A": "Copper Beech", "#FFE8C1AB": "Copper Blush", "#FF9A6051": "Copper Brown", "#FF77422C": "Copper Canyon Variant", "#FFF4BB92": "Copper Cloud", "#FFD89166": "Copper Cove", "#FFA35D31": "Copper Creek", "#FFD57E52": "Copper Harbor", "#FFC48C67": "Copper Hide", "#FFBF4000": "Copper Hopper", "#FFC09078": "Copper Lake", "#FFB2764C": "Copper Mine", "#FF398174": "Copper Mineral Green", "#FF945C54": "Copper Mining", "#FFC29978": "Copper Moon", "#FFAB714A": "Copper Mountain", "#FF9DB4A0": "Copper Patina", "#FF946877": "Copper Pink", "#FFDA8F67": "Copper Pipe", "#FF936647": "Copper Pot", "#FF3E4939": "Copper Pyrite Green", "#FFF7A270": "Copper River", "#FF6F978E": "Copper Roof Green", "#FF95524C": "Copper Rust Variant", "#FFDC8C5D": "Copper Tan", "#FFC18978": "Copper Trail", "#FF38887F": "Copper Turquoise", "#FFDB8B67": "Copper Wire", "#FFAD6342": "Copper-Metal Red", "#FFDA8A88": "Copperfield Variant", "#FFD68755": "Copperhead", "#FFCF8874": "Copperleaf", "#FFD98A3F": "Coppersmith", "#FF7F4330": "Coppery Orange", "#FF654636": "Copra", "#FFE5DCDC": "Coquette", "#FF9D8D8E": "Coquina", "#FFBB9A88": "Coquina Shell", "#FFE29D94": "Coral Almond", "#FFDC938D": "Coral Atoll", "#FFDA8C87": "Coral Banks", "#FFDDB8A3": "Coral Bay", "#FFFFBBAA": "Coral Beach", "#FFEF9A93": "Coral Bead", "#FFFBC5BB": "Coral Bells", "#FFF7C6B1": "Coral Bisque", "#FFF7BEA2": "Coral Blossom", "#FFE5A090": "Coral Blush", "#FFDD5544": "Coral Burst", "#FFF5D0C9": "Coral Candy", "#FFC2B1A1": "Coral Clay", "#FFDC958D": "Coral Cloud", "#FF068E9E": "Coral Coast", "#FFEE6666": "Coral Commander", "#FFFCCCA7": "Coral Confection", "#FFE9ADCA": "Coral Corn Snake", "#FFFEAD8C": "Coral Correlation", "#FFDDA69F": "Coral Cove", "#FFEAD6CE": "Coral Cream", "#FFFCD5C5": "Coral Dune", "#FFFFB48A": "Coral Dusk", "#FFEDAA86": "Coral Dust", "#FFD76A69": "Coral Expression", "#FFE3A9A2": "Coral Fountain", "#FFFFC39E": "Coral Gable", "#FFCF8179": "Coral Garden", "#FFCF714A": "Coral Gold", "#FFABE2CF": "Coral Green", "#FFE28980": "Coral Haze", "#FFFFDDC7": "Coral Kiss", "#FFFCD6CB": "Coral Mantle", "#FFE4694C": "Coral Orange", "#FFE76682": "Coral Paradise", "#FFF39081": "Coral Passion", "#FFF7685F": "Coral Quartz", "#FFF3774D": "Coral Rose", "#FFCA884E": "Coral Sand", "#FFF9A48E": "Coral Serenade", "#FFF2A37D": "Coral Silk", "#FFABD1AF": "Coral Springs", "#FFDDC3B6": "Coral Stone", "#FFFF8B87": "Coral Trails", "#FFAB6E67": "Coral Tree Variant", "#FFFEB890": "Coral Wisp", "#FFF08674": "Coralette", "#FFFF917A": "Coralistic", "#FFDD675A": "Coralite", "#FFF0DFCD": "Corallite", "#FFFEA89F": "Corally", "#FF9D6663": "Corazon", "#FF111122": "Corbeau", "#FF864C52": "Cordial", "#FF616665": "Cordite", "#FFEBE0C8": "Cordon Bleu Crust", "#FF7C3744": "Cordova Burgundy", "#FF57443D": "Cordovan Leather", "#FF404D49": "Corduroy Variant", "#FF594035": "Cordwood", "#FF008E8D": "Corfu Shallows", "#FF8993C3": "Corfu Sky", "#FF008AAD": "Corfu Waters", "#FFBBB58D": "Coriander Variant", "#FF7E7463": "Coriander Ochre", "#FFBA9C75": "Coriander Powder", "#FFBDAA6F": "Coriander Seed", "#FFDEDECF": "Corinthian Column", "#FFE1D1B1": "Corinthian Pillar", "#FFFFA6D9": "Corinthian Pink", "#FF5A4C42": "Cork Variant", "#FF7E6B43": "Cork Bark", "#FFCC8855": "Cork Brown", "#FFC1A98A": "Cork Wedge", "#FFCC7744": "Cork Wood", "#FF9D805D": "Corkboard", "#FFD1B9AB": "Corkscrew Willow", "#FFBD9B78": "Corky", "#FF437064": "Cormorant", "#FFEEC657": "Corn Bread", "#FFE1C595": "Corn Chowder", "#FFF8F3C4": "Corn Field Variant", "#FF8D702A": "Corn Harvest Variant", "#FFF2D6AE": "Corn Husk", "#FFCECD95": "Corn Husk Green", "#FFDEAA6E": "Corn Maze", "#FFEE4411": "Corn Poppy Cherry", "#FFAB6134": "Corn Snake", "#FFB31B11": "Cornell Red", "#FFBC8F5F": "Corner Caf\u00e9", "#FFE3D7BB": "Cornerstone", "#FF5170D7": "Cornflower Variant", "#FF6C91C5": "Cornflower Blue Variant", "#FFFFD691": "Cornmeal", "#FFEBD5C5": "Cornmeal Beige", "#FFF4C96C": "Cornsilk Yellow", "#FFA7B25F": "Cornstalk", "#FFED9B44": "Cornucopia", "#FF908C53": "Cornus Green", "#FF4885AA": "Cornwall", "#FF949488": "Cornwall Slate", "#FFFFB437": "Corona", "#FFD5A68D": "Coronado Dunes", "#FF9BA591": "Coronado Moss", "#FFEDECEC": "Coronation", "#FF59529C": "Coronation Blue", "#FF59728E": "Coronet Blue", "#FF78A486": "Corporate Green", "#FF61513D": "Corral", "#FF937360": "Corral Brown", "#FFF18A76": "Corralize", "#FF4DA48B": "Corrosion Green", "#FF772F21": "Corrosion Red", "#FF54D905": "Corrosive Green", "#FF18576C": "Corsair", "#FF85AC9D": "Corsican", "#FF646093": "Corsican Blue", "#FF7A85AF": "Corsican Purple", "#FFA99592": "Cortex", "#FFA4896A": "Cortez Chocolate", "#FF4A6267": "Corundum Blue", "#FF95687D": "Corundum Red", "#FFE9BA81": "Corvette Variant", "#FFA2C7D7": "Corydalis Blue", "#FFA4C48E": "Cos", "#FFD3BED5": "Cosmetic Mauve", "#FFF3C1AB": "Cosmetic Peach", "#FFA56078": "Cosmetic Red", "#FFDD669D": "Cosmetologist\u2019s Pink", "#FFB8B9CB": "Cosmic Variant", "#FFCFB3A6": "Cosmic Aura", "#FF8B4F90": "Cosmic Berry", "#FF001000": "Cosmic Bit Flip", "#FF93C3CB": "Cosmic Blue", "#FFE77E6C": "Cosmic Coral", "#FFDCE2E5": "Cosmic Dust", "#FF9392AB": "Cosmic Energy", "#FF551155": "Cosmic Explorer", "#FF30A877": "Cosmic Green", "#FF9601F4": "Cosmic Heart", "#FFE6A8D7": "Cosmic Pink", "#FF9EA19F": "Cosmic Quest", "#FFCADADA": "Cosmic Ray", "#FFDA244B": "Cosmic Red", "#FFAAAAC4": "Cosmic Sky", "#FF090533": "Cosmic Void", "#FFA09BC6": "Cosmo Purple", "#FF528BAB": "Cosmopolitan", "#FFFCD5CF": "Cosmos Variant", "#FF003249": "Cosmos Blue", "#FF4D8AA1": "Cossack Dancer", "#FF328E13": "Cossack Green", "#FF625D2A": "Costa Del Sol Variant", "#FF77BCE2": "Costa Rica Blue", "#FFC44041": "Costa Rican Palm", "#FF6477A0": "Costume Blue", "#FFC3A598": "Cosy Blanket", "#FFAA8F7D": "Cosy Cocoa", "#FFF2DDD8": "Cosy Cottage", "#FFE4C38F": "Cosy Cover", "#FFF0CCAD": "Cosy Coverlet", "#FFE0DBC7": "Cosy Cream", "#FFFBA765": "Cosy Nook", "#FFEB9F9F": "Cosy Summer Sunset", "#FFE8E1D0": "Cosy White", "#FFD1B99B": "Cosy Wool", "#FF017C85": "Cote D\u2019Azur", "#FF340059": "Cotinga Purple", "#FFDBCDAD": "Cotswold Dill", "#FF789EC5": "Cottage Blue", "#FF357697": "Cottage by the Sea", "#FFAFBEB0": "Cottage Charm", "#FFEDDBBD": "Cottage Cream", "#FFDCECDC": "Cottage Green", "#FFACB397": "Cottage Hill", "#FF828676": "Cottage Lattice Green", "#FFD9A89A": "Cottage Rose", "#FFA85846": "Cottage Spice", "#FFA08E77": "Cottage Walk", "#FFF7EFDD": "Cottage White", "#FFFFDAD9": "Cottagecore Sunset", "#FFEDDBD7": "Cottingley Fairies", "#FFEEEBE1": "Cotton", "#FFDCC6BA": "Cotton & Flax", "#FFF2F7FD": "Cotton Ball", "#FFF5F1E4": "Cotton Blossom", "#FFE7EFFB": "Cotton Boll", "#FFF5BCDE": "Cotton Candy Aesthetic", "#FFFFC3CB": "Cotton Candy Comet", "#FFDD22FF": "Cotton Candy Explosions", "#FFDEC74B": "Cotton Candy Grape", "#FF7596B8": "Cotton Cardigan", "#FFFAF4D4": "Cotton Cloth", "#FFC2E1EC": "Cotton Clouds", "#FFF3E4D3": "Cotton Club", "#FF91ABBE": "Cotton Denim", "#FFF0EAD8": "Cotton Down", "#FFDAD0BD": "Cotton Fibre", "#FFF2F0E8": "Cotton Field", "#FF9090A2": "Cotton Flannel", "#FFF8F0C7": "Cotton Floss", "#FFF9F4E5": "Cotton Fluff", "#FFD1CCC3": "Cotton Grey", "#FF066976": "Cotton Indigo", "#FFE5DFD2": "Cotton Knit", "#FFEED09C": "Cotton Muslin", "#FFFFFFE7": "Cotton Puff", "#FFF1EBDB": "Cotton Ridge", "#FFF7EBDD": "Cotton Sheets", "#FFFFF8D7": "Cotton Tail", "#FFE5E1D5": "Cotton Tuft", "#FFFAF1DF": "Cotton Whisper", "#FFE4E3D8": "Cotton White", "#FF83ABD2": "Cotton Wool Blue", "#FFF5E6C7": "Cottonseed", "#FF4E2A20": "Couch", "#FFC2B4A7": "Couch Potato", "#FF9A7F78": "Cougar", "#FF5E2D10": "Count Chocula", "#FF772277": "Count\u2019s Wardrobe", "#FF9FB6C6": "Country Air", "#FFEAE1CB": "Country Beige", "#FF717F9B": "Country Blue", "#FFE0D9D5": "Country Breeze", "#FFC7C0A7": "Country Charm", "#FF948675": "Country Club", "#FFB8A584": "Country Cork", "#FFD9C1B7": "Country Cottage", "#FFB0967C": "Country Dweller", "#FF414634": "Country House Green", "#FF5D7A85": "Country Lake", "#FFFCEAD1": "Country Lane", "#FF894340": "Country Lane Red", "#FFD7C2A6": "Country Linen", "#FF1A5A4E": "Country Meadow", "#FFDFEBE2": "Country Mist", "#FFD0BCA2": "Country Rubble", "#FF49545A": "Country Sky", "#FF7E4337": "Country Sleigh", "#FF124A42": "Country Squire", "#FFFFFBD7": "Country Summer", "#FF837B68": "Country Tweed", "#FF88C096": "Country Weekend", "#FFA4A404": "Countryside", "#FF1B4B35": "County Green Variant", "#FFDAA135": "Courgette Yellow", "#FFB9B7A0": "Court Green", "#FF926D9D": "Court Jester", "#FFCECB97": "Court-Bouillon", "#FFD2D3DE": "Courteous", "#FFBBAFC1": "Courtly Purple", "#FFC8BDA4": "Courtyard", "#FF718084": "Courtyard Blue", "#FF978D71": "Courtyard Green", "#FFE3D3BA": "Courtyard Tan", "#FFFFE29B": "Couscous", "#FF55A9D6": "Cousteau", "#FF86B097": "Covent Garden", "#FF8BA9BC": "Coventry Blue", "#FF494E4F": "Cover of Night", "#FF6A3C3B": "Covered Bridge", "#FFB9BABA": "Covered in Platinum", "#FF726449": "Covered Wagon", "#FF80765F": "Covert Green", "#FFD4CDD2": "Coverts Wood Pigeon", "#FF9BBDB2": "Coveted Blue", "#FFB6B3BF": "Coveted Gem", "#FFF1EDE5": "Cow\u2019s Milk", "#FFFBF1C0": "Cowardly Custard", "#FFFFE481": "Cowbell", "#FF443736": "Cowboy Variant", "#FF695239": "Cowboy Boots", "#FFB27D50": "Cowboy Hat", "#FF7A7F82": "Cowboy Spur", "#FF763E2D": "Cowboy Suede", "#FF8D6B4B": "Cowboy Trails", "#FF6A87AB": "Cowgirl Blue", "#FF9E7C60": "Cowgirl Boots", "#FF92484A": "Cowhide", "#FF661100": "Cowpeas", "#FFF9DAD8": "Coy Pink", "#FFDC9B68": "Coyote", "#FFB08F7F": "Coyote Tracks", "#FFAE9671": "Coyote Wild", "#FF0AAFA4": "Cozumel", "#FFF0B599": "Crab Bisque", "#FFD94B28": "Crab Curry", "#FF004455": "Crab Nebula", "#FF87382F": "Crabapple", "#FF753531": "Crabby Apple", "#FFB0A470": "Crack Willow", "#FFC5B1A0": "Cracked Earth", "#FF4F5152": "Cracked Pepper", "#FF69656A": "Cracked Slate", "#FFF4DFBD": "Cracked Wheat", "#FFD1B088": "Cracker Bitz", "#FFD3B9B0": "Cracker Crumbs", "#FFF2E7D1": "Crackled", "#FFA27C4F": "Crackled Leather", "#FFB3C5CC": "Crackling Lake", "#FFF1D3D9": "Cradle Pillow", "#FFEAC9D5": "Cradle Pink", "#FF293B4A": "Craft", "#FFB7A083": "Craft Brown", "#FFE3C8AA": "Craft Juggler", "#FF8A6645": "Craft Paper", "#FF008193": "Craftsman Blue", "#FFAE9278": "Craftsman Brown", "#FFD3B78B": "Craftsman Gold", "#FFA65648": "Crail Variant", "#FF2B8288": "Cranach Blue", "#FFDB8079": "Cranapple", "#FFE6C4C5": "Cranapple Cream", "#FF9E003A": "Cranberry Variant", "#FF7494B1": "Cranberry Blue", "#FFAB7A71": "Cranberry Foule", "#FFA34F55": "Cranberry Jam", "#FFC27277": "Cranberry Pie", "#FF7E5350": "Cranberry Red", "#FFA53756": "Cranberry Sauce", "#FFDA5265": "Cranberry Splash", "#FF893E40": "Cranberry Tart", "#FF8E4541": "Cranberry Whip", "#FFA65570": "Cranbrook", "#FF954C52": "Crantini", "#FFEEEE66": "Crash Dummy", "#FFCC88FF": "Crash Pink", "#FF3E6F87": "Crashing Waves", "#FF75674F": "Crater", "#FF4D3E3C": "Crater Brown Variant", "#FFC8CED6": "Crater Crawler", "#FF63797E": "Crater Lake", "#FFEEEDD9": "Cravat", "#FF016496": "Craving Cobalt", "#FFBC763C": "Cray", "#FF1DAC78": "Crayola Green", "#FFFE7438": "Crayola Orange", "#FFE5CB3F": "Crazy", "#FFCC1177": "Crazy Ex", "#FF5EB68D": "Crazy Eyes", "#FFA57648": "Crazy Horse", "#FFF970AC": "Crazy Pink", "#FFFFFFC2": "Cream Variant", "#FFFEEEA5": "Cream and Butter", "#FFDDCFB9": "Cream and Sugar", "#FFF8C49A": "Cream Blush", "#FFE3D0AD": "Cream Cake", "#FFEEC051": "Cream Can Variant", "#FFD7D3A6": "Cream Cheese Avocado", "#FFF4EFE2": "Cream Cheese Frosting", "#FFF1F3DA": "Cream Clear", "#FFF2D7B0": "Cream Custard", "#FFEAE3D0": "Cream Delight", "#FFF5F1DA": "Cream Filling", "#FFDCC356": "Cream Gold", "#FFDFD2BE": "Cream in My Coffee", "#FFEBD1BE": "Cream of Mushroom", "#FFF6E4D9": "Cream Pink", "#FFFFBB99": "Cream Puff", "#FFEEE3C6": "Cream Silk", "#FFECCBA0": "Cream Snap", "#FFE4C7B8": "Cream Tan", "#FFA9AABD": "Cream Violet", "#FFF2E0C5": "Cream Washed", "#FFE8DBC5": "Cream Wave", "#FFF2EEE2": "Cream White", "#FF70804D": "Creamed Avocado", "#FFFFFCD3": "Creamed Butter", "#FFB79C94": "Creamed Caramel", "#FF8B6962": "Creamed Muscat", "#FFBD6883": "Creamed Raspberry", "#FFEDD2B7": "Creamery", "#FFEFE8DB": "Creamy", "#FFFFE8BD": "Creamy Apricot", "#FFD8F19C": "Creamy Avocado", "#FFFFDAE8": "Creamy Axolotl", "#FFFDE1C5": "Creamy Beige", "#FFDEBCCD": "Creamy Berry", "#FFF9EEDC": "Creamy Cameo", "#FFDBCCB5": "Creamy Cappuccino", "#FFB3956C": "Creamy Caramel", "#FFE1CFAF": "Creamy Chenille", "#FFFFF5E0": "Creamy Cloud Dreams", "#FFDD7788": "Creamy Coral", "#FFFFF2C2": "Creamy Corn", "#FFF9E7BF": "Creamy Custard", "#FFEBD0DB": "Creamy Freesia", "#FFECEFE3": "Creamy Garlic", "#FFF0E2C5": "Creamy Gelato", "#FFEEDDAA": "Creamy Ivory", "#FFFFF0B2": "Creamy Lemon", "#FFDCCAD8": "Creamy Mauve", "#FFAAFFAA": "Creamy Mint", "#FF988374": "Creamy Mocha", "#FFCABDAE": "Creamy Mushroom", "#FFD4B88F": "Creamy Nougat", "#FFFFE0AF": "Creamy Oat", "#FFFCE9D1": "Creamy Orange", "#FFFE9C7B": "Creamy Orange Blush", "#FFF4A384": "Creamy Peach", "#FFB2BFA6": "Creamy Spinach", "#FFCAC990": "Creamy Spinaci", "#FFFCD2DF": "Creamy Strawberry", "#FFFFFBB0": "Creamy Sunshine Pastel", "#FFF7C34C": "Creamy Sweet Corn", "#FFF2E5BF": "Creamy Vanilla", "#FFF0E9D6": "Creamy White", "#FF7A6D44": "Crease", "#FFC9CABF": "Create", "#FFDCBA42": "Credo", "#FFC1A44A": "Creed", "#FFAB9D89": "Creek Bay", "#FF928C87": "Creek Bend", "#FFDBD7D9": "Creek Pebble", "#FFB48AC2": "Creeping Bellflower", "#FFC16104": "Crema", "#FFFFFFB6": "Cr\u00e8me", "#FFF8EDE2": "Creme Angels", "#FFFFE39B": "Cr\u00e8me Br\u00fbl\u00e9e", "#FFD4B38F": "Cr\u00e8me de Caramel", "#FFF3E7B4": "Cr\u00e8me de la Cr\u00e8me", "#FFF1FDE9": "Cr\u00e8me de Menthe", "#FFFDF5E0": "Cr\u00e8me de P\u00eache", "#FFECEEE6": "Cr\u00e8me Fra\u00eeche", "#FFFDD77A": "Cr\u00e8me P\u00e2tissi\u00e8re", "#FFF1F0E0": "Cr\u00e8me Vanille", "#FFCFA33B": "Cremini", "#FFF0E2C4": "Cremini Mushroom", "#FF393227": "Creole Variant", "#FFE7B89A": "Creole Cottage", "#FFC69B4E": "Creole Mustard", "#FFF5D6CC": "Creole Pink", "#FFEE8833": "Creole Sauce", "#FFD4BC94": "Crepe", "#FFE399CA": "Crepe Myrtle", "#FFDBD7C4": "Cr\u00eape Papier", "#FFF0EEE3": "Crepe Silk White", "#FFCABEAE": "Crepey Chest", "#FFE7DCCE": "Crepuscular", "#FFE3DF84": "Crescendo", "#FFEDD1B1": "Crescent Cream", "#FFF1E9CF": "Crescent Moon", "#FFBCA949": "Cress Green", "#FFBCB58A": "Cress Vinaigrette", "#FF8AAE7C": "Cressida", "#FF77A7A9": "Cresting Waves", "#FFB4BCBF": "Crestline", "#FF598784": "Cretan Green", "#FF96908B": "Crete Shore", "#FFCBB99B": "Crewel Tan", "#FFE4D5BC": "Cria Wool", "#FFA6A081": "Cricket", "#FFC7C10C": "Cricket Chirping", "#FFC3D29C": "Cricket Field", "#FF6A7B6B": "Cricket Green", "#FF908A78": "Cricket\u2019s Cross", "#FFE2CDB1": "Crimini Mushroom", "#FF8C000F": "Crimson Variant", "#FFAD3D1E": "Crimson Blaze", "#FFB44933": "Crimson Boy", "#FFC32F40": "Crimson Cloud", "#FF982531": "Crimson Garland", "#FFC13939": "Crimson Glow", "#FF980001": "Crimson Red", "#FF9A2B43": "Crimson Shadows", "#FFB5413B": "Crimson Silk", "#FF9F2D47": "Crimson Strawberry", "#FFC01B0C": "Crimson Sunset", "#FFB53111": "Crimson Sword", "#FFB52604": "Crimson Velvet Sunset", "#FFB35138": "Crimson Warrior", "#FFBB2222": "Crisis Red", "#FFF4EBD0": "Crisp Candlelight", "#FF5D6E3B": "Crisp Capsicum", "#FFCDCCA6": "Crisp Celery", "#FFA6080E": "Crisp Christmas Cranberries", "#FFB1C8D2": "Crisp Collar", "#FFF4F0E7": "Crisp Cotton", "#FF22FFFF": "Crisp Cyan", "#FF729EB9": "Crisp French Blue", "#FFABC43A": "Crisp Green", "#FF4F9785": "Crisp Lettuce", "#FFE7E1D3": "Crisp Linen", "#FFE9E2D7": "Crisp Muslin", "#FFF3DCC6": "Crisp Wonton", "#FFE7DFC1": "Crispa", "#FFE2BD67": "Crisps", "#FFDDAA44": "Crispy Chicken Skin", "#FF7A8F68": "Crispy Crunch", "#FFEBE0CF": "Crispy Crust", "#FFBB7838": "Crispy Gingersnap", "#FFC49832": "Crispy Gold", "#FFFFBB66": "Crispy Samosa", "#FFB1A685": "Crocker Grove", "#FFA49887": "Crockery", "#FF706950": "Crocodile Variant", "#FFCADABD": "Crocodile Dreams", "#FF777722": "Crocodile Eye", "#FFB7AC87": "Crocodile Green", "#FF898E58": "Crocodile Smile", "#FF767437": "Crocodile Style", "#FFD6D69B": "Crocodile Tears", "#FFD1CCC2": "Crocodile Tooth", "#FFC071A8": "Crocus", "#FFB99BC5": "Crocus Petal", "#FFFDF1C7": "Crocus Tint", "#FFC4AB86": "Croissant", "#FFF8EFD8": "Croissant Crumbs", "#FFD69F60": "Cronut", "#FF797869": "Crooked River", "#FFE9BF63": "Crop Circle", "#FF5C7B97": "Cropper Blue", "#FFAC9877": "Croque Monsieur", "#FF4971AD": "Croquet Blue", "#FFA5A080": "Cross Country", "#FFAD2A2D": "Cross My Heart", "#FF60543F": "Crossbow", "#FFDDB596": "Crossed Fingers", "#FFEDD2A3": "Crossroads", "#FF180614": "Crow", "#FF263145": "Crow Black", "#FF112F4B": "Crow Black Blue", "#FF102329": "Crow Feather", "#FF220055": "Crowberry", "#FF003447": "Crowberry Blue", "#FF5B4459": "Crowd Pleaser", "#FF484E68": "Crown Blue", "#FF701DCE": "Crown Chakra", "#FFB48C60": "Crown Gold", "#FF946DAD": "Crown Jewels", "#FFA1A9A9": "Crown of Ash", "#FFD8B411": "Crown of Liechtenstein", "#FF763C33": "Crown of Thorns Variant", "#FFFFF0C1": "Crown Point Cream", "#FFD4B597": "Crowned One", "#FF5A4F6C": "Crowning", "#FF555B59": "Crucible", "#FFCC0044": "Crucified Red", "#FF21C40E": "Crude Banana", "#FFEE2288": "Cruel Jewel", "#FFDD3344": "Cruel Ruby", "#FF213638": "Cruel Sea", "#FFB4E2D5": "Cruise Variant", "#FF018498": "Cruising", "#FFEFCEA0": "Crumble Topping", "#FFA38565": "Crumbling Cork", "#FFCABFB4": "Crumbling Statue", "#FFEE66BB": "Crumbly Lipstick", "#FFF2B95F": "Crunch", "#FF997A5A": "Crunch Granola", "#FFEA5013": "Crunchy Carrot", "#FFDBC364": "Crusade King", "#FFD4CAC5": "Crushed Almond", "#FFD15B9B": "Crushed Berries", "#FF83515B": "Crushed Berry", "#FFFFEDD5": "Crushed Cashew", "#FFB7735E": "Crushed Cinnamon", "#FFAE7F71": "Crushed Clay", "#FF835A88": "Crushed Grape", "#FFF5A497": "Crushed Grapefruit", "#FFC4FFF7": "Crushed Ice", "#FFD6DDD3": "Crushed Limestone", "#FFD6B694": "Crushed Nutmeg", "#FFE37730": "Crushed Orange", "#FF635D46": "Crushed Oregano", "#FFE0CFC8": "Crushed Out", "#FFE4DDD8": "Crushed Peony", "#FFEFCC44": "Crushed Pineapple", "#FFB06880": "Crushed Raspberry", "#FFD8CFBE": "Crushed Silk", "#FFBCAA9F": "Crushed Stone", "#FF445397": "Crushed Velvet", "#FF673C4C": "Crushed Violets", "#FF165B31": "Crusoe Variant", "#FF898076": "Crust", "#FFF38653": "Crusta Variant", "#FFC04E01": "Crustose Lichen", "#FFC3D4E7": "Cry Baby Blue", "#FF427898": "Cry Me a River", "#FFB23C5D": "Cry of a Rose", "#FFDDECE0": "Cryo Freeze", "#FF373B40": "Crypt", "#FF6D434E": "Cryptic Light", "#FFFFE314": "Crypto Gold", "#FFA7D8DE": "Crystal", "#FFCEE9A0": "Crystal Apple", "#FFA1D4D1": "Crystal Aqua", "#FF365955": "Crystal Ball", "#FFDBE2E7": "Crystal Bay", "#FFEFEEEF": "Crystal Bell", "#FF68A0B0": "Crystal Blue", "#FFE4E6DC": "Crystal Brooke", "#FFF4E9EA": "Crystal Clear", "#FFFCCDC1": "Crystal Coral", "#FFF8F4ED": "Crystal Cut", "#FF6D2C32": "Crystal Dark Red", "#FFE1E6F2": "Crystal Falls", "#FF79D0A7": "Crystal Gem", "#FFDDFFEE": "Crystal Glass", "#FFA4D579": "Crystal Green", "#FFCFC1B8": "Crystal Grey", "#FFE7E2D6": "Crystal Haze", "#FF88B5C4": "Crystal Lake", "#FFA9A3A4": "Crystal Noir", "#FFAFC7BF": "Crystal Oasis", "#FFD3CFAB": "Crystal Palace", "#FFE8C3BF": "Crystal Pink", "#FFB2E4D0": "Crystal Rapids", "#FFB1E2EE": "Crystal River", "#FFFDC3C6": "Crystal Rose", "#FFD9E5DD": "Crystal Salt White", "#FF5DAFCE": "Crystal Seas", "#FF006E81": "Crystal Teal", "#FFB4CEDF": "Crystal Waters", "#FFE4D99F": "Crystal Yellow", "#FFE9E3DE": "Crystalline", "#FFD9E6E2": "Crystalline Falls", "#FFDEBBBF": "Crystalline Pink", "#FFECDFDF": "Crystallise", "#FF4FB3B3": "Crystalsong Blue", "#FF6E5C4B": "Cub", "#FF4E6341": "Cub Scout", "#FF623D3D": "Cuba Brown", "#FF73383C": "Cuba Libre", "#FF927247": "Cuban Cigar", "#FFC76D6B": "Cuban Plaza", "#FF9B555D": "Cuban Rhythm", "#FFBC9B83": "Cuban Sand", "#FFB2B6BE": "Cube Farm", "#FFBBDD11": "Cucumber Bomber", "#FFE4EBB1": "Cucumber Cream", "#FFA2AC86": "Cucumber Crush", "#FF466353": "Cucumber Green", "#FFCDD79D": "Cucumber Ice", "#FFC2F177": "Cucumber Milk", "#FF3C773C": "Cucumber Queen", "#FF9BA373": "Cucuzza Verde", "#FFBCCAE8": "Cuddle", "#FFC1E0BE": "Cuddle Bug", "#FFEFEFE6": "Cuddle Down", "#FFAD8068": "Cuddlepot", "#FFFFFCE4": "Cuddly Yarn", "#FF7BB6C1": "Culinary Blue", "#FFE69B3A": "Culpeo", "#FF331233": "Cultist Robe", "#FFF6F4F5": "Cultured", "#FFE5DCD6": "Cultured Pearl", "#FFE5867B": "Cultured Rose", "#FFDADBDF": "Cumberland Fog", "#FF68655D": "Cumberland Grey", "#FFE5DFDC": "Cumberland Sausage", "#FFA58459": "Cumin Variant", "#FFA06600": "Cumin Ochre", "#FF695A45": "Cummings Oak", "#FFF19B7D": "Cumquat Cream", "#FFF3F3E6": "Cumulus Variant", "#FFB0C6DF": "Cumulus Cloud", "#FFFEDD7D": "Cup Noodles", "#FFBAA087": "Cup of Cocoa", "#FFCAAE7B": "Cup of Tea", "#FF8A6E53": "Cupcake", "#FFE6C7B7": "Cupcake Rose", "#FFF5B2C5": "Cupid Variant", "#FFEE6B8B": "Cupid\u2019s Arrow", "#FFFF22DD": "Cupid\u2019s Eye", "#FFEEDCDF": "Cupid\u2019s Revenge", "#FF406040": "Cupidity", "#FFDCBC8E": "Cupola Yellow", "#FFB09F8F": "Cuppa Coffee", "#FFB89871": "Cuppa Tea", "#FF008894": "Cura\u00e7ao Blue", "#FFA6A6B6": "Curated Lilac", "#FFEAE1CE": "Curated White", "#FFF8E1BA": "Curd", "#FFBCA483": "Curds and Whey", "#FFAA6988": "Cure All", "#FF380835": "Cured Eggplant", "#FFD3D8D2": "Curio", "#FF988977": "Curio Brown", "#FFD9E49E": "Curious", "#FF3D85B8": "Curious Blue Variant", "#FFDABFA4": "Curious Chipmunk", "#FFD2BB98": "Curious Collection", "#FF766859": "Curlew", "#FF9C9D76": "Curly Grass", "#FFD8C8BE": "Curly Maple", "#FFB1A387": "Curly Willow", "#FF884A50": "Currant Jam", "#FF553E51": "Currant Violet", "#FFD6A332": "Curry", "#FF845038": "Curry Brown", "#FFF5B700": "Curry Bubbles", "#FFBE9E6F": "Curry Sauce", "#FFDDAA33": "Currywurst", "#FF131313": "Cursed Black", "#FF70666A": "Curtain Call", "#FFFFD6B8": "Curtsy", "#FFC1C8AF": "Cushion Bush", "#FFFFFD78": "Custard", "#FFFBEFD0": "Custard Cream", "#FFF8DCAA": "Custard Powder", "#FFFCEEAE": "Custard Puff", "#FF003839": "Customs Green", "#FF9E909E": "Cut Heather", "#FFA01C2D": "Cut Ruby", "#FFBA7F38": "Cut the Mustard", "#FFB391C8": "Cut Velvet", "#FFDD4444": "Cute Crab", "#FFF4E2E1": "Cute Little Pink", "#FF8D8D40": "Cute Pixie", "#FFE3A49A": "Cuticle Pink", "#FFF4DDA5": "Cutlery Polish", "#FF7FBBC2": "Cuttlefish", "#FF5C8173": "Cutty Sark Variant", "#FF4E82B4": "Cyan Azure", "#FF14A3C7": "Cyan Blue", "#FF28589C": "Cyan Cobalt Blue", "#FF188BC2": "Cyan Cornflower Blue", "#FF00B5B8": "Cyan Sky", "#FF779080": "Cyanara", "#FF001F33": "Cyanophobia", "#FF77C9C2": "Cyantific", "#FF00FF26": "Cyber Neon Green", "#FFFFD400": "Cyber Yellow", "#FFFF2077": "Cyberpink", "#FF44484D": "Cyberspace", "#FFD687BA": "Cyclamen Variant", "#FFA7598D": "Cyclamen Red", "#FFF3E4A7": "Cymophane Yellow", "#FF171717": "Cynical Black", "#FF585D40": "Cypress", "#FF6F3028": "Cypress Bark Red", "#FF667C71": "Cypress Garden", "#FF9E8F57": "Cypress Green", "#FF6A7786": "Cypress Grey Blue", "#FF5E6552": "Cypress Vine", "#FF0F4645": "Cyprus Variant", "#FF699A88": "Cyprus Green", "#FFACB7B0": "Cyprus Spring", "#FFCFC5A7": "Cyrus Grass", "#FF775859": "Czarina", "#FFDEC9A9": "Czech Bakery", "#FF030764": "D. Darx Blue", "#FF516172": "Da Blues", "#FFAA6179": "Daah-Ling", "#FF7E553E": "Dachshund", "#FF2F484E": "Dad\u2019s Coupe", "#FFD3C1A0": "Dad\u2019s Slippers", "#FFB0AF8A": "Daddy-O", "#FF696684": "Daemonette Hide", "#FFFFE285": "Daffodil Yellow", "#FFE8E1D5": "Dagger Moth", "#FF8B4189": "Dahlia", "#FFF8BBD3": "Dahlia Delight", "#FF765067": "Dahlia Matte Red", "#FFB05988": "Dahlia Mauve", "#FF8774B0": "Dahlia Purple", "#FFD4D4C4": "Daikon White", "#FFFDC592": "Dainty Apricot", "#FFF5DD9D": "Dainty Daisy", "#FFF4BDB3": "Dainty Debutante", "#FFE9DFE5": "Dainty Flower", "#FFDECFBB": "Dainty Lace", "#FFFFCDB9": "Dainty Peach", "#FFECBCCE": "Dainty Pink", "#FFC6D26E": "Daiquiri Green", "#FFF4E7D4": "Dairy Belle", "#FFEDD2A4": "Dairy Cream Variant", "#FFF0B33C": "Dairy Made", "#FFFED340": "Daisy", "#FF5B3E90": "Daisy Bush Variant", "#FFFFF09B": "Daisy Chain", "#FFFCDF8A": "Daisy Desi", "#FFF4F3E8": "Daisy Field", "#FF55643B": "Daisy Leaf", "#FFF8F3E3": "Daisy White", "#FFD3C29D": "Dakota Trail", "#FFE1BD8E": "Dakota Wheat", "#FF664A2D": "Dallas Variant", "#FFECE0D6": "Dallas Dust", "#FFFDDC00": "Dallol Yellow", "#FF97A3DA": "Dalmatian Sage", "#FFAFDADF": "Daly Waters", "#FF996D32": "Damascene", "#FFFCF2DF": "Damask", "#FFD1B3A9": "Damask Dunes", "#FF999BA8": "Dame Dignity", "#FF5F6171": "Damp Basement", "#FF4A4747": "Dampened Black", "#FFC69EAE": "Damsel", "#FF854C65": "Damson", "#FF583563": "Damson Mauve", "#FF576780": "Dana", "#FFE6D8CD": "Dance of the Goddesses", "#FF064D83": "Dance Studio", "#FFD7CBAC": "Dance With the Wind", "#FFDC9399": "Dancer", "#FF94734C": "Dancing Bear", "#FFFCF3C6": "Dancing Butterfly", "#FF254A47": "Dancing Crocodiles", "#FFEFC857": "Dancing Daisy", "#FF6E493D": "Dancing Dogs", "#FFC4BAA1": "Dancing Dolphin", "#FF006658": "Dancing Dragonfly", "#FFC5CD8F": "Dancing Green", "#FFABC5D6": "Dancing in the Rain", "#FF7B7289": "Dancing in the Spring", "#FF429B77": "Dancing Jewel", "#FFC8CC9E": "Dancing Kite", "#FFBFC8D8": "Dancing Mist", "#FF016459": "Dancing Peacock", "#FF1C4D8F": "Dancing Sea", "#FFC8A4BD": "Dancing Wand", "#FFFEDF08": "Dandelion Variant", "#FFEAE8EC": "Dandelion Floatie", "#FFF7EAC1": "Dandelion Tea", "#FFF0E130": "Dandelion Tincture", "#FFFFF0B5": "Dandelion Whisper", "#FFFCF2B9": "Dandelion Wine", "#FFE3BB65": "Dandelion Wish", "#FFFCD93B": "Dandelion Yellow", "#FFFACC51": "Dandy Lion", "#FFFF0E0E": "Danger", "#FF595539": "Danger Ridge", "#FFD00220": "Dangerous Affair", "#FFCBC5C6": "Dangerous Robot", "#FF616B89": "Dangerously Elegant", "#FF16F12D": "Dangerously Green", "#FFD84139": "Dangerously Red", "#FF5E4235": "Daniel Boone", "#FFBA9967": "Danish Pine", "#FFB4D5D5": "Dante Peak", "#FF5B89C0": "Danube Variant", "#FF116DB1": "Daphne", "#FF715B49": "Dapper", "#FFE2C299": "Dapper Dingo", "#FF697078": "Dapper Greyhound", "#FF947F65": "Dapper Tan", "#FF959486": "Dapple Grey", "#FFC5CC9F": "Dappled Daydream", "#FFF2E3C9": "Dappled Sunlight", "#FF3A4A3F": "Dard Hunter Green", "#FFAB4343": "Daredevil", "#FFDF644E": "Daring", "#FFF0DFE0": "Daring Deception", "#FF374874": "Daring Indigo", "#FF1B2431": "Dark", "#FF353F51": "Dark & Stormy", "#FF9698A3": "Dark Ages", "#FF4F5A62": "Dark and Stormy Night", "#FF495252": "Dark as Night", "#FF6A6D6D": "Dark Ash", "#FF5C464A": "Dark Berry", "#FF533958": "Dark Blackberry", "#FFA68A6E": "Dark Blond", "#FF315B7D": "Dark Blue Tint", "#FF92462F": "Dark Brazilian Topaz", "#FF4B4146": "Dark Burgundy Wine", "#FF513116": "Dark Catacombs", "#FF55504D": "Dark Cavern", "#FF333232": "Dark Charcoal", "#FF774D41": "Dark Cherry Mocha", "#FF624A49": "Dark Chocolate", "#FFAABB00": "Dark Citron", "#FF4C3D31": "Dark Clove", "#FF33578A": "Dark Cobalt Blue", "#FF843C41": "Dark Crimson", "#FF3F4551": "Dark Crypt", "#FF2F1212": "Dark Danger", "#FF005588": "Dark Denim", "#FF00334F": "Dark Denim Blue", "#FF5A3939": "Dark Drama", "#FF332266": "Dark Dreams", "#FF884455": "Dark Earth", "#FF3D2004": "Dark Ebony Variant", "#FF112244": "Dark Eclipse", "#FF3B3F42": "Dark Elf", "#FF00834E": "Dark Emerald", "#FF503D4D": "Dark Energy", "#FF3E3F41": "Dark Engine", "#FFA4A582": "Dark Envy", "#FF3E554F": "Dark Everglade", "#FF573B4C": "Dark Fig Violet", "#FF556962": "Dark Forest", "#FF4F443F": "Dark Granite", "#FF033500": "Dark Green Variant", "#FF363737": "Dark Grey Variant", "#FF4E4459": "Dark Grey Mauve", "#FF222429": "Dark Gunmetal", "#FF666699": "Dark Horizon", "#FF661122": "Dark Humour", "#FF4D5A7E": "Dark Iris", "#FF66856F": "Dark Ivy", "#FF5C8774": "Dark Jade", "#FF0B3021": "Dark Jungle", "#FF333438": "Dark Kettle Black", "#FF151931": "Dark Knight", "#FF6A7F7D": "Dark Lagoon", "#FF856798": "Dark Lavender Variant", "#FF8BBE1B": "Dark Lemon Lime", "#FF9C6DA5": "Dark Lilac", "#FF84B701": "Dark Lime", "#FF7EBD01": "Dark Lime Green", "#FF989A98": "Dark Limestone", "#FF5F574F": "Dark LUA Console", "#FF482029": "Dark Mahogany", "#FF994939": "Dark Marmalade", "#FF3C0008": "Dark Maroon", "#FF110101": "Dark Matter", "#FF003377": "Dark Midnight Blue", "#FF515763": "Dark Mineral", "#FF161718": "Dark Moon", "#FF40495B": "Dark Navy", "#FF404B57": "Dark Night", "#FF373E02": "Dark Olive", "#FF6E5160": "Dark Olive Paste", "#FF2E2D30": "Dark Onyx", "#FFC65102": "Dark Orange Variant", "#FF251B19": "Dark Orchestra", "#FF653D7C": "Dark Pansy", "#FF665FD1": "Dark Periwinkle", "#FF606865": "Dark Pewter", "#FF193232": "Dark Pine Green", "#FFCB416B": "Dark Pink Variant", "#FF603E53": "Dark Potion", "#FF6B6C89": "Dark Prince", "#FFD9308A": "Dark Princess Pink", "#FF2B0F2E": "Dark Prom Queen", "#FF4F3A3C": "Dark Puce", "#FF35063E": "Dark Purple", "#FF6E576B": "Dark Purple Grey", "#FF505838": "Dark Rainforest", "#FF3B5150": "Dark Reaper", "#FF840000": "Dark Red Variant", "#FF4A2125": "Dark Red Brown", "#FF434257": "Dark Renaissance", "#FF060B14": "Dark Rift", "#FF3E4445": "Dark River", "#FF4A2D2F": "Dark Roast", "#FFB5485D": "Dark Rose", "#FF02066F": "Dark Royalty", "#FF45362B": "Dark Rum", "#FF6D765B": "Dark Sage", "#FFA2646F": "Dark Sakura", "#FFC85A53": "Dark Salmon Variant", "#FFE8957A": "Dark Salmon Injustice", "#FF3F012C": "Dark Sanctuary", "#FFA88F59": "Dark Sand", "#FF4C5560": "Dark Sea", "#FF666655": "Dark Seagreen", "#FF113691": "Dark Seashore Night", "#FF3E5361": "Dark Secret", "#FF113311": "Dark Serpent", "#FF4A4B4D": "Dark Shadow", "#FF004444": "Dark Side", "#FF070D0D": "Dark Side of the Moon", "#FF909989": "Dark Sky", "#FF465352": "Dark Slate", "#FF214761": "Dark Slate Blue Variant", "#FF66AA11": "Dark Slimelime", "#FF4D52DE": "Dark Soft Violet", "#FF587A65": "Dark Sorrel", "#FF112255": "Dark Soul", "#FF414A4C": "Dark Space", "#FF303B4C": "Dark Spell", "#FF7E736D": "Dark Sting", "#FF819094": "Dark Storm Cloud", "#FF80444C": "Dark Strawberry", "#FF383839": "Dark Summoning", "#FF483C3C": "Dark Taupe", "#FF634E43": "Dark Tavern", "#FF014D4E": "Dark Teal", "#FF97765C": "Dark Toast", "#FF121212": "Dark Tone Ink", "#FF817C87": "Dark Topaz", "#FF594D46": "Dark Truffle", "#FF045C5A": "Dark Turquoise Variant", "#FF0D2B52": "Dark Tyrian Blue", "#FF932904": "Dark Umber", "#FF141311": "Dark Veil", "#FF34013F": "Dark Violet Variant", "#FF151517": "Dark Void", "#FF56443E": "Dark Walnut", "#FF855E42": "Dark Wood", "#FF4F301F": "Dark Wood Grain", "#FFE7BF8E": "Dark Yellow", "#FF660011": "Darkest Dungeon", "#FF223311": "Darkest Forest", "#FF625768": "Darkest Grape", "#FF43455E": "Darkest Navy", "#FF303F3D": "Darkest Spruce", "#FF16160E": "Darkness", "#FF3A4645": "Darkness Green", "#FF2D1608": "Darkout", "#FF2F1611": "Darkroom", "#FF464964": "Darkshore", "#FF4F4969": "Darlak", "#FFFF88FF": "Darling Bud", "#FFD29F7A": "Darling Clementine", "#FFC9ACD6": "Darling Lilac", "#FF1D045D": "Darth Torus", "#FF27252A": "Darth Vader", "#FFCDDCE3": "Dartmoor Mist", "#FF00703C": "Dartmouth Green Variant", "#FFCA6E5F": "Dash of Curry", "#FF928459": "Dash of Oregano", "#FFFBD2CF": "Dash of Pink", "#FFEAEBE8": "Dashing", "#FF0BDB43": "Dasyphyllous Green", "#FFAF642B": "Date Fruit Brown", "#FFCCAC86": "DaVanzo Beige", "#FF58936D": "DaVanzo Green", "#FF6C4226": "Davao Durian", "#FFB1D27B": "Davao Green", "#FF535554": "Davy\u2019s Grey", "#FF9F9D91": "Dawn Variant", "#FFCACCCB": "Dawn Blue", "#FFCCFFFF": "Dawn Departs", "#FFFADFA9": "Dawn Light", "#FF770044": "Dawn of the Fairies", "#FFFEDFDC": "Dawn Patrol", "#FFE6D6CD": "Dawn Pink Variant", "#FFC9CED3": "Dawn\u2019s Early Light", "#FF70756E": "Dawnstone", "#FFFFA373": "Day at the Zoo", "#FFD9CDC4": "Day Dreamer", "#FFEADD82": "Day Glow", "#FFEB5C34": "Day Glow Orange", "#FFFFF9EC": "Day Lily", "#FFD5D2D1": "Day on Mercury", "#FFEAEFED": "Day Spa", "#FF7E7597": "Daybreak", "#FFC7C9D8": "Daybreak Beckons", "#FFF7EECB": "Daybreak Sun", "#FFE3EBAE": "Daydream", "#FFF4F0E1": "Daydreaming", "#FF38A1DB": "Dayflower", "#FF758CBF": "Dayflower Blue", "#FF546C52": "Daylight Jungle", "#FFA385B3": "Daylight Lilac", "#FFF8F0D2": "Daylily Yellow", "#FFFFF8DA": "Daystar", "#FF5287B9": "Dazzle", "#FFD99B7B": "Dazzle and Delight", "#FFEDEBEA": "Dazzle Me", "#FF3850A0": "Dazzling Blue", "#FFD82C0D": "Dazzling Red", "#FF85CA87": "De York Variant", "#FF99DEAD": "Dead 99", "#FF0055CC": "Dead Blue Eyes", "#FF434B4F": "Dead Forest", "#FFE4DC8A": "Dead Grass", "#FF2E5A88": "Dead Lake", "#FFD2DAD0": "Dead Nettle White", "#FF3B3A3A": "Dead Pixel", "#FF77EEEE": "Dead Sea", "#FF3A403B": "Dead Sea Mud", "#FF8F666A": "Deadlock", "#FF111144": "Deadly Depths", "#FFDEAD11": "Deadly Mustard", "#FFC2A84B": "Deadsy", "#FF596D7F": "Deadwind Pass", "#FFA30112": "Dear Darling", "#FFFEECB3": "Dear Melissa", "#FFF5F3E6": "Dear Reader", "#FF60443F": "Death by Chocolate", "#FFE7D9DB": "Death Cap", "#FF9EB37B": "Death Guard", "#FF141313": "Death Metal", "#FFE760D2": "Death of a Star", "#FFDDBB88": "Death Valley Beige", "#FFB36853": "Deathclaw Brown", "#FF5C6730": "Deathworld Forest", "#FFAF9294": "Deauville Mauve", "#FF90A0A6": "Debonair", "#FFCBD0DD": "Debonaire", "#FFEE7744": "Debrito", "#FFED7468": "Debutante", "#FF6E8DBB": "Debutante Ball", "#FF73667B": "Decadence", "#FF513233": "Decadent Chocolate", "#FF8D3630": "Decadent Red", "#FFDECADE": "Decadial Pink", "#FFADA3BB": "Decanter", "#FFBFA1AD": "Decanting", "#FFD57835": "Decaying Leaf", "#FFDFE2EA": "December Dawn", "#FF415064": "December Eve", "#FFE0E8DB": "December Forest", "#FFD6DDDC": "December Rain", "#FFD5D7D9": "December Sky", "#FF9DCEE3": "December Solstice", "#FFBFB5CA": "Decency", "#FFB69FCC": "Dechala Lilac", "#FFD79E62": "Dechant Pear Yellow", "#FFFDCC4E": "Decisive Yellow", "#FF5E7CAC": "Deck Crew", "#FFCCCF82": "Deco Variant", "#FF89978E": "Deco Grey", "#FFF6C2CC": "Deco Pink", "#FF824942": "Deco Red", "#FF9E6370": "Deco Rose", "#FFF9D5C9": "Deco Shell", "#FF8FCBC0": "Deco-Rate", "#FF7B736B": "Deconstruction", "#FFF2E5CF": "D\u00e9cor White", "#FFF6BB00": "Decor Yellow", "#FF3F74A3": "Decoration Blue", "#FF817181": "Decorative Iris", "#FFF6F4EC": "Decorator White", "#FF00829E": "Decore Splash", "#FFAC7559": "Decorous Amber", "#FFB39AA0": "Decorum", "#FFFEE2C8": "Dedication", "#FFD4CB83": "Deduction", "#FF5B3082": "Deep Amethyst", "#FF78DBE2": "Deep Aquamarine", "#FF004F57": "Deep Atlantic Blue", "#FF5C4A4D": "Deep Aubergine", "#FF3E5580": "Deep Azure", "#FFD99F50": "Deep Bamboo Yellow", "#FFC57776": "Deep Bloom", "#FF040273": "Deep Blue Variant", "#FF1A5D72": "Deep Blue Sea", "#FFE36F8A": "Deep Blush Variant", "#FF5E675A": "Deep Bottlebrush", "#FF27275F": "Deep Breath", "#FF51412D": "Deep Bronze Variant", "#FF342A2A": "Deep Brown", "#FF744456": "Deep Carmine Variant", "#FF007BBB": "Deep Cerulean Variant", "#FFFAD6C5": "Deep Champagne Variant", "#FF7A8790": "Deep Channel", "#FF6B473D": "Deep Cherrywood", "#FF771133": "Deep Claret", "#FFAE6A52": "Deep Clay Red", "#FF424769": "Deep Cobalt", "#FF7D4071": "Deep Commitment", "#FFDA7C55": "Deep Coral", "#FF007381": "Deep Current", "#FF322D2D": "Deep Daichi Black", "#FFE9E7E6": "Deep Daigi White", "#FF3311AA": "Deep Daijin Blue", "#FF7C2229": "Deep Dairei Red", "#FFF0CA00": "Deep Daishin Yellow", "#FF661177": "Deep Daitoku Purple", "#FF6688FF": "Deep Denim", "#FF545648": "Deep Depths", "#FF534C3E": "Deep Desert Shadow", "#FF2E5467": "Deep Dive", "#FF5E97A9": "Deep Diving", "#FF553D3A": "Deep Dungeon", "#FF4D4B4B": "Deep Earth", "#FF556551": "Deep Emerald", "#FF4C574B": "Deep Evergreen", "#FF614454": "Deep Exquisite", "#FF193925": "Deep Fir Variant", "#FFBF5C42": "Deep Fire", "#FF3C463E": "Deep Forest", "#FF393437": "Deep Forest Brown", "#FF335500": "Deep Forestial Escapade", "#FFF0B054": "Deep Fried", "#FFF6C75E": "Deep Fried Sunrays", "#FF414048": "Deep Galaxy", "#FF5A9274": "Deep Grass Green", "#FF02590F": "Deep Green", "#FF726A6E": "Deep Greige", "#FF4C567A": "Deep Indigo", "#FF306030": "Deep Into the Wood", "#FF3F564A": "Deep Jungle", "#FF343467": "Deep Koamaru Variant", "#FF005A6F": "Deep Lagoon", "#FF006C70": "Deep Lake", "#FF687189": "Deep Larkspur", "#FF565A7D": "Deep Lavender", "#FF747962": "Deep Lichen Green", "#FF2E5767": "Deep Loch", "#FFA0025C": "Deep Magenta Variant", "#FF634743": "Deep Mahogany", "#FF2E6469": "Deep Marine", "#FF623F45": "Deep Maroon", "#FF938565": "Deep Marsh", "#FF574958": "Deep Merlot", "#FF55AA66": "Deep Mint", "#FF3D4C46": "Deep Mooring", "#FF544954": "Deep Mulberry", "#FF494C59": "Deep Mystery", "#FF494C55": "Deep Night", "#FF2A4B5F": "Deep Ocean", "#FF2C4173": "Deep Ocean Floor", "#FFDC4D01": "Deep Orange", "#FF864735": "Deep Orange-Coloured Brown", "#FF525476": "Deep Orchid", "#FF006E62": "Deep Pacific", "#FF009286": "Deep Peacock Blue", "#FF7C83BC": "Deep Periwinkle", "#FF557138": "Deep Pine", "#FF4A2A59": "Deep Plum", "#FF014420": "Deep Pond", "#FF366D68": "Deep Pool Teal", "#FF36013F": "Deep Purple", "#FF9A0200": "Deep Red", "#FFBB603C": "Deep Reddish Orange", "#FF424F5F": "Deep Reservoir", "#FF7F5153": "Deep Rhubarb", "#FF4C6A68": "Deep Rift", "#FF0079B3": "Deep River", "#FF2D4243": "Deep River Green", "#FFB25550": "Deep Rose", "#FF364C68": "Deep Royal", "#FFFF9932": "Deep Saffron", "#FF195155": "Deep Sanction", "#FF082466": "Deep Sapphire Variant", "#FF167E65": "Deep Sea Variant", "#FF2C2C57": "Deep Sea Base", "#FF2A4B5A": "Deep Sea Blue", "#FFD86157": "Deep Sea Coral", "#FF376167": "Deep Sea Dive", "#FF255C61": "Deep Sea Diver", "#FF6A6873": "Deep Sea Dolphin", "#FF002D69": "Deep Sea Dream", "#FF2000B1": "Deep Sea Exploration", "#FF879293": "Deep Sea Grey", "#FF4F5A4C": "Deep Sea Shadow", "#FF5E5749": "Deep Sea Turtle", "#FF959889": "Deep Seagrass", "#FF37412A": "Deep Seaweed", "#FF7F6968": "Deep Serenity", "#FF514A3D": "Deep Shadow", "#FF737C84": "Deep Shale", "#FF0D75F8": "Deep Sky Blue Variant", "#FF0F261F": "Deep Slate Green", "#FF172713": "Deep Slate Olive", "#FF7D8392": "Deep Smoke Signal", "#FFB4989F": "Deep South", "#FF3F4143": "Deep Space", "#FF292E35": "Deep Space Echo", "#FF43363D": "Deep Space Frigate", "#FF332277": "Deep Space Rodeo", "#FF223382": "Deep Space Royal", "#FF726751": "Deep Tan", "#FF7B6660": "Deep Taupe", "#FF00555A": "Deep Teal Variant", "#FF8B483D": "Deep Terra Cotta", "#FF728787": "Deep Thoughts", "#FF01B0BD": "Deep Turquoise", "#FF375F71": "Deep Twilight Blue", "#FF404F95": "Deep Ultramarine", "#FF694D3D": "Deep Umber", "#FF017F9E": "Deep Universe", "#FF313248": "Deep Velvet", "#FF330066": "Deep Violet", "#FF4B6443": "Deep Viridian", "#FF615D58": "Deep Walnut", "#FF2A6FA1": "Deep Water", "#FF33313B": "Deep Well", "#FF444172": "Deep Wisteria", "#FF454669": "Deepest Fig", "#FF6D595A": "Deepest Mauve", "#FF44413C": "Deepest Nightmare", "#FF5C84B4": "Deepest Periwinkle", "#FF44534E": "Deepest River", "#FF444D56": "Deepest Sea", "#FF466174": "Deepest Water", "#FF6D4940": "Deeply Deanna", "#FFECB2B3": "Deeply Embarrassed", "#FF303F4C": "Deepsea Core", "#FF082599": "Deepsea Kraken", "#FFBA8759": "Deer", "#FF96847A": "Deer God", "#FFAC7434": "Deer Leather", "#FFB2A69A": "Deer Run", "#FFA1614C": "Deer Tracks", "#FF6A634C": "Deer Trail", "#FFC7A485": "Deer Valley", "#FF88FFEE": "Defence Matrix", "#FFC6D5E4": "Defenestration", "#FFB0C1C2": "Degas Blue", "#FFB37E8C": "Degas Pink", "#FF9CBCC6": "D\u00e9j\u00e0 Blue", "#FFBED1CC": "D\u00e9j\u00e0 Vu", "#FFB5998E": "Del Rio Variant", "#FFDABF92": "Del Sol Maize", "#FFAAB350": "Delaunay Green", "#FF76A09E": "Delaware Blue Hen", "#FFFDF901": "Delayed Yellow", "#FF9A92A7": "Delectable", "#FF3D5E8C": "Delft", "#FF3311EE": "Delft Blue", "#FFFDC1C5": "Delhi Dance Pink", "#FFA36A6D": "Delhi Spice", "#FFE8B523": "Deli Yellow", "#FFF5E3E2": "Delicacy", "#FFEBE2E5": "Delicacy White", "#FFC2D1E2": "Delicate Ballet Blue", "#FFEDE0D5": "Delicate Bliss", "#FFDBBFCE": "Delicate Bloom", "#FFBCDFE8": "Delicate Blue", "#FFBED7F0": "Delicate Blue Mist", "#FFEFD7D1": "Delicate Blush", "#FFA78C8B": "Delicate Brown", "#FFDDDFE8": "Delicate Cloud", "#FFE9EDC0": "Delicate Daisy", "#FFFED9BC": "Delicate Dawn", "#FFC5EAD3": "Delicate Frost", "#FF6AB2CA": "Delicate Girl Blue", "#FF93B0A9": "Delicate Green", "#FFBCAB99": "Delicate Honeysweet", "#FFB7D2E3": "Delicate Ice", "#FFF3E6D4": "Delicate Lace", "#FFEEDD77": "Delicate Lemon", "#FFB6AED6": "Delicate Lilac", "#FFD7D2E2": "Delicate Lilac Crystal", "#FFC5B5CA": "Delicate Mauve", "#FFDDF3E6": "Delicate Mint", "#FFE1EBE5": "Delicate Mist", "#FFE4CFD3": "Delicate Pink", "#FFEEC6C3": "Delicate Pink Rose", "#FFA95C68": "Delicate Prunus", "#FFF7E0D6": "Delicate Rose", "#FFD7F3DD": "Delicate Sapling", "#FFFFEFDD": "Delicate Seashell", "#FFD1E2D8": "Delicate Snow Goose", "#FFFDCDBD": "Delicate Sweet Apricot", "#FFAA9C8B": "Delicate Truffle", "#FFC0DFE2": "Delicate Turquoise", "#FFD7D6DC": "Delicate Viola", "#FF8C8DA8": "Delicate Violet", "#FFF1F2EE": "Delicate White", "#FF483B36": "Delicioso", "#FF585E46": "Delicious", "#FF654254": "Delicious Berry", "#FF77CC00": "Delicious Dill", "#FFFFAA11": "Delicious Mandarin", "#FFFFD7B0": "Delicious Melon", "#FF2E212D": "Delighted Chimp", "#FFD2B6BE": "Delightful", "#FFA5A943": "Delightful Camouflage", "#FFEEEE33": "Delightful Dandelion", "#FF00EE00": "Delightful Green", "#FFEBBE7C": "Delightful Moon", "#FFF9E7C8": "Delightful Pastry", "#FFFFEBD1": "Delightful Peach", "#FFDDCCCC": "Delirious Donkey", "#FF486531": "Dell Variant", "#FF7A9DCB": "Della Robbia Blue", "#FF8EC39E": "Delltone", "#FF169EC0": "Delos Blue", "#FF6198AE": "Delphinium Blue", "#FF4F92CD": "Delphinium Corsage", "#FF999B95": "Delta Variant", "#FF979147": "Delta Break", "#FF2D4A4C": "Delta Green", "#FFC5E6CF": "Delta Mint", "#FFA8917F": "Delta Sandbar", "#FFC4C2AB": "Delta Waters", "#FF0077AA": "Deluge Variant", "#FF66BBCC": "Delusional Dragonfly", "#FF8BC7E6": "Deluxe Days", "#FFE1B270": "Demerara Sugar", "#FFECDA9E": "Demeter", "#FF02CC02": "Demeter Green", "#FF493C31": "Demitasse", "#FF00AEF3": "Democrat", "#FF7A0006": "Demon", "#FF45538D": "Demon Blue", "#FFD2144B": "Demon Princess", "#FFBB2233": "Demonic", "#FFD02B48": "Demonic Kiss", "#FFD725DE": "Demonic Purple", "#FFFFE700": "Demonic Yellow", "#FFE8D4D5": "Demure", "#FFF7D2C4": "Demure Pink", "#FF7D775D": "Denali Green", "#FF2F6479": "Denim Blue", "#FF7C8D96": "Denim Drift", "#FFB8CAD5": "Denim Light", "#FF7F97B5": "Denim Tradition", "#FF636D65": "Dense Shrub", "#FF889911": "Densetsu Green", "#FFF2B717": "Dent Corn", "#FF99D590": "Dentist Green", "#FF7795C1": "Denver River", "#FFE7D8C7": "D\u00e9paysement", "#FF355859": "Depth Charge", "#FF2C319B": "Depths of Night", "#FF253F4E": "Depths of the Nile", "#FFF9E4C6": "Derby Variant", "#FF599C89": "Derby Green", "#FF245E36": "Derbyshire", "#FFF9E1CF": "Derry Coast Sunrise", "#FF669999": "Desaturated Cyan", "#FF445155": "Descent Into the Catacombs", "#FFCCAD60": "Desert Variant", "#FFC28996": "Desert Bud", "#FFAFCA9D": "Desert Cactus", "#FFC2AE88": "Desert Camel", "#FF80474E": "Desert Canyon", "#FFD3A169": "Desert Caravan", "#FFC99C7D": "Desert Carnation", "#FF727A60": "Desert Chaparral", "#FF9E6E43": "Desert Clay", "#FFF7D497": "Desert Convoy", "#FFD16459": "Desert Coral", "#FFD0C8A9": "Desert Cover", "#FFEDDBE8": "Desert Dawn", "#FFFFBA6B": "Desert Dessert", "#FFB5AB9C": "Desert Dune", "#FFAD9A91": "Desert Dusk", "#FFE2BB8A": "Desert Dust", "#FFB6A29D": "Desert Echo", "#FFEFCDB8": "Desert Field", "#FFC6B183": "Desert Floor", "#FFFF8D82": "Desert Flower", "#FFD3BDAC": "Desert Fortress", "#FFC5B686": "Desert Grass", "#FFB8A487": "Desert Grey", "#FFC4C8AA": "Desert Hot Springs", "#FFF3F2E1": "Desert Iguana", "#FFD6CDB7": "Desert Khaki", "#FFBD9C9D": "Desert Lights", "#FFFEF5DB": "Desert Lily", "#FFA9A450": "Desert Locust", "#FFE8D2D6": "Desert Mauve", "#FFEFCFBC": "Desert Mesa", "#FFB9E0CF": "Desert Mirage", "#FFDEB181": "Desert Mist", "#FFD0BBB0": "Desert Morning", "#FFBEA166": "Desert Moss", "#FFE9DBD2": "Desert Mountain", "#FF5F727A": "Desert Night", "#FF675239": "Desert Palm", "#FFC0CABC": "Desert Panzer", "#FFAAAE9A": "Desert Pear", "#FFCAB698": "Desert Pebble", "#FFE5DDC9": "Desert Plain", "#FFFBEFDA": "Desert Powder", "#FFB3837F": "Desert Red", "#FFD5A884": "Desert Riverbed", "#FFD5C6BD": "Desert Rock", "#FFCD616B": "Desert Rose", "#FF90926F": "Desert Sage", "#FFB9A795": "Desert Sandstorm", "#FF9F927A": "Desert Shadows", "#FFE9E4CF": "Desert Smog", "#FFA15F3B": "Desert Soil", "#FFC66B30": "Desert Spice", "#FFDCDDCB": "Desert Springs", "#FFF9F0E1": "Desert Star", "#FFEDE7E0": "Desert Storm Variant", "#FFD5C7B3": "Desert Suede", "#FFBB7326": "Desert Sun", "#FFFCB58D": "Desert Sunrise", "#FFA38C6C": "Desert Tan", "#FF7F7166": "Desert Taupe", "#FFDDCC99": "Desert Temple", "#FF89734B": "Desert Willow", "#FFE5D295": "Desert Wind", "#FFA29259": "Desert Yellow", "#FFE7DBBF": "Deserted Beach", "#FF857C64": "Deserted Island", "#FFE7BF7B": "Deserted Path", "#FFA47BAC": "Design Delight", "#FFEFE5BB": "Designer Cream Yellow", "#FFE1BCD8": "Designer Pink", "#FFE7DED1": "Designer White", "#FFA93435": "Desirable", "#FFEA3C53": "Desire", "#FFEEC5D2": "Desire Pink", "#FFD8D7D9": "Desired Dawn", "#FFC4ADB8": "Desire\u00e9", "#FFB5C1A9": "Desolace Dew", "#FFD3CBC6": "Desolate Field", "#FFF6E4D0": "Dessert Cream", "#FFFEDDB1": "Destined for Greatness", "#FFCFC9C6": "Destiny", "#FF98968D": "Destroyer Grey", "#FFE9E9E1": "Destroying Angels", "#FFFF3355": "Detailed Devil", "#FF8B8685": "Detective Coat", "#FF393C40": "Detective Thriller", "#FFC56639": "Determined Orange", "#FFBDD0D1": "Detroit", "#FFF7FCFE": "Deutzia White", "#FF006B4D": "Device Green", "#FF277594": "Devil Blue", "#FFFF3344": "Devil\u2019s Advocate", "#FFBB4422": "Devil\u2019s Butterfly", "#FF8F9805": "Devil\u2019s Flower Mantis", "#FF44AA55": "Devil\u2019s Grass", "#FF662A2C": "Devil\u2019s Lip", "#FF423450": "Devil\u2019s Plum", "#FFFECD82": "Deviled Eggs", "#FFDD3322": "Devilish", "#FFCE7790": "Devilish Diva", "#FF5A573F": "Devlan Mud", "#FF3C3523": "Devlan Mud Wash", "#FF3E5650": "Devon Green", "#FF717E6F": "Devon Rex", "#FFF5EFE7": "Devonshire", "#FFE7F2E9": "Dew Variant", "#FF97B391": "Dew Green", "#FFCEE3DC": "Dew Not Disturb", "#FFD7EDE8": "Dew Pointe", "#FF8B5987": "Dewberry", "#FFDDE4E3": "Dewdrop", "#FFB0B8AA": "Dewkissed Rain", "#FFC4D1C2": "Dewkist", "#FFDCEEDB": "Dewmist Delight", "#FFB2CED2": "Dewpoint", "#FFD6E1D8": "Dewy", "#FF6BB1B4": "Dexter", "#FFFAE432": "Dhalsim\u2019s Yoga Flame", "#FFCABAA8": "Dhurrie Beige", "#FFCD0D01": "Diablo Red", "#FF1B8E13": "Diamine Green", "#FFFAF7E2": "Diamond", "#FF2B303E": "Diamond Black", "#FFE9E9F0": "Diamond Cut", "#FFF8F5E5": "Diamond Dust", "#FF3E474B": "Diamond Grey", "#FFEEE3CC": "Diamond Ice", "#FFD0EFF3": "Diamond League", "#FFDFE7EC": "Diamond Light", "#FFBCDAEC": "Diamond Soft Blue", "#FFDCDBDC": "Diamond Stud", "#FFE2EFF3": "Diamond White", "#FFE5E2E1": "Diamonds in the Sky", "#FFE9E8E0": "Diamonds Therapy", "#FFFCF6DC": "Diantha", "#FF8D6D89": "Dianthus Mauve", "#FFD0CAD7": "Diaphanous", "#FF60B8BE": "Dickie Bird", "#FF322C2B": "Diesel Variant", "#FFBC934D": "Different Gold", "#FFEBE5D5": "Diffused Light", "#FF93739E": "Diffused Orchid", "#FF8E6E57": "Dig It", "#FFA37336": "Digger\u2019s Gold", "#FF636365": "Digital", "#FFB7B3A4": "Digital Garage", "#FFCAC0E1": "Digital Lavender", "#FFAA00FF": "Digital Violets", "#FFFFEB7E": "Digital Yellow", "#FF3B695F": "Dignified", "#FF716264": "Dignified Purple", "#FF094C73": "Dignity Blue", "#FF007044": "Diisha Green", "#FFA18251": "Dijon", "#FFE2CA73": "Dijon Mustard", "#FF9B8F55": "Dijonnaise", "#FF6F7755": "Dill", "#FFA2A57B": "Dill Grass", "#FFB6AC4B": "Dill Green", "#FF67744A": "Dill Pickle", "#FF9DA073": "Dill Powder", "#FFB3B295": "Dill Seed", "#FFD6E9E4": "Dillard\u2019s Blue", "#FF35495A": "Dilly Blue", "#FFF6DB5D": "Dilly Dally", "#FFF46860": "Diluno Red", "#FFB8DEF2": "Diluted Blue", "#FFDDEAE0": "Diluted Green", "#FFDADFEA": "Diluted Lilac", "#FFE8EFDB": "Diluted Lime", "#FFDAF4EA": "Diluted Mint", "#FFFBE8E2": "Diluted Orange", "#FFE9DFE8": "Diluted Pink", "#FFE8DDE2": "Diluted Red", "#FFC8C2BE": "Dim", "#FFBCE1EB": "Diminished Blue", "#FFE3E6D6": "Diminished Green", "#FFE9F3DD": "Diminished Mint", "#FFFAE9E1": "Diminished Orange", "#FFF1E5E0": "Diminished Pink", "#FFE8D8DA": "Diminished Red", "#FFD3F2ED": "Diminished Sky", "#FF062E03": "Diminishing Green", "#FFF1DEDE": "Diminutive Pink", "#FFE9808B": "Dimple", "#FF607C47": "Dingley Variant", "#FFC53151": "Dingy Dungeon", "#FFE6F2A2": "Dingy Sticky Note", "#FFE8F3E4": "Dinner Mint", "#FF7F997D": "Dinosaur", "#FF827563": "Dinosaur Bone", "#FFCABAA9": "Dinosaur Egg", "#FF8391A0": "Diopside Blue", "#FF439E8D": "Dioptase Green", "#FF9DBFB1": "Diorite", "#FF68CFC1": "Dip in the Pool", "#FF3A445D": "Diplomatic", "#FFE9EEEE": "Dipped in Cloudy Dreams", "#FFFCF6EB": "Dipped in Cream", "#FF282828": "Dire Wolf", "#FF3F8A24": "Direct Green", "#FF0061A8": "Directoire Blue", "#FF5ACAA4": "Diroset", "#FF9B7653": "Dirt", "#FF836539": "Dirt Brown", "#FFBB6644": "Dirt Track", "#FF926E2E": "Dirt Yellow", "#FFDFC393": "Dirty Blonde", "#FF3F829D": "Dirty Blue", "#FFB5651E": "Dirty Brown", "#FF667E2C": "Dirty Green", "#FF430005": "Dirty Leather", "#FFDDD0B6": "Dirty Martini", "#FFC87606": "Dirty Orange", "#FFCA7B80": "Dirty Pink", "#FF734A65": "Dirty Purple", "#FFCDCED5": "Dirty Snow", "#FFE8E4C9": "Dirty White", "#FFCDC50A": "Dirty Yellow", "#FFBBDEE5": "Disappearing Island", "#FFEAE3E0": "Disappearing Memories", "#FF3F313A": "Disappearing Purple", "#FF006E9D": "Disarm", "#FF47C6AC": "Disc Jockey", "#FF892D4F": "Disco Variant", "#FFD4D4D4": "Disco Ball", "#FFBDB0A0": "Discover", "#FF4A934E": "Discover Deco", "#FF276878": "Discovery Bay", "#FFFFAD98": "Discreet Orange", "#FFF5E5E1": "Discreet Romance", "#FFDFDCDB": "Discreet White", "#FFEBDBDD": "Discrete Pink", "#FF9F6F62": "Discretion", "#FF5BB4D7": "Disembark", "#FFB7B698": "Disguise", "#FFED9190": "Dishy Coral", "#FFE2ECF2": "Dissolved Denim", "#FF566A73": "Distance", "#FF2C66A0": "Distant Blue", "#FFE5EAE6": "Distant Cloud", "#FFEAD1DA": "Distant Flare", "#FFDFE4DA": "Distant Haze", "#FFACDCEE": "Distant Homeworld", "#FFF1F8FA": "Distant Horizon", "#FFA68A71": "Distant Land", "#FFE1EFDD": "Distant Landscape", "#FFF2F3CE": "Distant Searchlight", "#FFC2D8E3": "Distant Shore", "#FF6F8DAF": "Distant Sky", "#FFBAC1C3": "Distant Star", "#FFCFBDA5": "Distant Tan", "#FF7F8688": "Distant Thunder", "#FFC2B79A": "Distant Valley", "#FFEAEFF2": "Distant Wind Chime", "#FFCCFFCC": "Distilled Moss", "#FFFFBBFF": "Distilled Rose", "#FFC7FDB5": "Distilled Venom", "#FFEDE3E0": "Distilled Watermelon", "#FFA294C9": "Distinct Purple", "#FF141513": "Distinctive Lack of Hue", "#FFF1E6CB": "Distressed White", "#FFFEB308": "Dithered Amber", "#FFBCDFFF": "Dithered Sky", "#FFC9A0FF": "Diva", "#FF0079C1": "Diva Blue", "#FFE1CBDA": "Diva Girl", "#FFB24E76": "Diva Glam", "#FFEE99EE": "Diva Mecha", "#FFFA427E": "Diva Pink", "#FFE8B9A5": "Diva Rouge", "#FF5077BA": "Diva Violet", "#FF3C4D85": "Dive In", "#FF27546E": "Diver Lady", "#FF3A797E": "Diver\u2019s Eden", "#FFA99A89": "Diversion", "#FF9A7AA0": "Divine", "#FFE3E2D5": "Divine Cream", "#FFEEDDEE": "Divine Dove", "#FFD8E2E1": "Divine Inspiration", "#FFF4EFE1": "Divine Pleasure", "#FF69005F": "Divine Purple", "#FFE6DCCD": "Divine White", "#FF583E3E": "Divine Wine", "#FF47B07F": "Divot", "#FFCD8431": "Dixie Variant", "#FFD14E2F": "Dizzy Days", "#FF363B43": "Do Not Disturb", "#FF4B3C39": "Dobunezumi Brown", "#FF95AED0": "Dockside", "#FFA0B3BC": "Dockside Blue", "#FF813533": "Dockside Red", "#FFF9F9F9": "Doctor", "#FFA37355": "Dodge Pole", "#FF3E82FC": "Dodger Blue Variant", "#FFF79A12": "DodgeRoll Gold", "#FFFEF65B": "Dodie Yellow", "#FFB68761": "Doe", "#FFBE9F85": "Doe Eyes", "#FFFFF2E4": "Doeskin", "#FFCCC3BA": "Doeskin Grey", "#FFE5C1D9": "Dog Rose", "#FFFAEAE2": "Dogwood", "#FFC58F94": "Dogwood Bloom", "#FFF0D9E0": "Dolce Pink", "#FFFACFC1": "Doll House", "#FF7D8774": "Dollar", "#FFF590A0": "Dollie", "#FFF8EBD4": "Dollop of Cream", "#FFF5F171": "Dolly Variant", "#FFFCC9B6": "Dolly Cheek", "#FFFEE8F5": "Dolomite Crystal", "#FFC5769B": "Dolomite Red", "#FF86C4DA": "Dolphin Variant", "#FFB7B8BF": "Dolphin at Play", "#FF659FB5": "Dolphin Daze", "#FF6B6F78": "Dolphin Dream", "#FFCCCAC1": "Dolphin Fin", "#FFC7C7C2": "Dolphin Tales", "#FF9C9C6E": "Domain", "#FF5A5651": "Dominant Grey", "#FF6C5B4C": "Domino Variant", "#FF5A4F51": "Don Juan Variant", "#FFED2C1A": "Don\u2019t Be Shy", "#FF115500": "Donegal Green", "#FFC19964": "Donegal Tweed", "#FFBB7766": "D\u00f6ner Kebab", "#FF816E5C": "Donkey Brown Variant", "#FFAB4210": "Donkey Kong", "#FF8CAEA3": "Donnegal", "#FFEDCCD6": "Donquixote Doflamingo", "#FFFBDCA8": "Doodle", "#FF7C1E08": "Doombull Brown", "#FF6E5F56": "Dorado Variant", "#FFACA79E": "Dorian Grey", "#FFD0888E": "Doric Pink", "#FFD5CFBD": "Doric White", "#FFAD947C": "Dormer Brown", "#FF5D71A9": "Dormitory", "#FFFFF200": "Dorn Yellow", "#FF9D2C31": "Dorset Naga", "#FF6C6868": "Dotted Dove", "#FF009276": "D\u00f2u L\u01dc Green", "#FFA52939": "D\u00f2u Sh\u0101 S\u00e8 Red", "#FFE998B1": "Double Bubble", "#FF6F5245": "Double Chocolate", "#FFD0D2D1": "Double Click", "#FFF2D9A3": "Double Cream", "#FF95BCE1": "Double Denim", "#FFFCA044": "Double Dragon", "#FF686858": "Double Duty", "#FF54423E": "Double Espresso", "#FF6D544B": "Double Fudge", "#FF6F676B": "Double Infinity", "#FF4D786C": "Double Jeopardy", "#FFA78C71": "Double Latte", "#FFE9DCBE": "Double Pearl Lusta Variant", "#FFECE3CF": "Double Scoop", "#FFD2C3A3": "Double Spanish White Variant", "#FFF6D0B6": "Dough Yellow", "#FFEDA057": "Doughnut", "#FF6F9881": "Douglas Fir Green", "#FF555500": "Douro", "#FFB3ADA7": "Dove", "#FF755D5B": "Dove Feather", "#FFE6E2D8": "Dove White", "#FFF4F2EA": "Dove\u2019s Wing", "#FFF0E9D8": "Dover Cliffs", "#FF848585": "Dover Grey", "#FFCCAF92": "Dover Plains", "#FF326AB1": "Dover Straits", "#FFAEC3C4": "Dover Surf", "#FFF1EBDD": "Dover White", "#FF908A83": "Dovetail", "#FF838C82": "Dowager", "#FFBAAFB9": "Down Dog", "#FFFFF9E7": "Down Feathers", "#FFFFDF2B": "Down on the Sunflower Valley", "#FFCBC0BA": "Down-Home", "#FF5C6242": "Down-to-Earth", "#FF887B67": "Downing Earth", "#FFCBBCA5": "Downing Sand", "#FF777F86": "Downing Slate", "#FFA6A397": "Downing Stone", "#FFCAAB7D": "Downing Straw", "#FF4A4B57": "Downing Street", "#FF58D332": "Download Progress", "#FF43718B": "Downpour", "#FF7D6A58": "Downtown Benny Brown", "#FFADAAA2": "Downtown Grey", "#FF001100": "Downwell", "#FF6FD2BE": "Downy Variant", "#FFFEAA66": "Downy Feather", "#FFEDE9E4": "Downy Fluff", "#FF803F3F": "Dozen Roses", "#FF78587D": "Dr Who", "#FF828344": "Drab", "#FF749551": "Drab Green", "#FF808101": "Drably Olive", "#FF2C3539": "Dracula Orchid", "#FFFF9922": "Dragon Ball", "#FF5DA99F": "Dragon Bay", "#FF9C3418": "Dragon Dance", "#FFD75969": "Dragon Fruit", "#FF9E0200": "Dragon Red", "#FF877732": "Dragon Well", "#FFB84048": "Dragon\u2019s Blood", "#FFD41003": "Dragon\u2019s Breath", "#FFFC4A14": "Dragon\u2019s Fire", "#FFE7E04E": "Dragon\u2019s Gold", "#FFD50C15": "Dragon\u2019s Lair", "#FFC189BC": "Dragonflies", "#FF314A76": "Dragonfly", "#FF45ABCA": "Dragonfly Blue", "#FF6241C7": "Dragonlord Purple", "#FFBBB0A4": "Drake Tooth", "#FF31668A": "Drake\u2019s Neck", "#FF1F5DA0": "Drakenhof Nightshade", "#FFA37298": "Drama Queen", "#FFB883B0": "Drama Violet", "#FF240093": "Dramatic Blue", "#FF991100": "Dramatical Red", "#FF4B4775": "Dramatist", "#FF6C7179": "Draw Your Sword", "#FFD7E6EE": "Dream Blue", "#FFEBE2E8": "Dream Dust", "#FFDFDAC1": "Dream Grass", "#FF35836A": "Dream Green", "#FFF7CF26": "Dream of Spring", "#FFD5DEC3": "Dream Seascape", "#FFFF77BB": "Dream Setting", "#FFEFDDE1": "Dream State", "#FF9B868D": "Dream Sunset", "#FFCC99EE": "Dream Vapour", "#FFA5B2A9": "Dreamcatcher", "#FF8AC2D6": "Dreaming Blue", "#FFABC1BD": "Dreaming of the Day", "#FFB5B1BF": "Dreamland", "#FFFFD29D": "Dreams of Peach", "#FFC6C5C5": "Dreamscape Grey", "#FF9632CE": "Dreamscape Purple", "#FFF5D5C2": "Dreamsicle", "#FFD6E5EA": "Dreamstress", "#FFCCC6D7": "Dreamweaver", "#FFB195E4": "Dreamy Candy Forest", "#FFD7EEE4": "Dreamy Clouds", "#FF594158": "Dreamy Heaven", "#FFE8C3D9": "Dreamy Memory", "#FFDFABCD": "Dreamy Pink", "#FFFFAD61": "Dreamy Sunset", "#FF3D9C96": "Dreamy Teal", "#FFE3D8D5": "Dreamy White", "#FFA6C2D0": "Drenched", "#FFC1D1E2": "Drenched Rain", "#FF0086BB": "Dresden Blue", "#FF8CA8C6": "Dresden Doll", "#FF8EA7B9": "Dresden Dream", "#FF2A3244": "Dress Blues", "#FFF4EBEF": "Dress Pink", "#FFFAC7BF": "Dress Up", "#FF714640": "Dressed", "#FFBB5057": "Dressed Variant", "#FFB89D9A": "Dressy Rose", "#FFB2ABA1": "Dreyfus", "#FF898973": "Dried Basil", "#FF8C854F": "Dried Bay Leaf", "#FF4B0101": "Dried Blood", "#FFB6BF7F": "Dried Caspia", "#FFD1B375": "Dried Chamomile", "#FFB5BDA3": "Dried Chervil", "#FF7B7D69": "Dried Chive", "#FFEBE7D9": "Dried Coconut", "#FF4A423A": "Dried Dates", "#FFB19F80": "Dried Edamame", "#FF752653": "Dried Flower Purple", "#FFE2A829": "Dried Goldenrod", "#FFACA08D": "Dried Grass", "#FF847A59": "Dried Herb", "#FFEBE9EC": "Dried Lavender", "#FF77747F": "Dried Lavender Flowers", "#FF5C5043": "Dried Leaf", "#FFBBBBFF": "Dried Lilac", "#FFFF40FF": "Dried Magenta", "#FFC6AD6F": "Dried Moss", "#FF804A00": "Dried Mustard", "#FFE1DBAC": "Dried Palm", "#FFD8D6CC": "Dried Pipe Clay", "#FFE5CEA9": "Dried Plantain", "#FF683332": "Dried Plum", "#FFC33E29": "Dried Saffron", "#FFBBBCA1": "Dried Thyme", "#FFA0883B": "Dried Tobacco", "#FFAB6057": "Dried Tomatoes", "#FFDCD8D0": "Drift of Mist", "#FFBEB4A8": "Drifting", "#FFDBE0E1": "Drifting Cloud", "#FF61736F": "Drifting Downstream", "#FFCCBBE3": "Drifting Dream", "#FFA89F93": "Drifting Sand", "#FFDFEFEB": "Drifting Tide", "#FF5B4276": "Drifting Violet", "#FFA67A45": "Driftwood Variant", "#FFB6C1C8": "Driftwood Blues", "#FFA6CCD6": "Drip", "#FF7A280A": "Drip Coffee", "#FFF4E7A5": "Dripping Cheese", "#FFD2DFED": "Dripping Ice", "#FFBB99BB": "Dripping Wisteria", "#FFEEBB22": "Drippy Honey", "#FFA24857": "Drisheen", "#FFA62E30": "Drive-In Cherry", "#FFA0AF9D": "Drizzle", "#FF989D9F": "Drizzling Mist", "#FF523839": "Dro\u00ebwors", "#FFE3C295": "Dromedary", "#FFCAAD87": "Dromedary Camel", "#FF69BD5A": "Drop Green", "#FFFCF1BD": "Drop of Lemon", "#FFAADDFF": "Droplet", "#FFBB3300": "Dropped Brick", "#FFD4AE76": "Drops of Honey", "#FFD5D1CC": "Drought", "#FFFBEB9B": "Drover Variant", "#FFD4DBE1": "Drowsy Lavender", "#FF842994": "Druchii Violet", "#FF427131": "Druid Green", "#FFA66E4B": "Drum Solo", "#FFE7D7B9": "Drumskin", "#FFDD11DD": "Drunk-Tank Pink", "#FF33DD88": "Drunken Dragonfly", "#FFFF55CC": "Drunken Flamingo", "#FFEADFCE": "Dry Bone", "#FF968374": "Dry Brown", "#FFB9BDAE": "Dry Catmint", "#FFBD5C00": "Dry Clay", "#FFD8C7B6": "Dry Creek", "#FF817665": "Dry Dock", "#FFEFDFCF": "Dry Dune", "#FFC6B693": "Dry Earth", "#FF9EA26B": "Dry Grass", "#FF909373": "Dry Hemlock", "#FF2BA727": "Dry Highlighter Green", "#FFCFBF8B": "Dry Leaf", "#FFC7D9CC": "Dry Lichen", "#FF769958": "Dry Moss", "#FF777672": "Dry Mud", "#FF948971": "Dry Pasture", "#FFDE7E5D": "Dry Peach", "#FFD2C5AE": "Dry Riverbed", "#FFC22F4D": "Dry Rose", "#FF8C8B76": "Dry Sage", "#FFEAE4D6": "Dry Sand", "#FFD4CECD": "Dry Season", "#FFC7DC68": "Dry Seedlings", "#FFB09A77": "Dry Starfish", "#FF33312D": "Dryad Bark", "#FFF25F66": "Dubarry", "#FFAE8B64": "Dubbin", "#FF73BE6E": "Dublin", "#FF6FAB92": "Dublin Jack", "#FFD5B688": "Dubloon", "#FF5B2C31": "Dubonnet", "#FF6F7766": "Dubuffet Green", "#FF763D35": "Ducal", "#FFCE9096": "Ducal Pink", "#FF16A0A6": "Ducati", "#FFD1DBC7": "Duchamp Light Green", "#FF9B909D": "Duchess Lilac", "#FFF7AA97": "Duchess Rose", "#FFDDC75B": "Duck Butter", "#FFC8E3D2": "Duck Egg Cream", "#FF53665C": "Duck Green", "#FF005800": "Duck Hunt", "#FFCC9922": "Duck Sauce", "#FFE9D6B1": "Duck Tail", "#FF6A695A": "Duck Willow", "#FFCCDFE8": "Duck\u2019s Egg Blue", "#FFFFFF11": "Duckie Yellow", "#FFFCB057": "Duckling", "#FFFAFC5D": "Duckling Fluff", "#FFAEACAC": "Duct Tape Grey", "#FF464E3F": "Duffel Bag", "#FF71706E": "Dugong", "#FFD6851F": "Dulce de Leche", "#FF9AD4D8": "Dulcet", "#FFF0E2E4": "Dulcet Pink", "#FF59394C": "Dulcet Violet", "#FFD6D4E6": "Dulcinea", "#FF727171": "Dull", "#FFD09C97": "Dull Apricot", "#FF49759C": "Dull Blue", "#FF876E4B": "Dull Brown", "#FFDCD6D3": "Dull Desert", "#FF8F6D73": "Dull Dusky Pink", "#FF8A6F48": "Dull Gold", "#FF74A662": "Dull Green", "#FFE5D9B4": "Dull Light Yellow", "#FF8D4856": "Dull Magenta", "#FF7D7485": "Dull Mauve", "#FF7A7564": "Dull Olive", "#FFD8863B": "Dull Orange", "#FFD5869D": "Dull Pink", "#FF84597E": "Dull Purple", "#FFBB3F3F": "Dull Red", "#FFDBD4AB": "Dull Sage", "#FF5F9E8F": "Dull Teal", "#FF557D73": "Dull Turquoise", "#FFEEDC5B": "Dull Yellow", "#FFF7DDAA": "Dumpling", "#FF80B4DC": "Dun Morogh Blue", "#FFD5C0A1": "Dune Variant", "#FFC3A491": "Dune Beige", "#FFB88D70": "Dune Drift", "#FFCBC5B1": "Dune Grass", "#FFE9AA71": "Dune King", "#FF867665": "Dune Shadow", "#FFE2BB88": "Dune View", "#FF514F4A": "Dunes Manor", "#FFD9ECE6": "Dunnock Egg", "#FF6E6064": "Duomo", "#FF58A0BC": "Dupain", "#FF442211": "Duqqa Brown", "#FF566777": "Durango Blue", "#FFFBE3A1": "Durango Dust", "#FFFFB978": "Durazno Maduro", "#FFFFD8BB": "Durazno Palido", "#FF5D8A9B": "Durban Sky", "#FFB07939": "Durian", "#FFE5E0DB": "Durian Smell", "#FFE6D0AB": "Durian White", "#FFE1BD27": "Durian Yellow", "#FFF06126": "Durotar Fire", "#FF4E5481": "Dusk", "#FFD2CAC8": "Dusk in the Valley", "#FF9A7483": "Dusk Wine", "#FF123D55": "Duskwood", "#FFC3ABA8": "Dusky", "#FF296767": "Dusky Alpine Blue", "#FF774400": "Dusky Brown", "#FFE1C779": "Dusky Citron", "#FF7D6D70": "Dusky Cyclamen", "#FFB98478": "Dusky Damask", "#FFE5E1DE": "Dusky Dawn", "#FF877F95": "Dusky Grape", "#FF7A705B": "Dusky Green", "#FF8E969E": "Dusky Grouse", "#FFA77572": "Dusky Haze", "#FFB2A09E": "Dusky Hyacinth", "#FFD6CBDA": "Dusky Lilac", "#FFA39B7E": "Dusky Meadows", "#FF979BA8": "Dusky Mood", "#FFEDECD7": "Dusky Moon", "#FFD5CBC3": "Dusky Morn", "#FF873E1A": "Dusky Orange", "#FFA07A89": "Dusky Orchid", "#FF95978F": "Dusky Parakeet", "#FFCC7A8B": "Dusky Pink", "#FF895B7B": "Dusky Purple", "#FFBA6873": "Dusky Rose", "#FFE1CEC7": "Dusky Sand", "#FFC9BDB7": "Dusky Taupe", "#FFD0BFBE": "Dusky Violet", "#FFFFFF05": "Dusky Yellow", "#FFB2996E": "Dust", "#FFE2D8D3": "Dust Bowl", "#FFC8B6A6": "Dust Bunny", "#FFC6C8BE": "Dust Green", "#FFCFC9DF": "Dust of the Moon", "#FFE7D3B7": "Dust Storm Variant", "#FFBBBCBC": "Dust Variant", "#FF959BA0": "Dustblu", "#FFCC7357": "Dusted Clay", "#FFBEA775": "Dusted Olive", "#FF696BA0": "Dusted Peri", "#FF9C8373": "Dusted Truffle", "#FFE7ECE8": "Dusting Powder", "#FF685243": "Dustwallow Marsh", "#FFBDDACA": "Dusty Aqua", "#FFBFB6A3": "Dusty Attic", "#FF8093A4": "Dusty Blue", "#FFF3C090": "Dusty Boots", "#FF9A7E68": "Dusty Canyon", "#FFDD9592": "Dusty Cedar", "#FF847163": "Dusty Chestnut", "#FF888899": "Dusty Chimney", "#FFD29B83": "Dusty Coral", "#FF97A2A0": "Dusty Dream", "#FFB18377": "Dusty Duchess", "#FFD7B999": "Dusty Gold", "#FF76A973": "Dusty Green", "#FFCDCCD0": "Dusty Grey", "#FF8990A3": "Dusty Heather", "#FFF1DDBE": "Dusty Ivory", "#FF71AF98": "Dusty Jade Green", "#FFAC86A8": "Dusty Lavender", "#FFD3CACD": "Dusty Lilac", "#FF716D63": "Dusty Mountain", "#FF676658": "Dusty Olive", "#FFE16D4F": "Dusty Orange", "#FF8C7763": "Dusty Path", "#FFD58A94": "Dusty Pink", "#FFD7D0E1": "Dusty Plum", "#FF825F87": "Dusty Purple", "#FFB9484E": "Dusty Red", "#FFB56F76": "Dusty Rose", "#FFC0AA9F": "Dusty Rosewood", "#FFBDBAAE": "Dusty Sand", "#FF95A3A6": "Dusty Sky", "#FF4C9085": "Dusty Teal", "#FFC9BBA3": "Dusty Trail", "#FFC3B9A6": "Dusty Trail Rider", "#FF598A8F": "Dusty Turquoise", "#FFBAB7B3": "Dusty Warrior", "#FFCFC88F": "Dusty Yellow", "#FF4E6594": "Dutch Blue", "#FF8C706A": "Dutch Cocoa", "#FFA5ABB6": "Dutch Jug", "#FF404B56": "Dutch Licorice", "#FFDFA837": "Dutch Orange", "#FF9AABAB": "Dutch Tile Blue", "#FFF0DFBB": "Dutch White", "#FFC9A7AC": "Dutchess Dawn", "#FF0F8B8E": "Duvall", "#FF1D0200": "Dwarf Fortress", "#FFAF7B57": "Dwarf Pony", "#FFC8AC89": "Dwarf Rabbit", "#FF71847D": "Dwarf Spruce", "#FFBF652E": "Dwarven Bronze", "#FFEFDFE7": "Dwindling Damon", "#FFF9E9D6": "Dwindling Dandelion", "#FFCCE1EE": "Dwindling Denim", "#FF7B99B0": "Dyer\u2019s Woad", "#FF364141": "Dying Light", "#FF669C7D": "Dying Moss", "#FF111166": "Dying Storm Blue", "#FFD5B266": "Dylan Velvet", "#FF6D5160": "Dynamic", "#FF1F1C1D": "Dynamic Black", "#FF0192C6": "Dynamic Blue", "#FFA7E142": "Dynamic Green", "#FF8A547F": "Dynamic Magenta", "#FFFFE36D": "Dynamic Yellow", "#FFFF4422": "Dynamite", "#FFDD3311": "Dynamite Red", "#FF953D68": "Dynamo", "#FFC7CBBE": "Dynasty Celadon", "#FFF8D77F": "E. Honda Beige", "#FFA26C36": "Eagle Variant", "#FF736665": "Eagle Eye", "#FF7D776C": "Eagle Ridge", "#FF5C5242": "Eagle Rock", "#FF8D7D68": "Eagle\u2019s Meadow", "#FFD4CBCC": "Eagle\u2019s View", "#FF8A693F": "Eagles Nest", "#FFE9D9C0": "Eaglet Beige", "#FF466B82": "Eames for Blue", "#FF416659": "Earhart Emerald", "#FF747A64": "Earl Green", "#FFA6978A": "Earl Grey", "#FFB8A722": "Earls Green Variant", "#FFFFE5ED": "Early Blossom", "#FFEAE7E7": "Early Crocus", "#FF797287": "Early Dawn Variant", "#FF44AA00": "Early Dew", "#FFD3D6E9": "Early Dog Violet", "#FFCAC7BF": "Early Evening", "#FFBAE5EE": "Early Forget-Me-Not", "#FFDAE3E9": "Early Frost", "#FFB9BE82": "Early Harvest", "#FFA5DDEA": "Early July", "#FFB1D2DF": "Early June", "#FFADCDDC": "Early September", "#FFFDF3E4": "Early Snow", "#FF96BC4A": "Early Spring", "#FF3C3FB1": "Early Spring Night", "#FFE6D7A0": "Early Sprout", "#FFF3E3D8": "Early Sunset", "#FFA2653E": "Earth", "#FF49433B": "Earth Black", "#FF4F1507": "Earth Brown", "#FF705364": "Earth Brown Violet", "#FFC7AF88": "Earth Chi", "#FF664B40": "Earth Chicory", "#FF8C4F42": "Earth Crust", "#FF71BAB4": "Earth Eclipse", "#FF785240": "Earth Fired Red", "#FFC6BA92": "Earth Friendly", "#FF545F5B": "Earth Green", "#FFE3EDC8": "Earth Happiness", "#FFB57770": "Earth Rose", "#FFA06E57": "Earth Tone", "#FFBF9F91": "Earth Warming", "#FFA48A80": "Earthbound", "#FF667971": "Earthen Cheer", "#FFA85E39": "Earthen Jug", "#FFD1AC71": "Earthen Sienna", "#FFA89373": "Earthenware", "#FFDED6C7": "Earthling", "#FFAB8A68": "Earthly Delight", "#FF693C3B": "Earthly Pleasures", "#FF9D8675": "Earthnut", "#FF8C7553": "Earthquake", "#FFC3816E": "Earthworm", "#FFB7A996": "Earthy Beige", "#FFC5B28B": "Earthy Cane", "#FF666600": "Earthy Khaki Green", "#FFB89E78": "Earthy Ochre", "#FFFAE3E3": "Eased Pink", "#FFB29D8A": "Easily Suede", "#FF878B46": "East Aurora", "#FF47526E": "East Bay Variant", "#FFB0EEE2": "East Cape", "#FFF8E587": "East of the Sun", "#FFAA8CBC": "East Side Variant", "#FFEBE5EB": "Easter Bunny", "#FF8E97C7": "Easter Egg", "#FF8CFD7E": "Easter Green", "#FFE6E1E2": "Easter Orchid", "#FFC071FE": "Easter Purple", "#FFC7BFC3": "Easter Rabbit", "#FFEBB67E": "Eastern Amber", "#FF5E5D3D": "Eastern Bamboo", "#FF00879F": "Eastern Blue Variant", "#FF748695": "Eastern Bluebird", "#FFDAE0E6": "Eastern Breeze", "#FFB89B6C": "Eastern Gold", "#FF8FC1D2": "Eastern Sky", "#FFDBA87F": "Eastern Spice", "#FFEDE6D5": "Eastern Wind", "#FF7C6042": "Eastlake", "#FFC28E61": "Eastlake Gold", "#FF887D79": "Eastlake Lavender", "#FFA9A482": "Eastlake Olive", "#FFBEB394": "Easy", "#FFAACDD8": "Easy Breezy", "#FF9EB1AE": "Easy Breezy Blue", "#FF9FB289": "Easy Green", "#FFF9ECB6": "Easy on the Eyes", "#FF98B389": "Easygoing Green", "#FF696845": "Eat Your Greens", "#FF80987A": "Eat Your Peas", "#FFBB9F60": "Eaton Gold", "#FFE4BBD1": "Eau de Rose", "#FFCECDAD": "Eaves", "#FFE6D8D4": "Ebb Variant", "#FF7893A0": "Ebb Tide", "#FF688D8A": "Ebbing Tide", "#FF773C30": "Ebi Brown", "#FF5E2824": "Ebicha Brown", "#FF6D2B50": "Ebizome Purple", "#FF313337": "Ebony Tint", "#FF323438": "Ebony Clay Variant", "#FF4A4741": "Ebony Keys", "#FFB06A40": "Ebony Lips", "#FF2C3227": "Ebony Wood", "#FFFFFFEE": "Eburnean", "#FFB576A7": "Eccentric Magenta", "#FF968A9F": "Eccentricity", "#FFE7D8BE": "Echelon Ecru", "#FFFFA565": "Echinoderm", "#FFFFA756": "Echinoidea Thorns", "#FFD7E7E0": "Echo", "#FFB6DFF4": "Echo Iris", "#FF95B5DB": "Echo Isles Water", "#FFD8DFDF": "Echo Mist", "#FF629DA6": "Echo One", "#FF758883": "Echo Park", "#FFE6E2D6": "Echo Valley", "#FFEEDEDD": "Echoes of Love", "#FF7E4930": "\u00c9clair au Chocolat", "#FFAAAFBD": "Eclectic", "#FF8C6E67": "Eclectic Plum", "#FF483E45": "Eclectic Purple", "#FF3F3939": "Eclipse Variant", "#FF456074": "Eclipse Blue", "#FF1F2133": "Eclipse Elixir", "#FFA89768": "Eco Green", "#FF677F70": "Ecological", "#FFA8916C": "Ecosystem", "#FFA48D83": "Ecru Ochre", "#FFA08942": "Ecru Olive", "#FFD5CDB4": "Ecru Wealth", "#FFD6D1C0": "Ecru White Variant", "#FFC96138": "Ecstasy Variant", "#FFAA1122": "Ecstatic Red", "#FFFFFF7E": "Ecuadorian Banana", "#FF9CA389": "Edamame", "#FFEEE8D9": "Edelweiss", "#FF266255": "Eden Variant", "#FF95863C": "Eden Prairie", "#FF54585E": "Edge of Black", "#FF330044": "Edge of Space", "#FF303D3C": "Edge of the Galaxy", "#FFC1D8C5": "Edgewater Variant", "#FFB1975F": "Edgy Gold", "#FFBF9A67": "Edgy Green", "#FFBA3D3C": "Edgy Red", "#FFA13D2D": "Edocha", "#FFF6EDE0": "Edwardian Lace", "#FFCC9DA4": "Edwardian Rose", "#FFA9D6BA": "Eerie Glow", "#FFFBF4D1": "Effervescent", "#FF00315A": "Effervescent Blue", "#FF98DA2C": "Effervescent Lime", "#FF01FF07": "EGA Green", "#FFC1E7EB": "Egg Blue", "#FFFFD98C": "Egg Cream", "#FFDCCAA8": "Egg Liqueur", "#FFF1E3BD": "Egg Noodle", "#FFF9E4C5": "Egg Sour Variant", "#FFF2C911": "Egg Toast", "#FFE2E1C8": "Egg Wash", "#FFFFCE81": "Egg Yolk", "#FFFFDB0B": "Egg Yolk Sunrise", "#FFFDEA9F": "Eggnog", "#FF430541": "Eggplant Variant", "#FF656579": "Eggplant Ash", "#FF531B93": "Eggplant Tint", "#FFA3CCC9": "Eggshell Blue", "#FFF5EEDB": "Eggshell Cream", "#FFE2BE9F": "Eggshell Paper", "#FFBEA582": "Eggshell Pongee", "#FFF3E4DC": "Eggshell White", "#FFF4ECE1": "Egret", "#FFDFD9CF": "Egret White", "#FF005C69": "Egyptian Enamel", "#FFEFA84C": "Egyptian Gold", "#FF08847C": "Egyptian Green", "#FF7A4B3A": "Egyptian Jasper", "#FFFEBCAD": "Egyptian Javelin", "#FF70775C": "Egyptian Nile", "#FFC19A7D": "Egyptian Pyramid", "#FF983F4A": "Egyptian Red", "#FFBEAC90": "Egyptian Sand", "#FF008C8D": "Egyptian Teal", "#FFD6B378": "Egyptian Temple", "#FF3D496D": "Egyptian Violet", "#FFE5F1EC": "Egyptian White", "#FFE1DED7": "Eider White", "#FFE6DBC6": "Eiderdown", "#FF998E83": "Eiffel Tower", "#FF16161D": "Eigengrau", "#FF7799BB": "Eiger Nordwand", "#FF03050A": "Eight Ball", "#FF552299": "Eine Kleine Nachtmusik", "#FFD2BE9D": "Eire", "#FFB7A696": "El Capitan", "#FF946E48": "El Caramelo", "#FFA97F3C": "El Dorado", "#FFD0CACD": "El Ni\u00f1o", "#FF39392C": "El Paso Variant", "#FF8F4E45": "El Salva Variant", "#FFDAC4A9": "Elafonisi Beach", "#FFECA6CA": "Elastic Pink", "#FFDFDCE5": "Elation", "#FFECC083": "Eldar", "#FFED8A09": "Elden Ring Orange", "#FFAFA892": "Elder Creek", "#FF2E2249": "Elderberry", "#FF1E323B": "Elderberry Black", "#FFAEA8B0": "Elderberry Grey", "#FFEAE5CF": "Elderberry White", "#FFFBF9E8": "Elderflower", "#FF40373E": "Eleanor Ann", "#FF110320": "Election Night", "#FF55B492": "Electra", "#FFF7F7CB": "Electric Arc", "#FFFBFF00": "Electric Banana", "#FFE23D2A": "Electric Blood", "#FFB56257": "Electric Brown", "#FF0FF0FC": "Electric Cyan", "#FF88BBEE": "Electric Eel", "#FFC9E423": "Electric Energy", "#FFFC74FD": "Electric Flamingo", "#FFFFD100": "Electric Glow", "#FF21FC0D": "Electric Green", "#FF6600FF": "Electric Indigo Variant", "#FF26FF2A": "Electric Laser Lime", "#FFF4BFFF": "Electric Lavender", "#FF89DD01": "Electric Leaf", "#FF5CDCF1": "Electric Lemonade", "#FF7BD181": "Electric Lettuce", "#FFF2E384": "Electric Light Orchestra", "#FFFF3503": "Electric Orange Variant", "#FFCD00FE": "Electric Orchid", "#FF00FF04": "Electric Pickle", "#FFFF0490": "Electric Pink", "#FFE60000": "Electric Red", "#FF55FFFF": "Electric Sheep", "#FF909FBA": "Electric Sky", "#FF9DB0B9": "Electric Slide", "#FF8F00F1": "Electric Violet Variant", "#FFFFFC00": "Electric Yellow", "#FF5665A0": "Electrify", "#FFD41C4E": "Electrifying Kiss", "#FF89D1CB": "Electro Chill", "#FF2E3840": "Electromagnetic", "#FF0881D1": "Electron Blue", "#FF556D88": "Electronic", "#FFE7C697": "Electrum", "#FFAC8E73": "Elegant Earth", "#FFC4B9B7": "Elegant Ice", "#FFF1E6D6": "Elegant Ivory", "#FFFDCACA": "Elegant Light Rose", "#FF5500BB": "Elegant Midnight", "#FF48516A": "Elegant Navy", "#FF552367": "Elegant Purple Gown", "#FF217F74": "Elegant Silk", "#FFF5F0E1": "Elegant White", "#FFD0D3D3": "Elemental", "#FF969783": "Elemental Green", "#FFA0A09F": "Elemental Grey", "#FFCAB79C": "Elemental Tan", "#FF817162": "Elephant Variant", "#FF9E958D": "Elephant Cub", "#FF988F85": "Elephant Ear", "#FF95918C": "Elephant Grey", "#FFA8A9A8": "Elephant in the Room", "#FF88817A": "Elephant Skin", "#FFC0E8D8": "Elephant Toothpaste", "#FFB3C3D4": "Elevated", "#FFB5CED5": "Elevation", "#FF1B8A6B": "Elf", "#FFF7C985": "Elf Cream", "#FF68B082": "Elf Shoe", "#FFA6A865": "Elf Slippers", "#FF9DD196": "Elfin Games", "#FFCAB4D4": "Elfin Herb", "#FFEDDBE9": "Elfin Magic", "#FFEBE885": "Elfin Yellow", "#FFD8D7B9": "Elise", "#FF1B3053": "Elite Blue", "#FF133700": "Elite Green", "#FFBB8DA8": "Elite Pink", "#FF133337": "Elite Teal", "#FF987FA9": "Elite Wisteria", "#FFF4D7DC": "Eliza Jane", "#FFA1B8D2": "Elizabeth Blue", "#FFFADFD2": "Elizabeth Rose", "#FFA58D72": "Elk", "#FFCABCA4": "Elk Antler", "#FFE9E2D3": "Elk Horn", "#FFEAE6DC": "Elk Skin", "#FF897269": "Elkhound", "#FFE2C8B7": "Ellen", "#FFAAA9A4": "Ellie Grey", "#FFADB6BD": "Elliott Bay", "#FF778070": "Ellis Mist", "#FF297B76": "Elm Variant", "#FFB25B09": "Elm Brown Red", "#FF577657": "Elm Green", "#FF264066": "Elmer\u2019s Echo", "#FF8C7C61": "Elmwood", "#FFFFE8AB": "Elote", "#FFD2CFCC": "Elusion", "#FFFED7CF": "Elusive", "#FFDDE4E8": "Elusive Blue", "#FFD5BFAD": "Elusive Dawn", "#FFCDBFC6": "Elusive Dream", "#FFDEC4D2": "Elusive Mauve", "#FFD4C0C5": "Elusive Violet", "#FFE8E3D6": "Elusive White", "#FFF7CF8A": "Elven Beige", "#FF6CA024": "Elven Olympics", "#FF7A8716": "Elwynn Forest Olive", "#FF9AAE07": "Elysia Chlorotica", "#FFA5B145": "Elysian Green", "#FFCE9500": "Elysium Gold", "#FFB4A3BB": "Emanation", "#FF5D4643": "Embarcadero", "#FFEE7799": "Embarrassed", "#FF996611": "Embarrassed Frog", "#FFBBAAA5": "Embarrassed Shadow", "#FFFF7777": "Embarrassment", "#FF8BC7C8": "Embellished Blue", "#FFCBDEE2": "Embellishment", "#FF792445": "Ember Red", "#FFEA6759": "Emberglow", "#FFE8B8A7": "Embrace", "#FF246453": "Embracing", "#FFB8DCA7": "Embroidered Silk", "#FFD4BEBF": "Embroidery", "#FF028F1E": "Emerald Variant", "#FF4CBDAC": "Emerald Bliss", "#FF6A7E5F": "Emerald City", "#FF4F8129": "Emerald Clear Green", "#FF009185": "Emerald Coast", "#FF52C170": "Emerald Cory", "#FF007A5E": "Emerald Dream", "#FF018B79": "Emerald Enchantment", "#FF224347": "Emerald Forest", "#FF66BB00": "Emerald Glitter", "#FF046307": "Emerald Green", "#FF2AF589": "Emerald Ice Palace", "#FF019157": "Emerald Isle", "#FF069261": "Emerald Lake", "#FF00A267": "Emerald Light Green", "#FF67A195": "Emerald Oasis", "#FF155E60": "Emerald Pool", "#FF80C872": "Emerald Rain", "#FF578758": "Emerald Ring", "#FF50C777": "Emerald Rush", "#FF78944A": "Emerald Shimmer", "#FF095155": "Emerald Spring", "#FF11BB11": "Emerald Starling", "#FF016360": "Emerald Stone", "#FF55AAAA": "Emerald Succulent", "#FF2B5F3C": "Emerald Temple", "#FF4FB3A9": "Emerald Wave", "#FF2B8478": "Emerald Whispers", "#FF5C6B8F": "Emerald-Crested Manakin", "#FF911911": "Emergency", "#FFE36940": "Emergency Zone", "#FFE1E1CF": "Emerging Leaf", "#FFB8A196": "Emerging Taupe", "#FF3E6058": "Emerson", "#FFCAA78C": "Emery Board", "#FF6E3974": "Eminence Variant", "#FF7A6841": "Eminent Bronze", "#FFFFDE34": "Emoji Yellow", "#FFC65F47": "Emotional", "#FF856D70": "Emotive Ring", "#FF79573A": "Emperador", "#FF50494A": "Emperor Variant", "#FFAC2C32": "Emperor Cherry Red", "#FF007B75": "Emperor Jade", "#FF715A8D": "Emperor Jewel", "#FFF0A0B6": "Emperor\u2019s Children", "#FFB0976D": "Emperor\u2019s Gold", "#FF99959D": "Emperor\u2019s Robe", "#FF00816A": "Emperor\u2019s Silk", "#FF858070": "Empire", "#FF6193B4": "Empire Blue", "#FFC19F6E": "Empire Gold", "#FFE0DBD3": "Empire Porcelain", "#FF93826A": "Empire Ranch", "#FFE7C5C1": "Empire Rose", "#FFD9DBDF": "Empire State Grey", "#FF9264A2": "Empire Violet", "#FFF7D000": "Empire Yellow", "#FFB54644": "Empower", "#FF7C7173": "Empress Variant", "#FF2A9CA0": "Empress Envy", "#FFC7DEED": "Empress Lila", "#FF10605A": "Empress Teal", "#FFFCFDFC": "Emptiness", "#FF756E6D": "Emu", "#FF3D8481": "Emu Egg", "#FFD0C5BE": "En Plein Air", "#FF427F85": "Enamel Antique Green", "#FF00758E": "Enamel Blue", "#FFBACCA8": "Enamel Green", "#FF54C589": "Enamelled Dragon", "#FF045C61": "Enamelled Jewel", "#FFC67D84": "Enamoured", "#FFF7EBD6": "Encaje Aperlado", "#FFFD0202": "Encarnado", "#FFD1C6D2": "Enchant", "#FFC9E2CF": "Enchanted", "#FF047495": "Enchanted Blue", "#FF7ED89A": "Enchanted Emerald", "#FF79837F": "Enchanted Eve", "#FFD3E9EC": "Enchanted Evening", "#FF5C821A": "Enchanted Forest", "#FF166D29": "Enchanted Glen", "#FF8EC5C0": "Enchanted Isle", "#FFBFA3D9": "Enchanted Lavender", "#FFA893C1": "Enchanted Lilac", "#FFB1D4B7": "Enchanted Meadow", "#FF2D4A71": "Enchanted Navy", "#FFB5B5BD": "Enchanted Silver", "#FF3994AF": "Enchanted Well", "#FF94895F": "Enchanted Wood", "#FF82BADF": "Enchanting", "#FFAC7435": "Enchanting Ginger", "#FF315955": "Enchanting Ivy", "#FF276DD6": "Enchanting Sapphire", "#FF7886AA": "Enchanting Sky", "#FF5D3A47": "Enchantress", "#FF6D7383": "Encore", "#FF30525B": "Encore Teal", "#FFFF9552": "Encounter", "#FFCC8F15": "End of Summer", "#FFD2EED6": "End of the Rainbow", "#FFFFD8A1": "Endearment", "#FF29598B": "Endeavour Variant", "#FF8B6F64": "Ending Autumn", "#FFFCC9B9": "Ending Dawn", "#FF1C305C": "Ending Navy Blue", "#FFA9AF99": "Ending Spring", "#FFCEE1C8": "Endive", "#FF5B976A": "Endless", "#FF9DCDE5": "Endless Blue", "#FF000044": "Endless Galaxy", "#FFB1DBF5": "Endless Horizon", "#FF567AAD": "Endless River", "#FF32586E": "Endless Sea", "#FFDDDDBB": "Endless Silk", "#FFCAE3EA": "Endless Sky", "#FFB1AAB3": "Endless Slumber", "#FFF7CF00": "Endless Summer", "#FF5DA464": "Endo", "#FF586683": "Enduring", "#FF554C3E": "Enduring Bronze", "#FFEBE8DB": "Enduring Ice", "#FFD85739": "Energetic Orange", "#FFF3C6CC": "Energetic Pink", "#FFB300B3": "Energic Eggplant", "#FF7CCA6B": "Energise", "#FFD2D25A": "Energised", "#FFC0E740": "Energos", "#FFFF9532": "Energy Orange", "#FFBB5F60": "Energy Peak", "#FFF5D752": "Energy Yellow Variant", "#FFA73211": "Enfield Brown", "#FFC2C6C0": "Engagement Silver", "#FFA17548": "English Bartlett", "#FF441111": "English Breakfast", "#FF4E6173": "English Channel", "#FFCBD3E6": "English Channel Fog", "#FFC64A55": "English Coral", "#FFE2B66C": "English Custard", "#FFFFCA46": "English Daisy", "#FF606256": "English Forest", "#FF1B4D3F": "English Green", "#FF274234": "English Holly Variant", "#FFB5C9D2": "English Hollyhock", "#FF689063": "English Ivy", "#FF9D7BB0": "English Lavender", "#FF7181A4": "English Manor", "#FF028A52": "English Meadow", "#FF3C768A": "English River", "#FFF4C6C3": "English Rose", "#FFE9C9CB": "English Rosebud", "#FF8E6947": "English Saddle", "#FFE9CFBB": "English Scone", "#FF563D5D": "English Violet", "#FFF5F3E9": "Engulfed in Light", "#FFD2A5BE": "Enhance", "#FFBDBF35": "Enigma", "#FF7E7275": "Enigmatic", "#FFEAD4C4": "Enjoy", "#FFF5D6A9": "Enjoyable Yellow", "#FFE3EAD6": "Enlightened Lime", "#FFF8FAEE": "Enoki", "#FFFFEEDD": "Enokitake Mushroom", "#FF898C4A": "Enough Is Enough", "#FFEE0044": "Enraged", "#FFCB6649": "Ensh\u016bcha Red", "#FF3C4F6E": "Ensign Blue", "#FFEC6D51": "Entan Red", "#FF65788C": "Enterprise", "#FFAC92B0": "Enthroned Above", "#FF00FFAA": "Enthusiasm", "#FFB74E4F": "Enticing Red", "#FFD5B498": "Entrada Sandstone", "#FF005961": "Entrapment", "#FF53983C": "Enviable", "#FFDDF3C2": "Envious Pastel", "#FFB1B5A0": "Environmental", "#FF006C4B": "Environmental Green", "#FF88BB00": "Environmental Study", "#FF96BFB7": "Envisage", "#FF8BA58F": "Envy Variant", "#FF2DD78D": "Envy\u2019s Love", "#FFD4D3C9": "Eon", "#FFFF5EC4": "Eosin Pink", "#FF7A6270": "Ephemera", "#FFCBD4DF": "Ephemeral Blue", "#FFD6CED3": "Ephemeral Fog", "#FFC7CDD3": "Ephemeral Mist", "#FFFCE2D4": "Ephemeral Peach", "#FFE4CFD7": "Ephemeral Red", "#FF0E426F": "Epic Adventure", "#FF0066EE": "Epic Blue", "#FF8D8D75": "Epic Green", "#FFEA6A0A": "Epicurean Orange", "#FFAB924B": "Epidote Olvene Ore", "#FF4BB2D5": "Epimetheus", "#FFDD33FF": "Epink", "#FFDBC1DE": "Epiphany", "#FF829161": "Epsom", "#FF83A9B3": "Equanimity", "#FFDAB160": "Equator Variant", "#FFFFE6A0": "Equator Glow", "#FFFFCE84": "Equatorial", "#FF70855E": "Equatorial Forest", "#FFBEA781": "Equestrian", "#FF54654F": "Equestrian Green", "#FF5B5652": "Equestrian Leather", "#FFA07569": "Equestrienne", "#FFA49F9F": "Equilibrium", "#FF62696B": "Equinox", "#FFD7E3E5": "Era", "#FF060030": "Erebus Blue", "#FF836B4F": "Ermine", "#FFC84F68": "Eros Pink", "#FFDDD1BF": "Erosion", "#FFF2F2F4": "Errigal White", "#FF4A0505": "Erythrophobia", "#FFFC7AB0": "Erythrosine", "#FFA95F5C": "Escalante", "#FFCC8866": "Escalope", "#FFB89B59": "Escapade Gold", "#FFA8CDE4": "Escape", "#FFABAC9F": "Escape Grey", "#FFD5B79B": "Escarpment", "#FF4A4F52": "Eshin Grey", "#FF45BE76": "Esmeralda", "#FFC4B5A4": "Esoteric", "#FFABEDC9": "Esoteric Touch Green", "#FF2F5F3A": "Espalier", "#FF80F9AD": "Esper\u2019s Fungus Green", "#FFD5BDA4": "Esplanade", "#FF4E312D": "Espresso Variant", "#FF5B3F34": "Espresso Bar", "#FF4C443E": "Espresso Beans", "#FFD09C43": "Espresso Crema", "#FF4F4744": "Espresso Macchiato", "#FF8C3A00": "Espresso Martini", "#FFC18B5B": "Espresso Tonic", "#FFBEBD99": "Esprit", "#FFFFC49D": "Esprit Peach", "#FF7D6848": "Essential Brown", "#FFBCB8B6": "Essential Grey", "#FF007377": "Essential Teal", "#FFFFDE9F": "Essentially Bright", "#FFB0CCDA": "Essex Blue", "#FFE2EDDD": "Establish Mint", "#FFDCCDB4": "Estate Limestone", "#FFF3E0C6": "Estate Vanilla", "#FF68454B": "Estate Vineyard", "#FFC3C1D2": "Estate Violet", "#FF54A9CA": "Estero Sky", "#FFA5AF76": "Estragon", "#FF9B101F": "Estroruby", "#FF70A5B7": "Estuary Blue", "#FFE1C6D4": "Etcetera", "#FFDDE2E2": "Etched Glass", "#FFDD0044": "Eternal Cherry", "#FFB3C3DD": "Eternal Elegance", "#FFA13F49": "Eternal Flame", "#FFD6C9D5": "Eternal Moonrise", "#FFF7E504": "Eternal Summer", "#FFFAF3DC": "Eternal White", "#FF9CFAFF": "Eternal Winter", "#FF2D2F28": "Eternity Variant", "#FF98B2B4": "Ether", "#FFA3928C": "Etherea", "#FFF9EECB": "Ethereal", "#FF5CA6CE": "Ethereal Blue", "#FF1A4A5A": "Ethereal Dance", "#FF3E2723": "Ethereal Espresso", "#FFF0E8C6": "Ethereal Green", "#FFB0B8CC": "Ethereal Mist", "#FFCCE7EB": "Ethereal Mood", "#FFD5E4EC": "Ethereal Moonlight", "#FFDEDEB6": "Ethereal One", "#FFE6F1F1": "Ethereal White", "#FF3E5E4E": "Ethereal Woods", "#FFB9C4DE": "Etherium Blue", "#FF968777": "Ethiopia", "#FF985629": "Ethiopian Wolf", "#FFAAD4D1": "Eton Blue Variant", "#FF9B583C": "Etruscan", "#FFBB986F": "Etruscan Bronze", "#FFC9303E": "Etruscan Red", "#FFD5D2D7": "Etude Lilac", "#FF55BB11": "\u00c9tude Naturelle", "#FF4BC3A8": "Eucalipto", "#FF329760": "Eucalyptus Variant", "#FF1E675A": "Eucalyptus Green", "#FFBAD2B8": "Eucalyptus Leaf", "#FF88927E": "Eucalyptus Wreath", "#FFF2E8D4": "Eugenia", "#FFED3D66": "Eugenia Red", "#FFCDA59C": "Eunry Variant", "#FFBF36E0": "Eupatorium Purple", "#FFEEBBFF": "Euphoria", "#FFDAC7DA": "Euphoric Lilac", "#FF7F576D": "Euphoric Magenta", "#FF006796": "Europe Blue", "#FF756556": "European Pine", "#FF36FF9A": "Eva Green", "#FFD1D5D3": "Evaporation", "#FF998371": "Even Evan", "#FFB2AA7A": "Even Growth", "#FF2B2B41": "Evening Blue", "#FFC49087": "Evening Blush", "#FF454341": "Evening Canyon", "#FF4B535C": "Evening Cityscape", "#FF8E6B76": "Evening Crimson", "#FFC2BEAD": "Evening Dove", "#FFD1A19B": "Evening Dress", "#FF585E6A": "Evening East", "#FF8697A0": "Evening Eclipse", "#FF56736F": "Evening Emerald", "#FF4D4970": "Evening Fizz", "#FF8C9997": "Evening Fog", "#FF3A4A62": "Evening Glory", "#FFFDD792": "Evening Glow", "#FF7C7A3A": "Evening Green", "#FFB7B2C2": "Evening Haze", "#FF7B8CA8": "Evening Hush", "#FF938F9F": "Evening in Paris", "#FF5868AE": "Evening Lagoon", "#FF4D4469": "Evening Lavender", "#FF363E7D": "Evening Magic", "#FF463F67": "Evening Mauve", "#FFE3E9E8": "Evening Mist", "#FF434D66": "Evening Over the Ocean", "#FFA7879A": "Evening Pink", "#FFC2D61D": "Evening Primrose", "#FFDDB3AB": "Evening Sand", "#FF26604F": "Evening Sea Variant", "#FFA1838B": "Evening Shadow", "#FFA99EC1": "Evening Slipper", "#FFFFD160": "Evening Star", "#FF465058": "Evening Storm", "#FFFDDFB8": "Evening Sun", "#FFEDB06D": "Evening Sunset", "#FF51637B": "Evening Symphony", "#FFD8DBD7": "Evening White", "#FF656470": "Eventide", "#FFF0C8B6": "Everblooming", "#FFA0E3E0": "Everest", "#FF264334": "Everglade Variant", "#FF25464D": "Everglade Deck", "#FFB8C17E": "Everglade Glen", "#FFB7D7DE": "Everglade Mist", "#FF125B49": "Evergreen", "#FF535C55": "Evergreen Bough", "#FF47534F": "Evergreen Field", "#FF95978A": "Evergreen Fog", "#FF0E695F": "Evergreen Forest", "#FF045E17": "Evergreen Shade", "#FF6F7568": "Evergreen Trail", "#FFA1BED9": "Everlasting", "#FFF6FDFA": "Everlasting Ice", "#FF949587": "Everlasting Sage", "#FF463E3B": "Evermore", "#FFFFA62D": "Eversong Orange", "#FFE4DCD4": "Everyday White", "#FFD8ACA0": "Everything\u2019s Rosy", "#FFAA2211": "Evil Centipede", "#FF522000": "Evil Cigar", "#FF884488": "Evil Curse", "#FF1100CC": "Evil Eye", "#FF770022": "Evil Forces", "#FFC2191F": "Evil Sunz Scarlet", "#FFFED903": "Evil-Lyn", "#FF9DBCC7": "Evocative Blue", "#FF704A3D": "Evolution", "#FF538B89": "Evora", "#FF454042": "Ewa", "#FFF0EDDD": "Ewe", "#FF676168": "Excalibur", "#FF22606E": "Excellence", "#FF908B85": "Excelsior", "#FFCCA65B": "Excitable", "#FFF0B07A": "Exciting Orange", "#FFF9F1DD": "Exclusive Elixir", "#FF38493E": "Exclusive Green", "#FFE2D8C3": "Exclusive Ivory", "#FF736F78": "Exclusive Plum", "#FFB9ADBB": "Exclusive Violet", "#FF6B515F": "Exclusively", "#FFF2AEB8": "Exclusively Yours", "#FF8F8A70": "Executive Course", "#FFD2CEC4": "Exhale", "#FF81C784": "Exhilarating Green", "#FFA78558": "Exhumed Gold", "#FF0A0A0A": "Existential Angst", "#FF55BB33": "Exit Light", "#FF6264DC": "Exodus Fruit", "#FFAC6292": "Exotic Bloom", "#FFFD9D43": "Exotic Blossom", "#FF705660": "Exotic Eggplant", "#FF96D9DF": "Exotic Escape", "#FF58516E": "Exotic Evening", "#FFFFA24C": "Exotic Flower", "#FFC47F33": "Exotic Honey", "#FFB86F73": "Exotic Incense", "#FFAE7543": "Exotic Life", "#FFD198B5": "Exotic Lilac", "#FFDE0C62": "Exotic Liras", "#FFF84F1D": "Exotic Orange", "#FF75566C": "Exotic Orchid", "#FF909969": "Exotic Palm", "#FF6A5078": "Exotic Purple", "#FF01A7B2": "Exotic Sea", "#FFE1A0CF": "Exotic Violet", "#FF938586": "Exotica", "#FF777E65": "Expanse", "#FFAF9C76": "Expedition", "#FFDBBF90": "Expedition Khaki", "#FF64ACB5": "Experience", "#FFE56CC0": "Explicit Pink", "#FFFED83A": "Exploding Star", "#FF55A860": "Exploration Green", "#FF30AABC": "Explore Blue", "#FF57A3B3": "Explorer Blue", "#FF8B9380": "Explorer Green", "#FFB6AC95": "Explorer Khaki", "#FF3A1F76": "Explorer of the Galaxies", "#FFAA9A79": "Exploring Khaki", "#FFC4C4C4": "Explosive Grey", "#FFCC11BB": "Explosive Purple", "#FF395A73": "Express Blue", "#FF39497B": "Expressionism", "#FF52BC9A": "Expressionism Green", "#FF695C62": "Expressive Plum", "#FFC8A3BB": "Exquisite", "#FF330033": "Exquisite Eggplant", "#FF338860": "Exquisite Emerald", "#FF11CCBB": "Exquisite Turquoise", "#FF9490B2": "Extinct", "#FF4A4F5A": "Extinct Volcano", "#FFEF347C": "Extra Fuchsia", "#FF6AB417": "Extra Life", "#FF91916D": "Extra Mile", "#FFEEEFEA": "Extra White", "#FFBDA6C5": "Extraordinaire", "#FFE6E6E6": "Extraordinary Abundance of Tinge", "#FF4E4850": "Extravagance", "#FFB55067": "Extravagant Blush", "#FF0011AA": "Extravehicular Activity", "#FF661188": "Extraviolet", "#FFFF7133": "Extreme Carrot", "#FFDFC5D5": "Extreme Lavender", "#FFFFB729": "Extreme Yellow", "#FFD45B00": "Exuberance", "#FFF0622F": "Exuberant Orange", "#FFB54D7F": "Exuberant Pink", "#FF1E80C7": "Eye Blue", "#FFDDB835": "Eye Catching", "#FF607B7B": "Eye Grey", "#FFAE3D3B": "Eye of Newt", "#FFD9E3D9": "Eye of the Storm", "#FF232121": "Eye Patch", "#FFBB0077": "Eye Popping Cherry", "#FFFFFBF8": "Eyeball", "#FF8DB6B7": "Eyefull", "#FF553300": "Eyelash Camel", "#FFF4C54B": "Eyelash Viper", "#FF440000": "Eyelids", "#FFEDE8EB": "Eyes White Shut", "#FFD9D9EA": "Eyeshadow", "#FF6B94C5": "Eyeshadow Blue", "#FF008980": "Eyeshadow Turquoise", "#FFADA6C2": "Eyeshadow Viola", "#FF8F9482": "Eyre", "#FFAA1177": "Fabric of Love", "#FF341758": "Fabric of Space", "#FFBA90AD": "Fabulous Fantasy", "#FFE5C1A3": "Fabulous Fawn", "#FFABE3C9": "Fabulous Find", "#FFDDCDAB": "Fabulous Forties", "#FF88CC00": "Fabulous Frog", "#FFEE1188": "Fabulous Fuchsia", "#FF9083A5": "Fabulous Grape", "#FFA92C29": "Fabulous Red", "#FFB6A591": "Fa\u00e7ade", "#FFF7CF89": "Facemark", "#FF676965": "Fade", "#FF658CBB": "Faded Blue", "#FF7689A2": "Faded Denim", "#FFE5D9DC": "Faded Firebrick", "#FF9EB4C0": "Faded Flaxflower", "#FFE3E2D7": "Faded Forest", "#FFEDE2EE": "Faded Fuchsia", "#FF7BB274": "Faded Green", "#FFEAE8E4": "Faded Grey", "#FFB2C4D4": "Faded Indigo", "#FF5DBDCB": "Faded Jeans", "#FFA5975B": "Faded Khaki", "#FFBFAC86": "Faded Letter", "#FFF5E4DE": "Faded Light", "#FF92A2BB": "Faded Lilac", "#FFF0944D": "Faded Orange", "#FF887383": "Faded Orchid", "#FFDE9DAC": "Faded Pink", "#FF80DBEB": "Faded Poster", "#FF916E99": "Faded Purple", "#FFD3494E": "Faded Red", "#FFBE9480": "Faded Redwood", "#FFBF6464": "Faded Rose", "#FF8D9CAE": "Faded Sea", "#FFEBDCD7": "Faded Shells", "#FFFDCF6A": "Faded Sunlight", "#FFDDBEDD": "Faded Violet", "#FFFEFF7F": "Faded Yellow", "#FFDF691E": "Fading Ember", "#FFE8E4E0": "Fading Fog", "#FF442266": "Fading Horizon", "#FFC973A2": "Fading Love", "#FF3377CC": "Fading Night", "#FFECE6DC": "Fading Parchment", "#FFFAD0D1": "Fading Rose", "#FFB3B3C2": "Fading Sunset", "#FFF69A54": "Fading Torch", "#FFFBD2BB": "Fahrenheit", "#FF2A6D8B": "Faience", "#FF81762B": "Faience Green", "#FF99CCEE": "Fail Whale", "#FF908470": "Faint Allegory", "#FFD6DFEC": "Faint Blue", "#FFB2EED3": "Faint Clover", "#FFEEDED5": "Faint Coral", "#FFE2C59C": "Faint Fawn", "#FFDADBE0": "Faint Fern", "#FFE6DEEA": "Faint Fuchsia", "#FFB59410": "Faint Gold", "#FFA58B2C": "Faint Green", "#FFBAA391": "Faint Maple", "#FFF1AAA8": "Faint of Heart", "#FFF5DDC5": "Faint Peach", "#FFF6E4E4": "Faint Pink", "#FF1F2847": "Fainting Light", "#FF8C9151": "Fair and Square", "#FFB4E1D8": "Fair Aqua", "#FF92AF88": "Fair Green", "#FFFCE7CF": "Fair Ivory", "#FFF1E7DC": "Fair Maiden", "#FFBDA7BE": "Fair Orchid", "#FFF3E5DC": "Fair Pink Variant", "#FFF3E6D6": "Fair Winds", "#FF9D9C7E": "Fairbank Green", "#FFD3DFD1": "Fairest Jade", "#FF61463A": "Fairfax Brown", "#FFC9D3D7": "Fairfax Grey", "#FFA2BADB": "Fairies in America", "#FF6BA5A9": "Fairstar", "#FFDAC7C4": "Fairview Taupe", "#FF477050": "Fairway", "#FF26623F": "Fairway Green", "#FFCDE8B6": "Fairway Mist", "#FFFFEBFE": "Fairy Bubblegum Cloud", "#FFBCB0B1": "Fairy Bubbles", "#FFFFE8F4": "Fairy Dust", "#FFEBC9C6": "Fairy Floss", "#FFEED3CB": "Fairy Pink", "#FFF6DBDD": "Fairy Princess", "#FFFFE0F5": "Fairy Salt", "#FFB0E0F7": "Fairy Sparkles", "#FFECDDE5": "Fairy Tail", "#FFEFB4CA": "Fairy Tale", "#FF3E9ABD": "Fairy Tale Blue", "#FFFACFCC": "Fairy Tale Dream", "#FF88CC55": "Fairy Tale Green", "#FFAEA4C1": "Fairy Wand", "#FFDED4D8": "Fairy White", "#FFFFEBF2": "Fairy Wings", "#FF8A6FA9": "Fairy Wren", "#FFE2D7DA": "Fairy-Nuff", "#FF1F3636": "Fait Accompli", "#FFD5EBAC": "Faith", "#FFE7A7A0": "Faithful Rose", "#FFEFE6C1": "Fake Blonde", "#FFC88088": "Fake Crush", "#FF529875": "Fake Fern", "#FF13EAC9": "Fake Jade", "#FFCC77EE": "Fake Love", "#FFAA7711": "Falafel", "#FF6E5A5B": "Falcon Variant", "#FF898887": "Falcon Grey", "#FF007062": "Falcon Turquoise", "#FF76595E": "Falcon Wing", "#FFA5BEBD": "Falkland", "#FFC69896": "Fall Canyon", "#FFE1DDDB": "Fall Chill", "#FFC28359": "Fall Foliage", "#FFFFBC35": "Fall Gold", "#FFECFCBD": "Fall Green Variant", "#FFA78A59": "Fall Harvest", "#FFA49491": "Fall Heliotrope", "#FF7F6144": "Fall in Season", "#FFE5B7A5": "Fall Leaf", "#FFC17A3C": "Fall Leaves", "#FFE6C94C": "Fall Meadow", "#FFC2AC9B": "Fall Mood", "#FFF59344": "Fall River", "#FFFEE3C5": "Fall Straw", "#FFEDB2C4": "Fallen Blossoms", "#FFB85824": "Fallen Canopy", "#FF917347": "Fallen Leaves", "#FFF2E0DA": "Fallen Petals", "#FF8B8072": "Fallen Rock", "#FFA55A3B": "Falling Leaves", "#FFF0F1E7": "Falling Snow", "#FFCAD5C8": "Falling Star", "#FFC2D7DF": "Falling Tears", "#FFB6C121": "Fallout Green", "#FF889977": "Fallout Grey", "#FFC19A51": "Fallow Variant", "#FF9F8D57": "Fallow Deer", "#FF939B88": "False Cypress", "#FF784D4C": "False Morel", "#FFA57E52": "False Puce", "#FFDB9C7B": "Fame Orange", "#FFCAB3A0": "Familiar Beige", "#FFA7B191": "Family Tree", "#FFEE1199": "Fanatic Fuchsia", "#FFFFB1B0": "Fancy Flamingo", "#FFB4B780": "Fancy Flirt", "#FFFF0088": "Fancy Fuchsia", "#FFBDACCA": "Fancy Pansy", "#FFF3DAE1": "Fancy Pants", "#FFF6E9E8": "Fancy Pink", "#FFB40441": "Fancy Red Wine", "#FFC3833D": "Fancy Suede", "#FFE4DE65": "Fandangle", "#FFE04F80": "Fandango Pink", "#FFBB7B6D": "Faneuil Brick", "#FF008384": "Fanfare", "#FFBBAA97": "Fangtooth Fish", "#FFF2EEAF": "Fanlight", "#FF9F7E53": "Fantan", "#FF73788B": "Fantasia", "#FFE6C8C9": "Fantastic Pink", "#FFF2E6DD": "Fantasy Variant", "#FF29ADFF": "Fantasy Console Sky", "#FF8591A2": "Fantasy Grey", "#FFE83A72": "Fantasy Romance", "#FF7FA2BF": "Far Horizons", "#FFCEBEA2": "Far Side of the Mountain", "#FFE5EEEE": "Faraway Blue", "#FF8980C1": "Faraway Sky", "#FFE5D4C9": "Farfalle Noodle", "#FFDFC38D": "Farina", "#FF8E9B88": "Farm Fresh", "#FFEEE8D6": "Farm House", "#FFD5B54C": "Farm Straw", "#FFE7CFC1": "Farm-Fresh Eggs", "#FF8F917C": "Farmer\u2019s Market", "#FF96A69F": "Farmers Green", "#FFEEE3D6": "Farmers Milk", "#FFBD8339": "Farmhouse Ochre", "#FFA34B41": "Farmhouse Red", "#FFA6917D": "Farmyard", "#FF456F6E": "Farrago", "#FFC1A485": "Farro", "#FFE5E3EF": "Farsighted", "#FF006B64": "Fashion Blue", "#FFB9A998": "Fashion District", "#FFB3D26D": "Fashion Green", "#FFA29C94": "Fashion Grey", "#FFB5A8A8": "Fashion Mauve", "#FF998988": "Fashion Week", "#FFEDC537": "Fashion Yellow", "#FFBDB8B8": "Fashionable Grey", "#FFB28CA9": "Fashionably Plum", "#FF66616F": "Fashionista", "#FFC7CBC8": "Fast as the Wind", "#FF8B94C7": "Fast Velvet", "#FF868D82": "Fat Cat", "#FFE6BC00": "Fat Gold", "#FFC1537D": "Fat Smooch", "#FF352235": "Fat Tuesday", "#FF008822": "Fatal Fields", "#FFDA321C": "Fatal Fury", "#FFFFF7ED": "Fatback", "#FF6BA0BF": "Fate", "#FFEE0077": "Fatty Fuchsia", "#FFEEC4B4": "Fatty Sashimi", "#FFC59DAA": "Fauna", "#FFBAD09D": "Fava Bean", "#FFFAE6CC": "Favoured One", "#FF9D723E": "Favourite Ale", "#FF877252": "Favourite Fudge", "#FF8AA3B1": "Favourite Jeans", "#FFE3C5D6": "Favourite Lady", "#FFD3A5D6": "Favourite Lavender", "#FF9BC4C4": "Favourite Sweater", "#FFC1AE91": "Favourite Tan", "#FFCFAF7B": "Fawn Variant", "#FFA7A094": "Fawn Brindle", "#FF71452A": "Fawn Brown", "#FFEE0088": "Feasty Fuchsia", "#FFDAD9CE": "Feather", "#FFF1C9CD": "Feather Boa", "#FF606972": "Feather Falls", "#FFD5DCD0": "Feather Fern", "#FFEDD382": "Feather Gold", "#FFA3D0B6": "Feather Green", "#FFB8AD9E": "Feather Grey", "#FFFFDCB2": "Feather Plume", "#FFA4A1A2": "Feather Quill", "#FFA2AEBF": "Feather Soft Blue", "#FF59A1EF": "Feather Star", "#FFE7EAE5": "Feather White", "#FFAFCBE5": "Featherbed", "#FFBCB7A5": "Feathers of a Dove", "#FFCDC7BB": "Featherstone", "#FFABC2C7": "Feathery Blue", "#FFECE7ED": "Feathery Lilac", "#FFE0DEE3": "February Frost", "#FF43628B": "Federal Blue", "#FF30594B": "Federal Fund", "#FF634041": "Federation Brown", "#FFB71010": "Federation of Love", "#FF625665": "Fedora Variant", "#FFAED3C7": "Feeling Lucky", "#FFFE420F": "F\u0113i H\u00f3ng Scarlet", "#FFA5D785": "Feijoa Variant", "#FFEDF2C3": "Feijoa Flower", "#FFD19275": "Feldspar", "#FFBCA885": "Feldspar Grey", "#FFA0ADA9": "Feldspar Silver", "#FF917292": "Felicia", "#FFE5E4DF": "Felicity", "#FFDBB694": "Feline Frolic", "#FF00608F": "Felix", "#FF247345": "Felt", "#FF6FC391": "Felt Green", "#FF979083": "Felted Wool", "#FF2BC51B": "Felwood Leaves", "#FF4F4352": "Feminin Nightshade", "#FFC4A8CF": "Feminine Fancy", "#FFC7C2CE": "Femininity", "#FF9D5783": "Feminism", "#FF948593": "Femme Fatale", "#FFFF6CB5": "F\u011bn H\u00f3ng Pink", "#FF09332C": "Fence Green", "#FFD7D9C2": "Feng Shui", "#FFAC9D83": "Fenland", "#FFDAD7C8": "Fennec Fox", "#FF8FCE9B": "Fennel", "#FFDDEEBB": "Fennel Bulb", "#FF00AA44": "Fennel Fiasco", "#FF00BB77": "Fennel Fiesta", "#FF77AAFF": "Fennel Flower", "#FF8F7F50": "Fennel Seed", "#FFDECFB1": "Fennel Splash", "#FFB1B6A3": "Fennel Stem", "#FFD2F4DD": "Fennel Tea", "#FF9A9E80": "Fennelly", "#FFCEDEE7": "Fenrisian Grey", "#FFBD8965": "Fenugreek", "#FF8DE07C": "Feralas Lime", "#FF7B7054": "Fermata", "#FF548D44": "Fern Variant", "#FF758A5F": "Fern Canopy", "#FF6AA84F": "Fern Flash", "#FF576A7D": "Fern Flower", "#FF009B54": "Fern Green Variant", "#FF536943": "Fern Grotto", "#FF837B53": "Fern Grove", "#FF595646": "Fern Gully", "#FF99A787": "Fern Leaf", "#FF797447": "Fern Shade", "#FFD6E1B4": "Fern Shadow", "#FF71AB62": "Ferntastic", "#FF977B2F": "Fernweh", "#FFE2261F": "Ferocious", "#FFEE00CC": "Ferocious Flamingo", "#FFE25D1B": "Ferocious Fox", "#FFAA00CC": "Ferocious Fuchsia", "#FF876A68": "Ferra Variant", "#FFAD7D76": "Ferris Wheel", "#FFCC926C": "Ferrous", "#FF383E44": "Ferry", "#FF8B8757": "Fertile Green", "#FF8E603C": "Fertile Soil", "#FF66FC00": "Fertility Green", "#FFBC9042": "Fervent Brass", "#FF469F4E": "Fervent Green", "#FFA1CDA6": "Fescue", "#FFCB8E00": "Festering Brown", "#FFEACC4A": "Festival Variant", "#FFB5E1DB": "Festival de Verano", "#FFAA2F78": "Festival Fuchsia", "#FF6EA43C": "Festival Green", "#FFDE5E3F": "Festival Orange", "#FF6E0F12": "Festive Bordeaux", "#FFFF5566": "Festive Fennec", "#FFDFDFE5": "Festive Ferret", "#FF008C6C": "Festive Green", "#FFA0BBB8": "Festoon Aqua", "#FFDBE0D0": "Feta Variant", "#FF86725F": "Fetched Stick", "#FFEA4C4C": "Fever", "#FFDD5577": "Fever Dream", "#FFDD6677": "Feverish", "#FFDE4D7B": "Feverish Passion", "#FFCB3E50": "Feverish Pink", "#FF112358": "Fibonacci Blue", "#FFBEC0AF": "Fibre Moss", "#FF8B851B": "Fickle Pickle", "#FF3B593A": "Ficus", "#FF006131": "Ficus Elastica", "#FFA6C875": "Fiddle-Leaf Fig", "#FFC8C387": "Fiddlehead Fern", "#FF5A9589": "Fiddler", "#FFBB9FB1": "Fiddlesticks", "#FF4477AA": "Field Blue", "#FFC5E6A4": "Field Day", "#FFCEE9E8": "Field Frost", "#FF60B922": "Field Green", "#FFB1A891": "Field Khaki", "#FF80884E": "Field Maple", "#FFDEB699": "Field of Wheat", "#FFD86F3C": "Field Poppy", "#FF649050": "Field Time Green", "#FFC5BD9C": "Fields Afar", "#FFB18446": "Fields of Glory", "#FF98A1CF": "Fields of Heather", "#FFA491A7": "Fields of Provence", "#FF807E77": "Fieldstone", "#FF7FC15C": "Fierce Mantis", "#FFCC0021": "Fierce Red", "#FF5D3831": "Fiery Brown", "#FFE26058": "Fiery Coral", "#FFF96D7B": "Fiery Flamingo", "#FFB7386E": "Fiery Fuchsia", "#FFB71E1E": "Fiery Garnet", "#FFF0531C": "Fiery Glow", "#FFB1592F": "Fiery Orange Variant", "#FFDD1E2E": "Fiery Red", "#FFF76564": "Fiery Salmon", "#FFF07046": "Fiery Sky", "#FFB74537": "Fiery Topaz", "#FFEDD8D2": "Fiesta", "#FF6FC0B1": "Fiesta Blue", "#FFD47194": "Fiesta Pink", "#FFB67C80": "Fiesta Rojo", "#FFA9A5C2": "Fife", "#FF8E8779": "Fifth Olive-Nue", "#FF505050": "Fiftieth Shade of Grey", "#FF5A3140": "Fig", "#FF550022": "Fig Balsamic", "#FF7A634D": "Fig Branches", "#FF784A65": "Fig Cluster", "#FFA98691": "Fig Fruit Mauve", "#FFBB8610": "Fig Mustard Yellow", "#FFA7989E": "Fig Preserves", "#FF605F4B": "Fig Tree", "#FFFF99AA": "Fight the Sunrise", "#FF939498": "Fighter Jet", "#FF9469A2": "Figue", "#FF962C54": "Figue Pulp", "#FFEEDAC3": "Figure Stone", "#FFE4D5C0": "Figurine", "#FF00AAAC": "Fiji", "#FF6B5F68": "Fiji Coral", "#FF636F22": "Fiji Green", "#FF528D3C": "Fiji Palm", "#FFD8CAA9": "Fiji Sands", "#FFDFE7E8": "Filigree", "#FFA5AF89": "Filigree Green", "#FF93877C": "Film Fest", "#FF473933": "Film Noir", "#FFD1D3C7": "Filmy Green", "#FFB7E1D2": "Filtered Forest", "#FFB1B2C4": "Filtered Light", "#FFECCA9A": "Filtered Moon", "#FFD0B064": "Filtered Rays", "#FFE8AA08": "Filthy Brown", "#FFF1F5DB": "Final Departure", "#FFD0BF9E": "Final Straw", "#FF75785A": "Finch Variant", "#FFECD3CB": "Fine Alabaster", "#FFB6E1E1": "Fine Blue", "#FF815158": "Fine Burgundy", "#FFB1D2D5": "Fine China", "#FFDAA826": "Fine Gold", "#FFD8CFC1": "Fine Grain", "#FFB5A998": "Fine Greige", "#FFFAF5C3": "Fine Linen", "#FF008800": "Fine Pine", "#FFFAF0E1": "Fine Porcelain", "#FF5E548D": "Fine Purple", "#FFF1D5AE": "Fine Sand", "#FF84989E": "Fine Tuned Blue", "#FFFAEDE1": "Fine White", "#FFE4D4C0": "Fine White Sand", "#FF744E5B": "Fine Wine", "#FF96A8C8": "Finesse", "#FFDD8888": "Finest Blush", "#FFF1E5D7": "Finest Silk", "#FFE1C12F": "Finger Banana", "#FF8A7E61": "Fingerpaint", "#FF555356": "Fingerprint", "#FFCBBFB3": "Finishing Touch", "#FF61755B": "Finlandia Variant", "#FF694554": "Finn Tint", "#FF425357": "Finnegan", "#FF5DB0BE": "Finnish Fiord", "#FFFFFCE3": "Fioletowy Beige", "#FFFC44A3": "Fioletowy Purple", "#FF4B5A62": "Fiord", "#FFBFBFAF": "Fiorito", "#FF3D7965": "Fir", "#FF67592A": "Fir Green", "#FF6D7969": "Fir Spruce Green", "#FF8F3F2A": "Fire Variant", "#FF7C383D": "Fire Agate", "#FFBE6400": "Fire Ant", "#FFCE1620": "Fire Axe Red", "#FFCC4411": "Fire Bolt", "#FFE09842": "Fire Bush Variant", "#FFD2806C": "Fire Chalk", "#FF92353A": "Fire Chi", "#FFE3B46F": "Fire Coral", "#FFE3D590": "Fire Dance", "#FFF97306": "Fire Dragon Bright", "#FFB98D68": "Fire Dust", "#FFFE0002": "Fire Engine", "#FFF68F37": "Fire Flower", "#FFFF0D00": "Fire Hydrant", "#FFD95137": "Fire Island", "#FFBB7733": "Fire Lord", "#FFFBD9C4": "Fire Mist", "#FFFD3C06": "Fire Opal", "#FFFF8E57": "Fire Orange", "#FF79483E": "Fire Roasted", "#FFFFB70B": "Fire Yellow", "#FFBF3747": "Fire-Breathing Dragon", "#FFDD5522": "Firebird Tail Lights", "#FFCD5C51": "Firebug", "#FFF2643A": "Firecracker", "#FFF36363": "Firecracker Salmon", "#FF793030": "Fired Brick", "#FF884444": "Fired Clay", "#FF3D3732": "Fired Earth", "#FFD37A38": "Fired Up", "#FFF6DAA7": "Fireflies", "#FF314643": "Firefly Variant", "#FFFFF3A1": "Firefly Glow", "#FFD65E40": "Fireglow", "#FFF9D97B": "Firelight", "#FFBC7256": "Firenze", "#FFD08B73": "Fireplace Glow", "#FFC5C9C7": "Fireplace Kitten", "#FF847C70": "Fireplace Mantel", "#FF6E4A44": "Fireside", "#FFB87427": "Fireside Chat", "#FFEE8866": "Firewatch", "#FFB38491": "Fireweed", "#FF44363D": "Fireworks", "#FF47654A": "Firm Green", "#FFDA93C1": "Firm Pink", "#FF11353F": "Firmament Blue", "#FFF4EDEC": "First Blush", "#FFDBE64C": "First Colours of Spring", "#FFF6E2EA": "First Crush", "#FFF5B1A2": "First Date", "#FFF7D2D8": "First Daughter", "#FFFADBA0": "First Day of School", "#FFF1E798": "First Day of Summer", "#FFCFE5F0": "First Frost", "#FFF4E5E7": "First Impression", "#FFC47967": "First Lady", "#FF59A6CF": "First Landing", "#FFD9E6EE": "First Light", "#FFE7D6ED": "First Lilac", "#FFCF758A": "First Love", "#FFE47901": "First of Fall", "#FFBCE6EF": "First of July", "#FF24D427": "First of the Month Green", "#FFF4CAC6": "First Peach", "#FFB87592": "First Plum", "#FF2FBDA1": "First Post", "#FFBDD8EC": "First Rain", "#FFCBE1F2": "First Shade of Blue", "#FFE8EFF8": "First Snow", "#FFDAD9D4": "First Star", "#FF00E8D8": "First Timer Green", "#FFFFE79C": "First Tulip", "#FFD5BCB2": "First Waltz", "#FF32A0B1": "Fischer Blue", "#FFE4D9C5": "Fish Bone", "#FF11DDDD": "Fish Boy", "#FF7A9682": "Fish Camp Woods", "#FFE1E1D5": "Fish Ceviche", "#FFEECC55": "Fish Finger", "#FF1E446E": "Fish Net Blue", "#FF86C8ED": "Fish Pond", "#FF258F90": "Fish Story", "#FF015E6A": "Fish Tale", "#FF5182B9": "Fisher King", "#FFD1C9BE": "Fisherman Knit", "#FFD7F5F6": "Fishious Rend", "#FF1BA590": "Fishy House", "#FF225599": "Fist of the North Star", "#FFA2A415": "Fistfull of Green", "#FF5BB9D2": "Fitness Blue", "#FFB3B6B0": "Fitzgerald Smoke", "#FFFFAA4A": "Five Star", "#FFB1DBAA": "Fizz", "#FFDDBCBC": "Fizzing Whizbees", "#FFD8E4DE": "Fizzle", "#FFF7F5C8": "Fizzy & Dizzy", "#FFF7BC5C": "Fizzy Peach", "#FF616242": "Fjord Variant", "#FF005043": "Fjord Green", "#FF717C00": "Flag Green", "#FFB3BFB0": "Flagstaff Green", "#FFACADAD": "Flagstone", "#FF9A9E88": "Flagstone Quartzite", "#FFFE6FFF": "Flamazing Pink", "#FFF73D37": "Flamboyant", "#FFF74480": "Flamboyant Flamingo", "#FF694E52": "Flamboyant Plum", "#FF129C8B": "Flamboyant Teal", "#FFE7A500": "Flambrosia", "#FFFD4C29": "Flame Angelfish", "#FFCE0644": "Flame Lily", "#FFDB3C02": "Flame of Prometheus", "#FFFB8B23": "Flame Orange", "#FFBE5C48": "Flame Pea Variant", "#FF86282E": "Flame Red", "#FFF4E25A": "Flame Seal", "#FFD65D45": "Flame Stitch", "#FFFFCF49": "Flame Yellow", "#FFEA8645": "Flamenco Variant", "#FFDF7B62": "Flamenco Pink", "#FFF6A374": "Flaming Cauldron", "#FFD4202A": "Flaming Cherry", "#FFDD55FF": "Flaming Flamingo", "#FFFF005D": "Flaming Hot Flamingoes", "#FFEEBB66": "Flaming June", "#FFEE6633": "Flaming Orange", "#FFD2864E": "Flaming Torch", "#FFE1634F": "Flamingo Variant", "#FFFF44DD": "Flamingo Diva", "#FFEE888B": "Flamingo Dream", "#FFF8BDD9": "Flamingo Feather", "#FFB42121": "Flamingo Fervour", "#FFDF01F0": "Flamingo Fury", "#FFF6E2D8": "Flamingo Peach", "#FFCC33FF": "Flamingo Queen", "#FFEF8E87": "Flamingo Red", "#FFF6E3B4": "Flan", "#FF9E917C": "Flannel", "#FF8B8D98": "Flannel Pyjamas", "#FF495762": "Flapper Dance", "#FFFCAF52": "Flare", "#FFFF4519": "Flare Gun", "#FFFFFB05": "Flash Gitz Yellow", "#FFFF9977": "Flash in the Pan", "#FFFFAA00": "Flash of Orange", "#FFF9EED6": "Flashlight", "#FF7CBD85": "Flashman", "#FFF9F2D1": "Flashpoint", "#FF2C538A": "Flashy Sapphire", "#FFC3C6CD": "Flat Aluminium", "#FFF7D48F": "Flat Beige", "#FF3C73A8": "Flat Blue", "#FF754600": "Flat Brown", "#FFAA5533": "Flat Earth", "#FF699D4C": "Flat Green", "#FFFFF005": "Flat Yellow", "#FFEE6655": "Flattered Flamingo", "#FF527E9C": "Flattering Blue", "#FFF4D3B3": "Flattering Peach", "#FF6B4424": "Flattery", "#FFFCE300": "Flatty Yellow", "#FF8FB67B": "Flavoparmelia Caperata", "#FFFFFBFC": "Flawed White", "#FFD4C3B3": "Flax Beige", "#FFD2D8F4": "Flax Bloom", "#FFE0D68E": "Flax Fibre", "#FFB7A99A": "Flax Fibre Grey", "#FF5577AA": "Flax Flower", "#FF4499DD": "Flax Flower Blue", "#FFCBAA7D": "Flax Straw", "#FFFBECC9": "Flaxen", "#FFE3DDBD": "Flaxen Fair", "#FFBBA684": "Flaxen Field", "#FFF7E6C6": "Flaxseed", "#FFFCFCDE": "Flayed One", "#FF97BBE1": "Fleck", "#FFF2C6BB": "Fleecy Dreams", "#FFD8E2D8": "Fleeting Green", "#FFADD0E0": "Flemish Blue", "#FF894585": "Flesh Fly", "#FFDCDDD8": "Fleur de Sel", "#FFDA8704": "Fleur de Sel Caramel", "#FFB090C7": "Fleur-De-Lis", "#FFB1A3A1": "Flexible Grey", "#FFF8F6E6": "Flickering Firefly", "#FFAA6E49": "Flickering Flame", "#FFC6A668": "Flickering Gold", "#FFFFF1DC": "Flickering Light", "#FF5566EE": "Flickering Sea", "#FF4F81FF": "Flickery C64", "#FF90F215": "Flickery CRT Green", "#FF216BD6": "Flickr Blue", "#FFFB0081": "Flickr Pink", "#FFCDB891": "Flier Lie", "#FF91786B": "Flight of Fancy", "#FFA3B8CE": "Flight Time", "#FF6D7058": "Flinders Green", "#FF8ECFD0": "Fling Green", "#FF716E61": "Flint Variant", "#FFD9623B": "Flint Corn Red", "#FFA09C98": "Flint Grey", "#FF42424D": "Flint Purple", "#FF989493": "Flint Rock", "#FF8F9395": "Flint Shard", "#FFA8B2B1": "Flint Smoke", "#FF677283": "Flintstone", "#FF434252": "Flintstone Blue", "#FF45747E": "Flip", "#FFCCDDCC": "Flip a Coin", "#FFF2C4A7": "Flip-Flop", "#FF7F726B": "Flipper", "#FF7A2E4D": "Flirt Variant", "#FFBE3C37": "Flirt Alert", "#FFFFD637": "Flirtatious", "#FFCC22FF": "Flirtatious Flamingo", "#FF473F2D": "Flirtatious Indigo Tea", "#FF9E88B1": "Flirty Pink", "#FFD65E93": "Flirty Rose", "#FFFA7069": "Flirty Salmon", "#FFFDD685": "Float Like a Butterfly", "#FF93C5B6": "Float Your Boat", "#FFB0C9CD": "Floating Blue", "#FFE9D8C2": "Floating Feather", "#FFECE5CF": "Floating Island", "#FFEDEBCE": "Floating Lily", "#FFCCC7A1": "Floating Lily Pad", "#FFEAF5E7": "Floaty Bubble", "#FFB6BFBD": "Flock of Seagulls", "#FF6677BB": "Flood", "#FF877966": "Flood Mud", "#FF579DAB": "Flood Out", "#FF6C98A2": "Flood Tide", "#FF110044": "Floppy Disk", "#FFE0E0EB": "Flor Lila", "#FF73FA79": "Flora", "#FF91AD8A": "Flora Green", "#FFC6AC9F": "Floral Arrangement", "#FFE7CFB9": "Floral Bluff", "#FFBACB7C": "Floral Bouquet", "#FFFFB94E": "Floral Leaf", "#FFF5E2DE": "Floral Linen", "#FFCDCFDB": "Floral Note", "#FFEEEDE9": "Floral Scent", "#FFC39191": "Floral Tapestry", "#FF96B576": "Florence", "#FF835740": "Florence Brown", "#FF753F38": "Florence Red", "#FF7A5544": "Florentine Brown", "#FFC1937A": "Florentine Clay", "#FF755C32": "Florentine Dream", "#FF1C5798": "Florentine Lapis", "#FFBEA4A2": "Florida Grey", "#FF56BEAB": "Florida Keys", "#FFED9F6C": "Florida Mango", "#FFF7AA6F": "Florida Sunrise", "#FF6BB8B1": "Florida Turquoise", "#FF2A4983": "Florida Waters", "#FF664422": "Florida\u2019s Alligator", "#FFA54049": "Floriography", "#FFD7B3B9": "Floss", "#FF7BB0BA": "Flotation", "#FF4A8791": "Flounce", "#FFB9B297": "Flour Sack", "#FFEBDC9C": "Flourish", "#FFF27987": "Flower Blossom Pink", "#FFD9E8C9": "Flower Bulb", "#FFFDE6C6": "Flower Centre", "#FFD9A96F": "Flower Field", "#FFF498AD": "Flower Girl", "#FFEDE7E6": "Flower Girl Dress", "#FFF9D593": "Flower Hat Jellyfish", "#FFF5DFC5": "Flower of Oahu", "#FF8F4438": "Flower Pot", "#FFE6AED3": "Flower Power", "#FFFFC9D7": "Flower Spell", "#FFB5D5B0": "Flower Stem", "#FF988378": "Flower Wood", "#FFFFEBDA": "Flowerbed", "#FFF62E52": "Flowerhorn Cichlid Red", "#FFA2D4BD": "Flowering Cactus", "#FF875657": "Flowering Chestnut", "#FFA16C94": "Flowering Raspberry", "#FFE1D8B8": "Flowering Reed", "#FFE3D7E3": "Flowers of May", "#FFE4DCBF": "Flowery", "#FFFFF953": "Flowey Yellow", "#FFB9C6CB": "Flowing Breeze", "#FF335E6F": "Flowing River", "#FFFCDF39": "Fluffy Duckling", "#FFF7D6CB": "Fluffy Pink", "#FFD8E5E4": "Fluffy Slippers", "#FFC5D6EB": "Fluid Blue", "#FFA77D35": "Fluor Spar", "#FF89D178": "Fluorescence", "#FF984427": "Fluorescent Fire", "#FF08FF08": "Fluorescent Green", "#FFBDC233": "Fluorescent Lime", "#FFFFCF00": "Fluorescent Orange", "#FFFE1493": "Fluorescent Pink", "#FFFF5555": "Fluorescent Red", "#FFFC8427": "Fluorescent Red Orange", "#FF00FDFF": "Fluorescent Turquoise", "#FFCCFF02": "Fluorescent Yellow", "#FF8FAA8C": "Fluorine", "#FF0AFF02": "Fluro Green", "#FFF2EDE3": "Flurries", "#FFCA2425": "Flush Mahogany Variant", "#FFFF6F01": "Flush Orange Variant", "#FFF8CBC4": "Flush Pink", "#FFDD5555": "Flushed", "#FFC8DAF5": "Fly a Kite", "#FF85B3F3": "Fly Away", "#FF218D4B": "Fly the Green", "#FF1C1E4D": "Fly-by-Night", "#FF787489": "Flying Carpet", "#FF5376AB": "Flying Fish", "#FF024ACA": "Flying Fish Blue", "#FF5DB3D4": "Flyway", "#FFD0EAE8": "Foam Variant", "#FF90FDA9": "Foam Green", "#FF90D1DD": "Foaming Surf", "#FFDCE2BE": "Foamy Lime", "#FFF7F4F7": "Foamy Milk", "#FFB3D4DF": "Foamy Surf", "#FFE5E0D2": "Focus", "#FFFEF9D3": "Focus on Light", "#FF91C3BD": "Focus Point", "#FFD6D7D2": "Fog Variant", "#FFD8D6D1": "Fog Beacon", "#FF112233": "Fog of War", "#FFC4BAD2": "Fog Syringa", "#FFF1EFE4": "Fog White", "#FF57317E": "Foggy Amethyst", "#FF99AEBB": "Foggy Blue", "#FF7F8E1D": "Foggy Bog", "#FFE7E3DB": "Foggy Day", "#FFD1D5D0": "Foggy Dew", "#FFA7A69D": "Foggy Grey", "#FFE2C9FF": "Foggy Heath", "#FF5C5658": "Foggy London", "#FFD5C7E8": "Foggy Love", "#FFB19F9D": "Foggy Mauve", "#FFC8D1CC": "Foggy Mist", "#FFCAD0CE": "Foggy Morn", "#FF40494E": "Foggy Night", "#FFFBF6EF": "Foggy Pith", "#FFCFCBE5": "Foggy Plateau", "#FFB7A5AD": "Foggy Plum", "#FFBFA2A1": "Foggy Quartz", "#FFACBDB1": "Foggy Rain", "#FFA79C8E": "Foggy Sunrise", "#FF909FB2": "Foghorn", "#FFEEF0E7": "Fogtown", "#FFC0C3C4": "Foil", "#FFB0B99C": "Foille", "#FF95B388": "Foliage", "#FF3E6F58": "Foliage Green", "#FF547588": "Folk Blue", "#FF7A634F": "Folk Guitar", "#FF65A19F": "Folk Song", "#FFA5C1B6": "Folk Tales", "#FF684141": "Folklore", "#FF6D6562": "Folkstone", "#FF626879": "Folkstone Grey", "#FFD69969": "Folksy Gold", "#FFF7E5D0": "Follow the Leader", "#FFFD004D": "Folly Variant", "#FFC8BCB7": "Fond Memory", "#FFABA379": "Fond of Fronds", "#FFF4E2CF": "Fondant", "#FFFDF5C4": "Fondue", "#FF6D4B3E": "Fondue Fudge", "#FFCAD175": "Fool\u2019s Gold", "#FF825736": "Football", "#FF7EAF34": "Football Field", "#FF528E39": "Football Pitch", "#FFCAB48E": "Foothill Drive", "#FFE1CFA5": "Foothills", "#FFE6CEE6": "Footie Pyjamas", "#FF457E87": "For the Love of Hue", "#FF323F75": "Forbidden Blackberry", "#FF215354": "Forbidden Forest", "#FFFE7B7C": "Forbidden Fruit", "#FF661020": "Forbidden Passion", "#FFA38052": "Forbidden Peanut", "#FF8A4646": "Forbidden Red", "#FF856363": "Forbidden Thrill", "#FFD5CE69": "Force of Nature", "#FFF29312": "Forceful Orange", "#FF94A8D3": "Foresight", "#FF0B5509": "Forest", "#FF956378": "Forest Berry", "#FF1D5952": "Forest Biome", "#FF0D4462": "Forest Blues", "#FF738F50": "Forest Bound", "#FF969582": "Forest Canopy", "#FF627B72": "Forest Edge", "#FF3D7016": "Forest Empress", "#FF555142": "Forest Floor", "#FF78766D": "Forest Floor Khaki", "#FFE1DFBB": "Forest Found", "#FF88BB95": "Forest Frolic", "#FF68393B": "Forest Fruit Pink", "#FF6E2759": "Forest Fruit Red", "#FF154406": "Forest Green Variant", "#FF3E645B": "Forest Greenery", "#FF9AA22B": "Forest Lichen", "#FF52B963": "Forest Maid", "#FF858F83": "Forest Moss", "#FF434237": "Forest Night", "#FF708D6C": "Forest Path", "#FF216957": "Forest Rain", "#FF006800": "Forest Ride", "#FF555D46": "Forest Ridge", "#FFC8CAA4": "Forest Sand", "#FF336644": "Forest Serenade", "#FF91AC80": "Forest Shade", "#FF015A49": "Forest Shadows", "#FF667028": "Forest Spirit", "#FF016E61": "Forest Splendour", "#FFA4BA8A": "Forest Tapestry", "#FFBBA748": "Forest Tent", "#FFA88B83": "Forest Trail", "#FF9AA77C": "Forester", "#FF007733": "Forestial", "#FF556611": "Forestial Outpost", "#FF2F441F": "Forestry", "#FF4D5346": "Forestwood", "#FFBBE1E6": "Forever", "#FF899BB8": "Forever Blue", "#FF778590": "Forever Denim", "#FFD2BBB2": "Forever Fairytale", "#FFEFE6E1": "Forever Faithful", "#FFAAB4A7": "Forever Green", "#FFAFA5C7": "Forever Lilac", "#FF018E8E": "Forever Teal", "#FF67616B": "Forge", "#FF555257": "Forged Iron", "#FF5B5B59": "Forged Steel", "#FF358094": "Forget-Me-Not Blue", "#FFE1E1BE": "Forgive Quickly", "#FFFF1199": "Forgiven Sin", "#FFC0E5EC": "Forgotten Blue", "#FFC7B89F": "Forgotten Gold", "#FF955AF2": "Forgotten Kyawthuite", "#FFE2D9DB": "Forgotten Mosque", "#FFFFD9D6": "Forgotten Pink", "#FF9878F8": "Forgotten Purple", "#FFAFA696": "Forgotten Sandstone", "#FFC4BBA5": "Forgotten Tusk", "#FF34466C": "Forlorn Cruise", "#FF848391": "Formal Affair", "#FF3A984D": "Formal Garden", "#FF97969A": "Formal Grey", "#FF70474B": "Formal Maroon", "#FFA69A51": "Formosan Green", "#FF4E5B52": "Forrester", "#FFFFC801": "Forsythia", "#FFF6D76E": "Forsythia Blossom", "#FFBBCC55": "Forsythia Bud", "#FFC6C5C1": "Fortitude", "#FFBEA58E": "Fortress", "#FFB8B8B8": "Fortress Grey", "#FF9F97A3": "Fortune", "#FFE0C5A1": "Fortune Cookie", "#FFB0534D": "Fortune Red", "#FFDAA994": "Fortune\u2019s Prize", "#FF92345B": "Forward Fuchsia", "#FF867367": "Fossil", "#FFA78F65": "Fossil Butte", "#FF6C6A43": "Fossil Green", "#FFD2C8BB": "Fossil Sand", "#FFE3DDCC": "Fossil Stone", "#FFD1AF90": "Fossil Tan", "#FFDDCBAF": "Fossil White", "#FFB6B8B0": "Fossilised", "#FF756A43": "Fossilised Leaf", "#FF85C7A1": "Foul Green", "#FFDECFB3": "Found Fossil", "#FFF8E8C5": "Foundation", "#FFEFEEFF": "Foundation White", "#FF56B5CA": "Fountain", "#FF65ADB2": "Fountain Blue Variant", "#FF9CD4CF": "Fountain City", "#FFC6D0C6": "Fountain Foam", "#FFE4E4C5": "Fountain Frolic", "#FFCDEBEC": "Fountain Spout", "#FFB9DEF0": "Fountains of Budapest", "#FF738F5D": "Four Leaf Clover", "#FF6A7252": "Four Seasons Oolong", "#FF90908B": "Four-Wheel Grey", "#FFCA4E33": "Fox", "#FFC8AA92": "Fox Hill", "#FFBF8E7F": "Foxen", "#FF9F6949": "Foxfire Brown", "#FFA2ACC5": "Foxflower Viola", "#FFB57C8C": "Foxglove", "#FF454B40": "Foxhall Green", "#FFBC896E": "Foxtail", "#FFA85E53": "Foxy", "#FFD5A6AD": "Foxy Lady", "#FFDB95AB": "Foxy Pink", "#FF70625C": "Fozzie Bear", "#FFBBB8D0": "Fragile", "#FFE7D7C6": "Fragile Beauty", "#FFCFD1D3": "Fragile Blue", "#FFEFF2DB": "Fragile Fern", "#FF8E545C": "Fragrant Cherry", "#FFAC5E3A": "Fragrant Cloves", "#FFEABC83": "Fragrant Coriander", "#FFFBF6E7": "Fragrant Jasmine", "#FFCAA7BB": "Fragrant Lilac", "#FFDCBDC8": "Fragrant Orchid", "#FFA99FBA": "Fragrant Satchel", "#FFD5C5D4": "Fragrant Snowbell", "#FFADB1C1": "Fragrant Wand", "#FFEE88EE": "Frail Fuchsia", "#FFE40058": "Framboise", "#FFF4D5B2": "Frangipane", "#FFFFD7A0": "Frangipani Variant", "#FF225288": "Frank Blue", "#FFEFEBDB": "Frank Lloyd White", "#FFE5989C": "Franken Berry", "#FFDCAA6E": "Frankincense", "#FFE2DBCA": "Frankly Earnest", "#FFCEAE99": "Frapp\u00e9", "#FF9A6840": "Frapp\u00e9 au Chocolat", "#FF9B6F55": "Freckle Face", "#FFAF8B65": "Freckled Nose", "#FFD78775": "Freckles", "#FF74A690": "Free Green", "#FFD1CDCA": "Free Reign", "#FF029D74": "Free Speech Aquamarine", "#FF4156C5": "Free Speech Blue", "#FF09F911": "Free Speech Green", "#FFE35BD8": "Free Speech Magenta", "#FFC00000": "Free Speech Red", "#FFDEEEED": "Free Spirit", "#FFB29A8B": "Free Wheeling", "#FF3B5E68": "Freedom", "#FF657682": "Freedom Found", "#FF565266": "Freefall", "#FFF3C12C": "Freesia", "#FFB3B0D4": "Freesia Purple", "#FFDEE9F4": "Freeze Up", "#FFD4E9F5": "Freezing Vapour", "#FF99EEEE": "Freezy Breezy", "#FF99FFDD": "Freezy Wind", "#FF232F36": "Freinacht Black", "#FFF9F3D5": "French 75", "#FFA67B50": "French Beige Variant", "#FFF2D5D4": "French Bustle", "#FFCDC0B7": "French Castle", "#FF6A8EA2": "French Court", "#FFF2C9B1": "French Cream", "#FFF2E6CF": "French Creme", "#FF597191": "French Diamond", "#FFEBC263": "French Fry", "#FFBFBDC1": "French Grey", "#FFCAC8B6": "French Grey Linen", "#FFE9E2E0": "French Heirloom", "#FFDFC9D1": "French Lavender", "#FFDEB7D9": "French Lilac Variant", "#FFADBAE3": "French Lilac Blue", "#FFC0FF00": "French Lime", "#FFC9D6C2": "French Limestone", "#FFFEE6DC": "French Manicure", "#FFA2C7A3": "French Market", "#FFAD747D": "French Marron", "#FF446688": "French Mirage Blue", "#FF9FBBC3": "French Moire", "#FFBB9E7C": "French Oak", "#FFD4AB60": "French Pale Gold", "#FF9EA07D": "French Parsley", "#FFA4D2E0": "French Pass Variant", "#FFC4AA92": "French Pastry", "#FF9E9F7D": "French Pear", "#FF811453": "French Plum", "#FFF6F4F6": "French Porcelain", "#FFFAF1D7": "French Porcelain Clay", "#FF4E1609": "French Puce", "#FF644C48": "French Roast", "#FFBAB6A0": "French Shutter", "#FFC0C6D2": "French Silk", "#FFB8BCBC": "French Silver", "#FF667255": "French Tarragon", "#FFD3C2BF": "French Taupe", "#FFDD8822": "French Toast", "#FF896D61": "French Truffle", "#FFEFE1A7": "French Vanilla", "#FFFBE8CE": "French Vanilla Sorbet", "#FFF1E7DB": "French White", "#FFAC1E44": "French Wine", "#FF991133": "French Winery", "#FF814A5C": "Frenzied Red", "#FFFEB101": "Frenzy", "#FF9CC780": "Frenzy Plant", "#FFF4DBD9": "Fresco", "#FF034C67": "Fresco Blue", "#FFFCC9A6": "Fresco Cream", "#FF7BD9AD": "Fresco Green", "#FFD2693E": "Fresh Acorn", "#FFA6E7FF": "Fresh Air", "#FF97A346": "Fresh Apple", "#FFFFD7A5": "Fresh Apricot", "#FF7C8447": "Fresh Artichoke", "#FFA52A24": "Fresh Auburn", "#FFF8D7BE": "Fresh Baked Bread", "#FF5C5F4B": "Fresh Basil", "#FF8BD6E2": "Fresh Blue", "#FF069AF3": "Fresh Blue of Bel Air", "#FFBEEDDC": "Fresh Breeze", "#FFB8AA7D": "Fresh Brew", "#FFFF9C68": "Fresh Cantaloupe", "#FFA77F74": "Fresh Cedar", "#FF74754C": "Fresh Cilantro", "#FF995511": "Fresh Cinnamon", "#FFBE8035": "Fresh Clay", "#FFFCF7E0": "Fresh Cream", "#FFCC9F76": "Fresh Croissant", "#FF91CB7D": "Fresh Cut Grass", "#FFB5D9E5": "Fresh Dawn", "#FFDFE9E5": "Fresh Day", "#FFF0F4E5": "Fresh Dew", "#FFF2EBE6": "Fresh Dough", "#FF4F467E": "Fresh Eggplant Variant", "#FFFAF4CE": "Fresh Eggs", "#FFADBCB4": "Fresh Eucalyptus", "#FFDBE69D": "Fresh Frapp\u00e9", "#FFD3691F": "Fresh Gingerbread", "#FF7FF217": "Fresh Granny Smith", "#FF69D84F": "Fresh Green", "#FFF0F7C4": "Fresh Grown", "#FFA2B07E": "Fresh Guacamole", "#FFFFAADD": "Fresh Gum", "#FFD1C1DD": "Fresh Heather", "#FF77913B": "Fresh Herb", "#FFF6EFC5": "Fresh Honeydew", "#FF006A5B": "Fresh Ivy Green", "#FF8E90B4": "Fresh Lavender", "#FF88AA00": "Fresh Lawn", "#FF93EF10": "Fresh Leaf", "#FFECE678": "Fresh Lemonade", "#FFB2D58C": "Fresh Lettuce", "#FFD8F1CB": "Fresh Lime", "#FFEBE8DA": "Fresh Linen", "#FF2A5443": "Fresh Mint", "#FF83D9C5": "Fresh Mist", "#FF93B654": "Fresh Mown", "#FFDAA674": "Fresh Nectar", "#FFFF11FF": "Fresh Neon Pink", "#FFA69E73": "Fresh Olive", "#FFFAA9BB": "Fresh on the Market", "#FF5B8930": "Fresh Onion", "#FF4FAA6C": "Fresh Oregano", "#FFF6B98A": "Fresh Peaches", "#FFD0DD84": "Fresh Pear", "#FFC2B079": "Fresh Peas", "#FFA0BD14": "Fresh Pesto", "#FF4F5B49": "Fresh Pine", "#FFF3D64F": "Fresh Pineapple", "#FFE19091": "Fresh Pink", "#FFF4F3E9": "Fresh Popcorn", "#FFE7BB95": "Fresh Praline", "#FF503E2C": "Fresh Roe", "#FFFF7356": "Fresh Salmon", "#FFC8A278": "Fresh Sawdust", "#FFF1C11C": "Fresh Scent", "#FFF6EFE1": "Fresh Snow", "#FF91A085": "Fresh Sod", "#FF6AB9BB": "Fresh Soft Blue", "#FFC7C176": "Fresh Sprout", "#FFFFAD00": "Fresh Squeezed", "#FFCFD4A4": "Fresh Start", "#FFEECC66": "Fresh Straw", "#FF505B93": "Fresh Take", "#FFAEBDA8": "Fresh Thyme", "#FFB2C7C0": "Fresh Tone", "#FFDFEBB1": "Fresh Up", "#FFC6E3F7": "Fresh Water", "#FFDF9689": "Fresh Watermelon", "#FFE1D9AA": "Fresh Willow", "#FFEAE6CC": "Fresh Wood Ashes", "#FFF7E190": "Fresh Yellow", "#FFF5E9CF": "Fresh Zest", "#FFE2BEAB": "Fresh-Baked", "#FFE9C180": "Freshly Baked", "#FF5C5083": "Freshly Purpleized", "#FF663322": "Freshly Roasted Coffee", "#FFE6F2C4": "Freshman", "#FF4DA6B2": "Freshwater", "#FFB5C7A4": "Freshwater Green", "#FF535644": "Freshwater Marsh", "#FFE3E1DD": "Freshwater Pearls", "#FFB2A490": "Fretwire", "#FFDDB994": "Friar Tuck", "#FF754E3E": "Friar\u2019s Brown", "#FFFFE6C2": "Fricass\u00e9e", "#FFB9A161": "Frickles", "#FFFFE464": "Fried Egg", "#FFE2F5E1": "Friendly Basilisk", "#FF3F663E": "Friendly Fairway", "#FFB3AC36": "Friendly Frog", "#FFBFFBFF": "Friendly Frost", "#FFDFDFDD": "Friendly Ghost", "#FFC8A992": "Friendly Homestead", "#FFC4A079": "Friendly Tan", "#FFF5E0B1": "Friendly Yellow", "#FFE8C5C1": "Friends", "#FFFED8C2": "Friendship", "#FF004499": "Fright Night", "#FFEE77FF": "Frijid Pink", "#FF939FA9": "Frilled Shark", "#FF8FA6C1": "Frills", "#FFB4E1BB": "Fringy Flower Variant", "#FFCCDDA1": "Frisky", "#FF7BB1C9": "Frisky Blue", "#FFBDB8BB": "Frisky Whiskers", "#FFFEEBC8": "Frittata", "#FFCFD2C7": "Frivolous Folly", "#FF58BC08": "Frog", "#FF00693C": "Frog Green", "#FF7DA270": "Frog Hollow", "#FF8FB943": "Frog on a Log", "#FF73B683": "Frog Pond", "#FFBBD75A": "Frog Prince", "#FF8C8449": "Frog\u2019s Legs", "#FF8CD612": "Frogger", "#FF7FBA9E": "Froggy Pond", "#FFF9E7E1": "Frolic", "#FFE56D75": "Froly Variant", "#FF7B7F56": "Frond", "#FFCDCCC5": "Front Porch", "#FF314A49": "Frontier", "#FF9A8172": "Frontier Brown", "#FFC3B19F": "Frontier Fort", "#FFBCA59A": "Frontier Land", "#FF9A794A": "Frontier Paradise", "#FF655A4A": "Frontier Shadow", "#FF7B5F46": "Frontier Shingle", "#FFE1E4C5": "Frost Variant", "#FF5D9AA6": "Frost Blue", "#FFBBCFEF": "Frost Fairy", "#FF8ECB9E": "Frost Gum", "#FFDAEBEF": "Frost Wind", "#FFACFFFC": "Frostbite Variant", "#FFD2C2AC": "Frosted Almond", "#FF0055DD": "Frosted Blueberries", "#FFA89C91": "Frosted Cocoa", "#FF78B185": "Frosted Emerald", "#FFA7A796": "Frosted Fern", "#FFB4D5BD": "Frosted Fir", "#FFE2F7D9": "Frosted Garden", "#FFEAF0F0": "Frosted Glass", "#FFD4C4D2": "Frosted Grape", "#FFAAEEAA": "Frosted Hills", "#FFB1B9D9": "Frosted Iris", "#FFC2D1C4": "Frosted Jade", "#FFF0F4EB": "Frosted Juniper", "#FFFFEDC7": "Frosted Lemon", "#FFD3D1DC": "Frosted Lilac", "#FFE2F2E4": "Frosted Mint Variant", "#FFCCFFC2": "Frosted Mint Hills", "#FFBFB3D1": "Frosted Orchid", "#FFE0FFDF": "Frosted Plains", "#FFAD3D46": "Frosted Pomegranate", "#FFC6D1C4": "Frosted Sage", "#FFC5C9C5": "Frosted Silver", "#FFD5BCC2": "Frosted Sugar", "#FFF1DBBF": "Frosted Toffee", "#FFF6D8D7": "Frosted Tulip", "#FFDBE5D2": "Frostee Variant", "#FFFFFBEE": "Frosting Cream", "#FFDBF2D9": "Frostini", "#FFD1F0F6": "Frostproof", "#FFEFF1E3": "Frostwork", "#FFA1DED6": "Frosty", "#FFE1809A": "Frosty Berry", "#FFA4A78E": "Frosty Cedar", "#FFCBE9C9": "Frosty Dawn", "#FFCCEBF5": "Frosty Day", "#FFDEE1E9": "Frosty Fog", "#FFA0C0BF": "Frosty Glade", "#FFA3B5A6": "Frosty Green", "#FFDAE2B2": "Frosty Margarita", "#FFE2F7F1": "Frosty Mint", "#FFEFE8E8": "Frosty Morning", "#FF9497B3": "Frosty Nightfall", "#FFC7CFBE": "Frosty Pine", "#FFDFE9E3": "Frosty Pink", "#FFACCDE5": "Frosty Snow Cap", "#FFB4E0DE": "Frosty Soft Blue", "#FF578270": "Frosty Spruce", "#FFDDDDD6": "Frosty White", "#FFCCE9E4": "Frosty White Blue", "#FFC6B8AE": "Froth", "#FFFAEDE6": "Frothy Milk", "#FFE7EBE6": "Frothy Surf", "#FFFBF5D6": "Frozen Banana", "#FFA5C5D9": "Frozen Blue", "#FF00EEDD": "Frozen Boubble", "#FFE1F5E5": "Frozen Civilization", "#FFFBEABD": "Frozen Custard", "#FFD1CAA4": "Frozen Dew", "#FF9CA48A": "Frozen Edamame", "#FFCFE8B6": "Frozen Forest", "#FFDDC5D2": "Frozen Frapp\u00e9", "#FFE1CA99": "Frozen Fruit", "#FFDEEADC": "Frozen Grass", "#FF7B9CB3": "Frozen Lake", "#FFAEE4FF": "Frozen Landscape", "#FFDFD9DA": "Frozen Mammoth", "#FFDBE2CC": "Frozen Margarita", "#FFC6BB81": "Frozen Marguerita", "#FFD8E8E6": "Frozen Mint", "#FFC4EAD5": "Frozen Pea", "#FFC9D1EF": "Frozen Periwinkle", "#FFA5B4AE": "Frozen Pond", "#FFFEA993": "Frozen Salmon", "#FF26F7FD": "Frozen State", "#FFE1DEE5": "Frozen Statues", "#FF30555D": "Frozen Stream", "#FFDD5533": "Frozen Tomato", "#FFA3BFCB": "Frozen Tundra", "#FF53F6FF": "Frozen Turquoise", "#FFECB3BE": "Frozen Veins", "#FF56ACCA": "Frozen Wave", "#FF8BBBDB": "Frozen Whisper", "#FFA5D7B2": "Frugal", "#FFFDC9D0": "Fruit Bowl", "#FFD08995": "Fruit Cocktail", "#FFCA4F70": "Fruit Dove", "#FF946985": "Fruit of Passion", "#FFFA8970": "Fruit Red", "#FF4BA351": "Fruit Salad Variant", "#FFF39D8D": "Fruit Shake", "#FF604241": "Fruit Yard", "#FFEAC064": "Fruit Yellow", "#FF773B3E": "Fruitful Orchard", "#FF448822": "Fruitless Fig Tree", "#FF9B6E71": "Fruitwood", "#FFF69092": "Fruity Licious", "#FFED0DD9": "Fuchsia", "#FF333322": "Fuchsia Berries", "#FFE47CB8": "Fuchsia Blush", "#FFF44772": "Fuchsia Felicity", "#FFFF5599": "Fuchsia Fever", "#FFBB22BB": "Fuchsia Flair", "#FFFF01CD": "Fuchsia Flame", "#FFDD55CC": "Fuchsia Flash", "#FFAB446B": "Fuchsia Flock", "#FFBB2299": "Fuchsia Flourish", "#FFD800CC": "F\u00fachsia Intenso", "#FFCB6E98": "Fuchsia Kiss", "#FF7722AA": "Fuchsia Nebula", "#FF9F4CB7": "Fuchsia Pheromone", "#FFFF77FF": "Fuchsia Pink Variant", "#FFD33479": "Fuchsia Purple", "#FFB73879": "Fuchsia Red", "#FFC74375": "Fuchsia Rose", "#FFC255C1": "Fuchsia Tint", "#FFC3D9CE": "Fuchsite", "#FF5B7E70": "Fuchsite Green", "#FF583D43": "Fudge", "#FF997964": "Fudge Bar", "#FF572B16": "Fudge Brownie", "#FF604A3F": "Fudge Truffle", "#FFA88B7F": "Fudgecicle", "#FFAC6239": "Fudgesicle", "#FFC77E4D": "Fuegan Orange", "#FFEE5533": "Fuego Variant", "#FFEE6622": "Fuego Nuevo", "#FFC2D62E": "Fuego Verde", "#FF596472": "Fuel Town", "#FFD19033": "Fuel Yellow Variant", "#FFEE66AA": "Fugitive Flamingo", "#FFF6EEE2": "Fuji Peak", "#FF89729E": "Fuji Purple", "#FFF1EFE8": "Fuji Snow", "#FF766980": "Fujinezumi", "#FFF5B3CE": "Fulgrim Pink", "#FFE6B77E": "Fulgurite Copper", "#FFFBCDC3": "Full Bloom", "#FF662222": "Full City Roast", "#FFFAE4CE": "Full Cream", "#FF1CA350": "Full Energy", "#FF916B77": "Full Glass", "#FFF4F3E0": "Full Moon", "#FFCFEAE9": "Full Moon Grey", "#FFDE5F2F": "Full of Life", "#FF320094": "Full Swing Indigo", "#FFF9BC4F": "Full Yellow", "#FF514C7E": "Fully Purple", "#FF33789C": "Fun and Games", "#FF335083": "Fun Blue Variant", "#FF15633D": "Fun Green Variant", "#FFF7E594": "Fun Yellow", "#FFB6884D": "Funchal Yellow", "#FF3F6086": "Functional Blue", "#FFABA39A": "Functional Grey", "#FFCDD2C9": "Fundy Bay", "#FFCC00DD": "Fungal Hallucinations", "#FF8F8177": "Fungi", "#FFF3D9DC": "Funhouse", "#FF3EA380": "Funk", "#FFEE9999": "Funki Porcini", "#FF4A3C4A": "Funkie Friday", "#FF98BD3C": "Funky Frog", "#FFAD4E1A": "Funky Monkey", "#FFEDD26F": "Funky Yellow", "#FF113366": "Funnel Cloud", "#FFEDC8CE": "Funny Face", "#FFE35519": "Furious Fox", "#FF55EE00": "Furious Frog", "#FFEE2277": "Furious Fuchsia", "#FFE34D41": "Furious Pi\u00f1ata", "#FFFF1100": "Furious Red", "#FFEA5814": "Furious Tiger", "#FFC30A12": "Furious Tomato", "#FFDD4124": "Furnace", "#FFF5EFEB": "Furry Lady", "#FFF09338": "Furry Lion", "#FFD2BB79": "Further Afield", "#FFFF0011": "Fury", "#FFB56E91": "Fuscia Fizz", "#FFF1E8D6": "Fusilli", "#FFB0AE26": "Fusion", "#FFFF8576": "Fusion Coral", "#FFFF6163": "Fusion Red", "#FFE6A3B9": "Fussy Pink", "#FF614E6E": "Futaai Indigo", "#FFEDF6DB": "Futon", "#FF15ABBE": "Future", "#FFFF2040": "Future Fuchsia", "#FF20B562": "Future Hair", "#FFBCB6BC": "Future Vision", "#FF998DA8": "Futuristic", "#FFFFEA70": "Fuzzy Duckling", "#FFFFD69F": "Fuzzy Navel", "#FFFFBB8F": "Fuzzy Peach", "#FFECD2B4": "Fuzzy Scruff", "#FFF0E9D1": "Fuzzy Sheep", "#FFEAE3DB": "Fuzzy Unicorn", "#FFCC6666": "Fuzzy Wuzzy Variant", "#FFAEB1AC": "Fynbos Leaf", "#FF96834A": "G.I.", "#FF2C4641": "Gable Green Variant", "#FF8C6450": "Gaboon Viper", "#FFDACCA8": "Gabriel\u2019s Light", "#FFF8E6C6": "Gabriel\u2019s Torch", "#FFFFC4AE": "Gadabout", "#FFA5B3AB": "Gaelic Garden", "#FFAC0C20": "Gahar\u0101 L\u0101l", "#FFD3BC9E": "Gaia", "#FFA4BE8D": "Gaia Stone", "#FFF4E4E5": "Gaiety", "#FF785D7A": "Gala Ball", "#FFB04B63": "Gala Pink", "#FF442288": "Galactic Civilization", "#FF48357B": "Galactic Compass", "#FF111188": "Galactic Cruise", "#FF0AFA1E": "Galactic Emerald", "#FF330077": "Galactic Federation", "#FF7F8CB8": "Galactic Gossamer", "#FF6B327B": "Galactic Grapevine", "#FF3311BB": "Galactic Highway", "#FFE0DFDB": "Galactic Mediator", "#FF472E97": "Galactic Purple", "#FFC0C4C6": "Galactic Tint", "#FF442255": "Galactic Wonder", "#FFC4DDE2": "Galactica", "#FF95A69F": "Galago", "#FFD28083": "Galah", "#FF085F6D": "Galapagos", "#FF29685F": "Galapagos Green", "#FF2E305E": "Galaxea", "#FF4F4A52": "Galaxy", "#FF2D5284": "Galaxy Blue", "#FF444499": "Galaxy Express", "#FF79AFAD": "Galaxy Green", "#FF35454E": "Gale Force", "#FF007844": "Gale of the Wind", "#FF64776E": "Galena", "#FF374B52": "Galenite Blue", "#FF8A8342": "Galia Melon", "#FFA4763C": "Gallant Gold", "#FF99AA66": "Gallant Green", "#FF3F95BF": "Galleon Blue", "#FF8FA4AC": "Galleria Blue", "#FFDCD7D1": "Gallery Variant", "#FF9BBCE4": "Gallery Blue", "#FF88A385": "Gallery Green", "#FFC5C2BE": "Gallery Grey", "#FF935A59": "Gallery Red", "#FFD0C5B8": "Gallery Taupe", "#FFEAEBE3": "Gallery White", "#FFD5AA5E": "Galley Gold", "#FFD8A723": "Galliano Variant", "#FFA36629": "Gallstone Yellow", "#FFE8C8B8": "Galveston Tan", "#FFC4DDBB": "Galway", "#FF95A7A4": "Galway Bay", "#FF996600": "Gamboge Brown", "#FFE6D058": "Gamboge Yellow", "#FFE1B047": "Gambol Gold", "#FF7E8181": "Game Over", "#FF0F380F": "Gameboy Contrast", "#FF9BBC0F": "Gameboy Light", "#FF8BAC0F": "Gameboy Screen", "#FF306230": "Gameboy Shade", "#FFBFD1AF": "Gamin", "#FFC9FF27": "G\u01cen L\u01cen Hu\u00e1ng Olive", "#FF658B38": "G\u01cen L\u01cen L\u01dc Green", "#FF372B2C": "Ganache", "#FF239D42": "Gangly Gremlin", "#FFFFDD22": "Gangsters Gold", "#FFA4E4FC": "Ganon Blue", "#FF8B7D82": "Ganymede", "#FFF1D5A5": "Garbanzo Bean", "#FFEEC684": "Garbanzo Paste", "#FF9FAC98": "Garden", "#FF9C6989": "Garden Aroma", "#FF778679": "Garden Bower", "#FF60784F": "Garden Club", "#FFD5C5A8": "Garden Country", "#FF506A48": "Garden Cucumber", "#FFF1F8EC": "Garden Dawn", "#FF93B59D": "Garden District", "#FFCCD4EC": "Garden Fairy", "#FFA892A8": "Garden Flower", "#FF729588": "Garden Fountain", "#FFDADCC1": "Garden Gate", "#FFABC0BB": "Garden Gazebo", "#FFD9D7A1": "Garden Glade", "#FFFFC1D0": "Garden Glory", "#FF7DCC98": "Garden Glow", "#FF9B2002": "Garden Gnome Red", "#FF99CEA0": "Garden Goddess", "#FF4E6539": "Garden Green", "#FF658369": "Garden Greenery", "#FF5E7F57": "Garden Grove", "#FF6F7D6D": "Garden Hedge", "#FFE1D4B4": "Garden Lattice", "#FFD7DEAC": "Garden Leek", "#FF87762B": "Garden Lettuce Green", "#FF55422B": "Garden Loam", "#FF28A873": "Garden Medley", "#FFCED9C4": "Garden Mint", "#FFC38E46": "Garden Ochre", "#FFA09F5B": "Garden of Earthly Delights", "#FF7FA771": "Garden of Eden", "#FF8A8D66": "Garden of Paradise", "#FFE3A4B8": "Garden Party", "#FF424330": "Garden Path", "#FFE4E4D5": "Garden Pebble", "#FFE4D195": "Garden Picket", "#FF9D8292": "Garden Plum", "#FFAFC09E": "Garden Pond", "#FFA4A99B": "Garden Promenade", "#FFCDBE96": "Garden Rain", "#FFACCFA9": "Garden Room", "#FFF7EAD4": "Garden Rose White", "#FFA18B62": "Garden Salt Green", "#FFEBE6C7": "Garden Seat", "#FF334400": "Garden Shadow", "#FFD6EFDA": "Garden Shed", "#FFCDB1AB": "Garden Snail", "#FFB1CA95": "Garden Spot", "#FFAB863A": "Garden Sprout", "#FFBFD4C4": "Garden Statue", "#FF7DC683": "Garden Stroll", "#FF8CBD97": "Garden Swing", "#FFA3BBB3": "Garden Twilight", "#FF66BB8D": "Garden Variety", "#FF89B89A": "Garden View", "#FF827799": "Garden Violets", "#FF9FB1AB": "Garden Vista", "#FFAEA492": "Garden Wall", "#FF786E38": "Garden Weed", "#FF5E602A": "Gardener Green", "#FF5C534D": "Gardener\u2019s Soil", "#FFF1E8DF": "Gardenia", "#FFACBA8D": "Gardening", "#FF337700": "Gardens Sericourt", "#FFA75429": "Garfield", "#FFEEEE55": "Gargantua", "#FFABB39E": "Gargoyle", "#FFFFDF46": "Gargoyle Gas", "#FF00A4B1": "Garish Blue", "#FF51BF8A": "Garish Green", "#FF69887B": "Garland", "#FFB0AAA1": "Garlic Beige", "#FFEDDF5E": "Garlic Butter", "#FFE2D7C1": "Garlic Clove", "#FFECEACF": "Garlic Head", "#FFBFCF00": "Garlic Pesto", "#FFCDD2BC": "Garlic Suede", "#FFDDDD88": "Garlic Toast", "#FF733635": "Garnet", "#FF354A41": "Garnet Black Green", "#FF763B42": "Garnet Evening", "#FFCC7446": "Garnet Sand", "#FFC89095": "Garnet Shadow", "#FF384866": "Garnet Stone Blue", "#FF1E9752": "Garnish", "#FF756861": "Garret Brown", "#FF7B8588": "Garrison Grey", "#FFFFBB31": "Garuda Gold", "#FF98DCFF": "Gas Giant", "#FFFEFFEA": "Gaslight", "#FFD2935D": "Gates of Gold", "#FF88796C": "Gateway Arch", "#FFA0A09C": "Gateway Grey", "#FFB2AC9C": "Gateway Pillar", "#FFAB8F55": "Gathering Field", "#FFAD9466": "Gathering Place", "#FF665E43": "Gator Tail", "#FF8E3B2F": "Gatsby Brick", "#FFEED683": "Gatsby Glitter", "#FF78736E": "Gauntlet Grey", "#FF84C3AA": "Gauss Blaster Green", "#FFC8E1E9": "Gauzy Calico", "#FFE3DBD4": "Gauzy White", "#FF76826C": "Gazebo Green", "#FFD1D0CB": "Gazebo Grey", "#FF947E68": "Gazelle", "#FF9D913C": "Gecko", "#FF669900": "Gecko\u2019s Dream", "#FFAAC69A": "Geddy Green", "#FF7F4C00": "G\u00e9d\u00e9on Brown", "#FF40534E": "Gedney Green", "#FFC5832E": "Geebung Variant", "#FFB0A677": "Geek Chic", "#FFDBA674": "Gehenna\u2019s Gold", "#FFDD44FF": "Geisha Pink", "#FFB5ACB2": "Gellibrand", "#FF4D5B8A": "Gem", "#FF73C4A4": "Gem Silica", "#FF53C2C3": "Gem Turquoise", "#FFB4D6CB": "Gemini", "#FFFCA750": "Gemini Mustard Momento", "#FF004F6D": "Gemstone Blue", "#FF4B6331": "Gemstone Green", "#FFF4E386": "Gen Z Yellow", "#FF7761AB": "Genestealer Purple", "#FF18515D": "Genetic Code", "#FF1F7F76": "Geneva Green", "#FFBAB7B8": "Geneva Morn", "#FF33673F": "Genever Green", "#FFBCC4E0": "Genevieve", "#FF5F4871": "Gengiana", "#FF3E4364": "Genie", "#FF31796D": "Genoa Variant", "#FF698EB3": "Genteel Blue", "#FFE2E6EC": "Genteel Lavender", "#FF9079AD": "Gentian", "#FF312297": "Gentian Blue", "#FF57487F": "Gentian Violet", "#FFCEAF93": "Gentility", "#FF97CBD2": "Gentle Aquamarine", "#FFCDD2DE": "Gentle Blue", "#FFC4CEBF": "Gentle Calm", "#FFFCD7BA": "Gentle Caress", "#FFC3ECE9": "Gentle Cold", "#FFC3CC6E": "Gentle Dill", "#FFE8B793": "Gentle Doe", "#FFE6C9A4": "Gentle Fawn", "#FFDCE0CD": "Gentle Frost", "#FFB3EBE0": "Gentle Giant", "#FFF6E5B9": "Gentle Glow", "#FF908A9B": "Gentle Grape", "#FFDEE0E1": "Gentle Grey", "#FFEBC4BF": "Gentle Kiss", "#FFE3D9C8": "Gentle Lamb", "#FFA5CE8F": "Gentle Landscape", "#FF958C9E": "Gentle Mauve", "#FFCBC9C5": "Gentle Rain", "#FFB0C8D0": "Gentle Sea", "#FFD6CBBA": "Gentle Shadows", "#FF99BDD2": "Gentle Sky", "#FFE5D4BE": "Gentle Slumber", "#FFC3BFAE": "Gentle Soul", "#FFE3D5B8": "Gentle Touch", "#FFDAD5D9": "Gentle Violet", "#FF7AC0BB": "Gentle Wave", "#FFABD0EA": "Gentle Wind", "#FFFFF5BE": "Gentle Yellow", "#FFCA882C": "Gentleman\u2019s Whiskey", "#FFF1E68C": "Gentlemann\u2019s Business Pants", "#FFBDAE9E": "Gentry Grey", "#FF4B3F69": "Geode", "#FFB06144": "Georgia Clay", "#FFF97272": "Georgia Peach", "#FF22657F": "Georgian Bay", "#FFCF875E": "Georgian Leather", "#FFC6B8B4": "Georgian Pink", "#FF5B8D9F": "Georgian Revival Blue", "#FFD1974C": "Georgian Yellow", "#FFE77B75": "Geraldine Variant", "#FFDC465D": "Geranium", "#FFCFA1C7": "Geranium Bud", "#FF90AC74": "Geranium Leaf", "#FF9B8C7B": "German Camouflage Beige", "#FF53504E": "German Grey", "#FF89AC27": "German Hop", "#FF2E3749": "German Liquorice", "#FFCD7A00": "German Mustard", "#FF0094C8": "Germander Speedwell", "#FFDDC47E": "Germania", "#FF1A9D49": "Get up and Go", "#FFAC9B7B": "Getaway", "#FFC3DAE3": "Getting Wet", "#FFC7C1B7": "Gettysburg Grey", "#FFC4D7CF": "Geyser Variant", "#FFE3CAB5": "Geyser Basin", "#FFA9DCE2": "Geyser Pool", "#FFCBD0CF": "Geyser Steam", "#FFD8BC23": "Ghee Yellow", "#FFC0BFC7": "Ghost Variant", "#FFDFEDDA": "Ghost Lichen", "#FFC10102": "Ghost Pepper", "#FF887B6E": "Ghost Ship", "#FFE3E4DB": "Ghost Story", "#FFBEB6A8": "Ghost Town", "#FFCBD1D0": "Ghost Whisperer", "#FFBCB7AD": "Ghost Writer", "#FFE2E0DC": "Ghosted", "#FFCAC6BA": "Ghosting", "#FF113C42": "Ghostlands Coal", "#FFA7A09F": "Ghostly", "#FFD9D7B8": "Ghostly Green", "#FFCCCCD3": "Ghostly Grey", "#FF7B5D92": "Ghostly Purple", "#FFE2E6EF": "Ghostly Tuna", "#FFE2DBDB": "Ghostwaver", "#FF667744": "Ghoul", "#FFF1D236": "Giallo", "#FF88763F": "Giant Cactus Green", "#FF665D9E": "Giant Onion", "#FFB05C52": "Giant\u2019s Club", "#FFFE5A1D": "Giants Orange", "#FF626970": "Gibraltar", "#FF6F6A68": "Gibraltar Grey", "#FF133A54": "Gibraltar Sea", "#FF9FC0CE": "Gift of the Sea", "#FF564786": "Gigas Variant", "#FFEFF0D3": "Giggle", "#FFF4DB4F": "Gilded", "#FFE4B46E": "Gilded Age", "#FFB39F8D": "Gilded Beige", "#FF956841": "Gilded Glamour", "#FFB58037": "Gilded Gold", "#FFEBA13C": "Gilded Leaves", "#FFDAD5C9": "Gilded Linen", "#FFC09E6C": "Gilded Pear", "#FFBBAF5E": "Gilded Pesto", "#FFCD9D99": "Gilman Rose", "#FF6C8396": "Gilneas Grey", "#FFE6AD67": "Gilt Trip", "#FFB9AD61": "Gimblet Variant", "#FFD9DFCD": "Gin Variant", "#FFA3B9B6": "Gin Berry", "#FFF8EACA": "Gin Fizz Variant", "#FFBA8E5A": "Gin Still", "#FFECEBE5": "Gin Tonic", "#FFB06500": "Ginger", "#FFC9A86A": "Ginger Ale", "#FFF5DFBC": "Ginger Ale Fizz", "#FFC27F38": "Ginger Beer", "#FFEFE0D7": "Ginger Cream", "#FFCE915A": "Ginger Crisp", "#FFCEAA64": "Ginger Crunch", "#FFB06D3B": "Ginger Dough", "#FF97653C": "Ginger Dy", "#FFCF524E": "Ginger Flower", "#FF996251": "Ginger Gold", "#FFB8A899": "Ginger Grey Yellow", "#FFC6A05E": "Ginger Jar", "#FFF1E991": "Ginger Lemon Cake", "#FFFFFFAA": "Ginger Lemon Tea", "#FFF7A454": "Ginger Milk", "#FFF9D09F": "Ginger Peach", "#FF9A7D61": "Ginger Pie", "#FFC17444": "Ginger Root", "#FFF0C48C": "Ginger Root Peel", "#FFBE8774": "Ginger Rose", "#FFCB8F7B": "Ginger Scent", "#FFE3CEC6": "Ginger Shortbread", "#FFB65D48": "Ginger Spice", "#FFDDDACE": "Ginger Sugar", "#FFB19D77": "Ginger Tea", "#FFCC8877": "Ginger Whisper", "#FF8C4A2F": "Gingerbread", "#FF956A43": "Gingerbread Brick", "#FF9C5E33": "Gingerbread Crumble", "#FFCA994E": "Gingerbread House", "#FFB39479": "Gingerbread Latte", "#FFFFDD11": "Gingerline", "#FFC79E73": "Gingersnap", "#FFB06C3E": "Gingery", "#FFA3C899": "Gingko", "#FFB3A156": "Gingko Leaf", "#FF918260": "Gingko Tree", "#FFA5ACA4": "Ginkgo Green", "#FF858F79": "Ginkgo Tree", "#FF97867C": "Ginnezumi", "#FFB3D5C0": "Ginninderra", "#FFF3E9BD": "Ginseng", "#FFE6CDB5": "Ginseng Root", "#FFBC2D29": "Ginshu", "#FFB3CEAB": "Gio Ponti Green", "#FFD1C0DC": "Girl Crush", "#FFD39BCB": "Girl Power", "#FFE4C7C8": "Girl Talk", "#FFFFD3CF": "Girlie", "#FF5A82AC": "Girls in Blue", "#FFF6E6E5": "Girly Nursery", "#FFEE88FF": "Give Me Your Love", "#FFEBD4AE": "Givry Variant", "#FFD4A1B5": "Gizmo", "#FFD1DAD7": "Glacial", "#FFDEF7FE": "Glacial Day", "#FFC3FBF4": "Glacial Drift", "#FF6FB7A8": "Glacial Green", "#FFEAE9E7": "Glacial Ice", "#FFBCD8E2": "Glacial Stream", "#FFEAF2ED": "Glacial Tint", "#FFC9EAD4": "Glacial Water Green", "#FF78B1BF": "Glacier Variant", "#FFDEF2EE": "Glacier Bay", "#FFA9C1C0": "Glacier Blue", "#FFBBBCBD": "Glacier Grey", "#FFEAF3E6": "Glacier Ivy", "#FF62B4C0": "Glacier Lake", "#FFAABCCB": "Glacier Mist", "#FFD1D2DC": "Glacier Pearl", "#FFB3D8E5": "Glacier Point", "#FFE2E3D7": "Glacier Valley", "#FFF5E1AC": "Glad Yellow", "#FF9CA687": "Glade", "#FF5F8151": "Glade Green Variant", "#FF7A8CA6": "Gladeye", "#FF6E6C5E": "Gladiator Grey", "#FFA95C3E": "Gladiator Leather", "#FFD54F43": "Gladiola", "#FF6370B6": "Gladiola Blue", "#FF6E5178": "Gladiola Violet", "#FFCF748C": "Glam", "#FFDACBA7": "Glamorgan Sausage", "#FFB74E64": "Glamorous", "#FFC5AEA6": "Glamorous Taupe", "#FFF0EAE0": "Glamorous White", "#FFDB9DA7": "Glamour", "#FF007488": "Glamour Green", "#FFFFFCEC": "Glamour White", "#FFBDB8AE": "Glasgow Fog", "#FFC7BEC4": "Glass Bead", "#FF880000": "Glass Bull", "#FFDCDFB0": "Glass Green", "#FFFCF3DD": "Glass of Milk", "#FFDBD8CB": "Glass Pebble", "#FFCDB69B": "Glass Sand", "#FF587B9B": "Glass Sapphire", "#FFCDD0C0": "Glass Tile", "#FFB7A2CC": "Glass Violet", "#FFD7E2E5": "Glassine", "#FF46B5C0": "Glassmith", "#FFD1D9DA": "Glassware", "#FF3A3F45": "Glaucophobia", "#FFB3E8C2": "Glaucous Green", "#FF967217": "Glazed Chestnut", "#FFA15C30": "Glazed Ginger", "#FF5B5E61": "Glazed Granite", "#FFEFE3D2": "Glazed Pears", "#FFD19564": "Glazed Pecan", "#FFD34E36": "Glazed Persimmon", "#FFAD7356": "Glazed Pot", "#FFA44B62": "Glazed Raspberry", "#FF89626D": "Glazed Ringlet", "#FFFFDCCC": "Glazed Sugar", "#FFB9CBA3": "Gleam", "#FFF8DED1": "Gleaming Shells", "#FFB88C63": "Gleaming Tan", "#FF9DBB7D": "Gleeful", "#FFA892B4": "Gleeful Joy", "#FF4AAC72": "Glen", "#FFACB8C1": "Glen Falls", "#FF82817C": "Glen Plaid", "#FFA1BB8B": "Glendale", "#FFA7D3B7": "Glenwood Green", "#FF5D6F80": "Glide Time", "#FFD7D8D9": "Gliding Feather", "#FFE1E8E3": "Glimmer", "#FF4FB9CE": "Glimpse", "#FF121210": "Glimpse Into Space", "#FFFFF3F4": "Glimpse of Pink", "#FF335588": "Glimpse of Void", "#FFF2EFDC": "Glisten Green", "#FFF5E6AC": "Glisten Yellow", "#FFEED288": "Glistening", "#FFF6BA25": "Glistening Dawn", "#FFB1B3BE": "Glistening Grey", "#FF2C5463": "Glitch", "#FFE6E8FA": "Glitter", "#FFFEDC57": "Glitter Is Not Gold", "#FF44BBFF": "Glitter Lake", "#FF88FFFF": "Glitter Shower", "#FFF8D75A": "Glitter Yellow", "#FF944A63": "Glitterati", "#FFDEC0E2": "Glittering Gemstone", "#FFD3AD77": "Glittering Sun", "#FFEEEDDB": "Glittery Glow", "#FFF9EECD": "Glittery Yellow", "#FF965F73": "Glitz and Glamour", "#FFD6A02B": "Glitzy Gold", "#FFAF413B": "Glitzy Red", "#FFB4BE93": "Gloaming Green", "#FF696E51": "Global Green", "#FFF1D7D3": "Global Warming", "#FF5F6C3C": "Globe Artichoke", "#FF998D8D": "Globe Thistle Grey Rose", "#FF3C416A": "Gloomy Blue", "#FF8756E4": "Gloomy Purple", "#FF4A657A": "Gloomy Sea", "#FFCBA956": "Glorious Gold", "#FFAAEE11": "Glorious Green Glitter", "#FFFE2F4A": "Glorious Red", "#FFF88517": "Glorious Sunset", "#FF110011": "Glossy Black", "#FFFFDD77": "Glossy Gold", "#FFEEE3DE": "Glossy Kiss", "#FF636340": "Glossy Olive", "#FFF9F2DA": "Glow", "#FFFBE8C1": "Glow Home", "#FFBEFDB7": "Glow in the Dark", "#FFBED565": "Glow Worm", "#FFEE4444": "Glowing Brake Disc", "#FFBC4D39": "Glowing Coals", "#FFAF5941": "Glowing Firelight", "#FFD0D4C0": "Glowing Green", "#FFFBB736": "Glowing Lantern", "#FFEE4400": "Glowing Meteor", "#FFFFD15A": "Glowing Reviews", "#FFBD4649": "Glowing Scarlet", "#FFFFF6B9": "Glowlight", "#FF693162": "Gloxinia", "#FF1A1B1C": "Gluon Grey", "#FFDDCC66": "Gluten", "#FF00754B": "Gnarls Green", "#FFFFEEBB": "Gnocchi Beige", "#FF81A19B": "Gnome", "#FFADC484": "Gnome Green", "#FFB09F84": "Gnu Tan", "#FF007F87": "Go Alpha", "#FFF7CA50": "Go Bananas", "#FF786E4C": "Go Ben Variant", "#FFFCECD5": "Go Go Glow", "#FF008A7D": "Go Go Green", "#FFC6BE6B": "Go Go Lime", "#FFFEB87E": "Go Go Mango", "#FFFDD8D4": "Go Go Pink", "#FFDCD8D7": "Go", "#FF342C21": "Go Variant", "#FFA89A91": "Goat", "#FF5E5A6A": "Gobelin Mauve", "#FFEBDACE": "Gobi Beige", "#FFCDBBA2": "Gobi Desert", "#FFCFA56D": "Gobi Gold", "#FFD4AA6F": "Gobi Sand", "#FFBBA587": "Gobi Tan", "#FFDB4731": "Goblet Waxcap", "#FF34533D": "Goblin Variant", "#FF5F7278": "Goblin Blue", "#FFEB8931": "Goblin Eyes", "#FF4EFD54": "Goblin Warboss", "#FF770000": "Gochujang Red", "#FF550066": "God of Nights", "#FF4466CC": "God of Rain", "#FFFAF4E0": "God-Given", "#FFF56991": "God\u2019s Own Junkyard Pink", "#FFD0E1E8": "Goddess", "#FF904C6F": "Goddess of Dawn", "#FF5D7F9D": "Goddess of the Nile", "#FF3C4D03": "Godzilla", "#FF0087A1": "Gogo Blue", "#FF83807A": "Going Grey", "#FFAB858F": "Going Rouge", "#FFCC142F": "Goji Berry", "#FFF0833A": "Goku Orange", "#FFF3BC00": "Gold Abundance", "#FF2A2424": "Gold Black", "#FFECC481": "Gold Buff", "#FFEEDD99": "Gold Bullion", "#FFFFE8BB": "Gold Buttercup", "#FFAE9769": "Gold Canyon", "#FFC78538": "Gold Coast", "#FFDF9938": "Gold Crest", "#FFE0CE57": "Gold Deposit", "#FFD1B075": "Gold Digger", "#FFD56C30": "Gold Drop Variant", "#FFA4803F": "Gold Dust", "#FFDB9663": "Gold Earth", "#FF977A41": "Gold Estate", "#FFB44F22": "Gold Flame", "#FFD99F4D": "Gold Foil", "#FFEB9600": "Gold Fusion Variant", "#FFCFB352": "Gold Gleam", "#FFECE086": "Gold Grillz", "#FFE6C28C": "Gold Hearted", "#FFB1A57B": "Gold Infusion", "#FFEDBB26": "Gold Leaf", "#FFB17743": "Gold Metal", "#FFFFEAC7": "Gold of Midas", "#FFDB7210": "Gold Orange", "#FFECBC14": "Gold Ore", "#FFCAAF81": "Gold Perennial", "#FFC6795F": "Gold Pheasant", "#FFE6BD8F": "Gold Plate", "#FFB0834F": "Gold Plated", "#FFB39260": "Gold Ransom", "#FFEB5406": "Gold Red", "#FFC4A777": "Gold Rush", "#FFF7E5A9": "Gold Sand Variant", "#FFB19971": "Gold Season", "#FF786B3D": "Gold Sparkle", "#FFC19D61": "Gold Spell", "#FF907047": "Gold Spike", "#FFF3DFA6": "Gold Strand", "#FFBB9A39": "Gold Taffeta", "#FF9E865E": "Gold Tangiers", "#FFFEE8B0": "Gold Thread", "#FFDEC283": "Gold Tint", "#FFE2B227": "Gold Tips Variant", "#FFDBB40C": "Gold Tooth", "#FFBD955E": "Gold Torch", "#FFC9AB73": "Gold Tweed", "#FFB95E33": "Gold Varnish Brown", "#FFD6B956": "Gold Vein", "#FFEABA8A": "Gold Vessel", "#FFD4C19E": "Gold Wash", "#FFE6D682": "Gold Winged", "#FFFFC265": "Gold\u2019s Great Touch", "#FF9C8A53": "Goldbrown", "#FFF5BF03": "Golden", "#FFCEAB77": "Golden Age", "#FFCEA644": "Golden Age Gilt", "#FFE6BE59": "Golden Appeal", "#FFF3D74F": "Golden Apples", "#FFDBA950": "Golden Apricot", "#FFD29E68": "Golden Aura", "#FFFFEE77": "Golden Aurelia", "#FFCDA64A": "Golden Avocado", "#FFFCC62A": "Golden Banner", "#FFDABA55": "Golden Bass", "#FFBA985F": "Golden Bear", "#FFCEA277": "Golden Beige", "#FFCA8136": "Golden Bell Variant", "#FFD9A400": "Golden Beryl Yellow", "#FFCCAA55": "Golden Blond", "#FFEFE17E": "Golden Blonde", "#FFFF1155": "Golden Blood", "#FFFFDD44": "Golden Boy", "#FFB27A01": "Golden Brown Variant", "#FFFFCC22": "Golden Buddha Belly", "#FFF8E6C8": "Golden Buff", "#FFFFD96D": "Golden Butter", "#FFAC864B": "Golden Cadillac", "#FFE7C068": "Golden Chalice", "#FFDDDD11": "Golden Chandelier", "#FFEEB574": "Golden Chime", "#FFF4CE74": "Golden Churro", "#FFECC799": "Golden City Moon", "#FF968651": "Golden Clay", "#FFFCD975": "Golden Coin", "#FFF0B672": "Golden Corn", "#FFF7B768": "Golden Cream", "#FFECC909": "Golden Crescent", "#FFF6CA69": "Golden Crest", "#FFCCDDBB": "Golden Crested Wren", "#FFD7B056": "Golden Cricket", "#FFD2D88F": "Golden Delicious", "#FFF1CC2B": "Golden Dream Variant", "#FFD8C39F": "Golden Ecru", "#FFB29155": "Golden Egg", "#FFBDD5B1": "Golden Elm", "#FFC39E44": "Golden Field", "#FFEBDE31": "Golden Fizz Variant", "#FFEDD9AA": "Golden Fleece", "#FFF0EAD2": "Golden Fog", "#FFCCCC00": "Golden Foil", "#FFBDD043": "Golden Foliage", "#FFEEEE99": "Golden Fragrance", "#FFE2B31B": "Golden Frame", "#FF876F4D": "Golden Freesia", "#FFD9C09C": "Golden Gate", "#FFC0362D": "Golden Gate Bridge", "#FFF9F525": "Golden Ginkgo", "#FFEEBB44": "Golden Glam", "#FFFBE573": "Golden Glitter", "#FFEAD771": "Golden Glitter Storm", "#FF9E7551": "Golden Glove", "#FFF9D77E": "Golden Glow Variant", "#FFFFD34E": "Golden Goose", "#FFC59137": "Golden Grain", "#FFB8996B": "Golden Granola", "#FFDAA631": "Golden Grass Variant", "#FFBDB369": "Golden Green", "#FFA99058": "Golden Griffon", "#FFE1C3BB": "Golden Guernsey", "#FFDDDD00": "Golden Gun", "#FFDA9E38": "Golden Hamster", "#FFFFCC44": "Golden Handshake", "#FF9F8046": "Golden Harmony", "#FFCCCC11": "Golden Harvest", "#FFEDDFC1": "Golden Haystack", "#FFFCD896": "Golden Haze", "#FFFFFFBB": "Golden Hermes", "#FFA37111": "Golden Hind", "#FFBB993A": "Golden History", "#FFEDC283": "Golden Hominy", "#FFFFDB29": "Golden Honey Suckle", "#FFCFDD7B": "Golden Hop", "#FFF1B457": "Golden Hour", "#FFFFEFCB": "Golden Impression", "#FFDD9911": "Golden Key", "#FFE0C84B": "Golden Kingdom", "#FFF2CF34": "Golden Kiwi", "#FFEAA34B": "Golden Koi", "#FFD8C7A2": "Golden Lake", "#FFC48B42": "Golden Leaf", "#FF928D35": "Golden Lime", "#FFF3CA6C": "Golden Lion", "#FFCA602A": "Golden Lion Tamarin", "#FFF5BC1D": "Golden Lock", "#FFE9DBC4": "Golden Lotus", "#FFFDCC37": "Golden Marguerite", "#FFF0BE3A": "Golden Mary", "#FFC49B35": "Golden Mean", "#FFD4C990": "Golden Mist", "#FFFFCF60": "Golden Moray Eel", "#FFB39B67": "Golden Moss", "#FFF4E8D1": "Golden Mushroom", "#FFFFDA68": "Golden Nectar", "#FFD78E48": "Golden Nugget", "#FFA96A28": "Golden Oak", "#FFECBE91": "Golden Oat Coloured", "#FFC56F3B": "Golden Ochre", "#FFAB9C40": "Golden Olive", "#FFF7C070": "Golden Opportunity", "#FFD7942D": "Golden Orange", "#FFD3B66C": "Golden Pagoda", "#FFA58705": "Golden Palm", "#FFB4BB31": "Golden Passionfruit", "#FFF4D9B9": "Golden Pastel", "#FFE4AA76": "Golden Patina", "#FFFEDB2D": "Golden Period", "#FFCF9632": "Golden Pheasant", "#FF956F3F": "Golden Pilsner", "#FFFBD073": "Golden Plumeria", "#FFEBCEBD": "Golden Pop", "#FFFFC64D": "Golden Promise", "#FFCA884B": "Golden Pumpkin", "#FFAA8A58": "Golden Quartz Ochre", "#FFFFB657": "Golden Rain Yellow", "#FFF8D878": "Golden Raspberry", "#FFF6DA74": "Golden Rays", "#FFE8CE49": "Golden Relic", "#FFEEDEC7": "Golden Retriever", "#FFE3D474": "Golden Rice", "#FFDAAE49": "Golden Rule", "#FFB09D73": "Golden Sage", "#FFDFAF2B": "Golden Samovar", "#FFEACE6A": "Golden Sand Variant", "#FFEED280": "Golden Sap", "#FFEDDB8E": "Golden Scarab", "#FFDDBB11": "Golden Schnitzel", "#FFCCA24D": "Golden Sheaf", "#FFB98841": "Golden Slumber", "#FFF1E346": "Golden Snitch", "#FFFECC36": "Golden Spell", "#FFC6973F": "Golden Spice", "#FFF6D263": "Golden Sprinkles", "#FFF7EB83": "Golden Staff", "#FFF5EDAE": "Golden Straw", "#FF816945": "Golden Summer", "#FFEBD8B3": "Golden Syrup", "#FFFFC152": "Golden Tainoi Variant", "#FFE9C89B": "Golden Talisman", "#FFCAA375": "Golden Thistle Yellow", "#FFE8C47A": "Golden Thread", "#FFF5CC23": "Golden Ticket", "#FFE1A451": "Golden Tiger", "#FFCAA444": "Golden Touch", "#FFFFB118": "Golden Treasure", "#FFE8C954": "Golden Trident", "#FFFFFEC6": "Golden Wash", "#FFEADCC0": "Golden Weave", "#FFECD251": "Golden Week", "#FFE9CA94": "Golden West", "#FFE2C74F": "Golden Yarrow", "#FFFDCB18": "Goldenrod Variant", "#FFF0B053": "Goldenrod Field", "#FFA17841": "Goldenrod Tea", "#FFFFCE8F": "Goldenrod Yellow", "#FFF8E462": "Goldfinch", "#FFEEBB11": "Goldfinger", "#FFF2AD62": "Goldfish", "#FFCEB790": "Goldfrapp", "#FFC89D3F": "Goldie", "#FFBAAD75": "Goldie Oldie", "#FFFFF39A": "Goldilocks", "#FFEEB550": "Goldsmith", "#FFE7DE54": "Goldvreneli 1882", "#FFCDD80D": "Goldzilla", "#FF836E59": "Golem", "#FF53A391": "Golf Blazer", "#FF5A9E4B": "Golf Course", "#FF5A8B3F": "Golf Day", "#FF009B75": "Golf Green", "#FF5E6841": "Golfer Green", "#FFD77E70": "Golgfag Brown", "#FF8BB9DD": "Goluboy Blue", "#FFCC9933": "Gomashio Yellow", "#FF373332": "Gondola Variant", "#FF5DB1C5": "Gondolier", "#FF52A8B2": "Gone Fishing", "#FFD9C737": "Gone Giddy", "#FF5D06E9": "Gonzo Violet", "#FFD3BA75": "Good as Gold", "#FFA09883": "Good Earth", "#FFF3F0D6": "Good Graces", "#FF333C76": "Good Karma", "#FF499674": "Good Luck", "#FF86C994": "Good Luck Charm", "#FFFCFCDA": "Good Morning", "#FFF4EAD5": "Good Morning Akihabara", "#FF46565F": "Good Night!", "#FF3F6782": "Good Samaritan", "#FFC2B99D": "Good", "#FFEDD2A7": "Good-Looking", "#FFD9CAC3": "Goodbye Kiss", "#FFCCD87A": "Goody Gumdrop", "#FFC2BA8E": "Goody Two Shoes", "#FF41C379": "Googol", "#FF93AF3C": "Googolplex", "#FFFFBA80": "Goose Bill", "#FFF4E7DF": "Goose Down", "#FFD2CDC4": "Goose Feathers", "#FF629B92": "Goose Pond Green", "#FFA89DAC": "Goose Wing Grey", "#FFA23F5D": "Gooseberry", "#FFACB75F": "Gooseberry Fool", "#FFC7A94A": "Gooseberry Yellow", "#FFF0F0E0": "Gor\u0101 White", "#FFBF852B": "Gordal Olive", "#FF29332B": "Gordons Green Variant", "#FF8D9BA9": "Gorgeous Graphite", "#FF287C37": "Gorgeous Green", "#FFA495CB": "Gorgeous Hydrangea", "#FFE7DBD3": "Gorgeous White", "#FF4455CC": "Gorgonzola Blue", "#FFFDE336": "Gorse Variant", "#FFE99A3C": "Gorse Yellow Orange", "#FF654741": "Gorthor Brown", "#FFB42435": "Gory Movie", "#FFA30800": "Gory Red", "#FF444444": "Goshawk Grey", "#FF857668": "Gosling", "#FF399F86": "Gossamer Variant", "#FFB2CFBE": "Gossamer Green", "#FFFAC8C3": "Gossamer Pink", "#FF8AC8E2": "Gossamer Threads", "#FFD3CEC4": "Gossamer Veil", "#FFE8EEE9": "Gossamer Wings", "#FF9FD385": "Gossip Variant", "#FF807872": "Gotham", "#FF757C7D": "Gotham City", "#FF8A9192": "Gotham Grey", "#FF698890": "Gothic Variant", "#FFA38B93": "Gothic Amethyst", "#FFBB852F": "Gothic Gold", "#FF473951": "Gothic Grape", "#FF857555": "Gothic Olive", "#FF92838A": "Gothic Purple", "#FFA0A160": "Gothic Revival Green", "#FF7C6B6F": "Gothic Spire", "#FFD0C2B4": "Gotta Have It", "#FFEECC11": "Gouda Gold", "#FF8D6449": "Goulash", "#FF7D9EA2": "Gould Blue", "#FFBC9D70": "Gould Gold", "#FFE3CBA8": "Gourmet Honey", "#FF968D8C": "Gourmet Mushroom", "#FF32493E": "Government Green", "#FF51559B": "Governor Bay Variant", "#FFA8C0CE": "Graceful", "#FFDD897C": "Graceful Ballerina", "#FFBDDFB2": "Graceful Flower", "#FFCBA9D0": "Graceful Garden", "#FFA78A50": "Graceful Gazelle", "#FFACB7A8": "Graceful Green", "#FFBEB6AC": "Graceful Grey", "#FFDAEED5": "Graceful Mint", "#FF546C46": "Graceland Grass", "#FFC4D5CB": "Gracilis", "#FFF8EDD7": "Gracious", "#FFBAB078": "Gracious Glow", "#FFA5ABA1": "Gracious Grey", "#FFE3B7B1": "Gracious Rose", "#FFC0A480": "Graham Cracker", "#FF806240": "Graham Crust", "#FFCAB8A2": "Grain Brown Variant", "#FFD8C095": "Grain Mill", "#FFDFD2C0": "Grain of Rice", "#FFD8DBE1": "Grain of Salt", "#FFEFE3D8": "Grain White", "#FFB79E66": "Grainfield", "#FFF5F6F7": "Gram\u2019s Hair", "#FFE9DEC8": "Gramp\u2019s Tea Cup", "#FFA3896C": "Gramps Shoehorn", "#FFEE3300": "Gran Torino Red", "#FF5D81BB": "Granada Sky", "#FFE99F4C": "Granary Gold", "#FF665A48": "Grand Avenue", "#FF015482": "Grand Bleu", "#FF7E9CA0": "Grand Boulevard", "#FF3C797D": "Grand Canal", "#FFA05D4D": "Grand Canyon", "#FFEDCD62": "Grand Casino Gold", "#FFCB5C45": "Grand Duke", "#FFA8CCD5": "Grand Entrance", "#FF645764": "Grand Grape", "#FF86BB9D": "Grand Gusto", "#FFECECE1": "Grand Heron", "#FFD8D0BD": "Grand Piano", "#FF6C5657": "Grand Plum", "#FF864764": "Grand Poobah", "#FF496763": "Grand Prix", "#FF534778": "Grand Purple", "#FF38707E": "Grand Rapids", "#FFD9C2A8": "Grand Soiree", "#FFC38D87": "Grand Sunset", "#FF8DC07C": "Grand Valley", "#FF92576F": "Grandeur Plum", "#FFE0EBAF": "Grandiflora Rose", "#FFCAA84C": "Grandiose", "#FFFFCD73": "Grandis Variant", "#FFF7E7DD": "Grandma\u2019s Cameo", "#FFE0B8C0": "Grandma\u2019s Pink Tiles", "#FF6B927F": "Grandview", "#FF857767": "Grange Hall", "#FFB62758": "Granita", "#FF746A5E": "Granite", "#FF313238": "Granite Black", "#FF816F6B": "Granite Boulder", "#FF3D2D24": "Granite Brown", "#FF6C6F78": "Granite Canyon", "#FFD7CEC4": "Granite Dust", "#FF638496": "Granite Falls", "#FFB1BAC2": "Granite Fog", "#FF6B6869": "Granite Grey", "#FF606B75": "Granite Peak", "#FFC5C4C1": "Granitine", "#FFD0B690": "Granivorous", "#FFF6EEED": "Grannie\u2019s Pearls", "#FFC5E7CD": "Granny Apple Variant", "#FF7B948C": "Granny Smith Variant", "#FFF5CE9F": "Granola", "#FF9E6858": "Granrojo Jellyfish", "#FF8F8461": "Grant Drab", "#FF918F8A": "Grant Grey", "#FF6C90B2": "Grant Village", "#FFA8B989": "Grant Wood Ivy", "#FFE3E0DA": "Granular Limestone", "#FFFFFDF2": "Granulated Sugar", "#FF6C3461": "Grape Variant", "#FFA598C7": "Grape Arbour", "#FF24486C": "Grape Blue", "#FF905284": "Grape Candy", "#FFDFE384": "Grape Cassata", "#FF725F7F": "Grape Compote", "#FFBEBBBB": "Grape Creme", "#FF6A587E": "Grape Expectations", "#FF64435F": "Grape Fizz", "#FFA19ABD": "Grape Gatsby", "#FFDCCAE0": "Grape Glimmer", "#FF6D6166": "Grape Grey", "#FF807697": "Grape Harvest", "#FF606A88": "Grape Haze", "#FF5533CC": "Grape Hyacinth", "#FFB4A6D5": "Grape Illusion", "#FF979AC4": "Grape Ivy", "#FF7F779A": "Grape Jam", "#FF7E667F": "Grape Jelly", "#FF772F6C": "Grape Juice", "#FF82476F": "Grape Kiss", "#FFC2C4D4": "Grape Lavender", "#FF5A5749": "Grape Leaf", "#FF576049": "Grape Leaves", "#FFC5C0C9": "Grape Mist", "#FF8D5C75": "Grape Nectar", "#FFD3D9CE": "Grape Oil Green", "#FF8677A9": "Grape Parfait", "#FF60406D": "Grape Popsicle", "#FF5D1451": "Grape Purple", "#FF9B4682": "Grape Riot", "#FF522F57": "Grape Royale", "#FF886971": "Grape Shake", "#FFB69ABC": "Grape Smoke", "#FFAE94A6": "Grape Soda", "#FFF4DAF1": "Grape Taffy", "#FF797F5A": "Grape Vine", "#FF64344B": "Grape Wine", "#FFBEAECF": "Grape\u2019s Treasure", "#FFAA9FB2": "Grapeade", "#FFFD5956": "Grapefruit", "#FFEE6D8A": "Grapefruit Juice", "#FFDFA01A": "Grapefruit Yellow", "#FF714A8B": "Grapes of Italy", "#FF58424C": "Grapes of Wrath", "#FF71384B": "Grapeshot", "#FF880066": "Grapest", "#FF4C475E": "Grapewood", "#FF5C5E5F": "Graphic Charcoal", "#FF824E78": "Graphic Grape", "#FF0000FC": "Graphical 80\u2019s Sky", "#FF383428": "Graphite Variant", "#FF262A2B": "Graphite Black", "#FF32494B": "Graphite Black Green", "#FF7C7666": "Graphite Grey Green", "#FFF5D9B1": "Grappa", "#FF92786A": "Grapple", "#FF786E70": "Grapy", "#FF92B300": "Grasping Grass", "#FF5CAC2D": "Grass", "#FF636F46": "Grass Blade", "#FFB8B97E": "Grass Cloth", "#FF088D46": "Grass Court", "#FFCEB02A": "Grass Daisy", "#FF3F9B0B": "Grass Is Greener", "#FFCA84FC": "Grass Pink Orchid", "#FFA1AFA0": "Grass Sands", "#FFE2DAC2": "Grass Skirt", "#FFC0FB2D": "Grass Stain Green", "#FFF4F7EE": "Grass Valley", "#FF77824A": "Grasshopper", "#FFBCDABB": "Grasshopper Pie", "#FF87866F": "Grasshopper Wing", "#FF407548": "Grasslands", "#FFD8C475": "Grassroots", "#FF968154": "Grassy Cliff", "#FF5C7D47": "Grassy Field", "#FFD8DDCA": "Grassy Glade", "#FF419C03": "Grassy Green", "#FF76A55B": "Grassy Meadow", "#FFB8A336": "Grassy Ochre", "#FF9B9279": "Grassy Savannah", "#FFA60E46": "Grated Beet", "#FF71714E": "Gratefully Grass", "#FFDAE2CD": "Gratifying Green", "#FFE0D2A9": "Gratin Dauphinois", "#FFE0EAD7": "Gratitude", "#FF85A3B2": "Grauzone", "#FF4A4B46": "Gravel Variant", "#FFCECBC5": "Gravel Drive", "#FFBAB9A9": "Gravel Dust", "#FF637A82": "Gravel Grey Blue", "#FF938576": "Gravelle", "#FFD3C7B8": "Gravelstone", "#FF68553A": "Graveyard Earth", "#FFB8BCBD": "Gravity", "#FFEC834F": "Gravlax", "#FFA1A19F": "Grayve-Yard", "#FFA2B35A": "Greasy Green Beans", "#FF117755": "Greasy Greens", "#FF838583": "Greasy Grey", "#FF576E8B": "Great Basin", "#FFD5E0EE": "Great Blue Heron", "#FF7F8488": "Great Coat Grey", "#FFD1A369": "Great Dane", "#FF9FA6B3": "Great Falls", "#FF719BA2": "Great Fennel Flower", "#FF908675": "Great Frontier", "#FF5CA16D": "Great Gazoo", "#FF6B6D85": "Great Grape", "#FFA5A6A1": "Great Graphite", "#FFABB486": "Great Green", "#FFD8E6CB": "Great Joy", "#FF4A72A3": "Great Serpent", "#FFE9E2DB": "Great Tit Eggs", "#FF3B5760": "Great Void", "#FF9E7E54": "Grecian Gold", "#FF8C6D46": "Grecian Helmet", "#FF00A49B": "Grecian Isle", "#FFD6CFBE": "Grecian Ivory", "#FF00AA66": "Greedo Green", "#FFAA9922": "Greedy Gecko", "#FFC4CE3B": "Greedy Gold", "#FFD1FFBD": "Greedy Green", "#FF3D0734": "Greek Aubergine", "#FF009FBD": "Greek Blue", "#FF0D5EAF": "Greek Flag Blue", "#FF8CCE86": "Greek Garden", "#FFEDE9EF": "Greek Goddess", "#FFBBDCF0": "Greek Isles", "#FF9B8FB0": "Greek Lavender", "#FFA08650": "Greek Olive", "#FF7C754C": "Greek Oregano", "#FF72A7E1": "Greek Sea", "#FFF0ECE2": "Greek Villa", "#FF3E3D29": "Green 383", "#FF53A144": "Green Acres", "#FF688878": "Green Adirondack", "#FF52928D": "Green Agate", "#FFC8CCBA": "Green Alabaster", "#FF98A893": "Green Amazons", "#FFAFC1A3": "Green Andara", "#FF5EDC1F": "Green Apple", "#FFD2C785": "Green Apple Martini", "#FF95D6A1": "Green Ash", "#FF80C4A9": "Green Balloon", "#FFA0AC9E": "Green Balsam", "#FFA8B453": "Green Banana", "#FF79B088": "Green Bank", "#FFA9C4A6": "Green Bark", "#FF7E9285": "Green Bay", "#FF566E57": "Green Bayou", "#FFB0A36E": "Green Bean Casserole", "#FF228800": "Green Bell Pepper", "#FF2D7F6C": "Green Belt", "#FF516A62": "Green Beret", "#FF373A3A": "Green Black", "#FF22DD00": "Green Blob", "#FF42B395": "Green Blue", "#FF358082": "Green Blue Slate", "#FF8BB490": "Green Bonnet", "#FFDAF1E0": "Green Brocade", "#FF696006": "Green Brown", "#FF32A7B5": "Green Buoy", "#FF7F8866": "Green Bush", "#FFBBEE11": "Green Cacophony", "#FF89CE01": "Green Cape", "#FF919365": "Green Cast", "#FF146B47": "Green Caterpillar", "#FFBCDF8A": "Green Chalk", "#FFE7DDA7": "Green Charm", "#FFCFBB75": "Green Citrine", "#FF889988": "Green Clay", "#FF868E65": "Green Coconut", "#FF465149": "Green Column", "#FF828039": "Green Commando", "#FFBEEF69": "Green Cow", "#FF62AE9E": "Green Crush", "#FFACB977": "Green Curry", "#FF009966": "Green Cyan", "#FF75BBFD": "Green Darner Tail", "#FFBBEE88": "Green Day", "#FF8BD3C6": "Green Daze", "#FF006C67": "Green Dragon", "#FFC1CAB0": "Green Dragon Spring", "#FF6E7A77": "Green Dusk", "#FF00988E": "Green Dynasty", "#FFE3ECC5": "Green Eggs", "#FFB3C39C": "Green Eggs & Cam", "#FF7CB68E": "Green Eggs and Ham", "#FFBBCC11": "Green Elisabeth \u2161", "#FF00BB66": "Green Elliott", "#FFDAEAE2": "Green Emulsion", "#FF80905F": "Green Energy", "#FF77AA00": "Green Envy", "#FF7EFBB3": "Green Epiphany", "#FFE9EAC8": "Green Essence", "#FF7D956D": "Green Eyes", "#FF605E4F": "Green Fatigue", "#FFAAEE00": "Green Fiasco", "#FFB3A476": "Green Fig", "#FF297E6B": "Green Fingers", "#FF79C753": "Green Flash", "#FFBBAA22": "Green Flavour", "#FF55BBAA": "Green Fluorite", "#FF989A87": "Green Fog", "#FF319B64": "Green Fondant", "#FFD0D6BF": "Green Frost", "#FFD8F1EB": "Green Frosting", "#FF364847": "Green Gables", "#FF72C895": "Green Gala", "#FF72B874": "Green Galore", "#FF11BB00": "Green Gamora", "#FF009911": "Green Gardens", "#FF008176": "Green Garlands", "#FF61BA85": "Green Garter", "#FF676957": "Green Gate", "#FFC7C3A8": "Green Gaze", "#FFCDD47F": "Green Gecko", "#FFE7F0C2": "Green Glacier", "#FFEAF1E4": "Green Glaze", "#FF00BB00": "Green Glimmer", "#FFE7EAE3": "Green Glimpse", "#FFDCF1C7": "Green Glint", "#FFDDE26A": "Green Glitter", "#FF79AA87": "Green Globe", "#FF00955E": "Green Gloss", "#FFACC65D": "Green Glow", "#FF007722": "Green Glutton", "#FF505A39": "Green Goanna", "#FF11BB33": "Green Goblin", "#FF76AD83": "Green Goddess", "#FFC5B088": "Green Gold", "#FF73A236": "Green Gone Wild", "#FFB0DFA4": "Green Gooseberry", "#FF588266": "Green Goth Eyeshadow", "#FF7C9793": "Green Granite", "#FFC0DBC6": "Green Grapes", "#FF3DB9B2": "Green Grapple", "#FF39854A": "Green Grass", "#FF7EA07A": "Green Grey", "#FFAFA984": "Green Grey Mist", "#FF95E3C0": "Green Gum", "#FFA4C08A": "Green Herb", "#FF66CC22": "Green High", "#FF007800": "Green Hills", "#FF6A9D5D": "Green Hornet", "#FF587D79": "Green Hour", "#FFE8E8D4": "Green Iced Tea", "#FF6E6F56": "Green Illude", "#FFC4FE82": "Green Incandescence", "#FF11887B": "Green Ink", "#FF8C8E51": "Green Jalape\u00f1o", "#FF7A796E": "Green Jeans", "#FF349B82": "Green Jelly", "#FF95DABD": "Green Jewel", "#FF3BDE39": "Green Juice", "#FF53FE5C": "Green Katamari", "#FF393D2A": "Green Kelp Variant", "#FF647F4A": "Green Knoll", "#FF8AD370": "Green Lacewing", "#FFCAD6C4": "Green Lane", "#FF9CD03B": "Green Lantern", "#FF008684": "Green Lapis", "#FF526B2D": "Green Leaf Variant", "#FF9C9463": "Green Lentils", "#FFC1CEC1": "Green Lily", "#FF26B467": "Green Mana", "#FF5B7C5B": "Green Mantle", "#FF8DBC8A": "Green Masquerade", "#FFB2B55F": "Green Me", "#FF8EA8A0": "Green Meets Blue", "#FFD7D7AD": "Green Mesh", "#FF4D5947": "Green Mile", "#FF8A9992": "Green Milieu", "#FF99DD00": "Green Minions", "#FFD7E2D5": "Green Mirror", "#FFBFC298": "Green Mist Variant", "#FF008888": "Green Moblin", "#FF33565E": "Green Moonstone", "#FF3A7968": "Green Moray", "#FF887E48": "Green Moss", "#FFBDBFAF": "Green Motif", "#FFC5E1C3": "Green Myth", "#FF404404": "Green Not Found", "#FFB0B454": "Green Oasis", "#FF005249": "Green Oblivion", "#FF9F8F55": "Green Ochre", "#FFCDFA56": "Green of Bhabua", "#FF8D8B55": "Green Olive", "#FFBDAA89": "Green Olive Pit", "#FFC1E089": "Green Onion", "#FF989A82": "Green Onyx", "#FFE1FE52": "Green Ooze", "#FFE5CE77": "Green Papaya", "#FF7BD5BF": "Green Parakeet", "#FFCFDDB9": "Green Parlour", "#FF0D6349": "Green Paw Paw", "#FF266242": "Green Pea Variant", "#FF79BE58": "Green Pear", "#FF388004": "Green People", "#FF97BC62": "Green Pepper", "#FF47694F": "Green Perennial", "#FFA7C668": "Green Peridot", "#FF98A76E": "Green Plaza", "#FFE2E1C6": "Green Power", "#FF11DD55": "Green Priestess", "#FFD1E5B5": "Green Reflection", "#FF7B8762": "Green Relict", "#FF009944": "Green Revolution", "#FF288F3D": "Green Ribbon", "#FF80AEA4": "Green Room", "#FF888866": "Green Savage", "#FF858365": "Green Scene", "#FF22FF00": "Green Screen", "#FF44AA33": "Green Seduction", "#FF77BB00": "Green Serpent", "#FF99DD22": "Green Serum", "#FF45523A": "Green Shade Wash", "#FFD7C94A": "Green Sheen Variant", "#FFCCFD7F": "Green Shimmer", "#FFA2C2B0": "Green Silk", "#FF859D66": "Green Sky", "#FF39766C": "Green Sleeves", "#FF9CA664": "Green Smoke Variant", "#FF9EB788": "Green Snow", "#FFD1E9C4": "Green Song", "#FF006474": "Green Spool", "#FF589F7E": "Green Spruce", "#FF2B553E": "Green Stain", "#FF73884D": "Green Suede", "#FF9E8528": "Green Sulphur", "#FF66AA22": "Green Symphony", "#FFB5B68F": "Green Tea", "#FF65AB7C": "Green Tea Candy", "#FF93B13D": "Green Tea Ice Cream", "#FF939A89": "Green Tea Leaf", "#FF90A96E": "Green Tea Mochi", "#FF0CB577": "Green Teal", "#FFE3EDE0": "Green Tease", "#FF779900": "Green Thumb", "#FFD5E0D0": "Green Tilberi", "#FF47553C": "Green Tone Ink", "#FF5EAB81": "Green Tourmaline", "#FFA0D9A3": "Green Trance", "#FF99A798": "Green Trellis", "#FF679591": "Green Turquoise", "#FF51755B": "Green Valley", "#FFE0F1C4": "Green Veil", "#FF25B68B": "Green Velour", "#FF127453": "Green Velvet", "#FFB8F818": "Green Venom", "#FFD4E7C3": "Green Vibes", "#FF23414E": "Green Vogue Variant", "#FFC6DDCD": "Green Wash", "#FF2C2D24": "Green Waterloo Variant", "#FFC3DCD5": "Green Wave", "#FF548F6F": "Green Weed", "#FFE3EEE3": "Green Whisper", "#FFDEDDCB": "Green White Variant", "#FF22BB33": "Green With Envy", "#FF7D7853": "Green Woodpecker Olive", "#FF13DA25": "Green Wrasse", "#FFC6F808": "Green Yellow Variant", "#FF90BE9D": "Green-Eyed Lady", "#FF346E23": "Green-Eyed Monster", "#FF00DD00": "Greenalicious", "#FF245D3F": "Greenbriar", "#FF60857A": "Greenella", "#FF495A4C": "Greener Pastures", "#FF80A546": "Greenery", "#FFDAECC5": "Greenette", "#FF60724F": "Greenfield", "#FFBDA928": "Greenfinch", "#FF84BE84": "Greengage", "#FFB2CC9A": "Greenhorn", "#FF3E6334": "Greenhouse", "#FFDFE4D5": "Greening", "#FF40A368": "Greenish", "#FFC9D179": "Greenish Beige", "#FF454445": "Greenish Black", "#FF0B8B87": "Greenish Blue", "#FF696112": "Greenish Brown", "#FF2AFEB7": "Greenish Cyan", "#FF96AE8D": "Greenish Grey", "#FF66675A": "Greenish Grey Bark", "#FFBCCB7A": "Greenish Tan", "#FF32BF84": "Greenish Teal", "#FF00FBB0": "Greenish Turquoise", "#FFD1F1DE": "Greenish White", "#FFCDFD02": "Greenish Yellow", "#FFCAE03B": "Greenivorous", "#FF008C7D": "Greenlake", "#FF737D6A": "Greenland", "#FF367F9A": "Greenland Blue", "#FF22ACAE": "Greenland Green", "#FFB9D7D6": "Greenland Ice", "#FF016844": "Greens", "#FF565C00": "Greensboro", "#FFA5CF7E": "Greenscape", "#FF419A7D": "Greenway", "#FF475B49": "Greenwich", "#FF82826A": "Greenwich Green", "#FFAFBFBE": "Greenwich Village", "#FFBCBAAB": "Greenwood", "#FF067376": "Greeny Glaze", "#FFCBC8DD": "Gregorio Garden", "#FFB0A999": "Greige", "#FF9C8C9A": "Greige Violet", "#FFA79954": "Gremlin", "#FF527E6D": "Gremolata", "#FF8E6268": "Grenache", "#FFC32149": "Grenade", "#FFC14D36": "Grenadier Variant", "#FFAC545E": "Grenadine", "#FFFF616B": "Grenadine Pink", "#FF5D6732": "Gretchin Green", "#FF596442": "Gretna Green", "#FFA8B1C0": "Grey Agate", "#FF8F9394": "Grey Area", "#FFC4C5BA": "Grey Ashlar", "#FF77A1B5": "Grey Blue", "#FF6C8096": "Grey Blueberry", "#FFB4BFC2": "Grey Brook", "#FF7F7053": "Grey Brown", "#FFA1988B": "Grey by Me", "#FF7A5063": "Grey Carmine", "#FF6E6969": "Grey Charcoal", "#FF9FA3A7": "Grey Chateau", "#FFCCC9C5": "Grey Cloth", "#FFB7B7B2": "Grey Clouds", "#FFBBC1CC": "Grey Dawn", "#FFC8C7C5": "Grey Dolphin", "#FF897F98": "Grey Dusk", "#FFB0BFB6": "Grey Embrace", "#FFA1A196": "Grey Expose", "#FFA2998F": "Grey Flanks", "#FF8D9A9E": "Grey Flannel", "#FF788480": "Grey Flannel Suit", "#FFB8BFC2": "Grey Frost", "#FFDDDCDA": "Grey Ghost", "#FFE0E4E2": "Grey Glimpse", "#FFA3A29B": "Grey Gloss", "#FF86A17D": "Grey Green", "#FF868790": "Grey Heather", "#FF89928A": "Grey Heron", "#FFB9BBAD": "Grey Jade", "#FFD4CACD": "Grey Lilac", "#FF72695E": "Grey Locks", "#FFB9B4B1": "Grey Marble", "#FFC87F89": "Grey Matter", "#FFCAB8AB": "Grey Mauve", "#FFABAFAE": "Grey Mirage", "#FF96ACAB": "Grey Mist", "#FF707C78": "Grey Monument", "#FFCABEB5": "Grey Morn", "#FF9EB0AA": "Grey Morning", "#FFD1D3CC": "Grey Nurse", "#FFA2A2A2": "Grey of Darkness", "#FFA19A7F": "Grey Olive", "#FF776F67": "Grey Owl", "#FFCED0CF": "Grey Pearl", "#FFB0B7BE": "Grey Pearl Sand", "#FFCFCAC1": "Grey Pebble", "#FF84827D": "Grey Pepper", "#FFC3909B": "Grey Pink", "#FF4E4E52": "Grey Pinstripe", "#FFDDDDE2": "Grey Placidity", "#FF86837A": "Grey Porcelain", "#FF826D8C": "Grey Purple", "#FF847986": "Grey Ridge", "#FF99A1A1": "Grey River Rock", "#FFC3C0BB": "Grey Roads", "#FFC6B6B2": "Grey Rose", "#FF8E9598": "Grey Russian", "#FFE5CAAF": "Grey Sand", "#FFB8B0AF": "Grey Scape", "#FFC6CACA": "Grey Screen", "#FFC2BDBA": "Grey Shadows", "#FFBAAAAA": "Grey Sheep", "#FFD6D9D8": "Grey Shimmer", "#FF949392": "Grey Shingle", "#FFB0A99A": "Grey Silt", "#FFC8C7C2": "Grey Spell", "#FF989081": "Grey Squirrel", "#FF807A77": "Grey Suit", "#FF959491": "Grey Summit", "#FF5E9B8A": "Grey Teal", "#FFACAEB1": "Grey Timberwolf", "#FF81807D": "Grey Tote", "#FFB0BAB5": "Grey Tweed", "#FF9B8E8E": "Grey Violet", "#FF616669": "Grey Web", "#FF625F5C": "Grey Werewolf", "#FFE6E4E4": "Grey Whisper", "#FFD7D5CB": "Grey White", "#FF9CA0A6": "Grey Wolf", "#FFE5E8E6": "Grey Wonder", "#FFA9BBBC": "Grey Wool", "#FF98916C": "Grey-Headed Woodpecker Green", "#FFD4D0C5": "Greybeard", "#FF92B8A0": "Greyed Jade", "#FFB2ACA2": "Greyhound", "#FFCFCAC7": "Greyish", "#FFA8A495": "Greyish Beige", "#FF555152": "Greyish Black", "#FF5E819D": "Greyish Blue", "#FF7A6A4F": "Greyish Brown", "#FF82A67D": "Greyish Green", "#FFB8B8FF": "Greyish Lavender", "#FFC88D94": "Greyish Pink", "#FF887191": "Greyish Purple", "#FF719F91": "Greyish Teal", "#FFD6DEE9": "Greyish White", "#FF877254": "Greyish Yellow", "#FF948C8D": "Greylac", "#FF596368": "Greys Harbor", "#FF85837E": "Greystoke", "#FFB7B9B5": "Greystone", "#FFAACCBB": "Greywacke", "#FF9D9586": "Greywood", "#FFC3571D": "Grieving Daylily", "#FF70393F": "Griffon Brown", "#FF863B2C": "Grill Master", "#FF633F2E": "Grilled", "#FFFFC85F": "Grilled Cheese", "#FFAF3519": "Grilled Tomato", "#FFE3DCD6": "Grim Grey", "#FFDEADAF": "Grim Pink", "#FF441188": "Grim Purple", "#FF0F1039": "Grim Reaper", "#FFF6F1F4": "Grim White", "#FF50314C": "Grimace", "#FF565143": "Grime", "#FF93B83D": "Grinch Green", "#FFA5A9A8": "Gris", "#FF8F8A91": "Gris Morado", "#FFBCC7CB": "Gris N\u00e1utico", "#FF797371": "Gris Volcanico", "#FF91979F": "Grisaille", "#FFA29371": "Gristmill", "#FF636562": "Grizzle Grey", "#FF885818": "Grizzly", "#FF937043": "Grog Yellow", "#FFDE6491": "Groovy", "#FFEEAA11": "Groovy Giraffe", "#FFD6BE01": "Groovy Lemon Pie", "#FF63615D": "Gropius Grey", "#FFA0BF16": "Gross Green", "#FF64E986": "Grotesque Green", "#FF6F675C": "Grouchy Badger", "#FF604E42": "Ground Bean", "#FF63554B": "Ground Coffee", "#FF8A6C42": "Ground Cumin", "#FF7F5F00": "Ground Earth", "#FFCFCBC4": "Ground Fog", "#FFD9CA9F": "Ground Ginger", "#FFA05A3B": "Ground Nutmeg", "#FF766551": "Ground Pepper", "#FF757577": "Ground Truth", "#FF7B6F60": "Groundbreaking", "#FF64634D": "Groundcover", "#FFD18C62": "Grounded", "#FF1100AA": "Groundwater", "#FFE2C0A8": "Group Hug", "#FFF0E6D3": "Grow", "#FF88CC11": "Growing Nature", "#FFC3CDB0": "Growing Season", "#FF6CA178": "Growth", "#FF811412": "Grubby Red", "#FF4A5B51": "Grubenwald", "#FFC0CF3F": "Grunervetliner", "#FF838585": "Gryphon", "#FF883F11": "Gryphonne Sepia Wash", "#FF634950": "G\u01d4 T\u00f3ng H\u0113i Copper", "#FF95986B": "Guacamole", "#FFD2D392": "Guacamole Ambrosia", "#FFE4E1EA": "Guardian Angel", "#FF88AA22": "Guardian of Gardens", "#FF952E31": "Guardsman Red Variant", "#FFFF982E": "Guava", "#FFEEC0D2": "Guava Glow", "#FFA18D0D": "Guava Green", "#FFE08771": "Guava Jam", "#FFEE9685": "Guava Jelly", "#FFF4B694": "Guava Juice", "#FF142D25": "Guerrilla Forest", "#FFE3E0D2": "Guesthouse", "#FFEB4962": "Guide Pink", "#FFFEE9DA": "Guiding Star", "#FFD2D1CB": "Guild Grey", "#FF987652": "Guinea Pig", "#FFE8E4D6": "Guinea Pig White", "#FF4A8140": "Guinean Green", "#FF6B4C37": "Guitar", "#FF8B2E19": "Gulab Brown", "#FFC772C0": "Gul\u0101b\u012b Pink", "#FF343F5C": "Gulf Blue Variant", "#FFDDDED3": "Gulf Breeze", "#FF015C77": "Gulf Coast", "#FF225E64": "Gulf Harbour", "#FF01858B": "Gulf Stream Variant", "#FF689FC1": "Gulf Stream Blue", "#FF2DA6BF": "Gulf Waters", "#FF686E43": "Gulf Weed", "#FF93B2B2": "Gulf Winds", "#FF918C8F": "Gull", "#FFC2C2BC": "Gull Feather", "#FFA4ADB0": "Gull Grey", "#FFABAEA9": "Gull Wing", "#FF777661": "Gully", "#FF4B6E3B": "Gully Green", "#FFACC9B2": "Gum Leaf Variant", "#FFE7B2D0": "Gumball", "#FF718F8A": "Gumbo Variant", "#FF2EA785": "Gumdrop Green", "#FFFFC69D": "Gumdrops", "#FF06A9CA": "Gummy Dolphins", "#FF979D9A": "Gun Barrel", "#FF6B593C": "Gun Corps Brown", "#FF484753": "Gun Powder Variant", "#FF959984": "Gundaroo Green", "#FF5D8CAE": "Gunj\u014d Blue", "#FF536267": "Gunmetal Variant", "#FF908982": "Gunmetal Beige", "#FF777648": "Gunmetal Green", "#FF808C8C": "Gunmetal Grey", "#FFDCD3BC": "Gunny Sack", "#FFFF0077": "Guns N\u2019 Roses", "#FF818070": "Gunsmith", "#FF7A7C76": "Gunsmoke Variant", "#FFBD7E08": "Guo Tie Dumpling", "#FFAE5883": "Guppy Violet", "#FF989171": "Gurkha Variant", "#FFA49691": "Gustav", "#FFF8AC1D": "Gusto Gold", "#FF705284": "Gutsy Grape", "#FF897A68": "Guy", "#FFDFB46F": "Gyoza Dumpling", "#FFEEEDE4": "Gypsum", "#FFE2C4AF": "Gypsum Rose", "#FFD6CFBF": "Gypsum Sand", "#FFBFE1E6": "H\u2082O", "#FFF98513": "Haba\u00f1ero", "#FFB8473D": "Haba\u00f1ero Chile", "#FFFECF3C": "Haba\u00f1ero Gold", "#FF9E8022": "Hacienda Variant", "#FF0087A8": "Hacienda Blue", "#FFBB7731": "Hacienda Del Sol", "#FFB86D64": "Hacienda Tile", "#FFF0EDE7": "Hacienda White", "#FF277ABA": "Haddock\u2019s Sweater", "#FF1177FF": "Hadfield Blue", "#FF000022": "Hadopelagic Water", "#FFC61A1B": "Haemoglobin Red", "#FFE6C9A3": "Hagar Shores", "#FFC3C7B2": "Haggis", "#FF8B837D": "Hagstone Choir", "#FF2C75FF": "Hailey Blue", "#FFD0D1E1": "Hailstorm", "#FFBDBEB9": "Hailstorm Grey", "#FFC1D8D9": "Haint Blue", "#FF8B7859": "Hair Brown", "#FF939CC9": "Hair Ribbon", "#FF633528": "Hairy Heath Variant", "#FF2C2A35": "Haiti Variant", "#FF97495A": "Haitian Flower", "#FF88B378": "Hakusai Green", "#FFF0E483": "Halak\u0101 P\u012bl\u0101", "#FFD1D1CE": "Halation", "#FFF7BB73": "Halcyon Days", "#FF9BAAA2": "Halcyon Green", "#FF558F93": "Half Baked Variant", "#FFFBF0D6": "Half Dutch White Variant", "#FFCCCDB9": "Half Moon", "#FFCDA894": "Half Moon Bay Blush", "#FF976F3C": "Half Orc Highlight", "#FFF1EAD7": "Half Pearl Lusta", "#FFA9B8BB": "Half Sea Fog", "#FFE6DBC7": "Half Spanish White Variant", "#FF604C3D": "Half-Caff", "#FFEE8855": "Half-Smoke", "#FFC5B583": "Halfway Green", "#FF09324A": "Halite Blue", "#FFE2EBE5": "Hallowed Hush", "#FFFE653C": "Halloween", "#FFEB6123": "Halloween Party", "#FFDD2211": "Halloween Punch", "#FFE2C392": "Halo", "#FFF4E5D2": "Halogen", "#FFB0BAD5": "Halogen Blue", "#FFFF6633": "Halt and Catch Fire", "#FFA34E25": "Hamburger", "#FF8A99A4": "Hamilton Blue", "#FF65DCD6": "Hammam Blue", "#FF834831": "Hammered Copper", "#FFCB9D5E": "Hammered Gold", "#FF7E7567": "Hammered Pewter", "#FF978A7F": "Hammered Silver", "#FF4E7496": "Hammerhead Shark", "#FF6D8687": "Hammock", "#FFD9CADF": "Hampstead Heath", "#FFE8D4A2": "Hampton Variant", "#FF9D603B": "Hampton Beach", "#FF4F604F": "Hampton Green", "#FF597681": "Hampton Surf", "#FFA6814C": "Hamster Fur", "#FFC4D6AF": "Hamster Habitat", "#FFB07426": "Hamtaro Brown", "#FF1D697C": "Hanaasagi Blue", "#FF044F67": "Hanada Blue", "#FFF2ABE1": "Hanami Pink", "#FF4D6968": "Hancock", "#FFCEECEE": "Hand Sanitizer", "#FF7F735F": "Handmade", "#FFDDD6B7": "Handmade Linen", "#FFA87678": "Handmade Red", "#FF355887": "Handsome Blue", "#FF5286BA": "Handsome Hue", "#FFBFA984": "Handwoven", "#FF11AA44": "Hanging Gardens of Babylon", "#FF5C7F76": "Hanging Moss", "#FF6E897D": "Hanging Vine", "#FF685D4A": "Hannover Hills", "#FFDAC5B1": "Hanover", "#FF885D53": "Hanover Brick", "#FF848472": "Hanover Pewter", "#FFE9D66C": "Hansa Yellow", "#FF55FFAA": "Hanuman Green", "#FFE3D6C7": "Hanyauku", "#FFF8D664": "Happy", "#FF6B8350": "Happy Camper", "#FF979EA1": "Happy Cement", "#FF72A86A": "Happy Cricket", "#FF506E82": "Happy Days", "#FFF7CF1B": "Happy Daze", "#FFFFD10B": "Happy Face", "#FFD46362": "Happy Hearts", "#FF818581": "Happy Hippo", "#FFE0DABF": "Happy Margarine", "#FFF6CBCA": "Happy Piglets", "#FFFFBE98": "Happy Prawn", "#FFFAEED7": "Happy Skeleton", "#FFD1DFEB": "Happy Thoughts", "#FFB67A63": "Happy Trails", "#FF96B957": "Happy Tune", "#FFFFC217": "Happy Yipee", "#FFBACAD3": "Happy-Go-Lucky", "#FFE2D4D6": "Hapsburg Court", "#FF55AA55": "Har\u0101 Green", "#FF504A6F": "Harajuku Girl", "#FF495867": "Harbour", "#FFE0E9F3": "Harbour Afternoon", "#FF417491": "Harbour Blue", "#FFAFB1B4": "Harbour Fog", "#FFA8C0BB": "Harbour Grey", "#FFD7E0E7": "Harbour Light", "#FF88AAAA": "Harbour Mist", "#FF778071": "Harbour Mist Grey", "#FF757D75": "Harbour Rat", "#FF7EB6D0": "Harbour Sky", "#FF4E536B": "Harbourmaster", "#FFFFBBBB": "Hard Candy", "#FF656464": "Hard Coal", "#FF8B8372": "Hardware", "#FFB56C23": "Hardy Clay", "#FF006383": "Harem Silk", "#FFBCCF8F": "Haricot", "#FFA52A2A": "Harissa Red", "#FF46CB18": "Harlequin Green", "#FFC93413": "Harley Davidson Orange", "#FF8547B5": "Harley Hair Purple", "#FFBB0000": "Harlock\u2019s Cape", "#FFC1B287": "Harmonic Tan", "#FFAFC195": "Harmonious", "#FFEACFA3": "Harmonious Gold", "#FFF29CB7": "Harmonious Rose", "#FFEBC9C1": "Harmony", "#FF6DA493": "Harmony in Green", "#FF6D6353": "Harold", "#FFCBCEC0": "Harp Variant", "#FFCBD0D1": "Harp Strings", "#FF283B4C": "Harpoon", "#FFDCB571": "Harpswell Green", "#FF493C2B": "Harpy Brown", "#FF989B9E": "Harrison Grey", "#FF9A5F3F": "Harrison Rust", "#FF7E8E90": "Harrow\u2019s Gate", "#FFEACB9D": "Harvest", "#FFCB862C": "Harvest at Dusk", "#FFBA8E4E": "Harvest Blessing", "#FFB9A589": "Harvest Brown", "#FFA5997C": "Harvest Dance", "#FFEAB76A": "Harvest Gold Variant", "#FFDE6931": "Harvest Haze", "#FFCBAE84": "Harvest Home", "#FF554488": "Harvest Night", "#FF65564F": "Harvest Oak", "#FFCD632A": "Harvest Pumpkin", "#FFCF875F": "Harvest Time", "#FFF7D7C4": "Harvest Wreath", "#FFEDC38E": "Harvester", "#FFBFA46F": "Hashibami Brown", "#FF8D608C": "Hashita Purple", "#FFC9643B": "Hashut Copper", "#FF009E6D": "Hassan II Mosque", "#FF8F775D": "Hat Box Brown", "#FFCFEBDE": "Hatching Chameleon", "#FFB2D1D6": "Hatchling Blue", "#FF95859C": "Hatoba Pigeon", "#FF9E8B8E": "Hatoba-Nezumi Grey", "#FF929E9D": "Hatteras Grey", "#FF57446A": "Haunted Candelabra", "#FF333355": "Haunted Dreams", "#FF032E0E": "Haunted Forest", "#FF003311": "Haunted Hills", "#FF991177": "Haunted Purple", "#FFD3E0EC": "Haunting Hue", "#FF824855": "Haunting Melody", "#FFA0252A": "Haute Couture", "#FF545E49": "Haute Green", "#FF9F7B58": "Haute Hickory", "#FFD899B1": "Haute Pink", "#FFAA1829": "Haute Red", "#FF3B2B2C": "Havana", "#FFA5DBE5": "Havana Blue", "#FFAF884A": "Havana Cigar", "#FF554941": "Havana Coffee", "#FFF9E5C2": "Havana Cream", "#FF007993": "Havasu", "#FF0FAFC6": "Havasupai Falls", "#FF5784C1": "Havelock Blue Variant", "#FFA3B48C": "Haven", "#FF00BBFF": "Hawaii Morning", "#FFD24833": "Hawaiian Ahi Poke", "#FF75C7E0": "Hawaiian Breeze", "#FF6F4542": "Hawaiian Cinder", "#FF99522C": "Hawaiian Coconut", "#FFFAE8B8": "Hawaiian Cream", "#FF96300D": "Hawaiian Malasada", "#FF008DB9": "Hawaiian Ocean", "#FFFFA03E": "Hawaiian Passion", "#FFFDD773": "Hawaiian Pineapple", "#FFFF0051": "Hawaiian Raspberry", "#FFF3DBD9": "Hawaiian Shell", "#FF83A2BD": "Hawaiian Sky", "#FFC06714": "Hawaiian Sunset", "#FF008DBB": "Hawaiian Surf", "#FF1D7033": "Hawaiian Ti Leaf", "#FF77CABD": "Hawaiian Vacation", "#FFAE8C5C": "Hawk Feather", "#FF77757D": "Hawk Grey", "#FF00756A": "Hawk Turquoise", "#FF34363A": "Hawk\u2019s Eye", "#FFFDDB6D": "Hawkbit", "#FFF4C26C": "Hawker\u2019s Gold", "#FFD2DAED": "Hawkes Blue Variant", "#FF729183": "Hawkesbury", "#FFCC1111": "Hawthorn Berry", "#FFEEFFAA": "Hawthorn Blossom", "#FF884C5E": "Hawthorn Rose", "#FFCED7C1": "Hawthorne", "#FFD3CAA3": "Hay", "#FFDACD81": "Hay Day", "#FFCDAD59": "Hay Wain", "#FFC2A770": "Hay Yellow", "#FF5F5D50": "Hayden Valley", "#FFCDBA96": "Hayloft", "#FFD4AC99": "Hayride", "#FFCFAC47": "Haystack", "#FFC8C2C6": "Haze", "#FFC39E6D": "Hazed Nuts", "#FFA36B4B": "Hazel", "#FFEAE2DE": "Hazel Blush", "#FFB8BFB1": "Hazel Gaze", "#FFA8715A": "Hazelnut", "#FFD28A47": "Hazelnut Coffee", "#FFE6DFCF": "Hazelnut Cream", "#FF742719": "Hazelnut Kisses", "#FFEEAA77": "Hazelnut Milk", "#FFD9BE9D": "Hazelnut Parfait", "#FFFCE974": "Hazelnut Turkish Delight", "#FFFFF3D5": "Hazelwood", "#FFB4C2CF": "Hazy", "#FFBCC8CC": "Hazy Blue", "#FFF2CC99": "Hazy Day", "#FFA5B8C5": "Hazy Daze", "#FFD2BAA1": "Hazy Earth", "#FFF2F1DC": "Hazy Grove", "#FFC7BAC0": "Hazy Iris", "#FFC8C6CE": "Hazy Mauve", "#FFF1DCA1": "Hazy Moon", "#FFB39897": "Hazy Rose", "#FFADBBC4": "Hazy Skies", "#FFB7BDD6": "Hazy Sky", "#FFA6A5A0": "Hazy Stratus", "#FFD5C3B5": "Hazy Taupe", "#FFDCDACE": "Hazy Trail", "#FFEACEA0": "Hazy Yellow", "#FFE1DBE3": "He Loves Me", "#FF7F5E00": "H\u00e8 S\u00e8 Brown", "#FFD1DDE1": "Head in the Clouds", "#FFEBE2DE": "Head in the Sand", "#FF605972": "Head Over Heels", "#FFB9CAB3": "Healing Aloe", "#FF6C7D42": "Healing Plant", "#FFBAC2AA": "Healing Retreat", "#FFE1E2C2": "Healing Springs", "#FFD2DCCE": "Healthy Wealthy and Wise", "#FF5BBD7F": "Heart Chakra", "#FF9D7F4C": "Heart of Gold", "#FFF7FCFF": "Heart of Ice", "#FFD2CFA6": "Heart of Palm", "#FFA97FB1": "Heart Potion", "#FFEDE3DF": "Heart Stone", "#FFD4A9C3": "Heart", "#FFE2B5BD": "Heart\u2019s Content", "#FFAC3E5F": "Heart\u2019s Desire", "#FFAA0000": "Heartbeat", "#FFCC76A3": "Heartbreaker", "#FFFFADC9": "Heartfelt", "#FFE1CCA6": "Hearth", "#FFB2C4BB": "Hearth Frost", "#FFA17135": "Hearth Gold", "#FFC7BEB2": "Hearthstone", "#FFA7C297": "Heartland Frosty", "#FF623B70": "Heartless", "#FFD47A76": "Hearts Afire", "#FFCFC291": "Hearts of Palm", "#FF9BAFD0": "Heartsong", "#FFA82E33": "Heartthrob", "#FFBF1818": "Heartwarming", "#FF6F4232": "Heartwood", "#FF96BF83": "Hearty Hosta", "#FFB44B34": "Hearty Orange", "#FFE98D5B": "Heat of Summer", "#FFE3000E": "Heat Signature", "#FF4F2A2C": "Heath Variant", "#FF9ACDA9": "Heath Green", "#FFC9CBC2": "Heath Grey", "#FF9F5F9F": "Heath Spotted Orchid", "#FFA484AC": "Heather Variant", "#FFB8ACAF": "Heather Bay", "#FFC3ADC5": "Heather Feather", "#FF909095": "Heather Field", "#FFBBB0BB": "Heather Hill", "#FF998E8F": "Heather Moor", "#FFA39699": "Heather Plume", "#FF988E94": "Heather Red Grey", "#FFA76372": "Heather Rose", "#FF7B7173": "Heather Sachet", "#FFB18398": "Heather Violet", "#FFEE4422": "Heating Lamp", "#FFFF7788": "Heatstroke", "#FFB4CED5": "Heaven", "#FFC7F1FF": "Heaven Gates", "#FFEEE1EB": "Heaven Sent", "#FFCAD6DE": "Heaven Sent Storm", "#FF7EB2C5": "Heavenly", "#FFEEDFD5": "Heavenly Aromas", "#FFA3BBCD": "Heavenly Blue", "#FFBEA79D": "Heavenly Cocoa", "#FF93A394": "Heavenly Garden", "#FFD8D5E3": "Heavenly Haze", "#FFF4DEDE": "Heavenly Pink", "#FF6B90B3": "Heavenly Sky", "#FFFBD9C6": "Heavenly Song", "#FFEBE8E6": "Heavenly White", "#FF3A514D": "Heavy Black Green", "#FF2C5674": "Heavy Blue", "#FF9FABAF": "Heavy Blue Grey", "#FF73624A": "Heavy Brown", "#FF565350": "Heavy Charcoal", "#FFE8DDC6": "Heavy Cream", "#FFDDCCAA": "Heavy Gluten", "#FFBAAB74": "Heavy Goldbrown", "#FF49583E": "Heavy Green", "#FF82868A": "Heavy Grey", "#FFBEB9A2": "Heavy Hammock", "#FF771122": "Heavy Heart", "#FF5E6A34": "Heavy Khaki", "#FF46473E": "Heavy Metal Variant", "#FF888A8E": "Heavy Metal Armour", "#FF9B753D": "Heavy Ochre", "#FFEE4328": "Heavy Orange", "#FF898A86": "Heavy Rain", "#FF9E1212": "Heavy Red", "#FF735848": "Heavy Siena", "#FFEFF5F1": "Heavy Sugar", "#FF4F566C": "Heavy Violet", "#FFBDB3A7": "Heavy Warm Grey", "#FFF0E4D2": "Hectorite", "#FF768A75": "Hedge Green", "#FF00AA11": "Hedged Garden", "#FFC4AA5E": "Hedgehog Cactus Yellow Green", "#FFFAF0DA": "Hedgehog Mushroom", "#FF142030": "H\u0113i S\u00e8 Black", "#FF960117": "Heidelberg Red", "#FFC3BDB1": "Heifer", "#FFB67B71": "Heirloom", "#FFF4BEA6": "Heirloom Apricot", "#FFE3664C": "Heirloom Blush", "#FF327CCB": "Heirloom Hydrangea", "#FFF5E6D6": "Heirloom Lace", "#FF9D96B2": "Heirloom Lilac", "#FFAE9999": "Heirloom Orchid", "#FFDBD5D0": "Heirloom Pink", "#FFAB979A": "Heirloom Quilt", "#FF801F23": "Heirloom Red", "#FFD182A0": "Heirloom Rose", "#FFDCD8D4": "Heirloom Shade", "#FFB5B6AD": "Heirloom Silver", "#FF833633": "Heirloom Tomato", "#FF70D4FB": "Heisenberg Blue", "#FFC3B89F": "Helen of Troy", "#FFD28B72": "Helena Rose", "#FFD94FF5": "Heliotrope Variant", "#FFAB98A9": "Heliotrope Grey", "#FFAA00BB": "Heliotrope Magenta", "#FF9187BD": "Heliotropic Mauve", "#FFEAE5D8": "Helium", "#FFC40700": "Hell Rider", "#FFBA622A": "Hell\u2019s Bells", "#FF710101": "Hellbound", "#FF646944": "Hellebore", "#FF87C5AE": "Hellion Green", "#FF802280": "Hello Darkness My Old Friend", "#FFF3C7D1": "Hello Dolly", "#FF995533": "Hello Fall", "#FF44DD66": "Hello Spring", "#FF55BBFF": "Hello Summer", "#FF99FFEE": "Hello Winter", "#FFFFE59D": "Hello Yellow", "#FFF00000": "Helvetia Red", "#FF5F615F": "Hematite", "#FFDC8C59": "Hematitic Sand", "#FF5285A4": "Hemisphere", "#FF69684B": "Hemlock Variant", "#FFECEEDF": "Hemlock Bud", "#FF987D73": "Hemp Variant", "#FFB5AD88": "Hemp Fabric", "#FFB9A379": "Hemp Rope", "#FF864941": "Henna", "#FF6E3530": "Henna Red", "#FFB3675D": "Henna Shade", "#FFC4B146": "Hep Green", "#FFFBE5EA": "Hepatica", "#FFE1D4B6": "Hephaestus", "#FFFF9911": "Hephaestus Gold", "#FF6F123C": "Her Fierceness", "#FF432E6F": "Her Highness", "#FFF9A4A4": "Her Majesty", "#FFBB5F62": "Her Velour", "#FF7777EE": "Hera Blue", "#FFA46366": "Herald of Spring", "#FFCE9F2F": "Herald\u2019s Trumpet", "#FF444161": "Heraldic", "#FFE7E0D3": "Herare White", "#FF708452": "Herb", "#FF4B856C": "Herb Blend", "#FF6E7357": "Herb Cornucopia", "#FFE9F3E1": "Herb Garden", "#FF64654A": "Herb Garland", "#FFDDA0DF": "Herb Robert", "#FF9CAD60": "Herbal Garden", "#FFC9B23D": "Herbal Green", "#FF6BA520": "Herbal Lift", "#FFD2E6D3": "Herbal Mist", "#FF8E9B7C": "Herbal Scent", "#FFF9FEE9": "Herbal Tea", "#FFDDFFCC": "Herbal Vapours", "#FFA49B82": "Herbal Wash", "#FF6B6D4E": "Herbal Whispers", "#FF969E86": "Herbalist", "#FF119900": "Herbalist\u2019s Garden", "#FFEEEE22": "Herbery Honey", "#FFA9A487": "Herbes", "#FF3A5F49": "Herbes de Provence", "#FF88EE77": "Herbivore", "#FFFCDF63": "Here Comes the Sun", "#FF5F3B36": "Hereford Bull", "#FFB0BACC": "Heritage", "#FF5296B7": "Heritage Blue", "#FFCAC2B8": "Heritage Grey", "#FF5C453D": "Heritage Oak", "#FF69756C": "Heritage Park", "#FF956F7B": "Heritage Taffeta", "#FF9A8EC1": "Hermosa", "#FFFFB3F0": "Hermosa Pink", "#FF005D6A": "Hero", "#FF1166FF": "Heroic Blue", "#FFCBD5E9": "Heroic Heron", "#FFD1191C": "Heroic Red", "#FF6A6887": "Heron", "#FFE5E1D8": "Heron Plume", "#FFB1C4CD": "Heron Wing", "#FFC6C8CF": "Herring Silver", "#FFFFE296": "Hesperide Apple Gold", "#FFEE2200": "Hestia Red", "#FF6E0060": "Hexed Lichen", "#FFFBFF0A": "Hexos Palesun", "#FF16F8FF": "Hey Blue!", "#FFCD8E43": "Hey Honey!", "#FFBEA932": "Hey Pesto", "#FFBBB465": "Hi Def Lime", "#FFACA69F": "Hibernate", "#FF6F5166": "Hibernation", "#FFFE9773": "Hibiscus Delight", "#FFBC555E": "Hibiscus Flower", "#FF6E826E": "Hibiscus Leaf", "#FFEDAAAC": "Hibiscus Petal", "#FFDD77DD": "Hibiscus Pop", "#FF5C3D45": "Hibiscus Punch", "#FFA33737": "Hibiscus Red", "#FFB7A28E": "Hickory", "#FFAB8274": "Hickory Branch", "#FF69482A": "Hickory Chips", "#FF7C6E6D": "Hickory Cliff", "#FF655341": "Hickory Grove", "#FF78614C": "Hickory Nut", "#FF614539": "Hickory Plank", "#FF997772": "Hickory Stick", "#FFBC916F": "Hickory Tint", "#FF9C949B": "Hidcote", "#FF8D7F64": "Hidden Cottage", "#FFCEC6BD": "Hidden Cove", "#FFD5DAE0": "Hidden Creek", "#FF305451": "Hidden Depths", "#FFEDE4CC": "Hidden Diary", "#FF4F5A51": "Hidden Forest", "#FF98AD8E": "Hidden Glade", "#FF60737D": "Hidden Harbour", "#FFC5D2B1": "Hidden Hills", "#FFEBF1E2": "Hidden Jade", "#FF96748A": "Hidden Mask", "#FFBBCC5A": "Hidden Meadow", "#FFAA7C4C": "Hidden Morel", "#FF5E8B3D": "Hidden Paradise", "#FF3C3005": "Hidden Passage", "#FFE4D5B9": "Hidden Path", "#FF727D7F": "Hidden Peak", "#FF445771": "Hidden Sapphire", "#FF6FD1C9": "Hidden Sea Glass", "#FF1B8CB6": "Hidden Springs", "#FF5F5B4D": "Hidden Trail", "#FFA59074": "Hidden Treasure", "#FFBB9900": "Hidden Tribe", "#FF689938": "Hidden Valley", "#FF225258": "Hidden Waters", "#FFC8C0AA": "Hideaway", "#FF5386B7": "Hideout", "#FF77A373": "Hierba Santa", "#FF334F7B": "High Altar", "#FFA9DEDA": "High Altitude", "#FF4CA8E0": "High Blue", "#FF75603D": "High Chaparral", "#FFA06974": "High Country Rose", "#FF59B9CC": "High Dive", "#FF9A3843": "High Drama", "#FF665D25": "High Forest Green", "#FFBBDD00": "High Grass", "#FFEAEBE4": "High Hide White", "#FFE0B44D": "High Honey", "#FFDEEAAA": "High Hopes", "#FFD88CB5": "High Maintenance", "#FFCFB999": "High Noon", "#FF867A88": "High Note", "#FFE4B37A": "High Plateau", "#FFBCD8D2": "High Point", "#FF643949": "High Priest", "#FF005A85": "High Profile", "#FF645453": "High Rank", "#FFF7F7F1": "High Reflective White", "#FFAEB2B5": "High Rise", "#FFC71F2D": "High Risk Red", "#FF445056": "High Salute", "#FF7DABD8": "High Seas", "#FFCEDEE2": "High Sierra", "#FFCAB7C0": "High Society", "#FFAC9825": "High Strung", "#FFA8B1D7": "High Style", "#FFE4D7C3": "High Style Beige", "#FF7F6F57": "High Tea", "#FF567063": "High Tea Green", "#FF85A6C8": "High Tide", "#FFEEFF11": "High Voltage", "#FF6D7074": "High-Speed Steel", "#FF928C3C": "Highball", "#FF305144": "Highland Green", "#FF8F714B": "Highland Ridge", "#FFB9A1AE": "Highland Thistle", "#FFD3DAE3": "Highland Winds", "#FF3A533D": "Highlander", "#FF449084": "Highlands", "#FF445500": "Highlands Moss", "#FF484A80": "Highlands Twilight", "#FFEEF0DE": "Highlight", "#FFDFC16D": "Highlight Gold", "#FFFFE536": "Highlighter", "#FF3AAFDC": "Highlighter Blue", "#FF1BFC06": "Highlighter Green", "#FF85569C": "Highlighter Lavender", "#FFD72E83": "Highlighter Lilac", "#FFF39539": "Highlighter Orange", "#FFEA5A79": "Highlighter Pink", "#FFE94F58": "Highlighter Red", "#FF009E6C": "Highlighter Turquoise", "#FFF1E740": "Highlighter Yellow", "#FFBDB388": "Highway", "#FFCD1102": "Highway Variant", "#FF752E23": "Hihada Brown", "#FFD2B395": "Hiker\u2019s Delight", "#FF9D9866": "Hiking", "#FF5E5440": "Hiking Boots", "#FFA99170": "Hiking Trail", "#FFE0EEDF": "Hill Giant", "#FF8DC248": "Hill Lands", "#FFA7A07E": "Hillary Variant", "#FF417B42": "Hills of Ireland", "#FF7FA91F": "Hillsbrad Grass", "#FF8F9783": "Hillside Green", "#FF80BF69": "Hillside Grove", "#FF8DA090": "Hillside View", "#FF587366": "Hilltop", "#FF768AA1": "Hilo Bay", "#FF736330": "Himalaya Variant", "#FFAECDE0": "Himalaya Blue", "#FFE2EAF0": "Himalaya Peaks", "#FF7695C2": "Himalaya Sky", "#FFB9DEE9": "Himalaya White Blue", "#FFFF99CC": "Himalayan Balsam", "#FFE1F0ED": "Himalayan Mist", "#FFBEC6D6": "Himalayan Poppy", "#FFC07765": "Himalayan Salt", "#FFFCC800": "Himawari Yellow", "#FFBDC9E3": "Hindsight", "#FFEE4D83": "Hindu Lotus", "#FFF8DDB7": "Hinoki", "#FFBC002D": "Hinomaru Red", "#FFCEE1F2": "Hint of Blue", "#FFD5933D": "Hint of Caramel", "#FFDC6080": "Hint of Cherry", "#FFE4DED0": "Hint of Cream", "#FFDFEADE": "Hint of Green Variant", "#FFFFD66D": "Hint of Honey", "#FFE1DBD5": "Hint of Mauve", "#FFDFF1D6": "Hint of Mint", "#FFF8E6D9": "Hint of Orange", "#FFF1E4E1": "Hint of Pink", "#FFF6DFE0": "Hint of Red Variant", "#FFEEE8DC": "Hint of Vanilla", "#FFD2D5E1": "Hint of Violet", "#FFFAF1CD": "Hint of Yellow Variant", "#FF616C51": "Hinterland", "#FF304112": "Hinterlands Green", "#FFCED9DD": "Hinting Blue", "#FFE4E8A7": "Hip Hop", "#FF746A51": "Hip Waders", "#FF49889A": "Hippie Blue Variant", "#FF608A5A": "Hippie Green Variant", "#FFAB495C": "Hippie Pink Variant", "#FFC6AA2B": "Hippie Trail", "#FF5C3C0D": "Hippogriff Brown", "#FFCFC294": "Hippolyta", "#FFEAE583": "Hippy", "#FFF2F1D9": "Hipster", "#FFBFB3AB": "Hipster Hippo", "#FFFD7C6E": "Hipster Salmon", "#FF88513E": "Hipsterfication", "#FF9BB9E1": "His Eyes", "#FFABCED8": "Hisoku Blue", "#FFFDF3E3": "Historic Cream", "#FFADA791": "Historic Shade", "#FFA18A64": "Historic Town", "#FFEBE6D7": "Historic White", "#FFA7A699": "Historical Grey", "#FFBFB9A7": "Historical Ruins", "#FF38B48B": "Hisui Kingfisher", "#FFFDA470": "Hit Pink Variant", "#FFEEFFA9": "Hitchcock Milk", "#FFC48D69": "Hitching Post", "#FFEE66FF": "Hitsujiyama Pink", "#FFFFFF77": "Hive", "#FFF1C24B": "Hive Delight", "#FFDBECFB": "Hoarfrost", "#FF01AD8F": "Hobgoblin", "#FF59685F": "Hockham Green", "#FF57A9D4": "Hoeth Blue", "#FFDCD1BB": "Hog Bristle", "#FFFBE8E4": "Hog-Maw", "#FFDAD5C7": "Hog\u2019s Pudding", "#FFBB8E34": "Hokey Pokey Variant", "#FF647D86": "Hoki Variant", "#FF7736D9": "Hokkaido Lavender", "#FF547D86": "Holbein Blue Grey", "#FF705446": "Hold Your Horses", "#FF4AAE97": "Hole in One", "#FF598069": "Holenso", "#FF81C3B4": "Holiday", "#FF32BCD1": "Holiday Blue", "#FF6D9E7A": "Holiday Camp", "#FFB1D1E2": "Holiday Road", "#FF8AC6BD": "Holiday Turquoise", "#FF206174": "Holiday Velvet", "#FFB78846": "Holiday Waffle", "#FFCB4543": "Holland Red", "#FFDD9789": "Holland Tile", "#FFF89851": "Holland Tulip", "#FFFFEE44": "Hollandaise", "#FFAC8E84": "Hollow Brown", "#FF330055": "Hollow Knight", "#FF25342B": "Holly Variant", "#FFB44E5D": "Holly Berry", "#FF355D51": "Holly Bush", "#FF8CB299": "Holly Fern", "#FFA2B7B5": "Holly Glen", "#FF0F9D76": "Holly Green", "#FFB50729": "Holly Jolly Christmas", "#FF2E5A50": "Holly Leaf", "#FF913881": "Hollyhock", "#FFB7737D": "Hollyhock Bloom", "#FFBD79A5": "Hollyhock Blossom Pink", "#FFC2A1B5": "Hollyhock Pink", "#FFC7AF4A": "Hollywood", "#FFDEE7D4": "Hollywood Asparagus", "#FFF400A0": "Hollywood Cerise Variant", "#FFECD8B1": "Hollywood Golden Age", "#FFF2D082": "Hollywood Starlet", "#FFDBBB9E": "Holmes Cream", "#FFF0E5F5": "Holographic Lavender", "#FFDB783E": "Holy Cannoli", "#FF332F2C": "Holy Crow", "#FFEFE9E6": "Holy Ghost", "#FFE8D720": "Holy Grail", "#FF466E77": "Holy Water", "#FF666D69": "Homburg Grey", "#FFE8CABA": "Home and Hearth", "#FFF3D2B2": "Home Body", "#FF897B66": "Home Brew", "#FFF7EEDB": "Home Plate", "#FFF2EEC7": "Home Song", "#FF9B7E65": "Home Sweet Home", "#FF726E69": "Homebush", "#FF63884A": "Homegrown", "#FFB18D75": "Homeland", "#FFAC8674": "Homestead", "#FF6F5F52": "Homestead Brown", "#FF986E6E": "Homestead Red", "#FFE9BF91": "Homeward Bound", "#FF2299DD": "Homeworld", "#FFF0D8AF": "Homey Cream", "#FF5F7C47": "Homoeopathic", "#FFDBE7E3": "Homoeopathic Blue", "#FFE1EBD8": "Homoeopathic Green", "#FFE5E0EC": "Homoeopathic Lavender", "#FFE1E0EB": "Homoeopathic Lilac", "#FFE9F6E2": "Homoeopathic Lime", "#FFE5EAD8": "Homoeopathic Mint", "#FFF2E6E1": "Homoeopathic Orange", "#FFECDBE0": "Homoeopathic Red", "#FFE8DBDD": "Homoeopathic Rose", "#FFEDE7D7": "Homoeopathic Yellow", "#FF9D9887": "Honed Soapstone", "#FF867C83": "Honed Steel", "#FF9BB8E2": "Honest", "#FF5A839E": "Honest Blue", "#FFDFEBE9": "Honesty", "#FFAE8934": "Honey", "#FFF1DDAD": "Honey and Cream", "#FFAAAA00": "Honey and Thyme", "#FFFFAA99": "Honey Baked Ham", "#FFE8C281": "Honey Bear", "#FFFCDFA4": "Honey Bee", "#FFD39F5F": "Honey Beehive", "#FFF3E2C6": "Honey Beige", "#FFFFD28D": "Honey Bird", "#FFF5CF9B": "Honey Blush", "#FFDAA47A": "Honey Bronze", "#FFDBB881": "Honey Bunny", "#FFF5D29B": "Honey Butter", "#FFFF9955": "Honey Carrot Cake", "#FF883344": "Honey Chilli", "#FFE9C160": "Honey Crisp", "#FFFFBB55": "Honey Crusted Chicken", "#FFEDEDC7": "Honey Do", "#FF5C3C6D": "Honey Flower Variant", "#FFD18E54": "Honey Fungus", "#FF884422": "Honey Garlic Beef", "#FFBA6219": "Honey Ginger", "#FFFFD775": "Honey Glaze", "#FFE8B447": "Honey Glow", "#FFE1B67C": "Honey Gold", "#FFBC886A": "Honey Graham", "#FFDCB149": "Honey Grove", "#FFBC9263": "Honey Haven", "#FFDDCCBB": "Honey Lime Chicken", "#FFFFC367": "Honey Locust", "#FFA46D5C": "Honey Maple", "#FFE5D9B2": "Honey Mist", "#FFFBECCC": "Honey Moth", "#FFB28C4B": "Honey Mustard", "#FFF1DDA2": "Honey Nectar", "#FFE0BB96": "Honey Nougat", "#FFFAEED9": "Honey Oat Bread", "#FFDBBF9A": "Honey Peach", "#FFCC99AA": "Honey Pink", "#FFDFBB86": "Honey Robber", "#FFD8BE89": "Honey Tea", "#FFEE6611": "Honey Teriyaki", "#FFF8DC9B": "Honey Tone", "#FFFFAA22": "Honey Wax", "#FFCA9456": "Honey Yellow", "#FF937016": "Honey Yellow Green", "#FFF3F0D9": "Honey Yoghurt Popsicles", "#FFDDAA11": "Honeycomb", "#FFE4CF99": "Honeycomb Glow", "#FFDE9C52": "Honeycomb Yellow", "#FFE6ECCC": "Honeydew Melon", "#FFD4FB79": "Honeydew Peel", "#FFEECE8D": "Honeydew Sand", "#FFEFC488": "Honeyed Glow", "#FFD7BB80": "Honeymoon", "#FFFFC863": "Honeypot", "#FFE8ED69": "Honeysuckle Variant", "#FFEACA90": "Honeysuckle Beige", "#FFB3833F": "Honeysuckle Blast", "#FFE8D4C9": "Honeysuckle Delight", "#FFDE8F8D": "Honeysuckle Rose", "#FFFBF1C8": "Honeysuckle Vine", "#FFF8ECD3": "Honeysuckle White", "#FFE9CFC8": "Honeysweet", "#FFE02006": "H\u00f3ng B\u01ceo Sh\u016b Red", "#FF948E90": "Hong Kong Mist", "#FF676E7A": "Hong Kong Skyline", "#FFA8102A": "Hong Kong Taxi", "#FFCF3F4F": "H\u00f3ng L\u00f3u M\u00e8ng Red", "#FFFF0809": "H\u00f3ng S\u00e8 Red", "#FF564A33": "H\u00f3ng Z\u014dng Brown", "#FFFCEFD1": "Honied White", "#FF446A8D": "Honky Tonk Blue", "#FF96CBF1": "Honolulu", "#FF007FBF": "Honolulu Blue", "#FF164576": "Honourable Blue", "#FFFFC9C4": "Hooked Mimosa", "#FF4455FF": "Hooloovoo Blue", "#FFCD6D93": "Hopbush Variant", "#FFE581A0": "Hope", "#FF875942": "Hope Chest", "#FFF2D4E2": "Hopeful", "#FFA2B9BF": "Hopeful Blue", "#FF95A9CD": "Hopeful Dream", "#FF174871": "Hopi Blue Corn", "#FF9E8163": "Hopsack", "#FFAFBB42": "Hopscotch", "#FFF2E9D9": "Horchata", "#FF789B73": "Horenso Green", "#FF648894": "Horizon Variant", "#FFAD7171": "Horizon Glow", "#FF9CA9AA": "Horizon Grey", "#FF80C1E2": "Horizon Haze", "#FFCDD4C6": "Horizon Island", "#FFC2C3D3": "Horizon Sky", "#FF51576F": "Hormagaunt Purple", "#FFBBA46D": "Horn of Plenty", "#FF332222": "Hornblende", "#FF234E4D": "Hornblende Green", "#FFC2AE87": "Horned Frog", "#FFE8EAD5": "Horned Lizard", "#FFD5DFD3": "Hornet Nest", "#FFFF0033": "Hornet Sting", "#FFA67C08": "Hornet Yellow", "#FFD34D4D": "Horror Snob", "#FFE6DFC4": "Horseradish", "#FFEEEADD": "Horseradish Cream", "#FFFFDEA9": "Horseradish Yellow", "#FF6D562C": "Horses Neck Variant", "#FF8B8893": "Horseshoe", "#FF3D5D42": "Horsetail", "#FF9A6C39": "Horsing Around", "#FF61435B": "Hortensia", "#FFDBB8BF": "Hosanna", "#FF9BE5AA": "Hospital Green", "#FFDCDDE7": "Hosta Flower", "#FF475A56": "Hostaleaf", "#FFAC4362": "Hot", "#FFB35547": "Hot and Spicy", "#FFFFB3DE": "Hot Aquarelle Pink", "#FFFFF6D9": "Hot Beach", "#FFCC5511": "Hot Bolognese", "#FF984218": "Hot Brown", "#FFE69D00": "Hot Butter", "#FFA5694F": "Hot Cacao", "#FFFA8D7C": "Hot Calypso", "#FFCC6E3B": "Hot Caramel", "#FFB7513A": "Hot Chilli", "#FF683939": "Hot Chocolate", "#FFD1691C": "Hot Cinnamon Variant", "#FF806257": "Hot Cocoa", "#FFF35B53": "Hot Coral", "#FFBB0033": "Hot Cuba", "#FF815B28": "Hot Curry", "#FFEAE4DA": "Hot Desert", "#FF717C3E": "Hot Dog Relish", "#FFF55931": "Hot Embers", "#FFD40301": "Hot Fever", "#FFDD180E": "Hot Flamin Chilli", "#FFB35966": "Hot Flamingo", "#FF5E2912": "Hot Fudge", "#FFA36736": "Hot Ginger", "#FFE07C89": "Hot Gossip", "#FF25FF29": "Hot Green", "#FFDD6622": "Hot Hazel", "#FFBB2244": "Hot Hibiscus", "#FFBC3033": "Hot Jazz", "#FFAA0033": "Hot Lava", "#FFC9312B": "Hot Lips", "#FF735C12": "Hot Mustard", "#FFF4893D": "Hot Orange", "#FF598039": "Hot Pepper Green", "#FFFF028D": "Hot Pink Variant", "#FFFE69B6": "Hot Pink Fusion", "#FFCB00F5": "Hot Purple", "#FFCCAA00": "Hot Sand", "#FFAB4F41": "Hot Sauce", "#FF3F3F75": "Hot Sauna", "#FFEC4F28": "Hot Shot", "#FFCC2211": "Hot Spice", "#FFABA89E": "Hot Stone", "#FFF9B82B": "Hot Sun", "#FFA24A3F": "Hot Tamale", "#FFA7752C": "Hot Toddy Variant", "#FFD26E2D": "Hot Wings", "#FF755468": "Hothouse Orchid", "#FFF1F3F2": "Hotot Bunny", "#FFFF4433": "Hotspot", "#FFE68A00": "Hotter Butter", "#FFFF4455": "Hotter Than Hell", "#FFFF80FF": "Hottest of Pinks", "#FFE5E0D5": "Hourglass", "#FFE2E0DB": "House Martin Eggs", "#FF9AA0BD": "House of Owen", "#FFD6D9DD": "House Sparrow\u2019s Egg", "#FF4D495B": "House Stark Grey", "#FF58713F": "Houseplant", "#FFA0AEB8": "How Handsome", "#FF886150": "How Now", "#FFF9E4C8": "Howdy Neighbor", "#FFC6A698": "Howdy Partner", "#FF9C7F5A": "Howling Coyote", "#FFE50752": "Howling Pink", "#FF1DACD1": "H\u00fa L\u00e1n Blue", "#FFF8FF73": "Hu\u00e1ng D\u00ec Yellow", "#FFFADA6A": "Hu\u00e1ng J\u012bn Zh\u014du Gold", "#FFF0F20C": "Hu\u00e1ng S\u00e8 Yellow", "#FFE9BF8C": "Hubbard Squash", "#FF559933": "Hubert\u2019s Truck Green", "#FF5B4349": "Huckleberry", "#FF71563B": "Huckleberry Brown", "#FFEADBD2": "Hudson", "#FFFDEF02": "Hudson Bee", "#FF17A9E5": "Huelve\u00f1o Horizon", "#FF9FA09F": "Hugh\u2019s Hue", "#FFE6CFCC": "Hugo", "#FFC1C6D3": "H\u016bi S\u00e8 Grey", "#FF929264": "Hula Girl", "#FF726F6C": "Hulett Ore", "#FF4D140B": "Hull Red", "#FFE3DAD3": "Humble Beginnings", "#FFE3CDC2": "Humble Blush", "#FFEDC796": "Humble Gold", "#FFAAAA99": "Humble Hippo", "#FF1F6357": "Humboldt Redwoods", "#FFC9CCD2": "Humid Cave", "#FFCEEFE4": "Hummingbird", "#FF5B724A": "Hummingbird Green", "#FFEECC99": "Hummus", "#FFC6B836": "Humorous Green", "#FF473B3B": "Humpback Whale", "#FFB7A793": "Humus", "#FFB2B7D1": "Hundred Waters", "#FFF0000D": "Hungry Red", "#FFBB11FF": "Hunky Hummingbird", "#FF2A4F43": "Hunt Club", "#FF938370": "Hunt Club Brown", "#FF33534B": "Hunter", "#FF0B4008": "Hunter Green Variant", "#FF989A8D": "Hunter\u2019s Hollow", "#FFDB472C": "Hunter\u2019s Orange", "#FF6B5A54": "Hunting Boots", "#FF856829": "Hunting Jacket", "#FF96A782": "Huntington Garden", "#FF46554C": "Huntington Woods", "#FF8B7E77": "Hurricane Variant", "#FF254D54": "Hurricane Green Blue", "#FFBDBBAD": "Hurricane Haze", "#FFEBEEE8": "Hurricane Mist", "#FFC4BDBA": "Hush", "#FFE1DED8": "Hush Grey", "#FFF8E9E2": "Hush Pink", "#FFE4B095": "Hush Puppy", "#FFE5DAD4": "Hush White", "#FF5397B7": "Hush-A-Bye", "#FFA8857A": "Hushed Auburn", "#FFC8E0DB": "Hushed Green", "#FF6E8FB4": "Hushed Lilac", "#FFEEA5C1": "Hushed Rose", "#FFCDBBB9": "Hushed Violet", "#FFF1F2E4": "Hushed White", "#FFB2994B": "Husk Variant", "#FFE0EBFA": "Husky", "#FFBB613E": "Husky Orange", "#FFAE957C": "Hutchins Plaza", "#FF936CA7": "Hyacinth", "#FF6C6783": "Hyacinth Arbour", "#FF807388": "Hyacinth Dream", "#FFC8C8D2": "Hyacinth Ice", "#FF6F729F": "Hyacinth Mauve", "#FFA75536": "Hyacinth Red", "#FFB9C4D3": "Hyacinth Tint", "#FF9B4D93": "Hyacinth Violet", "#FFC1C7D7": "Hyacinth White Soft Blue", "#FFD0CDA9": "Hybrid", "#FF626045": "Hyde Park", "#FF006995": "Hydra", "#FF007A73": "Hydra Turquoise", "#FF768DC6": "Hydrangea", "#FFA6AEBE": "Hydrangea Blossom", "#FFCAA6A9": "Hydrangea Bouquet", "#FFE6EAE0": "Hydrangea Floret", "#FFCAA0FF": "Hydrangea Purple", "#FF9E194D": "Hydrangea Red", "#FF9B9B9B": "Hydrargyrum", "#FF49747F": "Hydro", "#FFA1F4F5": "Hydro Cannon", "#FF33476D": "Hydrogen Blue", "#FF89ACAC": "Hydrology", "#FF5E9CA1": "Hydroport", "#FFE0E1D8": "Hygge Green", "#FF5DBCB4": "Hygiene Green", "#FFF6F677": "Hyper Beam", "#FF015F97": "Hyper Blue", "#FF55FF00": "Hyper Green", "#FFEDDBDA": "Hyper Light Drifter", "#FFEC006C": "Hyper Pink", "#FF0000EE": "Hyperlink Blue", "#FF17F9A6": "Hyperpop Green", "#FF687783": "Hypnotic", "#FF73E608": "Hypnotic Green", "#FFCF0D14": "Hypnotic Red", "#FF00787F": "Hypnotic Sea", "#FF32584C": "Hypnotism", "#FF415D66": "Hypothalamus Grey", "#FF6D4976": "Hyssop", "#FFFFA917": "I Love", "#FFD97D8F": "I Love You Pink", "#FFDDDBC5": "I Miss You", "#FFD47F8D": "I Pink I Can", "#FF404034": "I R Dark Green", "#FFEBBF5C": "I\u2019m a Local", "#FF482400": "Ibex Brown", "#FFF4B3C2": "Ibis", "#FFE4D2D8": "Ibis Mouse", "#FFFBD0B9": "Ibis Pink", "#FFCA628F": "Ibis Rose", "#FFF2ECE6": "Ibis White", "#FFF58F84": "Ibis Wing", "#FF0086BC": "Ibiza Blue", "#FFD6FFFA": "Ice", "#FFC6E4E9": "Ice Age", "#FFEADEE8": "Ice Ballet", "#FF739BD0": "Ice Blue", "#FF717787": "Ice Blue Grey", "#FFCCE2DD": "Ice Bomb", "#FFA2CDCB": "Ice Boutique Turquoise", "#FFB9E7DD": "Ice Cap Green", "#FFD5EDFB": "Ice Castle", "#FFA0BEDA": "Ice Cave", "#FFB2F8F8": "Ice Citadel", "#FF25E2CD": "Ice Climber", "#FFD2EAF1": "Ice Cold Variant", "#FFD9EBAC": "Ice Cold Green", "#FFB1D1FC": "Ice Cold Stare", "#FFE3D0BF": "Ice Cream Cone", "#FFF7D3AD": "Ice Cream Parlour", "#FFA6E3E0": "Ice Crystal Blue", "#FFAFE3D6": "Ice Cube", "#FFCEE5DF": "Ice Dagger", "#FF005456": "Ice Dark Turquoise", "#FFD1DCE8": "Ice Desert", "#FFEAEBE1": "Ice Dream", "#FFD3E2EE": "Ice Drop", "#FFBBEEFF": "Ice Effect", "#FFDCECF5": "Ice Fishing", "#FFD8E7E1": "Ice Floe", "#FFBDCBCB": "Ice Flow", "#FFC3E7EC": "Ice Flower", "#FFDBECE9": "Ice Folly", "#FFFFFFE9": "Ice Glow", "#FF87D8C3": "Ice Green", "#FFCAC7C4": "Ice Grey", "#FF9BB2BA": "Ice Gull Grey Blue", "#FFE4BDC2": "Ice Hot Pink", "#FFBAEBAE": "Ice Ice", "#FF00FFDD": "Ice Ice Baby", "#FFFFFBC1": "Ice Lemon", "#FFC9C2DD": "Ice Mauve", "#FFB6DBBF": "Ice Mist", "#FFA5DBE3": "Ice Pack", "#FFE2E4D7": "Ice Palace", "#FFCF7EAD": "Ice Plant", "#FFBBDDEE": "Ice Rink", "#FFC8D6DA": "Ice Rink Blue", "#FFE1E6E5": "Ice Sculpture", "#FFC1DEE2": "Ice Shard Soft Blue", "#FF11FFEE": "Ice Temple", "#FFCDEBE1": "Ice Water Green", "#FFFEFECD": "Ice Yellow", "#FFDFF0E2": "Ice-Cold White", "#FFDAE4EE": "Iceberg Tint", "#FF8C9C92": "Iceberg Green", "#FFB7C2CC": "Icebreaker", "#FFC3D6E0": "Icecap", "#FFFEF4DD": "Iced Almond", "#FFCBD3C3": "Iced Aniseed", "#FFEFD6C0": "Iced Apricot", "#FFA7D4D9": "Iced Aqua", "#FFC8E4B9": "Iced Avocado", "#FFE9819A": "Iced Berry", "#FF9C8866": "Iced Cappuccino", "#FFE5E9B7": "Iced Celery", "#FFE8C7BF": "Iced Cherry", "#FFAA895D": "Iced Coffee", "#FFD0AE9A": "Iced Copper", "#FF5A4A42": "Iced Espresso", "#FFECEBC9": "Iced Green Apple", "#FFC2C7DB": "Iced Lavender", "#FFE8DCE3": "Iced Mauve", "#FFA3846C": "Iced Mocha", "#FF8E7D89": "Iced Orchid", "#FFD6DCD7": "Iced Slate", "#FFB87253": "Iced Tea", "#FFAFA9AF": "Iced Tulip", "#FFE1A4B2": "Iced Vovo", "#FFD1AFB7": "Iced Watermelon", "#FF008B52": "Iceland Green", "#FFF37E27": "Iceland Poppy", "#FFDAE4EC": "Icelandic", "#FFA0A4BC": "Icelandic Blue", "#FF0011FF": "Icelandic Water", "#FFD7DEEB": "Icelandic Winds", "#FFD9E7E3": "Icelandic Winter", "#FFCCDFDC": "Icelandish", "#FFDADCD0": "Icepick", "#FFA5FCDC": "Icery", "#FFE8ECEE": "Icewind Dale", "#FFCFE8E6": "Icicle Mint", "#FFD9E7F2": "Icicle Veil", "#FFBCC5C9": "Icicles", "#FFD5B7CB": "Icing Flower", "#FFF8C4C5": "Icing on the Cake", "#FFF5EEE7": "Icing Rose", "#FF8FAE22": "Icky Green", "#FFBBC7D2": "Icy", "#FFE0E5E2": "Icy Bay", "#FFCCEDEA": "Icy Blue", "#FFC4ECF0": "Icy Breeze", "#FFC1CAD9": "Icy Brook", "#FFD4EAE1": "Icy Glacier", "#FFC5E6F7": "Icy Landscape", "#FFE2E2ED": "Icy Lavender", "#FFF4E8B2": "Icy Lemonade", "#FF55FFEE": "Icy Life", "#FFE6E9F9": "Icy Lilac", "#FFC6E3D3": "Icy Mint", "#FFB0D3D1": "Icy Morn", "#FFF5CED8": "Icy Pink", "#FFE6F2E9": "Icy Pistachio", "#FFCFDAFB": "Icy Plains", "#FFD6DFE8": "Icy Teal", "#FFF7F5EC": "Icy Tundra", "#FFBCE2E8": "Icy Water", "#FFC0D2D0": "Icy Waterfall", "#FFD3F1EE": "Icy Wind", "#FF7890AC": "Identity", "#FFCAE277": "Idocrase Green", "#FF645A8B": "Idol", "#FF598476": "Idyllic Island", "#FF94C8D2": "Idyllic Isle", "#FFC89EB7": "Idyllic Pink", "#FF6F5A4A": "If a Tree Fell \u2026", "#FF6D68ED": "If I Could Fly", "#FFFDFCFA": "Igloo", "#FFC9E5EB": "Igloo Blue", "#FFF4D69A": "Igniting", "#FF2E776D": "Igua\u00e7uense Waterfall", "#FF878757": "Iguana", "#FF71BC77": "Iguana Green", "#FFF08F90": "Ikkonzome Pink", "#FF00022E": "Illicit Darkness", "#FF56FCA2": "Illicit Green", "#FFFF5CCD": "Illicit Pink", "#FFBF77F6": "Illicit Purple", "#FFEAA601": "Illuminate Me", "#FFF9E5D8": "Illuminated", "#FF419168": "Illuminati Green", "#FFEEEE77": "Illuminating", "#FFDEE4E0": "Illuminating Experience", "#FFEF95AE": "Illusion Variant", "#FFC3CED8": "Illusion Blue", "#FF574F64": "Illusionist", "#FFE1D5C2": "Illusive Dream", "#FF92948D": "Illusive Green", "#FF5533BB": "Illustrious Indigo", "#FF330011": "Ilvaite Black", "#FF7A6E70": "Imagery", "#FF89687D": "Imaginary Mauve", "#FFDFE0EE": "Imagination", "#FFFAE199": "Imam Ali Gold", "#FFD0576B": "Imayou Pink", "#FFCEEBB2": "Imbued With Mint", "#FFAACC00": "Immaculate Iguana", "#FF204F54": "Immersed", "#FFC0A9CC": "Immortal", "#FFD8B7CF": "Immortal Indigo", "#FF945B7F": "Immortality", "#FFD4A207": "Immortelle Yellow", "#FFF4CF95": "Impala", "#FFF1D2D7": "Impatiens Petal", "#FFFFC4BC": "Impatiens Pink", "#FFC47D7C": "Impatient Heart", "#FFDB7B97": "Impatient Pink", "#FF602F6B": "Imperial", "#FF002395": "Imperial Blue", "#FF33746B": "Imperial Dynasty", "#FFE0B181": "Imperial Gold", "#FF408750": "Imperial Green", "#FF676A6A": "Imperial Grey", "#FFF1E8D2": "Imperial Ivory", "#FF693E42": "Imperial Jewel", "#FFA99FCF": "Imperial Lilac", "#FF604E7A": "Imperial Palace", "#FF596458": "Imperial Palm", "#FF21303E": "Imperial Primer", "#FF5B3167": "Imperial Purple", "#FFEC2938": "Imperial Red", "#FFE4D68C": "Impetuous", "#FF1A2578": "Impression of Obscurity", "#FF8AA4AC": "Impressionist", "#FFA7CAC9": "Impressionist Blue", "#FFB9CEE0": "Impressionist Sky", "#FFF4DEC3": "Impressive Ivory", "#FF6E7376": "Improbable", "#FF705F63": "Impromptu", "#FF005B87": "Impulse", "#FF006699": "Impulse Blue", "#FF624977": "Impulsive Purple", "#FFF5E7E3": "Impure White", "#FF67AED0": "Imrik Blue", "#FFB98052": "In a Nutshell", "#FF978C59": "In a Pickle", "#FF693C2A": "In Caffeine We Trust", "#FFEE8877": "In for a Penny", "#FFD2C4A9": "In for the Night", "#FFB6D4A0": "In Good Taste", "#FF9EB0BB": "In the Blue", "#FFD6CBBF": "In the Buff", "#FF3B3C41": "In the Dark", "#FF91A8A8": "In the Deep End", "#FFAEA69B": "In the Hills", "#FF859893": "In the Moment", "#FF2178A4": "In the Mood", "#FF283849": "In the Navy", "#FF2B6DAA": "In the Nick", "#FFF4C4D0": "In the Pink", "#FFFF2233": "In the Red", "#FFCBC4C0": "In the Shadows", "#FF9AD9D4": "In the Shallows", "#FFE2C3CF": "In the Slip", "#FFEDE6ED": "In the Spotlight", "#FFA3BC3A": "In the Tropics", "#FF84838E": "In the Twilight", "#FF5C457B": "In the Vines", "#FF72786F": "In the Woods", "#FFF6DDDD": "In Your Dreams", "#FFAA6D28": "Inca Gold", "#FF8C7B6C": "Inca Temple", "#FFFFD301": "Inca Yellow", "#FFF9DDC4": "Incan Treasure", "#FFFFBB22": "Incandescence", "#FFAA0022": "Incarnadine", "#FFAF9A7E": "Incense", "#FF65644A": "Incense Cedar", "#FFFF0022": "Incision", "#FF8E8E82": "Incognito", "#FFE3DED7": "Incredible White", "#FF123456": "Incremental Blue", "#FFDA1D38": "Incubation Red", "#FF0B474A": "Incubi Darkness", "#FF772222": "Incubus", "#FFD2BA83": "Independent Gold", "#FF008A8E": "India Blue", "#FF3C3D4C": "India Ink", "#FFE0A362": "India Trade", "#FFA5823D": "Indian Brass", "#FFF2D0C0": "Indian Clay", "#FFF49476": "Indian Dance", "#FF54332E": "Indian Fig", "#FF91955F": "Indian Green", "#FF3C3F4A": "Indian Ink", "#FFD3B09C": "Indian Khaki Variant", "#FFCC1A97": "Indian Lake", "#FFE4C14D": "Indian Maize", "#FFD5A193": "Indian Mesa", "#FFEAE3D8": "Indian Muslin", "#FF86B7A1": "Indian Ocean", "#FFFA9761": "Indian Paintbrush", "#FFD5BC26": "Indian Pale Ale", "#FF0044AA": "Indian Peafowl", "#FFAD5B78": "Indian Pink", "#FFDA846D": "Indian Princess", "#FF850E04": "Indian Red", "#FF9F7060": "Indian Reed", "#FF8A5773": "Indian Silk", "#FFAE8845": "Indian Spice", "#FFA85143": "Indian Summer", "#FFD98A7D": "Indian Sunset", "#FF3C586B": "Indian Teal", "#FFEFDAC2": "Indian White", "#FFE88A5B": "Indiana Clay", "#FF588C3A": "Indica", "#FF2E2364": "Indie Go", "#FF9892B8": "Indifferent", "#FF301885": "Indiglow", "#FF4467A7": "Indigo Batik", "#FF002E51": "Indigo Black", "#FF8A83DA": "Indigo Bloom", "#FF3A18B1": "Indigo Blue", "#FF006CA9": "Indigo Bunting", "#FF006EC7": "Indigo Carmine", "#FFA09FCC": "Indigo Child", "#FF204C6C": "Indigo Cloth", "#FF627C98": "Indigo Dreams", "#FF00416C": "Indigo Dye Variant", "#FF4D0E88": "Indigo Girls", "#FF234156": "Indigo Go-Go", "#FF1F4788": "Indigo Hamlet", "#FF474A4D": "Indigo Ink", "#FF393432": "Indigo Ink Brown", "#FF393F4C": "Indigo Iron", "#FF5D76CB": "Indigo Light", "#FF6C848D": "Indigo Mouse", "#FF4C5E87": "Indigo Navy Blue", "#FF324680": "Indigo Night", "#FF695A78": "Indigo Red", "#FF1F0954": "Indigo Sloth", "#FF4B0183": "Indigo Static", "#FF2A465B": "Indigo Streamer", "#FFEBF6F7": "Indigo White", "#FFAC3B3B": "Indiscreet", "#FFD4CDCA": "Individual White", "#FF6611AA": "Indiviolet Sunset", "#FF9C5B34": "Indochine Variant", "#FFB96B00": "Indocile Tiger", "#FFA29DAD": "Indolence", "#FF008C69": "Indonesian Jungle", "#FFD1B272": "Indonesian Rattan", "#FF729E42": "Indubitably Green", "#FF533D47": "Indulgence", "#FF66565F": "Indulgent", "#FFD1C5B7": "Indulgent Mocha", "#FFAEADAD": "Industrial Age", "#FF322B26": "Industrial Black", "#FF00898C": "Industrial Blue", "#FF114400": "Industrial Green", "#FF5B5A57": "Industrial Grey", "#FF737373": "Industrial Revolution", "#FFE09887": "Industrial Rose", "#FF877A65": "Industrial Strength", "#FF008A70": "Industrial Turquoise", "#FF4F9153": "Ineffable Forest", "#FF63F7B4": "Ineffable Green", "#FFCAEDE4": "Ineffable Ice Cap", "#FFE6E1C7": "Ineffable Linen", "#FFCC99CC": "Ineffable Magenta", "#FF820E3B": "Inescapable Lover", "#FF777985": "Infamous", "#FFF0D5EA": "Infatuation", "#FFBB1177": "Infectious Love", "#FFDA5736": "Inferno", "#FFFF4400": "Inferno Orange", "#FF435A6F": "Infinite Deep Sea", "#FF2F1064": "Infinite Inkwell", "#FF071037": "Infinite Night", "#FFBDDDE1": "Infinitesimal Blue", "#FFD7E4CC": "Infinitesimal Green", "#FF222831": "Infinity", "#FF6E7E99": "Infinity and Beyond", "#FF94D4E4": "Infinity Pool", "#FFF1E7D0": "Informal Ivory", "#FFFE85AB": "Informative Pink", "#FFFFCCEE": "Infra-White", "#FFFE486C": "Infrared", "#FFDD3333": "Infrared Burn", "#FFCC3344": "Infrared Flush", "#FFCC3355": "Infrared Gloze", "#FFDD2244": "Infrared Tang", "#FFC8D0CA": "Infusion", "#FF334C5D": "Ing\u00e9nue Blue", "#FFAAA380": "Inglenook Olive", "#FFD7AE77": "Inheritance", "#FF252024": "Ink Black", "#FF00608B": "Ink Blotch", "#FF0C5A77": "Ink Blue", "#FF393F4B": "Inkblot", "#FF3B5066": "Inked", "#FFD9DCE4": "Inked Silk", "#FF44556B": "Inkjet", "#FF31363A": "Inkwell", "#FF1E1E21": "Inkwell Inception", "#FF4E7287": "Inky Blue", "#FF535266": "Inky Storm", "#FF747B9F": "Inky Violet", "#FF606B54": "Inland", "#FF7C939D": "Inland Waters", "#FF3F586E": "Inlet Harbour", "#FFBBAA7E": "Inner Cervela", "#FFF1BDB2": "Inner Child", "#FF6D69A1": "Inner Journey", "#FF78A6B5": "Inner Sanctum", "#FF285B5F": "Inner Space", "#FFBBAFBA": "Inner Touch", "#FF959272": "Inness Sage", "#FF229900": "Innisfree Garden", "#FFEBD1CF": "Innocence", "#FF91B3D0": "Innocent Blue", "#FF856F79": "Innocent Pink", "#FFD0C7FF": "Innocent Snowdrop", "#FFA4B0C4": "Innuendo", "#FF114477": "Inoffensive Blue", "#FF221122": "Inside", "#FFE0CFB5": "Inside Passage", "#FFC9B0AB": "Insightful Rose", "#FF285294": "Insignia", "#FFECF3F9": "Insignia White", "#FF06012C": "Insomnia", "#FF110077": "Insomniac Blue", "#FF4FA183": "Inspiration Peak", "#FFDFD9E4": "Inspired Lilac", "#FFD9CEC7": "Instant", "#FFE3DAC6": "Instant Classic", "#FFF4D493": "Instant Noodles", "#FFFF8D28": "Instant Orange", "#FFEDE7D2": "Instant Relief", "#FFADA7C8": "Instigate", "#FF405E95": "Integra", "#FF233E57": "Integrity", "#FF3F414C": "Intellectual", "#FFA8A093": "Intellectual Grey", "#FF7F5400": "Intense Brown", "#FF123328": "Intense Green", "#FF68C89D": "Intense Jade", "#FF682D63": "Intense Mauve", "#FFDF3163": "Intense Passion", "#FF4D4A6F": "Intense Purple", "#FF00978C": "Intense Teal", "#FFE19C35": "Intense Yellow", "#FFE4CAAD": "Interactive Cream", "#FFA0CDDE": "Intercoastal", "#FFA8B5BC": "Intercoastal Grey", "#FF360CCC": "Interdimensional Blue", "#FFD6E6E6": "Interdimensional Portal", "#FF9BAFB2": "Interesting Aqua", "#FFC1A392": "Interface Tan", "#FF4D516C": "Intergalactic", "#FFAFE0EF": "Intergalactic Blue", "#FF222266": "Intergalactic Cowboy", "#FF273287": "Intergalactic Highway", "#FF573935": "Intergalactic Ray", "#FF5B1E8B": "Intergalactic Settlement", "#FF536437": "Interior Green", "#FF564355": "Interlude", "#FF56626E": "Intermediate Blue", "#FF137730": "Intermediate Green", "#FF019694": "Intermezzo", "#FF3762A5": "International", "#FF002FA6": "International Klein Blue Variant", "#FF001155": "Interstellar Blue", "#FFCCBB99": "Intimate Journal", "#FFF0E1D8": "Intimate White", "#FF4F7BA7": "Into the Blue", "#FF0D6C49": "Into the Green", "#FF1E3642": "Into the Night", "#FF425267": "Into the Stratosphere", "#FF999885": "Into the Wild", "#FF11BB55": "Intoxicate", "#FFA1AC4D": "Intoxication", "#FF77B5AA": "Intracoastal", "#FFE0E2E0": "Intrepid Grey", "#FFEDDDCA": "Intricate Ivory", "#FF635951": "Intrigue", "#FFB24648": "Intrigue Red", "#FF6D6053": "Introspective", "#FFCFC6BC": "Intuitive", "#FF55A0B7": "Inuit", "#FFD8E4E7": "Inuit Blue", "#FFC2BDC2": "Inuit Ice", "#FFD1CDD0": "Inuit White", "#FF49017E": "Invasive Indigo", "#FFE89D6F": "Inventive Orange", "#FF576238": "Inverness", "#FFDCE3E2": "Inverness Grey", "#FFE47237": "Invigorate", "#FFF1EAB4": "Invigorating", "#FFA6773F": "Invitation Gold", "#FFCDC29D": "Inviting Gesture", "#FFF2D5B0": "Inviting Ivory", "#FFB9C4BC": "Inviting Veranda", "#FF7D89BB": "Iolite", "#FF368976": "Ionian", "#FFE7DFC5": "Ionic Ivory", "#FFD0EDE9": "Ionic Sky", "#FF55DDFF": "Ionized-Air Glow", "#FF93CFE3": "Iqaluit Ice", "#FF006C2E": "Ireland Green", "#FF3A5B52": "Iridescent", "#FF0153C8": "Iridescent Blue", "#FF48C072": "Iridescent Green", "#FF00707D": "Iridescent Peacock", "#FF7BFDC7": "Iridescent Turquoise", "#FF3D3C3A": "Iridium", "#FF5A4FCF": "Iris", "#FF5B649E": "Iris Bloom", "#FFADA0BD": "Iris Blossom", "#FF767694": "Iris Eyes", "#FFE0E3EF": "Iris Ice", "#FFE8E5EC": "Iris Isle", "#FFB39B94": "Iris Mauve", "#FFA767A2": "Iris Orchid", "#FF6B6273": "Iris Petal", "#FFCAB9BE": "Iris Pink", "#FF398A59": "Irish", "#FF007F59": "Irish Beauty", "#FF69905B": "Irish Charm", "#FF53734C": "Irish Clover", "#FF62422B": "Irish Coffee Variant", "#FFE9DBBE": "Irish Cream", "#FFD3E3BF": "Irish Folklore", "#FF019529": "Irish Green", "#FF7CB386": "Irish Hedge", "#FF66CC11": "Irish Jig", "#FFEEE4E0": "Irish Linen", "#FFE7E5DB": "Irish Mist", "#FFB5C0B3": "Irish Moor", "#FF9CA691": "Irish Paddock", "#FF90CCA3": "Irish Spring", "#FFA38D86": "Irish Tea", "#FF9DACB5": "Irogon Blue", "#FF5E5E5E": "Iron Variant", "#FF114B91": "Iron Blue", "#FF50676B": "Iron Creek", "#FF8AA1A6": "Iron Earth", "#FFCBCDCD": "Iron Fist", "#FF5D5B5B": "Iron Fixture", "#FF6E3B31": "Iron Flint", "#FF475A5E": "Iron Forge", "#FF6F797E": "Iron Frost", "#FF585A60": "Iron Gate", "#FF7C7F7C": "Iron Grey", "#FF344D56": "Iron Head", "#FFD6D1DC": "Iron Maiden", "#FF757574": "Iron Mountain", "#FFE7661E": "Iron Orange", "#FFAF5B46": "Iron Ore", "#FF835949": "Iron Oxide", "#FF4D504B": "Iron River", "#FF114E56": "Iron Teal", "#FF6A6B67": "Iron-ic", "#FF887F85": "Ironbreaker", "#FFA1A6A9": "Ironbreaker Metal", "#FF615C55": "Ironclad", "#FF7E8082": "Ironside", "#FF706E66": "Ironside Grey", "#FF865040": "Ironstone Variant", "#FFA19583": "Ironwood", "#FFDADEE6": "Irradiant Iris", "#FFAAFF55": "Irradiated Green", "#FFE6DDC6": "Irresistible Beige", "#FF786C57": "Irrigation", "#FF9955FF": "Irrigo Purple", "#FFEE1122": "Irritated Ibis", "#FF0022FF": "Is It Cold", "#FF9BD8C4": "Isabella\u2019s Aqua", "#FF484450": "Ishtar", "#FF009900": "Islamic Green Variant", "#FF8ADACF": "Island Breeze", "#FFD8877A": "Island Coral", "#FF139BA2": "Island Dream", "#FFDED9B4": "Island Embrace", "#FFFFB59A": "Island Girl", "#FF2BAE66": "Island Green", "#FFF6E3D6": "Island Hopping", "#FFA7C9EB": "Island Light", "#FF008292": "Island Lush", "#FF3FB2A8": "Island Moment", "#FF88D9D8": "Island Oasis", "#FFED681F": "Island Orange", "#FF6C7E71": "Island Palm", "#FF81D7D0": "Island Sea", "#FFF8EDDB": "Island Spice Variant", "#FFF2D66C": "Island Sun", "#FFC3DDEE": "Island View", "#FF0099C9": "Isle of Capri", "#FFBCCCB5": "Isle of Dreams", "#FF3E6655": "Isle of Pines", "#FFF9DD13": "Isle of Sand", "#FF80D7CF": "Isle Royale", "#FFFFB278": "Isn\u2019t It Just Peachy", "#FF494D55": "Isolation", "#FFDDFF55": "Isotonic Water", "#FFCFDAC3": "Issey-San", "#FFAF8A5B": "It Works", "#FFFFDAE2": "It\u2019s a Girl!", "#FFCC7365": "It\u2019s My Party", "#FFBC989E": "It\u2019s Your Mauve", "#FF5F6957": "Italian Basil", "#FF6B8C23": "Italian Buckthorn", "#FFD79979": "Italian Clay", "#FFD0C8E6": "Italian Fitch", "#FF413D4B": "Italian Grape", "#FFE2E0D3": "Italian Ice", "#FFEDE9D4": "Italian Lace", "#FF93685A": "Italian Mocha", "#FF807243": "Italian Olive", "#FF59354A": "Italian Plum", "#FFB1403A": "Italian Red", "#FF221111": "Italian Roast", "#FFB2FCFF": "Italian Sky Blue", "#FFE7D3A1": "Italian Straw", "#FFAD5D5D": "Italian Villa", "#FFD16169": "Italiano Rose", "#FFCCE5E8": "Ivalo River", "#FFE4CEAC": "Ivoire", "#FFC4B8A9": "Ivory Brown", "#FFEBD999": "Ivory Buff", "#FFFFF6DA": "Ivory Charm", "#FFFAF5DE": "Ivory Coast", "#FFD5B89C": "Ivory Cream", "#FFE7D8BC": "Ivory Essence", "#FFFCEFD6": "Ivory Invitation", "#FFF8F7E6": "Ivory Keys", "#FFECE2CC": "Ivory Lace", "#FFE6E6D8": "Ivory Lashes", "#FFE6DDCD": "Ivory Memories", "#FFEFEADE": "Ivory Mist", "#FFF9E4C1": "Ivory Oats", "#FFEEEADC": "Ivory Palace", "#FFE6DECA": "Ivory Paper", "#FFEFE3CA": "Ivory Parchment", "#FFD9C9B8": "Ivory Ridge", "#FFF0EADA": "Ivory Steam", "#FFEEE1CC": "Ivory Stone", "#FFF8EAD8": "Ivory Tassel", "#FFFBF3F1": "Ivory Tower", "#FFEDEDE4": "Ivory Wedding", "#FF277B74": "Ivy", "#FF93A272": "Ivy Enchantment", "#FF818068": "Ivy Garden", "#FF585442": "Ivy Green", "#FF007958": "Ivy League", "#FF67614F": "Ivy Topiary", "#FF708D76": "Ivy Wreath", "#FF6B6F59": "Iwai Brown", "#FF5E5545": "Iwaicha Brown", "#FFA59A59": "Iyanden Darksun", "#FFCEB0B5": "Izmir Pink", "#FF4D426E": "Izmir Purple", "#FFA06856": "J\u2019s Big Heart", "#FFAD6D68": "Jab\u0142o\u0144ski Brown", "#FF536871": "Jab\u0142o\u0144ski Grey", "#FFF9D7EE": "Jacaranda Variant", "#FF6C70A9": "Jacaranda Jazz", "#FFA8ACB7": "Jacaranda Light", "#FFC760FF": "Jacaranda Pink", "#FF440044": "Jacarta Variant", "#FFBCACCD": "Jacey\u2019s Favourite", "#FF920F0E": "Jack and Coke", "#FF869F69": "Jack Bone", "#FFDAE6E3": "Jack Frost", "#FFC0B2B1": "Jack Rabbit", "#FFD37A51": "Jack-O-Lantern", "#FFA9A093": "Jackal", "#FFF7C680": "Jackfruit", "#FF413628": "Jacko Bean Variant", "#FFD19431": "Jackpot", "#FFC3BDA9": "Jackson Antique", "#FF3D3F7D": "Jacksons Purple Variant", "#FFD6B281": "Jacob\u2019s Ladder", "#FFE4CCB0": "Jacobean Lace", "#FF5D4E50": "Jacqueline", "#FF007CAC": "Jacuzzi", "#FFC2D7AD": "Jade Bracelet", "#FFB0BDA2": "Jade Cameo", "#FF59B587": "Jade Cream", "#FF6AA193": "Jade Dragon", "#FFCEDDDA": "Jade Dust", "#FF779977": "Jade Green", "#FF247E81": "Jade Jewel", "#FFC1CAB7": "Jade Light Green", "#FF9DCA7B": "Jade Lime", "#FFD6E9D7": "Jade Mist", "#FF34C2A7": "Jade Mountain", "#FF166A45": "Jade Mussel Green", "#FF318F33": "Jade of Emeralds", "#FF00AAAA": "Jade Orchid", "#FFD0EED7": "Jade Palace", "#FF2BAF6A": "Jade Powder", "#FFB8E0D0": "Jade Sea", "#FF017B92": "Jade Shard", "#FFC1E5D5": "Jade Spell", "#FF74BB83": "Jade Stone Green", "#FFBBCCBC": "Jade Tinge", "#FF0092A1": "Jaded", "#FFAEDDD3": "Jaded Clouds", "#FFCC7766": "Jaded Ginger", "#FFBBD3AD": "Jaded Lime", "#FF38C6A1": "Jadeite", "#FF77A276": "Jadesheen", "#FF01A6A0": "Jadestone", "#FF61826C": "Jadite", "#FFE27945": "Jaffa Variant", "#FFD87839": "Jaffa Orange", "#FFFFCCCB": "Jagdwurst", "#FFCAE7E2": "Jagged Ice Variant", "#FF3F2E4C": "Jagger Variant", "#FF29292F": "Jaguar Variant", "#FFF1B3B6": "Jaguar Rose", "#FFA43323": "Jaipur", "#FFEFDDC3": "Jakarta", "#FF3D325D": "Jakarta Skyline", "#FF9A8D3F": "Jalape\u00f1o", "#FF576648": "Jalape\u00f1o Bouquet", "#FFB5AD70": "Jalape\u00f1o Jelly", "#FFAC335E": "Jam Jar", "#FFD4CFD6": "Jam Session", "#FF95CBC4": "Jamaica Bay", "#FF04627A": "Jamaican Dream", "#FF64D1BE": "Jamaican Jade", "#FF26A5BA": "Jamaican Sea", "#FFF7B572": "Jambalaya Variant", "#FFF2E3B5": "James Blonde", "#FFE3C2FF": "Jane Purple", "#FFFF2211": "Janemba Red", "#FFCEB5C8": "Janey\u2019s Party", "#FF2266CC": "Janitor", "#FF00A1B9": "January Blue", "#FFDFE2E5": "January Dawn", "#FF99C1DC": "January Frost", "#FF7B4141": "January Garnet", "#FFDDD6F3": "Japan Blush", "#FF829F96": "Japanese Bonsai", "#FF9F2832": "Japanese Carmine", "#FFC47A88": "Japanese Coral", "#FF965036": "Japanese Cypress", "#FFB5B94C": "Japanese Fern", "#FFA8BF93": "Japanese Horseradish", "#FF264348": "Japanese Indigo", "#FF7F5D3B": "Japanese Iris", "#FFCC6358": "Japanese Kimono", "#FFDB7842": "Japanese Koi", "#FF2F7532": "Japanese Laurel Variant", "#FFC4BAB7": "Japanese Poet", "#FFE4B6C4": "Japanese Rose Garden", "#FF313739": "Japanese Sable", "#FF53594B": "Japanese Seaweed", "#FFB77B57": "Japanese Wax Tree", "#FFEEE6D9": "Japanese White", "#FF522C35": "Japanese Wineberry", "#FFD8A373": "Japanese Yew", "#FFCE7259": "Japonica Variant", "#FFBDD0AB": "Jardin", "#FFC6CAA7": "Jardin de Hierbas", "#FF019A74": "Jardini\u00e8re", "#FF53A38F": "Jargon Jade", "#FF827058": "Jarrah", "#FFFFF4BB": "Jasmine Variant", "#FFF4E8E1": "Jasmine Flower", "#FF79C63D": "Jasmine Green", "#FF7E7468": "Jasmine Hollow", "#FFDEE2D9": "Jasmine Rice", "#FFE7C89F": "Jasper Cane", "#FF57605A": "Jasper Green", "#FFDE8F4E": "Jasper Orange", "#FF4A6558": "Jasper Park", "#FFFA2B00": "Jasper Red", "#FF8D9E97": "Jasper Stone", "#FF8DC36A": "Jaunty Green", "#FF259797": "Java Variant", "#FF50859E": "Jay Bird", "#FF7994B5": "Jay Wing Feathers", "#FF464152": "Jazlyn", "#FF5F2C2F": "Jazz", "#FF3B4A6C": "Jazz Age Blues", "#FFF1BFB1": "Jazz Age Coral", "#FF1A6A9F": "Jazz Blue", "#FFD48740": "Jazz Brass", "#FF9892A8": "Jazz Tune", "#FF674247": "Jazzberry Jam Variant", "#FFB6E12A": "Jazzercise", "#FFC31E4E": "Jazzy", "#FF3A4C88": "Jazzy Blue", "#FF55DDCC": "Jazzy Jade", "#FF771C2B": "Jazzy Red", "#FFB36B92": "Je t\u2019aime", "#FFBB0099": "Jealous Jellyfish", "#FF7FAB60": "Jealousy", "#FF7B90A2": "Jean Jacket Blue", "#FF6D8994": "Jeans Indigo", "#FF041108": "Jedi Night", "#FFF1E4C8": "Jefferson Cream", "#FF44798E": "Jelly Bean Variant", "#FFEE1177": "Jelly Berry", "#FFDE6646": "Jelly Slug", "#FFEDE6D9": "Jelly Yoghurt", "#FF9B6575": "Jellybean Pink", "#FF95CAD0": "Jellyfish Blue", "#FFEE6688": "Jellyfish Sting", "#FFF6D67F": "Jemima", "#FFDFB886": "Jerboa", "#FF4D8681": "Jericho Jade", "#FFF5DEBB": "Jersey Cream", "#FF25B387": "Jess", "#FF216879": "Jester Blue", "#FFAC112C": "Jester Red", "#FF353337": "Jet Black", "#FFD1EAEC": "Jet d\u2019Eau", "#FF575654": "Jet Fuel", "#FF9D9A9A": "Jet Grey", "#FF2F3734": "Jet Set", "#FF5492AF": "Jet Ski", "#FFBBD0C9": "Jet Stream Variant", "#FFF2EDE2": "Jet White", "#FF005D96": "Jetski Race", "#FFA6D40D": "Jeune Citron", "#FF136843": "Jewel Variant", "#FF8CC90B": "Jewel Beetle", "#FFD374D5": "Jewel Caterpillar", "#FF3C4173": "Jewel Cave", "#FF46A795": "Jewel Weed", "#FFCFEEE1": "Jewel White", "#FFCED6E6": "Jewellery White", "#FFE6DDCA": "Jewett White", "#FFFFAAFF": "Jigglypuff", "#FF3D5D64": "Jimbaran Bay", "#FFF5D565": "J\u012bn Hu\u00e1ng Gold", "#FFA5A502": "J\u012bn S\u00e8 Gold", "#FF8E7618": "J\u012bn Z\u014dng Gold", "#FFEE827C": "Jinza Safflower", "#FFF7665A": "Jinzamomi Pink", "#FFBAC08A": "Jitterbug", "#FF019D6E": "Jitterbug Jade", "#FF8DB0AD": "Jitterbug Lure", "#FF77EEBB": "Jittery Jade", "#FF005B7A": "Job\u2019s Tears", "#FF77CC99": "Jocose Jade", "#FFCCE2CA": "Jocular Green", "#FF9BD7E9": "Jodhpur Blue", "#FFDAD1C8": "Jodhpur Tan", "#FFEBDCB6": "Jodhpurs", "#FF433F39": "Joesmithite", "#FFC0B9A9": "Jogging Path", "#FFEEFF22": "John Lemon", "#FFBC86AF": "Joie de Vivre", "#FFD9BD7D": "Jojoba", "#FFEA5505": "Jokaero Orange", "#FFD70141": "Joker\u2019s Smile", "#FFB2C1C2": "Jolene", "#FF5E774A": "Jolly Green", "#FF77CCBB": "Jolly Jade", "#FF82C785": "Jolt of Green", "#FF4ABCA0": "Jolt of Jade", "#FFEEF293": "Jonquil Tint", "#FFF7D395": "Jonquil Trail", "#FF037A3B": "Jordan Jazz", "#FF7AAAE0": "Jordy Blue Variant", "#FFD3C3BE": "Josephine", "#FF7FB377": "Joshua Tree", "#FFE6D3B2": "Journal White", "#FF016584": "Journey Into Night", "#FFCDECED": "Journey", "#FFBAC9D4": "Journey\u2019s End", "#FF55AAFF": "Joust Blue", "#FFEEB9A7": "Jovial", "#FF88DDAA": "Jovial Jade", "#FFF6EEC0": "Joyful", "#FFE4D4E2": "Joyful Lilac", "#FFFA9335": "Joyful Orange", "#FFEBADA5": "Joyful Poppy", "#FF503136": "Joyful Ruby", "#FF006669": "Joyful Tears", "#FFFFEEB0": "Joyous", "#FFAE2719": "Joyous Red", "#FF5B365E": "Joyous Song", "#FFF9900F": "J\u00fa Hu\u00e1ng Tangerine", "#FF4B373C": "Jube", "#FF78CF86": "Jube Green", "#FF44AA77": "Jubilant Jade", "#FF7BB92B": "Jubilant Meadow", "#FFFBDD24": "Jubilation", "#FF7E6099": "Jubilee", "#FF7C7379": "Jubilee Grey", "#FF473739": "Judah Silk", "#FF5D5346": "Judge Grey", "#FFC3C8B3": "Jugendstil Green", "#FF9D6375": "Jugendstil Pink", "#FF5F9B9C": "Jugendstil Turquoise", "#FF255367": "Juggernaut", "#FF442238": "Juice Violet", "#FFD9787C": "Juicy Details", "#FF7D6C4A": "Juicy Fig", "#FFEEDD33": "Juicy Jackfruit", "#FFB1CF5D": "Juicy Lime", "#FFFFD08D": "Juicy Mango", "#FFF18870": "Juicy Passionfruit", "#FFD99290": "Juicy Peach", "#FF57AA80": "Julep", "#FFC7DBD9": "Julep Green", "#FFA73940": "Jules", "#FFA5BEC8": "Juliet Blue", "#FF8BD2E3": "July", "#FF773B4A": "July Ruby", "#FF878785": "Jumbo Variant", "#FF887636": "Jumping Juniper", "#FF9BC4D4": "June", "#FF264A48": "June Bug", "#FFFFE182": "June Day", "#FF416858": "June Ivy", "#FFF1F1DA": "June Vision", "#FF775496": "Juneberry", "#FF00A466": "Jungle", "#FF446D46": "Jungle Adventure", "#FF654F2D": "Jungle Beat", "#FF366C4E": "Jungle Book Green", "#FF53665A": "Jungle Camouflage", "#FF69673A": "Jungle Civilization", "#FF686959": "Jungle Cloak", "#FF565042": "Jungle Cover", "#FFB49356": "Jungle Expedition", "#FF048243": "Jungle Green Variant", "#FF115511": "Jungle Jam", "#FF58A64B": "Jungle Jewels", "#FFA4C161": "Jungle Juice", "#FFC7BEA7": "Jungle Khaki", "#FF4F4D32": "Jungle King", "#FF727736": "Jungle Look", "#FFB0C4C4": "Jungle Mist Variant", "#FFBDC3AC": "Jungle Moss", "#FF36716F": "Jungle Noises", "#FF938326": "Jungle Palm", "#FF6D6F42": "Jungle Trail", "#FF65801D": "Jungle Vibes", "#FF74918E": "Juniper Variant", "#FF798884": "Juniper Ash", "#FF547174": "Juniper Berries", "#FFB9B3C2": "Juniper Berry", "#FF3F626E": "Juniper Berry Blue", "#FFD9E0D8": "Juniper Breeze", "#FF567F69": "Juniper Green", "#FFC4D3C5": "Juniper Mist", "#FF6B8B75": "Juniper Oil", "#FFFBECD3": "Junket", "#FF998778": "Junkrat", "#FFE1E1E2": "Jupiter", "#FFAC8181": "Jupiter Brown", "#FFE6A351": "Jurassic Gold", "#FF0D601C": "Jurassic Moss", "#FF3C663E": "Jurassic Park", "#FF6C5D97": "Just a Fairytale", "#FFDBE0D6": "Just a Little", "#FFFBD6D2": "Just a Tease", "#FFE2E7D3": "Just About Green", "#FFE8E8E0": "Just About White", "#FFFAB4A4": "Just Blush", "#FFFFBA45": "Just Ducky", "#FFD6C4C1": "Just Gorgeous", "#FFF8C275": "Just Peachy", "#FFEAECD3": "Just Perfect", "#FFFFEBEE": "Just Pink Enough", "#FFDCBFAC": "Just Right Variant", "#FFC4A295": "Just Rosey", "#FF606B8E": "Justice", "#FFAD9773": "Jute", "#FF815D40": "Jute Brown", "#FF8ABBD0": "Juvie", "#FFA1D5F1": "Juzcar Blue", "#FF736354": "K\u0101 F\u0113i S\u00e8 Brown", "#FFB14A30": "Kabacha Brown", "#FF038C67": "Kabalite Green", "#FF044A05": "Kabocha Green", "#FFA73A3E": "Kabuki", "#FFF0DEC1": "Kabuki Clay", "#FF6C5E53": "Kabul Variant", "#FFE94B7E": "Kacey\u2019s Pink", "#FF393E4F": "Kachi Indigo", "#FF816D5A": "Kaffee", "#FFB7BFB0": "Kahili", "#FFBAB099": "Kahlua Milk", "#FF0093D6": "Kahu Blue", "#FFEED484": "Kaiser Cheese", "#FF245336": "Kaitoke Green Variant", "#FF7D806E": "Kakadu Trail", "#FF298256": "K\u0101k\u0101riki Green", "#FF3E62AD": "Kakitsubata Blue", "#FF201819": "K\u0101l\u0101 Black", "#FF46444C": "Kala Namak", "#FF9F5440": "Kalahari Sunset", "#FF6A6555": "Kalamata", "#FF648251": "Kale", "#FF4F6A56": "Kale Green", "#FF8DA8BE": "Kaleidoscope", "#FF00505A": "Kali Blue", "#FF552288": "Kalish Violet", "#FFB59808": "Kalliene Yellow", "#FF0FFEF9": "Kaltes Klares Wasser", "#FFC6C2B6": "Kamenozoki Grey", "#FFCCA483": "Kamut", "#FFDD8833": "Kanafeh", "#FF2D8284": "Kandinsky Turquoise", "#FFC5C3B0": "Kangaroo Variant", "#FFC4AD92": "Kangaroo Fur", "#FFDECAC5": "Kangaroo Paw", "#FFBDA289": "Kangaroo Pouch", "#FFE4D7CE": "Kangaroo Tan", "#FFFEE7CB": "Kansas Grain", "#FF001146": "Kantor Blue", "#FFFF8936": "Kanz\u014d Orange", "#FFAD7D40": "Kaolin", "#FFC5DED1": "Kappa Green", "#FF783C1D": "Kara Cha Brown", "#FFB35C44": "Karacha Red", "#FFBB9662": "Karak Stone", "#FF2D2D24": "Karaka Variant", "#FFF04925": "Karaka Orange", "#FFC91F37": "Karakurenai Red", "#FF2196F3": "Karimun Blue", "#FF6E7955": "Kariyasu Green", "#FFB2A484": "Karma", "#FF9F78A9": "Karma Chameleon", "#FF545F8A": "Karmic Grape", "#FFFEDCC1": "Karry Variant", "#FF6F8D6A": "Kashmir", "#FF576D8E": "Kashmir Blue Variant", "#FFE9C8C3": "Kashmir Pink", "#FFF3DFD5": "Kasugai Peach", "#FF8FA099": "Kathleen\u2019s Garden", "#FFAD9A5D": "Kathmandu", "#FFC9E3CC": "Katsura", "#FFAA0077": "Katy Berry", "#FF66BC91": "Katydid", "#FF5AC7AC": "Kauai", "#FFEAABBC": "Kawaii", "#FFFEC50C": "Kazakhstan Yellow", "#FFD49595": "Keel Joy", "#FFA49463": "Keemun", "#FF226600": "Keen Green", "#FFC0CED6": "Keepsake", "#FFB899A2": "Keepsake Lilac", "#FFB08693": "Keepsake Rose", "#FF0000BC": "Keese Blue", "#FFD5D5CE": "Kefir", "#FF02AB2E": "Kelley Green", "#FFDEC7CF": "Kellie Belle", "#FF339C5E": "Kelly Green Variant", "#FFBABD6C": "Kelly\u2019s Flower", "#FF4D503C": "Kelp Variant", "#FF716246": "Kelp Brown", "#FF448811": "Kelp Forest", "#FF0092AE": "Kelp\u2019thar Forest Blue", "#FF437B48": "Kemp Kelly", "#FFEC2C25": "Ken Masters Red", "#FF547867": "Kendal Green", "#FFF7CCCD": "Kendall Rose", "#FFD45871": "Kenny\u2019s Kiss", "#FF543F32": "Kenp\u014d Brown", "#FF2E211B": "Kenp\u014dzome Black", "#FF6395BF": "Kentucky", "#FFA5B3CC": "Kentucky Blue", "#FF22AABB": "Kentucky Bluegrass", "#FFCCA179": "Kenya", "#FF6C322E": "Kenyan Copper Variant", "#FFBB8800": "Kenyan Sand", "#FF5FB69C": "Keppel Variant", "#FF5CB200": "Kermit Green", "#FFECB976": "Kernel", "#FFFFB210": "Kernel of Truth", "#FF524E4D": "Keshizumi Cinder", "#FFE0D6C8": "Kestrel White", "#FF9A382D": "Ketchup", "#FFA91C1C": "Ketchup Later", "#FF141314": "Kettle Black", "#FFF6E2BD": "Kettle Corn", "#FF9BCB96": "Kettle Drum", "#FF606061": "Kettleman", "#FFECD1A5": "Key Keeper", "#FF7FB6A4": "Key Largo", "#FFAEFF6E": "Key Lime", "#FFBB9B7C": "Key", "#FF759FC1": "Key West Zenith", "#FFD5AF91": "Key Wester", "#FFB39372": "Keystone", "#FFB6BBB2": "Keystone Grey", "#FF9E9284": "Keystone Taupe", "#FF954E2A": "Khaki Brown", "#FFFBE4AF": "Khaki Core", "#FF728639": "Khaki Green", "#FFD4C5AC": "Khaki Shade", "#FFC9BEAA": "Khaki Shell", "#FFB16840": "Khardic", "#FF76664C": "Khemri Brown", "#FFEE5555": "Khmer Curry", "#FF6A0001": "Khorne Red", "#FF7777CC": "Kickstart Purple", "#FFB6AEAE": "Kid Gloves", "#FFA81000": "Kid Icarus", "#FFED8732": "Kid\u2019s Stuff", "#FFBFC0AB": "Kidnapper Variant", "#FFFEF263": "Kihada Yellow", "#FF2E4EBF": "Kikorangi Blue", "#FFE29C45": "Kikuchiba Gold", "#FF5D3F6A": "Kiky\u014d Purple", "#FF843D38": "Kilauea Lava", "#FFD7C5AE": "Kilim Beige", "#FF3A3532": "Kilimanjaro Variant", "#FF498555": "Kilkenny", "#FF49764F": "Killarney Variant", "#FFC9D2D1": "Killer Fog", "#FFC6BEA9": "Kiln Clay", "#FFA89887": "Kiln Dried", "#FF386B7D": "Kimberley Sea", "#FF696FA5": "Kimberlite", "#FF695D87": "Kimberly Variant", "#FFED4B00": "Kimchi", "#FF896C39": "Kimirucha Brown", "#FF6D86B6": "Kimono", "#FF3D4C51": "Kimono Grey", "#FF75769B": "Kimono Violet", "#FFF39800": "Kin Gold", "#FFC66B27": "Kincha Brown", "#FFAAC2B3": "Kind Green", "#FFB8BFCA": "Kinder", "#FFA09174": "Kinderhook Clay", "#FF7A7068": "Kindling", "#FFD4B2C0": "Kindness", "#FF71A2D4": "Kindred", "#FF254D6A": "Kinetic Blue", "#FF64CDBE": "Kinetic Teal", "#FF5F686F": "King Creek Falls", "#FFC64A4A": "King Crimson", "#FF757166": "King Fischer", "#FFAA9977": "King Ghidorah", "#FF161410": "King Kong", "#FFADD900": "King Lime", "#FF77DD22": "King Lizard", "#FFFFB800": "King Nacho", "#FF7794C0": "King Neptune", "#FFC6DCE7": "King of Waves", "#FFD88668": "King Salmon", "#FF86B394": "King Theo", "#FF2A7279": "King Tide", "#FF3C85BE": "King Triton", "#FFC48692": "King\u2019s Cloak", "#FF706D5E": "King\u2019s Court", "#FFF2E887": "King\u2019s Field", "#FFB3107A": "King\u2019s Plum Pie", "#FFB59D77": "King\u2019s Ransom", "#FF6274AB": "King\u2019s Robe", "#FFD1A436": "Kingdom Gold", "#FFE9CFB7": "Kingdom\u2019s Keys", "#FF3A5760": "Kingfisher", "#FF006491": "Kingfisher Blue", "#FF096872": "Kingfisher Bright", "#FF583580": "Kingfisher Daisy Variant", "#FF7E969F": "Kingfisher Grey", "#FF007FA2": "Kingfisher Sheen", "#FF7AB6B6": "Kingfisher Turquoise", "#FFDEDEDE": "Kingly Cloud", "#FFDE9930": "Kingpin Gold", "#FF2D8297": "Kings of Sea", "#FFEAD665": "Kings Yellow", "#FFD4DCD3": "Kingston", "#FF8FBCC4": "Kingston Aqua", "#FFBB00BB": "Kinky Koala", "#FFEE55CC": "Kinky Pinky", "#FF7F7793": "Kinlock", "#FF7D4E2D": "Kinsusutake Brown", "#FFB45877": "Kir Royale Rose", "#FF5C6116": "Kirchner Green", "#FFC5C5D3": "Kiri Mist", "#FF8B352D": "Kiriume Red", "#FFB2132B": "Kirsch", "#FF974953": "Kirsch Red", "#FFEFCDCB": "Kislev Pink", "#FFA18AB7": "Kismet", "#FFD28CA7": "Kiss", "#FFBEC187": "Kiss a Frog", "#FFD86773": "Kiss and Tell", "#FFAA854A": "Kiss Candy", "#FFE5C8D9": "Kiss Good Night", "#FFE7EEEC": "Kiss Me Kate", "#FFDE6B86": "Kiss Me More", "#FF8A0009": "Kiss of a Vampire", "#FFEAE2AC": "Kiss of Lime", "#FFBDCFB2": "Kiss of Mint", "#FFDC331A": "Kiss of the Scorpion", "#FFFD8F79": "Kissable", "#FFB15363": "Kissed by a Zombies", "#FFFCCCF5": "Kissed by Mist", "#FFFF66BB": "Kisses", "#FFFF6677": "Kisses and Hugs", "#FF8AB5BD": "Kitchen Blue", "#FFC0816E": "Kitchen Terra Cotta", "#FF95483F": "Kite Brown", "#FFD0C8B0": "Kitsilano Cookie", "#FFBB8141": "Kitsurubami Brown", "#FFD3C7BC": "Kitten", "#FF8AADF7": "Kitten\u2019s Eye", "#FFDAA89B": "Kitten\u2019s Paw", "#FFCCCCBB": "Kittiwake Gull", "#FFC7BDB3": "Kitty Kitty", "#FFB7B5B1": "Kitty Whiskers", "#FF749E4E": "Kiwi", "#FF7BC027": "Kiwi Crush", "#FF8EE53F": "Kiwi Green", "#FFE5E7A7": "Kiwi Ice Cream", "#FFEEF9C1": "Kiwi Kiss", "#FF9CEF43": "Kiwi Pulp", "#FFDEE8BE": "Kiwi Sorbet", "#FFD1EDCD": "Kiwi Squeeze", "#FF909495": "Kiwikiwi Grey", "#FF2987C7": "Klaxosaur Blue", "#FF3FA282": "Klimt Green", "#FF95896C": "Knapsack", "#FF4B5B40": "Knarloc Green", "#FF926CAC": "Knight Elf", "#FF0F0707": "Knight Rider", "#FF5C5D5D": "Knight\u2019s Armour", "#FFAA91AE": "Knight\u2019s Tale", "#FF3C3F52": "Knighthood", "#FFEDCC99": "Knightley Straw", "#FF403C39": "Knights of the Desert", "#FFC2CCC7": "Knightsbridge Mist", "#FF6D6C5F": "Knit Cardigan", "#FFC3C1BC": "Knitting Needles", "#FF9F9B84": "Knock on Wood", "#FFC42B2D": "Knockout", "#FFE16F3E": "Knockout Orange", "#FFFF399C": "Knockout Pink", "#FF988266": "Knot", "#FF837F67": "Knotweed", "#FFB9ACA0": "Koala", "#FFBDB7A3": "Koala Bear", "#FFDB5A6B": "K\u014dbai Red", "#FFE093AB": "Kobi Variant", "#FFF0D2CF": "Kobold Pink", "#FF00AA22": "Kobra Khan", "#FFE8F5FC": "Kodama White", "#FFE97551": "Koeksister", "#FF883322": "Kofta Brown", "#FF773644": "K\u00f6fte Brown", "#FFE5B321": "Kogane Gold", "#FFCA6924": "Kohaku Amber", "#FFC0B76C": "Kohlrabi", "#FFD9D9B1": "Kohlrabi Green", "#FFD2663B": "Koi", "#FF797F63": "Koi Pond", "#FFF6AD49": "Koji Orange", "#FF8B7D3A": "Koke Moss", "#FF7B3B3A": "Kokiake Brown", "#FF3A243B": "Kokimurasaki Purple", "#FF7B785A": "Kokoda Variant", "#FF171412": "Kokushoku Black", "#FF00477A": "Kolibri Blue", "#FF7B8D42": "Komatsuna Green", "#FF7E726D": "Kombu", "#FF3A4032": "Kombu Green", "#FFD89F66": "Kombucha", "#FF9D907E": "Kommando Khaki", "#FFA06814": "Komodo", "#FFB38052": "Komodo Dragon", "#FFBBC5B2": "Komorebi", "#FF192236": "Kon", "#FF574B50": "Kona", "#FF003171": "Konj\u014d Blue", "#FF191F45": "Konkiky\u014d Blue", "#FF58D854": "Koopa Green Shell", "#FF833D3E": "Kopi Luwak", "#FF203838": "K\u014drainando Green", "#FFF2D1C3": "Koral Kicks", "#FF5D7D61": "Korean Mint", "#FF8D4512": "Korichnewyi Brown", "#FFD7E9C8": "Korila", "#FF804E2C": "Korma Variant", "#FFFEB552": "Koromiko Variant", "#FF592B1F": "K\u014drozen", "#FF888877": "Kosher Khaki", "#FFF9D054": "Kournikova Variant", "#FFE1B029": "K\u014dwhai Yellow", "#FFE1D956": "Kowloon", "#FFD5B59C": "Kraft Paper", "#FFCD48A9": "Krameria", "#FFEB2E28": "Krasnyi Red", "#FF633639": "Kremlin Red", "#FFC0BD81": "Krieg Khaki", "#FF01ABFD": "Krishna Blue", "#FFEA27C2": "Kriss Me Not Fuchsia", "#FFB8C0C3": "Krypton", "#FF83890E": "Krypton Green", "#FF439946": "Kryptonite Green", "#FFE8000D": "KU Crimson", "#FFFFDB4F": "Kuchinashi Yellow", "#FF87D3F8": "Kul Sharif Blue", "#FFFB9942": "Kumquat", "#FFD2CCDA": "Kundalini Bliss", "#FF643B42": "Kung Fu", "#FFDDB6C6": "Kunzite", "#FFD7003A": "Kurenai Red", "#FF001122": "Kuretake Black Manga", "#FF554738": "Kuri Black", "#FF583822": "Kuro Brown", "#FF1B2B1B": "Kuro Green", "#FF23191E": "Kurobeni", "#FF14151D": "Kuroi Black", "#FF9F7462": "Kurumizome Brown", "#FF5789A5": "Kuta Surf", "#FF55295B": "Kuwanomi Purple", "#FF59292C": "Kuwazome Red", "#FFC7610F": "Kvass", "#FF1560FB": "Kyanite", "#FFE8530F": "Kyawthuite", "#FF9D5B8B": "Kyo Purple", "#FFBEE3EA": "Kyoto", "#FF503000": "Kyoto House", "#FFDFD6D1": "Kyoto Pearl", "#FF4B5D16": "Kyuri Green", "#FF7A7A60": "La Grange", "#FFFC2647": "L\u00e0 Ji\u0101o H\u00f3ng Red", "#FFA3498A": "La la Lavender", "#FFBF90BB": "La la Love", "#FFFFFFE5": "La Luna", "#FFFDDFA0": "La Luna Amarilla", "#FFF5E5DC": "La Minute", "#FF428929": "La Palma Variant", "#FFC1E5EA": "La Paz Siesta", "#FF577E88": "La Pineta", "#FFBAC00E": "La Rioja Variant", "#FFEA936E": "La Terra", "#FFEECCDD": "LA Vibes", "#FFC4A703": "La Vida", "#FFD2A5A3": "La Vie en Rose", "#FFC3B1BE": "La-De-Dah", "#FFF2ECD9": "Labrador", "#FFD6C575": "Labrador\u2019s Locks", "#FF657B83": "Labradorite", "#FF547D80": "Labradorite Green", "#FFC9A487": "Labyrinth Walk", "#FFEBEAED": "Lace Cap", "#FFECEBEA": "Lace Veil", "#FFC2BBC0": "Lace Wisteria", "#FFCCEE99": "Laced Green", "#FFD7E3CA": "Lacewing", "#FFCAAEAB": "Lacey", "#FF1B322C": "Lacquer Green", "#FFF0CFE1": "Lacquer Mauve", "#FF383838": "Lacquered Liquorice", "#FF2E5C58": "Lacrosse", "#FF19504C": "Lacustral", "#FFA78490": "Lacy Mist", "#FFFF8E13": "Laddu Orange", "#FFD9DED8": "Ladoga Bottom", "#FFFDE2DE": "Lady Anne", "#FFFDE5A7": "Lady Banksia", "#FF8FA174": "Lady Fern", "#FFCCBBC0": "Lady Fingers", "#FFD0A4AE": "Lady Flower", "#FFCAA09E": "Lady Guinevere", "#FFB34B47": "Lady in Red", "#FF47613C": "Lady Luck", "#FFD6D6CD": "Lady Nicole", "#FF05498B": "Lady of the Night", "#FF0000CC": "Lady of the Sea", "#FFAEDCEC": "Lady-In-Waiting", "#FFC99BB0": "Lady\u2019s Cushions Pink", "#FFE3E3EA": "Lady\u2019s Slipper", "#FFBD474E": "Ladybug", "#FFFFC3BF": "Ladylike", "#FFF5C8DD": "Laelia Pink", "#FFF6F513": "Lager", "#FF1FB4C3": "Lago Blue", "#FF4B9B93": "Lagoon", "#FFEAEDEE": "Lagoon Mirror", "#FF8B7E64": "Lagoon Moss", "#FFA9B9BB": "Lagoon Reflection", "#FF43BCBE": "Lagoon Rock", "#FF76C6D3": "Lagoona Teal", "#FF36A5C9": "Laguna", "#FFE9D7C0": "Laguna Beach", "#FF5A7490": "Laguna Blue", "#FF01B1AF": "Laguna Green", "#FF5F5855": "Lahar", "#FFE2DAD1": "Lahmian Medium", "#FFFFF80A": "Lahn Yellow", "#FFB3AFA7": "Laid Back Grey", "#FF819EA1": "Laid-Back Blue", "#FF79853C": "Laird", "#FFA6D3E6": "Laissez-Faire", "#FF92CDCC": "Lake", "#FF155084": "Lake Baikal", "#FF009EAF": "Lake Blue", "#FFD9CFB5": "Lake Bluff Putty", "#FFAED4D2": "Lake Breeze", "#FF96B4B1": "Lake Forest", "#FF4181A6": "Lake Henry", "#FF689DB7": "Lake Lucerne", "#FF83A0B4": "Lake Okoboji", "#FFAEB9BC": "Lake Placid", "#FFB74A70": "Lake Red", "#FF9DD8DB": "Lake Reflection", "#FFEE55EE": "Lake Retba Pink", "#FF3E6B83": "Lake Stream", "#FF34B1B2": "Lake Tahoe Turquoise", "#FF44BBDD": "Lake Thun", "#FF2E4967": "Lake View", "#FF86ABA5": "Lake Water", "#FF80A1B0": "Lake Winnipeg", "#FF8B9CA5": "Lakefront", "#FF306F73": "Lakelike", "#FF5B96A2": "Lakeshore", "#FFADB8C0": "Lakeside", "#FFB7D1D1": "Lakeside Find", "#FFD7EEEF": "Lakeside Mist", "#FF566552": "Lakeside Pine", "#FF6C849B": "Lakeville", "#FFE6BF95": "Laksa", "#FFD85525": "L\u0101l Red", "#FFE0BB95": "Lama", "#FF82502C": "Lamb Chop", "#FFC8CCBC": "Lamb\u2019s Ears", "#FFFFFFE3": "Lamb\u2019s Wool", "#FF3B5B92": "Lambent Lagoon", "#FFEBDCCA": "Lambskin", "#FFBAD0D5": "Lament Blue", "#FFFFFEB6": "Lamenters Yellow", "#FF3AFDDB": "Lamiaceae", "#FFBBD9BC": "Lamina", "#FF948C7E": "Laminated Wood", "#FF4A4F55": "Lamp Post", "#FFFFD140": "Lamplight", "#FFE4AF65": "Lamplit", "#FF805557": "Lampoon", "#FF4D4DFF": "L\u00e1n S\u00e8 Blue", "#FF97DDD4": "Land Ahoy!", "#FFBFBEAD": "Land Before Time", "#FFDFCAAA": "Land Light", "#FFEDABE6": "Land of Dreams", "#FFA99B88": "Land of Nod", "#FFE0D5B9": "Land of Trees", "#FFC9BBA1": "Land Rush Bone", "#FFEEE1D9": "Landing", "#FFAF403C": "Landj\u00e4ger", "#FF746854": "Landmark", "#FF756657": "Landmark Brown", "#FFC1CFA9": "Landscape", "#FFB5AB9A": "Langdon Dove", "#FFDC5226": "Langoustine", "#FFCA6C56": "Langoustino", "#FFA4B7BD": "Languid Blue", "#FFCD0101": "Lannister Red", "#FFD87273": "Lantana", "#FFD7ECCD": "Lantana Lime", "#FFEFDDB8": "Lantern", "#FFFFD97D": "Lantern Gold", "#FFF6EBB9": "Lantern Light", "#FFC09972": "Lanyard", "#FFA6927F": "Lap Dog", "#FF515366": "Lap of Luxury", "#FF98BBB7": "Lap Pool Blue", "#FF00508D": "Lapis Blue", "#FF165D95": "Lapis Jewel", "#FF215F96": "Lapis Lazuli Blue", "#FF1F22D2": "Lapis on Neptune", "#FF7A7562": "Lapwing Grey Green", "#FFDFC6AA": "Larb Gai", "#FFFFAA77": "Larch Bolete", "#FF70BAA7": "Larchmere", "#FFD79A74": "Laredo", "#FFC7994F": "Laredo Road", "#FFE4E2D6": "Large Wild Convolvulus", "#FF551F2F": "Largest Black Slug", "#FF1D78AB": "Larimar Blue", "#FF93D3BC": "Larimar Green", "#FFB89B72": "Lark", "#FF8AC1A1": "Lark Green", "#FFC4CED1": "Lark Wing", "#FF3C7D90": "Larkspur", "#FF20AEB1": "Larkspur Blue", "#FF798BBD": "Larkspur Bouquet", "#FFB7C0EA": "Larkspur Bud", "#FF928AAE": "Larkspur Violet", "#FFC6DA36": "Las Palmas Variant", "#FFC6A95E": "Laser Variant", "#FFFF3F6A": "Laser Trap", "#FF475F94": "Last Light Blue", "#FFAADD66": "Last of Lettuce", "#FFCBBBCD": "Last of the Lilacs", "#FFE3DBCD": "Last Straw", "#FFD30F3F": "Last Warning", "#FFB36663": "Lasting Impression", "#FF88FF00": "Lasting Lime", "#FFD4E6B1": "Lasting Thoughts", "#FFF8AB33": "Late Afternoon", "#FFDB8959": "Late Bloomer", "#FFF2E08E": "Late Day Sun", "#FF3B2932": "Late Night Out", "#FF008A51": "Later Gator", "#FF3B9C98": "Latigo Bay", "#FF292E44": "Latin Charm", "#FFC5A582": "Latte", "#FFF3F0E8": "Latte Froth", "#FFE8E7E6": "Latteo", "#FFCECEC6": "Lattice", "#FF6F9843": "Lattice Green", "#FFB9E1C2": "Lattice Work", "#FF8CBF6F": "Laudable Lime", "#FFC9C3D2": "Laughing Jack", "#FFF0E5A4": "Laughing Liam", "#FFF49807": "Laughing Orange", "#FFC0E7EB": "Launderette Blue", "#FFA2ADB3": "Laundry Blue", "#FFF6F7F1": "Laundry White", "#FFA6979A": "Laura", "#FF800008": "Laura Potato", "#FF6E8D71": "Laurel Variant", "#FF68705C": "Laurel Garland", "#FFAAACA2": "Laurel Grey", "#FF969B8B": "Laurel Leaf", "#FFACB5A1": "Laurel Mist", "#FF55403E": "Laurel Nut Brown", "#FF918C7E": "Laurel Oak", "#FFF7E1DC": "Laurel Pink", "#FF889779": "Laurel Tree", "#FF44493D": "Laurel Woods", "#FF52A786": "Laurel Wreath", "#FFEFEAE7": "Lauren\u2019s Lace", "#FFD5E5E7": "Lauren\u2019s Surprise", "#FF868172": "Lauriston Stone", "#FFF3C64A": "Lauwiliwilinukunuku\u2019oi\u2019oi", "#FF352F36": "Lava Black", "#FF5A4239": "Lava Cake", "#FF764031": "Lava Core", "#FFDBD0CE": "Lava Geyser", "#FF5E686D": "Lava Grey", "#FFEB7135": "Lava Lamp", "#FFE5530E": "Lava Nectar", "#FFE46F34": "Lava Pit", "#FF535E64": "Lava Rock", "#FFD04014": "Lava Smoothie", "#FFAF9894": "Lavenbrun", "#FF8F818B": "Lavendaire", "#FFE9EBEE": "Lavendar Wisp", "#FFB56EDC": "Lavender Variant", "#FF9998A7": "Lavender Ash", "#FF9F99AA": "Lavender Aura", "#FFE5D9DA": "Lavender Bikini", "#FFB68FC3": "Lavender Blaze", "#FFD3B8C5": "Lavender Blessing", "#FFCEC3DD": "Lavender Bliss", "#FF8B88F8": "Lavender Blue Shadow", "#FF9994C0": "Lavender Bonnet", "#FFC7C2D0": "Lavender Bouquet", "#FFE4E1E3": "Lavender Breeze", "#FFFCB4D5": "Lavender Candy", "#FFB8ABB1": "Lavender Cloud", "#FFC79FEF": "Lavender Cream", "#FF956D9A": "Lavender Crystal", "#FFB4AECC": "Lavender Dream", "#FFAF92BD": "Lavender Earl", "#FF9D9399": "Lavender Elan", "#FF786C75": "Lavender Elegance", "#FFC0C3D7": "Lavender Escape", "#FFDFDAD9": "Lavender Essence", "#FFCAADD8": "Lavender Field Dreamer", "#FFC5B5CC": "Lavender Fog", "#FFDDBBFF": "Lavender Fragrance", "#FFBDABBE": "Lavender Frost", "#FFD3D0DD": "Lavender Haze", "#FFAD88A4": "Lavender Herb", "#FFC0C2D2": "Lavender Honour", "#FFA99BA7": "Lavender Illusion", "#FFD3B0CE": "Lavender Knoll", "#FFDFDDE0": "Lavender Lace", "#FFA198A2": "Lavender Lake", "#FF8C9180": "Lavender Leaf Green", "#FFA5969C": "Lavender Lily", "#FF8C9CC1": "Lavender Lustre", "#FFEE82ED": "Lavender Magenta Variant", "#FF687698": "Lavender Mauve", "#FFD3D3E2": "Lavender Memory", "#FFE5E5FA": "Lavender Mist", "#FFF2DDE3": "Lavender Moon", "#FF67636E": "Lavender Moor", "#FF857E86": "Lavender Mosaic", "#FFC0C0CA": "Lavender Oil", "#FFEDE5E8": "Lavender Pearl", "#FFCB82E3": "Lavender Perceptions", "#FFA6BADF": "Lavender Phlox", "#FFC5B9D3": "Lavender Pillow", "#FFE9E2E5": "Lavender Pizzazz", "#FFE9D2EF": "Lavender Princess", "#FFBD88AB": "Lavender Quartz", "#FFBEC2DA": "Lavender Sachet", "#FFC3B7BF": "Lavender Sand", "#FFEEDDFF": "Lavender Savour", "#FFBFACB1": "Lavender Scent", "#FFDBD7F2": "Lavender Sky", "#FFF1BFE2": "Lavender Soap", "#FFCFCEDC": "Lavender Sparkle", "#FF9392AD": "Lavender Spectacle", "#FFC6CBDB": "Lavender Steel", "#FFB4A5A0": "Lavender Suede", "#FFBD83BE": "Lavender Sweater", "#FFE6E6F1": "Lavender Syrup", "#FFD783FF": "Lavender Tea", "#FFCCBBFF": "Lavender Tonic", "#FFC8C1C9": "Lavender Vapour", "#FFD9BBD3": "Lavender Veil", "#FF767BA5": "Lavender Violet", "#FFE3D7E5": "Lavender Vista", "#FFAAB0D4": "Lavender Wash", "#FFD2C9DF": "Lavender Water", "#FFB86FC2": "Lavendless", "#FFBCA4CB": "Lavendula", "#FFA38154": "Lavish Gold", "#FF7ED0B7": "Lavish Green", "#FFC2AEC3": "Lavish Lavender", "#FFF9EFCA": "Lavish Lemon", "#FFB0C175": "Lavish Lime", "#FF8469BC": "Lavish Spending", "#FF4DA409": "Lawn Green Variant", "#FF5EB56A": "Lawn Party", "#FF5C7186": "Layers of Ocean", "#FFAB9BA5": "Laylock", "#FF174C60": "Lazurite Blue", "#FF97928B": "Lazy Afternoon", "#FFE2E5C7": "Lazy Caterpillar", "#FFF6EBA1": "Lazy Daisy", "#FF95AED1": "Lazy Day", "#FFBEC1C3": "Lazy Grey", "#FFA3A0B3": "Lazy Lavender", "#FF6E6E5C": "Lazy Lichen", "#FF9C9C4B": "Lazy Lizard", "#FFCC0011": "Lazy Shell Red", "#FFFEF3C3": "Lazy Summer Day", "#FFFEC784": "Lazy Sun", "#FFCAD3E7": "Lazy Sunday", "#FFE4CB6B": "Le Bon Dijon", "#FFBF4E46": "Le Corbusier Crush", "#FF244E94": "Le Grand Bleu", "#FF5E6869": "Le Luxe", "#FF85B2A1": "Le Max", "#FF212121": "Lead", "#FF6C809C": "Lead Cast", "#FFFFFAE5": "Lead Glass", "#FF8A7963": "Lead Grey", "#FF99AABB": "Lead Ore", "#FFCACACB": "Leadbelcher", "#FF888D8F": "Leadbelcher Metal", "#FF71AA34": "Leaf", "#FFEFF19D": "Leaf Bud", "#FFE1D38E": "Leaf Print", "#FF6CB506": "Leaf Sheep", "#FFE9D79E": "Leaf Yellow", "#FF8B987B": "Leaflet", "#FF679B6A": "Leafy", "#FFAACC11": "Leafy Canopy", "#FF80BB66": "Leafy Greens", "#FFC0F000": "Leafy Lemon", "#FF7D8574": "Leafy Lichen", "#FF08690E": "Leafy Lush", "#FF8D9679": "Leafy Rise", "#FFB6C406": "Leafy Seadragon", "#FFAABB11": "Leafy Woodland", "#FFA0B7A8": "Leamington Spa", "#FFC4D3E3": "Leap of Faith", "#FF41A94F": "Leapfrog", "#FF447A66": "Leaping Lizard", "#FF6F803B": "Leaps and Bounds", "#FFABC123": "Learning Green", "#FF906A54": "Leather Variant", "#FF76563E": "Leather & Lace", "#FF916E52": "Leather Bound", "#FF97502B": "Leather Brown", "#FFA3754C": "Leather Chair", "#FF744E42": "Leather Clutch", "#FF867354": "Leather Loafers", "#FF7C4F3A": "Leather Satchel", "#FFA48454": "Leather Tan", "#FF8A6347": "Leather Work", "#FF3C351F": "LeChuck\u2019s Beard", "#FF0066A3": "LED Blue", "#FFD8CB32": "LED Green", "#FF98D98E": "Leek", "#FFBCA3B8": "Leek Blossom Pink", "#FF979C84": "Leek Green", "#FFB7B17A": "Leek Powder", "#FF7A9C58": "Leek Soup", "#FFCEDCCA": "Leek White", "#FFF5C71A": "Leery Lemon", "#FF4C2226": "Lees", "#FF699CAB": "Left Bank Blue", "#FFFF0303": "Left on Red", "#FF5E5A67": "Legacy", "#FF9EC9E2": "Legacy Blue", "#FF6D758F": "Legal Eagle", "#FF6F434A": "Legal Ribbon", "#FFC6BAAF": "Legendary", "#FF787976": "Legendary Grey", "#FF9D61D4": "Legendary Lavender", "#FFAD969D": "Legendary Lilac", "#FF4E4E63": "Legendary Purple", "#FF7F8384": "Legendary Sword", "#FF245A6A": "Legion Blue", "#FFD87B6A": "Lei Flower", "#FFC19634": "Leisure", "#FF6A8EA1": "Leisure Blue", "#FF438261": "Leisure Green", "#FF758C8F": "Leisure Time", "#FFEFE4AE": "Lemon Appeal", "#FFE5D9B6": "Lemon Balm", "#FF005228": "Lemon Balm Green", "#FFCEA02F": "Lemon Bar", "#FFFCECAD": "Lemon Blast", "#FFFCEBBF": "Lemon Bubble", "#FFFEF59F": "Lemon Bundt Cake", "#FFFED67E": "Lemon Burst", "#FFFCE99F": "Lemon Butter", "#FFF7DE9D": "Lemon Caipirinha", "#FFFAE8AB": "Lemon Candy", "#FFFFF7C4": "Lemon Chiffon Pie", "#FFFAAE00": "Lemon Chrome", "#FFFEE193": "Lemon Cream", "#FFFFEE11": "Lemon Curd", "#FFCDA323": "Lemon Curry", "#FFFCE699": "Lemon Delicious", "#FFEEA300": "Lemon Dream", "#FFFEE483": "Lemon Drizzle", "#FFFFE49D": "Lemon Drops", "#FFF4EFDE": "Lemon Edge", "#FFE2AE4D": "Lemon Essence", "#FFD4D292": "Lemon Fennel", "#FFF9E4A6": "Lemon Filling", "#FFF0E891": "Lemon Flesh", "#FF96FBC4": "Lemon Gate", "#FFF8EC9E": "Lemon Gelato", "#FF968428": "Lemon Ginger Variant", "#FFADF802": "Lemon Green", "#FFF6EBC8": "Lemon Icing", "#FFFFFFEC": "Lemon Juice", "#FFFAF4D9": "Lemon Lily", "#FFBFFE28": "Lemon Lime", "#FFCBBA61": "Lemon Lime Mojito", "#FFF6E199": "Lemon Meringue", "#FFF9F1DB": "Lemon Pearl", "#FFFFED80": "Lemon Peel", "#FFEBECA7": "Lemon Pepper", "#FFF1FF62": "Lemon Pie", "#FFE1AE58": "Lemon Poppy", "#FFFAF2D1": "Lemon Popsicle", "#FFFFDD93": "Lemon Pound Cake", "#FFFECF24": "Lemon Punch", "#FFFBE9AC": "Lemon Rose", "#FFFAF0CF": "Lemon Sachet", "#FFF1FFA8": "Lemon Sherbet", "#FFFFFBA8": "Lemon Slice", "#FFFFFCC4": "Lemon Soap", "#FFFFFAC0": "Lemon Sorbet", "#FFDCC68E": "Lemon Sorbet Yellow", "#FFFFE8AD": "Lemon Souffle", "#FFF9F6DE": "Lemon Splash", "#FFF7F0E1": "Lemon Sponge Cake", "#FFFBF7E0": "Lemon Stick", "#FFF0F6DD": "Lemon Sugar", "#FFE1BC5C": "Lemon Surprise", "#FFFFDD66": "Lemon Tart", "#FFE4CF86": "Lemon Thyme", "#FFFCF3CB": "Lemon Tint", "#FFEFEFB2": "Lemon Tonic", "#FFFED95D": "Lemon Twist", "#FFF2ED6B": "Lemon Verbena", "#FFFFE6BA": "Lemon Whip", "#FFFFB10D": "Lemon Whisper", "#FFFBF6E0": "Lemon White", "#FFF9D857": "Lemon Zest", "#FFEFE499": "Lemonade", "#FFF2CA3B": "Lemonade Stand", "#FF999A86": "Lemongrass", "#FFF9F3D7": "Lemonwood Place", "#FF695F4F": "Lemur", "#FFBFB9D4": "Lemures", "#FFCEE2E2": "Lens Flare Blue", "#FFB0FF9D": "Lens Flare Green", "#FFE4CBFF": "Lens Flare Pink", "#FF6FB5A8": "Lenticular Ore", "#FFDCC8B0": "Lentil", "#FFA09548": "Lentil Sprout", "#FFBA93D8": "Lenurple", "#FFE042D5": "Leo Royal Fuchsia", "#FFD09800": "Leopard", "#FF29906D": "Leprechaun", "#FF395549": "Leprechaun Green", "#FF8CC37D": "Leprechaun Laugh", "#FFD99631": "Leprous Brown", "#FFD0A000": "Lepton Gold", "#FF71635A": "Leroy", "#FF0F63B3": "Les Cavaliers Beach", "#FFE59D7B": "Les Demoiselles d\u2019Avignon", "#FF756761": "Less Brown", "#FF5D6957": "Less Traveled", "#FFAFD1C4": "Lester", "#FFB6B8BD": "Let It Rain", "#FFCFAE74": "Let It Ring", "#FFD8F1F4": "Let It Snow", "#FF88FF11": "Lethal Lime", "#FF95BE76": "Leticiaz", "#FF8F8F8B": "Letter Grey", "#FFCEDDA2": "Lettuce Alone", "#FFB9D087": "Lettuce Green", "#FF92A772": "Lettuce Mound", "#FFF2F1ED": "Leukocyte White", "#FFE8E8E9": "Leukophobia", "#FFE4090B": "Level !", "#FF468741": "Level Up", "#FFEDCDC2": "Leverkaas", "#FF8B2A98": "Leviathan Purple", "#FFCEA269": "Lewis Gold", "#FFF8F1D3": "Lewisburg Lemon", "#FF675A49": "Lewisham", "#FF00E436": "Lexaloffle Green", "#FF7D9294": "Lexington Blue", "#FF8C3F52": "Liaison", "#FFF075E6": "Li\u00e1n H\u00f3ng Lotus Pink", "#FFCCB8D2": "Liberace", "#FF0C4792": "Liberalist", "#FFD8DDCC": "Liberated Lime", "#FFE8C447": "Liberator Gold", "#FFEFE2DB": "Liberia", "#FF514B98": "Liberty Variant", "#FF696B6D": "Liberty Bell Grey", "#FF0E1531": "Liberty Blue", "#FF16A74E": "Liberty Green", "#FFAFBFC9": "Liberty Grey", "#FF4CD5FF": "Libra Blue Morpho", "#FFE6C19F": "Library Card", "#FF68554E": "Library Leather", "#FF8F7459": "Library Oak", "#FF7F7263": "Library Pewter", "#FF5B3530": "Library Red", "#FFA9A694": "Lich Grey", "#FF730061": "Liche Purple", "#FF8EBAA6": "Lichen", "#FF5D89B3": "Lichen Blue", "#FFDDE7AE": "Lichen Gold", "#FF9DA693": "Lichen Green", "#FF697056": "Lichen Moss", "#FFEE5577": "Lick and Kiss", "#FFA6CC72": "Lick of Lime", "#FFB4496C": "Lickedy Lick", "#FFC3D997": "Lickety Split", "#FFC99C59": "Liddell", "#FF019294": "Lido Deck", "#FF92B498": "Liebermann Green", "#FFFDFF38": "Liechtenstein Yellow", "#FFA2B0A8": "Life Aquatic", "#FFAFC9DC": "Life at Sea", "#FF6FB7E0": "Life Force", "#FFE5CDBE": "Life Is a Peach", "#FFE19B42": "Life Is Good", "#FFC5CABE": "Life Lesson", "#FF81B6BC": "Lifeboat Blue", "#FFE50000": "Lifeguard", "#FF00DEAD": "Lifeless Green", "#FFE6D699": "Lifeless Planet", "#FF990033": "Lifeline", "#FFCDD6C2": "Ligado", "#FFC3C5C5": "Light Aluminium", "#FFED9A76": "Light Amber Orange", "#FFD4D3E0": "Light Amourette", "#FFFFD5A1": "Light and Fluffy", "#FFDAD4E4": "Light Angel Kiss", "#FFC9D4E1": "Light Angora Blue", "#FFF2DAD6": "Light Apricot Variant", "#FFDECFD2": "Light Aroma", "#FFC2A487": "Light Ash Brown", "#FFDAC6A1": "Light Bamboo", "#FFDED0D8": "Light Bassinet", "#FFABD5DC": "Light Bathing", "#FFE5DECA": "Light Beige", "#FF9DB567": "Light Birch Green", "#FFD5D4D0": "Light Bleaches", "#FFE8D3AF": "Light Blond", "#FFECDDD6": "Light Blossom Time", "#FFD2D3E1": "Light Blue Cloud", "#FFA8D3E1": "Light Blue Glint", "#FFB7C9E2": "Light Blue Grey", "#FFC6DDE4": "Light Blue Sloth", "#FFC0D8EB": "Light Blue Veil", "#FFA4DBE4": "Light Bluish Water", "#FFE9C4CC": "Light Blush", "#FFADD2E3": "Light Bobby Blue", "#FFCFE0F2": "Light Breeze", "#FF94D0E9": "Light Bright Spark", "#FFBC9468": "Light Bronzer", "#FFB08699": "Light Brown Drab", "#FFD6D5D2": "Light Brume", "#FF9ED6E8": "Light Budgie Blue", "#FFDECED1": "Light Bunny Soft", "#FFC6D4E1": "Light Cameo Blue", "#FFC9D2DF": "Light Candela", "#FF8BD4C3": "Light Capri Green", "#FFA38A83": "Light Caramel", "#FFDBD9C9": "Light Cargo River", "#FFF9DBCF": "Light Carob", "#FFD8F3D7": "Light Carolina", "#FFD8F2DC": "Light Celery Stick", "#FFD1C6BE": "Light Chamois Beige", "#FF726E68": "Light Charcoal", "#FFECEAD1": "Light Chervil", "#FFF4E7E5": "Light Chiffon", "#FFE0D5C9": "Light Chintz", "#FFDFD3CA": "Light Christobel", "#FFD5DAD1": "Light Cipollino", "#FFAFD5D8": "Light Continental Waters", "#FFC48F4B": "Light Copper", "#FFF3E2D1": "Light Corn", "#FFE0C3A2": "Light Corn Yellow", "#FFDDD7D1": "Light Crushed Almond", "#FFCBD7ED": "Light Cuddle", "#FFF9E9C9": "Light Curd", "#FFC2E4E7": "Light Daly Waters", "#FFC6DEDF": "Light Dante Peak", "#FFE2D9D2": "Light Daydreamer", "#FFFCE9D5": "Light Dedication", "#FF9ED1E3": "Light Delphin", "#FFA4D4EC": "Light Deluxe Days", "#FFCDDBDC": "Light Detroit", "#FFC4DADD": "Light Dewpoint", "#FFA7AEA5": "Light Drizzle", "#FFD4E3D7": "Light Dry Lichen", "#FFD5EBDD": "Light Duck Egg Cream", "#FFD4CED1": "Light Easter Rabbit", "#FFD9D2C9": "Light Eggshell Pink", "#FFEAD5C7": "Light Ellen", "#FFD8CDD3": "Light Elusive Dream", "#FFD6EADB": "Light Enchanted", "#FFF3DED7": "Light Fairy Pink", "#FFEAD3E0": "Light Favourite Lady", "#FFD3D9C5": "Light Feather Green", "#FFC1D8EB": "Light Featherbed", "#FFE6E6D0": "Light Fern Green", "#FFC9CFCC": "Light French Grey", "#FFC2C0BB": "Light French Taupe", "#FFE2F4D7": "Light Fresh Lime", "#FFECF4D2": "Light Freshman", "#FFEDE8D7": "Light Frost", "#FFD7EFD5": "Light Frosty Dawn", "#FFD2D9CD": "Light Gentle Calm", "#FFD7D3CA": "Light Ghosting", "#FFF7D28C": "Light Ginger Yellow", "#FFC0B5AA": "Light Glaze", "#FFE2DDCF": "Light Granite", "#FF70AA7C": "Light Grass Green", "#FF76FF7B": "Light Green Tint", "#FFD5D8C9": "Light Green Alabaster", "#FFD7DDCD": "Light Green Ash", "#FFE5F4D5": "Light Green Glint", "#FFE8F4D2": "Light Green Veil", "#FFD4E6D9": "Light Green Wash", "#FFE2F0D2": "Light Greenette", "#FFD7D4E4": "Light Gregorio Garden", "#FFD8DCD6": "Light Grey", "#FFCDD6EA": "Light Hindsight", "#FFDCCFCE": "Light Hint of Lavender", "#FFE5DDCB": "Light Hog Bristle", "#FFD0D2DE": "Light Horizon Sky", "#FFD8DED0": "Light Iced Aniseed", "#FFD0D4E3": "Light Iced Lavender", "#FFAED4D8": "Light Imagine", "#FFEFDCBE": "Light Incense", "#FFE2D9D4": "Light Instant", "#FFDBE4D1": "Light Issey-San", "#FFACD6DB": "Light Jellyfish Blue", "#FFD6EAD8": "Light Katsura", "#FF998D7C": "Light Khaki", "#FFD3D2DD": "Light Kiri Mist", "#FFD6D9CB": "Light Lamb\u2019s Ears", "#FFEFC0FE": "Light Lavender", "#FFE3D2CF": "Light Lavender Blush", "#FFDDD6E7": "Light Lavender Water", "#FFBFB6A9": "Light Lichen", "#FFD9E0D0": "Light Ligado", "#FFEED2D7": "Light Light Blush", "#FFD3E7DC": "Light Light Lichen", "#FFDCC6D2": "Light Lilac", "#FFD8E6CE": "Light Lime Sherbet", "#FFDBD5CE": "Light Limed White", "#FFDAD1D7": "Light Limpid Light", "#FFE7D9D4": "Light Lip Gloss", "#FFD8D7CA": "Light Livingstone", "#FFD1F0DD": "Light Lost Lace", "#FFDCD5D3": "Light Lunette", "#FFDBD5DA": "Light Magnolia Rose", "#FF9B8B7C": "Light Mahogany", "#FFF6DDCE": "Light Maiden\u2019s Blush", "#FFE3DBD0": "Light Male", "#FFF4DDDB": "Light Marshmallow Magic", "#FFD1EFDD": "Light Martian Moon", "#FFC292A1": "Light Mauve Variant", "#FFCEE1D9": "Light Meadow Lane", "#FFB6FFBB": "Light Mint", "#FFDCE1D5": "Light Mist", "#FFB18673": "Light Mocha", "#FFDED5E2": "Light Modesty", "#FFC4D9EB": "Light Morality", "#FFD8CDD0": "Light Mosque", "#FFD1CAE1": "Light Mulberry", "#FFF8611A": "Light My Fire", "#FFD6E4D4": "Light Mystified", "#FFFBE6C7": "Light Nougat", "#FFF4DCDC": "Light Nursery", "#FFE3D8D4": "Light Nut Milk", "#FFD2B183": "Light Oak", "#FFEAF3D0": "Light of New Hope", "#FFACBF69": "Light Olive", "#FFC1E8EA": "Light Opale", "#FFD6CDD0": "Light Orchid Haze", "#FFCDE7DD": "Light Otto Ice", "#FFD4CBCE": "Light Pale Pearl", "#FFDBDACB": "Light Pale Tendril", "#FFB2FBA5": "Light Pastel Green", "#FFC0C2B4": "Light Patina", "#FFD5D3E3": "Light Pax", "#FFFFE6D8": "Light Peach Rose", "#FFDCD6D1": "Light Pearl Ash", "#FFBEC8D8": "Light Pearl Soft Blue", "#FFE1CED4": "Light Pelican Bill", "#FFC8D4E7": "Light Penna", "#FFD0D0D7": "Light Pensive", "#FFC1C6FC": "Light Periwinkle", "#FFE0D5CD": "Light Perk Up", "#FFF0D7D7": "Light Petite Pink", "#FFECDBD6": "Light Pianissimo", "#FFCDE5DE": "Light Picnic Bay", "#FFFFD1DF": "Light Pink Tint", "#FFDDCED1": "Light Pink Linen", "#FFE9D3D5": "Light Pink Pandora", "#FFD8C9CC": "Light Pink Polar", "#FFE2DEC8": "Light Pistachio Tang", "#FFC8D8E8": "Light Placid Blue", "#FFEBE1CB": "Light Pollinate", "#FFE7DAD7": "Light Porcelain", "#FFC4D9EF": "Light Powder Blue", "#FFD1D6EB": "Light Powdered Granite", "#FFC5D0D9": "Light Pre School", "#FFD9CED5": "Light Puffball", "#FFC2A585": "Light Pumpkin Brown", "#FFC2D2D8": "Light Pure Blue", "#FFE0D5E9": "Light Purity", "#FFCDDED7": "Light Quaver", "#FFFDE1D4": "Light Quilt", "#FFC6D5EA": "Light Radar", "#FFDACDB6": "Light Raffia", "#FFD1C1AA": "Light Rattan", "#FFECDFCA": "Light Raw Cotton", "#FFFF7F7F": "Light Red", "#FFCADDDE": "Light Relax", "#FFC3D5E5": "Light Ridge Light", "#FF615544": "Light Roast", "#FFF4D4D6": "Light Rose", "#FFF9EBE4": "Light Rose Beige", "#FFFFCCA5": "Light Saffron Orange", "#FFB3B0A3": "Light Sage", "#FFCCF1E3": "Light Salome", "#FFBBD3DA": "Light Salt Spray", "#FFDEDCC6": "Light Sandbank", "#FFE1DACF": "Light Sandy Day", "#FFB7CDD9": "Light Sea Breeze", "#FFB9D4E7": "Light Sea Cliff", "#FFABD6DE": "Light Sea Spray", "#FFA0FEBF": "Light Sea-Foam", "#FFA7FFB5": "Light Seafoam Green", "#FFE0E9D0": "Light Security", "#FFF1E8CE": "Light Shell Haven", "#FFFCE0D6": "Light Shell Tint", "#FFE7DCCF": "Light Shetland Lace", "#FFA3D4EF": "Light Shimmer", "#FF8E8887": "Light Sh\u014dchi Black", "#FF1155FF": "Light Sh\u014djin Blue", "#FFD0181F": "Light Sh\u014drei Red", "#FFCBE8DF": "Light Short Phase", "#FFF7E582": "Light Sh\u014dshin Yellow", "#FFAA55EE": "Light Sh\u014dtoku Purple", "#FFCEF2E4": "Light Shutterbug", "#FFD4DBD1": "Light Silver Grass", "#FFCEE3D9": "Light Silverton", "#FFA1D0E2": "Light Sky Babe", "#FFAFCFE0": "Light Sky Bus", "#FFBAD7DC": "Light Sky Chase", "#FFC2E3E8": "Light Skyway", "#FFCFD1D8": "Light Slipper Satin", "#FFCEDCD4": "Light Soft Celadon", "#FFCFE0D7": "Light Soft Fresco", "#FFCFDED7": "Light Spearmint Ice", "#FFB3A18E": "Light Spice", "#FFC3CAD3": "Light Spirit", "#FFD8EEE7": "Light Spirited", "#FFE0CFD2": "Light Sprig Muslin", "#FFD6E8D5": "Light Spring Burst", "#FFE3E3D7": "Light Sprinkle", "#FFC7D2DD": "Light Stargate", "#FFCBD0D7": "Light Starlight", "#FFD2CCD1": "Light Stately Frills", "#FFDCD1CC": "Light Stone", "#FFE4DAD3": "Light Subpoena", "#FFDEEDD4": "Light Tactile", "#FFB19D8D": "Light Taupe Variant", "#FFD5D0CB": "Light Taupe White", "#FFB1CCC5": "Light Teal", "#FFBBD6EA": "Light Template", "#FFDF9B81": "Light Terracotta", "#FFE2D8D4": "Light Thought", "#FFBCD6E9": "Light Tidal Foam", "#FFC5D2DF": "Light Time Travel", "#FFE1D0D8": "Light Tip Toes", "#FFD0756F": "Light Tomato", "#FFB08971": "Light Topaz Ochre", "#FFB5CDD7": "Light Topaz Soft Blue", "#FFF5ECDF": "Light Touch", "#FF7EF4CC": "Light Turquoise", "#FFBFE7EA": "Light Vandamint", "#FFB8CED9": "Light Vanilla Ice", "#FFD8D5D0": "Light Vanilla Quake", "#FFD6B4FC": "Light Violet", "#FFD4CCCE": "Light Wallis", "#FFACDCE7": "Light Washed Blue", "#FFBFD5EB": "Light Water Wash", "#FFC2F0E6": "Light Water Wings", "#FFB7DADD": "Light Watermark", "#FFE6DAD6": "Light Watermelon Milk", "#FFE0D4D0": "Light Weathered Hide", "#FF99D0E7": "Light Whimsy", "#FFCEDCD6": "Light White Box", "#FFBFBFB4": "Light Year", "#FFFFFE7A": "Light Yellow Variant", "#FFC2FF89": "Light Yellowish Green", "#FFEAD7D5": "Light Youth", "#FFD1DBD2": "Light Zen", "#FF75FD63": "Lighter Green", "#FFDFEBDD": "Lighter Mint", "#FFDCE4D6": "Lightest Sky", "#FFF7E0E1": "Lighthearted", "#FFEDD5DD": "Lighthearted Pink", "#FFC7A1A9": "Lighthearted Rose", "#FFF3F4F4": "Lighthouse", "#FFB4C4CA": "Lighthouse Shadows", "#FFD9DCD5": "Lighthouse View", "#FF3D7AFD": "Lightish Blue", "#FF61E160": "Lightish Green", "#FFA552E6": "Lightish Purple", "#FFF0EDA8": "Lightly Lime", "#FFE5EBE6": "Lightning Bolt", "#FFEFDE74": "Lightning Bug", "#FFF8EDD1": "Lightning White", "#FFF7A233": "Lightning Yellow Variant", "#FF01968B": "Lights at Sea", "#FFF8F2DE": "Lights of Shibuya", "#FF3D474B": "Lights Out", "#FF15F2FD": "Lightsaber Blue", "#FFF6E5C5": "Lightweight Beige", "#FF67765B": "Lignum Vit\u0153 Foliage", "#FFD2B18F": "Ligonier Tan", "#FFD1B7A8": "Likeable Sand", "#FFCEA2FD": "Lilac Variant", "#FFD7CDCD": "Lilac Ash", "#FFC6CDE0": "Lilac Bisque", "#FFAFABB8": "Lilac Bloom", "#FF9A93A9": "Lilac Blossom", "#FF8293AC": "Lilac Blue", "#FFA590C0": "Lilac Breeze", "#FFD7BDBE": "Lilac Buds", "#FF9470C4": "Lilac Bush Variant", "#FFDFE1E6": "Lilac Champagne", "#FFDE9BC4": "Lilac Chiffon", "#FFCDD7EC": "Lilac Cotton Candy", "#FFCBC5D9": "Lilac Crystal", "#FF8F939D": "Lilac Fields", "#FFB2BADB": "Lilac Flare", "#FFC8A4BF": "Lilac Fluff", "#FFE8DEEA": "Lilac Frost", "#FFBB88FF": "Lilac Geode", "#FFD5B6D4": "Lilac Haze", "#FFCACBD5": "Lilac Hint", "#FFA7ADBE": "Lilac Hush", "#FF9A7EA7": "Lilac Intuition", "#FFC6A1CF": "Lilac Lace", "#FFDFCBDA": "Lilac Lane", "#FFFF3388": "Lilac Lotion", "#FFC3B9D8": "Lilac Lust", "#FFC3BABF": "Lilac Marble", "#FFD6D0D6": "Lilac Mauve", "#FFE4E4E7": "Lilac Mist", "#FFE5E6EA": "Lilac Murmur", "#FFE9E8E5": "Lilac Muse", "#FFDCBBBA": "Lilac Paradise", "#FFC09DC8": "Lilac Pink", "#FFA183C0": "Lilac Purple", "#FFBD4275": "Lilac Rose", "#FFABB6D7": "Lilac Sachet", "#FF9EABD0": "Lilac Scent Soft Blue", "#FFB6A3A0": "Lilac Smoke", "#FFDCC0D3": "Lilac Snow", "#FF8822CC": "Lilac Spring", "#FFBA9B97": "Lilac Suede", "#FFD4C7C4": "Lilac Tan", "#FFA4ABBF": "Lilac Time", "#FF754A80": "Lilac Violet", "#FFE9CFE5": "Lilacs in Spring", "#FFCC99FF": "Lil\u00e1s", "#FFD4A1B0": "Lili Elbe\u2019s Pink", "#FFC48EFD": "Liliac", "#FFE1BF03": "Lilikoi", "#FF874886": "Lilikoi Flower Purple", "#FF88DD55": "Lilliputian Lime", "#FFFCEBD8": "Lilting Laughter", "#FFC19FB3": "Lily Variant", "#FFEEC7D6": "Lily Legs", "#FF9191BB": "Lily of the Nile", "#FFE2E3D6": "Lily of the Valley White", "#FF818F84": "Lily Pad Pond", "#FF6DB083": "Lily Pads", "#FFDEEAD8": "Lily Pond", "#FF55707F": "Lily Pond Blue", "#FFE6E6BC": "Lily Scent Green", "#FFF5DEE2": "Lily the Pink", "#FFE0E1C1": "Lilylock", "#FFA9F971": "Lima Variant", "#FFE1D590": "Lima Bean", "#FF88BE69": "Lima Bean Green", "#FFB1B787": "Lima Green", "#FF7AAC21": "Lima Sombrio", "#FF988870": "Limbert Leather", "#FFAAFF32": "Lime Tint", "#FFF4F2D3": "Lime Blossom", "#FFDAE3D0": "Lime Cake", "#FFAAFF00": "Lime Candy Pearl", "#FFE5DDC8": "Lime Chalk", "#FFE6EFCC": "Lime Coco Cake", "#FFD0E3AD": "Lime Cream", "#FFDDE6D7": "Lime Daiquiri", "#FFC2ECBC": "Lime Dream", "#FFEAC13D": "Lime Drop", "#FFCFE838": "Lime Fizz", "#FFD2E3CC": "Lime Flip", "#FFE1ECD9": "Lime Glow", "#FFDCE1B8": "Lime Granita", "#FF8EAD2C": "Lime Green Variant", "#FFCDAEA5": "Lime Hawk Moth", "#FFD1DD86": "Lime Ice", "#FF8CA94A": "Lime It or Leave It", "#FFE3FF00": "Lime Jelly", "#FFE7E4D3": "Lime Juice", "#FFE5E896": "Lime Juice Green", "#FFBEFD73": "Lime Lightning", "#FFABD35D": "Lime Lizard", "#FFB4BD7A": "Lime Lollipop", "#FFE6ECD6": "Lime Meringue", "#FFDDFFAA": "Lime Mist", "#FF00FF0D": "Lime on Steroides", "#FF95C577": "Lime Parfait", "#FFC6C191": "Lime Peel", "#FFB6848C": "Lime Pink", "#FFCCCB2F": "Lime Pop", "#FFC1DB3B": "Lime Popsicle", "#FFC0D725": "Lime Punch", "#FFB5CE08": "Lime Rasp", "#FFAFB96A": "Lime Rickey", "#FFCDD78A": "Lime Sherbet", "#FF1DF914": "Lime Shot", "#FFF0FDED": "Lime Slice", "#FF7AF9AB": "Lime Soap", "#FFBEE5BE": "Lime Sorbet", "#FFC6CD7D": "Lime Sorbet Green", "#FFCFDB8D": "Lime Splash", "#FFDAE1CF": "Lime Spritz", "#FFBAD1B5": "Lime Taffy", "#FFEBE734": "Lime Time", "#FFD8D06B": "Lime Tree", "#FFC6D624": "Lime Twist", "#FFE9E4DF": "Lime White", "#FFD0FE1D": "Lime Yellow", "#FFDDFF00": "Lime Zest", "#FFE8E2D0": "Lime-Washed", "#FF5F9727": "Limeade Variant", "#FFCFC9C0": "Limed White", "#FFEEE96B": "Limelight", "#FFF8B109": "Lime\u00f1o Lim\u00f3n", "#FF76857B": "Limerick", "#FFE0D4B7": "Limescent", "#FFF2EABF": "Limesicle", "#FFDCD8C7": "Limestone", "#FFA5AF9D": "Limestone Green", "#FFD6D7DB": "Limestone Mauve", "#FFCFD9D4": "Limestone Path", "#FFF9F6DB": "Limestone Quarry", "#FFC5E0BD": "Limestone Slate", "#FFA7CCA4": "Limestoned", "#FF8E9A21": "Limetta", "#FFDBD5CB": "Limewash", "#FFBBB875": "Liminal Yellow", "#FFDAD79B": "Liminality", "#FFEAECB9": "Limited Lime", "#FFF0DDB8": "Limitless", "#FF4B4950": "Limo-Scene", "#FFF3E0DB": "Limoge Pink", "#FF243F6C": "Limoges", "#FF97B73A": "Limolicious", "#FFF7EB73": "Limon", "#FFCEBC55": "Lim\u00f3n Fresco", "#FF11DD66": "Limonana", "#FFD6C443": "Limone", "#FFBE7F51": "Limonite", "#FF4B4433": "Limonite Brown", "#FF535F62": "Limousine Grey Blue", "#FF3B3C3B": "Limousine Leather", "#FF90DCD9": "Limpet Shell", "#FFCDC2CA": "Limpid Light", "#FFFEFC7E": "Limuyi Yellow", "#FFE3E6DA": "Lincolnshire Sausage", "#FFC1B76A": "Linden Green", "#FF8E9985": "Linden Spear", "#FF229922": "Linderhof Garden", "#FF172808": "Lindworm Green", "#FFF8F3DA": "Line Dried", "#FFF5EDED": "Line Dried Sheets", "#FF4C3430": "Lineage", "#FF164975": "Linear", "#FFD9BCA9": "Linen Cloth", "#FF466163": "Linen Grey", "#FFEFEBE3": "Linen Ruffle", "#FFB4D6E5": "Lingerie Blue", "#FFE6DEF0": "Lingering Lilac", "#FF858381": "Lingering Storm", "#FFFF255C": "Lingonberry", "#FFA95657": "Lingonberry Punch", "#FFCE4458": "Lingonberry Red", "#FF778290": "Link", "#FF01A049": "Link Green", "#FF7F7E72": "Link Grey", "#FFC7CDD8": "Link Water Variant", "#FF3EAF76": "Link\u2019s Awakening", "#FFC2ABC4": "Linnea Blossom", "#FFC3BCB3": "Linnet", "#FFFFCCDD": "Linnet Egg Red", "#FF427C9D": "Linoleum Blue", "#FF3AA372": "Linoleum Green", "#FFB0A895": "Linseed", "#FFADB28D": "Lint", "#FFC19A62": "Lion Variant", "#FFF9CDA4": "Lion Cub", "#FFDD9933": "Lion King", "#FFDBD1B9": "Lion of Hadrian", "#FFEEAA66": "Lion of Menecrates", "#FF81522E": "Lion\u2019s Lair", "#FFE8AF49": "Lion\u2019s Mane", "#FF946B41": "Lion\u2019s Mane Blonde", "#FFF5DAB3": "Lion\u2019s Roar", "#FFBB9252": "Lion\u2019s Slumber", "#FFE0AF47": "Lioness", "#FFA3A5AA": "Lionfish", "#FFE03C28": "Lionfish Red", "#FFD5B60A": "Lionhead", "#FFCC2222": "Lionheart", "#FFDFCDC7": "Lip Gloss", "#FFD16A68": "Lippie", "#FFC95B83": "Lipstick Variant", "#FFD4696D": "Lipstick Illusion", "#FFBD7F8A": "Lipstick Pink", "#FFC0022F": "Lipstick Red", "#FF61394B": "Liqueur Red", "#FF55B7CE": "Liquid Blue", "#FF2D3796": "Liquid Denim", "#FFFDC675": "Liquid Gold", "#FF3B7A5F": "Liquid Green Stuff", "#FFCEDFE0": "Liquid Hydrogen", "#FFF77511": "Liquid Lava", "#FFCDF80C": "Liquid Lime", "#FF757A80": "Liquid Mercury", "#FFC8FF00": "Liquid Neon", "#FFF3F3F4": "Liquid Nitrogen", "#FF0A0502": "Liquorice", "#FF2A4041": "Liquorice Green", "#FF740900": "Liquorice Red", "#FF222200": "Liquorice Root", "#FFB53E3D": "Liquorice Stick", "#FFE2C28D": "Lira", "#FFFFFB00": "Lisbon Lemon", "#FFDD5511": "Liselotte Syrup", "#FFFFFED8": "Lit", "#FFD6E8E1": "Lit\u2019l Buoy Blew", "#FFE5DFCF": "Litewood", "#FF53626E": "Lithic Sand", "#FF9895C5": "Litmus", "#FFF8B9D4": "Little Baby Girl", "#FF604B42": "Little Bear", "#FFB6D3C5": "Little Beaux Blue", "#FF43484B": "Little Black Dress", "#FF8AC5BA": "Little Blue Box", "#FF3C4378": "Little Blue Heron", "#FFD37C99": "Little Bow Pink", "#FFC7D8DB": "Little Boy Blu", "#FF6495DA": "Little Boy Blue", "#FFE4E6EA": "Little Dipper", "#FFEBE0CE": "Little Dove", "#FFFF1414": "Little Ladybug", "#FFEAE6D7": "Little Lamb", "#FF6A9A8E": "Little League", "#FFE0D8DF": "Little Lilac", "#FF2D454A": "Little Mermaid", "#FFF4EFED": "Little Pinky", "#FFA6D1EB": "Little Pond", "#FFE6AAC1": "Little Princess", "#FFE50102": "Little Red Corvette", "#FFF8D0E8": "Little Smile", "#FFDAE9D6": "Little Sprout", "#FFF7C85F": "Little Sun Dress", "#FF73778F": "Little Theatre", "#FFE7CFE8": "Little Touch", "#FFA4A191": "Little Valley", "#FF87819B": "Live Jazz", "#FFCECEBD": "Liveable Green", "#FFFFDFB9": "Liveliness", "#FFE67C7A": "Lively Coral", "#FFB3AE87": "Lively Ivy", "#FFE1DD8E": "Lively Laugh", "#FF816F7A": "Lively Lavender", "#FFA18899": "Lively Light", "#FF9096B7": "Lively Lilac", "#FFBEB334": "Lively Lime", "#FFC8D8E5": "Lively Tune", "#FFF7F3E0": "Lively White", "#FFFFE9B1": "Lively Yellow", "#FF654A46": "Liver Variant", "#FF513E32": "Liver Brown", "#FFA8D275": "Livery Green", "#FF6688CC": "Livid", "#FF312A29": "Livid Brown Variant", "#FFB8E100": "Livid Lime", "#FFFF6A52": "Living Coral", "#FFC87163": "Living Large", "#FF37708C": "Living Stream", "#FFA39880": "Livingston", "#FFCBCBBB": "Livingstone", "#FF7B6943": "Lizard", "#FFCCCC33": "Lizard Belly", "#FFEDBB32": "Lizard Breath", "#FF795419": "Lizard Brown", "#FF7F6944": "Lizard Legs", "#FF917864": "Llama Wool", "#FFC35B99": "Llilacquered", "#FFDBD9C2": "Loafer Variant", "#FF443724": "Loam", "#FF9FC8B2": "Lobaria Lichen", "#FFA780B2": "Lobby Lilac", "#FF7498BE": "Lobelia", "#FFB3BBB7": "Loblolly Variant", "#FFBB240C": "Lobster", "#FFDB8981": "Lobster Bisque", "#FFA73836": "Lobster Brown", "#FFCC8811": "Lobster Butter Sauce", "#FFCB9E34": "Local Curry", "#FF609795": "Loch Blue", "#FFDFE5BF": "Loch Modan Moss", "#FF5F6DB0": "Loch Ness", "#FF489084": "Lochinvar Variant", "#FF316EA0": "Lochmara Variant", "#FFBE9AA2": "Lockhart", "#FF988171": "Locomotion", "#FFA2A580": "Locust Variant", "#FF7D6546": "Loden Blanket", "#FF788F74": "Loden Frost", "#FF747A59": "Loden Green", "#FF553A76": "Loden Purple", "#FFB68B13": "Loden Yellow", "#FFACA690": "Lodgepole Pines", "#FFDCCAB7": "Loft Light", "#FFCBCECD": "Loft Space", "#FFD9A9C6": "Lofty Delight", "#FF705A46": "Log Cabin Variant", "#FF9D9CB4": "Logan Variant", "#FF5F4B6F": "Loganberry", "#FFC4B7A5": "Loggia", "#FFE1EBDE": "Loggia Lights", "#FF577042": "Loire Valley", "#FFE7CD8B": "Lol Yellow", "#FFB9ACBB": "Lola Variant", "#FFBF2735": "Lolita", "#FFD91E3F": "Lollipop", "#FFFD978F": "Lolly", "#FFA6DAD0": "Lolly Ice", "#FFCDCCCF": "London Calling", "#FFBAB0AC": "London Coach", "#FF9D988C": "London Fog", "#FF666677": "London Grey", "#FFAE94AB": "London Hue Variant", "#FFD1DCE4": "London Lights", "#FF0055BB": "London Rain", "#FF7F878A": "London Road", "#FF7F909D": "London Square", "#FFA89F94": "London Stones", "#FF94C84C": "Lone Hunter", "#FF575A44": "Lone Pine", "#FF4A0A00": "Lonely Chocolate", "#FF947754": "Lonely Road", "#FF522426": "Lonestar Variant", "#FFFAEFDF": "Long Beach", "#FFA1759C": "Long Forgotten Purple", "#FF95D0FC": "Long Island Sound", "#FF68757E": "Long Lake", "#FFC97586": "Long Spring", "#FF425A5E": "Long Water", "#FF002277": "Long-Haul Flight", "#FFBD7A33": "Longan", "#FF442117": "Longan\u2019s Kernel", "#FFCECEAF": "Longbeard Grey", "#FF60513A": "Longboat", "#FF90B1A3": "Longfellow", "#FFD88E5F": "Longhorn", "#FF4B9F5D": "Longing for Nature", "#FFADD5E4": "Longitude", "#FFEBD84B": "Longlure Frogfish", "#FF77928A": "Longmeadow", "#FFE3D3B5": "Loofah", "#FFFEBF01": "Look at the Bright Side", "#FF888786": "Looking Glass", "#FF454151": "Loom of Fate", "#FF2E6676": "Loon Turquoise", "#FF11FFFF": "Looney Blue", "#FFCBC0B3": "Loophole", "#FF84613D": "Loose Leather", "#FFAE7C4F": "Loquat Brown", "#FFB76764": "Lord Baltimore", "#FF664488": "Lords of the Night", "#FF50702D": "Loren Forest", "#FF8EBCBD": "Lorian", "#FF658477": "Lorna", "#FF8D9CA7": "Lost at Sea", "#FF5F7388": "Lost Atlantis", "#FF998E7A": "Lost Canyon", "#FF74AF54": "Lost Golfer", "#FF002489": "Lost in Heaven", "#FFDEE8E1": "Lost in Istanbul", "#FF151632": "Lost in Sadness", "#FF03386A": "Lost in Space", "#FF014426": "Lost in the Woods", "#FF9FAFBD": "Lost in Time", "#FFC2EBD1": "Lost Lace", "#FFB5ADB5": "Lost Lake", "#FF8D828C": "Lost Lavender Somewhere", "#FFE5D7D4": "Lost Love", "#FF929591": "Lost Soul Grey", "#FF969389": "Lost Space", "#FF887A6E": "Lost Summit", "#FFFEFDFA": "Lotion", "#FFE5ECB7": "Lots of Bubbles", "#FF768371": "Lottery Winnings", "#FFE40046": "Lotti Red", "#FF8B504B": "Lotus Variant", "#FFF4F0DA": "Lotus Flower", "#FF93A79E": "Lotus Leaf", "#FFF2E9DC": "Lotus Petal", "#FFE7D7C2": "Lotus Pod", "#FFD1717B": "Lotus Red", "#FF64EB65": "Loud Green", "#FF88FF22": "Loud Lime", "#FFD92FB4": "Loudicious Pink", "#FF655856": "Louisiana Mud", "#FF4C3347": "Loulou Variant", "#FFBB2288": "Loulou\u2019s Purple", "#FF8BA97F": "Lounge Green", "#FF563E31": "Lounge Leather", "#FF5E336D": "Lounge Violet", "#FFDDC3A4": "Louvre", "#FFC87570": "Lovable", "#FF98B1A6": "Lovage Green", "#FFFFBEC8": "Love Affair", "#FFE5A5B1": "Love at First Sight", "#FFC6D0E3": "Love Crystal", "#FFEB94DA": "Love Dust", "#FFCD0106": "Love for All", "#FFFDD0D5": "Love Fumes", "#FFCD0D0D": "Love Goddess", "#FFE1B9C2": "Love in a Mist", "#FFCC1155": "Love Juice", "#FFE4658E": "Love Letter", "#FFC54673": "Love Lord", "#FFA06582": "Love Poem", "#FFCE145E": "Love Potion", "#FFBB55CC": "Love Priestess", "#FFFF496C": "Love Red", "#FFDD8877": "Love Sceptre", "#FFF8B4C4": "Love Spell", "#FFCE1D51": "Love Surge", "#FFEE0099": "Love Vessel", "#FFAEAEB7": "Love-Struck Chinchilla", "#FFF0C1C6": "Loveable", "#FFC76A77": "Lovebirds", "#FFC8ABA8": "Lovebug", "#FFEEBBEE": "Lovecloud", "#FFE6718D": "Loveland", "#FFA69A5C": "Loveliest Leaves", "#FFF7D6D8": "Lovelight", "#FFE2D0B3": "Lovely Bluff", "#FFF9D8E4": "Lovely Breeze", "#FFFFEEFF": "Lovely Euphoric Delight", "#FFF4DBDC": "Lovely Harmony", "#FFD6D2DD": "Lovely Lavender", "#FFE9DD22": "Lovely Lemonade", "#FFA7B0CC": "Lovely Lilac", "#FFDBCEAC": "Lovely Linen", "#FFE35F66": "Lovely Little Rosy", "#FFD8BFD4": "Lovely Pink", "#FFD0C6B5": "Lover\u2019s Hideaway", "#FF8F3B3D": "Lover\u2019s Kiss", "#FFF2DBDB": "Lover\u2019s Knot", "#FF957E68": "Lover\u2019s Leap", "#FFF4CED8": "Lover\u2019s Retreat", "#FFB48CA3": "Lover\u2019s Tryst", "#FF5A6141": "Lovers Pine", "#FF6A2E36": "Lovestruck", "#FF99D1A4": "Low Tide", "#FFE0EFE3": "Lower Green", "#FFDCDFEF": "Lower Lavender", "#FFE2D6D8": "Lower Lilac", "#FFE6F1DE": "Lower Lime", "#FFE0DCD8": "Lower Linen", "#FFEC9079": "Lox", "#FF01455E": "Loyal Blue", "#FF4E6175": "Loyalty", "#FF02C14D": "L\u01dc S\u00e8 Green", "#FF989746": "Luau Green", "#FF0B83B5": "Lucario Blue", "#FF7CAFE1": "Lucea", "#FF00FF33": "Lucent Lime", "#FFE4D0A5": "Lucent Yellow", "#FF77B87C": "Lucerne", "#FF7E8D9F": "Lucid Blue", "#FF632F92": "Lucid Dreams", "#FF1E4469": "Lucidity", "#FFA6BBB7": "Lucinda", "#FFBAA2CE": "Lucius Lilac", "#FF547839": "Luck of the Irish", "#FFAB9A1C": "Lucky Variant", "#FF93834B": "Lucky Bamboo", "#FF008400": "Lucky Clover", "#FF929A7D": "Lucky Day", "#FFD3C8BA": "Lucky Dog", "#FFF4ECD7": "Lucky Duck", "#FF238652": "Lucky Green", "#FF777777": "Lucky Grey", "#FFCC3322": "Lucky Lobster", "#FFFF7700": "Lucky Orange", "#FFBC6F37": "Lucky Penny", "#FF292D4F": "Lucky Point Variant", "#FFEFEAD8": "Lucky Potato", "#FF487A7B": "Lucky Shamrock", "#FFFAD9DA": "Lucky You", "#FF91B2BC": "Lucy Blue", "#FFBB8877": "Ludicrous Lemming", "#FFEBDAC0": "Ludlow Beige", "#FFF7A58B": "Lugganath Orange", "#FFC3D5E8": "Lull Wind", "#FFCBD4D4": "Lullaby", "#FFFFE4CD": "Lumber", "#FF9D4542": "Lumberjack", "#FFFFFEED": "Luminary", "#FFDEE799": "Luminary Green", "#FFA4DDE9": "Luminescent Blue", "#FF769C18": "Luminescent Green", "#FFB9FF66": "Luminescent Lime", "#FFCAFFFB": "Luminescent Sky", "#FFECBF55": "Luminous Apricot", "#FFBF8FE5": "Luminous Lavender", "#FFBBAEB9": "Luminous Light", "#FFCDDED5": "Luminous Mist", "#FFDC6C84": "Luminous Pink", "#FFFEE37F": "Luminous Yellow", "#FF4E5154": "Lump of Coal", "#FFD4D8CE": "Luna", "#FFC1E0C8": "Luna Green", "#FFC2CECA": "Luna Light", "#FFECEAE1": "Luna Moon", "#FF70C1C9": "Luna Moona", "#FF414D62": "Luna Pier", "#FF686B67": "Lunar Basalt", "#FF878786": "Lunar Base", "#FFCCCCDD": "Lunar Dust", "#FF415053": "Lunar Eclipse", "#FF868381": "Lunar Federation", "#FF4E5541": "Lunar Green Variant", "#FFDECE9E": "Lunar Lander", "#FFD2CFC1": "Lunar Landing", "#FF938673": "Lunar Launch Site", "#FF9B959C": "Lunar Light", "#FFFBF4D6": "Lunar Luxury", "#FF828287": "Lunar Outpost", "#FFCACED2": "Lunar Rays", "#FFC5C5C5": "Lunar Rock", "#FF707685": "Lunar Shadow", "#FFB6B9B6": "Lunar Surface", "#FF6F968B": "Lunar Tide", "#FFF7E7CD": "Lunaria", "#FFDDAA88": "Lunatic Lynx", "#FF76FDA8": "Lunatic Sky Dancer", "#FFF2CA95": "Lunch Box", "#FFD0C8C6": "Lunette", "#FFD1E0E9": "Lupin Grey", "#FFBE9CC1": "Lupine", "#FF6A96BA": "Lupine Blue", "#FFB4F319": "Lurid Lettuce", "#FFFF33EE": "Lurid Pink", "#FFFF4505": "Lurid Red", "#FF903D49": "Luscious", "#FF049945": "Luscious Green", "#FFD7C8A4": "Luscious Latte", "#FF696987": "Luscious Lavender", "#FFBBCC22": "Luscious Leek", "#FFEEBD6A": "Luscious Lemon", "#FF517933": "Luscious Lemongrass", "#FF91A673": "Luscious Lime", "#FFC5847C": "Luscious Lobster", "#FF605C71": "Luscious Purple", "#FFC5BDA0": "Lush", "#FF004466": "Lush Aqua", "#FFAFBB33": "Lush Bamboo", "#FF88AA77": "Lush Fields", "#FF008811": "Lush Garden", "#FF468D45": "Lush Grass", "#FFBBEE00": "Lush Green", "#FF7FF23E": "Lush Greenery", "#FF7A9461": "Lush Highlands", "#FFFCA81B": "Lush Honeycomb", "#FF6C765C": "Lush Hosta", "#FFE9F6E0": "Lush Life", "#FF9D7EB7": "Lush Lilac", "#FFA091B7": "Lush Mauve", "#FF007351": "Lush Meadow", "#FF2E7D32": "Lush Paradise", "#FF22BB22": "Lush Plains", "#FF7B8476": "Lush Sage", "#FF54A64D": "Lush Un\u2019goro Crater", "#FFE62020": "Lust", "#FFBB3388": "Lust Priestess", "#FFCC4499": "Lustful Wishes", "#FFBECE61": "Lustre Green", "#FFF4F1EC": "Lustre White", "#FF415A09": "Lustrian Undergrowth", "#FFE6DA78": "Lustrous Yellow", "#FF8D5EB7": "Lusty Lavender", "#FFD5174E": "Lusty Lips", "#FF00BB11": "Lusty Lizard", "#FFE26D28": "Lusty Orange", "#FFB1383D": "Lusty Red", "#FFEFAFA7": "Lusty Salmon", "#FFFFCCCC": "Lusty-Gallant", "#FF516582": "Luxe Blue", "#FFC3BAB0": "Luxe Grey", "#FFA8A3B1": "Luxe Lilac", "#FFBDE9E5": "Luxor Blue", "#FFAB8D3F": "Luxor Gold Variant", "#FFD4B75D": "Luxurious", "#FF365F8C": "Luxurious Blue", "#FF88EE22": "Luxurious Lime", "#FF863A42": "Luxurious Red", "#FF818EB1": "Luxury", "#FF384172": "Lviv Blue", "#FF0182CC": "Lvivian Rain", "#FFADCF43": "Lyceum", "#FFCD0C41": "Lychee", "#FFC3A5A5": "Lychee Berry", "#FFF7F2DA": "Lychee Pulp", "#FF9E9478": "Lye", "#FF7F6B5D": "Lye Tinted", "#FFE5C7B9": "Lyman Camellia", "#FF697D89": "Lynch Variant", "#FF604D47": "Lynx", "#FF2CB1EB": "Lynx Screen Blue", "#FFF7F7F7": "Lynx White", "#FF005E76": "Lyons Blue", "#FF0087AD": "Lyrebird", "#FF728791": "Lyric Blue", "#FF72696F": "Lythrum", "#FFB4023D": "M. Bison", "#FFF4F7FD": "M\u0101 White", "#FF001C3D": "Maastricht Blue", "#FFCBE8E8": "Mabel Variant", "#FFE4B070": "Mac N Cheese", "#FF880033": "Macabre", "#FFE1CCAF": "Macadamia", "#FFF7DFBA": "Macadamia Beige", "#FFBBA791": "Macadamia Brown", "#FFEEE3DD": "Macadamia Nut", "#FFF3D085": "Macaroni", "#FFB38B71": "Macaroon", "#FFFEE8D6": "Macaroon Cream", "#FFF75280": "Macaroon Rose", "#FF46C299": "Macau", "#FFFFBD24": "Macaw", "#FF9CAD3B": "Macaw Green", "#FF928168": "Macchiato", "#FFDD4400": "Macharius Solar Orange", "#FFA6A23F": "Machine Green", "#FF454545": "Machine Gun Metal", "#FFF1E782": "Machine Oil", "#FF9999AA": "Machinery", "#FF99BB33": "Machu Picchu Gardens", "#FFBFAE5B": "Mack Creek", "#FF007D82": "Macquarie", "#FF004577": "Macragge Blue", "#FFADA5A3": "Maculata Bark", "#FFEDA69E": "Mad About Maddie", "#FF94508E": "Mad About Magenta", "#FFA9324B": "Mad About You", "#FFF8A200": "Mad for Mango", "#FF9D8544": "Madagascar", "#FFD194A1": "Madagascar Pink", "#FF7CA7CB": "Madam Butterfly", "#FFB5ADB4": "Madame Mauve", "#FFB7E3A8": "Madang Variant", "#FF754C50": "Madder", "#FFB5B6CE": "Madder Blue", "#FF783738": "Madder Brown", "#FF80496E": "Madder Magenta", "#FFF1BEB0": "Madder Orange", "#FFB7282E": "Madder Red", "#FFEEBBCB": "Madder Rose", "#FF6B717A": "Made in the Shade", "#FF5B686F": "Made of Steel", "#FF8F4826": "Madeira Brown", "#FFF504C9": "Mademoiselle Pink", "#FFEED09D": "Madera", "#FF2D3C54": "Madison Variant", "#FF3D3E3E": "Madison Avenue", "#FF3F4250": "Madonna", "#FF71B5D1": "Madonna Blue", "#FFEEE6DB": "Madonna Lily", "#FF473E23": "Madras Variant", "#FF9AC3DA": "Madras Blue", "#FFECBF9F": "Madrid Beige", "#FF8F003A": "Madrile\u00f1o Maroon", "#FFAA44DD": "Magenta Affair", "#FFFF55A3": "Magenta Crayon", "#FFDE0170": "Magenta Elephant", "#FFED24ED": "Magenta Fizz", "#FFA44775": "Magenta Haze", "#FF513D3C": "Magenta Ink", "#FF983166": "Magenta Manicure", "#FFB4559B": "Magenta Memoir", "#FFCC338B": "Magenta Pink", "#FF762A54": "Magenta Purple", "#FF913977": "Magenta Red", "#FF62416D": "Magenta Red Lips", "#FFFA5FF7": "Magenta Stream", "#FFBB989F": "Magenta Twilight", "#FF6C5389": "Magenta Violet", "#FFD521B8": "Magentella", "#FFAA11AA": "Magentle", "#FFAA22BB": "Magentleman", "#FFBF3CFF": "Magento", "#FFDDEEE2": "Maggie\u2019s Magic", "#FF656B78": "Magic", "#FF44DD00": "Magic Blade", "#FF3E8BAA": "Magic Blue", "#FF9488BE": "Magic Carpet", "#FF817C85": "Magic Dust", "#FF1F75FF": "Magic Fountain", "#FF8E7282": "Magic Gem", "#FFC2A260": "Magic Lamp", "#FF7F4774": "Magic Magenta", "#FFA5887E": "Magic Malt", "#FFDE9851": "Magic Melon", "#FF3F3925": "Magic Metal", "#FF757CAF": "Magic Moment", "#FF717462": "Magic Mountain", "#FF3A3B5B": "Magic Night", "#FFFF4466": "Magic Potion", "#FF598556": "Magic Sage", "#FFE0D2BA": "Magic Sail", "#FFCCC9D7": "Magic Scent", "#FF544F66": "Magic Spell", "#FFC3D9E4": "Magic Wand", "#FF17034A": "Magic Whale", "#FFC1CEDA": "Magical", "#FFFF7A8F": "Magical Girl", "#FF22CC88": "Magical Malachite", "#FFBAA3A9": "Magical Mauve", "#FFE9E9D0": "Magical Melon", "#FF3D8ED0": "Magical Merlin", "#FFF0EEEB": "Magical Moonlight", "#FFEAEADB": "Magical Stardust", "#FF784467": "Magician\u2019s Cloak", "#FFFF4E01": "Magma", "#FFDD0066": "Magna Cum Laude", "#FF64BFDC": "Magnesia Bay", "#FFC1C2C3": "Magnesium", "#FF525054": "Magnet", "#FF697987": "Magnet Dapple", "#FFB2B5AF": "Magnetic", "#FF054C8A": "Magnetic Blue", "#FF2B6867": "Magnetic Green", "#FF838789": "Magnetic Grey", "#FF3FBBB2": "Magnetic Magic", "#FFAA1D8E": "Magneto\u2019s Magenta", "#FF7F556F": "Magnificence", "#FFEE22AA": "Magnificent Magenta", "#FFAE8D7B": "Magnitude", "#FFFFF9E4": "Magnolia Tint", "#FFF4E7CE": "Magnolia Blossom", "#FFF7EEE3": "Magnolia Petal", "#FFECB9B3": "Magnolia Pink", "#FFF6E6CB": "Magnolia Spray", "#FFF4F2E7": "Magnolia Spring", "#FFD8BFC8": "Magnolia White", "#FF003686": "Magnus Blue", "#FF69475A": "Magos", "#FF4C4B45": "Magpie", "#FF3F354F": "Maharaja", "#FF812308": "Mahogany Brown", "#FFAA5511": "Mahogany Finish", "#FFC39D90": "Mahogany Rose", "#FF5B4646": "Mahogany Spice", "#FF62788E": "Mahonia Berry Blue", "#FFA56531": "Mai Tai Variant", "#FFF5E9CA": "Maiden Hair", "#FFEFDCEB": "Maiden of the Mist", "#FFFF2FEB": "Maiden Pink", "#FF8AC7D4": "Maiden Voyage", "#FFF3D3BF": "Maiden\u2019s Blush", "#FF44764A": "Maidenhair Fern", "#FFD8BAA6": "Maiko", "#FFB79400": "Main Mast Gold", "#FFC2792B": "Maine Coon Orange", "#FFA95249": "Maine-Anjou Cattle", "#FFD9DFE2": "Mainsail", "#FF2A2922": "Maire", "#FFD7D8DC": "Mais Oui", "#FFDFD2BF": "Maison Blanche", "#FFBB9B7D": "Maison de Campagne", "#FFE5F0D9": "Maison Verte", "#FFF4D054": "Maize Variant", "#FFFBEC5E": "Maizena", "#FF5D4250": "Majestic", "#FF3F425C": "Majestic Blue", "#FFF3BC80": "Majestic Dune", "#FF443388": "Majestic Eggplant", "#FFAD9A84": "Majestic Elk", "#FF7D8878": "Majestic Evergreen", "#FF607535": "Majestic Jungle", "#FFEE4488": "Majestic Magenta", "#FF555570": "Majestic Magic", "#FF7C8091": "Majestic Mount", "#FF447788": "Majestic Mountain", "#FF8D576D": "Majestic Orchid", "#FF806173": "Majestic Plum", "#FF65608C": "Majestic Purple", "#FFF2E9A5": "Majestic Treasures", "#FF9D9AC4": "Majestic Violet", "#FF673E6E": "Majesty", "#FFFFAACC": "Majin B\u016b Pink", "#FF2D4B65": "Majolica Blue", "#FF976352": "Majolica Earthenware", "#FFAEB08F": "Majolica Green", "#FFA08990": "Majolica Mauve", "#FF289EC4": "Major Blue", "#FF61574E": "Major Brown", "#FFF246A7": "Major Magenta", "#FF001177": "Major Tom", "#FF4A9C95": "Majorca Blue", "#FF808337": "Majorca Green", "#FF337766": "Majorelle Gardens", "#FF695F50": "Makara Variant", "#FF335F8D": "Make-Up Blue", "#FF88BB55": "Makin It Rain", "#FF505555": "Mako Variant", "#FF6E2F2C": "Makore Veneer Red", "#FF688C43": "Makrut Lime", "#FFCFBEA9": "Malabar", "#FF0E4F4F": "Malachite Blue Turquoise", "#FF004E00": "Malachite Green", "#FFAB5871": "Malaga", "#FF6E7D6E": "Malarca", "#FFB8D1D0": "Malaysian Mist", "#FF00BBDD": "Maldives", "#FFD6CEC3": "Male", "#FF347699": "Male Betta", "#FFBB6688": "Malevolent Mauve", "#FF66B7E1": "Malibu Variant", "#FFC9C0B1": "Malibu Beige", "#FF00A9DA": "Malibu Blue", "#FFE7CFC2": "Malibu Coast", "#FFE7CEB5": "Malibu Dune", "#FFFDC8B3": "Malibu Peach", "#FFFFF2D9": "Malibu Sun", "#FF254855": "Mallard Variant", "#FF3F6378": "Mallard Blue", "#FF478865": "Mallard Green", "#FF91B9C2": "Mallard Lake", "#FFF8F2D8": "Mallard\u2019s Egg", "#FF3A4531": "Mallardish", "#FF517B95": "Mallorca Blue", "#FFF8EBDE": "Mallow Root", "#FFA7D7FF": "Malm\u00f6 FF", "#FFDDCFBC": "Malt", "#FFBBA87F": "Malt Shake", "#FFA59784": "Malta Variant", "#FFDBC8C0": "Malted", "#FFE8D9CE": "Malted Milk", "#FFBFD6C8": "Malted Mint", "#FF11DDAA": "Malted Mint Madness", "#FF551111": "Mama Africa", "#FF594F40": "Mama Racoon", "#FF005E8C": "Mamala Bay", "#FF766D7C": "Mamba Variant", "#FF77AD3B": "Mamba Green", "#FFFDD014": "Mamba Yellow", "#FFEBA180": "Mamey", "#FFB00B1E": "Mammary Red", "#FF3B6A7A": "Mammoth Mountain", "#FF995522": "Mammoth Wool", "#FF816045": "Man Cave", "#FF3C4C5D": "Man Friday", "#FFB09737": "Mana", "#FF94BBDA": "Manakin", "#FF65916D": "Manchester", "#FF504440": "Manchester Brown", "#FF992222": "Manchester Nights", "#FFB57B2E": "Mandalay Variant", "#FFA05F45": "Mandalay Road", "#FFEE9944": "Mandarin Essence", "#FFFF8800": "Mandarin Jelly", "#FFEC6A37": "Mandarin Orange", "#FFE84F3C": "Mandarin Red", "#FFF1903D": "Mandarin Rind", "#FFF6E7E1": "Mandarin Sugar", "#FFD8D4D3": "Mandarin Tusk", "#FF97691E": "Mandolin", "#FF8889A0": "Mandrake", "#FFC6C0A6": "Mandu Dumpling", "#FFCD525B": "Mandy Variant", "#FFF5B799": "Mandys Pink", "#FFF5B9D8": "Manga Pink", "#FFE781A6": "Mangala Pink", "#FF202F4B": "Manganese Black", "#FF9DBCD4": "M\u00e5ngata", "#FFFFA62B": "Mango Variant", "#FFBB8434": "Mango Brown", "#FFFBEDDA": "Mango Cheesecake", "#FFFFB769": "Mango Creamsicles", "#FF96FF00": "Mango Green", "#FFF7BD8D": "Mango Ice", "#FFFFBB4D": "Mango Latte", "#FFFEB81C": "Mango Loco", "#FFFD8C23": "Mango Madness", "#FFF7B74E": "Mango Margarita", "#FFD1A229": "Mango Mojito", "#FFFFD49D": "Mango Nectar", "#FFFF8B58": "Mango Orange", "#FFFFB066": "Mango Salsa", "#FF8E6C39": "Mango Squash", "#FF383E5D": "Mangosteen", "#FF3A2732": "Mangosteen Violet", "#FF757461": "Mangrove", "#FF607C3D": "Mangrove Leaf", "#FF292938": "Mangu Black", "#FFB2896C": "Mangy Moose", "#FFE2AF80": "Manhattan Variant", "#FF404457": "Manhattan Blue", "#FFCCCFCF": "Manhattan Mist", "#FF97908E": "Mani", "#FF004058": "Maniac Mansion", "#FF899888": "Manifest", "#FF8A9BC2": "Manifest Destiny", "#FFE7C9A9": "Manila", "#FFFF6E61": "Manila Sunset", "#FFFFE2A7": "Manila Tint", "#FFDAC9B8": "Manilla Rope", "#FF5B92A2": "Manitou Blue", "#FFCF7336": "Mann Orange", "#FFFAE2BE": "Manna", "#FFEEDFDD": "Mannequin", "#FFF6E5CE": "Mannequin Cream", "#FFC19763": "Mannered Gold", "#FF665D57": "Manor House", "#FFFFBC02": "Mantella Frog", "#FF957840": "Manticore Brown", "#FFDD7711": "Manticore Wing", "#FF96A793": "Mantle Variant", "#FFDCE2DF": "Mantra", "#FF881144": "Manually Pressed Grapes", "#FFAD900D": "Manure", "#FFD1C9BA": "Manuscript", "#FF827E71": "Manuscript Ink", "#FFE4DB55": "Manz Variant", "#FF9E8F6B": "Manzanilla Olive", "#FF643C37": "Manzanita", "#FFB88E72": "Maple", "#FFFAD0A1": "Maple Beige", "#FFA38E6F": "Maple Brown Sugar", "#FFF6D193": "Maple Elixir", "#FFA76944": "Maple Glaze", "#FFD17B41": "Maple Leaf", "#FFE3D1BB": "Maple Pecan", "#FFBF514E": "Maple Red", "#FFC1967C": "Maple Sugar", "#FFBB9351": "Maple Syrup", "#FFC88554": "Maple Syrup Brown", "#FFD4A882": "Maple Tan", "#FFB49161": "Maple View", "#FFFF2600": "Maraschino", "#FFF3E5CB": "Marble Dust", "#FF646255": "Marble Garden", "#FFDEE2C7": "Marble Grape", "#FF8F9F97": "Marble Green", "#FF85928F": "Marble Green-Grey", "#FFE2DCD7": "Marble Quarry", "#FFA9606E": "Marble Red", "#FFF2F0E6": "Marble White", "#FFE3DACF": "March Breeze", "#FFD4CC00": "March Green", "#FFFF750F": "March Hare Orange", "#FFD8CDC5": "March Ice", "#FF9A7276": "March Pink", "#FFD4C978": "March Tulip Green", "#FFBAB9B6": "March Wind", "#FFF1D48A": "March Yellow", "#FF2E5464": "Marea Baja", "#FFF9ECDA": "Marfil", "#FFF2D930": "Margarine", "#FFA9BC81": "Margarita", "#FF445956": "Mariana Trench", "#FFFCC006": "Marigold Tint", "#FFF5CC3D": "Marigold Dust", "#FFE7C3AC": "Marilyn Monroe", "#FFC9001E": "Marilyn MonRouge", "#FF5A88C8": "Marina", "#FFB1C8BF": "Marina Isle", "#FFFF0008": "Marinara Red", "#FF042E60": "Marine", "#FF01386A": "Marine Blue", "#FF40A48E": "Marine Green", "#FFA5B2AA": "Marine Grey", "#FF6384B8": "Marine Ink", "#FFA5B4B6": "Marine Layer", "#FF515E62": "Marine Magic", "#FF008383": "Marine Teal", "#FF33A3B3": "Marine Tinge", "#FF1F7073": "Marine Wonder", "#FF42639F": "Mariner Variant", "#FFE4000F": "Mario", "#FF380282": "Marionberry", "#FFBDCFEA": "Maritime", "#FF2D3145": "Maritime Blue", "#FF8B9BA4": "Maritime Hush", "#FF1E4581": "Maritime Outpost", "#FF69B8C0": "Maritime Soft Blue", "#FFE5E6E0": "Maritime White", "#FFBFCBA2": "Marjoram", "#FF00869A": "Marker Blue", "#FF9DAF00": "Marker Green", "#FFE3969B": "Marker Pink", "#FFFBB377": "Market Melon", "#FF515B87": "Marlin", "#FF41A1AA": "Marlin Green", "#FFD46F14": "Marmalade", "#FFC27545": "Marmalade Glaze", "#FFDF7F73": "Marmalade Magic", "#FF46221B": "Marmite", "#FF928475": "Marmot", "#FFC32249": "Maroon Flush Variant", "#FFBF3160": "Maroon Light", "#FF8D5455": "Maroon Rover", "#FF86CDAB": "Marooned", "#FFF5EAD6": "Marquee White", "#FFD2783A": "Marquis Orange", "#FFF3D49D": "Marquisette", "#FF91734C": "Marrakech Brown", "#FF01B2BD": "Marrakech Mile", "#FF783B3C": "Marrakesh Red", "#FFCACAA3": "Marrett Apple", "#FF6E4C4B": "Marron", "#FFA7735A": "Marron Canela", "#FF008C8C": "Marrs Green", "#FFAD6242": "Mars", "#FFC92A37": "Mars Red", "#FF96534C": "Marsala", "#FFB7BBBB": "Marseilles", "#FF5C5337": "Marsh", "#FF6B8781": "Marsh Creek", "#FFB6CA90": "Marsh Fern", "#FFD4C477": "Marsh Field", "#FFC6D8C7": "Marsh Fog", "#FF82763D": "Marsh Grass", "#FFFFEF17": "Marsh Marigold", "#FFE8E1A1": "Marsh Mist", "#FF5A653A": "Marsh Mix", "#FFC4A3BF": "Marsh Orchid", "#FF3E4355": "Marshal Blue", "#FF2B2E26": "Marshland Variant", "#FFF0EEE4": "Marshmallow", "#FFDBC2B4": "Marshmallow Cocoa", "#FFF3E0D6": "Marshmallow Cream", "#FFFAF3DE": "Marshmallow Fluff", "#FFF9DCE3": "Marshmallow Heart", "#FFEFD2D0": "Marshmallow Magic", "#FFE0CAAA": "Marshmallow Mist", "#FFF7E5E6": "Marshmallow Rose", "#FFF9EFE0": "Marshmallow Whip", "#FF8E712E": "Marshy Green", "#FFB8AEA2": "Marshy Habitat", "#FFFDF200": "Marsupilami", "#FFAEA132": "Martian", "#FF57958B": "Martian Cerulean", "#FFE5750F": "Martian Colony", "#FF136C51": "Martian Green", "#FFADEACE": "Martian Haze", "#FFC15A4B": "Martian Ironearth", "#FFC3E9D3": "Martian Moon", "#FFF4E5B7": "Martica", "#FF8E8E41": "Martina Olive", "#FFB7A8A3": "Martini Variant", "#FFCE8C8D": "Martini East", "#FF3C3748": "Martinique Variant", "#FF6A7FB4": "Marvellous", "#FFE1C6D6": "Marvellous Magic", "#FF006A77": "Mary Blue", "#FFD1B5CA": "Mary Poppins", "#FFD7B1B0": "Mary Rose", "#FF69913D": "Mary\u2019s Garden", "#FFA6D0EC": "Marzena Dream", "#FFEBC881": "Marzipan Variant", "#FFEEBABC": "Marzipan Pink", "#FFEBE5D8": "Marzipan White", "#FF57534B": "Masala Variant", "#FFEECCAA": "Masala Chai", "#FFECE6D4": "Mascarpone", "#FFAB878D": "Mask", "#FFC6B2BE": "Masked Mauve", "#FFD57C6B": "Masoho Red", "#FF3A4B61": "Master", "#FFE0CE80": "Master Gardener", "#FFDDCC88": "Master Key", "#FFFFB81B": "Master Nacho", "#FFE78303": "Master Round Yellow", "#FF00FFEE": "Master Sword Blue", "#FFA1A2AB": "Masterpiece", "#FF4D646C": "Masuhana Blue", "#FFFF48D0": "Mat Dazzle Rose", "#FF544859": "Mata Hari", "#FFCF6E66": "Matador\u2019s Cape", "#FFD63756": "Match Head", "#FFFFAA44": "Match Strike", "#FFD8D458": "Matcha", "#FF9FAF6C": "Matcha Mecha", "#FF99BB00": "Matcha Picchu", "#FFA0D404": "Matcha Powder", "#FF7BB18D": "Mate Tea", "#FF590A01": "Mathematical Rage", "#FF365C7D": "Matisse Variant", "#FF7E6884": "Matriarch", "#FF454D32": "Matsuba Green", "#FF151515": "Matt Black", "#FF2C6FBB": "Matt Blue", "#FFDD4433": "Matt Demon", "#FF39AD48": "Matt Green", "#FFDEC6D3": "Matt Lilac", "#FFB2B9A5": "Matt Sage", "#FFFFFFD4": "Matt White", "#FF884433": "Mattar Paneer", "#FF8FB0CE": "Matte Blue", "#FFA17F67": "Matte Brown", "#FFA06570": "Matte Carmine", "#FFB4A8A4": "Matte Grey", "#FFB5CBBD": "Matte Jade Green", "#FF998F7F": "Matte Olive", "#FF8A9381": "Matte Sage Green", "#FF524B4B": "Matterhorn Variant", "#FFE0FEFE": "Matterhorn Snow", "#FFC4AFB3": "Mature", "#FF9A463D": "Mature Cognac", "#FF5F3F54": "Mature Grape", "#FF38796C": "Maturity", "#FF988286": "Maud", "#FF21A5BE": "Maui", "#FF52A2B4": "Maui Blue", "#FFB66044": "Maui Mai Tai", "#FFEEF2F3": "Maui Mist", "#FFB08A7D": "Maui Poi", "#FFE3D2DB": "Mauve Aquarelle", "#FFB69289": "Mauve Blush", "#FF62595F": "Mauve Brown", "#FFE5D0CF": "Mauve Chalk", "#FFAC8C8C": "Mauve Day", "#FF545883": "Mauve Dusk", "#FFCBB8C0": "Mauve Finery", "#FFD18489": "Mauve Glow", "#FFBB4466": "Mauve It", "#FF908186": "Mauve Jazz", "#FFAA7982": "Mauve Madness", "#FFBF91B2": "Mauve Magic", "#FFA69F9A": "Mauve Melody", "#FFA46A95": "Mauve Memento", "#FFC49BD4": "Mauve Mist", "#FF7D716E": "Mauve Mole", "#FFE7CCCD": "Mauve Morn", "#FFD9D0CF": "Mauve Morning", "#FFA98CA1": "Mauve Musk", "#FFB59EAD": "Mauve Muslin", "#FF685C61": "Mauve Mystery", "#FFBB4477": "Mauve Mystique", "#FFC0ADA6": "Mauve Nymph", "#FFAB728D": "Mauve Orchid", "#FFD9C4D0": "Mauve Organdie", "#FFBEBBC0": "Mauve Pansy", "#FFBB7788": "Mauve Seductress", "#FFAF8F9C": "Mauve Shadows", "#FFC4BAB6": "Mauve Stone", "#FFE7E1E1": "Mauve Tinge", "#FFDEE3E4": "Mauve White", "#FF653C4A": "Mauve Wine", "#FFEADDE1": "Mauve Wisp", "#FF90686C": "Mauve-a-Lish", "#FF8A6D8B": "Mauveine", "#FFD6B3C0": "Mauvelous Tint", "#FF9D8888": "Mauverine", "#FFC4B2A9": "Mauvette", "#FFAE6A78": "Mauvewood", "#FFBB8899": "Mauvey Nude", "#FF8C8188": "Mauvey Pink", "#FF86666B": "Mauving Up", "#FFC8B1C0": "Maverick Variant", "#FFEFE9DD": "Mawmaw\u2019s Pearls", "#FF017478": "Maxi Teal", "#FF6B4A40": "Maximum Mocha", "#FFFF5B00": "Maximum Orange", "#FF92D599": "May Apple", "#FF53CBC4": "May Day", "#FFA19FC8": "May Mist", "#FFFAEAD0": "May Sun", "#FF98D2D9": "Maya Green", "#FF006B6C": "Mayan Blue", "#FF655046": "Mayan Chocolate", "#FFB68C37": "Mayan Gold", "#FF6C4A43": "Mayan Red", "#FF7D6950": "Mayan Ruins", "#FFCE9844": "Mayan Treasure", "#FFF6D48D": "Maybe Maui", "#FFE2D8CB": "Maybe Mushroom", "#FFEDDFC9": "Maybeck Muslin", "#FFE6F0DE": "Mayfair White", "#FFED93D7": "Mayflower Orchid", "#FF696841": "Mayfly", "#FFE1E6F0": "Mayon Mist", "#FFF6EED1": "Mayonnaise", "#FFBEE8D3": "Maypole", "#FF2A407E": "Mazarine Blue", "#FF5C5638": "Maze", "#FFB0907C": "Mazzone", "#FFBF5BB0": "Mazzy Star", "#FF8C6338": "McKenzie", "#FF33FF11": "McNuke", "#FFFFC878": "Mead", "#FF81B489": "Meadow", "#FF7AB2D4": "Meadow Blossom Blue", "#FFE2D4AD": "Meadow Dew", "#FF987184": "Meadow Flower", "#FFCCD1B2": "Meadow Glen", "#FFC1D6B1": "Meadow Grass", "#FF739957": "Meadow Green", "#FFC0D7CD": "Meadow Lane", "#FFDFE9DE": "Meadow Light", "#FFAC5D91": "Meadow Mauve", "#FFCCD8BA": "Meadow Mist", "#FFAEBEA6": "Meadow Morn", "#FFA8AFC7": "Meadow Phlox", "#FFC5ACD0": "Meadow Thistle", "#FF8D8168": "Meadow Trail", "#FF764F82": "Meadow Violet", "#FFF7DA90": "Meadow Yellow", "#FF60A0A3": "Meadowbrook", "#FF807A55": "Meadowland", "#FFE8D940": "Meadowlark", "#FF9DA28E": "Meadowood", "#FFD4E3E2": "Meadowsweet Mist", "#FFFF00AE": "Mean Girls Lipstick", "#FF8F8C79": "Meander", "#FFBEDBD8": "Meander Blue", "#FFF8EED3": "Meatbun", "#FF663311": "Meatloaf", "#FFC89134": "Mecca Gold", "#FFBD5745": "Mecca Orange", "#FF663F3F": "Mecca Red", "#FFBBDDDD": "Mech Suit", "#FF8D847F": "Mecha Grey", "#FFD0C4D3": "Mecha Kitty", "#FF848393": "Mecha Metal", "#FFDEDCE2": "Mechagodzilla", "#FF3D4B4D": "Mechanicus Standard Grey", "#FFA31713": "Mechrite Red", "#FFC3A679": "Medallion", "#FF696DB0": "Mediaeval", "#FF2E3858": "Mediaeval Blue", "#FF878573": "Mediaeval Cobblestone", "#FF007E6B": "Mediaeval Forest", "#FFAC7F48": "Mediaeval Gold", "#FFA79F5C": "Mediaeval Sulphur", "#FF8C7D88": "Mediaeval Wine", "#FFB8C4B8": "Median Green", "#FF95CCE4": "Medical Mask", "#FF104773": "Medici Blue", "#FFF3E9D7": "Medici Ivory", "#FF69556D": "Medicine Man", "#FF99A28C": "Medicine Wheel", "#FFA9AC9D": "Meditation", "#FFA7A987": "Meditation Time", "#FF96AAB0": "Meditative", "#FF39636A": "Mediterranea", "#FF60797D": "Mediterranean", "#FF1682B9": "Mediterranean Blue", "#FFA1CFEC": "Mediterranean Charm", "#FF007B84": "Mediterranean Cove", "#FFB3C3BD": "Mediterranean Dusk", "#FFE0E9D3": "Mediterranean Green", "#FFBCE9D6": "Mediterranean Mist", "#FF1E8CAB": "Mediterranean Sea", "#FFD0BABB": "Mediterranean Sunset", "#FF2999A2": "Mediterranean Swirl", "#FF444443": "Medium Black", "#FF7F5112": "Medium Brown", "#FFF3E5AC": "Medium Champagne Variant", "#FFEAEAAE": "Medium Goldenrod", "#FF418C53": "Medium Green", "#FF7D7F7C": "Medium Grey", "#FF4D6B53": "Medium Grey Green", "#FF3F4952": "Medium Gunship Grey", "#FFDDA0FD": "Medium Lavender Magenta", "#FFF36196": "Medium Pink", "#FF9E43A2": "Medium Purple Variant", "#FFAA4069": "Medium Ruby", "#FFFC2847": "Medium Scarlet", "#FF80DAEB": "Medium Sky Blue", "#FFDC9D8B": "Medium Terracotta", "#FF794431": "Medium Tuscan Red", "#FFD9603B": "Medium Vermilion", "#FFA68064": "Medium Wood", "#FFD5D7BF": "Medlar", "#FF998800": "Medusa Green", "#FF777711": "Medusa\u2019s Snakes", "#FFEE7700": "Mee-hua Sunset", "#FF869F98": "Meek Moss Green", "#FFAB7647": "Meerkat", "#FF739DAD": "Meetinghouse Blue", "#FF366FA6": "Mega Blue", "#FFADA295": "Mega Greige", "#FFD767AD": "Mega Magenta", "#FFDFCBCF": "Mega Metal Mecha", "#FFC6CCD4": "Mega Metal Phoenix", "#FF0EE8A3": "Mega Teal", "#FF4A40AD": "Megadrive Screen", "#FF3CBCFC": "Megaman", "#FF0058F8": "Megaman Helmet", "#FFA10000": "Megido Red", "#FFFE023C": "M\u00e9i G\u016bi H\u00f3ng Red", "#FFE03FD8": "M\u00e9i G\u016bi Z\u01d0 Purple", "#FF123120": "M\u00e9i H\u0113i Coal", "#FF007FB9": "Meissen Blue", "#FF12390D": "Melancholia", "#FFAA1133": "Melancholic Macaw", "#FF53778F": "Melancholic Sea", "#FFDD8899": "Melancholy", "#FFC4C476": "M\u00e9lange Green", "#FFE0B7C2": "Melanie Variant", "#FF282E27": "Melanite Black Green", "#FF01081C": "Melanophobia", "#FF342931": "Melanzane Variant", "#FF4C7C4B": "Melbourne", "#FF45C3AD": "Melbourne Cup", "#FFB5D96B": "Melissa", "#FFF0DDA2": "Mella Yella", "#FFC9E1E0": "Mellifluous Blue", "#FFD7E2DD": "Mellow Blue", "#FFD5AF92": "Mellow Buff", "#FFE0897E": "Mellow Coral", "#FFF8DE7F": "Mellow Dandelion", "#FFFFC65F": "Mellow Drama", "#FFF1DFE9": "Mellow Flower", "#FFFFCFAD": "Mellow Glow", "#FFD5D593": "Mellow Green", "#FFCC4400": "Mellow Mango", "#FFE8C2B4": "Mellow Marrow", "#FF9C6579": "Mellow Mauve", "#FFEE2266": "Mellow Melon", "#FFDDEDBD": "Mellow Mint", "#FFF2C996": "Mellow Moment", "#FFB1B7A1": "Mellow Mood", "#FFD9A6A1": "Mellow Rose", "#FFC3CDBA": "Mellow Spring", "#FFF5D39C": "Mellow Sun", "#FFE2A94F": "Mellowed Gold", "#FFB6B2A1": "Melmac Silver", "#FFEEE8E8": "Melodic White", "#FF7BB5AE": "Melodious", "#FFDD22AA": "Melodramatic Magenta", "#FFBECBD7": "Melody", "#FFA8ACD0": "Melody Purple", "#FFFF7855": "Melon Variant", "#FFF2BD85": "Melon Balls", "#FF74AC8D": "Melon Green", "#FFF4D9C8": "Melon Ice", "#FFF9C291": "Melon Melody", "#FFF2B88C": "Mel\u00f3n Meloso", "#FFE88092": "Melon Mist", "#FFF08F48": "Melon Orange", "#FFF1D4C4": "Melon Pink", "#FFF69268": "Melon Red", "#FFF47869": "Melon Refresher", "#FF332C22": "Melon Seed", "#FFF8B797": "Melon Sorbet", "#FFFFCD9D": "Melon Sprinkle", "#FFF8E7D4": "Melon Tint", "#FFAA6864": "Melon Twist", "#FFFDBCB4": "Melon Water", "#FFEE8170": "Melondrama", "#FFC3B9DD": "Melrose Variant", "#FFB4CBE3": "Melt Ice", "#FFE3CFAB": "Melt With You", "#FFFFCF53": "Melted Butter", "#FF785F4C": "Melted Chocolate", "#FFCE8544": "Melted Copper", "#FFDCB7A6": "Melted Ice Cream", "#FFFEE2CC": "Melted Marshmallow", "#FFF6E6C5": "Melted Wax", "#FFE9F9F5": "Melting Glacier", "#FFCAE1D9": "Melting Ice", "#FFECEBE4": "Melting Icicles", "#FFBBA2B6": "Melting Moment", "#FFCBE1E4": "Melting Point", "#FFDAE5E0": "Melting Snowman", "#FFEAAB7D": "Melting Sunset", "#FFD4B8BF": "Melting Violet", "#FF79C0CC": "Meltwater", "#FF95A99E": "Melville", "#FFECF0DA": "Memoir", "#FFCF8A8D": "Memorable Rose", "#FFE8DEDA": "Memories", "#FF9197A4": "Memorise", "#FFC7D1DB": "Memory Lane", "#FFA7C3CE": "Memorybook Blue", "#FF5E9D7B": "Memphis Green", "#FF5D5F73": "Men\u2019s Night", "#FF69788A": "Menacing Clouds", "#FF837A64": "Mendocino Hills", "#FFF3E8B8": "Menoth White Base", "#FFF0F1CE": "Menoth White Highlight", "#FFDEB4C5": "Mental Floss", "#FFEAEEDE": "Mental Note", "#FFC1F9A2": "Menthol", "#FF9CD2B4": "Menthol Green", "#FFA0E2D4": "Menthol Kiss", "#FF9A1115": "Mephiston Red", "#FFACA495": "Mercer Charcoal", "#FF0343DF": "Merchant Marine Blue", "#FFB6B0A9": "Mercurial", "#FFEBEBEB": "Mercury Variant", "#FFCEC9CA": "Mercury Glass", "#FF89C8C3": "Mercury Mist", "#FF650021": "Merguez", "#FF877272": "Meridian", "#FF7BC8B2": "Meridian Star", "#FFFF9408": "Merin\u2019s Fire", "#FFF3E4B3": "Meringue", "#FFC2A080": "Meringue Tips", "#FFE1DBD0": "Merino Variant", "#FFCFC1AE": "Merino Wool", "#FFAAE1CE": "Meristem", "#FF4F4E48": "Merlin Variant", "#FFEFE2D9": "Merlin\u2019s Beard", "#FF9F8898": "Merlin\u2019s Choice", "#FF89556E": "Merlin\u2019s Cloak", "#FF444C8F": "Merlin\u2019s Robe", "#FF730039": "Merlot Variant", "#FF712735": "Merlot Fields", "#FFB64055": "Merlot Magic", "#FF817A65": "Mermaid", "#FF004477": "Mermaid Blues", "#FFFCB39A": "Mermaid Cheeks", "#FF0088BB": "Mermaid Dreams", "#FF00776F": "Mermaid Harbour", "#FF22CCCC": "Mermaid Net", "#FF297F6D": "Mermaid Sea", "#FF25AE8E": "Mermaid Song", "#FFD9E6A6": "Mermaid Tears", "#FF1FAFB4": "Mermaid Treasure", "#FF8AA786": "Mermaid\u2019s Cove", "#FF59C8A5": "Mermaid\u2019s Kiss", "#FF337B35": "Mermaid\u2019s Tail", "#FF70859B": "Merman", "#FFCED3C1": "Merry Music", "#FFEAC8DA": "Merry Pink", "#FFA5D0AF": "Merrylyn", "#FFBCA177": "Mesa", "#FFF2EBD6": "Mesa Beige", "#FFC19180": "Mesa Peach", "#FFDDB1A8": "Mesa Pink", "#FF92555B": "Mesa Red", "#FF772F39": "Mesa Ridge", "#FFEEB5AF": "Mesa Rose", "#FFDCB49D": "Mesa Sand", "#FFEA8160": "Mesa Sunrise", "#FFA78B71": "Mesa Tan", "#FFCDBDAD": "Mesa Tumbleweed", "#FF7F976C": "Mesa Verde", "#FF9DB682": "Mesclun Green", "#FF1F0B1E": "Me\u0161ki Black", "#FF8E9074": "Mesmerise", "#FF804040": "Mesopotamian Dagger", "#FF997700": "Mesozoic Green", "#FFE3C8B1": "Mesquite Powder", "#FF37B8AF": "Message Green", "#FF7D745E": "Messenger Bag", "#FFFEE2BE": "Messinesi", "#FFBABFBC": "Metal", "#FF9C9C9B": "Metal Chi", "#FF2F2E1F": "Metal Construction Green", "#FF244343": "Metal Deluxe", "#FF838782": "Metal Flake", "#FF837E74": "Metal Fringe", "#FFA2C3DB": "Metal Gear", "#FF677986": "Metal Grey", "#FFB090B2": "Metal Petal", "#FFEEFF99": "Metal Spark", "#FF34373C": "Metalise", "#FFBCC3C7": "Metallic", "#FF4F738E": "Metallic Blue", "#FF554A3C": "Metallic Bronze Variant", "#FF6E3D34": "Metallic Copper Variant", "#FF24855B": "Metallic Green", "#FFCDCCBE": "Metallic Mist", "#FFA9959F": "Metamorphosis", "#FFBB7431": "Meteor Variant", "#FF5533FF": "Meteor Shower", "#FF4A3B6A": "Meteorite Variant", "#FF414756": "Meteorite Black Blue", "#FF596D69": "Meteorological", "#FFCC2233": "Methadone", "#FF0074A8": "Methyl Blue", "#FF828393": "Metro", "#FFF83800": "Metroid Red", "#FF61584F": "Metropolis", "#FF99A1A5": "Metropolis Mood", "#FF3E4244": "Metropolitan Silhouette", "#FFEEECE7": "Metropolitan White", "#FFF9E1D4": "Mette Penne", "#FFDF7163": "Mettwurst", "#FFE39B99": "Mexicali Rose", "#FFD16D76": "Mexican Chile", "#FF6F5A48": "Mexican Chocolate", "#FFFFB9B2": "Mexican Milk", "#FFC99387": "Mexican Moonlight", "#FFFCD8DC": "Mexican Mudkip", "#FF5A3C55": "Mexican Purple", "#FF9B3D3D": "Mexican Red Variant", "#FFC6452F": "Mexican Red Papaya", "#FFAF9781": "Mexican Sand", "#FFDAD4C5": "Mexican Sand Dollar", "#FFCECEC8": "Mexican Silver", "#FFD68339": "Mexican Spirit", "#FFEC9F76": "Mexican Standoff", "#FFDAD7AD": "M\u01d0 B\u00e1i Beige", "#FFE8AF45": "M\u00ec Ch\u00e9ng Honey", "#FFFD8973": "Miami Coral", "#FF17917F": "Miami Jade", "#FFF7931A": "Miami Marmalade", "#FFF7C3DA": "Miami Pink", "#FF907A6E": "Miami Spice", "#FFF5D5B8": "Miami Stucco", "#FF6EC2B0": "Miami Teal", "#FFEDE4D3": "Miami Weiss", "#FFCCCCEE": "Miami White", "#FF70828F": "Mica Creek", "#FFC5DACC": "Micaceous Green", "#FFCDC7BD": "Micaceous Light Grey", "#FFB7B4B1": "Michigan Avenue", "#FFBABCC0": "Microchip", "#FF556E6B": "Micropolis", "#FFEE172B": "MicroProse Red", "#FF2D5254": "Microwave Blue", "#FF276AB3": "Mid Blue", "#FF553333": "Mid Century", "#FFAE5C1B": "Mid Century Furniture", "#FF779781": "Mid Cypress", "#FF50A747": "Mid Green", "#FFCFF7EF": "Mid Spring Morning", "#FFC4915E": "Mid Tan", "#FF81B39C": "Mid-century Gem", "#FFF6B404": "Midas Finger Gold", "#FFE8BD45": "Midas Touch", "#FFF7D78A": "Midday", "#FFFFE1A3": "Midday Sun", "#FF7C6942": "Middle Ditch", "#FF210837": "Middle Red Purple", "#FFC85179": "Middle Safflower", "#FFA2948D": "Middle-Earth", "#FFC7AB84": "Middlestone", "#FFAA8ED6": "Middy\u2019s Purple", "#FF03012D": "Midnight Tint", "#FF534657": "Midnight Affair", "#FF853C69": "Midnight Aubergine", "#FF585960": "Midnight Badger", "#FF020035": "Midnight Blue Tint", "#FF979FBF": "Midnight Blush", "#FF706048": "Midnight Brown", "#FF3C574E": "Midnight Clover", "#FF112473": "Midnight Crest", "#FF002233": "Midnight Dreams", "#FF0C121B": "Midnight Edition", "#FF2B3458": "Midnight Escapade", "#FF403C40": "Midnight Escape", "#FF21263A": "Midnight Express", "#FF685D49": "Midnight Forest", "#FF637057": "Midnight Garden", "#FF666A6D": "Midnight Grey", "#FF3E505F": "Midnight Haze", "#FF3B484F": "Midnight Hour", "#FF4E5A59": "Midnight in NY", "#FFDD8866": "Midnight in Saigon", "#FF435964": "Midnight in the Tropics", "#FF000088": "Midnight in Tokyo", "#FF32496F": "Midnight Interlude", "#FF484D61": "Midnight Iris", "#FF0B0119": "Midnight Jam", "#FF46474A": "Midnight Magic", "#FF2C2E47": "Midnight Masquerade", "#FF002266": "Midnight Melancholia", "#FF880044": "Midnight Merlot", "#FF001F3F": "Midnight Mirage", "#FF1C0F33": "Midnight Monarch", "#FF3D5267": "Midnight Mosaic", "#FF242E28": "Midnight Moss Variant", "#FF364251": "Midnight Navy", "#FF0F2D4D": "Midnight Ocean", "#FF0B0C14": "Midnight Oil", "#FF5F6C74": "Midnight Pearl", "#FF372D52": "Midnight Pie", "#FF17240B": "Midnight Pines", "#FF280137": "Midnight Purple", "#FF565B8D": "Midnight Sea", "#FF41434E": "Midnight Serenade", "#FF566373": "Midnight Shadow", "#FF546473": "Midnight Show", "#FF424753": "Midnight Sky", "#FF243A5E": "Midnight Sleigh", "#FF225374": "Midnight Sonata", "#FF555B53": "Midnight Spruce", "#FF4E5A6D": "Midnight Sun", "#FF476062": "Midnight Teal", "#FF2A2243": "Midnight Velvet", "#FF6A75AD": "Midnight Violet", "#FF2A603B": "Midori", "#FF3EB370": "Midori Green", "#FFF6D9A9": "Midsummer", "#FF88CC44": "Midsummer Field", "#FFEAB034": "Midsummer Gold", "#FF0011EE": "Midsummer Nights", "#FFB1A4B4": "Midsummer Twilight", "#FFB4D0D9": "Midsummer\u2019s Dream", "#FFB5A18A": "Midtown", "#FFDD1100": "Midwinter Fire", "#FFA5D4DC": "Midwinter Mist", "#FF8F7F85": "Mighty Mauve", "#FF000133": "Mighty Midnight", "#FF283482": "Migol Blue", "#FF3F3623": "Mikado Variant", "#FFA7A62D": "Mikado Green", "#FFF08300": "Mikan Orange", "#FF11EE55": "Mike Wazowski Green", "#FFEEE1DC": "Milady", "#FFF6F493": "Milan Variant", "#FFC1A181": "Milano", "#FF9E3332": "Milano Red Variant", "#FFCBD5DB": "Mild Blue", "#FF8EBBAC": "Mild Evergreen", "#FF789885": "Mild Green", "#FFF27362": "Mild Heart Attack", "#FF87F8A3": "Mild Menthol", "#FFDCE6E3": "Mild Mint", "#FFF3BB93": "Mild Orange", "#FF667960": "Mildura", "#FF829BA0": "Miles", "#FF7F848A": "Milestone", "#FF229955": "Militant Vegan", "#FF667C3E": "Military Green", "#FF706043": "Military Olive", "#FFFDFFF5": "Milk", "#FFE9E1DF": "Milk and Cookies", "#FFF7E4C2": "Milk and Honey", "#FFDCE3E7": "Milk Blue", "#FF8F7265": "Milk Brownie Dough", "#FF7F4E1E": "Milk Chocolate", "#FF966F5D": "Milk Coffee Brown", "#FFF6FFE8": "Milk Foam", "#FFFFEECC": "Milk Froth", "#FFFAF7F0": "Milk Jug", "#FFFAF3E6": "Milk Moustache", "#FFEFE9D9": "Milk Paint", "#FFFFF4D3": "Milk Punch Variant", "#FFF5DEAE": "Milk Quartz", "#FFF5EDE2": "Milk Star White", "#FF9E9B88": "Milk Thistle", "#FFD1B39C": "Milk Toast", "#FFDCD9CD": "Milk White", "#FFF0CDD2": "Milkshake Pink", "#FFE3E8D9": "Milkweed", "#FF95987E": "Milkweed Pod", "#FF916981": "Milkwort Red", "#FFE2DCD4": "Milky", "#FF038487": "Milky Aquamarine", "#FF72A8BA": "Milky Blue", "#FFC6D4C9": "Milky Green", "#FFAEA3D0": "Milky Lavender", "#FFF9D9A0": "Milky Maize", "#FFC3B1AF": "Milky Skies", "#FF6BB3DB": "Milky Waves", "#FFE8F4F7": "Milky Way", "#FFFAEFD5": "Milky Way Galaxy", "#FFF8DD74": "Milky Yellow", "#FF876E59": "Mill Creek", "#FF595648": "Millbrook Variant", "#FFEFC87D": "Mille-Feuille", "#FF939796": "Millennial Greige", "#FF7B7B7D": "Millennial Grey", "#FFF6C8C1": "Millennial Pink", "#FF8C9595": "Millennium Silver", "#FF999999": "Million Grey", "#FFB6843C": "Millionaire", "#FFB9D4DE": "Millstream", "#FF99BD91": "Milly Green", "#FF689663": "Milpa", "#FFB4B498": "Milton", "#FFBB7722": "Milvus Milvus Orange", "#FF2269CA": "Mimesia Blue", "#FFEE8811": "Mimolette Orange", "#FFF5E9D5": "Mimosa Variant", "#FFDFC633": "Mimosa Yellow", "#FFBDB387": "Minced Ginger", "#FFB66A3C": "Mincemeat", "#FFDAEA6F": "Mindaro Variant", "#FFC8AC82": "Mindful", "#FFBDB5AD": "Mindful Grey", "#FF8E8583": "Mine Rock", "#FF373E41": "Mine Shaft Variant", "#FF6C6B65": "Mined Coal", "#FFD3CEC5": "Miner\u2019s Dust", "#FFD7D1C5": "Mineral", "#FFD8C49F": "Mineral Beige", "#FF6D9192": "Mineral Blue", "#FF4D3F33": "Mineral Brown", "#FFABB0AC": "Mineral Deposit", "#FFFCE8CE": "Mineral Glow", "#FF506355": "Mineral Green Variant", "#FFB2B6AC": "Mineral Grey", "#FFB35457": "Mineral Red", "#FFEDF2EC": "Mineral Spring", "#FFB18B32": "Mineral Umber", "#FFDFEBD6": "Mineral Water", "#FFDCE5D9": "Mineral White", "#FFD19A3B": "Mineral Yellow", "#FFB5DEDA": "Minerva", "#FFC72616": "Minestrone", "#FF8F1CAE": "Minesweeper\u2019s Purple", "#FFFA0103": "Minesweeper\u2019s Red", "#FF407577": "Ming Variant", "#FF41B57E": "Ming Green", "#FF8AADCF": "Mini Bay", "#FF96D7DB": "Mini Blue", "#FFFBF6DE": "Mini Cake", "#FF9FC5AA": "Mini Green", "#FFE5BEBA": "Miniature Posey", "#FFD3DFEA": "Minified Ballerina Blue", "#FFB3DBEA": "Minified Blue", "#FFF2DDE1": "Minified Blush", "#FFDED9DB": "Minified Cinnamon", "#FFDDE8E0": "Minified Green", "#FFC1E3E9": "Minified Jade", "#FFEBF5DE": "Minified Lime", "#FFE6DFE8": "Minified Magenta", "#FFDDF3E5": "Minified Malachite", "#FFE5DBDA": "Minified Maroon", "#FFE0DCE4": "Minified Mauve", "#FFE4EBDC": "Minified Mint", "#FFE3E8DB": "Minified Moss", "#FFE9E6D4": "Minified Mustard", "#FFE1DEE7": "Minified Purple", "#FFF4EBD4": "Minified Yellow", "#FFF3EECD": "Minimal", "#FF948D99": "Minimal Grey", "#FFF2CFE0": "Minimal Rose", "#FFCABEAD": "Minimalist", "#FFE9ECE5": "Minimalistic", "#FFE8D3BA": "Minimum Beige", "#FFFECE4E": "Minion Yellow", "#FFF3DD51": "Minions", "#FF8A7561": "Mink", "#FF67594C": "Mink Brown", "#FFD7CFC6": "Mink Frost", "#FFC5B29F": "Mink Haze", "#FF7F7674": "Mink Suede", "#FF9B9FB5": "Minnesota April", "#FFB7DFE8": "Minor Blue", "#FF734B42": "Minotaur Red", "#FF882211": "Minotaurus Brown", "#FF3E3267": "Minsk Variant", "#FF118800": "Minstrel of the Woods", "#FFC89697": "Minstrel Rose", "#FF7EFFBA": "Mint Bliss", "#FFD7C2CE": "Mint Blossom Rose", "#FFBCE0DF": "Mint Blue", "#FF7DB6A8": "Mint Bonbon Green", "#FFE6FDF1": "Mint Chiffon", "#FFCFEBEA": "Mint Chip", "#FFA9CEAA": "Mint Circle", "#FFB8E2B0": "Mint Cocktail Green", "#FFCCFFEE": "Mint Coffee", "#FF6CBBA0": "Mint Cold Green", "#FFDFFBF3": "Mint Condition", "#FFDFEADB": "Mint Emulsion", "#FFE6F3E7": "Mint Fizz", "#FFDCF4E6": "Mint Flash", "#FFD0EBC8": "Mint Frapp\u00e9", "#FFC1BFA5": "Mint Frost", "#FFB1D9C4": "Mint Gala", "#FFC8F3CD": "Mint Gloss", "#FFE2F0E0": "Mint Grasshopper", "#FF487D4A": "Mint Green Tint", "#FFBDE8D8": "Mint Ice", "#FF98CDB5": "Mint Ice Cream", "#FFC9CAA1": "Mint Ice Green", "#FFC5DCC6": "Mint Intermezzo", "#FF45CEA2": "Mint Jelly", "#FFDEF0A3": "Mint Julep Variant", "#FF00CFB0": "Mint Leaf", "#FF6A7D4E": "Mint Leaves", "#FF7DD7C0": "Mint Majesty", "#FFB9E0D3": "Mint Mist", "#FF00DDCC": "Mint Morning", "#FFB4CCBD": "Mint Mousse", "#FFBBE6BB": "Mint Parfait", "#FFDAEED3": "Mint Shake", "#FFC5E6D1": "Mint Smoothie", "#FFCBD5B1": "Mint Soap", "#FF95B090": "Mint Stone", "#FFAFEEE1": "Mint Tea", "#FF98FF97": "Mint Variant", "#FF99EEAA": "Mint Tonic", "#FFC6EADD": "Mint Tulip Variant", "#FF98CBBA": "Mint Twist", "#FFDCE5D8": "Mint Wafer", "#FFC9E5DA": "Mint Whisper", "#FFCCFFDD": "Mint Zest", "#FFB6E9C8": "Mint-o-licious", "#FF78BFB2": "Mintage", "#FFAFFFD5": "Mintastic", "#FFE0EAD8": "Minted", "#FF26A6BE": "Minted Blue", "#FFB32651": "Minted Blueberry Lemonade", "#FF6EC9A3": "Minted Elegance", "#FFD8F3EB": "Minted Ice", "#FFC1C6A8": "Minted Lemon", "#FFABF4D2": "Mintie", "#FF7CBBAE": "Mintnight", "#FF80D9CC": "Mintos", "#FFD2E2D5": "Minty Delight", "#FFD2F2E7": "Minty Fresh", "#FFDBE8CF": "Minty Frosting", "#FF0BF77D": "Minty Green", "#FF00FFBB": "Minty Paradise", "#FFA5B6CF": "Minuet", "#FF777BA9": "Minuet Lilac", "#FFB38081": "Minuet Rose", "#FFE8E6E7": "Minuet White", "#FFD47791": "Minuette", "#FFF2E4F5": "Minute Mauve", "#FF886793": "Mirabella", "#FFF3BE67": "Mirabelle Yellow", "#FF898696": "Miracle", "#FF255BA2": "Miracle at Wrigley", "#FF799292": "Miracle Bay", "#FF617BA6": "Miracle Elixir", "#FFBCDCCD": "Mirador", "#FF373F43": "Mirage Variant", "#FF4F938F": "Mirage Lake", "#FF7B1C6E": "Mirage of Violets", "#FF614251": "Miranda\u2019s Spike", "#FFD6D4D7": "Mirror Ball", "#FF7AA8CB": "Mirror Lake", "#FFA8B0B2": "Mirror Mirror", "#FF8E876E": "Mirrored Willow", "#FFC2AFBA": "Mischief", "#FF954738": "Mischief Maker", "#FFB7BAB9": "Mischief Mouse", "#FFDFF2DD": "Mischievous", "#FFA5A9B2": "Mischka Variant", "#FFD4BA9E": "Mish Mosh", "#FFEFF0C0": "Missed", "#FF6F5D57": "Missing Link", "#FF9BA9AB": "Mission Bay Blue", "#FF775C47": "Mission Brown", "#FF818387": "Mission Control", "#FFF3D1B3": "Mission Courtyard", "#FFB78D61": "Mission Gold", "#FFB29C7F": "Mission Hills", "#FF456252": "Mission Jewel", "#FFDAC5B6": "Mission Stone", "#FFDAC6A8": "Mission Tan", "#FF874C3E": "Mission Tile", "#FF857A64": "Mission Trail", "#FFE2D8C2": "Mission White", "#FF9E5566": "Mission Wildflower", "#FF99886F": "Mississippi Mud", "#FF3B638C": "Mississippi River", "#FFA6A19B": "Missouri Mud", "#FFE4EBE7": "Mist Spirit", "#FFF8EED6": "Mist Yellow", "#FFD7E7E6": "Misted Aqua", "#FFA2B7CF": "Misted Eve", "#FFE1ECD1": "Misted Fern", "#FFDAB965": "Misted Yellow", "#FF8AA282": "Mistletoe", "#FF98B489": "Mistletoe Kiss", "#FF5A8065": "Mistletoe Whisper", "#FFB8BFCC": "Mistral", "#FFCDD2D2": "Misty", "#FFC6DCC7": "Misty Afternoon", "#FFBCDBDB": "Misty Aqua", "#FFF1EEDF": "Misty Beach Cattle", "#FFD2D59B": "Misty Bead", "#FFB4C4C3": "Misty Blue", "#FFDDC9C6": "Misty Blush", "#FFD5D9D3": "Misty Coast", "#FF83BBC1": "Misty Cold Sea", "#FFE4E5E0": "Misty Dawn", "#FFADC2BE": "Misty Dreams", "#FFCDE7DB": "Misty Glen", "#FF65434D": "Misty Grape", "#FF65769A": "Misty Harbour", "#FFCEC9C3": "Misty Haze", "#FFDCE5CC": "Misty Hillside", "#FFC5E4DC": "Misty Isle", "#FFACD0BB": "Misty Jade", "#FFC2D5C4": "Misty Lake", "#FFDBD9E1": "Misty Lavender", "#FFDFFAE1": "Misty Lawn", "#FFB9B1C2": "Misty Lilac", "#FFE4BDA8": "Misty Loch", "#FFD3E1D3": "Misty Marsh", "#FFBEC0B0": "Misty Meadow", "#FFEAC3D2": "Misty Memories", "#FFCBCDBE": "Misty Memory", "#FFDBC6B9": "Misty Mink", "#FFDEECDA": "Misty Mint", "#FFE5E0CC": "Misty Moonstone", "#FF718981": "Misty Moor", "#FFE7E1E3": "Misty Morn", "#FFB2C8BD": "Misty Morning", "#FFBAC1CC": "Misty Morning Dew", "#FFC0D0E6": "Misty Mountains", "#FFF7EBD1": "Misty Mustard", "#FFB5C8C9": "Misty Surf", "#FFBDC389": "Misty Valley", "#FFDBD7E4": "Misty Violet", "#FF0D789F": "Mitchell Blue", "#FF878787": "Mithril", "#FFBBBBC1": "Mithril Silver", "#FFCCCCBA": "Mix or Match", "#FF96819A": "Mixed Berries", "#FF6A4652": "Mixed Berry Jam", "#FFF9BAB2": "Mixed Fruit", "#FF719166": "Mixed Veggies", "#FF6E8659": "Mixedwood Leaf", "#FFE4030F": "Miyamoto Red", "#FF6FEA3E": "Miyazaki Verdant", "#FF70C1E0": "Mizu", "#FFA7DBED": "Mizu Cyan", "#FF749F8D": "Mizuasagi Green", "#FF1A3F2C": "Mizukaze Green", "#FF3E6A6B": "Moat", "#FF605A67": "Mobster Variant", "#FFDDE8ED": "Moby Dick", "#FFFBEBD6": "Moccasin", "#FF9D7651": "Mocha Variant", "#FF8D8171": "Mocha Accent", "#FF847569": "Mocha Bean", "#FF9A6340": "Mocha Bisque", "#FF6B565E": "Mocha Brown", "#FFBEAF93": "Mocha Cake", "#FFF1D96E": "Mocha Dandelion", "#FF8E664E": "Mocha Delight", "#FFBBA28E": "Mocha Foam", "#FF773322": "Mocha Glow", "#FFDFD2CA": "Mocha Ice", "#FF82715F": "Mocha Latte", "#FF8B6B58": "Mocha Madness", "#FF88796D": "Mocha Magic", "#FF996E5B": "Mocha Mousse", "#FF926F53": "Mocha Syrup", "#FFAC9680": "Mocha Tan", "#FF918278": "Mocha Wisp", "#FF945200": "Mochaccino", "#FF8EFA00": "Mochito", "#FFFF9863": "Mock Orange", "#FFD8583C": "Mod Orange", "#FF31A6D1": "Modal", "#FF40A6AC": "Modal Blue", "#FF96711F": "Mode Beige Variant", "#FF484F49": "Model T", "#FFE9DECF": "Moderate White", "#FFAD9167": "Modern Avocado", "#FFBAD1E9": "Modern Blue", "#FFD5CEC2": "Modern Grey", "#FFBEA27D": "Modern History", "#FFF5ECDC": "Modern Ivory", "#FFA8AAB3": "Modern Lavender", "#FF88A395": "Modern Mint", "#FF9D8376": "Modern Mocha", "#FFE0DEB2": "Modern Zen", "#FF745B49": "Moderne Class", "#FF838492": "Modest Mauve", "#FFC7C0BC": "Modest Silver", "#FFE9E4EF": "Modest Violet", "#FFE6DDD4": "Modest White", "#FFEEA59D": "Modestly Peach", "#FFD4C7D9": "Modesty", "#FFC3B68B": "Modish Moss", "#FFF19172": "Moegi Green", "#FF553311": "Moelleux au Chocolat", "#FFC8A692": "Moenkopi Soil", "#FFDDCC00": "Mogwa-Cheong Yellow", "#FFBFA59E": "Mohair Mauve", "#FFA78594": "Mohair Pink", "#FF97B2B7": "Mohair Soft Blue Grey", "#FFA79B7E": "Mohalla", "#FFBEADB0": "Moire", "#FF665D63": "Moire Satin", "#FFDBDB70": "Moist Gold", "#FFE0E7DD": "Moist Silver", "#FF004422": "Moisty Mire", "#FFBEA684": "Mojave Desert", "#FFB99178": "Mojave Dusk", "#FFBF9C65": "Mojave Gold", "#FFAA6A53": "Mojave Sunset", "#FFE4F3E0": "Mojito", "#FF97463C": "Mojo Variant", "#FF574A47": "Molasses", "#FF8B714B": "Molasses Cookie", "#FF392D2B": "Mole", "#FF938F8A": "Mole Grey", "#FFB0A196": "Moleskin", "#FFE6BCA0": "Mollusca", "#FFE3EFE3": "Molly Green", "#FF4D8B72": "Molly Robins", "#FFC69C04": "Molten Bronze", "#FFBB7A39": "Molten Caramel", "#FFE8C690": "Molten Gold", "#FFE1EDE6": "Molten Ice", "#FFB5332E": "Molten Lava", "#FF686A69": "Molten Lead", "#FFEAB781": "Mom\u2019s Apple Pie", "#FFFFD4BB": "Mom\u2019s Love", "#FFF5C553": "Mom\u2019s Pancake", "#FF8FC1E5": "Moment of Grace", "#FF57493D": "Momentous Occasion", "#FF746F5C": "Momentum", "#FFF47983": "Momo Peach", "#FF542D24": "Momoshio Brown", "#FFC45971": "Mon Cher Ami", "#FFFF9889": "Mona Lisa Tint", "#FFABD4E6": "Monaco", "#FF274376": "Monaco Blue", "#FF6B252C": "Monarch Variant", "#FFB7813C": "Monarch Gold", "#FFBF764C": "Monarch Migration", "#FFEFA06B": "Monarch Orange", "#FF543941": "Monarch Velvet", "#FFFF8D25": "Monarch Wing", "#FF8CB293": "Monarch\u2019s Cocoon", "#FF4B62D2": "Monarchist", "#FF9093AD": "Monarchy", "#FF41363A": "Monastery Mantle", "#FFABA9D2": "Monastic", "#FFB78999": "Monastir", "#FF9BB9AE": "Moncur", "#FF554D42": "Mondo Variant", "#FF0F478C": "Mondrian Blue", "#FFC3CFDC": "Monet", "#FFCDD7E6": "Monet Lily", "#FFC1ACC3": "Monet Magic", "#FFEEF0D1": "Monet Moonrise", "#FF92DBC4": "Monet Vert", "#FFDDE0EA": "Monet\u2019s Lavender", "#FF7B9A6D": "Money", "#FFAABE49": "Money Banks", "#FFC9937A": "Money Tree", "#FF777700": "Mongolian Plateau", "#FFA58B6F": "Mongoose Variant", "#FF6E6355": "Monk\u2019s Cloth", "#FF553B39": "Monkey Island", "#FFBF6414": "Monkey King", "#FF63584C": "Monkey Madness", "#FFAC9894": "Monkeypod", "#FF704822": "Monks Robe", "#FF595747": "Monogram", "#FFA1BCD8": "Monologue", "#FFB8BCBB": "Monorail Silver", "#FFDEC1B8": "Monroe Kiss", "#FF7A7679": "Monsoon Variant", "#FF839AB0": "Monsoon Season", "#FF2F8351": "Monster Monstera", "#FF5F674B": "Monstera", "#FF75BF0A": "Monstera Deliciosa", "#FF22CC11": "Monstrous Green", "#FF9EB6D8": "Mont Blanc", "#FFF2E7E7": "Mont Blanc Peak", "#FF8190A4": "Montage", "#FF393B3C": "Montana", "#FF76627C": "Montana Grape", "#FF6AB0B9": "Montana Sky", "#FFE2AC6E": "Montana Wheat Field", "#FFBBAD9E": "Montauk Sands", "#FF7AC5B4": "Monte Carlo Variant", "#FFB6A180": "Montecito", "#FF3FBABD": "Montego Bay", "#FF946E5C": "Monterey Brown", "#FF7D4235": "Monterey Chestnut", "#FFE0DFEA": "Monterey Mist", "#FFD2CDB6": "Montezuma", "#FFEECC44": "Montezuma Gold", "#FFA6B2A4": "Montezuma Hills", "#FFD9AD9E": "Montezuma\u2019s Castle", "#FF5879A2": "Montreux Blue", "#FF9D6A73": "Montrose Rose", "#FF84898C": "Monument", "#FFD3C190": "Monument Green", "#FFAD5C34": "Monument Valley", "#FFFBE5BD": "Moo", "#FF393F52": "Mood Indigo", "#FFFFE7D5": "Mood Lighting", "#FF7F90CB": "Mood Mode", "#FF49555D": "Moody Black", "#FF586E75": "Moody Blues", "#FF6586A5": "Moody Indigo", "#FFCAE2D9": "Moody Mist", "#FF4B7991": "Moody Teal", "#FF8F9294": "Moody Whitby", "#FFC7B8A9": "Mooloolaba", "#FF7D7D77": "Moon Base", "#FFC7BDC1": "Moon Buggy", "#FFFAEFBE": "Moon Dance", "#FFDDD5C9": "Moon Drop", "#FFDFE5E4": "Moon Garden", "#FFBCD1C7": "Moon Glass", "#FFF5F3CE": "Moon Glow Variant", "#FFCFC7D5": "Moon Goddess", "#FF8EB8CE": "Moon Jellyfish", "#FFA7A7A7": "Moon Landing", "#FFE6E6E7": "Moon Lily", "#FFFAF1DE": "Moon Maiden", "#FFF4F4E8": "Moon Rise", "#FF897D76": "Moon Rock", "#FF96A5B8": "Moon Shadow", "#FFE9E3D8": "Moon Shell", "#FFCBC5BB": "Moon Shot", "#FF6F8588": "Moon Tide", "#FFFCF1DE": "Moon Valley", "#FF8D99B1": "Moon Veil", "#FFEAF4FC": "Moon White", "#FFF0C420": "Moon Yellow", "#FFC2B8AE": "Moonbeam", "#FFF3DEBF": "Moondoggie", "#FF65FFFF": "Moonglade Water", "#FF1E2433": "Moonless Mystery", "#FF3C393D": "Moonless Night", "#FF444B4A": "Moonless Sky", "#FFF6EED5": "Moonlight", "#FF567090": "Moonlight Blue", "#FFD2E8D8": "Moonlight Green", "#FFC7E5DF": "Moonlight Jade", "#FFCA83A7": "Moonlight Mauve", "#FFAF73B0": "Moonlight Melody", "#FF4E468B": "Moonlight Sonata", "#FF999FB2": "Moonlight Stroll", "#FFF9F0DE": "Moonlight White", "#FFE1C38B": "Moonlight Yellow", "#FFF9F0E6": "Moonlit Beach", "#FF3E6D6A": "Moonlit Forest", "#FFD28FB0": "Moonlit Mauve", "#FF30445A": "Moonlit Ocean", "#FF949194": "Moonlit Orchid", "#FF205A61": "Moonlit Pool", "#FFEAEEEC": "Moonlit Snow", "#FFC9D9E0": "Moonmist", "#FF8D9596": "Moonquake", "#FFC0B2D7": "Moonraker", "#FFA53F48": "Moonrose", "#FF806B77": "Moonscape", "#FFC3C2B2": "Moonshine", "#FF3AA8C1": "Moonstone", "#FFFCF0C2": "Moonstruck", "#FFBEBEC4": "Moonwalk", "#FFA5AE9F": "Moonwort", "#FF6A584D": "Moor Oak Grey", "#FF3C6461": "Moor Pond Green", "#FF1F5429": "Moor-Monster", "#FFA6AB9B": "Moorland", "#FFCC94BE": "Moorland Heather", "#FFCFD1CA": "Moorstone", "#FF725440": "Moose Fur", "#FFCAB59F": "Moose Mousse", "#FF6B5445": "Moose Trail", "#FF5D5744": "Moosewood", "#FFA2DB10": "Moot Green", "#FF00EE33": "Moping Green", "#FF9955CC": "Morado Purple", "#FFCEB391": "Moraine", "#FFB4CDE5": "Morality", "#FF726138": "Morass", "#FFC8BD6A": "Moray", "#FF00A78B": "Moray Eel", "#FF9E0E64": "Morbid Princess", "#FF2A6671": "Mordant Blue", "#FF2F5684": "Mordian Blue", "#FFDFBF9A": "More Cowbell", "#FFD0AB70": "More Maple", "#FFE0E3C8": "More Melon", "#FFE6E8C5": "More Mint", "#FF8D8D8D": "More Than a Week", "#FF73645C": "Morel", "#FFDFCEC6": "Morganite", "#FF82979B": "Morning at Sea", "#FFF4DABB": "Morning Blossom", "#FFF9E8DF": "Morning Blush", "#FFE7E6DE": "Morning Bread", "#FFD5E3DE": "Morning Breeze", "#FFCEEEEF": "Morning Calm", "#FFCED5E3": "Morning Chill", "#FFB0B9AC": "Morning Dew", "#FFC6DBD6": "Morning Dew White", "#FFD0DBD7": "Morning Fog", "#FF6DAE81": "Morning Forest", "#FFEBF4DF": "Morning Frost", "#FF9ED1D3": "Morning Glory Variant", "#FFCA99B7": "Morning Glory Pink", "#FFEEF0D6": "Morning Glow", "#FF89BAB2": "Morning Green", "#FFE0E8ED": "Morning Haze", "#FFC9CCC9": "Morning Lake", "#FFE0EFE9": "Morning Light Wave", "#FFEE8A67": "Morning Marmalade", "#FFE5EDF1": "Morning Mist", "#FFADA7B9": "Morning Mist Grey", "#FFF7EECF": "Morning Moon", "#FFDAD6AE": "Morning Moor", "#FFACC0BD": "Morning Parlour", "#FFDEE4DC": "Morning Rush", "#FFF8EAED": "Morning Shine", "#FFD3DCEA": "Morning Shower", "#FFFCE9DE": "Morning Sigh", "#FFC7ECEA": "Morning Sky", "#FFF5F4ED": "Morning Snow", "#FFE4ECE9": "Morning Song", "#FFC3D1E5": "Morning Star", "#FFF3E6CE": "Morning Sun", "#FFFDEFCC": "Morning Sunlight", "#FFCABD94": "Morning Tea", "#FFE7D2A9": "Morning Wheat", "#FFCBCDB9": "Morning Zen", "#FFD9BE77": "Morning\u2019s Egg", "#FFF3E2DF": "Morningside", "#FFDCC6B9": "Mornington", "#FF115674": "Moroccan Blue", "#FF75583D": "Moroccan Blunt", "#FF7C726C": "Moroccan Brown", "#FF6B5E5D": "Moroccan Dusk", "#FF6E5043": "Moroccan Henna", "#FF6D4444": "Moroccan Leather", "#FFB4C0C3": "Moroccan Moderne", "#FFEAE0D4": "Moroccan Moonlight", "#FF335E8B": "Moroccan Resort", "#FF8D504B": "Moroccan Ruby", "#FFBF7756": "Moroccan Sky", "#FF8F623B": "Moroccan Spice", "#FFB67267": "Morocco", "#FF442D21": "Morocco Brown Variant", "#FF96453B": "Morocco Red", "#FFECE3CC": "Morocco Sand", "#FFAC8F6C": "Morrel", "#FF8CB295": "Morris Artichoke", "#FFA0B8CE": "Morris Blue", "#FFC2D3AF": "Morris Leaf", "#FFADA193": "Morris Room Grey", "#FF546B78": "Morro Bay", "#FFFCFCCF": "Morrow White", "#FFDEAD00": "Mortal Yellow", "#FF565051": "Mortar Variant", "#FF9E9F9E": "Mortar Grey", "#FF00E6D3": "MoS\u2082 Cyan", "#FF007C94": "Mosaic Blue", "#FF599F68": "Mosaic Green", "#FFE8CEC5": "Mosaic Pink", "#FF1C6B69": "Mosaic Tile", "#FF204652": "Moscow Midnight", "#FFEECC77": "Moscow Mule", "#FF937C00": "Moscow Papyrus", "#FF2E4E36": "Moselle Green", "#FF005F5B": "Mosque Variant", "#FF86A852": "Mosquito Fern", "#FF009051": "Moss", "#FFA0AA9A": "Moss Agate", "#FF6B7061": "Moss Beach", "#FF715B2E": "Moss Brown", "#FF8AA775": "Moss Candy", "#FF42544C": "Moss Cottage", "#FF7A7E66": "Moss Covered", "#FF768B59": "Moss Gardens", "#FF4A473F": "Moss Glen", "#FFAFAB97": "Moss Grey", "#FFC7CAC1": "Moss Ink", "#FFC8C6B4": "Moss Island", "#FF6D7E40": "Moss Landing", "#FFDEE1D3": "Moss Mist", "#FF7E8D60": "Moss Point Green", "#FFAFB796": "Moss Print", "#FF729067": "Moss Ring", "#FF5E5B4D": "Moss Rock", "#FF8F6D6B": "Moss Rose", "#FFB4A54B": "Moss Stone", "#FF38614C": "Moss Vale", "#FFB4C2B6": "Mossa", "#FF779966": "Mosslands", "#FF8C9D8F": "Mossleaf", "#FF7F805B": "Mosstone", "#FF857349": "Mossy", "#FF88806F": "Mossy Aura", "#FF8B8770": "Mossy Bank", "#FF83A28F": "Mossy Bench", "#FF525F48": "Mossy Bronze", "#FFA4A97B": "Mossy Cavern", "#FF789B4A": "Mossy Glossy", "#FF9C9273": "Mossy Gold", "#FF5A7C46": "Mossy Green", "#FF867E36": "Mossy Meadow", "#FF848178": "Mossy Oak", "#FF908C7E": "Mossy Pavement", "#FFA9965D": "Mossy Rock", "#FF7E6C44": "Mossy Shade", "#FFBAD147": "Mossy Shining Gold", "#FF828E74": "Mossy Statue", "#FFE7F2DE": "Mossy White", "#FF7A9703": "Mossy Woods", "#FFE3D6D4": "Most Delicate", "#FF575E5F": "Mostly Metal", "#FFC1C1C5": "Mote of Dust", "#FFCBC1A2": "Moth", "#FF007700": "Moth Green", "#FFDAD3CB": "Moth Grey", "#FFEDEBDE": "Moth Mist", "#FFD00172": "Moth Orchid", "#FFCFBDBA": "Moth Pink", "#FFEDF1DB": "Moth\u2019s Wing", "#FF849C8D": "Mother Earth", "#FFA28761": "Mother Lode", "#FFBDE1C4": "Mother Nature", "#FFE9D5C3": "Mother of Pearl", "#FF8FD89F": "Mother of Pearl Green", "#FFD1C4C6": "Mother of Pearl Pink", "#FFCCD6E6": "Mother of Pearl Silver", "#FFF7EDCA": "Mother\u2019s Milk", "#FFBCB667": "Motherland", "#FFEEDD82": "Mothra Wing", "#FFCEBBB3": "Mothy", "#FFA58E71": "Motif", "#FF495A6E": "Motor City Blue", "#FF917C6F": "Motto", "#FFD5A300": "Mouldy Ochre", "#FFE7EFE0": "Mount Eden", "#FF3D484C": "Mount Etna", "#FF3D703E": "Mount Hyjal", "#FFBAC0CD": "Mount Nirvana", "#FF716646": "Mount Olive", "#FFD4FFFF": "Mount Olympus", "#FFCAD3D4": "Mount Sterling", "#FF7C7B6A": "Mount Tam", "#FFE6E0E0": "Mountain Air", "#FFCC7700": "Mountain Ash", "#FF3C4B6C": "Mountain Blueberry", "#FF4C98C2": "Mountain Bluebird", "#FFBFB8B5": "Mountain Boulder", "#FFE2EFE8": "Mountain Crystal Silver", "#FFCFE2E0": "Mountain Dew", "#FF867965": "Mountain Elk", "#FFBDCAC0": "Mountain Falls", "#FF94B491": "Mountain Fern", "#FF383C49": "Mountain Fig", "#FF6C71A6": "Mountain Flower Mauve", "#FFF4DBC7": "Mountain Fog", "#FF4D663E": "Mountain Forest", "#FFB2B599": "Mountain Green", "#FFE8E3DB": "Mountain Grey", "#FF6C6E7E": "Mountain Haze", "#FFEEDAE6": "Mountain Heather", "#FFA2917B": "Mountain Hideaway", "#FF5C5687": "Mountain Iris", "#FF2D5975": "Mountain Lake", "#FF4CBCA7": "Mountain Lake Azure", "#FF85D4D4": "Mountain Lake Blue", "#FF75B996": "Mountain Lake Green", "#FFF4C8D5": "Mountain Laurel", "#FFA7AE9E": "Mountain Lichen", "#FF8DB8D0": "Mountain Main", "#FFEFCC7C": "Mountain Maize", "#FF418638": "Mountain Meadow Green", "#FF385058": "Mountain Midnight", "#FFA7E0C2": "Mountain Mint", "#FFA09F9C": "Mountain Mist Variant", "#FFD4DCD1": "Mountain Morn", "#FF94A293": "Mountain Moss", "#FF908456": "Mountain Olive", "#FF7F9F97": "Mountain Outlook", "#FF5C6A6A": "Mountain Pass", "#FFE9E0D4": "Mountain Peak", "#FF3B5257": "Mountain Pine", "#FF605B57": "Mountain Quail", "#FF53B8C9": "Mountain Range Blue", "#FF283123": "Mountain Range Green", "#FF75665E": "Mountain Ridge", "#FF475F77": "Mountain River", "#FF868578": "Mountain Road", "#FFA3AA8C": "Mountain Sage", "#FFB1AB9A": "Mountain Shade", "#FF8E877F": "Mountain Smoke", "#FFD9E1C1": "Mountain Spring", "#FF96AFB7": "Mountain Stream", "#FF615742": "Mountain Trail", "#FF394C3B": "Mountain View", "#FFD8D0E3": "Mountain\u2019s Majesty", "#FFE9EAEB": "Mourn Mountain Snow", "#FF6F5749": "Mournfang Brown", "#FF1651BD": "Mourning Blue", "#FF928D88": "Mourning Dove", "#FF474354": "Mourning Violet", "#FF9E928F": "Mouse Catcher", "#FF727664": "Mouse Tail", "#FFBEB1B0": "Mouse Trap", "#FF6D2A13": "Moussaka", "#FFE8CEF6": "Mousse aux Pruneaux", "#FF5C4939": "Mousy Brown", "#FF5C544E": "Mousy Indigo", "#FFBF9005": "Moutarde de B\u00e9nichon", "#FF4EFFCD": "Move Mint", "#FF9CCE9E": "Mover & Shaker", "#FFB2BFD5": "Movie Magic", "#FFC52033": "Movie Star", "#FFA9B49A": "Mow the Lawn", "#FF627948": "Mown Grass", "#FFE6D3BB": "Mown Hay", "#FFE5DAD8": "Moxie", "#FF485480": "Mozart", "#FFE39B7A": "Mozzarella Covered Chorizo", "#FFA3C5DB": "Mr Frosty", "#FFE4B857": "Mr Mustard", "#FFC0D5EF": "Mr. Glass", "#FFDBDBD4": "Mr. Kitty", "#FFD04127": "Mr. Krabs", "#FFFF00AA": "Ms. Pac-Man Kiss", "#FF597766": "Mt Burleigh", "#FFE7E9E6": "Mt. Hood White", "#FF7F8181": "Mt. Rushmore", "#FFF1F2D3": "M\u01d4 L\u00ec B\u00e1i Oyster", "#FF70543E": "Mud", "#FF966544": "Mud Ball", "#FF7C6841": "Mud Bath", "#FFD0C8C4": "Mud Berry", "#FF60460F": "Mud Brown", "#FF606602": "Mud Green", "#FF847146": "Mud House", "#FF9D9588": "Mud Pack", "#FFDCC0C3": "Mud Pink", "#FFB6B5B1": "Mud Pots", "#FF9D958B": "Mud Puddle", "#FF60584B": "Mud Room", "#FFC18136": "Mud Yellow", "#FFA08B76": "Mud-Dell", "#FFA46960": "Mudbrick", "#FF5A5243": "Muddled Basil", "#FFA13905": "Muddy", "#FF886806": "Muddy Brown", "#FF3F6976": "Muddy Cyan", "#FF657432": "Muddy Green", "#FFE4B3CC": "Muddy Mauve", "#FFC3988B": "Muddy Mire", "#FF805C44": "Muddy Mississippi", "#FF4B5D46": "Muddy Olive", "#FF715D3D": "Muddy River", "#FFE2BEB4": "Muddy Rose", "#FFA9844F": "Muddy Waters Variant", "#FFBFAC05": "Muddy Yellow", "#FFB8D0DA": "Mudra", "#FF897A69": "Mudskipper", "#FF84735F": "Mudslide", "#FF84846F": "Mudstone", "#FF9E7E53": "Muesli Variant", "#FFF9DDC7": "Muffin Magic", "#FFF5E0D0": "Muffin Mix", "#FFDADBE2": "Muffled White", "#FF448800": "Mughal Green", "#FFA38961": "Mukluks", "#FF920A4E": "Mulberry Variant", "#FF956F29": "Mulberry Brown", "#FFAD6EA0": "Mulberry Bush", "#FF463F60": "Mulberry Mauve Black", "#FF9F556C": "Mulberry Mix", "#FF4A3C62": "Mulberry Purple", "#FF94766C": "Mulberry Silk", "#FFC6BABE": "Mulberry Stain", "#FFC57F2E": "Mulberry Thorn", "#FF997C85": "Mulberry Wine", "#FF4E4240": "Mulch", "#FF827B77": "Mule", "#FF884F40": "Mule Fawn Variant", "#FFC2B332": "Mulgore Mustard", "#FFA18162": "Mulled Cider", "#FF675A74": "Mulled Grape", "#FF763D57": "Mulled Plum", "#FFD5A579": "Mulled Spice", "#FF524D5B": "Mulled Wine Variant", "#FFCA4042": "Mullen Pink", "#FFC18654": "Mulling Spice", "#FFCCD0DD": "Multnomah Falls", "#FF55BB00": "Mulu Frog", "#FF824B27": "Mummy Brown", "#FF828E84": "Mummy\u2019s Tomb", "#FFF23E67": "Munch on Melon", "#FF9BB139": "Munchkin", "#FFCAC76D": "Mung Bean", "#FFD2A172": "Muntok White Pepper", "#FFC5D6EE": "Murano Soft Blue", "#FF4F284B": "Murasaki", "#FF884898": "Murasaki Purple", "#FFAC7E04": "Murder Mustard", "#FFB3205F": "Murderous Magenta", "#FF5B8D6B": "Murdoch", "#FF847EB1": "Murex", "#FF02273A": "Murky Depths", "#FF6C7A0E": "Murky Green", "#FF7E8177": "Murky Sage", "#FFC7CFC7": "Murmur", "#FF6B3C39": "Murray Red", "#FFF4E5AB": "Muscadine", "#FFEBE2CF": "Muscat Blanc", "#FF5E5067": "Muscat Grape", "#FF7B6A68": "Muscatel", "#FF9B6957": "Muscovado Sugar", "#FFE9DECB": "Muscovite", "#FFA5857F": "Muse", "#FF685951": "Museum", "#FF2D4436": "Mushiao Green", "#FFBDACA3": "Mushroom", "#FF977A76": "Mushroom Basket", "#FFCAB49B": "Mushroom Bisque", "#FF906E58": "Mushroom Brown", "#FFD3BEAC": "Mushroom Cap", "#FF8E8062": "Mushroom Forest", "#FFDBD0CA": "Mushroom Risotto", "#FFF0E1CD": "Mushroom White", "#FFF8EAE6": "Musical Mist", "#FFCCA195": "Musk", "#FF7E5B58": "Musk Deer", "#FFCFBFB9": "Musk Dusk", "#FF774548": "Musk Memory", "#FF4D5052": "Muskeg Grey", "#FFA57545": "Muskelmannbraun", "#FF7D6D39": "Musket", "#FFE98447": "Muskmelon", "#FF7E6F4F": "Muskrat", "#FFD3D1C4": "Muslin", "#FFE0CDB1": "Muslin Tint", "#FF24342A": "Mussel Green", "#FFF0E2DE": "Mussel White", "#FF9B6B45": "Must Love Dogs", "#FF5E4A47": "Mustang", "#FFCEB301": "Mustard Variant", "#FFEF8144": "Mustard Crusted Salmon", "#FFD8B076": "Mustard Field", "#FFD2BD0A": "Mustard Flower", "#FFE7A733": "Mustard Glaze", "#FFA6894B": "Mustard Gold", "#FFA8B504": "Mustard Green", "#FF857139": "Mustard Magic", "#FFD5A129": "Mustard Musketeers", "#FFD5BD66": "Mustard Oil", "#FFDDCC33": "Mustard on Toast", "#FFEDBD68": "Mustard Sauce", "#FFC69F26": "Mustard Seed", "#FFC5A574": "Mustard Seed Beige", "#FFE1AD01": "Mustard Yellow", "#FFC29594": "Mutabilis", "#FF91788C": "Muted Berry", "#FF3B719F": "Muted Blue", "#FFCF8A78": "Muted Clay", "#FF515453": "Muted Ebony", "#FF5FA052": "Muted Green", "#FF3B5698": "Muted Lavender", "#FFD0C678": "Muted Lime", "#FFB3A9A3": "Muted Mauve", "#FF66626D": "Muted Mulberry", "#FFD1768F": "Muted Pink", "#FF805B87": "Muted Purple", "#FF954B51": "Muted Red", "#FF93907E": "Muted Sage", "#FFEE0000": "MVS Red", "#FFD3B395": "My Chai", "#FFF3C4C2": "My Fair Lady", "#FFCBB698": "My Guinea Pig", "#FFE1C6A8": "My Love", "#FFCABDCE": "My Mona", "#FFCFBAA4": "My Piazza", "#FFD68B80": "My Pink Variant", "#FF4F434E": "My Place or Yours", "#FFFDAE45": "My Sin Variant", "#FF6597AB": "My Skiff", "#FFF8E7DF": "My Sweetheart", "#FF387ABE": "Mykonos", "#FF005780": "Mykonos Blue", "#FF284C75": "Mykonos Reflection", "#FFE0218A": "Myoga Purple", "#FF00524C": "Myrtle Deep Green", "#FF9EB3DE": "Myrtle Flower", "#FFB77961": "Myrtle Pepper", "#FF8E6F76": "Myself", "#FF98817C": "Mystere", "#FF826F7A": "Mysteria", "#FF46394B": "Mysterioso", "#FF535E63": "Mysterious", "#FF3E7A85": "Mysterious Blue", "#FF060929": "Mysterious Depths", "#FFA6A3A9": "Mysterious Mauve", "#FF0F521A": "Mysterious Mixture", "#FF6F6A52": "Mysterious Moss", "#FF5C6070": "Mysterious Night", "#FF27454A": "Mysterious Waters", "#FFA4CDCC": "Mystery", "#FFBBEFD3": "Mystery Mint", "#FF063C89": "Mystery Oceans", "#FFD8DDDA": "Mystic Tint", "#FF48A8D0": "Mystic Blue", "#FFEAE9E1": "Mystic Fog", "#FFD8F878": "Mystic Green", "#FFD2E4EE": "Mystic Harbour", "#FF8596D2": "Mystic Iris", "#FFDDE5EC": "Mystic Light", "#FFE02E82": "Mystic Magenta", "#FFDBB7BA": "Mystic Mauve", "#FFEDEBB4": "Mystic Melon", "#FF4B2C74": "Mystic Nights", "#FFFBDDBE": "Mystic Opal", "#FFD5DDE2": "Mystic Pool", "#FFFF5500": "Mystic Red", "#FFB7CAE0": "Mystic River", "#FFF8C0BA": "Mystic Rose", "#FFB9E0DB": "Mystic Sea", "#FF5A4742": "Mystic Taupe", "#FFF9B3A3": "Mystic Tulip", "#FF00877B": "Mystic Turquoise", "#FFEBEBE9": "Mystic White", "#FF6E5881": "Mystical", "#FFB5AAA9": "Mystical Grey", "#FFE5E2E3": "Mystical Mist", "#FF745D83": "Mystical Purple", "#FFDCE3D1": "Mystical Sea", "#FF4C5364": "Mystical Shade", "#FF352B30": "Mystical Shadow", "#FF7A6A75": "Mystical Trip", "#FF2A4071": "Mystification", "#FFC9DBC7": "Mystified", "#FFC920B0": "Mystifying Magenta", "#FFA598A0": "Mystique", "#FF723D5B": "Mystique Violet", "#FF657175": "Myth", "#FF4A686C": "Mythic Forest", "#FF7E778E": "Mythical", "#FF93A8A7": "Mythical Blue", "#FF398467": "Mythical Forest", "#FF1C2E63": "Mythical Night", "#FFFF7F49": "Mythical Orange", "#FF7A0A14": "Mythical Wine", "#FFFFCB5D": "Nacho", "#FFFFBB00": "Nacho Cheese", "#FFE8E2D4": "Nacre", "#FFAFC9C0": "Nadia", "#FFC90406": "Naga Morich", "#FFED292B": "Naga Viper Pepper", "#FF3D3354": "Naggaroth Night", "#FFFDEDC3": "N\u01cei Y\u00f3u S\u00e8 Cream", "#FFBD4E84": "Nail Polish Pink", "#FFD9A787": "Nairobi Dusk", "#FFFCE7D3": "Naive Peach", "#FFF6EBE6": "Naivete", "#FFC93756": "Nakabeni Pink", "#FFC8A397": "Naked Clay", "#FFF7CB6E": "Naked Noodle", "#FFEBB5B3": "Naked Rose", "#FF785E49": "Namakabe Brown", "#FF7B7C7D": "Namara Grey", "#FFBDD8C0": "Namaste", "#FF7C6D61": "Namibia", "#FFA08DA7": "Nana", "#FFDFCBC3": "Nana\u2019s Pearls", "#FFDE9EAA": "Nana\u2019s Rose", "#FF57B8DC": "Nancy", "#FF8F423D": "Nandi Bear", "#FF4E5D4E": "Nandor Variant", "#FFB89E82": "Nankeen", "#FFF2F0EA": "Nano White", "#FFE3B130": "Nanohanacha Gold", "#FF7D9192": "Nantucket Bay", "#FFD0BFAA": "Nantucket Dune", "#FFCABFBF": "Nantucket Mist", "#FFB4A89A": "Nantucket Sands", "#FFA39A87": "Napa Variant", "#FFE1D8D5": "Napa Dawn", "#FF5B5162": "Napa Grape", "#FF534853": "Napa Harvest", "#FFCD915C": "Napa Sunset", "#FF5D4149": "Napa Wine", "#FF6A5C7D": "Napa Winery", "#FFEFDDC1": "Napery", "#FFFADA5F": "Naples Yellow Variant", "#FF404149": "Napoleon", "#FF2C4170": "Napoleonic Blue", "#FFFF8050": "N\u0101rang\u012b Orange", "#FFC39449": "Narcissus", "#FFE6E3D8": "Narcomedusae", "#FFFFC14B": "N\u00e2renji Orange", "#FFE9E6DC": "Narvik Variant", "#FF080813": "Narwhal Grey", "#FF746062": "Nasake", "#FFEDD4B1": "Nashi Pear Beige", "#FFFE5B2E": "Nasturtium", "#FFE64D1D": "Nasturtium Flower", "#FF87B369": "Nasturtium Leaf", "#FF869F49": "Nasturtium Shoot", "#FF70B23F": "Nasty Green", "#FF5D21D0": "Nasu Purple", "#FFA17917": "Nataneyu Gold", "#FFBA9F95": "Natchez", "#FFB1A76F": "Natchez Moss", "#FF3F6F98": "National Anthem", "#FFDC6B67": "Native Berry", "#FF9AA099": "Native Flora", "#FF848667": "Native Henna", "#FFD33300": "Native Hue of Resolution", "#FFA89AA7": "Native Lilac", "#FF887B69": "Native Soil", "#FF153043": "NATO Blue", "#FF555548": "NATO Olive", "#FFEBBC71": "Natrolite", "#FFC79843": "Natt\u014d", "#FFA48B74": "Natural", "#FFDED2BB": "Natural Almond", "#FF6D574D": "Natural Bark", "#FFA29171": "Natural Bridge", "#FFBBA88B": "Natural Chamois", "#FFE3DED0": "Natural Choice", "#FF8B655A": "Natural Copper", "#FF976343": "Natural Cork", "#FFA27833": "Natural Evolution", "#FFBCCD91": "Natural Green", "#FFC4C0BB": "Natural Grey", "#FF82745C": "Natural Habitat", "#FF91AA90": "Natural Harmony", "#FF003740": "Natural Indigo", "#FF017374": "Natural Instinct Green", "#FFA80E00": "Natural Leather", "#FFF1EBC8": "Natural Light", "#FFECDFCF": "Natural Linen", "#FF4C9C77": "Natural Orchestra", "#FF77B033": "Natural Order", "#FF4A4A43": "Natural Pumice", "#FFE7DCC1": "Natural Radiance", "#FFDCC39F": "Natural Rice Beige", "#FFFCE7C5": "Natural Sheepskin", "#FFD3C5C0": "Natural Silk Grey", "#FFE5D8C2": "Natural Soap", "#FFAA838B": "Natural Spring", "#FF8A8287": "Natural Steel", "#FFAEA295": "Natural Stone", "#FFDCD2C3": "Natural Tan", "#FFDBC39B": "Natural Twine", "#FF006E4E": "Natural Watercourse", "#FFF0E8CF": "Natural Whisper", "#FFFBEDE2": "Natural White", "#FFFFF6D7": "Natural Wool", "#FFEED88B": "Natural Yellow", "#FFD7E5B4": "Natural Youth", "#FFF1E0CF": "Naturale", "#FF68685D": "Naturalism", "#FF8B8C83": "Naturalist Grey", "#FFCED0D9": "Naturally Calm", "#FFBFD5B3": "Nature", "#FFFEB7A5": "Nature Apricot", "#FF7DAF94": "Nature Green", "#FF6C7763": "Nature Lover", "#FF7B8787": "Nature Retreat", "#FFC8C8B4": "Nature Spirits", "#FF52634B": "Nature Surrounds", "#FFE6D7BB": "Nature Trail", "#FF7C7A7A": "Nature Walk", "#FFA6D292": "Nature\u2019s Delight", "#FF666A60": "Nature\u2019s Gate", "#FF99A399": "Nature\u2019s Gift", "#FF006611": "Nature\u2019s Masterpiece", "#FFC5D4CD": "Nature\u2019s Reflection", "#FF117733": "Nature\u2019s Strength", "#FFCBC0AD": "Naturel", "#FFBA403A": "Naughty Hottie", "#FFE3CCDC": "Naughty Marietta", "#FF2E4A7D": "Nautical", "#FF1A5091": "Nautical Blue", "#FF295C7A": "Nautical Creatures", "#FFAAB5B7": "Nautical Star", "#FF273C5A": "Nautilus", "#FF378FB3": "Navagio Bay", "#FFEFDCC3": "Navajo", "#FFA48679": "Navajo Horizon", "#FF007C78": "Navajo Turquoise", "#FF41729F": "Naval", "#FF072688": "Naval Adventures", "#FF384B6B": "Naval Blue", "#FF011C39": "Naval Night", "#FF386782": "Naval Passage", "#FFEC8430": "Navel", "#FF008A86": "Navigate", "#FF5D83AB": "Navigator", "#FF01153E": "Navy", "#FFE0DCCF": "Navy Bean", "#FF263032": "Navy Black", "#FF2A2E3F": "Navy Blazer", "#FF57415C": "Navy Cosmos", "#FF425166": "Navy Damask", "#FF004C6A": "Navy Dark Blue", "#FF9556EB": "Navy Genesis Evangelion", "#FF35530A": "Navy Green", "#FF223A5E": "Navy Peony", "#FF253A91": "Navy Seal", "#FF20576E": "Navy Teal", "#FF203462": "Navy Trim", "#FF5DC2DB": "Naxos Sky", "#FF9B7A78": "Neapolitan", "#FF4D7FAA": "Neapolitan Blue", "#FF5EE7DF": "Near Moon", "#FFA88E76": "Nearly Brown", "#FFEFDED1": "Nearly Peach", "#FFC8D5DD": "Nearsighted", "#FFA104C3": "Nebula Variant", "#FF281367": "Nebula Night", "#FF922B9C": "Nebula Outpost", "#FF2E62A7": "Nebulas Blue", "#FFC4B9B8": "Nebulous", "#FFB3B2BF": "Nebulous December", "#FFDEDFDC": "Nebulous White", "#FF828B8E": "Necron Compound", "#FFDEAD69": "Necrophilic Brown", "#FFECDACD": "Nectar", "#FFF0D38F": "Nectar Jackpot", "#FFE3A701": "Nectar of the Goddess", "#FF513439": "Nectar of the Gods", "#FF7F4C64": "Nectar Red", "#FFD38D72": "Nectarina", "#FFFF8656": "Nectarine", "#FFD29B8D": "Nectarine Spice", "#FFDD5566": "Nectarous Nectarine", "#FFB1D6DE": "Needed Escape", "#FF546670": "Needlepoint Navy", "#FFC5CED8": "Nefarious Blue", "#FFE6D1DC": "Nefarious Mauve", "#FF938B4B": "Negishi Green", "#FFEEC7A2": "Negroni Variant", "#FFF3C1A3": "Neighbourly Peach", "#FF337711": "Nemophilist", "#FFAAFFCC": "Neo Mint", "#FFBEC0C2": "Neo Tokyo Grey", "#FF04D9FF": "Neon Blue", "#FFDFC5FE": "Neon Boneyard", "#FFFF9832": "Neon Carrot Variant", "#FFFFD4A9": "Neon Cloud", "#FF09CDCD": "Neon Lagoon", "#FFFFDF5E": "Neon Light", "#FF95FF00": "Neon Lime", "#FF4FDCE1": "Neon Nazar", "#FFFE019A": "Neon Pink", "#FFBC13FE": "Neon Purple", "#FFFF073A": "Neon Red", "#FFE9023A": "Neon Romance", "#FFFF629F": "Neon Sunset", "#FF674876": "Neon Violet", "#FFCFFF04": "Neon Yellow", "#FF93AAB9": "Nepal Variant", "#FF6D9288": "Nephrite", "#FF007DAC": "Neptune Variant", "#FF2E5D9D": "Neptune Blue", "#FF7FBB9E": "Neptune Green", "#FF003368": "Neptune\u2019s Dream", "#FF97C0D1": "Neptune\u2019s Realm", "#FF11425D": "Neptune\u2019s Wrath", "#FF4C793C": "Nereus", "#FF252525": "Nero Variant", "#FF318181": "Nero\u2019s Green", "#FFFF6EC7": "Nervous Neon Pink", "#FFD7C65B": "Nervy Hue", "#FF716748": "Nessie", "#FF917B68": "Nest", "#FFEEEADA": "Nesting Dove", "#FFB6A194": "Net Worker", "#FF881111": "Netherworld", "#FFE0CFB0": "Netsuke", "#FFBBAC7D": "Nettle", "#FF364C2E": "Nettle Green", "#FFE4F7E7": "Nettle Rash", "#FFA0A5A7": "Network Grey", "#FFCAC1B1": "Neutra", "#FF9D928F": "Neutral Buff", "#FFAAA583": "Neutral Green", "#FF8E918F": "Neutral Grey", "#FFE2DACA": "Neutral Ground", "#FFFFE6C3": "Neutral Peach", "#FF8B694D": "Neutral Valley", "#FFECEDE8": "Neutral White", "#FF01248F": "Neutrino Blue", "#FF666F6F": "Nevada Variant", "#FFFFD5A7": "Nevada Morning", "#FFEAD5B9": "Nevada Sand", "#FFA1D9E7": "Nevada Sky", "#FF6E6455": "Never Cry Wolf", "#FFA67283": "Never Forget", "#FF666556": "Nevergreen", "#FF9CE5D6": "Neverland", "#FF7BC8F6": "Nevermind Nirvana", "#FF403940": "Nevermore", "#FF13181B": "Neverything", "#FF496EAD": "New Age Blue", "#FF6D3B24": "New Amber", "#FFBDB264": "New Avocado", "#FFD8DECE": "New Baby Smell", "#FFADAC84": "New Bamboo", "#FF934C3D": "New Brick", "#FF482427": "New Bulgarian Rose", "#FFA28367": "New Chestnut", "#FFEFC1B5": "New Clay", "#FFB89B6B": "New Cork", "#FFEDE0C0": "New Cream", "#FFDA9A21": "New England Autumn", "#FFAD7065": "New England Brick", "#FFAA7755": "New England Roast", "#FFC9A171": "New Fawn", "#FFC2BC90": "New Foliage", "#FF47514D": "New Forest", "#FFBACCA0": "New Frond", "#FFEAD151": "New Gold", "#FFB5AC31": "New Green", "#FFDBD5B7": "New Growth", "#FFEDDFC7": "New Harvest Moon", "#FFBD827F": "New Haven Rose", "#FFD0E5F2": "New Heights", "#FFE2EFC2": "New Hope", "#FFF1EDE7": "New House White", "#FF4A5F58": "New Hunter", "#FFD9C7AA": "New Khaki", "#FF7C916E": "New Life", "#FFC6BBDB": "New Love", "#FF48C09B": "New Meadow", "#FFDEAA89": "New Mexico Mesa", "#FFB2BDBB": "New Morn Fog", "#FFC6D6C7": "New Moss", "#FF3B4A55": "New Navy Blue", "#FFBEC0AA": "New Neutral", "#FFC3CDD2": "New North", "#FFE4C385": "New Orleans Variant", "#FF95A294": "New Patina", "#FFA27D66": "New Penny", "#FFB1D9E9": "New Prince", "#FFEEEEE5": "New Ream", "#FF691D6C": "New Riders of the Purple Sage", "#FF875251": "New Roof", "#FF869E3E": "New Shoot", "#FF933C3C": "New Sled", "#FF738595": "New Steel", "#FF90ABBB": "New Toile", "#FFD6C1DD": "New Violet", "#FF11FF11": "New Wave Green", "#FFFF22FF": "New Wave Pink", "#FFD2AF6F": "New Wheat", "#FFD6C3B9": "New Wool", "#FFE8C247": "New Yellow", "#FFDD8374": "New York Pink Variant", "#FFFF0059": "New York Sunset", "#FFF0E1DF": "New Youth", "#FFA0C4BE": "New-Age Green", "#FF616550": "Newbury Moss", "#FF445A79": "Newburyport", "#FFB2C7E1": "Newman\u2019s Eye", "#FFEAE2DC": "Newmarket Sausage", "#FF1C8AC9": "Newport Blue", "#FF26566E": "Newport Grey", "#FF313D6C": "Newport Indigo", "#FF756F6D": "Newsprint", "#FF29A98B": "Niagara Variant", "#FFCBE3EE": "Niagara Falls", "#FFC5E8EE": "Niagara Mist", "#FF7DC734": "Niblet Green", "#FF107AB0": "Nice Blue", "#FFFAECD1": "Nice Cream", "#FFDBA37E": "Nice Tan", "#FFE6DDD5": "Nice White", "#FF65758F": "Niche", "#FF84979A": "Nichols Beach", "#FF909062": "Nick\u2019s Nook", "#FF929292": "Nickel Variant", "#FF537E7E": "Nickel Ore Green", "#FFC1C6BF": "Nickel Plate", "#FFCC7755": "Nicotine Glaze", "#FFEEBB33": "Nicotine Gold", "#FFB6C3C4": "Niebla Azul", "#FF019187": "Nifty Turquoise", "#FF613E3D": "Night Bloom", "#FFF9F7EC": "Night Blooming Jasmine", "#FF040348": "Night Blue", "#FF44281B": "Night Brown", "#FF322D25": "Night Brown Black", "#FF494B4E": "Night Club", "#FF201B20": "Night Demons", "#FF003355": "Night Dive", "#FF20586D": "Night Edition", "#FF572E89": "Night Fever", "#FF434D5C": "Night Flight", "#FF2D1962": "Night Fog", "#FF49646D": "Night Folly", "#FF302F27": "Night Green", "#FF45444D": "Night Grey", "#FF615D5C": "Night Gull Grey", "#FF443300": "Night in the Woods", "#FF005572": "Night Kite", "#FFF8E8CD": "Night Light", "#FF4C6177": "Night Market", "#FF508793": "Night Masque", "#FF5D3B41": "Night Mauve", "#FF5E5C50": "Night Mission", "#FF234E86": "Night Mode", "#FF9C96AF": "Night Music", "#FF4F4F5E": "Night Night", "#FF898487": "Night on the Moors", "#FF656A6E": "Night Out", "#FF5D7B89": "Night Owl", "#FF11FFBB": "Night Pearl", "#FF3C2727": "Night Red", "#FF66787E": "Night Rendezvous", "#FF332E2E": "Night Rider Variant", "#FF715055": "Night Romance", "#FFB0807A": "Night Rose", "#FF0E737E": "Night Scape", "#FFA23D54": "Night Shadz Variant", "#FF2A5C6A": "Night Shift", "#FF292B31": "Night Sky", "#FF756968": "Night Skyscraper", "#FFAACCFF": "Night Snow", "#FF254A57": "Night Swim", "#FF6B7BA7": "Night Thistle", "#FF455360": "Night Tide", "#FF4B7689": "Night Train", "#FF003833": "Night Turquoise", "#FF465560": "Night View", "#FF77BB55": "Night Vision", "#FF3C4F4E": "Night Watch", "#FFE1E1DD": "Night White", "#FFD7E2DB": "Night Wind", "#FF313740": "Night Wizard", "#FF43535E": "Nightfall", "#FF0011DD": "Nightfall in Suburbia", "#FF615452": "Nighthawk", "#FF234C47": "Nighthawks", "#FF5C4827": "Nightingale", "#FFBAAEA3": "Nightingale Grey", "#FF27426B": "Nightlife", "#FF536078": "Nightly", "#FF9BEEC1": "Nightly Aurora", "#FF5A7D9A": "Nightly Blade", "#FF0433FF": "Nightly Escapade", "#FF221188": "Nightly Expedition", "#FF444940": "Nightly Ivy", "#FF4F5B93": "Nightly Silhouette", "#FF784384": "Nightly Violet", "#FF391531": "Nightly Voyager", "#FF544563": "Nightly Walk", "#FF112211": "Nightmare", "#FF293135": "Nightmare Fuel", "#FF3C464B": "Nightshade", "#FF1B1811": "Nightshade Berries", "#FF535872": "Nightshade Purple", "#FFA383AC": "Nightshade Violet", "#FF555971": "Nightshadow Blue", "#FF161D3B": "Nightwalker", "#FF931121": "Nigritella Red", "#FF0055FF": "N\u012bl\u0101 Blue", "#FFAFB982": "Nile", "#FF8B8174": "Nile Clay", "#FF99BE85": "Nile Green", "#FF968F5F": "Nile Reed", "#FF9AB6A9": "Nile River", "#FFBBAD94": "Nile Sand", "#FF61C9C1": "Nile Stone", "#FFF1EBE0": "Nilla Vanilla", "#FF747880": "Nimbostratus", "#FF4422FF": "Nimbus Blue", "#FFC8C8CC": "Nimbus Cloud", "#FFF5E3EA": "Nina", "#FF46434A": "Nine Iron", "#FFFFEF19": "N\u00edng M\u00e9ng Hu\u00e1ng Lemon", "#FF020308": "Ninja", "#FF75528B": "Ninja Princess", "#FF94B1A9": "Ninja Turtle", "#FFBB7777": "Nipple", "#FFBC002C": "Nippon", "#FFA2919B": "Nirvana", "#FF64A5AD": "Nirvana Jewel", "#FF43242A": "Nisemurasaki Purple", "#FF056EEE": "N\u00edu Z\u01cei S\u00e8 Denim", "#FFA33F40": "No More Drama", "#FFFFD6DD": "No Need", "#FFFBAA95": "No Way Ros\u00e9", "#FFF8E888": "No$GMB Yellow", "#FFF8D68B": "\u21165", "#FFA99D9D": "Nobel Variant", "#FFECDEC5": "Nobility", "#FF414969": "Nobility Blue", "#FF202124": "Noble Black", "#FF697991": "Noble Blue", "#FFE8B9B2": "Noble Blush", "#FF990C0D": "Noble Cause", "#FF7E1E9C": "Noble Cause Purple", "#FF6D4433": "Noble Chocolate", "#FFE1DACE": "Noble Cream", "#FF8D755D": "Noble Crown", "#FF627B44": "Noble Eric", "#FF5A736D": "Noble Fir", "#FFC1BEB9": "Noble Grey", "#FF51384A": "Noble Hatter\u2019s Violet", "#FF69354F": "Noble Honour", "#FF394D78": "Noble Knight", "#FFB28392": "Noble Lilac", "#FF871F78": "Noble Plum", "#FFAFB1C5": "Noble Purple", "#FF92181D": "Noble Red", "#FF807070": "Noble Robe", "#FF73777F": "Noble Silver", "#FF884967": "Noble Tone", "#FF524B50": "Noblesse", "#FF463636": "Noblesse Oblige", "#FFBC8E6C": "Nobody but Ben", "#FF646B77": "Noctis", "#FF767D86": "Nocturnal", "#FF114C5A": "Nocturnal Expedition", "#FF675754": "Nocturnal Flight", "#FF2F3738": "Nocturnal Green", "#FFCC6699": "Nocturnal Rose", "#FF0E6071": "Nocturnal Sea", "#FF344D58": "Nocturne", "#FF37525F": "Nocturne Blue", "#FF7A4B56": "Nocturne Red", "#FF356FAD": "Nocturne Shade", "#FFBDBEBD": "Noghrei Silver", "#FF312B27": "Noir", "#FF150811": "Noir Fiction", "#FF1F180A": "Noir Mystique", "#FF866745": "Nom Nom", "#FFA19986": "Nomad Variant", "#FF7E736F": "Nomad Grey", "#FFAF9479": "Nomadic", "#FFC7B198": "Nomadic Desert", "#FFDBDEDB": "Nomadic Dream", "#FFD2C6AE": "Nomadic Taupe", "#FFE0C997": "Nomadic Travels", "#FF357567": "Nominee", "#FF8A8DAA": "Non Skid Grey", "#FFDD8811": "Non-Stop Orange", "#FFC3F400": "Non-Toxic Boyfriend", "#FFDEDDD1": "Nonchalant White", "#FFC1A65C": "Nonpareil Apple", "#FFF5DDC4": "Noodle Arms", "#FFF9E3B4": "Noodles", "#FF99A9AD": "Nor\u2019wester", "#FF003333": "Nora\u2019s Forest", "#FF1D393C": "Nordic", "#FFD3DDE7": "Nordic Breeze", "#FF317362": "Nordic Forest", "#FF1FAB58": "Nordic Grass Green", "#FF003344": "Nordic Noir", "#FF43694D": "Nordic Pine", "#FF7E95AB": "Nordland Blue", "#FF96AEC5": "Nordland Light Blue", "#FF2E7073": "Nordmann Fir", "#FF2E4B3C": "Norfolk Green", "#FF6CBAE7": "Norfolk Sky", "#FF112A12": "Nori Green", "#FF464826": "Nori Seaweed Green", "#FFE9C68E": "Norman Shaw Goldspar", "#FF3D9DC2": "Norse Blue", "#FF5E7B7F": "North Atlantic", "#FF3676B5": "North Atlantic Breeze", "#FF849C9D": "North Beach Blue", "#FF7A9595": "North Cape Grey", "#FF6A7777": "North Grey", "#FFBCB6B4": "North Island", "#FFD8A892": "North Rim", "#FF316C69": "North Sea", "#FF343C4C": "North Sea Blue", "#FFF2DEA4": "North Star", "#FF223399": "North Star Blue", "#FF059033": "North Texas Green", "#FF555A51": "North Woods", "#FF767962": "Northampton Trees", "#FFD1DCDD": "Northbound", "#FF948666": "Northeast Trail", "#FFDE743C": "Northern Barrens Dust", "#FFE9DAD2": "Northern Beach", "#FFBFC7D4": "Northern Exposure", "#FF536255": "Northern Glen", "#FF017466": "Northern Hemisphere", "#FF778F8E": "Northern Juniper", "#FFC5C1A3": "Northern Landscape", "#FFA7AEB4": "Northern Light Grey", "#FFE6F0EA": "Northern Lights", "#FFA3B9CD": "Northern Pond", "#FF8DACCC": "Northern Sky", "#FF9CBACD": "Northern Sky Blue", "#FFFFFFEA": "Northern Star", "#FF5E463C": "Northern Territory", "#FFAAA388": "Northgate Green", "#FF9E9181": "Northpointe", "#FFB9F2FF": "Northrend", "#FFCEE5E9": "Northwind", "#FFA4B88F": "Norway Variant", "#FF78888E": "Norwegian Blue", "#FF8896AA": "Norwegian Night", "#FFB4CDDE": "Norwegian Sky", "#FFACB597": "Norwich Green", "#FFFFE6EC": "Nosegay", "#FFA9A8A8": "Nosferatu", "#FF426579": "Noshime Flower", "#FFD6B8BD": "Nostalgia", "#FFDBDBF7": "Nostalgia Perfume", "#FFA2747D": "Nostalgia Rose", "#FF666C7E": "Nostalgic", "#FF47626F": "Nostalgic Evening", "#FF85C8D3": "Not a Cloud in Sight", "#FF7E7D78": "Not My Fault", "#FF6A6968": "Not so Innocent", "#FF090615": "Not Tonight", "#FFB1714C": "Not yet Caramel", "#FFFFC12C": "Not Yo Cheese", "#FF8BA7BB": "Notable Hue", "#FFE8EBE6": "Notebook Paper", "#FFD9BACC": "Noteworthy", "#FFF2DEB9": "Nothing Less", "#FFBA8686": "Notice Me", "#FFBDA998": "Notorious", "#FF664400": "Notorious Neanderthal", "#FFC6C5BC": "Notre Dame", "#FF585D4E": "Nottingham Forest", "#FFAE8A78": "Nougat", "#FF7C503F": "Nougat Brown", "#FF686F7E": "Nouveau", "#FFA05B42": "Nouveau Copper", "#FFB88127": "Nouveau Green", "#FF996872": "Nouveau Rose", "#FFFFBB77": "Nouveau-Riche", "#FFE1DCDA": "Nouvelle White", "#FFD94F9A": "Nova Pink", "#FFF8EED9": "Nova White", "#FFC2A4C2": "Novel Lilac", "#FF9DB9D3": "Novella Blue", "#FFE3C7B2": "Novelle Peach", "#FF515B62": "Novelty Navy", "#FFBE7767": "November", "#FF754D42": "November Foliage", "#FFF6B265": "November Gold", "#FF767764": "November Green", "#FFF1B690": "November Leaf", "#FFEDE6E8": "November Pink", "#FF7CAFB9": "November Skies", "#FF423F3B": "November Storms", "#FF89A203": "Noxious", "#FFE2E0D6": "Nuance", "#FFECF474": "Nuclear Acid", "#FFBBFF00": "Nuclear Blast", "#FFAA9900": "Nuclear Fallout", "#FFEE9933": "Nuclear Mango", "#FF44EE00": "Nuclear Meltdown", "#FF00DE00": "Nuclear Throne", "#FFE58F7C": "Nude Flamingo", "#FFB5948D": "Nude Lips", "#FFBC9229": "Nugget Variant", "#FFBF961F": "Nugget Gold", "#FF1E488F": "Nuit Blanche", "#FF14100E": "Nuln Oil", "#FF171310": "Nuln Oil Gloss", "#FF929BAC": "Numbers", "#FFE2E6DE": "Numero Uno", "#FFF8F6E9": "Nun Orchid", "#FF9B8F22": "Nurgle\u2019s Rot", "#FFB8CC82": "Nurgling Green", "#FFEFD0D2": "Nursery", "#FFEDF0DE": "Nursery Green", "#FFF4D8E8": "Nursery Pink", "#FF9EBCB7": "Nursery Wall", "#FFD7DCD5": "Nurture", "#FF98B092": "Nurture Green", "#FFA1A97B": "Nurturing", "#FF9D896C": "Nurude Brown", "#FF9E8A6D": "Nut", "#FF86695E": "Nut Brown", "#FF816C5B": "Nut Cracker", "#FFD7BEA4": "Nut Flavour", "#FFD9CCC8": "Nut Milk", "#FF775D38": "Nut Oil", "#FF8E725F": "Nuthatch", "#FF445599": "Nuthatch Back", "#FF7E4A3B": "Nutmeg Variant", "#FFECD9CA": "Nutmeg Frost", "#FFD8B691": "Nutmeg Glow", "#FF75663E": "Nutria", "#FF514035": "Nutria Fur Brown", "#FF898C92": "Nuts and Bolts", "#FFA9856B": "Nutshell", "#FFF7D4C6": "Nutter Butter", "#FFD4BCA3": "Nutty Beige", "#FF8A6F44": "Nutty Brown", "#FFEFEAE8": "NYC Apartment Wall", "#FFF7B731": "NYC Taxi", "#FF0B0F1A": "Nyctophobia", "#FF4D587A": "Nyctophobia Blue", "#FFE9E3CB": "Nylon", "#FFAEC2A5": "Nymph Green", "#FF7B6C8E": "Nymph\u2019s Delight", "#FFCEE0E3": "Nymphaeaceae", "#FF5F6E77": "NYPD", "#FFE1B8B5": "O Fortuna", "#FF005522": "O Tannenbaum", "#FFF3A347": "O\u2019Brien Orange", "#FF58AC8F": "O\u2019grady Green", "#FF395643": "O\u2019Neal Green", "#FF715636": "Oak Barrel", "#FFA18D80": "Oak Brown", "#FFCF9C63": "Oak Buff", "#FF5D504A": "Oak Creek", "#FFCDB386": "Oak Harbour", "#FFBB6B41": "Oak Palace", "#FF5D4F39": "Oak Plank", "#FFC0B0AB": "Oak Ridge", "#FFEED8C2": "Oak Shaving", "#FFD0C7B6": "Oak Tone", "#FFE0B695": "Oakley Apricot", "#FF6D7244": "Oakmoss", "#FFBDA58B": "Oakwood", "#FF8F716E": "Oakwood Brown", "#FF648D95": "Oarsman Blue", "#FF0092A3": "Oasis Variant", "#FFFCEDC5": "Oasis Sand", "#FF47A3C6": "Oasis Spring", "#FFA2EBD8": "Oasis Stream", "#FFE1CAB3": "Oat Cake", "#FFC0AD89": "Oat Field", "#FFF7E4CD": "Oat Flour", "#FFDEDACD": "Oat Milk", "#FFF1D694": "Oat Straw", "#FFC8B9A4": "Oatbran", "#FF4A465A": "Oath", "#FFC9C1B1": "Oatmeal", "#FFDDC7A2": "Oatmeal Bath", "#FFB7A86D": "Oatmeal Biscuit", "#FFEADAC6": "Oatmeal Cookie", "#FFE7BF81": "Oats and Honey", "#FFFCD389": "Oberon", "#FFE7C492": "Oberon on the Beach", "#FFB0A3B6": "Obi Lilac", "#FFB7A8A8": "Object of Desire", "#FFBBC6DE": "Objectivity", "#FF54645C": "Obligation", "#FF000435": "Oblivion", "#FF88654E": "Obscure Ochre", "#FF771908": "Obscure Ogre", "#FF4A5D23": "Obscure Olive", "#FFBB5500": "Obscure Orange", "#FF9D0759": "Obscure Orchid", "#FF008F70": "Observatory Variant", "#FFAE9550": "Obsession", "#FF445055": "Obsidian", "#FF523E35": "Obsidian Brown", "#FF382B46": "Obsidian Lava Black", "#FF3F1D50": "Obsidian Orchid", "#FF372A38": "Obsidian Red", "#FF060313": "Obsidian Shard", "#FF441166": "Obsidian Shell", "#FF3C3F40": "Obsidian Stone", "#FFD7552A": "Obstinate Orange", "#FFFFB077": "Obtrusive Orange", "#FF4B1E87": "Occult", "#FF005493": "Ocean", "#FF221166": "Ocean Abyss", "#FFDAE4ED": "Ocean Air", "#FF0077BE": "Ocean Boat Blue Variant", "#FFA4C8C8": "Ocean Boulevard", "#FFD3E5EB": "Ocean Breeze", "#FF8CADCD": "Ocean Bubble", "#FF2B6C8E": "Ocean Call", "#FF7896BA": "Ocean City", "#FFD6DDDD": "Ocean Crest", "#FF9CD4E1": "Ocean Cruise", "#FF537783": "Ocean Current", "#FF00657F": "Ocean Depths", "#FFD4DDE2": "Ocean Dream", "#FFB0BEC5": "Ocean Drive", "#FFAFC3BC": "Ocean Droplet", "#FFA9C7CF": "Ocean Eyes", "#FFCAC8B4": "Ocean Foam", "#FF7A7878": "Ocean Frigate", "#FFB8E3ED": "Ocean Front", "#FFC1CCC2": "Ocean Froth", "#FF3D9973": "Ocean Green Variant", "#FF7FC1D8": "Ocean Horizon", "#FF68DFBB": "Ocean in a Bowl", "#FFA4C3C5": "Ocean Kiss", "#FF189086": "Ocean Liner", "#FF7D999F": "Ocean Melody", "#FF00748F": "Ocean Mirage", "#FF637195": "Ocean Night", "#FF006C68": "Ocean Oasis", "#FFD3CFBD": "Ocean Pearl", "#FF016072": "Ocean Radiance", "#FF1F989B": "Ocean Reef", "#FF7594B3": "Ocean Ridge", "#FFE4D5CD": "Ocean Sand", "#FF5B7886": "Ocean Shadow", "#FF34B5D5": "Ocean Sigh", "#FFC8DCD5": "Ocean Silk", "#FF41767B": "Ocean Slumber", "#FF00878F": "Ocean Soul", "#FF005379": "Ocean Spray", "#FF3F677E": "Ocean Storm", "#FF79A2BD": "Ocean Surf", "#FF727C7E": "Ocean Swell", "#FF2E526A": "Ocean Trapeze", "#FF62AEBA": "Ocean Trip", "#FF67A6D4": "Ocean Tropic", "#FF729BB3": "Ocean View", "#FF7DBCAA": "Ocean Wave", "#FF6C6541": "Ocean Weed", "#FF306A78": "Ocean\u2019s Embrace", "#FF4F6D82": "Oceanic", "#FFBBC8C9": "Oceanic Climate", "#FF347284": "Oceanic Metropolis", "#FF1D5C83": "Oceanic Motion", "#FF172B36": "Oceanic Noir", "#FF9AD6E5": "Oceano", "#FF415F61": "Oceans Deep", "#FF015A6B": "Oceanside", "#FF90ABA8": "Oceanus", "#FFF1E2C9": "Ocelot", "#FF6B6047": "Ochicha Latte", "#FFD99E73": "Ochraceous Salmon", "#FF9F7B3E": "Ochre Brown", "#FFCC7733": "Ochre Maroon", "#FFC77135": "Ochre Pigment", "#FFEEC987": "Ochre Revival", "#FFE96D03": "Ochre Spice", "#FF085B73": "Octagon Ocean", "#FFCCDD00": "Octarine", "#FFC67533": "October", "#FFE3C6A3": "October Bounty", "#FFD1BB98": "October Harvest", "#FFF8AC8C": "October Haze", "#FF855743": "October Leaves", "#FF8FA2A2": "October Sky", "#FF357911": "Odd Pea Pod", "#FFB6E5D6": "Ode", "#FF9D404A": "Ode Variant", "#FFA798C2": "Ode Tint", "#FFFFDFBF": "Odious Orange", "#FF374A5A": "Odyssey", "#FF4C4E5D": "Odyssey Grey", "#FFD5C6CC": "Odyssey Lilac", "#FFE1C2C5": "Odyssey Plum", "#FF303030": "Off Black", "#FF5684AE": "Off Blue", "#FF433F3D": "Off Broadway", "#FF6BA353": "Off Green Variant", "#FFD1CCCB": "Off Shore", "#FF534C4B": "Off the Beaten Path", "#FF9F9049": "Off the Grid", "#FFFFFFE4": "Off White", "#FFF1F33F": "Off Yellow Variant", "#FF003723": "Off-Road Green", "#FFFFA259": "Off-The-Leash Orange", "#FFD6D0C6": "Offbeat", "#FF9C8B1F": "Offbeat Green", "#FF849DB4": "Office Blue", "#FF006C65": "Office Blue Green", "#FF00800F": "Office Green", "#FF635D54": "Office Grey", "#FFFF2277": "Office Neon Light", "#FF2E4182": "Official Violet", "#FFCAD8D8": "Offshore Mist", "#FFFF714E": "Often Orange", "#FFD7B235": "Ogen Melon", "#FFFD5240": "Ogre Odour", "#FF9DA94B": "Ogryn Camo", "#FFD1A14E": "Ogryn Wash", "#FFBBDAF8": "Oh Boy!", "#FFEDEEC5": "Oh Dahling", "#FFE3C81C": "Oh Em Ghee", "#FFEEBB55": "Oh My Gold", "#FFABCA99": "Oh Pistachio", "#FFF1C7D0": "Oh so Pink", "#FFEAC7CB": "Oh so Pretty", "#FFC74536": "Oh so Red", "#FF0180AB": "Oh so Vo", "#FF313330": "Oil Variant", "#FF678F8A": "Oil Blue", "#FF7B7F68": "Oil Green", "#FF3B3131": "Oil Grey", "#FFFF5511": "Oil on Fire", "#FF333144": "Oil Rush", "#FF031602": "Oil Slick", "#FFC2AB3F": "Oil Yellow", "#FF83BA8E": "Oilcloth Green", "#FF6C5A51": "Oiled Teak", "#FF996644": "Oiled up Kardashian", "#FFC2BE0E": "Oilseed Crops", "#FF2D0F47": "Oily Purple", "#FF99AAAA": "Oily Steel", "#FF5E644F": "Oitake Green", "#FFD07360": "OK Corral", "#FFCD4600": "\u014ckaihau Express", "#FFF5E0BA": "Oklahoma Wheat", "#FFBA7D4C": "Okonomiyaki Brown", "#FF3E912D": "Okra", "#FF40533D": "Okroshka", "#FFEFCB96": "Oktoberfest", "#FF87868F": "Old Amethyst", "#FF616652": "Old Army Helmet", "#FF929000": "Old Asparagus", "#FF769164": "Old Bamboo", "#FF029386": "Old Benchmark", "#FFDBC2AB": "Old Bone", "#FF7C644B": "Old Boot", "#FF5E624A": "Old Botanical Garden", "#FF8A3335": "Old Brick Variant", "#FF330000": "Old Brown Crayon", "#FFA8A89D": "Old Celadon", "#FFE3D6E9": "Old Chalk", "#FFDD6644": "Old Cheddar", "#FF704241": "Old Coffee", "#FF73503B": "Old Copper Variant", "#FF784430": "Old Cumin", "#FFBDAB9B": "Old Doeskin", "#FF97694F": "Old Driftwood", "#FFCDC4BA": "Old Eggshell", "#FF82A2BE": "Old Faithful", "#FFF4C6CC": "Old Fashioned Pink", "#FF73486B": "Old Fashioned Purple", "#FFF2B7B5": "Old Flame", "#FF757D43": "Old Four Leaf Clover", "#FFC66787": "Old Geranium", "#FF002868": "Old Glory Blue", "#FFBF0A30": "Old Glory Red", "#FF839573": "Old Green", "#FFB4B6AD": "Old Grey Mare", "#FFB35E1F": "Old Guitar", "#FF0063EC": "Old Gungeon Red", "#FFE66A77": "Old Heart", "#FFFFFFCB": "Old Ivory", "#FFEFF5DC": "Old Kitchen White", "#FFFDFC74": "Old Laser Lemon", "#FFA88B66": "Old Leather", "#FFAEC571": "Old Lime", "#FF4A0100": "Old Mahogany", "#FF8E2323": "Old Mandarin", "#FFD5C9BC": "Old Map", "#FF343B4E": "Old Mill", "#FF6E6F82": "Old Mill Blue", "#FFD8C2CA": "Old Mission Pink", "#FF2C5C4F": "Old Money", "#FF5E5896": "Old Nan Yarn", "#FFF6EBD7": "Old Pearls", "#FFC77986": "Old Pink", "#FFEBA770": "Old Plaster", "#FF745947": "Old Porch", "#FF8272A4": "Old Prune", "#FFD8CBCF": "Old Red Crest", "#FF917B53": "Old Ruin", "#FFBBA582": "Old Salem", "#FF353C3D": "Old School", "#FF918D80": "Old Soul", "#FFCFD5D1": "Old Sterling", "#FF431705": "Old Study", "#FFBB8811": "Old Trail", "#FF0A888A": "Old Truck", "#FF6C574C": "Old Tudor", "#FF687760": "Old Vine", "#FFDDAA55": "Old Whiskey", "#FF756947": "Old Willow Leaf", "#FF90091F": "Old Wine", "#FF91A8CF": "Old World", "#FFFEED9A": "Old Yella", "#FFECE6D7": "Old Yellow Bricks", "#FF8F6C3E": "Olde World Gold", "#FFEEB76B": "Olden Amber", "#FFD6B63D": "Oldies but Goldies", "#FFEBD5CC": "Ole Pink", "#FFC79E5F": "Ole Yeller", "#FFF2CCC5": "Oleander", "#FFF85898": "Oleander Pink", "#FF665439": "Oliva Oscuro", "#FF6E592C": "Olivary", "#FF808010": "Olive Variant", "#FF5F5537": "Olive Bark", "#FF957C4C": "Olive Boat", "#FF646A45": "Olive Branch", "#FFC3BEBB": "Olive Bread", "#FF645403": "Olive Brown", "#FFBCD382": "Olive Buff", "#FFA6997A": "Olive Chutney", "#FFE4E5D8": "Olive Conquering White", "#FF5F5D48": "Olive Court", "#FFE8ECC0": "Olive Creed", "#FF6F7632": "Olive Drab", "#FFBFAC8B": "Olive Gold", "#FF716A4D": "Olive Grove", "#FF888064": "Olive Haze Variant", "#FFC9BD88": "Olive Hint", "#FF4E4B35": "Olive Leaf", "#FFC7B778": "Olive Marinade", "#FFCED2AB": "Olive Martini", "#FF88432E": "Olive Ni\u00e7oise", "#FF565342": "Olive Night", "#FF837752": "Olive Ochre", "#FFBAB86C": "Olive Oil", "#FF83826D": "Olive Paste", "#FFA4A84D": "Olive Reserve", "#FFA39467": "Olive Rush", "#FF9ABF8D": "Olive Sand", "#FF7F7452": "Olive Sapling", "#FF7D7248": "Olive Shade", "#FF706041": "Olive Shadow", "#FF585545": "Olive Smudge", "#FF97A49A": "Olive Soap", "#FFACAF95": "Olive Sprig", "#FF6D8E2C": "Olive Thrill", "#FFEFEBD7": "Olive Tint", "#FF7E7957": "Olive Tint", "#FFABA77C": "Olive Tree", "#FF756244": "Olive Wood", "#FFC2B709": "Olive Yellow", "#FF333311": "Olivenite", "#FF747028": "Olivetone Variant", "#FF996622": "Olivia", "#FF655867": "Olivine Basalt", "#FF928E7C": "Olivine Grey", "#FFFFE6E2": "Olm Pink", "#FF84FBCE": "Olo", "#FF5A6647": "Olympia Ivy", "#FF1C4C8C": "Olympian Blue", "#FF4F8FE6": "Olympic Blue", "#FF9A724A": "Olympic Bronze", "#FF424C44": "Olympic Range", "#FFD4D8D7": "Olympus White", "#FF4A4E5D": "Ombre Blue", "#FF848998": "Ombre Grey", "#FF8CA891": "OMGreen", "#FFB5CEDF": "Omphalodes", "#FF019E91": "On a Whim", "#FFC2E7E8": "On Cloud Nine", "#FFD4C6DC": "On Location", "#FF948776": "On the Avenue", "#FF666D68": "On the Moor", "#FFB29AA7": "On the Nile", "#FFD0CEC8": "On the Rocks", "#FFD1E8F1": "On Thin Ice", "#FFC2E6EC": "Onahau Variant", "#FFBD2F10": "Once Bitten", "#FF0044BB": "Once in a Blue Moon", "#FF65362E": "One Dozen", "#FF003388": "One Minute", "#FFDCBDAD": "One", "#FF29465B": "One Year of Rain", "#FF48412B": "Onion Variant", "#FFECE2D4": "Onion Powder", "#FF47885E": "Onion Seedling", "#FFEEEDDF": "Onion Skin", "#FF4C5692": "Onion Skin Blue", "#FFE2D5C2": "Onion White", "#FFB0B5B5": "Online", "#FF468C3E": "Online Lime", "#FFE1BC99": "Only Natural", "#FFD4CDB5": "Only Oatmeal", "#FFCBCCB5": "Only Olive", "#FFF4D1B9": "Only Yesterday", "#FF66EEBB": "Onsen", "#FF777CB0": "Ontario Violet", "#FF464544": "Onyx Tint", "#FFDCBFD5": "Ooh la La", "#FFC2BEB6": "Ooid Sand", "#FFAEE0E4": "Opal Tint", "#FFFCEECE": "Opal Cream", "#FFE95C4B": "Opal Flame", "#FF157954": "Opal Green", "#FF9A9394": "Opal Grey", "#FF9DB9B2": "Opal Silk", "#FF96D1C3": "Opal Turquoise", "#FF7E8FBB": "Opal Violet", "#FFB1C6D1": "Opal Waters", "#FFB4C0B6": "Opalescence", "#FF3C94C1": "Opalescent", "#FFFFD2A9": "Opalescent Coral", "#FFC1D1C4": "Opaline", "#FFA3C57D": "Opaline Green", "#FFC6A0AB": "Opaline Pink", "#FFC7DFE0": "Open Air", "#FFF5F1E5": "Open Book", "#FFBBA990": "Open Canyon", "#FF91876B": "Open Range", "#FF83AFBC": "Open Seas", "#FFF8E2A9": "Open Sesame", "#FFD0E2E8": "Open Skies", "#FFBD6426": "Opening Night", "#FFBD273A": "Opening Ribbon", "#FF816575": "Opera", "#FF453E6E": "Opera Blue", "#FFE5F1EB": "Opera Glass", "#FF365360": "Opera Glasses", "#FFFF1B2D": "Opera Red", "#FF3A284C": "Operetta Mauve", "#FF987E7E": "Opium Variant", "#FF735362": "Opium Mauve", "#FFE9AB51": "Optimist Gold", "#FFF5E1A6": "Optimistic Yellow", "#FF465A7F": "Optimum Blue", "#FF130D0D": "Optophobia", "#FFD5892F": "Opulent", "#FF0055EE": "Opulent Blue", "#FF103222": "Opulent Green", "#FF88DD11": "Opulent Lime", "#FF462343": "Opulent Mauve", "#FFF2EBEA": "Opulent Opal", "#FFF16640": "Opulent Orange", "#FF775577": "Opulent Ostrich", "#FF673362": "Opulent Purple", "#FF88DDCC": "Opulent Turquoise", "#FFA09EC6": "Opulent Violet", "#FFCECAE1": "Opus", "#FFE3E1ED": "Opus Magnum", "#FF395555": "Oracle Variant", "#FFFAF8F7": "Orange Albedo", "#FFFF9682": "Orange Aura", "#FFFF8822": "Orange Avant-Garde", "#FFB56D41": "Orange Ballad", "#FFFF8844": "Orange Bell Pepper", "#FFF5C99B": "Orange Blast", "#FFB16002": "Orange Brown Variant", "#FFFF6E3A": "Orange Burst", "#FFFDD1A4": "Orange Canyon", "#FFDE954B": "Orange Caramel", "#FFFAD48B": "Orange Chalk", "#FFF9AE7D": "Orange Chiffon", "#FFF3C775": "Orange Chocolate", "#FFE6A57F": "Orange Clay", "#FFFF550E": "Orange Clown Fish", "#FFFBEBCF": "Orange Coloured White", "#FFF4E3D2": "Orange Confection", "#FFFFB710": "Orange Creamsicle", "#FFEE7733": "Orange Crush", "#FFDD6600": "Orange Danger", "#FFEB7D5D": "Orange Daylily", "#FFFFC355": "Orange Delight", "#FFE18E3F": "Orange Drop", "#FFD1907C": "Orange Essential", "#FFA96F55": "Orange Flambe", "#FFFE9E13": "Orange Fruit", "#FFFFCA7D": "Orange Glass", "#FFFFE2BD": "Orange Glow", "#FFEE7722": "Orange Gluttony", "#FFFBAF8D": "Orange Grove", "#FFFF9A45": "Orange Hibiscus", "#FFFFDEC1": "Orange Ice", "#FFFAC205": "Orange Jelly", "#FFFF9731": "Orange Jewel", "#FFCA5333": "Orange Keeper", "#FFBE7249": "Orange Lily", "#FFEDAA80": "Orange Liqueur", "#FFD3A083": "Orange Maple", "#FFFAAC72": "Orange Marmalade", "#FFFFBD8A": "Orange Mousse", "#FFCE8C4F": "Orange Nectar Bat", "#FFDA7631": "Orange Ochre", "#FFDD7700": "Orange Outburst", "#FFF49975": "Orange Pecan", "#FFCB8D5F": "Orange Pekoe", "#FFDA7D00": "Orange Pepper", "#FFFF6611": "Orange Pi\u00f1ata", "#FFFF6F52": "Orange Pink", "#FFFFBC3E": "Orange Pop", "#FFE68750": "Orange Poppy", "#FFFF7913": "Orange Popsicle", "#FFF2A60F": "Orange Pospsicle", "#FFFEBC61": "Orange Quench", "#FFFE4401": "Orange Red Variant", "#FFA85335": "Orange Roughy Variant", "#FFC05200": "Orange Rufous", "#FFF0B073": "Orange Salmonberry", "#FFDD9900": "Orange Satisfaction", "#FFFEC49B": "Orange Sherbet", "#FFE2D6BD": "Orange Shimmer", "#FFDD7744": "Orange Shot", "#FFF4915C": "Orange Slice", "#FFFAE1C7": "Orange Sparkle", "#FFFEA060": "Orange Spice", "#FFC27635": "Orange Squash", "#FFE8A320": "Orange Sulphur", "#FFFF7435": "Orange Supreme", "#FFFF8379": "Orange Tea Rose", "#FFF95C14": "Orange Tiger", "#FFFFB452": "Orange Toffee", "#FFBC5339": "Orange Vermillion", "#FFEAE3CD": "Orange White Variant", "#FFB74923": "Orange Wood", "#FFFDB915": "Orange Yellow Variant", "#FFFFA601": "Orange You Glad", "#FFFD7F22": "Orange You Happy?", "#FFF07227": "Orange Zest", "#FFE35435": "Orangeade", "#FFEE5511": "Orangealicious", "#FFE6BCA9": "Orangery", "#FFE88354": "Orangevale", "#FFE57059": "Orangeville", "#FFFEC615": "Orangina", "#FFFD8D49": "Orangish", "#FFB25F03": "Orangish Brown", "#FFF43605": "Orangish Red", "#FFEE6237": "Oranzhewyi Orange", "#FF772299": "Orb of Discord", "#FFEEDD44": "Orb of Harmony", "#FF6D83BB": "Orbital", "#FF220088": "Orbital Kingdom", "#FFD0CCC9": "Orca White", "#FF9A858C": "Orchard Plum", "#FFAE230E": "Orchestra of Red", "#FF7A81FF": "Orchid Variant", "#FFC3CCD5": "Orchid Ash", "#FFC5AECF": "Orchid Bloom", "#FFE4E1E4": "Orchid Blossom", "#FFD6CBC7": "Orchid Blush", "#FFD1ACCE": "Orchid Bouquet", "#FFAA55AA": "Orchid Dottyback", "#FFBB4488": "Orchid Ecstasy", "#FFC9C1D0": "Orchid Fragrance", "#FF5E5871": "Orchid Grey", "#FFB0879B": "Orchid Haze", "#FFC3BBD4": "Orchid House", "#FFE5E999": "Orchid Hue", "#FFCEC3D2": "Orchid Hush", "#FFE0D0DB": "Orchid Ice", "#FFAC74A4": "Orchid Kiss", "#FFE5DDE7": "Orchid Lane", "#FF9C4A7D": "Orchid Lei", "#FFE8E6E8": "Orchid Mist", "#FFFFA180": "Orchid Orange", "#FF876281": "Orchid Orchestra", "#FFBFB4CB": "Orchid Petal", "#FFAD878D": "Orchid Red", "#FFE9D1DA": "Orchid Rose", "#FFCBC5C2": "Orchid Shadow", "#FFE1CCDF": "Orchid Shimmer", "#FFCD899E": "Orchid Smoke", "#FFD3C9D4": "Orchid Tint", "#FFDDE0E8": "Orchid Whisper", "#FFF1EBD9": "Orchid White Variant", "#FF938EA9": "Orchilla", "#FF998188": "Ordain", "#FF1A4C32": "Order Green", "#FF1C3339": "Ore Bluish Black", "#FF2B6551": "Ore Mountains Green", "#FFFAEECB": "Orecchiette", "#FF7F8353": "Oregano", "#FF4DA241": "Oregano Green", "#FF8D8764": "Oregano Spice", "#FF448EE4": "Oregon Blue", "#FF49354E": "Oregon Grape", "#FF916238": "Oregon Hazel", "#FFEFB91B": "Oregon Trail", "#FFFFCDA8": "Orenju Ogon Koi", "#FF9E9B85": "Orestes", "#FF747261": "Organic", "#FFE1CDA4": "Organic Bamboo", "#FFFEEDE0": "Organic Fibre", "#FFC6C2AB": "Organic Field", "#FFABA964": "Organic Garden", "#FF7FAC6E": "Organic Green", "#FFA99E54": "Organic Matter", "#FFA67E3E": "Organic Style", "#FFFFDEA6": "Organza", "#FFBBCCBD": "Organza Green", "#FFFBEEDA": "Organza Peach", "#FF7391CC": "Organza Violet", "#FFECE0C6": "Origami", "#FFE1E6C7": "Origami Clover", "#FFE5E2DA": "Origami White", "#FFF0E5D3": "Original White", "#FFD2D3B3": "Orinoco Variant", "#FFFF8008": "Oriole", "#FFF6D576": "Oriole Yellow", "#FFFB4F14": "Orioles Orange", "#FFDE55A9": "Orion", "#FF40525F": "Orion Blue", "#FF535558": "Orion Grey", "#FFA55AF4": "Orion Nebula", "#FF27221F": "Orka Black", "#FF3E5755": "Orkhide Shade", "#FFD91407": "Orko", "#FF97D5E7": "Orleans Tune", "#FF00867D": "Ornamental Turquoise", "#FF806D95": "Ornate", "#FFF77D25": "Ornery Tangerine", "#FFC29436": "Oro", "#FFD9D8DA": "Orochimaru", "#FFD17C3F": "Orpiment Orange", "#FFF9C89B": "Orpiment Yellow", "#FFBE855E": "Orpington Chicken", "#FFF9EACC": "Orzo Pasta", "#FFF4A045": "Osage Orange", "#FF5B5A4D": "Osiris", "#FFA6BDBE": "Oslo Blue", "#FF63564B": "Osprey", "#FFCCBAB1": "Osprey Nest", "#FFAD9769": "Osso Bucco", "#FFE9E3D5": "Ostrich", "#FFDCD0BB": "Ostrich Egg", "#FFEADFE6": "Ostrich Tail", "#FF665D59": "Oswego Tea", "#FFFF4E20": "\u014ctan Red", "#FF633D38": "Otis Madeira", "#FF00A78D": "Ottawa Falls", "#FF7F674F": "Otter", "#FF654320": "Otter Brown", "#FF3F5A5D": "Otter Creek", "#FF938577": "Otter Tail", "#FF8C4512": "Otterly Brown", "#FFBEDFD3": "Otto Ice", "#FFD3DBCB": "Ottoman Variant", "#FFEE2222": "Ottoman Red", "#FF4F4944": "Oubliette", "#FFEE7948": "Ouni Red", "#FFA84B7A": "Our Little Secret", "#FFF26D8F": "Out of Fashion", "#FF2CED2B": "Out of Left Field", "#FF9C909C": "Out of Plumb", "#FF1199EE": "Out of the Blue", "#FFC9A375": "Outback", "#FF7E5D47": "Outback Brown", "#FF8D745E": "Outdoor Cafe", "#FFA07D5E": "Outdoor Land", "#FF6E6F4D": "Outdoor Oasis", "#FFB2974D": "Outdoorsy", "#FF654846": "Outer Boundary", "#FF2A6295": "Outer Reef", "#FF221177": "Outer Rim", "#FF314E64": "Outer Space Variant", "#FFB7A48B": "Outerbanks", "#FFE6955F": "Outgoing Orange", "#FFB67350": "Outlawed Orange", "#FF824438": "Outrageous", "#FF8AB733": "Outrageous Green", "#FF82714D": "Outrigger", "#FF9EB2B9": "Ovation", "#FF4D6D08": "Over the Hills", "#FFABB8D5": "Over the Moon", "#FF98D5EA": "Over the Sky", "#FFB09D8A": "Over the Taupe", "#FF61311C": "Overbaked", "#FF005555": "Overboard", "#FF73A3D0": "Overcast", "#FFB3583D": "Overcast Brick", "#FF8F99A2": "Overcast Day", "#FF42426F": "Overcast Night", "#FFA7B8C4": "Overcast Sky", "#FF0AE9B4": "Overchlorinated Pool", "#FF4400FF": "Overdue Blue", "#FFC7C3BE": "Overdue Grey", "#FFEFF4DC": "Overexposed Shot", "#FF88DD00": "Overgrown", "#FF888844": "Overgrown Citadel", "#FF8B8265": "Overgrown Granite", "#FF448833": "Overgrown Mausoleum", "#FF116611": "Overgrown Temple", "#FF6B6048": "Overgrown Trees", "#FF6A8988": "Overgrown Trellis", "#FF88CC33": "Overgrowth", "#FFEEC25F": "Overjoy", "#FF717481": "Overlook", "#FFFBF0DB": "Overnight Oats", "#FF97A554": "Overt Green", "#FF33557F": "Overtake", "#FFA4E3B3": "Overtone", "#FF8C7E49": "Ovoid Fruit", "#FFC0AF87": "Owl Manner Malt", "#FF90845F": "Owlet", "#FFC1E28A": "Oxalis", "#FF71383F": "Oxblood Red", "#FFB1BBC5": "Oxford", "#FF743B39": "Oxford Brick", "#FF504139": "Oxford Brown", "#FF001B2E": "Oxford by Night", "#FFDB7192": "Oxford Sausage", "#FFBDA07F": "Oxford Street", "#FFB8A99A": "Oxford Tan", "#FFBF7657": "Oxide", "#FF6D9A78": "Oxley Variant", "#FF92B6D5": "Oxygen Blue", "#FFE3D3BF": "Oyster", "#FFDBD0BB": "Oyster Bar", "#FF71818C": "Oyster Bay Variant", "#FFF2E5B1": "Oyster Bisque", "#FF4A4C45": "Oyster Catch", "#FFF4F0D2": "Oyster Cracker", "#FFCBC1AE": "Oyster Grey", "#FFE4DED2": "Oyster Haze", "#FFEFEFE5": "Oyster Island", "#FFB1AB96": "Oyster Linen", "#FFB8BCBE": "Oyster Mushroom", "#FFF5E6CC": "Oyster Omelette Cream", "#FFD4B5B0": "Oyster Pink Variant", "#FFBFB3A1": "Oyster Shoal", "#FFCBC4A2": "Oyster White", "#FF8B95A2": "Ozone", "#FFC7D3E0": "Ozone Blue", "#FF5E3A39": "Pa Red", "#FF864B36": "Paarl Variant", "#FF7A715C": "Pablo Variant", "#FFFFE737": "Pac-Man", "#FFECDFAD": "Paccheri", "#FFE5DDD0": "Pacer White", "#FF8F989D": "Pachyderm", "#FF24646B": "Pacific", "#FF96ACB8": "Pacific Bliss", "#FFC3A285": "Pacific Bluffs", "#FFC1DBE7": "Pacific Breeze", "#FF0052CC": "Pacific Bridge", "#FF5E85B1": "Pacific Coast", "#FF004488": "Pacific Depths", "#FFDCDCD5": "Pacific Fog", "#FF77B9DB": "Pacific Harbour", "#FF2D3544": "Pacific Line", "#FFCDD5D3": "Pacific Mist", "#FF25488A": "Pacific Navy", "#FF92CBF1": "Pacific Ocean", "#FF69A4B9": "Pacific Palisade", "#FFC0D6EA": "Pacific Panorama", "#FFE8EAE6": "Pacific Pearl", "#FF546B45": "Pacific Pine", "#FF167D97": "Pacific Pleasure", "#FF026B5D": "Pacific Queen", "#FFF1EBCD": "Pacific Sand", "#FF3E8083": "Pacific Sea Teal", "#FF3C4A56": "Pacific Spirit", "#FF035453": "Pacific Storm", "#FF4E77A3": "Pacifica", "#FFB7ACA0": "Packed Sand", "#FFBA9B5D": "Packing Paper", "#FF4F4037": "Paco Variant", "#FF859E94": "Padded Leaf", "#FF88724D": "Paddle Wheel", "#FFDA9585": "Paddy", "#FF99BB44": "Paddy Field", "#FF7EB394": "Padua Variant", "#FFDCC61F": "Paella", "#FFE1D7C2": "Paella Natural White", "#FF99DAC5": "Pageant Green", "#FFB6C3D1": "Pageant Song", "#FF68447C": "Pageantry Purple", "#FF127E93": "Pagoda", "#FF1B8192": "Pagoda Blue", "#FF8C8E65": "Paid in Full", "#FF6B4947": "Painite", "#FF11EEFF": "Paint the Sky", "#FFDDBA8F": "Painted Ash", "#FF5F3D32": "Painted Bark", "#FFEB8F6F": "Painted Clay", "#FFBEB8B6": "Painted Desert", "#FF6D544F": "Painted Leather", "#FFBB9471": "Painted Pony", "#FFCB5139": "Painted Poppy", "#FF008595": "Painted Sea", "#FFB28774": "Painted Skies", "#FF56745F": "Painted Turtle", "#FFF9F2DE": "Painter\u2019s Canvas", "#FFF2EBDD": "Painter\u2019s White", "#FF726F7E": "Paisley", "#FF8B79B1": "Paisley Purple", "#FFAD7362": "Pajarito Red", "#FF43456D": "Palace Arms", "#FF3973C0": "Palace Blue", "#FF426255": "Palace Green", "#FF799AAA": "Palace Intrigue", "#FF68457A": "Palace Purple", "#FF752745": "Palace Red", "#FFF8CAD5": "Palace Rose", "#FFA7A6B1": "Palace Walls", "#FFF4F0E5": "Palais White", "#FF888811": "Palak Paneer", "#FFEEDCD1": "Palatial", "#FFF9F2E4": "Palatial White", "#FFC9C7B6": "Palatine", "#FFFFF9D0": "Pale", "#FFF4E9BA": "Pale Adobe", "#FFFEF068": "Pale Ale", "#FFBCD4E1": "Pale Aqua", "#FFC9BFA8": "Pale Bamboo", "#FFF5E28D": "Pale Banana", "#FFCCC7B1": "Pale Beige", "#FFE2CCC7": "Pale Berries", "#FFE39E9C": "Pale Berry", "#FF98DED9": "Pale Beryl", "#FF4A475C": "Pale Blackish Purple", "#FFF0EAE1": "Pale Bloom", "#FFFDE1F0": "Pale Blossom", "#FFD0FEFE": "Pale Blue", "#FFA2ADB1": "Pale Blue Grey", "#FFDFAFA4": "Pale Blush", "#FFB1916E": "Pale Brown Variant", "#FFEEEBE8": "Pale Bud", "#FFF1EFA6": "Pale Canary Variant", "#FFE8DFD5": "Pale Cashmere", "#FFC9CBBE": "Pale Celadon", "#FFE9E9C7": "Pale Celery", "#FFEFE5D7": "Pale Chamois", "#FFFDEFF2": "Pale Cherry Blossom", "#FFDADEE9": "Pale Cloud", "#FFF0D0B4": "Pale Coral", "#FFAD6E44": "Pale Cordovan", "#FFCED9E1": "Pale Cornflower", "#FFD6D5BC": "Pale Cucumber", "#FFFDE89A": "Pale Daffodil", "#FFECCCC1": "Pale Dogwood Variant", "#FFFDE8D0": "Pale Egg", "#FFE7D1DD": "Pale Flamingo", "#FF698AAB": "Pale Flower", "#FFC7E1EE": "Pale Frost", "#FFEADDCA": "Pale Gingersnap", "#FFFDDE6C": "Pale Gold", "#FFC0A2C7": "Pale Grape", "#FF69B076": "Pale Green Variant", "#FF96907E": "Pale Green Grey", "#FFE2E2D2": "Pale Green Tea", "#FFFDFDFE": "Pale Grey", "#FFD4E2EB": "Pale Grey Blue", "#FFE7D8EA": "Pale Grey Magenta", "#FFF5D6AA": "Pale Honey", "#FF8895C5": "Pale Iris", "#FFD4CFB2": "Pale Ivy", "#FF77C3B4": "Pale Jade", "#FFFED6CC": "Pale Jasper", "#FF998877": "Pale Khaki", "#FFABF5ED": "Pale King\u2019s Blue", "#FFBDCAA8": "Pale Leaf Variant", "#FFD8D4BF": "Pale Lichen", "#FFB1FC99": "Pale Light Green", "#FFD8B5BF": "Pale Lilac", "#FFF3ECE7": "Pale Lily", "#FFB1FF65": "Pale Lime Green", "#FFDFE497": "Pale Lime Yellow", "#FFEBE5D6": "Pale Linen", "#FFCCD2CA": "Pale Loden", "#FFC4ACB2": "Pale Lychee", "#FFF5E1BA": "Pale Maize", "#FFFFBB44": "Pale Marigold", "#FFC6A4A4": "Pale Mauve", "#FFAAC2A1": "Pale Mint", "#FFDCC797": "Pale Moss", "#FFD0DBC4": "Pale Moss Green", "#FFBAE1D3": "Pale Mountain Lake Turquoise", "#FFE4DDE7": "Pale Muse", "#FFFAF5E2": "Pale Narcissus", "#FFDCE0CF": "Pale Oak Grove", "#FFD3C7A1": "Pale Olive", "#FFDEDBE5": "Pale Orchid", "#FFF6E2EC": "Pale Orchid Petal", "#FFFDEBBC": "Pale Organza", "#FF9C8D72": "Pale Oyster Variant", "#FFE5DBCA": "Pale Palomino", "#FFD1C3AD": "Pale Parchment", "#FFE3D8BF": "Pale Parsnip", "#FF9ADEDB": "Pale Pastel", "#FFFFE5AD": "Pale Peach", "#FFF4DA6E": "Pale Pear", "#FFFFF2DE": "Pale Pearl", "#FFF7E0DE": "Pale Perfection", "#FFC8D2E2": "Pale Periwinkle", "#FFD4ACAD": "Pale Persimmon", "#FFD9BFCE": "Pale Petals", "#FFBA9BA5": "Pale Petticoat", "#FFF8C0C7": "Pale Petunia", "#FFCCD5FF": "Pale Phthalo Blue", "#FFEFCDDB": "Pale Pink Variant", "#FFE3E7D1": "Pale Pistachio", "#FFBCA8AD": "Pale Poppy", "#FFEEC8D3": "Pale Primrose", "#FFB790D4": "Pale Purple", "#FFEFEADA": "Pale Quartz", "#FFEBEBD7": "Pale Rebelka Jakub", "#FFEFD6DA": "Pale Rose Variant", "#FFACBDA1": "Pale Sage", "#FFD3D1B9": "Pale Sagebrush", "#FFE5D5BA": "Pale Sand", "#FFF0BCAD": "Pale Satin Peach", "#FFC3E7E8": "Pale Seafoam", "#FFCACFDC": "Pale Shale", "#FFF8DBD6": "Pale Shrimp", "#FFDFC7BC": "Pale Sienna", "#FFBDF6FE": "Pale Sky Variant", "#FF9FABAD": "Pale Slate Variant", "#FFB3BE98": "Pale Spring Morning", "#FFE4DED8": "Pale Starlet", "#FFF2C880": "Pale Sunshine", "#FF82CBB2": "Pale Teal", "#FFCECDBB": "Pale Tendril", "#FFEAAA96": "Pale Terra", "#FFC8DCBB": "Pale Tidepool", "#FFA5FBD5": "Pale Turquoise Variant", "#FF6F9892": "Pale Verdigris", "#FFC6C3D6": "Pale Violet", "#FFD8DECF": "Pale Vista", "#FFB6D3DF": "Pale Whale", "#FFD9C29F": "Pale Wheat", "#FF89AB98": "Pale Willow", "#FFBBC8E6": "Pale Wisteria", "#FFEAD2A2": "Pale Wood", "#FFF4EED1": "Palest of Lemon", "#FFDCCDB9": "Palest Satin", "#FFC3B497": "Palisade", "#FFAF8EA5": "Palisade Orchid", "#FFF7ECE1": "Palish Peach", "#FFEEE9DF": "Palladian", "#FFB1B1B1": "Palladium", "#FF314A4E": "Pallasite Blue", "#FFB3CDD4": "Pallid Blue", "#FFC1E0C1": "Pallid Green", "#FFCBDCB7": "Pallid Light Green", "#FFFCB99D": "Pallid Orange", "#FFF3DFDB": "Pallid Rose", "#FFCDCEBE": "Pallid Wych", "#FFAFAF5E": "Palm", "#FFDBE2C9": "Palm Breeze", "#FFAEAD5B": "Palm Frond", "#FF20392C": "Palm Green Variant", "#FFDDD8C2": "Palm Heart Cream", "#FF7A7363": "Palm Lane", "#FF36482F": "Palm Leaf Variant", "#FF20887A": "Palm Springs Splash", "#FFEDD69D": "Palm Sugar Yellow", "#FF74B560": "Palm Tree", "#FF577063": "Palmerin", "#FF6D9A9B": "Palmetto", "#FFCEB993": "Palmetto Bluff", "#FFEAEACF": "Palmito", "#FF5F6356": "Palo Verde", "#FF9F9C99": "Paloma", "#FFE9B679": "Paloma Tan", "#FFBB7744": "Palomino", "#FFDAAE00": "Palomino Gold", "#FFE6D6BA": "Palomino Mane", "#FF837871": "Palomino Pony", "#FFC2AA8D": "Palomino Tan", "#FFEAE4DC": "Pampas Variant", "#FFF5EAEB": "Pampered Princess", "#FFDCA356": "Pan de Coco", "#FF657AEF": "Pan Purple", "#FFE8BE99": "Pan Tostado", "#FFCFD0C5": "Pan\u2019s Flute", "#FFDDDBC0": "Panacea", "#FFEBF7E4": "Panache Variant", "#FFEDC9D5": "Panache Pink", "#FFC6577C": "Panama Rose", "#FFF7D788": "Pancake", "#FFDFB992": "Pancho Variant", "#FFBFDB89": "Pancotto Pugliese", "#FF544F3A": "Panda", "#FF3C4748": "Panda Black", "#FFEAE2D4": "Panda White", "#FF9FB057": "Pandan Cake", "#FF616C44": "Pandanus", "#FFA1A4AE": "Pandemonium", "#FF71689A": "Pandora", "#FFE3D4CF": "Pandora Grey", "#FFFEDBB7": "Pandora\u2019s Box", "#FF9B5227": "Panela", "#FF4E4F6A": "Pango Black", "#FFF4AA53": "Pani Puri", "#FFCDB9A7": "Panko", "#FF7895CC": "Pannikin", "#FF327A88": "Panorama", "#FF35BDC8": "Panorama Blue", "#FF7F8FCA": "Pansy Garden", "#FF5F4561": "Pansy Petal", "#FFBFA7B6": "Pansy Posie", "#FFBCA3B4": "Pansy Posy", "#FFADAFBA": "Pantomime", "#FF5A4A64": "Paparazzi", "#FFC6CBD1": "Paparazzi Flash", "#FFFE985C": "Papaya", "#FFFCA07F": "Papaya Punch", "#FFFFEAC5": "Papaya Sorbet", "#FFFFD1AF": "Papaya Whip Variant", "#FFD7AC7F": "Paper Brown", "#FFF0E5C7": "Paper Daisy", "#FFD6C5A9": "Paper Dog", "#FFC5D0E6": "Paper Elephant", "#FFB1A99F": "Paper Goat", "#FFCC4466": "Paper Hearts", "#FFF2EBE1": "Paper Lamb", "#FFF2E0C4": "Paper Lantern", "#FFEAD4A6": "Paper Moon", "#FFF1ECE0": "Paper Plane", "#FFB4A07A": "Paper Sack", "#FFFDF1AF": "Paper Tiger", "#FFF6EFDF": "Paper White", "#FF249148": "Paperboy\u2019s Lawn", "#FFEFEADC": "Papier Blanc", "#FF8590AE": "Papilio Argeotus", "#FFF9EBCC": "Pappardelle Noodle", "#FF7C2D37": "Paprika Variant", "#FFC24325": "Paprika Kisses", "#FF999911": "Papyrus", "#FFC0AC92": "Papyrus Map", "#FFF5EDD6": "Papyrus Paper", "#FF507069": "Par Four", "#FFBEB755": "Parachute", "#FF362852": "Parachute Purple", "#FFFFE2B5": "Parachute Silk", "#FF00589B": "Parachuting", "#FFDEF1EA": "Paradise", "#FFFF8C55": "Paradise Bird", "#FF5F7475": "Paradise City", "#FF83988C": "Paradise Found", "#FF746565": "Paradise Grape", "#FFA3E493": "Paradise Green", "#FF5AA7A0": "Paradise Island", "#FF009494": "Paradise Landscape", "#FF398749": "Paradise of Greenery", "#FF006622": "Paradise Palms", "#FFE4445E": "Paradise Pink", "#FF66C6D0": "Paradise Sky", "#FF8DDEE1": "Paradisiacal Getaway", "#FF488084": "Paradiso Variant", "#FFA99A8A": "Parador Inn", "#FF908D86": "Parador Stone", "#FF78AE48": "Parakeet", "#FF7EB6FF": "Parakeet Blue", "#FFCBD3C6": "Parakeet Pete", "#FF5B6161": "Paramount", "#FFE4E2D5": "Paramount White", "#FF007D7C": "Parasailing", "#FF914B13": "Parasite Brown", "#FFE9DFDE": "Parasol", "#FF824A53": "Parauri Brown", "#FFFEFCAF": "Parchment Variant", "#FFF0E7D8": "Parchment Paper", "#FFF9EAE5": "Parchment White", "#FFC8A6A1": "Parfait", "#FF91A7BC": "Paris", "#FFB7DDED": "Paris Blue", "#FF888873": "Paris Creek", "#FFFBEB50": "Paris Daisy Variant", "#FF50C87C": "Paris Green", "#FFA6B7C8": "Paris Grey", "#FF312760": "Paris M Variant", "#FFC9C79A": "Paris Mint", "#FF737274": "Paris Paving", "#FFDA6D91": "Paris Pink", "#FFBFCDC0": "Paris White Variant", "#FF4F7CA4": "Parisian Blue", "#FF978478": "Parisian Caf\u00e9", "#FFD1C7B8": "Parisian Cashmere", "#FF6B9C42": "Parisian Green", "#FF323341": "Parisian Night", "#FF7D9B89": "Parisian Patina", "#FF787093": "Parisian Violet", "#FF465048": "Park Avenue", "#FF537F6C": "Park Bench", "#FF88C9A6": "Park Green Flat", "#FF428F46": "Park Picnic", "#FF46483E": "Parkview", "#FF477BBD": "Parkwater", "#FF465F7E": "Parlour Blue", "#FFA12D5D": "Parlour Red", "#FFBAA1B2": "Parlour Rose", "#FFC8C197": "Parlour Sage", "#FFD4C6AF": "Parlour Taupe", "#FF806E85": "Parma Grey", "#FFF89882": "Parma Ham", "#FF5F5680": "Parma Mauve", "#FF5E3958": "Parma Plum Red", "#FF55455A": "Parma Violet", "#FF887CAB": "Parmentier", "#FFFFFFDD": "Parmesan", "#FFA9A237": "Parrot Feather", "#FF018DA1": "Parrot Flight", "#FF8DB051": "Parrot Green", "#FFD998A0": "Parrot Pink", "#FFEEBFD5": "Parrot Tulip", "#FFF8DC74": "Parrot Waxcap", "#FF028FC4": "Parrotfish Blue", "#FF305D35": "Parsley Variant", "#FF5A9F4D": "Parsley Green", "#FF3D7049": "Parsley Sprig", "#FFD6C69A": "Parsnip", "#FF9D892E": "Parsnip Root", "#FFFFEDF8": "Partial Pink", "#FFDEF3E6": "Particle Cannon", "#FFCB3215": "Particle Ioniser Red", "#FFD0D2C5": "Particular Mint", "#FF9DBBCD": "Partly Cloudy", "#FF844C44": "Partridge", "#FF919098": "Partridge Grey", "#FFA9875B": "Partridge Knoll", "#FFCAC1E2": "Party Hat", "#FFE6E4DE": "Party Ice", "#FFEE99FF": "Party Pig", "#FFEEDF91": "Party Sponge Cake", "#FFE3A9C4": "Party Time", "#FFA84A49": "Pasadena Rose", "#FF929178": "Paseo Verde", "#FFC3B7A4": "Pasha Brown", "#FF44413B": "Pasilla Chiles", "#FFB9BD97": "Paspalum Grass", "#FF5D98B3": "Pass Time Blue", "#FF647B86": "Passageway", "#FFEED786": "Passementerie", "#FFCBCAC0": "Passing Shadow", "#FF6F5698": "Passion Flower", "#FFDD0D06": "Passion for Revenge", "#FF907895": "Passion Fruit", "#FFE8AA9D": "Passion Fruit Punch", "#FFE57681": "Passion Pink", "#FFE398AF": "Passion Potion", "#FF59355E": "Passion Razz", "#FF725062": "Passionate", "#FF1F3465": "Passionate Blue", "#FF334159": "Passionate Blueberry", "#FFEDEFCB": "Passionate Pause", "#FFDD00CC": "Passionate Pink", "#FF753A58": "Passionate Plum", "#FF882299": "Passionate Purple", "#FF513E49": "Passionfruit Mauve", "#FFCBCCC9": "Passive", "#FFDBA29E": "Passive Pink", "#FF795365": "Passive Royal", "#FFF7DFAF": "Pasta", "#FFFAE17F": "Pasta Luego", "#FFEEC474": "Pasta Rasta", "#FFA2BFFE": "Pastel Blue Variant", "#FFF0E4E0": "Pastel China", "#FFDFD8E1": "Pastel Day", "#FFF2C975": "Pastel de Nata", "#FFBCCBB9": "Pastel Grey Green", "#FFD2F0E0": "Pastel Jade", "#FFD8A1C4": "Pastel Lavender", "#FFBDB0D0": "Pastel Lilac", "#FFB9D786": "Pastel Lime", "#FFA9C9AF": "Pastel Meadow", "#FFCEF0CC": "Pastel Mint", "#FFADD0B3": "Pastel Mint Green", "#FFFFF5D9": "Pastel Moon Creme", "#FFFF964F": "Pastel Orange Variant", "#FFDBCCC3": "Pastel Parchment", "#FFBEE7A5": "Pastel Pea", "#FFF1CAAD": "Pastel Peach", "#FFE4C5B0": "Pastel Rose Tan", "#FFD5C6B4": "Pastel Sand", "#FFDEECE1": "Pastel Smirk", "#FFEF4F4C": "Pastel Strawberry", "#FF99C5C4": "Pastel Turquoise", "#FFEDFAD9": "Pastoral", "#FFE87175": "Pastrami", "#FFF8DEB8": "Pastry", "#FFFAEDD5": "Pastry Dough", "#FFF8E1D0": "Pastry Pink", "#FFB77D58": "Pastry Shell", "#FF506351": "Pasture Green", "#FF3887BD": "Pat-A-Pep", "#FF225511": "Patch of Land", "#FF8A7D6B": "Patches", "#FFC4A89E": "Patchwork Pink", "#FF7E696A": "Patchwork Plum", "#FFC7C7C6": "Paternoster", "#FFC4EEE8": "Path", "#FFDBD6D2": "Pathway", "#FFE6DDD6": "Patience", "#FFFFABA5": "Patient Pink", "#FFEDE2DE": "Patient White", "#FF66D0C0": "Patina Variant", "#FFB6C4BD": "Patina Creek", "#FFB6C0BC": "Patina Grey", "#FF83BAA8": "Patina Hue", "#FF695A67": "Patina Violet", "#FF3F5A50": "Patio Green", "#FF6B655B": "Patio Stone", "#FFEDDBC8": "Patisserie", "#FF800070": "Patriarch Variant", "#FF8CD9A1": "Patrice", "#FF72527F": "Patrician Purple", "#FFD9B611": "Patrinia Flowers", "#FFF2F2B0": "Patrinia Scabiosaefolia", "#FF3C3C5F": "Patriot Blue", "#FFD3E5EF": "Pattens Blue Variant", "#FFBCC6B1": "Pattipan", "#FFEDB80F": "Pattypan", "#FF2A2551": "P\u0101ua", "#FF245056": "P\u0101ua Shell", "#FF80A4CD": "Paula Loves Paris", "#FF629191": "Pauley", "#FF343445": "Pauper", "#FF828782": "Paved Path", "#FFE8D284": "Paved With Gold", "#FF554F53": "Pavement", "#FFC9C4BA": "Pavestone", "#FFBEBF84": "Pavilion", "#FFC5B6A4": "Pavilion Beige", "#FFDF9C45": "Pavilion Peach", "#FFEDE4D4": "Pavillion", "#FFCBCCC4": "Paving Stones", "#FFBAAB87": "Pavlova Variant", "#FFFBD49C": "Paw Paw", "#FF827A6D": "Paw Print", "#FF473430": "Pawn Broker", "#FFC8C6DA": "Pax", "#FF70916C": "Paying Mantis", "#FF002D04": "PCB Green", "#FFA4BF20": "Pea", "#FF7C9865": "Pea Aubergine", "#FF929901": "Pea Soup", "#FF3F7074": "Peabody", "#FFA2B2BD": "Peace", "#FFE0DAC8": "Peace & Quiet", "#FFC1875F": "Peace of Mind", "#FFA8BFCC": "Peace River", "#FFEECF9E": "Peace Yellow", "#FFDDCCAC": "Peaceable Kingdom", "#FF9AB6C0": "Peaceful Blue", "#FF878E83": "Peaceful Glade", "#FFD6E7E3": "Peaceful Night", "#FF94D8AC": "Peaceful Pastures", "#FF579797": "Peaceful Pond", "#FF660088": "Peaceful Purple", "#FFF1FBF1": "Peaceful Rain", "#FF47A0D2": "Peaceful River", "#FFFFB07C": "Peach Variant", "#FFFB9F93": "Peach Amber", "#FFFFCCB6": "Peach and Quiet", "#FFEFC4BB": "Peach Ash", "#FFFDCFA1": "Peach Beach", "#FFE7C3AB": "Peach Beauty", "#FFCD9489": "Peach Beige", "#FFFEDCAD": "Peach Bellini", "#FFD89A78": "Peach Bloom", "#FFDC7A83": "Peach Blossom", "#FFEECFBF": "Peach Blossom Red", "#FFDCBEB5": "Peach Blush", "#FFFFECE5": "Peach Breeze", "#FFE5CCBD": "Peach Brick", "#FFFDB2AB": "Peach Bud", "#FFCC99BB": "Peach Buff", "#FFF39998": "Peach Burst", "#FFFFAC3A": "Peach Butter", "#FFC56A3D": "Peach Caramel", "#FFFFD9AA": "Peach Cider", "#FFFCE2D8": "Peach Cloud", "#FFFFA167": "Peach Cobbler", "#FFFFCBA7": "Peach Crayon", "#FFFFE19D": "Peach Cr\u00e8me Br\u00fbl\u00e9e", "#FFF6C4A6": "Peach Damask", "#FFEFCDB4": "Peach Darling", "#FFF4DEBF": "Peach Dip", "#FFB3695F": "Peach Dunes", "#FFF0D8CC": "Peach Dust", "#FFF6725C": "Peach Echo", "#FFFEBC96": "Peach Encounter", "#FFF4E2D4": "Peach Everlasting", "#FFFCE9D6": "Peach Fade", "#FFFFA883": "Peach Fizz", "#FFE198B4": "Peach Flower", "#FFF88435": "Peach Fury", "#FFFFC7B9": "Peach Fuzz", "#FFEBD7D3": "Peach Glamour", "#FFFFDCAC": "Peach Glow", "#FFDFA479": "Peach Ice Cream", "#FFFFCFAB": "Peach Juice", "#FFFBE2CD": "Peach Kiss", "#FFE7C19F": "Peach Latte", "#FFC67464": "Peach Macaron", "#FFFBBDAF": "Peach Melba", "#FFF4A28C": "Peach Mimosa", "#FFFFAE96": "Peach Nectar", "#FFEDB48F": "Peach Nirvana", "#FFE3A385": "Peach Nougat", "#FFFFE2B4": "Peach of Mind", "#FFF6B895": "Peach Parfait", "#FFF3D5A1": "Peach Patch", "#FFFFB2A5": "Peach Pearl", "#FFD3B699": "Peach Petals", "#FFFF9A8A": "Peach Pink", "#FFDDAAAA": "Peach Poppy", "#FFE38372": "Peach Posey", "#FFE2BDB3": "Peach Powder", "#FFD29487": "Peach Preserve", "#FFF59997": "Peach Punch", "#FFEED0B6": "Peach Pur\u00e9e", "#FFF4B087": "Peach Quartz", "#FFF9CDC4": "Peach Red", "#FFF6E3D5": "Peach Rose", "#FFF6D9C9": "Peach Sachet", "#FFFFBCBC": "Peach Scone", "#FFF3DFD4": "Peach Shortcake", "#FFFFE5BD": "Peach Smoothie", "#FFECBCB2": "Peach Souffle", "#FFEC9D75": "Peach Squared", "#FFF3E3D1": "Peach Surprise", "#FFF3B68E": "Peach Taffy", "#FFFFB559": "Peach Tea", "#FFF2C5B2": "Peach Temptation", "#FFFFDC8D": "Peach Tickle", "#FFEFA498": "Peach Tile", "#FFF2E3DC": "Peach Tone", "#FFF9E8CE": "Peach Umbrella", "#FFF7B28B": "Peach Velour", "#FFD8B6B0": "Peach Whip", "#FFF9BAB5": "Peach Whisper", "#FFFD9B88": "Peach\u2019s Daydream", "#FFFADFC7": "Peachade", "#FFFFA361": "Peaches \u00e0 la Cr\u00e8me", "#FFD98586": "Peaches of Immortality", "#FFEEC9A6": "Peaches\u2019n\u2019Cream", "#FFDDB3B2": "Peachskin", "#FFF3DDCD": "Peachtree", "#FFFFA67A": "Peachy", "#FFFFD2B9": "Peachy Bon-Bon", "#FFFFC996": "Peachy Breezes", "#FFD4A88D": "Peachy Confection", "#FFFDE0DC": "Peachy Ethereal", "#FFED8666": "Peachy Feeling", "#FFFFDEDA": "Peachy Keen", "#FFE8956B": "Peachy Maroney", "#FFF3E0D8": "Peachy Milk", "#FFFFCCAA": "Peachy Pico", "#FFFF775E": "Peachy Pinky", "#FFFF9B80": "Peachy Salmon", "#FFFFDCB7": "Peachy Sand", "#FFDD7755": "Peachy Scene", "#FFF0CFA0": "Peachy Skin", "#FFFFD9A8": "Peachy Summer Skies", "#FFE3A381": "Peachy Tint", "#FFF1BF92": "Peachy-Kini", "#FF2D3146": "Peacoat", "#FF016795": "Peacock Blue", "#FF12939A": "Peacock Feather", "#FF006A50": "Peacock Green", "#FF2E4B44": "Peacock House", "#FF206D71": "Peacock Plume", "#FF006663": "Peacock Pride", "#FF513843": "Peacock Purple", "#FF6DA893": "Peacock Silk", "#FF01636D": "Peacock Tail", "#FF719E8A": "Peahen", "#FF768083": "Peak Point", "#FFFFDFC9": "Peak Season", "#FF7A4434": "Peanut Variant", "#FFA6893A": "Peanut Brittle", "#FFBE893F": "Peanut Butter", "#FFF7B565": "Peanut Butter Biscuit", "#FFFFB75F": "Peanut Butter Chicken", "#FFC8A38A": "Peanut Butter Crust", "#FFCE4A2D": "Peanut Butter Jelly", "#FF82B185": "Peapod", "#FF91AF88": "Pear Cactus", "#FFCCDD99": "Pear Perfume", "#FFF3EAC3": "Pear Sorbet", "#FFCBF85F": "Pear Spritz", "#FFE3DE92": "Pear Tint", "#FFEAE0C8": "Pearl", "#FF88D8C0": "Pearl Aqua", "#FFD0C9C3": "Pearl Ash", "#FF7FC6CC": "Pearl Bay", "#FF76727F": "Pearl Blackberry", "#FFF4CEC5": "Pearl Blush", "#FFE6E6E3": "Pearl Brite", "#FFDED1C6": "Pearl Bush Variant", "#FFDCE4E9": "Pearl City", "#FFF0EBE4": "Pearl Drops", "#FFEFE5D9": "Pearl Dust", "#FF377B70": "Pearl Green", "#FFEAE1C8": "Pearl Lusta Variant", "#FFFCF7EB": "Pearl Necklace", "#FFEEEFE1": "Pearl Onion", "#FFDDD6CB": "Pearl Oyster", "#FFDED7DA": "Pearl Pebble", "#FFFAFFED": "Pearl Powder", "#FFDFD3D4": "Pearl Rose", "#FFF4F1EB": "Pearl Sugar", "#FFE6E0E3": "Pearl Violet", "#FFF3F2ED": "Pearl White", "#FFF1E3BC": "Pearl Yellow", "#FFF2E9D5": "Pearled Couscous", "#FFF0DFCC": "Pearled Ivory", "#FFDCD0CB": "Pearls & Lace", "#FFF4E3DF": "Pearly", "#FFE8E6DE": "Pearly Gates", "#FFCEDBD5": "Pearly Jade", "#FFEE99CC": "Pearly Pink", "#FFDBD3BD": "Pearly Putty", "#FFE4E4DA": "Pearly Star", "#FFEEE9D8": "Pearly Swirly", "#FFFEEFD3": "Pearly White", "#FF8C7F3C": "Peas Please", "#FFDDC7A8": "Peasant Bread", "#FFC1EFC6": "Peasful Mint", "#FF8CAA95": "Peaslake", "#FF766D52": "Peat Variant", "#FF5A3D29": "Peat Brown", "#FF4A352B": "Peat Moss", "#FF6C5755": "Peat Red Brown", "#FF988C75": "Peat Swamp Forest", "#FF552211": "Peaty Brown", "#FF9D9880": "Pebble", "#FFDED8DC": "Pebble Beach", "#FFF3E1CA": "Pebble Cream", "#FFD5BC94": "Pebble Path", "#FFD3D7DC": "Pebble Soft Blue White", "#FFE0D9DA": "Pebble Stone", "#FFAFB2A7": "Pebble Walk", "#FFD8D0BC": "Pebblebrook", "#FFDECAB9": "Pebbled Courtyard", "#FFA0968D": "Pebbled Path", "#FFDBD5CA": "Pebbled Shore", "#FFA67253": "Pecan", "#FFF7DBA6": "Pecan Cream", "#FFF4DECB": "Pecan Sandie", "#FFE09F78": "Pecan Veneer", "#FFFDDCB7": "P\u00eache", "#FFE1A080": "Pecos Spice", "#FFC5AF91": "Peculiarly Drab Tincture", "#FF00BB22": "Pedestrian Green", "#FFF7F72D": "Pedestrian Lemon", "#FFCC1122": "Pedestrian Red", "#FF31646E": "Pedigree", "#FF8F8E8C": "Pedigrey", "#FFD3CCC4": "Pediment", "#FFC5E1E1": "Peek-a-Blue", "#FF0173BB": "Peek-A-Boo Blue", "#FFE6DEE6": "Peekaboo", "#FF87A96B": "Peeled Asparagus", "#FFFFCF38": "Peeps", "#FFFF2266": "Peevish Red", "#FFE8E9E4": "Pegasus", "#FFEA9FB4": "Pegeen Peony", "#FFF5D2AC": "Pekin Chicken", "#FF355D83": "Pelagic", "#FFFF3333": "Pelati", "#FFC1BCAC": "Pelican", "#FF9EACB1": "Pelican Bay", "#FFD7C0C7": "Pelican Bill", "#FFE8C3C2": "Pelican Feather", "#FFFB9A30": "Pelican Pecker", "#FFE2A695": "Pelican Pink", "#FFC8A481": "Pelican Tan", "#FF2599B2": "Pelorus", "#FFDBB7BB": "Pencil Eraser", "#FF5C6274": "Pencil Lead", "#FFFD9F01": "Pencil Me In", "#FF595D61": "Pencil Point", "#FF999D9E": "Pencil Sketch", "#FF7B8267": "Pendula Garden", "#FFE3E3EB": "Penelope", "#FF9D6984": "Penelope Pink", "#FF37799C": "Peninsula", "#FFB9C8E0": "Penna", "#FFA97F5A": "Penny", "#FFA2583A": "Pennywise", "#FFC2C1CB": "Pensive", "#FFEAB6AD": "Pensive Pink", "#FF96CCD1": "Pentagon", "#FFDBB2BC": "Pentalon", "#FFCABFB3": "Penthouse View", "#FF627E75": "Penzance", "#FFEB8F9D": "Peony", "#FFD8C1BE": "Peony Blush", "#FF9F86B7": "Peony Mauve", "#FFFADDD4": "Peony Prize", "#FFB75754": "Pepper & Spice", "#FFCC2244": "Pepper Jelly", "#FF777568": "Pepper Mill", "#FF8E7059": "Pepper Spice", "#FF7E9242": "Pepper Sprout", "#FFC79D9B": "Pepperberry", "#FF725C5B": "Peppercorn", "#FF4F4337": "Peppercorn Rent", "#FF807548": "Peppered Moss", "#FF957D6F": "Peppered Pecan", "#FF767461": "Peppergrass", "#FFD7E7D0": "Peppermint Variant", "#FF81BCA8": "Peppermint Bar", "#FF64BE9F": "Peppermint Fresh", "#FFB8FFEB": "Peppermint Frosting", "#FFD1E6D5": "Peppermint Patty", "#FFAAC7C1": "Peppermint Pie", "#FF90CBAA": "Peppermint Spray", "#FFE8B9BE": "Peppermint Stick", "#FFD35D7D": "Peppermint Swirl", "#FF009933": "Peppermint Toad", "#FF96CED5": "Peppermint Twist", "#FFD8C553": "Pepperoncini", "#FFAA4400": "Pepperoni", "#FF5B5752": "Peppery", "#FF72D7B7": "Peppy", "#FF55CCBB": "Peppy Peacock", "#FFFFFF44": "Peppy Pineapple", "#FFA3CE27": "P\u00eara Rocha", "#FFACB9E8": "Perano Variant", "#FFF0E8DD": "Percale", "#FFC1ADA9": "Perdu Pink", "#FFBC9B08": "Perennial", "#FF87A56F": "Perennial Garden", "#FFE6A7AC": "Perennial Phlox", "#FFCFC2B3": "Perfect Backdrop", "#FFD6CDBB": "Perfect Crust", "#FF313390": "Perfect Dark", "#FF49A0E7": "Perfect Days", "#FFB7AB9F": "Perfect Greige", "#FFB2A492": "Perfect Khaki", "#FF9EB2C3": "Perfect Landing", "#FF3062A0": "Perfect Ocean", "#FFE9E8BB": "Perfect Pear", "#FFA06A56": "Perfect Penny", "#FF6487B0": "Perfect Periwinkle", "#FFE5B3B2": "Perfect Pink", "#FFB72356": "Perfect Pout", "#FF4596CF": "Perfect Sky", "#FFF2EDD7": "Perfect Solution", "#FF9598A1": "Perfect Storm", "#FFCBAC88": "Perfect Tan", "#FFB6ACA0": "Perfect Taupe", "#FFF0EEEE": "Perfect White", "#FFD9D6E5": "Perfection", "#FF97D0E5": "Perfectly Blue", "#FF694878": "Perfectly Purple", "#FFCC22AA": "Perfectly Purple Place", "#FFC2A9DB": "Perfume Variant", "#FFE2C9CE": "Perfume Cloud", "#FFF3E9F7": "Perfume Haze", "#FFBFA58A": "Pergament", "#FFE4E0DC": "Pergament Shreds", "#FFE1E9DB": "Pergola Panorama", "#FFC62D2C": "Peri Peri", "#FFB6BDD3": "Peri Wink", "#FF904FEF": "Pericallis Hybrida", "#FFACB6B2": "Periglacial Blue Variant", "#FF524A46": "P\u00e9rigord Truffle", "#FFEF8B38": "Peril\u2019s Flames", "#FF52677B": "Periscope", "#FFAE905E": "Peristyle Brass", "#FF8E82FE": "Periwinkle Variant", "#FF8B9AB9": "Periwinkle Blossom", "#FF8F99FB": "Periwinkle Blue", "#FFB4C4DE": "Periwinkle Bud", "#FFB8CBE5": "Periwinkle Dream", "#FF8D9DB3": "Periwinkle Dusk", "#FF8CB7D7": "Periwinkle Sky", "#FFD3DDD6": "Periwinkle Tint", "#FFD6C7BE": "Perk Up", "#FF733C8E": "Perkin Mauve", "#FF408E7C": "Perky", "#FFFBF4D3": "Perky Tint", "#FFF2CA83": "Perky Yellow", "#FF4F4D51": "Perle Noir", "#FF98EFF9": "Permafrost", "#FF005437": "Permanent Green", "#FF584D75": "Perpetual Purple", "#FFBDB3C3": "Perplexed", "#FFDDAA99": "Perrigloss Tan", "#FF8F8CE7": "Perrywinkle", "#FFACB3C7": "Perseverance", "#FFC5988C": "Persian Bazaar", "#FF99AC4B": "Persian Belt", "#FFE3E1CC": "Persian Blinds", "#FFEFCADA": "Persian Delight", "#FFD4EBDD": "Persian Fable", "#FFE1C7A8": "Persian Flatbread", "#FF9B7939": "Persian Gold", "#FF6A7DBC": "Persian Jewel", "#FF990077": "Persian Luxury Purple", "#FFFFDCBF": "Persian Melon", "#FF206874": "Persian Mosaic", "#FFAA9499": "Persian Pastel", "#FF575B93": "Persian Plush", "#FF38343E": "Persian Prince", "#FF8C8EB2": "Persian Violet", "#FFFFB49B": "Persicus", "#FFE59B34": "Persimmon Tint", "#FFF7BD8F": "Persimmon Fade", "#FF934337": "Persimmon Juice", "#FFF47327": "Persimmon Orange", "#FFA74E4A": "Persimmon Red", "#FF9F563A": "Persimmon Varnish", "#FFCEBEDA": "Perspective", "#FFC4AE96": "Persuasion", "#FFCD853F": "Peru", "#FFCD7DB5": "Peruvian Lily", "#FF733D1F": "Peruvian Soil", "#FF7B7284": "Peruvian Violet", "#FF0099EE": "Pervenche", "#FF119922": "Pestering Pesto", "#FF9F8303": "Pestilence", "#FFC1B23E": "Pesto Variant", "#FF558800": "Pesto Alla Genovese", "#FFF49325": "Pesto Calabrese", "#FFB09D64": "Pesto di Noce", "#FFA7C437": "Pesto di Pistacchio", "#FF748A35": "Pesto di Rucola", "#FF9DC249": "Pesto Genovese", "#FF817553": "Pesto Green", "#FF898C66": "Pesto Paste", "#FFBB3333": "Pesto Rosso", "#FFAA9933": "Pestulance", "#FFF7D5DA": "Petal Bloom", "#FFF4DFCD": "Petal Dust", "#FF9F0630": "Petal of a Dying Rose", "#FFF4E5E0": "Petal Pink", "#FFDDAAEE": "Petal Plush", "#FFF8E3EE": "Petal Poise", "#FFF5A5B8": "Petal Power", "#FF53465D": "Petal Purple", "#FFE2DCD3": "Petal Smoke", "#FFFCD1BF": "Petal Soft", "#FFD9D9DF": "Petal Tip", "#FFF3BBC0": "Petals Unfolding", "#FF19A700": "Peter Pan", "#FF7FBAD1": "Petit Four", "#FFE9CDD8": "Petit Pink", "#FFDA9790": "Petite Orchid Variant", "#FFEACACB": "Petite Pink", "#FFCFBBD8": "Petite Purple", "#FF4076B4": "Petrel", "#FFA0AEBC": "Petrel Blue Grey", "#FF66CCCC": "Petrichor", "#FF9B846C": "Petrified Oak", "#FF9C87C1": "Petrified Purple", "#FF2F5961": "Petro Blue", "#FF005F6A": "Petrol", "#FF549B8C": "Petrol Green", "#FF243640": "Petrol Slumber", "#FF21211D": "Petroleum", "#FFFECDAC": "Petticoat", "#FF228822": "Pettifers", "#FF88806A": "Pettingill Sage", "#FFFFBAB0": "Petula", "#FF4D3466": "Petunia Variant", "#FF4B3C4B": "Petunia Patty", "#FFB8B0CF": "Petunia Trail", "#FF91A092": "Pewter Variant", "#FF9B9893": "Pewter Cast", "#FF5E6259": "Pewter Green", "#FFA7A19E": "Pewter Grey", "#FF8B8283": "Pewter Mug", "#FFBAB4A6": "Pewter Patter", "#FF8A8886": "Pewter Ring", "#FFA39B90": "Pewter Tankard", "#FFBDC5C0": "Pewter Tray", "#FFCECECB": "Pewter Vase", "#FFC5BBAE": "Peyote", "#FF5984A8": "Peyton", "#FFD03B52": "Pezzottaite", "#FF6E797B": "Phantom", "#FFD6E0D1": "Phantom Green", "#FF645D5E": "Phantom Hue", "#FF4B4441": "Phantom Mist", "#FF2F3434": "Phantom Ship", "#FF636285": "Pharaoh Purple", "#FF007367": "Pharaoh\u2019s Gem", "#FFEAD765": "Pharaoh\u2019s Gold", "#FF83D1A9": "Pharaoh\u2019s Jade", "#FF59BBC2": "Pharaoh\u2019s Seas", "#FF826663": "Pharlap Variant", "#FF087E34": "Pharmaceutical Green", "#FF005500": "Pharmacy Green", "#FFC17C54": "Pheasant", "#FF795435": "Pheasant Brown", "#FFE0DCD7": "Pheasant\u2019s Egg", "#FFF3C13A": "Phellodendron Amurense", "#FFC4BDAD": "Phelps Putty", "#FFFFCBA2": "Phenomenal Peach", "#FFEE55FF": "Phenomenal Pink", "#FF3E729B": "Phenomenon", "#FF8822BB": "Pheromone Purple", "#FFE2D9DD": "Philanthropist Pink", "#FF0038A7": "Philippine Blue", "#FF6E3A07": "Philippine Bronze", "#FF5D1916": "Philippine Brown", "#FFB17304": "Philippine Gold", "#FFEEBB00": "Philippine Golden Yellow", "#FF008543": "Philippine Green", "#FFFF7300": "Philippine Orange", "#FFFA1A8E": "Philippine Pink", "#FFCE1127": "Philippine Red", "#FF81007F": "Philippine Violet", "#FFFECB00": "Philippine Yellow", "#FF008F80": "Philips Green", "#FF116356": "Philodendron", "#FF757B7D": "Philosophical", "#FF4D483D": "Philosophically Speaking", "#FF7F4F78": "Phlox Flower Violet", "#FFCE5E9A": "Phlox Pink", "#FFFAA21A": "Phoenix Flames", "#FFF8D99E": "Phoenix Fossil", "#FFD2813A": "Phoenix Rising", "#FF99B1A2": "Phoenix Tears", "#FFF7EFDE": "Phoenix Villa", "#FF00AA00": "Phosphor Green", "#FF11EEEE": "Phosphorescent Blue", "#FF11FF00": "Phosphorescent Green", "#FFA5D0C6": "Phosphorus", "#FFAEAD96": "Photo Grey", "#FF88DDEE": "Photon Barrier", "#FF88EEFF": "Photon Projector", "#FFF8F8E8": "Photon White", "#FF8892BF": "PHP Purple", "#FF0480BD": "Phuket Palette", "#FFEF9548": "Physalis", "#FFEBE1D4": "Physalis Aquarelle", "#FFE1D8BB": "Physalis Peal", "#FF314159": "Pi", "#FFE6D0CA": "Pianissimo", "#FF17171A": "Piano Black", "#FF5C4C4A": "Piano Brown", "#FFCFC4C7": "Piano Grey Rose", "#FFEEE5D4": "Piano Keys", "#FF9E8996": "Piano Mauve", "#FF765C52": "Picador", "#FFA04933": "Picante", "#FFF8EA97": "Picasso Variant", "#FF634878": "Picasso Lily", "#FF625D5D": "Piccadilly Grey", "#FF51588E": "Piccadilly Purple", "#FF8BD2E2": "Piccolo", "#FF566955": "Picholine", "#FF958251": "Picholine Olive", "#FFB8A49B": "Pick of the Litter", "#FFF1919A": "Pick Your Brain", "#FFBFF128": "Pick Your Poison", "#FFF3F2EA": "Picket Fence", "#FFC9F0D1": "Pickford", "#FF85A16A": "Pickle", "#FFBBA528": "Pickle Juice", "#FFB3A74B": "Pickled", "#FF99BB11": "Pickled Avocado", "#FFAA0044": "Pickled Beets", "#FF695E4B": "Pickled Capers", "#FF94A135": "Pickled Cucumber", "#FFFFDD55": "Pickled Ginger", "#FF775500": "Pickled Grape Leaves", "#FFDDCC11": "Pickled Lemon", "#FFBBBB11": "Pickled Limes", "#FF887647": "Pickled Okra", "#FFDBB532": "Pickled Peppers", "#FFEEFF33": "Pickled Pineapple", "#FFDA467D": "Pickled Pink", "#FF8E4785": "Pickled Plum", "#FFDDBBAA": "Pickled Pork", "#FF8E7AA1": "Pickled Purple", "#FFEE1144": "Pickled Radish", "#FFFF6655": "Pickled Salmon", "#FFCFD2B5": "Pickling Spice", "#FF99C285": "Picnic", "#FFBCDBD4": "Picnic Bay", "#FF00CCEE": "Picnic Day Sky", "#FFAB5236": "Pico Earth", "#FF7E2553": "Pico Eggplant", "#FFFFF1E8": "Pico Ivory", "#FFC2C3C7": "Pico Metal", "#FFFFA300": "Pico Orange", "#FFFFEC27": "Pico Sun", "#FF1D2B53": "Pico Void", "#FFFF77A8": "Pico-8 Pink", "#FF5BA0D0": "Picton Blue Variant", "#FF00804C": "Picture Book Green", "#FFFBF2D1": "Picture Perfect", "#FFF1D99F": "Pie Crust", "#FF877A64": "Pie Safe", "#FFEDE7C8": "Piece of Cake", "#FFFFAF38": "Pieces of Eight", "#FFBEBDC2": "Pied Wagtail Grey", "#FFC2CEC5": "Piedmont", "#FFEAC185": "Piedra de Sol", "#FF88857D": "Pier", "#FF647D8E": "Pier 17 Steel", "#FFDD00EE": "Piercing Pink", "#FFDD1122": "Piercing Red", "#FF43232C": "Piermont Stone Red", "#FFF4D68C": "Pierogi", "#FFA1C8DB": "Piezo Blue", "#FF484848": "Pig Iron", "#FFA9AFAA": "Pigeon", "#FFC1B4A0": "Pigeon Grey", "#FF9D857F": "Pigeon Pink", "#FFFFCCBB": "Piggy Bank", "#FFFFCBC4": "Piggy Dreams", "#FFF0DCE3": "Piggyback", "#FFFFC0C6": "Piglet", "#FF4D0082": "Pigment Indigo Variant", "#FFF9E9D7": "Pignoli", "#FFE8DAD1": "Pigskin Puffball", "#FFEEE92D": "Pika Yellow", "#FFEEDE73": "Pikachu Chu", "#FF6C7779": "Pike Lake", "#FF15B01A": "Pikkoro Green", "#FFFFFF55": "P\u012bl\u0101 Yellow", "#FF006981": "Pilot Blue", "#FFF8F753": "Pilsener", "#FFCC2200": "Piment Piquant", "#FFDC5D47": "Pimento", "#FF6C5738": "Pimento Grain Brown", "#FFDF9E9D": "Pimlico", "#FFC3585C": "Pimm\u2019s", "#FFFFD97A": "Pina", "#FFF4DEB3": "Pina Colada", "#FF7198C0": "Pinafore Blue", "#FFF4C701": "Pinard Yellow", "#FFC17A62": "Pinata", "#FFC88CA4": "Pinch Me", "#FFD2916B": "Pinch of Clove", "#FFFFF8E3": "Pinch of Pearl", "#FF0F180A": "Pinch of Pepper", "#FFDDDDCC": "Pinch of Pistachio", "#FFB4ABAF": "Pinch Purple", "#FFAC989C": "Pincushion", "#FFBB4411": "Pindjur Red", "#FF2B5D34": "Pine", "#FF887468": "Pine Bark", "#FF645345": "Pine Cone Variant", "#FF675850": "Pine Cone Brown", "#FF5C6456": "Pine Cone Pass", "#FFB7B8A5": "Pine Crush", "#FF415241": "Pine Forest", "#FFDEEAE0": "Pine Frost", "#FF797E65": "Pine Garland", "#FFBDC07E": "Pine Glade Variant", "#FFEBC79E": "Pine Grain", "#FF0A481E": "Pine Green Variant", "#FF273F39": "Pine Grove", "#FF486358": "Pine Haven", "#FF204242": "Pine Hollow", "#FFECDBD2": "Pine Hutch", "#FF839B5C": "Pine Leaves", "#FFD5D8BC": "Pine Mist", "#FF5C685E": "Pine Mountain", "#FF334D41": "Pine Needle", "#FFEADAC2": "Pine Nut", "#FF776F56": "Pine Peak", "#FF6D9185": "Pine Ridge", "#FF4A6D42": "Pine Scent", "#FF066F6C": "Pine Scented Lagoon", "#FFD5BFA5": "Pine Strain", "#FF9C9F75": "Pine Trail", "#FFE5E7D5": "Pine Water", "#FFB3C6B9": "Pine Whisper", "#FF786D72": "Pineal Pink", "#FF563C0D": "Pineapple", "#FFB4655C": "Pineapple Blossom", "#FFEDBB6E": "Pineapple Cake", "#FFF2EAC3": "Pineapple Cream", "#FFEDDA8F": "Pineapple Crush", "#FFF0E7A9": "Pineapple Delight", "#FFF9F0D6": "Pineapple Fizz", "#FFFFC72C": "Pineapple Gold", "#FFEBEB57": "Pineapple High", "#FFF8E87B": "Pineapple Juice", "#FFEEEE88": "Pineapple Perfume", "#FF9C8F60": "Pineapple Sage", "#FFFD645F": "Pineapple Salmon", "#FFE7D791": "Pineapple Slice", "#FFE4E5CE": "Pineapple Soda", "#FFF7F4DA": "Pineapple Sorbet", "#FFEAD988": "Pineapple Whip", "#FFDBBC6C": "Pineapple Wine", "#FFF0D6DD": "Pineberry", "#FF2B7B66": "Pinehurst", "#FF57593F": "Pinetop", "#FF5B7349": "Pineweed", "#FF006655": "Piney Lake", "#FF70574F": "Piney Wood", "#FF23C48B": "P\u00edng G\u01d4o L\u01dc Green", "#FFE9B8A4": "Pink Abalone", "#FFF4E2E9": "Pink Amour", "#FFFFC3C6": "Pink and Sleek", "#FFD7B8AB": "Pink Apatite", "#FFFFB2F0": "Pink Apotheosis", "#FFFE69B5": "Pink as Hell", "#FFA6427C": "Pink Ballad", "#FFF9D1CF": "Pink Bangles", "#FFF6C3A6": "Pink Beach", "#FFDCA7C2": "Pink Beauty", "#FFDD9CBD": "Pink Begonia", "#FFF2A9BA": "Pink Blessing", "#FFE3ABCE": "Pink Bliss", "#FFFBE9DD": "Pink Blossom", "#FFF4ACB6": "Pink Blush", "#FFDD77EE": "Pink Bonnet", "#FFEFE1E4": "Pink Booties", "#FFC5B5C3": "Pink Bravado", "#FFFDBAC4": "Pink Bubble Tea", "#FFECC9CA": "Pink Cardoon", "#FFED7A9E": "Pink Carnation", "#FFECBAB3": "Pink Cashmere", "#FFFFB2D0": "Pink Cattleya", "#FFF4DED9": "Pink Chablis", "#FFF2A3BD": "Pink Chalk", "#FFE8DFED": "Pink Champagne", "#FFDBC8C3": "Pink Chantilly", "#FFDD66BB": "Pink Charge", "#FFE4898A": "Pink Chi", "#FFEFBECF": "Pink Chintz", "#FFFFD5D1": "Pink Clay", "#FFD99294": "Pink Clay Pot", "#FFF5D1C8": "Pink Cloud", "#FFE9B7AC": "Pink Cocoa", "#FFD1A5A6": "Pink Cocoa Cupcake", "#FFFF99DD": "Pink Condition", "#FFF5D0D6": "Pink Cupcake", "#FFFED5E9": "Pink Currant", "#FFB94C66": "Pink Dahlia", "#FFD98580": "Pink Damask", "#FFC97376": "Pink Dazzle", "#FFFF8AD8": "Pink Delight", "#FFF5BFD1": "Pink Destiny", "#FFFED0FC": "Pink Diamond", "#FFFFF4F2": "Pink Diminishing", "#FFB499A1": "Pink Discord", "#FFF7D1D1": "Pink Dogwood", "#FFFEA5A2": "Pink Dream", "#FFE9B8DE": "Pink Drink", "#FFF8E7E4": "Pink Duet", "#FFE4B5B2": "Pink Dust", "#FFECDFD5": "Pink Dyed Blond", "#FFB08272": "Pink Earth", "#FFFF99EE": "Pink Elephants", "#FFF2E4E2": "Pink Emulsion", "#FFF3A09A": "Pink Eraser", "#FFF56F88": "Pink Explosion", "#FFDD77FF": "Pink Fetish", "#FFCC55FF": "Pink Fever", "#FFFC845D": "Pink Fire", "#FFF5A8B2": "Pink Fit", "#FFCD4E82": "Pink Flamb\u00e9", "#FFB55A63": "Pink Flame", "#FFD8B4B6": "Pink Flare Variant", "#FFEB9A9D": "Pink Floyd", "#FFFBD3D9": "Pink Fluorite", "#FFF5A9BE": "Pink Flutter", "#FFE6D2DC": "Pink Frapp\u00e9", "#FFEDB0C7": "Pink Frenzy", "#FFF7D7E2": "Pink Frosting", "#FFD2738F": "Pink Garnet", "#FFE6B5D7": "Pink Gems", "#FFF6909D": "Pink Geranium", "#FFDFA3BA": "Pink Gin", "#FFCFA798": "Pink Ginger", "#FFFF1DCD": "Pink Glamour", "#FFFDDFDA": "Pink Glitter", "#FFFFECE0": "Pink Glow", "#FFBD9E97": "Pink Granite", "#FFF3BAC9": "Pink Grapefruit", "#FFF2BDDF": "Pink Heath", "#FFB36C86": "Pink Hibiscus", "#FF90305D": "Pink Horror", "#FFF8C1BB": "Pink Hydrangea", "#FFFE01B1": "Pink Hysteria", "#FFCF9FA9": "Pink Ice", "#FFEEA0A6": "Pink Icing", "#FFD8B8F8": "Pink Illusion", "#FFFF1476": "Pink Ink", "#FFCC44FF": "Pink Insanity", "#FFD6B3CC": "Pink Ivory", "#FF9E6B89": "Pink Jazz", "#FFFF55AA": "Pink Katydid", "#FFFF22EE": "Pink Kitsch", "#FFF6CCD7": "Pink Lace Variant", "#FFF3D7B6": "Pink Lady Variant", "#FFD9AFCA": "Pink Lavender", "#FFFFEAEB": "Pink Lemonade", "#FFF8D0E7": "Pink Lily", "#FFD2BFC4": "Pink Linen", "#FFFADBD7": "Pink Lotus", "#FFEAACC6": "Pink Macaroon", "#FFC16C7B": "Pink Manhattan", "#FFE5D0CA": "Pink Marble", "#FFF4B6D1": "Pink Marshmallow", "#FFF4B6A8": "Pink Mimosa", "#FFF4EDE9": "Pink Mirage", "#FFE6BCCD": "Pink Mist", "#FFED9AAB": "Pink Moment", "#FFA98981": "Pink Moroccan", "#FFD8AAB7": "Pink Nectar", "#FFD6C3B7": "Pink Nudity", "#FF6844FC": "Pink OCD", "#FFEDC5DD": "Pink Odyssey", "#FFFF9066": "Pink Orange", "#FFFD82C3": "Pink Orchid Mantis", "#FFE1D6D8": "Pink Organdy", "#FFFF33FF": "Pink Overflow", "#FFEACED4": "Pink Pail", "#FFDF9F8F": "Pink Palazzo", "#FFD1B6C3": "Pink Pampas", "#FFE1C5C9": "Pink Pandora", "#FFD5877E": "Pink Papaya", "#FFB26BA2": "Pink Parade", "#FFAD546E": "Pink Parakeet", "#FFFADDD5": "Pink Parfait", "#FFFF55EE": "Pink Party", "#FFD3236F": "Pink Peacock", "#FFE1BED9": "Pink Peony", "#FFEF586C": "Pink Pepper", "#FF8E5565": "Pink Peppercorn", "#FFC034AF": "Pink Perennial", "#FFFFDBE5": "Pink Perfume", "#FFFFAD97": "Pink Persimmon", "#FFF62681": "Pink Piano", "#FFEFC9B8": "Pink Pieris", "#FFEE66EE": "Pink Ping", "#FFB45389": "Pink Pizzazz", "#FFFC80C3": "Pink Plastic Fantastic", "#FFFFDFE5": "Pink Pleasure", "#FFEAD2D2": "Pink Plum", "#FFA4819F": "Pink Plumeria", "#FFE4ADD3": "Pink Plunge", "#FFFF007E": "Pink Poison", "#FFCCBABE": "Pink Polar", "#FFEAC7DC": "Pink Pony", "#FF8E6E74": "Pink Poppy", "#FFEE9091": "Pink Porky", "#FFEADEE0": "Pink Posey", "#FFEFDBE2": "Pink Posies", "#FFCEAEBB": "Pink Potion", "#FFD5B6CD": "Pink Power", "#FFEE99AA": "Pink Prestige", "#FFEF1DE7": "Pink Pride", "#FFF1E0E8": "Pink Proposal", "#FFD04A70": "Pink Punch", "#FFD983BD": "Pink Punk", "#FFDB4BDA": "Pink Purple", "#FFDC9F9F": "Pink Pussycat", "#FFFFBBEE": "Pink Quartz", "#FFAB485B": "Pink Quince", "#FFF5054F": "Pink Red", "#FFF8DAE1": "Pink Ribbon", "#FFFEAB9A": "Pink Rose Bud", "#FFEEBCB8": "Pink Sachet", "#FFF6CDC6": "Pink Salt", "#FFDFB19B": "Pink Sand", "#FFF6DBD3": "Pink Sangria", "#FFFFBBDD": "Pink Satin", "#FFF2E0D4": "Pink Scallop", "#FFF6DACB": "Pink Sea Salt", "#FFF0D4CA": "Pink Sentiment", "#FFCD7584": "Pink Shade", "#FFA4877D": "Pink Shade Granite", "#FFBB3377": "Pink Shadow", "#FFF780A1": "Pink Sherbet Variant", "#FFFDE0DA": "Pink Shimmer", "#FFD58D8A": "Pink Slip", "#FFEACAD0": "Pink Slipper", "#FFDEB8BC": "Pink Softness", "#FFFFE9EB": "Pink Sparkle", "#FFE7C9CA": "Pink Spinel", "#FFA328B3": "Pink Spyro", "#FFDDABAB": "Pink Stock", "#FFEEAAFF": "Pink Sugar", "#FFFFD9E6": "Pink Supremecy", "#FFBFB3B2": "Pink Swan Variant", "#FFFCEAE6": "Pink Swirl", "#FFFF81C0": "Pink Tease", "#FFFFE6E4": "Pink Theory", "#FFD9C8BA": "Pink Tint", "#FFFAE2D6": "Pink Touch", "#FF985672": "Pink Tulip", "#FFDEB59A": "Pink Tulle", "#FFF9E4E9": "Pink Tutu", "#FFF5E6E6": "Pink Vibernum", "#FFE0C9C4": "Pink Water", "#FFFFAAEE": "Pink Wink", "#FFDDBBBB": "Pink Wraith", "#FFD13D82": "Pink Yarrow", "#FFF2D8CD": "Pink Zest", "#FFCB5C5B": "Pinkadelic", "#FFFF99FF": "Pinkalicious", "#FFF1BDBA": "Pinkathon", "#FFFE54A3": "Pinkella", "#FFE8C5AE": "Pinkham", "#FFE82A8E": "Pinkinity", "#FFD46A7E": "Pinkish", "#FFB17261": "Pinkish Brown", "#FFC8ACA9": "Pinkish Grey", "#FFFF724C": "Pinkish Orange", "#FFD648D7": "Pinkish Purple", "#FFF10C45": "Pinkish Red", "#FFD99B82": "Pinkish Tan", "#FFFFF1FA": "Pinkish White", "#FFEB84F5": "Pinkling", "#FFDD11FF": "Pinkman", "#FFF9CED1": "Pinktone", "#FFF2DBD2": "Pinkwash", "#FFFC86AA": "Pinky", "#FFC9AA98": "Pinky Beige", "#FFB96D8E": "Pinky Pickle", "#FFF5D1CF": "Pinky Promise", "#FFEEAAEE": "Pinky Swear", "#FFBEDDD5": "Pinnacle", "#FF605258": "Pinot Noir", "#FFECA2AD": "Pinque", "#FFB89EA8": "Pinterested", "#FFD2DCDE": "Pinwheel Geyser", "#FF625A42": "Pinyon Pine", "#FF480840": "Pion Purple", "#FFAA9076": "Pioneer Village", "#FF857165": "Pipe", "#FF999D99": "Pipe Dream", "#FF9D5432": "Piper Variant", "#FFF5E6C4": "Pipitschah", "#FFFCDBD2": "Pippin Variant", "#FF769358": "Piquant Green", "#FFEE00EE": "Piquant Pink", "#FF71424A": "Pirat\u2019s Wine", "#FF363838": "Pirate Black", "#FFDDE7E2": "Pirate Coast", "#FFBA782A": "Pirate Gold Variant", "#FFB1905E": "Pirate Plunder", "#FF818988": "Pirate Silver", "#FFDDCA69": "Pirate Treasure", "#FF005176": "Pirate\u2019s Haven", "#FFB08F42": "Pirate\u2019s Hook", "#FF716970": "Pirate\u2019s Trinket", "#FFAB46EC": "Pisces Vivid Amethyst", "#FFBEEB71": "Pisco Sour", "#FF9C7AA4": "Pishposh", "#FFF4D6A4": "Pismo Dunes", "#FFC5D498": "Pistachio Cream", "#FFA5CF8C": "Pistachio Dream", "#FF4F8F00": "Pistachio Flour", "#FFA9D39E": "Pistachio Green", "#FFA0B7AD": "Pistachio Ice Cream", "#FFC0FA8B": "Pistachio Mousse", "#FFC6D4AC": "Pistachio Pudding", "#FFCFC5AF": "Pistachio Shell", "#FFC7BB73": "Pistachio Shortbread", "#FFD7D2B8": "Pistachio Tang", "#FF00BB55": "Pistou Green", "#FF414958": "Pit Stop", "#FFF5E7D2": "Pita", "#FFDEC8A6": "Pita Bread", "#FFEDEB9A": "Pitapat", "#FF423937": "Pitch", "#FF483C41": "Pitch Black", "#FF2D3D4A": "Pitch Cobalt", "#FF283330": "Pitch Green", "#FF5C4033": "Pitch Mary Brown", "#FF7C7766": "Pitch Pine", "#FF4A148C": "Pitch Violet", "#FF003322": "Pitch-Black Forests", "#FFB5D1BE": "Pitcher", "#FFD0A32E": "Pitmaston Pear Yellow", "#FF9BC2BD": "Pitter Patter", "#FFBB0022": "Pixel Bleeding", "#FFF7D384": "Pixel Cream", "#FF008751": "Pixel Nature", "#FFDBDCDB": "Pixel White", "#FF009337": "Pixelated Grass", "#FFF6D5DC": "Pixie Dust", "#FFBBCDA5": "Pixie Green Variant", "#FF391285": "Pixie Powder", "#FFACB1D4": "Pixie Violet", "#FFE9E6EB": "Pixie Wing", "#FFB4A6C6": "Pixieland", "#FFE57F3D": "Pizazz Variant", "#FFF5C795": "Pizazz Peach", "#FFBF8D3C": "Pizza Variant", "#FFCD2217": "Pizza Flame", "#FFA06165": "Pizza Pie", "#FFC6C3C0": "Place of Dust", "#FFE7E7E7": "Placebo", "#FFEBF4FC": "Placebo Blue", "#FFF8EBFC": "Placebo Fuchsia", "#FFEBFCEC": "Placebo Green", "#FFF6FCEB": "Placebo Lime", "#FFFCEBF4": "Placebo Magenta", "#FFFCF5EB": "Placebo Orange", "#FFFCEBFA": "Placebo Pink", "#FFF0EBFC": "Placebo Purple", "#FFFCEBEB": "Placebo Red", "#FFEBFCFC": "Placebo Sky", "#FFEBFCF5": "Placebo Turquoise", "#FFFCFBEB": "Placebo Yellow", "#FF88A9D2": "Placid Blue", "#FF1CADBA": "Placid Sea", "#FFDFB900": "Plague Brown", "#FFAB8D44": "Plaguelands Beige", "#FFAD5F28": "Plaguelands Hazel", "#FFEBF0D6": "Plain and Simple", "#FF905100": "Plain Old Brown", "#FFF5DE85": "Plains", "#FF8A5024": "Plane Brown", "#FFDADDC3": "Planet Earth", "#FFAF313A": "Planet Fever", "#FF496A76": "Planet Green", "#FF883333": "Planet of the Apes", "#FF1C70AD": "Planetarium", "#FFCCCFCB": "Planetary Silver", "#FF00534C": "Plankton Green", "#FF777A44": "Plant Green", "#FF97943B": "Plantain", "#FFD6A550": "Plantain Chips", "#FF356554": "Plantain Green", "#FF3E594C": "Plantation Variant", "#FF9B8A44": "Plantation Island", "#FF6A5143": "Plantation Shutters", "#FF339900": "Planter", "#FFD59CFC": "Plasma Trail", "#FFEAEAEA": "Plaster", "#FFE1EAEC": "Plaster Cast", "#FFEAD1A6": "Plaster Mix", "#FFF65D20": "Plastic Carrot", "#FFFFCC04": "Plastic Cheese", "#FFF5F0F1": "Plastic Clouds", "#FFEDDC70": "Plastic Lime", "#FFAA2266": "Plastic Lips", "#FFFFDDCC": "Plastic Marble", "#FF55AA11": "Plastic Pines", "#FF22FF22": "Plastic Veggie", "#FF4A623B": "Plasticine", "#FF8C8589": "Plate Mail Metal", "#FFD3E7E5": "Plateau", "#FFF0E8D7": "Platinum Blonde", "#FF807F7E": "Platinum Granite", "#FF6A6D6F": "Platinum Grey", "#FFECE1D3": "Platinum Ogon Koi", "#FFD1CCB0": "Platinum Sage", "#FF88CCFF": "Platonic Blue", "#FF2A4845": "Platoon Green", "#FF39536C": "Plaudit", "#FFFF8877": "Play \u2019til Dawn", "#FF638495": "Play Me a Melody", "#FFBAB6A9": "Play on Grey", "#FFCE5924": "Play School", "#FFB39BA9": "Play Time", "#FFDCC7B3": "Playa Arenosa", "#FFFCE3CA": "Playful Peach", "#FFCBBDD7": "Playful Petal", "#FFC46C73": "Playful Pink", "#FFBA99A2": "Playful Plum", "#FFBFB9D5": "Playful Purple", "#FF8B8C6B": "Playing Hooky", "#FFF4C0C3": "Plaza Pink", "#FFAEA393": "Plaza Taupe", "#FFA379AA": "Pleasant Dream", "#FF4D5A4C": "Pleasant Hill", "#FFCC3300": "Pleasant Pomegranate", "#FF8833AA": "Pleasant Purple", "#FF00A0A2": "Pleasant Stream", "#FFF5CDD2": "Pleasing Pink", "#FF80385C": "Pleasure", "#FF858BC2": "Pleated Mauve", "#FFB9C4D2": "Plein Air", "#FFD0ADD3": "Plink", "#FF56646B": "Plot Twist", "#FF6C6459": "Ploughed Earth", "#FF8F8373": "Plover Grey", "#FF66386A": "Plum Variant", "#FFF2A0A1": "Plum Blossom", "#FFB48A76": "Plum Blossom Dye", "#FF4B6176": "Plum Blue", "#FF4E4247": "Plum Brown", "#FF9C7EB7": "Plum Burst", "#FFD1BFDC": "Plum Cake", "#FF612246": "Plum Caspia", "#FF6A6A6F": "Plum Charcoal", "#FF670728": "Plum Cheese", "#FF716063": "Plum Crush", "#FF8B6878": "Plum Dandy", "#FFAA4C8F": "Plum Dust", "#FFB1A7B6": "Plum Frost", "#FF313048": "Plum Fuzz", "#FF695C39": "Plum Green", "#FF674555": "Plum Harvest", "#FF8B7574": "Plum Haze", "#FF885577": "Plum Highness", "#FF644348": "Plum Intended", "#FF463C4E": "Plum Island", "#FF704783": "Plum Jam", "#FFDEA1DD": "Plum Juice", "#FFAA3377": "Plum Kingdom", "#FF625B5C": "Plum Kitten", "#FF8E7482": "Plum Legacy", "#FF994548": "Plum Majesty", "#FFC099A0": "Plum Mouse", "#FF4E414B": "Plum Orbit", "#FFAA1166": "Plum Paradise", "#FF9B4B80": "Plum Passion", "#FFAA1155": "Plum Perfect", "#FFA489A3": "Plum Perfume", "#FFD4BDDF": "Plum Point", "#FF7E5E8D": "Plum Power", "#FF7C70AA": "Plum Preserve", "#FF644847": "Plum Raisin", "#FF64475E": "Plum Rich", "#FF876C7A": "Plum Royale", "#FF6A3939": "Plum Sauce", "#FF915D88": "Plum Savour", "#FF78738B": "Plum Shade", "#FF7C707C": "Plum Shadow", "#FF51304E": "Plum Skin", "#FF928E8E": "Plum Smoke", "#FF957E8E": "Plum Swirl", "#FFDDC1D7": "Plum Taffy", "#FFB6A19B": "Plum Taupe", "#FF6A5858": "Plum Truffle", "#FF734D58": "Plum Wine", "#FFDACEE8": "Plum\u2019s the Word", "#FF00998C": "Plumage", "#FF5C7287": "Plumbeous", "#FF735054": "Plumberry", "#FF7D665F": "Plumburn", "#FFA5CFD5": "Plume", "#FFD9D5C5": "Plume Grass", "#FFC3B5D4": "Plumeria", "#FF675A75": "Plummy", "#FF604454": "Plummy Rouge", "#FF64A281": "Plumosa", "#FF9E8185": "Plumville", "#FF5072A9": "Plunder", "#FF035568": "Plunge", "#FF00FFCC": "Plunge Pool", "#FF8CD4DF": "Plunging Waterfall", "#FF78AC01": "Plus Ultra", "#FF3B3549": "Plush", "#FF5D4A61": "Plush Purple", "#FFB1928C": "Plush Suede", "#FFB49C83": "Plush Tan", "#FF7E737D": "Plush Velvet", "#FFEAB7A8": "Plushy Pink", "#FF34ACB1": "Pluto", "#FF35FA00": "Plutonium", "#FF66DDDD": "Pluviophile", "#FFDDD3C2": "Plymouth Beige", "#FFB1B695": "Plymouth Green", "#FFB0B1AC": "Plymouth Grey", "#FFCDB3AC": "Plymouth Notch", "#FFF5D893": "Poached Egg", "#FFFF8552": "Poached Rainbow Trout", "#FF077F1B": "Poblano", "#FFEE9977": "Pochard Duck Head", "#FFB5D5D7": "Pocket Lint", "#FFC2A781": "Pocket Watch", "#FFC3B8C3": "Pocketful of Posies", "#FFE1C7C6": "Pocketful of Promise", "#FF00A844": "Poetic Green", "#FFE4E8E1": "Poetic Licence", "#FFE2DED8": "Poetic Light", "#FFF8E1E4": "Poetic Princess", "#FFFFFED7": "Poetic Yellow", "#FFC2ACA5": "Poetry in Motion", "#FF886891": "Poetry Mauve", "#FF6F5C5F": "Poetry Plum", "#FF9FAEC9": "Poetry Reading", "#FFECE4D2": "Pogo Sands", "#FF651C26": "Pohutukawa Variant", "#FFDB372A": "Poinciana", "#FFCC3842": "Poinsettia", "#FF859587": "Pointed Cabbage Green", "#FF575D56": "Pointed Fir", "#FF646767": "Pointed Rock", "#FFA77693": "Poise", "#FFFFA99D": "Poised Peach", "#FF8C7E78": "Poised Taupe", "#FF40FD14": "Poison Green", "#FFB300FF": "Poison Purple Paradise", "#FF73403E": "Poisonberry", "#FF66FF11": "Poisoning Green", "#FF7F01FE": "Poisoning Purple", "#FF55FF11": "Poisonous", "#FF993333": "Poisonous Apple", "#FFD3DB39": "Poisonous Cloud", "#FF77FF66": "Poisonous Dart", "#FFD7D927": "Poisonous Ice Cream", "#FFCAE80A": "Poisonous Pesto", "#FF88EE11": "Poisonous Pistachio", "#FF99DD33": "Poisonous Potion", "#FF2A0134": "Poisonous Purple", "#FF35654D": "Poker Green", "#FFE5F2E7": "Polar Variant", "#FFEAE9E0": "Polar Bear", "#FFFCFFFF": "Polar Bear in a Blizzard", "#FFB3E0E7": "Polar Blue", "#FFCCD5DA": "Polar Drift", "#FFC9E7E3": "Polar Expedition", "#FFD2D0C9": "Polar Fox", "#FF5097FC": "Polar Glow", "#FFADAFBD": "Polar Mist", "#FFC2D6EC": "Polar Opposite", "#FF6B7B7B": "Polar Pond", "#FFACCDE2": "Polar Seas", "#FFD0DCDE": "Polar Soft Blue", "#FFD0D1CF": "Polar Star", "#FFE6EFEC": "Polar White", "#FFB4DFED": "Polar Wind", "#FFA0AEAD": "Polaris", "#FF6F8A8C": "Polaris Blue", "#FFEFC47F": "Polenta", "#FF374F6B": "Police Blue", "#FFBD7D94": "Polignac", "#FFE9E8E7": "Polish White", "#FFDED8CE": "Polished", "#FF862A2E": "Polished Apple", "#FF77BCB6": "Polished Aqua", "#FF985538": "Polished Brown", "#FF9E9793": "Polished Concrete", "#FFB66325": "Polished Copper", "#FFC7D4D7": "Polished Cotton", "#FF953640": "Polished Garnet", "#FFEEAA55": "Polished Gold", "#FFEDE0C8": "Polished Ivory", "#FF4F4041": "Polished Leather", "#FFDCD5C8": "Polished Limestone", "#FF432722": "Polished Mahogany", "#FFD0BC9D": "Polished Marble", "#FF819AB1": "Polished Metal", "#FFF8EDD3": "Polished Pearl", "#FF9C9A99": "Polished Pewter", "#FFFFF2EF": "Polished Pink", "#FFBCC3C2": "Polished Rock", "#FFC5D1DA": "Polished Silver", "#FF6F828A": "Polished Steel", "#FFBEB49E": "Polished Stone", "#FFE9DDD4": "Polite White", "#FF5A4458": "Polka Dot Plum", "#FFFDE2A0": "Polka Dot Skirt", "#FFEEEEAA": "Pollen", "#FFF0C588": "Pollen Grains", "#FFFBD187": "Pollen Powder", "#FFB8A02A": "Pollen Storm", "#FFE3D6BC": "Pollinate", "#FFEEDD66": "Pollination", "#FFE2CF24": "Polliwog", "#FFFFCAA4": "Polly", "#FF8AA7CC": "Polo Blue Variant", "#FFD09258": "Polo Pony", "#FFF4E5DD": "Polo Tan", "#FFE8B87F": "Polvo de Oro", "#FFFEFFCC": "Polyanthus Narcissus", "#FFEAEAE8": "Polypropylene", "#FF856F76": "Pomace Red", "#FFB53D45": "Pomegranate Variant", "#FF95313A": "Pomegranate Seeds", "#FFAB6F73": "Pomegranate Tea", "#FFE38FAC": "Pomelo Red", "#FFFCE8E3": "Pomelo Sugar", "#FFC30232": "Pomodoro", "#FFF2D4DF": "Pomodoro e Mozzarella", "#FF87608E": "Pomp and Power", "#FF6A1F44": "Pompadour Variant", "#FFC87763": "Pompeian Pink", "#FFA82A38": "Pompeian Red", "#FF6C757D": "Pompeii Ash", "#FF004C71": "Pompeii Blue", "#FFD1462C": "Pompeii Red", "#FF77A8AB": "Pompeius Blue", "#FFFF6666": "Pompelmo", "#FFCA93C1": "Pomtini", "#FFF75C75": "Ponceau", "#FFB49073": "Poncho", "#FF00827F": "Pond Bath", "#FF8BB6C6": "Pond Blue", "#FFD0D8D9": "Pond Frost", "#FFA18E6B": "Pond Green", "#FF939990": "Pond Leaves", "#FF486B67": "Pond Newt", "#FFB6C9B8": "Pond\u2019s Edge", "#FFB88F88": "Ponder", "#FF29494E": "Ponderosa Pine", "#FFD7EFDE": "Pondscape", "#FF5F9228": "Pont Moss", "#FF0C608E": "Pontoon", "#FFC6AA81": "Pony", "#FF726A60": "Pony Express", "#FFD2BC9B": "Pony Tail", "#FF220000": "Ponzu Brown", "#FFEECEE6": "Poodle Pink", "#FFFFAEBB": "Poodle Skirt", "#FFEA927A": "Poodle Skirt Peach", "#FF824B2E": "Pookie Bear", "#FF8FABBD": "Pool Bar", "#FF67BCB3": "Pool Blue", "#FF7DAEE1": "Pool Floor", "#FF00AF9D": "Pool Green", "#FFBEE9E3": "Pool Party", "#FF039578": "Pool Table", "#FF70928E": "Pool Tide", "#FF2188FF": "Pool Water", "#FF8095A0": "Poolhouse", "#FF72ACAD": "Poolrooms", "#FFBEE0E2": "Poolside", "#FFA13741": "Pop of Poppy", "#FF93D4C0": "Pop Shop", "#FFF771B3": "Pop That Gum", "#FFF7D07A": "Popcorn", "#FFFCEBD1": "Popcorn Ball", "#FFA29F46": "Poplar", "#FF137953": "Poplar Forest", "#FFECE9E9": "Poplar Kitten", "#FFFED3E4": "Poplar Pink", "#FFDFE3D8": "Poplar White", "#FFC23C47": "Poppy", "#FFFBE9D8": "Poppy Crepe", "#FFF18C49": "Poppy Glow", "#FF88A496": "Poppy Leaf", "#FFF6A08C": "Poppy Petal", "#FF736157": "Poppy Pods", "#FFAE605B": "Poppy Prose", "#FFDD3845": "Poppy Red", "#FF4A4E54": "Poppy Seed", "#FFFF5630": "Poppy Surprise", "#FFCCD7DF": "Poppy\u2019s Whiskers", "#FFD4CCC3": "Popular Beige", "#FFDDDCDB": "Porcelain Variant", "#FFD9D0C4": "Porcelain Basin", "#FF95C0CB": "Porcelain Blue", "#FFF0DBD5": "Porcelain Blush", "#FFE9B7A8": "Porcelain Crab", "#FFEEFFBB": "Porcelain Earth", "#FFA9998C": "Porcelain Figurines", "#FFE1DAD9": "Porcelain Goldfish", "#FF118C7E": "Porcelain Green", "#FFDFE2E4": "Porcelain Jasper", "#FFDBE7E1": "Porcelain Mint", "#FFEBE8E2": "Porcelain Mould", "#FFF5D8BA": "Porcelain Peach", "#FFECD9B9": "Porcelain Pink", "#FFEA6B6A": "Porcelain Rose", "#FF808078": "Porcelain Shale", "#FFFFE7EB": "Porcelain Skin", "#FFF7D8C4": "Porcelain Tan", "#FF95B0C6": "Porcelain Vase", "#FFFDDDA7": "Porcelain Yellow", "#FFFFBFAB": "Porcellana", "#FFA4B3B9": "Porch Ceiling", "#FF566F8C": "Porch Song", "#FF597175": "Porch Swing", "#FFD2CABE": "Porch Swing Beige", "#FFAA8736": "Porchetta Crust", "#FFD9AE86": "Porcini", "#FF917A75": "Porcupine Needles", "#FFF8E0E7": "Pork Belly", "#FFD4CEBF": "Porous Stone", "#FF2A0033": "Porphyrophobia", "#FF2792C3": "Porpita Porpita", "#FFDBDBDA": "Porpoise", "#FFC8CBCD": "Porpoise Fin", "#FF076A7E": "Porpoise Place", "#FF6D3637": "Port", "#FF006A93": "Port au Prince", "#FF54383B": "Port Glow", "#FF3B436C": "Port Gore Variant", "#FF54C3C1": "Port Hope", "#FF0E4D4E": "Port Malmesbury", "#FF532D36": "Port Royale", "#FF6E0C0D": "Port Wine", "#FF846342": "Port Wine Barrel", "#FF85677B": "Port Wine Stain", "#FF9A8273": "Portabella", "#FF947A62": "Portabello", "#FF8B98D8": "Portage Variant", "#FFF8F6DA": "Portal Entrance", "#FFF0D555": "Portica Variant", "#FFBBAB95": "Portico", "#FF767671": "Portland Twilight", "#FFA28C82": "Portobello", "#FF9D928A": "Portobello Mushroom", "#FFF4F09B": "Portofino", "#FFF0D3DF": "Portrait Pink", "#FFC4957A": "Portrait Tone", "#FF768482": "Portsmouth", "#FFA1ADAD": "Portsmouth Bay", "#FF5B7074": "Portsmouth Blue", "#FF6B6B44": "Portsmouth Olive", "#FFA75546": "Portsmouth Spice", "#FF3C5E95": "Portuguese Blue", "#FFC48F85": "Portuguese Dawn", "#FF717910": "Portuguese Green", "#FF143C5D": "Poseidon", "#FF66EEEE": "Poseidon Jr.", "#FFD9D6C7": "Poseidon\u2019s Beard", "#FF227478": "Poseidon\u2019s Castle", "#FFD59A54": "Poseidon\u2019s Gold", "#FF4400EE": "Poseidon\u2019s Territory", "#FFA5B4C6": "Posey Blue", "#FFEFB495": "Posh Peach", "#FF7D2621": "Posh Red", "#FFDFBBD9": "Posies", "#FFE1E2CF": "Positive Energy", "#FFAD2C34": "Positive Red", "#FF76745D": "Positively Palm", "#FFE7BFB6": "Positively Pink", "#FF7782A3": "Positively Purple", "#FF773355": "Possessed Plum", "#FF881166": "Possessed Purple", "#FFC2264D": "Possessed Red", "#FFF3DACE": "Possibly Pink", "#FFC8CDD8": "Post Apocalyptic Cloud", "#FF7A99AD": "Post Boy", "#FF0074B4": "Post It", "#FFFFEE01": "Post Yellow", "#FF134682": "Poster Blue", "#FF006B56": "Poster Green", "#FFECC100": "Poster Yellow", "#FFB39C8E": "Postmodern Mauve", "#FFC7C4CD": "Posture & Pose", "#FF466F97": "Postwar Boom", "#FFF3E1D3": "Posy", "#FF366254": "Posy Green", "#FFF3879C": "Posy Petal", "#FF6E6386": "Posy Purple", "#FF161616": "Pot Black", "#FFF9F4E6": "Pot of Cream", "#FFF6CD23": "Pot of Gold", "#FFE07757": "Potash", "#FFFDDC57": "Potato Chip", "#FF532D45": "Potent Purple", "#FFD5CDE3": "Potentially Purple", "#FF8F3129": "Potion \u2116 9", "#FF9ECCA7": "Potted Plant", "#FF938A2A": "Potter Green", "#FFA64826": "Potter\u2019s Clay", "#FFC2937B": "Potter\u2019s Pink", "#FF845C40": "Potter\u2019s Pot", "#FF88564C": "Potter\u2019s Rock", "#FF54A6C2": "Pottery Blue", "#FFB9714A": "Pottery Clay", "#FFB05D59": "Pottery Red", "#FFAA866E": "Pottery Urn", "#FFCAAC91": "Pottery Wheel", "#FFA0A089": "Potting Moss", "#FF5B3E31": "Potting Soil", "#FFE68E96": "Poudretteite Pink", "#FFDECCE4": "Pouf of Pink", "#FFFDF1C3": "Pound Cake", "#FF818081": "Pound Sterling", "#FFFB9B82": "Pouring Copper", "#FFE4CCC3": "Pout", "#FFFF82CE": "Pout Pink", "#FFF9A7A4": "Pouty Pink", "#FFE7D7EF": "Pouty Purple", "#FFD8948B": "Powder Blush", "#FFDFD7CA": "Powder Cake", "#FFB7B7BC": "Powder Dust", "#FFBFC2CE": "Powder Lilac", "#FF9CB3B5": "Powder Mill", "#FFFDD6E5": "Powder Pink", "#FFFFEFF3": "Powder Puff", "#FF95396A": "Powder Red", "#FFEEE0DD": "Powder Room", "#FFF5B3BC": "Powder Rose", "#FFF7F1DD": "Powder Sand", "#FFB9C9D7": "Powder Soft Blue", "#FFCBC2D3": "Powder Viola", "#FFD9D3E5": "Powder Viola White", "#FFEBF1F5": "Powder White", "#FFF9F2E7": "Powdered", "#FFC9AB9A": "Powdered Allspice", "#FFF8DCDB": "Powdered Blush", "#FFAC9B9B": "Powdered Brick", "#FF341C02": "Powdered Cocoa", "#FFA0450E": "Powdered Coffee", "#FFE8D2B1": "Powdered Gold", "#FFC3C9E6": "Powdered Granite", "#FFC5C56A": "Powdered Green Tea", "#FFA0B0A4": "Powdered Gum", "#FFFDE2D1": "Powdered Peach", "#FFE3C7C6": "Powdered Petals", "#FFC7D6D0": "Powdered Pool", "#FFB7B38D": "Powdered Sage", "#FFF8F4E6": "Powdered Snow", "#FFE4E0EB": "Powdery Mist", "#FFA2A4A6": "Power Grey", "#FFD4D1C7": "Power Lunch", "#FF332244": "Power Outage", "#FFEE5588": "Power Peony", "#FF66303C": "Power Tie", "#FFBBB7AB": "Powered Rock", "#FF4C3F5D": "Powerful Mauve", "#FF372252": "Powerful Violet", "#FFC9B29C": "Practical Beige", "#FFE1CBB6": "Practical Tan", "#FF679A7C": "Practice Green", "#FFC2A593": "Pragmatic", "#FF0B9D6A": "Prairie", "#FF935444": "Prairie Clay", "#FF516678": "Prairie Denim", "#FF937067": "Prairie Dog", "#FFFBD5BD": "Prairie Dune", "#FFCEC5AD": "Prairie Dusk", "#FFB9AB8F": "Prairie Dust", "#FF996E5A": "Prairie Fire", "#FF6E5F45": "Prairie Foliage", "#FFB1A38E": "Prairie Grass", "#FF50A400": "Prairie Green", "#FF8E7D5D": "Prairie Grove", "#FFD3C9AD": "Prairie House", "#FFE2CC9C": "Prairie Land", "#FFAE5F55": "Prairie Poppy", "#FFF2C8BE": "Prairie Rose", "#FFB3A98C": "Prairie Sage", "#FF883C32": "Prairie Sand Variant", "#FFC6D7E0": "Prairie Sky", "#FFEEA372": "Prairie Sun", "#FFFFB199": "Prairie Sunset", "#FFD2D8B5": "Prairie Willow", "#FFE8E6D9": "Prairie Winds", "#FFB2B1AE": "Praise Giving", "#FF221155": "Praise of Shadow", "#FFF3F4D9": "Praise the Sun", "#FFAD8B75": "Praline", "#FFC58380": "Prancer", "#FF002F0F": "Prasinophobia", "#FFF6F7ED": "Praxeti White", "#FFD59C6A": "Prayer Flag", "#FFA5BE8F": "Praying Mantis", "#FFB5C2CD": "Pre School", "#FF8B7F7A": "Pre-Raphaelite", "#FFF1DAB2": "Precious", "#FF008389": "Precious Blue", "#FF885522": "Precious Copper", "#FFF5F5E4": "Precious Dewdrop", "#FF186E50": "Precious Emerald", "#FFB7757C": "Precious Garnet", "#FF613A57": "Precious Jewels", "#FFFFDE9C": "Precious Nectar", "#FF6D9A79": "Precious Oxley", "#FFF1F0EF": "Precious Pearls", "#FFBD4048": "Precious Peony", "#FFFF7744": "Precious Persimmon", "#FFF6B5B6": "Precious Pink", "#FFE16233": "Precious Pumpkin", "#FF328696": "Precious Stone", "#FF2C3944": "Precision", "#FFE8DEE3": "Precocious Red", "#FFE5DBCB": "Predictable", "#FF6D6E7B": "Prediction", "#FF5772B0": "Prefect", "#FF546762": "Prehistoric", "#FFEE2211": "Prehistoric Meteor", "#FFC3738D": "Prehistoric Pink", "#FF9AA0A3": "Prehistoric Stone", "#FF5E5239": "Prehistoric Wood", "#FFD0A700": "Prehnite Yellow", "#FFDFEBEE": "Prelude Variant", "#FFE1DEDA": "Prelude Tint", "#FFE6B6BE": "Premium Pink", "#FFD1668F": "Preppy Rose", "#FF665864": "Preservation Plum", "#FF4A3C50": "Preserve", "#FFB0655A": "Preserved Petals", "#FF3E4D59": "Presidential", "#FFEC9580": "Presidio Peach", "#FFBB9174": "Presidio Plaza", "#FF634875": "Presley Purple", "#FF606B77": "Press Agent", "#FFC2968B": "Pressed Blossoms", "#FFD492BD": "Pressed Flower", "#FFFEFE22": "Pressed Laser Lemon", "#FFEFABA6": "Pressed Rose", "#FF00CC11": "Pressing My Luck", "#FFB8A7A0": "Prestige", "#FF303742": "Prestige Blue", "#FF154647": "Prestige Green", "#FF4C213D": "Prestige Mauve", "#FF5E6277": "Presumption", "#FF4444FF": "Pretentious Peacock", "#FFE5A3C5": "Prettiest Pink", "#FFE9AF9B": "Pretty in Peach", "#FFFABFE4": "Pretty in Pink", "#FFCC5588": "Pretty in Plum", "#FF6B295A": "Pretty in Prune", "#FFC3A1B6": "Pretty Lady", "#FF849457": "Pretty Maiden", "#FFE3C6D6": "Pretty Pale", "#FFAC5D3E": "Pretty Parasol", "#FFDFCDB2": "Pretty Pastry", "#FFAE5D6D": "Pretty Peony", "#FFD6B7E2": "Pretty Petunia", "#FFEEAADD": "Pretty Pink Piggy", "#FFFFCCC8": "Pretty Please", "#FFBCBDE4": "Pretty Posie", "#FFF5A994": "Pretty Primrose", "#FF7B6065": "Pretty Puce", "#FFC4BBD6": "Pretty Purple", "#FFE4B197": "Pretty Rascal", "#FF254770": "Pretty Twilight Night", "#FFE5A68D": "Priceless Coral", "#FF46373F": "Priceless Purple", "#FFA89942": "Prickly Pear", "#FF69916E": "Prickly Pear Cactus", "#FFF42C93": "Prickly Pink", "#FFA264BA": "Prickly Purple", "#FFE2CDD5": "Prim Variant", "#FFCBA792": "Primal", "#FF0081B5": "Primal Blue", "#FF11875D": "Primal Green", "#FFF4301C": "Primal Rage", "#FFA92B4F": "Primal Red", "#FF0804F9": "Primary Blue", "#FF6FA77A": "Primavera", "#FF0064A1": "Prime Blue", "#FF92B979": "Prime Merchandise", "#FFFF8D86": "Prime Pink", "#FF656293": "Prime Purple", "#FF685E4E": "Primitive", "#FFDED6AC": "Primitive Green", "#FF663C55": "Primitive Plum", "#FF7CBC6C": "Primo", "#FFD6859F": "Primrose Variant", "#FFF3949B": "Primrose Garden", "#FFFFE262": "Primrose Path", "#FFE7C2CA": "Primrose Pink", "#FFECE4D0": "Primrose White", "#FFF6D155": "Primrose Yellow", "#FFCA9FA5": "Primula", "#FF4B384C": "Prince", "#FFCC2277": "Prince Charming", "#FFA0ADAC": "Prince Grey", "#FF9D7957": "Prince Paris", "#FF60606F": "Prince Royal", "#FF7D4961": "Princely", "#FF6D5C7B": "Princely Violet", "#FFF0A8B5": "Princess", "#FF0056A1": "Princess Blue", "#FFCCEEEE": "Princess Blue Feather", "#FFF4C1C1": "Princess Bride", "#FFF6E9EA": "Princess Elle", "#FFE5DBEB": "Princess Fairy Tale", "#FFFAEAD5": "Princess Ivory", "#FFF878F8": "Princess Peach", "#FFFF85CF": "Princess Perfume", "#FFDFB5B0": "Princess Pink", "#FF756F54": "Priory", "#FFF1D3DA": "Priscilla", "#FFAADCCD": "Prism", "#FFF0A1BF": "Prism Pink", "#FF53357D": "Prism Violet", "#FF117777": "Prismarine", "#FFEAE8DD": "Prismatic Pearl", "#FF005C77": "Prismatic Springs", "#FFFDAA48": "Prison Jumpsuit", "#FFF2E8DA": "Pristine", "#FF00CCBB": "Pristine Oceanic", "#FFD5E1E0": "Pristine Petal", "#FF007799": "Pristine Seas", "#FF807658": "Private Affair", "#FF4C4949": "Private Black", "#FF006E89": "Private Eye", "#FF4F531F": "Private Green", "#FF889DB2": "Private Jet", "#FF845469": "Private Tone", "#FFF3EBD9": "Private White", "#FFD9B2C7": "Private-Label Pink", "#FF768775": "Privet", "#FF588A79": "Privet Hedge", "#FF7A8775": "Privilege Green", "#FFF3EAD7": "Privileged", "#FF597695": "Privileged Elite", "#FFCC9DC6": "Prize Winning Orchid", "#FFA2C0B9": "Prized Celadon", "#FF393540": "Professor Plum", "#FF40243D": "Profound Mauve", "#FFC39297": "Profound Pink", "#FFDAA5AA": "Prom", "#FFE7C3E7": "Prom Corsage", "#FFC4A99D": "Prom Night", "#FFE9A9BB": "Prom Pink", "#FF9B1DCD": "Prom Queen", "#FFF8F6DF": "Promenade", "#FFF4581E": "Prometheus Orange", "#FF2B7DA6": "Prominent Blue", "#FFDA9EC5": "Prominent Pink", "#FFBB11EE": "Promiscuous Pink", "#FFACC0E2": "Promise", "#FFAFC7E8": "Promise Keeping", "#FF686278": "Promised Amethyst", "#FF5E7FB5": "Prompt", "#FFADA8A5": "Proper Grey", "#FF59476B": "Proper Purple", "#FFEDE5C7": "Proper Temperature", "#FF4B5667": "Property", "#FF6F58A6": "Prophet Violet", "#FFBE8B8F": "Prophetess", "#FF624F59": "Prophetic Purple", "#FF818B9C": "Prophetic Sea", "#FFE0B4A4": "Prosciutto", "#FF62584B": "Prospect", "#FF915F66": "Prosperity", "#FF66543E": "Prot\u00e9g\u00e9 Bronze", "#FFFF8866": "Protein High", "#FF840804": "Proton Red", "#FFE0C778": "Protoss", "#FF00AAFF": "Protoss Pylon", "#FF658DC6": "Provence", "#FF8A9C99": "Provence Blue", "#FFFFEDCB": "Provence Creme", "#FF827191": "Provence Violet", "#FF5C7B8C": "Providence", "#FFA8ACA2": "Provincial", "#FF5E7B91": "Provincial Blue", "#FFF6E3DA": "Provincial Pink Variant", "#FF4C5079": "Provocative", "#FFD4C6DB": "Prudence", "#FF701C11": "Prune", "#FF211640": "Prune Plum", "#FF6C445B": "Prune Purple", "#FF864788": "Prunella", "#FFDD4492": "Prunus Avium", "#FF3F585F": "Prussian", "#FF0B085F": "Prussian Nights", "#FF6F4B5C": "Prussian Plum", "#FFDD00FF": "Psychedelic Purple", "#FF625981": "Psychic", "#FFCE5DAE": "P\u00fa T\u00e1o Z\u01d0 Purple", "#FFFF1177": "Pucker Up", "#FFA0A6A0": "Puddle", "#FF6A8389": "Puddle Jumper", "#FF6E3326": "Pueblo Variant", "#FFE9786E": "Pueblo Rose", "#FFE7C3A4": "Pueblo Sand", "#FFE5DFCD": "Pueblo White", "#FF54927E": "Puerto Princesa", "#FF59BAA3": "Puerto Rico Variant", "#FF635940": "Puff Dragon", "#FFFFCBEE": "Puff of Pink", "#FFFCCF8B": "Puff Pastry Yellow", "#FFCCBFC9": "Puffball", "#FFE2DADF": "Puffball Vapour", "#FFE95C20": "Puffins Bill", "#FFD2DEF2": "Puffy Cloud", "#FFD7EDEA": "Puffy Little Cloud", "#FFE7E5DE": "Puffy Pillow", "#FF7722CC": "Puissant Purple", "#FFCBB5B2": "Puka Shell", "#FF6E8D98": "Pulitzer Blue", "#FFF1D6BC": "Pulled Taffy", "#FF3B331C": "Pullman Green", "#FFE18289": "Pulp", "#FF01678D": "Pulsating Blue", "#FF96711C": "Puma", "#FFBAC0B4": "Pumice Variant", "#FF807375": "Pumice Grey", "#FFCAC2B9": "Pumice Stone", "#FF6C462D": "Pumpernickel", "#FFF7504A": "Pumping Spice", "#FFCBA077": "Pumpkin Butter", "#FFEB7B07": "Pumpkin Cat", "#FF8D2D13": "Pumpkin Choco", "#FFE6C8A9": "Pumpkin Cream", "#FFB96846": "Pumpkin Drizzle", "#FFF7DAC0": "Pumpkin Essence", "#FF286848": "Pumpkin Green", "#FF183425": "Pumpkin Green Black", "#FFF6A379": "Pumpkin Hue", "#FFF2C3A7": "Pumpkin Mousse", "#FFFB7D07": "Pumpkin Orange", "#FFD59466": "Pumpkin Patch", "#FFE99E56": "Pumpkin Pie", "#FFEDC994": "Pumpkin Pie Oh My!", "#FFFFFDD8": "Pumpkin Seed", "#FFE17701": "Pumpkin Soup", "#FFB2691A": "Pumpkin Spice", "#FFCE862F": "Pumpkin Squash", "#FFDE9456": "Pumpkin Toast", "#FFFFA74F": "Pumpkin Vapour", "#FFE99A10": "Pumpkin Yellow", "#FFECD086": "Punch of Yellow", "#FF6888FC": "Punch Out Glove", "#FFB68692": "Punched Pink", "#FF56414D": "Punchit Purple", "#FF856B71": "Punctuate", "#FF534931": "Punga Variant", "#FF8811FF": "Punk Rock Pink", "#FFBB11AA": "Punk Rock Purple", "#FFB2485B": "Punky Pink", "#FF070303": "Pupil", "#FF79CCB3": "Puppeteers", "#FFBCAEA0": "Puppy", "#FFE2BABF": "Puppy Love", "#FFEAD2BC": "Puppy Paws", "#FFC687D6": "Purception", "#FFDFC6A5": "Pure Almond", "#FF6AB54B": "Pure Apple", "#FFE9D0C4": "Pure Beige", "#FF595652": "Pure Black", "#FF0203E2": "Pure Blue", "#FFABA093": "Pure Cashmere", "#FF36BFA8": "Pure Cyan", "#FFA79480": "Pure Earth", "#FFFAEAE1": "Pure Frost", "#FFFF2255": "Pure Hedonist", "#FFFDF5CA": "Pure Laughter", "#FF036C91": "Pure Light Blue", "#FF6F5390": "Pure Mauve", "#FF112266": "Pure Midnight", "#FFB40039": "Pure Passion", "#FFF51360": "Pure Pleasure", "#FF751973": "Pure Purple", "#FFD22D1D": "Pure Red", "#FFFFEE15": "Pure Sunshine", "#FF7ABEC2": "Pure Turquoise", "#FFF8F8F2": "Pure White", "#FF58503C": "Pure Woody", "#FF615753": "Pure Zeal", "#FF67707D": "Purebred", "#FFC74222": "Pureed Pumpkin", "#FFC3DCE9": "Purification", "#FFA8B0AE": "Puritan Grey", "#FFD7C9E3": "Purity", "#FF988EB4": "Purple Agate", "#FF9190BA": "Purple Amethyst", "#FF8866FF": "Purple Anemone", "#FFC20078": "Purple Anxiety", "#FF8F8395": "Purple Ash", "#FF9D9EB4": "Purple Balance", "#FF625B87": "Purple Balloon", "#FF5C4450": "Purple Basil", "#FF4C4A74": "Purple Berry", "#FF4A455D": "Purple Blanket", "#FF544258": "Purple Bloom", "#FF661AEE": "Purple Blue Variant", "#FF673A3F": "Purple Brown", "#FF3D34A5": "Purple Cabbage", "#FFA83A9A": "Purple Cactus Flower", "#FFC4ADC9": "Purple Chalk", "#FFB67CA2": "Purple Cheeks", "#FF8800FF": "Purple Climax", "#FF6E6970": "Purple Comet", "#FF5A4E8F": "Purple Corallite", "#FF593C50": "Purple Cort", "#FFD7CBD7": "Purple Cream", "#FFE7E7EB": "Purple Crystal", "#FF771166": "Purple Curse", "#FF835177": "Purple Davenport", "#FF63647E": "Purple Daze", "#FF331144": "Purple Door", "#FF917F84": "Purple Dove", "#FF754260": "Purple Drab", "#FFC6BEDD": "Purple Dragon", "#FF660066": "Purple Dreamer", "#FF7C6B76": "Purple Dusk", "#FF624646": "Purple Earth", "#FF6633BB": "Purple Emperor", "#FF5A4D55": "Purple Empire", "#FFEADCE2": "Purple Emulsion", "#FFC2B1C8": "Purple Essence", "#FF943589": "Purple Excellency", "#FF594670": "Purple Feather", "#FF880099": "Purple Feather Boa", "#FF85598A": "Purple Fluorite", "#FFACB0CA": "Purple Freedom", "#FF50599F": "Purple Frenzy", "#FF747582": "Purple Funk", "#FF5C4D5C": "Purple Fury", "#FFC4ABD4": "Purple Gala", "#FF8397D0": "Purple Gentian", "#FFC1ABD4": "Purple Gladiola", "#FF736993": "Purple Grapes", "#FF866F85": "Purple Grey", "#FF6A6283": "Purple Gumball", "#FF835F79": "Purple Gumdrop", "#FF807396": "Purple Haze", "#FF69359C": "Purple Heart Variant", "#FFCC2288": "Purple Heart Kiwi", "#FFBAB8D3": "Purple Heather", "#FF8773BB": "Purple Hebe", "#FFAA66FF": "Purple Hedonist", "#FFCCAAFF": "Purple Hepatica", "#FFB6ADD8": "Purple Hibiscus", "#FF8E85A7": "Purple Hills", "#FFD96CAD": "Purple Hollyhock", "#FF8855FF": "Purple Honeycreeper", "#FF6E8FC0": "Purple Hyacinth", "#FFB8B8F8": "Purple Illusion", "#FFA675FE": "Purple Illusionist", "#FF7C89AB": "Purple Impression", "#FF9A2CA0": "Purple Ink", "#FF73626F": "Purple Kasbah", "#FF512C31": "Purple Kite", "#FFCC77CC": "Purple Kush", "#FFB88AAC": "Purple Lepidolite", "#FF653475": "Purple Magic", "#FF9A8891": "Purple Mauve", "#FFDBD1E2": "Purple Mist", "#FF7A70A8": "Purple Mountain Majesty", "#FF9D81BA": "Purple Mountains\u2019 Majesty", "#FF815989": "Purple Mystery", "#FF322C56": "Purple Noir", "#FF643E65": "Purple Odyssey", "#FF60569A": "Purple Opulence", "#FFAD4D8C": "Purple Orchid", "#FF79669D": "Purple Paradise", "#FF645E77": "Purple Passage", "#FF784674": "Purple Passion", "#FF5C2E88": "Purple Patch", "#FF452E4A": "Purple Pennant", "#FF5B4763": "Purple People Eater", "#FF903F75": "Purple Peril", "#FF9483A8": "Purple Phantom", "#FFC83CB9": "Purple Pink", "#FFBB00AA": "Purple Pirate", "#FFC7CEE8": "Purple Pj\u2019s", "#FF81459E": "Purple Pleasures", "#FF473854": "Purple Plumeria", "#FFDAB4CC": "Purple Poodle", "#FF4C4976": "Purple Pool", "#FFAA00AA": "Purple Potion", "#FFB9A0D2": "Purple Premiere", "#FFA274B5": "Purple Pride", "#FF5B4D54": "Purple Prince", "#FF7733AA": "Purple Pristine", "#FFBB9ECA": "Purple Prophet", "#FF543254": "Purple Prose", "#FF593569": "Purple Prot\u00e9g\u00e9", "#FF8822DD": "Purple Protest", "#FF523E49": "Purple Province", "#FF696374": "Purple Punch", "#FFC9C6DF": "Purple Purity", "#FF8C8798": "Purple Ragwort", "#FF7442C8": "Purple Rain", "#FF990147": "Purple Red", "#FF5C4971": "Purple Reign", "#FF8278AD": "Purple Rhapsody", "#FFAF9FCA": "Purple Rose", "#FF8B7880": "Purple Rubiate", "#FF75697E": "Purple Sage", "#FFC2B2F0": "Purple Sand", "#FF754B8F": "Purple Sapphire", "#FF522A6F": "Purple Seduction", "#FF4E2E53": "Purple Shade", "#FFC8BAD4": "Purple Shine", "#FF776D90": "Purple Silhouette", "#FF62547E": "Purple Sky", "#FFCC69E4": "Purple Snail", "#FF563948": "Purple Sphinx", "#FF746F9D": "Purple Spire", "#FF5E3B6A": "Purple Splendour", "#FFAB9BBC": "Purple Springs", "#FF845998": "Purple Squid", "#FFB16D90": "Purple Starburst", "#FF6E5755": "Purple Statement", "#FFA885B5": "Purple Statice", "#FF624154": "Purple Stiletto", "#FF605467": "Purple Stone", "#FFB183A8": "Purple Stripe", "#FF766478": "Purple Suede", "#FF853682": "Purple Sultan", "#FF9B95A9": "Purple Surf", "#FF835995": "Purple Tanzanite", "#FFF0B9BE": "Purple Thorn", "#FFA22DA4": "Purple Tone Ink", "#FF665261": "Purple Trinket", "#FFC364C5": "Purple Urn Orchid", "#FF665B80": "Purple Valley", "#FFD3D5E0": "Purple Veil", "#FF581A57": "Purple Velour", "#FF483B56": "Purple Velvet", "#FF46354B": "Purple Verbena", "#FFA29CC8": "Purple Vision", "#FF442244": "Purple Void", "#FFD4C0DF": "Purple Whisper", "#FFD3C2CF": "Purple White", "#FF97397F": "Purple Wine", "#FF5A395B": "Purple Wineberry", "#FFA846D7": "Purple Yam", "#FFDD1166": "Purple Yearning", "#FFA15589": "Purple Zergling", "#FFEEC3EE": "Purple\u2019s Baby Sister", "#FFB6C4DD": "Purpletini", "#FF837F92": "Purpletone", "#FF602287": "Purplex", "#FFC8BDD2": "Purpling Dawn", "#FF98568D": "Purplish", "#FF6140EF": "Purplish Blue", "#FF6B4247": "Purplish Brown", "#FF7A687F": "Purplish Grey", "#FFDF4EC8": "Purplish Pink", "#FFB0054B": "Purplish Red", "#FFDFD3E3": "Purplish White", "#FF5E0DC2": "Purplue", "#FF776C76": "Purposeful", "#FF8D8485": "Purpura", "#FF9A4EAE": "Purpureus", "#FF864480": "Purpurite Red", "#FF57385E": "Purpurite Violet", "#FF898078": "Purri Sticks", "#FF879F6C": "Purslane", "#FFF1D260": "Pursuit of Happiness", "#FFCEBADA": "Pussyfoot", "#FFB2ADA4": "Pussywillow", "#FFA2A193": "Pussywillow Grey", "#FFC8DDEA": "Put on Ice", "#FF8D4362": "Putnam Plum", "#FF89A572": "Putrid Green", "#FFF1E4C9": "Putting Bench", "#FF3A9234": "Putting Green", "#FFCDAE70": "Putty Variant", "#FFBDA89C": "Putty Grey", "#FFA99891": "Putty Pearl", "#FF9D8E7F": "Putty Yellow", "#FFADA2CE": "Puturple", "#FF55FF55": "Puyo Blob Green", "#FFD6D0CF": "Pygmy Goat", "#FF6299AA": "Pyjama Blue", "#FF9FBADF": "Pylon", "#FF9F7D4F": "Pyramid", "#FFE5B572": "Pyramid Gold", "#FFF8C642": "Pyrite", "#FFAC9362": "Pyrite Gold", "#FF3A6364": "Pyrite Green", "#FF867452": "Pyrite Slate Green", "#FFC4BF33": "Pyrite Yellow", "#FF3776AB": "Python Blue", "#FFFFD343": "Python Yellow", "#FF7B5804": "Qahvei Brown", "#FFCF3C71": "Qermez Red", "#FF89A0B0": "Qi\u0101n H\u016bi Grey", "#FF4455EE": "Qing Dynasty Dragon", "#FFDD2266": "Qing Dynasty Fire", "#FFFFCC66": "Qing Yellow", "#FFFFE989": "Quack Quack", "#FF998811": "Quagmire Green", "#FF96838B": "Quail", "#FFE3DDCE": "Quail Egg", "#FF5C4E53": "Quail Hollow", "#FFACA397": "Quail Ridge", "#FFAB9673": "Quail Valley", "#FFEACDC1": "Quaint Peche", "#FF686C62": "Quaking Bog", "#FFBBC6A4": "Quaking Grass", "#FF6E799B": "Quantum Blue", "#FFB2DDC4": "Quantum Effect", "#FF7C948B": "Quantum Green", "#FF130173": "Quantum of Light", "#FFE7F1E6": "Quark White", "#FFEBE6D5": "Quarried Limestone", "#FF8A9399": "Quarry", "#FFAF9A91": "Quarry Quartz", "#FFF2EDDD": "Quarter Pearl Lusta Variant", "#FFEBE2D2": "Quarter Spanish White Variant", "#FF1272A3": "Quarterdeck", "#FF85695B": "Quartersawn Oak", "#FFD9D9F3": "Quartz", "#FF6E7C45": "Quartz Green", "#FFFDEAE6": "Quartz Light Pink", "#FFA9AAAB": "Quartz Sand", "#FFE8E8E5": "Quartz Stone", "#FFF3E8E1": "Quartz White", "#FF232E26": "Quartzite", "#FFC7D0DA": "Quarzo", "#FFBED3CB": "Quaver", "#FFFAB001": "Queen Anne", "#FFC0B6B4": "Queen Anne Lilac", "#FFF2EEDE": "Queen Anne\u2019s Lace", "#FFE8BC95": "Queen Conch Shell", "#FF77613D": "Queen Lioness", "#FFBBDD55": "Queen of Gardens", "#FF98333A": "Queen of Hearts", "#FF817699": "Queen of Sheba", "#FF295776": "Queen of the Night", "#FF3D4585": "Queen of the Night Shift", "#FF1C401F": "Queen of Trees", "#FFD2EAEA": "Queen of Waves", "#FFAD9E4B": "Queen Palm", "#FF6C7068": "Queen Valley", "#FF7B6FA0": "Queen\u2019s", "#FF9D433F": "Queen\u2019s Coat", "#FF472557": "Queen\u2019s Domain", "#FF8B5776": "Queen\u2019s Honour", "#FF753A40": "Queen\u2019s Rose", "#FFD3BCC5": "Queen\u2019s Tart", "#FFCDB9C4": "Queen\u2019s Violet", "#FFD3ACCE": "Queenly", "#FFF9ECD0": "Queenly Laugh", "#FF652793": "Queens of the Dead", "#FF88ACE0": "Queer Blue", "#FFB36FF6": "Queer Purple", "#FFB4E0E7": "Quench Blue", "#FFE5B03D": "Quercitron", "#FFBDC1C1": "Quest", "#FFADA5A5": "Quest Grey", "#FFEF9A49": "Question Mark Block", "#FF006868": "Quetzal Green", "#FFB393C0": "Quibble", "#FFFED56F": "Quiche Lorraine", "#FFBDDBE1": "Quick-Freeze", "#FFAC9884": "Quicksand Variant", "#FF160435": "Quiet Abyss", "#FF6597CC": "Quiet Bay", "#FF017AA6": "Quiet Cove", "#FFD7D9D5": "Quiet Dew", "#FFB7D0C5": "Quiet Drizzle", "#FF9EBC97": "Quiet Green", "#FFB9BABD": "Quiet Grey", "#FF5A789A": "Quiet Harbour", "#FFDDD7CC": "Quiet Interlude", "#FFB1E2CB": "Quiet Jade", "#FFCED3C7": "Quiet Mint", "#FF96AEB0": "Quiet Moment", "#FF3E8FBC": "Quiet Night", "#FFACDEEB": "Quiet Nook", "#FFE4E2DD": "Quiet on the Set", "#FFDBA39A": "Quiet Pink", "#FF94D8E2": "Quiet Pond", "#FFE7EFCF": "Quiet Rain", "#FFB69C97": "Quiet Refuge", "#FF686970": "Quiet Shade", "#FFF5EBD6": "Quiet Shore", "#FFFAE6CA": "Quiet Splendour", "#FFEECC9F": "Quiet Star", "#FF2F596D": "Quiet Storm", "#FFA9BAB1": "Quiet Teal", "#FFAAAD94": "Quiet Thicket", "#FFB8BCB8": "Quiet Time", "#FFEBD2A7": "Quiet Veranda", "#FFB9BEA3": "Quiet Waters", "#FFF1F3E8": "Quiet Whisper", "#FFADBBB2": "Quietude", "#FFDCD2B8": "Quill", "#FFCBC9C0": "Quill Grey", "#FF2D3359": "Quill Tip", "#FF612741": "Quills of Terico", "#FF7F9DAF": "Quilotoa Blue", "#FF70A38D": "Quilotoa Green", "#FFFCD9C6": "Quilt", "#FFEAC365": "Quilt Gold", "#FFCE9FB4": "Quilted Heart", "#FFD4CB60": "Quince", "#FFF89330": "Quince Jelly", "#FF6A5445": "Quincy Variant", "#FFB5B5AF": "Quincy Granite", "#FFF9ECD1": "Quinoa", "#FFF5E326": "Quinoline Yellow", "#FF008CA9": "Quintana", "#FFC2DBC6": "Quintessential", "#FFC76356": "Quite Coral", "#FF97171E": "Quite Red", "#FF9BE510": "Quithayran Green", "#FF886037": "Quiver", "#FF8E7F6A": "Quiver Tan", "#FF948491": "Quixotic", "#FF4A4653": "Quixotic Plum", "#FF5F575C": "Rabbit", "#FF885D62": "Rabbit Paws", "#FF6225B8": "Rabbit-Ear Iris", "#FFD2C1B5": "Rabbit\u2019s Foot", "#FF735E56": "Raccoon Tail", "#FFCF4944": "Race Car Stripe", "#FFEEF3D0": "Race the Sun", "#FFCBBEB5": "Race Track", "#FFE8B9AE": "Rachel Pink", "#FF014600": "Racing Green Variant", "#FFC21727": "Racing Red", "#FFD6341E": "Rackham Red", "#FF5D8AAA": "Rackley", "#FF776A3C": "Racoon Eyes", "#FFB6C8E4": "Radar", "#FF96F97B": "Radar Blip Green", "#FFBB9157": "Radiance", "#FFECE2CE": "Radiant Dawn", "#FF5500FF": "Radiant Feather", "#FF659C35": "Radiant Foliage", "#FFFFEED2": "Radiant Glow", "#FF10F144": "Radiant Hulk", "#FFA489A0": "Radiant Lilac", "#FFE0E6F0": "Radiant Moon", "#FFAD5E99": "Radiant Orchid", "#FFE31B5D": "Radiant Raspberry", "#FFEED5D4": "Radiant Rose", "#FFD7B1B2": "Radiant Rouge", "#FF8F979D": "Radiant Silver", "#FFF0CC50": "Radiant Sun", "#FFEEBE1B": "Radiant Sunrise", "#FFFC9E21": "Radiant Yellow", "#FFFFA343": "Radiation Carrot", "#FF326A2B": "Radical Green", "#FF745166": "Radicchio", "#FF694D3A": "Radigan Conagher Brown", "#FF89FE05": "Radioactive", "#FFF9006F": "Radioactive Eggplant", "#FF2CFA1F": "Radioactive Green", "#FF66DD00": "Radioactive Lilypad", "#FFA42E41": "Radish", "#FFEE3355": "Radish Lips", "#FFEC4872": "Radishical", "#FFE5E7E6": "Radisson", "#FFFFD15C": "Radler", "#FFF1C7A1": "Radome Tan", "#FFDCC6A0": "Raffia Variant", "#FFCDA09A": "Raffia Cream", "#FFB3A996": "Raffia Greige", "#FFCBD9D8": "Raffia Light Grey", "#FFCBB08C": "Raffia Ribbon", "#FFEEEEE3": "Raffia White", "#FFCA9A5D": "Raffles Tan", "#FF3C5F9B": "Raftsman", "#FFFF1133": "Rage", "#FFF32507": "Rage of Quel\u2019Danas", "#FF8D514C": "Ragin\u2019 Cajun", "#FFB54E45": "Raging Bull", "#FFDD5500": "Raging Leaf", "#FFAA3333": "Raging Raisin", "#FF8D969E": "Raging Sea", "#FF004F63": "Raging Thunderstorm", "#FF5187A0": "Raging Tide", "#FF4A5E6C": "Ragtime Blues", "#FF513933": "Ragtop", "#FF7BE892": "Ragweed", "#FFF6AD3A": "Raichu Orange", "#FF0056A8": "Raiden Blue", "#FFE34029": "Raiden\u2019s Fury", "#FF544540": "Railroad Ties", "#FFABBEBF": "Rain", "#FF8B795F": "Rain Barrel", "#FF354D65": "Rain Boots", "#FFB6D5DE": "Rain Check", "#FF919FA1": "Rain Cloud", "#FF685346": "Rain Drum", "#FFB5DCEA": "Rain or Shine", "#FF677276": "Rain Shadow", "#FFBCA849": "Rain Slicker", "#FFC5D5E9": "Rain Song", "#FFF6BFBC": "Rainbow", "#FF2863AD": "Rainbow Bright", "#FFFF975C": "Rainbow Trout", "#FFFF09FF": "Rainbow\u2019s Inner Rim", "#FFFF0001": "Rainbow\u2019s Outer Rim", "#FF819392": "Raindance", "#FF9EC6C6": "Raindrop", "#FFB3C1B1": "Rainee Variant", "#FF759180": "Rainford", "#FF009A70": "Rainforest", "#FFE6DAB1": "Rainforest Dew", "#FFCEC192": "Rainforest Fern", "#FFB2C346": "Rainforest Glow", "#FF446019": "Rainforest Matcha", "#FF002200": "Rainforest Nights", "#FF016D43": "Rainforest Retreat", "#FF7F795F": "Rainforest Zipline", "#FF558484": "Rainier Blue", "#FF485769": "Rainmaker", "#FF9CA4A9": "Rainmaster", "#FF244653": "Rainstorm", "#FFC2CDC5": "Rainwashed", "#FF87D9D2": "Rainwater", "#FF889A95": "Rainy Afternoon", "#FFCFC8BD": "Rainy Day", "#FFA5A5A5": "Rainy Grey", "#FF3F6C8F": "Rainy Lake", "#FF4499AA": "Rainy Mood", "#FF005566": "Rainy Morning", "#FFD1D8D6": "Rainy Season", "#FF9BAFBB": "Rainy Sidewalk", "#FF99BBDD": "Rainy Week", "#FF5D4A4E": "Raisin", "#FF78615C": "Raisin in the Sun", "#FFE6D9E2": "Rajah Rose", "#FF957D48": "Raked Leaves", "#FFBF794E": "Rakuda Brown", "#FF7EC083": "Rally Green", "#FFB7A9AC": "Ramadi Grey", "#FF5A804F": "Rambling Green", "#FFD98899": "Rambling Rose", "#FFA94737": "Rambutan", "#FFCDBDA2": "Ramie", "#FF4C73AF": "Ramjet", "#FF8F9A88": "Ramona", "#FF603231": "Rampant Rhubarb", "#FFBCB7B1": "Rampart", "#FF195456": "Ramsons", "#FFF3E7CF": "Ranch Acres", "#FF9E7454": "Ranch Brown", "#FF7B645A": "Ranch House", "#FF968379": "Ranch Mink", "#FFC8B7A1": "Ranch Tan", "#FFDFD8B3": "Rancho Verde", "#FFB7B7B4": "Rand Moon", "#FF756E60": "Randall", "#FF68BD56": "Range Land", "#FF6A8472": "Ranger Green", "#FF707651": "Ranger Station", "#FF2B2E25": "Rangoon Green Variant", "#FFF7ECD8": "Ranier White", "#FFF5DDE6": "Ranunculus White", "#FFD28239": "Rapakivi Granite", "#FFC19A13": "Rapeseed", "#FFFFEC47": "Rapeseed Blossom", "#FFA69425": "Rapeseed Oil", "#FFA39281": "Rapid Rock", "#FF52C7C7": "Rapids", "#FFD8DFDA": "Rapier Silver", "#FF45363A": "Rapt", "#FF114444": "Rapture", "#FFA7D6DD": "Rapture Blue", "#FFCF5A70": "Rapture Rose", "#FFF6F3E7": "Rapture\u2019s Light", "#FFF6D77F": "Rapunzel", "#FFD2D2D4": "Rapunzel Silver", "#FF0044FF": "Rare Blue", "#FFE1DEE8": "Rare Crystal", "#FFAC8044": "Rare Find", "#FFA6A69B": "Rare Grey", "#FF8DACA0": "Rare Happening", "#FFDBDCE2": "Rare Orchid", "#FFDD1133": "Rare Red", "#FFCC0022": "Rare Rhubarb", "#FF00748E": "Rare Turquoise", "#FFE8E9CC": "Rare White Jade", "#FFE8DBDF": "Rare White Raven", "#FF55FFCC": "Rare Wind", "#FF3B2736": "Rare Wine", "#FF594C42": "Rare Wood", "#FFE1E6E6": "Rarified Air", "#FFB00149": "Raspberry Tint", "#FF875169": "Raspberry Crush", "#FF8E3643": "Raspberry Fool", "#FF915F6C": "Raspberry Glace", "#FFFF77AA": "Raspberry Glaze", "#FFD9CCC7": "Raspberry Ice", "#FF9F3753": "Raspberry Ice Red", "#FFCA3767": "Raspberry Jam", "#FF9B6287": "Raspberry Jelly Red", "#FFC9A196": "Raspberry Kahlua", "#FF044F3B": "Raspberry Leaf Green", "#FFE1AAAF": "Raspberry Lemonade", "#FF9A1A60": "Raspberry Magenta", "#FFEBD2D1": "Raspberry Milk", "#FFE06F8B": "Raspberry Mousse", "#FFB96482": "Raspberry Parfait", "#FFA34F66": "Raspberry Patch", "#FF94435A": "Raspberry Pudding", "#FF882D50": "Raspberry Radiance", "#FFF62217": "Raspberry Rave", "#FFCD827D": "Raspberry Ripple", "#FF972B51": "Raspberry Romantic", "#FFB3436C": "Raspberry Rose Variant", "#FFFF3888": "Raspberry Shortcake", "#FFD0A1B4": "Raspberry Smoothie", "#FFD2386C": "Raspberry Sorbet", "#FF8A5D55": "Raspberry Truffle", "#FFB3737F": "Raspberry Whip", "#FFB63753": "Raspberry Wine", "#FF885F01": "Rat Brown", "#FF6F6138": "Rationality", "#FFA58E61": "Rattan", "#FFA79069": "Rattan Basket", "#FF8F876B": "Rattan Palm", "#FF7F7667": "Rattlesnake", "#FFC35530": "Raucous Orange", "#FF54463F": "Rave Raisin", "#FFA13B34": "Rave Red", "#FF00619D": "Rave Regatta", "#FF3D3D3D": "Raven Black", "#FF6F747B": "Raven Grey", "#FF3B3F66": "Raven Night", "#FFBB2255": "Raven\u2019s Banquet", "#FF030205": "Raven\u2019s Coat", "#FF4B4045": "Raven\u2019s Wing", "#FF0A0555": "Ravenclaw", "#FF464543": "Ravenwood", "#FFD3CEC7": "Ravine", "#FFFADE79": "Ravioli al Limone", "#FFE79580": "Ravishing Coral", "#FFBB2200": "Ravishing Rouge", "#FFF2EED3": "Raw Alabaster", "#FF544173": "Raw Amethyst", "#FFC8BEB1": "Raw Cashew Nut", "#FF662200": "Raw Chocolate", "#FF7D403B": "Raw Cinnabar", "#FFC46B51": "Raw Copper", "#FFE3D4BB": "Raw Cotton", "#FF9E7172": "Raw Edge", "#FF8B6C7E": "Raw Garnet Viola", "#FFCC8844": "Raw Linen", "#FFE4BC8C": "Raw Peanut", "#FF9A6200": "Raw Sienna Variant", "#FFE1D9C7": "Raw Silk", "#FFD8CAB2": "Raw Sugar", "#FFF95D2D": "Raw Sunset", "#FFA75E09": "Raw Umber Variant", "#FF865E49": "Rawhide", "#FF7A643F": "Rawhide Canoe", "#FFFDF2C0": "Ray of Light", "#FFF4C454": "Rayo de Sol", "#FF7197CB": "Razee", "#FFD1768C": "Razzberries", "#FFE1D5D4": "Razzberry Fizz", "#FFBA417B": "Razzle Dazzle", "#FFF08101": "R\u00e8 D\u00e0i Ch\u00e9ng Orange", "#FFDD484E": "Re-Entry", "#FFCD0317": "Re-Entry Red", "#FF7D5D5E": "Reading Tea Leaves", "#FF7BA570": "Ready Lawn", "#FF563D2D": "Real Brown", "#FFC1A17F": "Real Cork", "#FF00577E": "Real Mccoy", "#FFDD79A2": "Real Raspberry", "#FFA90308": "Real Red", "#FFCCB896": "Real Simple", "#FF45657D": "Real Teal", "#FF008A4C": "Real Turquoise", "#FFD3C8BD": "Realist Beige", "#FFE9EADB": "Really Light Green", "#FFE8ECDB": "Really Rain", "#FFC2B1C5": "Really Romantic", "#FF016367": "Really Teal", "#FF796C70": "Realm", "#FF114411": "Realm of the Underworld", "#FF453430": "Rebel Variant", "#FFCD4035": "Rebel Red", "#FF9B7697": "Rebel Rouser", "#FFCC0404": "Rebellion Red", "#FF28A8CD": "Reboot", "#FFBAD56B": "Rebounder", "#FF4A4E5C": "Receding Night", "#FFBAB6AB": "Reclaimed Wood", "#FFB7D7BF": "Reclining Green", "#FF1A525B": "Recollection Blue", "#FFCDB6A0": "Recycled", "#FFB7C3B7": "Recycled Glass", "#FFFF0F0F": "Red Alert", "#FFE44E4D": "Red Arremer", "#FFBB0011": "Red Baron", "#FF8E3738": "Red Bay", "#FFDD3300": "Red Bell Pepper", "#FF701F28": "Red Berry Variant", "#FF9D2B22": "Red Birch", "#FFD13B40": "Red Bliss", "#FF8A3C38": "Red Blooded", "#FF824E46": "Red Bluff", "#FFA52A2F": "Red Brown", "#FF534A77": "Red Cabbage", "#FF833C3D": "Red Candle", "#FFFF3322": "Red Card", "#FFBC2026": "Red Carpet", "#FFD87678": "Red Cedar", "#FFAD654C": "Red Cent", "#FFED7777": "Red Chalk", "#FF883543": "Red Chicory", "#FF95372D": "Red Chilli", "#FF834C3E": "Red Chipotle", "#FFC10C27": "Red City of Morocco", "#FF8F4B41": "Red Clay", "#FF77413D": "Red Clay Hill", "#FFBB8580": "Red Clover", "#FFD43E38": "Red Clown", "#FFB33234": "Red Contrast", "#FF91433E": "Red Craft", "#FFE45E32": "Red Cray", "#FF8B5E52": "Red Curry", "#FF85222B": "Red Dahlia", "#FFCB6F4A": "Red Damask Variant", "#FFBB012D": "Red Dead Redemption", "#FFBA2A1A": "Red Death", "#FFAC0000": "Red Door", "#FFE8DEDB": "Red Dust", "#FFA2816E": "Red Earth", "#FF85464B": "Red Elegance", "#FFE9DBDE": "Red Emulsion", "#FF794D60": "Red Endive", "#FFD00000": "Red Epiphyllum", "#FFC54831": "Red Fang", "#FFFF2244": "Red Flag", "#FFD76968": "Red Geranium", "#FFB07473": "Red Gerbera", "#FF604046": "Red Gooseberry", "#FFAD1400": "Red Gore", "#FFB8866E": "Red Gravel", "#FFB83312": "Red Gravy", "#FF99686A": "Red Grey", "#FFAC3A3E": "Red Gumball", "#FF8A453B": "Red Hawk", "#FFDD1144": "Red Herring", "#FF845544": "Red Hook", "#FFDD0033": "Red Hot", "#FFDB1D27": "Red Hot Chili Pepper", "#FF773C31": "Red Hot Jazz", "#FFC93543": "Red Icon", "#FFBB1E1E": "Red Inferno", "#FFAC3235": "Red Ink", "#FF783F54": "Red Jade", "#FFC01141": "Red Jalape\u00f1o", "#FF913228": "Red Kite", "#FFDD0011": "Red Knuckles", "#FFAB4D50": "Red Leather", "#FF881400": "Red Leever", "#FFFF0055": "Red Light Neon", "#FFD24B38": "Red Lightning", "#FFBFBAC0": "Red Lilac Purple", "#FFB4090D": "Red Lust", "#FF663B43": "Red Mahogany", "#FFF95554": "Red Mana", "#FF6D3D2A": "Red Mane", "#FF834C4B": "Red Maple Leaf", "#FFAA2121": "Red Menace", "#FFC92B1E": "Red Mist", "#FFFF8888": "Red Mull", "#FF880022": "Red Mulled Wine", "#FF994341": "Red My Mind", "#FFB63731": "Red Obsession", "#FF953334": "Red Ochre", "#FF773243": "Red Octopus", "#FF473442": "Red Onion", "#FFCD6D57": "Red Orpiment", "#FF5D1F1E": "Red Oxide Variant", "#FFC34B1B": "Red Panda", "#FFBB0044": "Red Paracentrotus", "#FF82383C": "Red Pear", "#FFDD0000": "Red Pegasus", "#FFA60000": "Red Pentacle", "#FF7A3F38": "Red Pepper", "#FFF6B894": "Red Perfume", "#FF72423F": "Red Pines", "#FFFA2A55": "Red Pink", "#FF7C2949": "Red Plum", "#FF995D50": "Red Potato", "#FFDD143D": "Red Potion", "#FFD63D3B": "Red Power", "#FF8E3928": "Red Prairie", "#FFBB1100": "Red Prayer Flag", "#FFD92849": "Red Prickly Pear", "#FFEE3344": "Red Radish", "#FFEE3322": "Red Rampage", "#FF91403D": "Red Red Red", "#FF814142": "Red Red Wine", "#FF800707": "Red Reign", "#FFFFE0DE": "Red Remains", "#FFD70200": "Red Republic", "#FFA8453B": "Red Revival", "#FFCA1B1B": "Red Rhapsody", "#FFB9271C": "Red Rider", "#FFFE2713": "Red Riding Hood", "#FFB95543": "Red River", "#FF7D4138": "Red Robin Variant", "#FFA65052": "Red Rock", "#FFA27253": "Red Rock Falls", "#FFB29E9D": "Red Rock Panorama", "#FF7E5146": "Red Rooster", "#FF693F43": "Red Rumour", "#FFE5CAC0": "Red Sandstorm", "#FFCC3B22": "Red Sauce Parlour", "#FFEE0128": "Red Savina Pepper", "#FFC92D21": "Red Seal", "#FFB9090F": "Red Sentinel", "#FF862808": "Red Shade Wash", "#FFFEE0DA": "Red Shimmer", "#FF874C62": "Red Shrivel", "#FFC8756D": "Red Sparowes", "#FFAA262A": "Red Square", "#FFAD522E": "Red Stage Variant", "#FFFF2222": "Red Stop", "#FFCC1133": "Red Tape", "#FFB8383B": "Red Team Spirit", "#FFAE4930": "Red Terra", "#FF6E3637": "Red Theatre", "#FFBE0119": "Red Tolumnia Orchid", "#FF8B2E08": "Red Tone Ink", "#FFC44D4F": "Red Trillium", "#FFB41B40": "Red Tuna Fruit", "#FF783B38": "Red Velvet", "#FF5F383C": "Red Vine", "#FF9E0168": "Red Violet Variant", "#FF9F1A1D": "Red Vitality", "#FF765952": "Red Wattle Hog", "#FF885A55": "Red Willow", "#FFDB5947": "Red Wire", "#FFEE1155": "Red Wrath", "#FFE0180C": "Red Wrath of Zeus", "#FFDD1155": "Red-Eye", "#FFDD2233": "Red-Handed", "#FFA91F29": "Red-Hot Mama", "#FFCC0055": "Red-Letter Day", "#FFA27547": "Red-Tailed-Hawk", "#FFBB2211": "Redalicious", "#FF94332F": "Redbox", "#FFAD5E65": "Redbud", "#FF88455E": "Redcurrant", "#FF9C6E63": "Reddened Earth", "#FF9B4045": "Reddest Red", "#FFC44240": "Reddish", "#FFFFBB88": "Reddish Banana", "#FF433635": "Reddish Black", "#FF7F2B0A": "Reddish Brown", "#FF997570": "Reddish Grey", "#FFF8481C": "Reddish Orange", "#FFFE2C54": "Reddish Pink", "#FF910951": "Reddish Purple", "#FFFFF8D5": "Reddish White", "#FF6E1005": "Reddy Brown", "#FFAE8E7E": "Redend Point", "#FFF5D6D8": "Redneck", "#FFEA8A7A": "Redolenc", "#FF9D4E34": "Redridge Brown", "#FFA24D47": "Redrock Canyon", "#FFFF3A2D": "Redshift", "#FFE46B71": "Redstone", "#FFD90B0B": "Redsurrection", "#FFAF4544": "Redtail", "#FFE7E9D8": "Reduced Green", "#FFEFDEDF": "Reduced Red", "#FFDAECE7": "Reduced Turquoise", "#FFF0EAD7": "Reduced Yellow", "#FFE55E58": "Reductant", "#FF98010D": "Redwing", "#FF5B342E": "Redwood Tint", "#FF916F5E": "Redwood Forest", "#FFFF2200": "Red\u042fum", "#FFB0AD96": "Reed Bed", "#FFA1A14A": "Reed Green", "#FFCD5E3C": "Reed Mace", "#FFDCC79E": "Reed Yellow", "#FFA0BCA7": "Reeds", "#FF017371": "Reef Variant", "#FF93BDCF": "Reef Blue", "#FF00968F": "Reef Encounter", "#FF0474AD": "Reef Escape", "#FFA98D36": "Reef Gold Variant", "#FFA5E1C4": "Reef Green", "#FFD1EF9F": "Reef Refractions", "#FF274256": "Reef Resort", "#FF6F9FA9": "Reef Waters", "#FFF1EAE2": "Refer", "#FF8C1B3C": "Refined Chianti", "#FF384543": "Refined Green", "#FFF1F9EC": "Refined Mint", "#FFAF6879": "Refined Rose", "#FF234251": "Reflecting Pond", "#FFDCDFDC": "Reflecting Pool", "#FFDB899E": "Reflecting Rose", "#FFD3D5D3": "Reflection", "#FFCADBDF": "Reflection Pool", "#FFA1D4C8": "Refresh", "#FFCFE587": "Refreshed", "#FF617A74": "Refreshing Green", "#FFB7E6E6": "Refreshing Pool", "#FFD7FFFE": "Refreshing Primer", "#FFEBDDA6": "Refreshing Tea", "#FFBADFCD": "Refrigerator Green", "#FF607D84": "Refuge", "#FFDAC2B3": "Regal", "#FF6A76AF": "Regal Azure", "#FF2E508A": "Regal Destiny", "#FF655777": "Regal Gown", "#FFA282A9": "Regal Orchid", "#FF99484A": "Regal Red", "#FF9D7374": "Regal Rose", "#FF749E8F": "Regal View", "#FFA298A2": "Regal Violet", "#FF7DB5D3": "Regale Blue", "#FF664480": "Regality", "#FF5382BB": "Regatta", "#FF2D5367": "Regatta Bay", "#FFE1BB87": "Regency Cream", "#FFA78881": "Regency Rose", "#FF798488": "Regent Grey", "#FFA0CDD9": "Regent St Blue Variant", "#FFD2AA92": "Regina Peach", "#FF000200": "Registration Black", "#FF009999": "Regula Barbara Blue", "#FFF7250B": "Reign of Tomatoes", "#FF76679E": "Reign Over Me", "#FFCA6C4D": "Reikland", "#FFDAC0BA": "Reindeer", "#FFBDF8A3": "Reindeer Moss", "#FFC4C7A5": "Rejuvenate", "#FFA4A783": "Rejuvenation", "#FFB9D2D3": "Relax", "#FFA8D19E": "Relaxation Green", "#FF698A97": "Relaxed Blue", "#FFC8BBA3": "Relaxed Khaki", "#FF627377": "Relaxed Navy", "#FFBBAAAA": "Relaxed Rhino", "#FF899DAA": "Relaxing Blue", "#FFE0EADB": "Relaxing Green", "#FFAC7C2E": "Release the Hounds", "#FF71713E": "Relentless Olive", "#FFE8DED3": "Reliable White", "#FF88789B": "Relic", "#FF906A3A": "Relic Bronze", "#FFBF2133": "Relief", "#FFE9DBDF": "Relieved Red", "#FFFF2288": "Reliquial Rose", "#FFB3CBAA": "Relish", "#FF8A4C38": "Remaining Embers", "#FFEFE7D6": "Remarkable Beige", "#FF974F49": "Rembrandt Ruby", "#FF8A4536": "Remember Me Red", "#FFCA9E9C": "Remembrance", "#FFA25D4C": "Remington Rust", "#FFEBC8B8": "Renaissance Fresco", "#FF865560": "Renaissance Rose", "#FF820747": "Renanthera Orchid", "#FF9B4B20": "Rendang", "#FFABBED0": "Rendez-Blue", "#FF8F968B": "Renegade", "#FF9CB8B5": "Renew Blue", "#FFB55233": "Renga Brick", "#FF989F7A": "Renkon Beige", "#FFB87F84": "Rennie\u2019s Rose", "#FFB26E33": "Reno Sand Variant", "#FFDABE9F": "Renoir Bisque", "#FFC3B09D": "Renwick Beige", "#FF504E47": "Renwick Brown", "#FF96724C": "Renwick Golden Oak", "#FF8B7D7B": "Renwick Heather", "#FF97896A": "Renwick Olive", "#FFAF8871": "Renwick Rose Beige", "#FFCCC9C0": "Repose Grey", "#FF24DA91": "Reptile Green", "#FF5E582B": "Reptile Revenge", "#FF009E82": "Reptilian Green", "#FF8F9961": "Reptilian Leader", "#FFDE0100": "Republican", "#FF4E3F44": "Requiem", "#FFB9B2A9": "Requisite Grey", "#FF9DA98C": "Reseda", "#FF75946B": "Reseda Green", "#FF8C7544": "Reservation", "#FFAC8A98": "Reserve", "#FFE2E1D6": "Reserved Beige", "#FFD2D8DE": "Reserved Blue", "#FFE0E0D9": "Reserved White", "#FF01638B": "Reservoir", "#FF85B0C4": "Resolute Blue", "#FF01A2C6": "Resonant Blue", "#FFF4D7C5": "Resort Sunrise", "#FF907D66": "Resort Tan", "#FFF4F1E4": "Resort White", "#FFCD8E89": "Resounding Rose", "#FF97B4C3": "Respite", "#FFB4A8B1": "Resplendent", "#FF3D8B37": "Resplendent Growth", "#FF9BBFC9": "Rest Assured", "#FFBBBBA4": "Restful", "#FF8C7E6F": "Restful Brown", "#FFF1F2DD": "Restful Rain", "#FFB1C7C9": "Restful Retreat", "#FFEEE8D7": "Restful White", "#FFBCC8BE": "Resting Place", "#FF38515D": "Restless Sea", "#FF939581": "Restoration", "#FFE9E1CA": "Restoration Ivory", "#FFD2B084": "Restrained Gold", "#FFD9CDC3": "Reticence", "#FFB6C7E0": "Retina Soft Blue", "#FFD5EAE8": "Retiring Blue", "#FF7A8076": "Retreat", "#FFC39E81": "Retributor Armour Metal", "#FF9BDC96": "Retro", "#FF958D45": "Retro Avocado", "#FF2B62F4": "Retro Blue", "#FF19CC89": "Retro Lime", "#FF9FCDB1": "Retro Mint", "#FFEF7D16": "Retro Nectarine", "#FFE85112": "Retro Orange", "#FFE7C0AD": "Retro Peach", "#FFB48286": "Retro Pink", "#FFFF0073": "Retro Pink Pop", "#FFCB9711": "Retro Vibe", "#FF5A758A": "Retro-Colonial Blue", "#FF936630": "Retrofit", "#FF4C6B8A": "Revel Blue", "#FF67B8CE": "Revelry Blue", "#FFC59782": "Revenant Brown", "#FF8A7C75": "Revere Greige", "#FFA78FAF": "Revered", "#FFF4E5E1": "Reverie Pink", "#FF080808": "Reversed Grey", "#FF5F81A4": "Revival", "#FF665043": "Revival Mahogany", "#FF7F4E47": "Revival of the Red", "#FFC09084": "Revival Rose", "#FFE8E097": "Reviving Green", "#FF37363F": "Revolver Variant", "#FFB46848": "Reynard", "#FFDC9E94": "Rhapsodic", "#FF9C83A8": "Rhapsody", "#FF002244": "Rhapsody in Blue", "#FFBABFDC": "Rhapsody Lilac", "#FF74676C": "Rhapsody Rap", "#FF969565": "Rhind Papyrus", "#FF5F5E5F": "Rhine Castle", "#FFE3EADB": "Rhine Falls", "#FFAB3560": "Rhine River Rose", "#FFC87291": "Rhine Wine", "#FF8E6C94": "Rhinestone", "#FF3D4653": "Rhino Variant", "#FFD4A278": "Rhino Tusk", "#FF727A7C": "Rhinoceros", "#FF440011": "Rhinoceros Beetle", "#FF493435": "Rhinox Hide", "#FF9B5B55": "Rhode Island Red", "#FF89C0E6": "Rhodes", "#FF7D2F45": "Rhododendron", "#FFF3B3C5": "Rhodonite", "#FF4D4141": "Rhodonite Brown", "#FF330018": "Rhodophobia", "#FF7F222E": "Rhubarb", "#FFD9A6C1": "Rhubarb Gin", "#FFBCA872": "Rhubarb Leaf Green", "#FFD78187": "Rhubarb Pie", "#FF8C474B": "Rhubarb Smoothie", "#FFCB7841": "Rhumba Orange", "#FF383867": "Rhynchites Nitens", "#FFBECEB4": "Rhys", "#FF767194": "Rhythm Variant", "#FF70767B": "Rhythm & Blues", "#FFB8D5D7": "Rhythmic Blue", "#FF914D57": "Rialto", "#FFF1E7D5": "Rice Bowl", "#FFEFECDE": "Rice Cake Variant", "#FFE0CCB6": "Rice Crackers", "#FFB2854A": "Rice Curry", "#FFE4D8AB": "Rice Fibre", "#FFEFF5D1": "Rice Flower Variant", "#FFDFD4B0": "Rice Paddy", "#FFFFFCDB": "Rice Paper", "#FFFFF0E3": "Rice Pudding", "#FFF5E7C8": "Rice Wine", "#FF977540": "Rich and Rare", "#FF948165": "Rich Biscuit", "#FF021BF9": "Rich Blue", "#FF514142": "Rich Bordeaux", "#FF925850": "Rich Brocade", "#FF715E4F": "Rich Brown", "#FFD70041": "Rich Carmine Variant", "#FF6F4136": "Rich Chocolate", "#FFBF7D52": "Rich Copper", "#FFFAEAC3": "Rich Cream", "#FFF57F4F": "Rich Gardenia", "#FFDE7A63": "Rich Georgia Clay", "#FFFFE8A0": "Rich Glow", "#FFAA8833": "Rich Gold Variant", "#FF218845": "Rich Green", "#FF324943": "Rich Grey Turquoise", "#FFF9BC7D": "Rich Honey", "#FFFFF0C4": "Rich Ivory", "#FF583D37": "Rich Loam", "#FF604944": "Rich Mahogany", "#FFB0306A": "Rich Maroon", "#FF745342": "Rich Mocha", "#FFA7855A": "Rich Oak", "#FF3E4740": "Rich Olive", "#FF6B7172": "Rich Pewter", "#FFB5C9D3": "Rich Porcelain Blue", "#FF720058": "Rich Purple", "#FFFF1144": "Rich Red", "#FF7C3651": "Rich Red Violet", "#FFB9A37F": "Rich Reward", "#FFA87C3E": "Rich Sorrel", "#FFB39C89": "Rich Taupe", "#FF645671": "Rich Texture", "#FF50578B": "Rich Violet", "#FF7C5F4A": "Rich Walnut", "#FF854C47": "Richardson Brick", "#FFA6A660": "Rickrack", "#FF817C74": "Ricochet", "#FFF3CF64": "Ride off Into the Sunset", "#FFB2C8DD": "Ridge Light", "#FFA3AAB8": "Ridge View", "#FFEF985C": "Ridgeback", "#FF9D8861": "Ridgecrest", "#FF7A5D46": "Ridgeline", "#FF734A35": "Riding Boots", "#FFA0A197": "Riding Star", "#FFBFB065": "Riesling Grape", "#FFB2B3A7": "Rigani", "#FFA0A082": "Rigby Ridge", "#FFC0E1E4": "Right as Rain", "#FF534A32": "Rikan Brown", "#FF826B58": "Riky\u016b Brown", "#FF656255": "Riky\u016bnezumi Brown", "#FFB0927A": "Riky\u016bshira Brown", "#FFC7B39E": "Rincon Cove", "#FFB4D9D8": "Ring Box Blue", "#FFFBEDBE": "Ringlet", "#FFD5D9DD": "Rinse", "#FFFF7952": "Rinsed-Out Red", "#FFB7C61A": "Rio Grande Variant", "#FF92242E": "Rio Red", "#FF926956": "Rio Rust", "#FFCAE1DA": "Rio Sky", "#FFDFAB56": "Rip Cord", "#FF8FA4D2": "Rip Van Periwinkle", "#FF94312F": "Ripasso", "#FFCF9D41": "Ripe Banana", "#FF5C5666": "Ripe Berry", "#FFFBB363": "Ripe Cantaloupe", "#FFC3556F": "Ripe Cherry", "#FF8A3C3E": "Ripe Currant", "#FF492D35": "Ripe Eggplant", "#FF6A5254": "Ripe Fig", "#FF747A2C": "Ripe Green", "#FFA259CE": "Ripe Lavander", "#FFF5576C": "Ripe Malinka", "#FFFFC324": "Ripe Mango", "#FF62465B": "Ripe Mulberry", "#FF44483D": "Ripe Olive", "#FFBE6A3C": "Ripe Peach", "#FFE1E36E": "Ripe Pear", "#FFFFE07B": "Ripe Pineapple", "#FFFFAF37": "Ripe Pumpkin", "#FF7E3947": "Ripe Rhubarb", "#FFE3C494": "Ripe Wheat", "#FF6F3942": "Ripening Grape", "#FFD3DCDC": "Ripple", "#FFC4C5BC": "Rippled Rock", "#FFC4BEA8": "Rippling Stone", "#FF89D9C8": "Riptide Variant", "#FFFFE99E": "Rise and Shine", "#FF978888": "Rising Ash", "#FFF7F6D5": "Rising Star", "#FFCBD6D8": "Rising Tide", "#FF6A7E88": "Risk-Reward", "#FFF8F5E9": "Risotto", "#FF00AA55": "Rita Repulsa", "#FFBA7176": "Rita\u2019s Rouge", "#FFFFEBA9": "Rite of Spring", "#FF293286": "Ritterlich Blue", "#FF767081": "Ritual", "#FF533844": "Ritual Experience", "#FFD79C5F": "Ritzy", "#FF38AFCD": "River Blue", "#FFC7B5A9": "River Clay", "#FF545B45": "River Forest", "#FF248591": "River Fountain", "#FF6C6C5F": "River God", "#FFD6E1D4": "River Mist", "#FFA08B71": "River Mud", "#FFAE9474": "River Oak", "#FFE4B55D": "River of Gold", "#FFA39C92": "River Pebble", "#FFDEDBC4": "River Reed", "#FFAE8D6B": "River Road", "#FFD8CDC4": "River Rock", "#FFEC9B9D": "River Rouge", "#FFDFDCD5": "River Shark", "#FF161820": "River Styx", "#FF848B99": "River Tour", "#FF94A5B7": "River Valley", "#FFD9E0DE": "River Veil", "#FFAE9E87": "Riverbank", "#FF86BEBE": "Riverbed", "#FF486B65": "Riverbend", "#FFBEC5BA": "Riverdale", "#FF84A27B": "Rivergrass", "#FFAFD9D7": "Rivers Edge", "#FF4E6F95": "Riverside", "#FF6CB4C3": "Riverside Blue", "#FF757878": "Riverstone", "#FF5D7274": "Riverway", "#FFB7A9A2": "Riveter Rose", "#FF189FAC": "Riviera", "#FFC0AE96": "Riviera Beach", "#FF65B4D8": "Riviera Blue", "#FFC9A88D": "Riviera Clay", "#FFE6D9CA": "Riviera Dune", "#FF009A9E": "Riviera Paradise", "#FFD9C0A6": "Riviera Retreat", "#FFF7B1A6": "Riviera Rose", "#FFDFC6A0": "Riviera Sand", "#FF1B8188": "Riviera Sea", "#FFD5DAE1": "Rivulet", "#FF605655": "Rix", "#FF8B7B6E": "Road Less-Travelled", "#FFC8C0B7": "Roadrunner", "#FFADA493": "Roadside", "#FFE1E0D7": "Roadster White", "#FFF3DEA2": "Roadster Yellow", "#FF857373": "Roaming Pony", "#FF885156": "Roan Rouge", "#FF372B25": "Roanoke", "#FF8F837D": "Roanoke Taupe", "#FFB39C9E": "Roaring Twenties", "#FF704341": "Roast Coffee", "#FF785246": "Roasted", "#FFD2B49C": "Roasted Almond", "#FF655A5C": "Roasted Black", "#FFEA993B": "Roasted Chestnut", "#FFAB8C72": "Roasted Coconut", "#FF684134": "Roasted Coffee", "#FFAF967D": "Roasted Hazelnut", "#FF574037": "Roasted Kona", "#FF604D42": "Roasted Nuts", "#FF93542B": "Roasted Pecan", "#FF890A01": "Roasted Pepper", "#FFC9B27C": "Roasted Pistachio", "#FFE1A175": "Roasted Seeds", "#FF625342": "Roasted Sepia", "#FFFEC36B": "Roasted Sesame", "#FFEA7E5D": "Roasted Sienna", "#FFE6A255": "Roasted Squash", "#FF692302": "Roastery", "#FFDDAD56": "Rob Roy Variant", "#FF654F4F": "Robeson Rose", "#FF6DEDFD": "Robin\u2019s Egg", "#FFABC3CA": "Robin\u2019s Pride", "#FF6E6A3B": "Robinhood", "#FFADADAD": "Robo Master", "#FFEEEE11": "Robot Grendizer Gold", "#FF94A2B1": "Robotic Gods", "#FFC4633E": "Robust Orange", "#FF5A4D41": "Rock Variant", "#FF93A2BA": "Rock Blue Variant", "#FF484C49": "Rock Bottom", "#FFDEE1DF": "Rock Candy", "#FFC0AF92": "Rock Cliffs", "#FF688082": "Rock Creek", "#FFCECBCB": "Rock Crystal", "#FF465448": "Rock Garden", "#FFF00B52": "Rock Lobster", "#FFA1968C": "Rock Slide", "#FFBDC2BD": "Rock Solid", "#FF9D442D": "Rock Spray Variant", "#FFC58BBA": "Rock Star Pink", "#FF8B785C": "Rock\u2019n Oak", "#FFFC8AAA": "Rock\u2019n\u2019Rose", "#FF6C7186": "Rockabilly", "#FFF4E4E0": "Rockabye Baby", "#FFFEFBE5": "Rocket Fuel", "#FFBEBEC3": "Rocket Man", "#FFFF3311": "Rocket Science", "#FFAABBAA": "Rockfall", "#FFBC363C": "Rockin Red", "#FF721F37": "Rocking Chair", "#FF90413D": "Rocking Chair Red", "#FF31A2F2": "Rockman Blue", "#FFD3E0B1": "Rockmelon Rind", "#FF519EA1": "Rockpool", "#FF8E7D62": "Rocks of Normandy", "#FF6D7E42": "Rockwall Vine", "#FF443735": "Rockweed", "#FFC8E0BA": "Rockwood Jade", "#FFB3AEA8": "Rocky Bluffs", "#FFBABBAD": "Rocky Cliff", "#FF3E4D50": "Rocky Creek", "#FF567B8A": "Rocky Hill", "#FFA9867D": "Rocky Mountain", "#FFD4D4D2": "Rocky Mountain Mist", "#FF76443E": "Rocky Mountain Red", "#FFB5BFBD": "Rocky Mountain Sky", "#FFB27A47": "Rocky Racoon", "#FF918C86": "Rocky Ridge", "#FF5E706A": "Rocky River", "#FF64453C": "Rocky Road", "#FFB8BABA": "Rocky Shelter", "#FFC33846": "Rococco Red", "#FFDFD6C1": "Rococo Beige", "#FF977447": "Rococo Gold", "#FFFDDC5C": "Rodan Gold", "#FF7F5E46": "Rodeo", "#FFC7A384": "Rodeo Dust Variant", "#FFA24B3A": "Rodeo Red", "#FFA68E6D": "Rodeo Roundup", "#FFA78B74": "Rodeo Tan", "#FFB2B26C": "Rodham", "#FFA68370": "Roebuck", "#FF883311": "Rogan Josh", "#FF807A6C": "Rogue", "#FF4B5764": "Rogue Blue", "#FFBA9E88": "Rogue Cowboy", "#FFF8A4C0": "Rogue Pink", "#FF4E7376": "Rogue Waters", "#FF734A45": "Rohwurst", "#FFB57466": "Rojo Dust", "#FF4B3029": "Rojo Marr\u00f3n", "#FF8D4942": "Rojo Oxide", "#FF665343": "Rok\u014d Brown", "#FF8C7042": "Rokou Brown", "#FF407A52": "Rokush\u014d Green", "#FFBB5522": "Roland-Garros", "#FF8C8578": "Roller Coaster", "#FF0078BE": "Roller Coaster Chariot", "#FF8CFFDB": "Roller Derby", "#FF6D5698": "Rollick", "#FFA9BE9E": "Rolling Glen", "#FF7C7E6A": "Rolling Hills", "#FFA09482": "Rolling Pebble", "#FFA29367": "Rolling Prairie", "#FF5A6D77": "Rolling Sea", "#FF6D7876": "Rolling Stone Variant", "#FFBFD1C9": "Rolling Waves", "#FFC0D2AD": "Romaine", "#FFA38E00": "Romaine Green", "#FFD8625B": "Roman Variant", "#FF8C97A3": "Roman Bath", "#FFAB7F5B": "Roman Brick", "#FF514100": "Roman Bronze", "#FF7D6757": "Roman Coffee Variant", "#FFAC8760": "Roman Coin", "#FFF6F0E2": "Roman Column", "#FFEE1111": "Roman Empire Red", "#FFD19B2F": "Roman Gold", "#FFDAD1CE": "Roman Mosaic", "#FFDDD2BF": "Roman Plaster", "#FF524765": "Roman Purple", "#FFC0B5A0": "Roman Ruins", "#FFA49593": "Roman Snail", "#FF4D517F": "Roman Violet", "#FFB0ADA0": "Roman Wall", "#FFDEEDEB": "Roman White", "#FFF4F0E6": "Romance Variant", "#FF7983A4": "Romanesque", "#FFD0AF7A": "Romanesque Gold", "#FF3B0346": "Romanic Scene", "#FFB97DA8": "Romanov Mauve", "#FFFFC69E": "Romantic Variant", "#FFE0BEDF": "Romantic Ballad", "#FFE7E0EE": "Romantic Cruise", "#FFEED1CD": "Romantic D\u00e9j\u00e0 Vu", "#FFB23E4F": "Romantic Embers", "#FFA497A1": "Romantic Holiday", "#FF007C75": "Romantic Isle", "#FF9076AE": "Romantic Moment", "#FFFCD5CB": "Romantic Morn", "#FFDDBBDD": "Romantic Morning", "#FF96353A": "Romantic Night", "#FFCAC0CE": "Romantic Poetry", "#FFAA4488": "Romantic Rose", "#FFA2101B": "Romantic Thriller", "#FF991166": "Romantic Vampire", "#FFDACFC5": "Romantic White", "#FFE3D2CE": "Romeo", "#FFA4233C": "Romeo O Romeo", "#FFF48101": "Romesco", "#FF983D48": "Romp", "#FFDFC1A8": "Romulus", "#FFEAB852": "Ronchi Variant", "#FFA60044": "Rondo of Blood", "#FFA14743": "Roof Terracotta Variant", "#FF043132": "Roof Tile Green", "#FF9EAD92": "Rooftop Garden", "#FFAE3C29": "Rooibos Tea", "#FFC08650": "Rookwood Amber", "#FFA58258": "Rookwood Antique Gold", "#FF738478": "Rookwood Blue Green", "#FF7F614A": "Rookwood Brown", "#FF9A7E64": "Rookwood Clay", "#FF5F4D43": "Rookwood Dark Brown", "#FF565C4A": "Rookwood Dark Green", "#FF4B2929": "Rookwood Dark Red", "#FF979F7F": "Rookwood Jade", "#FF6E5241": "Rookwood Medium Brown", "#FF622F2D": "Rookwood Red", "#FF506A67": "Rookwood Sash Green", "#FF303B39": "Rookwood Shutter Green", "#FF975840": "Rookwood Terra Cotta", "#FF96463F": "Rooster Comb", "#FF81544A": "Root Beer", "#FFCFA46B": "Root Beer Float", "#FF290E05": "Root Brew", "#FF6B3226": "Root Brown", "#FFC83A40": "Root Chakra", "#FF5F4F3E": "Rooted", "#FF4E3C32": "Rootstock", "#FF8E593C": "Rope Variant", "#FFFE86A4": "Rosa", "#FFEFD9E1": "Rosa Bonito", "#FFC77579": "Rosa Chinensis", "#FFF0E3DF": "Rosa Vieja", "#FFA38887": "Rosaline Pearl", "#FFFAEADD": "Rosarian", "#FFD9BEBB": "Rosario Ridge", "#FF998871": "Roscoe Village", "#FFF7746B": "Ros\u00e9", "#FFB5ACAB": "Rose Ashes", "#FFF1C6CA": "Rose Aspect", "#FFE3CBC4": "Rose Beige", "#FFC6A499": "Rose Bisque", "#FF80565B": "Rose Branch", "#FF996C6E": "Rose Brocade", "#FFBC8E8F": "Rose Brown", "#FFF0738A": "Rose Cheeks", "#FFDAB09E": "Rose Cloud", "#FFDCB6B5": "Rose Coloured", "#FFE5C4C0": "Rose Coloured Glasses", "#FFEFE0DE": "Rose Cream", "#FFFF9EDE": "Rose Daphne", "#FFB38380": "Rose Dawn", "#FFCB8E81": "Rose de Mai", "#FFE099AD": "Rose Delight", "#FFEECFFE": "Rose Drag\u00e9e", "#FFE5A192": "Rose Dusk", "#FFCDB2A5": "Rose Dust Variant", "#FFE9A1B8": "Rose Elegance", "#FFC59EA1": "Rose Embroidery", "#FFF29B89": "Rose Essence", "#FFF1E8EC": "Rose Fantasy", "#FFFFECE9": "Rose Frost", "#FFF96653": "Rose Fusion", "#FFDA9EC8": "Rose Garden", "#FF865A64": "Rose Garland", "#FF960145": "Rose Garnet", "#FFE585A5": "Rose Glory", "#FFFFDBEB": "Rose Glow", "#FFECC5C0": "Rose Haze", "#FFDBB9B6": "Rose Hip Tonic", "#FFA6465B": "Rose Laffy Taffy", "#FFE8AEA2": "Rose Linen", "#FFECD7C6": "Rose Lotion", "#FFE33636": "Rose Madder", "#FFF4AAC7": "Rose Mallow", "#FFCEB9C4": "Rose Marble", "#FFA76464": "Rose Marquee", "#FFDD94A1": "Rose Marquis", "#FFAF9690": "Rose Mauve", "#FFC4989E": "Rose Meadow", "#FFECBFC9": "Rose Melody", "#FFF6D2D4": "Rose Milk", "#FFFFE9ED": "Rose Mochi", "#FFAC512D": "Rose of Sharon Variant", "#FFDFD4CC": "Rose Pearl", "#FFE6C1BB": "Rose Petal", "#FF7C383E": "Rose Pink Villa", "#FFC9A59F": "Rose Potpourri", "#FFD0A29E": "Rose Pottery", "#FFF7CAC1": "Rose Quartz Variant", "#FFC92351": "Rose Red", "#FFF4C0C6": "Rose Reminder", "#FFDD2255": "Rose Rush", "#FFCE858F": "Rose Sachet", "#FFF9C2CD": "Rose Shadow", "#FFE7AFA8": "Rose Silk", "#FFCEADA3": "Rose Smoke", "#FFE08395": "Rose Soiree", "#FFFBD3CD": "Rose Sorbet", "#FFC290C5": "Rose Souvenir", "#FFD3B6BA": "Rose Stain", "#FFFAE6E5": "Rose Sugar", "#FFFAE8E1": "Rose Tan", "#FFE1A890": "Rose Tattoo", "#FFB48993": "Rose Tea", "#FFFFDDDD": "Rose Tonic", "#FFAB4E5F": "Rose Vale Variant", "#FFF2DBD6": "Rose Vapour", "#FFC44D97": "Rose Violet", "#FFFBEEE8": "Rose White Variant", "#FF9D5C75": "Rose Wine", "#FFD9BCB7": "Rose Yoghurt", "#FFF7E5DB": "Roseate", "#FFE7B6BC": "Roseate Hues", "#FFE0ADC4": "Roseate Spoonbill", "#FFCB9AAD": "Rosebay", "#FFF4A6A1": "Roseberry", "#FFE290B2": "Rosebloom", "#FFE0CDD1": "Rosebud", "#FF8A2D52": "Rosebud Cherry", "#FFEEBBDD": "Rosecco", "#FF7B7FA9": "Roseine Plum", "#FF926566": "Roseland", "#FFF0DEE4": "Roselle", "#FF819B4F": "Rosemarried", "#FF405E5C": "Rosemary", "#FF699B72": "Rosemary Green", "#FF626A52": "Rosemary Sprig", "#FFDFE3DB": "Rosemary White", "#FFBC8A90": "Rosenkavalier", "#FFAA3646": "Roses Are Red", "#FFE7AECD": "Roses in the Snow", "#FFBA8F7F": "Rosetta", "#FFCE8E8B": "Rosette", "#FFD7A491": "Rosettee", "#FFCF929A": "Rosetti", "#FFD8A2A5": "Rosetti Pink", "#FFB495B0": "Roseville", "#FFF6DBD8": "Rosewater", "#FFD39EA2": "Rosewood Apricot", "#FF72371C": "Rosewood Brown", "#FFEBBEB5": "Rosewood Dreams", "#FFFAC8CE": "Rosey Afterglow", "#FFFFBBCC": "Rosie", "#FFEFE4E9": "Rosie Posie", "#FFBE7583": "Rosily", "#FF39382F": "Rosin", "#FFB319AB": "Rosolanc Purple", "#FFC4091E": "Rossini Red", "#FFBE89A8": "Rosy", "#FFD8B3B9": "Rosy Aura", "#FFDC506E": "Rosy Cheeks", "#FFFEDECD": "Rosy Cloud", "#FFCF8974": "Rosy Copper", "#FFCC77FF": "Rosy Fluffy Bedspread", "#FFE7C0BC": "Rosy Glow", "#FFF7D994": "Rosy Highlight", "#FFD7C7D0": "Rosy Lavender", "#FFC6B4B2": "Rosy Linen", "#FFFEC9ED": "Rosy Maple Moth", "#FFF2C2E1": "Rosy Nectar", "#FFF5AB9E": "Rosy Outlook", "#FFD38469": "Rosy Pashmina", "#FFF6688E": "Rosy Pink", "#FFCF9384": "Rosy Queen", "#FF735551": "Rosy Sandstone", "#FFF7B978": "Rosy Skin", "#FFD95854": "Rosy Sunset", "#FFC8ABA7": "Rosy Tan", "#FFB69642": "Roti Variant", "#FFF5E0BF": "Rotunda Gold", "#FFD7D1C6": "Rotunda White", "#FFAB1239": "Rouge Variant", "#FF814D54": "Rouge Charm", "#FFA94064": "Rouge Like", "#FFE24666": "Rouge Red", "#FFEE2233": "Rouge Sarde", "#FFBDBEBF": "Rough Asphalt", "#FF7A8687": "Rough Ride", "#FF9EA2AA": "Rough Stone", "#FF97635F": "Roulette", "#FFEAD6AF": "Rousseau Gold", "#FF175844": "Rousseau Green", "#FF994400": "Roux", "#FFBFB8AD": "Row House", "#FFD2BB9D": "Row House Tan", "#FFCB0162": "Rowan", "#FFEAA007": "Rowdy Orange", "#FF8E8E6E": "Rowntree", "#FF7A5546": "Roxy Brown", "#FF0C1793": "Royal", "#FF550344": "Royal Ash", "#FFC74767": "Royal Banner", "#FF2F3844": "Royal Battle", "#FF105E80": "Royal Blood", "#FF082D4F": "Royal Blue Metallic", "#FF1600FC": "Royal Blue Tang", "#FFF26E54": "Royal Blush", "#FF275779": "Royal Breeze", "#FF523B35": "Royal Brown", "#FF16A5A3": "Royal Cloak", "#FF2E5686": "Royal Consort", "#FF3E3542": "Royal Coronation", "#FF4F325E": "Royal Crown", "#FF282A4A": "Royal Curtsy", "#FF403547": "Royal Decree", "#FF7B5867": "Royal Fig", "#FFA0365F": "Royal Flush", "#FFEE6600": "Royal Flycatcher Crest", "#FF747792": "Royal Fortune", "#FF653A3D": "Royal Garnet", "#FF9791BC": "Royal Glimmer", "#FFD4CA8E": "Royal Goblet", "#FFE1BC8A": "Royal Gold", "#FFD0A54E": "Royal Gold Pearl", "#FFA152BD": "Royal Gramma Purple", "#FF698396": "Royal Grey", "#FFB54B73": "Royal Heath Variant", "#FF4411EE": "Royal Hunter Blue", "#FF3F5948": "Royal Hunter Green", "#FF464B6A": "Royal Hyacinth", "#FF4E4260": "Royal Indigo", "#FF687094": "Royal Intrigue", "#FF553E42": "Royal Iris", "#FF787866": "Royal Ivy", "#FF84549B": "Royal Lilac", "#FF6E3D58": "Royal Lines", "#FF784D40": "Royal Liqueur", "#FFDA202A": "Royal Mail Red", "#FF543938": "Royal Maroon", "#FF545E97": "Royal Marquis", "#FFA1A0B7": "Royal Mile", "#FFF7CFB4": "Royal Milk Tea", "#FF45505B": "Royal Navy", "#FF1C3B42": "Royal Neptune", "#FF2B3191": "Royal Night", "#FF533CC6": "Royal Nightcore", "#FF879473": "Royal Oakleaf", "#FFFF7722": "Royal Oranje", "#FF5F6D59": "Royal Orchard", "#FF418D84": "Royal Palm", "#FF27ACE0": "Royal Peacock", "#FF355E5A": "Royal Pine", "#FF654161": "Royal Plum", "#FFA063A1": "Royal Pretender", "#FFAAA0BC": "Royal Proclamation", "#FF4B006E": "Royal Purple Variant", "#FF881177": "Royal Purpleness", "#FF806F72": "Royal Raisin", "#FF60456D": "Royal Rajah", "#FF8F7BB7": "Royal Raul", "#FF8E3C3F": "Royal Red Flush", "#FF678BC9": "Royal Regatta", "#FF614A7B": "Royal Robe", "#FFA54A4A": "Royal Rum", "#FF4B3841": "Royal Silk", "#FFE0DDDD": "Royal Silver", "#FFFEDE4F": "Royal Star", "#FF494256": "Royal Treatment", "#FF513E4D": "Royal Velvet", "#FF4433EE": "Royal Vessel", "#FF3E4967": "Royal Wave", "#FFFBE3E3": "Royal Wedding", "#FF463699": "Royal Wisteria", "#FFFADA50": "Royal Yellow", "#FF5930A9": "Royalty", "#FFAE58AB": "Royalty Loyalty", "#FFA76251": "Roycroft Adobe", "#FF324038": "Roycroft Bottle Green", "#FF7A6A51": "Roycroft Brass", "#FF575449": "Roycroft Bronze Green", "#FF7B3728": "Roycroft Copper Red", "#FFC2BDB1": "Roycroft Mist Grey", "#FF616564": "Roycroft Pewter", "#FFC08F80": "Roycroft Rose", "#FFA79473": "Roycroft Suede", "#FFE8D9BD": "Roycroft Vellum", "#FFF2A8B8": "Rozowy Pink", "#FFC11C84": "Rrosy-Fingered Dawn", "#FF5F5547": "Rub Elbows", "#FF815D37": "Rubber", "#FFFACF58": "Rubber Ducky", "#FFFFC5CB": "Rubber Rose", "#FFC04042": "Rubiate", "#FFC5B9B4": "Rubidium", "#FF6C383C": "Rubine", "#FFCA0147": "Ruby", "#FF975B60": "Ruby Crystal", "#FFD0A2A0": "Ruby Eye", "#FFF20769": "Ruby Fire", "#FF73525C": "Ruby Grey", "#FF813E45": "Ruby Lips", "#FF6F3B51": "Ruby Nights", "#FFC41A3B": "Ruby Passion", "#FFAA5B67": "Ruby Petals", "#FFB0063D": "Ruby Queen", "#FFA03D41": "Ruby Ring", "#FFA2566F": "Ruby Shade", "#FFBD1C30": "Ruby Shard", "#FF9C2E33": "Ruby Slippers", "#FFBB1122": "Ruby Star", "#FF5C384E": "Ruby Violet", "#FF85393D": "Ruby Wine", "#FFDB1459": "Rubylicious", "#FFEDB508": "Rucksack Tan", "#FFA5654E": "Ruddy Oak", "#FF894E45": "Rudraksha Beads", "#FF705749": "Rue Bourbon", "#FFF9F2DC": "Ruffled Clam", "#FFACC2D1": "Ruffled Feathers", "#FF9FA3C0": "Ruffled Iris", "#FFFBC0B4": "Ruffled Parasol", "#FFFBBBAE": "Ruffles", "#FFBB9D87": "Rugby Tan", "#FF998568": "Rugged", "#FF694336": "Rugged Brown", "#FF78736F": "Rugged Suede", "#FFB8A292": "Rugged Tan", "#FF254E5D": "Rugged Teal", "#FF484435": "Ruggero Grey", "#FF8C3F3E": "Ruggero Red", "#FF0F1012": "Ruined Smores", "#FFCADECE": "Ruins of Civilization", "#FF9B8B84": "Ruins of Metal", "#FF5E615B": "Ruins of Pompeii", "#FF774E55": "Rule Breaker", "#FF716675": "Rum Variant", "#FF450E15": "Rum Chocolate", "#FFE9CFAA": "Rum Custard", "#FFAA423A": "Rum Punch", "#FF683B3C": "Rum Raisin", "#FF990044": "Rum Riche", "#FFAA7971": "Rum Spice", "#FFF1EDD4": "Rum Swizzle Variant", "#FFC77B42": "Rumba Orange", "#FF902A40": "Rumba Red", "#FF5CA904": "Ruminant\u2019s Paradise", "#FF744245": "Rumours", "#FFDA2811": "Run Lola Run", "#FFD3AA94": "Rundlet Peach", "#FFC4C4C7": "Runefang Steel", "#FFB6A89A": "Runelord Brass", "#FFCAB2C1": "Runic Mauve", "#FF326193": "Running Water", "#FF99AF73": "Rural Eyes", "#FF8D844D": "Rural Green", "#FFBB1144": "Rural Red", "#FF1E50A2": "Ruri Blue", "#FF1B294B": "Rurikon Blue", "#FF536770": "Rush Hour", "#FF549EB9": "Rushing Rapids", "#FF8B3643": "Rushing Red", "#FF5F7797": "Rushing River", "#FF65C3D6": "Rushing Stream", "#FFB7B2A6": "Rushmore Grey", "#FF866C5E": "Ruskie", "#FF546B75": "Ruskin Blue", "#FF4D4D41": "Ruskin Bronze", "#FF935242": "Ruskin Red", "#FFACA17D": "Ruskin Room Green", "#FF547587": "Russ Grey", "#FFE7D6B1": "Russeau Gold", "#FF823D38": "Russet Brown", "#FFE3D9A0": "Russet Green", "#FF965849": "Russet Leather", "#FFE47127": "Russet Orange", "#FF9F6859": "Russet Red", "#FF9AAEBB": "Russian Blue", "#FF726647": "Russian Olive", "#FF4D4244": "Russian Red", "#FFD0C4AF": "Russian Toffee", "#FF5F6D36": "Russian Uniform", "#FFA83C09": "Rust Variant", "#FFA46454": "Rust Coloured", "#FFBB3344": "Rust Effect", "#FF966684": "Rust Magenta", "#FFC45508": "Rust Orange", "#FFAA2704": "Rust Red", "#FF60372E": "Rusted Crimson", "#FF8B5134": "Rusted Earth", "#FFAA4411": "Rusted Lock", "#FF884B3D": "Rusted Nail", "#FFD39A72": "Rustic Adobe", "#FF935848": "Rustic Brown", "#FF705536": "Rustic Cabin", "#FFBA9A67": "Rustic City", "#FFF6EFE2": "Rustic Cream", "#FF93674C": "Rustic Earth", "#FF9C8E74": "Rustic Hacienda", "#FF996E58": "Rustic Oak", "#FFDF745B": "Rustic Pottery", "#FF8D794F": "Rustic Ranch", "#FF3A181A": "Rustic Red Variant", "#FFCBB6A4": "Rustic Rose", "#FF9D2626": "Rustic Rouge", "#FFCDB9A2": "Rustic Taupe", "#FF6E5949": "Rustic Tobacco", "#FFB18A56": "Rustic Wicker", "#FF8A3A2C": "Rustica", "#FFF5BFB2": "Rustique", "#FFAD6961": "Rustling Leaves", "#FF96512A": "Rusty", "#FFC6494C": "Rusty Chainmail", "#FF8D5F2C": "Rusty Coin", "#FFAF6649": "Rusty Gate", "#FFA04039": "Rusty Heart", "#FFCC5522": "Rusty Nail Variant", "#FFCD5909": "Rusty Orange", "#FFE3DCE0": "Rusty Pebble", "#FFAF2F0D": "Rusty Red", "#FFEDB384": "Rusty Sand", "#FFA4493D": "Rusty Sword", "#FF719DA8": "Rusty Tap", "#FFEAD5B6": "Rutabaga", "#FF8D734F": "Rutherford", "#FF573894": "Ruthless Empress", "#FFD1AE7B": "Rye", "#FFCDBEA1": "Rye Bread", "#FF807465": "Rye Brown", "#FF807365": "Rye Dough Brown", "#FFBBB286": "Ryegrass", "#FFDCCB18": "Ryoku-Ou-Shoku Yellow", "#FFF3F1E1": "Ryu\u2019s Kimono", "#FFEC631A": "Ryza Dust", "#FFA87F5F": "S\u2019mores", "#FF496A4E": "Sabal Palm", "#FF6A7F7A": "Sabiasagi Blue", "#FF455859": "Sabionando Grey", "#FF898A74": "Sabiseiji Grey", "#FF784841": "Sable", "#FFF6D8BE": "Sabl\u00e9", "#FF946A52": "Sable Brown", "#FF505650": "Sable Calm", "#FFC4A7A1": "Sable Cloaked", "#FF5D5E58": "Sable Evening", "#FFECDFD6": "Sablewood", "#FF978D59": "Sabo Garden", "#FFD4B385": "Sabre Tooth", "#FFE69656": "Sabre-Toothed Tiger", "#FFA5D610": "Sabz Green", "#FFD4D8ED": "Sachet Cushion", "#FFEE9941": "Sacral Chakra", "#FF5E0E0B": "Sacramental Red", "#FF97B9E0": "Sacred Blue", "#FFB2865D": "Sacred Ground", "#FF7C8635": "Sacred Sapling", "#FF950C1B": "Sacred Scarlet", "#FFC7CBCE": "Sacred Spring", "#FF49B3A5": "Sacred Turquoise", "#FFA59C8D": "Sacred Vortex", "#FF2A5774": "Sacrifice", "#FF850101": "Sacrifice Altar", "#FF229911": "Sacro Bosco", "#FF5D4E46": "Saddle Variant", "#FF9F906E": "Saddle Soap", "#FFAB927A": "Saddle Up", "#FF8A534E": "Saddlebag", "#FFB6A58B": "Safari", "#FF8E7F6D": "Safari Beige", "#FF976C60": "Safari Brown", "#FFB7AA96": "Safari Chic", "#FF6C6D2F": "Safari Green", "#FFE2CCBA": "Safari Map", "#FFB4875E": "Safari Sun", "#FF8F7B51": "Safari Trail", "#FFB2A68F": "Safari Vest", "#FF1E8EA1": "Safe Harbour", "#FF5588AA": "Safe Haven", "#FFFDAE44": "Safflower", "#FFBB5548": "Safflower Bark", "#FF9A493F": "Safflower Kite", "#FFB44C97": "Safflower Purple", "#FFD9333F": "Safflower Red", "#FFE83929": "Safflower Scarlet", "#FFCCA6BF": "Safflower Wisteria", "#FF8491C3": "Safflowerish Sky", "#FF9C8AAB": "Saffron Blossom Mauve", "#FFE8E9CF": "Saffron Bread", "#FF584C77": "Saffron Crocus", "#FFC24359": "Saffron Desires", "#FFF08F00": "Saffron Gold", "#FFD49F4E": "Saffron Robe", "#FFFEB209": "Saffron Souffl\u00e9", "#FFD39952": "Saffron Strands", "#FFDA9E35": "Saffron Sunset", "#FFFFB301": "Saffron Thread", "#FFF2EAD6": "Saffron Tint", "#FFA7843E": "Saffron Valley", "#FFD09B2C": "Saffron Yellow", "#FF932A25": "Saffronaut", "#FF75A0B1": "Saga Blue", "#FF6A31CA": "Sagat Purple", "#FF87AE73": "Sage Tint", "#FF948B76": "Sage Advice", "#FF4E78A9": "Sage Blossom Blue", "#FFBAD3C7": "Sage Bundle", "#FF7FAB70": "Sage Garden", "#FF887766": "Sage Green", "#FF69796A": "Sage Green Grey", "#FF73705E": "Sage Green Light", "#FF9EA49D": "Sage Grey", "#FF7B8B5D": "Sage Leaves", "#FFDBEBDE": "Sage Salt", "#FFB2E191": "Sage Sensation", "#FF5E6A61": "Sage Shadow", "#FF7D8277": "Sage Slate", "#FFE4E5D2": "Sage Splash", "#FFC3C6A4": "Sage Splendour", "#FFACA28F": "Sage the Day", "#FFDADED1": "Sage Tint", "#FF413C7B": "Sage Violet", "#FFAFAD96": "Sage Wisdom", "#FF947252": "Sagebrush", "#FF567572": "Sagebrush Green", "#FFA2AA8C": "Sagey", "#FFFA7F46": "Sagittarius Amber Artefact", "#FFD8CFC3": "Sago", "#FF94BE7F": "Sago Garden", "#FF655F2D": "Saguaro", "#FFB79826": "Sahara Variant", "#FFDFC08A": "Sahara Gravel", "#FFF0E1DB": "Sahara Light Red", "#FFE2AB60": "Sahara Shade", "#FF9B7448": "Sahara Splendour", "#FFC67363": "Sahara Sun", "#FFDFD4B7": "Sahara Wind", "#FFA5CEEC": "Sail Variant", "#FF55B4DE": "Sail Away", "#FF588FA0": "Sail Cover", "#FFCABAA4": "Sail Grey", "#FFA3BBDC": "Sail Into the Horizon", "#FF0E4D72": "Sail Maker", "#FF4575AD": "Sail On", "#FF99C3F0": "Sail Tint", "#FF314A72": "Sailboat", "#FFECE0C4": "Sailcloth", "#FF2E9CBB": "Sailfish", "#FF869CBB": "Sailing", "#FFAECBD9": "Sailing on the Bay", "#FF3A5161": "Sailing Safari", "#FFFFA857": "Sailing Tangerine", "#FF445780": "Sailor", "#FF0F445C": "Sailor Blue", "#FFAEBBD0": "Sailor Boy", "#FFFFEE00": "Sailor Moon", "#FF496F8E": "Sailor\u2019s Bay", "#FF334B58": "Sailor\u2019s Coat", "#FFB8A47A": "Sailor\u2019s Knot", "#FFD39733": "Saimin Noodle Soup", "#FF66B89C": "Sainsbury", "#FFEEEE00": "Saint Seiya Gold", "#FFFF9BB7": "Saira Red", "#FFDFB1B6": "Sakura", "#FFCEA19F": "Sakura Mochi", "#FFE8DFE4": "Sakura Nezu", "#FF7B6C7C": "Sakura Night", "#FF8BA673": "Salad", "#FF7E8F69": "Saladin", "#FF637D74": "Salal Leaves", "#FF63775B": "Salamander", "#FFE25E31": "Salametti", "#FF820000": "Salami", "#FFDA655E": "Salami Slice", "#FF177B4D": "Salem Variant", "#FF45494D": "Salem Black", "#FF66A9D3": "Salem Blue", "#FFB4AB99": "Salem Clay", "#FFCAD2D4": "Salina Springs", "#FFC5E8E7": "Saline Water", "#FFDDD8C6": "Salisbury Stone", "#FFFF796C": "Salmon Tint", "#FFFBC6B6": "Salmon Beauty", "#FFFEA871": "Salmon Buff", "#FFEE867D": "Salmon Carpaccio", "#FFE9CFCF": "Salmon Cream", "#FFFEDDC5": "Salmon Creek", "#FFF7D560": "Salmon Eggs", "#FFF1C9CC": "Salmon Flush", "#FFFBB1A2": "Salmon Fresco", "#FFEBB9AF": "Salmon Glow", "#FFE3B6AA": "Salmon Grey", "#FFFCD1C3": "Salmon Mousse", "#FFF9906F": "Salmon Nigiri", "#FFD5847E": "Salmon Pate", "#FFFDC5B5": "Salmon Peach", "#FFE1958F": "Salmon Pink Red", "#FFEE7777": "Salmon Pok\u00e9 Bowl", "#FFFF737E": "Salmon Rose", "#FFEDAF9C": "Salmon Run", "#FFE7968B": "Salmon Salt", "#FFD9AA8F": "Salmon Sand", "#FFFF7E79": "Salmon Sashimi", "#FFF1AC8D": "Salmon Slice", "#FFD4BFBD": "Salmon Smoke", "#FFFF9BAA": "Salmon Tartare", "#FFEFCCBF": "Salmon Tint", "#FFBBEDDB": "Salome", "#FFFFD67B": "Salomie Variant", "#FF7D8697": "Salon Bleu", "#FFAB7878": "Salon Rose", "#FFB31928": "Salsa", "#FFBB4F5C": "Salsa Diane", "#FFE35401": "Salsa Haba\u00f1ero", "#FFA03413": "Salsa Mexicana", "#FFAB250B": "Salsa Picante", "#FFE97B3E": "Salsa Sizzle", "#FFCEC754": "Salsa Verde", "#FF2BB179": "Salsify Grass", "#FFDED8C4": "Salsify White", "#FFEFEDE6": "Salt", "#FF7D9C9D": "Salt Blue", "#FFD3934D": "Salt Caramel", "#FFDEE0D7": "Salt Cellar", "#FFF0EDE0": "Salt Crystal", "#FFCFD4CE": "Salt Glaze", "#FF757261": "Salt Island Green", "#FF74C6D3": "Salt Lake", "#FFD7FEFE": "Salt Mountain", "#FFF9ECEA": "Salt Pebble", "#FFD0C6AF": "Salt Scrub", "#FFA7C5CE": "Salt Spray", "#FFEEDDBB": "Salt Steppe", "#FFDCD9DB": "Salt-N-Pepa", "#FFF5E9D9": "Saltbox", "#FF648FA4": "Saltbox Blue", "#FFEBEADC": "Salted", "#FFA69151": "Salted Capers", "#FFEBB367": "Salted Caramel", "#FFFDB251": "Salted Caramel Popcorn", "#FF816B56": "Salted Pretzel", "#FFEEF3E5": "Saltpan Variant", "#FFC2D0DE": "Saltwater", "#FF145C78": "Saltwater Denim", "#FF52896B": "Saltwater Depth", "#FFD1AB99": "Saltwater Taffy", "#FFDDE2D7": "Salty Breeze", "#FFE2C681": "Salty Cracker", "#FF234058": "Salty Dog", "#FFCCE2F3": "Salty Ice", "#FF7C8B7F": "Salty Sea", "#FFC1B993": "Salty Seeds", "#FFBACAD4": "Salty Tears", "#FF96B403": "Salty Thyme", "#FFCBDEE3": "Salty Vapour", "#FF2F323D": "Salute", "#FFBAC9CF": "Salvaged Silk", "#FF514E5C": "Salvation", "#FFA8B59E": "Salvia", "#FF96BFE6": "Salvia Blue", "#FF929752": "Salvia Divinorum", "#FFF2D7E6": "Samantha\u2019s Room", "#FFAA262B": "Samba", "#FFF0E0D4": "Samba de Coco", "#FF3B2E25": "Sambuca Variant", "#FF17182B": "Sambucus", "#FFFABC46": "Samoan Sun", "#FFB8BEBE": "Samovar Silver", "#FFF7F3E6": "Sampaguita Pearl", "#FF4DB560": "Samphire Green", "#FFA69474": "San Antonio Sage", "#FFD9BB8E": "San Carlos Plaza", "#FF2C6E31": "San Felix Variant", "#FFC4C2BC": "San Francisco Fog", "#FF936A6D": "San Francisco Mauve", "#FFBB33AA": "San Francisco Pink", "#FF2F6679": "San Gabriel Blue", "#FF445761": "San Juan Variant", "#FF4E6C9D": "San Marino Variant", "#FF527585": "San Miguel Blue", "#FFD4C9A6": "Sanctuary", "#FF66B2E4": "Sanctuary Spa", "#FF3C2F4E": "Sanctum of Shadows", "#FFE2CA76": "Sand", "#FFFFEEDA": "Sand Crystal", "#FFE6DDD2": "Sand Dagger", "#FFE0C8B9": "Sand Dance", "#FFFAE8BC": "Sand Diamond", "#FFDCCDBB": "Sand Dollar", "#FFFAE3C9": "Sand Dollar White", "#FFE5E0D3": "Sand Drift", "#FFE3D2C0": "Sand Dune Variant", "#FFE3E4D9": "Sand Grain", "#FFF4D1C2": "Sand Island", "#FFDDC6A8": "Sand Motif", "#FFB58853": "Sand Mountain", "#FFFFDFC2": "Sand Muffin", "#FFE7D4B6": "Sand Pearl", "#FFB09D7F": "Sand Pebble", "#FFE6E5D3": "Sand Puffs", "#FFDDCC77": "Sand Pyramid", "#FFC1B7B0": "Sand Ripples", "#FFE7D0A6": "Sand Sculpture", "#FF5A86AD": "Sand Shark", "#FFD0C6A1": "Sand Trail", "#FFBBA595": "Sand Trap", "#FF9D89BD": "Sand Verbena", "#FFA3876A": "Sandal Variant", "#FF615543": "Sandalwood", "#FFF2D1B1": "Sandalwood Beige", "#FF005160": "Sandalwood Grey Blue", "#FF907F68": "Sandalwood Tan", "#FFF2ECDE": "Sandalwood White", "#FFE9D5AD": "Sandbank", "#FFCBBFAD": "Sandbar", "#FFDECBAB": "Sandblast", "#FFE5D7C4": "Sandcastle", "#FFC8AB96": "Sanderling", "#FF93917F": "Sandgrass Green", "#FFEDDCBE": "Sandhill", "#FF015E60": "Sandhill Crane", "#FFEFEBDE": "Sanding Sugar", "#FFD7B1A5": "Sandpaper", "#FFEBDAC8": "Sandpiper", "#FF717474": "Sandpiper Cove", "#FF9E7C5E": "Sandpit", "#FFEACDB0": "Sandpoint", "#FFAF937D": "Sandrift Variant", "#FFC4B19A": "Sandrock", "#FFBCA38B": "Sands of Time", "#FFD6C8B8": "Sandshell", "#FFC9AE74": "Sandstone Variant", "#FFD2C9B7": "Sandstone Cliff", "#FF857266": "Sandstone Grey", "#FF88928C": "Sandstone Grey Green", "#FFD9CCB6": "Sandstone Palette", "#FF886E70": "Sandstone Red Grey", "#FFCBBE9C": "Sandwashed", "#FF706859": "Sandwashed Driftwood", "#FFDEE8E3": "Sandwashed Glassshard", "#FFDECB81": "Sandwisp Variant", "#FFF1DA7A": "Sandy", "#FFE4DED5": "Sandy Ash", "#FFFAD7B3": "Sandy Bay", "#FFF9E2D0": "Sandy Beach Variant", "#FFDDC6AB": "Sandy Beaches", "#FFACA088": "Sandy Bluff", "#FFDBD0BD": "Sandy Clay", "#FFCEB587": "Sandy Cove", "#FFD7CFC1": "Sandy Day", "#FFB7AC97": "Sandy Hair", "#FFD2C098": "Sandy Pail", "#FFDF7B35": "Sandy Peppers", "#FFA18E77": "Sandy Ridge", "#FFC7C2B4": "Sandy Sage", "#FF847563": "Sandy Shoes", "#FFF2E9BB": "Sandy Shore", "#FFFDD9B5": "Sandy Tan", "#FF967111": "Sandy Taupe", "#FFC7B8A4": "Sandy Toes", "#FFCABAA0": "Sandy Trail", "#FF771100": "Sang de Boeuf", "#FFF5B1AA": "Sango Pink", "#FFF8674F": "Sango Red", "#FF881100": "Sangoire Red", "#FFB14566": "Sangria Variant", "#FFF01A4D": "Sanguinary", "#FF6C110E": "Sanguine", "#FF6C3736": "Sanguine Brown Variant", "#FFE69332": "Sanskrit", "#FFAD2C15": "Santa Belly Red", "#FFEDDDD3": "Santa Fe Spirit", "#FFCC9469": "Santa Fe Sunrise", "#FFA75A4C": "Santa Fe Sunset", "#FFD5AD85": "Santa Fe Tan", "#FFE9E7DE": "Santa\u2019s Beard", "#FF714636": "Santana Soul", "#FFE95F24": "Santiago Orange", "#FFD6D2CE": "Santo", "#FFE3D0D5": "Santolina Blooms", "#FF41B0D0": "Santorini", "#FF416D83": "Santorini Blue", "#FF019A8D": "Santorini Seascape", "#FF5C8B15": "Sap Green Variant", "#FFBEBDAC": "Sapless Green", "#FFA3C05A": "Sapling Variant", "#FF9E3D3F": "Sappanwood", "#FFA24F46": "Sappanwood Incense", "#FF4F5F7E": "Sapphire Earbobs", "#FF284B75": "Sapphire Earrings", "#FF99A8C9": "Sapphire Fog", "#FF0033CC": "Sapphire Glitter", "#FF235C8E": "Sapphire Lace", "#FFCDC7B4": "Sapphire Light Yellow", "#FF887084": "Sapphire Pink", "#FF5776AF": "Sapphire Shimmer Blue", "#FF662288": "Sapphire Siren", "#FF135E84": "Sapphire Sparkle", "#FF2425B9": "Sapphire Splendour", "#FF41495D": "Sapphire Stone", "#FFC9E5EE": "Sapphireberry", "#FF5B82A6": "Sapphired", "#FF00AAC1": "Sarah\u2019s Garden", "#FF555B2C": "Saratoga Variant", "#FFF4EEBA": "Sarawak White Pepper", "#FFFFDDAA": "Sarcoline", "#FF28A4CB": "Sardinia Beaches", "#FF3D4A67": "Sargasso Sea", "#FFE47C64": "Sari", "#FF5B4C44": "Sarsaparilla", "#FF817265": "Saruk Grey", "#FFCFB4A8": "Sashay Sand", "#FFFF4681": "Sasquatch Socks", "#FF5A3940": "Sassafras", "#FFDBD8CA": "Sassafras Tea", "#FFC18862": "Sassy", "#FF7A8C31": "Sassy Grass", "#FFBBA86A": "Sassy Green", "#FFDFE289": "Sassy Lime", "#FF906B20": "Sassy Olive", "#FFFFB07F": "Sassy Peach", "#FFF6CEFC": "Sassy Pink", "#FFEE7C54": "Sassy Salmon", "#FFD5A272": "Sassy Sassafras", "#FFACB5DC": "Sassy Violet", "#FFF0C374": "Sassy Yellow", "#FFE63626": "Satan", "#FF9F8D89": "Satellite", "#FF4E5152": "Satin Black", "#FFFFE4C6": "Satin Blush", "#FF773344": "Satin Chocolate", "#FFFDF3D5": "Satin Cream White", "#FF1C1E21": "Satin Deep Black", "#FFB48FBD": "Satin Flower", "#FFC7DFB8": "Satin Green", "#FFFAD7B0": "Satin Latour", "#FF33EE00": "Satin Lime", "#FFFFF8EE": "Satin Purse", "#FFFFD8DC": "Satin Ribbon", "#FF9CADC7": "Satin Soft Blue", "#FF6B4836": "Satin Soil", "#FFEFE0BC": "Satin Souffle", "#FFE2CAC7": "Satin Teddy", "#FFF3EDD9": "Satin Weave", "#FFCFD5DB": "Satin White", "#FFC4C2CD": "Satire", "#FFB5BF50": "Sativa", "#FF644222": "Satoimo Brown", "#FF96466A": "Satsuma Imo Red", "#FFAA6622": "Sattle", "#FF4B4CFC": "Saturated Sky", "#FFFAE5BF": "Saturn", "#FFB8B19F": "Saturn Grey", "#FFDDDBCE": "Saturnia", "#FFBCA17A": "Satyr Brown", "#FFCA3F16": "Sauce Piquante", "#FF882C17": "Saucisson", "#FFC6B077": "Saucy", "#FFB6743B": "Saucy Gold", "#FF9E958A": "Saudi Sand", "#FFEEE0B9": "Sauerkraut", "#FFEDEBE1": "Sauna Steam", "#FFEBDFCD": "Sausage Roll", "#FFF4E5C5": "Sausalito", "#FF5D6F85": "Sausalito Port", "#FF6A5D53": "Sausalito Ridge", "#FFAB9378": "Sauteed Mushroom", "#FFC5A253": "Sauterne", "#FFF4EAE4": "Sauvignon Variant", "#FFB18276": "Sauvignon Blanc", "#FF5C9F59": "Savage Garden", "#FF874C44": "Savanna", "#FFD1BD92": "Savannah", "#FFBABC72": "Savannah Grass", "#FF47533F": "Savannah Moss", "#FFA36159": "Savannah Red", "#FFFFB989": "Savannah Sun", "#FFAA2200": "Saveloy", "#FFC0B7CF": "Savile Row", "#FF550011": "Saving Light", "#FFEED9B6": "Savon de Provence", "#FFF9DAA5": "Savoury Beige", "#FFD19C97": "Savoury Salmon", "#FF87B550": "Savoy", "#FF4B61D1": "Savoy Blue", "#FF7E4242": "Savoy House", "#FFF6E9CF": "Sawdust", "#FFD1CFC0": "Sawgrass", "#FFC3B090": "Sawgrass Basket", "#FFD3CDA2": "Sawgrass Cottage", "#FFAA7766": "Sawshark", "#FFEC956C": "Sawtooth Aak", "#FFABC1A0": "Saxon", "#FF435965": "Saxon Blue", "#FF216B88": "Saxony Blue", "#FFCEAF81": "Saxophone Gold", "#FFC7111E": "Say It With Red Roses", "#FF383739": "Sayward Pine", "#FFF5DEC4": "Sazerac Variant", "#FFFBD8C9": "Scallop Shell", "#FFF2D1A0": "Scalloped Oak", "#FFFCE3CF": "Scalloped Potato", "#FFF6D58B": "Scalloped Potatoes", "#FFF3E9E0": "Scalloped Shell", "#FFE5D5BD": "Scallywag", "#FF027275": "Scaly Green", "#FF6F63A0": "Scampi Variant", "#FF6B8CA9": "Scanda", "#FFADD9D1": "Scandal Variant", "#FFDFBDD0": "Scandalous Rose", "#FF1A1110": "Scandinavian Liquorice", "#FFC2D3D6": "Scandinavian Sky", "#FF6B6A6C": "Scapa Flow", "#FF2C3D37": "Scarab", "#FF414040": "Scarabaeus Sacer", "#FF7D8C55": "Scarab\u0153us Nobilis", "#FF809391": "Scarborough", "#FFA55E2B": "Scarecrow Frown", "#FF922E4A": "Scarlet Apple", "#FFBB2D32": "Scarlet Beebalm", "#FFB21F1F": "Scarlet Blaze", "#FFF8D6CB": "Scarlet Butter", "#FFC70752": "Scarlet Cattleya Orchid", "#FF993366": "Scarlet Flame", "#FFCB0103": "Scarlet Glow", "#FF4A2D57": "Scarlet Gum Variant", "#FFF4601B": "Scarlet Ibis", "#FFA53B3D": "Scarlet Past", "#FFD44531": "Scarlet Rebels", "#FFB63E36": "Scarlet Red", "#FFA4334A": "Scarlet Ribbons", "#FFAA2335": "Scarlet Sage", "#FF7E2530": "Scarlet Shade", "#FFCC0C1B": "Scarlet Splendour", "#FFDA5640": "Scarlet Sun", "#FFB43B3D": "Scarlet Tanager", "#FF8CA468": "Scarpetta", "#FF647983": "Scatman Blue", "#FF7B8285": "Scattered Showers", "#FF81A79E": "Scenario", "#FFAF6D62": "Scene Stealer", "#FF486275": "Scenic Blue", "#FFCEC5B4": "Scenic Path", "#FF95BFB2": "Scenic View", "#FF88BBFF": "Scenic Water", "#FF907361": "Scented Candle", "#FF61524C": "Scented Clove", "#FFCAAEB8": "Scented Frill", "#FFDBC5D2": "Scented Soap", "#FFEED5EE": "Scented Spring", "#FFF3D9D6": "Scented Valentine", "#FF9DB4AA": "Sceptic", "#FFE2E2B5": "Schabziger Green", "#FFE84998": "Schiaparelli Pink", "#FF192961": "Schiava Blue", "#FF8B714C": "Schindler Brown", "#FF87876F": "Schist Variant", "#FFDD8855": "Schnipo", "#FF586A7D": "Scholarship", "#FF31373F": "School Ink", "#FF8D8478": "Schooner Variant", "#FF006666": "Sci-fi Petrol", "#FF00604B": "Sci-Fi Takeout", "#FF0076CC": "Science Blue Variant", "#FF97A53F": "Science Experiment", "#FF764663": "Scintillating Violet", "#FFAE935D": "Sconce", "#FF957340": "Sconce Gold", "#FF110055": "Scoop of Dark Matter", "#FF308EA0": "Scooter Variant", "#FF351F19": "Scorched", "#FF4D0001": "Scorched Brown", "#FF44403D": "Scorched Earth", "#FF423D27": "Scorched Metal", "#FFE75323": "Scorpio Scarlet Seal", "#FF6A6466": "Scorpion Variant", "#FF99AAC8": "Scorpion Grass Blue", "#FF62D84E": "Scorpion Green", "#FF97EA10": "Scorpion Venom", "#FF8EEF15": "Scorpy Green", "#FF544E03": "Scorzonera Brown", "#FF000077": "Scotch Blue", "#FFFE9F00": "Scotch Bonnet", "#FF649D85": "Scotch Lassie", "#FFEEE7C8": "Scotch Mist Variant", "#FF99719E": "Scotch Thistle", "#FFEBCCB9": "Scotchtone", "#FF87954F": "Scotland Isle", "#FF9BAA9A": "Scotland Road", "#FF5F653B": "Scots Pine", "#FF66A3C3": "Scott Base", "#FFC2CDCA": "Scottish Heath", "#FF7D9A7B": "Scottish Highlands", "#FF3B7960": "Scouring Rush", "#FFE34B26": "Scoville High", "#FF900405": "Scoville Highness", "#FFAB0040": "Screamer Pink", "#FFC16F45": "Screaming Bell Metal", "#FFF0F2D2": "Screaming Skull", "#FFEAE4D8": "Screech Owl", "#FF9A908A": "Screed Grey", "#FF9D7798": "Screen Gem", "#FF66EEAA": "Screen Glow", "#FF999EB0": "Screen Test", "#FF9FABB6": "Scribe", "#FF60616B": "Script Ink", "#FFDBDDDF": "Script White", "#FFDBA539": "Scrofulous Brown", "#FFEFE0CB": "Scroll", "#FFF3E5C0": "Scroll of Wisdom", "#FFE9DDC9": "Scrolled Parchment", "#FF3D4031": "Scrub", "#FF6392B7": "Scuba", "#FF00A2C5": "Scuba Blue", "#FFACD7C8": "Scud", "#FF0044EE": "Scuff Blue", "#FFC3BEB7": "Sculpting Clay", "#FFCCC3B4": "Sculptor Clay", "#FFD1DAD5": "Sculptural Silver", "#FF02737A": "Scurf Green", "#FFFC824A": "S\u00e8 L\u00e8i Orange", "#FF3C9992": "Sea", "#FFE8DAD6": "Sea Anemone", "#FF8BB8C3": "Sea Angel", "#FF62777E": "Sea Beast", "#FF29848D": "Sea Bed", "#FF41545C": "Sea Blithe", "#FF006994": "Sea Blue", "#FFC0E1DD": "Sea Breath", "#FFA4BFCE": "Sea Breeze", "#FFC9D9E7": "Sea Breeze Green", "#FFFFBF65": "Sea Buckthorn Variant", "#FF519D76": "Sea Cabbage", "#FF45868B": "Sea Caller", "#FFE4F3DF": "Sea Cap", "#FF61BDDC": "Sea Capture", "#FF005986": "Sea Cave", "#FF2C585C": "Sea Challenge", "#FFA5C7DF": "Sea Cliff", "#FF00586D": "Sea Creature", "#FF608BA6": "Sea Crystal", "#FF4C959D": "Sea Current", "#FF9ECDD5": "Sea Dew", "#FF4B7794": "Sea Drifter", "#FFC2D2E0": "Sea Drive", "#FF77675C": "Sea Elephant", "#FF975476": "Sea Fan Fuchsia", "#FF1A9597": "Sea Fantasy", "#FF656D54": "Sea Fern", "#FF87E0CF": "Sea Foam", "#FFCBDCE2": "Sea Foam Mist", "#FFDFDDD6": "Sea Fog", "#FF64B6E2": "Sea Frolic", "#FFD5DCDC": "Sea Frost", "#FF568E88": "Sea Garden", "#FFAFC1BF": "Sea Glass", "#FFA0E5D9": "Sea Glass Teal", "#FF216987": "Sea Goddess", "#FF2A2E44": "Sea Going", "#FF3300AA": "Sea Grape", "#FF53FCA1": "Sea Green Variant", "#FF6D716A": "Sea Grove", "#FFCBD9D4": "Sea Haze Grey", "#FFA7A291": "Sea Hazel", "#FF245878": "Sea Hunter", "#FFD7F2ED": "Sea Ice", "#FFA6D0C9": "Sea Inspired", "#FF30A299": "Sea Kale", "#FF354A55": "Sea Kelp", "#FFCFB1D8": "Sea Lavender", "#FF67A181": "Sea Lettuce", "#FF7F8793": "Sea Lion", "#FF8C6E60": "Sea Lion Brown", "#FF6E99D1": "Sea Loch", "#FF434A54": "Sea Mariner", "#FF92B6CF": "Sea Mark", "#FFDBEEE0": "Sea Mist Variant", "#FF658C7B": "Sea Monster", "#FF2C5252": "Sea Moss", "#FFF47633": "Sea Nettle", "#FF5482C2": "Sea Note", "#FF8AAEA4": "Sea Nymph Variant", "#FF2D535A": "Sea of Atlantis", "#FF0693D5": "Sea of Crete", "#FF466590": "Sea of Galilee", "#FF0B334D": "Sea of Stars", "#FF1D4DA0": "Sea of Tears", "#FF81D1DA": "Sea of Tranquillity", "#FF00507A": "Sea Paint", "#FF72897E": "Sea Palm", "#FF457973": "Sea Pea", "#FFE0E9E4": "Sea Pearl", "#FF4C6969": "Sea Pine", "#FFDB817E": "Sea Pink Variant", "#FF3E7984": "Sea Quest", "#FF799781": "Sea Radish", "#FF45A3CB": "Sea Ridge", "#FFA3D1E2": "Sea Rover", "#FFEEE1D7": "Sea Salt", "#FF5087BD": "Sea Salt Rivers", "#FFFFF5F7": "Sea Salt Sherbet", "#FF67B3BE": "Sea Serenade", "#FF4BC7CF": "Sea Serpent", "#FF5511CC": "Sea Serpent\u2019s Tears", "#FF00789B": "Sea Sight", "#FF469BA7": "Sea Sparkle", "#FFD2EBEA": "Sea Spray", "#FFB7CCC7": "Sea Sprite", "#FFBAA243": "Sea Squash", "#FF4D939A": "Sea Star", "#FF018F6B": "Sea Swell", "#FF337F86": "Sea Swimmer", "#FF427A9A": "Sea Swirl", "#FF78D1B1": "Sea Treasure", "#FF818A40": "Sea Turtle", "#FF367D83": "Sea Urchin", "#FF83D6D4": "Sea Wave", "#FFACCAD5": "Sea Wind", "#FF0F9BC0": "Sea Wonder", "#FF85C2B2": "Seaborn", "#FF7AA5C9": "Seaborne", "#FF4B81AF": "Seabrook", "#FFCD7B00": "Seabuckthorn Yellow Brown", "#FF3E8896": "Seachange", "#FFB6C9A6": "Seacrest", "#FFB8F8D8": "Seafair Green", "#FF204D68": "Seafarer", "#FF78D1B6": "Seafoam Blue", "#FFC0CDC2": "Seafoam Dream", "#FF99BB88": "Seafoam Green", "#FFC2ECD8": "Seafoam Pearl", "#FFA6BCBE": "Seafoam Slate", "#FFB0EFCE": "Seafoam Splashes", "#FFDAEFCE": "Seafoam Spray", "#FF9DA49C": "Seafoam Storm", "#FFA1BDBF": "Seafoam Whisper", "#FFBCC6A2": "Seagrass", "#FF264E50": "Seagrass Green", "#FFE0DED8": "Seagull Variant", "#FFC9BBB4": "Seagull Beach", "#FFD9D9D2": "Seagull Grey", "#FFC7BDA8": "Seagull Wail", "#FF475663": "Seal Blue", "#FF8A9098": "Seal Grey", "#FF65869B": "Seal Pup", "#FF6B8B8B": "Sealegs", "#FF48423C": "Sealskin", "#FFE9ECE6": "Sealskin Shadow", "#FF15646D": "Seamount", "#FF69326E": "Seance Tint", "#FF3A3F41": "Seaplane Grey", "#FF006E8C": "Seaport", "#FFAECAC8": "Seaport Steam", "#FF6C7F9A": "Searching Blue", "#FFEFF0BF": "Searchlight", "#FF9A5633": "Seared Earth", "#FF495157": "Seared Grey", "#FF6B3B23": "Searing Gorge Brown", "#FF77A2AD": "Seascape", "#FFA6BAD1": "Seascape Blue", "#FFB5E4E4": "Seascape Green", "#FF104C77": "Seashell Cove", "#FFEDB9BD": "Seashell Froth", "#FFD5D4CF": "Seashell Grey", "#FFFFF6DE": "Seashell Peach Variant", "#FFF7C8C2": "Seashell Pink", "#FFB5DCEF": "Seashore Dreams", "#FF66A4B0": "Seaside", "#FFBDC3B5": "Seaside Fog", "#FFF2E9D7": "Seaside Sand", "#FFE9D5C9": "Seaside Villa", "#FFBEA27B": "Season Finale", "#FFE6B99F": "Seasonal Beige", "#FF7F6640": "Seasoned Acorn", "#FF8DB600": "Seasoned Green", "#FFCEC2A1": "Seasoned Salt", "#FF7D8B8A": "Seastone", "#FF777E90": "Seattle Haze", "#FF7D212A": "Seattle Red", "#FFA9C095": "Seawashed Glass", "#FF18D17B": "Seaweed Variant", "#FF35AD6B": "Seaweed Green", "#FF7D7B55": "Seaweed Salad", "#FF5D7759": "Seaweed Tea", "#FF4D473D": "Seaweed Wrap", "#FF125459": "Seaworld", "#FF314D58": "Seaworthy", "#FF84AEBA": "Sebastian Inlet", "#FFBD5701": "Sebright Chicken", "#FFCAC5B6": "Secluded Beach", "#FFC6876F": "Secluded Canyon", "#FF7B9893": "Secluded Garden", "#FF6F6D56": "Secluded Green", "#FF495A52": "Secluded Woods", "#FFE89AAD": "Second Blush", "#FF585642": "Second Nature", "#FF887CA4": "Second Pour", "#FFDFECE9": "Second Wind", "#FF50759E": "Secrecy", "#FFC41661": "Secret Affair", "#FFE1D2D5": "Secret Blush", "#FF68909D": "Secret Cove", "#FFD7DFD6": "Secret Crush", "#FF11AA66": "Secret Garden", "#FFB5B88D": "Secret Glade", "#FF7C6055": "Secret Journal", "#FF72754F": "Secret Meadow", "#FF777465": "Secret Mission", "#FFA2A59D": "Secret Moss", "#FF372A05": "Secret Passage", "#FF6D695E": "Secret Passageway", "#FF737054": "Secret Path", "#FF9FA89C": "Secret Retreat", "#FFC6BB68": "Secret Safari", "#FF7A0E0E": "Secret Scarlet", "#FFE3D7DC": "Secret Scent", "#FF464E5A": "Secret Society", "#FF5389A1": "Secure Blue", "#FFD6E1C2": "Security", "#FFD1CDBF": "Sedate Grey", "#FFB1A591": "Sedge", "#FF9A8841": "Sedge Grasses", "#FF707A68": "Sedge Green", "#FFB0A67E": "Sedia", "#FFB3A698": "Sediment", "#FFE7E0CF": "Sedona", "#FFBF7C45": "Sedona at Sunset", "#FF886244": "Sedona Brown", "#FFC16A55": "Sedona Canyon", "#FFD6B8A7": "Sedona Pink", "#FF686D6C": "Sedona Sage", "#FF665F70": "Sedona Shadow", "#FF8E462F": "Sedona Stone", "#FFFBF2BF": "Seduction", "#FFA2C748": "Seductive Thorns", "#FF734F39": "Seed Brown", "#FFD7CCC6": "Seed Cathedral", "#FFE6DAC4": "Seed Pearl", "#FFDAC43C": "Seed Pod", "#FFD3C3D4": "Seedless Grape", "#FFC0CBA1": "Seedling", "#FFA99BA9": "Seeress", "#FFAFBBC1": "Seersucker", "#FFFFF1F1": "Sefid White", "#FF903037": "Sehnsucht Red", "#FF3A6960": "Seiheki Green", "#FF819C8B": "Seiji Green", "#FF8B8074": "Seine", "#FFCCC4B0": "Seine and Sensibility", "#FFE5ABBE": "Sekichiku Pink", "#FF683F36": "Sekkasshoku Brown", "#FFE6DFE7": "Selago Variant", "#FF777E7A": "Selenium", "#FF8C7591": "Self Powered", "#FFC2B398": "Self-Destruct", "#FFD22B6D": "Self-Love", "#FF4488EE": "Seljuk Blue", "#FFD4AE5E": "Sell Gold", "#FF90A2B7": "Sell Out", "#FFAB9649": "Semi Opal", "#FF6B5250": "Semi Sweet", "#FF6B4226": "Semi Sweet Chocolate", "#FF659B97": "Semi-Precious", "#FFFFE2B6": "Semifreddo", "#FFC7AB8B": "Semolina", "#FFFFE8C7": "Semolina Pudding", "#FF4CA973": "S\u0113n L\u00edn L\u01dc Forest", "#FF4A515F": "Senate", "#FF824B35": "Sencha Brown", "#FF9A927F": "Seneca Rock", "#FFFDECC7": "Senior Moment", "#FF494A41": "Sensai Brown", "#FF3B3429": "Sensaicha Brown", "#FF374231": "Sensaimidori Green", "#FFBFA38D": "Sensational Sand", "#FFEDB159": "Sense of Wonder", "#FFEAD7B4": "Sensible Hue", "#FFCC2266": "Sensitive Scorpion", "#FFCEC9CC": "Sensitive Tint", "#FFA1B0BE": "Sensitivity", "#FFCD68E2": "Sensual Fumes", "#FFFFD2B6": "Sensual Peach", "#FFB75E6B": "Sensuous", "#FF837D7F": "Sensuous Grey", "#FFE6D8D2": "Sentimental", "#FFE0D8C5": "Sentimental Beige", "#FFC4D3DC": "Sentimental Lady", "#FFF8EEF4": "Sentimental Pink", "#FFD2E0D6": "Sentinel", "#FF4B3526": "Sepia Brown", "#FFCBB499": "Sepia Filter", "#FFD4BBB6": "Sepia Rose", "#FF9F5C42": "Sepia Skin Variant", "#FF8C7562": "Sepia Tint", "#FFB8A88A": "Sepia Tone", "#FF995915": "Sepia Wash", "#FF8C7340": "Sepia Yellow", "#FFA8BEDD": "September Aster", "#FFD3C9B9": "September Fog", "#FF8D7548": "September Gold", "#FFEDE6B3": "September Morn", "#FFFFE9BB": "September Morning", "#FF597570": "September Sea", "#FFD5D8C8": "September Song", "#FFFDD7A2": "September Sun", "#FFD4D158": "Sequesta", "#FFE1C28D": "Sequin", "#FF84463B": "Sequoia", "#FF795953": "Sequoia Dusk", "#FFC5BBAF": "Sequoia Fog", "#FF935E4E": "Sequoia Grove", "#FF506C6B": "Sequoia Lake", "#FF763F3D": "Sequoia Redwood", "#FFD88B4D": "Serape", "#FFD7824B": "Seraphim Sepia", "#FF616F65": "Seraphinite", "#FF3E644F": "Serbian Green", "#FFCFD0C1": "Serena", "#FFFCE9D7": "Serenade Variant", "#FF4A4354": "Serendibite Black", "#FFBDE1D8": "Serendipity", "#FFA6CDDF": "Serendipity Blue", "#FFDCE3E4": "Serene", "#FF1199BB": "Serene Blue", "#FFBDD9D0": "Serene Breeze", "#FFCFD8D1": "Serene Journey", "#FFF5D3B7": "Serene Peach", "#FFF6C6B9": "Serene Pink", "#FF7B9F4B": "Serene Sage", "#FFD2C880": "Serene Scene", "#FF78A7C3": "Serene Sea", "#FFC5D2D9": "Serene Setting", "#FFC3E3EB": "Serene Sky", "#FF819DAA": "Serene Stream", "#FFC5C0AC": "Serene Thought", "#FFCED7D5": "Serenely", "#FFAB9579": "Serengeti", "#FFE7DBC9": "Serengeti Dust", "#FF77CC88": "Serengeti Green", "#FFFCE7D0": "Serengeti Sand", "#FF76BAA8": "Sereni Teal", "#FF91A8D0": "Serenity", "#FF507BCE": "Serenity\u2019s Reign", "#FFDD3744": "Serial Kisses", "#FF7D848B": "Serious Cloud", "#FFCEC9C7": "Serious Grey", "#FFF9E0CD": "Serious Moonlight", "#FFDCCCB4": "Seriously Sand", "#FF817F6D": "Serpent", "#FFBBCC00": "Serpent Sceptre", "#FF9B8E54": "Serpentine", "#FFA2B37A": "Serpentine Green", "#FF003300": "Serpentine Shadow", "#FF556600": "Serrano Pepper", "#FF9CA9AD": "Seryi Grey", "#FFBAA38B": "Sesame", "#FFC26A35": "Sesame Crunch", "#FFE1D9B8": "Sesame Seed", "#FF00A870": "Sesame Street Green", "#FFE00012": "Setsugekka", "#FF5CCBC7": "Setting Sail", "#FF7E7970": "Settlement", "#FFD3DAE1": "Seven Days of Rain", "#FF4A5C6A": "Seven Seas", "#FFDCE2E0": "Seven Sisters", "#FFE3B8BD": "Seven Veils", "#FFEEE7DE": "Severe Seal", "#FF241005": "Severely Burnt Toast", "#FF955843": "Seville Scarlet", "#FFBB8A8E": "Shabby Chic", "#FFEFDDD6": "Shabby Chic Pink", "#FFAE7181": "Shade of Mauve", "#FF4E5147": "Shade-Grown", "#FF786947": "Shaded Fern", "#FF664348": "Shaded Fuchsia", "#FF8E824A": "Shaded Glen", "#FF859C9B": "Shaded Hammock", "#FFEBAEAC": "Shaded Primrose", "#FF005F6D": "Shaded Spruce", "#FFF3EBA5": "Shaded Sun", "#FF8AB18A": "Shaded Willow", "#FFBE2BDF": "Shades of Rhodonite", "#FF9C0009": "Shades of Ruby", "#FF605F5F": "Shades On", "#FFE96A97": "Shadow Azalea Pink", "#FF7A6F66": "Shadow Cliff", "#FF877D83": "Shadow Dance", "#FF788788": "Shadow Effect", "#FF686767": "Shadow Gargoyle", "#FF9AC0B6": "Shadow Green Variant", "#FFB09691": "Shadow Grey", "#FF395648": "Shadow Leaf", "#FFCCDE95": "Shadow Lime", "#FF585858": "Shadow Mountain", "#FF2A4F61": "Shadow of Night", "#FFA3A2A1": "Shadow of the Colossus", "#FF356454": "Shadow Pine", "#FF221144": "Shadow Planet", "#FF4E334E": "Shadow Purple", "#FF5B5343": "Shadow Ridge", "#FF9C917F": "Shadow Taupe", "#FF5E534A": "Shadow Woods", "#FF111155": "Shadowdancer", "#FF4B4B4B": "Shadowed Steel", "#FF6B6D6A": "Shadows", "#FF018466": "Shadows of Time", "#FFDBD6CB": "Shady", "#FF42808A": "Shady Blue", "#FF4C4B4C": "Shady Character", "#FF007865": "Shady Glade", "#FF635D4C": "Shady Green", "#FF849292": "Shady Grey", "#FF9F9B9D": "Shady Lady Variant", "#FF5555FF": "Shady Neon Blue", "#FF73694B": "Shady Oak", "#FF456B67": "Shady Palm", "#FFC4A1AF": "Shady Pink", "#FFF0E9DF": "Shady White", "#FF939689": "Shady Willow", "#FF645D41": "Shagbark Olive", "#FFB3AB98": "Shaggy Barked", "#FFC9B6A1": "Shaggy Dog", "#FFCBC99D": "Shagreen", "#FFC3BEA1": "Shaken or Stirred?", "#FF748C96": "Shaker Blue", "#FF6C6556": "Shaker Grey", "#FF886A3F": "Shaker Peg", "#FF609AB8": "Shakespeare Variant", "#FF7F4340": "Shakker Red", "#FFAA3311": "Shakshuka", "#FF752100": "Shaku-Do Copper", "#FF4A3F41": "Shale", "#FF6B886B": "Shale Green", "#FF899DA3": "Shale Grey", "#FFF8F6A8": "Shalimar Variant", "#FF7B8D73": "Shallot Bulb", "#FF505C3A": "Shallot Leaf", "#FFEEC378": "Shallot Peel", "#FFC5F5E8": "Shallow End", "#FF9AB8C2": "Shallow Sea", "#FF9DD6D4": "Shallow Shoal", "#FFB0DEC8": "Shallow Shore", "#FFBCC2C3": "Shallow Skies", "#FF8AF1FE": "Shallow Water", "#FF8CAEAC": "Shallow Water Ground", "#FFCC855A": "Shamanic Journey", "#FFFFCFF1": "Shampoo", "#FF358D52": "Shamrock Field", "#FF4EA77D": "Shamrock Green Variant", "#FFFA9A85": "Sh\u0101n H\u00fa H\u00f3ng Coral", "#FFFFE670": "Shandy", "#FFAAD9BB": "Shanghai Jade", "#FFD79A91": "Shanghai Peach", "#FFC8DFC3": "Shanghai Silk", "#FFECD4D2": "Shangri La", "#FF4C1050": "Shani Purple", "#FFA18B5D": "Shank", "#FF9BE3D7": "Sharbah Fizz", "#FFFFA26B": "Sharegaki Persimmon", "#FFCADCDE": "Shark Variant", "#FFEE6699": "Shark Bait", "#FF969795": "Shark Fin", "#FFA5B4BA": "Shark Loop", "#FFE4E1D3": "Shark Tooth", "#FF35524A": "Sharknado", "#FF808184": "Sharkskin", "#FF2B3D54": "Sharp Blue", "#FF007D58": "Sharp Green", "#FFC9CAD1": "Sharp Grey", "#FFC0AD28": "Sharp Lime", "#FFDBD6D8": "Sharp Pebbles", "#FFECC043": "Sharp Yellow", "#FFEAE1D6": "Sharp-Rip Drill", "#FF355C74": "Shasta Lake", "#FFBB5577": "Shattan Gold", "#FFB5A088": "Shattell", "#FFDAEEE6": "Shattered Ice", "#FFEEE2E0": "Shattered Porcelain", "#FFD0DDE9": "Shattered Sky", "#FFF1F1E5": "Shattered White", "#FF5A4039": "Shaved Chocolate", "#FFA9B4BA": "Shaved Ice", "#FFE1E5E5": "Shaving Cream", "#FFDD9955": "Shawarma", "#FFE39B96": "She Loves Pink", "#FFAB214B": "She Pouts", "#FFF8F1EB": "Shea", "#FFD2AE84": "Sheaf", "#FFE8E1CE": "Shearling", "#FF5B5B6C": "Shearwater Black", "#FF81876F": "Shebang", "#FFD9B28B": "Sheepskin", "#FFAD9E87": "Sheepskin Gloves", "#FFCBBFA6": "Sheepskin Rug", "#FFF3C99D": "Sheer Apricot", "#FFE4D0C3": "Sheer Blouse", "#FFCFD8D5": "Sheer Elegance", "#FFB0C69A": "Sheer Green", "#FFEFE2F2": "Sheer Lavender", "#FFCDD2CE": "Sheer Light", "#FFB793C0": "Sheer Lilac", "#FFFFF7E7": "Sheer Peach", "#FFF6E5DB": "Sheer Pink", "#FFFFE8E5": "Sheer Rosebud", "#FFE3D6CA": "Sheer Scarf", "#FFFFFEDF": "Sheer Sunlight", "#FF52616F": "Sheet Blue", "#FF5E6063": "Sheet Metal", "#FF638F7B": "Sheffield", "#FF6B7680": "Sheffield Grey", "#FFEFECEE": "Sheikh White", "#FFE6EFEF": "Sheikh Zayed White", "#FFDCC5BC": "Shell", "#FFEEE7E6": "Shell Brook", "#FF56564B": "Shell Brown", "#FFE88D68": "Shell Coral", "#FFF9E4D6": "Shell Ginger", "#FFEBDFC0": "Shell Haven", "#FFF88180": "Shell Pink", "#FFFDD7CA": "Shell Tint", "#FFB6B6A8": "Shell Walk", "#FFF0EBE0": "Shell White", "#FFB8986C": "Shelter", "#FF758F9A": "Sheltered Bay", "#FFC03F20": "Sh\u0113n Ch\u00e9ng Orange", "#FFBE0620": "Sh\u0113n H\u00f3ng Red", "#FF5A8643": "Shepherd\u2019s Green", "#FFC06F68": "Shepherd\u2019s Warning", "#FF8F8666": "Sheraton Sage", "#FFF8C8BB": "Sherbet Fruit", "#FFFED5A3": "Sherbet Pop", "#FFEBCFAA": "Sheriff", "#FF735153": "Sheringa Rose", "#FF00494E": "Sherpa Blue Variant", "#FFF9E4DB": "Sherry Cream", "#FF555A4C": "Sherwood Forest", "#FF1B4636": "Sherwood Green Variant", "#FFC99B5F": "Shetland", "#FFDFD0C0": "Shetland Lace", "#FFD6A48D": "Shetland Pony", "#FFBBCFD4": "Shhh\u2026", "#FFE29F31": "Sh\u00ec Z\u01d0 Ch\u00e9ng Persimmon", "#FF9900AA": "Shiffurple", "#FFD6C0AB": "Shifting Sand", "#FFA5988A": "Shiitake", "#FF2B2028": "Shikon", "#FFE6B2A6": "Shilo Variant", "#FF88C7E9": "Shimmer", "#FF82DBCC": "Shimmering Blue", "#FF64B3D3": "Shimmering Brook", "#FFF3DEBC": "Shimmering Champagne", "#FF45E9FD": "Shimmering Expanse Cyan", "#FFA4943A": "Shimmering Glade", "#FFFF88CC": "Shimmering Love", "#FFD2EFE6": "Shimmering Pool", "#FF2B526A": "Shimmering Sea", "#FFDBD1E8": "Shimmering Sky", "#FF9A373F": "Shin Godzilla", "#FF59B9C6": "Shinbashi", "#FF006C7F": "Shinbashi Azure", "#FF00A990": "Shindig", "#FFA85E6E": "Shine Baby Shine", "#FF773CA7": "Shiner", "#FF745937": "Shingle Fawn Variant", "#FF908B8E": "Shining Armour", "#FFFCDA89": "Shining Bright", "#FFFFD200": "Shining Gold", "#FF989EA7": "Shining Knight", "#FFC7C7C9": "Shining Silver", "#FFF0E185": "Shinjuku Gold", "#FFDACDCD": "Shinkansen White", "#FF8F1D21": "Shinshu", "#FFA1A9A8": "Shiny Armour", "#FFAE9F65": "Shiny Gold", "#FFCEA190": "Shiny Kettle", "#FFDBDDDB": "Shiny Lustre", "#FFCCD3D8": "Shiny Nickel", "#FF3A363B": "Shiny Rubber", "#FFF7ECCA": "Shiny Silk", "#FFECAE58": "Shiny Trumpet", "#FF7988AB": "Ship Cove Variant", "#FF709797": "Ship Shape", "#FF62493B": "Ship Steering Wheel", "#FF4F84AF": "Ship\u2019s Harbour", "#FF2D3A49": "Ship\u2019s Officer", "#FF7AA3CC": "Shipmate", "#FF968772": "Shipwreck", "#FF4F6F85": "Shipyard", "#FFC48E69": "Shiracha Brown", "#FF842833": "Shiraz Variant", "#FF646B59": "Shire", "#FF68E52F": "Shire Green", "#FFF19000": "Shiritorier\u2019s Orange", "#FFEBF5F0": "Shiroi White", "#FFFEDDCB": "Shironeri Silk", "#FF6598AF": "Shirt Blue", "#FF3C3B3C": "Shisha Coal", "#FFEFAB93": "Shishi Pink", "#FFBBF90F": "Shishito Pepper Green", "#FF63A950": "Shiso Green", "#FF99DBFE": "Shiva Blue", "#FF24DD7E": "Shivering Green", "#FFBB88AA": "Shock Jockey", "#FFE899BE": "Shocking Variant", "#FFFF0D04": "Shocking Crimson", "#FFFF6E1C": "Shocking Orange", "#FFFE02A2": "Shocking Pink Variant", "#FFFF006A": "Shocking Rose", "#FF72C8B8": "Shockwave", "#FF2B2B2B": "Shoe Wax", "#FFEAE4D9": "Shoelace", "#FFF6EBD3": "Shoelace Beige", "#FFDED5C7": "Sh\u014dji", "#FFE2041B": "Shojo\u2019s Blood", "#FFDC3023": "Sh\u014dj\u014dhi Red", "#FFECF0EB": "Shooting Star", "#FF5A4743": "Shopping Bag", "#FF81D5C6": "Shore", "#FF6797A2": "Shore Water", "#FFEAD9CB": "Shoreland", "#FF6C8791": "Shoreline", "#FF58C6AB": "Shoreline Green", "#FFD2CBBC": "Shoreline Haze", "#FFEDD1D3": "Short and Sweet", "#FFBBDFD5": "Short Phase", "#FFF5E6D3": "Shortbread", "#FFEACEB0": "Shortbread Cookie", "#FFEEDAAC": "Shortcake", "#FF9E957C": "Shortgrass Prairie", "#FF1D0D1D": "Shot in the Dark", "#FF4A5C69": "Shot Over", "#FF716B63": "Shot-Put", "#FF37C4FA": "Shovel Knight", "#FFDD835B": "Show Business", "#FFA42E37": "Show-Stopper", "#FF93AEBC": "Showcase Blue", "#FF9FADB7": "Shower", "#FF7737AA": "Shredder Lavender", "#FFE29A86": "Shrimp", "#FFF5BE9D": "Shrimp Boat", "#FFDBBFA3": "Shrimp Boudin", "#FFF4A461": "Shrimp Cocktail", "#FFFAAB77": "Shrimp Shell", "#FFF7C5A0": "Shrimp Toast", "#FFCC3388": "Shrine of Pleasures", "#FF5D84B1": "Shrinking Violet", "#FF003636": "Shrub Green", "#FFA9C08A": "Shrubbery", "#FFB5D1DB": "Shrubby Lichen", "#FFEB6101": "Shu Red", "#FFDCCCA3": "Shui Jiao Dumpling", "#FF2B64AD": "Shukra Blue", "#FF333344": "Shuriken", "#FF666E7F": "Shutter Blue", "#FFBB6622": "Shutter Copper", "#FF797F7D": "Shutter Grey", "#FFBBA262": "Shutterbug", "#FF6C705E": "Shutters", "#FF61666B": "Shuttle Grey", "#FFE2DED6": "Shy Beige", "#FFD3D8DE": "Shy Blunt", "#FFD6DDE6": "Shy Candela", "#FFDEA392": "Shy Champagne Blush", "#FFF0D6CA": "Shy Cupid", "#FFD7DADD": "Shy Denim", "#FFFFD7CF": "Shy Girl", "#FFE5E8D9": "Shy Green", "#FFAA0055": "Shy Guy Red", "#FFE0E4DB": "Shy Mint", "#FFAAAAFF": "Shy Moment", "#FFDFD9DC": "Shy Pink", "#FFDCBBBE": "Shy Smile", "#FFDDAC9A": "Shy Time", "#FFD6C7D6": "Shy Violet", "#FFDFB8BC": "Shy Young Salmon", "#FF5AB9A4": "Shylock", "#FFF3F3D9": "Shyness", "#FF686B50": "Siam Variant", "#FF896F40": "Siam Gold", "#FF9DAC79": "Siamese Green", "#FFEFE1D5": "Siamese Kitten", "#FFCF8D9A": "Sianna Rose", "#FFEEE2D5": "Siberian Fur", "#FF4E6157": "Siberian Green", "#FFC1CCD6": "Siberian Ice", "#FFFF44FF": "Sicilia Bougainvillaea", "#FFFCC792": "Sicilian Villa", "#FFC1C6AD": "Sicily Sea", "#FF502D86": "Sick Blue", "#FF9DB92C": "Sick Green", "#FF94B21C": "Sickly Green", "#FFD0E429": "Sickly Yellow", "#FFE9D9A9": "Sidecar Variant", "#FFBFC3AE": "Sidekick", "#FFA17858": "Sidesaddle", "#FFE2C591": "Sideshow", "#FFDBE9ED": "Sidewalk Chalk Blue", "#FFF7CCC4": "Sidewalk Chalk Pink", "#FF7B8F99": "Sidewalk Grey", "#FFA9561E": "Sienna", "#FFCDA589": "Sienna Buff", "#FFDCC4AC": "Sienna Dust", "#FFDE9F83": "Sienna Ochre", "#FFB1635E": "Sienna Red", "#FFBC735A": "Sienna Sky", "#FFF1D28C": "Sienna Yellow", "#FFA35C46": "Sierra", "#FFA28A67": "Sierra Foothills", "#FFC2BCAE": "Sierra Madre", "#FFED9181": "Sierra Pink", "#FF924E3C": "Sierra Redwood", "#FFAFA28F": "Sierra Sand", "#FFF0C3A7": "Siesta", "#FFC9A480": "Siesta Dreams", "#FFEC7878": "Siesta Rose", "#FFF1E6E0": "Siesta Sands", "#FFE9D8C8": "Siesta Tan", "#FFCBDADB": "Siesta White", "#FFABB6AC": "Sigh of Relief", "#FF76A4A6": "Sightful", "#FFCAAD76": "Sigmarite", "#FFE3EDE2": "Sign of Spring", "#FFFCE299": "Sign of the Crown", "#FF33FF00": "Signal Green", "#FF838684": "Signal Grey", "#FFB15384": "Signal Pink", "#FFECECE6": "Signal White", "#FF455371": "Signature Blue", "#FFEAEDE5": "Silence", "#FFC2A06D": "Silence Is Golden", "#FFE9F1EC": "Silent Breath", "#FFC6EAEC": "Silent Breeze", "#FFE5E7E8": "Silent Delight", "#FF9FA5A5": "Silent Film", "#FF526771": "Silent Night", "#FF231C4D": "Silent Orbit", "#FFABE3DE": "Silent Ripple", "#FFBAD9D9": "Silent River", "#FF729988": "Silent Sage", "#FFA99582": "Silent Sands", "#FF3A4A63": "Silent Sea", "#FFDBD7CE": "Silent Smoke", "#FFEEF7FA": "Silent Snowfall", "#FFC3C7BD": "Silent Storm", "#FF7C929A": "Silent Tide", "#FF0F084B": "Silent Twilight", "#FFE3E7E4": "Silent White", "#FFCCBBBB": "Silentropae Cloud", "#FFB29E81": "Silestone", "#FFCBCDC4": "Silhouette", "#FFDBD4BF": "Silica", "#FFEDE2E0": "Silica Sand", "#FF88B2A9": "Silicate Green", "#FFCDDAD3": "Silicate Light Turquoise", "#FF5A3D4A": "Siliceous Red", "#FFEBE0CA": "Silicone Seduction", "#FFDCDCCF": "Silicone Serena", "#FFD57B65": "Silithus Brown", "#FFBBADA1": "Silk Variant", "#FFCCBFC7": "Silk Chiffon", "#FF354E4B": "Silk Crepe Grey", "#FF6E7196": "Silk Crepe Mauve", "#FFEEE9DC": "Silk Dessou", "#FFF6E8DE": "Silk Elegance", "#FFECDDC9": "Silk for the Gods", "#FFFCEEDB": "Silk Gown", "#FF02517A": "Silk Jewel", "#FF70939E": "Silk Khimar", "#FF9188B5": "Silk Lilac", "#FFFCEFE0": "Silk Lining", "#FFF3F0EA": "Silk Pillow", "#FFC86E8B": "Silk Ribbon", "#FF97976F": "Silk Road", "#FFF6EECD": "Silk Sails", "#FF009283": "Silk Sari", "#FF8B4248": "Silk Satin", "#FFEFDDDF": "Silk Sheets", "#FFA5B2C7": "Silk Sox", "#FFF5EEC6": "Silk Star", "#FFCC9999": "Silk Stone", "#FFF0DFC9": "Silk Teddy", "#FFB77D5F": "Silken Chocolate", "#FFFCE17C": "Silken Gold", "#FF11A39E": "Silken Jade", "#FF427584": "Silken Peacock", "#FFD0D0C9": "Silken Pebble", "#FF495D5A": "Silken Pine", "#FFAA7D89": "Silken Raspberry", "#FFE81320": "Silken Ruby", "#FFD6BEA1": "Silken Stockings", "#FFFEF6D8": "Silken Tofu", "#FFDADCD4": "Silken Web", "#FFFDEFDB": "Silkie Chicken", "#FFEEEECC": "Silkworm", "#FFEAE0CD": "Silky Bamboo", "#FFBDC2BB": "Silky Green", "#FFD7ECD9": "Silky Mint", "#FFFFF5E4": "Silky Tofu", "#FFEFEBE2": "Silky White", "#FFF2F3CD": "Silky Yoghurt", "#FFF4B0BB": "Silly Puddy", "#FF8A7D72": "Silt", "#FFA3B9AC": "Silt Green", "#FFDDDBD0": "Silver Ash", "#FFB8B4B6": "Silver Bells", "#FFD2CFC4": "Silver Birch", "#FFFBF5F0": "Silver Bird", "#FF8A9A9A": "Silver Blue", "#FF5B7085": "Silver Blueberry", "#FFBABAB5": "Silver Bonnet", "#FFB6B5B8": "Silver Bullet", "#FFACAEA9": "Silver Chalice Variant", "#FFADB0B4": "Silver Charm", "#FFE2E4E9": "Silver City", "#FFA6AAA2": "Silver Clouds", "#FFD9DAD2": "Silver Creek", "#FFCDC5C2": "Silver Cross", "#FFC1C1D1": "Silver Dagger", "#FFBDB6AE": "Silver Dollar", "#FF9AB2A9": "Silver Drop", "#FFB4C8D2": "Silver Dusk", "#FFE8E7E0": "Silver Dust", "#FFCFCFCF": "Silver Eagle", "#FF899587": "Silver Eucalyptus", "#FFEDEBE7": "Silver Feather", "#FFE1DDBF": "Silver Fern", "#FF7F7C81": "Silver Filigree", "#FF7196A2": "Silver Fir Blue", "#FFBDBCC4": "Silver Fox", "#FFC6CEC3": "Silver Grass", "#FFDFE4DC": "Silver Grass Traces", "#FFD7D7C7": "Silver Green", "#FFA8A8A4": "Silver Grey", "#FF6D747B": "Silver Hill", "#FFDEDDDD": "Silver Lake", "#FF5686B4": "Silver Lake Blue", "#FFD8DCC8": "Silver Laurel", "#FF9DB7A5": "Silver Leaf", "#FF859382": "Silver Linden Grey", "#FFBBBFC3": "Silver Lined", "#FFB8B1A5": "Silver Lining", "#FFA8A798": "Silver Lustre", "#FF71776E": "Silver Maple Green", "#FFC8C8C0": "Silver Marlin", "#FFDBCCD3": "Silver Mauve", "#FFD6D6D6": "Silver Medal", "#FFBEC2C1": "Silver Mine", "#FF9F8D7C": "Silver Mink", "#FFB4B9B9": "Silver Mistral", "#FFD9D7C9": "Silver Moon", "#FFB6BCB2": "Silver Moss", "#FFE1C1B9": "Silver Peony", "#FFEBECF5": "Silver Phoenix", "#FF526E6B": "Silver Pine", "#FFD9A8A8": "Silver Pink", "#FFC6C6C6": "Silver Polish", "#FFD29EA6": "Silver Rose", "#FFC9A0DF": "Silver Rust Variant", "#FF8E8572": "Silver Sage", "#FFDADEDD": "Silver Sand Variant", "#FFC7C6C0": "Silver Sateen", "#FFA19FA5": "Silver Sconce", "#FFA6AEAA": "Silver Screen", "#FFB2AAAA": "Silver Service", "#FFD8DADB": "Silver Setting", "#FFD8DAD8": "Silver Shadow", "#FF87A1B1": "Silver Skate", "#FFEAECE9": "Silver Sky", "#FF8E9090": "Silver Snippet", "#FFD3D3D2": "Silver Spoon", "#FFB7BDC4": "Silver Springs", "#FFCADFDD": "Silver Spruce", "#FF98A0B8": "Silver Star", "#FF8599A8": "Silver Storm", "#FFB8C7CE": "Silver Strand", "#FFCACDCA": "Silver Strand Beach", "#FFF2C1C0": "Silver Strawberry", "#FF7E7D88": "Silver Surfer", "#FFC4C9E2": "Silver Sweetpea", "#FFB8B2A2": "Silver Taupe", "#FFE7D5C5": "Silver Thistle Beige", "#FFCAC9C2": "Silver Thistle Down", "#FFB6B3A9": "Silver Tinsel", "#FFBFC3BF": "Silver Tipped Sage", "#FFD9D9D3": "Silver Tradition", "#FF67BE90": "Silver Tree Variant", "#FFBBC5C4": "Silver Whiskers", "#FFE7E8E3": "Silver White", "#FF637C5B": "Silver Willow Green", "#FFCDC7C7": "Silver-Tongued", "#FF6A6472": "Silverado", "#FFA7A89B": "Silverado Ranch", "#FFB7BBC6": "Silverado Trail", "#FFCBCBCB": "Silverback", "#FF5A6A43": "Silverbeet", "#FFBEBBC9": "Silverberry", "#FF8D95AA": "Silverfish", "#FFB0B8B2": "Silvermist", "#FFC2C0BA": "Silverplate", "#FFD1D2CB": "Silverpointe", "#FFB1B3B3": "Silverstone", "#FFBFD9CE": "Silverton", "#FFB8B8BF": "Silverware", "#FFE6E5DC": "Silvery Moon", "#FFD5DBD5": "Silvery Streak", "#FF2F4E4E": "Similar", "#FF4C3D30": "Simmered Seaweed", "#FFCB9281": "Simmering Ridge", "#FFA99F96": "Simmering Smoke", "#FFA8C1D4": "Simpatico Blue", "#FFBABAAD": "Simple Grey", "#FFF9A3AA": "Simple Pink", "#FFF1D8B2": "Simple Pleasures", "#FF84737A": "Simple Plum", "#FFC8D9E5": "Simple Serenity", "#FF7A716E": "Simple Silhouette", "#FFCDC7B7": "Simple Stone", "#FFDFD9D2": "Simple White", "#FFCED0DB": "Simplicity", "#FFD6C7B9": "Simplify Beige", "#FF58CEB2": "Simply Aqua", "#FFADBBC9": "Simply Blue", "#FFFBAD90": "Simply Coral", "#FFFFD2C1": "Simply Delicious", "#FFCEDDE7": "Simply Elegant", "#FF00AA81": "Simply Green", "#FFDAC4DC": "Simply Lavender", "#FFFFC06C": "Simply Peachy", "#FF8CB9D4": "Simply Posh", "#FF715BB1": "Simply Purple", "#FFA7A996": "Simply Sage", "#FFC2852E": "Simply Sienna", "#FFB0C5E0": "Simply Sparkling", "#FFE6D2BF": "Simply Subtle Pink", "#FFAD9F93": "Simply Taupe", "#FFA6A1D7": "Simply Violet", "#FFEBEDE7": "Simply White", "#FF82856D": "Simpson Surprise", "#FFFFD90F": "Simpsons Yellow", "#FFCFA236": "Sin City", "#FFFFD500": "Sinag Gold", "#FF4675B7": "Sinatra", "#FFA6D5D0": "Sinbad Variant", "#FF645059": "Sinful", "#FF8E9C98": "Singing in the Rain", "#FF2B4D68": "Singing the Blues", "#FF713E39": "Single Origin", "#FF12110E": "Sinister", "#FF353331": "Sinister Minister", "#FFA89C94": "Sinister Mood", "#FF4C4DFF": "Siniy Blue", "#FF49716D": "Sinkhole", "#FFD8B778": "Sinking Sand", "#FFFEE5CB": "Sinner\u2019s City", "#FFBB1111": "Sinoper Red", "#FFB6BD4A": "Sinsemilla", "#FFDEDFC9": "Sip of Mint", "#FFEAE2DF": "Sip of Nut Milk", "#FF20415D": "Sir Edmund", "#FF69293B": "Siren Variant", "#FF68A43C": "Siren of Nature", "#FFB21D1D": "Siren Scarlet", "#FFF3DECE": "Siren Song", "#FF68766E": "Sirocco Variant", "#FF884411": "Sis Kebab", "#FFC5BAA0": "Sisal Variant", "#FFC8C76F": "Siskin Green", "#FF7A942E": "Siskin Sprout", "#FF863F76": "Sister Loganberry Frost", "#FFDCDEDC": "Site White", "#FF3C2233": "Sitter Red", "#FFF4E2D9": "Sitting Pretty", "#FF3D322E": "Six Feet Under", "#FFFD02FF": "Sixteen Million Pink", "#FF0079A9": "Sixties Blue", "#FF1C1B1A": "Siy\u00e2h Black", "#FF8E3537": "Sizzling Bacon", "#FFA36956": "Sizzling Hot", "#FFEB7E4D": "Sizzling Sunset", "#FFFA005C": "Sizzling Watermelon", "#FF5F9370": "Skarsnik Green", "#FF47413B": "Skavenblight Dinge", "#FFEBDECC": "Skeleton", "#FFF4EBBC": "Skeleton Bone", "#FF773399": "Skeletor\u2019s Cape", "#FFC9133E": "Ski Patrol", "#FFE1E5E3": "Ski Slope", "#FFD2E3E5": "Ski White", "#FF4477DD": "Skies of Cyberspace", "#FF41332F": "Skilandis", "#FFFEFFE3": "Skimmed Milk White", "#FF5CBFCE": "Skink Blue", "#FFC3D7E0": "Skinny Dippin\u2019", "#FF5588FF": "Skinny Jeans", "#FF748796": "Skipper", "#FF4C4D78": "Skipper Blue", "#FFD1D0C9": "Skipping Rocks", "#FFD0CBB6": "Skipping Stone", "#FF51B73B": "Skirret Green", "#FFB04E0F": "Skrag Brown", "#FFF1C78E": "Skullcrusher Brass", "#FFF9F5DA": "Skullfire", "#FF76D6FF": "Sky", "#FF88C1D8": "Sky Babe", "#FF9FB9E2": "Sky Blue Variant", "#FFDCBFE1": "Sky Blue Pink", "#FFA2DCD3": "Sky Blue View", "#FF99C1D6": "Sky Bus", "#FF242933": "Sky Captain", "#FFA5CAD1": "Sky Chase", "#FFA0BDD9": "Sky City", "#FFADDEE5": "Sky Cloud", "#FF4499FF": "Sky Dancer", "#FF8EAABD": "Sky Eyes", "#FF89C6DF": "Sky Fall", "#FFD1DCD8": "Sky Glass", "#FFB6C3C1": "Sky Grey", "#FFA7C2EB": "Sky High", "#FFCADADE": "Sky Light View", "#FF546977": "Sky Lodge", "#FF0099FF": "Sky of Magritte", "#FF82CDE5": "Sky of Ocean", "#FFA2BAD4": "Sky Pilot", "#FF4B7B87": "Sky Space", "#FFC9D3D3": "Sky Splash", "#FFB8DCED": "Sky Wanderer", "#FF8ACFD6": "Sky Watch", "#FFBBCEE0": "Sky\u2019s the Limit", "#FF66CCFF": "Skyan", "#FF60BFD3": "Skydive", "#FFC6D6D7": "Skydiving", "#FF38A3CC": "Skydome", "#FF6BCCC1": "Skylar", "#FFC1E4F0": "Skylark", "#FFB8D6D7": "Skylight", "#FF959EB7": "Skyline", "#FFB9C0C3": "Skyline Steel", "#FF1F7CC2": "Skylla", "#FF818DB3": "Skysail Blue", "#FF6CB3D8": "Skyscape", "#FFD3DBE2": "Skyscraper", "#FFDCD7CD": "Skyvory", "#FFC1DEEA": "Skywalker", "#FF8FFE08": "Skywalker Green", "#FFA6C4CA": "Skyward", "#FFA6BBCF": "Skyway", "#FFC9E0EA": "Skywriter", "#FFDBD5E6": "Slaanesh Grey", "#FFC9CC4A": "Slap Happy", "#FF516572": "Slate", "#FF4B3D33": "Slate Black", "#FFA0987C": "Slate Brown", "#FF465355": "Slate Court", "#FF6E7F8D": "Slate Expectations", "#FF658D6D": "Slate Green", "#FF59656D": "Slate Grey", "#FF625C63": "Slate Mauve", "#FFB4ADA9": "Slate Pebble", "#FFB3586C": "Slate Pink", "#FF868988": "Slate Rock", "#FFBA6671": "Slate Rose", "#FFACB4AC": "Slate Stone", "#FF606E74": "Slate Tile", "#FF7A818D": "Slate Tint", "#FF989192": "Slate Violet", "#FF40535D": "Slate Wall", "#FF4C5055": "Sled", "#FF825D2A": "Sleek Otter", "#FFFAF6E9": "Sleek White", "#FF4579AC": "Sleep", "#FFBED1E1": "Sleep Baby Sleep", "#FF98BDDD": "Sleeping Easy", "#FF786D5E": "Sleeping Giant", "#FFD8D3CF": "Sleeping Inn", "#FFBADBED": "Sleepless Blue", "#FFB2B8B1": "Sleepy", "#FFBCCBCE": "Sleepy Blue", "#FF9B9028": "Sleepy Hamlet", "#FF839C6D": "Sleepy Hollow", "#FFDCC7D5": "Sleepy Kisses", "#FFA2A1A1": "Sleepy Kitten", "#FFB5A78D": "Sleepy Owlet", "#FF8A8C94": "Sleet", "#FFDEC29F": "Slender Reed", "#FFC0AA60": "Slender Stem", "#FFBD997A": "Slice of Brioche", "#FF0022EE": "Slice of Heaven", "#FFE1697C": "Slice of Watermelon", "#FFCCCFBF": "Sliced Cucumber", "#FFEDE5BC": "Slices of Happy", "#FF73CCD8": "Slick Blue", "#FF615D4C": "Slick Green", "#FFA66E49": "Slick Mud", "#FF97AEAF": "Sliding", "#FFCFC9C5": "Slight Mushroom", "#FFCB904E": "Slightly Golden", "#FFFCE6DB": "Slightly in Love", "#FFF1DDD8": "Slightly Peach", "#FFE6CFCE": "Slightly Rose", "#FF92D2ED": "Slightly Spritzig", "#FFDCE4DD": "Slightly Zen", "#FFA7C408": "Slime", "#FF00BB88": "Slime Girl", "#FFB8EBC5": "Slime Lime", "#FFAADD00": "Slimer Green", "#FF7DED17": "Slimy Green", "#FFBFC1CB": "Slipper Satin", "#FFBEBA82": "Slippery Moss", "#FFF87E63": "Slippery Salmon", "#FF7B766C": "Slippery Shale", "#FFEFEDD8": "Slippery Soap", "#FF8D6A4A": "Slippery Stone", "#FFD5F3EC": "Slippery Tub", "#FFC3D4E8": "Slipstream", "#FFD2B698": "Slopes", "#FFDBDCC4": "Slow Dance", "#FFDDE3DC": "Slow Down", "#FFC6D5C9": "Slow Green", "#FFD5D4CE": "Slow Perch", "#FFE1C2BE": "Slubbed Silk", "#FFCA6B02": "Sludge", "#FF42342B": "Slugger", "#FF2D517C": "Slumber", "#FFCDC5B5": "Slumber Sloth", "#FFD6CEC4": "Slush", "#FF804741": "Sly Fox", "#FFF8E2D9": "Sly Shrimp", "#FFAC9A7E": "Smallmouth Bass", "#FF496267": "Smalt Blue Variant", "#FF4A9976": "Smaragdine", "#FF8775A1": "Smashed Grape", "#FFE2D0B9": "Smashed Potatoes", "#FFFF6D3A": "Smashed Pumpkin", "#FFFF5522": "Smashing Pumpkins", "#FFD9DDCB": "Smell of Garlic", "#FFDCE0EA": "Smell of Lavender", "#FFBEF7CF": "Smell the Mint", "#FFBB7283": "Smell the Roses", "#FFD7CECD": "Smells of Fresh Bread", "#FFF0CCD9": "Smidgen of Love", "#FF313138": "Smiler\u2019s Mist", "#FFFFC962": "Smiley Face", "#FF3B646C": "Smock Blue", "#FFBFC8C3": "Smoke", "#FF939789": "Smoke & Ash", "#FFD9E6E8": "Smoke and Mirrors", "#FFCC7788": "Smoke Bush", "#FFAD8177": "Smoke Bush Rose", "#FFADB6B9": "Smoke Cloud", "#FFCCBBAA": "Smoke Dragon", "#FFCEBAA8": "Smoke Grey", "#FFB1B8B2": "Smoke Infusion", "#FF9BACB3": "Smoke on the Water", "#FF446B61": "Smoke Pine", "#FFBB5F34": "Smoke Tree", "#FF84625A": "Smoked Almond", "#FF5A4351": "Smoked Amethyst", "#FF3B2F2F": "Smoked Black Coffee", "#FF583A39": "Smoked Claret", "#FF674244": "Smoked Flamingo", "#FFF2B381": "Smoked Ham", "#FFCEB5B3": "Smoked Lavender", "#FFA89C97": "Smoked Mauve", "#FF725F6C": "Smoked Mulberry", "#FF573F16": "Smoked Oak Brown", "#FFD9D2CD": "Smoked Oyster", "#FF793A30": "Smoked Paprika", "#FF6F6E70": "Smoked Pearl", "#FF444251": "Smoked Purple", "#FFDDBBCC": "Smoked Silver", "#FFAEA494": "Smoked Tan", "#FFD0C6BD": "Smoked Umber", "#FF716354": "Smokehouse", "#FF5E5755": "Smokescreen", "#FFBEB2A5": "Smokestack", "#FF647B84": "Smokey Blue", "#FF88716D": "Smokey Claret", "#FFE9DFD5": "Smokey Cream", "#FF747B92": "Smokey Denim", "#FF928996": "Smokey Lilac", "#FFCEBDB4": "Smokey Pink", "#FFA5B5AC": "Smokey Slate", "#FF9A9DA2": "Smokey Stone", "#FF9F8C7C": "Smokey Tan", "#FFA57B5B": "Smokey Topaz Variant", "#FFA7A5A3": "Smokey Wings", "#FF954A3D": "Smokin Hot", "#FFA29587": "Smoking Mirror", "#FF43454C": "Smoking Night Blue", "#FF992200": "Smoking Red", "#FF605D6B": "Smoky Variant", "#FF708D9E": "Smoky Azurite", "#FFB9A796": "Smoky Beige", "#FF7196A6": "Smoky Blue", "#FF34282C": "Smoky Charcoal", "#FFA49E93": "Smoky Day", "#FF4C726B": "Smoky Emerald", "#FF817D68": "Smoky Forest", "#FF9B8FA6": "Smoky Grape", "#FF939087": "Smoky Grey Green", "#FFD7D9C6": "Smoky Light", "#FF998BA5": "Smoky Mauve", "#FFAFA8A9": "Smoky Mountain", "#FF71ADB2": "Smoky Mountain Spring", "#FFE1D9DC": "Smoky Orchid", "#FFBB8D88": "Smoky Pink", "#FF5C6E7C": "Smoky Pitch", "#FF51484F": "Smoky Quartz", "#FFE2B6A7": "Smoky Salmon", "#FFA1A18F": "Smoky Slate", "#FF7E8590": "Smoky Studio", "#FFAA9793": "Smoky Sunrise", "#FF9D9E9D": "Smoky Tone", "#FF7E7668": "Smoky Topaz", "#FF857D72": "Smoky Trout", "#FF647D9E": "Smoky Violet", "#FFAEADA3": "Smoky White", "#FFB2ACA9": "Smoky Wings", "#FF7E2056": "Smooch", "#FFD13D4B": "Smooch Rouge", "#FFF4E4B3": "Smooth as Corn Silk", "#FFD3BB96": "Smooth Beech", "#FF5D4E4C": "Smooth Coffee", "#FFCABAB1": "Smooth Pebbles", "#FFA2D5D3": "Smooth Satin", "#FFF6EAD2": "Smooth Silk", "#FFBCB6B3": "Smooth Stone", "#FF97B2B1": "Smooth-Hound Shark", "#FF988E01": "Smoothie Green", "#FFB6321E": "Smouldering", "#FFAA6E4B": "Smouldering Copper", "#FFCA3434": "Smouldering Red", "#FFEE4466": "Smudged Lips", "#FFE9EEEB": "Snail Trail Silver", "#FFC8C1AA": "Snake Charmer", "#FFE9CB4C": "Snake Eyes", "#FFDB2217": "Snake Fruit", "#FF45698C": "Snake River", "#FFBB4444": "Snakebite", "#FFBAA208": "Snakebite Leather", "#FF889717": "Snakes in the Grass", "#FF786E61": "Snakeskin", "#FF8A8650": "Snap Pea Green", "#FF2B3E52": "Snap-Shot", "#FFFED777": "Snapdragon", "#FFBCF5A6": "Snappy Green", "#FFEB8239": "Snappy Happy", "#FFCC0088": "Snappy Violet", "#FF9AE37D": "Snarky Mint", "#FF840014": "Sneaky Devil", "#FF896A46": "Sneaky Sesame", "#FFF2B635": "Sneezeweeds", "#FF9D7938": "Sneezy", "#FF718854": "Snip of Parsley", "#FFDCCEBB": "Snip of Tannin", "#FF92B57B": "Snipped Chive", "#FFDD7733": "Snobby Shore", "#FF49556C": "Snoop", "#FF034F84": "Snorkel Blue", "#FF004F7D": "Snorkel Sea", "#FF222277": "Snorlax", "#FFACBB0D": "Snot", "#FF9DC100": "Snot Green", "#FFDEF1E7": "Snow Ballet", "#FFDDE3E8": "Snow Blanket", "#FFE5E9EB": "Snow Cloud", "#FFE4F0E8": "Snow Crystal Green", "#FFF7F5ED": "Snow Day", "#FFE3E3DC": "Snow Drift Variant", "#FFB3C0D3": "Snow Flurries", "#FFEAF7C9": "Snow Flurry Variant", "#FFF4F2E9": "Snow Globe", "#FFC3D9CB": "Snow Goose", "#FFC9DCDC": "Snow in June", "#FFCFDFDB": "Snow Leopard", "#FFEBE2CC": "Snow on the Pines", "#FFFCD0C7": "Snow Pa", "#FF6CCC7B": "Snow Pea", "#FFE0DCDB": "Snow Peak", "#FFEBC6C0": "Snow Pink", "#FFF4EAF0": "Snow Plum", "#FFD2D8DA": "Snow Quartz", "#FFD7E4ED": "Snow Shadow", "#FFEEEDEA": "Snow Storm", "#FFDADCE0": "Snow Tiger", "#FFEEFFEE": "Snow White", "#FFF8AFA9": "Snow White Blush", "#FFD9E9E5": "Snowball Effect", "#FFE8E9E9": "Snowbank", "#FFBED0DA": "Snowbell", "#FFEEF1EC": "Snowbelt", "#FFEFECED": "Snowberry", "#FF74A9B9": "Snowboard", "#FFDDEBE3": "Snowbound", "#FFDCE5EB": "Snowcap", "#FFD6E5F0": "Snowdrift Glow", "#FFEEFFCC": "Snowdrop", "#FFE0EFE1": "Snowdrop Explosion", "#FFE8E5DE": "Snowed In", "#FFE0DEDA": "Snowfall", "#FFEEEDE0": "Snowfall White", "#FFEFF0F0": "Snowflake", "#FFC8C8C4": "Snowglory", "#FFFEFAFB": "Snowman", "#FFC9E6E9": "Snowmelt", "#FFE7E3D6": "Snowshoe Hare", "#FF001188": "Snowstorm Space Shuttle", "#FFEDF2E0": "Snowy Evergreen", "#FFD6F0CD": "Snowy Mint Variant", "#FFF1EEEB": "Snowy Mount", "#FFF0EFE3": "Snowy Pine", "#FFD3DBEC": "Snowy Shadow", "#FFC5D8E9": "Snowy Summit", "#FFA5ADBD": "Snub", "#FFE4D7E5": "Snuff Variant", "#FFBEA998": "Snug as a Bug", "#FFFCDB80": "Snug Yellow", "#FFA58F73": "Snuggle Pie", "#FFE1B89B": "So Baja", "#FFD4D8E3": "So Blue-Berry", "#FFEC7A93": "So Charming", "#FFCECDC5": "So Chic!", "#FFDFC89D": "So Close", "#FFCDC0C9": "So Dainty", "#FFD8D4D7": "So Fresh so Clean", "#FFB17494": "So Long Shadow", "#FF84525A": "So Merlot", "#FFF1E0CB": "So Much Fawn", "#FFDAD5D6": "So Shy", "#FF00FF11": "So Sour", "#FF8B847C": "So Sublime", "#FFFFDEF2": "So This Is Love", "#FF006F47": "So-Sari", "#FFF7D163": "Soaked in Sun", "#FFCEC8EF": "Soap", "#FFB2DCEF": "Soap Bubble", "#FFA0B28E": "Soap Green", "#FFE5BFCA": "Soap Pink", "#FFECE5DA": "Soapstone Variant", "#FFDDF0F0": "Soar", "#FF9BADBE": "Soaring Eagle", "#FFB9E5E8": "Soaring Sky", "#FFD1B49F": "Soba", "#FF22BB00": "Soccer Turf", "#FFCF8C76": "Sociable", "#FFFAF2DC": "Social Butterfly", "#FF921A1C": "Socialist", "#FF907676": "Socialite", "#FFE49780": "Sockeye", "#FFC3C67E": "Soda Pop", "#FF2C447B": "Sodalite Blue", "#FF9B533F": "S\u014ddenkaracha Brown", "#FF93806A": "Sofisticata", "#FFEAE0D6": "Soft & Warm", "#FFCFBEA5": "Soft Amber Variant", "#FFCFB7C9": "Soft Amethyst", "#FFE0B392": "Soft Apricot", "#FF897670": "Soft Bark", "#FFB9B5AF": "Soft Beige", "#FF6488EA": "Soft Blue", "#FF888CBA": "Soft Blue Lavender", "#FFDAE7E9": "Soft Blue White", "#FFE3BCBC": "Soft Blush", "#FFFFB737": "Soft Boiled", "#FFF6F0EB": "Soft Breeze", "#FF99656C": "Soft Bromeliad", "#FFA18666": "Soft Bronze", "#FFF4E1B6": "Soft Butter", "#FFFFEDBE": "Soft Buttercup", "#FFF7EACF": "Soft Candlelight", "#FFEFB6D8": "Soft Cashmere", "#FFBFCFC8": "Soft Celadon", "#FFC4CD87": "Soft Celery", "#FFDBB67A": "Soft Chamois", "#FF838298": "Soft Charcoal", "#FFE4BC5B": "Soft Cheddar", "#FFD0E3ED": "Soft Cloud", "#FF987B71": "Soft Cocoa", "#FFFFEEE0": "Soft Coral", "#FFF5EFD6": "Soft Cream", "#FFB9C6CA": "Soft Denim", "#FFE0CFB9": "Soft Doeskin", "#FFC2BBB2": "Soft Dove", "#FFB59778": "Soft Fawn", "#FFEFE4DC": "Soft Feather", "#FFC7D368": "Soft Fern", "#FF817714": "Soft Fig", "#FFE2EFDD": "Soft Focus", "#FFC0D5CA": "Soft Fresco", "#FFBDCCB3": "Soft Froth", "#FFD496BD": "Soft Fuchsia", "#FF7E7574": "Soft Fur", "#FFFBEEDE": "Soft Gossamer", "#FFC1DFC4": "Soft Grass", "#FF6FC276": "Soft Green", "#FFD7C3B5": "Soft Greige", "#FFC8D8D1": "Soft Haze", "#FFBEA8B7": "Soft Heather", "#FFE7CFCA": "Soft Ice Rose", "#FFB28EA8": "Soft Impact", "#FFA28B7E": "Soft Impala", "#FFE6E3EB": "Soft Iris", "#FFFBF1DF": "Soft Ivory", "#FFD1D2BE": "Soft Kind", "#FFF5EDE5": "Soft Lace", "#FFF6E5F6": "Soft Lavender", "#FFD9A077": "Soft Leather", "#FFE2D4DF": "Soft Lilac", "#FFBEDDBA": "Soft Lumen", "#FFDD99BB": "Soft Matte", "#FFBAB2B1": "Soft Metal", "#FFE6F9F1": "Soft Mint", "#FFEFECD7": "Soft Moonlight", "#FFCCE1C7": "Soft Moss", "#FFF7EADF": "Soft Muslin", "#FF59604F": "Soft Olive", "#FFEEC0AB": "Soft Orange", "#FFFFDCD2": "Soft Orange Bloom", "#FF555054": "Soft Panther", "#FFEEDFDE": "Soft Peach Variant", "#FFFFF3F0": "Soft Peach Mist", "#FFEFE7DB": "Soft Pearl", "#FFCDC1B2": "Soft Pelican", "#FFEBF8EF": "Soft Petals", "#FFFFF5E7": "Soft Pillow", "#FFFDB0C0": "Soft Pink", "#FFFDEBE5": "Soft Pink Mist", "#FF949EA2": "Soft Pumice", "#FFDC8E31": "Soft Pumpkin", "#FFA66FB5": "Soft Purple", "#FFC0BBA5": "Soft Putty", "#FF927C75": "Soft Rabbit Brown", "#FF412533": "Soft Red", "#FFFDD47E": "Soft Saffron", "#FFBCBCAE": "Soft Sage", "#FFEAAAA2": "Soft Salmon", "#FFEEC5CE": "Soft Satin", "#FF837E87": "Soft Savvy", "#FFD6D4CA": "Soft Secret", "#FFE8D5C6": "Soft Shoe", "#FFA2BBC1": "Soft Shore", "#FFD09F93": "Soft Sienna", "#FFE1DFE2": "Soft Silk", "#FFF7F9E9": "Soft Silver", "#FFE4B185": "Soft Skills", "#FFB5B5CB": "Soft Sky", "#FF404854": "Soft Steel", "#FFC6B8A5": "Soft Stones", "#FFF5D180": "Soft Straw", "#FFD8CBAD": "Soft Suede", "#FFA1D7EF": "Soft Summer Rain", "#FFF2E3D8": "Soft Sunrise", "#FFC3B3B2": "Soft Tone", "#FF9D6016": "Soft Tone Ink", "#FF639B95": "Soft Touch", "#FF74CED2": "Soft Turquoise", "#FFDDDEBE": "Soft Vellum", "#FFE9E6E2": "Soft Violet", "#FFD9BD9C": "Soft Wheat", "#FFECE8DD": "Soft Wool", "#FFE3CDB6": "Soft-Spoken", "#FFBBBCA7": "Softened Green", "#FFDACAB2": "Softer Tan", "#FFC9B7CE": "Softly Softly", "#FFF3CA40": "Softsun", "#FF7F8486": "Software", "#FFE0815E": "Sohi Orange", "#FFE35C38": "Sohi Red", "#FFAB6953": "Soho Red", "#FF845C00": "Soil of Avagddu", "#FF416F8B": "Sojourn Blue", "#FFFBEAB8": "Solar", "#FFCC6622": "Solar Ash", "#FFF7DA74": "Solar Energy", "#FFE67C41": "Solar Flare", "#FFDC9F46": "Solar Fusion", "#FFFAF1BD": "Solar Glow", "#FFFAF0C9": "Solar Light", "#FFF0CA4A": "Solar Plexus Chakra", "#FFF4B435": "Solar Power", "#FFD2AB51": "Solar Relic", "#FFFFC16C": "Solar Storm", "#FFFCE9B9": "Solar Wind", "#FFF5D68F": "Solaria", "#FFE1BA36": "Solarium", "#FFFBCF4F": "Solarized", "#FF545A2C": "Soldier Green", "#FFF7DDA1": "Sol\u00e9", "#FFE9CB2E": "Soleil", "#FFD3D8D8": "Solemn Silence", "#FF635C59": "Solid Empire", "#FFB7D24B": "Solid Gold", "#FFEEEAE2": "Solid Opal", "#FFC78B95": "Solid Pink Variant", "#FFA1A58C": "Solid Snake", "#FFC6DECF": "Solitaire Variant", "#FF80796D": "Solitary Slate", "#FFC4C7C4": "Solitary State", "#FF539B6A": "Solitary Tree", "#FFE9ECF1": "Solitude Variant", "#FFCBD2D0": "Solo", "#FFBABDB8": "Solstice", "#FFCDD2BB": "Solstice Breeze", "#FF77ABAB": "Solution", "#FF6C5751": "Somali Brown", "#FFCBB489": "Sombre", "#FF005C2B": "Sombre Green", "#FF555470": "Sombre Grey", "#FF161E34": "Sombre Night", "#FFAF546B": "Sombre Roses", "#FFB39C8C": "Sombrero", "#FFCBA391": "Sombrero Tan", "#FFEFE4CC": "Someday", "#FFB0D6E6": "Something Blue", "#FFCC99DD": "Somewhere in a Fairytale", "#FF5D3736": "Sommelier", "#FFABC8D8": "Sonata", "#FF8A9EAE": "Sonata Blue", "#FF55AA44": "Sonata in Green Minor", "#FFFCE7B5": "Song of Summer", "#FF4A73A8": "Song of the Sea", "#FFAF987F": "Song Thrush", "#FFF2E5E0": "Song Thrush Egg", "#FFA3D1EB": "Songbird", "#FFF3C8C2": "Sonia Rose", "#FF17569B": "Sonic Blue", "#FFC5D061": "Sonic Lime", "#FF83599B": "Sonic Plum", "#FFA0D1EC": "Sonic Sky", "#FFC0C7C4": "Sonnet Grey", "#FFDDCB91": "Sonoma Chardonnay", "#FF90A58A": "Sonoma Sage", "#FFBFD1CA": "Sonoma Sky", "#FFE0B493": "Sonora Apricot", "#FFBEA77D": "Sonora Hills", "#FFE8D2E3": "Sonora Rose", "#FFC89672": "Sonora Shade", "#FFCFB8A1": "Sonoran Desert", "#FFDDD5C6": "Sonoran Sands", "#FFFAF0CB": "Sonorous Bells", "#FF7BB369": "Soon", "#FF550000": "Soooo Bloody", "#FF555E5F": "Soot", "#FF5D5E5C": "Soot Veil", "#FF9CC9D4": "Soothing Blue", "#FFB3BEC4": "Soothing Breeze", "#FFF2E7DE": "Soothing Pink", "#FFB4AE95": "Soothing Sage", "#FF307DD3": "Soothing Sapphire", "#FFBCE6DE": "Soothing Sea", "#FFBCCBC4": "Soothing Spring", "#FFE1EFE2": "Soothing Vapour", "#FFE1E2E4": "Soothing White", "#FF8092BC": "Soothsayer", "#FF141414": "Sooty", "#FF474D52": "Sooty Lashes", "#FF4D4B3A": "Sooty Willow Bamboo", "#FF956C87": "Sophisticated Lilac", "#FF5D5153": "Sophisticated Plum", "#FF537175": "Sophisticated Teal", "#FFBFB5A6": "Sophistication", "#FF7D7170": "Sophomore", "#FFA0D8EF": "Sora Blue", "#FF4D8FAC": "Sora Sky", "#FFA1A6D6": "Sorbet Ice Mauve", "#FFDAC100": "Sorbet Yellow", "#FFDD6B38": "Sorbus Variant", "#FF3398CE": "Sorcerer", "#FF9B6D51": "Sorrel Brown", "#FFA49688": "Sorrel Felt", "#FF887E64": "Sorrel Leaf", "#FF9D7F61": "Sorrell Brown Variant", "#FFF1D058": "Sorreno Lemon", "#FFFC0156": "Sorx Red", "#FF47788A": "Sotek Green", "#FFEDD1A8": "Souffl\u00e9", "#FF5C1C39": "Soul Anchor", "#FF7EADC7": "Soul Blue", "#FF7E989D": "Soul Quenching", "#FF377290": "Soul Search", "#FF624C4C": "Soul Searching", "#FFFFAA55": "Soul Side", "#FFB2B5C8": "Soul Sister", "#FF58475E": "Soul Train", "#FF374357": "Soulful", "#FF757C91": "Soulful Blue", "#FFB8B5AA": "Soulful Grey", "#FF3B4457": "Soulful Music", "#FF1B150D": "Soulless", "#FF85777B": "Soulmate", "#FFFFC8E9": "Soulmate Pink", "#FF333B33": "Sound of Silence", "#FFA0C6D7": "Sound Waves", "#FFDFE5D7": "Sounds of Nature", "#FFE5EDB5": "Sour", "#FFA0AC4F": "Sour Apple", "#FFAAEE22": "Sour Apple Candy", "#FF33BB00": "Sour Apple Rings", "#FF8B844E": "Sour Bubba", "#FF66B348": "Sour Candy", "#FFE24736": "Sour Cherry", "#FFADC979": "Sour Face", "#FFC1E613": "Sour Green", "#FFC8FFB0": "Sour Green Cherry", "#FFFFEEA5": "Sour Lemon", "#FFACC326": "Sour Lime", "#FFF4D9C5": "Sour Patch Peach", "#FFFEE5C8": "Sour Tarts", "#FFFFFFBF": "Sour Veil", "#FFEEFF04": "Sour Yellow", "#FFCDEAE5": "Source Blue", "#FF84B6A2": "Source Green", "#FFC9B59A": "Sourdough", "#FFE8A05A": "South Haven", "#FF76614B": "South Kingston", "#FF698694": "South Pacific", "#FFEAD2BB": "South Peach", "#FFEADFD2": "South Peak", "#FFA6847B": "South Rim Trail", "#FFFFDC9E": "South Shore Sun", "#FFB98258": "Southern Barrens Mud", "#FFF7DDDB": "Southern Beauty", "#FFA6D6C3": "Southern Belle", "#FF365787": "Southern Blue", "#FFE4DFD1": "Southern Breeze", "#FF34657D": "Southern Evening", "#FFBCA66A": "Southern Moss", "#FFE5B792": "Southern Peach", "#FFACB4AB": "Southern Pine", "#FFD0D34D": "Southern Platyfish", "#FF9D766F": "Southern Road", "#FFDE9F85": "Southwest Stone", "#FFCC6758": "Southwestern Clay", "#FFEDE0CE": "Southwestern Sand", "#FF4B4356": "Sovereign", "#FFCE243F": "Sovereign Red", "#FF304E63": "Sovereignty", "#FFFFD900": "Soviet Gold", "#FFCD0000": "Soviet Red", "#FFD5D2C7": "Soy Milk", "#FF1A0600": "Soy Sauce", "#FFFAE3BC": "Soya", "#FF6F634B": "Soya Bean Variant", "#FFCAB68B": "Soybean", "#FFCEECE7": "Spa", "#FFD3DEDF": "Spa Blue", "#FF1993BE": "Spa Dream", "#FF529182": "Spa Green", "#FFD4E4E6": "Spa Retreat", "#FFD7C9A5": "Spa Sangria", "#FF3B4271": "Space Angel", "#FF440099": "Space Battle Blue", "#FF150F5B": "Space Colonisation", "#FF667788": "Space Convoy", "#FF002299": "Space Dust", "#FF001199": "Space Exploration", "#FF114499": "Space Explorer", "#FF110022": "Space Grey", "#FF139D08": "Space Invader", "#FF324471": "Space Missions", "#FF5511DD": "Space Opera", "#FF4B433B": "Space Shuttle", "#FF6C6D7A": "Space Station", "#FFDAE6EF": "Space Wolves Grey", "#FF5C6B6B": "Spacebox", "#FF5F6882": "Spaceman", "#FF222255": "Spacescape", "#FF877D75": "Spacious Grey", "#FF9A8557": "Spacious Plain", "#FFD5EAF2": "Spacious Skies", "#FFAEB5C7": "Spacious Sky", "#FF424142": "Spade Black", "#FFFEF69E": "Spaghetti", "#FFDDDDAA": "Spaghetti Carbonara", "#FFEECC88": "Spaghetti Monster", "#FF8D7F75": "Spalding Grey", "#FF36B14E": "Spandex Green", "#FFE5DBE5": "Spangle", "#FF7F5F52": "Spanish Chestnut", "#FFFCE5C0": "Spanish Cream", "#FFE51A4C": "Spanish Crimson", "#FF817863": "Spanish Galleon", "#FFB09A4F": "Spanish Gold", "#FF7B8976": "Spanish Green Variant", "#FFFCE8CA": "Spanish Lace", "#FF8E6A3F": "Spanish Leather", "#FF684B40": "Spanish Mustang", "#FFA1A867": "Spanish Olive", "#FFC57556": "Spanish Peanut", "#FF5C3357": "Spanish Plum", "#FF66033C": "Spanish Purple", "#FF61504E": "Spanish Raisin", "#FF111133": "Spanish Roast", "#FFAC6768": "Spanish Rose Shadow", "#FFCAB08E": "Spanish Sand", "#FF93765C": "Spanish Style", "#FF702425": "Spanish Tile", "#FFDBB39E": "Spanish Villa", "#FFDED1B7": "Spanish White", "#FFF6B511": "Spanish Yellow", "#FFE4E4DD": "Spare White", "#FFF5D2B5": "Sparkle Glow", "#FFFFEE99": "Sparkler", "#FF77B244": "Sparkling Apple", "#FFC15187": "Sparkling Blueberry Lemonade", "#FFDCEEE3": "Sparkling Brook", "#FFEFCF98": "Sparkling Champagne", "#FFFFFDEB": "Sparkling Cider", "#FFF9736E": "Sparkling Cosmo", "#FF2DA4B6": "Sparkling Cove", "#FF1F6C53": "Sparkling Emerald", "#FFD2D5DA": "Sparkling Frost", "#FF82387E": "Sparkling Grape", "#FF66EE00": "Sparkling Green", "#FFAFC5BC": "Sparkling Lake", "#FFEECCFF": "Sparkling Lavender", "#FFC3C3C7": "Sparkling Metal", "#FFA4DDD3": "Sparkling Mint", "#FFF5CEE6": "Sparkling Pink", "#FFCC11FF": "Sparkling Purple", "#FFEE3333": "Sparkling Red", "#FFD6EDF1": "Sparkling River", "#FFC7CBBC": "Sparkling Sage", "#FFCBD0CD": "Sparkling Silver", "#FFF5FEFD": "Sparkling Snow", "#FFD9E3E0": "Sparkling Spring", "#FFFF7711": "Sparks in the Dark", "#FF22EEFF": "Sparky Blue", "#FF69595C": "Sparrow", "#FF523E47": "Sparrow Grey Red", "#FFFF6622": "Sparrow\u2019s Fire", "#FF76A4A7": "Spartacus", "#FF7A8898": "Spartan Blue", "#FF9E1316": "Spartan Crimson", "#FFAFA994": "Spartan Stone", "#FFC1EDD3": "Spatial Spirit", "#FFDEDDDB": "Spatial White", "#FFFFEE88": "Sp\u00e4tzle Yellow", "#FFFFD9A6": "Speak", "#FF826A6C": "Speakeasy", "#FFA8415B": "Speaking of the Devil", "#FF885500": "Spear Shaft", "#FF5FB6BF": "Spearfish", "#FF64BFA4": "Spearmint", "#FF5CBE86": "Spearmint Burst", "#FF8DC2A8": "Spearmint Frosting", "#FF9CB8A6": "Spearmint Haze", "#FFBFD3CB": "Spearmint Ice", "#FFCBECE6": "Spearmint Leaf", "#FFDBE8DF": "Spearmint Scent", "#FFE8F0E2": "Spearmint Stick", "#FFB1EAE8": "Spearmint Water", "#FFBCE3C9": "Spearmints", "#FFA5B2B7": "Special Delivery", "#FF7B787D": "Special Grey", "#FF868B53": "Special Ops", "#FFEBC09B": "Special Peach", "#FFDCD867": "Species", "#FFD38798": "Speckled Easter Egg", "#FFEBD6BD": "Speckled Eggs", "#FFBB02FE": "Spectacular Purple", "#FFEDD924": "Spectacular Saffron", "#FFF72305": "Spectacular Scarlet", "#FF375D4F": "Spectra Variant", "#FF00A096": "Spectra Green", "#FFF6AA09": "Spectra Yellow", "#FFDFDCD8": "Spectral", "#FF008664": "Spectral Green", "#FF44448D": "Spectrum Blue", "#FFF20000": "Spectrum Red", "#FF90BFD4": "Speedboat", "#FFB4547E": "Speedwell", "#FF5E4F50": "Spell", "#FF4A373E": "Spell Caster", "#FFE7DFCE": "Spellbound", "#FFA38C6B": "Spelt Grain Brown", "#FF35465E": "Spelunking", "#FFA5AD44": "Sphagnales Moss", "#FF75693D": "Sphagnum Moss", "#FFF2E8CC": "Sphere", "#FFA99593": "Sphinx", "#FF6C4F3F": "Spice Variant", "#FF86613F": "Spice Bazaar", "#FFB87243": "Spice Cake", "#FFF0DED3": "Spice Cookie", "#FFF3E9CF": "Spice Delight", "#FFC9D6B4": "Spice Garden", "#FFE1C2C1": "Spice Girl", "#FFEBD0A4": "Spice Is Nice", "#FFF4EEDC": "Spice Ivory", "#FFB84823": "Spice Market", "#FF86493F": "Spice of Life", "#FFFFB088": "Spice Pink", "#FFC26E4B": "Spice Route", "#FF604941": "Spiceberry", "#FFBB715B": "Spiced", "#FF7F403A": "Spiced Apple", "#FFE9D2BB": "Spiced Beige", "#FF85443F": "Spiced Berry", "#FFBB9683": "Spiced Brandy", "#FFFFD978": "Spiced Butternut", "#FFA4624C": "Spiced Carrot", "#FFD3B080": "Spiced Cashews", "#FF915B41": "Spiced Cider", "#FF805B48": "Spiced Cinnamon", "#FFD55459": "Spiced Coral", "#FFA38061": "Spiced Honey", "#FF53433E": "Spiced Hot Chocolate", "#FF886C57": "Spiced Latte", "#FFB99563": "Spiced Mustard", "#FFFFBB72": "Spiced Nectarine", "#FFBC693E": "Spiced Nut", "#FF927D6C": "Spiced Nutmeg", "#FFEDC7B6": "Spiced Orange", "#FF764F80": "Spiced Plum", "#FF905D5F": "Spiced Potpourri", "#FFD88D56": "Spiced Pumpkin", "#FFBB1166": "Spiced Purple", "#FF8B4C3D": "Spiced Red", "#FFAD8B6A": "Spiced Rum", "#FFAB6162": "Spiced Tea", "#FFB14B38": "Spiced Up", "#FFE67A37": "Spiced up Orange", "#FFCDBA99": "Spiced Vinegar", "#FF664942": "Spiced Wine", "#FFFF1111": "Spicy", "#FFCC3366": "Spicy Berry", "#FF9B5B4F": "Spicy Cayenne", "#FFA85624": "Spicy Cinnamon", "#FF994B35": "Spicy Hue", "#FFEEBBAA": "Spicy Hummus", "#FF8B5F4D": "Spicy Mix Variant", "#FFDA482D": "Spicy Orange", "#FFF38F39": "Spicy Paella", "#FFFF1CAE": "Spicy Pink Variant", "#FFB9396E": "Spicy Purple", "#FF97413E": "Spicy Red", "#FFF6AC00": "Spicy Sweetcorn", "#FFC75433": "Spicy Tomato", "#FFE2E8DF": "Spider Cotton", "#FF656271": "Spike", "#FFFDDDB7": "Spiked Apricot", "#FF600000": "Spikey Red", "#FF9B351B": "Spill the Beans", "#FFE4E1DE": "Spilled Cappuccino", "#FFF4F4D1": "Spilt Milk", "#FF46A35A": "Spinach", "#FFAAAA55": "Spinach Banana Smoothie", "#FFB1CDAC": "Spinach Dip", "#FF909B4C": "Spinach Green", "#FF83825B": "Spinach Souffle", "#FF6E750E": "Spinach Soup", "#FFE4E8DA": "Spinach White", "#FFB3C4D8": "Spindle Variant", "#FF73FCD6": "Spindrift", "#FF464D4A": "Spinel Black", "#FF49424A": "Spinel Grey", "#FF59181C": "Spinel Red", "#FF272A3B": "Spinel Stone Black", "#FF38283D": "Spinel Violet", "#FFA3E2DD": "Spinnaker", "#FF018CB6": "Spinnaker Blue", "#FF5B6A7C": "Spinning Blue", "#FFF3DDBC": "Spinning Silk", "#FFF6EDDA": "Spinning Wheel", "#FFB2BBC6": "Spirit", "#FF323B6C": "Spirit Blue", "#FF514B80": "Spirit Dance", "#FF6A8B98": "Spirit Mountain", "#FF5F534E": "Spirit Rock", "#FFD45341": "Spirit Warrior", "#FFE3EEBF": "Spirit Whisper", "#FFFDE7E3": "Spirited Away", "#FFBDDEC7": "Spirited Green", "#FFFFDC83": "Spirited Yellow", "#FFFD411E": "Spiritstone Red", "#FF5A665C": "Spirulina", "#FF6F757D": "Spitsbergen Blue", "#FFF1D79E": "Splash", "#FFD8B98C": "Splash of Honey", "#FF5984B0": "Splash Palace", "#FFD4E8D8": "Splashdown", "#FF44DDFF": "Splashing Wave", "#FF019196": "Splashy", "#FFA9586C": "Splatter", "#FFD01A2C": "Splatter Movie", "#FFCCEE00": "Spleen Green", "#FF7598B5": "Splendid Blue", "#FF806E7C": "Splendiferous", "#FFF3DFCC": "Splendour", "#FF5870A4": "Splendour and Pride", "#FFFFB14E": "Splendour Gold", "#FFA3713F": "Splinter", "#FF3194CE": "Splish Splash", "#FF96983F": "Split Pea", "#FFC8B165": "Split Pea Soup", "#FF8E6C51": "Split Rail", "#FFE8FF2A": "Spoiled Egg", "#FFB6BFE5": "Spoiled Rotten", "#FFA0956F": "Sponge", "#FFFFFE40": "Sponge Cake", "#FFC2A048": "Spontaneous Sprig", "#FFD1D2BF": "Spooky", "#FFD4D1D9": "Spooky Ghost", "#FF685E4F": "Spooky Graveyard", "#FFFF6B00": "Spooky Tangerine", "#FFF5EAE3": "Spooled White", "#FFE7E9E3": "Spoonful of Sugar", "#FF7F8162": "Spores", "#FF00A27D": "Sport Green", "#FFEFD678": "Sport Yellow", "#FF434C47": "Sporting Green", "#FF399BB4": "Sports Blue", "#FFE08119": "Sports Fan", "#FF4D8064": "Sports Field Green", "#FF6A8AA4": "Sporty Blue", "#FFFAEACD": "Spotlight", "#FFBFBFBD": "Spotted Dove", "#FFB1D3E3": "Spotted Snake Eel", "#FF7ECDDD": "Spray Variant", "#FFAEA692": "Spray Green", "#FFBDD0C3": "Spray of Mint", "#FF007711": "Spreadsheet Green", "#FFD6C1C5": "Sprig Muslin", "#FF8BE0BA": "Sprig of Mint", "#FFF2F6DB": "Sprig of Sage", "#FF00F900": "Spring", "#FFB3BB6B": "Spring Ahead", "#FFE9EDBD": "Spring Blossom", "#FF69CD7F": "Spring Bouquet", "#FFD7B9CB": "Spring Boutique", "#FFA98C37": "Spring Branch", "#FFE8EBD9": "Spring Breeze", "#FFC9E0C8": "Spring Burst", "#FFFFF6C2": "Spring Buttercup", "#FFB7629B": "Spring Crocus", "#FFDBD7B7": "Spring Day", "#FFD9EEE1": "Spring Dew", "#FFE5E3BF": "Spring Fever", "#FFB3CDAC": "Spring Fields", "#FFECF1EC": "Spring Fog", "#FF67926F": "Spring Forest", "#FF11BB22": "Spring Forth", "#FFBABD29": "Spring Forward", "#FF558961": "Spring Garden", "#FFD3E0B8": "Spring Glow", "#FFD5CB7F": "Spring Grass", "#FF00FF7C": "Spring Green Variant", "#FFC5C6B3": "Spring Grey", "#FFBFD392": "Spring Has Sprung", "#FFFFFDDD": "Spring Heat", "#FFC4CBB2": "Spring Hill", "#FFCDD9BB": "Spring Joy", "#FF4A754A": "Spring Juniper", "#FFE3EFB2": "Spring Kiss", "#FFCBDD94": "Spring Lawn", "#FFA8C3AA": "Spring Leaves Variant", "#FFFCF4C8": "Spring Lily", "#FF9FC65E": "Spring Lime", "#FFE8A59D": "Spring Linen", "#FF640125": "Spring Lobster", "#FF6C2C2F": "Spring Lobster Brown", "#FF7A4171": "Spring Lobster Dye", "#FFC0B05D": "Spring Marsh", "#FFD3E0DE": "Spring Mist", "#FFE5F0D5": "Spring Morn", "#FFA99757": "Spring Moss", "#FF596C3C": "Spring Onion", "#FFF6E6E2": "Spring Petal", "#FFDFBCC9": "Spring Pink", "#FFA3BD9C": "Spring Rain Variant", "#FFA1BFAB": "Spring Reflection", "#FFC4A661": "Spring Roll", "#FFA06567": "Spring Rosewood", "#FFCCEECC": "Spring Savour", "#FFE2EDC1": "Spring Shoot", "#FFABDCEE": "Spring Shower", "#FFB8F8B8": "Spring Slumber Green", "#FFFACCBF": "Spring Song", "#FF9BA294": "Spring Spirits", "#FFA2C09B": "Spring Sprig", "#FF86BA4A": "Spring Sprout", "#FFA9C6CB": "Spring Storm", "#FF98BEB2": "Spring Stream", "#FFF1F1C6": "Spring Sun Variant", "#FFD9DCDD": "Spring Thaw", "#FFD8DCB3": "Spring Thyme", "#FF99CDE6": "Spring Tide", "#FFCED7C5": "Spring Valley", "#FF82A320": "Spring Vegetables", "#FFACB193": "Spring Walk", "#FF7AB5AE": "Spring Water Turquoise", "#FFE0EED4": "Spring Wheat", "#FFECFCEC": "Spring White", "#FFCDA4DE": "Spring Wisteria", "#FFE9E1D9": "Spring Wood Variant", "#FFF2E47D": "Spring Yellow", "#FF284C3C": "Springbok Green", "#FFC8CB8E": "Springtide Green", "#FF9AA955": "Springtide Melodies", "#FFE9E5B3": "Springtime", "#FFDB88AC": "Springtime Bloom", "#FFFFFFEF": "Springtime Dew", "#FFEBEEF3": "Springtime Rain", "#FF7EA15A": "Springview Green", "#FFEBDDEA": "Sprinkle", "#FFCFECEE": "Sprinkled With Dew", "#FFE7A2AE": "Sprinkled With Pink", "#FFB9DCC3": "Sprite Twist", "#FF76C5E7": "Spritzig", "#FFB8CA9D": "Sprout Variant", "#FFC8D5CF": "Sprout Green", "#FFF3D48B": "Sprouted", "#FF0A5F38": "Spruce", "#FF91A49D": "Spruce Shade", "#FF838A7A": "Spruce Shadow", "#FF9FC09C": "Spruce Stone", "#FFB35E97": "Spruce Tree Flower", "#FF6E6A51": "Spruce Woods", "#FFB77D42": "Spruce Yellow", "#FF9A7F28": "Spruced Up", "#FFBCCFA4": "Spumoni Green", "#FFF3ECDC": "Spun Cotton", "#FFEDAA6E": "Spun Gold", "#FFF4E4CF": "Spun Jute", "#FFDBD1DE": "Spun Moonbeams", "#FFA2A1AC": "Spun Pearl Variant", "#FFFAE2ED": "Spun Sugar", "#FFE3DED4": "Spun Wool", "#FF5E0092": "SQL Injection Purple", "#FF828383": "Squant", "#FFF2AB15": "Squash", "#FFE7B17C": "Squash Bisque", "#FFF8B438": "Squash Blossom", "#FF6CC4DA": "Squeaky", "#FF11EE00": "Squeeze Toy Alien", "#FF6E2233": "Squid Hat", "#FFF5B4BD": "Squid Pink", "#FF041330": "Squid\u2019s Ink", "#FFAA4F44": "Squig Orange", "#FF8F7D6B": "Squirrel Variant", "#FF665E48": "Squirrel\u2019s Nest", "#FF95BCC5": "Squirt", "#FFF56961": "Sriracha", "#FFADDAE3": "Srsly Blue", "#FFD0DDCC": "St. Augustine", "#FF577C88": "St. Bart\u2019s", "#FFEEDDDD": "St. Nicholas Beard", "#FF2B8C4E": "St. Patrick", "#FFDEE8F3": "St. Petersburg", "#FF39527D": "St. Tropaz", "#FF325482": "St. Tropez", "#FFC1D0B2": "Stability", "#FFF6E0BE": "Stable Hay", "#FF858885": "Stack Variant", "#FFD1B992": "Stacked Limestone", "#FF918676": "Stacked Stone", "#FFD5F534": "Stadium Grass", "#FF9AF764": "Stadium Lawn", "#FF603B41": "Stag Beetle", "#FF9E6928": "Stage Gold", "#FFB081AA": "Stage Mauve", "#FFD8D9CA": "Stage Whisper", "#FF7F5A44": "Stagecoach", "#FF556682": "Stained Glass", "#FFB4BDC7": "Stainless Steel", "#FF67716E": "Stairway", "#FFD4C4A7": "Stalactite Brown", "#FF7CB26E": "Stalk", "#FFB0A8AD": "Stamina", "#FF638636": "Stamnank\u00e1thi Green", "#FF2EA18C": "Stamp Pad Green", "#FFA0A09A": "Stamped Concrete", "#FF725D4B": "Stampede", "#FF7F8596": "Stand Out", "#FFFF0066": "Standby LED", "#FFBFB9BD": "Standing Ovation", "#FF9D6F4B": "Standing Still", "#FF005599": "Standing Waters", "#FF85979A": "Standish Blue", "#FF658F7C": "Stanford Green", "#FFBCAB9C": "Stanford Stone", "#FF9BC2B4": "Stanley", "#FFFFE500": "Star", "#FFC51F26": "Star and Crescent Red", "#FF5C5042": "Star Anise", "#FFA77B4D": "Star Anise Scent", "#FFE8DDAE": "Star Bright", "#FF5796A1": "Star City", "#FFF9F3DD": "Star Dust Variant", "#FFBEAA4A": "Star Fruit Yellow Green", "#FF75DBC1": "Star Grass", "#FFD8DCE6": "Star Jasmine", "#FFE4D8D8": "Star Magic", "#FFEEEFE2": "Star Magnolia", "#FFDAE2E9": "Star Map", "#FFB3C6CE": "Star Mist", "#FFF5EEDC": "Star of Bethlehem", "#FF0000F7": "Star of David", "#FFEBE3C7": "Star of Gold", "#FF057BC1": "Star of Life", "#FFEBBBBE": "Star of the Morning", "#FF9500FF": "Star Platinum Purple", "#FF3C6A9D": "Star Sapphire", "#FFF8F6E3": "Star Shine", "#FF3A5779": "Star Spangled", "#FFEFEFE8": "Star White", "#FFF7EBAC": "Star-Studded", "#FF016C4F": "Starboard", "#FFF5ECC9": "Starbright", "#FF6CB037": "Starbur", "#FFDCE7E5": "Starburst", "#FFAC9A6C": "Starch", "#FFA6B2B5": "Stardew", "#FFDDD3AE": "Stardust", "#FFDACFD4": "Stardust Ballroom", "#FFB8BFDC": "Stardust Evening", "#FFE5BCA5": "Starfish", "#FF0096FF": "Starfleet Blue", "#FF4E9AB0": "Starflower Blue", "#FFF0E8D5": "Starfox", "#FFE4D183": "Starfruit", "#FFB7C4D3": "Stargate", "#FF7777FF": "Stargate Shimmer", "#FF3F5865": "Stargazer", "#FF414549": "Stargazing", "#FFFAEEDE": "Starglider", "#FFD2C6B6": "Stark White Variant", "#FF3E4855": "Starless Night", "#FFEDC2DB": "Starlet Pink", "#FFBCC0CC": "Starlight", "#FFB5CED4": "Starlight Blue", "#FFE8DFD8": "Starling\u2019s Egg", "#FF384351": "Starlit Eve", "#FF3B476B": "Starlit Night", "#FF286492": "Starry Night", "#FF4F5E7E": "Starry Sky Blue", "#FF758BA4": "Starset", "#FFE3DD39": "Starship Variant", "#FFCCE7E8": "Starship Tonic", "#FF229966": "Starship Trooper", "#FF4664A5": "Starstruck", "#FFE56131": "Startling Orange", "#FFC5BDC4": "Stately Frills", "#FFB0B0AA": "Stately Greystone", "#FF577A6C": "Stately Stems", "#FFFAF9EA": "Stately White", "#FFD5D3C3": "Static", "#FF9EA4A5": "Statuary", "#FF376D64": "Statue of Liberty", "#FFD0BCB1": "Statued", "#FFE0DFD9": "Statuesque", "#FFDC8A30": "Status Bronze", "#FF9FAC5C": "Stay in Lime", "#FF314662": "Stay the Night", "#FF4A5777": "Steadfast", "#FF8A6B4D": "Steady Brown", "#FF4B4844": "Stealth Jet", "#FFDDDDDD": "Steam", "#FFCCD0DA": "Steam Bath", "#FFEBE1A9": "Steam Chestnut", "#FFB2B2AD": "Steam Engine", "#FFE8E9E5": "Steam White", "#FFD2CCB4": "Steamboat Geyser", "#FFE0D4BD": "Steamed Chai", "#FFD3B17D": "Steamed Chestnut", "#FFEAD8BE": "Steamed Milk", "#FFEE8888": "Steamed Salmon", "#FFC39C55": "Steampunk Gold", "#FFB8C0C8": "Steampunk Grey", "#FF6F3B34": "Steampunk Leather", "#FFEAE9B4": "Steamy Dumpling", "#FFB1CFC7": "Steamy Spring", "#FF797979": "Steel", "#FF767275": "Steel Armour", "#FF7D94C6": "Steel Blue Eyes", "#FF436175": "Steel Blue Grey", "#FF43464B": "Steel Grey", "#FF7A744D": "Steel Legion Drab", "#FF5599B6": "Steel Light Blue", "#FFDDD5CE": "Steel Me", "#FFC6CEDA": "Steel Mist", "#FF71A6A1": "Steel Pan Mallet", "#FF5F8A8B": "Steel Teal", "#FF929894": "Steel Toe", "#FF767574": "Steel Wool", "#FF81919D": "Steele Blue", "#FFD43728": "Steelhead Redd", "#FF90979B": "Steely Grey", "#FF827E7C": "Steeple Grey", "#FF074863": "Stegadon Scale Green", "#FF415862": "Steiglitz Fog", "#FFF5D056": "Stella", "#FF46647E": "Stellar", "#FF9FB5CE": "Stellar Blue", "#FF002222": "Stellar Explorer", "#FFAB9D9C": "Stellar Mist", "#FFFF5C8D": "Stellar Strawberry", "#FF9A9959": "Stem", "#FFABDF8F": "Stem Green", "#FFB4CEDA": "Stencil Blue", "#FF7D7640": "Steppe Green", "#FFB2A18C": "Stepping Stones", "#FFFFF5CF": "Stereotypical Duck", "#FFD1D4D1": "Sterling", "#FFA2B9C2": "Sterling Blue", "#FFE9EBDE": "Sterling Shadow", "#FF9EAFC2": "Sterling Silver", "#FF9E7A58": "Stetson", "#FFC5B5A4": "Steveareno Beige", "#FFBAA482": "Sticks & Stones", "#FF131212": "Sticky Black Tarmac", "#FFCC8149": "Sticky Toffee", "#FF8D8F8E": "Stieglitz Silver", "#FFFADB5E": "Stil de Grain Yellow", "#FF323235": "Stiletto Variant", "#FFB6453E": "Stiletto Love", "#FFADAF9C": "Still", "#FFC154C0": "Still Fuchsia", "#FFABA9A0": "Still Grey", "#FFA1A39F": "Still Life in Grey", "#FFCBC4B2": "Still Moment", "#FFFFF8E1": "Still Morning", "#FF4A5D5F": "Still Waters Run Deep", "#FFD6EBE7": "Stillness", "#FF70A4B0": "Stillwater", "#FFC2D0DF": "Stillwater Lake", "#FFA29A6A": "Stilted Stalks", "#FF495D39": "Stinging Nettle", "#FFAEFD6C": "Stinging Wasabi", "#FFB0ABA3": "Stingray Grey", "#FF2A545C": "Stinkhorn", "#FFAE5A2C": "Stirland Battlemire", "#FF492B00": "Stirland Mud", "#FFF6B064": "Stirring Orange", "#FF900910": "Stizza", "#FF806852": "Stock Horse", "#FF104F4A": "Stockade Green", "#FFE9E5D8": "Stocking White", "#FF647B72": "Stockleaf", "#FF77454C": "Stoic", "#FFE0E0FF": "Stoic White", "#FFEFDCD3": "Stolen Kiss", "#FF0088B0": "Stomy Shower", "#FFADA587": "Stone", "#FFA29F9B": "Stone Age", "#FF7A3F2B": "Stone Age Queen", "#FF52706C": "Stone Bridge", "#FFB79983": "Stone Brown", "#FF888C90": "Stone Cairn", "#FF7D867C": "Stone Craft", "#FF8F9183": "Stone Creek", "#FF5F7D6C": "Stone Cypress Green", "#FF929C9C": "Stone Fence", "#FFC5C0B0": "Stone Fortress", "#FFF2A28C": "Stone Fruit", "#FFC2CBD2": "Stone Golem", "#FF658E67": "Stone Green", "#FF9F9484": "Stone Grey", "#FFD39831": "Stone Ground", "#FFCABA97": "Stone Guardians", "#FFE8E0D8": "Stone Harbour", "#FF93888C": "Stone Haze", "#FF636869": "Stone Hearth", "#FFB3A491": "Stone Lion", "#FFA29482": "Stone Manor", "#FF959897": "Stone Mason", "#FFB6B7AD": "Stone Mill", "#FF7A807A": "Stone Monument", "#FFE4EFE5": "Stone Path", "#FFEFE5D4": "Stone Pillar", "#FF665C46": "Stone Pine", "#FFECE4DC": "Stone Quarry", "#FF8BA8AE": "Stone Silver", "#FFA09484": "Stone Terrace", "#FF4D404F": "Stone Violet", "#FFB5B09E": "Stone Walkway", "#FF605C58": "Stone\u2019s Throw", "#FFDDCEA7": "Stonebread", "#FFCBA97E": "Stonebriar", "#FFA08F6F": "Stonecrop", "#FF99917E": "Stonegate", "#FFA79D8D": "Stonehenge Greige", "#FFBAB1A3": "Stonelake", "#FF8D7A4D": "Stonetalon Mountains", "#FF807661": "Stonewall Variant", "#FFC1C1C1": "Stonewall Grey", "#FFA5978D": "Stoneware", "#FF74819A": "Stonewash", "#FFDDD7C5": "Stonewashed", "#FFDCCCC0": "Stonewashed Brown", "#FFF4EEE4": "Stonewashed Pink", "#FFCCB49A": "Stonish Beige", "#FF948F82": "Stony Creek", "#FF615547": "Stony Field", "#FFD3D3C8": "Stony Path", "#FFC33A36": "Stop", "#FFDD1111": "Stoplight", "#FFE5E1DD": "Storksbill", "#FFF2F2E2": "Storksbill White", "#FF000B44": "Storm", "#FF507B9C": "Storm Blue", "#FF938988": "Storm Break", "#FF808283": "Storm Cloud", "#FF807A7E": "Storm Front", "#FF113333": "Storm Green", "#FF3D3D63": "Storm Is Coming", "#FFF9E69C": "Storm Lightning", "#FF7F95A5": "Storm Petrel", "#FFA28A88": "Storm Red", "#FF999495": "Storm Rolling In", "#FF696863": "Storm Warning", "#FFE7B57F": "Stormeye", "#FF80A7C1": "Stormfang", "#FFBBC6C9": "Stormhost Silver", "#FF8D9390": "Storms Mountain", "#FF5C5954": "Stormvermin Fur", "#FFB0BCC3": "Stormy", "#FF9AAFAF": "Stormy Bay", "#FFADB5A3": "Stormy Day", "#FF7D7B7C": "Stormy Grey", "#FF777799": "Stormy Horizon", "#FF71738C": "Stormy Mauve", "#FF372354": "Stormy Night", "#FF70818E": "Stormy Oceans", "#FFC36666": "Stormy Passion", "#FFE3B5AD": "Stormy Pink", "#FF946658": "Stormy Plum", "#FF507B9A": "Stormy Ridge", "#FF6E8082": "Stormy Sea", "#FF90A1AA": "Stormy Skies", "#FF0F9B8E": "Stormy Strait Green", "#FF6B8BA4": "Stormy Strait Grey", "#FF84A9B0": "Stormy Waters", "#FF63707B": "Stormy Weather", "#FFC05F1C": "Storybook Sundown", "#FF0F0B0A": "Stout", "#FF7B8393": "Stowaway", "#FF52A550": "Straightforward Green", "#FF628026": "Straken Green", "#FFDBB060": "Stranglethorn Ochre", "#FF528A9A": "Stratford Blue", "#FF8C8670": "Stratford Sage", "#FF3799C8": "Stratos Blue", "#FF9EC1CC": "Stratosphere", "#FFACB8B2": "Stratton Blue", "#FF8193AA": "Stratus", "#FF996E74": "Stravinsky", "#FF77515A": "Stravinsky Pink", "#FFD9C69A": "Straw Basket", "#FFFCF679": "Straw Gold", "#FFDBC8A2": "Straw Harvest", "#FFF0D5A8": "Straw Hat", "#FFBDB268": "Straw Hut", "#FFF0D696": "Straw Yellow", "#FFF1E3C7": "Straw-Bale", "#FFFB2943": "Strawberry Variant", "#FFEF4F41": "Strawberry Avalanche", "#FFFFDADC": "Strawberry Blonde Variant", "#FFFFEBFA": "Strawberry Bonbon", "#FFF8B3FF": "Strawberry Buttercream", "#FFFFDBE9": "Strawberry Cheesecake", "#FFF4BFC6": "Strawberry Confection", "#FF990011": "Strawberry Cough", "#FFF0ADB3": "Strawberry Cream", "#FFA23D50": "Strawberry Daiquiri", "#FFFF88AA": "Strawberry Dreams", "#FFFFF0EA": "Strawberry Dust", "#FFFA8383": "Strawberry Field", "#FFFFA2AA": "Strawberry Frapp\u00e9", "#FFC677A8": "Strawberry Freeze", "#FFFFDBF7": "Strawberry Frost", "#FFFF6FFC": "Strawberry Frosting", "#FFDAB7BE": "Strawberry Glaze", "#FFE78B90": "Strawberry Ice", "#FF86423E": "Strawberry Jam", "#FFC08591": "Strawberry Jubilee", "#FFD2ADB5": "Strawberry Latte", "#FFE0C1A7": "Strawberry Malt", "#FFE2958D": "Strawberry Memory", "#FFFFD9E7": "Strawberry Milk", "#FFD47186": "Strawberry Milkshake", "#FFE42D65": "Strawberry Mix", "#FFCF5570": "Strawberry Moon", "#FFA5647E": "Strawberry Mousse", "#FFF46C80": "Strawberry Pink", "#FFEE2255": "Strawberry Pop", "#FFB96364": "Strawberry Rhubarb", "#FFF7CDCE": "Strawberry Ripple", "#FFEEC6BF": "Strawberry Risotto", "#FFE29991": "Strawberry Rose", "#FFFA8E99": "Strawberry Shortcake", "#FFEE0055": "Strawberry Smash", "#FFE79EA6": "Strawberry Smoothie", "#FFF7879A": "Strawberry Soap", "#FFFA4224": "Strawberry Spinach Red", "#FFB9758D": "Strawberry Surprise", "#FFF9D7CD": "Strawberry Whip", "#FFCB6A6B": "Strawberry Wine", "#FFE9B3B4": "Strawberry Yoghurt", "#FFDDBDBA": "Strawflower", "#FF495E7B": "Stream", "#FF556061": "Stream Bed", "#FF5E7E7D": "Stream Burble", "#FFB9BAC0": "Streamlined Grey", "#FFD8E2DF": "Streetwise", "#FF2D2E33": "Stretch Limo", "#FF9DBBD0": "Stretch of Water", "#FFDFD8C8": "Stretched Canvas", "#FFD7AA60": "Streusel Cake", "#FF5A4659": "Strike a Pose", "#FFD7B55F": "Strike It Rich", "#FF946A81": "Strikemaster Variant", "#FF00667B": "Striking", "#FFCE7843": "Striking Orange", "#FF944E87": "Striking Purple", "#FFC03543": "Striking Red", "#FFAA9F96": "String", "#FFF1E8D8": "String Ball", "#FFFBF1DD": "String Cheese", "#FF7F7860": "String Deep", "#FFEBE3D8": "String of Pearls", "#FF406356": "Stromboli Variant", "#FF0C06F7": "Strong Blue", "#FF960056": "Strong Cerise", "#FF782E2C": "Strong Envy", "#FF5E5F7E": "Strong Iris", "#FF6F372D": "Strong Mocha", "#FFA88905": "Strong Mustard", "#FF646756": "Strong Olive", "#FFFF0789": "Strong Pink", "#FF2B6460": "Strong Sage", "#FF8A3E34": "Strong Strawberry", "#FF454129": "Strong Tone Wash", "#FF22578A": "Strong Will", "#FFA3A59B": "Strong Winds", "#FFA86F48": "Stroopwafel", "#FFF0E1E8": "Struck by Lightning", "#FF0E9BD1": "Structural Blue", "#FFA58D7F": "Stucco", "#FFF1B19D": "Stucco Wall", "#FFE2D3B9": "Stucco White", "#FF005577": "Studer Blue", "#FF724AA1": "Studio Variant", "#FFC1B2A1": "Studio Beige", "#FF6D817B": "Studio Blue Green", "#FFD9CCB8": "Studio Clay", "#FFEBDBAA": "Studio Cream", "#FFC6B9B8": "Studio Mauve", "#FFA59789": "Studio Taupe", "#FFE8DCD5": "Studio White", "#FFADAC7C": "Stuffed Olive", "#FFBF9B84": "Stuffing", "#FF5E5F4D": "Stump Green", "#FFDA9A5D": "Stunning Gold", "#FF185887": "Stunning Sapphire", "#FF676064": "Stunning Shade", "#FFBC9479": "Sturdy Bronze", "#FF9B856F": "Sturdy Brown", "#FF57544D": "Sturgis Grey", "#FFCEC1A5": "Stylish", "#FFBC3439": "Stylish Red", "#FF9FA0A0": "Su-Nezumi Grey", "#FFD1D8DD": "Suave Grey", "#FF57A1CE": "Sub-Zero", "#FF00576F": "Subaqueous", "#FFCCB8B3": "Subdue Red", "#FFC6B1AD": "Subdued Hue", "#FFCC896C": "Subdued Sienna", "#FFECEDE0": "Sublime", "#FF7A7778": "Submarine Variant", "#FF5566AA": "Submarine Base", "#FF4D585C": "Submarine Grey", "#FF4A7D82": "Submerged", "#FF00576E": "Submersible", "#FF012253": "Subnautical", "#FFD8CCC6": "Subpoena", "#FF4F4E4A": "Subterrain Kingdom", "#FF452C1F": "Subterranean", "#FF1F3B4D": "Subterranean River", "#FFDEDADB": "Subtle", "#FFD9E4E5": "Subtle Blue", "#FFF1DBC5": "Subtle Blush", "#FFB5D2D8": "Subtle Breeze", "#FFEBD7A7": "Subtle Glow", "#FFB5CBBB": "Subtle Green", "#FF554B4F": "Subtle Night Sky", "#FFFAE0C4": "Subtle Peach", "#FFD8D8D0": "Subtle Shadow", "#FFD0BD94": "Subtle Suede", "#FFE4D89A": "Subtle Sunshine", "#FFDBDBD9": "Subtle Touch", "#FF7A9693": "Subtle Turquoise", "#FFB29E9E": "Subtle Violet", "#FF87857C": "Subway", "#FF513B6E": "Succinct Violet", "#FF990022": "Succubus", "#FFBCCBB2": "Succulent Garden", "#FF5E9B86": "Succulent Green", "#FF8BA477": "Succulent Greenhouse", "#FF658E64": "Succulent Leaves", "#FFDCDD65": "Succulent Lime", "#FF007744": "Succulents", "#FFFBDDAF": "Such a Peach", "#FFC6C1C5": "Such Melodrama", "#FFBC752D": "Sudan Brown", "#FF6376A9": "Sudden Sapphire", "#FF1A5897": "Suddenly Sapphire", "#FFA6B4C5": "Suds", "#FFBA8864": "Suede", "#FFD9C7B9": "Suede Beige", "#FF857F7A": "Suede Grey", "#FF585D6D": "Suede Indigo", "#FF896757": "Suede Leather", "#FFD79043": "Suede Vest", "#FFD3DBE7": "Sueded Grey", "#FF235E80": "Suez Canal", "#FFECD0A1": "Suffragette Yellow", "#FF935529": "Sugar Almond", "#FF834253": "Sugar Beet", "#FFE3D4CD": "Sugar Berry", "#FFFFCCFF": "Sugar Chic", "#FFFFEDF1": "Sugar Coated", "#FFBB6611": "Sugar Coated Almond", "#FFF2E2A4": "Sugar Cookie", "#FFE8CFB1": "Sugar Cookie Crust", "#FFF56C73": "Sugar Coral", "#FFC96FA8": "Sugar Creek", "#FFF9EDE3": "Sugar Dust", "#FFFFF0E1": "Sugar Glaze", "#FFCC9955": "Sugar Glazed Cashew", "#FF9437FF": "Sugar Grape", "#FFEFC9EC": "Sugar High", "#FFDDAA66": "Sugar Honey Cashew", "#FFCDB141": "Sugar Leaves", "#FFFFF9F5": "Sugar Milk", "#FFC0E2C5": "Sugar Mint", "#FFC7A77B": "Sugar Pie", "#FF73776E": "Sugar Pine", "#FFAED6D4": "Sugar Pool", "#FFE58281": "Sugar Poppy", "#FFEBE5D7": "Sugar Quill", "#FFD85DA1": "Sugar Rush", "#FFCFB599": "Sugar Rush Peach Pepper", "#FFEED5B6": "Sugar Shack", "#FFEFE8DC": "Sugar Soap", "#FFECC4DC": "Sugar Sweet", "#FFEAE3D6": "Sugar Swizzle", "#FFD68F9F": "Sugar Tooth", "#FFA2999A": "Sugar Tree", "#FF8B2E16": "Sugar-Candied Peanuts", "#FFEDD1C7": "Sugarcane", "#FFF7C2BF": "Sugarcane Dahlia", "#FFB49D7B": "Sugared Almond", "#FFFDDCC6": "Sugared Peach", "#FFEBD5B7": "Sugared Pears", "#FF554400": "Sugarloaf Brown", "#FFFFDDFF": "Sugarpills", "#FFD1ABB5": "Sugarplum Dance", "#FFFDC5E3": "Sugarwinkle", "#FFA2999F": "Sugilite", "#FFA32E1D": "Sugo Della Nonna", "#FF2B3036": "Suit Blue", "#FF645A4B": "Suitable Brown", "#FFA58B34": "Sullen Gold", "#FFF7C5D1": "Sullivan\u2019s Heart", "#FFBAA600": "Sulphine Yellow", "#FFCAB012": "Sulphur", "#FFE5CC69": "Sulphur Pit", "#FFD5D717": "Sulphur Spring", "#FFF2F3CF": "Sulphur Water", "#FFCCC050": "Sulphur Yellow", "#FFEEED56": "Sulphuric", "#FFF6AC17": "Sultan Gold", "#FFE89BC7": "Sultan of Pink", "#FFE3C9BE": "Sultan Sand", "#FF134558": "Sultan\u2019s Silk", "#FF674668": "Sultana", "#FF567D84": "Sultry Bay", "#FF948D84": "Sultry Castle", "#FF506770": "Sultry Sea", "#FF73696F": "Sultry Smoke", "#FF716563": "Sultry Spell", "#FFC6EA80": "Sulu Variant", "#FFE08A1E": "Sumac Dyed", "#FFF6E8CC": "Sumatra", "#FF735D4B": "Sumatra Blend", "#FF4F666A": "Sumatra Chicken", "#FF595857": "Sumi Ink", "#FF7058A3": "Sumire Violet", "#FF3FAFCF": "Summer Air", "#FFDBC2B9": "Summer Beige", "#FFBBD5EF": "Summer Birthday", "#FFFCF1CF": "Summer Bliss", "#FFD1BEB4": "Summer Bloom", "#FF1880A1": "Summer Blue", "#FFF6DFD6": "Summer Blush", "#FFD3E5DB": "Summer Breeze", "#FFF8822A": "Summer Citrus", "#FFBBFFEE": "Summer Cloud", "#FFE5CFDE": "Summer Clover", "#FFECB6B5": "Summer Cocktails", "#FF57595D": "Summer Concrete", "#FFFAD1E0": "Summer Cosmos", "#FFF2D6DA": "Summer Crush", "#FFFFE078": "Summer Daffodil", "#FFEAAA62": "Summer Day", "#FF83ADA3": "Summer Dragonfly", "#FFE2C278": "Summer Field", "#FFC45940": "Summer Fig", "#FF7AAC80": "Summer Garden", "#FFEEAA44": "Summer Glow", "#FF8FB69C": "Summer Green Variant", "#FFE8E4DE": "Summer Grey", "#FFFFE69A": "Summer Harvest", "#FFC1A58D": "Summer Hill", "#FFC8EFE2": "Summer House", "#FFFFEFC2": "Summer Hue", "#FFCDA168": "Summer in the City", "#FFEEEBD6": "Summer Jasmine", "#FF0077A7": "Summer Lake", "#FFF8D374": "Summer Lily", "#FFEAD5AE": "Summer Melon", "#FFDF856E": "Summer Memory", "#FFCBEAEE": "Summer Mist", "#FFFDEDCF": "Summer Moon", "#FFFBEEC1": "Summer Morning", "#FFB8AF65": "Summer Moss", "#FF36576A": "Summer Night", "#FF74CDD8": "Summer of \u201982", "#FFFFB653": "Summer Orange", "#FFF5F0D1": "Summer Pear", "#FFE1E8DB": "Summer Rain", "#FFF7EFBA": "Summer Resort", "#FFECE4CE": "Summer Sandcastle", "#FF66A9B1": "Summer Sea", "#FFD1D9D7": "Summer Shade", "#FFE5EBE3": "Summer Shower", "#FFB7E0B9": "Summer Sigh", "#FF38B0DE": "Summer Sky", "#FF94D3D1": "Summer Soft Blue", "#FFDED1A3": "Summer Solstice", "#FF816E63": "Summer Sparrow", "#FF80CFE5": "Summer Splash", "#FFB0C5DF": "Summer Storm", "#FFFFDC00": "Summer Sun", "#FFD88167": "Summer Sunset", "#FFF7E8C7": "Summer Sunshine", "#FF008572": "Summer Turquoise", "#FF4B9CAB": "Summer Turquoise Blue", "#FFAFA685": "Summer Valley", "#FF215399": "Summer Waters", "#FFBB8E55": "Summer Weasel", "#FFF4E9D6": "Summer White", "#FFDC9367": "Summer\u2019s End", "#FFA97069": "Summer\u2019s Eve", "#FFF9E699": "Summer\u2019s Heat", "#FF376698": "Summerday Blue", "#FF84AAB8": "Summerhouse Blue", "#FFC47A3D": "Summerset", "#FFF2D178": "Summertime", "#FF8CBC9E": "Summertown", "#FF997651": "Summerville Brown", "#FFD4B28B": "Summerwood", "#FF8BB6B8": "Summit", "#FFE5B99B": "Sumptuous Peach", "#FF604C81": "Sumptuous Purple", "#FFB1B48C": "Sumptuous Sage", "#FFEF8E38": "Sun Variant", "#FFCD6E53": "Sun Baked", "#FFA36658": "Sun Baked Earth", "#FFE3EFE1": "Sun Bleached Mint", "#FFE3AB7B": "Sun Bleached Ochre", "#FFFFFED9": "Sun City", "#FFC4AA4D": "Sun Dance", "#FFF0DCA0": "Sun Deck", "#FFFFE7A3": "Sun Drenched", "#FFEAAF11": "Sun Drops", "#FFF6E0A4": "Sun Dust", "#FFD0D418": "Sun Flooded Woods", "#FFF1F4D1": "Sun Glare", "#FFFAF3D9": "Sun Glint", "#FFDFBA5A": "Sun God", "#FFEACDB7": "Sun Kiss", "#FFF37724": "Sun Orange", "#FFCCC2C6": "Sun Painter", "#FFE7C26F": "Sun Salutation", "#FFFFDE73": "Sun Shower", "#FFE9AD17": "Sun Song", "#FFFBD795": "Sun Splashed", "#FFFFF2A0": "Sun Surprise", "#FFFAD675": "Sun Touched", "#FF698538": "Sun Valley", "#FFB88B46": "Sun Valley Vista", "#FFECC033": "Sun Wukong\u2019s Crown", "#FFFFDF22": "Sun Yellow", "#FFFFEEC2": "Sun-Kissed", "#FFF2BDA8": "Sun-Kissed Apricot", "#FFDEAB9B": "Sun-Kissed Beach", "#FFB75E41": "Sun-Kissed Brick", "#FFEA6777": "Sun-Kissed Coral", "#FFFED8BF": "Sun-Kissed Peach", "#FFFFE9BA": "Sun-Kissed Yellow", "#FFF6F2E5": "Sun\u2019s Glory", "#FFA94E37": "Sun\u2019s Rage", "#FFDCD3B2": "Suna White", "#FFAB9A6E": "Sunbaked Adobe", "#FFD1AB74": "Sunbaked Straw", "#FFF5DD98": "Sunbathed", "#FFFAD28F": "Sunbathed Beach", "#FF7E4730": "Sunbathing Beauty", "#FFF5EDB2": "Sunbeam", "#FFF0D39D": "Sunbeam Yellow", "#FFFEFF0F": "Sunblast Yellow", "#FFE5E0D7": "Sunbleached", "#FFF9D964": "Sunbound", "#FFB37256": "Sunburn", "#FFFF404C": "Sunburnt Cyclops", "#FFD79584": "Sunburnt Toes", "#FFF5B57B": "Sunburst", "#FFF6C778": "Sunday Afternoon", "#FFFCC9C7": "Sunday Best", "#FFDCC9AE": "Sunday Drive", "#FFD7BAD1": "Sunday Gloves", "#FF3D4035": "Sunday Niqab", "#FFC5D2D5": "Sunday Sky", "#FFFAE198": "Sundaze", "#FFE1CDAE": "Sundew", "#FFC0805D": "Sundial", "#FFF5C99E": "Sundown Variant", "#FFEBCF89": "Sundress", "#FFEABD5B": "Sundried", "#FF7D252C": "Sundried Tomato", "#FF6E5F57": "Sunezumi Brown", "#FFFFC512": "Sunflower Variant", "#FFFFE26A": "Sunflower Dandelion", "#FFEA9D4F": "Sunflower Field", "#FFFFCD01": "Sunflower Island", "#FFFFB700": "Sunflower Mango", "#FFFFE3A9": "Sunflower Seed", "#FFFDBD27": "Sunflower Valley", "#FFFFDA03": "Sunflower Yellow", "#FFC76155": "Sunglo Variant", "#FFFFCF48": "Sunglow Gecko", "#FF51574F": "Sunken Battleship", "#FF273E3E": "Sunken Cascades", "#FFB29700": "Sunken Gold", "#FF1C3D44": "Sunken Harbour", "#FF23505A": "Sunken Mystery", "#FFC8DDDA": "Sunken Pool", "#FF10252A": "Sunken Ship", "#FFCCCF86": "Sunken Treasure", "#FFEBCD95": "Sunlight", "#FFFFDB78": "Sunlit", "#FF9787BB": "Sunlit Allium", "#FF98D4A0": "Sunlit Glade", "#FF7D7103": "Sunlit Kelp Green", "#FFD8D8A9": "Sunlit Leaf", "#FFD4C350": "Sunlit Meadow", "#FFBBCFB9": "Sunlit Sea", "#FFDA8433": "Sunlounge", "#FFE8D7B1": "Sunning Deck", "#FFF2F27A": "Sunny", "#FFEADFAA": "Sunny Burrata", "#FFDBA637": "Sunny Disposition", "#FFFFC946": "Sunny Festival", "#FFEDE1CC": "Sunny Gazebo", "#FFE8D99C": "Sunny Glory", "#FFC5CD40": "Sunny Green", "#FFF8F0D8": "Sunny Honey", "#FFD0875A": "Sunny Horizon", "#FFE1EE82": "Sunny Lime", "#FFF5F5CC": "Sunny Mimosa", "#FFF7C84A": "Sunny Mood", "#FFF6D365": "Sunny Morning", "#FFD9D7D9": "Sunny Pavement", "#FFFFDC41": "Sunny Side Up", "#FFFFC900": "Sunny Summer", "#FFE3E9CF": "Sunny Summit", "#FFFEDF94": "Sunny Veranda", "#FFFFF917": "Sunny Yellow", "#FFF8D016": "Sunnyside", "#FFFFD18C": "Sunporch", "#FFCFC5B6": "Sunray Venus", "#FFF4BF77": "Sunrise", "#FFFEF0C5": "Sunrise Glow", "#FFCAA061": "Sunrise Heat", "#FFA69799": "Sunrise Over Tahiti", "#FFFFDB67": "Sunrose Yellow", "#FFC0514A": "Sunset", "#FFD0A584": "Sunset Beige", "#FFE95E2A": "Sunset Blaze", "#FFFF9607": "Sunset Boulevard", "#FFBE916D": "Sunset Cloud", "#FFDCB397": "Sunset Cove", "#FFFFBE94": "Sunset Cruise", "#FFD1A69B": "Sunset Curtains", "#FFEABBA2": "Sunset Drive", "#FFFFB52D": "Sunset Glow", "#FFF6C362": "Sunset Gold", "#FFBA87AA": "Sunset Horizon", "#FFF0C484": "Sunset in Italy", "#FFA5A796": "Sunset Meadow", "#FFFD5E53": "Sunset Orange Variant", "#FFFEAC89": "Sunset Over the Alps", "#FFFC7D64": "Sunset Papaya", "#FFF8A77F": "Sunset Peach", "#FFFAD6E5": "Sunset Pink", "#FF724770": "Sunset Purple", "#FF7F5158": "Sunset Red", "#FF594265": "Sunset Serenade", "#FFFFBC00": "Sunset Strip", "#FFFA873D": "Sunset Yellow", "#FFFA9D49": "Sunshade Variant", "#FFF9D376": "Sunshine", "#FFF5C20B": "Sunshine Mellow", "#FFFCB02F": "Sunshine Surprise", "#FF886688": "Sunshone Plum", "#FFFBCA69": "Sunspark", "#FFDDC283": "Sunspill", "#FFFEE2B2": "Sunstitch", "#FFC7887F": "Sunstone", "#FFD8A27A": "Sunswept Sand", "#FFD9B19F": "Suntan", "#FFBE8C74": "Suntan Glow", "#FFE3C1B3": "Sunwashed Brick", "#FF7E2639": "Su\u014d", "#FFFFFE71": "Super Banana", "#FF221100": "Super Black", "#FFAA8822": "Super Gold", "#FFCA535B": "Super Hero", "#FFBA5E0F": "Super Leaf Brown", "#FFE2B238": "Super Lemon", "#FFCE6BA6": "Super Pink", "#FF14BAB4": "Super Rare Jade", "#FFCB1028": "Super Rose Red", "#FFFFDD00": "Super Saiyan", "#FFFFAA88": "Super Sepia", "#FF785F8E": "Super Violet", "#FFFFFF69": "Supercalifragilisticexpialidocious", "#FF3A5E73": "Superior Blue", "#FF786957": "Superior Bronze", "#FFFF1122": "Superman Red", "#FF00928C": "Supermint", "#FF313641": "Supernatural", "#FFEF760E": "Supernatural Saffron", "#FFD1D5E6": "Supernova Variant", "#FFD9ECE9": "Supernova Residues", "#FFFFCC11": "Superstar", "#FF5B6E74": "Superstition", "#FFAC91B5": "Superstitious", "#FFE8EAEA": "Superwhite", "#FF78A300": "Support Green", "#FFCFDDC7": "Supreme Green", "#FFD4D8DB": "Supreme Pontiff", "#FFAFBED4": "Supremely Cool", "#FFFC53FC": "Surati Pink", "#FFB8D4BB": "Surf Variant", "#FFC3D6BD": "Surf Crest Variant", "#FF427573": "Surf Green", "#FF0193C2": "Surf Rider", "#FFB4C8C2": "Surf Spray", "#FF20377F": "Surf the Web", "#FF87CECA": "Surf Wash", "#FF374755": "Surf\u2019n\u2019dive", "#FFC4D3E5": "Surf\u2019s Surprise", "#FFC6E4EB": "Surf\u2019s Up", "#FF70B8BA": "Surfer", "#FFDB6484": "Surfer Girl", "#FF007B77": "Surfie Green Variant", "#FF73C0D2": "Surfin\u2019", "#FF9ACAD3": "Surfside", "#FF59A4C1": "Surgical", "#FFC9936F": "Surprise", "#FFEFB57A": "Surprise Amber", "#FFDCC89D": "Surrey Cream", "#FF70191F": "Surya Red", "#FF7C9F2F": "Sushi Variant", "#FFFFF7DF": "Sushi Rice", "#FF58BAC2": "Sussie", "#FF887F7A": "Susu Green", "#FF6F514C": "Susu-Take Bamboo", "#FF859D95": "Sutherland", "#FFC2963F": "Suzani Gold", "#FF9EA1A3": "Suzu Grey", "#FFAA4F37": "Suzume Brown", "#FF8C4736": "Suzumecha Brown", "#FFB8A3BB": "Svelte", "#FFB2AC96": "Svelte Sage", "#FFB1B8B5": "Swag Bag Silver", "#FF19B6B5": "Swagger", "#FF154962": "Swallow Blue", "#FFECE9DD": "Swallow-Tailed Moth", "#FFFFE690": "Swallowtail", "#FF7F755F": "Swamp Variant", "#FFB79D69": "Swamp Fox", "#FF748500": "Swamp Green Variant", "#FF094C49": "Swamp Mausoleum", "#FF005511": "Swamp Monster", "#FF252F2F": "Swamp Mosquito", "#FF698339": "Swamp Moss", "#FF857947": "Swamp Mud", "#FF36310D": "Swamp of Sorrows", "#FF93977F": "Swamp Sessions", "#FF6D753B": "Swamp Shrub", "#FF226633": "Swampland", "#FFBBAB6D": "Swampwater", "#FFE5E1BD": "Swan Dance", "#FFE5E4DD": "Swan Dive", "#FFC5E5E2": "Swan Lake", "#FFA6C1BF": "Swan Sea", "#FFF5F2E6": "Swan Wing", "#FFB5B1B5": "Swanky Grey", "#FF5F7963": "Swanndri", "#FFDAE6DD": "Swans Down Variant", "#FF1D4E8F": "Sweat Bee", "#FFCCCCC5": "Sweater Weather", "#FF007CC0": "Swedish Blue", "#FF7B8867": "Swedish Clover", "#FF184D43": "Swedish Green", "#FFFCE081": "Swedish Yellow", "#FFC4BF0B": "Sweet & Sour", "#FFF29EAB": "Sweet 60", "#FFCC9977": "Sweet Almond", "#FFE7C2DE": "Sweet Alyssum", "#FFE1C9D1": "Sweet and Sassy", "#FFF5C8BB": "Sweet Angel", "#FFE8D08E": "Sweet Angelica", "#FF9C946E": "Sweet Annie", "#FFFCC0A6": "Sweet Apricot", "#FFA7E8D3": "Sweet Aqua", "#FFE5EAE3": "Sweet Ariel", "#FFFFE9AC": "Sweet as Honey", "#FFC24F40": "Sweet Baby Rose", "#FFC6947C": "Sweet Beige", "#FFEEDADD": "Sweet Bianca", "#FFAEBED2": "Sweet Blue", "#FFE6BFC1": "Sweet Blush", "#FFC8DAE3": "Sweet Breeze", "#FFFFFCD7": "Sweet Butter", "#FFFCEEDD": "Sweet Buttermilk", "#FFD59875": "Sweet Cardamom", "#FF9C9480": "Sweet Carolina", "#FFF6AFBB": "Sweet Caroline", "#FFCC764F": "Sweet Carrot", "#FFDDAA77": "Sweet Cashew", "#FFFFE186": "Sweet Chamomile", "#FF9F4F4D": "Sweet Cherry", "#FF84172C": "Sweet Cherry Red", "#FFF5160B": "Sweet Chilli", "#FFDD84A3": "Sweet Chrysanthemum", "#FFBFC0AA": "Sweet Clover", "#FFF9E176": "Sweet Corn Variant", "#FFF0EAE7": "Sweet Cream", "#FF5F4C54": "Sweet Currant", "#FFE8A773": "Sweet Curry", "#FFAA33EE": "Sweet Desire", "#FFDBCBAB": "Sweet Dough", "#FFECCEE5": "Sweet Dreams", "#FFAB9368": "Sweet Earth", "#FFCBD1E1": "Sweet Emily", "#FF8844FF": "Sweet Escape", "#FF674196": "Sweet Flag", "#FF8A9B76": "Sweet Florence", "#FFE2E2EA": "Sweet Flower", "#FFFFF8E4": "Sweet Frosting", "#FF5FD1BA": "Sweet Garden", "#FFEFE4DA": "Sweet Gardenia", "#FF8B715A": "Sweet Georgia Brown", "#FF4D3D52": "Sweet Grape", "#FFB2B68A": "Sweet Grass", "#FFD9DDE7": "Sweet Harbour", "#FFE0E8EC": "Sweet Illusion", "#FFF9F4D4": "Sweet Jasmine", "#FFB8BFD2": "Sweet Juliet", "#FF8E8DB9": "Sweet Lavender", "#FFF7BF43": "Sweet Lemon", "#FFFFF45A": "Sweet Lemon Seed", "#FFE8B5CE": "Sweet Lilac", "#FFCCBBDD": "Sweet Lucid Dreams", "#FF9B4040": "Sweet Lychee", "#FFFFDEE2": "Sweet Mallow", "#FFD35E3A": "Sweet Mandarin", "#FFDDAF6C": "Sweet Maple", "#FFEBAF95": "Sweet Marmalade", "#FFECD5AA": "Sweet Marzipan", "#FFE87973": "Sweet Melon", "#FFFDC8CD": "Sweet Memories", "#FFC2E4BC": "Sweet Menthol", "#FFA7C74F": "Sweet Midori", "#FFB4CCBE": "Sweet Mint", "#FFBBEE99": "Sweet Mint Pesto", "#FFD5E3D0": "Sweet Mint Tea", "#FF4B423F": "Sweet Molasses", "#FFB57312": "Sweet Mulled Cider", "#FFECC5DF": "Sweet Murmur", "#FFD1B871": "Sweet Mustard", "#FFFABDAF": "Sweet Nectar", "#FFFAE6E1": "Sweet Nothing", "#FFEBCCB3": "Sweet Orange", "#FFEDC8B1": "Sweet Pastel", "#FF9BA15C": "Sweet Pea", "#FFA89E81": "Sweet Pea in a Pod", "#FFE2BCB3": "Sweet Peach", "#FFD49AB9": "Sweet Perfume", "#FFCBBAD0": "Sweet Petal", "#FFFE6346": "Sweet Pimento", "#FFEE918D": "Sweet Pink Variant", "#FFD5CAAA": "Sweet Porridge", "#FFD87C3B": "Sweet Potato", "#FF917798": "Sweet Potato Peel", "#FF93DAD3": "Sweet Rhapsody", "#FFFFC4DD": "Sweet Romance", "#FFEAE1DD": "Sweet Roses", "#FFFFD8F0": "Sweet Sachet", "#FFF1DBC2": "Sweet Sand", "#FFC7B7D0": "Sweet Scent", "#FFFFC5D5": "Sweet Serenade", "#FFF0B9A9": "Sweet Sheba", "#FFEEBDB6": "Sweet Sherry", "#FFFFC9D3": "Sweet Sixteen", "#FFD0DEDD": "Sweet Slumber", "#FFF8B8F8": "Sweet Slumber Pink", "#FFA8946B": "Sweet Sparrow", "#FF7B453E": "Sweet Spiceberry", "#FFD1E8C2": "Sweet Spring", "#FFD8AA86": "Sweet Sue", "#FFC9D0B8": "Sweet Surrender", "#FFECBCD4": "Sweet Taffy", "#FFEAAEA9": "Sweet Tart", "#FFC18244": "Sweet Tea", "#FF5F6255": "Sweet Tooth", "#FFF0DCD7": "Sweet Truffle", "#FFEEEBE6": "Sweet Vanilla", "#FFB6FF1A": "Sweet Venom", "#FF8C667A": "Sweet Violet", "#FFFC5669": "Sweet Watermelon", "#FF8892C1": "Sweet William", "#FFF1CA90": "Sweet!", "#FFA7BDBA": "Sweetest Damask", "#FFF3C3D8": "Sweetheart", "#FFE1BBDB": "Sweetie Pie", "#FFFFE5EF": "Sweetly", "#FFF8DBC4": "Sweetness", "#FFC39F87": "Sweetwood", "#FFE7CEE3": "Sweety Pie", "#FF82AADC": "Swift", "#FFE1BC73": "Swiftly Green", "#FF6BB8B5": "Swim", "#FF0A91BF": "Swimmer", "#FF2DC5BB": "Swimmers Pool", "#FFC2E5E5": "Swimming", "#FFA8CFC0": "Swimming Pool Green", "#FF947569": "Swing Brown", "#FFC2C0A9": "Swing Sage", "#FF706842": "Swinging Vine", "#FFD7CEC5": "Swirl Variant", "#FFCECAC1": "Swirling Smoke", "#FFE6E9E9": "Swirling Water", "#FFE4DACC": "Swiss Almond", "#FF6E5F53": "Swiss Brown", "#FFDD5E6D": "Swiss Chard", "#FFFFF4B9": "Swiss Cheese", "#FF8C6150": "Swiss Chocolate", "#FFD5C3AD": "Swiss Coffee Variant", "#FFECEAD9": "Swiss Cream", "#FFC6C3B4": "Swiss Livery", "#FFE8D5DD": "Swiss Rose", "#FF67667C": "Swollen Sky", "#FFD6D2DE": "Sword Steel", "#FF8BCBAB": "Sybarite Green", "#FF6A8779": "Sycamore Grove", "#FF959E8F": "Sycamore Stand", "#FF9C8A79": "Sycamore Tan", "#FF3F544F": "Sycamore Tree", "#FFCBB394": "Sycorax Bronze", "#FF97BBC8": "Sydney Harbour", "#FFADAAB1": "Sylph", "#FF979381": "Sylvan", "#FFE7EACB": "Sylvan Green", "#FFAC8262": "Sylvaneth Bark", "#FFB29EAD": "Symbolic", "#FF8FA0A7": "Symmetry", "#FFC0A887": "Symphony Gold", "#FF89A0A6": "Symphony of Blue", "#FF331133": "Synallactida", "#FFC7D4CE": "Synchronicity", "#FFF8C300": "Syndicalist", "#FF918151": "Syndicate Camouflage", "#FF48C2B0": "Synergy", "#FF9FFEB0": "Synthetic Mint", "#FF1EF876": "Synthetic Spearmint", "#FF792E35": "Syrah", "#FFA16717": "Syrah Soil", "#FFDFCAE4": "Syrian Violet", "#FFB18867": "Syrup", "#FF3A2EFE": "System Shock Blue", "#FF8020FF": "Sz\u00f6ll\u0151si Grape", "#FF6FC1AF": "T-Bird Turquoise", "#FF8E908D": "T-Rex Fossil", "#FFC7C4A5": "Ta Prohm", "#FF709572": "Tabbouleh", "#FF526525": "Tabbouleh Green", "#FF9A948D": "Tabby Cat Grey", "#FFF1E9DC": "Table Linen", "#FFE5C279": "Table Pear Yellow", "#FF005553": "Tabriz Teal", "#FFF6AE78": "Tacao Variant", "#FFD2B960": "Tacha Variant", "#FFF3C7B3": "Taco", "#FFD3E7C7": "Tactile", "#FF7AD7AD": "Tadorna Teal", "#FF7D7771": "Tadpole", "#FFAF797E": "Taffeta Darling", "#FF81825F": "Taffeta Sheen", "#FFF3E0EB": "Taffeta Tint", "#FFC39B6A": "Taffy", "#FFFEA6C8": "Taffy Pink", "#FFAAD0BA": "Taffy Twist", "#FFF9F2D4": "Tagliatelle", "#FFD9BEAA": "Tagsale Linen", "#FFDDBB77": "Tahini", "#FF9B856B": "Tahini Brown", "#FFDC722A": "Tahiti Gold Variant", "#FFB8E9E4": "Tahitian Breeze", "#FF263644": "Tahitian Pearl", "#FFF5DCB4": "Tahitian Sand", "#FFC9E9E7": "Tahitian Sky", "#FF00677E": "Tahitian Tide", "#FF00686D": "Tahitian Treat", "#FF94B8C1": "Tahoe Blue", "#FFD7E1E5": "Tahoe Snow", "#FFD8CC9B": "Tahuna Sands Variant", "#FF768078": "Taiga", "#FFDD4411": "Tail Lights", "#FFDFC2AA": "Tailor\u2019s Buff", "#FFBD9D7E": "Tailored Tan", "#FFF5E8CF": "Tailwind", "#FFB54A4A": "Tainan Brick", "#FFEAD795": "Tainted Gold", "#FFBB5520": "Taisha Brown", "#FF9F5233": "Taisha Red", "#FF3377A2": "Taiwan Blue Magpie", "#FFC7AA71": "Taiwan Gold", "#FF734A33": "Taj", "#FFEDE9DF": "Taj Mahal", "#FF3D4D78": "Takaka", "#FFBACBC5": "Take a Hike", "#FFB3C9D3": "Take Five", "#FFD8D4DD": "Take the Plunge", "#FFE6CCB7": "Take-Out", "#FFA0928B": "Talavera", "#FFE7B25D": "Tal\u00e2yi Gold", "#FF707E84": "Taliesin Blue", "#FF648149": "Talipot Palm", "#FF159F9B": "Talismanic Teal", "#FF853534": "Tall Poppy Variant", "#FF0E81B9": "Tall Ships", "#FF5C9BCC": "Tall Waves", "#FF947E74": "Tallarn Leather", "#FFA79B5E": "Tallarn Sand", "#FFA39977": "Tallow Variant", "#FFFCD575": "Tamago Egg", "#FFFFA631": "Tamago Orange", "#FF3B3F40": "Tamahagane", "#FFF0E4C6": "Tamale", "#FFF8E7B7": "Tamale Maize", "#FFDEAA9B": "Tamanegi Peel", "#FFF2DD55": "Tamarack Yellow", "#FF11DDEE": "Tamarama", "#FF752B2F": "Tamarillo Variant", "#FF75503B": "Tamarind Fruit", "#FF8E604B": "Tamarind Tart", "#FF7C7D57": "Tambo Tank", "#FFBDC8AF": "Tamboon", "#FFF0EDD6": "Tambourine", "#FF658498": "Tambua Bay", "#FFC1E6DF": "Tame Teal", "#FFC5C5AC": "Tame Thyme", "#FF9C2626": "Tamed Beast", "#FFCFBCCF": "Tamed Beauty", "#FFD1B26F": "Tan Variant", "#FFA38D6D": "Tan 686A", "#FFAB7E4C": "Tan Brown", "#FFA9BE70": "Tan Green", "#FF323939": "T\u00e0n H\u0113i Soot", "#FFC2AA87": "Tan Oak", "#FFC19E78": "Tan Plan", "#FFCDB59C": "Tan Suede", "#FFF0BD9E": "Tan Temptation", "#FFA3755D": "Tan Wagon", "#FFF1D7CE": "Tan Whirl", "#FFB5905A": "Tan Your Hide", "#FFB8B5A1": "Tana Variant", "#FFA43834": "Tanager", "#FF8DD8E7": "Tanager Turquoise", "#FFD0B25C": "Tanami Desert", "#FFE9B581": "Tanaris Beige", "#FF896656": "Tanbark", "#FF766451": "Tanbark Trail", "#FF4A766E": "Tandayapa Cloud Forest", "#FFBB5C4D": "Tandoori", "#FFD25762": "Tandoori Red", "#FF9F4440": "Tandoori Spice", "#FF97725F": "Tangara", "#FF1E2F3C": "Tangaroa Variant", "#FFF2E9DE": "Tangelo Cream", "#FFEAD3A2": "Tangent", "#FF50507F": "Tangent Periwinkle", "#FFFF9300": "Tangerine Variant", "#FFD86130": "Tangerine Bliss", "#FFFF8449": "Tangerine Dream", "#FFE57F5B": "Tangerine Flake", "#FFDE8417": "Tangerine Haze", "#FFFFCC95": "Tangerine Silk", "#FFFF9E4B": "Tangerine Tango", "#FFF8AD1B": "Tangerine Twist", "#FFFECD01": "Tangerine Yellow", "#FFA97164": "Tangier", "#FF7C7C65": "Tangle", "#FFB19466": "Tangled Twine", "#FFCAC19A": "Tangled Vines", "#FFB2B2B2": "Tangled Web", "#FFA58F85": "Tanglewood", "#FFE7CEB8": "Tanglewood Park", "#FFD46F31": "Tango Variant", "#FFE47F7A": "Tango Pink", "#FFB10E2A": "Tango Red", "#FFE3C382": "Tangy", "#FF9A9147": "Tangy Dill", "#FFBB9B52": "Tangy Green", "#FFE7CAC3": "Tangy Taffy", "#FF5C6141": "Tank", "#FF848481": "Tank Grey", "#FF9AA0A2": "Tank Head", "#FFEFC93D": "Tank Yellow", "#FF7D7463": "Tankard Grey", "#FFF2C108": "Tanned Leather", "#FF8F6E4B": "Tanned Wood", "#FF5C493E": "Tannery Brown", "#FFA68B6D": "Tannin", "#FFAE6C37": "Tanooki Suit Brown", "#FFC7C844": "Tansy", "#FF95945C": "Tansy Green", "#FF669CCB": "Tantalise", "#FF87DCCE": "Tantalising Teal", "#FF857158": "Tantanmen Brown", "#FFFCD215": "Tanzanian Gold", "#FF1478A7": "Tanzanite", "#FF114A6B": "Tanzanite Blue", "#FF7983D7": "Tanzine", "#FFBFA77F": "Taos Taupe", "#FF2B8C8A": "Taos Turquoise", "#FF2F3032": "Tap Shoe", "#FF7C7C72": "Tapa Variant", "#FF906528": "Tapenade", "#FFF7F2DD": "Tapering Light", "#FFB37084": "Tapestry Variant", "#FFB8AC9E": "Tapestry Beige", "#FFB4966B": "Tapestry Gold", "#FFC06960": "Tapestry Red", "#FF4D7F86": "Tapestry Teal", "#FFDAC9B9": "Tapioca", "#FFDEF1DD": "Tara Variant", "#FF767A49": "Tara\u2019s Drapes", "#FF253C48": "Tarawera Variant", "#FF105673": "Tardis", "#FF003B6F": "Tardis Blue", "#FFA1CDBF": "Tareyton", "#FF5A5348": "Tarmac", "#FF477F4A": "Tarmac Green", "#FF7F6C24": "Tarnished Brass", "#FF797B80": "Tarnished Silver", "#FFB9A47E": "Tarnished Treasure", "#FFD5B176": "Tarnished Trumpet", "#FFC1B55C": "Tarpon Green", "#FFA4AE77": "Tarragon", "#FFB4AC84": "Tarragon Tease", "#FF825E61": "Tarsier", "#FFB6D27E": "Tart Apple", "#FFF6EEC9": "Tart Gelato", "#FFB1282A": "Tartan Red", "#FFBF5B3C": "Tartare", "#FFFDDCD9": "Tartlet", "#FFF7D917": "Tartrazine", "#FF919585": "Tarzan Green", "#FFBAC0B3": "Tasman Variant", "#FFE6C562": "Tasman Honey Yellow", "#FF658A8A": "Tasmanian Sea", "#FFC6884A": "Tassel", "#FFF9C0CE": "Tassel Flower", "#FF9F9291": "Tassel Taupe", "#FFC8A3B8": "Taste of Berry", "#FFF2AE73": "Taste of Summer", "#FF9B6D54": "Tasty Toffee", "#FFDECCAF": "Tatami", "#FFAF9D83": "Tatami Mat", "#FFBA8C64": "Tatami Tan", "#FF976E9A": "Tatarian Aster", "#FF988F63": "Tate Olive", "#FFA2806F": "Tattered Teddy", "#FF80736A": "Tattletail", "#FF376D03": "Tatzelwurm Green", "#FFF7D60D": "Tau Light Ochre", "#FFA3813F": "Tau Sept Ochre", "#FFB9A281": "Taupe Variant", "#FF483C30": "Taupe Brown", "#FFE1D9D0": "Taupe Mist", "#FF705A56": "Taupe Night", "#FFDAD2C6": "Taupe of the Morning", "#FFC3A79A": "Taupe Tapestry", "#FFE0D9CF": "Taupe Tease", "#FFADA090": "Taupe Tone", "#FFC7C1BB": "Taupe White", "#FFBAAEA8": "Taupedo", "#FF77BE52": "Taurus Forest Fern", "#FFB7A594": "Tavern", "#FF957046": "Tavern Creek", "#FF9E938F": "Tavern Taupe", "#FFD19776": "Tawny Amber", "#FFAA7B65": "Tawny Birch", "#FFAB856F": "Tawny Brown", "#FFEEE4D1": "Tawny Daylily", "#FFA7947C": "Tawny Ember", "#FFB39997": "Tawny Mushroom", "#FFC4962C": "Tawny Olive", "#FFD37F6F": "Tawny Orange", "#FF978B71": "Tawny Owl", "#FF643A48": "Tawny Port Variant", "#FFCCB299": "Tawny Tan", "#FF496569": "Tax Break", "#FF5C3937": "Taxite", "#FF5F5879": "Taylor", "#FF2B4B40": "Te Papa Green Variant", "#FFBFB5A2": "Tea Variant", "#FF726259": "Tea Bag", "#FFF5EBE1": "Tea Biscuit", "#FFB5A9AC": "Tea Blossom Pink", "#FF605547": "Tea Chest", "#FFF4E1C0": "Tea Cookie", "#FF8F8667": "Tea Leaf", "#FFA59564": "Tea Leaf Brown", "#FF888E7E": "Tea Leaf Mouse", "#FFF8E4C2": "Tea Light", "#FFFFD7D0": "Tea Party", "#FFDCB5B0": "Tea Room", "#FFF883C2": "Tea Rose", "#FFCAC6B5": "Tea Stain", "#FFC5A5C7": "Tea Towel", "#FFDC3855": "Teaberry", "#FFB44940": "Teaberry Blossom", "#FFAB8953": "Teak Variant", "#FF8D7E6D": "Teakwood", "#FF71999B": "Teal & Tonic", "#FF57A1A0": "Teal Bayou", "#FF01889F": "Teal Blue Variant", "#FF0F4D5C": "Teal Dark Blue", "#FF006D57": "Teal Dark Green", "#FF99E6B3": "Teal Deer", "#FF24604F": "Teal Drama", "#FF3DA3AE": "Teal Essence", "#FF405B5D": "Teal Forest", "#FF1A6C76": "Teal Fury", "#FF3E9EAC": "Teal Glacier", "#FF25A36F": "Teal Green Variant", "#FFD1EFE9": "Teal Ice", "#FF0DACA7": "Teal Me No Lies", "#FFC8EDE2": "Teal Melody", "#FF50B7CF": "Teal Moir\u00e9", "#FF406976": "Teal Mosaic", "#FF006D73": "Teal Motif", "#FF032425": "Teal Spill", "#FF627F7B": "Teal Stencil", "#FFD9F2E3": "Teal Treat", "#FF94B9B4": "Teal Tree", "#FF4E939D": "Teal Trinket", "#FF00A093": "Teal Trip", "#FF02708C": "Teal Tune", "#FF007765": "Teal Waters", "#FF8B9EA1": "Teal Wave", "#FF2B7B6C": "Teal We Meet Again", "#FF01697A": "Teal With It", "#FF24BCA8": "Tealish", "#FF0CDC73": "Tealish Green", "#FF416986": "Team Spirit", "#FFE2E2E1": "Teapot", "#FFB5D8DF": "Tear", "#FFD1EAEA": "Teardrop", "#FFF0F1DA": "Tears of Joy", "#FFDED2E8": "Teary Eyed", "#FFCEAEFA": "Teasel Dipsacus", "#FFF1E1D7": "Teasing Peach", "#FFBE9B79": "Teatime", "#FFC8A89E": "Teatime Mauve", "#FF4C7A9D": "Tech Wave", "#FF9FA1A1": "Techile", "#FF587C8D": "Technical Blue", "#FF006B8B": "Techno Blue", "#FF69AC58": "Techno Green", "#FFDD95B4": "Techno Pink", "#FFBFB9AA": "Techno Taupe", "#FF60BD8E": "Techno Turquoise", "#FFFF80F9": "Technolust", "#FFA3BAE3": "Teclis Blue", "#FFAC8067": "Teddy", "#FF9D8164": "Teddy Bear", "#FFCEB79D": "Teddy Bears & Cream", "#FFBCAC9F": "Teddy\u2019s Taupe", "#FFC00C20": "Tedious Red", "#FF68855A": "Tee Off", "#FFA67498": "Teen Queen", "#FF326395": "Teeny Bikini", "#FFF2DBD7": "Teewurst", "#FFC1B65F": "Tegreen", "#FFAD66D2": "Teldrassil Purple", "#FFAA22CC": "Telemagenta Variant", "#FFF0F3F1": "Tell Me a Secret", "#FF2D2541": "Telopea", "#FF47626A": "Tempe Star", "#FF2B8725": "Temperamental Green", "#FFBFB1AA": "Temperate Taupe", "#FF987E70": "Tempered Allspice", "#FF772211": "Tempered Chocolate", "#FFA1AEB1": "Tempered Grey", "#FFE3DBB7": "Tempered Sage", "#FF79839B": "Tempest", "#FF303A40": "Tempest in a Teapot", "#FFF2E688": "Templar\u2019s Gold", "#FFA6C9E3": "Template", "#FF339A8D": "Temple Guard Blue", "#FFEE7755": "Temple of Orange", "#FFA9855D": "Temple Tile", "#FF33ABB2": "Tempo", "#FF018C94": "Tempo Teal", "#FF987465": "Temptation", "#FFFF7733": "Temptatious Tangerine", "#FFDD8CB5": "Tempting Pink", "#FFCCAA99": "Tempting Taupe", "#FF3C2126": "Temptress Variant", "#FF98F6B0": "Tenacious Tentacles", "#FFF7E8D7": "Tender", "#FFC5CFB6": "Tender Greens", "#FFE0D4E0": "Tender Limerence", "#FFF8D5B8": "Tender Peach", "#FFDDC6BD": "Tender Shell", "#FFACCB35": "Tender Shoot", "#FFE8E287": "Tender Sprout", "#FFC4B198": "Tender Taupe", "#FFD5C6D6": "Tender Touch", "#FF82D9C5": "Tender Turquoise", "#FFA17E64": "Tender Twig", "#FFB7CFE2": "Tender Twilight", "#FFD4C3DA": "Tender Violet", "#FFBADBDF": "Tender Waves", "#FFE8EBAF": "Tender Yellow", "#FFC8DBCE": "Tenderness", "#FF869C65": "Tendril", "#FFDFFF4F": "Tennis Ball", "#FF7CB5C6": "Tennis Blue", "#FFC8450C": "Tennis Court", "#FFA35732": "Tense Terracotta", "#FFA89F86": "Tent Green", "#FFFFBACD": "Tentacle Pink", "#FF9ECFD9": "Tenzing", "#FFF4D0A4": "Tequila Variant", "#FFEB6238": "Teri-Gaki Persimmon", "#FFDCDFE5": "Terminator Chrome", "#FFBDB192": "Terminatus Stone", "#FFDDBB66": "Termite Beige", "#FFD5CDBD": "Tern Grey", "#FF5A382D": "Terra Brun", "#FFE2B5A6": "Terra Cotta Blush", "#FFD08F73": "Terra Cotta Clay", "#FFD38D71": "Terra Cotta Pot", "#FFC94D42": "Terra Cotta Red", "#FF9C675F": "Terra Cotta Sun", "#FFB06F60": "Terra Cotta Urn", "#FF844C47": "Terra Cove", "#FFD59982": "Terra Earth", "#FFCC7661": "Terra Orange", "#FFBB6569": "Terra Rosa", "#FF9F6D66": "Terra Rose", "#FFE8B57B": "Terra Sol", "#FFB6706B": "Terra Tone", "#FF73544D": "Terrace Brown", "#FFA1D8E0": "Terrace Pool", "#FFB2AB9C": "Terrace Taupe", "#FF275B60": "Terrace Teal", "#FFCAD0BF": "Terrace View", "#FFCB6843": "Terracotta Variant", "#FFC47C5E": "Terracotta Chip", "#FF976A66": "Terracotta Red Brown", "#FFD6BA9B": "Terracotta Sand", "#FF708157": "Terrain", "#FFA1965E": "Terran Khaki", "#FF807F4A": "Terrapin", "#FF5F6D5C": "Terrarium", "#FFA28873": "Terrazzo Brown", "#FFBE8973": "Terrazzo Tan", "#FF276757": "Terrestrial", "#FF1D4769": "Terror From the Deep", "#FFDDAAFF": "Testosterose", "#FFD90166": "T\u00eate-\u00e0-T\u00eate", "#FF8D99A1": "Teton Blue", "#FFDFEAE8": "Teton Breeze", "#FF1E20C7": "Tetraammine", "#FF8E6F73": "Tetrarose", "#FF2B3733": "Tetsu Black", "#FF005243": "Tetsu Green", "#FF455765": "Tetsu Iron", "#FF281A14": "Tetsu-Guro Black", "#FF17184B": "Tetsu-Kon Blue", "#FF2B3736": "Tetsuonando Black", "#FFE2DDD1": "Texan Angel", "#FFECE67E": "Texas Variant", "#FF8B6947": "Texas Boots", "#FFA54E37": "Texas Heatwave", "#FFC9926E": "Texas Hills", "#FFA0522D": "Texas Ranger Brown", "#FFF1D2C9": "Texas Rose Variant", "#FFB9A77C": "Texas Sage", "#FFFC9625": "Texas Sunset", "#FF794840": "Texas Sweet Tea", "#FF5500EE": "Tezcatlip\u014dca Blue", "#FF7A7F3F": "Thai Basil", "#FFCE0001": "Thai Chilli", "#FFBD6B1C": "Thai Curry", "#FFFE1C06": "Thai Hot", "#FFE0A878": "Thai Ice Tea", "#FFBB4455": "Thai Spice", "#FF624435": "Thai Teak", "#FF2E8689": "Thai Teal", "#FFE7C630": "Thai Temple", "#FF53B1BA": "Thalassa", "#FF44AADD": "Thalassophile", "#FF58F898": "Thallium Flame", "#FF181818": "Thamar Black", "#FF62676A": "Thames Dusk", "#FF949586": "Thames Fog", "#FFB56E4A": "Thanksgiving", "#FFB0B08E": "That\u2019s Atomic", "#FF8E3350": "That\u2019s My Jam", "#FFDCD290": "That\u2019s My Lime", "#FFB1948F": "Thatch Variant", "#FF867057": "Thatch Brown", "#FF544E31": "Thatch Green Variant", "#FFD6C7A6": "Thatched Cottage", "#FFEFE0C6": "Thatched Roof", "#FFE1EEEC": "Thawed Out", "#FFDD0088": "The Art of Seduction", "#FFBBFFFF": "The Big Freeze", "#FF214DAA": "The Blues Brothers", "#FFFFC8C2": "The Bluff", "#FFD0A492": "The Boulevard", "#FF837663": "The Cottage", "#FF102030": "The Count\u2019s Black", "#FF22C1D5": "The Crowd Roars!", "#FFA75455": "The Ego Has Landed", "#FF2A2A2A": "The End", "#FFEED508": "The End Is Beer", "#FF585673": "The Fang", "#FF436174": "The Fang Grey", "#FFF0E22C": "The Fifth Sun", "#FFFFF08C": "The First Daffodil", "#FFE9D2AF": "The Golden State", "#FFAAA651": "The Goods", "#FFBB00FF": "The Grape War of 97\u2019", "#FF558844": "The Legend of Green", "#FF70F15E": "The Matrix", "#FFFF8400": "The New Black", "#FF818580": "The North Wind Blows", "#FF4466EE": "The Rainbow Fish", "#FFF6F4EF": "The Speed of Light", "#FF110066": "The Vast of Night", "#FFCDBB63": "The Weight of Gold", "#FFDADDED": "The White Death", "#FF118844": "The Wild Apothecary", "#FF21467A": "Theatre Blue", "#FFEEF4DB": "Theatre District Lights", "#FF274242": "Theatre Dress", "#FFA76924": "Theatre Gold", "#FFE2D4D4": "Theatre Powder Rose", "#FFE2B13C": "Themeda Japonica", "#FFEE7711": "Therapeutic Toucan", "#FF002288": "There Is Light", "#FFAD9569": "There\u2019s No Place Like Home", "#FF3F5052": "Thermal", "#FF9CCEBE": "Thermal Aqua", "#FF589489": "Thermal Spring", "#FFEB3318": "Thermic Orange", "#FF8FADBD": "Thermocline", "#FFD2BB95": "Thermos", "#FFFBE4B3": "They Call It Mellow", "#FF5566DD": "Thick Blue", "#FFDCD3CE": "Thick Fog", "#FF88CC22": "Thick Green", "#FFCC55DD": "Thick Pink", "#FF8833DD": "Thick Purple", "#FFB30D0D": "Thick Red", "#FFF1D512": "Thick Yellow", "#FF69865B": "Thicket", "#FF93840F": "Thicket Green", "#FFA05D8B": "Thimble Red", "#FFE34B50": "Thimbleberry", "#FFAFA97D": "Thimbleberry Leaf", "#FFC6FCFF": "Thin Air", "#FFD4DCDA": "Thin Cloud", "#FFCAE0DF": "Thin Heights", "#FF834841": "Think Brick", "#FF4D8871": "Think Leaf", "#FFE5A5C1": "Think Pink", "#FF243BCD": "Third Eye Chakra", "#FF726E9B": "Thirsty Thursday", "#FF9499BB": "Thistle Down", "#FFC0B6A8": "Thistle Grey", "#FF834D7C": "Thistle Mauve", "#FF8AB3BF": "Thistleblossom Soft Blue", "#FF44CCFF": "Thor\u2019s Thunder", "#FFB5A197": "Thorn Crown", "#FF9C2D5D": "Thorne Wines", "#FF4C4A41": "Thorny Branch", "#FF7F716A": "Thorny Brush", "#FF5B3D34": "Thoroughfare", "#FFD8CDC8": "Thought", "#FF317589": "Thousand Herb", "#FFFFD9BB": "Thousand Needles Sand", "#FFE9E8E1": "Thousand Shells", "#FF02D8E9": "Thousand Sons Blue", "#FF316745": "Thousand Years Green", "#FF4F6446": "Thraka Green Wash", "#FFCEC2AA": "Threaded Loom", "#FFC30305": "Threatening Red", "#FF73C4D7": "Thredbo", "#FFFDDBB6": "Three Ring Circus", "#FFBDC4BB": "Three Wishes", "#FF93CCE7": "Thresher Shark", "#FFAC9A8A": "Threshold Taupe", "#FF8C283B": "Thrill Ride", "#FF8CC34B": "Thrilling Lime", "#FF281F3F": "Throat", "#FF6ACED4": "Throat Chakra", "#FFADA274": "Throw Rug", "#FF936B4F": "Thrush", "#FFA4B6A7": "Thrush Egg", "#FF11610F": "Thuja Green", "#FFDF6FA1": "Thulian Pink Variant", "#FFDDC2BA": "Thulite Rose", "#FFBDADA4": "Thumper", "#FFE88A76": "Thundelarra", "#FF4D4D4B": "Thunder Variant", "#FFF9F5DB": "Thunder & Lightning", "#FFCBD9D7": "Thunder Bay", "#FFAAC4D4": "Thunder Chi", "#FF57534C": "Thunder Grey", "#FFCE1B1F": "Thunder Mountain Longhorn Pepper", "#FF1A4876": "Thunder Night", "#FF923830": "Thunderbird Variant", "#FFFDEFAD": "Thunderbolt", "#FF8A99A3": "Thundercat", "#FF698589": "Thundercloud", "#FFF6F3A7": "Thunderdome", "#FF417074": "Thunderhawk Blue", "#FF597388": "Thundering Clouds", "#FF6D6C62": "Thunderous", "#FFD2D1CE": "Thundersnow", "#FF9098A1": "Thunderstorm", "#FF5F5755": "Thunderstruck", "#FF7F7B60": "Thurman", "#FF98514A": "Thy Flesh Consumed", "#FF50574C": "Thyme", "#FF6F8770": "Thyme and Place", "#FF629A31": "Thyme and Salt", "#FF737B6C": "Thyme Green", "#FF97422D": "Tia Maria Variant", "#FF9BB2AA": "Tiamo", "#FF5DB3FF": "Ti\u0101n L\u00e1n Sky", "#FF566B38": "Ti\u0101nt\u0101i Mountain Green", "#FFB9C3BE": "Tiara Variant", "#FFEAE0E8": "Tiara Jewel", "#FFDAA6CF": "Tiara Pink", "#FF184343": "Tiber Variant", "#FF6A6264": "Tibetan Cloak", "#FFF6F3E1": "Tibetan Jasmine", "#FFAE5848": "Tibetan Orange", "#FF88FFDD": "Tibetan Plateau", "#FF8B3145": "Tibetan Red", "#FF9C8A52": "Tibetan Silk", "#FFDBEAED": "Tibetan Sky", "#FF74B7C1": "Tibetan Stone", "#FF814D50": "Tibetan Temple", "#FF0084A6": "Tibetan Turquoise", "#FF268BCC": "Ticino Blue", "#FFEFA7BF": "Tickled Pink", "#FFF0F590": "Tidal Variant", "#FF78B3CC": "Tidal Basin", "#FFBFB9A3": "Tidal Foam", "#FFCDCA98": "Tidal Green", "#FFE5E9E1": "Tidal Mist", "#FF005E59": "Tidal Pool", "#FF43A7AA": "Tidal Teal", "#FF8B866B": "Tidal Thicket", "#FF355978": "Tidal Wave", "#FFBEB4AB": "Tide Variant", "#FF0A6F69": "Tide Pools", "#FF809899": "Tidepool Wonder", "#FF002400": "Tides of Darkness", "#FFC2E3DD": "Tidewater", "#FF343450": "Ti\u011b H\u0113i Metal", "#FFC2DDD3": "Tierra Del Fuego Sea Green", "#FFB58141": "Tiffany Amber", "#FFFDE4B4": "Tiffany Light", "#FFD2A694": "Tiffany Rose", "#FFBE9C67": "Tiger", "#FFCDA035": "Tiger Cat", "#FFE1DACA": "Tiger Claw", "#FFDEAE46": "Tiger Cub", "#FFDD9922": "Tiger King", "#FFE1583F": "Tiger Lily", "#FFF8F2DD": "Tiger Moth", "#FFDD6611": "Tiger Moth Orange", "#FFFF8855": "Tiger of Mysore", "#FFBF6F39": "Tiger Stripe", "#FFFEE9C4": "Tiger Tail", "#FFFFDE7E": "Tiger Yellow", "#FF9A7C63": "Tiger\u2019s Eye", "#FFAA5500": "Tijolo", "#FF8A6E45": "Tiki Hut", "#FFB9A37E": "Tiki Straw", "#FFBB9E3F": "Tiki Torch", "#FF0094A5": "Tile Blue", "#FF7A958E": "Tile Green", "#FFC76B4A": "Tile Red", "#FF60397F": "Tillandsia Purple", "#FF80624E": "Tilled Earth", "#FF6B4D35": "Tilled Soil", "#FF87815F": "Tilleul de No\u00e9mie", "#FFEE5522": "Tilted Pinball", "#FF991122": "Tilted Red", "#FFA0855C": "Timber Beam", "#FF605652": "Timber Brown", "#FFC0B09C": "Timber Dust", "#FF324336": "Timber Green Variant", "#FF908D85": "Timber Town", "#FFA1755C": "Timber Trail", "#FF73573F": "Timberline", "#FFB4ACA3": "Timberwolf Tint", "#FFA59888": "Time Capsule", "#FF7F6D37": "Time Honoured", "#FFFADEB8": "Time Out", "#FFB3C4D5": "Time Travel", "#FF9397A3": "Time Warp", "#FFB1D8DB": "Timeless", "#FFB6273E": "Timeless Beauty", "#FF976D59": "Timeless Copper", "#FFECE9E8": "Timeless Day", "#FFAFB2C4": "Timeless Lilac", "#FF9A4149": "Timeless Ruby", "#FFAFE4E2": "Timeless Seafoam", "#FF908379": "Timeless Taupe", "#FF20C073": "Times Square Screens", "#FFDADFB0": "Timid Absinthe", "#FFE5E0DD": "Timid Beige", "#FFD9E0EE": "Timid Blue", "#FFDCDDE5": "Timid Cloud", "#FFE1E2D6": "Timid Green", "#FFE4E0DA": "Timid Lime", "#FFDFDFEA": "Timid Purple", "#FF66A9B0": "Timid Sea", "#FFE4E0DD": "Timid Sheep", "#FFDED3DD": "Timid Violet", "#FFADB274": "Timothy Grass", "#FF919191": "Tin", "#FF48452B": "Tin Bitz", "#FFACB0B0": "Tin Foil", "#FF928A98": "Tin Lizzie", "#FFA4A298": "Tin Man", "#FFA3898A": "Tin Pink", "#FFE6541B": "Tingle", "#FFFBEDB8": "Tinker Light", "#FFFCF0C3": "Tinkerbell Trail", "#FF4B492D": "Tinny Tin", "#FFB88A3E": "Tinsel", "#FFC3D1D9": "Tinsel Beam", "#FFDCE0DC": "Tinsmith", "#FFCE9480": "Tint of Earth", "#FFDAE9DC": "Tint of Green", "#FFFFCBC9": "Tint of Rose", "#FF41BFB5": "Tint of Turquoise", "#FFCFF6F4": "Tinted Ice", "#FFC4B7D8": "Tinted Iris", "#FF9DA9D0": "Tinted Lilac", "#FFE3E7C4": "Tinted Mint", "#FFE1C8D1": "Tinted Rosewood", "#FF0088AB": "Tiny Bubbles", "#FFE0BFA5": "Tiny Calf", "#FFC08B6D": "Tiny Fawn", "#FFD4CFCC": "Tiny Ghost Town", "#FFB8D3E4": "Tiny Mr Frosty", "#FFFFD6C7": "Tiny Pink", "#FFB98FAF": "Tiny Ribbons", "#FF8A8D69": "Tiny Seedling", "#FFBBE4EA": "Tip of the Iceberg", "#FFD8C2CD": "Tip Toes", "#FF744B3E": "Tiramisu", "#FFD3BDA4": "Tiramisu Cream", "#FF75DE2F": "Tirisfal Lime", "#FF9E915C": "Tirol", "#FFDDD6E1": "Titan White Variant", "#FFAD8F0F": "Titanite Yellow", "#FF888586": "Titanium", "#FF5B798E": "Titanium Blue", "#FF545B62": "Titanium Grey", "#FFBCB9C0": "Titanium Man", "#FFE4E4E4": "Titanium White", "#FF646048": "Titanoboa", "#FFBD5620": "Titian Red", "#FFB8B2BE": "Titmouse Grey", "#FFF9F3DF": "Tizzy", "#FF316F82": "Tl\u0101loc Blue", "#FF25212A": "To Hell and Black", "#FF748D70": "Toad", "#FF3D6C54": "Toad King", "#FFB8282F": "Toadstool", "#FFD7E7DA": "Toadstool Dot", "#FF988088": "Toadstool Soup", "#FF016C75": "Toadstool Teal", "#FF9F715F": "Toast Variant", "#FFD2AD84": "Toast and Butter", "#FFB59274": "Toasted", "#FFDACFBA": "Toasted Almond", "#FF986B4D": "Toasted Bagel", "#FFE7DDCB": "Toasted Barley", "#FFDAB7A8": "Toasted Beige", "#FFE2D0B8": "Toasted Cashew", "#FFA7775B": "Toasted Chestnut", "#FFE9C2A1": "Toasted Coconut", "#FFA24A3B": "Toasted Cranberry", "#FFC5A986": "Toasted Grain", "#FFED8A53": "Toasted Husk", "#FFEFE0D4": "Toasted Marshmallow", "#FFFFF9EB": "Toasted Marshmallow Fluff", "#FFC08768": "Toasted Nut", "#FFA47365": "Toasted Nutmeg", "#FFEFDECC": "Toasted Oatmeal", "#FFA34631": "Toasted Paprika", "#FF765143": "Toasted Pecan", "#FFDCC6A6": "Toasted Pine Nut", "#FFAF9A73": "Toasted Sesame", "#FFAA3344": "Toasted Truffle", "#FF746A5A": "Toasted Walnut", "#FFC9AF96": "Toasted Wheat", "#FF957258": "Toasty", "#FFD1CCC0": "Toasty Grey", "#FF684F3C": "Tobacco", "#FF6D5843": "Tobacco Brown Variant", "#FF8C724F": "Tobacco Leaf", "#FF44362D": "Tobago", "#FFD39898": "Tobermory", "#FF077A7D": "Tobernite", "#FFAD785C": "Tobey Rattan", "#FF4C221B": "Tobi Brown", "#FFE45C10": "Tobiko Orange", "#FFF8DCBF": "Toes in the Sand", "#FF755139": "Toffee", "#FF947255": "Toffee Bar", "#FFD98B51": "Toffee Cream", "#FF995E39": "Toffee Crunch", "#FF968678": "Toffee Fingers", "#FFA5654A": "Toffee Glaze", "#FFB17426": "Toffee Sauce", "#FFC8A883": "Toffee Tan", "#FFC08950": "Toffee Tart", "#FFE6BAA9": "Tofino", "#FF03719C": "Tofino Belue", "#FFE6E5D6": "Tofu", "#FF94BEE1": "Toile Blue", "#FF8B534E": "Toile Red", "#FFB88884": "Toki Brown", "#FF007B43": "Tokiwa Green", "#FFECF3D8": "Tokyo Underground", "#FF6C5846": "Tol Barad Green", "#FF4E4851": "Tol Barad Grey", "#FF3E2631": "Toledo Variant", "#FFDECBB1": "Toledo Cuoio", "#FF4F6348": "Tom Thumb Variant", "#FFE9D988": "Tomatillo", "#FFCAD3C1": "Tomatillo Peel", "#FFBBB085": "Tomatillo Salsa", "#FFEF4026": "Tomato Variant", "#FFE10D18": "Tomato Baby", "#FFD15915": "Tomato Bisque", "#FFD6201A": "Tomato Burst", "#FFF6561C": "Tomato Concass\u00e9", "#FFBF753B": "Tomato Cream", "#FFE84A2E": "Tomato Curry", "#FFFF4444": "Tomato Frog", "#FFC9344C": "Tomato Puree", "#FFDD4422": "Tomato Queen", "#FFEC2D01": "Tomato Red", "#FFB21807": "Tomato Sauce", "#FFE44458": "Tomato Sceptre", "#FFA2423D": "Tomato Slices", "#FFAA251B": "Tomato Sunrise", "#FF0099CC": "Tomb Blue", "#FFCEC5B6": "Tombstone Grey", "#FFFAA945": "T\u014dmorokoshi Corn", "#FFEEC362": "T\u014dmorokoshi Yellow", "#FFFFC5A3": "Tomorrow\u2019s Coral", "#FFD14155": "T\u014dnatiuh Red", "#FFD1908E": "Tongue", "#FFDEDDAA": "Tonic", "#FF975437": "Tonicha", "#FF6B524C": "Tonka Bean", "#FFEDAC36": "Tonkatsu", "#FFB1A290": "Tony Taupe", "#FFE79E88": "Tonys Pink", "#FF9596A4": "Too Big", "#FF3D6695": "Too Blue", "#FF0088FF": "Too Blue Variant", "#FF0011BB": "Too Dark Tonight", "#FFFFB61E": "T\u014d\u014d Gold", "#FFBF6153": "Too Hot", "#FFD6BA66": "Tookie Bird", "#FF637985": "Tool Blue", "#FF7F7711": "Tool Green", "#FFBF843B": "Toothpick", "#FFF9DBE2": "Tootie Fruity", "#FFFAD873": "Top Banana", "#FFC1A393": "Top Hat Tan", "#FF82889C": "Top Shelf", "#FFD04838": "Top Tomato", "#FFCF7E40": "Topaz Variant", "#FFC5DDD0": "Topaz Green", "#FF92653F": "Topaz Mountain", "#FFEB975E": "Topaz Yellow", "#FF8E9655": "Topiary", "#FF6F7C00": "Topiary Garden", "#FF667700": "Topiary Green", "#FF618A4D": "Topiary Sculpture", "#FFBBC9B2": "Topiary Tint", "#FFAA5C71": "Topinambur Root", "#FFDAE2E0": "Topsail", "#FFE7E2DA": "Toque White", "#FFFD0D35": "Torch Red Variant", "#FFFFC985": "Torchlight", "#FF353D75": "Torea Bay Variant", "#FFCD123F": "Toreador", "#FFDB3E00": "Torii Red", "#FFD1D3CF": "Tornado", "#FF5E5B60": "Tornado Cloud", "#FF4D7179": "Tornado Season", "#FFABA698": "Tornado Watch", "#FF60635F": "Tornado Wind", "#FFFFDECD": "Toronja", "#FF4E241E": "Torrefacto Roast", "#FF55784F": "Torrey Pine", "#FF00938B": "Torrid Turquoise", "#FF5E8E91": "Tort", "#FFEFDBA7": "Tortilla", "#FF7C4937": "Tortoise Shell", "#FF9C5F22": "Tortoiseshell Specs", "#FF84816F": "Tortuga", "#FF374E88": "Tory Blue Variant", "#FFD6685F": "Tory Red", "#FF744042": "Tosca Variant", "#FF9F846B": "Toscana", "#FFE3C19C": "Tostada", "#FFA67E4B": "Tosty Crust", "#FF303543": "Total Eclipse", "#FFF6EAD8": "Total Recall", "#FF3F4041": "Totally Black", "#FF909853": "Totally Broccoli", "#FFCCA683": "Totally Tan", "#FF01868C": "Totally Teal", "#FFDD9977": "Totally Toffee", "#FFF09650": "Toucan", "#FFFBC90D": "Toucan Gentleman", "#FFC2D7E9": "Touch of Blue", "#FFF6DED5": "Touch of Blush", "#FF8E6F6E": "Touch of Class", "#FFC5E5DD": "Touch of Frost", "#FFDD8844": "Touch of Glamour", "#FFDBE9D5": "Touch of Green", "#FFD1CFCA": "Touch of Grey", "#FFE1E5D7": "Touch of Lime", "#FFF8FFF8": "Touch of Mint", "#FFD5C7BA": "Touch of Sand", "#FFC8D4CC": "Touch of Spring", "#FFFAF7E9": "Touch of Sun", "#FFEED9D1": "Touch of Tan", "#FFF7E4D0": "Touch of Topaz", "#FFA1D4CF": "Touch of Turquoise", "#FFECDFD8": "Touchable Pink", "#FFC3E4E8": "Touched by the Sea", "#FFF4E1D7": "Touching White", "#FFC7AC7D": "Toupe", "#FF83A1A7": "Tourmaline Variant", "#FFBDA3A5": "Tourmaline Mauve", "#FF4F9E96": "Tourmaline Turquoise", "#FF99D3DF": "Tourmaline Water Blue", "#FF54836B": "Tournament Field", "#FF7BA0A0": "Tower Bridge", "#FF9CACA5": "Tower Grey", "#FFD5B59B": "Tower Tan", "#FF897565": "Towering Cliffs", "#FFC3AA8C": "Townhall Tan", "#FFC19859": "Townhouse Tan", "#FFBBB09B": "Townhouse Taupe", "#FFCCFF11": "Toxic Boyfriend", "#FFCCEEBB": "Toxic Essence", "#FF61DE2A": "Toxic Green", "#FFE1F8E7": "Toxic Latte", "#FF43E85F": "Toxic Slime", "#FF00BB33": "Toxic Sludge", "#FFC1FDC9": "Toxic Steam", "#FF00889F": "Toy Blue", "#FF117700": "Toy Camouflage", "#FF776EA2": "Toy Mauve", "#FF005280": "Toy Submarine Blue", "#FF6D6F4F": "Toy Tank Green", "#FFDBB9A0": "Tracery", "#FFD66352": "Track and Field", "#FF849E88": "Track Green", "#FF00BFFE": "Tractor Beam", "#FF1C6A51": "Tractor Green", "#FFFD0F35": "Tractor Red", "#FF6A7978": "Trade Secret", "#FFB7C5C6": "Trade Winds", "#FFBB8D3B": "Trading Post", "#FF776255": "Traditional", "#FF1F648D": "Traditional Blue", "#FFC7C7C1": "Traditional Grey", "#FF6F4F3E": "Traditional Leather", "#FFBE013C": "Traditional Rose", "#FF0504AA": "Traditional Royal Blue", "#FFD6D2C0": "Traditional Tan", "#FF55FF22": "Traffic Green", "#FF8C9900": "Traffic Light Green", "#FFFF1C1C": "Traffic Red", "#FFF1F0EA": "Traffic White", "#FFFEDC39": "Traffic Yellow", "#FF927383": "Tragic Juliet", "#FFD0C4AC": "Trail Dust", "#FF6B6662": "Trail Print", "#FFC0B28E": "Trailblazer", "#FF776C61": "Trailhead", "#FFCFD5A7": "Trailing Vine", "#FF8F97A5": "Trance", "#FFDDEDE9": "Tranquil Variant", "#FF7C9AA0": "Tranquil Aqua", "#FF74B8DE": "Tranquil Bay", "#FFD3B2C3": "Tranquil Dusk", "#FFECE7F2": "Tranquil Eve", "#FFA4AF9E": "Tranquil Green", "#FFFCE2D7": "Tranquil Peach", "#FF768294": "Tranquil Pond", "#FF88DDFF": "Tranquil Pool", "#FFDBD2CF": "Tranquil Retreat", "#FFD2D2DF": "Tranquil Sea", "#FF629091": "Tranquil Seashore", "#FFB0A596": "Tranquil Taupe", "#FF8AC7BB": "Tranquil Teal", "#FF6C9DA9": "Tranquili Teal", "#FF8E9B96": "Tranquillity", "#FF307D67": "Trans Tasman", "#FFC3AC98": "Transcend", "#FFF8F4D8": "Transcendence", "#FFA5ACB7": "Transformer", "#FFEA1833": "Transfusion", "#FFFFE9E1": "Translucent Silk", "#FFFFEDEF": "Translucent Unicorn", "#FFE5EFD7": "Translucent Vision", "#FFE4E3E9": "Translucent White", "#FFF4ECC2": "Transparent Beige", "#FFDDDDFF": "Transparent Blue", "#FFDDFFDD": "Transparent Green", "#FFB4A6BF": "Transparent Mauve", "#FFFFAA66": "Transparent Orange", "#FFFFDDEE": "Transparent Pink", "#FFCBDCCB": "Transparent White", "#FFFFEEAA": "Transparent Yellow", "#FF004F54": "Transporter Green", "#FF0E1D32": "Trapped Darkness", "#FF005239": "Trapper Green", "#FFF4E8B6": "Trapunto", "#FFE2DDC7": "Travertine Variant", "#FFB5AB8F": "Travertine Path", "#FFDDF5E7": "Treacherous Blizzard", "#FF885D2D": "Treacle", "#FFDE9832": "Treacle Fudge", "#FF9B7856": "Treasure Casket", "#FF998866": "Treasure Chamber", "#FF726854": "Treasure Chest", "#FF47493B": "Treasure Island", "#FF609D91": "Treasure Isle", "#FFD0BB9D": "Treasure Map", "#FF658FAA": "Treasure Map Waters", "#FF3F363D": "Treasure Seeker", "#FF9C7947": "Treasured", "#FFBB2277": "Treasured Love", "#FF52C1B3": "Treasured Teal", "#FF006633": "Treasured Wilderness", "#FFBA8B36": "Treasures", "#FFDBD186": "Treasury", "#FF715E58": "Tree Bark", "#FF665B4E": "Tree Bark Brown", "#FF304B4A": "Tree Bark Green", "#FF8A7362": "Tree Branch", "#FF7FB489": "Tree Fern", "#FF9FB32E": "Tree Frog", "#FF7CA14E": "Tree Frog Green", "#FF2A7E19": "Tree Green", "#FF79774A": "Tree Hugger", "#FFC3DB8D": "Tree Line", "#FFDCDBCA": "Tree Moss", "#FF595D45": "Tree of Life", "#FFA4345D": "Tree Peony", "#FFE2813B": "Tree Poppy Variant", "#FFBDC7BC": "Tree Pose", "#FF22CC00": "Tree Python", "#FFCC7711": "Tree Sap", "#FF476A30": "Tree Shade", "#FF726144": "Tree Swing", "#FFD1B7A7": "Treeless", "#FF609F6E": "Treelet", "#FF91B6AC": "Treetop", "#FF2F4A15": "Treetop Cathedral", "#FF47562F": "Trefoil", "#FFD1AE9A": "Trek Tan", "#FF4E606D": "Trekking Blue", "#FF3D5C54": "Trekking Green", "#FFEAEFE5": "Trellis", "#FF7F753C": "Trellis Climber", "#FF5D7F74": "Trellis Vine", "#FF9AA097": "Trellised Ivy", "#FFAF9770": "Trench", "#FF7E8424": "Trendy Green Variant", "#FF805D80": "Trendy Pink Variant", "#FFA82F2E": "Tr\u00e8s Bien", "#FFDCC7AD": "Tres Naturale", "#FFC2DFE2": "Trevi Fountain", "#FFF2D1C4": "Tri-Tip", "#FF94A089": "Triamble", "#FF67422D": "Triassic", "#FF807943": "Tribal", "#FF514843": "Tribal Drum", "#FFA78876": "Tribal Pottery", "#FFA4918D": "Tribeca", "#FF33373B": "Tribecca Corner", "#FF74B8DA": "Tribute", "#FFEAB38A": "Trick or Treat", "#FF2F2F30": "Tricorn Black", "#FFDCD3E3": "Tricot Lilac White", "#FFB09994": "Tricycle Taupe", "#FF494F62": "Tried & True Blue", "#FFF5F5DA": "Triforce Shine", "#FFF0F00F": "Triforce Yellow", "#FFA89896": "Trillium", "#FF756D44": "Trim", "#FFC54F33": "Trinidad Variant", "#FFD0343D": "Trinidad Moruga Scorpion", "#FFB9B79B": "Trinity Islands", "#FFD69835": "Trinket", "#FFA7885F": "Trinket Gold", "#FFC96272": "Triple Berry", "#FFE5E3E5": "Tripoli White", "#FFCC00EE": "Trippy Velvet", "#FF8EB9C4": "Trisha\u2019s Eyes", "#FF0C0C1F": "Tristesse", "#FFF4F0E3": "Trite White", "#FF705676": "Trixter", "#FF775020": "Trojan Horse Brown", "#FF014E2E": "Troll Green", "#FFF4A34C": "Troll Slayer Orange", "#FFAE5F51": "Trolley Dash", "#FF818181": "Trolley Grey", "#FF708386": "Trooper", "#FF73B7C2": "Tropez Blue", "#FF4889AC": "Tropic", "#FFBCC23C": "Tropic Canary", "#FF016F92": "Tropic Sea", "#FF6CC1BB": "Tropic Tide", "#FF6AB5A4": "Tropic Turquoise", "#FFA8E8CB": "Tropical", "#FF9DB7AF": "Tropical Bay", "#FFD7967E": "Tropical Blooms", "#FFAEC9EB": "Tropical Blue Variant", "#FFEBEDEE": "Tropical Breeze", "#FF8CA8A0": "Tropical Cascade", "#FFB9CABD": "Tropical Cyclone", "#FFD9EAE5": "Tropical Dream", "#FF33FF22": "Tropical Elements", "#FF4DBBAF": "Tropical Escape", "#FF3E6252": "Tropical Foliage", "#FF024A43": "Tropical Forest", "#FF228B21": "Tropical Forest Green", "#FF99DDCC": "Tropical Freeze", "#FFFDD3A7": "Tropical Fruit", "#FF55DD00": "Tropical Funk", "#FF17806D": "Tropical Green", "#FFC2343C": "Tropical Heat", "#FF9C6071": "Tropical Hibiscus", "#FF17A99E": "Tropical Hideaway", "#FF8FCDC7": "Tropical Holiday", "#FF009D7D": "Tropical Kelp", "#FF1E98AE": "Tropical Lagoon", "#FF9CD572": "Tropical Light", "#FFCAE8E8": "Tropical Mist", "#FFD2C478": "Tropical Moss", "#FF2A2E4C": "Tropical Night Blue", "#FF98523D": "Tropical Nut", "#FF579AA5": "Tropical Oasis", "#FFA0828A": "Tropical Orchid", "#FFF7A082": "Tropical Paradise", "#FFFFC4B2": "Tropical Peach", "#FFBFDEEF": "Tropical Pool", "#FF447777": "Tropical Rain", "#FF03A598": "Tropical Sea", "#FFDDC073": "Tropical Siesta", "#FF155D66": "Tropical Skies", "#FFC5556D": "Tropical Smoothie", "#FF70CBCE": "Tropical Splash", "#FFFBB719": "Tropical Sun", "#FFE0DEB8": "Tropical Tale", "#FFCBB391": "Tropical Tan", "#FF008794": "Tropical Teal", "#FF5ECAAE": "Tropical Tide", "#FF50A074": "Tropical Tone", "#FF89D1B5": "Tropical Trail", "#FF20AEA7": "Tropical Tree", "#FF04CDFF": "Tropical Turquoise", "#FF837946": "Tropical Twist", "#FFCDA5DF": "Tropical Violet", "#FFBEE7E2": "Tropical Waterfall", "#FF007C7E": "Tropical Waters", "#FFBA8F68": "Tropical Wood", "#FF603B2A": "Tropical Wood Brown", "#FF447700": "Tropicana", "#FF009B8E": "Tropics", "#FF726D40": "Trough Shell", "#FF918754": "Trough Shell Brown", "#FF00666D": "Trouser Blue", "#FF4C5356": "Trout Variant", "#FFF75300": "Trout Caviar", "#FFCB7077": "Trout Pout", "#FFDCC49B": "True Blonde", "#FF010FCC": "True Blue Variant", "#FF8B5643": "True Copper", "#FFA22042": "True Crimson", "#FF089404": "True Green", "#FFB8AE98": "True Khaki", "#FF7E89C8": "True Lavender", "#FFE27E8A": "True Love", "#FF465784": "True Navy", "#FF65318E": "True Purple", "#FFC81A3A": "True Red", "#FF4D456A": "True Romance", "#FF24A6C2": "True Sky Blue", "#FFA8A095": "True Taupewood", "#FF0A8391": "True Teal", "#FFCDD3A3": "True", "#FF8E72C7": "True V Variant", "#FF967A67": "True Walnut", "#FFB46C42": "Truepenny", "#FF99BBFF": "Truesky Gloxym", "#FFC2A78E": "Truffle", "#FFA35139": "Truffle Trouble", "#FF777250": "Truly Olive", "#FFAC9E97": "Truly Taupe", "#FFFAA76C": "Trump Tan", "#FFD1B669": "Trumpet", "#FFE49977": "Trumpet Flower", "#FFE9B413": "Trumpet Gold", "#FF5A7D7A": "Trumpet Teal", "#FF907BAA": "Trumpeter", "#FF9B5FC0": "Trunks Hair", "#FF527498": "Trustee", "#FFB59F8F": "Trusty Tan", "#FF344989": "Truth", "#FF34B334": "Try Your Luck", "#FF8B7F7B": "Tsar", "#FFD1B4C6": "Tsarina", "#FF869BAF": "Tsunami", "#FFCFD4D3": "Tsunami Sky", "#FF9BA88D": "Tsurubami Green", "#FF574D35": "T\u01d4 H\u0113i Black", "#FF454642": "Tuatara Variant", "#FF0E8787": "Tubbataha Teal", "#FFFFFAEC": "Tuberose", "#FF00858B": "Tucson Teal", "#FF5F572B": "Tucum\u00e1n Green", "#FFC1CECF": "Tudor Ice", "#FFB68960": "Tudor Tan", "#FFA59788": "Tuffet", "#FFCBC2AD": "Tuft", "#FFF9D3BE": "Tuft Bush Variant", "#FFB96C46": "Tufted Leather", "#FFCCDDEE": "Tu\u011f\u00e7e Silver", "#FFB89CBC": "Tuileries Tint", "#FF573B2A": "Tuk Tuk", "#FFFF878D": "Tulip", "#FFFBF4DA": "Tulip Petals", "#FF531938": "Tulip Poplar Purple", "#FFB8516A": "Tulip Red", "#FFC3C4D6": "Tulip Soft Blue", "#FFE3AC3D": "Tulip Tree Variant", "#FFF1E5D1": "Tulip White", "#FF966993": "Tulipan Violet", "#FF86586A": "Tulipwood", "#FFF1BA91": "Tulle", "#FF8D9098": "Tulle Grey", "#FFD9E7E5": "Tulle Soft Blue", "#FFFBEDE5": "Tulle White", "#FFCDBB9C": "Tumblin\u2019 Tumbleweed", "#FFCEBF9C": "Tumbling Tumbleweed", "#FF46494E": "Tuna Variant", "#FFCF6275": "Tuna Sashimi", "#FF585452": "Tundora Variant", "#FFD6D9D7": "Tundra", "#FFE1E1DB": "Tundra Frost", "#FFB5AC9F": "Tungsten", "#FF00CC00": "Tunic Green", "#FFFFDDB5": "Tunisian Stone", "#FFC0A04D": "Tupelo Honey", "#FF9C9152": "Tupelo Tree", "#FFF9BB59": "Turbinado Sugar", "#FF555C63": "Turbulence", "#FF536A79": "Turbulent Sea", "#FF415B36": "Turf", "#FF2A8948": "Turf Green", "#FF009922": "Turf Master", "#FF738050": "Turf War", "#FF006169": "Turkish Aqua", "#FFBB937B": "Turkish Bath", "#FF007FAE": "Turkish Blue", "#FF0E9CA5": "Turkish Boy", "#FF483F39": "Turkish Coffee", "#FFF7F3BD": "Turkish Ginger", "#FF2B888D": "Turkish Jade", "#FFA56E75": "Turkish Rose Variant", "#FF194F90": "Turkish Sea", "#FF2F7A92": "Turkish Stone", "#FF72CAC1": "Turkish Teal", "#FF007E9F": "Turkish Tile", "#FFD9D9D1": "Turkish Tower", "#FF77DDE7": "Turkish Turquoise", "#FFF3D8BE": "Turkscap", "#FFAE9041": "Turmeric Variant", "#FFC18116": "Turmeric Brown", "#FFCA7A40": "Turmeric Red", "#FFFEAE0D": "Turmeric Root", "#FFD88E2D": "Turmeric Tea", "#FF8D7448": "Turned Leaf", "#FF93BCBB": "Turner\u2019s Light", "#FFE6C26F": "Turner\u2019s Yellow", "#FFCED9C3": "Turning Leaf", "#FFEDE1A8": "Turning Oakleaf", "#FFB9AAA1": "Turning Tables", "#FFEFC6A1": "Turnip Boy", "#FFBB9ECD": "Turnip Crown", "#FFE5717B": "Turnip the Pink", "#FFD3CFBF": "Turnstone", "#FF448899": "Turquesa", "#FF01A192": "Turquish", "#FF06C2AC": "Turquoise Tint", "#FF6FE7DB": "Turquoise Chalk", "#FF0E7C61": "Turquoise Cyan", "#FF6DAFA7": "Turquoise Fantasies", "#FF04F489": "Turquoise Green Variant", "#FFB4CECF": "Turquoise Grey", "#FF89F5E3": "Turquoise Pearl", "#FF00C5CD": "Turquoise Surf", "#FF13BBAF": "Turquoise Topaz", "#FF457B74": "Turquoise Tortoise", "#FFA8E3CC": "Turquoise Tower", "#FF73D4C2": "Turquoise Twist", "#FFCFE9DC": "Turquoise White", "#FF523F31": "Turtle", "#FF84897F": "Turtle Bay", "#FFCED8C1": "Turtle Chalk", "#FF65926D": "Turtle Creek", "#FF75B84F": "Turtle Green Variant", "#FF73B7A5": "Turtle Lake", "#FF939717": "Turtle Moss", "#FF897C64": "Turtle Shell", "#FF363E1D": "Turtle Skin", "#FFB6B5A0": "Turtle Trail", "#FF35B76D": "Turtle Warrior", "#FFD6CEBB": "Turtledove", "#FFFBD5A6": "Tuscan", "#FFE7D2AD": "Tuscan Bread", "#FF6F4C37": "Tuscan Brown", "#FFAA5E5A": "Tuscan Clay", "#FF658362": "Tuscan Herbs", "#FF746E38": "Tuscan Hills", "#FFDC938C": "Tuscan Image", "#FFA08D71": "Tuscan Mosaic", "#FF5D583E": "Tuscan Olive", "#FF9C6350": "Tuscan Rooftops", "#FF723D3B": "Tuscan Russet", "#FFFFD84D": "Tuscan Sun", "#FFBB7C3F": "Tuscan Sunset", "#FFFCC492": "Tuscan Wall", "#FF008893": "Tuscana Blue", "#FFB98C7B": "Tuscany Tint", "#FF7E875F": "Tuscany Hillside", "#FF0082AD": "Tusche Blue", "#FF9996B3": "Tusi Grey", "#FFE3E5B1": "Tusk Variant", "#FF883636": "Tuskgor Fur", "#FFEDC5D7": "Tussie-Mussie", "#FFBF914B": "Tussock Variant", "#FFBC587B": "Tutti Frutti", "#FFF8E4E3": "Tutu Variant", "#FFE95295": "Tutuji Pink", "#FF3F3C43": "Tuxedo", "#FF937B56": "Tweed", "#FFCCB586": "Tweed Jacket", "#FF8F7B5B": "Tweedy", "#FFFFBE4C": "Twenty Carat", "#FFCA999E": "Twice Shy", "#FF77623A": "Twig Basket", "#FF4E518B": "Twilight Variant", "#FFC8BFB5": "Twilight Beige", "#FFCFB4C2": "Twilight Bloom", "#FF0A437A": "Twilight Blue Variant", "#FFB59A9C": "Twilight Blush", "#FF3F5363": "Twilight Chimes", "#FF606079": "Twilight Dusk", "#FF1C3378": "Twilight Express", "#FF54574F": "Twilight Forest", "#FFD1D6D6": "Twilight Grey", "#FF7E664B": "Twilight Jungle", "#FFDAC0CD": "Twilight Light", "#FF977D7F": "Twilight Mauve", "#FF51A5A4": "Twilight Meadow", "#FFBDC9E4": "Twilight Mist", "#FFBFBCD2": "Twilight Pearl", "#FF65648B": "Twilight Purple", "#FF71898D": "Twilight Stroll", "#FFA79994": "Twilight Taupe", "#FF7B85C6": "Twilight Twinkle", "#FFE5E6D7": "Twilight Twist", "#FF191916": "Twilight Zone", "#FFA3957C": "Twill", "#FFBEDBED": "Twin Blue", "#FFA4C7C8": "Twin Cities", "#FF684344": "Twinberry", "#FFC19156": "Twine Variant", "#FF74A69B": "Twining Vine", "#FFADC6D3": "Twinkle", "#FFD0D7DF": "Twinkle Blue", "#FFFCE79A": "Twinkle Little Star", "#FF636CA8": "Twinkle Night", "#FFFBD8CC": "Twinkle Pink", "#FFE2D39B": "Twinkle Toes", "#FFFCF0C5": "Twinkle Twinkle", "#FFE9DBE4": "Twinkled Pink", "#FFFFFAC1": "Twinkling Lights", "#FFCF4796": "Twinkly Pinkily", "#FF76C4D1": "Twisted Blue", "#FF9A845E": "Twisted Tail", "#FF7F6C6E": "Twisted Time", "#FF655F50": "Twisted Vine", "#FF854A2E": "Two Cents", "#FFBED3E1": "Two Harbours", "#FFA5CA4F": "Two Peas in a Pod", "#FF4C5053": "Typewriter Ink", "#FF463D2B": "Typhus Corrosion", "#FFCDC586": "Tyrant Skull", "#FF4E4D59": "Tyrian", "#FF9448B0": "Tyrian Shimmer", "#FFB3CDBF": "Tyrol", "#FF00A499": "Tyrolite Blue-Green", "#FF736458": "Tyson Taupe", "#FFDDEECC": "Tzatziki Green", "#FF7B5838": "\u00dcber Umber", "#FF989FA3": "UFO", "#FF88AA11": "UFO Defense Green", "#FF645530": "Uguisu Brown", "#FF928C36": "Uguisu Green", "#FFFABF14": "Ukon Saffron", "#FFFCC680": "Uldum Beige", "#FFC7E0D9": "Ulthuan Grey", "#FFA9A8A9": "Ultimate Grey", "#FFFF4200": "Ultimate Orange", "#FFFF55FF": "Ultimate Pink", "#FF7EBA4D": "Ultra Green", "#FF4433FF": "Ultra Indigo", "#FFA3EFB8": "Ultra Mint", "#FFD1F358": "Ultra Moss", "#FFF06FFF": "Ultra Pink", "#FFF8F8F3": "Ultra Pure White", "#FF7366BD": "Ultra Violet", "#FFAA22AA": "Ultra Violet Lentz", "#FFF6F6F0": "Ultra White", "#FF770088": "Ultraberry", "#FF1805DB": "Ultramarine Variant", "#FF657ABB": "Ultramarine Blue Variant", "#FF007A64": "Ultramarine Green", "#FF2E328F": "Ultramarine Highlight", "#FF090045": "Ultramarine Shadow", "#FF1D2A58": "Ultramarine Violet", "#FF654EA3": "Ultrapurple Merge", "#FFBB44CC": "Ultraviolet Berl", "#FFBB44BB": "Ultraviolet Cryner", "#FFBB44AA": "Ultraviolet Nusp", "#FFBB44DD": "Ultraviolet Onsible", "#FF921D0F": "Uluru Red", "#FF704336": "Uluru Sunset", "#FFB26400": "Umber Variant", "#FF613936": "Umber Brown", "#FF765138": "Umber Rust", "#FF4E4D2F": "Umber Shade Wash", "#FFECE7DD": "Umber Style", "#FF211E1F": "Umbra", "#FF87706B": "Umbra Sand", "#FF520200": "Umbral Umber", "#FFA2AF70": "Umbrella Green", "#FFB54753": "Umbria Red", "#FF8F4155": "Umemurasaki Purple", "#FF97645A": "Umenezumi Plum", "#FFFA9258": "Umezome Pink", "#FF75A14F": "Unakite", "#FFFBFAF5": "Unbleached", "#FFF5D8BB": "Unbleached Calico", "#FFFBE7E6": "Unburdened Pink", "#FFA9B0B1": "Uncertain Grey", "#FF19565E": "Uncharted", "#FFC7AB90": "Uncommon Coir", "#FF476E42": "Under the Radar", "#FF395D68": "Under the Sea", "#FFEFD100": "Under the Sun", "#FFBE9E48": "Underbrush", "#FF428C49": "Underclover", "#FFA38479": "Undercooked Bacon", "#FF7FC3E1": "Undercool", "#FF665A51": "Underground", "#FF524B4C": "Underground Civilization", "#FF87968B": "Underground Gardens", "#FF120A65": "Underground Stream", "#FFB2AC88": "Underhive Ash", "#FFCC4422": "Underpass Shrine", "#FF90B1AE": "Undersea", "#FFD4C9BB": "Understated", "#FFC3C4AC": "Understory", "#FF779999": "Undertow", "#FFCFEEE8": "Underwater", "#FF0022BB": "Underwater Falling", "#FF06D078": "Underwater Fern", "#FFE78EA5": "Underwater Flare", "#FF4488AA": "Underwater Moonlight", "#FF243062": "Underwater Realm", "#FF657F7A": "Underwater World", "#FF1E231C": "Underworld", "#FF89C1BA": "Undine", "#FF69667C": "Unexplained", "#FFE7D8DE": "Unfading Dusk", "#FF986962": "Unfired Clay", "#FFDDCCB1": "Unforgettable", "#FFAE8245": "Unforgettably Gold", "#FFD6C8C0": "Unfussy Beige", "#FFD6A766": "Ungor Beige", "#FFFF2F92": "Unicorn Dust", "#FFE8E8E8": "Unicorn Silver", "#FFA7B7CA": "Uniform", "#FF6E5D3E": "Uniform Brown", "#FF4C4623": "Uniform Green", "#FF5F7B7E": "Uniform Green Grey", "#FFA8A8A8": "Uniform Grey", "#FF8C7EB9": "Unimaginable", "#FFB5D1C7": "Uninhibited", "#FF9C9680": "Union Springs", "#FFC7C5BA": "Union Station", "#FFCBC9C9": "Unique Grey", "#FF264D8E": "Unity", "#FF006B38": "Universal Green", "#FFB8A992": "Universal Khaki", "#FF9C8168": "Universal Umber", "#FFB78727": "University of California Gold", "#FFF77F00": "University of Tennessee Orange", "#FFD2CAB7": "Unmarked Trail", "#FFB12D35": "Unmatched Beauty", "#FFFEFE66": "Unmellow Yellow", "#FF4B4840": "Unplugged", "#FF7B746B": "Unpredictable Hue", "#FF5C6E70": "Unreal Teal", "#FFF0FF57": "Unripe Banana", "#FFB1BEA8": "Unroasted Coffee", "#FFDE5730": "Untamed Orange", "#FFDD0022": "Untamed Red", "#FFA3A7A0": "Unusual Grey", "#FFF2F8ED": "Unwind", "#FF014431": "UP Forest Green", "#FF6E706D": "Up in Smoke", "#FFC2D5D8": "Up in the Air", "#FF6F9587": "Up North", "#FFF1D9A5": "Upbeat", "#FFEB9724": "Uplifting Yellow", "#FFA3758B": "Upper Crust", "#FF8D6051": "Upper East Side", "#FFEE1100": "Uproar Red", "#FFA8ADC2": "Upscale", "#FFB52923": "Upset Tomato", "#FFCAC7B8": "Upstate", "#FFFFA8A6": "Upstream Salmon", "#FFA791A8": "Uptown Girl", "#FFF1E4D7": "Uptown Taupe", "#FFBDC9D2": "Upward", "#FFBCB58C": "Urahayanagi Green", "#FF93B778": "Uran Mica", "#FF7C4A2C": "Urban Auburn", "#FFDDD4C5": "Urban Bird", "#FFAEA28C": "Urban Charm", "#FF424C4A": "Urban Chic", "#FF89776E": "Urban Exploration", "#FF7C7466": "Urban Garden", "#FF005042": "Urban Green", "#FFCACACC": "Urban Grey", "#FFA4947E": "Urban Jungle", "#FF67585F": "Urban Legend", "#FFD3DBD9": "Urban Mist", "#FF374F54": "Urban Oasis", "#FFB0B1A9": "Urban Putty", "#FFBDCBCA": "Urban Raincoat", "#FF978B6E": "Urban Safari", "#FFDBD8DA": "Urban Snowfall", "#FFBDBEBA": "Urban Sunrise", "#FFC9BDB6": "Urban Taupe", "#FF8899AA": "Urban Vibes", "#FF696447": "Urbane", "#FF54504A": "Urbane Bronze", "#FF4D5659": "Urbanite", "#FFFFD72E": "Uri Yellow", "#FFFFECC2": "Urnebes Beige", "#FF00308F": "US Air Force Blue", "#FF716140": "US Field Drab", "#FF990010": "USC Cardinal", "#FF231712": "Used Oil", "#FFCFCABD": "Useful Beige", "#FFBBBB7F": "Ushabti Bone", "#FF373D31": "USMC Green", "#FFE597B2": "Usu Koubai Blossom", "#FFA87CA0": "Usu Pink", "#FF8C9C76": "Usuao Blue", "#FFF2666C": "Usubeni Red", "#FFFCA474": "Usugaki Persimmon", "#FFFEA464": "Usuk\u014d", "#FF8DB255": "Usumoegi Green", "#FFA58F7B": "Utaupeia", "#FF32B6C7": "Utopia Beckons", "#FFB5A597": "Utterly Beige", "#FFA8C6DE": "Utterly Blue", "#FFF5C8D8": "Utterly Pink", "#FF0098C8": "UV Light", "#FFEFD5CF": "Va Va Bloom", "#FFE3B34C": "Va Va Voom", "#FFD1D991": "Vacation Island", "#FFFDE882": "Vacherin Cheese", "#FFAA8877": "Vagabond", "#FFD1C5C4": "Vaguely Mauve", "#FFDBE1EF": "Vaguely Violet", "#FFD4574E": "Valencia Variant", "#FF837048": "Valencia Moss", "#FFA53A4E": "Valentine", "#FFBA789E": "Valentine Heart", "#FFBA0728": "Valentine Lava", "#FF9B233B": "Valentine Red", "#FFA63864": "Valentine\u2019s Day", "#FFB63364": "Valentine\u2019s Kiss", "#FFB64476": "Valentino Variant", "#FF382C38": "Valentino Nero", "#FF9F7A93": "Valerian", "#FFFDE6E7": "Valerie", "#FF2A2B41": "Valhalla Variant", "#FFF2EDE7": "Valhallan Blizzard", "#FFBC3C2C": "Valiant Poppy", "#FF3E4371": "Valiant Violet", "#FF80502B": "Valise", "#FFEECC22": "Valkyrie", "#FF35709D": "Vallarta Blue", "#FF848D85": "Valley Floor", "#FFFFDD9D": "Valley Flower", "#FF848A83": "Valley Hills", "#FFC9D5CB": "Valley Mist", "#FFFF8A4A": "Valley of Fire", "#FF2D7E96": "Valley of Glaciers", "#FFD1E1E4": "Valley of Tears", "#FFB0C376": "Valley View", "#FF8A763D": "Valley Vineyards", "#FF79C9D1": "Valonia", "#FFA3BCDB": "Valour", "#FFCC2255": "Vampire Fangs", "#FF9B0F11": "Vampire Fiction", "#FF610507": "Vampire Hunter", "#FFDD0077": "Vampire Love Story", "#FFDD4132": "Vampire Red", "#FFCC1100": "Vampire State Building", "#FF9B2848": "Vampirella", "#FFCC0066": "Vampiric Bloodlust", "#FF5C0C0C": "Vampiric Council", "#FFBFB6AA": "Vampiric Shadow", "#FF523936": "Van Cleef Variant", "#FFFAF7EB": "Van de Cane", "#FFABDDF1": "Van Gogh Blue", "#FF65CE95": "Van Gogh Green", "#FF759465": "Van Gogh Olives", "#FF00A3E0": "Vanadyl Blue", "#FFD8CBB3": "Vanderbilt Beach", "#FFABDEE4": "Vandermint", "#FF7B5349": "Vandyck Brown", "#FF362C1D": "Vanilla Bean Brown", "#FFFCEDE4": "Vanilla Blush", "#FFFCF0CA": "Vanilla Cake", "#FFF8E3AB": "Vanilla Cream", "#FFF3E0BE": "Vanilla Custard", "#FFF5E8D5": "Vanilla Delight", "#FFFFFFEB": "Vanilla Drop", "#FFE9DFCF": "Vanilla Flower", "#FFFFD2AB": "Vanilla Fran\u00e7aise", "#FFFDE9C5": "Vanilla Frost", "#FFFDF2D1": "Vanilla Ice Variant", "#FFFFE6B3": "Vanilla Ice Cream", "#FFC9DAE2": "Vanilla Ice Smoke", "#FFDAC2AA": "Vanilla Iced Coffee", "#FFE6E0CC": "Vanilla Love", "#FFF1ECE2": "Vanilla Milkshake", "#FFEBDBC8": "Vanilla Mocha", "#FFF3E7D3": "Vanilla Paste", "#FFFAF3DD": "Vanilla Powder", "#FFF7E26B": "Vanilla Pudding", "#FFCBC8C2": "Vanilla Quake", "#FFCCB69B": "Vanilla Seed", "#FFFFFBF0": "Vanilla Shake", "#FFDED5CC": "Vanilla Steam", "#FFFFE6E7": "Vanilla Strawberry", "#FFF1E8DC": "Vanilla Sugar", "#FFF1E9DD": "Vanilla Tan", "#FFF3EAD2": "Vanilla Wafer", "#FFF6EEE5": "Vanilla White", "#FFF2E3CA": "Vanillin", "#FF331155": "Vanishing", "#FFCFDFEF": "Vanishing Blue", "#FF990088": "Vanishing Night", "#FFDDEEDD": "Vanishing Point", "#FF5692B2": "Vanity", "#FFE6CCDD": "Vanity Pink", "#FF000100": "Vantablack", "#FFE8E8D7": "Vape Smoke", "#FFCBF4F8": "Vaporised", "#FFD8D6CE": "Vaporous Grey", "#FFFF66EE": "Vaporwave", "#FF22DDFF": "Vaporwave Blue", "#FF99EEBB": "Vaporwave Pool", "#FFBEBDBD": "Vapour Blue", "#FFF5EEDF": "Vapour Trail", "#FF855F43": "Vaquero Boots", "#FFFDEFD3": "Varden Variant", "#FF747D5A": "Variegated Frond", "#FFE6DCCC": "Varnished Ivory", "#FFC9BDB8": "Vast", "#FFC2B197": "Vast Desert", "#FFD2C595": "Vast Escape", "#FFA9C9D7": "Vast Sky", "#FFAA55FF": "Vega Violet", "#FF22BB88": "Vegan", "#FF006C47": "Vegan Green", "#FF22BB55": "Vegan Mastermind", "#FFAA9911": "Vegan Villain", "#FF316738": "Vegas Green", "#FF26538D": "Vegeta Blue", "#FF8B8C40": "Vegetable Garden", "#FF22AA00": "Vegetarian", "#FF78945A": "Vegetarian Veteran", "#FFCCCC99": "Vegetarian Vulture", "#FF5CCD97": "Vegetation", "#FF4C433D": "Vehicle Body Grey", "#FF784F50": "Veil of Cinder", "#FFDAD8C9": "Veil of Dusk", "#FF80B690": "Veiled Chameleon", "#FFB2B0BD": "Veiled Delight", "#FFF9DFD7": "Veiled Pink", "#FFF7CDC8": "Veiled Rose", "#FFCFD5D7": "Veiled Spotlight", "#FFF6EDB6": "Veiled Treasure", "#FF5B5962": "Veiled Vinyl", "#FFB19BB0": "Veiled Violet", "#FFD4EAFF": "Veiling Waterfalls", "#FFA17D61": "Velddrif", "#FFEFE4D9": "Vellum Parchment", "#FFBAA7BF": "Velour", "#FF8E5164": "Velour Scar", "#FFD7D8C3": "Veltliner White", "#FFD6CEB9": "Velum Smoke", "#FF750851": "Velvet", "#FFD0C5B1": "Velvet Beige", "#FF5C466B": "Velvet Beret", "#FF241F20": "Velvet Black", "#FFE3D5D8": "Velvet Blush", "#FF9D253D": "Velvet Cake", "#FF623941": "Velvet Cape", "#FF656D63": "Velvet Clover", "#FF441144": "Velvet Cosmos", "#FF9291BC": "Velvet Crest", "#FFAA0066": "Velvet Cupcake", "#FF7E85A3": "Velvet Curtain", "#FFBDB0BC": "Velvet Dawn", "#FFC5ADB4": "Velvet Ears", "#FF33505E": "Velvet Evening", "#FFACAAB3": "Velvet Grey", "#FFD39ED2": "Velvet Horizon", "#FF96C193": "Velvet Leaf", "#FFBB1155": "Velvet Magic", "#FFE58D3F": "Velvet Marigold", "#FF692B57": "Velvet Mauve", "#FF6C769B": "Velvet Morning", "#FF6B1B2A": "Velvet Outbreak", "#FF573A56": "Velvet Plum", "#FF939DCC": "Velvet Robe", "#FF36526A": "Velvet Rope", "#FF7E374C": "Velvet Rose", "#FFE3DFEC": "Velvet Scarf", "#FFC5D3DD": "Velvet Sky", "#FF846C76": "Velvet Slipper", "#FF523544": "Velvet Touch", "#FF6B605A": "Velvet Umber", "#FFAB0102": "Velvet Volcano", "#FF540D6E": "Velvet Vortex", "#FF9A435D": "Velvet Wine", "#FF936064": "Velveteen Crush", "#FFA2877D": "Velvety Chestnut", "#FF794143": "Velvety Merlot", "#FFABC88C": "Venerable Verde", "#FFF7F5F3": "Venerable White", "#FF928083": "Venetian", "#FF9CB08A": "Venetian Glass", "#FFB39142": "Venetian Gold", "#FFF7EDDA": "Venetian Lace", "#FFE7CEB6": "Venetian Mask", "#FF7755FF": "Venetian Nights", "#FFD2EAD5": "Venetian Pearl", "#FFBB8E84": "Venetian Pink", "#FFE0A28D": "Venetian Plaster", "#FFEFC6E1": "Venetian Rose", "#FF949486": "Venetian Wall", "#FFF6E3A1": "Venetian Yellow", "#FF2C5778": "Venice Blue Variant", "#FF76AFB2": "Venice Escape", "#FF6BFFB3": "Venice Green", "#FFE6C591": "Venice Square", "#FFA9A52A": "Venom", "#FF01FF01": "Venom Dart", "#FF607038": "Venom Wyrm", "#FF66FF22": "Venomous Green", "#FFC6EC7A": "Venomous Sting", "#FF3F3033": "Venous Blood Red", "#FFCDE6E8": "Ventilated", "#FF7381B3": "Venture Violet", "#FFEED053": "Venus Variant", "#FF8F7974": "Venus Deva", "#FF9EA6CF": "Venus Flower", "#FF94B44C": "Venus Flytrap", "#FF5F606E": "Venus Mist", "#FFF0E5E5": "Venus Pink", "#FF85A4A2": "Venus Teal", "#FF7A6DC0": "Venus Violet", "#FF71384C": "Venusian", "#FF61A9A5": "Veranda", "#FF66B6B0": "Veranda Blue", "#FF9EB1AF": "Veranda Charm", "#FFAF9968": "Veranda Gold", "#FF8E977E": "Veranda Green", "#FFCCB994": "Veranda Hills", "#FF9DA4BE": "Veranda Iris", "#FFE4B773": "Veranda Yellow", "#FFF1DFDF": "Verbena", "#FF847E35": "Verdant", "#FF5AD33E": "Verdant Fields", "#FF28615D": "Verdant Forest", "#FF167D60": "Verdant Green", "#FF84A97C": "Verdant Haven", "#FF4E5A4A": "Verdant Hush", "#FF817C4A": "Verdant Leaf", "#FF62C46C": "Verdant Oasis", "#FF90A197": "Verdant Retreat", "#FF7F9E5B": "Verdant Serenade", "#FF0E7C36": "Verdant Vale", "#FF75794A": "Verdant Views", "#FF7FB383": "Verde", "#FF877459": "Verde Marr\u00f3n", "#FFEDF5E7": "Verde Pastel", "#FFA7AD8D": "Verde Tortuga", "#FF758000": "Verde Tropa", "#FF81A595": "Verdigreen", "#FF62BE77": "Verdigris Coloured", "#FF62603E": "Verdigris Fonc\u00e9", "#FF61AC86": "Verdigris Green", "#FF558367": "Verdigris Roundhead", "#FF00BBAA": "Verditer", "#FF55AABB": "Verditer Blue", "#FF48531A": "Verdun Green Variant", "#FF937496": "Veri Berri", "#FF232324": "Verified Black", "#FF00844B": "Veritably Verdant", "#FF2B7CAF": "Vermeer Blue", "#FFDABE82": "Vermicelles", "#FFD1B791": "Vermicelli", "#FFF4320C": "Vermilion Tint", "#FFF24433": "Vermilion Bird", "#FFE34244": "Vermilion Cinnabar", "#FF474230": "Vermilion Green", "#FFF9603B": "Vermilion Orange", "#FFB5493A": "Vermilion Red", "#FFD1062B": "Vermilion Scarlet", "#FF973A36": "Vermilion Seabass", "#FFD46036": "Vermilion Waxcap", "#FF8F7303": "Vermin Brown", "#FF55CC11": "Verminal", "#FFA16954": "Verminlord Hide", "#FFF8F5E8": "Vermont Cream", "#FF48535A": "Vermont Slate", "#FFE9D3BA": "Verona Beach", "#FFECBFA8": "Veronese Peach", "#FFA020FF": "Veronica", "#FF7E3075": "Veronica Purple", "#FFACDFAD": "Vers de Terre", "#FFC4B0AD": "Versailles Rose", "#FFC1B6AB": "Versatile Taupe", "#FF18880D": "Verse Green", "#FF4A615C": "Vert Pierre", "#FF990055": "Vertigo Cherry", "#FFFCEDD8": "Verve", "#FF944F80": "Verve Violet", "#FFBB3381": "Very Berry", "#FF34363E": "Very Black", "#FF33365B": "Very Blue", "#FF664411": "Very Coffee", "#FF927288": "Very Grape", "#FF3A4859": "Very Navy", "#FFF3D19F": "Vespa Yellow", "#FF0011CC": "Vesper", "#FF99A0B2": "Vesper Violet", "#FFCDC8BF": "Vessel", "#FF8A8B8F": "Vessel Grey", "#FF937899": "Vestige", "#FF879860": "Vesuvian Green", "#FFA28A9F": "Vesuvian Violet", "#FFA85533": "Vesuvius Variant", "#FF2E58E8": "Veteran\u2019s Day Blue", "#FF807D6F": "Vetiver", "#FFC1BBB1": "Viaduct", "#FFD9D140": "Viameter", "#FFFFD44D": "Vibrant", "#FFD1902E": "Vibrant Amber", "#FFEBE541": "Vibrant Arsenic", "#FF0339F8": "Vibrant Blue", "#FF0ADD08": "Vibrant Green", "#FFFFBD31": "Vibrant Honey", "#FF00FFE5": "Vibrant Mint", "#FFFF6216": "Vibrant Orange", "#FF804B81": "Vibrant Orchid", "#FFAD03DE": "Vibrant Purple", "#FFC24C6A": "Vibrant Red", "#FF88D6DC": "Vibrant Soft Blue", "#FFBB0088": "Vibrant Velvet", "#FF4B373A": "Vibrant Vine", "#FF9400D4": "Vibrant Violet", "#FF6C6068": "Vibrant Vision", "#FFEAEDEB": "Vibrant White", "#FFFFDA29": "Vibrant Yellow", "#FFC7FFFF": "VIC 20 Blue", "#FFFFFFB2": "VIC 20 Creme", "#FF94E089": "VIC 20 Green", "#FFEA9FF6": "VIC 20 Pink", "#FF87D6DD": "VIC 20 Sky", "#FF664E62": "Vicarious Violet", "#FFEE00DD": "Vice City", "#FF8F509D": "Vicious Violet", "#FF564985": "Victoria Variant", "#FF0853A7": "Victoria Blue", "#FF006A4D": "Victoria Green", "#FF007755": "Victoria Peak", "#FF6A3C3A": "Victoria Red", "#FF988F97": "Victorian", "#FFD4C5CA": "Victorian Cottage", "#FFC38B36": "Victorian Crown", "#FF558E4C": "Victorian Garden", "#FFA2783B": "Victorian Gold", "#FF00B191": "Victorian Greenhouse", "#FF6D657E": "Victorian Iris", "#FFEFE1CD": "Victorian Lace", "#FFB68B88": "Victorian Mauve", "#FF104A65": "Victorian Peacock", "#FFF1E3D8": "Victorian Pearl", "#FF828388": "Victorian Pewter", "#FF8E6278": "Victorian Plum", "#FF966B6F": "Victorian Rose", "#FFD28085": "Victorian Rouge", "#FF6C7773": "Victorian Tapestry", "#FFAE6AA1": "Victorian Valentine", "#FFB079A7": "Victorian Violet", "#FFD6B2AD": "Victoriana", "#FF3A405A": "Victory Blue", "#FF92ABD8": "Victory Lake", "#FFA1DDD4": "Vidalia", "#FFD2A88F": "Vienna Beige", "#FFF7EFEF": "Vienna Dawn", "#FFE9D9D4": "Vienna Lace", "#FF330022": "Vienna Roast", "#FFFED1BD": "Vienna Sausage", "#FF8C8185": "Viennese", "#FF4278AF": "Viennese Blue", "#FFEEC172": "Vietnamese Lantern", "#FF81796F": "Vigilant", "#FF645681": "Vigorous Violet", "#FF8B9D30": "Vigorous Wasabi", "#FF4DB1C8": "Viking Variant", "#FF757266": "Viking Castle", "#FFCABAE0": "Viking Diva", "#FF8FCDB0": "Vile Green", "#FF789695": "Villa Blue", "#FFCCC4B4": "Villa Grey", "#FF817362": "Villa Oliva", "#FFEFEAE1": "Villa White", "#FFAB9769": "Village Crier", "#FF7E867C": "Village Green", "#FF825F40": "Village Lane", "#FF7B6F61": "Village Square", "#FF728F66": "Villandry", "#FFB47463": "Vin Cuit", "#FF955264": "Vin Rouge Variant", "#FFF59994": "Vinaceous", "#FFF48B8B": "Vinaceous Cinnamon", "#FFC74300": "Vinaceous Tawny", "#FFEFDAAE": "Vinaigrette", "#FFACB3AE": "Vinalhaven", "#FF5778A7": "Vinca", "#FF45584C": "Vinca & Vine", "#FF483743": "Vincotto", "#FFAE7579": "Vindaloo", "#FF338544": "Vine", "#FF4D5F4F": "Vine Leaf", "#FF819E84": "Vineyard", "#FFEE4455": "Vineyard Autumn", "#FF5F7355": "Vineyard Green", "#FF777146": "Vineyard View", "#FF684047": "Vineyard Wine", "#FFB31A38": "Vinho do Porto", "#FF4B7378": "Vining Ivy", "#FF4C1C24": "Vino Tinto", "#FF847592": "Vintage", "#FFDFE1CC": "Vintage Beige", "#FFC0B0D0": "Vintage Bloom", "#FF87B8B5": "Vintage Blue", "#FF78726E": "Vintage Boots", "#FF966B34": "Vintage Caramel", "#FFC7B0A7": "Vintage Charm", "#FFA3B22E": "Vintage Chartreuse", "#FF9D5F46": "Vintage Copper", "#FFD68C76": "Vintage Coral", "#FFD8CEB9": "Vintage Ephemera", "#FF4F4D48": "Vintage Frame", "#FFCBD8B9": "Vintage Glass", "#FFB79E78": "Vintage Gold", "#FF6F636D": "Vintage Grape", "#FF505D74": "Vintage Indigo", "#FF958B80": "Vintage Khaki", "#FFF1E7D2": "Vintage Lace", "#FFB07B40": "Vintage Lincoln", "#FFE3DCCA": "Vintage Linen", "#FFBAAFAC": "Vintage Mauve", "#FF763D4B": "Vintage Merlot", "#FFFFB05F": "Vintage Orange", "#FFFDCFB0": "Vintage Peach", "#FF675D62": "Vintage Plum", "#FFF2EDEC": "Vintage Porcelain", "#FFA66C47": "Vintage Pottery", "#FF9E3641": "Vintage Red", "#FF9097B4": "Vintage Ribbon", "#FFCDBFB9": "Vintage Taupe", "#FF669699": "Vintage Teal", "#FF485169": "Vintage Velvet", "#FF94B2A6": "Vintage Vessel", "#FF888F4F": "Vintage Vibe", "#FFE59DAC": "Vintage Victorian", "#FF634F62": "Vintage Violet", "#FFF4EFE4": "Vintage White", "#FF65344E": "Vintage Wine", "#FF72491E": "Vintage Wood", "#FF68546A": "Vintner", "#FF966EBD": "Viola Variant", "#FF2F2A41": "Viola Black", "#FF8C6897": "Viola Grey", "#FFC6C8D0": "Viola Ice Grey", "#FFB9A5BD": "Viola Sororia", "#FFBF8FC4": "Violaceous", "#FF881188": "Violaceous Greti", "#FF9A0EEA": "Violet Variant", "#FF838BA4": "Violet Aura", "#FFBAB3CB": "Violet Beauty", "#FF49434A": "Violet Black", "#FF510AC9": "Violet Blue", "#FFB9B1C8": "Violet Bouquet", "#FFDCDCE5": "Violet Breeze", "#FF531745": "Violet Carmine", "#FF8F7DA5": "Violet Chalk", "#FFEFECEF": "Violet Clues", "#FFD8D3E6": "Violet Crush", "#FFA89B9C": "Violet Dawn", "#FFB59C9C": "Violet Dusk", "#FFDFDEE5": "Violet Echo", "#FFA387AC": "Violet Eclipse", "#FFE6E5E6": "Violet Essence", "#FF65677A": "Violet Evening", "#FFDEE2EC": "Violet Extract", "#FFBA97A9": "Violet Fantasy", "#FFA66DA1": "Violet Femmes", "#FFB8A4C8": "Violet Fields", "#FF926EAE": "Violet Frog", "#FFC4C0E9": "Violet Gems", "#FF4422EE": "Violet Glow", "#FF675B72": "Violet Haze", "#FFCDB7FA": "Violet Heaven", "#FF330099": "Violet Hickey", "#FFE5E2E7": "Violet Hush", "#FFC0A9AD": "Violet Ice", "#FF482D67": "Violet Indigo", "#FF4D4456": "Violet Intense", "#FFF0A0D1": "Violet Kiss", "#FF64338B": "Violet Magician", "#FF644982": "Violet Majesty", "#FFDACCDE": "Violet Mist", "#FFACA8CD": "Violet Mix", "#FFCA7988": "Violet Orchid", "#FF927B97": "Violet Persuasion", "#FFFB5FFC": "Violet Pink", "#FF8601BF": "Violet Poison", "#FF60394D": "Violet Posy", "#FFC7CCD8": "Violet Powder", "#FF3A2F52": "Violet Purple Variant", "#FF8B4963": "Violet Quartz", "#FFA50055": "Violet Red Variant", "#FFBCC6DF": "Violet Scent Soft Blue", "#FF4D4860": "Violet Shadow", "#FF5C619D": "Violet Storm", "#FFC7C5DC": "Violet Sweet Pea", "#FF9E91C3": "Violet Tulip", "#FFBA86B5": "Violet Tulle", "#FFDAAFD1": "Violet Vamp", "#FFE5DAE1": "Violet Vapour", "#FF898CA3": "Violet Verbena", "#FF898098": "Violet Vibes", "#FFD8E0EA": "Violet Vignette", "#FFB7BDD1": "Violet Vision", "#FFB09F9E": "Violet Vista", "#FF883377": "Violet Vixen", "#FFE9E1E8": "Violet Vogue", "#FF1A161D": "Violet Void", "#FFA669DB": "Violet Voltage", "#FFD2D6E6": "Violet Water", "#FF833E82": "Violet Webcap", "#FFDAD6DF": "Violet Whimsey", "#FFB48BB8": "Violet Whimsy", "#FFE2E3E9": "Violet White", "#FFC8D4E4": "Violet Wisp", "#FFACA7CB": "Violeta Silvestre", "#FF5A226F": "Violethargic", "#FF7487C6": "Violets Are Blue", "#FFAC6B98": "Violetta", "#FF882055": "Violettuce", "#FF674403": "Violin Brown", "#FF008F3C": "Viper Green", "#FFE2DCAB": "Virgin Olive Oil", "#FFECBDB0": "Virgin Peach", "#FFB7C3D7": "Virginia Blue", "#FF538F4E": "Virgo Green Goddess", "#FF99CC00": "Viric Green", "#FF1E9167": "Viridian Variant", "#FFBCD7D4": "Viridian Green Variant", "#FFC8E0AB": "Viridine Green", "#FF00846D": "Viridis", "#FFFE0215": "Virtual Boy", "#FF8AA56E": "Virtual Forest", "#FFC1EE13": "Virtual Golf", "#FFCF184B": "Virtual Pink", "#FF8A7A6A": "Virtual Taupe", "#FF66537F": "Virtual Violet", "#FF5D5558": "Virtuoso", "#FF9F7BA9": "Virtuous", "#FFB7B0BF": "Virtuous Violet", "#FFF9E496": "Vis Vis Variant", "#FFD2CCE5": "Vision", "#FFDFD3CB": "Vision of Light", "#FF9B94C2": "Vision Quest", "#FF83477D": "Visiona Red", "#FFF6E0A9": "Visionary", "#FFCBACAB": "Vista", "#FF97D5B3": "Vista Blue Variant", "#FFE3DFD9": "Vista White Variant", "#FF5C2C45": "Vistoris Lake", "#FF138859": "Vital Green", "#FFEDE0C5": "Vital Yellow", "#FF8F9B5B": "Vitality", "#FF2AAA45": "Vitalize", "#FFE3AC72": "Viva Gold", "#FFB39953": "Viva Las Vegas", "#FFA0488C": "Viva Magenta", "#FFA7295F": "Vivacious", "#FFDC89A8": "Vivacious Pink", "#FF804665": "Vivacious Violet", "#FFEF3939": "Vivaldi Red", "#FF995468": "Vive L\u2019amour", "#FF97BEE2": "Vive le Bleu", "#FFAED1E8": "Vive le Vent", "#FFCC9900": "Vivid Amber", "#FF152EFF": "Vivid Blue", "#FF00AAEE": "Vivid Cerulean", "#FFCC0033": "Vivid Crimson", "#FFB13AAD": "Vivid Fuchsia", "#FF2FEF10": "Vivid Green", "#FFA6D608": "Vivid Lime Green", "#FF00CC33": "Vivid Malachite", "#FFB80CE3": "Vivid Mulberry", "#FFFF5F00": "Vivid Orange", "#FFFFA102": "Vivid Orange Peel", "#FFCC00FF": "Vivid Orchid", "#FF9900FA": "Vivid Purple", "#FFFF006C": "Vivid Raspberry", "#FFF70D1A": "Vivid Red", "#FFDF6124": "Vivid Red Tangelo", "#FF87C95F": "Vivid Spring", "#FFF07427": "Vivid Tangelo", "#FFE56024": "Vivid Vermilion", "#FFA4407E": "Vivid Viola", "#FF5E4B62": "Vivid Vision", "#FFFFE302": "Vivid Yellow", "#FF573D37": "Vixen", "#FF7B9E98": "Vizcaya", "#FF47644B": "Vizcaya Palm", "#FFBFC0EE": "Vodka", "#FF050D25": "Void", "#FF41373A": "Void Wellness Coach", "#FFAF8BA8": "Voila!", "#FFA55749": "Volcanic", "#FF6F7678": "Volcanic Ash", "#FFE15835": "Volcanic Blast", "#FF72453A": "Volcanic Brick", "#FF615C60": "Volcanic Glass", "#FF605244": "Volcanic Island", "#FF6B6965": "Volcanic Rock", "#FF404048": "Volcanic Sand", "#FF45433B": "Volcanic Stone Green", "#FFBE462F": "Volcanic Unrest", "#FF4E2728": "Volcano", "#FF2D135F": "Voldemort", "#FF3B4956": "Voltage", "#FF673033": "Voluptuous", "#FF7711DD": "Voluptuous Violet", "#FF445A5E": "Volute", "#FF443240": "Voodoo Variant", "#FF824D8F": "Voodoo Violet", "#FFFEEEED": "Voracious White", "#FF83769C": "Voxatron Purple", "#FF719CA4": "Voyage", "#FF4D5062": "Voyager", "#FF9A937F": "Voysey Grey", "#FF36383C": "Vulcan Variant", "#FF5F3E42": "Vulcan Burgundy", "#FFE6390D": "Vulcan Fire", "#FF897F79": "Vulcan Mud", "#FF424443": "Vulcanised", "#FFC8C8B5": "Wabi-Sabi", "#FFEEAACC": "Waddles Pink", "#FFD4BBB1": "Wafer Variant", "#FFE2C779": "Waffle Cone", "#FFCDBDBA": "Wafting Grey", "#FF6D4D27": "Wagon Train", "#FFC2B79E": "Wagon Wheel", "#FF272D4E": "Wahoo", "#FF5B6E91": "Waikawa Grey", "#FF218BA0": "Waikiki", "#FF004411": "Wailing Woods", "#FF4CA2D9": "Waimea Blue", "#FF9C9D85": "Wainscot Green", "#FF4C4E31": "Waiouru Variant", "#FF654BC9": "Waiporoporo Purple", "#FF9D9D9D": "Waiting", "#FF00656E": "Wakame Green", "#FF6B9362": "Wakatake Green", "#FFF6D559": "Wake Me Up", "#FF295468": "Wakefield", "#FF789BB6": "Walden Pond", "#FF88BB11": "Walk in the Park", "#FF3BB08F": "Walk in the Woods", "#FF496568": "Walk Me Home", "#FF3D87BB": "Walker Lake", "#FFFAF5FA": "Walkie Chalkie", "#FF849B63": "Walking Dead", "#FFFCFC9D": "Walking on Sunshine", "#FFA2785D": "Walking Stick", "#FFA3999C": "Walkway", "#FFABAE86": "Wall Green", "#FF656D73": "Wall Street", "#FF11CC44": "Walled Garden", "#FF9B5953": "Walleye", "#FFA0848A": "Wallflower", "#FFE4E3E6": "Wallflower White", "#FFC6BDBF": "Wallis", "#FFE9EDF1": "Walls of Santorini", "#FF4C0400": "Walnut Brown", "#FFF5D8B2": "Walnut Cream", "#FF5C5644": "Walnut Grove", "#FF5D5242": "Walnut Hull", "#FFFFF0CF": "Walnut Milkies", "#FFEECB88": "Walnut Oil", "#FFAA8344": "Walnut Shell", "#FFA68B6E": "Walnut Shell Brown", "#FF774E37": "Walnut Wood", "#FF999B9B": "Walrus", "#FFC8D9DD": "Wan Blue", "#FFE4E2DC": "Wan White", "#FF5E5648": "Wanderer", "#FFCDB573": "Wandering", "#FF73A4C6": "Wandering River", "#FF876D5E": "Wandering Road", "#FFA6A897": "Wandering Willow", "#FF426267": "Wanderlust", "#FF643530": "War God", "#FFDC571D": "War Paint Red", "#FFB50038": "Warlock Red", "#FFBA0033": "Warlord", "#FF654740": "Warm Air of Debonair", "#FFCDB49F": "Warm Alpaca", "#FFCBB68F": "Warm and Toasty", "#FFFFB865": "Warm Apricot", "#FFCFC9C7": "Warm Ashes", "#FF9C9395": "Warm Asphalt", "#FF3B1F23": "Warm Balaclavas Are Forever", "#FFE3CDAC": "Warm Biscuits", "#FF4B57DB": "Warm Blue", "#FFF9E6D3": "Warm Bread", "#FFA1BEC1": "Warm Breezes", "#FF964E02": "Warm Brown", "#FF604840": "Warm Brownie", "#FFD8BFA2": "Warm Buff", "#FFE6D5BA": "Warm Buttercream", "#FFD0B082": "Warm Butterscotch", "#FFBF6A52": "Warm Cider", "#FFF9D09C": "Warm Cocoon", "#FFA88168": "Warm Cognac", "#FFB38A82": "Warm Comfort", "#FFB97254": "Warm Copper", "#FFC36C2D": "Warm Cream Spirit", "#FFE4CEB5": "Warm Croissant", "#FF927558": "Warm Earth", "#FF93817E": "Warm Embrace", "#FF7E8272": "Warm Eucalyptus", "#FFDED3CA": "Warm Fog", "#FFF6E2CE": "Warm Fuzzies", "#FFF1CF8A": "Warm Glow", "#FFA49E97": "Warm Granite", "#FF978A84": "Warm Grey", "#FFACA49A": "Warm Grey Flannel", "#FF736967": "Warm Haze", "#FFF6B3A7": "Warm Heart", "#FFBE9677": "Warm Hearth", "#FFC89F59": "Warm Leather", "#FFFFF9D8": "Warm Light", "#FF6D4741": "Warm Mahogany", "#FFF6F1E1": "Warm Milk", "#FFE1BE8B": "Warm Muffin", "#FFC1B19D": "Warm Neutral", "#FF8F6A50": "Warm Nutmeg", "#FFD9CEC0": "Warm Oatmeal", "#FFD8CFBA": "Warm Oats", "#FFC7B63C": "Warm Olive", "#FF4C4845": "Warm Onyx", "#FF483838": "Warm Operator\u2019s Overalls", "#FFB4ADA6": "Warm Pewter", "#FFFB5581": "Warm Pink", "#FF513938": "Warm Port", "#FF5C4E44": "Warm Pumpernickel", "#FF952E8F": "Warm Purple", "#FFD2C8B9": "Warm Putty", "#FFC5AE91": "Warm Sand", "#FFB2B1AF": "Warm Shale", "#FFDDC9B1": "Warm Shell", "#FF987744": "Warm Spice", "#FF4286BC": "Warm Spring", "#FFA79A8A": "Warm Stone", "#FFFAF6DB": "Warm Sun", "#FFF1CA95": "Warm Sunshine", "#FFAB917D": "Warm Taupe", "#FFC1775E": "Warm Terra Cotta", "#FFF3F5DC": "Warm Turbulence", "#FF9E6654": "Warm Up", "#FFA66E68": "Warm Wassail", "#FFA89A8C": "Warm Waterlogged Lab Coat", "#FF7EBBC2": "Warm Waters", "#FFEA9073": "Warm Welcome", "#FF8D894A": "Warm Wetlands", "#FFEFEBD8": "Warm White", "#FFD4EDE3": "Warm Winter", "#FFD0B55A": "Warm Woollen", "#FF5C3839": "Warmed Wine", "#FFD44B3B": "Warming Heart", "#FFE4B9A2": "Warming Peach", "#FF9F552D": "Warmth", "#FF803020": "Warmth of Teamwork", "#FFE5D5C9": "Warp & Weft", "#FFEAF2F1": "Warp Drive", "#FF6B6A74": "Warpfiend Grey", "#FF515131": "Warplock Bronze", "#FF927D7B": "Warplock Bronze Metal", "#FF168340": "Warpstone Glow", "#FFB8966E": "Warrant", "#FF6B654E": "Warren Tavern", "#FF7D685B": "Warrior", "#FFA32D48": "Warrior Queen", "#FFAFD77F": "Wasabi Variant", "#FFA9AD74": "Wasabi Green", "#FF333300": "Wasabi Nori", "#FF849137": "Wasabi Nuts", "#FFB4C79C": "Wasabi Peanut", "#FFBDB38F": "Wasabi Powder", "#FFD2CCA0": "Wasabi Zing", "#FFFAFBFD": "Wash Me", "#FF1F262A": "Washed Black", "#FF94D1DF": "Washed Blue", "#FFF3F0DA": "Washed Canvas", "#FF819DBE": "Washed Denim", "#FFE1E3D7": "Washed Dollar", "#FFCCD1C8": "Washed Green", "#FFFAE8C8": "Washed in Light", "#FFCAC2AF": "Washed Khaki", "#FFC5C0A3": "Washed Olive", "#FFDEDFCC": "Washed Sage", "#FFFFB3A7": "Washed-Out Crimson", "#FFC3D8E4": "Washing Powder Soft Blue", "#FFC2DCE3": "Washing Powder White", "#FFDCB89D": "Wassail", "#FF9C8855": "Wasteland", "#FF89C3EB": "Wasurenagusa Blue", "#FF8FBABC": "Watchet", "#FFD4F1F9": "Water", "#FF5AB5CB": "Water Baby", "#FFCFDFDD": "Water Baptism", "#FF0E87CC": "Water Blue", "#FF4999A1": "Water Carrier", "#FFEDE4CF": "Water Chestnut", "#FF355873": "Water Chi", "#FF75A7AD": "Water Cooler", "#FFE1E5DC": "Water Droplet", "#FF75B790": "Water Fern", "#FF7AC6D9": "Water Flow", "#FF77B6D5": "Water Fountain", "#FF76AFB6": "Water Glitter", "#FF81B89A": "Water Green", "#FFA0A3D2": "Water Hyacinth", "#FFE2E3EB": "Water Iris", "#FFB6ECDE": "Water Leaf Variant", "#FFDDE3D5": "Water Lily", "#FFC7D8E3": "Water Mist", "#FF6FB0BE": "Water Music", "#FF81D0DF": "Water Nymph", "#FF4F5156": "Water Ouzel", "#FF54AF9C": "Water Park", "#FFB56C60": "Water Persimmon", "#FF0083C8": "Water Raceway", "#FFB0AB80": "Water Reed", "#FF949381": "Water Scrub", "#FF65A5D5": "Water Spirit", "#FF44BBCC": "Water Sports", "#FFE5EECC": "Water Sprout", "#FFD8EBEA": "Water Squirt", "#FFA9BDB8": "Water Surface", "#FF958F88": "Water Tower", "#FFACC7E5": "Water Wash", "#FFA28566": "Water Wheel", "#FF80D5CC": "Water Wings", "#FF80D4D0": "Water Wonder", "#FF084D58": "Watercolour Blue", "#FFD6C9DE": "Watercolour Grape", "#FF96B47E": "Watercolour Green", "#FFB9D9E7": "Watercolour Sky", "#FFDBE5DB": "Watercolour White", "#FF5CCBD6": "Watercourse Variant", "#FF6E9377": "Watercress", "#FFC7C7A1": "Watercress Pesto", "#FF748C69": "Watercress Spice", "#FF3AB0A2": "Waterfall", "#FFE4EEEA": "Waterfall Mist", "#FFD4E4E5": "Waterfront", "#FF2F3F53": "Waterhen Back", "#FF436BAD": "Waterline Blue", "#FFDEE9DF": "Watermark", "#FFFD4659": "Watermelon", "#FFBF6C6E": "Watermelon Crush", "#FFC0686E": "Watermelon Gelato", "#FFF05C85": "Watermelon Juice", "#FFDFCFCA": "Watermelon Milk", "#FFFBE0E8": "Watermelon Mousse", "#FFC77690": "Watermelon Pink", "#FFE08880": "Watermelon Punch", "#FFBF4147": "Watermelon Red", "#FFE42B73": "Watermelon Sugar", "#FFEB4652": "Watermelonade", "#FFD3CCCD": "Watermill Wood", "#FFDCECE7": "Waterscape", "#FFB0CEC2": "Watershed", "#FFD2F3EB": "Waterslide", "#FFA4F4F9": "Waterspout", "#FF637FBB": "Watertown", "#FF7EB7BF": "Waterway", "#FF00718A": "Waterworld", "#FFAEBDBB": "Watery", "#FF88BFE7": "Watery Sea", "#FF74AEBA": "Watson Lake", "#FFD6CA3D": "Wattle Variant", "#FFF2CDBB": "Watusi Variant", "#FFA5CED5": "Wave", "#FFDCE9EA": "Wave Crest", "#FF9AAAB6": "Wave Goodbye", "#FF6C919F": "Wave Jumper", "#FFA0764A": "Wave of Grain", "#FFA7C9C0": "Wave Runner", "#FFCBE4E7": "Wave Splash", "#FFAFD9D3": "Wave Top", "#FF7DC4CD": "Wavelet", "#FFC7AA7C": "Waves of Grain", "#FFAEA266": "Wavy Glass", "#FF006597": "Wavy Navy", "#FFDDBB33": "Wax", "#FF00A4A6": "Wax Crayon Blue", "#FFEEB39E": "Wax Flower Variant", "#FFD8DB8B": "Wax Green", "#FFF1E6CC": "Wax Poetic", "#FFE2D5BD": "Wax Sculpture", "#FFD3B667": "Wax Way", "#FFEAE8A0": "Wax Yellow", "#FFB38241": "Waxen Moon", "#FFC0C2C0": "Waxwing", "#FFF8B500": "Waxy Corn", "#FF1188CC": "Way Beyond the Blue", "#FF00C000": "Waystone Green", "#FFD9DCD1": "Wayward Willow", "#FFDEDFE2": "Wayward Wind", "#FF99CC04": "Waywatcher Green", "#FF5E5A59": "Waza Bear", "#FFB21B00": "Wazdakka Red", "#FFFDD7D8": "We Peep Variant", "#FFE1F2DF": "Weak Green", "#FFEADEE4": "Weak Mauve", "#FFE0F0E5": "Weak Mint", "#FFFAEDE3": "Weak Orange", "#FFECDEE5": "Weak Pink", "#FFB47B27": "Weapon Bronze", "#FF9F947D": "Weather Board", "#FF593A27": "Weathered Bamboo", "#FFD2E2F2": "Weathered Blue", "#FF59504C": "Weathered Brown", "#FFEAD0A9": "Weathered Coral", "#FF988A72": "Weathered Fossil", "#FFD5C6C2": "Weathered Hide", "#FF90614A": "Weathered Leather", "#FFE4F5E1": "Weathered Mint", "#FFBABBB3": "Weathered Moss", "#FF7B9093": "Weathered Pebble", "#FFEADFE8": "Weathered Pink", "#FF867C61": "Weathered Plank", "#FFF9F4D9": "Weathered Plastic", "#FFB5745C": "Weathered Saddle", "#FFDFC0A6": "Weathered Sandstone", "#FF937F68": "Weathered Shingle", "#FFC4C5C6": "Weathered Stone", "#FFE6E3D9": "Weathered White", "#FF97774D": "Weathered Wicker", "#FFB19C86": "Weathered Wood", "#FFBDAE95": "Weatherhead", "#FF2C201A": "Weathervane", "#FFBFB18A": "Weaver\u2019s Spool", "#FF9D7F62": "Weaver\u2019s Tool", "#FF8F684B": "Webcap Brown", "#FFEDEADC": "Wedded Bliss", "#FFEDE6E9": "Wedding", "#FFEEE2C9": "Wedding Cake", "#FFE7E8E1": "Wedding Cake White", "#FFFEFEE7": "Wedding Dress", "#FFBCB6CB": "Wedding Flowers", "#FFFFFEE5": "Wedding in White", "#FFB5C1AC": "Wedding Mint", "#FFF6DFD8": "Wedding Pink", "#FFE1ECA5": "Wedge of Lime", "#FF4C6B88": "Wedgewood Variant", "#FF9FE4AA": "Weekend Gardener", "#FFE9C2AD": "Weekend Retreat", "#FF5D8727": "Weeping Fig", "#FFB3B17B": "Weeping Willow", "#FFD7DDEC": "Weeping Wisteria", "#FF5A06EF": "W\u00e8i L\u00e1n Azure", "#FF9C8D7D": "Weimaraner", "#FF3AE57F": "Weird Green", "#FFB3833B": "Weissbier", "#FFE4E1D6": "Weisswurst White", "#FFC09C6A": "Welcome Home", "#FFD4C6A7": "Welcome Walkway", "#FFF3E3CA": "Welcome White", "#FFEEAA00": "Welcoming Wasp", "#FFF1E3CA": "Welcoming White", "#FF6F6F6D": "Welded Iron", "#FF7C98AB": "Weldon Blue", "#FF00888B": "Well Blue", "#FF7E633F": "Well Read Variant", "#FF63ADB9": "Well Water", "#FF564537": "Well-Bred Brown", "#FF54606A": "Wellerman", "#FF4F6364": "Wellington", "#FFB9B5A4": "Wells Grey", "#FF26AD8D": "Wells of Wonder", "#FF22BB66": "Welsh Onion", "#FF3E2A2C": "Wenge Black", "#FF345362": "Wentworth", "#FF7E6152": "Werewolf Fur", "#FF5C512F": "West Coast Variant", "#FFE5823A": "West Side Variant", "#FF586D77": "West Winds", "#FFD4CFC5": "Westar Variant", "#FFA49D70": "Westcar Papyrus", "#FF797978": "Westchester Grey", "#FFE0D5CC": "Westchester White", "#FF5D3B31": "Western Brown", "#FF8B6A65": "Western Clay", "#FFBA816E": "Western Pink", "#FF8D5E41": "Western Pursuit", "#FF9B6959": "Western Red", "#FF8D876D": "Western Reserve", "#FFB28B80": "Western Ridge", "#FFBEAA99": "Western Sandstone", "#FFFADCA7": "Western Sky", "#FFDAA36F": "Western Sunrise", "#FFCDBB8D": "Western Wear", "#FFFCD450": "Westfall Yellow", "#FF2A4442": "Westhaven", "#FFF3EEE3": "Westhighland White", "#FF9C7C5B": "Westminster", "#FFA3623B": "Wet Adobe", "#FF5A6457": "Wet Aloeswood", "#FF989CAB": "Wet Asphalt", "#FF89877F": "Wet Cement", "#FF222023": "Wet Charcoal", "#FFA49690": "Wet Clay", "#FF353838": "Wet Concrete", "#FFD1584C": "Wet Coral", "#FF000B00": "Wet Crow\u2019s Wing", "#FFA36C64": "Wet Desert Clay", "#FF162825": "Wet Foliage", "#FF001144": "Wet Latex", "#FFB9A023": "Wet Leaf", "#FF9E9F97": "Wet Pavement", "#FFE0816F": "Wet Pottery Clay", "#FF897870": "Wet River Rock", "#FFAE8F60": "Wet Sand", "#FF786D5F": "Wet Sandstone", "#FF8A96A0": "Wet Seal", "#FF50493C": "Wet Suit", "#FF907E6C": "Wet Taupe", "#FF929090": "Wet Weather", "#FF706B71": "Wetbar", "#FF7D949E": "Wethers Field", "#FF859488": "Wethersfield Moss", "#FFC1A98F": "Wetland Clay", "#FFA49F80": "Wetland Stone", "#FF71736A": "Wetlands", "#FF372418": "Wetlands Swamp", "#FF7C8181": "Whale", "#FFE5E7E4": "Whale Bone", "#FF59676B": "Whale Grey", "#FF607C8E": "Whale Shark", "#FF86878C": "Whale Tail", "#FFA5A495": "Whale Watching", "#FFC7D3D5": "Whale\u2019s Mouth", "#FF115A82": "Whale\u2019s Tale", "#FF2E7176": "Whaling Waters", "#FF65737E": "Wharf View", "#FFBBBCAE": "Wharf Wind", "#FF441122": "What We Do in the Shadows", "#FFFBDD7E": "Wheat Variant", "#FFBF923B": "Wheat Beer", "#FFDFBB7E": "Wheat Bread", "#FFDDD6CA": "Wheat Flour White", "#FFC7C088": "Wheat Grass", "#FF976B53": "Wheat Penny", "#FFE3D1C8": "Wheat Seed", "#FFDFD4C4": "Wheat Sheaf", "#FFD8B998": "Wheat Toast", "#FFA49A79": "Wheat Tortilla", "#FFAD935B": "Wheatacre", "#FFC8865E": "Wheatberry", "#FFFBEBBB": "Wheaten White", "#FFDFD7BD": "Wheatfield Variant", "#FF9E8451": "Wheatmeal", "#FFFFCD00": "Wheel of Dharma", "#FFB8BEBF": "Wheels Up!", "#FF584165": "When Blue Met Red", "#FF564375": "When Red Met Blue", "#FF585C79": "When Worlds Collide", "#FFC19851": "Where Buffalo Roam", "#FFC7CCCE": "Where There Is Smoke", "#FFDD262B": "Whero Red", "#FFD8D6C5": "Whetstone", "#FF9F6F55": "Whetstone Brown", "#FFDEE9CF": "Whiff of Green", "#FF00EBFF": "Whimsical Blue", "#FFECE4E2": "Whimsical White", "#FFED9987": "Whimsy", "#FFB0DCED": "Whimsy Blue", "#FFA09176": "Whipcord", "#FFC74547": "Whiplash", "#FFFFD299": "Whipped Apricot", "#FFF0EDD2": "Whipped Citron", "#FFEDECE7": "Whipped Coconut Cream", "#FFF2F0E7": "Whipped Cream", "#FFC7DDD6": "Whipped Mint", "#FFFACCAD": "Whipped Peach", "#FF9CCA84": "Whipped Pistachio", "#FFD8CBE0": "Whipped Plum", "#FFC9565A": "Whipped Strawberry", "#FFA1A8D5": "Whipped Violet", "#FFF2EEE0": "Whipped White", "#FFCEC1B5": "Whippet", "#FFFAF5E7": "Whipping Cream", "#FF9BA868": "Whirled Peas", "#FFE6CDCA": "Whirligig", "#FFDFD4C0": "Whirligig Geyser", "#FFA5D8CD": "Whirlpool", "#FFA7D0C5": "Whirlpool Green", "#FFE2D5D3": "Whirlwind", "#FFF6F1E2": "Whiskers", "#FFD29062": "Whiskey Variant", "#FF49463F": "Whiskey and Wine", "#FF85705F": "Whiskey Barrel", "#FFD4915D": "Whiskey Sour", "#FFC2877B": "Whisky", "#FF96745B": "Whisky Barrel", "#FF772233": "Whisky Cola", "#FFEEAA33": "Whisky Sour", "#FFEFE6E6": "Whisper Variant", "#FFE5E8F2": "Whisper Blue", "#FFCBEDE5": "Whisper of Grass", "#FFD4AFDA": "Whisper of Plum", "#FFCDA2AC": "Whisper of Rose", "#FFCBCECF": "Whisper of Smoke", "#FFEADBCA": "Whisper of White", "#FFD4C5B4": "Whisper Pink", "#FFC9C3B5": "Whisper Ridge", "#FFC7C0C5": "Whisper Soft", "#FFDFDED9": "Whisper Softly", "#FFFFE5B9": "Whisper Yellow", "#FF3F4855": "Whispered Secret", "#FFC8DCDC": "Whispering Blue", "#FFD7E5D8": "Whispering Frost", "#FFAC9D64": "Whispering Grasslands", "#FF536151": "Whispering Oaks", "#FFFEDCC3": "Whispering Peach", "#FFC8CAB5": "Whispering Pine", "#FFECECDA": "Whispering Rain", "#FFD8D8D4": "Whispering Smoke", "#FFE3E6DB": "Whispering Waterfall", "#FF919C81": "Whispering Willow", "#FFB7C3BF": "Whispering Winds", "#FFD6E9E6": "Whispery Breeze", "#FFC49E8F": "Whistler Rose", "#FFD7A98C": "White Acorn", "#FFEFEBE7": "White Alyssum", "#FFFEFEFE": "White as Heaven", "#FFECEABE": "White Asparagus", "#FFE4DFD0": "White Basics", "#FFE8EFEC": "White Bass", "#FFF5EFE5": "White Beach", "#FFE8D0B2": "White Bean Hummus", "#FFEBDFDD": "White Beet", "#FFE3E7E1": "White Blaze", "#FFF4ECDB": "White Blossom", "#FFCDD6DB": "White Blue", "#FFFBECD8": "White Blush", "#FFBFD0CB": "White Box", "#FFE3E0E8": "White Bud", "#FFDFDFDA": "White Bullet", "#FFB0B49B": "White Cabbage", "#FFFAECE1": "White Canvas", "#FFDBD5D1": "White Castle", "#FFF5DEC2": "White Cedar", "#FFF6F4F1": "White Chalk", "#FFE7DBDD": "White Cherry", "#FFF0E3C7": "White Chocolate", "#FFF4E8E8": "White Christmas", "#FFD6D0CC": "White City", "#FFE8E1D3": "White Clay", "#FFE8E3C9": "White Cliffs", "#FFF2F2ED": "White Cloud", "#FFE6E0D4": "White Coffee", "#FFF4F2F4": "White Convolvulus", "#FFF0D498": "White Corn", "#FFF9F8EF": "White Crest", "#FFF9EBC5": "White Currant", "#FFFDFAF1": "White Desert", "#FFEFEAE6": "White Dogwood", "#FFF5EEDE": "White Down", "#FFCECABA": "White Duck", "#FFEDEDED": "White Edgar", "#FFDEDEE5": "White Elephant", "#FFF2E9D3": "White Fence", "#FFFBF4E8": "White Fever", "#FFC8C2C0": "White Flag", "#FFF5EDE0": "White Flour", "#FFDEE6EC": "White Frost", "#FFF1EFE7": "White Fur", "#FFC9C2BD": "White Gauze", "#FFF1F1E1": "White Geranium", "#FFDDEEEE": "White Glaze", "#FFFFEEEE": "White Gloss", "#FFF0EFED": "White Glove", "#FFC8D1C4": "White Granite", "#FFBBCC99": "White Grape", "#FFFCF0DE": "White Grapefruit", "#FFD6E9CA": "White Green", "#FFE2E6D7": "White Hamburg Grapes", "#FFFDF9EF": "White Heat", "#FFE7E1D7": "White Heron", "#FFEAD8BB": "White Hot Chocolate", "#FFF3E5D1": "White Hyacinth", "#FFF9F6DD": "White Hydrangea", "#FFDFE2E7": "White Iris", "#FFD0D6A8": "White Jade", "#FFF7F4DF": "White Jasmine", "#FFDFDFDB": "White Kitten", "#FFE2E7E7": "White Lake", "#FFE1E2EB": "White Lavender", "#FFDEDEDC": "White Lie", "#FFE7E5E8": "White Lilac Variant", "#FFFAF0DB": "White Lily", "#FFEEE7DD": "White Linen Variant", "#FFE2DCD2": "White Luxe", "#FFF7F0E5": "White Luxury", "#FFF2E6DF": "White Meadow", "#FFECF3E1": "White Mecca", "#FFD1D1CF": "White Metal", "#FFEFEEE9": "White Mink", "#FFE0E7DA": "White Mint", "#FFF5F4E6": "White Mirage", "#FFE7DCCC": "White Mocha", "#FFEBEAE2": "White Moderne", "#FFD8D6C0": "White Moss", "#FFF6EDDB": "White Mountain", "#FFB9A193": "White Mouse", "#FFF8F6D8": "White Nectar", "#FFCE9F6F": "White Oak", "#FFE7E2DD": "White Opal", "#FFF5F3F5": "White Owl", "#FFF9E6DA": "White Peach", "#FFEDE1D1": "White Pearl", "#FFAE9E86": "White Pepper", "#FFF0EFEB": "White Picket Fence", "#FFDAD6CC": "White Pointer Variant", "#FFF8FBF8": "White Porcelain", "#FFC3BDAB": "White Primer", "#FFF6E8DF": "White Pudding", "#FFF8EEE7": "White Rabbit", "#FFE2E8CF": "White Radish", "#FFE5C28B": "White Raisin", "#FFD4CFB4": "White Rock Variant", "#FFF0E0DC": "White Russian", "#FFD2D4C3": "White Sage", "#FFEBEBE7": "White Sail", "#FFF5EBD8": "White Sand", "#FFE4EEEB": "White Sapphire", "#FFD9DED6": "White Sash", "#FF8C9FA1": "White Scar", "#FFD7E5EA": "White Sea", "#FFE4DBCE": "White Sesame", "#FFD1D3E0": "White Shadow", "#FFF1F0EC": "White Shoulders", "#FFEBE6D8": "White Silence", "#FFF4F5FA": "White Solid", "#FF9FBDAD": "White Spruce", "#FFFDE3B5": "White Strawberry", "#FFF1FAEA": "White Sulphur", "#FFF7F1E2": "White Swan", "#FFC5B8A8": "White Tiger", "#FFEFDBCD": "White Truffle", "#FF83CCD2": "White Ultramarine", "#FFC5DCB3": "White Vienna", "#FFEFE6D1": "White Warm Wool", "#FFEDEEEF": "White Whale", "#FFECF4DD": "White Willow", "#FFDEDBCE": "White Wisp", "#FFDED8D2": "White Woodruff", "#FFF2EFDE": "White Wool", "#FFF8EEE3": "White Zin", "#FFDEDBDE": "White-Collar", "#FFF3E8EA": "White-Red", "#FFDEE3DE": "Whitecap Foam", "#FFDBD0BC": "Whitecap Grey", "#FFFFFDFD": "Whitecap Snow", "#FF050D02": "Whiten\u2019t", "#FFDEE0D2": "Whitened Sage", "#FFFBFBFB": "Whiteout", "#FFF8F9F5": "Whitest White", "#FFF4EEE5": "Whitetail", "#FFFEFFFC": "Whitewash", "#FFCAC9C0": "Whitewash Oak", "#FFFAF2E3": "Whitewashed Fence", "#FFAEC9DC": "Whiting Cottage", "#FFB2A188": "Whitney Oaks", "#FF8B7181": "Who-Dun-It", "#FFD4AE7E": "Whole Grain", "#FFA48B73": "Whole Wheat", "#FFAAA662": "Wholemeal Cookie", "#FFBEC1CF": "Whomp Grey", "#FF9BCA47": "Wicked Green", "#FF37115C": "Wicked Purple", "#FF5B984F": "Wicked Witch", "#FF847567": "Wicker Basket", "#FFFCE4AF": "Wickerware", "#FFC19E80": "Wickerwork", "#FF4F6C8F": "Wickford Bay", "#FF416FAF": "Wide Sky", "#FFF9D9D7": "Wide-Eyed", "#FF99AAFF": "Widowmaker", "#FF874E3C": "Wiener Dog", "#FFEE9900": "Wiener Schnitzel", "#FFFEF9D7": "Wild Apple", "#FFA53783": "Wild Aster", "#FF63775A": "Wild Axolotl", "#FFEAC37E": "Wild Bamboo", "#FF6B8372": "Wild Beet Leaf", "#FF7E3A3C": "Wild Berry", "#FF795745": "Wild Bill Brown", "#FF553322": "Wild Boar", "#FF5A4747": "Wild Boysenberry", "#FF47241A": "Wild Brown", "#FF1CD3A2": "Wild Caribbean Green", "#FF916D5D": "Wild Cattail", "#FFBC5D58": "Wild Chestnut", "#FF665134": "Wild Chocolate", "#FF93A3C1": "Wild Clary", "#FF6E3C42": "Wild Cranberry", "#FF7C3239": "Wild Currant", "#FFF4D967": "Wild Daffodil", "#FF8B8C89": "Wild Dove", "#FF545989": "Wild Elderberry", "#FF38914A": "Wild Forest", "#FF986A79": "Wild Geranium", "#FF825059": "Wild Ginger", "#FF80805D": "Wild Ginseng", "#FF5E496C": "Wild Grapes", "#FF998643": "Wild Grass", "#FF95856D": "Wild Hawk", "#FF9D7B74": "Wild Hemp", "#FFCEC7AA": "Wild Hillside", "#FFEECC00": "Wild Honey", "#FF8D6747": "Wild Horses", "#FF2F2F4A": "Wild Iris", "#FF6F7142": "Wild Jungle", "#FFA47FA3": "Wild Lavender", "#FFA5C1C0": "Wild Life", "#FFBEB8CD": "Wild Lilac", "#FFC4CD4F": "Wild Lime", "#FF684944": "Wild Manzanita", "#FFFFE2C7": "Wild Maple", "#FFA96388": "Wild Mulberry", "#FF84704B": "Wild Mushroom", "#FF695649": "Wild Mustang", "#FFCAA867": "Wild Mustard Seed", "#FFECDBC3": "Wild Oats", "#FF9C8042": "Wild Olive", "#FFD979A2": "Wild Orchid", "#FFB4B6DA": "Wild Orchid Blue", "#FF6373B4": "Wild Pansy", "#FFB97A77": "Wild Party", "#FF9EA5C3": "Wild Phlox", "#FF767C6B": "Wild Pigeon", "#FF83455D": "Wild Plum", "#FFB85B57": "Wild Poppy", "#FFD6C0AA": "Wild Porcini", "#FFEBDD99": "Wild Primrose", "#FF614746": "Wild Raisin", "#FFD5BFB4": "Wild Rice Variant", "#FF447382": "Wild River", "#FFCB7D96": "Wild Rose", "#FFB5A38C": "Wild Rye", "#FF7E877D": "Wild Sage", "#FFE7E4DE": "Wild Sand Variant", "#FF8A6F45": "Wild Seaweed", "#FF7C5644": "Wild Stallion", "#FF654243": "Wild Thing", "#FF9E9FB6": "Wild Thistle", "#FF7E9C6F": "Wild Thyme", "#FF463F3C": "Wild Truffle", "#FF63209B": "Wild Violet", "#FFFC6D84": "Wild Watermelon Variant", "#FF7E5C52": "Wild West", "#FFE0E1D1": "Wild Wheat", "#FF91857C": "Wild Wilderness", "#FFBECA60": "Wild Willow Variant", "#FF686B93": "Wild Wisteria", "#FFF5EEC0": "Wildcat Grey", "#FF8F886C": "Wilderness", "#FFC2BAA8": "Wilderness Grey", "#FFFF8833": "Wildfire", "#FF927D9B": "Wildflower", "#FFFFB3B1": "Wildflower Bouquet", "#FFC69C5D": "Wildflower Honey", "#FFCCCFE2": "Wildflower Prairie", "#FF5D9865": "Wildness Mint", "#FFCDB99B": "Wildwood", "#FFAA83A4": "Wilhelminian Pink", "#FFD7D8DD": "Will o\u2019 the Wisp", "#FFDDC765": "Williams Pear Yellow", "#FF8C7A48": "Willow", "#FF59754D": "Willow Bough", "#FFDFE6CF": "Willow Brook Variant", "#FF93B881": "Willow Dyed", "#FFC3CABF": "Willow Green", "#FF817B69": "Willow Grey", "#FF69755C": "Willow Grove Variant", "#FF84C299": "Willow Hedge", "#FFA1A46D": "Willow Leaf", "#FF5B6356": "Willow Sooty Bamboo", "#FFE7E6E0": "Willow Springs", "#FF9E8F66": "Willow Tree", "#FFC8D5BB": "Willow Tree Mouse", "#FFD5DCA9": "Willow Wind", "#FF58504D": "Willow Wood", "#FFF0D29D": "Willow-Flower Yellow", "#FF929D81": "Willowbrook Manor", "#FF914681": "Willowherb", "#FFF3F2E8": "Willowside", "#FF886144": "Wilmington", "#FFBD9872": "Wilmington Tan", "#FFAB4C3D": "Wilted Brown", "#FFEEDAC9": "Wilted Leaf", "#FF626D5B": "Wimbledon", "#FFDDE3E7": "Wind Blown", "#FFB1C9DF": "Wind Blue", "#FF686C7B": "Wind Cave", "#FFDFE0E2": "Wind Chime", "#FFD5E2EE": "Wind Force", "#FFD0D8CF": "Wind Fresh White", "#FFC8DEEA": "Wind of Change", "#FFE8BABD": "Wind Rose", "#FFBFD6D9": "Wind Speed", "#FF6875B7": "Wind Star", "#FFC7DFE6": "Wind Tunnel", "#FFC5D1D8": "Wind Weaver", "#FFD5D8D7": "Windchill", "#FF84A7CE": "Windfall", "#FFBC9CA2": "Windflower", "#FF5B584C": "Windgate Hill", "#FFF5E6C9": "Windham Cream", "#FFC6BBA2": "Winding Path", "#FF62A5DF": "Windjammer", "#FFF5ECE7": "Windmill", "#FFA79B83": "Windmill Park", "#FFF0F1EC": "Windmill Wings", "#FFBCAFBB": "Window Box", "#FF989EA1": "Window Grey", "#FFE4ECDF": "Window Pane", "#FF7C8899": "Window Screen", "#FF018281": "Windows 95 Desktop", "#FF3778BF": "Windows Blue", "#FFCEBCAE": "Windrift Beige", "#FF5E6C62": "Windrock", "#FFDBD3C6": "Windrush", "#FFE0E1DA": "Winds Breath", "#FF462C77": "Windsor Variant", "#FFC4B49C": "Windsor Greige", "#FF626066": "Windsor Grey", "#FFA697A7": "Windsor Haze", "#FF545C4A": "Windsor Moss", "#FFC9AFD0": "Windsor Purple", "#FFCABBA1": "Windsor Tan", "#FFCCB490": "Windsor Toffee", "#FF9FC9E4": "Windsor Way", "#FF5F2E3D": "Windsor Wine", "#FF6D98C4": "Windstorm", "#FF91AAB8": "Windsurf", "#FF718BAE": "Windsurf Blue", "#FFD7E2DE": "Windsurfer", "#FF3A7099": "Windsurfing", "#FFD1F1F5": "Windswept", "#FFE3E4E5": "Windswept Beach", "#FFDBA480": "Windswept Canyon", "#FF9EB8BB": "Windswept Clouds", "#FFB7926B": "Windswept Leaves", "#FFC2E5E0": "Windwood Spring", "#FFBDD1D2": "Windy", "#FFAABAC6": "Windy Blue", "#FF88A3C2": "Windy City", "#FF8CB0CB": "Windy Day", "#FFB0A676": "Windy Meadow", "#FF3D604A": "Windy Pine", "#FF667F8B": "Windy Seas", "#FFE8EBE7": "Windy Sky", "#FF80013F": "Wine Variant", "#FFA33540": "Wine & Roses", "#FF772C30": "Wine and Unwind", "#FFAA5522": "Wine Barrel", "#FFD3D6C4": "Wine Bottle", "#FF254636": "Wine Bottle Green", "#FF5F3E3E": "Wine Brown", "#FF913338": "Wine Cask", "#FF70403D": "Wine Cellar", "#FF866D4C": "Wine Cork", "#FF602234": "Wine Country", "#FF96837D": "Wine Crush", "#FF673145": "Wine Dregs", "#FFE5D8E1": "Wine Frost", "#FF643B46": "Wine Goblet", "#FF941751": "Wine Grape", "#FF67334C": "Wine Gummy Red", "#FF355E4B": "Wine Leaf", "#FF864C58": "Wine Not", "#FF7B0323": "Wine Red Variant", "#FF69444F": "Wine Stain", "#FF8F7191": "Wine Stroll", "#FF492A34": "Wine Tasting", "#FF653B66": "Wine Tour", "#FFD7C485": "Wine Yellow", "#FF663366": "Wineberry", "#FF433748": "Wineshade", "#FF0065AC": "Wing Commander", "#FF5A6868": "Wing Man", "#FFEBE4E2": "Winged Victory", "#FFEFE8D7": "Wings of an Angel", "#FF9EBBDA": "Wings of Pegasus", "#FFBAD5D4": "Wingsuit Wind", "#FF7792AF": "Wink", "#FF365771": "Winner\u2019s Circle", "#FFF0DCCB": "Winners Shower", "#FF894144": "Winning Red", "#FF636653": "Winning Ticket", "#FFE0CFC2": "Winsome Beige", "#FFE7E9E4": "Winsome Grey", "#FFA7D8E1": "Winsome Hue", "#FFCCACC1": "Winsome Orchid", "#FFC28BA1": "Winsome Rose", "#FFB0A6C2": "Winter Amethyst", "#FF314747": "Winter Balsam", "#FFB8C8D3": "Winter Blizzard", "#FF582D48": "Winter Bloom", "#FFBDB5B3": "Winter Calm", "#FF8ECED8": "Winter Chill", "#FF83C7DF": "Winter Chime", "#FF45494C": "Winter Coat", "#FFBAAAA7": "Winter Cocoa", "#FF6E7A7C": "Winter Could Grey", "#FFDEDAD8": "Winter Dawn", "#FFE3E7E9": "Winter Day", "#FF9B8767": "Winter Delta", "#FFF5F1EA": "Winter Doldrums", "#FFB8B8CB": "Winter Dusk", "#FFB4E5EC": "Winter Escape", "#FF476476": "Winter Evening", "#FFBCAF9E": "Winter Feather", "#FF9CA4A5": "Winter Flannel", "#FFD6EEDD": "Winter Fresh", "#FFE4DECD": "Winter Frost", "#FFC4D2D0": "Winter Garden", "#FFEAEBE0": "Winter Glaze", "#FF48907B": "Winter Green", "#FF5E737D": "Winter Harbor", "#FFE1E6EB": "Winter Haven", "#FFCEC9C1": "Winter Haze", "#FFD0C383": "Winter Hazel Variant", "#FF798772": "Winter Hedge", "#FFDBDFE9": "Winter Ice", "#FFD8D2C7": "Winter in New York", "#FF6E878B": "Winter in Paris", "#FF5C97CF": "Winter Lake", "#FFB7FFFA": "Winter Meadow", "#FFE7FBEC": "Winter Mist", "#FFD1C295": "Winter Mood", "#FFD9D9D6": "Winter Morn", "#FFFDEBD8": "Winter Morning", "#FFA7B3B5": "Winter Morning Mist", "#FF676449": "Winter Moss", "#FFA99F97": "Winter Nap", "#FF63594B": "Winter Oak", "#FFF2FAED": "Winter Oasis", "#FFE7E3E7": "Winter Orchid", "#FF41638A": "Winter Palace", "#FF95928D": "Winter Park", "#FF8E9549": "Winter Pea Green", "#FFEBD9D0": "Winter Peach", "#FFB0B487": "Winter Pear", "#FFC7A55F": "Winter Pear Beige", "#FFAE3C3D": "Winter Poinsettia", "#FFAAA99D": "Winter Rye", "#FFAA9D80": "Winter Sage", "#FFDAC7BA": "Winter Savanna", "#FFBECEDB": "Winter Scene", "#FF303E55": "Winter Sea", "#FF4F6B79": "Winter Shadow", "#FFE3EFDD": "Winter Shamrock", "#FFA9C0CB": "Winter Sky Variant", "#FF49545C": "Winter Solstice", "#FFACB99F": "Winter Squash", "#FF4B7079": "Winter Storm", "#FFE8DADD": "Winter Sunrise", "#FFCA6636": "Winter Sunset", "#FF7FB9AE": "Winter Surf", "#FF4090A2": "Winter Time", "#FF877C6D": "Winter Twig", "#FF80A9B8": "Winter Twilight", "#FFE0E7E0": "Winter Veil", "#FFD8D5CC": "Winter Walk", "#FF21424D": "Winter Waves", "#FF3E474C": "Winter Way", "#FFF1E4DC": "Winter Wedding", "#FFDCBE97": "Winter Wheat", "#FFF5ECD2": "Winter White", "#FFC1D8AC": "Winter Willow Green", "#FFA0E6FF": "Winter Wizard", "#FFB3BFC8": "Winter Wonderland", "#FFDEECED": "Winter\u2019s Breath", "#FFB4474D": "Winterberry Frost", "#FF20F986": "Wintergreen", "#FFC6E5CA": "Wintergreen Mint", "#FF4F9E81": "Wintergreen Shadow", "#FF94D2BF": "Wintermint", "#FFBDD4DE": "Winterscape", "#FFB5AFFF": "Winterspring Lilac", "#FF786DAA": "Wintertime Mauve", "#FF8BA494": "Wintessa", "#FFFDE6D6": "Winthrop Peach", "#FFA7AFAC": "Wintry Sky", "#FF8B7180": "Wiped Out", "#FF3E8094": "Wipeout", "#FFE2E3D8": "Wisdom", "#FFCDBBA5": "Wise Owl", "#FFB6BCDF": "Wish", "#FF447F8A": "Wish Upon a Star", "#FF53786A": "Wishard", "#FFD8DDE6": "Wishful Blue", "#FFC8E2CC": "Wishful Green", "#FFFCDADF": "Wishful Thinking", "#FFF4F1E8": "Wishful White", "#FF604F5A": "Wishing Star", "#FF9A834B": "Wishing Troll", "#FFD0D1C1": "Wishing Well", "#FFEBDEDB": "Wishy-Washy Beige", "#FFC6E0E1": "Wishy-Washy Blue", "#FFD1C2C2": "Wishy-Washy Brown", "#FFDFEAE1": "Wishy-Washy Green", "#FFDEEDE4": "Wishy-Washy Lichen", "#FFF5DFE7": "Wishy-Washy Lilies", "#FFEEF5DB": "Wishy-Washy Lime", "#FFEDDDE4": "Wishy-Washy Mauve", "#FFDDE2D9": "Wishy-Washy Mint", "#FFF0DEE7": "Wishy-Washy Pink", "#FFE1DADD": "Wishy-Washy Red", "#FFE9E9D5": "Wishy-Washy Yellow", "#FFF2A599": "Wisley Pink", "#FFA9BADD": "Wisp", "#FFC2DCB4": "Wisp Green", "#FFD9C6B2": "Wisp of Cocoa", "#FFE5E7E9": "Wisp of Smoke", "#FFF9E8E2": "Wisp Pink Variant", "#FFC6AEAA": "Wispy Mauve", "#FFBCC7A4": "Wispy Mint", "#FFF3EBEA": "Wispy Pink", "#FFD8DCE1": "Wispy Skies", "#FFFFC196": "Wispy White", "#FFA87DC2": "Wisteria Variant", "#FF84A2D4": "Wisteria Blue", "#FFBBBCDE": "Wisteria Fragrance", "#FFA6A8C5": "Wisteria Light Soft Blue", "#FFE6C8FF": "Wisteria Powder", "#FF875F9A": "Wisteria Purple", "#FFCED9DC": "Wisteria Snow", "#FFB2ADBF": "Wisteria Trellis", "#FFE4CADC": "Wisteria Whisper", "#FFF7C114": "Wisteria Yellow", "#FFB2A7CC": "Wisteria-Wise", "#FFA29ECD": "Wistful Variant", "#FFEADDD7": "Wistful Beige", "#FFE33928": "Wistful Longing", "#FF966F77": "Wistful Mauve", "#FFAA9966": "Wistman\u2019s Wood", "#FF888738": "Witch Brew", "#FFFBF073": "Witch Hazel", "#FF8E8976": "Witch Hazel Leaf", "#FF692746": "Witch Soup", "#FF113300": "Witch Wart", "#FF7C4A33": "Witch Wood", "#FF4C3D29": "Witch\u2019s Cottage", "#FF474C50": "Witchcraft", "#FF35343F": "Witches Cauldron", "#FF4F4552": "Witching", "#FFD1D1BB": "With a Twist", "#FFBCA380": "With the Grain", "#FFC5B692": "Withered Moss", "#FFA26766": "Withered Rose", "#FF90C0C9": "Witness", "#FFB1D99D": "Witty Green", "#FF4D5B88": "Wizard", "#FF0073CF": "Wizard Blue", "#FF525E68": "Wizard Grey", "#FF6D4660": "Wizard Time", "#FFDFF1FD": "Wizard White", "#FFA090B8": "Wizard\u2019s Brew", "#FF5D6098": "Wizard\u2019s Potion", "#FF584B4E": "Wizard\u2019s Spell", "#FF007C76": "Wizards Orb", "#FF597FB9": "Woad Blue", "#FF6C9898": "Woad Indigo", "#FF584769": "Woad Purple", "#FF788389": "Wolf", "#FFA8FF04": "Wolf Lichen", "#FF78776F": "Wolf Pack", "#FF3D343F": "Wolf\u2019s Bane", "#FF5C5451": "Wolf\u2019s Fur", "#FFB5B6B7": "Wolfram", "#FF91989D": "Wolverine", "#FFEF8E9F": "Wonder Lust", "#FFA085A6": "Wonder Violet", "#FF635D63": "Wonder Wine", "#FFA97898": "Wonder Wish", "#FFABCB7B": "Wonder Woods", "#FF718A70": "Wonderland", "#FFB8CDDD": "Wondrous Blue", "#FFA3B1F2": "Wondrous Wisteria", "#FF4A2559": "Wonka Purple", "#FFD0A46D": "Wonton Dumpling", "#FF645A56": "Wood Acres", "#FFD7CAB0": "Wood Ash", "#FFFBEEAC": "Wood Avens", "#FF302621": "Wood Bark Variant", "#FF464646": "Wood Charcoal", "#FF90835E": "Wood Chi", "#FF7A7229": "Wood Garlic", "#FFA78C59": "Wood Green", "#FFA08475": "Wood Lake", "#FFEBA0A7": "Wood Nymph", "#FFAABBCC": "Wood Pigeon", "#FF796A4E": "Wood Stain Brown", "#FFA47D43": "Wood Thrush", "#FF78426F": "Wood Violet", "#FF584043": "Wood-Black Red", "#FF61633F": "Wood\u2019s Creek", "#FF8A8A36": "Woodbine", "#FF847451": "Woodbridge", "#FFB3987D": "Woodbridge Trail", "#FF463629": "Woodburn", "#FF8E746C": "Woodchuck", "#FF8F847A": "Woodcraft", "#FFB59B7E": "Wooded Acre", "#FF765A3F": "Wooden Cabin", "#FF745C51": "Wooden Nutmeg", "#FFA89983": "Wooden Peg", "#FFA58563": "Wooden Swing", "#FFDFB07E": "Wooden Sword", "#FF815A48": "Wooden Wagon", "#FF996633": "Woodgrain", "#FF9E7B6C": "Woodhaven", "#FF96856A": "Woodkraft", "#FF626746": "Woodland Variant", "#FF5F4737": "Woodland Brown", "#FF004400": "Woodland Grass", "#FF5C645C": "Woodland Moss", "#FF475C5D": "Woodland Night", "#FF69804B": "Woodland Nymph", "#FFA4A393": "Woodland Sage", "#FF127A49": "Woodland Soul", "#FFB09B89": "Woodland Stone", "#FF8B8D63": "Woodland Walk", "#FFD8D5D2": "Woodland Wildflower", "#FF0D6323": "Woodland Wonder", "#FF405B50": "Woodlawn Green", "#FF4B5D31": "Woodman", "#FFAC8989": "Woodrose", "#FF8B9916": "Woodruff Green", "#FF45402B": "Woodrush Variant", "#FF2B3230": "Woodsmoke Variant", "#FF9B8A5F": "Woodstock", "#FFE9CAB6": "Woodstock Rose", "#FF3D271D": "Woodsy Brown", "#FF755F4A": "Woodward Park", "#FF6E2D2B": "Woody Brown Variant", "#FF40446C": "Wooed", "#FF5F655A": "Woohringa", "#FFAD9A90": "Wool Coat", "#FFD9CFBA": "Wool Skein", "#FF005152": "Wool Turquoise", "#FF917747": "Wool Tweed", "#FF5E5587": "Wool Violet", "#FFDBBDAA": "Wool-Milk Pig", "#FFB59F55": "Woollen Mittens", "#FFDCC9AD": "Woollen Sox", "#FFB0A582": "Woollen Vest", "#FFE7D5C9": "Woolly Beige", "#FFF1EBE4": "Woolly Lamb", "#FFBB9C7C": "Woolly Mammoth", "#FFBDE7CF": "Woolly Mint", "#FF907E63": "Woolly Thyme", "#FFA5A192": "Wooster Smoke", "#FF572B26": "Worcestershire Sauce", "#FFF2BE96": "Word of Mouth", "#FF004D67": "Work Blue", "#FFCDA366": "Workbench", "#FFBFE6D2": "Workout Green", "#FFFFD789": "Workout Routine", "#FF02667B": "Workshop Blue", "#FF005477": "World Peace", "#FF03667B": "World\u2019s Away", "#FFCEC6BF": "Worldly Grey", "#FF98978D": "Worlds Away", "#FF9FAE9E": "Wormwood Green", "#FF4282C6": "Worn Denim", "#FFD4DED4": "Worn Jade Tiles", "#FFA69C81": "Worn Khaki", "#FF6F6C0A": "Worn Olive", "#FFE8DBD3": "Worn Wood", "#FF634333": "Worn Wooden", "#FFAB9379": "Worsted Tan", "#FFE0D1A0": "Woven", "#FF8E7B58": "Woven Basket", "#FFCABBB4": "Woven Cashmere", "#FFDCB639": "Woven Gold", "#FFC9AB96": "Woven Jute", "#FFCEAD8E": "Woven Navajo", "#FFF1DFC0": "Woven Raffia", "#FFDDDCBF": "Woven Reed", "#FFC1AC8B": "Woven Straw", "#FFB99974": "Woven Wicker", "#FFECEAD0": "Wrack White", "#FF5F6D6E": "Wrapped in Twilight", "#FF76856A": "Wreath", "#FF4A4139": "Wren", "#FF71635E": "Wright Brown", "#FFE9D6BD": "Writer\u2019s Parchment", "#FFF1E6CF": "Writing Paper", "#FF474749": "Wrought Iron", "#FFF8D106": "Wu-Tang Gold", "#FFCE7639": "Wulfenite", "#FF86A96F": "Wyvern Green", "#FFE6474A": "X Marks the Spot", "#FFFEF2DC": "X\u00e2kestari White", "#FFFFEE55": "Xanthe Yellow", "#FFF4E216": "Xanthic Variant", "#FF4A3E00": "Xanthophobia", "#FF6AB4E0": "Xavier Blue", "#FF30A9FF": "Xed", "#FF847E54": "Xena", "#FFB7C0D7": "Xenon Blue", "#FF7D0061": "Xereus Purple", "#FFE60626": "Xi\u0101n H\u00f3ng Red", "#FFECE6D1": "Xi\u00e0ng Y\u00e1 B\u00e1i Ivory", "#FFBCB7B0": "Xiao Long Bao Dumpling", "#FFFCE166": "X\u00ecng Hu\u00e1ng Yellow", "#FFCC1166": "X\u012bpe Tot\u0113c Red", "#FF990020": "Xmas Candy", "#FFF08497": "Xoxo", "#FF679BB3": "Yacht Blue", "#FF566062": "Yacht Club", "#FF485783": "Yacht Club Blue", "#FF7C9DAE": "Yacht Harbour", "#FFFABBA9": "Yahoo", "#FFECAB3F": "Yakitori", "#FF0F4D92": "Yale Blue Tint", "#FFC98431": "Yam", "#FFFFA400": "Yamabuki Gold", "#FFCB7E1F": "Yamabukicha Gold", "#FF9C8A4D": "Yanagicha", "#FF8C9E5E": "Yanagizome Green", "#FFF1A141": "Y\u00e1ng Ch\u00e9ng Orange", "#FFEDE8DD": "Yang Mist", "#FF4D5A6B": "Yankee Doodle", "#FF1C2841": "Yankees Blue", "#FF9E826A": "Yardbird", "#FFDCCFB6": "Yarmouth Oyster", "#FFD8AD39": "Yarrow", "#FF547497": "Yawl", "#FF7C6C57": "Ye Olde Rustic Colour", "#FFAD896A": "Yearling", "#FF061088": "Yearning", "#FFCA135E": "Yearning Desire", "#FFFAE1AC": "Yeast", "#FFFFFE00": "Yell for Yellow", "#FFB68D4C": "Yellow Acorn", "#FFF5F5D9": "Yellow Avarice", "#FFF9EED0": "Yellow Beam", "#FFE3C08D": "Yellow Beige", "#FFFFDD33": "Yellow Bell Pepper", "#FFF1CD7B": "Yellow Bird", "#FFF4EABA": "Yellow Bliss", "#FFFDF4BB": "Yellow Blitz", "#FFFAF3CF": "Yellow Bombinate", "#FFF9F6E8": "Yellow Bonnet", "#FFEAC853": "Yellow Brick Road", "#FFAE8B0C": "Yellow Brown", "#FFEEDD11": "Yellow Buzzing", "#FFFFEAAC": "Yellow Canary", "#FFF5F9AD": "Yellow Chalk", "#FFEDB856": "Yellow Coneflower", "#FFFFDE88": "Yellow Corn", "#FFEED36C": "Yellow Cream", "#FFF7C66B": "Yellow Currant", "#FFF6F1D7": "Yellow Diamond", "#FFF8E47E": "Yellow Dragon", "#FFD8E63C": "Yellow Duranta", "#FFF0F0D9": "Yellow Emulsion", "#FFD2CC81": "Yellow Endive", "#FFFCE1B6": "Yellow Essence", "#FFFFB102": "Yellow Exhilaration", "#FFFFCA00": "Yellow Flash", "#FFFFE1A0": "Yellow Geranium", "#FFBE8400": "Yellow Gold", "#FFC8FD3D": "Yellow Green Soft", "#FFF7B930": "Yellow Groove", "#FFEDE68A": "Yellow Iris", "#FFFFCC3A": "Yellow Jacket", "#FFDAA436": "Yellow Jasper", "#FFFFD379": "Yellow Jubilee", "#FFDAB46F": "Yellow Lab", "#FFF6D099": "Yellow Lotus", "#FFCCAA4D": "Yellow Lupine", "#FFDCC449": "Yellow Magic Orchestra", "#FFC0A85A": "Yellow Maize", "#FFFDFCBF": "Yellow Mana", "#FFD28034": "Yellow Mandarin", "#FFF6D255": "Yellow Mask", "#FFF0D31E": "Yellow Mellow", "#FF73633E": "Yellow Metal Variant", "#FF95804A": "Yellow Nile", "#FFC39143": "Yellow Ochre", "#FFEAAD04": "Yellow of Izamal", "#FFFCB001": "Yellow Orange Variant", "#FFEADCC6": "Yellow Page", "#FFE9DF8A": "Yellow Pear", "#FFEEEF06": "Yellow Pepper", "#FFF0E74B": "Yellow Petal", "#FFE4E4CB": "Yellow Phosphenes", "#FFFCB867": "Yellow Polka Dot", "#FFFCFD74": "Yellow Powder", "#FFE6E382": "Yellow Press", "#FFFFF47C": "Yellow Salmonberry", "#FFFDEE73": "Yellow Sand", "#FFF49F35": "Yellow Sea Variant", "#FFD19932": "Yellow Shout", "#FFFFFF14": "Yellow Submarine", "#FFE19447": "Yellow Sumac", "#FFF9B500": "Yellow Summer", "#FFFFF601": "Yellow Sunshine", "#FFF7EDB7": "Yellow Taffy", "#FFFFF29D": "Yellow Tail", "#FFF9D988": "Yellow Trumpet", "#FFF6D06E": "Yellow Tulip", "#FFEAB565": "Yellow Varnish", "#FFFFBA6F": "Yellow Warbler", "#FFC69035": "Yellow Warning", "#FFEDE5B7": "Yellow Wax Pepper", "#FFFEF6BE": "Yellow Yarn", "#FFFFEE33": "Yellow-Bellied", "#FFC8CD37": "Yellow-Green Grosbeak", "#FFEEBB77": "Yellow-Rumped Warbler", "#FFF6F1C4": "Yellowed Bone", "#FFFAEE66": "Yellowish", "#FF9B7A01": "Yellowish Brown", "#FFB0DD16": "Yellowish Green", "#FFEDEEDA": "Yellowish Grey", "#FFFFAB0F": "Yellowish Orange", "#FFFCFC81": "Yellowish Tan", "#FFE9F1D0": "Yellowish White", "#FFF3D80E": "Yellowl", "#FFCEB736": "Yellowstone", "#FFE4D6BA": "Yellowstone Park", "#FFC8C48F": "Yerba Mate", "#FFC7D7E0": "Yeti Footprint", "#FFABBC01": "Yew", "#FF656952": "Yew Hedge", "#FFE0E1E2": "Y\u00edn B\u00e1i Silver", "#FF848999": "Yin H\u016bi Silver", "#FF3B3C3C": "Yin Mist", "#FFB1C4CB": "Y\u00edn S\u00e8 Silver", "#FF05FFA6": "Y\u00edng Gu\u0101ng S\u00e8 Green", "#FFFF69AF": "Y\u00edng Gu\u0101ng S\u00e8 Pink", "#FF632DE9": "Y\u00edng Gu\u0101ng S\u00e8 Purple", "#FF265EF7": "YInMn Blue", "#FFF9F59F": "Yippie Ya Yellow", "#FFFFFF84": "Yippie Yellow", "#FFE3E4D2": "Yoga Daze", "#FFFFECC3": "Yoghurt", "#FFF5E9CE": "Yoghurt Br\u00fbl\u00e9e", "#FF8A8C66": "Yogi", "#FFA291BA": "Yolanda", "#FFD5A585": "Yolande", "#FFEEC701": "Yolk", "#FFE2B051": "Yolk Yellow", "#FFAA987F": "York Beige", "#FFF3D9C7": "York Bisque", "#FFD3BFE5": "York Plum", "#FF67706D": "York River Green", "#FF735C53": "Yorkshire Brown", "#FFBAC3CC": "Yorkshire Cloud", "#FF55AA00": "Yoshi", "#FFEE7776": "You\u2019re Blushing", "#FFFCD8B5": "Young Apricot", "#FFD5A1A9": "Young at Heart", "#FF68BE8D": "Young Bamboo", "#FF86AF38": "Young Bud", "#FF938C83": "Young Colt", "#FFF6A09D": "Young Crab", "#FFC3B4B3": "Young Fawn", "#FF71BC78": "Young Fern", "#FFAAC0AD": "Young Gecko", "#FFC3D825": "Young Grass", "#FFAACF53": "Young Green Onion", "#FF97D499": "Young Greens", "#FFB0C86F": "Young Leaf", "#FF232323": "Young Night", "#FFF2E1D2": "Young Peach", "#FFACC729": "Young Plum", "#FFB28EBC": "Young Prince", "#FFBC64A4": "Young Purple", "#FFFFB6B4": "Young Salmon", "#FFFFA474": "Young Tangerine", "#FFC9AFA9": "Young Turk", "#FFDCDF9D": "Young Wheat", "#FFB9B58E": "Young Willow", "#FF220044": "Your Darkness", "#FF61496E": "Your Majesty", "#FFFFC5BB": "Your Pink Variant", "#FF787E93": "Your Shadow", "#FFFBD9CD": "Yours Truly", "#FFE2C9C8": "Youth", "#FFEE8073": "Youthful Coral", "#FFA7B3B7": "Yreka!", "#FFC0E2E1": "Y\u00f9 Sh\u00ed B\u00e1i White", "#FFE9AF78": "Yucatan", "#FFF2EFE0": "Yucatan White Haba\u00f1ero", "#FF75978F": "Yucca", "#FFA1D7C9": "Yucca Cream", "#FFF2EAD5": "Yucca White", "#FF2138AB": "Yu\u00e8 Gu\u0101ng L\u00e1n Blue", "#FF5959AB": "Yu\u00e8 Gu\u0101ng L\u00e1n Moonlight", "#FF826A21": "Yukon Gold Variant", "#FFC58A78": "Yum Raw Spam", "#FFC7B882": "Yuma Variant", "#FFFFD678": "Yuma Gold", "#FFCFC5AE": "Yuma Sand", "#FF1F911F": "Yushan Green", "#FFFDD200": "Yuzu Jam", "#FFFFD766": "Yuzu Marmalade", "#FF112200": "Yuzu Soy", "#FFD4DE49": "Yuzukosh\u014d", "#FFEC6D71": "Zahri Pink", "#FF6B5A5A": "Zambezi Variant", "#FFFF990E": "Zambia", "#FFDDA026": "Zamesi Desert", "#FFB2C6B1": "Zanah Variant", "#FFD38977": "Zanci", "#FFA39A61": "Zandri Dust", "#FF823C3D": "Zangief\u2019s Chest", "#FFE47486": "Zany Pink", "#FF7E6765": "Zanzibar", "#FF8E7163": "Zanzibar Spice", "#FFC1264C": "Z\u01ceo H\u00f3ng Maroon", "#FFF1F3F3": "Zappy Zebra", "#FFFDE634": "Zard Yellow", "#FF60A448": "Zatar Leaf", "#FFCEC6BB": "Zebra Finch", "#FF9DA286": "Zebra Grass", "#FF0090AD": "Zeftron", "#FF016612": "Zelyony Green", "#FFCFD9DE": "Zen", "#FF99A4BA": "Zen Blue", "#FFC6BFA7": "Zen Essence", "#FFD1DAC0": "Zen Garden", "#FF445533": "Zen Garden Olive", "#FFBAB8AD": "Zen Pebble", "#FF5B5D5C": "Zen Retreat", "#FF497A9F": "Zenith", "#FFA6C8C7": "Zenith Heights", "#FF424F3B": "Zepheniah\u2019s Greed", "#FFC39EA3": "Zephyr", "#FF7AB091": "Zephyr Green", "#FFDDD9C4": "Zero Degrees", "#FF332233": "Zero Gravity", "#FFC6723B": "Zest Variant", "#FF92A360": "Zesty Apple", "#FF3B3C38": "Zeus Variant", "#FF3C343D": "Zeus Palace", "#FF660077": "Zeus Purple", "#FF6C94CD": "Zeus Temple", "#FFEEFF00": "Zeus\u2019s Bolt", "#FFFEF200": "Zheleznogorsk Yellow", "#FFF8F8F9": "Zh\u0113n Zh\u016b B\u00e1i Pearl", "#FFE4C500": "Zhohltyi Yellow", "#FFCB464A": "Zh\u016b H\u00f3ng Vermillion", "#FF9F0FEF": "Z\u01d0 L\u00fao L\u00e1n S\u00e8 Violet", "#FFC94CBE": "Z\u01d0 S\u00e8 Purple", "#FF082903": "Zia Olive", "#FF81A6AA": "Ziggurat Variant", "#FF16B8F3": "Zima Blue", "#FF6A5287": "Zimidar", "#FF92898A": "Zinc", "#FFA3907E": "Zinc Blend", "#FF84948B": "Zinc Blue", "#FF5B5C5A": "Zinc Dust", "#FF655B55": "Zinc Grey", "#FF8C8373": "Zinc Lustre", "#FF5A2538": "Zinfandel", "#FF5A3844": "Zinfandel Red", "#FFFBC17B": "Zing", "#FFDAC01A": "Zingiber", "#FFFFA111": "Zinnia", "#FFFFD781": "Zinnia Gold", "#FFBCC5AA": "Zion", "#FF838567": "Zipline Green", "#FFDEE3E3": "Zircon Variant", "#FF00849D": "Zircon Blue", "#FF807473": "Zircon Grey", "#FFD0E4E5": "Zircon Ice", "#FF2C7C79": "Zirconia Teal", "#FFF4F3CD": "Zitronenzucker", "#FF8B9196": "Zodiac", "#FFEE8844": "Zodiac Constellation", "#FFDADEAD": "Zombie Variant", "#FF54C571": "Zombie Green", "#FFCA6641": "Z\u014dng H\u00f3ng Red", "#FFB8BF71": "Zoodles", "#FFA29589": "Zorba Variant", "#FF17462E": "Zucchini", "#FF97A98B": "Zucchini Cream", "#FFE8A64E": "Zucchini Flower", "#FFAFA170": "Zucchini Garden", "#FFC8D07F": "Zucchini Noodles", "#FFCDD5D5": "Zumthor Variant", "#FF6BC026": "Zunda Green", "#FF008996": "Zuni", "#FF248BCC": "Z\u00fcrich Blue", "#FFE6E1D9": "Z\u00fcrich White", "#FF2B61A0": "Zydeco Blue", "#FFEFC872": "Hit the Hay", "#FF838996": "Roman Silver"} ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/ImageColorDetector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.colors import android.graphics.Bitmap import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.magnifier import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.isFinite import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.luminance import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.core.graphics.get import com.t8rin.gesture.observePointersCountWithOffset import com.t8rin.gesture.pointerMotionEvents import com.t8rin.image.ImageWithConstraints import net.engawapg.lib.zoomable.ZoomableDefaults.defaultZoomOnDoubleTap import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @Composable fun ImageColorDetector( modifier: Modifier = Modifier, color: Color, panEnabled: Boolean = false, imageBitmap: ImageBitmap, isMagnifierEnabled: Boolean, boxModifier: Modifier = Modifier, onColorChange: (Color) -> Unit ) { var pickerOffset by remember { mutableStateOf(Offset.Unspecified) } val zoomState = rememberZoomState(maxScale = 20f) val magnifierEnabled by remember(zoomState.scale, isMagnifierEnabled, panEnabled) { derivedStateOf { zoomState.scale <= 3f && !panEnabled && isMagnifierEnabled } } var globalTouchPosition by remember { mutableStateOf(Offset.Unspecified) } var globalTouchPointersCount by remember { mutableIntStateOf(0) } Box( modifier = boxModifier .observePointersCountWithOffset { size, offset -> globalTouchPointersCount = size globalTouchPosition = offset } .then( if (magnifierEnabled && globalTouchPointersCount == 1) { Modifier.magnifier( sourceCenter = { if (pickerOffset.isSpecified) { globalTouchPosition } else Offset.Unspecified }, magnifierCenter = { globalTouchPosition - Offset(0f, 100.dp.toPx()) }, size = DpSize(height = 100.dp, width = 100.dp), zoom = 2f / zoomState.scale, cornerRadius = 50.dp, elevation = 2.dp ) } else Modifier ) .zoomable( zoomState = zoomState, zoomEnabled = (globalTouchPointersCount >= 2 || panEnabled), enableOneFingerZoom = panEnabled, onDoubleTap = { pos -> if (panEnabled) zoomState.defaultZoomOnDoubleTap(pos) } ), contentAlignment = Alignment.Center ) { ImageWithConstraints( modifier = modifier, imageBitmap = imageBitmap ) { val density = LocalDensity.current.density val size = rememberUpdatedState( newValue = Size( width = imageWidth.value * density, height = imageHeight.value * density ) ) fun updateOffset(pointerInputChange: PointerInputChange): Offset { val offsetX = pointerInputChange.position.x .coerceIn(0f, size.value.width) val offsetY = pointerInputChange.position.y .coerceIn(0f, size.value.height) pointerInputChange.consume() return Offset(offsetX, offsetY) } if (!panEnabled) { var touchStartedWithOnePointer by remember { mutableStateOf(false) } Box( modifier = Modifier .size(imageWidth, imageHeight) .pointerMotionEvents( onDown = { touchStartedWithOnePointer = globalTouchPointersCount <= 1 if (touchStartedWithOnePointer) pickerOffset = updateOffset(it) }, onMove = { if (touchStartedWithOnePointer) pickerOffset = updateOffset(it) }, onUp = { if (touchStartedWithOnePointer) pickerOffset = updateOffset(it) }, delayAfterDownInMillis = 20 ) ) } if (pickerOffset.isSpecified && pickerOffset.isFinite) { onColorChange( calculateColorInPixel( offsetX = pickerOffset.x, offsetY = pickerOffset.y, rect = rect, width = imageWidth.value * density, height = imageHeight.value * density, bitmap = imageBitmap.asAndroidBitmap() ) ) } ColorSelectionDrawing( modifier = Modifier .size(imageWidth, imageHeight), selectedColor = color, zoom = zoomState.scale, offset = pickerOffset ) } } } @Composable internal fun ColorSelectionDrawing( modifier: Modifier, selectedColor: Color = Color.Black, zoom: Float = 1f, offset: Offset, ) { val color = animateColorAsState( if (selectedColor.luminance() > 0.3f) Color.Black else Color.White ).value Canvas(modifier = modifier.fillMaxSize()) { if (offset.isSpecified) { val radius: Float = 8.dp.toPx() / zoom // Draw touch position circle drawCircle( color, radius = radius * 0.3f, center = offset ) drawCircle( color, radius = radius * 1.6f, center = offset, style = Stroke(radius * 0.6f) ) } } } /** * Calculate color of a pixel in a [Bitmap] that is drawn to a Composable with * [width] and [height]. [startImageX] * * @param offsetX x coordinate in Composable from top left corner * @param offsetY y coordinate in Composable from top left corner * @param startImageX x coordinate of top left position of image * @param startImageY y coordinate of top left position of image * @param rect contains coordinates of original bitmap to be used as. Full bitmap has * rect with (0,0) top left and size of [bitmap] * @param width of the Composable that draws this [bitmap] * @param height of the Composable that draws this [bitmap] * @param bitmap of picture/image that to detect color of a specific pixel in */ private fun calculateColorInPixel( offsetX: Float, offsetY: Float, startImageX: Float = 0f, startImageY: Float = 0f, rect: IntRect, width: Float, height: Float, bitmap: Bitmap, ): Color { val bitmapWidth = bitmap.width val bitmapHeight = bitmap.height if (bitmapWidth == 0 || bitmapHeight == 0) return Color.Unspecified // End positions, this might be less than Image dimensions if bitmap doesn't fit Image val endImageX = width - startImageX val endImageY = height - startImageY val scaledX = scale( start1 = startImageX, end1 = endImageX, pos = offsetX, start2 = rect.left.toFloat(), end2 = rect.right.toFloat() ).toInt().coerceIn(0, bitmapWidth - 1) val scaledY = scale( start1 = startImageY, end1 = endImageY, pos = offsetY, start2 = rect.top.toFloat(), end2 = rect.bottom.toFloat() ).toInt().coerceIn(0, bitmapHeight - 1) val pixel: Int = bitmap[scaledX, scaledY] val red = android.graphics.Color.red(pixel) val green = android.graphics.Color.green(pixel) val blue = android.graphics.Color.blue(pixel) return (Color(red, green, blue)) } /** * [Linear Interpolation](https://en.wikipedia.org/wiki/Linear_interpolation) function that moves * amount from it's current position to start and amount * @param start of interval * @param end of interval * @param amount e closed unit interval [0, 1] */ private fun lerp(start: Float, end: Float, amount: Float): Float { return (1 - amount) * start + amount * end } /** * Scale x1 from start1..end1 range to start2..end2 range */ private fun scale(start1: Float, end1: Float, pos: Float, start2: Float, end2: Float) = lerp(start2, end2, calculateFraction(start1, end1, pos)) /** * Calculate fraction for value between a range [end] and [start] coerced into 0f-1f range */ private fun calculateFraction(start: Float, end: Float, pos: Float) = (if (end - start == 0f) 0f else (pos - start) / (end - start)).coerceIn(0f, 1f) ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/ImageColorPaletteState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.colors import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.luminance import androidx.palette.graphics.Palette import com.t8rin.colors.model.ColorData import com.t8rin.colors.parser.ColorNameParser import com.t8rin.colors.util.ColorUtil data class PaletteData( val colorData: ColorData, val percent: Float ) @Composable fun rememberImageColorPaletteState( imageBitmap: ImageBitmap, maximumColorCount: Int = 32, ): ImageColorPaletteState { return remember(imageBitmap, maximumColorCount) { derivedStateOf { ImageColorPaletteStateImpl( image = imageBitmap, maximumColorCount = maximumColorCount ) } }.value } interface ImageColorPaletteState { val image: ImageBitmap val maximumColorCount: Int val paletteData: List } private class ImageColorPaletteStateImpl( override val image: ImageBitmap, override val maximumColorCount: Int ) : ImageColorPaletteState { override val paletteData: List = run { val paletteData = mutableListOf() val palette = Palette .from(image.asAndroidBitmap()) .maximumColorCount(maximumColorCount) .generate() val numberOfPixels: Float = palette.swatches.sumOf { it.population }.toFloat() palette.swatches.forEach { paletteSwatch -> paletteSwatch?.let { swatch -> val color = Color(swatch.rgb) val name = ColorNameParser.parseColorName(color) val colorData = ColorData(color, name) val percent: Float = swatch.population / numberOfPixels paletteData.add(PaletteData(colorData = colorData, percent)) } } paletteData.distinctBy { it.colorData.name }.sortedWith( compareBy( { it.colorData.color.luminance() }, { ColorUtil.colorToHSV(it.colorData.color)[0] }, ) ) } } ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/model/ColorData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.colors.model import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color import com.t8rin.colors.util.ColorUtil @Immutable data class ColorData(val color: Color, val name: String) { val hexText: String = ColorUtil.colorToHex(color = color) } ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/parser/ColorNameParser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.colors.parser import android.content.Context import android.util.JsonReader import androidx.compose.ui.graphics.Color import com.t8rin.colors.util.ColorUtil import com.t8rin.colors.util.HexUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import kotlin.math.sqrt interface ColorNameParser { val colorNames: Map fun parseColorName(color: Color): String fun parseColorFromName(name: String): List fun parseColorFromNameSingle(name: String): Color suspend fun init( context: Context, colorsAsset: String = "color_names.json" ) companion object : ColorNameParser by ColorNameParserImpl } /** * Parses color name from [Color] */ private object ColorNameParserImpl : ColorNameParser { override val colorNames: MutableMap = mutableMapOf() /** * Parse name of [Color] */ override fun parseColorName(color: Color): String { val hex = ColorUtil.colorToHex(color).uppercase().replace("#", "#FF") return colorNames[hex]?.name ?: run { val red = color.red val green = color.green val blue = color.blue colorNames.values.minByOrNull { color -> sqrt( (color.red - red) * (color.red - red) + (color.green - green) * (color.green - green) + (color.blue - blue) * (color.blue - blue) ) }?.name ?: "?????" } } override fun parseColorFromName( name: String ): List = colorNames.values.filter { it.name.contains( other = name, ignoreCase = true ) || name.contains( other = it.name, ignoreCase = true ) }.ifEmpty { listOf( ColorWithName( color = Color.Black, name = "Black" ) ) } override fun parseColorFromNameSingle(name: String): Color { val normalizedName = name.trim().lowercase() val values = colorNames.values return values.firstOrNull { color -> color.name.lowercase() == normalizedName }?.color ?: values.firstOrNull { color -> color.name.lowercase().contains(normalizedName) || normalizedName.contains(color.name.lowercase()) }?.color ?: Color.Black } override suspend fun init( context: Context, colorsAsset: String ) = withContext(Dispatchers.IO) { try { JsonReader(context.assets.open(colorsAsset).bufferedReader()).use { reader -> reader.beginObject() while (reader.hasNext() && isActive) { val hex = reader.nextName() val name = reader.nextString() val color = HexUtil.hexToColor(hex) colorNames[hex] = ColorWithName( color = color, name = name ) } reader.endObject() } } catch (t: Throwable) { t.printStackTrace() } } } data class ColorWithName( val color: Color, val name: String ) { val red get() = color.red val green get() = color.green val blue get() = color.blue } ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/util/ColorUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.colors.util import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.ColorUtils object ColorUtil { /** * Convert Jetpack Compose [Color] to HSV (hue-saturation-value) components. * ``` * Hue is [0 .. 360) * Saturation is [0...1] * Value is [0...1] * ``` * @param hslIn 3 element array which holds the input HSL components. */ fun colorToHSV(color: Color, hslIn: FloatArray) { val rgbArray: IntArray = colorIntToRGBArray(color.toArgb()) val red = rgbArray[0] val green = rgbArray[1] val blue = rgbArray[2] RGBUtil.rgbToHSV(red, green, blue, hslIn) } /** * Convert Jetpack Compose [Color] to HSV (hue-saturation-value) components. * ``` * Hue is [0 .. 360) * Saturation is [0...1] * Value is [0...1] * ``` */ fun colorToHSV(color: Color): FloatArray { val rgbArray: IntArray = colorIntToRGBArray(color.toArgb()) val red = rgbArray[0] val green = rgbArray[1] val blue = rgbArray[2] return RGBUtil.rgbToHSV(red, green, blue) } /** * Convert Jetpack Compose [Color] to HSV (hue-saturation-value) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` * @param hslIn 3 element array which holds the input HSL components. */ fun colorToHSL(color: Color, hslIn: FloatArray) { val rgbArray: IntArray = colorIntToRGBArray(color.toArgb()) val red = rgbArray[0] val green = rgbArray[1] val blue = rgbArray[2] RGBUtil.rgbToHSL(red, green, blue, hslIn) } /** * Convert Jetpack Compose [Color] to HSV (hue-saturation-value) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` */ fun colorToHSL(color: Color): FloatArray { val rgbArray: IntArray = colorIntToRGBArray(color.toArgb()) val red = rgbArray[0] val green = rgbArray[1] val blue = rgbArray[2] return RGBUtil.rgbToHSL(red, green, blue) } /* COLOR-RGB Conversions */ /** * Convert Jetpack [Color] into 3 element array of red, green, and blue *``` * rgb[0] is Red [0 .. 255] * rgb[1] is Green [0...255] * rgb[2] is Blue [0...255] * ``` * @param color Jetpack Compose [Color] * @return 3 element array which holds the input RGB components. */ fun colorToARGBArray(color: Color): IntArray { return colorIntToRGBArray(color.toArgb()) } /** * Convert Jetpack [Color] into 3 element array of red, green, and blue *``` * rgb[0] is Red [0 .. 255] * rgb[1] is Green [0...255] * rgb[2] is Blue [0...255] * ``` * @param color Jetpack Compose [Color] * @return 3 element array which holds the input RGB components. */ fun colorToRGBArray(color: Color): IntArray { return colorIntToRGBArray(color.toArgb()) } /** * Convert Jetpack [Color] into 3 element array of red, green, and blue *``` * rgb[0] is Red [0 .. 255] * rgb[1] is Green [0...255] * rgb[2] is Blue [0...255] * ``` * @param color Jetpack Compose [Color] * @param rgbIn 3 element array which holds the input RGB components. */ fun colorToRGBArray(color: Color, rgbIn: IntArray) { val argbArray = colorIntToRGBArray(color.toArgb()) rgbIn[0] = argbArray[0] rgbIn[1] = argbArray[1] rgbIn[2] = argbArray[2] } fun colorToHex(color: Color): String { return RGBUtil.rgbToHex(color.red, color.green, color.blue) } fun colorToHexAlpha(color: Color): String { return RGBUtil.argbToHex(color.alpha, color.red, color.green, color.blue) } /** * Convert a RGB color in [Integer] form to HSV (hue-saturation-value) components. * * For instance, red =255, green =0, blue=0 is -65536 * ``` * hsv[0] is Hue [0 .. 360) * hsv[1] is Saturation [0...1] * hsv[2] is Value [0...1] * ``` */ fun colorIntToHSV(color: Int): FloatArray { val hsvOut = floatArrayOf(0f, 0f, 0f) android.graphics.Color.colorToHSV(color, hsvOut) return hsvOut } /** * Convert a RGB color in [Integer] form to HSV (hue-saturation-value) components. * * For instance, red =255, green =0, blue=0 is -65536 * * ``` * hsv[0] is Hue [0 .. 360) * hsv[1] is Saturation [0...1] * hsv[2] is Value [0...1] * ``` * @param hsvIn 3 element array which holds the input HSV components. */ fun colorIntToHSV(color: Int, hsvIn: FloatArray) { android.graphics.Color.colorToHSV(color, hsvIn) } /** * Convert RGB color [Integer] to HSL (hue-saturation-lightness) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` */ fun colorIntToHSL(color: Int): FloatArray { val hslOut = floatArrayOf(0f, 0f, 0f) ColorUtils.colorToHSL(color, hslOut) return hslOut } /** * Convert RGB color [Integer] to HSL (hue-saturation-lightness) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` */ fun colorIntToHSL(color: Int, hslIn: FloatArray) { ColorUtils.colorToHSL(color, hslIn) } /* ColorInt-RGB Conversions */ /** * Convert Color [Integer] into 3 element array of red, green, and blue *``` * rgb[0] is Red [0 .. 255] * rgb[1] is Green [0...255] * rgb[2] is Blue [0...255] * ``` * @return 3 element array which holds the input RGB components. */ fun colorIntToRGBArray(color: Int): IntArray { val red = android.graphics.Color.red(color) val green = android.graphics.Color.green(color) val blue = android.graphics.Color.blue(color) return intArrayOf(red, green, blue) } /** * Convert Color [Integer] into 3 element array of red, green, and blue *``` * rgb[0] is Red [0 .. 255] * rgb[1] is Green [0...255] * rgb[2] is Blue [0...255] * ``` * @param rgbIn 3 element array which holds the input RGB components. */ fun colorIntToRGBArray(color: Int, rgbIn: IntArray) { val red = android.graphics.Color.red(color) val green = android.graphics.Color.green(color) val blue = android.graphics.Color.blue(color) rgbIn[0] = red rgbIn[1] = green rgbIn[2] = blue } /** * Convert Color [Integer] into 4 element array of alpha red, green, and blue *``` * rgb[0] is Alpha [0 .. 255] * rgb[1] is Red [0 .. 255] * rgb[2] is Green [0...255] * rgb[3] is Blue [0...255] * ``` * @return 4 element array which holds the input ARGB components. */ fun colorIntToARGBArray(color: Int): IntArray { val alpha = android.graphics.Color.alpha(color) val red = android.graphics.Color.red(color) val green = android.graphics.Color.green(color) val blue = android.graphics.Color.blue(color) return intArrayOf(alpha, red, green, blue) } /** * Convert Color [Integer] into 4 element array of alpha red, green, and blue *``` * rgb[0] is Alpha [0 .. 255] * rgb[1] is Red [0 .. 255] * rgb[2] is Green [0...255] * rgb[3] is Blue [0...255] * ``` * @param argbIn 4 element array which holds the input ARGB components. */ fun colorIntToARGBArray(color: Int, argbIn: IntArray) { val alpha = android.graphics.Color.alpha(color) val red = android.graphics.Color.red(color) val green = android.graphics.Color.green(color) val blue = android.graphics.Color.blue(color) argbIn[0] = alpha argbIn[1] = red argbIn[2] = green argbIn[3] = blue } } ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/util/HexRegex.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.colors.util // Regex for checking if this string is a 6 char hex or 8 char hex val hexWithAlphaRegex = "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})\$".toRegex() val hexRegex = "^#?([0-9a-fA-F]{6})\$".toRegex() // Check only on char if it's in range of 0-9, a-f, A-F val hexRegexSingleChar = "[a-fA-F0-9]".toRegex() ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/util/HexUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.colors.util import androidx.compose.ui.graphics.Color import androidx.core.graphics.toColorInt object HexUtil { /* HEX-ColorInt Conversion */ fun hexToColorInt(colorString: String): Int { val completeColorString = if (colorString.first() == '#') colorString else "#$colorString" return completeColorString.toColorInt() } /* HEX-RGB Conversion */ fun hexToRGB(colorString: String): IntArray { val colorInt = hexToColorInt(colorString) return ColorUtil.colorIntToRGBArray(colorInt) } fun hexToRGB(colorString: String, rgbIn: IntArray) { val colorInt = hexToColorInt(colorString) ColorUtil.colorIntToRGBArray(colorInt, rgbIn) } fun hexToARGB(colorString: String): IntArray { val colorInt = hexToColorInt(colorString) return ColorUtil.colorIntToARGBArray(colorInt) } fun hexToARGB(colorString: String, argbIn: IntArray) { val colorInt = hexToColorInt(colorString) ColorUtil.colorIntToARGBArray(colorInt, argbIn) } fun hexToColor(colorString: String): Color { return Color(hexToColorInt(colorString)) } } ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/util/HexVisualTransformation.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.colors.util import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation class HexVisualTransformation(private val useAlpha: Boolean) : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { val limit = if (useAlpha) 8 else 6 val trimmed = if (text.text.length >= limit) text.text.substring(0 until limit) else text.text val output = if (trimmed.isEmpty()) { trimmed } else { if (!useAlpha || trimmed.length < 7) { "#${trimmed.uppercase()}" } else { "#${ trimmed.substring(0, 2).lowercase() + trimmed.substring(2, trimmed.length).uppercase() }" } } return TransformedText( AnnotatedString(output), if (useAlpha) hexAlphaOffsetMapping else hexOffsetMapping ) } private val hexOffsetMapping = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { // when empty return only 1 char for # if (offset == 0) return offset if (offset <= 5) return offset + 1 return 7 } override fun transformedToOriginal(offset: Int): Int { if (offset == 0) return offset // #ABCABC if (offset <= 6) return offset - 1 return 6 } } private val hexAlphaOffsetMapping = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { // when empty return only 1 char for # if (offset == 0) return offset if (offset <= 7) return offset + 1 return 9 } override fun transformedToOriginal(offset: Int): Int { if (offset == 0) return offset // #ffABCABC if (offset <= 8) return offset - 1 return 8 } } } ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/util/RGBUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.colors.util import android.graphics.Color import androidx.core.graphics.ColorUtils import java.util.Locale object RGBUtil { /** * Convert RGB red, green blue to HSV (hue-saturation-value) components. * ``` * hsv[0] is Hue [0 .. 360) * hsv[1] is Saturation [0...1] * hsv[2] is Value [0...1] * ``` * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color */ fun rgbToHSV( red: Int, green: Int, blue: Int ): FloatArray { val hsvOut = floatArrayOf(0f, 0f, 0f) Color.RGBToHSV(red, green, blue, hsvOut) return hsvOut } /** * Convert RGB red, green blue to HSV (hue-saturation-value) components. * ``` * hsv[0] is Hue [0 .. 360) * hsv[1] is Saturation [0...1] * hsv[2] is Value [0...1] * ``` * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color * @param hsvIn 3 element array which holds the output HSV components. */ fun rgbToHSV( red: Int, green: Int, blue: Int, hsvIn: FloatArray ) { Color.RGBToHSV(red, green, blue, hsvIn) } /** * Convert RGB red, green blue to HSL (hue-saturation-lightness) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color */ fun rgbToHSL( red: Int, green: Int, blue: Int ): FloatArray { val outHsl = floatArrayOf(0f, 0f, 0f) ColorUtils.RGBToHSL(red, green, blue, outHsl) return outHsl } /** * Convert RGB red, green blue to HSL (hue-saturation-lightness) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color * @param hslIn 3 element array which holds the input HSL components. */ fun rgbToHSL( red: Int, green: Int, blue: Int, hslIn: FloatArray ) { ColorUtils.RGBToHSL(red, green, blue, hslIn) } /** * Convert RGB red, green, blue components in [0f..1f] range to * HSV (hue-saturation-value) components. * * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color * @return 3 element array which holds the output RGB components. */ fun rgbFloatToHSV( red: Float, green: Float, blue: Float ): FloatArray { val colorInt = rgbToColorInt(red = red, green = green, blue = blue) val outHsl = floatArrayOf(0f, 0f, 0f) ColorUtil.colorIntToHSV(colorInt, outHsl) return outHsl } /** * Convert RGB red, green, blue components in [0f..1f] range to * HSV (hue-saturation-value) components. * * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color * @param hsvIn 3 element array which holds the output HSV components. */ fun rgbFloatToHSV( red: Float, green: Float, blue: Float, hsvIn: FloatArray ) { val colorInt = rgbToColorInt(red = red, green = green, blue = blue) ColorUtil.colorIntToHSV(colorInt, hsvIn) } /** * Convert RGB red, green blue in [0f..1f] range to HSL (hue-saturation-lightness) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` * @param red Red component [0f..1f] of the color * @param green Green component [0f..1f] of the color * @param blue Blue component [0f..1f] of the color */ fun rgbFloatToHSL( red: Float, green: Float, blue: Float ): FloatArray { val colorInt = rgbToColorInt(red = red, green = green, blue = blue) val outHsl = floatArrayOf(0f, 0f, 0f) ColorUtil.colorIntToHSL(colorInt, outHsl) return outHsl } /** * Convert RGB red, green blue in [0f..1f] range to HSL (hue-saturation-lightness) components. * ``` * hsl[0] is Hue [0 .. 360) * hsl[1] is Saturation [0...1] * hsl[2] is Lightness [0...1] * ``` * @param red Red component [0f..1f] of the color * @param green Green component [0f..1f] of the color * @param blue Blue component [0f..1f] of the color * @param hslIn 3 element array which holds the input HSL components. */ fun rgbFloatToHSL( red: Float, green: Float, blue: Float, hslIn: FloatArray ) { val colorInt = rgbToColorInt(red = red, green = green, blue = blue) ColorUtil.colorIntToHSL(colorInt, hslIn) } /** * Return a color-int from alpha, red, green, blue components. * These component values should be [0..255], but there is no range check performed, * so if they are out of range, the returned color is undefined. * * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color */ fun rgbToColorInt( red: Int, green: Int, blue: Int ): Int { return Color.rgb(red, green, blue) } /** * Return a color-int from alpha, red, green, blue components. * These component values should be [0f..1f], but there is no range check performed, * so if they are out of range, the returned color is undefined. * * @param red Red component [0f..1f] of the color * @param green Green component [0..1f] of the color * @param blue Blue component [0..1f] of the color * */ fun rgbToColorInt( red: Float, green: Float, blue: Float ): Int { val redInt = red.fractionToRGBRange() val greenInt = green.fractionToRGBRange() val blueInt = blue.fractionToRGBRange() return Color.rgb(redInt, greenInt, blueInt) } /** * Convert red, green, blue components [0..255] range in [Integer] to Hex format String */ fun rgbToHex( red: Int, green: Int, blue: Int ): String { return "#" + Integer.toHexString(red).toStringComponent() + Integer.toHexString(green).toStringComponent() + Integer.toHexString(blue).toStringComponent() } /** * Convert rgb array to Hex format String * ``` * rgb[0] is Red [0 .. 255] * rgb[1] is Green [0...255] * rgb[2] is Blue [0...255] * ``` */ fun rgbToHex(rgb: IntArray): String { return "#" + Integer.toHexString(rgb[0]).toStringComponent() + Integer.toHexString(rgb[1]).toStringComponent() + Integer.toHexString(rgb[2]).toStringComponent() } /** * Convert red, green, blue components [0f..1f] range in [Float] to Hex format String */ fun rgbToHex( red: Float, green: Float, blue: Float ): String { return "#" + Integer.toHexString(red.fractionToRGBRange()).toStringComponent() + Integer.toHexString(green.fractionToRGBRange()).toStringComponent() + Integer.toHexString(blue.fractionToRGBRange()).toStringComponent() } /** * Return a color-int from alpha, red, green, blue components. * These component values should be [0..255], but there is no range check performed, * so if they are out of range, the returned color is undefined. * * @param alpha Alpha component [0..255] of the color * @param red Red component [0..255] of the color * @param green Green component [0..255] of the color * @param blue Blue component [0..255] of the color */ fun argbToColorInt( alpha: Int, red: Int, green: Int, blue: Int ): Int { return Color.argb(alpha, red, green, blue) } /** * Return a color-int from alpha, red, green, blue components. * These component values should be [0f..1f], but there is no range check performed, * so if they are out of range, the returned color is undefined. * * @param alpha Alpha component [0f..1f] of the color * @param red Red component [0f..1f] of the color * @param green Green component [0..1f] of the color * @param blue Blue component [0..1f] of the color */ fun argbToColorInt( alpha: Float, red: Float, green: Float, blue: Float ): Int { val alphaInt = alpha.fractionToRGBRange() val redInt = red.fractionToRGBRange() val greenInt = green.fractionToRGBRange() val blueInt = blue.fractionToRGBRange() return Color.argb(alphaInt, redInt, greenInt, blueInt) } /** * Convert alpha, red, green, blue components in [0..255] range argb to Hex format String * * ``` * Alpha is [0 .. 255] * Red is [0 .. 255] * Green is [0...255] * Blue is [0...255] * ``` */ fun argbToHex( alpha: Int, red: Int, green: Int, blue: Int ): String { return "#" + Integer.toHexString(alpha).toStringComponent() + Integer.toHexString(red).toStringComponent() + Integer.toHexString(green).toStringComponent() + Integer.toHexString(blue).toStringComponent() } /** * Convert alpha, red, green, blue components in [0f..1f] range in [Float] argb to Hex format String * * ``` * Alpha is [0f .. 1f] * Red is [0f .. 1f] * Green is [0...1f] * Blue is [0...1f] * ``` */ fun argbToHex( alpha: Float, red: Float, green: Float, blue: Float ): String { return "#" + Integer.toHexString(alpha.fractionToRGBRange()).toStringComponent() + Integer.toHexString(red.fractionToRGBRange()).toStringComponent() + Integer.toHexString(green.fractionToRGBRange()).toStringComponent() + Integer.toHexString(blue.fractionToRGBRange()).toStringComponent() } /* RGB-HEX Conversions */ /** * Get hex representation of a rgb Color in [Integer] format */ fun Int.toRgbString(): String = ("#" + red.toStringComponent() + green.toStringComponent() + blue.toStringComponent()) .uppercase(Locale.getDefault()) /** * Get hex representation of a argb Color in [Integer] format */ fun Int.toArgbString(): String = ("#" + alpha.toStringComponent() + red.toStringComponent() + green.toStringComponent() + blue.toStringComponent() ).uppercase(Locale.getDefault()) private fun String.toStringComponent() = this.let { if (it.length == 1) "0${it}" else it } private fun Int.toStringComponent(): String = this.toString(16).let { if (it.length == 1) "0${it}" else it } inline val Int.alpha: Int get() = (this shr 24) and 0xFF inline val Int.red: Int get() = (this shr 16) and 0xFF inline val Int.green: Int get() = (this shr 8) and 0xFF inline val Int.blue: Int get() = this and 0xFF } ================================================ FILE: lib/colors/src/main/java/com/t8rin/colors/util/RoundngUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.colors.util import kotlin.math.roundToInt /** * Converts alpha, red, green or blue values from range of [0f-1f] to [0-255]. */ fun Float.fractionToRGBRange() = (this * 255.0f).toInt() /** * Converts alpha, red, green or blue values from range of [0f-1f] to [0-255] and returns * it as [String]. */ fun Float.fractionToRGBString() = this.fractionToRGBRange().toString() /** * Rounds this [Float] to another with 2 significant numbers * 0.1234 is rounded to 0.12 * 0.127 is rounded to 0.13 */ fun Float.roundToTwoDigits() = (this * 100.0f).roundToInt() / 100.0f /** * Rounds this [Float] to closest int. */ fun Float.round() = this.roundToInt() /** * Converts **HSV** or **HSL** colors that are in range of [0f-1f] to [0-100] range in [Integer] * with [Float.roundToInt] */ fun Float.fractionToPercent() = (this * 100.0f).roundToInt() /** * Converts **HSV** or **HSL** colors that are in range of [0f-1f] to [0-100] range in [Integer] * with [Float.toInt] */ fun Float.fractionToIntPercent() = (this * 100.0f).toInt() ================================================ FILE: lib/cropper/.gitignore ================================================ /build ================================================ FILE: lib/cropper/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.cropper" dependencies { implementation(projects.lib.gesture) } ================================================ FILE: lib/cropper/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/CropModifier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.debugInspectorInfo import com.t8rin.cropper.model.CropData import com.t8rin.cropper.state.CropState import com.t8rin.cropper.state.cropData import com.t8rin.cropper.util.ZoomLevel import com.t8rin.cropper.util.getNextZoomLevel import com.t8rin.cropper.util.update import com.t8rin.gesture.detectMotionEventsAsList import com.t8rin.gesture.detectTransformGestures import kotlinx.coroutines.launch /** * Modifier that zooms in or out of Composable set to. This zoom modifier has option * to move back to bounds with an animation or option to have fling gesture when user removes * from screen while velocity is higher than threshold to have smooth touch effect. * * @param keys are used for [Modifier.pointerInput] to restart closure when any keys assigned * change * empty space on sides or edges of parent. * @param cropState State of the zoom that contains option to set initial, min, max zoom, * enabling rotation, pan or zoom and contains current [CropData] * event propagations. Also contains [Rect] of visible area based on pan, zoom and rotation * @param zoomOnDoubleTap lambda that returns current [ZoomLevel] and based on current level * enables developer to define zoom on double tap gesture * @param onGestureStart callback to to notify gesture has started and return current * [CropData] of this modifier * @param onGesture callback to notify about ongoing gesture and return current * [CropData] of this modifier * @param onGestureEnd callback to notify that gesture finished return current * [CropData] of this modifier */ fun Modifier.crop( vararg keys: Any?, cropState: CropState, zoomOnDoubleTap: (ZoomLevel) -> Float = cropState.DefaultOnDoubleTap, onDown: ((CropData) -> Unit)? = null, onMove: ((CropData) -> Unit)? = null, onUp: ((CropData) -> Unit)? = null, onGestureStart: ((CropData) -> Unit)? = null, onGesture: ((CropData) -> Unit)? = null, onGestureEnd: ((CropData) -> Unit)? = null ) = composed( factory = { LaunchedEffect(key1 = cropState) { cropState.init() } val coroutineScope = rememberCoroutineScope() // Current Zoom level var zoomLevel by remember { mutableStateOf(ZoomLevel.Min) } val transformModifier = Modifier.pointerInput(*keys) { detectTransformGestures( consume = false, onGestureStart = { onGestureStart?.invoke(cropState.cropData) }, onGestureEnd = { coroutineScope.launch { cropState.onGestureEnd { onGestureEnd?.invoke(cropState.cropData) } } }, onGesture = { centroid, pan, zoom, rotate, mainPointer, pointerList -> coroutineScope.launch { cropState.onGesture( centroid = centroid, panChange = pan, zoomChange = zoom, rotationChange = rotate, mainPointer = mainPointer, changes = pointerList ) } onGesture?.invoke(cropState.cropData) mainPointer.consume() } ) } val tapModifier = Modifier.pointerInput(*keys) { detectTapGestures( onDoubleTap = { offset: Offset -> coroutineScope.launch { zoomLevel = getNextZoomLevel(zoomLevel) val newZoom = zoomOnDoubleTap(zoomLevel) cropState.onDoubleTap( offset = offset, zoom = newZoom ) { onGestureEnd?.invoke(cropState.cropData) } } } ) } val touchModifier = Modifier.pointerInput(*keys) { detectMotionEventsAsList( onDown = { coroutineScope.launch { cropState.onDown(it) onDown?.invoke(cropState.cropData) } }, onMove = { coroutineScope.launch { cropState.onMove(it) onMove?.invoke(cropState.cropData) } }, onUp = { coroutineScope.launch { cropState.onUp(it) onUp?.invoke(cropState.cropData) } } ) } val graphicsModifier = Modifier.graphicsLayer { this.update(cropState) } this.then( Modifier .clipToBounds() .then(tapModifier) .then(transformModifier) .then(touchModifier) .then(graphicsModifier) ) }, inspectorInfo = debugInspectorInfo { name = "crop" // add name and value of each argument properties["keys"] = keys properties["onDown"] = onGestureStart properties["onMove"] = onGesture properties["onUp"] = onGestureEnd } ) internal val CropState.DefaultOnDoubleTap: (ZoomLevel) -> Float get() = { zoomLevel: ZoomLevel -> when (zoomLevel) { ZoomLevel.Min -> 1f ZoomLevel.Mid -> 3f.coerceIn(zoomMin, zoomMax) ZoomLevel.Max -> 5f.coerceAtLeast(zoomMax) } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/ImageCropper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import com.t8rin.cropper.crop.CropAgent import com.t8rin.cropper.draw.DrawingOverlay import com.t8rin.cropper.draw.ImageDrawCanvas import com.t8rin.cropper.image.ImageWithConstraints import com.t8rin.cropper.image.getScaledImageBitmap import com.t8rin.cropper.model.CropOutline import com.t8rin.cropper.settings.CropDefaults import com.t8rin.cropper.settings.CropProperties import com.t8rin.cropper.settings.CropStyle import com.t8rin.cropper.settings.CropType import com.t8rin.cropper.state.DynamicCropState import com.t8rin.cropper.state.rememberCropState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart @Composable fun ImageCropper( modifier: Modifier = Modifier, imageBitmap: ImageBitmap, contentDescription: String? = null, cropStyle: CropStyle = CropDefaults.style(), cropProperties: CropProperties, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, crop: Boolean = false, onCropStart: () -> Unit, onZoomChange: (Float) -> Unit, onCropSuccess: (ImageBitmap) -> Unit, backgroundModifier: Modifier = Modifier ) { ImageWithConstraints( modifier = modifier.clipToBounds(), contentScale = cropProperties.contentScale, contentDescription = contentDescription, filterQuality = filterQuality, imageBitmap = imageBitmap, drawImage = false ) { // No crop operation is applied by ScalableImage so rect points to bounds of original // bitmap val scaledImageBitmap = getScaledImageBitmap( imageWidth = imageWidth, imageHeight = imageHeight, rect = rect, bitmap = imageBitmap, contentScale = cropProperties.contentScale, ) // Container Dimensions val containerWidthPx = constraints.maxWidth val containerHeightPx = constraints.maxHeight val containerWidth: Dp val containerHeight: Dp // Bitmap Dimensions val bitmapWidth = scaledImageBitmap.width val bitmapHeight = scaledImageBitmap.height // Dimensions of Composable that displays Bitmap val imageWidthPx: Int val imageHeightPx: Int with(LocalDensity.current) { imageWidthPx = imageWidth.roundToPx() imageHeightPx = imageHeight.roundToPx() - if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) 0 else WindowInsets.navigationBars.getBottom(LocalDensity.current) containerWidth = containerWidthPx.toDp() containerHeight = containerHeightPx.toDp() } val cropType = cropProperties.cropType val contentScale = cropProperties.contentScale val fixedAspectRatio = cropProperties.fixedAspectRatio val cropOutline = cropProperties.cropOutlineProperty.cropOutline // these keys are for resetting cropper when image width/height, contentScale or // overlay aspect ratio changes val resetKeys = getResetKeys( scaledImageBitmap, imageWidthPx, imageHeightPx, contentScale, cropType, fixedAspectRatio ) val cropState = rememberCropState( imageSize = IntSize(bitmapWidth, bitmapHeight), containerSize = IntSize(containerWidthPx, containerHeightPx), drawAreaSize = IntSize(imageWidthPx, imageHeightPx), cropProperties = cropProperties, keys = resetKeys ) onZoomChange(cropState.zoom) val isHandleTouched by remember(cropState) { derivedStateOf { cropState is DynamicCropState && handlesTouched(cropState.touchRegion) } } val pressedStateColor = remember(cropStyle.backgroundColor) { cropStyle.backgroundColor .copy(cropStyle.backgroundColor.alpha * .7f) } val transparentColor by animateColorAsState( animationSpec = tween(300, easing = LinearEasing), targetValue = if (isHandleTouched) pressedStateColor else cropStyle.backgroundColor ) // Crops image when user invokes crop operation Crop( crop, scaledImageBitmap, cropState.cropRect, cropOutline, onCropStart, onCropSuccess ) val imageModifier = Modifier .size(containerWidth, containerHeight) .crop( keys = resetKeys, cropState = cropState ) LaunchedEffect(key1 = cropProperties) { cropState.updateProperties(cropProperties) } /// Create a MutableTransitionState for the AnimatedVisibility. var visible by remember { mutableStateOf(false) } LaunchedEffect(Unit) { delay(100) visible = true } ImageCropper( modifier = imageModifier, visible = visible, imageBitmap = imageBitmap, containerWidth = containerWidth, containerHeight = containerHeight, imageWidthPx = imageWidthPx, imageHeightPx = imageHeightPx, handleSize = cropProperties.handleSize, middleHandleSize = cropProperties.middleHandleSize, overlayRect = cropState.overlayRect, cropType = cropType, cropOutline = cropOutline, cropStyle = cropStyle, transparentColor = transparentColor, backgroundModifier = backgroundModifier ) } } @Composable private fun ImageCropper( modifier: Modifier, visible: Boolean, imageBitmap: ImageBitmap, containerWidth: Dp, containerHeight: Dp, imageWidthPx: Int, imageHeightPx: Int, handleSize: Float, middleHandleSize: Float, cropType: CropType, cropOutline: CropOutline, cropStyle: CropStyle, overlayRect: Rect, transparentColor: Color, backgroundModifier: Modifier ) { Box( modifier = Modifier .fillMaxSize() .then(backgroundModifier) ) { AnimatedVisibility( visible = visible, enter = fadeIn(tween(500)) ) { ImageCropperImpl( modifier = modifier, imageBitmap = imageBitmap, containerWidth = containerWidth, containerHeight = containerHeight, imageWidthPx = imageWidthPx, imageHeightPx = imageHeightPx, cropType = cropType, cropOutline = cropOutline, handleSize = handleSize, middleHandleSize = middleHandleSize, cropStyle = cropStyle, rectOverlay = overlayRect, transparentColor = transparentColor ) } } } @Composable private fun ImageCropperImpl( modifier: Modifier, imageBitmap: ImageBitmap, containerWidth: Dp, containerHeight: Dp, imageWidthPx: Int, imageHeightPx: Int, cropType: CropType, cropOutline: CropOutline, handleSize: Float, middleHandleSize: Float, cropStyle: CropStyle, transparentColor: Color, rectOverlay: Rect ) { Box(contentAlignment = Alignment.Center) { // Draw Image ImageDrawCanvas( modifier = modifier, imageBitmap = imageBitmap, imageWidth = imageWidthPx, imageHeight = imageHeightPx ) val drawOverlay = cropStyle.drawOverlay val drawGrid = cropStyle.drawGrid val overlayColor = cropStyle.overlayColor val handleColor = cropStyle.handleColor val drawHandles = cropType == CropType.Dynamic val strokeWidth = cropStyle.strokeWidth DrawingOverlay( modifier = Modifier.size(containerWidth, containerHeight), drawOverlay = drawOverlay, rect = rectOverlay, cropOutline = cropOutline, drawGrid = drawGrid, overlayColor = overlayColor, handleColor = handleColor, strokeWidth = strokeWidth, drawHandles = drawHandles, handleSize = handleSize, middleHandleSize = middleHandleSize, transparentColor = transparentColor, ) } } @Composable private fun Crop( crop: Boolean, scaledImageBitmap: ImageBitmap, cropRect: Rect, cropOutline: CropOutline, onCropStart: () -> Unit, onCropSuccess: (ImageBitmap) -> Unit ) { val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current // Crop Agent is responsible for cropping image val cropAgent = remember { CropAgent() } LaunchedEffect(crop) { if (crop) { flow { emit( cropAgent.crop( scaledImageBitmap, cropRect, cropOutline, layoutDirection, density ) ) } .flowOn(Dispatchers.Default) .onStart { onCropStart() delay(400) } .onEach { onCropSuccess(it) } .launchIn(this) } } } @Composable private fun getResetKeys( scaledImageBitmap: ImageBitmap, imageWidthPx: Int, imageHeightPx: Int, contentScale: ContentScale, cropType: CropType, fixedAspectRatio: Boolean, ) = remember( scaledImageBitmap, imageWidthPx, imageHeightPx, contentScale, cropType, fixedAspectRatio, ) { arrayOf( scaledImageBitmap, imageWidthPx, imageHeightPx, contentScale, cropType, fixedAspectRatio, ) } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/TouchRegion.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper /** * Enum for detecting which section of Composable user has initially touched */ enum class TouchRegion { TopLeft, TopRight, BottomLeft, BottomRight, TopCenter, CenterRight, BottomCenter, CenterLeft, Inside, None } fun handlesTouched(touchRegion: TouchRegion) = touchRegion != TouchRegion.None && touchRegion != TouchRegion.Inside ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/crop/CropAgent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.crop import android.graphics.Bitmap import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.addOutline import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import androidx.core.graphics.scale import com.t8rin.cropper.model.CropImageMask import com.t8rin.cropper.model.CropOutline import com.t8rin.cropper.model.CropPath import com.t8rin.cropper.model.CropShape /** * Crops imageBitmap based on path that is passed in [crop] function */ internal class CropAgent { private val imagePaint = Paint().apply { blendMode = BlendMode.SrcIn } private val paint = Paint() fun crop( imageBitmap: ImageBitmap, cropRect: Rect, cropOutline: CropOutline, layoutDirection: LayoutDirection, density: Density, ): ImageBitmap { return runCatching { val croppedBitmap: Bitmap = Bitmap.createBitmap( imageBitmap.asAndroidBitmap(), cropRect.left.toInt(), cropRect.top.toInt(), cropRect.width.toInt(), cropRect.height.toInt(), ) val imageToCrop = croppedBitmap .copy(Bitmap.Config.ARGB_8888, true)!! .asImageBitmap() drawCroppedImage(cropOutline, cropRect, layoutDirection, density, imageToCrop) imageToCrop }.getOrNull() ?: imageBitmap } private fun drawCroppedImage( cropOutline: CropOutline, cropRect: Rect, layoutDirection: LayoutDirection, density: Density, imageToCrop: ImageBitmap, ) { when (cropOutline) { is CropShape -> { val path = Path().apply { val outline = cropOutline.shape.createOutline(cropRect.size, layoutDirection, density) addOutline(outline) } Canvas(image = imageToCrop).run { saveLayer(nativeCanvas.clipBounds.toComposeRect(), imagePaint) // Destination drawPath(path, paint) // Source drawImage( image = imageToCrop, topLeftOffset = Offset.Zero, paint = imagePaint ) restore() } } is CropPath -> { val path = Path().apply { addPath(cropOutline.path) val pathSize = getBounds().size val rectSize = cropRect.size val matrix = android.graphics.Matrix() matrix.postScale( rectSize.width / pathSize.width, cropRect.height / pathSize.height ) this.asAndroidPath().transform(matrix) val left = getBounds().left val top = getBounds().top translate(Offset(-left, -top)) } Canvas(image = imageToCrop).run { saveLayer(nativeCanvas.clipBounds.toComposeRect(), imagePaint) // Destination drawPath(path, paint) // Source drawImage(image = imageToCrop, topLeftOffset = Offset.Zero, imagePaint) restore() } } is CropImageMask -> { val imageMask = cropOutline.image.asAndroidBitmap() .scale(cropRect.width.toInt(), cropRect.height.toInt()).asImageBitmap() Canvas(image = imageToCrop).run { saveLayer(nativeCanvas.clipBounds.toComposeRect(), imagePaint) // Destination drawImage(imageMask, topLeftOffset = Offset.Zero, paint) // Source drawImage(image = imageToCrop, topLeftOffset = Offset.Zero, imagePaint) restore() } } } } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/draw/ImageDrawCanvas.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.draw import androidx.compose.foundation.Canvas import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import kotlin.math.roundToInt /** * Draw image to be cropped */ @Composable internal fun ImageDrawCanvas( modifier: Modifier, imageBitmap: ImageBitmap, imageWidth: Int, imageHeight: Int ) { Canvas(modifier = modifier) { val canvasWidth = size.width.roundToInt() val canvasHeight = size.height.roundToInt() drawImage( image = imageBitmap, srcSize = IntSize(imageBitmap.width, imageBitmap.height), dstSize = IntSize(imageWidth, imageHeight), dstOffset = IntOffset( x = (canvasWidth - imageWidth) / 2, y = (canvasHeight - imageHeight) / 2 ) ) } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/draw/Overlay.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.draw import androidx.compose.foundation.Canvas import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import com.t8rin.cropper.model.CropImageMask import com.t8rin.cropper.model.CropOutline import com.t8rin.cropper.model.CropPath import com.t8rin.cropper.model.CropShape import com.t8rin.cropper.util.drawGrid import com.t8rin.cropper.util.drawWithLayer import com.t8rin.cropper.util.scaleAndTranslatePath /** * Draw overlay composed of 9 rectangles. When [drawHandles] * is set draw handles for changing drawing rectangle */ @Composable internal fun DrawingOverlay( modifier: Modifier, drawOverlay: Boolean, rect: Rect, cropOutline: CropOutline, drawGrid: Boolean, transparentColor: Color, overlayColor: Color, handleColor: Color, strokeWidth: Dp, drawHandles: Boolean, handleSize: Float, middleHandleSize: Float, ) { val density = LocalDensity.current val layoutDirection: LayoutDirection = LocalLayoutDirection.current val strokeWidthPx = LocalDensity.current.run { strokeWidth.toPx() } val pathHandles = remember { Path() } val middlePathHandles = remember { Path() } when (cropOutline) { is CropShape -> { val outline = remember(rect, cropOutline) { cropOutline.shape.createOutline(rect.size, layoutDirection, density) } DrawingOverlayImpl( modifier = modifier, drawOverlay = drawOverlay, rect = rect, drawGrid = drawGrid, transparentColor = transparentColor, overlayColor = overlayColor, handleColor = handleColor, strokeWidth = strokeWidthPx, drawHandles = drawHandles, handleSize = handleSize, middleHandleSize = middleHandleSize, pathHandles = pathHandles, middlePathHandles = middlePathHandles, outline = outline ) } is CropPath -> { val path = remember(rect, cropOutline) { Path().apply { addPath(cropOutline.path) scaleAndTranslatePath(rect.width, rect.height) } } DrawingOverlayImpl( modifier = modifier, drawOverlay = drawOverlay, rect = rect, drawGrid = drawGrid, transparentColor = transparentColor, overlayColor = overlayColor, handleColor = handleColor, strokeWidth = strokeWidthPx, drawHandles = drawHandles, handleSize = handleSize, middleHandleSize = middleHandleSize, pathHandles = pathHandles, middlePathHandles = middlePathHandles, path = path ) } is CropImageMask -> { val imageBitmap = cropOutline.image DrawingOverlayImpl( modifier = modifier, drawOverlay = drawOverlay, rect = rect, drawGrid = drawGrid, transparentColor = transparentColor, overlayColor = overlayColor, handleColor = handleColor, strokeWidth = strokeWidthPx, drawHandles = drawHandles, handleSize = handleSize, middleHandleSize = middleHandleSize, pathHandles = pathHandles, middlePathHandles = middlePathHandles, image = imageBitmap ) } } } @Composable private fun DrawingOverlayImpl( modifier: Modifier, drawOverlay: Boolean, rect: Rect, drawGrid: Boolean, transparentColor: Color, overlayColor: Color, handleColor: Color, strokeWidth: Float, drawHandles: Boolean, handleSize: Float, middleHandleSize: Float, pathHandles: Path, middlePathHandles: Path, outline: Outline, ) { Canvas(modifier = modifier) { drawOverlay( drawOverlay = drawOverlay, rect = rect, drawGrid = drawGrid, transparentColor = transparentColor, overlayColor = overlayColor, handleColor = handleColor, strokeWidth = strokeWidth, drawHandles = drawHandles, handleSize = handleSize, middleHandleSize = middleHandleSize, pathHandles = pathHandles, middlePathHandles = middlePathHandles ) { drawCropOutline(outline = outline) } } } @Composable private fun DrawingOverlayImpl( modifier: Modifier, drawOverlay: Boolean, rect: Rect, drawGrid: Boolean, transparentColor: Color, overlayColor: Color, handleColor: Color, strokeWidth: Float, drawHandles: Boolean, handleSize: Float, middleHandleSize: Float, pathHandles: Path, middlePathHandles: Path, path: Path, ) { Canvas(modifier = modifier) { drawOverlay( drawOverlay = drawOverlay, rect = rect, drawGrid = drawGrid, transparentColor = transparentColor, overlayColor = overlayColor, handleColor = handleColor, strokeWidth = strokeWidth, drawHandles = drawHandles, handleSize = handleSize, middleHandleSize = middleHandleSize, pathHandles = pathHandles, middlePathHandles = middlePathHandles ) { drawCropPath(path) } } } @Composable private fun DrawingOverlayImpl( modifier: Modifier, drawOverlay: Boolean, rect: Rect, drawGrid: Boolean, transparentColor: Color, overlayColor: Color, handleColor: Color, strokeWidth: Float, drawHandles: Boolean, handleSize: Float, middleHandleSize: Float, pathHandles: Path, middlePathHandles: Path, image: ImageBitmap, ) { Canvas(modifier = modifier) { drawOverlay( drawOverlay = drawOverlay, rect = rect, drawGrid = drawGrid, transparentColor = transparentColor, overlayColor = overlayColor, handleColor = handleColor, strokeWidth = strokeWidth, drawHandles = drawHandles, handleSize = handleSize, middleHandleSize = middleHandleSize, pathHandles = pathHandles, middlePathHandles = middlePathHandles ) { drawCropImage(rect, image) } } } private fun DrawScope.drawOverlay( drawOverlay: Boolean, rect: Rect, drawGrid: Boolean, transparentColor: Color, overlayColor: Color, handleColor: Color, strokeWidth: Float, drawHandles: Boolean, handleSize: Float, middleHandleSize: Float, pathHandles: Path, middlePathHandles: Path, drawBlock: DrawScope.() -> Unit ) { drawWithLayer { // Destination drawRect(transparentColor) // Source translate(left = rect.left, top = rect.top) { drawBlock() } if (drawGrid) { drawGrid( rect = rect, strokeWidth = strokeWidth, color = overlayColor ) } } if (drawOverlay) { drawRect( topLeft = rect.topLeft, size = rect.size, color = overlayColor, style = Stroke(width = strokeWidth) ) if (drawHandles) { pathHandles.apply { reset() updateHandlePath(rect, handleSize) } middlePathHandles.apply { reset() updateMiddleHandlePath(rect, middleHandleSize) } drawPath( path = middlePathHandles, color = handleColor.copy(0.9f).compositeOver(Color.Black), style = Stroke( width = strokeWidth * 4, cap = StrokeCap.Round, join = StrokeJoin.Round ) ) drawPath( path = pathHandles, color = handleColor, style = Stroke( width = strokeWidth * 4, cap = StrokeCap.Round, join = StrokeJoin.Round ) ) } } } private fun DrawScope.drawCropImage( rect: Rect, imageBitmap: ImageBitmap, blendMode: BlendMode = BlendMode.DstOut ) { drawImage( image = imageBitmap, dstSize = IntSize(rect.size.width.toInt(), rect.size.height.toInt()), blendMode = blendMode ) } private fun DrawScope.drawCropOutline( outline: Outline, blendMode: BlendMode = BlendMode.SrcOut ) { drawOutline( outline = outline, color = Color.Transparent, blendMode = blendMode ) } private fun DrawScope.drawCropPath( path: Path, blendMode: BlendMode = BlendMode.SrcOut ) { drawPath( path = path, color = Color.Transparent, blendMode = blendMode ) } private fun Path.updateHandlePath( rect: Rect, handleSize: Float ) { if (rect != Rect.Zero) { // Top left lines moveTo(rect.topLeft.x, rect.topLeft.y + handleSize) lineTo(rect.topLeft.x, rect.topLeft.y) lineTo(rect.topLeft.x + handleSize, rect.topLeft.y) // Top right lines moveTo(rect.topRight.x - handleSize, rect.topRight.y) lineTo(rect.topRight.x, rect.topRight.y) lineTo(rect.topRight.x, rect.topRight.y + handleSize) // Bottom right lines moveTo(rect.bottomRight.x, rect.bottomRight.y - handleSize) lineTo(rect.bottomRight.x, rect.bottomRight.y) lineTo(rect.bottomRight.x - handleSize, rect.bottomRight.y) // Bottom left lines moveTo(rect.bottomLeft.x + handleSize, rect.bottomLeft.y) lineTo(rect.bottomLeft.x, rect.bottomLeft.y) lineTo(rect.bottomLeft.x, rect.bottomLeft.y - handleSize) } } private fun Path.updateMiddleHandlePath( rect: Rect, handleSize: Float ) { if (rect != Rect.Zero) { // Top middle lines moveTo(rect.topCenter.x - handleSize / 2, rect.topCenter.y) lineTo(rect.topCenter.x + handleSize / 2, rect.topCenter.y) // Right middle lines moveTo(rect.centerRight.x, rect.centerRight.y - handleSize / 2) lineTo(rect.centerRight.x, rect.centerRight.y + handleSize / 2) // Bottom middle lines moveTo(rect.bottomCenter.x - handleSize / 2, rect.bottomCenter.y) lineTo(rect.bottomCenter.x + handleSize / 2, rect.bottomCenter.y) // Left middle lines moveTo(rect.centerLeft.x, rect.centerLeft.y - handleSize / 2) lineTo(rect.centerLeft.x, rect.centerLeft.y + handleSize / 2) } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/image/ImageScope.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.image import android.graphics.Bitmap import androidx.compose.foundation.Canvas import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect /** * Receiver scope being used by the children parameter of [ImageWithConstraints] */ @Stable interface ImageScope { /** * The constraints given by the parent layout in pixels. * * Use [minWidth], [maxWidth], [minHeight] or [maxHeight] if you need value in [Dp]. */ val constraints: Constraints /** * The minimum width in [Dp]. * * @see constraints for the values in pixels. */ val minWidth: Dp /** * The maximum width in [Dp]. * * @see constraints for the values in pixels. */ val maxWidth: Dp /** * The minimum height in [Dp]. * * @see constraints for the values in pixels. */ val minHeight: Dp /** * The maximum height in [Dp]. * * @see constraints for the values in pixels. */ val maxHeight: Dp /** * Width of area inside BoxWithConstraints that is scaled based on [ContentScale] * This is width of the [Canvas] draw [ImageBitmap] */ val imageWidth: Dp /** * Height of area inside BoxWithConstraints that is scaled based on [ContentScale] * This is height of the [Canvas] draw [ImageBitmap] */ val imageHeight: Dp /** * [IntRect] that covers boundaries of [ImageBitmap] */ val rect: IntRect } internal data class ImageScopeImpl( private val density: Density, override val constraints: Constraints, override val imageWidth: Dp, override val imageHeight: Dp, override val rect: IntRect, ) : ImageScope { override val minWidth: Dp get() = with(density) { constraints.minWidth.toDp() } override val maxWidth: Dp get() = with(density) { if (constraints.hasBoundedWidth) constraints.maxWidth.toDp() else Dp.Infinity } override val minHeight: Dp get() = with(density) { constraints.minHeight.toDp() } override val maxHeight: Dp get() = with(density) { if (constraints.hasBoundedHeight) constraints.maxHeight.toDp() else Dp.Infinity } } @Composable internal fun getScaledImageBitmap( imageWidth: Dp, imageHeight: Dp, rect: IntRect, bitmap: ImageBitmap, contentScale: ContentScale ): ImageBitmap { val scaledBitmap = remember(bitmap, rect, imageWidth, imageHeight, contentScale) { // This bitmap is needed when we crop original bitmap due to scaling mode // and aspect ratio result of cropping // We might have center section of the image after cropping, and // because of that thumbLayout either should have rectangle and some // complex calculation for srcOffset and srcSide along side with touch offset // or we can create a new bitmap that only contains area bounded by rectangle runCatching { Bitmap.createBitmap( bitmap.asAndroidBitmap(), rect.left, rect.top, rect.width, rect.height ).asImageBitmap() }.getOrNull() ?: bitmap } return scaledBitmap } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/image/ImageWithConstraints.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.image import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import com.t8rin.cropper.util.getParentSize import com.t8rin.cropper.util.getScaledBitmapRect /** * A composable that lays out and draws a given [ImageBitmap]. This will attempt to * size the composable according to the [ImageBitmap]'s given width and height. However, an * optional [Modifier] parameter can be provided to adjust sizing or draw additional content (ex. * background). Any unspecified dimension will leverage the [ImageBitmap]'s size as a minimum * constraint. * * [ImageScope] returns constraints, width and height of the drawing area based on [contentScale] * and rectangle of [imageBitmap] drawn. When a bitmap is displayed scaled to fit area of Composable * space used for drawing image is represented with [ImageScope.imageWidth] and * [ImageScope.imageHeight]. * * When we display a bitmap 1000x1000px with [ContentScale.Crop] if it's cropped to 500x500px * [ImageScope.rect] returns `IntRect(250,250,750,750)`. * * @param alignment determines where image will be aligned inside [BoxWithConstraints] * This is observable when bitmap image/width ratio differs from [Canvas] that draws [ImageBitmap] * @param contentDescription text used by accessibility services to describe what this image * represents. This should always be provided unless this image is used for decorative purposes, * and does not represent a meaningful action that a user can take. This text should be * localized, such as by using [androidx.compose.ui.res.stringResource] or similar * @param contentScale how image should be scaled inside Canvas to match parent dimensions. * [ContentScale.Fit] for instance maintains src ratio and scales image to fit inside the parent. * @param alpha Opacity to be applied to [imageBitmap] from 0.0f to 1.0f representing * fully transparent to fully opaque respectively * @param colorFilter ColorFilter to apply to the [imageBitmap] when drawn into the destination * @param filterQuality Sampling algorithm applied to the [imageBitmap] when it is scaled and drawn * into the destination. The default is [FilterQuality.Low] which scales using a bilinear * sampling algorithm * @param content is a Composable that can be matched at exact position where [imageBitmap] is drawn. * This is useful for drawing thumbs, cropping or another layout that should match position * with the image that is scaled is drawn * @param drawImage flag to draw image on canvas. Some Composables might only require * the calculation and rectangle bounds of image after scaling but not drawing. * Composables like image cropper that scales or * rotates image. Drawing here again have 2 drawings overlap each other. */ @Composable internal fun ImageWithConstraints( modifier: Modifier = Modifier, imageBitmap: ImageBitmap, alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit, contentDescription: String? = null, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, drawImage: Boolean = true, content: @Composable ImageScope.() -> Unit = {} ) { val semantics = if (contentDescription != null) { Modifier.semantics { this.contentDescription = contentDescription this.role = Role.Image } } else { Modifier } BoxWithConstraints( modifier = modifier .then(semantics), contentAlignment = alignment, ) { val bitmapWidth = imageBitmap.width val bitmapHeight = imageBitmap.height val (boxWidth: Int, boxHeight: Int) = getParentSize(bitmapWidth, bitmapHeight) // Src is Bitmap, Dst is the container(Image) that Bitmap will be displayed val srcSize = Size(bitmapWidth.toFloat(), bitmapHeight.toFloat()) val dstSize = Size(boxWidth.toFloat(), boxHeight.toFloat()) val scaleFactor = contentScale.computeScaleFactor(srcSize, dstSize) // Image is the container for bitmap that is located inside Box // image bounds can be smaller or bigger than its parent based on how it's scaled val imageWidth = bitmapWidth * scaleFactor.scaleX val imageHeight = bitmapHeight * scaleFactor.scaleY val bitmapRect = getScaledBitmapRect( boxWidth = boxWidth, boxHeight = boxHeight, imageWidth = imageWidth, imageHeight = imageHeight, bitmapWidth = bitmapWidth, bitmapHeight = bitmapHeight ) ImageLayout( constraints = constraints, imageBitmap = imageBitmap, bitmapRect = bitmapRect, imageWidth = imageWidth, imageHeight = imageHeight, boxWidth = boxWidth, boxHeight = boxHeight, alpha = alpha, colorFilter = colorFilter, filterQuality = filterQuality, drawImage = drawImage, content = content ) } } @Composable private fun ImageLayout( constraints: Constraints, imageBitmap: ImageBitmap, bitmapRect: IntRect, imageWidth: Float, imageHeight: Float, boxWidth: Int, boxHeight: Int, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, drawImage: Boolean = true, content: @Composable ImageScope.() -> Unit ) { val density = LocalDensity.current // Dimensions of canvas that will draw this Bitmap val canvasWidthInDp: Dp val canvasHeightInDp: Dp with(density) { canvasWidthInDp = imageWidth.coerceAtMost(boxWidth.toFloat()).toDp() canvasHeightInDp = imageHeight.coerceAtMost(boxHeight.toFloat()).toDp() } // Send rectangle of Bitmap drawn to Canvas as bitmapRect, content scale modes like // crop might crop image from center so Rect can be such as IntRect(250,250,500,500) // canvasWidthInDp, and canvasHeightInDp are Canvas dimensions coerced to Box size // that covers Canvas val imageScopeImpl = ImageScopeImpl( density = density, constraints = constraints, imageWidth = canvasWidthInDp, imageHeight = canvasHeightInDp, rect = bitmapRect ) // width and height params for translating draw position if scaled Image dimensions are // bigger than Canvas dimensions if (drawImage) { ImageImpl( modifier = Modifier.size(canvasWidthInDp, canvasHeightInDp), imageBitmap = imageBitmap, alpha = alpha, width = imageWidth.toInt(), height = imageHeight.toInt(), colorFilter = colorFilter, filterQuality = filterQuality ) } imageScopeImpl.content() } @Composable private fun ImageImpl( modifier: Modifier, imageBitmap: ImageBitmap, width: Int, height: Int, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, ) { val bitmapWidth = imageBitmap.width val bitmapHeight = imageBitmap.height Canvas(modifier = modifier.clipToBounds()) { val canvasWidth = size.width.toInt() val canvasHeight = size.height.toInt() // Translate to left or down when Image size is bigger than this canvas. // ImageSize is bigger when scale modes like Crop is used which enlarges image // For instance 1000x1000 image can be 1000x2000 for a Canvas with 1000x1000 // so top is translated -500 to draw center of ImageBitmap translate( top = (-height + canvasHeight) / 2f, left = (-width + canvasWidth) / 2f, ) { drawImage( imageBitmap, srcSize = IntSize(bitmapWidth, bitmapHeight), dstSize = IntSize(width, height), alpha = alpha, colorFilter = colorFilter, filterQuality = filterQuality ) } } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/model/AspectRatios.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.model import com.t8rin.cropper.util.createRectShape /** * Aspect ratio list with pre-defined aspect ratios */ val aspectRatios = listOf( CropAspectRatio( title = "9:16", shape = createRectShape(AspectRatio(9 / 16f)), aspectRatio = AspectRatio(9 / 16f) ), CropAspectRatio( title = "2:3", shape = createRectShape(AspectRatio(2 / 3f)), aspectRatio = AspectRatio(2 / 3f) ), CropAspectRatio( title = "Original", shape = createRectShape(AspectRatio.Original), aspectRatio = AspectRatio.Original ), CropAspectRatio( title = "1:1", shape = createRectShape(AspectRatio(1 / 1f)), aspectRatio = AspectRatio(1 / 1f) ), CropAspectRatio( title = "16:9", shape = createRectShape(AspectRatio(16 / 9f)), aspectRatio = AspectRatio(16 / 9f) ), CropAspectRatio( title = "1.91:1", shape = createRectShape(AspectRatio(1.91f / 1f)), aspectRatio = AspectRatio(1.91f / 1f) ), CropAspectRatio( title = "3:2", shape = createRectShape(AspectRatio(3 / 2f)), aspectRatio = AspectRatio(3 / 2f) ), CropAspectRatio( title = "3:4", shape = createRectShape(AspectRatio(3 / 4f)), aspectRatio = AspectRatio(3 / 4f) ), CropAspectRatio( title = "3:5", shape = createRectShape(AspectRatio(3 / 5f)), aspectRatio = AspectRatio(3 / 5f) ) ) ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/model/CropAspectRatio.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.model import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Shape /** * Model for drawing title with shape for crop selection menu. Aspect ratio is used * for setting overlay in state and UI */ @Immutable data class CropAspectRatio( val title: String, val shape: Shape, val aspectRatio: AspectRatio = AspectRatio.Original, val icons: List = listOf() ) /** * Value class for containing aspect ratio * and [AspectRatio.Original] for comparing */ @Immutable data class AspectRatio(val value: Float) { companion object { val Original = AspectRatio(-1f) } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/model/CropData.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.model import androidx.compose.runtime.Immutable import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect /** * Class that contains information about * current zoom, pan and rotation, and rectangle of zoomed and panned area for cropping [cropRect], * and area of overlay as[overlayRect] * */ @Immutable data class CropData( val zoom: Float = 1f, val pan: Offset = Offset.Zero, val rotation: Float = 0f, val overlayRect: Rect, val cropRect: Rect ) ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutline.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.model import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import com.t8rin.cropper.util.createPolygonShape /** * Common ancestor for list of shapes, paths or images to crop inside [CropOutlineContainer] */ interface CropOutline { val id: Int val title: String } /** * Crop outline that contains a [Shape] like [RectangleShape] to draw frame for cropping */ interface CropShape : CropOutline { val shape: Shape } /** * Crop outline that contains a [Path] to draw frame for cropping */ interface CropPath : CropOutline { val path: Path } /** * Crop outline that contains a [ImageBitmap] to draw frame for cropping. And blend modes * to draw */ interface CropImageMask : CropOutline { val image: ImageBitmap } /** * Wrapper class that implements [CropOutline] and is a shape * wrapper that contains [RectangleShape] */ @Immutable data class RectCropShape( override val id: Int, override val title: String, ) : CropShape { override val shape: Shape = RectangleShape } /** * Wrapper class that implements [CropOutline] and is a shape * wrapper that contains [RoundedCornerShape] */ @Immutable data class RoundedCornerCropShape( override val id: Int, override val title: String, val cornerRadius: CornerRadiusProperties = CornerRadiusProperties(), override val shape: RoundedCornerShape = RoundedCornerShape( topStartPercent = cornerRadius.topStartPercent, topEndPercent = cornerRadius.topEndPercent, bottomEndPercent = cornerRadius.bottomEndPercent, bottomStartPercent = cornerRadius.bottomStartPercent ) ) : CropShape /** * Wrapper class that implements [CropOutline] and is a shape * wrapper that contains [CutCornerShape] */ @Immutable data class CutCornerCropShape( override val id: Int, override val title: String, val cornerRadius: CornerRadiusProperties = CornerRadiusProperties(), override val shape: CutCornerShape = CutCornerShape( topStartPercent = cornerRadius.topStartPercent, topEndPercent = cornerRadius.topEndPercent, bottomEndPercent = cornerRadius.bottomEndPercent, bottomStartPercent = cornerRadius.bottomStartPercent ) ) : CropShape /** * Wrapper class that implements [CropOutline] and is a shape * wrapper that contains [CircleShape] */ @Immutable data class OvalCropShape( override val id: Int, override val title: String, val ovalProperties: OvalProperties = OvalProperties(), override val shape: Shape = CircleShape ) : CropShape /** * Wrapper class that implements [CropOutline] and is a shape * wrapper that contains [CircleShape] */ @Immutable data class PolygonCropShape( override val id: Int, override val title: String, val polygonProperties: PolygonProperties = PolygonProperties(), override val shape: Shape = createPolygonShape(polygonProperties.sides, polygonProperties.angle) ) : CropShape /** * Wrapper class that implements [CropOutline] and is a [Path] wrapper to crop using drawable * files converted fom svg or Vector Drawable to [Path] */ @Immutable data class CustomPathOutline( override val id: Int, override val title: String, override val path: Path ) : CropPath /** * Wrapper class that implements [CropOutline] and is a [ImageBitmap] wrapper to crop * using a reference png and blend modes to crop */ @Immutable data class ImageMaskOutline( override val id: Int, override val title: String, override val image: ImageBitmap, ) : CropImageMask ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineContainer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.model /** * Interface for containing multiple [CropOutline]s, currently selected item and index * for displaying on settings UI */ interface CropOutlineContainer { var selectedIndex: Int val outlines: List val selectedItem: O get() = outlines[selectedIndex] val size: Int get() = outlines.size } /** * Container for [RectCropShape] */ data class RectOutlineContainer( override var selectedIndex: Int = 0, override val outlines: List ) : CropOutlineContainer /** * Container for [RoundedCornerCropShape]s */ data class RoundedRectOutlineContainer( override var selectedIndex: Int = 0, override val outlines: List ) : CropOutlineContainer /** * Container for [CutCornerCropShape]s */ data class CutCornerRectOutlineContainer( override var selectedIndex: Int = 0, override val outlines: List ) : CropOutlineContainer /** * Container for [OvalCropShape]s */ data class OvalOutlineContainer( override var selectedIndex: Int = 0, override val outlines: List ) : CropOutlineContainer /** * Container for [PolygonCropShape]s */ data class PolygonOutlineContainer( override var selectedIndex: Int = 0, override val outlines: List ) : CropOutlineContainer /** * Container for [CustomPathOutline]s */ data class CustomOutlineContainer( override var selectedIndex: Int = 0, override val outlines: List ) : CropOutlineContainer /** * Container for [ImageMaskOutline]s */ data class ImageMaskOutlineContainer( override var selectedIndex: Int = 0, override val outlines: List ) : CropOutlineContainer ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/model/CropOutlineProperties.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.model import androidx.compose.runtime.Immutable import androidx.compose.ui.geometry.Offset @Immutable data class CornerRadiusProperties( val topStartPercent: Int = 20, val topEndPercent: Int = 20, val bottomStartPercent: Int = 20, val bottomEndPercent: Int = 20 ) @Immutable data class PolygonProperties( val sides: Int = 6, val angle: Float = 0f, val offset: Offset = Offset.Zero ) @Immutable data class OvalProperties( val startAngle: Float = 0f, val sweepAngle: Float = 360f, val offset: Offset = Offset.Zero ) ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/model/OutlineType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.model enum class OutlineType { Rect, RoundedRect, CutCorner, Oval, Polygon, Custom, ImageMask } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/settings/CropDefaults.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.settings import androidx.compose.runtime.Immutable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.t8rin.cropper.ImageCropper import com.t8rin.cropper.crop import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.model.CropOutline import com.t8rin.cropper.model.OutlineType import com.t8rin.cropper.model.aspectRatios import com.t8rin.cropper.state.CropState /** * Contains the default values used by [ImageCropper] */ object CropDefaults { /** * Properties effect crop behavior that should be passed to [CropState] */ fun properties( cropType: CropType = CropType.Dynamic, handleSize: Float = 60f, middleHandleSize: Float = handleSize * 1.5f, maxZoom: Float = 10f, contentScale: ContentScale = ContentScale.Fit, cropOutlineProperty: CropOutlineProperty, aspectRatio: AspectRatio = aspectRatios[2].aspectRatio, overlayRatio: Float = .9f, pannable: Boolean = true, fling: Boolean = false, zoomable: Boolean = true, rotatable: Boolean = false, minDimension: IntSize? = null, fixedAspectRatio: Boolean = false, ): CropProperties { return CropProperties( cropType = cropType, handleSize = handleSize, middleHandleSize = middleHandleSize, contentScale = contentScale, cropOutlineProperty = cropOutlineProperty, maxZoom = maxZoom, aspectRatio = aspectRatio, overlayRatio = overlayRatio, pannable = pannable, fling = fling, zoomable = zoomable, rotatable = rotatable, minDimension = minDimension, fixedAspectRatio = fixedAspectRatio, ) } /** * Style is cosmetic changes that don't effect how [CropState] behaves because of that * none of these properties are passed to [CropState] */ fun style( drawOverlay: Boolean = true, drawGrid: Boolean = true, strokeWidth: Dp = 1.dp, overlayColor: Color = DefaultOverlayColor, handleColor: Color = DefaultHandleColor, backgroundColor: Color = DefaultBackgroundColor ): CropStyle { return CropStyle( drawOverlay = drawOverlay, drawGrid = drawGrid, strokeWidth = strokeWidth, overlayColor = overlayColor, handleColor = handleColor, backgroundColor = backgroundColor ) } } /** * Data class for selecting cropper properties. Fields of this class control inner work * of [CropState] while some such as [cropType], [aspectRatio], [handleSize] * is shared between ui and state. */ @Immutable data class CropProperties( val cropType: CropType, val handleSize: Float, val middleHandleSize: Float, val contentScale: ContentScale, val cropOutlineProperty: CropOutlineProperty, val aspectRatio: AspectRatio, val overlayRatio: Float, val pannable: Boolean, val fling: Boolean, val rotatable: Boolean, val zoomable: Boolean, val maxZoom: Float, val minDimension: IntSize? = null, val fixedAspectRatio: Boolean = false, ) /** * Data class for cropper styling only. None of the properties of this class is used * by [CropState] or [Modifier.crop] */ @Immutable data class CropStyle( val drawOverlay: Boolean, val drawGrid: Boolean, val strokeWidth: Dp, val overlayColor: Color, val handleColor: Color, val backgroundColor: Color, val cropTheme: CropTheme = CropTheme.Dark ) /** * Property for passing [CropOutline] between settings UI to [ImageCropper] */ @Immutable data class CropOutlineProperty( val outlineType: OutlineType, val cropOutline: CropOutline ) /** * Light, Dark or system controlled theme */ enum class CropTheme { Light, Dark, System } private val DefaultBackgroundColor = Color(0x99000000) private val DefaultOverlayColor = Color.Gray private val DefaultHandleColor = Color.White ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/settings/CropType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.settings /** * Type of cropping operation * * If [CropType.Static] is selected overlay is stationary, image is movable. * If [CropType.Dynamic] is selected overlay can be moved, resized, image is stationary. */ enum class CropType { Static, Dynamic } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/settings/Paths.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.settings import androidx.compose.ui.graphics.Path object Paths { val Favorite get() = Path().apply { moveTo(12.0f, 21.35f) relativeLineTo(-1.45f, -1.32f) cubicTo(5.4f, 15.36f, 2.0f, 12.28f, 2.0f, 8.5f) cubicTo(2.0f, 5.42f, 4.42f, 3.0f, 7.5f, 3.0f) relativeCubicTo(1.74f, 0.0f, 3.41f, 0.81f, 4.5f, 2.09f) cubicTo(13.09f, 3.81f, 14.76f, 3.0f, 16.5f, 3.0f) cubicTo(19.58f, 3.0f, 22.0f, 5.42f, 22.0f, 8.5f) relativeCubicTo(0.0f, 3.78f, -3.4f, 6.86f, -8.55f, 11.54f) lineTo(12.0f, 21.35f) close() } val Star = Path().apply { moveTo(12.0f, 17.27f) lineTo(18.18f, 21.0f) relativeLineTo(-1.64f, -7.03f) lineTo(22.0f, 9.24f) relativeLineTo(-7.19f, -0.61f) lineTo(12.0f, 2.0f) lineTo(9.19f, 8.63f) lineTo(2.0f, 9.24f) relativeLineTo(5.46f, 4.73f) lineTo(5.82f, 21.0f) close() } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/state/CropState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.state import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.unit.IntSize import com.t8rin.cropper.settings.CropProperties import com.t8rin.cropper.settings.CropType /** * Create and [remember] the [CropState] based on the currently appropriate transform * configuration to allow changing pan, zoom, and rotation. * @param imageSize size of the **Bitmap** * @param containerSize size of the Composable that draws **Bitmap** * @param cropProperties wrapper class that contains crop state properties such as * crop type, * @param keys are used to reset remember block to initial calculations. This can be used * when image, contentScale or any property changes which requires values to be reset to initial * values */ @Composable fun rememberCropState( imageSize: IntSize, containerSize: IntSize, drawAreaSize: IntSize, cropProperties: CropProperties, vararg keys: Any? ): CropState { // Properties of crop state val handleSize = cropProperties.handleSize val cropType = cropProperties.cropType val aspectRatio = cropProperties.aspectRatio val overlayRatio = cropProperties.overlayRatio val maxZoom = cropProperties.maxZoom val fling = cropProperties.fling val zoomable = cropProperties.zoomable val pannable = cropProperties.pannable val rotatable = cropProperties.rotatable val minDimension = cropProperties.minDimension val fixedAspectRatio = cropProperties.fixedAspectRatio return remember(*keys) { when (cropType) { CropType.Static -> { StaticCropState( imageSize = imageSize, containerSize = containerSize, drawAreaSize = drawAreaSize, aspectRatio = aspectRatio, overlayRatio = overlayRatio, maxZoom = maxZoom, fling = fling, zoomable = zoomable, pannable = pannable, rotatable = rotatable, limitPan = false ) } else -> { DynamicCropState( imageSize = imageSize, containerSize = containerSize, drawAreaSize = drawAreaSize, aspectRatio = aspectRatio, overlayRatio = overlayRatio, maxZoom = maxZoom, handleSize = handleSize, fling = fling, zoomable = zoomable, pannable = pannable, rotatable = rotatable, limitPan = true, minDimension = minDimension, fixedAspectRatio = fixedAspectRatio, ) } } } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/state/CropStateImpl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.state import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.tween import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.unit.IntSize import com.t8rin.cropper.TouchRegion import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.model.CropData import com.t8rin.cropper.settings.CropProperties val CropState.cropData: CropData get() = CropData( zoom = animatableZoom.targetValue, pan = Offset(animatablePanX.targetValue, animatablePanY.targetValue), rotation = animatableRotation.targetValue, overlayRect = overlayRect, cropRect = cropRect ) /** * Base class for crop operations. Any class that extends this class gets access to pan, zoom, * rotation values and animations via [TransformState], fling and moving back to bounds animations. * @param imageSize size of the **Bitmap** * @param containerSize size of the Composable that draws **Bitmap**. This is full size * of the Composable. [drawAreaSize] can be smaller than [containerSize] initially based * on content scale of Image composable * @param drawAreaSize size of the area that **Bitmap** is drawn * @param maxZoom maximum zoom value * @param fling when set to true dragging pointer builds up velocity. When last * pointer leaves Composable a movement invoked against friction till velocity drops below * to threshold * @param zoomable when set to true zoom is enabled * @param pannable when set to true pan is enabled * @param rotatable when set to true rotation is enabled * @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating * empty space on sides or edges of parent */ abstract class CropState internal constructor( imageSize: IntSize, containerSize: IntSize, drawAreaSize: IntSize, maxZoom: Float, internal var fling: Boolean = true, internal var aspectRatio: AspectRatio, internal var overlayRatio: Float, zoomable: Boolean = true, pannable: Boolean = true, rotatable: Boolean = false, limitPan: Boolean = false ) : TransformState( imageSize = imageSize, containerSize = containerSize, drawAreaSize = drawAreaSize, initialZoom = 1f, initialRotation = 0f, maxZoom = maxZoom, zoomable = zoomable, pannable = pannable, rotatable = rotatable, limitPan = limitPan ) { private val animatableRectOverlay = Animatable( getOverlayFromAspectRatio( containerSize.width.toFloat(), containerSize.height.toFloat(), drawAreaSize.width.toFloat(), aspectRatio, overlayRatio ), Rect.VectorConverter ) val overlayRect: Rect get() = animatableRectOverlay.value var cropRect: Rect = Rect.Zero get() = getCropRectangle( imageSize.width, imageSize.height, drawAreaRect, animatableRectOverlay.targetValue ) private set private var initialized: Boolean = false /** * Region of touch inside, corners of or outside of overlay rectangle */ var touchRegion by mutableStateOf(TouchRegion.None) internal suspend fun init() { // When initial aspect ratio doesn't match drawable area // overlay gets updated so updates draw area as well animateTransformationToOverlayBounds(overlayRect, animate = true) initialized = true } /** * Update properties of [CropState] and animate to valid intervals if required */ internal open suspend fun updateProperties( cropProperties: CropProperties, forceUpdate: Boolean = false ) { if (!initialized) return fling = cropProperties.fling pannable = cropProperties.pannable zoomable = cropProperties.zoomable rotatable = cropProperties.rotatable val maxZoom = cropProperties.maxZoom // Update overlay rectangle val aspectRatio = cropProperties.aspectRatio // Ratio of overlay to screen val overlayRatio = cropProperties.overlayRatio if ( this.aspectRatio.value != aspectRatio.value || maxZoom != zoomMax || this.overlayRatio != overlayRatio || forceUpdate ) { this.aspectRatio = aspectRatio this.overlayRatio = overlayRatio zoomMax = maxZoom animatableZoom.updateBounds(zoomMin, zoomMax) val currentZoom = if (zoom > zoomMax) zoomMax else zoom // Set new zoom snapZoomTo(currentZoom) // Calculate new region of image is drawn. It can be drawn left of 0 and right // of container width depending on transformation drawAreaRect = updateImageDrawRectFromTransformation() // Update overlay rectangle based on current draw area and new aspect ratio animateOverlayRectTo( getOverlayFromAspectRatio( containerSize.width.toFloat(), containerSize.height.toFloat(), drawAreaSize.width.toFloat(), aspectRatio, overlayRatio ) ) } // Animate zoom, pan, rotation to move draw area to cover overlay rect // inside draw area rect animateTransformationToOverlayBounds(overlayRect, animate = true) } /** * Animate overlay rectangle to target value */ internal suspend fun animateOverlayRectTo( rect: Rect, animationSpec: AnimationSpec = tween(400) ) { animatableRectOverlay.animateTo( targetValue = rect, animationSpec = animationSpec ) } /** * Snap overlay rectangle to target value */ internal suspend fun snapOverlayRectTo(rect: Rect) { animatableRectOverlay.snapTo(rect) } /* Touch gestures */ internal abstract suspend fun onDown(change: PointerInputChange) internal abstract suspend fun onMove(changes: List) internal abstract suspend fun onUp(change: PointerInputChange) /* Transform gestures */ internal abstract suspend fun onGesture( centroid: Offset, panChange: Offset, zoomChange: Float, rotationChange: Float, mainPointer: PointerInputChange, changes: List ) internal abstract suspend fun onGestureStart() internal abstract suspend fun onGestureEnd(onBoundsCalculated: () -> Unit) // Double Tap internal abstract suspend fun onDoubleTap( offset: Offset, zoom: Float = 1f, onAnimationEnd: () -> Unit ) /** * Check if area that image is drawn covers [overlayRect] */ internal fun isOverlayInImageDrawBounds(): Boolean { return drawAreaRect.left <= overlayRect.left && drawAreaRect.top <= overlayRect.top && drawAreaRect.right >= overlayRect.right && drawAreaRect.bottom >= overlayRect.bottom } /** * Check if [rect] is inside container bounds */ internal fun isRectInContainerBounds(rect: Rect): Boolean { return rect.left >= 0 && rect.right <= containerSize.width && rect.top >= 0 && rect.bottom <= containerSize.height } /** * Update rectangle for area that image is drawn. This rect changes when zoom and * pan changes and position of image changes on screen as result of transformation. * * This function is called * * * when [onGesture] is called to update rect when zoom or pan changes * and if [fling] is true just after **fling** gesture starts with target * value in [StaticCropState]. * * * when [updateProperties] is called in [CropState] * * * when [onUp] is called in [DynamicCropState] to match [overlayRect] that could be * changed and animated if it's out of [containerSize] bounds or its grow * bigger than previous size */ internal fun updateImageDrawRectFromTransformation(): Rect { val containerWidth = containerSize.width val containerHeight = containerSize.height val originalDrawWidth = drawAreaSize.width val originalDrawHeight = drawAreaSize.height val panX = animatablePanX.targetValue val panY = animatablePanY.targetValue val left = (containerWidth - originalDrawWidth) / 2 val top = (containerHeight - originalDrawHeight) / 2 val zoom = animatableZoom.targetValue val newWidth = originalDrawWidth * zoom val newHeight = originalDrawHeight * zoom return Rect( offset = Offset( left - (newWidth - originalDrawWidth) / 2 + panX, top - (newHeight - originalDrawHeight) / 2 + panY, ), size = Size(newWidth, newHeight) ) } // TODO Add resetting back to bounds for rotated state as well /** * Resets to bounds with animation and resets tracking for fling animation. * Changes pan, zoom and rotation to valid bounds based on [drawAreaRect] and [overlayRect] */ internal suspend fun animateTransformationToOverlayBounds( overlayRect: Rect, animate: Boolean, animationSpec: AnimationSpec = tween(400) ) { // Keep current zoom // val zoom = zoom.coerceAtLeast(1f) // Calculate new pan based on overlay val newDrawAreaRect = calculateValidImageDrawRect(overlayRect, drawAreaRect) val newZoom = calculateNewZoom(oldRect = drawAreaRect, newRect = newDrawAreaRect, zoom = zoom) val leftChange = newDrawAreaRect.left - drawAreaRect.left val topChange = newDrawAreaRect.top - drawAreaRect.top val widthChange = newDrawAreaRect.width - drawAreaRect.width val heightChange = newDrawAreaRect.height - drawAreaRect.height val panXChange = leftChange + widthChange / 2 val panYChange = topChange + heightChange / 2 val newPanX = pan.x + panXChange val newPanY = pan.y + panYChange // Update draw area based on new pan and zoom values drawAreaRect = newDrawAreaRect if (animate) { resetWithAnimation( pan = Offset(newPanX, newPanY), zoom = newZoom, animationSpec = animationSpec ) } else { snapPanXto(newPanX) snapPanYto(newPanY) snapZoomTo(newZoom) } resetTracking() } /** * If new overlay is bigger, when crop type is dynamic, we need to increase zoom at least * size of bigger dimension for image draw area([drawAreaRect]) to cover overlay([overlayRect]) */ private fun calculateNewZoom(oldRect: Rect, newRect: Rect, zoom: Float): Float { if (oldRect.size == Size.Zero || newRect.size == Size.Zero) return zoom val widthChange = (newRect.width / oldRect.width) .coerceAtLeast(1f) val heightChange = (newRect.height / oldRect.height) .coerceAtLeast(1f) return widthChange.coerceAtLeast(heightChange) * zoom } /** * Calculate valid position for image draw rectangle when pointer is up. Overlay rectangle * should fit inside draw image rectangle to have valid bounds when calculation is completed. * * @param rectOverlay rectangle of overlay that is used for cropping * @param rectDrawArea rectangle of image that is being drawn */ private fun calculateValidImageDrawRect(rectOverlay: Rect, rectDrawArea: Rect): Rect { var width = rectDrawArea.width var height = rectDrawArea.height if (width < rectOverlay.width) { width = rectOverlay.width } if (height < rectOverlay.height) { height = rectOverlay.height } var rectImageArea = Rect(offset = rectDrawArea.topLeft, size = Size(width, height)) if (rectImageArea.left > rectOverlay.left) { rectImageArea = rectImageArea.translate(rectOverlay.left - rectImageArea.left, 0f) } if (rectImageArea.right < rectOverlay.right) { rectImageArea = rectImageArea.translate(rectOverlay.right - rectImageArea.right, 0f) } if (rectImageArea.top > rectOverlay.top) { rectImageArea = rectImageArea.translate(0f, rectOverlay.top - rectImageArea.top) } if (rectImageArea.bottom < rectOverlay.bottom) { rectImageArea = rectImageArea.translate(0f, rectOverlay.bottom - rectImageArea.bottom) } return rectImageArea } /** * Create [Rect] to draw overlay based on selected aspect ratio */ internal fun getOverlayFromAspectRatio( containerWidth: Float, containerHeight: Float, drawAreaWidth: Float, aspectRatio: AspectRatio, coefficient: Float ): Rect { if (aspectRatio == AspectRatio.Original) { val imageAspectRatio = imageSize.width.toFloat() / imageSize.height.toFloat() // Maximum width and height overlay rectangle can be measured with val overlayWidthMax = drawAreaWidth.coerceAtMost(containerWidth * coefficient) val overlayHeightMax = (overlayWidthMax / imageAspectRatio).coerceAtMost(containerHeight * coefficient) val offsetX = (containerWidth - overlayWidthMax) / 2f val offsetY = (containerHeight - overlayHeightMax) / 2f return Rect( offset = Offset(offsetX, offsetY), size = Size(overlayWidthMax, overlayHeightMax) ) } val overlayWidthMax = containerWidth * coefficient val overlayHeightMax = containerHeight * coefficient val aspectRatioValue = aspectRatio.value var width = overlayWidthMax var height = overlayWidthMax / aspectRatioValue if (height > overlayHeightMax) { height = overlayHeightMax width = height * aspectRatioValue } val offsetX = (containerWidth - width) / 2f val offsetY = (containerHeight - height) / 2f return Rect(offset = Offset(offsetX, offsetY), size = Size(width, height)) } /** * Get crop rectangle */ private fun getCropRectangle( bitmapWidth: Int, bitmapHeight: Int, drawAreaRect: Rect, overlayRect: Rect ): Rect { if (drawAreaRect == Rect.Zero || overlayRect == Rect.Zero) return Rect( offset = Offset.Zero, Size(bitmapWidth.toFloat(), bitmapHeight.toFloat()) ) // Calculate latest image draw area based on overlay position // This is valid rectangle that contains crop area inside overlay val newRect = calculateValidImageDrawRect(overlayRect, drawAreaRect) val overlayWidth = overlayRect.width val overlayHeight = overlayRect.height val drawAreaWidth = newRect.width val drawAreaHeight = newRect.height val widthRatio = overlayWidth / drawAreaWidth val heightRatio = overlayHeight / drawAreaHeight val diffLeft = overlayRect.left - newRect.left val diffTop = overlayRect.top - newRect.top val croppedBitmapLeft = (diffLeft * (bitmapWidth / drawAreaWidth)) val croppedBitmapTop = (diffTop * (bitmapHeight / drawAreaHeight)) val croppedBitmapWidth = bitmapWidth * widthRatio val croppedBitmapHeight = bitmapHeight * heightRatio return Rect( offset = Offset(croppedBitmapLeft, croppedBitmapTop), size = Size(croppedBitmapWidth, croppedBitmapHeight) ) } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/state/DynamicCropState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.state import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.positionChangeIgnoreConsumed import androidx.compose.ui.unit.IntSize import com.t8rin.cropper.TouchRegion import com.t8rin.cropper.model.AspectRatio import com.t8rin.cropper.settings.CropProperties import kotlinx.coroutines.coroutineScope import kotlin.math.roundToInt /** * State for cropper with dynamic overlay. Overlay of this state can be moved or resized * using handles or touching inner position of overlay. When overlay overflow out of image bounds * or moves out of bounds it animates back to valid size and position * * @param handleSize size of the handle to control, move or scale dynamic overlay * @param imageSize size of the **Bitmap** * @param containerSize size of the Composable that draws **Bitmap** * @param maxZoom maximum zoom value * @param fling when set to true dragging pointer builds up velocity. When last * pointer leaves Composable a movement invoked against friction till velocity drops below * to threshold * @param zoomable when set to true zoom is enabled * @param pannable when set to true pan is enabled * @param rotatable when set to true rotation is enabled * @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating * @param minDimension minimum size of the overlay, if null defaults to handleSize * 2 * @param fixedAspectRatio when set to true aspect ratio of overlay is fixed * empty space on sides or edges of parent */ class DynamicCropState internal constructor( private var handleSize: Float, imageSize: IntSize, containerSize: IntSize, drawAreaSize: IntSize, aspectRatio: AspectRatio, overlayRatio: Float, maxZoom: Float, fling: Boolean, zoomable: Boolean, pannable: Boolean, rotatable: Boolean, limitPan: Boolean, private val minDimension: IntSize?, private val fixedAspectRatio: Boolean, ) : CropState( imageSize = imageSize, containerSize = containerSize, drawAreaSize = drawAreaSize, aspectRatio = aspectRatio, overlayRatio = overlayRatio, maxZoom = maxZoom, fling = fling, zoomable = zoomable, pannable = pannable, rotatable = rotatable, limitPan = limitPan ) { /** * Rectangle that covers bounds of Composable. This is a rectangle uses [containerSize] as * size and [Offset.Zero] as top left corner */ private val rectBounds = Rect( offset = Offset.Zero, size = Size(containerSize.width.toFloat(), containerSize.height.toFloat()) ) // This rectangle is needed to set bounds set at first touch position while // moving to constraint current bounds to temp one from first down // When pointer is up private var rectTemp = Rect.Zero // Touch position for edge of the rectangle, used for not jumping to edge of rectangle // when user moves a handle. We set positionActual as position of selected handle // and using this distance as offset to not have a jump from touch position private var distanceToEdgeFromTouch = Offset.Zero private var doubleTapped = false // Check if transform gesture has been invoked // inside overlay but with multiple pointers to zoom private var gestureInvoked = false override suspend fun updateProperties(cropProperties: CropProperties, forceUpdate: Boolean) { handleSize = cropProperties.handleSize super.updateProperties(cropProperties, forceUpdate) } override suspend fun onDown(change: PointerInputChange) { rectTemp = overlayRect.copy() val position = change.position val touchPositionScreenX = position.x val touchPositionScreenY = position.y val touchPositionOnScreen = Offset(touchPositionScreenX, touchPositionScreenY) // Get whether user touched outside, handles of rectangle or inner region or overlay // rectangle. Depending on where is touched we can move or scale overlay touchRegion = getTouchRegion( position = touchPositionOnScreen, rect = overlayRect, threshold = handleSize * 1.5f ) // This is the difference between touch position and edge // This is required for not moving edge of draw rect to touch position on move distanceToEdgeFromTouch = getDistanceToEdgeFromTouch(touchRegion, rectTemp, touchPositionOnScreen) } override suspend fun onMove(changes: List) { if (changes.isEmpty()) { touchRegion = TouchRegion.None return } gestureInvoked = changes.size > 1 && (touchRegion == TouchRegion.Inside) // If overlay is touched and pointer size is one update // or pointer size is bigger than one but touched any handles update if (touchRegion != TouchRegion.None && changes.size == 1 && !gestureInvoked) { val change = changes.first() // Default min dimension is handle size * 5 val doubleHandleSize = handleSize * 5 val defaultMinDimension = IntSize(doubleHandleSize.roundToInt(), doubleHandleSize.roundToInt()) // update overlay rectangle based on where its touched and touch position to corners // This function moves and/or scales overlay rectangle val newRect = updateOverlayRect( distanceToEdgeFromTouch = distanceToEdgeFromTouch, touchRegion = touchRegion, minDimension = minDimension ?: defaultMinDimension, rectTemp = rectTemp, overlayRect = overlayRect, change = change, aspectRatio = getAspectRatio(), fixedAspectRatio = fixedAspectRatio, ) snapOverlayRectTo(newRect) } } private fun getAspectRatio(): Float { return if (aspectRatio == AspectRatio.Original) { imageSize.width / imageSize.height.toFloat() } else { aspectRatio.value } } override suspend fun onUp(change: PointerInputChange) = coroutineScope { if (touchRegion != TouchRegion.None) { val isInContainerBounds = isRectInContainerBounds(overlayRect) if (!isInContainerBounds) { // Calculate new overlay since it's out of Container bounds rectTemp = calculateOverlayRectInBounds(rectBounds, overlayRect) // Animate overlay to new bounds inside container animateOverlayRectTo(rectTemp) } // Update and animate pan, zoom and image draw area after overlay position is updated animateTransformationToOverlayBounds(overlayRect, true) // Update image draw area after animating pan, zoom or rotation is completed drawAreaRect = updateImageDrawRectFromTransformation() touchRegion = TouchRegion.None } gestureInvoked = false } override suspend fun onGesture( centroid: Offset, panChange: Offset, zoomChange: Float, rotationChange: Float, mainPointer: PointerInputChange, changes: List ) { if (touchRegion == TouchRegion.None || gestureInvoked) { doubleTapped = false val newPan = if (gestureInvoked) Offset.Zero else panChange updateTransformState( centroid = centroid, zoomChange = zoomChange, panChange = newPan, rotationChange = rotationChange ) // Update image draw rectangle based on pan, zoom or rotation change drawAreaRect = updateImageDrawRectFromTransformation() // Fling Gesture if (pannable && fling) { if (changes.size == 1) { addPosition(mainPointer.uptimeMillis, mainPointer.position) } } } } override suspend fun onGestureStart() = Unit override suspend fun onGestureEnd(onBoundsCalculated: () -> Unit) { if (touchRegion == TouchRegion.None || gestureInvoked) { // Gesture end might be called after second tap and we don't want to fling // or animate back to valid bounds when doubled tapped if (!doubleTapped) { if (pannable && fling && !gestureInvoked && zoom > 1) { fling { // We get target value on start instead of updating bounds after // gesture has finished drawAreaRect = updateImageDrawRectFromTransformation() onBoundsCalculated() } } else { onBoundsCalculated() } animateTransformationToOverlayBounds(overlayRect, animate = true) } } } override suspend fun onDoubleTap( offset: Offset, zoom: Float, onAnimationEnd: () -> Unit ) { doubleTapped = true if (fling) { resetTracking() } resetWithAnimation(pan = pan, zoom = zoom, rotation = rotation) // We get target value on start instead of updating bounds after // gesture has finished drawAreaRect = updateImageDrawRectFromTransformation() if (!isOverlayInImageDrawBounds()) { // Moves rectangle to bounds inside drawArea Rect while keeping aspect ratio // of current overlay rect animateOverlayRectTo( getOverlayFromAspectRatio( containerSize.width.toFloat(), containerSize.height.toFloat(), drawAreaSize.width.toFloat(), aspectRatio, overlayRatio ) ) animateTransformationToOverlayBounds(overlayRect, false) } onAnimationEnd() } // //TODO Change pan when zoom is bigger than 1f and touchRegion is inside overlay rect // private suspend fun moveOverlayToBounds(change: PointerInputChange, newRect: Rect) { // val bounds = drawAreaRect // // val positionChange = change.positionChangeIgnoreConsumed() // // // When zoom is bigger than 100% and dynamic overlay is not at any edge of // // image we can pan in the same direction motion goes towards when touch region // // of rectangle is not one of the handles but region inside // val isPanRequired = touchRegion == TouchRegion.Inside && zoom > 1f // // // Overlay moving right // if (isPanRequired && newRect.right < bounds.right) { // println("Moving right newRect $newRect, bounds: $bounds") // snapOverlayRectTo(newRect.translate(-positionChange.x, 0f)) // snapPanXto(pan.x - positionChange.x * zoom) // // Overlay moving left // } else if (isPanRequired && pan.x < bounds.left && newRect.left <= 0f) { // snapOverlayRectTo(newRect.translate(-positionChange.x, 0f)) // snapPanXto(pan.x - positionChange.x * zoom) // } else if (isPanRequired && pan.y < bounds.top && newRect.top <= 0f) { // // Overlay moving top // snapOverlayRectTo(newRect.translate(0f, -positionChange.y)) // snapPanYto(pan.y - positionChange.y * zoom) // } else if (isPanRequired && -pan.y < bounds.bottom && newRect.bottom >= containerSize.height) { // // Overlay moving bottom // snapOverlayRectTo(newRect.translate(0f, -positionChange.y)) // snapPanYto(pan.y - positionChange.y * zoom) // } else { // snapOverlayRectTo(newRect) // } // if (touchRegion != TouchRegion.None) { // change.consume() // } // } /** * When pointer is up calculate valid position and size overlay can be updated to inside * a virtual rect between `topLeft = (0,0)` to `bottomRight=(containerWidth, containerHeight)` * * [overlayRect] might be shrunk or moved up/down/left/right to container bounds when * it's out of Composable region */ private fun calculateOverlayRectInBounds(rectBounds: Rect, rectCurrent: Rect): Rect { var width = rectCurrent.width var height = rectCurrent.height if (width > rectBounds.width) { width = rectBounds.width } if (height > rectBounds.height) { height = rectBounds.height } var rect = Rect(offset = rectCurrent.topLeft, size = Size(width, height)) if (rect.left < rectBounds.left) { rect = rect.translate(rectBounds.left - rect.left, 0f) } if (rect.top < rectBounds.top) { rect = rect.translate(0f, rectBounds.top - rect.top) } if (rect.right > rectBounds.right) { rect = rect.translate(rectBounds.right - rect.right, 0f) } if (rect.bottom > rectBounds.bottom) { rect = rect.translate(0f, rectBounds.bottom - rect.bottom) } return rect } private fun updateOverlayRect( distanceToEdgeFromTouch: Offset, touchRegion: TouchRegion, minDimension: IntSize, rectTemp: Rect, overlayRect: Rect, change: PointerInputChange, aspectRatio: Float, fixedAspectRatio: Boolean, ): Rect { val position = change.position val screenX = position.x + distanceToEdgeFromTouch.x val screenY = position.y + distanceToEdgeFromTouch.y val minW = minDimension.width.toFloat() val minH = minDimension.height.toFloat() val bounds = rectBounds fun clampNonFixed(left: Float, top: Float, right: Float, bottom: Float): Rect { val l = left.coerceIn(bounds.left, bounds.right - minW) val t = top.coerceIn(bounds.top, bounds.bottom - minH) val r = right.coerceIn(l + minW, bounds.right) val b = bottom.coerceIn(t + minH, bounds.bottom) return Rect(l, t, r, b) } fun anchoredCornerTopLeft(anchorR: Float, anchorB: Float, candidateLeft: Float): Rect { var width = (anchorR - candidateLeft).coerceAtLeast(minW) val maxWidth = (anchorR - bounds.left).coerceAtLeast(minW) width = width.coerceAtMost(maxWidth) var height = width / aspectRatio val maxHeight = (anchorB - bounds.top).coerceAtLeast(minH) if (height > maxHeight) { height = maxHeight width = (height * aspectRatio).coerceAtLeast(minW) } val left = anchorR - width val top = anchorB - height return Rect( left.coerceAtLeast(bounds.left), top.coerceAtLeast(bounds.top), anchorR.coerceAtMost(bounds.right), anchorB.coerceAtMost(bounds.bottom) ) } fun anchoredCornerBottomLeft(anchorR: Float, anchorT: Float, candidateLeft: Float): Rect { var width = (anchorR - candidateLeft).coerceAtLeast(minW) val maxWidth = (anchorR - bounds.left).coerceAtLeast(minW) width = width.coerceAtMost(maxWidth) var height = width / aspectRatio val maxHeight = (bounds.bottom - anchorT).coerceAtLeast(minH) if (height > maxHeight) { height = maxHeight width = (height * aspectRatio).coerceAtLeast(minW) } val left = anchorR - width val bottom = anchorT + height return Rect( left.coerceAtLeast(bounds.left), anchorT.coerceAtLeast(bounds.top), anchorR.coerceAtMost(bounds.right), bottom.coerceAtMost(bounds.bottom) ) } fun anchoredCornerTopRight(anchorL: Float, anchorB: Float, candidateRight: Float): Rect { var width = (candidateRight - anchorL).coerceAtLeast(minW) val maxWidth = (bounds.right - anchorL).coerceAtLeast(minW) width = width.coerceAtMost(maxWidth) var height = width / aspectRatio val maxHeight = (anchorB - bounds.top).coerceAtLeast(minH) if (height > maxHeight) { height = maxHeight width = (height * aspectRatio).coerceAtLeast(minW) } val right = anchorL + width val top = anchorB - height return Rect( anchorL.coerceAtLeast(bounds.left), top.coerceAtLeast(bounds.top), right.coerceAtMost(bounds.right), anchorB.coerceAtMost(bounds.bottom) ) } fun anchoredCornerBottomRight(anchorL: Float, anchorT: Float, candidateRight: Float): Rect { var width = (candidateRight - anchorL).coerceAtLeast(minW) val maxWidth = (bounds.right - anchorL).coerceAtLeast(minW) width = width.coerceAtMost(maxWidth) var height = width / aspectRatio val maxHeight = (bounds.bottom - anchorT).coerceAtLeast(minH) if (height > maxHeight) { height = maxHeight width = (height * aspectRatio).coerceAtLeast(minW) } val right = anchorL + width val bottom = anchorT + height return Rect( anchorL.coerceAtLeast(bounds.left), anchorT.coerceAtLeast(bounds.top), right.coerceAtMost(bounds.right), bottom.coerceAtMost(bounds.bottom) ) } fun centerTop(anchorB: Float, candidateTop: Float): Rect { var height = (anchorB - candidateTop).coerceAtLeast(minH) val maxHeight = (anchorB - bounds.top).coerceAtLeast(minH) height = height.coerceAtMost(maxHeight) var width = height * aspectRatio val halfMaxWidth = minOf(bounds.right - rectTemp.center.x, rectTemp.center.x - bounds.left) val maxWidth = halfMaxWidth * 2f if (width > maxWidth) { width = maxWidth height = (width / aspectRatio).coerceAtLeast(minH) } val left = rectTemp.center.x - width / 2f val right = rectTemp.center.x + width / 2f val top = anchorB - height return Rect( left.coerceAtLeast(bounds.left), top.coerceAtLeast(bounds.top), right.coerceAtMost(bounds.right), anchorB.coerceAtMost(bounds.bottom) ) } fun centerBottom(anchorT: Float, candidateBottom: Float): Rect { var height = (candidateBottom - anchorT).coerceAtLeast(minH) val maxHeight = (bounds.bottom - anchorT).coerceAtLeast(minH) height = height.coerceAtMost(maxHeight) var width = height * aspectRatio val halfMaxWidth = minOf(bounds.right - rectTemp.center.x, rectTemp.center.x - bounds.left) val maxWidth = halfMaxWidth * 2f if (width > maxWidth) { width = maxWidth height = (width / aspectRatio).coerceAtLeast(minH) } val left = rectTemp.center.x - width / 2f val right = rectTemp.center.x + width / 2f val bottom = anchorT + height return Rect( left.coerceAtLeast(bounds.left), anchorT.coerceAtLeast(bounds.top), right.coerceAtMost(bounds.right), bottom.coerceAtMost(bounds.bottom) ) } fun centerLeft(anchorR: Float, candidateLeft: Float): Rect { var width = (anchorR - candidateLeft).coerceAtLeast(minW) val maxWidth = (anchorR - bounds.left).coerceAtLeast(minW) width = width.coerceAtMost(maxWidth) var height = width / aspectRatio val halfMaxHeight = minOf(rectTemp.center.y - bounds.top, bounds.bottom - rectTemp.center.y) val maxHeight = halfMaxHeight * 2f if (height > maxHeight) { height = maxHeight width = (height * aspectRatio).coerceAtLeast(minW) } val left = anchorR - width val top = rectTemp.center.y - height / 2f val bottom = rectTemp.center.y + height / 2f return Rect( left.coerceAtLeast(bounds.left), top.coerceAtLeast(bounds.top), anchorR.coerceAtMost(bounds.right), bottom.coerceAtMost(bounds.bottom) ) } fun centerRight(anchorL: Float, candidateRight: Float): Rect { var width = (candidateRight - anchorL).coerceAtLeast(minW) val maxWidth = (bounds.right - anchorL).coerceAtLeast(minW) width = width.coerceAtMost(maxWidth) var height = width / aspectRatio val halfMaxHeight = minOf(rectTemp.center.y - bounds.top, bounds.bottom - rectTemp.center.y) val maxHeight = halfMaxHeight * 2f if (height > maxHeight) { height = maxHeight width = (height * aspectRatio).coerceAtLeast(minW) } val right = anchorL + width val top = rectTemp.center.y - height / 2f val bottom = rectTemp.center.y + height / 2f return Rect( anchorL.coerceAtLeast(bounds.left), top.coerceAtLeast(bounds.top), right.coerceAtMost(bounds.right), bottom.coerceAtMost(bounds.bottom) ) } val result = when (touchRegion) { TouchRegion.TopLeft -> { val anchorR = rectTemp.right.coerceAtMost(bounds.right) if (fixedAspectRatio) anchoredCornerTopLeft( anchorR, rectTemp.bottom.coerceAtMost(bounds.bottom), screenX.coerceAtMost(anchorR - minW) ) else { val left = screenX.coerceIn(bounds.left, anchorR - minW) val top = screenY.coerceIn( bounds.top, rectTemp.bottom.coerceAtMost(bounds.bottom) - minH ) clampNonFixed(left, top, anchorR, rectTemp.bottom.coerceAtMost(bounds.bottom)) } } TouchRegion.BottomLeft -> { val anchorR = rectTemp.right.coerceAtMost(bounds.right) if (fixedAspectRatio) anchoredCornerBottomLeft( anchorR, rectTemp.top.coerceAtLeast(bounds.top), screenX.coerceAtMost(anchorR - minW) ) else { val left = screenX.coerceIn(bounds.left, anchorR - minW) val bottom = screenY.coerceIn( rectTemp.top.coerceAtLeast(bounds.top) + minH, bounds.bottom ) clampNonFixed(left, rectTemp.top.coerceAtLeast(bounds.top), anchorR, bottom) } } TouchRegion.TopRight -> { val anchorL = rectTemp.left.coerceAtLeast(bounds.left) if (fixedAspectRatio) anchoredCornerTopRight( anchorL, rectTemp.bottom.coerceAtMost(bounds.bottom), screenX.coerceAtLeast(anchorL + minW) ) else { val right = screenX.coerceIn(anchorL + minW, bounds.right) val top = screenY.coerceIn( bounds.top, rectTemp.bottom.coerceAtMost(bounds.bottom) - minH ) clampNonFixed(anchorL, top, right, rectTemp.bottom.coerceAtMost(bounds.bottom)) } } TouchRegion.BottomRight -> { val anchorL = rectTemp.left.coerceAtLeast(bounds.left) if (fixedAspectRatio) anchoredCornerBottomRight( anchorL, rectTemp.top.coerceAtLeast(bounds.top), screenX.coerceAtLeast(anchorL + minW) ) else { val right = screenX.coerceIn(anchorL + minW, bounds.right) val bottom = screenY.coerceIn( rectTemp.top.coerceAtLeast(bounds.top) + minH, bounds.bottom ) clampNonFixed(anchorL, rectTemp.top.coerceAtLeast(bounds.top), right, bottom) } } TouchRegion.TopCenter -> { if (fixedAspectRatio) centerTop( rectTemp.bottom.coerceAtMost(bounds.bottom), screenY.coerceAtMost(rectTemp.bottom - minH) ) else { val top = screenY.coerceIn( bounds.top, rectTemp.bottom.coerceAtMost(bounds.bottom) - minH ) clampNonFixed( rectTemp.left.coerceAtLeast(bounds.left), top, rectTemp.right.coerceAtMost(bounds.right), rectTemp.bottom.coerceAtMost(bounds.bottom) ) } } TouchRegion.BottomCenter -> { if (fixedAspectRatio) centerBottom( rectTemp.top.coerceAtLeast(bounds.top), screenY.coerceAtLeast(rectTemp.top + minH) ) else { val bottom = screenY.coerceIn( rectTemp.top.coerceAtLeast(bounds.top) + minH, bounds.bottom ) clampNonFixed( rectTemp.left.coerceAtLeast(bounds.left), rectTemp.top.coerceAtLeast(bounds.top), rectTemp.right.coerceAtMost(bounds.right), bottom ) } } TouchRegion.CenterLeft -> { val anchorR = rectTemp.right.coerceAtMost(bounds.right) if (fixedAspectRatio) centerLeft(anchorR, screenX.coerceAtMost(anchorR - minW)) else { val left = screenX.coerceIn(bounds.left, anchorR - minW) clampNonFixed( left, rectTemp.top.coerceAtLeast(bounds.top), anchorR, rectTemp.bottom.coerceAtMost(bounds.bottom) ) } } TouchRegion.CenterRight -> { val anchorL = rectTemp.left.coerceAtLeast(bounds.left) if (fixedAspectRatio) centerRight(anchorL, screenX.coerceAtLeast(anchorL + minW)) else { val right = screenX.coerceIn(anchorL + minW, bounds.right) clampNonFixed( anchorL, rectTemp.top.coerceAtLeast(bounds.top), right, rectTemp.bottom.coerceAtMost(bounds.bottom) ) } } TouchRegion.Inside -> { val drag = change.positionChangeIgnoreConsumed() val newLeft = (overlayRect.left + drag.x).coerceIn( bounds.left, bounds.right - overlayRect.width ) val newTop = (overlayRect.top + drag.y).coerceIn( bounds.top, bounds.bottom - overlayRect.height ) Rect(newLeft, newTop, newLeft + overlayRect.width, newTop + overlayRect.height) } else -> overlayRect } return result } /** * get [TouchRegion] based on touch position on screen relative to [overlayRect]. */ private fun getTouchRegion( position: Offset, rect: Rect, threshold: Float ): TouchRegion { val closedTouchRange = -threshold / 2..threshold val centerX = rect.left + rect.width / 2 val centerY = rect.top + rect.height / 2 return when { position.x - rect.left in closedTouchRange && position.y - rect.top in closedTouchRange -> TouchRegion.TopLeft rect.right - position.x in closedTouchRange && position.y - rect.top in closedTouchRange -> TouchRegion.TopRight rect.right - position.x in closedTouchRange && rect.bottom - position.y in closedTouchRange -> TouchRegion.BottomRight position.x - rect.left in closedTouchRange && rect.bottom - position.y in closedTouchRange -> TouchRegion.BottomLeft centerX - position.x in closedTouchRange && position.y - rect.top in closedTouchRange -> TouchRegion.TopCenter rect.right - position.x in closedTouchRange && position.y - centerY in closedTouchRange -> TouchRegion.CenterRight position.x - rect.left in closedTouchRange && position.y - centerY in closedTouchRange -> TouchRegion.CenterLeft centerX - position.x in closedTouchRange && rect.bottom - position.y in closedTouchRange -> TouchRegion.BottomCenter else -> { if (rect.contains(offset = position)) TouchRegion.Inside else TouchRegion.None } } } /** * Returns how far user touched to corner or center of sides of the screen. [TouchRegion] * where user exactly has touched is already passed to this function. For instance user * touched top left then this function returns distance to top left from user's position so * we can add an offset to not jump edge to position user touched. */ private fun getDistanceToEdgeFromTouch( touchRegion: TouchRegion, rect: Rect, touchPosition: Offset ) = when (touchRegion) { TouchRegion.TopLeft -> { rect.topLeft - touchPosition } TouchRegion.TopRight -> { rect.topRight - touchPosition } TouchRegion.BottomLeft -> { rect.bottomLeft - touchPosition } TouchRegion.BottomRight -> { rect.bottomRight - touchPosition } TouchRegion.TopCenter -> { rect.topCenter - touchPosition } TouchRegion.CenterRight -> { rect.centerRight - touchPosition } TouchRegion.BottomCenter -> { rect.bottomCenter - touchPosition } TouchRegion.CenterLeft -> { rect.centerLeft - touchPosition } else -> { Offset.Zero } } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/state/StaticCropState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.state import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.unit.IntSize import com.t8rin.cropper.model.AspectRatio import kotlinx.coroutines.coroutineScope /** * * State for cropper with dynamic overlay. When this state is selected instead of overlay * image is moved while overlay is stationary. * * @param imageSize size of the **Bitmap** * @param containerSize size of the Composable that draws **Bitmap** * @param maxZoom maximum zoom value * @param fling when set to true dragging pointer builds up velocity. When last * pointer leaves Composable a movement invoked against friction till velocity drops below * to threshold * @param zoomable when set to true zoom is enabled * @param pannable when set to true pan is enabled * @param rotatable when set to true rotation is enabled * @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating * empty space on sides or edges of parent */ class StaticCropState internal constructor( imageSize: IntSize, containerSize: IntSize, drawAreaSize: IntSize, aspectRatio: AspectRatio, overlayRatio: Float, maxZoom: Float = 5f, fling: Boolean = false, zoomable: Boolean = true, pannable: Boolean = true, rotatable: Boolean = false, limitPan: Boolean = false ) : CropState( imageSize = imageSize, containerSize = containerSize, drawAreaSize = drawAreaSize, aspectRatio = aspectRatio, overlayRatio = overlayRatio, maxZoom = maxZoom, fling = fling, zoomable = zoomable, pannable = pannable, rotatable = rotatable, limitPan = limitPan ) { override suspend fun onDown(change: PointerInputChange) = Unit override suspend fun onMove(changes: List) = Unit override suspend fun onUp(change: PointerInputChange) = Unit private var doubleTapped = false /* Transform gestures */ override suspend fun onGesture( centroid: Offset, panChange: Offset, zoomChange: Float, rotationChange: Float, mainPointer: PointerInputChange, changes: List ) = coroutineScope { doubleTapped = false updateTransformState( centroid = centroid, zoomChange = zoomChange, panChange = panChange, rotationChange = rotationChange ) // Update image draw rectangle based on pan, zoom or rotation change drawAreaRect = updateImageDrawRectFromTransformation() // Fling Gesture if (pannable && fling) { if (changes.size == 1) { addPosition(mainPointer.uptimeMillis, mainPointer.position) } } } override suspend fun onGestureStart() = coroutineScope {} override suspend fun onGestureEnd(onBoundsCalculated: () -> Unit) { // Gesture end might be called after second tap and we don't want to fling // or animate back to valid bounds when doubled tapped if (!doubleTapped) { if (pannable && fling && zoom > 1) { fling { // We get target value on start instead of updating bounds after // gesture has finished drawAreaRect = updateImageDrawRectFromTransformation() onBoundsCalculated() } } else { onBoundsCalculated() } animateTransformationToOverlayBounds(overlayRect, animate = true) } } // Double Tap override suspend fun onDoubleTap( offset: Offset, zoom: Float, onAnimationEnd: () -> Unit ) { doubleTapped = true if (fling) { resetTracking() } resetWithAnimation(pan = pan, zoom = zoom, rotation = rotation) drawAreaRect = updateImageDrawRectFromTransformation() animateTransformationToOverlayBounds(overlayRect, true) onAnimationEnd() } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/state/TransformState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.state import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.exponentialDecay import androidx.compose.animation.core.tween import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.unit.IntSize import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch /** * State of the pan, zoom and rotation. Allows to change zoom, pan via [Animatable] * objects' [Animatable.animateTo], [Animatable.snapTo]. * @param initialZoom initial zoom level * @param initialRotation initial angle in degrees * @param minZoom minimum zoom * @param maxZoom maximum zoom * @param zoomable when set to true zoom is enabled * @param pannable when set to true pan is enabled * @param rotatable when set to true rotation is enabled * @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating * empty space on sides or edges of parent. * */ @Stable open class TransformState( internal val imageSize: IntSize, val containerSize: IntSize, val drawAreaSize: IntSize, initialZoom: Float = 1f, initialRotation: Float = 0f, minZoom: Float = 0.5f, // Definitely maxZoom: Float = 10f, internal var zoomable: Boolean = true, internal var pannable: Boolean = true, internal var rotatable: Boolean = true, internal var limitPan: Boolean = false ) { var drawAreaRect: Rect by mutableStateOf( Rect( offset = Offset( x = ((containerSize.width - drawAreaSize.width) / 2).toFloat(), y = ((containerSize.height - drawAreaSize.height) / 2).toFloat() ), size = Size(drawAreaSize.width.toFloat(), drawAreaSize.height.toFloat()) ) ) internal val zoomMin = minZoom.coerceAtLeast(0.5f) // Definitely internal var zoomMax = maxZoom.coerceAtLeast(1f) private val zoomInitial = initialZoom.coerceIn(zoomMin, zoomMax) private val rotationInitial = initialRotation % 360 internal val animatablePanX = Animatable(0f) internal val animatablePanY = Animatable(0f) internal val animatableZoom = Animatable(zoomInitial) internal val animatableRotation = Animatable(rotationInitial) private val velocityTracker = VelocityTracker() init { animatableZoom.updateBounds(zoomMin, zoomMax) require(zoomMax >= zoomMin) } val pan: Offset get() = Offset(animatablePanX.value, animatablePanY.value) val zoom: Float get() = animatableZoom.value val rotation: Float get() = animatableRotation.value val isZooming: Boolean get() = animatableZoom.isRunning val isPanning: Boolean get() = animatablePanX.isRunning || animatablePanY.isRunning val isRotating: Boolean get() = animatableRotation.isRunning val isAnimationRunning: Boolean get() = isZooming || isPanning || isRotating internal open fun updateBounds(lowerBound: Offset?, upperBound: Offset?) { animatablePanX.updateBounds(lowerBound?.x, upperBound?.x) animatablePanY.updateBounds(lowerBound?.y, upperBound?.y) } /** * Update centroid, pan, zoom and rotation of this state when transform gestures are * invoked with one or multiple pointers */ internal open suspend fun updateTransformState( centroid: Offset, panChange: Offset, zoomChange: Float, rotationChange: Float = 1f, ) { val newZoom = (this.zoom * zoomChange).coerceIn(zoomMin, zoomMax) snapZoomTo(newZoom) val newRotation = if (rotatable) { this.rotation + rotationChange } else { 0f } snapRotationTo(newRotation) if (pannable) { val newPan = this.pan + panChange.times(this.zoom / newZoom) snapPanXto(newPan.x) snapPanYto(newPan.y) } } /** * Reset [pan], [zoom] and [rotation] with animation. */ internal suspend fun resetWithAnimation( pan: Offset = Offset.Zero, zoom: Float = 1f, rotation: Float = 0f, animationSpec: AnimationSpec = tween(400) ) = coroutineScope { launch { animatePanXto(pan.x, animationSpec) } launch { animatePanYto(pan.y, animationSpec) } launch { animateZoomTo(zoom, animationSpec) } launch { animateRotationTo(rotation, animationSpec) } } internal suspend fun animatePanXto( panX: Float, animationSpec: AnimationSpec = tween(400) ) { if (pannable && pan.x != panX) { animatablePanX.animateTo(panX, animationSpec) } } internal suspend fun animatePanYto( panY: Float, animationSpec: AnimationSpec = tween(400) ) { if (pannable && pan.y != panY) { animatablePanY.animateTo(panY, animationSpec) } } internal suspend fun animateZoomTo( zoom: Float, animationSpec: AnimationSpec = tween(400) ) { if (zoomable && this.zoom != zoom) { val newZoom = zoom.coerceIn(zoomMin, zoomMax) animatableZoom.animateTo(newZoom, animationSpec) } } suspend fun animateRotationTo( rotation: Float, animationSpec: AnimationSpec = tween(400) ) { if (rotatable && this.rotation != rotation) { animatableRotation.animateTo(rotation, animationSpec) } } internal suspend fun snapPanXto(panX: Float) { if (pannable) { animatablePanX.snapTo(panX) } } internal suspend fun snapPanYto(panY: Float) { if (pannable) { animatablePanY.snapTo(panY) } } internal suspend fun snapZoomTo(zoom: Float) { if (zoomable) { animatableZoom.snapTo(zoom.coerceIn(zoomMin, zoomMax)) } } internal suspend fun snapRotationTo(rotation: Float) { if (rotatable) { animatableRotation.snapTo(rotation) } } /* Fling gesture */ internal fun addPosition(timeMillis: Long, position: Offset) { velocityTracker.addPosition( timeMillis = timeMillis, position = position ) } /** * Create a fling gesture when user removes finger from scree to have continuous movement * until [velocityTracker] speed reached to lower bound */ internal suspend fun fling(onFlingStart: () -> Unit) = coroutineScope { val velocityTracker = velocityTracker.calculateVelocity() val velocity = Offset(velocityTracker.x, velocityTracker.y) var flingStarted = false launch { animatablePanX.animateDecay( velocity.x, exponentialDecay(absVelocityThreshold = 20f), block = { // This callback returns target value of fling gesture initially if (!flingStarted) { onFlingStart() flingStarted = true } } ) } launch { animatablePanY.animateDecay( velocity.y, exponentialDecay(absVelocityThreshold = 20f), block = { // This callback returns target value of fling gesture initially if (!flingStarted) { onFlingStart() flingStarted = true } } ) } } internal fun resetTracking() { velocityTracker.resetTracking() } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/util/DimensionUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.util /** * [Linear Interpolation](https://en.wikipedia.org/wiki/Linear_interpolation) function that moves * amount from it's current position to start and amount * @param start of interval * @param stop of interval * @param fraction closed unit interval [0, 1] */ fun lerp(start: Float, stop: Float, fraction: Float): Float { return (1 - fraction) * start + fraction * stop } /** * Scale x1 from start1..end1 range to start2..end2 range */ fun scale(start1: Float, end1: Float, pos: Float, start2: Float, end2: Float) = lerp(start2, end2, calculateFraction(start1, end1, pos)) /** * Scale x.start, x.endInclusive from a1..b1 range to a2..b2 range */ fun scale( start1: Float, end1: Float, range: ClosedFloatingPointRange, start2: Float, end2: Float ) = scale(start1, end1, range.start, start2, end2)..scale( start1, end1, range.endInclusive, start2, end2 ) /** * Calculate fraction for value between a range [end] and [start] coerced into 0f-1f range */ fun calculateFraction(start: Float, end: Float, pos: Float) = (if (end - start == 0f) 0f else (pos - start) / (end - start)).coerceIn(0f, 1f) ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/util/DrawScopeUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.util import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.nativeCanvas /** * Draw grid that is divided by 2 vertical and 2 horizontal lines for overlay */ fun DrawScope.drawGrid(rect: Rect, strokeWidth: Float, color: Color) { val width = rect.width val height = rect.height val gridWidth = width / 3 val gridHeight = height / 3 // Horizontal lines for (i in 1..2) { drawLine( color = color, start = Offset(rect.left, rect.top + i * gridHeight), end = Offset(rect.right, rect.top + i * gridHeight), strokeWidth = strokeWidth ) } // Vertical lines for (i in 1..2) { drawLine( color, start = Offset(rect.left + i * gridWidth, rect.top), end = Offset(rect.left + i * gridWidth, rect.bottom), strokeWidth = strokeWidth ) } } /** * Draw with layer to use [BlendMode]s */ fun DrawScope.drawWithLayer(block: DrawScope.() -> Unit) { with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) block() restoreToCount(checkPoint) } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/util/ImageContentScaleUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.util import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize /** * Get Rectangle of [ImageBitmap] with [bitmapWidth] and [bitmapHeight] that is drawn inside * Canvas with [imageWidth] and [imageHeight]. [boxWidth] and [boxHeight] belong * to [BoxWithConstraints] that contains Canvas. * @param boxWidth width of the parent container * @param boxHeight height of the parent container * @param imageWidth width of the [Canvas] that draws [ImageBitmap] * @param imageHeight height of the [Canvas] that draws [ImageBitmap] * @param bitmapWidth intrinsic width of the [ImageBitmap] * @param bitmapHeight intrinsic height of the [ImageBitmap] * @return [IntRect] that covers [ImageBitmap] bounds. When image [ContentScale] is crop * this rectangle might return smaller rectangle than actual [ImageBitmap] and left or top * of the rectangle might be bigger than zero. */ internal fun getScaledBitmapRect( boxWidth: Int, boxHeight: Int, imageWidth: Float, imageHeight: Float, bitmapWidth: Int, bitmapHeight: Int ): IntRect { // Get scale of box to width of the image // We need a rect that contains Bitmap bounds to pass if any child requires it // For a image with 100x100 px with 300x400 px container and image with crop 400x400px // So we need to pass top left as 0,50 and size val scaledBitmapX = boxWidth / imageWidth val scaledBitmapY = boxHeight / imageHeight val topLeft = IntOffset( x = (bitmapWidth * (imageWidth - boxWidth) / imageWidth / 2) .coerceAtLeast(0f).toInt(), y = (bitmapHeight * (imageHeight - boxHeight) / imageHeight / 2) .coerceAtLeast(0f).toInt() ) val size = IntSize( width = (bitmapWidth * scaledBitmapX).toInt().coerceAtMost(bitmapWidth), height = (bitmapHeight * scaledBitmapY).toInt().coerceAtMost(bitmapHeight) ) return IntRect(offset = topLeft, size = size) } /** * Get [IntSize] of the parent or container that contains [Canvas] that draws [ImageBitmap] * @param bitmapWidth intrinsic width of the [ImageBitmap] * @param bitmapHeight intrinsic height of the [ImageBitmap] * @return size of parent Composable. When Modifier is assigned with fixed or finite size * they are used, but when any dimension is set to infinity intrinsic dimensions of * [ImageBitmap] are returned */ internal fun BoxWithConstraintsScope.getParentSize( bitmapWidth: Int, bitmapHeight: Int ): IntSize { // Check if Composable has fixed size dimensions val hasBoundedDimens = constraints.hasBoundedWidth && constraints.hasBoundedHeight // Check if Composable has infinite dimensions val hasFixedDimens = constraints.hasFixedWidth && constraints.hasFixedHeight // Box is the parent(BoxWithConstraints) that contains Canvas under the hood // Canvas aspect ratio or size might not match parent but it's upper bounds are // what are passed from parent. Canvas cannot be bigger or taller than BoxWithConstraints val boxWidth: Int = if (hasBoundedDimens || hasFixedDimens) { constraints.maxWidth } else { constraints.minWidth.coerceAtLeast(bitmapWidth) } val boxHeight: Int = if (hasBoundedDimens || hasFixedDimens) { constraints.maxHeight } else { constraints.minHeight.coerceAtLeast(bitmapHeight) } return IntSize(boxWidth, boxHeight) } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/util/OffsetUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.util import androidx.compose.ui.geometry.Offset /** * Coerce an [Offset] x value in [horizontalRange] and y value in [verticalRange] */ fun Offset.coerceIn( horizontalRange: ClosedRange, verticalRange: ClosedRange ) = Offset(this.x.coerceIn(horizontalRange), this.y.coerceIn(verticalRange)) ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/util/ShapeUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.util import android.graphics.Matrix import androidx.compose.foundation.shape.GenericShape import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import com.t8rin.cropper.model.AspectRatio import kotlin.math.cos import kotlin.math.sin /** * Creates a polygon with number of [sides] centered at ([cx],[cy]) with [radius]. * ``` * To generate regular polygons (i.e. where each interior angle is the same), * polar coordinates are extremely useful. You can calculate the angle necessary * to produce the desired number of sides (as the interior angles total 360º) * and then use multiples of this angle with the same radius to describe each point. * val x = radius * Math.cos(angle); * val y = radius * Math.sin(angle); * * For instance to draw triangle loop thrice with angle * 0, 120, 240 degrees in radians and draw lines from each coordinate. * ``` */ fun createPolygonPath(cx: Float, cy: Float, sides: Int, radius: Float): Path { val angle = 2.0 * Math.PI / sides return Path().apply { moveTo( cx + (radius * cos(0.0)).toFloat(), cy + (radius * sin(0.0)).toFloat() ) for (i in 1 until sides) { lineTo( cx + (radius * cos(angle * i)).toFloat(), cy + (radius * sin(angle * i)).toFloat() ) } close() } } /** * Create a polygon shape */ fun createPolygonShape(sides: Int, degrees: Float = 0f): GenericShape { return GenericShape { size: Size, _: LayoutDirection -> val radius = size.width.coerceAtMost(size.height) / 2 addPath( createPolygonPath( cx = size.width / 2, cy = size.height / 2, sides = sides, radius = radius ) ) val matrix = Matrix() matrix.postRotate(degrees, size.width / 2, size.height / 2) this.asAndroidPath().transform(matrix) } } /** * Creates a [Rect] shape with given aspect ratio. */ fun createRectShape(aspectRatio: AspectRatio): GenericShape { return GenericShape { size: Size, _: LayoutDirection -> val value = aspectRatio.value val width = size.width val height = size.height val shapeSize = if (aspectRatio == AspectRatio.Original) Size(width, height) else if (value > 1) Size(width = width, height = width / value) else Size(width = height * value, height = height) addRect(Rect(offset = Offset.Zero, size = shapeSize)) } } /** * Scales this path to [width] and [height] from [Path.getBounds] and translates * as difference between scaled path and original path */ fun Path.scaleAndTranslatePath( width: Float, height: Float, ) { val pathSize = getBounds().size val matrix = Matrix() matrix.postScale( width / pathSize.width, height / pathSize.height ) this.asAndroidPath().transform(matrix) val left = getBounds().left val top = getBounds().top translate(Offset(-left, -top)) } /** * Build an outline from a shape using aspect ratio, shape and coefficient to scale * * @return [Triple] that contains left, top offset and [Outline] */ fun buildOutline( aspectRatio: AspectRatio, coefficient: Float, shape: Shape, size: Size, layoutDirection: LayoutDirection, density: Density ): Pair { val (shapeSize, offset) = calculateSizeAndOffsetFromAspectRatio(aspectRatio, coefficient, size) val outline = shape.createOutline( size = shapeSize, layoutDirection = layoutDirection, density = density ) return Pair(offset, outline) } /** * Calculate new size and offset based on [size], [coefficient] and [aspectRatio] * * For 4/3f aspect ratio with 1000px width, 1000px height with coefficient 1f * it returns Size(1000f, 750f), Offset(0f, 125f). */ fun calculateSizeAndOffsetFromAspectRatio( aspectRatio: AspectRatio, coefficient: Float, size: Size, ): Pair { val width = size.width val height = size.height val value = aspectRatio.value val newSize = if (aspectRatio == AspectRatio.Original) { Size(width * coefficient, height * coefficient) } else if (value > 1) { Size( width = coefficient * width, height = coefficient * width / value ) } else { Size(width = coefficient * height * value, height = coefficient * height) } val left = (width - newSize.width) / 2 val top = (height - newSize.height) / 2 return Pair(newSize, Offset(left, top)) } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomLevel.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.util /** * Enum class for zoom levels */ enum class ZoomLevel { Min, Mid, Max } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/util/ZoomUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.util import androidx.compose.ui.graphics.GraphicsLayerScope import com.t8rin.cropper.state.TransformState /** * Calculate zoom level and zoom value when user double taps */ internal fun calculateZoom( zoomLevel: ZoomLevel, initial: Float, min: Float, max: Float ): Pair { val newZoomLevel: ZoomLevel val newZoom: Float when (zoomLevel) { ZoomLevel.Mid -> { newZoomLevel = ZoomLevel.Max newZoom = max.coerceAtMost(3f) } ZoomLevel.Max -> { newZoomLevel = ZoomLevel.Min newZoom = if (min == initial) initial else min } else -> { newZoomLevel = ZoomLevel.Mid newZoom = if (min == initial) (min + max.coerceAtMost(3f)) / 2 else initial } } return Pair(newZoomLevel, newZoom) } internal fun getNextZoomLevel(zoomLevel: ZoomLevel): ZoomLevel = when (zoomLevel) { ZoomLevel.Mid -> { ZoomLevel.Max } ZoomLevel.Max -> { ZoomLevel.Min } else -> { ZoomLevel.Mid } } /** * Update graphic layer with [transformState] */ internal fun GraphicsLayerScope.update(transformState: TransformState) { // Set zoom val zoom = transformState.zoom this.scaleX = zoom this.scaleY = zoom // Set pan val pan = transformState.pan val translationX = pan.x val translationY = pan.y this.translationX = translationX this.translationY = translationY // Set rotation this.rotationZ = transformState.rotation } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/widget/AspectRatioSlectionCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.widget import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.cropper.model.CropAspectRatio @Composable fun AspectRatioSelectionCard( modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.surface, color: Color, cropAspectRatio: CropAspectRatio, onClick: ((List) -> Unit)? = null ) { Box( modifier = modifier .background(contentColor) .padding(4.dp) ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current Box( modifier = Modifier .fillMaxWidth() .padding(4.dp) .aspectRatio(1f) .drawWithCache { val outline = cropAspectRatio.shape.createOutline( size = size, layoutDirection = layoutDirection, density = density ) val width = size.width val height = size.height val outlineWidth = outline.bounds.width val outlineHeight = outline.bounds.height onDrawWithContent { translate( left = (width - outlineWidth) / 2, top = (height - outlineHeight) / 2 ) { drawOutline( outline = outline, color = color, style = Stroke(3.dp.toPx()) ) } drawContent() } }, contentAlignment = Alignment.Center ) { GridImageLayout( modifier = Modifier .matchParentSize() .padding(5.dp), thumbnails = cropAspectRatio.icons, onClick = onClick ) } if (cropAspectRatio.title.isNotEmpty()) { Text( text = cropAspectRatio.title, color = color, fontSize = 14.sp, overflow = TextOverflow.Ellipsis, maxLines = 1 ) } } } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/widget/CropFrameDisplayCard.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.widget import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.Image import androidx.compose.material.icons.outlined.StarBorder import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawOutline import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.t8rin.cropper.model.CropOutline import com.t8rin.cropper.model.CropPath import com.t8rin.cropper.model.CropShape import com.t8rin.cropper.settings.Paths import com.t8rin.cropper.util.scale @Composable fun CropFrameDisplayCard( modifier: Modifier = Modifier, editable: Boolean, scale: Float, outlineColor: Color, editButtonBackgroundColor: Color = MaterialTheme.colorScheme.tertiary, editButtonContentColor: Color = MaterialTheme.colorScheme.onTertiary, fontSize: TextUnit = 12.sp, title: String, cropOutline: CropOutline, onEditClick: () -> Unit = {}, ) { Box( modifier = modifier, contentAlignment = Alignment.Center ) { Column( Modifier .graphicsLayer { scaleX = scale scaleY = scale }, horizontalAlignment = Alignment.CenterHorizontally ) { CropFrameDisplay( modifier = Modifier .fillMaxWidth() .padding(4.dp) .aspectRatio(1f), cropOutline = cropOutline, color = outlineColor ) { if (editable) { Icon( modifier = Modifier .graphicsLayer { val iconScale = scale( start1 = .9f, end1 = 1f, pos = scale.coerceAtLeast(.9f), start2 = 0f, end2 = 1f ) scaleX = iconScale scaleY = iconScale val translation = this.density.run { 12.dp.toPx() } translationX = translation translationY = -translation clip = true shape = CircleShape this.density } .clickable { onEditClick() } .padding(8.dp) .background(editButtonBackgroundColor, CircleShape) .size(20.dp) .padding(4.dp), imageVector = Icons.Default.Edit, tint = editButtonContentColor, contentDescription = "Edit" ) } } if (title.isNotEmpty()) { Text( text = title, color = outlineColor, fontSize = fontSize, maxLines = 1, overflow = TextOverflow.Ellipsis ) } } } } @Composable private fun CropFrameDisplay( modifier: Modifier, cropOutline: CropOutline, color: Color, content: @Composable () -> Unit ) { val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current when (cropOutline) { is CropShape -> { val shape = remember { cropOutline.shape } Box( modifier.drawWithCache { val outline = shape.createOutline( size = size, layoutDirection = layoutDirection, density = density ) onDrawWithContent { val width = size.width val height = size.height val outlineWidth = outline.bounds.width val outlineHeight = outline.bounds.height translate( left = (width - outlineWidth) / 2, top = (height - outlineHeight) / 2 ) { drawOutline( outline = outline, color = color, style = Stroke(6.dp.toPx()) ) } drawContent() } }, contentAlignment = Alignment.TopEnd ) { content() } } is CropPath -> { Box( modifier = modifier, contentAlignment = Alignment.TopEnd ) { if (cropOutline.path == Paths.Star) { Icon( modifier = Modifier .matchParentSize() .scale(1.3f), imageVector = Icons.Outlined.StarBorder, tint = color, contentDescription = "Crop with Path" ) } else { Icon( modifier = Modifier .matchParentSize() .scale(1.3f), imageVector = Icons.Outlined.FavoriteBorder, tint = color, contentDescription = "Crop with Path" ) } content() } } else -> { Box( modifier = modifier, contentAlignment = Alignment.TopEnd ) { Icon( modifier = Modifier .matchParentSize() .scale(1.3f), imageVector = Icons.Outlined.Image, tint = color, contentDescription = "Crop with Image Mask" ) content() } } } } ================================================ FILE: lib/cropper/src/main/java/com/t8rin/cropper/widget/GridImageLayout.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.cropper.widget import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.layout.Placeable import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp /** * Composable that positions and displays [Image]s based on count as one element, * horizontal two images, 2 images as quarter of parent and one has lower hals, 4 quarter images * 3 images and [Text] that displays number of images that are not visible. * @param thumbnails images or icons to be displayed with resource ids * @param divider divider space between items when number of items is bigger than 1 * @param onClick callback for returning images when this composable is clicked */ @Composable fun GridImageLayout( modifier: Modifier = Modifier, thumbnails: List, divider: Dp = 2.dp, onClick: ((List) -> Unit)? = null ) { if (thumbnails.isNotEmpty()) { ImageDrawLayout( modifier = modifier .clickable { onClick?.invoke(thumbnails) }, divider = divider, itemCount = thumbnails.size ) { val size = thumbnails.size if (size < 5) { thumbnails.forEach { Image( painter = painterResource(id = it), contentDescription = "Icon", contentScale = ContentScale.Crop, ) } } else { thumbnails.take(3).forEach { Image( painter = painterResource(id = it), contentDescription = "Icon", contentScale = ContentScale.Crop, ) } Box( contentAlignment = Alignment.Center ) { val carry = size - 3 Text(text = "+$carry", fontSize = 20.sp) } } } } } @Composable private fun ImageDrawLayout( modifier: Modifier = Modifier, itemCount: Int, divider: Dp, content: @Composable () -> Unit ) { val spacePx = LocalDensity.current.run { (divider).roundToPx() } val measurePolicy = remember(itemCount, spacePx) { MeasurePolicy { measurables, constraints -> val newConstraints = when (itemCount) { 1 -> constraints 2 -> Constraints.fixed( width = constraints.maxWidth / 2 - spacePx / 2, height = constraints.maxHeight ) else -> Constraints.fixed( width = constraints.maxWidth / 2 - spacePx / 2, height = constraints.maxHeight / 2 - spacePx / 2 ) } val placeables: List = if (measurables.size != 3) { measurables.map { measurable: Measurable -> measurable.measure(constraints = newConstraints) } } else { measurables .take(2) .map { measurable: Measurable -> measurable.measure(constraints = newConstraints) } + measurables .last() .measure( constraints = Constraints.fixed( constraints.maxWidth, constraints.maxHeight / 2 - spacePx ) ) } layout(constraints.maxWidth, constraints.maxHeight) { when (itemCount) { 1 -> { placeables.forEach { placeable: Placeable -> placeable.placeRelative(0, 0) } } 2 -> { var xPos = 0 placeables.forEach { placeable: Placeable -> placeable.placeRelative(xPos, 0) xPos += placeable.width + spacePx } } else -> { var xPos = 0 var yPos = 0 placeables.forEachIndexed { index: Int, placeable: Placeable -> placeable.placeRelative(xPos, yPos) if (index % 2 == 0) { xPos += placeable.width + spacePx } else { xPos = 0 } if (index % 2 == 1) { yPos += placeable.height + spacePx } } } } } } } Layout( modifier = modifier, content = content, measurePolicy = measurePolicy ) } ================================================ FILE: lib/curves/.gitignore ================================================ /build ================================================ FILE: lib/curves/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.curves" dependencies { implementation(libs.coilCompose) implementation(libs.toolbox.gpuimage) implementation(libs.toolbox.histogram) } ================================================ FILE: lib/curves/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.curves import android.app.Activity import android.graphics.Bitmap import android.view.TextureView import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.LocalContentColor import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.RequestDisallowInterceptTouchEvent import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.t8rin.curves.utils.safeAspectRatio import com.t8rin.curves.view.PhotoFilterCurvesControl import jp.co.cyberagent.android.gpuimage.GLTextureView import jp.co.cyberagent.android.gpuimage.GPUImage import jp.co.cyberagent.android.gpuimage.filter.GPUImageContrastFilter @Composable fun ImageCurvesEditor( bitmap: Bitmap?, state: ImageCurvesEditorState = remember { ImageCurvesEditorState.Default }, onStateChange: (ImageCurvesEditorState) -> Unit, imageObtainingTrigger: Boolean, onImageObtained: (Bitmap) -> Unit, modifier: Modifier = Modifier, containerModifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(16.dp), curvesSelectionText: @Composable (curveType: Int) -> Unit = {}, colors: ImageCurvesEditorColors = ImageCurvesEditorDefaults.Colors, drawNotActiveCurves: Boolean = true, placeControlsAtTheEnd: Boolean = false, showOriginal: Boolean = false, shape: Shape = RoundedCornerShape(2.dp), disallowInterceptTouchEvents: Boolean = true, ) { val context = LocalContext.current as Activity AnimatedContent( modifier = containerModifier, targetState = bitmap, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { image -> if (image != null) { Box( contentAlignment = Alignment.Center, modifier = modifier ) { var imageHeight by remember(image) { mutableFloatStateOf(image.height.toFloat()) } var imageWidth by remember(image) { mutableFloatStateOf(image.width.toFloat()) } var imageOffset by remember(image) { mutableStateOf(Offset.Zero) } var textureView by remember(image) { mutableStateOf(null) } val gpuImage by remember(context, image) { mutableStateOf( GPUImage(context).apply { setImage(image) setFilter(state.buildFilter()) } ) } LaunchedEffect(showOriginal, state) { gpuImage.setFilter( if (showOriginal) { GPUImageContrastFilter(1f) } else { state.buildFilter() } ) } LaunchedEffect(imageObtainingTrigger, gpuImage) { if (imageObtainingTrigger) { onImageObtained(gpuImage.bitmapWithFilterApplied) } } var controlsPadding by remember { mutableStateOf(0.dp) } val space = with(LocalDensity.current) { 1.dp.toPx() } AndroidView( modifier = Modifier .padding(contentPadding) .then( if (placeControlsAtTheEnd) Modifier.padding(end = controlsPadding) else Modifier.padding(bottom = controlsPadding) ) .aspectRatio(image.safeAspectRatio) .onGloballyPositioned { imageHeight = it.size.height.toFloat() imageWidth = it.size.width.toFloat() - space imageOffset = Offset( x = it.positionInParent().x, y = it.positionInParent().y ) } .clip(shape) .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) .drawWithContent { drawContent() drawRect( topLeft = Offset( x = size.width - space, y = 0f ), color = Color.Transparent, blendMode = BlendMode.Clear ) }, factory = { GLTextureView(it).apply { textureView = this gpuImage.setGLTextureView(this) } } ) var curvesView by remember { mutableStateOf(null) } val disallowIntercept = remember { RequestDisallowInterceptTouchEvent() } AndroidView( modifier = Modifier .matchParentSize() .pointerInteropFilter( requestDisallowInterceptTouchEvent = disallowIntercept, onTouchEvent = { curvesView?.onTouchEvent(it) disallowIntercept.invoke(disallowInterceptTouchEvents) true } ), factory = { PhotoFilterCurvesControl( context = it, value = state.curvesToolValue ).apply { curvesView = this setColors( lumaCurveColor = colors.lumaCurveColor.toArgb(), redCurveColor = colors.redCurveColor.toArgb(), greenCurveColor = colors.greenCurveColor.toArgb(), blueCurveColor = colors.blueCurveColor.toArgb(), defaultCurveColor = colors.defaultCurveColor.toArgb(), guidelinesColor = colors.guidelinesColor.toArgb() ) setDrawNotActiveCurves(drawNotActiveCurves) setActualArea(imageOffset.x, imageOffset.y, imageWidth, imageHeight) setDelegate { onStateChange(state.copy()) gpuImage.setFilter(state.buildFilter()) } } }, update = { it.updateValue(state.curvesToolValue) it.setActualArea(imageOffset.x, imageOffset.y, imageWidth, imageHeight) it.setDelegate { onStateChange(state.copy()) gpuImage.setFilter(state.buildFilter()) } it.setColors( lumaCurveColor = colors.lumaCurveColor.toArgb(), redCurveColor = colors.redCurveColor.toArgb(), greenCurveColor = colors.greenCurveColor.toArgb(), blueCurveColor = colors.blueCurveColor.toArgb(), defaultCurveColor = colors.defaultCurveColor.toArgb(), guidelinesColor = colors.guidelinesColor.toArgb() ) it.setDrawNotActiveCurves(drawNotActiveCurves) } ) val direction = LocalLayoutDirection.current val density = LocalDensity.current val controlsModifier = Modifier .align( if (placeControlsAtTheEnd) Alignment.CenterEnd else Alignment.BottomCenter ) .then( if (placeControlsAtTheEnd) { Modifier.padding( top = contentPadding.calculateTopPadding(), bottom = contentPadding.calculateBottomPadding(), start = 0.dp, end = contentPadding.calculateEndPadding(direction) ) } else { Modifier.padding( top = 0.dp, bottom = contentPadding.calculateBottomPadding(), start = contentPadding.calculateStartPadding(direction), end = contentPadding.calculateEndPadding(direction) ) } ) .onGloballyPositioned { controlsPadding = with(density) { if (placeControlsAtTheEnd) { it.size.width } else { it.size.height }.toDp() } } if (placeControlsAtTheEnd) { Column( verticalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterVertically ), modifier = controlsModifier ) { val invalidations = remember(state) { mutableIntStateOf(0) } CurvesSelectionRadioButton( state = state, color = colors.lumaCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeLuminance, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) CurvesSelectionRadioButton( state = state, color = colors.redCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeRed, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) CurvesSelectionRadioButton( state = state, color = colors.greenCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeGreen, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) CurvesSelectionRadioButton( state = state, color = colors.blueCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeBlue, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) } } else { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy( space = 8.dp, alignment = Alignment.CenterHorizontally ), modifier = controlsModifier ) { val invalidations = remember(state) { mutableIntStateOf(0) } CurvesSelectionRadioButton( state = state, color = colors.lumaCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeLuminance, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) CurvesSelectionRadioButton( state = state, color = colors.redCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeRed, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) CurvesSelectionRadioButton( state = state, color = colors.greenCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeGreen, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) CurvesSelectionRadioButton( state = state, color = colors.blueCurveColor, type = PhotoFilterCurvesControl.CurvesToolValue.CurvesTypeBlue, curvesSelectionText = curvesSelectionText, invalidations = invalidations ) } } } } } } @Composable private fun RowScope.CurvesSelectionRadioButton( state: ImageCurvesEditorState, color: Color, type: Int, invalidations: MutableState, curvesSelectionText: @Composable (type: Int) -> Unit ) { val interactionSource = remember { MutableInteractionSource() } Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .weight(1f, false) .clickable( indication = null, interactionSource = interactionSource ) { state.curvesToolValue.activeType = type invalidations.value++ } ) { val isSelected by remember(invalidations.value) { mutableStateOf(state.curvesToolValue.activeType == type) } RadioButton( selected = isSelected, onClick = { state.curvesToolValue.activeType = type invalidations.value++ }, colors = RadioButtonDefaults.colors( selectedColor = color, unselectedColor = color ), interactionSource = interactionSource ) CompositionLocalProvider(LocalContentColor provides color) { curvesSelectionText(type) } } } @Composable private fun ColumnScope.CurvesSelectionRadioButton( state: ImageCurvesEditorState, color: Color, type: Int, invalidations: MutableState, curvesSelectionText: @Composable (type: Int) -> Unit ) { val interactionSource = remember { MutableInteractionSource() } Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier .weight(1f, false) .clickable( indication = null, interactionSource = interactionSource ) { state.curvesToolValue.activeType = type invalidations.value++ } ) { val isSelected by remember(invalidations.value) { mutableStateOf(state.curvesToolValue.activeType == type) } RadioButton( selected = isSelected, onClick = { state.curvesToolValue.activeType = type invalidations.value++ }, colors = RadioButtonDefaults.colors( selectedColor = color, unselectedColor = color ), interactionSource = interactionSource ) CompositionLocalProvider(LocalContentColor provides color) { curvesSelectionText(type) } } } ================================================ FILE: lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorColors.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.curves import androidx.compose.ui.graphics.Color data class ImageCurvesEditorColors( val lumaCurveColor: Color, val redCurveColor: Color, val greenCurveColor: Color, val blueCurveColor: Color, val guidelinesColor: Color, val defaultCurveColor: Color ) ================================================ FILE: lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorDefaults.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.curves import androidx.annotation.FloatRange import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.ColorUtils object ImageCurvesEditorDefaults { val Colors: ImageCurvesEditorColors @Composable get() { return ImageCurvesEditorColors( lumaCurveColor = Color.White.blend(MaterialTheme.colorScheme.primary), redCurveColor = Color(-0x12c2b4).blend(MaterialTheme.colorScheme.primary), greenCurveColor = Color(-0xef1163).blend(MaterialTheme.colorScheme.primary), blueCurveColor = Color(-0xcc8805).blend(MaterialTheme.colorScheme.primary), guidelinesColor = Color(-0x66000001).blend(MaterialTheme.colorScheme.primary), defaultCurveColor = Color(-0x66000001).blend(MaterialTheme.colorScheme.primary) ) } private fun Color.blend( color: Color, @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.25f ): Color = Color(ColorUtils.blendARGB(this.toArgb(), color.toArgb(), fraction)) } ================================================ FILE: lib/curves/src/main/java/com/t8rin/curves/ImageCurvesEditorState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.curves import android.graphics.PointF import com.t8rin.curves.view.PhotoFilterCurvesControl.CurvesToolValue import com.t8rin.curves.view.PhotoFilterCurvesControl.CurvesValue import jp.co.cyberagent.android.gpuimage.filter.GPUImageToneCurveFilter @ConsistentCopyVisibility data class ImageCurvesEditorState internal constructor( internal val curvesToolValue: CurvesToolValue ) { constructor(controlPoints: List>) : this(CurvesToolValue()) { initControlPoints(controlPoints) } fun copy( controlPoints: List> ): ImageCurvesEditorState = ImageCurvesEditorState(controlPoints) internal fun buildFilter(): GPUImageToneCurveFilter = GPUImageToneCurveFilter().apply { setAllControlPoints(getControlPointsImpl()) } fun isDefault(): Boolean = listOf( curvesToolValue.luminanceCurve, curvesToolValue.redCurve, curvesToolValue.greenCurve, curvesToolValue.blueCurve ).all { it.isDefault } val controlPoints: List> get() = getControlPointsImpl().map { list -> list.map { it.y } } private fun initControlPoints(controlPoints: List>) { curvesToolValue.luminanceCurve.setPoints(controlPoints[0]) curvesToolValue.redCurve.setPoints(controlPoints[1]) curvesToolValue.greenCurve.setPoints(controlPoints[2]) curvesToolValue.blueCurve.setPoints(controlPoints[3]) } private fun getControlPointsImpl(): List> = listOf( curvesToolValue.luminanceCurve.toPoints(), curvesToolValue.redCurve.toPoints(), curvesToolValue.greenCurve.toPoints(), curvesToolValue.blueCurve.toPoints() ) private fun CurvesValue.setPoints(points: List) { blacksLevel = points[0] * 100f shadowsLevel = points[1] * 100f midtonesLevel = points[2] * 100f highlightsLevel = points[3] * 100f whitesLevel = points[4] * 100f } private fun CurvesValue.toPoints(): Array = listOf( PointF(0.0f, blacksLevel / 100f), PointF(0.25f, shadowsLevel / 100f), PointF(0.5f, midtonesLevel / 100f), PointF(0.75f, highlightsLevel / 100f), PointF(1.0f, whitesLevel / 100f), ).toTypedArray() companion object { val Default: ImageCurvesEditorState get() = ImageCurvesEditorState(CurvesToolValue()) } } fun GPUImageToneCurveFilter( controlPoints: List> ): GPUImageToneCurveFilter = GPUImageToneCurveFilter( state = ImageCurvesEditorState(controlPoints) ) fun GPUImageToneCurveFilter( state: ImageCurvesEditorState ): GPUImageToneCurveFilter = state.buildFilter() ================================================ FILE: lib/curves/src/main/java/com/t8rin/curves/utils/Utils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.curves.utils import android.graphics.Bitmap internal val Bitmap.aspectRatio: Float get() = width / height.toFloat() internal val Bitmap.safeAspectRatio: Float get() = aspectRatio .coerceAtLeast(0.005f) .coerceAtMost(1000f) ================================================ FILE: lib/curves/src/main/java/com/t8rin/curves/view/PhotoFilterCurvesControl.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("ConstPropertyName") package com.t8rin.curves.view import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.graphics.Canvas import android.graphics.Paint import android.graphics.Path import android.graphics.RectF import android.text.TextPaint import android.view.MotionEvent import android.view.View import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import java.util.Locale import kotlin.math.abs import kotlin.math.ceil import kotlin.math.floor import kotlin.math.max import kotlin.math.min internal class PhotoFilterCurvesControl @JvmOverloads constructor( context: Context?, value: CurvesToolValue = CurvesToolValue() ) : View(context) { private var activeSegment = CurvesSegmentNone private var isMoving = false private var checkForMoving = true private var lastX = 0f private var lastY = 0f private val actualArea = Rect() private val paint = Paint(Paint.ANTI_ALIAS_FLAG) private val paintDash = Paint(Paint.ANTI_ALIAS_FLAG) private val paintCurve = Paint(Paint.ANTI_ALIAS_FLAG) private val paintNotActiveCurve = Paint(Paint.ANTI_ALIAS_FLAG) private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) private val path = Path() private var delegate: PhotoFilterCurvesControlDelegate? = null private var curveValue: CurvesToolValue fun updateValue( value: CurvesToolValue ) { curveValue = value invalidate() } private var drawNotActiveCurves: Boolean = true private var lumaCurveColor = -0x1 private var redCurveColor = -0x12c2b4 private var greenCurveColor = -0xef1163 private var blueCurveColor = -0xcc8805 private var defaultCurveColor = -0x66000001 private var guidelinesColor = -0x66000001 init { setWillNotDraw(false) curveValue = value paint.color = guidelinesColor paint.strokeWidth = dp(1f).toFloat() paint.style = Paint.Style.STROKE paintDash.color = defaultCurveColor paintDash.strokeWidth = dp(2f).toFloat() paintDash.style = Paint.Style.STROKE paintDash.strokeCap = Paint.Cap.ROUND paintCurve.color = lumaCurveColor paintCurve.strokeWidth = dp(2f).toFloat() paintCurve.style = Paint.Style.STROKE paintCurve.strokeCap = Paint.Cap.ROUND paintCurve.setShadowLayer(2f, 0f, 0f, Color.Black.copy(0.5f).toArgb()) paintNotActiveCurve.color = lumaCurveColor paintNotActiveCurve.strokeWidth = dp(1f).toFloat() paintNotActiveCurve.style = Paint.Style.STROKE paintNotActiveCurve.strokeCap = Paint.Cap.ROUND paintNotActiveCurve.setShadowLayer(1f, 0f, 0f, Color.Black.copy(0.5f).toArgb()) textPaint.color = Color(defaultCurveColor).copy(1f).toArgb() textPaint.textSize = dp(13f).toFloat() } fun setDrawNotActiveCurves( drawNotActiveCurves: Boolean ) { this.drawNotActiveCurves = drawNotActiveCurves invalidate() } fun setColors( lumaCurveColor: Int, redCurveColor: Int, greenCurveColor: Int, blueCurveColor: Int, defaultCurveColor: Int, guidelinesColor: Int ) { this.lumaCurveColor = lumaCurveColor this.redCurveColor = redCurveColor this.greenCurveColor = greenCurveColor this.blueCurveColor = blueCurveColor this.guidelinesColor = guidelinesColor this.defaultCurveColor = defaultCurveColor paint.color = guidelinesColor paintDash.color = defaultCurveColor textPaint.color = Color(defaultCurveColor).copy(1f).toArgb() invalidate() } fun setDelegate(photoFilterCurvesControlDelegate: PhotoFilterCurvesControlDelegate?) { delegate = photoFilterCurvesControlDelegate } fun setActualArea(x: Float, y: Float, width: Float, height: Float) { actualArea.x = x actualArea.y = y actualArea.width = width actualArea.height = height } @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { val action = event.actionMasked when (action) { MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_DOWN -> { if (event.pointerCount == 1) { if (checkForMoving && !isMoving) { val locationX = event.x val locationY = event.y lastX = locationX lastY = locationY if (locationX >= actualArea.x && locationX <= actualArea.x + actualArea.width && locationY >= actualArea.y && locationY <= actualArea.y + actualArea.height) { isMoving = true } checkForMoving = false if (isMoving) { handlePan(GestureStateBegan, event) } } } else { if (isMoving) { handlePan(GestureStateEnded, event) checkForMoving = true isMoving = false } } } MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { if (isMoving) { handlePan(GestureStateEnded, event) isMoving = false } checkForMoving = true } MotionEvent.ACTION_MOVE -> { if (isMoving) { handlePan(GestureStateChanged, event) } } } return true } private fun handlePan(state: Int, event: MotionEvent) { val locationX = event.x val locationY = event.y when (state) { GestureStateBegan -> { selectSegmentWithPoint(locationX) } GestureStateChanged -> { val delta = min(2.0, ((lastY - locationY) / 8.0f).toDouble()) .toFloat() var curveValue: CurvesValue? = null when (this.curveValue.activeType) { CurvesToolValue.CurvesTypeLuminance -> curveValue = this.curveValue.luminanceCurve CurvesToolValue.CurvesTypeRed -> curveValue = this.curveValue.redCurve CurvesToolValue.CurvesTypeGreen -> curveValue = this.curveValue.greenCurve CurvesToolValue.CurvesTypeBlue -> curveValue = this.curveValue.blueCurve else -> {} } checkNotNull(curveValue) when (activeSegment) { CurvesSegmentBlacks -> curveValue.blacksLevel = max(0.0, min(100.0, (curveValue.blacksLevel + delta).toDouble())) .toFloat() CurvesSegmentShadows -> curveValue.shadowsLevel = max(0.0, min(100.0, (curveValue.shadowsLevel + delta).toDouble())) .toFloat() CurvesSegmentMidtones -> curveValue.midtonesLevel = max(0.0, min(100.0, (curveValue.midtonesLevel + delta).toDouble())) .toFloat() CurvesSegmentHighlights -> curveValue.highlightsLevel = max(0.0, min(100.0, (curveValue.highlightsLevel + delta).toDouble())) .toFloat() CurvesSegmentWhites -> curveValue.whitesLevel = max(0.0, min(100.0, (curveValue.whitesLevel + delta).toDouble())) .toFloat() else -> {} } invalidate() if (delegate != null) { delegate!!.valueChanged() } lastX = locationX lastY = locationY } GestureStateEnded, GestureStateCancelled, GestureStateFailed -> { unselectSegments() } else -> {} } } private fun selectSegmentWithPoint(pointX: Float) { var pointx = pointX if (activeSegment != CurvesSegmentNone) { return } val segmentWidth = actualArea.width / 5.0f pointx -= actualArea.x activeSegment = floor(((pointx / segmentWidth) + 1).toDouble()).toInt() } private fun unselectSegments() { if (activeSegment == CurvesSegmentNone) { return } activeSegment = CurvesSegmentNone } @SuppressLint("DrawAllocation") override fun onDraw(canvas: Canvas) { val segmentWidth = actualArea.width / 5.0f canvas.drawRect( RectF( actualArea.x, actualArea.y + actualArea.height - dp(20f), actualArea.x + actualArea.width, actualArea.y + actualArea.height ), Paint().apply { color = Color.Black.copy(0.5f).toArgb() } ) for (i in 0..3) { canvas.drawLine( actualArea.x + segmentWidth + i * segmentWidth, actualArea.y, actualArea.x + segmentWidth + i * segmentWidth, actualArea.y + actualArea.height, paint ) } canvas.drawLine( actualArea.x, actualArea.y + actualArea.height, actualArea.x + actualArea.width, actualArea.y, paintDash ) var curvesValue: CurvesValue? = null when (curveValue.activeType) { CurvesToolValue.CurvesTypeLuminance -> { paintCurve.color = lumaCurveColor curvesValue = curveValue.luminanceCurve } CurvesToolValue.CurvesTypeRed -> { paintCurve.color = redCurveColor curvesValue = curveValue.redCurve } CurvesToolValue.CurvesTypeGreen -> { paintCurve.color = greenCurveColor curvesValue = curveValue.greenCurve } CurvesToolValue.CurvesTypeBlue -> { paintCurve.color = blueCurveColor curvesValue = curveValue.blueCurve } else -> Unit } for (a in 0..4) { checkNotNull(curvesValue) val str = when (a) { 0 -> String.format(Locale.US, "%.2f", curvesValue.blacksLevel / 100.0f) 1 -> String.format(Locale.US, "%.2f", curvesValue.shadowsLevel / 100.0f) 2 -> String.format(Locale.US, "%.2f", curvesValue.midtonesLevel / 100.0f) 3 -> String.format(Locale.US, "%.2f", curvesValue.highlightsLevel / 100.0f) 4 -> String.format(Locale.US, "%.2f", curvesValue.whitesLevel / 100.0f) else -> "" } val width = textPaint.measureText(str) canvas.drawText( str, actualArea.x + (segmentWidth - width) / 2 + segmentWidth * a, actualArea.y + actualArea.height - dp(4f), textPaint ) } var points: FloatArray if (drawNotActiveCurves) { listOf( curveValue.luminanceCurve to lumaCurveColor, curveValue.redCurve to redCurveColor, curveValue.greenCurve to greenCurveColor, curveValue.blueCurve to blueCurveColor ).filter { it.first != curvesValue && !it.first.isDefault }.forEach { (curve, color) -> paintNotActiveCurve.color = Color(color).copy(0.7f).toArgb() points = curve.interpolateCurve() invalidate() path.reset() for (a in 0 until points.size / 2) { if (a == 0) { path.moveTo( actualArea.x + points[0] * actualArea.width, actualArea.y + (1.0f - points[1]) * actualArea.height ) } else { path.lineTo( actualArea.x + points[a * 2] * actualArea.width, actualArea.y + (1.0f - points[a * 2 + 1]) * actualArea.height ) } } canvas.drawPath(path, paintNotActiveCurve) } } points = curvesValue!!.interpolateCurve() invalidate() path.reset() for (a in 0 until points.size / 2) { if (a == 0) { path.moveTo( actualArea.x + points[0] * actualArea.width, actualArea.y + (1.0f - points[1]) * actualArea.height ) } else { path.lineTo( actualArea.x + points[a * 2] * actualArea.width, actualArea.y + (1.0f - points[a * 2 + 1]) * actualArea.height ) } } canvas.drawPath(path, paintCurve) } fun interface PhotoFilterCurvesControlDelegate { fun valueChanged() } internal class CurvesValue { var blacksLevel: Float = 0.0f var shadowsLevel: Float = 25.0f var midtonesLevel: Float = 50.0f var highlightsLevel: Float = 75.0f var whitesLevel: Float = 100.0f private var cachedDataPoints: FloatArray? = null val dataPoints: FloatArray? get() { if (cachedDataPoints == null) { interpolateCurve() } return cachedDataPoints } fun interpolateCurve(): FloatArray { val points = floatArrayOf( -0.001f, blacksLevel / 100.0f, 0.0f, blacksLevel / 100.0f, 0.25f, shadowsLevel / 100.0f, 0.5f, midtonesLevel / 100.0f, 0.75f, highlightsLevel / 100.0f, 1f, whitesLevel / 100.0f, 1.001f, whitesLevel / 100.0f ) val dataPoints = ArrayList(100) val interpolatedPoints = ArrayList(100) interpolatedPoints.add(points[0]) interpolatedPoints.add(points[1]) for (index in 1 until points.size / 2 - 2) { val point0x = points[(index - 1) * 2] val point0y = points[(index - 1) * 2 + 1] val point1x = points[index * 2] val point1y = points[index * 2 + 1] val point2x = points[(index + 1) * 2] val point2y = points[(index + 1) * 2 + 1] val point3x = points[(index + 2) * 2] val point3y = points[(index + 2) * 2 + 1] for (i in 1 until curveGranularity) { val t = i.toFloat() * (1.0f / curveGranularity.toFloat()) val tt = t * t val ttt = tt * t val pix = 0.5f * (2 * point1x + (point2x - point0x) * t + (2 * point0x - 5 * point1x + 4 * point2x - point3x) * tt + (3 * point1x - point0x - 3 * point2x + point3x) * ttt) var piy = 0.5f * (2 * point1y + (point2y - point0y) * t + (2 * point0y - 5 * point1y + 4 * point2y - point3y) * tt + (3 * point1y - point0y - 3 * point2y + point3y) * ttt) piy = max(0.0, min(1.0, piy.toDouble())).toFloat() if (pix > point0x) { interpolatedPoints.add(pix) interpolatedPoints.add(piy) } if ((i - 1) % curveDataStep == 0) { dataPoints.add(piy) } } interpolatedPoints.add(point2x) interpolatedPoints.add(point2y) } interpolatedPoints.add(points[12]) interpolatedPoints.add(points[13]) cachedDataPoints = FloatArray(dataPoints.size) for (a in cachedDataPoints!!.indices) { cachedDataPoints!![a] = dataPoints[a] } val retValue = FloatArray(interpolatedPoints.size) for (a in retValue.indices) { retValue[a] = interpolatedPoints[a] } return retValue } val isDefault: Boolean get() = abs((blacksLevel - 0).toDouble()) < 0.00001 && abs((shadowsLevel - 25).toDouble()) < 0.00001 && abs( (midtonesLevel - 50).toDouble() ) < 0.00001 && abs((highlightsLevel - 75).toDouble()) < 0.00001 && abs( (whitesLevel - 100).toDouble() ) < 0.00001 } internal class CurvesToolValue { var luminanceCurve: CurvesValue = CurvesValue() var redCurve: CurvesValue = CurvesValue() var greenCurve: CurvesValue = CurvesValue() var blueCurve: CurvesValue = CurvesValue() var activeType: Int = CurvesTypeLuminance companion object { const val CurvesTypeLuminance: Int = 0 const val CurvesTypeRed: Int = 1 const val CurvesTypeGreen: Int = 2 const val CurvesTypeBlue: Int = 3 } } companion object { private const val curveGranularity = 100 private const val curveDataStep = 2 private val density = Resources.getSystem().displayMetrics.density private const val CurvesSegmentNone = 0 private const val CurvesSegmentBlacks = 1 private const val CurvesSegmentShadows = 2 private const val CurvesSegmentMidtones = 3 private const val CurvesSegmentHighlights = 4 private const val CurvesSegmentWhites = 5 private const val GestureStateBegan = 1 private const val GestureStateChanged = 2 private const val GestureStateEnded = 3 private const val GestureStateCancelled = 4 private const val GestureStateFailed = 5 fun dp(value: Float): Int { if (value == 0f) { return 0 } return ceil((density * value).toDouble()) .toInt() } } } ================================================ FILE: lib/curves/src/main/java/com/t8rin/curves/view/Rect.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.curves.view internal class Rect { @JvmField var x: Float = 0f @JvmField var y: Float = 0f @JvmField var width: Float = 0f @JvmField var height: Float = 0f constructor() constructor(x: Float, y: Float, width: Float, height: Float) { this.x = x this.y = y this.width = width this.height = height } } ================================================ FILE: lib/documentscanner/.gitignore ================================================ /build ================================================ FILE: lib/documentscanner/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) } android.namespace = "com.websitebeaver.documentscanner" dependencies { implementation(libs.opencv) implementation(libs.appCompat) implementation(libs.toolbox.exif) implementation(projects.lib.opencvTools) } ================================================ FILE: lib/documentscanner/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner import android.app.Activity import android.content.Intent import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import com.websitebeaver.documentscanner.constants.DefaultSetting import com.websitebeaver.documentscanner.constants.DocumentScannerExtra import com.websitebeaver.documentscanner.constants.ResponseType import com.websitebeaver.documentscanner.extensions.toBase64 import com.websitebeaver.documentscanner.utils.ImageUtil import java.io.File /** * This class is used to start a document scan. It accepts parameters used to customize * the document scan, and callback parameters. * * @param activity the current activity * @param successHandler event handler that gets called on document scan success * @param errorHandler event handler that gets called on document scan error * @param cancelHandler event handler that gets called when a user cancels the document scan * @param responseType the cropped image gets returned in this format * @param letUserAdjustCrop whether or not the user can change the auto detected document corners * @param maxNumDocuments the maximum number of documents a user can scan at once * @param croppedImageQuality the 0 - 100 quality of the cropped image * @constructor creates document scanner */ class DocumentScanner( private val activity: ComponentActivity, private val successHandler: ((documentScanResults: ArrayList) -> Unit)? = null, private val errorHandler: ((errorMessage: String) -> Unit)? = null, private val cancelHandler: (() -> Unit)? = null, private var responseType: String? = null, private var letUserAdjustCrop: Boolean? = null, private var maxNumDocuments: Int? = null, private var croppedImageQuality: Int? = null ) { init { responseType = responseType ?: DefaultSetting.RESPONSE_TYPE croppedImageQuality = croppedImageQuality ?: DefaultSetting.CROPPED_IMAGE_QUALITY } /** * create intent to launch document scanner and set custom options */ fun createDocumentScanIntent(): Intent { val documentScanIntent = Intent(activity, DocumentScannerActivity::class.java) documentScanIntent.putExtra( DocumentScannerExtra.EXTRA_CROPPED_IMAGE_QUALITY, croppedImageQuality ) documentScanIntent.putExtra( DocumentScannerExtra.EXTRA_LET_USER_ADJUST_CROP, letUserAdjustCrop ) documentScanIntent.putExtra( DocumentScannerExtra.EXTRA_MAX_NUM_DOCUMENTS, maxNumDocuments ) return documentScanIntent } /** * handle response from document scanner * * @param result the document scanner activity result */ fun handleDocumentScanIntentResult(result: ActivityResult) = try { // make sure responseType is valid if (!arrayOf( ResponseType.BASE64, ResponseType.IMAGE_FILE_PATH ).contains(responseType) ) { throw Exception( "responseType must be either ${ResponseType.BASE64} " + "or ${ResponseType.IMAGE_FILE_PATH}" ) } when (result.resultCode) { Activity.RESULT_OK -> { // check for errors val error = result.data?.extras?.getString("error") if (error != null) { throw Exception("error - $error") } // get an array with scanned document file paths val croppedImageResults: ArrayList = result.data?.getStringArrayListExtra( "croppedImageResults" ) ?: throw Exception("No cropped images returned") // if responseType is imageFilePath return an array of file paths var successResponse: ArrayList = croppedImageResults // if responseType is base64 return an array of base64 images if (responseType == ResponseType.BASE64) { val base64CroppedImages = croppedImageResults.map { croppedImagePath -> // read cropped image from file path, and convert to base 64 val base64Image = ImageUtil().readBitmapFromFileUriString( croppedImagePath, activity.contentResolver ).toBase64(croppedImageQuality!!) // delete cropped image from android device to avoid // accumulating photos File(croppedImagePath).delete() base64Image } successResponse = base64CroppedImages as ArrayList } // trigger the success event handler with an array of cropped images successHandler?.let { it(successResponse) } } Activity.RESULT_CANCELED -> { // user closed camera cancelHandler?.let { it() } } else -> Unit } } catch (exception: Exception) { // trigger the error event handler errorHandler?.let { it(exception.localizedMessage ?: "An error happened") } } /** * add document scanner result handler and launch the document scanner */ fun startScan() { activity.registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result: ActivityResult -> handleDocumentScanIntentResult(result) } .launch(createDocumentScanIntent()) } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("DEPRECATION") package com.websitebeaver.documentscanner import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.os.Bundle import android.view.View import android.widget.ImageButton import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.FileProvider import com.t8rin.opencv_tools.document_detector.DocumentDetector import com.websitebeaver.documentscanner.constants.DefaultSetting import com.websitebeaver.documentscanner.constants.DocumentScannerExtra import com.websitebeaver.documentscanner.extensions.move import com.websitebeaver.documentscanner.extensions.onClick import com.websitebeaver.documentscanner.extensions.saveToFile import com.websitebeaver.documentscanner.extensions.screenHeight import com.websitebeaver.documentscanner.extensions.screenWidth import com.websitebeaver.documentscanner.models.Document import com.websitebeaver.documentscanner.models.Quad import com.websitebeaver.documentscanner.ui.ImageCropView import com.websitebeaver.documentscanner.utils.FileUtil import com.websitebeaver.documentscanner.utils.ImageUtil import org.opencv.android.OpenCVLoader import org.opencv.core.Point import java.io.File import java.io.IOException /** * This class contains the main document scanner code. It opens the camera, lets the user * take a photo of a document (homework paper, business card, etc.), detects document corners, * allows user to make adjustments to the detected corners, depending on options, and saves * the cropped document. It allows the user to do this for 1 or more documents. * * @constructor creates document scanner activity */ class DocumentScannerActivity : AppCompatActivity() { /** * @property maxNumDocuments maximum number of documents a user can scan at a time */ private var maxNumDocuments = DefaultSetting.MAX_NUM_DOCUMENTS /** * @property letUserAdjustCrop whether or not to let user move automatically detected corners */ private var letUserAdjustCrop = DefaultSetting.LET_USER_ADJUST_CROP /** * @property croppedImageQuality the 0 - 100 quality of the cropped image */ private var croppedImageQuality = DefaultSetting.CROPPED_IMAGE_QUALITY /** * @property cropperOffsetWhenCornersNotFound if we can't find document corners, we set * corners to image size with a slight margin */ private val cropperOffsetWhenCornersNotFound = 100.0 /** * @property document This is the current document. Initially it's null. Once we capture * the photo, and find the corners we update document. */ private var document: Document? = null /** * @property documents a list of documents (original photo file path, original photo * dimensions and 4 corner points) */ private val documents = mutableListOf() /** * @property imageView container with original photo and cropper */ private lateinit var imageView: ImageCropView /** * called when activity is created * * @param savedInstanceState persisted data that maintains state */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) try { OpenCVLoader.initLocal() } catch (exception: Exception) { finishIntentWithError( "error starting OpenCV: ${exception.message}" ) } // Show cropper, accept crop button, add new document button, and // retake photo button. Since we open the camera in a few lines, the user // doesn't see this until they finish taking a photo setContentView(R.layout.activity_image_crop) imageView = findViewById(R.id.image_view) try { // validate maxNumDocuments option, and update default if user sets it var userSpecifiedMaxImages: Int? = null intent.extras?.get(DocumentScannerExtra.EXTRA_MAX_NUM_DOCUMENTS)?.let { if (it.toString().toIntOrNull() == null) { throw Exception( "${DocumentScannerExtra.EXTRA_MAX_NUM_DOCUMENTS} must be a positive number" ) } userSpecifiedMaxImages = it as Int maxNumDocuments = userSpecifiedMaxImages } // validate letUserAdjustCrop option, and update default if user sets it intent.extras?.get(DocumentScannerExtra.EXTRA_LET_USER_ADJUST_CROP)?.let { if (!arrayOf("true", "false").contains(it.toString())) { throw Exception( "${DocumentScannerExtra.EXTRA_LET_USER_ADJUST_CROP} must true or false" ) } letUserAdjustCrop = it as Boolean } // if we don't want user to move corners, we can let the user only take 1 photo if (!letUserAdjustCrop) { maxNumDocuments = 1 if (userSpecifiedMaxImages != null && userSpecifiedMaxImages != 1) { throw Exception( "${DocumentScannerExtra.EXTRA_MAX_NUM_DOCUMENTS} must be 1 when " + "${DocumentScannerExtra.EXTRA_LET_USER_ADJUST_CROP} is false" ) } } // validate croppedImageQuality option, and update value if user sets it intent.extras?.get(DocumentScannerExtra.EXTRA_CROPPED_IMAGE_QUALITY)?.let { if (it !is Int || it < 0 || it > 100) { throw Exception( "${DocumentScannerExtra.EXTRA_CROPPED_IMAGE_QUALITY} must be a number " + "between 0 and 100" ) } croppedImageQuality = it } } catch (exception: Exception) { finishIntentWithError( "invalid extra: ${exception.message}" ) return } // set click event handlers for new document button, accept and crop document button, // and retake document photo button val newPhotoButton: ImageButton = findViewById(R.id.new_photo_button) val completeDocumentScanButton: ImageButton = findViewById( R.id.complete_document_scan_button ) val retakePhotoButton: ImageButton = findViewById(R.id.retake_photo_button) newPhotoButton.onClick { onClickNew() } completeDocumentScanButton.onClick { onClickDone() } retakePhotoButton.onClick { onClickRetake() } // open camera, so user can snap document photo try { openCamera() } catch (exception: Exception) { finishIntentWithError( "error opening camera: ${exception.message}" ) } } /** * Pass in a photo of a document, and get back 4 corner points (top left, top right, bottom * right, bottom left). This tries to detect document corners, but falls back to photo corners * with slight margin in case we can't detect document corners. * * @param photo the original photo with a rectangular document * @return a List of 4 OpenCV points (document corners) */ private fun getDocumentCorners(photo: Bitmap): List { val cornerPoints: List? = DocumentDetector.findDocumentCorners(photo) // if cornerPoints is null then default the corners to the photo bounds with a margin return cornerPoints ?: listOf( Point(0.0, 0.0).move( cropperOffsetWhenCornersNotFound, cropperOffsetWhenCornersNotFound ), Point(photo.width.toDouble(), 0.0).move( -cropperOffsetWhenCornersNotFound, cropperOffsetWhenCornersNotFound ), Point(0.0, photo.height.toDouble()).move( cropperOffsetWhenCornersNotFound, -cropperOffsetWhenCornersNotFound ), Point(photo.width.toDouble(), photo.height.toDouble()).move( -cropperOffsetWhenCornersNotFound, -cropperOffsetWhenCornersNotFound ) ) } /** * Set document to null since we're capturing a new document, and open the camera. If the * user captures a photo successfully document gets updated. */ /** * @property photoFilePath the photo file path */ private lateinit var photoFilePath: String /** * @property startForResult used to launch camera */ private val startForResult = registerForActivityResult( ActivityResultContracts.TakePicture() ) { isSuccess -> if (isSuccess) { // send back photo file path on capture success val originalPhotoPath = photoFilePath // user takes photo // if maxNumDocuments is 3 and this is the 3rd photo, hide the new photo button since // we reach the allowed limit if (documents.size == maxNumDocuments - 1) { val newPhotoButton: ImageButton = findViewById(R.id.new_photo_button) newPhotoButton.isClickable = false newPhotoButton.visibility = View.INVISIBLE } // get bitmap from photo file path val photo: Bitmap = ImageUtil().getImageFromFilePath(originalPhotoPath) // get document corners by detecting them, or falling back to photo corners with // slight margin if we can't find the corners val corners = try { val (topLeft, topRight, bottomLeft, bottomRight) = getDocumentCorners(photo) Quad(topLeft, topRight, bottomRight, bottomLeft) } catch (exception: Exception) { finishIntentWithError( "unable to get document corners: ${exception.message}" ) return@registerForActivityResult } document = Document(originalPhotoPath, photo.width, photo.height, corners) if (letUserAdjustCrop) { // user is allowed to move corners to make corrections try { // set preview image height based off of photo dimensions imageView.setImagePreviewBounds(photo, screenWidth, screenHeight) // display original photo, so user can adjust detected corners imageView.setImage(photo) // document corner points are in original image coordinates, so we need to // scale and move the points to account for blank space (caused by photo and // photo container having different aspect ratios) val cornersInImagePreviewCoordinates = corners .mapOriginalToPreviewImageCoordinates( imageView.imagePreviewBounds, imageView.imagePreviewBounds.height() / photo.height ) // display cropper, and allow user to move corners imageView.setCropper(cornersInImagePreviewCoordinates) } catch (exception: Exception) { finishIntentWithError( "unable get image preview ready: ${exception.message}" ) return@registerForActivityResult } } else { // user isn't allowed to move corners, so accept automatically detected corners document?.let { document -> documents.add(document) } // create cropped document image, and return file path to complete document scan cropDocumentAndFinishIntent() } } else { // delete the photo since the user didn't finish taking the photo File(photoFilePath).delete() // user exits camera // complete document scan if this is the first document since we can't go to crop view // until user takes at least 1 photo if (documents.isEmpty()) { onClickCancel() } } } /** * open the camera by launching an image capture intent * * @param pageNumber the current document page number */ @Throws(IOException::class) private fun openCamera(pageNumber: Int = documents.size) { // create new file for photo val photoFile: File = FileUtil().createImageFile(this, pageNumber) // store the photo file path, and send it back once the photo is saved photoFilePath = photoFile.absolutePath // photo gets saved to this file path // open camera startForResult.launch( FileProvider.getUriForFile( this, "${packageName}.DocumentScannerFileProvider", photoFile ) ) } /** * Once user accepts by pressing check button, or by pressing add new document button, add * original photo path and 4 document corners to documents list. If user isn't allowed to * adjust corners, call this automatically. */ private fun addSelectedCornersAndOriginalPhotoPathToDocuments() { // only add document it's not null (the current document photo capture, and corner // detection are successful) document?.let { document -> // convert corners from image preview coordinates to original photo coordinates // (original image is probably bigger than the preview image) val cornersInOriginalImageCoordinates = imageView.corners .mapPreviewToOriginalImageCoordinates( imageView.imagePreviewBounds, imageView.imagePreviewBounds.height() / document.originalPhotoHeight ) document.corners = cornersInOriginalImageCoordinates documents.add(document) } } /** * This gets called when a user presses the new document button. Store current photo path * with document corners. Then open the camera, so user can take a photo of the next * page or document */ private fun onClickNew() { addSelectedCornersAndOriginalPhotoPathToDocuments() openCamera() } /** * This gets called when a user presses the done button. Store current photo path with * document corners. Then crop document using corners, and return cropped image paths */ private fun onClickDone() { addSelectedCornersAndOriginalPhotoPathToDocuments() cropDocumentAndFinishIntent() } /** * This gets called when a user presses the retake photo button. The user presses this in * case the original document photo isn't good, and they need to take it again. */ private fun onClickRetake() { // we're going to retake the photo, so delete the one we just took document?.let { document -> File(document.originalPhotoFilePath).delete() } openCamera() } /** * This gets called when a user doesn't want to complete the document scan after starting. * For example a user can quit out of the camera before snapping a photo of the document. */ private fun onClickCancel() { setResult(RESULT_CANCELED) finish() } /** * This crops original document photo, saves cropped document photo, deletes original * document photo, and returns cropped document photo file path. It repeats that for * all document photos. */ private fun cropDocumentAndFinishIntent() { val croppedImageResults = arrayListOf() for ((pageNumber, document) in documents.withIndex()) { // crop document photo by using corners val croppedImage: Bitmap = try { ImageUtil().crop( document.originalPhotoFilePath, document.corners ) } catch (exception: Exception) { finishIntentWithError("unable to crop image: ${exception.message}") return } // delete original document photo File(document.originalPhotoFilePath).delete() // save cropped document photo try { val croppedImageFile = FileUtil().createImageFile(this, pageNumber) croppedImage.saveToFile(croppedImageFile, croppedImageQuality) croppedImageResults.add(Uri.fromFile(croppedImageFile).toString()) } catch (exception: Exception) { finishIntentWithError( "unable to save cropped image: ${exception.message}" ) } } // return array of cropped document photo file paths setResult( RESULT_OK, Intent().putExtra("croppedImageResults", croppedImageResults) ) finish() } /** * This ends the document scanner activity, and returns an error message that can be * used to debug error * * @param errorMessage an error message */ private fun finishIntentWithError(errorMessage: String) { setResult( RESULT_OK, Intent().putExtra("error", errorMessage) ) finish() } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerFileProvider.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner import androidx.core.content.FileProvider class DocumentScannerFileProvider : FileProvider() ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.constants /** * This class contains default document scanner options */ class DefaultSetting { companion object { const val CROPPED_IMAGE_QUALITY = 100 const val LET_USER_ADJUST_CROP = true const val MAX_NUM_DOCUMENTS = 24 const val RESPONSE_TYPE = ResponseType.IMAGE_FILE_PATH } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.constants /** * This class contains constants meant to be used as intent extras */ class DocumentScannerExtra { companion object { const val EXTRA_CROPPED_IMAGE_QUALITY = "croppedImageQuality" const val EXTRA_LET_USER_ADJUST_CROP = "letUserAdjustCrop" const val EXTRA_MAX_NUM_DOCUMENTS = "maxNumDocuments" } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ResponseType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.constants /** * constants that represent all possible document scanner response formats */ class ResponseType { companion object { const val BASE64 = "base64" const val IMAGE_FILE_PATH = "imageFilePath" } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/enums/QuadCorner.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.enums /** * enums for all 4 quad corners */ enum class QuadCorner { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/AppCompatActivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.extensions import android.graphics.Rect import androidx.appcompat.app.AppCompatActivity @Suppress("DEPRECATION") /** * @property screenBounds the screen bounds (used to get screen width and height) */ val AppCompatActivity.screenBounds: Rect get() { // currentWindowMetrics was added in Android R if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { return windowManager.currentWindowMetrics.bounds } // fall back to get screen width and height if using a version before Android R return Rect( 0, 0, windowManager.defaultDisplay.width, windowManager.defaultDisplay.height ) } /** * @property screenWidth the screen width */ val AppCompatActivity.screenWidth: Int get() = screenBounds.width() /** * @property screenHeight the screen height */ val AppCompatActivity.screenHeight: Int get() = screenBounds.height() ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Bitmap.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.extensions import android.graphics.Bitmap import android.util.Base64 import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream import kotlin.math.sqrt /** * This converts the bitmap to base64 * * @return image as a base64 string */ fun Bitmap.toBase64(quality: Int): String { val byteArrayOutputStream = ByteArrayOutputStream() compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream) return Base64.encodeToString( byteArrayOutputStream.toByteArray(), Base64.DEFAULT ) } /** * This converts the bitmap to base64 * * @param file the bitmap gets saved to this file */ fun Bitmap.saveToFile(file: File, quality: Int) { val fileOutputStream = FileOutputStream(file) compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream) fileOutputStream.close() } /** * This resizes the image, so that the byte count is a little less than targetBytes * * @param targetBytes the returned bitmap has a size a little less than targetBytes */ fun Bitmap.changeByteCountByResizing(targetBytes: Int): Bitmap { val scale = sqrt(targetBytes.toDouble() / byteCount.toDouble()) return Bitmap.createScaledBitmap( this, (width * scale).toInt(), (height * scale).toInt(), true ) } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Canvas.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.extensions import android.graphics.Canvas import android.graphics.Matrix import android.graphics.Paint import android.graphics.PointF import android.graphics.RectF import android.graphics.drawable.Drawable import com.websitebeaver.documentscanner.enums.QuadCorner import com.websitebeaver.documentscanner.models.Line import com.websitebeaver.documentscanner.models.Quad /** * This draws a quad (used to draw cropper). It draws 4 circles and * 4 connecting lines * * @param quad 4 corners * @param pointRadius corner circle radius * @param cropperLinesAndCornersStyles quad style (color, thickness for example) * @param cropperSelectedCornerFillStyles style for selected corner * @param selectedCorner selected corner */ fun Canvas.drawQuad( quad: Quad, pointRadius: Float, cropperLinesAndCornersStyles: Paint, cropperSelectedCornerFillStyles: Paint, selectedCorner: QuadCorner?, imagePreviewBounds: RectF, ratio: Float, selectedCornerRadiusMagnification: Float, selectedCornerBackgroundMagnification: Float, ) { // draw 4 corner points for ((quadCorner: QuadCorner, cornerPoint: PointF) in quad.corners) { var circleRadius = pointRadius if (quadCorner === selectedCorner) { // the cropper corner circle grows when you touch and drag it circleRadius = selectedCornerRadiusMagnification * pointRadius val matrix = Matrix() matrix.postScale(ratio, ratio, ratio / cornerPoint.x, ratio / cornerPoint.y) matrix.postTranslate(imagePreviewBounds.left, imagePreviewBounds.top) matrix.postScale( selectedCornerBackgroundMagnification, selectedCornerBackgroundMagnification, cornerPoint.x, cornerPoint.y ) cropperSelectedCornerFillStyles.shader.setLocalMatrix(matrix) // fill selected corner circle with magnified image, so it's easier to crop drawCircle(cornerPoint.x, cornerPoint.y, circleRadius, cropperSelectedCornerFillStyles) } // draw corner circles drawCircle( cornerPoint.x, cornerPoint.y, circleRadius, cropperLinesAndCornersStyles ) } // draw 4 connecting lines for (edge: Line in quad.edges) { drawLine(edge.from.x, edge.from.y, edge.to.x, edge.to.y, cropperLinesAndCornersStyles) } } /** * This draws the check icon on the finish document scan button. It's needed * because the inner circle covers the check icon. * * @param buttonCenterX the button center x coordinate * @param buttonCenterY the button center y coordinate * @param drawable the check icon */ fun Canvas.drawCheck(buttonCenterX: Float, buttonCenterY: Float, drawable: Drawable) { val mutate = drawable.constantState?.newDrawable()?.mutate() mutate?.setBounds( (buttonCenterX - drawable.intrinsicWidth.toFloat() / 2).toInt(), (buttonCenterY - drawable.intrinsicHeight.toFloat() / 2).toInt(), (buttonCenterX + drawable.intrinsicWidth.toFloat() / 2).toInt(), (buttonCenterY + drawable.intrinsicHeight.toFloat() / 2).toInt() ) mutate?.draw(this) } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/ImageButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.extensions import android.widget.ImageButton /** * This function adds an on click listener to the button. It makes the button not clickable, * calls the on click function, and then makes the button clickable. This prevents the on click * function from being called while it runs. * * @param onClick the click event handler */ fun ImageButton.onClick(onClick: () -> Unit) { setOnClickListener { isClickable = false onClick() isClickable = true } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/extensions/Point.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.extensions import android.graphics.PointF import org.opencv.core.Point import kotlin.math.pow import kotlin.math.sqrt /** * converts an OpenCV point to Android point * * @return Android point */ fun Point.toPointF(): PointF { return PointF(x.toFloat(), y.toFloat()) } /** * calculates the distance between 2 OpenCV points * * @param point the 2nd OpenCV point * @return the distance between this point and the 2nd point */ fun Point.distance(point: Point): Double { return sqrt((point.x - x).pow(2) + (point.y - y).pow(2)) } /** * offset the OpenCV point by (dx, dy) * * @param dx horizontal offset * @param dy vertical offset * @return the OpenCV point after moving it (dx, dy) */ fun Point.move(dx: Double, dy: Double): Point { return Point(x + dx, y + dy) } /** * converts an Android point to OpenCV point * * @return OpenCV point */ fun PointF.toOpenCVPoint(): Point { return Point(x.toDouble(), y.toDouble()) } /** * multiply an Android point by magnitude * * @return Android point after multiplying by magnitude */ fun PointF.multiply(magnitude: Float): PointF { return PointF(magnitude * x, magnitude * y) } /** * offset the Android point by (dx, dy) * * @param dx horizontal offset * @param dy vertical offset * @return the Android point after moving it (dx, dy) */ fun PointF.move(dx: Float, dy: Float): PointF { return PointF(x + dx, y + dy) } /** * calculates the distance between 2 Android points * * @param point the 2nd Android point * @return the distance between this point and the 2nd point */ fun PointF.distance(point: PointF): Float { return sqrt((point.x - x).pow(2) + (point.y - y).pow(2)) } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Document.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.models /** * This class contains the original document photo, and a cropper. The user can drag the corners * to make adjustments to the detected corners. * * @param originalPhotoFilePath the photo file path before cropping * @param originalPhotoWidth the original photo width * @param originalPhotoHeight the original photo height * @param corners the document's 4 corner points * @constructor creates a document */ class Document( val originalPhotoFilePath: String, private val originalPhotoWidth: Int, val originalPhotoHeight: Int, var corners: Quad ) ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Line.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.models import android.graphics.PointF /** * represents a line connecting 2 Android points * * @param fromPoint the 1st point * @param toPoint the 2nd point * @constructor creates a line connecting 2 points */ class Line(fromPoint: PointF, toPoint: PointF) { val from: PointF = fromPoint val to: PointF = toPoint } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/models/Quad.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.models import android.graphics.PointF import android.graphics.RectF import com.websitebeaver.documentscanner.enums.QuadCorner import com.websitebeaver.documentscanner.extensions.distance import com.websitebeaver.documentscanner.extensions.move import com.websitebeaver.documentscanner.extensions.multiply import com.websitebeaver.documentscanner.extensions.toPointF import org.opencv.core.Point /** * This class is used to represent the cropper. It contains 4 corners. * * @param topLeftCorner the top left corner * @param topRightCorner the top right corner * @param bottomRightCorner the bottom right corner * @param bottomLeftCorner the bottom left corner * @constructor creates a quad from Android points */ class Quad( val topLeftCorner: PointF, val topRightCorner: PointF, val bottomRightCorner: PointF, val bottomLeftCorner: PointF ) { /** * @constructor creates a quad from OpenCV points */ constructor( topLeftCorner: Point, topRightCorner: Point, bottomRightCorner: Point, bottomLeftCorner: Point ) : this( topLeftCorner.toPointF(), topRightCorner.toPointF(), bottomRightCorner.toPointF(), bottomLeftCorner.toPointF() ) /** * @property corners lets us get the point coordinates for any corner */ var corners: MutableMap = mutableMapOf( QuadCorner.TOP_LEFT to topLeftCorner, QuadCorner.TOP_RIGHT to topRightCorner, QuadCorner.BOTTOM_RIGHT to bottomRightCorner, QuadCorner.BOTTOM_LEFT to bottomLeftCorner ) /** * @property edges 4 lines that connect the 4 corners */ val edges: Array get() = arrayOf( Line(topLeftCorner, topRightCorner), Line(topRightCorner, bottomRightCorner), Line(bottomRightCorner, bottomLeftCorner), Line(bottomLeftCorner, topLeftCorner) ) /** * This finds the corner that's closest to a point. When a user touches to drag * the cropper, that point is used to determine which corner to move. * * @param point we want to find the corner closest to this point * @return the closest corner */ fun getCornerClosestToPoint(point: PointF): QuadCorner { return corners.minByOrNull { corner -> corner.value.distance(point) }?.key!! } /** * This moves a corner by (dx, dy) * * @param corner the corner that needs to be moved * @param dx the corner moves dx horizontally * @param dy the corner moves dy vertically */ fun moveCorner(corner: QuadCorner, dx: Float, dy: Float) { corners[corner]?.offset(dx, dy) } /** * This maps original image coordinates to preview image coordinates. The original image * is probably larger than the preview image. * * @param imagePreviewBounds offset the point by the top left of imagePreviewBounds * @param ratio multiply the point by ratio * @return the 4 corners after mapping coordinates */ fun mapOriginalToPreviewImageCoordinates(imagePreviewBounds: RectF, ratio: Float): Quad { return Quad( topLeftCorner.multiply(ratio).move( imagePreviewBounds.left, imagePreviewBounds.top ), topRightCorner.multiply(ratio).move( imagePreviewBounds.left, imagePreviewBounds.top ), bottomRightCorner.multiply(ratio).move( imagePreviewBounds.left, imagePreviewBounds.top ), bottomLeftCorner.multiply(ratio).move( imagePreviewBounds.left, imagePreviewBounds.top ) ) } /** * This maps preview image coordinates to original image coordinates. The original image * is probably larger than the preview image. * * @param imagePreviewBounds reverse offset the point by the top left of imagePreviewBounds * @param ratio divide the point by ratio * @return the 4 corners after mapping coordinates */ fun mapPreviewToOriginalImageCoordinates(imagePreviewBounds: RectF, ratio: Float): Quad { return Quad( topLeftCorner.move( -imagePreviewBounds.left, -imagePreviewBounds.top ).multiply(1 / ratio), topRightCorner.move( -imagePreviewBounds.left, -imagePreviewBounds.top ).multiply(1 / ratio), bottomRightCorner.move( -imagePreviewBounds.left, -imagePreviewBounds.top ).multiply(1 / ratio), bottomLeftCorner.move( -imagePreviewBounds.left, -imagePreviewBounds.top ).multiply(1 / ratio) ) } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/CircleButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.ui import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageButton import com.websitebeaver.documentscanner.R /** * This class creates a circular done button by modifying an image button. This is used for the * add new document button and retake photo button. * * @param context image button context * @param attrs image button attributes * @constructor creates circle button */ class CircleButton( context: Context, attrs: AttributeSet ) : AppCompatImageButton(context, attrs) { /** * @property ring the button's outer ring */ private val ring = Paint(Paint.ANTI_ALIAS_FLAG) init { // set outer ring style ring.color = Color.WHITE ring.style = Paint.Style.STROKE ring.strokeWidth = resources.getDimension(R.dimen.small_button_ring_thickness) } /** * This gets called repeatedly. We use it to draw the button * * @param canvas the image button canvas */ override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // draw outer ring canvas.drawCircle( (width / 2).toFloat(), (height / 2).toFloat(), (width.toFloat() - ring.strokeWidth) / 2, ring ) } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/DoneButton.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.ui import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageButton import androidx.core.content.ContextCompat import com.websitebeaver.documentscanner.R import com.websitebeaver.documentscanner.extensions.drawCheck /** * This class creates a circular done button by modifying an image button. The user presses * this button once they finish cropping an image * * @param context image button context * @param attrs image button attributes * @constructor creates done button */ class DoneButton( context: Context, attrs: AttributeSet ) : AppCompatImageButton(context, attrs) { /** * @property ring the button's outer ring */ private val ring = Paint(Paint.ANTI_ALIAS_FLAG) /** * @property circle the button's inner circle */ private val circle = Paint(Paint.ANTI_ALIAS_FLAG) init { // set outer ring style ring.color = Color.WHITE ring.style = Paint.Style.STROKE ring.strokeWidth = resources.getDimension(R.dimen.large_button_ring_thickness) // set inner circle style circle.color = ContextCompat.getColor(context, R.color.done_button_inner_circle_color) circle.style = Paint.Style.FILL } /** * This gets called repeatedly. We use it to draw the done button. * * @param canvas the image button canvas */ override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // calculate button center point, outer ring radius, and inner circle radius val centerX = width.toFloat() / 2 val centerY = height.toFloat() / 2 val outerRadius = (width.toFloat() - ring.strokeWidth) / 2 val innerRadius = outerRadius - resources.getDimension( R.dimen.large_button_outer_ring_offset ) // draw outer ring canvas.drawCircle(centerX, centerY, outerRadius, ring) // draw inner circle canvas.drawCircle(centerX, centerY, innerRadius, circle) // draw check icon since it gets covered by inner circle canvas.drawCheck(centerX, centerY, drawable) } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/ui/ImageCropView.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.ui import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapShader import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.PointF import android.graphics.RectF import android.graphics.Shader import android.util.AttributeSet import android.view.MotionEvent import androidx.appcompat.widget.AppCompatImageView import androidx.core.graphics.drawable.toBitmap import com.websitebeaver.documentscanner.R import com.websitebeaver.documentscanner.enums.QuadCorner import com.websitebeaver.documentscanner.extensions.changeByteCountByResizing import com.websitebeaver.documentscanner.extensions.drawQuad import com.websitebeaver.documentscanner.models.Quad /** * This class contains the original document photo, and a cropper. The user can drag the corners * to make adjustments to the detected corners. * * @param context view context * @param attrs view attributes * @constructor creates image crop view */ class ImageCropView(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs) { /** * @property quad the 4 document corners */ private var quad: Quad? = null /** * @property prevTouchPoint keep track of where the user touches, so we know how much * to move corners on drag */ private var prevTouchPoint: PointF? = null /** * @property closestCornerToTouch if the user touches close to the top left corner for * example, that corner should move on drag */ private var closestCornerToTouch: QuadCorner? = null /** * @property cropperLinesAndCornersStyles paint style for 4 corners and connecting lines */ private val cropperLinesAndCornersStyles = Paint(Paint.ANTI_ALIAS_FLAG) /** * @property cropperSelectedCornerFillStyles when you tap and drag a cropper corner the circle * acts like a magnifying glass */ private val cropperSelectedCornerFillStyles = Paint() /** * @property imagePreviewHeight this is needed because height doesn't update immediately * after we set the image */ private var imagePreviewHeight = height /** * @property imagePreviewWidth this is needed because width doesn't update immediately * after we set the image */ private var imagePreviewWidth = width /** * @property ratio image container height to image height ratio used to map container * to image coordinates and vice versa */ private val ratio: Float get() = imagePreviewBounds.height() / drawable.intrinsicHeight /** * @property corners document corners in image preview coordinates */ val corners: Quad get() = quad!! /** * @property imagePreviewMaxSizeInBytes if the photo is too big, we need to scale it down * before we display it */ private val imagePreviewMaxSizeInBytes = 100 * 1024 * 1024 init { // set cropper style cropperLinesAndCornersStyles.color = Color.WHITE cropperLinesAndCornersStyles.style = Paint.Style.STROKE cropperLinesAndCornersStyles.strokeWidth = 3f } /** * Initially the image preview height is 0. This calculates the height by using the photo * dimensions. It maintains the photo aspect ratio (we likely need to scale the photo down * to fit the preview container). * * @param photo the original photo with a rectangular document * @param screenWidth the device width */ fun setImagePreviewBounds(photo: Bitmap, screenWidth: Int, screenHeight: Int) { // image width to height aspect ratio val imageRatio = photo.width.toFloat() / photo.height.toFloat() val buttonsViewMinHeight = context.resources.getDimension( R.dimen.buttons_container_min_height ).toInt() imagePreviewHeight = if (photo.height > photo.width) { // if user takes the photo in portrait (screenWidth.toFloat() / imageRatio).toInt() } else { // if user takes the photo in landscape (screenWidth.toFloat() * imageRatio).toInt() } // set a cap on imagePreviewHeight, so that the bottom buttons container isn't hidden imagePreviewHeight = Integer.min( imagePreviewHeight, screenHeight - buttonsViewMinHeight ) imagePreviewWidth = screenWidth // image container initially has a 0 width and 0 height, calculate both and set them layoutParams.height = imagePreviewHeight layoutParams.width = imagePreviewWidth // refresh layout after we change height requestLayout() } /** * Insert bitmap in image view, and trigger onSetImage event handler */ fun setImage(photo: Bitmap) { var previewImagePhoto = photo // if the image is too large, we need to scale it down before displaying it if (photo.byteCount > imagePreviewMaxSizeInBytes) { previewImagePhoto = photo.changeByteCountByResizing(imagePreviewMaxSizeInBytes) } this.setImageBitmap(previewImagePhoto) this.onSetImage() } /** * Once the user takes a photo, we try to detect corners. This function stores them as quad. * * @param cropperCorners 4 corner points in original photo coordinates */ fun setCropper(cropperCorners: Quad) { quad = cropperCorners } /** * @property imagePreviewBounds image coordinates - if the image ratio is different than * the image container ratio then there's blank space either at the top and bottom of the * image or the left and right of the image */ val imagePreviewBounds: RectF get() { // image container width to height ratio val imageViewRatio: Float = imagePreviewWidth.toFloat() / imagePreviewHeight.toFloat() // image width to height ratio val imageRatio = drawable.intrinsicWidth.toFloat() / drawable.intrinsicHeight.toFloat() var left = 0f var top = 0f var right = imagePreviewWidth.toFloat() var bottom = imagePreviewHeight.toFloat() if (imageRatio > imageViewRatio) { // if the image is really wide, there's blank space at the top and bottom val offset = (imagePreviewHeight - (imagePreviewWidth / imageRatio)) / 2 top += offset bottom -= offset } else { // if the image is really tall, there's blank space at the left and right // it's also possible that the image ratio matches the image container ratio // in which case there's no blank space val offset = (imagePreviewWidth - (imagePreviewHeight * imageRatio)) / 2 left += offset right -= offset } return RectF(left, top, right, bottom) } /** * This ensures that the user doesn't drag a corner outside the image * * @param point a point * @return true if the point is inside the image preview container, false it's not */ private fun isPointInsideImage(point: PointF): Boolean { return (point.x >= imagePreviewBounds.left && point.y >= imagePreviewBounds.top && point.x <= imagePreviewBounds.right && point.y <= imagePreviewBounds.bottom) } /** * This gets called once we insert an image in this image view */ private fun onSetImage() { cropperSelectedCornerFillStyles.style = Paint.Style.FILL cropperSelectedCornerFillStyles.shader = BitmapShader( drawable.toBitmap(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP ) } /** * This gets called constantly, and we use it to update the cropper corners * * @param canvas the image preview canvas */ override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (quad !== null) { // draw 4 corners and connecting lines canvas.drawQuad( quad!!, resources.getDimension(R.dimen.cropper_corner_radius), cropperLinesAndCornersStyles, cropperSelectedCornerFillStyles, closestCornerToTouch, imagePreviewBounds, ratio, resources.getDimension(R.dimen.cropper_selected_corner_radius_magnification), resources.getDimension(R.dimen.cropper_selected_corner_background_magnification) ) } } /** * This gets called when the user touches, drags, and stops touching screen. We use this * to figure out which corner we need to move, and how far we need to move it. * * @param event the touch event */ @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { // keep track of the touched point val touchPoint = PointF(event.x, event.y) when (event.action) { MotionEvent.ACTION_DOWN -> { // when the user touches the screen record the point, and find the closest // corner to the touch point prevTouchPoint = touchPoint closestCornerToTouch = quad!!.getCornerClosestToPoint(touchPoint) } MotionEvent.ACTION_UP -> { // when the user stops touching the screen reset these values prevTouchPoint = null closestCornerToTouch = null } MotionEvent.ACTION_MOVE -> { // when the user drags their finger, update the closest corner position val touchMoveXDistance = touchPoint.x - prevTouchPoint!!.x val touchMoveYDistance = touchPoint.y - prevTouchPoint!!.y val cornerNewPosition = PointF( quad!!.corners[closestCornerToTouch]!!.x + touchMoveXDistance, quad!!.corners[closestCornerToTouch]!!.y + touchMoveYDistance ) // make sure the user doesn't drag the corner outside the image preview container if (isPointInsideImage(cornerNewPosition)) { quad!!.moveCorner( closestCornerToTouch!!, touchMoveXDistance, touchMoveYDistance ) } // record the point touched, so we can use it to calculate how far to move corner // next time the user drags (assuming they don't stop touching the screen) prevTouchPoint = touchPoint } } // force refresh view invalidate() return true } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/FileUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.utils import androidx.activity.ComponentActivity import java.io.File import java.io.IOException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale /** * This class contains a helper function creating temporary files * * @constructor creates file util */ class FileUtil { /** * create a temporary file * * @param activity the current activity * @param pageNumber the current document page number */ @Throws(IOException::class) fun createImageFile(activity: ComponentActivity, pageNumber: Int): File { // use current time to make file name more unique val dateTime: String = SimpleDateFormat( "yyyyMMdd_HHmmss", Locale.US ).format(Date()) return File.createTempFile( "DOCUMENT_SCAN_${pageNumber}_${dateTime}", ".jpg", activity.cacheDir ) } } ================================================ FILE: lib/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/ImageUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.websitebeaver.documentscanner.utils import android.content.ContentResolver import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Matrix import androidx.core.graphics.createBitmap import androidx.core.net.toUri import com.t8rin.exif.ExifInterface import com.websitebeaver.documentscanner.extensions.distance import com.websitebeaver.documentscanner.extensions.toOpenCVPoint import com.websitebeaver.documentscanner.models.Quad import org.opencv.android.Utils import org.opencv.core.Mat import org.opencv.core.MatOfPoint2f import org.opencv.core.Point import org.opencv.core.Size import org.opencv.imgcodecs.Imgcodecs import org.opencv.imgproc.Imgproc import java.io.File import kotlin.math.min /** * This class contains helper functions for processing images * * @constructor creates image util */ class ImageUtil { /** * get image matrix from file path * * @param filePath image is saved here * @return image matrix */ private fun getImageMatrixFromFilePath(filePath: String): Mat { // read image as matrix using OpenCV val image: Mat = Imgcodecs.imread(filePath) // if OpenCV fails to read the image then it's empty if (!image.empty()) { // convert image to RGB color space since OpenCV reads it using BGR color space Imgproc.cvtColor(image, image, Imgproc.COLOR_BGR2RGB) return image } if (!File(filePath).exists()) { throw Exception("File doesn't exist - $filePath") } if (!File(filePath).canRead()) { throw Exception("You don't have permission to read $filePath") } // try reading image without OpenCV var imageBitmap = BitmapFactory.decodeFile(filePath) val rotation = when (ExifInterface(filePath).getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL )) { ExifInterface.ORIENTATION_ROTATE_90 -> 90 ExifInterface.ORIENTATION_ROTATE_180 -> 180 ExifInterface.ORIENTATION_ROTATE_270 -> 270 else -> 0 } imageBitmap = Bitmap.createBitmap( imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height, Matrix().apply { postRotate(rotation.toFloat()) }, true ) Utils.bitmapToMat(imageBitmap, image) return image } /** * get bitmap image from file path * * @param filePath image is saved here * @return image bitmap */ fun getImageFromFilePath(filePath: String): Bitmap { // read image as matrix using OpenCV val image: Mat = this.getImageMatrixFromFilePath(filePath) // convert image matrix to bitmap val bitmap = createBitmap(image.cols(), image.rows()) Utils.matToBitmap(image, bitmap) return bitmap } /** * take a photo with a document, crop everything out but document, and force it to display * as a rectangle * * @param photoFilePath original image is saved here * @param corners the 4 document corners * @return bitmap with cropped and warped document */ fun crop(photoFilePath: String, corners: Quad): Bitmap { // read image with OpenCV val image = this.getImageMatrixFromFilePath(photoFilePath) // convert top left, top right, bottom right, and bottom left document corners from // Android points to OpenCV points val tLC = corners.topLeftCorner.toOpenCVPoint() val tRC = corners.topRightCorner.toOpenCVPoint() val bRC = corners.bottomRightCorner.toOpenCVPoint() val bLC = corners.bottomLeftCorner.toOpenCVPoint() // Calculate the document edge distances. The user might take a skewed photo of the // document, so the top left corner to top right corner distance might not be the same // as the bottom left to bottom right corner. We could take an average of the 2, but // this takes the smaller of the 2. It does the same for height. val width = min(tLC.distance(tRC), bLC.distance(bRC)) val height = min(tLC.distance(bLC), tRC.distance(bRC)) // create empty image matrix with cropped and warped document width and height val croppedImage = MatOfPoint2f( Point(0.0, 0.0), Point(width, 0.0), Point(width, height), Point(0.0, height), ) // This crops the document out of the rest of the photo. Since the user might take a // skewed photo instead of a straight on photo, the document might be rotated and // skewed. This corrects that problem. output is an image matrix that contains the // corrected image after this fix. val output = Mat() Imgproc.warpPerspective( image, output, Imgproc.getPerspectiveTransform( MatOfPoint2f(tLC, tRC, bRC, bLC), croppedImage ), Size(width, height) ) // convert output image matrix to bitmap val croppedBitmap = createBitmap(output.cols(), output.rows()) Utils.matToBitmap(output, croppedBitmap) return croppedBitmap } /** * get bitmap image from file uri * * @param fileUriString image is saved here and starts with file:/// * @return bitmap image */ fun readBitmapFromFileUriString( fileUriString: String, contentResolver: ContentResolver ): Bitmap { return BitmapFactory.decodeStream( contentResolver.openInputStream(fileUriString.toUri()) ) } } ================================================ FILE: lib/documentscanner/src/main/res/animator/button_grow_animation.xml ================================================ ================================================ FILE: lib/documentscanner/src/main/res/drawable/ic_baseline_add_24.xml ================================================ ================================================ FILE: lib/documentscanner/src/main/res/drawable/ic_baseline_arrow_back_24.xml ================================================ ================================================ FILE: lib/documentscanner/src/main/res/drawable/ic_baseline_check_24.xml ================================================ ================================================ FILE: lib/documentscanner/src/main/res/layout/activity_image_crop.xml ================================================ ================================================ FILE: lib/documentscanner/src/main/res/values/colors.xml ================================================ #000000 #D0E4FF ================================================ FILE: lib/documentscanner/src/main/res/values/dimens.xml ================================================ 0dp 1 0.1dp 5dp 200dp 25px 4px 5px 1dp 1.07 0dp 15.4dp 75dp 2.91dp 8dp 54.5dp 2dp ================================================ FILE: lib/documentscanner/src/main/res/values/integers.xml ================================================ 100 150 ================================================ FILE: lib/documentscanner/src/main/res/values/strings.xml ================================================ Original Image With Cropper ================================================ FILE: lib/documentscanner/src/main/res/values/themes.xml ================================================ ================================================ FILE: lib/documentscanner/src/main/res/xml/file_paths.xml ================================================ ================================================ FILE: lib/dynamic-theme/.gitignore ================================================ /build ================================================ FILE: lib/dynamic-theme/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.dynamic.theme" dependencies { implementation(libs.androidx.palette.ktx) implementation(libs.materialKolor) } ================================================ FILE: lib/dynamic-theme/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/ColorBlindUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.dynamic.theme import androidx.compose.material3.ColorScheme import androidx.compose.ui.graphics.Color fun Color.toColorBlind(type: ColorBlindType): Color { val (r, g, b) = listOf(this.red, this.green, this.blue) val newColor = when (type) { ColorBlindType.Protanomaly -> { val matrix = listOf( 0.817, 0.183, 0.0, 0.333, 0.667, 0.0, 0.0, 0.125, 0.875 ) transformColor(r, g, b, matrix) } ColorBlindType.Deuteranomaly -> { val matrix = listOf( 0.8, 0.2, 0.0, 0.258, 0.742, 0.0, 0.0, 0.142, 0.858 ) transformColor(r, g, b, matrix) } ColorBlindType.Tritanomaly -> { val matrix = listOf( 0.967, 0.033, 0.0, 0.0, 0.733, 0.267, 0.0, 0.183, 0.817 ) transformColor(r, g, b, matrix) } ColorBlindType.Protanopia -> { val matrix = listOf( 0.567, 0.433, 0.0, 0.558, 0.442, 0.0, 0.0, 0.242, 0.758 ) transformColor(r, g, b, matrix) } ColorBlindType.Deuteranopia -> { val matrix = listOf( 0.625, 0.375, 0.0, 0.7, 0.3, 0.0, 0.0, 0.3, 0.7 ) transformColor(r, g, b, matrix) } ColorBlindType.Tritanopia -> { val matrix = listOf( 0.95, 0.05, 0.0, 0.0, 0.433, 0.567, 0.0, 0.475, 0.525 ) transformColor(r, g, b, matrix) } ColorBlindType.Achromatomaly -> { val matrix = listOf( 0.618, 0.320, 0.062, 0.163, 0.775, 0.062, 0.163, 0.320, 0.516 ) transformColor(r, g, b, matrix) } ColorBlindType.Achromatopsia -> { val matrix = listOf( 0.299, 0.587, 0.114, 0.299, 0.587, 0.114, 0.299, 0.587, 0.114 ) transformColor(r, g, b, matrix) } } return Color(newColor[0], newColor[1], newColor[2], this.alpha) } private fun transformColor(r: Float, g: Float, b: Float, matrix: List): List { val newR = (matrix[0] * r + matrix[1] * g + matrix[2] * b).toFloat() val newG = (matrix[3] * r + matrix[4] * g + matrix[5] * b).toFloat() val newB = (matrix[6] * r + matrix[7] * g + matrix[8] * b).toFloat() return listOf(newR.coerceIn(0f, 1f), newG.coerceIn(0f, 1f), newB.coerceIn(0f, 1f)) } enum class ColorBlindType { Protanomaly, Deuteranomaly, Tritanomaly, Protanopia, Deuteranopia, Tritanopia, Achromatomaly, Achromatopsia } fun ColorScheme.toColorBlind(type: ColorBlindType?): ColorScheme { if (type == null) return this return this.copy( primary = primary.toColorBlind(type), onPrimary = onPrimary.toColorBlind(type), primaryContainer = primaryContainer.toColorBlind(type), onPrimaryContainer = onPrimaryContainer.toColorBlind(type), inversePrimary = inversePrimary.toColorBlind(type), secondary = secondary.toColorBlind(type), onSecondary = onSecondary.toColorBlind(type), secondaryContainer = secondaryContainer.toColorBlind(type), onSecondaryContainer = onSecondaryContainer.toColorBlind(type), tertiary = tertiary.toColorBlind(type), onTertiary = onTertiary.toColorBlind(type), tertiaryContainer = tertiaryContainer.toColorBlind(type), onTertiaryContainer = onTertiaryContainer.toColorBlind(type), background = background.toColorBlind(type), onBackground = onBackground.toColorBlind(type), surface = surface.toColorBlind(type), onSurface = onSurface.toColorBlind(type), surfaceVariant = surfaceVariant.toColorBlind(type), onSurfaceVariant = onSurfaceVariant.toColorBlind(type), surfaceTint = surfaceTint.toColorBlind(type), inverseSurface = inverseSurface.toColorBlind(type), inverseOnSurface = inverseOnSurface.toColorBlind(type), error = error.toColorBlind(type), onError = onError.toColorBlind(type), errorContainer = errorContainer.toColorBlind(type), onErrorContainer = onErrorContainer.toColorBlind(type), outline = outline.toColorBlind(type), outlineVariant = outlineVariant.toColorBlind(type), surfaceBright = surfaceBright.toColorBlind(type), surfaceDim = surfaceDim.toColorBlind(type), surfaceContainer = surfaceContainer.toColorBlind(type), surfaceContainerHigh = surfaceContainerHigh.toColorBlind(type), surfaceContainerHighest = surfaceContainerHighest.toColorBlind(type), surfaceContainerLow = surfaceContainerLow.toColorBlind(type), surfaceContainerLowest = surfaceContainerLowest.toColorBlind(type), primaryFixed = primaryFixed.toColorBlind(type), primaryFixedDim = primaryFixedDim.toColorBlind(type), onPrimaryFixed = onPrimaryFixed.toColorBlind(type), onPrimaryFixedVariant = onPrimaryFixedVariant.toColorBlind(type), secondaryFixed = secondaryFixed.toColorBlind(type), secondaryFixedDim = secondaryFixedDim.toColorBlind(type), onSecondaryFixed = onSecondaryFixed.toColorBlind(type), onSecondaryFixedVariant = onSecondaryFixedVariant.toColorBlind(type), tertiaryFixed = tertiaryFixed.toColorBlind(type), tertiaryFixedDim = tertiaryFixedDim.toColorBlind(type), onTertiaryFixed = onTertiaryFixed.toColorBlind(type), onTertiaryFixedVariant = onTertiaryFixedVariant.toColorBlind(type), ) } ================================================ FILE: lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/DynamicTheme.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused", "MemberVisibilityCanBePrivate", "KotlinConstantConditions") package com.t8rin.dynamic.theme import android.Manifest import android.annotation.SuppressLint import android.app.WallpaperManager import android.content.Context import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter import android.graphics.Paint import android.os.Build import androidx.annotation.FloatRange import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Typography import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.createBitmap import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.palette.graphics.Palette import com.materialkolor.dynamiccolor.MaterialDynamicColors import com.materialkolor.hct.Hct import com.materialkolor.palettes.TonalPalette import com.materialkolor.scheme.DynamicScheme import com.materialkolor.scheme.SchemeContent import com.materialkolor.scheme.SchemeExpressive import com.materialkolor.scheme.SchemeFidelity import com.materialkolor.scheme.SchemeFruitSalad import com.materialkolor.scheme.SchemeMonochrome import com.materialkolor.scheme.SchemeNeutral import com.materialkolor.scheme.SchemeRainbow import com.materialkolor.scheme.SchemeVibrant import com.materialkolor.scheme.Variant import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * DynamicTheme allows you to dynamically change the color scheme of the content hierarchy. * To do this you just need to update [DynamicThemeState]. * @param state - current instance of [DynamicThemeState] * */ @Composable fun DynamicTheme( state: DynamicThemeState, typography: Typography = MaterialTheme.typography, density: Density = LocalDensity.current, defaultColorTuple: ColorTuple, dynamicColor: Boolean = true, amoledMode: Boolean = false, isDarkTheme: Boolean, style: PaletteStyle = PaletteStyle.TonalSpot, contrastLevel: Double = 0.0, isInvertColors: Boolean = false, colorBlindType: ColorBlindType? = null, colorAnimationSpec: AnimationSpec = tween(300), dynamicColorsOverride: ((isDarkTheme: Boolean) -> ColorTuple?)? = null, content: @Composable () -> Unit, ) { val colorTuple = rememberAppColorTuple( defaultColorTuple = defaultColorTuple, dynamicColor = dynamicColor, darkTheme = isDarkTheme ) val configuration = LocalConfiguration.current var prevOrientation by rememberSaveable { mutableIntStateOf(configuration.orientation) } LaunchedEffect(colorTuple, prevOrientation) { if (prevOrientation == configuration.orientation) { state.updateColorTuple(colorTuple) } else prevOrientation = configuration.orientation } val lightTheme = !isDarkTheme val systemUiController = rememberSystemUiController() val useDarkIcons = if (dynamicColor) { lightTheme } else if (isInvertColors) { isDarkTheme } else { lightTheme } SideEffect { systemUiController.setSystemBarsColor( color = Color.Transparent, darkIcons = useDarkIcons, isNavigationBarContrastEnforced = false ) } CompositionLocalProvider( values = arrayOf( LocalDynamicThemeState provides state, LocalDensity provides density ), content = { MaterialTheme( typography = typography, colorScheme = rememberColorScheme( amoledMode = amoledMode, isDarkTheme = isDarkTheme, colorTuple = state.colorTuple.value, style = style, contrastLevel = contrastLevel, dynamicColor = dynamicColor, isInvertColors = isInvertColors, colorBlindType = colorBlindType, dynamicColorsOverride = dynamicColorsOverride ).animateAllColors(colorAnimationSpec), content = content ) } ) } /**Composable representing ColorTuple object **/ @Composable fun ColorTupleItem( modifier: Modifier = Modifier, backgroundColor: Color = MaterialTheme.colorScheme.surface, colorTuple: ColorTuple, shape: Shape = CircleShape, containerShape: Shape = MaterialTheme.shapes.medium, contentPadding: PaddingValues = PaddingValues(8.dp), content: (@Composable BoxScope.() -> Unit)? = null ) { val (primary, secondary, tertiary) = remember(colorTuple) { derivedStateOf { colorTuple.run { val hct = Hct.fromInt(colorTuple.primary.toArgb()) val hue = hct.hue val chroma = hct.chroma val secondary = colorTuple.secondary?.toArgb().let { if (it != null) { TonalPalette.fromInt(it) } else { TonalPalette.fromHueAndChroma(hue, chroma / 3.0) } } val tertiary = colorTuple.tertiary?.toArgb().let { if (it != null) { TonalPalette.fromInt(it) } else { TonalPalette.fromHueAndChroma(hue + 60.0, chroma / 2.0) } } Triple( primary, colorTuple.secondary ?: Color(secondary.tone(70)), colorTuple.tertiary ?: Color(tertiary.tone(70)) ) } } }.value.run { Triple( animateColorAsState(targetValue = first).value, animateColorAsState(targetValue = second).value, animateColorAsState(targetValue = third).value ) } Surface( modifier = modifier, color = backgroundColor, shape = containerShape ) { Box( modifier = Modifier .fillMaxSize() .padding(contentPadding) .clip(shape), contentAlignment = Alignment.Center ) { Column(Modifier.fillMaxSize()) { Box( modifier = Modifier .fillMaxWidth() .weight(1f) .background(primary) ) Row( modifier = Modifier .weight(1f) .fillMaxWidth() ) { Box( modifier = Modifier .weight(1f) .fillMaxHeight() .background(tertiary) ) Box( modifier = Modifier .weight(1f) .fillMaxHeight() .background(secondary) ) } } content?.invoke(this) } } } fun Color.calculateSecondaryColor(): Int { val hct = Hct.fromInt(this.toArgb()) val hue = hct.hue val chroma = hct.chroma return TonalPalette.fromHueAndChroma(hue, chroma / 3.0).tone(80) } fun Color.calculateTertiaryColor(): Int { val hct = Hct.fromInt(this.toArgb()) val hue = hct.hue val chroma = hct.chroma return TonalPalette.fromHueAndChroma(hue + 60.0, chroma / 2.0).tone(80) } fun Color.calculateSurfaceColor(): Int { val hct = Hct.fromInt(this.toArgb()) val hue = hct.hue val chroma = hct.chroma return TonalPalette.fromHueAndChroma(hue, (chroma / 12.0).coerceAtMost(4.0)).tone(90) } @SuppressLint("MissingPermission") @Composable fun rememberAppColorTuple( defaultColorTuple: ColorTuple, dynamicColor: Boolean, darkTheme: Boolean ): ColorTuple { val context = LocalContext.current return remember( LocalLifecycleOwner.current.lifecycle.observeAsState().value, dynamicColor, darkTheme, defaultColorTuple ) { derivedStateOf { runCatching { val wallpaperManager = WallpaperManager.getInstance(context) val wallColors = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { wallpaperManager .getWallpaperColors(WallpaperManager.FLAG_SYSTEM) } else null when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { if (darkTheme) { dynamicDarkColorScheme(context) } else { dynamicLightColorScheme(context) }.run { ColorTuple( primary = primary, secondary = secondary, tertiary = tertiary, surface = surface ) } } dynamicColor && wallColors != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> { ColorTuple( primary = Color(wallColors.primaryColor.toArgb()), secondary = wallColors.secondaryColor?.toArgb()?.let { Color(it) }, tertiary = wallColors.tertiaryColor?.toArgb()?.let { Color(it) } ) } dynamicColor && ActivityCompat.checkSelfPermission( context, Manifest.permission.READ_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED -> { val primary = wallpaperManager.drawable?.toBitmap()?.extractPrimaryColor() primary?.let { ColorTuple( primary = primary ) } } else -> defaultColorTuple } }.getOrNull() ?: defaultColorTuple } }.value } @Composable fun Lifecycle.observeAsState(): State { val state = remember { mutableStateOf(Lifecycle.Event.ON_ANY) } DisposableEffect(this) { val observer = LifecycleEventObserver { _, event -> state.value = event } this@observeAsState.addObserver(observer) onDispose { this@observeAsState.removeObserver(observer) } } return state } /** * This function animates colors when current color scheme changes. * * @param animationSpec Animation that will be applied when theming option changes. * @return [ColorScheme] with animated colors. */ @Composable fun ColorScheme.animateAllColors(animationSpec: AnimationSpec): ColorScheme { /** * Wraps color into [animateColorAsState]. * * @return Animated [Color]. */ @Composable fun Color.animateColor() = animateColorAsState(this, animationSpec).value return this.copy( primary = primary.animateColor(), onPrimary = onPrimary.animateColor(), primaryContainer = primaryContainer.animateColor(), onPrimaryContainer = onPrimaryContainer.animateColor(), inversePrimary = inversePrimary.animateColor(), secondary = secondary.animateColor(), onSecondary = onSecondary.animateColor(), secondaryContainer = secondaryContainer.animateColor(), onSecondaryContainer = onSecondaryContainer.animateColor(), tertiary = tertiary.animateColor(), onTertiary = onTertiary.animateColor(), tertiaryContainer = tertiaryContainer.animateColor(), onTertiaryContainer = onTertiaryContainer.animateColor(), background = background.animateColor(), onBackground = onBackground.animateColor(), surface = surface.animateColor(), onSurface = onSurface.animateColor(), surfaceVariant = surfaceVariant.animateColor(), onSurfaceVariant = onSurfaceVariant.animateColor(), surfaceTint = surfaceTint.animateColor(), inverseSurface = inverseSurface.animateColor(), inverseOnSurface = inverseOnSurface.animateColor(), error = error.animateColor(), onError = onError.animateColor(), errorContainer = errorContainer.animateColor(), onErrorContainer = onErrorContainer.animateColor(), outline = outline.animateColor(), outlineVariant = outlineVariant.animateColor(), scrim = scrim.animateColor(), surfaceBright = surfaceBright.animateColor(), surfaceDim = surfaceDim.animateColor(), surfaceContainer = surfaceContainer.animateColor(), surfaceContainerHigh = surfaceContainerHigh.animateColor(), surfaceContainerHighest = surfaceContainerHighest.animateColor(), surfaceContainerLow = surfaceContainerLow.animateColor(), surfaceContainerLowest = surfaceContainerLowest.animateColor() ) } private fun Int.blend( color: Int, @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.5f ): Int = ColorUtils.blendARGB(this, color, fraction) fun Bitmap.extractPrimaryColor(default: Int = 0, blendWithVibrant: Boolean = true): Color { val palette = Palette .from(this) .generate() return Color( palette.getDominantColor(default).run { if (blendWithVibrant) blend(palette.getVibrantColor(default), 0.5f) else this } ) } /** Class that represents App color scheme based on three main colors * @param primary primary color * @param secondary secondary color * @param tertiary tertiary color */ data class ColorTuple( val primary: Color, val secondary: Color? = null, val tertiary: Color? = null, val surface: Color? = null ) { override fun toString(): String = "ColorTuple(primary=${primary.toArgb()}, secondary=${secondary?.toArgb()}, tertiary=${tertiary?.toArgb()}, surface=${surface?.toArgb()})" } /** * Creates and remember [DynamicThemeState] instance * */ @Composable fun rememberDynamicThemeState( initialColorTuple: ColorTuple = ColorTuple( primary = MaterialTheme.colorScheme.primary, secondary = MaterialTheme.colorScheme.secondary, tertiary = MaterialTheme.colorScheme.tertiary, surface = MaterialTheme.colorScheme.surface ) ): DynamicThemeState = rememberSaveable(saver = DynamicThemeStateSaver) { DynamicThemeState(initialColorTuple) } val DynamicThemeStateSaver = listSaver( save = { val colorTuple = it.colorTuple.value val list: List = listOf( colorTuple.primary.toArgb(), colorTuple.secondary?.toArgb() ?: -1, colorTuple.tertiary?.toArgb() ?: -1, colorTuple.surface?.toArgb() ?: -1 ) list }, restore = { ints: List -> DynamicThemeState( initialColorTuple = ColorTuple( primary = Color(ints[0]), secondary = ints[1].takeIf { it != -1 }?.let { Color(it) }, tertiary = ints[2].takeIf { it != -1 }?.let { Color(it) }, surface = ints[3].takeIf { it != -1 }?.let { Color(it) }, ) ) } ) @Stable class DynamicThemeState( initialColorTuple: ColorTuple ) { private val _colorTuple: MutableState = mutableStateOf(initialColorTuple) val colorTuple: State = _colorTuple fun updateColor(color: Color) { _colorTuple.value = ColorTuple(primary = color, secondary = null, tertiary = null) } fun updateColorTuple(newColorTuple: ColorTuple) { _colorTuple.value = newColorTuple } fun updateColorByImage(bitmap: Bitmap) { CoroutineScope(Dispatchers.Main).launch { updateColor(bitmap.saturate(2f).extractPrimaryColor()) } } private suspend fun Bitmap.saturate(saturation: Float): Bitmap = withContext(Dispatchers.IO) { val src = this@saturate val w = src.width.coerceAtMost(600) val h = src.height.coerceAtMost(600) val bitmapResult = createBitmap(w, h) val canvasResult = Canvas(bitmapResult) val paint = Paint() val colorMatrix = ColorMatrix() colorMatrix.setSaturation(saturation) val filter = ColorMatrixColorFilter(colorMatrix) paint.setColorFilter(filter) canvasResult.drawBitmap(src, 0f, 0f, paint) return@withContext bitmapResult } } @Composable fun rememberColorScheme( isDarkTheme: Boolean, amoledMode: Boolean, colorTuple: ColorTuple, style: PaletteStyle, contrastLevel: Double, dynamicColor: Boolean, isInvertColors: Boolean, colorBlindType: ColorBlindType? = null, dynamicColorsOverride: ((isDarkTheme: Boolean) -> ColorTuple?)? = null, ): ColorScheme { val context = LocalContext.current return remember( colorTuple, isDarkTheme, amoledMode, contrastLevel, dynamicColor, style, isInvertColors, colorBlindType, dynamicColorsOverride ) { derivedStateOf { context.getColorScheme( isDarkTheme = isDarkTheme, amoledMode = amoledMode, colorTuple = colorTuple, style = style, contrastLevel = contrastLevel, dynamicColor = dynamicColor, isInvertColors = isInvertColors, colorBlindType = colorBlindType, dynamicColorsOverride = dynamicColorsOverride ) } }.value } fun Context.getColorScheme( isDarkTheme: Boolean, amoledMode: Boolean, colorTuple: ColorTuple, style: PaletteStyle, contrastLevel: Double, dynamicColor: Boolean, isInvertColors: Boolean, colorBlindType: ColorBlindType? = null, dynamicColorsOverride: ((isDarkTheme: Boolean) -> ColorTuple?)? = null, ): ColorScheme { val overridden = if (dynamicColor) dynamicColorsOverride?.invoke(isDarkTheme) else null val colorTuple = overridden ?: colorTuple val colorScheme = if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && overridden == null) { if (isDarkTheme) { dynamicDarkColorScheme(this) } else { dynamicLightColorScheme(this) } } else { val hct = Hct.fromInt(colorTuple.primary.toArgb()) val hue = hct.hue val chroma = hct.chroma val a1 = colorTuple.primary.let { TonalPalette.fromInt(it.toArgb()) } val a2 = colorTuple.secondary?.toArgb().let { if (it != null) { TonalPalette.fromInt(it) } else { TonalPalette.fromHueAndChroma(hue, chroma / 3.0) } } val a3 = colorTuple.tertiary?.toArgb().let { if (it != null) { TonalPalette.fromInt(it) } else { TonalPalette.fromHueAndChroma(hue + 60.0, chroma / 2.0) } } val n1 = colorTuple.surface?.toArgb().let { if (it != null) { TonalPalette.fromInt(it) } else { TonalPalette.fromHueAndChroma(hue, (chroma / 12.0).coerceAtMost(4.0)) } } val n2 = TonalPalette.fromInt(n1.tone(90)) val scheme = when (style) { PaletteStyle.TonalSpot -> DynamicScheme( hct, Variant.TONAL_SPOT, isDarkTheme, contrastLevel, a1, a2, a3, n1, n2 ) PaletteStyle.Neutral -> SchemeNeutral(hct, isDarkTheme, contrastLevel) PaletteStyle.Vibrant -> SchemeVibrant(hct, isDarkTheme, contrastLevel) PaletteStyle.Expressive -> SchemeExpressive(hct, isDarkTheme, contrastLevel) PaletteStyle.Rainbow -> SchemeRainbow(hct, isDarkTheme, contrastLevel) PaletteStyle.FruitSalad -> SchemeFruitSalad(hct, isDarkTheme, contrastLevel) PaletteStyle.Monochrome -> SchemeMonochrome(hct, isDarkTheme, contrastLevel) PaletteStyle.Fidelity -> SchemeFidelity(hct, isDarkTheme, contrastLevel) PaletteStyle.Content -> SchemeContent(hct, isDarkTheme, contrastLevel) } scheme.toColorScheme() } return colorScheme .toAmoled(amoledMode && isDarkTheme) .toColorBlind(colorBlindType) .invertColors(isInvertColors && !dynamicColor) .run { copy( outlineVariant = onSecondaryContainer .copy(alpha = 0.2f) .compositeOver(surfaceColorAtElevation(6.dp)) ) } } private fun ColorScheme.invertColors( enabled: Boolean ): ColorScheme { fun Color.invertColor(): Color = if (enabled) { Color(this.toArgb() xor 0x00ffffff) } else this return this.copy( primary = primary.invertColor(), onPrimary = onPrimary.invertColor(), primaryContainer = primaryContainer.invertColor(), onPrimaryContainer = onPrimaryContainer.invertColor(), inversePrimary = inversePrimary.invertColor(), secondary = secondary.invertColor(), onSecondary = onSecondary.invertColor(), secondaryContainer = secondaryContainer.invertColor(), onSecondaryContainer = onSecondaryContainer.invertColor(), tertiary = tertiary.invertColor(), onTertiary = onTertiary.invertColor(), tertiaryContainer = tertiaryContainer.invertColor(), onTertiaryContainer = onTertiaryContainer.invertColor(), background = background.invertColor(), onBackground = onBackground.invertColor(), surface = surface.invertColor(), onSurface = onSurface.invertColor(), surfaceVariant = surfaceVariant.invertColor(), onSurfaceVariant = onSurfaceVariant.invertColor(), surfaceTint = surfaceTint.invertColor(), inverseSurface = inverseSurface.invertColor(), inverseOnSurface = inverseOnSurface.invertColor(), error = error.invertColor(), onError = onError.invertColor(), errorContainer = errorContainer.invertColor(), onErrorContainer = onErrorContainer.invertColor(), outline = outline.invertColor(), outlineVariant = outlineVariant.invertColor(), surfaceBright = surfaceBright.invertColor(), surfaceDim = surfaceDim.invertColor(), surfaceContainer = surfaceContainer.invertColor(), surfaceContainerHigh = surfaceContainerHigh.invertColor(), surfaceContainerHighest = surfaceContainerHighest.invertColor(), surfaceContainerLow = surfaceContainerLow.invertColor(), surfaceContainerLowest = surfaceContainerLowest.invertColor(), primaryFixed = primaryFixed.invertColor(), primaryFixedDim = primaryFixedDim.invertColor(), onPrimaryFixed = onPrimaryFixed.invertColor(), onPrimaryFixedVariant = onPrimaryFixedVariant.invertColor(), secondaryFixed = secondaryFixed.invertColor(), secondaryFixedDim = secondaryFixedDim.invertColor(), onSecondaryFixed = onSecondaryFixed.invertColor(), onSecondaryFixedVariant = onSecondaryFixedVariant.invertColor(), tertiaryFixed = tertiaryFixed.invertColor(), tertiaryFixedDim = tertiaryFixedDim.invertColor(), onTertiaryFixed = onTertiaryFixed.invertColor(), onTertiaryFixedVariant = onTertiaryFixedVariant.invertColor(), ) } private fun DynamicScheme.toColorScheme(): ColorScheme { val colors = MaterialDynamicColors() val scheme = this return ColorScheme( primary = Color(colors.primary().getArgb(scheme)), onPrimary = Color(colors.onPrimary().getArgb(scheme)), primaryContainer = Color(colors.primaryContainer().getArgb(scheme)), onPrimaryContainer = Color(colors.onPrimaryContainer().getArgb(scheme)), inversePrimary = Color(colors.inversePrimary().getArgb(scheme)), secondary = Color(colors.secondary().getArgb(scheme)), onSecondary = Color(colors.onSecondary().getArgb(scheme)), secondaryContainer = Color(colors.secondaryContainer().getArgb(scheme)), onSecondaryContainer = Color(colors.onSecondaryContainer().getArgb(scheme)), tertiary = Color(colors.tertiary().getArgb(scheme)), onTertiary = Color(colors.onTertiary().getArgb(scheme)), tertiaryContainer = Color(colors.tertiaryContainer().getArgb(scheme)), onTertiaryContainer = Color(colors.onTertiaryContainer().getArgb(scheme)), background = Color(colors.background().getArgb(scheme)), onBackground = Color(colors.onBackground().getArgb(scheme)), surface = Color(colors.surface().getArgb(scheme)), onSurface = Color(colors.onSurface().getArgb(scheme)), surfaceVariant = Color(colors.surfaceVariant().getArgb(scheme)), onSurfaceVariant = Color(colors.onSurfaceVariant().getArgb(scheme)), surfaceTint = Color(colors.surfaceTint().getArgb(scheme)), inverseSurface = Color(colors.inverseSurface().getArgb(scheme)), inverseOnSurface = Color(colors.inverseOnSurface().getArgb(scheme)), error = Color(colors.error().getArgb(scheme)), onError = Color(colors.onError().getArgb(scheme)), errorContainer = Color(colors.errorContainer().getArgb(scheme)), onErrorContainer = Color(colors.onErrorContainer().getArgb(scheme)), outline = Color(colors.outline().getArgb(scheme)), outlineVariant = Color(colors.outlineVariant().getArgb(scheme)), scrim = Color(colors.scrim().getArgb(scheme)), surfaceBright = Color(colors.surfaceBright().getArgb(scheme)), surfaceDim = Color(colors.surfaceDim().getArgb(scheme)), surfaceContainer = Color(colors.surfaceContainer().getArgb(scheme)), surfaceContainerHigh = Color(colors.surfaceContainerHigh().getArgb(scheme)), surfaceContainerHighest = Color(colors.surfaceContainerHighest().getArgb(scheme)), surfaceContainerLow = Color(colors.surfaceContainerLow().getArgb(scheme)), surfaceContainerLowest = Color(colors.surfaceContainerLowest().getArgb(scheme)), primaryFixed = Color(colors.primaryFixed().getArgb(scheme)), primaryFixedDim = Color(colors.primaryFixedDim().getArgb(scheme)), onPrimaryFixed = Color(colors.onPrimaryFixed().getArgb(scheme)), onPrimaryFixedVariant = Color(colors.onPrimaryFixedVariant().getArgb(scheme)), secondaryFixed = Color(colors.secondaryFixed().getArgb(scheme)), secondaryFixedDim = Color(colors.secondaryFixedDim().getArgb(scheme)), onSecondaryFixed = Color(colors.onSecondaryFixed().getArgb(scheme)), onSecondaryFixedVariant = Color(colors.onSecondaryFixedVariant().getArgb(scheme)), tertiaryFixed = Color(colors.tertiaryFixed().getArgb(scheme)), tertiaryFixedDim = Color(colors.tertiaryFixedDim().getArgb(scheme)), onTertiaryFixed = Color(colors.onTertiaryFixed().getArgb(scheme)), onTertiaryFixedVariant = Color(colors.onTertiaryFixedVariant().getArgb(scheme)), ) } private fun ColorScheme.toAmoled(amoledMode: Boolean): ColorScheme { fun Color.darken(fraction: Float = 0.5f): Color = Color(toArgb().blend(Color.Black.toArgb(), fraction)) return if (amoledMode) { copy( primary = primary.darken(0.3f), onPrimary = onPrimary.darken(0.1f), primaryContainer = primaryContainer.darken(0.3f), onPrimaryContainer = onPrimaryContainer.darken(0.1f), inversePrimary = inversePrimary.darken(0.3f), secondary = secondary.darken(0.3f), onSecondary = onSecondary.darken(0.1f), secondaryContainer = secondaryContainer.darken(0.3f), onSecondaryContainer = onSecondaryContainer.darken(0.1f), tertiary = tertiary.darken(0.3f), onTertiary = onTertiary.darken(0.1f), tertiaryContainer = tertiaryContainer.darken(0.3f), onTertiaryContainer = onTertiaryContainer.darken(0.1f), background = Color.Black, onBackground = onBackground.darken(0.1f), surface = Color.Black, onSurface = onSurface.darken(0.1f), surfaceVariant = surfaceVariant.darken(0.1f), onSurfaceVariant = onSurfaceVariant.darken(0.1f), surfaceTint = surfaceTint, inverseSurface = inverseSurface.darken(), inverseOnSurface = inverseOnSurface.darken(0.1f), error = error.darken(0.3f), onError = onError.darken(0.1f), errorContainer = errorContainer.darken(0.3f), onErrorContainer = onErrorContainer.darken(0.1f), outline = outline.darken(0.2f), outlineVariant = outlineVariant.darken(0.2f), scrim = scrim.darken(), surfaceBright = surfaceBright.darken(), surfaceDim = surfaceDim.darken(), surfaceContainer = surfaceContainer.darken(), surfaceContainerHigh = surfaceContainerHigh.darken(), surfaceContainerHighest = surfaceContainerHighest.darken(), surfaceContainerLow = surfaceContainerLow.darken(), surfaceContainerLowest = surfaceContainerLowest.darken(), primaryFixed = primaryFixed.darken(0.3f), primaryFixedDim = primaryFixedDim.darken(0.3f), onPrimaryFixed = onPrimaryFixed.darken(0.1f), onPrimaryFixedVariant = onPrimaryFixedVariant.darken(0.1f), secondaryFixed = secondaryFixed.darken(0.3f), secondaryFixedDim = secondaryFixedDim.darken(0.3f), onSecondaryFixed = onSecondaryFixed.darken(0.1f), onSecondaryFixedVariant = onSecondaryFixedVariant.darken(0.1f), tertiaryFixed = tertiaryFixed.darken(0.3f), tertiaryFixedDim = tertiaryFixedDim.darken(0.3f), onTertiaryFixed = onTertiaryFixed.darken(0.1f), onTertiaryFixedVariant = onTertiaryFixedVariant.darken(0.1f), ) } else this } val LocalDynamicThemeState: ProvidableCompositionLocal = compositionLocalOf { error("DynamicThemeState not present") } ================================================ FILE: lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/PaletteStyle.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.dynamic.theme enum class PaletteStyle { TonalSpot, Neutral, Vibrant, Expressive, Rainbow, FruitSalad, Monochrome, Fidelity, Content } ================================================ FILE: lib/dynamic-theme/src/main/java/com/t8rin/dynamic/theme/SystemUiController.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("PrivatePropertyName", "DEPRECATION", "unused") package com.t8rin.dynamic.theme import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.os.Build import android.view.View import android.view.Window import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView import androidx.compose.ui.window.DialogWindowProvider import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat @Stable interface SystemUiController { /** * Control for the behavior of the system bars. This value should be one of the * [WindowInsetsControllerCompat] behavior constants: * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH] (Deprecated), * [WindowInsetsControllerCompat.BEHAVIOR_DEFAULT] and * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE]. */ var systemBarsBehavior: Int /** * Property which holds the status bar visibility. If set to true, show the status bar, * otherwise hide the status bar. */ var isStatusBarVisible: Boolean /** * Property which holds the navigation bar visibility. If set to true, show the navigation bar, * otherwise hide the navigation bar. */ var isNavigationBarVisible: Boolean /** * Property which holds the status & navigation bar visibility. If set to true, show both bars, * otherwise hide both bars. */ var isSystemBarsVisible: Boolean get() = isNavigationBarVisible && isStatusBarVisible set(value) { isStatusBarVisible = value isNavigationBarVisible = value } /** * Set the status bar color. * * @param color The **desired** [Color] to set. This may require modification if running on an * API level that only supports white status bar icons. * @param darkIcons Whether dark status bar icons would be preferable. * @param transformColorForLightContent A lambda which will be invoked to transform [color] if * dark icons were requested but are not available. Defaults to applying a black scrim. * * @see statusBarDarkContentEnabled */ fun setStatusBarColor( color: Color, darkIcons: Boolean = color.luminance() > 0.5f, transformColorForLightContent: (Color) -> Color = BlackScrimmed ) /** * Set the navigation bar color. * * @param color The **desired** [Color] to set. This may require modification if running on an * API level that only supports white navigation bar icons. Additionally this will be ignored * and [Color.Transparent] will be used on API 29+ where gesture navigation is preferred or the * system UI automatically applies background protection in other navigation modes. * @param darkIcons Whether dark navigation bar icons would be preferable. * @param navigationBarContrastEnforced Whether the system should ensure that the navigation * bar has enough contrast when a fully transparent background is requested. Only supported on * API 29+. * @param transformColorForLightContent A lambda which will be invoked to transform [color] if * dark icons were requested but are not available. Defaults to applying a black scrim. * * @see navigationBarDarkContentEnabled * @see navigationBarContrastEnforced */ fun setNavigationBarColor( color: Color, darkIcons: Boolean = color.luminance() > 0.5f, navigationBarContrastEnforced: Boolean = true, transformColorForLightContent: (Color) -> Color = BlackScrimmed ) /** * Set the status and navigation bars to [color]. * * @see setStatusBarColor * @see setNavigationBarColor */ fun setSystemBarsColor( color: Color, darkIcons: Boolean = color.luminance() > 0.5f, isNavigationBarContrastEnforced: Boolean = true, transformColorForLightContent: (Color) -> Color = BlackScrimmed ) { setStatusBarColor(color, darkIcons, transformColorForLightContent) setNavigationBarColor( color, darkIcons, isNavigationBarContrastEnforced, transformColorForLightContent ) } /** * Property which holds whether the status bar icons + content are 'dark' or not. */ var statusBarDarkContentEnabled: Boolean /** * Property which holds whether the navigation bar icons + content are 'dark' or not. */ var navigationBarDarkContentEnabled: Boolean /** * Property which holds whether the status & navigation bar icons + content are 'dark' or not. */ var systemBarsDarkContentEnabled: Boolean get() = statusBarDarkContentEnabled && navigationBarDarkContentEnabled set(value) { statusBarDarkContentEnabled = value navigationBarDarkContentEnabled = value } /** * Property which holds whether the system is ensuring that the navigation bar has enough * contrast when a fully transparent background is requested. Only has an affect when running * on Android API 29+ devices. */ var isNavigationBarContrastEnforced: Boolean } /** * Remembers a [SystemUiController] for the given [window]. * * If no [window] is provided, an attempt to find the correct [Window] is made. * * First, if the [LocalView]'s parent is a [DialogWindowProvider], then that dialog's [Window] will * be used. * * Second, we attempt to find [Window] for the [Activity] containing the [LocalView]. * * If none of these are found (such as may happen in a preview), then the functionality of the * returned [SystemUiController] will be degraded, but won't throw an exception. */ @Composable fun rememberSystemUiController( window: Window? = findWindow(), ): SystemUiController { val view = LocalView.current return remember(view, window) { AndroidSystemUiController(view, window) } } @Composable private fun findWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window ?: LocalView.current.context.findWindow() private tailrec fun Context.findWindow(): Window? = when (this) { is Activity -> window is ContextWrapper -> baseContext.findWindow() else -> null } /** * A helper class for setting the navigation and status bar colors for a [View], gracefully * degrading behavior based upon API level. * * Typically you would use [rememberSystemUiController] to remember an instance of this. */ internal class AndroidSystemUiController( private val view: View, private val window: Window? ) : SystemUiController { private val windowInsetsController = window?.let { WindowCompat.getInsetsController(it, view) } override fun setStatusBarColor( color: Color, darkIcons: Boolean, transformColorForLightContent: (Color) -> Color ) { statusBarDarkContentEnabled = darkIcons window?.statusBarColor = when { darkIcons && windowInsetsController?.isAppearanceLightStatusBars != true -> { // If we're set to use dark icons, but our windowInsetsController call didn't // succeed (usually due to API level), we instead transform the color to maintain // contrast transformColorForLightContent(color) } else -> color }.toArgb() } override fun setNavigationBarColor( color: Color, darkIcons: Boolean, navigationBarContrastEnforced: Boolean, transformColorForLightContent: (Color) -> Color ) { navigationBarDarkContentEnabled = darkIcons isNavigationBarContrastEnforced = navigationBarContrastEnforced window?.navigationBarColor = when { darkIcons && windowInsetsController?.isAppearanceLightNavigationBars != true -> { // If we're set to use dark icons, but our windowInsetsController call didn't // succeed (usually due to API level), we instead transform the color to maintain // contrast transformColorForLightContent(color) } else -> color }.toArgb() } override var systemBarsBehavior: Int get() = windowInsetsController?.systemBarsBehavior ?: 0 set(value) { windowInsetsController?.systemBarsBehavior = value } override var isStatusBarVisible: Boolean get() { return ViewCompat.getRootWindowInsets(view) ?.isVisible(WindowInsetsCompat.Type.statusBars()) == true } set(value) { if (value) { windowInsetsController?.show(WindowInsetsCompat.Type.statusBars()) } else { windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars()) } } override var isNavigationBarVisible: Boolean get() { return ViewCompat.getRootWindowInsets(view) ?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true } set(value) { if (value) { windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars()) } else { windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars()) } } override var statusBarDarkContentEnabled: Boolean get() = windowInsetsController?.isAppearanceLightStatusBars == true set(value) { windowInsetsController?.isAppearanceLightStatusBars = value } override var navigationBarDarkContentEnabled: Boolean get() = windowInsetsController?.isAppearanceLightNavigationBars == true set(value) { windowInsetsController?.isAppearanceLightNavigationBars = value } override var isNavigationBarContrastEnforced: Boolean get() = Build.VERSION.SDK_INT >= 29 && window?.isNavigationBarContrastEnforced == true set(value) { if (Build.VERSION.SDK_INT >= 29) { window?.isNavigationBarContrastEnforced = value } } } private val BlackScrim = Color(0f, 0f, 0f, 0.3f) // 30% opaque black private val BlackScrimmed: (Color) -> Color = { original -> BlackScrim.compositeOver(original) } ================================================ FILE: lib/gesture/.gitignore ================================================ /build ================================================ FILE: lib/gesture/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.gesture" ================================================ FILE: lib/gesture/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/gesture/src/main/java/com/t8rin/gesture/AwaitPointerMotionEvent.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.gesture import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerEventPass.Final import androidx.compose.ui.input.pointer.PointerEventPass.Initial import androidx.compose.ui.input.pointer.PointerEventPass.Main import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch /** * Reads [awaitFirstDown], and [AwaitPointerEventScope.awaitPointerEvent] to * get [PointerInputChange] and motion event states * [onDown], [onMove], and [onUp]. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove one or multiple pointers are being moved on screen. * @param onUp last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. */ suspend fun PointerInputScope.detectMotionEvents( onDown: (PointerInputChange) -> Unit = {}, onMove: (PointerInputChange) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main ) { coroutineScope { awaitEachGesture { // Wait for at least one pointer to press down, and set first contact position val down: PointerInputChange = awaitFirstDown(requireUnconsumed) var pointer = down // Main pointer is the one that is down initially var pointerId = down.id // If a move event is followed fast enough down is skipped, especially by Canvas // to prevent it we add delay after first touch var waitedAfterDown = false launch { delay(delayAfterDownInMillis) onDown(down) waitedAfterDown = true } while (true) { val event: PointerEvent = awaitPointerEvent(pass) val anyPressed = event.changes.any { it.pressed } // There are at least one pointer pressed if (anyPressed) { // Get pointer that is down, if first pointer is up // get another and use it if other pointers are also down // event.changes.first() doesn't return same order val pointerInputChange = event.changes.firstOrNull { it.id == pointerId } ?: event.changes.first() // Next time will check same pointer with this id pointerId = pointerInputChange.id pointer = pointerInputChange if (waitedAfterDown) { onMove(pointer) } } else { // All of the pointers are up onUp(pointer) break } } } } } /** * Reads [awaitFirstDown], and [AwaitPointerEventScope.awaitPointerEvent] to * get [PointerInputChange] and motion event states * [onDown], [onMove], and [onUp]. Unlike overload of this function [onMove] returns * list of [PointerInputChange] to get data about all pointers that are on the screen. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove one or multiple pointers are being moved on screen. * @param onUp last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. * */ suspend fun PointerInputScope.detectMotionEventsAsList( onDown: (PointerInputChange) -> Unit = {}, onMove: (List) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main ) { coroutineScope { awaitEachGesture { // Wait for at least one pointer to press down, and set first contact position val down: PointerInputChange = awaitFirstDown(requireUnconsumed) onDown(down) var pointer = down // Main pointer is the one that is down initially var pointerId = down.id // If a move event is followed fast enough down is skipped, especially by Canvas // to prevent it we add delay after first touch var waitedAfterDown = false launch { delay(delayAfterDownInMillis) waitedAfterDown = true } while (true) { val event: PointerEvent = awaitPointerEvent(pass) val anyPressed = event.changes.any { it.pressed } // There are at least one pointer pressed if (anyPressed) { // Get pointer that is down, if first pointer is up // get another and use it if other pointers are also down // event.changes.first() doesn't return same order val pointerInputChange = event.changes.firstOrNull { it.id == pointerId } ?: event.changes.first() // Next time will check same pointer with this id pointerId = pointerInputChange.id pointer = pointerInputChange if (waitedAfterDown) { onMove(event.changes) } } else { // All of the pointers are up onUp(pointer) break } } } } } ================================================ FILE: lib/gesture/src/main/java/com/t8rin/gesture/PointerMotionModifier.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.gesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerEventPass.Final import androidx.compose.ui.input.pointer.PointerEventPass.Initial import androidx.compose.ui.input.pointer.PointerEventPass.Main import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput /** * Create a modifier for processing pointer motion input within the region of the modified element. * * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then * [onDown] is called at first pointer contact. * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. * When last pointer is up [onUp] is called. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove is invoked when one or multiple pointers are being moved on screen. * @param onUp is invoked when last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. * * The pointer input handling block will be cancelled and re-started when pointerInput * is recomposed with a different [key1]. */ fun Modifier.pointerMotionEvents( onDown: (PointerInputChange) -> Unit = {}, onMove: (PointerInputChange) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main, key1: Any? = Unit ) = this.then( Modifier.pointerInput(key1) { detectMotionEvents( onDown, onMove, onUp, delayAfterDownInMillis, requireUnconsumed, pass ) } ) /** * Create a modifier for processing pointer motion input within the region of the modified element. * * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then * [onDown] is called at first pointer contact. * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. * When last pointer is up [onUp] is called. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove is invoked when one or multiple pointers are being moved on screen. * @param onUp is invoked when last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. * * The pointer input handling block will be cancelled and re-started when pointerInput * is recomposed with a different [key1] or [key2]. */ fun Modifier.pointerMotionEvents( onDown: (PointerInputChange) -> Unit = {}, onMove: (PointerInputChange) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main, key1: Any?, key2: Any? ) = this.then( Modifier.pointerInput(key1, key2) { detectMotionEvents( onDown, onMove, onUp, delayAfterDownInMillis, requireUnconsumed, pass ) } ) /** * Create a modifier for processing pointer motion input within the region of the modified element. * * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then * [onDown] is called at first pointer contact. * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. * When last pointer is up [onUp] is called. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove is invoked when one or multiple pointers are being moved on screen. * @param onUp is invoked when last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. * * The pointer input handling block will be cancelled and re-started when pointerInput * is recomposed with any different [keys]. */ fun Modifier.pointerMotionEvents( onDown: (PointerInputChange) -> Unit = {}, onMove: (PointerInputChange) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main, vararg keys: Any?, ) = this.then( Modifier.pointerInput(*keys) { detectMotionEvents( onDown, onMove, onUp, delayAfterDownInMillis, requireUnconsumed, pass ) } ) /** * Create a modifier for processing pointer motion input within the region of the modified element. * * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then * [onDown] is called at first pointer contact. * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. * When last pointer is up [onUp] is called. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove is invoked when one or multiple pointers are being moved on screen. * @param onUp is invoked when last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. * * The pointer input handling block will be cancelled and re-started when pointerInput * is recomposed with a different [key1]. */ fun Modifier.pointerMotionEventList( onDown: (PointerInputChange) -> Unit = {}, onMove: (List) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main, key1: Any? = Unit ) = this.then( Modifier.pointerInput(key1) { detectMotionEventsAsList( onDown, onMove, onUp, delayAfterDownInMillis, requireUnconsumed, pass ) } ) /** * Create a modifier for processing pointer motion input within the region of the modified element. * * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then * [onDown] is called at first pointer contact. * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. * When last pointer is up [onUp] is called. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove is invoked when one or multiple pointers are being moved on screen. * @param onUp is invoked when last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. * * The pointer input handling block will be cancelled and re-started when pointerInput * is recomposed with a different [key1] or [key2]. */ fun Modifier.pointerMotionEventList( onDown: (PointerInputChange) -> Unit = {}, onMove: (List) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main, key1: Any? = Unit, key2: Any? = Unit ) = this.then( Modifier.pointerInput(key1, key2) { detectMotionEventsAsList( onDown, onMove, onUp, delayAfterDownInMillis, requireUnconsumed, pass ) } ) /** * Create a modifier for processing pointer motion input within the region of the modified element. * * After [AwaitPointerEventScope.awaitFirstDown] returned a [PointerInputChange] then * [onDown] is called at first pointer contact. * Moving any pointer causes [AwaitPointerEventScope.awaitPointerEvent] then [onMove] is called. * When last pointer is up [onUp] is called. * * To prevent other pointer functions that call [awaitFirstDown] * or [AwaitPointerEventScope.awaitPointerEvent] * (scroll, swipe, detect functions) * receiving changes call [PointerInputChange.consume] in [onMove] or call * [PointerInputChange.consume] in [onDown] to prevent events * that check first pointer interaction. * * @param onDown is invoked when first pointer is down initially. * @param onMove is invoked when one or multiple pointers are being moved on screen. * @param onUp is invoked when last pointer is up * @param delayAfterDownInMillis is optional delay after [onDown] This delay might be * required Composables like **Canvas** to process [onDown] before [onMove] * @param requireUnconsumed is `true` and the first * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored. * @param pass The enumeration of passes where [PointerInputChange] * traverses up and down the UI tree. * * PointerInputChanges traverse throw the hierarchy in the following passes: * * 1. [Initial]: Down the tree from ancestor to descendant. * 2. [Main]: Up the tree from descendant to ancestor. * 3. [Final]: Down the tree from ancestor to descendant. * * These passes serve the following purposes: * * 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants. * This is where, for example, a scroller may block buttons from getting tapped by other fingers * once scrolling has started. * 2. Main: The primary pass where gesture filters should react to and consume aspects of * [PointerInputChange]s. This is the primary path where descendants will interact with * [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a * container of the bottom to respond to a tap. * 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were * consumed by parents during the [Main] pass. For example, this is how a button determines that * it should no longer respond to fingers lifting off of it because a parent scroller has * consumed movement in a [PointerInputChange]. * * The pointer input handling block will be cancelled and re-started when pointerInput * is recomposed with any different [keys]. */ fun Modifier.pointerMotionEventList( onDown: (PointerInputChange) -> Unit = {}, onMove: (List) -> Unit = {}, onUp: (PointerInputChange) -> Unit = {}, delayAfterDownInMillis: Long = 0L, requireUnconsumed: Boolean = true, pass: PointerEventPass = Main, vararg keys: Any? ) = this.then( Modifier.pointerInput(*keys) { detectMotionEventsAsList( onDown, onMove, onUp, delayAfterDownInMillis, requireUnconsumed, pass ) } ) ================================================ FILE: lib/gesture/src/main/java/com/t8rin/gesture/TransformGesture.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.gesture import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.calculateCentroid import androidx.compose.foundation.gestures.calculateCentroidSize import androidx.compose.foundation.gestures.calculatePan import androidx.compose.foundation.gestures.calculateRotation import androidx.compose.foundation.gestures.calculateZoom import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChanged import androidx.compose.ui.util.fastAny import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.isActive import kotlin.coroutines.cancellation.CancellationException import kotlin.math.PI import kotlin.math.abs enum class PointerRequisite { LessThan, EqualTo, GreaterThan, None } /** * A gesture detector for rotation, panning, and zoom. Once touch slop has been reached, the * user can use rotation, panning and zoom gestures. [onGesture] will be called when any of the * rotation, zoom or pan occurs, passing the rotation angle in degrees, zoom in scale factor and * pan as an offset in pixels. Each of these changes is a difference between the previous call * and the current gesture. This will consume all position changes after touch slop has * been reached. [onGesture] will also provide centroid of all the pointers that are down. * * After gesture started when last pointer is up [onGestureEnd] is triggered. * * @param consume flag consume [PointerInputChange]s this gesture uses. Consuming * returns [PointerInputChange.isConsumed] true which is observed by other gestures such * as drag, scroll and transform. When this flag is true other gesture don't receive events * @param onGestureStart callback for notifying transform gesture has started with initial * pointer * @param onGesture callback for passing centroid, pan, zoom, rotation and main pointer and * pointer size to caller. Main pointer is the one that touches screen first. If it's lifted * next one that is down is the main pointer. * @param onGestureEnd callback that notifies last pointer is up and gesture is ended if it's * started by fulfilling requisite. * */ suspend fun PointerInputScope.detectTransformGestures( panZoomLock: Boolean = false, consume: Boolean = true, onGestureStart: (PointerInputChange) -> Unit = {}, onGesture: ( centroid: Offset, pan: Offset, zoom: Float, rotation: Float, mainPointer: PointerInputChange, changes: List ) -> Unit, onGestureEnd: (PointerInputChange) -> Unit = {} ) { awaitEachGesture { var rotation = 0f var zoom = 1f var pan = Offset.Zero var pastTouchSlop = false val touchSlop = viewConfiguration.touchSlop var lockedToPanZoom = false // Wait for at least one pointer to press down, and set first contact position val down: PointerInputChange = awaitFirstDown(requireUnconsumed = false) onGestureStart(down) var pointer = down // Main pointer is the one that is down initially var pointerId = down.id do { val event = awaitPointerEvent() // If any position change is consumed from another PointerInputChange // or pointer count requirement is not fulfilled val canceled = event.changes.any { it.isConsumed } if (!canceled) { // Get pointer that is down, if first pointer is up // get another and use it if other pointers are also down // event.changes.first() doesn't return same order val pointerInputChange = event.changes.firstOrNull { it.id == pointerId } ?: event.changes.first() // Next time will check same pointer with this id pointerId = pointerInputChange.id pointer = pointerInputChange val zoomChange = event.calculateZoom() val rotationChange = event.calculateRotation() val panChange = event.calculatePan() if (!pastTouchSlop) { zoom *= zoomChange rotation += rotationChange pan += panChange val centroidSize = event.calculateCentroidSize(useCurrent = false) val zoomMotion = abs(1 - zoom) * centroidSize val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f) val panMotion = pan.getDistance() if (zoomMotion > touchSlop || rotationMotion > touchSlop || panMotion > touchSlop ) { pastTouchSlop = true lockedToPanZoom = panZoomLock && rotationMotion < touchSlop } } if (pastTouchSlop) { val centroid = event.calculateCentroid(useCurrent = false) val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange if (effectiveRotation != 0f || zoomChange != 1f || panChange != Offset.Zero ) { onGesture( centroid, panChange, zoomChange, effectiveRotation, pointer, event.changes ) } if (consume) { event.changes.forEach { if (it.positionChanged()) { it.consume() } } } } } } while (!canceled && event.changes.any { it.pressed }) onGestureEnd(pointer) } } /** * A gesture detector for rotation, panning, and zoom. Once touch slop has been reached, the * user can use rotation, panning and zoom gestures. [onGesture] will be called when any of the * rotation, zoom or pan occurs, passing the rotation angle in degrees, zoom in scale factor and * pan as an offset in pixels. Each of these changes is a difference between the previous call * and the current gesture. This will consume all position changes after touch slop has * been reached. [onGesture] will also provide centroid of all the pointers that are down. * * After gesture started when last pointer is up [onGestureEnd] is triggered. * * * @param numberOfPointers number of pointer required to be down for gestures to commence. Value * of this parameter cannot be lower than 1 * @param requisite determines whether number of pointer down should be equal to less than or * greater than [numberOfPointers] for this gesture. * If [PointerRequisite.None] is set [numberOfPointers] is * not taken into consideration * @param consume flag consume [PointerInputChange]s this gesture uses. Consuming * returns [PointerInputChange.isConsumed] true which is observed by other gestures such * as drag, scroll and transform. When this flag is true other gesture don't receive events * @param onGestureStart callback for notifying transform gesture has started with initial * pointer * @param onGesture callback for passing centroid, pan, zoom, rotation and pointer size to * caller * @param onGestureEnd callback that notifies last pointer is up and gesture is ended if it's * started by fulfilling requisite. * */ suspend fun PointerInputScope.detectPointerTransformGestures( panZoomLock: Boolean = false, numberOfPointers: Int = 1, requisite: PointerRequisite = PointerRequisite.None, consume: Boolean = true, onGestureStart: (PointerInputChange) -> Unit = {}, onGesture: ( centroid: Offset, pan: Offset, zoom: Float, rotation: Float, mainPointer: PointerInputChange, changes: List ) -> Unit, onGestureEnd: (PointerInputChange) -> Unit = {}, onGestureCancel: () -> Unit = {}, ) { require(numberOfPointers > 0) awaitEachGesture { var rotation = 0f var zoom = 1f var pan = Offset.Zero var pastTouchSlop = false val touchSlop = viewConfiguration.touchSlop var lockedToPanZoom = false var gestureStarted = false // Wait for at least one pointer to press down, and set first contact position val down: PointerInputChange = awaitFirstDown(requireUnconsumed = false) onGestureStart(down) var pointer = down // Main pointer is the one that is down initially var pointerId = down.id do { val event = awaitPointerEvent() val downPointerCount = event.changes.map { it.pressed }.size val requirementFulfilled = when (requisite) { PointerRequisite.LessThan -> { (downPointerCount < numberOfPointers) } PointerRequisite.EqualTo -> { (downPointerCount == numberOfPointers) } PointerRequisite.GreaterThan -> { (downPointerCount > numberOfPointers) } else -> true } // If any position change is consumed from another PointerInputChange // or pointer count requirement is not fulfilled val canceled = event.changes.any { it.isConsumed } if (!canceled && requirementFulfilled) { gestureStarted = true // Get pointer that is down, if first pointer is up // get another and use it if other pointers are also down // event.changes.first() doesn't return same order val pointerInputChange = event.changes.firstOrNull { it.id == pointerId } ?: event.changes.first() // Next time will check same pointer with this id pointerId = pointerInputChange.id pointer = pointerInputChange val zoomChange = event.calculateZoom() val rotationChange = event.calculateRotation() val panChange = event.calculatePan() if (!pastTouchSlop) { zoom *= zoomChange rotation += rotationChange pan += panChange val centroidSize = event.calculateCentroidSize(useCurrent = false) val zoomMotion = abs(1 - zoom) * centroidSize val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f) val panMotion = pan.getDistance() if (zoomMotion > touchSlop || rotationMotion > touchSlop || panMotion > touchSlop ) { pastTouchSlop = true lockedToPanZoom = panZoomLock && rotationMotion < touchSlop } } if (pastTouchSlop) { val centroid = event.calculateCentroid(useCurrent = false) val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange if (effectiveRotation != 0f || zoomChange != 1f || panChange != Offset.Zero ) { onGesture( centroid, panChange, zoomChange, effectiveRotation, pointer, event.changes ) } if (consume) { event.changes.forEach { if (it.positionChanged()) { it.consume() } } } } } } while (!canceled && event.changes.any { it.pressed }) if (gestureStarted) { onGestureEnd(pointer) } else { onGestureCancel() } } } fun Modifier.observePointersCountWithOffset( enabled: Boolean = true, onChange: (Int, Offset) -> Unit ) = this then if (enabled) Modifier.pointerInput(Unit) { onEachGesture { val context = currentCoroutineContext() awaitPointerEventScope { do { val event = awaitPointerEvent() onChange( event.changes.size, event.changes.firstOrNull()?.position ?: Offset.Unspecified ) } while (event.changes.any { it.pressed } && context.isActive) onChange(0, Offset.Unspecified) } } } else Modifier suspend fun PointerInputScope.onEachGesture(block: suspend PointerInputScope.() -> Unit) { val currentContext = currentCoroutineContext() while (currentContext.isActive) { try { block() // Wait for all pointers to be up. Gestures start when a finger goes down. awaitAllPointersUp() } catch (e: CancellationException) { if (currentContext.isActive) { // The current gesture was canceled. Wait for all fingers to be "up" before looping // again. awaitAllPointersUp() } else { // forEachGesture was cancelled externally. Rethrow the cancellation exception to // propagate it upwards. throw e } } } } private suspend fun PointerInputScope.awaitAllPointersUp() { awaitPointerEventScope { awaitAllPointersUp() } } private suspend fun AwaitPointerEventScope.awaitAllPointersUp() { if (!allPointersUp()) { do { val events = awaitPointerEvent(PointerEventPass.Final) } while (events.changes.fastAny { it.pressed }) } } private fun AwaitPointerEventScope.allPointersUp(): Boolean = !currentEvent.changes.fastAny { it.pressed } ================================================ FILE: lib/image/.gitignore ================================================ /build ================================================ FILE: lib/image/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.image" dependencies { implementation(projects.lib.gesture) implementation(libs.androidx.palette.ktx) } ================================================ FILE: lib/image/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/image/src/main/java/com/t8rin/image/ImageScope.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.image import androidx.compose.foundation.Canvas import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect /** * Receiver scope being used by the children parameter of [ImageWithConstraints] */ @Stable interface ImageScope { /** * The constraints given by the parent layout in pixels. * * Use [minWidth], [maxWidth], [minHeight] or [maxHeight] if you need value in [Dp]. */ val constraints: Constraints /** * The minimum width in [Dp]. * * @see constraints for the values in pixels. */ val minWidth: Dp /** * The maximum width in [Dp]. * * @see constraints for the values in pixels. */ val maxWidth: Dp /** * The minimum height in [Dp]. * * @see constraints for the values in pixels. */ val minHeight: Dp /** * The maximum height in [Dp]. * * @see constraints for the values in pixels. */ val maxHeight: Dp /** * Width of area inside BoxWithConstraints that is scaled based on [ContentScale] * This is width of the [Canvas] draw [ImageBitmap] */ val imageWidth: Dp /** * Height of area inside BoxWithConstraints that is scaled based on [ContentScale] * This is height of the [Canvas] draw [ImageBitmap] */ val imageHeight: Dp /** * [IntRect] that covers boundaries of [ImageBitmap] */ val rect: IntRect } internal data class ImageScopeImpl( private val density: Density, override val constraints: Constraints, override val imageWidth: Dp, override val imageHeight: Dp, override val rect: IntRect, ) : ImageScope { override val minWidth: Dp get() = with(density) { constraints.minWidth.toDp() } override val maxWidth: Dp get() = with(density) { if (constraints.hasBoundedWidth) constraints.maxWidth.toDp() else Dp.Infinity } override val minHeight: Dp get() = with(density) { constraints.minHeight.toDp() } override val maxHeight: Dp get() = with(density) { if (constraints.hasBoundedHeight) constraints.maxHeight.toDp() else Dp.Infinity } } ================================================ FILE: lib/image/src/main/java/com/t8rin/image/ImageWithConstraints.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.image import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import com.t8rin.image.util.getParentSize import com.t8rin.image.util.getScaledBitmapRect /** * A composable that lays out and draws a given [ImageBitmap]. This will attempt to * size the composable according to the [ImageBitmap]'s given width and height. However, an * optional [Modifier] parameter can be provided to adjust sizing or draw additional content (ex. * background). Any unspecified dimension will leverage the [ImageBitmap]'s size as a minimum * constraint. * * [ImageScope] returns constraints, width and height of the drawing area based on [contentScale] * and rectangle of [imageBitmap] drawn. When a bitmap is displayed scaled to fit area of Composable * space used for drawing image is represented with [ImageScope.imageWidth] and * [ImageScope.imageHeight]. * * When we display a bitmap 1000x1000px with [ContentScale.Crop] if it's cropped to 500x500px * [ImageScope.rect] returns `IntRect(250,250,750,750)`. * * @param alignment determines where image will be aligned inside [BoxWithConstraints] * This is observable when bitmap image/width ratio differs from [Canvas] that draws [ImageBitmap] * @param contentDescription text used by accessibility services to describe what this image * represents. This should always be provided unless this image is used for decorative purposes, * and does not represent a meaningful action that a user can take. This text should be * localized, such as by using [androidx.compose.ui.res.stringResource] or similar * @param contentScale how image should be scaled inside Canvas to match parent dimensions. * [ContentScale.Fit] for instance maintains src ratio and scales image to fit inside the parent. * @param alpha Opacity to be applied to [imageBitmap] from 0.0f to 1.0f representing * fully transparent to fully opaque respectively * @param colorFilter ColorFilter to apply to the [imageBitmap] when drawn into the destination * @param filterQuality Sampling algorithm applied to the [imageBitmap] when it is scaled and drawn * into the destination. The default is [FilterQuality.Low] which scales using a bilinear * sampling algorithm * @param content is a Composable that can be matched at exact position where [imageBitmap] is drawn. * This is useful for drawing thumbs, cropping or another layout that should match position * with the image that is scaled is drawn * @param drawImage flag to draw image on canvas. Some Composables might only require * the calculation and rectangle bounds of image after scaling but not drawing. * Composables like image cropper that scales or * rotates image. Drawing here again have 2 drawings overlap each other. */ @Composable fun ImageWithConstraints( modifier: Modifier = Modifier, imageBitmap: ImageBitmap, alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit, contentDescription: String? = null, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, drawImage: Boolean = true, content: @Composable ImageScope.() -> Unit = {} ) { val semantics = if (contentDescription != null) { Modifier.semantics { this.contentDescription = contentDescription this.role = Role.Image } } else { Modifier } BoxWithConstraints( modifier = modifier .then(semantics), contentAlignment = alignment, ) { val bitmapWidth = imageBitmap.width val bitmapHeight = imageBitmap.height val (boxWidth: Int, boxHeight: Int) = getParentSize(bitmapWidth, bitmapHeight) // Src is Bitmap, Dst is the container(Image) that Bitmap will be displayed val srcSize = Size(bitmapWidth.toFloat(), bitmapHeight.toFloat()) val dstSize = Size(boxWidth.toFloat(), boxHeight.toFloat()) val scaleFactor = contentScale.computeScaleFactor(srcSize, dstSize) // Image is the container for bitmap that is located inside Box // image bounds can be smaller or bigger than its parent based on how it's scaled val imageWidth = bitmapWidth * scaleFactor.scaleX val imageHeight = bitmapHeight * scaleFactor.scaleY val bitmapRect = getScaledBitmapRect( boxWidth = boxWidth, boxHeight = boxHeight, imageWidth = imageWidth, imageHeight = imageHeight, bitmapWidth = bitmapWidth, bitmapHeight = bitmapHeight ) ImageLayout( constraints = constraints, imageBitmap = imageBitmap, bitmapRect = bitmapRect, imageWidth = imageWidth, imageHeight = imageHeight, boxWidth = boxWidth, boxHeight = boxHeight, alpha = alpha, colorFilter = colorFilter, filterQuality = filterQuality, drawImage = drawImage, content = content ) } } @Composable private fun ImageLayout( constraints: Constraints, imageBitmap: ImageBitmap, bitmapRect: IntRect, imageWidth: Float, imageHeight: Float, boxWidth: Int, boxHeight: Int, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, drawImage: Boolean = true, content: @Composable ImageScope.() -> Unit ) { val density = LocalDensity.current // Dimensions of canvas that will draw this Bitmap val canvasWidthInDp: Dp val canvasHeightInDp: Dp with(density) { canvasWidthInDp = imageWidth.coerceAtMost(boxWidth.toFloat()).toDp() canvasHeightInDp = imageHeight.coerceAtMost(boxHeight.toFloat()).toDp() } // Send rectangle of Bitmap drawn to Canvas as bitmapRect, content scale modes like // crop might crop image from center so Rect can be such as IntRect(250,250,500,500) // canvasWidthInDp, and canvasHeightInDp are Canvas dimensions coerced to Box size // that covers Canvas val imageScopeImpl = ImageScopeImpl( density = density, constraints = constraints, imageWidth = canvasWidthInDp, imageHeight = canvasHeightInDp, rect = bitmapRect ) // width and height params for translating draw position if scaled Image dimensions are // bigger than Canvas dimensions if (drawImage) { ImageImpl( modifier = Modifier.size(canvasWidthInDp, canvasHeightInDp), imageBitmap = imageBitmap, alpha = alpha, width = imageWidth.toInt(), height = imageHeight.toInt(), colorFilter = colorFilter, filterQuality = filterQuality ) } imageScopeImpl.content() } @Composable private fun ImageImpl( modifier: Modifier, imageBitmap: ImageBitmap, width: Int, height: Int, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality, ) { val bitmapWidth = imageBitmap.width val bitmapHeight = imageBitmap.height Canvas(modifier = modifier.clipToBounds()) { val canvasWidth = size.width.toInt() val canvasHeight = size.height.toInt() // Translate to left or down when Image size is bigger than this canvas. // ImageSize is bigger when scale modes like Crop is used which enlarges image // For instance 1000x1000 image can be 1000x2000 for a Canvas with 1000x1000 // so top is translated -500 to draw center of ImageBitmap translate( top = (-height + canvasHeight) / 2f, left = (-width + canvasWidth) / 2f, ) { drawImage( imageBitmap, srcSize = IntSize(bitmapWidth, bitmapHeight), dstSize = IntSize(width, height), alpha = alpha, colorFilter = colorFilter, filterQuality = filterQuality ) } } } ================================================ FILE: lib/image/src/main/java/com/t8rin/image/util/ImageContentScaleUtil.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.image.util import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize /** * Get Rectangle of [ImageBitmap] with [bitmapWidth] and [bitmapHeight] that is drawn inside * Canvas with [imageWidth] and [imageHeight]. [boxWidth] and [boxHeight] belong * to [BoxWithConstraints] that contains Canvas. * @param boxWidth width of the parent container * @param boxHeight height of the parent container * @param imageWidth width of the [Canvas] that draws [ImageBitmap] * @param imageHeight height of the [Canvas] that draws [ImageBitmap] * @param bitmapWidth intrinsic width of the [ImageBitmap] * @param bitmapHeight intrinsic height of the [ImageBitmap] * @return [IntRect] that covers [ImageBitmap] bounds. When image [ContentScale] is crop * this rectangle might return smaller rectangle than actual [ImageBitmap] and left or top * of the rectangle might be bigger than zero. */ internal fun getScaledBitmapRect( boxWidth: Int, boxHeight: Int, imageWidth: Float, imageHeight: Float, bitmapWidth: Int, bitmapHeight: Int ): IntRect { // Get scale of box to width of the image // We need a rect that contains Bitmap bounds to pass if any child requires it // For a image with 100x100 px with 300x400 px container and image with crop 400x400px // So we need to pass top left as 0,50 and size val scaledBitmapX = boxWidth / imageWidth val scaledBitmapY = boxHeight / imageHeight val topLeft = IntOffset( x = (bitmapWidth * (imageWidth - boxWidth) / imageWidth / 2) .coerceAtLeast(0f).toInt(), y = (bitmapHeight * (imageHeight - boxHeight) / imageHeight / 2) .coerceAtLeast(0f).toInt() ) val size = IntSize( width = (bitmapWidth * scaledBitmapX).toInt().coerceAtMost(bitmapWidth), height = (bitmapHeight * scaledBitmapY).toInt().coerceAtMost(bitmapHeight) ) return IntRect(offset = topLeft, size = size) } /** * Get [IntSize] of the parent or container that contains [Canvas] that draws [ImageBitmap] * @param bitmapWidth intrinsic width of the [ImageBitmap] * @param bitmapHeight intrinsic height of the [ImageBitmap] * @return size of parent Composable. When Modifier is assigned with fixed or finite size * they are used, but when any dimension is set to infinity intrinsic dimensions of * [ImageBitmap] are returned */ internal fun BoxWithConstraintsScope.getParentSize( bitmapWidth: Int, bitmapHeight: Int ): IntSize { // Check if Composable has fixed size dimensions val hasBoundedDimens = constraints.hasBoundedWidth && constraints.hasBoundedHeight // Check if Composable has infinite dimensions val hasFixedDimens = constraints.hasFixedWidth && constraints.hasFixedHeight // Box is the parent(BoxWithConstraints) that contains Canvas under the hood // Canvas aspect ratio or size might not match parent but it's upper bounds are // what are passed from parent. Canvas cannot be bigger or taller than BoxWithConstraints val boxWidth: Int = if (hasBoundedDimens || hasFixedDimens) { constraints.maxWidth } else { constraints.minWidth.coerceAtLeast(bitmapWidth) } val boxHeight: Int = if (hasBoundedDimens || hasFixedDimens) { constraints.maxHeight } else { constraints.minHeight.coerceAtLeast(bitmapHeight) } return IntSize(boxWidth, boxHeight) } ================================================ FILE: lib/modalsheet/.gitignore ================================================ /build ================================================ FILE: lib/modalsheet/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.modalsheet" dependencies { implementation(libs.lifecycle.viewmodel) } ================================================ FILE: lib/modalsheet/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/modalsheet/src/main/java/com/t8rin/modalsheet/FullscreenPopup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.modalsheet import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.view.KeyEvent import android.view.View import android.view.ViewGroup import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionContext import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCompositionContext import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.R import androidx.compose.ui.platform.AbstractComposeView import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.ViewRootForInspector import androidx.compose.ui.semantics.popup import androidx.compose.ui.semantics.semantics import androidx.core.view.children import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.lifecycle.setViewTreeViewModelStoreOwner import androidx.savedstate.findViewTreeSavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner import java.util.UUID /** * Opens a popup with the given content. * The popup is visible as long as it is part of the composition hierarchy. * * Note: This is highly reduced version of the official Popup composable with some changes: * * Fixes an issue with action mode (copy-paste) menu, see https://issuetracker.google.com/issues/216662636 * * Adds the view to the decor view of the window, instead of the window itself. * * Do not have properties, as Popup is laid out as fullscreen. * * @param onDismiss Executes when the user clicks outside of the popup. * @param content The content to be displayed inside the popup. */ @Composable fun FullscreenPopup( onDismiss: (() -> Unit)? = null, placeAboveAll: Boolean = false, content: @Composable () -> Unit ) { val view = LocalView.current val parentComposition = rememberCompositionContext() val currentContent by rememberUpdatedState(content) val popupId = rememberSaveable { UUID.randomUUID() } val popupLayout = remember { PopupLayout( onDismiss = onDismiss, composeView = view, popupId = popupId, placeAboveAll = placeAboveAll ).apply { setContent(parentComposition) { Box(Modifier.semantics { this.popup() }) { currentContent() } } } } DisposableEffect(popupLayout) { popupLayout.show() popupLayout.updateParameters( onDismiss = onDismiss, placeAboveAll = placeAboveAll ) onDispose { popupLayout.disposeComposition() // Remove the window popupLayout.dismiss() } } SideEffect { popupLayout.updateParameters( onDismiss = onDismiss, placeAboveAll = placeAboveAll ) } } /** * The layout the popup uses to display its content. */ @SuppressLint("ViewConstructor") private class PopupLayout( private var onDismiss: (() -> Unit)?, composeView: View, popupId: UUID, private var placeAboveAll: Boolean ) : AbstractComposeView(composeView.context), ViewRootForInspector { private val ABOVE_ALL_Z = Float.MAX_VALUE private val decorView = findOwner(composeView.context)?.window?.decorView as ViewGroup override val subCompositionView: AbstractComposeView get() = this init { id = android.R.id.content setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner()) setViewTreeViewModelStoreOwner(composeView.findViewTreeViewModelStoreOwner()) setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner()) // Set unique id for AbstractComposeView. This allows state restoration for the state // defined inside the Popup via rememberSaveable() setTag(R.id.compose_view_saveable_id_tag, "Popup:$popupId") setTag(R.id.consume_window_insets_tag, false) } private var content: @Composable () -> Unit by mutableStateOf({}) override var shouldCreateCompositionOnAttachedToWindow: Boolean = false private set fun show() { // Place popup above all current views var placeAboveAllView: View? = null val topView = decorView.children.maxBy { if (it.z == ABOVE_ALL_Z) { placeAboveAllView = it -ABOVE_ALL_Z } else it.z } z = if (placeAboveAll) ABOVE_ALL_Z else topView.z + 1 decorView.addView( this, 0, MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) ) placeAboveAllView?.bringToFront() (decorView as View).invalidate() requestFocus() } fun setContent(parent: CompositionContext, content: @Composable () -> Unit) { setParentCompositionContext(parent) this.content = content shouldCreateCompositionOnAttachedToWindow = true } @Composable override fun Content() { content() } @Suppress("ReturnCount") override fun dispatchKeyEvent(event: KeyEvent): Boolean { if (event.keyCode == KeyEvent.KEYCODE_BACK && onDismiss != null) { if (keyDispatcherState == null) { return super.dispatchKeyEvent(event) } if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) { val state = keyDispatcherState state?.startTracking(event, this) return true } else if (event.action == KeyEvent.ACTION_UP) { val state = keyDispatcherState if (state != null && state.isTracking(event) && !event.isCanceled) { onDismiss?.invoke() return true } } } return super.dispatchKeyEvent(event) } fun updateParameters( onDismiss: (() -> Unit)?, placeAboveAll: Boolean ) { this.onDismiss = onDismiss this.placeAboveAll = placeAboveAll } fun dismiss() { setViewTreeLifecycleOwner(null) decorView.removeView(this) } } private inline fun findOwner(context: Context): T? { var innerContext = context while (innerContext is ContextWrapper) { if (innerContext is T) { return innerContext } innerContext = innerContext.baseContext } return null } ================================================ FILE: lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheet.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.modalsheet import androidx.compose.animation.core.AnimationSpec import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.zIndex /** * Modal sheet that behaves like bottom sheet and draws over system UI. * Should be used on with the content which is not dependent on the outer data. For dynamic content use [ModalSheet] * overload with a 'data' parameter. * * @param visible True if modal should be visible. * @param onVisibleChange Called when visibility changes. * @param cancelable When true, this modal sheet can be closed with swipe gesture, tap on scrim or tap on hardware back * button. Note: passing 'false' does not disable the interaction with the sheet. Only the resulting state after the * sheet settles. * @param shape The shape of the bottom sheet. * @param elevation The elevation of the bottom sheet. * @param containerColor The background color of the bottom sheet. * @param contentColor The preferred content color provided by the bottom sheet to its * children. Defaults to the matching content color for [containerColor], or if that is not * a color from the theme, this will keep the same content color set above the bottom sheet. * @param scrimColor The color of the scrim that is applied to the rest of the screen when the * bottom sheet is visible. If the color passed is [Color.Unspecified], then a scrim will no * longer be applied and the bottom sheet will not block interaction with the rest of the screen * when visible. * @param content The content of the bottom sheet. */ @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun ModalSheet( visible: Boolean, onVisibleChange: (Boolean) -> Unit, modifier: Modifier = Modifier, dragHandle: @Composable ColumnScope.() -> Unit = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { BottomSheetDefaults.DragHandle() } }, nestedScrollEnabled: Boolean = false, animationSpec: AnimationSpec = SwipeableV2Defaults.AnimationSpec, sheetModifier: Modifier = Modifier, cancelable: Boolean = true, skipHalfExpanded: Boolean = true, shape: Shape = BottomSheetDefaults.ExpandedShape, elevation: Dp = BottomSheetDefaults.Elevation, containerColor: Color = BottomSheetDefaults.ContainerColor, contentColor: Color = contentColorFor(containerColor), scrimColor: Color = BottomSheetDefaults.ScrimColor, content: @Composable ColumnScope.() -> Unit, ) { // Hold cancelable flag internally and set to true when modal sheet is dismissed via "visible" property in // non-cancellable modal sheet. This ensures that "confirmValueChange" will return true when sheet is set to hidden // state. val internalCancelable = remember { mutableStateOf(cancelable) } val sheetState = rememberModalBottomSheetState( skipHalfExpanded = skipHalfExpanded, initialValue = ModalBottomSheetValue.Hidden, animationSpec = animationSpec, confirmValueChange = { // Intercept and disallow hide gesture / action if (it == ModalBottomSheetValue.Hidden && !internalCancelable.value) { return@rememberModalBottomSheetState false } true }, ) LaunchedEffect(visible, cancelable) { if (visible) { internalCancelable.value = cancelable sheetState.show() } else { internalCancelable.value = true sheetState.hide() } } LaunchedEffect(sheetState.currentValue, sheetState.targetValue, sheetState.progress) { if (sheetState.progress == 1f && sheetState.currentValue == sheetState.targetValue) { val newVisible = sheetState.isVisible if (newVisible != visible) { onVisibleChange(newVisible) } } } if (!visible && sheetState.currentValue == sheetState.targetValue && !sheetState.isVisible) { return } ModalSheet( sheetState = sheetState, onDismiss = { if (cancelable) { onVisibleChange(false) } }, dragHandle = dragHandle, nestedScrollEnabled = nestedScrollEnabled, sheetModifier = sheetModifier, modifier = modifier, shape = shape, elevation = elevation, containerColor = containerColor, contentColor = contentColor, scrimColor = scrimColor, content = content, ) } /** * Modal sheet that behaves like bottom sheet and draws over system UI. * Takes [ModalSheetState] as parameter to fine-tune sheet behavior. * * Note: In this case [ModalSheet] is always added to the composition. See [ModalSheet] overload with visible parameter, * or data object to conditionally add / remove modal sheet to / from the composition. * * @param sheetState The state of the underlying Material bottom sheet. * @param onDismiss Called when user taps on the hardware back button. * @param shape The shape of the bottom sheet. * @param elevation The elevation of the bottom sheet. * @param containerColor The background color of the bottom sheet. * @param contentColor The preferred content color provided by the bottom sheet to its * children. Defaults to the matching content color for [containerColor], or if that is not * a color from the theme, this will keep the same content color set above the bottom sheet. * @param scrimColor The color of the scrim that is applied to the rest of the screen when the * bottom sheet is visible. If the color passed is [Color.Unspecified], then a scrim will no * longer be applied and the bottom sheet will not block interaction with the rest of the screen * when visible. * @param content The content of the bottom sheet. */ @ExperimentalMaterial3Api @Composable fun ModalSheet( modifier: Modifier = Modifier, sheetModifier: Modifier = Modifier, dragHandle: @Composable ColumnScope.() -> Unit = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { BottomSheetDefaults.DragHandle() } }, sheetState: ModalSheetState, onDismiss: (() -> Unit)?, nestedScrollEnabled: Boolean = false, shape: Shape = BottomSheetDefaults.ExpandedShape, elevation: Dp = BottomSheetDefaults.Elevation, containerColor: Color = BottomSheetDefaults.ContainerColor, contentColor: Color = contentColorFor(containerColor), scrimColor: Color = BottomSheetDefaults.ScrimColor, content: @Composable ColumnScope.() -> Unit, ) { FullscreenPopup( onDismiss = onDismiss, ) { Box(Modifier.fillMaxSize()) { ModalSheetLayout( nestedScrollEnabled = nestedScrollEnabled, sheetModifier = sheetModifier, dragHandle = { Column(Modifier.zIndex(100f)) { dragHandle() } }, modifier = modifier.align(Alignment.BottomCenter), sheetState = sheetState, sheetShape = shape, sheetElevation = elevation, sheetContainerColor = containerColor, sheetContentColor = contentColor, scrimColor = scrimColor, sheetContent = { Column(Modifier.zIndex(-100f)) { content() } }, content = {} ) } } } ================================================ FILE: lib/modalsheet/src/main/java/com/t8rin/modalsheet/ModalSheetLayout.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.modalsheet import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.TweenSpec import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.widthIn import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Surface import androidx.compose.material.contentColorFor import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.semantics.collapse import androidx.compose.ui.semantics.dismiss import androidx.compose.ui.semantics.expand import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import com.t8rin.modalsheet.ModalBottomSheetValue.Expanded import com.t8rin.modalsheet.ModalBottomSheetValue.HalfExpanded import com.t8rin.modalsheet.ModalBottomSheetValue.Hidden import com.t8rin.modalsheet.ModalSheetState.Companion.Saver import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch import kotlin.math.max import kotlin.math.roundToInt enum class ModalBottomSheetValue { /** * The bottom sheet is not visible. */ Hidden, /** * The bottom sheet is visible at full height. */ Expanded, /** * The bottom sheet is partially visible at 50% of the screen height. This state is only * enabled if the height of the bottom sheet is more than 50% of the screen height. */ HalfExpanded } /** * State of the [ModalSheetLayout] composable. * * @param initialValue The initial value of the state. Must not be set to * [ModalBottomSheetValue.HalfExpanded] if [isSkipHalfExpanded] is set to true. * @param animationSpec The default animation that will be used to animate to a new state. * @param isSkipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the * [Hidden] state when hiding the sheet, either programmatically or by user interaction. * Must not be set to true if the initialValue is [ModalBottomSheetValue.HalfExpanded]. * If supplied with [ModalBottomSheetValue.HalfExpanded] for the initialValue, an * [IllegalArgumentException] will be thrown. * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change. */ @ExperimentalMaterial3Api @OptIn(ExperimentalMaterialApi::class) class ModalSheetState( initialValue: ModalBottomSheetValue, animationSpec: AnimationSpec = SwipeableV2Defaults.AnimationSpec, confirmValueChange: (ModalBottomSheetValue) -> Boolean = { true }, val isSkipHalfExpanded: Boolean = false ) { val swipeableState = SwipeableV2State( initialValue = initialValue, animationSpec = animationSpec, confirmValueChange = confirmValueChange, positionalThreshold = PositionalThreshold, velocityThreshold = VelocityThreshold ) val currentValue: ModalBottomSheetValue get() = swipeableState.currentValue val targetValue: ModalBottomSheetValue get() = swipeableState.targetValue val progress: Float get() = swipeableState.progress /** * Whether the bottom sheet is visible. */ val isVisible: Boolean get() = swipeableState.currentValue != Hidden val hasHalfExpandedState: Boolean get() = swipeableState.hasAnchorForValue(HalfExpanded) init { if (isSkipHalfExpanded) { require(initialValue != HalfExpanded) { "The initial value must not be set to HalfExpanded if skipHalfExpanded is set to" + " true." } } } /** * Show the bottom sheet with animation and suspend until it's shown. If the sheet is taller * than 50% of the parent's height, the bottom sheet will be half expanded. Otherwise it will be * fully expanded. * * @throws [CancellationException] if the animation is interrupted */ suspend fun show() { val targetValue = when { hasHalfExpandedState -> HalfExpanded else -> Expanded } animateTo(targetValue) } /** * Half expand the bottom sheet if half expand is enabled with animation and suspend until it * animation is complete or cancelled * * @throws [CancellationException] if the animation is interrupted */ suspend fun halfExpand() { if (!hasHalfExpandedState) { return } animateTo(HalfExpanded) } /** * Fully expand the bottom sheet with animation and suspend until it if fully expanded or * animation has been cancelled. * * * @throws [CancellationException] if the animation is interrupted */ suspend fun expand() { if (!swipeableState.hasAnchorForValue(Expanded)) { return } animateTo(Expanded) } /** * Hide the bottom sheet with animation and suspend until it if fully hidden or animation has * been canceled. * * @throws [CancellationException] if the animation is interrupted */ suspend fun hide() = animateTo(Hidden) suspend fun animateTo( target: ModalBottomSheetValue, velocity: Float = swipeableState.lastVelocity ) = swipeableState.animateTo(target, velocity) suspend fun snapTo(target: ModalBottomSheetValue) = swipeableState.snapTo(target) fun requireOffset() = swipeableState.requireOffset() val lastVelocity: Float get() = swipeableState.lastVelocity val isAnimationRunning: Boolean get() = swipeableState.isAnimationRunning companion object { /** * The default [Saver] implementation for [ModalSheetState]. * Saves the [currentValue] and recreates a [ModalSheetState] with the saved value as * initial value. */ fun Saver( animationSpec: AnimationSpec, confirmValueChange: (ModalBottomSheetValue) -> Boolean, skipHalfExpanded: Boolean, ): Saver = Saver( save = { it.currentValue }, restore = { ModalSheetState( initialValue = it, animationSpec = animationSpec, isSkipHalfExpanded = skipHalfExpanded, confirmValueChange = confirmValueChange ) } ) } } /** * Create a [ModalSheetState] and [remember] it. * * @param initialValue The initial value of the state. * @param animationSpec The default animation that will be used to animate to a new state. * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change. * @param skipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the * [Hidden] state when hiding the sheet, either programmatically or by user interaction. * Must not be set to true if the [initialValue] is [ModalBottomSheetValue.HalfExpanded]. * If supplied with [ModalBottomSheetValue.HalfExpanded] for the [initialValue], an * [IllegalArgumentException] will be thrown. */ @OptIn(ExperimentalMaterialApi::class) @ExperimentalMaterial3Api @Composable fun rememberModalBottomSheetState( initialValue: ModalBottomSheetValue, animationSpec: AnimationSpec = SpringSpec(), confirmValueChange: (ModalBottomSheetValue) -> Boolean = { true }, skipHalfExpanded: Boolean = false, ): ModalSheetState { // Key the rememberSaveable against the initial value. If it changed we don't want to attempt // to restore as the restored value could have been saved with a now invalid set of anchors. // b/152014032 return key(initialValue) { rememberSaveable( initialValue, animationSpec, skipHalfExpanded, confirmValueChange, saver = Saver( animationSpec = animationSpec, skipHalfExpanded = skipHalfExpanded, confirmValueChange = confirmValueChange ) ) { ModalSheetState( initialValue = initialValue, animationSpec = animationSpec, isSkipHalfExpanded = skipHalfExpanded, confirmValueChange = confirmValueChange ) } } } /** * Material Design modal bottom sheet. * * Modal bottom sheets present a set of choices while blocking interaction with the rest of the * screen. They are an alternative to inline menus and simple dialogs, providing * additional room for content, iconography, and actions. * * ![Modal bottom sheet image](https://developer.android.com/images/reference/androidx/compose/material/modal-bottom-sheet.png) * * A simple example of a modal bottom sheet looks like this: * * * @param sheetContent The content of the bottom sheet. * @param modifier Optional [Modifier] for the entire component. * @param sheetState The state of the bottom sheet. * @param sheetShape The shape of the bottom sheet. * @param sheetElevation The elevation of the bottom sheet. * @param sheetContainerColor The background color of the bottom sheet. * @param sheetContentColor The preferred content color provided by the bottom sheet to its * children. Defaults to the matching content color for [sheetContainerColor], or if that is not * a color from the theme, this will keep the same content color set above the bottom sheet. * @param scrimColor The color of the scrim that is applied to the rest of the screen when the * bottom sheet is visible. If the color passed is [Color.Unspecified], then a scrim will no * longer be applied and the bottom sheet will not block interaction with the rest of the screen * when visible. * @param content The content of rest of the screen. */ @OptIn(ExperimentalMaterialApi::class) @ExperimentalMaterial3Api @Composable fun ModalSheetLayout( sheetContent: @Composable ColumnScope.() -> Unit, modifier: Modifier = Modifier, dragHandle: @Composable ColumnScope.() -> Unit = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { BottomSheetDefaults.DragHandle() } }, nestedScrollEnabled: Boolean = true, sheetModifier: Modifier = Modifier, sheetState: ModalSheetState = rememberModalBottomSheetState(Hidden), sheetShape: Shape = BottomSheetDefaults.ExpandedShape, sheetElevation: Dp = BottomSheetDefaults.Elevation, sheetContainerColor: Color = BottomSheetDefaults.ContainerColor, sheetContentColor: Color = contentColorFor(sheetContainerColor), scrimColor: Color = BottomSheetDefaults.ScrimColor, content: @Composable () -> Unit ) { val scope = rememberCoroutineScope() val orientation = Orientation.Vertical val anchorChangeHandler = remember(sheetState, scope) { ModalBottomSheetAnchorChangeHandler( state = sheetState, animateTo = { target, velocity -> scope.launch { sheetState.animateTo(target, velocity = velocity) } }, snapTo = { target -> scope.launch { sheetState.snapTo(target) } } ) } BoxWithConstraints(modifier) { val fullHeight = constraints.maxHeight.toFloat() Box(Modifier.fillMaxSize()) { content() Scrim( color = scrimColor, onDismiss = { if (sheetState.swipeableState.confirmValueChange(Hidden)) { scope.launch { sheetState.hide() } } }, visible = sheetState.swipeableState.targetValue != Hidden ) } Surface( Modifier .align(Alignment.TopCenter) // We offset from the top so we'll center from there .widthIn(max = MaxModalBottomSheetWidth) .fillMaxWidth() .then( if (nestedScrollEnabled) { Modifier.nestedScroll( remember(sheetState.swipeableState, orientation) { ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection( state = sheetState.swipeableState, orientation = orientation ) } ) } else Modifier) .offset { IntOffset( 0, sheetState.swipeableState .requireOffset() .roundToInt() ) } .swipeableV2( state = sheetState.swipeableState, orientation = orientation, enabled = sheetState.swipeableState.currentValue != Hidden, ) .swipeAnchors( state = sheetState.swipeableState, possibleValues = setOf(Hidden, HalfExpanded, Expanded), anchorChangeHandler = anchorChangeHandler ) { state, sheetSize -> when (state) { Hidden -> fullHeight HalfExpanded -> when { sheetSize.height < fullHeight / 2f -> null sheetState.isSkipHalfExpanded -> null else -> fullHeight / 2f } Expanded -> if (sheetSize.height != 0) { max(0f, fullHeight - sheetSize.height) } else null } } .semantics { if (sheetState.isVisible) { dismiss { if (sheetState.swipeableState.confirmValueChange(Hidden)) { scope.launch { sheetState.hide() } } true } if (sheetState.swipeableState.currentValue == HalfExpanded) { expand { if (sheetState.swipeableState.confirmValueChange(Expanded)) { scope.launch { sheetState.expand() } } true } } else if (sheetState.hasHalfExpandedState) { collapse { if (sheetState.swipeableState.confirmValueChange(HalfExpanded)) { scope.launch { sheetState.halfExpand() } } true } } } } .then(sheetModifier), shape = sheetShape, elevation = sheetElevation, color = sheetContainerColor, contentColor = sheetContentColor ) { Column( content = { dragHandle() sheetContent() } ) } } } @Composable private fun Scrim( color: Color, onDismiss: () -> Unit, visible: Boolean ) { if (color.isSpecified) { val alpha by animateFloatAsState( targetValue = if (visible) 1f else 0f, animationSpec = TweenSpec() ) val dismissModifier = if (visible) { Modifier .pointerInput(onDismiss) { detectTapGestures { onDismiss() } } .semantics(mergeDescendants = true) { onClick { onDismiss(); true } } } else { Modifier } Canvas( Modifier .fillMaxSize() .then(dismissModifier) ) { drawRect(color = color, alpha = alpha) } } } @OptIn(ExperimentalMaterialApi::class) private fun ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection( state: SwipeableV2State<*>, orientation: Orientation ): NestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { val delta = available.toFloat() return if (delta < 0 && source == NestedScrollSource.UserInput) { state.dispatchRawDelta(delta).toOffset() } else { Offset.Zero } } override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { return if (source == NestedScrollSource.UserInput) { state.dispatchRawDelta(available.toFloat()).toOffset() } else { Offset.Zero } } override suspend fun onPreFling(available: Velocity): Velocity { val toFling = available.toFloat() val currentOffset = state.requireOffset() return if (toFling < 0 && currentOffset > state.minOffset) { state.settle(velocity = toFling) // since we go to the anchor with tween settling, consume all for the best UX available } else { Velocity.Zero } } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { state.settle(velocity = available.toFloat()) return available } private fun Float.toOffset(): Offset = Offset( x = if (orientation == Orientation.Horizontal) this else 0f, y = if (orientation == Orientation.Vertical) this else 0f ) @JvmName("velocityToFloat") private fun Velocity.toFloat() = if (orientation == Orientation.Horizontal) x else y @JvmName("offsetToFloat") private fun Offset.toFloat(): Float = if (orientation == Orientation.Horizontal) x else y } @ExperimentalMaterial3Api @OptIn(ExperimentalMaterialApi::class) private fun ModalBottomSheetAnchorChangeHandler( state: ModalSheetState, animateTo: (target: ModalBottomSheetValue, velocity: Float) -> Unit, snapTo: (target: ModalBottomSheetValue) -> Unit, ) = AnchorChangeHandler { previousTarget, previousAnchors, newAnchors -> val previousTargetOffset = previousAnchors[previousTarget] val newTarget = when (previousTarget) { Hidden -> Hidden HalfExpanded, Expanded -> { val hasHalfExpandedState = newAnchors.containsKey(HalfExpanded) val newTarget = if (hasHalfExpandedState) HalfExpanded else if (newAnchors.containsKey(Expanded)) Expanded else Hidden newTarget } } val newTargetOffset = newAnchors.getValue(newTarget) if (newTargetOffset != previousTargetOffset) { if (state.isAnimationRunning) { // Re-target the animation to the new offset if it changed animateTo(newTarget, state.lastVelocity) } else { // Snap to the new offset value of the target if no animation was running snapTo(newTarget) } } } private val PositionalThreshold: Density.(Float) -> Float = { 56.dp.toPx() } private val VelocityThreshold = 125.dp private val MaxModalBottomSheetWidth = 640.dp ================================================ FILE: lib/modalsheet/src/main/java/com/t8rin/modalsheet/SwipeableV2.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("FunctionName") package com.t8rin.modalsheet /* * Copyright 2022 The Android Open Source Project * * 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. */ import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.animate import androidx.compose.foundation.gestures.DraggableState import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.offset import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.LayoutModifier import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.OnRemeasuredModifier import androidx.compose.ui.platform.InspectorInfo import androidx.compose.ui.platform.InspectorValueInfo import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.t8rin.modalsheet.SwipeableV2State.Companion.Saver import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch import kotlin.math.abs /** * Enable swipe gestures between a set of predefined values. * * When a swipe is detected, the offset of the [SwipeableV2State] will be updated with the swipe * delta. You should use this offset to move your content accordingly (see [Modifier.offset]). * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is * reached, the value of the [SwipeableV2State] will also be updated to the value corresponding to * the new anchor. * * Swiping is constrained between the minimum and maximum anchors. * * @param state The associated [SwipeableV2State]. * @param orientation The orientation in which the swipeable can be swiped. * @param enabled Whether this [swipeableV2] is enabled and should react to the user's input. * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom * swipe will behave like bottom to top, and a left to right swipe will behave like right to left. * @param interactionSource Optional [MutableInteractionSource] that will passed on to * the internal [Modifier.draggable]. */ @ExperimentalMaterialApi internal fun Modifier.swipeableV2( state: SwipeableV2State, orientation: Orientation, enabled: Boolean = true, reverseDirection: Boolean = false, interactionSource: MutableInteractionSource? = null ) = draggable( state = state.draggableState, orientation = orientation, enabled = enabled, interactionSource = interactionSource, reverseDirection = reverseDirection, startDragImmediately = state.isAnimationRunning, onDragStopped = { velocity -> launch { state.settle(velocity) } } ) /** * Define anchor points for a given [SwipeableV2State] based on this node's layout size and update * the state with them. * * @param state The associated [SwipeableV2State] * @param possibleValues All possible values the [SwipeableV2State] could be in. * @param anchorChangeHandler A callback to be invoked when the anchors have changed, * `null` by default. Components with custom reconciliation logic should implement this callback, * i.e. to re-target an in-progress animation. * @param calculateAnchor This method will be invoked to calculate the position of all * [possibleValues], given this node's layout size. Return the anchor's offset from the initial * anchor, or `null` to indicate that a value does not have an anchor. */ @ExperimentalMaterialApi internal fun Modifier.swipeAnchors( state: SwipeableV2State, possibleValues: Set, anchorChangeHandler: AnchorChangeHandler? = null, calculateAnchor: (value: T, layoutSize: IntSize) -> Float?, ) = this.then( SwipeAnchorsModifier( onDensityChanged = { state.density = it }, onSizeChanged = { layoutSize -> val previousAnchors = state.anchors val newAnchors = mutableMapOf() possibleValues.forEach { val anchorValue = calculateAnchor(it, layoutSize) if (anchorValue != null) { newAnchors[it] = anchorValue } } if (previousAnchors != newAnchors) { val previousTarget = state.targetValue val stateRequiresCleanup = state.updateAnchors(newAnchors) if (stateRequiresCleanup) { anchorChangeHandler?.onAnchorsChanged( previousTarget, previousAnchors, newAnchors ) } } }, inspectorInfo = debugInspectorInfo { name = "swipeAnchors" properties["state"] = state properties["possibleValues"] = possibleValues properties["anchorChangeHandler"] = anchorChangeHandler properties["calculateAnchor"] = calculateAnchor } )) /** * State of the [swipeableV2] modifier. * * This contains necessary information about any ongoing swipe or animation and provides methods * to change the state either immediately or by starting an animation. To create and remember a * [SwipeableV2State] use [rememberSwipeableV2State]. * * @param initialValue The initial value of the state. * @param animationSpec The default animation that will be used to animate to a new state. * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change. * @param positionalThreshold The positional threshold to be used when calculating the target state * while a swipe is in progress and when settling after the swipe ends. This is the distance from * the start of a transition. It will be, depending on the direction of the interaction, added or * subtracted from/to the origin offset. It should always be a positive value. See the * [fractionalPositionalThreshold] and [fixedPositionalThreshold] methods. * @param velocityThreshold The velocity threshold (in dp per second) that the end velocity has to * exceed in order to animate to the next state, even if the [positionalThreshold] has not been * reached. */ @Stable @ExperimentalMaterialApi class SwipeableV2State( initialValue: T, val animationSpec: AnimationSpec = SwipeableV2Defaults.AnimationSpec, val confirmValueChange: (newValue: T) -> Boolean = { true }, val positionalThreshold: Density.(totalDistance: Float) -> Float = SwipeableV2Defaults.PositionalThreshold, val velocityThreshold: Dp = SwipeableV2Defaults.VelocityThreshold, ) { /** * The current value of the [SwipeableV2State]. */ var currentValue: T by mutableStateOf(initialValue) private set /** * The target value. This is the closest value to the current offset (taking into account * positional thresholds). If no interactions like animations or drags are in progress, this * will be the current value. */ val targetValue: T by derivedStateOf { animationTarget ?: run { val currentOffset = offset if (currentOffset != null) { computeTarget(currentOffset, currentValue, velocity = 0f) } else currentValue } } /** * The current offset, or null if it has not been initialized yet. * * The offset will be initialized during the first measurement phase of the node that the * [swipeableV2] modifier is attached to. These are the phases: * Composition { -> Effects } -> Layout { Measurement -> Placement } -> Drawing * During the first composition, the offset will be null. In subsequent compositions, the offset * will be derived from the anchors of the previous pass. * Always prefer accessing the offset from a LaunchedEffect as it will be scheduled to be * executed the next frame, after layout. * * To guarantee stricter semantics, consider using [requireOffset]. */ @get:Suppress("AutoBoxing") var offset: Float? by mutableStateOf(null) private set /** * Require the current offset. * * @throws IllegalStateException If the offset has not been initialized yet */ fun requireOffset(): Float = checkNotNull(offset) { "The offset was read before being initialized. Did you access the offset in a phase " + "before layout, like effects or composition?" } /** * Whether an animation is currently in progress. */ val isAnimationRunning: Boolean get() = animationTarget != null /** * The fraction of the progress going from [currentValue] to [targetValue], within [0f..1f] * bounds. */ /*@FloatRange(from = 0f, to = 1f)*/ val progress: Float by derivedStateOf { val a = anchors[currentValue] ?: 0f val b = anchors[targetValue] ?: 0f val distance = abs(b - a) if (distance > 1e-6f) { val progress = (this.requireOffset() - a) / (b - a) // If we are very close to 0f or 1f, we round to the closest if (progress < 1e-6f) 0f else if (progress > 1 - 1e-6f) 1f else progress } else 1f } /** * The velocity of the last known animation. Gets reset to 0f when an animation completes * successfully, but does not get reset when an animation gets interrupted. * You can use this value to provide smooth reconciliation behavior when re-targeting an * animation. */ var lastVelocity: Float by mutableStateOf(0f) private set /** * The minimum offset this state can reach. This will be the smallest anchor, or * [Float.NEGATIVE_INFINITY] if the anchors are not initialized yet. */ val minOffset by derivedStateOf { anchors.minOrNull() ?: Float.NEGATIVE_INFINITY } /** * The maximum offset this state can reach. This will be the biggest anchor, or * [Float.POSITIVE_INFINITY] if the anchors are not initialized yet. */ val maxOffset by derivedStateOf { anchors.maxOrNull() ?: Float.POSITIVE_INFINITY } private var animationTarget: T? by mutableStateOf(null) val draggableState = DraggableState { offset = ((offset ?: 0f) + it).coerceIn(minOffset, maxOffset) } internal var anchors by mutableStateOf(emptyMap()) internal var density: Density? = null /** * Update the anchors. * If the previous set of anchors was empty, attempt to update the offset to match the initial * value's anchor. * * @return true if the state needs to be adjusted after updating the anchors, e.g. if the * initial value is not found in the initial set of anchors. false if no further updates are * needed. */ internal fun updateAnchors(newAnchors: Map): Boolean { val previousAnchorsEmpty = anchors.isEmpty() anchors = newAnchors val initialValueHasAnchor = if (previousAnchorsEmpty) { val initialValueAnchor = anchors[currentValue] val initialValueHasAnchor = initialValueAnchor != null if (initialValueHasAnchor) offset = initialValueAnchor initialValueHasAnchor } else true return !initialValueHasAnchor || !previousAnchorsEmpty } /** * Whether the [value] has an anchor associated with it. */ fun hasAnchorForValue(value: T): Boolean = anchors.containsKey(value) /** * Snap to a [targetValue] without any animation. * If the [targetValue] is not in the set of anchors, the [currentValue] will be updated to the * [targetValue] without updating the offset. * * @throws CancellationException if the interaction interrupted by another interaction like a * gesture interaction or another programmatic interaction like a [animateTo] or [snapTo] call. * * @param targetValue The target value of the animation */ suspend fun snapTo(targetValue: T) { val targetOffset = anchors[targetValue] if (targetOffset != null) { try { draggableState.drag { animationTarget = targetValue dragBy(targetOffset - requireOffset()) } this.currentValue = targetValue } finally { animationTarget = null } } else { currentValue = targetValue } } /** * Animate to a [targetValue]. * If the [targetValue] is not in the set of anchors, the [currentValue] will be updated to the * [targetValue] without updating the offset. * * @throws CancellationException if the interaction interrupted by another interaction like a * gesture interaction or another programmatic interaction like a [animateTo] or [snapTo] call. * * @param targetValue The target value of the animation * @param velocity The velocity the animation should start with, [lastVelocity] by default */ suspend fun animateTo( targetValue: T, velocity: Float = lastVelocity, ) { val targetOffset = anchors[targetValue] if (targetOffset != null) { try { draggableState.drag { animationTarget = targetValue var prev = offset ?: 0f animate(prev, targetOffset, velocity, animationSpec) { value, velocity -> // Our onDrag coerces the value within the bounds, but an animation may // overshoot, for example a spring animation or an overshooting interpolator // We respect the user's intention and allow the overshoot, but still use // DraggableState's drag for its mutex. offset = value prev = value lastVelocity = velocity } lastVelocity = 0f } } finally { animationTarget = null val endOffset = requireOffset() val endState = anchors .entries .firstOrNull { (_, anchorOffset) -> abs(anchorOffset - endOffset) < 0.5f } ?.key this.currentValue = endState ?: currentValue } } else { currentValue = targetValue } } /** * Find the closest anchor taking into account the velocity and settle at it with an animation. */ suspend fun settle(velocity: Float) { val previousValue = this.currentValue val targetValue = computeTarget( offset = requireOffset(), currentValue = previousValue, velocity = velocity ) if (confirmValueChange(targetValue)) { animateTo(targetValue, velocity) } else { // If the user vetoed the state change, rollback to the previous state. animateTo(previousValue, velocity) } } /** * Swipe by the [delta], coerce it in the bounds and dispatch it to the [draggableState]. * * @return The delta the [draggableState] will consume */ fun dispatchRawDelta(delta: Float): Float { val currentDragPosition = offset ?: 0f val potentiallyConsumed = currentDragPosition + delta val clamped = potentiallyConsumed.coerceIn(minOffset, maxOffset) val deltaToConsume = clamped - currentDragPosition if (abs(deltaToConsume) > 0) { draggableState.dispatchRawDelta(deltaToConsume) } return deltaToConsume } private fun computeTarget( offset: Float, currentValue: T, velocity: Float ): T { val currentAnchors = anchors val currentAnchor = currentAnchors[currentValue] val currentDensity = requireDensity() val velocityThresholdPx = with(currentDensity) { velocityThreshold.toPx() } return if (currentAnchor == offset || currentAnchor == null) { currentValue } else if (currentAnchor < offset) { // Swiping from lower to upper (positive). if (velocity >= velocityThresholdPx) { currentAnchors.closestAnchor(offset, true) } else { val upper = currentAnchors.closestAnchor(offset, true) val distance = abs(currentAnchors.getValue(upper) - currentAnchor) val relativeThreshold = abs(positionalThreshold(currentDensity, distance)) val absoluteThreshold = abs(currentAnchor + relativeThreshold) if (offset < absoluteThreshold) currentValue else upper } } else { // Swiping from upper to lower (negative). if (velocity <= -velocityThresholdPx) { currentAnchors.closestAnchor(offset, false) } else { val lower = currentAnchors.closestAnchor(offset, false) val distance = abs(currentAnchor - currentAnchors.getValue(lower)) val relativeThreshold = abs(positionalThreshold(currentDensity, distance)) val absoluteThreshold = abs(currentAnchor - relativeThreshold) if (offset < 0) { // For negative offsets, larger absolute thresholds are closer to lower anchors // than smaller ones. if (abs(offset) < absoluteThreshold) currentValue else lower } else { if (offset > absoluteThreshold) currentValue else lower } } } } private fun requireDensity() = requireNotNull(density) { "SwipeableState did not have a density attached. Are you using Modifier.swipeable with " + "this=$this SwipeableState?" } companion object { /** * The default [Saver] implementation for [SwipeableV2State]. */ @ExperimentalMaterialApi fun Saver( animationSpec: AnimationSpec, confirmValueChange: (T) -> Boolean, positionalThreshold: Density.(distance: Float) -> Float, velocityThreshold: Dp ) = Saver, T>( save = { it.currentValue }, restore = { SwipeableV2State( initialValue = it, animationSpec = animationSpec, confirmValueChange = confirmValueChange, positionalThreshold = positionalThreshold, velocityThreshold = velocityThreshold ) } ) } } /** * Create and remember a [SwipeableV2State]. * * @param initialValue The initial value. * @param animationSpec The default animation that will be used to animate to a new value. * @param confirmValueChange Optional callback invoked to confirm or veto a pending value change. */ @Composable @ExperimentalMaterialApi internal fun rememberSwipeableV2State( initialValue: T, animationSpec: AnimationSpec = SwipeableV2Defaults.AnimationSpec, confirmValueChange: (newValue: T) -> Boolean = { true } ): SwipeableV2State { return rememberSaveable( initialValue, animationSpec, confirmValueChange, saver = Saver( animationSpec = animationSpec, confirmValueChange = confirmValueChange, positionalThreshold = SwipeableV2Defaults.PositionalThreshold, velocityThreshold = SwipeableV2Defaults.VelocityThreshold ), ) { SwipeableV2State( initialValue = initialValue, animationSpec = animationSpec, confirmValueChange = confirmValueChange, positionalThreshold = SwipeableV2Defaults.PositionalThreshold, velocityThreshold = SwipeableV2Defaults.VelocityThreshold ) } } /** * Expresses a fixed positional threshold of [threshold] dp. This will be the distance from an * anchor that needs to be reached for [SwipeableV2State] to settle to the next closest anchor. * * @see [fractionalPositionalThreshold] for a fractional positional threshold */ @ExperimentalMaterialApi internal fun fixedPositionalThreshold(threshold: Dp): Density.(distance: Float) -> Float = { threshold.toPx() } /** * Expresses a relative positional threshold of the [fraction] of the distance to the closest anchor * in the current direction. This will be the distance from an anchor that needs to be reached for * [SwipeableV2State] to settle to the next closest anchor. * * @see [fixedPositionalThreshold] for a fixed positional threshold */ @ExperimentalMaterialApi internal fun fractionalPositionalThreshold( fraction: Float ): Density.(distance: Float) -> Float = { distance -> distance * fraction } /** * Contains useful defaults for [swipeableV2] and [SwipeableV2State]. */ @Stable @ExperimentalMaterialApi internal object SwipeableV2Defaults { /** * The default animation used by [SwipeableV2State]. */ @ExperimentalMaterialApi val AnimationSpec = SpringSpec() /** * The default velocity threshold (1.8 dp per millisecond) used by [rememberSwipeableV2State]. */ @ExperimentalMaterialApi val VelocityThreshold: Dp = 125.dp /** * The default positional threshold (56 dp) used by [rememberSwipeableV2State] */ @ExperimentalMaterialApi val PositionalThreshold: Density.(totalDistance: Float) -> Float = fixedPositionalThreshold(56.dp) /** * A [AnchorChangeHandler] implementation that attempts to reconcile an in-progress animation * by re-targeting it if necessary or finding the closest new anchor. * If the previous anchor is not in the new set of anchors, this implementation will snap to the * closest anchor. * * Consider implementing a custom handler for more complex components like sheets. * The [animate] and [snap] lambdas hoist the animation and snap logic. Usually these will just * delegate to [SwipeableV2State]. * * @param state The [SwipeableV2State] the change handler will read from * @param animate A lambda that gets invoked to start an animation to a new target * @param snap A lambda that gets invoked to snap to a new target */ @ExperimentalMaterialApi internal fun ReconcileAnimationOnAnchorChangeHandler( state: SwipeableV2State, animate: (target: T, velocity: Float) -> Unit, snap: (target: T) -> Unit ) = AnchorChangeHandler { previousTarget, previousAnchors, newAnchors -> val previousTargetOffset = previousAnchors[previousTarget] val newTargetOffset = newAnchors[previousTarget] if (previousTargetOffset != newTargetOffset) { if (newTargetOffset != null) { animate(previousTarget, state.lastVelocity) } else { snap(newAnchors.closestAnchor(offset = state.requireOffset())) } } } } /** * Defines a callback that is invoked when the anchors have changed. * * Components with custom reconciliation logic should implement this callback, for example to * re-target an in-progress animation when the anchors change. * * @see SwipeableV2Defaults.ReconcileAnimationOnAnchorChangeHandler for a default implementation */ @ExperimentalMaterialApi internal fun interface AnchorChangeHandler { /** * Callback that is invoked when the anchors have changed, after the [SwipeableV2State] has been * updated with them. Use this hook to re-launch animations or interrupt them if needed. * * @param previousTargetValue The target value before the anchors were updated * @param previousAnchors The previously set anchors * @param newAnchors The newly set anchors */ fun onAnchorsChanged( previousTargetValue: T, previousAnchors: Map, newAnchors: Map ) } @Stable private class SwipeAnchorsModifier( private val onDensityChanged: (density: Density) -> Unit, private val onSizeChanged: (layoutSize: IntSize) -> Unit, inspectorInfo: InspectorInfo.() -> Unit, ) : LayoutModifier, OnRemeasuredModifier, InspectorValueInfo(inspectorInfo) { private var lastDensity: Float = -1f private var lastFontScale: Float = -1f override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { if (density != lastDensity || fontScale != lastFontScale) { onDensityChanged(Density(density, fontScale)) lastDensity = density lastFontScale = fontScale } val placeable = measurable.measure(constraints) return layout(placeable.width, placeable.height) { placeable.place(0, 0) } } override fun onRemeasured(size: IntSize) { onSizeChanged(size) } override fun toString() = "SwipeAnchorsModifierImpl(updateDensity=$onDensityChanged, " + "onSizeChanged=$onSizeChanged)" } private fun Map.closestAnchor( offset: Float = 0f, searchUpwards: Boolean = false ): T { require(isNotEmpty()) { "The anchors were empty when trying to find the closest anchor" } return minBy { (_, anchor) -> val delta = if (searchUpwards) anchor - offset else offset - anchor if (delta < 0) Float.POSITIVE_INFINITY else delta }.key } private fun Map.minOrNull() = minOfOrNull { (_, offset) -> offset } private fun Map.maxOrNull() = maxOfOrNull { (_, offset) -> offset } private fun Map.requireAnchor(value: T) = requireNotNull(this[value]) { "Required anchor $value was not found in anchors. Current anchors: ${this.toMap()}" } ================================================ FILE: lib/neural-tools/.gitignore ================================================ /build ================================================ FILE: lib/neural-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android { namespace = "com.t8rin.neural_tools" } dependencies { api(libs.onnx.runtime) implementation(libs.ktor) implementation(libs.ktor.logging) implementation(libs.aire) } ================================================ FILE: lib/neural-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/DownloadProgress.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools data class DownloadProgress( val currentPercent: Float, val currentTotalSize: Long ) ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/NeuralTool.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools import android.app.Application import io.ktor.client.HttpClient import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logging abstract class NeuralTool { protected val context get() = application companion object { private var _httpClient: HttpClient = HttpClient { install(Logging) { level = LogLevel.INFO } } internal val httpClient: HttpClient get() = _httpClient internal var baseUrl = "" private var _context: Application? = null internal val application: Application get() = _context ?: throw NullPointerException("Call NeuralTool.init() in Application onCreate to use this feature") fun init( context: Application, httpClient: HttpClient? = null, baseUrl: String ) { _httpClient = httpClient ?: _httpClient this.baseUrl = baseUrl _context = context } } } ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BgRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.bgremover import android.graphics.Bitmap import com.t8rin.neural_tools.DownloadProgress import com.t8rin.neural_tools.NeuralTool import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.util.concurrent.ConcurrentHashMap object BgRemover : NeuralTool() { enum class Type { RMBG1_4, InSPyReNet, U2NetP, U2Net, BiRefNet, BiRefNetTiny, ISNet } val downloadedModels: StateFlow> = channelFlow { val map = ConcurrentHashMap() Type.entries.forEach { type -> if (type == Type.U2NetP) { map[type] = true send(map.filterValues { it }.keys.toList()) } launch { getRemover(type).isDownloaded.collectLatest { isDownloaded -> map[type] = isDownloaded send(map.filterValues { it }.keys.toList()) } } } }.debounce(500).stateIn( scope = CoroutineScope(Dispatchers.IO), started = SharingStarted.Eagerly, initialValue = listOf(Type.U2NetP) ) fun downloadModel( type: Type ): Flow = getRemover(type).startDownload() fun removeBackground( image: Bitmap, type: Type ): Bitmap? = runCatching { getRemover(type).removeBackground(image) }.getOrNull() fun closeAll() { Type.entries.forEach { getRemover(it).close() } } fun getRemover(type: Type) = when (type) { Type.RMBG1_4 -> RMBGBackgroundRemover Type.InSPyReNet -> InSPyReNetBackgroundRemover Type.U2NetP -> U2NetPortableBackgroundRemover Type.U2Net -> U2NetFullBackgroundRemover Type.BiRefNet -> BiRefNetBackgroundRemover Type.BiRefNetTiny -> BiRefNetTinyBackgroundRemover Type.ISNet -> ISNetBackgroundRemover } } ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.bgremover internal object BiRefNetBackgroundRemover : GenericBackgroundRemover( path = "birefnet_fp16.ort", trainedSize = 1024 ) ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/BiRefNetTinyBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.bgremover internal object BiRefNetTinyBackgroundRemover : GenericBackgroundRemover( path = "birefnet_swin_tiny.ort", trainedSize = 1024 ) ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/GenericBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UNCHECKED_CAST", "PropertyName") package com.t8rin.neural_tools.bgremover import ai.onnxruntime.OnnxTensor import ai.onnxruntime.OrtEnvironment import ai.onnxruntime.OrtSession import android.graphics.Bitmap import android.graphics.Color import androidx.core.graphics.createBitmap import androidx.core.graphics.set import com.awxkee.aire.Aire import com.awxkee.aire.ResizeFunction import com.awxkee.aire.ScaleColorSpace import com.t8rin.neural_tools.DownloadProgress import com.t8rin.neural_tools.NeuralTool import io.ktor.client.request.prepareGet import io.ktor.client.statement.bodyAsChannel import io.ktor.http.contentLength import io.ktor.utils.io.readRemaining import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.update import kotlinx.io.readByteArray import java.io.File import java.io.FileOutputStream import java.nio.FloatBuffer import kotlin.math.roundToInt abstract class GenericBackgroundRemover( path: String, val trainedSize: Int? ) : NeuralTool() { private val downloadLink = baseUrl.replace( oldValue = "*", newValue = path ) protected val env: OrtEnvironment by lazy { OrtEnvironment.getEnvironment() } protected var session: OrtSession? = null val directory: File get() = File(context.filesDir, "ai_models").apply(File::mkdirs) val modelFile get() = File( directory, downloadLink.substringAfterLast('/') ) protected open val _isDownloaded = MutableStateFlow(modelFile.exists() && modelFile.length() > 0) val isDownloaded: StateFlow = _isDownloaded open fun checkModel(): Boolean { if (!modelFile.exists() || modelFile.length() <= 0) { _isDownloaded.update { false } modelFile.delete() close() return false } _isDownloaded.update { true } return true } open fun removeBackground( image: Bitmap, modelPath: String = modelFile.path, trainedSize: Int? = this.trainedSize ): Bitmap? { if (!modelFile.exists() || modelFile.length() <= 0) { _isDownloaded.update { false } modelFile.delete() close() return null } val session = session ?: env.createSession(modelPath, OrtSession.SessionOptions()).also { session = it } val dstWidth = trainedSize ?: image.width val dstHeight = trainedSize ?: image.height val scaled = if (trainedSize == null) { image } else { Aire.scale( bitmap = image, dstWidth = dstWidth, dstHeight = dstHeight, scaleMode = ResizeFunction.Bilinear, colorSpace = ScaleColorSpace.SRGB ) } val input = bitmapToFloatBuffer(scaled, dstWidth, dstHeight) val inputName = session.inputNames.first() val inputTensor = OnnxTensor.createTensor( env, input, longArrayOf(1, 3, dstWidth.toLong(), dstHeight.toLong()) ) val output = session.run(mapOf(inputName to inputTensor)) val outputArray = (output[0].value as Array>>)[0][0] val maskBmp = createBitmap(dstWidth, dstHeight) var i = 0 for (y in 0 until dstHeight) { for (x in 0 until dstWidth) { val alpha = (outputArray[y][x] * 255f).roundToInt().coerceIn(0, 255) maskBmp[x, y] = Color.argb(alpha, 255, 255, 255) i++ } } val maskScaled = if (trainedSize == null) { maskBmp } else { Aire.scale( bitmap = maskBmp, dstWidth = image.width, dstHeight = image.height, scaleMode = ResizeFunction.Bilinear, colorSpace = ScaleColorSpace.SRGB ) } val pixels = IntArray(image.width * image.height) val maskPixels = IntArray(image.width * image.height) image.getPixels(pixels, 0, image.width, 0, 0, image.width, image.height) maskScaled.getPixels(maskPixels, 0, image.width, 0, 0, image.width, image.height) for (idx in pixels.indices) { val alpha = Color.alpha(maskPixels[idx]) val src = pixels[idx] pixels[idx] = Color.argb(alpha, Color.red(src), Color.green(src), Color.blue(src)) } val result = createBitmap(image.width, image.height) result.setPixels(pixels, 0, image.width, 0, 0, image.width, image.height) result.setHasAlpha(true) inputTensor.close() output.close() return result } open fun startDownload(forced: Boolean = false): Flow = callbackFlow { if (modelFile.exists() || modelFile.length() > 0) { if (!forced) _isDownloaded.update { true } } httpClient.prepareGet(downloadLink).execute { response -> val total = response.contentLength() ?: -1L val tmp = File(modelFile.parentFile, modelFile.name + ".tmp") val channel = response.bodyAsChannel() var downloaded = 0L FileOutputStream(tmp).use { fos -> try { while (!channel.isClosedForRead) { val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) while (!packet.exhausted()) { val bytes = packet.readByteArray() downloaded += bytes.size fos.write(bytes) trySend( DownloadProgress( currentPercent = if (total > 0) downloaded.toFloat() / total else 0f, currentTotalSize = downloaded ) ) } } tmp.renameTo(modelFile) _isDownloaded.update { true } close() } catch (e: Throwable) { tmp.delete() close(e) } } } }.flowOn(Dispatchers.IO) fun close() { session?.close() session = null } protected fun bitmapToFloatBuffer( bitmap: Bitmap, width: Int, height: Int ): FloatBuffer { val pixels = IntArray(width * height) bitmap.getPixels(pixels, 0, width, 0, 0, width, height) val buffer = FloatBuffer.allocate(3 * width * height) var offsetR = 0 var offsetG = width * height var offsetB = 2 * width * height for (p in pixels) { buffer.put(offsetR++, Color.red(p) / 255f) buffer.put(offsetG++, Color.green(p) / 255f) buffer.put(offsetB++, Color.blue(p) / 255f) } return buffer } } ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/ISNetBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.bgremover internal object ISNetBackgroundRemover : GenericBackgroundRemover( path = "isnet-general-use.onnx", trainedSize = 1024 ) ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/InSPyReNetBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.bgremover internal object InSPyReNetBackgroundRemover : GenericBackgroundRemover( path = "onnx/bgremove/inspyrenet.onnx", trainedSize = 1024 ) ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/RMBGBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.bgremover internal object RMBGBackgroundRemover : GenericBackgroundRemover( path = "onnx/bgremove/RMBG_1.4.ort", trainedSize = 1024 ) ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetFullBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.bgremover internal object U2NetFullBackgroundRemover : GenericBackgroundRemover( path = "onnx/bgremove/u2net.onnx", trainedSize = 320 ) ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/bgremover/U2NetPortableBackgroundRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UNCHECKED_CAST") package com.t8rin.neural_tools.bgremover import android.graphics.Bitmap import com.t8rin.neural_tools.DownloadProgress import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.update import java.io.FileOutputStream internal object U2NetPortableBackgroundRemover : GenericBackgroundRemover( path = "onnx/bgremove/u2netp.onnx", trainedSize = 320 ) { init { extract() } override fun startDownload(forced: Boolean): Flow = callbackFlow { extract() close() } override fun removeBackground(image: Bitmap, modelPath: String, trainedSize: Int?): Bitmap? { extract() return super.removeBackground( image = image, modelPath = modelPath, trainedSize = trainedSize ) } override fun checkModel(): Boolean { extract() return super.checkModel() } private fun extract() { if (!modelFile.exists() || modelFile.length() <= 0) { context.assets.open("u2netp.onnx").use { input -> FileOutputStream(modelFile).use { output -> input.copyTo(output) } } } _isDownloaded.update { true } } } ================================================ FILE: lib/neural-tools/src/main/java/com/t8rin/neural_tools/inpaint/LaMaProcessor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.neural_tools.inpaint import ai.onnxruntime.OnnxTensor import ai.onnxruntime.OrtEnvironment import ai.onnxruntime.OrtSession import android.graphics.Bitmap import android.util.Log import com.awxkee.aire.Aire import com.awxkee.aire.ResizeFunction import com.awxkee.aire.ScaleColorSpace import com.t8rin.neural_tools.DownloadProgress import com.t8rin.neural_tools.NeuralTool import io.ktor.client.request.prepareGet import io.ktor.client.statement.bodyAsChannel import io.ktor.http.contentLength import io.ktor.utils.io.readRemaining import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.update import kotlinx.io.readByteArray import java.io.File import java.io.FileOutputStream import java.nio.FloatBuffer object LaMaProcessor : NeuralTool() { private const val TRAINED_SIZE = 512 private var isFastModel: Boolean = false private val MODEL_DOWNLOAD_LINK get() = baseUrl.replace( oldValue = "*", newValue = if (isFastModel) { "onnx/inpaint/lama/LaMa_512_FAST.onnx" } else { "onnx/inpaint/lama/LaMa_512.onnx" } ) private val directory: File get() = File(context.filesDir, "onnx").apply { mkdirs() } private val modelFile get() = File( directory, MODEL_DOWNLOAD_LINK.substringAfterLast('/') ) private var sessionHolder: OrtSession? = null private val session: OrtSession get() = sessionHolder ?: run { val options = OrtSession.SessionOptions().apply { runCatching { addCUDA() } runCatching { setOptimizationLevel(OrtSession.SessionOptions.OptLevel.ALL_OPT) } runCatching { setInterOpNumThreads(8) } runCatching { setIntraOpNumThreads(8) } runCatching { setMemoryPatternOptimization(true) } } OrtEnvironment.getEnvironment().createSession(modelFile.absolutePath, options) }.also { sessionHolder = it } private val _isDownloaded = MutableStateFlow(modelFile.exists()) val isDownloaded: StateFlow = _isDownloaded fun setIsFastModel(boolean: Boolean) { isFastModel = boolean _isDownloaded.update { modelFile.exists() } sessionHolder?.close() sessionHolder = null } fun startDownload(): Flow = callbackFlow { httpClient.prepareGet(MODEL_DOWNLOAD_LINK).execute { response -> val total = response.contentLength() ?: -1L val tmp = File(modelFile.parentFile, modelFile.name + ".tmp") val channel = response.bodyAsChannel() var downloaded = 0L FileOutputStream(tmp).use { fos -> try { while (!channel.isClosedForRead) { val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) while (!packet.exhausted()) { val bytes = packet.readByteArray() downloaded += bytes.size fos.write(bytes) trySend( DownloadProgress( currentPercent = if (total > 0) downloaded.toFloat() / total else 0f, currentTotalSize = downloaded ) ) } } tmp.renameTo(modelFile) _isDownloaded.update { true } close() } catch (e: Throwable) { tmp.delete() close(e) } } } }.flowOn(Dispatchers.IO) fun inpaint( image: Bitmap, mask: Bitmap ): Bitmap? = runCatching { if (!modelFile.exists()) { _isDownloaded.update { false } return null } val inputImage = if (image.width != TRAINED_SIZE || image.height != TRAINED_SIZE) { Aire.scale( bitmap = image, dstWidth = TRAINED_SIZE, dstHeight = TRAINED_SIZE, scaleMode = ResizeFunction.Lanczos3, colorSpace = ScaleColorSpace.LAB ) } else { image } val inputMask = if (mask.width != TRAINED_SIZE || mask.height != TRAINED_SIZE) { Aire.scale( bitmap = mask, dstWidth = TRAINED_SIZE, dstHeight = TRAINED_SIZE, scaleMode = ResizeFunction.Nearest, colorSpace = ScaleColorSpace.SRGB ) } else { mask } val tensorImg = bitmapToOnnxTensor(bitmap = inputImage) val tensorMask = bitmapToMaskTensor(bitmap = inputMask) val inputs = mapOf("image" to tensorImg, "mask" to tensorMask) session.run(inputs).use { res -> val outValue = res[0] val outTensor = outValue as? OnnxTensor ?: throw IllegalStateException("Model output is not OnnxTensor") val resultBitmap = outputTensorToBitmap(outTensor) tensorImg.close() tensorMask.close() if (image.width != TRAINED_SIZE || image.height != TRAINED_SIZE) { return Aire.scale( bitmap = resultBitmap, dstWidth = image.width, dstHeight = image.height, scaleMode = ResizeFunction.Lanczos3, colorSpace = ScaleColorSpace.LAB ) } return resultBitmap } }.onFailure { Log.e("LaMaProcessor", "failure", it) }.getOrNull() private fun bitmapToMaskTensor( bitmap: Bitmap ): OnnxTensor { val env = OrtEnvironment.getEnvironment() val w = bitmap.width val h = bitmap.height val pixels = IntArray(w * h) bitmap.getPixels(pixels, 0, w, 0, 0, w, h) val data = FloatArray(w * h) for (i in pixels.indices) { val p = pixels[i] val r = (p shr 16) and 0xFF data[i] = if (r > 0) 1f else 0f } return OnnxTensor.createTensor( env, FloatBuffer.wrap(data), longArrayOf(1, 1, h.toLong(), w.toLong()) ) } private fun bitmapToOnnxTensor( bitmap: Bitmap ): OnnxTensor { val env = OrtEnvironment.getEnvironment() val w = bitmap.width val h = bitmap.height val pixels = IntArray(w * h) bitmap.getPixels(pixels, 0, w, 0, 0, w, h) val size = 3 * w * h val data = FloatArray(size) val channelSize = w * h for (i in 0 until channelSize) { val p = pixels[i] val r = ((p shr 16) and 0xFF) / 255f val g = ((p shr 8) and 0xFF) / 255f val b = (p and 0xFF) / 255f data[i] = r data[channelSize + i] = g data[2 * channelSize + i] = b } return OnnxTensor.createTensor( env, FloatBuffer.wrap(data), longArrayOf(1, 3, h.toLong(), w.toLong()) ) } private fun outputTensorToBitmap( tensor: OnnxTensor ): Bitmap { val buffer = tensor.floatBuffer val data = FloatArray(buffer.capacity()) buffer.get(data) val width = TRAINED_SIZE val height = TRAINED_SIZE val size = width * height val pixels = IntArray(size) val amp = if (isFastModel) 255 else 1 for (i in 0 until size) { val r = (data[i] * amp).toInt().coerceIn(0, 255) val g = (data[size + i] * amp).toInt().coerceIn(0, 255) val b = (data[2 * size + i] * amp).toInt().coerceIn(0, 255) pixels[i] = (0xFF shl 24) or (r shl 16) or (g shl 8) or b } return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888) } } ================================================ FILE: lib/opencv-tools/.gitignore ================================================ /build ================================================ FILE: lib/opencv-tools/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.opencv_tools" dependencies { api(libs.opencv) implementation(libs.coilCompose) implementation(projects.lib.image) implementation(projects.lib.zoomable) implementation(projects.lib.gesture) } ================================================ FILE: lib/opencv-tools/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/opencv-tools/src/main/assets/haarcascade_eye.xml ================================================ 20 20 <_> <_> <_> <_>0 8 20 12 -1. <_>0 14 20 6 2. 0 0.1296395957469940 -0.7730420827865601 0.6835014820098877 <_> <_> <_>9 1 4 15 -1. <_>9 6 4 5 3. 0 -0.0463268086314201 0.5735275149345398 -0.4909768998622894 <_> <_> <_>6 10 9 2 -1. <_>9 10 3 2 3. 0 -0.0161730907857418 0.6025434136390686 -0.3161070942878723 <_> <_> <_>7 0 10 9 -1. <_>7 3 10 3 3. 0 -0.0458288416266441 0.6417754888534546 -0.1554504036903381 <_> <_> <_>12 2 2 18 -1. <_>12 8 2 6 3. 0 -0.0537596195936203 0.5421931743621826 -0.2048082947731018 <_> <_> <_>8 6 8 6 -1. <_>8 9 8 3 2. 0 0.0341711901128292 -0.2338819056749344 0.4841090142726898 -1.4562760591506958 -1 -1 <_> <_> <_> <_>2 0 17 18 -1. <_>2 6 17 6 3. 0 -0.2172762006521225 0.7109889984130859 -0.5936073064804077 <_> <_> <_>10 10 1 8 -1. <_>10 14 1 4 2. 0 0.0120719699189067 -0.2824048101902008 0.5901355147361755 <_> <_> <_>7 10 9 2 -1. <_>10 10 3 2 3. 0 -0.0178541392087936 0.5313752293586731 -0.2275896072387695 <_> <_> <_>5 1 6 6 -1. <_>5 3 6 2 3. 0 0.0223336108028889 -0.1755609959363937 0.6335613727569580 <_> <_> <_>3 1 15 9 -1. <_>3 4 15 3 3. 0 -0.0914200171828270 0.6156309247016907 -0.1689953058958054 <_> <_> <_>6 3 9 6 -1. <_>6 5 9 2 3. 0 0.0289736501872540 -0.1225007995963097 0.7440117001533508 <_> <_> <_>8 17 6 3 -1. <_>10 17 2 3 3. 0 7.8203463926911354e-003 0.1697437018156052 -0.6544165015220642 <_> <_> <_>9 10 9 1 -1. <_>12 10 3 1 3. 0 0.0203404892235994 -0.1255664974451065 0.8271045088768005 <_> <_> <_>1 7 6 11 -1. <_>3 7 2 11 3. 0 -0.0119261499494314 0.3860568106174469 -0.2099234014749527 <_> <_> <_>9 18 3 1 -1. <_>10 18 1 1 3. 0 -9.7281101625412703e-004 -0.6376119256019592 0.1295239031314850 <_> <_> <_>16 16 1 2 -1. <_>16 17 1 1 2. 0 1.8322050891583785e-005 -0.3463147878646851 0.2292426973581314 <_> <_> <_>9 17 6 3 -1. <_>11 17 2 3 3. 0 -8.0854417756199837e-003 -0.6366580128669739 0.1307865977287293 -1.2550230026245117 0 -1 <_> <_> <_> <_>8 0 5 18 -1. <_>8 6 5 6 3. 0 -0.1181226968765259 0.6784452199935913 -0.5004578232765198 <_> <_> <_>6 7 9 7 -1. <_>9 7 3 7 3. 0 -0.0343327596783638 0.6718636155128479 -0.3574487864971161 <_> <_> <_>14 6 6 10 -1. <_>16 6 2 10 3. 0 -0.0215307995676994 0.7222070097923279 -0.1819241940975189 <_> <_> <_>9 8 9 5 -1. <_>12 8 3 5 3. 0 -0.0219099707901478 0.6652938723564148 -0.2751022875308991 <_> <_> <_>3 7 9 6 -1. <_>6 7 3 6 3. 0 -0.0287135392427444 0.6995570063591003 -0.1961558014154434 <_> <_> <_>1 7 6 6 -1. <_>3 7 2 6 3. 0 -0.0114674801006913 0.5926734805107117 -0.2209735065698624 <_> <_> <_>16 0 4 18 -1. <_>16 6 4 6 3. 0 -0.0226111691445112 0.3448306918144226 -0.3837955892086029 <_> <_> <_>0 17 3 3 -1. <_>0 18 3 1 3. 0 -1.9308089977130294e-003 -0.7944571971893311 0.1562865972518921 <_> <_> <_>16 0 2 1 -1. <_>17 0 1 1 2. 0 5.6419910833938047e-005 -0.3089601099491119 0.3543108999729157 -1.3728189468383789 1 -1 <_> <_> <_> <_>0 8 20 12 -1. <_>0 14 20 6 2. 0 0.1988652050495148 -0.5286070108413696 0.3553672134876251 <_> <_> <_>6 6 9 8 -1. <_>9 6 3 8 3. 0 -0.0360089391469955 0.4210968911647797 -0.3934898078441620 <_> <_> <_>5 3 12 9 -1. <_>5 6 12 3 3. 0 -0.0775698497891426 0.4799154102802277 -0.2512216866016388 <_> <_> <_>4 16 1 2 -1. <_>4 17 1 1 2. 0 8.2630853285081685e-005 -0.3847548961639404 0.3184922039508820 <_> <_> <_>18 10 2 1 -1. <_>19 10 1 1 2. 0 3.2773229759186506e-004 -0.2642731964588165 0.3254724144935608 <_> <_> <_>9 8 6 5 -1. <_>11 8 2 5 3. 0 -0.0185748506337404 0.4673658907413483 -0.1506727039813995 <_> <_> <_>0 0 2 1 -1. <_>1 0 1 1 2. 0 -7.0008762122597545e-005 0.2931315004825592 -0.2536509931087494 <_> <_> <_>6 8 6 6 -1. <_>8 8 2 6 3. 0 -0.0185521300882101 0.4627366065979004 -0.1314805001020432 <_> <_> <_>11 7 6 7 -1. <_>13 7 2 7 3. 0 -0.0130304200574756 0.4162721931934357 -0.1775148957967758 <_> <_> <_>19 14 1 2 -1. <_>19 15 1 1 2. 0 6.5694141085259616e-005 -0.2803510129451752 0.2668074071407318 <_> <_> <_>6 17 1 2 -1. <_>6 18 1 1 2. 0 1.7005260451696813e-004 -0.2702724933624268 0.2398165017366409 <_> <_> <_>14 7 2 7 -1. <_>15 7 1 7 2. 0 -3.3129199873656034e-003 0.4441143870353699 -0.1442888975143433 <_> <_> <_>6 8 2 4 -1. <_>7 8 1 4 2. 0 1.7583490116521716e-003 -0.1612619012594223 0.4294076859951019 <_> <_> <_>5 8 12 6 -1. <_>5 10 12 2 3. 0 -0.0251947492361069 0.4068729877471924 -0.1820258051156998 <_> <_> <_>2 17 1 3 -1. <_>2 18 1 1 3. 0 1.4031709870323539e-003 0.0847597867250443 -0.8001856803894043 <_> <_> <_>6 7 3 6 -1. <_>7 7 1 6 3. 0 -7.3991729877889156e-003 0.5576609969139099 -0.1184315979480743 -1.2879480123519897 2 -1 <_> <_> <_> <_>6 7 9 12 -1. <_>9 7 3 12 3. 0 -0.0299430806189775 0.3581081032752991 -0.3848763108253479 <_> <_> <_>6 2 11 12 -1. <_>6 6 11 4 3. 0 -0.1256738007068634 0.3931693136692047 -0.3001225888729096 <_> <_> <_>1 12 5 8 -1. <_>1 16 5 4 2. 0 5.3635272197425365e-003 -0.4390861988067627 0.1925701051950455 <_> <_> <_>14 7 6 7 -1. <_>16 7 2 7 3. 0 -8.0971820279955864e-003 0.3990666866302490 -0.2340787053108215 <_> <_> <_>10 8 6 6 -1. <_>12 8 2 6 3. 0 -0.0165979098528624 0.4209528863430023 -0.2267484068870544 <_> <_> <_>16 18 4 2 -1. <_>16 19 4 1 2. 0 -2.0199299324303865e-003 -0.7415673136711121 0.1260118931531906 <_> <_> <_>18 17 2 3 -1. <_>18 18 2 1 3. 0 -1.5202340437099338e-003 -0.7615460157394409 0.0863736122846603 <_> <_> <_>9 7 3 7 -1. <_>10 7 1 7 3. 0 -4.9663940444588661e-003 0.4218223989009857 -0.1790491938591003 <_> <_> <_>5 6 6 8 -1. <_>7 6 2 8 3. 0 -0.0192076005041599 0.4689489901065826 -0.1437875032424927 <_> <_> <_>2 6 6 11 -1. <_>4 6 2 11 3. 0 -0.0122226802632213 0.3284207880496979 -0.2180214971303940 <_> <_> <_>8 10 12 8 -1. <_>8 14 12 4 2. 0 0.0575486682355404 -0.3676880896091461 0.2435711026191711 <_> <_> <_>7 17 6 3 -1. <_>9 17 2 3 3. 0 -9.5794079825282097e-003 -0.7224506735801697 0.0636645630002022 <_> <_> <_>10 9 3 3 -1. <_>11 9 1 3 3. 0 -2.9545740690082312e-003 0.3584643900394440 -0.1669632941484451 <_> <_> <_>8 8 3 6 -1. <_>9 8 1 6 3. 0 -4.2017991654574871e-003 0.3909480869770050 -0.1204179003834724 <_> <_> <_>7 0 6 5 -1. <_>9 0 2 5 3. 0 -0.0136249903589487 -0.5876771807670593 0.0884047299623489 <_> <_> <_>6 17 1 3 -1. <_>6 18 1 1 3. 0 6.2853112467564642e-005 -0.2634845972061157 0.2141927927732468 <_> <_> <_>0 18 4 2 -1. <_>0 19 4 1 2. 0 -2.6782939676195383e-003 -0.7839016914367676 0.0805269628763199 <_> <_> <_>4 1 11 9 -1. <_>4 4 11 3 3. 0 -0.0705971792340279 0.4146926105022430 -0.1398995965719223 <_> <_> <_>3 1 14 9 -1. <_>3 4 14 3 3. 0 0.0920936465263367 -0.1305518001317978 0.5043578147888184 <_> <_> <_>0 9 6 4 -1. <_>2 9 2 4 3. 0 -8.8004386052489281e-003 0.3660975098609924 -0.1403664946556091 <_> <_> <_>18 13 1 2 -1. <_>18 14 1 1 2. 0 7.5080977694597095e-005 -0.2970443964004517 0.2070294022560120 <_> <_> <_>13 5 3 11 -1. <_>14 5 1 11 3. 0 -2.9870450962334871e-003 0.3561570048332214 -0.1544596999883652 <_> <_> <_>0 18 8 2 -1. <_>0 18 4 1 2. <_>4 19 4 1 2. 0 -2.6441509835422039e-003 -0.5435351729393005 0.1029511019587517 -1.2179850339889526 3 -1 <_> <_> <_> <_>5 8 12 5 -1. <_>9 8 4 5 3. 0 -0.0478624701499939 0.4152823984622955 -0.3418582081794739 <_> <_> <_>4 7 11 10 -1. <_>4 12 11 5 2. 0 0.0873505324125290 -0.3874978125095367 0.2420420050621033 <_> <_> <_>14 9 6 4 -1. <_>16 9 2 4 3. 0 -0.0168494991958141 0.5308247804641724 -0.1728291064500809 <_> <_> <_>0 7 6 8 -1. <_>3 7 3 8 2. 0 -0.0288700293749571 0.3584350943565369 -0.2240259051322937 <_> <_> <_>0 16 3 3 -1. <_>0 17 3 1 3. 0 2.5679389946162701e-003 0.1499049961566925 -0.6560940742492676 <_> <_> <_>7 11 12 1 -1. <_>11 11 4 1 3. 0 -0.0241166595369577 0.5588967800140381 -0.1481028050184250 <_> <_> <_>4 8 9 4 -1. <_>7 8 3 4 3. 0 -0.0328266583383083 0.4646868109703064 -0.1078552976250649 <_> <_> <_>5 16 6 4 -1. <_>7 16 2 4 3. 0 -0.0152330603450537 -0.7395442724227905 0.0562368817627430 <_> <_> <_>18 17 1 3 -1. <_>18 18 1 1 3. 0 -3.0209511169232428e-004 -0.4554882049560547 0.0970698371529579 <_> <_> <_>18 17 1 3 -1. <_>18 18 1 1 3. 0 7.5365108205005527e-004 0.0951472967863083 -0.5489501953125000 <_> <_> <_>4 9 4 10 -1. <_>4 9 2 5 2. <_>6 14 2 5 2. 0 -0.0106389503926039 0.4091297090053558 -0.1230840981006622 <_> <_> <_>4 8 6 4 -1. <_>6 8 2 4 3. 0 -7.5217830017209053e-003 0.4028914868831635 -0.1604878008365631 <_> <_> <_>10 2 2 18 -1. <_>10 8 2 6 3. 0 -0.1067709997296333 0.6175932288169861 -0.0730911865830421 <_> <_> <_>0 5 8 6 -1. <_>0 5 4 3 2. <_>4 8 4 3 2. 0 0.0162569191306829 -0.1310368031263351 0.3745365142822266 <_> <_> <_>6 0 6 5 -1. <_>8 0 2 5 3. 0 -0.0206793602555990 -0.7140290737152100 0.0523900091648102 <_> <_> <_>18 0 2 14 -1. <_>18 7 2 7 2. 0 0.0170523691922426 0.1282286047935486 -0.3108068108558655 <_> <_> <_>8 18 4 2 -1. <_>10 18 2 2 2. 0 -5.7122060097754002e-003 -0.6055650711059570 0.0818847566843033 <_> <_> <_>1 17 6 3 -1. <_>1 18 6 1 3. 0 2.0851430235779844e-005 -0.2681298851966858 0.1445384025573731 <_> <_> <_>11 8 3 5 -1. <_>12 8 1 5 3. 0 7.9284431412816048e-003 -0.0787953510880470 0.5676258206367493 <_> <_> <_>11 8 3 4 -1. <_>12 8 1 4 3. 0 -2.5217379443347454e-003 0.3706862926483154 -0.1362057030200958 <_> <_> <_>11 0 6 5 -1. <_>13 0 2 5 3. 0 -0.0224261991679668 -0.6870499849319458 0.0510628595948219 <_> <_> <_>1 7 6 7 -1. <_>3 7 2 7 3. 0 -7.6451441273093224e-003 0.2349222004413605 -0.1790595948696137 <_> <_> <_>0 13 1 3 -1. <_>0 14 1 1 3. 0 -1.1175329564139247e-003 -0.5986905097961426 0.0743244364857674 <_> <_> <_>3 2 9 6 -1. <_>3 4 9 2 3. 0 0.0192127898335457 -0.1570255011320114 0.2973746955394745 <_> <_> <_>8 6 9 2 -1. <_>8 7 9 1 2. 0 5.6293429806828499e-003 -0.0997690185904503 0.4213027060031891 <_> <_> <_>0 14 3 6 -1. <_>0 16 3 2 3. 0 -9.5671862363815308e-003 -0.6085879802703857 0.0735062584280968 <_> <_> <_>1 11 6 4 -1. <_>3 11 2 4 3. 0 0.0112179601565003 -0.1032081022858620 0.4190984964370728 -1.2905240058898926 4 -1 <_> <_> <_> <_>6 9 9 3 -1. <_>9 9 3 3 3. 0 -0.0174864400178194 0.3130728006362915 -0.3368118107318878 <_> <_> <_>6 0 9 6 -1. <_>6 2 9 2 3. 0 0.0307146497070789 -0.1876619011163712 0.5378080010414124 <_> <_> <_>8 5 6 6 -1. <_>8 7 6 2 3. 0 -0.0221887193620205 0.3663788139820099 -0.1612481027841568 <_> <_> <_>1 12 2 1 -1. <_>2 12 1 1 2. 0 -5.0700771680567414e-005 0.2124571055173874 -0.2844462096691132 <_> <_> <_>10 10 6 2 -1. <_>12 10 2 2 3. 0 -7.0170420221984386e-003 0.3954311013221741 -0.1317359060049057 <_> <_> <_>13 8 6 6 -1. <_>15 8 2 6 3. 0 -6.8563609384000301e-003 0.3037385940551758 -0.2065781950950623 <_> <_> <_>6 16 6 4 -1. <_>8 16 2 4 3. 0 -0.0141292596235871 -0.7650300860404968 0.0982131883502007 <_> <_> <_>8 0 9 9 -1. <_>8 3 9 3 3. 0 -0.0479154810309410 0.4830738902091980 -0.1300680935382843 <_> <_> <_>18 17 1 3 -1. <_>18 18 1 1 3. 0 4.7032979637151584e-005 -0.2521657049655914 0.2438668012619019 <_> <_> <_>18 17 1 3 -1. <_>18 18 1 1 3. 0 1.0221180273219943e-003 0.0688576027750969 -0.6586114168167114 <_> <_> <_>7 10 3 3 -1. <_>8 10 1 3 3. 0 -2.6056109927594662e-003 0.4294202923774719 -0.1302246004343033 <_> <_> <_>9 14 2 2 -1. <_>9 14 1 1 2. <_>10 15 1 1 2. 0 5.4505340813193470e-005 -0.1928862035274506 0.2895849943161011 <_> <_> <_>9 14 2 2 -1. <_>9 14 1 1 2. <_>10 15 1 1 2. 0 -6.6721157054416835e-005 0.3029071092605591 -0.1985436975955963 <_> <_> <_>0 8 19 12 -1. <_>0 14 19 6 2. 0 0.2628143131732941 -0.2329394072294235 0.2369246035814285 <_> <_> <_>7 6 9 14 -1. <_>10 6 3 14 3. 0 -0.0235696695744991 0.1940104067325592 -0.2848461866378784 <_> <_> <_>13 8 3 4 -1. <_>14 8 1 4 3. 0 -3.9120172150433064e-003 0.5537897944450378 -0.0956656783819199 <_> <_> <_>4 17 1 3 -1. <_>4 18 1 1 3. 0 5.0788799853762612e-005 -0.2391265928745270 0.2179948985576630 <_> <_> <_>4 9 6 3 -1. <_>6 9 2 3 3. 0 -7.8732017427682877e-003 0.4069742858409882 -0.1276804059743881 <_> <_> <_>2 18 5 2 -1. <_>2 19 5 1 2. 0 -1.6778609715402126e-003 -0.5774465799331665 0.0973247885704041 <_> <_> <_>7 8 2 2 -1. <_>7 8 1 1 2. <_>8 9 1 1 2. 0 -2.6832430739887059e-004 0.2902188003063202 -0.1683126986026764 <_> <_> <_>7 8 2 2 -1. <_>7 8 1 1 2. <_>8 9 1 1 2. 0 7.8687182394787669e-005 -0.1955157071352005 0.2772096991539002 <_> <_> <_>5 10 13 2 -1. <_>5 11 13 1 2. 0 0.0129535002633929 -0.0968383178114891 0.4032387137413025 <_> <_> <_>10 8 1 9 -1. <_>10 11 1 3 3. 0 -0.0130439596250653 0.4719856977462769 -0.0892875492572784 <_> <_> <_>15 8 2 12 -1. <_>15 8 1 6 2. <_>16 14 1 6 2. 0 3.0261781066656113e-003 -0.1362338066101074 0.3068627119064331 <_> <_> <_>4 0 3 5 -1. <_>5 0 1 5 3. 0 -6.0438038781285286e-003 -0.7795410156250000 0.0573163107037544 <_> <_> <_>12 6 3 7 -1. <_>13 6 1 7 3. 0 -2.2507249377667904e-003 0.3087705969810486 -0.1500630974769592 <_> <_> <_>7 16 6 4 -1. <_>9 16 2 4 3. 0 0.0158268101513386 0.0645518898963928 -0.7245556712150574 <_> <_> <_>9 16 2 1 -1. <_>10 16 1 1 2. 0 6.5864507632795721e-005 -0.1759884059429169 0.2321038991212845 -1.1600480079650879 5 -1 <_> <_> <_> <_>6 10 9 2 -1. <_>9 10 3 2 3. 0 -0.0278548691421747 0.4551844894886017 -0.1809991002082825 <_> <_> <_>0 6 15 14 -1. <_>0 13 15 7 2. 0 0.1289504021406174 -0.5256553292274475 0.1618890017271042 <_> <_> <_>9 1 5 6 -1. <_>9 3 5 2 3. 0 0.0244031809270382 -0.1497496068477631 0.4235737919807434 <_> <_> <_>3 9 3 4 -1. <_>4 9 1 4 3. 0 -2.4458570405840874e-003 0.3294866979122162 -0.1744769066572189 <_> <_> <_>5 7 3 6 -1. <_>6 7 1 6 3. 0 -3.5336529836058617e-003 0.4742664098739624 -0.0736183598637581 <_> <_> <_>17 16 1 2 -1. <_>17 17 1 1 2. 0 5.1358150813030079e-005 -0.3042193055152893 0.1563327014446259 <_> <_> <_>9 8 6 12 -1. <_>11 8 2 12 3. 0 -0.0162256807088852 0.2300218045711517 -0.2035982012748718 <_> <_> <_>6 10 6 1 -1. <_>8 10 2 1 3. 0 -4.6007009223103523e-003 0.4045926928520203 -0.1348544061183929 <_> <_> <_>7 17 9 3 -1. <_>10 17 3 3 3. 0 -0.0219289995729923 -0.6872448921203613 0.0806842669844627 <_> <_> <_>14 18 6 2 -1. <_>14 19 6 1 2. 0 -2.8971210122108459e-003 -0.6961960792541504 0.0485452190041542 <_> <_> <_>9 5 3 14 -1. <_>10 5 1 14 3. 0 -4.4074649922549725e-003 0.2516626119613648 -0.1623664945363998 <_> <_> <_>8 16 9 4 -1. <_>11 16 3 4 3. 0 0.0284371692687273 0.0603942610323429 -0.6674445867538452 <_> <_> <_>0 0 4 14 -1. <_>0 7 4 7 2. 0 0.0832128822803497 0.0643579214811325 -0.5362604260444641 <_> <_> <_>8 1 6 3 -1. <_>10 1 2 3 3. 0 -0.0124193299561739 -0.7081686258316040 0.0575266107916832 <_> <_> <_>6 8 3 4 -1. <_>7 8 1 4 3. 0 -4.6992599964141846e-003 0.5125433206558228 -0.0873508006334305 <_> <_> <_>4 8 3 4 -1. <_>5 8 1 4 3. 0 -7.8025809489190578e-004 0.2668766081333160 -0.1796150952577591 <_> <_> <_>5 1 6 5 -1. <_>7 1 2 5 3. 0 -0.0197243392467499 -0.6756373047828674 0.0729419067502022 <_> <_> <_>1 18 1 2 -1. <_>1 19 1 1 2. 0 1.0269250487908721e-003 0.0539193190634251 -0.5554018020629883 <_> <_> <_>7 0 6 6 -1. <_>7 2 6 2 3. 0 -0.0259571895003319 0.5636252760887146 -0.0718983933329582 <_> <_> <_>0 18 4 2 -1. <_>0 19 4 1 2. 0 -1.2552699772641063e-003 -0.5034663081169128 0.0896914526820183 <_> <_> <_>12 3 8 12 -1. <_>12 7 8 4 3. 0 -0.0499705784022808 0.1768511980772018 -0.2230195999145508 <_> <_> <_>12 9 3 4 -1. <_>13 9 1 4 3. 0 -2.9899610672146082e-003 0.3912242054939270 -0.1014975011348724 <_> <_> <_>12 8 3 5 -1. <_>13 8 1 5 3. 0 4.8546842299401760e-003 -0.1177017986774445 0.4219093918800354 <_> <_> <_>16 0 2 1 -1. <_>17 0 1 1 2. 0 1.0448860120959580e-004 -0.1733397990465164 0.2234444022178650 <_> <_> <_>5 17 1 3 -1. <_>5 18 1 1 3. 0 5.9689260524464771e-005 -0.2340963035821915 0.1655824035406113 <_> <_> <_>10 2 3 6 -1. <_>10 4 3 2 3. 0 -0.0134239196777344 0.4302381873130798 -0.0997236520051956 <_> <_> <_>4 17 2 3 -1. <_>4 18 2 1 3. 0 2.2581999655812979e-003 0.0727209895849228 -0.5750101804733276 <_> <_> <_>12 7 1 9 -1. <_>12 10 1 3 3. 0 -0.0125462803989649 0.3618457913398743 -0.1145701035857201 <_> <_> <_>7 6 3 9 -1. <_>8 6 1 9 3. 0 -2.8705769218504429e-003 0.2821053862571716 -0.1236755028367043 <_> <_> <_>17 13 3 6 -1. <_>17 15 3 2 3. 0 0.0197856407612562 0.0478767491877079 -0.8066623806953430 <_> <_> <_>7 7 3 8 -1. <_>8 7 1 8 3. 0 4.7588930465281010e-003 -0.1092538982629776 0.3374697864055634 <_> <_> <_>5 0 3 5 -1. <_>6 0 1 5 3. 0 -6.9974269717931747e-003 -0.8029593825340271 0.0457067005336285 <_> <_> <_>4 6 9 8 -1. <_>7 6 3 8 3. 0 -0.0130334803834558 0.1868043988943100 -0.1768891066312790 <_> <_> <_>2 9 3 3 -1. <_>3 9 1 3 3. 0 -1.3742579612880945e-003 0.2772547900676727 -0.1280900985002518 <_> <_> <_>16 18 4 2 -1. <_>16 19 4 1 2. 0 2.7657810132950544e-003 0.0907589420676231 -0.4259473979473114 <_> <_> <_>17 10 3 10 -1. <_>17 15 3 5 2. 0 2.8941841446794569e-004 -0.3881632983684540 0.0892677977681160 -1.2257250547409058 6 -1 <_> <_> <_> <_>8 9 6 4 -1. <_>10 9 2 4 3. 0 -0.0144692296162248 0.3750782907009125 -0.2492828965187073 <_> <_> <_>5 2 10 12 -1. <_>5 6 10 4 3. 0 -0.1331762969493866 0.3016637861728668 -0.2241407036781311 <_> <_> <_>6 9 6 3 -1. <_>8 9 2 3 3. 0 -0.0101321600377560 0.3698559105396271 -0.1785001009702683 <_> <_> <_>11 7 3 7 -1. <_>12 7 1 7 3. 0 -7.8511182218790054e-003 0.4608676135540009 -0.1293139010667801 <_> <_> <_>12 8 6 4 -1. <_>14 8 2 4 3. 0 -0.0142958397045732 0.4484142959117889 -0.1022624000906944 <_> <_> <_>14 8 6 5 -1. <_>16 8 2 5 3. 0 -5.9606940485537052e-003 0.2792798876762390 -0.1532382965087891 <_> <_> <_>12 12 2 4 -1. <_>12 14 2 2 2. 0 0.0109327696263790 -0.1514174044132233 0.3988964855670929 <_> <_> <_>3 15 1 2 -1. <_>3 16 1 1 2. 0 5.0430990086169913e-005 -0.2268157005310059 0.2164438962936401 <_> <_> <_>12 7 3 4 -1. <_>13 7 1 4 3. 0 -5.8431681245565414e-003 0.4542014896869659 -0.1258715987205505 <_> <_> <_>10 0 6 6 -1. <_>12 0 2 6 3. 0 -0.0223462097346783 -0.6269019246101379 0.0824031233787537 <_> <_> <_>10 6 3 8 -1. <_>11 6 1 8 3. 0 -4.8836669884622097e-003 0.2635925114154816 -0.1468663066625595 <_> <_> <_>16 17 1 2 -1. <_>16 18 1 1 2. 0 7.5506002758629620e-005 -0.2450702041387558 0.1667888015508652 <_> <_> <_>16 16 1 3 -1. <_>16 17 1 1 3. 0 -4.9026997294276953e-004 -0.4264996051788330 0.0899735614657402 <_> <_> <_>11 11 1 2 -1. <_>11 12 1 1 2. 0 1.4861579984426498e-003 -0.1204025000333786 0.3009765148162842 <_> <_> <_>3 7 6 9 -1. <_>5 7 2 9 3. 0 -0.0119883399456739 0.2785247862339020 -0.1224434003233910 <_> <_> <_>4 18 9 1 -1. <_>7 18 3 1 3. 0 0.0105022396892309 0.0404527597129345 -0.7405040860176086 <_> <_> <_>0 11 4 9 -1. <_>0 14 4 3 3. 0 -0.0309630092233419 -0.6284269094467163 0.0480137616395950 <_> <_> <_>9 17 6 3 -1. <_>11 17 2 3 3. 0 0.0114145204424858 0.0394052118062973 -0.7167412042617798 <_> <_> <_>7 8 6 12 -1. <_>9 8 2 12 3. 0 -0.0123370001092553 0.1994132995605469 -0.1927430033683777 <_> <_> <_>6 8 3 4 -1. <_>7 8 1 4 3. 0 -5.9942267835140228e-003 0.5131816267967224 -0.0616580583155155 <_> <_> <_>3 17 1 3 -1. <_>3 18 1 1 3. 0 -1.1923230485990644e-003 -0.7260529994964600 0.0506527200341225 <_> <_> <_>11 9 6 4 -1. <_>13 9 2 4 3. 0 -7.4582789093255997e-003 0.2960307896137238 -0.1175478994846344 <_> <_> <_>6 1 3 2 -1. <_>7 1 1 2 3. 0 2.7877509128302336e-003 0.0450687110424042 -0.6953541040420532 <_> <_> <_>1 0 2 1 -1. <_>2 0 1 1 2. 0 -2.2503209766000509e-004 0.2004725039005280 -0.1577524989843369 <_> <_> <_>1 0 2 14 -1. <_>1 0 1 7 2. <_>2 7 1 7 2. 0 -5.0367889925837517e-003 0.2929981946945190 -0.1170049980282784 <_> <_> <_>5 5 11 8 -1. <_>5 9 11 4 2. 0 0.0747421607375145 -0.1139231994748116 0.3025662004947662 <_> <_> <_>9 3 5 6 -1. <_>9 5 5 2 3. 0 0.0202555190771818 -0.1051589027047157 0.4067046046257019 <_> <_> <_>7 9 5 10 -1. <_>7 14 5 5 2. 0 0.0442145094275475 -0.2763164043426514 0.1236386969685555 <_> <_> <_>15 10 2 2 -1. <_>16 10 1 2 2. 0 -8.7259558495134115e-004 0.2435503005981445 -0.1330094933509827 <_> <_> <_>0 18 8 2 -1. <_>0 19 8 1 2. 0 -2.4453739169985056e-003 -0.5386617183685303 0.0625106468796730 <_> <_> <_>7 17 1 3 -1. <_>7 18 1 1 3. 0 8.2725353422574699e-005 -0.2077220976352692 0.1627043932676315 <_> <_> <_>7 2 11 6 -1. <_>7 4 11 2 3. 0 -0.0366271100938320 0.3656840920448303 -0.0903302803635597 <_> <_> <_>8 3 9 3 -1. <_>8 4 9 1 3. 0 3.0996399000287056e-003 -0.1318302005529404 0.2535429894924164 <_> <_> <_>0 9 2 2 -1. <_>0 10 2 1 2. 0 -2.4709280114620924e-003 -0.5685349702835083 0.0535054318606853 <_> <_> <_>0 5 3 6 -1. <_>0 7 3 2 3. 0 -0.0141146704554558 -0.4859901070594788 0.0584852509200573 <_> <_> <_>6 7 2 2 -1. <_>6 7 1 1 2. <_>7 8 1 1 2. 0 8.4537261864170432e-004 -0.0800936371088028 0.4026564955711365 <_> <_> <_>7 6 3 6 -1. <_>8 6 1 6 3. 0 -7.1098632179200649e-003 0.4470323920249939 -0.0629474371671677 <_> <_> <_>12 1 6 4 -1. <_>14 1 2 4 3. 0 -0.0191259607672691 -0.6642286777496338 0.0498227700591087 <_> <_> <_>9 11 6 8 -1. <_>11 11 2 8 3. 0 -5.0773010589182377e-003 0.1737940013408661 -0.1685059964656830 <_> <_> <_>17 15 3 3 -1. <_>17 16 3 1 3. 0 -2.9198289848864079e-003 -0.6011028289794922 0.0574279390275478 <_> <_> <_>6 6 3 9 -1. <_>6 9 3 3 3. 0 -0.0249021500349045 0.2339798063039780 -0.1181845963001251 <_> <_> <_>0 5 8 6 -1. <_>0 5 4 3 2. <_>4 8 4 3 2. 0 0.0201477799564600 -0.0894598215818405 0.3602440059185028 <_> <_> <_>0 6 1 3 -1. <_>0 7 1 1 3. 0 1.7597640398889780e-003 0.0494584403932095 -0.6310262084007263 <_> <_> <_>17 0 2 6 -1. <_>18 0 1 6 2. 0 1.3812039978802204e-003 -0.1521805971860886 0.1897173970937729 <_> <_> <_>10 17 6 3 -1. <_>12 17 2 3 3. 0 -0.0109045403078198 -0.5809738039970398 0.0448627285659313 <_> <_> <_>13 15 2 2 -1. <_>13 15 1 1 2. <_>14 16 1 1 2. 0 7.5157178798690438e-005 -0.1377734988927841 0.1954316049814224 <_> <_> <_>4 0 12 3 -1. <_>4 1 12 1 3. 0 3.8649770431220531e-003 -0.1030222997069359 0.2537496984004974 -1.2863140106201172 7 -1 <_> <_> <_> <_>5 3 10 9 -1. <_>5 6 10 3 3. 0 -0.1021588966250420 0.4168125987052918 -0.1665562987327576 <_> <_> <_>7 7 9 7 -1. <_>10 7 3 7 3. 0 -0.0519398190081120 0.3302395045757294 -0.2071571052074432 <_> <_> <_>5 8 9 6 -1. <_>8 8 3 6 3. 0 -0.0427177809178829 0.2609373033046722 -0.1601389050483704 <_> <_> <_>0 16 6 2 -1. <_>0 17 6 1 2. 0 4.3890418601222336e-004 -0.3475053012371063 0.1391891986131668 <_> <_> <_>12 6 7 14 -1. <_>12 13 7 7 2. 0 0.0242643896490335 -0.4255205988883972 0.1357838064432144 <_> <_> <_>13 7 6 8 -1. <_>15 7 2 8 3. 0 -0.0238205995410681 0.3174980878829956 -0.1665204018354416 <_> <_> <_>2 10 6 3 -1. <_>4 10 2 3 3. 0 -7.0518180727958679e-003 0.3094717860221863 -0.1333830058574677 <_> <_> <_>18 17 1 3 -1. <_>18 18 1 1 3. 0 -6.8517157342284918e-004 -0.6008226275444031 0.0877470001578331 <_> <_> <_>7 1 6 2 -1. <_>7 2 6 1 2. 0 5.3705149330198765e-003 -0.1231144964694977 0.3833355009555817 <_> <_> <_>6 0 6 4 -1. <_>6 2 6 2 2. 0 -0.0134035395458341 0.3387736976146698 -0.1014048978686333 <_> <_> <_>8 18 6 2 -1. <_>10 18 2 2 3. 0 -6.6856360062956810e-003 -0.6119359731674194 0.0477402210235596 <_> <_> <_>7 6 5 2 -1. <_>7 7 5 1 2. 0 -4.2887418530881405e-003 0.2527579069137573 -0.1443451046943665 <_> <_> <_>6 7 3 6 -1. <_>7 7 1 6 3. 0 -0.0108767496421933 0.5477573275566101 -0.0594554804265499 <_> <_> <_>18 18 2 2 -1. <_>18 18 1 1 2. <_>19 19 1 1 2. 0 3.7882640026509762e-004 0.0834103003144264 -0.4422636926174164 <_> <_> <_>16 8 3 7 -1. <_>17 8 1 7 3. 0 -2.4550149682909250e-003 0.2333099991083145 -0.1396448016166687 <_> <_> <_>0 16 2 3 -1. <_>0 17 2 1 3. 0 1.2721839593723416e-003 0.0604802891612053 -0.4945608973503113 <_> <_> <_>5 19 6 1 -1. <_>7 19 2 1 3. 0 -4.8933159559965134e-003 -0.6683326959609985 0.0462184995412827 <_> <_> <_>9 5 6 6 -1. <_>9 7 6 2 3. 0 0.0264499895274639 -0.0732353627681732 0.4442596137523651 <_> <_> <_>0 10 2 4 -1. <_>0 12 2 2 2. 0 -3.3706070389598608e-003 -0.4246433973312378 0.0686765611171722 <_> <_> <_>0 9 4 3 -1. <_>2 9 2 3 2. 0 -2.9559480026364326e-003 0.1621803939342499 -0.1822299957275391 <_> <_> <_>1 10 6 9 -1. <_>3 10 2 9 3. 0 0.0306199099868536 -0.0586433410644531 0.5326362848281860 <_> <_> <_>9 0 6 2 -1. <_>11 0 2 2 3. 0 -9.5765907317399979e-003 -0.6056268215179443 0.0533459894359112 <_> <_> <_>14 1 2 1 -1. <_>15 1 1 1 2. 0 6.6372493165545166e-005 -0.1668083965778351 0.1928416043519974 <_> <_> <_>0 8 1 4 -1. <_>0 10 1 2 2. 0 5.0975950434803963e-003 0.0441195107996464 -0.5745884180068970 <_> <_> <_>15 6 2 2 -1. <_>15 6 1 1 2. <_>16 7 1 1 2. 0 3.7112718564458191e-004 -0.1108639985322952 0.2310539036989212 <_> <_> <_>7 5 3 6 -1. <_>8 5 1 6 3. 0 -8.6607588455080986e-003 0.4045628905296326 -0.0624460913240910 <_> <_> <_>19 17 1 3 -1. <_>19 18 1 1 3. 0 8.7489158613607287e-004 0.0648751482367516 -0.4487104117870331 <_> <_> <_>7 10 3 1 -1. <_>8 10 1 1 3. 0 1.1120870476588607e-003 -0.0938614606857300 0.3045391142368317 <_> <_> <_>12 1 6 6 -1. <_>14 1 2 6 3. 0 -0.0238378196954727 -0.5888742804527283 0.0466594211757183 <_> <_> <_>15 5 2 1 -1. <_>16 5 1 1 2. 0 2.2272899514064193e-004 -0.1489859968423843 0.1770195066928864 <_> <_> <_>8 2 7 4 -1. <_>8 4 7 2 2. 0 0.0244674701243639 -0.0557896010577679 0.4920830130577087 <_> <_> <_>4 0 14 15 -1. <_>4 5 14 5 3. 0 -0.1423932015895844 0.1519200056791306 -0.1877889931201935 <_> <_> <_>7 8 6 6 -1. <_>9 8 2 6 3. 0 -0.0201231203973293 0.2178010046482086 -0.1208190023899078 <_> <_> <_>11 17 1 3 -1. <_>11 18 1 1 3. 0 1.1513679783092812e-004 -0.1685658991336823 0.1645192950963974 <_> <_> <_>12 16 2 4 -1. <_>12 16 1 2 2. <_>13 18 1 2 2. 0 -2.7556740678846836e-003 -0.6944203972816467 0.0394494682550430 <_> <_> <_>10 13 2 1 -1. <_>11 13 1 1 2. 0 -7.5843912782147527e-005 0.1894136965274811 -0.1518384069204330 <_> <_> <_>11 8 3 3 -1. <_>12 8 1 3 3. 0 -7.0697711780667305e-003 0.4706459939479828 -0.0579276196658611 <_> <_> <_>2 0 6 8 -1. <_>4 0 2 8 3. 0 -0.0373931787908077 -0.7589244842529297 0.0341160483658314 <_> <_> <_>3 5 6 6 -1. <_>3 5 3 3 2. <_>6 8 3 3 2. 0 -0.0159956105053425 0.3067046999931335 -0.0875255763530731 <_> <_> <_>10 8 3 3 -1. <_>11 8 1 3 3. 0 -3.1183990649878979e-003 0.2619537115097046 -0.0912148877978325 <_> <_> <_>5 17 4 2 -1. <_>5 18 4 1 2. 0 1.0651360498741269e-003 -0.1742756068706513 0.1527764052152634 <_> <_> <_>8 16 5 2 -1. <_>8 17 5 1 2. 0 -1.6029420075938106e-003 0.3561263084411621 -0.0766299962997437 <_> <_> <_>0 4 3 3 -1. <_>0 5 3 1 3. 0 4.3619908392429352e-003 0.0493569709360600 -0.5922877192497253 <_> <_> <_>6 3 6 2 -1. <_>8 3 2 2 3. 0 -0.0107799097895622 -0.6392217874526978 0.0332045406103134 <_> <_> <_>4 4 9 3 -1. <_>7 4 3 3 3. 0 -4.3590869754552841e-003 0.1610738933086395 -0.1522132009267807 <_> <_> <_>0 13 1 4 -1. <_>0 15 1 2 2. 0 7.4596069753170013e-003 0.0331729613244534 -0.7500774264335632 <_> <_> <_>0 17 8 3 -1. <_>0 18 8 1 3. 0 8.1385448575019836e-003 0.0263252798467875 -0.7173116207122803 <_> <_> <_>6 1 11 6 -1. <_>6 3 11 2 3. 0 -0.0333384908735752 0.3353661000728607 -0.0708035901188850 -1.1189440488815308 8 -1 <_> <_> <_> <_>4 10 6 2 -1. <_>6 10 2 2 3. 0 0.0195539798587561 -0.1043972000479698 0.5312895178794861 <_> <_> <_>10 8 1 12 -1. <_>10 14 1 6 2. 0 0.0221229195594788 -0.2474727034568787 0.2084725052118301 <_> <_> <_>5 8 3 4 -1. <_>6 8 1 4 3. 0 -4.1829389519989491e-003 0.3828943967819214 -0.1471157968044281 <_> <_> <_>0 17 1 3 -1. <_>0 18 1 1 3. 0 -8.6381728760898113e-004 -0.6263288855552673 0.1199325993657112 <_> <_> <_>0 17 1 3 -1. <_>0 18 1 1 3. 0 7.9958612332120538e-004 0.0925734713673592 -0.5516883134841919 <_> <_> <_>13 8 3 4 -1. <_>14 8 1 4 3. 0 9.1527570039033890e-003 -0.0729298070073128 0.5551251173019409 <_> <_> <_>1 5 5 4 -1. <_>1 7 5 2 2. 0 -3.9388681761920452e-003 0.2019603997468948 -0.2091203927993774 <_> <_> <_>18 14 1 2 -1. <_>18 15 1 1 2. 0 1.4613410166930407e-004 -0.2786181867122650 0.1381741017103195 <_> <_> <_>13 8 2 4 -1. <_>14 8 1 4 2. 0 -3.1691689509898424e-003 0.3668589890003204 -0.0763082429766655 <_> <_> <_>10 6 6 8 -1. <_>12 6 2 8 3. 0 -0.0221893899142742 0.3909659981727600 -0.1097154021263123 <_> <_> <_>8 6 6 10 -1. <_>10 6 2 10 3. 0 -7.4523608200252056e-003 0.1283859014511108 -0.2415986955165863 <_> <_> <_>17 16 1 3 -1. <_>17 17 1 1 3. 0 7.7997002517804503e-004 0.0719780698418617 -0.4397650063037872 <_> <_> <_>1 7 2 10 -1. <_>2 7 1 10 2. 0 -4.6783639118075371e-003 0.2156984955072403 -0.1420592069625855 <_> <_> <_>5 9 6 3 -1. <_>7 9 2 3 3. 0 -0.0151886399835348 0.3645878136157990 -0.0826759263873100 <_> <_> <_>0 8 5 12 -1. <_>0 14 5 6 2. 0 5.0619798712432384e-003 -0.3438040912151337 0.0920682325959206 <_> <_> <_>0 11 1 3 -1. <_>0 12 1 1 3. 0 -1.7351920250803232e-003 -0.6172549724578857 0.0492144785821438 <_> <_> <_>6 16 6 4 -1. <_>8 16 2 4 3. 0 -0.0124234501272440 -0.5855895280838013 0.0461126007139683 <_> <_> <_>0 6 2 6 -1. <_>0 8 2 2 3. 0 -0.0130314296111465 -0.5971078872680664 0.0406724587082863 <_> <_> <_>11 18 2 1 -1. <_>12 18 1 1 2. 0 -1.2369629694148898e-003 -0.6833416819572449 0.0331561788916588 <_> <_> <_>5 1 9 2 -1. <_>5 2 9 1 2. 0 6.1022108420729637e-003 -0.0947292372584343 0.3010224103927612 <_> <_> <_>0 0 1 2 -1. <_>0 1 1 1 2. 0 6.6952849738299847e-004 0.0818168669939041 -0.3519603013992310 <_> <_> <_>15 9 3 3 -1. <_>16 9 1 3 3. 0 -1.7970580374822021e-003 0.2371897995471954 -0.1176870986819267 <_> <_> <_>18 16 1 3 -1. <_>18 17 1 1 3. 0 -7.1074528386816382e-004 -0.4476378858089447 0.0576824806630611 <_> <_> <_>11 10 6 1 -1. <_>13 10 2 1 3. 0 -5.9126471169292927e-003 0.4342541098594666 -0.0668685734272003 <_> <_> <_>1 3 4 4 -1. <_>3 3 2 4 2. 0 -3.3132149837911129e-003 0.1815001070499420 -0.1418032050132752 <_> <_> <_>11 2 1 18 -1. <_>11 8 1 6 3. 0 -0.0608146600425243 0.4722171127796173 -0.0614106394350529 <_> <_> <_>9 1 5 12 -1. <_>9 5 5 4 3. 0 -0.0967141836881638 0.2768316864967346 -0.0944900363683701 <_> <_> <_>12 0 8 1 -1. <_>16 0 4 1 2. 0 3.9073550142347813e-003 -0.1227853000164032 0.2105740010738373 <_> <_> <_>8 6 3 10 -1. <_>9 6 1 10 3. 0 -9.0431869029998779e-003 0.3564156889915466 -0.0778062269091606 <_> <_> <_>19 2 1 6 -1. <_>19 4 1 2 3. 0 -4.8800031654536724e-003 -0.4103479087352753 0.0696943774819374 <_> <_> <_>18 6 2 2 -1. <_>18 7 2 1 2. 0 -4.3547428213059902e-003 -0.7301788926124573 0.0366551503539085 <_> <_> <_>7 7 3 4 -1. <_>8 7 1 4 3. 0 -9.6500627696514130e-003 0.5518112778663635 -0.0531680807471275 <_> <_> <_>5 0 6 5 -1. <_>7 0 2 5 3. 0 -0.0173973105847836 -0.5708423256874085 0.0502140894532204 <_> <_> <_>0 3 7 3 -1. <_>0 4 7 1 3. 0 -6.8304329179227352e-003 -0.4618028104305267 0.0502026900649071 <_> <_> <_>1 6 2 1 -1. <_>2 6 1 1 2. 0 3.3255619928240776e-004 -0.0953627303242683 0.2598375976085663 <_> <_> <_>4 8 2 10 -1. <_>4 8 1 5 2. <_>5 13 1 5 2. 0 -2.3100529797375202e-003 0.2287247031927109 -0.1053353026509285 <_> <_> <_>2 18 18 2 -1. <_>2 18 9 1 2. <_>11 19 9 1 2. 0 -7.5426651164889336e-003 -0.5699051022529602 0.0488634593784809 <_> <_> <_>2 7 4 4 -1. <_>2 7 2 2 2. <_>4 9 2 2 2. 0 -5.2723060362040997e-003 0.3514518141746521 -0.0823901072144508 <_> <_> <_>17 3 3 4 -1. <_>18 3 1 4 3. 0 -4.8578968271613121e-003 -0.6041762232780457 0.0445394404232502 <_> <_> <_>16 9 2 8 -1. <_>16 9 1 4 2. <_>17 13 1 4 2. 0 1.5867310576140881e-003 -0.1034090965986252 0.2328201979398727 <_> <_> <_>15 7 1 6 -1. <_>15 9 1 2 3. 0 -4.7427811659872532e-003 0.2849028110504150 -0.0980904996395111 <_> <_> <_>14 2 2 2 -1. <_>14 3 2 1 2. 0 -1.3515240279957652e-003 0.2309643030166626 -0.1136184036731720 <_> <_> <_>17 0 2 3 -1. <_>17 1 2 1 3. 0 2.2526069078594446e-003 0.0644783228635788 -0.4220589101314545 <_> <_> <_>16 18 2 2 -1. <_>16 18 1 1 2. <_>17 19 1 1 2. 0 -3.8038659840822220e-004 -0.3807620108127594 0.0600432902574539 <_> <_> <_>10 4 4 3 -1. <_>10 5 4 1 3. 0 4.9043921753764153e-003 -0.0761049985885620 0.3323217034339905 <_> <_> <_>0 2 8 6 -1. <_>4 2 4 6 2. 0 -9.0969670563936234e-003 0.1428779065608978 -0.1688780039548874 <_> <_> <_>7 14 6 6 -1. <_>7 16 6 2 3. 0 -6.9317929446697235e-003 0.2725540995597839 -0.0928795635700226 <_> <_> <_>11 15 2 2 -1. <_>11 16 2 1 2. 0 1.1471060570329428e-003 -0.1527305990457535 0.1970240026712418 <_> <_> <_>7 1 9 4 -1. <_>10 1 3 4 3. 0 -0.0376628898084164 -0.5932043790817261 0.0407386012375355 <_> <_> <_>9 7 3 7 -1. <_>10 7 1 7 3. 0 -6.8165571428835392e-003 0.2549408972263336 -0.0940819606184959 <_> <_> <_>6 17 2 2 -1. <_>6 17 1 1 2. <_>7 18 1 1 2. 0 6.6205562325194478e-004 0.0467957183718681 -0.4845437109470367 <_> <_> <_>4 6 3 9 -1. <_>5 6 1 9 3. 0 -4.2202551849186420e-003 0.2468214929103851 -0.0946739763021469 <_> <_> <_>0 10 19 10 -1. <_>0 15 19 5 2. 0 -0.0689865127205849 -0.6651480197906494 0.0359263904392719 <_> <_> <_>5 17 6 1 -1. <_>7 17 2 1 3. 0 6.1707608401775360e-003 0.0258333198726177 -0.7268627285957336 <_> <_> <_>0 12 6 3 -1. <_>3 12 3 3 2. 0 0.0105362497270107 -0.0818289965391159 0.2976079881191254 -1.1418989896774292 9 -1 <_> <_> <_> <_>2 5 18 5 -1. <_>8 5 6 5 3. 0 -0.0627587288618088 0.2789908051490784 -0.2965610921382904 <_> <_> <_>1 15 6 4 -1. <_>1 17 6 2 2. 0 3.4516479354351759e-003 -0.3463588058948517 0.2090384066104889 <_> <_> <_>14 10 6 6 -1. <_>16 10 2 6 3. 0 -7.8699486330151558e-003 0.2414488941431046 -0.1920557022094727 <_> <_> <_>0 14 4 3 -1. <_>0 15 4 1 3. 0 -3.4624869003891945e-003 -0.5915178060531616 0.1248644962906838 <_> <_> <_>1 7 6 11 -1. <_>3 7 2 11 3. 0 -9.4818761572241783e-003 0.1839154064655304 -0.2485826015472412 <_> <_> <_>13 17 7 2 -1. <_>13 18 7 1 2. 0 2.3226840130519122e-004 -0.3304725885391235 0.1099926009774208 <_> <_> <_>0 14 2 3 -1. <_>0 15 2 1 3. 0 1.8101120367646217e-003 0.0987440124154091 -0.4963478147983551 <_> <_> <_>0 0 6 2 -1. <_>3 0 3 2 2. 0 -5.4422430694103241e-003 0.2934441864490509 -0.1309475004673004 <_> <_> <_>0 1 6 3 -1. <_>3 1 3 3 2. 0 7.4148122221231461e-003 -0.1476269960403442 0.3327716886997223 <_> <_> <_>0 8 2 6 -1. <_>0 10 2 2 3. 0 -0.0155651401728392 -0.6840490102767944 0.0998726934194565 <_> <_> <_>1 2 6 14 -1. <_>1 2 3 7 2. <_>4 9 3 7 2. 0 0.0287205204367638 -0.1483328044414520 0.3090257942676544 <_> <_> <_>17 5 2 2 -1. <_>17 5 1 1 2. <_>18 6 1 1 2. 0 9.6687392215244472e-005 -0.1743104010820389 0.2140295952558518 <_> <_> <_>11 10 9 4 -1. <_>14 10 3 4 3. 0 0.0523710586130619 -0.0701568573713303 0.4922299087047577 <_> <_> <_>2 9 12 4 -1. <_>6 9 4 4 3. 0 -0.0864856913685799 0.5075724720954895 -0.0752942115068436 <_> <_> <_>7 10 12 2 -1. <_>11 10 4 2 3. 0 -0.0421698689460754 0.4568096101284027 -0.0902199000120163 <_> <_> <_>2 13 1 2 -1. <_>2 14 1 1 2. 0 4.5369830331765115e-005 -0.2653827965259552 0.1618953943252564 <_> <_> <_>16 7 4 3 -1. <_>16 8 4 1 3. 0 5.2918000146746635e-003 0.0748901516199112 -0.5405467152595520 <_> <_> <_>19 16 1 3 -1. <_>19 17 1 1 3. 0 -7.5511651812121272e-004 -0.4926199018955231 0.0587239488959312 <_> <_> <_>18 11 1 2 -1. <_>18 12 1 1 2. 0 7.5108138844370842e-005 -0.2143210023641586 0.1407776027917862 <_> <_> <_>12 7 8 2 -1. <_>12 7 4 1 2. <_>16 8 4 1 2. 0 4.9981209449470043e-003 -0.0905473381280899 0.3571606874465942 <_> <_> <_>14 9 2 4 -1. <_>15 9 1 4 2. 0 -1.4929979806765914e-003 0.2562345862388611 -0.1422906965017319 <_> <_> <_>14 2 6 4 -1. <_>14 2 3 2 2. <_>17 4 3 2 2. 0 2.7239411137998104e-003 -0.1564925014972687 0.2108871042728424 <_> <_> <_>14 0 6 1 -1. <_>17 0 3 1 2. 0 2.2218320518732071e-003 -0.1507298946380615 0.2680186927318573 <_> <_> <_>3 12 2 1 -1. <_>4 12 1 1 2. 0 -7.3993072146549821e-004 0.2954699099063873 -0.1069239005446434 <_> <_> <_>17 2 3 1 -1. <_>18 2 1 1 3. 0 2.0113459322601557e-003 0.0506143495440483 -0.7168337106704712 <_> <_> <_>1 16 18 2 -1. <_>7 16 6 2 3. 0 0.0114528704434633 -0.1271906942129135 0.2415277957916260 <_> <_> <_>2 19 8 1 -1. <_>6 19 4 1 2. 0 -1.0782170575112104e-003 0.2481300979852676 -0.1346119940280914 <_> <_> <_>1 17 4 3 -1. <_>1 18 4 1 3. 0 3.3417691010981798e-003 0.0535783097147942 -0.5227416753768921 <_> <_> <_>19 13 1 2 -1. <_>19 14 1 1 2. 0 6.9398651248775423e-005 -0.2169874012470245 0.1281217932701111 <_> <_> <_>9 16 10 4 -1. <_>9 16 5 2 2. <_>14 18 5 2 2. 0 -4.0982551872730255e-003 0.2440188974142075 -0.1157058998942375 <_> <_> <_>12 9 2 4 -1. <_>12 9 1 2 2. <_>13 11 1 2 2. 0 -1.6289720078930259e-003 0.2826147079467773 -0.1065946966409683 <_> <_> <_>19 11 1 9 -1. <_>19 14 1 3 3. 0 0.0139848599210382 0.0427158996462822 -0.7364631295204163 -1.1255199909210205 10 -1 <_> <_> <_> <_>6 6 14 14 -1. <_>6 13 14 7 2. 0 0.1641651988029480 -0.4896030128002167 0.1760770976543427 <_> <_> <_>2 17 4 2 -1. <_>2 18 4 1 2. 0 8.3413062384352088e-004 -0.2822043001651764 0.2419957965612412 <_> <_> <_>0 2 1 3 -1. <_>0 3 1 1 3. 0 -1.7193210078403354e-003 -0.7148588895797730 0.0861622169613838 <_> <_> <_>0 12 1 3 -1. <_>0 13 1 1 3. 0 -1.5654950402677059e-003 -0.7297238111495972 0.0940706729888916 <_> <_> <_>15 15 4 4 -1. <_>15 17 4 2 2. 0 1.9124479731544852e-003 -0.3118715882301331 0.1814339011907578 <_> <_> <_>2 5 18 7 -1. <_>8 5 6 7 3. 0 -0.1351236999034882 0.2957729995250702 -0.2217925041913986 <_> <_> <_>1 16 5 3 -1. <_>1 17 5 1 3. 0 -4.0300549007952213e-003 -0.6659513711929321 0.0854310169816017 <_> <_> <_>0 4 2 3 -1. <_>0 5 2 1 3. 0 -2.8640460222959518e-003 -0.6208636164665222 0.0531060211360455 <_> <_> <_>0 6 2 6 -1. <_>1 6 1 6 2. 0 -1.4065420255064964e-003 0.2234628945589066 -0.2021100968122482 <_> <_> <_>16 14 4 3 -1. <_>16 15 4 1 3. 0 -3.5820449702441692e-003 -0.5403040051460266 0.0682136192917824 <_> <_> <_>0 0 10 6 -1. <_>0 0 5 3 2. <_>5 3 5 3 2. 0 0.0415444709360600 -0.0652158409357071 0.6210923194885254 <_> <_> <_>2 2 3 6 -1. <_>3 2 1 6 3. 0 -9.1709550470113754e-003 -0.7555329799652100 0.0526404492557049 <_> <_> <_>2 0 3 10 -1. <_>3 0 1 10 3. 0 6.1552738770842552e-003 0.0909394025802612 -0.4424613118171692 <_> <_> <_>5 5 2 2 -1. <_>5 6 2 1 2. 0 -1.0043520014733076e-003 0.2429233044385910 -0.1866979002952576 <_> <_> <_>12 6 4 4 -1. <_>12 8 4 2 2. 0 0.0115198297426105 -0.1176315024495125 0.3672345876693726 <_> <_> <_>13 5 7 3 -1. <_>13 6 7 1 3. 0 -8.9040733873844147e-003 -0.4893133044242859 0.1089702025055885 <_> <_> <_>10 13 1 2 -1. <_>10 14 1 1 2. 0 5.3973670583218336e-004 -0.2185039967298508 0.1848998963832855 <_> <_> <_>16 16 4 2 -1. <_>18 16 2 2 2. 0 1.3727260520681739e-003 -0.1507291048765183 0.2917312979698181 <_> <_> <_>16 12 4 7 -1. <_>18 12 2 7 2. 0 -0.0108073903247714 0.4289745092391968 -0.1028013974428177 <_> <_> <_>16 17 1 3 -1. <_>16 18 1 1 3. 0 1.2670770520344377e-003 0.0741921588778496 -0.6420825123786926 <_> <_> <_>19 9 1 3 -1. <_>19 10 1 1 3. 0 2.2991129662841558e-003 0.0471002794802189 -0.7233523130416870 <_> <_> <_>18 7 2 6 -1. <_>19 7 1 6 2. 0 2.7187510859221220e-003 -0.1708686947822571 0.2351350933313370 <_> <_> <_>8 1 3 4 -1. <_>9 1 1 4 3. 0 -6.6619180142879486e-003 -0.7897542715072632 0.0450846701860428 <_> <_> <_>14 0 6 9 -1. <_>16 0 2 9 3. 0 -0.0482666492462158 -0.6957991719245911 0.0419760793447495 <_> <_> <_>4 2 10 2 -1. <_>9 2 5 2 2. 0 0.0152146900072694 -0.1081828027963638 0.3646062016487122 <_> <_> <_>2 12 8 4 -1. <_>2 12 4 2 2. <_>6 14 4 2 2. 0 -6.0080131515860558e-003 0.3097099065780640 -0.1135921031236649 <_> <_> <_>0 4 7 3 -1. <_>0 5 7 1 3. 0 6.6127157770097256e-003 0.0806653425097466 -0.4665853083133698 <_> <_> <_>14 14 3 3 -1. <_>15 14 1 3 3. 0 -7.9607013612985611e-003 -0.8720194101333618 0.0367745906114578 <_> <_> <_>0 3 4 3 -1. <_>2 3 2 3 2. 0 3.8847199175506830e-003 -0.1166628971695900 0.3307026922702789 <_> <_> <_>1 0 2 7 -1. <_>2 0 1 7 2. 0 -1.0988810099661350e-003 0.2387257069349289 -0.1765675991773605 -1.1729990243911743 11 -1 <_> <_> <_> <_>15 16 4 4 -1. <_>15 18 4 2 2. 0 3.5903379321098328e-003 -0.2368807941675186 0.2463164031505585 <_> <_> <_>5 8 12 4 -1. <_>5 10 12 2 2. 0 6.4815930090844631e-003 -0.3137362003326416 0.1867575943470001 <_> <_> <_>3 17 1 2 -1. <_>3 18 1 1 2. 0 7.3048402555286884e-005 -0.2764435112476349 0.1649623960256577 <_> <_> <_>6 1 3 4 -1. <_>7 1 1 4 3. 0 -3.8514640182256699e-003 -0.5601450800895691 0.1129473969340324 <_> <_> <_>6 2 3 4 -1. <_>7 2 1 4 3. 0 3.8588210009038448e-003 0.0398489981889725 -0.5807185769081116 <_> <_> <_>6 8 9 12 -1. <_>9 8 3 12 3. 0 -0.0246512200683355 0.1675501018762589 -0.2534367144107819 <_> <_> <_>8 1 8 6 -1. <_>8 3 8 2 3. 0 0.0472455210983753 -0.1066208034753799 0.3945198059082031 <_> <_> <_>14 2 6 3 -1. <_>17 2 3 3 2. 0 6.5964651294052601e-003 -0.1774425059556961 0.2728019058704376 <_> <_> <_>0 6 1 3 -1. <_>0 7 1 1 3. 0 -1.3177490327507257e-003 -0.5427265167236328 0.0486065894365311 <_> <_> <_>10 0 10 2 -1. <_>15 0 5 2 2. 0 -5.0261709839105606e-003 0.2439424991607666 -0.1314364969730377 <_> <_> <_>11 0 3 2 -1. <_>12 0 1 2 3. 0 3.4632768947631121e-003 0.0690493434667587 -0.7033624053001404 <_> <_> <_>3 19 10 1 -1. <_>8 19 5 1 2. 0 2.1692588925361633e-003 -0.1328946053981781 0.2209852933883667 <_> <_> <_>0 4 7 16 -1. <_>0 12 7 8 2. 0 0.0293958708643913 -0.2853052020072937 0.1354399025440216 <_> <_> <_>2 16 1 3 -1. <_>2 17 1 1 3. 0 -9.6181448316201568e-004 -0.5804138183593750 0.0374506488442421 <_> <_> <_>7 8 12 6 -1. <_>11 8 4 6 3. 0 -0.1082099974155426 0.3946728110313416 -0.0786559432744980 <_> <_> <_>14 9 6 7 -1. <_>16 9 2 7 3. 0 -0.0180248692631722 0.2735562920570374 -0.1341529935598373 <_> <_> <_>12 17 6 1 -1. <_>14 17 2 1 3. 0 6.2509840354323387e-003 0.0233880598098040 -0.8008859157562256 <_> <_> <_>16 1 3 1 -1. <_>17 1 1 1 3. 0 -1.6088379779830575e-003 -0.5676252245903015 0.0412156693637371 <_> <_> <_>0 17 8 2 -1. <_>0 17 4 1 2. <_>4 18 4 1 2. 0 7.7564752427861094e-004 -0.1489126980304718 0.1908618062734604 <_> <_> <_>17 0 2 1 -1. <_>18 0 1 1 2. 0 8.7122338300105184e-005 -0.1555753052234650 0.1942822039127350 <_> <_> <_>4 15 6 5 -1. <_>6 15 2 5 3. 0 -0.0207553207874298 -0.6300653219223023 0.0361343808472157 <_> <_> <_>7 2 8 2 -1. <_>7 3 8 1 2. 0 -6.2931738793849945e-003 0.2560924887657166 -0.1058826968073845 <_> <_> <_>4 1 8 4 -1. <_>4 3 8 2 2. 0 0.0108441496267915 -0.1012485027313232 0.3032212853431702 <_> <_> <_>5 19 2 1 -1. <_>6 19 1 1 2. 0 -6.3752777350600809e-005 0.1911157965660095 -0.1384923011064529 <_> <_> <_>5 19 2 1 -1. <_>6 19 1 1 2. 0 6.6480963141657412e-005 -0.1520525068044663 0.2170630991458893 <_> <_> <_>16 17 1 3 -1. <_>16 18 1 1 3. 0 1.3560829684138298e-003 0.0494317896664143 -0.6427984237670898 <_> <_> <_>0 11 2 3 -1. <_>1 11 1 3 2. 0 -9.0662558795884252e-004 0.1798201054334641 -0.1404460966587067 <_> <_> <_>0 19 4 1 -1. <_>2 19 2 1 2. 0 1.0473709553480148e-003 -0.1093354970216751 0.2426594048738480 <_> <_> <_>0 18 4 2 -1. <_>2 18 2 2 2. 0 -1.0243969736620784e-003 0.2716268002986908 -0.1182091981172562 <_> <_> <_>2 17 1 3 -1. <_>2 18 1 1 3. 0 -1.2024149764329195e-003 -0.7015110254287720 0.0394898988306522 <_> <_> <_>5 7 11 2 -1. <_>5 8 11 1 2. 0 7.6911649666726589e-003 -0.0922189131379128 0.3104628920555115 <_> <_> <_>9 2 4 10 -1. <_>9 7 4 5 2. 0 -0.1396654993295670 0.6897938847541809 -0.0397061184048653 <_> <_> <_>0 2 4 3 -1. <_>0 3 4 1 3. 0 2.1276050247251987e-003 0.0972776114940643 -0.2884179949760437 <_> <_> <_>10 19 10 1 -1. <_>15 19 5 1 2. 0 -2.7594310231506824e-003 0.2416867017745972 -0.1127782016992569 <_> <_> <_>11 17 8 3 -1. <_>15 17 4 3 2. 0 5.2236132323741913e-003 -0.1143027991056442 0.2425678074359894 <_> <_> <_>8 19 3 1 -1. <_>9 19 1 1 3. 0 -1.2590440455824137e-003 -0.5967938899993897 0.0476639606058598 <_> <_> <_>14 0 3 4 -1. <_>15 0 1 4 3. 0 -3.7192099262028933e-003 -0.4641413092613220 0.0528476908802986 <_> <_> <_>10 6 4 3 -1. <_>10 7 4 1 3. 0 5.9696151874959469e-003 -0.0732442885637283 0.3874309062957764 <_> <_> <_>0 8 3 2 -1. <_>0 9 3 1 2. 0 -5.1776720210909843e-003 -0.7419322729110718 0.0404967106878757 <_> <_> <_>7 12 3 6 -1. <_>7 14 3 2 3. 0 5.0035100430250168e-003 -0.1388880014419556 0.1876762062311173 <_> <_> <_>1 18 1 2 -1. <_>1 19 1 1 2. 0 -5.2013457752764225e-004 -0.5494061708450317 0.0494178496301174 <_> <_> <_>0 12 4 4 -1. <_>2 12 2 4 2. 0 5.3168768063187599e-003 -0.0824829787015915 0.3174056112766266 <_> <_> <_>1 8 6 7 -1. <_>3 8 2 7 3. 0 -0.0147745897993445 0.2081609964370728 -0.1211555972695351 <_> <_> <_>0 8 4 5 -1. <_>2 8 2 5 2. 0 -0.0414164513349533 -0.8243780732154846 0.0333291888237000 -1.0368299484252930 12 -1 <_> <_> <_> <_>19 16 1 3 -1. <_>19 17 1 1 3. 0 9.0962520334869623e-004 0.0845799669623375 -0.5611841082572937 <_> <_> <_>1 5 18 6 -1. <_>7 5 6 6 3. 0 -0.0561397895216942 0.1534174978733063 -0.2696731984615326 <_> <_> <_>2 15 4 2 -1. <_>2 16 4 1 2. 0 1.0292009683325887e-003 -0.2048998028039932 0.2015317976474762 <_> <_> <_>18 6 2 11 -1. <_>19 6 1 11 2. 0 2.8783010784536600e-003 -0.1735114008188248 0.2129794955253601 <_> <_> <_>0 12 2 6 -1. <_>0 14 2 2 3. 0 -7.4144392274320126e-003 -0.5962486863136292 0.0470779500901699 <_> <_> <_>12 5 3 2 -1. <_>12 6 3 1 2. 0 -1.4831849839538336e-003 0.1902461051940918 -0.1598639041185379 <_> <_> <_>1 3 2 3 -1. <_>1 4 2 1 3. 0 4.5968941412866116e-003 0.0314471311867237 -0.6869434118270874 <_> <_> <_>16 14 4 4 -1. <_>16 16 4 2 2. 0 2.4255330208688974e-003 -0.2360935956239700 0.1103610992431641 <_> <_> <_>6 8 12 5 -1. <_>10 8 4 5 3. 0 -0.0849505662918091 0.2310716062784195 -0.1377653032541275 <_> <_> <_>13 7 2 7 -1. <_>14 7 1 7 2. 0 -5.0145681016147137e-003 0.3867610991001129 -0.0562173798680305 <_> <_> <_>1 8 2 6 -1. <_>2 8 1 6 2. 0 -2.1482061129063368e-003 0.1819159984588623 -0.1761569976806641 <_> <_> <_>15 0 3 7 -1. <_>16 0 1 7 3. 0 -0.0103967702016234 -0.7535138130187988 0.0240919701755047 <_> <_> <_>4 2 6 2 -1. <_>6 2 2 2 3. 0 -0.0134667502716184 -0.7211886048316956 0.0349493697285652 <_> <_> <_>0 9 20 9 -1. <_>0 12 20 3 3. 0 -0.0844354778528214 -0.3379263877868652 0.0711138173937798 <_> <_> <_>10 14 2 2 -1. <_>10 15 2 1 2. 0 2.4771490134298801e-003 -0.1176510974764824 0.2254198938608170 <_> <_> <_>6 5 10 4 -1. <_>6 7 10 2 2. 0 0.0158280506730080 -0.0695362165570259 0.3139536976814270 <_> <_> <_>6 1 5 9 -1. <_>6 4 5 3 3. 0 0.0649169832468033 -0.0750435888767242 0.4067733883857727 <_> <_> <_>16 18 2 2 -1. <_>16 18 1 1 2. <_>17 19 1 1 2. 0 2.9652469675056636e-004 0.0739533603191376 -0.3454400897026062 <_> <_> <_>0 14 2 4 -1. <_>0 16 2 2 2. 0 1.3129520229995251e-003 -0.1690943986177445 0.1525837033987045 <_> <_> <_>10 8 2 5 -1. <_>11 8 1 5 2. 0 -5.8032129891216755e-003 0.3526014983654022 -0.0834440663456917 <_> <_> <_>3 7 12 7 -1. <_>7 7 4 7 3. 0 -0.1479167938232422 0.4300465881824493 -0.0573099292814732 <_> <_> <_>0 0 6 6 -1. <_>3 0 3 6 2. 0 -0.0165841504931450 0.2343268990516663 -0.1090764030814171 <_> <_> <_>1 0 4 4 -1. <_>3 0 2 4 2. 0 3.0183270573616028e-003 -0.1360093951225281 0.2640928924083710 <_> <_> <_>0 0 6 8 -1. <_>2 0 2 8 3. 0 -0.0364719182252884 -0.6280974149703980 0.0435451082885265 <_> <_> <_>0 0 2 1 -1. <_>1 0 1 1 2. 0 -7.3119226726703346e-005 0.1647063046693802 -0.1646378040313721 <_> <_> <_>0 0 3 3 -1. <_>0 1 3 1 3. 0 -3.6719450727105141e-003 -0.4742136001586914 0.0485869199037552 <_> <_> <_>5 4 2 4 -1. <_>5 6 2 2 2. 0 -4.0151178836822510e-003 0.1822218000888825 -0.1409751027822495 <_> <_> <_>2 10 9 1 -1. <_>5 10 3 1 3. 0 0.0199480205774307 -0.0697876587510109 0.3670746088027954 <_> <_> <_>1 17 1 3 -1. <_>1 18 1 1 3. 0 7.6699437340721488e-004 0.0557292997837067 -0.4458543062210083 <_> <_> <_>0 17 2 3 -1. <_>0 18 2 1 3. 0 -1.1806039838120341e-003 -0.4687662124633789 0.0489022210240364 <_> <_> <_>0 15 16 3 -1. <_>8 15 8 3 2. 0 0.0158473495393991 -0.1212020963430405 0.2056653052568436 <_> <_> <_>0 5 4 1 -1. <_>2 5 2 1 2. 0 -1.1985700111836195e-003 0.2026209980249405 -0.1282382011413574 <_> <_> <_>1 0 6 20 -1. <_>3 0 2 20 3. 0 -0.1096495985984802 -0.8661919236183167 0.0303518492728472 <_> <_> <_>2 5 4 6 -1. <_>2 5 2 3 2. <_>4 8 2 3 2. 0 -9.2532606795430183e-003 0.2934311926364899 -0.0853619500994682 <_> <_> <_>9 16 6 3 -1. <_>11 16 2 3 3. 0 0.0146865304559469 0.0327986218035221 -0.7755656242370606 <_> <_> <_>11 17 6 1 -1. <_>14 17 3 1 2. 0 -1.3514430029317737e-003 0.2442699968814850 -0.1150325015187264 <_> <_> <_>3 17 15 2 -1. <_>8 17 5 2 3. 0 -4.3728090822696686e-003 0.2168767005205154 -0.1398448050022125 <_> <_> <_>18 0 2 3 -1. <_>18 1 2 1 3. 0 3.4263390116393566e-003 0.0456142202019691 -0.5456771254539490 <_> <_> <_>13 1 7 4 -1. <_>13 3 7 2 2. 0 -3.8404068909585476e-003 0.1494950056076050 -0.1506250947713852 <_> <_> <_>13 6 4 4 -1. <_>13 6 2 2 2. <_>15 8 2 2 2. 0 3.7988980766385794e-003 -0.0873016268014908 0.2548153102397919 <_> <_> <_>17 6 3 4 -1. <_>17 8 3 2 2. 0 -2.0094281062483788e-003 0.1725907027721405 -0.1428847014904022 <_> <_> <_>14 9 2 2 -1. <_>15 9 1 2 2. 0 -2.4370709434151649e-003 0.2684809863567352 -0.0818982198834419 <_> <_> <_>17 17 1 3 -1. <_>17 18 1 1 3. 0 1.0485399980098009e-003 0.0461132600903511 -0.4724327921867371 <_> <_> <_>3 19 8 1 -1. <_>7 19 4 1 2. 0 1.7460780218243599e-003 -0.1103043034672737 0.2037972956895828 <_> <_> <_>0 9 3 6 -1. <_>0 12 3 3 2. 0 5.8608627878129482e-003 -0.1561965942382813 0.1592743992805481 <_> <_> <_>4 7 15 5 -1. <_>9 7 5 5 3. 0 -0.0277249794453382 0.1134911999106407 -0.2188514024019241 <_> <_> <_>6 9 9 5 -1. <_>9 9 3 5 3. 0 0.0470806397497654 -0.0416887290775776 0.5363004803657532 <_> <_> <_>8 1 6 2 -1. <_>10 1 2 2 3. 0 -7.9283770173788071e-003 -0.5359513163566589 0.0442375093698502 <_> <_> <_>4 0 12 2 -1. <_>10 0 6 2 2. 0 -0.0128805404528975 0.2323794960975647 -0.1024625003337860 <_> <_> <_>7 0 10 3 -1. <_>12 0 5 3 2. 0 0.0236047692596912 -0.0882914364337921 0.3056105971336365 <_> <_> <_>5 0 9 6 -1. <_>5 2 9 2 3. 0 0.0159022007137537 -0.1223810985684395 0.1784912049770355 <_> <_> <_>8 3 6 4 -1. <_>8 5 6 2 2. 0 7.9939495772123337e-003 -0.0837290063500404 0.3231959044933319 <_> <_> <_>17 4 2 3 -1. <_>17 5 2 1 3. 0 5.7100867852568626e-003 0.0384792089462280 -0.6813815236091614 -1.0492420196533203 13 -1 <_> <_> <_> <_>5 2 4 3 -1. <_>5 3 4 1 3. 0 2.2480720654129982e-003 -0.1641687005758286 0.4164853096008301 <_> <_> <_>5 9 2 6 -1. <_>6 9 1 6 2. 0 4.5813550241291523e-003 -0.1246595978736877 0.4038512110710144 <_> <_> <_>14 10 2 6 -1. <_>15 10 1 6 2. 0 -1.6073239967226982e-003 0.2608245909214020 -0.2028252035379410 <_> <_> <_>7 4 3 3 -1. <_>7 5 3 1 3. 0 2.5205370038747787e-003 -0.1055722981691361 0.3666911125183106 <_> <_> <_>12 4 8 2 -1. <_>12 4 4 1 2. <_>16 5 4 1 2. 0 2.4119189474731684e-003 -0.1387760043144226 0.2995991110801697 <_> <_> <_>15 8 1 6 -1. <_>15 10 1 2 3. 0 5.7156179100275040e-003 -0.0776834636926651 0.4848192036151886 <_> <_> <_>4 17 11 3 -1. <_>4 18 11 1 3. 0 3.1093840952962637e-003 -0.1122900024056435 0.2921550869941711 <_> <_> <_>3 0 16 20 -1. <_>3 10 16 10 2. 0 -0.0868366286158562 -0.3677960038185120 0.0725972428917885 <_> <_> <_>12 4 4 6 -1. <_>12 6 4 2 3. 0 5.2652182057499886e-003 -0.1089029014110565 0.3179126083850861 <_> <_> <_>11 0 6 6 -1. <_>13 0 2 6 3. 0 -0.0199135299772024 -0.5337343811988831 0.0705857127904892 <_> <_> <_>13 1 6 4 -1. <_>13 1 3 2 2. <_>16 3 3 2 2. 0 3.8297839928418398e-003 -0.1357591003179550 0.2278887927532196 <_> <_> <_>11 0 6 4 -1. <_>13 0 2 4 3. 0 0.0104318596422672 0.0887979120016098 -0.4795897006988525 <_> <_> <_>8 6 6 9 -1. <_>10 6 2 9 3. 0 -0.0200404394418001 0.1574553996324539 -0.1777157038450241 <_> <_> <_>7 0 3 4 -1. <_>8 0 1 4 3. 0 -5.2967290394008160e-003 -0.6843491792678833 0.0356714613735676 <_> <_> <_>0 17 14 2 -1. <_>0 17 7 1 2. <_>7 18 7 1 2. 0 -2.1624139044433832e-003 0.2831803858280182 -0.0985112786293030 <_> <_> <_>6 18 2 2 -1. <_>6 18 1 1 2. <_>7 19 1 1 2. 0 -3.5464888787828386e-004 -0.3707734048366547 0.0809329524636269 <_> <_> <_>18 17 1 3 -1. <_>18 18 1 1 3. 0 -1.8152060511056334e-004 -0.3220703005790710 0.0775510594248772 <_> <_> <_>17 18 2 2 -1. <_>17 18 1 1 2. <_>18 19 1 1 2. 0 -2.7563021285459399e-004 -0.3244127929210663 0.0879494771361351 <_> <_> <_>5 7 1 9 -1. <_>5 10 1 3 3. 0 6.3823810778558254e-003 -0.0889247134327888 0.3172721862792969 <_> <_> <_>5 3 6 4 -1. <_>7 3 2 4 3. 0 0.0111509095877409 0.0710198432207108 -0.4049403965473175 <_> <_> <_>1 9 6 2 -1. <_>1 9 3 1 2. <_>4 10 3 1 2. 0 -1.0593760525807738e-003 0.2605066895484924 -0.1176564022898674 <_> <_> <_>6 9 2 3 -1. <_>7 9 1 3 2. 0 2.3906480055302382e-003 -0.0843886211514473 0.3123055100440979 <_> <_> <_>6 8 6 12 -1. <_>8 8 2 12 3. 0 -0.0110007496550679 0.1915224939584732 -0.1521002054214478 <_> <_> <_>4 18 2 2 -1. <_>4 18 1 1 2. <_>5 19 1 1 2. 0 -2.4643228971399367e-004 -0.3176515996456146 0.0865822583436966 <_> <_> <_>9 1 6 6 -1. <_>9 3 6 2 3. 0 0.0230532698333263 -0.1008976027369499 0.2576929032802582 <_> <_> <_>6 17 6 2 -1. <_>6 18 6 1 2. 0 -2.2135660983622074e-003 0.4568921029567719 -0.0524047911167145 <_> <_> <_>3 18 16 2 -1. <_>3 19 16 1 2. 0 -9.7139709396287799e-004 -0.3551838099956513 0.0800943821668625 <_> <_> <_>3 0 3 11 -1. <_>4 0 1 11 3. 0 1.5676229959353805e-003 0.1009142026305199 -0.2160304039716721 <_> <_> <_>13 18 3 1 -1. <_>14 18 1 1 3. 0 7.5460801599547267e-004 0.0578961782157421 -0.4046111106872559 <_> <_> <_>6 0 9 6 -1. <_>6 2 9 2 3. 0 -0.0206989701837301 0.3154363036155701 -0.0807130485773087 <_> <_> <_>1 2 12 4 -1. <_>1 2 6 2 2. <_>7 4 6 2 2. 0 -0.0206199400126934 0.2718166112899780 -0.0763586163520813 <_> <_> <_>3 3 6 4 -1. <_>5 3 2 4 3. 0 0.0216111298650503 0.0394934490323067 -0.5942965149879456 <_> <_> <_>12 0 8 1 -1. <_>16 0 4 1 2. 0 6.5676742233335972e-003 -0.0983536690473557 0.2364927977323532 <_> <_> <_>9 0 6 2 -1. <_>11 0 2 2 3. 0 -8.8434796780347824e-003 -0.5252342820167542 0.0430999211966991 <_> <_> <_>3 3 12 1 -1. <_>9 3 6 1 2. 0 -9.4260741025209427e-003 0.2466513067483902 -0.0941307172179222 <_> <_> <_>2 7 6 2 -1. <_>2 7 3 1 2. <_>5 8 3 1 2. 0 -1.9830230157822371e-003 0.2674370110034943 -0.0900693163275719 <_> <_> <_>0 8 4 6 -1. <_>0 10 4 2 3. 0 -1.7358399927616119e-003 0.1594001948833466 -0.1578941047191620 <_> <_> <_>9 6 3 7 -1. <_>10 6 1 7 3. 0 -0.0135138696059585 0.4079233109951019 -0.0642231181263924 <_> <_> <_>9 6 6 13 -1. <_>11 6 2 13 3. 0 -0.0193940103054047 0.1801564991474152 -0.1373140066862106 <_> <_> <_>11 12 6 1 -1. <_>13 12 2 1 3. 0 -3.2684770412743092e-003 0.2908039093017578 -0.0801619067788124 <_> <_> <_>18 9 2 6 -1. <_>18 12 2 3 2. 0 4.1773589327931404e-004 -0.2141298055648804 0.1127343997359276 <_> <_> <_>17 2 3 9 -1. <_>18 2 1 9 3. 0 -7.6351119205355644e-003 -0.4536595940589905 0.0546250604093075 <_> <_> <_>13 8 4 6 -1. <_>13 8 2 3 2. <_>15 11 2 3 2. 0 -8.3652976900339127e-003 0.2647292017936707 -0.0943341106176376 <_> <_> <_>4 2 12 6 -1. <_>10 2 6 6 2. 0 0.0277684498578310 -0.1013671010732651 0.2074397951364517 <_> <_> <_>4 14 16 6 -1. <_>12 14 8 6 2. 0 -0.0548912286758423 0.2884030938148499 -0.0753120407462120 <_> <_> <_>6 19 10 1 -1. <_>11 19 5 1 2. 0 2.5793339591473341e-003 -0.1108852997422218 0.2172496020793915 <_> <_> <_>6 17 1 3 -1. <_>6 18 1 1 3. 0 6.6196516854688525e-005 -0.1887210011482239 0.1444068998098373 <_> <_> <_>4 14 10 3 -1. <_>4 15 10 1 3. 0 5.0907251425087452e-003 -0.0776012316346169 0.2939837872982025 <_> <_> <_>6 0 12 12 -1. <_>6 4 12 4 3. 0 -0.1044425964355469 0.2013310939073563 -0.1090397015213966 <_> <_> <_>5 7 4 2 -1. <_>5 7 2 1 2. <_>7 8 2 1 2. 0 -6.7273090826347470e-004 0.1794590055942535 -0.1202367022633553 <_> <_> <_>17 5 3 2 -1. <_>18 5 1 2 3. 0 3.2412849832326174e-003 0.0406881310045719 -0.5460057258605957 -1.1122100353240967 14 -1 <_> <_> <_> <_>8 13 6 3 -1. <_>8 14 6 1 3. 0 5.2965320646762848e-003 -0.1215452998876572 0.6442037224769592 <_> <_> <_>8 13 5 3 -1. <_>8 14 5 1 3. 0 -2.5326260365545750e-003 0.5123322010040283 -0.1110825985670090 <_> <_> <_>13 2 1 18 -1. <_>13 11 1 9 2. 0 -2.9183230362832546e-003 -0.5061542987823486 0.1150197982788086 <_> <_> <_>6 10 9 2 -1. <_>9 10 3 2 3. 0 -0.0236923396587372 0.3716728091239929 -0.1467268019914627 <_> <_> <_>11 0 7 4 -1. <_>11 2 7 2 2. 0 0.0201774705201387 -0.1738884001970291 0.4775949120521545 <_> <_> <_>1 0 6 8 -1. <_>3 0 2 8 3. 0 -0.0217232108116150 -0.4388009011745453 0.1357689946889877 <_> <_> <_>9 15 3 3 -1. <_>9 16 3 1 3. 0 2.8369780629873276e-003 -0.1251206994056702 0.4678902924060822 <_> <_> <_>9 17 9 3 -1. <_>9 18 9 1 3. 0 2.7148420922458172e-003 -0.0880188569426537 0.3686651885509491 <_> <_> <_>12 12 3 3 -1. <_>12 13 3 1 3. 0 3.2625689636915922e-003 -0.0853353068232536 0.5164473056793213 <_> <_> <_>4 1 3 5 -1. <_>5 1 1 5 3. 0 -3.5618850961327553e-003 -0.4450393021106720 0.0917381718754768 <_> <_> <_>10 14 2 3 -1. <_>10 15 2 1 3. 0 1.9227749435231090e-003 -0.1107731014490128 0.3941699862480164 <_> <_> <_>18 17 2 2 -1. <_>18 17 1 1 2. <_>19 18 1 1 2. 0 -3.5111969918943942e-004 -0.3777570128440857 0.1216617003083229 <_> <_> <_>18 18 2 2 -1. <_>18 18 1 1 2. <_>19 19 1 1 2. 0 1.9121779769193381e-004 0.0748160183429718 -0.4076710045337677 <_> <_> <_>18 18 2 2 -1. <_>18 18 1 1 2. <_>19 19 1 1 2. 0 -2.6525629800744355e-004 -0.3315171897411346 0.1129112020134926 <_> <_> <_>4 10 9 1 -1. <_>7 10 3 1 3. 0 0.0200867000967264 -0.0615981183946133 0.5612881779670715 <_> <_> <_>3 9 6 5 -1. <_>5 9 2 5 3. 0 0.0367832481861115 -0.0602513886988163 0.5219249129295349 <_> <_> <_>18 8 1 12 -1. <_>18 14 1 6 2. 0 1.3941619545221329e-003 -0.3550305068492889 0.1086302027106285 <_> <_> <_>0 2 8 6 -1. <_>0 2 4 3 2. <_>4 5 4 3 2. 0 -0.0151816699653864 0.2273965030908585 -0.1625299006700516 <_> <_> <_>9 4 3 3 -1. <_>9 5 3 1 3. 0 4.6796840615570545e-003 -0.0575350411236286 0.4812423884868622 <_> <_> <_>3 18 2 2 -1. <_>3 18 1 1 2. <_>4 19 1 1 2. 0 -1.7988319450523704e-004 -0.3058767020702362 0.1086815968155861 <_> <_> <_>6 4 4 3 -1. <_>6 5 4 1 3. 0 -3.5850999411195517e-003 0.3859694004058838 -0.0921940729022026 <_> <_> <_>16 7 4 2 -1. <_>16 7 2 1 2. <_>18 8 2 1 2. 0 1.0793360415846109e-003 -0.1119038984179497 0.3112520873546600 <_> <_> <_>5 17 1 3 -1. <_>5 18 1 1 3. 0 7.3285802500322461e-005 -0.2023991048336029 0.1558668017387390 <_> <_> <_>2 0 15 20 -1. <_>2 10 15 10 2. 0 0.1367873996496201 -0.2167285978794098 0.1442039012908936 <_> <_> <_>8 11 6 4 -1. <_>8 11 3 2 2. <_>11 13 3 2 2. 0 -0.0117292599752545 0.4350377023220062 -0.0748865306377411 <_> <_> <_>8 16 4 3 -1. <_>8 17 4 1 3. 0 3.9230841211974621e-003 -0.0502893291413784 0.5883116126060486 <_> <_> <_>8 18 2 2 -1. <_>8 18 1 1 2. <_>9 19 1 1 2. 0 -2.9819121118634939e-004 -0.3823240101337433 0.0924511328339577 <_> <_> <_>2 16 13 3 -1. <_>2 17 13 1 3. 0 -4.7992770560085773e-003 0.4848878979682922 -0.0731365233659744 <_> <_> <_>16 16 2 2 -1. <_>16 16 1 1 2. <_>17 17 1 1 2. 0 -3.0155890271998942e-004 -0.3575735986232758 0.1058188006281853 <_> <_> <_>8 1 6 3 -1. <_>10 1 2 3 3. 0 0.0103907696902752 0.0529204681515694 -0.5724965929985046 <_> <_> <_>16 7 2 2 -1. <_>16 7 1 1 2. <_>17 8 1 1 2. 0 -9.4488041941076517e-004 0.4496682882308960 -0.0830755233764648 <_> <_> <_>14 7 4 2 -1. <_>14 7 2 1 2. <_>16 8 2 1 2. 0 1.2651870492845774e-003 -0.0966954380273819 0.3130227029323578 <_> <_> <_>4 0 14 1 -1. <_>11 0 7 1 2. 0 0.0170945394784212 -0.0812489762902260 0.3611383140087128 <_> <_> <_>10 4 8 2 -1. <_>10 4 4 1 2. <_>14 5 4 1 2. 0 2.5973359588533640e-003 -0.1133835017681122 0.2223394960165024 <_> <_> <_>8 2 3 2 -1. <_>9 2 1 2 3. 0 1.4527440071105957e-003 0.0697504431009293 -0.3672071099281311 <_> <_> <_>12 11 6 3 -1. <_>12 12 6 1 3. 0 4.7638658434152603e-003 -0.0657889619469643 0.3832854032516480 <_> <_> <_>1 5 1 4 -1. <_>1 7 1 2 2. 0 -6.2501081265509129e-003 -0.7075446844100952 0.0383501984179020 <_> <_> <_>1 1 1 18 -1. <_>1 7 1 6 3. 0 -3.1765329185873270e-003 0.1375540047883987 -0.2324002981185913 <_> <_> <_>11 13 3 2 -1. <_>11 14 3 1 2. 0 3.2191169448196888e-003 -0.1293545067310333 0.2273788005113602 <_> <_> <_>0 1 12 2 -1. <_>0 1 6 1 2. <_>6 2 6 1 2. 0 -5.6365579366683960e-003 0.3806715011596680 -0.0672468394041061 <_> <_> <_>10 18 2 2 -1. <_>10 18 1 1 2. <_>11 19 1 1 2. 0 -2.3844049428589642e-004 -0.3112238049507141 0.0838383585214615 <_> <_> <_>4 5 4 4 -1. <_>4 5 2 2 2. <_>6 7 2 2 2. 0 -4.1017560288310051e-003 0.2606728076934815 -0.1044974029064179 <_> <_> <_>6 7 1 3 -1. <_>6 8 1 1 3. 0 1.3336989795789123e-003 -0.0582501403987408 0.4768244028091431 <_> <_> <_>14 10 6 2 -1. <_>16 10 2 2 3. 0 -1.2090239906683564e-003 0.1483450978994370 -0.1732946932315826 -1.2529590129852295 15 -1 <_> <_> <_> <_>16 8 3 6 -1. <_>17 8 1 6 3. 0 -3.1760931015014648e-003 0.3333333134651184 -0.1664234995841980 <_> <_> <_>4 10 6 2 -1. <_>6 10 2 2 3. 0 0.0248580798506737 -0.0727288722991943 0.5667458176612854 <_> <_> <_>6 5 3 7 -1. <_>7 5 1 7 3. 0 -7.7597280032932758e-003 0.4625856876373291 -0.0931121781468391 <_> <_> <_>0 13 6 6 -1. <_>0 16 6 3 2. 0 7.8239021822810173e-003 -0.2741461098194122 0.1324304938316345 <_> <_> <_>12 5 1 9 -1. <_>12 8 1 3 3. 0 -0.0109488395974040 0.2234548032283783 -0.1496544927358627 <_> <_> <_>5 9 3 3 -1. <_>6 9 1 3 3. 0 -3.4349008928984404e-003 0.3872498869895935 -0.0661217272281647 <_> <_> <_>7 5 6 13 -1. <_>9 5 2 13 3. 0 -0.0311562903225422 0.2407827973365784 -0.1140690967440605 <_> <_> <_>19 8 1 10 -1. <_>19 13 1 5 2. 0 1.1100519914180040e-003 -0.2820797860622406 0.1327542960643768 <_> <_> <_>11 18 6 1 -1. <_>13 18 2 1 3. 0 3.1762740109115839e-003 0.0345859304070473 -0.5137431025505066 <_> <_> <_>9 7 6 12 -1. <_>11 7 2 12 3. 0 -0.0279774591326714 0.2392677962779999 -0.1325591951608658 <_> <_> <_>12 7 6 6 -1. <_>14 7 2 6 3. 0 -0.0230979397892952 0.3901962041854858 -0.0784780085086823 <_> <_> <_>15 8 3 4 -1. <_>16 8 1 4 3. 0 -3.9731930010020733e-003 0.3069106936454773 -0.0706014037132263 <_> <_> <_>6 11 4 2 -1. <_>6 12 4 1 2. 0 3.0335749033838511e-003 -0.1400219053030014 0.1913485974073410 <_> <_> <_>1 6 6 8 -1. <_>3 6 2 8 3. 0 -0.0108443703502417 0.1654873043298721 -0.1565777957439423 <_> <_> <_>11 15 6 5 -1. <_>13 15 2 5 3. 0 -0.0181505102664232 -0.6324359178543091 0.0395618192851543 <_> <_> <_>15 17 4 2 -1. <_>15 18 4 1 2. 0 7.1052298881113529e-004 -0.1851557046175003 0.1340880990028381 <_> <_> <_>13 11 6 1 -1. <_>15 11 2 1 3. 0 0.0108933402225375 -0.0267302300781012 0.6097180247306824 <_> <_> <_>5 18 2 2 -1. <_>5 18 1 1 2. <_>6 19 1 1 2. 0 -2.8780900174751878e-004 -0.3006514012813568 0.0731714591383934 <_> <_> <_>4 8 4 4 -1. <_>4 8 2 2 2. <_>6 10 2 2 2. 0 -3.5855069290846586e-003 0.2621760964393616 -0.0797140970826149 <_> <_> <_>11 7 9 3 -1. <_>11 8 9 1 3. 0 -0.0197592806071043 -0.5903922915458679 0.0406989715993404 <_> <_> <_>0 3 10 4 -1. <_>0 3 5 2 2. <_>5 5 5 2 2. 0 -0.0108452104032040 0.1636455953121185 -0.1258606016635895 <_> <_> <_>7 18 6 1 -1. <_>9 18 2 1 3. 0 -4.3183090165257454e-003 -0.5747488141059876 0.0376443117856979 <_> <_> <_>0 8 3 3 -1. <_>0 9 3 1 3. 0 1.4913700288161635e-003 0.0609134696424007 -0.3022292852401733 <_> <_> <_>0 0 6 8 -1. <_>0 0 3 4 2. <_>3 4 3 4 2. 0 0.0156756993383169 -0.0731459110975266 0.2937945127487183 <_> <_> <_>7 6 3 8 -1. <_>8 6 1 8 3. 0 -0.0110335601493716 0.3931880891323090 -0.0470843203365803 <_> <_> <_>13 7 7 3 -1. <_>13 8 7 1 3. 0 8.8555756956338882e-003 0.0376013815402985 -0.4910849034786224 <_> <_> <_>3 3 2 2 -1. <_>3 4 2 1 2. 0 -8.9665671112015843e-004 0.1795202046632767 -0.1108623966574669 <_> <_> <_>0 3 3 3 -1. <_>0 4 3 1 3. 0 -3.0592409893870354e-003 -0.4442946016788483 0.0510054305195808 <_> <_> <_>9 3 5 2 -1. <_>9 4 5 1 2. 0 6.3201179727911949e-003 -0.0528410896658897 0.3719710111618042 <_> <_> <_>6 5 9 4 -1. <_>9 5 3 4 3. 0 0.0206828303635120 0.0576671697199345 -0.3690159916877747 <_> <_> <_>3 10 12 3 -1. <_>7 10 4 3 3. 0 0.0998226627707481 -0.0373770184814930 0.5816559195518494 <_> <_> <_>8 7 3 6 -1. <_>9 7 1 6 3. 0 -6.5854229032993317e-003 0.2850944101810455 -0.0609780699014664 <_> <_> <_>5 5 6 5 -1. <_>8 5 3 5 2. 0 -0.0609003007411957 -0.5103176832199097 0.0377874001860619 <_> <_> <_>0 5 2 3 -1. <_>0 6 2 1 3. 0 -2.9991709161549807e-003 -0.4794301092624664 0.0388338901102543 <_> <_> <_>9 7 3 4 -1. <_>10 7 1 4 3. 0 -9.8906438797712326e-003 0.4060907959938049 -0.0478696487843990 <_> <_> <_>1 0 6 15 -1. <_>3 0 2 15 3. 0 -0.0826889276504517 -0.7067118287086487 0.0274877492338419 <_> <_> <_>15 1 3 5 -1. <_>16 1 1 5 3. 0 5.0060399807989597e-003 0.0282084401696920 -0.5290969014167786 <_> <_> <_>9 2 3 10 -1. <_>10 2 1 10 3. 0 6.1695030890405178e-003 -0.0545548610389233 0.3283798098564148 <_> <_> <_>8 8 6 12 -1. <_>10 8 2 12 3. 0 -3.3914761152118444e-003 0.0921176671981812 -0.2163711041212082 <_> <_> <_>16 4 3 4 -1. <_>16 6 3 2 2. 0 -2.6131230406463146e-003 0.1365101933479309 -0.1378113031387329 <_> <_> <_>16 7 2 2 -1. <_>16 7 1 1 2. <_>17 8 1 1 2. 0 8.0490659456700087e-004 -0.0686371102929115 0.3358106911182404 <_> <_> <_>13 0 6 9 -1. <_>13 3 6 3 3. 0 -0.0381065085530281 0.2944543063640595 -0.0682392269372940 <_> <_> <_>7 17 1 3 -1. <_>7 18 1 1 3. 0 7.2450799052603543e-005 -0.1675013005733490 0.1217823028564453 <_> <_> <_>12 1 4 2 -1. <_>12 2 4 1 2. 0 1.5837959945201874e-003 -0.0920428484678268 0.2134899049997330 <_> <_> <_>17 3 1 3 -1. <_>17 4 1 1 3. 0 1.2924340553581715e-003 0.0629172325134277 -0.3617450892925263 <_> <_> <_>0 16 9 3 -1. <_>0 17 9 1 3. 0 9.9146775901317596e-003 0.0195340607315302 -0.8101503849029541 <_> <_> <_>3 6 2 4 -1. <_>3 6 1 2 2. <_>4 8 1 2 2. 0 -1.7086310544982553e-003 0.2552523910999298 -0.0682294592261314 <_> <_> <_>13 18 3 1 -1. <_>14 18 1 1 3. 0 2.1844399161636829e-003 0.0233140494674444 -0.8429678082466126 <_> <_> <_>0 18 4 2 -1. <_>2 18 2 2 2. 0 -3.4244330599904060e-003 0.2721368968486786 -0.0763952285051346 <_> <_> <_>1 19 2 1 -1. <_>2 19 1 1 2. 0 2.7591470279730856e-004 -0.1074284017086029 0.2288897037506104 <_> <_> <_>0 18 4 2 -1. <_>0 19 4 1 2. 0 -6.0005177510902286e-004 -0.2985421121120453 0.0634797364473343 <_> <_> <_>2 17 1 3 -1. <_>2 18 1 1 3. 0 -2.5001438916660845e-004 -0.2717896997928619 0.0696150064468384 <_> <_> <_>4 8 3 5 -1. <_>5 8 1 5 3. 0 6.8751391954720020e-003 -0.0571858994662762 0.3669595122337341 <_> <_> <_>2 1 6 7 -1. <_>4 1 2 7 3. 0 0.0127619002014399 0.0679556876420975 -0.2853415012359619 <_> <_> <_>3 6 2 8 -1. <_>3 6 1 4 2. <_>4 10 1 4 2. 0 -1.4752789866179228e-003 0.2068066000938416 -0.1005939021706581 <_> <_> <_>4 5 11 10 -1. <_>4 10 11 5 2. 0 0.1213881969451904 -0.0971267968416214 0.1978961974382401 <_> <_> <_>0 13 20 2 -1. <_>10 13 10 2 2. 0 -0.0500812791287899 0.2841717898845673 -0.0678799971938133 <_> <_> <_>1 13 16 3 -1. <_>9 13 8 3 2. 0 0.0314549505710602 -0.0894686728715897 0.2129842042922974 <_> <_> <_>16 4 4 4 -1. <_>16 4 2 2 2. <_>18 6 2 2 2. 0 1.8878319533541799e-003 -0.1165644004940987 0.1666352003812790 <_> <_> <_>16 0 4 12 -1. <_>16 0 2 6 2. <_>18 6 2 6 2. 0 -5.7211960665881634e-003 0.2370214015245438 -0.0907766073942184 <_> <_> <_>14 15 3 1 -1. <_>15 15 1 1 3. 0 -1.8076719425152987e-004 0.1795192956924439 -0.1079348027706146 <_> <_> <_>3 4 12 10 -1. <_>3 9 12 5 2. 0 -0.1976184993982315 0.4567429125308991 -0.0404801592230797 <_> <_> <_>9 18 2 2 -1. <_>9 18 1 1 2. <_>10 19 1 1 2. 0 -2.3846809926908463e-004 -0.2373300939798355 0.0759221613407135 <_> <_> <_>9 18 2 2 -1. <_>9 18 1 1 2. <_>10 19 1 1 2. 0 2.1540730085689574e-004 0.0816880166530609 -0.2868503034114838 <_> <_> <_>13 4 2 14 -1. <_>13 4 1 7 2. <_>14 11 1 7 2. 0 0.0101630901917815 -0.0412500202655792 0.4803834855556488 <_> <_> <_>4 2 6 4 -1. <_>7 2 3 4 2. 0 -7.2184870950877666e-003 0.1745858043432236 -0.1014650017023087 <_> <_> <_>0 0 18 20 -1. <_>0 0 9 10 2. <_>9 10 9 10 2. 0 0.2426317036151886 0.0534264817833900 -0.3231852948665619 <_> <_> <_>15 11 1 2 -1. <_>15 12 1 1 2. 0 6.9304101634770632e-004 -0.1149917989969254 0.1479393988847733 <_> <_> <_>16 10 2 4 -1. <_>16 10 1 2 2. <_>17 12 1 2 2. 0 3.5475199110805988e-003 -0.0394249781966209 0.5312618017196655 <_> <_> <_>18 17 2 2 -1. <_>18 17 1 1 2. <_>19 18 1 1 2. 0 2.1403690334409475e-004 0.0697538331151009 -0.2731958031654358 <_> <_> <_>9 17 1 2 -1. <_>9 18 1 1 2. 0 -5.7119462871924043e-004 0.3436990082263947 -0.0576990097761154 <_> <_> <_>8 4 9 6 -1. <_>11 4 3 6 3. 0 -6.6290069371461868e-003 0.1175848990678787 -0.1502013951539993 -1.1188739538192749 16 -1 <_> <_> <_> <_>6 9 9 10 -1. <_>9 9 3 10 3. 0 -0.0265134498476982 0.2056864053010941 -0.2647390067577362 <_> <_> <_>5 0 5 4 -1. <_>5 2 5 2 2. 0 9.7727458924055099e-003 -0.1119284033775330 0.3257054984569550 <_> <_> <_>5 7 11 4 -1. <_>5 9 11 2 2. 0 0.0322903506457806 -0.0985747575759888 0.3177917003631592 <_> <_> <_>2 4 2 14 -1. <_>3 4 1 14 2. 0 -2.8103240765631199e-003 0.1521389931440353 -0.1968640983104706 <_> <_> <_>8 6 3 5 -1. <_>9 6 1 5 3. 0 -0.0109914299100637 0.5140765905380249 -0.0437072105705738 <_> <_> <_>8 4 3 9 -1. <_>9 4 1 9 3. 0 6.3133831135928631e-003 -0.0927810221910477 0.3470247089862824 <_> <_> <_>0 8 20 6 -1. <_>0 10 20 2 3. 0 0.0871059820055962 0.0300536490976810 -0.8281481862068176 <_> <_> <_>14 16 6 1 -1. <_>17 16 3 1 2. 0 1.1799359926953912e-003 -0.1292842030525208 0.2064612060785294 <_> <_> <_>17 18 2 2 -1. <_>17 19 2 1 2. 0 -9.3056890182197094e-004 -0.5002143979072571 0.0936669930815697 <_> <_> <_>8 17 6 3 -1. <_>10 17 2 3 3. 0 -0.0136871701106429 -0.7935814857482910 -6.6733639687299728e-003 <_> <_> <_>4 1 9 15 -1. <_>7 1 3 15 3. 0 -0.0759174525737762 0.3046964108943939 -0.0796558931469917 <_> <_> <_>11 5 3 12 -1. <_>12 5 1 12 3. 0 -2.8559709899127483e-003 0.2096146047115326 -0.1273255050182343 <_> <_> <_>0 15 4 3 -1. <_>0 16 4 1 3. 0 -4.0231510065495968e-003 -0.6581727862358093 0.0506836399435997 <_> <_> <_>0 0 15 1 -1. <_>5 0 5 1 3. 0 0.0175580400973558 -0.0853826925158501 0.3617455959320068 <_> <_> <_>6 0 6 4 -1. <_>8 0 2 4 3. 0 0.0219882391393185 0.0629436969757080 -0.7089633941650391 <_> <_> <_>2 0 9 3 -1. <_>5 0 3 3 3. 0 -2.8599589131772518e-003 0.1468378007411957 -0.1646597981452942 <_> <_> <_>13 6 3 7 -1. <_>14 6 1 7 3. 0 -0.0100308498367667 0.4957993924617767 -0.0271883402019739 <_> <_> <_>7 6 4 2 -1. <_>7 7 4 1 2. 0 -6.9560329429805279e-003 0.2797777950763702 -0.0779533311724663 <_> <_> <_>6 18 6 1 -1. <_>8 18 2 1 3. 0 -3.8356808945536613e-003 -0.5816398262977600 0.0357399396598339 <_> <_> <_>18 6 2 2 -1. <_>18 7 2 1 2. 0 -3.2647319603711367e-003 -0.4994508028030396 0.0469864904880524 <_> <_> <_>6 4 7 3 -1. <_>6 5 7 1 3. 0 -7.8412350267171860e-003 0.3453283011913300 -0.0688104033470154 <_> <_> <_>12 7 3 1 -1. <_>13 7 1 1 3. 0 -8.1718113506212831e-005 0.1504171043634415 -0.1414667963981628 <_> <_> <_>15 1 2 10 -1. <_>15 1 1 5 2. <_>16 6 1 5 2. 0 -3.2448628917336464e-003 0.2272451072931290 -0.0928602069616318 <_> <_> <_>0 18 2 2 -1. <_>0 19 2 1 2. 0 -7.8561151167377830e-004 -0.4431901872158051 0.0578124411404133 <_> <_> <_>19 4 1 8 -1. <_>19 8 1 4 2. 0 -6.2474247533828020e-004 0.1395238935947418 -0.1466871947050095 <_> <_> <_>1 17 1 3 -1. <_>1 18 1 1 3. 0 -3.2942948746494949e-004 -0.2990157008171082 0.0760667398571968 <_> <_> <_>0 15 6 4 -1. <_>0 15 3 2 2. <_>3 17 3 2 2. 0 1.2605739757418633e-003 -0.1612560003995895 0.1395380049943924 <_> <_> <_>19 0 1 18 -1. <_>19 6 1 6 3. 0 -0.0516670197248459 -0.5314283967018127 0.0407195203006268 <_> <_> <_>10 2 6 2 -1. <_>12 2 2 2 3. 0 -0.0152856195345521 -0.7820637822151184 0.0271837692707777 <_> <_> <_>2 8 12 2 -1. <_>6 8 4 2 3. 0 0.0690298229455948 -0.0364270210266113 0.7110251784324646 <_> <_> <_>16 0 4 1 -1. <_>18 0 2 1 2. 0 1.4522749697789550e-003 -0.0968905165791512 0.2166842073202133 <_> <_> <_>8 4 2 6 -1. <_>8 7 2 3 2. 0 -2.4765590205788612e-003 0.1164531037211418 -0.1822797954082489 <_> <_> <_>14 5 2 10 -1. <_>15 5 1 10 2. 0 -1.5134819550439715e-003 0.1786397993564606 -0.1221496984362602 <_> <_> <_>13 4 2 2 -1. <_>13 5 2 1 2. 0 -1.5099470037966967e-003 0.1808623969554901 -0.1144606992602348 <_> <_> <_>11 1 3 6 -1. <_>11 3 3 2 3. 0 -6.7054620012640953e-003 0.2510659992694855 -0.0918714627623558 <_> <_> <_>6 9 12 2 -1. <_>10 9 4 2 3. 0 -0.0140752000734210 0.1370750963687897 -0.1733350008726120 <_> <_> <_>9 16 4 2 -1. <_>9 17 4 1 2. 0 -2.2400720044970512e-003 0.4009298086166382 -0.0475768782198429 <_> <_> <_>5 14 15 4 -1. <_>5 16 15 2 2. 0 0.0197823699563742 -0.1904035061597824 0.1492341011762619 <_> <_> <_>18 16 2 2 -1. <_>18 17 2 1 2. 0 2.6002870872616768e-003 0.0469717681407928 -0.4330765902996063 <_> <_> <_>16 18 2 2 -1. <_>16 18 1 1 2. <_>17 19 1 1 2. 0 -5.3445628145709634e-004 -0.4374423027038574 0.0415201894938946 <_> <_> <_>6 4 3 8 -1. <_>7 4 1 8 3. 0 -0.0174665097147226 0.6581817269325256 -0.0344474911689758 <_> <_> <_>5 9 3 1 -1. <_>6 9 1 1 3. 0 -2.0425589755177498e-003 0.3965792953968048 -0.0440524294972420 <_> <_> <_>0 8 1 6 -1. <_>0 10 1 2 3. 0 2.6661779265850782e-003 0.0587709583342075 -0.3280636966228485 <_> <_> <_>11 2 9 6 -1. <_>14 2 3 6 3. 0 -0.0559823699295521 -0.5173547267913818 0.0357918404042721 <_> <_> <_>12 2 6 4 -1. <_>14 2 2 4 3. 0 -1.5066330088302493e-003 0.1512386947870255 -0.1252018064260483 <_> <_> <_>1 7 2 4 -1. <_>1 9 2 2 2. 0 -0.0114723695442081 -0.6293053030967712 0.0347043313086033 <_> <_> <_>13 1 6 4 -1. <_>13 3 6 2 2. 0 0.0234096292406321 -0.0580633506178856 0.3866822123527527 <_> <_> <_>4 10 2 10 -1. <_>4 10 1 5 2. <_>5 15 1 5 2. 0 -2.3243729956448078e-003 0.1875409930944443 -0.0983946695923805 <_> <_> <_>2 16 9 3 -1. <_>5 16 3 3 3. 0 -0.0290392991155386 -0.5448690056800842 0.0409263409674168 <_> <_> <_>1 2 3 9 -1. <_>2 2 1 9 3. 0 -0.0144746499136090 -0.6724839210510254 0.0231288503855467 <_> <_> <_>19 7 1 4 -1. <_>19 9 1 2 2. 0 -5.2086091600358486e-003 -0.4327144026756287 0.0437806509435177 <_> <_> <_>14 11 6 8 -1. <_>14 11 3 4 2. <_>17 15 3 4 2. 0 4.9382899887859821e-003 -0.1087862029671669 0.1934258937835693 <_> <_> <_>15 12 4 6 -1. <_>15 12 2 3 2. <_>17 15 2 3 2. 0 -4.3193930760025978e-003 0.2408093065023422 -0.1038080006837845 <_> <_> <_>16 15 2 2 -1. <_>16 15 1 1 2. <_>17 16 1 1 2. 0 2.3705669445917010e-004 -0.0873490720987320 0.2046623975038528 <_> <_> <_>17 16 2 2 -1. <_>17 16 1 1 2. <_>18 17 1 1 2. 0 4.7858079778961837e-004 0.0456245802342892 -0.3885467052459717 <_> <_> <_>17 16 2 2 -1. <_>17 16 1 1 2. <_>18 17 1 1 2. 0 -8.5342838428914547e-004 -0.5507794022560120 0.0358258895576000 <_> <_> <_>2 3 2 2 -1. <_>2 3 1 1 2. <_>3 4 1 1 2. 0 5.4772121075075120e-005 -0.1122523993253708 0.1750351935625076 <_> <_> <_>10 10 3 3 -1. <_>11 10 1 3 3. 0 -3.8445889949798584e-003 0.2452670037746429 -0.0811325684189796 <_> <_> <_>5 9 7 8 -1. <_>5 13 7 4 2. 0 -0.0401284582912922 -0.6312270760536194 0.0269726701080799 <_> <_> <_>7 16 2 2 -1. <_>7 16 1 1 2. <_>8 17 1 1 2. 0 -1.7886360001284629e-004 0.1985509991645813 -0.1033368036150932 <_> <_> <_>7 16 2 2 -1. <_>7 16 1 1 2. <_>8 17 1 1 2. 0 1.7668239888735116e-004 -0.0913590118288994 0.1984872072935104 <_> <_> <_>9 8 10 3 -1. <_>14 8 5 3 2. 0 0.0727633833885193 0.0500755794346333 -0.3385263085365295 <_> <_> <_>6 7 4 8 -1. <_>6 7 2 4 2. <_>8 11 2 4 2. 0 0.0101816300302744 -0.0932299792766571 0.2005959004163742 <_> <_> <_>1 6 4 3 -1. <_>1 7 4 1 3. 0 2.4409969337284565e-003 0.0646366328001022 -0.2692174017429352 <_> <_> <_>6 10 6 10 -1. <_>8 10 2 10 3. 0 -3.6227488890290260e-003 0.1316989064216614 -0.1251484006643295 <_> <_> <_>4 6 3 6 -1. <_>5 6 1 6 3. 0 -1.3635610230267048e-003 0.1635046005249023 -0.1066593974828720 -1.0888810157775879 17 -1 <_> <_> <_> <_>3 10 4 4 -1. <_>3 10 2 2 2. <_>5 12 2 2 2. 0 -9.6991164609789848e-003 0.6112532019615173 -0.0662253126502037 <_> <_> <_>3 10 4 4 -1. <_>3 10 2 2 2. <_>5 12 2 2 2. 0 -9.6426531672477722e-003 -1. 2.7699959464371204e-003 <_> <_> <_>3 10 4 4 -1. <_>3 10 2 2 2. <_>5 12 2 2 2. 0 -9.6381865441799164e-003 1. -2.9904270195402205e-004 <_> <_> <_>14 8 2 6 -1. <_>15 8 1 6 2. 0 -4.2553939856588840e-003 0.2846438884735107 -0.1554012000560761 <_> <_> <_>3 10 4 4 -1. <_>3 10 2 2 2. <_>5 12 2 2 2. 0 -9.6223521977663040e-003 -1. 0.0439991801977158 <_> <_> <_>3 10 4 4 -1. <_>3 10 2 2 2. <_>5 12 2 2 2. 0 -9.1231241822242737e-003 0.8686934113502502 -2.7267890982329845e-003 <_> <_> <_>12 4 3 9 -1. <_>13 4 1 9 3. 0 -8.6240433156490326e-003 0.4535248875617981 -0.0860713794827461 <_> <_> <_>12 3 1 12 -1. <_>12 7 1 4 3. 0 -8.9324144646525383e-003 0.1337555944919586 -0.2601251900196075 <_> <_> <_>2 0 18 1 -1. <_>8 0 6 1 3. 0 -0.0142078101634979 0.3207764029502869 -0.0972264111042023 <_> <_> <_>10 0 10 6 -1. <_>10 0 5 3 2. <_>15 3 5 3 2. 0 0.0259110108017921 -0.1296408027410507 0.2621864974498749 <_> <_> <_>18 16 2 2 -1. <_>18 17 2 1 2. 0 2.0531509653665125e-004 -0.1240428015589714 0.2106295973062515 <_> <_> <_>3 5 4 2 -1. <_>3 5 2 1 2. <_>5 6 2 1 2. 0 -5.4795680625829846e-005 0.1197429969906807 -0.2320127934217453 <_> <_> <_>11 8 3 3 -1. <_>12 8 1 3 3. 0 6.8555199541151524e-003 -0.0632761269807816 0.4104425013065338 <_> <_> <_>11 7 3 5 -1. <_>12 7 1 5 3. 0 -0.0122530404478312 0.5488333106040955 -0.0397311002016068 <_> <_> <_>3 19 15 1 -1. <_>8 19 5 1 3. 0 -3.9058770053088665e-003 0.2419098019599915 -0.0970960110425949 <_> <_> <_>8 13 3 2 -1. <_>8 14 3 1 2. 0 2.7560980524867773e-003 -0.1256967931985855 0.1945665031671524 <_> <_> <_>2 12 8 4 -1. <_>2 12 4 2 2. <_>6 14 4 2 2. 0 -7.7662160620093346e-003 0.2976570129394531 -0.0968181565403938 <_> <_> <_>16 16 2 2 -1. <_>16 16 1 1 2. <_>17 17 1 1 2. 0 3.8997188676148653e-004 0.0621884018182755 -0.4204089939594269 <_> <_> <_>7 0 3 2 -1. <_>8 0 1 2 3. 0 3.3579880837351084e-003 0.0474981404840946 -0.6321688294410706 <_> <_> <_>6 7 2 5 -1. <_>7 7 1 5 2. 0 -0.0167455393821001 0.7109813094139099 -0.0391573496162891 <_> <_> <_>18 0 2 17 -1. <_>19 0 1 17 2. 0 -6.5409899689257145e-003 -0.3504317104816437 0.0706169530749321 <_> <_> <_>16 16 1 3 -1. <_>16 17 1 1 3. 0 3.0016340315341949e-004 0.0919024571776390 -0.2461867034435272 <_> <_> <_>14 8 3 7 -1. <_>15 8 1 7 3. 0 0.0149189904332161 -0.0519094504415989 0.5663604140281677 <_> <_> <_>10 17 2 2 -1. <_>10 17 1 1 2. <_>11 18 1 1 2. 0 4.8153079114854336e-004 0.0646595582365990 -0.3659060895442963 <_> <_> <_>4 9 1 3 -1. <_>4 10 1 1 3. 0 -3.0211321427486837e-004 0.1792656928300858 -0.1141066029667854 <_> <_> <_>18 10 2 3 -1. <_>18 11 2 1 3. 0 3.8521419628523290e-004 0.1034561991691589 -0.2007246017456055 <_> <_> <_>12 1 3 10 -1. <_>13 1 1 10 3. 0 8.0837132409214973e-003 -0.0660734623670578 0.3028424978256226 <_> <_> <_>8 12 9 1 -1. <_>11 12 3 1 3. 0 -0.0228049699217081 0.5296235084533691 -0.0401189997792244 <_> <_> <_>5 18 2 2 -1. <_>5 18 1 1 2. <_>6 19 1 1 2. 0 1.9440450705587864e-004 0.0818548202514648 -0.2466336041688919 <_> <_> <_>19 6 1 9 -1. <_>19 9 1 3 3. 0 -0.0128480903804302 -0.3497331142425537 0.0569162294268608 <_> <_> <_>4 7 2 4 -1. <_>4 7 1 2 2. <_>5 9 1 2 2. 0 -1.0937290498986840e-003 0.2336868047714233 -0.0916048064827919 <_> <_> <_>1 4 6 14 -1. <_>3 4 2 14 3. 0 1.0032650316134095e-003 0.1185218021273613 -0.1846919059753418 <_> <_> <_>10 5 9 3 -1. <_>13 5 3 3 3. 0 -0.0446884296834469 -0.6436246037483215 0.0303632691502571 <_> <_> <_>18 7 2 6 -1. <_>18 9 2 2 3. 0 8.1657543778419495e-003 0.0436746589839458 -0.4300208985805512 <_> <_> <_>5 6 2 7 -1. <_>6 6 1 7 2. 0 -0.0117178102955222 0.4178147912025452 -0.0482336990535259 <_> <_> <_>10 4 6 8 -1. <_>13 4 3 8 2. 0 0.0842771306633949 0.0534612797200680 -0.3795219063758850 <_> <_> <_>0 8 2 9 -1. <_>0 11 2 3 3. 0 0.0142118399962783 0.0449009388685226 -0.4298149943351746 <_> <_> <_>0 7 5 3 -1. <_>0 8 5 1 3. 0 1.5028340276330709e-003 0.0822276398539543 -0.2470639944076538 <_> <_> <_>8 1 7 2 -1. <_>8 2 7 1 2. 0 0.0100035797804594 -0.0572216697037220 0.3460937142372131 <_> <_> <_>7 5 3 5 -1. <_>8 5 1 5 3. 0 -9.0706320479512215e-003 0.4505808949470520 -0.0427953191101551 <_> <_> <_>19 2 1 2 -1. <_>19 3 1 1 2. 0 -3.3141620224341750e-004 0.1833691000938416 -0.1075994968414307 <_> <_> <_>6 7 10 11 -1. <_>11 7 5 11 2. 0 0.1972327977418900 -0.0303638298064470 0.6642342805862427 <_> <_> <_>9 19 6 1 -1. <_>11 19 2 1 3. 0 -7.1258801035583019e-003 -0.8922504782676697 0.0256699901074171 <_> <_> <_>3 0 12 1 -1. <_>7 0 4 1 3. 0 8.6921341717243195e-003 -0.0707643702626228 0.2821052968502045 <_> <_> <_>4 1 6 5 -1. <_>6 1 2 5 3. 0 8.9262127876281738e-003 0.0710782334208488 -0.3023256063461304 <_> <_> <_>6 12 12 6 -1. <_>10 12 4 6 3. 0 0.0572860091924667 0.0509741306304932 -0.3919695019721985 <_> <_> <_>16 13 2 3 -1. <_>16 14 2 1 3. 0 3.7920880131423473e-003 0.0338419415056705 -0.5101628899574280 <_> <_> <_>7 14 4 2 -1. <_>7 15 4 1 2. 0 -1.4508679741993546e-003 0.3087914884090424 -0.0638450831174850 <_> <_> <_>7 14 2 2 -1. <_>7 15 2 1 2. 0 9.8390132188796997e-004 -0.1302956938743591 0.1460441052913666 <_> <_> <_>3 10 2 4 -1. <_>3 10 1 2 2. <_>4 12 1 2 2. 0 -1.7221809830516577e-003 0.2915700972080231 -0.0685495585203171 <_> <_> <_>0 3 2 6 -1. <_>0 5 2 2 3. 0 0.0109482500702143 0.0343514084815979 -0.4770225882530212 <_> <_> <_>1 10 2 2 -1. <_>1 10 1 1 2. <_>2 11 1 1 2. 0 -1.7176309484057128e-005 0.1605526953935623 -0.1169084012508392 <_> <_> <_>16 4 4 3 -1. <_>16 5 4 1 3. 0 -5.4884208366274834e-003 -0.4341588914394379 0.0461062416434288 <_> <_> <_>5 10 2 4 -1. <_>5 10 1 2 2. <_>6 12 1 2 2. 0 -3.0975250992923975e-003 0.3794333934783936 -0.0568605512380600 <_> <_> <_>5 11 13 2 -1. <_>5 12 13 1 2. 0 6.4182081259787083e-003 -0.1585821062326431 0.1233541965484619 <_> <_> <_>10 2 3 11 -1. <_>11 2 1 11 3. 0 0.0118312397971749 -0.0409292913973331 0.4587895870208740 <_> <_> <_>10 2 4 4 -1. <_>10 4 4 2 2. 0 0.0135404998436570 -0.0537255592644215 0.3505612015724182 <_> <_> <_>8 8 6 2 -1. <_>10 8 2 2 3. 0 -2.5932150892913342e-003 0.1101052016019821 -0.1675221025943756 <_> <_> <_>11 2 3 3 -1. <_>12 2 1 3 3. 0 1.6856270376592875e-003 0.0665743574500084 -0.3083502054214478 <_> <_> <_>6 18 14 2 -1. <_>6 18 7 1 2. <_>13 19 7 1 2. 0 2.6524690911173820e-003 0.0663184821605682 -0.2786133885383606 <_> <_> <_>17 7 1 12 -1. <_>17 11 1 4 3. 0 -7.7341729775071144e-003 0.1971835941076279 -0.1078291982412338 <_> <_> <_>10 5 10 3 -1. <_>10 6 10 1 3. 0 5.0944271497428417e-003 0.0853374898433685 -0.2484700977802277 <_> <_> <_>6 1 3 3 -1. <_>7 1 1 3 3. 0 -2.9162371065467596e-003 -0.4747635126113892 0.0335664898157120 <_> <_> <_>13 8 3 1 -1. <_>14 8 1 1 3. 0 3.0121419113129377e-003 -0.0475753806531429 0.4258680045604706 <_> <_> <_>10 14 2 6 -1. <_>10 16 2 2 3. 0 3.1694869976490736e-003 -0.1051945015788078 0.1716345995664597 <_> <_> <_>4 1 12 14 -1. <_>8 1 4 14 3. 0 0.2232756018638611 -0.0143702095374465 0.9248365163803101 <_> <_> <_>14 1 6 14 -1. <_>16 1 2 14 3. 0 -0.0955850481987000 -0.7420663833618164 0.0278189703822136 <_> <_> <_>3 16 2 2 -1. <_>3 16 1 1 2. <_>4 17 1 1 2. 0 3.4773729566950351e-005 -0.1276578009128571 0.1292666941881180 <_> <_> <_>0 16 2 2 -1. <_>0 17 2 1 2. 0 7.2459770308341831e-005 -0.1651857942342758 0.1003680974245071 -1.0408929586410522 18 -1 <_> <_> <_> <_>15 6 4 6 -1. <_>15 6 2 3 2. <_>17 9 2 3 2. 0 -6.5778270363807678e-003 0.3381525874137878 -0.1528190970420837 <_> <_> <_>12 5 2 2 -1. <_>12 6 2 1 2. 0 -1.0922809597104788e-003 0.2228236943483353 -0.1930849999189377 <_> <_> <_>7 6 6 13 -1. <_>9 6 2 13 3. 0 -0.0297595895826817 0.2595987021923065 -0.1540940999984741 <_> <_> <_>1 9 6 5 -1. <_>3 9 2 5 3. 0 -0.0131475403904915 0.1903381049633026 -0.1654399931430817 <_> <_> <_>0 5 3 4 -1. <_>0 7 3 2 2. 0 -1.4396329643204808e-003 0.2007171064615250 -0.1233894005417824 <_> <_> <_>4 1 16 2 -1. <_>4 1 8 1 2. <_>12 2 8 1 2. 0 -3.5928250290453434e-003 0.2398552000522614 -0.1292214989662170 <_> <_> <_>1 18 4 2 -1. <_>1 18 2 1 2. <_>3 19 2 1 2. 0 -1.5314699849113822e-003 -0.4901489913463593 0.1027503013610840 <_> <_> <_>7 7 3 4 -1. <_>8 7 1 4 3. 0 -6.2372139655053616e-003 0.3121463954448700 -0.1140562966465950 <_> <_> <_>3 4 9 3 -1. <_>6 4 3 3 3. 0 -0.0333646498620510 -0.4952087998390198 0.0513284504413605 <_> <_> <_>4 6 6 10 -1. <_>6 6 2 10 3. 0 -0.0228276997804642 0.3255882859230042 -0.0650893077254295 <_> <_> <_>9 0 8 10 -1. <_>13 0 4 10 2. 0 -0.0861990973353386 -0.6764633059501648 0.0269856993108988 <_> <_> <_>8 0 8 1 -1. <_>12 0 4 1 2. 0 -2.1065981127321720e-003 0.2245243042707443 -0.1261022984981537 <_> <_> <_>6 2 8 16 -1. <_>6 2 4 8 2. <_>10 10 4 8 2. 0 0.0391201488673687 0.1132939979434013 -0.2686063051223755 <_> <_> <_>14 10 2 10 -1. <_>14 10 1 5 2. <_>15 15 1 5 2. 0 3.5082739777863026e-003 -0.1135995984077454 0.2564977109432221 <_> <_> <_>12 11 1 2 -1. <_>12 12 1 1 2. 0 5.9289898490533233e-004 -0.1494296938180924 0.1640983968973160 <_> <_> <_>16 0 3 8 -1. <_>17 0 1 8 3. 0 7.1766850305721164e-004 0.0999056920409203 -0.2196796983480454 <_> <_> <_>14 0 6 10 -1. <_>17 0 3 10 2. 0 -0.0218036007136106 -0.3171172142028809 0.0828895866870880 <_> <_> <_>16 0 3 5 -1. <_>17 0 1 5 3. 0 -3.2962779514491558e-003 -0.3804872930049896 0.0608193799853325 <_> <_> <_>4 5 11 2 -1. <_>4 6 11 1 2. 0 2.4196270387619734e-003 -0.0960130169987679 0.2854058146476746 <_> <_> <_>1 0 2 1 -1. <_>2 0 1 1 2. 0 -4.4187481398694217e-004 0.2212793976068497 -0.0974349081516266 <_> <_> <_>0 0 2 3 -1. <_>0 1 2 1 3. 0 3.4523929934948683e-003 0.0375531204044819 -0.5796905159950256 <_> <_> <_>11 6 6 11 -1. <_>13 6 2 11 3. 0 -0.0218346007168293 0.2956213951110840 -0.0800483003258705 <_> <_> <_>14 0 3 1 -1. <_>15 0 1 1 3. 0 -2.1309500152710825e-004 0.2281450927257538 -0.1011418998241425 <_> <_> <_>19 7 1 2 -1. <_>19 8 1 1 2. 0 -1.6166249988600612e-003 -0.5054119825363159 0.0447645410895348 <_> <_> <_>17 0 3 9 -1. <_>18 0 1 9 3. 0 7.5959609821438789e-003 0.0459865406155586 -0.4119768142700195 <_> <_> <_>12 7 3 4 -1. <_>13 7 1 4 3. 0 3.8601809646934271e-003 -0.0865631699562073 0.2480999976396561 <_> <_> <_>0 1 14 2 -1. <_>0 1 7 1 2. <_>7 2 7 1 2. 0 6.0622231103479862e-003 -0.0755573734641075 0.2843326032161713 <_> <_> <_>3 1 3 2 -1. <_>4 1 1 2 3. 0 -1.7097420059144497e-003 -0.3529582023620606 0.0584104992449284 <_> <_> <_>4 0 15 2 -1. <_>9 0 5 2 3. 0 0.0165155790746212 -0.0804869532585144 0.2353743016719818 <_> <_> <_>10 2 6 1 -1. <_>12 2 2 1 3. 0 4.8465100117027760e-003 0.0418952181935310 -0.4844304919242859 <_> <_> <_>9 4 6 11 -1. <_>11 4 2 11 3. 0 -0.0311671700328588 0.1919230967760086 -0.1026815995573998 <_> <_> <_>2 16 2 4 -1. <_>2 18 2 2 2. 0 6.1892281519249082e-004 -0.2108577042818070 0.0938869267702103 <_> <_> <_>6 17 6 3 -1. <_>8 17 2 3 3. 0 0.0119463102892041 0.0390961691737175 -0.6224862933158875 <_> <_> <_>7 9 6 2 -1. <_>9 9 2 2 3. 0 -7.5677200220525265e-003 0.1593683958053589 -0.1225078031420708 <_> <_> <_>6 8 9 2 -1. <_>9 8 3 2 3. 0 -0.0537474118173122 -0.5562217831611633 0.0411900095641613 <_> <_> <_>6 6 2 10 -1. <_>6 6 1 5 2. <_>7 11 1 5 2. 0 0.0155135300010443 -0.0398268811404705 0.6240072846412659 <_> <_> <_>0 11 2 3 -1. <_>0 12 2 1 3. 0 1.5246650436893106e-003 0.0701386779546738 -0.3078907132148743 <_> <_> <_>11 15 4 1 -1. <_>13 15 2 1 2. 0 -4.8315100139006972e-004 0.1788765937089920 -0.1095862016081810 <_> <_> <_>6 17 1 2 -1. <_>6 18 1 1 2. 0 2.7374739293009043e-003 0.0274785906076431 -0.8848956823348999 <_> <_> <_>0 0 6 20 -1. <_>2 0 2 20 3. 0 -0.0657877177000046 -0.4643214046955109 0.0350371487438679 <_> <_> <_>3 10 2 2 -1. <_>4 10 1 2 2. 0 1.2409730115905404e-003 -0.0964792370796204 0.2877922058105469 <_> <_> <_>4 7 3 5 -1. <_>5 7 1 5 3. 0 8.1398809561505914e-004 0.1151171997189522 -0.1676616072654724 <_> <_> <_>3 12 6 2 -1. <_>5 12 2 2 3. 0 0.0239018201828003 -0.0326031893491745 0.6001734733581543 <_> <_> <_>6 15 7 4 -1. <_>6 17 7 2 2. 0 0.0275566000491381 -0.0661373436450958 0.2999447882175446 <_> <_> <_>17 16 2 2 -1. <_>17 16 1 1 2. <_>18 17 1 1 2. 0 -3.8070970913395286e-004 -0.3388118147850037 0.0644507706165314 <_> <_> <_>15 1 3 16 -1. <_>16 1 1 16 3. 0 -1.3335429830476642e-003 0.1458866000175476 -0.1321762055158615 <_> <_> <_>6 16 6 3 -1. <_>8 16 2 3 3. 0 -9.3507990241050720e-003 -0.5117782950401306 0.0349694713950157 <_> <_> <_>15 14 3 2 -1. <_>15 15 3 1 2. 0 7.6215229928493500e-003 0.0232495293021202 -0.6961941123008728 <_> <_> <_>12 16 1 2 -1. <_>12 17 1 1 2. 0 -5.3407860832521692e-005 0.2372737973928452 -0.0869107097387314 <_> <_> <_>0 2 4 4 -1. <_>0 2 2 2 2. <_>2 4 2 2 2. 0 -1.5332329785451293e-003 0.1922841072082520 -0.1042239964008331 <_> <_> <_>1 1 6 4 -1. <_>1 1 3 2 2. <_>4 3 3 2 2. 0 4.3135890737175941e-003 -0.0962195470929146 0.2560121119022369 <_> <_> <_>1 18 1 2 -1. <_>1 19 1 1 2. 0 -2.3042880638968199e-004 -0.3156475126743317 0.0588385984301567 <_> <_> <_>4 7 2 3 -1. <_>4 8 2 1 3. 0 -7.8411828726530075e-003 -0.6634092926979065 0.0245009995996952 <_> <_> <_>1 0 9 14 -1. <_>1 7 9 7 2. 0 0.1710374057292938 0.0338314995169640 -0.4561594128608704 <_> <_> <_>4 9 2 6 -1. <_>4 9 1 3 2. <_>5 12 1 3 2. 0 -1.6011140542104840e-003 0.2157489061355591 -0.0836225301027298 <_> <_> <_>3 9 4 3 -1. <_>5 9 2 3 2. 0 -0.0105357803404331 0.2455231994390488 -0.0823844894766808 <_> <_> <_>0 9 2 4 -1. <_>0 11 2 2 2. 0 -5.8351638726890087e-003 -0.4780732989311218 0.0440862216055393 <_> <_> <_>16 6 3 10 -1. <_>17 6 1 10 3. 0 -0.0187061093747616 -0.6002402901649475 0.0214100405573845 <_> <_> <_>16 11 2 1 -1. <_>17 11 1 1 2. 0 -9.3307439237833023e-004 0.2432359009981155 -0.0741657167673111 -1.0566600561141968 19 -1 <_> <_> <_> <_>5 7 4 4 -1. <_>5 9 4 2 2. 0 0.0106462296098471 -0.1386138945817947 0.2649407088756561 <_> <_> <_>10 11 9 2 -1. <_>13 11 3 2 3. 0 0.0352982692420483 -0.0758217275142670 0.3902106881141663 <_> <_> <_>15 10 2 2 -1. <_>15 10 1 1 2. <_>16 11 1 1 2. 0 7.5638387352228165e-004 -0.0955214425921440 0.2906199991703033 <_> <_> <_>10 6 6 14 -1. <_>10 13 6 7 2. 0 0.0924977064132690 -0.2770423889160156 0.0794747024774551 <_> <_> <_>14 7 3 5 -1. <_>15 7 1 5 3. 0 -2.9340879991650581e-003 0.2298953980207443 -0.0785500109195709 <_> <_> <_>6 11 12 3 -1. <_>10 11 4 3 3. 0 -0.0865358486771584 0.4774481058120728 -6.8231220357120037e-003 <_> <_> <_>17 16 1 2 -1. <_>17 17 1 1 2. 0 5.4699288739357144e-005 -0.2264260947704315 0.0881921127438545 <_> <_> <_>8 5 5 4 -1. <_>8 7 5 2 2. 0 -0.0365925207734108 0.2735387086868286 -0.0986067429184914 <_> <_> <_>11 6 4 2 -1. <_>11 7 4 1 2. 0 2.6469118893146515e-003 -0.0440839789807796 0.3144528865814209 <_> <_> <_>3 4 8 2 -1. <_>3 4 4 1 2. <_>7 5 4 1 2. 0 -4.4271810911595821e-003 0.2382272928953171 -0.0867842733860016 <_> <_> <_>0 8 6 6 -1. <_>2 8 2 6 3. 0 -5.1882481202483177e-003 0.1504276990890503 -0.1267210990190506 <_> <_> <_>7 4 6 2 -1. <_>7 5 6 1 2. 0 4.5530400238931179e-003 -0.0559450201690197 0.3650163114070892 <_> <_> <_>7 3 6 3 -1. <_>9 3 2 3 3. 0 0.0145624103024602 0.0363977700471878 -0.5355919003486633 <_> <_> <_>2 17 3 3 -1. <_>2 18 3 1 3. 0 6.8677567469421774e-005 -0.1747962981462479 0.1106870993971825 <_> <_> <_>3 10 6 1 -1. <_>5 10 2 1 3. 0 -5.9744901955127716e-003 0.3107787072658539 -0.0665302276611328 <_> <_> <_>7 2 6 2 -1. <_>9 2 2 2 3. 0 -5.8691250160336494e-003 -0.3190149068832398 0.0639318302273750 <_> <_> <_>4 11 9 1 -1. <_>7 11 3 1 3. 0 -0.0111403102055192 0.2436479032039642 -0.0809351801872253 <_> <_> <_>7 7 11 12 -1. <_>7 13 11 6 2. 0 -0.0586435310542583 -0.7608326077461243 0.0308096297085285 <_> <_> <_>3 2 3 4 -1. <_>4 2 1 4 3. 0 -4.6097282320261002e-003 -0.4531502127647400 0.0298790596425533 <_> <_> <_>9 7 9 3 -1. <_>12 7 3 3 3. 0 -9.3032103031873703e-003 0.1451337933540344 -0.1103316992521286 <_> <_> <_>15 11 2 6 -1. <_>15 11 1 3 2. <_>16 14 1 3 2. 0 1.3253629440441728e-003 -0.0976989567279816 0.1964644044637680 <_> <_> <_>0 5 5 3 -1. <_>0 6 5 1 3. 0 4.9800761044025421e-003 0.0336480811238289 -0.3979220986366272 <_> <_> <_>8 1 6 12 -1. <_>10 1 2 12 3. 0 -7.6542161405086517e-003 0.0908419936895370 -0.1596754938364029 <_> <_> <_>3 7 15 13 -1. <_>8 7 5 13 3. 0 -0.3892059028148651 -0.6657109260559082 0.0190288294106722 <_> <_> <_>0 9 9 9 -1. <_>0 12 9 3 3. 0 -0.1001966968178749 -0.5755926966667175 0.0242827795445919 <_> <_> <_>16 0 3 8 -1. <_>17 0 1 8 3. 0 7.3541211895644665e-004 0.0879198014736176 -0.1619534045457840 <_> <_> <_>16 2 4 2 -1. <_>18 2 2 2 2. 0 -3.4802639856934547e-003 0.2606449127197266 -0.0602008104324341 <_> <_> <_>13 0 6 5 -1. <_>16 0 3 5 2. 0 8.4000425413250923e-003 -0.1097972989082336 0.1570730954408646 <_> <_> <_>15 1 3 2 -1. <_>16 1 1 2 3. 0 2.3786011151969433e-003 0.0360582396388054 -0.4727719128131867 <_> <_> <_>11 8 3 2 -1. <_>12 8 1 2 3. 0 7.3831682093441486e-003 -0.0357563607394695 0.4949859082698822 <_> <_> <_>1 8 2 12 -1. <_>1 8 1 6 2. <_>2 14 1 6 2. 0 3.2115620560944080e-003 -0.1012556031346321 0.1574798971414566 <_> <_> <_>0 1 6 12 -1. <_>2 1 2 12 3. 0 -0.0782096683979034 -0.7662708163261414 0.0229658298194408 <_> <_> <_>19 17 1 3 -1. <_>19 18 1 1 3. 0 5.3303989261621609e-005 -0.1341435015201569 0.1111491993069649 <_> <_> <_>11 3 3 10 -1. <_>12 3 1 10 3. 0 -9.6419155597686768e-003 0.2506802976131439 -0.0666081383824348 <_> <_> <_>8 1 9 8 -1. <_>11 1 3 8 3. 0 -0.0710926726460457 -0.4005681872367859 0.0402977913618088 <_> <_> <_>18 16 2 2 -1. <_>18 16 1 1 2. <_>19 17 1 1 2. 0 3.5171560011804104e-004 0.0418611802160740 -0.3296119868755341 <_> <_> <_>18 16 2 2 -1. <_>18 16 1 1 2. <_>19 17 1 1 2. 0 -3.3458150574006140e-004 -0.2602983117103577 0.0678927376866341 <_> <_> <_>6 13 2 6 -1. <_>6 15 2 2 3. 0 -4.1451421566307545e-003 0.2396769970655441 -0.0720933377742767 <_> <_> <_>9 14 2 2 -1. <_>9 15 2 1 2. 0 3.1754500232636929e-003 -0.0712352693080902 0.2412845045328140 <_> <_> <_>14 10 2 4 -1. <_>14 10 1 2 2. <_>15 12 1 2 2. 0 -5.5184490047395229e-003 0.5032023787498474 -0.0296866800636053 <_> <_> <_>0 15 2 2 -1. <_>0 15 1 1 2. <_>1 16 1 1 2. 0 -3.0242869979701936e-004 0.2487905025482178 -0.0567585788667202 <_> <_> <_>6 7 2 2 -1. <_>6 7 1 1 2. <_>7 8 1 1 2. 0 -1.3125919504091144e-003 0.3174780011177063 -0.0418458618223667 <_> <_> <_>11 18 2 2 -1. <_>11 18 1 1 2. <_>12 19 1 1 2. 0 -2.7123570907860994e-004 -0.2704207003116608 0.0568289905786514 <_> <_> <_>0 0 6 4 -1. <_>0 0 3 2 2. <_>3 2 3 2 2. 0 -7.3241777718067169e-003 0.2755667865276337 -0.0542529709637165 <_> <_> <_>4 1 6 6 -1. <_>6 1 2 6 3. 0 -0.0168517101556063 -0.3485291004180908 0.0453689992427826 <_> <_> <_>15 13 5 4 -1. <_>15 15 5 2 2. 0 0.0299021005630493 0.0316210798919201 -0.4311437010765076 <_> <_> <_>7 17 6 1 -1. <_>9 17 2 1 3. 0 2.8902660124003887e-003 0.0380299612879753 -0.3702709972858429 <_> <_> <_>16 19 4 1 -1. <_>18 19 2 1 2. 0 -1.9242949783802032e-003 0.2480027973651886 -0.0593332983553410 <_> <_> <_>16 16 4 4 -1. <_>18 16 2 4 2. 0 4.9354149959981441e-003 -0.0830684006214142 0.2204380929470062 <_> <_> <_>7 8 9 4 -1. <_>10 8 3 4 3. 0 0.0820756033062935 -0.0194134395569563 0.6908928751945496 <_> <_> <_>16 18 2 2 -1. <_>16 18 1 1 2. <_>17 19 1 1 2. 0 -2.4699489586055279e-004 -0.2466056942939758 0.0647764503955841 <_> <_> <_>2 9 2 4 -1. <_>2 9 1 2 2. <_>3 11 1 2 2. 0 -1.8365769647061825e-003 0.2883616089820862 -0.0533904582262039 <_> <_> <_>0 3 8 4 -1. <_>0 3 4 2 2. <_>4 5 4 2 2. 0 -4.9553811550140381e-003 0.1274082958698273 -0.1255941987037659 <_> <_> <_>0 1 8 1 -1. <_>4 1 4 1 2. 0 -8.3086621016263962e-003 0.2347811013460159 -0.0716764926910400 <_> <_> <_>0 5 8 9 -1. <_>4 5 4 9 2. 0 -0.1087991967797279 -0.2599223852157593 0.0586897395551205 <_> <_> <_>7 18 6 2 -1. <_>9 18 2 2 3. 0 -9.6786450594663620e-003 -0.7072042822837830 0.0187492594122887 <_> <_> <_>0 4 1 12 -1. <_>0 8 1 4 3. 0 -0.0271368306130171 -0.5838422775268555 0.0216841306537390 <_> <_> <_>19 13 1 6 -1. <_>19 15 1 2 3. 0 -6.5389778465032578e-003 -0.5974891185760498 0.0214803107082844 <_> <_> <_>2 8 6 8 -1. <_>4 8 2 8 3. 0 -0.0120956301689148 0.1326903998851776 -0.0997227206826210 <_> <_> <_>0 0 9 17 -1. <_>3 0 3 17 3. 0 -0.1677609980106354 -0.5665506720542908 0.0321230888366699 <_> <_> <_>7 9 6 8 -1. <_>9 9 2 8 3. 0 -0.0132625503465533 0.1149559020996094 -0.1173838973045349 <_> <_> <_>5 10 9 4 -1. <_>8 10 3 4 3. 0 0.0767445191740990 -0.0314132310450077 0.5993549227714539 <_> <_> <_>5 0 8 3 -1. <_>5 1 8 1 3. 0 5.0785229541361332e-003 -0.0529119409620762 0.2334239929914475 <_> <_> <_>16 6 4 4 -1. <_>16 6 2 2 2. <_>18 8 2 2 2. 0 3.1800279393792152e-003 -0.0777343884110451 0.1765290945768356 <_> <_> <_>17 4 2 8 -1. <_>17 4 1 4 2. <_>18 8 1 4 2. 0 -1.7729829996824265e-003 0.1959162950515747 -0.0797521993517876 <_> <_> <_>2 16 1 3 -1. <_>2 17 1 1 3. 0 -4.8560940194875002e-004 -0.2880037128925324 0.0490471199154854 <_> <_> <_>2 16 1 3 -1. <_>2 17 1 1 3. 0 3.6554320831783116e-004 0.0679228976368904 -0.2249943017959595 <_> <_> <_>11 0 1 3 -1. <_>11 1 1 1 3. 0 -2.6938671362586319e-004 0.1658217012882233 -0.0897440984845161 <_> <_> <_>11 2 9 7 -1. <_>14 2 3 7 3. 0 0.0786842331290245 0.0260816793888807 -0.5569373965263367 <_> <_> <_>10 2 3 6 -1. <_>11 2 1 6 3. 0 -7.3774810880422592e-004 0.1403687000274658 -0.1180030032992363 <_> <_> <_>5 9 15 2 -1. <_>5 10 15 1 2. 0 0.0239578299224377 0.0304707400500774 -0.4615997970104218 <_> <_> <_>8 16 6 2 -1. <_>8 17 6 1 2. 0 -1.6239080578088760e-003 0.2632707953453064 -0.0567653700709343 <_> <_> <_>9 16 10 2 -1. <_>9 16 5 1 2. <_>14 17 5 1 2. 0 -9.0819748584181070e-004 0.1546245962381363 -0.1108706966042519 <_> <_> <_>9 17 2 2 -1. <_>9 17 1 1 2. <_>10 18 1 1 2. 0 3.9806248969398439e-004 0.0556303709745407 -0.2833195924758911 <_> <_> <_>10 15 6 4 -1. <_>10 15 3 2 2. <_>13 17 3 2 2. 0 2.0506449509412050e-003 -0.0916048362851143 0.1758553981781006 <_> <_> <_>4 5 15 12 -1. <_>9 5 5 12 3. 0 0.0267425496131182 0.0620030313730240 -0.2448700070381165 <_> <_> <_>11 13 2 3 -1. <_>11 14 2 1 3. 0 -2.1497008856385946e-003 0.2944929897785187 -0.0532181486487389 <_> <_> <_>8 13 7 3 -1. <_>8 14 7 1 3. 0 5.6671658530831337e-003 -0.0642982423305511 0.2490568011999130 <_> <_> <_>1 12 1 2 -1. <_>1 13 1 1 2. 0 6.8317902332637459e-005 -0.1681963056325913 0.0965485796332359 <_> <_> <_>16 18 2 2 -1. <_>16 18 1 1 2. <_>17 19 1 1 2. 0 1.7600439605303109e-004 0.0653080120682716 -0.2426788061857224 <_> <_> <_>1 19 18 1 -1. <_>7 19 6 1 3. 0 4.1861608624458313e-003 -0.0979885831475258 0.1805288940668106 <_> <_> <_>1 17 6 1 -1. <_>4 17 3 1 2. 0 -2.1808340679854155e-003 0.1923127025365830 -0.0941239297389984 <_> <_> <_>1 3 1 12 -1. <_>1 9 1 6 2. 0 0.0217304006218910 0.0355785116553307 -0.4508853852748871 <_> <_> <_>0 9 3 6 -1. <_>0 11 3 2 3. 0 -0.0147802699357271 -0.4392701089382172 0.0317355915904045 <_> <_> <_>5 4 3 10 -1. <_>6 4 1 10 3. 0 -3.6145891062915325e-003 0.1981147974729538 -0.0777014195919037 <_> <_> <_>6 17 2 1 -1. <_>7 17 1 1 2. 0 1.8892709631472826e-003 0.0199624393135309 -0.7204172015190125 <_> <_> <_>1 0 6 12 -1. <_>3 0 2 12 3. 0 -1.3822480104863644e-003 0.0984669476747513 -0.1488108038902283 <_> <_> <_>4 7 9 2 -1. <_>7 7 3 2 3. 0 -3.9505911991000175e-003 0.1159323006868362 -0.1279197037220001 -0.9769343137741089 20 -1 <_> <_> <_> <_>6 11 9 1 -1. <_>9 11 3 1 3. 0 -0.0193955395370722 0.4747475087642670 -0.1172109022736549 <_> <_> <_>17 10 2 10 -1. <_>17 15 2 5 2. 0 0.0131189199164510 -0.2555212974548340 0.1637880057096481 <_> <_> <_>4 10 2 10 -1. <_>4 10 1 5 2. <_>5 15 1 5 2. 0 -5.1606801571324468e-004 0.1945261955261231 -0.1744889020919800 <_> <_> <_>12 3 3 12 -1. <_>13 3 1 12 3. 0 -0.0131841599941254 0.4418145120143890 -0.0900487527251244 <_> <_> <_>15 3 4 6 -1. <_>15 3 2 3 2. <_>17 6 2 3 2. 0 3.4657081123441458e-003 -0.1347709000110626 0.1805634051561356 <_> <_> <_>12 8 3 3 -1. <_>13 8 1 3 3. 0 6.2980200164020061e-003 -0.0541649796068668 0.3603338003158569 <_> <_> <_>4 14 2 4 -1. <_>4 16 2 2 2. 0 1.6879989998415112e-003 -0.1999794989824295 0.1202159970998764 <_> <_> <_>6 16 1 3 -1. <_>6 17 1 1 3. 0 3.6039709812030196e-004 0.1052414029836655 -0.2411606013774872 <_> <_> <_>1 1 2 3 -1. <_>2 1 1 3 2. 0 -1.5276849735528231e-003 0.2813552916049957 -0.0689648166298866 <_> <_> <_>0 2 4 1 -1. <_>2 2 2 1 2. 0 3.5033570602536201e-003 -0.0825195834040642 0.4071359038352966 <_> <_> <_>8 17 12 3 -1. <_>12 17 4 3 3. 0 -4.7337161377072334e-003 0.1972700953483582 -0.1171014010906220 <_> <_> <_>9 16 6 4 -1. <_>11 16 2 4 3. 0 -0.0115571497008204 -0.5606111288070679 0.0681709572672844 <_> <_> <_>4 6 3 6 -1. <_>4 9 3 3 2. 0 -0.0274457205086946 0.4971862137317658 -0.0623801499605179 <_> <_> <_>6 2 12 9 -1. <_>6 5 12 3 3. 0 -0.0528257787227631 0.1692122071981430 -0.1309355050325394 <_> <_> <_>6 0 14 20 -1. <_>6 0 7 10 2. <_>13 10 7 10 2. 0 -0.2984969913959503 -0.6464967131614685 0.0400768183171749 <_> <_> <_>15 16 2 2 -1. <_>15 16 1 1 2. <_>16 17 1 1 2. 0 -2.6307269581593573e-004 0.2512794137001038 -0.0894948393106461 <_> <_> <_>15 16 2 2 -1. <_>15 16 1 1 2. <_>16 17 1 1 2. 0 2.3261709429789335e-004 -0.0868439897894859 0.2383197993040085 <_> <_> <_>19 8 1 3 -1. <_>19 9 1 1 3. 0 2.3631360090803355e-004 0.1155446022748947 -0.1893634945154190 <_> <_> <_>13 4 1 2 -1. <_>13 5 1 1 2. 0 2.0742209162563086e-003 -0.0485948510468006 0.5748599171638489 <_> <_> <_>0 4 4 2 -1. <_>0 5 4 1 2. 0 -7.0308889262378216e-003 -0.5412080883979797 0.0487437509000301 <_> <_> <_>19 5 1 6 -1. <_>19 7 1 2 3. 0 8.2652270793914795e-003 0.0264945197850466 -0.6172845959663391 <_> <_> <_>16 0 2 1 -1. <_>17 0 1 1 2. 0 2.0042760297656059e-004 -0.1176863014698029 0.1633386015892029 <_> <_> <_>13 1 1 3 -1. <_>13 2 1 1 3. 0 1.6470040427520871e-003 -0.0599549189209938 0.3517970144748688 <_> <_> <_>17 17 1 3 -1. <_>17 18 1 1 3. 0 -3.5642538568936288e-004 -0.3442029953002930 0.0649482533335686 <_> <_> <_>5 4 8 8 -1. <_>5 4 4 4 2. <_>9 8 4 4 2. 0 -0.0309358704835176 0.1997970044612885 -0.0976936966180801 <_> <_> <_>1 2 2 2 -1. <_>1 2 1 1 2. <_>2 3 1 1 2. 0 -6.3578772824257612e-004 -0.3148139119148254 0.0594250410795212 <_> <_> <_>0 0 8 6 -1. <_>0 0 4 3 2. <_>4 3 4 3 2. 0 -0.0118621801957488 0.2004369050264359 -0.0894475430250168 <_> <_> <_>6 3 4 2 -1. <_>6 4 4 1 2. 0 7.1508930996060371e-003 -0.0390060618519783 0.5332716107368469 <_> <_> <_>1 0 3 3 -1. <_>1 1 3 1 3. 0 -2.0059191156178713e-003 -0.2846972048282623 0.0707236081361771 <_> <_> <_>6 1 7 2 -1. <_>6 2 7 1 2. 0 3.6412389017641544e-003 -0.1066031977534294 0.2494480013847351 <_> <_> <_>2 6 12 6 -1. <_>6 6 4 6 3. 0 -0.1346742957830429 0.4991008043289185 -0.0403322204947472 <_> <_> <_>1 16 9 2 -1. <_>4 16 3 2 3. 0 -2.2547659464180470e-003 0.1685169041156769 -0.1111928001046181 <_> <_> <_>7 15 6 4 -1. <_>9 15 2 4 3. 0 4.3842289596796036e-003 0.0861394926905632 -0.2743177115917206 <_> <_> <_>6 15 12 1 -1. <_>12 15 6 1 2. 0 -7.3361168615520000e-003 0.2487521022558212 -0.0959191620349884 <_> <_> <_>17 17 1 3 -1. <_>17 18 1 1 3. 0 6.4666912658140063e-004 0.0674315765500069 -0.3375408053398132 <_> <_> <_>17 15 2 2 -1. <_>17 15 1 1 2. <_>18 16 1 1 2. 0 2.2983769304119051e-004 -0.0839030519127846 0.2458409965038300 <_> <_> <_>3 13 3 3 -1. <_>3 14 3 1 3. 0 6.7039071582257748e-003 0.0290793292224407 -0.6905593872070313 <_> <_> <_>10 17 1 3 -1. <_>10 18 1 1 3. 0 5.0734888645820320e-005 -0.1569671928882599 0.1196542978286743 <_> <_> <_>4 0 14 8 -1. <_>11 0 7 8 2. 0 -0.2033555954694748 -0.6950634717941284 0.0275075193494558 <_> <_> <_>2 0 12 2 -1. <_>6 0 4 2 3. 0 9.4939414411783218e-003 -0.0874493718147278 0.2396833002567291 <_> <_> <_>2 0 4 3 -1. <_>4 0 2 3 2. 0 -2.4055240210145712e-003 0.2115096002817154 -0.1314893066883087 <_> <_> <_>13 1 1 2 -1. <_>13 2 1 1 2. 0 -1.1342419747961685e-004 0.1523378938436508 -0.1272590011358261 <_> <_> <_>7 5 3 6 -1. <_>8 5 1 6 3. 0 0.0149922100827098 -0.0341279692947865 0.5062407255172730 <_> <_> <_>18 2 2 2 -1. <_>18 2 1 1 2. <_>19 3 1 1 2. 0 7.4068200774490833e-004 0.0487647503614426 -0.4022532105445862 <_> <_> <_>15 1 2 14 -1. <_>16 1 1 14 2. 0 -4.2459447868168354e-003 0.2155476063489914 -0.0871269926428795 <_> <_> <_>15 6 2 2 -1. <_>15 6 1 1 2. <_>16 7 1 1 2. 0 6.8655109498649836e-004 -0.0754187181591988 0.2640590965747833 <_> <_> <_>3 1 6 3 -1. <_>5 1 2 3 3. 0 -0.0167514607310295 -0.6772903203964233 0.0329187288880348 <_> <_> <_>7 16 2 2 -1. <_>7 16 1 1 2. <_>8 17 1 1 2. 0 -2.6301678735762835e-004 0.2272586971521378 -0.0905348733067513 <_> <_> <_>5 17 2 2 -1. <_>5 17 1 1 2. <_>6 18 1 1 2. 0 4.3398610432632267e-004 0.0558943785727024 -0.3559266924858093 <_> <_> <_>9 10 6 10 -1. <_>11 10 2 10 3. 0 -0.0201501492410898 0.1916276067495346 -0.0949299708008766 <_> <_> <_>10 17 6 3 -1. <_>12 17 2 3 3. 0 -0.0144521296024323 -0.6851034164428711 0.0254221707582474 <_> <_> <_>14 5 2 10 -1. <_>14 10 2 5 2. 0 -0.0211497396230698 0.3753319084644318 -0.0514965802431107 <_> <_> <_>11 12 6 2 -1. <_>11 13 6 1 2. 0 0.0211377702653408 0.0290830805897713 -0.8943036794662476 <_> <_> <_>8 1 1 3 -1. <_>8 2 1 1 3. 0 1.1524349683895707e-003 -0.0696949362754822 0.2729980051517487 <_> <_> <_>12 15 2 2 -1. <_>12 15 1 1 2. <_>13 16 1 1 2. 0 -1.9070580310653895e-004 0.1822811961174011 -0.0983670726418495 <_> <_> <_>6 8 6 4 -1. <_>6 8 3 2 2. <_>9 10 3 2 2. 0 -0.0363496318459511 -0.8369309902191162 0.0250557605177164 <_> <_> <_>7 5 3 5 -1. <_>8 5 1 5 3. 0 -9.0632075443863869e-003 0.4146350026130676 -0.0544134490191936 <_> <_> <_>0 5 7 3 -1. <_>0 6 7 1 3. 0 -2.0535490475594997e-003 -0.1975031048059464 0.1050689965486527 -1.0129359960556030 21 -1 <_> <_> <_> <_>7 9 6 6 -1. <_>9 9 2 6 3. 0 -0.0227170195430517 0.2428855001926422 -0.1474552005529404 <_> <_> <_>5 7 8 8 -1. <_>5 11 8 4 2. 0 0.0255059506744146 -0.2855173945426941 0.1083720996975899 <_> <_> <_>4 9 2 6 -1. <_>4 9 1 3 2. <_>5 12 1 3 2. 0 -2.6640091091394424e-003 0.2927573025226593 -0.1037271022796631 <_> <_> <_>10 11 6 1 -1. <_>12 11 2 1 3. 0 -3.8115289062261581e-003 0.2142689973115921 -0.1381113976240158 <_> <_> <_>13 6 6 11 -1. <_>15 6 2 11 3. 0 -0.0167326908558607 0.2655026018619537 -0.0439113304018974 <_> <_> <_>8 17 2 2 -1. <_>8 17 1 1 2. <_>9 18 1 1 2. 0 4.9277010839432478e-004 0.0211045593023300 -0.4297136068344116 <_> <_> <_>4 12 12 1 -1. <_>8 12 4 1 3. 0 -0.0366911105811596 0.5399242043495178 -0.0436488017439842 <_> <_> <_>11 17 3 2 -1. <_>11 18 3 1 2. 0 1.2615970335900784e-003 -0.1293386965990067 0.1663877069950104 <_> <_> <_>8 17 6 1 -1. <_>10 17 2 1 3. 0 -8.4106856957077980e-003 -0.9469841122627258 0.0214658491313457 <_> <_> <_>4 1 14 6 -1. <_>4 3 14 2 3. 0 0.0649027228355408 -0.0717277601361275 0.2661347985267639 <_> <_> <_>14 2 2 12 -1. <_>14 8 2 6 2. 0 0.0303050000220537 -0.0827824920415878 0.2769432067871094 <_> <_> <_>12 13 3 2 -1. <_>12 14 3 1 2. 0 2.5875340215861797e-003 -0.1296616941690445 0.1775663048028946 <_> <_> <_>6 1 6 1 -1. <_>8 1 2 1 3. 0 -7.0240451022982597e-003 -0.6424317955970764 0.0399432107806206 <_> <_> <_>10 6 6 1 -1. <_>12 6 2 1 3. 0 -1.0099769569933414e-003 0.1417661011219025 -0.1165997013449669 <_> <_> <_>3 19 2 1 -1. <_>4 19 1 1 2. 0 -4.1179071558872238e-005 0.1568766981363297 -0.1112734004855156 <_> <_> <_>18 16 2 2 -1. <_>18 16 1 1 2. <_>19 17 1 1 2. 0 -4.7293151146732271e-004 -0.3355455994606018 0.0459777303040028 <_> <_> <_>16 11 3 7 -1. <_>17 11 1 7 3. 0 -1.7178079579025507e-003 0.1695290952920914 -0.1057806983590126 <_> <_> <_>19 5 1 6 -1. <_>19 8 1 3 2. 0 -0.0133331697434187 -0.5825781226158142 0.0309784300625324 <_> <_> <_>9 8 4 3 -1. <_>9 9 4 1 3. 0 -1.8783430568873882e-003 0.1426687985658646 -0.1113125979900360 <_> <_> <_>16 8 4 4 -1. <_>16 8 2 2 2. <_>18 10 2 2 2. 0 -6.5765981562435627e-003 0.2756136059761047 -0.0531003288924694 <_> <_> <_>2 8 2 2 -1. <_>2 8 1 1 2. <_>3 9 1 1 2. 0 -7.7210381277836859e-005 0.1324024051427841 -0.1116779968142510 <_> <_> <_>3 5 6 4 -1. <_>3 5 3 2 2. <_>6 7 3 2 2. 0 0.0219685398042202 -0.0269681606441736 0.5006716847419739 <_> <_> <_>2 3 8 16 -1. <_>2 3 4 8 2. <_>6 11 4 8 2. 0 -0.0274457503110170 -0.2408674061298370 0.0604782700538635 <_> <_> <_>17 17 1 3 -1. <_>17 18 1 1 3. 0 7.8305849456228316e-005 -0.1333488970994949 0.1012346968054771 <_> <_> <_>7 2 8 11 -1. <_>11 2 4 11 2. 0 0.0701906830072403 -0.0548637807369232 0.2480994015932083 <_> <_> <_>13 3 6 14 -1. <_>16 3 3 14 2. 0 -0.0719021335244179 -0.3784669041633606 0.0422109998762608 <_> <_> <_>0 9 18 2 -1. <_>6 9 6 2 3. 0 -0.1078097969293594 -0.3748658895492554 0.0428334400057793 <_> <_> <_>6 10 14 3 -1. <_>6 11 14 1 3. 0 1.4364200178533792e-003 0.0804763585329056 -0.1726378947496414 <_> <_> <_>10 9 9 3 -1. <_>13 9 3 3 3. 0 0.0682891905307770 -0.0355957895517349 0.4076131880283356 <_> <_> <_>3 5 4 6 -1. <_>3 5 2 3 2. <_>5 8 2 3 2. 0 -6.8037179298698902e-003 0.1923379004001617 -0.0823680236935616 <_> <_> <_>3 7 3 7 -1. <_>4 7 1 7 3. 0 -5.6193489581346512e-004 0.1305712014436722 -0.1435514986515045 <_> <_> <_>2 8 11 6 -1. <_>2 10 11 2 3. 0 -0.0582766495645046 -0.3012543916702271 0.0528196506202221 <_> <_> <_>8 9 6 3 -1. <_>8 10 6 1 3. 0 -6.1205718666315079e-003 0.2204390019178391 -0.0756917521357536 <_> <_> <_>3 3 3 11 -1. <_>4 3 1 11 3. 0 -0.0135943097993732 -0.3904936015605927 0.0418571084737778 <_> <_> <_>0 19 6 1 -1. <_>3 19 3 1 2. 0 1.3626200379803777e-003 -0.0953634232282639 0.1497032046318054 <_> <_> <_>18 18 1 2 -1. <_>18 19 1 1 2. 0 -1.5074219845701009e-004 -0.2394558042287827 0.0647983327507973 <_> <_> <_>8 0 12 6 -1. <_>8 0 6 3 2. <_>14 3 6 3 2. 0 -0.0774142593145370 0.5594198107719421 -0.0245168805122375 <_> <_> <_>19 5 1 3 -1. <_>19 6 1 1 3. 0 9.2117872554808855e-004 0.0549288615584373 -0.2793481051921845 <_> <_> <_>5 8 2 1 -1. <_>6 8 1 1 2. 0 1.0250780032947659e-003 -0.0621673092246056 0.2497636973857880 <_> <_> <_>13 11 2 1 -1. <_>14 11 1 1 2. 0 -8.1174750812351704e-004 0.2343793958425522 -0.0657258108258247 <_> <_> <_>3 6 15 13 -1. <_>8 6 5 13 3. 0 0.0834310203790665 0.0509548000991344 -0.3102098107337952 <_> <_> <_>4 3 6 2 -1. <_>6 3 2 2 3. 0 -9.2014456167817116e-003 -0.3924253880977631 0.0329269506037235 <_> <_> <_>0 18 1 2 -1. <_>0 19 1 1 2. 0 -2.9086650465615094e-004 -0.3103975057601929 0.0497118197381496 <_> <_> <_>7 8 2 6 -1. <_>8 8 1 6 2. 0 7.7576898038387299e-003 -0.0440407507121563 0.3643135130405426 <_> <_> <_>3 0 6 19 -1. <_>5 0 2 19 3. 0 -0.1246609017252922 -0.8195707798004150 0.0191506408154964 <_> <_> <_>3 1 6 5 -1. <_>5 1 2 5 3. 0 0.0132425501942635 0.0389888398349285 -0.3323068022727966 <_> <_> <_>17 14 3 6 -1. <_>17 16 3 2 3. 0 -6.6770128905773163e-003 -0.3579013943672180 0.0404602102935314 <_> <_> <_>17 13 2 6 -1. <_>18 13 1 6 2. 0 -2.7479929849505424e-003 0.2525390088558197 -0.0564278215169907 <_> <_> <_>17 18 2 2 -1. <_>18 18 1 2 2. 0 8.2659651525318623e-004 -0.0719886571168900 0.2278047949075699 <_> <_> <_>11 14 9 4 -1. <_>14 14 3 4 3. 0 -0.0501534007489681 -0.6303647160530090 0.0274620503187180 <_> <_> <_>15 8 4 6 -1. <_>15 8 2 3 2. <_>17 11 2 3 2. 0 7.4203149415552616e-003 -0.0666107162833214 0.2778733968734741 <_> <_> <_>1 16 1 3 -1. <_>1 17 1 1 3. 0 -6.7951780511066318e-004 -0.3632706105709076 0.0427954308688641 <_> <_> <_>7 0 3 14 -1. <_>8 0 1 14 3. 0 -1.9305750029161572e-003 0.1419623047113419 -0.1075998023152351 <_> <_> <_>12 0 2 1 -1. <_>13 0 1 1 2. 0 -3.8132671033963561e-004 0.2159176021814346 -0.0702026635408401 <_> <_> <_>7 9 6 5 -1. <_>10 9 3 5 2. 0 -0.0709903463721275 0.4526660144329071 -0.0407504811882973 <_> <_> <_>15 5 4 9 -1. <_>17 5 2 9 2. 0 -0.0533680804073811 -0.6767405867576599 0.0192883405834436 <_> <_> <_>11 0 6 6 -1. <_>13 0 2 6 3. 0 -0.0200648494064808 -0.4336543083190918 0.0318532884120941 <_> <_> <_>16 15 2 2 -1. <_>16 15 1 1 2. <_>17 16 1 1 2. 0 1.1976360110566020e-003 -0.0265598706901073 0.5079718232154846 <_> <_> <_>16 15 2 2 -1. <_>16 15 1 1 2. <_>17 16 1 1 2. 0 -2.2697300300933421e-004 0.1801259964704514 -0.0836065486073494 <_> <_> <_>13 2 2 18 -1. <_>13 11 2 9 2. 0 0.0152626996859908 -0.2023892998695374 0.0674220174551010 <_> <_> <_>8 4 8 10 -1. <_>8 9 8 5 2. 0 -0.2081176936626434 0.6694386005401611 -0.0224521104246378 <_> <_> <_>8 3 2 3 -1. <_>8 4 2 1 3. 0 1.5514369588345289e-003 -0.0751218423247337 0.1732691973447800 <_> <_> <_>11 1 6 9 -1. <_>11 4 6 3 3. 0 -0.0529240109026432 0.2499251961708069 -0.0628791674971581 <_> <_> <_>15 4 5 6 -1. <_>15 6 5 2 3. 0 -0.0216488502919674 -0.2919428050518036 0.0526144914329052 <_> <_> <_>12 18 2 2 -1. <_>12 18 1 1 2. <_>13 19 1 1 2. 0 -2.2905069636180997e-004 -0.2211730033159256 0.0631683394312859 <_> <_> <_>1 17 1 3 -1. <_>1 18 1 1 3. 0 5.0170070608146489e-005 -0.1151070967316628 0.1161144003272057 <_> <_> <_>12 19 2 1 -1. <_>13 19 1 1 2. 0 -1.6416069411206990e-004 0.1587152034044266 -0.0826006010174751 <_> <_> <_>8 10 6 6 -1. <_>10 10 2 6 3. 0 -0.0120032895356417 0.1221809014678001 -0.1122969985008240 <_> <_> <_>14 2 6 5 -1. <_>16 2 2 5 3. 0 -0.0177841000258923 -0.3507278859615326 0.0313419215381145 <_> <_> <_>9 5 2 6 -1. <_>9 7 2 2 3. 0 -6.3457582145929337e-003 0.1307806968688965 -0.1057441011071205 <_> <_> <_>1 15 2 2 -1. <_>2 15 1 2 2. 0 -7.9523242311552167e-004 0.1720467060804367 -0.0860019922256470 <_> <_> <_>18 17 1 3 -1. <_>18 18 1 1 3. 0 -3.1029590172693133e-004 -0.2843317091464996 0.0518171191215515 <_> <_> <_>10 14 4 6 -1. <_>10 16 4 2 3. 0 -0.0170537102967501 0.3924242854118347 -0.0401432700455189 <_> <_> <_>9 7 3 2 -1. <_>10 7 1 2 3. 0 4.6504959464073181e-003 -0.0318375602364540 0.4123769998550415 <_> <_> <_>6 9 6 2 -1. <_>6 9 3 1 2. <_>9 10 3 1 2. 0 -0.0103587601333857 -0.5699319839477539 0.0292483791708946 <_> <_> <_>0 2 1 12 -1. <_>0 6 1 4 3. 0 -0.0221962407231331 -0.4560528993606567 0.0262859892100096 <_> <_> <_>4 0 15 1 -1. <_>9 0 5 1 3. 0 -7.0536029525101185e-003 0.1599832028150559 -0.0915948599576950 <_> <_> <_>9 0 8 2 -1. <_>9 0 4 1 2. <_>13 1 4 1 2. 0 -5.7094299700111151e-004 -0.1407632976770401 0.1028741970658302 <_> <_> <_>12 2 8 1 -1. <_>16 2 4 1 2. 0 -2.2152599412947893e-003 0.1659359931945801 -0.0852739885449409 <_> <_> <_>7 1 10 6 -1. <_>7 3 10 2 3. 0 -0.0280848909169436 0.2702234089374542 -0.0558738112449646 <_> <_> <_>18 6 2 3 -1. <_>18 7 2 1 3. 0 2.1515151020139456e-003 0.0424728915095329 -0.3200584948062897 <_> <_> <_>4 12 2 2 -1. <_>4 12 1 1 2. <_>5 13 1 1 2. 0 -2.9733829433098435e-004 0.1617716997861862 -0.0851155892014503 <_> <_> <_>6 6 6 2 -1. <_>8 6 2 2 3. 0 -0.0166947804391384 -0.4285877048969269 0.0305416099727154 <_> <_> <_>0 9 9 6 -1. <_>3 9 3 6 3. 0 0.1198299005627632 -0.0162772908806801 0.7984678149223328 <_> <_> <_>17 18 2 2 -1. <_>18 18 1 2 2. 0 -3.5499420482665300e-004 0.1593593955039978 -0.0832728818058968 <_> <_> <_>11 2 6 16 -1. <_>13 2 2 16 3. 0 -0.0182262696325779 0.1952728033065796 -0.0739398896694183 <_> <_> <_>2 4 15 13 -1. <_>7 4 5 13 3. 0 -4.0238600922748446e-004 0.0791018083691597 -0.2080612927675247 <_> <_> <_>16 2 3 10 -1. <_>17 2 1 10 3. 0 4.0892060496844351e-004 0.1003663018345833 -0.1512821018695831 <_> <_> <_>6 10 2 1 -1. <_>7 10 1 1 2. 0 9.5368112670257688e-004 -0.0730116665363312 0.2175202071666718 <_> <_> <_>1 1 18 16 -1. <_>10 1 9 16 2. 0 0.4308179914951325 -0.0274506993591785 0.5706158280372620 <_> <_> <_>14 4 3 15 -1. <_>15 4 1 15 3. 0 5.3564831614494324e-004 0.1158754006028175 -0.1279056072235107 <_> <_> <_>19 13 1 2 -1. <_>19 14 1 1 2. 0 2.4430730263702571e-005 -0.1681662946939468 0.0804499834775925 <_> <_> <_>2 6 5 8 -1. <_>2 10 5 4 2. 0 -0.0553456507623196 0.4533894956111908 -0.0312227793037891 -0.9774749279022217 22 -1 ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/AutoStraighten.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.auto_straight import android.graphics.Bitmap import androidx.compose.ui.geometry.Offset import com.t8rin.opencv_tools.auto_straight.model.Corners import com.t8rin.opencv_tools.auto_straight.model.StraightenMode import com.t8rin.opencv_tools.free_corners_crop.FreeCrop import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Core import org.opencv.core.Mat import org.opencv.core.MatOfPoint import org.opencv.core.MatOfPoint2f import org.opencv.core.Point import org.opencv.core.Rect import org.opencv.core.Size import org.opencv.imgproc.Imgproc import org.opencv.photo.Photo import kotlin.math.abs import kotlin.math.atan2 import kotlin.math.cos import kotlin.math.pow import kotlin.math.sin import kotlin.math.sqrt object AutoStraighten : OpenCV() { fun process( input: Bitmap, mode: StraightenMode ): Bitmap = when (mode) { is StraightenMode.Perspective -> autoPerspective(input = input) is StraightenMode.Deskew -> autoDeskew( input = input, maxSkew = mode.maxSkew, allowCrop = mode.allowCrop ) is StraightenMode.Manual -> perspectiveFromPoints(input = input, corners = mode.corners) } private fun autoDeskew(input: Bitmap, maxSkew: Int, allowCrop: Boolean): Bitmap { val srcMat = input.toMat() val gray = Mat() Imgproc.cvtColor(srcMat, gray, Imgproc.COLOR_BGR2GRAY) Photo.fastNlMeansDenoising(gray, gray, 3f) val binary = Mat() Imgproc.threshold( gray, binary, 0.0, 255.0, Imgproc.THRESH_BINARY_INV or Imgproc.THRESH_OTSU ) val lines = Mat() Imgproc.HoughLinesP( binary, lines, 1.0, Math.PI / 180, 200, srcMat.width() / 12.0, srcMat.width() / 150.0 ) if (lines.rows() == 0) return input val angles = mutableListOf() for (i in 0 until lines.rows()) { val l = lines.get(i, 0) val x1 = l[0] val y1 = l[1] val x2 = l[2] val y2 = l[3] angles += atan2(y2 - y1, x2 - x1) } val landscape = angles.count { abs(it) > Math.PI / 4 } > angles.size / 2 val filtered = if (landscape) { angles.filter { val deg = abs(Math.toDegrees(it)) deg > (90 - maxSkew) && deg < (90 + maxSkew) } } else { angles.filter { abs(Math.toDegrees(it)) < maxSkew } } if (filtered.size < 5) return input var angleDeg = Math.toDegrees(filtered.median()) val rotated = Mat() if (landscape) { angleDeg = if (angleDeg < 0) { Core.rotate(srcMat, rotated, Core.ROTATE_90_CLOCKWISE) angleDeg + 90 } else { Core.rotate(srcMat, rotated, Core.ROTATE_90_COUNTERCLOCKWISE) angleDeg - 90 } } else { srcMat.copyTo(rotated) } val center = Point(rotated.width() / 2.0, rotated.height() / 2.0) val rotMat = Imgproc.getRotationMatrix2D(center, angleDeg, 1.0) val angleRad = Math.toRadians(angleDeg) val absSin = abs(sin(angleRad)) val absCos = abs(cos(angleRad)) val newWidth = (rotated.height() * absSin + rotated.width() * absCos).toInt() val newHeight = (rotated.height() * absCos + rotated.width() * absSin).toInt() rotMat.put(0, 2, rotMat.get(0, 2)[0] + (newWidth / 2.0 - center.x)) rotMat.put(1, 2, rotMat.get(1, 2)[0] + (newHeight / 2.0 - center.y)) val rotatedFull = Mat() Imgproc.warpAffine( rotated, rotatedFull, rotMat, Size(newWidth.toDouble(), newHeight.toDouble()), Imgproc.INTER_LINEAR, Core.BORDER_REPLICATE, ) return if (allowCrop) { val cropRect = getLargestRotatedRect( width = rotated.width(), height = rotated.height(), angle = angleRad ) Mat(rotatedFull, cropRect).clone() } else { rotatedFull }.toBitmap() } private fun autoPerspective(input: Bitmap): Bitmap { val srcMat = input.toMat() val gray = Mat() Imgproc.cvtColor(srcMat, gray, Imgproc.COLOR_BGR2GRAY) Photo.fastNlMeansDenoising(gray, gray, 3f) val binary = Mat() Imgproc.threshold( gray, binary, 0.0, 255.0, Imgproc.THRESH_BINARY or Imgproc.THRESH_OTSU ) val contours = mutableListOf() Imgproc.findContours( binary.clone(), contours, Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE ) val biggest = contours .mapNotNull { contour -> val approx = MatOfPoint2f() val c2f = MatOfPoint2f(*contour.toArray()) Imgproc.approxPolyDP(c2f, approx, Imgproc.arcLength(c2f, true) * 0.02, true) if (approx.total() == 4L && Imgproc.isContourConvex(MatOfPoint(*approx.toArray()))) approx else null } .maxByOrNull { Imgproc.contourArea(MatOfPoint(*it.toArray())) } ?: return input val sorted = sortCorners(biggest.toArray()) val widthA = distance(sorted[0], sorted[1]) val widthB = distance(sorted[2], sorted[3]) val maxWidth = maxOf(widthA, widthB).toInt() val heightA = distance(sorted[0], sorted[3]) val heightB = distance(sorted[1], sorted[2]) val maxHeight = maxOf(heightA, heightB).toInt() val dst = MatOfPoint2f( Point(0.0, 0.0), Point(maxWidth.toDouble(), 0.0), Point(maxWidth.toDouble(), maxHeight.toDouble()), Point(0.0, maxHeight.toDouble()) ) val transform = Imgproc.getPerspectiveTransform(MatOfPoint2f(*sorted), dst) val out = Mat() Imgproc.warpPerspective( srcMat, out, transform, Size(maxWidth.toDouble(), maxHeight.toDouble()) ) return out.toBitmap() } private fun perspectiveFromPoints(input: Bitmap, corners: Corners): Bitmap { val width = input.width val height = input.height val absPoints = if (corners.isAbsolute) { corners.points.map { Offset( x = it.x.toFloat(), y = it.y.toFloat() ) } } else { corners.points.map { Offset( x = (it.x * width).toFloat(), y = (it.y * height).toFloat() ) } } return FreeCrop.crop( bitmap = input, points = absPoints ) } private fun sortCorners(pts: Array): Array { val sum = pts.sortedBy { it.y + it.x } val diff = pts.sortedBy { it.y - it.x } return arrayOf( sum.first(), diff.first(), sum.last(), diff.last() ) } private fun distance(p1: Point, p2: Point): Double = sqrt((p1.x - p2.x).pow(2.0) + (p1.y - p2.y).pow(2.0)) private fun getLargestRotatedRect(width: Int, height: Int, angle: Double): Rect { val absSin = abs(sin(angle)) val absCos = abs(cos(angle)) val boundWidth = (width * absCos - height * absSin).coerceAtLeast(0.0) val boundHeight = (height * absCos - width * absSin).coerceAtLeast(0.0) val newWidth = (height * absSin + width * absCos) val newHeight = (height * absCos + width * absSin) val x = ((newWidth - boundWidth) / 2.0).toInt() val y = ((newHeight - boundHeight) / 2.0).toInt() return Rect(x, y, boundWidth.toInt(), boundHeight.toInt()) } private fun List.median(): Double { if (isEmpty()) return 0.0 val sorted = this.sorted() return if (size % 2 == 0) (sorted[size / 2 - 1] + sorted[size / 2]) / 2 else sorted[size / 2] } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/Corners.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.auto_straight.model data class Corners( val topLeft: PointD, val topRight: PointD, val bottomRight: PointD, val bottomLeft: PointD, val isAbsolute: Boolean = true ) { val points = listOf(topLeft, topRight, bottomRight, bottomLeft) } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/PointD.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.auto_straight.model data class PointD(val x: Double, val y: Double) ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/auto_straight/model/StraightenMode.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.auto_straight.model import androidx.annotation.IntRange sealed class StraightenMode { data object Perspective : StraightenMode() data class Deskew( @param:IntRange(0, 90) val maxSkew: Int = 10, val allowCrop: Boolean = true ) : StraightenMode() data class Manual(val corners: Corners) : StraightenMode() } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/AutoCropper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.autocrop import android.graphics.Bitmap import com.t8rin.opencv_tools.autocrop.model.CropEdges import com.t8rin.opencv_tools.autocrop.model.CropParameters import com.t8rin.opencv_tools.autocrop.model.CropSensitivity import com.t8rin.opencv_tools.autocrop.model.edgeCandidateThreshold import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.multiChannelMean import com.t8rin.opencv_tools.utils.singleChannelMean import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.imgproc.Imgproc object AutoCropper : OpenCV() { fun crop( bitmap: Bitmap, @CropSensitivity sensitivity: Int ): Bitmap? = bitmap.findEdges(sensitivity)?.run { bitmap.cropByEdges(edges) } private fun Bitmap.cropByEdges( edges: CropEdges ): Bitmap = Bitmap.createBitmap( this, 0, edges.top + 1, width, edges.height - 1 ) private fun Bitmap.findEdges( @CropSensitivity sensitivity: Int ): CropParameters? { val matRGBA = toMat() return getEdgeCandidates(matRGBA, sensitivity)?.let { CropParameters( edges = getMaxScoreCropEdges(candidates = it, matRGBA = matRGBA), candidates = it ) } } private fun getEdgeCandidates(matRGBA: Mat, @CropSensitivity sensitivity: Int): List? { // Convert to gray scale val matGrayScale = Mat() Imgproc.cvtColor(matRGBA, matGrayScale, Imgproc.COLOR_RGBA2GRAY) Imgproc.medianBlur(matGrayScale, matGrayScale, 3) // Get canny edge detected matrix val matCanny = Mat() Imgproc.Canny(matGrayScale, matCanny, 100.0, 200.0) // Convert sensitivity to threshold val threshold = edgeCandidateThreshold(sensitivity) return (0 until matCanny.rows()).filter { i -> matCanny.row(i).singleChannelMean() > threshold } .run { if (isEmpty()) null else listOf(0) + this + listOf(matCanny.rows()) } } private fun getMaxScoreCropEdges(candidates: List, matRGBA: Mat): CropEdges { val matSobel = Mat() Imgproc.Sobel(matRGBA, matSobel, CvType.CV_16U, 2, 2, 5) var maxScore = 0f var maxScoreEdges: CropEdges? = null candidates.windowed(2) .map { CropEdges(it) } .forEach { edges -> val cropAreaMean: Float = matSobel.rowRange(edges.top, edges.bottom).multiChannelMean().toFloat() val heightPortion: Float = edges.height.toFloat() / matSobel.rows().toFloat() val score: Float = cropAreaMean * heightPortion if (score > maxScore) { maxScore = score maxScoreEdges = edges } } return maxScoreEdges!! } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropEdges.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.autocrop.model import android.os.Parcelable import kotlinx.parcelize.Parcelize @Parcelize data class CropEdges(val top: Int, val bottom: Int) : Parcelable { constructor(edges: Pair) : this(edges.first, edges.second) constructor(edges: List) : this(edges.first(), edges.last()) val height: Int get() = bottom - top } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropParameters.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.autocrop.model data class CropParameters( val edges: CropEdges, val candidates: List ) ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/autocrop/model/CropSensitivity.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.autocrop.model import androidx.annotation.IntRange /** * := (255 - [EDGE_CANDIDATE_THRESHOLD_MIN]) / [CROP_SENSITIVITY_MAX] */ private const val EDGE_CANDIDATE_THRESHOLD_PER_SENSITIVITY_STEP: Float = 20.5f private const val EDGE_CANDIDATE_THRESHOLD_MIN: Int = 50 private const val CROP_SENSITIVITY_MAX: Int = 10 @Retention(AnnotationRetention.BINARY) @IntRange(from = 0, to = CROP_SENSITIVITY_MAX.toLong()) annotation class CropSensitivity @IntRange(50, 255) internal fun edgeCandidateThreshold(@CropSensitivity cropSensitivity: Int): Int = ((CROP_SENSITIVITY_MAX - cropSensitivity) * EDGE_CANDIDATE_THRESHOLD_PER_SENSITIVITY_STEP).toInt() + EDGE_CANDIDATE_THRESHOLD_MIN ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/ColorMap.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.color_map import android.graphics.Bitmap import com.t8rin.opencv_tools.color_map.model.ColorMapType import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Mat import org.opencv.imgproc.Imgproc object ColorMap : OpenCV() { fun apply( bitmap: Bitmap, map: ColorMapType = ColorMapType.JET ): Bitmap { val grayMat = bitmap.toMat() Imgproc.cvtColor(grayMat, grayMat, Imgproc.COLOR_RGBA2BGR) Imgproc.cvtColor(grayMat, grayMat, Imgproc.COLOR_BGR2GRAY) val colorMat = Mat() Imgproc.applyColorMap(grayMat, colorMat, map.ordinal) Imgproc.cvtColor(colorMat, colorMat, Imgproc.COLOR_BGR2RGBA) grayMat.release() return colorMat.toBitmap() } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/color_map/model/ColorMapType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.color_map.model enum class ColorMapType { AUTUMN, BONE, JET, WINTER, RAINBOW, OCEAN, SUMMER, SPRING, COOL, HSV, PINK, HOT, PARULA, MAGMA, INFERNO, PLASMA, VIRIDIS, CIVIDIS, TWILIGHT, TWILIGHT_SHIFTED, TURBO, DEEPGREEN } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/document_detector/DocumentDetector.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.document_detector import android.graphics.Bitmap import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Core import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.core.MatOfPoint import org.opencv.core.MatOfPoint2f import org.opencv.core.Point import org.opencv.core.Size import org.opencv.imgproc.Imgproc /** * This class uses OpenCV to find document corners. * * @constructor creates document detector */ object DocumentDetector : OpenCV() { /** * take a photo with a document, and find the document's corners * * @param image a photo with a document * @return a list with document corners (top left, top right, bottom right, bottom left) */ fun findDocumentCorners(image: Bitmap): List? { // convert bitmap to OpenCV matrix val mat = image.toMat() // shrink photo to make it easier to find document corners val shrunkImageHeight = 500.0 Imgproc.resize( mat, mat, Size( shrunkImageHeight * image.width / image.height, shrunkImageHeight ) ) // convert photo to LUV colorspace to avoid glares caused by lights Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2Luv) // separate photo into 3 parts, (L, U, and V) val imageSplitByColorChannel: List = mutableListOf() Core.split(mat, imageSplitByColorChannel) // find corners for each color channel, then pick the quad with the largest // area, and scale point to account for shrinking image before document detection val documentCorners: List? = imageSplitByColorChannel .mapNotNull { findCorners(it) } .maxByOrNull { Imgproc.contourArea(it) } ?.toList() ?.map { Point( it.x * image.height / shrunkImageHeight, it.y * image.height / shrunkImageHeight ) } // sort points to force this order (top left, top right, bottom left, bottom right) return documentCorners ?.sortedBy { it.y } ?.chunked(2) ?.map { it.sortedBy { point -> point.x } } ?.flatten() } /** * take an image matrix with a document, and find the document's corners * * @param image a photo with a document in matrix format (only 1 color space) * @return a matrix with document corners or null if we can't find corners */ private fun findCorners(image: Mat): MatOfPoint? { val outputImage = Mat() // blur image to help remove noise Imgproc.GaussianBlur(image, outputImage, Size(5.0, 5.0), 0.0) // convert all pixels to either black or white (document should be black after this), but // there might be other parts of the photo that turn black Imgproc.threshold( outputImage, outputImage, 0.0, 255.0, Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU ) // detect the document's border using the Canny edge detection algorithm Imgproc.Canny(outputImage, outputImage, 50.0, 200.0) // the detect edges might have gaps, so try to close those Imgproc.morphologyEx( outputImage, outputImage, Imgproc.MORPH_CLOSE, Mat.ones(Size(5.0, 5.0), CvType.CV_8U) ) // get outline of document edges, and outlines of other shapes in photo val contours: MutableList = mutableListOf() Imgproc.findContours( outputImage, contours, Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE ) // approximate outlines using polygons var approxContours = contours.map { val approxContour = MatOfPoint2f() val contour2f = MatOfPoint2f(*it.toArray()) Imgproc.approxPolyDP( contour2f, approxContour, 0.02 * Imgproc.arcLength(contour2f, true), true ) MatOfPoint(*approxContour.toArray()) } // We now have many polygons, so remove polygons that don't have 4 sides since we // know the document has 4 sides. Calculate areas for all remaining polygons, and // remove polygons with small areas. We assume that the document takes up a large portion // of the photo. Remove polygons that aren't convex since a document can't be convex. approxContours = approxContours.filter { it.height() == 4 && Imgproc.contourArea(it) > 1000 && Imgproc.isContourConvex(it) } // Once we have all large, convex, 4-sided polygons find and return the 1 with the // largest area return approxContours.maxByOrNull { Imgproc.contourArea(it) } } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/forensics/ImageForensics.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.forensics import android.graphics.Bitmap import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.android.Utils import org.opencv.core.Core import org.opencv.core.CvType import org.opencv.core.CvType.CV_32F import org.opencv.core.CvType.CV_8U import org.opencv.core.Mat import org.opencv.core.MatOfByte import org.opencv.core.MatOfInt import org.opencv.core.Scalar import org.opencv.imgcodecs.Imgcodecs import org.opencv.imgproc.Imgproc import java.util.Random import kotlin.math.abs import kotlin.math.atan2 import kotlin.math.cos import kotlin.math.hypot import kotlin.math.sin object ImageForensics : OpenCV() { fun errorLevelAnalysis( input: Bitmap, quality: Int = 90 ): Bitmap { val src = input.toMat() Utils.bitmapToMat(input, src) Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2BGR) val buf = MatOfByte() val params = MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, quality) Imgcodecs.imencode(".jpg", src, buf, params) val resaved = Imgcodecs.imdecode(buf, Imgcodecs.IMREAD_COLOR) val diff = Mat() Core.absdiff(src, resaved, diff) val dst = Mat() Core.normalize(diff, dst, 0.0, 255.0, Core.NORM_MINMAX) dst.convertTo(dst, CvType.CV_8UC3) Imgproc.cvtColor(dst, dst, Imgproc.COLOR_BGR2RGBA) val outBmp = dst.toBitmap() src.release() resaved.release() diff.release() dst.release() buf.release() return outBmp } fun luminanceGradient(input: Bitmap): Bitmap { val src = input.toMat() Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2BGR) val gray = Mat() Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY) gray.convertTo(gray, CV_32F) val sobelX = Mat() val sobelY = Mat() Imgproc.Sobel(gray, sobelX, CV_32F, 1, 0, 3, 1.0, 0.0) Imgproc.Sobel(gray, sobelY, CV_32F, 0, 1, 3, 1.0, 0.0) val magnitude = Mat() Core.magnitude(sobelX, sobelY, magnitude) val rows = magnitude.rows() val cols = magnitude.cols() val dst = Mat(rows, cols, CvType.CV_32FC3) val sxRow = FloatArray(cols) val syRow = FloatArray(cols) val magRow = FloatArray(cols) for (r in 0 until rows) { sobelX.get(r, 0, sxRow) sobelY.get(r, 0, syRow) magnitude.get(r, 0, magRow) val outRow = FloatArray(cols * 3) for (c in 0 until cols) { val angle = atan2(sxRow[c].toDouble(), syRow[c].toDouble()).toFloat() val mag = magRow[c] val g = (-sin(angle.toDouble()) / 2.0 + 0.5).toFloat() val rchan = (-cos(angle.toDouble()) / 2.0 + 0.5).toFloat() outRow[c * 3 + 0] = mag outRow[c * 3 + 1] = g outRow[c * 3 + 2] = rchan } dst.put(r, 0, outRow) } val channels = ArrayList(3) Core.split(dst, channels) Core.normalize(channels[0], channels[0], 0.0, 1.0, Core.NORM_MINMAX) Core.merge(channels, dst) dst.convertTo(dst, CvType.CV_8UC3, 255.0) Imgproc.cvtColor(dst, dst, Imgproc.COLOR_BGR2RGBA) val outBmp = dst.toBitmap() gray.release() sobelX.release() sobelY.release() magnitude.release() for (m in channels) m.release() dst.release() src.release() return outBmp } fun averageDistance(input: Bitmap): Bitmap { val src = input.toMat() Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2BGR) val src32 = Mat() src.convertTo(src32, CV_32F, 1.0 / 255.0) val kernel = Mat(3, 3, CV_32F) kernel.put(0, 0, 0.0, 0.25, 0.0) kernel.put(1, 0, 0.25, 0.0, 0.25) kernel.put(2, 0, 0.0, 0.25, 0.0) val filtered = Mat() Imgproc.filter2D(src32, filtered, -1, kernel) val diff = Mat() Core.absdiff(src32, filtered, diff) Core.normalize(diff, diff, 0.0, 1.0, Core.NORM_MINMAX) diff.convertTo(diff, CvType.CV_8UC3, 255.0) Imgproc.cvtColor(diff, diff, Imgproc.COLOR_BGR2RGBA) val outBmp = diff.toBitmap() src.release() src32.release() kernel.release() filtered.release() diff.release() return outBmp } fun detectCopyMove( input: Bitmap, retain: Int = 4, qCoefficent: Double = 1.0 ): Bitmap { val srcColor = input.toMat() val srcGray = Mat() Imgproc.cvtColor(srcColor, srcGray, Imgproc.COLOR_BGR2GRAY) srcGray.convertTo(srcGray, CV_32F) val blockSize = 16 val blocksHeight = srcGray.rows() - blockSize + 1 val blocksWidth = srcGray.cols() - blockSize + 1 val totalBlocks = blocksHeight * blocksWidth val blocks = ArrayList(totalBlocks) val tmp = Mat() for (y in 0 until blocksHeight) { for (x in 0 until blocksWidth) { val roi = srcGray.submat(y, y + blockSize, x, x + blockSize) Core.dct(roi, tmp) Core.divide(tmp, Scalar(qCoefficent), tmp) tmp.convertTo(tmp, CV_8U) blocks.add(tmp.submat(0, retain, 0, retain).clone()) } } val index = Array(totalBlocks) { it } index.sortWith { a, b -> val aBytes = ByteArray(retain * retain) val bBytes = ByteArray(retain * retain) blocks[a].get(0, 0, aBytes) blocks[b].get(0, 0, bBytes) for (i in aBytes.indices) { val diff = aBytes[i].toInt() - bBytes[i].toInt() if (diff != 0) return@sortWith diff } 0 } val sCount = IntArray(srcGray.rows() * srcGray.cols() * 2) val rectBuffer = srcColor.clone() for (i in 0 until totalBlocks - 1) { val aBytes = ByteArray(retain * retain) val bBytes = ByteArray(retain * retain) blocks[index[i]].get(0, 0, aBytes) blocks[index[i + 1]].get(0, 0, bBytes) if (aBytes.contentEquals(bBytes)) { val curX = index[i] % blocksWidth val curY = index[i] / blocksWidth val nextX = index[i + 1] % blocksWidth val nextY = index[i + 1] / blocksWidth val shiftX = abs(curX - nextX) var shiftY = abs(curY - nextY) val magnitude = hypot(shiftX.toDouble(), shiftY.toDouble()) shiftY += srcGray.rows() if (magnitude > blockSize) sCount[shiftY * srcGray.cols() + shiftX]++ } } for (i in 0 until totalBlocks - 1) { val aBytes = ByteArray(retain * retain) val bBytes = ByteArray(retain * retain) blocks[index[i]].get(0, 0, aBytes) blocks[index[i + 1]].get(0, 0, bBytes) if (aBytes.contentEquals(bBytes)) { val curX = index[i] % blocksWidth val curY = index[i] / blocksWidth val nextX = index[i + 1] % blocksWidth val nextY = index[i + 1] / blocksWidth val shiftX = abs(curX - nextX) var shiftY = abs(curY - nextY) val magnitude = hypot(shiftX.toDouble(), shiftY.toDouble()) shiftY += srcGray.rows() if (sCount[shiftY * srcGray.cols() + shiftX] > 10) { val rng = Random(magnitude.toLong()) val color = Scalar( rng.nextInt(256).toDouble(), rng.nextInt(256).toDouble(), rng.nextInt(256).toDouble() ) for (ii in 0 until blockSize) { for (jj in 0 until blockSize) { rectBuffer.put(curY + ii, curX + jj, *color.`val`) rectBuffer.put(nextY + ii, nextX + jj, *color.`val`) } } } } } val dst = Mat() Core.addWeighted(srcColor, 0.2, rectBuffer, 0.8, 0.0, dst) return dst.toBitmap() } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/FreeCrop.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.free_corners_crop import android.graphics.Bitmap import android.graphics.PointF import androidx.compose.ui.geometry.Offset import com.t8rin.opencv_tools.free_corners_crop.model.Quad import com.t8rin.opencv_tools.free_corners_crop.model.distance import com.t8rin.opencv_tools.free_corners_crop.model.toOpenCVPoint import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Mat import org.opencv.core.MatOfPoint2f import org.opencv.core.Point import org.opencv.core.Size import org.opencv.imgproc.Imgproc import kotlin.math.min object FreeCrop : OpenCV() { fun crop( bitmap: Bitmap, points: List ): Bitmap { val corners = Quad( topLeftCorner = PointF(points[0].x, points[0].y), topRightCorner = PointF(points[1].x, points[1].y), bottomRightCorner = PointF(points[2].x, points[2].y), bottomLeftCorner = PointF(points[3].x, points[3].y) ) val image = bitmap.toMat() // convert top left, top right, bottom right, and bottom left document corners from // Android points to OpenCV points val tLC = corners.topLeftCorner.toOpenCVPoint() val tRC = corners.topRightCorner.toOpenCVPoint() val bRC = corners.bottomRightCorner.toOpenCVPoint() val bLC = corners.bottomLeftCorner.toOpenCVPoint() val width = min(tLC.distance(tRC), bLC.distance(bRC)) val height = min(tLC.distance(bLC), tRC.distance(bRC)) // create empty image matrix with cropped and warped document width and height val croppedImage = MatOfPoint2f( Point(0.0, 0.0), Point(width, 0.0), Point(width, height), Point(0.0, height), ) val output = Mat() Imgproc.warpPerspective( image, output, Imgproc.getPerspectiveTransform( MatOfPoint2f(tLC, tRC, bRC, bLC), croppedImage ), Size(width, height) ) return output.toBitmap() } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/compose/FreeCornersCropper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.free_corners_crop.compose import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.magnifier import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import coil3.imageLoader import coil3.request.ImageRequest import coil3.request.allowHardware import coil3.toBitmap import com.t8rin.gesture.observePointersCountWithOffset import com.t8rin.image.ImageWithConstraints import com.t8rin.opencv_tools.free_corners_crop.FreeCrop import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable import kotlin.math.roundToInt @Composable fun FreeCornersCropper( imageModel: Any?, croppingTrigger: Boolean, onCropped: (Bitmap) -> Unit, modifier: Modifier = Modifier, showMagnifier: Boolean = true, handlesSize: Dp = 8.dp, frameStrokeWidth: Dp = 1.2.dp, coercePointsToImageArea: Boolean = true, overlayColor: Color = Color.Black.copy(0.5f), contentPadding: PaddingValues = PaddingValues(24.dp), containerModifier: Modifier = Modifier, onLoadingStateChange: (Boolean) -> Unit = {} ) { var bitmap by remember { mutableStateOf(null) } val context = LocalContext.current LaunchedEffect(imageModel) { bitmap = if (imageModel is Bitmap?) imageModel else { onLoadingStateChange(true) context.imageLoader.execute( ImageRequest.Builder(context).data(imageModel) .allowHardware(false).build() ).image?.toBitmap() } onLoadingStateChange(false) } AnimatedContent( targetState = bitmap, modifier = containerModifier, transitionSpec = { fadeIn() togetherWith fadeOut() } ) { image -> if (image != null) { FreeCornersCropper( bitmap = image, croppingTrigger = croppingTrigger, onCropped = onCropped, modifier = modifier, showMagnifier = showMagnifier, contentPadding = contentPadding, coercePointsToImageArea = coercePointsToImageArea, handlesSize = handlesSize, frameStrokeWidth = frameStrokeWidth, overlayColor = overlayColor ) } } } @Composable fun FreeCornersCropper( bitmap: Bitmap, croppingTrigger: Boolean, onCropped: (Bitmap) -> Unit, modifier: Modifier = Modifier, showMagnifier: Boolean = true, handlesSize: Dp = 8.dp, frameStrokeWidth: Dp = 1.2.dp, coercePointsToImageArea: Boolean = true, overlayColor: Color = Color.Black.copy(0.5f), contentPadding: PaddingValues = PaddingValues(24.dp) ) { val density = LocalDensity.current val handleRadiusPx = with(density) { handlesSize.toPx() } val frameStrokeWidthPx = with(density) { frameStrokeWidth.toPx() } val touchIndex = remember { mutableIntStateOf(-1) } var globalTouchPointersCount by remember { mutableIntStateOf(0) } val colorScheme = MaterialTheme.colorScheme val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() } var magnifierCenter by remember { mutableStateOf(Offset.Unspecified) } ImageWithConstraints( modifier = modifier .clipToBounds() .observePointersCountWithOffset { size, _ -> globalTouchPointersCount = size } .drawBehind { drawRect(overlayColor) } .zoomable( zoomState = rememberZoomState(maxScale = 10f), zoomEnabled = globalTouchPointersCount >= 2 ), imageBitmap = imageBitmap, drawImage = false ) { var imageWidth by remember { mutableIntStateOf(bitmap.width) } var imageHeight by remember { mutableIntStateOf(bitmap.height) } val internalPaddingDp = 16.dp val internalPadding = with(density) { internalPaddingDp.toPx() } var topOffset by remember { mutableIntStateOf(0) } var startOffset by remember { mutableIntStateOf(0) } val drawPoints = rememberSaveable( topOffset, startOffset, imageWidth, imageHeight, contentPadding, stateSaver = OffsetListSaver ) { mutableStateOf( listOf( Offset( x = internalPadding + startOffset, y = internalPadding + topOffset ), Offset( x = imageWidth - internalPadding + startOffset, y = internalPadding + topOffset ), Offset( x = imageWidth - internalPadding + startOffset, y = imageHeight - internalPadding + topOffset ), Offset( x = internalPadding + startOffset, y = imageHeight - internalPadding + topOffset ) ) ) } val pointScales = List(drawPoints.value.size) { animateFloatAsState(if (it == touchIndex.intValue) 1.4f else 1f) } LaunchedEffect(croppingTrigger) { if (croppingTrigger) { val widthScale = bitmap.width.toFloat() / imageWidth val heightScale = bitmap.height.toFloat() / imageHeight onCropped( FreeCrop.crop( bitmap = bitmap, points = drawPoints.value.map { Offset( x = ((it.x - startOffset) * widthScale).roundToInt() .coerceIn(0, bitmap.width).toFloat(), y = ((it.y - topOffset) * heightScale).roundToInt() .coerceIn(0, bitmap.height).toFloat() ) } ) ) } } Image( bitmap = imageBitmap, contentDescription = null, modifier = Modifier .padding(internalPaddingDp) .padding(contentPadding) .aspectRatio(bitmap.width / bitmap.height.toFloat()) .onGloballyPositioned { topOffset = it.positionInParent().y.toInt() startOffset = it.positionInParent().x.toInt() imageWidth = it.size.width imageHeight = it.size.height }, contentScale = ContentScale.FillBounds ) fun Offset.coerceToImageBounds(): Offset = coerceIn( horizontalRange = (startOffset).toFloat()..((imageWidth + startOffset).toFloat()), verticalRange = (topOffset).toFloat()..((imageHeight + topOffset).toFloat()) ) LaunchedEffect(coercePointsToImageArea) { drawPoints.value = drawPoints.value.map { it.coerceToImageBounds() } } Canvas( modifier = Modifier .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) .size(maxWidth, maxHeight) .then( if (showMagnifier && magnifierCenter.isSpecified) { Modifier.magnifier( magnifierCenter = { magnifierCenter - Offset(0f, 100.dp.toPx()) }, sourceCenter = { magnifierCenter }, size = DpSize(100.dp, 100.dp), cornerRadius = 50.dp, elevation = 2.dp ) } else Modifier ) .pointerInput( contentPadding, coercePointsToImageArea, handleRadiusPx, bitmap, showMagnifier ) { detectDragGestures( onDragStart = { offset -> touchIndex.intValue = -1 drawPoints.value.forEachIndexed { index, drawProperties -> val isTouched = isTouched( center = drawProperties, touchPosition = offset, radius = handleRadiusPx ) if (isTouched) { touchIndex.intValue = index } } magnifierCenter = if (showMagnifier && touchIndex.intValue != -1) offset else Offset.Unspecified }, onDrag = { _, dragAmount -> drawPoints.value .getOrNull(touchIndex.intValue) ?.let { point -> drawPoints.value = drawPoints.value .toMutableList() .apply { this[touchIndex.intValue] = point .plus(dragAmount) .let { if (coercePointsToImageArea) { it.coerceToImageBounds() } else it } .also { newPoint -> if (showMagnifier) magnifierCenter = newPoint } } } }, onDragEnd = { drawPoints.value .getOrNull(touchIndex.intValue) ?.let { point -> drawPoints.value = drawPoints.value .toMutableList() .apply { this[touchIndex.intValue] = point } } touchIndex.intValue = -1 magnifierCenter = Offset.Unspecified }, onDragCancel = { drawPoints.value .getOrNull(touchIndex.intValue) ?.let { point -> drawPoints.value = drawPoints.value .toMutableList() .apply { this[touchIndex.intValue] = point } } touchIndex.intValue = -1 magnifierCenter = Offset.Unspecified } ) } ) { val (x, y) = drawPoints.value[0] val (x1, y1) = drawPoints.value[1] val (x2, y2) = drawPoints.value[2] val (x3, y3) = drawPoints.value[3] val framePath = Path().apply { moveTo(x, y) lineTo(x1, y1) lineTo(x2, y2) lineTo(x3, y3) close() } drawPath( path = framePath, brush = SolidColor(Color.Transparent), blendMode = BlendMode.Clear ) drawPath( path = framePath, brush = SolidColor(colorScheme.primaryContainer), style = Stroke(frameStrokeWidthPx) ) drawPoints.value.forEachIndexed { index, point -> val scale = pointScales[index].value drawCircle( color = colorScheme.primary, center = point, radius = handleRadiusPx * scale ) drawCircle( color = colorScheme.primaryContainer, center = point, radius = handleRadiusPx * 0.8f * scale ) } } } } private fun isTouched(center: Offset, touchPosition: Offset, radius: Float): Boolean { return center.minus(touchPosition).getDistanceSquared() < radius * radius * radius } private val OffsetListSaver: Saver, String> = Saver( save = { list -> list.joinToString(",") { (x, y) -> "$x:$y" } }, restore = { string -> string.split(",").map { o -> val (x, y) = o.split(":").map { it.toFloat() } Offset(x, y) } } ) private fun Offset.coerceIn( horizontalRange: ClosedRange, verticalRange: ClosedRange ) = Offset(this.x.coerceIn(horizontalRange), this.y.coerceIn(verticalRange)) ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/free_corners_crop/model/Quad.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.free_corners_crop.model import android.graphics.PointF import org.opencv.core.Point import kotlin.math.pow import kotlin.math.sqrt class Quad( val topLeftCorner: PointF, val topRightCorner: PointF, val bottomRightCorner: PointF, val bottomLeftCorner: PointF ) fun PointF.toOpenCVPoint(): Point { return Point(x.toDouble(), y.toDouble()) } fun Point.distance(point: Point): Double { return sqrt((point.x - x).pow(2) + (point.y - y).pow(2)) } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/ImageDiffTool.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.image_comparison import android.graphics.Bitmap import androidx.core.graphics.createBitmap import com.t8rin.opencv_tools.image_comparison.model.ComparisonType import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.resizeAndPad import com.t8rin.opencv_tools.utils.toScalar import org.opencv.android.Utils import org.opencv.core.Core import org.opencv.core.Mat import org.opencv.core.Scalar import org.opencv.core.Size import org.opencv.imgproc.Imgproc import kotlin.math.log10 import kotlin.math.sqrt object ImageDiffTool : OpenCV() { fun highlightDifferences( input: Bitmap, other: Bitmap, comparisonType: ComparisonType, highlightColor: Int, threshold: Float = 4f ): Bitmap { val bitmap1 = input.copy(Bitmap.Config.ARGB_8888, false) val bitmap2 = other.copy(Bitmap.Config.ARGB_8888, false) val mat1 = Mat() val mat2Raw = Mat() Utils.bitmapToMat(bitmap1, mat1) Utils.bitmapToMat(bitmap2, mat2Raw) val mat2 = mat2Raw.resizeAndPad(mat1.size()) if (mat1.size() != mat2.size() || mat1.type() != mat2.type()) { throw IllegalArgumentException("Bitmaps must have the same size and type") } val diff = Mat() val thresholdValue = (threshold.coerceIn(0f, 100f) * 2.55).coerceIn(0.0, 255.0) when (comparisonType) { ComparisonType.SSIM -> { val blurred1 = Mat() val blurred2 = Mat() Imgproc.GaussianBlur(mat1, blurred1, Size(11.0, 11.0), 1.5) Imgproc.GaussianBlur(mat2, blurred2, Size(11.0, 11.0), 1.5) val ssimMap = Mat() Core.absdiff(blurred1, blurred2, ssimMap) Imgproc.threshold(ssimMap, diff, 20.0, 255.0, Imgproc.THRESH_BINARY) } ComparisonType.AE -> { Core.absdiff(mat1, mat2, diff) Core.compare(diff, Scalar(thresholdValue), diff, Core.CMP_GT) } ComparisonType.MAE -> { Core.absdiff(mat1, mat2, diff) val meanScalar = Core.mean(diff) val meanValue = (meanScalar.`val`[0] + meanScalar.`val`[1] + meanScalar.`val`[2]) / 3.0 Core.compare(diff, Scalar(meanValue + thresholdValue), diff, Core.CMP_GT) } ComparisonType.NCC -> { val result = Mat() Imgproc.matchTemplate(mat1, mat2, result, Imgproc.TM_CCORR_NORMED) Core.absdiff(mat1, mat2, diff) Core.compare(diff, Scalar(thresholdValue), diff, Core.CMP_GT) } ComparisonType.PSNR -> { val mse = Mat() Core.absdiff(mat1, mat2, mse) Core.pow(mse, 2.0, mse) val meanMse = Core.mean(mse).`val`[0] val psnr = if (meanMse == 0.0) 100.0 else 10.0 * log10(255.0 * 255.0 / meanMse) Core.absdiff(mat1, mat2, diff) if (psnr > 30) Core.multiply(diff, Scalar(0.2), diff) Core.compare(diff, Scalar(thresholdValue), diff, Core.CMP_GT) } ComparisonType.RMSE -> { val mse = Mat() Core.absdiff(mat1, mat2, mse) Core.pow(mse, 2.0, mse) val meanMse = Core.mean(mse).`val`[0] val rmse = sqrt(meanMse) Core.absdiff(mat1, mat2, diff) if (rmse < 10) Core.multiply(diff, Scalar(0.2), diff) Core.compare(diff, Scalar(thresholdValue), diff, Core.CMP_GT) } } val mask = Mat() Imgproc.cvtColor(diff, mask, Imgproc.COLOR_BGR2GRAY) Imgproc.threshold(mask, mask, 1.0, 255.0, Imgproc.THRESH_BINARY) val result = mat1.clone() result.setTo(highlightColor.toScalar(), mask) val outputBitmap = createBitmap(bitmap1.width, bitmap1.height) Utils.matToBitmap(result, outputBitmap) return outputBitmap } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_comparison/model/ComparisonType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.image_comparison.model enum class ComparisonType { SSIM, AE, MAE, NCC, PSNR, RMSE } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/image_processing/ImageProcessing.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.image_processing import android.graphics.Bitmap import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Mat import org.opencv.imgproc.Imgproc object ImageProcessing : OpenCV() { fun canny( bitmap: Bitmap, thresholdOne: Float, thresholdTwo: Float ): Bitmap { val matGrayScale = Mat() Imgproc.cvtColor(bitmap.toMat(), matGrayScale, Imgproc.COLOR_RGBA2GRAY) Imgproc.medianBlur(matGrayScale, matGrayScale, 3) val matCanny = Mat() Imgproc.Canny(matGrayScale, matCanny, thresholdOne.toDouble(), thresholdTwo.toDouble()) return matCanny.toBitmap() } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/LensCorrection.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("LocalVariableName", "unused") package com.t8rin.opencv_tools.lens_correction import android.graphics.Bitmap import android.net.Uri import android.util.Log import com.t8rin.opencv_tools.lens_correction.model.LCException import com.t8rin.opencv_tools.lens_correction.model.LCException.InvalidCalibDimensions import com.t8rin.opencv_tools.lens_correction.model.LCException.InvalidDistortionCoeffs import com.t8rin.opencv_tools.lens_correction.model.LCException.InvalidMatrixSize import com.t8rin.opencv_tools.lens_correction.model.LCException.MissingFisheyeParams import com.t8rin.opencv_tools.lens_correction.model.LensProfile import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.json.JSONArray import org.json.JSONObject import org.opencv.calib3d.Calib3d import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.imgproc.Imgproc import java.io.InputStream import java.io.Reader import kotlin.contracts.contract object LensCorrection : OpenCV() { fun undistort( bitmap: Bitmap, lensDataUri: Uri, intensity: Double = 1.0 ): Bitmap = undistort( bitmap = bitmap, lensData = context.contentResolver.openInputStream(lensDataUri)!!, intensity = intensity ) fun undistort( bitmap: Bitmap, lensData: InputStream, intensity: Double = 1.0 ): Bitmap = undistort( bitmap = bitmap, lensDataJson = lensData.bufferedReader().use(Reader::readText), intensity = intensity ) fun undistort( bitmap: Bitmap, lensDataJson: String, intensity: Double = 1.0 ): Bitmap = undistort( bitmap = bitmap, lensProfile = LensProfile.fromJson(lensDataJson).withIntensity(intensity) ) fun undistort( bitmap: Bitmap, lensProfile: LensProfile ): Bitmap = undistortImpl( bitmap = bitmap, cameraMatrix = lensProfile.cameraMatrix, distortionCoeffs = lensProfile.distortionCoeffs, calibWidth = lensProfile.calibWidth, calibHeight = lensProfile.calibHeight ) private fun undistortImpl( bitmap: Bitmap, cameraMatrix: List>, distortionCoeffs: List, calibWidth: Int, calibHeight: Int ): Bitmap { lcCheck( value = distortionCoeffs.size == 4, message = InvalidDistortionCoeffs() ) val rgbaMat = bitmap.toMat() val K = Mat(3, 3, CvType.CV_64F) val D = Mat(4, 1, CvType.CV_64F) val undistorted = Mat() try { Imgproc.cvtColor(rgbaMat, rgbaMat, Imgproc.COLOR_RGBA2RGB) cameraMatrix.forEachIndexed { i, row -> lcCheck( value = row.size == 3, message = InvalidMatrixSize() ) row.forEachIndexed { j, value -> K.put(i, j, value) } } distortionCoeffs.forEachIndexed { i, v -> D.put(i, 0, -v) } val scaleX = bitmap.width.toDouble() / calibWidth val scaleY = bitmap.height.toDouble() / calibHeight K.put(0, 0, K.get(0, 0)[0] * scaleX) // fx K.put(0, 2, K.get(0, 2)[0] * scaleX) // cx K.put(1, 1, K.get(1, 1)[0] * scaleY) // fy K.put(1, 2, K.get(1, 2)[0] * scaleY) // cy Calib3d.fisheye_undistortImage(rgbaMat, undistorted, K, D, K, rgbaMat.size()) Imgproc.cvtColor(undistorted, undistorted, Imgproc.COLOR_RGB2RGBA) return undistorted.toBitmap() } finally { rgbaMat.release() K.release() D.release() } } fun LensProfile.Companion.fromJson(json: String): LensProfile = JSONObject(json).run { if (has("name")) Log.d("LensCorrection", "name detected: ${get("name")}") lcCheck( value = has("fisheye_params"), message = MissingFisheyeParams() ) val fisheyeParams = getJSONObject("fisheye_params") lcCheck( value = fisheyeParams.has("camera_matrix"), message = InvalidMatrixSize() ) lcCheck( value = fisheyeParams.has("distortion_coeffs"), message = InvalidDistortionCoeffs() ) val calibDim = lcCheck( value = safeJSONObject("calib_dimension") ?: safeJSONObject("orig_dimension") ?: safeJSONObject("output_dimension"), message = InvalidCalibDimensions() ) val calibW = calibDim.getInt("w") val calibH = calibDim.getInt("h") lcCheck( value = calibW > 0 && calibH > 0, message = InvalidCalibDimensions() ) return LensProfile( cameraMatrix = fisheyeParams.getCameraMatrix(), distortionCoeffs = fisheyeParams.getDistortionCoeffs(), calibWidth = calibW, calibHeight = calibH ) } private fun JSONObject.getDistortionCoeffs(): List { val distCoeffsArray = safeJSONArray("distortion_coeffs") lcCheck( value = distCoeffsArray?.length() == 4, message = InvalidDistortionCoeffs() ) return List(size = 4, init = distCoeffsArray::getDouble) } private fun JSONObject.getCameraMatrix(): List> { val cameraMatrixArray = safeJSONArray("camera_matrix") lcCheck( value = cameraMatrixArray?.length() == 3, message = InvalidMatrixSize() ) return List(3) { i -> List(3) { j -> cameraMatrixArray.getJSONArray(i).getDouble(j) } } } private fun lcCheck(value: Boolean, message: LCException) { contract { returns() implies value } if (!value) throw message } private fun lcCheck(value: T?, message: LCException): T { contract { returns() implies (value != null) } if (value == null) throw message else return value } private fun JSONObject.safeJSONObject(key: String): JSONObject? = runCatching { getJSONObject(key) }.getOrNull() private fun JSONObject.safeJSONArray(key: String): JSONArray? = runCatching { getJSONArray(key) }.getOrNull() } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LCException.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.lens_correction.model @Suppress("JavaIoSerializableObjectMustHaveReadResolve") sealed class LCException(message: String) : Exception(message) { class MissingFisheyeParams : LCException("No fisheye_params in JSON") class InvalidMatrixSize : LCException("Incorrect camera_matrix size (pass 3x3)") class InvalidCalibDimensions : LCException("Invalid calibration dimensions") class InvalidDistortionCoeffs : LCException("Bad distortion coefficients (pass only 4)") } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/LensProfile.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.lens_correction.model data class LensProfile( val cameraMatrix: List>, val distortionCoeffs: List, val calibWidth: Int, val calibHeight: Int ) { fun withIntensity( intensity: Double ): LensProfile = copy( distortionCoeffs = distortionCoeffs.map { it * intensity } ) companion object } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/lens_correction/model/Sample.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnusedReceiverParameter") package com.t8rin.opencv_tools.lens_correction.model import com.t8rin.opencv_tools.lens_correction.LensCorrection val LensCorrection.SAMPLE_LENS_PROFILE get() = "{\"name\":\"Canon_200d Mark II_EFS 17-55mm_Lens And Body Stabilization on_1080p_16by9_1920x1080-25.00fps\",\"note\":\"Lens And Body Stabilization on\",\"calibrated_by\":\"Kane McCarthy\",\"camera_brand\":\"Canon\",\"camera_model\":\"200d Mark II\",\"lens_model\":\"EFS 17-55mm\",\"camera_setting\":\"\",\"calib_dimension\":{\"w\":1920,\"h\":1080},\"orig_dimension\":{\"w\":1920,\"h\":1080},\"output_dimension\":{\"w\":1920,\"h\":1080},\"frame_readout_time\":null,\"gyro_lpf\":null,\"input_horizontal_stretch\":1.0,\"input_vertical_stretch\":1.0,\"num_images\":15,\"fps\":25.0,\"crop\":null,\"official\":false,\"asymmetrical\":false,\"fisheye_params\":{\"RMS_error\":0.6572613277627248,\"camera_matrix\":[[1672.4188601991846,0.0,933.6098162432148],[0.0,1677.6413802774082,539.4748019935479],[0.0,0.0,1.0]],\"distortion_coeffs\":[0.25149531135525693,-0.4430334263157348,3.9251216996331664,-8.164669122998902],\"radial_distortion_limit\":null},\"identifier\":\"\",\"calibrator_version\":\"1.5.4\",\"date\":\"2024-01-05\",\"compatible_settings\":[],\"sync_settings\":null,\"distortion_model\":null,\"digital_lens\":null,\"digital_lens_params\":null,\"interpolations\":null,\"focal_length\":null,\"crop_factor\":null,\"global_shutter\":false}" ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/Moire.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("SameParameterValue", "unused") package com.t8rin.opencv_tools.moire import android.graphics.Bitmap import android.util.Log import com.t8rin.opencv_tools.moire.model.MoireType import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Core import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.core.Point import org.opencv.core.Rect import org.opencv.core.Scalar import org.opencv.core.Size import kotlin.math.abs import kotlin.math.atan2 import kotlin.math.pow import kotlin.math.sqrt object Moire : OpenCV() { fun remove( bitmap: Bitmap, type: MoireType = MoireType.AUTO ): Bitmap { val rgba = bitmap.toMat() val channels = mutableListOf() Core.split(rgba, channels) val filteredChannels = channels.take(3).map { channel -> val floatMat = Mat() channel.convertTo(floatMat, CvType.CV_32F) val planes = mutableListOf(floatMat, Mat.zeros(channel.size(), CvType.CV_32F)) val complex = Mat() Core.merge(planes, complex) Core.dft(complex, complex) shiftDFT(complex) val detected = detectNoiseType(complex) val appliedType = type.takeIf { it != MoireType.AUTO } ?: (detected ?: MoireType.VERTICAL) Log.d("Moire", "Detected: $detected, Applied: $appliedType") applyMask(complex, appliedType) val butterworth = butterworthLP(size = channel.size(), d0 = 80.0, n = 10) val filterPlanes = mutableListOf(butterworth, butterworth.clone()) val filter = Mat() Core.merge(filterPlanes, filter) Core.mulSpectrums(complex, filter, complex, 0) shiftDFT(complex) val inverse = Mat() Core.idft(complex, inverse, Core.DFT_SCALE or Core.DFT_REAL_OUTPUT, 0) inverse.convertTo(inverse, CvType.CV_8U) inverse } // Собираем каналы обратно + alpha если был val outputChannels = ArrayList(filteredChannels) if (channels.size == 4) { outputChannels.add(channels[3]) // alpha } val merged = Mat() Core.merge(outputChannels, merged) return merged.toBitmap() } private fun detectNoiseType(dftMat: Mat): MoireType? { val planes = mutableListOf() Core.split(dftMat, planes) val mag = Mat() Core.magnitude(planes[0], planes[1], mag) Core.add(mag, Scalar.all(1.0), mag) Core.log(mag, mag) Core.normalize(mag, mag, 0.0, 255.0, Core.NORM_MINMAX) mag.convertTo(mag, CvType.CV_8U) val center = Point(mag.cols() / 2.0, mag.rows() / 2.0) val threshold = 180.0 val peaks = mutableListOf() for (y in 0 until mag.rows()) { for (x in 0 until mag.cols()) { val value = mag.get(y, x)[0] if (value > threshold) { val dx = abs(x - center.x) val dy = abs(y - center.y) if (dx > 10 || dy > 10) { peaks.add(Point(x.toDouble(), y.toDouble())) } } } } Log.d("Moire", "Detected peaks: ${peaks.size}") if (peaks.size < 2) return null val dataPts = Mat(peaks.size, 2, CvType.CV_64F) for (i in peaks.indices) { dataPts.put(i, 0, peaks[i].x) dataPts.put(i, 1, peaks[i].y) } val mean = Mat() val eigenvectors = Mat() Core.PCACompute(dataPts, mean, eigenvectors) val vx = eigenvectors.get(0, 0)[0] val vy = eigenvectors.get(0, 1)[0] val angle = atan2(vy, vx) * 180.0 / Math.PI Log.d("Moire", "Angle: $angle") return when { abs(angle) < 15 -> MoireType.VERTICAL abs(angle) > 70 && abs(angle) < 110 -> MoireType.HORIZONTAL angle in 15.0..60.0 -> MoireType.RIGHT_DIAGONAL angle in -60.0..-30.0 -> MoireType.LEFT_DIAGONAL else -> null } } private fun butterworthLP(size: Size, d0: Double, n: Int): Mat { val rows = size.height.toInt() val cols = size.width.toInt() val centerX = rows / 2.0 val centerY = cols / 2.0 val filter = Mat(rows, cols, CvType.CV_32F) for (i in 0 until rows) { for (j in 0 until cols) { val d = sqrt((i - centerX).pow(2) + (j - centerY).pow(2)) val value = 1.0 / (1.0 + (d / d0).pow(2.0 * n)) filter.put(i, j, value) } } return filter } private fun applyMask(dftMat: Mat, type: MoireType?) { if (type == null || type == MoireType.AUTO) return val rows = dftMat.rows() val cols = dftMat.cols() val crow = rows / 2 val ccol = cols / 2 val radius = 8 fun suppressCircle(cx: Int, cy: Int) { for (y in -radius..radius) { for (x in -radius..radius) { val dx = cx + x val dy = cy + y if (dx in 0 until cols && dy in 0 until rows) { if (x * x + y * y <= radius * radius) { dftMat.put(dy, dx, 0.0, 0.0) } } } } } val planes = mutableListOf() Core.split(dftMat, planes) val mag = Mat() Core.magnitude(planes[0], planes[1], mag) Core.add(mag, Scalar.all(1.0), mag) Core.log(mag, mag) Core.normalize(mag, mag, 0.0, 255.0, Core.NORM_MINMAX) mag.convertTo(mag, CvType.CV_8U) val center = Point(ccol.toDouble(), crow.toDouble()) val threshold = 180.0 val peaks = mutableListOf() for (y in 0 until rows) { for (x in 0 until cols) { val value = mag.get(y, x)[0] if (value > threshold) { val dx = x - center.x val dy = y - center.y if (abs(dx) > 10 || abs(dy) > 10) { peaks.add(Point(x.toDouble(), y.toDouble())) } } } } Log.d("Moire", "Masking ${peaks.size} peaks for $type") for (peak in peaks) { val dx = peak.x - center.x val dy = peak.y - center.y val angle = atan2(dy, dx) * 180.0 / Math.PI val matched = when (type) { MoireType.VERTICAL -> abs(angle) !in 15.0..165.0 MoireType.HORIZONTAL -> abs(angle - 90) < 15 || abs(angle + 90) < 15 MoireType.RIGHT_DIAGONAL -> abs(angle - 45) < 15 || abs(angle + 135) < 15 MoireType.LEFT_DIAGONAL -> abs(angle + 45) < 15 || abs(angle - 135) < 15 } if (matched) { suppressCircle(peak.x.toInt(), peak.y.toInt()) } } } private fun shiftDFT(mat: Mat) { val cx = mat.cols() / 2 val cy = mat.rows() / 2 val q0 = Mat(mat, Rect(0, 0, cx, cy)) // Top-Left val q1 = Mat(mat, Rect(cx, 0, cx, cy)) // Top-Right val q2 = Mat(mat, Rect(0, cy, cx, cy)) // Bottom-Left val q3 = Mat(mat, Rect(cx, cy, cx, cy)) // Bottom-Right val tmp = Mat() q0.copyTo(tmp) q3.copyTo(q0) tmp.copyTo(q3) q1.copyTo(tmp) q2.copyTo(q1) tmp.copyTo(q2) } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/moire/model/MoireType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.moire.model enum class MoireType { AUTO, VERTICAL, HORIZONTAL, RIGHT_DIAGONAL, LEFT_DIAGONAL } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/qr_prepare/QrPrepareHelper.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.qr_prepare import android.graphics.Bitmap import androidx.core.graphics.createBitmap import com.t8rin.opencv_tools.utils.OpenCV import org.opencv.android.Utils import org.opencv.core.Mat import org.opencv.core.Size import org.opencv.imgproc.Imgproc object QrPrepareHelper : OpenCV() { fun prepareQrForDecode(bitmap: Bitmap): Bitmap { // 1. Bitmap -> Mat val src = Mat() Utils.bitmapToMat(bitmap, src) // 2. В grayscale val gray = Mat() Imgproc.cvtColor(src, gray, Imgproc.COLOR_RGBA2GRAY) // 3. CLAHE для локального контраста val claheMat = Mat() val clahe = Imgproc.createCLAHE() clahe.clipLimit = 2.0 clahe.tilesGridSize = Size(8.0, 8.0) clahe.apply(gray, claheMat) // 4. Глобальная бинаризация через Otsu (полностью заливает модули) val binary = Mat() Imgproc.threshold( claheMat, binary, 0.0, 255.0, Imgproc.THRESH_BINARY or Imgproc.THRESH_OTSU ) // 5. Морфология для устранения дырок val kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(3.0, 3.0)) Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_CLOSE, kernel) // 6. Конвертируем в Bitmap val result = createBitmap(binary.cols(), binary.rows()) Imgproc.cvtColor(binary, binary, Imgproc.COLOR_GRAY2RGBA) Utils.matToBitmap(binary, result) // 7. Освобождаем память src.release() gray.release() claheMat.release() binary.release() return result } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/red_eye/RedEyeRemover.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.red_eye import android.graphics.Bitmap import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Core import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.core.MatOfRect import org.opencv.core.Point import org.opencv.core.Rect import org.opencv.core.Scalar import org.opencv.core.Size import org.opencv.imgproc.Imgproc import org.opencv.objdetect.CascadeClassifier import java.io.File import java.io.FileOutputStream object RedEyeRemover : OpenCV() { fun removeRedEyes( bitmap: Bitmap, minEyeSize: Double = 50.0, redThreshold: Double = 150.0 ): Bitmap { val srcMat = bitmap.toMat() Imgproc.cvtColor(srcMat, srcMat, Imgproc.COLOR_RGBA2BGR) val resultMat = srcMat.clone() val grayMat = Mat() Imgproc.cvtColor(srcMat, grayMat, Imgproc.COLOR_BGR2GRAY) val eyes = MatOfRect() loadCascade().detectMultiScale( grayMat, eyes, 1.3, 4, 0, Size(minEyeSize, minEyeSize), Size() ) for (rect in eyes.toArray()) { val roi = Rect(rect.x, rect.y, rect.width, rect.height) val eyeMat = srcMat.submat(roi).clone() val channels = ArrayList() Core.split(eyeMat, channels) val blue = channels[0] val green = channels[1] val red = channels[2] val bg = Mat() Core.add(blue, green, bg) val maskRed = Mat() Imgproc.threshold(red, maskRed, redThreshold, 255.0, Imgproc.THRESH_BINARY) val maskCmp = Mat() Core.compare(red, bg, maskCmp, Core.CMP_GT) val redEyeMask = Mat() Core.bitwise_and(maskRed, maskCmp, redEyeMask) val filledMask = fillHoles(redEyeMask) val dilatedMask = Mat() Imgproc.dilate(filledMask, dilatedMask, Mat(), Point(-1.0, -1.0), 3) val meanMat = Mat() Core.divide(bg, Scalar(2.0), meanMat) val mean3 = Mat() Core.merge(listOf(meanMat, meanMat, meanMat), mean3) val eyeOut = eyeMat.clone() mean3.copyTo(eyeOut, dilatedMask) eyeOut.copyTo(resultMat.submat(roi)) } Imgproc.cvtColor(resultMat, resultMat, Imgproc.COLOR_BGR2RGBA) return resultMat.toBitmap() } private fun fillHoles(mask: Mat): Mat { val maskFloodfill = mask.clone() val rows = maskFloodfill.rows() val cols = maskFloodfill.cols() val floodfillMask = Mat.zeros(rows + 2, cols + 2, CvType.CV_8UC1) Imgproc.floodFill(maskFloodfill, floodfillMask, Point(0.0, 0.0), Scalar(255.0)) val maskInv = Mat() Core.bitwise_not(maskFloodfill, maskInv) val filledMask = Mat() Core.bitwise_or(maskInv, mask, filledMask) return filledMask } private fun loadCascade(): CascadeClassifier { val inputStream = context.assets.open(EYE_DETECTION) val cascadeFile = File(context.cacheDir, EYE_DETECTION) FileOutputStream(cascadeFile).use { output -> inputStream.copyTo(output) } return CascadeClassifier(cascadeFile.absolutePath) } private const val EYE_DETECTION = "haarcascade_eye.xml" } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/seam_carving/SeamCarver.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.seam_carving import android.graphics.Bitmap import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Core import org.opencv.core.CvType import org.opencv.core.Mat import org.opencv.core.Size import org.opencv.imgproc.Imgproc import kotlin.math.min object SeamCarver : OpenCV() { /** * Main entry: * input: bitmap, desiredWidth, desiredHeight * returns: processed Bitmap (content-aware reduced). If desired dimensions are larger than source, * result will be upscaled with normal resizing after seam carving. */ fun carve(bitmap: Bitmap, desiredWidth: Int, desiredHeight: Int): Bitmap { var mat = bitmap.toMat() val targetW = desiredWidth.coerceAtLeast(1) val targetH = desiredHeight.coerceAtLeast(1) // If target dims are bigger than current, we will only seam-carve to min(current, target) and then resize up. val targetWClamp = min(targetW, mat.cols()) val targetHClamp = min(targetH, mat.rows()) // reduce width while (mat.cols() > targetWClamp) { val energy = computeEnergy(mat) val seam = findVerticalSeam(energy) mat = removeVerticalSeam(mat, seam) } // reduce height via transpose trick mat = transposeMat(mat) while (mat.cols() > targetHClamp) { val energy = computeEnergy(mat) val seam = findVerticalSeam(energy) mat = removeVerticalSeam(mat, seam) } mat = transposeMat(mat) // If the user requested larger dimensions than we could seam-carve to, upscale with interpolation. val finalMat = if (mat.cols() != targetW || mat.rows() != targetH) { val dst = Mat() Imgproc.resize( mat, dst, Size(targetW.toDouble(), targetH.toDouble()), 0.0, 0.0, Imgproc.INTER_CUBIC ) dst } else { mat } return finalMat.toBitmap() } private fun transposeMat(src: Mat): Mat { val dst = Mat() Core.transpose(src, dst) return dst } /** * Compute energy map using gradient magnitude (Sobel) * returns Mat of type CV_64F with shape rows x cols */ private fun computeEnergy(src: Mat): Mat { val gray = Mat() Imgproc.cvtColor(src, gray, Imgproc.COLOR_RGB2GRAY) gray.convertTo(gray, CvType.CV_64F) val gradX = Mat() val gradY = Mat() Imgproc.Sobel(gray, gradX, CvType.CV_64F, 1, 0, 3, 1.0, 0.0, Core.BORDER_DEFAULT) Imgproc.Sobel(gray, gradY, CvType.CV_64F, 0, 1, 3, 1.0, 0.0, Core.BORDER_DEFAULT) val gradXSq = Mat() val gradYSq = Mat() Core.multiply(gradX, gradX, gradXSq) Core.multiply(gradY, gradY, gradYSq) val energy = Mat() Core.add(gradXSq, gradYSq, energy) Core.sqrt(energy, energy) // sqrt(gx^2 + gy^2) return energy } /** * Find vertical seam (one pixel per row) with minimum cumulative energy. * energy: CV_64F Mat. * returns IntArray of length rows where each element is column index of seam in that row. */ private fun findVerticalSeam(energy: Mat): IntArray { val rows = energy.rows() val cols = energy.cols() // read energy into 2D double array val e = Array(rows) { DoubleArray(cols) } val buf = DoubleArray(cols) for (r in 0 until rows) { energy.get(r, 0, buf) for (c in 0 until cols) e[r][c] = buf[c] } // dp cumulative energy val dp = Array(rows) { DoubleArray(cols) { Double.POSITIVE_INFINITY } } val backtrack = Array(rows) { IntArray(cols) } // initialize first row for (c in 0 until cols) dp[0][c] = e[0][c] for (r in 1 until rows) { for (c in 0 until cols) { // consider three pixels from previous row var minPrev = dp[r - 1][c] var idx = c if (c > 0 && dp[r - 1][c - 1] < minPrev) { minPrev = dp[r - 1][c - 1]; idx = c - 1 } if (c < cols - 1 && dp[r - 1][c + 1] < minPrev) { minPrev = dp[r - 1][c + 1]; idx = c + 1 } dp[r][c] = e[r][c] + minPrev backtrack[r][c] = idx } } // find min in last row var minCol = 0 var minVal = dp[rows - 1][0] for (c in 1 until cols) { if (dp[rows - 1][c] < minVal) { minVal = dp[rows - 1][c] minCol = c } } // reconstruct seam val seam = IntArray(rows) var cur = minCol for (r in rows - 1 downTo 0) { seam[r] = cur if (r > 0) cur = backtrack[r][cur] } return seam } /** * Remove vertical seam from src (3-channel RGB Mat) and return new Mat with cols-1. */ private fun removeVerticalSeam(src: Mat, seam: IntArray): Mat { val rows = src.rows() val cols = src.cols() val dst = Mat(rows, cols - 1, src.type()) // We'll copy row by row val rowBuf = ByteArray(cols * src.channels()) val outBuf = ByteArray((cols - 1) * src.channels()) val channels = src.channels() for (r in 0 until rows) { src.get(r, 0, rowBuf) val skipCol = seam[r] var dstIdx = 0 var srcIdx = 0 for (c in 0 until cols) { if (c == skipCol) { srcIdx += channels continue } // copy channels for (ch in 0 until channels) { outBuf[dstIdx++] = rowBuf[srcIdx++] } } dst.put(r, 0, outBuf) } return dst } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/SpotHealer.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.spot_heal import android.graphics.Bitmap import com.t8rin.opencv_tools.spot_heal.model.HealType import com.t8rin.opencv_tools.utils.OpenCV import com.t8rin.opencv_tools.utils.toBitmap import com.t8rin.opencv_tools.utils.toMat import org.opencv.core.Mat import org.opencv.core.Size import org.opencv.imgproc.Imgproc import org.opencv.photo.Photo object SpotHealer : OpenCV() { fun heal( image: Bitmap, mask: Bitmap, radius: Float, type: HealType ): Bitmap { val src = image.toMat() val inpaintMask = Mat() Imgproc.resize( mask.toMat(), inpaintMask, Size(image.width.toDouble(), image.height.toDouble()) ) Imgproc.cvtColor(src, src, Imgproc.COLOR_RGB2XYZ) Imgproc.cvtColor(inpaintMask, inpaintMask, Imgproc.COLOR_BGR2GRAY) val dst = Mat() Photo.inpaint( src, inpaintMask, dst, radius.toDouble(), type.ordinal ) Imgproc.cvtColor(dst, dst, Imgproc.COLOR_XYZ2RGB) return dst.toBitmap() } } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/spot_heal/model/HealType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.opencv_tools.spot_heal.model enum class HealType { NS, TELEA } ================================================ FILE: lib/opencv-tools/src/main/java/com/t8rin/opencv_tools/utils/OpenCVUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("unused") package com.t8rin.opencv_tools.utils import android.app.Application import android.graphics.Bitmap import androidx.core.graphics.createBitmap import org.opencv.android.OpenCVLoader import org.opencv.android.Utils import org.opencv.core.Core import org.opencv.core.Mat import org.opencv.core.Rect import org.opencv.core.Scalar import org.opencv.core.Size import org.opencv.imgproc.Imgproc fun Bitmap.toMat(): Mat = Mat().apply { Utils.bitmapToMat(copy(Bitmap.Config.ARGB_8888, false), this) } fun Mat.toBitmap(): Bitmap = createBitmap(cols(), rows()).apply { Utils.matToBitmap(this@toBitmap, this) release() } fun Mat.multiChannelMean(): Double = Core.mean(this).`val`.average() fun Mat.singleChannelMean(): Double = Core.mean(this).`val`.first() fun Mat.resizeAndCrop(targetSize: Size): Mat { val aspectRatio = this.width().toDouble() / this.height() val targetAspectRatio = targetSize.width / targetSize.height val resizedMat = Mat() if (aspectRatio > targetAspectRatio) { val newWidth = (this.height() * targetAspectRatio).toInt() Imgproc.resize(this, resizedMat, Size(newWidth.toDouble(), this.height().toDouble())) } else { val newHeight = (this.width() / targetAspectRatio).toInt() Imgproc.resize(this, resizedMat, Size(this.width().toDouble(), newHeight.toDouble())) } val xOffset = (resizedMat.width() - targetSize.width).toInt() / 2 val yOffset = (resizedMat.height() - targetSize.height).toInt() / 2 return Mat( resizedMat, Rect(xOffset, yOffset, targetSize.width.toInt(), targetSize.height.toInt()) ) } fun Mat.resizeAndPad(targetSize: Size): Mat { val aspectRatio = this.width().toDouble() / this.height() val targetAspectRatio = targetSize.width / targetSize.height val resizedMat = Mat() if (aspectRatio > targetAspectRatio) { val newHeight = (targetSize.width / aspectRatio).toInt() Imgproc.resize(this, resizedMat, Size(targetSize.width, newHeight.toDouble())) } else { val newWidth = (targetSize.height * aspectRatio).toInt() Imgproc.resize(this, resizedMat, Size(newWidth.toDouble(), targetSize.height)) } val paddedMat = Mat(targetSize, this.type(), Scalar(0.0, 0.0, 0.0)) resizedMat.copyTo(paddedMat.submat(Rect(0, 0, resizedMat.width(), resizedMat.height()))) return paddedMat } fun Int.toScalar(): Scalar { val alpha = (this shr 24 and 0xFF).toDouble() val red = (this shr 16 and 0xFF).toDouble() val green = (this shr 8 and 0xFF).toDouble() val blue = (this and 0xFF).toDouble() return Scalar(red, green, blue, alpha) } abstract class OpenCV { init { OpenCVLoader.initLocal() } protected val context get() = application companion object { private var _context: Application? = null internal val application: Application get() = _context ?: throw NullPointerException("Call OpenCV.init() in Application onCreate to use this feature") fun init(context: Application) { _context = context } } } ================================================ FILE: lib/palette/.gitignore ================================================ /build ================================================ FILE: lib/palette/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) kotlin("plugin.serialization") } android.namespace = "com.t8rin.palette" dependencies { implementation(libs.kotlinx.serialization.json) testImplementation(kotlin("test")) testImplementation(libs.junit) } ================================================ FILE: lib/palette/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/ColorByteFormat.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette /** * Byte ordering for RGB */ enum class ColorByteFormat { RGB, BGR, ARGB, RGBA, ABGR, BGRA } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/ColorGroup.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette import kotlinx.serialization.Serializable import java.util.UUID /** * A grouping of colors */ @Serializable data class ColorGroup( val id: String = UUID.randomUUID().toString(), val name: String = "", val colors: List = mutableListOf() ) ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/ColorSpace.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette import kotlinx.serialization.Serializable /** * Color space representation */ @Serializable enum class ColorSpace { CMYK, RGB, LAB, Gray } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/ColorType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette import kotlinx.serialization.Serializable /** * The type of the color (normal, spot, global) */ @Serializable enum class ColorType { Global, Spot, Normal } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/Palette.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette import kotlinx.serialization.Serializable import java.util.UUID /** * Color grouping within a palette */ sealed class ColorGrouping { object Global : ColorGrouping() data class Group(val index: Int) : ColorGrouping() } /** * A color palette */ @Serializable data class Palette( val name: String = "", val colors: List = listOf(), val groups: List = listOf() ) { val id: String = UUID.randomUUID().toString() /** * The total number of colors in the palette */ val totalColorCount: Int get() = colors.size + groups.sumOf { it.colors.size } /** * Returns all the groups for the palette. Global colors are represented in a group called 'global' */ val allGroups: List get() = listOf(ColorGroup(colors = colors, name = "global")) + groups /** * Returns all the colors in the palette as a flat array of colors (all group information is lost) */ fun allColors(): List { val results = colors.toMutableList() groups.forEach { results.addAll(it.colors) } return results } /** * Find the first instance of a color by name within the palette */ fun color(name: String, caseSensitive: Boolean = false): PaletteColor? { return if (caseSensitive) { allColors().firstOrNull { it.name == name } } else { val lowerName = name.lowercase() allColors().firstOrNull { it.name.lowercase() == lowerName } } } /** * Return an array of colors for the specified palette group type */ fun colors(groupType: ColorGrouping): List { return when (groupType) { is ColorGrouping.Global -> colors is ColorGrouping.Group -> { if (groupType.index >= 0 && groupType.index < groups.size) { groups[groupType.index].colors } else { throw PaletteCoderException.IndexOutOfRange() } } } } /** * An index for a color within the palette */ data class ColorIndex( val group: ColorGrouping = ColorGrouping.Global, val colorIndex: Int ) /** * Retrieve a color from the palette */ fun color(group: ColorGrouping = ColorGrouping.Global, colorIndex: Int): PaletteColor { return when (group) { is ColorGrouping.Global -> { if (colorIndex >= 0 && colorIndex < colors.size) { colors[colorIndex] } else { throw PaletteCoderException.IndexOutOfRange() } } is ColorGrouping.Group -> { if (group.index >= 0 && group.index < groups.size && colorIndex >= 0 && colorIndex < groups[group.index].colors.size ) { groups[group.index].colors[colorIndex] } else { throw PaletteCoderException.IndexOutOfRange() } } } } /** * Retrieve a color from the palette using ColorIndex */ fun color(index: ColorIndex): PaletteColor { return color(index.group, index.colorIndex) } /** * Update a color */ fun updateColor( group: ColorGrouping = ColorGrouping.Global, colorIndex: Int, color: PaletteColor ): Palette { val builder = newBuilder() when (group) { is ColorGrouping.Global -> { if (colorIndex >= 0 && colorIndex < colors.size) { builder.colors[colorIndex] = color } else { throw PaletteCoderException.IndexOutOfRange() } } is ColorGrouping.Group -> { if (group.index >= 0 && group.index < groups.size && colorIndex >= 0 && colorIndex < groups[group.index].colors.size ) { builder.groups[group.index] = groups[group.index].copy( colors = colors.toMutableList().apply { set(colorIndex, color) } ) } else { throw PaletteCoderException.IndexOutOfRange() } } } return builder.build() } /** * Update a color using ColorIndex */ fun updateColor(index: ColorIndex, color: PaletteColor): Palette = updateColor(index.group, index.colorIndex, color) /** * Returns a bucketed color for a time value mapped within an evenly spaced array of colors */ fun bucketedColor(at: Double, type: ColorGrouping = ColorGrouping.Global): PaletteColor { val colorList = colors(type) if (colorList.isEmpty()) throw PaletteCoderException.TooFewColors() val clampedT = at.coerceIn(0.0, 1.0) val index = (clampedT * (colorList.size - 1)).toInt().coerceIn(0, colorList.size - 1) return colorList[index] } /** * Returns an interpolated color for a time value mapped within an evenly spaced array of colors */ fun interpolatedColor(at: Double, type: ColorGrouping = ColorGrouping.Global): PaletteColor { val colorList = colors(type) if (colorList.isEmpty()) throw PaletteCoderException.TooFewColors() if (colorList.size == 1) return colorList[0] val clampedT = at.coerceIn(0.0, 1.0) val position = clampedT * (colorList.size - 1) val index = position.toInt().coerceIn(0, colorList.size - 2) val fraction = position - index val c1 = colorList[index] val c2 = colorList[index + 1] // Simple linear interpolation in RGB space val rgb1 = c1.toRgb() val rgb2 = c2.toRgb() return PaletteColor.rgb( r = rgb1.rf + (rgb2.rf - rgb1.rf) * fraction, g = rgb1.gf + (rgb2.gf - rgb1.gf) * fraction, b = rgb1.bf + (rgb2.bf - rgb1.bf) * fraction, a = rgb1.af + (rgb2.af - rgb1.af) * fraction ) } companion object Companion { /** * Return a palette containing random colors */ fun random( count: Int, colorSpace: ColorSpace = ColorSpace.RGB, colorType: ColorType = ColorType.Global ): Palette { require(count > 0) { "Count must be greater than 0" } return Palette( colors = (0 until count).map { PaletteColor.random( colorSpace = colorSpace, colorType = colorType, name = "Color_$it" ) }.toMutableList() ) } /** * Create a palette by interpolating between two colors */ fun interpolated( startColor: PaletteColor, endColor: PaletteColor, count: Int, useOkLab: Boolean = false, name: String = "" ): Palette { require(count > 0) { "Count must be greater than 0" } val colors = if (useOkLab) { // TODO: Implement OkLab interpolation simpleInterpolate(startColor, endColor, count) } else { simpleInterpolate(startColor, endColor, count) } return Palette(colors = colors.toMutableList(), name = name) } private fun simpleInterpolate( start: PaletteColor, end: PaletteColor, count: Int ): List { val startRgb = start.toRgb() val endRgb = end.toRgb() return (0 until count).map { i -> val t = if (count > 1) i / (count - 1.0) else 0.0 PaletteColor.rgb( r = startRgb.rf + (endRgb.rf - startRgb.rf) * t, g = startRgb.gf + (endRgb.gf - startRgb.gf) * t, b = startRgb.bf + (endRgb.bf - startRgb.bf) * t, a = startRgb.af + (endRgb.af - startRgb.af) * t ) } } } class Builder( var name: String = "", var colors: MutableList = mutableListOf(), var groups: MutableList = mutableListOf() ) { fun build() = Palette( name = name, colors = colors, groups = groups ) } fun newBuilder(): Builder = Builder( name = name, colors = colors.toMutableList(), groups = groups.toMutableList() ) } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/PaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette import android.content.Context import android.net.Uri import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream /** * Protocol for palette coders */ interface PaletteCoder { /** * Decode a palette from input stream */ fun decode(input: InputStream): Palette /** * Encode a palette to output stream */ fun encode(palette: Palette, output: OutputStream) } /** * Decode a palette from byte array */ fun PaletteCoder.decode(data: ByteArray): Palette = decode(ByteArrayInputStream(data).buffered()) /** * Encode a palette to byte array */ fun PaletteCoder.encode(palette: Palette): ByteArray = ByteArrayOutputStream().use { encode(palette, it) it.toByteArray() } inline fun PaletteCoder.use(action: PaletteCoder.() -> T): Result = runCatching { action() } fun PaletteCoder.decode(uri: Uri, context: Context) = decode(context.contentResolver.openInputStream(uri)!!) ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/PaletteCoderException.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette sealed class PaletteCoderException(message: String) : Throwable(message) { class UnsupportedPaletteType : PaletteCoderException("Unsupported palette type") class InvalidFormat : PaletteCoderException("Invalid format") class InvalidASEHeader : PaletteCoderException("Invalid ASE header") class InvalidColorComponentCountForModelType : PaletteCoderException("Invalid color component count for model type") class UnknownBlockType : PaletteCoderException("Unknown block type") class GroupAlreadyOpen : PaletteCoderException("Group already open") class GroupNotOpen : PaletteCoderException("Group not open") class UnsupportedColorSpace : PaletteCoderException("Unsupported color space") class InvalidVersion : PaletteCoderException("Invalid version") class InvalidBOM : PaletteCoderException("Invalid BOM") class NotImplemented : PaletteCoderException("Not implemented") class CannotCreateColor : PaletteCoderException("Cannot create color") class TooFewColors : PaletteCoderException("Too few colors") class IndexOutOfRange : PaletteCoderException("Index out of range") data class UnknownColorMode(val mode: String) : PaletteCoderException("Unknown color mode: $mode") data class UnknownColorType(val type: Int) : PaletteCoderException("Unknown color type: $type") data class InvalidRGBHexString(val string: String) : PaletteCoderException("Invalid RGB hex string: $string") data class InvalidRGBAHexString(val string: String) : PaletteCoderException("Invalid RGBA hex string: $string") } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/PaletteColor.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.ColorUtils import com.t8rin.palette.utils.extractHexRGBA import kotlinx.serialization.Serializable import java.util.UUID /** * A color in the palette */ @Serializable data class PaletteColor( val name: String = "", val colorType: ColorType = ColorType.Global, val colorSpace: ColorSpace, val colorComponents: List, val alpha: Double = 1.0 ) { val id: String = UUID.randomUUID().toString() init { checkValidity() } /** * Returns true if the underlying color structure is valid for its colorspace */ val isValid: Boolean get() = when (colorSpace) { ColorSpace.CMYK -> colorComponents.size == 4 ColorSpace.RGB -> colorComponents.size == 3 ColorSpace.LAB -> colorComponents.size == 3 ColorSpace.Gray -> colorComponents.size == 1 } /** * Throws an error if the color is in an invalid state */ fun checkValidity() { if (!isValid) { throw PaletteCoderException.InvalidColorComponentCountForModelType() } } /** * Convert to Jetpack Compose Color (ARGB Int) */ fun toComposeColor(): Color { return when (colorSpace) { ColorSpace.RGB -> { Color( red = colorComponents[0].coerceIn(0.0, 1.0).toFloat(), green = colorComponents[1].coerceIn(0.0, 1.0).toFloat(), blue = colorComponents[2].coerceIn(0.0, 1.0).toFloat(), alpha = alpha.coerceIn(0.0, 1.0).toFloat() ) } ColorSpace.CMYK -> { // Convert CMYK to RGB val c = colorComponents[0].coerceIn(0.0, 1.0).toFloat() val m = colorComponents[1].coerceIn(0.0, 1.0).toFloat() val y = colorComponents[2].coerceIn(0.0, 1.0).toFloat() val k = colorComponents[3].coerceIn(0.0, 1.0).toFloat() Color( red = (1f - c) * (1f - k), green = (1f - m) * (1f - k), blue = (1f - y) * (1f - k), alpha = alpha.coerceIn(0.0, 1.0).toFloat() ) } ColorSpace.Gray -> { val gray = colorComponents[0].coerceIn(0.0, 1.0).toFloat() val a = alpha.coerceIn(0.0, 1.0).toFloat() Color( red = gray, green = gray, blue = gray, alpha = a ) } ColorSpace.LAB -> { // Convert LAB to RGB (simplified) Color( ColorUtils.LABToColor( colorComponents[0], colorComponents[1], colorComponents[2] ) ).copy( alpha = alpha.coerceIn(0.0, 1.0).toFloat() ) } } } /** * Convert to ARGB Int */ fun toArgb(): Int = toComposeColor().toArgb() /** * Create from ARGB Int */ companion object Companion { /** * Create RGB color */ fun rgb( r: Double, g: Double, b: Double, a: Double = 1.0, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { return PaletteColor( name = name, colorType = colorType, colorSpace = ColorSpace.RGB, colorComponents = listOf( r.coerceIn(0.0, 1.0), g.coerceIn(0.0, 1.0), b.coerceIn(0.0, 1.0) ), alpha = a.coerceIn(0.0, 1.0) ) } /** * Create CMYK color */ fun cmyk( c: Double, m: Double, y: Double, k: Double, alpha: Double = 1.0, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { return PaletteColor( name = name, colorType = colorType, colorSpace = ColorSpace.CMYK, colorComponents = listOf( c.coerceIn(0.0, 1.0), m.coerceIn(0.0, 1.0), y.coerceIn(0.0, 1.0), k.coerceIn(0.0, 1.0) ), alpha = alpha.coerceIn(0.0, 1.0) ) } /** * Create Gray color */ fun gray( white: Double, alpha: Double = 1.0, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { return PaletteColor( name = name, colorType = colorType, colorSpace = ColorSpace.Gray, colorComponents = listOf(white.coerceIn(0.0, 1.0)), alpha = alpha.coerceIn(0.0, 1.0) ) } /** * Create LAB color */ fun lab( l: Double, a: Double, b: Double, alpha: Double = 1.0, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { return PaletteColor( name = name, colorType = colorType, colorSpace = ColorSpace.LAB, colorComponents = listOf(l, a, b), alpha = alpha.coerceIn(0.0, 1.0) ) } /** * Create HSB color (converts to RGB) * @param hf Hue (0.0 ... 1.0) * @param sf Saturation (0.0 ... 1.0) * @param bf Brightness (0.0 ... 1.0) */ fun hsb( hf: Double, sf: Double, bf: Double, alpha: Double = 1.0, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { val h = hf.coerceIn(0.0, 1.0) val s = sf.coerceIn(0.0, 1.0) val b = bf.coerceIn(0.0, 1.0) // Convert HSB to RGB val c = b * s val x = c * (1 - kotlin.math.abs((h * 6) % 2 - 1)) val m = b - c val (r, g, bl) = when { h < 1.0 / 6 -> Triple(c, x, 0.0) h < 2.0 / 6 -> Triple(x, c, 0.0) h < 3.0 / 6 -> Triple(0.0, c, x) h < 4.0 / 6 -> Triple(0.0, x, c) h < 5.0 / 6 -> Triple(x, 0.0, c) else -> Triple(c, 0.0, x) } return rgb( r = (r + m).coerceIn(0.0, 1.0), g = (g + m).coerceIn(0.0, 1.0), b = (bl + m).coerceIn(0.0, 1.0), a = alpha, name = name, colorType = colorType ) } /** * Create HSL color (converts to RGB) * @param hf Hue (0.0 ... 1.0) * @param sf Saturation (0.0 ... 1.0) * @param lf Lightness (0.0 ... 1.0) */ fun hsl( hf: Double, sf: Double, lf: Double, alpha: Double = 1.0, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { val h = hf.coerceIn(0.0, 1.0) val s = sf.coerceIn(0.0, 1.0) val l = lf.coerceIn(0.0, 1.0) // Convert HSL to RGB val c = (1 - kotlin.math.abs(2 * l - 1)) * s val x = c * (1 - kotlin.math.abs((h * 6) % 2 - 1)) val m = l - c / 2 val (r, g, b) = when { h < 1.0 / 6 -> Triple(c, x, 0.0) h < 2.0 / 6 -> Triple(x, c, 0.0) h < 3.0 / 6 -> Triple(0.0, c, x) h < 4.0 / 6 -> Triple(0.0, x, c) h < 5.0 / 6 -> Triple(x, 0.0, c) else -> Triple(c, 0.0, x) } return rgb( r = (r + m).coerceIn(0.0, 1.0), g = (g + m).coerceIn(0.0, 1.0), b = (b + m).coerceIn(0.0, 1.0), a = alpha, name = name, colorType = colorType ) } /** * Create color with white (gray) value */ fun white( white: Double, alpha: Double = 1.0, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { return gray(white, alpha, name, colorType) } /** * Generate a random color */ fun random( colorSpace: ColorSpace = ColorSpace.RGB, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor { return when (colorSpace) { ColorSpace.RGB -> rgb( r = kotlin.random.Random.nextDouble(0.0, 1.0), g = kotlin.random.Random.nextDouble(0.0, 1.0), b = kotlin.random.Random.nextDouble(0.0, 1.0), name = name, colorType = colorType ) ColorSpace.CMYK -> cmyk( c = kotlin.random.Random.nextDouble(0.0, 1.0), m = kotlin.random.Random.nextDouble(0.0, 1.0), y = kotlin.random.Random.nextDouble(0.0, 1.0), k = kotlin.random.Random.nextDouble(0.0, 1.0), name = name, colorType = colorType ) ColorSpace.Gray -> gray( white = kotlin.random.Random.nextDouble(0.0, 1.0), name = name, colorType = colorType ) ColorSpace.LAB -> throw PaletteCoderException.NotImplemented() } } } /** * Return a copy with modified alpha */ fun withAlpha(newAlpha: Double): PaletteColor { return copy(alpha = newAlpha.coerceIn(0.0, 1.0)) } /** * Return a copy with modified name */ fun named(newName: String): PaletteColor { return copy(name = newName) } /** * Convert color to another colorspace */ fun converted(colorspace: ColorSpace): PaletteColor { if (this.colorSpace == colorspace) return this return when (colorspace) { ColorSpace.CMYK -> { val cmyk = toCmyk() cmyk( c = cmyk.cf, m = cmyk.mf, y = cmyk.yf, k = cmyk.kf, alpha = cmyk.af, name = name, colorType = colorType ) } ColorSpace.RGB -> { val rgb = toRgb() rgb( r = rgb.rf, g = rgb.gf, b = rgb.bf, a = rgb.af, name = name, colorType = colorType ) } ColorSpace.LAB -> { val lab = toLab() lab( l = lab.l, a = lab.a, b = lab.b, alpha = lab.alpha, name = name, colorType = colorType ) } ColorSpace.Gray -> { val rgb = toRgb() val gray = 0.299 * rgb.rf + 0.587 * rgb.gf + 0.114 * rgb.bf gray( white = gray, alpha = rgb.af, name = name, colorType = colorType ) } } } /** * Convert color to RGB components */ fun toRgb(): RGB { return when (colorSpace) { ColorSpace.RGB -> RGB( r = colorComponents[0], g = colorComponents[1], b = colorComponents[2], a = alpha ) ColorSpace.CMYK -> { val c = colorComponents[0] val m = colorComponents[1] val y = colorComponents[2] val k = colorComponents[3] RGB( r = (1.0 - c) * (1.0 - k), g = (1.0 - m) * (1.0 - k), b = (1.0 - y) * (1.0 - k), a = alpha ) } ColorSpace.Gray -> { val gray = colorComponents[0] RGB(r = gray, g = gray, b = gray, a = alpha) } ColorSpace.LAB -> { val labToRgb = Color( ColorUtils.LABToColor( colorComponents[0], colorComponents[1], colorComponents[2] ) ) RGB( r = labToRgb.red.toDouble(), g = labToRgb.green.toDouble(), b = labToRgb.blue.toDouble(), a = alpha ) } } } /** * Convert color to CMYK components */ fun toCmyk(): CMYK { val cmyk = if (colorSpace == ColorSpace.CMYK) { this.colorComponents } else { val rgb = toRgb() val r = rgb.rf val g = rgb.gf val b = rgb.bf val k = 1.0 - maxOf(r, g, b) val c = if (k < 1.0) (1.0 - r - k) / (1.0 - k) else 0.0 val m = if (k < 1.0) (1.0 - g - k) / (1.0 - k) else 0.0 val y = if (k < 1.0) (1.0 - b - k) / (1.0 - k) else 0.0 listOf(c, m, y, k) } return CMYK( c = cmyk[0], m = cmyk[1], y = cmyk[2], k = cmyk[3], a = alpha ) } /** * Convert color to LAB components */ fun toLab(): LAB { val lab = if (colorSpace == ColorSpace.LAB) this.colorComponents else { val arr = DoubleArray(3) ColorUtils.colorToLAB(toArgb(), arr) arr.toList() } return LAB( l = lab[0], a = lab[1], b = lab[2], alpha = alpha ) } /** * Convert color to HSB components */ fun toHsb(): HSB { val rgb = toRgb() val r = rgb.rf val g = rgb.gf val b = rgb.bf val max = maxOf(r, g, b) val min = minOf(r, g, b) val delta = max - min val h = when { delta == 0.0 -> 0.0 max == r -> ((g - b) / delta) % 6.0 * 60.0 max == g -> ((b - r) / delta + 2.0) * 60.0 else -> ((r - g) / delta + 4.0) * 60.0 }.let { if (it < 0) it + 360.0 else it } / 360.0 val s = if (max == 0.0) 0.0 else delta / max return HSB( h = h, s = s, b = max, a = alpha ) } /** * RGB color components */ data class RGB( val r: Double, val g: Double, val b: Double, val a: Double = 1.0 ) { val rf: Double get() = r.coerceIn(0.0, 1.0) val gf: Double get() = g.coerceIn(0.0, 1.0) val bf: Double get() = b.coerceIn(0.0, 1.0) val af: Double get() = a.coerceIn(0.0, 1.0) val r255: Int get() = (rf * 255).toInt() val g255: Int get() = (gf * 255).toInt() val b255: Int get() = (bf * 255).toInt() val a255: Int get() = (af * 255).toInt() } /** * CMYK color components */ data class CMYK( val c: Double, val m: Double, val y: Double, val k: Double, val a: Double = 1.0 ) { val cf: Double get() = c.coerceIn(0.0, 1.0) val mf: Double get() = m.coerceIn(0.0, 1.0) val yf: Double get() = y.coerceIn(0.0, 1.0) val kf: Double get() = k.coerceIn(0.0, 1.0) val af: Double get() = a.coerceIn(0.0, 1.0) } /** * LAB color components */ data class LAB( val l: Double, val a: Double, val b: Double, val alpha: Double = 1.0 ) /** * HSB color components */ data class HSB( val h: Double, val s: Double, val b: Double, val a: Double = 1.0 ) { val hf: Double get() = h.coerceIn(0.0, 1.0) val sf: Double get() = s.coerceIn(0.0, 1.0) val bf: Double get() = b.coerceIn(0.0, 1.0) val af: Double get() = a.coerceIn(0.0, 1.0) } } /** * Create from hex string */ fun PaletteColor( rgbHexString: String, format: ColorByteFormat = ColorByteFormat.RGBA, name: String = "", colorType: ColorType = ColorType.Normal ): PaletteColor { val rgb = extractHexRGBA(rgbHexString, format) ?: throw PaletteCoderException.InvalidRGBHexString(rgbHexString) return PaletteColor( name = name, colorType = colorType, colorSpace = ColorSpace.RGB, colorComponents = listOf(rgb.rf, rgb.gf, rgb.bf), alpha = rgb.af ) } /** * Create from CMYK hex string */ fun PaletteColor( cmykHexString: String, name: String = "", colorType: ColorType = ColorType.Normal ): PaletteColor { var hex = cmykHexString.lowercase().replace(Regex("[^0-9a-f]"), "") if (hex.startsWith("0x") || hex.startsWith("#")) { hex = hex.substring(2) } if (hex.startsWith("#")) { hex = hex.substring(1) } if (hex.length != 8) { throw PaletteCoderException.InvalidFormat() } val `val` = hex.toLongOrNull(16) ?: throw PaletteCoderException.InvalidFormat() val c = ((`val` shr 24) and 0xFF) / 255.0 val m = ((`val` shr 16) and 0xFF) / 255.0 val y = ((`val` shr 8) and 0xFF) / 255.0 val k = (`val` and 0xFF) / 255.0 return PaletteColor( name = name, colorType = colorType, colorSpace = ColorSpace.CMYK, colorComponents = listOf(c, m, y, k), alpha = 1.0 ) } /** * Create from CMYK hex string */ fun PaletteColor( color: Color, name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor = PaletteColor( name = name, colorType = colorType, colorSpace = ColorSpace.RGB, colorComponents = listOf( color.red.toDouble(), color.green.toDouble(), color.blue.toDouble() ), alpha = color.alpha.toDouble() ) /** * Create PaletteColor from Jetpack Compose Color */ fun Color.toPaletteColor( name: String = "", colorType: ColorType = ColorType.Global ): PaletteColor = PaletteColor( color = this, name = name, colorType = colorType ) ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/PaletteFormat.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette import com.t8rin.palette.coders.PaletteFormatCoder /** * Supported palette formats */ enum class PaletteFormat( val fileExtension: List, val withPaletteName: Boolean ) { ACB( fileExtension = listOf("acb"), withPaletteName = true ), // Adobe Color Book ACO( fileExtension = listOf("aco"), withPaletteName = true ), // Adobe Photoshop Swatch ACT( fileExtension = listOf("act"), withPaletteName = false ), // Adobe Color Tables ANDROID_XML( fileExtension = listOf("xml"), withPaletteName = false ), // Android XML Palette file ASE( fileExtension = listOf("ase"), withPaletteName = true ), // Adobe Swatch Exchange BASIC_XML( fileExtension = listOf("xml"), withPaletteName = true ), // Basic XML palette format COREL_PAINTER( fileExtension = listOf("txt"), withPaletteName = false ), // Corel Painter Swatches COREL_DRAW( fileExtension = listOf("xml"), withPaletteName = true ), // CorelDraw XML SCRIBUS_XML( fileExtension = listOf("xml"), withPaletteName = true ), // Scribus XML swatches COREL_PALETTE( fileExtension = listOf("cpl"), withPaletteName = true ), // Corel Palette CSV( fileExtension = listOf("csv"), withPaletteName = false ), // CSV Palette DCP( fileExtension = listOf("dcp"), withPaletteName = true ), // ColorPaletteCodable binary format GIMP( fileExtension = listOf("gpl"), withPaletteName = true ), // GIMP gpl format HEX_RGBA( fileExtension = listOf("hex"), withPaletteName = false ), // Hex RGBA coded files IMAGE( fileExtension = listOf("png", "jpg", "jpeg"), withPaletteName = false ), // image-based palette coder JSON( fileExtension = listOf("jsoncolorpalette", "json"), withPaletteName = true ), // ColorPaletteCodable JSON format OPEN_OFFICE( fileExtension = listOf("soc"), withPaletteName = false ), // OpenOffice palette format (.soc) PAINT_NET( fileExtension = listOf("txt"), withPaletteName = true ), // Paint.NET palette file (.txt) PAINT_SHOP_PRO( fileExtension = listOf("psppalette", "pal"), withPaletteName = true ), // Paint Shop Pro palette (.pal, .psppalette) RGBA( fileExtension = listOf("rgba", "txt"), withPaletteName = false ), // RGBA encoded text files (.rgba, .txt) RGB( fileExtension = listOf("rgb", "txt"), withPaletteName = false ), // RGB encoded text files (.rgb, .txt) RIFF( fileExtension = listOf("pal"), withPaletteName = false ), // Microsoft RIFF palette (.pal) SKETCH( fileExtension = listOf("sketchpalette"), withPaletteName = false ), // Sketch palette file (.sketchpalette) SKP( fileExtension = listOf("skp"), withPaletteName = false ), // SKP Palette SVG( fileExtension = listOf("svg"), withPaletteName = true ), // Scalable Vector Graphics palette (.svg) SWIFT( fileExtension = listOf("swift"), withPaletteName = true ), // (export only) Swift source file (.swift) KOTLIN( fileExtension = listOf("kt"), withPaletteName = true ), // (export only) Kotlin/Jetpack Compose source file (.kt) COREL_DRAW_V3( fileExtension = listOf("pal"), withPaletteName = true ), // Corel Draw V3 file (.pal) CLF( fileExtension = listOf("clf"), withPaletteName = true ), // LAB colors SWATCHES( fileExtension = listOf("swatches"), withPaletteName = true ), // Procreate swatches AUTODESK_COLOR_BOOK( fileExtension = listOf("acb"), withPaletteName = true ), // Autodesk Color Book (unencrypted only) (.acb) SIMPLE_PALETTE( fileExtension = listOf("color-palette"), withPaletteName = true ), // Simple Palette format SWATCHBOOKER( fileExtension = listOf("sbz"), withPaletteName = true ), // Swatchbooker .sbz file AFPALETTE( fileExtension = listOf("afpalette"), withPaletteName = true ), // Affinity Designer .afpalette file XARA( fileExtension = listOf("jcw"), withPaletteName = true ), // Xara palette file (.jcw) KOFFICE( fileExtension = listOf("colors"), withPaletteName = false ), // KOffice palette file (.colors) HPL( fileExtension = listOf("hpl"), withPaletteName = true ), // Homesite Palette file (.hpl) SKENCIL( fileExtension = listOf("spl"), withPaletteName = true ), // Skencil Palette file (.spl) VGA_24BIT( fileExtension = listOf("vga24"), withPaletteName = false ), // 24-bit RGB VGA (3 bytes RRGGBB) VGA_18BIT( fileExtension = listOf("vga18"), withPaletteName = false ), // 18-bit RGB VGA (3 bytes RRGGBB) KRITA( fileExtension = listOf("kpl"), withPaletteName = true ); // KRITA Palette file (.kpl) companion object { /** * Get format from file extension * Searches through all enum entries to find matching file extension */ fun fromFilename(filename: String): PaletteFormat? = PaletteFormat.entries.firstOrNull { format -> format.fileExtension.isNotEmpty() && format.fileExtension.any(filename::endsWith) } } } /** * Get coder for format */ fun PaletteFormat.getCoder(): PaletteCoder = PaletteFormatCoder(this) ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/ACBPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.InputStream import java.io.OutputStream /** * Adobe Color Book (ACB) palette coder (decode only) */ class ACBPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val parser = BytesReader(input) val result = Palette.Builder() // Check BOM "8BCB" val bom = parser.readStringASCII(4) if (bom != "8BCB") { throw PaletteCoderException.InvalidBOM() } // Version val version = parser.readUInt16(ByteOrder.BIG_ENDIAN) if (version.toInt() != 1) { // Log warning but continue } // Identifier parser.readUInt16(ByteOrder.BIG_ENDIAN) // Title var title = parser.readAdobePascalStyleString() if (title.startsWith("$$$")) { title = title.split("=").lastOrNull() ?: title } result.name = title // Prefix, suffix, description (skip) parser.readAdobePascalStyleString() parser.readAdobePascalStyleString() parser.readAdobePascalStyleString() // Color count val colorCount = parser.readUInt16(ByteOrder.BIG_ENDIAN).toInt() // Page size, page selector offset (skip) parser.readUInt16(ByteOrder.BIG_ENDIAN) parser.readUInt16(ByteOrder.BIG_ENDIAN) // Color space val colorSpace = parser.readUInt16(ByteOrder.BIG_ENDIAN).toInt() val colorspace: ColorSpace val componentCount: Int when (colorSpace) { 0 -> { // RGB colorspace = ColorSpace.RGB componentCount = 3 } 2 -> { // CMYK colorspace = ColorSpace.CMYK componentCount = 4 } 7 -> { // LAB colorspace = ColorSpace.LAB componentCount = 3 } 8 -> { // Grayscale colorspace = ColorSpace.Gray componentCount = 1 } else -> throw PaletteCoderException.UnsupportedColorSpace() } // Read colors for (i in 0 until colorCount) { val colorName = parser.readAdobePascalStyleString() val colorCode = parser.readStringASCII(6) // Color channels val channels = parser.readBytes(componentCount) if (colorName.trim().isEmpty() && colorCode.trim().isEmpty()) { continue } val mapped = channels.map { it.toUByte().toDouble() } val components: List when (colorspace) { ColorSpace.CMYK -> { components = listOf( ((255.0 - mapped[0]) / 255.0).coerceIn(0.0, 1.0), ((255.0 - mapped[1]) / 255.0).coerceIn(0.0, 1.0), ((255.0 - mapped[2]) / 255.0).coerceIn(0.0, 1.0), ((255.0 - mapped[3]) / 255.0).coerceIn(0.0, 1.0) ) } ColorSpace.RGB -> { components = listOf( (mapped[0] / 255.0).coerceIn(0.0, 1.0), (mapped[1] / 255.0).coerceIn(0.0, 1.0), (mapped[2] / 255.0).coerceIn(0.0, 1.0) ) } ColorSpace.LAB -> { components = listOf( mapped[0] / 2.55, // 0...100 mapped[1] - 128.0, // -128...128 mapped[2] - 128.0 // -128...128 ) } ColorSpace.Gray -> { components = listOf((mapped[0] / 255.0).coerceIn(0.0, 1.0)) } } try { val color = PaletteColor( name = colorName, colorSpace = colorspace, colorComponents = components, alpha = 1.0 ) result.colors.add(color) } catch (_: Throwable) { // Skip invalid colors } } // Spot identifier (may or may not be present) try { parser.readStringASCII(8) } catch (_: Throwable) { // Ignore if not present } return result.build() } @Suppress("VariableNeverRead", "AssignedValueIsNeverRead") override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) val allColors = palette.allColors() val colorCount = allColors.size if (colorCount == 0) { throw PaletteCoderException.TooFewColors() } // Determine color space - use first color's space, or convert all to RGB // ACB supports RGB (0), CMYK (2), LAB (7), Grayscale (8) var targetColorSpace = allColors[0].colorSpace var acbColorSpace: Int var componentCount: Int // Check if all colors can be in the same space, otherwise convert to RGB val allSameSpace = allColors.all { it.colorSpace == targetColorSpace } if (!allSameSpace || targetColorSpace !in listOf( ColorSpace.RGB, ColorSpace.CMYK, ColorSpace.LAB, ColorSpace.Gray ) ) { targetColorSpace = ColorSpace.RGB } when (targetColorSpace) { ColorSpace.RGB -> { acbColorSpace = 0 componentCount = 3 } ColorSpace.CMYK -> { acbColorSpace = 2 componentCount = 4 } ColorSpace.LAB -> { acbColorSpace = 7 componentCount = 3 } ColorSpace.Gray -> { acbColorSpace = 8 componentCount = 1 } } // BOM "8BCB" (4 bytes) writer.writeStringASCII("8BCB") // Version (2 bytes, big endian) - typically 1 writer.writeUInt16(1u, ByteOrder.BIG_ENDIAN) // Identifier (2 bytes, big endian) - typically 0 writer.writeUInt16(0u, ByteOrder.BIG_ENDIAN) // Title (Adobe Pascal style string) val title = palette.name.ifEmpty { "Palette" } writer.writeAdobePascalStyleString(title) // Prefix (Adobe Pascal style string) - empty writer.writeAdobePascalStyleString("") // Suffix (Adobe Pascal style string) - empty writer.writeAdobePascalStyleString("") // Description (Adobe Pascal style string) - empty writer.writeAdobePascalStyleString("") // Color count (2 bytes, big endian) writer.writeUInt16(colorCount.toUShort(), ByteOrder.BIG_ENDIAN) // Page size (2 bytes, big endian) - typically 0 writer.writeUInt16(0u, ByteOrder.BIG_ENDIAN) // Page selector offset (2 bytes, big endian) - typically 0 writer.writeUInt16(0u, ByteOrder.BIG_ENDIAN) // Color space (2 bytes, big endian) writer.writeUInt16(acbColorSpace.toUShort(), ByteOrder.BIG_ENDIAN) // Write colors allColors.forEach { color -> // Convert color to target color space val convertedColor = if (color.colorSpace == targetColorSpace) { color } else { try { color.converted(targetColorSpace) } catch (_: Throwable) { // Fallback: convert to RGB color.converted(ColorSpace.RGB) } } // Color name (Adobe Pascal style string) val colorName = convertedColor.name.ifEmpty { "Color" } writer.writeAdobePascalStyleString(colorName) // Color code (6 bytes ASCII) - typically empty or hex code val hexCode = try { val rgb = convertedColor.toRgb() String.format( "%02X%02X%02X", (rgb.rf * 255).toInt().coerceIn(0, 255), (rgb.gf * 255).toInt().coerceIn(0, 255), (rgb.bf * 255).toInt().coerceIn(0, 255) ) } catch (_: Throwable) { "000000" } writer.writeStringASCII(hexCode.padEnd(6, '0').take(6)) // Color channels val channels = when (targetColorSpace) { ColorSpace.CMYK -> { // CMYK: 0 = 100% ink, so invert listOf( ((1.0 - convertedColor.colorComponents[0]) * 255).toInt().coerceIn(0, 255) .toByte(), ((1.0 - convertedColor.colorComponents[1]) * 255).toInt().coerceIn(0, 255) .toByte(), ((1.0 - convertedColor.colorComponents[2]) * 255).toInt().coerceIn(0, 255) .toByte(), ((1.0 - convertedColor.colorComponents[3]) * 255).toInt().coerceIn(0, 255) .toByte() ) } ColorSpace.RGB -> { listOf( (convertedColor.colorComponents[0] * 255).toInt().coerceIn(0, 255).toByte(), (convertedColor.colorComponents[1] * 255).toInt().coerceIn(0, 255).toByte(), (convertedColor.colorComponents[2] * 255).toInt().coerceIn(0, 255).toByte() ) } ColorSpace.LAB -> { listOf( (convertedColor.colorComponents[0] * 2.55).toInt().coerceIn(0, 255) .toByte(), ((convertedColor.colorComponents[1] + 128.0).toInt() .coerceIn(0, 255)).toByte(), ((convertedColor.colorComponents[2] + 128.0).toInt() .coerceIn(0, 255)).toByte() ) } ColorSpace.Gray -> { listOf( (convertedColor.colorComponents[0] * 255).toInt().coerceIn(0, 255).toByte() ) } } writer.writeData(channels.toByteArray()) } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/ACOPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.InputStream import java.io.OutputStream import kotlin.math.abs /** * Adobe Photoshop Swatch (ACO) palette coder */ class ACOPaletteCoder : PaletteCoder { private enum class ACOColorspace(val rawValue: UShort) { RGB(0u), HSB(1u), CMYK(2u), LAB(7u), GRAYSCALE(8u) } override fun decode(input: InputStream): Palette { val reader = BytesReader(input) val result = Palette.Builder() val v1Colors = mutableListOf() val v2Colors = mutableListOf() for (type in 1..2) { try { val version = reader.readUInt16(ByteOrder.BIG_ENDIAN) if (version.toInt() != type) { throw PaletteCoderException.InvalidVersion() } } catch (_: Throwable) { // Version 1 file only result.colors = v1Colors return result.build() } val numberOfColors = reader.readUInt16(ByteOrder.BIG_ENDIAN) repeat(numberOfColors.toInt()) { val colorSpace = reader.readUInt16(ByteOrder.BIG_ENDIAN) val c0 = reader.readUInt16(ByteOrder.BIG_ENDIAN) val c1 = reader.readUInt16(ByteOrder.BIG_ENDIAN) val c2 = reader.readUInt16(ByteOrder.BIG_ENDIAN) val c3 = reader.readUInt16(ByteOrder.BIG_ENDIAN) val name = if (type == 2) { reader.readAdobePascalStyleString() } else { "" } val acoSpace = ACOColorspace.entries.find { it.rawValue == colorSpace } val color = when (acoSpace) { ACOColorspace.RGB -> PaletteColor.rgb( r = c0.toDouble() / 65535.0, g = c1.toDouble() / 65535.0, b = c2.toDouble() / 65535.0, name = name ) ACOColorspace.CMYK -> PaletteColor.cmyk( c = (65535 - c0.toInt()).toDouble() / 65535.0, m = (65535 - c1.toInt()).toDouble() / 65535.0, y = (65535 - c2.toInt()).toDouble() / 65535.0, k = (65535 - c3.toInt()).toDouble() / 65535.0, name = name ) ACOColorspace.GRAYSCALE -> PaletteColor.gray( white = c0.toDouble() / 10000.0, name = name ) ACOColorspace.LAB -> PaletteColor.lab( l = c0.toDouble() / 100.0, a = c1.toDouble() / 100.0, b = c2.toDouble() / 100.0, name = name ) ACOColorspace.HSB -> { // Convert HSB to RGB val h = c0.toDouble() / 65535.0 val s = c1.toDouble() / 65535.0 val b = c2.toDouble() / 65535.0 // Simple HSB to RGB conversion val rgb = hsbToRgb(h, s, b) PaletteColor.rgb(rgb.first, rgb.second, rgb.third, name = name) } null -> PaletteColor.rgb(1.0, 0.0, 0.0, 0.5, name = "Unsupported Colorspace") } if (type == 1) { v1Colors.add(color) } else { v2Colors.add(color) } } } if (v2Colors.isNotEmpty()) { result.colors = v2Colors } else { result.colors = v1Colors } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) val allColors = palette.allColors() // Write both v1 and v2 for (type in 1..2) { writer.writeUInt16(type.toUShort(), ByteOrder.BIG_ENDIAN) writer.writeUInt16(allColors.size.toUShort(), ByteOrder.BIG_ENDIAN) allColors.forEach { color -> val (c0, c1, c2, c3, acoModel) = when (color.colorSpace) { ColorSpace.RGB -> { val rgb = color.toRgb() Quad( (rgb.rf * 65535).toInt().toUShort(), (rgb.gf * 65535).toInt().toUShort(), (rgb.bf * 65535).toInt().toUShort(), 0u, ACOColorspace.RGB ) } ColorSpace.CMYK -> { Quad( (65535 - (color.colorComponents[0] * 65535).toInt()).toUShort(), (65535 - (color.colorComponents[1] * 65535).toInt()).toUShort(), (65535 - (color.colorComponents[2] * 65535).toInt()).toUShort(), (65535 - (color.colorComponents[3] * 65535).toInt()).toUShort(), ACOColorspace.CMYK ) } ColorSpace.Gray -> { Quad( (color.colorComponents[0] * 10000).toInt().toUShort(), 0u, 0u, 0u, ACOColorspace.GRAYSCALE ) } ColorSpace.LAB -> { // Convert LAB to RGB for ACO val converted = color.converted(ColorSpace.RGB) val rgb = converted.toRgb() Quad( (rgb.rf * 65535).toInt().toUShort(), (rgb.gf * 65535).toInt().toUShort(), (rgb.bf * 65535).toInt().toUShort(), 0u, ACOColorspace.RGB ) } } writer.writeUInt16(acoModel.rawValue, ByteOrder.BIG_ENDIAN) writer.writeUInt16(c0, ByteOrder.BIG_ENDIAN) writer.writeUInt16(c1, ByteOrder.BIG_ENDIAN) writer.writeUInt16(c2, ByteOrder.BIG_ENDIAN) writer.writeUInt16(c3, ByteOrder.BIG_ENDIAN) if (type == 2) { writer.writeAdobePascalStyleString(color.name) } } } } private data class Quad( val a: UShort, val b: UShort, val c: UShort, val d: UShort, val model: ACOColorspace ) private fun hsbToRgb(h: Double, s: Double, b: Double): Triple { val c = b * s val x = c * (1 - abs((h * 6) % 2 - 1)) val m = b - c val (r, g, bl) = when { h < 1.0 / 6 -> Triple(c, x, 0.0) h < 2.0 / 6 -> Triple(x, c, 0.0) h < 3.0 / 6 -> Triple(0.0, c, x) h < 4.0 / 6 -> Triple(0.0, x, c) h < 5.0 / 6 -> Triple(x, 0.0, c) else -> Triple(c, 0.0, x) } return Triple(r + m, g + m, bl + m) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/ACTPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets /** * Adobe Color Table (ACT) palette coder */ class ACTPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val allData = input.readBytes() val reader = BytesReader(allData) val result = Palette.Builder() // Read 256 RGB colors (768 bytes) repeat(256) { val rgb = reader.readData(3) val r = rgb[0].toUByte().toInt() val g = rgb[1].toUByte().toInt() val b = rgb[2].toUByte().toInt() val color = PaletteColor.rgb( r = r / 255.0, g = g / 255.0, b = b / 255.0 ) result.colors.add(color) } var numColors: Int // Try to read number of colors (optional) try { val numColorsBytes = reader.readData(2) numColors = ((numColorsBytes[0].toUByte().toInt() shl 8) or numColorsBytes[1].toUByte() .toInt()).toShort().toInt() if (numColors in 1..<256) { result.colors = result.colors.take(numColors).toMutableList() } } catch (_: Throwable) { // No number of colors field } // Try to read transparency index (optional) try { val alphaIndexBytes = reader.readData(2) val alphaIndex = ((alphaIndexBytes[0].toUByte().toInt() shl 8) or alphaIndexBytes[1].toUByte() .toInt()).toShort() if (alphaIndex >= 0 && alphaIndex < result.colors.size) { result.colors[alphaIndex.toInt()] = result.colors[alphaIndex.toInt()].withAlpha(0.0) } } catch (_: Throwable) { // No transparency index field } // Try to read color names from extension (non-standard) try { val remainingData = allData.sliceArray(reader.readPosition.toInt() until allData.size) val remainingText = String(remainingData, StandardCharsets.UTF_8) val nameLine = remainingText.lines().find { it.startsWith("; ACT_NAMES:") } if (nameLine != null) { val namesStr = nameLine.substring("; ACT_NAMES: ".length).trim() val names = namesStr.split("|") names.forEachIndexed { index, name -> if (index < result.colors.size && name.isNotEmpty()) { result.colors[index] = result.colors[index].named(name) } } } } catch (_: Throwable) { // No names extension, continue without names } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) val flattenedColors = palette.allColors().map { it.toRgb() } val colors = flattenedColors.take(256) val maxColors = colors.size // Write 256 colors (pad with zeros if needed) repeat(256) { index -> if (index < maxColors) { val c = colors[index] writer.writeData( byteArrayOf( (c.rf * 255).toInt().coerceIn(0, 255).toByte(), (c.gf * 255).toInt().coerceIn(0, 255).toByte(), (c.bf * 255).toInt().coerceIn(0, 255).toByte() ) ) } else { writer.writeData(byteArrayOf(0, 0, 0)) } } // Write number of colors if less than 256 if (maxColors < 256) { writer.writeUInt16(maxColors.toUShort(), ByteOrder.BIG_ENDIAN) writer.writeUInt16(0xFFFFu, ByteOrder.BIG_ENDIAN) } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/AFPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.InputStream import java.io.OutputStream /** * Affinity Designer Palette (AFPalette) coder — более терпимый парсер: * - пытается определить BOM в разных эндианах * - вместо жёсткой ошибки ищет маркеры в потоке * - при проблемах возвращает корректную ошибку */ class AFPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val allData = input.readBytes() val parser = BytesReader(allData) val result = Palette.Builder() var hasUnsupportedColorType = false // Helper: try both byte orders for a 4-byte ASCII marker fun findMarkerAnyOrder(markerLE: ByteArray, markerBE: ByteArray): Int { // findPattern returns index or -1 val idx1 = parser.findPattern(markerLE) if (idx1 >= 0) return idx1 val idx2 = parser.findPattern(markerBE) return idx2 } // MARK: BOM / NClP try { // try BOM little-endian first val startPos = parser.readPosition.toInt() try { val bom = parser.readUInt32(ByteOrder.LITTLE_ENDIAN).toInt() if (bom != 0x414BFF00 && bom != 0x00FF4B41) { // not recognized -> fallthrough to searching markers parser.seekSet(startPos) throw Throwable("not bom") } // ok — BOM accepted, continue from current position } catch (_: Throwable) { // try to find NClP marker in data (accept both byte orders) parser.seekSet(0) val nclpLE = byteArrayOf(0x4E, 0x43, 0x6C, 0x50) // 'NClP' val nclpBE = byteArrayOf(0x50, 0x6C, 0x43, 0x4E) // reversed form sometimes used val found = findMarkerAnyOrder(nclpLE, nclpBE) if (found < 0) throw PaletteCoderException.InvalidBOM() // position should be just after marker parser.seekSet(found + 4) } } catch (e: PaletteCoderException) { throw e } catch (_: Throwable) { throw PaletteCoderException.InvalidBOM() } // Now we expect NClP somewhere after current position. // Ensure we're positioned right after an NClP-like marker. try { // If current bytes are not NClP, try to locate it from current pos runCatching { parser.readUInt32(ByteOrder.LITTLE_ENDIAN) .toInt() parser.seekSet((parser.readPosition - 4).toInt()) } // Simpler: just try to find marker from current pos try { parser.seekToNextInstanceOfPattern(0x4E, 0x43, 0x6C, 0x50) // 'NClP' // move cursor to just after marker parser.seek(4) } catch (_: Throwable) { // try reversed marker try { parser.seekToNextInstanceOfPattern(0x50, 0x6C, 0x43, 0x4E) parser.seek(4) } catch (_: Throwable) { // if we can't find it, it's invalid throw PaletteCoderException.InvalidFormat() } } } catch (e: PaletteCoderException) { throw e } // Filename length (uint32 little-endian) + name ASCII val filenameLen = try { parser.readUInt32(ByteOrder.LITTLE_ENDIAN).toInt() } catch (_: Throwable) { throw PaletteCoderException.InvalidFormat() } val filename = try { if (filenameLen <= 0) "" else parser.readStringASCII(filenameLen) } catch (_: Throwable) { "" } result.name = filename // Найти VlaP (pattern can be in two byte orders) try { try { parser.seekToNextInstanceOfPattern(0x56, 0x6C, 0x61, 0x50) // maybe reversed } catch (_: Throwable) { parser.seekToNextInstanceOfPattern(0x50, 0x61, 0x6C, 0x56) // other order } // position is at start of marker; move after it parser.seek(4) } catch (_: Throwable) { throw PaletteCoderException.InvalidFormat() } val colorCount = try { parser.readUInt32(ByteOrder.LITTLE_ENDIAN).toInt() } catch (_: Throwable) { throw PaletteCoderException.InvalidFormat() } val colors = mutableListOf() for (index in 0 until colorCount) { index + 1 try { // Find "rloC" marker (either ASCII or bytes), then set cursor to start try { parser.seekToNextInstanceOfASCII("rloC") } catch (_: Throwable) { // try bytes variant try { parser.seekToNextInstanceOfPattern(0x72, 0x6C, 0x6F, 0x43) } catch (_: Throwable) { // couldn't find marker for this color — break/handle if (colors.isEmpty()) throw PaletteCoderException.InvalidFormat() break } } // We're at start of "rloC" // advance past marker parser.seek(4) // Safe skip 6 bytes if available (unknown data) parser.trySkipBytes(6) // Read color type (4 ASCII chars) val colorType = try { parser.readStringASCII(4) } catch (_: Throwable) { // cannot read type -> break if (colors.isEmpty()) throw PaletteCoderException.InvalidFormat() break } when (colorType) { "ABGR" -> { // Find "Dloc_" (marker) - accept ASCII search try { parser.seekToNextInstanceOfASCII("Dloc_") parser.seek(5) // move after 'Dloc_' } catch (_: Throwable) { // if not found, continue — maybe values are right here } val r = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() val g = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() val b = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() colors.add(PaletteColor.rgb(r = r, g = g, b = b)) } "ABAL" -> { try { parser.seekToNextInstanceOfASCII(" { try { parser.seekToNextInstanceOfASCII("Hloc_"); parser.seek(5) } catch (_: Throwable) { } val c = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() val m = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() val y = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() val k = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() colors.add(PaletteColor.cmyk(c = c, m = m, y = y, k = k)) } "ALSH" -> { try { parser.seekToNextInstanceOfASCII("Dloc_"); parser.seek(5) } catch (_: Throwable) { } val h = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() val s = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() val l = parser.readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() colors.add(PaletteColor.hsl(hf = h, sf = s, lf = l)) } "YARG" -> { try { parser.seekToNextInstanceOfASCII(" { hasUnsupportedColorType = true throw PaletteCoderException.CannotCreateColor() } } } catch (_: PaletteCoderException) { if (hasUnsupportedColorType) throw PaletteCoderException.UnsupportedPaletteType() if (colors.isEmpty()) throw PaletteCoderException.InvalidFormat() break } catch (_: Throwable) { if (colors.isEmpty()) throw PaletteCoderException.InvalidFormat() break } } // Read names section (VNaP) if present try { // try finding either order of VNaP val vnapIdx = parser.findPattern(byteArrayOf(0x56, 0x4e, 0x61, 0x50)).takeIf { it >= 0 } ?: parser.findPattern(byteArrayOf(0x50, 0x61, 0x4E, 0x56)).takeIf { it >= 0 } if (vnapIdx != null) { parser.seekSet(vnapIdx + 4) // unknown offset parser.readUInt32(ByteOrder.LITTLE_ENDIAN) val nameCount = parser.readUInt32(ByteOrder.LITTLE_ENDIAN).toInt() for (i in 0 until minOf(nameCount, colors.size)) { try { val colorNameLen = parser.readUInt32(ByteOrder.LITTLE_ENDIAN).toInt() val colorName = parser.readStringUTF8(colorNameLen) colors[i] = colors[i].copy( name = colorName ) } catch (_: Throwable) { break } } } } catch (_: Throwable) { // ignore — names optional } if (colors.isEmpty()) throw PaletteCoderException.InvalidFormat() result.colors = colors return result.build() } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) val allColors = palette.allColors() val colorCount = allColors.size if (colorCount == 0) throw PaletteCoderException.TooFewColors() // BOM (как раньше), версия, и т.д. writer.writeUInt32(0x414BFF00u, ByteOrder.LITTLE_ENDIAN) writer.writeUInt32(11u, ByteOrder.LITTLE_ENDIAN) writer.writePattern(0x50, 0x6C, 0x43, 0x4E) // NClP val filename = palette.name.ifEmpty { "Palette" } writer.writeStringASCIIWithLength(filename, ByteOrder.LITTLE_ENDIAN) writer.writePattern(0x50, 0x61, 0x6C, 0x56) // VlaP writer.writeUInt32(colorCount.toUInt(), ByteOrder.LITTLE_ENDIAN) allColors.forEach { color -> writer.writePattern(0x72, 0x6C, 0x6F, 0x43) // rloC writer.writePattern(0x00, 0x00, 0x00, 0x00, 0x00, 0x00) // skip 6 writer.writePattern(0x41, 0x42, 0x47, 0x52) // ABGR writer.writePattern(0x44, 0x6C, 0x6F, 0x63, 0x5F) // Dloc_ val rgb = color.toRgb() writer.writeFloat32(rgb.rf.toFloat(), ByteOrder.LITTLE_ENDIAN) writer.writeFloat32(rgb.gf.toFloat(), ByteOrder.LITTLE_ENDIAN) writer.writeFloat32(rgb.bf.toFloat(), ByteOrder.LITTLE_ENDIAN) } writer.writePattern(0x50, 0x61, 0x4E, 0x56) // VNaP writer.writeUInt32(0u, ByteOrder.LITTLE_ENDIAN) writer.writeUInt32(colorCount.toUInt(), ByteOrder.LITTLE_ENDIAN) allColors.forEach { color -> val colorName = color.name.ifEmpty { "Color" } writer.writeStringUTF8WithLength(colorName, ByteOrder.LITTLE_ENDIAN) } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/ASEPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.ColorSpace import com.t8rin.palette.ColorType import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream /** * Adobe Swatch Exchange (ASE) palette coder */ class ASEPaletteCoder : PaletteCoder { companion object { private val ASE_HEADER_DATA = byteArrayOf(65, 83, 69, 70) // "ASEF" private val ASE_GROUP_START: UShort = 0xC001u private val ASE_GROUP_END: UShort = 0xC002u private val ASE_BLOCK_COLOR: UShort = 0x0001u } override fun decode(input: InputStream): Palette { val reader = BytesReader(input) val result = Palette.Builder() // Read and validate header val header = reader.readData(4) if (!header.contentEquals(ASE_HEADER_DATA)) { throw PaletteCoderException.InvalidASEHeader() } // Read version val version0 = reader.readUInt16(ByteOrder.BIG_ENDIAN) val version1 = reader.readUInt16(ByteOrder.BIG_ENDIAN) if (version0.toUInt() != 1u || version1.toUInt() != 0u) { // Unknown version, but continue } // Read number of blocks val numberOfBlocks = reader.readUInt32(ByteOrder.BIG_ENDIAN) var currentGroup: ColorGroup? = null // Read all blocks repeat(numberOfBlocks.toInt()) { val type = reader.readUInt16(ByteOrder.BIG_ENDIAN) reader.readUInt32(ByteOrder.BIG_ENDIAN) when (type) { ASE_GROUP_START -> { if (currentGroup != null) { throw PaletteCoderException.GroupAlreadyOpen() } currentGroup = readStartGroupBlock(reader) } ASE_GROUP_END -> { if (currentGroup == null) { throw PaletteCoderException.GroupNotOpen() } result.groups.add(currentGroup) currentGroup = null } ASE_BLOCK_COLOR -> { val color = readColor(reader) if (currentGroup != null) { currentGroup = currentGroup.copy( colors = currentGroup.colors + color ) } else { result.colors.add(color) } } else -> throw PaletteCoderException.UnknownBlockType() } } // If there's still an open group, add it if (currentGroup != null) { result.groups.add(currentGroup) } return result.build() } private fun readStartGroupBlock(reader: BytesReader): ColorGroup { reader.readUInt16(ByteOrder.BIG_ENDIAN) val name = reader.readStringUTF16NullTerminated(ByteOrder.BIG_ENDIAN) return ColorGroup(name = name) } private fun readColor(reader: BytesReader): PaletteColor { reader.readUInt16(ByteOrder.BIG_ENDIAN) val name = reader.readStringUTF16NullTerminated(ByteOrder.BIG_ENDIAN) val colorModel = when (val mode = reader.readStringASCII(4)) { "CMYK" -> ASEColorModel.CMYK "RGB " -> ASEColorModel.RGB "LAB " -> ASEColorModel.LAB "Gray" -> ASEColorModel.Gray else -> throw PaletteCoderException.UnknownColorMode(mode) } val colors: List val colorspace: ColorSpace when (colorModel) { ASEColorModel.CMYK -> { colorspace = ColorSpace.CMYK colors = listOf( reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble(), reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble(), reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble(), reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble() ) } ASEColorModel.RGB -> { colorspace = ColorSpace.RGB colors = listOf( reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble(), reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble(), reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble() ) } ASEColorModel.LAB -> { colorspace = ColorSpace.LAB colors = listOf( reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble() * 100.0, reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble(), reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble() ) } ASEColorModel.Gray -> { colorspace = ColorSpace.Gray colors = listOf(reader.readFloat32(ByteOrder.BIG_ENDIAN).toDouble()) } } val colorTypeValue = reader.readUInt16(ByteOrder.BIG_ENDIAN) val colorType = when (colorTypeValue.toInt()) { 0 -> ColorType.Global 1 -> ColorType.Spot 2 -> ColorType.Normal else -> throw PaletteCoderException.UnknownColorType(colorTypeValue.toInt()) } return PaletteColor( colorSpace = colorspace, colorComponents = colors, name = name, colorType = colorType ) } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) // Write header writer.writeData(ASE_HEADER_DATA) writer.writeUInt16(1u, ByteOrder.BIG_ENDIAN) writer.writeUInt16(0u, ByteOrder.BIG_ENDIAN) var totalBlocks = palette.colors.size + (palette.groups.size * 2) palette.groups.forEach { totalBlocks += it.colors.size } writer.writeUInt32(totalBlocks.toUInt(), ByteOrder.BIG_ENDIAN) // Write global colors palette.colors.forEach { color -> writeColorData(writer, color) } // Write groups palette.groups.forEach { group -> // Group header writer.writeUInt16(ASE_GROUP_START, ByteOrder.BIG_ENDIAN) val groupData = ByteArrayOutputStream() val groupWriter = BytesWriter(groupData) val groupNameBytes = group.name.toByteArray(java.nio.charset.StandardCharsets.UTF_16BE) val groupNameLen = (group.name.length + 1).toUShort() groupWriter.writeUInt16(groupNameLen, ByteOrder.BIG_ENDIAN) if (groupNameBytes.isNotEmpty()) { groupWriter.writeData(groupNameBytes) } groupWriter.writeData(byteArrayOf(0, 0)) // null terminator writer.writeUInt32(groupData.size().toUInt(), ByteOrder.BIG_ENDIAN) writer.writeData(groupData.toByteArray()) // Group colors group.colors.forEach { color -> writeColorData(writer, color) } // Group footer writer.writeUInt16(ASE_GROUP_END, ByteOrder.BIG_ENDIAN) writer.writeUInt32(0u, ByteOrder.BIG_ENDIAN) } } private fun writeColorData(writer: BytesWriter, color: PaletteColor) { writer.writeUInt16(ASE_BLOCK_COLOR, ByteOrder.BIG_ENDIAN) val colorData = ByteArrayOutputStream() val colorWriter = BytesWriter(colorData) // Write name val colorNameBytes = color.name.toByteArray(java.nio.charset.StandardCharsets.UTF_16BE) val colorNameLen = (color.name.length + 1).toUShort() colorWriter.writeUInt16(colorNameLen, ByteOrder.BIG_ENDIAN) if (colorNameBytes.isNotEmpty()) { colorWriter.writeData(colorNameBytes) } colorWriter.writeData(byteArrayOf(0, 0)) // null terminator // Write model val colorModel = when (color.colorSpace) { ColorSpace.RGB -> ASEColorModel.RGB ColorSpace.CMYK -> ASEColorModel.CMYK ColorSpace.LAB -> ASEColorModel.LAB ColorSpace.Gray -> ASEColorModel.Gray } colorWriter.writeStringASCII(colorModel.rawValue) // Write components when (color.colorSpace) { ColorSpace.CMYK -> { color.colorComponents.forEach { comp -> colorWriter.writeFloat32(comp.toFloat(), ByteOrder.BIG_ENDIAN) } } ColorSpace.RGB -> { color.colorComponents.forEach { comp -> colorWriter.writeFloat32(comp.toFloat(), ByteOrder.BIG_ENDIAN) } } ColorSpace.LAB -> { colorWriter.writeFloat32( (color.colorComponents[0] / 100.0).toFloat(), ByteOrder.BIG_ENDIAN ) colorWriter.writeFloat32(color.colorComponents[1].toFloat(), ByteOrder.BIG_ENDIAN) colorWriter.writeFloat32(color.colorComponents[2].toFloat(), ByteOrder.BIG_ENDIAN) } ColorSpace.Gray -> { colorWriter.writeFloat32(color.colorComponents[0].toFloat(), ByteOrder.BIG_ENDIAN) } } // Write color type val colorTypeValue: UShort = when (color.colorType) { ColorType.Global -> 0u ColorType.Spot -> 1u ColorType.Normal -> 2u } colorWriter.writeUInt16(colorTypeValue, ByteOrder.BIG_ENDIAN) writer.writeUInt32(colorData.size().toUInt(), ByteOrder.BIG_ENDIAN) writer.writeData(colorData.toByteArray()) } private enum class ASEColorModel(val rawValue: String) { CMYK("CMYK"), RGB("RGB "), LAB("LAB "), Gray("Gray") } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/AndroidColorsXMLCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.xmlDecoded import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import javax.xml.parsers.SAXParserFactory /** * Android colors.xml palette coder */ class AndroidColorsXMLCoder( private val includeAlphaDuringExport: Boolean = true ) : PaletteCoder { private class AndroidXMLHandler : DefaultHandler() { val palette = Palette.Builder() private var currentElement = "" private var currentName: String? = null private var isInsideResourcesBlock = false private var currentChars = StringBuilder() private fun elementName(localName: String, qName: String?): String = localName.ifBlank { qName ?: "" }.substringAfter(':').lowercase() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() when (elementName(localName, qName)) { "resources" -> isInsideResourcesBlock = true "color" -> { currentElement = "color" currentName = attributes.getValue("name")?.xmlDecoded() } } } override fun endElement(uri: String?, localName: String, qName: String?) { when (elementName(localName, qName)) { "resources" -> isInsideResourcesBlock = false "color" -> { if (isInsideResourcesBlock && currentElement == "color") { val colorString = currentChars.toString().trim() val colorName = currentName ?: "color_${palette.colors.size}" try { val color = PaletteColor( rgbHexString = colorString, format = ColorByteFormat.ARGB, name = colorName ) palette.colors.add(color) } catch (_: Throwable) { // Skip invalid colors } } currentElement = "" currentName = null } } currentChars.clear() } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } } override fun decode(input: InputStream): Palette { val handler = AndroidXMLHandler() val factory = SAXParserFactory.newInstance() val parser = factory.newSAXParser() parser.parse(input, handler) if (handler.palette.colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return handler.palette.build() } override fun encode(palette: Palette, output: OutputStream) { var xml = """ """ palette.allColors().forEachIndexed { index, color -> var name = color.name.ifEmpty { "color_$index" } name = name.replace(" ", "_").xmlEscaped() val format = if (includeAlphaDuringExport) ColorByteFormat.ARGB else ColorByteFormat.RGB val hex = color.hexString(format, hashmark = true, uppercase = true) xml += " $hex\n" } xml += "\n" output.write(xml.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/AutodeskColorBookCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.xmlDecoded import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets import javax.xml.parsers.SAXParserFactory class AutodeskColorBookCoder : PaletteCoder { private class AutodeskXMLHandler : DefaultHandler() { val palette = Palette.Builder() private var currentGroup: ColorGroup? = null private var colorName: String? = null private var r: Int? = null private var g: Int? = null private var b: Int? = null private val xmlStack = mutableListOf() private var currentChars = StringBuilder() private fun elm(localName: String?, qName: String?) = (qName ?: localName ?: "").trim() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() val elementName = elm(localName, qName) when (elementName.lowercase()) { "colorpage" -> { val groupName = attributes.getValue("name")?.xmlDecoded() ?: "" currentGroup = ColorGroup(name = groupName) } "colorentry", "pagecolor" -> { r = null; g = null; b = null; colorName = null } } xmlStack.add(elementName) } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } override fun endElement(uri: String?, localName: String, qName: String?) { val elementName = elm(localName, qName) val content = currentChars.toString().trim() when (elementName.lowercase()) { "bookname" -> if (content.isNotEmpty()) palette.name = content "colorname" -> if (content.isNotEmpty()) colorName = content "red" -> r = content.toIntOrNull()?.coerceIn(0, 255) "green" -> g = content.toIntOrNull()?.coerceIn(0, 255) "blue" -> b = content.toIntOrNull()?.coerceIn(0, 255) "colorentry", "pagecolor" -> { if (r != null && g != null && b != null) { val color = PaletteColor.rgb( r = r!! / 255.0, g = g!! / 255.0, b = b!! / 255.0, name = colorName ?: "" ) if (currentGroup == null) currentGroup = ColorGroup() currentGroup?.let { currentGroup = it.copy( colors = it.colors + color ) } } r = null; g = null; b = null; colorName = null } "colorpage" -> { currentGroup?.let { group -> if (group.colors.isNotEmpty()) { if (palette.colors.isEmpty() && palette.groups.isEmpty()) { palette.colors.addAll(group.colors) if (group.name.isNotEmpty() && palette.name.isEmpty()) palette.name = group.name } else { palette.groups.add( if (group.name.isEmpty()) { group.copy( name = "Color Page ${palette.groups.size + 1}" ) } else group ) } } } currentGroup = null } } if (xmlStack.isNotEmpty()) xmlStack.removeAt(xmlStack.size - 1) currentChars.clear() } } override fun decode(input: InputStream): Palette { return try { val handler = AutodeskXMLHandler() val factory = SAXParserFactory.newInstance() factory.isNamespaceAware = true val parser = factory.newSAXParser() parser.parse(input, handler) // Если нет главного цвета, но есть группы, возьмём первую группу if (handler.palette.colors.isEmpty() && handler.palette.groups.isNotEmpty()) { val firstGroup = handler.palette.groups[0] handler.palette.colors.addAll(firstGroup.colors) handler.palette.groups.removeAt(0) } if (handler.palette.colors.isEmpty() && handler.palette.groups.isEmpty()) { throw PaletteCoderException.InvalidFormat() } handler.palette.build() } catch (_: Throwable) { // Не удалось распарсить — не падаем, возвращаем пустой palette Palette() } } override fun encode(palette: Palette, output: OutputStream) { val sb = StringBuilder() sb.append("\n") sb.append("\n") val name = palette.name.ifEmpty { "Untitled" } sb.append(" ${name.xmlEscaped()}\n") sb.append(" 1\n") sb.append(" 0\n") val allGroups = palette.allGroups allGroups.forEach { group -> if (group.colors.isEmpty()) return@forEach sb.append(" \n") group.colors.forEach { color -> val colorName = color.name.ifEmpty { "Color" } sb.append(" \n") sb.append(" ${colorName.xmlEscaped()}\n") sb.append(encodeColor(color)) sb.append(" \n") } sb.append(" \n") } sb.append("\n") output.write(sb.toString().toByteArray(StandardCharsets.UTF_8)) } private fun encodeColor(color: PaletteColor): String { val rgb = color.toRgb() return """ ${rgb.r255} ${rgb.g255} ${rgb.b255} """ } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/BasicXMLCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.xmlDecoded import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import javax.xml.parsers.SAXParserFactory /** * Basic XML palette coder */ class BasicXMLCoder : PaletteCoder { private class BasicXMLHandler : DefaultHandler() { val palette = Palette.Builder() private var currentChars = StringBuilder() private fun elementName(localName: String, qName: String?): String = localName.ifBlank { qName ?: "" }.substringAfter(':').lowercase() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() when (elementName(localName, qName)) { "palette" -> { val name = attributes.getValue("name")?.xmlDecoded() if (name != null) { palette.name = name } } "color" -> { val name = attributes.getValue("name")?.xmlDecoded() ?: "" val hex = attributes.getValue("hex") val r = attributes.getValue("r")?.toIntOrNull() val g = attributes.getValue("g")?.toIntOrNull() val b = attributes.getValue("b")?.toIntOrNull() val a = attributes.getValue("a")?.toIntOrNull() ?: 255 try { val color = if (hex != null) { PaletteColor( rgbHexString = hex, format = ColorByteFormat.RGBA, name = name ) } else if (r != null && g != null && b != null) { PaletteColor.rgb( r = r / 255.0, g = g / 255.0, b = b / 255.0, a = a / 255.0, name = name ) } else { null } if (color != null) { palette.colors.add(color) } } catch (_: Throwable) { // Skip invalid colors } } } } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } } override fun decode(input: InputStream): Palette { val handler = BasicXMLHandler() val factory = SAXParserFactory.newInstance() val parser = factory.newSAXParser() parser.parse(input, handler) val palette = handler.palette.build() if (palette.totalColorCount == 0) { throw PaletteCoderException.InvalidFormat() } return palette } override fun encode(palette: Palette, output: OutputStream) { var xml = "\n" xml += " val rgb = color.toRgb() val hex = rgb.hexString(ColorByteFormat.RGBA, hashmark = false, uppercase = false) xml += ". */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * CLF Lab Colors coder (decode only) */ class CLFPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val text = input.readText() val result = Palette.Builder() text.lines().forEach { line -> val components = line.split("\t") if (components.size == 4) { val name = components[0] val ls = components[1].replace(",", ".") val `as` = components[2].replace(",", ".") val bs = components[3].replace(",", ".") val l = ls.trim().toDoubleOrNull() ?: return@forEach val a = `as`.trim().toDoubleOrNull() ?: return@forEach val b = bs.trim().toDoubleOrNull() ?: return@forEach try { val color = PaletteColor.lab( l = l, a = a, b = b, name = name ) result.colors.add(color) } catch (_: Throwable) { // Skip invalid colors } } } if (result.colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val allColors = palette.allColors() val lines = allColors.map { color -> // Convert to LAB if not already val lab = if (color.colorSpace == ColorSpace.LAB) { color } else { try { color.converted(ColorSpace.LAB) } catch (_: Throwable) { // If conversion fails, try to create a LAB color from RGB val rgb = color.toRgb() // Approximate conversion to LAB (simplified) PaletteColor.lab( l = (rgb.rf * 0.299 + rgb.gf * 0.587 + rgb.bf * 0.114) * 100, a = (rgb.rf - rgb.gf) * 127, b = (rgb.gf - rgb.bf) * 127, name = color.name ) } } val name = color.name.ifEmpty { "Color" } val l = lab.colorComponents[0] val a = lab.colorComponents[1] val b = lab.colorComponents[2] // Format: name[tab]L[tab]a[tab]b // Replace comma with dot for decimal separator "$name\t${l.toString().replace(',', '.')}\t${ a.toString().replace(',', '.') }\t${b.toString().replace(',', '.')}" } val text = lines.joinToString("\n") output.write(text.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/CPLPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.ColorType import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.InputStream import java.io.OutputStream import java.nio.charset.Charset /** * Corel Palette (CPL) coder */ class CPLPaletteCoder : PaletteCoder { companion object { private val spotPaletteTypes = listOf( 3u, 8u, 9u, 10u, 11u, 16u, 17u, 18u, 20u, 21u, 22u, 23u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 35u, 36u, 37u ) } override fun decode(input: InputStream): Palette { val data = input.readBytes() val parser = BytesReader(data) val result = Palette.Builder() var spot = false var paletteType: UShort = 0u val version = parser.readUInt16(ByteOrder.BIG_ENDIAN) val numberOfColors: UShort when (version.toInt()) { 0xDCDC -> { // This version has a palette name val filenamelength = parser.readUInt8().toInt() if (filenamelength > 0) { result.name = parser.readStringASCII(filenamelength) } numberOfColors = parser.readUInt16(ByteOrder.LITTLE_ENDIAN) } 0xCCBC, 0xCCDC -> { // This version doesn't have a palette name, just colors numberOfColors = parser.readUInt16(ByteOrder.LITTLE_ENDIAN) } else -> { // Read in headers val headerCount = parser.readInt32(ByteOrder.LITTLE_ENDIAN) data class Header(val hid: Int, val offset: Int) val headers = mutableListOf
() repeat(headerCount) { val hid = parser.readInt32(ByteOrder.LITTLE_ENDIAN) val offset = parser.readInt32(ByteOrder.LITTLE_ENDIAN) headers.add(Header(hid, offset)) } // Name if (headers.isNotEmpty()) { parser.seekSet(headers[0].offset) val filenamelength = parser.readUInt8().toInt() var name = "" if (filenamelength > 0) { if (version.toInt() == 0xCDDC) { val nameData = parser.readBytes(filenamelength) name = try { String(nameData, Charset.forName("ISO-8859-1")) } catch (_: Throwable) { String(nameData, java.nio.charset.StandardCharsets.US_ASCII) } } else { name = parser.readStringUTF16(ByteOrder.LITTLE_ENDIAN, filenamelength) } } result.name = name } // Palette Type if (headers.size > 1) { parser.seekSet(headers[1].offset) paletteType = parser.readUInt16(ByteOrder.LITTLE_ENDIAN) } // Number of colors if (headers.size > 2) { parser.seekSet(headers[2].offset) numberOfColors = parser.readUInt16(ByteOrder.LITTLE_ENDIAN) // Check if we are a spot palette spot = spotPaletteTypes.contains(paletteType.toUInt()) parser.seekSet(headers[2].offset + 2) } else { numberOfColors = 0u } } } val long = listOf(0xCDBC, 0xCDDC, 0xCDDD).contains(version.toInt()) && paletteType.toInt() < 38 && paletteType.toInt() != 5 && paletteType.toInt() != 16 for (i in 0 until numberOfColors.toInt()) { if (long) { parser.readUInt32(ByteOrder.LITTLE_ENDIAN) } val model = parser.readUInt16(ByteOrder.LITTLE_ENDIAN) parser.seek(2) var colorspace: ColorSpace? = null var colorComponents: List? = null var colorspace2: ColorSpace? = null var colorComponents2: List? = null when (model.toInt()) { 2 -> { // CMYK percentages parser.seek(4) val cmyk = parser.readBytes(4) colorspace = ColorSpace.CMYK colorComponents = listOf( cmyk[0].toUByte().toDouble() / 100.0, cmyk[1].toUByte().toDouble() / 100.0, cmyk[2].toUByte().toDouble() / 100.0, cmyk[3].toUByte().toDouble() / 100.0 ) } 3, 17 -> { // CMYK fractions parser.seek(4) val cmyk = parser.readBytes(4) colorspace = ColorSpace.CMYK colorComponents = listOf( cmyk[0].toUByte().toDouble() / 255.0, cmyk[1].toUByte().toDouble() / 255.0, cmyk[2].toUByte().toDouble() / 255.0, cmyk[3].toUByte().toDouble() / 255.0 ) } 4 -> { // CMY fractions parser.seek(4) val cmyk = parser.readBytes(4) colorspace = ColorSpace.CMYK colorComponents = listOf( cmyk[0].toUByte().toDouble() / 255.0, cmyk[1].toUByte().toDouble() / 255.0, cmyk[2].toUByte().toDouble() / 255.0, 0.0 ) } 5, 21 -> { // BGR fractions parser.seek(4) val bgr = parser.readBytes(3) colorspace = ColorSpace.RGB colorComponents = listOf( bgr[2].toUByte().toDouble() / 255.0, bgr[1].toUByte().toDouble() / 255.0, bgr[0].toUByte().toDouble() / 255.0 ) parser.seek(1) } 9 -> { // Grayscale parser.seek(4) val k = parser.readUInt8().toInt() colorspace = ColorSpace.Gray colorComponents = listOf((255 - k) / 255.0) parser.seek(3) } else -> { // Unknown type, try to recover parser.seek(8) } } if (long) { val model2 = parser.readUInt16(ByteOrder.LITTLE_ENDIAN) when (model2.toInt()) { 2 -> { parser.seek(4) val cmyk = parser.readBytes(4) colorspace2 = ColorSpace.CMYK colorComponents2 = listOf( cmyk[0].toUByte().toDouble() / 100.0, cmyk[1].toUByte().toDouble() / 100.0, cmyk[2].toUByte().toDouble() / 100.0, cmyk[3].toUByte().toDouble() / 100.0 ) } 3, 17 -> { parser.seek(4) val cmyk = parser.readBytes(4) colorspace2 = ColorSpace.CMYK colorComponents2 = listOf( cmyk[0].toUByte().toDouble() / 255.0, cmyk[1].toUByte().toDouble() / 255.0, cmyk[2].toUByte().toDouble() / 255.0, cmyk[3].toUByte().toDouble() / 255.0 ) } 4 -> { parser.seek(4) val cmyk = parser.readBytes(4) colorspace2 = ColorSpace.CMYK colorComponents2 = listOf( cmyk[0].toUByte().toDouble() / 255.0, cmyk[1].toUByte().toDouble() / 255.0, cmyk[2].toUByte().toDouble() / 255.0, 0.0 ) } 5, 21 -> { parser.seek(4) val bgr = parser.readBytes(3) colorspace2 = ColorSpace.RGB colorComponents2 = listOf( bgr[2].toUByte().toDouble() / 255.0, bgr[1].toUByte().toDouble() / 255.0, bgr[0].toUByte().toDouble() / 255.0 ) parser.seek(1) } 9 -> { parser.seek(4) val k = parser.readUInt8().toInt() colorspace2 = ColorSpace.Gray colorComponents2 = listOf((255 - k) / 255.0) parser.seek(3) } else -> { parser.seek(8) } } } val nameLength = parser.readUInt8().toInt() var colorName = "" if (nameLength > 0) { if (version.toInt() in listOf(0xCDDC, 0xDCDC, 0xCCDC)) { val nameData = parser.readBytes(nameLength) colorName = try { String(nameData, Charset.forName("ISO-8859-1")) } catch (_: Throwable) { String(nameData, java.nio.charset.StandardCharsets.US_ASCII) } } else { colorName = parser.readStringUTF16(ByteOrder.LITTLE_ENDIAN, nameLength) } } if (colorspace != null && colorComponents != null) { try { val readColor = PaletteColor( name = colorName, colorSpace = colorspace, colorComponents = colorComponents, colorType = if (spot) ColorType.Spot else ColorType.Normal ) result.colors.add(readColor) } catch (_: Throwable) { // Skip invalid colors } } if (colorspace2 != null && colorComponents2 != null) { try { val readColor = PaletteColor( name = colorName, colorSpace = colorspace2, colorComponents = colorComponents2, colorType = if (spot) ColorType.Spot else ColorType.Normal ) result.colors.add(readColor) } catch (_: Throwable) { // Skip invalid colors } } if (version.toInt() == 0xCDDD) { // row and column parser.readUInt32(ByteOrder.LITTLE_ENDIAN) parser.readUInt32(ByteOrder.LITTLE_ENDIAN) parser.readUInt32(ByteOrder.LITTLE_ENDIAN) } } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) val allColors = palette.allColors() val colorCount = allColors.size if (colorCount == 0) { throw PaletteCoderException.TooFewColors() } // Use version 0xDCDC (has palette name) writer.writeUInt16(0xDCDCu.toUShort(), ByteOrder.BIG_ENDIAN) // Filename length and name val filename = palette.name.ifEmpty { "Palette" } val filenameBytes = filename.toByteArray(Charset.forName("ISO-8859-1")) val filenameLength = filenameBytes.size.coerceIn(0, 255) writer.writeUInt8(filenameLength.toUByte()) if (filenameLength > 0) { writer.writeData(filenameBytes.sliceArray(0 until filenameLength)) } // Number of colors (2 bytes, little endian) writer.writeUInt16(colorCount.toUShort(), ByteOrder.LITTLE_ENDIAN) // Write colors - use model 5 (BGR fractions) for RGB colors allColors.forEach { color -> val rgb = color.toRgb() // Model (2 bytes, little endian) - 5 = BGR fractions writer.writeUInt16(5u.toUShort(), ByteOrder.LITTLE_ENDIAN) // Skip 2 bytes writer.writePattern(0x00, 0x00) // Skip 4 bytes (unknown) writer.writePattern(0x00, 0x00, 0x00, 0x00) // BGR values (3 bytes) - reversed order writer.writeByte((rgb.bf * 255).toInt().coerceIn(0, 255).toByte()) writer.writeByte((rgb.gf * 255).toInt().coerceIn(0, 255).toByte()) writer.writeByte((rgb.rf * 255).toInt().coerceIn(0, 255).toByte()) // Skip 1 byte writer.writeByte(0) // Color name length val colorName = color.name.ifEmpty { "" } val colorNameBytes = colorName.toByteArray(Charset.forName("ISO-8859-1")) val colorNameLength = colorNameBytes.size.coerceIn(0, 255) writer.writeUInt8(colorNameLength.toUByte()) // Color name if (colorNameLength > 0) { writer.writeData(colorNameBytes.sliceArray(0 until colorNameLength)) } } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/CSVPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.CSVParser import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * CSV palette coder */ class CSVPaletteCoder( private val hexFormat: ColorByteFormat = ColorByteFormat.RGB ) : PaletteCoder { override fun decode(input: InputStream): Palette { val text = input.readText() val records = CSVParser.parse(text) if (records.isEmpty()) { throw PaletteCoderException.InvalidFormat() } val colors = when (records.size) { 1 -> { // Single line of hex colors records[0].mapNotNull { field -> try { PaletteColor(field.trim(), ColorByteFormat.RGBA) } catch (_: Throwable) { null } } } else -> { records.mapNotNull { record -> when { record.isEmpty() -> null record.size == 1 -> { try { PaletteColor(record[0].trim(), ColorByteFormat.RGBA) } catch (_: Throwable) { null } } else -> { try { PaletteColor( record[0].trim(), ColorByteFormat.RGBA, record[1].trim() ) } catch (_: Throwable) { null } } } } } } if (colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return Palette(colors = colors.toMutableList()) } override fun encode(palette: Palette, output: OutputStream) { val allColors = palette.allColors() var results = "" allColors.forEach { color -> results += color.hexString(hexFormat, hashmark = true, uppercase = false) if (color.name.isNotEmpty()) { results += ", ${color.name}" } results += "\n" } output.write(results.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/CorelDraw3PaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * Corel Draw V3 Palette coder */ class CorelDraw3PaletteCoder : PaletteCoder { companion object { private val regex = Regex("\"(.*)\"[ \\t]*(\\d*\\.?\\d+)[ \\t]*(\\d*\\.?\\d+)[ \\t]*(\\d*\\.?\\d+)[ \\t]*(\\d*\\.?\\d+)[ \\t]*") } override fun decode(input: InputStream): Palette { val text = input.readText() val result = Palette.Builder() text.lines().forEach { line -> val trimmed = line.trim() regex.find(trimmed)?.let { match -> val name = match.groupValues[1] val cyan = match.groupValues[2].toDoubleOrNull() ?: return@let val magenta = match.groupValues[3].toDoubleOrNull() ?: return@let val yellow = match.groupValues[4].toDoubleOrNull() ?: return@let val black = match.groupValues[5].toDoubleOrNull() ?: return@let val color = PaletteColor.cmyk( c = (cyan / 100.0).coerceIn(0.0, 1.0), m = (magenta / 100.0).coerceIn(0.0, 1.0), y = (yellow / 100.0).coerceIn(0.0, 1.0), k = (black / 100.0).coerceIn(0.0, 1.0), name = name ) result.colors.add(color) } } val palette = result.build() if (palette.allColors().isEmpty()) { throw PaletteCoderException.InvalidFormat() } return palette } override fun encode(palette: Palette, output: OutputStream) { var result = "" val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.CMYK) color else color.converted(ColorSpace.CMYK) } colors.forEach { color -> val cmyk = color.toCmyk() val ci = ((cmyk.cf * 100).roundToInt()) val mi = ((cmyk.mf * 100).roundToInt()) val yi = ((cmyk.yf * 100).roundToInt()) val ki = ((cmyk.kf * 100).roundToInt()) result += "\"${color.name}\" $ci $mi $yi $ki\r\n" } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } private fun Double.roundToInt(): Int { return kotlin.math.round(this).toInt() } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/CorelPainterCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * Corel Painter Swatch coder */ class CorelPainterCoder : PaletteCoder { companion object { private val regex = Regex("[ \\t]*R[ \\t]*:[ \\t]*(\\d*\\.?\\d+)[ \\t,]*G[ \\t]*:[ \\t]*(\\d*\\.?\\d+)[ \\t,]*B[ \\t]*:[ \\t]*(\\d*\\.?\\d+)[ \\t,]*(?:HV[ \\t]*:[ \\t]*(\\d*\\.?\\d+)[ \\t,]*)?(?:SV[ \\t]*:[ \\t]*(\\d*\\.?\\d+)[ \\t,]*)?(?:VV[ \\t]*:[ \\t]*(\\d*\\.?\\d+)[ \\t,]*)?(.*)") } override fun decode(input: InputStream): Palette { val text = input.readText() if (!text.startsWith("ROWS ")) { throw PaletteCoderException.InvalidFormat() } val lines = text.lines() if (lines.size < 7) { throw PaletteCoderException.InvalidFormat() } if (!lines[0].startsWith("ROWS") || !lines[1].startsWith("COLS") || !lines[2].startsWith("WIDTH") || !lines[3].startsWith("HEIGHT") || !lines[4].startsWith("TEXTHEIGHT") || !lines[5].startsWith("SPACING") ) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() for (line in lines.drop(6)) { val trimmed = line.trim() regex.find(trimmed)?.let { match -> val r = match.groupValues[1].toIntOrNull() ?: return@let val g = match.groupValues[2].toIntOrNull() ?: return@let val b = match.groupValues[3].toIntOrNull() ?: return@let val name = match.groupValues[7].trim() val color = PaletteColor.rgb( r = (r / 255.0).coerceIn(0.0, 1.0), g = (g / 255.0).coerceIn(0.0, 1.0), b = (b / 255.0).coerceIn(0.0, 1.0), name = name ) result.colors.add(color) } } return result.build() } override fun encode(palette: Palette, output: OutputStream) { var result = """ROWS 12 COLS 22 WIDTH 16 HEIGHT 16 TEXTHEIGHT 0 SPACING 1 """ val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } colors.forEach { color -> val rgb = color.toRgb() result += "R: ${rgb.r255}, G:${rgb.g255}, B:${rgb.b255} HV:0.00, SV:0.00, VV:0.00" if (color.name.isNotEmpty()) { result += " ${color.name}" } result += "\n" } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/CorelXMLPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.xmlDecoded import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import java.util.Locale import javax.xml.parsers.SAXParserFactory /** * CorelDraw XML palette coder (исправлена обработка имён элементов и поиск colorspaces по регистру) */ class CorelXMLPaletteCoder : PaletteCoder { private class CorelXMLHandler : DefaultHandler() { val palette = Palette.Builder() private var currentGroup: ColorGroup? = null private var isInColorsSection = false private var isInColorspaceSection = false private val colorspaces = mutableListOf() private var currentChars = StringBuilder() private class Colorspace(val name: String) { val colors = mutableListOf() } private fun elmName(localName: String?, qName: String?) = (qName ?: localName ?: "").trim() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() when (elmName(localName, qName).lowercase()) { "palette" -> { val name = attributes.getValue("name")?.xmlDecoded() if (!name.isNullOrEmpty()) { palette.name = name } } "colorspaces" -> isInColorspaceSection = true "cs" -> { val name = attributes.getValue("name")?.xmlDecoded() ?: "" colorspaces.add(Colorspace(name.lowercase())) } "colors" -> isInColorsSection = true "page" -> { val name = attributes.getValue("name")?.xmlDecoded() ?: "" currentGroup = ColorGroup(name = name) } "color" -> { val csRaw = attributes.getValue("cs") val cs = csRaw?.lowercase() val name = attributes.getValue("name")?.xmlDecoded() ?: "" val tints = attributes.getValue("tints") ?: "" val components = tints.split(",").mapNotNull { it.trim().toDoubleOrNull() } val color: PaletteColor? = when (cs) { "cmyk" -> { if (components.size >= 4) { try { PaletteColor.cmyk( c = components[0], m = components[1], y = components[2], k = components[3], name = name ) } catch (_: Throwable) { null } } else null } "rgb" -> { if (components.size >= 3) { try { PaletteColor.rgb( r = components[0], g = components[1], b = components[2], name = name ) } catch (_: Throwable) { null } } else null } "lab" -> { if (components.size >= 3) { try { val l = components[0] * 100.0 val a = components[1] * 256.0 - 128.0 val b = components[2] * 256.0 - 128.0 PaletteColor.lab(l = l, a = a, b = b, name = name) } catch (_: Throwable) { null } } else null } "gray", "grey" -> { if (components.isNotEmpty()) { try { PaletteColor.gray(white = components[0], name = name) } catch (_: Throwable) { null } } else null } else -> { // Если указан colorspace (cs) и он описан в секции colorspaces, // берём первый цвет из описанной colorspace как шаблон. if (!cs.isNullOrEmpty()) { colorspaces.find { it.name == cs }?.colors?.firstOrNull() ?.let { existingColor -> try { PaletteColor( name = name, colorSpace = existingColor.colorSpace, colorComponents = existingColor.colorComponents.toMutableList(), alpha = existingColor.alpha, colorType = existingColor.colorType ) } catch (_: Throwable) { null } } } else null } } if (color != null) { if (isInColorspaceSection) { colorspaces.lastOrNull()?.colors?.add(color) } else { if (currentGroup == null) currentGroup = ColorGroup() currentGroup?.let { currentGroup = it.copy( colors = it.colors + color ) } } } } } } override fun endElement(uri: String?, localName: String, qName: String?) { when (elmName(localName, qName).lowercase()) { "page" -> { currentGroup?.let { group -> if (group.colors.isNotEmpty()) { if (group.name.isEmpty() && palette.colors.isEmpty() && palette.groups.isEmpty()) { palette.colors.addAll(group.colors) } else if (group.name.isNotEmpty()) { palette.groups.add(group) } else { palette.colors.addAll(group.colors) } } } currentGroup = null } "colors" -> isInColorsSection = false "colorspaces" -> isInColorspaceSection = false } currentChars.clear() } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } } override fun decode(input: InputStream): Palette { val handler = CorelXMLHandler() val factory = SAXParserFactory.newInstance() factory.isNamespaceAware = true factory.isValidating = false val parser = factory.newSAXParser() parser.parse(input, handler) if (handler.palette.colors.isEmpty() && handler.palette.groups.isNotEmpty()) { val firstGroup = handler.palette.groups[0] handler.palette.colors.addAll(firstGroup.colors) handler.palette.groups.removeAt(0) } if (handler.palette.colors.isEmpty() && handler.palette.groups.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return handler.palette.build() } override fun encode(palette: Palette, output: OutputStream) { val sb = StringBuilder() sb.append("\n") sb.append("\n") sb.append("\n") if (palette.colors.isNotEmpty()) { sb.append(pageData(name = "", colors = palette.colors)) } palette.groups.forEach { group -> sb.append(pageData(name = group.name, colors = group.colors)) } sb.append("\n") sb.append("\n") output.write(sb.toString().toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } private fun pageData(name: String, colors: List): String { val locale = Locale.US val result = StringBuilder() result.append("") colors.forEach { color -> result.append(" { "CMYK" to color.colorComponents.joinToString(",") { "%.6f".format(locale, it) } } ColorSpace.RGB -> { "RGB" to color.colorComponents.joinToString(",") { "%.6f".format(locale, it) } } ColorSpace.Gray -> { "GRAY" to color.colorComponents.joinToString(",") { "%.6f".format(locale, it) } } ColorSpace.LAB -> { if (color.colorComponents.size < 3) { "LAB" to "" } else { "LAB" to listOf( color.colorComponents[0] / 100.0, (color.colorComponents[1] + 128.0) / 256.0, (color.colorComponents[2] + 128.0) / 256.0 ).joinToString(",") { "%.6f".format(locale, it) } } } } result.append(" cs=\"$cs\"") if (tints.isNotEmpty()) result.append(" tints=\"$tints\"") result.append("/>\n") } result.append("\n") return result.toString() } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/DCPPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.ColorSpace import com.t8rin.palette.ColorType import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.ByteArrayInputStream import java.io.InputStream import java.io.OutputStream /** * ColorPaletteCodable binary format (DCP) coder */ class DCPPaletteCoder : PaletteCoder { companion object { const val BOM: UShort = 32156u const val VERSION: UShort = 1u const val GROUP_IDENTIFIER: UByte = 0xEAu const val COLOR_IDENTIFIER: UByte = 0xC0u } override fun decode(input: InputStream): Palette { // Read all data first for seek support val data = input.readBytes() val parser = BytesReader(ByteArrayInputStream(data)) val result = Palette.Builder() // Read BOM if (parser.readUInt16(ByteOrder.LITTLE_ENDIAN) != BOM) { throw PaletteCoderException.InvalidBOM() } // Read version if (parser.readUInt16(ByteOrder.LITTLE_ENDIAN) != VERSION) { throw PaletteCoderException.InvalidBOM() } // Palette name result.name = parser.readPascalStringUTF16(ByteOrder.LITTLE_ENDIAN) // Read the expected number of groups val expectedGroupCount = parser.readUInt16(ByteOrder.LITTLE_ENDIAN).toInt() // Read in the groups val groups = mutableListOf() for (i in 0 until expectedGroupCount) { // Read a group identifier tag if (parser.readByte() != GROUP_IDENTIFIER.toByte()) { throw PaletteCoderException.InvalidBOM() } // Read the group name val groupName = parser.readPascalStringUTF16(ByteOrder.LITTLE_ENDIAN) // Read the expected number of colors val expectedColorCount = parser.readUInt16(ByteOrder.LITTLE_ENDIAN).toInt() // The groups colors val colors = mutableListOf() for (j in 0 until expectedColorCount) { colors.add(parser.readColor()) } groups.add(ColorGroup(colors = colors, name = groupName)) } // First group is always the 'global' colors if (groups.isEmpty()) { throw PaletteCoderException.InvalidFormat() } result.colors = groups[0].colors.toMutableList() result.groups = groups.drop(1).toMutableList() return result.build() } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) // Expected BOM writer.writeUInt16(BOM, ByteOrder.LITTLE_ENDIAN) // Version writer.writeUInt16(VERSION, ByteOrder.LITTLE_ENDIAN) // Write the palette name writer.writePascalStringUTF16(palette.name, ByteOrder.LITTLE_ENDIAN) // Write the number of groups (global colors + groups) val allGroups = palette.allGroups writer.writeUInt16(allGroups.size.toUShort(), ByteOrder.LITTLE_ENDIAN) allGroups.forEach { group -> // Write a group identifier tag writer.writeByte(GROUP_IDENTIFIER.toByte()) // Write the group name writer.writePascalStringUTF16(group.name, ByteOrder.LITTLE_ENDIAN) // Write the number of colors in the group writer.writeUInt16(group.colors.size.toUShort(), ByteOrder.LITTLE_ENDIAN) group.colors.forEach { color -> writer.writeColor(color) } } } } private fun BytesReader.readColor(): PaletteColor { // Read a color identifier tag if (readByte() != DCPPaletteCoder.COLOR_IDENTIFIER.toByte()) { throw PaletteCoderException.InvalidBOM() } // Read the color name val colorName = readPascalStringUTF16(ByteOrder.LITTLE_ENDIAN) val colorspaceID = readUInt8() val colorSpace: ColorSpace val components: List when (colorspaceID.toInt()) { 1 -> { // CMYK colorSpace = ColorSpace.CMYK components = listOf( readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble(), readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble(), readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble(), readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() ) } 2 -> { // RGB colorSpace = ColorSpace.RGB components = listOf( readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble(), readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble(), readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() ) } 3 -> { // LAB colorSpace = ColorSpace.LAB components = listOf( readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble(), readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble(), readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() ) } 4 -> { // Gray colorSpace = ColorSpace.Gray components = listOf(readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble()) } else -> throw PaletteCoderException.InvalidFormat() } // Alpha component val alpha = readFloat32(ByteOrder.LITTLE_ENDIAN).toDouble() // Color type val type = readUInt8() val colorType: ColorType = when (type.toInt()) { 1 -> ColorType.Global 2 -> ColorType.Spot 3 -> ColorType.Normal else -> throw PaletteCoderException.InvalidFormat() } return PaletteColor( name = colorName, colorType = colorType, colorSpace = colorSpace, colorComponents = components, alpha = alpha ) } private fun BytesWriter.writeColor(color: PaletteColor) { // Write a color identifier tag writeByte(DCPPaletteCoder.COLOR_IDENTIFIER.toByte()) // Color name writePascalStringUTF16(color.name, ByteOrder.LITTLE_ENDIAN) // Write the colorspace identifier val colorspaceID: UByte = when (color.colorSpace) { ColorSpace.CMYK -> 1u ColorSpace.RGB -> 2u ColorSpace.LAB -> 3u ColorSpace.Gray -> 4u } writeUInt8(colorspaceID) // Write the color components color.colorComponents.forEach { comp -> writeFloat32(comp.toFloat(), ByteOrder.LITTLE_ENDIAN) } // Color alpha writeFloat32(color.alpha.toFloat(), ByteOrder.LITTLE_ENDIAN) // Color type val type: UByte = when (color.colorType) { ColorType.Global -> 1u ColorType.Spot -> 2u ColorType.Normal -> 3u } writeUInt8(type) } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/GIMPPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream import kotlin.math.roundToInt /** * GIMP Palette (GPL) coder */ class GIMPPaletteCoder : PaletteCoder { companion object { private val nameRegex = Regex("^Name:\\s*(.*)$", RegexOption.IGNORE_CASE) private val colorRegex = Regex("^\\s*(\\d{1,3})\\s+(\\d{1,3})\\s+(\\d{1,3})(?:\\s+(.*))?$") } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() val header = lines.firstOrNull()?.removePrefix("\uFEFF")?.trim() ?: "" if (!header.equals("GIMP Palette", ignoreCase = true)) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() for (line in lines.drop(1)) { val trimmed = line.trim() if (trimmed.isEmpty()) continue if (trimmed.startsWith("#")) continue if (trimmed.startsWith("Columns:", ignoreCase = true)) continue // Check for name nameRegex.find(trimmed)?.let { match -> result.name = match.groupValues[1].trim() continue } // Check for color colorRegex.find(trimmed)?.let { match -> val r = match.groupValues[1].toIntOrNull() ?: continue val g = match.groupValues[2].toIntOrNull() ?: continue val b = match.groupValues[3].toIntOrNull() ?: continue if (r !in 0..255 || g !in 0..255 || b !in 0..255) continue val name = match.groupValues.getOrElse(4) { "" }.trim() val color = PaletteColor.rgb( r = (r / 255.0).coerceIn(0.0, 1.0), g = (g / 255.0).coerceIn(0.0, 1.0), b = (b / 255.0).coerceIn(0.0, 1.0), name = name ) result.colors.add(color) } } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val originalColors = palette.allColors() val result = buildString { appendLine("GIMP Palette") appendLine("Name: ${sanitizeGimpText(palette.name)}") appendLine("Columns: 0") appendLine("#Colors: ${originalColors.size}") originalColors.forEach { color -> val rgb = if (color.colorSpace == ColorSpace.RGB) color.toRgb() else color.converted(ColorSpace.RGB).toRgb() val r = (rgb.rf * 255.0).roundToInt().coerceIn(0, 255) val g = (rgb.gf * 255.0).roundToInt().coerceIn(0, 255) val b = (rgb.bf * 255.0).roundToInt().coerceIn(0, 255) append("$r\t$g\t$b") val colorName = sanitizeGimpText(color.name) if (colorName.isNotEmpty()) { append('\t') append(colorName) } append('\n') } } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } private fun sanitizeGimpText(value: String): String = value.replace(Regex("[\\r\\n\\t]+"), " ").trim() } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/HEXPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * Hex RGBA palette coder */ class HEXPaletteCoder : PaletteCoder { companion object { private val validHexChars = "#0123456789abcdefABCDEF" } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() val result = Palette.Builder() var currentName = "" for (line in lines) { val trimmed = line.trim() if (trimmed.isEmpty()) continue if (trimmed.firstOrNull() == ';') { // Comment - might be a color name val commentText = trimmed.substring(1).trim() if (commentText.isNotEmpty()) { currentName = commentText } continue } var current = "" for (char in line) { if (validHexChars.contains(char)) { current += char } else { if (current.isNotEmpty()) { try { val color = PaletteColor(current, ColorByteFormat.RGBA, name = currentName) result.colors.add(color) currentName = "" } catch (_: Throwable) { // Skip invalid hex } current = "" } } } if (current.isNotEmpty()) { try { val color = PaletteColor(current, ColorByteFormat.RGBA, name = currentName) result.colors.add(color) currentName = "" } catch (_: Throwable) { // Skip invalid hex } } } if (result.colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val rgbColors = palette.allColors().map { if (it.colorSpace == ColorSpace.RGB) it else it.converted(ColorSpace.RGB) } var content = "" rgbColors.forEach { color -> val rgb = color.toRgb() val format = if (rgb.af < 1.0) ColorByteFormat.RGBA else ColorByteFormat.RGB val hex = color.hexString(format, hashmark = true, uppercase = false) if (color.name.isNotEmpty()) { content += "; ${color.name}\n" } content += "$hex\n" } output.write(content.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/HPLPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * Homesite Palette coder */ class HPLPaletteCoder : PaletteCoder { companion object { private val colorRegex = Regex("^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+).*$") } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() if (lines.isEmpty() || !lines[0].contains("Palette") || lines.size < 2 || !lines[1].contains( "Version 4.0" ) ) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() var currentName = "" for (line in lines.drop(3)) { val trimmed = line.trim() if (trimmed.isEmpty()) continue if (trimmed.startsWith(";")) { // Comment - might be a color name val commentText = trimmed.substring(1).trim() if (commentText.isNotEmpty()) { currentName = commentText } continue } colorRegex.find(line)?.let { match -> val r = match.groupValues[1].toIntOrNull() ?: return@let val g = match.groupValues[2].toIntOrNull() ?: return@let val b = match.groupValues[3].toIntOrNull() ?: return@let val color = PaletteColor.rgb( r = (r / 255.0).coerceIn(0.0, 1.0), g = (g / 255.0).coerceIn(0.0, 1.0), b = (b / 255.0).coerceIn(0.0, 1.0), name = currentName ) result.colors.add(color) currentName = "" } } return result.build() } override fun encode(palette: Palette, output: OutputStream) { var result = "Palette\nVersion 4.0\n-----------\n" val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } colors.forEach { color -> if (color.name.isNotEmpty()) { result += "; ${color.name}\n" } val rgb = color.toRgb() val r = ((rgb.rf * 255).coerceIn(0.0, 255.0).toInt()) val g = ((rgb.gf * 255).coerceIn(0.0, 255.0).toInt()) val b = ((rgb.bf * 255).coerceIn(0.0, 255.0).toInt()) result += "$r $g $b\n" } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/ImagePaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import androidx.core.graphics.createBitmap import androidx.core.graphics.get import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import java.io.InputStream import java.io.OutputStream import android.graphics.Color as AndroidColor /** * Image-based palette coder * Note: This requires Android Bitmap API */ class ImagePaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val data = input.readBytes() val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) ?: throw PaletteCoderException.InvalidFormat() val result = Palette.Builder() val colorOrder = mutableListOf() // Read first row of pixels - one pixel per color swatch val width = bitmap.width val swatchWidth = 32 // Each color is 32 pixels wide val numColors = width / swatchWidth // Read one pixel from the center of each swatch for (i in 0 until numColors) { val x = i * swatchWidth + swatchWidth / 2 if (x < width) { val pixel = bitmap[x, 0] val a = AndroidColor.alpha(pixel) / 255.0 val r = AndroidColor.red(pixel) / 255.0 val g = AndroidColor.green(pixel) / 255.0 val b = AndroidColor.blue(pixel) / 255.0 val colorPixel = ColorPixel(r, g, b, a) colorOrder.add(colorPixel) } } // Try to read color names from PNG text chunk or extension val colorNames = mutableListOf() try { // Check if there's a JSON extension after PNG data val pngEndMarker = byteArrayOf( 0x49, 0x45, 0x4E, 0x44, 0xAE.toByte(), 0x42, 0x60, 0x82.toByte() ) // IEND chunk val pngEndIndex = data.indexOfSlice(pngEndMarker) if (pngEndIndex >= 0 && pngEndIndex + 8 < data.size) { val extensionData = data.sliceArray(pngEndIndex + 8 until data.size) val extensionText = String(extensionData, java.nio.charset.StandardCharsets.UTF_8) if (extensionText.startsWith("\n; IMAGE_NAMES: ")) { val namesLine = extensionText.lines().find { it.startsWith("; IMAGE_NAMES:") } if (namesLine != null) { val namesStr = namesLine.substring("; IMAGE_NAMES: ".length).trim() colorNames.addAll(namesStr.split("|")) } } } } catch (_: Throwable) { // No names extension } // Convert to PaletteColor, preserving order and names // Don't filter duplicates - preserve all colors in order colorOrder.forEachIndexed { index, pixel -> val colorName = if (index < colorNames.size) colorNames[index] else "" val color = PaletteColor.rgb( r = pixel.r, g = pixel.g, b = pixel.b, a = pixel.a, name = colorName ) result.colors.add(color) } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val colors = palette.allColors() if (colors.isEmpty()) { throw PaletteCoderException.TooFewColors() } val swatchWidth = 32 val swatchHeight = 32 val bitmapWidth = colors.size * swatchWidth val bitmap = createBitmap(bitmapWidth, swatchHeight) val canvas = Canvas(bitmap) colors.forEachIndexed { index, color -> val rgb = color.toRgb() val androidColor = AndroidColor.argb( (rgb.af * 255).toInt().coerceIn(0, 255), (rgb.rf * 255).toInt().coerceIn(0, 255), (rgb.gf * 255).toInt().coerceIn(0, 255), (rgb.bf * 255).toInt().coerceIn(0, 255) ) val x = index * swatchWidth canvas.drawRect( x.toFloat(), 0f, (x + swatchWidth).toFloat(), swatchHeight.toFloat(), android.graphics.Paint().apply { this.color = androidColor style = android.graphics.Paint.Style.FILL } ) } val pngOutputStream = java.io.ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.PNG, 100, pngOutputStream) val pngData = pngOutputStream.toByteArray() output.write(pngData) // Append color names as extension (non-standard but preserves names) // Save all names, including empty ones, to preserve order val names = colors.map { it.name } val nameText = "\n; IMAGE_NAMES: ${names.joinToString("|")}\n" output.write(nameText.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } private data class ColorPixel( val r: Double, val g: Double, val b: Double, val a: Double ) } private fun ByteArray.indexOfSlice(slice: ByteArray, startIndex: Int = 0): Int { if (slice.isEmpty() || this.isEmpty() || slice.size > this.size) return -1 outer@ for (i in startIndex..this.size - slice.size) { for (j in slice.indices) { if (this[i + j] != slice[j]) continue@outer } return i } return -1 } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/JCWPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.ColorType import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.InputStream import java.io.OutputStream import java.nio.charset.Charset /** * Xara Palette (JCW) coder */ class JCWPaletteCoder : PaletteCoder { enum class SupportedColorSpace { CMYK, RGB, HSB } override fun decode(input: InputStream): Palette { val parser = BytesReader(input) val result = Palette.Builder() // Check BOM "JCW" val bom = parser.readStringASCII(3) if (bom != "JCW") { throw PaletteCoderException.InvalidBOM() } // Version val version = parser.readUInt8() if (version.toInt() != 1) { // Log warning but continue } // Number of colors val numColors = parser.readUInt16(ByteOrder.LITTLE_ENDIAN).toInt() // Expected color space for ALL colors val colorSpace = parser.readUInt8().toInt() // Expected length of color names val nameLength = parser.readUInt8().toInt() data class SupportedCS(val space: SupportedColorSpace, val type: ColorType) val ct: SupportedCS = when (colorSpace) { 1, 8 -> SupportedCS(SupportedColorSpace.CMYK, ColorType.Normal) 9 -> SupportedCS(SupportedColorSpace.CMYK, ColorType.Spot) 2, 10 -> SupportedCS(SupportedColorSpace.RGB, ColorType.Normal) 11 -> SupportedCS(SupportedColorSpace.RGB, ColorType.Spot) 3, 12 -> SupportedCS(SupportedColorSpace.HSB, ColorType.Normal) 13 -> SupportedCS(SupportedColorSpace.HSB, ColorType.Spot) else -> throw PaletteCoderException.InvalidFormat() } for (index in 0 until numColors) { val c0 = parser.readUInt16(ByteOrder.LITTLE_ENDIAN).toInt().coerceIn(0, 10000) val c1 = parser.readUInt16(ByteOrder.LITTLE_ENDIAN).toInt().coerceIn(0, 10000) val c2 = parser.readUInt16(ByteOrder.LITTLE_ENDIAN).toInt().coerceIn(0, 10000) val c3 = parser.readUInt16(ByteOrder.LITTLE_ENDIAN).toInt().coerceIn(0, 10000) // Read the bytes for the name val nameData = parser.readBytes(nameLength) // Try to convert to string (ISO Latin1 or ASCII) val name = try { String(nameData, Charset.forName("ISO-8859-1")) } catch (_: Throwable) { String(nameData, java.nio.charset.StandardCharsets.US_ASCII) }.trimEnd { it.code == 0 }.ifEmpty { "c$index" } val color = when (ct.space) { SupportedColorSpace.RGB -> { PaletteColor.rgb( r = c0 / 10000.0, g = c1 / 10000.0, b = c2 / 10000.0, name = name, colorType = ct.type ) } SupportedColorSpace.CMYK -> { PaletteColor.cmyk( c = c0 / 10000.0, m = c1 / 10000.0, y = c2 / 10000.0, k = c3 / 10000.0, name = name, colorType = ct.type ) } SupportedColorSpace.HSB -> { PaletteColor.hsb( hf = c0 / 10000.0, sf = c1 / 10000.0, bf = c2 / 10000.0, name = name, colorType = ct.type ) } } result.colors.add(color) } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) // Palette colors (all RGB for the first attempt) val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } // BOM writer.writeStringASCII("JCW") // version writer.writeUInt8(1u) // The number of colors (all RGB for the moment) writer.writeUInt16(colors.size.toUShort(), ByteOrder.LITTLE_ENDIAN) // Colorspace (basic RGB) writer.writeUInt8(10u) // Name length (14 bytes) writer.writeUInt8(14u) colors.forEach { color -> // Color components val rgb = color.toRgb() writer.writeUInt16((rgb.rf * 10000).toInt().toUShort(), ByteOrder.LITTLE_ENDIAN) writer.writeUInt16((rgb.gf * 10000).toInt().toUShort(), ByteOrder.LITTLE_ENDIAN) writer.writeUInt16((rgb.bf * 10000).toInt().toUShort(), ByteOrder.LITTLE_ENDIAN) writer.writeUInt16(0u, ByteOrder.LITTLE_ENDIAN) // Write the name (ISO-Latin1, trimmed to 14 bytes, padded with zeros) val nameBytes = try { color.name.toByteArray(Charset.forName("ISO-8859-1")) } catch (_: Throwable) { color.name.toByteArray(java.nio.charset.StandardCharsets.US_ASCII) } val paddedName = ByteArray(14) { index -> if (index < nameBytes.size) nameBytes[index] else 0 } writer.writeData(paddedName) } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/JSONPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import kotlinx.serialization.json.Json import java.io.InputStream import java.io.OutputStream /** * JSON palette coder */ class JSONPaletteCoder : PaletteCoder { private val json = Json { ignoreUnknownKeys = true encodeDefaults = false } override fun decode(input: InputStream): Palette { val text = input.bufferedReader().use { it.readText() } return json.decodeFromString(Palette.serializer(), text) } override fun encode(palette: Palette, output: OutputStream) { val text = json.encodeToString(Palette.serializer(), palette) output.bufferedWriter().use { it.write(text) } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/KOfficePaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * KOffice Palette coder */ class KOfficePaletteCoder : PaletteCoder { companion object { private val colorRegex = Regex("^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)(.*)$") } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() if (lines.isEmpty() || !lines[0].contains("KDE RGB Palette")) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() for (line in lines.drop(1)) { colorRegex.find(line)?.let { match -> val r = match.groupValues[1].toIntOrNull() ?: return@let val g = match.groupValues[2].toIntOrNull() ?: return@let val b = match.groupValues[3].toIntOrNull() ?: return@let val name = match.groupValues[4].trim() val color = PaletteColor.rgb( r = (r / 255.0).coerceIn(0.0, 1.0), g = (g / 255.0).coerceIn(0.0, 1.0), b = (b / 255.0).coerceIn(0.0, 1.0), name = name ) result.colors.add(color) } } return result.build() } override fun encode(palette: Palette, output: OutputStream) { var result = "KDE RGB Palette\n" val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } colors.forEach { color -> val rgb = color.toRgb() val r = ((rgb.rf * 255).coerceIn(0.0, 255.0).toInt()) val g = ((rgb.gf * 255).coerceIn(0.0, 255.0).toInt()) val b = ((rgb.bf * 255).coerceIn(0.0, 255.0).toInt()) result += "$r\t$g\t$b" if (color.name.isNotEmpty()) { result += "\t${color.name}" } result += "\n" } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/KRITAPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.ColorType import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.xmlDecoded import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale import java.util.zip.CRC32 import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream import javax.xml.parsers.SAXParserFactory import kotlin.math.ceil import kotlin.math.sqrt /** * KRITA Palette (KPL) coder * KPL is a ZIP archive containing a mimetype entry, colorset.xml and profiles.xml. */ class KRITAPaletteCoder : PaletteCoder { private data class PositionedColor( val row: Int, val column: Int, val color: PaletteColor ) private class ColorsetHandler : DefaultHandler() { val palette = Palette.Builder() private data class GroupState( val name: String, val colors: MutableList = mutableListOf() ) private val globalColors = mutableListOf() private var currentGroup: GroupState? = null private var currentEntryName = "" private var currentEntrySpot = false private var currentEntryColor: PaletteColor? = null private var currentEntryRow = Int.MAX_VALUE private var currentEntryColumn = Int.MAX_VALUE private fun elementName(localName: String, qName: String?): String = localName.ifBlank { qName ?: "" }.substringAfter(':').trim().lowercase() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { when (elementName(localName, qName)) { "colorset" -> { val name = attributes.getValue("name")?.xmlDecoded() if (!name.isNullOrEmpty()) { palette.name = name } } "group" -> { currentGroup = GroupState( name = attributes.getValue("name")?.xmlDecoded().orEmpty() ) } "colorsetentry" -> { currentEntryName = attributes.getValue("name")?.xmlDecoded().orEmpty() currentEntrySpot = attributes.getValue("spot") ?.equals("true", ignoreCase = true) == true currentEntryColor = null currentEntryRow = Int.MAX_VALUE currentEntryColumn = Int.MAX_VALUE } "position" -> { currentEntryRow = attributes.getValue("row")?.toIntOrNull() ?: Int.MAX_VALUE currentEntryColumn = attributes.getValue("column")?.toIntOrNull() ?: Int.MAX_VALUE } "srgb", "rgb" -> { currentEntryColor = createRgbColor(attributes) } "cmyk" -> { currentEntryColor = createCmykColor(attributes) } "lab" -> { currentEntryColor = createLabColor(attributes) } "gray" -> { currentEntryColor = createGrayColor(attributes) } } } override fun endElement(uri: String?, localName: String, qName: String?) { when (elementName(localName, qName)) { "colorsetentry" -> { currentEntryColor?.let { color -> val positionedColor = PositionedColor( row = currentEntryRow, column = currentEntryColumn, color = color ) currentGroup?.colors?.add(positionedColor) ?: globalColors.add( positionedColor ) } currentEntryColor = null currentEntryName = "" currentEntrySpot = false currentEntryRow = Int.MAX_VALUE currentEntryColumn = Int.MAX_VALUE } "group" -> { currentGroup?.let { group -> if (group.colors.isNotEmpty()) { palette.groups.add( ColorGroup( name = group.name, colors = group.colors.sorted().map { it.color } ) ) } } currentGroup = null } } } fun buildPalette(): Palette { palette.colors = globalColors.sorted().map { it.color }.toMutableList() if (palette.colors.isEmpty() && palette.groups.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return palette.build() } private fun createRgbColor(attributes: Attributes): PaletteColor? { val r = attributes.getValue("r")?.toDoubleOrNull() ?: return null val g = attributes.getValue("g")?.toDoubleOrNull() ?: return null val b = attributes.getValue("b")?.toDoubleOrNull() ?: return null return PaletteColor.rgb( r = r.coerceIn(0.0, 1.0), g = g.coerceIn(0.0, 1.0), b = b.coerceIn(0.0, 1.0), name = currentEntryName, colorType = currentEntryColorType() ) } private fun createCmykColor(attributes: Attributes): PaletteColor? { val c = attributes.getValue("c")?.toDoubleOrNull() ?: return null val m = attributes.getValue("m")?.toDoubleOrNull() ?: return null val y = attributes.getValue("y")?.toDoubleOrNull() ?: return null val k = attributes.getValue("k")?.toDoubleOrNull() ?: return null return PaletteColor.cmyk( c = c.coerceIn(0.0, 1.0), m = m.coerceIn(0.0, 1.0), y = y.coerceIn(0.0, 1.0), k = k.coerceIn(0.0, 1.0), name = currentEntryName, colorType = currentEntryColorType() ) } private fun createLabColor(attributes: Attributes): PaletteColor? { val l = (attributes.getValue("L") ?: attributes.getValue("l")) ?.toDoubleOrNull() ?: return null val a = attributes.getValue("a")?.toDoubleOrNull() ?: return null val b = attributes.getValue("b")?.toDoubleOrNull() ?: return null return PaletteColor.lab( l = l, a = a, b = b, name = currentEntryName, colorType = currentEntryColorType() ) } private fun createGrayColor(attributes: Attributes): PaletteColor? { val gray = (attributes.getValue("g") ?: attributes.getValue("gray")) ?.toDoubleOrNull() ?: return null return PaletteColor.gray( white = gray.coerceIn(0.0, 1.0), name = currentEntryName, colorType = currentEntryColorType() ) } private fun currentEntryColorType(): ColorType = if (currentEntrySpot) ColorType.Spot else ColorType.Normal private fun List.sorted(): List = sortedWith(compareBy({ it.row }, { it.column })) } private val numberFormatter = DecimalFormat("0.######", DecimalFormatSymbols(Locale.US)) override fun decode(input: InputStream): Palette { var colorsetXmlData: ByteArray? = null ZipInputStream(input.buffered()).use { zipInputStream -> var entry = zipInputStream.nextEntry while (entry != null) { if (entry.name.equals("colorset.xml", ignoreCase = true) || entry.name.lowercase().endsWith("/colorset.xml") ) { colorsetXmlData = zipInputStream.readBytes() break } zipInputStream.closeEntry() entry = zipInputStream.nextEntry } } val xmlData = colorsetXmlData ?: throw PaletteCoderException.InvalidFormat() val handler = ColorsetHandler() val factory = SAXParserFactory.newInstance() factory.isNamespaceAware = true factory.isValidating = false val parser = factory.newSAXParser() parser.parse(xmlData.inputStream(), handler) return handler.buildPalette() } override fun encode(palette: Palette, output: OutputStream) { if (palette.totalColorCount == 0) { throw PaletteCoderException.TooFewColors() } val colorsetXml = buildColorsetXml(palette) val profilesXml = """ """ ZipOutputStream(output).use { zipOutputStream -> writeStoredEntry( zipOutputStream = zipOutputStream, name = "mimetype", data = KPL_MIME_TYPE.toByteArray(Charsets.US_ASCII) ) writeTextEntry(zipOutputStream, "colorset.xml", colorsetXml) writeTextEntry(zipOutputStream, "profiles.xml", profilesXml) } } private fun buildColorsetXml(palette: Palette): String { val maxColorCount = maxOf( palette.colors.size, palette.groups.maxOfOrNull { it.colors.size } ?: 0, 1 ) val columns = ceil(sqrt(maxColorCount.toDouble())).toInt().coerceAtLeast(1) return buildString { appendLine("""""") appendLine( """""" ) appendColorEntries( colors = palette.colors, columns = columns, indent = " ", idPrefix = "global" ) palette.groups.forEachIndexed { index, group -> appendLine( """ """ ) appendColorEntries( colors = group.colors, columns = columns, indent = " ", idPrefix = "group_${index + 1}" ) appendLine(" ") } appendLine("") } } private fun StringBuilder.appendColorEntries( colors: List, columns: Int, indent: String, idPrefix: String ) { colors.forEachIndexed { index, color -> val row = index / columns val column = index % columns val colorName = sanitizeXmlText(color.name) val colorId = sanitizeId("${idPrefix}_${index + 1}") val rgb = color.toRgb() appendLine( """${indent}""" ) appendLine( """${indent} """ ) appendLine("""${indent} """) appendLine("${indent}") } } private fun writeStoredEntry( zipOutputStream: ZipOutputStream, name: String, data: ByteArray ) { val crc32 = CRC32().apply { update(data) } val entry = ZipEntry(name).apply { method = ZipEntry.STORED size = data.size.toLong() compressedSize = data.size.toLong() crc = crc32.value } zipOutputStream.putNextEntry(entry) zipOutputStream.write(data) zipOutputStream.closeEntry() } private fun writeTextEntry( zipOutputStream: ZipOutputStream, name: String, content: String ) { zipOutputStream.putNextEntry(ZipEntry(name)) zipOutputStream.write(content.toByteArray(Charsets.UTF_8)) zipOutputStream.closeEntry() } private fun formatUnit(value: Double): String = numberFormatter.format(value.coerceIn(0.0, 1.0)) private fun rowCount(colorCount: Int, columns: Int): Int = if (colorCount <= 0) 0 else ceil(colorCount / columns.toDouble()).toInt() private fun sanitizeXmlText(value: String): String = value.replace(Regex("[\\r\\n\\t]+"), " ").trim() private fun sanitizeId(value: String): String = value.replace(Regex("[^A-Za-z0-9_.-]+"), "_").trim('_').ifEmpty { "color" } companion object { private const val KPL_MIME_TYPE = "application/x-krita-palette" } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/KotlinPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * Kotlin/Jetpack Compose code generator */ class KotlinPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val text = input.readText() val result = Palette.Builder() // Parse Color(0xAARRGGBB) or Color(0xRRGGBB) // Also try to capture variable names: val name: Color = Color(0x...) val colorRegex = Regex( """(?:val\s+(\w+)\s*:\s*Color\s*=\s*)?Color\s*\(\s*0x([0-9A-Fa-f]{6,8})\s*\)""", RegexOption.IGNORE_CASE ) // Use Set to track unique colors by RGB values to avoid duplicates val seenColors = mutableSetOf>() var colorIndex = 0 colorRegex.findAll(text).forEach { match -> try { val colorName = match.groupValues[1].takeIf { it.isNotEmpty() } ?: "" val hexValue = match.groupValues[2] val value = hexValue.toLongOrNull(16) ?: return@forEach val (r, g, b, a) = if (hexValue.length == 8) { // AARRGGBB val aVal = ((value shr 24) and 0xFF) / 255.0 val rVal = ((value shr 16) and 0xFF) / 255.0 val gVal = ((value shr 8) and 0xFF) / 255.0 val bVal = (value and 0xFF) / 255.0 Quad(rVal, gVal, bVal, aVal) } else { // RRGGBB val rVal = ((value shr 16) and 0xFF) / 255.0 val gVal = ((value shr 8) and 0xFF) / 255.0 val bVal = (value and 0xFF) / 255.0 Quad(rVal, gVal, bVal, 1.0) } // Check for duplicates by RGB values (rounded to avoid floating point issues) val rInt = (r * 255).toInt() val gInt = (g * 255).toInt() val bInt = (b * 255).toInt() val colorKey = Triple(rInt, gInt, bInt) if (colorKey !in seenColors) { seenColors.add(colorKey) val finalName = if (colorName.isNotEmpty()) { colorName } else { "Color_$colorIndex" } val color = PaletteColor.rgb( r = r.coerceIn(0.0, 1.0), g = g.coerceIn(0.0, 1.0), b = b.coerceIn(0.0, 1.0), a = a.coerceIn(0.0, 1.0), name = finalName ) result.colors.add(color) colorIndex++ } } catch (_: Throwable) { // Skip invalid colors } } // Try to extract palette name from comments or object name val objectNameMatch = Regex("""object\s+(\w+)""").find(text) if (objectNameMatch != null) { result.name = objectNameMatch.groupValues[1] } else { val commentMatch = Regex("""Exported palette:\s*(.+)""").find(text) if (commentMatch != null) { result.name = commentMatch.groupValues[1].trim() } } if (result.colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return result.build() } private data class Quad(val r: Double, val g: Double, val b: Double, val a: Double) private fun sanitizeName(name: String): String { // Remove invalid characters and make it a valid Kotlin identifier return name .replace(Regex("[^a-zA-Z0-9_]"), "_") .replace(Regex("^[0-9]"), "_$0") // Can't start with digit .takeIf { it.isNotEmpty() } ?: "color" } private fun formatColor(rgb: PaletteColor.RGB): String { val r = (rgb.rf * 255).toInt().coerceIn(0, 255) val g = (rgb.gf * 255).toInt().coerceIn(0, 255) val b = (rgb.bf * 255).toInt().coerceIn(0, 255) val a = (rgb.af * 255).toInt().coerceIn(0, 255) // Format: Color(0xAARRGGBB) val argb = (a shl 24) or (r shl 16) or (g shl 8) or b return "Color(0x${argb.toUInt().toString(16).uppercase().padStart(8, '0')})" } override fun encode(palette: Palette, output: OutputStream) { val packageName = if (palette.name.isNotEmpty()) { palette.name.lowercase() .replace(Regex("[^a-z0-9]"), "") .takeIf { it.isNotEmpty() && it[0].isLetter() } ?: "palette" } else { "palette" } var result = "package $packageName\n\n" result += "import androidx.compose.ui.graphics.Color\n\n" result += "/**\n" result += " * Exported palette: ${palette.name.ifEmpty { "Untitled" }}\n" result += " * Total colors: ${palette.totalColorCount}\n" result += " */\n" result += "object ExportedPalette {\n\n" // Generate individual color constants with names val allColorsList = palette.allColors() if (allColorsList.isNotEmpty()) { result += " // Individual color constants\n" allColorsList.forEachIndexed { index, color -> try { val converted = if (color.colorSpace == ColorSpace.RGB) color else color.converted( ColorSpace.RGB ) val rgb = converted.toRgb() val colorName = if (color.name.isNotEmpty()) { sanitizeName(color.name) } else { "color$index" } result += " val $colorName: Color = ${formatColor(rgb)}\n" } catch (_: Throwable) { // Skip invalid colors } } result += "\n" } // Generate groups palette.allGroups.forEachIndexed { groupIndex, group -> if (group.colors.isEmpty()) return@forEachIndexed val groupName = if (group.name.isNotEmpty() && group.name != "global") { sanitizeName(group.name) } else { "group$groupIndex" } result += " // Group: ${group.name}\n" result += " val $groupName: List = listOf(\n" group.colors.forEachIndexed { index, color -> try { val converted = if (color.colorSpace == ColorSpace.RGB) color else color.converted( ColorSpace.RGB ) val rgb = converted.toRgb() val indent = " " result += "$indent${formatColor(rgb)}" if (index < group.colors.size - 1) { result += "," } result += "\n" } catch (_: Throwable) { // Skip invalid colors } } result += " )\n\n" } // Generate allColors list val allColors = allColorsList.mapNotNull { color -> try { val converted = if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) converted.toRgb() } catch (_: Throwable) { null } } if (allColors.isNotEmpty()) { result += " /**\n" result += " * All colors from all groups\n" result += " */\n" result += " val allColors: List = listOf(\n" allColors.forEachIndexed { index, rgb -> val indent = " " result += "$indent${formatColor(rgb)}" if (index < allColors.size - 1) { result += "," } result += "\n" } result += " )\n" } result += "}\n" output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/OpenOfficePaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.xmlDecoded import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets import javax.xml.parsers.SAXParserFactory /** * OpenOffice palette coder */ class OpenOfficePaletteCoder : PaletteCoder { private class OpenOfficeXMLHandler : DefaultHandler() { val palette = Palette.Builder() private var currentChars = StringBuilder() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() if (localName == "draw:color" || qName == "draw:color") { val name = attributes.getValue("draw:name")?.xmlDecoded() ?: "" val colorString = attributes.getValue("draw:color") ?: "" try { val color = PaletteColor( rgbHexString = colorString, format = ColorByteFormat.RGB, name = name ) palette.colors.add(color) } catch (_: Throwable) { // Skip invalid colors } } } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } } override fun decode(input: InputStream): Palette { val handler = OpenOfficeXMLHandler() val factory = SAXParserFactory.newInstance() factory.isNamespaceAware = true val parser = factory.newSAXParser() parser.parse(input, handler) val palette = handler.palette.build() if (palette.totalColorCount == 0) { throw PaletteCoderException.InvalidFormat() } return palette } override fun encode(palette: Palette, output: OutputStream) { var xml = """ """ palette.allColors().forEach { color -> try { val hex = color.hexString(ColorByteFormat.RGB, hashmark = true, uppercase = true) xml += "\n" } catch (_: Throwable) { // Skip colors that can't be converted } } xml += "\n" output.write(xml.toByteArray(StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/PaintNETPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import java.io.InputStream import java.io.OutputStream /** * Paint.NET palette coder */ class PaintNETPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val allData = input.readBytes() // Check for UTF-8 BOM val data = if (allData.size >= 3 && allData[0].toInt() == 0xEF && allData[1].toInt() == 0xBB && allData[2].toInt() == 0xBF ) { allData.drop(3).toByteArray() } else { allData } val content = String(data, java.nio.charset.StandardCharsets.UTF_8) val result = Palette.Builder() var currentName = "" for (line in content.lines()) { val trimmed = line.trim() if (trimmed.isEmpty()) { continue } if (trimmed.startsWith(";")) { // Comment - might be a color name val commentText = trimmed.substring(1).trim() if (commentText.startsWith("Palette Name", ignoreCase = true) || commentText.startsWith("Palette:", ignoreCase = true) ) { val name = commentText.substringAfter(":").trim() if (name.isNotBlank()) { result.name = name } continue } if (commentText.isNotEmpty() && !commentText.contains("paint.net") && !commentText.contains( "Colors are written" ) && !commentText.contains( "Downloaded" ) && !commentText.contains( "Description" ) && !commentText.contains( "Colors" ) ) { currentName = commentText } continue } if (trimmed.length != 8) { throw PaletteCoderException.InvalidFormat() } // Parse AARRGGBB val a = trimmed.take(2).toIntOrNull(16) ?: continue val r = trimmed.substring(2, 4).toIntOrNull(16) ?: continue val g = trimmed.substring(4, 6).toIntOrNull(16) ?: continue val b = trimmed.substring(6, 8).toIntOrNull(16) ?: continue val color = PaletteColor.rgb( r = r / 255.0, g = g / 255.0, b = b / 255.0, a = a / 255.0, name = currentName ) result.colors.add(color) currentName = "" } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val rgbColors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } var content = """; paint.net Palette File ; Lines that start with a semicolon are comments ; Colors are written as 8-digit hexadecimal numbers: aarrggbb """ if (palette.name.isNotEmpty()) { content += "; Palette Name: ${palette.name}\r\n" } rgbColors.forEach { color -> color.toRgb() if (color.name.isNotEmpty()) { content += "; ${color.name}\r\n" } val hex = color.hexString(ColorByteFormat.ARGB, hashmark = false, uppercase = true) content += "$hex\r\n" } output.write(content.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/PaintShopProPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * Paint Shop Pro (JASC-PAL) palette coder */ class PaintShopProPaletteCoder : PaletteCoder { companion object { private val colorRegex = Regex("^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*$") } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() if (lines.size < 3) { throw PaletteCoderException.InvalidFormat() } // Check BOM if (!lines[0].contains("JASC-PAL")) { throw PaletteCoderException.InvalidFormat() } // Check version if (lines[1] != "0100") { throw PaletteCoderException.InvalidFormat() } // Get color count lines[2].toIntOrNull() ?: throw PaletteCoderException.InvalidFormat() val result = Palette.Builder() var currentName = "" for (line in lines.drop(3)) { val trimmed = line.trim() if (trimmed.isEmpty()) continue if (trimmed.startsWith(";")) { // Comment - might be a color name val commentText = trimmed.substring(1).trim() if (commentText.isNotEmpty()) { currentName = commentText } continue } colorRegex.find(line)?.let { match -> val r = match.groupValues[1].toIntOrNull() ?: return@let val g = match.groupValues[2].toIntOrNull() ?: return@let val b = match.groupValues[3].toIntOrNull() ?: return@let val color = PaletteColor.rgb( r = (r / 255.0).coerceIn(0.0, 1.0), g = (g / 255.0).coerceIn(0.0, 1.0), b = (b / 255.0).coerceIn(0.0, 1.0), name = currentName ) result.colors.add(color) currentName = "" } } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val flattenedColors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } var result = "JASC-PAL\n0100\n${flattenedColors.size}\n" flattenedColors.forEach { color -> if (color.name.isNotEmpty()) { result += "; ${color.name}\n" } val rgb = color.toRgb() val r = ((rgb.rf * 255).coerceIn(0.0, 255.0).toInt()) val g = ((rgb.gf * 255).coerceIn(0.0, 255.0).toInt()) val b = ((rgb.bf * 255).coerceIn(0.0, 255.0).toInt()) result += "$r $g $b\n" } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/PaletteFormatCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteFormat class PaletteFormatCoder( val paletteFormat: PaletteFormat ) : PaletteCoder by when (paletteFormat) { PaletteFormat.ACB -> ACBPaletteCoder() PaletteFormat.ACO -> ACOPaletteCoder() PaletteFormat.ACT -> ACTPaletteCoder() PaletteFormat.ANDROID_XML -> AndroidColorsXMLCoder() PaletteFormat.ASE -> ASEPaletteCoder() PaletteFormat.BASIC_XML -> BasicXMLCoder() PaletteFormat.COREL_PAINTER -> CorelPainterCoder() PaletteFormat.COREL_DRAW -> CorelXMLPaletteCoder() PaletteFormat.SCRIBUS_XML -> ScribusXMLPaletteCoder() PaletteFormat.COREL_PALETTE -> CPLPaletteCoder() PaletteFormat.CSV -> CSVPaletteCoder() PaletteFormat.DCP -> DCPPaletteCoder() PaletteFormat.GIMP -> GIMPPaletteCoder() PaletteFormat.HEX_RGBA -> HEXPaletteCoder() PaletteFormat.IMAGE -> ImagePaletteCoder() PaletteFormat.JSON -> JSONPaletteCoder() PaletteFormat.OPEN_OFFICE -> OpenOfficePaletteCoder() PaletteFormat.PAINT_NET -> PaintNETPaletteCoder() PaletteFormat.PAINT_SHOP_PRO -> PaintShopProPaletteCoder() PaletteFormat.RGBA -> RGBAPaletteCoder() PaletteFormat.RGB -> RGBPaletteCoder() PaletteFormat.RIFF -> RIFFPaletteCoder() PaletteFormat.SKETCH -> SketchPaletteCoder() PaletteFormat.SVG -> SVGPaletteCoder() PaletteFormat.SKP -> SKPPaletteCoder() PaletteFormat.SWIFT -> SwiftPaletteCoder() PaletteFormat.KOTLIN -> KotlinPaletteCoder() PaletteFormat.COREL_DRAW_V3 -> CorelDraw3PaletteCoder() PaletteFormat.CLF -> CLFPaletteCoder() PaletteFormat.SWATCHES -> ProcreateSwatchesCoder() PaletteFormat.AUTODESK_COLOR_BOOK -> AutodeskColorBookCoder() PaletteFormat.SIMPLE_PALETTE -> SimplePaletteCoder() PaletteFormat.SWATCHBOOKER -> SwatchbookerCoder() PaletteFormat.AFPALETTE -> AFPaletteCoder() PaletteFormat.XARA -> JCWPaletteCoder() PaletteFormat.KOFFICE -> KOfficePaletteCoder() PaletteFormat.HPL -> HPLPaletteCoder() PaletteFormat.SKENCIL -> SkencilPaletteCoder() PaletteFormat.VGA_24BIT -> VGA24BitPaletteCoder() PaletteFormat.VGA_18BIT -> VGA18BitPaletteCoder() PaletteFormat.KRITA -> KRITAPaletteCoder() } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/ProcreateSwatchesCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import java.io.InputStream import java.io.OutputStream import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream /** * Procreate Swatches coder */ class ProcreateSwatchesCoder : PaletteCoder { private val json = Json { ignoreUnknownKeys = true } @Serializable private data class Swatch( val name: String? = null, val hue: Double, val saturation: Double, val brightness: Double, val alpha: Double = 1.0, val colorSpace: Int? = null, val origin: Int? = null, val colorModel: Int? = null, val colorProfile: String? = null, val version: String? = null, val components: List? = null ) @Serializable private data class SwatchPalette( val name: String, val swatches: List ) override fun decode(input: InputStream): Palette { val data = input.readBytes() val zipInputStream = ZipInputStream(java.io.ByteArrayInputStream(data)) var jsonData = ByteArray(0) var entry: ZipEntry? = zipInputStream.nextEntry while (entry != null) { if (entry.name == "Swatches.json") { jsonData = zipInputStream.readBytes() break } entry = zipInputStream.nextEntry } zipInputStream.close() if (jsonData.isEmpty()) { throw PaletteCoderException.InvalidFormat() } val jsonText = String(jsonData, java.nio.charset.StandardCharsets.UTF_8) val swatches: List = try { json.decodeFromString>(jsonText) } catch (_: Throwable) { // Try single palette try { listOf(json.decodeFromString(SwatchPalette.serializer(), jsonText)) } catch (_: Throwable) { throw PaletteCoderException.InvalidFormat() } } val result = Palette.Builder() swatches.forEach { palette -> val groupColors = palette.swatches .filterNotNull() .map { swatch -> val color = PaletteColor.hsb( hf = swatch.hue, sf = swatch.saturation, bf = swatch.brightness, alpha = swatch.alpha, name = swatch.name ?: "" ) color } if (groupColors.isNotEmpty()) { // Add colors to main palette if it's the first group or if palette name is empty if (result.colors.isEmpty() && result.groups.isEmpty()) { result.colors.addAll(groupColors) result.name = palette.name } else { val group = ColorGroup(colors = groupColors.toMutableList(), name = palette.name) result.groups.add(group) } } } val palette = result.build() if (palette.totalColorCount == 0) { throw PaletteCoderException.InvalidFormat() } return palette } override fun encode(palette: Palette, output: OutputStream) { if (palette.totalColorCount == 0) { throw PaletteCoderException.TooFewColors() } // Map each group in the palette val groups = listOf(ColorGroup(colors = palette.colors, name = palette.name)) + palette.groups val mapped: List = groups.mapNotNull { group -> if (group.colors.isEmpty()) { return@mapNotNull null } SwatchPalette( name = group.name, swatches = group.colors.map { color -> val hsb = color.toHsb() Swatch( name = color.name.ifEmpty { null }, hue = hsb.hf, saturation = hsb.sf, brightness = hsb.bf, alpha = hsb.af ) } ) } val jsonData = json.encodeToString>(mapped) // Create ZIP archive val zipOutputStream = ZipOutputStream(output) val entry = ZipEntry("Swatches.json") zipOutputStream.putNextEntry(entry) zipOutputStream.write(jsonData.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) zipOutputStream.closeEntry() zipOutputStream.close() } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/RGBAPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * RGBA text palette coder */ class RGBAPaletteCoder : PaletteCoder { companion object { private val regex = Regex("^#?\\s*([a-f0-9]{3,8})\\s*(.*)\\s*", RegexOption.IGNORE_CASE) } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() val result = Palette.Builder() for (line in lines) { val trimmed = line.trim() if (trimmed.isEmpty()) continue val matches = regex.findAll(trimmed).toList() if (matches.isEmpty()) { throw PaletteCoderException.InvalidRGBAHexString(trimmed) } matches.forEach { match -> val hex = match.groupValues[1] val name = match.groupValues[2].trim() try { val color = PaletteColor(hex, ColorByteFormat.RGBA, name) result.colors.add(color) } catch (_: Throwable) { throw PaletteCoderException.InvalidRGBAHexString(hex) } } } val palette = result.build() if (palette.allColors().isEmpty()) { throw PaletteCoderException.InvalidFormat() } return palette } override fun encode(palette: Palette, output: OutputStream) { val flattenedColors = palette.allColors().map { if (it.colorSpace == ColorSpace.RGB) it else it.converted(ColorSpace.RGB) } var result = "" flattenedColors.forEach { color -> if (result.isNotEmpty()) { result += "\r\n" } val rgb = color.toRgb() result += rgb.hexString( format = ColorByteFormat.RGBA, hashmark = true, uppercase = false ) if (color.name.isNotEmpty()) { result += " ${color.name}" } } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/RGBPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * RGB text palette coder */ class RGBPaletteCoder : PaletteCoder { companion object { private val regex = Regex("^#?\\s*([a-f0-9]{3,8})\\s*(.*)\\s*", RegexOption.IGNORE_CASE) } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() val result = Palette.Builder() for (line in lines) { val trimmed = line.trim() if (trimmed.isEmpty()) continue regex.findAll(trimmed).forEach { match -> val hex = match.groupValues[1] val name = match.groupValues[2].trim() try { val color = PaletteColor(hex, ColorByteFormat.RGB, name) result.colors.add(color) } catch (_: Throwable) { // Skip invalid color } } } val palette = result.build() if (palette.allColors().isEmpty()) { throw PaletteCoderException.InvalidFormat() } return palette } override fun encode(palette: Palette, output: OutputStream) { val flattenedColors = palette.allColors().map { if (it.colorSpace == ColorSpace.RGB) it else it.converted(ColorSpace.RGB) } var result = "" flattenedColors.forEach { color -> if (result.isNotEmpty()) { result += "\r\n" } result += color.hexString( format = ColorByteFormat.RGB, hashmark = true, uppercase = false ) if (color.name.isNotEmpty()) { result += " ${color.name}" } } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/RIFFPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.ByteOrder import com.t8rin.palette.utils.BytesReader import com.t8rin.palette.utils.BytesWriter import java.io.InputStream import java.io.OutputStream /** * Microsoft RIFF palette coder */ class RIFFPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val parser = BytesReader(input) val result = Palette.Builder() val header = parser.readStringASCII(4) if (header != "RIFF") { throw PaletteCoderException.InvalidFormat() } parser.readInt32(ByteOrder.LITTLE_ENDIAN) val riffType = parser.readStringASCII(4) if (riffType != "PAL ") { throw PaletteCoderException.InvalidFormat() } val dataHeader = parser.readStringASCII(4) parser.readInt32(ByteOrder.LITTLE_ENDIAN) if (dataHeader != "data") { throw PaletteCoderException.InvalidFormat() } parser.readInt16(ByteOrder.LITTLE_ENDIAN) // palVersion val palNumEntries = parser.readInt16(ByteOrder.LITTLE_ENDIAN) for (i in 0 until palNumEntries) { val rgb = parser.readData(4) val r = rgb[0].toUByte().toInt() val g = rgb[1].toUByte().toInt() val b = rgb[2].toUByte().toInt() // rgb[3] is unused val color = PaletteColor.rgb( r = r / 255.0, g = g / 255.0, b = b / 255.0 ) result.colors.add(color) } // Try to read color names from "name" chunk (non-standard extension) try { val nameHeader = parser.readInt32(ByteOrder.BIG_ENDIAN) if (nameHeader == 0x6E616D65) { // "name" in ASCII val nameChunkSize = parser.readInt32(ByteOrder.LITTLE_ENDIAN) val nameData = parser.readData(nameChunkSize) val nameText = String(nameData, java.nio.charset.StandardCharsets.UTF_8).trimEnd('\u0000') val names = nameText.split("\n") names.forEachIndexed { index, name -> if (index < result.colors.size && name.isNotEmpty()) { result.colors[index] = result.colors[index].named(name) } } } else { // Not a name chunk, reset position parser.seekSet((parser.readPosition - 4).toInt()) } } catch (_: Throwable) { // No names chunk, continue without names } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val writer = BytesWriter(output) val sourceColors = palette.allColors() val allColors = sourceColors.map { it.toRgb() } val colorCount = allColors.size val dataChunkSize = 2 + 2 + colorCount * 4 val dataChunkTotalSize = 8 + dataChunkSize val names = sourceColors.map { it.name } val hasNames = names.any { it.isNotEmpty() } val nameBytes = if (hasNames) { names.joinToString("\n").toByteArray(java.nio.charset.StandardCharsets.UTF_8) } else { ByteArray(0) } val nameChunkPadding = if (hasNames && nameBytes.size % 2 != 0) 1 else 0 val nameChunkTotalSize = if (hasNames) 8 + nameBytes.size + nameChunkPadding else 0 val fileSizeMinus8 = 4 + dataChunkTotalSize + nameChunkTotalSize writer.writeInt32(0x52494646, ByteOrder.BIG_ENDIAN) writer.writeInt32(fileSizeMinus8, ByteOrder.LITTLE_ENDIAN) writer.writeInt32(0x50414C20, ByteOrder.BIG_ENDIAN) writer.writeInt32(0x64617461, ByteOrder.BIG_ENDIAN) writer.writeInt32(dataChunkSize, ByteOrder.LITTLE_ENDIAN) // palVersion (2 bytes, little endian) - typically 3 writer.writeInt16(3, ByteOrder.LITTLE_ENDIAN) // palNumEntries (2 bytes, little endian) writer.writeInt16(colorCount.toShort(), ByteOrder.LITTLE_ENDIAN) // Write colors (4 bytes each: R, G, B, unused) allColors.forEach { rgb -> writer.writeByte((rgb.rf * 255).toInt().coerceIn(0, 255).toByte()) writer.writeByte((rgb.gf * 255).toInt().coerceIn(0, 255).toByte()) writer.writeByte((rgb.bf * 255).toInt().coerceIn(0, 255).toByte()) writer.writeByte(0) // unused } if (hasNames) { writer.writeInt32(0x6E616D65, ByteOrder.BIG_ENDIAN) writer.writeInt32(nameBytes.size, ByteOrder.LITTLE_ENDIAN) writer.writeData(nameBytes) if (nameChunkPadding != 0) { writer.writeByte(0) } } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/SKPPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream /** * SK1 Color Palette coder */ class SKPPaletteCoder : PaletteCoder { companion object { private val rgbColorRegex = Regex("color\\(\\['RGB', \\[(\\d+(?:\\.\\d+)?), (\\d+(?:\\.\\d+)?), (\\d+(?:\\.\\d+)?)], (\\d+(?:\\.\\d+)?),.*?'(.*?)'") private val paletteNameRegex = Regex("set_name\\(.*?'(.*?)'\\)") } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() if (lines.isEmpty() || !lines[0].contains("##sK1 palette")) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() for (line in lines.drop(1)) { val trimmed = line.trim() if (trimmed.isEmpty()) continue // Check for palette name paletteNameRegex.find(trimmed)?.let { match -> val name = match.groupValues[1] if (name.isNotEmpty()) { result.name = name } } // Check for color definition rgbColorRegex.find(trimmed)?.let { match -> val r = match.groupValues[1].toDoubleOrNull() ?: return@let val g = match.groupValues[2].toDoubleOrNull() ?: return@let val b = match.groupValues[3].toDoubleOrNull() ?: return@let val a = match.groupValues[4].toDoubleOrNull() ?: return@let val name = match.groupValues[5] val color = PaletteColor.rgb( r = r.coerceIn(0.0, 1.0), g = g.coerceIn(0.0, 1.0), b = b.coerceIn(0.0, 1.0), a = a.coerceIn(0.0, 1.0), name = name ) result.colors.add(color) } } val palette = result.build() if (palette.allColors().isEmpty()) { throw PaletteCoderException.InvalidFormat() } return palette } override fun encode(palette: Palette, output: OutputStream) { var result = "##sK1 palette\n" result += "Palette.Builder()\n" result += "set_name('${palette.name.replace("'", "_").replace("\"", "_")}')\n" result += "set_source('ColorPaletteCodable')\n" result += "set_columns(4)\n" palette.allColors().forEach { color -> val rgb = color.toRgb() val colorName = color.name.replace("'", "_").replace("\"", "_") result += "color(['RGB', [${rgb.rf}, ${rgb.gf}, ${rgb.bf}], ${rgb.af}, '$colorName'])\n" } result += "palette_end()\n" output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/SVGPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.extractHexRGBA import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets import java.text.DecimalFormat import javax.xml.parsers.SAXParserFactory /** * SVG palette coder */ class SVGPaletteCoder( private val swatchSize: Size = Size(width = 40.0, height = 40.0), private val maxExportWidth: Double = 600.0, private val edgeInset: EdgeInsets = EdgeInsets(top = 4.0, left = 4.0, bottom = 4.0, right = 4.0) ) : PaletteCoder { data class Size(val width: Double, val height: Double) data class EdgeInsets(val top: Double, val left: Double, val bottom: Double, val right: Double) private val formatter = DecimalFormat("#.###").apply { decimalFormatSymbols = java.text.DecimalFormatSymbols(java.util.Locale.US) } private class SVGHandler : DefaultHandler() { val palette = Palette.Builder() private var currentChars = StringBuilder() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() val elementName = (qName ?: localName).trim().lowercase() if (elementName == "rect") { val fill = attributes.getValue("fill") val fillOpacity = attributes.getValue("fill-opacity")?.toDoubleOrNull() ?: 1.0 val name = attributes.getValue("id") ?: "" if (fill != null && fill.isNotEmpty()) { try { val color = when { fill.startsWith("#") -> { val rgb = extractHexRGBA(fill, ColorByteFormat.RGB) if (rgb != null) { PaletteColor.rgb( r = rgb.rf, g = rgb.gf, b = rgb.bf, a = fillOpacity, name = name ) } else null } fill.startsWith("rgb") -> { // Parse rgb(r, g, b) or rgba(r, g, b, a) val rgbMatch = Regex("""rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)""") .find(fill) if (rgbMatch != null) { val r = rgbMatch.groupValues[1].toIntOrNull() ?: 0 val g = rgbMatch.groupValues[2].toIntOrNull() ?: 0 val b = rgbMatch.groupValues[3].toIntOrNull() ?: 0 val a = rgbMatch.groupValues[4].toDoubleOrNull() ?: fillOpacity PaletteColor.rgb( r = (r / 255.0).coerceIn(0.0, 1.0), g = (g / 255.0).coerceIn(0.0, 1.0), b = (b / 255.0).coerceIn(0.0, 1.0), a = a.coerceIn(0.0, 1.0), name = name ) } else null } else -> null } if (color != null) { palette.colors.add(color) } } catch (_: Throwable) { // Skip invalid colors } } } } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } } override fun decode(input: InputStream): Palette { val handler = SVGHandler() val factory = SAXParserFactory.newInstance() factory.isNamespaceAware = true factory.isValidating = false val parser = factory.newSAXParser() parser.parse(input, handler) if (handler.palette.colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return handler.palette.build() } override fun encode(palette: Palette, output: OutputStream) { var xOffset = edgeInset.left var yOffset = edgeInset.top fun exportGrouping(colors: List): String { var result = "" colors.forEach { color -> val rgb = color.toRgb() val hex = rgb.hexString( format = ColorByteFormat.RGB, hashmark = true, uppercase = true ) result += " maxExportWidth) { yOffset += swatchSize.height + 1 xOffset = edgeInset.left } } return result } var colorsXml = "" // Global colors first colorsXml += exportGrouping(palette.colors) palette.groups.forEach { group -> xOffset = edgeInset.left colorsXml += exportGrouping(group.colors) if (group.name.isNotEmpty()) { yOffset += swatchSize.height + 10 colorsXml += " ${group.name.xmlEscaped()}\n\n" } yOffset += 10 } yOffset += edgeInset.bottom val result = """ """ output.write(result.toByteArray(StandardCharsets.UTF_8)) output.write(colorsXml.toByteArray(StandardCharsets.UTF_8)) output.write("\n".toByteArray(StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/ScribusXMLPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.hexString import com.t8rin.palette.utils.xmlDecoded import com.t8rin.palette.utils.xmlEscaped import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets import javax.xml.parsers.SAXParserFactory /** * Scribus XML palette coder */ class ScribusXMLPaletteCoder : PaletteCoder { private class ScribusXMLHandler : DefaultHandler() { val palette = Palette.Builder() private var currentChars = StringBuilder() override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() val elementName = (qName ?: localName).lowercase() if (elementName == "scribuscolors" || elementName == "scolors") { val name = attributes.getValue("Name") ?: attributes.getValue("name") ?: "" palette.name = name.xmlDecoded() } else if (elementName == "color") { val name = (attributes.getValue("NAME") ?: attributes.getValue("name") ?: "").xmlDecoded() val rgbHex = attributes.getValue("RGB") ?: attributes.getValue("rgb") val cmykHex = attributes.getValue("CMYK") ?: attributes.getValue("cmyk") try { val color = if (rgbHex != null) { PaletteColor( rgbHexString = rgbHex, format = ColorByteFormat.RGB, name = name ) } else if (cmykHex != null) { // CMYK hex format - parse it PaletteColor(cmykHexString = cmykHex, name = name) } else { null } if (color != null) { palette.colors.add(color) } } catch (_: Throwable) { // Skip invalid colors } } } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } } override fun decode(input: InputStream): Palette { val handler = ScribusXMLHandler() val factory = SAXParserFactory.newInstance() factory.isNamespaceAware = false val parser = factory.newSAXParser() parser.parse(input, handler) if (handler.palette.colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return handler.palette.build() } override fun encode(palette: Palette, output: OutputStream) { var xml = "\n" xml += " try { if (color.colorSpace == ColorSpace.CMYK) { val cmyk = color.toCmyk() // CMYK hex representation (simplified) val c = (cmyk.c * 255).toInt().coerceIn(0, 255) val m = (cmyk.m * 255).toInt().coerceIn(0, 255) val y = (cmyk.y * 255).toInt().coerceIn(0, 255) val k = (cmyk.k * 255).toInt().coerceIn(0, 255) val hex = String.format("#%02x%02x%02x%02x", c, m, y, k) xml += ". */ package com.t8rin.palette.coders import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import java.io.InputStream import java.io.OutputStream import kotlin.math.pow /** * Simple Color Palette coder * https://sindresorhus.com/simple-color-palette */ class SimplePaletteCoder : PaletteCoder { private val json = Json { ignoreUnknownKeys = true prettyPrint = true } @Serializable private data class SimplePaletteColor( val name: String? = null, val components: List ) @Serializable private data class SimplePalette( val name: String? = null, val colors: List ) companion object { private const val MAX_DECIMAL_PLACES = 4 private fun Double.roundToPlaces(places: Int): Double { if (places <= 0) return this val multiplier = 10.0.pow(places.toDouble()) return (this * multiplier).let { kotlin.math.round(it) } / multiplier } private fun sRGB2Linear(value: Double): Double { return if (value <= 0.04045) { value / 12.92 } else { ((value + 0.055) / 1.055).pow(2.4) } } private fun linearSRGB2SRGB(value: Double): Double { return if (value <= 0.0031308) { value * 12.92 } else { 1.055 * value.pow(1.0 / 2.4) - 0.055 } } } override fun decode(input: InputStream): Palette { val text = input.readText() val s = json.decodeFromString(SimplePalette.serializer(), text) val result = Palette.Builder() result.name = s.name ?: "" val colors = s.colors.mapNotNull { colorData -> val components = colorData.components.map { it.roundToPlaces(MAX_DECIMAL_PLACES) } if (components.size < 3 || components.size > 4) return@mapNotNull null val lr = components[0] val lg = components[1] val lb = components[2] val la = if (components.size == 4) components[3].coerceIn(0.0, 1.0) else 1.0 // Convert from linear extended sRGB to sRGB val r = linearSRGB2SRGB(lr) val g = linearSRGB2SRGB(lg) val b = linearSRGB2SRGB(lb) PaletteColor.rgb( r = r, g = g, b = b, a = la, name = colorData.name ?: "" ) } result.colors = colors.toMutableList() return result.build() } override fun encode(palette: Palette, output: OutputStream) { val name: String? = palette.name.ifEmpty { null } val colors = palette.allColors().map { color -> val rgb = color.toRgb() // Convert to linear (extended) val components = mutableListOf( sRGB2Linear(rgb.rf).roundToPlaces(MAX_DECIMAL_PLACES), sRGB2Linear(rgb.gf).roundToPlaces(MAX_DECIMAL_PLACES), sRGB2Linear(rgb.bf).roundToPlaces(MAX_DECIMAL_PLACES) ) // Add alpha if not 1.0 if (rgb.af.roundToPlaces(MAX_DECIMAL_PLACES) != 1.0) { components.add(rgb.af.roundToPlaces(MAX_DECIMAL_PLACES)) } SimplePaletteColor( name = color.name.ifEmpty { null }, components = components ) } val result = SimplePalette(name = name, colors = colors) val encoded = json.encodeToString(SimplePalette.serializer(), result) output.write(encoded.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/SkencilPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale /** * Skencil Palette coder */ class SkencilPaletteCoder : PaletteCoder { companion object { private val colorRegex = Regex("^\\s*(\\d*\\.?\\d+|\\d+)\\s+(\\d*\\.?\\d+|\\d+)\\s+(\\d*\\.?\\d+|\\d+)\\s+(.*)$") private val formatter = DecimalFormat("0.000000", DecimalFormatSymbols(Locale.US)) } override fun decode(input: InputStream): Palette { val text = input.readText() val lines = text.lines() if (lines.isEmpty() || !lines[0].contains("##Sketch RGBPalette 0")) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() for (line in lines.drop(1)) { colorRegex.find(line)?.let { match -> val r = match.groupValues[1].toDoubleOrNull() ?: return@let val g = match.groupValues[2].toDoubleOrNull() ?: return@let val b = match.groupValues[3].toDoubleOrNull() ?: return@let val name = match.groupValues[4].trim() val color = PaletteColor.rgb( r = r.coerceIn(0.0, 1.0), g = g.coerceIn(0.0, 1.0), b = b.coerceIn(0.0, 1.0), name = name ) result.colors.add(color) } } return result.build() } override fun encode(palette: Palette, output: OutputStream) { var result = "##Sketch RGBPalette 0\n" val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } colors.forEach { color -> val rgb = color.toRgb() val rs = formatter.format(rgb.rf.coerceIn(0.0, 1.0)) val gs = formatter.format(rgb.gf.coerceIn(0.0, 1.0)) val bs = formatter.format(rgb.bf.coerceIn(0.0, 1.0)) result += "$rs $gs $bs" if (color.name.isNotEmpty()) { result += " ${color.name}" } result += "\n" } output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/SketchPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import java.io.InputStream import java.io.OutputStream /** * Sketch palette coder */ class SketchPaletteCoder : PaletteCoder { private val json = Json { ignoreUnknownKeys = true } @Serializable private data class SketchColor( val red: Double, val green: Double, val blue: Double, val alpha: Double ) @Serializable private data class SketchFile( val compatibleVersion: String, val pluginVersion: String, val colors: List ) override fun decode(input: InputStream): Palette { val text = input.readText() // Try to decode with names first val result = Palette.Builder() try { val sketchFileWithNames = json.decodeFromString(SketchFileWithNames.serializer(), text) result.colors = sketchFileWithNames.colors.map { sketchColor -> PaletteColor.rgb( r = sketchColor.red, g = sketchColor.green, b = sketchColor.blue, a = sketchColor.alpha, name = sketchColor.name ?: "" ) }.toMutableList() } catch (_: Throwable) { // Fall back to old format without names val sketchFile = json.decodeFromString(SketchFile.serializer(), text) result.colors = sketchFile.colors.map { sketchColor -> PaletteColor.rgb( r = sketchColor.red, g = sketchColor.green, b = sketchColor.blue, a = sketchColor.alpha ) }.toMutableList() } return result.build() } @Serializable private data class SketchColorWithName( val name: String? = null, val red: Double, val green: Double, val blue: Double, val alpha: Double ) @Serializable private data class SketchFileWithNames( val compatibleVersion: String, val pluginVersion: String, val colors: List ) override fun encode(palette: Palette, output: OutputStream) { val colors = palette.allColors().map { color -> val rgb = color.toRgb() SketchColorWithName( name = if (color.name.isNotEmpty()) color.name else null, red = rgb.rf, green = rgb.gf, blue = rgb.bf, alpha = rgb.af ) } val file = SketchFileWithNames( compatibleVersion = "1.4", pluginVersion = "1.4", colors = colors ) val encoded = json.encodeToString(SketchFileWithNames.serializer(), file) output.write(encoded.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/SwatchbookerCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteColor import org.xml.sax.Attributes import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.OutputStream import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream import javax.xml.parsers.SAXParserFactory class SwatchbookerCoder : PaletteCoder { private class SwatchbookerXMLHandler : DefaultHandler() { var palette = Palette.Builder() private val nodeStack = mutableListOf() private var colorTitle: String? = null private var colorID: String? = null private var colorMode: String? = null private var colorComponents = mutableListOf() val colorMaterialMap = mutableMapOf() val colorMaterialOrdering = mutableListOf() private var currentChars = StringBuilder() private data class Node( val name: String, val attrs: Map, var content: String = "" ) private fun attributesToMap(attributes: Attributes): Map { val map = mutableMapOf() for (i in 0 until attributes.length) { val key = attributes.getQName(i).ifEmpty { attributes.getLocalName(i) } map[key] = attributes.getValue(i) } return map } override fun startElement( uri: String?, localName: String, qName: String?, attributes: Attributes ) { currentChars.clear() val elementName = (qName ?: localName).trim() if (elementName.isEmpty()) return val attrs = attributesToMap(attributes) nodeStack.add(Node(elementName, attrs)) when { elementName.equals( "color", ignoreCase = true ) && nodeStack.any { it.name.equals("materials", ignoreCase = true) } -> { colorTitle = null colorID = null colorMode = null colorComponents.clear() } elementName.equals("values", ignoreCase = true) -> { colorMode = attrs["model"] ?: attrs["MODEL"] ?: attrs["Model"] colorComponents.clear() } elementName.equals( "swatch", ignoreCase = true ) && nodeStack.any { it.name.equals("book", ignoreCase = true) } -> { attrs["material"]?.let { colorMaterialOrdering.add(it.trim()) } } } } override fun endElement(uri: String?, localName: String, qName: String?) { val elementName = (qName ?: localName).trim() if (elementName.isEmpty()) { if (nodeStack.isNotEmpty()) nodeStack.removeAt(nodeStack.size - 1) return } nodeStack.lastOrNull() ?: run { if (nodeStack.isNotEmpty()) nodeStack.removeAt(nodeStack.size - 1) return } val content = currentChars.toString().trim() when { elementName.equals("dc:title", ignoreCase = true) -> { if (nodeStack.any { it.name.equals("materials", ignoreCase = true) }) { colorTitle = content } else { palette.name = content } } elementName.equals("dc:identifier", ignoreCase = true) -> colorID = content elementName.equals("values", ignoreCase = true) -> { colorComponents = content.split(Regex("\\s+")).mapNotNull { it.toDoubleOrNull() } .toMutableList() } elementName.equals( "color", ignoreCase = true ) && nodeStack.any { it.name.equals("materials", ignoreCase = true) } -> { val name = colorTitle ?: colorID ?: "Color ${palette.colors.size + 1}" val color: PaletteColor? = when { colorMode.equals("RGB", true) && colorComponents.size >= 3 -> { val r = colorComponents[0] val g = colorComponents[1] val b = colorComponents[2] val a = if (colorComponents.size >= 4) colorComponents[3] else 1.0 PaletteColor.rgb(r, g, b, a, name) } colorMode.equals( "Lab", true ) && colorComponents.size >= 3 -> PaletteColor.lab( colorComponents[0], colorComponents[1], colorComponents[2], 1.0, name ) colorMode.equals( "CMYK", true ) && colorComponents.size >= 4 -> PaletteColor.cmyk( colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3], 1.0, name ) colorMode.equals( "GRAY", true ) && colorComponents.isNotEmpty() -> PaletteColor.gray( colorComponents[0], 1.0, name ) colorMode.equals( "HSV", true ) && colorComponents.size >= 3 -> PaletteColor.hsb( colorComponents[0], colorComponents[1], colorComponents[2], 1.0, name ) colorMode.equals( "HSL", true ) && colorComponents.size >= 3 -> PaletteColor.hsl( colorComponents[0], colorComponents[1], colorComponents[2], 1.0, name ) else -> null } if (color != null) { val id = colorID ?: "color_${palette.colors.size}" palette.colors.add(color) colorMaterialMap[id] = color.id } } } if (nodeStack.isNotEmpty()) nodeStack.removeAt(nodeStack.size - 1) currentChars.clear() } override fun characters(ch: CharArray, start: Int, length: Int) { currentChars.appendRange(ch, start, start + length) } } override fun decode(input: InputStream): Palette { val data = input.readBytes() val zipInputStream = ZipInputStream(data.inputStream()) var xmlData = ByteArray(0) var entry: ZipEntry? = zipInputStream.nextEntry while (entry != null) { if (entry.name.equals("swatchbook.xml", true)) { xmlData = zipInputStream.readBytes() break } entry = zipInputStream.nextEntry } zipInputStream.close() if (xmlData.isEmpty()) return Palette() val handler = SwatchbookerXMLHandler() val factory = SAXParserFactory.newInstance() factory.isNamespaceAware = true factory.newSAXParser().parse(xmlData.inputStream(), handler) val ordered = mutableListOf() val remaining = handler.palette.colors.toMutableList() handler.colorMaterialOrdering.forEach { materialID -> val colorID = handler.colorMaterialMap[materialID] if (colorID != null) { val idx = remaining.indexOfFirst { it.id == colorID } if (idx >= 0) ordered.add(remaining.removeAt(idx)) } } ordered.addAll(remaining) handler.palette.colors = ordered return handler.palette.build() } override fun encode(palette: Palette, output: OutputStream) { val zipOutputStream = ZipOutputStream(output) zipOutputStream.putNextEntry(ZipEntry("swatchbook.xml")) val xml = buildString { appendLine("""""") appendLine("""""") appendLine(" ") appendLine(" ${escapeXml(palette.name)}") appendLine(" ") appendLine(" ") palette.allColors().forEachIndexed { index, color -> appendLine(" ") appendLine(" ") appendLine(" ${escapeXml(color.name)}") appendLine(" color_$index") appendLine(" ") appendLine(" ") appendLine(" ${getColorValues(color)}") appendLine(" ") appendLine(" ") } appendLine(" ") appendLine(" ") palette.allColors() .forEachIndexed { index, _ -> appendLine(" ") } appendLine(" ") appendLine("") } zipOutputStream.write(xml.toByteArray(Charsets.UTF_8)) zipOutputStream.closeEntry() zipOutputStream.close() } private fun escapeXml(text: String): String = text.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """) .replace("'", "'") private fun getColorModel(color: PaletteColor): String = when (color.colorSpace) { ColorSpace.RGB -> "RGB" ColorSpace.CMYK -> "CMYK" ColorSpace.LAB -> "Lab" ColorSpace.Gray -> "GRAY" } private fun getColorValues(color: PaletteColor): String { val comps = color.colorComponents.toMutableList() if (color.colorSpace == ColorSpace.RGB) comps.add(color.alpha) return comps.joinToString(" ") } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/SwiftPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import com.t8rin.palette.utils.readText import java.io.InputStream import java.io.OutputStream import java.text.DecimalFormat /** * Swift code generator */ class SwiftPaletteCoder : PaletteCoder { private val formatter = DecimalFormat("0.0000").apply { decimalFormatSymbols = java.text.DecimalFormatSymbols(java.util.Locale.US) } override fun decode(input: InputStream): Palette { val text = input.readText() val result = Palette.Builder() // Parse #colorLiteral(red: x, green: y, blue: z, alpha: w) // Also try to capture comments with color names after the colorLiteral: , // name // Format: #colorLiteral(...), // name // Use non-greedy match to stop at next #colorLiteral or end of line val colorLiteralRegex = Regex( pattern = """#colorLiteral\s*\(\s*red:\s*([\d.]+)\s*,\s*green:\s*([\d.]+)\s*,\s*blue:\s*([\d.]+)\s*,\s*alpha:\s*([\d.]+)\s*\)\s*,?\s*//\s*([^#\n]*?)(?=\s*#colorLiteral|$)""", options = setOf( RegexOption.IGNORE_CASE, RegexOption.MULTILINE ) ) var colorIndex = 0 colorLiteralRegex.findAll(text).forEach { match -> try { val r = match.groupValues[1].toDoubleOrNull() ?: 0.0 val g = match.groupValues[2].toDoubleOrNull() ?: 0.0 val b = match.groupValues[3].toDoubleOrNull() ?: 0.0 val a = match.groupValues[4].toDoubleOrNull() ?: 1.0 val colorName = match.groupValues[5].trim().takeIf { it.isNotEmpty() } ?: "" val finalName = colorName.ifEmpty { "Color_$colorIndex" } val color = PaletteColor.rgb( r = r.coerceIn(0.0, 1.0), g = g.coerceIn(0.0, 1.0), b = b.coerceIn(0.0, 1.0), a = a.coerceIn(0.0, 1.0), name = finalName ) result.colors.add(color) colorIndex++ } catch (_: Throwable) { // Skip invalid colors } } // Try to extract palette name from comments or struct name val paletteCommentMatch = Regex(pattern = """(?m)^\s*//\s*Palette:\s*(.+)\s*$""").find(text) if (paletteCommentMatch != null) { result.name = paletteCommentMatch.groupValues[1].trim() } else { val structNameMatch = Regex("""struct\s+(\w+)""").find(text) if (structNameMatch != null) { result.name = structNameMatch.groupValues[1] } } if (result.colors.isEmpty()) { throw PaletteCoderException.InvalidFormat() } return result.build() } override fun encode(palette: Palette, output: OutputStream) { fun mapColors(group: ColorGroup, offset: Int): String { val mapped = group.colors.mapNotNull { color -> try { val converted = if (color.colorSpace == ColorSpace.RGB) color else color.converted( ColorSpace.RGB ) converted.toRgb() } catch (_: Throwable) { null } } if (mapped.isEmpty()) return "" var result = " // Group (${group.name})\n" result += " static let group$offset: [CGColor] = [" mapped.forEachIndexed { index, rgb -> // Add comment with color name if available (on same line before colorLiteral) val originalColor = group.colors.getOrNull(index) val colorNameComment = if (originalColor != null && originalColor.name.isNotEmpty()) { " // ${originalColor.name}" } else { "" } if (index % 8 == 0) { result += "\n " } val rs = formatter.format(rgb.rf) val gs = formatter.format(rgb.gf) val bs = formatter.format(rgb.bf) val aas = formatter.format(rgb.af) result += " #colorLiteral(red: $rs, green: $gs, blue: $bs, alpha: $aas),$colorNameComment" } result += "\n ]\n\n" return result } var result = "" if (palette.name.isNotEmpty()) { result += "// Palette: ${palette.name}\n" } result += "struct ExportedPalettes {\n" palette.allGroups.forEachIndexed { index, group -> result += mapColors(group, index) } result += "}\n" output.write(result.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/VGA18BitPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import java.io.InputStream import java.io.OutputStream /** * VGA 18-bit RGB palette coder (3 bytes per color, 6-bit per channel) */ class VGA18BitPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val allData = input.readBytes() // Check if there's a text header with names var data = allData val names = mutableListOf() try { val text = String(allData, java.nio.charset.StandardCharsets.UTF_8) if (text.startsWith("; Names: ")) { val firstNewline = text.indexOf('\n') if (firstNewline > 0) { val nameLine = text.substring(0, firstNewline) val namesStr = nameLine.substring("; Names: ".length) names.addAll(namesStr.split(", ").map { it.trim() }) data = allData.sliceArray(firstNewline + 1 until allData.size) } } } catch (_: Throwable) { // Not a text header, use binary data as-is data = allData } if (data.size % 3 != 0) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() for (i in data.indices step 3) { val r = data[i].toUByte().toInt() val g = data[i + 1].toUByte().toInt() val b = data[i + 2].toUByte().toInt() if (r > 63 || g > 63 || b > 63) { throw PaletteCoderException.InvalidFormat() } val colorIndex = i / 3 val colorName = if (colorIndex < names.size) names[colorIndex] else "" val color = PaletteColor.rgb( r = r / 63.0, g = g / 63.0, b = b / 63.0, name = colorName ) result.colors.add(color) } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } // VGA format doesn't support names, but we can write them as a comment header val names = colors.mapNotNull { if (it.name.isNotEmpty()) it.name else null } if (names.isNotEmpty()) { val nameHeader = "; Names: ${names.joinToString(", ")}\n" output.write(nameHeader.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } colors.forEach { color -> val rgb = color.toRgb() val r = ((rgb.rf * 63).roundToInt().coerceIn(0, 63)) val g = ((rgb.gf * 63).roundToInt().coerceIn(0, 63)) val b = ((rgb.bf * 63).roundToInt().coerceIn(0, 63)) output.write(byteArrayOf(r.toByte(), g.toByte(), b.toByte())) } } private fun Double.roundToInt(): Int { return kotlin.math.round(this).toInt() } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/coders/VGA24BitPaletteCoder.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorSpace import com.t8rin.palette.Palette import com.t8rin.palette.PaletteCoder import com.t8rin.palette.PaletteCoderException import com.t8rin.palette.PaletteColor import java.io.InputStream import java.io.OutputStream /** * VGA 24-bit RGB palette coder (3 bytes per color: RRGGBB) */ class VGA24BitPaletteCoder : PaletteCoder { override fun decode(input: InputStream): Palette { val allData = input.readBytes() // Check if there's a text header with names var data = allData val names = mutableListOf() try { val text = String(allData, java.nio.charset.StandardCharsets.UTF_8) if (text.startsWith("; Names: ")) { val firstNewline = text.indexOf('\n') if (firstNewline > 0) { val nameLine = text.substring(0, firstNewline) val namesStr = nameLine.substring("; Names: ".length) names.addAll(namesStr.split(", ").map { it.trim() }) data = allData.sliceArray(firstNewline + 1 until allData.size) } } } catch (_: Throwable) { // Not a text header, use binary data as-is data = allData } if (data.size % 3 != 0) { throw PaletteCoderException.InvalidFormat() } val result = Palette.Builder() for (i in data.indices step 3) { val r = data[i].toUByte().toInt() val g = data[i + 1].toUByte().toInt() val b = data[i + 2].toUByte().toInt() val colorIndex = i / 3 val colorName = if (colorIndex < names.size) names[colorIndex] else "" val color = PaletteColor.rgb( r = r / 255.0, g = g / 255.0, b = b / 255.0, name = colorName ) result.colors.add(color) } return result.build() } override fun encode(palette: Palette, output: OutputStream) { val colors = palette.allColors().map { color -> if (color.colorSpace == ColorSpace.RGB) color else color.converted(ColorSpace.RGB) } // VGA format doesn't support names, but we can write them as a comment header val names = colors.mapNotNull { if (it.name.isNotEmpty()) it.name else null } if (names.isNotEmpty()) { val nameHeader = "; Names: ${names.joinToString(", ")}\n" output.write(nameHeader.toByteArray(java.nio.charset.StandardCharsets.UTF_8)) } colors.forEach { color -> val rgb = color.toRgb() val r = ((rgb.rf * 255).coerceIn(0.0, 255.0).toInt()) val g = ((rgb.gf * 255).coerceIn(0.0, 255.0).toInt()) val b = ((rgb.bf * 255).coerceIn(0.0, 255.0).toInt()) output.write(byteArrayOf(r.toByte(), g.toByte(), b.toByte())) } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/utils/BytesReader.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.utils import java.io.InputStream import java.nio.ByteBuffer /** * Byte order for reading/writing */ internal enum class ByteOrder { BIG_ENDIAN, LITTLE_ENDIAN } /** * Reader for binary data streams */ internal class BytesReader(inputStream: InputStream) { // Always read all data for seek support private val data: ByteArray = inputStream.readBytes() private var position: Int = 0 constructor(data: ByteArray) : this(java.io.ByteArrayInputStream(data)) /** * Read a specified number of bytes */ fun readData(count: Int): ByteArray { if (position + count > data.size) { throw java.io.EOFException("Unexpected end of stream") } val result = data.copyOfRange(position, position + count) position += count return result } /** * Seek to absolute position */ fun seekSet(pos: Int) { if (pos < 0 || pos > data.size) { throw java.io.EOFException("Position out of range") } position = pos } fun trySkipBytes(count: Int): Boolean { return if (position + count <= data.size) { position += count true } else { false } } fun findPattern(pattern: ByteArray): Int { outer@ for (i in position until data.size - pattern.size + 1) { for (j in pattern.indices) { if (data[i + j] != pattern[j]) continue@outer } return i } return -1 } /** * Read UInt8 */ fun readUInt8(): UByte { if (position >= data.size) throw java.io.EOFException("Unexpected end of stream") return data[position++].toUByte() } /** * Read byte */ fun readByte(): Byte { if (position >= data.size) throw java.io.EOFException("Unexpected end of stream") return data[position++] } /** * Read Int16 */ fun readInt16(order: ByteOrder = ByteOrder.BIG_ENDIAN): Short { val data = readData(2) val bb = ByteBuffer.wrap(data) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) return bb.short } /** * Read Int32 */ fun readInt32(order: ByteOrder = ByteOrder.BIG_ENDIAN): Int { val data = readData(4) val bb = ByteBuffer.wrap(data) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) return bb.int } /** * Read UInt16 */ fun readUInt16(order: ByteOrder = ByteOrder.BIG_ENDIAN): UShort { val data = readData(2) val bb = ByteBuffer.wrap(data) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) return bb.short.toUShort() } /** * Read UInt32 */ fun readUInt32(order: ByteOrder = ByteOrder.BIG_ENDIAN): UInt { val data = readData(4) val bb = ByteBuffer.wrap(data) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) return bb.int.toUInt() } /** * Read Float32 */ fun readFloat32(order: ByteOrder = ByteOrder.BIG_ENDIAN): Float { val data = readData(4) val bb = ByteBuffer.wrap(data) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) return bb.float } /** * Read UTF-16 string (null-terminated) */ fun readStringUTF16NullTerminated(order: ByteOrder = ByteOrder.BIG_ENDIAN): String { val bytes = mutableListOf() while (position + 1 < data.size) { val b1 = data[position++].toInt().and(0xFF) val b2 = data[position++].toInt().and(0xFF) if (b1 == 0 && b2 == 0) break // null terminator bytes.add(b1.toByte()) bytes.add(b2.toByte()) } val charset = if (order == ByteOrder.BIG_ENDIAN) java.nio.charset.StandardCharsets.UTF_16BE else java.nio.charset.StandardCharsets.UTF_16LE return String(bytes.toByteArray(), charset) } /** * Read ASCII string with specified length */ fun readStringASCII(length: Int): String { val data = readData(length) return String(data, java.nio.charset.StandardCharsets.US_ASCII) } /** * Read ISO Latin1 string with specified length */ fun readStringISOLatin1(length: Int): String { val data = readData(length) return String(data, java.nio.charset.Charset.forName("ISO-8859-1")) } /** * Read Adobe Pascal-style string (UInt32 length in UTF-16 code units + UTF-16 string + 2 byte null) */ fun readAdobePascalStyleString(): String { val length = readUInt32(ByteOrder.BIG_ENDIAN).toInt() if (length == 0) return "" val stringData = mutableListOf() repeat(length) { if (position + 1 >= data.size) throw java.io.EOFException("Unexpected end of stream") stringData.add(data[position++]) stringData.add(data[position++]) } // Remove trailing null if present val result = String(stringData.toByteArray(), java.nio.charset.StandardCharsets.UTF_16BE) return result.trimEnd('\u0000') } /** * Read UTF-8 string (null-terminated) */ fun readStringUTF8NullTerminated(): String { val bytes = mutableListOf() while (position < data.size) { val b = data[position++] if (b.toInt() == 0) break bytes.add(b) } return String(bytes.toByteArray(), java.nio.charset.StandardCharsets.UTF_8) } fun seekToNextInstanceOfPattern(vararg pattern: Int) { val searchPattern = pattern.map { it.toByte() }.toByteArray() var patternIndex = 0 val startPos = position while (position < data.size) { val b = data[position].toInt().and(0xFF) position++ if (b == searchPattern[patternIndex].toInt().and(0xFF)) { patternIndex++ if (patternIndex >= searchPattern.size) { // нашли совпадение, откатываем на начало паттерна position -= searchPattern.size return } } else { patternIndex = 0 } } // если не нашли — вернём позицию на место и бросим EOF position = startPos throw java.io.EOFException("Pattern not found") } fun seekToNextInstanceOfASCII(pattern: String) { val searchPattern = pattern.toByteArray(Charsets.US_ASCII) var patternIndex = 0 val startPos = position while (position < data.size) { val b = data[position].toInt().and(0xFF) position++ if (b == searchPattern[patternIndex].toInt().and(0xFF)) { patternIndex++ if (patternIndex >= searchPattern.size) { position -= searchPattern.size return } } else { patternIndex = 0 } } position = startPos throw java.io.EOFException("Pattern not found") } /** * Read UTF-8 string with byte count */ fun readStringUTF8(byteCount: Int): String { val data = readData(byteCount) return String(data, java.nio.charset.StandardCharsets.UTF_8) } /** * Read UTF-16 string with specified length (in characters) */ fun readStringUTF16(order: ByteOrder, length: Int): String { val byteCount = length * 2 val data = readData(byteCount) val charset = if (order == ByteOrder.BIG_ENDIAN) java.nio.charset.StandardCharsets.UTF_16BE else java.nio.charset.StandardCharsets.UTF_16LE return String(data, charset) } /** * Read Pascal-style UTF-16 string (UInt16 length in characters + UTF-16 string) */ fun readPascalStringUTF16(order: ByteOrder): String { val length = readUInt16(order).toInt() if (length == 0) return "" val byteCount = length * 2 val stringData = readData(byteCount) val charset = if (order == ByteOrder.BIG_ENDIAN) java.nio.charset.StandardCharsets.UTF_16BE else java.nio.charset.StandardCharsets.UTF_16LE return String(stringData, charset) } /** * Read bytes count */ fun readBytes(count: Int): ByteArray { return readData(count) } /** * Skip bytes */ fun seek(count: Int) { if (position + count > data.size) { throw java.io.EOFException("Unexpected end of stream") } position += count } /** * Current read position */ val readPosition: Long get() = position.toLong() } /** * Helper to read text from InputStream */ fun InputStream.readText(): String { return bufferedReader().use { it.readText() } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/utils/BytesWriter.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.utils import java.io.OutputStream import java.nio.ByteBuffer /** * Writer for binary data streams */ internal class BytesWriter(private val outputStream: OutputStream) { /** * Write bytes */ fun writeData(data: ByteArray) { outputStream.write(data) } /** * Write UInt8 */ fun writeUInt8(value: UByte) { outputStream.write(value.toInt()) } /** * Write byte */ fun writeByte(value: Byte) { outputStream.write(value.toInt()) } /** * Write Int16 */ fun writeInt16(value: Short, order: ByteOrder = ByteOrder.BIG_ENDIAN) { val bb = ByteBuffer.allocate(2) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) bb.putShort(value) outputStream.write(bb.array()) } /** * Write Int32 */ fun writeInt32(value: Int, order: ByteOrder = ByteOrder.BIG_ENDIAN) { val bb = ByteBuffer.allocate(4) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) bb.putInt(value) outputStream.write(bb.array()) } /** * Write UInt16 */ fun writeUInt16(value: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN) { val bb = ByteBuffer.allocate(2) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) bb.putShort(value.toShort()) outputStream.write(bb.array()) } /** * Write UInt32 */ fun writeUInt32(value: UInt, order: ByteOrder = ByteOrder.BIG_ENDIAN) { val bb = ByteBuffer.allocate(4) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) bb.putInt(value.toInt()) outputStream.write(bb.array()) } /** * Write Float32 */ fun writeFloat32(value: Float, order: ByteOrder = ByteOrder.BIG_ENDIAN) { val bb = ByteBuffer.allocate(4) bb.order(if (order == ByteOrder.BIG_ENDIAN) java.nio.ByteOrder.BIG_ENDIAN else java.nio.ByteOrder.LITTLE_ENDIAN) bb.putFloat(value) outputStream.write(bb.array()) } /** * Write UTF-16 string (null-terminated) */ fun writeStringUTF16NullTerminated(value: String, order: ByteOrder = ByteOrder.BIG_ENDIAN) { val charset = if (order == ByteOrder.BIG_ENDIAN) java.nio.charset.StandardCharsets.UTF_16BE else java.nio.charset.StandardCharsets.UTF_16LE val bytes = value.toByteArray(charset) outputStream.write(bytes) // Write null terminator outputStream.write(0) outputStream.write(0) } /** * Write ASCII string */ fun writeStringASCII(value: String) { val bytes = value.toByteArray(java.nio.charset.StandardCharsets.US_ASCII) outputStream.write(bytes) } /** * Write Adobe Pascal-style string (UInt32 length in UTF-16 characters + UTF-16BE string) */ fun writeAdobePascalStyleString(value: String) { val utf16Bytes = value.toByteArray(java.nio.charset.StandardCharsets.UTF_16BE) val length = utf16Bytes.size / 2 // UTF-16 is 2 bytes per character writeUInt32(length.toUInt(), ByteOrder.BIG_ENDIAN) if (utf16Bytes.isNotEmpty()) { outputStream.write(utf16Bytes) } } /** * Write ASCII string with length prefix (UInt32, little endian) */ fun writeStringASCIIWithLength(value: String, order: ByteOrder = ByteOrder.LITTLE_ENDIAN) { val bytes = value.toByteArray(java.nio.charset.StandardCharsets.US_ASCII) writeUInt32(bytes.size.toUInt(), order) if (bytes.isNotEmpty()) { outputStream.write(bytes) } } /** * Write UTF-8 string (null-terminated) */ fun writeStringUTF8NullTerminated(value: String) { val bytes = value.toByteArray(java.nio.charset.StandardCharsets.UTF_8) outputStream.write(bytes) outputStream.write(0) // null terminator } /** * Write UTF-8 string with length prefix (UInt32, little endian) */ fun writeStringUTF8WithLength(value: String, order: ByteOrder = ByteOrder.LITTLE_ENDIAN) { val bytes = value.toByteArray(java.nio.charset.StandardCharsets.UTF_8) writeUInt32(bytes.size.toUInt(), order) if (bytes.isNotEmpty()) { outputStream.write(bytes) } } /** * Write pattern bytes */ fun writePattern(vararg bytes: Int) { bytes.forEach { outputStream.write(it) } } /** * Write Pascal-style UTF-16 string (UInt16 length in characters + UTF-16 string) */ fun writePascalStringUTF16(value: String, order: ByteOrder) { val charset = if (order == ByteOrder.BIG_ENDIAN) java.nio.charset.StandardCharsets.UTF_16BE else java.nio.charset.StandardCharsets.UTF_16LE val utf16Bytes = value.toByteArray(charset) val length = utf16Bytes.size / 2 // UTF-16 is 2 bytes per character if (length > 65535) { throw IllegalArgumentException("String too long for Pascal UTF-16 (max 65535 characters)") } writeUInt16(length.toUShort(), order) if (utf16Bytes.isNotEmpty()) { outputStream.write(utf16Bytes) } } /** * Write Float32 array */ fun writeFloat32(values: List, order: ByteOrder) { values.forEach { writeFloat32(it, order) } } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/utils/CSVParser.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.utils /** * Simple CSV parser */ internal object CSVParser { /** * Parse CSV text into records */ fun parse(text: String): List> { val records = mutableListOf>() val lines = text.lines() for (line in lines) { if (line.isBlank()) continue val fields = line.split(',').map { it.trim() } if (fields.isNotEmpty()) { records.add(fields) } } return records } } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/utils/HexUtils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.utils import com.t8rin.palette.ColorByteFormat import com.t8rin.palette.PaletteColor /** * Hex color utilities */ private data class Quad(val a: Long, val b: Long, val c: Long, val d: Long) /** * Extract RGBA from hex string */ internal fun extractHexRGBA(rgbHexString: String, format: ColorByteFormat): PaletteColor.RGB? { var hex = rgbHexString.lowercase().replace(Regex("[^0-9a-f]"), "") if (hex.startsWith("0x")) { hex = hex.substring(2) } val `val` = hex.toLongOrNull(16) ?: return null val hasAlpha = hex.length == 4 || hex.length == 8 val quad = when (hex.length) { 3 -> { // RGB (12-bit) val r = ((`val` shr 8) and 0xF) * 17 val g = ((`val` shr 4) and 0xF) * 17 val b = (`val` and 0xF) * 17 Quad(r, g, b, 255L) } 4 -> { // RGBA (12-bit) val r = ((`val` shr 12) and 0xF) * 17 val g = ((`val` shr 8) and 0xF) * 17 val b = ((`val` shr 4) and 0xF) * 17 val a = (`val` and 0xF) * 17 Quad(r, g, b, a) } 6 -> { // RRGGBB (24-bit) val r = (`val` shr 16) and 0xFF val g = (`val` shr 8) and 0xFF val b = `val` and 0xFF Quad(r, g, b, 255L) } 8 -> { // RRGGBBAA (32-bit) val r = (`val` shr 24) and 0xFF val g = (`val` shr 16) and 0xFF val b = (`val` shr 8) and 0xFF val a = `val` and 0xFF Quad(r, g, b, a) } else -> return null } val c0 = quad.a val c1 = quad.b val c2 = quad.c val c3 = quad.d return when (format) { ColorByteFormat.RGB -> PaletteColor.RGB( r = c0 / 255.0, g = c1 / 255.0, b = c2 / 255.0, a = 1.0 ) ColorByteFormat.BGR -> PaletteColor.RGB( r = c2 / 255.0, g = c1 / 255.0, b = c0 / 255.0, a = 1.0 ) ColorByteFormat.RGBA -> PaletteColor.RGB( r = c0 / 255.0, g = c1 / 255.0, b = c2 / 255.0, a = if (hasAlpha) c3 / 255.0 else 1.0 ) ColorByteFormat.ARGB -> PaletteColor.RGB( r = c1 / 255.0, g = c2 / 255.0, b = c3 / 255.0, a = if (hasAlpha) c0 / 255.0 else 1.0 ) ColorByteFormat.BGRA -> PaletteColor.RGB( r = c2 / 255.0, g = c1 / 255.0, b = c0 / 255.0, a = if (hasAlpha) c3 / 255.0 else 1.0 ) ColorByteFormat.ABGR -> PaletteColor.RGB( r = c3 / 255.0, g = c2 / 255.0, b = c1 / 255.0, a = if (hasAlpha) c0 / 255.0 else 1.0 ) } } /** * Generate hex string */ internal fun hexRGBString( r255: Int, g255: Int, b255: Int, a255: Int = 255, format: ColorByteFormat, hashmark: Boolean = true, uppercase: Boolean = false ): String { val prefix = if (hashmark) "#" else "" val fmt = if (uppercase) "%02X%02X%02X%02X" else "%02x%02x%02x%02x" val fmt3 = if (uppercase) "%02X%02X%02X" else "%02x%02x%02x" return when (format) { ColorByteFormat.RGB -> prefix + String.format(fmt3, r255, g255, b255) ColorByteFormat.BGR -> prefix + String.format(fmt3, b255, g255, r255) ColorByteFormat.ARGB -> prefix + String.format(fmt, a255, r255, g255, b255) ColorByteFormat.RGBA -> prefix + String.format(fmt, r255, g255, b255, a255) ColorByteFormat.ABGR -> prefix + String.format(fmt, a255, b255, g255, r255) ColorByteFormat.BGRA -> prefix + String.format(fmt, b255, g255, r255, a255) } } /** * Extension functions for PaletteColor hex support */ fun PaletteColor.hexString(format: ColorByteFormat, hashmark: Boolean, uppercase: Boolean): String { val rgb = toRgb() return hexRGBString( r255 = (rgb.rf * 255).toInt().coerceIn(0, 255), g255 = (rgb.gf * 255).toInt().coerceIn(0, 255), b255 = (rgb.bf * 255).toInt().coerceIn(0, 255), a255 = (rgb.af * 255).toInt().coerceIn(0, 255), format = format, hashmark = hashmark, uppercase = uppercase ) } /** * Extension functions for PaletteColor.RGB hex support */ fun PaletteColor.RGB.hexString( format: ColorByteFormat, hashmark: Boolean, uppercase: Boolean ): String { return hexRGBString( r255 = (rf * 255).toInt().coerceIn(0, 255), g255 = (gf * 255).toInt().coerceIn(0, 255), b255 = (bf * 255).toInt().coerceIn(0, 255), a255 = (af * 255).toInt().coerceIn(0, 255), format = format, hashmark = hashmark, uppercase = uppercase ) } ================================================ FILE: lib/palette/src/main/java/com/t8rin/palette/utils/Xml.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.utils internal fun String.xmlDecoded(): String { return this.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace(""", "\"") .replace("'", "'") } internal fun String.xmlEscaped(): String { return this.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\"", """) .replace("'", "'") } ================================================ FILE: lib/palette/src/test/java/com/t8rin/palette/coders/PaletteCoderCompatibilityTest.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.palette.coders import com.t8rin.palette.ColorGroup import com.t8rin.palette.Palette import com.t8rin.palette.PaletteColor import com.t8rin.palette.encode import org.junit.Test import java.nio.ByteBuffer import java.nio.ByteOrder import java.util.zip.CRC32 import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream import kotlin.test.assertContains import kotlin.test.assertEquals class PaletteCoderCompatibilityTest { @Test fun `krita decoder reads documented colorset structure`() { val colorsetXml = """ """.trimIndent() val data = zipOf( "mimetype" to "application/x-krita-palette".toByteArray(Charsets.US_ASCII), "colorset.xml" to colorsetXml.toByteArray(Charsets.UTF_8), "profiles.xml" to """""".toByteArray( Charsets.UTF_8 ) ) val palette = KRITAPaletteCoder().decode(data.inputStream()) assertEquals("Demo Palette", palette.name) assertEquals(1, palette.colors.size) assertEquals("Red", palette.colors.first().name) assertEquals(1, palette.groups.size) assertEquals("Group One", palette.groups.first().name) assertEquals("Green Spot", palette.groups.first().colors.first().name) assertEquals( com.t8rin.palette.ColorType.Spot, palette.groups.first().colors.first().colorType ) } @Test fun `krita encoder writes mimetype and documented xml`() { val palette = Palette( name = "Krita Export", colors = listOf(PaletteColor.rgb(1.0, 0.0, 0.0, name = "Red")), groups = listOf( ColorGroup( name = "Group One", colors = listOf(PaletteColor.cmyk(1.0, 0.0, 1.0, 0.0, name = "CMYK Color")) ) ) ) val data = KRITAPaletteCoder().encode(palette) val entries = unzip(data) assertEquals("mimetype", entries.first().first) assertEquals(ZipEntry.STORED, entries.first().second.method) assertEquals( "application/x-krita-palette", entries.first().third.toString(Charsets.US_ASCII) ) val colorsetXml = entries.first { it.first == "colorset.xml" }.third.toString(Charsets.UTF_8) assertContains(colorsetXml, "\n " ) val decoded = KRITAPaletteCoder().decode(data.inputStream()) assertEquals(2, decoded.totalColorCount) assertEquals("Krita Export", decoded.name) assertEquals("Group One", decoded.groups.first().name) } @Test fun `gimp encoder always writes version 2 header`() { val encoded = GIMPPaletteCoder().encode( Palette( colors = listOf(PaletteColor.rgb(1.0, 0.0, 0.0, name = "Primary\tRed")) ) ).toString(Charsets.UTF_8) val lines = encoded.lines() assertEquals("GIMP Palette", lines[0]) assertEquals("Name: ", lines[1]) assertEquals("Columns: 0", lines[2]) assertEquals("#Colors: 1", lines[3]) assertEquals("255\t0\t0\tPrimary Red", lines[4]) } @Test fun `basic xml decoder works without namespace aware parser`() { val xml = """ """.trimIndent() val palette = BasicXMLCoder().decode(xml.byteInputStream()) assertEquals("Basic", palette.name) assertEquals(1, palette.colors.size) assertEquals("Red", palette.colors.first().name) } @Test fun `android colors xml decoder reads plain resources`() { val xml = """ #80FF0000 """.trimIndent() val palette = AndroidColorsXMLCoder().decode(xml.byteInputStream()) assertEquals(1, palette.colors.size) assertEquals("accent", palette.colors.first().name) } @Test fun `act encoder stays within standard file size`() { val data = ACTPaletteCoder().encode( Palette( colors = listOf(PaletteColor.rgb(1.0, 0.0, 0.0, name = "Named Color")) ) ) assertEquals(772, data.size) } @Test fun `riff encoder writes little endian chunk size`() { val data = RIFFPaletteCoder().encode( Palette( colors = listOf( PaletteColor.rgb(1.0, 0.0, 0.0, name = "Red"), PaletteColor.rgb(0.0, 1.0, 0.0) ) ) ) val riffSize = ByteBuffer.wrap(data, 4, 4).order(ByteOrder.LITTLE_ENDIAN).int assertEquals(data.size - 8, riffSize) } private fun zipOf(vararg entries: Pair): ByteArray { val output = java.io.ByteArrayOutputStream() ZipOutputStream(output).use { zipOutputStream -> entries.forEachIndexed { index, (name, data) -> val entry = ZipEntry(name) if (index == 0 && name == "mimetype") { val crc = CRC32().apply { update(data) } entry.method = ZipEntry.STORED entry.size = data.size.toLong() entry.compressedSize = data.size.toLong() entry.crc = crc.value } zipOutputStream.putNextEntry(entry) zipOutputStream.write(data) zipOutputStream.closeEntry() } } return output.toByteArray() } private fun unzip(data: ByteArray): List> { val entries = mutableListOf>() ZipInputStream(data.inputStream()).use { zipInputStream -> var entry = zipInputStream.nextEntry while (entry != null) { entries.add(Triple(entry.name, entry, zipInputStream.readBytes())) entry = zipInputStream.nextEntry } } return entries } } ================================================ FILE: lib/qrose/.gitignore ================================================ /build ================================================ FILE: lib/qrose/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "io.github.alexzhirkevich" ================================================ FILE: lib/qrose/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/CachedPainter.kt ================================================ package io.github.alexzhirkevich.qrose import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.toSize import kotlin.math.ceil abstract class CachedPainter : Painter() { private var alpha = 1f private var colorFilter: ColorFilter? = null private var cachedSize: Size? = null private var cacheDrawScope = DrawCache() override fun applyAlpha(alpha: Float): Boolean { this.alpha = alpha return true } override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { this.colorFilter = colorFilter return true } abstract fun DrawScope.onCache() private val block: DrawScope.() -> Unit = { onCache() } override fun DrawScope.onDraw() { if (cachedSize != size) { cacheDrawScope.drawCachedImage( size = IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()), density = this, layoutDirection = layoutDirection, block = block ) cachedSize = size } cacheDrawScope.drawInto( target = this, alpha = alpha, colorFilter = colorFilter ) } } /** * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] * which can be drawn directly in another [DrawScope] instance. This is useful to cache * complicated drawing commands across frames especially if the content has not changed. * Additionally some drawing operations such as rendering paths are done purely in * software so it is beneficial to cache the result and render the contents * directly through a texture as done by [DrawScope.drawImage] */ private class DrawCache { var mCachedImage: ImageBitmap? = null private var cachedCanvas: Canvas? = null private var scopeDensity: Density? = null private var layoutDirection: LayoutDirection = LayoutDirection.Ltr private var size: IntSize = IntSize.Zero private val cacheScope = CanvasDrawScope() /** * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided * size. If the same size is provided across calls, the same [ImageBitmap] instance is * re-used and the contents are cleared out before drawing content in it again */ fun drawCachedImage( size: IntSize, density: Density, layoutDirection: LayoutDirection, block: DrawScope.() -> Unit ) { this.scopeDensity = density this.layoutDirection = layoutDirection var targetImage = mCachedImage var targetCanvas = cachedCanvas if (targetImage == null || targetCanvas == null || size.width > targetImage.width || size.height > targetImage.height ) { targetImage = ImageBitmap(size.width, size.height) targetCanvas = Canvas(targetImage) mCachedImage = targetImage cachedCanvas = targetCanvas } this.size = size cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) { clear() block() } targetImage.prepareToDraw() } /** * Draw the cached content into the provided [DrawScope] instance */ fun drawInto( target: DrawScope, alpha: Float = 1.0f, colorFilter: ColorFilter? = null ) { val targetImage = mCachedImage check(targetImage != null) { "drawCachedImage must be invoked first before attempting to draw the result " + "into another destination" } target.drawImage(targetImage, srcSize = size, alpha = alpha, colorFilter = colorFilter) } /** * Helper method to clear contents of the draw environment from the given bounds of the * DrawScope */ private fun DrawScope.clear() { drawRect(color = Color.Black, blendMode = BlendMode.Clear) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/Converters.kt ================================================ package io.github.alexzhirkevich.qrose import android.graphics.Bitmap import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import java.io.ByteArrayOutputStream enum class ImageFormat { PNG, JPEG, WEBP } /** * Converts [ImageBitmap] to image with desired [format] and returns its bytes. * */ fun ImageBitmap.toByteArray(format: ImageFormat = ImageFormat.PNG): ByteArray = asAndroidBitmap().run { ByteArrayOutputStream().use { compress( when (format) { ImageFormat.PNG -> Bitmap.CompressFormat.PNG ImageFormat.JPEG -> Bitmap.CompressFormat.JPEG ImageFormat.WEBP -> Bitmap.CompressFormat.WEBP }, 100, it ) it.toByteArray() } } /** * Converts [Painter] to image with desired [width], [height] and [format] and returns its bytes. * */ fun Painter.toByteArray(width: Int, height: Int, format: ImageFormat = ImageFormat.PNG): ByteArray = toImageBitmap(width, height).toByteArray(format) /** * Converts [Painter] to [ImageBitmap] with desired [width], [height], [alpha] and [colorFilter] * */ fun Painter.toImageBitmap( width: Int, height: Int, alpha: Float = 1f, colorFilter: ColorFilter? = null ): ImageBitmap { val bmp = ImageBitmap(width, height) val canvas = Canvas(bmp) CanvasDrawScope().draw( density = Density(1f, 1f), layoutDirection = LayoutDirection.Ltr, canvas = canvas, size = Size(width.toFloat(), height.toFloat()) ) { draw(this@draw.size, alpha, colorFilter) } return bmp } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/DelicateQRoseApi.kt ================================================ package io.github.alexzhirkevich.qrose @RequiresOptIn( message = "This API may negatively impact QR code functionality", level = RequiresOptIn.Level.WARNING ) annotation class DelicateQRoseApi ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrCodePainter.kt ================================================ package io.github.alexzhirkevich.qrose import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.PathOperation import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.painter.Painter import io.github.alexzhirkevich.qrose.options.Neighbors import io.github.alexzhirkevich.qrose.options.QrBrush import io.github.alexzhirkevich.qrose.options.QrBrushMode import io.github.alexzhirkevich.qrose.options.QrCodeMatrix import io.github.alexzhirkevich.qrose.options.QrColors import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrLogo import io.github.alexzhirkevich.qrose.options.QrLogoPadding import io.github.alexzhirkevich.qrose.options.QrOptions import io.github.alexzhirkevich.qrose.options.QrShapeModifier import io.github.alexzhirkevich.qrose.options.QrShapes import io.github.alexzhirkevich.qrose.options.dsl.QrOptionsBuilderScope import io.github.alexzhirkevich.qrose.options.isSpecified import io.github.alexzhirkevich.qrose.options.neighbors import io.github.alexzhirkevich.qrose.options.newPath import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel import io.github.alexzhirkevich.qrose.qrcode.QRCode import kotlin.math.roundToInt /** * Create and remember QR code painter * * @param data QR code payload * @param keys keys of the [options] block. QR code will be re-generated when any key is changed. * @param options [QrOptions] builder block * */ @Composable fun rememberQrCodePainter( data: String, vararg keys: Any?, onSuccess: () -> Unit = {}, onFailure: (Throwable) -> Unit = {}, options: QrOptionsBuilderScope.() -> Unit ): QrCodePainter = rememberQrCodePainter( data = data, options = remember(keys) { QrOptions(options) }, onFailure = onFailure, onSuccess = onSuccess ) /** * Create and remember QR code painter * * @param data QR code payload * @param options QR code styling options * */ @Composable fun rememberQrCodePainter( data: String, options: QrOptions, onSuccess: () -> Unit = {}, onFailure: (Throwable) -> Unit = {} ): QrCodePainter = remember(data, options, onFailure, onSuccess) { QrCodePainter( data = data, options = options, onFailure = onFailure, onSuccess = onSuccess ) } @Composable fun rememberQrCodePainter( data: String, shapes: QrShapes = QrShapes(), colors: QrColors = QrColors(), logo: QrLogo = QrLogo(), errorCorrectionLevel: QrErrorCorrectionLevel = QrErrorCorrectionLevel.Auto, fourEyed: Boolean = false, onFailure: (Throwable) -> Unit = {}, onSuccess: () -> Unit = {}, ): QrCodePainter = rememberQrCodePainter( data = data, options = remember(shapes, colors, logo, errorCorrectionLevel, fourEyed) { QrOptions( shapes = shapes, colors = colors, logo = logo, errorCorrectionLevel = errorCorrectionLevel, fourEyed = fourEyed ) }, onFailure = onFailure, onSuccess = onSuccess ) /** * Encodes [data] payload and renders it into the compose [Painter] using styling [options] * */ @Immutable class QrCodePainter( val data: String, val options: QrOptions = QrOptions(), val onSuccess: () -> Unit, val onFailure: (Throwable) -> Unit ) : CachedPainter() { private val initialMatrixSize: Int private val actualCodeMatrix = options.shapes.code.run { val initialMatrix = runCatching { QRCode( data = data, errorCorrectionLevel = if (options.errorCorrectionLevel == QrErrorCorrectionLevel.Auto) options.errorCorrectionLevel.fit(options).lvl else options.errorCorrectionLevel.lvl ).encode(maskPattern = options.maskPattern) }.onFailure(onFailure).onSuccess { if (it.size == 0) onFailure(IllegalArgumentException("Failed to generate QR code")) else onSuccess() }.getOrDefault(QrCodeMatrix(1)) initialMatrixSize = initialMatrix.size initialMatrix.transform() } private var codeMatrix = actualCodeMatrix override val intrinsicSize: Size = Size( codeMatrix.size.toFloat() * 10f, codeMatrix.size.toFloat() * 10f ) private val shapeIncrease = (codeMatrix.size - initialMatrixSize) / 2 private val balls = mutableListOf( 2 + shapeIncrease to 2 + shapeIncrease, 2 + shapeIncrease to initialMatrixSize - 5 + shapeIncrease, initialMatrixSize - 5 + shapeIncrease to 2 + shapeIncrease ).apply { if (options.fourEyed) this += initialMatrixSize - 5 + shapeIncrease to initialMatrixSize - 5 + shapeIncrease }.toList() private val frames = mutableListOf( shapeIncrease to shapeIncrease, shapeIncrease to initialMatrixSize - 7 + shapeIncrease, initialMatrixSize - 7 + shapeIncrease to shapeIncrease ).apply { if (options.fourEyed) { this += initialMatrixSize - 7 + shapeIncrease to initialMatrixSize - 7 + shapeIncrease } }.toList() private val shouldSeparateDarkPixels get() = options.colors.dark.mode == QrBrushMode.Separate private val shouldSeparateLightPixels get() = options.colors.light.mode == QrBrushMode.Separate private val shouldSeparateFrames get() = options.colors.frame.isSpecified || shouldSeparateDarkPixels private val shouldSeparateBalls get() = options.colors.ball.isSpecified || shouldSeparateDarkPixels override fun toString(): String { return "QrCodePainter(data = $data)" } override fun hashCode(): Int { return data.hashCode() * 31 + options.hashCode() } private val DrawScope.logoSize get() = size * options.logo.size private val DrawScope.logoPaddingSize get() = logoSize.width * (1 + options.logo.padding.size) private val DrawScope.pixelSize: Float get() = minOf(size.width, size.height) / codeMatrix.size override fun DrawScope.onCache() { draw() } private fun DrawScope.draw() { if (actualCodeMatrix.size > 1) { runCatching { drawRect( options.colors.background.brush( size = maxOf(size.width, size.height), neighbors = Neighbors.Empty ) ) val pixelSize = pixelSize prepareLogo(pixelSize) val (dark, light) = createMainElements(pixelSize) if (shouldSeparateDarkPixels || shouldSeparateLightPixels) { drawSeparatePixels(pixelSize) } if (!shouldSeparateLightPixels) { drawPath( path = light, brush = options.colors.light .brush(pixelSize * codeMatrix.size, Neighbors.Empty), ) } if (!shouldSeparateDarkPixels) { drawPath( path = dark, brush = options.colors.dark .brush(pixelSize * codeMatrix.size, Neighbors.Empty), ) } if (shouldSeparateFrames) { drawFrames(pixelSize) } if (shouldSeparateBalls) { drawBalls(pixelSize) } drawLogo() }.onFailure(onFailure) } } private fun DrawScope.drawSeparatePixels( pixelSize: Float, ) { val darkPaint = darkPaintFactory(pixelSize) val lightPaint = lightPaintFactory(pixelSize) val darkPixelPath = darkPixelPathFactory(pixelSize) val lightPixelPath = lightPixelPathFactory(pixelSize) repeat(codeMatrix.size) { i -> repeat(codeMatrix.size) inner@{ j -> if (isInsideFrameOrBall(i, j)) return@inner translate( left = i * pixelSize, top = j * pixelSize ) { if (shouldSeparateDarkPixels && codeMatrix[i, j] == QrCodeMatrix.PixelType.DarkPixel) { val n = codeMatrix.neighbors(i, j) drawPath( path = darkPixelPath.next(n), brush = darkPaint.next(n), ) } if (shouldSeparateLightPixels && codeMatrix[i, j] == QrCodeMatrix.PixelType.LightPixel) { val n = codeMatrix.neighbors(i, j) drawPath( path = lightPixelPath.next(n), brush = lightPaint.next(n), ) } } } } } private fun DrawScope.prepareLogo(pixelSize: Float) { val ps = logoPaddingSize if (options.logo.padding is QrLogoPadding.Natural) { val logoPath = options.logo.shape.newPath( size = ps, neighbors = Neighbors.Empty ).apply { translate( Offset( (size.width - ps) / 2f, (size.height - ps) / 2f, ) ) } val darkPathF = darkPixelPathFactory(pixelSize) val lightPathF = lightPixelPathFactory(pixelSize) val logoPixels = (codeMatrix.size * options.logo.size.coerceIn(0f, 1f) * (1 + options.logo.padding.size.coerceIn(0f, 1f))).roundToInt() + 1 val xRange = (codeMatrix.size - logoPixels) / 2 until (codeMatrix.size + logoPixels) / 2 val yRange = (codeMatrix.size - logoPixels) / 2 until (codeMatrix.size + logoPixels) / 2 for (x in xRange) { for (y in yRange) { val neighbors = codeMatrix.neighbors(x, y) val offset = Offset(x * pixelSize, y * pixelSize) val darkPath = darkPathF.next(neighbors).apply { translate(offset) } val lightPath = lightPathF.next(neighbors).apply { translate(offset) } if ( codeMatrix[x, y] == QrCodeMatrix.PixelType.DarkPixel && logoPath.intersects(darkPath) || codeMatrix[x, y] == QrCodeMatrix.PixelType.LightPixel && logoPath.intersects(lightPath) ) { codeMatrix[x, y] = QrCodeMatrix.PixelType.Logo } } } } } private fun DrawScope.drawLogo() { val ps = logoPaddingSize if (options.logo.padding is QrLogoPadding.Accurate) { val path = options.logo.shape.newPath( size = ps, neighbors = Neighbors.Empty ) translate( left = center.x - ps / 2, top = center.y - ps / 2 ) { drawPath(path, Color.Black, blendMode = BlendMode.Clear) } } options.logo.painter?.let { it.run { translate( left = center.x - logoSize.width / 2, top = center.y - logoSize.height / 2 ) { draw(logoSize) } } } } private fun DrawScope.drawBalls( pixelSize: Float ) { val brush by ballBrushFactory(pixelSize) val path by ballShapeFactory(pixelSize) balls.forEach { translate( it.first * pixelSize, it.second * pixelSize ) { drawPath( path = path, brush = brush, ) } } } private fun DrawScope.drawFrames( pixelSize: Float ) { val ballBrush by frameBrushFactory(pixelSize) val ballPath by frameShapeFactory(pixelSize) frames.forEach { translate( it.first * pixelSize, it.second * pixelSize ) { drawPath( path = ballPath, brush = ballBrush, ) } } } private fun createMainElements( pixelSize: Float ): Pair { val darkPath = Path().apply { fillType = PathFillType.EvenOdd } val lightPath = Path().apply { fillType = PathFillType.EvenOdd } val rotatedFramePath by frameShapeFactory(pixelSize) val rotatedBallPath by ballShapeFactory(pixelSize) val darkPixelPathFactory = darkPixelPathFactory(pixelSize) val lightPixelPathFactory = lightPixelPathFactory(pixelSize) for (x in 0 until codeMatrix.size) { for (y in 0 until codeMatrix.size) { val neighbors = codeMatrix.neighbors(x, y) when { !shouldSeparateFrames && isFrameStart(x, y) -> darkPath .addPath( path = rotatedFramePath, offset = Offset(x * pixelSize, y * pixelSize) ) !shouldSeparateBalls && isBallStart(x, y) -> darkPath .addPath( path = rotatedBallPath, offset = Offset(x * pixelSize, y * pixelSize) ) isInsideFrameOrBall(x, y) -> Unit !shouldSeparateDarkPixels && codeMatrix[x, y] == QrCodeMatrix.PixelType.DarkPixel -> darkPath .addPath( path = darkPixelPathFactory.next(neighbors), offset = Offset(x * pixelSize, y * pixelSize) ) !shouldSeparateLightPixels && codeMatrix[x, y] == QrCodeMatrix.PixelType.LightPixel -> lightPath .addPath( path = lightPixelPathFactory.next(neighbors), offset = Offset(x * pixelSize, y * pixelSize) ) } } } return darkPath to lightPath } private fun isFrameStart(x: Int, y: Int) = x - shapeIncrease == 0 && y - shapeIncrease == 0 || x - shapeIncrease == 0 && y - shapeIncrease == initialMatrixSize - 7 || x - shapeIncrease == initialMatrixSize - 7 && y - shapeIncrease == 0 || options.fourEyed && x - shapeIncrease == initialMatrixSize - 7 && y - shapeIncrease == initialMatrixSize - 7 private fun isBallStart(x: Int, y: Int) = x - shapeIncrease == 2 && y - shapeIncrease == initialMatrixSize - 5 || x - shapeIncrease == initialMatrixSize - 5 && y - shapeIncrease == 2 || x - shapeIncrease == 2 && y - shapeIncrease == 2 || options.fourEyed && x - shapeIncrease == initialMatrixSize - 5 && y - shapeIncrease == initialMatrixSize - 5 private fun isInsideFrameOrBall(x: Int, y: Int): Boolean { return x - shapeIncrease in -1..7 && y - shapeIncrease in -1..7 || x - shapeIncrease in -1..7 && y - shapeIncrease in initialMatrixSize - 8 until initialMatrixSize + 1 || x - shapeIncrease in initialMatrixSize - 8 until initialMatrixSize + 1 && y - shapeIncrease in -1..7 || options.fourEyed && x - shapeIncrease in initialMatrixSize - 8 until initialMatrixSize + 1 && y - shapeIncrease in initialMatrixSize - 8 until initialMatrixSize + 1 } private fun darkPaintFactory(pixelSize: Float) = pixelBrushFactory( brush = options.colors.dark, separate = shouldSeparateDarkPixels, pixelSize = pixelSize ) private fun lightPaintFactory(pixelSize: Float) = pixelBrushFactory( brush = options.colors.light, separate = shouldSeparateLightPixels, pixelSize = pixelSize ) private fun ballBrushFactory(pixelSize: Float) = eyeBrushFactory(brush = options.colors.ball, pixelSize = pixelSize) private fun frameBrushFactory(pixelSize: Float) = eyeBrushFactory(brush = options.colors.frame, pixelSize = pixelSize) private fun ballShapeFactory(pixelSize: Float): Lazy = rotatedPathFactory( shape = options.shapes.ball, shapeSize = pixelSize * BALL_SIZE ) private fun frameShapeFactory(pixelSize: Float): Lazy = rotatedPathFactory( shape = options.shapes.frame, shapeSize = pixelSize * FRAME_SIZE ) private fun darkPixelPathFactory(pixelSize: Float) = pixelPathFactory( shape = options.shapes.darkPixel, pixelSize = pixelSize ) private fun lightPixelPathFactory(pixelSize: Float) = pixelPathFactory( shape = options.shapes.lightPixel, pixelSize = pixelSize ) private fun pixelPathFactory( shape: QrShapeModifier, pixelSize: Float ): NeighborsBasedFactory { val path = Path() return NeighborsBasedFactory { path.rewind() path.apply { shape.run { path(pixelSize, it) } } path } } private fun rotatedPathFactory( shape: QrShapeModifier, shapeSize: Float, ): Lazy { var number = 0 val path = Path() val factory = NeighborsBasedFactory { path.apply { rewind() fillType = PathFillType.EvenOdd shape.run { path(shapeSize, it) } } } return Recreating { factory.next(Neighbors.forEyeWithNumber(number, options.fourEyed)).apply { if (options.shapes.centralSymmetry) { val angle = when (number) { 0 -> 0f 1 -> -90f 2 -> 90f else -> 180f } rotate(angle, Offset(shapeSize / 2, shapeSize / 2)) } }.also { number = (number + 1) % if (options.fourEyed) 4 else 3 } } } private fun eyeBrushFactory( brush: QrBrush, pixelSize: Float ): Lazy { val b = brush .takeIf { it.isSpecified } ?: QrBrush.Default var number = 0 val factory = { b.brush( size = pixelSize, neighbors = Neighbors.forEyeWithNumber(number, options.fourEyed) ).also { number = (number + 1) % if (options.fourEyed) 4 else 3 } } return Recreating(factory) } private fun pixelBrushFactory( brush: QrBrush, separate: Boolean, pixelSize: Float, ): NeighborsBasedFactory { val size = if (separate) pixelSize else codeMatrix.size * pixelSize val joinBrush by lazy { brush.brush(size, Neighbors.Empty) } return NeighborsBasedFactory { if (separate) brush.brush(size, it) else joinBrush } } override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false other as QrCodePainter if (data != other.data) return false if (options != other.options) return false return true } } private const val BALL_SIZE = 3 private const val FRAME_SIZE = 7 private fun Path.rotate(degree: Float, pivot: Offset) { translate(-pivot) transform(Matrix().apply { rotateZ(degree) }) translate(pivot) } private fun Path.intersects(other: Path) = Path.combine( operation = PathOperation.Intersect, path1 = this, path2 = other ).isEmpty.not() private class Recreating( private val factory: () -> T ) : Lazy { override val value: T get() = factory() override fun isInitialized(): Boolean = true } private fun Neighbors.Companion.forEyeWithNumber( number: Int, fourthEyeEnabled: Boolean ): Neighbors { return when (number) { 0 -> Neighbors(bottom = true, right = true, bottomRight = fourthEyeEnabled) 1 -> Neighbors(bottom = fourthEyeEnabled, left = true, bottomLeft = true) 2 -> Neighbors(top = true, topRight = true, right = fourthEyeEnabled) 3 -> Neighbors(top = true, left = true, topLeft = true) else -> throw IllegalStateException("Incorrect eye number: $number") } } private fun interface NeighborsBasedFactory { fun next(neighbors: Neighbors): T } private fun QrErrorCorrectionLevel.fit( options: QrOptions ): QrErrorCorrectionLevel { val logoSize = options.logo.size * (1 + options.logo.padding.size) //* // options.shapes.code.shapeSizeIncrease val hasLogo = options.logo.padding != QrLogoPadding.Empty return if (this == QrErrorCorrectionLevel.Auto) when { !hasLogo -> QrErrorCorrectionLevel.Low logoSize > .3 -> QrErrorCorrectionLevel.High logoSize in .2.. .3 && lvl < ErrorCorrectionLevel.Q -> QrErrorCorrectionLevel.MediumHigh logoSize > .05f && lvl < ErrorCorrectionLevel.M -> QrErrorCorrectionLevel.Medium else -> this } else this } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/QrData.kt ================================================ @file:Suppress("UnusedReceiverParameter", "unused") package io.github.alexzhirkevich.qrose object QrData fun QrData.text(text: String): String = text fun QrData.phone(phoneNumber: String): String = "TEL:$phoneNumber" fun QrData.email( email: String, copyTo: String? = null, subject: String? = null, body: String? = null ): String = buildString { append("mailto:$email") if (listOf(copyTo, subject, body).any { it.isNullOrEmpty().not() }) { append("?") } val queries = buildList { if (copyTo.isNullOrEmpty().not()) { add("cc=$copyTo") } if (subject.isNullOrEmpty().not()) { add("subject=${escape(subject)}") } if (body.isNullOrEmpty().not()) { add("body=${escape(body)}") } } append(queries.joinToString(separator = "&")) } fun QrData.sms( phoneNumber: String, subject: String, isMMS: Boolean ): String = "${if (isMMS) "MMS" else "SMS"}:" + "$phoneNumber${if (subject.isNotEmpty()) ":$subject" else ""}" fun QrData.meCard( name: String? = null, address: String? = null, phoneNumber: String? = null, email: String? = null, ): String = buildString { append("MECARD:") if (name != null) append("N:$name;") if (address != null) append("ADR:$address;") if (phoneNumber != null) append("TEL:$phoneNumber;") if (email != null) append("EMAIL:$email;") append(";") } fun QrData.vCard( name: String? = null, company: String? = null, title: String? = null, phoneNumber: String? = null, email: String? = null, address: String? = null, website: String? = null, note: String? = null, ): String = buildString { append("BEGIN:VCARD\n") append("VERSION:3.0\n") if (name != null) append("N:$name\n") if (company != null) append("ORG:$company\n") if (title != null) append("TITLE$title\n") if (phoneNumber != null) append("TEL:$phoneNumber\n") if (website != null) append("URL:$website\n") if (email != null) append("EMAIL:$email\n") if (address != null) append("ADR:$address\n") if (note != null) { append("NOTE:$note\n") } append("END:VCARD") } fun QrData.bizCard( firstName: String? = null, secondName: String? = null, job: String? = null, company: String? = null, address: String? = null, phone: String? = null, email: String? = null, ): String = buildString { append("BIZCARD:") if (firstName != null) append("N:$firstName;") if (secondName != null) append("X:$secondName;") if (job != null) append("T:$job;") if (company != null) append("C:$company;") if (address != null) append("A:$address;") if (phone != null) append("B:$phone;") if (email != null) append("E:$email;") append(";") } fun QrData.wifi( authentication: String? = null, ssid: String? = null, psk: String? = null, hidden: Boolean = false, ): String = buildString { append("WIFI:") if (ssid != null) append("S:${escape(ssid)};") if (authentication != null) append("T:${authentication};") if (psk != null) append("P:${escape(psk)};") append("H:$hidden;") } fun QrData.enterpriseWifi( ssid: String? = null, psk: String? = null, hidden: Boolean = false, user: String? = null, eap: String? = null, phase: String? = null, ): String = buildString { append("WIFI:") if (ssid != null) append("S:${escape(ssid)};") if (user != null) append("U:${escape(user)};") if (psk != null) append("P:${escape(psk)};") if (eap != null) append("E:${escape(eap)};") if (phase != null) append("PH:${escape(phase)};") append("H:$hidden;") } fun QrData.event( uid: String? = null, stamp: String? = null, organizer: String? = null, start: String? = null, end: String? = null, summary: String? = null, ): String = buildString { append("BEGIN:VEVENT\n") if (uid != null) append("UID:$uid\n") if (stamp != null) append("DTSTAMP:$stamp\n") if (organizer != null) append("ORGANIZER:$organizer\n") if (start != null) append("DTSTART:$start\n") if (end != null) append("DTEND:$end\n") if (summary != null) append("SUMMARY:$summary\n") append("END:VEVENT") } fun QrData.location( lat: Float, lon: Float ): String = "GEO:$lat,$lon" private fun escape(text: String): String { return text.replace("\\", "\\\\") .replace(",", "\\,") .replace(";", "\\;") .replace(".", "\\.") .replace("\"", "\\\"") .replace("'", "\\'") } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned /** * Single dimension barcode encoder. * */ interface BarcodeEncoder { /** * Encodes string [contents] into barcode [BooleanArray] where 1 is a black bar and 0 is a white bar. * * Illegal contents can throw exceptions * */ fun encode(contents: String): BooleanArray } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt ================================================ package io.github.alexzhirkevich.qrose.oned import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.Painter import io.github.alexzhirkevich.qrose.CachedPainter typealias BarcodePathBuilder = (size: Size, code: BooleanArray) -> Path /** * Remember barcode painter * * @param data code contents. Invalid contents can throw an exception * @param type barcode type * @param brush code brush * @param onError called when input content is invalid * @param builder build code path using painter size and encoded boolean list * * @see BarcodePainter * */ @Composable @Stable fun rememberBarcodePainter( data: String, type: BarcodeType, brush: Brush = SolidColor(Color.Black), onError: (Throwable) -> Painter = { throw it }, builder: BarcodePathBuilder = ::defaultBarcodeBuilder ): Painter { val updatedBuilder by rememberUpdatedState(builder) return remember(data, brush) { runCatching { BarcodePainter( data = data, type = type, brush = brush, builder = { size, code -> updatedBuilder(size, code) }, ) }.getOrElse(onError) } } /** * Create barcode painter * * @param code encoded barcode data * @param brush code brush * @param builder build code path using painter size and encoded boolean list * * @see rememberBarcodePainter * */ @Immutable class BarcodePainter( private val code: BooleanArray, private val brush: Brush = SolidColor(Color.Black), private val builder: BarcodePathBuilder = ::defaultBarcodeBuilder ) : CachedPainter() { /** * Create barcode painter * * @param data code contents. Invalid contents can throw an exception * @param type barcode type * @param brush code brush * @param builder build code path using painter size and encoded boolean list * * @see rememberBarcodePainter * */ constructor( data: String, type: BarcodeType, brush: Brush = SolidColor(Color.Black), builder: BarcodePathBuilder = ::defaultBarcodeBuilder ) : this( code = type.encoder.encode(data), brush = brush, builder = builder ) override fun DrawScope.onCache() { drawPath( path = builder(size, code), brush = brush ) } override val intrinsicSize: Size = Size( width = 6f * code.size, height = 120f ) } @PublishedApi @Stable internal fun defaultBarcodeBuilder(size: Size, data: BooleanArray): Path = Path().apply { val width = size.width / data.size data.forEachIndexed { i, b -> if (b) { addRect( Rect( left = i * width, top = 0f, right = (i + 1) * width, bottom = size.height ) ) } } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt ================================================ package io.github.alexzhirkevich.qrose.oned enum class BarcodeType(val encoder: BarcodeEncoder) { Codabar(CodabarEncoder), Code39(Code39Encoder), Code93(Code93Encoder), Code128(Code128Encoder), EAN8(CodeEAN8Encoder), EAN13(CodeEAN13Encoder), ITF(CodeITFEncoder), UPCA(CodeUPCAEncoder), UPCE(CodeUPCEEncoder) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodabarEncoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object CodabarEncoder : BarcodeEncoder { override fun encode(contents: String): BooleanArray { var actualContents = contents if (actualContents.length < 2) { // Can't have a start/end guard, so tentatively add default guards actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD } else { // Verify input and calculate decoded length. val firstChar = actualContents[0].uppercaseChar() val lastChar = actualContents[actualContents.length - 1].uppercaseChar() val startsNormal: Boolean = firstChar in START_END_CHARS val endsNormal: Boolean = lastChar in START_END_CHARS val startsAlt: Boolean = firstChar in ALT_START_END_CHARS val endsAlt: Boolean = lastChar in ALT_START_END_CHARS if (startsNormal) { if (!endsNormal) { throw IllegalArgumentException("Invalid start/end guards: $actualContents") } // else already has valid start/end } else if (startsAlt) { if (!endsAlt) { throw IllegalArgumentException("Invalid start/end guards: $actualContents") } // else already has valid start/end } else { // Doesn't start with a guard if (endsNormal || endsAlt) { throw IllegalArgumentException("Invalid start/end guards: $actualContents") } // else doesn't end with guard either, so add a default actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD } } // The start character and the end character are decoded to 10 length each. var resultLength = 20 for (i in 1 until actualContents.length - 1) { resultLength += if (actualContents[i].isDigit() || actualContents[i] == '-' || actualContents[i] == '$') { 9 } else if (actualContents[i] in CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED) { 10 } else { throw IllegalArgumentException("Cannot encode : '" + actualContents[i] + '\'') } } // A blank is placed between each character. resultLength += actualContents.length - 1 val result = BooleanArray(resultLength) var position = 0 for (index in actualContents.indices) { var c = actualContents[index].uppercaseChar() if (index == 0 || index == actualContents.length - 1) { // The start/end chars are not in the CodaBarReader.ALPHABET. when (c) { 'T' -> c = 'A' 'N' -> c = 'B' '*' -> c = 'C' 'E' -> c = 'D' } } var code = 0 for (i in ALPHABET.indices) { // Found any, because I checked above. if (c == ALPHABET[i]) { code = CHARACTER_ENCODINGS[i] break } } var color = true var counter = 0 var bit = 0 while (bit < 7) { // A character consists of 7 digit. result[position] = color position++ if (code shr 6 - bit and 1 == 0 || counter == 1) { color = !color // Flip the color. bit++ counter = 0 } else { counter++ } } if (index < actualContents.length - 1) { result[position] = false position++ } } return result } private val START_END_CHARS = charArrayOf('A', 'B', 'C', 'D') private val ALT_START_END_CHARS = charArrayOf('T', 'N', '*', 'E') private val CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = charArrayOf('/', ':', '+', '.') private val DEFAULT_GUARD = START_END_CHARS[0] private const val ALPHABET_STRING = "0123456789-$:/.+ABCD" private val ALPHABET = ALPHABET_STRING.toCharArray() /** * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. */ private val CHARACTER_ENCODINGS = intArrayOf( 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E ) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object Code128Encoder : BarcodeEncoder { // Results of minimal lookahead for code C private enum class CType { UNCODABLE, ONE_DIGIT, TWO_DIGITS, FNC_1 } override fun encode(contents: String): BooleanArray { return encode( contents = contents, compact = true, codeSet = null ) } fun encode( contents: String, compact: Boolean = true, codeSet: Code128Type? = null ): BooleanArray { val forcedCodeSet = check(contents, codeSet) return if (compact) { MinimalEncoder().encode(contents) } else { encodeFast( contents, forcedCodeSet ) } } /** * Encodes minimally using Divide-And-Conquer with Memoization */ private class MinimalEncoder { private enum class Charset { A, B, C, NONE } private enum class Latch { A, B, C, SHIFT, NONE } private var memoizedCost: Array? = null private var minPath: Array>? = null fun encode(contents: String): BooleanArray { memoizedCost = Array(4) { IntArray(contents.length) } minPath = Array(4) { arrayOfNulls( contents.length ) } encode(contents, Charset.NONE, 0) val patterns: MutableCollection = ArrayList() val checkSum = intArrayOf(0) val checkWeight = intArrayOf(1) val length = contents.length var charset = Charset.NONE var i = 0 while (i < length) { val latch = minPath!![charset.ordinal][i] when (latch) { Latch.A -> { charset = Charset.A addPattern( patterns, if (i == 0) CODE_START_A else CODE_CODE_A, checkSum, checkWeight, i ) } Latch.B -> { charset = Charset.B addPattern( patterns, if (i == 0) CODE_START_B else CODE_CODE_B, checkSum, checkWeight, i ) } Latch.C -> { charset = Charset.C addPattern( patterns, if (i == 0) CODE_START_C else CODE_CODE_C, checkSum, checkWeight, i ) } Latch.SHIFT -> addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i) else -> {} } if (charset == Charset.C) { if (contents[i] == ESCAPE_FNC_1) { addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i) } else { addPattern( patterns, contents.substring(i, i + 2).toInt(), checkSum, checkWeight, i ) require(i + 1 < length) //the algorithm never leads to a single trailing digit in character set C if (i + 1 < length) { i++ } } } else { // charset A or B var patternIndex: Int patternIndex = when (contents[i]) { ESCAPE_FNC_1 -> CODE_FNC_1 ESCAPE_FNC_2 -> CODE_FNC_2 ESCAPE_FNC_3 -> CODE_FNC_3 ESCAPE_FNC_4 -> if (charset == Charset.A && latch != Latch.SHIFT || charset == Charset.B && latch == Latch.SHIFT ) { CODE_FNC_4_A } else { CODE_FNC_4_B } else -> contents[i].code - ' '.code } if ((charset == Charset.A && latch != Latch.SHIFT || charset == Charset.B && latch == Latch.SHIFT) && patternIndex < 0 ) { patternIndex += '`'.code } addPattern(patterns, patternIndex, checkSum, checkWeight, i) } i++ } memoizedCost = null minPath = null return produceResult(patterns, checkSum[0]) } private fun canEncode(contents: CharSequence, charset: Charset, position: Int): Boolean { val c = contents[position] return when (charset) { Charset.A -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || A.indexOf( c ) >= 0 Charset.B -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || B.indexOf( c ) >= 0 Charset.C -> c == ESCAPE_FNC_1 || position + 1 < contents.length && isDigit(c) && isDigit(contents[position + 1]) else -> false } } /** * Encode the string starting at position position starting with the character set charset */ private fun encode(contents: CharSequence, charset: Charset, position: Int): Int { require(position < contents.length) val mCost = memoizedCost!![charset.ordinal][position] if (mCost > 0) { return mCost } var minCost = Int.MAX_VALUE var minLatch: Latch? = Latch.NONE val atEnd = position + 1 >= contents.length val sets = arrayOf(Charset.A, Charset.B) for (i in 0..1) { if (canEncode(contents, sets[i], position)) { var cost = 1 var latch = Latch.NONE if (charset != sets[i]) { cost++ latch = Latch.valueOf( sets[i].toString() ) } if (!atEnd) { cost += encode(contents, sets[i], position + 1) } if (cost < minCost) { minCost = cost minLatch = latch } cost = 1 if (charset == sets[(i + 1) % 2]) { cost++ latch = Latch.SHIFT if (!atEnd) { cost += encode(contents, charset, position + 1) } if (cost < minCost) { minCost = cost minLatch = latch } } } } if (canEncode(contents, Charset.C, position)) { var cost = 1 var latch = Latch.NONE if (charset != Charset.C) { cost++ latch = Latch.C } val advance = if (contents[position] == ESCAPE_FNC_1) 1 else 2 if (position + advance < contents.length) { cost += encode(contents, Charset.C, position + advance) } if (cost < minCost) { minCost = cost minLatch = latch } } if (minCost == Int.MAX_VALUE) { throw IllegalArgumentException("Bad character in input: ASCII value=" + contents[position].code) } memoizedCost!![charset.ordinal][position] = minCost minPath!![charset.ordinal][position] = minLatch return minCost } companion object { const val A = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" + "\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" + "\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" + "\u00FF" const val B = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" + "stuvwxyz{|}~\u007F\u00FF" private const val CODE_SHIFT = 98 private fun addPattern( patterns: MutableCollection, patternIndex: Int, checkSum: IntArray, checkWeight: IntArray, position: Int, ) { patterns.add(CODE_PATTERNS.get(patternIndex)) if (position != 0) { checkWeight[0]++ } checkSum[0] += patternIndex * checkWeight[0] } private fun isDigit(c: Char): Boolean { return c in '0'..'9' } } } private const val CODE_START_A = 103 private const val CODE_START_B = 104 private const val CODE_START_C = 105 internal const val CODE_CODE_A = 101 internal const val CODE_CODE_B = 100 internal const val CODE_CODE_C = 99 private const val CODE_STOP = 106 // Dummy characters used to specify control characters in input private const val ESCAPE_FNC_1 = '\u00f1' private const val ESCAPE_FNC_2 = '\u00f2' private const val ESCAPE_FNC_3 = '\u00f3' private const val ESCAPE_FNC_4 = '\u00f4' private const val CODE_FNC_1 = 102 // Code A, Code B, Code C private const val CODE_FNC_2 = 97 // Code A, Code B private const val CODE_FNC_3 = 96 // Code A, Code B private const val CODE_FNC_4_A = 101 // Code A private const val CODE_FNC_4_B = 100 // Code B private fun check(contents: String, codeSet: Code128Type?): Int { // Check content val length = contents.length for (i in 0 until length) { val c = contents[i] when (c) { ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4 -> {} else -> if (c.code > 127) { // no full Latin-1 character set available at the moment // shift and manual code change are not supported throw IllegalArgumentException("Bad character in input: ASCII value=" + c.code) } } when (codeSet) { Code128Type.A -> // allows no ascii above 95 (no lower caps, no special symbols) if (c.code in 96..127) { throw IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + c.code) } Code128Type.B -> // allows no ascii below 32 (terminal symbols) if (c.code < 32) { throw IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + c.code) } Code128Type.C -> // allows only numbers and no FNC 2/3/4 if (c.code < 48 || c.code in 58..127 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) { throw IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + c.code) } else -> {} } } return codeSet?.v ?: -1 } private fun encodeFast(contents: String, forcedCodeSet: Int): BooleanArray { val length = contents.length val patterns: MutableCollection = ArrayList() // temporary storage for patterns var checkSum = 0 var checkWeight = 1 var codeSet = 0 // selected code (CODE_CODE_B or CODE_CODE_C) var position = 0 // position in contents while (position < length) { //Select code to use var newCodeSet: Int newCodeSet = if (forcedCodeSet == -1) { chooseCode(contents, position, codeSet) } else { forcedCodeSet } //Get the pattern index var patternIndex: Int if (newCodeSet == codeSet) { // Encode the current character // First handle escapes when (contents[position]) { ESCAPE_FNC_1 -> patternIndex = CODE_FNC_1 ESCAPE_FNC_2 -> patternIndex = CODE_FNC_2 ESCAPE_FNC_3 -> patternIndex = CODE_FNC_3 ESCAPE_FNC_4 -> patternIndex = if (codeSet == CODE_CODE_A) { CODE_FNC_4_A } else { CODE_FNC_4_B } else -> when (codeSet) { CODE_CODE_A -> { patternIndex = contents[position].code - ' '.code if (patternIndex < 0) { // everything below a space character comes behind the underscore in the code patterns table patternIndex += '`'.code } } CODE_CODE_B -> patternIndex = contents[position].code - ' '.code else -> { // CODE_CODE_C if (position + 1 == length) { // this is the last character, but the encoding is C, which always encodes two characers throw IllegalArgumentException("Bad number of characters for digit only encoding.") } patternIndex = contents.substring(position, position + 2).toInt() position++ // Also incremented below } } } position++ } else { // Should we change the current code? // Do we have a code set? patternIndex = if (codeSet == 0) { // No, we don't have a code set when (newCodeSet) { CODE_CODE_A -> CODE_START_A CODE_CODE_B -> CODE_START_B else -> CODE_START_C } } else { // Yes, we have a code set newCodeSet } codeSet = newCodeSet } // Get the pattern patterns.add(CODE_PATTERNS[patternIndex]) // Compute checksum checkSum += patternIndex * checkWeight if (position != 0) { checkWeight++ } } return produceResult(patterns, checkSum) } fun produceResult(patterns: MutableCollection, checkSum: Int): BooleanArray { // Compute and append checksum val checkSumMod = checkSum % 103 if (checkSumMod < 0) { throw IllegalArgumentException("Unable to compute a valid input checksum") } patterns.add(CODE_PATTERNS[checkSumMod]) // Append stop code patterns.add(CODE_PATTERNS[CODE_STOP]) // Compute code width var codeWidth = 0 for (pattern in patterns) { for (width in pattern) { codeWidth += width } } // Compute result val result = BooleanArray(codeWidth) var pos = 0 for (pattern in patterns) { pos += appendPattern(result, pos, pattern, true) } return result } private fun findCType(value: CharSequence, start: Int): CType { val last = value.length if (start >= last) { return CType.UNCODABLE } var c = value[start] if (c == ESCAPE_FNC_1) { return CType.FNC_1 } if (c < '0' || c > '9') { return CType.UNCODABLE } if (start + 1 >= last) { return CType.ONE_DIGIT } c = value[start + 1] return if (c < '0' || c > '9') { CType.ONE_DIGIT } else CType.TWO_DIGITS } private fun chooseCode(value: CharSequence, start: Int, oldCode: Int): Int { var lookahead = findCType(value, start) if (lookahead == CType.ONE_DIGIT) { return if (oldCode == CODE_CODE_A) { CODE_CODE_A } else CODE_CODE_B } if (lookahead == CType.UNCODABLE) { if (start < value.length) { val c = value[start] if (c < ' ' || oldCode == CODE_CODE_A && (c < '`' || c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)) { // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 return CODE_CODE_A } } return CODE_CODE_B // no choice } if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) { return CODE_CODE_A } if (oldCode == CODE_CODE_C) { // can continue in code C return CODE_CODE_C } if (oldCode == CODE_CODE_B) { if (lookahead == CType.FNC_1) { return CODE_CODE_B // can continue in code B } // Seen two consecutive digits, see what follows lookahead = findCType(value, start + 2) if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) { return CODE_CODE_B // not worth switching now } if (lookahead == CType.FNC_1) { // two digits, then FNC_1... lookahead = findCType(value, start + 3) return if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch CODE_CODE_C } else { CODE_CODE_B // otherwise not worth switching } } // At this point, there are at least 4 consecutive digits. // Look ahead to choose whether to switch now or on the next round. var index = start + 4 while (findCType(value, index).also { lookahead = it } == CType.TWO_DIGITS) { index += 2 } return if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later CODE_CODE_B } else CODE_CODE_C // even number of digits, switch now } // Here oldCode == 0, which means we are choosing the initial code if (lookahead == CType.FNC_1) { // ignore FNC_1 lookahead = findCType(value, start + 1) } return if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C CODE_CODE_C } else CODE_CODE_B } } internal fun appendPattern( target: BooleanArray, pos: Int, pattern: IntArray, startColor: Boolean, ): Int { var pos = pos var color = startColor var numAdded = 0 for (len in pattern) { for (j in 0 until len) { target[pos++] = color } numAdded += len color = !color // flip color after each segment } return numAdded } private val CODE_PATTERNS by lazy { arrayOf( intArrayOf(2, 1, 2, 2, 2, 2), intArrayOf(2, 2, 2, 1, 2, 2), intArrayOf(2, 2, 2, 2, 2, 1), intArrayOf(1, 2, 1, 2, 2, 3), intArrayOf(1, 2, 1, 3, 2, 2), intArrayOf(1, 3, 1, 2, 2, 2), intArrayOf(1, 2, 2, 2, 1, 3), intArrayOf(1, 2, 2, 3, 1, 2), intArrayOf(1, 3, 2, 2, 1, 2), intArrayOf(2, 2, 1, 2, 1, 3), intArrayOf(2, 2, 1, 3, 1, 2), intArrayOf(2, 3, 1, 2, 1, 2), intArrayOf(1, 1, 2, 2, 3, 2), intArrayOf(1, 2, 2, 1, 3, 2), intArrayOf(1, 2, 2, 2, 3, 1), intArrayOf(1, 1, 3, 2, 2, 2), intArrayOf(1, 2, 3, 1, 2, 2), intArrayOf(1, 2, 3, 2, 2, 1), intArrayOf(2, 2, 3, 2, 1, 1), intArrayOf(2, 2, 1, 1, 3, 2), intArrayOf(2, 2, 1, 2, 3, 1), intArrayOf(2, 1, 3, 2, 1, 2), intArrayOf(2, 2, 3, 1, 1, 2), intArrayOf(3, 1, 2, 1, 3, 1), intArrayOf(3, 1, 1, 2, 2, 2), intArrayOf(3, 2, 1, 1, 2, 2), intArrayOf(3, 2, 1, 2, 2, 1), intArrayOf(3, 1, 2, 2, 1, 2), intArrayOf(3, 2, 2, 1, 1, 2), intArrayOf(3, 2, 2, 2, 1, 1), intArrayOf(2, 1, 2, 1, 2, 3), intArrayOf(2, 1, 2, 3, 2, 1), intArrayOf(2, 3, 2, 1, 2, 1), intArrayOf(1, 1, 1, 3, 2, 3), intArrayOf(1, 3, 1, 1, 2, 3), intArrayOf(1, 3, 1, 3, 2, 1), intArrayOf(1, 1, 2, 3, 1, 3), intArrayOf(1, 3, 2, 1, 1, 3), intArrayOf(1, 3, 2, 3, 1, 1), intArrayOf(2, 1, 1, 3, 1, 3), intArrayOf(2, 3, 1, 1, 1, 3), intArrayOf(2, 3, 1, 3, 1, 1), intArrayOf(1, 1, 2, 1, 3, 3), intArrayOf(1, 1, 2, 3, 3, 1), intArrayOf(1, 3, 2, 1, 3, 1), intArrayOf(1, 1, 3, 1, 2, 3), intArrayOf(1, 1, 3, 3, 2, 1), intArrayOf(1, 3, 3, 1, 2, 1), intArrayOf(3, 1, 3, 1, 2, 1), intArrayOf(2, 1, 1, 3, 3, 1), intArrayOf(2, 3, 1, 1, 3, 1), intArrayOf(2, 1, 3, 1, 1, 3), intArrayOf(2, 1, 3, 3, 1, 1), intArrayOf(2, 1, 3, 1, 3, 1), intArrayOf(3, 1, 1, 1, 2, 3), intArrayOf(3, 1, 1, 3, 2, 1), intArrayOf(3, 3, 1, 1, 2, 1), intArrayOf(3, 1, 2, 1, 1, 3), intArrayOf(3, 1, 2, 3, 1, 1), intArrayOf(3, 3, 2, 1, 1, 1), intArrayOf(3, 1, 4, 1, 1, 1), intArrayOf(2, 2, 1, 4, 1, 1), intArrayOf(4, 3, 1, 1, 1, 1), intArrayOf(1, 1, 1, 2, 2, 4), intArrayOf(1, 1, 1, 4, 2, 2), intArrayOf(1, 2, 1, 1, 2, 4), intArrayOf(1, 2, 1, 4, 2, 1), intArrayOf(1, 4, 1, 1, 2, 2), intArrayOf(1, 4, 1, 2, 2, 1), intArrayOf(1, 1, 2, 2, 1, 4), intArrayOf(1, 1, 2, 4, 1, 2), intArrayOf(1, 2, 2, 1, 1, 4), intArrayOf(1, 2, 2, 4, 1, 1), intArrayOf(1, 4, 2, 1, 1, 2), intArrayOf(1, 4, 2, 2, 1, 1), intArrayOf(2, 4, 1, 2, 1, 1), intArrayOf(2, 2, 1, 1, 1, 4), intArrayOf(4, 1, 3, 1, 1, 1), intArrayOf(2, 4, 1, 1, 1, 2), intArrayOf(1, 3, 4, 1, 1, 1), intArrayOf(1, 1, 1, 2, 4, 2), intArrayOf(1, 2, 1, 1, 4, 2), intArrayOf(1, 2, 1, 2, 4, 1), intArrayOf(1, 1, 4, 2, 1, 2), intArrayOf(1, 2, 4, 1, 1, 2), intArrayOf(1, 2, 4, 2, 1, 1), intArrayOf(4, 1, 1, 2, 1, 2), intArrayOf(4, 2, 1, 1, 1, 2), intArrayOf(4, 2, 1, 2, 1, 1), intArrayOf(2, 1, 2, 1, 4, 1), intArrayOf(2, 1, 4, 1, 2, 1), intArrayOf(4, 1, 2, 1, 2, 1), intArrayOf(1, 1, 1, 1, 4, 3), intArrayOf(1, 1, 1, 3, 4, 1), intArrayOf(1, 3, 1, 1, 4, 1), intArrayOf(1, 1, 4, 1, 1, 3), intArrayOf(1, 1, 4, 3, 1, 1), intArrayOf(4, 1, 1, 1, 1, 3), intArrayOf(4, 1, 1, 3, 1, 1), intArrayOf(1, 1, 3, 1, 4, 1), intArrayOf(1, 1, 4, 1, 3, 1), intArrayOf(3, 1, 1, 1, 4, 1), intArrayOf(4, 1, 1, 1, 3, 1), intArrayOf(2, 1, 1, 4, 1, 2), intArrayOf(2, 1, 1, 2, 1, 4), intArrayOf(2, 1, 1, 2, 3, 2), intArrayOf(2, 3, 3, 1, 1, 1, 2) ) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt ================================================ package io.github.alexzhirkevich.qrose.oned import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.painter.Painter /** * Code 128 barcode painter * * @param brush code brush * @param compact Specifies whether to use compact mode for Code-128 code. * This can yield slightly smaller bar codes. This option and [forceCodeSet] are mutually exclusive. * @param forceCodeSet Forces which encoding will be used. This option and [compact] are mutually exclusive. * @param onError called when input content is invalid. * @param builder build code path using painter size and encoded boolean list. * * @see Code128Painter * */ @Composable internal fun rememberCode128Painter( data: String, brush: Brush = SolidColor(Color.Black), compact: Boolean = true, forceCodeSet: Code128Type? = null, onError: (Throwable) -> Painter = { throw it }, builder: BarcodePathBuilder = ::defaultBarcodeBuilder ): Painter { val updatedBuilder by rememberUpdatedState(builder) return remember(data, brush, forceCodeSet) { runCatching { Code128Painter( data = data, brush = brush, compact = compact, codeSet = forceCodeSet, builder = { size, code -> updatedBuilder(size, code) }, ) }.getOrElse(onError) } } enum class Code128Type(internal val v: Int) { A(Code128Encoder.CODE_CODE_A), B(Code128Encoder.CODE_CODE_B), C(Code128Encoder.CODE_CODE_C) } @Stable fun Code128Painter( data: String, brush: Brush = SolidColor(Color.Black), compact: Boolean = true, codeSet: Code128Type? = null, builder: BarcodePathBuilder = ::defaultBarcodeBuilder ) = BarcodePainter( code = Code128Encoder.encode(data, compact, codeSet), brush = brush, builder = builder ) ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object Code39Encoder : BarcodeEncoder { override fun encode(contents: String): BooleanArray { var actualContent = contents var length = actualContent.length if (length > 80) { throw IllegalArgumentException( "Requested contents should be less than 80 digits long, but got $length" ) } for (i in 0 until length) { val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) if (indexInString < 0) { actualContent = tryToConvertToExtendedMode(actualContent) length = actualContent.length if (length > 80) { throw IllegalArgumentException( "Requested contents should be less than 80 digits long, but got " + length + " (extended full ASCII mode)" ) } break } } val widths = IntArray(9) val codeWidth = 24 + 1 + (13 * length) val result = BooleanArray(codeWidth) toIntArray(ASTERISK_ENCODING, widths) var pos = appendPattern(result, 0, widths, true) val narrowWhite = intArrayOf(1) pos += appendPattern(result, pos, narrowWhite, false) //append next character to byte matrix for (i in 0 until length) { val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) toIntArray(CHARACTER_ENCODINGS[indexInString], widths) pos += appendPattern(result, pos, widths, true) pos += appendPattern(result, pos, narrowWhite, false) } toIntArray(ASTERISK_ENCODING, widths) appendPattern(result, pos, widths, true) return result } const val ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. \$/+%" val CHARACTER_ENCODINGS by lazy { intArrayOf( 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 // 0-9 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J // A-J 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T // K-T 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$ // U-$ 0x0A2, 0x08A, 0x02A // /-% // /-% ) } val ASTERISK_ENCODING = 0x094 private fun toIntArray(a: Int, toReturn: IntArray) { for (i in 0..8) { val temp = a and (1 shl (8 - i)) toReturn[i] = if (temp == 0) 1 else 2 } } private fun tryToConvertToExtendedMode(contents: String): String { val length = contents.length val extendedContent = StringBuilder() for (i in 0 until length) { val character = contents[i] when (character) { '\u0000' -> extendedContent.append("%U") ' ', '-', '.' -> extendedContent.append(character) '@' -> extendedContent.append("%V") '`' -> extendedContent.append("%W") else -> if (character.code <= 26) { extendedContent.append('$') extendedContent.append(('A'.code + (character.code - 1)).toChar()) } else if (character < ' ') { extendedContent.append('%') extendedContent.append(('A'.code + (character.code - 27)).toChar()) } else if ((character <= ',') || (character == '/') || (character == ':')) { extendedContent.append('/') extendedContent.append(('A'.code + (character.code - 33)).toChar()) } else if (character <= '9') { extendedContent.append(('0'.code + (character.code - 48)).toChar()) } else if (character <= '?') { extendedContent.append('%') extendedContent.append(('F'.code + (character.code - 59)).toChar()) } else if (character <= 'Z') { extendedContent.append(('A'.code + (character.code - 65)).toChar()) } else if (character <= '_') { extendedContent.append('%') extendedContent.append(('K'.code + (character.code - 91)).toChar()) } else if (character <= 'z') { extendedContent.append('+') extendedContent.append(('A'.code + (character.code - 97)).toChar()) } else if (character.code <= 127) { extendedContent.append('%') extendedContent.append(('P'.code + (character.code - 123)).toChar()) } else { throw IllegalArgumentException( "Requested content contains a non-encodable character: '" + contents[i] + "'" ) } } } return extendedContent.toString() } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object Code93Encoder : BarcodeEncoder { override fun encode(contents: String): BooleanArray { var actualContents = contents var length = actualContents.length if (length > 80) { throw IllegalArgumentException( "Requested contents should be less than 80 digits long, but got $length" ) } for (i in 0 until length) { val indexInString: Int = Code39Encoder.ALPHABET_STRING.indexOf(actualContents[i]) if (indexInString < 0) { actualContents = tryToConvertToExtendedMode(actualContents) length = actualContents.length if (length > 80) { throw IllegalArgumentException( "Requested contents should be less than 80 digits long, but got " + length + " (extended full ASCII mode)" ) } break } } val widths = IntArray(9) val codeWidth = 24 + 1 + (13 * length) val result = BooleanArray(codeWidth) toIntArray(Code39Encoder.ASTERISK_ENCODING, widths) var pos = appendPattern(result, 0, widths, true) val narrowWhite = intArrayOf(1) pos += appendPattern(result, pos, narrowWhite, false) //append next character to byte matrix for (i in 0 until length) { val indexInString: Int = Code39Encoder.ALPHABET_STRING.indexOf(actualContents[i]) toIntArray(Code39Encoder.CHARACTER_ENCODINGS.get(indexInString), widths) pos += appendPattern(result, pos, widths, true) pos += appendPattern(result, pos, narrowWhite, false) } toIntArray(Code39Encoder.ASTERISK_ENCODING, widths) appendPattern(result, pos, widths, true) return result } private fun toIntArray(a: Int, toReturn: IntArray) { for (i in 0..8) { val temp = a and (1 shl (8 - i)) toReturn[i] = if (temp == 0) 1 else 2 } } private fun tryToConvertToExtendedMode(contents: String): String { val length = contents.length val extendedContent: StringBuilder = StringBuilder() for (i in 0 until length) { val character = contents[i] when (character) { '\u0000' -> extendedContent.append("%U") ' ', '-', '.' -> extendedContent.append(character) '@' -> extendedContent.append("%V") '`' -> extendedContent.append("%W") else -> if (character.code <= 26) { extendedContent.append('$') extendedContent.append(('A'.code + (character.code - 1)).toChar()) } else if (character < ' ') { extendedContent.append('%') extendedContent.append(('A'.code + (character.code - 27)).toChar()) } else if ((character <= ',') || (character == '/') || (character == ':')) { extendedContent.append('/') extendedContent.append(('A'.code + (character.code - 33)).toChar()) } else if (character <= '9') { extendedContent.append(('0'.code + (character.code - 48)).toChar()) } else if (character <= '?') { extendedContent.append('%') extendedContent.append(('F'.code + (character.code - 59)).toChar()) } else if (character <= 'Z') { extendedContent.append(('A'.code + (character.code - 65)).toChar()) } else if (character <= '_') { extendedContent.append('%') extendedContent.append(('K'.code + (character.code - 91)).toChar()) } else if (character <= 'z') { extendedContent.append('+') extendedContent.append(('A'.code + (character.code - 97)).toChar()) } else if (character.code <= 127) { extendedContent.append('%') extendedContent.append(('P'.code + (character.code - 123)).toChar()) } else { throw IllegalArgumentException( "Requested content contains a non-encodable character: '" + contents[i] + "'" ) } } } return extendedContent.toString() } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object CodeEAN13Encoder : BarcodeEncoder { private val CODE_WIDTH = 3 + 7 * 6 + // left bars 5 + 7 * 6 + // right bars 3 // end guard override fun encode(contents: String): BooleanArray { var actualContents = contents val length = actualContents.length when (length) { 12 -> { actualContents += getStandardUPCEANChecksum(actualContents) } 13 -> if (!checkStandardUPCEANChecksum(actualContents)) { throw IllegalArgumentException("Contents do not pass checksum") } else -> throw IllegalArgumentException( "Requested contents should be 12 or 13 digits long, but got $length" ) } actualContents.requireNumeric() val firstDigit = actualContents[0].digitToIntOrNull() ?: -1 val parities: Int = UpcEan.FIRST_DIGIT_ENCODINGS[firstDigit] val result = BooleanArray(CODE_WIDTH) var pos = 0 pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) // See EAN13Reader for a description of how the first digit & left bars are encoded for (i in 1..6) { var digit = actualContents[i].digitToIntOrNull() ?: -1 if (parities shr 6 - i and 1 == 1) { digit += 10 } pos += appendPattern( result, pos, UpcEan.L_AND_G_PATTERNS.get(digit), false ) } pos += appendPattern(result, pos, UpcEan.MIDDLE_PATTERN, false) for (i in 7..12) { val digit = actualContents[i].digitToIntOrNull() ?: -1 pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) } appendPattern(result, pos, UpcEan.START_END_PATTERN, true) return result } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object CodeEAN8Encoder : BarcodeEncoder { private val CODE_WIDTH = 3 + 7 * 4 + // left bars 5 + 7 * 4 + // right bars 3 // end guard override fun encode(contents: String): BooleanArray { var actualContentns = contents val length = actualContentns.length when (length) { 7 -> { // No check digit present, calculate it and add it val check: Int = getStandardUPCEANChecksum(actualContentns) actualContentns += check } 8 -> if (!checkStandardUPCEANChecksum(actualContentns)) { throw IllegalArgumentException("Contents do not pass checksum") } else -> throw IllegalArgumentException( "Requested contents should be 7 or 8 digits long, but got $length" ) } actualContentns.requireNumeric() val result = BooleanArray(CODE_WIDTH) var pos = 0 pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) for (i in 0..3) { val digit = actualContentns[i].digitToIntOrNull() ?: -1 pos += appendPattern( result, pos, UpcEan.L_PATTERNS[digit], false ) } pos += appendPattern( result, pos, UpcEan.MIDDLE_PATTERN, false ) for (i in 4..7) { val digit = actualContentns[i].digitToIntOrNull() ?: -1 pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) } appendPattern(result, pos, UpcEan.START_END_PATTERN, true) return result } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object CodeITFEncoder : BarcodeEncoder { override fun encode(contents: String): BooleanArray { val length = contents.length if (length % 2 != 0) { throw IllegalArgumentException("The length of the input should be even") } if (length > 80) { throw IllegalArgumentException( "Requested contents should be less than 80 digits long, but got $length" ) } contents.requireNumeric() val result = BooleanArray(9 + 9 * length) var pos = appendPattern(result, 0, START_PATTERN, true) var i = 0 while (i < length) { val one = contents[i].digitToIntOrNull() ?: -1 val two = contents[i + 1].digitToIntOrNull() ?: -1 val encoding = IntArray(10) for (j in 0..4) { encoding[2 * j] = PATTERNS[one][j] encoding[2 * j + 1] = PATTERNS[two][j] } pos += appendPattern(result, pos, encoding, true) i += 2 } appendPattern(result, pos, END_PATTERN, true) return result } private val START_PATTERN = intArrayOf(1, 1, 1, 1) private val END_PATTERN = intArrayOf(3, 1, 1) private const val W = 3 // Pixel width of a 3x wide line private const val N = 1 // Pixed width of a narrow line // See ITFReader.PATTERNS private val PATTERNS = arrayOf( intArrayOf(N, N, W, W, N), intArrayOf(W, N, N, N, W), intArrayOf( N, W, N, N, W ), intArrayOf(W, W, N, N, N), intArrayOf(N, N, W, N, W), intArrayOf(W, N, W, N, N), intArrayOf( N, W, W, N, N ), intArrayOf(N, N, N, W, W), intArrayOf(W, N, N, W, N), intArrayOf(N, W, N, W, N) ) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object CodeUPCAEncoder : BarcodeEncoder { override fun encode(contents: String) = CodeEAN13Encoder.encode("0$contents") } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/CodeUPCEEncoder.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object CodeUPCEEncoder : BarcodeEncoder { private const val CODE_WIDTH = 3 + 7 * 6 + // bars 6 // end guard override fun encode(contents: String): BooleanArray { var contents = contents val length = contents.length when (length) { 7 -> { // No check digit present, calculate it and add it val check: Int = getStandardUPCEANChecksum(convertUPCEtoUPCA(contents)) contents += check } 8 -> if (!checkStandardUPCEANChecksum(convertUPCEtoUPCA(contents))) { throw IllegalArgumentException("Contents do not pass checksum") } else -> throw IllegalArgumentException( "Requested contents should be 7 or 8 digits long, but got $length" ) } contents.requireNumeric() val firstDigit = contents[0].digitToIntOrNull() ?: -1 if (firstDigit != 0 && firstDigit != 1) { throw IllegalArgumentException("Number system must be 0 or 1") } val checkDigit = contents[7].digitToIntOrNull() ?: -1 val parities: Int = UpcEan.NUMSYS_AND_CHECK_DIGIT_PATTERNS.get(firstDigit).get(checkDigit) val result = BooleanArray(CODE_WIDTH) var pos = appendPattern(result, 0, UpcEan.START_END_PATTERN, true) for (i in 1..6) { var digit = contents[i].digitToIntOrNull() ?: -1 if (parities shr 6 - i and 1 == 1) { digit += 10 } pos += appendPattern(result, pos, UpcEan.L_AND_G_PATTERNS.get(digit), false) } appendPattern(result, pos, UpcEan.END_PATTERN, false) return result } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt ================================================ package io.github.alexzhirkevich.qrose.oned internal object UpcEan { val NUMSYS_AND_CHECK_DIGIT_PATTERNS = arrayOf( intArrayOf(0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25), intArrayOf(0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A) ) /** * Start/end guard pattern. */ val START_END_PATTERN by lazy { intArrayOf(1, 1, 1) } /** * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. */ val MIDDLE_PATTERN by lazy { intArrayOf(1, 1, 1, 1, 1) } /** * end guard pattern. */ val END_PATTERN by lazy { intArrayOf(1, 1, 1, 1, 1, 1) } /** * "Odd", or "L" patterns used to encode UPC/EAN digits. */ val L_PATTERNS by lazy { arrayOf( intArrayOf(3, 2, 1, 1), intArrayOf(2, 2, 2, 1), intArrayOf(2, 1, 2, 2), intArrayOf(1, 4, 1, 1), intArrayOf(1, 1, 3, 2), intArrayOf(1, 2, 3, 1), intArrayOf(1, 1, 1, 4), intArrayOf(1, 3, 1, 2), intArrayOf(1, 2, 1, 3), intArrayOf(3, 1, 1, 2) ) } val L_AND_G_PATTERNS by lazy { buildList(20) { addAll(L_PATTERNS) for (i in 10..19) { val widths: IntArray = L_PATTERNS[i - 10] val reversedWidths = IntArray(widths.size) for (j in widths.indices) { reversedWidths[j] = widths[widths.size - j - 1] } add(reversedWidths) } } } val FIRST_DIGIT_ENCODINGS by lazy { intArrayOf( 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A ) } } internal fun String.requireNumeric() = require(all { it.isDigit() }) { "Input should only contain digits 0-9" } internal fun convertUPCEtoUPCA(upce: String): String { val upceChars = upce.toCharArray(1, 7) val result = StringBuilder(12) result.append(upce[0]) val lastChar = upceChars[5] when (lastChar) { '0', '1', '2' -> { result.appendRange(upceChars, 0, 0 + 2) result.append(lastChar) result.append("0000") result.appendRange(upceChars, 2, 3) } '3' -> { result.appendRange(upceChars, 0, 3) result.append("00000") result.appendRange(upceChars, 3, 2) } '4' -> { result.appendRange(upceChars, 0, 4) result.append("00000") result.append(upceChars[4]) } else -> { result.appendRange(upceChars, 0, 5) result.append("0000") result.append(lastChar) } } // Only append check digit in conversion if supplied if (upce.length >= 8) { result.append(upce[7]) } return result.toString() } internal fun checkStandardUPCEANChecksum(s: CharSequence): Boolean { val length = s.length if (length == 0) { return false } val check = s[length - 1].digitToIntOrNull() ?: -1 return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check } internal fun getStandardUPCEANChecksum(s: CharSequence): Int { val length = s.length var sum = 0 run { var i = length - 1 while (i >= 0) { val digit = s[i].code - '0'.code if (digit < 0 || digit > 9) { throw IllegalStateException("Illegal contents") } sum += digit i -= 2 } } sum *= 3 var i = length - 2 while (i >= 0) { val digit = s[i].code - '0'.code if (digit < 0 || digit > 9) { throw IllegalStateException("Illegal contents") } sum += digit i -= 2 } return (1000 - sum) % 10 } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Neighbors.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable /** * Status of the neighbor QR code pixels or eyes * */ @Immutable class Neighbors( val topLeft: Boolean = false, val topRight: Boolean = false, val left: Boolean = false, val top: Boolean = false, val right: Boolean = false, val bottomLeft: Boolean = false, val bottom: Boolean = false, val bottomRight: Boolean = false, ) { companion object { val Empty = Neighbors() } override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false other as Neighbors if (topLeft != other.topLeft) return false if (topRight != other.topRight) return false if (left != other.left) return false if (top != other.top) return false if (right != other.right) return false if (bottomLeft != other.bottomLeft) return false if (bottom != other.bottom) return false if (bottomRight != other.bottomRight) return false return true } override fun hashCode(): Int { var result = topLeft.hashCode() result = 31 * result + topRight.hashCode() result = 31 * result + left.hashCode() result = 31 * result + top.hashCode() result = 31 * result + right.hashCode() result = 31 * result + bottomLeft.hashCode() result = 31 * result + bottom.hashCode() result = 31 * result + bottomRight.hashCode() return result } } val Neighbors.hasAny: Boolean get() = topLeft || topRight || left || top || right || bottomLeft || bottom || bottomRight val Neighbors.hasAllNearest get() = top && bottom && left && right val Neighbors.hasAll: Boolean get() = topLeft && topRight && left && top && right && bottomLeft && bottom && bottomRight ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBallShape.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Path /** * Style of the qr-code eye internal ball. * */ interface QrBallShape : QrShapeModifier { companion object { val Default: QrBallShape = square() } } fun QrBallShape.Companion.square(size: Float = 1f): QrBallShape = object : QrBallShape, QrShapeModifier by SquareShape(size) {} fun QrBallShape.Companion.circle(size: Float = 1f): QrBallShape = object : QrBallShape, QrShapeModifier by CircleShape(size) {} fun QrBallShape.Companion.roundCorners( radius: Float, topLeft: Boolean = true, bottomLeft: Boolean = true, topRight: Boolean = true, bottomRight: Boolean = true, ): QrBallShape = object : QrBallShape, QrShapeModifier by RoundCornersShape( cornerRadius = radius, topLeft = topLeft, bottomLeft = bottomLeft, topRight = topRight, bottomRight = bottomRight, withNeighbors = false ) {} fun QrBallShape.Companion.asPixel(pixelShape: QrPixelShape): QrBallShape = AsPixelBallShape(pixelShape) @Immutable private class AsPixelBallShape( private val pixelShape: QrPixelShape ) : QrBallShape { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val matrix = QrCodeMatrix(3, QrCodeMatrix.PixelType.DarkPixel) repeat(3) { i -> repeat(3) { j -> addPath( pixelShape.newPath( size / 3, matrix.neighbors(i, j) ), Offset(size / 3 * i, size / 3 * j) ) } } } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrBrush.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageShader import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.isUnspecified import androidx.compose.ui.graphics.painter.Painter import io.github.alexzhirkevich.qrose.options.QrBrushMode.Separate import io.github.alexzhirkevich.qrose.toImageBitmap import kotlin.random.Random enum class QrBrushMode { /** * If applied to QR code pattern, the whole pattern will be combined to the single [Path] * and then painted using produced [Brush] with large size and [Neighbors.Empty]. * * Balls and frames with [QrBrush.Unspecified] will also be joined with this path. * If balls or frames have specified [QrBrush] then [Neighbors] parameter will be passed * according to eye position * */ Join, /** * If applied to QR code pattern, each QR code pixel will be painted separately and for each * pixel new [Brush] will be created. In this scenario [Neighbors] parameter for pixels will * be chosen according to the actual pixel neighbors. * * Balls and frames with [QrBrush.Unspecified] will be painted with [QrBrush.Default]. * */ Separate } /** * Color [Brush] factory for a QR code part. * */ interface QrBrush { /** * Brush [mode] indicates the way this brush is applied to the QR code part. * */ val mode: QrBrushMode /** * Factory method of the [Brush] for the element with given [size] and [neighbors]. * */ fun brush(size: Float, neighbors: Neighbors): Brush companion object { /** * Delegates painting to other most suitable brush * */ val Unspecified: QrBrush = solid(Color.Unspecified) /** * Default solid black brush * */ val Default: QrBrush = solid(Color.Black) } } /** * Check if this brush is not specified * */ val QrBrush.isUnspecified get() = this === QrBrush.Unspecified || this is Solid && this.color.isUnspecified /** * Check if this brush is specified * */ val QrBrush.isSpecified: Boolean get() = !isUnspecified /** * [SolidColor] brush from [color] * */ fun QrBrush.Companion.solid(color: Color): QrBrush = Solid(color) /** * Any Compose brush constructed in [builder] with specific QR code part size. * This can be gradient brushes like, shader brush or any other. * * Example: * ``` * QrBrush.brush { * Brush.linearGradient( * 0f to Color.Red, * 1f to Color.Blue, * end = Offset(it,it) * ) * } * ``` * */ fun QrBrush.Companion.brush( builder: (size: Float) -> Brush ): QrBrush = BrushColor(builder) /** * Random solid color picked from given [probabilities]. * Custom source of [random]ness can be used. * * Note: This brush uses [Separate] brush mode. * * Example: * ``` * QrBrush.random( * 0.05f to Color.Red, * 1f to Color.Black * ) * ``` * */ fun QrBrush.Companion.random( vararg probabilities: Pair, random: Random = Random(13) ): QrBrush = Random(probabilities.toList(), random) /** * Shader brush that resizes the image [painter] to the required size. * [painter] resolution should be square for better result. * */ fun QrBrush.Companion.image( painter: Painter, alpha: Float = 1f, colorFilter: ColorFilter? = null ): QrBrush = Image(painter, alpha, colorFilter) @Immutable private class Image( private val painter: Painter, private val alpha: Float = 1f, private val colorFilter: ColorFilter? = null ) : QrBrush { override val mode: QrBrushMode get() = QrBrushMode.Join private var cachedBrush: Brush? = null private var cachedSize: Int = -1 override fun brush(size: Float, neighbors: Neighbors): Brush { val intSize = size.toInt() if (cachedBrush != null && cachedSize == intSize) { return cachedBrush!! } val bmp = painter.toImageBitmap(intSize, intSize, alpha, colorFilter) cachedBrush = ShaderBrush(ImageShader(bmp, TileMode.Decal, TileMode.Decal)) cachedSize = intSize return cachedBrush!! } } @Immutable private class Solid(val color: Color) : QrBrush by BrushColor({ SolidColor(color) }) @Immutable private class BrushColor(private val builder: (size: Float) -> Brush) : QrBrush { override val mode: QrBrushMode get() = QrBrushMode.Join override fun brush(size: Float, neighbors: Neighbors): Brush = this.builder(size) } @Immutable private class Random( private val probabilities: List>, private val random: Random ) : QrBrush { private val _probabilities = mutableListOf, Color>>() init { require(probabilities.isNotEmpty()) { "Random color list can't be empty" } (listOf(0f) + probabilities.map { it.first }).reduceIndexed { index, sum, i -> _probabilities.add(sum..(sum + i) to probabilities[index - 1].second) sum + i } } override val mode: QrBrushMode = Separate override fun brush(size: Float, neighbors: Neighbors): Brush { val random = random.nextFloat() * _probabilities.last().first.endInclusive val idx = _probabilities.binarySearch { when { random < it.first.start -> 1 random > it.first.endInclusive -> -1 else -> 0 } } return SolidColor(probabilities[idx].second) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeMatrix.kt ================================================ package io.github.alexzhirkevich.qrose.options class QrCodeMatrix(val size: Int, initialFill: PixelType = PixelType.Background) { constructor(list: List>) : this(list.size) { types = list.flatten().toMutableList() } enum class PixelType { DarkPixel, LightPixel, Background, Logo } private var types = MutableList(size * size) { initialFill } operator fun get(i: Int, j: Int): PixelType { val outOfBound = when { i !in 0 until size -> i j !in 0 until size -> j else -> null } if (outOfBound != null) throw IndexOutOfBoundsException( "Index $outOfBound is out of 0..${size - 1} matrix bound" ) return types[i + j * size] } operator fun set(i: Int, j: Int, type: PixelType) { val outOfBound = when { i !in 0 until size -> i j !in 0 until size -> j else -> null } if (outOfBound != null) throw IndexOutOfBoundsException( "Index $outOfBound is out of 0..${size - 1} matrix bound" ) types[i + j * size] = type } fun copy(): QrCodeMatrix = QrCodeMatrix(size).apply { types = ArrayList(this@QrCodeMatrix.types) } } internal fun QrCodeMatrix.neighbors(i: Int, j: Int): Neighbors { fun cmp(i2: Int, j2: Int) = kotlin.runCatching { this[i2, j2] == this[i, j] }.getOrDefault(false) return Neighbors( topLeft = cmp(i - 1, j - 1), topRight = cmp(i + 1, j - 1), left = cmp(i - 1, j), top = cmp(i, j - 1), right = cmp(i + 1, j), bottomLeft = cmp(i - 1, j + 1), bottom = cmp(i, j + 1), bottomRight = cmp(i + 1, j + 1) ) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrCodeShape.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import io.github.alexzhirkevich.qrose.options.QrCodeShape.Companion.Default import kotlin.math.abs import kotlin.math.cos import kotlin.math.roundToInt import kotlin.math.sin import kotlin.math.sqrt import kotlin.random.Random /** * Shape of the QR-code pattern. * * Limitations: * - The [Default] shape is the smallest available. * Any custom shapes must have bigger size. * - QR code pattern must always be at the center of the matrix. * - QR code matrix is always square. * */ interface QrCodeShape { /** * How much was QR code size increased in fraction related to default size (1.0). * */ val shapeSizeIncrease: Float /** * Transform receiver matrix or create new with bigger size. * */ fun QrCodeMatrix.transform(): QrCodeMatrix companion object { val Default = object : QrCodeShape { override val shapeSizeIncrease: Float = 1f override fun QrCodeMatrix.transform(): QrCodeMatrix = this } } } fun QrCodeShape.Companion.circle( padding: Float = 1.1f, precise: Boolean = true, random: Random = Random(233), ): QrCodeShape = Circle( padding = padding, random = random, precise = precise ) fun QrCodeShape.Companion.hexagon( rotation: Float = 30f, precise: Boolean = true, random: Random = Random(233), ): QrCodeShape = Hexagon( rotationDegree = rotation, random = random, precise = precise ) @Immutable private class Circle( private val padding: Float, private val random: Random, private val precise: Boolean ) : QrCodeShape { override val shapeSizeIncrease: Float = 1 + (padding * sqrt(2.0) - 1).toFloat() override fun QrCodeMatrix.transform(): QrCodeMatrix { val padding = padding.coerceIn(1f, 2f) val added = (((size * padding * sqrt(2.0)) - size) / 2).roundToInt() val newSize = size + 2 * added val newMatrix = QrCodeMatrix(newSize) val center = newSize / 2f for (i in 0 until newSize) { for (j in 0 until newSize) { val notInSquare = (i <= added - 1 || j <= added - 1 || i >= added + size || j >= added + size) val inLargeCircle = isInCircle(center, i.toFloat(), j.toFloat(), center) if (notInSquare && inLargeCircle) { val inSmallCircle = !precise || isInCircle( center, i.toFloat(), j.toFloat(), center - 1 ) newMatrix[i, j] = if (!inSmallCircle || random.nextBoolean()) QrCodeMatrix.PixelType.DarkPixel else QrCodeMatrix.PixelType.LightPixel } } } for (i in 0 until size) { for (j in 0 until size) { newMatrix[added + i, added + j] = this[i, j] } } return newMatrix } fun isInCircle(center: Float, i: Float, j: Float, radius: Float): Boolean { return sqrt((center - i) * (center - i) + (center - j) * (center - j)) < radius } } @Immutable private class Hexagon( rotationDegree: Float, private val random: Random, private val precise: Boolean ) : QrCodeShape { override val shapeSizeIncrease: Float = 1.6f override fun QrCodeMatrix.transform(): QrCodeMatrix { val a = sqrt(size * size / 1.268 / 1.268) val newSize = (1.575f * size).toInt() val newMatrix = QrCodeMatrix(newSize) val (x1, y1) = rotate(newSize / 2, newSize / 2) repeat(newSize) { i -> repeat(newSize) { j -> val (x, y) = rotate(i, j) val inLarge = isInHexagon(x, y, x1, y1, a) if (inLarge) { val inSmall = !precise || isInHexagon(x, y, x1, y1, a - 1.1) newMatrix[i, j] = if (!inSmall || random.nextBoolean()) QrCodeMatrix.PixelType.DarkPixel else QrCodeMatrix.PixelType.LightPixel } } } val diff = (newSize - size) / 2 repeat(size) { i -> repeat(size) { j -> newMatrix[diff + i, diff + j] = this[i, j] } } return newMatrix } private fun isInHexagon(x1: Double, y1: Double, x2: Double, y2: Double, z: Double): Boolean { val x = abs(x1 - x2) val y = abs(y1 - y2) val py1 = z * 0.86602540378 val px2 = z * 0.5088190451 val py2 = z * 0.8592582628 val p_angle_01 = -x * (py1 - y) - x * y val p_angle_20 = -y * (px2 - x) + x * (py2 - y) val p_angle_03 = y * z val p_angle_12 = -x * (py2 - y) - (px2 - x) * (py1 - y) val p_angle_32 = (z - x) * (py2 - y) + y * (px2 - x) val is_inside_1 = (p_angle_01 * p_angle_12 >= 0) && (p_angle_12 * p_angle_20 >= 0) val is_inside_2 = (p_angle_03 * p_angle_32 >= 0) && (p_angle_32 * p_angle_20 >= 0) return is_inside_1 || is_inside_2 } private val rad = rotationDegree * 0.0174533 private val si = sin(rad) private val co = cos(rad) private val isModulo60 = rotationDegree.roundToInt() % 60 == 0 private fun rotate(x: Int, y: Int): Pair { if (isModulo60) { return x.toDouble() to y.toDouble() } return (x * co - y * si) to (x * si + y * co) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrColors.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable /** * Colors of QR code elements * * @param dark brush of the dark QR code pixels * @param light brush of the light QR code pixels * @param ball brush of the QR code eye balls * @param frame brush of the QR code eye frames */ @Immutable data class QrColors( val dark: QrBrush = QrBrush.Default, val light: QrBrush = QrBrush.Unspecified, val ball: QrBrush = QrBrush.Unspecified, val frame: QrBrush = QrBrush.Unspecified, val background: QrBrush = QrBrush.Unspecified ) ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrErrorCorrectionLevel.kt ================================================ @file:Suppress("UNUSED") package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel /** * QR code allows you to read encoded information even if a * part of the QR code image is damaged. It also allows to have logo * inside the code as a part of "damage". * */ @Immutable enum class QrErrorCorrectionLevel( internal val lvl: ErrorCorrectionLevel ) { /** * Minimum possible level will be used. * */ Auto(ErrorCorrectionLevel.L), /** * ~7% of QR code can be damaged (or used as logo). * */ Low(ErrorCorrectionLevel.L), /** * ~15% of QR code can be damaged (or used as logo). * */ Medium(ErrorCorrectionLevel.M), /** * ~25% of QR code can be damaged (or used as logo). * */ MediumHigh(ErrorCorrectionLevel.Q), /** * ~30% of QR code can be damaged (or used as logo). * */ High(ErrorCorrectionLevel.H) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrFrameShape.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathOperation /** * Style of the qr-code eye frame. */ interface QrFrameShape : QrShapeModifier { companion object { val Default: QrFrameShape = square() } } fun QrFrameShape.Companion.square(size: Float = 1f): QrFrameShape = SquareFrameShape(size) fun QrFrameShape.Companion.circle(size: Float = 1f): QrFrameShape = CircleFrameShape(size) fun QrFrameShape.Companion.roundCorners( corner: Float, width: Float = 1f, topLeft: Boolean = true, bottomLeft: Boolean = true, topRight: Boolean = true, bottomRight: Boolean = true, ): QrFrameShape = RoundCornersFrameShape( corner = corner, width = width, topLeft = topLeft, bottomLeft = bottomLeft, topRight = topRight, bottomRight = bottomRight ) /** * [corner] from 0f to 0.35f */ fun QrFrameShape.Companion.cutCorners( corner: Float, width: Float = 1f, topLeft: Boolean = true, bottomLeft: Boolean = true, topRight: Boolean = true, bottomRight: Boolean = true, ): QrFrameShape = CutCornersFrameShape( corner = corner, width = width, topLeft = topLeft, bottomLeft = bottomLeft, topRight = topRight, bottomRight = bottomRight ) fun QrFrameShape.Companion.asPixel(pixelShape: QrPixelShape): QrFrameShape = AsPixelFrameShape(pixelShape) @Immutable private class AsPixelFrameShape( val pixelShape: QrPixelShape ) : QrFrameShape { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val matrix = QrCodeMatrix(7) repeat(7) { i -> repeat(7) { j -> matrix[i, j] = if (i == 0 || j == 0 || i == 6 || j == 6) QrCodeMatrix.PixelType.DarkPixel else QrCodeMatrix.PixelType.Background } } repeat(7) { i -> repeat(7) { j -> if (matrix[i, j] == QrCodeMatrix.PixelType.DarkPixel) addPath( pixelShape.newPath( size / 7, matrix.neighbors(i, j) ), Offset(size / 7 * i, size / 7 * j) ) } } } } @Immutable private class SquareFrameShape( private val size: Float = 1f ) : QrFrameShape { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val width = size / 7f * this@SquareFrameShape.size.coerceAtLeast(0f) addRect( Rect(0f, 0f, size, size) ) addRect( Rect(width, width, size - width, size - width) ) } } @Immutable private class CircleFrameShape( private val size: Float = 1f ) : QrFrameShape { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val width = size / 7f * this@CircleFrameShape.size.coerceAtLeast(0f) addOval( Rect(0f, 0f, size, size) ) addOval( Rect(width, width, size - width, size - width) ) } } @Immutable private class CutCornersFrameShape( private val corner: Float, private val width: Float = 1f, private val topLeft: Boolean = true, private val bottomLeft: Boolean = true, private val topRight: Boolean = true, private val bottomRight: Boolean = true, ) : QrFrameShape { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val strokeWidth = (size / 7f) * width.coerceAtLeast(0f) val realCorner = corner.coerceIn(0f, 0.5f) val outerCut = realCorner * size val outerRect = Rect(0f, 0f, size, size) val innerRect = Rect(strokeWidth, strokeWidth, size - strokeWidth, size - strokeWidth) val scale = (size - 2 * strokeWidth) / size val innerCut = outerCut * scale val outerPath = buildCutRectPath(outerRect, outerCut, topLeft, topRight, bottomLeft, bottomRight) val innerPath = buildCutRectPath(innerRect, innerCut, topLeft, topRight, bottomLeft, bottomRight) op(outerPath, innerPath, PathOperation.Difference) } private fun buildCutRectPath( rect: Rect, cut: Float, topLeft: Boolean, topRight: Boolean, bottomLeft: Boolean, bottomRight: Boolean ): Path = Path().apply { with(rect) { moveTo(left + if (topLeft) cut else 0f, top) lineTo(right - if (topRight) cut else 0f, top) if (topRight) lineTo(right, top + cut) lineTo(right, bottom - if (bottomRight) cut else 0f) if (bottomRight) lineTo(right - cut, bottom) lineTo(left + if (bottomLeft) cut else 0f, bottom) if (bottomLeft) lineTo(left, bottom - cut) lineTo(left, top + if (topLeft) cut else 0f) if (topLeft) lineTo(left + cut, top) close() } } } @Immutable private class RoundCornersFrameShape( private val corner: Float, private val width: Float = 1f, private val topLeft: Boolean = true, private val bottomLeft: Boolean = true, private val topRight: Boolean = true, private val bottomRight: Boolean = true, ) : QrFrameShape { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val strokeWidth = (size / 7f) * width.coerceAtLeast(0f) val realCorner = corner.coerceIn(0f, 0.5f) // радиус внешнего круга val outerCorner = realCorner * size // радиус внутреннего круга = внешний радиус - толщина, чтобы бордер не ломался val innerCorner = (outerCorner - strokeWidth).coerceAtLeast(0f) val outer = CornerRadius(outerCorner, outerCorner) val inner = CornerRadius(innerCorner, innerCorner) val outerRect = Rect(0f, 0f, size, size) val innerRect = Rect(strokeWidth, strokeWidth, size - strokeWidth, size - strokeWidth) val outerPath = Path().apply { addRoundRect( RoundRect( outerRect, topLeft = if (topLeft) outer else CornerRadius.Zero, topRight = if (topRight) outer else CornerRadius.Zero, bottomLeft = if (bottomLeft) outer else CornerRadius.Zero, bottomRight = if (bottomRight) outer else CornerRadius.Zero ) ) } val innerPath = Path().apply { addRoundRect( RoundRect( innerRect, topLeft = if (topLeft) inner else CornerRadius.Zero, topRight = if (topRight) inner else CornerRadius.Zero, bottomLeft = if (bottomLeft) inner else CornerRadius.Zero, bottomRight = if (bottomRight) inner else CornerRadius.Zero ) ) } op(outerPath, innerPath, PathOperation.Difference) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogo.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.painter.Painter /** * Logo (middle image) of the QR code. * * @param painter middle image. * @param size image size in fraction relative to QR code size * @param padding style and size of the QR code padding. * Can be used without [painter] if you want to place a logo manually. * @param shape shape of the logo padding * */ @Immutable data class QrLogo( val painter: Painter? = null, val size: Float = 0.25f, val padding: QrLogoPadding = QrLogoPadding.Empty, val shape: QrLogoShape = QrLogoShape.Default, ) ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoPadding.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable /** * Type of padding applied to the logo. * Helps to highlight the logo inside the QR code pattern. * Padding can be added regardless of the presence of a logo. * */ sealed interface QrLogoPadding { /** * Padding size relatively to the size of logo * */ val size: Float /** * Logo will be drawn on top of QR code without any padding. * QR code pixels might be visible through transparent logo. * * Prefer empty padding if your qr code encodes large amount of data * to avoid performance issues. * */ @Immutable data object Empty : QrLogoPadding { override val size: Float get() = 0f } /** * Padding will be applied precisely according to the shape of logo * */ @Immutable class Accurate(override val size: Float) : QrLogoPadding /** * Works like [Accurate] but all clipped pixels will be removed. * * WARNING: this padding can cause performance issues * for QR codes with large amount out data * */ @Immutable class Natural(override val size: Float) : QrLogoPadding } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrLogoShape.kt ================================================ package io.github.alexzhirkevich.qrose.options interface QrLogoShape : QrShapeModifier { companion object { val Default: QrLogoShape = object : QrLogoShape, QrShapeModifier by SquareShape() {} } } fun QrLogoShape.Companion.circle(): QrLogoShape = object : QrLogoShape, QrShapeModifier by CircleShape(1f) {} fun QrLogoShape.Companion.roundCorners(radius: Float): QrLogoShape = object : QrLogoShape, QrShapeModifier by RoundCornersShape(radius, false) {} ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrOptions.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import io.github.alexzhirkevich.qrose.options.dsl.InternalQrOptionsBuilderScope import io.github.alexzhirkevich.qrose.options.dsl.QrOptionsBuilderScope import io.github.alexzhirkevich.qrose.qrcode.MaskPattern fun QrOptions(block: QrOptionsBuilderScope.() -> Unit): QrOptions { val builder = QrOptions.Builder() InternalQrOptionsBuilderScope(builder).apply(block) return builder.build() } /** * Styling options of the QR code * * @param shapes shapes of the QR code pattern and its parts * @param colors colors of the QR code parts * @param logo middle image * @param errorCorrectionLevel level of error correction * @param fourEyed enable fourth eye * */ @Immutable data class QrOptions( val shapes: QrShapes = QrShapes(), val colors: QrColors = QrColors(), val logo: QrLogo = QrLogo(), val errorCorrectionLevel: QrErrorCorrectionLevel = QrErrorCorrectionLevel.Auto, val maskPattern: MaskPattern? = null, val fourEyed: Boolean = false, ) { internal class Builder { var shapes: QrShapes = QrShapes() var colors: QrColors = QrColors() var logo: QrLogo = QrLogo() var errorCorrectionLevel: QrErrorCorrectionLevel = QrErrorCorrectionLevel.Auto var fourthEyeEnabled: Boolean = false var maskPattern: MaskPattern? = null fun build(): QrOptions = QrOptions( shapes = shapes, colors = colors, logo = logo, errorCorrectionLevel = errorCorrectionLevel, maskPattern = maskPattern, fourEyed = fourthEyeEnabled ) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrPixelShape.kt ================================================ package io.github.alexzhirkevich.qrose.options /** * Style of the qr-code pixels. * */ fun interface QrPixelShape : QrShapeModifier { companion object { val Default: QrPixelShape = square() } } fun QrPixelShape.Companion.square(size: Float = 1f): QrPixelShape = object : QrPixelShape, QrShapeModifier by SquareShape(size) {} fun QrPixelShape.Companion.circle(size: Float = 1f): QrPixelShape = object : QrPixelShape, QrShapeModifier by CircleShape(size) {} fun QrPixelShape.Companion.roundCorners(radius: Float = .5f): QrPixelShape = object : QrPixelShape, QrShapeModifier by RoundCornersShape(radius, true) {} fun QrPixelShape.Companion.verticalLines(width: Float = 1f): QrPixelShape = object : QrPixelShape, QrShapeModifier by VerticalLinesShape(width) {} fun QrPixelShape.Companion.horizontalLines(width: Float = 1f): QrPixelShape = object : QrPixelShape, QrShapeModifier by HorizontalLinesShape(width) {} ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifier.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd fun interface QrShapeModifier { /** * Modify current path or create new one. * * Receiver path is empty and reused for optimization. * Most benefit this optimization gives when the shape is used for pixels with [QrBrushMode.Separate]. * * Note: parent path has [EvenOdd] fill type! And this path will inherit it. * */ fun Path.path(size: Float, neighbors: Neighbors): Path } internal fun QrShapeModifier.newPath(size: Float, neighbors: Neighbors): Path = Path().apply { path(size, neighbors) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapeModifiers.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Path @Immutable internal class SquareShape( val size: Float = 1f ) : QrShapeModifier { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val s = size * this@SquareShape.size.coerceIn(0f, 1f) val offset = (size - s) / 2 addRect( Rect( Offset(offset, offset), Size(s, s) ) ) } } @Immutable internal class CircleShape( val size: Float ) : QrShapeModifier { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val s = size * this@CircleShape.size.coerceIn(0f, 1f) val offset = (size - s) / 2 addOval( Rect( Offset(offset, offset), Size(s, s) ) ) } } @Immutable internal class RoundCornersShape( val cornerRadius: Float, val withNeighbors: Boolean, val topLeft: Boolean = true, val bottomLeft: Boolean = true, val topRight: Boolean = true, val bottomRight: Boolean = true, ) : QrShapeModifier { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val corner = (cornerRadius.coerceIn(0f, .5f) * size).let { CornerRadius(it, it) } addRoundRect( RoundRect( Rect(0f, 0f, size, size), topLeft = if (topLeft && (withNeighbors.not() || neighbors.top.not() && neighbors.left.not())) corner else CornerRadius.Zero, topRight = if (topRight && (withNeighbors.not() || neighbors.top.not() && neighbors.right.not())) corner else CornerRadius.Zero, bottomRight = if (bottomRight && (withNeighbors.not() || neighbors.bottom.not() && neighbors.right.not())) corner else CornerRadius.Zero, bottomLeft = if (bottomLeft && (withNeighbors.not() || neighbors.bottom.not() && neighbors.left.not())) corner else CornerRadius.Zero ) ) } } @Immutable internal class VerticalLinesShape( private val width: Float ) : QrShapeModifier { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val padding = (size * (1 - width.coerceIn(0f, 1f))) if (neighbors.top) { addRect(Rect(Offset(padding, 0f), Size(size - padding * 2, size / 2))) } else { addArc(Rect(Offset(padding, 0f), Size(size - padding * 2, size)), 180f, 180f) } if (neighbors.bottom) { addRect(Rect(Offset(padding, size / 2), Size(size - padding * 2, size / 2))) } else { addArc(Rect(Offset(padding, 0f), Size(size - padding * 2, size)), 0f, 180f) } } } @Immutable internal class HorizontalLinesShape( private val width: Float ) : QrShapeModifier { override fun Path.path(size: Float, neighbors: Neighbors): Path = apply { val padding = (size * (1 - width.coerceIn(0f, 1f))) if (neighbors.left) { addRect(Rect(Offset(0f, padding), Size(size / 2, size - padding * 2))) } else { addArc(Rect(Offset(0f, padding), Size(size, size - padding * 2)), 90f, 180f) } if (neighbors.right) { addRect(Rect(Offset(size / 2, padding), Size(size / 2, size - padding * 2))) } else { addArc(Rect(Offset(0f, padding), Size(size, size - padding * 2)), -90f, 180f) } } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/QrShapes.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable /** * Shapes of QR code elements * * @param code shape of the QR code pattern. * @param darkPixel shape of the dark QR code pixels * @param lightPixel shape of the light QR code pixels * @param ball shape of the QR code eye balls * @param frame shape of the QR code eye frames * @param centralSymmetry if true, [ball] and [frame] shapes will be turned * to the center according to the current corner * */ @Immutable class QrShapes( val code: QrCodeShape = QrCodeShape.Default, val darkPixel: QrPixelShape = QrPixelShape.Default, val lightPixel: QrPixelShape = QrPixelShape.Default, val ball: QrBallShape = QrBallShape.Default, val frame: QrFrameShape = QrFrameShape.Default, val centralSymmetry: Boolean = true ) { fun copy( code: QrCodeShape = this.code, darkPixel: QrPixelShape = this.darkPixel, lightPixel: QrPixelShape = this.lightPixel, ball: QrBallShape = this.ball, frame: QrFrameShape = this.frame, centralSymmetry: Boolean = this.centralSymmetry ) = QrShapes( code = code, darkPixel = darkPixel, lightPixel = lightPixel, ball = ball, frame = frame, centralSymmetry = centralSymmetry ) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/Util.kt ================================================ package io.github.alexzhirkevich.qrose.options import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.Painter object EmptyPainter : Painter() { override val intrinsicSize: Size get() = Size.Zero override fun DrawScope.onDraw() { } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrColorsBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import io.github.alexzhirkevich.qrose.options.QrBrush import io.github.alexzhirkevich.qrose.options.QrOptions internal class InternalQrColorsBuilderScope( private val builder: QrOptions.Builder ) : QrColorsBuilderScope { override var dark: QrBrush get() = builder.colors.dark set(value) = with(builder) { colors = colors.copy( dark = value ) } override var light: QrBrush get() = builder.colors.light set(value) = with(builder) { colors = colors.copy( light = value ) } override var frame: QrBrush get() = builder.colors.frame set(value) = with(builder) { colors = colors.copy( frame = value ) } override var ball: QrBrush get() = builder.colors.ball set(value) = with(builder) { colors = colors.copy( ball = value ) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrLogoBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import androidx.compose.ui.graphics.painter.Painter import io.github.alexzhirkevich.qrose.options.QrLogoPadding import io.github.alexzhirkevich.qrose.options.QrLogoShape import io.github.alexzhirkevich.qrose.options.QrOptions internal class InternalQrLogoBuilderScope( private val builder: QrOptions.Builder, ) : QrLogoBuilderScope { override var painter: Painter? get() = builder.logo.painter set(value) = with(builder) { logo = logo.copy(painter = value) } override var size: Float get() = builder.logo.size set(value) = with(builder) { logo = logo.copy(size = value) } override var padding: QrLogoPadding get() = builder.logo.padding set(value) = with(builder) { logo = logo.copy(padding = value) } override var shape: QrLogoShape get() = builder.logo.shape set(value) = with(builder) { logo = logo.copy(shape = value) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import io.github.alexzhirkevich.qrose.DelicateQRoseApi import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrOptions internal class InternalQrOptionsBuilderScope( private val builder: QrOptions.Builder ) : QrOptionsBuilderScope { override var errorCorrectionLevel: QrErrorCorrectionLevel get() = builder.errorCorrectionLevel set(value) { builder.errorCorrectionLevel = value } @DelicateQRoseApi override var fourEyed: Boolean get() = builder.fourthEyeEnabled set(value) { builder.fourthEyeEnabled = value } override fun shapes( centralSymmetry: Boolean, block: QrShapesBuilderScope.() -> Unit ) { InternalQrShapesBuilderScope(builder, centralSymmetry) .apply(block) } override fun colors(block: QrColorsBuilderScope.() -> Unit) { InternalQrColorsBuilderScope(builder).apply(block) } override fun logo(block: QrLogoBuilderScope.() -> Unit) { InternalQrLogoBuilderScope(builder) .apply(block) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/InternalQrShapesBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import io.github.alexzhirkevich.qrose.options.QrBallShape import io.github.alexzhirkevich.qrose.options.QrCodeShape import io.github.alexzhirkevich.qrose.options.QrFrameShape import io.github.alexzhirkevich.qrose.options.QrOptions import io.github.alexzhirkevich.qrose.options.QrPixelShape internal class InternalQrShapesBuilderScope( private val builder: QrOptions.Builder, centralSymmetry: Boolean, ) : QrShapesBuilderScope { init { builder.shapes = builder.shapes.copy( centralSymmetry = centralSymmetry ) } override var pattern: QrCodeShape get() = builder.shapes.code set(value) = with(builder) { shapes = shapes.copy( code = value ) } override var darkPixel: QrPixelShape get() = builder.shapes.darkPixel set(value) = with(builder) { shapes = shapes.copy( darkPixel = value ) } override var lightPixel: QrPixelShape get() = builder.shapes.lightPixel set(value) = with(builder) { shapes = shapes.copy( lightPixel = value ) } override var ball: QrBallShape get() = builder.shapes.ball set(value) = with(builder) { shapes = shapes.copy( ball = value ) } override var frame: QrFrameShape get() = builder.shapes.frame set(value) = with(builder) { shapes = shapes.copy( frame = value ) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrColorsBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import io.github.alexzhirkevich.qrose.options.QrBrush /** * Colors of QR code elements * * @property dark Brush of the dark QR code pixels * @property light Brush of the light QR code pixels * @property ball Brush of the QR code eye balls * @property frame Brush of the QR code eye frames */ sealed interface QrColorsBuilderScope { var dark: QrBrush var light: QrBrush var frame: QrBrush var ball: QrBrush } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrLogoBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import androidx.compose.ui.graphics.painter.Painter import io.github.alexzhirkevich.qrose.options.QrLogoPadding import io.github.alexzhirkevich.qrose.options.QrLogoShape /** * Logo (middle image) of the QR code. * * @property painter Middle image. * @property size Image size in fraction relative to QR code size * @property padding Style and size of the QR code padding. * Can be used without [painter] if you want to place a logo manually. * @property shape Shape of the logo padding * */ sealed interface QrLogoBuilderScope { var painter: Painter? var size: Float var padding: QrLogoPadding var shape: QrLogoShape } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import io.github.alexzhirkevich.qrose.DelicateQRoseApi import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel.Auto sealed interface QrOptionsBuilderScope { /** * Level of error correction. * [Auto] by default * */ var errorCorrectionLevel: QrErrorCorrectionLevel /** * Enable 4th qr code eye. False by default * */ @DelicateQRoseApi var fourEyed: Boolean /** * Shapes of the QR code pattern and its parts. * */ fun shapes(centralSymmetry: Boolean = true, block: QrShapesBuilderScope.() -> Unit) /** * Colors of QR code parts. * */ fun colors(block: QrColorsBuilderScope.() -> Unit) /** * Middle image. * */ fun logo(block: QrLogoBuilderScope.() -> Unit) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/options/dsl/QrShapesBuilderScope.kt ================================================ package io.github.alexzhirkevich.qrose.options.dsl import io.github.alexzhirkevich.qrose.options.QrBallShape import io.github.alexzhirkevich.qrose.options.QrCodeShape import io.github.alexzhirkevich.qrose.options.QrFrameShape import io.github.alexzhirkevich.qrose.options.QrPixelShape /** * Shapes of QR code elements * * @property pattern Shape of the QR code pattern. * @property darkPixel Shape of the dark QR code pixels * @property lightPixel Shape of the light QR code pixels * @property ball Shape of the QR code eye balls * @property frame Shape of the QR code eye frames * */ sealed interface QrShapesBuilderScope { var darkPixel: QrPixelShape var lightPixel: QrPixelShape var ball: QrBallShape var frame: QrFrameShape var pattern: QrCodeShape } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCode.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode import io.github.alexzhirkevich.qrose.options.QrCodeMatrix import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType.DEFAULT import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType.NUMBERS import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType.UPPER_ALPHA_NUM import io.github.alexzhirkevich.qrose.qrcode.internals.BitBuffer import io.github.alexzhirkevich.qrose.qrcode.internals.Polynomial import io.github.alexzhirkevich.qrose.qrcode.internals.QR8BitByte import io.github.alexzhirkevich.qrose.qrcode.internals.QRAlphaNum import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.applyMaskPattern import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.setupBottomLeftPositionProbePattern import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.setupPositionAdjustPattern import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.setupTimingPattern import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.setupTopLeftPositionProbePattern import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.setupTopRightPositionProbePattern import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.setupTypeInfo import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSetup.setupTypeNumber import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSquare import io.github.alexzhirkevich.qrose.qrcode.internals.QRData import io.github.alexzhirkevich.qrose.qrcode.internals.QRNumber import io.github.alexzhirkevich.qrose.qrcode.internals.QRUtil import io.github.alexzhirkevich.qrose.qrcode.internals.RSBlock internal class QRCode @JvmOverloads constructor( private val data: String, private val errorCorrectionLevel: ErrorCorrectionLevel = ErrorCorrectionLevel.M, private val dataType: QRCodeDataType = QRUtil.getDataType(data) ) { private val qrCodeData: QRData = when (dataType) { NUMBERS -> QRNumber(data) UPPER_ALPHA_NUM -> QRAlphaNum(data) DEFAULT -> QR8BitByte(data) } companion object { const val DEFAULT_CELL_SIZE = 1 private const val PAD0 = 0xEC private const val PAD1 = 0x11 /** * Calculates a suitable value for the [dataType] field for you. */ @JvmStatic @JvmOverloads fun typeForDataAndECL( data: String, errorCorrectionLevel: ErrorCorrectionLevel, dataType: QRCodeDataType = QRUtil.getDataType(data) ): Int { val qrCodeData = when (dataType) { NUMBERS -> QRNumber(data) UPPER_ALPHA_NUM -> QRAlphaNum(data) DEFAULT -> QR8BitByte(data) } val dataLength = qrCodeData.length() for (typeNum in 1 until errorCorrectionLevel.maxTypeNum) { if (dataLength <= QRUtil.getMaxLength(typeNum, dataType, errorCorrectionLevel)) { return typeNum } } return 40 } } @JvmOverloads fun encode( type: Int = 0, maskPattern: MaskPattern? ): QrCodeMatrix { val finalType = if (type > 0) type else typeForDataAndECL(data, errorCorrectionLevel) val maskPattern = if (maskPattern == null) { var bestScore = Int.MAX_VALUE var bestPattern = MaskPattern.PATTERN000 for (pattern in MaskPattern.entries) { val matrix = encode(type, pattern) val score = calculateMaskPenalty(matrix) if (score < bestScore) { bestScore = score bestPattern = pattern } } bestPattern } else maskPattern val moduleCount = finalType * 4 + 17 val modules: Array> = Array(moduleCount) { Array(moduleCount) { null } } setupTopLeftPositionProbePattern(modules) setupTopRightPositionProbePattern(modules) setupBottomLeftPositionProbePattern(modules) setupPositionAdjustPattern(finalType, modules) setupTimingPattern(moduleCount, modules) setupTypeInfo(errorCorrectionLevel, maskPattern, moduleCount, modules) if (finalType >= 7) { setupTypeNumber(finalType, moduleCount, modules) } val data = try { createData(finalType) } catch (e: IllegalArgumentException) { if (finalType < 40) { return encode(finalType + 1, maskPattern) } else { throw e } } applyMaskPattern(data, maskPattern, moduleCount, modules) return QrCodeMatrix( modules.map { row -> row.map { pixel -> if (pixel?.dark == true) QrCodeMatrix.PixelType.DarkPixel else QrCodeMatrix.PixelType.LightPixel } } ) } private fun calculateMaskPenalty(matrix: QrCodeMatrix): Int { val size = matrix.size var penalty = 0 // Rule 1: длинные ряды одинакового цвета (по горизонтали) for (y in 0 until size) { var runColor = matrix[0, y] var runLength = 1 for (x in 1 until size) { val color = matrix[x, y] if (color == runColor) { runLength++ } else { if (runLength >= 5) penalty += 3 + (runLength - 5) runColor = color runLength = 1 } } if (runLength >= 5) penalty += 3 + (runLength - 5) } // Rule 1 (вертикали) for (x in 0 until size) { var runColor = matrix[x, 0] var runLength = 1 for (y in 1 until size) { val color = matrix[x, y] if (color == runColor) { runLength++ } else { if (runLength >= 5) penalty += 3 + (runLength - 5) runColor = color runLength = 1 } } if (runLength >= 5) penalty += 3 + (runLength - 5) } // Rule 2: квадраты 2x2 одного цвета for (y in 0 until size - 1) { for (x in 0 until size - 1) { val color = matrix[x, y] if (color == matrix[x + 1, y] && color == matrix[x, y + 1] && color == matrix[x + 1, y + 1] ) { penalty += 3 } } } // Rule 3: паттерны типа 1011101 (finder-like) for (y in 0 until size) { for (x in 0 until size - 6) { if (matrix[x, y] == QrCodeMatrix.PixelType.DarkPixel && matrix[x + 1, y] == QrCodeMatrix.PixelType.LightPixel && matrix[x + 2, y] == QrCodeMatrix.PixelType.DarkPixel && matrix[x + 3, y] == QrCodeMatrix.PixelType.DarkPixel && matrix[x + 4, y] == QrCodeMatrix.PixelType.DarkPixel && matrix[x + 5, y] == QrCodeMatrix.PixelType.LightPixel && matrix[x + 6, y] == QrCodeMatrix.PixelType.DarkPixel ) { penalty += 40 } } } for (x in 0 until size) { for (y in 0 until size - 6) { if (matrix[x, y] == QrCodeMatrix.PixelType.DarkPixel && matrix[x, y + 1] == QrCodeMatrix.PixelType.LightPixel && matrix[x, y + 2] == QrCodeMatrix.PixelType.DarkPixel && matrix[x, y + 3] == QrCodeMatrix.PixelType.DarkPixel && matrix[x, y + 4] == QrCodeMatrix.PixelType.DarkPixel && matrix[x, y + 5] == QrCodeMatrix.PixelType.LightPixel && matrix[x, y + 6] == QrCodeMatrix.PixelType.DarkPixel ) { penalty += 40 } } } // Rule 4: баланс тёмных и светлых (должно быть около 50%) var darkCount = 0 for (y in 0 until size) { for (x in 0 until size) { if (matrix[x, y] == QrCodeMatrix.PixelType.DarkPixel) darkCount++ } } val total = size * size val percent = darkCount * 100 / total penalty += (kotlin.math.abs(percent - 50) / 5) * 10 return penalty } private fun createData(type: Int): IntArray { val rsBlocks = RSBlock.getRSBlocks(type, errorCorrectionLevel) val buffer = BitBuffer() buffer.put(qrCodeData.dataType.value, 4) buffer.put(qrCodeData.length(), qrCodeData.getLengthInBits(type)) qrCodeData.write(buffer) val totalDataCount = rsBlocks.sumOf { it.dataCount } * 8 if (buffer.lengthInBits > totalDataCount) { throw IllegalArgumentException("Code length overflow (${buffer.lengthInBits} > $totalDataCount)") } if (buffer.lengthInBits + 4 <= totalDataCount) { buffer.put(0, 4) } while (buffer.lengthInBits % 8 != 0) { buffer.put(false) } while (true) { if (buffer.lengthInBits >= totalDataCount) { break } buffer.put(PAD0, 8) if (buffer.lengthInBits >= totalDataCount) { break } buffer.put(PAD1, 8) } return createBytes(buffer, rsBlocks) } private fun createBytes(buffer: BitBuffer, rsBlocks: Array): IntArray { var offset = 0 var maxDcCount = 0 var maxEcCount = 0 var totalCodeCount = 0 val dcData = Array(rsBlocks.size) { IntArray(0) } val ecData = Array(rsBlocks.size) { IntArray(0) } rsBlocks.forEachIndexed { i, it -> val dcCount = it.dataCount val ecCount = it.totalCount - dcCount totalCodeCount += it.totalCount maxDcCount = maxDcCount.coerceAtLeast(dcCount) maxEcCount = maxEcCount.coerceAtLeast(ecCount) // Init dcData[i] dcData[i] = IntArray(dcCount) { idx -> 0xff and buffer.buffer[idx + offset] } offset += dcCount // Init ecData[i] val rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount) val rawPoly = Polynomial(dcData[i], rsPoly.len() - 1) val modPoly = rawPoly.mod(rsPoly) val ecDataSize = rsPoly.len() - 1 ecData[i] = IntArray(ecDataSize) { idx -> val modIndex = idx + modPoly.len() - ecDataSize if ((modIndex >= 0)) modPoly[modIndex] else 0 } } var index = 0 val data = IntArray(totalCodeCount) for (i in 0 until maxDcCount) { for (r in rsBlocks.indices) { if (i < dcData[r].size) { data[index++] = dcData[r][i] } } } for (i in 0 until maxEcCount) { for (r in rsBlocks.indices) { if (i < ecData[r].size) { data[index++] = ecData[r][i] } } } return data } override fun toString(): String = "QRCode(data=$data" + ", errorCorrectionLevel=$errorCorrectionLevel" + ", dataType=$dataType" + ", qrCodeData=${qrCodeData::class.simpleName}" + ")" } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/QRCodeEnums.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel.H import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel.Q /** * The level of Error Correction to apply to the QR Code image. The Higher the Error Correction, the lower quality * **print** the QRCode can be (think of "wow, even with the paper a bit crumpled, it still read the QR Code!" - that * is likely a [Q] or [H] error correction). * * The trade-off is the amount of data you can encode. The higher the error correction level, the less amount of data * you'll be able to encode. * * Please consult [Kazuhiko's Online Demo](https://kazuhikoarase.github.io/qrcode-generator/js/demo/) where at the time * of writing a handy table showed how many bytes can be encoded given a data type ([QRCodeDataType]) and Error Correction Level. * * This library automatically tries to fit ~2048 bytes into the QR Code regardless of error correction level. That is * the reason and meaning of [maxTypeNum]. * * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/ErrorCorrectionLevel.java) * * @param value Value associated with this error correction level * @param maxTypeNum Maximum `type` value which can fit 2048 bytes. Used to automatically calculate the `type` value. * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal enum class ErrorCorrectionLevel(val value: Int, val maxTypeNum: Int) { L(1, 21), M(0, 25), Q(3, 30), H(2, 34) } /** * Patterns to apply to the QRCode. They change how the QRCode looks in the end. * * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/MaskPattern.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ enum class MaskPattern { /** This is the default pattern (no pattern is applied) */ PATTERN000, PATTERN001, PATTERN010, PATTERN011, PATTERN100, PATTERN101, PATTERN110, PATTERN111 } /** * QRCode Modes. Basically represents which kind of data is being encoded. * * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/Mode.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal enum class QRCodeDataType(val value: Int) { /** Strictly numerical data. Like huge integers. These can be way bigger than [Long.MAX_VALUE]. */ NUMBERS(1 shl 0), /** Represents Uppercase Alphanumerical data. Allowed characters: `[0-9A-Z $%*+\-./:]`. */ UPPER_ALPHA_NUM(1 shl 1), /** This can be any kind of data. With this you can encode Strings, URLs, images, files, anything. */ DEFAULT(1 shl 2) } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/BitBuffer.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/BitBuffer.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal class BitBuffer { var buffer: IntArray private set var lengthInBits: Int private set private val increments = 32 private operator fun get(index: Int): Boolean = buffer[index / 8] ushr 7 - index % 8 and 1 == 1 fun put(num: Int, length: Int) { for (i in 0 until length) { put(num ushr length - i - 1 and 1 == 1) } } fun put(bit: Boolean) { if (lengthInBits == buffer.size * 8) { buffer = buffer.copyOf(buffer.size + increments) } if (bit) { buffer[lengthInBits / 8] = buffer[lengthInBits / 8] or (0x80 ushr lengthInBits % 8) } lengthInBits++ } init { buffer = IntArray(increments) lengthInBits = 0 } override fun toString(): String { val buffer = StringBuilder() for (i in 0 until lengthInBits) { buffer.append(if (get(i)) '1' else '0') } return buffer.toString() } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/ErrorMessage.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals /** * Helps generates error messages. * * @author Rafael Lins - g0dkar */ internal object ErrorMessage { private const val BUG_REPORT_URL = "https://github.com/g0dkar/qrcode-kotlin/issues/new?assignees=g0dkar&labels=bug&template=bug_report.md&title=" /** Generates an error message with a "Please report" appended to it. */ fun error(string: String) = "$string - Please, report this error via this URL: $BUG_REPORT_URL" } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/Polynomial.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals import io.github.alexzhirkevich.qrose.qrcode.internals.QRMath.gexp import io.github.alexzhirkevich.qrose.qrcode.internals.QRMath.glog /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/Polynomial.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal class Polynomial(num: IntArray, shift: Int = 0) { private val data: IntArray init { val offset = num.indexOfFirst { it != 0 }.coerceAtLeast(0) this.data = IntArray(num.size - offset + shift) { 0 } arraycopy(num, offset, this.data, 0, num.size - offset) } private fun arraycopy(from: IntArray, fromPos: Int, to: IntArray, toPos: Int, length: Int) { for (i in 0 until length) { to[toPos + i] = from[fromPos + i] } } operator fun get(i: Int) = data[i] fun len(): Int = data.size fun multiply(other: Polynomial): Polynomial = IntArray(len() + other.len() - 1) { 0 } .let { for (i in 0 until len()) { for (j in 0 until other.len()) { it[i + j] = it[i + j] xor gexp(glog(this[i]) + glog(other[j])) } } Polynomial(it) } fun mod(other: Polynomial): Polynomial = if (len() - other.len() < 0) { this } else { val ratio = glog(this[0]) - glog(other[0]) val result = data.copyOf() other.data.forEachIndexed { i, it -> result[i] = result[i] xor gexp(glog(it) + ratio) } Polynomial(result).mod(other) } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSetup.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel import io.github.alexzhirkevich.qrose.qrcode.MaskPattern import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.BOTTOM_LEFT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.BOTTOM_MID import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.BOTTOM_RIGHT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.CENTER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.LEFT_MID import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.MARGIN import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.RIGHT_MID import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.TOP_LEFT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.TOP_MID import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.TOP_RIGHT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSquareType.POSITION_PROBE /** * Object with helper methods and constants to setup stuff into the QRCode such as Position Probes and Timing Probes. * * @author Rafael Lins - g0dkar */ internal object QRCodeSetup { private const val DEFAULT_PROBE_SIZE = 7 fun setupTopLeftPositionProbePattern( modules: Array>, probeSize: Int = DEFAULT_PROBE_SIZE ) { setupPositionProbePattern(0, 0, modules, probeSize) } fun setupTopRightPositionProbePattern( modules: Array>, probeSize: Int = DEFAULT_PROBE_SIZE ) { setupPositionProbePattern(modules.size - probeSize, 0, modules, probeSize) } fun setupBottomLeftPositionProbePattern( modules: Array>, probeSize: Int = DEFAULT_PROBE_SIZE ) { setupPositionProbePattern(0, modules.size - probeSize, modules, probeSize) } fun setupPositionProbePattern( rowOffset: Int, colOffset: Int, modules: Array>, probeSize: Int = DEFAULT_PROBE_SIZE ) { val modulesSize = modules.size for (row in -1..probeSize) { for (col in -1..probeSize) { if (!isInsideModules(row, rowOffset, col, colOffset, modulesSize)) { continue } val isDark = isTopBottomRowSquare(row, col, probeSize) || isLeftRightColSquare(row, col, probeSize) || isMidSquare(row, col, probeSize) val region = findSquareRegion(row, col, probeSize) modules[row + rowOffset][col + colOffset] = QRCodeSquare( dark = isDark, row = row + rowOffset, col = col + colOffset, squareInfo = QRCodeSquareInfo(POSITION_PROBE, region), moduleSize = modulesSize ) } } } private fun isInsideModules( row: Int, rowOffset: Int, col: Int, colOffset: Int, modulesSize: Int ) = row + rowOffset in 0 until modulesSize && col + colOffset in 0 until modulesSize private fun isTopBottomRowSquare(row: Int, col: Int, probeSize: Int) = col in 0 until probeSize && (row == 0 || row == probeSize - 1) private fun isLeftRightColSquare(row: Int, col: Int, probeSize: Int) = row in 0 until probeSize && (col == 0 || col == probeSize - 1) private fun isMidSquare(row: Int, col: Int, probeSize: Int) = row in 2 until (probeSize - 2) && 2 <= col && col <= probeSize - 3 private fun findSquareRegion(row: Int, col: Int, probeSize: Int) = when (row) { 0 -> when (col) { // 0 x ?: ┌───┐ 0 -> TOP_LEFT_CORNER // 0 x 0: ┌ probeSize - 1 -> TOP_RIGHT_CORNER // 0 x MAX: ┐ probeSize -> MARGIN // Outside boundaries else -> TOP_MID // between: ─ } probeSize - 1 -> when (col) { // MAX x ?: └───┘ 0 -> BOTTOM_LEFT_CORNER // MAX x 0: └ probeSize - 1 -> BOTTOM_RIGHT_CORNER // MAX x MAX: ┘ probeSize -> MARGIN // Outside boundaries else -> BOTTOM_MID // between: ─ } probeSize -> MARGIN // Outside boundaries else -> when (col) { // Inside boundaries but not in any edge 0 -> LEFT_MID probeSize - 1 -> RIGHT_MID probeSize -> MARGIN // Outside boundaries else -> CENTER // Middle/Center square } } fun setupPositionAdjustPattern(type: Int, modules: Array>) { val pos = QRUtil.getPatternPosition(type) for (i in pos.indices) { for (j in pos.indices) { val row = pos[i] val col = pos[j] if (modules[row][col] != null) { continue } for (r in -2..2) { for (c in -2..2) { modules[row + r][col + c] = QRCodeSquare( dark = r == -2 || r == 2 || c == -2 || c == 2 || r == 0 && c == 0, row = row + r, col = col + c, squareInfo = QRCodeSquareInfo( QRCodeSquareType.POSITION_ADJUST, QRCodeRegion.UNKNOWN ), moduleSize = modules.size ) } } } } } fun setupTimingPattern(moduleCount: Int, modules: Array>) { for (r in 8 until moduleCount - 8) { if (modules[r][6] != null) { continue } modules[r][6] = QRCodeSquare( dark = r % 2 == 0, row = r, col = 6, squareInfo = QRCodeSquareInfo( QRCodeSquareType.TIMING_PATTERN, QRCodeRegion.UNKNOWN ), moduleSize = modules.size ) } for (c in 8 until moduleCount - 8) { if (modules[6][c] != null) { continue } modules[6][c] = QRCodeSquare( dark = c % 2 == 0, row = 6, col = c, squareInfo = QRCodeSquareInfo( QRCodeSquareType.TIMING_PATTERN, QRCodeRegion.UNKNOWN ), moduleSize = modules.size ) } } fun setupTypeInfo( errorCorrectionLevel: ErrorCorrectionLevel, maskPattern: MaskPattern, moduleCount: Int, modules: Array> ) { val data = errorCorrectionLevel.value shl 3 or maskPattern.ordinal val bits = QRUtil.getBCHTypeInfo(data) for (i in 0..14) { val mod = bits shr i and 1 == 1 if (i < 6) { set(i, 8, mod, modules) } else if (i < 8) { set(i + 1, 8, mod, modules) } else { set(moduleCount - 15 + i, 8, mod, modules) } } for (i in 0..14) { val mod = bits shr i and 1 == 1 if (i < 8) { set(8, moduleCount - i - 1, mod, modules) } else if (i < 9) { set(8, 15 - i, mod, modules) } else { set(8, 15 - i - 1, mod, modules) } } set(moduleCount - 8, 8, true, modules) } fun setupTypeNumber(type: Int, moduleCount: Int, modules: Array>) { val bits = QRUtil.getBCHTypeNumber(type) for (i in 0..17) { val mod = bits shr i and 1 == 1 set(i / 3, i % 3 + moduleCount - 8 - 3, mod, modules) } for (i in 0..17) { val mod = bits shr i and 1 == 1 set(i % 3 + moduleCount - 8 - 3, i / 3, mod, modules) } } fun applyMaskPattern( data: IntArray, maskPattern: MaskPattern, moduleCount: Int, modules: Array> ) { var inc = -1 var bitIndex = 7 var byteIndex = 0 var row = moduleCount - 1 var col = moduleCount - 1 while (col > 0) { if (col == 6) { col-- } while (true) { for (c in 0..1) { if (modules[row][col - c] == null) { var dark = false if (byteIndex < data.size) { dark = (data[byteIndex] ushr bitIndex) and 1 == 1 } val mask = QRUtil.getMask(maskPattern, row, col - c) if (mask) { dark = !dark } set(row, col - c, dark, modules) bitIndex-- if (bitIndex == -1) { byteIndex++ bitIndex = 7 } } } row += inc if (row < 0 || moduleCount <= row) { row -= inc inc = -inc break } } col -= 2 } } private fun set(row: Int, col: Int, value: Boolean, modules: Array>) { val qrCodeSquare = modules[row][col] if (qrCodeSquare != null) { qrCodeSquare.dark = value } else { modules[row][col] = QRCodeSquare( dark = value, row = row, col = col, moduleSize = modules.size ) } } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRCodeSquare.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.BOTTOM_LEFT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.BOTTOM_RIGHT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.TOP_LEFT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.TOP_RIGHT_CORNER import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeRegion.UNKNOWN import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSquareType.DEFAULT import io.github.alexzhirkevich.qrose.qrcode.internals.QRCodeSquareType.MARGIN /** * Represents a single QRCode square unit. It has information about its "color" (either dark or bright), * its position (row and column) and what it represents. * * It can be part of a position probe (aka those big squares at the extremities), part of a position * adjustment square, part of a timing pattern or just another square as any other :) * * @author Rafael Lins - g0dkar */ internal class QRCodeSquare( /** Is this a painted square? */ var dark: Boolean, /** The row (top-to-bottom) that this square represents. */ val row: Int, /** The column (left-to-right) that this square represents. */ val col: Int, /** How big is the whole QRCode matrix? (e.g. if this is "16" then this is part of a 16x16 matrix) */ val moduleSize: Int, /** What does this square represent within the QRCode? */ val squareInfo: QRCodeSquareInfo = QRCodeSquareInfo(DEFAULT, UNKNOWN) ) /** * Returns information on the square itself. It has the [type] of square and its [region] within its relative type. * * For example, if `type = POSITION_PROBE` then [region] will represent where within the Position Probe this square * is positioned. A [region] of [QRCodeRegion.TOP_LEFT_CORNER] for example represents the top left corner of the * position probe this particular square is part of (a QRCode have 3 position probes). */ internal class QRCodeSquareInfo( private val type: QRCodeSquareType, private val region: QRCodeRegion ) { companion object { internal fun margin() = QRCodeSquareInfo(MARGIN, QRCodeRegion.MARGIN) } } /** * The types available for squares in a QRCode. * * @author Rafael Lins - g0dkar */ internal enum class QRCodeSquareType { /** Part of a position probe: one of those big squares at the extremities of the QRCode. */ POSITION_PROBE, /** Part of a position adjustment pattern: just like a position probe, but much smaller. */ POSITION_ADJUST, /** Part of the timing pattern. Make it a square like any other :) */ TIMING_PATTERN, /** Anything special. Just a square. */ DEFAULT, /** Used to point out that this is part of the margin. */ MARGIN } /** * Represents which part/region of a given square type a particular, single square is. * * For example, a position probe is visually composed of multiple squares that form a bigger one. * * For example, this is what a position probe normally looks like (squares spaced for ease of understanding): * * ``` * A■■■■B * ■ ■■ ■ * ■ ■■ ■ * C■■■■D * ``` * * The positions marked with `A`, `B`, `C` and `D` would be regions [TOP_LEFT_CORNER], [TOP_RIGHT_CORNER], * [BOTTOM_LEFT_CORNER] and [BOTTOM_RIGHT_CORNER] respectively. */ internal enum class QRCodeRegion { TOP_LEFT_CORNER, TOP_RIGHT_CORNER, TOP_MID, LEFT_MID, RIGHT_MID, CENTER, BOTTOM_LEFT_CORNER, BOTTOM_RIGHT_CORNER, BOTTOM_MID, MARGIN, UNKNOWN } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType.DEFAULT import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType.NUMBERS import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType.UPPER_ALPHA_NUM /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/QRData.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal abstract class QRData(val dataType: QRCodeDataType, val data: String) { abstract fun length(): Int abstract fun write(buffer: BitBuffer) fun getLengthInBits(type: Int): Int = when (type) { in 1..9 -> { when (dataType) { NUMBERS -> 10 UPPER_ALPHA_NUM -> 9 DEFAULT -> 8 } } in 1..26 -> { when (dataType) { NUMBERS -> 12 UPPER_ALPHA_NUM -> 11 DEFAULT -> 16 } } in 1..40 -> { when (dataType) { NUMBERS -> 14 UPPER_ALPHA_NUM -> 13 DEFAULT -> 16 } } else -> { throw IllegalArgumentException("'type' must be greater than 0 and cannot be greater than 40: $type") } } } /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/QR8BitByte.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal class QR8BitByte(data: String) : QRData(DEFAULT, data) { private val dataBytes = data.encodeToByteArray() override fun write(buffer: BitBuffer) { for (i in dataBytes.indices) { buffer.put(dataBytes[i].toInt(), 8) } } override fun length(): Int = dataBytes.size } /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/QRAlphaNum.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal class QRAlphaNum(data: String) : QRData(UPPER_ALPHA_NUM, data) { override fun write(buffer: BitBuffer) { var i = 0 val dataLength = data.length while (i + 1 < dataLength) { buffer.put(charCode(data[i]) * 45 + charCode(data[i + 1]), 11) i += 2 } if (i < dataLength) { buffer.put(charCode(data[i]), 6) } } override fun length(): Int = data.length private fun charCode(c: Char): Int = when (c) { in '0'..'9' -> c - '0' in 'A'..'Z' -> c - 'A' + 10 else -> { when (c) { ' ' -> 36 '$' -> 37 '%' -> 38 '*' -> 39 '+' -> 40 '-' -> 41 '.' -> 42 '/' -> 43 ':' -> 44 else -> throw IllegalArgumentException("Illegal character: $c") } } } } /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/QRNumber.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal class QRNumber(data: String) : QRData(NUMBERS, data) { override fun write(buffer: BitBuffer) { var i = 0 val len = length() while (i + 2 < len) { val num = data.substring(i, i + 3).toInt() buffer.put(num, 10) i += 3 } if (i < len) { if (len - i == 1) { val num = data.substring(i, i + 1).toInt() buffer.put(num, 4) } else if (len - i == 2) { val num = data.substring(i, i + 2).toInt() buffer.put(num, 7) } } } override fun length(): Int = data.length } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRMath.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/QRMath.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal object QRMath { private val EXP_TABLE = IntArray(256) private val LOG_TABLE = IntArray(256) fun glog(n: Int): Int = LOG_TABLE[n] fun gexp(n: Int): Int { var i = n while (i < 0) { i += 255 } while (i >= 256) { i -= 255 } return EXP_TABLE[i] } init { for (i in 0..7) { EXP_TABLE[i] = 1 shl i } for (i in 8..255) { EXP_TABLE[i] = ( EXP_TABLE[i - 4] xor EXP_TABLE[i - 5] xor EXP_TABLE[i - 6] xor EXP_TABLE[i - 8] ) } for (i in 0..254) { LOG_TABLE[EXP_TABLE[i]] = i } } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/QRUtil.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel import io.github.alexzhirkevich.qrose.qrcode.MaskPattern import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN000 import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN001 import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN010 import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN011 import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN100 import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN101 import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN110 import io.github.alexzhirkevich.qrose.qrcode.MaskPattern.PATTERN111 import io.github.alexzhirkevich.qrose.qrcode.QRCodeDataType /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/QRUtil.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal object QRUtil { fun getPatternPosition(typeNumber: Int): IntArray = PATTERN_POSITION_TABLE[typeNumber - 1] private val PATTERN_POSITION_TABLE = arrayOf( intArrayOf(), intArrayOf(6, 18), intArrayOf(6, 22), intArrayOf(6, 26), intArrayOf(6, 30), intArrayOf(6, 34), intArrayOf(6, 22, 38), intArrayOf(6, 24, 42), intArrayOf(6, 26, 46), intArrayOf(6, 28, 50), intArrayOf(6, 30, 54), intArrayOf(6, 32, 58), intArrayOf(6, 34, 62), intArrayOf(6, 26, 46, 66), intArrayOf(6, 26, 48, 70), intArrayOf(6, 26, 50, 74), intArrayOf(6, 30, 54, 78), intArrayOf(6, 30, 56, 82), intArrayOf(6, 30, 58, 86), intArrayOf(6, 34, 62, 90), intArrayOf(6, 28, 50, 72, 94), intArrayOf(6, 26, 50, 74, 98), intArrayOf(6, 30, 54, 78, 102), intArrayOf(6, 28, 54, 80, 106), intArrayOf(6, 32, 58, 84, 110), intArrayOf(6, 30, 58, 86, 114), intArrayOf(6, 34, 62, 90, 118), intArrayOf(6, 26, 50, 74, 98, 122), intArrayOf(6, 30, 54, 78, 102, 126), intArrayOf(6, 26, 52, 78, 104, 130), intArrayOf(6, 30, 56, 82, 108, 134), intArrayOf(6, 34, 60, 86, 112, 138), intArrayOf(6, 30, 58, 86, 114, 142), intArrayOf(6, 34, 62, 90, 118, 146), intArrayOf(6, 30, 54, 78, 102, 126, 150), intArrayOf(6, 24, 50, 76, 102, 128, 154), intArrayOf(6, 28, 54, 80, 106, 132, 158), intArrayOf(6, 32, 58, 84, 110, 136, 162), intArrayOf(6, 26, 54, 82, 110, 138, 166), intArrayOf(6, 30, 58, 86, 114, 142, 170) ) private val MAX_LENGTH = arrayOf( arrayOf( intArrayOf(41, 25, 17, 10), intArrayOf(34, 20, 14, 8), intArrayOf(27, 16, 11, 7), intArrayOf(17, 10, 7, 4) ), arrayOf( intArrayOf(77, 47, 32, 20), intArrayOf(63, 38, 26, 16), intArrayOf(48, 29, 20, 12), intArrayOf(34, 20, 14, 8) ), arrayOf( intArrayOf(127, 77, 53, 32), intArrayOf(101, 61, 42, 26), intArrayOf(77, 47, 32, 20), intArrayOf(58, 35, 24, 15) ), arrayOf( intArrayOf(187, 114, 78, 48), intArrayOf(149, 90, 62, 38), intArrayOf(111, 67, 46, 28), intArrayOf(82, 50, 34, 21) ), arrayOf( intArrayOf(255, 154, 106, 65), intArrayOf(202, 122, 84, 52), intArrayOf(144, 87, 60, 37), intArrayOf(106, 64, 44, 27) ), arrayOf( intArrayOf(322, 195, 134, 82), intArrayOf(255, 154, 106, 65), intArrayOf(178, 108, 74, 45), intArrayOf(139, 84, 58, 36) ), arrayOf( intArrayOf(370, 224, 154, 95), intArrayOf(293, 178, 122, 75), intArrayOf(207, 125, 86, 53), intArrayOf(154, 93, 64, 39) ), arrayOf( intArrayOf(461, 279, 192, 118), intArrayOf(365, 221, 152, 93), intArrayOf(259, 157, 108, 66), intArrayOf(202, 122, 84, 52) ), arrayOf( intArrayOf(552, 335, 230, 141), intArrayOf(432, 262, 180, 111), intArrayOf(312, 189, 130, 80), intArrayOf(235, 143, 98, 60) ), arrayOf( intArrayOf(652, 395, 271, 167), intArrayOf(513, 311, 213, 131), intArrayOf(364, 221, 151, 93), intArrayOf(288, 174, 119, 74) ), arrayOf( intArrayOf(772, 468, 321, 198), intArrayOf(604, 366, 251, 155), intArrayOf(427, 259, 177, 109), intArrayOf(331, 200, 137, 85) ), arrayOf( intArrayOf(883, 535, 367, 226), intArrayOf(691, 419, 287, 177), intArrayOf(489, 296, 203, 125), intArrayOf(374, 227, 155, 96) ), arrayOf( intArrayOf(1022, 619, 425, 262), intArrayOf(796, 483, 331, 204), intArrayOf(580, 352, 241, 149), intArrayOf(427, 259, 177, 109) ), arrayOf( intArrayOf(1101, 667, 458, 282), intArrayOf(871, 528, 362, 223), intArrayOf(621, 376, 258, 159), intArrayOf(468, 283, 194, 120) ), arrayOf( intArrayOf(1250, 758, 520, 320), intArrayOf(991, 600, 412, 254), intArrayOf(703, 426, 292, 180), intArrayOf(530, 321, 220, 136) ), arrayOf( intArrayOf(1408, 854, 586, 361), intArrayOf(1082, 656, 450, 277), intArrayOf(775, 470, 322, 198), intArrayOf(602, 365, 250, 154) ), arrayOf( intArrayOf(1548, 938, 644, 397), intArrayOf(1212, 734, 504, 310), intArrayOf(876, 531, 364, 224), intArrayOf(674, 408, 280, 173) ), arrayOf( intArrayOf(1725, 1046, 718, 442), intArrayOf(1346, 816, 560, 345), intArrayOf(948, 574, 394, 243), intArrayOf(746, 452, 310, 191) ), arrayOf( intArrayOf(1903, 1153, 792, 488), intArrayOf(1500, 909, 624, 384), intArrayOf(1063, 644, 442, 272), intArrayOf(813, 493, 338, 208) ), arrayOf( intArrayOf(2061, 1249, 858, 528), intArrayOf(1600, 970, 666, 410), intArrayOf(1159, 702, 482, 297), intArrayOf(919, 557, 382, 235) ), arrayOf( intArrayOf(2232, 1352, 929, 572), intArrayOf(1708, 1035, 711, 438), intArrayOf(1224, 742, 509, 314), intArrayOf(969, 587, 403, 248) ), arrayOf( intArrayOf(2409, 1460, 1003, 618), intArrayOf(1872, 1134, 779, 480), intArrayOf(1358, 823, 565, 348), intArrayOf(1056, 640, 439, 270) ), arrayOf( intArrayOf(2620, 1588, 1091, 672), intArrayOf(2059, 1248, 857, 528), intArrayOf(1468, 890, 611, 376), intArrayOf(1108, 672, 461, 284) ), arrayOf( intArrayOf(2812, 1704, 1171, 721), intArrayOf(2188, 1326, 911, 561), intArrayOf(1588, 963, 661, 407), intArrayOf(1228, 744, 511, 315) ), arrayOf( intArrayOf(3057, 1853, 1273, 784), intArrayOf(2395, 1451, 997, 614), intArrayOf(1718, 1041, 715, 440), intArrayOf(1286, 779, 535, 330) ), arrayOf( intArrayOf(3283, 1990, 1367, 842), intArrayOf(2544, 1542, 1059, 652), intArrayOf(1804, 1094, 751, 462), intArrayOf(1425, 864, 593, 365) ), arrayOf( intArrayOf(3517, 2132, 1465, 902), intArrayOf(2701, 1637, 1125, 692), intArrayOf(1933, 1172, 805, 496), intArrayOf(1501, 910, 625, 385) ), arrayOf( intArrayOf(3669, 2223, 1528, 940), intArrayOf(2857, 1732, 1190, 732), intArrayOf(2085, 1263, 868, 534), intArrayOf(1581, 958, 658, 405) ), arrayOf( intArrayOf(3909, 2369, 1628, 1002), intArrayOf(3035, 1839, 1264, 778), intArrayOf(2181, 1322, 908, 559), intArrayOf(1677, 1016, 698, 430) ), arrayOf( intArrayOf(4158, 2520, 1732, 1066), intArrayOf(3289, 1994, 1370, 843), intArrayOf(2358, 1429, 982, 604), intArrayOf(1782, 1080, 742, 457) ), arrayOf( intArrayOf(4417, 2677, 1840, 1132), intArrayOf(3486, 2113, 1452, 894), intArrayOf(2473, 1499, 1030, 634), intArrayOf(1897, 1150, 790, 486) ), arrayOf( intArrayOf(4686, 2840, 1952, 1201), intArrayOf(3693, 2238, 1538, 947), intArrayOf(2670, 1618, 1112, 684), intArrayOf(2022, 1226, 842, 518) ), arrayOf( intArrayOf(4965, 3009, 2068, 1273), intArrayOf(3909, 2369, 1628, 1002), intArrayOf(2805, 1700, 1168, 719), intArrayOf(2157, 1307, 898, 553) ), arrayOf( intArrayOf(5253, 3183, 2188, 1347), intArrayOf(4134, 2506, 1722, 1060), intArrayOf(2949, 1787, 1228, 756), intArrayOf(2301, 1394, 958, 590) ) ) fun getMaxLength( typeNumber: Int, dataType: QRCodeDataType, errorCorrectionLevel: ErrorCorrectionLevel ): Int = MAX_LENGTH[typeNumber - 1][errorCorrectionLevel.ordinal][dataType.ordinal] fun getErrorCorrectPolynomial(errorCorrectLength: Int): Polynomial { var a = Polynomial(intArrayOf(1)) for (i in 0 until errorCorrectLength) { a = a.multiply(Polynomial(intArrayOf(1, QRMath.gexp(i)))) } return a } /** * Each Mask Pattern [applies a different formula](https://www.thonky.com/qr-code-tutorial/mask-patterns). */ fun getMask(maskPattern: MaskPattern, i: Int, j: Int): Boolean = when (maskPattern) { PATTERN000 -> (i + j) % 2 == 0 PATTERN001 -> i % 2 == 0 PATTERN010 -> j % 3 == 0 PATTERN011 -> (i + j) % 3 == 0 PATTERN100 -> (i / 2 + j / 3) % 2 == 0 PATTERN101 -> (i * j) % 2 + (i * j) % 3 == 0 PATTERN110 -> ((i * j) % 2 + (i * j) % 3) % 2 == 0 PATTERN111 -> ((i * j) % 3 + (i + j) % 2) % 2 == 0 } /** * Returns a suitable [QRCodeDataType] to the given input String based on a simple matching. * * @see QRCodeDataType */ fun getDataType(s: String): QRCodeDataType = if (isAlphaNum(s)) { if (isNumber(s)) { QRCodeDataType.NUMBERS } else { QRCodeDataType.UPPER_ALPHA_NUM } } else { QRCodeDataType.DEFAULT } private fun isNumber(s: String) = s.matches(Regex("^\\d+$")) private fun isAlphaNum(s: String) = s.matches(Regex("^[0-9A-Z $%*+\\-./:]+$")) private const val G15 = ( 1 shl 10 or (1 shl 8) or (1 shl 5) or (1 shl 4) or (1 shl 2) or (1 shl 1) or (1 shl 0) ) private const val G18 = ( 1 shl 12 or (1 shl 11) or (1 shl 10) or (1 shl 9) or (1 shl 8) or (1 shl 5) or (1 shl 2) or (1 shl 0) ) private const val G15_MASK = ( 1 shl 14 or (1 shl 12) or (1 shl 10) or (1 shl 4) or (1 shl 1) ) fun getBCHTypeInfo(data: Int): Int { var d = data shl 10 while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { d = d xor (G15 shl getBCHDigit(d) - getBCHDigit(G15)) } return data shl 10 or d xor G15_MASK } fun getBCHTypeNumber(data: Int): Int { var d = data shl 12 while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { d = d xor (G18 shl getBCHDigit(d) - getBCHDigit(G18)) } return data shl 12 or d } private fun getBCHDigit(data: Int): Int { var i = data var digit = 0 while (i != 0) { digit++ i = i ushr 1 } return digit } } ================================================ FILE: lib/qrose/src/main/java/io/github/alexzhirkevich/qrose/qrcode/internals/RSBlock.kt ================================================ package io.github.alexzhirkevich.qrose.qrcode.internals import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel /** * Rewritten in Kotlin from the [original (GitHub)](https://github.com/kazuhikoarase/qrcode-generator/blob/master/java/src/main/java/com/d_project/qrcode/RSBlock.java) * * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ internal class RSBlock(val totalCount: Int, val dataCount: Int) { companion object { private val RS_BLOCK_TABLE = arrayOf( intArrayOf(1, 26, 19), intArrayOf(1, 26, 16), intArrayOf(1, 26, 13), intArrayOf(1, 26, 9), intArrayOf(1, 44, 34), intArrayOf(1, 44, 28), intArrayOf(1, 44, 22), intArrayOf(1, 44, 16), intArrayOf(1, 70, 55), intArrayOf(1, 70, 44), intArrayOf(2, 35, 17), intArrayOf(2, 35, 13), intArrayOf(1, 100, 80), intArrayOf(2, 50, 32), intArrayOf(2, 50, 24), intArrayOf(4, 25, 9), intArrayOf(1, 134, 108), intArrayOf(2, 67, 43), intArrayOf(2, 33, 15, 2, 34, 16), intArrayOf(2, 33, 11, 2, 34, 12), intArrayOf(2, 86, 68), intArrayOf(4, 43, 27), intArrayOf(4, 43, 19), intArrayOf(4, 43, 15), intArrayOf(2, 98, 78), intArrayOf(4, 49, 31), intArrayOf(2, 32, 14, 4, 33, 15), intArrayOf(4, 39, 13, 1, 40, 14), intArrayOf(2, 121, 97), intArrayOf(2, 60, 38, 2, 61, 39), intArrayOf(4, 40, 18, 2, 41, 19), intArrayOf(4, 40, 14, 2, 41, 15), intArrayOf(2, 146, 116), intArrayOf(3, 58, 36, 2, 59, 37), intArrayOf(4, 36, 16, 4, 37, 17), intArrayOf(4, 36, 12, 4, 37, 13), intArrayOf(2, 86, 68, 2, 87, 69), intArrayOf(4, 69, 43, 1, 70, 44), intArrayOf(6, 43, 19, 2, 44, 20), intArrayOf(6, 43, 15, 2, 44, 16), intArrayOf(4, 101, 81), intArrayOf(1, 80, 50, 4, 81, 51), intArrayOf(4, 50, 22, 4, 51, 23), intArrayOf(3, 36, 12, 8, 37, 13), intArrayOf(2, 116, 92, 2, 117, 93), intArrayOf(6, 58, 36, 2, 59, 37), intArrayOf(4, 46, 20, 6, 47, 21), intArrayOf(7, 42, 14, 4, 43, 15), intArrayOf(4, 133, 107), intArrayOf(8, 59, 37, 1, 60, 38), intArrayOf(8, 44, 20, 4, 45, 21), intArrayOf(12, 33, 11, 4, 34, 12), intArrayOf(3, 145, 115, 1, 146, 116), intArrayOf(4, 64, 40, 5, 65, 41), intArrayOf(11, 36, 16, 5, 37, 17), intArrayOf(11, 36, 12, 5, 37, 13), intArrayOf(5, 109, 87, 1, 110, 88), intArrayOf(5, 65, 41, 5, 66, 42), intArrayOf(5, 54, 24, 7, 55, 25), intArrayOf(11, 36, 12, 7, 37, 13), intArrayOf(5, 122, 98, 1, 123, 99), intArrayOf(7, 73, 45, 3, 74, 46), intArrayOf(15, 43, 19, 2, 44, 20), intArrayOf(3, 45, 15, 13, 46, 16), intArrayOf(1, 135, 107, 5, 136, 108), intArrayOf(10, 74, 46, 1, 75, 47), intArrayOf(1, 50, 22, 15, 51, 23), intArrayOf(2, 42, 14, 17, 43, 15), intArrayOf(5, 150, 120, 1, 151, 121), intArrayOf(9, 69, 43, 4, 70, 44), intArrayOf(17, 50, 22, 1, 51, 23), intArrayOf(2, 42, 14, 19, 43, 15), intArrayOf(3, 141, 113, 4, 142, 114), intArrayOf(3, 70, 44, 11, 71, 45), intArrayOf(17, 47, 21, 4, 48, 22), intArrayOf(9, 39, 13, 16, 40, 14), intArrayOf(3, 135, 107, 5, 136, 108), intArrayOf(3, 67, 41, 13, 68, 42), intArrayOf(15, 54, 24, 5, 55, 25), intArrayOf(15, 43, 15, 10, 44, 16), intArrayOf(4, 144, 116, 4, 145, 117), intArrayOf(17, 68, 42), intArrayOf(17, 50, 22, 6, 51, 23), intArrayOf(19, 46, 16, 6, 47, 17), intArrayOf(2, 139, 111, 7, 140, 112), intArrayOf(17, 74, 46), intArrayOf(7, 54, 24, 16, 55, 25), intArrayOf(34, 37, 13), intArrayOf(4, 151, 121, 5, 152, 122), intArrayOf(4, 75, 47, 14, 76, 48), intArrayOf(11, 54, 24, 14, 55, 25), intArrayOf(16, 45, 15, 14, 46, 16), intArrayOf(6, 147, 117, 4, 148, 118), intArrayOf(6, 73, 45, 14, 74, 46), intArrayOf(11, 54, 24, 16, 55, 25), intArrayOf(30, 46, 16, 2, 47, 17), intArrayOf(8, 132, 106, 4, 133, 107), intArrayOf(8, 75, 47, 13, 76, 48), intArrayOf(7, 54, 24, 22, 55, 25), intArrayOf(22, 45, 15, 13, 46, 16), intArrayOf(10, 142, 114, 2, 143, 115), intArrayOf(19, 74, 46, 4, 75, 47), intArrayOf(28, 50, 22, 6, 51, 23), intArrayOf(33, 46, 16, 4, 47, 17), intArrayOf(8, 152, 122, 4, 153, 123), intArrayOf(22, 73, 45, 3, 74, 46), intArrayOf(8, 53, 23, 26, 54, 24), intArrayOf(12, 45, 15, 28, 46, 16), intArrayOf(3, 147, 117, 10, 148, 118), intArrayOf(3, 73, 45, 23, 74, 46), intArrayOf(4, 54, 24, 31, 55, 25), intArrayOf(11, 45, 15, 31, 46, 16), intArrayOf(7, 146, 116, 7, 147, 117), intArrayOf(21, 73, 45, 7, 74, 46), intArrayOf(1, 53, 23, 37, 54, 24), intArrayOf(19, 45, 15, 26, 46, 16), intArrayOf(5, 145, 115, 10, 146, 116), intArrayOf(19, 75, 47, 10, 76, 48), intArrayOf(15, 54, 24, 25, 55, 25), intArrayOf(23, 45, 15, 25, 46, 16), intArrayOf(13, 145, 115, 3, 146, 116), intArrayOf(2, 74, 46, 29, 75, 47), intArrayOf(42, 54, 24, 1, 55, 25), intArrayOf(23, 45, 15, 28, 46, 16), intArrayOf(17, 145, 115), intArrayOf(10, 74, 46, 23, 75, 47), intArrayOf(10, 54, 24, 35, 55, 25), intArrayOf(19, 45, 15, 35, 46, 16), intArrayOf(17, 145, 115, 1, 146, 116), intArrayOf(14, 74, 46, 21, 75, 47), intArrayOf(29, 54, 24, 19, 55, 25), intArrayOf(11, 45, 15, 46, 46, 16), intArrayOf(13, 145, 115, 6, 146, 116), intArrayOf(14, 74, 46, 23, 75, 47), intArrayOf(44, 54, 24, 7, 55, 25), intArrayOf(59, 46, 16, 1, 47, 17), intArrayOf(12, 151, 121, 7, 152, 122), intArrayOf(12, 75, 47, 26, 76, 48), intArrayOf(39, 54, 24, 14, 55, 25), intArrayOf(22, 45, 15, 41, 46, 16), intArrayOf(6, 151, 121, 14, 152, 122), intArrayOf(6, 75, 47, 34, 76, 48), intArrayOf(46, 54, 24, 10, 55, 25), intArrayOf(2, 45, 15, 64, 46, 16), intArrayOf(17, 152, 122, 4, 153, 123), intArrayOf(29, 74, 46, 14, 75, 47), intArrayOf(49, 54, 24, 10, 55, 25), intArrayOf(24, 45, 15, 46, 46, 16), intArrayOf(4, 152, 122, 18, 153, 123), intArrayOf(13, 74, 46, 32, 75, 47), intArrayOf(48, 54, 24, 14, 55, 25), intArrayOf(42, 45, 15, 32, 46, 16), intArrayOf(20, 147, 117, 4, 148, 118), intArrayOf(40, 75, 47, 7, 76, 48), intArrayOf(43, 54, 24, 22, 55, 25), intArrayOf(10, 45, 15, 67, 46, 16), intArrayOf(19, 148, 118, 6, 149, 119), intArrayOf(18, 75, 47, 31, 76, 48), intArrayOf(34, 54, 24, 34, 55, 25), intArrayOf(20, 45, 15, 61, 46, 16) ) fun getRSBlocks( typeNumber: Int, errorCorrectionLevel: ErrorCorrectionLevel ): Array = RS_BLOCK_TABLE[(typeNumber - 1) * 4 + errorCorrectionLevel.ordinal] .let { rsBlock -> if (rsBlock.size == 3) { val block = RSBlock(rsBlock[1], rsBlock[2]) Array(rsBlock[0]) { block } } else { val blocksSize = rsBlock[0] + rsBlock[3] val firstBlock = RSBlock(rsBlock[1], rsBlock[2]) val secondBlock = RSBlock(rsBlock[4], rsBlock[5]) Array(blocksSize) { if (it < rsBlock[0]) { firstBlock } else { secondBlock } } } } } } ================================================ FILE: lib/snowfall/.gitignore ================================================ /build ================================================ FILE: lib/snowfall/build.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "com.t8rin.snowfall" ================================================ FILE: lib/snowfall/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/snowfall/src/main/java/com/t8rin/snowfall/Constants.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.snowfall internal object Constants { internal const val angleRange = 0.1f internal const val angleDivisor = 10000.0f internal const val angleSeed = 100.0f internal const val baseFrameDurationMillis = 16 internal const val snowflakeDensity = 0.05 internal const val defaultAlpha = 0.65f internal val incrementRange = 0.2f..0.8f internal val sizeRange = 20f..40f internal val angleSeedRange = -angleSeed..angleSeed } ================================================ FILE: lib/snowfall/src/main/java/com/t8rin/snowfall/SnowAnimState.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.snowfall import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.IntSize import com.t8rin.snowfall.Constants.angleRange import com.t8rin.snowfall.Constants.angleSeed import com.t8rin.snowfall.Constants.incrementRange import com.t8rin.snowfall.Constants.sizeRange import com.t8rin.snowfall.types.AnimType import kotlin.math.PI import kotlin.math.roundToInt internal data class SnowAnimState( var tickNanos: Long, val snowflakes: List, val painters: List, val animType: AnimType, val colors: List, val density: Double, val alpha: Float, ) { constructor( tick: Long, canvasSize: IntSize, painters: List, animType: AnimType, colors: List, density: Double, alpha: Float ) : this( tickNanos = tick, snowflakes = createSnowFlakes( flakesProvider = painters, canvasSize = canvasSize, animType = animType, colors = colors, density = density, alpha = alpha ), painters = painters, animType = animType, colors = colors, density = density, alpha = alpha ) fun draw(contentDrawScope: ContentDrawScope) { snowflakes.forEach { it.draw(contentDrawScope) } } fun resize(newSize: IntSize) = copy( snowflakes = createSnowFlakes( flakesProvider = painters, canvasSize = newSize, animType = animType, colors = colors, density = density, alpha = alpha ) ) companion object { fun createSnowFlakes( flakesProvider: List, canvasSize: IntSize, animType: AnimType, colors: List, density: Double, alpha: Float, ): List = when (animType) { AnimType.Falling -> createFallingSnowflakes( canvasSize = canvasSize, painters = flakesProvider, colors = colors, snowflakeDensity = density, alpha = alpha ) AnimType.Melting -> createMeltingSnowflakes( canvasSize = canvasSize, painters = flakesProvider, colors = colors, snowflakeDensity = density, ) } private fun createMeltingSnowflakes( canvasSize: IntSize, painters: List, colors: List, snowflakeDensity: Double, ): List { if (canvasSize.height == 0 || canvasSize.width == 0 || snowflakeDensity == 0.0) { return emptyList() } val canvasArea = canvasSize.width * canvasSize.height val normalizedDensity = snowflakeDensity.coerceIn(0.0..1.0) / 2000.0 val count = (canvasArea * normalizedDensity).roundToInt() val snowflakesCount = count.coerceIn(painters.size.coerceAtMost(count), count) return List(snowflakesCount) { MeltingSnowflake( incrementFactor = incrementRange.random(), canvasSize = canvasSize, maxAlpha = (0.1f..0.7f).random(), painter = painters[it % painters.size], initialPosition = canvasSize.randomPosition(), color = colors.random(), ) } } private fun createFallingSnowflakes( canvasSize: IntSize, painters: List, colors: List, snowflakeDensity: Double, alpha: Float, ): List { val canvasArea = canvasSize.width * canvasSize.height val normalizedDensity = snowflakeDensity.coerceIn(0.0..1.0) / 1000.0 val snowflakesCount = (canvasArea * normalizedDensity).roundToInt() return List(snowflakesCount) { FallingSnowflake( incrementFactor = incrementRange.random(), size = sizeRange.random(), canvasSize = canvasSize, initialPosition = canvasSize.randomPosition(), angle = angleSeed.random() / angleSeed * angleRange + (PI / 2.0) - (angleRange / 2.0), painter = painters[it % painters.size], color = colors.random(), alpha = alpha, ) } } } } ================================================ FILE: lib/snowfall/src/main/java/com/t8rin/snowfall/Snowfall.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.snowfall import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.IntSize import androidx.lifecycle.Lifecycle import com.t8rin.snowfall.Constants.defaultAlpha import com.t8rin.snowfall.Constants.snowflakeDensity import com.t8rin.snowfall.types.AnimType import com.t8rin.snowfall.types.FlakeType import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlin.time.Duration.Companion.nanoseconds /** * Creates flakes falling animation base on `FlakeType` param. * @param type type of flake. * @param colors list of Colors for Flakes * @param density density of Flakes must be between 0.0 (no Flakes) or 1.0 (a lot of Flakes). Default is 0.05 * @param alpha transparency of the falling flakes * */ fun Modifier.snowfall( type: FlakeType = FlakeType.Snowflakes, colors: List, density: Double = snowflakeDensity, alpha: Float = defaultAlpha, ): Modifier = letItSnow( flakeType = type, animType = AnimType.Falling, colors = colors, density = density, alpha = alpha ) /** * Creates flakes falling animation base on `FlakeType` param. * @param type type of flake. * @param color single Color for Flakes * @param density density of Flakes must be between 0.0 (no Flakes) or 1.0 (a lot of Flakes). Default is 0.05 * @param alpha transparency of the falling flakes * */ fun Modifier.snowfall( type: FlakeType = FlakeType.Snowflakes, color: Color = Color.Unspecified, density: Double = snowflakeDensity, alpha: Float = defaultAlpha, ): Modifier = letItSnow( flakeType = type, animType = AnimType.Falling, colors = listOf(color), density = density, alpha = alpha ) /** * Creates flakes melting animation base on `FlakeType` param. * @param type - type of flake. * @param colors - list of Colors for Flakes * @param density - density of Flakes must be between 0.0 (no Flakes) or 1.0 (a lot of Flakes). Default is 0.05 * */ fun Modifier.snowmelt( type: FlakeType = FlakeType.Snowflakes, colors: List, density: Double = snowflakeDensity, ): Modifier = letItSnow(type, AnimType.Melting, colors = colors, density = density) /** * Creates flakes melting animation base on `FlakeType` param. * @param type - type of flake. * @param color - single Color for Flakes * @param density - density of Flakes must be between 0.0 (no Flakes) or 1.0 (a lot of Flakes). Default is 0.05 * */ fun Modifier.snowmelt( type: FlakeType = FlakeType.Snowflakes, color: Color = Color.Unspecified, density: Double = snowflakeDensity, ): Modifier = letItSnow(type, AnimType.Melting, colors = listOf(color), density = density) private fun Modifier.letItSnow( flakeType: FlakeType, animType: AnimType, colors: List, density: Double, alpha: Float = 1f, ) = composed { val flakes = when (flakeType) { is FlakeType.Custom -> flakeType.data is FlakeType.Snowflakes -> getDefaultFlakes() } var snowAnimState by remember { mutableStateOf( SnowAnimState( tick = -1, canvasSize = IntSize(0, 0), painters = flakes, animType = animType, colors = colors, density = density, alpha = alpha, ) ) } val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current LaunchedEffect(Unit) { while (isActive) { if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { withFrameNanos { frameTimeNanos -> val elapsedMillis = (frameTimeNanos - snowAnimState.tickNanos).nanoseconds.inWholeMilliseconds val isFirstRun = snowAnimState.tickNanos < 0 snowAnimState.tickNanos = frameTimeNanos if (isFirstRun) return@withFrameNanos 0 snowAnimState.snowflakes.forEach { it.update(elapsedMillis) } return@withFrameNanos frameTimeNanos } } else { withFrameNanos { frameTimeNanos -> snowAnimState.tickNanos = frameTimeNanos } } delay(8L) } } onSizeChanged { newSize -> snowAnimState = snowAnimState.resize(newSize) } .clipToBounds() .drawWithContent { drawContent() snowAnimState.draw(this) } } @Composable private fun getDefaultFlakes(): List = listOf( painterResource(id = R.drawable.ic_flake_1), painterResource(id = R.drawable.ic_flake_2), painterResource(id = R.drawable.ic_flake_3), painterResource(id = R.drawable.ic_flake_4), painterResource(id = R.drawable.ic_flake_5), painterResource(id = R.drawable.ic_flake_6), painterResource(id = R.drawable.ic_flake_7), painterResource(id = R.drawable.ic_flake_8), painterResource(id = R.drawable.ic_flake_9), painterResource(id = R.drawable.ic_flake_10), ) ================================================ FILE: lib/snowfall/src/main/java/com/t8rin/snowfall/Snowflake.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.snowfall import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableDoubleStateOf import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.IntSize import com.t8rin.snowfall.Constants.angleDivisor import com.t8rin.snowfall.Constants.angleSeedRange import com.t8rin.snowfall.Constants.baseFrameDurationMillis import kotlin.math.cos import kotlin.math.sin internal interface Snowflake { fun update(elapsedMillis: Long) fun draw(contentDrawScope: ContentDrawScope) } internal class MeltingSnowflake( private val initialPosition: Offset, private val incrementFactor: Float, private val canvasSize: IntSize, private val maxAlpha: Float, private val painter: Painter, private val color: Color, ) : Snowflake { init { require(maxAlpha in 0.1..1.0) } private var position by mutableStateOf(initialPosition) private var alpha by mutableFloatStateOf(0.001f) private var isIncreasing by mutableStateOf(true) override fun update(elapsedMillis: Long) { val increment = incrementFactor * (elapsedMillis / baseFrameDurationMillis) / 100f alpha = (if (isIncreasing) { alpha + increment } else { alpha - increment }).coerceIn(0f, maxAlpha) if (alpha == maxAlpha) { isIncreasing = false } if (alpha == 0f) { isIncreasing = true alpha = 0.001f position = canvasSize.randomPosition() } } override fun draw(contentDrawScope: ContentDrawScope) { with(contentDrawScope) { translate( left = position.x, top = position.y ) { with(painter) { draw( size = intrinsicSize, alpha = alpha, colorFilter = if (color == Color.Unspecified) null else ColorFilter.tint( color ) ) } } } } } internal class FallingSnowflake( private val incrementFactor: Float, private val size: Float, private val canvasSize: IntSize, initialPosition: Offset, angle: Double, private val painter: Painter, private val color: Color, private val alpha: Float, ) : Snowflake { private val baseSpeedPxAt60Fps = 5 private var position by mutableStateOf(initialPosition) private var angle by mutableDoubleStateOf(angle) override fun update(elapsedMillis: Long) { val increment = incrementFactor * (elapsedMillis / baseFrameDurationMillis) * baseSpeedPxAt60Fps val xDelta = (increment * cos(angle)).toFloat() val yDelta = (increment * sin(angle)).toFloat() position = Offset(position.x + xDelta, position.y + yDelta) angle += angleSeedRange.random() / angleDivisor if (position.y > canvasSize.height + size) { position = Offset(canvasSize.width.random().toFloat(), -size - painter.intrinsicSize.height) } } override fun draw(contentDrawScope: ContentDrawScope) { with(contentDrawScope) { translate( position.x, position.y ) { with(painter) { draw( size = intrinsicSize, alpha = alpha, colorFilter = if (color == Color.Unspecified) null else ColorFilter.tint( color ) ) } } } } } ================================================ FILE: lib/snowfall/src/main/java/com/t8rin/snowfall/Utils.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.snowfall import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import java.util.concurrent.ThreadLocalRandom internal fun ClosedRange.random() = ThreadLocalRandom.current().nextFloat() * (endInclusive - start) + start internal fun Float.random() = ThreadLocalRandom.current().nextFloat() * this internal fun Int.random() = ThreadLocalRandom.current().nextInt(this) internal fun IntSize.randomPosition() = Offset(width.random().toFloat(), height.random().toFloat()) ================================================ FILE: lib/snowfall/src/main/java/com/t8rin/snowfall/types/AnimType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.snowfall.types internal enum class AnimType { Falling, Melting } ================================================ FILE: lib/snowfall/src/main/java/com/t8rin/snowfall/types/FlakeType.kt ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ package com.t8rin.snowfall.types import androidx.compose.ui.graphics.painter.Painter /** * Type of flake used for animation. */ sealed interface FlakeType { data object Snowflakes : FlakeType class Custom(val data: List) : FlakeType } ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_1.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_10.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_2.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_3.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_4.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_5.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_6.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_7.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_8.xml ================================================ ================================================ FILE: lib/snowfall/src/main/res/drawable/ic_flake_9.xml ================================================ ================================================ FILE: lib/zoomable/.gitignore ================================================ /build /.idea/ /.idea ================================================ FILE: lib/zoomable/build.gradle.kts ================================================ plugins { alias(libs.plugins.image.toolbox.library) alias(libs.plugins.image.toolbox.compose) } android.namespace = "net.engawapg.lib.zoomable" ================================================ FILE: lib/zoomable/src/main/AndroidManifest.xml ================================================ ================================================ FILE: lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomState.kt ================================================ /* * Copyright 2022 usuiat * * 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. */ package net.engawapg.lib.zoomable import androidx.annotation.FloatRange import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.DecayAnimationSpec import androidx.compose.animation.core.exponentialDecay import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.util.VelocityTracker import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import java.lang.Float.max import kotlin.math.abs /** * A state object that manage scale and offset. * * @param maxScale The maximum scale of the content. * @param contentSize Size of content (i.e. image size.) If Zero, the composable layout size will * be used as content size. * @param velocityDecay The decay animation spec for fling behaviour. */ @Stable class ZoomState( @FloatRange(from = 1.0) val maxScale: Float = 5f, @FloatRange(from = 0.0) val minScale: Float = 1f, private var contentSize: Size = Size.Zero, private val velocityDecay: DecayAnimationSpec = exponentialDecay(), ) { init { require(maxScale >= 1.0f) { "maxScale must be at least 1.0." } require(minScale > 0.0f) { "minScale must be at least 0.0." } } private var _scale = Animatable(minScale).apply { updateBounds(0.9f, maxScale) } /** * The scale of the content. */ val scale: Float get() = _scale.value private var _offsetX = Animatable(0f) /** * The horizontal offset of the content. */ val offsetX: Float get() = _offsetX.value private var _offsetY = Animatable(0f) /** * The vertical offset of the content. */ val offsetY: Float get() = _offsetY.value private var layoutSize = Size.Zero /** * Set composable layout size. * * Basically This function is called from [Modifier.zoomable] only. * * @param size The size of composable layout size. */ fun setLayoutSize(size: Size) { layoutSize = size updateFitContentSize() } /** * Set the content size. * * @param size The content size, for example an image size in pixel. */ fun setContentSize(size: Size) { contentSize = size updateFitContentSize() } private var fitContentSize = Size.Zero private fun updateFitContentSize() { if (layoutSize == Size.Zero) { fitContentSize = Size.Zero return } if (contentSize == Size.Zero) { fitContentSize = layoutSize return } val contentAspectRatio = contentSize.width / contentSize.height val layoutAspectRatio = layoutSize.width / layoutSize.height fitContentSize = if (contentAspectRatio > layoutAspectRatio) { contentSize * (layoutSize.width / contentSize.width) } else { contentSize * (layoutSize.height / contentSize.height) } } /** * Reset the scale and the offsets. */ suspend fun reset() = coroutineScope { launch { _scale.snapTo(minScale) } _offsetX.updateBounds(0f, 0f) launch { _offsetX.snapTo(0f) } _offsetY.updateBounds(0f, 0f) launch { _offsetY.snapTo(0f) } } private val velocityTracker = VelocityTracker() internal fun startGesture() { velocityTracker.resetTracking() } internal fun willChangeOffset(pan: Offset): Boolean { var willChange = true val ratio = (abs(pan.x) / abs(pan.y)) if (ratio > 3) { // Horizontal drag if ((pan.x < 0) && (_offsetX.value == _offsetX.lowerBound)) { // Drag R to L when right edge of the content is shown. willChange = false } if ((pan.x > 0) && (_offsetX.value == _offsetX.upperBound)) { // Drag L to R when left edge of the content is shown. willChange = false } } else if (ratio < 0.33) { // Vertical drag if ((pan.y < 0) && (_offsetY.value == _offsetY.lowerBound)) { // Drag bottom to top when bottom edge of the content is shown. willChange = false } if ((pan.y > 0) && (_offsetY.value == _offsetY.upperBound)) { // Drag top to bottom when top edge of the content is shown. willChange = false } } return willChange } internal suspend fun applyGesture( pan: Offset, zoom: Float, position: Offset, timeMillis: Long ) = coroutineScope { val newScale = (scale * zoom).coerceIn(0.9f, maxScale) val newOffset = calculateNewOffset(newScale, position, pan) val newBounds = calculateNewBounds(newScale) _offsetX.updateBounds(newBounds.left, newBounds.right) launch { _offsetX.snapTo(newOffset.x) } _offsetY.updateBounds(newBounds.top, newBounds.bottom) launch { _offsetY.snapTo(newOffset.y) } launch { _scale.snapTo(newScale) } if (zoom == minScale) { velocityTracker.addPosition(timeMillis, position) } else { velocityTracker.resetTracking() } } /** * Change the scale with animation. * * Zoom in or out to [targetScale] around the [position]. * * @param targetScale The target scale value. * @param position Zoom around this point. * @param animationSpec The animation configuration. */ suspend fun changeScale( targetScale: Float, position: Offset, animationSpec: AnimationSpec = spring(), ) = coroutineScope { val newScale = targetScale.coerceIn(1f, maxScale) val newOffset = calculateNewOffset(newScale, position, Offset.Zero) val newBounds = calculateNewBounds(newScale) val x = newOffset.x.coerceIn(newBounds.left, newBounds.right) launch { _offsetX.updateBounds(null, null) _offsetX.animateTo(x, animationSpec) _offsetX.updateBounds(newBounds.left, newBounds.right) } val y = newOffset.y.coerceIn(newBounds.top, newBounds.bottom) launch { _offsetY.updateBounds(null, null) _offsetY.animateTo(y, animationSpec) _offsetY.updateBounds(newBounds.top, newBounds.bottom) } launch { _scale.animateTo(newScale, animationSpec) } } private fun calculateNewOffset( newScale: Float, position: Offset, pan: Offset, ): Offset { val size = fitContentSize * scale val newSize = fitContentSize * newScale val deltaWidth = newSize.width - size.width val deltaHeight = newSize.height - size.height // Position with the origin at the left top corner of the content. val xInContent = position.x - offsetX + (size.width - layoutSize.width) * 0.5f val yInContent = position.y - offsetY + (size.height - layoutSize.height) * 0.5f // Amount of offset change required to zoom around the position. val deltaX = (deltaWidth * 0.5f) - (deltaWidth * xInContent / size.width) val deltaY = (deltaHeight * 0.5f) - (deltaHeight * yInContent / size.height) val x = offsetX + pan.x + deltaX val y = offsetY + pan.y + deltaY return Offset(x, y) } private fun calculateNewBounds( newScale: Float, ): Rect { val newSize = fitContentSize * newScale val boundX = max((newSize.width - layoutSize.width), 0f) * 0.5f val boundY = max((newSize.height - layoutSize.height), 0f) * 0.5f return Rect(-boundX, -boundY, boundX, boundY) } internal suspend fun startFling() = coroutineScope { val velocity = velocityTracker.calculateVelocity() if (velocity.x != 0f) { launch { _offsetX.animateDecay(velocity.x, velocityDecay) } } if (velocity.y != 0f) { launch { _offsetY.animateDecay(velocity.y, velocityDecay) } } } /** * Animates the centering of content by modifying the offset and scale based on content coordinates. * * @param offset The offset to apply for centering the content. * @param scale The scale to apply for zooming the content. * @param animationSpec AnimationSpec for centering and scaling. */ suspend fun centerByContentCoordinate( offset: Offset, scale: Float = 3f, animationSpec: AnimationSpec = tween(700), ) = coroutineScope { val fitContentSizeFactor = fitContentSize.width / contentSize.width val boundX = max((fitContentSize.width * scale - layoutSize.width), 0f) / 2f val boundY = max((fitContentSize.height * scale - layoutSize.height), 0f) / 2f suspend fun executeZoomWithAnimation() { listOf( async { val fixedTargetOffsetX = ((fitContentSize.width / 2 - offset.x * fitContentSizeFactor) * scale) .coerceIn( minimumValue = -boundX, maximumValue = boundX, ) // Adjust zoom target position to prevent execute zoom animation to out of content boundaries _offsetX.animateTo(fixedTargetOffsetX, animationSpec) }, async { val fixedTargetOffsetY = ((fitContentSize.height / 2 - offset.y * fitContentSizeFactor) * scale) .coerceIn(minimumValue = -boundY, maximumValue = boundY) _offsetY.animateTo(fixedTargetOffsetY, animationSpec) }, async { _scale.animateTo(scale, animationSpec) }, ).awaitAll() } if (scale > _scale.value) { _offsetX.updateBounds(-boundX, boundX) _offsetY.updateBounds(-boundY, boundY) executeZoomWithAnimation() } else { executeZoomWithAnimation() _offsetX.updateBounds(-boundX, boundX) _offsetY.updateBounds(-boundY, boundY) } } /** * Animates the centering of content by modifying the offset and scale based on layout coordinates. * * @param offset The offset to apply for centering the content. * @param scale The scale to apply for zooming the content. * @param animationSpec AnimationSpec for centering and scaling. */ suspend fun centerByLayoutCoordinate( offset: Offset, scale: Float = 3f, animationSpec: AnimationSpec = tween(700), ) = coroutineScope { val boundX = max((fitContentSize.width * scale - layoutSize.width), 0f) / 2f val boundY = max((fitContentSize.height * scale - layoutSize.height), 0f) / 2f suspend fun executeZoomWithAnimation() { listOf( async { val fixedTargetOffsetX = ((layoutSize.width / 2 - offset.x) * scale) .coerceIn( minimumValue = -boundX, maximumValue = boundX, ) // Adjust zoom target position to prevent execute zoom animation to out of content boundaries _offsetX.animateTo(fixedTargetOffsetX, animationSpec) }, async { val fixedTargetOffsetY = ((layoutSize.height / 2 - offset.y) * scale) .coerceIn(minimumValue = -boundY, maximumValue = boundY) _offsetY.animateTo(fixedTargetOffsetY, animationSpec) }, async { _scale.animateTo(scale, animationSpec) }, ).awaitAll() } if (scale > _scale.value) { _offsetX.updateBounds(-boundX, boundX) _offsetY.updateBounds(-boundY, boundY) executeZoomWithAnimation() } else { executeZoomWithAnimation() _offsetX.updateBounds(-boundX, boundX) _offsetY.updateBounds(-boundY, boundY) } } } /** * Creates a [ZoomState] that is remembered across compositions. * * @param maxScale The maximum scale of the content. * @param minScale The minimum scale of the content. * @param contentSize Size of content (i.e. image size.) If Zero, the composable layout size will * be used as content size. * @param velocityDecay The decay animation spec for fling behaviour. * @param key remembering composition key */ @Composable fun rememberZoomState( @FloatRange(from = 1.0) maxScale: Float = 5f, @FloatRange(from = 0.0) minScale: Float = 1f, contentSize: Size = Size.Zero, velocityDecay: DecayAnimationSpec = exponentialDecay(), key: Any? = null ) = remember(key) { ZoomState( maxScale = maxScale, minScale = minScale, contentSize = contentSize, velocityDecay = velocityDecay ) } ================================================ FILE: lib/zoomable/src/main/java/net/engawapg/lib/zoomable/Zoomable.kt ================================================ /* * Copyright 2022 usuiat * * 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. */ package net.engawapg.lib.zoomable import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.spring import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.calculateCentroid import androidx.compose.foundation.gestures.calculatePan import androidx.compose.foundation.gestures.calculateZoom import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode import androidx.compose.ui.input.pointer.positionChanged import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.node.DelegatingNode import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.PointerInputModifierNode import androidx.compose.ui.platform.InspectorInfo import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.toSize import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastForEach import kotlinx.coroutines.launch /** * Customized transform gesture detector. * * A caller of this function can choose if the pointer events will be consumed. * And the caller can implement [onGestureStart] and [onGestureEnd] event. * * @param canConsumeGesture Lambda that asks the caller whether the gesture can be consumed. * @param onGesture This lambda is called when [canConsumeGesture] returns true. * @param onGestureStart This lambda is called when a gesture starts. * @param onGestureEnd This lambda is called when a gesture ends. * @param onTap will be called when single tap is detected. * @param onDoubleTap will be called when double tap is detected. * @param enableOneFingerZoom If true, enable one finger zoom gesture, double tap followed by * vertical scrolling. */ private suspend fun PointerInputScope.detectTransformGestures( cancelIfZoomCanceled: Boolean, canConsumeGesture: (pan: Offset, zoom: Float) -> Boolean, onGesture: (centroid: Offset, pan: Offset, zoom: Float, timeMillis: Long) -> Unit, onGestureStart: () -> Unit = {}, onGestureEnd: () -> Unit = {}, onTap: (position: Offset) -> Unit = {}, onDoubleTap: (position: Offset) -> Unit = {}, enableOneFingerZoom: Boolean = true, ) = awaitEachGesture { val firstDown = awaitFirstDown(requireUnconsumed = false) onGestureStart() var firstUp: PointerInputChange = firstDown var hasMoved = false var isMultiTouch = false var isLongPressed = false var isCanceled = false forEachPointerEventUntilReleased( onCancel = { isCanceled = true }, ) { event, isTouchSlopPast -> if (isTouchSlopPast) { val zoomChange = event.calculateZoom() val panChange = event.calculatePan() if (zoomChange != 1f || panChange != Offset.Zero) { val centroid = event.calculateCentroid(useCurrent = true) val timeMillis = event.changes[0].uptimeMillis if (canConsumeGesture(panChange, zoomChange)) { onGesture(centroid, panChange, zoomChange, timeMillis) event.consumePositionChanges() } } hasMoved = true } if (event.changes.size > 1) { isMultiTouch = true } firstUp = event.changes[0] val cancelGesture = cancelIfZoomCanceled && isMultiTouch && event.changes.size == 1 !cancelGesture } if (firstUp.uptimeMillis - firstDown.uptimeMillis > viewConfiguration.longPressTimeoutMillis) { isLongPressed = true } val isTap = !hasMoved && !isMultiTouch && !isLongPressed && !isCanceled // Vertical scrolling following a double tap is treated as a zoom gesture. if (isTap) { val secondDown = awaitSecondDown(firstUp) if (secondDown == null) { onTap(firstUp.position) } else { var isDoubleTap = true var isSecondCanceled = false var secondUp: PointerInputChange = secondDown forEachPointerEventUntilReleased( onCancel = { isSecondCanceled = true } ) { event, isTouchSlopPast -> if (isTouchSlopPast) { if (enableOneFingerZoom) { val panChange = event.calculatePan() val zoomChange = 1f + panChange.y * 0.004f if (zoomChange != 1f) { val centroid = event.calculateCentroid(useCurrent = true) val timeMillis = event.changes[0].uptimeMillis if (canConsumeGesture(Offset.Zero, zoomChange)) { onGesture(centroid, Offset.Zero, zoomChange, timeMillis) event.consumePositionChanges() } } } isDoubleTap = false } if (event.changes.size > 1) { isDoubleTap = false } secondUp = event.changes[0] true } if (secondUp.uptimeMillis - secondDown.uptimeMillis > viewConfiguration.longPressTimeoutMillis) { isDoubleTap = false } if (isDoubleTap && !isSecondCanceled) { onDoubleTap(secondUp.position) } } } onGestureEnd() } /** * Invoke action for each PointerEvent until all pointers are released. * * @param onCancel Callback function that will be called if PointerEvents is consumed by other composable. * @param action Callback function that will be called every PointerEvents occur. */ private suspend fun AwaitPointerEventScope.forEachPointerEventUntilReleased( onCancel: () -> Unit, action: (event: PointerEvent, isTouchSlopPast: Boolean) -> Boolean, ) { val touchSlop = TouchSlop(viewConfiguration.touchSlop) do { val mainEvent = awaitPointerEvent(pass = PointerEventPass.Main) if (mainEvent.changes.fastAny { it.isConsumed }) { break } val isTouchSlopPast = touchSlop.isPast(mainEvent) val canContinue = action(mainEvent, isTouchSlopPast) if (!canContinue) { break } if (isTouchSlopPast) { continue } val finalEvent = awaitPointerEvent(pass = PointerEventPass.Final) if (finalEvent.changes.fastAny { it.isConsumed }) { onCancel() break } } while (mainEvent.changes.fastAny { it.pressed }) } /** * Await second down or timeout from first up * * @param firstUp The first up event * @return If the second down event comes before timeout, returns it. If not, returns null. */ private suspend fun AwaitPointerEventScope.awaitSecondDown( firstUp: PointerInputChange ): PointerInputChange? = withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) { val minUptime = firstUp.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis var change: PointerInputChange // The second tap doesn't count if it happens before DoubleTapMinTime of the first tap do { change = awaitFirstDown() } while (change.uptimeMillis < minUptime) change } /** * Consume event if the position is changed. */ private fun PointerEvent.consumePositionChanges() { changes.fastForEach { if (it.positionChanged()) { it.consume() } } } /** * Touch slop detector. * * This class holds accumulated zoom and pan value to see if touch slop is past. * * @param threshold Threshold of movement of gesture after touch down. If the movement exceeds this * value, it is judged to be a swipe or zoom gesture. */ private class TouchSlop(private val threshold: Float) { private var pan = Offset.Zero private var _isPast = false /** * Judge the touch slop is past. * * @param event Event that occurs this time. * @return True if the accumulated zoom or pan exceeds the threshold. */ fun isPast(event: PointerEvent): Boolean { if (_isPast) { return true } if (event.changes.size > 1) { // If there are two or more fingers, we determine the touch slop is past immediately. _isPast = true } else { pan += event.calculatePan() _isPast = pan.getDistance() > threshold } return _isPast } } /** * [ScrollGesturePropagation] defines when [Modifier.zoomable] propagates scroll gestures to the * parent composable element. */ enum class ScrollGesturePropagation { /** * Propagates the scroll gesture to the parent composable element when the content is scrolled * to the edge and attempts to scroll further. */ ContentEdge, /** * Propagates the scroll gesture to the parent composable element when the content is not zoomed. */ NotZoomed, } /** * Modifier function that make the content zoomable. * * @param zoomState A [ZoomState] object. * @param zoomEnabled specifies if zoom behaviour is enabled or disabled. Even if this is false, * [onTap] and [onDoubleTap] will be called. * @param enableOneFingerZoom If true, enable one finger zoom gesture, double tap followed by * vertical scrolling. * @param scrollGesturePropagation specifies when scroll gestures are propagated to the parent * composable element. * @param onTap will be called when single tap is detected on the element. * @param onDoubleTap will be called when double tap is detected on the element. This is a suspend * function and called in a coroutine scope. The default is to toggle the scale between 1.0f and * 2.5f with animation. */ fun Modifier.zoomable( zoomState: ZoomState, zoomEnabled: Boolean = true, enableOneFingerZoom: Boolean = true, scrollGesturePropagation: ScrollGesturePropagation = ScrollGesturePropagation.ContentEdge, onTap: (position: Offset) -> Unit = {}, onDoubleTap: suspend (position: Offset) -> Unit = { position -> if (zoomEnabled) zoomState.toggleScale( 5f, position ) }, ): Modifier = this then ZoomableElement( zoomState = zoomState, zoomEnabled = zoomEnabled, enableOneFingerZoom = enableOneFingerZoom, snapBackEnabled = false, scrollGesturePropagation = scrollGesturePropagation, onTap = onTap, onDoubleTap = onDoubleTap, ) fun Modifier.snapBackZoomable( zoomState: ZoomState, zoomEnabled: Boolean = true, onTap: (position: Offset) -> Unit = {}, onDoubleTap: suspend (position: Offset) -> Unit = {}, ): Modifier = this then ZoomableElement( zoomState = zoomState, zoomEnabled = zoomEnabled, enableOneFingerZoom = false, snapBackEnabled = true, scrollGesturePropagation = ScrollGesturePropagation.NotZoomed, onTap = onTap, onDoubleTap = onDoubleTap, ) private data class ZoomableElement( val zoomState: ZoomState, val zoomEnabled: Boolean, val enableOneFingerZoom: Boolean, val snapBackEnabled: Boolean, val scrollGesturePropagation: ScrollGesturePropagation, val onTap: (position: Offset) -> Unit, val onDoubleTap: suspend (position: Offset) -> Unit, ) : ModifierNodeElement() { override fun create(): ZoomableNode = ZoomableNode( zoomState = zoomState, zoomEnabled = zoomEnabled, enableOneFingerZoom = enableOneFingerZoom, snapBackEnabled = snapBackEnabled, scrollGesturePropagation = scrollGesturePropagation, onTap = onTap, onDoubleTap = onDoubleTap, ) override fun update(node: ZoomableNode) { node.update( zoomState = zoomState, zoomEnabled = zoomEnabled, enableOneFingerZoom = enableOneFingerZoom, snapBackEnabled = snapBackEnabled, scrollGesturePropagation = scrollGesturePropagation, onTap = onTap, onDoubleTap = onDoubleTap, ) } override fun InspectorInfo.inspectableProperties() { name = "zoomable" properties["zoomState"] = zoomState properties["zoomEnabled"] = zoomEnabled properties["enableOneFingerZoom"] = enableOneFingerZoom properties["snapBackEnabled"] = snapBackEnabled properties["scrollGesturePropagation"] = scrollGesturePropagation properties["onTap"] = onTap properties["onDoubleTap"] = onDoubleTap } } private class ZoomableNode( var zoomState: ZoomState, var zoomEnabled: Boolean, var enableOneFingerZoom: Boolean, var snapBackEnabled: Boolean, var scrollGesturePropagation: ScrollGesturePropagation, var onTap: (position: Offset) -> Unit, var onDoubleTap: suspend (position: Offset) -> Unit, ) : PointerInputModifierNode, LayoutModifierNode, DelegatingNode() { var measuredSize = Size.Zero fun update( zoomState: ZoomState, zoomEnabled: Boolean, enableOneFingerZoom: Boolean, snapBackEnabled: Boolean, scrollGesturePropagation: ScrollGesturePropagation, onTap: (position: Offset) -> Unit, onDoubleTap: suspend (position: Offset) -> Unit, ) { if (this.zoomState != zoomState) { zoomState.setLayoutSize(measuredSize) this.zoomState = zoomState } this.zoomEnabled = zoomEnabled this.enableOneFingerZoom = enableOneFingerZoom this.scrollGesturePropagation = scrollGesturePropagation this.snapBackEnabled = snapBackEnabled this.onTap = onTap this.onDoubleTap = onDoubleTap } val pointerInputNode = delegate( SuspendingPointerInputModifierNode { detectTransformGestures( cancelIfZoomCanceled = snapBackEnabled, onGestureStart = { resetConsumeGesture() zoomState.startGesture() }, canConsumeGesture = { pan, zoom -> zoomEnabled && canConsumeGesture(pan, zoom) }, onGesture = { centroid, pan, zoom, timeMillis -> if (zoomEnabled) { coroutineScope.launch { zoomState.applyGesture( pan = pan, zoom = zoom, position = centroid, timeMillis = timeMillis, ) } } }, onGestureEnd = { coroutineScope.launch { if (snapBackEnabled || zoomState.scale < 1f) { zoomState.changeScale(1f, Offset.Zero) } else { zoomState.startFling() } } }, onTap = onTap, onDoubleTap = { position -> coroutineScope.launch { onDoubleTap(position) } }, enableOneFingerZoom = enableOneFingerZoom, ) } ) private var consumeGesture: Boolean? = null private fun resetConsumeGesture() { consumeGesture = null } private fun canConsumeGesture(pan: Offset, zoom: Float): Boolean { val currentValue = consumeGesture if (currentValue != null) { return currentValue } val newValue = when { zoom != 1f -> true zoomState.scale == 1f -> false scrollGesturePropagation == ScrollGesturePropagation.NotZoomed -> true else -> zoomState.willChangeOffset(pan) } consumeGesture = newValue return newValue } override fun onPointerEvent( pointerEvent: PointerEvent, pass: PointerEventPass, bounds: IntSize ) { pointerInputNode.onPointerEvent(pointerEvent, pass, bounds) } override fun onCancelPointerInput() { pointerInputNode.onCancelPointerInput() } override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val placeable = measurable.measure(constraints) measuredSize = IntSize(placeable.measuredWidth, placeable.measuredHeight).toSize() zoomState.setLayoutSize(measuredSize) return layout(placeable.width, placeable.height) { placeable.placeWithLayer(x = 0, y = 0) { scaleX = zoomState.scale scaleY = zoomState.scale translationX = zoomState.offsetX translationY = zoomState.offsetY } } } } /** * Toggle the scale between [targetScale] and 1.0f. * * @param targetScale Scale to be set if this function is called when the scale is 1.0f. * @param position Zoom around this point. * @param animationSpec The animation configuration. */ suspend fun ZoomState.toggleScale( targetScale: Float, position: Offset, animationSpec: AnimationSpec = spring(), ) { val newScale = if (scale == minScale) targetScale else minScale changeScale(newScale, position, animationSpec) } ================================================ FILE: lib/zoomable/src/main/java/net/engawapg/lib/zoomable/ZoomableDefaults.kt ================================================ package net.engawapg.lib.zoomable import androidx.compose.ui.geometry.Offset object ZoomableDefaults { val ZoomState.defaultZoomOnDoubleTap: suspend (position: Offset) -> Unit get() = { position -> toggleScale(targetScale = 5f, position = position) } val ZoomState.threeLevelZoomOnDoubleTap: suspend (position: Offset) -> Unit get() = { position -> val scale = scale val minScale = minScale val maxScale = maxScale val midScale = (maxScale - minScale) / 2f val newScale = when (scale) { in minScale.. (maxScale - minScale) / 2f in midScale.. maxScale else -> minScale } changeScale(newScale, position) } val DefaultEnabled: (Float, Offset) -> Boolean get() = { _, _ -> true } } ================================================ FILE: settings.gradle.kts ================================================ /* * ImageToolbox is an image editor for android * Copyright (c) 2026 T8RIN (Malik Mukhametzyanov) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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. * * You should have received a copy of the Apache License * along with this program. If not, see . */ @file:Suppress("UnstableApiUsage") pluginManagement { repositories { includeBuild("build-logic") gradlePluginPortal() google { content { includeGroupByRegex("com\\.android.*") includeGroupByRegex("com\\.google.*") includeGroupByRegex("androidx.*") } } mavenCentral() maven("https://jitpack.io") { name = "JitPack" } } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google { content { includeGroupByRegex("com\\.android.*") includeGroupByRegex("com\\.google.*") includeGroupByRegex("androidx.*") } } maven("https://androidx.dev/storage/compose-compiler/repository") { name = "Compose Compiler Snapshots" content { includeGroup("androidx.compose.compiler") } } mavenCentral() maven("https://jitpack.io") { name = "JitPack" } maven("https://oss.sonatype.org/content/repositories/snapshots/") { name = "Sonatype Snapshots" mavenContent { snapshotsOnly() } } } } enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") rootProject.name = "ImageToolbox" include(":app") include(":feature:main") include(":feature:pick-color") include(":feature:image-stitch") include(":core:filters") include(":feature:filters") include(":feature:draw") include(":feature:erase-background") include(":feature:single-edit") include(":feature:pdf-tools") include(":feature:resize-convert") include(":feature:palette-tools") include(":feature:delete-exif") include(":feature:compare") include(":feature:weight-resize") include(":feature:image-preview") include(":feature:cipher") include(":feature:limits-resize") include(":feature:crop") include(":feature:load-net-image") include(":feature:recognize-text") include(":feature:watermarking") include(":feature:gradient-maker") include(":feature:gif-tools") include(":feature:apng-tools") include(":feature:zip") include(":feature:jxl-tools") include(":feature:media-picker") include(":feature:quick-tiles") include(":feature:settings") include(":feature:easter-egg") include(":feature:svg-maker") include(":feature:format-conversion") include(":feature:document-scanner") include(":feature:scan-qr-code") include(":feature:image-stacking") include(":feature:image-splitting") include(":feature:color-tools") include(":feature:webp-tools") include(":feature:noise-generation") include(":feature:collage-maker") include(":feature:libraries-info") include(":feature:markup-layers") include(":feature:base64-tools") include(":feature:checksum-tools") include(":feature:mesh-gradients") include(":feature:edit-exif") include(":feature:image-cutting") include(":feature:audio-cover-extractor") include(":feature:library-details") include(":feature:wallpapers-export") include(":feature:ascii-art") include(":feature:ai-tools") include(":feature:color-library") include(":feature:root") include(":core:settings") include(":core:resources") include(":core:data") include(":core:domain") include(":core:ui") include(":core:di") include(":core:crash") include(":core:ksp") include(":core:utils") include(":lib:neural-tools") include(":lib:collages") include(":lib:ascii") include(":lib:opencv-tools") include(":lib:documentscanner") include(":lib:snowfall") include(":lib:curves") include(":lib:dynamic-theme") include(":lib:palette") include(":lib:modalsheet") include(":lib:qrose") include(":lib:cropper") include(":lib:colors") include(":lib:gesture") include(":lib:image") include(":lib:zoomable") include(":benchmark")